[
  {
    "path": ".appveyor.yml",
    "content": "version: build-{build}\n\nenvironment:\n  global:\n    PLATFORMTOOLSET: \"v140\"\n    CMAKEPREFIXPATH: \".\"\n    SEVENZIP: \"C:\\\\Program Files\\\\7-Zip\\\\7z.exe\"\n    WGET_BIN: \"C:\\\\msys64\\\\usr\\\\bin\\\\wget.exe\"\n    APPVEYOR_BUILD_WORKER_IMAGE: \"Visual Studio 2015\"\n  matrix:\n    - BUILD_TYPE: MinSizeRel\n      COMPILER: MinGW-w32\n      COMPILER_FAMILY: MinGW\n      GENERATOR: \"MinGW Makefiles\"\n      PLATFORM: Win32\n      CMAKEPREFIXPATH: \"C:/Qt/Tools/mingw730_32\"\n      TOOLCHAIN_BIN: \"C:\\\\Qt\\\\Tools\\\\mingw730_32\\\\bin\"\n    - BUILD_TYPE: MinSizeRel\n      COMPILER: MinGW-w64\n      COMPILER_FAMILY: MinGW\n      GENERATOR: \"MinGW Makefiles\"\n      PLATFORM: x64\n      CMAKEPREFIXPATH: \"C:/Qt/Tools/mingw730_64\"\n      TOOLCHAIN_BIN: \"C:\\\\Qt\\\\Tools\\\\mingw730_64\\\\bin\"\n\nbuild_script:\n  - git submodule init\n  - git submodule update\n  - md b\n  - cd b\n  - if NOT [%TOOLCHAIN_BIN%]==[] set PATH=%TOOLCHAIN_BIN%;%PATH:C:\\Program Files\\Git\\usr\\bin;=%\n  - cmake -G \"%GENERATOR%\" -DCPACK_PACKAGE_FILE_NAME=thextech-%APPVEYOR_REPO_BRANCH%-%COMPILER%-%BUILD_TYPE%-%PLATFORM% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_PREFIX_PATH=%CMAKEPREFIXPATH% ..\n  - cmake --build . --config %BUILD_TYPE% -- -j 2\n  - cpack ..\n  - appveyor PushArtifact \"thextech-%APPVEYOR_REPO_BRANCH%-%COMPILER%-%BUILD_TYPE%-%PLATFORM%.7z\"\n\ndeploy:\n  - provider: Environment\n    name: WohlnetFTP\n\n#on_finish:\n#  - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))\n\n"
  },
  {
    "path": ".gitattributes",
    "content": "changelog.txt       text eol=crlf\nLICENSE             text eol=crlf\nREADME.md           text eol=crlf\n*.lvl               text eol=crlf\n*.wld               text eol=crlf\n*.lvlx              text eol=lf\n*.wldx              text eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "content": "name: Bug Report\ndescription: Create a report of the issue ocurred with the game during its use\nlabels: [\"NEW-BUG\"]\nprojects: [\"TheXTech/1\"]\n\nbody:\n  - type: textarea\n    id: description\n    attributes:\n      label: Describe the issue\n      description: A clear and concise description of what the bug.\n    validations:\n      required: true\n\n  - type: dropdown\n    id: bug_type\n    attributes:\n      label: A type of bug\n      description: |\n        How you would classify this bug by yourself?\n        * **Native bug** - The own bug of TheXTech itself that was never happen in the original SMBX.\n        * **Vanilla bug** - The bug of original SMBX that got being inherited by TheXTech during its initial creation.\n        * **Compatibility bug** - The deviation of behaviour from the original even with the strict compatibility mode (run the game with `--compat-level smbx13` command-line argument).\n\n        **Note:** The development team can classify this issue in a different way depending on the result of the analysis.\n      options:\n        - [Not specified]\n        - Native bug\n        - Vanilla bug\n        - Compatibility bug\n    validations:\n      required: true\n\n  - type: dropdown\n    id: frequency\n    attributes:\n      label: Does this issue happens always or randomly?\n      description: Is it possible to reproduce this issue easily, or it happens very rare?\n      options:\n        - Always happens\n        - Happens randomly, often\n        - Happens randomly, rare\n        - Happens very rare\n    validations:\n      required: true\n\n  - type: input\n    id: version\n    attributes:\n      label: Version\n      description: What version of TheXtech you are using? You can see it in the title of the window.\n    validations:\n      required: true\n\n  - type: input\n    id: version_hash\n    attributes:\n      label: Version Hash\n      description: If you run a **DEVEL** version, please also tell the hash tag (like `#1A2BC3E`) that can be found in the window title or in the main menu at the right-bottom of the screen.\n    validations:\n      required: false\n\n  - type: dropdown\n    id: os\n    attributes:\n      label: Platform\n      description: What the platform where you run the game?\n      options:\n        - [Not specified]\n        - Linux\n        - Windows\n        - macOS\n        - Android\n        - Haiku\n        - xBSD\n        - Web-browser (Emscripten)\n        - 3DS\n        - Wii\n        - Wii U\n        - Switch\n        - PS Vita\n        - Other (tell in the bug description)\n    validations:\n      required: true\n\n  - type: dropdown\n    id: cpu\n    attributes:\n      label: Processor architecture\n      description: |\n        Which architecture your processor where you ran the game have?\n\n        * If you run the **Windows**, you can easily check your architecture (x86 32-bit, 64-bit, or \"64-bit processor ARM\"), you can open the properties of the \"This computer\" / \"My computer\".\n        * On **Linux** / **xBSD**, you can open the terminal and run the `uname -m` command to get the exact processor architecture.\n        * On **macOS**, just can open the \"About this Mac\" to see the architecture in the description. If you see in description such as 32-bit \"Power PC\" (PPC G3, PPC G4) or PPC G5 which is 64-bit Power PC, **Intel Core Solo, Core Duo (not 2 Duo!)** which are 32-bit i386, **Intel Core 2 Duo, Quad-Core, i3/i5/i7** which are 64-bit x86_64, and the **Apple M1/M2/M3/etc.** which is an ARM64).\n        * On **Android** you might want to use program such as AIDA64 where you can open the \"CPU\" list, and find the \"Instructions set\" paragraph where you can see exact processor architecture that your device runs.\n\n        **Note:** Some platforms do have one single architecture only, for example:\n        * **PSP** has the MIPS32.\n        * **3DS** and **Vita** has the ARM32/ARMv7.\n        * **Wii** and **Wii U** has the PPC32.\n        * **Switch** has the ARM64/AARCH64.\n      options:\n        - [Not specified / I don't know]\n        - x86_64 / x64 (64-bit x86)\n        - i386 / x86_32 (32-bit x86)\n        - ARM64 / AARCH64 (64-bit ARM)\n        - ARM32 / ARMv7 (32-bit ARM)\n        - PPC64LE (64-bit Power PC with Little-Endian)\n        - PPC64 (64-bit Power PC with Big-Endian)\n        - PPC32 (32-bit Power PC with Big-Endian)\n        - MIPS64 (32-bit MIPS)\n        - MIPS32 (64-bit MIPS)\n        - RISC-V\n        - WebAssembly (Emscripten)\n        - Other (tell in the bug description)\n    validations:\n      required: false\n\n  - type: textarea\n    id: log_file\n    attributes:\n      label: Log file (if presented)\n      description: |\n        Please upload the log file that have the `TheXTech_log_YYYY_MM_DD_HH_mm_ss.txt` name which can be found in the `logs` sub-directory in the game's user directory.\n    validations:\n      required: false\n\n  - type: textarea\n    id: example_case\n    attributes:\n      label: Example Case\n      description: |\n        Post a link to the level where the issue occurs:\n        *levelname.lvl* from `[Episode name](https://link.to.episode/)`\n        Even better: If you make a dedicated test level and attach it to your GitHub Post (pack it as `.zip` first, and then drag it and drop into this text area).\n    validations:\n      required: false\n\n  - type: textarea\n    id: recording\n    attributes:\n      label: Recording\n      description: Embed or add a link to a Recording here. If it's an in-game issue, you can use TheXTech's built-in GIF Recorder (Press F11 or F10 on macOS). If it's a crash, use a Screen Capture program such as OBS or vokoscreen.\n    validations:\n      required: false\n\n  - type: textarea\n    id: vanilla_recording\n    attributes:\n      label: Vanilla Recording\n      description: Not required, but if you can run SMBX 1.3 (or [use our Research Version](https://github.com/Wohlstand/smbx-experiments/releases) that will work better on Linux under Wine and has the built-in GIF recorder using the F11 key), it would be appreciated if you made a recording of whether the bug occurs here as well.\n    validations:\n      required: false\n\n  - type: textarea\n    id: misc_info\n    attributes:\n      label: Additional context\n      description: Add any other context that could be useful for solving the problem here. If your problem is tied to your Operating System, add it at the beginning of the Title (\"[Windows] Windows Issue\").\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/compile-fail-report.yml",
    "content": "name: Build/Compilation failure report\ndescription: Create a report of the problems to compile the game from the source code\nlabels: [\"NEW-BUG\"]\nprojects: [\"TheXTech/1\"]\n\nbody:\n  - type: textarea\n    id: description\n    attributes:\n      label: Describe the issue\n      description: A clear and concise description of what the bug.\n    validations:\n      required: true\n\n  - type: input\n    id: version\n    attributes:\n      label: Version\n      description: What version of TheXtech you are using? Do you trying to build the stable version or the latest state of the GIT?\n    validations:\n      required: true\n\n  - type: input\n    id: version_hash\n    attributes:\n      label: Version Hash\n      description: If you build the game from the source source code clonned from the GIT, retrieve the hash of the commit where your current state is.\n    validations:\n      required: false\n\n  - type: dropdown\n    id: os_host\n    attributes:\n      label: Build platform\n      description: What the platform where you build the game?\n      options:\n        - [Not specified]\n        - Linux\n        - Windows\n        - macOS\n        - Android\n        - Haiku\n        - xBSD\n        - Other (tell in the bug description)\n    validations:\n      required: true\n\n  - type: dropdown\n    id: os_target\n    attributes:\n      label: Target platform\n      description: For which platform you trying to build the game?\n      options:\n        - [Not specified]\n        - Linux\n        - Windows\n        - macOS\n        - Android\n        - Haiku\n        - xBSD\n        - Web-browser (Emscripten)\n        - 3DS\n        - Wii\n        - Wii U\n        - Switch\n        - PS Vita\n        - Other (tell in the bug description)\n    validations:\n      required: true\n\n  - type: dropdown\n    id: cpu\n    attributes:\n      label: Target processor architecture\n      description: Which target architecture for which you attempted to build the game?\n      options:\n        - [Not specified]\n        - x86_64 / x64 (64-bit x86)\n        - i386 / x86_32 (32-bit x86)\n        - ARM64 / AARCH64 (64-bit ARM)\n        - ARM32 / ARMv7 (32-bit ARM)\n        - PPC64LE (64-bit Power PC with Little-Endian)\n        - PPC64 (64-bit Power PC with Big-Endian)\n        - PPC32 (32-bit Power PC with Big-Endian)\n        - MIPS64 (32-bit MIPS)\n        - MIPS32 (64-bit MIPS)\n        - RISC-V\n        - Other (tell in the bug description)\n    validations:\n      required: true\n\n  - type: dropdown\n    id: compiler\n    attributes:\n      label: Compiler name\n      description: What the compiler you used to build the game?\n      options:\n        - [Not specified]\n        - GCC\n        - Clang\n        - MSVC\n        - Intel-CC\n        - ARM-CC\n        - Other (tell in the bug description)\n    validations:\n      required: true\n\n  - type: textarea\n    id: build_log\n    attributes:\n      label: Build log\n      description: |\n        Please save your full build output log into a text file and upload it here.\n    validations:\n      required: false\n\n  - type: textarea\n    id: build_commands\n    attributes:\n      label: What commands you typed in the terminal or what you did in your IDE?\n      description: |\n        Explain step-by-step what you did before you got an error.\n    validations:\n      required: false\n\n  - type: textarea\n    id: misc_info\n    attributes:\n      label: Additional context\n      description: Add any other context that could be useful for solving the problem here. If your problem is tied to your Operating System, add it at the beginning of the Title (\"[Windows] Windows Issue\").\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ci-helper/.gitignore",
    "content": ".idea/"
  },
  {
    "path": ".github/ci-helper/create-dmg.sh",
    "content": "#!/bin/bash\n\n# Create a read-only disk image of the conkmtents of a folder\n\nset -e\n\nfunction pure_version() {\n    echo '1.0.1.0'\n}\n\nfunction version() {\n    echo \"create-dmg $(pure_version)\"\n}\n\nfunction usage() {\n    version\n    echo \"Creates a fancy DMG file.\"\n    echo \"Usage:  $(basename $0) options... image.dmg source_folder\"\n    echo \"All contents of source_folder will be copied into the disk image.\"\n    echo \"Options:\"\n    echo \"  --volname name\"\n    echo \"      set volume name (displayed in the Finder sidebar and window title)\"\n    echo \"  --volicon icon.icns\"\n    echo \"      set volume icon\"\n    echo \"  --background pic.png\"\n    echo \"      set folder background image (provide png, gif, jpg)\"\n    echo \"  --window-pos x y\"\n    echo \"      set position the folder window\"\n    echo \"  --window-size width height\"\n    echo \"      set size of the folder window\"\n    echo \"  --icon-size icon_size\"\n    echo \"      set window icons size (up to 128)\"\n    echo \"  --icon file_name x y\"\n    echo \"      set position of the file's icon\"\n    echo \"  --hide-extension file_name\"\n    echo \"      hide the extension of file\"\n    echo \"  --custom-icon file_name custom_icon_or_sample_file x y\"\n    echo \"      set position and custom icon\"\n    echo \"  --app-drop-link x y\"\n    echo \"      make a drop link to Applications, at location x,y\"\n    echo \"  --eula eula_file\"\n    echo \"      attach a license file to the dmg\"\n    echo \"  --no-internet-enable\"\n    echo \"      disable automatic mount&copy\"\n    echo \"  --subfolder\"\n    echo \"      put all content of source folder into sub-folder\"\n    echo \"  --version         show tool version number\"\n    echo \"  -h, --help        display this help\"\n    exit 0\n}\n\nWINX=10\nWINY=60\nWINW=500\nWINH=350\nICON_SIZE=128\n\nwhile test \"${1:0:1}\" = \"-\"; do\n    case $1 in\n    --volname)\n        VOLUME_NAME=\"$2\"\n        shift\n        shift\n        ;;\n    --volicon)\n        VOLUME_ICON_FILE=\"$2\"\n        shift\n        shift\n        ;;\n    --background)\n        BACKGROUND_FILE=\"$2\"\n        BACKGROUND_FILE_NAME=\"$(basename $BACKGROUND_FILE)\"\n        BACKGROUND_CLAUSE=\"set background picture of opts to file \\\".background:$BACKGROUND_FILE_NAME\\\"\"\n        shift\n        shift\n        ;;\n    --icon-size)\n        ICON_SIZE=\"$2\"\n        shift\n        shift\n        ;;\n    --window-pos)\n        WINX=$2\n        WINY=$3\n        shift\n        shift\n        shift\n        ;;\n    --window-size)\n        WINW=$2\n        WINH=$3\n        shift\n        shift\n        shift\n        ;;\n    --icon)\n        POSITION_CLAUSE=\"${POSITION_CLAUSE}set position of item \\\"$2\\\" to {$3, $4}\n\"\n        shift\n        shift\n        shift\n        shift\n        ;;\n    --hide-extension)\n        HIDING_CLAUSE=\"${HIDING_CLAUSE}set the extension hidden of item \\\"$2\\\" to true\"\n        shift\n        shift\n        ;;\n    --custom-icon)\n        shift\n        shift\n        shift\n        shift\n        shift\n        ;;\n    -h | --help)\n        usage\n        ;;\n    --version)\n        version\n        exit 0\n        ;;\n    --pure-version)\n        pure_version\n        exit 0\n        ;;\n    --app-drop-link)\n        APPLICATION_LINK=$2\n        APPLICATION_CLAUSE=\"set position of item \\\"Applications\\\" to {$2, $3}\n\"\n        shift\n        shift\n        shift\n        ;;\n    --eula)\n        EULA_RSRC=$2\n        shift\n        shift\n        ;;\n    --no-internet-enable)\n        NOINTERNET=1\n        shift\n        ;;\n    --subfolder)\n        DOSUBFOLDER=1\n        shift\n        ;;\n    -*)\n        echo \"Unknown option $1. Run with --help for help.\"\n        exit 1\n        ;;\n    esac\ndone\n\ntest -z \"$2\" && {\n    echo \"Not enough arguments. Invoke with --help for help.\"\n    exit 1\n}\n\nDMG_PATH=\"$1\"\nDMG_DIRNAME=\"$(dirname \"$DMG_PATH\")\"\nDMG_DIR=\"$(\n    cd $DMG_DIRNAME >/dev/null\n    pwd\n)\"\nDMG_NAME=\"$(basename \"$DMG_PATH\")\"\nDMG_TEMP_NAME=\"$DMG_DIR/rw.${DMG_NAME}\"\nSRC_FOLDER=\"$(\n    cd \"$2\" >/dev/null\n    pwd\n)\"\ntest -z \"$VOLUME_NAME\" && VOLUME_NAME=\"$(basename \"$DMG_PATH\" .dmg)\"\n\nAUX_PATH=\"$(dirname $0)/support\"\n\ntest -d \"$AUX_PATH\" || {\n    echo \"Cannot find support directory: $AUX_PATH\"\n    exit 1\n}\n\nif [[ $DOSUBFOLDER == 1 ]]; then\n    SRC_FOLDER_TEMP=\"${SRC_FOLDER}_tmp/\"\n    SRC_FOLDER_SF=\"${SRC_FOLDER}_tmp/${SRC_FOLDER##*/}\"\n    echo \"Generating subfolder $SRC_FOLDER_SF...\"\n    mkdir -p \"$SRC_FOLDER_SF\"\n    cp -a \"$SRC_FOLDER\" \"$SRC_FOLDER_SF\"\n    SRC_FOLDER=\"$SRC_FOLDER_SF\"\nfi\n\nif [ -f \"$SRC_FOLDER/.DS_Store\" ]; then\n    echo \"Deleting any .DS_Store in source folder\"\n    rm \"$SRC_FOLDER/.DS_Store\"\nfi\n\n# Create the image\necho \"Creating disk image...\"\ntest -f \"${DMG_TEMP_NAME}\" && rm -f \"${DMG_TEMP_NAME}\"\nACTUAL_SIZE=$(du -sm \"$SRC_FOLDER\" | sed -e 's/\t.*//g')\nDISK_IMAGE_SIZE=$(expr $ACTUAL_SIZE + 20)\nhdiutil create -srcfolder \"$SRC_FOLDER\" -volname \"${VOLUME_NAME}\" -fs HFS+ -fsargs \"-c c=64,a=16,e=16\" -format UDRW -size ${DISK_IMAGE_SIZE}m \"${DMG_TEMP_NAME}\"\n\n# mount it\necho \"Mounting disk image...\"\nMOUNT_DIR=\"/Volumes/${VOLUME_NAME}\"\n\n# try unmount dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it)\necho \"Unmounting disk image...\"\nDEV_NAME=$(hdiutil info | egrep '^/dev/' | sed 1q | awk '{print $1}')\ntest -d \"${MOUNT_DIR}\" && hdiutil detach \"${DEV_NAME}\"\n\necho \"Mount directory: $MOUNT_DIR\"\nDEV_NAME=$(hdiutil attach -readwrite -noverify -noautoopen \"${DMG_TEMP_NAME}\" | egrep '^/dev/' | sed 1q | awk '{print $1}')\necho \"Device name:     $DEV_NAME\"\n\nif ! test -z \"$BACKGROUND_FILE\"; then\n    echo \"Copying background file...\"\n    test -d \"$MOUNT_DIR/.background\" || mkdir \"$MOUNT_DIR/.background\"\n    cp \"$BACKGROUND_FILE\" \"$MOUNT_DIR/.background/$BACKGROUND_FILE_NAME\"\nfi\n\nif ! test -z \"$APPLICATION_LINK\"; then\n    echo \"making link to Applications dir\"\n    echo $MOUNT_DIR\n    ln -s /Applications \"$MOUNT_DIR/Applications\"\nfi\n\nif ! test -z \"$VOLUME_ICON_FILE\"; then\n    echo \"Copying volume icon file '$VOLUME_ICON_FILE'...\"\n    cp \"$VOLUME_ICON_FILE\" \"$MOUNT_DIR/.VolumeIcon.icns\"\n    SetFile -c icnC \"$MOUNT_DIR/.VolumeIcon.icns\"\nfi\n\n# run applescript\nAPPLESCRIPT=$(mktemp -t createdmg.XXXXXXX)\ncat \"$AUX_PATH/template.applescript\" | sed -e \"s/WINX/$WINX/g\" -e \"s/WINY/$WINY/g\" -e \"s/WINW/$WINW/g\" -e \"s/WINH/$WINH/g\" -e \"s/BACKGROUND_CLAUSE/$BACKGROUND_CLAUSE/g\" -e \"s/ICON_SIZE/$ICON_SIZE/g\" | perl -pe \"s/POSITION_CLAUSE/$POSITION_CLAUSE/g\" | perl -pe \"s/APPLICATION_CLAUSE/$APPLICATION_CLAUSE/g\" | perl -pe \"s/HIDING_CLAUSE/$HIDING_CLAUSE/\" >\"$APPLESCRIPT\"\n\necho \"Running Applescript: /usr/bin/osascript \\\"${APPLESCRIPT}\\\" \\\"${VOLUME_NAME}\\\"\"\n\"/usr/bin/osascript\" \"${APPLESCRIPT}\" \"${VOLUME_NAME}\" || true\necho \"Done running the applescript...\"\nsleep 4\n\nrm \"$APPLESCRIPT\"\n\n# make sure it's not world writeable\necho \"Fixing permissions...\"\nchmod -Rf go-w \"${MOUNT_DIR}\" &>/dev/null || true\necho \"Done fixing permissions.\"\n\n# make the top window open itself on mount:\necho \"Blessing started\"\nbless --folder \"${MOUNT_DIR}\" --openfolder \"${MOUNT_DIR}\"\necho \"Blessing finished\"\n\nif ! test -z \"$VOLUME_ICON_FILE\"; then\n    # tell the volume that it has a special file attribute\n    SetFile -a C \"$MOUNT_DIR\"\nfi\n\n# unmount\necho \"Unmounting disk image...\"\nhdiutil detach \"${DEV_NAME}\"\n\n# compress image\necho \"Compressing disk image...\"\nhdiutil convert \"${DMG_TEMP_NAME}\" -format UDBZ -o \"${DMG_DIR}/${DMG_NAME}\"\nrm -f \"${DMG_TEMP_NAME}\"\n\n# adding EULA resources\nif [ ! -z \"${EULA_RSRC}\" -a \"${EULA_RSRC}\" != \"-null-\" ]; then\n    echo \"adding EULA resources\"\n    \"${AUX_PATH}/dmg-license3.py\" \"${DMG_DIR}/${DMG_NAME}\" \"${EULA_RSRC}\"\nfi\n\nif [ ! -z \"${NOINTERNET}\" -a \"${NOINTERNET}\" == 1 ]; then\n    echo \"not setting 'internet-enable' on the dmg\"\nelse\n    hdiutil internet-enable -yes \"${DMG_DIR}/${DMG_NAME}\"\nfi\n\n# Removing temporary folder\nif [[ $DOSUBFOLDER == 1 ]]; then\n    rm -Rf ${SRC_FOLDER_TEMP}\nfi\n\necho \"Disk image done\"\nexit 0\n"
  },
  {
    "path": ".github/ci-helper/pack-game-macos.sh",
    "content": "#!/bin/bash\n\n# $1 assets; $2 icon $3 bundle name\n\nASSETS_NAME=$1\nICON_FILE=$2\nBUNDLE_NAME=$3\nARCHIVE_NAME=$4\n\nif [[ \"${ASSETS_NAME}\" != \"none\" ]]; then\n    echo \"Preparing the application...\"\n    cp -R TheXTech.app \"tmpapp\"\n    cp -R $ASSETS_NAME/* \"tmpapp/Contents/Resources/assets/\"\n    find tmpapp -name \".DS_Store\" -delete\n    plutil -replace CFBundleName -string \"$BUNDLE_NAME\" \"tmpapp/Contents/Info.plist\"\n    plutil -replace CFBundleIconFile -string \"$ICON_FILE\" \"tmpapp/Contents/Info.plist\"\n    mv tmpapp \"$BUNDLE_NAME.app\"\nfi\n\nmkdir dmg-root\n\ncp LICENSE \"dmg-root/License.TheXTech.txt\"\n\ncp README.md \"dmg-root/ReadMe.txt\"\ncp README.RUS.md \"dmg-root/ReadMe.RUS.txt\"\ncp README.ESP.md \"dmg-root/ReadMe.ESP.txt\"\n\necho \"== mv \\\"$BUNDLE_NAME.app\\\" dmg-root/\"\nmv \"$BUNDLE_NAME.app\" dmg-root/\n\nif [[ \"${ASSETS_NAME}\" == \"none\" ]]; then\n    echo \"Creating ZIP...\"\n    cd dmg-root\n    zip -9 -r ../${ARCHIVE_NAME} *\n    cd ..\nelse\n    echo \"Creating DMG...\"\n    ./.github/ci-helper/create-dmg.sh \\\n        --volname \"$BUNDLE_NAME\" \\\n        --window-size 800 600 \\\n        --app-drop-link 450 320 \\\n        --no-internet-enable \\\n        \"$ARCHIVE_NAME\" \\\n        \"dmg-root/\"\nfi\n\necho \"Cleaning up...\"\nrm -Rf dmg-root\nprintf \"\\n----------------------------------------------------------------\\n\\n\"\n"
  },
  {
    "path": ".github/ci-helper/pack-game.sh",
    "content": "#!/bin/bash\n\nRUNNER_OS=$1\nSUBDIR_NAME=$2\nEXECUTABLE_NAME=$3\nARCHIVE_NAME=\"$4\"\nASSETS_NAME=$5\n\n\ncd build\n\nmkdir -p \"package/${SUBDIR_NAME}\"\n\ncp ../changelog.txt \"package/${SUBDIR_NAME}/\"\n\ncp ../LICENSE \"package/${SUBDIR_NAME}/License.TheXTech.txt\"\n\ncp ../README.md \"package/${SUBDIR_NAME}/ReadMe.txt\"\ncp ../README.RUS.md \"package/${SUBDIR_NAME}/ReadMe.RUS.txt\"\ncp ../README.ESP.md \"package/${SUBDIR_NAME}/ReadMe.ESP.txt\"\n\nif [[ \"${RUNNER_OS}\" == \"Windows\" ]]; then\n    cp output/bin/thextech.exe \"package/${SUBDIR_NAME}/${EXECUTABLE_NAME}.exe\"\n    cp output/bin/*.dll \"package/${SUBDIR_NAME}/\"\nelif [[ \"${RUNNER_OS}\" == \"Linux\" ]]; then\n    cp output/bin/thextech \"package/${SUBDIR_NAME}/${EXECUTABLE_NAME}\"\nfi\n\nif [[ \"${ASSETS_NAME}\" != \"none\" ]]; then\n    cp -r ../${ASSETS_NAME}/* \"package/${SUBDIR_NAME}/\"\nfi\n\ncd package\n\nif [[ \"${RUNNER_OS}\" == \"Windows\" ]]; then\n    7z a \"${ARCHIVE_NAME}.7z\" \"${SUBDIR_NAME}\"\nelse\n    tar -cvzf \"${ARCHIVE_NAME}.tar.gz\" \"${SUBDIR_NAME}\"\nfi\n\nrm -Rf \"${SUBDIR_NAME}\"\n\ncd ../..\n"
  },
  {
    "path": ".github/ci-helper/support/dmg-license.py",
    "content": "#! /usr/bin/env python\n\"\"\"\nThis script adds a license file to a DMG. Requires Xcode and a plain ascii text\nlicense file.\nObviously only runs on a Mac.\n\nCopyright (C) 2011 Jared Hobbs\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\"\"\"\nimport os\nimport sys\nimport tempfile\nimport optparse\n\n\nclass Path(str):\n    def __enter__(self):\n        return self\n\n    def __exit__(self, in_type, value, traceback):\n        os.unlink(self)\n\n\ndef mktemp(dir=None, suffix=''):\n    (fd, filename) = tempfile.mkstemp(dir=dir, suffix=suffix)\n    os.close(fd)\n    return Path(filename)\n\n\ndef main(in_options, in_args):\n    dmg_file, license_file = in_args\n    with mktemp('.') as tmpFile:\n        with open(tmpFile, 'w') as f:\n            f.write(\"\"\"data 'LPic' (5000) {\n    $\"0002 0011 0003 0001 0000 0000 0002 0000\"\n    $\"0000 000E 0006 0001 0005 0007 0000 0007\"\n    $\"0008 0000 0047 0009 0000 0034 000A 0001\"\n    $\"0035 000B 0001 0020 000C 0000 0011 000D\"\n    $\"0000 005B 0004 0000 0033 000F 0001 000C\"\n    $\"0010 0000 000B 000E 0000\"\n};\\n\\n\"\"\")\n            with open(license_file, 'r') as l:\n                f.write('data \\'TEXT\\' (5002, \"English\") {\\n')\n                for line in l:\n                    if len(line) < 1000:\n                        f.write('    \"' + line.strip().replace('\"', '\\\\\"') +\n                                '\\\\n\"\\n')\n                    else:\n                        for liner in line.split('.'):\n                            f.write('    \"' +\n                                    liner.strip().replace('\"', '\\\\\"') +\n                                    '. \\\\n\"\\n')\n                f.write('};\\n\\n')\n            f.write(\"\"\"resource 'STR#' (5002, \"English\") {\n    {\n        \"English\",\n        \"Agree\",\n        \"Disagree\",\n        \"Print\",\n        \"Save...\",\n        \"IMPORTANT - By clicking on the \\\\\"Agree\\\\\" button, you agree \"\n        \"to be bound by the terms of the License Agreement.\",\n        \"Software License Agreement\",\n        \"This text cannot be saved. This disk may be full or locked, or the \"\n        \"file may be locked.\",\n        \"Unable to print. Make sure you have selected a printer.\"\n    }\n};\"\"\")\n        os.system('/usr/bin/hdiutil unflatten -quiet \"%s\"' % dmg_file)\n        os.system('%s \"%s/\"*.r %s -a -o \"%s\"' %\n                  (in_options.rez, in_options.flat_carbon, tmpFile, dmg_file))\n\n        os.system('/usr/bin/hdiutil flatten -quiet \"%s\"' % dmg_file)\n        if in_options.compression is not None:\n            os.system('cp %s %s.temp.dmg' % (dmg_file, dmg_file))\n            os.remove(dmg_file)\n            if in_options.compression == \"bz2\":\n                os.system('hdiutil convert %s.temp.dmg -format UDBZ -o %s' %\n                          (dmg_file, dmg_file))\n            elif in_options.compression == \"gz\":\n                os.system('hdiutil convert %s.temp.dmg -format ' % dmg_file +\n                          'UDZO -imagekey zlib-devel=9 -o %s' % dmg_file)\n            os.remove('%s.temp.dmg' % dmg_file)\n    print \"Successfully added license to '%s'\" % dmg_file\n\n\nif __name__ == '__main__':\n    parser = optparse.OptionParser()\n    parser.set_usage(\"\"\"%prog <dmgFile> <licenseFile> [OPTIONS]\n  This program adds a software license agreement to a DMG file.\n  It requires Xcode and a plain ascii text <licenseFile>.\n\n  See --help for more details.\"\"\")\n    parser.add_option(\n        '--rez',\n        '-r',\n        action='store',\n        default='/Applications/Xcode.app/Contents/Developer/Tools/Rez',\n        help='The path to the Rez tool. Defaults to %default'\n    )\n    parser.add_option(\n        '--flat-carbon',\n        '-f',\n        action='store',\n        default='/Applications/Xcode.app/Contents/Developer/Platforms'\n                '/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk'\n                '/Developer/Headers/FlatCarbon',\n        help='The path to the FlatCarbon headers. Defaults to %default'\n    )\n    parser.add_option(\n        '--compression',\n        '-c',\n        action='store',\n        choices=['bz2', 'gz'],\n        default=None,\n        help='Optionally compress dmg using specified compression type. '\n             'Choices are bz2 and gz.'\n    )\n    options, args = parser.parse_args()\n    cond = len(args) != 2 or not os.path.exists(options.rez) \\\n           or not os.path.exists(options.flat_carbon)\n    if cond:\n        parser.print_usage()\n        sys.exit(1)\n    main(options, args)\n"
  },
  {
    "path": ".github/ci-helper/support/dmg-license3.py",
    "content": "#! /usr/bin/env python3\n\"\"\"\nThis script adds a license file to a DMG. Requires Xcode and a plain ascii text\nlicense file.\nObviously only runs on a Mac.\n\nCopyright (C) 2011 Jared Hobbs\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\"\"\"\nimport os\nimport sys\nimport tempfile\nimport optparse\n\n\nclass Path(str):\n    def __enter__(self):\n        return self\n\n    def __exit__(self, in_type, value, traceback):\n        os.unlink(self)\n\n\ndef mktemp(dirname=None, suffix=''):\n    (fd, filename) = tempfile.mkstemp(dir=dirname, suffix=suffix)\n    os.close(fd)\n    return Path(filename)\n\n\ndef main(in_options, in_args):\n    dmg_file, license_file = in_args\n    with mktemp('.') as tmpFile:\n        with open(tmpFile, 'w') as f:\n            f.write(\"\"\"data 'LPic' (5000) {\n    $\"0002 0011 0003 0001 0000 0000 0002 0000\"\n    $\"0000 000E 0006 0001 0005 0007 0000 0007\"\n    $\"0008 0000 0047 0009 0000 0034 000A 0001\"\n    $\"0035 000B 0001 0020 000C 0000 0011 000D\"\n    $\"0000 005B 0004 0000 0033 000F 0001 000C\"\n    $\"0010 0000 000B 000E 0000\"\n};\\n\\n\"\"\")\n            with open(license_file, 'r') as l:\n                f.write('data \\'TEXT\\' (5002, \"English\") {\\n')\n                for line in l:\n                    if len(line) < 1000:\n                        f.write('    \"' + line.strip().replace('\"', '\\\\\"') +\n                                '\\\\n\"\\n')\n                    else:\n                        for liner in line.split('.'):\n                            f.write('    \"' +\n                                    liner.strip().replace('\"', '\\\\\"') +\n                                    '. \\\\n\"\\n')\n                f.write('};\\n\\n')\n            f.write(\"\"\"resource 'STR#' (5002, \"English\") {\n    {\n        \"English\",\n        \"Agree\",\n        \"Disagree\",\n        \"Print\",\n        \"Save...\",\n        \"IMPORTANT - By clicking on the \\\\\"Agree\\\\\" button, you agree \"\n        \"to be bound by the terms of the License Agreement.\",\n        \"Software License Agreement\",\n        \"This text cannot be saved. This disk may be full or locked, or the \"\n        \"file may be locked.\",\n        \"Unable to print. Make sure you have selected a printer.\"\n    }\n};\"\"\")\n        os.system('/usr/bin/hdiutil unflatten -quiet \"%s\"' % dmg_file)\n        os.system('%s \"%s/\"*.r %s -a -o \"%s\"' %\n                  (in_options.rez, in_options.flat_carbon, tmpFile, dmg_file))\n\n        os.system('/usr/bin/hdiutil flatten -quiet \"%s\"' % dmg_file)\n        if in_options.compression is not None:\n            os.system('cp %s %s.temp.dmg' % (dmg_file, dmg_file))\n            os.remove(dmg_file)\n            if in_options.compression == \"bz2\":\n                os.system('hdiutil convert %s.temp.dmg -format UDBZ -o %s' %\n                          (dmg_file, dmg_file))\n            elif in_options.compression == \"gz\":\n                os.system('hdiutil convert %s.temp.dmg -format ' % dmg_file +\n                          'UDZO -imagekey zlib-devel=9 -o %s' % dmg_file)\n            os.remove('%s.temp.dmg' % dmg_file)\n    print(\"Successfully added license to '%s'\" % dmg_file)\n\n\nif __name__ == '__main__':\n    parser = optparse.OptionParser()\n    parser.set_usage(\"\"\"%prog <dmgFile> <licenseFile> [OPTIONS]\n  This program adds a software license agreement to a DMG file.\n  It requires Xcode and a plain ascii text <licenseFile>.\n\n  See --help for more details.\"\"\")\n    parser.add_option(\n        '--rez',\n        '-r',\n        action='store',\n        default='/Applications/Xcode.app/Contents/Developer/Tools/Rez',\n        help='The path to the Rez tool. Defaults to %default'\n    )\n    parser.add_option(\n        '--flat-carbon',\n        '-f',\n        action='store',\n        default='/Applications/Xcode.app/Contents/Developer/Platforms'\n                '/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk'\n                '/Developer/Headers/FlatCarbon',\n        help='The path to the FlatCarbon headers. Defaults to %default'\n    )\n    parser.add_option(\n        '--compression',\n        '-c',\n        action='store',\n        choices=['bz2', 'gz'],\n        default=None,\n        help='Optionally compress dmg using specified compression type. '\n             'Choices are bz2 and gz.'\n    )\n    options, args = parser.parse_args()\n    cond = len(args) != 2 or not os.path.exists(options.rez) \\\n        or not os.path.exists(options.flat_carbon)\n    if cond:\n        parser.print_usage()\n        sys.exit(1)\n    main(options, args)\n"
  },
  {
    "path": ".github/ci-helper/support/template.applescript",
    "content": "on run (volumeName)\n\ttell application \"Finder\"\n\t\ttell disk (volumeName as string)\n\t\t\topen\n\t\t\t\n\t\t\tset theXOrigin to WINX\n\t\t\tset theYOrigin to WINY\n\t\t\tset theWidth to WINW\n\t\t\tset theHeight to WINH\n\t\t\t\n\t\t\tset theBottomRightX to (theXOrigin + theWidth)\n\t\t\tset theBottomRightY to (theYOrigin + theHeight)\n\t\t\tset dsStore to \"\\\"\" & \"/Volumes/\" & volumeName & \"/\" & \".DS_STORE\\\"\"\n\t\t\t\n\t\t\ttell container window\n\t\t\t\tset current view to icon view\n\t\t\t\tset toolbar visible to false\n\t\t\t\tset statusbar visible to false\n\t\t\t\tset the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}\n\t\t\t\tset statusbar visible to false\n\t\t\tend tell\n\t\t\t\n\t\t\tset opts to the icon view options of container window\n\t\t\ttell opts\n\t\t\t\tset icon size to ICON_SIZE\n\t\t\t\tset arrangement to not arranged\n\t\t\tend tell\n\t\t\tBACKGROUND_CLAUSE\n\t\t\t\n\t\t\t-- Positioning\n\t\t\tPOSITION_CLAUSE\n\t\t\t\n\t\t\t-- Hiding\n\t\t\tHIDING_CLAUSE\n\t\t\t\n\t\t\t-- Application Link Clause\n\t\t\tAPPLICATION_CLAUSE\n            close\n            open\n\t\t\t\n\t\t\tupdate without registering applications\n\t\t\t-- Force saving of the size\n\t\t\tdelay 1\n\t\t\t\n\t\t\ttell container window\n\t\t\t\tset statusbar visible to false\n\t\t\t\tset the bounds to {theXOrigin, theYOrigin, theBottomRightX - 10, theBottomRightY - 10}\n\t\t\tend tell\n\t\t\t\n\t\t\tupdate without registering applications\n\t\tend tell\n\t\t\n\t\tdelay 1\n\t\t\n\t\ttell disk (volumeName as string)\n\t\t\ttell container window\n\t\t\t\tset statusbar visible to false\n\t\t\t\tset the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}\n\t\t\tend tell\n\t\t\t\n\t\t\tupdate without registering applications\n\t\tend tell\n\t\t\n\t\t--give the finder some time to write the .DS_Store file\n\t\tdelay 3\n\t\t\n\t\tset waitTime to 0\n\t\tset ejectMe to false\n\t\trepeat while ejectMe is false\n\t\t\tdelay 1\n\t\t\tset waitTime to waitTime + 1\n\t\t\t\n\t\t\tif (do shell script \"[ -f \" & dsStore & \" ]; echo $?\") = \"0\" then set ejectMe to true\n\t\tend repeat\n\t\tlog \"waited \" & waitTime & \" seconds for .DS_STORE to be created.\"\n\tend tell\nend run\n"
  },
  {
    "path": ".github/ci-helper/translate_patcher.py",
    "content": "#!/usr/bin/python3\n\nimport json\nimport sys\nimport optparse\n\n\ndef merge_jsons(my_dict, output):\n    for k, v in my_dict.items():\n        if isinstance(v, dict):\n            if k not in output:\n                output[k] = dict()\n            merge_jsons(v, output[k])\n            continue\n\n        output[k] = v\n\n\ndef main(in_args):\n    in_file = in_args[0]\n    patch_file = in_args[1]\n    out_file = in_args[2]\n\n    with open(in_file, encoding='utf-8') as first_file:\n        json_out = json.load(first_file)\n\n    with open(patch_file, encoding='utf-8') as second_file:\n        json_in = json.load(second_file)\n\n    print(\"-- Patching translation file: %s\\n\"\n          \"-- By the patch file: %s\\n\"\n          \"-- Writing result into the %s\" % (in_file, patch_file, out_file))\n\n    merge_jsons(json_in, json_out)\n\n    with open(out_file, 'w', encoding='utf-8') as f:\n        json.dump(json_out, f, ensure_ascii=False, indent=4)\n\n    print(\"Completed!\")\n\n\nif __name__ == '__main__':\n    parser = optparse.OptionParser()\n    parser.set_usage(\"\"\"%prog <in file to patch> <patch file> <output file>.\n\n  See --help for more details.\"\"\")\n\n    options, args = parser.parse_args()\n    cond = len(args) != 3\n    if cond:\n        parser.print_usage()\n        sys.exit(1)\n\n    main(args)\n"
  },
  {
    "path": ".github/ci-helper/translate_sync_assets.sh",
    "content": "#!/bin/bash\n\nOLD=$PWD\nGIT_ROOT=$PWD/build-git\nWORKDIR=\"$PWD/.github/ci-helper\"\n\nmkdir -p build-git\n\nfunction update_repo()\n{\n    q=$1\n    branch=$2\n\n    if [[ \"$branch\" != \"\" ]]; then\n        branch_command=\"-b $branch\"\n        branch_display=\"[Branch $branch] \"\n    fi\n\n    echo \"=============================================================\"\n    echo \"================ $q $branch_display================\"\n    echo \"=============================================================\"\n    cd \"${GIT_ROOT}\"\n    git clone \"$q\" --depth 1 $branch_command repo\n\n    cd \"${WORKDIR}\"\n    bash translate_update.sh \"${GIT_ROOT}/repo\"\n\n    cd \"${GIT_ROOT}/repo\"\n    if [[ ! -z $(git status -s) ]]; then\n        echo \"-- Found updated languages, commiting...\"\n        git add --all\n        git commit --author=\"${GIT_AUTHOR}\" -m \"Synchronized translations\"\n        git push\n    else\n        echo \"-- No updates found, skipping...\"\n    fi\n    cd ..\n    rm -Rf repo\n    printf \"=============================================================\\n\\n\\n\"\n}\n\n\n# FIXME: Replace this hardcoded list with a server-side one to don't re-commit this list\n# every time\nfor q in \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-smbx.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-adventures-of-demo.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-convert-kit.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-a2xt-analog-funk.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-a2xt-prelude-to-the-stupid.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-lowser-s-conquest-beta.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-sarasaland-adventure-v1-2.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-sarasaland-adventure-2.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-sarasaland-adventure-v4-0.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-smbx-nes.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-smbx-nostalgic.git\" \\\n    \"git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-super-talking-time-bros-1n2-v1-5.git\" \\\n    ;\ndo\n    update_repo \"$q\"\ndone\n\n# Nostalgic Paradise needs another branch\nupdate_repo git@gitea.wohlsoft.ru:Games-for-TheXTech/nostalgic-paradise.git development\n\ncd \"${OLD}\"\n"
  },
  {
    "path": ".github/ci-helper/translate_sync_to_stable.sh",
    "content": "#!/bin/bash\n\nOLD=$PWD\nGIT_ROOT=$PWD/build-git\nWORKDIR=\"$PWD/.github/ci-helper\"\n\nSTABLE_BRANCH=stable-1.3.7.x\n\nif [[ -z \"$GITHUB_TOKEN\" ]]; then\n    echo 'Missing input \"github_token: $GITHUB_TOKEN\".';\n    exit 1;\nfi\n\ngit clone https://github.com/TheXTech/TheXTech.git --depth 1 -b ${STABLE_BRANCH} build-git\n\n# General engine translations\ncp -av resources/languages/* build-git/resources/languages/\n\n# Android launcher translations\ncd android-project/thextech/src/main/res\nfor q in values values-*; do\n    if [[ -f \"$q/strings.xml\" ]]; then\n        cp -v \"$q/strings.xml\" \"${GIT_ROOT}/android-project/thextech/src/main/res/${q}/strings.xml\"\n    fi\n    if [[ -f \"$q/arrays.xml\" ]]; then\n        cp -v \"$q/arrays.xml\" \"${GIT_ROOT}/android-project/thextech/src/main/res/${q}/arrays.xml\"\n    fi\ndone\ncd \"$OLD\"\n\n# Commit all changes that was done\ncd build-git\nif [[ ! -z $(git status -s) ]]; then\n    echo \"-- Found updated languages, commiting...\"\n    git add --all\n    git commit --author=\"${GIT_AUTHOR}\" -m \"Synchronized translations with mainstream branch\"\n    remote_repo=\"https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/TheXTech/TheXTech.git\"\n    git push \"${remote_repo}\" ${STABLE_BRANCH}\nelse\n    echo \"-- No updates found, skipping...\"\nfi\n\ncd ..\nrm -Rf build-git\n\ncd \"${OLD}\"\n\nexit 0\n"
  },
  {
    "path": ".github/ci-helper/translate_update.sh",
    "content": "#!/bin/bash\n\nOLD_DIR=~+\n\nASSETS_ROOT=$1\n\nLANGS_IN=\"$PWD/../../resources/languages\"\nLANGS_PATCH=$1/languages/patches\nLANGS_OUT=$1/languages\n\n# Update only existing files at the target (Don't copy new files, they should be placed manually to add them for the autosync)\ncd \"${LANGS_OUT}\"\n\nfunction update_tr_file()\n{\n    q=\"$1\"\n    if [[ ! -f \"${LANGS_PATCH}/$q\" ]]; then\n        # If no patch file exists, just copy\n        printf \"==== COPY: %s ====\\n\" $q\n        cp -v \"${LANGS_IN}/$q\" \"${LANGS_OUT}/$q\"\n    else\n        # Otherwise, apply the patch\n        printf \"==== PATCH: %s ====\\n\" $q\n        echo python3 \"${OLD_DIR}/translate_patcher.py\" \"${LANGS_IN}/$q\" \"${LANGS_PATCH}/$q\" \"${LANGS_OUT}/$q\"\n        python3 \"${OLD_DIR}/translate_patcher.py\" \"${LANGS_IN}/$q\" \"${LANGS_PATCH}/$q\" \"${LANGS_OUT}/$q\"\n    fi\n    printf \"\\n\"\n}\n\necho \"---------------------------------------------\"\n\nfor q in thextech_*.json; do\n    update_tr_file \"$q\"\ndone\n\nfor q in assets_*.json; do\n    update_tr_file \"$q\"\ndone\n\necho \"---------------------------------------------\"\n\ncd \"$OLD_DIR\"\n"
  },
  {
    "path": ".github/workflows/16m-ci.yml",
    "content": "name: DSi CI\n\non:\n  push:\n    branches:\n      - main\n      - versus-ci-homebrew\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ubuntu-latest\n    container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest\n    env:\n        DEVKITPRO: /opt/devkitpro\n        DEVKITARM: /opt/devkitpro/devkitARM\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"DSi build\",\n\n            extra_options: \"-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/NDS.cmake -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON\",\n\n            deps_cmdline: \"echo 'DevkitPro SDK is already pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"RelWithDebInfo\",\n            executable_name: \"thextech\",\n            subdir_name: \"thextech-dsi\",\n            upload_directory: \"www/dsi/\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Configure\n      shell: bash\n      run: |\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} .\n\n    - name: Build\n      shell: bash\n      run: |\n        cmake --build build --target all -j4\n\n    - name: Check codesize and RAM usage\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        size build/output/bin/thextech.elf\n\n    - name: Create Package\n      if: success()\n      shell: bash\n      run: |\n        cd build\n        mkdir package\n        mkdir \"package/${{ matrix.config.subdir_name }}\"\n        cp ../changelog.txt \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../LICENSE \"package/${{ matrix.config.subdir_name }}/License.TheXTech.txt\"\n        cat ../docs/README_DSI.md ../README.md >> \"package/${{ matrix.config.subdir_name }}/README.md\"\n        cp thextech.nds \"package/${{ matrix.config.subdir_name }}/\"\n        cd package\n        zip -9 -r \"thextech-calico-${BRANCH_NAME}.zip\" \"${{ matrix.config.subdir_name }}\"\n        rm -Rf \"${{ matrix.config.subdir_name }}\"\n        cd ../..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.zip\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        for q in ./build/package/*.zip; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n        done\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/3ds-ci.yml",
    "content": "name: 3DS CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci-homebrew\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ubuntu-latest\n    container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest\n    env:\n        DEVKITPRO: /opt/devkitpro\n        DEVKITARM: /opt/devkitpro/devkitARM\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"3DS build\",\n\n            extra_options: \"-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/3DS.cmake -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON\",\n\n            deps_cmdline: \"echo 'DevkitPro SDK is already pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"Release\",\n            executable_name: \"thextech\",\n            subdir_name: \"thextech-3ds\",\n            upload_directory: \"www/3ds/\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Configure\n      shell: bash\n      run: |\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} .\n\n    - name: Build\n      shell: bash\n      run: |\n        cmake --build build --target all -j4\n\n    - name: Check codesize and RAM usage\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        size build/output/bin/thextech.elf\n\n    - name: Create Package\n      if: success()\n      shell: bash\n      run: |\n        cd build\n        mkdir package\n        mkdir \"package/${{ matrix.config.subdir_name }}\"\n        cp ../changelog.txt \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../LICENSE \"package/${{ matrix.config.subdir_name }}/License.TheXTech.txt\"\n        cat ../docs/README_3DS.md ../README.md >> \"package/${{ matrix.config.subdir_name }}/README.md\"\n        cp thextech.3dsx \"package/${{ matrix.config.subdir_name }}/\"\n        cd package\n        zip -9 -r \"thextech-3ds-${BRANCH_NAME}.zip\" \"${{ matrix.config.subdir_name }}\"\n        rm -Rf \"${{ matrix.config.subdir_name }}\"\n        cd ../..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.zip\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        for q in ./build/package/*.zip; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n        done\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/android-ci.yml",
    "content": "name: Android CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci-android\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\ndefaults:\n  run:\n    working-directory: android-project\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    name: Build release-apk\n    steps:\n      - uses: TheXTech/checkout@v0.1\n\n      - uses: TheXTech/branch-name@v0.1\n\n      - name: Pull submodules\n        shell: bash\n        run: |\n          git submodule update --init --recursive\n\n      - name: Check for the upload support\n        id: upload-check\n        shell: bash\n        run: |\n          if [[ \"${{ secrets.builds_login }}\" != '' && \\\n                \"${{ secrets.builds_password }}\" != '' && \\\n                \"${{ secrets.builds_host }}\" != '' ]]; then\n            echo \"available=true\" >> $GITHUB_OUTPUT;\n          else\n            echo \"available=false\" >> $GITHUB_OUTPUT;\n          fi\n\n      - name: Check for the upload support\n        id: signing-check\n        shell: bash\n        run: |\n          if [[ \"${{ secrets.ANDROID_KEYSTORE }}\" != '' && \\\n                \"${{ secrets.RELEASE_STORE_PASSWORD }}\" != '' && \\\n                \"${{ secrets.RELEASE_KEY_PASSWORD }}\" != '' && \\\n                \"${{ secrets.RELEASE_KEY_ALIAS }}\" != '' ]];\n          then\n              echo \"available=true\" >> $GITHUB_OUTPUT;\n          else\n              echo \"available=false\" >> $GITHUB_OUTPUT;\n          fi\n\n      - name: Install Dependencies\n        shell: bash\n        run: |\n          sudo apt-get update -qq\n          sudo apt-get install -qq lftp\n\n      - name: Set up JDK 21\n        uses: actions/setup-java@v3.11.0\n        with:\n          java-version: 21\n          distribution: 'temurin'\n\n      - uses: nttld/setup-ndk@v1\n        with:\n          ndk-version: r23c\n      # IMPORTANT NOTE: The SDK r23b is REQUIRED to support Android 4.1, 4.2, and 4.3, and to support non-Neon hardware\n\n      - name: Setup Android SDK\n        uses: android-actions/setup-android@v2\n\n      # Without NDK not compile and not normal error message. NDK is required\n      #- name: Install NDK\n      #  run: echo \"y\" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install \"ndk;23.2.8568313\" --sdk_root=${ANDROID_SDK_ROOT}\n      # Some times is have problems with permissions for ./gradle file. Then uncommit it code\n      #    - name: Make gradlew executable\n      #      run: chmod +x ./gradlew\n\n      - name: Output version code\n        run: echo VERSION_CODE=${{ github.run_number }} > ./version.properties\n\n      - name: Import the signing\n        if: ${{ steps.signing-check.outputs.available == 'true' }}\n        run: echo \"${{ secrets.ANDROID_KEYSTORE }}\" | base64 -d > release-key.jks\n\n      - name: Build with Gradle\n        if: ${{ steps.signing-check.outputs.available == 'true' }}\n        run: ./gradlew assembleApkReleaseci\n        env:\n          RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }}\n          RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}\n          RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}\n          PIN_ALIAS: ${{ secrets.PIN_ALIAS }}\n          DB_PASS_ALIAS: ${{ secrets.DB_PASS_ALIAS }}\n\n      - name: Build with Gradle (unsigned)\n        if: ${{ steps.signing-check.outputs.available != 'true' }}\n        run: ./gradlew assembleApkRelease\n        env:\n          RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }}\n          RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}\n          RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}\n          PIN_ALIAS: ${{ secrets.PIN_ALIAS }}\n          DB_PASS_ALIAS: ${{ secrets.DB_PASS_ALIAS }}\n\n      - name: Rename APK\n        if: ${{ steps.signing-check.outputs.available == 'true' }}\n        shell: bash\n        run: |\n          mv thextech/build/outputs/apk/apk/releaseci/thextech-apk-releaseci.apk thextech-android-${BRANCH_NAME}.apk\n\n      - name: Rename APK (unsigned)\n        if: ${{ steps.signing-check.outputs.available != 'true' }}\n        shell: bash\n        run: |\n          mv thextech/build/outputs/apk/apk/release/thextech-apk-release-unsigned.apk thextech-android-${BRANCH_NAME}-unsigned.apk\n\n      - name: Upload APK\n        if: success() && ${{ steps.signing-check.outputs.available == 'true' }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: thextech-android-${BRANCH_NAME}\n          path: android-project/thextech-android-*.apk\n\n      - name: Upload APK (unsigned)\n        if: success() && ${{ steps.signing-check.outputs.available != 'true' }}\n        uses: actions/upload-artifact@v4\n        continue-on-error: true\n        with:\n          name: thextech-android-${BRANCH_NAME}-unsigned\n          path: android-project/thextech-android-*.apk\n\n      - name: Deploy to builds.wohlsoft.ru\n        if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n        continue-on-error: true\n        shell: bash\n        run: |\n          UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n          if [[ \"${{ steps.signing-check.outputs.available }}\" == 'true' ]]; then\n              UPLOAD_LIST=\"${UPLOAD_LIST} put -O \"www/android/\" ./thextech-android-${BRANCH_NAME}.apk;\"\n          else\n              UPLOAD_LIST=\"${UPLOAD_LIST} put -O \"www/android/\" ./thextech-android-${BRANCH_NAME}-unsigned.apk;\"\n          fi\n          lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n      - name: List Build Directory\n        if: always()\n        shell: bash\n        run: |\n          git status\n          ls -lR .\n"
  },
  {
    "path": ".github/workflows/android-fdroid-ci.yml",
    "content": "name: Android (F-Droid) CI\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - release-1.3.7-fdroid\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\ndefaults:\n  run:\n    working-directory: android-project\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    name: Build release-apk\n    steps:\n      - uses: TheXTech/checkout@v0.1\n\n      - uses: TheXTech/branch-name@v0.1\n\n      - name: Pull submodules\n        shell: bash\n        run: |\n          git submodule update --init --recursive\n\n      - name: Check for the upload support\n        id: upload-check\n        shell: bash\n        run: |\n          if [[ \"${{ secrets.builds_login }}\" != '' && \\\n                \"${{ secrets.builds_password }}\" != '' && \\\n                \"${{ secrets.builds_host }}\" != '' ]]; then\n            echo \"available=true\" >> $GITHUB_OUTPUT;\n          else\n            echo \"available=false\" >> $GITHUB_OUTPUT;\n          fi\n\n      - name: Check for the upload support\n        id: signing-check\n        shell: bash\n        run: |\n          if [[ \"${{ secrets.ANDROID_KEYSTORE }}\" != '' && \\\n                \"${{ secrets.RELEASE_STORE_PASSWORD }}\" != '' && \\\n                \"${{ secrets.RELEASE_KEY_PASSWORD }}\" != '' && \\\n                \"${{ secrets.RELEASE_KEY_ALIAS }}\" != '' ]];\n          then\n              echo \"available=true\" >> $GITHUB_OUTPUT;\n          else\n              echo \"available=false\" >> $GITHUB_OUTPUT;\n          fi\n\n      - name: Extracting version name\n        id: extract-version\n        shell: bash\n        run: |\n            ver_name=`perl -ne 'm/THEXTECH_ANDROID_VERSION_NAME\\s+\"([\\d\\w.\\-]+)\"/ && print \"$1\"' ../version.cmake`\n            echo \"Version name in version.cmake: ${ver_name}\"\n            echo \"ver_name=${ver_name}\" >> $GITHUB_OUTPUT;\n\n      - name: Install Dependencies\n        shell: bash\n        run: |\n          sudo apt-get update -qq\n          sudo apt-get install -qq lftp apksigner\n\n      - name: Set up JDK 21\n        uses: actions/setup-java@v3.11.0\n        with:\n          java-version: 21\n          distribution: 'temurin'\n\n      - uses: nttld/setup-ndk@v1\n        with:\n          ndk-version: r23c\n      # IMPORTANT NOTE: The SDK r23b is REQUIRED to support Android 4.1, 4.2, and 4.3, and to support non-Neon hardware\n\n      - name: Setup Android SDK\n        uses: android-actions/setup-android@v2\n\n      # Without NDK not compile and not normal error message. NDK is required\n      #- name: Install NDK\n      #  run: echo \"y\" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install \"ndk;23.2.8568313\" --sdk_root=${ANDROID_SDK_ROOT}\n      # Some times is have problems with permissions for ./gradle file. Then uncommit it code\n      #    - name: Make gradlew executable\n      #      run: chmod +x ./gradlew\n\n      - name: Output version code\n        run: echo VERSION_CODE=${{ github.run_number }} > ./version.properties\n\n      - name: Import the signing\n        if: ${{ steps.signing-check.outputs.available == 'true' }}\n        run: echo \"${{ secrets.ANDROID_KEYSTORE }}\" | base64 -d > release-key.jks\n\n      - name: Build unsigned with Gradle\n        run: ./gradlew assembleFdroidRelease\n\n      - name: Sign and rename APK\n        if: ${{ steps.signing-check.outputs.available == 'true' }}\n        shell: bash\n        run: |\n          apksigner sign --ks 'release-key.jks' \\\n            --ks-pass env:RELEASE_STORE_PASSWORD \\\n            --ks-key-alias ${{ secrets.RELEASE_KEY_ALIAS }} \\\n            --min-sdk-version 16 \\\n            --v1-signing-enabled \\\n            --v2-signing-enabled \\\n            --out thextech-android-fdroid-${{ steps.extract-version.outputs.ver_name }}.apk \\\n            thextech/build/outputs/apk/fdroid/release/thextech-fdroid-release-unsigned.apk\n        env:\n          RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }}\n          RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}\n          RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}\n          PIN_ALIAS: ${{ secrets.PIN_ALIAS }}\n          DB_PASS_ALIAS: ${{ secrets.DB_PASS_ALIAS }}\n\n      - name: Rename APK (unsigned)\n        if: ${{ steps.signing-check.outputs.available != 'true' }}\n        shell: bash\n        run: |\n          mv thextech/build/outputs/apk/fdroid/release/thextech-fdroid-release-unsigned.apk thextech-android-fdroid-${{ steps.extract-version.outputs.ver_name }}-unsigned.apk\n\n      - name: Upload APK\n        if: success() && ${{ steps.signing-check.outputs.available == 'true' }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: thextech-android-${{ steps.extract-version.outputs.ver_name }}\n          path: android-project/thextech-android-*.apk\n\n      - name: Upload APK (unsigned)\n        if: success() && ${{ steps.signing-check.outputs.available != 'true' }}\n        uses: actions/upload-artifact@v4\n        continue-on-error: true\n        with:\n          name: thextech-android-${{ steps.extract-version.outputs.ver_name }}-unsigned\n          path: android-project/thextech-android-*.apk\n\n      - name: Deploy to builds.wohlsoft.ru\n        if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n        continue-on-error: true\n        shell: bash\n        run: |\n          UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n          if [[ \"${{ steps.signing-check.outputs.available }}\" == 'true' ]]; then\n              UPLOAD_LIST=\"${UPLOAD_LIST} put -O \"www/android/fdroid/\" ./thextech-android-fdroid-${{ steps.extract-version.outputs.ver_name }}.apk;\"\n          else\n              UPLOAD_LIST=\"${UPLOAD_LIST} put -O \"www/android/fdroid/\" ./thextech-android-fdroid-${{ steps.extract-version.outputs.ver_name }}-unsigned.apk;\"\n          fi\n          lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n      - name: List Build Directory\n        if: always()\n        shell: bash\n        run: |\n          git status\n          ls -lR .\n"
  },
  {
    "path": ".github/workflows/blocksds-ci.yml",
    "content": "name: DSi BlocksDS CI\n\non:\n  push:\n    branches:\n      - main\n      - versus-ci-homebrew\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ubuntu-latest\n    container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-blocksds:latest\n    env:\n        WONDERFUL_TOOLCHAIN: /opt/wonderful\n        BLOCKSDS: /opt/wonderful/thirdparty/blocksds/core\n        BLOCKSDSEXT: /opt/wonderful/thirdparty/blocksds/external\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"DSi BlocksDS build\",\n\n            extra_options: \"-DCMAKE_TOOLCHAIN_FILE=$BLOCKSDS/cmake/BlocksDSi.cmake -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON\",\n            extra_path: \"/opt/wonderful/bin\",\n\n            deps_cmdline: \"echo 'BlocksDS SDK is already pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"RelWithDebInfo\",\n            executable_name: \"thextech\",\n            subdir_name: \"thextech-dsi\",\n            upload_directory: \"www/dsi/\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Configure\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n            export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} .\n\n    - name: Build\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n            export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n\n        cmake --build build --target all -j4\n\n    - name: Check codesize and RAM usage\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        size build/output/bin/thextech.elf\n\n    - name: Create Package\n      if: success()\n      shell: bash\n      run: |\n        cd build\n        mkdir package\n        mkdir \"package/${{ matrix.config.subdir_name }}\"\n        cp ../changelog.txt \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../LICENSE \"package/${{ matrix.config.subdir_name }}/License.TheXTech.txt\"\n        cat ../docs/README_DSI.md ../README.md >> \"package/${{ matrix.config.subdir_name }}/README.md\"\n        cp thextech.nds \"package/${{ matrix.config.subdir_name }}/\"\n        cd package\n        zip -9 -r \"thextech-blocksds-${BRANCH_NAME}.zip\" \"${{ matrix.config.subdir_name }}\"\n        rm -Rf \"${{ matrix.config.subdir_name }}\"\n        cd ../..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.zip\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n            export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        for q in ./build/package/*.zip; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n        done\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/emscripten.yml",
    "content": "name: Emscripten CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci-emscripten\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ${{ matrix.config.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        # ======================================= Adventures of Demo =======================================\n        # The side game about Demo and siblings from the A2XT universe by raocow and his fan community.\n        - {\n            name: \"Adventures of Demo\",\n            os: ubuntu-24.04,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=advdemo \\\n            -DTHEXTECH_GAME_NAME_TITLE=\\\"Adventures of Demo - Web Edition\\\" \\\n            -DTHEXTECH_CREDITS_URL=\\\"wohlsoft.ru\\\" \\\n            -DTHEXTECH_CREDITS_TITLE=\\\"Adventures of Demo\\\" \\\n            -DTHEXTECH_MANIFEST_NAME=\\\"AoD on TheXTech ${THEXTECH_VERSION_STRING}\\\" \\\n            -DTHEXTECH_MANIFEST_ID=\\\"wohlsoft-aod-thextech\\\" \\\n            -DTHEXTECH_MANIFEST_DESC=\\\"Play AoD on TheXTech ${THEXTECH_VERSION_STRING}\\\" \\\n            -DTHEXTECH_DEPLOY_URL=\\\"https://wohlsoft.ru/projects/TheXTech/aod-on-web-debug/\\\"\",\n\n            deps_cmdline: \"sudo apt-get update -qq \\\n            && sudo apt-get install -qq cmake ninja-build cmake ninja-build lftp\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name: \"advdemo\",\n            assets_url: \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z\",\n            subdir_name: \"thextech-adventures-of-demo\",\n            package_filename_game: \"adventures-of-demo\",\n            upload_directory: \"www/webassembly/\",\n            deploy_directory: \"www/webassembly/debug-deploy/aod-on-web-debug\"\n          }\n\n        # ======================================= Super Mario Bros. X - a fan-game =======================================\n        # Was made in 2009 by Andrew Spinks \"Redigit\", and supported up to 2011 by it's original author.\n        - {\n            name: \"Super Mario Bros. X\",\n            os: ubuntu-24.04,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=smbx \\\n            -DTHEXTECH_GAME_NAME_TITLE=\\\"Super Mario Bros. X - Web Edition\\\" \\\n            -DTHEXTECH_CREDITS_URL=\\\"www.SuperMarioBrosX.org\\\" \\\n            -DTHEXTECH_CREDITS_TITLE=\\\"Super Mario Bros. X\\\" \\\n            -DTHEXTECH_MANIFEST_NAME=\\\"SMBX on TheXTech ${THEXTECH_VERSION_STRING}\\\" \\\n            -DTHEXTECH_MANIFEST_ID=\\\"wohlsoft-smbx-thextech\\\" \\\n            -DTHEXTECH_MANIFEST_DESC=\\\"Play SMBX on TheXTech ${THEXTECH_VERSION_STRING}\\\" \\\n            -DTHEXTECH_DEPLOY_URL=\\\"https://wohlsoft.ru/projects/TheXTech/smbx-on-web-debug/\\\"\",\n\n            deps_cmdline: \"sudo apt-get update -qq \\\n            && sudo apt-get install -qq cmake ninja-build cmake ninja-build lftp\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name: \"smbx\",\n            assets_url: \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z\",\n            subdir_name: \"thextech-super-mario-bros-x\",\n            package_filename_game: \"super-mario-bros-x\",\n            upload_directory: \"www/webassembly/\",\n            deploy_directory: \"www/webassembly/debug-deploy/smbx-on-web-debug\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        # Pull submodules\n        git submodule update --init --recursive\n\n    - name: Install emscripten\n      run: |\n        cd ..\n        git clone https://github.com/emscripten-core/emsdk.git\n        cd emsdk\n        ./emsdk install latest\n        ./emsdk activate latest\n\n    - name: Download assets\n      shell: bash\n      run: wget -d -nv -t 5 -O assets.7z \"${{ matrix.config.assets_url }}\"\n\n    - name: Unpack assets\n      shell: bash\n      run: |\n        mkdir -p assets\n        cd assets\n        7z x ../assets.7z\n        cd ..\n        rm assets.7z\n\n    - name: Apply update to translations\n      shell: bash\n      run: |\n        ASSETS_ROOT=\"$PWD/assets\"\n        cd .github/ci-helper\n        bash translate_update.sh \"${ASSETS_ROOT}\"\n        cd ../..\n\n    - name: Configure\n      shell: bash\n      run: |\n        source ../emsdk/emsdk_env.sh\n        emcmake cmake -B build -G \"${{ matrix.config.generator }}\" \\\n            -DPGE_PRELOAD_ENVIRONMENT=\"`pwd`/assets\" \\\n            -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} .\n\n    - name: Build\n      shell: bash\n      run: |\n        source ../emsdk/emsdk_env.sh\n        emmake cmake --build build --target all\n\n    - name: Create Package\n      if: success()\n      shell: bash\n      run: |\n        cd build\n        mkdir package\n        mkdir \"package/${{ matrix.config.subdir_name }}\"\n        cp ../changelog.txt \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../LICENSE \"package/${{ matrix.config.subdir_name }}/License.TheXTech.txt\"\n        cp ../README.md \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../README.RUS.md \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../README.ESP.md \"package/${{ matrix.config.subdir_name }}/\"\n        cp output/bin/* \"package/${{ matrix.config.subdir_name }}/\"\n        cd package\n        tar -cvzf \"thextech-${{ matrix.config.package_filename_game }}-webassembly-${BRANCH_NAME}.tar.gz\" \"${{ matrix.config.subdir_name }}\"\n        rm -Rf \"${{ matrix.config.subdir_name }}\"\n        cd ../..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.tar.gz\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n            export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        for q in ./build/package/*.tar.gz; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n        done\n\n        UPLOAD_LIST=\"${UPLOAD_LIST} rm -rf ${{ matrix.config.deploy_directory }}-next;\"\n        UPLOAD_LIST=\"${UPLOAD_LIST} mkdir ${{ matrix.config.deploy_directory }}-next;\"\n        for q in ./build/output/bin/*; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.deploy_directory }}-next/ $q;\"\n        done\n\n        UPLOAD_LIST=\"${UPLOAD_LIST} rm -rf ${{ matrix.config.deploy_directory }}-prev;\"\n        UPLOAD_LIST=\"${UPLOAD_LIST} mv ${{ matrix.config.deploy_directory }} ${{ matrix.config.deploy_directory }}-prev;\"\n        UPLOAD_LIST=\"${UPLOAD_LIST} mv ${{ matrix.config.deploy_directory }}-next ${{ matrix.config.deploy_directory }};\"\n        UPLOAD_LIST=\"${UPLOAD_LIST} rm -rf ${{ matrix.config.deploy_directory }}-prev;\"\n\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/flatpak.yml",
    "content": "name: Flatpak CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci\n      - versus-ci-flatpak\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: Flatpak for ${{ matrix.arch.name }}\n    runs-on: ${{ matrix.arch.runs_on }}\n    container:\n      image: ${{ matrix.arch.image }}\n      options: --privileged\n    strategy:\n      fail-fast: true\n      matrix:\n        arch:\n        - {\n            name: \"x86-64\",\n            id: \"x86_64\",\n#            sdk_ver: \"22.08\",\n            runs_on: \"ubuntu-latest\",\n            image: \"ghcr.io/thextech/wohlnet-ci-ubuntu2204:latest\"\n          }\n        - {\n            name: \"arm64\",\n            id: \"aarch64\",\n#            sdk_ver: \"22.08\",\n            runs_on: \"ubuntu-24.04-arm\",\n            image: \"ghcr.io/thextech/wohlnet-ci-ubuntu2204-arm64:latest\"\n          }\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n#    - name: Install Ubuntu Dependencies\n#      shell: bash\n#      run: |\n#        apt update --fix-missing -y\n#        apt upgrade -y\n#\n#        apt install -y flatpak flatpak-builder p7zip-full git lftp\n#\n#    - name: Install flatpak Dependencies\n#      shell: bash\n#      run: |\n#        flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo\n#        flatpak install -y flathub org.freedesktop.Platform/${{ matrix.arch.id }}/${{ matrix.arch.sdk_ver }} org.freedesktop.Sdk/${{ matrix.arch.id }}/${{ matrix.arch.sdk_ver }}\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - uses: TheXTech/checkout@v0.1\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git config --global --add safe.directory '*'\n        git submodule update --init --recursive\n\n    - name: Create flatpak manifest YML\n      id: make-yml\n      shell: bash\n      run: |\n        export REPO_PATH=\"$(pwd)\"\n        export REPO_PATH_SED=\"$(pwd | sed 's_/_\\\\/_g')\"\n        export BRANCH_NAME_SED=\"$(echo $BRANCH_NAME | sed 's_/_\\\\/_g')\"\n        cd ..\n        cat $REPO_PATH/resources/flatpak/build.yml \\\n          | sed \"s/- type: git/- type: dir/\" \\\n          | sed \"s/url: https:.*/path: $REPO_PATH_SED/\" \\\n          | sed \"s/OVERRIDE_GIT_BRANCH=/OVERRIDE_GIT_BRANCH=$BRANCH_NAME_SED/\" \\\n          | tee ci-build.yml\n\n    - name: Build flatpak\n      shell: bash\n      run: |\n        cd ..\n        flatpak-builder --arch=${{ matrix.arch.id }} --force-clean --repo=temp_repo temp_build ci-build.yml\n\n    - name: Create bundle\n      shell: bash\n      run: |\n        mkdir -p package\n        # ru.wohlsoft.TheXTech is the package id, and dev is the branch\n        flatpak build-bundle --arch=${{ matrix.arch.id }} ../temp_repo package/thextech-${{ matrix.arch.name }}.flatpak ru.wohlsoft.TheXTech dev\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: package/thextech-${{ matrix.arch.name }}.flatpak\n        name: TheXTech (${{ matrix.arch.name }})\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        cd package\n        mv \"thextech-${{ matrix.arch.name }}.flatpak\" \"thextech-${BRANCH_NAME}-${{ matrix.arch.name }}.flatpak\"\n\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        UPLOAD_LIST=\"${UPLOAD_LIST} put -O www/flatpak thextech-${BRANCH_NAME}-${{ matrix.arch.name }}.flatpak;\"\n\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n"
  },
  {
    "path": ".github/workflows/homebrew-assets-ci.yml",
    "content": "name: Homebrew Assets CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n    paths:\n      - .github/workflows/homebrew-assets-ci.yml\n      - utils/convertkit/assets-convert-homebrew.py\n  pull_request:\n    branches:\n      - main\n    paths:\n      - .github/workflows/homebrew-assets-ci.yml\n      - utils/convertkit/assets-convert-homebrew.py\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: ${{ matrix.source.name }} for ${{ matrix.target.name }}\n    runs-on: ubuntu-latest\n    container: ${{ matrix.target.docker_image }}\n    env:\n        DEVKITPRO: /opt/devkitpro\n    strategy:\n      fail-fast: true\n      matrix:\n        target:\n        - {\n            name: \"3DS\",\n            id: \"3ds\",\n            docker_image: \"devkitpro/devkitarm\",\n            upload_directory: \"www/3ds/\"\n          }\n        - {\n            name: \"Wii\",\n            id: \"wii\",\n            docker_image: \"devkitpro/devkitppc\",\n            upload_directory: \"www/wii/\"\n          }\n        source:\n        # ======================================= Adventures of Demo =======================================\n        # The side game about Demo and siblings from the A2XT universe by raocow and his fan community.\n        - {\n            name: \"Adventures of Demo\",\n            id: \"aod\",\n            assets_url: \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z\",\n          }\n        # ======================================= Super Mario Bros. X - a fan-game =======================================\n        # Was made in 2009 by Andrew Spinks \"Redigit\", and supported up to 2011 by it's original author.\n        - {\n            name: \"Super Mario Bros. X\",\n            id: \"smbx13\",\n            assets_url: \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z\",\n          }\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Resolve artifact filename\n      id: res\n      shell: bash\n      run: |\n        echo \"filename=assets-${{ matrix.source.id }}-${{ matrix.target.id }}.zip\" >> $GITHUB_OUTPUT;\n        echo \"The target will be named: [assets-${{ matrix.source.id }}-${{ matrix.target.id }}.${{ matrix.target.package_ext }}]\"\n\n    - name: Install Debian Dependencies\n      shell: bash\n      run: |\n        apt update\n        apt install -y build-essential cmake  \\\n                       imagemagick ffmpeg     \\\n                       python3                \\\n                       p7zip-full             \\\n                       zip                    \\\n                       lftp\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Build and install spc2it Dependency\n      shell: bash\n      continue-on-error: true\n      run: |\n        git clone https://github.com/ds-sloth/spc2it ../spc2it\n        cd ../spc2it\n        mkdir build\n        cd build\n        cmake ..\n        make\n        cp spc2it /usr/bin/spc2it\n\n    - uses: TheXTech/checkout@v0.1\n\n    - name: Download assets\n      shell: bash\n      run: wget -d -nv -t 5 -O assets-source.7z \"${{ matrix.source.assets_url }}\"\n\n    - name: Unpack assets\n      shell: bash\n      run: |\n        mkdir -p assets/source\n        cd assets/source\n        7z x ../../assets-source.7z\n        cd ../..\n        rm assets-source.7z\n\n        mkdir -p package\n\n    - name: Apply update to translations\n      shell: bash\n      run: |\n        ASSETS_ROOT=\"$PWD/assets/source\"\n        cd .github/ci-helper\n        bash translate_update.sh \"${ASSETS_ROOT}\"\n        cd ../..\n\n    - name: Convert assets\n      shell: bash\n      run: |\n        cd assets\n        python3 ../utils/convertkit/assets-convert-homebrew.py -t ${{ matrix.target.id }} source target\n\n    - name: Package for 3DS\n      if: matrix.target.id == '3ds'\n      shell: bash\n      run: |\n        mkromfs3ds assets/target package/${{ steps.res.outputs.filename }}.romfs\n        cd package\n        zip -9 \"${{ steps.res.outputs.filename }}\" \"${{ steps.res.outputs.filename }}.romfs\"\n        cd ..\n\n    - name: Package for Wii\n      if: matrix.target.id == 'wii'\n      shell: bash\n      run: |\n        cd assets/target\n        zip -9 -r \"../../package/${{ steps.res.outputs.filename }}\" .\n        cd ../..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: package/${{ steps.res.outputs.filename }}\n        name: ${{ matrix.source.name }} Assets (${{ matrix.target.name }})\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        cd package\n\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.target.upload_directory }} ${{ steps.res.outputs.filename }};\"\n\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        cd assets/target\n        pwd\n        ls -lR\n"
  },
  {
    "path": ".github/workflows/macos-ci.yml",
    "content": "name: macOS CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci-macos\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ${{ matrix.config.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"macOS Universal\",\n            os: macos-15-intel,\n            extra_options: \"-DTHEXTECH_PRELOAD_ENVIRONMENT_MANUALLY=ON -DTHEXTECH_EXECUTABLE_NAME=\\\"thextech\\\"\",\n            deps_cmdline: \"brew install ninja wget p7zip lftp\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            upload_directory: \"www/macosx/\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n\n    - name: CI Server's network meta-info\n      shell: bash\n      run: |\n        echo \"CI server IP address:\"\n        wget -q -O - ipinfo.io/ip\n        printf \"\\n\"\n        wget -d ipinfo.io/ip -O/dev/null 2>&1 |grep ^User-Agent\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Download SMBX assets\n      shell: bash\n      run: wget -d -nv -t 5 -O smbx13.7z \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z\"\n\n    - name: Download AoD assets\n      shell: bash\n      run: wget -d -nv -t 5 -O aod.7z \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z\"\n\n    - name: Unpack all assets\n      shell: bash\n      run: |\n        mkdir -p smbx13\n        cd smbx13\n        7z x ../smbx13.7z\n        cd ..\n        rm smbx13.7z\n        mkdir -p aod\n        cd aod\n        7z x ../aod.7z\n        cd ..\n        rm aod.7z\n\n    - name: Apply update to translations\n      shell: bash\n      run: |\n        ASSETS_ROOT1=\"$PWD/smbx13\"\n        ASSETS_ROOT2=\"$PWD/aod\"\n        cd .github/ci-helper\n        bash translate_update.sh \"${ASSETS_ROOT1}\"\n        bash translate_update.sh \"${ASSETS_ROOT2}\"\n        cd ../..\n\n    - name: Configure (x86_64)\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n          echo \"PATH environment: ${PATH}\"\n        fi\n        if [[ \"${{ secrets.DISCORD_APP_ID }}\" != '' ]]; then\n          LOCAL_EXTRA_SETUP=\"-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\\\"${{ secrets.DISCORD_APP_ID }}\\\"\"\n        fi\n        cmake -B build-x64 -G \"${{ matrix.config.generator }}\" \\\n              -DCMAKE_OSX_DEPLOYMENT_TARGET=\"10.9\" \\\n              -DCMAKE_OSX_ARCHITECTURES=\"x86_64\" \\\n              -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \\\n              ${{ matrix.config.extra_options }} \\\n              ${LOCAL_EXTRA_SETUP} .\n\n    - name: Build (x86_64)\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        export MAKEFLAGS=--keep-going\n        cmake --build build-x64 --config ${{ matrix.config.build_type }} --parallel 3\n\n    - name: Configure (arm64)\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n          echo \"PATH environment: ${PATH}\"\n        fi\n        if [[ \"${{ secrets.DISCORD_APP_ID }}\" != '' ]]; then\n          LOCAL_EXTRA_SETUP=\"-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\\\"${{ secrets.DISCORD_APP_ID }}\\\"\"\n        fi\n        cmake -B build-arm -G \"${{ matrix.config.generator }}\"  \\\n              -DCMAKE_OSX_DEPLOYMENT_TARGET=\"11.0\" \\\n              -DCMAKE_OSX_ARCHITECTURES=\"arm64\" \\\n              -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \\\n              ${{ matrix.config.extra_options }} \\\n              ${LOCAL_EXTRA_SETUP} .\n\n    - name: Build (arm64)\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        export MAKEFLAGS=--keep-going\n        cmake --build build-arm --config ${{ matrix.config.build_type }} --parallel 3\n\n    - name: Merging executible\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        cp -R build-arm/output/bin/TheXTech.app TheXTech.app\n        rm TheXTech.app/Contents/MacOS/TheXTech\n        lipo -create -output TheXTech.app/Contents/MacOS/TheXTech build-x64/output/bin/TheXTech.app/Contents/MacOS/TheXTech build-arm/output/bin/TheXTech.app/Contents/MacOS/TheXTech\n        mkdir TheXTech.app/Contents/Resources/assets\n\n    - name: Executable info\n      shell: bash\n      run: |\n        file TheXTech.app/Contents/MacOS/TheXTech\n        otool -L TheXTech.app/Contents/MacOS/TheXTech\n\n    - name: Create Package\n      if: success()\n      shell: bash\n      run: |\n        bash .github/ci-helper/pack-game-macos.sh \\\n            aod \\\n            \"assets/graphics/ui/icon/advdemo.icns\" \\\n            \"Adventures of Demo\" \\\n            \"thextech-adventures-of-demo-macos-uni-${BRANCH_NAME}.dmg\"\n\n        bash .github/ci-helper/pack-game-macos.sh \\\n            smbx13 \\\n            \"assets/graphics/ui/icon/smbx.icns\" \\\n            \"Super Mario Bros. X\" \\\n            \"thextech-super-mario-bros-x-macos-uni-${BRANCH_NAME}.dmg\"\n\n        bash .github/ci-helper/pack-game-macos.sh \\\n            none \\\n            \"thextech.icns\" \\\n            \"TheXTech\" \\\n            \"thextech-macos-uni-${BRANCH_NAME}.zip\"\n\n    - name: Upload DMG artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: ./*.dmg\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} DMG\n\n    - name: Upload ZIP artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: ./*.zip\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} ZIP\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        if ls *.dmg 1> /dev/null 2>&1; then\n            for q in *.dmg; do\n                UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n            done\n        fi\n        if ls *.zip 1> /dev/null 2>&1; then\n            for q in *.zip; do\n                UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n            done\n        fi\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: Clean-up\n      if: always()\n      shell: bash\n      run: |\n        rm -Rf TheXTech.app\n        rm -Rf aod\n        rm -Rf smbx13\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build-x64\n        ls -lR build-arm\n"
  },
  {
    "path": ".github/workflows/portmaster-ci.yml",
    "content": "name: PortMaster CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci\n      - versus-ci-portmaster\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ${{ matrix.config.os }}\n    container: ${{ matrix.config.container }}\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"PortMaster Ubuntu 20.04 aarch64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2004-arm64-cross:latest\",\n\n            extra_options: \"-DPORTMASTER=ON -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 20 arm64, cross from x86_64, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"portmaster\",\n            upload_directory: \"www/portmaster/\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Configure\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n          echo \"PATH environment: ${PATH}\"\n        fi\n        if [[ \"${{ secrets.DISCORD_APP_ID }}\" != '' ]]; then\n          LOCAL_EXTRA_SETUP=\"-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\\\"${{ secrets.DISCORD_APP_ID }}\\\"\"\n        fi\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} .\n\n    - name: Build\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        export MAKEFLAGS=--keep-going\n        cmake --build build --config ${{ matrix.config.build_type }} --parallel 4\n\n    - name: List dependent libraries\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        file build/output/bin/thextech\n#        ldd build/output/bin/thextech # Not for cross compile!\n\n\n    - name: Create Package\n      if: success()\n      id: create_package\n      shell: bash\n      run: |\n        bash .github/ci-helper/pack-game.sh \\\n            \"${{ runner.os }}\" \\\n            \"thextech-bin\" \\\n            \"thextech${{ matrix.config.executable_name_suffix }}\" \\\n            \"thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}\" \\\n            \"none\"\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.tar.gz\n        name: TheXTech PortMaster ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        if [[ \"${{ runner.os }}\" == 'Windows' ]]; then\n            for q in ./build/package/*.7z; do\n                UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n            done\n        elif [[ \"${{ runner.os }}\" == 'Linux' ]]; then\n            for q in ./build/package/*.tar.gz; do\n                UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n            done\n        fi\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/regression-testing-ci.yaml",
    "content": "name: Regression Test CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ${{ matrix.config.os }}\n    container: ${{ matrix.config.container }}\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"Ubuntu 24.04 x86_64 (no-SDL)\",\n            os: ubuntu-24.04,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DTHEXTECH_CLI_BUILD=ON \\\n            -DTHEXTECH_NO_SDL_BUILD=ON\",\n\n            deps_cmdline: \"sudo apt-get update -qq \\\n            && sudo apt-get install -qq cmake ninja-build lftp \\\n            build-essential\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-24-04-amd64\",\n            upload_directory: \"\"\n          }\n        - {\n            name: \"Ubuntu 24.04 x86_64 (no-SDL, legacy floats)\",\n            os: ubuntu-24.04,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DTHEXTECH_CLI_BUILD=ON \\\n            -DTHEXTECH_NO_SDL_BUILD=ON \\\n            -DTHEXTECH_FIXED_POINT=OFF\",\n\n            deps_cmdline: \"sudo apt-get update -qq \\\n            && sudo apt-get install -qq cmake ninja-build lftp \\\n            build-essential\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-24-04-amd64-legacy-floats\",\n            upload_directory: \"\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n#    - name: Download SMBX assets\n#      shell: bash\n#      run: wget -d -nv -t 5 -O smbx13.7z \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z\"\n#\n#    - name: Download AoD assets\n#      shell: bash\n#      run: wget -d -nv -t 5 -O aod.7z \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z\"\n#\n#    - name: Unpack all assets\n#      shell: bash\n#      run: |\n#        mkdir -p smbx13\n#        cd smbx13\n#        7z x ../smbx13.7z\n#        cd ..\n#        rm smbx13.7z\n#        mkdir -p aod\n#        cd aod\n#        7z x ../aod.7z\n#        cd ..\n#        rm aod.7z\n#\n#    - name: Apply update to translations\n#      shell: bash\n#      run: |\n#        ASSETS_ROOT1=\"$PWD/smbx13\"\n#        ASSETS_ROOT2=\"$PWD/aod\"\n#        cd .github/ci-helper\n#        bash translate_update.sh \"${ASSETS_ROOT1}\"\n#        bash translate_update.sh \"${ASSETS_ROOT2}\"\n#        cd ../..\n\n    - name: Configure\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n          echo \"PATH environment: ${PATH}\"\n        fi\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} .\n\n    - name: Build\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        export MAKEFLAGS=--keep-going\n        cmake --build build --config ${{ matrix.config.build_type }} --parallel 3\n\n    - name: List dependent libraries\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        file build/output/bin/thextech\n        ldd build/output/bin/thextech\n\n    - name: Check codesize and RAM usage\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        size build/output/bin/thextech\n\n    # TODO: some performance and regression testing on a suite. Wait for wip-archives merge so that it is easy to use a test-suite.\n\n    # - name: Create Package\n    #   if: success()\n    #   id: create_package\n    #   shell: bash\n    #   run: |\n    #     bash .github/ci-helper/pack-game.sh \\\n    #         \"${{ runner.os }}\" \\\n    #         \"thextech-bin\" \\\n    #         \"thextech${{ matrix.config.executable_name_suffix }}\" \\\n    #         \"thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}\" \\\n    #         \"none\"\n\n    # - name: Upload artifact\n    #   if: success() && runner.os == 'Linux' && config.upload_directory != ''\n    #   uses: actions/upload-artifact@v3\n    #   continue-on-error: true\n    #   with:\n    #     path: build/package/*.tar.gz\n    #     name: TheXTech ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    # - name: Upload artifact\n    #   if: success() && runner.os == 'Windows'\n    #   uses: actions/upload-artifact@v3\n    #   continue-on-error: true\n    #   with:\n    #     path: build/package/*.7z\n    #     name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    # - name: Deploy to builds.wohlsoft.ru\n    #   if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' && config.upload_directory != ''\n    #   continue-on-error: true\n    #   shell: bash\n    #   run: |\n    #     if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n    #       export PATH=${{ matrix.config.extra_path }}:${PATH}\n    #     fi\n    #     UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n    #     if [[ \"${{ runner.os }}\" == 'Windows' ]]; then\n    #         for q in ./build/package/*.7z; do\n    #             UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n    #         done\n    #     elif [[ \"${{ runner.os }}\" == 'Linux' ]]; then\n    #         for q in ./build/package/*.tar.gz; do\n    #             UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n    #         done\n    #     fi\n    #     lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/switch-ci.yml",
    "content": "name: Switch CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci-homebrew\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ubuntu-latest\n    container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest\n    env:\n        DEVKITPRO: /opt/devkitpro\n        DEVKITA64: /opt/devkitpro/devkitA64\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"Switch build\",\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Switch.cmake \\\n            -DCMAKE_INSTALL_PREFIX=$DEVKITPRO/portlibs/switch\",\n            # -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/switch-local\n\n            deps_cmdline: \"echo 'DevkitPro SDK is already pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name: \"thextech\",\n            assets_url: \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z\",\n            subdir_name: \"thextech-switch\",\n            upload_directory: \"www/switch/\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n#    - name: Install Devkit Pro\n#      run: |\n#        cd ..\n#        sudo chmod 755 /opt\n#        if [[ ! -f /usr/local/share/keyring/devkitpro-pub.gpg ]]; then\n#            sudo mkdir -p /usr/local/share/keyring/\n#            sudo wget -O /usr/local/share/keyring/devkitpro-pub.gpg https://apt.devkitpro.org/devkitpro-pub.gpg\n#        fi\n#\n#        if [[ ! -f /etc/apt/sources.list.d/devkitpro.list ]]; then\n#            echo \"deb [signed-by=/usr/local/share/keyring/devkitpro-pub.gpg] https://apt.devkitpro.org stable main\" | sudo tee -a /etc/apt/sources.list.d/devkitpro.list\n#        fi\n#\n#        sudo apt-get update -qq\n#        sudo apt-get install -qq devkitpro-pacman\n#\n#        sudo dkp-pacman --noconfirm -Sy\n#        sudo dkp-pacman --noconfirm -Syu\n#        sudo dkp-pacman --noconfirm -Sy switch-dev switch-libpng switch-libjpeg-turbo switch-zlib switch-tools switch-cmake switch-mesa switch-libdrm_nouveau switch-glm switch-glad\n\n#    - name: Install modified SDL2\n#      run: |\n#        cd ..\n#        git clone https://github.com/Wohlstand/SDL.git -b switch-sdl2-2.0.14-dev --depth 1 SDL2\n#        cd SDL2\n#        mkdir build\n#        cd build\n#        cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Switch.cmake -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/switch-local -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/switch-local ..\n#        cmake --build . --target all -j4\n#        sudo cmake --build . --target install\n\n    - name: Configure\n      shell: bash\n      run: |\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} .\n\n    - name: Build\n      shell: bash\n      run: |\n        cmake --build build --target all -j4\n\n    - name: Create Package\n      if: success()\n      shell: bash\n      run: |\n        cd build\n        mkdir package\n        mkdir \"package/${{ matrix.config.subdir_name }}\"\n        cp ../changelog.txt \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../LICENSE \"package/${{ matrix.config.subdir_name }}/License.TheXTech.txt\"\n        cat ../docs/README_SWITCH.md ../README.md >> \"package/${{ matrix.config.subdir_name }}/README.md\"\n        cp output/bin/thextech.nro \"package/${{ matrix.config.subdir_name }}/\"\n        cd package\n        zip -9 -r \"thextech-switch-${BRANCH_NAME}.zip\" \"${{ matrix.config.subdir_name }}\"\n        rm -Rf \"${{ matrix.config.subdir_name }}\"\n        cd ../..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.zip\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        for q in ./build/package/*.zip; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n        done\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/sync-langs.yml",
    "content": "name: Languages sync CI\n\non:\n  push:\n    branches:\n      - main\n      - versus-ci-langsync\n    paths:\n      - .github/workflows/sync-langs.yml\n      - .github/ci-helper/translate_sync_assets.sh\n      - .github/ci-helper/translate_update.sh\n      - .github/ci-helper/translate_patcher.py\n      - resources/languages/**.json\n      - android-project/thextech/src/main/res/values/strings.xml\n      - android-project/thextech/src/main/res/values/arrays.xml\n      - android-project/thextech/src/main/res/values-**/strings.xml\n      - android-project/thextech/src/main/res/values-**/arrays.xml\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: Syncing languages\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      packages: read\n    container: ghcr.io/thextech/wohlnet-ci-ubuntu2404:latest\n    strategy:\n      fail-fast: true\n    steps:\n    - uses: TheXTech/branch-name@v0.1\n\n    - uses: TheXTech/checkout@v0.1\n\n    - name: Adding Gitea SSH key\n      shell: bash\n      env:\n        SSH_KEY: ${{secrets.GITEA_SYNCBOT_SSH_KEY}}\n      run: |\n        mkdir ~/.ssh\n        chmod 700 ~/.ssh\n        eval `ssh-agent -s`\n        ssh-add - <<< \"${SSH_KEY}\"\n        ls -la ~/.ssh/\n        ssh -o StrictHostKeyChecking=no -T git@gitea.wohlsoft.ru\n        echo \"SSH_AUTH_SOCK=${SSH_AUTH_SOCK}\" >> $GITHUB_ENV\n        git config --global user.email \"ci@example.ru\"\n        git config --global user.name \"Language Sync Bot\"\n\n    - name: Copy updated translations to stable branch\n      shell: bash\n      env:\n        GIT_AUTHOR: \"github-actions[bot] <github-actions[bot]@users.noreply.github.com>\"\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }}\n      run: |\n        bash .github/ci-helper/translate_sync_to_stable.sh\n\n    - name: Apply update to all the translations\n      shell: bash\n      env:\n        GIT_AUTHOR: ${{secrets.GITEA_SYNCBOT_AUTHOR}}\n      run: |\n        bash .github/ci-helper/translate_sync_assets.sh\n"
  },
  {
    "path": ".github/workflows/ubuntu-deb-ci.yml",
    "content": "name: Ubuntu DEB CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci\n      - versus-ci-ubuntu\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ${{ matrix.config.os }}\n    container: ${{ matrix.config.container }}\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"DEB Ubuntu 14.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1404-64bit:latest\",\n            cross: false,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=OFF -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \\\n            -DCMAKE_C_COMPILER=gcc-8 \\\n            -DCMAKE_CXX_COMPILER=g++-8 \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.19), libpng12-0 (>= 1.2.50), libjpeg-turbo8 (>= 1.3.0) | libturbojpeg0 (>= 1.3.0), libasound2 (>= 1.0.27), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\"\",\n\n            deps_cmdline: \"echo 'Ubuntu 14 x86_64, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-14-04-amd64\",\n            upload_directory: \"www/ubuntu-14-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 14.04 32bit\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1404-32bit:latest\",\n            cross: false,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_FREEIMAGE_SYSTEM_LIBS=OFF \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \\\n            -DCMAKE_C_COMPILER=/usr/bin/i686-linux-gnu-gcc-8 \\\n            -DCMAKE_CXX_COMPILER=/usr/bin/i686-linux-gnu-g++-8 \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.19), libpng12-0 (>= 1.2.50), libjpeg-turbo8 (>= 1.3.0) | libturbojpeg0 (>= 1.3.0), libasound2 (>= 1.0.27), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\"\",\n\n            deps_cmdline: \"echo 'Ubuntu 14 x86_32, everything pre-installed'; ln -sf /usr/bin/i686-linux-gnu-gcc-8 /usr/bin/gcc; ln -sf /usr/bin/i686-linux-gnu-g++-8 /usr/bin/g++;\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-14-04-i386\",\n            upload_directory: \"www/ubuntu-14-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 16.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1604-64bit:latest\",\n            cross: false,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=OFF -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.23), libpng12-0 (>= 1.2.54), libjpeg-turbo8 (>= 1.4.2) | libturbojpeg0 (>= 1.4.2), libasound2 (>= 1.1.0), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\"\",\n\n            deps_cmdline: \"echo 'Ubuntu 16 x86_64, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-16-04-amd64\",\n            upload_directory: \"www/ubuntu-16-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 16.04 32bit\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1604-32bit:latest\",\n            cross: false,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_FREEIMAGE_SYSTEM_LIBS=OFF \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.23), libpng12-0 (>= 1.2.54), libjpeg-turbo8 (>= 1.4.2) | libturbojpeg0 (>= 1.4.2), libasound2 (>= 1.1.0), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\" \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_x32.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 16 x86_32, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-16-04-i386\",\n            upload_directory: \"www/ubuntu-16-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 16.04 armhf\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1604-armhf-cross:latest\",\n            cross: true,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_FREEIMAGE_SYSTEM_LIBS=OFF \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=armhf \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.23), libpng12-0 (>= 1.2.54), libjpeg-turbo8 (>= 1.4.2) | libturbojpeg0 (>= 1.4.2), libasound2 (>= 1.1.0), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\" \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_armhf.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 16 armhf, cross from x86_64, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-16-04-armhf\",\n            upload_directory: \"www/ubuntu-16-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 18.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1804:latest\",\n            cross: false,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.27), libsdl2-2.0-0 (>= 2.0.8), libpng16-16 (>= 1.6.34), libjpeg-turbo8 (>= 1.5.2) | libturbojpeg0 (>= 1.5.2), libasound2 (>= 1.1.3), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\"\",\n\n            deps_cmdline: \"echo 'Ubuntu 18 x86_32, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-18-04-amd64\",\n            upload_directory: \"www/ubuntu-18-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 20.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2004:latest\",\n            cross: false,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.31), libsdl2-2.0-0 (>= 2.0.10), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.0.3) | libturbojpeg0 (>= 2.0.3), libasound2 (>= 1.2.2), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\"\",\n\n            deps_cmdline: \"echo 'Ubuntu 20 x86_32, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-20-04-amd64\",\n            upload_directory: \"www/ubuntu-20-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 20.04 arm64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2004-arm64-cross:latest\",\n            cross: true,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=arm64 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.31), libsdl2-2.0-0 (>= 2.0.10), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.0.3) | libturbojpeg0 (>= 2.0.3), libasound2 (>= 1.2.2), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\" \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 20 arm64, cross from x86_64, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-20-04-aarch64\",\n            upload_directory: \"www/ubuntu-20-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 20.04 armhf\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2004-armhf-cross:latest\",\n            cross: true,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=armhf \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.31), libsdl2-2.0-0 (>= 2.0.10), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.0.3) | libturbojpeg0 (>= 2.0.3), libasound2 (>= 1.2.2), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\" \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_armhf.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 20 armhf, cross from x86_64, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-20-04-armhf\",\n            upload_directory: \"www/ubuntu-20-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 22.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2204:latest\",\n            cross: false,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.35), libsdl2-2.0-0 (>= 2.0.20), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.1.2) | libturbojpeg0 (>= 2.1.2), libasound2 (>= 1.2.6), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\"\",\n\n            deps_cmdline: \"echo 'Ubuntu 22 x86_32, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-22-04-amd64\",\n            upload_directory: \"www/ubuntu-22-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 22.04 aarch64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2204-arm64-cross:latest\",\n            cross: true,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=arm64 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.35), libsdl2-2.0-0 (>= 2.0.20), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.1.2) | libturbojpeg0 (>= 2.1.2), libasound2 (>= 1.2.6), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\" \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 22 arm64, cross from x86_64, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-22-04-aarch64\",\n            upload_directory: \"www/ubuntu-22-04/\"\n          }\n        - {\n            name: \"DEB Ubuntu 24.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2404:latest\",\n            cross: false,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.38), libsdl2-2.0-0 (>= 2.30.0), libpng16-16 (>= 1.6.43), libjpeg-turbo8 (>= 2.1.5), libasound2 (>= 1.2.10), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\"\",\n\n            deps_cmdline: \"echo 'Ubuntu 24 x86_64, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-24-04-amd64\",\n            upload_directory: \"www/ubuntu-24-04/\"\n          }\n        - {\n            name: \"DEB Debian 13 (Trixie) x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-debian13:latest\",\n            cross: false,\n\n            extra_options: \"-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n            -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \\\n            -DTHEXTECH_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DTHEXTECH_INSTALLER_PACKAGE_NAME=\\\"thextech-bin\\\" \\\n            -DCPACK_GENERATOR=DEB \\\n            -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\\\"https://wohlsoft.ru\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \\\n            -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \\\n            -DCPACK_DEBIAN_PACKAGE_DEPENDS=\\\"libc6 (>= 2.38), libsdl2-2.0-0 (>= 2.30.0), libpng16-16 (>= 1.6.43), libturbojpeg0 (>= 2.1.5), libasound2 (>= 1.2.10), libpulse0\\\" \\\n            -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\\\"TheXTech - the modern C++ port and successor of the SMBX engine\\\"\",\n\n            deps_cmdline: \"echo 'Debian 13 Trixie x86_64, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"debian-13-trixie-amd64\",\n            upload_directory: \"www/debian-13/\"\n          }\n\n    steps:\n    - name: Host info\n      shell: bash\n      run: |\n        uname -a\n        cat /proc/cpuinfo\n\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Configure\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n          echo \"PATH environment: ${PATH}\"\n        fi\n        if [[ \"${{ secrets.DISCORD_APP_ID }}\" != '' ]]; then\n          LOCAL_EXTRA_SETUP=\"-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\\\"${{ secrets.DISCORD_APP_ID }}\\\"\"\n        fi\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} .\n\n    - name: Build\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        export MAKEFLAGS=--keep-going\n        cmake --build build --config ${{ matrix.config.build_type }} --parallel 3\n\n    - name: List dependent libraries\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        file build/output/bin/${{ matrix.config.executable_name }}\n        if [[ \"${{ matrix.config.cross}}\" != true ]]; then\n            ldd build/output/bin/${{ matrix.config.executable_name }}\n        fi\n\n    - name: Create DEB packages\n      shell: bash\n      id: create_package\n      if: success()\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        cd build\n        cpack .\n        mv *.deb thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}.deb\n        cd ..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/*.deb\n        name: TheXTech ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        for q in ./build/*.deb; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n        done\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/ubuntu-tar-ci.yml",
    "content": "name: Ubuntu TAR CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci\n      - versus-ci-ubuntu\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ${{ matrix.config.os }}\n    container: ${{ matrix.config.container }}\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"TAR Ubuntu 14.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1404-64bit:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \\\n            -DCMAKE_C_COMPILER=gcc-8 \\\n            -DCMAKE_CXX_COMPILER=g++-8\",\n\n            deps_cmdline: \"echo 'Ubuntu 14 x86_64, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-14-04-amd64\",\n            upload_directory: \"www/ubuntu-14-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 14.04 x86_64 (legacy floats)\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1404-64bit:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \\\n            -DTHEXTECH_FIXED_POINT=OFF \\\n            -DCMAKE_C_COMPILER=gcc-8 \\\n            -DCMAKE_CXX_COMPILER=g++-8\",\n\n            deps_cmdline: \"echo 'Ubuntu 14 x86_64, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-14-04-amd64-legacy-floats\",\n            upload_directory: \"www/ubuntu-14-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 14.04 32bit\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1404-32bit:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \\\n            -DCMAKE_C_COMPILER=/usr/bin/i686-linux-gnu-gcc-8 \\\n            -DCMAKE_CXX_COMPILER=/usr/bin/i686-linux-gnu-g++-8\",\n\n            deps_cmdline: \"echo 'Ubuntu 14 x86_32, everything pre-installed'; ln -sf /usr/bin/i686-linux-gnu-gcc-8 /usr/bin/gcc; ln -sf /usr/bin/i686-linux-gnu-g++-8 /usr/bin/g++;\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-14-04-i386\",\n            upload_directory: \"www/ubuntu-14-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 16.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1604-64bit:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF\",\n\n            deps_cmdline: \"echo 'Ubuntu 16 x86_64, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-16-04-amd64\",\n            upload_directory: \"www/ubuntu-16-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 16.04 x86_64 (legacy floats)\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1604-64bit:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DTHEXTECH_FIXED_POINT=OFF\",\n\n            deps_cmdline: \"echo 'Ubuntu 16 x86_64, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-16-04-amd64-legacy-floats\",\n            upload_directory: \"www/ubuntu-16-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 16.04 32bit\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1604-32bit:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_x32.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 16 x86_32, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-16-04-i386\",\n            upload_directory: \"www/ubuntu-16-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 16.04 armhf\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1604-armhf-cross:latest\",\n            cross: true,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_armhf.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 16 armhf, cross from x86_64, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-16-04-armhf\",\n            upload_directory: \"www/ubuntu-16-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 18.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu1804:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF\",\n\n            deps_cmdline: \"echo 'Ubuntu 18 x86_32, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-18-04-amd64\",\n            upload_directory: \"www/ubuntu-18-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 20.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2004:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF\",\n\n            deps_cmdline: \"echo 'Ubuntu 20 x86_32, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-20-04-amd64\",\n            upload_directory: \"www/ubuntu-20-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 20.04 aarch64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2004-arm64-cross:latest\",\n            cross: true,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 20 arm64, cross from x86_64, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-20-04-aarch64\",\n            upload_directory: \"www/ubuntu-20-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 20.04 armhf\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2004-armhf-cross:latest\",\n            cross: true,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_armhf.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 20 armhf, cross from x86_64, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-20-04-armhf\",\n            upload_directory: \"www/ubuntu-20-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 22.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2204:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF\",\n\n            deps_cmdline: \"echo 'Ubuntu 22 x86_32, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-22-04-amd64\",\n            upload_directory: \"www/ubuntu-22-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 22.04 aarch64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2204-arm64-cross:latest\",\n            cross: true,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake\",\n\n            deps_cmdline: \"echo 'Ubuntu 22 arm64, cross from x86_64, everything pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-22-04-aarch64\",\n            upload_directory: \"www/ubuntu-22-04/\"\n          }\n        - {\n            name: \"TAR Ubuntu 24.04 x86_64\",\n            os: ubuntu-latest,\n            container: \"ghcr.io/thextech/wohlnet-ci-ubuntu2404:latest\",\n            cross: false,\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DUSE_STATIC_LIBC=ON \\\n            -DUSE_SYSTEM_LIBS=OFF \\\n            -DUSE_SYSTEM_SDL2=OFF \\\n            -DUSE_SHARED_FREEIMAGE=OFF \\\n            -DPGE_SHARED_SDLMIXER=OFF\",\n\n            deps_cmdline: \"echo 'Ubuntu 24 x86_32, everything pre-installed'\",\n\n            executable_name: \"thextech\",\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"ubuntu-24-04-amd64\",\n            upload_directory: \"www/ubuntu-24-04/\"\n          }\n\n    steps:\n    - name: Host info\n      shell: bash\n      run: |\n        uname -a\n        cat /proc/cpuinfo\n\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Download SMBX assets\n      shell: bash\n      run: wget -d -nv -t 5 -O smbx13.7z \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z\"\n\n    - name: Download AoD assets\n      shell: bash\n      run: wget -d -nv -t 5 -O aod.7z \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z\"\n\n    - name: Unpack all assets\n      shell: bash\n      run: |\n        mkdir -p smbx13\n        cd smbx13\n        7z x ../smbx13.7z\n        cd ..\n        rm smbx13.7z\n        mkdir -p aod\n        cd aod\n        7z x ../aod.7z\n        cd ..\n        rm aod.7z\n\n    - name: Apply update to translations\n      shell: bash\n      run: |\n        ASSETS_ROOT1=\"$PWD/smbx13\"\n        ASSETS_ROOT2=\"$PWD/aod\"\n        cd .github/ci-helper\n        bash translate_update.sh \"${ASSETS_ROOT1}\"\n        bash translate_update.sh \"${ASSETS_ROOT2}\"\n        cd ../..\n\n    - name: Configure\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n          echo \"PATH environment: ${PATH}\"\n        fi\n        if [[ \"${{ secrets.DISCORD_APP_ID }}\" != '' ]]; then\n          LOCAL_EXTRA_SETUP=\"-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\\\"${{ secrets.DISCORD_APP_ID }}\\\"\"\n        fi\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} .\n\n    - name: Build\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        export MAKEFLAGS=--keep-going\n        cmake --build build --config ${{ matrix.config.build_type }} --parallel 3\n\n    - name: List dependent libraries\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        file build/output/bin/thextech\n        if [[ \"${{ matrix.config.cross}}\" != true ]]; then\n            ldd build/output/bin/thextech\n        fi\n\n    - name: Create Package\n      if: success()\n      id: create_package\n      shell: bash\n      # ======================================= Adventures of Demo =======================================\n      # The side game about Demo and siblings from the A2XT universe by raocow and his fan community.\n      # ======================================= Super Mario Bros. X - a fan-game =======================================\n      # Was made in 2009 by Andrew Spinks \"Redigit\", and supported up to 2011 by it's original author.\n      run: |\n        bash .github/ci-helper/pack-game.sh \\\n            \"${{ runner.os }}\" \\\n            \"thextech-adventures-of-demo\" \\\n            \"advdemo${{ matrix.config.executable_name_suffix }}\" \\\n            \"thextech-adventures-of-demo-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}\" \\\n            \"aod\"\n\n        bash .github/ci-helper/pack-game.sh \\\n            \"${{ runner.os }}\" \\\n            \"thextech-super-mario-bros-x\" \\\n            \"smbx${{ matrix.config.executable_name_suffix }}\" \\\n            \"thextech-super-mario-bros-x-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}\" \\\n            \"smbx13\"\n\n        bash .github/ci-helper/pack-game.sh \\\n            \"${{ runner.os }}\" \\\n            \"thextech-bin\" \\\n            \"thextech${{ matrix.config.executable_name_suffix }}\" \\\n            \"thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}\" \\\n            \"none\"\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.tar.gz\n        name: TheXTech ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        if [[ \"${{ runner.os }}\" == 'Windows' ]]; then\n            for q in ./build/package/*.7z; do\n                UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n            done\n        elif [[ \"${{ runner.os }}\" == 'Linux' ]]; then\n            for q in ./build/package/*.tar.gz; do\n                UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n            done\n        fi\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/vita-ci.yml",
    "content": "name: Vita CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci-homebrew\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ubuntu-latest\n    container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest\n    env:\n        VITASDK: /usr/local/vitasdk\n    strategy:\n      fail-fast: true\n      matrix:\n        config:\n        - {\n            name: \"Vita build\",\n\n            extra_options: \"-DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake\",\n\n            deps_cmdline: \"echo 'Vita SDK is already pre-installed'\",\n\n            generator: \"Unix Makefiles\",\n            build_type: \"MinSizeRel\",\n            executable_name: \"thextech\",\n            assets_url: \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z\",\n            subdir_name: \"thextech-vita\",\n            upload_directory: \"www/vita/\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n# NOTE: 2023-08-11 now using container including Vita SDK\n#    - name: Install Vita SDK\n#      run: |\n#        cd ..\n#        export VITASDK=/usr/local/vitasdk\n#        export PATH=$VITASDK/bin:$PATH\n#        git clone https://github.com/vitasdk/vdpm\n#        cd vdpm\n#        ./bootstrap-vitasdk.sh\n#        ./install-all.sh || echo A library from vitasdk failed to install.\n\n# NOTE: 2/13/2022 Current Vita version does not use cglm or vitaGL renderer. All is handled by SDL2 at the moment.\n#    - name: Install cglm\n#      run: |\n#        cd ..\n#        export PATH=$VITASDK/bin:$PATH\n#        git clone https://github.com/recp/cglm.git\n#        cd cglm\n#        sed -i \"s|-Werror||g\" CMakeLists.txt\n#        cmake -B build-vita -G Ninja -DCMAKE_INSTALL_PREFIX=$VITASDK/arm-vita-eabi -DCGLM_STATIC=ON -DCGLM_SHARED=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=/usr/local/vitasdk/share/vita.toolchain.cmake .\n#        cmake --build build-vita --target all\n#        cmake --build build-vita --target install\n\n#    - name: Install Latest vitaGL\n#      run: |\n#        cd ..\n#        export PATH=$VITASDK/bin:$PATH\n#        git clone https://github.com/Rinnegatamante/vitaGL.git\n#        cd vitaGL\n#        NO_DEBUG=1 NO_TEX_COMBINER=1 make\n#        make install\n\n# no longer distributing packed Vita builds\n#    - name: Download assets\n#      uses: carlosperate/download-file-action@v2\n#      with:\n#        file-url: \"${{ matrix.config.assets_url }}\"\n#        file-name: assets.7z\n#\n#    - name: Unpack assets\n#      shell: bash\n#      run: |\n#        mkdir -p assets\n#        cd assets\n#        7z x ../assets.7z\n#        cd ..\n#        rm assets.7z\n\n    - name: Configure\n      shell: bash\n      run: |\n        export PATH=$VITASDK/bin:$PATH\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} .\n\n    - name: Build\n      shell: bash\n      run: |\n        export PATH=$VITASDK/bin:$PATH\n        cmake --build build --target all -j4\n\n    - name: Create Package\n      if: success()\n      id: create_package\n      shell: bash\n      run: |\n        cd build\n        mkdir package\n        mkdir \"package/${{ matrix.config.subdir_name }}\"\n        cp ../changelog.txt \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../LICENSE \"package/${{ matrix.config.subdir_name }}/License.TheXTech.txt\"\n        cat ../docs/README_VITA.md ../README.md >> \"package/${{ matrix.config.subdir_name }}/README.md\"\n        cat ../docs/README_VITA.RUS.md ../README.RUS.md >> \"package/${{ matrix.config.subdir_name }}/README.RUS.md\"\n        cat ../docs/README_VITA.ESP.md ../README.ESP.md >> \"package/${{ matrix.config.subdir_name }}/README.ESP.md\"\n        cp thextech.vpk \"package/${{ matrix.config.subdir_name }}/\"\n        cd package\n        zip -9 -r \"thextech-vita-${BRANCH_NAME}.zip\" \"${{ matrix.config.subdir_name }}\"\n        rm -Rf \"${{ matrix.config.subdir_name }}\"\n        cd ../..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.zip\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        for q in ./build/package/*.zip; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n        done\n        if [ \"${BRANCH_NAME}\" = \"main\" ]; then\n          UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} build/thextech.vpk -o thextech-vitadb-nightly.vpk;\"\n        fi\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/wii-ci.yml",
    "content": "name: Wii CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci-homebrew\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ubuntu-latest\n    container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest\n    env:\n        DEVKITPRO: /opt/devkitpro\n        DEVKITPPC: /opt/devkitpro/devkitPPC\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"Wii build\",\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Wii.cmake \\\n            -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wii-local \\\n            -DCMAKE_INSTALL_PREFIX=$DEVKITPRO/portlibs/wii \\\n            -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON\",\n\n            deps_cmdline: \"echo 'DevkitPro SDK is already pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name: \"thextech\",\n            subdir_name: \"thextech-wii\",\n            upload_directory: \"www/wii/\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Install modified SDL2\n      run: |\n        cd ..\n        git clone https://github.com/Wohlstand/SDL.git -b wii-support --depth 1 SDL2\n        cd SDL2\n        mkdir build\n        cd build\n        cmake -G Ninja \\\n            -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Wii.cmake \\\n            -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wii-local \\\n            -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wii-local \\\n            -DSDL_ALTIVEC=OFF \\\n            -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \\\n            ..\n        cmake --build . --target all -j4\n        sudo cmake --build . --target install\n\n    - name: Configure\n      shell: bash\n      run: |\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} .\n\n    - name: Build\n      shell: bash\n      run: |\n        cmake --build build --target all -j4\n\n    - name: Check codesize and RAM usage\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        size build/output/bin/thextech.elf\n\n    - name: Create Package\n      if: success()\n      shell: bash\n      run: |\n        cd build\n        mkdir \"package/${{ matrix.config.subdir_name }}\"\n        cp ../changelog.txt \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../LICENSE \"package/${{ matrix.config.subdir_name }}/License.TheXTech.txt\"\n        mv \"package/meta.xml\" \"package/${{ matrix.config.subdir_name }}/\"\n        mv \"package/icon.png\" \"package/${{ matrix.config.subdir_name }}/\"\n        mv \"package/boot.dol\" \"package/${{ matrix.config.subdir_name }}/boot.dol\"\n        cat ../docs/README_WII.md ../README.md >> \"package/${{ matrix.config.subdir_name }}/README.md\"\n        cd package\n        zip -9 -r \"thextech-wii-${BRANCH_NAME}.zip\" \"${{ matrix.config.subdir_name }}\"\n        rm -Rf \"${{ matrix.config.subdir_name }}\"\n        cd ../..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.zip\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        for q in ./build/package/*.zip; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n        done\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/wiiu-ci.yml",
    "content": "name: Wii U CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci-homebrew\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ubuntu-latest\n    container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest\n    env:\n        DEVKITPRO: /opt/devkitpro\n        DEVKITPPC: /opt/devkitpro/devkitPPC\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"Wii U build\",\n\n            extra_options: \"-DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/WiiU.cmake \\\n            -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wiiu-local \\\n            -DSDL2_DIR=/opt/devkitpro/portlibs/wiiu-local/lib/cmake/SDL2 \\\n            -DCMAKE_INSTALL_PREFIX=$DEVKITPRO/portlibs/wiiu\",\n\n            deps_cmdline: \"echo 'DevkitPro SDK is already pre-installed'\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            executable_name: \"thextech\",\n            assets_url: \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z\",\n            subdir_name: \"thextech-wiiu\",\n            upload_directory: \"www/wiiu/\"\n          }\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Install modified SDL2\n      run: |\n        cd ..\n        git clone https://github.com/Wohlstand/SDL.git -b wiiu-fixes-2.28--06-2025 --depth 1 SDL2\n        cd SDL2\n        mkdir build\n        cd build\n        cmake -G Ninja \\\n            -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/WiiU.cmake \\\n            -DCMAKE_INSTALL_PATH=/opt/devkitpro/portlibs/wiiu-local \\\n            -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wiiu \\\n            -DSDL_ALTIVEC=OFF \\\n            -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \\\n            ..\n        cmake --build . --target all -j4\n        sudo cmake --build . --target install\n\n    - name: Configure\n      shell: bash\n      run: |\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} .\n\n    - name: Build\n      shell: bash\n      run: |\n        cmake --build build --target all -j4\n\n    - name: Create Package\n      if: success()\n      shell: bash\n      run: |\n        cd build\n        mkdir package\n        mkdir \"package/${{ matrix.config.subdir_name }}\"\n        cp ../changelog.txt \"package/${{ matrix.config.subdir_name }}/\"\n        cp ../LICENSE \"package/${{ matrix.config.subdir_name }}/License.TheXTech.txt\"\n        cp meta.xml \"package/${{ matrix.config.subdir_name }}/\"\n        cp icon.png \"package/${{ matrix.config.subdir_name }}/\"\n        cp thextech.rpx \"package/${{ matrix.config.subdir_name }}/thextech.rpx\"\n        cp thextech.wuhb \"package/${{ matrix.config.subdir_name }}/thextech.wuhb\"\n        cat ../docs/README_WIIU.md ../README.md >> \"package/${{ matrix.config.subdir_name }}/README.md\"\n        cd package\n        zip -9 -r \"thextech-wiiu-${BRANCH_NAME}.zip\" \"${{ matrix.config.subdir_name }}\"\n        rm -Rf \"${{ matrix.config.subdir_name }}\"\n        cd ../..\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.zip\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        for q in ./build/package/*.zip; do\n            UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n        done\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".github/workflows/windows-ci.yml",
    "content": "name: Windows CI\n\non:\n  push:\n    branches:\n      - main\n      - stable*\n      - versus-ci\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    name: \"${{ matrix.config.name }} | ${{ matrix.config.build_type }}\"\n    runs-on: ${{ matrix.config.os }}\n    container: ${{ matrix.config.container }}\n    strategy:\n      fail-fast: false\n      matrix:\n        config:\n        - {\n            name: \"Windows - 64-bit\",\n            os: windows-latest,\n\n            extra_options: \"-DCMAKE_PREFIX_PATH=C:/WohlMinGWw64/mingw64 \\\n            -DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_windows_mingw_toolchain_x64.cmake\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            extra_path: \"/c/WohlMinGWw64/mingw64/bin\",\n            executable_name_suffix: \"-win64\",\n            package_filename_suffix: \"win64\",\n            upload_directory: \"www/win32/\",\n            mingw_download: \"https://wohlsoft.ru/docs/Software/MinGW/x86_64-12.2.0-release-posix-seh-rt_v10-rev1.7z\", mingw_install_dir: \"C:/WohlMinGWw64/\",\n            ninja_download: \"https://wohlsoft.ru/docs/Software/Ninja-Build/ninja-win.zip\", ninja_install_dir: \"C:/WohlMinGWw64/mingw64/bin\",\n            lftp_download: \"https://wohlsoft.ru/docs/Software/lftp-4.4.15.win64-openssl-1.0.1g.7z\", lftp_install_dir: \"C:/WohlMinGWw64/mingw64/\"\n          }\n        - {\n            name: \"Windows - 32-bit\",\n            os: windows-latest,\n\n            extra_options: \"-DCMAKE_PREFIX_PATH=C:/WohlMinGWw64/mingw32 \\\n            -DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_windows_mingw_toolchain_x32.cmake\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            extra_path: \"/c/WohlMinGWw64/mingw32/bin\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"win32\",\n            upload_directory: \"www/win32/\",\n            mingw_download: \"https://wohlsoft.ru/docs/Software/MinGW/i686-12.2.0-release-posix-dwarf-rt_v10-rev1.7z\", mingw_install_dir: \"C:/WohlMinGWw64/\",\n            ninja_download: \"https://wohlsoft.ru/docs/Software/Ninja-Build/ninja-win.zip\", ninja_install_dir: \"C:/WohlMinGWw64/mingw32/bin\",\n            lftp_download: \"https://wohlsoft.ru/docs/Software/lftp-4.4.15.win64-openssl-1.0.1g.7z\", lftp_install_dir: \"C:/WohlMinGWw64/mingw32/\"\n          }\n        - {\n            name: \"Windows - 32-bit (legacy floats)\",\n            os: windows-latest,\n\n            extra_options: \"-DCMAKE_PREFIX_PATH=C:/WohlMinGWw64/mingw32 \\\n            -DTHEXTECH_EXECUTABLE_NAME=thextech \\\n            -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_windows_mingw_toolchain_x32.cmake \\\n            -DTHEXTECH_FIXED_POINT=OFF\",\n\n            generator: \"Ninja\",\n            build_type: \"MinSizeRel\",\n            extra_path: \"/c/WohlMinGWw64/mingw32/bin\",\n            executable_name_suffix: \"\",\n            package_filename_suffix: \"win32-legacy-floats\",\n            upload_directory: \"www/win32/\",\n            mingw_download: \"https://wohlsoft.ru/docs/Software/MinGW/i686-12.2.0-release-posix-dwarf-rt_v10-rev1.7z\", mingw_install_dir: \"C:/WohlMinGWw64/\",\n            ninja_download: \"https://wohlsoft.ru/docs/Software/Ninja-Build/ninja-win.zip\", ninja_install_dir: \"C:/WohlMinGWw64/mingw32/bin\",\n            lftp_download: \"https://wohlsoft.ru/docs/Software/lftp-4.4.15.win64-openssl-1.0.1g.7z\", lftp_install_dir: \"C:/WohlMinGWw64/mingw32/\"\n          }\n        - {\n            name: \"Windows - ARM64\",\n            os: windows-2022,\n            extra_options: \"-A ARM64 -DTHEXTECH_EXECUTABLE_NAME=thextech\",\n            generator: \"Visual Studio 17 2022\",\n            build_type: \"MinSizeRel\",\n            extra_path: \"/c/WohlLFTP/bin\",\n            executable_name_suffix: \"-arm64\",\n            package_filename_suffix: \"arm64\",\n            upload_directory: \"www/win-arm/\",\n            lftp_download: \"https://wohlsoft.ru/docs/Software/lftp-4.4.15.win64-openssl-1.0.1g.7z\", lftp_install_dir: \"C:/WohlLFTP/\"\n          }\n\n\n    steps:\n    - name: Check for the upload support\n      id: upload-check\n      shell: bash\n      run: |\n        if [[ \"${{ secrets.builds_login }}\" != '' && \\\n              \"${{ secrets.builds_password }}\" != '' && \\\n              \"${{ secrets.builds_host }}\" != '' ]]; then\n          echo \"available=true\" >> $GITHUB_OUTPUT;\n        else\n          echo \"available=false\" >> $GITHUB_OUTPUT;\n        fi\n\n    - name: Install Dependencies\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.deps_cmdline }}\" ]]; then\n          eval ${{ matrix.config.deps_cmdline }}\n        fi\n        cmake --version\n\n    - uses: TheXTech/checkout@v0.1\n\n    - uses: TheXTech/branch-name@v0.1\n\n\n    - name: Pull submodules\n      shell: bash\n      run: |\n        git submodule update --init --recursive\n\n    - name: Download MinGW\n      if: matrix.config.mingw_download\n      shell: bash\n      run: C:\\\\msys64\\\\usr\\\\bin\\\\wget.exe -d -nv -t 5 -O mingw.7z \"${{ matrix.config.mingw_download }}\"\n\n    - name: Extract MinGW\n      if: matrix.config.mingw_install_dir\n      shell: bash\n      run: |\n        7z x mingw.7z -o\"${{ matrix.config.mingw_install_dir }}\"\n\n    - name: Download Ninja\n      if: matrix.config.ninja_download\n      shell: bash\n      run: C:\\\\msys64\\\\usr\\\\bin\\\\wget.exe -d -nv -t 5 -O ninja.zip \"${{ matrix.config.ninja_download }}\"\n\n    - name: Extract Ninja\n      if: matrix.config.ninja_install_dir\n      shell: bash\n      run: |\n        7z x ninja.zip -o\"${{ matrix.config.ninja_install_dir }}\"\n\n    - name: Download LFTP\n      if: matrix.config.lftp_download\n      shell: bash\n      run: C:\\\\msys64\\\\usr\\\\bin\\\\wget.exe -d -nv -t 5 -O lftp.7z \"${{ matrix.config.lftp_download }}\"\n\n    - name: Extract LFTP\n      if: matrix.config.lftp_install_dir\n      shell: bash\n      run: |\n        7z x lftp.7z bin etc -o\"${{ matrix.config.lftp_install_dir }}\"\n\n    - name: Download SMBX assets\n      shell: bash\n      run: C:\\\\msys64\\\\usr\\\\bin\\\\wget.exe -d -nv -t 5 -O smbx13.7z \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z\"\n\n    - name: Download AoD assets\n      shell: bash\n      run: C:\\\\msys64\\\\usr\\\\bin\\\\wget.exe -d -nv -t 5 -O aod.7z \"https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z\"\n\n    - name: Unpack all assets\n      shell: bash\n      run: |\n        mkdir -p smbx13\n        cd smbx13\n        7z x ../smbx13.7z\n        cd ..\n        rm smbx13.7z\n        mkdir -p aod\n        cd aod\n        7z x ../aod.7z\n        cd ..\n        rm aod.7z\n\n    - name: Apply update to translations\n      shell: bash\n      run: |\n        ASSETS_ROOT1=\"$PWD/smbx13\"\n        ASSETS_ROOT2=\"$PWD/aod\"\n        cd .github/ci-helper\n        bash translate_update.sh \"${ASSETS_ROOT1}\"\n        bash translate_update.sh \"${ASSETS_ROOT2}\"\n        cd ../..\n\n    - name: Configure\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n          echo \"PATH environment: ${PATH}\"\n        fi\n        if [[ \"${{ secrets.DISCORD_APP_ID }}\" != '' ]]; then\n          LOCAL_EXTRA_SETUP=\"-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\\\"${{ secrets.DISCORD_APP_ID }}\\\"\"\n        fi\n        cmake -B build -G \"${{ matrix.config.generator }}\" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} .\n\n    - name: Build\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        export MAKEFLAGS=--keep-going\n        cmake --build build --config ${{ matrix.config.build_type }} --parallel 3\n\n    - name: List dependent libraries\n      if: success() && runner.os == 'Linux'\n      shell: bash\n      run: |\n        file build/output/bin/thextech\n        ldd build/output/bin/thextech\n\n\n    - name: Create Package\n      if: success()\n      shell: bash\n      # ======================================= Adventures of Demo =======================================\n      # The side game about Demo and siblings from the A2XT universe by raocow and his fan community.\n      # ======================================= Super Mario Bros. X - a fan-game =======================================\n      # Was made in 2009 by Andrew Spinks \"Redigit\", and supported up to 2011 by it's original author.\n      run: |\n        bash .github/ci-helper/pack-game.sh \\\n            \"${{ runner.os }}\" \\\n            \"thextech-adventures-of-demo\" \\\n            \"advdemo${{ matrix.config.executable_name_suffix }}\" \\\n            \"thextech-adventures-of-demo-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}\" \\\n            \"aod\"\n\n        bash .github/ci-helper/pack-game.sh \\\n            \"${{ runner.os }}\" \\\n            \"thextech-super-mario-bros-x\" \\\n            \"smbx${{ matrix.config.executable_name_suffix }}\" \\\n            \"thextech-super-mario-bros-x-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}\" \\\n            \"smbx13\"\n\n        bash .github/ci-helper/pack-game.sh \\\n            \"${{ runner.os }}\" \\\n            \"thextech-bin\" \\\n            \"thextech${{ matrix.config.executable_name_suffix }}\" \\\n            \"thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}\" \\\n            \"none\"\n\n    - name: Upload artifact\n      if: success()\n      uses: actions/upload-artifact@v4\n      continue-on-error: true\n      with:\n        path: build/package/*.7z\n        name: ${{ matrix.config.name }} ${{ matrix.config.build_type }}\n\n    - name: Deploy to builds.wohlsoft.ru\n      if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true'\n      continue-on-error: true\n      shell: bash\n      run: |\n        if [[ ! -z \"${{ matrix.config.extra_path }}\" ]]; then\n          export PATH=${{ matrix.config.extra_path }}:${PATH}\n        fi\n        UPLOAD_LIST=\"set ssl:verify-certificate no;\"\n        if [[ \"${{ runner.os }}\" == 'Windows' ]]; then\n            for q in ./build/package/*.7z; do\n                UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n            done\n        elif [[ \"${{ runner.os }}\" == 'Linux' ]]; then\n            for q in ./build/package/*.tar.gz; do\n                UPLOAD_LIST=\"${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;\"\n            done\n        fi\n        lftp -e \"${UPLOAD_LIST} exit\" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }}\n\n    - name: List Build Directory\n      if: always()\n      shell: bash\n      run: |\n        git status\n        ls -lR build\n"
  },
  {
    "path": ".gitignore",
    "content": "# This file is used to ignore files which are generated\n# ----------------------------------------------------------------------------\n\n*~\n*.autosave\n*.a\n*.core\n*.moc\n*.o\n*.obj\n*.orig\n*.rej\n*.so\n*.so.*\n*_pch.h.cpp\n*_resource.rc\n*.qm\n.#*\n*.*#\ncore\n!core/\ntags\n.DS_Store\n.directory\n*.debug\nMakefile*\n*.prl\n*.app\nmoc_*.cpp\nui_*.h\nqrc_*.cpp\nThumbs.db\n*.res\n/.qmake.cache\n/.qmake.stash\n\n# qtcreator generated files\n*.pro.user*\n*.txt.user*\n\n# xemacs temporary files\n*.flc\n\n# Vim temporary files\n.*.swp\n\n# Visual Studio generated files\n*.ib_pdb_index\n*.idb\n*.ilk\n*.pdb\n*.sln\n*.suo\n*.vcproj\n*vcproj.*.*.user\n*.ncb\n*.sdf\n*.opensdf\n*.vcxproj\n*vcxproj.*\n\nbuild-*\n\nbuild/\n\n# MinGW generated files\n*.Debug\n*.Release\n\n# Python byte code\n*.pyc\n\n# Binaries\n# --------\n*.dll\n*.exe\n\n# CLion and like environments\n.idea/*\ncmake-build-*/*\n\n.vscode/*\n.vs/*\nCMakeSettings.json\n\n# PVS-Studio related stuff\n*.PVS-Studio.*\n.PVS-Studio/\n\n# Vendored assets packages shouldn't appear at the GIT history\n/assets/\n\n# clangd related stuff\n.cache/\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"3rdparty/PGE_File_Formats\"]\n\tpath = 3rdparty/PGE_File_Formats\n\turl = https://github.com/WohlSoft/PGE-File-Library-STL.git\n\tbranch = wip-mdx\n[submodule \"3rdparty/LuaJIT\"]\n\tpath = 3rdparty/LuaJIT\n\turl = https://github.com/WohlSoft/LuaJIT.git\n\tbranch = v2.1\n[submodule \"3rdparty/luabind\"]\n\tpath = 3rdparty/luabind\n\turl = https://github.com/WohlSoft/luabind-deboostified.git\n\tbranch = master\n[submodule \"3rdparty/AudioCodecs\"]\n\tpath = 3rdparty/AudioCodecs\n\turl = https://github.com/WohlSoft/AudioCodecs.git\n\tbranch = master\n[submodule \"3rdparty/SDL-Mixer-X\"]\n\tpath = 3rdparty/SDL-Mixer-X\n\turl = https://github.com/WohlSoft/SDL-Mixer-X.git\n\tbranch = master\n[submodule \"3rdparty/FreeImageLite\"]\n\tpath = 3rdparty/FreeImageLite\n\turl = https://github.com/WohlSoft/libFreeImage.git\n\tbranch = master\n[submodule \"3rdparty/freetype\"]\n\tpath = 3rdparty/freetype\n\turl = https://github.com/TheXTech/freetype.git\n\tbranch = master\n[submodule \"3rdparty/thextech-discord-rpc\"]\n\tpath = 3rdparty/thextech-discord-rpc\n\turl = https://github.com/TheXTech/thextech-discord-rpc.git\n\tbranch = main\n[submodule \"3rdparty/glew-cmake\"]\n\tpath = 3rdparty/glew-cmake\n\turl = https://github.com/TheXTech/thextech-glew-cmake.git\n\tbranch = master\n[submodule \"3rdparty/DirManager\"]\n\tpath = 3rdparty/DirManager\n\turl = https://github.com/WohlSoft/DirManager.git\n\tbranch = master\n[submodule \"3rdparty/IniProcessor\"]\n\tpath = 3rdparty/IniProcessor\n\turl = https://github.com/WohlSoft/IniProcessing.git\n\tbranch = master\n[submodule \"3rdparty/FileMapper\"]\n\tpath = 3rdparty/FileMapper\n\turl = https://github.com/WohlSoft/FileMapper.git\n\tbranch = master\n[submodule \"3rdparty/angle-shader-translator\"]\n\tpath = 3rdparty/angle-shader-translator\n\turl = https://github.com/TheXTech/angle-shader-translator-library.git\n\tbranch = dist-no-spirv\n[submodule \"3rdparty/luau\"]\n\tpath = 3rdparty/luau\n\turl = https://github.com/TheXTech/luau.git\n\tbranch = master\n[submodule \"mbediso\"]\n\tpath = 3rdparty/mbediso\n\turl = https://github.com/ds-sloth/mbediso/\n\tbranch = main\n[submodule \"3rdparty/SDL_net\"]\n\tpath = 3rdparty/SDL_net\n\turl = https://github.com/TheXTech/SDL_net.git\n\tbranch = SDL2\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.5...3.10)\n\nif(POLICY CMP0069) # Allow CMAKE_INTERPROCEDURAL_OPTIMIZATION (lto) to be set\n    cmake_policy(SET CMP0069 NEW)\n    set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)\nendif()\n\nif(POLICY CMP0079) # Allow linking subprojects against each other\n    cmake_policy(SET CMP0079 NEW)\n    set(CMAKE_POLICY_DEFAULT_CMP0079 NEW)\nendif()\n\nproject(TheXTech LANGUAGES C CXX)\n\nif(APPLE)\n    enable_language(OBJC)\nendif()\n\ninclude(CheckCCompilerFlag)\ninclude(CheckCXXCompilerFlag)\ninclude(ExternalProject)\ninclude(CheckLibraryExists)\ninclude(CheckFunctionExists)\ninclude(GNUInstallDirs)\ninclude(FindBacktrace)\ninclude(TestBigEndian)\ninclude(CheckIncludeFile)\n\n\ninclude(cmake/git_info.cmake)\nmessage(\"== Current GIT hash [${GIT_COMMIT_HASH}], branch [${GIT_BRANCH}], package [${PACKAGE_SUFFIX}] ==\")\n\nif(NOT WIN32 AND NOT NINTENDO_SWITCH AND (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX))\n    check_cxx_compiler_flag(\"-no-pie\" HAS_NO_PIE)\nendif()\n\nfunction(pge_set_nopie _target)\n    set_target_properties(${_target} PROPERTIES\n        POSITION_INDEPENDENT_CODE False\n    )\n    if(HAS_NO_PIE AND (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX))\n        set_property(TARGET ${_target} APPEND_STRING PROPERTY LINK_FLAGS \" -no-pie\")\n    endif()\nendfunction()\n\nset(CMAKE_CXX_STANDARD 11)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_POSITION_INDEPENDENT_CODE FALSE)\n\nset(CMAKE_INSTALL_RPATH \".\")\nset(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)\n\nif(EMSCRIPTEN)\n    set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)\nendif()\n\nif(APPLE)\n    if(CMAKE_HOST_SYSTEM_VERSION VERSION_LESS 9)\n        set(XTECH_PLIST_NAME \"thextech.plist.tiger.in\")\n        set(XTECH_DEFAULT_ICNS \"resources/tiger/thextech.icns\")\n    else()\n        set(XTECH_PLIST_NAME \"thextech.plist.in\")\n        set(XTECH_DEFAULT_ICNS \"resources/thextech.icns\")\n    endif()\n\n    if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET STREQUAL \"\")\n        if(CMAKE_HOST_SYSTEM_VERSION VERSION_LESS 9)\n            set(THEXTECH_MACOS_MIN_VERSION \"10.4\")\n        else()\n            set(THEXTECH_MACOS_MIN_VERSION \"10.9\")\n        endif()\n    else()\n        set(THEXTECH_MACOS_MIN_VERSION \"${CMAKE_OSX_DEPLOYMENT_TARGET}\")\n    endif()\n\n    message(\"-> Mac OS X minimal version: ${THEXTECH_MACOS_MIN_VERSION}\")\nendif()\n\nif(ANDROID)\n    set(DEPENDENCIES_INSTALL_DIR ${CMAKE_BINARY_DIR}/output-deps)\n    set(FDROID_BUILD OFF CACHE BOOL \"Is this build a part of the F-Droid workflow?\")\n    mark_as_advanced(FDROID_BUILD)\nelse()\n    set(DEPENDENCIES_INSTALL_DIR ${CMAKE_BINARY_DIR}/output)\nendif()\n\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib)\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib)\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/bin)\n\nforeach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})\n    message(\"--> ${OUTPUTCONFIG}\")\n    string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)\n    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} \"${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}\")\n    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} \"${CMAKE_LIBRARY_OUTPUT_DIRECTORY}\")\n    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\")\nendforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES )\n\nset(PGE_INSTALL_DIRECTORY \"TheXTech\")\n\ninclude(cmake/build_props.cmake)\n\nif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)\n    # Update if necessary\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -ffloat-store\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -ffloat-store\")\nendif()\n\nif(WIN32 AND NOT EMSCRIPTEN)\n    set(CMAKE_SHARED_LIBRARY_PREFIX \"\")\nendif()\n\nif(UNIX)\n    check_include_file(/opt/vc/include/bcm_host.h BCMHOST_H)\nendif()\n\nstring(TOLOWER \"${CMAKE_BUILD_TYPE}\" CMAKE_BUILD_TYPE_LOWER)\n\nif(CMAKE_BUILD_TYPE_LOWER STREQUAL \"debug\")\n    add_definitions(-DDEBUG_BUILD)\n    if(CMAKE_COMPILER_IS_GNUCXX)\n        add_definitions(-D_GLIBCXX_DEBUG=1 -D_GLIBCXX_ASSERTIONS=1)\n    endif()\nendif()\n\n# Version\ninclude(version.cmake)\n# Default GIT version\ninclude(cmake/git_version.cmake)\n\n\nconfigure_file(lib/CrashHandler/backtrace.h.in\n               generated-include/lib/CrashHandler/backtrace.h)\ninclude_directories(${CMAKE_CURRENT_BINARY_DIR}/generated-include)\n\nif(NINTENDO_SWITCH)\n    include_directories($ENV{DEVKITPRO}/portlibs/switch/include)\nendif()\n\n#if(WIN32 OR EMSCRIPTEN OR HAIKU)\n#    set(USE_SYSTEM_LIBS_DEFAULT OFF)\n#else()\n#    set(USE_SYSTEM_LIBS_DEFAULT ON)\n#endif()\nset(USE_SYSTEM_LIBS_DEFAULT OFF)\n\n\nif(UNIX)\n    option(PORTMASTER \"Make a special build for PortMaster\" OFF)\nendif()\n\nif(HAIKU OR NINTENDO_SWITCH OR NINTENDO_WII OR NINTENDO_WIIU OR XTECH_MACOSX_TIGER OR PORTMASTER)\n    set(USE_SYSTEM_SDL2_DEFAULT ON)  # Should be used on Haiku, own SDL2 Haiku build on CMake is broken\nelse()\n    set(USE_SYSTEM_SDL2_DEFAULT ${USE_SYSTEM_LIBS_DEFAULT})\nendif()\n\nif(NINTENDO_DS)\n    set(THEXTECH_ENABLE_TTF_SUPPORT_DEFAULT OFF)\nelse()\n    set(THEXTECH_ENABLE_TTF_SUPPORT_DEFAULT ON)\nendif()\n\nif((APPLE AND NOT XTECH_MACOSX_TIGER) OR WIN32 OR (CMAKE_SYSTEM_NAME STREQUAL \"Linux\"))\n    set(THEXTECH_USE_ANGLE_TRANSLATOR_DEFAULT ON)\nelse()\n    set(THEXTECH_USE_ANGLE_TRANSLATOR_DEFAULT OFF)\nendif()\n\noption(USE_SYSTEM_LIBS \"Use dependent libraries like SDL2, FreeImageLite and MixerX, installed in the system\" ${USE_SYSTEM_LIBS_DEFAULT})\noption(USE_SYSTEM_SDL2 \"Use SDL2 from a system even prefering system libraries\" ${USE_SYSTEM_SDL2_DEFAULT})\n\noption(USE_STATIC_LIBC \"Link libc and libstdc++ statically\" OFF)\n\nif(NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_DS)\n    set(USE_STATIC_LIBC ON)\nendif()\n\nif(NINTENDO_3DS)\n    option(THEXTECH_CUSTOM_AUDIO_LIBRARY \"Use the custom audio library instead of MixerX\" OFF)\nendif()\n\nif(ANDROID OR VITA OR NINTENDO_DS OR NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_SWITCH OR PGE_MIN_PORT OR THEXTECH_NO_SDL_BUILD OR PORTMASTER)\n    set(THEXTECH_FORCE_FULLSCREEN ON)\nelse()\n    option(THEXTECH_FORCE_FULLSCREEN \"Force the game to only use fullscreen mode\" OFF)\nendif()\n\nif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Linux\" AND \"${TARGET_PROCESSOR}\" MATCHES \"arm.*\")\n    message(\"-- Desktop OpenGL build will be disabled on unsupported platform (${CMAKE_SYSTEM_NAME} on ${TARGET_PROCESSOR})\")\n    set(THEXTECH_BUILD_GL_DESKTOP_DEFAULT OFF)\nelse()\n    set(THEXTECH_BUILD_GL_DESKTOP_DEFAULT ON)\nendif()\n\noption(THEXTECH_BUILD_GL_DESKTOP_MODERN \"On SDL builds, include the OpenGL 2.0+ Core/Compatibility renderer\" ${THEXTECH_BUILD_GL_DESKTOP_DEFAULT})\noption(THEXTECH_BUILD_GL_ES_MODERN \"On SDL builds, include the OpenGL ES 2.0+ renderer\" ON)\noption(THEXTECH_BUILD_GL_DESKTOP_LEGACY \"On SDL builds, include the OpenGL 1.1+ renderer\" ${THEXTECH_BUILD_GL_DESKTOP_DEFAULT})\noption(THEXTECH_BUILD_GL_ES_LEGACY \"On SDL builds, include the OpenGL ES 1.1 renderer\" OFF)\n\noption(THEXTECH_USE_ANGLE_TRANSLATOR \"On modern OpenGL builds, include the ANGLE shader translator to improve shader support in OpenGL 3.0+ Core/Compatibility profiles\" ${THEXTECH_USE_ANGLE_TRANSLATOR_DEFAULT})\n\noption(RANGE_ARR_USE_HEAP \"Store data of RangeArr<> template in a heap\" OFF)\noption(RANGE_ARR_UNSAFE_MODE \"Disable all range checks at RangeArr<> template\" OFF)\n\noption(ENABLE_ANTICHEAT_TRAP \"Enable anti-cheating trap for the \\\"redigitiscool\\\" cheat code\" OFF)\noption(ENABLE_OLD_CREDITS \"Use original Redigit's credits without changes\" OFF)\noption(ENABLE_LOGGING \"Enable debug logging written into a file (may not work on some platfors)\" ON)\n\noption(THEXTECH_ENABLE_LUA \"Enable lua scripting support\" OFF)\noption(THEXTECH_ENABLE_LUAU \"Enable luau build testing\" OFF)\noption(THEXTECH_ENABLE_LUNA_AUTOCODE \"Enable LunaDLL Autocode scripting support\" ON)\n\noption(THEXTECH_ENABLE_SDL_NET \"Enable SDL_net build testing\" OFF)\n\noption(THEXTECH_ENABLE_EDITOR \"Enable runtime support for editor features\" ON)\n\noption(THEXTECH_ENABLE_TTF_SUPPORT \"Enable TTF fonts support (FreeType required)\" ${THEXTECH_ENABLE_TTF_SUPPORT_DEFAULT})\n\noption(THEXTECH_CLI_BUILD \"Minimal CLI build for testing and benchmarking on desktop platforms\" OFF)\noption(THEXTECH_NO_SDL_BUILD \"Do not use SDL in the CLI build; useful as a starting point for new ports\" OFF)\n\noption(THEXTECH_ENABLE_WIP_FEATURES \"Enable experimental and incomplete features (disabled by default)\" OFF)\nmark_as_advanced(THEXTECH_ENABLE_WIP_FEATURES)\n\noption(THEXTECH_NO_BUILD_DATE \"Disables the using of the build timestamp in order to make the build reproducible\" OFF)\n\noption(THEXTECH_ENABLE_AUDIO_FX \"Enable real-time audio effects support\" ON)\n\noption(THEXTECH_FIXED_POINT \"Experimental: use fixed-point arithmetic for gameplay logic\" ON)\n\noption(ENABLE_ADDRESS_SANITIZER \"Enable the Address Sanitizer GCC feature\" OFF)\n\n# ============ Customization ==============\nset(LIB_SRC_EXTRA)\n\nset(THEXTECH_PACKAGE_NAME \"thextech\" CACHE STRING \"Name of package archive file\")\nset(THEXTECH_EXECUTABLE_NAME \"thextech\" CACHE STRING \"Name of executable file\")\nset(THEXTECH_DIRECTORY_PREFIX \"TheXTech\" CACHE STRING \"Name used for directories on installed targets (should vary across forks, not asset packs)\")\nset(THEXTECH_INSTALLER_PACKAGE_NAME \"${THEXTECH_EXECUTABLE_NAME}\" CACHE STRING \"The package name for the package manager\")\n\nif(APPLE)\n    set(THEXTECH_PRELOAD_ENVIRONMENT \"\" CACHE STRING \"Path to resources root to pack\")\n    option(THEXTECH_PRELOAD_ENVIRONMENT_MANUALLY \"Make application look for assets inside the bundle, but don't actually put any assets. The 'Template' app will be produced for manual addition of assets.\" OFF)\n    set(THEXTECH_BUNDLE_NAME \"TheXTech\" CACHE STRING \"Name of bundle folder\")\n    set(THEXTECH_ICON_NAME \"thextech.icns\" CACHE STRING \"Name of bundle icon file\")\n    set(THEXTECH_CUSTOM_ICON_PATH \"\" CACHE STRING \"Name of icon file to pack into the bundle\")\n    if(NOT THEXTECH_CUSTOM_ICON_PATH STREQUAL \"\")\n        message(\"Use custom macOS icon: ${THEXTECH_CUSTOM_ICON_PATH}\")\n        list(APPEND LIB_SRC_EXTRA\n            \"${THEXTECH_CUSTOM_ICON_PATH}\"\n        )\n    endif()\nendif()\n\nif(NOT APPLE AND NOT ANDROID AND NOT EMSCRIPTEN)\n    set(THEXTECH_UNIX_INSTALL TRUE)\nendif()\n\n# ============ Customization ==end=========\n\n# ============ Platform-conf ==============\n\nif(EMSCRIPTEN)\n    # config for web app deployment manifest\n    set(THEXTECH_MANIFEST_NAME \"TheXTech Engine ${THEXTECH_VERSION_STRING}\" CACHE STRING \"Web app manifest name, used as title after installation\")\n    set(THEXTECH_MANIFEST_ID \"wohlsoft-thextech\" CACHE STRING \"Web app ID, uniquely identifies installed app\")\n    set(THEXTECH_MANIFEST_DESC \"\" CACHE STRING \"Web app description\")\n    set(THEXTECH_DEPLOY_URL \"http://localhost:8080/\" CACHE STRING \"Fully qualified HTTPS URL where application will be deployed (HTTP may only be used for local testing)\")\n\n    # versioned ID\n    if(GIT_BRANCH STREQUAL \"main\")\n        set(GIT_BRANCH_IF_NOT_MAIN \"\")\n    else()\n        set(GIT_BRANCH_IF_NOT_MAIN \"-${GIT_BRANCH}\")\n    endif()\n\n    set(THEXTECH_MANIFEST_ID_V \"${THEXTECH_MANIFEST_ID}${GIT_BRANCH_IF_NOT_MAIN}${THEXTECH_VERSION_REL}\")\n\n    string(CONFIGURE \"${THEXTECH_MANIFEST_NAME}\" THEXTECH_MANIFEST_NAME_OUT)\n    string(CONFIGURE \"${THEXTECH_MANIFEST_ID_V}\"   THEXTECH_MANIFEST_ID_OUT)\n    string(CONFIGURE \"${THEXTECH_MANIFEST_DESC}\" THEXTECH_MANIFEST_DESC_OUT)\nendif()\n\nif(VITA)\n    message(\"Enabling PS Vita Support and fast-math flags.\")\n\n    # Fast Math flags for Vita, ensuring -DVITA is passed to the compiler.\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -ffast-math -mtune=cortex-a9 -mfpu=neon -DVITA\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -ffast-math -mtune=cortex-a9 -mfpu=neon -DVITA\")\n\n    # Disable annoying warning for Parameter passing.\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-psabi\")\n\n    set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL \"\" FORCE)\n\n    # note: to test GLESv2 on Vita, remove the below line, install VitaGL with shader support and Northfear's SDL2 fork, and enable USE_SYSTEM_SDL2\n    # As of August 2025, Northfear's SDL2 fork invalidly reports the OpenGL profile as Compatibility instead of ES, and VitaGL doesn't support clearing buffers, and its shader transpiler is imperfect\n    set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL \"\" FORCE)\nendif()\n\nif(NINTENDO_SWITCH)\n    message(\"Enabling Nintendo Switch Support and fast-math flags.\")\n\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -DNINTENDO_SWITCH -D__SWITCH__\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -DNINTENDO_SWITCH -D__SWITCH__\")\nendif()\n\nif(NINTENDO_WII OR NINTENDO_3DS OR NINTENDO_DS)\n    set(PGE_MIN_PORT ON)\n    set(THEXTECH_NO_ARGV_HANDLING ON)\n    add_compile_definitions(PGE_MIN_PORT)\n\n    set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL \"\" FORCE)\nendif()\n\nif(NINTENDO_WIIU)\n    set(THEXTECH_NO_ARGV_HANDLING ON)\n    set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL \"\" FORCE)\nendif()\n\nif(PORTMASTER)\n    message(\"Special build for PortMaster.\")\n\n    set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_MODERN ON CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL \"\" FORCE)\n    set(PGE_SHARED_SDLMIXER OFF CACHE BOOL \"\" FORCE)\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -pthread\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -pthread\")\nendif()\n\nif(THEXTECH_CLI_BUILD)\n    set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL \"\" FORCE)\nendif()\n\nif(THEXTECH_CLI_BUILD OR PGE_MIN_PORT OR EMSCRIPTEN OR PORTMASTER)\n    set(RANGE_ARR_UNSAFE_MODE ON)\nendif()\n\nif(NINTENDO_DS)\n    set(THEXTECH_NO_SDL_BUILD ON CACHE BOOL \"\" FORCE)\nendif()\n\nif(ANDROID)\n    set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_LEGACY ON CACHE BOOL \"\")\nendif()\n\nif(APPLE OR WIN32)\n    set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL \"\" FORCE)\nendif()\n\n# 32-bit Android special case, temporarily disabled for some experiments\n# if(ANDROID AND CMAKE_SIZEOF_VOID_P EQUAL 4)\n#     set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL \"\" FORCE)\n# endif()\n\nif(EMSCRIPTEN)\n    set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_LEGACY      OFF CACHE BOOL \"\" FORCE)\nendif()\n\nif(NINTENDO_SWITCH)\n    set(THEXTECH_BUILD_GL_ES_MODERN      ON  CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_ES_LEGACY      OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL \"\" FORCE)\n    set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL \"\" FORCE)\nendif()\n\nif(THEXTECH_NO_SDL_BUILD)\n    set(THEXTECH_ENABLE_AUDIO_FX OFF CACHE BOOL \"\" FORCE)\n    set(MBEDISO_THREADS \"NONE\")\nelse()\n    set(MBEDISO_THREADS \"SDL2\")\nendif()\n\nif(EMSCRIPTEN)\n    message(\"Is EMSCRIPTEN!\")\n    set(PGE_PRELOAD_ENVIRONMENT \"/home/vitaly/.PGE_Project/_emscripten/thextech\" CACHE STRING \"Path to resources root to pack\")\n    set(CMAKE_EXECUTABLE_SUFFIX \".html\")\n#    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -msse -msse2\")\n    # -DIS_CXX -s USE_PTHREADS=1\n#    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -s WASM=1 -s 'BINARYEN_METHOD=\\\"native-wasm\\\"'\")\n    #set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -s EMTERPRETIFY=1 -s EMTERPRETIFY_ASYNC=1  -s 'EMTERPRETIFY_FILE=\\\"pge_engine.binary\\\"'\")\n    # -s \\\"EMTERPRETIFY_WHITELIST=['_main']\\\"\n    if(CMAKE_BUILD_TYPE_LOWER STREQUAL \"debug\" OR CMAKE_BUILD_TYPE_LOWER STREQUAL \"relwithdebinfo\")\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1\")\n    endif()\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -s USE_SDL=0\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -s USE_SDL=0\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s \\\"DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['\\\\$autoResumeAudioContext', '\\\\$dynCall']\\\"\") #-s USE_SDL=2\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s LLD_REPORT_UNDEFINED -s ERROR_ON_UNDEFINED_SYMBOLS=1 -s ASYNCIFY=1 -lidbfs.js\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s \\\"EXPORTED_FUNCTIONS=['_main', '_unlockLoadingCustomState']\\\"\")\n#    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -s 'BINARYEN_TRAP_MODE=\\\"clamp\\\"'\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=83886080 --no-heap-copy -s ALLOW_MEMORY_GROWTH=1\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_RUNTIME_METHODS=ccall\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s BINARYEN_EXTRA_PASSES=coalesce-locals\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s MAX_WEBGL_VERSION=2\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -s GL_ENABLE_GET_PROC_ADDRESS\")\n#    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -s \\\"EXTRA_EXPORTED_RUNTIME_METHODS=['Pointer_stringify']\\\"\")\n#    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} --embed-file \\\"${_LANGAUGES_TEMP_FOLDER}\\\"@\\\"languages/\\\"\")\n#    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} --preload-file \\\"${PGE_PRELOAD_CONFIG_PACK}\\\"@\\\"configs/${PGE_PRELOAD_CONFIG_PACK_NAME}/\\\"\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} --preload-file \\\"${PGE_PRELOAD_ENVIRONMENT}\\\"@\\\"/\\\"\")\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} --shell-file '${CMAKE_CURRENT_SOURCE_DIR}/resources/emscripten/shell_minimal.html'\")\n    # set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} --use-preload-cache\")\n    # set(LINK_FLAGS \"${LINK_FLAGS} -DIS_LINK -s USE_PTHREADS=1 -s FORCE_FILESYSTEM=1 --embed-file '${CMAKE_CURRENT_SOURCE_DIR}/@languages'\")\n\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fexceptions\") # bump to -fwasm-exceptions once widely supported\n    set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -fexceptions\") # bump to -fwasm-exceptions once widely supported\nendif()\n\n# ============ Platform-conf ==end=========\n\noption(PGEFL_QT_SUPPORT \"Build PGE-FL with Qt support [Unneeded]\" OFF)\nset(Moondust_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # Don't install PGE-FL libraries\nset(PGEFL_ENABLE_RWOPS ON)\n\nif(PGE_MIN_PORT)\n    set(PGEFL_DISABLE_SMBX38A ON)\nendif()\n\nadd_subdirectory(3rdparty/PGE_File_Formats)\nmark_as_advanced(WITH_UNIT_TESTS PGEFL_QT_SUPPORT)\n\nif(USE_STATIC_LIBC)\n    set(BUILD_SHARED_LIBS OFF)\nendif()\n\ninclude(lib/Allocator/pool-allocator.cmake)\ninclude(lib/pge_video_rec/pge-video-rec.cmake)\ninclude(3rdparty/DirManager/dirman.cmake)\n\nif(NOT VITA AND NOT NINTENDO_SWITCH AND NOT NINTENDO_3DS AND NOT NINTENDO_WII AND NOT NINTENDO_WIIU AND NOT NINTENDO_DS)\n    include(3rdparty/FileMapper/FileMapper.cmake)\nendif()\n\ninclude(lib/fmt/fmt.cmake)\ninclude(3rdparty/IniProcessor/IniProcessor.cmake)\ninclude(lib/Utils/Utils.cmake)\ninclude(lib/Logger/logger.cmake)\ninclude(lib/AppPath/app_path.cmake)\ninclude(lib/tclap/tclap.cmake)\ninclude(lib/md5/md5.cmake)\ninclude(lib/sdl_proxy/sdl_proxy.cmake)\nif(WIN32)\n    include(lib/CrashHandler/StackWalker/StackWalker.cmake)\nendif()\n\ninclude(cmake/library_zlib.cmake)\n\nadd_definitions(\"-DMOONDUST_LOGGER_FILENAME_PREFIX=\\\"TheXTech\\\"\")\n\nif(THEXTECH_ENABLE_TTF_SUPPORT)\n#    option(THEXTECH_ENABLE_TTF_HARFBUZZ \"Enable the HarfBuzz library use\" OFF)\n#    mark_as_advanced(THEXTECH_ENABLE_TTF_HARFBUZZ) # Temporarily disable\n#    if(THEXTECH_ENABLE_TTF_HARFBUZZ AND NOT NINTENDO_3DS)\n#        include(cmake/library_HarfBuzz.cmake)\n#    else()\n#        set(FT_DISABLE_HARFBUZZ ON)\n#    endif()\n    set(FT_DISABLE_HARFBUZZ ON)\n    include(cmake/library_FreeType.cmake)\nendif()\n\nif(PGE_MIN_PORT)\n    set(BUILD_SHARED_LIBS OFF CACHE BOOL \"\" FORCE)\n    set(ENABLE_UBSAN OFF CACHE BOOL \"\" FORCE)\nendif()\n\nif(THEXTECH_ENABLE_LUA)\n    include(cmake/library_luajit.cmake)\n    include(cmake/library_luabind.cmake)\nendif()\n\nif(NOT THEXTECH_CLI_BUILD AND NOT THEXTECH_NO_SDL_BUILD)\n    include(cmake/library_discord_rpc.cmake)\nendif()\n\nset(A2XT_LIBS pgefl ${UTILS_LIBS})\nset(A2XT_INCS src lib 3rdparty)\nset(A2XT_DEPS)\n\nadd_library(A2XT_Int INTERFACE)\n\nif(THEXTECH_NO_SDL_BUILD)\n    add_subdirectory(lib/sdl_proxy_rwops)\n    get_target_property(SDL_PROXY_RWOPS_INCS sdl_proxy_rwops INTERFACE_INCLUDE_DIRECTORIES)\n    target_include_directories(pgefl PRIVATE ${SDL_PROXY_RWOPS_INCS})\n    add_dependencies(pgefl sdl_proxy_rwops)\n    target_link_libraries(A2XT_Int INTERFACE sdl_proxy_rwops)\nendif()\n\nif(THEXTECH_ENABLE_TTF_SUPPORT)\n    list(APPEND A2XT_DEPS FREETYPE_Local)\n    list(APPEND A2XT_LIBS PGE_FreeType)\n    if(TARGET PGE_HarfBuzz)\n        list(APPEND A2XT_DEPS HARFBUZZ_Local)\n        list(APPEND A2XT_LIBS PGE_HarfBuzz)\n    endif()\nendif()\n\nif(THEXTECH_ENABLE_LUA)\n    list(APPEND A2XT_DEPS LuaBind_Local)\n    list(APPEND A2XT_INCS script/include)\nendif()\n\ntarget_compile_definitions(A2XT_Int INTERFACE\n    -DINI_PROCESSING_USE_MDX_PARSER  # IniProcessor should use the MDX tokenizer for lists\n)\n\ninclude(cmake/library_glew.cmake)\ninclude(cmake/library_syslibs.cmake)\n\nif(USE_SYSTEM_LIBS)\n    find_package(SDL2 REQUIRED)\n\n    find_library(MIXERX_LIB NAMES SDL2_mixer_ext)\n    find_path(MIXERX_HEAD_DIR SDL_mixer_ext.h PATH_SUFFIXES SDL2)\n\n    find_library(FREEIMAGELITE_LIB NAMES FreeImageLite)\n    find_path(FREEIMAGELITE_HEAD_DIR NAMES FreeImageLite.h)\n\n    if(MINGW AND NOT SDL2_LIBRARIES)\n        set(SDL2_LIBRARIES mingw32 SDL2main SDL2)\n    endif()\n\n    list(APPEND A2XT_LIBS ${FREEIMAGELITE_LIB} ${MIXERX_LIB} \"${SDL2_LIBRARIES}\" ${THEXTECH_SYSLIBS})\n    list(APPEND A2XT_INCS ${SDL2_INCLUDE_DIRS} ${MIXERX_HEAD_DIR} ${FREEIMAGELITE_HEAD_DIR})\n    target_include_directories(pgefl PRIVATE ${SDL2_INCLUDE_DIRS})\nelseif(NOT THEXTECH_NO_SDL_BUILD)\n    include(cmake/library_FreeImage.cmake)\n    list(APPEND A2XT_DEPS FreeImage_Local)\n    list(APPEND A2XT_LIBS PGE_FreeImage)\n\n    include(cmake/library_SDLMixerX.cmake)\n    list(APPEND A2XT_INCS \"${DEPENDENCIES_INSTALL_DIR}/include\" ${SDL2_INCLUDE_DIRS})\n    target_include_directories(pgefl PRIVATE ${SDL2_INCLUDE_DIRS})\n\n    # depend on RWops from AudioCodecs_Local SDL2 build\n    add_dependencies(pgefl AudioCodecs_Local)\n\n    if(NOT USE_SYSTEM_ZLIB) # Depend on ZLib at AudioCodecs\n        if(TARGET FREETYPE_Local)\n            add_dependencies(FREETYPE_Local AudioCodecs_Local)\n        endif()\n        add_dependencies(FreeImage_Local AudioCodecs_Local)\n    endif()\n\n    if(NOT THEXTECH_NO_MIXER_X)\n        list(APPEND A2XT_DEPS SDLMixerX_Local)\n    else()\n        list(APPEND A2XT_DEPS AudioCodecs_Local)\n    endif()\n\n    if(PGE_SHARED_SDLMIXER)\n        list(APPEND A2XT_LIBS PGE_SDLMixerX)\n    else()\n        list(APPEND A2XT_LIBS PGE_SDLMixerX_static)\n    endif()\n\n# UNIX seems to be too general -- how to target desktop Linux specifically?\n#    if(UNIX AND (THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_DESKTOP_LEGACY OR THEXTECH_BUILD_GL_ES_MODERN OR THEXTECH_BUILD_GL_ES_LEGACY))\n#        list(APPEND A2XT_LIBS GL)\n#    endif()\n\n    if(THEXTECH_ENABLE_VENDORED_GLEW)\n        list(APPEND A2XT_DEPS GLEW_Local)\n        list(APPEND A2XT_LIBS PGE_GLEW)\n    endif()\n\n    if(WIN32 AND (THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_DESKTOP_LEGACY))\n        # glu32 not needed or provided on ARM\n        if(\"${TARGET_PROCESSOR}\" STREQUAL \"i386\" OR \"${TARGET_PROCESSOR}\" STREQUAL \"x86_64\")\n            list(APPEND A2XT_LIBS glu32)\n        endif()\n\n        list(APPEND A2XT_LIBS opengl32 gdi32)\n    endif()\n\n    if(PGE_SHARED_SDLMIXER)\n        if(NOT WIN32 AND NOT EMSCRIPTEN AND NOT APPLE AND NOT ANDROID AND NOT NINTENDO_SWITCH)\n            if(THEXTECH_BUILD_GL_ES_MODERN AND NOT THEXTECH_BUILD_GL_DESKTOP_MODERN AND NOT THEXTECH_BUILD_GL_DESKTOP_LEGACY AND NOT THEXTECH_BUILD_GL_ES_LEGACY)\n                list(APPEND A2XT_LIBS GLESv2)\n            else()\n                find_library(_LIB_GL GL)\n                if(_LIB_GL)\n                    list(APPEND A2XT_LIBS ${_LIB_GL})\n                endif()\n            endif()\n        else()\n            list(APPEND A2XT_LIBS shell32)\n        endif()\n    endif()\n\n    if(THEXTECH_CUSTOM_AUDIO_LIBRARY)\n        list(APPEND A2XT_LIBS gme vorbisidec ogg)\n    endif()\n\n    list(APPEND A2XT_LIBS PGE_ZLib)\nendif()\n\nif(THEXTECH_ENABLE_DISCORD_RPC)\n    list(APPEND A2XT_DEPS DiscordPRC_Local)\n    list(APPEND A2XT_LIBS PGE_DiscordRPC)\n    set(THEXTECH_ENABLE_INTEGRATOR ON)  # This feature needs for an Integrator\nendif()\n\nif(PGE_ENABLE_VIDEO_REC)\n    list(APPEND A2XT_LIBS ${VIDEO_REC_LIBS})\n    list(APPEND A2XT_DEPS ${VIDEO_REC_DEPS})\n    list(APPEND A2XT_INCS ${VIDEO_REC_INCS})\nendif()\n\nif(THEXTECH_BUILD_GL_DESKTOP_MODERN AND THEXTECH_USE_ANGLE_TRANSLATOR)\n    add_subdirectory(3rdparty/angle-shader-translator)\n\n    message(\"== Building ANGLE shader translator ==\")\n    # suppress some known warnings on GCC / Clang\n    if(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\" OR \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\")\n        target_compile_options(angle_shader_translator PRIVATE -Wno-unused-parameter)\n        target_compile_options(angle_shader_translator PRIVATE -Wno-c++17-attribute-extensions)\n    elseif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"AppleClang\")\n        target_compile_options(angle_shader_translator PRIVATE -Wno-unused-parameter)\n        target_compile_options(angle_shader_translator PRIVATE -Wno-c++17-extensions)\n    elseif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"MSVC\")\n        target_compile_options(angle_shader_translator PRIVATE /wd4100) # unreferenced formal parameter\n    endif()\n\n    target_link_libraries(A2XT_Int INTERFACE angle_shader_translator)\nendif()\n\nif(THEXTECH_ENABLE_SDL_NET)\n    message(\"== Building SDL_net... but it doesn't do anything! ==\")\n    set(SDL_NET_PGE_DEPENDENT_BUILD ON)\n    add_subdirectory(3rdparty/SDL_net EXCLUDE_FROM_ALL)\n    target_link_libraries(SDL2_net PRIVATE A2XT_Int)\n    target_compile_definitions(A2XT_Int INTERFACE\n        -DTHEXTECH_ENABLE_SDL_NET\n    )\nendif()\n\nif(THEXTECH_ENABLE_LUAU)\n    message(\"== Building Luau... but it doesn't do anything! ==\")\n    add_subdirectory(3rdparty/luau EXCLUDE_FROM_ALL)\n    if(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\" OR \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\")\n        target_compile_options(Luau.Compiler PRIVATE \"-w\")\n        target_compile_options(Luau.Ast PRIVATE \"-w\")\n        target_compile_options(Luau.VM PRIVATE \"-w\")\n    elseif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"MSVC\")\n        target_compile_options(Luau.Compiler PRIVATE /wd4100) # unreferenced formal parameter\n        target_compile_options(Luau.Ast PRIVATE /wd4100) # unreferenced formal parameter\n        target_compile_options(Luau.VM PRIVATE /wd4100) # unreferenced formal parameter\n    endif()\n    target_link_libraries(A2XT_Int INTERFACE Luau.Compiler Luau.VM)\nendif()\n\nif(NINTENDO_DS AND CALICO_ROOT)\n    add_library(xtbootstrap src/core/16m/boot_16m.c)\n    set_property(TARGET xtbootstrap PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)\n    target_link_libraries(A2XT_Int INTERFACE xtbootstrap)\nendif()\n\nadd_subdirectory(3rdparty/mbediso EXCLUDE_FROM_ALL)\nif(NOT THEXTECH_NO_SDL_BUILD)\n    if(NOT USE_SYSTEM_SDL2)\n        target_include_directories(mbediso PRIVATE \"${DEPENDENCIES_INSTALL_DIR}/include\")\n        add_dependencies(mbediso AudioCodecs_Local)\n    else()\n        target_include_directories(mbediso PRIVATE \"${SDL2_INCLUDE_DIRS}\")\n    endif()\nendif()\ntarget_link_libraries(A2XT_Int INTERFACE mbediso)\ntarget_compile_definitions(A2XT_Int INTERFACE\n    -DPGE_USE_ARCHIVES   # DirMan should use the archives library\n    -DPGE_FILES_PRESENT  # DirMan should use the Files checkSuffix routine\n)\n\nif(ANDROID)\n    set_target_properties(lz4 PROPERTIES POSITION_INDEPENDENT_CODE ON)\n    set_target_properties(lz4_static PROPERTIES POSITION_INDEPENDENT_CODE ON)\n    set_target_properties(mbediso PROPERTIES POSITION_INDEPENDENT_CODE ON)\nendif()\n\n\ntarget_link_libraries(A2XT_Int INTERFACE ${A2XT_LIBS})\ntarget_include_directories(A2XT_Int INTERFACE ${A2XT_INCS})\n\nset(LIB_SRC\n    lib/util.cpp\n    lib/fmt_impl.cpp\n    lib/Graphics/image_size.cpp\n    lib/Graphics/size.cpp\n    lib/Archives/archives_rwops.cpp\n    lib/Archives/archives_mount.cpp\n    lib/Archives/archives_dir.cpp\n    ${APPPATH_SRCS}\n    ${DIRMANAGER_SRCS}\n    ${FMT_SRCS}\n    ${MD5_SRCS}\n    ${POOLALLOC_SRCS}\n    ${INIPROCESSOR_SRCS}\n    ${LOGGER_SRCS}\n    ${UTILS_SRCS}\n    ${LIB_SRC_EXTRA}\n    ${SDLPROXY_SRCS}\n)\n\nif(THEXTECH_FIXED_POINT)\n    list(APPEND LIB_SRC\n        lib/fixed_point.cpp\n    )\n    target_compile_definitions(A2XT_Int INTERFACE\n        -DTHEXTECH_FIXED_POINT\n    )\nelse()\n    list(APPEND LIB_SRC\n        lib/floating_point.cpp\n    )\nendif()\n\nif(NOT THEXTECH_NO_SDL_BUILD)\nlist(APPEND LIB_SRC\n    lib/Graphics/graphics_funcs.cpp\n)\nendif()\n\nif(NOT ANDROID\n   AND NOT EMSCRIPTEN\n   AND NOT VITA\n   AND NOT NINTENDO_3DS\n   AND NOT NINTENDO_WII\n   AND NOT NINTENDO_WIIU\n   AND NOT NINTENDO_SWITCH\n   AND NOT THEXTECH_CLI_BUILD\n   AND NOT PGE_MIN_PORT)\n    list(APPEND LIB_SRC\n        lib/InterProcess/editor_pipe.h\n        lib/InterProcess/editor_pipe.cpp\n        lib/InterProcess/intproc.h\n        lib/InterProcess/intproc.cpp\n        src/capabilities.cpp\n    )\n    set(THEXTECH_INTERPROC_SUPPORTED ON)\nendif()\n\n# Integrations with external applications\nif(THEXTECH_ENABLE_INTEGRATOR)\n    list(APPEND LIB_SRC\n        lib/Integrator/integrator.h\n        lib/Integrator/integrator.cpp)\n\n    if(THEXTECH_ENABLE_DISCORD_RPC)\n        list(APPEND LIB_SRC\n            lib/Integrator/int_discorcrpc.h\n            lib/Integrator/int_discorcrpc.cpp\n        )\n    endif()\nendif()\n\nif(THEXTECH_ENABLE_SDL_NET)\n    list(APPEND LIB_SRC\n        src/main/client.cpp\n        src/main/client_methods.cpp)\nendif()\n\nif(NOT NINTENDO_3DS AND NOT NINTENDO_WII AND NOT NINTENDO_WIIU AND NOT NINTENDO_SWITCH AND NOT VITA AND NOT PGE_MIN_PORT)\n    list(APPEND LIB_SRC ${FILEMAPPER_SRCS})\n    set(THEXTECH_FILEMAPPER_SUPPORTED ON)\nendif()\n\n\nif(NINTENDO_3DS OR NINTENDO_WII OR NOT PGE_MIN_PORT)\n    list(APPEND LIB_SRC\n        ${STACK_WALKER_SRCS}\n        lib/CrashHandler/crash_handler.cpp\n    )\n    set(THEXTECH_CRASHHANDLER_SUPPORTED ON)\nendif()\n\nif(PGE_ENABLE_VIDEO_REC)\n    list(APPEND LIB_SRC ${VIDEO_REC_SRCS})\nendif()\n\nif(APPLE)\n    file(GLOB PGE_FILE_ICONS \"${CMAKE_CURRENT_SOURCE_DIR}/resources/file_icons/*.icns\")\n    list(APPEND LIB_SRC\n        ${XTECH_DEFAULT_ICNS}\n        ${PGE_FILE_ICONS}\n        ${CMAKE_CURRENT_SOURCE_DIR}/resources/PkgInfo\n    )\n\n    if(XTECH_MACOSX_TIGER)\n        set(THEXTECH_MAC_EXEX_GENERATED \"${CMAKE_BINARY_DIR}/generated/TheXTechRun\")\n        configure_file(\"${CMAKE_CURRENT_SOURCE_DIR}/resources/tiger/TheXTechRun.in\" \"${THEXTECH_MAC_EXEX_GENERATED}_tmp/TheXTechRun\")\n        file(COPY\n            \"${THEXTECH_MAC_EXEX_GENERATED}_tmp/TheXTechRun\"\n            DESTINATION \"${CMAKE_BINARY_DIR}/generated/\"\n            FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE\n        )\n        list(APPEND LIB_SRC \"${THEXTECH_MAC_EXEX_GENERATED}\")\n    endif()\nendif()\n\nset(THEXTECH_SRC\n    src/game_main.cpp\n    src/globals.cpp\n    src/npc.cpp\n    src/sound.cpp\n    src/sound_spatial.cpp\n    src/sound/sound_msgsnd.cpp\n    src/frame_timer.cpp\n    src/global_dirs.cpp\n    src/global_strings.cpp\n    src/config/config_base.cpp\n    src/config/config_hooks.cpp\n    src/config/config_legacy_compat.cpp\n    src/config/config_main.cpp\n    src/main/world_loop.cpp\n    src/main/world_file.cpp\n    src/main/game_info.cpp\n    src/main/game_loop.cpp\n    src/main/gameplay_timer.cpp\n    src/main/player_frames.cpp\n    src/main/setup_vars.cpp\n    src/main/setup_physics.cpp\n    src/main/speedrunner.cpp\n    src/main/record.cpp\n    src/main/game_save.cpp\n    src/main/level_save_info.cpp\n    src/main/level_medals.cpp\n    src/main/main_config.cpp\n    src/main/level_file.cpp\n    src/main/menu_loop.cpp\n    src/main/menu_main.cpp\n    src/main/screen_pause.cpp\n    src/main/screen_connect.cpp\n    src/main/screen_options.cpp\n    src/main/screen_quickreconnect.cpp\n    src/main/screen_textentry.cpp\n    src/main/screen_prompt.cpp\n    src/main/screen_progress.cpp\n    src/main/screen_content.cpp\n    src/main/hints.cpp\n    src/main/game_strings.cpp\n    src/main/translate.cpp\n    src/main/translate_episode.cpp\n    src/main/menu_controls.cpp\n    src/main/cheat_code.cpp\n    src/main/outro_loop.cpp\n    src/main/trees.cpp\n    src/main/block_table.cpp\n    src/main/asset_pack.cpp\n    src/main/screen_asset_pack.cpp\n    src/graphics/gfx_update2.cpp\n    src/graphics/gfx_update.cpp\n    src/graphics/gfx_background.cpp\n    src/graphics/gfx_print.cpp\n    src/graphics/gfx_message.cpp\n    src/graphics/gfx_editor.cpp\n    src/graphics/gfx_frame.cpp\n    src/graphics/gfx_camera.cpp\n    src/graphics/gfx_world.cpp\n    src/graphics/gfx_credits.cpp\n    src/graphics/gfx_hud.cpp\n    src/graphics/gfx_enter_screen.cpp\n    src/graphics/gfx_draw_player.cpp\n    src/graphics/gfx_special_frames.cpp\n    src/graphics/gfx_screen.cpp\n    src/graphics/gfx_keyhole.cpp\n    src/graphics/gfx_marquee.cpp\n    src/control/duplicate.cpp\n    src/control/controls.cpp\n    src/core/xerror.cpp\n    src/script/msg_preprocessor.cpp\n    src/script/msg_macros.cpp\n    src/logic/object_graph.cpp\n    src/change_res.cpp\n    src/effect.cpp\n    src/collision.cpp\n    src/load_gfx.cpp\n    src/layers.cpp\n    src/saved_layers.cpp\n    src/custom.cpp\n    src/graphics.cpp\n    src/main.cpp\n    src/blocks.cpp\n    src/gfx.cpp\n    src/frm_main.cpp\n    src/player.cpp\n    src/rand.cpp\n    src/sorting.cpp\n    src/screen.cpp\n    src/screen_fader.cpp\n    src/message.cpp\n    src/std_picture.cpp\n    src/phys_env.cpp\n    src/npc/npc_hit.cpp\n    src/npc/npc_kill.cpp\n    src/npc/npc_update.cpp\n    src/npc/npc_update/npc_movement_logic.cpp\n    src/npc/npc_update/npc_block_logic.cpp\n    src/npc/npc_update/npc_collide.cpp\n    src/npc/npc_update/npc_walking_logic.cpp\n    src/npc/npc_update/npc_effects.cpp\n    src/npc/npc_update/npc_special_maybe_held.cpp\n    src/npc/npc_update/npc_generator.cpp\n    src/npc/npc_frames.cpp\n    src/npc/npc_bonus.cpp\n    src/npc/npc_queues.cpp\n    src/npc/section_overlap.cpp\n    src/npc/npc_activation.cpp\n    src/player/player_update.cpp\n    src/player/player_npc_logic.cpp\n    src/player/player_block_logic.cpp\n    src/player/player_vine_logic.cpp\n    src/player/player_screen_logic.cpp\n    src/player/player_fairy_logic.cpp\n    src/player/player_action_logic.cpp\n    src/player/player_movement_logic.cpp\n    src/player/player_pinched_logic.cpp\n    src/player/player_death_logic.cpp\n    src/player/player_vehicle_logic.cpp\n    src/player/player_char5_logic.cpp\n    src/player/player_warp_logic.cpp\n    src/fontman/font_manager.cpp\n    src/fontman/font_manager_private.cpp\n    src/fontman/raster_font.cpp\n    src/fontman/legacy_font.cpp\n    src/fontman/hardcoded_font.cpp\n    src/fontman/utf8_helpers.cpp\n)\n\nif(NOT CMAKE_BUILD_TYPE_LOWER STREQUAL \"debug\")\n    # some performance-critical files get special treatment, even though the executable overall is normally -Os\n    set_source_files_properties(\n        src/main/trees.cpp\n        src/main/block_table.cpp\n    PROPERTIES COMPILE_FLAGS -O2)\nendif()\n\nif(NOT THEXTECH_NO_SDL_BUILD)\n    list(APPEND THEXTECH_SRC src/sound_thread.cpp)\nendif()\n\nif(THEXTECH_ENABLE_TTF_SUPPORT)\n    add_definitions(-DTHEXTECH_ENABLE_TTF_SUPPORT)\n    list(APPEND THEXTECH_SRC\n        src/fontman/ttf_font.cpp\n    )\nendif()\n\n# Luna Autocode script engine\nlist(APPEND THEXTECH_SRC\n    src/script/luna/lunaglobals.cpp\n    src/script/luna/lunacounter.cpp\n    src/script/luna/lunacounter_record.cpp\n)\n\nif(THEXTECH_ENABLE_LUNA_AUTOCODE)\n    add_definitions(-DTHEXTECH_ENABLE_LUNA_AUTOCODE)\n    list(APPEND THEXTECH_SRC\n        src/script/luna/luna.cpp\n        src/script/luna/autocode.cpp\n        src/script/luna/autocode_manager.cpp\n        src/script/luna/csprite.cpp\n        src/script/luna/lunaspriteman.cpp\n        src/script/luna/hitbox.cpp\n        src/script/luna/lunaimgbox.cpp\n        src/script/luna/sprite_component.cpp\n        src/script/luna/sprite_funcs.cpp\n        src/script/luna/lunalevels.cpp\n        src/script/luna/lunaplayer.cpp\n        src/script/luna/lunanpc.cpp\n        src/script/luna/lunablock.cpp\n        src/script/luna/lunalayer.cpp\n        src/script/luna/lunamisc.cpp\n        src/script/luna/lunalevel.cpp\n        src/script/luna/lunarender.cpp\n        src/script/luna/lunainput.cpp\n        src/script/luna/lunavarbank.cpp\n        src/script/luna/renderop_bitmap.cpp\n        src/script/luna/renderop_rect.cpp\n        src/script/luna/renderop_effect.cpp\n        src/script/luna/renderop_string.cpp\n        src/script/luna/mememu.cpp\n        src/script/luna/levels/Docopoper-AbstractAssault.cpp\n        src/script/luna/levels/Docopoper-Calleoca.cpp\n        src/script/luna/levels/Docopoper-TheFloorisLava.cpp\n        src/script/luna/levels/SAJewers-QraestoliaCaverns.cpp\n        src/script/luna/levels/SAJewers-Snowboardin.cpp\n        src/script/luna/levels/Talkhaus-Science_Final_Battle.cpp\n        src/script/luna/levels/KilArmoryCode.cpp\n    )\nendif()\n\nif(THEXTECH_ENABLE_AUDIO_FX)\n    add_definitions(-DTHEXTECH_ENABLE_AUDIO_FX)\n    list(APPEND THEXTECH_SRC\n        src/sound/fx/spc_echo.cpp\n        src/sound/fx/reverb.cpp\n    )\nendif()\n\nif(THEXTECH_ENABLE_EDITOR)\n    add_definitions(-DTHEXTECH_ENABLE_EDITOR)\n    list(APPEND THEXTECH_SRC\n        src/editor/editor.cpp\n        src/editor/new_editor.cpp\n        src/editor/write_common.cpp\n        src/editor/write_level.cpp\n        src/editor/write_world.cpp\n        src/editor/editor_custom.cpp\n        src/editor/editor_strings.cpp\n        src/editor/magic_block.cpp\n    )\nendif()\n\nif(THEXTECH_ENABLE_LUAU)\n    target_compile_definitions(A2XT_Int INTERFACE -DTHEXTECH_ENABLE_LUAU)\n    list(APPEND THEXTECH_SRC\n        src/script/luau/test.cpp\n    )\nendif()\n\nif(NINTENDO_3DS)\n    add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM)\n    list(APPEND THEXTECH_SRC\n        src/control/input_3ds.cpp\n        src/core/3ds/render_3ds.cpp\n        src/core/3ds/window_3ds.cpp\n        src/core/3ds/msgbox_3ds.cpp\n        src/core/3ds/events_3ds.cpp\n#        src/core/3ds/3ds-audio-lib.cpp\n        src/core/sdl/sdl_core.cpp\n        src/core/power/power_3ds.cpp\n    )\nelseif(NINTENDO_WII)\n    add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM)\n    list(APPEND THEXTECH_SRC\n        src/control/input_wii.cpp\n        src/control/input_wii_gc.cpp\n        src/core/wii/render_wii.cpp\n        src/core/wii/window_wii.cpp\n        src/core/wii/msgbox_wii.cpp\n        src/core/wii/events_wii.cpp\n        src/core/sdl/sdl_core.cpp\n        src/core/power/power_wii.cpp\n    )\nelseif(NINTENDO_DS)\n    add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM -DTHEXTECH_NO_SDL_CORE)\n    list(REMOVE_ITEM THEXTECH_SRC src/sound.cpp src/sound/sound_msgsnd.cpp)\n\n    list(APPEND THEXTECH_SRC\n        src/control/input_16m.cpp\n        src/core/16m/render_16m.cpp\n        src/core/16m/msgbox_16m.cpp\n        src/core/16m/window_16m.cpp\n        src/core/16m/events_16m.cpp\n        src/core/16m/sound_16m.cpp\n        src/core/16m/sound_stream_16m.cpp\n        src/core/power/power_16m.cpp\n    )\nelseif(THEXTECH_NO_SDL_BUILD)\n    add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM -DTHEXTECH_NO_SDL_CORE)\n    list(APPEND THEXTECH_SRC\n        src/core/null/render_null.cpp\n        src/core/null/window_null.cpp\n        src/core/null/msgbox_null.cpp\n        src/core/null/events_null.cpp\n        src/core/power/power_null.cpp\n    )\nelseif(THEXTECH_CLI_BUILD)\n    add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM)\n    list(APPEND THEXTECH_SRC\n        src/core/null/render_null.cpp\n        src/core/null/window_null.cpp\n        src/core/null/msgbox_null.cpp\n        src/core/null/events_null.cpp\n        src/core/sdl/sdl_core.cpp\n        src/core/power/power_null.cpp\n    )\nelse()\n    add_definitions(-DCORE_EVERYTHING_SDL)\n    list(APPEND THEXTECH_SRC\n        src/control/joystick.cpp\n        src/control/keyboard.cpp\n        src/control/touchscreen.cpp\n        src/core/base/render_base.cpp\n        src/core/base/window_base.cpp\n        src/core/base/msgbox_base.cpp\n        src/core/base/events_base.cpp\n        src/core/sdl/render_sdl.cpp\n        src/core/sdl/window_sdl.cpp\n        src/core/sdl/msgbox_sdl.cpp\n        src/core/sdl/events_sdl.cpp\n        src/core/sdl/sdl_core.cpp\n        src/core/power/power_sdl.cpp\n        lib/Graphics/xt_qoi.cpp\n    )\n\n    if(NOT THEXTECH_FORCE_FULLSCREEN AND NOT EMSCRIPTEN)\n        add_definitions(-DRENDER_FULLSCREEN_TYPES_SUPPORTED)\n    endif()\n\n    if(NINTENDO_WIIU)\n        list(APPEND THEXTECH_SRC\n            src/core/wiiu/msgbox_wiiu.cpp\n        )\n    else()\n        list(APPEND THEXTECH_SRC\n            src/core/sdl/msgbox_sdl.cpp\n        )\n    endif()\n\n    if(THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_DESKTOP_LEGACY OR THEXTECH_BUILD_GL_ES_MODERN OR THEXTECH_BUILD_GL_ES_LEGACY)\n        list(APPEND THEXTECH_SRC\n            src/core/opengl/gl_program_object.cpp\n            src/core/opengl/render_gl_frontend.cpp\n            src/core/opengl/render_gl_backend.cpp\n            src/core/opengl/render_gl_init.cpp\n            src/core/opengl/render_gl_shaders.cpp\n            src/core/opengl/render_gl_shader_lighting.cpp\n        )\n    endif()\n\n    if(THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_ES_MODERN)\n        list(APPEND THEXTECH_SRC\n            src/core/opengl/gl_program_bank.cpp\n            src/core/opengl/gl_particle_system.cpp\n        )\n    endif()\n\n    if(THEXTECH_BUILD_GL_DESKTOP_MODERN AND THEXTECH_USE_ANGLE_TRANSLATOR)\n        add_definitions(-DTHEXTECH_USE_ANGLE_TRANSLATOR)\n        list(APPEND THEXTECH_SRC\n            src/core/opengl/gl_shader_translator.cpp\n        )\n    endif()\n\nendif()\n\n\nlist(APPEND THEXTECH_SRC src/core/language/language_common.cpp)\n\n# Notes:\n# - First, algorithm will try to detect language using SDL's method if available (Supported by SDL 2.0.14 and newer)\n# - Then, falls to the system specific code to detect the language\n# If there is a necessary to disable use of SDL-based language detection,\n# define the macro `THEXTECH_DISABLE_SDL_LOCALE` which will completely disable the SDL-based language detector\n\nif(THEXTECH_NO_SDL_BUILD)\n    add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE)\nendif()\n\nif(WIN32)\n    list(APPEND THEXTECH_SRC src/core/language/language_win32.cpp)\nelseif(APPLE)\n    list(APPEND THEXTECH_SRC src/core/language/language_apple.cpp)\nelseif(ANDROID)\n    list(APPEND THEXTECH_SRC src/core/language/language_android.cpp)\nelseif(NINTENDO_DS)\n    # FIXME: Implement the Nintendo DS language initializer\n    add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE)\n    list(APPEND THEXTECH_SRC src/core/language/language_dummy.cpp)\nelseif(NINTENDO_3DS)\n    add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) # SDL for 3DS doesn't support language detection\n    list(APPEND THEXTECH_SRC src/core/language/language_3ds.cpp)\nelseif(NINTENDO_WII)\n    add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) # SDL for Wii doesn't support language detection\n    list(APPEND THEXTECH_SRC src/core/language/language_wii.cpp)\nelseif(NINTENDO_WIIU)\n    add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) # SDL for WiiU doesn't support language detection\n    list(APPEND THEXTECH_SRC src/core/language/language_wiiu.cpp)\nelseif(NINTENDO_SWITCH)\n    add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) # SDL for Switch doesn't support language detection\n    list(APPEND THEXTECH_SRC src/core/language/language_switch.cpp)\nelseif(VITA)\n    add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE)\n    list(APPEND THEXTECH_SRC src/core/language/language_vita.cpp)\nelseif(UNIX)\n    list(APPEND THEXTECH_SRC src/core/language/language_unix.cpp)\nelse()\n    add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE)\n    list(APPEND THEXTECH_SRC src/core/language/language_dummy.cpp)\nendif()\n\n\n# Add heads into the list\nfile(GLOB THEXTECH_HEADS_SRC\n    *.h\n    lib/*.h\n    lib/AppPath/*.h\n    lib/CrashHandler/*.h\n    lib/Graphics/*.h\n    lib/tclap/*.h\n    lib/fmt/*.h\n    lib/sorting/*.h\n    lib/Archives/*.h\n    lib/json/*.hpp\n    src/*.h\n    src/*.hpp\n    src/script/*.h\n    src/script/luna/*.h\n    src/script/luna/level/*.h\n    src/control/*.h\n    src/core/*.h\n    src/core/16m/*.h\n    src/core/3ds/*.h\n    src/core/base/*.h\n    src/core/language/*.h\n    src/core/minport/*.h\n    src/core/null/*.h\n    src/core/opengl/*.h\n    src/core/power/*.h\n    src/core/sdl/*.h\n    src/core/vita/*.h\n    src/core/wii/*.h\n    src/core/wiiu/*.h\n    src/editor/*.h\n    src/main/*.h\n    src/main/translate/*.h\n    src/main/*.hpp\n    src/npc/*.h\n    src/npc/*.hpp\n    src/sound/*.h\n    src/sound/fx/*.h\n    src/sound/fx/*.hpp\n    src/fontman/*.h\n)\nlist(APPEND THEXTECH_SRC ${THEXTECH_HEADS_SRC})\n\n# ----------------------------------------------------\n# Non-buildable meta files such as change log, Readmes, etc.\nset(THEXTECH_EXTRA_RESOURCES\n    changelog.txt\n    README.md\n    README.RUS.md\n    README.ESP.md\n    resources/thextech.plist.in\n    resources/thextech.plist.tiger.in\n    resources/emscripten/manifest.json.in\n    resources/emscripten/shell_minimal.html\n    resources/emscripten/sw.js.in\n    resources/flatpak/appdata.xml.in\n    resources/flatpak/build.yml\n    resources/haiku/PackageInfo.in\n    resources/haiku/icon.hvif\n    resources/haiku/icon.rdef\n    resources/vita/frag.cgf\n    resources/vita/vert.cgv\n    resources/vita/sce_sys/icon0.png\n    resources/vita/sce_sys/livearea/contents/bg.png\n    resources/vita/sce_sys/livearea/contents/startup.png\n    resources/vita/sce_sys/livearea/contents/template.xml.in\n    resources/nds/icon.bmp\n    resources/switch/splash_logo.png\n    resources/switch/thextech-logo.jpg\n    resources/switch/thextech-logo.png\n    resources/tiger/TheXTechRun.in\n    resources/wii/icon.png\n    resources/wii/meta.xml.in\n    resources/wiiu/icon.png\n    resources/wiiu/meta.xml.in\n    resources/wiiu/wuhb-splash.png\n)\n\nlist(APPEND THEXTECH_SRC ${THEXTECH_EXTRA_RESOURCES})\nset_source_files_properties(${THEXTECH_EXTRA_RESOURCES} PROPERTIES HEADER_FILE_ONLY ON)\nsource_group(\"Resources\" FILES ${THEXTECH_EXTRA_RESOURCES})\n\n# ------------------ Unit tests ----------------------\noption(WITH_UNIT_TESTS \"Enable unit testing\" OFF)\nif(WITH_UNIT_TESTS)\n    enable_testing()\n    add_subdirectory(test)\nendif()\n\n# ----------------------------------------------------\n\nif(WIN32 AND NOT EMSCRIPTEN)\n    list(APPEND THEXTECH_SRC resources/thextech.rc)\nendif()\n\nif(THEXTECH_ENABLE_LUA)\n    add_subdirectory(script)\n    target_link_libraries(A2XT_Int INTERFACE XTechLua ${libLuaBind_Lib} ${libLuaJit_Lib})\n    if(UNIX)\n        find_library(LIB_DL_PATH dl)\n        if(LIB_DL_PATH)\n            target_link_libraries(A2XT_Int INTERFACE ${LIB_DL_PATH})\n        endif()\n    endif()\nendif()\n\nif(ANDROID)\n    set(CMAKE_DEBUG_POSTFIX \"\")\n    add_library(thextech SHARED ${THEXTECH_SRC} ${LIB_SRC})\n    target_compile_options(thextech PRIVATE -fPIC)\n\n    target_link_options(thextech PUBLIC \"-Wl,-z,max-page-size=16384\")\n    target_link_options(thextech PUBLIC \"-Wl,-z,common-page-size=16384\")\n\n    # Include own-built libraries into the APK\n    set(APK_PACK_LIBS ${CMAKE_SOURCE_DIR}/android-project/thextech/jniLibs/${CMAKE_BUILD_TYPE_LOWER}/${ANDROID_ABI})\n    add_custom_target(SDL_ApkPackLibs_makeDir ALL COMMAND ${CMAKE_COMMAND} -E make_directory \"${APK_PACK_LIBS}\")\n\n    if(PGE_SHARED_SDLMIXER)\n#        add_library(SDL2_mixer_ext SHARED IMPORTED DEPENDS SDLMixerX_Local AudioCodecs_Local)\n#        set_target_properties(SDL2_mixer_ext PROPERTIES IMPORTED_LOCATION \"${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2_mixer_ext.so\")\n        add_custom_target(SDL_ApkPackLibs_mixerx ALL\n            COMMAND ${CMAKE_COMMAND} -E copy \"${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2_mixer_ext.so\" \"${APK_PACK_LIBS}\"\n            DEPENDS SDL_ApkPackLibs_makeDir ${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2_mixer_ext.so AudioCodecs_Local\n        )\n        add_dependencies(thextech SDL_ApkPackLibs_mixerx)\n\n#        add_library(SDL2 SHARED IMPORTED DEPENDS AudioCodecs_Local)\n#        set_target_properties(SDL2 PROPERTIES IMPORTED_LOCATION \"${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2.so\")\n        add_custom_target(SDL_ApkPackLibs_sdl2 ALL\n            COMMAND ${CMAKE_COMMAND} -E copy \"${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2.so\" \"${APK_PACK_LIBS}\"\n            DEPENDS SDL_ApkPackLibs_makeDir ${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2.so AudioCodecs_Local\n        )\n        add_dependencies(thextech SDL_ApkPackLibs_sdl2)\n    endif()\n\n#    add_library(hidapi SHARED IMPORTED DEPENDS AudioCodecs_Local)\n#    set_target_properties(hidapi PROPERTIES IMPORTED_LOCATION \"${DEPENDENCIES_INSTALL_DIR}/lib/libhidapi.so\")\n\n# libhidapi.so is NO LONGER required since SDL 2.0.18\n#    add_custom_target(SDL_ApkPackLibs_hidapi ALL\n#        COMMAND ${CMAKE_COMMAND} -E copy \"${DEPENDENCIES_INSTALL_DIR}/lib/libhidapi.so\" \"${APK_PACK_LIBS}\"\n#        DEPENDS SDL_ApkPackLibs_makeDir \"${DEPENDENCIES_INSTALL_DIR}/lib/libhidapi.so\" AudioCodecs_Local\n#    )\n#    add_dependencies(thextech SDL_ApkPackLibs_hidapi)\n\nelseif(NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_3DS OR NINTENDO_DS)\n    add_executable(thextech ${THEXTECH_SRC} ${LIB_SRC})\n    set_target_properties(thextech PROPERTIES SUFFIX \".elf\")\n\nelse()\n    add_executable(thextech ${THEXTECH_SRC} ${LIB_SRC})\n    pge_set_nopie(thextech)\nendif()\n\nadd_dependencies(thextech git_version)\ntarget_include_directories(thextech PRIVATE \"${CMAKE_CURRENT_BINARY_DIR}/generated-include\")\n\nset_target_properties(thextech PROPERTIES\n    OUTPUT_NAME \"${THEXTECH_EXECUTABLE_NAME}\"\n)\n\nif(CMAKE_BUILD_TYPE_LOWER STREQUAL \"debug\" OR CMAKE_BUILD_TYPE_LOWER STREQUAL \"relwithdebinfo\")\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_DEBUG_INFO)\nendif()\n\nif(APPLE)\n    if(XTECH_MACOSX_TIGER)\n        set(THEXTECH_EXECUTABLE_NAME_OUT TheXTechRun)\n    else()\n        set(THEXTECH_EXECUTABLE_NAME_OUT ${THEXTECH_EXECUTABLE_NAME})\n    endif()\n\n    set_target_properties(thextech PROPERTIES\n        OUTPUT_NAME \"${THEXTECH_BUNDLE_NAME}\"\n        MACOSX_BUNDLE TRUE\n        MACOSX_BUNDLE_INFO_PLIST \"${CMAKE_CURRENT_SOURCE_DIR}/resources/${XTECH_PLIST_NAME}\"\n        MACOSX_BUNDLE_BUNDLE_NAME \"${THEXTECH_BUNDLE_NAME}\"\n        MACOSX_BUNDLE_EXECUTABLE_NAME \"${THEXTECH_EXECUTABLE_NAME_OUT}\"\n        MACOSX_BUNDLE_GUI_IDENTIFIER \"ru.wohlsoft.thextech\"\n        MACOSX_BUNDLE_SHORT_VERSION_STRING \"${THEXTECH_VERSION_STRING}\"\n        MACOSX_BUNDLE_LONG_VERSION_STRING \"${THEXTECH_VERSION_STRING}\"\n        MACOSX_BUNDLE_ICON_FILE \"${THEXTECH_ICON_NAME}\"\n        CPACK_BUNDLE_NAME \"${THEXTECH_BUNDLE_NAME}\"\n        MACOSX_BUNDLE_INFO_STRING \"TheXTech classic game engine\"\n    )\n    set_source_files_properties(\"${CMAKE_CURRENT_SOURCE_DIR}/${XTECH_DEFAULT_ICNS}\" PROPERTIES MACOSX_PACKAGE_LOCATION \"Resources\")\n    set_source_files_properties(${PGE_FILE_ICONS} PROPERTIES MACOSX_PACKAGE_LOCATION \"Resources\")\n    set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/resources/PkgInfo PROPERTIES MACOSX_PACKAGE_LOCATION \"\")\n    if(XTECH_MACOSX_TIGER)\n        set_source_files_properties(\"${THEXTECH_MAC_EXEX_GENERATED}\" PROPERTIES MACOSX_PACKAGE_LOCATION \"MacOS\")\n    endif()\n\n    if(THEXTECH_CUSTOM_ICON_PATH AND NOT THEXTECH_CUSTOM_ICON_PATH STREQUAL \"\")\n        set_source_files_properties(\"${THEXTECH_CUSTOM_ICON_PATH}\" PROPERTIES MACOSX_PACKAGE_LOCATION \"Resources\")\n    endif()\n\n    if(THEXTECH_PRELOAD_ENVIRONMENT AND NOT THEXTECH_PRELOAD_ENVIRONMENT STREQUAL \"\")\n        add_custom_command(TARGET thextech POST_BUILD\n            COMMAND ${CMAKE_COMMAND} -E make_directory \"$<TARGET_FILE_DIR:thextech>/../Resources/assets\"\n            COMMAND ${CMAKE_COMMAND} -E copy_directory \"${THEXTECH_PRELOAD_ENVIRONMENT}\" \"$<TARGET_FILE_DIR:thextech>/../Resources/assets\"\n            COMMENT \"Copying assets into bundle...\"\n        )\n        target_compile_definitions(thextech PRIVATE -DUSE_BUNDLED_ASSETS)\n    endif()\n\n    if(THEXTECH_PRELOAD_ENVIRONMENT_MANUALLY)\n        target_compile_definitions(thextech PRIVATE -DUSE_BUNDLED_ASSETS)\n    endif()\n\n    if(XTECH_MACOSX_TIGER) # Use X11 mode on Mac OS X Tiger\n        target_compile_definitions(thextech PRIVATE -DUSE_APPLE_X11)\n    endif()\n\n    check_library_exists(iconv iconv_open \"\" HAVE_LIBICONV)\n    if(HAVE_LIBICONV)\n        target_link_libraries(A2XT_Int INTERFACE iconv)\n    endif()\n\n    if(PGE_USE_LUAJIT)\n        # Required to link on 64-bit macOS\n        # See: http://luajit.org/install.html\n        set_property(TARGET thextech APPEND_STRING PROPERTY\n            LINK_FLAGS \" -pagezero_size 10000 -image_base 100000000\"\n        )\n    endif()\nendif()\n\nif(ANDROID)\n    target_link_libraries(A2XT_Int INTERFACE log)\n\n    if(THEXTECH_BUILD_GL_ES_LEGACY OR THEXTECH_BUILD_GL_ES_MODERN)\n        target_link_libraries(A2XT_Int INTERFACE EGL)\n    endif()\nendif()\n\nif(NINTENDO_SWITCH)\n    target_link_libraries(A2XT_Int INTERFACE nx)\n\n    if(THEXTECH_BUILD_GL_DESKTOP_MODERN)\n        target_link_libraries(A2XT_Int INTERFACE glad)\n    endif()\n\n    # Set explicit flags for this project\n    target_compile_definitions(thextech PRIVATE -D__SWITCH__ -DNO_INTPTROC)\nendif()\n\nif(VITA)\n    if(THEXTECH_BUILD_GL_ES_MODERN)\n        target_link_libraries(A2XT_Int INTERFACE vitaGL vitashark)\n    endif()\n\n    # Link Vita specific libraries to the generic interface.\n    target_link_libraries(A2XT_Int INTERFACE ${VITA_ADDTL_LIBS})\n\n    # Set explicit flags for this project\n    target_compile_definitions(thextech PRIVATE\n        -DVITA -DNO_SCREENSHOT -DTHEXTECH_PRELOAD_LEVELS -DNO_INTPTROC)\nendif()\n\nif(WIN32 AND WINDOWS_STORE)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_WINRT)\nendif()\n\nif(NINTENDO_3DS)\n    target_compile_definitions(thextech PRIVATE -DPGE_SDL_MUTEX -DLOW_MEM -DTHEXTECH_PRELOAD_LEVELS -D__3DS__)\nelseif(NINTENDO_WII)\n    target_compile_definitions(thextech PRIVATE -DPGE_SDL_MUTEX -DLOW_MEM -DTHEXTECH_PRELOAD_LEVELS -D__WII__)\nelseif(NINTENDO_WIIU)\n    target_compile_definitions(thextech PRIVATE -DPGE_SDL_MUTEX)\nelseif(NINTENDO_DS)\n    target_link_libraries(thextech PRIVATE mm9)\n    target_compile_definitions(thextech PRIVATE -DPGE_NO_THREADING -DLOW_MEM -DTHEXTECH_PRELOAD_LEVELS -D__16M__)\n\n    if(CALICO_ROOT)\n        target_compile_definitions(thextech PRIVATE -D__CALICO__)\n    elseif(BLOCKSDS)\n        target_compile_definitions(thextech PRIVATE -D__BLOCKS__)\n    endif()\n\n    if(NOT BLOCKSDS)\n        target_link_libraries(thextech PRIVATE fat)\n        set(CMAKE_EXE_LINKER_FLAGS \"-L${CALICO_ROOT}/lib -L${NDS_ROOT}/lib -specs=${CALICO_ROOT}/share/ds9.specs -T ${CMAKE_CURRENT_SOURCE_DIR}/cmake/overlays-16m.ld\")\n    endif()\n\n    if(BLOCKSDS)\n        set(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -Wl,--defsym,__dtcm_data_size=8\")\n    endif()\nelseif(PGE_MIN_PORT)\n    target_compile_definitions(thextech PRIVATE -DLOW_MEM)\nendif()\n\nif(VITA OR NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_SWITCH)\n    # Run in-game message box to show assert failures on platforms without SDL's message box\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_ASSERTS_INGAME_MESSAGE)\nendif()\n\nif(VITA OR NINTENDO_DS OR NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_SWITCH OR ANDROID OR EMSCRIPTEN)\n    # Disable language tools at non-desktop platforms\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_DISABLE_LANG_TOOLS)\nendif()\n\nif((UNIX OR NINTENDO_SWITCH OR ANDROID OR APPLE) AND NOT EMSCRIPTEN)\n    # Allow hotswapping renderer on fully tested platforms\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_RESTART_RENDERER_SUPPORTED)\nendif()\n\nif(THEXTECH_NO_SDL_BUILD)\n    target_compile_definitions(thextech PRIVATE -DPGE_NO_THREADING -DTHEXTECH_NO_SDL_BUILD)\nendif()\n\nif(THEXTECH_CLI_BUILD)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_CLI_BUILD)\nendif()\n\nif(THEXTECH_BUILD_GL_DESKTOP_MODERN)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_DESKTOP_MODERN)\nendif()\n\nif(THEXTECH_BUILD_GL_ES_MODERN)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_ES_MODERN)\nendif()\n\nif(THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_ES_MODERN)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_MODERN)\nendif()\n\nif(THEXTECH_BUILD_GL_DESKTOP_LEGACY)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_DESKTOP_LEGACY)\nendif()\n\nif(THEXTECH_BUILD_GL_ES_LEGACY)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_ES_LEGACY)\nendif()\n\nif(THEXTECH_ENABLE_WIP_FEATURES)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_WIP_FEATURES)\nendif()\n\ntarget_compile_definitions(thextech PRIVATE -DTHEXTECH_DIRECTORY_PREFIX=\"${THEXTECH_DIRECTORY_PREFIX}\")\n\nif(THEXTECH_NO_BUILD_DATE)\n    target_compile_definitions(thextech PRIVATE -DDISABLE_XTECH_BUILD_DATE)\nendif()\n\nif(THEXTECH_ENABLE_LUA)\n    target_compile_definitions(thextech PRIVATE -DENABLE_XTECH_LUA)\nendif()\n\nif(THEXTECH_ENABLE_INTEGRATOR)\n    target_compile_definitions(thextech PRIVATE -DENABLE_XTECH_INTEGRATOR)\nendif()\n\nif(THEXTECH_ENABLE_DISCORD_RPC)\n    target_compile_definitions(thextech PRIVATE -DENABLE_XTECH_DISCORD_RPC -DXTECH_DISCORD_APPID=${THEXTECH_DISCORD_APPID})\nendif()\n\nif(PGE_ENABLE_VIDEO_REC)\n    target_compile_definitions(thextech PRIVATE -DPGE_ENABLE_VIDEO_REC)\n\n    if(PGE_VIDEO_REC_WEBM_SUPPORTED)\n        target_compile_definitions(thextech PRIVATE -DPGE_VIDEO_REC_WEBM_SUPPORTED)\n    endif()\nendif()\n\nif(ENABLE_ADDRESS_SANITIZER)\n    target_compile_options(thextech PRIVATE -fsanitize=address)\n    target_link_options(thextech PRIVATE -fsanitize=address)\nendif()\n\ntarget_include_directories(thextech PRIVATE ${A2XT_INCS})\ntarget_link_libraries(thextech PRIVATE A2XT_Int)\n\nif(THEXTECH_ENABLE_SDL_NET)\n    target_link_libraries(thextech PRIVATE SDL2_net)\nendif()\n\nif(RANGE_ARR_USE_HEAP)\n    target_compile_definitions(thextech PRIVATE -DRANGE_ARR_USE_HEAP)\nendif()\n\nif(RANGE_ARR_UNSAFE_MODE)\n    target_compile_definitions(thextech PRIVATE -DRANGE_ARR_UNSAFE_MODE)\nendif()\n\nif(ENABLE_ANTICHEAT_TRAP)\n    target_compile_definitions(thextech PRIVATE -DENABLE_ANTICHEAT_TRAP)\nendif()\n\nif(ENABLE_OLD_CREDITS)\n    target_compile_definitions(thextech PRIVATE -DENABLE_OLD_CREDITS)\n    set_target_properties(thextech PROPERTIES OUTPUT_NAME \"smbx\")\nendif()\n\nif(NOT ENABLE_LOGGING)\n    target_compile_definitions(thextech PRIVATE -DDISABLE_LOGGING -DNO_FILE_LOGGING)\nendif()\n\nif(EMSCRIPTEN)\n    target_compile_definitions(thextech PRIVATE -DPGE_NO_THREADING -DNO_FILE_LOGGING)\nendif()\n\nif(THEXTECH_NO_ARGV_HANDLING)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_NO_ARGV_HANDLING)\nendif()\n\nif(THEXTECH_INTERPROC_SUPPORTED)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_INTERPROC_SUPPORTED)\nendif()\n\nif(THEXTECH_FILEMAPPER_SUPPORTED)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_FILEMAPPER_SUPPORTED)\nendif()\n\nif(THEXTECH_CRASHHANDLER_SUPPORTED)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_CRASHHANDLER_SUPPORTED)\nendif()\n\n# ======================= Render specific global macros ========================\nif(EMSCRIPTEN)\n    target_compile_definitions(thextech PRIVATE\n        -DNO_WINDOW_FOCUS_TRACKING  # Don't track for window focus\n    )\nendif()\n\nif(NOT EMSCRIPTEN AND\n   NOT NINTENDO_3DS AND\n   NOT NINTENDO_WII AND\n   NOT NINTENDO_WIIU AND\n   NOT PGE_MIN_PORT AND\n   NOT THEXTECH_CLI_BUILD)\n    target_compile_definitions(thextech PRIVATE\n        -DUSE_SCREENSHOTS_AND_RECS  # Built-in screenshots and GIF recording\n    )\nendif()\n\nif(ANDROID)\n    target_compile_definitions(thextech PRIVATE\n        -DUSE_RENDER_BLOCKING\n    )\nendif()\n\nif(THEXTECH_FORCE_FULLSCREEN)\n    target_compile_definitions(thextech PRIVATE\n        -DRENDER_FULLSCREEN_ALWAYS  # Game works always fullscreen\n        -DNO_WINDOW_FOCUS_TRACKING  # Don't track for window focus\n    )\nendif()\n\n# ==============================================================================\n\nif(NOT USE_SYSTEM_LIBS AND A2XT_DEPS)\n    add_dependencies(thextech ${A2XT_DEPS})\nendif()\n\nif(THEXTECH_IS_BIG_ENDIAN)\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_BIG_ENDIAN -DSOUND_FX_BIG_ENDIAN)\nendif()\n\nif(CMAKE_SIZEOF_VOID_P MATCHES \"8\")\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_WORDSIZE=64)\nelseif(CMAKE_SIZEOF_VOID_P MATCHES \"4\")\n    target_compile_definitions(thextech PRIVATE -DTHEXTECH_WORDSIZE=32)\nelse()\n    message(WARNING \"Unknown word size ${CMAKE_SIZEOF_VOID_P}!\")\nendif()\n\nif(USE_STATIC_LIBC AND NOT USE_SYSTEM_LIBS)\n    if(NOT APPLE AND NOT MSVC)\n#        target_link_libraries(thextech PRIVATE -static)\n        set_property(TARGET thextech APPEND_STRING PROPERTY LINK_FLAGS \" -static-libgcc -static-libstdc++\")\n    endif()\n\n    if(MSVC)\n        target_compile_options(thextech PRIVATE /MT)\n        target_link_options(thextech PRIVATE /INCREMENTAL:NO /NODEFAULTLIB:MSVCRT)\n    endif()\nendif()\n\nif (Backtrace_FOUND)\n    set_property(TARGET thextech APPEND_STRING PROPERTY LINK_FLAGS \" ${Backtrace_LIBRARIES}\")\nendif()\n\nif(WIN32)\n    set_target_properties(thextech PROPERTIES WIN32_EXECUTABLE ON)\n    target_compile_definitions(thextech PRIVATE -DNOMINMAX)\n    target_link_libraries(thextech PRIVATE \"version\" dbghelp) # needed by StackWalker\nendif()\n\ninclude(cmake/deploy.cmake)\n\nif(APPLE)\n    install(TARGETS thextech DESTINATION .)\n    install(FILES changelog.txt DESTINATION .)\n    install(FILES README.md DESTINATION . RENAME ReadMe.txt)\n    install(FILES LICENSE DESTINATION . RENAME License.txt)\nelseif(WIN32)\n    # For MinGW toolchain, copy missing DLLs\n    if(MINGW)\n        function(find_mingw_dll _FieldName _FileName _DestList _SearchPaths)\n            find_file(MINGWDLL_${_FieldName} ${_FileName} PATH_SUFFIXES bin PATHS \"${_SearchPaths}\")\n            if(MINGWDLL_${_FieldName})\n                list(APPEND ${_DestList} \"${MINGWDLL_${_FieldName}}\")\n                set(${_DestList} ${${_DestList}} PARENT_SCOPE)\n            endif()\n        endfunction()\n\n        set(MINGW_BIN_PATH \"\")\n        set(MINGW_DLLS)\n\n        find_mingw_dll(LIBGCCDW         \"libgcc_s_dw2-1.dll\" MINGW_DLLS \"${MINGW_BIN_PATH}\")\n        find_mingw_dll(LIBGCCSJLJ       \"libgcc_s_sjlj-1.dll\" MINGW_DLLS \"${MINGW_BIN_PATH}\")\n        find_mingw_dll(LIBGCCSEC        \"libgcc_s_seh-1.dll\" MINGW_DLLS \"${MINGW_BIN_PATH}\")\n        find_mingw_dll(MINGWEX          \"libmingwex-0.dll\" MINGW_DLLS \"${MINGW_BIN_PATH}\")\n        find_mingw_dll(WINPTHREAD       \"libwinpthread-1.dll\" MINGW_DLLS \"${MINGW_BIN_PATH}\")\n        find_mingw_dll(WINPTHREADGC3    \"pthreadGC-3.dll\" MINGW_DLLS \"${MINGW_BIN_PATH}\")\n        find_mingw_dll(STDCPP           \"libstdc++-6.dll\" MINGW_DLLS \"${MINGW_BIN_PATH}\")\n\n        message(\"MinGW DLLs: [${MINGW_DLLS}]\")\n        file(COPY ${MINGW_DLLS} DESTINATION \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/\")\n    endif()\n\n    install(TARGETS thextech DESTINATION .)\n    file(GLOB BUILT_DLLS \"${DEPENDENCIES_INSTALL_DIR}/bin/*-*.dll\")\n    file(GLOB BUILT_DLLS2 \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/*-*.dll\")\n    list(APPEND BUILT_DLLS ${BUILT_DLLS2})\n    if(NOT USE_SYSTEM_SDL2)\n        list(APPEND BUILT_DLLS\n            \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/SDL2.dll\"\n            \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/SDL2_mixer_ext.dll\"\n        )\n    endif()\n    install(FILES ${BUILT_DLLS} DESTINATION .)\n    install(FILES changelog.txt DESTINATION .)\n    install(FILES README.md DESTINATION . RENAME ReadMe.txt)\n    install(FILES LICENSE DESTINATION . RENAME License.TheXTech.txt)\nelseif(VITA)\n    include(cmake/package_vita.cmake)\nelseif(NINTENDO_3DS)\n    if(NOT \"${THEXTECH_VERSION_REL}\" STREQUAL \"\")\n        set(SMDH_VERSION \"git branch ${GIT_BRANCH} #${GIT_COMMIT_HASH}\")\n    else()\n        set(SMDH_VERSION \"Based on SMBX 1.3 by Redigit\")\n    endif()\n\n    ctr_generate_smdh(OUTPUT \"thextech.smdh\"\n        NAME \"TheXTech ${THEXTECH_VERSION_STRING}\"\n        DESCRIPTION \"${SMDH_VERSION}\"\n        AUTHOR \"Wohlstand and ds-sloth\"\n        ICON \"${CMAKE_SOURCE_DIR}/resources/icon/thextech_48.png\"\n    )\n\n    ctr_create_3dsx(thextech SMDH \"thextech.smdh\")\nelseif(NINTENDO_WII)\n    string(TIMESTAMP XTECH_WIIMETA_RELEASE_DATE \"%Y%m%d\")\n    if(NOT \"${THEXTECH_VERSION_REL}\" STREQUAL \"\")\n        set(XTECH_WIIMETA_SHORT_DESC \"git ${GIT_BRANCH} #${GIT_COMMIT_HASH}\")\n    else()\n        set(XTECH_WIIMETA_SHORT_DESC \"Based on SMBX 1.3 by Redigit\")\n    endif()\n\n    configure_file(${CMAKE_SOURCE_DIR}/resources/wii/icon.png ${CMAKE_BINARY_DIR}/package/icon.png COPYONLY)\n    configure_file(${CMAKE_SOURCE_DIR}/resources/wii/meta.xml.in ${CMAKE_BINARY_DIR}/package/meta.xml)\n\n    ogc_create_dol(thextech)\n    add_custom_command(TARGET thextech POST_BUILD\n                       COMMAND ${CMAKE_COMMAND} -E copy\n                           ${CMAKE_BINARY_DIR}/thextech.dol\n                           ${CMAKE_BINARY_DIR}/package/boot.dol\n                       COMMENT \"Copying thextech executable into package\")\nelseif(NINTENDO_WIIU)\n    string(TIMESTAMP XTECH_WIIMETA_RELEASE_DATE \"%Y%m%d\")\n    configure_file(${CMAKE_SOURCE_DIR}/resources/wiiu/icon.png ${CMAKE_BINARY_DIR}/icon.png COPYONLY)\n    configure_file(${CMAKE_SOURCE_DIR}/resources/wiiu/meta.xml.in ${CMAKE_BINARY_DIR}/meta.xml)\n    wut_create_rpx(thextech)\n    wut_create_wuhb(thextech\n        CONTENT\n        NAME \"TheXTech v${THEXTECH_VERSION_STRING}\"\n        SHORTNAME \"TheXTech\"\n        AUTHOR \"Wohlstand and ds-sloth\"\n        ICON \"${CMAKE_SOURCE_DIR}/resources/switch/thextech-logo.png\"\n        TVSPLASH \"${CMAKE_SOURCE_DIR}/resources/wiiu/wuhb-splash.png\"\n        DRCSPLASH \"${CMAKE_SOURCE_DIR}/resources/wiiu/wuhb-splash.png\"\n    )\nelseif(NINTENDO_DS)\n    if(NOT \"${THEXTECH_VERSION_REL}\" STREQUAL \"\")\n        set(ROM_VERSION \"${GIT_BRANCH} #${GIT_COMMIT_HASH}\")\n    else()\n        set(ROM_VERSION \"Based on SMBX 1.3\")\n    endif()\n\n    nds_create_rom(thextech\n        NAME \"TheXTech ${THEXTECH_VERSION_STRING}\"\n        SUBTITLE1 \"Wohlstand and ds-sloth\"\n        SUBTITLE2 \"${ROM_VERSION}\"\n        ICON \"${CMAKE_SOURCE_DIR}/resources/nds/icon.bmp\"\n    )\nelseif(NINTENDO_SWITCH)\n    include(cmake/package_switch.cmake)\nelseif(EMSCRIPTEN)\n    add_custom_command(\n        TARGET thextech POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E rename\n                \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${THEXTECH_EXECUTABLE_NAME}.html\"\n                \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/index.html\")\n\n    add_custom_command(\n        TARGET thextech POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E copy\n                \"${PGE_PRELOAD_ENVIRONMENT}/graphics/ui/icon/thextech.ico\"\n                \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/favicon.ico\")\n\n    add_custom_command(\n        TARGET thextech POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E copy\n                \"${PGE_PRELOAD_ENVIRONMENT}/graphics/ui/icon/thextech_32.png\"\n                \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/thextech_32.png\")\n\n    add_custom_command(\n        TARGET thextech POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E copy\n                \"${PGE_PRELOAD_ENVIRONMENT}/graphics/ui/icon/thextech_256.png\"\n                \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/thextech_256.png\")\n\n    configure_file(${CMAKE_SOURCE_DIR}/resources/emscripten/manifest.json.in ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/manifest.json)\n    configure_file(${CMAKE_SOURCE_DIR}/resources/emscripten/sw.js.in ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sw.js)\n\nelseif(HAIKU)\n    string(TIMESTAMP XTECH_HAIKU_COPYRIGHT_YEAR \"%Y\")\n    message(\"-- Haiku package version: ${THEXTECH_HAIKU_VERSION_STRING} (Yer ${XTECH_HAIKU_COPYRIGHT_YEAR})\")\n    set(DESKTOP_NAME \"TheXTech\" CACHE STRING \"Name for the application icon\")\n    set(XTECH_HAIKU_SHORT_DESC \"TheXTech is an open-source continuation of the SMBX 1.3 platformer game engine. Its highest priority is full compatibility with the SMBX 1.3 content standard, but it also adds a number of enhancements and bugfixes.\" CACHE STRING \"Description for the installable Haiku package.\")\n    configure_file(\"${CMAKE_SOURCE_DIR}/resources/haiku/PackageInfo.in\" \"${CMAKE_BINARY_DIR}/package/.PackageInfo\")\n\n    set_target_properties(thextech PROPERTIES LINK_FLAGS \"-Wl,-rpath,'$ORIGIN/../lib'\")\n\n    add_custom_command(\n        TARGET thextech POST_BUILD\n        COMMAND rc\n                -o \"${CMAKE_BINARY_DIR}/icon.rsrc\"\n                \"${CMAKE_SOURCE_DIR}/resources/haiku/icon.rdef\"\n                COMMENT \"Preparing icon file...\")\n\n    add_custom_command(\n        TARGET thextech POST_BUILD\n        COMMAND resattr\n                -o \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${THEXTECH_EXECUTABLE_NAME}\"\n                \"${CMAKE_BINARY_DIR}/icon.rsrc\"\n        COMMENT \"Assigning icon to ${THEXTECH_EXECUTABLE_NAME}...\")\n\n    if(NOT EXISTS \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}\")\n        file(MAKE_DIRECTORY \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin\")\n        file(MAKE_DIRECTORY \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/lib\")\n        file(MAKE_DIRECTORY \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/doc\")\n    endif()\n\n    if(NOT EXISTS \"${CMAKE_BINARY_DIR}/package/data/deskbar/menu/Applications\")\n        file(MAKE_DIRECTORY \"${CMAKE_BINARY_DIR}/package/data/deskbar/menu/Applications\")\n    endif()\n\n    add_custom_target(make_hkpg\n        DEPENDS \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin/${THEXTECH_EXECUTABLE_NAME}\"\n        COMMENT \"Creating HPKG package...\"\n    )\n\n    add_custom_command(\n        OUTPUT \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin/${THEXTECH_EXECUTABLE_NAME}\"\n        DEPENDS \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${THEXTECH_EXECUTABLE_NAME}\"\n        COMMAND\n            ${CMAKE_COMMAND} -E copy\n            \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${THEXTECH_EXECUTABLE_NAME}\"\n            \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin/${THEXTECH_EXECUTABLE_NAME}\"\n    )\n\n    add_custom_command(\n        TARGET make_hkpg PRE_BUILD\n        COMMAND\n            ${CMAKE_COMMAND} -E copy\n            \"${CMAKE_SOURCE_DIR}/changelog.txt\"\n            \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/doc/changelog.txt\"\n    )\n\n    add_custom_command(\n        TARGET make_hkpg PRE_BUILD\n        COMMAND\n            ${CMAKE_COMMAND} -E copy\n            \"${CMAKE_SOURCE_DIR}/README.md\"\n            \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/doc/ReadMe.txt\"\n    )\n\n    add_custom_command(\n        TARGET make_hkpg PRE_BUILD\n        COMMAND\n            ${CMAKE_COMMAND} -E copy\n            \"${CMAKE_SOURCE_DIR}/LICENSE\"\n            \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/doc/License.TheXTech.txt\"\n    )\n\n    add_custom_command(\n        TARGET make_hkpg PRE_BUILD\n        COMMAND resattr\n                -o \"${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin/${THEXTECH_EXECUTABLE_NAME}\"\n                \"${CMAKE_BINARY_DIR}/icon.rsrc\"\n        COMMENT \"Assigning icon to ${THEXTECH_EXECUTABLE_NAME}...\")\n\n    add_custom_command(\n        TARGET make_hkpg PRE_BUILD\n        COMMAND cp -a \"${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/*.so*\" \"${CMAKE_BINARY_DIR}/package/apps/TheXTech/lib/\"\n    )\n\n    add_custom_command(\n        TARGET make_hkpg PRE_BUILD\n        COMMAND ${CMAKE_COMMAND} -E create_symlink\n                \"../../../../apps/TheXTech/bin/${THEXTECH_EXECUTABLE_NAME}\"\n                \"${CMAKE_BINARY_DIR}/package/data/deskbar/menu/Applications/${DESKTOP_NAME}\"\n    )\n\n    add_custom_command(\n        TARGET make_hkpg POST_BUILD\n        COMMAND package create -C \"${CMAKE_BINARY_DIR}/package/\"\n                \"${CMAKE_BINARY_DIR}/${THEXTECH_INSTALLER_PACKAGE_NAME}_bin-${THEXTECH_HAIKU_VERSION_STRING}-${TARGET_PROCESSOR}.hpkg\"\n    )\n\nelse()\n    install(TARGETS thextech\n        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}\n        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}\n        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}\n    )\n\n    if(THEXTECH_UNIX_INSTALL)\n        # set up application metadata\n        if(NOT HAIKU)\n            # application launcher\n            if(NOT EXISTS \"${DEPENDENCIES_INSTALL_DIR}/share/applications\")\n                file(MAKE_DIRECTORY \"${DEPENDENCIES_INSTALL_DIR}/share/applications\")\n            endif()\n\n            set(DESKTOP_EXEC ${THEXTECH_EXECUTABLE_NAME})\n            set(DESKTOP_ICON ${THEXTECH_EXECUTABLE_NAME})\n            set(DESKTOP_WMCLASS ${THEXTECH_EXECUTABLE_NAME})\n            set(DESKTOP_NAME \"TheXTech\" CACHE STRING \"Name for the application icon\")\n            set(DESKTOP_GENERIC_NAME \"TheXTech Engine\" CACHE STRING \"\")\n            set(DESKTOP_COMMENT \"TheXTech - the modern C++ port and successor of the SMBX engine\" CACHE STRING \"Short description of the application\")\n\n            configure_file(\"${CMAKE_CURRENT_SOURCE_DIR}/cmake/icon.desktop.in\"\n                           \"${DEPENDENCIES_INSTALL_DIR}/share/applications/${THEXTECH_EXECUTABLE_NAME}.desktop\")\n            install(FILES \"${DEPENDENCIES_INSTALL_DIR}/share/applications/${THEXTECH_EXECUTABLE_NAME}.desktop\"\n                    DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications)\n\n            # flatpak manifest\n            set(FLATPAK_BUILD OFF CACHE BOOL \"Is this build a part of a flatpak workflow?\")\n            mark_as_advanced(FLATPAK_BUILD)\n            if(FLATPAK_BUILD)\n                set(FLATPAK_DESCRIPTION \"TheXTech is an open-source continuation of the SMBX 1.3 platformer game engine. Its highest priority is full compatibility with the SMBX 1.3 content standard, but it also adds a number of enhancements and bugfixes.\" CACHE STRING \"Description for the installable flatpak package.\")\n\n                configure_file(\"${CMAKE_CURRENT_SOURCE_DIR}/resources/flatpak/appdata.xml.in\"\n                               \"${DEPENDENCIES_INSTALL_DIR}/share/metainfo/${THEXTECH_EXECUTABLE_NAME}.metainfo.xml\")\n                install(FILES \"${DEPENDENCIES_INSTALL_DIR}/share/metainfo/${THEXTECH_EXECUTABLE_NAME}.metainfo.xml\"\n                        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo)\n            endif()\n\n            # application icons\n            set(THEXTECH_ICON_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/resources/icon/\")\n\n            install(FILES \"${THEXTECH_ICON_PATH}/thextech_16.png\"\n                    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps/\" RENAME \"${THEXTECH_EXECUTABLE_NAME}.png\")\n            install(FILES \"${THEXTECH_ICON_PATH}/thextech_32.png\"\n                    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps/\" RENAME \"${THEXTECH_EXECUTABLE_NAME}.png\")\n            install(FILES \"${THEXTECH_ICON_PATH}/thextech_48.png\"\n                    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps/\" RENAME \"${THEXTECH_EXECUTABLE_NAME}.png\")\n            install(FILES \"${THEXTECH_ICON_PATH}/thextech_128.png\"\n                    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps/\" RENAME \"${THEXTECH_EXECUTABLE_NAME}.png\")\n            install(FILES \"${THEXTECH_ICON_PATH}/thextech_256.png\"\n                    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps/\" RENAME \"${THEXTECH_EXECUTABLE_NAME}.png\")\n            install(FILES \"${THEXTECH_ICON_PATH}/thextech_512.png\" OPTIONAL\n                    DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps/\" RENAME \"${THEXTECH_EXECUTABLE_NAME}.png\")\n        endif()\n\n        # install readmes\n        install(FILES changelog.txt DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/${THEXTECH_EXECUTABLE_NAME})\n        install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/${THEXTECH_EXECUTABLE_NAME} RENAME ReadMe.txt)\n        install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/${THEXTECH_EXECUTABLE_NAME} RENAME License.TheXTech.txt)\n    else()\n        install(FILES changelog.txt DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/thextech)\n        install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/thextech RENAME ReadMe.txt)\n        install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/thextech RENAME License.TheXTech.txt)\n    endif()\nendif()\n\n# ================== Clean-up ==================\nadd_custom_target(clean_external)\nadd_custom_command(\n    TARGET clean_external POST_BUILD\n    COMMAND ${CMAKE_COMMAND} -E remove_directory \"${CMAKE_BINARY_DIR}/external/\"\n    COMMAND ${CMAKE_COMMAND} -E remove_directory \"${CMAKE_BINARY_DIR}/output/include\"\n    COMMAND ${CMAKE_COMMAND} -E remove_directory \"${CMAKE_BINARY_DIR}/output/lib\"\n    COMMAND ${CMAKE_COMMAND} -E remove_directory \"${CMAKE_BINARY_DIR}/output/share\"\n)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "We're glad you're interested in the project! Well-implemented PRs that improve TheXTech's compatibility with SMBX64 or add/improve our ports are always welcome. We are also very interested in PRs that add quality-of-life features to base SMBX64 content.\n\n# Project Roadmap\n\nA broad overview of the goals for the project in the next few versions:\n\n## 1.3.7 (released)\n\n* [Multiple resolution system to play game at non-800x600 resolutions](https://github.com/TheXTech/TheXTech/pull/328)\n* [In-game configuration system](https://github.com/TheXTech/TheXTech/pull/734)\n* Shared-screen and split-screen >2P multiplayer\n\n## 1.3.7.x\n\n* Compressed archive support for episodes and asset packs (`.xte` / `.xta`)\n* Updated file formats system\n* NetPlay (aka Online Play) system\n\n## 1.(3.)8\n\n* See [here](https://github.com/TheXTech/TheXTech/issues/952).\n\n## ???\n\n* [Luau scripting system](https://github.com/TheXTech/TheXTech/issues/472) for **Classic Events** and **NPC's**\n* GLSL ES shader API\n* TAS System\n* Rewinding\n\nPlease see the [Issues](https://github.com/TheXTech/TheXTech/issues) and [milestones](https://github.com/TheXTech/TheXTech/milestones) that developers have endorsed for a fine-grained list of the development tasks and targets we are currently considering.\n\n## An important note on the scope of TheXTech\n\nTheXTech is based on the [SMBX64](https://wohlsoft.ru/pgewiki/SMBX64) standard, and it also aims for limited compatibility with the other SMBX branches, as well as Low-end device support, NetPlay support, and TAS/Rewind system support. Therefore, any topics (including but not limited to issues, PR's, and discussions) whose purpose is to make the base TheXTech engine with one of the following situations, will be rejected as **out-of-scope**, even with Lua Script System support:\n\n1. Imitating features of non-SMBX games\n2. Featuring major gameplay changes\n3. Affecting low-end device support\n  - We monitor code size, static and dynamic memory usage, and CPU performance\n4. Breaks legacy SMBX64 logic\n5. Affects past works\n6. Spamming new content ideas\n7. Causing the engine slowdown\n8. Affecting TAS (Tool-assisted Speedrun) Mechanic, Netplay, or rewinding\n9. Mentioning unofficial engine features\n10. Create lots of forks\n  - For anything platform games to be made from scratch, You should use **Moondust Engine** instead, but currently is in progress.\n\nRelated comments will also be marked as \"off-topic\". Related conversations of partial issues and PR's will be locked as \"off-topic\".\n\n## A note on \"bugfixes\"\n\nTheXTech is a faithful reproduction of the SMBX 1.3 engine, so a number of things that may appear to be bugs to a user or contributor may actually be TheXTech reproducing SMBX 1.3 logic intended. Please see [Types of Bugs](https://github.com/TheXTech/TheXTech/wiki/Types-of-bugs) for a categorization of types of bugs.\n\nPRs fixing native bugs and critical vanilla bugs are always welcome (but you may be asked to justify that the bug in question is indeed critical).\n\nFixes for other vanilla bugs are not a good first contribution to the engine, due to the considerations we need to employ when faced with these. You are welcome to file an issue for any vanilla peculiarities you discover, but we are unlikely to fix them.\n\n# TheXTech content standard\nWe are in the process of developing a content standard for TheXTech, including new engine features such as multistars, more flexible warps, and world map sections. We are also developing a scripting layer including TheXTech-specific Lua and GLSL ES APIs.\n\nWe are being very careful in how we create the TheXTech standard, and we are generally hesitant to accept PRs that add new content to the standard. We intend to maintain indefinite compatibility with all content created for TheXTech 1.3.7+, and this makes it difficult to remove or refactor features once added.\n\n# Ports and limitations\n\nWe maintain ports for a wide variety of systems, and you should take care when developing new features to ensure that the appropriate ports of the game will continue to run with our minimum system requirements (16 MB of RAM and 200 MHz CPU).\n\nIf you are interested in contributing new ports to any such systems, please see [Porting](PORTING.md).\n"
  },
  {
    "path": "LICENSE",
    "content": "GNU GENERAL PUBLIC LICENSE\r\n                       Version 3, 29 June 2007\r\n\r\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\r\n Everyone is permitted to copy and distribute verbatim copies\r\n of this license document, but changing it is not allowed.\r\n\r\n                            Preamble\r\n\r\n  The GNU General Public License is a free, copyleft license for\r\nsoftware and other kinds of works.\r\n\r\n  The licenses for most software and other practical works are designed\r\nto take away your freedom to share and change the works.  By contrast,\r\nthe GNU General Public License is intended to guarantee your freedom to\r\nshare and change all versions of a program--to make sure it remains free\r\nsoftware for all its users.  We, the Free Software Foundation, use the\r\nGNU General Public License for most of our software; it applies also to\r\nany other work released this way by its authors.  You can apply it to\r\nyour programs, too.\r\n\r\n  When we speak of free software, we are referring to freedom, not\r\nprice.  Our General Public Licenses are designed to make sure that you\r\nhave the freedom to distribute copies of free software (and charge for\r\nthem if you wish), that you receive source code or can get it if you\r\nwant it, that you can change the software or use pieces of it in new\r\nfree programs, and that you know you can do these things.\r\n\r\n  To protect your rights, we need to prevent others from denying you\r\nthese rights or asking you to surrender the rights.  Therefore, you have\r\ncertain responsibilities if you distribute copies of the software, or if\r\nyou modify it: responsibilities to respect the freedom of others.\r\n\r\n  For example, if you distribute copies of such a program, whether\r\ngratis or for a fee, you must pass on to the recipients the same\r\nfreedoms that you received.  You must make sure that they, too, receive\r\nor can get the source code.  And you must show them these terms so they\r\nknow their rights.\r\n\r\n  Developers that use the GNU GPL protect your rights with two steps:\r\n(1) assert copyright on the software, and (2) offer you this License\r\ngiving you legal permission to copy, distribute and/or modify it.\r\n\r\n  For the developers' and authors' protection, the GPL clearly explains\r\nthat there is no warranty for this free software.  For both users' and\r\nauthors' sake, the GPL requires that modified versions be marked as\r\nchanged, so that their problems will not be attributed erroneously to\r\nauthors of previous versions.\r\n\r\n  Some devices are designed to deny users access to install or run\r\nmodified versions of the software inside them, although the manufacturer\r\ncan do so.  This is fundamentally incompatible with the aim of\r\nprotecting users' freedom to change the software.  The systematic\r\npattern of such abuse occurs in the area of products for individuals to\r\nuse, which is precisely where it is most unacceptable.  Therefore, we\r\nhave designed this version of the GPL to prohibit the practice for those\r\nproducts.  If such problems arise substantially in other domains, we\r\nstand ready to extend this provision to those domains in future versions\r\nof the GPL, as needed to protect the freedom of users.\r\n\r\n  Finally, every program is threatened constantly by software patents.\r\nStates should not allow patents to restrict development and use of\r\nsoftware on general-purpose computers, but in those that do, we wish to\r\navoid the special danger that patents applied to a free program could\r\nmake it effectively proprietary.  To prevent this, the GPL assures that\r\npatents cannot be used to render the program non-free.\r\n\r\n  The precise terms and conditions for copying, distribution and\r\nmodification follow.\r\n\r\n                       TERMS AND CONDITIONS\r\n\r\n  0. Definitions.\r\n\r\n  \"This License\" refers to version 3 of the GNU General Public License.\r\n\r\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\r\nworks, such as semiconductor masks.\r\n\r\n  \"The Program\" refers to any copyrightable work licensed under this\r\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\r\n\"recipients\" may be individuals or organizations.\r\n\r\n  To \"modify\" a work means to copy from or adapt all or part of the work\r\nin a fashion requiring copyright permission, other than the making of an\r\nexact copy.  The resulting work is called a \"modified version\" of the\r\nearlier work or a work \"based on\" the earlier work.\r\n\r\n  A \"covered work\" means either the unmodified Program or a work based\r\non the Program.\r\n\r\n  To \"propagate\" a work means to do anything with it that, without\r\npermission, would make you directly or secondarily liable for\r\ninfringement under applicable copyright law, except executing it on a\r\ncomputer or modifying a private copy.  Propagation includes copying,\r\ndistribution (with or without modification), making available to the\r\npublic, and in some countries other activities as well.\r\n\r\n  To \"convey\" a work means any kind of propagation that enables other\r\nparties to make or receive copies.  Mere interaction with a user through\r\na computer network, with no transfer of a copy, is not conveying.\r\n\r\n  An interactive user interface displays \"Appropriate Legal Notices\"\r\nto the extent that it includes a convenient and prominently visible\r\nfeature that (1) displays an appropriate copyright notice, and (2)\r\ntells the user that there is no warranty for the work (except to the\r\nextent that warranties are provided), that licensees may convey the\r\nwork under this License, and how to view a copy of this License.  If\r\nthe interface presents a list of user commands or options, such as a\r\nmenu, a prominent item in the list meets this criterion.\r\n\r\n  1. Source Code.\r\n\r\n  The \"source code\" for a work means the preferred form of the work\r\nfor making modifications to it.  \"Object code\" means any non-source\r\nform of a work.\r\n\r\n  A \"Standard Interface\" means an interface that either is an official\r\nstandard defined by a recognized standards body, or, in the case of\r\ninterfaces specified for a particular programming language, one that\r\nis widely used among developers working in that language.\r\n\r\n  The \"System Libraries\" of an executable work include anything, other\r\nthan the work as a whole, that (a) is included in the normal form of\r\npackaging a Major Component, but which is not part of that Major\r\nComponent, and (b) serves only to enable use of the work with that\r\nMajor Component, or to implement a Standard Interface for which an\r\nimplementation is available to the public in source code form.  A\r\n\"Major Component\", in this context, means a major essential component\r\n(kernel, window system, and so on) of the specific operating system\r\n(if any) on which the executable work runs, or a compiler used to\r\nproduce the work, or an object code interpreter used to run it.\r\n\r\n  The \"Corresponding Source\" for a work in object code form means all\r\nthe source code needed to generate, install, and (for an executable\r\nwork) run the object code and to modify the work, including scripts to\r\ncontrol those activities.  However, it does not include the work's\r\nSystem Libraries, or general-purpose tools or generally available free\r\nprograms which are used unmodified in performing those activities but\r\nwhich are not part of the work.  For example, Corresponding Source\r\nincludes interface definition files associated with source files for\r\nthe work, and the source code for shared libraries and dynamically\r\nlinked subprograms that the work is specifically designed to require,\r\nsuch as by intimate data communication or control flow between those\r\nsubprograms and other parts of the work.\r\n\r\n  The Corresponding Source need not include anything that users\r\ncan regenerate automatically from other parts of the Corresponding\r\nSource.\r\n\r\n  The Corresponding Source for a work in source code form is that\r\nsame work.\r\n\r\n  2. Basic Permissions.\r\n\r\n  All rights granted under this License are granted for the term of\r\ncopyright on the Program, and are irrevocable provided the stated\r\nconditions are met.  This License explicitly affirms your unlimited\r\npermission to run the unmodified Program.  The output from running a\r\ncovered work is covered by this License only if the output, given its\r\ncontent, constitutes a covered work.  This License acknowledges your\r\nrights of fair use or other equivalent, as provided by copyright law.\r\n\r\n  You may make, run and propagate covered works that you do not\r\nconvey, without conditions so long as your license otherwise remains\r\nin force.  You may convey covered works to others for the sole purpose\r\nof having them make modifications exclusively for you, or provide you\r\nwith facilities for running those works, provided that you comply with\r\nthe terms of this License in conveying all material for which you do\r\nnot control copyright.  Those thus making or running the covered works\r\nfor you must do so exclusively on your behalf, under your direction\r\nand control, on terms that prohibit them from making any copies of\r\nyour copyrighted material outside their relationship with you.\r\n\r\n  Conveying under any other circumstances is permitted solely under\r\nthe conditions stated below.  Sublicensing is not allowed; section 10\r\nmakes it unnecessary.\r\n\r\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\r\n\r\n  No covered work shall be deemed part of an effective technological\r\nmeasure under any applicable law fulfilling obligations under article\r\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\r\nsimilar laws prohibiting or restricting circumvention of such\r\nmeasures.\r\n\r\n  When you convey a covered work, you waive any legal power to forbid\r\ncircumvention of technological measures to the extent such circumvention\r\nis effected by exercising rights under this License with respect to\r\nthe covered work, and you disclaim any intention to limit operation or\r\nmodification of the work as a means of enforcing, against the work's\r\nusers, your or third parties' legal rights to forbid circumvention of\r\ntechnological measures.\r\n\r\n  4. Conveying Verbatim Copies.\r\n\r\n  You may convey verbatim copies of the Program's source code as you\r\nreceive it, in any medium, provided that you conspicuously and\r\nappropriately publish on each copy an appropriate copyright notice;\r\nkeep intact all notices stating that this License and any\r\nnon-permissive terms added in accord with section 7 apply to the code;\r\nkeep intact all notices of the absence of any warranty; and give all\r\nrecipients a copy of this License along with the Program.\r\n\r\n  You may charge any price or no price for each copy that you convey,\r\nand you may offer support or warranty protection for a fee.\r\n\r\n  5. Conveying Modified Source Versions.\r\n\r\n  You may convey a work based on the Program, or the modifications to\r\nproduce it from the Program, in the form of source code under the\r\nterms of section 4, provided that you also meet all of these conditions:\r\n\r\n    a) The work must carry prominent notices stating that you modified\r\n    it, and giving a relevant date.\r\n\r\n    b) The work must carry prominent notices stating that it is\r\n    released under this License and any conditions added under section\r\n    7.  This requirement modifies the requirement in section 4 to\r\n    \"keep intact all notices\".\r\n\r\n    c) You must license the entire work, as a whole, under this\r\n    License to anyone who comes into possession of a copy.  This\r\n    License will therefore apply, along with any applicable section 7\r\n    additional terms, to the whole of the work, and all its parts,\r\n    regardless of how they are packaged.  This License gives no\r\n    permission to license the work in any other way, but it does not\r\n    invalidate such permission if you have separately received it.\r\n\r\n    d) If the work has interactive user interfaces, each must display\r\n    Appropriate Legal Notices; however, if the Program has interactive\r\n    interfaces that do not display Appropriate Legal Notices, your\r\n    work need not make them do so.\r\n\r\n  A compilation of a covered work with other separate and independent\r\nworks, which are not by their nature extensions of the covered work,\r\nand which are not combined with it such as to form a larger program,\r\nin or on a volume of a storage or distribution medium, is called an\r\n\"aggregate\" if the compilation and its resulting copyright are not\r\nused to limit the access or legal rights of the compilation's users\r\nbeyond what the individual works permit.  Inclusion of a covered work\r\nin an aggregate does not cause this License to apply to the other\r\nparts of the aggregate.\r\n\r\n  6. Conveying Non-Source Forms.\r\n\r\n  You may convey a covered work in object code form under the terms\r\nof sections 4 and 5, provided that you also convey the\r\nmachine-readable Corresponding Source under the terms of this License,\r\nin one of these ways:\r\n\r\n    a) Convey the object code in, or embodied in, a physical product\r\n    (including a physical distribution medium), accompanied by the\r\n    Corresponding Source fixed on a durable physical medium\r\n    customarily used for software interchange.\r\n\r\n    b) Convey the object code in, or embodied in, a physical product\r\n    (including a physical distribution medium), accompanied by a\r\n    written offer, valid for at least three years and valid for as\r\n    long as you offer spare parts or customer support for that product\r\n    model, to give anyone who possesses the object code either (1) a\r\n    copy of the Corresponding Source for all the software in the\r\n    product that is covered by this License, on a durable physical\r\n    medium customarily used for software interchange, for a price no\r\n    more than your reasonable cost of physically performing this\r\n    conveying of source, or (2) access to copy the\r\n    Corresponding Source from a network server at no charge.\r\n\r\n    c) Convey individual copies of the object code with a copy of the\r\n    written offer to provide the Corresponding Source.  This\r\n    alternative is allowed only occasionally and noncommercially, and\r\n    only if you received the object code with such an offer, in accord\r\n    with subsection 6b.\r\n\r\n    d) Convey the object code by offering access from a designated\r\n    place (gratis or for a charge), and offer equivalent access to the\r\n    Corresponding Source in the same way through the same place at no\r\n    further charge.  You need not require recipients to copy the\r\n    Corresponding Source along with the object code.  If the place to\r\n    copy the object code is a network server, the Corresponding Source\r\n    may be on a different server (operated by you or a third party)\r\n    that supports equivalent copying facilities, provided you maintain\r\n    clear directions next to the object code saying where to find the\r\n    Corresponding Source.  Regardless of what server hosts the\r\n    Corresponding Source, you remain obligated to ensure that it is\r\n    available for as long as needed to satisfy these requirements.\r\n\r\n    e) Convey the object code using peer-to-peer transmission, provided\r\n    you inform other peers where the object code and Corresponding\r\n    Source of the work are being offered to the general public at no\r\n    charge under subsection 6d.\r\n\r\n  A separable portion of the object code, whose source code is excluded\r\nfrom the Corresponding Source as a System Library, need not be\r\nincluded in conveying the object code work.\r\n\r\n  A \"User Product\" is either (1) a \"consumer product\", which means any\r\ntangible personal property which is normally used for personal, family,\r\nor household purposes, or (2) anything designed or sold for incorporation\r\ninto a dwelling.  In determining whether a product is a consumer product,\r\ndoubtful cases shall be resolved in favor of coverage.  For a particular\r\nproduct received by a particular user, \"normally used\" refers to a\r\ntypical or common use of that class of product, regardless of the status\r\nof the particular user or of the way in which the particular user\r\nactually uses, or expects or is expected to use, the product.  A product\r\nis a consumer product regardless of whether the product has substantial\r\ncommercial, industrial or non-consumer uses, unless such uses represent\r\nthe only significant mode of use of the product.\r\n\r\n  \"Installation Information\" for a User Product means any methods,\r\nprocedures, authorization keys, or other information required to install\r\nand execute modified versions of a covered work in that User Product from\r\na modified version of its Corresponding Source.  The information must\r\nsuffice to ensure that the continued functioning of the modified object\r\ncode is in no case prevented or interfered with solely because\r\nmodification has been made.\r\n\r\n  If you convey an object code work under this section in, or with, or\r\nspecifically for use in, a User Product, and the conveying occurs as\r\npart of a transaction in which the right of possession and use of the\r\nUser Product is transferred to the recipient in perpetuity or for a\r\nfixed term (regardless of how the transaction is characterized), the\r\nCorresponding Source conveyed under this section must be accompanied\r\nby the Installation Information.  But this requirement does not apply\r\nif neither you nor any third party retains the ability to install\r\nmodified object code on the User Product (for example, the work has\r\nbeen installed in ROM).\r\n\r\n  The requirement to provide Installation Information does not include a\r\nrequirement to continue to provide support service, warranty, or updates\r\nfor a work that has been modified or installed by the recipient, or for\r\nthe User Product in which it has been modified or installed.  Access to a\r\nnetwork may be denied when the modification itself materially and\r\nadversely affects the operation of the network or violates the rules and\r\nprotocols for communication across the network.\r\n\r\n  Corresponding Source conveyed, and Installation Information provided,\r\nin accord with this section must be in a format that is publicly\r\ndocumented (and with an implementation available to the public in\r\nsource code form), and must require no special password or key for\r\nunpacking, reading or copying.\r\n\r\n  7. Additional Terms.\r\n\r\n  \"Additional permissions\" are terms that supplement the terms of this\r\nLicense by making exceptions from one or more of its conditions.\r\nAdditional permissions that are applicable to the entire Program shall\r\nbe treated as though they were included in this License, to the extent\r\nthat they are valid under applicable law.  If additional permissions\r\napply only to part of the Program, that part may be used separately\r\nunder those permissions, but the entire Program remains governed by\r\nthis License without regard to the additional permissions.\r\n\r\n  When you convey a copy of a covered work, you may at your option\r\nremove any additional permissions from that copy, or from any part of\r\nit.  (Additional permissions may be written to require their own\r\nremoval in certain cases when you modify the work.)  You may place\r\nadditional permissions on material, added by you to a covered work,\r\nfor which you have or can give appropriate copyright permission.\r\n\r\n  Notwithstanding any other provision of this License, for material you\r\nadd to a covered work, you may (if authorized by the copyright holders of\r\nthat material) supplement the terms of this License with terms:\r\n\r\n    a) Disclaiming warranty or limiting liability differently from the\r\n    terms of sections 15 and 16 of this License; or\r\n\r\n    b) Requiring preservation of specified reasonable legal notices or\r\n    author attributions in that material or in the Appropriate Legal\r\n    Notices displayed by works containing it; or\r\n\r\n    c) Prohibiting misrepresentation of the origin of that material, or\r\n    requiring that modified versions of such material be marked in\r\n    reasonable ways as different from the original version; or\r\n\r\n    d) Limiting the use for publicity purposes of names of licensors or\r\n    authors of the material; or\r\n\r\n    e) Declining to grant rights under trademark law for use of some\r\n    trade names, trademarks, or service marks; or\r\n\r\n    f) Requiring indemnification of licensors and authors of that\r\n    material by anyone who conveys the material (or modified versions of\r\n    it) with contractual assumptions of liability to the recipient, for\r\n    any liability that these contractual assumptions directly impose on\r\n    those licensors and authors.\r\n\r\n  All other non-permissive additional terms are considered \"further\r\nrestrictions\" within the meaning of section 10.  If the Program as you\r\nreceived it, or any part of it, contains a notice stating that it is\r\ngoverned by this License along with a term that is a further\r\nrestriction, you may remove that term.  If a license document contains\r\na further restriction but permits relicensing or conveying under this\r\nLicense, you may add to a covered work material governed by the terms\r\nof that license document, provided that the further restriction does\r\nnot survive such relicensing or conveying.\r\n\r\n  If you add terms to a covered work in accord with this section, you\r\nmust place, in the relevant source files, a statement of the\r\nadditional terms that apply to those files, or a notice indicating\r\nwhere to find the applicable terms.\r\n\r\n  Additional terms, permissive or non-permissive, may be stated in the\r\nform of a separately written license, or stated as exceptions;\r\nthe above requirements apply either way.\r\n\r\n  8. Termination.\r\n\r\n  You may not propagate or modify a covered work except as expressly\r\nprovided under this License.  Any attempt otherwise to propagate or\r\nmodify it is void, and will automatically terminate your rights under\r\nthis License (including any patent licenses granted under the third\r\nparagraph of section 11).\r\n\r\n  However, if you cease all violation of this License, then your\r\nlicense from a particular copyright holder is reinstated (a)\r\nprovisionally, unless and until the copyright holder explicitly and\r\nfinally terminates your license, and (b) permanently, if the copyright\r\nholder fails to notify you of the violation by some reasonable means\r\nprior to 60 days after the cessation.\r\n\r\n  Moreover, your license from a particular copyright holder is\r\nreinstated permanently if the copyright holder notifies you of the\r\nviolation by some reasonable means, this is the first time you have\r\nreceived notice of violation of this License (for any work) from that\r\ncopyright holder, and you cure the violation prior to 30 days after\r\nyour receipt of the notice.\r\n\r\n  Termination of your rights under this section does not terminate the\r\nlicenses of parties who have received copies or rights from you under\r\nthis License.  If your rights have been terminated and not permanently\r\nreinstated, you do not qualify to receive new licenses for the same\r\nmaterial under section 10.\r\n\r\n  9. Acceptance Not Required for Having Copies.\r\n\r\n  You are not required to accept this License in order to receive or\r\nrun a copy of the Program.  Ancillary propagation of a covered work\r\noccurring solely as a consequence of using peer-to-peer transmission\r\nto receive a copy likewise does not require acceptance.  However,\r\nnothing other than this License grants you permission to propagate or\r\nmodify any covered work.  These actions infringe copyright if you do\r\nnot accept this License.  Therefore, by modifying or propagating a\r\ncovered work, you indicate your acceptance of this License to do so.\r\n\r\n  10. Automatic Licensing of Downstream Recipients.\r\n\r\n  Each time you convey a covered work, the recipient automatically\r\nreceives a license from the original licensors, to run, modify and\r\npropagate that work, subject to this License.  You are not responsible\r\nfor enforcing compliance by third parties with this License.\r\n\r\n  An \"entity transaction\" is a transaction transferring control of an\r\norganization, or substantially all assets of one, or subdividing an\r\norganization, or merging organizations.  If propagation of a covered\r\nwork results from an entity transaction, each party to that\r\ntransaction who receives a copy of the work also receives whatever\r\nlicenses to the work the party's predecessor in interest had or could\r\ngive under the previous paragraph, plus a right to possession of the\r\nCorresponding Source of the work from the predecessor in interest, if\r\nthe predecessor has it or can get it with reasonable efforts.\r\n\r\n  You may not impose any further restrictions on the exercise of the\r\nrights granted or affirmed under this License.  For example, you may\r\nnot impose a license fee, royalty, or other charge for exercise of\r\nrights granted under this License, and you may not initiate litigation\r\n(including a cross-claim or counterclaim in a lawsuit) alleging that\r\nany patent claim is infringed by making, using, selling, offering for\r\nsale, or importing the Program or any portion of it.\r\n\r\n  11. Patents.\r\n\r\n  A \"contributor\" is a copyright holder who authorizes use under this\r\nLicense of the Program or a work on which the Program is based.  The\r\nwork thus licensed is called the contributor's \"contributor version\".\r\n\r\n  A contributor's \"essential patent claims\" are all patent claims\r\nowned or controlled by the contributor, whether already acquired or\r\nhereafter acquired, that would be infringed by some manner, permitted\r\nby this License, of making, using, or selling its contributor version,\r\nbut do not include claims that would be infringed only as a\r\nconsequence of further modification of the contributor version.  For\r\npurposes of this definition, \"control\" includes the right to grant\r\npatent sublicenses in a manner consistent with the requirements of\r\nthis License.\r\n\r\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\r\npatent license under the contributor's essential patent claims, to\r\nmake, use, sell, offer for sale, import and otherwise run, modify and\r\npropagate the contents of its contributor version.\r\n\r\n  In the following three paragraphs, a \"patent license\" is any express\r\nagreement or commitment, however denominated, not to enforce a patent\r\n(such as an express permission to practice a patent or covenant not to\r\nsue for patent infringement).  To \"grant\" such a patent license to a\r\nparty means to make such an agreement or commitment not to enforce a\r\npatent against the party.\r\n\r\n  If you convey a covered work, knowingly relying on a patent license,\r\nand the Corresponding Source of the work is not available for anyone\r\nto copy, free of charge and under the terms of this License, through a\r\npublicly available network server or other readily accessible means,\r\nthen you must either (1) cause the Corresponding Source to be so\r\navailable, or (2) arrange to deprive yourself of the benefit of the\r\npatent license for this particular work, or (3) arrange, in a manner\r\nconsistent with the requirements of this License, to extend the patent\r\nlicense to downstream recipients.  \"Knowingly relying\" means you have\r\nactual knowledge that, but for the patent license, your conveying the\r\ncovered work in a country, or your recipient's use of the covered work\r\nin a country, would infringe one or more identifiable patents in that\r\ncountry that you have reason to believe are valid.\r\n\r\n  If, pursuant to or in connection with a single transaction or\r\narrangement, you convey, or propagate by procuring conveyance of, a\r\ncovered work, and grant a patent license to some of the parties\r\nreceiving the covered work authorizing them to use, propagate, modify\r\nor convey a specific copy of the covered work, then the patent license\r\nyou grant is automatically extended to all recipients of the covered\r\nwork and works based on it.\r\n\r\n  A patent license is \"discriminatory\" if it does not include within\r\nthe scope of its coverage, prohibits the exercise of, or is\r\nconditioned on the non-exercise of one or more of the rights that are\r\nspecifically granted under this License.  You may not convey a covered\r\nwork if you are a party to an arrangement with a third party that is\r\nin the business of distributing software, under which you make payment\r\nto the third party based on the extent of your activity of conveying\r\nthe work, and under which the third party grants, to any of the\r\nparties who would receive the covered work from you, a discriminatory\r\npatent license (a) in connection with copies of the covered work\r\nconveyed by you (or copies made from those copies), or (b) primarily\r\nfor and in connection with specific products or compilations that\r\ncontain the covered work, unless you entered into that arrangement,\r\nor that patent license was granted, prior to 28 March 2007.\r\n\r\n  Nothing in this License shall be construed as excluding or limiting\r\nany implied license or other defenses to infringement that may\r\notherwise be available to you under applicable patent law.\r\n\r\n  12. No Surrender of Others' Freedom.\r\n\r\n  If conditions are imposed on you (whether by court order, agreement or\r\notherwise) that contradict the conditions of this License, they do not\r\nexcuse you from the conditions of this License.  If you cannot convey a\r\ncovered work so as to satisfy simultaneously your obligations under this\r\nLicense and any other pertinent obligations, then as a consequence you may\r\nnot convey it at all.  For example, if you agree to terms that obligate you\r\nto collect a royalty for further conveying from those to whom you convey\r\nthe Program, the only way you could satisfy both those terms and this\r\nLicense would be to refrain entirely from conveying the Program.\r\n\r\n  13. Use with the GNU Affero General Public License.\r\n\r\n  Notwithstanding any other provision of this License, you have\r\npermission to link or combine any covered work with a work licensed\r\nunder version 3 of the GNU Affero General Public License into a single\r\ncombined work, and to convey the resulting work.  The terms of this\r\nLicense will continue to apply to the part which is the covered work,\r\nbut the special requirements of the GNU Affero General Public License,\r\nsection 13, concerning interaction through a network will apply to the\r\ncombination as such.\r\n\r\n  14. Revised Versions of this License.\r\n\r\n  The Free Software Foundation may publish revised and/or new versions of\r\nthe GNU General Public License from time to time.  Such new versions will\r\nbe similar in spirit to the present version, but may differ in detail to\r\naddress new problems or concerns.\r\n\r\n  Each version is given a distinguishing version number.  If the\r\nProgram specifies that a certain numbered version of the GNU General\r\nPublic License \"or any later version\" applies to it, you have the\r\noption of following the terms and conditions either of that numbered\r\nversion or of any later version published by the Free Software\r\nFoundation.  If the Program does not specify a version number of the\r\nGNU General Public License, you may choose any version ever published\r\nby the Free Software Foundation.\r\n\r\n  If the Program specifies that a proxy can decide which future\r\nversions of the GNU General Public License can be used, that proxy's\r\npublic statement of acceptance of a version permanently authorizes you\r\nto choose that version for the Program.\r\n\r\n  Later license versions may give you additional or different\r\npermissions.  However, no additional obligations are imposed on any\r\nauthor or copyright holder as a result of your choosing to follow a\r\nlater version.\r\n\r\n  15. Disclaimer of Warranty.\r\n\r\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\r\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\r\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\r\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\r\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\r\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\r\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\r\n\r\n  16. Limitation of Liability.\r\n\r\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\r\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\r\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\r\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\r\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\r\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\r\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\r\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\r\nSUCH DAMAGES.\r\n\r\n  17. Interpretation of Sections 15 and 16.\r\n\r\n  If the disclaimer of warranty and limitation of liability provided\r\nabove cannot be given local legal effect according to their terms,\r\nreviewing courts shall apply local law that most closely approximates\r\nan absolute waiver of all civil liability in connection with the\r\nProgram, unless a warranty or assumption of liability accompanies a\r\ncopy of the Program in return for a fee.\r\n\r\n                     END OF TERMS AND CONDITIONS\r\n\r\n            How to Apply These Terms to Your New Programs\r\n\r\n  If you develop a new program, and you want it to be of the greatest\r\npossible use to the public, the best way to achieve this is to make it\r\nfree software which everyone can redistribute and change under these terms.\r\n\r\n  To do so, attach the following notices to the program.  It is safest\r\nto attach them to the start of each source file to most effectively\r\nstate the exclusion of warranty; and each file should have at least\r\nthe \"copyright\" line and a pointer to where the full notice is found.\r\n\r\n    {one line to give the program's name and a brief idea of what it does.}\r\n    Copyright (C) {year}  {name of author}\r\n\r\n    This program is free software: you can redistribute it and/or modify\r\n    it under the terms of the GNU General Public License as published by\r\n    the Free Software Foundation, either version 3 of the License, or\r\n    (at your option) any later version.\r\n\r\n    This program is distributed in the hope that it will be useful,\r\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n    GNU General Public License for more details.\r\n\r\n    You should have received a copy of the GNU General Public License\r\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n\r\nAlso add information on how to contact you by electronic and paper mail.\r\n\r\n  If the program does terminal interaction, make it output a short\r\nnotice like this when it starts in an interactive mode:\r\n\r\n    {project}  Copyright (C) {year}  {fullname}\r\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\r\n    This is free software, and you are welcome to redistribute it\r\n    under certain conditions; type `show c' for details.\r\n\r\nThe hypothetical commands `show w' and `show c' should show the appropriate\r\nparts of the General Public License.  Of course, your program's commands\r\nmight be different; for a GUI interface, you would use an \"about box\".\r\n\r\n  You should also get your employer (if you work as a programmer) or school,\r\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\r\nFor more information on this, and how to apply and follow the GNU GPL, see\r\n<http://www.gnu.org/licenses/>.\r\n\r\n  The GNU General Public License does not permit incorporating your program\r\ninto proprietary programs.  If your program is a subroutine library, you\r\nmay consider it more useful to permit linking proprietary applications with\r\nthe library.  If this is what you want to do, use the GNU Lesser General\r\nPublic License instead of this License.  But first, please read\r\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\r\n"
  },
  {
    "path": "PORTING.md",
    "content": "TheXTech is a cross-platform engine and is built to be easy to port to new systems. It has very limited dependencies and can be built for most POSIX-like targets.\n\n# System Requirements\n\nThe target must have at least **16 MB RAM** (working on Wii with ~48 MB available), a **200 MHz CPU** (working on old Nintendo 3DS with 268 MHz ARM11), and a **C++11-compliant compiler**.\n\nOn some of our existing ports, storage or filesystem bandwidth is a pressing consideration.\n\nFull compliance with the SMBX64 standard requires support for the AND and OR bitwise / logical blend modes, but the game includes tools used by our SDL2 ports to approximate these with alpha blending. Some features require the target to support compiling GLSL ES 1.00 and 3.00 shaders.\n\n# Port types and guidelines\n\nMost versions of the game use SDL2 + OpenGL for rendering and SDL Mixer X for software mixing. Your port may make use of these APIs, but is not required to.\n\n## SDL2\n\nIf your target supports the SDL2 joystick, window, message box, render, audio, and threading APIs, porting to your target may be as simple as pointing CMake to the appropriate toolchain file. You may need to edit the `CMakeLists.txt` build instructions to enable / disable features for your target as appropriate. Use the Switch, Wii U, or Vita port for reference.\n\n## Custom + SDL2 audio\n\nIf your target supports the SDL2 audio and threading APIs but is missing other APIs, you may create custom bindings for many of the subsystems under `src/core/`. Use the 3DS and Wii port for reference.\n\n## Custom\n\nWe maintain a \"null\" port designed to compile on virtually any POSIX-compliant system. This port does **not** depend on SDL2 but uses a shim layer to delegate most SDL2 calls to their POSIX equivalents. This port may be used in a command-line environment with a gameplay recording file to verify gameplay logic before you invest the time in creating a full port for your target.\n\nThe null port may be built by passing `-DTHEXTECH_CLI_BUILD=On -DTHEXTECH_NO_SDL_BUILD=On` to CMake in addition to specifying your toolchain.\n\nYou may initially extend the null port with a minimal renderer for your target system that (for instance) draws rectangles for all drawn textures. This facilitates early testing and development.\n\nWe previously maintained a custom audio library that used the 3DS's DSP for hardware-accelerated mixing. This source code may be checked to understand how to implement a MixerX-like audio library. It may also be possible to replace the audio at a different level of abstraction (for instance, replacing `sound.cpp` entirely).\n"
  },
  {
    "path": "README.ESP.md",
    "content": "<p align=\"center\">\n<a href=\"https://github.com/Wohlstand/TheXTech/releases\"><img src=\"https://raw.githubusercontent.com/Wohlstand/TheXTech/master/resources/icon/thextech_512.png\" alt=\"TheXTech\"></a>\n</p>\n\n# TheXTech\n\nMotor SMBX, reescrito en C ++ a partir de VisualBasic 6.\n\n| <!-- -->      | <!-- -->        | <!-- -->      |\n|:-------------:|:---------------:|:-------------:|\n| [English](README.md) | [Русский](README.RUS.md) | Español |\n\n-----------\n\n\n# Preguntas Frecuentes\n\n## ¿Qué es esto?\nEs una continuación directa del motor SMBX 1.3. Originalmente fue escrito en VB6 para Windows, y más tarde, fue portado / reescrito en C++ y se convirtió en un motor multiplataforma. Reproduce enteramente el antiguo motor SMBX 1.3 (aparte de su Editor), incluye varios de sus errores lógicos (se reparan los errores críticos que hacen que el juego se bloquee o se falle) y también agrega muchas actualizaciones y características nuevas.\n\n\n## ¿Por qué lo hiciste?\nTengo diversos fines para realizarlo:\n- Es un modelo de investigación bastante conveniente que deseo usar en el desarrollo del motor Moondust.\n- Proveer una copia plenamente compatible del motor antiguo para plataformas modernas, lo cual posibilita jugar niveles y episodios viejos con la misma sensación que si se hubieran jugado en el juego SMBX original basado en VB6.\n- Para que funcione sin la necesidad de usar Wine en plataformas que no sean Windows y que se encuentre disponible en plataformas que no sean x86 / x64.\n- Optimizarlo para utilizar menos recursos de hardware que el juego original con base en VB6.\n\n## Tienes Moondust Engine, ¿por qué has pasado bastante más de un mes para producir esta cosa?\nLo necesito para el desarrollo de Moondust Engine de manera directa, es muchísimo más simple de hackear y examinar que un ambiente VB6 antiguo e inconveniente.\n\n## ¿Cuál es el futuro de Moondust Engine ahora que existe TheXTech?\nContinuaré desarrollando el motor Moondust debido a que aún tengo que conseguir el segundo objetivo del proyecto.\nA partir de su fundación, el Proyecto Moondust poseía 2 fines:\n1) Salvar SMBX\n2) Dar un grupo de herramientas flexible para nuevos juegos de plataformas.\nLa apertura del código fuente de SMBX y la introducción de TheXTech ha resuelto el primer objetivo: SMBX ha sido salvado y ahora es un software multiplataforma gratuito de código abierto. Moondust Engine se usará para el segundo objetivo: dar un grupo de herramientas para nuevos juegos. A diferencia de TheXTech, Moondust Engine da un elevado nivel de flexibilidad que posibilita a cualquier persona edificar algo nuevo a partir de cero sin heredar un viejo juego base. No obstante, TheXTech se necesita para Moondust Engine como modelo de investigacion funcional para desarrollar el nuevo motor. Va a ser parecido a las transportaciónes GZDoom y Chocolate Doom del juego Doom: GZDoom es un motor potente y servible, la mejor alternativa para los modders; Chocolate Doom es una adaptacion precisa del juego original a  plataformas modernas para representar el juego original, incluidos los errores. El motor Moondust pretende ser como GZDoom, en lo que TheXTech es un semejante de Chocolate Doom para representar un juego original en plataformas modernas.\n\n## ¿Los niveles y episodios con LunaDLL Autocode funcionarán en Este juego?\nSí, lo harán. A partir de la versión 1.3.6 de TheXTech, hay una implementación incorporada del lenguaje de scripting LunaDLL Autocode, llamado LunaScript. Con este sistema, es posible ejecutar episodios LunaDLL en cualquier hardware, incluso con una arquitectura de procesador que no sea x86.\n\n## ¿Puede LunaLua funcionar en esto?\nNo, LunaLua no funcionará: este proyecto es binario-incompatible con LunaLua. Esto también significa que el contenido SMBX2 es incompatible. El sistema de secuencias de comandos planificado en el lenguaje lua no podrá garantizar la compatibilidad. Por lo tanto, después de la posible aparición de soporte para scripts lua, será aconsejable portar o crear desde cero específicamente para TheXTech.\n\n\n## ¿Por qué el código aquí es tan malo?\nEl autor original escribió la mayor parte del código en la carpeta \"* src *\" en VB6. Hice una conversión completa del código con un esfuerzo por lograr una reproducción precisa. Por lo tanto, gran parte del código es idéntico a lo que se escribió originalmente en VB6. La plataforma VB6 tenía muchos desafíos y limitaciones como:\n- Todas las variables son globales y accesibles desde todos los módulos y se forman de forma predeterminada sin ninguna inclusión o importación. La razón por la que existe \"globals.h\": tiene una lista completa de variables disponibles globalmente.\n- Soporte limitado e inconveniente para las clases, por lo tanto, el código tiende a abusar de una tonelada de variables y matrices globales (también una falta inicial de experiencia del autor original fue otro factor que llevó a este problema).\n- Todas las funciones de todos los módulos son globales y se pueden llamar directamente desde cada módulo. Excepto las llamadas marcadas como \"privadas\". Por lo tanto, tuve un trabajo adicional para proporcionar inclusiones en archivos donde se solicitan estas llamadas.\n- ¿Por qué tanto `if-elseif-elseif-elseif-elseif -....?` Sí, aquí probablemente sea correcto usar `switch ()` (en VB6 el operador `Select Case` analógico). Otro factor que muestra que el autor original tenía poca experiencia cuando codificó este proyecto.\n- ¿Por qué la lasaña `if () {if {} if {....}}`? Dos razones: 1) inexperiencia del autor original, 2) solución para no verificar todas las condiciones de expresión que pueden causar un bloqueo. En C ++ con múltiples condiciones divididas por el operador `&&`, nunca se ejecuta cuando una de ellas obtiene un resultado falso. En VB6, TODAS las condiciones en la obtención de expresiones siempre se ejecutarán. Esta diferencia provocó la siguiente situación: en VB6, una expresión `if A <5 And Array (A) = 1 Then` provocará un bloqueo cuando A sea más de 5. En C ++ el mismo` if (A <5 && Array [ Una expresión] == 1) `nunca fallará porque una segunda verificación nunca se ejecutará si una primera verificación dio un resultado falso.\n- ¿Por qué expresiones tan largas como `if (id == 1 || id == 3 || id == 4 || ... id == N)`? En lugar de hacer un montón de condiciones como esta, sería mejor usar clases con un polimorfismo y separar la lógica de cada objeto entre diferentes clases. También debería poder resolverse teniendo que usar punteros de función (que no son posibles en VB6 sin workaronds, pero son posibles en C ++). Pero, nuevamente, la inexperiencia del autor original combinada con un montón de límites de VB6 causaron estas construcciones.\n\n\n## ¿Como usar esto?\nAquí hay muchas formas de jugar con él:\n- hay algunos paquetes listos para usar, simplemente tómelos y juegue como lo hizo con SMBX.\n- [usuarios de macOS, omitan esto]: use la misma manera que el juego original: coloque el archivo ejecutable en la carpeta raíz del juego con un \"thextech.ini\" que contenga el siguiente texto:\n```\n[Main]\nforce-portable = true\n```\n, music.ini, sounds.ini y una carpeta adicional \"graphics/ui\". Una nota importante: todos los gráficos predeterminados deben convertirse a PNG, use la herramienta GIFs2PNG de PGE Project en su carpeta \"gráficos\" con un interruptor \"-d\". No use el interruptor \"-r\" para mantener los GIF originales junto con los PNG recién creados si planea continuar usando SMBX original escrito en VB6.\n- use esto para el modo de depuración: en su directorio de inicio, cree la carpeta \".PGE_Project/thextech\" (en macOS, la \"`~/TheXTech Games/Debug Assets/`\") donde debe colocar un set completo de recursos del juego y cosas de mundos, esta carpeta funcionará como la raíz del juego en el juego original. Este modo le permite ejecutar un archivo ejecutable desde cualquier ubicación de la carpeta en su computadora y usar la misma ubicación de recursos para todas las compilaciones (excepto las que están marcadas como portátiles por el archivo INI).\n\n\n## ¿Cómo agregar episodios personalizados para la versión de macOS?\nSi tiene una compilación empaquetada de TheXTech, todos los recursos predeterminados están dentro de su .app: \"Content/Resources/assets/\". Puedes modificar el contenido, ¡pero no es recomendable! En cambio, después de la primera ejecución de un juego, en su directorio de inicio aparecerá el siguiente directorio:\n''\n   ~/TheXTech Episodes\n''\nEn este directorio, encontrará una carpeta vacía de \"batalla\" y \"mundos\" para colocar sus cosas personalizadas. En la ruta \"`~/Library/Application Support/PGE Project/thextech` \", se almacenarán los registros, las configuraciones y las partidas guardadas.\nSi desea reemplazar los recursos predeterminados por los suyos, puede modificar el contenido del paquete de la aplicación o hacer una nueva compilación con los argumentos de CMake necesarios que se necesitan para empaquetar la raíz de los recursos personalizados y el icono en el nuevo paquete o crear la compilación sin recursos (si no das argumentos, resultará la compilación sin recursos). Por lo tanto, debes colocar el contenido completo de la raíz del juego en la carpeta \"`~/TheXTech Games/Debug Assets/` \", incluye recursos predeterminados (gráficos, música, sonidos, niveles de introducción y salida, niveles de batalla predeterminados y carpetas de mundos).\n\n\n## ¿Qué diferencia hay con esto en comparación con la versión original en VB6?\n- En primer lugar, está escrito en C ++, mientras que el original (como ya sabemos) está escrito en VB6.\n- No tiene editor. En cambio, tiene una integración profunda con Moondust Editor que permite usarlo con la misma funcionalidad que en el editor original (la funcionalidad de \"mano mágica\" se mantuvo para permitir la edición en tiempo real del nivel durante la prueba, es necesario usar la comunicación IPC con Moondust Editor para tener la capacidad de usarlo mejor).\n- Soporte completo de UTF-8 en rutas de nombre de archivo y datos de texto internos (el juego original tenía solamente soporte ANSI de 8 bits).\n- Para gráficos y control, usa una biblioteca SDL2, mientras que el juego original ha usado llamadas WinAPI y biblioteca GDI.\n- Utiliza PGE-FL que tiene un mejor soporte de formatos de archivo.\n- Un soporte para mapas del mundo WLDX permite líneas de crédito ilimitadas y música personalizada sin que sea necesario usar un music.ini para reemplazos de música.\n- Algunas características exclusivas de LVLX ahora funcionan: envolvimiento de sección vertical, warp bidireccional, mensaje personalizado de \"Estrella requerida\", evento de entrada de warp, capacidad para deshabilitar la impresión de estrellas en episodios HUB para puertas específicas, capacidad para deshabilitar la visualización entre escenas al pasar a otro nivel a través de un warp.\n- Soporte incorporado para extensos music.ini y sounds.ini de episodios y niveles para anular los recursos de musica y sonidos predeterminados.\n- Los mapas del mundo ahora admiten un directorio personalizado para almacenar recursos específicos como baldosas/escenas/caminos/niveles personalizados y ya no enviar spam a la carpeta raíz del episodio con recursos del mapa del mundo.\n- El formato de configuración por defecto es INI, el viejo formato config.dat ya no es compatible, principalmente por valores de codigo de teclas incompatibles (SDL_Scancode versus enumeración de VirtualKeys de Windows API).\n- El juego se guarda ahora usando el formato SAVX en lugar del clásico SAV. Sin embargo, si ya tiene un juego guardado antiguo, aún puede reanudar su juego usando el nuevo motor ahora (el próximo intento de guardado de juegos dará como resultado un archivo SAVX, el juego guardado antiguo en formato SAV se mantendrá intacto).\n- Soporte PNG incorporado para gráficos personalizados y predeterminados. Sin embargo, los GIF enmascarados todavía son compatibles por compatibilidad con versiones anteriores, sin realizar una conversión automática inesperada como lo hace SMBX-38A.\n- ¡Los puntos de control ahora tienen puntos múltiples! ¡Puedes usarlos en tus niveles varias veces sin límites!\n- Utiliza un algoritmo de descompresión diferida para acelerar la carga del juego y reducir el uso de memoria.\n- ¡Para música y SFX, la librería MixerX se usa para brindar soporte a una amplia cantidad de formatos de sonido y música!\n- No incorpora ningún gráfico: NO hay gráficos realmente codificados, ¡ahora todo está representado por gráficos externos!\n- Se han ampliado algunos límites internos.\n- Grabador GIF integrado con la tecla F11 (F10 en macOS, F11 está reservado por la interfaz de usuario del sistema para una acción de \"mostrar escritorio\")\n- Comienza más rápido: la carga del juego es casi instantánea (depende de una computadora y del rendimiento de su HDD/SSD).\n- Utiliza menos RAM (80 ... 150 MB en lugar de 600 ... 800 MB como usualmente), y está libre de pérdidas de memoria dadas por la interfaz MCI utilizada originalmente por SMBX en VB6.\n- no sobrecarga la CPU (la razón fue una mala manera de procesar bucles infinitos, también hice la corrección de la compilación de VB6 en mi rama \"smbx-experiment\")\n- Puede funcionar en una \"tostadora\" (una computadora débil) mientras que VB6-SMBX no funcionará.\n- es completamente multiplataforma y no depende de Windows, y ya no depende del procesador x86: también puede funcionar en procesadores ARM y MIPS (VB6-SMBX no funcionará en ARM en absoluto, con el emulador x86 funcionará 20 veces más lento de lo habitual).\n\n\n## ¿Cómo compilarlo?\nPuede leer una guía sobre cómo compilar este proyecto desde la fuente aquí: https://github.com/Wohlstand/TheXTech/wiki/Building-the-game\n\nPara compilarlo, necesita tener lo siguiente:\n- CMake\n- Ninja opcionalmente (para acelerar el proceso de construcción)\n- Compilador C/C ++ compatible (GCC, Clang, MinGW, MSVC 2017 y 2019, posiblemente también se construirá en 2015, pero no se ha probado)\n- Git (necesario para extraer submódulos y clonar la fuente de las líbrerias dependientes para construirlos en su lugar)\n- Mercurial (necesario para clonar un repositorio oficial de SDL2 para construirlo en su lugar aquí)\n- Opcionalmente: dependencias instaladas en todo el sistema: SDL2, libFreeImageLite (una implementación modificada de FreeImage), librería de sonido MixerX, colección de librerías AudioCodecs. Tenerlos instalados en el sistema aumenta la velocidad de compilación. Sin embargo, es posible compilar todas estas dependencias en su lugar aquí con un costo adicional de tiempo de compilación agregado.\n"
  },
  {
    "path": "README.RUS.md",
    "content": "<p align=\"center\">\n<a href=\"https://github.com/Wohlstand/TheXTech/releases\"><img src=\"https://raw.githubusercontent.com/Wohlstand/TheXTech/master/resources/icon/thextech_512.png\" alt=\"TheXTech\"></a>\n</p>\n\n# TheXTech\n\nSMBX-движок, переписанный на C++ с VisualBasic 6\n\n| <!-- -->      | <!-- -->        | <!-- -->      |\n|:-------------:|:---------------:|:-------------:|\n| [English](README.md) | Русский | [Español](README.ESP.md) |\n\n-----------\n\n\n# Часто задаваемые вопросы\n\n## Что это такое?\nЭто прямое продолжение старого движка SMBX 1.3. Изначально движок был написан на VB6 под Windows, а позже был портирован/переписан на C++: и с тех пор движок стал кросс-платформенным. Он полностью повторяет старый движок (кроме редактора), включая множество логических багов (часть багов была исправлена, первым делом те, которые ломают игру), а также содержит множество обновлений и новых возможностей.\n\n\n## Зачем ты это сделал?\nЗачем? У меня имеется несколько целей, почему я это сделал:\n- Это - очень удобная живая модель для исследований, которую я хочу использовать для разработки Moondust-движка.\n- Чтобы предоставить полностью совместимую со старым SMBX-движком игру для современных платформ, чтобы можно было играть старые уровни и эпизоды, сохранив первозданную атмосферу оригинальной игры SMBX, писанной на VB6.\n- Чтобы игра работала на не-Windows платформах без необходимости использовать Wine, а также запускать игру на других процессорах, отличных от x86 (например ARM).\n- Чтобы была возможность оптимизировать игру, используя меньше аппаратных ресурсов, в отличии от оригинальной игры, основанной на VB6.\n\n\n## У тебя есть Moondust-движок, зачем ты портатил больше месяца на то, чтобы создать эту вещь?\nМне оно нужно для разработки Moondust-движка напрямую. С ним мне намного проще исследовать и экспериментировать, чем через старую и неудобную среду VB6.\n\n\n## Какое будущее у движка Mooddust (ранее PGE) с тех пор как существует TheXTech?\nЯ продолжу разработку Moondust-движка, поскльку мне всё равно надо исполнить вторую цель Moondust Project.\nС момента основания у Moondust Project было две цели: 1) спасти SMBX; 2) предоставить гибкий набор для создания новых игр-платформеров. Открытие исходников SMBX и связанное с этим появление TheXTech, полностью исполнило первую цель: SMBX спасён, и теперь это свободное ПО с открытым исходным кодом. Moondust-движок останется для исполнения второй цели - предоставить набор для новых игр. В отличии от TheXTech, Moondust-движок даёт максимальную гибкость, которая позволяет каждому создать что-либо с нуля, без необходимости наследовать старую игровую базу. Однако, TheXTech нужен для Moondust-движка в качестве модели для исследований, чтобы разработать новый движок. В итоге, результат будет схож с портами игры Doom: GZDoom и Chocolate Doom. GZDoom - мощный и функциональный движок, лучший выбор для моддеров. Chocolate Doom - это высокоточный порт оригинальной игры на современные платформы с целью предоставить оригинальную игру, включая даже баги. Moondust-движок стремится быть похожим на GZDoom в то время, как TheXTech будет аналогом Chocolate Doom с целью повторить оригинальную игру на современных платформах.\n\n\n## Смогут ли уровни и эпизоды с LunaDLL Autocode работать в этой игре?\nДа, смогут. Начиная с версии TheXTech 1.3.6, имеется встроенная реализация скриптового языка LunaDLL Autocode, именуемая LunaScript. С этой системой можно запускать LunaDLL-эпизоды на любом оборудовании, даже с процессорной архитектурой, отличной от x86.\n\n\n## Сможет LunaLua работать в этой игре?\nНет, LunaLua не сможет работать: данный проект бинарно несовместим с LunaLua. Это ещё значит, что контент, предназначенный для SMBX2, также будет несовместим. Планируемая скриптовая система на языке lua не сможет гарантировать совместимость. Поэтому, после возможного появления поддержки lua-скриптов, будет целесообразно портировать, либо создавать с нуля конкретно для TheXTech.\n\n\n## Почему здесь такой плохой код?\nИзначально, большинство кода в папке \"src\" было написано на VB6 его изначальным автором. Я сделал полное преобразование кода с упором на максимально точное воспроизведение игры. То есть, большинство кода практически идентично тому, что было изначально написано на VB6. Платформа VB6 имела огромное количество всевозможных трудностей и ограничений:\n- Все переменные глобальны и доступны из каждого модуля и формы по умолчанию, без каких-либо включений или импортов. Это и есть причина, почему существует \"globals.h\": этот файл содержит в себе весь полный список глобально доступных переменных.\n- Ограниченная и неудобная поддержка классов, почему большая часть кода нагло злоупотребляла большим колличеством глобальных переменных и массивов (также свою лепту привнесло отсутствие опыта у изначального автора, которое стало дополнительным фактором существования всего этого безобразия).\n- Все функции во всех модулях глобальны, и могут вызываться из кажого модуля напрямую (за исключением вызовов, помеченных как \"приватные\"). Из-за чего мне пришлось проделать дополнительную работу по включению заголовков в файлы, где эти вызовы применялись.\n- Почему так много `if-elseif-elseif-elseif-elseif-....`? Да, здесь, скорее всего, было бы правильно использовать оператор `switch()` (аналог в VB6: `Select Case`). Ещё одна причина, которая объясняет, что у изначального автора на тот момент времени не хватало опыта, пока он работал над этим проектом.\n- Почему такие глубокие лазаньи из `if() { if {} if { .... } }`? На то есть две причины: 1) мало опыта у изначального автора, 2) костыль, предназначенный для того, чтобы не проверять все условия логического выражения в случаях, способных вызвать крэш. В C++, множество условий, разделённых с помощью оператора `&&`, никогда не будут проверены, если одно из них даёт отрицательный результат. В VB6 наборот, ВСЕ узлы выражения исполняются, не зависимо от их реузльтата. Эта разница даёт следующее: в VB6, выражение `if A < 5 And Array(A) = 1 Then` спровоцирует ошибку, если A будет больше пяти. В C++, аналогичное выражение `if(A < 5 && Array[A] == 1)` никогда не спровоцирует ошибку, потому что вторая часть выражения никогда не проверяется, если первая дала отрицательный результат.\n- Почему такие длинные выражения по типу `if(id == 1 || id == 3 || id == 4 || ... id == N)`? Вместо того, чтобы создавать кучи подобных выражений, было бы лучше использовать классы и полиморфизм с отдельной логикой для каждого объекта между разными классами. Это также должно решаться с помощью указателей на функции (которые невозможны в VB6 без костылей, однако легко возможны в C++). Однако снова, отсутствие опыта у изначального автора одновременно с огромной кучей ограничений VB6 спровоцировало появление подобных конструкций.\n\n\n\n## Как пользоваться игрой?\nИмеется несколько способов, как пользоваться этой игрой:\n- Имеются уже готовые к работе сборки, просто взять использовать также, как SMBX.\n- [Пропустить этот пункт пользователям macOS]: Смешать исполняемый файл игры с существующими игровыми ресурсами: положить исполняемый файл в кореневую папку ресурсов совместно с INI-файлом \"thextech.ini\", содержащим следующий текст:\n```ini\n[Main]\nforce-portable = true\n```\n, файлами \"music.ini\", \"sounds.ini\" и с дополнительной папкой \"graphics/ui\".\nВажно: вся штатная графика должна быть преобразована в PNG. Используйте утилиту GIFs2PNG из комплекта Moondust Project, применив на папке \"graphics\" с флагом \"-d\". Не используйте флаг \"-r\", чтобы сохранить исходные GIF-файлы совместно с новоиспечёнными PNG, если есть в планах продолжить использовать оригинальную игру, написанную на VB6.\n- Использовать это в отладочном режиме: в Вашем домашнем каталоге, создайте папку `.PGE_Project` (обязательно должно начинаться с точки), и затем внутри `.PGE_Project`, создать папку \"thextech\" (на macOS вместо этого нужно создать путь \"`~/TheXTech Games/Debug Assets/`\"). Это отладочный корень игры, в коротом необходимо разместить полный комплект ресурсов игры и данные эпизодов. Этот каталог будет действовать аналогично корневому каталогу оригинальной игры. Этот режим позволяет запускать исполняемый файл игры из абсолютно любого места на Вашем компьютере, и при этом использовать одно общее местоположение игровых ресурсов для всех сборок (за исключением тех, которые были помечены как портативные с помощью INI-файла).\n\n\n## Как добавить сторонние эпизоды в версии для macOS?\nЕсли у вас укомплектованная сборка TheXTech, то все встроенные ресурсы находятся внутри вашего .app: \"Content/Resources/assets/\". Можно изменить содержиеме, однако, этого не рекомендуется делать! Вместо этого, после первого запуска игры в вашем домашнем каталоге появятся следующая директория:\n\n```\n   ~/TheXTech Games/<имя игры>/\n```\nВнутри этой директории будут две пустые папки: \"battle\" и \"worlds\", в которые вы сможете положить ваши эпизоды. В подпапку \"settings\" будут сохранены настройки и сохранения игры.\nЛоги будут сохранены в папке \"`~/Library/Application Support/PGE Project/thextech`\".\nЕсли же вы хотите заменить встроенные ресурсы на собственные, можно изменить содержимое пакета приложения, или собрать новую сборку с указанием необходимых флагов CMake, с помощью которых вы сможете упаковать ваш собственный набор ресурсов и иконку для нового пакета, либо же сделать безресурсную сборку (по умолчанию, если не указать никаких аргументов, будет собираться безресурсная сборка по-умолчанию). Поэтому вам нужно разместить полный комплект игровых ресурсов в папку \"`~/TheXTech Games/Debug Assets/`\", включая все основные ресурсы (графику, музыку, звуки, вводный и завершающий уровни, уровни битв и папки миров по умолчанию).\n\n\n## В чём разница между этой и оригинальной сборкой игры, созданной на VB6?\n- Первым делом, эта игра написана на C++, когда оригинал (как мы уже знаем) был создан на VisualBasic6.\n- До выпуска 1.3.6, редактор полностью отсутствовал. Начиная с 1.3.6 имеет встроенный мобильный редактор, работающий в полноэкранном режиме. Им можно управлять не только клавиатурой и мышкой, но и геймпадов или сенсорным экраном. Также, игра имеет глубокую интеграцию с редактором Moondust, которая позволяет использовать игру с той же функциональностью, что и через оригинальный редактор (Была сохранена функциональность \"волшебной руки\", которая позволяет редактировать уровень в реальном времени во время тестирования).\n- Полная поддержка UTF-8 в именах файлов, путях и внутренних текстовых ресурсах (оригинальная игра поддерживала только 8битные кодировки ANSI).\n- Игра использует SDL2 для графики и управления в то время, как оригинальная игра использовала вызовы WinAPI и библиотеки GDI.\n- Игра использует библиотеку PGE-FL для улучшенной поддержки форматов файлов.\n- Имеется поддержка карт мира WLDX, которые позволяют сохранять неограниченное число строк титров, а также встроенная поддежка использования сторонней музыки без необходимости использовать music.ini.\n- В игре работают некоторые уникальные возможности уровней LVLX: вертикальное соединение; двунаправленные проходы; возможность указать собственное сообщение, чтобы рассказать о необходимом числе звёзд для взода в проход; событие входа в проход; возможность отключить отображение числа звёзд над определёнными дверями в корридорных мирах; возможность отключить входной экран при переходе между уровнями через проход.\n- Встроенная поддержка файлов \"music.ini\" и \"sounds.ini\" для замены музыки и звуков на отдельных уровнях, и даже в целых эпизодах.\n- Карты мира отныне поддерживают собственную директорию, чтобы размещать в ней любые специфичные ресурсы (ландшафт, декорации, пути, уровни), и при этом больше не засорять корневую папкпу эпизода ресурсами карты мира.\n- Формат настроек теперь INI, старый формат config.dat больше не поддерживается, первым делом из-за несовместимых форматов кодов клавиш (SDL_Scancode против VirtualKeys из Windows API).\n- Сохранения игры отныне используют формат SAVX вместо классического SAV. Однако, если у вас уже имеется старое сохранение, вы всё ещё можете продожить игру, используя новый движок (при следующем сохранеии игры, будет создан файл в новом SAVX-формате, старое сохранение игры останется нетронутым).\n- Встроенная поддкржика PNG для сторонней и встроенной графики. Масковые GIF по прежнему поддерживаются для обратной совместимости, однако, без автопреобразований, как это обычно делает SMBX-38A.\n- Контрольные точки отныне можно использовать по много раз! Вы сможете использовать их на ваших уровнях по несколько раз без каких либо ограничений!\n- Игра использует алгоритм ленивой распаковки, чтобы ускорить время загрузки игры и уменьшить использование памяти.\n- Был полностью переработан алгоритм поиска объектов, чего позволило полностью решить проблему сильного замедления на слабых машинах из-за горизонтального движения слоёв (См. подробное описание проблемы тут: [Проблема Доктора Пеппера](https://wohlsoft.ru/pgewiki/DrPepper_Problem/ru))\n- Игра использует библиотеку MixerX для звука и музыки, которая поддерживает огрномное число различных аудиоформатов!\n- Игра не внедряет в сборку какую-либо графику: отныне в игре больше НИКАКИХ истинно \"внедрённых\" ресурсов, теперь вся графика представлена в виде внешних ресурсов!\n- Расширены некоторые встроенные ограничения.\n- Встроення запись GIF с помощью клавиши F11 (на macOS используется клавиша F10, F11 используется системой, чтобы показать/скрыть рабочий сто).\n- Игра запускается быстрее: игра загружается почти мгновенно (зависит от компьютера и производительности его диска).\n- Игра использует меньше оперативной памяти (вместо 600...800 мегабайт, игра теперь в среднем использует 80...150 мегабайт), а также, она теперь чиста от утечек в памяти, вызванных MCI-интерфейсом, который использовался в оригинальной игре.\n- Игра больше не перегружает процессор до 100% (проблема была из-за неправильного способа обработки вечных циклов, я также исправил этот деффект и в VB6-сборке в ветке \"smbx-experiments\").\n- Игра способна работать даже на \"тостере\" (старом/слабом компьютере) в то время как VB6-SMBX не можеты.\n- Игра полностью кросс-платформенна, и больше не зависит от Windows, а также больше не зависит от процессоров x86: игра сможет работать на ARM и MIPS-процессорах тоже (VB6-SMBX зависит от Windows и x86).\n\n\n## Как собрать игру?\nВы можете почитать подробно инструкцию сборки этого проекта из исходников тут: https://github.com/Wohlstand/TheXTech/wiki/Building-the-game\n\nЧтобы собрать игру, нужно иметь несколько вещей:\n- CMake\n- Ninja, не обязательно (чтобы ускорить процесс сборки)\n- Совместимый компилятор C/C++ (GCC, Clang, MinGW, MSVC 2017 и 2019, вожможно будет собираться и в MSVC 2015, но не проверено)\n- Git (нужен для загрузки подмодулей и клонирования исходников зависисимых библиотек для их сборки на месте)\n- Mercurial (необходим для загрузки исходников с официального репозитория SDL2, чтобы собрать его на месте)\n- Не обязательно: зависимости, установленные в систему: SDL2, libFreeImageLite (модифицированная реализация FreeImage), звуковая библиотека MixerX, коллекция библиотек AudioCodecs. Если все эти зависимости установлены в системе, процесс сборки будет идти значительно быстрее. Однако, возможно собирать все зависимости прямо на месте, но при этом сборка будет идти дольше.\n\n\n## Локализация\nНекоторые части TheXTech можно локазиловать (напримет, загрузчик на Android), Вы можете помочь перевести их, используя платформу WebLate: https://hosted.weblate.org/projects/thextech/\n\n\n## Используемые инструменты\n* GCC, MinGW-w64 и Clang - основные компиляторы, используемые во время разработки игры. Редко MSVC, который используется исключительно для Windows-сборок под процессоры ARM64.\n* [Qt Creator](https://www.qt.io/product/development-tools) - среда разработки из комплекта Qt, в основном используемя при разработке.\n* [JetBrans CLion](https://www.jetbrains.com/ru-ru/clion/) - среда разработки для C/C++ на базе Intelliji Idea, бесплатна по [лицензии для СПО](https://www.jetbrains.com/ru-ru/community/opensource/).\n* [PVS-Studio](https://pvs-studio.com/ru/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - статический анализатор кода для C, C++, C# и Java. Используется для периодических проверок кода на возможные ошибки. Бесплатен по [лицензии для СПО](https://pvs-studio.com/ru/order/open-source-license/).\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\r\n<a href=\"https://github.com/Wohlstand/TheXTech/releases\"><img src=\"https://raw.githubusercontent.com/Wohlstand/TheXTech/master/resources/icon/thextech_512.png\" alt=\"TheXTech\"></a>\r\n</p>\r\n\r\n# TheXTech\r\n\r\nSMBX engine, rewritten into C++ from VisualBasic 6.\r\n\r\n| <!-- -->      | <!-- -->        | <!-- -->      |\r\n|:-------------:|:---------------:|:-------------:|\r\n| English | [Русский](README.RUS.md) | [Español](README.ESP.md) |\r\n\r\n-----------\r\n\r\n\r\n# Frequently Asked Questions\r\n\r\n## What is this?\r\nThis is a direct continuation of the SMBX 1.3 engine. Originally it was written in VB6 for Windows, and later, it got ported/rewritten into C++ and became a cross-platform engine. It completely reproduces the old SMBX 1.3 engine (aside from its Editor), includes many of its logical bugs (critical bugs that lead the game to crash or freeze got fixed), and also adds a lot of new updates and features.\r\n\r\n\r\n## Why did you make it?\r\nI have several purposes for making it:\r\n- It's a very convenient research model I want to use in developent of the Moondust Engine.\r\n- To provide a fully-compatible replica of the old engine for modern platforms, allowing to play old levels and episodes with the same feeling as if they were played on the original VB6-based SMBX game.\r\n- To make it work without the necessity to use Wine on non-Windows platforms and making it available on non-x86/x64 platforms.\r\n- Optimizing it to use fewer hardware resources than the original VB6-based game.\r\n\r\n\r\n## You have Moondust Engine, why you have spent over a month to craft this thing?\r\nI need it for Moondust Engine development directly, it's much easier to hack and inspect than an old, inconvenient VB6 environment.\r\n\r\n\r\n## What's the future of Moondust Engine now that TheXTech exists?\r\nI'll continue developing the Moondust Engine as I have yet to reach the second goal of the project.\r\nSince it's foundation, the Moondust Project had two goals: 1) save SMBX; 2) give a flexible toolkit for new platform games. The opening of SMBX's source-code and introducing TheXTech has solved the first goal: SMBX has been saved and now it's free/opensource cross-platform software. Moondust Engine will be used for the second goal - giving a toolkit for new games. Unlike TheXTech, Moondust Engine gives a high degree of flexibility that allows anyone to build something new from scratch without inheriting an old game base. However, TheXTech is needed for Moondust Engine as a working research model to develop the new engine. It will be similar to GZDoom and Chocolate Doom ports of the Doom game: GZDoom is a powerful and functional engine, the best choice for modders; Chocolate Doom is an accurate port of the original game to a modern platform with the purpose to represent the original game including even bugs. The Moondust Engine intends to be like GZDoom while TheXTech is an analog of Chocolate Doom to represent an original game on modern platforms.\r\n\r\n\r\n## Can levels and episodes with LunaDLL Autocode work on this?\r\nYes, can. Since the TheXTech version 1.3.6, there is a built-in implementation of the LunaDLL Autocode language, called LunaScript. This system allows the running of LunaDLL-episodes on any hardware including non-x86 processor architectures.\r\n\r\n\r\n## Can LunaLua work on this?\r\nNo, LunaLua won't work: this project is binary-incompatible with LunaLua. This also means that SMBX2 content is incompatible. The planned lua-based scripting system won't guarantee compatibility. Therefore, after the possible appearance of the Lua scripts support, it will be reasonable to port, or create new from the ground up and target to TheXTech exclusively.\r\n\r\n\r\n## Why is the code here so bad?\r\nThe original author wrote most of the code in the \"*src*\" folder in VB6. I did a whole conversion of the code with an effort to accurate reproduction. So, a lot of the code is identical to what was written in VB6 originally. The VB6 platform had a lot of challenges and limitations such as:\r\n- All variables are global and accessible from every module and form by default without any includes or imports. The reason why \"globals.h\" exists: it has a full list of globally available variables.\r\n- Limited and inconvenient support for classes, therefore the code tends to abuse a ton of global variables and arrays (also an initial lack of experience of the original author was an another factor that lead to this mess).\r\n- All functions in all modules are global and can be called from each module directly. Except calls marked as \"private\". Therefore I had an additional work to provide inclusions into files where these calls are requested.\r\n- Why so much `if-elseif-elseif-elseif-elseif-....?` Yes, here probably will be correct to use `switch()` (in VB6 the `Select Case` analogue) operator. Another factor that shows the original author had a low amount of experience when he coded this project.\r\n- Why the `if() { if {} if { .... } }` lasagna? Two reasons: 1) inexperience of original author, 2) workaround to not check all conditions of expression which may cause a crash. In C++ with multiple conditions splitted by `&&` operator, never executing when one of them gets a false result. In VB6, ALL conditions in expression getting be always executed. This difference caused the next situation: in VB6, an expression `if A < 5 And Array(A) = 1 Then` will cause a crash when A is more than 5. In C++ the same `if(A < 5 && Array[A] == 1)` expression will never crash because a second check gets be never executed if a first check gave a false result.\r\n- Why so long expressions like `if(id == 1 || id == 3 || id == 4 || ... id == N)`? Rather making a ton of conditions like this, it's would be better to use classes with a polymorphism and separate the logic of every object between different classes. Also should be solvable with having to use of function pointers (which aren't possible in VB6 without workaronds, but possible in C++). But, again, the original author's inexperience combined with a bunch of VB6 limits caused these constructions.\r\n\r\n\r\n## How to use this?\r\nHere are many ways to play games with it:\r\n- There are some ready-to-use packages, just download them, unpack and run them as you did it with SMBX, absolutely same.\r\n- [macOS users, skip this]: Mix the game with existing assets directory: put an executable file into the game root folder with an \"thextech.ini\" that contains next text:\r\n```ini\r\n[Main]\r\nforce-portable = true\r\n```\r\n, music.ini, sounds.ini and additional \"graphics/ui\" folder. An important note: all default graphics must be converted into PNG, use GIFs2PNG tool from Moondust Project over your \"graphics\" folder with a \"-d\" switch. Don't use \"-r\" switch to keep original GIFs together with new-made PNGs if you plan to continue the use of original VB6-written SMBX.\r\n- Use it for debug mode: in your home directory, create the \".PGE_Project/thextech\" folder (on macOS the \"`~/TheXTech Games/Debug Assets/`\") where you should put a full set of game resources and worlds stuff, this folder will work like a game root in the original game. This mode allows you to run an executable file from any folder location of your computer and use the same location of resources for all builds (except these are marked as portable by an INI file).\r\n\r\n\r\n## How to add custom episodes for the macOS version?\r\nIf you have a bundled build of TheXTech, all default resources are inside your .app: \"Content/Resources/assets/\". You can modify the content, but it's not recommended! Instead, after the first run of a game, in your home directory will appear the next directory:\r\n```\r\n   ~/TheXTech Games/<game name>/\r\n```\r\nIn this directory, you will find an empty \"battle\" and \"worlds\" folder to put your custom stuff. At the \"settings\" sub-directory the game settings and game saves will be stored.\r\nAt the \"`~/Library/Application Support/PGE Project/thextech/logs/`\" path logs will be stored.\r\nIf you want to replace default assets with your own, you can modify the content of the app bundle or compile a new build with giving of the necessary CMake arguments which needed to pack your custom assets root and icon into the new bundle or make the assets-less build (if you give no arguments, the assets-less build will result). Therefore, you need to put the full content of the game root into the \"`~/TheXTech Games/Debug Assets/`\" folder, include default assets (graphics, music, sounds, intro and outro levels, default battle and worlds folders).\r\n\r\n\r\n## What is different with this thing in comparison to the original VB6 build?\r\n- First off, it's written in C++ while original (as we already know) is written in VB6.\r\n- Before the version 1.3.6, it had no Editor at all. Since the version 1.3.6, it has an embedded mobile editor that works in a full screen. It can be controlled by keyboard and mouse as well, as by gamepad or by touch screen. In addition, it has a deep integration with Moondust Editor that allows to use game with the same functionality as was possible in the original editor (the \"magic hand\" functionality was kept to allow real-time editing of a level while testing).\r\n- Full support of UTF-8 in filename paths and internal text data (original game had the only 8bit ANSI support).\r\n- For graphics and controlling, it uses an SDL2 library while original game have used WinAPI calls and GDI library.\r\n- It uses PGE-FL library that has better file formats support.\r\n- A support for WLDX world maps are allowing unlimited credits lines and custom music without it being necessary to use a music.ini for music replacements.\r\n- Some LVLX exclusive features now working: vertical section wrap, two-way warps, custom \"star needed\" message, warp enter event, ability to disable stars printing in HUB episodes for specific doors, ability to disable interscene showing when going to another level through a warp.\r\n- Built-in support for episode and level wide music.ini and sounds.ini to override default music and sounds assets.\r\n- World maps now supports a custom directory to store any specific resources like custom tiles/scenes/paths/levels and not spam the episode root folder with world map resources anymore.\r\n- Default config format is INI, old config.dat format is no longer supported, mainly because of incompatible key code values (SDL_Scancode versus VirtualKeys enum of Windows API).\r\n- Game saves now using the SAVX format instead of a classic SAV. However, if you already have an old gamesave, you still can resume your game by using a new engine now (next gamesave attempt will result a SAVX file, old gamesave in SAV format will be kept untouched).\r\n- Built-in PNG support for custom and default graphics. Masked GIFs are still supported for backward compatibility, however, without making an unexpected auto-conversion like SMBX-38A does.\r\n- Checkpoints now have multi-points! You can use them in your levels multiple times without limits!\r\n- It does use of lazy-decompress algorithm to speed-up the loading of a game and reduce the memory usage.\r\n- It has the completely reworked objects search algorithm which resolves the lag problem on slow systems after horizontal move of layers (See description of the [DrPepper Problem](https://wohlsoft.ru/pgewiki/DrPepper_Problem)).\r\n- For music and SFX, the MixerX library is used to give a support for a wide amount of sound and music formats!\r\n- It doesn't embeds any graphics: there are NO trurely hardcoded graphics, everything is now represented by external graphics!\r\n- Some internal limits have been expanded.\r\n- Built-in GIF recorder by F11 key (F10 on macOS, F11 is reserved by system UI for a \"show desktop\" action)\r\n- It starts faster: the loading of the game is almost instant (depend on a computer and it's HDD/SSD performance).\r\n- It uses less RAM (80...150 MB instead of 600...800 MB like usually), and it's free from memory leaks given by the MCI interface used by VB6 SMBX originally.\r\n- it doesn't overload CPU (the reason was a bad way to process infinite loops, I did the fix of VB6 build too at my \"smbx-experiments\" branch)\r\n- it able to work on \"toaster\" (a weak computer) while VB6-SMBX won't work.\r\n- it's fully cross-platform and doesn't depend on Windows, and it no longer depends on x86 processor: it can work on ARM and MIPS processors too (VB6-SMBX won't work on ARM at all, with x86 emulator it will 20x times slower than usualy).\r\n\r\n\r\n## How to build it?\r\nYou can read a guide how to build this project from source here: https://github.com/Wohlstand/TheXTech/wiki/Building-the-game\r\n\r\nTo build it, you need to have the following things:\r\n- CMake\r\n- Ninja optionally (to speed-up the build process)\r\n- Compatible C/C++ compiler (GCC, Clang, MinGW, MSVC 2017 and 2019, possibly will build also on 2015, but wasn't tested)\r\n- Git (required to pull submodules and clone source of dependent libraries to build them in place)\r\n- Mercurial (required to clone an official SDL2 repository to build it in place here)\r\n- Optionally: system-wide installed dependencies: SDL2, libFreeImageLite (a modded implementation of the FreeImage), MixerX sound library, AudioCodecs collection of libraries. Having them be installed in a system gives a major build speed up. However, it's possible to build all these dependencies in place here with a cost of extra build time being added.\r\n\r\n## Localization\r\nSome parts of TheXTech (such as Android launcher) can be localized into many languages, you may help to translate them using WebLate platform: https://hosted.weblate.org/projects/thextech/\r\n\r\n\r\n## Used software\r\n* GCC, MinGW-w64, and Clang - main compilers used during the development of the game. The MSVC is used sometimes which is used for Windows builds for ARM64 processors.\r\n* [Qt Creator](https://www.qt.io/product/development-tools) - IDE from the Qt toolkit, mainly used during the development.\r\n* [JetBrans CLion](https://www.jetbrains.com/ru-ru/clion/) - Intelliji Idea based C/C++ IDE, free for the [Open Source development](https://www.jetbrains.com/ru-ru/community/opensource/).\r\n* [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. Is used for periodical checking of the code for possible bugs. It's free for the [Open Source development](https://pvs-studio.com/en/order/open-source-license/).\r\n"
  },
  {
    "path": "android-project/.gitignore",
    "content": "# Folders\n/.idea/*\n/.gradle/*\n/build/*\n/local.properties\n/SDL-default/*\n/thextech/jni/SDL/*\n/thextech/build/*\n/thextech/release/*\n/thextech/apk/release/*\n/thextech/fdroid/release/*\n/thextech/debug/*\n/thextech/.cxx/*\n.externalNativeBuild\n/thextech/src/main/assets/languages/*\n\n# Files\n*.iml\n*.apk\n\n# Key files\n*.jks\n*.keystore\n"
  },
  {
    "path": "android-project/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        mavenCentral()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:8.13.2'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\ntasks.register('clean', Delete) {\n    delete rootProject.layout.buildDirectory\n}\n"
  },
  {
    "path": "android-project/build_init.sh",
    "content": "#!/bin/bash\n\n# Remove old version if presented\nif [[ -d SDL-default ]]; then\n    rm -Rf SDL-default\nfi\n\n# Unpack recent SDL2 tarball\nif [[ ! -d SDL-default ]]; then\n    wget https://github.com/WohlSoft/PGE-Project/raw/master/_Libs/_sources/SDL-default.tar.gz\n    tar -xf SDL-default.tar.gz\n    rm SDL-default.tar.gz\nfi\n\nif [[ ! -d thextech/jni/SDL ]]; then\n    mkdir thextech/jni/SDL\nfi\n\n# Make a link for SDL2 sources\nif [[ ! -e thextech/jni/SDL/src ]]; then\n    ln -s ../../../SDL-default/src thextech/jni/SDL/src\nfi\n\n# Make a link for SDL2 includes\nif [[ ! -e thextech/jni/SDL/include ]]; then\n    ln -s ../../../SDL-default/src thextech/jni/SDL/include\nfi\n\n# Remove old SDL2 Java files\nrm -f thextech/src/main/java/org/libsdl/app/*.java\n\n# Copy SDL2 Java files\ncp SDL-default/android-project/app/src/main/java/org/libsdl/app/*.java thextech/src/main/java/org/libsdl/app\n\n# Copy Android NDK makefile\ncp SDL-default/Android.mk thextech/jni/SDL\n\necho \"Done!\"\n"
  },
  {
    "path": "android-project/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionSha256Sum=ed1a8d686605fd7c23bdf62c7fc7add1c5b23b2bbc3721e661934ef4a4911d7c\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.3-all.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "android-project/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\nandroid.enableJetifier=false\nandroid.nonFinalResIds=true\nandroid.nonTransitiveRClass=true\nandroid.useAndroidX=true\norg.gradle.jvmargs=-Xmx1536m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "android-project/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "android-project/gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n@rem SPDX-License-Identifier: Apache-2.0\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "android-project/settings.gradle",
    "content": "include ':thextech'\n"
  },
  {
    "path": "android-project/thextech/build.gradle",
    "content": "def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');\ndef buildAsApplication = !buildAsLibrary\nif (buildAsApplication) {\n    apply plugin: 'com.android.application'\n} else {\n    apply plugin: 'com.android.library'\n}\n\ndef release_store_password = System.env.RELEASE_STORE_PASSWORD\ndef release_key_password = System.env.RELEASE_KEY_PASSWORD\ndef release_key_alias = System.env.RELEASE_KEY_ALIAS\ndef pin_alias = System.env.PIN_ALIAS\ndef db_pass_alias = System.env.DB_PASS_ALIAS\n\ndef getGitHash() {\n    def gitExec = providers.exec {\n        commandLine('git', 'rev-parse', '--short', 'HEAD')\n    }.standardOutput.asText.get()\n    return gitExec.trim()\n}\n\ndef getGitBranch() {\n    def gitExec = providers.exec {\n        commandLine('git', 'rev-parse', '--abbrev-ref', 'HEAD')\n    }.standardOutput.asText.get()\n    return gitExec.trim()\n}\n\ndef parseVersion(verNum) {\n    def cmakeVersion = file('../../version.cmake')\n\n    if (cmakeVersion.canRead()) {\n        def input = new FileInputStream(cmakeVersion)\n        def rawVersion = input.getText()\n\n        def verMajorEx      = ~/(?m)THEXTECH_VERSION_1 (\\d+)/\n        def verMajorM       = verMajorEx.matcher(rawVersion)\n        verNum[0]           = verMajorM[0][1].toInteger()\n\n        def verMinorEx      = ~/(?m)THEXTECH_VERSION_2 (\\d+)/\n        def verMinorM       = verMinorEx.matcher(rawVersion)\n        verNum[1]           = verMinorM[0][1].toInteger()\n\n        def verRevisionEx   = ~/(?m)THEXTECH_VERSION_3 (\\d+)/\n        def verRevisionM    = verRevisionEx.matcher(rawVersion)\n        verNum[2]           = verRevisionM[0][1].toInteger()\n\n        def verPatchEx      = ~/(?m)THEXTECH_VERSION_4 (\\d+)/\n        def verPatchM       = verPatchEx.matcher(rawVersion)\n        verNum[3]           = verPatchM[0][1].toInteger()\n\n        def verRelEx        = ~/(?m)THEXTECH_VERSION_REL \"(-?\\w*)\"/\n        def verRelM         = verRelEx.matcher(rawVersion)\n        verNum[5]           = verRelM[0][1]\n\n        return verNum\n    } else {\n        throw new GradleException(\"Can't read ../../version.cmake file\")\n    }\n}\n\ndef cmakeToVersionCode(verNum) {\n    def ret = \"\"\n\n    ret += verNum[0].toString()\n    ret += \".\" + verNum[1].toString()\n    if (verNum[2] > 0 || verNum[3] > 0) {\n        ret += \".\" + verNum[2].toString()\n        if (verNum[3] > 0) {\n            ret += \".\" + verNum[3].toString()\n        }\n    }\n\n    if (verNum[5] != \"\") {\n        ret += verNum[5]\n    }\n\n    ret += \" #\" + getGitHash()\n\n    return ret\n}\n\nstatic def cmakeToClearVersionCode(verNum) {\n    def ret = \"\"\n\n    ret += verNum[0].toString()\n    ret += \".\" + verNum[1].toString()\n    if (verNum[2] > 0 || verNum[3] > 0) {\n        ret += \".\" + verNum[2].toString()\n        if (verNum[3] > 0) {\n            ret += \".\" + verNum[3].toString()\n        }\n    }\n\n    if (verNum[5] != \"\") {\n        ret += verNum[5]\n    }\n\n    return ret\n}\n\ndef verNum = [\n    0: 0, // major\n    1: 0, // minor\n    2: 0, // revision\n    3: 0, // patch\n    4: 0, // CI build number\n    5: \"\" // release type\n]\n\nparseVersion(verNum)\ndef xTechBranchOrig = getGitBranch()\ndef xTechBranch = xTechBranchOrig.replace('.', '').replace('-', '')\ndef xTechVersionString = cmakeToVersionCode(verNum)\ndef xTechVersionStringClear = cmakeToClearVersionCode(verNum)\ndef xTechVersionType = verNum[5]\ndef xTechVersionNumber = (verNum[0] * 1000000) + (verNum[1] * 10000) + (verNum[2] * 100) + verNum[3]\n\nprintln \"Project version: \" + xTechVersionString + \" (Clear version: \" + xTechVersionStringClear + \", version code: \" + xTechVersionNumber.toString()\n\nandroid {\n    compileSdkVersion 36\n\n    // Note: The NDK 23 is SIGNIFICANTLY important, because of the support for non-NEON and older Android versions\n    //       The NDK 24 removes the support for Android 4.1, 4.2, and 4.3.\n    //       See details: https://github.com/android/ndk/wiki/Changelog-r24\n    ndkVersion = \"23.2.8568313\"\n\n    buildFeatures {\n        buildConfig = true\n    }\n\n    dependenciesInfo {\n        // Disables dependency metadata when building APKs.\n        includeInApk = false\n        // Disables dependency metadata when building Android App Bundles.\n        includeInBundle = false\n    }\n\n    defaultConfig {\n        if (buildAsApplication) {\n            applicationId \"ru.wohlsoft.thextech\"\n        }\n\n        minSdkVersion 16\n        targetSdkVersion 36\n\n        versionName xTechVersionString\n        versionCode xTechVersionNumber\n\n        externalNativeBuild {\n            cmake {\n                abiFilters \"armeabi-v7a\", \"arm64-v8a\", \"x86\", \"x86_64\"\n                arguments \"-DANDROID_PLATFORM=16\", \"-DANDROID_STL=c++_static\"\n            }\n        }\n        ndk{\n            abiFilters \"armeabi-v7a\", \"arm64-v8a\", \"x86\", \"x86_64\"\n        }\n    }\n\n    signingConfigs {\n        releaseci {\n            storeFile file(\"../release-key.jks\")\n            storePassword = release_store_password\n            keyAlias = release_key_alias\n            keyPassword = release_key_password\n        }\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n\n            if(xTechVersionType == \"-dev\") {\n                applicationIdSuffix = '.dev.' + xTechBranch\n                android.sourceSets.release.res.srcDirs += project.projectDir.toString() + '/icon/devel'\n                manifestPlaceholders.dstAppName = \"TheXTech DEV [\" + xTechBranchOrig + \"]\"\n            } else {\n                android.sourceSets.release.res.srcDirs += project.projectDir.toString() + '/icon/main'\n                manifestPlaceholders.dstAppName = \"@string/app_name\"\n            }\n        }\n        releaseci {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            buildConfigField \"String\", \"PIN_ALIAS\", \"\\\"$pin_alias\\\"\"\n            buildConfigField \"String\", \"DB_PASS_ALIAS\", \"\\\"$db_pass_alias\\\"\"\n            signingConfig = signingConfigs.releaseci\n\n            if(xTechVersionType == \"-dev\") {\n                applicationIdSuffix = '.dev.' + xTechBranch\n                android.sourceSets.releaseci.res.srcDirs += project.projectDir.toString() + '/icon/devel'\n                manifestPlaceholders.dstAppName = \"TheXTech DEV [\" + xTechBranchOrig + \"]\"\n            } else {\n                android.sourceSets.releaseci.res.srcDirs += project.projectDir.toString() + '/icon/main'\n                manifestPlaceholders.dstAppName = \"@string/app_name\"\n            }\n        }\n        debug {\n            applicationIdSuffix '.debug'\n            versionNameSuffix '-DEVDEBUG'\n            manifestPlaceholders.dstAppName = \"TheXTech [DEBUG]\"\n            debuggable true\n            android.sourceSets.debug.res.srcDirs += project.projectDir.toString() + '/icon/debug'\n        }\n    }\n\n    flavorDimensions = [\"dist\"]\n    productFlavors {\n        apk {\n            dimension \"dist\"\n            isDefault = true\n        }\n        fdroid {\n            dimension \"dist\"\n            externalNativeBuild {\n                cmake {\n                    arguments += [\"-DTHEXTECH_NO_BUILD_DATE=ON\", \"-DFDROID_BUILD=TRUE\", \"-DOVERRIDE_GIT_BRANCH=\\\"<fdroid>\\\"\", \"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON\"]\n                }\n            }\n\n            versionName = xTechVersionStringClear\n\n            if(project.hasProperty('applicationIdSuffix')) {\n                applicationIdSuffix += '.fdroid'\n            } else {\n                applicationIdSuffix = '.fdroid'\n            }\n        }\n    }\n\n    println \"Resource paths per config:\"\n    println \"Main: \" + android.sourceSets.main.res.srcDirs\n    println \"Debug: \" + android.sourceSets.debug.res.srcDirs\n    println \"Release: \" + android.sourceSets.release.res.srcDirs\n    println \"Release-CI: \" + android.sourceSets.releaseci.res.srcDirs\n\n    lint {\n        abortOnError = false\n    }\n    namespace = 'ru.wohlsoft.thextech'\n\n    if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {\n        sourceSets {\n            main {\n                jniLibs.srcDirs += \"jniLibs\"\n            }\n            debug {\n                jniLibs.srcDirs += \"jniLibs/debug\"\n            }\n            release {\n                jniLibs.srcDirs += [\"jniLibs/release\", \"jniLibs/minsizerel\"]\n            }\n            releaseci {\n                jniLibs.srcDirs += [\"jniLibs/release\", \"jniLibs/minsizerel\"]\n            }\n        }\n        externalNativeBuild {\n            cmake {\n                path \"../../CMakeLists.txt\"\n            }\n        }\n    }\n\n\n    if (buildAsLibrary) {\n        libraryVariants.all { variant ->\n            variant.outputs.each { output ->\n                def outputFile = output.outputFile\n                if (outputFile != null && outputFile.name.endsWith(\".aar\")) {\n                    def fileName = \"ru.wohlsoft.thextech.aar\";\n                    output.outputFile = new File(outputFile.parent, fileName);\n                }\n            }\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'jniLibs')\n    //noinspection GradleDependency: the version 1.6.1 is last that support minSDK 16, newer version will require MinSDK 21\n    implementation 'androidx.appcompat:appcompat:1.6.1'\n    //noinspection GradleDependency: the version 2.1.4 is last that support minSDK 16, newer version will require MinSDK 21\n    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'\n    def lifecycle_version = \"2.6.2\"\n    //noinspection GradleDependency: the version 2.6.2 is last that support minSDK 16, newer version will require MinSDK 19\n    implementation \"androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version\"\n    //noinspection GradleDependency\n    implementation \"androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version\"\n    //noinspection GradleDependency: the version 1.11.0 is last that support minSDK 16, newer version will require MinSDK 19\n    implementation 'com.google.android.material:material:1.11.0'\n    implementation 'androidx.preference:preference:1.2.1'\n//    constraints {\n//        // Workaround to fix the build: https://dev.to/retyui/fix-react-native-android-builds-duplicate-class-kotlincollections-found-in-modules-jetified-kotlin-stdlib-180-2ca7\n//        implementation(\"org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0\") {\n//            because(\"kotlin-stdlib-jdk7 is now a part of kotlin-stdlib\")\n//        }\n//        implementation(\"org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0\") {\n//            because(\"kotlin-stdlib-jdk8 is now a part of kotlin-stdlib\")\n//        }\n//    }\n}\n"
  },
  {
    "path": "android-project/thextech/icon/debug/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "android-project/thextech/icon/debug/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "android-project/thextech/icon/debug/values/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#C1A4A7</color>\n</resources>"
  },
  {
    "path": "android-project/thextech/icon/devel/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "android-project/thextech/icon/devel/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "android-project/thextech/icon/devel/values/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#BF8054</color>\n</resources>"
  },
  {
    "path": "android-project/thextech/icon/main/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "android-project/thextech/icon/main/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "android-project/thextech/icon/main/values/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#696F91</color>\n</resources>"
  },
  {
    "path": "android-project/thextech/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in [sdk]/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "android-project/thextech/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:installLocation=\"auto\">\n\n    <!-- OpenGL ES 2.0 -->\n    <uses-feature android:glEsVersion=\"0x00020000\" />\n\n    <!-- Game controller support -->\n    <uses-feature\n        android:name=\"android.hardware.bluetooth\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.gamepad\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.usb.host\"\n        android:required=\"false\" />\n\n    <!-- External mouse input events -->\n    <uses-feature\n        android:name=\"android.hardware.type.pc\"\n        android:required=\"false\" />\n\n    <!-- Touchscreen support -->\n    <uses-feature\n        android:name=\"android.hardware.touchscreen\"\n        android:required=\"false\" />\n\n    <!-- Android TV support -->\n    <uses-feature android:name=\"android.software.leanback\"\n        android:required=\"false\" />\n\n\n    <!-- Allow writing to external storage -->\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <!-- Allow reading from the external storage -->\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <!-- This game needs to have the resource directory at user's storage to allow easier customization and episodes placement -->\n    <uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\" tools:ignore=\"ScopedStorage\" />\n    <!-- Allow access to the vibrator -->\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <!-- Allow access to Bluetooth devices -->\n    <uses-permission android:name=\"android.permission.BLUETOOTH\" />\n\n\n    <application\n        android:allowBackup=\"true\"\n        android:fullBackupContent=\"false\"\n        android:hardwareAccelerated=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:banner=\"@mipmap/ic_banner\"\n        android:label=\"${dstAppName}\"\n        android:launchMode=\"singleInstance\"\n        android:requestLegacyExternalStorage=\"true\"\n        android:theme=\"@android:style/Theme.NoTitleBar.Fullscreen\"\n        android:isGame=\"true\"\n        tools:targetApi=\"lollipop\"\n        tools:ignore=\"DataExtractionRules\">\n\n        <activity\n            android:name=\".Launcher\"\n            android:exported=\"true\"\n            android:theme=\"@style/Theme.AppCompat.NoActionBar\"\n            android:launchMode=\"singleInstance\"\n            android:screenOrientation=\"landscape\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <action android:name=\"android.intent.action.EDIT\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"file\"/>\n                <data android:scheme=\"content\"/>\n                <data android:mimeType=\"*/*\"/>\n                <data android:host=\"*\" />\n\n                <data android:mimeType=\"application/lvl\" />\n                <data android:mimeType=\"application/wld\" />\n                <data android:mimeType=\"application/lvlx\" />\n                <data android:mimeType=\"application/wldx\" />\n\n                <data android:mimeType=\"application/x-lvl\" />\n                <data android:mimeType=\"application/x-wld\" />\n                <data android:mimeType=\"application/x-lvlx\" />\n                <data android:mimeType=\"application/x-wldx\" />\n\n                <data android:pathPattern=\".*\\\\.lvl\" />\n                <data android:pathPattern=\".*\\\\..*\\\\.lvl\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\.lvl\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.lvl\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.lvl\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.lvl\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.lvl\" />\n\n                <data android:pathPattern=\".*\\\\.LVL\" />\n                <data android:pathPattern=\".*\\\\..*\\\\.LVL\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\.LVL\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.LVL\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.LVL\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.LVL\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.LVL\" />\n\n                <data android:pathPattern=\".*\\\\.wld\" />\n                <data android:pathPattern=\".*\\\\..*\\\\.wld\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\.wld\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.wld\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.wld\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.wld\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.wld\" />\n\n                <data android:pathPattern=\".*\\\\.WLD\" />\n                <data android:pathPattern=\".*\\\\..*\\\\.WLD\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\.WLD\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.WLD\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.WLD\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.WLD\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.WLD\" />\n\n                <data android:pathPattern=\".*\\\\.lvlx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\.lvlx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\.lvlx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.lvlx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.lvlx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.lvlx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.lvlx\" />\n\n                <data android:pathPattern=\".*\\\\.LVLX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\.LVLX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\.LVLX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.LVLX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.LVLX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.LVLX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.LVLX\" />\n\n                <data android:pathPattern=\".*\\\\.wldx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\.wldx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\.wldx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.wldx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.wldx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.wldx\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.wldx\" />\n\n                <data android:pathPattern=\".*\\\\.WLDX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\.WLDX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\.WLDX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\.WLDX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.WLDX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.WLDX\" />\n                <data android:pathPattern=\".*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\..*\\\\.WLDX\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".GameSettings\"\n            android:theme=\"@style/Theme.AppCompat.DayNight\"\n            android:label=\"@string/title_activity_game_settings\"\n            android:screenOrientation=\"landscape\"\n            android:parentActivityName=\".Launcher\">\n            <meta-data\n                android:name=\"android.support.PARENT_ACTIVITY\"\n                android:value=\".Launcher\" />\n        </activity>\n\n        <activity\n            android:name=\"thextechActivity\"\n            android:alwaysRetainTaskState=\"true\"\n            android:configChanges=\"layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation\"\n            android:launchMode=\"singleInstance\"\n            android:screenOrientation=\"landscape\">\n            <!-- android:label=\"@string/app_name\" -->\n            <!-- Drop file event -->\n            <!--\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <data android:mimeType=\"*/*\" />\n            </intent-filter>\n            -->\n        </activity>\n\n        <meta-data android:name=\"SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK\" android:value=\"0\"/>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "android-project/thextech/src/main/assets/buttons/Credits.txt",
    "content": "Button pictures made by ZeZeinzer (Discord BlueBoi#8980)\nAdditions and mods by 0lhi and Wohlstand\n"
  },
  {
    "path": "android-project/thextech/src/main/generate_patternPath.sh",
    "content": "#!/bin/bash\n\nfor q in lvl LVL wld WLD lvlx LVLX wldx WLDX; do\n    echo \"                <data android:pathPattern=\\\".*\\\\\\\\.${q}\\\" />\"\n    echo \"                <data android:pathPattern=\\\".*\\\\\\\\..*\\\\\\\\.${q}\\\" />\"\n    echo \"                <data android:pathPattern=\\\".*\\\\\\\\..*\\\\\\\\..*\\\\\\\\.${q}\\\" />\"\n    echo \"                <data android:pathPattern=\\\".*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\.${q}\\\" />\"\n    echo \"                <data android:pathPattern=\\\".*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\.${q}\\\" />\"\n    echo \"                <data android:pathPattern=\\\".*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\.${q}\\\" />\"\n    echo \"                <data android:pathPattern=\\\".*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\..*\\\\\\\\.${q}\\\" />\"\n    echo \"\"\ndone\n"
  },
  {
    "path": "android-project/thextech/src/main/java/javautil/FileUtils.java",
    "content": "package javautil;\n\nimport android.annotation.SuppressLint;\nimport android.content.ContentResolver;\nimport android.content.ContentUris;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.provider.DocumentsContract;\nimport android.provider.MediaStore;\nimport android.provider.OpenableColumns;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.UUID;\n\npublic class FileUtils\n{\n    public static String FALLBACK_COPY_FOLDER = \"upload_part\";\n\n    private static final String TAG = \"FileUtils\";\n\n    private static Uri contentUri = null;\n\n    private boolean m_wasCopiedToInternal = false;\n\n    Context context;\n\n    public FileUtils(Context context)\n    {\n        this.context = context;\n    }\n\n    public boolean isInternalCopy()\n    {\n        return m_wasCopiedToInternal;\n    }\n\n    @SuppressLint(\"NewApi\")\n    public String getPath(final Uri uri)\n    {\n        // check here to KITKAT or new version\n        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;\n        String selection = null;\n        String[] selectionArgs = null;\n        // DocumentProvider\n\n        if (isKitKat)\n        {\n            // ExternalStorageProvider\n            if (isExternalStorageDocument(uri))\n            {\n                final String docId = DocumentsContract.getDocumentId(uri);\n                final String[] split = docId.split(\":\");\n                final String type = split[0];\n\n                String fullPath = getPathFromExtSD(split);\n\n                if (fullPath == null || !fileExists(fullPath))\n                {\n                    Log.d(TAG, \"Copy files as a fallback\");\n                    fullPath = copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER);\n                }\n                else\n                    m_wasCopiedToInternal = false;\n\n                if (!fullPath.equals(\"\"))\n                    return fullPath;\n                else\n                    return null;\n            }\n\n            // DownloadsProvider\n            if (isDownloadsDocument(uri))\n            {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)\n                {\n                    final String id;\n                    Cursor cursor = null;\n\n                    try\n                    {\n                        cursor = context.getContentResolver().query(uri, new String[] { MediaStore.MediaColumns.DISPLAY_NAME }, null, null, null);\n                        if (cursor != null && cursor.moveToFirst())\n                        {\n                            String fileName = cursor.getString(0);\n                            String path = Environment.getExternalStorageDirectory().toString() + \"/Download/\" + fileName;\n                            if (!TextUtils.isEmpty(path))\n                            {\n                                m_wasCopiedToInternal = false;\n                                return path;\n                            }\n                        }\n                    }\n                    finally\n                    {\n                        if (cursor != null)\n                            cursor.close();\n                    }\n\n                    id = DocumentsContract.getDocumentId(uri);\n\n                    if (!TextUtils.isEmpty(id))\n                    {\n                        if (id.startsWith(\"raw:\"))\n                            return id.replaceFirst(\"raw:\", \"\");\n\n                        String[] contentUriPrefixesToTry = new String[]\n                        {\n                            \"content://downloads/public_downloads\",\n                            \"content://downloads/my_downloads\"\n                        };\n\n                        for (String contentUriPrefix : contentUriPrefixesToTry)\n                        {\n                            try\n                            {\n                                final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.parseLong(id));\n                                return getDataColumn(context, contentUri, null, null);\n                            }\n                            catch (NumberFormatException e)\n                            {\n                                //In Android 8 and Android P the id is not a number\n                                String ret = uri.getPath();\n                                assert(ret != null);\n                                return ret.replaceFirst(\"^/document/raw:\", \"\").replaceFirst(\"^raw:\", \"\");\n                            }\n                        }\n                    }\n                }\n                else\n                {\n                    final String id = DocumentsContract.getDocumentId(uri);\n\n                    if (id.startsWith(\"raw:\"))\n                    {\n                        m_wasCopiedToInternal = true;\n                        return id.replaceFirst(\"raw:\", \"\");\n                    }\n\n                    try\n                    {\n                        contentUri = ContentUris.withAppendedId(Uri.parse(\"content://downloads/public_downloads\"), Long.parseLong(id));\n                    }\n                    catch (NumberFormatException e)\n                    {\n                        e.printStackTrace();\n                    }\n\n                    if (contentUri != null)\n                        return getDataColumn(context, contentUri, null, null);\n                }\n            }\n\n\n            // MediaProvider\n            if (isMediaDocument(uri))\n            {\n                final String docId = DocumentsContract.getDocumentId(uri);\n                final String[] split = docId.split(\":\");\n                final String type = split[0];\n\n                Log.d(TAG, \"MEDIA DOCUMENT TYPE: \" + type);\n\n                Uri contentUri = null;\n\n                if (\"image\".equals(type))\n                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;\n                else if (\"video\".equals(type))\n                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;\n                else if (\"audio\".equals(type))\n                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;\n                else if (\"document\".equals(type))\n                    contentUri = MediaStore.Files.getContentUri(MediaStore.getVolumeName(uri));\n\n                selection = \"_id=?\";\n                selectionArgs = new String[] { split[1] };\n\n                return getDataColumn(context, contentUri, selection, selectionArgs);\n            }\n\n            if (isGoogleDriveUri(uri))\n                return getDriveFilePath(uri);\n\n            if (isWhatsAppFile(uri))\n                return getFilePathForWhatsApp(uri);\n\n            if (\"content\".equalsIgnoreCase(uri.getScheme()))\n            {\n                if (isGooglePhotosUri(uri))\n                {\n                    m_wasCopiedToInternal = false;\n                    return uri.getLastPathSegment();\n                }\n\n                if (isGoogleDriveUri(uri) || isSamsungMyFilesAppFile(uri))\n                    return getDriveFilePath(uri);\n\n                // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)\n                if (Build.VERSION.SDK_INT >= 30 && !Environment.isExternalStorageManager())\n                {\n                    // return getFilePathFromURI(context,uri);\n                    return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER);\n                    // return getRealPathFromURI(context,uri);\n                }\n                else\n                {\n                    return getDataColumn(context, uri, null, null);\n                }\n            }\n\n            if (\"file\".equalsIgnoreCase(uri.getScheme()))\n            {\n                m_wasCopiedToInternal = false;\n                return uri.getPath();\n            }\n\n        }\n        else\n        {\n            String scheme = uri.getScheme();\n\n            if (isWhatsAppFile(uri))\n                return getFilePathForWhatsApp(uri);\n\n            if(scheme == null)\n                return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER);\n\n            if (scheme.equalsIgnoreCase(ContentResolver.SCHEME_FILE))\n            {\n                String ret = uri.getPath();\n                Log.d(TAG, \"Got URI path from File intent: \" + ret);\n                return ret;\n            }\n            else if (scheme.equalsIgnoreCase(ContentResolver.SCHEME_CONTENT))\n            {\n                String[] projection = { MediaStore.Images.Media.DATA };\n                Cursor cursor = null;\n\n                try\n                {\n                    cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);\n                    assert(cursor != null);\n\n                    int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);\n\n                    if (cursor.moveToFirst())\n                    {\n                        String ret = cursor.getString(column_index);\n                        cursor.close();\n                        return ret;\n                    }\n\n                    cursor.close();\n                }\n                catch (Exception e)\n                {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER);\n    }\n\n    private static boolean fileExists(String filePath)\n    {\n        File file = new File(filePath);\n        return file.exists();\n    }\n\n    private static String getPathFromExtSD(String[] pathData)\n    {\n        final String type = pathData[0];\n        final String relativePath = File.separator + pathData[1];\n        String fullPath = \"\";\n\n\n        Log.d(TAG, \"MEDIA EXTSD TYPE: \" + type);\n        Log.d(TAG, \"Relative path: \" + relativePath);\n        // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string\n        // something like \"71F8-2C0A\", some kind of unique id per storage\n        // don't know any API that can get the root path of that storage based on its id.\n        //\n        // so no \"primary\" type, but let the check here for other devices\n        if (\"primary\".equalsIgnoreCase(type))\n        {\n            fullPath = Environment.getExternalStorageDirectory() + relativePath;\n            if (fileExists(fullPath))\n            {\n                return fullPath;\n            }\n        }\n\n        if (\"home\".equalsIgnoreCase(type))\n        {\n            fullPath = \"/storage/emulated/0/Documents\" + relativePath;\n            if (fileExists(fullPath))\n            {\n                return fullPath;\n            }\n        }\n\n        // Environment.isExternalStorageRemovable() is `true` for external and internal storage\n        // so we cannot relay on it.\n        //\n        // instead, for each possible path, check if file exists\n        // we'll start with secondary storage as this could be our (physically) removable sd card\n        fullPath = System.getenv(\"SECONDARY_STORAGE\") + relativePath;\n        if (fileExists(fullPath))\n        {\n            return fullPath;\n        }\n\n        fullPath = System.getenv(\"EXTERNAL_STORAGE\") + relativePath;\n        if (fileExists(fullPath))\n        {\n            return fullPath;\n        }\n\n        return null;\n    }\n\n    private String getDriveFilePath(Uri uri)\n    {\n        //Uri returnUri = uri;\n        Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null);\n        assert(returnCursor != null);\n\n        /*\n         * Get the column indexes of the data in the Cursor,\n         *     * move to the first row in the Cursor, get the data,\n         *     * and display it.\n         * */\n        int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);\n        // int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);\n        returnCursor.moveToFirst();\n        String name = (returnCursor.getString(nameIndex));\n        // String size = (Long.toString(returnCursor.getLong(sizeIndex)));\n        File file = new File(context.getCacheDir(), name);\n\n        try\n        {\n            InputStream inputStream = context.getContentResolver().openInputStream(uri);\n            assert(inputStream != null);\n            FileOutputStream outputStream = new FileOutputStream(file);\n\n            int read = 0;\n            int maxBufferSize = 1024 * 1024;\n            int bytesAvailable = inputStream.available();\n\n            //int bufferSize = 1024;\n            int bufferSize = Math.min(bytesAvailable, maxBufferSize);\n\n            final byte[] buffers = new byte[bufferSize];\n            while ((read = inputStream.read(buffers)) != -1)\n                outputStream.write(buffers, 0, read);\n\n            Log.e(TAG, \"Size \" + file.length());\n            inputStream.close();\n            outputStream.close();\n            Log.e(TAG, \"Path \" + file.getPath());\n            Log.e(TAG, \"Size \" + file.length());\n        }\n        catch (Exception e)\n        {\n            String err = e.getMessage();\n            assert(err != null);\n            Log.e(TAG, err);\n        }\n\n        returnCursor.close();\n        m_wasCopiedToInternal = true;\n\n        return file.getPath();\n    }\n\n    /***\n     * Used for Android Q+\n     * @param uri Input URI file\n     * @param newDirName if you want to create a directory, you can set this variable\n     * @return path to copied file\n     */\n    private String copyFileToInternalStorage(Uri uri, String newDirName)\n    {\n        Cursor returnCursor = context.getContentResolver().query(uri, new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE }, null, null, null);\n        assert(returnCursor != null);\n        String name;\n\n        /*\n         * Get the column indexes of the data in the Cursor,\n         *     * move to the first row in the Cursor, get the data,\n         *     * and display it.\n         * */\n        int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);\n        // int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);\n        returnCursor.moveToFirst();\n        try\n        {\n            name = (returnCursor.getString(nameIndex));\n        }\n        catch (Exception e)\n        {\n            if(e.getMessage() != null)\n                Log.e(TAG, e.getMessage());\n            else\n                Log.e(TAG, \"<NULL error>\");\n\n            name = \"unknown.lvlx\";\n        }\n        // String size = (Long.toString(returnCursor.getLong(sizeIndex)));\n\n        File output;\n        if (!newDirName.equals(\"\"))\n        {\n            String random_collision_avoidance = UUID.randomUUID().toString();\n\n            File dir = new File(context.getFilesDir() + File.separator + newDirName + File.separator + random_collision_avoidance);\n            if (!dir.exists())\n            {\n                if(!dir.mkdirs())\n                    Log.e(TAG, \"Can't create directory: \" + dir.getPath());\n            }\n\n            output = new File(context.getFilesDir() + File.separator + newDirName + File.separator + random_collision_avoidance + File.separator + name);\n        }\n        else\n        {\n            output = new File(context.getFilesDir() + File.separator + name);\n        }\n\n        try\n        {\n            InputStream inputStream = context.getContentResolver().openInputStream(uri);\n            if(inputStream == null)\n                throw new Exception(\"InputStream is NULL\");\n\n            FileOutputStream outputStream = new FileOutputStream(output);\n            int read = 0;\n            int bufferSize = 1024;\n            final byte[] buffers = new byte[bufferSize];\n\n            while ((read = inputStream.read(buffers)) != -1)\n            {\n                outputStream.write(buffers, 0, read);\n            }\n\n            inputStream.close();\n            outputStream.close();\n        }\n        catch (Exception e)\n        {\n            if(e.getMessage() != null)\n                Log.e(TAG, e.getMessage());\n            else\n                Log.e(TAG, \"<NULL error>\");\n        }\n\n        returnCursor.close();\n        m_wasCopiedToInternal = true;\n\n        return output.getPath();\n    }\n\n    private String getFilePathForWhatsApp(Uri uri)\n    {\n        return copyFileToInternalStorage(uri, \"whatsapp\");\n    }\n\n    private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs)\n    {\n        Cursor cursor = null;\n        final String column = \"_data\";\n        final String[] projection = { column };\n\n        try\n        {\n            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);\n\n            if (cursor != null && cursor.moveToFirst())\n            {\n                List<String> columns = Arrays.asList(cursor.getColumnNames());\n                Log.d(TAG, \"Available columns: \" + String.join(\", \", columns));\n                final int index = cursor.getColumnIndexOrThrow(column);\n                m_wasCopiedToInternal = false;\n                return cursor.getString(index);\n            }\n        }\n        catch(Exception e)\n        {\n            if(e.getMessage() != null)\n                Log.e(TAG, e.getMessage());\n            else\n                Log.e(TAG, \"<NULL error>\");\n\n            return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER);\n        }\n        finally\n        {\n            if(cursor != null)\n                cursor.close();\n        }\n\n        return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER);\n    }\n\n    private static boolean isExternalStorageDocument(Uri uri)\n    {\n        return \"com.android.externalstorage.documents\".equals(uri.getAuthority());\n    }\n\n    private static boolean isDownloadsDocument(Uri uri)\n    {\n        return \"com.android.providers.downloads.documents\".equals(uri.getAuthority());\n    }\n\n    private boolean isMediaDocument(Uri uri)\n    {\n        return \"com.android.providers.media.documents\".equals(uri.getAuthority());\n    }\n\n    private boolean isGooglePhotosUri(Uri uri)\n    {\n        return \"com.google.android.apps.photos.content\".equals(uri.getAuthority());\n    }\n\n    public boolean isWhatsAppFile(Uri uri)\n    {\n        return \"com.whatsapp.provider.media\".equals(uri.getAuthority());\n    }\n\n    public boolean isSamsungMyFilesAppFile(Uri uri)\n    {\n        return \"com.sec.android.app.myfiles.FileProvider\".equals(uri.getAuthority());\n    }\n\n    private boolean isGoogleDriveUri(Uri uri)\n    {\n        return \"com.google.android.apps.docs.storage\".equals(uri.getAuthority()) || \"com.google.android.apps.docs.storage.legacy\".equals(uri.getAuthority());\n    }\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/javautil/README.md",
    "content": "# Android Filename Picker using Java\r\nA library for getting filenames in an Android device\r\n\r\nOriginal got from the repository: https://github.com/saparkhid/AndroidFileNamePicker\r\nReferred at: https://stackoverflow.com/a/60642994\r\n"
  },
  {
    "path": "android-project/thextech/src/main/java/org/libsdl/app/HIDDevice.java",
    "content": "package org.libsdl.app;\n\nimport android.hardware.usb.UsbDevice;\n\ninterface HIDDevice\n{\n    public int getId();\n    public int getVendorId();\n    public int getProductId();\n    public String getSerialNumber();\n    public int getVersion();\n    public String getManufacturerName();\n    public String getProductName();\n    public UsbDevice getDevice();\n    public boolean open();\n    public int sendFeatureReport(byte[] report);\n    public int sendOutputReport(byte[] report);\n    public boolean getFeatureReport(byte[] report);\n    public void setFrozen(boolean frozen);\n    public void close();\n    public void shutdown();\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\nimport android.bluetooth.BluetoothDevice;\nimport android.bluetooth.BluetoothGatt;\nimport android.bluetooth.BluetoothGattCallback;\nimport android.bluetooth.BluetoothGattCharacteristic;\nimport android.bluetooth.BluetoothGattDescriptor;\nimport android.bluetooth.BluetoothManager;\nimport android.bluetooth.BluetoothProfile;\nimport android.bluetooth.BluetoothGattService;\nimport android.hardware.usb.UsbDevice;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.Log;\nimport android.os.*;\n\n//import com.android.internal.util.HexDump;\n\nimport java.lang.Runnable;\nimport java.util.Arrays;\nimport java.util.LinkedList;\nimport java.util.UUID;\n\nclass HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {\n\n    private static final String TAG = \"hidapi\";\n    private HIDDeviceManager mManager;\n    private BluetoothDevice mDevice;\n    private int mDeviceId;\n    private BluetoothGatt mGatt;\n    private boolean mIsRegistered = false;\n    private boolean mIsConnected = false;\n    private boolean mIsChromebook = false;\n    private boolean mIsReconnecting = false;\n    private boolean mFrozen = false;\n    private LinkedList<GattOperation> mOperations;\n    GattOperation mCurrentOperation = null;\n    private Handler mHandler;\n\n    private static final int TRANSPORT_AUTO = 0;\n    private static final int TRANSPORT_BREDR = 1;\n    private static final int TRANSPORT_LE = 2;\n\n    private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;\n\n    static public final UUID steamControllerService = UUID.fromString(\"100F6C32-1735-4313-B402-38567131E5F3\");\n    static public final UUID inputCharacteristic = UUID.fromString(\"100F6C33-1735-4313-B402-38567131E5F3\");\n    static public final UUID reportCharacteristic = UUID.fromString(\"100F6C34-1735-4313-B402-38567131E5F3\");\n    static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };\n\n    static class GattOperation {\n        private enum Operation {\n            CHR_READ,\n            CHR_WRITE,\n            ENABLE_NOTIFICATION\n        }\n\n        Operation mOp;\n        UUID mUuid;\n        byte[] mValue;\n        BluetoothGatt mGatt;\n        boolean mResult = true;\n\n        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {\n            mGatt = gatt;\n            mOp = operation;\n            mUuid = uuid;\n        }\n\n        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {\n            mGatt = gatt;\n            mOp = operation;\n            mUuid = uuid;\n            mValue = value;\n        }\n\n        public void run() {\n            // This is executed in main thread\n            BluetoothGattCharacteristic chr;\n\n            switch (mOp) {\n                case CHR_READ:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Reading characteristic \" + chr.getUuid());\n                    if (!mGatt.readCharacteristic(chr)) {\n                        Log.e(TAG, \"Unable to read characteristic \" + mUuid.toString());\n                        mResult = false;\n                        break;\n                    }\n                    mResult = true;\n                    break;\n                case CHR_WRITE:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Writing characteristic \" + chr.getUuid() + \" value=\" + HexDump.toHexString(value));\n                    chr.setValue(mValue);\n                    if (!mGatt.writeCharacteristic(chr)) {\n                        Log.e(TAG, \"Unable to write characteristic \" + mUuid.toString());\n                        mResult = false;\n                        break;\n                    }\n                    mResult = true;\n                    break;\n                case ENABLE_NOTIFICATION:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Writing descriptor of \" + chr.getUuid());\n                    if (chr != null) {\n                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString(\"00002902-0000-1000-8000-00805f9b34fb\"));\n                        if (cccd != null) {\n                            int properties = chr.getProperties();\n                            byte[] value;\n                            if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {\n                                value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;\n                            } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {\n                                value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;\n                            } else {\n                                Log.e(TAG, \"Unable to start notifications on input characteristic\");\n                                mResult = false;\n                                return;\n                            }\n\n                            mGatt.setCharacteristicNotification(chr, true);\n                            cccd.setValue(value);\n                            if (!mGatt.writeDescriptor(cccd)) {\n                                Log.e(TAG, \"Unable to write descriptor \" + mUuid.toString());\n                                mResult = false;\n                                return;\n                            }\n                            mResult = true;\n                        }\n                    }\n            }\n        }\n\n        public boolean finish() {\n            return mResult;\n        }\n\n        private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {\n            BluetoothGattService valveService = mGatt.getService(steamControllerService);\n            if (valveService == null)\n                return null;\n            return valveService.getCharacteristic(uuid);\n        }\n\n        static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {\n            return new GattOperation(gatt, Operation.CHR_READ, uuid);\n        }\n\n        static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {\n            return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);\n        }\n\n        static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {\n            return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);\n        }\n    }\n\n    public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {\n        mManager = manager;\n        mDevice = device;\n        mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());\n        mIsRegistered = false;\n        mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\");\n        mOperations = new LinkedList<GattOperation>();\n        mHandler = new Handler(Looper.getMainLooper());\n\n        mGatt = connectGatt();\n        // final HIDDeviceBLESteamController finalThis = this;\n        // mHandler.postDelayed(new Runnable() {\n        //     @Override\n        //     public void run() {\n        //         finalThis.checkConnectionForChromebookIssue();\n        //     }\n        // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);\n    }\n\n    public String getIdentifier() {\n        return String.format(\"SteamController.%s\", mDevice.getAddress());\n    }\n\n    public BluetoothGatt getGatt() {\n        return mGatt;\n    }\n\n    // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead\n    // of TRANSPORT_LE.  Let's force ourselves to connect low energy.\n    private BluetoothGatt connectGatt(boolean managed) {\n        if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {\n            try {\n                return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);\n            } catch (Exception e) {\n                return mDevice.connectGatt(mManager.getContext(), managed, this);\n            }\n        } else {\n            return mDevice.connectGatt(mManager.getContext(), managed, this);\n        }\n    }\n\n    private BluetoothGatt connectGatt() {\n        return connectGatt(false);\n    }\n\n    protected int getConnectionState() {\n\n        Context context = mManager.getContext();\n        if (context == null) {\n            // We are lacking any context to get our Bluetooth information.  We'll just assume disconnected.\n            return BluetoothProfile.STATE_DISCONNECTED;\n        }\n\n        BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);\n        if (btManager == null) {\n            // This device doesn't support Bluetooth.  We should never be here, because how did\n            // we instantiate a device to start with?\n            return BluetoothProfile.STATE_DISCONNECTED;\n        }\n\n        return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);\n    }\n\n    public void reconnect() {\n\n        if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {\n            mGatt.disconnect();\n            mGatt = connectGatt();\n        }\n\n    }\n\n    protected void checkConnectionForChromebookIssue() {\n        if (!mIsChromebook) {\n            // We only do this on Chromebooks, because otherwise it's really annoying to just attempt\n            // over and over.\n            return;\n        }\n\n        int connectionState = getConnectionState();\n\n        switch (connectionState) {\n            case BluetoothProfile.STATE_CONNECTED:\n                if (!mIsConnected) {\n                    // We are in the Bad Chromebook Place.  We can force a disconnect\n                    // to try to recover.\n                    Log.v(TAG, \"Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback.  Forcing a reconnect.\");\n                    mIsReconnecting = true;\n                    mGatt.disconnect();\n                    mGatt = connectGatt(false);\n                    break;\n                }\n                else if (!isRegistered()) {\n                    if (mGatt.getServices().size() > 0) {\n                        Log.v(TAG, \"Chromebook: We are connected to a controller, but never got our registration.  Trying to recover.\");\n                        probeService(this);\n                    }\n                    else {\n                        Log.v(TAG, \"Chromebook: We are connected to a controller, but never discovered services.  Trying to recover.\");\n                        mIsReconnecting = true;\n                        mGatt.disconnect();\n                        mGatt = connectGatt(false);\n                        break;\n                    }\n                }\n                else {\n                    Log.v(TAG, \"Chromebook: We are connected, and registered.  Everything's good!\");\n                    return;\n                }\n                break;\n\n            case BluetoothProfile.STATE_DISCONNECTED:\n                Log.v(TAG, \"Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us.  Attempting a disconnect/reconnect, but we may not be able to recover.\");\n\n                mIsReconnecting = true;\n                mGatt.disconnect();\n                mGatt = connectGatt(false);\n                break;\n\n            case BluetoothProfile.STATE_CONNECTING:\n                Log.v(TAG, \"Chromebook: We're still trying to connect.  Waiting a bit longer.\");\n                break;\n        }\n\n        final HIDDeviceBLESteamController finalThis = this;\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                finalThis.checkConnectionForChromebookIssue();\n            }\n        }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);\n    }\n\n    private boolean isRegistered() {\n        return mIsRegistered;\n    }\n\n    private void setRegistered() {\n        mIsRegistered = true;\n    }\n\n    private boolean probeService(HIDDeviceBLESteamController controller) {\n\n        if (isRegistered()) {\n            return true;\n        }\n\n        if (!mIsConnected) {\n            return false;\n        }\n\n        Log.v(TAG, \"probeService controller=\" + controller);\n\n        for (BluetoothGattService service : mGatt.getServices()) {\n            if (service.getUuid().equals(steamControllerService)) {\n                Log.v(TAG, \"Found Valve steam controller service \" + service.getUuid());\n\n                for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {\n                    if (chr.getUuid().equals(inputCharacteristic)) {\n                        Log.v(TAG, \"Found input characteristic\");\n                        // Start notifications\n                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString(\"00002902-0000-1000-8000-00805f9b34fb\"));\n                        if (cccd != null) {\n                            enableNotification(chr.getUuid());\n                        }\n                    }\n                }\n                return true;\n            }\n        }\n\n        if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {\n            Log.e(TAG, \"Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.\");\n            mIsConnected = false;\n            mIsReconnecting = true;\n            mGatt.disconnect();\n            mGatt = connectGatt(false);\n        }\n\n        return false;\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private void finishCurrentGattOperation() {\n        GattOperation op = null;\n        synchronized (mOperations) {\n            if (mCurrentOperation != null) {\n                op = mCurrentOperation;\n                mCurrentOperation = null;\n            }\n        }\n        if (op != null) {\n            boolean result = op.finish(); // TODO: Maybe in main thread as well?\n\n            // Our operation failed, let's add it back to the beginning of our queue.\n            if (!result) {\n                mOperations.addFirst(op);\n            }\n        }\n        executeNextGattOperation();\n    }\n\n    private void executeNextGattOperation() {\n        synchronized (mOperations) {\n            if (mCurrentOperation != null)\n                return;\n\n            if (mOperations.isEmpty())\n                return;\n\n            mCurrentOperation = mOperations.removeFirst();\n        }\n\n        // Run in main thread\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                synchronized (mOperations) {\n                    if (mCurrentOperation == null) {\n                        Log.e(TAG, \"Current operation null in executor?\");\n                        return;\n                    }\n\n                    mCurrentOperation.run();\n                    // now wait for the GATT callback and when it comes, finish this operation\n                }\n            }\n        });\n    }\n\n    private void queueGattOperation(GattOperation op) {\n        synchronized (mOperations) {\n            mOperations.add(op);\n        }\n        executeNextGattOperation();\n    }\n\n    private void enableNotification(UUID chrUuid) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);\n        queueGattOperation(op);\n    }\n\n    public void writeCharacteristic(UUID uuid, byte[] value) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);\n        queueGattOperation(op);\n    }\n\n    public void readCharacteristic(UUID uuid) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);\n        queueGattOperation(op);\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////  BluetoothGattCallback overridden methods\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {\n        //Log.v(TAG, \"onConnectionStateChange status=\" + status + \" newState=\" + newState);\n        mIsReconnecting = false;\n        if (newState == 2) {\n            mIsConnected = true;\n            // Run directly, without GattOperation\n            if (!isRegistered()) {\n                mHandler.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        mGatt.discoverServices();\n                    }\n                });\n            }\n        }\n        else if (newState == 0) {\n            mIsConnected = false;\n        }\n\n        // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.\n    }\n\n    public void onServicesDiscovered(BluetoothGatt gatt, int status) {\n        //Log.v(TAG, \"onServicesDiscovered status=\" + status);\n        if (status == 0) {\n            if (gatt.getServices().size() == 0) {\n                Log.v(TAG, \"onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.\");\n                mIsReconnecting = true;\n                mIsConnected = false;\n                gatt.disconnect();\n                mGatt = connectGatt(false);\n            }\n            else {\n                probeService(this);\n            }\n        }\n    }\n\n    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {\n        //Log.v(TAG, \"onCharacteristicRead status=\" + status + \" uuid=\" + characteristic.getUuid());\n\n        if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {\n            mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {\n        //Log.v(TAG, \"onCharacteristicWrite status=\" + status + \" uuid=\" + characteristic.getUuid());\n\n        if (characteristic.getUuid().equals(reportCharacteristic)) {\n            // Only register controller with the native side once it has been fully configured\n            if (!isRegistered()) {\n                Log.v(TAG, \"Registering Steam Controller with ID: \" + getId());\n                mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);\n                setRegistered();\n            }\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {\n    // Enable this for verbose logging of controller input reports\n        //Log.v(TAG, \"onCharacteristicChanged uuid=\" + characteristic.getUuid() + \" data=\" + HexDump.dumpHexString(characteristic.getValue()));\n\n        if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {\n            mManager.HIDDeviceInputReport(getId(), characteristic.getValue());\n        }\n    }\n\n    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {\n        //Log.v(TAG, \"onDescriptorRead status=\" + status);\n    }\n\n    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {\n        BluetoothGattCharacteristic chr = descriptor.getCharacteristic();\n        //Log.v(TAG, \"onDescriptorWrite status=\" + status + \" uuid=\" + chr.getUuid() + \" descriptor=\" + descriptor.getUuid());\n\n        if (chr.getUuid().equals(inputCharacteristic)) {\n            boolean hasWrittenInputDescriptor = true;\n            BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);\n            if (reportChr != null) {\n                Log.v(TAG, \"Writing report characteristic to enter valve mode\");\n                reportChr.setValue(enterValveMode);\n                gatt.writeCharacteristic(reportChr);\n            }\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {\n        //Log.v(TAG, \"onReliableWriteCompleted status=\" + status);\n    }\n\n    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {\n        //Log.v(TAG, \"onReadRemoteRssi status=\" + status);\n    }\n\n    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {\n        //Log.v(TAG, \"onMtuChanged status=\" + status);\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////// Public API\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    @Override\n    public int getId() {\n        return mDeviceId;\n    }\n\n    @Override\n    public int getVendorId() {\n        // Valve Corporation\n        final int VALVE_USB_VID = 0x28DE;\n        return VALVE_USB_VID;\n    }\n\n    @Override\n    public int getProductId() {\n        // We don't have an easy way to query from the Bluetooth device, but we know what it is\n        final int D0G_BLE2_PID = 0x1106;\n        return D0G_BLE2_PID;\n    }\n\n    @Override\n    public String getSerialNumber() {\n        // This will be read later via feature report by Steam\n        return \"12345\";\n    }\n\n    @Override\n    public int getVersion() {\n        return 0;\n    }\n\n    @Override\n    public String getManufacturerName() {\n        return \"Valve Corporation\";\n    }\n\n    @Override\n    public String getProductName() {\n        return \"Steam Controller\";\n    }\n\n    @Override\n    public UsbDevice getDevice() {\n        return null;\n    }\n\n    @Override\n    public boolean open() {\n        return true;\n    }\n\n    @Override\n    public int sendFeatureReport(byte[] report) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted sendFeatureReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return -1;\n        }\n\n        // We need to skip the first byte, as that doesn't go over the air\n        byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);\n        //Log.v(TAG, \"sendFeatureReport \" + HexDump.dumpHexString(actual_report));\n        writeCharacteristic(reportCharacteristic, actual_report);\n        return report.length;\n    }\n\n    @Override\n    public int sendOutputReport(byte[] report) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted sendOutputReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return -1;\n        }\n\n        //Log.v(TAG, \"sendFeatureReport \" + HexDump.dumpHexString(report));\n        writeCharacteristic(reportCharacteristic, report);\n        return report.length;\n    }\n\n    @Override\n    public boolean getFeatureReport(byte[] report) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted getFeatureReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return false;\n        }\n\n        //Log.v(TAG, \"getFeatureReport\");\n        readCharacteristic(reportCharacteristic);\n        return true;\n    }\n\n    @Override\n    public void close() {\n    }\n\n    @Override\n    public void setFrozen(boolean frozen) {\n        mFrozen = frozen;\n    }\n\n    @Override\n    public void shutdown() {\n        close();\n\n        BluetoothGatt g = mGatt;\n        if (g != null) {\n            g.disconnect();\n            g.close();\n            mGatt = null;\n        }\n        mManager = null;\n        mIsRegistered = false;\n        mIsConnected = false;\n        mOperations.clear();\n    }\n\n}\n\n"
  },
  {
    "path": "android-project/thextech/src/main/java/org/libsdl/app/HIDDeviceManager.java",
    "content": "package org.libsdl.app;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.PendingIntent;\nimport android.bluetooth.BluetoothAdapter;\nimport android.bluetooth.BluetoothDevice;\nimport android.bluetooth.BluetoothManager;\nimport android.bluetooth.BluetoothProfile;\nimport android.os.Build;\nimport android.util.Log;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.hardware.usb.*;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class HIDDeviceManager {\n    private static final String TAG = \"hidapi\";\n    private static final String ACTION_USB_PERMISSION = \"org.libsdl.app.USB_PERMISSION\";\n\n    private static HIDDeviceManager sManager;\n    private static int sManagerRefCount = 0;\n\n    public static HIDDeviceManager acquire(Context context) {\n        if (sManagerRefCount == 0) {\n            sManager = new HIDDeviceManager(context);\n        }\n        ++sManagerRefCount;\n        return sManager;\n    }\n\n    public static void release(HIDDeviceManager manager) {\n        if (manager == sManager) {\n            --sManagerRefCount;\n            if (sManagerRefCount == 0) {\n                sManager.close();\n                sManager = null;\n            }\n        }\n    }\n\n    private Context mContext;\n    private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();\n    private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();\n    private int mNextDeviceId = 0;\n    private SharedPreferences mSharedPreferences = null;\n    private boolean mIsChromebook = false;\n    private UsbManager mUsbManager;\n    private Handler mHandler;\n    private BluetoothManager mBluetoothManager;\n    private List<BluetoothDevice> mLastBluetoothDevices;\n\n    private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String action = intent.getAction();\n            if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDeviceAttached(usbDevice);\n            } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDeviceDetached(usbDevice);\n            } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));\n            }\n        }\n    };\n\n    private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String action = intent.getAction();\n            // Bluetooth device was connected. If it was a Steam Controller, handle it\n            if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {\n                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);\n                Log.d(TAG, \"Bluetooth device connected: \" + device);\n\n                if (isSteamController(device)) {\n                    connectBluetoothDevice(device);\n                }\n            }\n\n            // Bluetooth device was disconnected, remove from controller manager (if any)\n            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {\n                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);\n                Log.d(TAG, \"Bluetooth device disconnected: \" + device);\n\n                disconnectBluetoothDevice(device);\n            }\n        }\n    };\n\n    private HIDDeviceManager(final Context context) {\n        mContext = context;\n\n        HIDDeviceRegisterCallback();\n\n        mSharedPreferences = mContext.getSharedPreferences(\"hidapi\", Context.MODE_PRIVATE);\n        mIsChromebook = mContext.getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\");\n\n//        if (shouldClear) {\n//            SharedPreferences.Editor spedit = mSharedPreferences.edit();\n//            spedit.clear();\n//            spedit.commit();\n//        }\n//        else\n        {\n            mNextDeviceId = mSharedPreferences.getInt(\"next_device_id\", 0);\n        }\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    public int getDeviceIDForIdentifier(String identifier) {\n        SharedPreferences.Editor spedit = mSharedPreferences.edit();\n\n        int result = mSharedPreferences.getInt(identifier, 0);\n        if (result == 0) {\n            result = mNextDeviceId++;\n            spedit.putInt(\"next_device_id\", mNextDeviceId);\n        }\n\n        spedit.putInt(identifier, result);\n        spedit.commit();\n        return result;\n    }\n\n    private void initializeUSB() {\n        mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);\n        if (mUsbManager == null) {\n            return;\n        }\n\n        /*\n        // Logging\n        for (UsbDevice device : mUsbManager.getDeviceList().values()) {\n            Log.i(TAG,\"Path: \" + device.getDeviceName());\n            Log.i(TAG,\"Manufacturer: \" + device.getManufacturerName());\n            Log.i(TAG,\"Product: \" + device.getProductName());\n            Log.i(TAG,\"ID: \" + device.getDeviceId());\n            Log.i(TAG,\"Class: \" + device.getDeviceClass());\n            Log.i(TAG,\"Protocol: \" + device.getDeviceProtocol());\n            Log.i(TAG,\"Vendor ID \" + device.getVendorId());\n            Log.i(TAG,\"Product ID: \" + device.getProductId());\n            Log.i(TAG,\"Interface count: \" + device.getInterfaceCount());\n            Log.i(TAG,\"---------------------------------------\");\n\n            // Get interface details\n            for (int index = 0; index < device.getInterfaceCount(); index++) {\n                UsbInterface mUsbInterface = device.getInterface(index);\n                Log.i(TAG,\"  *****     *****\");\n                Log.i(TAG,\"  Interface index: \" + index);\n                Log.i(TAG,\"  Interface ID: \" + mUsbInterface.getId());\n                Log.i(TAG,\"  Interface class: \" + mUsbInterface.getInterfaceClass());\n                Log.i(TAG,\"  Interface subclass: \" + mUsbInterface.getInterfaceSubclass());\n                Log.i(TAG,\"  Interface protocol: \" + mUsbInterface.getInterfaceProtocol());\n                Log.i(TAG,\"  Endpoint count: \" + mUsbInterface.getEndpointCount());\n\n                // Get endpoint details\n                for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)\n                {\n                    UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);\n                    Log.i(TAG,\"    ++++   ++++   ++++\");\n                    Log.i(TAG,\"    Endpoint index: \" + epi);\n                    Log.i(TAG,\"    Attributes: \" + mEndpoint.getAttributes());\n                    Log.i(TAG,\"    Direction: \" + mEndpoint.getDirection());\n                    Log.i(TAG,\"    Number: \" + mEndpoint.getEndpointNumber());\n                    Log.i(TAG,\"    Interval: \" + mEndpoint.getInterval());\n                    Log.i(TAG,\"    Packet size: \" + mEndpoint.getMaxPacketSize());\n                    Log.i(TAG,\"    Type: \" + mEndpoint.getType());\n                }\n            }\n        }\n        Log.i(TAG,\" No more devices connected.\");\n        */\n\n        // Register for USB broadcasts and permission completions\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);\n        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);\n        filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);\n        mContext.registerReceiver(mUsbBroadcast, filter);\n\n        for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {\n            handleUsbDeviceAttached(usbDevice);\n        }\n    }\n\n    UsbManager getUSBManager() {\n        return mUsbManager;\n    }\n\n    private void shutdownUSB() {\n        try {\n            mContext.unregisterReceiver(mUsbBroadcast);\n        } catch (Exception e) {\n            // We may not have registered, that's okay\n        }\n    }\n\n    private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {\n        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {\n            return true;\n        }\n        if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {\n            return true;\n        }\n        return false;\n    }\n\n    private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {\n        final int XB360_IFACE_SUBCLASS = 93;\n        final int XB360_IFACE_PROTOCOL = 1; // Wired\n        final int XB360W_IFACE_PROTOCOL = 129; // Wireless\n        final int[] SUPPORTED_VENDORS = {\n            0x0079, // GPD Win 2\n            0x044f, // Thrustmaster\n            0x045e, // Microsoft\n            0x046d, // Logitech\n            0x056e, // Elecom\n            0x06a3, // Saitek\n            0x0738, // Mad Catz\n            0x07ff, // Mad Catz\n            0x0e6f, // PDP\n            0x0f0d, // Hori\n            0x1038, // SteelSeries\n            0x11c9, // Nacon\n            0x12ab, // Unknown\n            0x1430, // RedOctane\n            0x146b, // BigBen\n            0x1532, // Razer Sabertooth\n            0x15e4, // Numark\n            0x162e, // Joytech\n            0x1689, // Razer Onza\n            0x1949, // Lab126, Inc.\n            0x1bad, // Harmonix\n            0x20d6, // PowerA\n            0x24c6, // PowerA\n            0x2c22, // Qanba\n            0x2dc8, // 8BitDo\n            0x9886, // ASTRO Gaming\n        };\n\n        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&\n            usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&\n            (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||\n             usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {\n            int vendor_id = usbDevice.getVendorId();\n            for (int supportedVid : SUPPORTED_VENDORS) {\n                if (vendor_id == supportedVid) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {\n        final int XB1_IFACE_SUBCLASS = 71;\n        final int XB1_IFACE_PROTOCOL = 208;\n        final int[] SUPPORTED_VENDORS = {\n            0x03f0, // HP\n            0x044f, // Thrustmaster\n            0x045e, // Microsoft\n            0x0738, // Mad Catz\n            0x0b05, // ASUS\n            0x0e6f, // PDP\n            0x0f0d, // Hori\n            0x10f5, // Turtle Beach\n            0x1532, // Razer Wildcat\n            0x20d6, // PowerA\n            0x24c6, // PowerA\n            0x2dc8, // 8BitDo\n            0x2e24, // Hyperkin\n            0x3537, // GameSir\n        };\n\n        if (usbInterface.getId() == 0 &&\n            usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&\n            usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&\n            usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {\n            int vendor_id = usbDevice.getVendorId();\n            for (int supportedVid : SUPPORTED_VENDORS) {\n                if (vendor_id == supportedVid) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private void handleUsbDeviceAttached(UsbDevice usbDevice) {\n        connectHIDDeviceUSB(usbDevice);\n    }\n\n    private void handleUsbDeviceDetached(UsbDevice usbDevice) {\n        List<Integer> devices = new ArrayList<Integer>();\n        for (HIDDevice device : mDevicesById.values()) {\n            if (usbDevice.equals(device.getDevice())) {\n                devices.add(device.getId());\n            }\n        }\n        for (int id : devices) {\n            HIDDevice device = mDevicesById.get(id);\n            mDevicesById.remove(id);\n            device.shutdown();\n            HIDDeviceDisconnected(id);\n        }\n    }\n\n    private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {\n        for (HIDDevice device : mDevicesById.values()) {\n            if (usbDevice.equals(device.getDevice())) {\n                boolean opened = false;\n                if (permission_granted) {\n                    opened = device.open();\n                }\n                HIDDeviceOpenResult(device.getId(), opened);\n            }\n        }\n    }\n\n    private void connectHIDDeviceUSB(UsbDevice usbDevice) {\n        synchronized (this) {\n            int interface_mask = 0;\n            for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {\n                UsbInterface usbInterface = usbDevice.getInterface(interface_index);\n                if (isHIDDeviceInterface(usbDevice, usbInterface)) {\n                    // Check to see if we've already added this interface\n                    // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive\n                    int interface_id = usbInterface.getId();\n                    if ((interface_mask & (1 << interface_id)) != 0) {\n                        continue;\n                    }\n                    interface_mask |= (1 << interface_id);\n\n                    HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);\n                    int id = device.getId();\n                    mDevicesById.put(id, device);\n                    HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());\n                }\n            }\n        }\n    }\n\n    private void initializeBluetooth() {\n        Log.d(TAG, \"Initializing Bluetooth\");\n\n        if (Build.VERSION.SDK_INT >= 31 /* Android 12  */ &&\n            mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT\");\n            return;\n        }\n\n        if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&\n            mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH\");\n            return;\n        }\n\n        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE\");\n            return;\n        }\n\n        // Find bonded bluetooth controllers and create SteamControllers for them\n        mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);\n        if (mBluetoothManager == null) {\n            // This device doesn't support Bluetooth.\n            return;\n        }\n\n        BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();\n        if (btAdapter == null) {\n            // This device has Bluetooth support in the codebase, but has no available adapters.\n            return;\n        }\n\n        // Get our bonded devices.\n        for (BluetoothDevice device : btAdapter.getBondedDevices()) {\n\n            Log.d(TAG, \"Bluetooth device available: \" + device);\n            if (isSteamController(device)) {\n                connectBluetoothDevice(device);\n            }\n\n        }\n\n        // NOTE: These don't work on Chromebooks, to my undying dismay.\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);\n        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);\n        mContext.registerReceiver(mBluetoothBroadcast, filter);\n\n        if (mIsChromebook) {\n            mHandler = new Handler(Looper.getMainLooper());\n            mLastBluetoothDevices = new ArrayList<BluetoothDevice>();\n\n            // final HIDDeviceManager finalThis = this;\n            // mHandler.postDelayed(new Runnable() {\n            //     @Override\n            //     public void run() {\n            //         finalThis.chromebookConnectionHandler();\n            //     }\n            // }, 5000);\n        }\n    }\n\n    private void shutdownBluetooth() {\n        try {\n            mContext.unregisterReceiver(mBluetoothBroadcast);\n        } catch (Exception e) {\n            // We may not have registered, that's okay\n        }\n    }\n\n    // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.\n    // This function provides a sort of dummy version of that, watching for changes in the\n    // connected devices and attempting to add controllers as things change.\n    public void chromebookConnectionHandler() {\n        if (!mIsChromebook) {\n            return;\n        }\n\n        ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();\n        ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();\n\n        List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);\n\n        for (BluetoothDevice bluetoothDevice : currentConnected) {\n            if (!mLastBluetoothDevices.contains(bluetoothDevice)) {\n                connected.add(bluetoothDevice);\n            }\n        }\n        for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {\n            if (!currentConnected.contains(bluetoothDevice)) {\n                disconnected.add(bluetoothDevice);\n            }\n        }\n\n        mLastBluetoothDevices = currentConnected;\n\n        for (BluetoothDevice bluetoothDevice : disconnected) {\n            disconnectBluetoothDevice(bluetoothDevice);\n        }\n        for (BluetoothDevice bluetoothDevice : connected) {\n            connectBluetoothDevice(bluetoothDevice);\n        }\n\n        final HIDDeviceManager finalThis = this;\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                finalThis.chromebookConnectionHandler();\n            }\n        }, 10000);\n    }\n\n    public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {\n        Log.v(TAG, \"connectBluetoothDevice device=\" + bluetoothDevice);\n        synchronized (this) {\n            if (mBluetoothDevices.containsKey(bluetoothDevice)) {\n                Log.v(TAG, \"Steam controller with address \" + bluetoothDevice + \" already exists, attempting reconnect\");\n\n                HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);\n                device.reconnect();\n\n                return false;\n            }\n            HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);\n            int id = device.getId();\n            mBluetoothDevices.put(bluetoothDevice, device);\n            mDevicesById.put(id, device);\n\n            // The Steam Controller will mark itself connected once initialization is complete\n        }\n        return true;\n    }\n\n    public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {\n        synchronized (this) {\n            HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);\n            if (device == null)\n                return;\n\n            int id = device.getId();\n            mBluetoothDevices.remove(bluetoothDevice);\n            mDevicesById.remove(id);\n            device.shutdown();\n            HIDDeviceDisconnected(id);\n        }\n    }\n\n    public boolean isSteamController(BluetoothDevice bluetoothDevice) {\n        // Sanity check.  If you pass in a null device, by definition it is never a Steam Controller.\n        if (bluetoothDevice == null) {\n            return false;\n        }\n\n        // If the device has no local name, we really don't want to try an equality check against it.\n        if (bluetoothDevice.getName() == null) {\n            return false;\n        }\n\n        return bluetoothDevice.getName().equals(\"SteamController\") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);\n    }\n\n    private void close() {\n        shutdownUSB();\n        shutdownBluetooth();\n        synchronized (this) {\n            for (HIDDevice device : mDevicesById.values()) {\n                device.shutdown();\n            }\n            mDevicesById.clear();\n            mBluetoothDevices.clear();\n            HIDDeviceReleaseCallback();\n        }\n    }\n\n    public void setFrozen(boolean frozen) {\n        synchronized (this) {\n            for (HIDDevice device : mDevicesById.values()) {\n                device.setFrozen(frozen);\n            }\n        }\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private HIDDevice getDevice(int id) {\n        synchronized (this) {\n            HIDDevice result = mDevicesById.get(id);\n            if (result == null) {\n                Log.v(TAG, \"No device for id: \" + id);\n                Log.v(TAG, \"Available devices: \" + mDevicesById.keySet());\n            }\n            return result;\n        }\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    ////////// JNI interface functions\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    public boolean initialize(boolean usb, boolean bluetooth) {\n        Log.v(TAG, \"initialize(\" + usb + \", \" + bluetooth + \")\");\n\n        if (usb) {\n            initializeUSB();\n        }\n        if (bluetooth) {\n            initializeBluetooth();\n        }\n        return true;\n    }\n\n    public boolean openDevice(int deviceID) {\n        Log.v(TAG, \"openDevice deviceID=\" + deviceID);\n        HIDDevice device = getDevice(deviceID);\n        if (device == null) {\n            HIDDeviceDisconnected(deviceID);\n            return false;\n        }\n\n        // Look to see if this is a USB device and we have permission to access it\n        UsbDevice usbDevice = device.getDevice();\n        if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {\n            HIDDeviceOpenPending(deviceID);\n            try {\n                final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31\n                int flags;\n                if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {\n                    flags = FLAG_MUTABLE;\n                } else {\n                    flags = 0;\n                }\n                if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {\n                   Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);\n                   intent.setPackage(mContext.getPackageName());\n                   mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));\n               } else {\n                   mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));\n               }\n            } catch (Exception e) {\n                Log.v(TAG, \"Couldn't request permission for USB device \" + usbDevice);\n                HIDDeviceOpenResult(deviceID, false);\n            }\n            return false;\n        }\n\n        try {\n            return device.open();\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return false;\n    }\n\n    public int sendOutputReport(int deviceID, byte[] report) {\n        try {\n            //Log.v(TAG, \"sendOutputReport deviceID=\" + deviceID + \" length=\" + report.length);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return -1;\n            }\n\n            return device.sendOutputReport(report);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return -1;\n    }\n\n    public int sendFeatureReport(int deviceID, byte[] report) {\n        try {\n            //Log.v(TAG, \"sendFeatureReport deviceID=\" + deviceID + \" length=\" + report.length);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return -1;\n            }\n\n            return device.sendFeatureReport(report);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return -1;\n    }\n\n    public boolean getFeatureReport(int deviceID, byte[] report) {\n        try {\n            //Log.v(TAG, \"getFeatureReport deviceID=\" + deviceID);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return false;\n            }\n\n            return device.getFeatureReport(report);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return false;\n    }\n\n    public void closeDevice(int deviceID) {\n        try {\n            Log.v(TAG, \"closeDevice deviceID=\" + deviceID);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return;\n            }\n\n            device.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n    }\n\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    /////////////// Native methods\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private native void HIDDeviceRegisterCallback();\n    private native void HIDDeviceReleaseCallback();\n\n    native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);\n    native void HIDDeviceOpenPending(int deviceID);\n    native void HIDDeviceOpenResult(int deviceID, boolean opened);\n    native void HIDDeviceDisconnected(int deviceID);\n\n    native void HIDDeviceInputReport(int deviceID, byte[] report);\n    native void HIDDeviceFeatureReport(int deviceID, byte[] report);\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/org/libsdl/app/HIDDeviceUSB.java",
    "content": "package org.libsdl.app;\n\nimport android.hardware.usb.*;\nimport android.os.Build;\nimport android.util.Log;\nimport java.util.Arrays;\n\nclass HIDDeviceUSB implements HIDDevice {\n\n    private static final String TAG = \"hidapi\";\n\n    protected HIDDeviceManager mManager;\n    protected UsbDevice mDevice;\n    protected int mInterfaceIndex;\n    protected int mInterface;\n    protected int mDeviceId;\n    protected UsbDeviceConnection mConnection;\n    protected UsbEndpoint mInputEndpoint;\n    protected UsbEndpoint mOutputEndpoint;\n    protected InputThread mInputThread;\n    protected boolean mRunning;\n    protected boolean mFrozen;\n\n    public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {\n        mManager = manager;\n        mDevice = usbDevice;\n        mInterfaceIndex = interface_index;\n        mInterface = mDevice.getInterface(mInterfaceIndex).getId();\n        mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());\n        mRunning = false;\n    }\n\n    public String getIdentifier() {\n        return String.format(\"%s/%x/%x/%d\", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);\n    }\n\n    @Override\n    public int getId() {\n        return mDeviceId;\n    }\n\n    @Override\n    public int getVendorId() {\n        return mDevice.getVendorId();\n    }\n\n    @Override\n    public int getProductId() {\n        return mDevice.getProductId();\n    }\n\n    @Override\n    public String getSerialNumber() {\n        String result = null;\n        if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n            try {\n                result = mDevice.getSerialNumber();\n            }\n            catch (SecurityException exception) {\n                //Log.w(TAG, \"App permissions mean we cannot get serial number for device \" + getDeviceName() + \" message: \" + exception.getMessage());\n            }\n        }\n        if (result == null) {\n            result = \"\";\n        }\n        return result;\n    }\n\n    @Override\n    public int getVersion() {\n        return 0;\n    }\n\n    @Override\n    public String getManufacturerName() {\n        String result = null;\n        if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n            result = mDevice.getManufacturerName();\n        }\n        if (result == null) {\n            result = String.format(\"%x\", getVendorId());\n        }\n        return result;\n    }\n\n    @Override\n    public String getProductName() {\n        String result = null;\n        if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n            result = mDevice.getProductName();\n        }\n        if (result == null) {\n            result = String.format(\"%x\", getProductId());\n        }\n        return result;\n    }\n\n    @Override\n    public UsbDevice getDevice() {\n        return mDevice;\n    }\n\n    public String getDeviceName() {\n        return getManufacturerName() + \" \" + getProductName() + \"(0x\" + String.format(\"%x\", getVendorId()) + \"/0x\" + String.format(\"%x\", getProductId()) + \")\";\n    }\n\n    @Override\n    public boolean open() {\n        mConnection = mManager.getUSBManager().openDevice(mDevice);\n        if (mConnection == null) {\n            Log.w(TAG, \"Unable to open USB device \" + getDeviceName());\n            return false;\n        }\n\n        // Force claim our interface\n        UsbInterface iface = mDevice.getInterface(mInterfaceIndex);\n        if (!mConnection.claimInterface(iface, true)) {\n            Log.w(TAG, \"Failed to claim interfaces on USB device \" + getDeviceName());\n            close();\n            return false;\n        }\n\n        // Find the endpoints\n        for (int j = 0; j < iface.getEndpointCount(); j++) {\n            UsbEndpoint endpt = iface.getEndpoint(j);\n            switch (endpt.getDirection()) {\n            case UsbConstants.USB_DIR_IN:\n                if (mInputEndpoint == null) {\n                    mInputEndpoint = endpt;\n                }\n                break;\n            case UsbConstants.USB_DIR_OUT:\n                if (mOutputEndpoint == null) {\n                    mOutputEndpoint = endpt;\n                }\n                break;\n            }\n        }\n\n        // Make sure the required endpoints were present\n        if (mInputEndpoint == null || mOutputEndpoint == null) {\n            Log.w(TAG, \"Missing required endpoint on USB device \" + getDeviceName());\n            close();\n            return false;\n        }\n\n        // Start listening for input\n        mRunning = true;\n        mInputThread = new InputThread();\n        mInputThread.start();\n\n        return true;\n    }\n\n    @Override\n    public int sendFeatureReport(byte[] report) {\n        int res = -1;\n        int offset = 0;\n        int length = report.length;\n        boolean skipped_report_id = false;\n        byte report_number = report[0];\n\n        if (report_number == 0x0) {\n            ++offset;\n            --length;\n            skipped_report_id = true;\n        }\n\n        res = mConnection.controlTransfer(\n            UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,\n            0x09/*HID set_report*/,\n            (3/*HID feature*/ << 8) | report_number,\n            mInterface,\n            report, offset, length,\n            1000/*timeout millis*/);\n\n        if (res < 0) {\n            Log.w(TAG, \"sendFeatureReport() returned \" + res + \" on device \" + getDeviceName());\n            return -1;\n        }\n\n        if (skipped_report_id) {\n            ++length;\n        }\n        return length;\n    }\n\n    @Override\n    public int sendOutputReport(byte[] report) {\n        int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);\n        if (r != report.length) {\n            Log.w(TAG, \"sendOutputReport() returned \" + r + \" on device \" + getDeviceName());\n        }\n        return r;\n    }\n\n    @Override\n    public boolean getFeatureReport(byte[] report) {\n        int res = -1;\n        int offset = 0;\n        int length = report.length;\n        boolean skipped_report_id = false;\n        byte report_number = report[0];\n\n        if (report_number == 0x0) {\n            /* Offset the return buffer by 1, so that the report ID\n               will remain in byte 0. */\n            ++offset;\n            --length;\n            skipped_report_id = true;\n        }\n\n        res = mConnection.controlTransfer(\n            UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,\n            0x01/*HID get_report*/,\n            (3/*HID feature*/ << 8) | report_number,\n            mInterface,\n            report, offset, length,\n            1000/*timeout millis*/);\n\n        if (res < 0) {\n            Log.w(TAG, \"getFeatureReport() returned \" + res + \" on device \" + getDeviceName());\n            return false;\n        }\n\n        if (skipped_report_id) {\n            ++res;\n            ++length;\n        }\n\n        byte[] data;\n        if (res == length) {\n            data = report;\n        } else {\n            data = Arrays.copyOfRange(report, 0, res);\n        }\n        mManager.HIDDeviceFeatureReport(mDeviceId, data);\n\n        return true;\n    }\n\n    @Override\n    public void close() {\n        mRunning = false;\n        if (mInputThread != null) {\n            while (mInputThread.isAlive()) {\n                mInputThread.interrupt();\n                try {\n                    mInputThread.join();\n                } catch (InterruptedException e) {\n                    // Keep trying until we're done\n                }\n            }\n            mInputThread = null;\n        }\n        if (mConnection != null) {\n            UsbInterface iface = mDevice.getInterface(mInterfaceIndex);\n            mConnection.releaseInterface(iface);\n            mConnection.close();\n            mConnection = null;\n        }\n    }\n\n    @Override\n    public void shutdown() {\n        close();\n        mManager = null;\n    }\n\n    @Override\n    public void setFrozen(boolean frozen) {\n        mFrozen = frozen;\n    }\n\n    protected class InputThread extends Thread {\n        @Override\n        public void run() {\n            int packetSize = mInputEndpoint.getMaxPacketSize();\n            byte[] packet = new byte[packetSize];\n            while (mRunning) {\n                int r;\n                try\n                {\n                    r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);\n                }\n                catch (Exception e)\n                {\n                    Log.v(TAG, \"Exception in UsbDeviceConnection bulktransfer: \" + e);\n                    break;\n                }\n                if (r < 0) {\n                    // Could be a timeout or an I/O error\n                }\n                if (r > 0) {\n                    byte[] data;\n                    if (r == packetSize) {\n                        data = packet;\n                    } else {\n                        data = Arrays.copyOfRange(packet, 0, r);\n                    }\n\n                    if (!mFrozen) {\n                        mManager.HIDDeviceInputReport(mDeviceId, data);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/org/libsdl/app/SDL.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\n\nimport java.lang.Class;\nimport java.lang.reflect.Method;\n\n/**\n    SDL library initialization\n*/\npublic class SDL {\n\n    // This function should be called first and sets up the native code\n    // so it can call into the Java classes\n    public static void setupJNI() {\n        SDLActivity.nativeSetupJNI();\n        SDLAudioManager.nativeSetupJNI();\n        SDLControllerManager.nativeSetupJNI();\n    }\n\n    // This function should be called each time the activity is started\n    public static void initialize() {\n        setContext(null);\n\n        SDLActivity.initialize();\n        SDLAudioManager.initialize();\n        SDLControllerManager.initialize();\n    }\n\n    // This function stores the current activity (SDL or not)\n    public static void setContext(Context context) {\n        SDLAudioManager.setContext(context);\n        mContext = context;\n    }\n\n    public static Context getContext() {\n        return mContext;\n    }\n\n    public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {\n        loadLibrary(libraryName, mContext);\n    }\n\n    public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {\n\n        if (libraryName == null) {\n            throw new NullPointerException(\"No library name provided.\");\n        }\n\n        try {\n            // Let's see if we have ReLinker available in the project.  This is necessary for \n            // some projects that have huge numbers of local libraries bundled, and thus may \n            // trip a bug in Android's native library loader which ReLinker works around.  (If\n            // loadLibrary works properly, ReLinker will simply use the normal Android method\n            // internally.)\n            //\n            // To use ReLinker, just add it as a dependency.  For more information, see \n            // https://github.com/KeepSafe/ReLinker for ReLinker's repository.\n            //\n            Class<?> relinkClass = context.getClassLoader().loadClass(\"com.getkeepsafe.relinker.ReLinker\");\n            Class<?> relinkListenerClass = context.getClassLoader().loadClass(\"com.getkeepsafe.relinker.ReLinker$LoadListener\");\n            Class<?> contextClass = context.getClassLoader().loadClass(\"android.content.Context\");\n            Class<?> stringClass = context.getClassLoader().loadClass(\"java.lang.String\");\n\n            // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if \n            // they've changed during updates.\n            Method forceMethod = relinkClass.getDeclaredMethod(\"force\");\n            Object relinkInstance = forceMethod.invoke(null);\n            Class<?> relinkInstanceClass = relinkInstance.getClass();\n\n            // Actually load the library!\n            Method loadMethod = relinkInstanceClass.getDeclaredMethod(\"loadLibrary\", contextClass, stringClass, stringClass, relinkListenerClass);\n            loadMethod.invoke(relinkInstance, context, libraryName, null, null);\n        }\n        catch (final Throwable e) {\n            // Fall back\n            try {\n                System.loadLibrary(libraryName);\n            }\n            catch (final UnsatisfiedLinkError ule) {\n                throw ule;\n            }\n            catch (final SecurityException se) {\n                throw se;\n            }\n        }\n    }\n\n    protected static Context mContext;\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/org/libsdl/app/SDLActivity.java",
    "content": "package org.libsdl.app;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.app.UiModeManager;\nimport android.content.ClipboardManager;\nimport android.content.ClipData;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.Configuration;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.PorterDuff;\nimport android.graphics.drawable.Drawable;\nimport android.hardware.Sensor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.text.Editable;\nimport android.text.InputType;\nimport android.text.Selection;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.util.SparseArray;\nimport android.view.Display;\nimport android.view.Gravity;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.PointerIcon;\nimport android.view.Surface;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.view.inputmethod.BaseInputConnection;\nimport android.view.inputmethod.EditorInfo;\nimport android.view.inputmethod.InputConnection;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport java.util.Hashtable;\nimport java.util.Locale;\n\n\n/**\n    SDL Activity\n*/\npublic class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener {\n    private static final String TAG = \"SDL\";\n    private static final int SDL_MAJOR_VERSION = 2;\n    private static final int SDL_MINOR_VERSION = 32;\n    private static final int SDL_MICRO_VERSION = 10;\n/*\n    // Display InputType.SOURCE/CLASS of events and devices\n    //\n    // SDLActivity.debugSource(device.getSources(), \"device[\" + device.getName() + \"]\");\n    // SDLActivity.debugSource(event.getSource(), \"event\");\n    public static void debugSource(int sources, String prefix) {\n        int s = sources;\n        int s_copy = sources;\n        String cls = \"\";\n        String src = \"\";\n        int tst = 0;\n        int FLAG_TAINTED = 0x80000000;\n\n        if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0)     cls += \" BUTTON\";\n        if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0)   cls += \" JOYSTICK\";\n        if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0)    cls += \" POINTER\";\n        if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0)   cls += \" POSITION\";\n        if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0)  cls += \" TRACKBALL\";\n\n\n        int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits\n        s2 &= ~(  InputDevice.SOURCE_CLASS_BUTTON\n                | InputDevice.SOURCE_CLASS_JOYSTICK\n                | InputDevice.SOURCE_CLASS_POINTER\n                | InputDevice.SOURCE_CLASS_POSITION\n                | InputDevice.SOURCE_CLASS_TRACKBALL);\n\n        if (s2 != 0) cls += \"Some_Unknown\";\n\n        s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;\n\n        if (Build.VERSION.SDK_INT >= 23) {\n            tst = InputDevice.SOURCE_BLUETOOTH_STYLUS;\n            if ((s & tst) == tst) src += \" BLUETOOTH_STYLUS\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_DPAD;\n        if ((s & tst) == tst) src += \" DPAD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_GAMEPAD;\n        if ((s & tst) == tst) src += \" GAMEPAD\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 21) {\n            tst = InputDevice.SOURCE_HDMI;\n            if ((s & tst) == tst) src += \" HDMI\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_JOYSTICK;\n        if ((s & tst) == tst) src += \" JOYSTICK\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_KEYBOARD;\n        if ((s & tst) == tst) src += \" KEYBOARD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_MOUSE;\n        if ((s & tst) == tst) src += \" MOUSE\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 26) {\n            tst = InputDevice.SOURCE_MOUSE_RELATIVE;\n            if ((s & tst) == tst) src += \" MOUSE_RELATIVE\";\n            s2 &= ~tst;\n\n            tst = InputDevice.SOURCE_ROTARY_ENCODER;\n            if ((s & tst) == tst) src += \" ROTARY_ENCODER\";\n            s2 &= ~tst;\n        }\n        tst = InputDevice.SOURCE_STYLUS;\n        if ((s & tst) == tst) src += \" STYLUS\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TOUCHPAD;\n        if ((s & tst) == tst) src += \" TOUCHPAD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TOUCHSCREEN;\n        if ((s & tst) == tst) src += \" TOUCHSCREEN\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 18) {\n            tst = InputDevice.SOURCE_TOUCH_NAVIGATION;\n            if ((s & tst) == tst) src += \" TOUCH_NAVIGATION\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_TRACKBALL;\n        if ((s & tst) == tst) src += \" TRACKBALL\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_ANY;\n        if ((s & tst) == tst) src += \" ANY\";\n        s2 &= ~tst;\n\n        if (s == FLAG_TAINTED) src += \" FLAG_TAINTED\";\n        s2 &= ~FLAG_TAINTED;\n\n        if (s2 != 0) src += \" Some_Unknown\";\n\n        Log.v(TAG, prefix + \"int=\" + s_copy + \" CLASS={\" + cls + \" } source(s):\" + src);\n    }\n*/\n\n    public static boolean mIsResumedCalled, mHasFocus;\n    public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24  /* Android 7.0 (N) */);\n\n    // Cursor types\n    // private static final int SDL_SYSTEM_CURSOR_NONE = -1;\n    private static final int SDL_SYSTEM_CURSOR_ARROW = 0;\n    private static final int SDL_SYSTEM_CURSOR_IBEAM = 1;\n    private static final int SDL_SYSTEM_CURSOR_WAIT = 2;\n    private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3;\n    private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4;\n    private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5;\n    private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6;\n    private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7;\n    private static final int SDL_SYSTEM_CURSOR_SIZENS = 8;\n    private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9;\n    private static final int SDL_SYSTEM_CURSOR_NO = 10;\n    private static final int SDL_SYSTEM_CURSOR_HAND = 11;\n\n    protected static final int SDL_ORIENTATION_UNKNOWN = 0;\n    protected static final int SDL_ORIENTATION_LANDSCAPE = 1;\n    protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2;\n    protected static final int SDL_ORIENTATION_PORTRAIT = 3;\n    protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4;\n\n    protected static int mCurrentOrientation;\n    protected static Locale mCurrentLocale;\n\n    // Handle the state of the native layer\n    public enum NativeState {\n           INIT, RESUMED, PAUSED\n    }\n\n    public static NativeState mNextNativeState;\n    public static NativeState mCurrentNativeState;\n\n    /** If shared libraries (e.g. SDL or the native application) could not be loaded. */\n    public static boolean mBrokenLibraries = true;\n\n    // Main components\n    protected static SDLActivity mSingleton;\n    protected static SDLSurface mSurface;\n    protected static DummyEdit mTextEdit;\n    protected static boolean mScreenKeyboardShown;\n    protected static ViewGroup mLayout;\n    protected static SDLClipboardHandler mClipboardHandler;\n    protected static Hashtable<Integer, PointerIcon> mCursors;\n    protected static int mLastCursorID;\n    protected static SDLGenericMotionListener_API12 mMotionListener;\n    protected static HIDDeviceManager mHIDDeviceManager;\n\n    // This is what SDL runs in. It invokes SDL_main(), eventually\n    protected static Thread mSDLThread;\n\n    protected static SDLGenericMotionListener_API12 getMotionListener() {\n        if (mMotionListener == null) {\n            if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {\n                mMotionListener = new SDLGenericMotionListener_API26();\n            } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n                mMotionListener = new SDLGenericMotionListener_API24();\n            } else {\n                mMotionListener = new SDLGenericMotionListener_API12();\n            }\n        }\n\n        return mMotionListener;\n    }\n\n    /**\n     * This method returns the name of the shared object with the application entry point\n     * It can be overridden by derived classes.\n     */\n    protected String getMainSharedObject() {\n        String library;\n        String[] libraries = SDLActivity.mSingleton.getLibraries();\n        if (libraries.length > 0) {\n            library = \"lib\" + libraries[libraries.length - 1] + \".so\";\n        } else {\n            library = \"libmain.so\";\n        }\n        return getContext().getApplicationInfo().nativeLibraryDir + \"/\" + library;\n    }\n\n    /**\n     * This method returns the name of the application entry point\n     * It can be overridden by derived classes.\n     */\n    protected String getMainFunction() {\n        return \"SDL_main\";\n    }\n\n    /**\n     * This method is called by SDL before loading the native shared libraries.\n     * It can be overridden to provide names of shared libraries to be loaded.\n     * The default implementation returns the defaults. It never returns null.\n     * An array returned by a new implementation must at least contain \"SDL2\".\n     * Also keep in mind that the order the libraries are loaded may matter.\n     * @return names of shared libraries to be loaded (e.g. \"SDL2\", \"main\").\n     */\n    protected String[] getLibraries() {\n        return new String[] {\n            \"SDL2\",\n            // \"SDL2_image\",\n            // \"SDL2_mixer\",\n            // \"SDL2_net\",\n            // \"SDL2_ttf\",\n            \"main\"\n        };\n    }\n\n    // Load the .so\n    public void loadLibraries() {\n       for (String lib : getLibraries()) {\n          SDL.loadLibrary(lib, this);\n       }\n    }\n\n    /**\n     * This method is called by SDL before starting the native application thread.\n     * It can be overridden to provide the arguments after the application name.\n     * The default implementation returns an empty array. It never returns null.\n     * @return arguments for the native application.\n     */\n    protected String[] getArguments() {\n        return new String[0];\n    }\n\n    public static void initialize() {\n        // The static nature of the singleton and Android quirkyness force us to initialize everything here\n        // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values\n        mSingleton = null;\n        mSurface = null;\n        mTextEdit = null;\n        mLayout = null;\n        mClipboardHandler = null;\n        mCursors = new Hashtable<Integer, PointerIcon>();\n        mLastCursorID = 0;\n        mSDLThread = null;\n        mIsResumedCalled = false;\n        mHasFocus = true;\n        mNextNativeState = NativeState.INIT;\n        mCurrentNativeState = NativeState.INIT;\n    }\n    \n    protected SDLSurface createSDLSurface(Context context) {\n        return new SDLSurface(context);\n    }\n\n    // Setup\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        Log.v(TAG, \"Device: \" + Build.DEVICE);\n        Log.v(TAG, \"Model: \" + Build.MODEL);\n        Log.v(TAG, \"onCreate()\");\n        super.onCreate(savedInstanceState);\n\n        try {\n            Thread.currentThread().setName(\"SDLActivity\");\n        } catch (Exception e) {\n            Log.v(TAG, \"modify thread properties failed \" + e.toString());\n        }\n\n        // Load shared libraries\n        String errorMsgBrokenLib = \"\";\n        try {\n            loadLibraries();\n            mBrokenLibraries = false; /* success */\n        } catch(UnsatisfiedLinkError e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        } catch(Exception e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        }\n\n        if (!mBrokenLibraries) {\n            String expected_version = String.valueOf(SDL_MAJOR_VERSION) + \".\" +\n                                      String.valueOf(SDL_MINOR_VERSION) + \".\" +\n                                      String.valueOf(SDL_MICRO_VERSION);\n            String version = nativeGetVersion();\n            if (!version.equals(expected_version)) {\n                mBrokenLibraries = true;\n                errorMsgBrokenLib = \"SDL C/Java version mismatch (expected \" + expected_version + \", got \" + version + \")\";\n            }\n        }\n\n        if (mBrokenLibraries) {\n            mSingleton = this;\n            AlertDialog.Builder dlgAlert  = new AlertDialog.Builder(this);\n            dlgAlert.setMessage(\"An error occurred while trying to start the application. Please try again and/or reinstall.\"\n                  + System.getProperty(\"line.separator\")\n                  + System.getProperty(\"line.separator\")\n                  + \"Error: \" + errorMsgBrokenLib);\n            dlgAlert.setTitle(\"SDL Error\");\n            dlgAlert.setPositiveButton(\"Exit\",\n                new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog,int id) {\n                        // if this button is clicked, close current activity\n                        SDLActivity.mSingleton.finish();\n                    }\n                });\n           dlgAlert.setCancelable(false);\n           dlgAlert.create().show();\n\n           return;\n        }\n\n        // Set up JNI\n        SDL.setupJNI();\n\n        // Initialize state\n        SDL.initialize();\n\n        // So we can call stuff from static callbacks\n        mSingleton = this;\n        SDL.setContext(this);\n\n        mClipboardHandler = new SDLClipboardHandler();\n\n        mHIDDeviceManager = HIDDeviceManager.acquire(this);\n\n        // Set up the surface\n        mSurface = createSDLSurface(this);\n\n        mLayout = new RelativeLayout(this);\n        mLayout.addView(mSurface);\n\n        // Get our current screen orientation and pass it down.\n        mCurrentOrientation = SDLActivity.getCurrentOrientation();\n        // Only record current orientation\n        SDLActivity.onNativeOrientationChanged(mCurrentOrientation);\n\n        try {\n            if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n                mCurrentLocale = getContext().getResources().getConfiguration().locale;\n            } else {\n                mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0);\n            }\n        } catch(Exception ignored) {\n        }\n\n        setContentView(mLayout);\n\n        setWindowStyle(false);\n\n        getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);\n\n        // Get filename from \"Open with\" of another application\n        Intent intent = getIntent();\n        if (intent != null && intent.getData() != null) {\n            String filename = intent.getData().getPath();\n            if (filename != null) {\n                Log.v(TAG, \"Got filename: \" + filename);\n                SDLActivity.onNativeDropFile(filename);\n            }\n        }\n    }\n\n    protected void pauseNativeThread() {\n        mNextNativeState = NativeState.PAUSED;\n        mIsResumedCalled = false;\n\n        if (SDLActivity.mBrokenLibraries) {\n            return;\n        }\n\n        SDLActivity.handleNativeState();\n    }\n\n    protected void resumeNativeThread() {\n        mNextNativeState = NativeState.RESUMED;\n        mIsResumedCalled = true;\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        SDLActivity.handleNativeState();\n    }\n\n    // Events\n    @Override\n    protected void onPause() {\n        Log.v(TAG, \"onPause()\");\n        super.onPause();\n\n        if (mHIDDeviceManager != null) {\n            mHIDDeviceManager.setFrozen(true);\n        }\n        if (!mHasMultiWindow) {\n            pauseNativeThread();\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        Log.v(TAG, \"onResume()\");\n        super.onResume();\n\n        if (mHIDDeviceManager != null) {\n            mHIDDeviceManager.setFrozen(false);\n        }\n        if (!mHasMultiWindow) {\n            resumeNativeThread();\n        }\n    }\n\n    @Override\n    protected void onStop() {\n        Log.v(TAG, \"onStop()\");\n        super.onStop();\n        if (mHasMultiWindow) {\n            pauseNativeThread();\n        }\n    }\n\n    @Override\n    protected void onStart() {\n        Log.v(TAG, \"onStart()\");\n        super.onStart();\n        if (mHasMultiWindow) {\n            resumeNativeThread();\n        }\n    }\n\n    public static int getCurrentOrientation() {\n        int result = SDL_ORIENTATION_UNKNOWN;\n\n        Activity activity = (Activity)getContext();\n        if (activity == null) {\n            return result;\n        }\n        Display display = activity.getWindowManager().getDefaultDisplay();\n\n        switch (display.getRotation()) {\n            case Surface.ROTATION_0:\n                result = SDL_ORIENTATION_PORTRAIT;\n                break;\n\n            case Surface.ROTATION_90:\n                result = SDL_ORIENTATION_LANDSCAPE;\n                break;\n\n            case Surface.ROTATION_180:\n                result = SDL_ORIENTATION_PORTRAIT_FLIPPED;\n                break;\n\n            case Surface.ROTATION_270:\n                result = SDL_ORIENTATION_LANDSCAPE_FLIPPED;\n                break;\n        }\n\n        return result;\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        super.onWindowFocusChanged(hasFocus);\n        Log.v(TAG, \"onWindowFocusChanged(): \" + hasFocus);\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        mHasFocus = hasFocus;\n        if (hasFocus) {\n           mNextNativeState = NativeState.RESUMED;\n           SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded();\n\n           SDLActivity.handleNativeState();\n           nativeFocusChanged(true);\n\n        } else {\n           nativeFocusChanged(false);\n           if (!mHasMultiWindow) {\n               mNextNativeState = NativeState.PAUSED;\n               SDLActivity.handleNativeState();\n           }\n        }\n    }\n\n    @Override\n    public void onLowMemory() {\n        Log.v(TAG, \"onLowMemory()\");\n        super.onLowMemory();\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        SDLActivity.nativeLowMemory();\n    }\n\n    @Override\n    public void onConfigurationChanged(Configuration newConfig) {\n        Log.v(TAG, \"onConfigurationChanged()\");\n        super.onConfigurationChanged(newConfig);\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) {\n            mCurrentLocale = newConfig.locale;\n            SDLActivity.onNativeLocaleChanged();\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        Log.v(TAG, \"onDestroy()\");\n\n        if (mHIDDeviceManager != null) {\n            HIDDeviceManager.release(mHIDDeviceManager);\n            mHIDDeviceManager = null;\n        }\n\n        SDLAudioManager.release(this);\n\n        if (SDLActivity.mBrokenLibraries) {\n           super.onDestroy();\n           return;\n        }\n\n        if (SDLActivity.mSDLThread != null) {\n\n            // Send Quit event to \"SDLThread\" thread\n            SDLActivity.nativeSendQuit();\n\n            // Wait for \"SDLThread\" thread to end\n            try {\n                SDLActivity.mSDLThread.join();\n            } catch(Exception e) {\n                Log.v(TAG, \"Problem stopping SDLThread: \" + e);\n            }\n        }\n\n        SDLActivity.nativeQuit();\n\n        super.onDestroy();\n    }\n\n    @Override\n    public void onBackPressed() {\n        // Check if we want to block the back button in case of mouse right click.\n        //\n        // If we do, the normal hardware back button will no longer work and people have to use home,\n        // but the mouse right click will work.\n        //\n        boolean trapBack = SDLActivity.nativeGetHintBoolean(\"SDL_ANDROID_TRAP_BACK_BUTTON\", false);\n        if (trapBack) {\n            // Exit and let the mouse handler handle this button (if appropriate)\n            return;\n        }\n\n        // Default system back button behavior.\n        if (!isFinishing()) {\n            super.onBackPressed();\n        }\n    }\n\n    // Called by JNI from SDL.\n    public static void manualBackButton() {\n        mSingleton.pressBackButton();\n    }\n\n    // Used to get us onto the activity's main thread\n    public void pressBackButton() {\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                if (!SDLActivity.this.isFinishing()) {\n                    SDLActivity.this.superOnBackPressed();\n                }\n            }\n        });\n    }\n\n    // Used to access the system back behavior.\n    public void superOnBackPressed() {\n        super.onBackPressed();\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(KeyEvent event) {\n\n        if (SDLActivity.mBrokenLibraries) {\n           return false;\n        }\n\n        int keyCode = event.getKeyCode();\n        // Ignore certain special keys so they're handled by Android\n        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||\n            keyCode == KeyEvent.KEYCODE_VOLUME_UP ||\n            keyCode == KeyEvent.KEYCODE_CAMERA ||\n            keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */\n            keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */\n            ) {\n            return false;\n        }\n        return super.dispatchKeyEvent(event);\n    }\n\n    /* Transition to next state */\n    public static void handleNativeState() {\n\n        if (mNextNativeState == mCurrentNativeState) {\n            // Already in same state, discard.\n            return;\n        }\n\n        // Try a transition to init state\n        if (mNextNativeState == NativeState.INIT) {\n\n            mCurrentNativeState = mNextNativeState;\n            return;\n        }\n\n        // Try a transition to paused state\n        if (mNextNativeState == NativeState.PAUSED) {\n            if (mSDLThread != null) {\n                nativePause();\n            }\n            if (mSurface != null) {\n                mSurface.handlePause();\n            }\n            mCurrentNativeState = mNextNativeState;\n            return;\n        }\n\n        // Try a transition to resumed state\n        if (mNextNativeState == NativeState.RESUMED) {\n            if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) {\n                if (mSDLThread == null) {\n                    // This is the entry point to the C app.\n                    // Start up the C app thread and enable sensor input for the first time\n                    // FIXME: Why aren't we enabling sensor input at start?\n\n                    mSDLThread = new Thread(new SDLMain(), \"SDLThread\");\n                    mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);\n                    mSDLThread.start();\n\n                    // No nativeResume(), don't signal Android_ResumeSem\n                } else {\n                    nativeResume();\n                }\n                mSurface.handleResume();\n\n                mCurrentNativeState = mNextNativeState;\n            }\n        }\n    }\n\n    // Messages from the SDLMain thread\n    static final int COMMAND_CHANGE_TITLE = 1;\n    static final int COMMAND_CHANGE_WINDOW_STYLE = 2;\n    static final int COMMAND_TEXTEDIT_HIDE = 3;\n    static final int COMMAND_SET_KEEP_SCREEN_ON = 5;\n\n    protected static final int COMMAND_USER = 0x8000;\n\n    protected static boolean mFullscreenModeActive;\n\n    /**\n     * This method is called by SDL if SDL did not handle a message itself.\n     * This happens if a received message contains an unsupported command.\n     * Method can be overwritten to handle Messages in a different class.\n     * @param command the command of the message.\n     * @param param the parameter of the message. May be null.\n     * @return if the message was handled in overridden method.\n     */\n    protected boolean onUnhandledMessage(int command, Object param) {\n        return false;\n    }\n\n    /**\n     * A Handler class for Messages from native SDL applications.\n     * It uses current Activities as target (e.g. for the title).\n     * static to prevent implicit references to enclosing object.\n     */\n    protected static class SDLCommandHandler extends Handler {\n        @Override\n        public void handleMessage(Message msg) {\n            Context context = SDL.getContext();\n            if (context == null) {\n                Log.e(TAG, \"error handling message, getContext() returned null\");\n                return;\n            }\n            switch (msg.arg1) {\n            case COMMAND_CHANGE_TITLE:\n                if (context instanceof Activity) {\n                    ((Activity) context).setTitle((String)msg.obj);\n                } else {\n                    Log.e(TAG, \"error handling message, getContext() returned no Activity\");\n                }\n                break;\n            case COMMAND_CHANGE_WINDOW_STYLE:\n                if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n                    if (context instanceof Activity) {\n                        Window window = ((Activity) context).getWindow();\n                        if (window != null) {\n                            if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) {\n                                int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |\n                                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |\n                                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |\n                                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |\n                                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |\n                                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;\n                                window.getDecorView().setSystemUiVisibility(flags);\n                                window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n                                window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n                                SDLActivity.mFullscreenModeActive = true;\n                            } else {\n                                int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE;\n                                window.getDecorView().setSystemUiVisibility(flags);\n                                window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n                                window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n                                SDLActivity.mFullscreenModeActive = false;\n                            }\n                            if (Build.VERSION.SDK_INT >= 28 /* Android 9 (Pie) */) {\n                                window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;\n                            }\n                        }\n                    } else {\n                        Log.e(TAG, \"error handling message, getContext() returned no Activity\");\n                    }\n                }\n                break;\n            case COMMAND_TEXTEDIT_HIDE:\n                if (mTextEdit != null) {\n                    // Note: On some devices setting view to GONE creates a flicker in landscape.\n                    // Setting the View's sizes to 0 is similar to GONE but without the flicker.\n                    // The sizes will be set to useful values when the keyboard is shown again.\n                    mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));\n\n                    InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);\n                    imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);\n\n                    mScreenKeyboardShown = false;\n\n                    mSurface.requestFocus();\n                }\n                break;\n            case COMMAND_SET_KEEP_SCREEN_ON:\n            {\n                if (context instanceof Activity) {\n                    Window window = ((Activity) context).getWindow();\n                    if (window != null) {\n                        if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) {\n                            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\n                        } else {\n                            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\n                        }\n                    }\n                }\n                break;\n            }\n            default:\n                if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {\n                    Log.e(TAG, \"error handling message, command is \" + msg.arg1);\n                }\n            }\n        }\n    }\n\n    // Handler for the messages\n    Handler commandHandler = new SDLCommandHandler();\n\n    // Send a message from the SDLMain thread\n    boolean sendCommand(int command, Object data) {\n        Message msg = commandHandler.obtainMessage();\n        msg.arg1 = command;\n        msg.obj = data;\n        boolean result = commandHandler.sendMessage(msg);\n\n        if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n            if (command == COMMAND_CHANGE_WINDOW_STYLE) {\n                // Ensure we don't return until the resize has actually happened,\n                // or 500ms have passed.\n\n                boolean bShouldWait = false;\n\n                if (data instanceof Integer) {\n                    // Let's figure out if we're already laid out fullscreen or not.\n                    Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();\n                    DisplayMetrics realMetrics = new DisplayMetrics();\n                    display.getRealMetrics(realMetrics);\n\n                    boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) &&\n                            (realMetrics.heightPixels == mSurface.getHeight()));\n\n                    if ((Integer) data == 1) {\n                        // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going\n                        // to change size and should wait for surfaceChanged() before we return, so the size\n                        // is right back in native code.  If we're already laid out fullscreen, though, we're\n                        // not going to change size even if we change decor modes, so we shouldn't wait for\n                        // surfaceChanged() -- which may not even happen -- and should return immediately.\n                        bShouldWait = !bFullscreenLayout;\n                    } else {\n                        // If we're laid out fullscreen (even if the status bar and nav bar are present),\n                        // or are actively in fullscreen, we're going to change size and should wait for\n                        // surfaceChanged before we return, so the size is right back in native code.\n                        bShouldWait = bFullscreenLayout;\n                    }\n                }\n\n                if (bShouldWait && (SDLActivity.getContext() != null)) {\n                    // We'll wait for the surfaceChanged() method, which will notify us\n                    // when called.  That way, we know our current size is really the\n                    // size we need, instead of grabbing a size that's still got\n                    // the navigation and/or status bars before they're hidden.\n                    //\n                    // We'll wait for up to half a second, because some devices\n                    // take a surprisingly long time for the surface resize, but\n                    // then we'll just give up and return.\n                    //\n                    synchronized (SDLActivity.getContext()) {\n                        try {\n                            SDLActivity.getContext().wait(500);\n                        } catch (InterruptedException ie) {\n                            ie.printStackTrace();\n                        }\n                    }\n                }\n            }\n        }\n\n        return result;\n    }\n\n    // C functions we call\n    public static native String nativeGetVersion();\n    public static native int nativeSetupJNI();\n    public static native int nativeRunMain(String library, String function, Object arguments);\n    public static native void nativeLowMemory();\n    public static native void nativeSendQuit();\n    public static native void nativeQuit();\n    public static native void nativePause();\n    public static native void nativeResume();\n    public static native void nativeFocusChanged(boolean hasFocus);\n    public static native void onNativeDropFile(String filename);\n    public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate);\n    public static native void onNativeResize();\n    public static native void onNativeKeyDown(int keycode);\n    public static native void onNativeKeyUp(int keycode);\n    public static native boolean onNativeSoftReturnKey();\n    public static native void onNativeKeyboardFocusLost();\n    public static native void onNativeMouse(int button, int action, float x, float y, boolean relative);\n    public static native void onNativeTouch(int touchDevId, int pointerFingerId,\n                                            int action, float x,\n                                            float y, float p);\n    public static native void onNativeAccel(float x, float y, float z);\n    public static native void onNativeClipboardChanged();\n    public static native void onNativeSurfaceCreated();\n    public static native void onNativeSurfaceChanged();\n    public static native void onNativeSurfaceDestroyed();\n    public static native String nativeGetHint(String name);\n    public static native boolean nativeGetHintBoolean(String name, boolean default_value);\n    public static native void nativeSetenv(String name, String value);\n    public static native void onNativeOrientationChanged(int orientation);\n    public static native void nativeAddTouch(int touchId, String name);\n    public static native void nativePermissionResult(int requestCode, boolean result);\n    public static native void onNativeLocaleChanged();\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setActivityTitle(String title) {\n        // Called from SDLMain() thread and can't directly affect the view\n        return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void setWindowStyle(boolean fullscreen) {\n        // Called from SDLMain() thread and can't directly affect the view\n        mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     * This is a static method for JNI convenience, it calls a non-static method\n     * so that is can be overridden\n     */\n    public static void setOrientation(int w, int h, boolean resizable, String hint)\n    {\n        if (mSingleton != null) {\n            mSingleton.setOrientationBis(w, h, resizable, hint);\n        }\n    }\n\n    /**\n     * This can be overridden\n     */\n    public void setOrientationBis(int w, int h, boolean resizable, String hint)\n    {\n        int orientation_landscape = -1;\n        int orientation_portrait = -1;\n\n        /* If set, hint \"explicitly controls which UI orientations are allowed\". */\n        if (hint.contains(\"LandscapeRight\") && hint.contains(\"LandscapeLeft\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;\n        } else if (hint.contains(\"LandscapeLeft\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;\n        } else if (hint.contains(\"LandscapeRight\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;\n        }\n\n        /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */\n        boolean contains_Portrait = hint.contains(\"Portrait \") || hint.endsWith(\"Portrait\");\n\n        if (contains_Portrait && hint.contains(\"PortraitUpsideDown\")) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;\n        } else if (contains_Portrait) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;\n        } else if (hint.contains(\"PortraitUpsideDown\")) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;\n        }\n\n        boolean is_landscape_allowed = (orientation_landscape != -1);\n        boolean is_portrait_allowed = (orientation_portrait != -1);\n        int req; /* Requested orientation */\n\n        /* No valid hint, nothing is explicitly allowed */\n        if (!is_portrait_allowed && !is_landscape_allowed) {\n            if (resizable) {\n                /* All orientations are allowed, respecting user orientation lock setting */\n                req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;\n            } else {\n                /* Fixed window and nothing specified. Get orientation from w/h of created window */\n                req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);\n            }\n        } else {\n            /* At least one orientation is allowed */\n            if (resizable) {\n                if (is_portrait_allowed && is_landscape_allowed) {\n                    /* hint allows both landscape and portrait, promote to full user */\n                    req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;\n                } else {\n                    /* Use the only one allowed \"orientation\" */\n                    req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);\n                }\n            } else {\n                /* Fixed window and both orientations are allowed. Choose one. */\n                if (is_portrait_allowed && is_landscape_allowed) {\n                    req = (w > h ? orientation_landscape : orientation_portrait);\n                } else {\n                    /* Use the only one allowed \"orientation\" */\n                    req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);\n                }\n            }\n        }\n\n        Log.v(TAG, \"setOrientation() requestedOrientation=\" + req + \" width=\" + w +\" height=\"+ h +\" resizable=\" + resizable + \" hint=\" + hint);\n        mSingleton.setRequestedOrientation(req);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void minimizeWindow() {\n\n        if (mSingleton == null) {\n            return;\n        }\n\n        Intent startMain = new Intent(Intent.ACTION_MAIN);\n        startMain.addCategory(Intent.CATEGORY_HOME);\n        startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        mSingleton.startActivity(startMain);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean shouldMinimizeOnFocusLoss() {\n/*\n        if (Build.VERSION.SDK_INT >= 24) {\n            if (mSingleton == null) {\n                return true;\n            }\n\n            if (mSingleton.isInMultiWindowMode()) {\n                return false;\n            }\n\n            if (mSingleton.isInPictureInPictureMode()) {\n                return false;\n            }\n        }\n\n        return true;\n*/\n        return false;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isScreenKeyboardShown()\n    {\n        if (mTextEdit == null) {\n            return false;\n        }\n\n        if (!mScreenKeyboardShown) {\n            return false;\n        }\n\n        InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n        return imm.isAcceptingText();\n\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean supportsRelativeMouse()\n    {\n        // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under\n        // Android 7 APIs, and simply returns no data under Android 8 APIs.\n        //\n        // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and\n        // thus SDK version 27.  If we are in DeX mode and not API 27 or higher, as a result,\n        // we should stick to relative mode.\n        //\n        if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) {\n            return false;\n        }\n\n        return SDLActivity.getMotionListener().supportsRelativeMouse();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setRelativeMouseEnabled(boolean enabled)\n    {\n        if (enabled && !supportsRelativeMouse()) {\n            return false;\n        }\n\n        return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean sendMessage(int command, int param) {\n        if (mSingleton == null) {\n            return false;\n        }\n        return mSingleton.sendCommand(command, param);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static Context getContext() {\n        return SDL.getContext();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isAndroidTV() {\n        UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE);\n        if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"MINIX\") && Build.MODEL.equals(\"NEO-U1\")) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"Amlogic\") && Build.MODEL.equals(\"X96-W\")) {\n            return true;\n        }\n        return Build.MANUFACTURER.equals(\"Amlogic\") && Build.MODEL.startsWith(\"TV\");\n    }\n\n    public static double getDiagonal()\n    {\n        DisplayMetrics metrics = new DisplayMetrics();\n        Activity activity = (Activity)getContext();\n        if (activity == null) {\n            return 0.0;\n        }\n        activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);\n\n        double dWidthInches = metrics.widthPixels / (double)metrics.xdpi;\n        double dHeightInches = metrics.heightPixels / (double)metrics.ydpi;\n\n        return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches));\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isTablet() {\n        // If our diagonal size is seven inches or greater, we consider ourselves a tablet.\n        return (getDiagonal() >= 7.0);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isChromebook() {\n        if (getContext() == null) {\n            return false;\n        }\n        return getContext().getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\");\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isDeXMode() {\n        if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n            return false;\n        }\n        try {\n            final Configuration config = getContext().getResources().getConfiguration();\n            final Class<?> configClass = config.getClass();\n            return configClass.getField(\"SEM_DESKTOP_MODE_ENABLED\").getInt(configClass)\n                    == configClass.getField(\"semDesktopModeEnabled\").getInt(config);\n        } catch(Exception ignored) {\n            return false;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static DisplayMetrics getDisplayDPI() {\n        return getContext().getResources().getDisplayMetrics();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean getManifestEnvironmentVariables() {\n        try {\n            if (getContext() == null) {\n                return false;\n            }\n\n            ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);\n            Bundle bundle = applicationInfo.metaData;\n            if (bundle == null) {\n                return false;\n            }\n            String prefix = \"SDL_ENV.\";\n            final int trimLength = prefix.length();\n            for (String key : bundle.keySet()) {\n                if (key.startsWith(prefix)) {\n                    String name = key.substring(trimLength);\n                    String value = bundle.get(key).toString();\n                    nativeSetenv(name, value);\n                }\n            }\n            /* environment variables set! */\n            return true;\n        } catch (Exception e) {\n           Log.v(TAG, \"exception \" + e.toString());\n        }\n        return false;\n    }\n\n    // This method is called by SDLControllerManager's API 26 Generic Motion Handler.\n    public static View getContentView() {\n        return mLayout;\n    }\n\n    static class ShowTextInputTask implements Runnable {\n        /*\n         * This is used to regulate the pan&scan method to have some offset from\n         * the bottom edge of the input region and the top edge of an input\n         * method (soft keyboard)\n         */\n        static final int HEIGHT_PADDING = 15;\n\n        public int x, y, w, h;\n\n        public ShowTextInputTask(int x, int y, int w, int h) {\n            this.x = x;\n            this.y = y;\n            this.w = w;\n            this.h = h;\n\n            /* Minimum size of 1 pixel, so it takes focus. */\n            if (this.w <= 0) {\n                this.w = 1;\n            }\n            if (this.h + HEIGHT_PADDING <= 0) {\n                this.h = 1 - HEIGHT_PADDING;\n            }\n        }\n\n        @Override\n        public void run() {\n            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);\n            params.leftMargin = x;\n            params.topMargin = y;\n\n            if (mTextEdit == null) {\n                mTextEdit = new DummyEdit(SDL.getContext());\n\n                mLayout.addView(mTextEdit, params);\n            } else {\n                mTextEdit.setLayoutParams(params);\n            }\n\n            mTextEdit.setVisibility(View.VISIBLE);\n            mTextEdit.requestFocus();\n\n            InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n            imm.showSoftInput(mTextEdit, 0);\n\n            mScreenKeyboardShown = true;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean showTextInput(int x, int y, int w, int h) {\n        // Transfer the task to the main thread as a Runnable\n        return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));\n    }\n\n    public static boolean isTextInputEvent(KeyEvent event) {\n\n        // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT\n        if (event.isCtrlPressed()) {\n            return false;\n        }\n\n        return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;\n    }\n\n    public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) {\n        int deviceId = event.getDeviceId();\n        int source = event.getSource();\n\n        if (source == InputDevice.SOURCE_UNKNOWN) {\n            InputDevice device = InputDevice.getDevice(deviceId);\n            if (device != null) {\n                source = device.getSources();\n            }\n        }\n\n//        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n//            Log.v(\"SDL\", \"key down: \" + keyCode + \", deviceId = \" + deviceId + \", source = \" + source);\n//        } else if (event.getAction() == KeyEvent.ACTION_UP) {\n//            Log.v(\"SDL\", \"key up: \" + keyCode + \", deviceId = \" + deviceId + \", source = \" + source);\n//        }\n\n        // Dispatch the different events depending on where they come from\n        // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD\n        // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD\n        //\n        // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and\n        // SOURCE_JOYSTICK, while its key events arrive from the keyboard source\n        // So, retrieve the device itself and check all of its sources\n        if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) {\n            // Note that we process events with specific key codes here\n            if (event.getAction() == KeyEvent.ACTION_DOWN) {\n                if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) {\n                    return true;\n                }\n            } else if (event.getAction() == KeyEvent.ACTION_UP) {\n                if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) {\n                    return true;\n                }\n            }\n        }\n\n        if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {\n            // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses\n            // they are ignored here because sending them as mouse input to SDL is messy\n            if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {\n                switch (event.getAction()) {\n                case KeyEvent.ACTION_DOWN:\n                case KeyEvent.ACTION_UP:\n                    // mark the event as handled or it will be handled by system\n                    // handling KEYCODE_BACK by system will call onBackPressed()\n                    return true;\n                }\n            }\n        }\n\n        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n            if (isTextInputEvent(event)) {\n                if (ic != null) {\n                    ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);\n                } else {\n                    SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1);\n                }\n            }\n            onNativeKeyDown(keyCode);\n            return true;\n        } else if (event.getAction() == KeyEvent.ACTION_UP) {\n            onNativeKeyUp(keyCode);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static Surface getNativeSurface() {\n        if (SDLActivity.mSurface == null) {\n            return null;\n        }\n        return SDLActivity.mSurface.getNativeSurface();\n    }\n\n    // Input\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void initTouch() {\n        int[] ids = InputDevice.getDeviceIds();\n\n        for (int id : ids) {\n            InputDevice device = InputDevice.getDevice(id);\n            /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */\n            if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN\n                    || device.isVirtual())) {\n\n                int touchDevId = device.getId();\n                /*\n                 * Prevent id to be -1, since it's used in SDL internal for synthetic events\n                 * Appears when using Android emulator, eg:\n                 *  adb shell input mouse tap 100 100\n                 *  adb shell input touchscreen tap 100 100\n                 */\n                if (touchDevId < 0) {\n                    touchDevId -= 1;\n                }\n                nativeAddTouch(touchDevId, device.getName());\n            }\n        }\n    }\n\n    // Messagebox\n\n    /** Result of current messagebox. Also used for blocking the calling thread. */\n    protected final int[] messageboxSelection = new int[1];\n\n    /**\n     * This method is called by SDL using JNI.\n     * Shows the messagebox from UI thread and block calling thread.\n     * buttonFlags, buttonIds and buttonTexts must have same length.\n     * @param buttonFlags array containing flags for every button.\n     * @param buttonIds array containing id for every button.\n     * @param buttonTexts array containing text for every button.\n     * @param colors null for default or array of length 5 containing colors.\n     * @return button id or -1.\n     */\n    public int messageboxShowMessageBox(\n            final int flags,\n            final String title,\n            final String message,\n            final int[] buttonFlags,\n            final int[] buttonIds,\n            final String[] buttonTexts,\n            final int[] colors) {\n\n        messageboxSelection[0] = -1;\n\n        // sanity checks\n\n        if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {\n            return -1; // implementation broken\n        }\n\n        // collect arguments for Dialog\n\n        final Bundle args = new Bundle();\n        args.putInt(\"flags\", flags);\n        args.putString(\"title\", title);\n        args.putString(\"message\", message);\n        args.putIntArray(\"buttonFlags\", buttonFlags);\n        args.putIntArray(\"buttonIds\", buttonIds);\n        args.putStringArray(\"buttonTexts\", buttonTexts);\n        args.putIntArray(\"colors\", colors);\n\n        // trigger Dialog creation on UI thread\n\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                messageboxCreateAndShow(args);\n            }\n        });\n\n        // block the calling thread\n\n        synchronized (messageboxSelection) {\n            try {\n                messageboxSelection.wait();\n            } catch (InterruptedException ex) {\n                ex.printStackTrace();\n                return -1;\n            }\n        }\n\n        // return selected value\n\n        return messageboxSelection[0];\n    }\n\n    protected void messageboxCreateAndShow(Bundle args) {\n\n        // TODO set values from \"flags\" to messagebox dialog\n\n        // get colors\n\n        int[] colors = args.getIntArray(\"colors\");\n        int backgroundColor;\n        int textColor;\n        int buttonBorderColor;\n        int buttonBackgroundColor;\n        int buttonSelectedColor;\n        if (colors != null) {\n            int i = -1;\n            backgroundColor = colors[++i];\n            textColor = colors[++i];\n            buttonBorderColor = colors[++i];\n            buttonBackgroundColor = colors[++i];\n            buttonSelectedColor = colors[++i];\n        } else {\n            backgroundColor = Color.TRANSPARENT;\n            textColor = Color.TRANSPARENT;\n            buttonBorderColor = Color.TRANSPARENT;\n            buttonBackgroundColor = Color.TRANSPARENT;\n            buttonSelectedColor = Color.TRANSPARENT;\n        }\n\n        // create dialog with title and a listener to wake up calling thread\n\n        final AlertDialog dialog = new AlertDialog.Builder(this).create();\n        dialog.setTitle(args.getString(\"title\"));\n        dialog.setCancelable(false);\n        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {\n            @Override\n            public void onDismiss(DialogInterface unused) {\n                synchronized (messageboxSelection) {\n                    messageboxSelection.notify();\n                }\n            }\n        });\n\n        // create text\n\n        TextView message = new TextView(this);\n        message.setGravity(Gravity.CENTER);\n        message.setText(args.getString(\"message\"));\n        if (textColor != Color.TRANSPARENT) {\n            message.setTextColor(textColor);\n        }\n\n        // create buttons\n\n        int[] buttonFlags = args.getIntArray(\"buttonFlags\");\n        int[] buttonIds = args.getIntArray(\"buttonIds\");\n        String[] buttonTexts = args.getStringArray(\"buttonTexts\");\n\n        final SparseArray<Button> mapping = new SparseArray<Button>();\n\n        LinearLayout buttons = new LinearLayout(this);\n        buttons.setOrientation(LinearLayout.HORIZONTAL);\n        buttons.setGravity(Gravity.CENTER);\n        for (int i = 0; i < buttonTexts.length; ++i) {\n            Button button = new Button(this);\n            final int id = buttonIds[i];\n            button.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    messageboxSelection[0] = id;\n                    dialog.dismiss();\n                }\n            });\n            if (buttonFlags[i] != 0) {\n                // see SDL_messagebox.h\n                if ((buttonFlags[i] & 0x00000001) != 0) {\n                    mapping.put(KeyEvent.KEYCODE_ENTER, button);\n                }\n                if ((buttonFlags[i] & 0x00000002) != 0) {\n                    mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */\n                }\n            }\n            button.setText(buttonTexts[i]);\n            if (textColor != Color.TRANSPARENT) {\n                button.setTextColor(textColor);\n            }\n            if (buttonBorderColor != Color.TRANSPARENT) {\n                // TODO set color for border of messagebox button\n            }\n            if (buttonBackgroundColor != Color.TRANSPARENT) {\n                Drawable drawable = button.getBackground();\n                if (drawable == null) {\n                    // setting the color this way removes the style\n                    button.setBackgroundColor(buttonBackgroundColor);\n                } else {\n                    // setting the color this way keeps the style (gradient, padding, etc.)\n                    drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);\n                }\n            }\n            if (buttonSelectedColor != Color.TRANSPARENT) {\n                // TODO set color for selected messagebox button\n            }\n            buttons.addView(button);\n        }\n\n        // create content\n\n        LinearLayout content = new LinearLayout(this);\n        content.setOrientation(LinearLayout.VERTICAL);\n        content.addView(message);\n        content.addView(buttons);\n        if (backgroundColor != Color.TRANSPARENT) {\n            content.setBackgroundColor(backgroundColor);\n        }\n\n        // add content to dialog and return\n\n        dialog.setView(content);\n        dialog.setOnKeyListener(new Dialog.OnKeyListener() {\n            @Override\n            public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {\n                Button button = mapping.get(keyCode);\n                if (button != null) {\n                    if (event.getAction() == KeyEvent.ACTION_UP) {\n                        button.performClick();\n                    }\n                    return true; // also for ignored actions\n                }\n                return false;\n            }\n        });\n\n        dialog.show();\n    }\n\n    private final Runnable rehideSystemUi = new Runnable() {\n        @Override\n        public void run() {\n            if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n                int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |\n                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |\n                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |\n                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |\n                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |\n                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;\n\n                SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags);\n            }\n        }\n    };\n\n    public void onSystemUiVisibilityChange(int visibility) {\n        if (SDLActivity.mFullscreenModeActive && ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0)) {\n\n            Handler handler = getWindow().getDecorView().getHandler();\n            if (handler != null) {\n                handler.removeCallbacks(rehideSystemUi); // Prevent a hide loop.\n                handler.postDelayed(rehideSystemUi, 2000);\n            }\n\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean clipboardHasText() {\n        return mClipboardHandler.clipboardHasText();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static String clipboardGetText() {\n        return mClipboardHandler.clipboardGetText();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void clipboardSetText(String string) {\n        mClipboardHandler.clipboardSetText(string);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) {\n        Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);\n        ++mLastCursorID;\n\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mCursors.put(mLastCursorID, PointerIcon.create(bitmap, hotSpotX, hotSpotY));\n            } catch (Exception e) {\n                return 0;\n            }\n        } else {\n            return 0;\n        }\n        return mLastCursorID;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void destroyCustomCursor(int cursorID) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mCursors.remove(cursorID);\n            } catch (Exception e) {\n            }\n        }\n        return;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setCustomCursor(int cursorID) {\n\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mSurface.setPointerIcon(mCursors.get(cursorID));\n            } catch (Exception e) {\n                return false;\n            }\n        } else {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setSystemCursor(int cursorID) {\n        int cursor_type = 0; //PointerIcon.TYPE_NULL;\n        switch (cursorID) {\n        case SDL_SYSTEM_CURSOR_ARROW:\n            cursor_type = 1000; //PointerIcon.TYPE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_IBEAM:\n            cursor_type = 1008; //PointerIcon.TYPE_TEXT;\n            break;\n        case SDL_SYSTEM_CURSOR_WAIT:\n            cursor_type = 1004; //PointerIcon.TYPE_WAIT;\n            break;\n        case SDL_SYSTEM_CURSOR_CROSSHAIR:\n            cursor_type = 1007; //PointerIcon.TYPE_CROSSHAIR;\n            break;\n        case SDL_SYSTEM_CURSOR_WAITARROW:\n            cursor_type = 1004; //PointerIcon.TYPE_WAIT;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENWSE:\n            cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENESW:\n            cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZEWE:\n            cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENS:\n            cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZEALL:\n            cursor_type = 1020; //PointerIcon.TYPE_GRAB;\n            break;\n        case SDL_SYSTEM_CURSOR_NO:\n            cursor_type = 1012; //PointerIcon.TYPE_NO_DROP;\n            break;\n        case SDL_SYSTEM_CURSOR_HAND:\n            cursor_type = 1002; //PointerIcon.TYPE_HAND;\n            break;\n        }\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mSurface.setPointerIcon(PointerIcon.getSystemIcon(SDL.getContext(), cursor_type));\n            } catch (Exception e) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void requestPermission(String permission, int requestCode) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            nativePermissionResult(requestCode, true);\n            return;\n        }\n\n        Activity activity = (Activity)getContext();\n        if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {\n            activity.requestPermissions(new String[]{permission}, requestCode);\n        } else {\n            nativePermissionResult(requestCode, true);\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        boolean result = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);\n        nativePermissionResult(requestCode, result);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int openURL(String url)\n    {\n        try {\n            Intent i = new Intent(Intent.ACTION_VIEW);\n            i.setData(Uri.parse(url));\n\n            int flags = Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_MULTIPLE_TASK;\n            if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n                flags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;\n            } else {\n                flags |= Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET;\n            }\n            i.addFlags(flags);\n\n            mSingleton.startActivity(i);\n        } catch (Exception ex) {\n            return -1;\n        }\n        return 0;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int showToast(String message, int duration, int gravity, int xOffset, int yOffset)\n    {\n        if(null == mSingleton) {\n            return - 1;\n        }\n\n        try\n        {\n            class OneShotTask implements Runnable {\n                String mMessage;\n                int mDuration;\n                int mGravity;\n                int mXOffset;\n                int mYOffset;\n\n                OneShotTask(String message, int duration, int gravity, int xOffset, int yOffset) {\n                    mMessage  = message;\n                    mDuration = duration;\n                    mGravity  = gravity;\n                    mXOffset  = xOffset;\n                    mYOffset  = yOffset;\n                }\n\n                public void run() {\n                    try\n                    {\n                        Toast toast = Toast.makeText(mSingleton, mMessage, mDuration);\n                        if (mGravity >= 0) {\n                            toast.setGravity(mGravity, mXOffset, mYOffset);\n                        }\n                        toast.show();\n                    } catch(Exception ex) {\n                        Log.e(TAG, ex.getMessage());\n                    }\n                }\n            }\n            mSingleton.runOnUiThread(new OneShotTask(message, duration, gravity, xOffset, yOffset));\n        } catch(Exception ex) {\n            return -1;\n        }\n        return 0;\n    }\n}\n\n/**\n    Simple runnable to start the SDL application\n*/\nclass SDLMain implements Runnable {\n    @Override\n    public void run() {\n        // Runs SDL_main()\n        String library = SDLActivity.mSingleton.getMainSharedObject();\n        String function = SDLActivity.mSingleton.getMainFunction();\n        String[] arguments = SDLActivity.mSingleton.getArguments();\n\n        try {\n            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY);\n        } catch (Exception e) {\n            Log.v(\"SDL\", \"modify thread properties failed \" + e.toString());\n        }\n\n        Log.v(\"SDL\", \"Running main function \" + function + \" from library \" + library);\n\n        SDLActivity.nativeRunMain(library, function, arguments);\n\n        Log.v(\"SDL\", \"Finished main function\");\n\n        if (SDLActivity.mSingleton != null && !SDLActivity.mSingleton.isFinishing()) {\n            // Let's finish the Activity\n            SDLActivity.mSDLThread = null;\n            SDLActivity.mSingleton.finish();\n        }  // else: Activity is already being destroyed\n\n    }\n}\n\n/* This is a fake invisible editor view that receives the input and defines the\n * pan&scan region\n */\nclass DummyEdit extends View implements View.OnKeyListener {\n    InputConnection ic;\n\n    public DummyEdit(Context context) {\n        super(context);\n        setFocusableInTouchMode(true);\n        setFocusable(true);\n        setOnKeyListener(this);\n    }\n\n    @Override\n    public boolean onCheckIsTextEditor() {\n        return true;\n    }\n\n    @Override\n    public boolean onKey(View v, int keyCode, KeyEvent event) {\n        return SDLActivity.handleKeyEvent(v, keyCode, event, ic);\n    }\n\n    //\n    @Override\n    public boolean onKeyPreIme (int keyCode, KeyEvent event) {\n        // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event\n        // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639\n        // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not\n        // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout\n        // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android\n        // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)\n        if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {\n            if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {\n                SDLActivity.onNativeKeyboardFocusLost();\n            }\n        }\n        return super.onKeyPreIme(keyCode, event);\n    }\n\n    @Override\n    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {\n        ic = new SDLInputConnection(this, true);\n\n        outAttrs.inputType = InputType.TYPE_CLASS_TEXT |\n                             InputType.TYPE_TEXT_FLAG_MULTI_LINE;\n        outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |\n                              EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;\n\n        return ic;\n    }\n}\n\nclass SDLInputConnection extends BaseInputConnection {\n\n    protected EditText mEditText;\n    protected String mCommittedText = \"\";\n\n    public SDLInputConnection(View targetView, boolean fullEditor) {\n        super(targetView, fullEditor);\n        mEditText = new EditText(SDL.getContext());\n    }\n\n    @Override\n    public Editable getEditable() {\n        return mEditText.getEditableText();\n    }\n\n    @Override\n    public boolean sendKeyEvent(KeyEvent event) {\n        /*\n         * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)\n         * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses\n         * and so we need to generate them ourselves in commitText.  To avoid duplicates on the handful of keys\n         * that still do, we empty this out.\n         */\n\n        /*\n         * Return DOES still generate a key event, however.  So rather than using it as the 'click a button' key\n         * as we do with physical keyboards, let's just use it to hide the keyboard.\n         */\n\n        if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {\n            if (SDLActivity.onNativeSoftReturnKey()) {\n                return true;\n            }\n        }\n\n        return super.sendKeyEvent(event);\n    }\n\n    @Override\n    public boolean commitText(CharSequence text, int newCursorPosition) {\n        if (!super.commitText(text, newCursorPosition)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    @Override\n    public boolean setComposingText(CharSequence text, int newCursorPosition) {\n        if (!super.setComposingText(text, newCursorPosition)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    @Override\n    public boolean deleteSurroundingText(int beforeLength, int afterLength) {\n        if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {\n            // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection\n            // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265\n            if (beforeLength > 0 && afterLength == 0) {\n                // backspace(s)\n                while (beforeLength-- > 0) {\n                    nativeGenerateScancodeForUnichar('\\b');\n                }\n                return true;\n           }\n        }\n\n        if (!super.deleteSurroundingText(beforeLength, afterLength)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    protected void updateText() {\n        final Editable content = getEditable();\n        if (content == null) {\n            return;\n        }\n\n        String text = content.toString();\n        int compareLength = Math.min(text.length(), mCommittedText.length());\n        int matchLength, offset;\n\n        /* Backspace over characters that are no longer in the string */\n        for (matchLength = 0; matchLength < compareLength; ) {\n            int codePoint = mCommittedText.codePointAt(matchLength);\n            if (codePoint != text.codePointAt(matchLength)) {\n                break;\n            }\n            matchLength += Character.charCount(codePoint);\n        }\n        /* FIXME: This doesn't handle graphemes, like '🌬️' */\n        for (offset = matchLength; offset < mCommittedText.length(); ) {\n            int codePoint = mCommittedText.codePointAt(offset);\n            nativeGenerateScancodeForUnichar('\\b');\n            offset += Character.charCount(codePoint);\n        }\n\n        if (matchLength < text.length()) {\n            String pendingText = text.subSequence(matchLength, text.length()).toString();\n            for (offset = 0; offset < pendingText.length(); ) {\n                int codePoint = pendingText.codePointAt(offset);\n                if (codePoint == '\\n') {\n                    if (SDLActivity.onNativeSoftReturnKey()) {\n                        return;\n                    }\n                }\n                /* Higher code points don't generate simulated scancodes */\n                if (codePoint < 128) {\n                    nativeGenerateScancodeForUnichar((char)codePoint);\n                }\n                offset += Character.charCount(codePoint);\n            }\n            SDLInputConnection.nativeCommitText(pendingText, 0);\n        }\n        mCommittedText = text;\n    }\n\n    public static native void nativeCommitText(String text, int newCursorPosition);\n\n    public static native void nativeGenerateScancodeForUnichar(char c);\n}\n\nclass SDLClipboardHandler implements\n    ClipboardManager.OnPrimaryClipChangedListener {\n\n    protected ClipboardManager mClipMgr;\n\n    SDLClipboardHandler() {\n       mClipMgr = (ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);\n       mClipMgr.addPrimaryClipChangedListener(this);\n    }\n\n    public boolean clipboardHasText() {\n       return mClipMgr.hasPrimaryClip();\n    }\n\n    public String clipboardGetText() {\n        ClipData clip = mClipMgr.getPrimaryClip();\n        if (clip != null) {\n            ClipData.Item item = clip.getItemAt(0);\n            if (item != null) {\n                CharSequence text = item.getText();\n                if (text != null) {\n                    return text.toString();\n                }\n            }\n        }\n        return null;\n    }\n\n    public void clipboardSetText(String string) {\n       mClipMgr.removePrimaryClipChangedListener(this);\n       ClipData clip = ClipData.newPlainText(null, string);\n       mClipMgr.setPrimaryClip(clip);\n       mClipMgr.addPrimaryClipChangedListener(this);\n    }\n\n    @Override\n    public void onPrimaryClipChanged() {\n        SDLActivity.onNativeClipboardChanged();\n    }\n}\n\n"
  },
  {
    "path": "android-project/thextech/src/main/java/org/libsdl/app/SDLAudioManager.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\nimport android.media.AudioDeviceCallback;\nimport android.media.AudioDeviceInfo;\nimport android.media.AudioFormat;\nimport android.media.AudioManager;\nimport android.media.AudioRecord;\nimport android.media.AudioTrack;\nimport android.media.MediaRecorder;\nimport android.os.Build;\nimport android.util.Log;\n\nimport java.util.Arrays;\n\npublic class SDLAudioManager {\n    protected static final String TAG = \"SDLAudio\";\n\n    protected static AudioTrack mAudioTrack;\n    protected static AudioRecord mAudioRecord;\n    protected static Context mContext;\n\n    private static final int[] NO_DEVICES = {};\n\n    private static AudioDeviceCallback mAudioDeviceCallback;\n\n    public static void initialize() {\n        mAudioTrack = null;\n        mAudioRecord = null;\n        mAudioDeviceCallback = null;\n\n        if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)\n        {\n            mAudioDeviceCallback = new AudioDeviceCallback() {\n                @Override\n                public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {\n                    Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));\n                }\n\n                @Override\n                public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {\n                    Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));\n                }\n            };\n        }\n    }\n\n    public static void setContext(Context context) {\n        mContext = context;\n        if (context != null) {\n            registerAudioDeviceCallback();\n        }\n    }\n\n    public static void release(Context context) {\n        unregisterAudioDeviceCallback(context);\n    }\n\n    // Audio\n\n    protected static String getAudioFormatString(int audioFormat) {\n        switch (audioFormat) {\n            case AudioFormat.ENCODING_PCM_8BIT:\n                return \"8-bit\";\n            case AudioFormat.ENCODING_PCM_16BIT:\n                return \"16-bit\";\n            case AudioFormat.ENCODING_PCM_FLOAT:\n                return \"float\";\n            default:\n                return Integer.toString(audioFormat);\n        }\n    }\n\n    protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {\n        int channelConfig;\n        int sampleSize;\n        int frameSize;\n\n        Log.v(TAG, \"Opening \" + (isCapture ? \"capture\" : \"playback\") + \", requested \" + desiredFrames + \" frames of \" + desiredChannels + \" channel \" + getAudioFormatString(audioFormat) + \" audio at \" + sampleRate + \" Hz\");\n\n        /* On older devices let's use known good settings */\n        if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {\n            if (desiredChannels > 2) {\n                desiredChannels = 2;\n            }\n        }\n\n        /* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */\n        if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {\n            if (sampleRate < 8000) {\n                sampleRate = 8000;\n            } else if (sampleRate > 48000) {\n                sampleRate = 48000;\n            }\n        }\n\n        if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {\n            int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);\n            if (Build.VERSION.SDK_INT < minSDKVersion) {\n                audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n            }\n        }\n        switch (audioFormat)\n        {\n        case AudioFormat.ENCODING_PCM_8BIT:\n            sampleSize = 1;\n            break;\n        case AudioFormat.ENCODING_PCM_16BIT:\n            sampleSize = 2;\n            break;\n        case AudioFormat.ENCODING_PCM_FLOAT:\n            sampleSize = 4;\n            break;\n        default:\n            Log.v(TAG, \"Requested format \" + audioFormat + \", getting ENCODING_PCM_16BIT\");\n            audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n            sampleSize = 2;\n            break;\n        }\n\n        if (isCapture) {\n            switch (desiredChannels) {\n            case 1:\n                channelConfig = AudioFormat.CHANNEL_IN_MONO;\n                break;\n            case 2:\n                channelConfig = AudioFormat.CHANNEL_IN_STEREO;\n                break;\n            default:\n                Log.v(TAG, \"Requested \" + desiredChannels + \" channels, getting stereo\");\n                desiredChannels = 2;\n                channelConfig = AudioFormat.CHANNEL_IN_STEREO;\n                break;\n            }\n        } else {\n            switch (desiredChannels) {\n            case 1:\n                channelConfig = AudioFormat.CHANNEL_OUT_MONO;\n                break;\n            case 2:\n                channelConfig = AudioFormat.CHANNEL_OUT_STEREO;\n                break;\n            case 3:\n                channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;\n                break;\n            case 4:\n                channelConfig = AudioFormat.CHANNEL_OUT_QUAD;\n                break;\n            case 5:\n                channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;\n                break;\n            case 6:\n                channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;\n                break;\n            case 7:\n                channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;\n                break;\n            case 8:\n                if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {\n                    channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;\n                } else {\n                    Log.v(TAG, \"Requested \" + desiredChannels + \" channels, getting 5.1 surround\");\n                    desiredChannels = 6;\n                    channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;\n                }\n                break;\n            default:\n                Log.v(TAG, \"Requested \" + desiredChannels + \" channels, getting stereo\");\n                desiredChannels = 2;\n                channelConfig = AudioFormat.CHANNEL_OUT_STEREO;\n                break;\n            }\n\n/*\n            Log.v(TAG, \"Speaker configuration (and order of channels):\");\n\n            if ((channelConfig & 0x00000004) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_LEFT\");\n            }\n            if ((channelConfig & 0x00000008) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_RIGHT\");\n            }\n            if ((channelConfig & 0x00000010) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_CENTER\");\n            }\n            if ((channelConfig & 0x00000020) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_LOW_FREQUENCY\");\n            }\n            if ((channelConfig & 0x00000040) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_BACK_LEFT\");\n            }\n            if ((channelConfig & 0x00000080) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_BACK_RIGHT\");\n            }\n            if ((channelConfig & 0x00000100) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_LEFT_OF_CENTER\");\n            }\n            if ((channelConfig & 0x00000200) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_RIGHT_OF_CENTER\");\n            }\n            if ((channelConfig & 0x00000400) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_BACK_CENTER\");\n            }\n            if ((channelConfig & 0x00000800) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_SIDE_LEFT\");\n            }\n            if ((channelConfig & 0x00001000) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_SIDE_RIGHT\");\n            }\n*/\n        }\n        frameSize = (sampleSize * desiredChannels);\n\n        // Let the user pick a larger buffer if they really want -- but ye\n        // gods they probably shouldn't, the minimums are horrifyingly high\n        // latency already\n        int minBufferSize;\n        if (isCapture) {\n            minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);\n        } else {\n            minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);\n        }\n        desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);\n\n        int[] results = new int[4];\n\n        if (isCapture) {\n            if (mAudioRecord == null) {\n                mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,\n                        channelConfig, audioFormat, desiredFrames * frameSize);\n\n                // see notes about AudioTrack state in audioOpen(), above. Probably also applies here.\n                if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {\n                    Log.e(TAG, \"Failed during initialization of AudioRecord\");\n                    mAudioRecord.release();\n                    mAudioRecord = null;\n                    return null;\n                }\n\n                if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {\n                    mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));\n                }\n\n                mAudioRecord.startRecording();\n            }\n\n            results[0] = mAudioRecord.getSampleRate();\n            results[1] = mAudioRecord.getAudioFormat();\n            results[2] = mAudioRecord.getChannelCount();\n\n        } else {\n            if (mAudioTrack == null) {\n                mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);\n\n                // Instantiating AudioTrack can \"succeed\" without an exception and the track may still be invalid\n                // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java\n                // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()\n                if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {\n                    /* Try again, with safer values */\n\n                    Log.e(TAG, \"Failed during initialization of Audio Track\");\n                    mAudioTrack.release();\n                    mAudioTrack = null;\n                    return null;\n                }\n\n                if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {\n                    mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));\n                }\n\n                mAudioTrack.play();\n            }\n\n            results[0] = mAudioTrack.getSampleRate();\n            results[1] = mAudioTrack.getAudioFormat();\n            results[2] = mAudioTrack.getChannelCount();\n        }\n        results[3] = desiredFrames;\n\n        Log.v(TAG, \"Opening \" + (isCapture ? \"capture\" : \"playback\") + \", got \" + results[3] + \" frames of \" + results[2] + \" channel \" + getAudioFormatString(results[1]) + \" audio at \" + results[0] + \" Hz\");\n\n        return results;\n    }\n\n    private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))\n                    .filter(deviceInfo -> deviceInfo.getId() == deviceId)\n                    .findFirst()\n                    .orElse(null);\n        } else {\n            return null;\n        }\n    }\n\n    private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))\n                    .filter(deviceInfo -> deviceInfo.getId() == deviceId)\n                    .findFirst()\n                    .orElse(null);\n        } else {\n            return null;\n        }\n    }\n\n    private static void registerAudioDeviceCallback() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);\n        }\n    }\n\n    private static void unregisterAudioDeviceCallback(Context context) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);\n            audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] getAudioOutputDevices() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();\n        } else {\n            return NO_DEVICES;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] getAudioInputDevices() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();\n        } else {\n            return NO_DEVICES;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {\n        return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void audioWriteFloatBuffer(float[] buffer) {\n        if (mAudioTrack == null) {\n            Log.e(TAG, \"Attempted to make audio call with uninitialized audio!\");\n            return;\n        }\n\n        if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {\n            Log.e(TAG, \"Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)\");\n            return;\n        }\n\n        for (int i = 0; i < buffer.length;) {\n            int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);\n            if (result > 0) {\n                i += result;\n            } else if (result == 0) {\n                try {\n                    Thread.sleep(1);\n                } catch(InterruptedException e) {\n                    // Nom nom\n                }\n            } else {\n                Log.w(TAG, \"SDL audio: error return from write(float)\");\n                return;\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void audioWriteShortBuffer(short[] buffer) {\n        if (mAudioTrack == null) {\n            Log.e(TAG, \"Attempted to make audio call with uninitialized audio!\");\n            return;\n        }\n\n        for (int i = 0; i < buffer.length;) {\n            int result = mAudioTrack.write(buffer, i, buffer.length - i);\n            if (result > 0) {\n                i += result;\n            } else if (result == 0) {\n                try {\n                    Thread.sleep(1);\n                } catch(InterruptedException e) {\n                    // Nom nom\n                }\n            } else {\n                Log.w(TAG, \"SDL audio: error return from write(short)\");\n                return;\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void audioWriteByteBuffer(byte[] buffer) {\n        if (mAudioTrack == null) {\n            Log.e(TAG, \"Attempted to make audio call with uninitialized audio!\");\n            return;\n        }\n\n        for (int i = 0; i < buffer.length; ) {\n            int result = mAudioTrack.write(buffer, i, buffer.length - i);\n            if (result > 0) {\n                i += result;\n            } else if (result == 0) {\n                try {\n                    Thread.sleep(1);\n                } catch(InterruptedException e) {\n                    // Nom nom\n                }\n            } else {\n                Log.w(TAG, \"SDL audio: error return from write(byte)\");\n                return;\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {\n        return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            return 0;\n        } else {\n            return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static int captureReadShortBuffer(short[] buffer, boolean blocking) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            return mAudioRecord.read(buffer, 0, buffer.length);\n        } else {\n            return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            return mAudioRecord.read(buffer, 0, buffer.length);\n        } else {\n            return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static void audioClose() {\n        if (mAudioTrack != null) {\n            mAudioTrack.stop();\n            mAudioTrack.release();\n            mAudioTrack = null;\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static void captureClose() {\n        if (mAudioRecord != null) {\n            mAudioRecord.stop();\n            mAudioRecord.release();\n            mAudioRecord = null;\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static void audioSetThreadPriority(boolean iscapture, int device_id) {\n        try {\n\n            /* Set thread name */\n            if (iscapture) {\n                Thread.currentThread().setName(\"SDLAudioC\" + device_id);\n            } else {\n                Thread.currentThread().setName(\"SDLAudioP\" + device_id);\n            }\n\n            /* Set thread priority */\n            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);\n\n        } catch (Exception e) {\n            Log.v(TAG, \"modify thread properties failed \" + e.toString());\n        }\n    }\n\n    public static native int nativeSetupJNI();\n\n    public static native void removeAudioDevice(boolean isCapture, int deviceId);\n\n    public static native void addAudioDevice(boolean isCapture, int deviceId);\n\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/org/libsdl/app/SDLControllerManager.java",
    "content": "package org.libsdl.app;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.VibrationEffect;\nimport android.os.Vibrator;\nimport android.util.Log;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\npublic class SDLControllerManager\n{\n\n    public static native int nativeSetupJNI();\n\n    public static native int nativeAddJoystick(int device_id, String name, String desc,\n                                               int vendor_id, int product_id,\n                                               boolean is_accelerometer, int button_mask,\n                                               int naxes, int axis_mask, int nhats, int nballs);\n    public static native int nativeRemoveJoystick(int device_id);\n    public static native int nativeAddHaptic(int device_id, String name);\n    public static native int nativeRemoveHaptic(int device_id);\n    public static native int onNativePadDown(int device_id, int keycode);\n    public static native int onNativePadUp(int device_id, int keycode);\n    public static native void onNativeJoy(int device_id, int axis,\n                                          float value);\n    public static native void onNativeHat(int device_id, int hat_id,\n                                          int x, int y);\n\n    protected static SDLJoystickHandler mJoystickHandler;\n    protected static SDLHapticHandler mHapticHandler;\n\n    private static final String TAG = \"SDLControllerManager\";\n\n    public static void initialize() {\n        if (mJoystickHandler == null) {\n            if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n                mJoystickHandler = new SDLJoystickHandler_API19();\n            } else {\n                mJoystickHandler = new SDLJoystickHandler_API16();\n            }\n        }\n\n        if (mHapticHandler == null) {\n            if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {\n                mHapticHandler = new SDLHapticHandler_API26();\n            } else {\n                mHapticHandler = new SDLHapticHandler();\n            }\n        }\n    }\n\n    // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance\n    public static boolean handleJoystickMotionEvent(MotionEvent event) {\n        return mJoystickHandler.handleMotionEvent(event);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void pollInputDevices() {\n        mJoystickHandler.pollInputDevices();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void pollHapticDevices() {\n        mHapticHandler.pollHapticDevices();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void hapticRun(int device_id, float intensity, int length) {\n        mHapticHandler.run(device_id, intensity, length);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void hapticStop(int device_id)\n    {\n        mHapticHandler.stop(device_id);\n    }\n\n    // Check if a given device is considered a possible SDL joystick\n    public static boolean isDeviceSDLJoystick(int deviceId) {\n        InputDevice device = InputDevice.getDevice(deviceId);\n        // We cannot use InputDevice.isVirtual before API 16, so let's accept\n        // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)\n        if ((device == null) || (deviceId < 0)) {\n            return false;\n        }\n        int sources = device.getSources();\n\n        /* This is called for every button press, so let's not spam the logs */\n        /*\n        if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" has class joystick.\");\n        }\n        if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" is a dpad.\");\n        }\n        if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" is a gamepad.\");\n        }\n        */\n\n        return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||\n                ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||\n                ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)\n        );\n    }\n\n}\n\nclass SDLJoystickHandler {\n\n    /**\n     * Handles given MotionEvent.\n     * @param event the event to be handled.\n     * @return if given event was processed.\n     */\n    public boolean handleMotionEvent(MotionEvent event) {\n        return false;\n    }\n\n    /**\n     * Handles adding and removing of input devices.\n     */\n    public void pollInputDevices() {\n    }\n}\n\n/* Actual joystick functionality available for API >= 12 devices */\nclass SDLJoystickHandler_API16 extends SDLJoystickHandler {\n\n    static class SDLJoystick {\n        public int device_id;\n        public String name;\n        public String desc;\n        public ArrayList<InputDevice.MotionRange> axes;\n        public ArrayList<InputDevice.MotionRange> hats;\n    }\n    static class RangeComparator implements Comparator<InputDevice.MotionRange> {\n        @Override\n        public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {\n            // Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL\n            int arg0Axis = arg0.getAxis();\n            int arg1Axis = arg1.getAxis();\n            if (arg0Axis == MotionEvent.AXIS_GAS) {\n                arg0Axis = MotionEvent.AXIS_BRAKE;\n            } else if (arg0Axis == MotionEvent.AXIS_BRAKE) {\n                arg0Axis = MotionEvent.AXIS_GAS;\n            }\n            if (arg1Axis == MotionEvent.AXIS_GAS) {\n                arg1Axis = MotionEvent.AXIS_BRAKE;\n            } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {\n                arg1Axis = MotionEvent.AXIS_GAS;\n            }\n\n            // Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.\n            // This is because the usual pairing are:\n            // - AXIS_X + AXIS_Y (left stick).\n            // - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).\n            // - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).\n            // This sorts the axes in the above order, which tends to be correct\n            // for Xbox-ish game pads that have the right stick on RX/RY and the\n            // triggers on Z/RZ.\n            //\n            // Gamepads that don't have AXIS_Z/AXIS_RZ but use\n            // AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.\n            //\n            // References:\n            // - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input\n            // - https://www.kernel.org/doc/html/latest/input/gamepad.html\n            if (arg0Axis == MotionEvent.AXIS_Z) {\n                arg0Axis = MotionEvent.AXIS_RZ - 1;\n            } else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {\n                --arg0Axis;\n            }\n            if (arg1Axis == MotionEvent.AXIS_Z) {\n                arg1Axis = MotionEvent.AXIS_RZ - 1;\n            } else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {\n                --arg1Axis;\n            }\n\n            return arg0Axis - arg1Axis;\n        }\n    }\n\n    private final ArrayList<SDLJoystick> mJoysticks;\n\n    public SDLJoystickHandler_API16() {\n\n        mJoysticks = new ArrayList<SDLJoystick>();\n    }\n\n    @Override\n    public void pollInputDevices() {\n        int[] deviceIds = InputDevice.getDeviceIds();\n\n        for (int device_id : deviceIds) {\n            if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {\n                SDLJoystick joystick = getJoystick(device_id);\n                if (joystick == null) {\n                    InputDevice joystickDevice = InputDevice.getDevice(device_id);\n                    joystick = new SDLJoystick();\n                    joystick.device_id = device_id;\n                    joystick.name = joystickDevice.getName();\n                    joystick.desc = getJoystickDescriptor(joystickDevice);\n                    joystick.axes = new ArrayList<InputDevice.MotionRange>();\n                    joystick.hats = new ArrayList<InputDevice.MotionRange>();\n\n                    List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();\n                    Collections.sort(ranges, new RangeComparator());\n                    for (InputDevice.MotionRange range : ranges) {\n                        if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {\n                            if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {\n                                joystick.hats.add(range);\n                            } else {\n                                joystick.axes.add(range);\n                            }\n                        }\n                    }\n\n                    mJoysticks.add(joystick);\n                    SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,\n                            getVendorId(joystickDevice), getProductId(joystickDevice), false,\n                            getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);\n                }\n            }\n        }\n\n        /* Check removed devices */\n        ArrayList<Integer> removedDevices = null;\n        for (SDLJoystick joystick : mJoysticks) {\n            int device_id = joystick.device_id;\n            int i;\n            for (i = 0; i < deviceIds.length; i++) {\n                if (device_id == deviceIds[i]) break;\n            }\n            if (i == deviceIds.length) {\n                if (removedDevices == null) {\n                    removedDevices = new ArrayList<Integer>();\n                }\n                removedDevices.add(device_id);\n            }\n        }\n\n        if (removedDevices != null) {\n            for (int device_id : removedDevices) {\n                SDLControllerManager.nativeRemoveJoystick(device_id);\n                for (int i = 0; i < mJoysticks.size(); i++) {\n                    if (mJoysticks.get(i).device_id == device_id) {\n                        mJoysticks.remove(i);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    protected SDLJoystick getJoystick(int device_id) {\n        for (SDLJoystick joystick : mJoysticks) {\n            if (joystick.device_id == device_id) {\n                return joystick;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public boolean handleMotionEvent(MotionEvent event) {\n        int actionPointerIndex = event.getActionIndex();\n        int action = event.getActionMasked();\n        if (action == MotionEvent.ACTION_MOVE) {\n            SDLJoystick joystick = getJoystick(event.getDeviceId());\n            if (joystick != null) {\n                for (int i = 0; i < joystick.axes.size(); i++) {\n                    InputDevice.MotionRange range = joystick.axes.get(i);\n                    /* Normalize the value to -1...1 */\n                    float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;\n                    SDLControllerManager.onNativeJoy(joystick.device_id, i, value);\n                }\n                for (int i = 0; i < joystick.hats.size() / 2; i++) {\n                    int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));\n                    int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));\n                    SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);\n                }\n            }\n        }\n        return true;\n    }\n\n    public String getJoystickDescriptor(InputDevice joystickDevice) {\n        String desc = joystickDevice.getDescriptor();\n\n        if (desc != null && !desc.isEmpty()) {\n            return desc;\n        }\n\n        return joystickDevice.getName();\n    }\n    public int getProductId(InputDevice joystickDevice) {\n        return 0;\n    }\n    public int getVendorId(InputDevice joystickDevice) {\n        return 0;\n    }\n    public int getAxisMask(List<InputDevice.MotionRange> ranges) {\n        return -1;\n    }\n    public int getButtonMask(InputDevice joystickDevice) {\n        return -1;\n    }\n}\n\nclass SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {\n\n    @Override\n    public int getProductId(InputDevice joystickDevice) {\n        return joystickDevice.getProductId();\n    }\n\n    @Override\n    public int getVendorId(InputDevice joystickDevice) {\n        return joystickDevice.getVendorId();\n    }\n\n    @Override\n    public int getAxisMask(List<InputDevice.MotionRange> ranges) {\n        // For compatibility, keep computing the axis mask like before,\n        // only really distinguishing 2, 4 and 6 axes.\n        int axis_mask = 0;\n        if (ranges.size() >= 2) {\n            // ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))\n            axis_mask |= 0x0003;\n        }\n        if (ranges.size() >= 4) {\n            // ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))\n            axis_mask |= 0x000c;\n        }\n        if (ranges.size() >= 6) {\n            // ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))\n            axis_mask |= 0x0030;\n        }\n        // Also add an indicator bit for whether the sorting order has changed.\n        // This serves to disable outdated gamecontrollerdb.txt mappings.\n        boolean have_z = false;\n        boolean have_past_z_before_rz = false;\n        for (InputDevice.MotionRange range : ranges) {\n            int axis = range.getAxis();\n            if (axis == MotionEvent.AXIS_Z) {\n                have_z = true;\n            } else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {\n                have_past_z_before_rz = true;\n            }\n        }\n        if (have_z && have_past_z_before_rz) {\n            // If both these exist, the compare() function changed sorting order.\n            // Set a bit to indicate this fact.\n            axis_mask |= 0x8000;\n        }\n        return axis_mask;\n    }\n\n    @Override\n    public int getButtonMask(InputDevice joystickDevice) {\n        int button_mask = 0;\n        int[] keys = new int[] {\n            KeyEvent.KEYCODE_BUTTON_A,\n            KeyEvent.KEYCODE_BUTTON_B,\n            KeyEvent.KEYCODE_BUTTON_X,\n            KeyEvent.KEYCODE_BUTTON_Y,\n            KeyEvent.KEYCODE_BACK,\n            KeyEvent.KEYCODE_MENU,\n            KeyEvent.KEYCODE_BUTTON_MODE,\n            KeyEvent.KEYCODE_BUTTON_START,\n            KeyEvent.KEYCODE_BUTTON_THUMBL,\n            KeyEvent.KEYCODE_BUTTON_THUMBR,\n            KeyEvent.KEYCODE_BUTTON_L1,\n            KeyEvent.KEYCODE_BUTTON_R1,\n            KeyEvent.KEYCODE_DPAD_UP,\n            KeyEvent.KEYCODE_DPAD_DOWN,\n            KeyEvent.KEYCODE_DPAD_LEFT,\n            KeyEvent.KEYCODE_DPAD_RIGHT,\n            KeyEvent.KEYCODE_BUTTON_SELECT,\n            KeyEvent.KEYCODE_DPAD_CENTER,\n\n            // These don't map into any SDL controller buttons directly\n            KeyEvent.KEYCODE_BUTTON_L2,\n            KeyEvent.KEYCODE_BUTTON_R2,\n            KeyEvent.KEYCODE_BUTTON_C,\n            KeyEvent.KEYCODE_BUTTON_Z,\n            KeyEvent.KEYCODE_BUTTON_1,\n            KeyEvent.KEYCODE_BUTTON_2,\n            KeyEvent.KEYCODE_BUTTON_3,\n            KeyEvent.KEYCODE_BUTTON_4,\n            KeyEvent.KEYCODE_BUTTON_5,\n            KeyEvent.KEYCODE_BUTTON_6,\n            KeyEvent.KEYCODE_BUTTON_7,\n            KeyEvent.KEYCODE_BUTTON_8,\n            KeyEvent.KEYCODE_BUTTON_9,\n            KeyEvent.KEYCODE_BUTTON_10,\n            KeyEvent.KEYCODE_BUTTON_11,\n            KeyEvent.KEYCODE_BUTTON_12,\n            KeyEvent.KEYCODE_BUTTON_13,\n            KeyEvent.KEYCODE_BUTTON_14,\n            KeyEvent.KEYCODE_BUTTON_15,\n            KeyEvent.KEYCODE_BUTTON_16,\n        };\n        int[] masks = new int[] {\n            (1 << 0),   // A -> A\n            (1 << 1),   // B -> B\n            (1 << 2),   // X -> X\n            (1 << 3),   // Y -> Y\n            (1 << 4),   // BACK -> BACK\n            (1 << 6),   // MENU -> START\n            (1 << 5),   // MODE -> GUIDE\n            (1 << 6),   // START -> START\n            (1 << 7),   // THUMBL -> LEFTSTICK\n            (1 << 8),   // THUMBR -> RIGHTSTICK\n            (1 << 9),   // L1 -> LEFTSHOULDER\n            (1 << 10),  // R1 -> RIGHTSHOULDER\n            (1 << 11),  // DPAD_UP -> DPAD_UP\n            (1 << 12),  // DPAD_DOWN -> DPAD_DOWN\n            (1 << 13),  // DPAD_LEFT -> DPAD_LEFT\n            (1 << 14),  // DPAD_RIGHT -> DPAD_RIGHT\n            (1 << 4),   // SELECT -> BACK\n            (1 << 0),   // DPAD_CENTER -> A\n            (1 << 15),  // L2 -> ??\n            (1 << 16),  // R2 -> ??\n            (1 << 17),  // C -> ??\n            (1 << 18),  // Z -> ??\n            (1 << 20),  // 1 -> ??\n            (1 << 21),  // 2 -> ??\n            (1 << 22),  // 3 -> ??\n            (1 << 23),  // 4 -> ??\n            (1 << 24),  // 5 -> ??\n            (1 << 25),  // 6 -> ??\n            (1 << 26),  // 7 -> ??\n            (1 << 27),  // 8 -> ??\n            (1 << 28),  // 9 -> ??\n            (1 << 29),  // 10 -> ??\n            (1 << 30),  // 11 -> ??\n            (1 << 31),  // 12 -> ??\n            // We're out of room...\n            0xFFFFFFFF,  // 13 -> ??\n            0xFFFFFFFF,  // 14 -> ??\n            0xFFFFFFFF,  // 15 -> ??\n            0xFFFFFFFF,  // 16 -> ??\n        };\n        boolean[] has_keys = joystickDevice.hasKeys(keys);\n        for (int i = 0; i < keys.length; ++i) {\n            if (has_keys[i]) {\n                button_mask |= masks[i];\n            }\n        }\n        return button_mask;\n    }\n}\n\nclass SDLHapticHandler_API26 extends SDLHapticHandler {\n    @Override\n    public void run(int device_id, float intensity, int length) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            Log.d(\"SDL\", \"Rtest: Vibe with intensity \" + intensity + \" for \" + length);\n            if (intensity == 0.0f) {\n                stop(device_id);\n                return;\n            }\n\n            int vibeValue = Math.round(intensity * 255);\n\n            if (vibeValue > 255) {\n                vibeValue = 255;\n            }\n            if (vibeValue < 1) {\n                stop(device_id);\n                return;\n            }\n            try {\n                haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));\n            }\n            catch (Exception e) {\n                // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if\n                // something went horribly wrong with the Android 8.0 APIs.\n                haptic.vib.vibrate(length);\n            }\n        }\n    }\n}\n\nclass SDLHapticHandler {\n\n    static class SDLHaptic {\n        public int device_id;\n        public String name;\n        public Vibrator vib;\n    }\n\n    private final ArrayList<SDLHaptic> mHaptics;\n\n    public SDLHapticHandler() {\n        mHaptics = new ArrayList<SDLHaptic>();\n    }\n\n    public void run(int device_id, float intensity, int length) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            haptic.vib.vibrate(length);\n        }\n    }\n\n    public void stop(int device_id) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            haptic.vib.cancel();\n        }\n    }\n\n    public void pollHapticDevices() {\n\n        final int deviceId_VIBRATOR_SERVICE = 999999;\n        boolean hasVibratorService = false;\n\n        int[] deviceIds = InputDevice.getDeviceIds();\n        // It helps processing the device ids in reverse order\n        // For example, in the case of the XBox 360 wireless dongle,\n        // so the first controller seen by SDL matches what the receiver\n        // considers to be the first controller\n\n        for (int i = deviceIds.length - 1; i > -1; i--) {\n            SDLHaptic haptic = getHaptic(deviceIds[i]);\n            if (haptic == null) {\n                InputDevice device = InputDevice.getDevice(deviceIds[i]);\n                Vibrator vib = device.getVibrator();\n                if (vib != null) {\n                    if (vib.hasVibrator()) {\n                        haptic = new SDLHaptic();\n                        haptic.device_id = deviceIds[i];\n                        haptic.name = device.getName();\n                        haptic.vib = vib;\n                        mHaptics.add(haptic);\n                        SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);\n                    }\n                }\n            }\n        }\n\n        /* Check VIBRATOR_SERVICE */\n        Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);\n        if (vib != null) {\n            hasVibratorService = vib.hasVibrator();\n\n            if (hasVibratorService) {\n                SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);\n                if (haptic == null) {\n                    haptic = new SDLHaptic();\n                    haptic.device_id = deviceId_VIBRATOR_SERVICE;\n                    haptic.name = \"VIBRATOR_SERVICE\";\n                    haptic.vib = vib;\n                    mHaptics.add(haptic);\n                    SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);\n                }\n            }\n        }\n\n        /* Check removed devices */\n        ArrayList<Integer> removedDevices = null;\n        for (SDLHaptic haptic : mHaptics) {\n            int device_id = haptic.device_id;\n            int i;\n            for (i = 0; i < deviceIds.length; i++) {\n                if (device_id == deviceIds[i]) break;\n            }\n\n            if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {\n                if (i == deviceIds.length) {\n                    if (removedDevices == null) {\n                        removedDevices = new ArrayList<Integer>();\n                    }\n                    removedDevices.add(device_id);\n                }\n            }  // else: don't remove the vibrator if it is still present\n        }\n\n        if (removedDevices != null) {\n            for (int device_id : removedDevices) {\n                SDLControllerManager.nativeRemoveHaptic(device_id);\n                for (int i = 0; i < mHaptics.size(); i++) {\n                    if (mHaptics.get(i).device_id == device_id) {\n                        mHaptics.remove(i);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    protected SDLHaptic getHaptic(int device_id) {\n        for (SDLHaptic haptic : mHaptics) {\n            if (haptic.device_id == device_id) {\n                return haptic;\n            }\n        }\n        return null;\n    }\n}\n\nclass SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {\n    // Generic Motion (mouse hover, joystick...) events go here\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n        float x, y;\n        int action;\n\n        switch ( event.getSource() ) {\n            case InputDevice.SOURCE_JOYSTICK:\n                return SDLControllerManager.handleJoystickMotionEvent(event);\n\n            case InputDevice.SOURCE_MOUSE:\n                action = event.getActionMasked();\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = event.getX(0);\n                        y = event.getY(0);\n\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    default:\n                        break;\n                }\n                break;\n\n            default:\n                break;\n        }\n\n        // Event was not managed\n        return false;\n    }\n\n    public boolean supportsRelativeMouse() {\n        return false;\n    }\n\n    public boolean inRelativeMode() {\n        return false;\n    }\n\n    public boolean setRelativeMouseEnabled(boolean enabled) {\n        return false;\n    }\n\n    public void reclaimRelativeMouseModeIfNeeded()\n    {\n\n    }\n\n    public float getEventX(MotionEvent event) {\n        return event.getX(0);\n    }\n\n    public float getEventY(MotionEvent event) {\n        return event.getY(0);\n    }\n\n}\n\nclass SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {\n    // Generic Motion (mouse hover, joystick...) events go here\n\n    private boolean mRelativeModeEnabled;\n\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n\n        // Handle relative mouse mode\n        if (mRelativeModeEnabled) {\n            if (event.getSource() == InputDevice.SOURCE_MOUSE) {\n                int action = event.getActionMasked();\n                if (action == MotionEvent.ACTION_HOVER_MOVE) {\n                    float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);\n                    float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);\n                    SDLActivity.onNativeMouse(0, action, x, y, true);\n                    return true;\n                }\n            }\n        }\n\n        // Event was not managed, call SDLGenericMotionListener_API12 method\n        return super.onGenericMotion(v, event);\n    }\n\n    @Override\n    public boolean supportsRelativeMouse() {\n        return true;\n    }\n\n    @Override\n    public boolean inRelativeMode() {\n        return mRelativeModeEnabled;\n    }\n\n    @Override\n    public boolean setRelativeMouseEnabled(boolean enabled) {\n        mRelativeModeEnabled = enabled;\n        return true;\n    }\n\n    @Override\n    public float getEventX(MotionEvent event) {\n        if (mRelativeModeEnabled) {\n            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);\n        } else {\n            return event.getX(0);\n        }\n    }\n\n    @Override\n    public float getEventY(MotionEvent event) {\n        if (mRelativeModeEnabled) {\n            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);\n        } else {\n            return event.getY(0);\n        }\n    }\n}\n\nclass SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {\n    // Generic Motion (mouse hover, joystick...) events go here\n    private boolean mRelativeModeEnabled;\n\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n        float x, y;\n        int action;\n\n        switch ( event.getSource() ) {\n            case InputDevice.SOURCE_JOYSTICK:\n                return SDLControllerManager.handleJoystickMotionEvent(event);\n\n            case InputDevice.SOURCE_MOUSE:\n            // DeX desktop mouse cursor is a separate non-standard input type.\n            case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:\n                action = event.getActionMasked();\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = event.getX(0);\n                        y = event.getY(0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    default:\n                        break;\n                }\n                break;\n\n            case InputDevice.SOURCE_MOUSE_RELATIVE:\n                action = event.getActionMasked();\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = event.getX(0);\n                        y = event.getY(0);\n                        SDLActivity.onNativeMouse(0, action, x, y, true);\n                        return true;\n\n                    default:\n                        break;\n                }\n                break;\n\n            default:\n                break;\n        }\n\n        // Event was not managed\n        return false;\n    }\n\n    @Override\n    public boolean supportsRelativeMouse() {\n        return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);\n    }\n\n    @Override\n    public boolean inRelativeMode() {\n        return mRelativeModeEnabled;\n    }\n\n    @Override\n    public boolean setRelativeMouseEnabled(boolean enabled) {\n        if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {\n            if (enabled) {\n                SDLActivity.getContentView().requestPointerCapture();\n            } else {\n                SDLActivity.getContentView().releasePointerCapture();\n            }\n            mRelativeModeEnabled = enabled;\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    @Override\n    public void reclaimRelativeMouseModeIfNeeded()\n    {\n        if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {\n            SDLActivity.getContentView().requestPointerCapture();\n        }\n    }\n\n    @Override\n    public float getEventX(MotionEvent event) {\n        // Relative mouse in capture mode will only have relative for X/Y\n        return event.getX(0);\n    }\n\n    @Override\n    public float getEventY(MotionEvent event) {\n        // Relative mouse in capture mode will only have relative for X/Y\n        return event.getY(0);\n    }\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/org/libsdl/app/SDLSurface.java",
    "content": "package org.libsdl.app;\n\n\nimport android.content.Context;\nimport android.content.pm.ActivityInfo;\nimport android.hardware.Sensor;\nimport android.hardware.SensorEvent;\nimport android.hardware.SensorEventListener;\nimport android.hardware.SensorManager;\nimport android.os.Build;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.view.Display;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.Surface;\nimport android.view.SurfaceHolder;\nimport android.view.SurfaceView;\nimport android.view.View;\nimport android.view.WindowManager;\n\n\n/**\n    SDLSurface. This is what we draw on, so we need to know when it's created\n    in order to do anything useful.\n\n    Because of this, that's where we set up the SDL thread\n*/\npublic class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,\n    View.OnKeyListener, View.OnTouchListener, SensorEventListener  {\n\n    // Sensors\n    protected SensorManager mSensorManager;\n    protected Display mDisplay;\n\n    // Keep track of the surface size to normalize touch events\n    protected float mWidth, mHeight;\n\n    // Is SurfaceView ready for rendering\n    public boolean mIsSurfaceReady;\n\n    // Startup\n    public SDLSurface(Context context) {\n        super(context);\n        getHolder().addCallback(this);\n\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        setOnKeyListener(this);\n        setOnTouchListener(this);\n\n        mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();\n        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);\n\n        setOnGenericMotionListener(SDLActivity.getMotionListener());\n\n        // Some arbitrary defaults to avoid a potential division by zero\n        mWidth = 1.0f;\n        mHeight = 1.0f;\n\n        mIsSurfaceReady = false;\n    }\n\n    public void handlePause() {\n        enableSensor(Sensor.TYPE_ACCELEROMETER, false);\n    }\n\n    public void handleResume() {\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        setOnKeyListener(this);\n        setOnTouchListener(this);\n        enableSensor(Sensor.TYPE_ACCELEROMETER, true);\n    }\n\n    public Surface getNativeSurface() {\n        return getHolder().getSurface();\n    }\n\n    // Called when we have a valid drawing surface\n    @Override\n    public void surfaceCreated(SurfaceHolder holder) {\n        Log.v(\"SDL\", \"surfaceCreated()\");\n        SDLActivity.onNativeSurfaceCreated();\n    }\n\n    // Called when we lose the surface\n    @Override\n    public void surfaceDestroyed(SurfaceHolder holder) {\n        Log.v(\"SDL\", \"surfaceDestroyed()\");\n\n        // Transition to pause, if needed\n        SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;\n        SDLActivity.handleNativeState();\n\n        mIsSurfaceReady = false;\n        SDLActivity.onNativeSurfaceDestroyed();\n    }\n\n    // Called when the surface is resized\n    @Override\n    public void surfaceChanged(SurfaceHolder holder,\n                               int format, int width, int height) {\n        Log.v(\"SDL\", \"surfaceChanged()\");\n\n        if (SDLActivity.mSingleton == null) {\n            return;\n        }\n\n        mWidth = width;\n        mHeight = height;\n        int nDeviceWidth = width;\n        int nDeviceHeight = height;\n        try\n        {\n            if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {\n                DisplayMetrics realMetrics = new DisplayMetrics();\n                mDisplay.getRealMetrics( realMetrics );\n                nDeviceWidth = realMetrics.widthPixels;\n                nDeviceHeight = realMetrics.heightPixels;\n            }\n        } catch(Exception ignored) {\n        }\n\n        synchronized(SDLActivity.getContext()) {\n            // In case we're waiting on a size change after going fullscreen, send a notification.\n            SDLActivity.getContext().notifyAll();\n        }\n\n        Log.v(\"SDL\", \"Window size: \" + width + \"x\" + height);\n        Log.v(\"SDL\", \"Device size: \" + nDeviceWidth + \"x\" + nDeviceHeight);\n        SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());\n        SDLActivity.onNativeResize();\n\n        // Prevent a screen distortion glitch,\n        // for instance when the device is in Landscape and a Portrait App is resumed.\n        boolean skip = false;\n        int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();\n\n        if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {\n            if (mWidth > mHeight) {\n               skip = true;\n            }\n        } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {\n            if (mWidth < mHeight) {\n               skip = true;\n            }\n        }\n\n        // Special Patch for Square Resolution: Black Berry Passport\n        if (skip) {\n           double min = Math.min(mWidth, mHeight);\n           double max = Math.max(mWidth, mHeight);\n\n           if (max / min < 1.20) {\n              Log.v(\"SDL\", \"Don't skip on such aspect-ratio. Could be a square resolution.\");\n              skip = false;\n           }\n        }\n\n        // Don't skip in MultiWindow.\n        if (skip) {\n            if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n                if (SDLActivity.mSingleton.isInMultiWindowMode()) {\n                    Log.v(\"SDL\", \"Don't skip in Multi-Window\");\n                    skip = false;\n                }\n            }\n        }\n\n        if (skip) {\n           Log.v(\"SDL\", \"Skip .. Surface is not ready.\");\n           mIsSurfaceReady = false;\n           return;\n        }\n\n        /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */\n        SDLActivity.onNativeSurfaceChanged();\n\n        /* Surface is ready */\n        mIsSurfaceReady = true;\n\n        SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;\n        SDLActivity.handleNativeState();\n    }\n\n    // Key events\n    @Override\n    public boolean onKey(View v, int keyCode, KeyEvent event) {\n        return SDLActivity.handleKeyEvent(v, keyCode, event, null);\n    }\n\n    // Touch events\n    @Override\n    public boolean onTouch(View v, MotionEvent event) {\n        /* Ref: http://developer.android.com/training/gestures/multi.html */\n        int touchDevId = event.getDeviceId();\n        final int pointerCount = event.getPointerCount();\n        int action = event.getActionMasked();\n        int pointerFingerId;\n        int i = -1;\n        float x,y,p;\n\n        /*\n         * Prevent id to be -1, since it's used in SDL internal for synthetic events\n         * Appears when using Android emulator, eg:\n         *  adb shell input mouse tap 100 100\n         *  adb shell input touchscreen tap 100 100\n         */\n        if (touchDevId < 0) {\n            touchDevId -= 1;\n        }\n\n        // 12290 = Samsung DeX mode desktop mouse\n        // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN\n        // 0x2   = SOURCE_CLASS_POINTER\n        if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {\n            int mouseButton = 1;\n            try {\n                Object object = event.getClass().getMethod(\"getButtonState\").invoke(event);\n                if (object != null) {\n                    mouseButton = (Integer) object;\n                }\n            } catch(Exception ignored) {\n            }\n\n            // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values\n            // if we are.  We'll leverage our existing mouse motion listener\n            SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();\n            x = motionListener.getEventX(event);\n            y = motionListener.getEventY(event);\n\n            SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());\n        } else {\n            switch(action) {\n                case MotionEvent.ACTION_MOVE:\n                    for (i = 0; i < pointerCount; i++) {\n                        pointerFingerId = event.getPointerId(i);\n                        x = event.getX(i) / mWidth;\n                        y = event.getY(i) / mHeight;\n                        p = event.getPressure(i);\n                        if (p > 1.0f) {\n                            // may be larger than 1.0f on some devices\n                            // see the documentation of getPressure(i)\n                            p = 1.0f;\n                        }\n                        SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);\n                    }\n                    break;\n\n                case MotionEvent.ACTION_UP:\n                case MotionEvent.ACTION_DOWN:\n                    // Primary pointer up/down, the index is always zero\n                    i = 0;\n                    /* fallthrough */\n                case MotionEvent.ACTION_POINTER_UP:\n                case MotionEvent.ACTION_POINTER_DOWN:\n                    // Non primary pointer up/down\n                    if (i == -1) {\n                        i = event.getActionIndex();\n                    }\n\n                    pointerFingerId = event.getPointerId(i);\n                    x = event.getX(i) / mWidth;\n                    y = event.getY(i) / mHeight;\n                    p = event.getPressure(i);\n                    if (p > 1.0f) {\n                        // may be larger than 1.0f on some devices\n                        // see the documentation of getPressure(i)\n                        p = 1.0f;\n                    }\n                    SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);\n                    break;\n\n                case MotionEvent.ACTION_CANCEL:\n                    for (i = 0; i < pointerCount; i++) {\n                        pointerFingerId = event.getPointerId(i);\n                        x = event.getX(i) / mWidth;\n                        y = event.getY(i) / mHeight;\n                        p = event.getPressure(i);\n                        if (p > 1.0f) {\n                            // may be larger than 1.0f on some devices\n                            // see the documentation of getPressure(i)\n                            p = 1.0f;\n                        }\n                        SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);\n                    }\n                    break;\n\n                default:\n                    break;\n            }\n        }\n\n        return true;\n   }\n\n    // Sensor events\n    public void enableSensor(int sensortype, boolean enabled) {\n        // TODO: This uses getDefaultSensor - what if we have >1 accels?\n        if (enabled) {\n            mSensorManager.registerListener(this,\n                            mSensorManager.getDefaultSensor(sensortype),\n                            SensorManager.SENSOR_DELAY_GAME, null);\n        } else {\n            mSensorManager.unregisterListener(this,\n                            mSensorManager.getDefaultSensor(sensortype));\n        }\n    }\n\n    @Override\n    public void onAccuracyChanged(Sensor sensor, int accuracy) {\n        // TODO\n    }\n\n    @Override\n    public void onSensorChanged(SensorEvent event) {\n        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {\n\n            // Since we may have an orientation set, we won't receive onConfigurationChanged events.\n            // We thus should check here.\n            int newOrientation;\n\n            float x, y;\n            switch (mDisplay.getRotation()) {\n                case Surface.ROTATION_90:\n                    x = -event.values[1];\n                    y = event.values[0];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;\n                    break;\n                case Surface.ROTATION_270:\n                    x = event.values[1];\n                    y = -event.values[0];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;\n                    break;\n                case Surface.ROTATION_180:\n                    x = -event.values[0];\n                    y = -event.values[1];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;\n                    break;\n                case Surface.ROTATION_0:\n                default:\n                    x = event.values[0];\n                    y = event.values[1];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;\n                    break;\n            }\n\n            if (newOrientation != SDLActivity.mCurrentOrientation) {\n                SDLActivity.mCurrentOrientation = newOrientation;\n                SDLActivity.onNativeOrientationChanged(newOrientation);\n            }\n\n            SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,\n                                      y / SensorManager.GRAVITY_EARTH,\n                                      event.values[2] / SensorManager.GRAVITY_EARTH);\n\n\n        }\n    }\n\n    // Captured pointer events for API 26.\n    public boolean onCapturedPointerEvent(MotionEvent event)\n    {\n        int action = event.getActionMasked();\n\n        float x, y;\n        switch (action) {\n            case MotionEvent.ACTION_SCROLL:\n                x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                SDLActivity.onNativeMouse(0, action, x, y, false);\n                return true;\n\n            case MotionEvent.ACTION_HOVER_MOVE:\n            case MotionEvent.ACTION_MOVE:\n                x = event.getX(0);\n                y = event.getY(0);\n                SDLActivity.onNativeMouse(0, action, x, y, true);\n                return true;\n\n            case MotionEvent.ACTION_BUTTON_PRESS:\n            case MotionEvent.ACTION_BUTTON_RELEASE:\n\n                // Change our action value to what SDL's code expects.\n                if (action == MotionEvent.ACTION_BUTTON_PRESS) {\n                    action = MotionEvent.ACTION_DOWN;\n                } else { /* MotionEvent.ACTION_BUTTON_RELEASE */\n                    action = MotionEvent.ACTION_UP;\n                }\n\n                x = event.getX(0);\n                y = event.getY(0);\n                int button = event.getButtonState();\n\n                SDLActivity.onNativeMouse(button, action, x, y, true);\n                return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/ru/wohlsoft/thextech/GameSettings.java",
    "content": "package ru.wohlsoft.thextech;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Environment;\nimport android.os.VibrationEffect;\nimport android.os.Vibrator;\nimport android.util.Pair;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\n\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.preference.Preference;\nimport androidx.preference.PreferenceFragmentCompat;\nimport androidx.preference.PreferenceManager;\n\npublic class GameSettings extends AppCompatActivity\n{\n    @Override\n    protected void onCreate(Bundle savedInstanceState)\n    {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.settings_activity);\n        if (savedInstanceState == null)\n        {\n            getSupportFragmentManager()\n                    .beginTransaction()\n                    .replace(R.id.settings, new SettingsFragment())\n                    .commit();\n        }\n\n        ActionBar actionBar = getSupportActionBar();\n        if (actionBar != null)\n            actionBar.setDisplayHomeAsUpEnabled(true);\n    }\n\n    public static class SettingsFragment extends PreferenceFragmentCompat\n    {\n        @Override\n        public void onCreatePreferences(Bundle savedInstanceState, String rootKey)\n        {\n            setPreferencesFromResource(R.xml.root_preferences, rootKey);\n\n            Preference button = getPreferenceManager().findPreference(\"setup_assets_path\");\n            if (button != null)\n            {\n                button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener()\n                {\n                    @Override\n                    public boolean onPreferenceClick(Preference arg0)\n                    {\n                        Context ctx = getContext();\n                        if(ctx == null)\n                            return false;// Null context, not allowed!\n                        return selectAssetsPath(ctx, null);\n                    }\n                });\n            }\n        }\n    }\n\n    private static final String m_defaultAssetsDir = Environment.getExternalStorageDirectory().getAbsolutePath() + \"/PGE Project/thextech\";\n\n    public static boolean isDirectoryExist(String dir)\n    {\n        File file = new File(dir);\n        return file.exists() && file.isDirectory();\n    }\n\n    public static boolean isFileExist(String f)\n    {\n        File file = new File(f);\n        return file.exists() && file.isFile();\n    }\n\n    public static boolean verifyAssetsPath(String path)\n    {\n        if(path.isEmpty())\n            path = GameSettings.m_defaultAssetsDir;\n\n        if(!isDirectoryExist(path))\n            return false;\n\n        if(!isDirectoryExist(path + \"/graphics\"))\n            return false;\n\n        if(!isDirectoryExist(path + \"/sound\"))\n            return false;\n\n        if(!isDirectoryExist(path + \"/music\"))\n            return false;\n\n        if(!isFileExist(path + \"/intro.lvlx\") &&\n           !isFileExist(path + \"/intro.lvl\") &&\n           !isDirectoryExist(path + \"/introset\"))\n            return false;\n\n        return true;\n    }\n\n    public static JSONArray sortAssetsList(JSONArray inArr)\n    {\n        try\n        {\n            ArrayList<JSONObject> array = new ArrayList<JSONObject>();\n\n            for(int i = 0; i < inArr.length(); i++)\n                array.add(inArr.getJSONObject(i));\n\n            Collections.sort(array, new Comparator<JSONObject>()\n            {\n                @Override\n                public int compare(JSONObject lhs, JSONObject rhs)\n                {\n                    try\n                    {\n                        return (lhs.getString(\"game\").toLowerCase().compareTo(rhs.getString(\"game\").toLowerCase()));\n                    }\n                    catch (JSONException e)\n                    {\n                        e.printStackTrace();\n                        return 0;\n                    }\n                }\n            });\n\n            JSONArray outArray = new JSONArray();\n\n            for(int i = 0; i < array.size(); i++)\n                outArray.put(array.get(i));\n\n            return outArray;\n        }\n        catch (JSONException e)\n        {\n            e.printStackTrace();\n            return inArr;\n        }\n    }\n\n    public static boolean selectAssetsPath(Context ctx, final Launcher l)\n    {\n        if(ctx == null)\n            return false;// Null context, not allowed!\n\n        SharedPreferences setup = PreferenceManager.getDefaultSharedPreferences(ctx);\n        String gameAssetsPath = setup.getString(\"setup_assets_path\", \"\");\n        if(gameAssetsPath.isEmpty())\n            gameAssetsPath = GameSettings.m_defaultAssetsDir;\n\n        File file = new File(gameAssetsPath);\n        if(!file.exists() || !file.isDirectory())\n        {\n            gameAssetsPath = Environment.getExternalStorageDirectory().getAbsolutePath();\n        }\n\n        OpenFileDialog fileDialog = new OpenFileDialog(ctx)\n                .setDirectoryMode()\n                .setFilter(\"gameinfo\\\\.ini\")\n                .setCurrentDirectory(gameAssetsPath)\n                .setOpenDialogListener(new OpenFileDialog.OpenDialogListener()\n                {\n                    @Override\n                    public void OnSelectedDirectory(Context ctx, String lastPath)\n                    {\n                        SharedPreferences setup = PreferenceManager.getDefaultSharedPreferences(ctx);\n                        setup.edit().putString(\"setup_assets_path\", lastPath).apply();\n                        if(l != null)\n                            l.updateOverlook();\n\n                        // Update list of assets (if needed)\n                        String gameName = \"<untitled game>\";\n                        String giPath = lastPath + \"/gameinfo.ini\";\n\n                        boolean hasGameInfo = !lastPath.isEmpty() && GameSettings.isFileExist(giPath);\n                        if(hasGameInfo)\n                        {\n                            IniFile gi = new IniFile(giPath);\n                            gameName = gi.getString(\"game\", \"title\", gameName);\n                        }\n\n                        // Add assets if missing\n                        String gameAssetsPath = setup.getString(\"setup_assets_list\", \"\");\n                        try\n                        {\n                            boolean updateNeeded = false;\n                            if(gameAssetsPath.isEmpty()) // Create new list\n                            {\n                                JSONObject jObject = new JSONObject();\n                                JSONArray jArray = new JSONArray();\n                                JSONObject oneObject = new JSONObject();\n                                oneObject.put(\"game\", gameName);\n                                oneObject.put(\"path\", lastPath);\n                                jArray.put(oneObject);\n                                jObject.put(\"assets\", jArray);\n                                setup.edit().putString(\"setup_assets_list\", jObject.toString()).apply();\n                                updateNeeded = true;\n                            }\n                            else // Append if not exists\n                            {\n                                JSONObject jObject = new JSONObject(gameAssetsPath);\n                                JSONArray jArray = jObject.getJSONArray(\"assets\");\n                                updateNeeded = true;\n\n                                for(int i = 0; i < jArray.length(); i++)\n                                {\n                                    JSONObject oneObject = jArray.getJSONObject(i);\n                                    String tPath = oneObject.getString(\"path\");\n                                    if(tPath.equals(lastPath))\n                                    {\n                                        updateNeeded = false;\n                                        break;\n                                    }\n                                }\n\n                                if(updateNeeded) // Append missing item\n                                {\n                                    JSONObject oneObject = new JSONObject();\n                                    oneObject.put(\"game\", gameName);\n                                    oneObject.put(\"path\", lastPath);\n                                    jArray.put(oneObject);\n                                    jObject.put(\"assets\", sortAssetsList(jArray));\n                                    setup.edit().putString(\"setup_assets_list\", jObject.toString()).apply();\n                                }\n                            }\n\n                            if(updateNeeded && l != null)\n                                l.reloadAssetsList();\n                        }\n                        catch (JSONException e)\n                        {\n                            e.printStackTrace();\n                        }\n                    }\n                    @Override\n                    public void OnSelectedFile(Context ctx, String fileName, String lastPath){}\n                });\n        fileDialog.show();\n        return true;\n    }\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/ru/wohlsoft/thextech/IniFile.java",
    "content": "package ru.wohlsoft.thextech;\n\n/*\n    Code made by Aerospace and published at the StackOverflow https://stackoverflow.com/a/15638381\n    CC BY-SA 3.0\n */\n\nimport java.io.BufferedReader;\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class IniFile\n{\n    private final Pattern _section = Pattern.compile(\"\\\\s*\\\\[([^]]*)\\\\]\\\\s*\");\n    private final Pattern _keyValue = Pattern.compile(\"\\\\s*([^=]*)=(.*)\");\n    private final Map<String, Map<String, String>> _entries = new HashMap<>();\n\n    public IniFile(String path)\n    {\n        load(path);\n    }\n\n    public void load(String path)\n    {\n        try\n        {\n            BufferedReader br = new BufferedReader(new FileReader(path));\n            String line;\n            String section = null;\n\n            while((line = br.readLine()) != null)\n            {\n                Matcher m = _section.matcher(line);\n\n                if(m.matches())\n                {\n                    section = m.group(1);\n                    if(section != null)\n                        section = section.trim();\n                }\n                else if(section != null)\n                {\n                    m = _keyValue.matcher(line);\n                    if(m.matches())\n                        insertValue(section, m);\n                }\n            }\n        }\n        catch (IOException e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    private void insertValue(String section, Matcher m)\n    {\n        String key = m.group(1);\n        String value = m.group(2);\n\n        if(key == null || value == null)\n            return;\n\n        key = key.trim();\n        value = value.trim();\n\n        boolean quoteOpen = false;\n        for(int i = 0; i < value.length(); ++i)\n        {\n            char c = value.charAt(i);\n            if(!quoteOpen && c == '\"')\n            {\n                quoteOpen = true;\n            }\n            else if(quoteOpen && c == '\"')\n            {\n                quoteOpen = false;\n            }\n            else if(!quoteOpen && c == ';')\n            {\n                value = value.substring(0, i).trim();\n                break; // Found leading comment, let's strip it!\n            }\n        }\n\n        if(value.startsWith(\"\\\"\") && value.endsWith(\"\\\"\"))\n            value = value.substring(1, value.length()-1).trim();\n\n        Map<String, String> kv = _entries.get(section);\n\n        if(kv == null)\n            _entries.put(section, kv = new HashMap<>());\n\n        kv.put(key, value);\n    }\n\n    public String getString(String section, String key, String defaultvalue)\n    {\n        Map<String, String> kv = _entries.get(section);\n        if(kv == null)\n            return defaultvalue;\n        String ret = kv.get(key);\n        if(ret == null)\n            return defaultvalue;\n        return ret;\n    }\n\n    public int getInt(String section, String key, int defaultvalue)\n    {\n        Map<String, String> kv = _entries.get(section);\n        if(kv == null)\n            return defaultvalue;\n        String ret = kv.get(key);\n        if(ret == null)\n            return defaultvalue;\n        return Integer.parseInt(ret);\n    }\n\n    public float getFloat(String section, String key, float defaultvalue)\n    {\n        Map<String, String> kv = _entries.get(section);\n        if(kv == null)\n            return defaultvalue;\n        String ret = kv.get(key);\n        if(ret == null)\n            return defaultvalue;\n        return Float.parseFloat(ret);\n    }\n\n    public double getDouble(String section, String key, double defaultvalue)\n    {\n        Map<String, String> kv = _entries.get(section);\n        if(kv == null)\n            return defaultvalue;\n        String ret = kv.get(key);\n        if(ret == null)\n            return defaultvalue;\n        return Double.parseDouble(ret);\n    }\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/ru/wohlsoft/thextech/Launcher.java",
    "content": "package ru.wohlsoft.thextech;\n\nimport android.Manifest;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Shader;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Build;\nimport androidx.annotation.NonNull;\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\nimport android.app.AlertDialog;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.preference.PreferenceManager;\n\nimport android.os.Bundle;\nimport android.os.Environment;\nimport android.provider.Settings;\nimport android.util.Log;\nimport android.util.Pair;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.ImageView;\nimport android.widget.PopupMenu;\nimport android.widget.RelativeLayout;\nimport android.widget.Toast;\n\nimport com.google.android.material.floatingactionbutton.FloatingActionButton;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\n\nimport javautil.FileUtils;\n\n\npublic class Launcher extends AppCompatActivity\n{\n    final String LOG_TAG = \"TheXTech\";\n    public static final int READWRITE_PERMISSION_FOR_GAME = 1;\n    public static final int READWRITE_PERMISSION_FOR_GAME_BY_INTENT = 2;\n    public static final int READWRITE_PERMISSION_FOR_ADD_DIRECTORY = 3;\n    private Context m_context = null;\n    private String filePathToOpen;\n    private String filePathToEdit;\n    private boolean editRequested = false;\n\n    /* ============ Animated background code ============ */\n    private int m_bgAnimatorFrames = 1;\n    private int m_bgAnimatorCurrentFrame = 0;\n    private Bitmap m_bgAnimatorBitmap;\n    private final UIUpdater m_bgAnimator = new UIUpdater(new Runnable()\n    {\n        @Override\n        public void run()\n        {\n            m_bgAnimatorCurrentFrame++;\n            if(m_bgAnimatorCurrentFrame >= m_bgAnimatorFrames)\n                m_bgAnimatorCurrentFrame = 0;\n            RelativeLayout launcher = findViewById(R.id.LauncherLayout);\n            int fHeight = m_bgAnimatorBitmap.getHeight() / m_bgAnimatorFrames;\n            int fOffset = m_bgAnimatorCurrentFrame * fHeight;\n            Bitmap bitmap = Bitmap.createBitmap(m_bgAnimatorBitmap, 0, fOffset, m_bgAnimatorBitmap.getWidth(), fHeight);\n            BitmapDrawable drawable = new BitmapDrawable(Launcher.this.getResources(), bitmap);\n            drawable.setTileModeX(Shader.TileMode.REPEAT);\n            drawable.getPaint().setFilterBitmap(false);\n            launcher.setBackground(drawable);\n        }\n    });\n    /* ================================================== */\n\n    private static final int MENU_ADD = Menu.FIRST;\n    private List<Pair<String, String>> menu_items = new ArrayList<Pair<String, String>>();;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState)\n    {\n        if(!isTaskRoot())\n        {\n            finish();\n            return;\n        }\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_launcher);\n        initUiSetup();\n        filePathToOpen = \"\";\n        filePathToEdit = \"\";\n        handleFileIntent();\n    }\n\n    @Override\n    protected void onResume()\n    {\n        super.onResume();\n        updateOverlook();\n    }\n\n    private void initUiSetup()\n    {\n        Button startGame = findViewById(R.id.startGame);\n        startGame.setOnClickListener(new View.OnClickListener()\n        {\n            @Override\n            public void onClick(View view)\n            {\n                OnStartGameClick(view);\n            }\n        });\n\n        Button gameSettings = findViewById(R.id.gameSettings);\n        gameSettings.setOnClickListener(\n        new View.OnClickListener()\n        {\n            @Override\n            public void onClick(View view)\n            {\n                Intent myIntent = new Intent(Launcher.this, GameSettings.class);\n                Launcher.this.startActivity(myIntent);\n            }\n        });\n\n        updateOverlook();\n        reloadAssetsList();\n\n        FloatingActionButton fab = findViewById(R.id.selectGame);\n        fab.setOnClickListener(new View.OnClickListener()\n        {\n            @Override\n            public void onClick(View view)\n            {\n                int i = 0;\n                PopupMenu menu = new PopupMenu(Launcher.this, view);\n\n                MenuItem dirSelect = menu.getMenu().add(0, MENU_ADD, Menu.NONE, R.string.launcher_gameSelect_pickTheDir);\n                dirSelect.setIcon(R.drawable.add_directory);\n\n                for(i = 0; i < menu_items.size(); i++)\n                {\n                    Pair<String, String> e = menu_items.get(i);\n                    String gameName = e.first;\n                    String gamePath = e.second;\n\n                    Log.d(LOG_TAG, \"Assets name: \" + gameName + \"; path: \" + gamePath);\n\n                    if(!GameSettings.isDirectoryExist(gamePath))\n                        continue; // Skip invalid assets\n\n                    MenuItem it = menu.getMenu().add(0, MENU_ADD + i + 1, Menu.NONE, gameName);\n\n                    String iconPath = gamePath + \"/graphics/ui/icon/thextech_128.png\";\n                    if(GameSettings.isFileExist(iconPath)) // Add icon only when it exists and accessible\n                    {\n                        Log.d(LOG_TAG, \"Icon file exists: \" + iconPath);\n                        Drawable d = Drawable.createFromPath(iconPath);\n                        it.setIcon(d);\n                    }\n                    else\n                        Log.d(LOG_TAG, \"Icon file DOES NOT EXISTS: \" + iconPath);\n                }\n\n                int CLEAR_ITEM = MENU_ADD + i + 1;\n                MenuItem clearItem = menu.getMenu().add(0, CLEAR_ITEM, Menu.NONE, R.string.launcher_gameSelect_clearList);\n                clearItem.setIcon(R.drawable.clear_list);\n\n                menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener()\n                {\n                    @Override\n                    public boolean onMenuItemClick(MenuItem item)\n                    {\n                        int id = item.getItemId();\n\n                        if(id == MENU_ADD)\n                        {\n                            if(checkFilePermissions(READWRITE_PERMISSION_FOR_ADD_DIRECTORY) || !hasManageAppFS())\n                                return false;\n                            GameSettings.selectAssetsPath(Launcher.this, Launcher.this);\n                        }\n                        else if(id == CLEAR_ITEM)\n                        {\n                            AlertDialog.Builder b = new AlertDialog.Builder(Launcher.this);\n                            b.setTitle(R.string.launcher_gameselect_clearlist_title);\n                            b.setMessage(R.string.launcher_gameselect_clearlist_question);\n                            b.setNegativeButton(android.R.string.no, null);\n                            b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()\n                            {\n                                public void onClick(DialogInterface dialog, int whichButton)\n                                {\n                                    SharedPreferences setup = PreferenceManager.getDefaultSharedPreferences(getBaseContext());\n                                    setup.edit().putString(\"setup_assets_list\", \"\").apply();\n                                    reloadAssetsList();\n                                    Toast.makeText(getApplicationContext(), getString(R.string.launcher_gameselect_toast_listclean), Toast.LENGTH_SHORT).show();\n                                }\n                            });\n                            b.show();\n                        }\n                        else\n                        {\n                            int itemId = id - 2;\n                            String gameName = menu_items.get(itemId).first;\n                            String gamePath = menu_items.get(itemId).second;\n\n                            Toast.makeText(getApplicationContext(), String.format(getString(R.string.launcher_gameselect_toast_selected), gameName), Toast.LENGTH_SHORT).show();\n                            SharedPreferences setup = PreferenceManager.getDefaultSharedPreferences(getBaseContext());\n                            setup.edit().putString(\"setup_assets_path\", gamePath).apply();\n                            updateOverlook();\n                        }\n\n                        return true;\n                    }\n                });\n\n                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)\n                    menu.setForceShowIcon(true);\n\n                menu.show();\n            }\n        });\n\n    }\n\n    private void handleFileIntent()\n    {\n        Intent intent = getIntent();\n        String scheme = intent.getScheme();\n        if(scheme != null)\n        {\n            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n            if(checkFilePermissions(READWRITE_PERMISSION_FOR_GAME_BY_INTENT) || !hasManageAppFS())\n                return;\n\n            if(m_context == null)\n                m_context = getApplicationContext();\n\n            FileUtils utils = new FileUtils(this.m_context);\n            filePathToOpen = utils.getPath(intent.getData());\n\n            editRequested = Objects.equals(intent.getAction(), Intent.ACTION_EDIT);\n\n            Log.d(LOG_TAG, \"Got a file: \" + filePathToOpen + \";\");\n\n            if(utils.isInternalCopy()) // File was coped into internal storage, resources are unavailable!\n            {\n                DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener()\n                {\n                    @Override\n                    public void onClick(DialogInterface dialog, int which)\n                    {\n                        switch (which){\n                            case DialogInterface.BUTTON_POSITIVE:\n                                tryStartGame(m_context);\n                                break;\n                            case DialogInterface.BUTTON_NEGATIVE:\n                                break;\n                        }\n                    }\n                };\n\n                AlertDialog.Builder builder = new AlertDialog.Builder(this);\n                builder.setMessage(R.string.launcher_openfile_indirectly)\n                        .setPositiveButton(android.R.string.yes, dialogClickListener)\n                        .setNegativeButton(android.R.string.no, dialogClickListener)\n                        .show();\n            }\n            else // File is accessible directly, do open it normally\n                tryStartGame(m_context);\n        }\n    }\n\n    public void OnStartGameClick(View view)\n    {\n        if(m_context == null)\n            m_context = view.getContext();\n\n        // Here, thisActivity is the current activity\n        if(checkFilePermissions(READWRITE_PERMISSION_FOR_GAME) || !hasManageAppFS())\n            return;\n\n        tryStartGame(m_context);\n    }\n\n    private void tryStartGame(Context context)\n    {\n        assert(context != null);\n\n        SharedPreferences setup = PreferenceManager.getDefaultSharedPreferences(getBaseContext());\n        String gameAssetsPath = setup.getString(\"setup_assets_path\", \"\");\n\n        if(!GameSettings.verifyAssetsPath(gameAssetsPath))\n        {\n            DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener()\n            {\n                @Override\n                public void onClick(DialogInterface dialog, int which)\n                {\n                    switch (which){\n                        case DialogInterface.BUTTON_POSITIVE:\n                            GameSettings.selectAssetsPath(Launcher.this, Launcher.this);\n                            break;\n                        case DialogInterface.BUTTON_NEGATIVE:\n                            //No button clicked\n                            break;\n                    }\n                }\n            };\n\n            AlertDialog.Builder builder = new AlertDialog.Builder(context);\n            builder.setMessage(R.string.launcher_no_resources_question)\n                    .setPositiveButton(android.R.string.yes, dialogClickListener)\n                    .setNegativeButton(android.R.string.no, dialogClickListener)\n                    .show();\n            return;\n        }\n\n        startGame();\n    }\n\n    public void reloadAssetsList()\n    {\n        SharedPreferences setup = PreferenceManager.getDefaultSharedPreferences(getBaseContext());\n        String gameAssetsPath = setup.getString(\"setup_assets_list\", \"\");\n\n        menu_items.clear();\n\n        if(!gameAssetsPath.isEmpty())\n        {\n            try\n            {\n                JSONObject jObject = new JSONObject(gameAssetsPath);\n                JSONArray jArray = jObject.getJSONArray(\"assets\");\n                for(int i = 0; i < jArray.length(); i++)\n                {\n                    JSONObject oneObject = jArray.getJSONObject(i);\n                    String gameName = oneObject.getString(\"game\");\n                    String gamePath = oneObject.getString(\"path\");\n                    menu_items.add(new Pair<String, String>(gameName, gamePath));\n                }\n            }\n            catch (JSONException e)\n            {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    public void updateOverlook()\n    {\n        SharedPreferences setup = PreferenceManager.getDefaultSharedPreferences(getBaseContext());\n        String gameAssetsPath = setup.getString(\"setup_assets_path\", \"\");\n\n        String logoImagePath = \"graphics/ui/MenuGFX2.png\";\n        String assetsIcon = \"graphics/ui/icon/thextech_128.png\";\n        String bgImagePath = \"graphics/background2/background2-2.png\";\n        int bgImageFrames = 1;\n        int bgImageFramesDelay = 125;\n\n        String giPath = gameAssetsPath + \"/gameinfo.ini\";\n        boolean hasGameInfo = !gameAssetsPath.isEmpty() && GameSettings.isFileExist(giPath);\n        if(hasGameInfo)\n        {\n            IniFile gi = new IniFile(giPath);\n            logoImagePath = gi.getString(\"android\", \"logo\", logoImagePath);\n            bgImagePath = gi.getString(\"android\", \"background\", bgImagePath);\n            bgImageFrames = gi.getInt(\"android\", \"background-frames\", 1);\n            bgImageFramesDelay = gi.getInt(\"android\", \"background-delay\", 125);\n        }\n\n        String bgPath = gameAssetsPath + \"/\" + bgImagePath;\n        String logoPath = gameAssetsPath + \"/\" + logoImagePath;\n        String iconPath = gameAssetsPath + \"/\" + assetsIcon;\n\n        m_bgAnimator.stopUpdates();\n\n        RelativeLayout launcher = findViewById(R.id.LauncherLayout);\n        if(!gameAssetsPath.isEmpty() && GameSettings.isFileExist(bgPath))\n        {\n            Bitmap bitmap = BitmapFactory.decodeFile(bgPath);\n            if(bgImageFrames > 1)\n            {\n                m_bgAnimatorBitmap = Bitmap.createBitmap(bitmap);\n                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight() / bgImageFrames);\n                m_bgAnimatorFrames = bgImageFrames;\n                m_bgAnimatorCurrentFrame = 0;\n                m_bgAnimator.setInterval(bgImageFramesDelay);\n            }\n            BitmapDrawable drawable = new BitmapDrawable(this.getResources(), bitmap);\n            drawable.setTileModeX(Shader.TileMode.REPEAT);\n            drawable.getPaint().setFilterBitmap(false);\n            launcher.setBackground(drawable);\n            if(bgImageFrames > 1)\n                m_bgAnimator.startUpdates();\n        }\n        else\n            launcher.setBackgroundResource(R.drawable.background);\n\n        ImageView gameLogo = findViewById(R.id.gameLogo);\n        if(!gameAssetsPath.isEmpty() && GameSettings.isFileExist(logoPath))\n        {\n            Bitmap bitmap = BitmapFactory.decodeFile(logoPath);\n            BitmapDrawable drawable = new BitmapDrawable(this.getResources(), bitmap);\n            drawable.getPaint().setFilterBitmap(false);\n            gameLogo.setImageDrawable(drawable);\n        }\n        else\n            gameLogo.setImageResource(R.drawable.logo);\n\n        FloatingActionButton fab = findViewById(R.id.selectGame);\n        if(!gameAssetsPath.isEmpty() && GameSettings.isFileExist(iconPath))\n        {\n            Drawable d = Drawable.createFromPath(iconPath);\n            fab.setImageDrawable(d);\n        }\n        else\n            fab.setImageResource(R.drawable.add_directory);\n    }\n\n    private boolean checkFilePermissions(int requestCode)\n    {\n        final int grant = PackageManager.PERMISSION_GRANTED;\n        final String exStorage = Manifest.permission.WRITE_EXTERNAL_STORAGE;\n\n        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)\n            return false; /* Has no effect, the manage file storage permission is used instead of this */\n\n        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)\n        {\n            if(ContextCompat.checkSelfPermission(this, exStorage) == grant)\n                Log.d(LOG_TAG, \"File permission is granted\");\n            else\n                Log.d(LOG_TAG, \"File permission is revoked\");\n        }\n\n//      // if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)\n//        {\n        if(ContextCompat.checkSelfPermission(this, exStorage) == grant)\n            return false;\n\n        // Should we show an explanation?\n        if(ActivityCompat.shouldShowRequestPermissionRationale(this, exStorage))\n        {\n            // Show an explanation to the user *asynchronously* -- don't block\n            // this thread waiting for the user's response! After the user\n            // sees the explanation, try again to request the permission.\n            AlertDialog.Builder b = new AlertDialog.Builder(this);\n            b.setTitle(\"Permission denied\");\n            b.setMessage(\"Sorry, but permission is denied!\\n\"+\n                         \"Please, check the External Storage access permission to the game!\");\n            b.setNegativeButton(android.R.string.ok, null);\n            b.show();\n            return true;\n        }\n        else\n        {\n            // No explanation needed, we can request the permission.\n            ActivityCompat.requestPermissions(this, new String[] { exStorage }, requestCode);\n            // MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE\n            // MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE is an\n            // app-defined int constant. The callback method gets the\n            // result of the request.\n        }\n\n        return true;\n//        } // if JELLY_BEAN\n\n//        return false;\n    }\n\n    public boolean hasManageAppFS()\n    {\n        if(Build.VERSION.SDK_INT >= 30)\n        {\n            if(Environment.isExternalStorageManager())\n                return true;\n\n            AlertDialog.Builder b = new AlertDialog.Builder(this);\n            b.setTitle(R.string.managePermExplainTitle);\n            b.setMessage(R.string.managePermExplainText);\n            b.setNegativeButton(android.R.string.ok, new DialogInterface.OnClickListener()\n            {\n                public void onClick(DialogInterface dialog, int whichButton)\n                {\n                    Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);\n                    String pName = getPackageName();\n                    Uri uri = Uri.fromParts(\"package\", pName, null);\n                    intent.setData(uri);\n                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n                    startActivity(intent);\n                }\n            });\n            b.show();\n\n            return false;\n        }\n\n        return true;\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)\n    {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n\n        if(grantResults.length == 0)\n            return;\n        if(!permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE))\n            return;\n        if(grantResults[0] != PackageManager.PERMISSION_GRANTED)\n            return;\n        if(!hasManageAppFS())\n            return;\n\n        switch(requestCode)\n        {\n        case READWRITE_PERMISSION_FOR_GAME:\n            tryStartGame(m_context);\n            break;\n\n        case READWRITE_PERMISSION_FOR_ADD_DIRECTORY:\n            GameSettings.selectAssetsPath(Launcher.this, Launcher.this);\n            break;\n        }\n    }\n\n    public void startGame()\n    {\n        Intent myIntent = new Intent(Launcher.this, thextechActivity.class);\n        if(!filePathToOpen.isEmpty())\n            myIntent.putExtra(\"do-open-file\", filePathToOpen);\n\n        if(editRequested)\n            myIntent.putExtra(\"edit-requested\", true);\n\n//        myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);\n//        myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\n        myIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);\n        Launcher.this.startActivity(myIntent);\n        Launcher.this.finish();\n    }\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/ru/wohlsoft/thextech/OpenFileDialog.java",
    "content": "package ru.wohlsoft.thextech;\n\n\nimport android.app.AlertDialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.Point;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\n//import android.os.Build;\nimport android.os.Environment;\nimport android.util.DisplayMetrics;\nimport android.util.TypedValue;\nimport android.view.*;\nimport android.widget.*;\n\nimport java.io.File;\nimport java.io.FilenameFilter;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.*;\n\nimport androidx.core.content.res.ResourcesCompat;\n\n\n/**\n * Created with IntelliJ IDEA.\n * User: Scogun\n * Date: 27.11.13\n * Time: 10:47\n */\npublic class OpenFileDialog extends AlertDialog.Builder {\n\n    private String currentPath = Environment.getExternalStorageDirectory().getPath();\n    private String initialPath = currentPath;\n    private List<File> files = new ArrayList<File>();\n    private TextView title;\n    private ListView listView;\n    private FilenameFilter filenameFilter;\n    private int selectedIndex = -1;\n    private OpenDialogListener listener;\n    private Drawable folderIcon;\n    private Drawable fileIcon;\n    private String accessDeniedMessage;\n    private Boolean m_directoryMode = false;\n\n    public interface OpenDialogListener\n    {\n        public void OnSelectedFile(Context ctx, String fileName, String lastPath);\n        public void OnSelectedDirectory(Context ctx, String lastPath);\n    }\n\n    private class FileAdapter extends ArrayAdapter<File> {\n\n        public FileAdapter(Context context, List<File> files) {\n            super(context, android.R.layout.simple_list_item_1, files);\n        }\n\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n            TextView view = (TextView) super.getView(position, convertView, parent);\n            File file = getItem(position);\n            assert file != null;\n            view.setText(file.getName());\n            if (file.isDirectory()) {\n                setDrawable(view, folderIcon);\n                view.setTextColor(Color.DKGRAY);\n                view.setBackgroundColor(Color.LTGRAY);\n            } else {\n                setDrawable(view, fileIcon);\n                view.setTextColor(Color.BLACK);\n                if (selectedIndex == position)\n                    view.setBackgroundColor(Color.LTGRAY);\n                else\n                    view.setBackgroundColor(Color.WHITE);\n            }\n            return view;\n        }\n\n        private void setDrawable(TextView view, Drawable drawable) {\n            if (view != null) {\n                if (drawable != null) {\n                    drawable.setBounds(0, 0, 60, 60);\n                    view.setCompoundDrawables(drawable, null, null, null);\n                } else {\n                    view.setCompoundDrawables(null, null, null, null);\n                }\n            }\n        }\n    }\n\n    public OpenFileDialog(Context context) {\n        super(context);\n        title = createTitle(context);\n        changeTitle();\n\n        LinearLayout linearLayout = createMainLayout(context);\n\n        LinearLayout menuBar = createMenuLayout(linearLayout.getContext());\n        menuBar.addView(createBackItem(context));\n        menuBar.addView(createGoHomeItem(context));\n        menuBar.addView(createGoInitPathItem(context));\n        linearLayout.addView(menuBar);\n\n        listView = createListView(context);\n        linearLayout.addView(listView);\n\n        setCustomTitle(title)\n            .setView(linearLayout)\n            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()\n            {\n                @Override\n                public void onClick(DialogInterface dialog, int which)\n                {\n                    if(m_directoryMode)\n                    {\n                        listener.OnSelectedDirectory(getContext(), currentPath);\n                    }\n                    else if (selectedIndex > -1 && listener != null)\n                    {\n                        listener.OnSelectedFile(getContext(), listView.getItemAtPosition(selectedIndex).toString(), currentPath);\n                    }\n                }\n            })\n            .setNegativeButton(android.R.string.cancel, null);\n    }\n\n    @Override\n    public AlertDialog show() {\n        files.addAll(getFiles(currentPath));\n        listView.setAdapter(new FileAdapter(getContext(), files));\n        return super.show();\n    }\n\n    public OpenFileDialog setFilter(final String filter) {\n        filenameFilter = new FilenameFilter() {\n            @Override\n            public boolean accept(File file, String fileName) {\n                Pattern filt = Pattern.compile(filter, Pattern.CASE_INSENSITIVE);\n                File tempFile = new File(String.format(\"%s/%s\", file.getPath(), fileName));\n                if (tempFile.isFile()) {\n                    Matcher m = filt.matcher(tempFile.getName());\n                    return m.matches();\n                }\n\n                return true;\n            }\n        };\n        return this;\n    }\n\n    public OpenFileDialog setDirectoryMode()\n    {\n        m_directoryMode = true;\n        return this;\n    }\n\n    public OpenFileDialog setCurrentDirectory(String path)\n    {\n        currentPath = path;\n        initialPath = path;\n        changeTitle();\n        return this;\n    }\n\n    public OpenFileDialog setOpenDialogListener(OpenDialogListener listener) {\n        this.listener = listener;\n        return this;\n    }\n\n    public OpenFileDialog setFolderIcon(Drawable drawable) {\n        this.folderIcon = drawable;\n        return this;\n    }\n\n    public OpenFileDialog setFileIcon(Drawable drawable) {\n        this.fileIcon = drawable;\n        return this;\n    }\n\n    public OpenFileDialog setAccessDeniedMessage(String message) {\n        this.accessDeniedMessage = message;\n        return this;\n    }\n\n    private static Display getDefaultDisplay(Context context) {\n        return ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();\n    }\n\n    private static Point getScreenSize(Context context) {\n        Point screeSize = new Point();\n//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {\n        getDefaultDisplay(context).getSize(screeSize);\n//        } else {\n//            screeSize.set( getDefaultDisplay(context).getWidth(), getDefaultDisplay(context).getHeight() );\n//        }\n        return screeSize;\n    }\n\n    private static int getLinearLayoutMinHeight(Context context) {\n        return getScreenSize(context).y;\n    }\n\n    private LinearLayout createMainLayout(Context context) {\n        LinearLayout linearLayout = new LinearLayout(context);\n        linearLayout.setOrientation(LinearLayout.VERTICAL);\n        linearLayout.setMinimumHeight(getLinearLayoutMinHeight(context));\n        return linearLayout;\n    }\n\n    private LinearLayout createMenuLayout(Context context) {\n        LinearLayout linearLayout = new LinearLayout(context);\n        linearLayout.setOrientation(LinearLayout.HORIZONTAL);\n        return linearLayout;\n    }\n\n    private int getItemHeight(Context context) {\n        TypedValue value = new TypedValue();\n        DisplayMetrics metrics = new DisplayMetrics();\n        context.getTheme().resolveAttribute(android.R.attr.listPreferredItemHeightSmall, value, true);\n        getDefaultDisplay(context).getMetrics(metrics);\n        return (int) TypedValue.complexToDimension(value.data, metrics);\n    }\n\n    private TextView createTextView(Context context, int style) {\n        TextView textView = new TextView(context);\n        //textView.setTextAppearance(context, style);\n        int itemHeight = getItemHeight(context);\n        textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, itemHeight));\n        textView.setMinHeight(itemHeight);\n        textView.setGravity(Gravity.CENTER_VERTICAL);\n        textView.setPadding(15, 0, 0, 0);\n        return textView;\n    }\n\n    private TextView createTitle(Context context) {\n        return createTextView(context, android.R.style.TextAppearance_DeviceDefault_DialogWindowTitle);\n    }\n\n    private TextView createBackItem(Context context) {\n        TextView textView = createTextView(context, android.R.style.TextAppearance_DeviceDefault_Small);\n        Drawable drawable = ResourcesCompat.getDrawable(getContext().getResources(), R.drawable.ic_menu_back, context.getTheme());\n        assert drawable != null;\n        drawable.setBounds(0, 0, 60, 60);\n        textView.setCompoundDrawables(drawable, null, null, null);\n        textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));\n        textView.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                File file = new File(currentPath);\n                File parentDirectory = file.getParentFile();\n                if (parentDirectory != null) {\n                    currentPath = parentDirectory.getPath();\n                    RebuildFiles(((FileAdapter) listView.getAdapter()));\n                }\n            }\n        });\n        return textView;\n    }\n\n    private TextView createGoHomeItem(Context context) {\n        TextView textView = createTextView(context, android.R.style.TextAppearance_DeviceDefault_Small);\n        Drawable drawable = ResourcesCompat.getDrawable(getContext().getResources(), R.drawable.ic_menu_home, context.getTheme());\n        assert drawable != null;\n        drawable.setBounds(0, 0, 60, 60);\n        textView.setCompoundDrawables(drawable, null, null, null);\n        textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));\n        textView.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                currentPath = Environment.getExternalStorageDirectory().getPath();\n                RebuildFiles(((FileAdapter) listView.getAdapter()));\n            }\n        });\n        return textView;\n    }\n\n    private TextView createGoInitPathItem(Context context) {\n        TextView textView = createTextView(context, android.R.style.TextAppearance_DeviceDefault_Small);\n        Drawable drawable = ResourcesCompat.getDrawable(getContext().getResources(), android.R.drawable.ic_menu_directions, context.getTheme());\n        assert drawable != null;\n        drawable.setBounds(0, 0, 60, 60);\n        textView.setCompoundDrawables(drawable, null, null, null);\n        textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));\n        textView.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                currentPath = initialPath;\n                RebuildFiles(((FileAdapter) listView.getAdapter()));\n            }\n        });\n        return textView;\n    }\n\n    private int getTextWidth(String text, Paint paint) {\n        Rect bounds = new Rect();\n        paint.getTextBounds(text, 0, text.length(), bounds);\n        return bounds.left + bounds.width() + 80;\n    }\n\n    private void changeTitle() {\n        String titleText = currentPath;\n        int screenWidth = getScreenSize(getContext()).x;\n        int maxWidth = (int) (screenWidth * 0.99);\n        if (getTextWidth(titleText, title.getPaint()) > maxWidth) {\n            while (getTextWidth(\"...\" + titleText, title.getPaint()) > maxWidth) {\n                int start = titleText.indexOf(\"/\", 2);\n                if (start > 0)\n                    titleText = titleText.substring(start);\n                else\n                    titleText = titleText.substring(2);\n            }\n            title.setText( String.format(\"...%s\", titleText) );\n        } else {\n            title.setText(titleText);\n        }\n    }\n\n    private List<File> getFiles(String directoryPath) {\n        File directory = new File(directoryPath);\n        File[] list = directory.listFiles(filenameFilter);\n        if(list == null)\n            list = new File[]{};\n        List<File> fileList = Arrays.asList(list);\n        Collections.sort(fileList, new Comparator<File>() {\n            @Override\n            public int compare(File file, File file2) {\n                if (file.isDirectory() && file2.isFile())\n                    return -1;\n                else if (file.isFile() && file2.isDirectory())\n                    return 1;\n                else\n                    return file.getPath().compareToIgnoreCase(file2.getPath());\n            }\n        });\n        return fileList;\n    }\n\n    private void RebuildFiles(ArrayAdapter<File> adapter) {\n        try {\n            List<File> fileList = getFiles(currentPath);\n            files.clear();\n            selectedIndex = -1;\n            files.addAll(fileList);\n            adapter.notifyDataSetChanged();\n            changeTitle();\n        } catch (NullPointerException e) {\n            String message = getContext().getResources().getString(android.R.string.unknownName);\n            if (!accessDeniedMessage.equals(\"\"))\n                message = accessDeniedMessage;\n            Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();\n        }\n    }\n\n    private ListView createListView(Context context) {\n        ListView listView = new ListView(context);\n        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n\n            @Override\n            public void onItemClick(AdapterView<?> adapterView, View view, int index, long l) {\n                final ArrayAdapter<File> adapter = (FileAdapter) adapterView.getAdapter();\n                File file = adapter.getItem(index);\n                assert file != null;\n                if (file.isDirectory()) {\n                    currentPath = file.getPath();\n                    RebuildFiles(adapter);\n                } else {\n                    if (index != selectedIndex)\n                        selectedIndex = index;\n                    else\n                        selectedIndex = -1;\n                    adapter.notifyDataSetChanged();\n                }\n            }\n        });\n        return listView;\n    }\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/ru/wohlsoft/thextech/UIUpdater.java",
    "content": "package ru.wohlsoft.thextech;\n\nimport android.os.Handler;\nimport android.os.Looper;\n\n/**\n * A class used to perform periodical updates,\n * specified inside a runnable object. An update interval\n * may be specified (otherwise, the class will perform the\n * update every 2 seconds).\n *\n * @author Carlos Simões\n *\n * https://stackoverflow.com/a/14234984\n */\npublic class UIUpdater\n{\n    // Create a Handler that uses the Main Looper to run in\n    private final Handler mHandler = new Handler(Looper.getMainLooper());\n\n    private Runnable mStatusChecker;\n    private int UPDATE_INTERVAL = 2000;\n\n    /**\n     * Creates an UIUpdater object, that can be used to\n     * perform UIUpdates on a specified time interval.\n     *\n     * @param uiUpdater A runnable containing the update routine.\n     */\n    public UIUpdater(final Runnable uiUpdater)\n    {\n        init(uiUpdater);\n    }\n\n    /**\n     * The same as the default constructor, but specifying the\n     * intended update interval.\n     *\n     * @param uiUpdater A runnable containing the update routine.\n     * @param interval  The interval over which the routine\n     *                  should run (milliseconds).\n     */\n    public UIUpdater(Runnable uiUpdater, int interval)\n    {\n        UPDATE_INTERVAL = interval;\n        init(uiUpdater);\n    }\n\n    private void init(final Runnable uiUpdater)\n    {\n        mStatusChecker = new Runnable()\n        {\n            @Override\n            public void run()\n            {\n                // Run the passed runnable\n                uiUpdater.run();\n                // Re-run it after the update interval\n                mHandler.postDelayed(this, UPDATE_INTERVAL);\n            }\n        };\n    }\n\n    public void setInterval(int interval)\n    {\n        UPDATE_INTERVAL = interval;\n    }\n\n    /**\n     * Starts the periodical update routine (mStatusChecker\n     * adds the callback to the handler).\n     */\n    public synchronized void startUpdates()\n    {\n        mStatusChecker.run();\n    }\n\n    /**\n     * Stops the periodical update routine from running,\n     * by removing the callback.\n     */\n    public synchronized void stopUpdates()\n    {\n        mHandler.removeCallbacks(mStatusChecker);\n    }\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/java/ru/wohlsoft/thextech/thextechActivity.java",
    "content": "package ru.wohlsoft.thextech;\n\nimport android.app.AlertDialog;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.ActivityInfo;\nimport android.os.Bundle;\nimport android.os.Environment;\nimport android.text.InputType;\nimport android.util.DisplayMetrics;\nimport android.widget.EditText;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\n\nimport org.libsdl.app.SDLActivity;\n\nimport androidx.preference.PreferenceManager;\n\nenum ControllerKeys\n{\n    key_BEGIN(0),\n    key_start(0),\n    key_left(1),\n    key_right(2),\n    key_up(3),\n    key_down(4),\n    key_run(5),\n    key_jump(6),\n    key_altrun(7),\n    key_altjump(8),\n    key_drop(9),\n    key_END(10);\n\n    private final int value;\n\n    ControllerKeys(final int newValue)\n    {\n        value = newValue;\n    }\n\n    public int getValue()\n    {\n        return value;\n    }\n}\n\npublic class thextechActivity extends SDLActivity\n{\n    static boolean gameRunning = false;\n    private String levelToRun;\n    private boolean editRequested = false;\n\n    protected String[] getLibraries()\n    {\n        return new String[] {\n//            \"c++_shared\",\n//            \"hidapi\",\n//            \"SDL2\",\n            \"thextech\"\n        };\n    }\n\n    private void detectLanguage()\n    {\n        String lang = Locale.getDefault().toString();\n        String[] langD = lang.split(\"_\");\n        if(langD.length >= 2)\n            setLanguageCodes(langD[0], langD[1]);\n        else if(langD.length == 1)\n            setLanguageCodes(langD[0], \"\");\n    }\n\n    protected String[] getArguments()\n    {\n        List<String> args = new ArrayList<>();\n        args.add(\"thextech\"); // fake %0\n\n        SharedPreferences setup = PreferenceManager.getDefaultSharedPreferences(getBaseContext());\n        if(setup.getBoolean(\"enable_frame_skip\", false))\n            args.add(\"--frameskip\");\n        if(setup.getBoolean(\"disable_sound\", false))\n            args.add(\"--no-sound\");\n        if(setup.getBoolean(\"show_fps\", false))\n            args.add(\"--show-fps\");\n        if(setup.getBoolean(\"enable_max_fps\", false))\n            args.add(\"--max-fps\");\n\n        if(setup.getBoolean(\"setup_show_controller_state\", false))\n            args.add(\"--show-controls\");\n\n        String renderer = setup.getString(\"setup_renderer\", \"\");\n        if(!renderer.isEmpty())\n        {\n            args.add(\"--render\");\n            args.add(renderer);\n        }\n\n        int showBatteryStatus = Integer.parseInt(setup.getString(\"setup_show_battery_status\", \"0\"));\n        if(showBatteryStatus > 0)\n        {\n            args.add(\"--show-battery-status\");\n            args.add(String.valueOf(showBatteryStatus));\n        }\n\n        int speedRunMode = Integer.parseInt(setup.getString(\"setup_speedRunMode\", \"0\"));\n        if(speedRunMode > 0)\n        {\n            args.add(\"--speed-run-mode\");\n            args.add(String.valueOf(speedRunMode));\n\n            if(setup.getBoolean(\"setup_sr_showStopwatchTransparent\", false))\n                args.add(\"--speed-run-semitransparent\");\n        }\n\n        if(editRequested)\n            args.add(\"-e\");\n\n        if(!levelToRun.isEmpty())\n            args.add(levelToRun);\n\n        String gameAssetsPath = setup.getString(\"setup_assets_path\", \"\");\n        if(!gameAssetsPath.isEmpty())\n        {\n            File f = new File(gameAssetsPath);\n            if (f.exists() && f.isDirectory()) {\n                setGameAssetsPath(gameAssetsPath);\n            }\n        }\n\n        String[] argsOut = new String[args.size()];\n        args.toArray(argsOut);\n\n        return argsOut;\n    }\n\n    @Override\n    protected void onStart()\n    {\n        super.onStart();\n        gameRunning = true;\n        levelToRun = \"\";\n        // Detect current language of the system\n        detectLanguage();\n        Intent intent = getIntent();\n        Bundle extras = intent.getExtras();\n        if(extras != null)\n        {\n            if(extras.containsKey(\"do-open-file\"))\n                levelToRun = extras.getString(\"do-open-file\");\n\n            if(extras.containsKey(\"edit-requested\"))\n                editRequested = true;\n        }\n    }\n\n\n    @Override\n    public void onStop()\n    {\n        super.onStop();\n        gameRunning = false;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState)\n    {\n        super.onCreate(savedInstanceState);\n        this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n        setSdCardPath(Environment.getExternalStorageDirectory().getAbsolutePath());\n        setAppDataPath(getApplication().getApplicationContext().getFilesDir().getAbsolutePath());\n\n        DisplayMetrics displayMetrics = new DisplayMetrics();\n        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);\n        setScreenSize(SDLActivity.getDiagonal(), displayMetrics.widthPixels, displayMetrics.heightPixels);\n    }\n\n    @Override\n    protected void onDestroy()\n    {\n        super.onDestroy();\n        // Completely close the application.\n        System.exit(0);\n    }\n\n    private void requestTextShow()\n    {\n        AlertDialog.Builder builder = new AlertDialog.Builder(this);\n        builder.setTitle(R.string.cheat_dialog_title);\n\n        // Set up the input\n        final EditText input = new EditText(this);\n\n        input.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);\n        builder.setView(input);\n\n        builder.setPositiveButton(R.string.cheat_dialog_ok, new DialogInterface.OnClickListener()\n        {\n            @Override\n            public void onClick(DialogInterface dialog, int which)\n            {\n                textentry_setBuffer(input.getText().toString());\n                messageboxSelection[0] = 1;\n                dialog.dismiss();\n            }\n        });\n\n        builder.setNegativeButton(R.string.cheat_dialog_cancel, new DialogInterface.OnClickListener()\n        {\n            @Override\n            public void onClick(DialogInterface dialog, int which) {\n                messageboxSelection[0] = 1;\n                dialog.cancel();\n            }\n        });\n\n        final AlertDialog dialog = builder.create();\n        dialog.setCancelable(false);\n        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {\n            @Override\n            public void onDismiss(DialogInterface unused) {\n                synchronized (messageboxSelection) {\n                    messageboxSelection.notify();\n                }\n            }\n        });\n\n        dialog.show();\n    }\n\n    public void requestText()\n    {\n        messageboxSelection[0] = -1;\n\n        runOnUiThread(new Runnable()\n        {\n            @Override\n            public void run() {\n                requestTextShow();\n            }\n        });\n\n        synchronized (messageboxSelection) {\n            try {\n                messageboxSelection.wait();\n            } catch (InterruptedException ex) {\n                ex.printStackTrace();\n            }\n        }\n    }\n\n    public static native void setScreenSize(double screenSize, double width, double height);\n    public static native void setSdCardPath(String path);\n    public static native void setAppDataPath(String path);\n    public static native void setGameAssetsPath(String path);\n    // Send the cheat buffer line\n    public static native void textentry_setBuffer(String line);\n    // set language settings\n    public static native void setLanguageCodes(String lang, String country);\n}\n"
  },
  {
    "path": "android-project/thextech/src/main/res/layout/activity_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/LauncherLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/background\"\n    android:orientation=\"horizontal\"\n    tools:context=\".Launcher\">\n\n    <Button\n        android:id=\"@+id/startGame\"\n        android:layout_width=\"127dp\"\n        android:layout_height=\"77dp\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_marginStart=\"75dp\"\n        android:layout_marginLeft=\"75dp\"\n        android:layout_marginBottom=\"47dp\"\n        android:saveEnabled=\"true\"\n        android:text=\"@string/start_game_button\" />\n\n    <Button\n        android:id=\"@+id/gameSettings\"\n        android:layout_width=\"127dp\"\n        android:layout_height=\"77dp\"\n        android:layout_alignParentStart=\"false\"\n        android:layout_alignParentLeft=\"false\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_marginStart=\"225dp\"\n        android:layout_marginLeft=\"225dp\"\n        android:layout_marginEnd=\"81dp\"\n        android:layout_marginRight=\"81dp\"\n        android:layout_marginBottom=\"47dp\"\n        android:saveEnabled=\"true\"\n        android:text=\"@string/settings_button\" />\n\n    <ImageView\n        android:id=\"@+id/gameLogo\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_marginStart=\"99dp\"\n        android:layout_marginLeft=\"99dp\"\n        android:layout_marginTop=\"15dp\"\n        android:layout_marginEnd=\"99dp\"\n        android:layout_marginRight=\"99dp\"\n        android:layout_marginBottom=\"160dp\"\n        android:contentDescription=\"@string/game_logo_label\"\n        android:saveEnabled=\"true\"\n        android:scaleType=\"fitCenter\"\n        app:srcCompat=\"@drawable/logo\" />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/selectGame\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginEnd=\"14dp\"\n        android:layout_marginRight=\"14dp\"\n        android:clickable=\"true\"\n        app:backgroundTint=\"#FFFFFF\"\n        app:srcCompat=\"@android:drawable/ic_menu_add\" />\n\n</RelativeLayout>"
  },
  {
    "path": "android-project/thextech/src/main/res/layout/settings_activity.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <FrameLayout\n        android:id=\"@+id/settings\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n</LinearLayout>"
  },
  {
    "path": "android-project/thextech/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string-array name=\"touchscreenControllerTypes\">\n        <item>@string/touchscreenControllerType0</item>\n        <item>@string/touchscreenControllerType1</item>\n        <item>@string/touchscreenControllerType2</item>\n    </string-array>\n    <string-array name=\"touchscreenControllerTypesValues\">\n        <item>2</item>\n        <item>1</item>\n        <item>0</item>\n    </string-array>\n    <string name=\"touchscreenControllerType0\">Always enable</string>\n    <string name=\"touchscreenControllerType1\">Disable when hardware keyboard is plugged</string>\n    <string name=\"touchscreenControllerType2\">Always disable</string>\n    <string-array name=\"speedRunModes\">\n        <item>@string/speedRunMode0</item>\n        <item>@string/speedRunMode1</item>\n        <item>@string/speedRunMode2</item>\n        <item>@string/speedRunMode3</item>\n    </string-array>\n    <string-array name=\"speedRunModesValues\">\n        <item>0</item>\n        <item>1</item>\n        <item>2</item>\n        <item>3</item>\n    </string-array>\n    <string name=\"speedRunMode0\">Off</string>\n    <string name=\"speedRunMode1\">Mode 1 (TheXTech native)</string>\n    <string name=\"speedRunMode2\">Mode 2 (Disable time-winning features)</string>\n    <string name=\"speedRunMode3\">Mode 3 (Strict SMBX 1.3, all bugs enabled)</string>\n    <string-array name=\"touchPadStyles\">\n        <item>@string/touchPadStyle0</item>\n        <item>@string/touchPadStyle1</item>\n        <item>@string/touchPadStyle2</item>\n    </string-array>\n    <string-array name=\"touchPadStylesValues\">\n        <item>0</item>\n        <item>1</item>\n        <item>2</item>\n    </string-array>\n    <string name=\"touchPadStyle0\">In-Game Actions</string>\n    <string name=\"touchPadStyle1\">Nintendo/Xbox style (A/B/X/Y)</string>\n    <string name=\"touchPadStyle2\">PlayStation style (X/O/D/A</string>\n    <string-array name=\"batteryStatusShow\">\n        <item>@string/batteryStatusShow0</item>\n        <item>@string/batteryStatusShow1</item>\n        <item>@string/batteryStatusShow2</item>\n    </string-array>\n    <string-array name=\"batteryStatusShowValues\">\n        <item>0</item>\n        <item>2</item>\n        <item>4</item>\n    </string-array>\n    <string name=\"batteryStatusShow0\">Never show</string>\n    <string name=\"batteryStatusShow1\">Show when low battery</string>\n    <string name=\"batteryStatusShow2\">Show always</string>\n    <string-array name=\"renderer\">\n        <item>@string/rendererDefault</item>\n        <item>@string/rendererSdl</item>\n        <item>@string/rendererGles</item>\n        <item>@string/rendererGles11</item>\n    </string-array>\n    <string-array name=\"rendererValues\">\n        <item></item>\n        <item>sdl</item>\n        <item>opengles</item>\n        <item>opengles11</item>\n    </string-array>\n    <string name=\"rendererDefault\">Default</string>\n    <string name=\"rendererSdl\" translatable=\"false\">SDL2</string>\n    <string name=\"rendererGles\" translatable=\"false\">OpenGL ES 2.0+</string>\n    <string name=\"rendererGles11\" translatable=\"false\">OpenGL ES 1.1</string>\n    <string-array name=\"feedBackStrength\">\n        <item>0.1</item>\n        <item>0.2</item>\n        <item>0.3</item>\n        <item>0.4</item>\n        <item>0.5</item>\n        <item>0.6</item>\n        <item>0.7</item>\n        <item>0.8</item>\n        <item>0.9</item>\n        <item>1.0</item>\n    </string-array>\n    <string-array name=\"feedBackStrengthValues\">\n        <item>0.1</item>\n        <item>0.2</item>\n        <item>0.3</item>\n        <item>0.4</item>\n        <item>0.5</item>\n        <item>0.6</item>\n        <item>0.7</item>\n        <item>0.8</item>\n        <item>0.9</item>\n        <item>1.0</item>\n    </string-array>\n    <string-array name=\"feedBackLength\">\n        <item>5</item>\n        <item>6</item>\n        <item>7</item>\n        <item>8</item>\n        <item>9</item>\n        <item>10</item>\n        <item>11</item>\n        <item>12</item>\n        <item>13</item>\n        <item>14</item>\n        <item>15</item>\n        <item>16</item>\n        <item>17</item>\n        <item>18</item>\n        <item>19</item>\n        <item>20</item>\n        <item>21</item>\n        <item>22</item>\n        <item>23</item>\n        <item>24</item>\n        <item>25</item>\n        <item>26</item>\n        <item>27</item>\n        <item>28</item>\n        <item>29</item>\n        <item>30</item>\n    </string-array>\n    <string-array name=\"feedBackLengthValues\">\n        <item>5</item>\n        <item>6</item>\n        <item>7</item>\n        <item>8</item>\n        <item>9</item>\n        <item>10</item>\n        <item>11</item>\n        <item>12</item>\n        <item>13</item>\n        <item>14</item>\n        <item>15</item>\n        <item>16</item>\n        <item>17</item>\n        <item>18</item>\n        <item>19</item>\n        <item>20</item>\n        <item>21</item>\n        <item>22</item>\n        <item>23</item>\n        <item>24</item>\n        <item>25</item>\n        <item>26</item>\n        <item>27</item>\n        <item>28</item>\n        <item>29</item>\n        <item>30</item>\n    </string-array>\n</resources>"
  },
  {
    "path": "android-project/thextech/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">TheXTech</string>\n    <string name=\"title_activity_game_settings\">Game settings</string>\n    <string name=\"start_game_button\">Start game</string>\n    <string name=\"settings_button\">Settings</string>\n    <string name=\"game_logo_label\">Game logo</string>\n    <string name=\"settings_enable_frameskip\">Enable frame skipping</string>\n    <string name=\"setup_disable_sound\">Disable sound</string>\n    <string name=\"setup_general_cat\">General</string>\n    <string name=\"setup_control_cat\">Touchscreen controller</string>\n    <string name=\"setup_display_cat\">Display</string>\n    <string name=\"setup_touchscreen_controller_enable\">Use touch-screen controller</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Show the touchscreen controller on start</string>\n    <string name=\"setup_show_framerate\">Show frame-rate value</string>\n    <string name=\"setup_max_framerate_mode\">Max frame-rate mode</string>\n    <string name=\"setup_touchscreen_mode\">Controller mode</string>\n    <string name=\"setup_sr_speedrunMode\">Speed-Run Mode</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Make stopwatch being semi-transparent</string>\n    <string name=\"setup_speedrunner\">Speed-runner</string>\n    <string name=\"setup_touchpad_style\">Controller style</string>\n    <string name=\"managePermExplainTitle\">All files access permission is required</string>\n    <string name=\"managePermExplainText\">This game uses resources (such as levels, episodes, graphics, music, sounds, configs, etc.) that were placed by the user on the local store directly. This concept allows users to modify and extend the content for their own purposes like that was done at the desktop version of this game. Unfortunately, Android 11+ doesn\\'t give the way to grant individual read permission for the specified directory instead of a single file. To run this game you should grant access to all files for the game because of explained reasons.</string>\n    <string name=\"setup_assets_path_label\">Change the resources absolute path</string>\n    <string name=\"launcher_no_resources_question\">The game requires a resource package for the work. Please download the game resources and unpack them at any convenient directory. Do you want to select an already unpacked folder\\?</string>\n    <string name=\"setup_feedback_enable_vibration\">Enable vibration</string>\n    <string name=\"setup_feedback_vibration_strength\">Vibration strength</string>\n    <string name=\"setup_feedback_vibration_length\">Vibration length</string>\n    <string name=\"setup_feedback\">Feedback</string>\n    <string name=\"touchscreen_vibration_test_label\">Test feedback</string>\n    <string name=\"setup_show_battery_status_label\">Device battery status</string>\n    <string name=\"setup_renderer_label\">Render system</string>\n    <string name=\"setup_show_controller_state_label\">Show a game controller state</string>\n    <string name=\"cheat_dialog_cancel\">Cancel</string>\n    <string name=\"cheat_dialog_ok\">Run</string>\n    <string name=\"cheat_dialog_title\">Type command:</string>\n    <string name=\"launcher_gameSelect_clearList\">Clear list</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">Select directory...</string>\n    <string name=\"launcher_gameselect_toast_selected\">Switched the game: %s</string>\n    <string name=\"launcher_gameselect_toast_listclean\">The list of games has been clean.</string>\n    <string name=\"launcher_gameselect_clearlist_title\">Clear the games list</string>\n    <string name=\"launcher_gameselect_clearlist_question\">Do you really want to clear the list of games?</string>\n    <string name=\"launcher_openfile_indirectly\">Can\\'t retrieve a direct absolute path for the opened file, and therefore, it was coped into the internal storage of the game. Extra resources (such as custom graphics, music, configs, scripts, etc.) can\\'t be loaded. Do you want to start the game in any way\\?</string>\n</resources>"
  },
  {
    "path": "android-project/thextech/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"android:Theme.Holo.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-bg/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    </resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-bg/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"start_game_button\">Започни игра</string>\n    <string name=\"game_logo_label\">Лого на играта</string>\n    <string name=\"title_activity_game_settings\">Настройки на играта</string>\n    <string name=\"setup_disable_sound\">Забраняване на звука</string>\n    <string name=\"setup_general_cat\">Основни</string>\n    <string name=\"setup_control_cat\">Сензорен контролер</string>\n    <string name=\"setup_display_cat\">Дисплей</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Направете хронометъра полупрозрачен</string>\n    <string name=\"setup_speedrunner\">Спийдрънер</string>\n    <string name=\"setup_touchpad_style\">Стил на контролера</string>\n    <string name=\"setup_assets_path_label\">Промяна на абсолютният път на ресурсите</string>\n    <string name=\"launcher_no_resources_question\">Играта изисква ресурсен пакет за да работи. Моля, изтеглете ресурсите на играта и ги разопаковайте във всяка удобна директория. Искате ли да изберете вече разопакована папка?</string>\n    <string name=\"setup_feedback_enable_vibration\">Разрешаване на вибрация</string>\n    <string name=\"setup_feedback_vibration_strength\">Сила на вибрация</string>\n    <string name=\"setup_feedback_vibration_length\">Дължина на вибрация</string>\n    <string name=\"setup_feedback\">Обратна връзка</string>\n    <string name=\"touchscreen_vibration_test_label\">Изпробване на обратна връзка</string>\n    <string name=\"settings_button\">Настройки</string>\n    <string name=\"settings_enable_frameskip\">Разрешаване на пропускане на кадри</string>\n    <string name=\"setup_show_framerate\">Показване на стойността на кадровата честота</string>\n    <string name=\"setup_touchscreen_controller_enable\">Използване на сензорен контролер</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Показване на сензорният контролер при стартиране</string>\n    <string name=\"setup_touchscreen_mode\">Режим на контролера</string>\n    <string name=\"setup_max_framerate_mode\">Режим на максимална кадрова честота</string>\n    <string name=\"managePermExplainTitle\">Изисква се разрешение за достъп до всички файлове</string>\n    <string name=\"setup_sr_speedrunMode\">Режим на спийдрън</string>\n    <string name=\"managePermExplainText\">Тази игра използва ресурси (като нива, епизоди, графики, музика, звуци, настройки и т.н.), които са били поставени от потребителя директно в местното хранилище. Тази концепция позволява на потребителите да променят и разширяват съдържанието за свои собствени цели, точно както в настолната версия на тази игра. За съжаление Android 11+ не дава възможност да се предоставят индивидуални права за четене на определена директория вместо на отделен файл. За да стартирате тази игра, трябва да дадете разрешение за достъп до всички файлове за играта поради обяснените причини.</string>\n    <string name=\"setup_show_controller_state_label\">Показване на състоянието на игровия контролер</string>\n    <string name=\"cheat_dialog_title\">Въведете команда:</string>\n    <string name=\"cheat_dialog_ok\">Изпълнение</string>\n    <string name=\"setup_show_battery_status_label\">Състояние на батерията на устройството</string>\n    <string name=\"cheat_dialog_cancel\">Отказ</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-de/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType2\">Immer deaktiviert</string>\n    <string name=\"touchscreenControllerType1\">Deaktivieren wenn Hardware Tastatur angeschlossen ist</string>\n    <string name=\"speedRunMode1\">Modus 1 (TheXTech Nativ)</string>\n    <string name=\"speedRunMode2\">Modus 2 (Deaktiviert Zeit-Gewinn Funktionen)</string>\n    <string name=\"touchPadStyle1\">Nintendo/Xbox Stil (A/B/X/Y)</string>\n    <string name=\"touchPadStyle2\">PlayStation Stil (X/O/D/A)</string>\n    <string name=\"touchPadStyle0\">Aktionen im Spiel</string>\n    <string name=\"batteryStatusShow0\">Niemals anzeigen</string>\n    <string name=\"batteryStatusShow1\">Anzeigen wenn schwaches Batterie</string>\n    <string name=\"batteryStatusShow2\">Immer anzeigen</string>\n    <string name=\"rendererDefault\">Standard</string>\n    <string name=\"touchscreenControllerType0\">Immer aktiviert</string>\n    <string name=\"speedRunMode0\">Aus</string>\n    <string name=\"speedRunMode3\">Modus 3 (Striktes SMBX 1.3, alle Fehlern aktiviert)</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"title_activity_game_settings\">Spieleinstellungen</string>\n    <string name=\"start_game_button\">Spiel starten</string>\n    <string name=\"settings_button\">Einstellungen</string>\n    <string name=\"game_logo_label\">Spiellogo</string>\n    <string name=\"settings_enable_frameskip\">Aktiviere Frameüberspringung</string>\n    <string name=\"setup_disable_sound\">Deaktiviere Ton</string>\n    <string name=\"setup_general_cat\">Allgemein</string>\n    <string name=\"setup_control_cat\">Touchscreen Steuerung</string>\n    <string name=\"setup_display_cat\">Anzeige</string>\n    <string name=\"setup_touchscreen_controller_enable\">Benutze Touchscreen Steuerung</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Zeige Touchscreen Steuerung beim Starten</string>\n    <string name=\"setup_show_framerate\">Zeige Framerate Wert</string>\n    <string name=\"setup_max_framerate_mode\">Maximaler Framerate Modus</string>\n    <string name=\"setup_touchscreen_mode\">Steuerungsmodus</string>\n    <string name=\"setup_sr_speedrunMode\">Geschwindigkeitsmodus</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Stoppuhr Halbtransparenz anzeigen</string>\n    <string name=\"setup_speedrunner\">Geschwindigkeitsläufer</string>\n    <string name=\"setup_touchpad_style\">Steuerungsstil</string>\n    <string name=\"setup_feedback_enable_vibration\">Vibration aktivieren</string>\n    <string name=\"setup_feedback_vibration_strength\">Vibrationsstärke</string>\n    <string name=\"setup_feedback_vibration_length\">Vibrationsdauer</string>\n    <string name=\"setup_feedback\">Vibration</string>\n    <string name=\"touchscreen_vibration_test_label\">Teste Vibration</string>\n    <string name=\"setup_show_battery_status_label\">Gerätbatterienstatus</string>\n    <string name=\"cheat_dialog_cancel\">Abbruch</string>\n    <string name=\"managePermExplainTitle\">Alle Dateien-Zugriffsberechtigungen sind erforderlich</string>\n    <string name=\"launcher_no_resources_question\">Das Spiel braucht ein Ressourcenpaket fürs abspielen. Bitte den Spiel-Ressourcen herunterladen, entpacke und platziere es an einem bequemen Verzeichnis. Möchtest du den entpackten Ordner aussuchen?</string>\n    <string name=\"setup_assets_path_label\">Ändere absoluten Weg der Ressourcen</string>\n    <string name=\"managePermExplainText\">Dieses Spiel verwendet Ressourcen (z. B. Niveaus, Episoden, Grafiken, Musik, Sounds, Konfigurationen usw.), die vom Benutzer direkt im lokalen Speicher platziert wurden. Mit diesem Konzept können Benutzer den Inhalt für ihre eigenen Zwecke ändern und erweitern, die in der Desktop Version dieses Spiels durchgeführt werden. Leider gibt Android 11+ nicht die Möglichkeit, die individuelle Lese-Erlaubnis für das angegebene Verzeichnis anstelle einer einzelnen Datei zu erteilen. Um dieses Spiel auszuführen, solltest du aus den erklärten Gründen Zugriff auf alle Dateien für das Spiel gewähren.</string>\n    <string name=\"setup_show_controller_state_label\">Zeige Spiel-Kontroller Zustand</string>\n    <string name=\"setup_renderer_label\">Rendersystem</string>\n    <string name=\"cheat_dialog_ok\">Ausführen</string>\n    <string name=\"cheat_dialog_title\">Befehl eingeben:</string>\n    <string name=\"launcher_gameSelect_clearList\">Liste löschen</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">Verzeichnis auswählen...</string>\n    <string name=\"launcher_gameselect_toast_selected\">Spiel geändert zu: %s</string>\n    <string name=\"launcher_gameselect_toast_listclean\">Die Spieleliste wurde geleert.</string>\n    <string name=\"launcher_gameselect_clearlist_title\">Die Spieleliste leeren</string>\n    <string name=\"launcher_gameselect_clearlist_question\">Willst du sicher die Spieleliste leeren?</string>\n    <string name=\"launcher_openfile_indirectly\">Kann keinen absoluten Pfad für die geöffnete Datei finden, weshalb sie in den internen Spiele-Ordner verschoben wurde. Extra Ressourcen (so wie eigene Grafiken, Musik, Configs, Scripte usw.) können nicht geladen werden. Möchtest du das Spiel trotzdem starten?</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-es-rES/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType0\">Activar siempre</string>\n    <string name=\"touchscreenControllerType1\">Desactivar si se ha conectado un teclado fisico</string>\n    <string name=\"touchscreenControllerType2\">Desactivar siempre</string>\n    <string name=\"speedRunMode0\">Apagado</string>\n    <string name=\"speedRunMode1\">Modo 1 (Motor X-Tech nativo)</string>\n    <string name=\"speedRunMode2\">Modo 2 (Desactivar funciones que dan ventaja)</string>\n    <string name=\"speedRunMode3\">Modo 3 (Simular SMBX 1.3 original, errores incluidos)</string>\n    <string name=\"touchPadStyle0\">Acciones del juego</string>\n    <string name=\"touchPadStyle1\">Estilo Nintendo/Xbox (A/B/X/Y)</string>\n    <string name=\"touchPadStyle2\">Estilo PlayStation (X/O/D/A)</string>\n    <string name=\"batteryStatusShow0\">Nunca mostrar</string>\n    <string name=\"batteryStatusShow1\">Mostrar con batería baja</string>\n    <string name=\"batteryStatusShow2\">Mostrar siempre</string>\n    <string name=\"rendererDefault\">Por defecto</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-es-rES/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"title_activity_game_settings\">Ajustes del juego</string>\n    <string name=\"start_game_button\">Jugar</string>\n    <string name=\"settings_button\">Ajustes</string>\n    <string name=\"game_logo_label\">Logo del Juego</string>\n    <string name=\"settings_enable_frameskip\">Activar omisión de fotogramas</string>\n    <string name=\"setup_disable_sound\">Desactivar sonido</string>\n    <string name=\"setup_general_cat\">General</string>\n    <string name=\"setup_control_cat\">Mando en pantalla</string>\n    <string name=\"setup_display_cat\">Mostrar</string>\n    <string name=\"setup_touchscreen_controller_enable\">Usar mando en pantalla</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Mostrar el mando en pantalla al iniciar el juego</string>\n    <string name=\"setup_show_framerate\">Mostrar valor de fotogramas por segundo</string>\n    <string name=\"setup_max_framerate_mode\">Máximos fotogramas por segundo</string>\n    <string name=\"setup_touchscreen_mode\">Modo de mando</string>\n    <string name=\"setup_sr_speedrunMode\">Modo Speed-Run</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Hacer el cronómetro translúcido</string>\n    <string name=\"setup_speedrunner\">Speed-runner</string>\n    <string name=\"setup_touchpad_style\">Estilo de mando en pantalla</string>\n    <string name=\"managePermExplainTitle\">Se requiere acceso total al almacenamiento</string>\n    <string name=\"managePermExplainText\">Este juego utiliza recursos (como niveles, episodios, gráficos, música, sonidos, configuraciones, etc.) que el usuario colocó directamente en la tienda local. Este concepto permite a los usuarios modificar y ampliar el contenido para sus propios fines, como se hizo en la versión de escritorio de este juego. Desafortunadamente, Android 11+ no permite otorgar permiso de lectura individual para el directorio especificado en lugar de para un solo archivo. Para ejecutar este juego, debes otorgar acceso a todos los archivos del juego por razones explicadas.</string>\n    <string name=\"setup_assets_path_label\">Cambiar la ruta de los recursos</string>\n    <string name=\"launcher_no_resources_question\">El juego requiere un paquete de recursos para el trabajo. Por favor descargue los recursos y descomprimirlos en cualquier directorio conveniente. ¿Desea seleccionar una carpeta ya descomprimida?</string>\n    <string name=\"setup_feedback_enable_vibration\">Activar la vibración</string>\n    <string name=\"setup_feedback_vibration_strength\">Fuerza de vibración</string>\n    <string name=\"cheat_dialog_cancel\">Cancelar</string>\n    <string name=\"cheat_dialog_ok\">Ejecutar</string>\n    <string name=\"cheat_dialog_title\">Escribir comando:</string>\n    <string name=\"setup_feedback\">Retroalimentación</string>\n    <string name=\"setup_feedback_vibration_length\">Duración de vibración</string>\n    <string name=\"touchscreen_vibration_test_label\">Probar retroalimentación</string>\n    <string name=\"setup_show_battery_status_label\">Estado de la batería</string>\n    <string name=\"setup_show_controller_state_label\">Mostrar un estado de controlador</string>\n    <string name=\"launcher_gameselect_clearlist_title\">Limpiar la lista de juegos</string>\n    <string name=\"launcher_gameSelect_clearList\">Limpiar lista</string>\n    <string name=\"launcher_gameselect_toast_selected\">Cambió el juego: %s</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">Seleccionar directorio...</string>\n    <string name=\"launcher_gameselect_toast_listclean\">La lista de juegos fue limpiada.</string>\n    <string name=\"launcher_gameselect_clearlist_question\">¿Desea limpiar la lista de juegos?</string>\n    <string name=\"setup_renderer_label\">Sistema de renderizado</string>\n    <string name=\"launcher_openfile_indirectly\">No se puede recuperar una ruta directa para el archivo abierto y, por lo tanto, se guardó en el almacenamiento interno del juego. No se pueden cargar los recursos adicionales (como gráficos personalizados, música, configuraciones, scripts, etc.). ¿Quieres iniciar el juego de igual manera?</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-fr/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    </resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"setup_feedback\">Réactions</string>\n    <string name=\"setup_show_controller_state_label\">Afficher l\\'état d\\'une manette de jeu</string>\n    <string name=\"setup_show_battery_status_label\">État de la batterie de l\\'appareil</string>\n    <string name=\"touchscreen_vibration_test_label\">Tester les réactions</string>\n    <string name=\"setup_feedback_vibration_length\">Durée des vibrations</string>\n    <string name=\"setup_feedback_vibration_strength\">Intensité des vibrations</string>\n    <string name=\"setup_feedback_enable_vibration\">Activer les vibrations</string>\n    <string name=\"launcher_no_resources_question\">Le jeu nécessite un paquet de ressources. Veuillez télécharger les ressources du jeu et les décompresser dans le répertoire de votre choix. Voulez-vous sélectionner un dossier déjà décompressé ?</string>\n    <string name=\"setup_assets_path_label\">Modifier le chemin absolu des ressources</string>\n    <string name=\"managePermExplainText\">Ce jeu utilise des ressources (comme des niveaux, des épisodes, des graphiques, de la musique, des sons, des configurations, etc.) qui ont été placées par l\\'utilisateur directement sur le magasin local. Ce concept permet aux utilisateurs de modifier et d\\'étendre le contenu pour leurs propres besoins, comme cela a été fait pour la version de bureau de ce jeu. Malheureusement, Android 11+ ne permet pas d\\'accorder des droits de lecture individuels pour le répertoire spécifié au lieu d\\'un seul fichier. Pour exécuter ce jeu, vous devez avoir accès à tous les fichiers du jeu pour les raisons expliquées.</string>\n    <string name=\"managePermExplainTitle\">L\\'autorisation d\\'accès à tous les fichiers est nécessaire</string>\n    <string name=\"setup_touchpad_style\">Style de manette</string>\n    <string name=\"setup_speedrunner\">Speed-runneur</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Rendre le chronomètre semi-transparent</string>\n    <string name=\"setup_sr_speedrunMode\">Mode speed-run</string>\n    <string name=\"setup_touchscreen_mode\">Mode manette</string>\n    <string name=\"setup_max_framerate_mode\">Mode de fréquence d\\'images maximal</string>\n    <string name=\"setup_show_framerate\">Afficher la valeur de la fréquence d\\'images</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Afficher la manette tactile au démarrage</string>\n    <string name=\"setup_touchscreen_controller_enable\">Utiliser la manette tactile</string>\n    <string name=\"setup_display_cat\">Affichage</string>\n    <string name=\"setup_control_cat\">Manette tactile</string>\n    <string name=\"setup_general_cat\">Général</string>\n    <string name=\"setup_disable_sound\">Désactiver le son</string>\n    <string name=\"settings_enable_frameskip\">Activer le saut de trame</string>\n    <string name=\"game_logo_label\">Logo du jeu</string>\n    <string name=\"settings_button\">Paramètres</string>\n    <string name=\"start_game_button\">Démarrer le jeu</string>\n    <string name=\"title_activity_game_settings\">Paramètres du jeu</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-hu/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"batteryStatusShow1\">Mutasd alacsony töltöttség esetén</string>\n    <string name=\"touchscreenControllerType0\">Mindig engedélyezve</string>\n    <string name=\"touchscreenControllerType1\">Letiltva, amikor fizikai billentyűzet csatlakoztatva van</string>\n    <string name=\"touchscreenControllerType2\">Mindig letiltva</string>\n    <string name=\"speedRunMode0\">Ki</string>\n    <string name=\"speedRunMode1\">1-es mód (TheXTech natív)</string>\n    <string name=\"speedRunMode2\">2-es mód (időnyerési funkciók letiltva)</string>\n    <string name=\"touchPadStyle0\">Játékbeli műveletek</string>\n    <string name=\"touchPadStyle1\">Nintendo/Xbox stílus (A,B,X,Y)</string>\n    <string name=\"touchPadStyle2\">PlayStation stílus (X,O,D,A)</string>\n    <string name=\"batteryStatusShow0\">Soha ne mutasd</string>\n    <string name=\"batteryStatusShow2\">Mindig mutatsd</string>\n    <string name=\"speedRunMode3\">3-as mód (nyers SMBX 1.3, minden bug engedélyezve)</string>\n    <string name=\"rendererDefault\">Alapértelmezett</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-hu/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"title_activity_game_settings\">Játékbeállítások</string>\n    <string name=\"start_game_button\">Játék indítása</string>\n    <string name=\"settings_button\">Beállítások</string>\n    <string name=\"game_logo_label\">Játék logó</string>\n    <string name=\"settings_enable_frameskip\">Képkockakihagyás engedélyezése</string>\n    <string name=\"setup_disable_sound\">Hang tiltása</string>\n    <string name=\"setup_general_cat\">Központi</string>\n    <string name=\"setup_control_cat\">Érintőképernyős vezérlés</string>\n    <string name=\"setup_display_cat\">Kijelző</string>\n    <string name=\"setup_touchscreen_controller_enable\">Érintőképernyős vezérlés használata</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Érintőképernyős vezérlés mutatása indításkor</string>\n    <string name=\"setup_show_framerate\">Képráta mutatása</string>\n    <string name=\"setup_max_framerate_mode\">Maximális képráta mód</string>\n    <string name=\"setup_touchscreen_mode\">Kontroller mód</string>\n    <string name=\"setup_sr_speedrunMode\">Sprintjáték mód</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Áttetsző stopperóra</string>\n    <string name=\"setup_touchpad_style\">Kontrollerstílus</string>\n    <string name=\"managePermExplainTitle\">Fájlhozzáférési jog szükséges</string>\n    <string name=\"setup_assets_path_label\">A források abszolút útvonalának megváltoztatása</string>\n    <string name=\"launcher_no_resources_question\">A játék bizonyos adatcsomagokat igényel működéséhez. Kérjük töltsd le és tömörítsd ki az adatokat a megfelelő könyvtárba. Szeretnél kiválasztani egy már kicsomagolt adatcsomagot?</string>\n    <string name=\"setup_feedback_enable_vibration\">Rezgés engedélyezése</string>\n    <string name=\"setup_feedback_vibration_strength\">Rezgés erőssége</string>\n    <string name=\"setup_feedback_vibration_length\">Rezgés hossza</string>\n    <string name=\"setup_feedback\">Visszajelzés</string>\n    <string name=\"touchscreen_vibration_test_label\">Visszajelzés próba</string>\n    <string name=\"setup_show_battery_status_label\">Eszköz akkumulátorállapota</string>\n    <string name=\"setup_show_controller_state_label\">A kontrollerállapot mutatása</string>\n    <string name=\"cheat_dialog_cancel\">Mégse</string>\n    <string name=\"cheat_dialog_ok\">Futtatás</string>\n    <string name=\"cheat_dialog_title\">Írj be parancsot:</string>\n    <string name=\"managePermExplainText\">Ez a játék olyan forrásokat (mint a pályák, epizódok, grafikák, zene, hangok, konfigurációk, stb...) használ, amiket a felhasználónak kell elhelyeznie közvetlenül a helyi tárolóba. Ez megengedi, hogy módosítsd és bővítsd a játék tartalmát, ahogyan az asztali verziójában is történik. Sajnos az Android 11-es verziójától nem lehet egyedi hozzáférést adni néhány játékkönyvtárhoz vagy fájlhoz. Hogy futtasd ezt a játékot, engedélyezned kell a hozzáférést az összes játékfájlhoz az előbbi okokból.</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">Könyvtár kiválasztása...</string>\n    <string name=\"launcher_gameSelect_clearList\">Lista ürítése</string>\n    <string name=\"launcher_gameselect_clearlist_title\">Játéklista ürítése</string>\n    <string name=\"launcher_gameselect_toast_listclean\">A jétéklista ürítve.</string>\n    <string name=\"launcher_gameselect_clearlist_question\">Biztosan szeretnéd üríteni a játéklistát?</string>\n    <string name=\"setup_speedrunner\">Sprintjáték</string>\n    <string name=\"launcher_gameselect_toast_selected\">Játék váltva: %s</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-it/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"rendererDefault\">Predefinito</string>\n    <string name=\"speedRunMode0\">Off</string>\n    <string name=\"speedRunMode1\">Modalità 1 (nativa di TheXTech)</string>\n    <string name=\"speedRunMode3\">Modalità 3 (SMBX 1.3 con tutti i bug abilitati)</string>\n    <string name=\"touchPadStyle0\">Azioni in gioco</string>\n    <string name=\"touchPadStyle1\">Stile Nintendo/Xbox (A/B/X/Y)</string>\n    <string name=\"touchPadStyle2\">Stile PlayStation (X/O/D/A)</string>\n    <string name=\"batteryStatusShow0\">Non mostrare mai</string>\n    <string name=\"batteryStatusShow1\">Mostra quando la batteria è scarica</string>\n    <string name=\"batteryStatusShow2\">Mostra sempre</string>\n    <string name=\"touchscreenControllerType0\">Sempre attivo</string>\n    <string name=\"touchscreenControllerType1\">Disabilita quando la tastiera hardware è collegata</string>\n    <string name=\"touchscreenControllerType2\">Sempre disattivato</string>\n    <string name=\"speedRunMode2\">Modalitò 2 (Disabilita le funzioni di time-winning)</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"title_activity_game_settings\">Impostazioni di gioco</string>\n    <string name=\"start_game_button\">Inizia gioco</string>\n    <string name=\"settings_button\">Impostazioni</string>\n    <string name=\"game_logo_label\">Logo del gioco</string>\n    <string name=\"settings_enable_frameskip\">Salta i frame</string>\n    <string name=\"setup_disable_sound\">Disabilita suoni</string>\n    <string name=\"setup_general_cat\">Generale</string>\n    <string name=\"setup_control_cat\">Controlli a schermo</string>\n    <string name=\"setup_display_cat\">Schermo</string>\n    <string name=\"setup_touchscreen_controller_enable\">Usa i controlli a schermo</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Mostra i controlli a schermo all\\'avvio</string>\n    <string name=\"setup_show_framerate\">Mostra valori frame-rate</string>\n    <string name=\"setup_max_framerate_mode\">Modalità frame-rate massimi</string>\n    <string name=\"setup_touchscreen_mode\">Modalità controller</string>\n    <string name=\"setup_sr_speedrunMode\">Modalità Speed-Run</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-ja/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType0\">つねに有効にする</string>\n    <string name=\"batteryStatusShow0\">表示しない</string>\n    <string name=\"batteryStatusShow1\">残量低下時に表示</string>\n    <string name=\"batteryStatusShow2\">つねに表示</string>\n    <string name=\"rendererDefault\">デフォルト</string>\n    <string name=\"touchscreenControllerType1\">ハードウェアキーボードが接続されている時は無効にする</string>\n    <string name=\"touchscreenControllerType2\">つねに無効にする</string>\n    <string name=\"speedRunMode0\">オフ</string>\n    <string name=\"speedRunMode1\">モード1(TheXTech ネイティブ)</string>\n    <string name=\"touchPadStyle2\">PSスタイル(‪✕‬/〇/□/△)</string>\n    <string name=\"speedRunMode2\">モード2(時間短縮機能が無効)</string>\n    <string name=\"speedRunMode3\">モード3(すべてのバグが有効。完全なSMBX 1.3)</string>\n    <string name=\"touchPadStyle0\">ゲーム内操作</string>\n    <string name=\"touchPadStyle1\">ニンテンドー/Xboxスタイル</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-ja/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"setup_general_cat\">一般</string>\n    <string name=\"setup_feedback\">振動</string>\n    <string name=\"start_game_button\">ゲームをはじめる</string>\n    <string name=\"settings_enable_frameskip\">フレームスキップを有効にする</string>\n    <string name=\"setup_max_framerate_mode\">最大フレームレートモード</string>\n    <string name=\"title_activity_game_settings\">ゲーム設定</string>\n    <string name=\"setup_touchscreen_controller_enable\">タッチスクリーンコントローラーを使用する</string>\n    <string name=\"cheat_dialog_ok\">実行</string>\n    <string name=\"launcher_gameSelect_clearList\">リストを全削除する</string>\n    <string name=\"launcher_gameselect_toast_selected\">ゲームを%sにへんこうしました</string>\n    <string name=\"setup_feedback_vibration_strength\">振動の強さ</string>\n    <string name=\"setup_renderer_label\">描画システム</string>\n    <string name=\"touchscreen_vibration_test_label\">振動をテスト</string>\n    <string name=\"setup_speedrunner\">スピードランナー</string>\n    <string name=\"setup_touchpad_style\">コントローラースタイル</string>\n    <string name=\"setup_sr_speedrunMode\">スピードランモード</string>\n    <string name=\"launcher_openfile_indirectly\">ファイルの絶対パスを取得できないため、ゲームは内部ストレージにコピーされました。\\n追加リソース（カスタムグラフィック、音楽、設定、スクリプトなど）を読み込むことができませんが、それでもゲームを起動しますか？</string>\n    <string name=\"setup_show_framerate\">FPSを表示する</string>\n    <string name=\"managePermExplainTitle\">すべてのファイルへのアクセス権限が必要です</string>\n    <string name=\"setup_feedback_enable_vibration\">振動を有効にする</string>\n    <string name=\"setup_disable_sound\">音声を無効にする</string>\n    <string name=\"cheat_dialog_cancel\">キャンセル</string>\n    <string name=\"launcher_no_resources_question\">このゲームの動作にはリソースパッケージが必須です。ゲームリソースをダウンロードし、任意の場所に解凍してください。解凍済みのフォルダーを指定しますか？</string>\n    <string name=\"launcher_gameselect_clearlist_question\">ほんとうにゲームリストを全削除しますか？</string>\n    <string name=\"setup_show_battery_status_label\">デバイスのバッテリー状態</string>\n    <string name=\"settings_button\">設定</string>\n    <string name=\"setup_touchscreen_alwaysshow\">開始時にタッチスクリーンコントローラーを表示</string>\n    <string name=\"setup_control_cat\">タッチスクリーン コントローラー</string>\n    <string name=\"setup_touchscreen_mode\">コントローラーモード</string>\n    <string name=\"launcher_gameselect_toast_listclean\">ゲームリストは空になりました。</string>\n    <string name=\"setup_assets_path_label\">リソースの絶対パスを変更</string>\n    <string name=\"setup_show_controller_state_label\">コントローラーの操作状態を表示する</string>\n    <string name=\"setup_display_cat\">ディスプレイ</string>\n    <string name=\"launcher_gameselect_clearlist_title\">ゲームリストを全削除する</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">場所をえらんでください...</string>\n    <string name=\"cheat_dialog_title\">コマンドを入力:</string>\n    <string name=\"game_logo_label\">ゲームロゴ</string>\n    <string name=\"setup_feedback_vibration_length\">振動のつよさ</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">ストップウォッチを半透明にする</string>\n    <string name=\"managePermExplainText\">このゲームは、この端末に保存したローカルファイルを読み込んでいます。そのおかげで、プレイヤーはPC版と同じように様々なコンテンツを独自の方法で改変・拡張することができるのです。しかし、Android 11以降では、ファイルの読み取り権限を個々のファイルや一部のディレクトリのみに付与することができなくなりました。そのため、このゲームを実行するためにはすべてのファイルへのアクセス許可が必要となっています。</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-ko/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType1\">하드웨어 키보드가 연결되면 비활성화</string>\n    <string name=\"rendererDefault\">기본값</string>\n    <string name=\"speedRunMode2\">모드 2 (시간을 적약하는 기능 비활성화)</string>\n    <string name=\"speedRunMode0\">끔</string>\n    <string name=\"batteryStatusShow2\">항상 표시</string>\n    <string name=\"batteryStatusShow1\">배터리가 부족할 때 표시</string>\n    <string name=\"touchPadStyle2\">플레이스테이션 스타일 (X/O/D/A</string>\n    <string name=\"speedRunMode3\">모드 3 (엄격한 SMBX 1.3, 모든 버그 활성화)</string>\n    <string name=\"speedRunMode1\">모드 1 (TheXTech 기본)</string>\n    <string name=\"touchscreenControllerType2\">항상 비활성화</string>\n    <string name=\"touchscreenControllerType0\">항상 활성화</string>\n    <string name=\"touchPadStyle1\">닌텐도/Xbox 스타일 (A/B/X/Y)</string>\n    <string name=\"touchPadStyle0\">게임 내 동작</string>\n    <string name=\"batteryStatusShow0\">표시하지 않음</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-ko/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"setup_general_cat\">일반 사항</string>\n    <string name=\"setup_feedback\">피드백</string>\n    <string name=\"start_game_button\">게임 시작</string>\n    <string name=\"settings_enable_frameskip\">프레임 건너뛰기 활성화</string>\n    <string name=\"setup_max_framerate_mode\">최대 프레임 속도 모드</string>\n    <string name=\"title_activity_game_settings\">게임 설정</string>\n    <string name=\"setup_touchscreen_controller_enable\">터치스크린 컨트롤러 사용</string>\n    <string name=\"cheat_dialog_ok\">시작</string>\n    <string name=\"launcher_gameSelect_clearList\">목록 지우기</string>\n    <string name=\"launcher_gameselect_toast_selected\">게임 전환: %s</string>\n    <string name=\"setup_feedback_vibration_strength\">진동 강도</string>\n    <string name=\"setup_renderer_label\">렌더 시스템</string>\n    <string name=\"managePermExplainText\">이 게임은 사용자가 로컬 스토어에 직접 배치한 리소스 (예: 레벨, 에피소드, 그래픽, 음악, 사운드, 구성 등)를 사용합니다. 이 개념을 통해 사용자는 이 게임의 데스크톱 버전에서 수행된 것처럼 자신의 목적에 맞게 콘텐츠를 수정하고 확장할 수 있습니다. 안타깝게도 안드로이드 11 이상에서는 단일 파일 대신 지정된 디렉터리에 대해 개별 읽기 권한을 부여하는 방법을 제공하지 않습니다. 이 게임을 실행하려면 설명된 이유 때문에 게임의 모든 파일에 대한 그랜드 접속이 필요합니다.</string>\n    <string name=\"touchscreen_vibration_test_label\">테스트 피드백</string>\n    <string name=\"setup_speedrunner\">스피드러너</string>\n    <string name=\"setup_touchpad_style\">컨트롤러 스타일</string>\n    <string name=\"setup_sr_speedrunMode\">스피드런 모드</string>\n    <string name=\"launcher_openfile_indirectly\">열린 파일의 직접 절대 경로를 검색할 수 없어 게임 내부 저장소에 대처했습니다. 추가 리소스 (예: 사용자 정의 그래픽, 음악, 구성, 스크립트 등)는 불러올 수 없습니다. 어떤 방식으로든 게임을 시작하겠습니까?</string>\n    <string name=\"setup_show_framerate\">프레임 속도 값 표시</string>\n    <string name=\"managePermExplainTitle\">모든 파일 접근 권한 필요함</string>\n    <string name=\"setup_feedback_enable_vibration\">진동 활성화</string>\n    <string name=\"setup_disable_sound\">사운드 비활성화</string>\n    <string name=\"cheat_dialog_cancel\">취소</string>\n    <string name=\"launcher_no_resources_question\">게임에는 작업을 위한 리소스 패키지가 필요합니다. 게임 리소스를 다운로드하고 편리한 디렉터리에 압축을 풀어보세요. 이미 압축이 풀린 폴더를 선택하겠습니까?</string>\n    <string name=\"launcher_gameselect_clearlist_question\">정말로 게임 목록을 지우겠습니까?</string>\n    <string name=\"setup_show_battery_status_label\">기기 배터리 상태</string>\n    <string name=\"settings_button\">설정</string>\n    <string name=\"setup_touchscreen_alwaysshow\">시작 시 터치스크린 컨트롤러 표시</string>\n    <string name=\"setup_control_cat\">터치스크린 컨트롤러</string>\n    <string name=\"setup_touchscreen_mode\">컨트롤러 모드</string>\n    <string name=\"launcher_gameselect_toast_listclean\">게임 목록이 깨끗해졌습니다.</string>\n    <string name=\"setup_assets_path_label\">리소스 절대 경로 변경</string>\n    <string name=\"setup_show_controller_state_label\">게임 컨트롤러 상태 표시</string>\n    <string name=\"setup_display_cat\">디스플레이</string>\n    <string name=\"launcher_gameselect_clearlist_title\">게임 목록 지우기</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">디렉토리 선택...</string>\n    <string name=\"cheat_dialog_title\">명령 입력 :</string>\n    <string name=\"game_logo_label\">게임 로고</string>\n    <string name=\"setup_feedback_vibration_length\">진동 길이</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">스톱워치를 반투명하게 만들기</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-nb-rNO/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    </resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-nb-rNO/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"managePermExplainText\">På Android 11+ trengs må full filtilgang innvilges til spillressursmappen (nivåer, episoder, grafikk, musikk, lyder, oppsett, osv.) som er å finne i lokallager.</string>\n    <string name=\"launcher_no_resources_question\">Last ned spillressursene for å starte spillet. Ønsker du å velge en allerede utpakket mappe?</string>\n    <string name=\"settings_enable_frameskip\">Hopp over rammer</string>\n    <string name=\"setup_assets_path_label\">Endre absolutt sti for ressurser</string>\n    <string name=\"managePermExplainTitle\">Full filtilgang kreves</string>\n    <string name=\"setup_touchpad_style\">Kontrollerstil</string>\n    <string name=\"setup_speedrunner\">Hastighetsfullfører</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Gjør stoppeklokke half-gjennomsiktig</string>\n    <string name=\"setup_sr_speedrunMode\">Hastighetsfullføringsmodus</string>\n    <string name=\"setup_feedback_vibration_length\">Varighet</string>\n    <string name=\"setup_feedback_vibration_strength\">Styrke</string>\n    <string name=\"setup_feedback_enable_vibration\">Vibrasjon</string>\n    <string name=\"setup_show_controller_state_label\">Vis spillkontrollertilstand</string>\n    <string name=\"setup_show_battery_status_label\">Enhetsbatteristatus</string>\n    <string name=\"touchscreen_vibration_test_label\">Test-tilbakemelding</string>\n    <string name=\"setup_feedback\">Tilbakemelding</string>\n    <string name=\"setup_touchscreen_mode\">Kontrollermodus</string>\n    <string name=\"setup_max_framerate_mode\">Tak for rammetakt</string>\n    <string name=\"setup_show_framerate\">Vis rammetaktsverdi</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Vis pekeskjermskontrolleren ved programstart</string>\n    <string name=\"setup_touchscreen_controller_enable\">Bruk pekeskjermskontroller</string>\n    <string name=\"setup_display_cat\">Skjerm</string>\n    <string name=\"setup_control_cat\">Pekeskjermskontroller</string>\n    <string name=\"setup_general_cat\">Generelt</string>\n    <string name=\"setup_disable_sound\">Skru av lyd</string>\n    <string name=\"game_logo_label\">Spill-logo</string>\n    <string name=\"settings_button\">Innstillinger</string>\n    <string name=\"start_game_button\">Start spill</string>\n    <string name=\"title_activity_game_settings\">Spillinnstillinger</string>\n    <string name=\"cheat_dialog_cancel\">Avbryt</string>\n    <string name=\"cheat_dialog_ok\">Kjør</string>\n    <string name=\"cheat_dialog_title\">Skriv kommando:</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-pl/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    </resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    </resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-pt/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType0\">Sempre ativo</string>\n    <string name=\"touchscreenControllerType1\">Desativar ao conectar teclado físico</string>\n    <string name=\"touchscreenControllerType2\">Sempre desativado</string>\n    <string name=\"speedRunMode0\">Desligado</string>\n    <string name=\"speedRunMode1\">Modo 1 (Nativo TheXTech)</string>\n    <string name=\"speedRunMode2\">Modo 2 (Sem vantagens de tempo)</string>\n    <string name=\"speedRunMode3\">Modo 3 (Derivado do SMBX 1.3, com todos os bugs)</string>\n    <string name=\"touchPadStyle0\">Ações no jogo</string>\n    <string name=\"touchPadStyle1\">Estilo Nintendo/Xbox (A/B/X/Y)</string>\n    <string name=\"touchPadStyle2\">Estilo PlayStation (𐄂/○/□/△)</string>\n    <string name=\"batteryStatusShow0\">Nunca mostrar</string>\n    <string name=\"batteryStatusShow1\">Mostrar caso a bateria esteja baixa</string>\n    <string name=\"batteryStatusShow2\">Exibir sempre</string>\n    <string name=\"rendererDefault\">Padrão</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"setup_disable_sound\">Desativar som</string>\n    <string name=\"setup_general_cat\">Gerais</string>\n    <string name=\"setup_control_cat\">Controles de toque</string>\n    <string name=\"setup_display_cat\">Ecrã</string>\n    <string name=\"setup_touchscreen_controller_enable\">Usar controles de toque</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Mostrar controles de toque ao iniciar</string>\n    <string name=\"setup_show_framerate\">Mostrar taxa de quadros</string>\n    <string name=\"setup_max_framerate_mode\">Não limitar taxa de quadros</string>\n    <string name=\"setup_touchscreen_mode\">Modo do controle</string>\n    <string name=\"setup_sr_speedrunMode\">Modo corrida (Speedrun)</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Cronômetro semitransparente</string>\n    <string name=\"setup_speedrunner\">Corridas (Speedruns)</string>\n    <string name=\"setup_touchpad_style\">Estilo do controle</string>\n    <string name=\"managePermExplainTitle\">Permissão para acesso total a ficheiros é necessária</string>\n    <string name=\"setup_assets_path_label\">Mudar caminho absoluto dos recursos</string>\n    <string name=\"managePermExplainText\">Este jogo utiliza recursos (como níveis, episódios, gráficos, músicas, sons, configurações, etc.) que foram alocadas pelo utilizador no armazenamento local. Isso permite que o utilizador modifique e expanda o conteúdo para propósitos próprios exatamente como a versão Desktop do jogo. Infelizmente, a partir do Android 11, não existe uma maneira de garantir acesso individual a uma única pasta em vez de um ficheiro. Para executar o jogo, deve garantir acesso a todos os ficheiros pelas razões citadas acima.</string>\n    <string name=\"setup_feedback_enable_vibration\">Ativar vibração</string>\n    <string name=\"setup_feedback_vibration_strength\">Força da vibração</string>\n    <string name=\"launcher_no_resources_question\">O jogo requer um pacote de recursos para funcionar. Por favor, descarregue os recursos do jogo e desempacote num lugar conveniente. Deseja selecionar uma pasta já desempacotada?</string>\n    <string name=\"setup_feedback_vibration_length\">Duração da vibração</string>\n    <string name=\"setup_feedback\">Vibração</string>\n    <string name=\"touchscreen_vibration_test_label\">Testar vibração</string>\n    <string name=\"setup_show_battery_status_label\">Estado da bateria do aparelho</string>\n    <string name=\"setup_renderer_label\">Sistema de renderização</string>\n    <string name=\"setup_show_controller_state_label\">Mostrar estado do controle</string>\n    <string name=\"cheat_dialog_cancel\">Cancelar</string>\n    <string name=\"cheat_dialog_ok\">Executar</string>\n    <string name=\"cheat_dialog_title\">Escrever comando:</string>\n    <string name=\"launcher_gameSelect_clearList\">Limpar lista</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">Selecionar diretório...</string>\n    <string name=\"launcher_gameselect_toast_selected\">Mudou o jogo: %s</string>\n    <string name=\"launcher_gameselect_toast_listclean\">A lista de jogos foi limpa.</string>\n    <string name=\"launcher_gameselect_clearlist_title\">Limpar lista de jogos</string>\n    <string name=\"launcher_openfile_indirectly\">Não foi possível aceder o caminho absoluto para o ficheiro aberto, foi criada uma copia no armazenamento do jogo. Recursos personalizados (como gráficos, músicas, configurações, scripts e outros) não serão carregados. Quer iniciar o jogo mesmo assim?</string>\n    <string name=\"title_activity_game_settings\">Configurações do jogo</string>\n    <string name=\"start_game_button\">Iniciar jogo</string>\n    <string name=\"settings_button\">Configurações</string>\n    <string name=\"game_logo_label\">Logo do jogo</string>\n    <string name=\"settings_enable_frameskip\">Ativar pulo de frames (Frameskipping)</string>\n    <string name=\"launcher_gameselect_clearlist_question\">Deseja limpar a lista de jogos?</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-pt-rBR/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"speedRunMode0\">Desligado</string>\n    <string name=\"speedRunMode1\">Modo 1 (Nativo TheXTech)</string>\n    <string name=\"speedRunMode2\">Modo 2 (Sem vantagens de tempo)</string>\n    <string name=\"speedRunMode3\">Modo 3 (Derivado do SMBX 1.3, com todos os bugs)</string>\n    <string name=\"touchPadStyle0\">Ações no jogo</string>\n    <string name=\"batteryStatusShow0\">Nunca mostrar</string>\n    <string name=\"batteryStatusShow1\">Mostrar caso a bateria esteja baixa</string>\n    <string name=\"rendererDefault\">Padrão</string>\n    <string name=\"batteryStatusShow2\">Mostrar sempre</string>\n    <string name=\"touchscreenControllerType1\">Desabilitar ao conectar teclado físico</string>\n    <string name=\"touchscreenControllerType2\">Desabilitado sempre</string>\n    <string name=\"touchPadStyle1\">Estilo Nintendo/Xbox (A/B/X/Y)</string>\n    <string name=\"touchPadStyle2\">Estilo PlayStation (𐄂/○/□/△)</string>\n    <string name=\"touchscreenControllerType0\">Sempre na tela</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"start_game_button\">Iniciar jogo</string>\n    <string name=\"game_logo_label\">Logo do jogo</string>\n    <string name=\"settings_enable_frameskip\">Habilitar pulo de frames (Frameskipping)</string>\n    <string name=\"setup_disable_sound\">Desabilitar som</string>\n    <string name=\"setup_general_cat\">Gerais</string>\n    <string name=\"setup_display_cat\">Tela</string>\n    <string name=\"setup_touchscreen_controller_enable\">Usar controles de toque</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Mostrar controles de toque ao iniciar</string>\n    <string name=\"setup_touchscreen_mode\">Modo do controle</string>\n    <string name=\"setup_sr_speedrunMode\">Modo corrida (Speedrun)</string>\n    <string name=\"setup_speedrunner\">Corridas (Speedruns)</string>\n    <string name=\"setup_touchpad_style\">Estilo do controle</string>\n    <string name=\"managePermExplainTitle\">Permissão para acesso total a arquivos é necessária</string>\n    <string name=\"title_activity_game_settings\">Configurações do jogo</string>\n    <string name=\"settings_button\">Configurações</string>\n    <string name=\"setup_control_cat\">Controles de toque</string>\n    <string name=\"setup_show_framerate\">Mostrar taxa de quadros</string>\n    <string name=\"setup_max_framerate_mode\">Não limitar taxa de quadros</string>\n    <string name=\"setup_assets_path_label\">Mudar caminho absoluto dos recursos</string>\n    <string name=\"launcher_no_resources_question\">O jogo requer um pacote de recursos para funcionar. Por favor, descarregue os recursos do jogo e desempacote em um lugar conveniente. Você deseja selecionar uma pasta já desempacotada?</string>\n    <string name=\"setup_feedback_enable_vibration\">Habilitar vibração</string>\n    <string name=\"setup_feedback_vibration_strength\">Força da vibração</string>\n    <string name=\"setup_feedback_vibration_length\">Duração da vibração</string>\n    <string name=\"setup_feedback\">Vibração</string>\n    <string name=\"touchscreen_vibration_test_label\">Testar vibração</string>\n    <string name=\"setup_show_battery_status_label\">Estado da bateria do dispositivo</string>\n    <string name=\"setup_renderer_label\">Sistema de renderização</string>\n    <string name=\"cheat_dialog_ok\">Executar</string>\n    <string name=\"cheat_dialog_title\">Escrever comando:</string>\n    <string name=\"launcher_gameSelect_clearList\">Limpar lista</string>\n    <string name=\"setup_show_controller_state_label\">Mostrar estado do controle</string>\n    <string name=\"cheat_dialog_cancel\">Cancelar</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">Selecionar diretório...</string>\n    <string name=\"launcher_gameselect_toast_selected\">Mudou o jogo: %s</string>\n    <string name=\"launcher_gameselect_toast_listclean\">A lista de jogos foi limpa.</string>\n    <string name=\"launcher_gameselect_clearlist_title\">Limpar lista de jogos</string>\n    <string name=\"launcher_gameselect_clearlist_question\">Você deseja limpar a lista de jogos?</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Cronômetro semitransparente</string>\n    <string name=\"managePermExplainText\">Este jogo utiliza recursos (como níveis, episódios, gráficos, músicas, sons, configurações, etc.) que foram alocadas pelo usuário no armazenamento local. Isso permite que o usuário modifique e expanda o conteúdo para propósitos próprios exatamente como a versão Desktop do jogo. Infelizmente, a partir do Android 11, não existe uma maneira de garantir acesso individual a uma única pasta em vez de um arquivo. Para rodar o jogo, você deve garantir acesso a todos os arquivos pelas razões citadas acima.</string>\n    <string name=\"launcher_openfile_indirectly\">Não foi possível acessar o caminho absoluto para o arquivo aberto, foi criada uma copia no armazenamento do jogo. Recursos personalizados (como gráficos, músicas, configurações, scripts, e outros) não serão carregados. Quer iniciar o jogo mesmo assim?</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-ru-rRU/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType0\">Всегда включать</string>\n    <string name=\"touchscreenControllerType1\">Выключить при наличии аппаратной клавиатуры</string>\n    <string name=\"touchscreenControllerType2\">Всегда выключать</string>\n    <string name=\"speedRunMode0\">Отключить</string>\n    <string name=\"speedRunMode1\">Режим 1 (Штатный функционал TheXTech)</string>\n    <string name=\"speedRunMode2\">Режим 2 (Отключить функции, дающие преимущество во времени)</string>\n    <string name=\"speedRunMode3\">Режим 3 (Строгое следование SMBX 1.3, все баги будут включены)</string>\n    <string name=\"touchPadStyle0\">Внутри-игровые действия</string>\n    <string name=\"touchPadStyle1\">Стиль Nintendo/Xbox (A/B/X/Y)</string>\n    <string name=\"touchPadStyle2\">Стиль PlayStation (X/O/D/A)</string>\n    <string name=\"batteryStatusShow0\">Скрывать</string>\n    <string name=\"batteryStatusShow1\">Показывать при слабом заряде</string>\n    <string name=\"batteryStatusShow2\">Показывать всегда</string>\n    <string name=\"rendererDefault\">По-умолчанию</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-ru-rRU/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"title_activity_game_settings\">Настройки игры</string>\n    <string name=\"start_game_button\">Начать игру</string>\n    <string name=\"settings_button\">Настройки</string>\n    <string name=\"game_logo_label\">Логотип игры</string>\n    <string name=\"settings_enable_frameskip\">Включить пропуск кадров</string>\n    <string name=\"setup_disable_sound\">Отключить звук</string>\n    <string name=\"setup_general_cat\">Общее</string>\n    <string name=\"setup_control_cat\">Сенсорный контроллер</string>\n    <string name=\"setup_display_cat\">Отображение</string>\n    <string name=\"setup_touchscreen_controller_enable\">Включить экранный джойстик</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Показывать экранный джойстик при запуске</string>\n    <string name=\"setup_show_framerate\">Показать частоту кадров</string>\n    <string name=\"setup_max_framerate_mode\">Режим максимальной частоты кадров</string>\n    <string name=\"setup_touchscreen_mode\">Режим контроллера</string>\n    <string name=\"setup_sr_speedrunMode\">Режим спидраннера</string>\n    <string name=\"setup_speedrunner\">Скоростное прохождение</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Сделать секундомер прозрачным</string>\n    <string name=\"setup_touchpad_style\">Стиль контроллера</string>\n    <string name=\"managePermExplainTitle\">Требуется разрешение на доступ ко всем файлам</string>\n    <string name=\"managePermExplainText\">Эта игра использует ресурсы (т.е. уровни, эпизоды, графику, музыку, звуки, конфиги, и т.п.), размещённые пользователем в локальном хранилище напрямую. Такая концепция позволяет пользователям менять и расширять содержимое ресурсов для любых своих целей, в точности как и на настольной версии игры. К сожалению, Android 11+ не позволяет предоставлять разрешение для чтения на отдельную папку вместо каждого отдельного файла. Чтобы запустить игру, Вы должны предоставить разрешение на доступ ко всем файлам из-за объяснённой выше причины.</string>\n    <string name=\"setup_assets_path_label\">Изменить абсолютный путь к ресурсам</string>\n    <string name=\"launcher_no_resources_question\">Для работы игры необходим пакет ресурсов. Пожалуйста, загрузите пакет ресурсов и распакуйте его в любую удобную директорию. Хотите выбрать директорию с уже распакованным пакетом ресурсов?</string>\n    <string name=\"setup_feedback\">Обратная отдача</string>\n    <string name=\"setup_feedback_enable_vibration\">Включить вибрацию</string>\n    <string name=\"setup_feedback_vibration_strength\">Сила вибрации</string>\n    <string name=\"setup_feedback_vibration_length\">Длина вибрации</string>\n    <string name=\"touchscreen_vibration_test_label\">Тест обратной отдачи</string>\n    <string name=\"setup_show_battery_status_label\">Состояние батареи устройства</string>\n    <string name=\"setup_renderer_label\">Графическая система</string>\n    <string name=\"setup_show_controller_state_label\">Показать состояние игрового контроллера</string>\n    <string name=\"cheat_dialog_ok\">Исполнить</string>\n    <string name=\"cheat_dialog_title\">Введите команду:</string>\n    <string name=\"cheat_dialog_cancel\">Отмена</string>\n    <string name=\"launcher_gameSelect_clearList\">Очистить список</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">Указать директорию...</string>\n    <string name=\"launcher_gameselect_toast_selected\">Выбрана игра: %s</string>\n    <string name=\"launcher_gameselect_toast_listclean\">Список игр очищен.</string>\n    <string name=\"launcher_gameselect_clearlist_title\">Очистить список игр</string>\n    <string name=\"launcher_gameselect_clearlist_question\">Вы действительно хотите очистить список игр?</string>\n    <string name=\"launcher_openfile_indirectly\">Невозможно получить абсолютный путь к открытому файлу. Поэтому, последний был скопирован во внутреннее хранилище игры. Дополнительные ресурсы (пользовательская графика, музыка, настройки, скрипты, и т.п.) не загрузятся. Запустить игру в любом случае?</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-ta/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType1\">வன்பொருள் விசைப்பலகை செருகப்படும்போது முடக்கு</string>\n    <string name=\"speedRunMode0\">அணை</string>\n    <string name=\"speedRunMode1\">பயன்முறை 1 (textech native)</string>\n    <string name=\"speedRunMode2\">பயன்முறை 2 (நேரத்தை வென்ற அம்சங்களை முடக்கு)</string>\n    <string name=\"speedRunMode3\">பயன்முறை 3 (கடுமையான SMBX 1.3, அனைத்து பிழைகள் இயக்கப்பட்டன)</string>\n    <string name=\"touchPadStyle0\">விளையாட்டு செயல்கள்</string>\n    <string name=\"touchPadStyle1\">நிண்டெண்டோ/எக்ச்பாக்ச் பாணி (a/b/x/y)</string>\n    <string name=\"touchPadStyle2\">பிளேச்டேசன் பாணி (x/o/d/a</string>\n    <string name=\"batteryStatusShow1\">குறைந்த பேட்டரி போது காண்பி</string>\n    <string name=\"rendererDefault\">இயல்புநிலை</string>\n    <string name=\"touchscreenControllerType0\">எப்போதும் இயக்கவும்</string>\n    <string name=\"touchscreenControllerType2\">எப்போதும் முடக்கு</string>\n    <string name=\"batteryStatusShow0\">ஒருபோதும் காட்ட வேண்டாம்</string>\n    <string name=\"batteryStatusShow2\">எப்போதும் காட்டு</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-ta/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"title_activity_game_settings\">விளையாட்டு அமைப்புகள்</string>\n    <string name=\"start_game_button\">விளையாட்டைத் தொடங்கவும்</string>\n    <string name=\"settings_button\">அமைப்புகள்</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">ச்டாப்வாட்ச் அரை வெளிப்படையானதாக ஆக்குங்கள்</string>\n    <string name=\"setup_speedrunner\">வேகம்-ரன்னர்</string>\n    <string name=\"setup_feedback_enable_vibration\">அதிர்வுகளை இயக்கவும்</string>\n    <string name=\"setup_feedback_vibration_strength\">அதிர்வு வலிமை</string>\n    <string name=\"setup_feedback\">கருத்து</string>\n    <string name=\"setup_renderer_label\">அமைப்பு வழங்கவும்</string>\n    <string name=\"setup_show_controller_state_label\">விளையாட்டு கட்டுப்பாட்டு நிலையைக் காட்டு</string>\n    <string name=\"launcher_gameselect_toast_selected\">விளையாட்டை மாற்றியது: %s</string>\n    <string name=\"launcher_gameselect_toast_listclean\">விளையாட்டுகளின் பட்டியல் சுத்தமாக உள்ளது.</string>\n    <string name=\"game_logo_label\">விளையாட்டு லோகோ</string>\n    <string name=\"settings_enable_frameskip\">பிரேம் ச்கிப்பிங்கை இயக்கவும்</string>\n    <string name=\"setup_disable_sound\">ஒலியை முடக்கு</string>\n    <string name=\"setup_general_cat\">பொது</string>\n    <string name=\"setup_control_cat\">தொடுதிரை கட்டுப்படுத்தி</string>\n    <string name=\"setup_display_cat\">காட்சி</string>\n    <string name=\"setup_touchscreen_controller_enable\">தொடு-திரை கட்டுப்படுத்தியைப் பயன்படுத்தவும்</string>\n    <string name=\"setup_touchscreen_alwaysshow\">தொடக்கத்தில் தொடுதிரை கட்டுப்படுத்தியைக் காட்டு</string>\n    <string name=\"setup_show_framerate\">பிரேம்-வீத மதிப்பைக் காட்டு</string>\n    <string name=\"setup_max_framerate_mode\">அதிகபட்ச பிரேம்-வீத முறை</string>\n    <string name=\"setup_touchscreen_mode\">கட்டுப்படுத்தி பயன்முறை</string>\n    <string name=\"setup_sr_speedrunMode\">வேகம்-ரன் பயன்முறை</string>\n    <string name=\"setup_touchpad_style\">கட்டுப்படுத்தி நடை</string>\n    <string name=\"managePermExplainTitle\">அனைத்து கோப்புகள் அணுகல் இசைவு தேவை</string>\n    <string name=\"managePermExplainText\">இந்த விளையாட்டு உள்ளக கடையில் பயனரால் நேரடியாக வைக்கப்பட்ட வளங்களை (நிலைகள், அத்தியாயங்கள், கிராபிக்ச், இசை, ஒலிகள், உள்ளமைவுகள் போன்றவை) பயன்படுத்துகிறது. இந்த கருத்து பயனர்கள் தங்கள் சொந்த நோக்கங்களுக்காக உள்ளடக்கத்தை மாற்றியமைக்கவும் நீட்டிக்கவும் இந்த விளையாட்டின் டெச்க்டாப் பதிப்பில் செய்யப்பட்டது. துரதிர்ச்டவசமாக, ஆண்ட்ராய்டு 11+ ஒரு கோப்பிற்கு பதிலாக குறிப்பிட்ட கோப்பகத்திற்கு தனிப்பட்ட வாசிப்பு அனுமதியை வழங்க வழிவகுக்காது. இந்த விளையாட்டை இயக்க நீங்கள் விளக்கப்பட்ட காரணங்களால் விளையாட்டுக்கான அனைத்து கோப்புகளுக்கும் அணுகலை வழங்க வேண்டும்.</string>\n    <string name=\"setup_assets_path_label\">வளங்களின் முழுமையான பாதையை மாற்றவும்</string>\n    <string name=\"launcher_no_resources_question\">விளையாட்டுக்கு வேலைக்கு ஒரு ஆதார தொகுப்பு தேவை. தயவுசெய்து விளையாட்டு வளங்களைப் பதிவிறக்கி, எந்தவொரு வசதியான கோப்பகத்திலும் அவற்றைத் திறக்கவும். ஏற்கனவே திறக்கப்படாத கோப்புறையைத் தேர்ந்தெடுக்க விரும்புகிறீர்களா?</string>\n    <string name=\"setup_feedback_vibration_length\">அதிர்வு நீளம்</string>\n    <string name=\"touchscreen_vibration_test_label\">சோதனை கருத்து</string>\n    <string name=\"setup_show_battery_status_label\">சாதன பேட்டரி நிலை</string>\n    <string name=\"cheat_dialog_cancel\">ரத்துசெய்</string>\n    <string name=\"cheat_dialog_ok\">ஓடு</string>\n    <string name=\"cheat_dialog_title\">கட்டளை வகை:</string>\n    <string name=\"launcher_gameSelect_clearList\">தெளிவான பட்டியல்</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">கோப்பகத்தைத் தேர்ந்தெடுக்கவும் ...</string>\n    <string name=\"launcher_gameselect_clearlist_title\">விளையாட்டு பட்டியலை அழிக்கவும்</string>\n    <string name=\"launcher_gameselect_clearlist_question\">விளையாட்டுகளின் பட்டியலை நீங்கள் உண்மையில் அழிக்க விரும்புகிறீர்களா?</string>\n    <string name=\"launcher_openfile_indirectly\">திறந்த கோப்பிற்கான நேரடி முழுமையான பாதையை மீட்டெடுக்க முடியாது, எனவே, இது விளையாட்டின் உள் சேமிப்பகத்தில் சமாளிக்கப்பட்டது. கூடுதல் ஆதாரங்களை (தனிப்பயன் கிராபிக்ச், இசை, உள்ளமைவுகள், ச்கிரிப்ட்கள் போன்றவை) ஏற்ற முடியாது. நீங்கள் எந்த வகையிலும் விளையாட்டைத் தொடங்க விரும்புகிறீர்களா?</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-tr/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType0\">Her zaman açık</string>\n    <string name=\"touchscreenControllerType1\">Klavye takılı olduğu zaman kapat</string>\n    <string name=\"touchscreenControllerType2\">Her zaman kapalı</string>\n    <string name=\"speedRunMode0\">Kapat</string>\n    <string name=\"speedRunMode2\">2. Seçenek (Zaman kazandıran özellikleri kapat)</string>\n    <string name=\"touchPadStyle2\">PlayStation tarzı (X/O/D/A)</string>\n    <string name=\"touchPadStyle1\">Nintendo/Xbox tarzı (A/B/X/Y)</string>\n    <string name=\"batteryStatusShow1\">Batarya düşükken göster</string>\n    <string name=\"batteryStatusShow0\">Asla gösterme</string>\n    <string name=\"batteryStatusShow2\">Her zaman göster</string>\n    <string name=\"speedRunMode1\">1. Seçenek (TheXTech native)</string>\n    <string name=\"touchPadStyle0\">Oyun İçi Aksiyon</string>\n    <string name=\"speedRunMode3\">3. Seçenek (SMBX 1.3 gibi, bütün buglar dahil)</string>\n    <string name=\"rendererDefault\">Varsayılan</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"launcher_no_resources_question\">Bu oyunun çalışması için kaynak paketi gerekmektedir. Lütfen oyunun kaynak paketini indirip istediğiniz bir dizine boşaltın. Zaten boşaltılmış bir dosya seçmek ister misiniz?</string>\n    <string name=\"setup_feedback\">Geri dönüş</string>\n    <string name=\"launcher_gameSelect_clearList\">Listeyi temizle</string>\n    <string name=\"launcher_gameselect_toast_selected\">Oyunu değiştir: %s</string>\n    <string name=\"setup_display_cat\">Görüntü</string>\n    <string name=\"launcher_gameselect_toast_listclean\">Oyun listesi temizlenmiştir.</string>\n    <string name=\"touchscreen_vibration_test_label\">Geri dönüşü test et</string>\n    <string name=\"launcher_gameselect_clearlist_title\">Oyun listesini temizle</string>\n    <string name=\"cheat_dialog_title\">Komut yaz:</string>\n    <string name=\"setup_sr_speedrunMode\">Speed-Run Modu</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Stopwatchı yarı şeffaf yap</string>\n    <string name=\"setup_speedrunner\">Speed-runner</string>\n    <string name=\"setup_touchpad_style\">Kontrolcü tarzı</string>\n    <string name=\"managePermExplainTitle\">Bütün dosyalara erişim izni gereklidir</string>\n    <string name=\"cheat_dialog_cancel\">İptal</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">Dizin seçiniz...</string>\n    <string name=\"title_activity_game_settings\">Oyun ayarları</string>\n    <string name=\"setup_control_cat\">Dokunmatik kontroller</string>\n    <string name=\"start_game_button\">Oyunu başlat</string>\n    <string name=\"settings_button\">Ayarlar</string>\n    <string name=\"game_logo_label\">Oyun logosu</string>\n    <string name=\"setup_general_cat\">Genel</string>\n    <string name=\"setup_show_framerate\">Kare hızı değerini göster</string>\n    <string name=\"setup_feedback_enable_vibration\">Titreşimi aç</string>\n    <string name=\"setup_feedback_vibration_strength\">Titreşim gücü</string>\n    <string name=\"launcher_gameselect_clearlist_question\">Oyun listesini temizlemek istediğinize emin misiniz?</string>\n    <string name=\"setup_disable_sound\">Sesi kapat</string>\n    <string name=\"settings_enable_frameskip\">Kare atlamayı aç</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Dokunmatik ekran kontrollerini başlangıçta göster</string>\n    <string name=\"setup_touchscreen_controller_enable\">Dokunmatik ekran kontrollerini kullan</string>\n    <string name=\"setup_touchscreen_mode\">Kontrolcü modu</string>\n    <string name=\"setup_max_framerate_mode\">Maksimum kare hızı modu</string>\n    <string name=\"managePermExplainText\">Bu oyun kullanıcı tarafından cihazın hafızasına konulmuş dosyaları (seviyeler, bölümler, grafikle, müzikler, ayarlar, vb. gibi) kullanır. Bu kullanıcıların içerikleri değiştirmelerine ve geliştirmelerini sağlar (masaüstü versiyonundaki gibi). Maalesef ki Android11 ve üzeri sürümler bazı dosyaların tek tek okunmasına izin vermiyor. Bu oyunu çalıştırmak için oyunun, bütün dosyalara görmesine izin vermeniz gerekmektedir.</string>\n    <string name=\"setup_assets_path_label\">Kaynak yolunu değiştir</string>\n    <string name=\"setup_feedback_vibration_length\">Titreşim uzunluğu</string>\n    <string name=\"setup_show_battery_status_label\">Cihazın batarya durumu</string>\n    <string name=\"setup_show_controller_state_label\">Bir oyun kontrolcüsü durumu göster</string>\n    <string name=\"cheat_dialog_ok\">Çalıştır</string>\n    <string name=\"setup_renderer_label\">Render sistemi</string>\n    <string name=\"launcher_openfile_indirectly\">Açılan dosyalar için dizindeki kaynaklara ulaşılamadı. Bu nedenle oyun var olan kaynaklarla açılacaktır. Fazladan kaynaklar (custom grafikler, müzikler, ayarlar, scriptler vb.) yüklenememektedir. Oyunu başlatmak istediğinize emin misiniz?</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-uk/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType0\">Завжди вмикати</string>\n    <string name=\"touchscreenControllerType1\">Вимкнути при наявності апаратної клавіатури</string>\n    <string name=\"touchscreenControllerType2\">Завжди вимикати</string>\n    <string name=\"speedRunMode0\">Вимкнути</string>\n    <string name=\"speedRunMode1\">Режим 1 (Штатний функціонал TheXTech)</string>\n    <string name=\"speedRunMode2\">Режим 2 (Вимкнути функції, які дають перевагу у часі)</string>\n    <string name=\"speedRunMode3\">Режим 3 (суворо SMBX 1.3, зі всіма його помилками)</string>\n    <string name=\"touchPadStyle0\">Внутрішньо-ігрові дії</string>\n    <string name=\"touchPadStyle1\">Стиль Nintendo/Xbox (A/B/X/Y)</string>\n    <string name=\"touchPadStyle2\">Стиль PlayStation (X/O/D/A)</string>\n    <string name=\"batteryStatusShow0\">Ховати</string>\n    <string name=\"batteryStatusShow1\">Показувати при низькому заряді</string>\n    <string name=\"batteryStatusShow2\">Завжди показувати</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"title_activity_game_settings\">Налаштування гри</string>\n    <string name=\"start_game_button\">Почати гру</string>\n    <string name=\"game_logo_label\">Логотип гри</string>\n    <string name=\"settings_enable_frameskip\">Увімкнути пропуск кадрів</string>\n    <string name=\"setup_disable_sound\">Вимкнути аудіо</string>\n    <string name=\"setup_general_cat\">Загальні</string>\n    <string name=\"setup_control_cat\">Сенсорний контролер</string>\n    <string name=\"setup_display_cat\">Відображення</string>\n    <string name=\"setup_show_framerate\">Вивести частоту кадрів</string>\n    <string name=\"setup_max_framerate_mode\">Режим максимальної частоти кадрів</string>\n    <string name=\"setup_touchscreen_mode\">Режим контролера</string>\n    <string name=\"setup_sr_speedrunMode\">Режим спідранера</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">Зробити секундомір напівпрозорим</string>\n    <string name=\"setup_speedrunner\">Швидке проходження</string>\n    <string name=\"setup_touchpad_style\">Стиль контролера</string>\n    <string name=\"setup_assets_path_label\">Змінити абсолютний шлях до ресурсів</string>\n    <string name=\"launcher_no_resources_question\">Гра потребує необхідний пакет ресурсів. Будь ласка, завантажте пакет ресурсів і розпакуйте його в будь-яку зручну для вас директорію. Чи бажаєте ви вибрати директорію з вже розпакованим пакетом ресурсів?</string>\n    <string name=\"setup_feedback_enable_vibration\">Увімкнути вібрацію</string>\n    <string name=\"setup_feedback_vibration_strength\">Сила вібрації</string>\n    <string name=\"setup_feedback_vibration_length\">Час вібрації</string>\n    <string name=\"setup_feedback\">Зворотній зв\\'язок</string>\n    <string name=\"touchscreen_vibration_test_label\">Тест зворотнього зв\\'язку</string>\n    <string name=\"setup_show_battery_status_label\">Стан батареї пристрою</string>\n    <string name=\"setup_show_controller_state_label\">Вивести стан ігрового контролера</string>\n    <string name=\"setup_touchscreen_controller_enable\">Увімкнути екранний джойстик</string>\n    <string name=\"settings_button\">Налаштування</string>\n    <string name=\"setup_touchscreen_alwaysshow\">Виводити екранний джойстик при запуску</string>\n    <string name=\"managePermExplainTitle\">Потрібен дозвіл на доступ до всіх файлів</string>\n    <string name=\"managePermExplainText\">Ця гра використовує ресурси, (такі, як рівні, епізоди, графіку, музику, звуки, конфіги, і т.д.) які були поміщені користувачем у локальному сховищі. Така концепція дозволяє користувачам змінювати і розширювати зміст ресурсів для будь-яких своїх цілей, так само, як і в настільній версії гри. Нажаль, Android 11+ не надає дозволу на читання окремих директорій, замість читання окремого файлу. Щоб запустити гру, вам потрібно надати дозвіл на доступ до всіх файлів із-за поясненої вище причини.</string>\n    <string name=\"cheat_dialog_cancel\">Скасувати</string>\n    <string name=\"cheat_dialog_ok\">Виконати</string>\n    <string name=\"cheat_dialog_title\">Введіть команду:</string>\n    <string name=\"launcher_gameSelect_clearList\">Очистити список</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">Вказати директорію...</string>\n    <string name=\"launcher_gameselect_toast_selected\">Вибрана гра: %s</string>\n    <string name=\"launcher_gameselect_toast_listclean\">Список ігр очищено.</string>\n    <string name=\"launcher_gameselect_clearlist_title\">Очистити список ігр</string>\n    <string name=\"launcher_gameselect_clearlist_question\">Ви справді бажаєте очистити список ігр?</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-zh-rCN/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType0\">启用</string>\n    <string name=\"touchscreenControllerType1\">仅在未连接实体键盘时启用</string>\n    <string name=\"touchscreenControllerType2\">禁用</string>\n    <string name=\"speedRunMode0\">禁用</string>\n    <string name=\"speedRunMode1\">模式 1（原始TheXTech）</string>\n    <string name=\"speedRunMode2\">模式 2（禁用计时过关）</string>\n    <string name=\"speedRunMode3\">模式 3（SMBX 1.3，启用所有Bug）</string>\n    <string name=\"touchPadStyle0\">游戏内动作</string>\n    <string name=\"touchPadStyle1\">字母风格（A/B/X/Y）</string>\n    <string name=\"touchPadStyle2\">PS风格（X/O/D/A）</string>\n    <string name=\"batteryStatusShow0\">不显示</string>\n    <string name=\"batteryStatusShow2\">显示</string>\n    <string name=\"batteryStatusShow1\">仅在电池电量低时显示</string>\n    <string name=\"rendererDefault\">默认</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"title_activity_game_settings\">游戏设置</string>\n    <string name=\"start_game_button\">开始游戏</string>\n    <string name=\"settings_button\">设置</string>\n    <string name=\"setup_speedrunner\">秒表计时</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">半透明秒表计时器</string>\n    <string name=\"setup_sr_speedrunMode\">速通模式</string>\n    <string name=\"setup_touchscreen_mode\">按键模式</string>\n    <string name=\"setup_max_framerate_mode\">禁用锁帧</string>\n    <string name=\"setup_show_framerate\">显示帧数</string>\n    <string name=\"setup_touchscreen_alwaysshow\">启动时显示虚拟按键</string>\n    <string name=\"setup_touchscreen_controller_enable\">使用虚拟按键</string>\n    <string name=\"setup_display_cat\">显示</string>\n    <string name=\"setup_control_cat\">虚拟按键</string>\n    <string name=\"setup_general_cat\">常规</string>\n    <string name=\"setup_disable_sound\">禁用声音</string>\n    <string name=\"settings_enable_frameskip\">启用跳帧</string>\n    <string name=\"game_logo_label\">游戏Logo</string>\n    <string name=\"setup_touchpad_style\">控制器样式</string>\n    <string name=\"managePermExplainTitle\">需要文件访问权限</string>\n    <string name=\"setup_assets_path_label\">更改组件所在文件夹</string>\n    <string name=\"managePermExplainText\">此游戏使用的是用户在直接在本地存储的数据（例如关卡、素材、音乐、音效、设置等），即允许用户修改或扩展内容。不幸的是安卓 11 以上的版本不支持设置文件夹权限。因此当游戏运行时，请您开启所有文件的访问权限。</string>\n    <string name=\"launcher_no_resources_question\">此游戏需要相关的组件才能运行，请您下载游戏组件并解压。现在要选择已经解压的游戏组件所在的文件夹吗？</string>\n    <string name=\"setup_feedback\">反馈</string>\n    <string name=\"setup_feedback_enable_vibration\">启用震动</string>\n    <string name=\"setup_feedback_vibration_strength\">震动强度</string>\n    <string name=\"setup_feedback_vibration_length\">震动时间</string>\n    <string name=\"touchscreen_vibration_test_label\">测试反馈</string>\n    <string name=\"setup_show_battery_status_label\">电池状态</string>\n    <string name=\"setup_show_controller_state_label\">显示控制键状态</string>\n    <string name=\"cheat_dialog_ok\">执行</string>\n    <string name=\"cheat_dialog_cancel\">取消</string>\n    <string name=\"cheat_dialog_title\">输入指令：</string>\n    <string name=\"launcher_gameSelect_clearList\">清除列表</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">选择文件夹……</string>\n    <string name=\"launcher_gameselect_toast_listclean\">游戏列表已清除。</string>\n    <string name=\"launcher_gameselect_clearlist_title\">清除游戏列表</string>\n    <string name=\"launcher_gameselect_toast_selected\">游戏已更改为：%s</string>\n    <string name=\"launcher_gameselect_clearlist_question\">要清除游戏列表吗？</string>\n    <string name=\"launcher_openfile_indirectly\">无法检索所打开文件的指定绝对路径，但已复制到该游戏所在的内置存储位置，因此无法加载外部资源（例如自定义素材、音乐、设置、脚本等）。您还要启动游戏吗？</string>\n    <string name=\"setup_renderer_label\">图形渲染类型</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-zh-rTW/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"touchscreenControllerType0\">啓用</string>\n    <string name=\"touchscreenControllerType1\">僅在未連接實體鍵盤時啓用</string>\n    <string name=\"touchscreenControllerType2\">禁用</string>\n    <string name=\"speedRunMode0\">禁用</string>\n    <string name=\"speedRunMode1\">模式1（原始TheXTech）</string>\n    <string name=\"speedRunMode2\">模式 2（禁用計時過關）</string>\n    <string name=\"speedRunMode3\">模式 3（SMBX 1.3，啓用所有Bug）</string>\n    <string name=\"touchPadStyle0\">遊戲內動作</string>\n    <string name=\"touchPadStyle1\">字母風格（A/B/X/Y）</string>\n    <string name=\"touchPadStyle2\">PS風格（X/O/D/A）</string>\n    <string name=\"batteryStatusShow0\">不顯示</string>\n    <string name=\"batteryStatusShow1\">僅在電池電量低時顯示</string>\n    <string name=\"batteryStatusShow2\">顯示</string>\n    <string name=\"rendererDefault\">默認</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/values-zh-rTW/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"setup_control_cat\">虛擬按鍵</string>\n    <string name=\"setup_sr_speedrunMode\">秒錶計時模式</string>\n    <string name=\"setup_display_cat\">顯示</string>\n    <string name=\"setup_touchscreen_controller_enable\">使用虛擬按鍵</string>\n    <string name=\"setup_touchscreen_mode\">按鍵模式</string>\n    <string name=\"launcher_no_resources_question\">此遊戲需要相關的組件才能運行，請您下載遊戲組件並解壓。現在要選擇已經解壓的遊戲組件所在的資料夾嗎？</string>\n    <string name=\"launcher_gameselect_clearlist_question\">要清除遊戲列表嗎？</string>\n    <string name=\"launcher_gameselect_toast_listclean\">遊戲列表已清除。</string>\n    <string name=\"launcher_openfile_indirectly\">無法檢索所打開文件的指定絕對路徑，但已複製到該遊戲所在的內置存儲位置，因此無法加載外部資源（例如自定義素材、音樂、設置、腳本等）。您還要啓動遊戲嗎？</string>\n    <string name=\"start_game_button\">開始遊戲</string>\n    <string name=\"title_activity_game_settings\">遊戲設定</string>\n    <string name=\"settings_button\">設定</string>\n    <string name=\"game_logo_label\">遊戲Logo</string>\n    <string name=\"settings_enable_frameskip\">啓用跳幀</string>\n    <string name=\"setup_disable_sound\">禁用聲音</string>\n    <string name=\"setup_general_cat\">常規</string>\n    <string name=\"setup_touchscreen_alwaysshow\">啓動時顯示虛擬按鍵</string>\n    <string name=\"setup_show_framerate\">顯示幀數</string>\n    <string name=\"setup_max_framerate_mode\">禁用鎖幀</string>\n    <string name=\"setup_speedrunner\">秒錶計時</string>\n    <string name=\"setup_sr_stopwatchSemiTransparent\">半透明秒錶計時器</string>\n    <string name=\"setup_touchpad_style\">控制器樣式</string>\n    <string name=\"managePermExplainTitle\">需要檔案訪問權限</string>\n    <string name=\"setup_assets_path_label\">更改組件所在位置</string>\n    <string name=\"setup_feedback_enable_vibration\">啓用震動</string>\n    <string name=\"setup_feedback_vibration_strength\">震動強度</string>\n    <string name=\"setup_feedback_vibration_length\">震動時間</string>\n    <string name=\"setup_feedback\">反饋</string>\n    <string name=\"touchscreen_vibration_test_label\">測試反饋</string>\n    <string name=\"setup_show_battery_status_label\">電池狀態</string>\n    <string name=\"setup_renderer_label\">圖形渲染類型</string>\n    <string name=\"setup_show_controller_state_label\">顯示控制鍵狀態</string>\n    <string name=\"cheat_dialog_cancel\">取消</string>\n    <string name=\"cheat_dialog_ok\">執行</string>\n    <string name=\"cheat_dialog_title\">輸入指令：</string>\n    <string name=\"launcher_gameSelect_clearList\">清除列表</string>\n    <string name=\"launcher_gameSelect_pickTheDir\">選擇資料夾……</string>\n    <string name=\"launcher_gameselect_toast_selected\">遊戲已更改為：%s</string>\n    <string name=\"launcher_gameselect_clearlist_title\">清除遊戲列表</string>\n    <string name=\"managePermExplainText\">此遊戲使用的是用戶在直接在本地存儲的數據（例如關卡、素材、音樂、音效、設置等），即允許用戶修改或擴展內容。不幸的是安卓 11 以上的版本不支持設置資料夾權限。因此當遊戲運行時，請您開啓所有檔案的訪問權限。</string>\n</resources>\n"
  },
  {
    "path": "android-project/thextech/src/main/res/xml/root_preferences.xml",
    "content": "<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <PreferenceCategory android:title=\"@string/setup_general_cat\">\n        <CheckBoxPreference\n            android:defaultValue=\"false\"\n            android:key=\"enable_frame_skip\"\n            android:title=\"@string/settings_enable_frameskip\" />\n        <CheckBoxPreference\n            android:defaultValue=\"false\"\n            android:key=\"disable_sound\"\n            android:title=\"@string/setup_disable_sound\" />\n        <Preference\n            android:key=\"setup_assets_path\"\n            android:title=\"@string/setup_assets_path_label\" />\n    </PreferenceCategory>\n    <PreferenceCategory android:title=\"@string/setup_display_cat\">\n        <ListPreference\n            android:defaultValue=\"\"\n            android:entries=\"@array/renderer\"\n            android:entryValues=\"@array/rendererValues\"\n            android:key=\"setup_renderer\"\n            android:title=\"@string/setup_renderer_label\" />\n        <CheckBoxPreference\n            android:defaultValue=\"false\"\n            android:key=\"show_fps\"\n            android:title=\"@string/setup_show_framerate\" />\n        <CheckBoxPreference\n            android:defaultValue=\"false\"\n            android:key=\"enable_max_fps\"\n            android:title=\"@string/setup_max_framerate_mode\" />\n        <CheckBoxPreference\n            android:defaultValue=\"false\"\n            android:key=\"setup_show_controller_state\"\n            android:title=\"@string/setup_show_controller_state_label\" />\n        <ListPreference\n            android:defaultValue=\"0\"\n            android:entries=\"@array/batteryStatusShow\"\n            android:entryValues=\"@array/batteryStatusShowValues\"\n            android:key=\"setup_show_battery_status\"\n            android:title=\"@string/setup_show_battery_status_label\" />\n    </PreferenceCategory>\n    <PreferenceCategory android:title=\"@string/setup_speedrunner\">\n        <ListPreference\n            android:defaultValue=\"0\"\n            android:entries=\"@array/speedRunModes\"\n            android:entryValues=\"@array/speedRunModesValues\"\n            android:key=\"setup_speedRunMode\"\n            android:title=\"@string/setup_sr_speedrunMode\" />\n        <CheckBoxPreference\n            android:defaultValue=\"false\"\n            android:key=\"setup_sr_showStopwatchTransparent\"\n            android:title=\"@string/setup_sr_stopwatchSemiTransparent\" />\n    </PreferenceCategory>\n</PreferenceScreen>"
  },
  {
    "path": "changelog.txt",
    "content": "TheXTech Changelog.\r\n\r\nChangelog for 1.3.8-dev\r\n-Unstable changes will go here for testing during the 1.3.7.x release lifespan...\r\n-Update LVLX/WLDX/SAVX loader to use new MDX file parser (@ds-sloth)\r\n-3DS/Wii: disable partial support for loading SMBX-38A content (@ds-sloth)\r\n-Technical update: reduce mainloop nesting to improve engine flexibility (@ds-sloth)\r\n-Add experimental support for loading game/episode content from single-file archives (@ds-sloth)\r\n-Important notice: gray and red bricks are now killed at the end of the frame they are destroyed (like other blocks). This is an intentional change to the game workflow that is required for us to support NetPlay. Content where these blocks have destroy / last events can no longer be loaded in Vanilla Mode. (@ds-sloth)\r\n-Improve the performance of levels with many coins, guarded by compat flag \"optimize-coins\". (@ds-sloth)\r\n-Improve the performance of levels with many conveyor belts, guarded by compat flag \"new-conveyor-belts\". (@ds-sloth)\r\n-In split screen, extra screen shake effects now apply only to the camera where the object is present. (@ds-sloth)\r\n-Extra screen shake effects now applies separately per camera [Classic/Modern Mode] (@ds-sloth)\r\n-Performance improvements for the stopwatch freeze logic (@ds-sloth)\r\n-Add experimental support for maze physics zones to the game and editor (@ds-sloth)\r\n-Add experimental cheat \"borntoclimb\" (@ds-sloth)\r\n-Added an experimental port to the Nintendo DSi port (@ds-sloth)\r\n-Fix SMBX 1.3 visual peculiarity where a large reserve item was drawn at the wrong offset and size, guarded by compat flag \"fix-visual-bugs\" [Modern Mode] (@ds-sloth)\r\n-Battle mode: allow eaten player to escape by repeatedly hitting Jump (@ds-sloth)\r\n-Fix SMBX 1.3 peculiarity where NPCs would not respawn when coming back onscreen during a timestop, guarded by compat flag \"fix-timestop-respawn\" [Modern Mode] (@ds-sloth)\r\n-Add experimental support for attaching wings to NPCs to the game and editor (@ds-sloth)\r\n-Switch the engine to fixed point arithmetic for performance and reproducibility (@ds-sloth)\r\n-Tweak camera logic to reduce jitter when standing on a group of NPCs (@ds-sloth)\r\n-Battle mode: char 5's shield now blocks and cancels another char 5 player's stab, creating an opening for attack (@ds-sloth)\r\n-Battle mode: allow multiple players to choose the same character (@ds-sloth)\r\n-For creators: add compat flag \"alt-powerdown\" which makes Chars 1 and 2 go to Big after being hit with a stronger powerup, and disables the item box (@ds-sloth)\r\n\r\nChangelog for 1.3.7.3-dev\r\n-Fixed an inability to save the state of the collected medals on triggering the game beat (@Wohlstand, thanks to @Liebning for the report)\r\n-Fixed a TTF text print bug from the version 1.3.7.2 that caused missing vertical offset on line breaking (@Wohlstand, thanks to 呜哩哇啦 for the report)\r\n-Wii: fix v1.3.7.2 bug where deleting an in-use controls profile could lead to a crash (@ds-sloth)\r\n-Fix TheXTech v1.3.7 bug where popping an empty bubble (ID 283) could harm the player (@ds-sloth, thanks to @Liebning for the report)\r\n-Fix SMBX 1.3 peculiarity where frozen NPCs would sometimes be rendered incorrectly, guarded by compat flag \"fix-visual-bugs\" [Modern Mode] (@ds-sloth)\r\n-Fix SMBX 1.3 bug where the swap power (ID 273) could result in unexpected deaths or softlocks if the players were in different sections, guarded by compat flag \"fix-multiplayer-targeting\" [Modern/Classic Mode] (@ds-sloth, thanks to @Shadowblitz16 for the report)\r\n-Logic change for TheXTech-exclusive feature: portal warps will not trigger until the player has spent 10 frames without overlapping any portal warps (@ds-sloth)\r\n-Fixed an inability to load a game pack from command line by path without slash ending (@Wohlstand)\r\n-3DS: fix v1.3.7 bug where the title screen could get stuck at the right of the level (@ds-sloth)\r\n-Enabled running of Layers, Events, and LunaScript loops at the Outro screen to allow more dynamics (@Wohlstand)\r\n-Fix SMBX 1.3 bug where character passthrough blocks treated char 2-4's ice balls as belonging to char 1, guarded by compat flag \"fix-char-pass-balls\" [Modern Mode] (@ds-sloth, thanks to @Wohlstand for the report)\r\n-Fix SMBX 1.3 bug where character passthrough blocks did not allow char 5's fire/ice balls to pass through, guarded by compat flag \"fix-char-pass-balls\" [Modern Mode] (@ds-sloth)\r\n-Fixed a crash caused by hit of invisible Block-88 with a coin inside while the Coin Swtich is active (@Wohlstand)\r\n\r\nChangelog for 1.3.7.2\r\n-i18n: Added support for the localisation of the loading screen (@Wohlstand)\r\n-i18n: Removed the support for legacy placement of translations outside the i18n sub-directory since no production projects used it. (@Wohlstand)\r\n-Modern mode: allow static, always-shown NPCs (eg, O or bullet shooters) to deactivate while onscreen (@Wohlstand, @ds-sloth)\r\n-Added an ability to assign some TTF font per language (@Wohlstand)\r\n-Menus: Alt Run is now used to erase options, instead of Alt Jump (@ds-sloth)\r\n-Menus: Alt Jump may now advance message boxes and start levels, guarded by existing compat flag \"multiplayer-pause-controls\" [Modern/Classic Mode] (@ds-sloth)\r\n-Controls: add Alt Menu Controls option to controller profiles, which makes Jump act as Back and Alt Jump act as Confirm, to improve experience on Japanese-layout controllers (@ds-sloth)\r\n-Controls: re-use controller profile when connected on different interfaces (@ds-sloth)\r\n-Controls: remove ability to load controller profiles saved by versions before v1.3.6 (2022) (@ds-sloth)\r\n-Controls: fix v1.3.6 bug where controller was assigned the wrong profile after its original profile was deleted (@ds-sloth)\r\n-Controls: allow deleting a profile in use. This will allow the controller's profile to be reset. (@ds-sloth)\r\n-Controls: fix face button labels on Switch port. (@Wohlstand, @ds-sloth)\r\n-Controls: fix v1.3.6 bug where closing the Enter Code screen with the Start button might prevent successful code entry (@ds-sloth, @Wohlstand)\r\n-Controls: adjusted support for Wii Remote and Wii Remote with Nunchuk controllers on Wii U. (@Wohlstand)\r\n-Visuals: fix v1.3.7.1 bug where there was a black frame during the transition from the main menu to the asset pack select screen (@ds-sloth)\r\n-Fonts: Fixed the incorrect vertical alignment of some TTF glyph. (@Wohlstand)\r\n-Fix v1.3.7.1 bug where npc-300.txt was not loaded properly - fixes spinning platforms in Lowser's Conquest (@ds-sloth, @Wohlstand)\r\n-Fix v1.3.7.1 bug where default masks/outlines were not loaded for custom GIF player sprites, resulting in black boxes (@ds-sloth)\r\n-Fix v1.3.7.1 Modern/Classic bug where it was not safe to run on a series of fall blocks with gaps between them, caused by the downward clip fix introduced in v1.3.7.1 (@ds-sloth, thanks to @Liebning for the report)\r\n-Vanilla mode: reset score between gameplay sessions, like SMBX 1.3 (@ds-sloth)\r\n-Desktop: fix a minor cosmetic bug where the screen could flicker when resizing the window (@ds-sloth)\r\n-Fixed the SMBX 1.3 bug where the engine would appear to break if an AutoStart event (other than \"Level - Start\") changed section bounds, guarded by compat flag \"modern-section-change\" [Modern/Classic Mode] (@Wohlstand)\r\n-Fixed the v1.3.6 bug where skull raft with modern logic enabled will glitch when impacts a barrier being in a water (@Wohlstand)\r\n-Fix v1.3.1 inaccuracy where NPCs 69 and 70 were one pixel shorter than they are in SMBX 1.3 (@ds-sloth, thanks to @Liebning for the report)\r\n-Fix SMBX 1.3 crash caused by hitting a coin switch while on a slope (@ds-sloth, thanks to Peter Smith for the report)\r\n-Logic change: when modern (LVLX) autoscroll hits the end of the section, it now keeps the current camera size instead of resetting the camera to 800x800 as in SMBX 1.3 (@ds-sloth)\r\n-Logic change: the terminal velocity for rail/lineguide platforms has been increased from 16 to 40 to address compatibility issues at some levels where falling speed is not enough to beat the challenge. (@Wohlstand)\r\n-Fixed an inability to auto-create the screenshots directory on macOS (@Wohlstand)\r\n-Fix v1.3.7 bug where the engine would fail to load if the first encountered asset pack was invalid (@ds-sloth)\r\n-Fix v1.3.6 inaccuracy where warp-spawned NPCs would fail to be hidden if their layer was hidden immediately following spawn (their layer is set to \"Spawned NPCs\" when warp is complete) (@ds-sloth)\r\n-Fix v1.3.7.1 bug where static NPCs spawned by a generator could despawn while onscreen (@ds-sloth)\r\n-Fix v1.3.1 bug where whipping a turtle (with leaf power) would kill the turtle as well as releasing it from its shell (@ds-sloth, thanks to @Liebning for the report)\r\n-Fixed vanilla crash bug caused by division by zero on rendering of Background2-25 with a texture width smaller than 800 and when the section has an exact width of 800 (@Wohlstand)\r\nChangelog for 1.3.7.2-hotfix1\r\n-Fixed an incorrect resampling logic at the MixerX audio library that caused accelerated tempo of music on 3DS, Wii, and PSVita (@Wohlstand)\r\n\r\nChangelog for 1.3.7.1\r\n-Add GameCube controller support for Wii (@ds-sloth)\r\n-Fix TheXTech 1.3.7 peculiarity where NPCs that hid themselves on activation would be shown before coming onscreen (@ds-sloth)\r\n-Fix TheXTech 1.3.7 peculiarity where player preview sprites in the main menu would not use the correct death effects (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug where a plant on a hidden moving layer would sometimes not appear after showing the layer (@ds-sloth, @Wohlstand)\r\n-Fixed a bug from the TheXTech 1.3.6 where music of a wrong section gets started when player enters level by a warp into section with no music (@Wohlstand)\r\n-Fix TheXTech 1.3.7 bug where rail/lineguide platforms could get stuck below the current section (@ds-sloth, thanks to @Liebning for the report)\r\n-3DS: fix bug where gameplay could slow down when audio performance is reduced (@ds-sloth)\r\n-Implemented support for Power-of-Two only textures on some platforms via SDL Render (@Wohlstand)\r\n-Adjusted the frame-skip behaviour to address the performance slowdown on some systems (@Wohlstand)\r\n-Added exclusive full-screen mode support to maintain good performance on weak devices (@Wohlstand)\r\n-Windows: use SDL2 renderer on video cards without OpenGL acceleration (@ds-sloth)\r\n-Fix TheXTech 1.3.5.3 visual bug where P2's screen fader was not reset when P1 gets a level exit (@ds-sloth)\r\n-Wii: fix bug where Wiimote could get stuck in Rumble state (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 visual change in the width of spaces in the in-game message box (@ds-sloth)\r\n-Fix TheXTech 1.3.7 visual change in the line-breaking algorithm for the in-game message box (@ds-sloth)\r\n-Fix vanilla bug where chars 3 and 4 could clip downwards if they powered up while digging, guarded by \"fix-player-grab-clip\" [Classic Mode] (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug where setting width or height to 0 in npc.txt would prevent the NPC from spawning (this affected the boss of Hall of Masks in MM4) (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug where colored platform NPCs could mistakenly trigger activation events (this affected The Sinister Side in SRW) (@ds-sloth)\r\n-Fix SMBX 1.3 crash caused by freezing a bubble when the NPC with index 3 is an ice ball (@ds-sloth)\r\n-Fix TheXTech 1.3.1 inaccuracy where certain internal values were not rounded correctly (@ds-sloth)\r\n-Fix TheXTech 1.3.1 bug where characters 2 and 5 couldn't release all coins in a block if the block was in front of another sizable block (@ds-sloth)\r\n-Added compat flag \"no-shell-grab-top\" to disable grabbing shells while falling from above, for compatibility with pre-SMBX 1.3 content (@Wohlstand)\r\n-Fix loading of custom resolution specified in thextech.ini (@ds-sloth)\r\n-Add 3x scale mode (@ds-sloth, thanks to @Yave-Yu for the request)\r\n-Fix TheXTech 1.3.7 bug where medals were incorrectly tracked after using Coin Switch in Classic Mode (@ds-sloth, thanks to @Agatha for the report)\r\n-Fix TheXTech 1.3.7 editor bug where sizable block priority was not used when resizing sizable blocks (@ds-sloth, thanks to @ChristianSilvermoon for the report)\r\n-Refine Magic Block handling of lava next to slopes (@ds-sloth, thanks to @ChristianSilvermoon for the report)\r\n-Fix TheXTech v1.3.7 change to the timing of events triggered by warp enter/exit events (@ds-sloth, thanks to @ChristianSilvermoon for the report)\r\n-Fix TheXTech v1.3.6.1 inaccuracy where generator NPCs with IDs 57, 60, 62, 64, and 66 could not be walked on in their generator state (@ds-sloth)\r\n-Resolved the pool allocator memory overflow crash problem (Caused by LunaScript's render operations with frame skipping enabled on slow devices) (@Wohlstand)\r\n-Fix TheXTech 1.3.7-beta bug which prevented the compat flag \"fix-player-clip-wall-at-npc\" from working as intended (@ds-sloth)\r\n-Fix vanilla editor peculiarity where the order of overlapping blocks might change. Note that overlapping blocks larger than 1 tile are still sorted according to SMBX rules. (@ds-sloth)\r\n-Rapidly clicking on menu items no longer toggles fullscreen. (@ds-sloth, thanks to @Agatha for the suggestion)\r\n-The mouse can now be used to click through message boxes in the main menu. (@ds-sloth, thanks to @Agatha for the suggestion)\r\n-Advanced options are now applied only when hitting Select or returning to the previous screen. (@ds-sloth, thanks to @Wohlstand for the suggestion)\r\n-Fix TheXTech v1.3.7-beta visual bug in which the backdrop did not have proper borders in 4P split screen at 1440p. (@ds-sloth)\r\n-Fix TheXTech v1.3.7 bug in which input methods might get disconnected when switching between battle levels in 3/4-player mode. (@ds-sloth)\r\n-Added an explicit indication that game is suspended when window is out of focus by showing the \"Paused\" label over the gray filter. (@Wohlstand)\r\n-Fix TheXTech v1.3.7-beta bug in which bullet generators not facing the player could become inactive. (@ds-sloth, thanks to @Liebning for the report)\r\n-Tune TheXTech v1.3.7 Modern/Classic Mode logic for when to activate NPCs shown by events to better match Vanilla. (@ds-sloth, thanks to @Liebning for the report)\r\n-Fix critical TheXTech v1.3.6 editor bug where reordering layers resulting in objects losing their layers. (@ds-sloth, thanks to @ChristianSilvermoon for the report)\r\n-Fix TheXTech v1.3.6 editor bug where world music tiles would move a tiny bit when the world was saved (@ds-sloth, thanks to @ChristianSilvermoon for the report)\r\n-Fix vanilla bug where the player could clip downwards when hurt or running into a block while on a platform, guarded by existing compat flag \"fix-player-downward-clip\" [Classic Mode] (affected Frozen Valley in Fallen Spirits and Mushroom Heights in Princess Cliche) (@ds-sloth, thanks to @Agatha for the report)\r\n-Fixed the viewport problem on non-SMBX resolutions at the PS Vita (@Wohlstand, @ds-sloth)\r\n-Fixed the early interrupting of the level workflow when going to the inter-level warp on some platforms (@Wohlstand)\r\n-Fixed the visual glitch of the last screen fade out frame being frozen instead of complete going into darkness (@Wohlstand)\r\n-Fixed the bug that causes gamesave's fails counter not being copied or removed via game menu (@Wohlstand)\r\n-Fix TheXTech 1.3.7-beta editor bug where a player object could become deselected when closing the editor pane (@ds-sloth, thanks to @fbitninja for the report)\r\n-Fixed the incorrect work of the Java-coded INI parser on Android that led some properties being loaded incorrectly (for example, valid background picture fails to load) (@Wohlstand)\r\n-On failures while using interprocess-based level testing, the explicit error messages will be shown (@Wohlstand)\r\n1.3.7.1-hotfix:\r\n-Added workaround for the texture corruption problem on Windows 10+ with the Intel Iris Xe GPU: the depth buffer will be completely disabled here to avoid glitches (@Wohlstand)\r\n-Implemented the proper initialisation of the OpenGL on VirtualBox's SVGA3D driver (@Wohlstand)\r\n1.3.7.1-hotfix2:\r\n-Fixed the startup crash on OpenGL render initialization, now the OpenGL ES 2+ is used instead (@Wohlstand)\r\n\r\nChangelog for 1.3.7\r\n-Fix vanilla peculiarity where medals were sometimes not collected when killed, guarded by \"fix-medal-kill\" [Modern Mode] (@ds-sloth, thanks to @Superbloxen for the report)\r\n-Implemented the in-game assert failures will be shown as in-game message box until perform an emergency close on platforms that doesn't have SDL's message box (@Wohlstand)\r\n-Wii: fix TheXTech 1.3.6.1 inaccuracy in how very small GIF bitmasks are rendered (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta visual glitch when the vanilla cam hotkey was pressed during the level intro scene (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta bug where vanilla cam was not reset before entering credits (@ds-sloth)\r\n-Refine GIF bitmask detection and fallback algorithm (@ds-sloth)\r\n-3DS/Wii: speed up graphics loading code (@ds-sloth)\r\n-3DS/Wii: fix TheXTech 1.3.7-beta bug where levels with 101 layers/events could not load (@ds-sloth)\r\n-Update level loading error screen to be more informative (@ds-sloth)\r\n-Add world loading error screen (@ds-sloth)\r\n-Hints: fix the graphical display of the surfing hint (@ds-sloth)\r\n-Refine the placement of HUD items at low resolutions (@ds-sloth)\r\n-Don't allow pressing the vanilla cam hotkey during a camera pan event (@ds-sloth)\r\n-3DS: add advanced option \"Inaccurate GIFs\" to allow playing in 3D on levels with GIFs (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta bug where battle levels could be created even if the asset pack disables battle mode (@ds-sloth, thanks to @LoveBodhi for the report)\r\n-Fix TheXTech 1.3.6.1 bug where player-dependent event text could be inaccurate if Autocode cancelled a different event (@ds-sloth)\r\n-TheXTech-exclusive events are now processed at the end of the frame they are triggered in (@ds-sloth)\r\n-Fix TheXTech 1.3.1 inaccuracy where a vanilla bug failed to replicate. This bug caused a pending event to be overwritten when two events triggered in a single frame. The bugfix now has compat flag \"fix-event-swap-bug\" (@ds-sloth)\r\n-Improve the performance of levels with many climbable NPCs (@ds-sloth)\r\n-Move the `[intro]` and `[outro]` sections of `gameinfo.ini` to `[activity-setup]` in the `compat.ini` file for the intro/outro levels, for per-level configuration of these settings. Setting the fields via `gameinfo.ini` is deprecated and will be removed during the v1.3.7.x series. (@ds-sloth, @Wohlstand)\r\n-Fix TheXTech 1.3.7-beta bug where it was impossible to enter speedrun mode using the main menu interface (@ds-sloth, thanks to @0lhi for the report)\r\n-Added support for on-exit warp event. (@Wohlstand)\r\n-Fix TheXTech 1.3.7-beta bug where the modern lives count was shown at the title screen for vanilla gamesaves (@ds-sloth, thanks to @0lhi for the report)\r\n-Fix TheXTech 1.3.7-beta bug where \"4shared\" and \"4split\" could give Chars 3 and 4 pet mounts (@ds-sloth, thanks to @0lhi for the report)\r\n-Fix TheXTech 1.3.7-beta defect where it was not clear whether vanilla cam was activated at high resolutions (@ds-sloth, thanks to @0lhi for the report)\r\n-Fix TheXTech 1.3.7-beta inaccuracy in which an NPC that moves into a section during gameplay might fail to spawn (this affected the final boss of Valtteri's Island - Revisited) (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta bug where an assertion failure could be triggered when a player went offscreen in shared screen multiplayer (@ds-sloth)\r\n-Ensure that the player setup menu always remembers the most recently selected characters (@ds-sloth, thanks to @0lhi for the suggestion)\r\n-Fix TheXTech 1.3.7-beta bug where the logical screen could desync after dropping a player (@ds-sloth)\r\n-Fixed the random crash and memory damaging during the load process (@Wohlstand)\r\n-Add ability to change last warp resume setting on hub worlds. This allows playing episodes incompatible with this behavior in Modern Mode. (@ds-sloth, thanks to @0lhi for the suggestion)\r\n-Fix TheXTech 1.3.6 bug where some controls profiles could not be deleted. (@ds-sloth, thanks to @0lhi for the report)\r\n-Added support for custom main menu single intro level or introset powered by recently played episode. (@Wohlstand)\r\n-Added support for custom outro level powered by just completed currently playing episode. (@Wohlstand)\r\n-Fix TheXTech 1.3.7-beta bug where menus could become unresponsive if a player had been dropped while holding a button. (@ds-sloth, thanks to @0lhi for the report)\r\n-Add ability to keep forced character through menu quit (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta bug where the player start points were incorrect for some content (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta bug where hitting Escape at the pause menu could return to menu (@ds-sloth, thanks to @sl4cer for the report)\r\n-Fix TheXTech 1.3.7-beta PortMaster peculiarity where the load screen would be drawn too small (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta Wii bug where the no-assets screen would not display correctly (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta Wii bug where unconverted UI assets could not be loaded (@ds-sloth)\r\n-3DS: display system and video memory usage in Debug Info screen (@ds-sloth)\r\n-Add support for TTF fonts up to 24x24 (@ds-sloth)\r\n-Allow customization of TTF colour of fonts (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta bug where the controller preview on the controls menu was inaccurate (@ds-sloth)\r\n-Fix TheXTech 1.3.7-beta bug where simple editor controls would interfere with text entry in the editor (@ds-sloth)\r\n\r\nChangelog for 1.3.7-beta\r\n-Add ability to trigger event layer smoke in the in-game editor (@ds-sloth)\r\n-Add tracking for medals collected in levels (@ds-sloth)\r\n-Save the number of medals / stars that exist in levels to speed up subsequent loads (@ds-sloth)\r\n-Fix thrower vertical position logic in split-screen, guarded by compat flag modern-npc-camera-logic (@ds-sloth)\r\n-Internal change: added draw plane system to track different object groups' scene depth (@ds-sloth)\r\n-Note: screen-space autocode draws now occur in the HUD plane instead of the level plane (@ds-sloth)\r\n-Added \"opensesame\" world map cheat to unlock paths from level (@ds-sloth)\r\n-Added smooth path unlock animations at the world map (@ds-sloth)\r\n-Added full game and in-game editor support for world map sections that limit screen view (@ds-sloth)\r\n-Added support for more than 2 cameras (@ds-sloth)\r\n-Added support for different display resolutions (@ds-sloth)\r\n-Allow event logic NPCs to consider SMBX 1.3 camera when activating, guarded by compat flag modern-npc-camera-logic (@ds-sloth)\r\n-Added code \"logicscreen\" to view camera used by event logic NPCs (@ds-sloth)\r\n-Add npc.txt attribute \"usedefaultcam\"; set this to \"1\" to force NPCs to use the event logic camera to activate and \"0\" to force them to use the visible camera (@ds-sloth)\r\n-Add compat.ini setting \"dynamic-camera-logic\" which may be disabled to force a level to use the 800x600 camera for all logic (@ds-sloth)\r\n-Added backdrop for levels smaller than the screen at graphics/ui/Backdrop.png (@ds-sloth)\r\n-Added alternative format for world map frame with better support for various display resolutions (@ds-sloth)\r\n-Add ability to edit BGO sort layers and offset in the in-game editor (@ds-sloth)\r\n-Redesigned character select screen for multiplayer game start and player setup (@Savbyn, @ds-sloth)\r\n-Drop/add screen renamed to \"Player Setup\"\r\n-In modern gameplay, the main menu now has a single \"Play Episode\" item instead of separate 1P/2P items\r\n-COMPATIBILITY CHANGE: remove automatic version targeting for pre-SMBX 1.3 content (@ds-sloth)\r\n-Sounds now get quieter when they are further from the screen (@ds-sloth)\r\n-In Modern and Classic modes, now allow negative lives instead of game over (@ds-sloth, @0lhi)\r\n-Game looks for asset packs in the assets/ subdirectory of the user and system directories (@ds-sloth)\r\n-Add ability to specify asset pack by ID in the command line (as well as by path) (@ds-sloth)\r\n-Add ability to switch asset pack at main menu screen by holding select button (@ds-sloth)\r\n-Very long SFX are now played from disk to save memory (@ds-sloth)\r\n-Made some internal memory optimizations to the Block and NPC objects, saving 360KB RAM (@ds-sloth)\r\n-Add hints system to the loading and pause screens (@ds-sloth)\r\n-Fix SMBX 1.3 bug where camera would not track respawning player, guarded by compat flag multiplayer-pause-controls (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug where level fadeout did not properly occur on fail in 2P mode (@ds-sloth)\r\n-Fix ghost, boss, and other NPC target player selection, guarded by compat flag \"fix-multiplayer-targeting\" (@ds-sloth)\r\n-Add ability to resize placed items, section boundaries, and event section boundaries in the editor (@ds-sloth)\r\n-Overhaul the Main Menu and in-game Options menu with support for editing all \"thextech.ini\" options (@ds-sloth)\r\n-Add \"Modern\", \"Classic\", and \"Vanilla\" playstyles which determine which bugfixes and gameplay updates are applied (@ds-sloth, @0lhi)\r\n-Add ability to start speedrun in-game by pressing Select when making a new game save (@ds-sloth, @0lhi)\r\n-Add cheat \"edityourfriends\" to experiment with compatibility settings (@ds-sloth)\r\n-Add option to always use shared or split screen in 2P (@ds-sloth)\r\n-Add ability to play >2P mode with shared or split screen (@ds-sloth)\r\n-Add cheats \"4shared\" and \"4split\" to test 4-player shared / split screen modes (@ds-sloth)\r\n-Add new item drop system, used by default in Modern Mode at low resolutions (@ds-sloth, @ChristianSilvermoon, @0lhi)\r\n-GIF recorder now turns grey and skips frames when recording is slower than gameplay (@ds-sloth)\r\n-Add compat flag \"disable-spin-jump\", which causes the AltJump key to map to a normal jump, but still allows players to dismount. The flag replaces a hack used to force-disable the key in Superb Demo Sisters. (@ds-sloth)\r\n-Fix vanilla peculiarity where plants would make a sound when dying in a no-turn-back level, guarded by compat flag \"fix-visual-bugs\" [Modern Mode] (@ds-sloth)\r\n-Update TheXTech's logic for climbing moving fences (@ds-sloth)\r\n-Fix vanilla peculiarity where the speed of blocks attached to an NPC would not be fully reset when the NPC dies, guarded by compat flag \"fix-attlayer-reset\" [Modern Mode] (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 peculiarity where a player could reach an inaccessible location by respawning while another player was scrolling (@ds-sloth)\r\n-Editor: fix TheXTech 1.3.6 bug where level test might incorrectly start following text input (@ds-sloth)\r\n-Fix vanilla peculiarity where some held NPCs would appear to move on the player's hands / feet, guarded by compat flag \"fix-visual-bugs\" [Modern Mode] (@ds-sloth)\r\n-Change cheat \"shadowstar\" to use a 75% black tint (instead of 100% as in SMBX 1.3) for visibility against dark level backgrounds (@0lhi, @ds-sloth)\r\n-Added support for error boxes at the Wii U to explicitly show reasons of errors to users (@Wohlstand)\r\n-Fixed crash on attempt to execute the \"SetHits\" and \"AllFace\" LunaScript commands (@Wohlstand)\r\n-Speedrun timer no longer permanently stops following initial game win, allowing speedruns of postgame content (@ds-sloth)\r\n-Fix TheXTech 1.3.5.1 bug where the lower half of a player's sprite could be shown behind a shoe / sack (@ds-sloth)\r\n-Fixed the inability to close error LunaScript parse error box on Android when file contains too long lines (@Wohlstand)\r\n-Fix vanilla editor bug where NPC spawn logic might be inaccurate on level test (@ds-sloth)\r\n-Fix TheXTech 1.3.6 bug where an item could be cloned by changing characters during powerup animation (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 OpenGL bug where a mask larger than its texture could be drawn incorrectly (@ds-sloth, thanks to @AntonioGZZ96 for the report)\r\n-Fix TheXTech 1.3.6.1 inaccuracy affecting \"Endless Exploration\" where levels started via an invalid warp point could not be played (in SMBX 1.3, the warp point is ignored) (@ds-sloth)\r\n-Fix TheXTech 1.3.1 bug where many max-ID custom GFX were not loaded (including player-5 map sprites) (@ds-sloth, thanks to @AntonioGZZ96 for the report)\r\n-Add option (on-by-default) for gamepads to use simple editor controls. Prevents getting locked in the editor. (@ds-sloth)\r\n-Fix TheXTech v1.3.6 editor bug where NPC properties could change when their layer was hidden. (@ds-sloth, thanks to @cre8iveexercise for the report)\r\n-Fix TheXTech v1.3.6.1 Android bug where the screen would be black after switching applications. (@ds-sloth)\r\n-System message boxes will have their unique style that is different from the in-game one. (@Wohlstand)\r\n-Update TheXTech userdata locations to system-native locations on new installs. (@ds-sloth, @Wohlstand)\r\n\r\nChangelog for 1.3.6.6\r\n-Fix vanilla bug where vehicle could be vulnerable if player entered it during AltJump (requires frame perfect down press), guarded by compat flag \"fix-vehicle-altjump-bug\" (@ds-sloth)\r\n-Fix vanilla bug where vehicle could not be exited if player entered it while holding AltJump key, guarded by compat flag \"fix-vehicle-altjump-lock\" (@ds-sloth)\r\n-Fixed the problem when a touch screen is not detected on some Android devices (@Wohlstand)\r\n-Fix minor bug that caused certain configurations on macOS to crash on startup (@ds-sloth)\r\n-Fix Wii U bug where resizing the game screen could cause the game to crash (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 crash caused when a pet mount is eating the last NPC in the level and the eaten NPC is killed (@ds-sloth)\r\n-Fixed Wii U bug where game quits into the black screen instead of the Wii U's main menu when game started from the Aroma (@Wohlstand)\r\n-Fix TheXTech 1.3.6.1 inaccuracy allowing the player to dismount a vehicle when blocked by an NPC (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug where conveyor belts would sometimes not activate correctly (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 OpenGL bug where the shadow effect interacted inaccurately with bitmasked textures (@ds-sloth)\r\n-Fix TheXTech 1.3.6 bug where it was impossible to unpause while holding an item (@ds-sloth, thanks to SimplyMav for the report)\r\n-Fix TheXTech 1.3.6.1 bug where GIF masks for sizable block 261 were not loaded (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug where SMBX level version autodetection did not work for platforms (note that this logic will be fully removed in 1.3.7) (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug where NPCs on hidden layers were incorrectly allowed to chain-activate. (The bug affected the outro scene of Dynamite Grotto in SRW2.) (@ds-sloth)\r\n-Add workaround for TheXTech 1.3.6.1 Modern Mode inaccuracy where NPC clipping did not match SMBX 1.3. (This bug affected the same scene.) (@ds-sloth)\r\n-Fix vanilla bug where player can get softlocked if hit by a grabbable NPC while digging dirt, guarded by compat flag \"fix-player-stuck-on-dirt\" (Classic Mode) (@ds-sloth)\r\n\r\nChangelog for 1.3.6.5\r\n-Fix TheXTech 1.3.6 bug where auto movement on the world map was not reset when switching episodes (@ds-sloth)\r\n-Fix TheXTech 1.3.6 bug where dropping a player at the world map removed their mount (@ds-sloth)\r\n-Fix TheXTech 1.3.6.4 editor bug where NPCs could not be erased from a battle level (@ds-sloth)\r\n-Fix vanilla bug where an incorrect frame gets shown when player attempts to enter the star-locked pipe, guarded by compat flag fix-visual-bugs (@Wohlstand)\r\n-Added an experimental Sub-Hubs sub-system: it's will be possible to mark any level as a \"sub-hub\" where player can save the game and resume it from that level instead of the main hub (@Wohlstand)\r\n-Added the CopyVar command to the LunaScript which allows to copy value of one user variable to another (@Wohlstand)\r\n-The fails counter now increments whenever any player dies (not just P1) (@ds-sloth)\r\n-Fix a graphical bug where an NPC emerging upwards from a block might use the wrong frame and width (@ds-sloth)\r\n-Fix the logical size of an NPC emerging downwards from a block, guarded by compat flag fix-npc-emerge-size (@ds-sloth)\r\n-Fix a vanilla bug where blocks could become intangible to NPCs after a coin switch was used (@ds-sloth)\r\n-Add a terminal velocity of 16 for rail/lineguide platform blocks, guarded by compat flag fix-platform-acceleration (@ds-sloth)\r\n-Add a workaround for a memory exhaustion error from rail/lineguide platform blocks (@ds-sloth)\r\n-Fix TheXTech 1.3.6 bug where carefully-constructed single-frame wall clips would not work in TheXTech (@ds-sloth)\r\n-Fix TheXTech 1.3.6 bug where the vanilla player-filter-bounce bug was not correctly reproduced in TheXTech (@ds-sloth)\r\n-Added automatical language detection for Vita (@Wohlstand)\r\n-Fixed a leak of file descriptors in the translations sub-system (@Wohlstand)\r\n-Allow asset packs to customize Font 5 (outlined mixed-case font) (@ds-sloth)\r\n-Fixed the problem when anti-cheat trap will break levels in the editor (@Wohlstand)\r\n-Fixed the unexpected fade-in effect that gets played during any level quits by warps (@Wohlstand)\r\n-Fixed the incorrect work of the touch screen on PS Vita (@Wohlstand)\r\n\r\nChangelog for 1.3.6.4\r\n-Use OpenGL as the default renderer on supported platforms (\"sdl\" may be used in config.ini or at at the command line to request the SDL2 renderer)\r\n-Add ability to edit battle levels using in-game editor (@ds-sloth)\r\n-Fix bug where it was not possible to return to the main menu from an invalid battle mode level (@ds-sloth)\r\n-Fix vanilla bug where P2 could not close message box during shared screen coop (@ds-sloth)\r\n-Changed credits font id to 5 for outlines. (@0lhi)\r\n-Fix Emscripten bug where worlds created in the editor would get lost on page refresh (@ds-sloth)\r\n-Remove option \"osk-fill-screen\"; this behavior is now used whenever a touchscreen is active (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 crash on activating a joystick with an empty SDL name (@ds-sloth)\r\n-Fixed the TheXTech 1.3.6.3's startup crash on some 32-bit Windows systems. (@Wohlstand)\r\n-Removed config option \"editor-edge-scroll\" (enable by default on gamepads) (@ds-sloth)\r\n-Add ability to scroll in editor using mouse wheel or touchpad (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug where bitmask GIFs would sometimes be rendered incorrectly (@ds-sloth)\r\n-Fix TheXTech 1.3.6 bug where the editor could occasionally crash when placing warps (@ds-sloth)\r\n-Fix TheXTech 1.3.6 bug where the editor could not set the level name (used in battle mode) (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug affecting debug builds where the editor could crash when erasing objects (@ds-sloth)\r\n-Improve the performance of MixerX on low-end targets (3DS, Wii, Vita) (@Wohlstand)\r\n\r\nChangelog for 1.3.6.3\r\n-Fixed a TheXTech 1.3.6.1 crash where object lookup table could not process items with negative size (@ds-sloth)\r\n-On level load, replace negative item sizes with size zero (such items cannot be created by any editor)\r\n-Fix TheXTech 1.3.6.1 bug where item would drop above incorrect player in SharedCoop mode (@ds-sloth)\r\n-Fix rare TheXTech 1.3.6 bug where newly-added player could get stuck in immune state if other player died (@ds-sloth)\r\n-Display current version (and git revision if a dev build) on title screen (@ds-sloth)\r\n-Wii U port: add WUHB build (@ds-sloth)\r\n-Fix TheXTech 1.3.6.1 bug where the score display would not show '9's when using legacy assets (@ds-sloth)\r\n-Use the ANGLE shader translator library to improve support for OpenGL ES 3.0 shaders on desktop platforms (@ds-sloth)\r\n-Fix vanilla / TheXTech bug where a player could clip into ground while standing on a downwards-moving slope, guarded by fix-player-downward-clip compat flag (@ds-sloth)\r\n-CONTROLS / LOGIC CHANGE: when riding a purple pet mount, alt run is now used to enter a pound (down is still used in compat mode) (@0lhi, @ds-sloth, @Wohlstand)\r\n-Fix TheXTech modern mode bug where simultaneous slope-ground collisions were not handled properly (@ds-sloth)\r\n\r\nChangelog for 1.3.6.2\r\n-Fixed a crash on attempt to add a game directory after storage permission grant on Android (@Wohlstand)\r\n-Added workaround for Little File Manager on Android (to open level file by content with no resources) (@Wohlstand)\r\n-Fixed TheXTech 1.3.6.1 bug where error messages at the main menu would lock the game (@ds-sloth)\r\n-Fixed an inability to parse LunaDLL Autocode files via MSVC builds of the game (@Wohlstand)\r\n-Added experimental support for the Nintendo Wii U homebrew platform (@Wohlstand).\r\n-Fixed audio not working when \"/3ds/dspfirm.cdc\" is missing on the 3DS (@Wohlstand).\r\n\r\nChangelog for 1.3.6.1\r\n-Fixed some of the performance slowdown problems using improved object spatial lookup tables (@ds-sloth)\r\n-Fixed the work of Sound FX on big-endian architectures (@Wohlstand)\r\n-Fixed directory creation flags on Vita (@suicvne)\r\n-Minor fixes to the gameplay recording system (@Wohlstand, @ds-sloth)\r\n-Changed the logic of the music pausing at the pause menu when P-Switch is active: don't suspend the entire sound stream, do suspend the music only (@Wohlstand)\r\n-Added experimental support for the Nintendo Switch homebrew platform (@Wohlstand)\r\n-Added experimental support for the Nintendo Wii homebrew platform (@ds-sloth - graphics and controls, @Wohlstand - audio and threading)\r\n-Added CLI build of the application with very minimal dependencies, suitable for CI testing of the game logic (@ds-sloth)\r\n-Added incomplete version of the Nintendo 3DS port (locked to 400x300 resolution until 1.3.7) (@ds-sloth)\r\n-Added performance statistics (in debug info HUD) for level designers and developers to check CPU time spent on each game task (@ds-sloth)\r\n-In-Game Editor: added ability to customize block, BGO, NPC, tile, music, section BG, and sound layouts in the editor (@ds-sloth)\r\n-In-Game Editor: added new Magic Block feature that automatically picks the proper item given its context (@ds-sloth)\r\n-Adjusted the bitmask to the RGBA conversion algorithm to apply the compromise among different semi-transparent textures (@Wohlstand)\r\n-Fixed an invalid byte hacking behaviour at the memory emulator on x86_32 processors (@Wohlstand)\r\n-Fixed the validation of  assets packages on Android where \"introset\"-based assets were treated as invalid (@Wohlstand)\r\n-The new font engine based on the Moondust Engine implementation has been added. Now, the game can print Unicode text in a condition where the assets package contains the necessary font resources. (@Wohlstand, @ds-sloth)\r\n-Added animation for keyhole exit. (@ds-sloth)\r\n-Added ShowLevelName and ShowLevelFile LunaScript commands (@Wohlstand)\r\n-An uncolored PCursor.png is now automatically colored and used for indicating player menu selections; MCursor0.png and MCursor3.png are used as fallbacks. (@ds-sloth)\r\n-If controller inputs are displayed onscreen, Player 2's controller is now shown at the world map screen. (@ds-sloth, @0lhi)\r\n-Controls: don't continuously rumble on player hurt after getting level exit NPC. (@ds-sloth)\r\n-Added compatibility flags for multiple early vanilla bugfixes. (@ds-sloth)\r\n-Added level exit cheats from SMBX2. (@SpencerEverly, @0lhi, @ds-sloth)\r\n-Fixed an unexpected disappear of LunaScript-drawn text and graphics when a pause menu or a message box was open. (@Wohlstand)\r\n-Added game save info (score, lives, coins, etc) display to the main menu screen. (@ds-sloth)\r\n-Allow a single player to reconnect their controls at the menu screen. (@ds-sloth)\r\n-In Magic Hand mode, cursor now snaps to current layer's grid, instead of global grid. (@ds-sloth)\r\n-Fixed crash that could occur if player warped with >2P cheat active. (@ds-sloth)\r\n-Correctly render large level graphics at the edge of the screen. (@Wohlstand, @ds-sloth)\r\n-Miscellaneous improvements for the Emscripten experience on iOS (@ds-sloth)\r\n-Fixed bug where touchscreen controller would stay active during text entry screen without rendering (@ds-sloth)\r\n-Added an ability to place multiple stars at the same section using the Unique Star ID special value. (@Wohlstand)\r\n-Added support for Discord Rich Presense support (need to enable it at the game settings). (@Wohlstand)\r\n-Added the localisation system which allows you to translate engine into other languages than English. (@Wohlstand)\r\n-Deep revision of the game's internal NPC and render logic, with up to 2x higher framerates on very large levels. (@ds-sloth)\r\n-Fixed vanilla bug where player could be crushed by a sloped ceiling (@ds-sloth)\r\n-Fixed vanilla bug where player could be crushed by a horizontally (not vertically) moving ceiling (@ds-sloth)\r\n-Fixed TheXTech bug where a semi-transparent message box was not rendered correctly with a single-line messages. (@ds-sloth)\r\n-Log messages now include timestamps relative to application start time. (@Wohlstand)\r\n-Keyhole framerate fix will be enabled by default (@Wohlstand, @0lhi)\r\n-Fixed vanilla bug where player could fall through an Instant/Portal warp while performing the \"Pound\" move. (@ds-sloth)\r\n-Fixed vanilla bug where held item would not affect a hostile NPC that intersects with an immune NPC (such as an egg container). (@ds-sloth)\r\n-Fixed vanilla bug where engine would appear to break if an event changed the boundaries of a different section. (@ds-sloth)\r\n-Added modern animations for section boundary change in multiplayer. (@ds-sloth)\r\n-The keyhole framerate fix has been turned on permanently, now it's impossible to disable this fix (@Wohlstand, @0lhi)\r\n-Misc improvements for the level testing experience (@ds-sloth, @0lhi)\r\n-Restyled pause menu user interface (@ds-sloth, @0lhi)\r\n-Misc stability improvements (@Wohlstand, @ds-sloth)\r\n-Fade out effect occurs during the last 30 frames of the SMBX64 death animation instead of during a pause following the animation (@ds-sloth)\r\n-Added support for starting a level test from a certain warp (available in editor and command line flag -w / --start-warp) (@ds-sloth)\r\n-Added support for starting a world with a specific save (--save-slot X) from the command line (@ds-sloth)\r\n-In modern mode, pause speedrun timer during qScreen animation (game logic is frozen) (@0lhi, @ds-sloth)\r\n-On attempt to start a level without start point or entrance warp specified, an error message will be shown (@Wohlstand)\r\n-Update of controller disconnect user interface (@0lhi, @ds-sloth)\r\n-Add a beta OpenGL renderer for the game, supporting proper bitmask (GIF) rendering (@ds-sloth)\r\n-The pause menu is now able to be controlled by all players. (@ds-sloth, @0lhi)\r\n-Multiple message boxes are now controlled by the player who originally triggered the event. (@ds-sloth)\r\n-Added simple pre-processor that allows showing different message text depending on a playable characters (@Wohlstand)\r\n-Updated scroll transition effect for warps; no longer pauses the game for other players. (@ds-sloth)\r\n-Fix vanilla bug where NPC would never respawn if it came onscreen same frame as it had timed out. (@ds-sloth)\r\n-Removed thextech.ini option to enter codes at pause menu; this ability is now unlocked by the input sequence LRLRRLRRRLRRRR (@0lhi, @ds-sloth)\r\n-Removed TheXTech 1.3.6 exploit where dead player could be respawned for free by dropping then adding them (@ds-sloth)\r\n-Removed \"strict-drop-add\" thextech.ini option (@ds-sloth)\r\n-Removed deprecated global controls settings \"ground-pound-by-alt-run\", \"enable-rumble\", and \"enable-battery-status\". Set these per-profile in the controls menu. (@ds-sloth)\r\n-In speed run mode, indicate whether Cheater mode is active by showing \"CMode\" instead of \"Mode\" in the top-right. (@0lhi)\r\n-Fixed the version number being printed improperly via `--version` command-line argument. (@Wohlstand)\r\n-Fixed TheXTech 1.3.6 bug where P2 would keep partial control during Single Coop (superbdemo2) and Cloned Player (superbdemoX) modes. (@ds-sloth)\r\n-Fixed TheXTech 1.3.6 bug where 2P gameplay recordings might fail to replay correctly. (@ds-sloth)\r\n\r\nChangelog for 1.3.6\r\n-Added an ability to toggle drawing of the HUD and other on-screen meta data using F1 key (@Wohlstand)\r\n-Added an ability to type cheats on touchscreen using special new cheats key (@Wohlstand, @ds-sloth)\r\n-Added native support for LunaDLL Autocode script language (@Wohlstand)\r\n-Added deaths counter feature (keeps counting of deaths per game save) (@Wohlstand)\r\n-Reworked the controllers support (@ds-sloth)\r\n-Touchscreen support for non-Android platforms (@ds-sloth)\r\n-Added ability to save multiple keyboard profiles (@ds-sloth)\r\n-New multiplayer start screen (@ds-sloth with design input from @0lhi)\r\n-Added controller disconnect recovery screen (@ds-sloth)\r\n-Ground pound by alt run is now a per-controller setting (@ds-sloth)\r\n-Added an ability to add/drop the second player from the pause menu (@ds-sloth)\r\n-Added the support for real-time audio effects such as reverb and SPC echo. They can be configured by sounds.ini for each level section (@Wohlstand)\r\n-Performance improvements for showing, hiding, and moving layers (@ds-sloth)\r\n-Reduced game object memory footprint (@ds-sloth)\r\n-Performance improvements when run on slow filesystems (@ds-sloth)\r\n-Fixed unexpected music got started after the level got entered too quick from the world map (@Wohlstand)\r\n-Fixed the vanilla bug: a Player can stuck in the pipe when enters it being a fairy (@Wohlstand)\r\n-Added an option to enable the fast walking on the world map (@ds-sloth, @Wohlstand)\r\n-Added an ability to customize UI/common assets from the episode and level sides (@Wohlstand)\r\n-Added a blink effect at speedrun timer on level/episode completion (@Wohlstand)\r\n-Added an in-game editor based on the original game's editor code (requires EditorIcons.png asset) (@ds-sloth)\r\n-Added config option to show the current episode's title onscreen (@ds-sloth)\r\n-Fixed the clipped render of the Roto-Disk's effect (@Wohlstand)\r\n-Fixed the lock/star sign behavior at locked two-way warps: now they work correctly at the opposite side (@Wohlstand)\r\n-Added an ability to set a different path for the user directory by command line argument (@Wohlstand)\r\n-Blocked an ability to start the game with no episodes or battle levels available in the list and prevented several issues with this (@Wohlstand)\r\n-Added option to disable or always perform 2x texture scale down optimization (@ds-sloth)\r\n-Added the ability to have multiple intro levels per assets pack (if introset directory exists and it contains at least one level file, they will be selected randomly every time when main menu opens) (@Wohlstand)\r\n-Added game selection menu into Android launcher: every browsed assets directory will be remembered and can be accessed quickly using the new menu (@Wohlstand)\r\n-Added the individual sound for Ludwig von Koopa's death (@Wohlstand)\r\n-Added the individual sound for Larry Koopa's death and shell spinning after stomp (@Wohlstand)\r\n-Fixed an improper new (when specific compatibility option is not disabled) behaviour of the Skull Raft when the head segment impacts a wall but loses a floor at the same time, causing the following segments to suspend forever (@Wohlstand)\r\n-Fixed the issue of a possible fall through the floor if walking point-blank to a block that had an extracted bonus and another solid block placed under it (does not happen if the \"fix-player-downward-clip\" option was disabled) (https://github.com/Wohlstand/TheXTech/issues/401) (@Wohlstand)\r\n-Removed the \"require-ground-to-enter-warps\" compat.ini option as redundant: there is the native option of LVLX and 38A-LVL level formats to enable this behaviour. (@Wohlstand)\r\n-Some options of the compat.ini have been renamed (for backward compatibility, there are aliases kept) (@Wohlstand)\r\n-Added the individual sound for Link's iceball shoot (@Wohlstand)\r\n-Added the proper sound for Link's fireball shoot (@Wohlstand)\r\n-Fixed TheXTech bug where if a moving layer stopped while a player was climbing a BGO, the player would keep moving. Also added bugfix for corresponding vanilla bug that occurred if a player was climbing a moving layer NPC. (@ds-sloth)\r\n\r\nChanges for 1.3.5.3-1\r\n-Fixed autostart events behaviour\r\n\r\nChanges for 1.3.5.3\r\n-Fixed the vanilla crash: Attempting to freeze any Pokey segments when they have the \"noiceball=0\" option set will cause the out-of-range error with NPC-ID=-3 in a random moment. (https://github.com/Wohlstand/TheXTech/issues/220)\r\n-Changed the user directory location for macOS-built games into the \"~/TheXTech Games/<bundle name without .app suffix>/\" to avoid conflict between different games ran on the same device.\r\n-Changed the debug assets location on macOS into \"~/TheXTech Games/Debug Assets/\" to simplify the debug environment installation.\r\n-Fixed BGO-65, Bowser III, and rail platforms compatibility with SMBX build version up to 30\r\n-Fixed the false positive that enables the Yoshi Mode of music when a player actually doesn't ride any Yoshi\r\n-Mario and Luigi will be only available at episodes built with SMBX older than build version 30 (the first version that introduces Peach, Toad, and Link)\r\n-Fixed the invalid vertical offset while drawing a playable character sprite\r\n-Resolved the old jittering problem when drawing a playable character while moving the camera (thanks to @ds-sloth for the fix)\r\n-Fixed the minor graphical distortion of a playable character that appears while flying up and down with a shoe.\r\n-The last played episode will be reminded and scrolled at the episode selection menu\r\n-Improved episodes menu control: added mouse wheel, page-up/page-down/home/end/delete, and left/right/alt-run/alt-jump/drop on controllers to scroll episodes lists\r\n-Fixed the vanilla bug: episodes list menu gets reset every attempt to go back from the character or the game save slot select menu\r\n-Fixed the soft-lock in an attempt to erase the gamesave using the two-player game menu\r\n-Order of episodes lists now alphabetical\r\n-Added the \"compat.ini\" option to require players to be on the ground to enter warps\r\n-On-screen elements such as HUD and other printing debug information will not shake with in-game happenings\r\n-Added screen fading effects at the world map and levels\r\n-Added support for different transition effects per every warp entry\r\n-Fixed the vanilla bug: The deadlock in the form of a looped message box showing will happen in the condition: the first NPC activates another NPC using recursive activation of all touching NPCs. And when the second NPC has an on-activation event trigger that hides its layer and shows a message box\r\n-Fixed the unexpected Wart's death sound spam\r\n\r\nChanges for 1.3.5.2\r\n-Fixed the ARM64 building\r\n-Updated the log writer\r\n-Disabled the \"Boss Dead\" event hook to stop the episode speed-run timer by default, added the compat.ini options to enable and configure it\r\n-Fixed the unexpected Swooper flap sound played after the Swooper has gone far away\r\n-Fixed the vanilla bug: The Swooper has avoided being started to fly when inactive\r\n-Added the compatibility level command line option separated from the speed-run modes (However, the speed-run mode will still override the compativility level)\r\n-Added an option to enable the background game controller input handling\r\n-Portal warps have been allowed to be used for level entrances and level exits (Instant warps kept unsupported to avoid possible compatibility issues)\r\n-Added the cannon pipes that can shoot the player with a given speed\r\n-Added the workaround for custom background2-42 that has non-multiple 4 height (there are several custom sprites with the 3455 invalid height, they must be 3456)\r\n-Fixed an incorrect speed-run timer render at multiplayer mode\r\n-The controller state print has been replaced with a controller drawing to indicate the pressure state of buttons\r\n-Added the opt-in 2x scale down optimization for all textures to reduce the memory usage\r\n-Fixed the confusion when using the modern section auto-scroll settings when an auto-scroll fix was disabled\r\n-License was changed into GPLv3 (see details: https://github.com/Wohlstand/TheXTech/issues/162)\r\n-Enabled the work on the Android TV\r\n-Fixed the game start error after granting the storage permission on Android 9 and lower\r\n-Added an ability to change the look of the launcher screen on Android via gameinfo.ini (Change the logo, change the background, and set up the simple animation)\r\n-Added mask fallback support for custom graphics mask compatibility for old episodes\r\n-Fixed vanilla bug: Attempt to enter the locked warp or take the star with star-locked warp the game will crash if a level has no BGO at all\r\n-Make turn blocks (block-90) work as bricks when SMBX19 and lower level file version is open\r\n-Added a workaround for the yellow platform (NPC-60) at \"The Invasion 1\" episode where it won't move at level42\r\n-Added Billy Gun (NPC-22) behavior compatibility with SMBX 1.2 and 1.1.x and older file formats\r\n-Added wireless game controllers battery status displaying when enabled at the settings\r\n-Added device battery status displaying at the right-top corner when enabled at the settings\r\n-Fixed an inability to save the game when game assets were stored at SD Card on an Android device\r\n-Added support for gamecontrollerdb.txt at the assets root and at the user directory\r\n-Fixed vanilla bug: World map music won't resume after going through the warp that leads to no music points or the music point with the same song\r\n-Fixed the improper custom music change when going through the warp on the world map\r\n-Fixed vanilla bug: Offscreen NPC can now respawn during the stopwatch freeze\r\n-Added Thwomp (NPC-37) behavior compatibility with SMBX 1.0.x\r\n-Added the Yoshi mode support for specified songs at the music.ini: mute selected track number without Yoshi, enable it back when Yoshi is presented\r\n-Fixed a bug when a last world map music is not remembered when sound is disabled\r\n-Fixed the wrong restart place when running the lasanha of levels and \"restart level after death\" option is enabled\r\n-Fixed the NPC logic distortion caused by early gfx update\r\n-Added an ability to customize an outro behaviour using gameinfo.ini\r\n-Battle mode access can be disabled via gameinfo.ini\r\n-Two-player game mode access can be disabled via gameinfo.ini\r\n-The Scores of Power-Ups (NPC-9, NPC-14, NPC-34, NPC-169, NPC-170, NPC-182, NPC-183, NPC-184, NPC-185, NPC-249, NPC-250, NPC-264, NPC-277) can be changed via npc-X.txt\r\n-Added a fixed behavior for rail platforms and grinder (NPC-60, NPC-62, NPC-64, NPC-66, NPC-104, NPC-179). It can be enabled using new-added custom values\r\n-Fixed the collision bug when an attempt of the player with the shoe to jump up at the moving NPC being pushed into the wall, causes the player to go through the wall\r\n-The settings/thextech.ini config file will be created on the first launch if not exist\r\n\r\nChanges for 1.3.5.1\r\n-Episodes and battle levels lists now loading in the separated thread with a progress showing\r\n-Fixed the regression caused world map music not being switched properly\r\n-Added an ability to control the intro scene activity on the level by `gameinfo.ini`: set maximum number of running players, and enable/disable the activity at all\r\n-Fixed an inability to copy the game save based on old files at episode directory\r\n-Fixed the vanilla bug: incorrect blooper effect when stomped (needed an npc-*.txt with the \"jumphurt=0\" flag being set)\r\n-Changed the default compat.ini value \"fix-autoscroll-speed\" into \"false\" to prevent possible glitches from invalid setup given by several old levels\r\n-The \"Checkpoint\" sound will be played when selecting the \"Save and Continue\" menu item at the pause menu\r\n-Fixed a minor glitch at the game save copy and erase menu while moving the mouse under menu block\r\n-Fixed the bullet bill getting stuck after it got unfrozen (Thanks to the @ds-sloth for the contribution!)\r\n-Updated an inter-process protocol with adding new commands\r\n-Fixed the vanilla bug: The dragon coin has avoided being turned into the block by the P-Switch activation\r\n\r\nChanges for 1.3.5\r\n-Fixed the gamepad control stuck because of axes returned value that doesn't equal the initial state on some devices\r\n-Fixed the invalid conversion of old sprites with a mask that is smaller than the front\r\n-Implemented usage of the SDL_GameController with some extras like using the rumble and the battery status (Render of the status is not implemented yet)\r\n-Player now can quit the credits screen by pressing the Start button on the gamepad\r\n-Fixed the glitched rendering of the first frame at the main menu and at the world map when frame-skip is enabled\r\n-Every gamepad model will keep its individual settings per player\r\n-Added the \"Reset to default\" menu item to reset controls\r\n-Resolved the slow work of the world map on weak devices\r\n-Added the performance debug printing that you can activate by the F3 key\r\n-Automatically select the gamepad on startup if the user didn't manually set up the keyboard usage\r\n-Added the ability of the hot connection and disconnection of game controllers\r\n-Control mappings have been moved into the \"controls.ini\" to keep the \"thextech.ini\" file being human-friendly\r\n-Swooper can play the flap sound if presented at the assets pack\r\n-Iceball may have the individual shoot sound of sounds.ini enables that\r\n-Fixed an unexpected ability of Peach to spin-jump from off vines and on pressing of the ALT-JUMP while the JUMP key is held\r\n-Fixed the GFX shaking of the upside-down piranha plants when they got customized in some cases\r\n-NPC freezing sound got an individual sound instead of the shell hit\r\n-Frozen NPC breaking got an individual sound\r\n-Added an ability to put the silence instead of the actual sound file by specifying the \"silent=true\" field instead of the \"file=...\"\r\n-Thwomps will slightly shake the screen on their fall\r\n-Yoshi's ground pound will slightly shake the screen\r\n-Sprout vine from blocks got an individual sound\r\n-Fixed an incorrect word wrapping at message boxes\r\n-Fixed the vanilla GFX bug: the thwomp's ground pound dirt effect being improperly aligned\r\n-Bowser III'rd will shake the screen on his ground pound\r\n-Fixed the vanilla bug: Link may die when attempting to use the Clown Car being in a fairy form, and when flying up on the Clown Car while any vines behind him\r\n-Avoided the ability to switch the playable character by the Clown Car at the top of a player switch block\r\n-Added an ability to restore old checkpoints behavior of the SMBX 1.3 using the \"compat.ini\" file\r\n-Fixed the vanilla GFX bug: an incorrect Link's alignment at the Clown Car after being transformed from anybody who rides Yoshi\r\n-Fixed the skull raft getting stuck at the ceiling slope blocks\r\n-Fixed the vanilla GFX bug: the unexpected water splash effect appearance at BGOs\r\n-Fixed the inability to set up the jump button on gamepads in some conditions\r\n-Fixed the vanilla bug: inability to set the autoscrolling for any non-zero section\r\n-Added the support for the simple-style per-section autoscrolling setup from the SMBX-38A specification\r\n-Fixed the vanilla crash when NPC being located far off the screen, tries to find blocks\r\n-Added an ability to copy and delete game saves from the game menu\r\n-Fixed the missing \"Game Beaten\" flag while loading the game save\r\n\r\nChanges for 1.3.4\r\n-Fixed the inability to use any custom sounds and custom music at level's custom data directory\r\n-Added an ability to change sound setup (sample rate, channels, sample format, and the buffer size) via the [sound] INI section\r\n-Optimized the work of a lazy-decompression algorithm workflow\r\n-At credits, the bible quote has been replaced with a meme\r\n-Fixed the vanilla bug: free-falling platforms getting confused when the player changes their power-up state\r\n-Fixed an inability to autostart events when \"restart level after death\" mode is enabled on the world map\r\n-Fixed the case when Pokey getting collapsed during walking on slopes\r\n-Fixed an unwanted player bounce by a filter block after NPC stomp\r\n-Added the \"compat.ini\" support for episode and level specific to enable/disable certain bugs of the old game\r\n-Fixed the vanilla bug: player can get clipped into floor while standing on a block of a down-moving layer\r\n-Fixed the vanilla bug: NPC can get clipped into floor while standing on a block of a down-moving layer\r\n-Fence BGOs now can move player together with a moving layer as vine NPCs does\r\n-Fixed the vanilla bug: attempt of player to jump from NPC standing together with getting pushed into the wall causes the clipping\r\n-Fixed the vanilla bug: resolved an incorrect behavior of a skull raft cells that makes the raft being squished when reaching a barrier wall\r\n-Credits look and performance has been improved\r\n-Fixed the vanilla bug: Peach can't escape the running shell surf\r\n-Loading screen now goes smoother (except Emscripten)\r\n-Level testing will quit on success completion of a level\r\n-Improved the framerate controller behavior, added the guarantee of the 65.1025 FPS on almost all supported platforms\r\n-Added the Speed-Runner mode which can be enabled via the --speed-run-mode <mode> command line argument\r\n-Fixed an wrong FPS while the key-hole exit is in process\r\n-Improved the frame-skip logic\r\n\r\nChanges for 1.3.3.1\r\n-Fixed a crash, caused by the incremental locked warps counter that leads BGO array exciting\r\n-Added support for Windows ARM64 target architecture\r\n-Added support for Apple Silicon target architecture\r\n-Fixed the inability to enter levels through warp were extension is missing\r\n\r\nChanges for 1.3.3\r\n-Added the \"gameinfo.ini\" to give the support for the partial customization of internals (window/game title, character names in menu, credits, etc.)\r\n-Added an ability to globally override NPC settings by placing of npc-*.txt file into graphics/npc directlry of assets\r\n-Added an ability to run executable with a different assets directory specified\r\n-Added the support for playable character custom INI calibrations\r\n-Message box line breaking algorithm has been improved, added support for new-line characters\r\n-Settings now can remember the initial state of the frame-skip and show-fps options\r\n-Added an option to make use the Alt-Run key for Yoshi's Ground-Pound ([gameplay] section and 'ground-pound-by-alt-run' boolean field)\r\n\r\nChanges for 1.3.2.4\r\n-Allow music change for outro level (if outro level has non-silent music, it will be played instead of built-in)\r\n-Allow unlimited count of episodes and battle levels\r\n-Fixed an incorrect effect frames count rounding, caused glitches at the death effect for a Hammer Bros.\r\n-Corrected the sensitivity of axes-based sticks of game-pads to prevent the mystery, caused by value errors\r\n\r\nChanges for 1.3.2.3\r\n-Fixed a SIGFPE crash, caused by an attempt to load some custom effects\r\n-Resolved the inability to grab vegetables normally, caused by an incorrect porting of logical expression\r\n\r\nChanges for 1.3.2.2\r\n-Fixed an incorrect computation of effect sizes, caused by a missing part of code\r\n\r\nChanges for 1.3.2.1\r\n-Reworked joysticks support\r\n-Added a workaround for case-sensitive file systems\r\n-The 1up sound now playing multiple times if the player has got more than 1 life (for example, a 3-up moon)\r\n-Fixed a broken work of magic potion warp packed inside of a grass container\r\n-Fixed a minor inaccuracy of the moving layers system\r\n-Fixed an incorrect process of blocked playable characters while loaiding the episodes list\r\n\r\n\r\nChanges for 1.3.2\r\n-Added an own timeout sound-92 for P-Switch timer (will be played with 3 seconds before the end)\r\n-Added a level testing interprocess interface to work with PGE Editor\r\n-Fixed the Podoboo's tail effect glitch\r\n-Fixed an incorrect priority of BGO65 in the level file of SMBX prior to 10 format\r\n-Fixed a classic Bowser's behavior in \"The Invasion 1\" episode from SMBX 1.0.x\r\n-Removed 20 layers limit to show/hide/toggle per event\r\n-Extended limit to use up to 200 sections in one level\r\n-Added an error message on attempt to open an invalid level file\r\n-Fixed a glitch of the rendering of the two-camera screen caused by an incorrect SDL_RenderSetViewport() call usage\r\n\r\n\r\nChanges for 1.3.1 (First version made by Wohlstand in March 2020):\r\n\r\nTechnical stuff\r\n-The whole code has been ported into C++, taking use of libFreeImageLite, SDL2, and MixerX.\r\n-Built-in Editor has been removed in favor of PGE devkit\r\n-Full support of UTF-8 in filename paths and internal text data (original game had the only 8bit ANSI support).\r\n-For graphics and controlling, it uses an SDL2 library while original game have used WinAPI calls and GDI library.\r\n-It uses PGE-FL that has a better file formats support.\r\n-A support for WLDX world maps are allowing unlimited credits lines and custom music without necessary to use a music.ini for music replacements.\r\n-Some LVLX exclusive features now working: vertical section wrap, two-way warps, custom \"star needed\" message, warp enter event, ability to disable stars printing in HUB episodes for specific door, ability to disable interscene showing when going to another level through a warp.\r\n-Built-in support for episide and level wide music.ini and sounds.ini to override default music and sounds assets.\r\n-World maps now supports a custom directory to store any specific resources like custom tiles/scenes/paths/levels and don't spam the episode root folder with wolrd map resources anymore.\r\n-Default config format is INI, old config.dat format is no more supported, mainly because of incompatible key code values (SDL_Scancode versus VirtualKeys enum of Windows API).\r\n-Game saves now using the SAVX format instead of a classic SAV. However, if you already have an old gamesave, you still can resume your game with using of a new engine now (next gamesave attempt will result a SAVX file, old gamesave in SAV format will be kept untouched).\r\n-Game saves now saving in the \"settings/gamesaves\" folder instead of episode root to allow them be read-only.\r\n-Built-in PNG support for custom and default graphics. Masked GIFs are still supported for backward compatibility, however, without making an unexpected auto-conversion.\r\n-Checkpoints now multi-points! You can use them in your levels multiple times without limits!\r\n-Make the use of lazy-decompress algorithm to speed-up the loading of a game and reduce the whole memory usage.\r\n-For music and SFX, the MixerX library is used to give a support for a wide amount of sound and music formats!\r\n-It doesn't embeds any graphics: here is NO any trurely hardcoded graphics, everything now representing as an external graphics!\r\n-Some internal limits was been expanded.\r\n-Built-in GIF recorder by F11 key (F10 on macOS, F11 is reserved by system UI for a \"show desktop\" action)\r\n\r\nBugfixes\r\n-Fixed a crash when mushroom falls into the lava\r\n-Fixed a division by zero error caused by match of player and Venus Firetrap NPC centers\r\n-Fixed an unnecessary ability to climb invisible fences\r\n-Fixed a mystical speed-adding while climbing fences, caused by after standing on any moving layers\r\n-Attempt to fix the level data initialization which was been incomplete and caused various issues like incorrect position of moving layers\r\n\r\n\r\n\r\nSuper Mario Bros. X Changelog.\r\n\r\nChanges for 1.3 (last version made by Redigit in October 2010):\r\n\r\nPlayers\r\n-Peach, Toad, and Link can now make use of the Leaf, Tanooki Suit, and Hammer Suit power-ups.\r\n-Toad can spin jump.\r\n-Link now moves a little bit quicker.\r\n\r\nMisc.\r\n-Quicksand\r\n-Attach Layers to NPCs\r\n-Fleet Glide Galaxy Music\r\n-SMW Desert Night Background\r\n-Block Fill Tool\r\n-NPC Text Preview\r\n-Custumizable NPCs\r\n-Screen Shot capturing (F12)\r\n-Slippery Blocks\r\n-Battle Mode\r\n-NPC generators can now be up to 60 second delay\r\n\r\nNew Items\r\n-Ice Flower\r\n-Bubbles\r\n-SMB2 Door Potion\r\n-Propeller Block\r\n-Flame Thrower\r\n-Dragon Coins\r\n-? Mushroom\r\n-Random Power-ups\r\n-Toad's Boomerang\r\n-Peach's Heart Bombs.\r\n\r\nNew Enemies\r\n-Roto-Disc\r\n-Firebar\r\n-Nipper\r\n-Mouser\r\n-Larry Koopa\r\n-Ludwig Koopa\r\n-Swooper\r\n-Hoopster\r\n-Volcano Lotus\r\n-SMW Lakitu (with the ability to choose what he throws)\r\n-SMW Spineys\r\n\r\n\r\n\r\nChanges for 1.2.2:\r\n\r\nPlayers\r\n-Link has been added as a playable character.\r\n-Toad has been added as a playable character.\r\n-Peach has been added as a playable character.\r\n-Mario and Luigi no longer automatically drop their reserve item when hurt.\r\n\r\nNPCs\r\n-The Silver SMW P-Switch has been replaced with a Green SMB3 P-Switch\r\n-All times stop effects play the SMB3 switch song\r\n-Added green, blue, and red rupees.\r\n-Added the heart piece.\r\n-Added the fairy pendant that turns the user into a fairy.\r\n-Added the SMB2 mushroom.\r\n-Added a Zelda 2 style lock.\r\n-Turtle shells now have a 'spark' effect when kicked/hit.\r\n-Red Yoshi fireballs no longer die when they hit an enemy.\r\n-Added a blue SMW coin that is worth 5 coins.\r\n-Fireballs have a different smoke effect when they 'die.'\r\n-Added a giant Piranha Plant that takes multiple hits to kill.\r\n-Max time for NPC generators has been raised to 60 seconds.\r\n-NPCs in the level editor are now animated.\r\n\r\nBlocks\r\n-Added blocks that change the player's character when hit.\r\n-Added blocks that only allows specific characters through them.\r\n-The SMB2 cracked block now has it's own smash graphic.\r\n\r\nMisc\r\n-The game window is now resizable.\r\n-Playable characters can be blocked in the world editor.\r\n-The player can switch characters on the world map by pressing pause and then tapping left or right.\r\n-The editor now shows what is inside blocks.\r\n-The debugger displays information about what NPCs are in the level.\r\n-You can now use PageUp and PageDown to change your current section in the editor.\r\n-There is now a menu option that brings up the help file.\r\n-The Level Settings tab no longer defaults to the level size cursor.\r\n-When using custom graphics, the icons in the editor now show the correct picture.\r\n-Added an option to play the game without sound.\r\n-Added an option to turn of the frame skip.\r\n-The editor now shows the contents of Eggs and Burried Items.\r\n\r\nBug Fixes\r\n-Fixed a bug that would cause sound/music not to load in the game.\r\n-Fixed the bug that caused NPCs to get 'smashed' when they shouldn't have.\r\n-The level background is now drawn correctly when the screen splits horizontally.\r\n-Several small bug fixes.\r\n\r\nCheat Codes\r\n-Added 'anothercastle'\r\n-Added 'ibakedacakeforyou'\r\n-Added 'iamerror'\r\n-Added 'fairymagic'\r\n\r\n\r\n\r\nChanges for 1.2.1:\r\n\r\nEngine Updates\r\n-NPCs can now be crushed by moving blocks.\r\n-Added SMB2 throw sound for SMB2 enemies that are thrown.\r\n-You are now given the option to chose between Mario and Luigi in 1 player mode.\r\n-Cheat codes are now back in the main game, but you can no longer save after using them.\r\n-You can no longer save the game while playing a level.\r\n-Added the cheat code wetwater.\r\n-Added the cheat code istillplaywithlegos.\r\n-The cheat sonicstoslow has become sonicstooslow.\r\n-Added the cheat code itsrainingmen.\r\n-Added the cheat code donttypethis.\r\n-Several cheat codes have been fixed.\r\n-The test settings window has been updated to include the new power ups.\r\n-The hot box for lava tiles has been pushed down by 6 pixels.\r\n-Now you can use the mouse to select the options on the menu screen.\r\n-Hit detection is now a little bit more lenient when deciding if a player should be hurt by an NPC.\r\n-Added the cheat code needaclock.\r\n-Added the cheat code powhammer.\r\n-Revived the cheat code imtiredofallthiswalking.\r\n\r\nPlayer\r\n-The player now sticks to downward slopes instead of falling over them. This should make it easier to jump over cliffs when running down a slope.\r\n-There is now a brief animation and delay when picking things up SMB2 style.\r\n-The player now spins while shell surfing.\r\n-The player no longer falls off a shell when it bounces off a wall.\r\n-The player can now grab shells from above.\r\n-The player now has walking animations when using a warp pipe.\r\n-The player can now swim upward fast by holding up while swimming.\r\n-The distance needed to fly was shortened very slightly.\r\n-Luigi's fireballs now go up steep slopes.\r\n-Luigi's controls have been tightened slightly.\r\n-When the first player is Luigi, he appears instead of Mario on the world map.\r\n-The player gets a slight speed boost when running down a slope.\r\n-Yoshi will be returned to you after you complete a level that takes him away.\r\n-Dead players no longer spawn from blocks containing the Hammer Suit or Tanooki Suit.\r\n-Luigi now causes all the coins to pop out of coin blocks after he hits them.\r\n\r\nNPCs\r\n-The SMW Goomba and SMB3 Bob-omb have been updated to more accurately reflect the games they're from. SMB3 Bob-ombs go farther when kicked.\r\n-Boom Boom now has a brief period of immunity when hit by objects. This stops him from being insta-killed.\r\n-After a veggie has been thrown up it will change speed if it hits an enemy. This prevents a single veggie from killing bosses.\r\n-The smashed Rex and Mega Mole now move faster. The SMB1 and SMW 1-ups now move the correct speed.\r\n-The Ice Block now melts 7 seconds after being picked up, unless thrown.\r\n-Only bosses now give points when falling into lava.\r\n-Wart now gives the correct amount of points when killed.\r\n-SMB1 Bowser now has a more predictable hammer throwing pattern.\r\n-NPCs now have walking animations and will walk out of a pipe when spawned with a warp generator.\r\n-Grass and turnips now have a higher pitched grab sound.\r\n-SMW saws now make the 'ticking' sound when on screen, are drawn behind blocks, and only stick out of the ground halfway.\r\n-Thwomps now create 'smoke effect' after hitting the ground.\r\n-The Ice Block now breaks bricks when thrown up.\r\n-Switch goombas and platforms have been recolored to match the SMW switch blocks.\r\n-Paratroopas set to hover left/right now flutter up and down.\r\n-Yoshi, Kuribo's shoe, and several other power-ups can now be off screen for 60 seconds before being reset.\r\n-The off screen timer of shells now resets after they break a block. Now you don't have to supervise them to make sure they are doing there job.\r\n-Sonic rings have been changed to a graphic style that fits the game better.\r\n-Mr. Saturn is now indestructible.\r\n-Blaarg's hit box has been fixed.\r\n-The Clown car now checks for collisions against blocks.\r\n-Added a silver P Switch that stops time for a short while when hit.\r\n-Added a red switch used for triggering events.\r\n-Added the stop watch from SMB2.\r\n-Added the POW block from SMB2.\r\n-Added the brown Paragoomba from SMB3 with Paratroopa AI.\r\n-Added the Paragoomba from SML2 with Paratroopa AI.\r\n-Added the goomba from SML2.\r\n-Added the venus fire trap from SMB3\r\n-Added pokey from SMB2.\r\n-Added the stop watch power-up.\r\n\r\nBlocks\r\n-Digable dirt now behaves correctly.\r\n-Munchers hurt from all sides.\r\n-SMW switch blocks have been recolored to look like they did in SMW.\r\n\r\nBackgrounds\r\n-SMW Ghost House background is now animated.\r\n-SMW Castle background is now properly animated.\r\n\r\nBug Fixes\r\n-Fixed a bug that stopped the game from actually skipping frames when it was supposed to.\r\n-The yellow Yoshi now correctly ground pounds enemies on slopes.\r\n-The world map no longer unloads custom graphics after playing a level.\r\n-The Billy Gun and Hammer Bros. no longer keep shooting when the player loses control of himself.\r\n-The game more accurately decides which block the player should stand on while on multiple blocks. This mainly affects note blocks.\r\n-Fixed a bug that would push the player through blocks when standing on a falling block. (As seen in the last fight of The Invasion 2)\r\n-The game no longer freezes when using events to takeover the players controls and opening a message window.\r\n-The player can no longer climb vines while in Tanooki form.\r\n-NPC speed is now calculated correctly when walking on another NPC while underwater.\r\n-Checkpoints now work on episodes that use a hub world.\r\n\r\n\r\n\r\nChanges for 1.2:\r\n\r\nEngine Updates\r\n-Sloped tiles have been fully implemented, including the ability to slide down slopes.\r\n-New \"projectile\" spawn point that allows NPCs to be shot out of pipes.\r\n-Updated the graphics engine, allowing for custom graphics.\r\n-Spikes only hurt when hit from the correct angle\r\n-Keys can be used to unlock doors.\r\n-Swimmable water has been implemented.\r\n-The title screen now plays a level.\r\n-You can now use events to move layers.\r\n-Added online level edit mode.\r\n-Cheat codes now only work in the level editor.\r\n-Added auto frame skip.\r\n\r\nPlayers\r\n-Added the Tanooki Suit.\r\n-Added the Hammer Suit.\r\n-Added Fire Shoe Power-up that turns stomped enemies into fireballs and allows the wearer to walk on lava.\r\n-Added Blue Shoe Power-up that allows the wearer infinite flight.\r\n-There is a longer delay between eating enemies with Yoshi.\r\n-You can no longer spit and eat a shell in midair to fly indefinitely.\r\n-The player now needs to run longer before he can fly.\r\n-Players now \"slide\" when spit out of Yoshi.\r\n-Players can use lives to come back to life instantly in 2 player mode.\r\n\r\nTiles/Backgrounds\r\n-Added cave tiles and two backgrounds (SMB3)\r\n-Added Mystic Cave Zone background (Sonic 2).\r\n-Added the underwater block (SMB1).\r\n-Added sloped grass (SMW, SMB3, SMB2) and cave (SMW, SMB3 & SMB1) tiles.\r\n-Added castle tiles and background (SMB1).\r\n-Added several tiles (SMB1) (SMB2).\r\n-Added coral block (SMB1).\r\n-Added fence and tree backgrounds (SMB1).\r\n-Added several castle tiles and backgrounds (SMW).\r\n-Added various blocks (SMW).\r\n-Added multiple lava tiles (SMW).\r\n-Added Ghost House background (SMW).\r\n-Added Wart's Throne Room tileset (SMB2).\r\n-Added several new Super Metroid tilesets.\r\n-Added 2 Super Metroid backgrounds.\r\n-Added clouds background (SMB2).\r\n-Added several new blocks. (Multiple)\r\n-Added six backgrounds. (SMB1 & SMB2)\r\n-Add table and chair background objects. (SMA4)\r\n-Added underwater backgrounds. (SMB3, SMW)\r\n-Added water dirt blocks (SMB3)\r\n-Added giant wood blocks (SMB3)\r\n-Added giant coral (SMB3)\r\n-Added water backgrounds objects (SMB1, SMB3)\r\n-Added bubble background.\r\n-Added the frozen coin and chomper blocks.\r\n\r\nNPCs\r\n-Added Goomba and Paragoomba (SMW).\r\n-Added Rex (SMW).\r\n-Added Mega Mole (SMW).\r\n-Added Poison Mushroom (SMB).\r\n-Added rings that act like coins (Sonic the Hedgehog)\r\n-Added Mushroom Blocks (SMB2).\r\n-Added Digable Dirt (SMB2).\r\n-Added flying Airship part (SMB3).\r\n-Added Mister Saturn (Earthbound).\r\n-Changed the Cheep Cheep (SMB1) to die when jumped on.\r\n-Blue Beach Koopas (SMW) can now kick Ice Blocks (SMB3).\r\n-Added Bullies (SM64). Sprites by LuigiFan.\r\n-Thwomp and the Boos can now be killed.\r\n-Added both normal Koopas and Parakoopas (SMB1)\r\n-Added Axe, which dies on contact with the player, that can be used to start events. (SMB1)\r\n-Added Thwomp (SMW)\r\n-Added Grinder (SMW)\r\n-Added Dry Bones. (SMW)\r\n-Added Mushroom, Fire Flower, 1-Up, and 3-Up Moon (SMW) (SMB1).\r\n-Added skull ride. (SMW)\r\n-Added the Rainbow shell, which when hit by a tail becomes a powerful item and allows Mario to ride it. (SMW)\r\n-Added Princess Peach. (SMB3)\r\n-Added collectible stars that don't end the level. (SMW)\r\n-Add the King Koopa. (SMB1)\r\n-Added Wart (SMB2)\r\n-Added Spark (SMB2).\r\n-Added 3 Super Metroid monsters.\r\n-Added Spike Top (SMW).\r\n-Added Mother Brain\r\n-Added Green Cheep Cheeps (SMB1, SMB3)\r\n-Added Red Cheep Cheeps that jump out of the water at the player (SMB3)\r\n-Added Bloopers (SMB1, SMB3)\r\n-Added some misc. Sushi from SMW\r\n-SMB2 enemies are now immune to fireballs\r\n-Podoboos are immune to fire and no longer kill enemy NPCs\r\n-Yoshi now run away faster when the player is hit.\r\n-The Billy Gun now shoots roughly twice as fast.\r\n\r\nWorld Map\r\n-Added several tiles and scenery objects (SMW).\r\n-Ability to choose start point.\r\n-Added level path background options.\r\n-Added several level icons. (SMBA4) (SMB3) (SMW\r\n-Instant warp zones.\r\n-Player faces the direction he is walking.\r\n-Added new levels and path graphics.\r\n\r\nMusic\r\n-Added Meta Knight's Revenge (SSBB).\r\n-Added Castle music (SMW).\r\n-Added Castle music (SMB1).\r\n-Added Wart battle music (SMB2).\r\n-Added Item Room music (Super Metroid).\r\n-Added Underwater music (SMB1, SMB3, SMW, SM64)\r\n-Added Waluigi Pinball and SSBB Underground themes.\r\n-Added SM64 cave theme.\r\n-Added SMB3 hammer bros. theme.\r\n\r\nBug Fixes\r\n-The player now correctly changes sections when he enters a door while standing on a NPC.\r\n-Fixed a bug that would cause the map to scroll to a location other then 0, 0 when changing modes to the world editor.\r\n-Added an auto-align toggle to the world editor.\r\n-Fixed several bugs with the conveyor belts.\r\n-Exits no longer despawn when they go off-screen.\r\n-World editor now plays sounds when the user saves and erases the map.\r\n-Player stops ducking when leaving a vertical pipe warp.\r\n-Spin Jump has been toned down.\r\n-Platform now properly pass through cross sections.\r\n-Fixed a bug that prevented music from playing in the 'intro.lvl\"\r\n-P switch is no longer linked to music.\r\n"
  },
  {
    "path": "cmake/TargetArch.cmake",
    "content": "# Based on the Qt 5 processor detection code, so should be very accurate\n# https://qt.gitorious.org/qt/qtbase/blobs/master/src/corelib/global/qprocessordetection.h\n# Currently handles arm (v5, v6, v7), x86 (32/64), ia64, and ppc (32/64)\n\n# Regarding POWER/PowerPC, just as is noted in the Qt source,\n# \"There are many more known variants/revisions that we do not handle/detect.\"\n\nset(archdetect_c_code \"\n#if defined(__arm__) || defined(__TARGET_ARCH_ARM) || defined(__ARM_ARCH)\n    #if defined(__ARM_ARCH_8__) \\\\\n        || defined(__ARM_ARCH_8A) \\\\\n        || defined(__ARM_ARCH_8A__) \\\\\n        || defined(__ARM_ARCH_8R__) \\\\\n        || defined(__ARM_ARCH_8M__) \\\\\n        || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 8) \\\\\n        || (defined(__ARM_ARCH) && __ARM_ARCH >= 8)\n        #error cmake_ARCH arm64\n    #elif defined(__ARM_ARCH_7__) \\\\\n        || defined(__ARM_ARCH_7A__) \\\\\n        || defined(__ARM_ARCH_7R__) \\\\\n        || defined(__ARM_ARCH_7M__) \\\\\n        || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 7) \\\\\n        || (defined(__ARM_ARCH) && __ARM_ARCH >= 7)\n        #error cmake_ARCH armv7\n    #elif defined(__ARM_ARCH_6__) \\\\\n        || defined(__ARM_ARCH_6J__) \\\\\n        || defined(__ARM_ARCH_6T2__) \\\\\n        || defined(__ARM_ARCH_6Z__) \\\\\n        || defined(__ARM_ARCH_6K__) \\\\\n        || defined(__ARM_ARCH_6ZK__) \\\\\n        || defined(__ARM_ARCH_6M__) \\\\\n        || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 6) \\\\\n        || (defined(__ARM_ARCH) && __ARM_ARCH >= 6)\n        #error cmake_ARCH armv6\n    #elif defined(__ARM_ARCH_5TEJ__) \\\\\n        || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 5)\n        #error cmake_ARCH armv5\n    #else\n        #error cmake_ARCH arm\n    #endif\n#elif defined(_M_ARM64) || defined(__aarch64__)\n    #error cmake_ARCH arm64\n#elif defined(_M_ARM_ARMV7VE)\n    #error cmake_ARCH armv7\n#elif defined(_M_ARM) || defined(__arm__) || defined(_ARM)\n    #error cmake_ARCH arm\n#elif defined(__i386) || defined(__i386__) || defined(_M_IX86)\n    #error cmake_ARCH i386\n#elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64)\n    #error cmake_ARCH x86_64\n#elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64)\n    #error cmake_ARCH ia64\n#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\\\\n      || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC)  \\\\\n      || defined(_M_MPPC) || defined(_M_PPC)\n    #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__)\n        #error cmake_ARCH ppc64\n    #else\n        #error cmake_ARCH ppc\n    #endif\n#endif\n\n#error cmake_ARCH unknown\n\")\n\n# Set ppc_support to TRUE before including this file or ppc and ppc64\n# will be treated as invalid architectures since they are no longer supported by Apple\n\nfunction(target_architecture output_var)\n    if(APPLE AND CMAKE_OSX_ARCHITECTURES)\n        # On OS X we use CMAKE_OSX_ARCHITECTURES *if* it was set\n        # First let's normalize the order of the values\n\n        # Note that it's not possible to compile PowerPC applications if you are using\n        # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we\n        # disable it by default\n        # See this page for more information:\n        # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4\n\n        # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime.\n        # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise.\n\n        foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES})\n            if(\"${osx_arch}\" STREQUAL \"ppc\" AND ppc_support)\n                set(osx_arch_ppc TRUE)\n            elseif(\"${osx_arch}\" STREQUAL \"i386\")\n                set(osx_arch_i386 TRUE)\n            elseif(\"${osx_arch}\" STREQUAL \"x86_64\")\n                set(osx_arch_x86_64 TRUE)\n            elseif(\"${osx_arch}\" STREQUAL \"ppc64\" AND ppc_support)\n                set(osx_arch_ppc64 TRUE)\n            elseif(\"${osx_arch}\" STREQUAL \"arm64\")\n                set(osx_arch_arm64 TRUE)\n            elseif(\"${osx_arch}\" STREQUAL \"arm\")\n                set(osx_arch_arm TRUE)\n            else()\n                message(FATAL_ERROR \"Invalid OS X arch name: ${osx_arch}\")\n            endif()\n        endforeach()\n\n        # Now add all the architectures in our normalized order\n        if(osx_arch_ppc)\n            list(APPEND ARCH ppc)\n        endif()\n\n        if(osx_arch_i386)\n            list(APPEND ARCH i386)\n        endif()\n\n        if(osx_arch_x86_64)\n            list(APPEND ARCH x86_64)\n        endif()\n\n        if(osx_arch_ppc64)\n            list(APPEND ARCH ppc64)\n        endif()\n\n        if(osx_arch_arm)\n            list(APPEND ARCH arm)\n        endif()\n\n        if(osx_arch_arm64)\n            list(APPEND ARCH arm64)\n        endif()\n    else()\n        file(WRITE \"${CMAKE_BINARY_DIR}/arch.c\" \"${archdetect_c_code}\")\n\n        enable_language(C)\n\n        # Detect the architecture in a rather creative way...\n        # This compiles a small C program which is a series of ifdefs that selects a\n        # particular #error preprocessor directive whose message string contains the\n        # target architecture. The program will always fail to compile (both because\n        # file is not a valid C program, and obviously because of the presence of the\n        # #error preprocessor directives... but by exploiting the preprocessor in this\n        # way, we can detect the correct target architecture even when cross-compiling,\n        # since the program itself never needs to be run (only the compiler/preprocessor)\n        try_run(\n            run_result_unused\n            compile_result_unused\n            \"${CMAKE_BINARY_DIR}\"\n            \"${CMAKE_BINARY_DIR}/arch.c\"\n            COMPILE_OUTPUT_VARIABLE ARCH\n            CMAKE_FLAGS CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}\n        )\n\n        # Parse the architecture name from the compiler output\n        string(REGEX MATCH \"cmake_ARCH ([a-zA-Z0-9_]+)\" ARCH \"${ARCH}\")\n\n        # Get rid of the value marker leaving just the architecture name\n        string(REPLACE \"cmake_ARCH \" \"\" ARCH \"${ARCH}\")\n\n        # If we are compiling with an unknown architecture this variable should\n        # already be set to \"unknown\" but in the case that it's empty (i.e. due\n        # to a typo in the code), then set it to unknown\n        if (NOT ARCH)\n            set(ARCH unknown)\n        endif()\n    endif()\n\n    set(${output_var} \"${ARCH}\" PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "cmake/build_props.cmake",
    "content": "# ============================ Generic setup ==================================\n# If platform is Emscripten\nif(${CMAKE_SYSTEM_NAME} STREQUAL \"Emscripten\")\n    set(EMSCRIPTEN 1 BOOLEAN)\n    unset(WIN32)\n    unset(APPLE)\nendif()\n\nif(APPLE AND CMAKE_HOST_SYSTEM_VERSION VERSION_LESS 9)\n    message(\"-- MacOS X 10.4 Tiger detected!\")\n    set(XTECH_MACOSX_TIGER TRUE)\nendif()\n\n# =========================== Architecture info ===============================\n\ninclude(${CMAKE_CURRENT_LIST_DIR}/TargetArch.cmake)\ntarget_architecture(TARGET_PROCESSOR)\nmessage(STATUS \"Target architecture: ${TARGET_PROCESSOR}\")\n\ntest_big_endian(THEXTECH_IS_BIG_ENDIAN)\nif(THEXTECH_IS_BIG_ENDIAN)\n    message(STATUS \"Target processor endianess: BIG ENDIAN\")\nelse()\n    message(STATUS \"Target processor endianess: LITTLE ENDIAN\")\nendif()\n\nmessage(STATUS \"Size of void pointer is ${CMAKE_SIZEOF_VOID_P}!\")\n\n# =============================== Policies ====================================\n\n# Ninja requires custom command byproducts to be explicit.\nif(POLICY CMP0058)\n    cmake_policy(SET CMP0058 NEW)\nendif()\n\n# ExternalProject step targets fully adopt their steps.\nif(POLICY CMP0114)\n    cmake_policy(SET CMP0114 NEW)\nendif()\n\n# Report about improperly use of PRE_BUILD, PRE_LINK, and POST_BUILD keywords\nif(POLICY CMP0175)\n    cmake_policy(SET CMP0175 NEW)\nendif()\n\n# ========================= Macros and Functions ==============================\n\ninclude(CheckCCompilerFlag)\ninclude(CheckCXXCompilerFlag)\n\nmacro(xtech_add_warning_flag WARNINGFLAG WARNING_VAR)\n    check_c_compiler_flag(\"${WARNINGFLAG}\" HAVE_W_C_${WARNING_VAR})\n    if(HAVE_W_C_${WARNING_VAR})\n        set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ${WARNINGFLAG}\")\n    endif()\n\n    check_cxx_compiler_flag(\"${WARNINGFLAG}\" HAVE_W_CXX_${WARNING_VAR})\n    if(HAVE_W_CXX_${WARNING_VAR})\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${WARNINGFLAG}\")\n    endif()\nendmacro()\n\nmacro(xtech_disable_warning_flag WARNINGFLAG WARNING_VAR)\n    check_c_compiler_flag(\"-W${WARNINGFLAG}\" HAVE_W_C_${WARNING_VAR})\n    if(HAVE_W_C_${WARNING_VAR})\n        set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -Wno-${WARNINGFLAG}\")\n    endif()\n\n    check_cxx_compiler_flag(\"-W${WARNINGFLAG}\" HAVE_W_CXX_${WARNING_VAR})\n    if(HAVE_W_CXX_${WARNING_VAR})\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-${WARNINGFLAG}\")\n    endif()\nendmacro()\n\nmacro(pge_add_opt_flag OPTFLAG OPT_VAR)\n    check_c_compiler_flag(\"${OPTFLAG}\" HAVE_M_C_${OPT_VAR})\n    if(HAVE_M_C_${OPT_VAR})\n        set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ${OPTFLAG}\")\n    endif()\n\n    check_cxx_compiler_flag(\"${OPTFLAG}\" HAVE_M_CXX_${OPT_VAR})\n    if(HAVE_M_CXX_${OPT_VAR})\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${OPTFLAG}\")\n    endif()\nendmacro()\n\nfunction(pge_cxx_standard STDVER)\n    if(NOT WIN32)\n        set(CMAKE_CXX_STANDARD ${STDVER} PARENT_SCOPE)\n    elseif(MSVC AND CMAKE_VERSION VERSION_LESS \"3.9.0\" AND MSVC_VERSION GREATER_EQUAL \"1900\")\n        CHECK_CXX_COMPILER_FLAG(\"/std:c++${STDVER}\" _cpp_stdxx_flag_supported)\n        if (_cpp_stdxx_flag_supported)\n            add_compile_options(\"/std:c++${STDVER}\")\n        else()\n            CHECK_CXX_COMPILER_FLAG(\"/std:c++latest\" _cpp_latest_flag_supported)\n            if (_cpp_latest_flag_supported)\n                add_compile_options(\"/std:c++latest\")\n            endif()\n        endif()\n    else()\n        set(CMAKE_CXX_STANDARD ${STDVER} PARENT_SCOPE)\n    endif()\nendfunction()\n\n\n# ============================ Optimisations ==================================\n\n# Strip garbage\nif(APPLE)\n    string(REGEX REPLACE \"-O3\" \"\"\n        CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE}\")\n    string(REGEX REPLACE \"-O3\" \"\"\n        CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE}\")\n    set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -O2\")\n    set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -O2\")\n    set(LINK_FLAGS_RELEASE  \"${LINK_FLAGS_RELEASE} -dead_strip\")\n\n    # Unify visibility to meet llvm's default.\n    check_cxx_compiler_flag(\"-fvisibility-inlines-hidden\" SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG)\n    if(SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG)\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden\")\n    endif()\nelseif(NOT MSVC)\n    if(EMSCRIPTEN)\n        set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -O3 -Os -fdata-sections -ffunction-sections\")\n        set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -O3 -Os -fdata-sections -ffunction-sections\")\n        set(CMAKE_C_FLAGS_MINSIZEREL \"${CMAKE_C_FLAGS_MINSIZEREL} -O3 -Os -fdata-sections -ffunction-sections\")\n        set(CMAKE_CXX_FLAGS_MINSIZEREL \"${CMAKE_CXX_FLAGS_MINSIZEREL} -O3 -Os -fdata-sections -ffunction-sections\")\n        set(CMAKE_C_FLAGS_RELWITHDEBINFO \"${CMAKE_C_FLAGS_RELWITHDEBINFO} -O3 -fdata-sections -ffunction-sections\")\n        set(CMAKE_CXX_FLAGS_RELWITHDEBINFO \"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -fdata-sections -ffunction-sections\")\n\n        if(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\")\n            set(LINK_FLAGS_RELEASE  \"${LINK_FLAGS_RELEASE} -dead_strip\")\n            set(LINK_FLAGS_MINSIZEREL  \"${LINK_FLAGS_MINSIZEREL} -dead_strip\")\n            set(LINK_FLAGS_RELWITHDEBINFO  \"${LINK_FLAGS_RELWITHDEBINFO} -dead_strip\")\n        else()\n            set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -Wl,--gc-sections -Wl,-s\")\n            set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -Wl,--gc-sections -Wl,-s\")\n            set(CMAKE_C_FLAGS_MINSIZEREL \"${CMAKE_C_FLAGS_MINSIZEREL} -Wl,--gc-sections -Wl,-s\")\n            set(CMAKE_CXX_FLAGS_MINSIZEREL \"${CMAKE_CXX_FLAGS_MINSIZEREL} -Wl,--gc-sections -Wl,-s\")\n            set(LINK_FLAGS_RELEASE  \"${LINK_FLAGS_RELEASE} -Wl,--gc-sections -Wl,-s\")\n            set(LINK_FLAGS_MINSIZEREL  \"${LINK_FLAGS_MINSIZEREL} -Wl,--gc-sections -Wl,-s\")\n            set(LINK_FLAGS_RELWITHDEBINFO  \"${LINK_FLAGS_RELWITHDEBINFO} -Wl,--gc-sections -Wl,-s\")\n        endif()\n    else()\n        string(REGEX REPLACE \"-O3\" \"\"\n            CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE}\")\n        string(REGEX REPLACE \"-O3\" \"\"\n            CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE}\")\n        set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -O3 -fdata-sections -ffunction-sections\")\n        set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -O3 -fdata-sections -ffunction-sections\")\n        if(ANDROID)\n            set(CMAKE_C_FLAGS_DEBUG \"${CMAKE_C_FLAGS_DEBUG} -funwind-tables\")\n            set(CMAKE_CXX_FLAGS_DEBUG \"${CMAKE_CXX_FLAGS_DEBUG} -funwind-tables\")\n        elseif(THEXTECH_CLI_BUILD AND THEXTECH_NO_SDL_BUILD)\n            # use -Os by default for all build types\n            string(REGEX REPLACE \"-O3\" \"-Os\"\n                CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE}\")\n            string(REGEX REPLACE \"-O3\" \"-Os\"\n                CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE}\")\n            string(REGEX REPLACE \"-O2\" \"-Os\"\n                CMAKE_C_FLAGS_RELWITHDEBINFO \"${CMAKE_C_FLAGS_RELWITHDEBINFO}\")\n            string(REGEX REPLACE \"-O2\" \"-Os\"\n                CMAKE_CXX_FLAGS_RELWITHDEBINFO \"${CMAKE_CXX_FLAGS_RELWITHDEBINFO}\")\n        elseif(NINTENDO_DS)\n            # use -Os by default for all build types\n            string(REGEX REPLACE \"-O3\" \"-Os\"\n                CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE}\")\n            string(REGEX REPLACE \"-O3\" \"-Os\"\n                CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE}\")\n            string(REGEX REPLACE \"-O2\" \"-Os\"\n                CMAKE_C_FLAGS_RELWITHDEBINFO \"${CMAKE_C_FLAGS_RELWITHDEBINFO}\")\n            string(REGEX REPLACE \"-O2\" \"-Os\"\n                CMAKE_CXX_FLAGS_RELWITHDEBINFO \"${CMAKE_CXX_FLAGS_RELWITHDEBINFO}\")\n\n            # Supress the std::vector::insert() GCC change warning\n            set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fcompare-debug-second\")\n            set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fcompare-debug-second\")\n            # use --gc-sections for all build types\n            set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -g -Wl,--gc-sections -fipa-pta\")\n            set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -g -Wl,--gc-sections -fipa-pta\")\n            set(LINK_FLAGS_RELEASE  \"${LINK_FLAGS_RELEASE} -g -Wl,--gc-sections\")\n            set(CMAKE_C_FLAGS_RELWITHDEBINFO \"${CMAKE_C_FLAGS_RELWITHDEBINFO} -Wl,--gc-sections -fipa-pta\")\n            set(CMAKE_CXX_FLAGS_RELWITHDEBINFO \"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wl,--gc-sections -fipa-pta\")\n            set(LINK_FLAGS_RELWITHDEBINFO  \"${LINK_FLAGS_RELWITHDEBINFO} -Wl,--gc-sections\")\n            set(CMAKE_C_FLAGS_MINSIZEREL \"${CMAKE_C_FLAGS_MINSIZEREL} -g -Wl,--gc-sections -fipa-pta\")\n            set(CMAKE_CXX_FLAGS_MINSIZEREL \"${CMAKE_CXX_FLAGS_MINSIZEREL} -g -Wl,--gc-sections -fipa-pta\")\n            set(LINK_FLAGS_MINSIZEREL  \"${LINK_FLAGS_MINSIZEREL} -g -Wl,--gc-sections\")\n        elseif(NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_SWITCH)\n            # Supress the std::vector::insert() GCC change warning\n            set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fcompare-debug-second\")\n            set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fcompare-debug-second\")\n            if(NINTENDO_WII OR NINTENDO_WIIU)\n                set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -mno-altivec -Ubool -Uvector -U_GNU_SOURCE\")\n                set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -mno-altivec -Ubool -Uvector -U_GNU_SOURCE\")\n            endif()\n            # use --gc-sections for all build types\n            set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -g -Wl,--gc-sections\")\n            set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -g -Wl,--gc-sections\")\n            set(LINK_FLAGS_RELEASE  \"${LINK_FLAGS_RELEASE} -g -Wl,--gc-sections\")\n            set(CMAKE_C_FLAGS_RELWITHDEBINFO \"${CMAKE_C_FLAGS_RELWITHDEBINFO} -Wl,--gc-sections\")\n            set(CMAKE_CXX_FLAGS_RELWITHDEBINFO \"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wl,--gc-sections\")\n            set(LINK_FLAGS_RELWITHDEBINFO  \"${LINK_FLAGS_RELWITHDEBINFO} -Wl,--gc-sections\")\n            set(CMAKE_C_FLAGS_MINSIZEREL \"${CMAKE_C_FLAGS_MINSIZEREL} -g -Wl,--gc-sections\")\n            set(CMAKE_CXX_FLAGS_MINSIZEREL \"${CMAKE_CXX_FLAGS_MINSIZEREL} -g -Wl,--gc-sections\")\n            set(LINK_FLAGS_MINSIZEREL  \"${LINK_FLAGS_MINSIZEREL} -g -Wl,--gc-sections\")\n        elseif(VITA)\n            # Supress the std::vector::insert() GCC change warning\n            set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -DVITA=1 -fcompare-debug-second\")\n            set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -DVITA=1 -fcompare-debug-second\")\n            # VitaSDK specifies -O2 for release configurations. PS Vita Support - Axiom 2022\n            set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -g -I../src -Wl,--gc-sections -DVITA=1\")\n            set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -g -I../src -Wl,--gc-sections -DVITA=1 -fpermissive -fno-optimize-sibling-calls -Wno-class-conversion\")\n            set(LINK_FLAGS_RELEASE  \"${LINK_FLAGS_RELEASE} -Wl,--gc-sections\")\n        elseif(NOT \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\")\n            set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -s -Wl,--gc-sections -Wl,-s\")\n            set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -s -Wl,--gc-sections -Wl,-s\")\n            set(LINK_FLAGS_RELEASE  \"${LINK_FLAGS_RELEASE} -Wl,--gc-sections -Wl,-s\")\n        else()\n            set(LINK_FLAGS_RELEASE  \"${LINK_FLAGS_RELEASE} -dead_strip\")\n        endif()\n    endif()\nendif()\n\n# Global optimization flags\nif(NOT MSVC)\n    set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} -fno-omit-frame-pointer\")\n    set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -fno-omit-frame-pointer\")\nendif()\n\nif(ANDROID)\n    if(${ANDROID_ABI} STREQUAL \"armeabi-v7a\")\n        # Disable NEON support for old devices\n        set(ANDROID_ARM_NEON FALSE)\n    elseif(NOT DEFINED ANDROID_ARM_NEON)\n        set(ANDROID_ARM_NEON TRUE)\n    endif()\n\n    if(NOT DEFINED ANDROID_STL)\n        # include(ndk-stl-config.cmake)\n        set(ANDROID_STL \"c++_static\")\n    endif()\n\n    if(NOT DEFINED ANDROID_PLATFORM)\n        set(ANDROID_PLATFORM 16)\n    endif()\n\n    set(CMAKE_LINKER_FLAGS \"${CMAKE_LINKER_FLAGS} -Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384\")\n\n    if(FDROID_BUILD)\n        set(FILE_PATH_OVERRIDE \"-ffile-prefix-map=${CMAKE_SOURCE_DIR}=/builds/fdroid/fdroiddata/build/ru.wohlsoft.thextech.fdroid/\")\n        set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} ${FILE_PATH_OVERRIDE}\")\n        set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} ${FILE_PATH_OVERRIDE}\")\n        set(CMAKE_C_FLAGS_RELWITHDEBINFO \"${CMAKE_C_FLAGS_RELWITHDEBINFO} ${FILE_PATH_OVERRIDE}\")\n        set(CMAKE_CXX_FLAGS_RELWITHDEBINFO \"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${FILE_PATH_OVERRIDE}\")\n        set(CMAKE_C_FLAGS_MINSIZEREL \"${CMAKE_C_FLAGS_MINSIZEREL} ${FILE_PATH_OVERRIDE}\")\n        set(CMAKE_CXX_FLAGS_MINSIZEREL \"${CMAKE_CXX_FLAGS_MINSIZEREL} ${FILE_PATH_OVERRIDE}\")\n        set(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} -Wl,--build-id=none\")\n        set(ENV{CFLAGS} \"${FILE_PATH_OVERRIDE}\")\n        set(ENV{CXXFLAGS} \"${FILE_PATH_OVERRIDE}\")\n    endif()\n\n    set(ANDROID_CMAKE_FLAGS\n        \"-DANDROID_ABI=${ANDROID_ABI}\"\n        \"-DANDROID_NDK=${ANDROID_NDK}\"\n        \"-DANDROID_STL=${ANDROID_STL}\"\n        \"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${CMAKE_LIBRARY_OUTPUT_DIRECTORY}\"\n        \"-DANDROID_PLATFORM=${ANDROID_PLATFORM}\"\n        \"-DANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN}\"\n        \"-DANDROID_NATIVE_API_LEVEL=${ANDROID_NATIVE_API_LEVEL}\"\n        \"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}\"\n        \"-DANDROID_ARM_NEON=${ANDROID_ARM_NEON}\"\n        \"-DCMAKE_LINKER_FLAGS=-Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384\"\n    )\n\n    message(\"--Current Android STL=${ANDROID_STL} and flags: ${ANDROID_CMAKE_FLAGS}\")\n\n    if(FDROID_BUILD)\n        # Ensure reproducibility of builds by replacing build paths with a custom one\n        # So, forward all flags that was set above into dependencies\n        list(APPEND ANDROID_CMAKE_FLAGS\n            \"-DCMAKE_SHARED_LINKER_FLAGS=-Wl,--build-id=none\"\n            \"-DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE}\"\n            \"-DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE}\"\n            \"-DCMAKE_C_FLAGS_RELWITHDEBINFO=${CMAKE_C_FLAGS_RELWITHDEBINFO}\"\n            \"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=${CMAKE_CXX_FLAGS_RELWITHDEBINFO}\"\n            \"-DCMAKE_C_FLAGS_MINSIZEREL=${CMAKE_C_FLAGS_MINSIZEREL}\"\n            \"-DCMAKE_CXX_FLAGS_MINSIZEREL=${CMAKE_CXX_FLAGS_MINSIZEREL}\"\n            \"-DFDROID_BUILD=TRUE\"\n        )\n    endif()\nendif()\n\nif(VITA)\n    include(cmake/vita_buildprops.cmake)\nendif()\n\nstring(TOLOWER \"${CMAKE_BUILD_TYPE}\" CMAKE_BUILD_TYPE_LOWER)\nif (CMAKE_BUILD_TYPE_LOWER STREQUAL \"release\")\n    add_definitions(-DNDEBUG)\nendif()\n\nif(CMAKE_BUILD_TYPE_LOWER STREQUAL \"debug\" AND NOT ANDROID)\n    set(PGE_LIBS_DEBUG_SUFFIX \"d\")\nelse()\n    set(PGE_LIBS_DEBUG_SUFFIX \"\")\nendif()\n\n\n# ============================= Warnings ======================================\n\nif(MSVC)\n    # Force to always compile with W4\n    if(CMAKE_CXX_FLAGS MATCHES \"/W[0-4]\")\n        string(REGEX REPLACE \"/W[0-4]\" \"/W4\" CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS}\")\n    else()\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /W4\")\n    endif()\n    if(CMAKE_C_FLAGS MATCHES \"/W[0-4]\")\n        string(REGEX REPLACE \"/W[0-4]\" \"/W4\" CMAKE_C_FLAGS \"${CMAKE_C_FLAGS}\")\n    else()\n        set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} /W4\")\n    endif()\n\n    # Remove \"/showIncludes\" flag\n    if(CMAKE_CXX_FLAGS MATCHES \"/showIncludes\")\n        string(REGEX REPLACE \"/showIncludes\" \"\" CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS}\")\n    endif()\n    if(CMAKE_C_FLAGS MATCHES \"/showIncludes\")\n        string(REGEX REPLACE \"/showIncludes\" \"\" CMAKE_C_FLAGS \"${CMAKE_C_FLAGS}\")\n    endif()\n\n    # Disable bogus MSVC warnings\n    add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS)\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} /wd4244 /wd4551 /wd4276 /wd6388\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /wd4244 /wd4551 /wd4276 /wd6388\")\n\nelse()\n    xtech_add_warning_flag(\"-Wall\" ALL)\n    xtech_add_warning_flag(\"-Wextra\" EXTRA)\n    if(NOT HAVE_W_EXTRA)\n        xtech_add_warning_flag(\"-W\" W)\n    endif()\n    xtech_add_warning_flag(\"-Wpedantic\" PEDANTIC_WARNING)\n    xtech_disable_warning_flag(\"variadic-macros\" NO_VARIADIC_MACROS_WARNING)\n    xtech_disable_warning_flag(\"psabi\" NO_PSABI_WARNING)\n    xtech_disable_warning_flag(\"dangling-reference\" NO_DANGLING_REFERENCE_WARNING)\nendif()\n\n\n# ================================ Tweaks ====================================\n\n# -fPIC thing\nif(LIBRARY_PROJECT AND NOT WIN32 AND NOT VITA)\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fPIC\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fPIC\")\nendif()\n\nif(UNIX) # When include/library/binary directory name is not usual in a system, make symbolic links for them\n    if(NOT \"${CMAKE_INSTALL_LIBDIR}\" STREQUAL \"lib\")\n        message(\"${CMAKE_INSTALL_LIBDIR} IS NOT STREQUAL lib\")\n        file(MAKE_DIRECTORY \"${DEPENDENCIES_INSTALL_DIR}\")\n        execute_process(COMMAND ln -s \"lib\" \"${DEPENDENCIES_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}\")\n    endif()\n    if(NOT \"${CMAKE_INSTALL_BINDIR}\" STREQUAL \"bin\")\n        message(\"${CMAKE_INSTALL_BINDIR} IS NOT STREQUAL bin\")\n        file(MAKE_DIRECTORY \"${DEPENDENCIES_INSTALL_DIR}\")\n        execute_process(COMMAND ln -s \"bin\" \"${DEPENDENCIES_INSTALL_DIR}/${CMAKE_INSTALL_BINDIR}\")\n    endif()\n    if(NOT \"${CMAKE_INSTALL_INCLUDEDIR}\" STREQUAL \"include\")\n        message(\"${CMAKE_INSTALL_INCLUDEDIR} IS NOT STREQUAL lib\")\n        file(MAKE_DIRECTORY \"${DEPENDENCIES_INSTALL_DIR}\")\n        execute_process(COMMAND ln -s \"include\" \"${DEPENDENCIES_INSTALL_DIR}/${CMAKE_INSTALL_INCLUDEDIR}\")\n    endif()\nendif()\n\nif(APPLE)\n    # Prevent \"directory not exists\" warnings when building XCode as project\n    file(MAKE_DIRECTORY ${DEPENDENCIES_INSTALL_DIR}/lib/Debug)\n    file(MAKE_DIRECTORY ${DEPENDENCIES_INSTALL_DIR}/lib/Release)\n    # Don't store built executables into \"Debug\" and \"Release\" folders\n    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)\n    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)\n    set(CMAKE_BUNDLE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)\n    set(CMAKE_BUNDLE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)\nendif()\n\nif(CMAKE_STATIC_LIBRARY_PREFIX STREQUAL \"\" AND CMAKE_STATIC_LIBRARY_SUFFIX STREQUAL \".lib\")\n    set(LIBRARY_STATIC_NAME_SUFFIX \"-static\")\nelse()\n    set(LIBRARY_STATIC_NAME_SUFFIX \"\")\nendif()\n\n# Library path helpers\nmacro(set_static_lib OUTPUT_VAR LIBDIR LIBNAME)\n    set(${OUTPUT_VAR} \"${LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBNAME}${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}\")\nendmacro()\n\nmacro(set_shared_lib OUTPUT_VAR LIBDIR LIBNAME)\n    if(WIN32)\n        set(${OUTPUT_VAR} \"${LIBDIR}/${CMAKE_IMPORT_LIBRARY_PREFIX}${LIBNAME}${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX}\")\n    else()\n        set(${OUTPUT_VAR} \"${LIBDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBNAME}${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}\")\n    endif()\nendmacro()\n"
  },
  {
    "path": "cmake/ci_linux_gcc_toolchain_arm64.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Linux)\n\nset(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc)\nset(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++)\nset(CMAKE_ASM_COMPILER /usr/bin/aarch64-linux-gnu-as)\nset(CMAKE_ASM-ATT_COMPILER /usr/bin/aarch64-linux-gnu-as)\nset(CMAKE_AR ar)\n\nset(CMAKE_SYSTEM_PROCESSOR aarch64)\n\nset(CMAKE_CROSSCOMPILING ON)\n\nset(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)\n\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n#set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n#set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n#set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n"
  },
  {
    "path": "cmake/ci_linux_gcc_toolchain_armhf.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Linux)\n\nset(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc)\nset(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++)\nset(CMAKE_ASM_COMPILER /usr/bin/arm-linux-gnueabihf-as)\nset(CMAKE_ASM-ATT_COMPILER /usr/bin/arm-linux-gnueabihf-as)\nset(CMAKE_AR ar)\n\nset(CMAKE_SYSTEM_PROCESSOR armhf)\n\nset(CMAKE_CROSSCOMPILING ON)\n\nset(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf)\n\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n#set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n#set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n#set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n\n"
  },
  {
    "path": "cmake/ci_linux_gcc_toolchain_x32.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Linux)\n\nset(CMAKE_C_COMPILER gcc)\nset(CMAKE_C_FLAGS -m32)\nset(CMAKE_CXX_COMPILER g++)\nset(CMAKE_CXX_FLAGS -m32)\nset(CMAKE_AR ar)\n\nset(CMAKE_CROSSCOMPILING ON)\n\nset(CMAKE_FIND_ROOT_PATH /usr/lib/i386-linux-gnu/)\n\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)\n#set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n#set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n#set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n"
  },
  {
    "path": "cmake/ci_windows_mingw_toolchain_x32.cmake",
    "content": "set(CMAKE_SYSROOT \"C:/WohlMinGWw64/mingw32/\")\n\nset(CMAKE_SYSTEM_PREFIX_PATH \"C:/WohlMinGWw64/mingw32/\")\n\nset(CMAKE_C_COMPILER \"C:/WohlMinGWw64/mingw32/bin/gcc.exe\")\nset(CMAKE_CXX_COMPILER \"C:/WohlMinGWw64/mingw32/bin/g++.exe\")\n\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)\nset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n"
  },
  {
    "path": "cmake/ci_windows_mingw_toolchain_x64.cmake",
    "content": "set(CMAKE_SYSROOT \"C:/WohlMinGWw64/mingw64/\")\n\nset(CMAKE_SYSTEM_PREFIX_PATH \"C:/WohlMinGWw64/mingw64/\")\n\nset(CMAKE_C_COMPILER \"C:/WohlMinGWw64/mingw64/bin/gcc.exe\")\nset(CMAKE_CXX_COMPILER \"C:/WohlMinGWw64/mingw64/bin/g++.exe\")\n\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)\nset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n"
  },
  {
    "path": "cmake/ci_windows_msvc_toolchain_arm64.cmake",
    "content": "set(CMAKE_MSVC_RUNTIME_LIBRARY \"MultiThreaded$<$<CONFIG:Debug>:Debug>\")\n"
  },
  {
    "path": "cmake/deploy.cmake",
    "content": "set(CPACK_PACKAGE_VENDOR \"Wohlstand\")\nset(CPACK_PACKAGE_CONTACT \"admin@wohlnet.ru\")\nset(CPACK_PACKAGE_VERSION \"${THEXTECH_VERSION_STRING}\")\n\nif(APPLE)\n    set(CPACK_PACKAGE_NAME \"${THEXTECH_BUNDLE_NAME}\")\n    set(CPACK_GENERATOR \"DragNDrop\")\n    set(CPACK_PACKAGE_VERSION \"${THEXTECH_VERSION_STRING}\")\n    set(CPACK_DMG_FORMAT \"UDBZ\")\n    set(CPACK_DMG_VOLUME_NAME \"${THEXTECH_BUNDLE_NAME}\")\n    if(NOT CPACK_PACKAGE_FILE_NAME)\n        set(CPACK_PACKAGE_FILE_NAME \"${THEXTECH_BUNDLE_NAME}-${THEXTECH_VERSION_STRING}\")\n    endif()\n    include(CPack)\n\nelseif(UNIX)\n    set(CPACK_PACKAGE_NAME \"${THEXTECH_PACKAGE_NAME}\")\n    if(NOT CPACK_GENERATOR)\n        set(CPACK_GENERATOR \"TBZ2\")\n    endif()\n\n    # DEB related\n    set(CPACK_DEB_COMPONENT_INSTALL OFF)\n    set(CPACK_DEBIAN_PACKAGE_NAME \"${THEXTECH_INSTALLER_PACKAGE_NAME}\")\n    set(CPACK_DEBIAN_PACKAGE_VERSION ${THEXTECH_VERSION_STRING})\n    if(NOT CPACK_DEBIAN_PACKAGE_RELEASE)\n        set(CPACK_DEBIAN_PACKAGE_RELEASE 1)\n    endif()\n\n    set(CPACK_DEBIAN_PACKAGE_MAINTAINER \"${CPACK_PACKAGE_VENDOR} <${CPACK_PACKAGE_CONTACT}>\")\n    set(CPACK_DEBIAN_PACKAGE_SECTION games)\n    set(CPACK_DEBIAN_PACKAGE_PRIORITY optional)\n    if(NOT CPACK_DEBIAN_PACKAGE_HOMEPAGE)\n        set(CPACK_DEBIAN_PACKAGE_HOMEPAGE \"https://wohlsoft.ru/\")\n    endif()\n\n    # RPM related\n    set(CPACK_RPM_PACKAGE_VENDOR \"${CPACK_PACKAGE_VENDOR} <${CPACK_PACKAGE_CONTACT}>\")\n    set(CPACK_RPM_PACKAGE_NAME  \"${THEXTECH_INSTALLER_PACKAGE_NAME}\")\n    set(CPACK_RPM_PACKAGE_VERSION ${THEXTECH_VERSION_STRING})\n\n    if(NOT CPACK_RPM_PACKAGE_RELEASE)\n        set(CPACK_RPM_PACKAGE_RELEASE 1)\n    endif()\n\n    if(NOT CPACK_RPM_PACKAGE_URL)\n        set(CPACK_RPM_PACKAGE_URL \"https://wohlsoft.ru/\")\n    endif()\n\n    # Generic\n    if(NOT CPACK_PACKAGE_FILE_NAME)\n        set(CPACK_PACKAGE_FILE_NAME \"${THEXTECH_PACKAGE_NAME}-${THEXTECH_VERSION_STRING}\")\n    endif()\n    include(CPack)\nendif()\n\nif(WIN32)\n    set(CPACK_PACKAGE_NAME \"${THEXTECH_PACKAGE_NAME}\")\n    set(CPACK_GENERATOR \"7Z\")\n    set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)\n    if(NOT CPACK_PACKAGE_FILE_NAME)\n        set(CPACK_PACKAGE_FILE_NAME \"${THEXTECH_PACKAGE_NAME}-${THEXTECH_VERSION_STRING}\")\n    endif()\n    include(CPack)\nendif()\n\n"
  },
  {
    "path": "cmake/git_info.cmake",
    "content": "# Get the current working branch\nset(OVERRIDE_GIT_BRANCH \"\" CACHE STRING \"Override name of GIT branch\")\n\n# Identify the Pull-Request\nif((DEFINED ENV{TRAVIS_PULL_REQUEST} AND NOT \"$ENV{TRAVIS_PULL_REQUEST}\" STREQUAL \"false\")\n    OR (DEFINED ENV{APPVEYOR_PULL_REQUEST_NUMBER})\n    OR (DEFINED ENV{PULL_REQUEST_NUMBER}))\n    set(CI_PULL_REQUEST \"-pr-check\")\nelse()\n    set(CI_PULL_REQUEST \"\")\nendif()\n\nif(OVERRIDE_GIT_BRANCH)\n    set(GIT_BRANCH ${OVERRIDE_GIT_BRANCH})\nelseif(DEFINED ENV{APPVEYOR_REPO_BRANCH})\n    set(GIT_BRANCH $ENV{APPVEYOR_REPO_BRANCH})\nelseif(DEFINED ENV{TRAVIS_BRANCH})\n    set(GIT_BRANCH $ENV{TRAVIS_BRANCH})\nelseif(DEFINED ENV{BRANCH_NAME})\n    set(GIT_BRANCH $ENV{BRANCH_NAME})\nelse()\n    execute_process(\n            COMMAND git rev-parse --abbrev-ref HEAD\n            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n            OUTPUT_VARIABLE GIT_BRANCH\n            OUTPUT_STRIP_TRAILING_WHITESPACE\n    )\nendif()\n\n# Get the latest abbreviated commit hash of the working branch\nexecute_process(\n        COMMAND git log -1 --format=%h\n        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n        OUTPUT_VARIABLE GIT_COMMIT_HASH\n        OUTPUT_STRIP_TRAILING_WHITESPACE\n)\n\n# Check if the git index is dirty\nexecute_process(\n        COMMAND git diff-index HEAD --\n        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n        OUTPUT_VARIABLE GIT_DIRTY_STRING\n        OUTPUT_STRIP_TRAILING_WHITESPACE\n)\n\n# flatpak-builder breaks any commands that check the git tree,\n# and fdroid applies local autochanges such as gradle wrapper removal and build.gradle patching\nif(NOT \"${GIT_DIRTY_STRING}\" STREQUAL \"\" AND NOT FLATPAK_BUILD AND NOT FDROID_BUILD)\n    set(GIT_COMMIT_HASH \"${GIT_COMMIT_HASH}-dirty\")\nendif()\n\nset(PACKAGE_SUFFIX ${GIT_BRANCH}${CI_PULL_REQUEST})\n"
  },
  {
    "path": "cmake/git_version.cmake",
    "content": "# adds a custom target for git version (always built)\nadd_custom_target(git_version ALL)\n\n# Report about improperly use of PRE_BUILD, PRE_LINK, and POST_BUILD keywords\nif(POLICY CMP0175)\n    cmake_policy(SET CMP0175 NEW)\nendif()\n\n# creates git_version.h using cmake script\nadd_custom_command(TARGET git_version PRE_BUILD\n    COMMAND ${CMAKE_COMMAND}\n        \"-DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}\"\n        \"-DOVERRIDE_GIT_BRANCH=${OVERRIDE_GIT_BRANCH}\"\n        \"-DFLATPAK_BUILD=${FLATPAK_BUILD}\"\n        \"-DFDROID_BUILD=${FDROID_BUILD}\"\n        -P \"${CMAKE_CURRENT_SOURCE_DIR}/cmake/git_version_update.cmake\")\n\n# attempt to force reconfigure for the build AFTER git status changes\nset_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS \"${CMAKE_CURRENT_BINARY_DIR}/generated-include/git_version.h\")\n"
  },
  {
    "path": "cmake/git_version_update.cmake",
    "content": "# Get the current working branch\nset(OVERRIDE_GIT_BRANCH \"\" CACHE STRING \"Override name of GIT branch\")\n\nif(OVERRIDE_GIT_BRANCH)\n    set(GIT_BRANCH ${OVERRIDE_GIT_BRANCH})\nelseif(DEFINED ENV{APPVEYOR_REPO_BRANCH})\n    set(GIT_BRANCH $ENV{APPVEYOR_REPO_BRANCH})\nelseif(DEFINED ENV{TRAVIS_BRANCH})\n    set(GIT_BRANCH $ENV{TRAVIS_BRANCH})\nelseif(DEFINED ENV{BRANCH_NAME})\n    set(GIT_BRANCH $ENV{BRANCH_NAME})\nelse()\n    execute_process(\n            COMMAND git rev-parse --abbrev-ref HEAD\n            WORKING_DIRECTORY ${SOURCE_DIR}\n            OUTPUT_VARIABLE GIT_BRANCH\n            OUTPUT_STRIP_TRAILING_WHITESPACE\n    )\nendif()\n\n# Get the latest abbreviated commit hash of the working branch\nexecute_process(\n        COMMAND git log -1 --format=%h --abbrev=8\n        WORKING_DIRECTORY ${SOURCE_DIR}\n        OUTPUT_VARIABLE GIT_COMMIT_HASH\n        OUTPUT_STRIP_TRAILING_WHITESPACE\n)\n\n# Check if the git index is dirty\nexecute_process(\n        COMMAND git diff-index HEAD --\n        WORKING_DIRECTORY ${SOURCE_DIR}\n        OUTPUT_VARIABLE GIT_DIRTY_STRING\n        OUTPUT_STRIP_TRAILING_WHITESPACE\n)\n\n# flatpak-builder breaks any commands that check the git tree\nif(NOT \"${GIT_DIRTY_STRING}\" STREQUAL \"\" AND NOT FLATPAK_BUILD AND NOT FDROID_BUILD)\n    # on CI, display the git diff in the log\n    if(OVERRIDE_GIT_BRANCH OR DEFINED ENV{CI})\n        message(\"=== git index dirty, see diff:\")\n        message(\"${GIT_DIRTY_STRING}\")\n    endif()\n\n    set(GIT_COMMIT_HASH \"${GIT_COMMIT_HASH}-dirty\")\nendif()\n\nif(NOT GIT_COMMIT_HASH)\n    set(THEXTECH_GIT_VERSION \"CMakeUnknown\")\nelse()\n    set(THEXTECH_GIT_VERSION \"${GIT_COMMIT_HASH}\")\nendif()\n\nif(NOT GIT_BRANCH)\n    set(THEXTECH_GIT_BRANCH \"unknown\")\nelse()\n    set(THEXTECH_GIT_BRANCH \"${GIT_BRANCH}\")\nendif()\n\nmessage(\"=== git info: commit ${THEXTECH_GIT_VERSION}, branch ${THEXTECH_GIT_BRANCH}\")\n\nfile(MAKE_DIRECTORY generated-include)\nfile(WRITE generated-include/git_version.h.txt \"#define GIT_VERSION \\\"${THEXTECH_GIT_VERSION}\\\"\\n#define GIT_BRANCH \\\"${THEXTECH_GIT_BRANCH}\\\"\\n\")\n\nexecute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different generated-include/git_version.h.txt generated-include/git_version.h)\n"
  },
  {
    "path": "cmake/icon.desktop.in",
    "content": "[Desktop Entry]\nName=@DESKTOP_NAME@\nGenericName=@DESKTOP_GENERIC_NAME@\nComment=@DESKTOP_COMMENT@\nEncoding=UTF-8\nVersion=1.0\nType=Application\nTerminal=false\nIcon=@DESKTOP_ICON@\nExec=@DESKTOP_EXEC@ %f\nX-Purism-FormFactor=Workstation;Mobile;\nCategories=Game;\nStartupWMClass=@DESKTOP_WMCLASS@\nActions=SpeedRunMode1;SpeedRunMode2;SpeedRunMode3;CompatModeClassic;CompatModeVanilla;\n\n[Desktop Action SpeedRunMode1]\nName=Speedrun Mode 1 (Native)\nExec=@DESKTOP_EXEC@ --speed-run-mode 1 %f\n\n[Desktop Action SpeedRunMode2]\nName=Speedrun Mode 2 (Classic)\nExec=@DESKTOP_EXEC@ --speed-run-mode 2 %f\n\n[Desktop Action SpeedRunMode3]\nName=Speedrun Mode 3 (Strict Vanilla)\nExec=@DESKTOP_EXEC@ --speed-run-mode 3 %f\n\n[Desktop Action CompatModeClassic]\nName=Run in \"Classic\" compatibiltiy mode (critical bugfixes only)\nExec=@DESKTOP_EXEC@ --compat-level classic %f\n\n[Desktop Action CompatModeVanilla]\nName=Run in \"Vanilla\" strict compatibiltiy mode (no bugfixes at all)\nExec=@DESKTOP_EXEC@ --compat-level vanilla %f\n"
  },
  {
    "path": "cmake/library_FreeImage.cmake",
    "content": "# FreeImage is an image management library\n\nadd_library(PGE_FreeImage INTERFACE)\n\nif(NINTENDO_SWITCH)\n    set(USE_FREEIMAGE_SYSTEM_LIBS_DEFAULT ON)\nelse()\n    set(USE_FREEIMAGE_SYSTEM_LIBS_DEFAULT OFF)\nendif()\n\nif(NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_3DS OR NINTENDO_SWITCH OR XTECH_MACOSX_TIGER)\n    set(USE_PNG_HARDWARE_OPTIMIZATIONS_DEFAULT OFF)\nelse()\n    set(USE_PNG_HARDWARE_OPTIMIZATIONS_DEFAULT ON)\nendif()\n\noption(USE_SHARED_FREEIMAGE \"Use shared build of FreeImage\" OFF)\noption(USE_FREEIMAGE_SYSTEM_LIBS \"Let FreeImage to use libPNG and libJPEG from the system\" ${USE_FREEIMAGE_SYSTEM_LIBS_DEFAULT})\noption(USE_PNG_HARDWARE_OPTIMIZATIONS \"Enable hardware optimizations for the FreeImage internal build of libPNG\" ${USE_PNG_HARDWARE_OPTIMIZATIONS_DEFAULT})\noption(FREEIMAGE_PIC \"Enable -fPIC flag for libFreeImage\" ON)\n\nif(VITA OR NINTENDO_SWITCH OR NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU)\n    set(FREEIMAGE_PIC OFF)\nendif()\n\n\nif(USE_SHARED_FREEIMAGE)\n    set_shared_lib(libFreeImage_Libs \"${DEPENDENCIES_INSTALL_DIR}/lib\" FreeImageLite)\n    set(USE_STATIC_FREEIMAGE OFF)\nelse()\n    set_static_lib(libFreeImage_Libs \"${DEPENDENCIES_INSTALL_DIR}/lib\" FreeImageLite${LIBRARY_STATIC_NAME_SUFFIX})\n    set(USE_STATIC_FREEIMAGE ON)\nendif()\n\nif(NOT THEXTECH_NO_SDL_BUILD)\n    set(USE_FREEIMAGE_SYSTEM_ZLIB ON)\n    # Use Zlib from AudioCodecs kit\n    if(USE_SYSTEM_ZLIB)\n        set(FREEIMAGE_ZLIB_INCLUDE ${ZLIB_INCLUDE_DIRS})\n        set(FREEIMAGE_ZLIB_LIBS ${ZLIB_LIBRARIES})\n    else()\n        set(FREEIMAGE_ZLIB_INCLUDE \"${DEPENDENCIES_INSTALL_DIR}/include\")\n        set(FREEIMAGE_ZLIB_LIBS ${AC_ZLIB})\n    endif()\nendif()\n\nExternalProject_Add(\n    FreeImage_Local\n    PREFIX ${CMAKE_BINARY_DIR}/external/FreeImage\n#    GIT_REPOSITORY https://github.com/WohlSoft/libFreeImage.git\n#    UPDATE_COMMAND \"\"\n    DOWNLOAD_COMMAND \"\"\n    SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../3rdparty/FreeImageLite\n    CMAKE_ARGS\n        \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n        \"-DFREEIMAGE_SHARED=${USE_SHARED_FREEIMAGE}\"\n        \"-DFREEIMAGE_STATIC=${USE_STATIC_FREEIMAGE}\"\n        \"-DDEPENDENCIES_INSTALL_DIR=${DEPENDENCIES_INSTALL_DIR}\"\n        #\"-DCMAKE_PROJECT_FreeImage_INCLUDE=${CMAKE_SOURCE_DIR}/_common/build_env.cmake\"\n        \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"\n        \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n        \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\"\n        \"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}\"\n        \"-DCMAKE_POSITION_INDEPENDENT_CODE=${FREEIMAGE_PIC}\"\n        \"-DCMAKE_DEBUG_POSTFIX=${PGE_LIBS_DEBUG_SUFFIX}\"\n        \"-DFREEIMAGE_USE_SYSTEM_LIBPNG=${USE_FREEIMAGE_SYSTEM_LIBS}\"\n        \"-DFREEIMAGE_USE_SYSTEM_LIBJPEG=${USE_FREEIMAGE_SYSTEM_LIBS}\"\n        \"-DFREEIMAGE_USE_SYSTEM_ZLIB=${USE_FREEIMAGE_SYSTEM_ZLIB}\"\n        \"-DPNG_HARDWARE_OPTIMIZATIONS=${USE_PNG_HARDWARE_OPTIMIZATIONS}\"\n        #\"-DFREEIMAGE_PNG_INCLUDE=${PNG_INCLUDE_DIRS}\"\n        \"-DFREEIMAGE_ZLIB_INCLUDE=${FREEIMAGE_ZLIB_INCLUDE}\"\n        #\"-DFREEIMAGE_PNG_LIB=${libPNG_A_Lib}\"\n        \"-DFREEIMAGE_ZLIB_LIB=${FREEIMAGE_ZLIB_LIBS}\"\n        $<$<BOOL:APPLE>:-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}>\n        $<$<BOOL:APPLE>:-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}>\n        ${ANDROID_CMAKE_FLAGS}\n    BUILD_BYPRODUCTS\n        \"${libFreeImage_Libs}\"\n)\n\ntarget_link_libraries(PGE_FreeImage INTERFACE\n    \"${libFreeImage_Libs}\"\n)\n\nif(USE_FREEIMAGE_SYSTEM_LIBS AND NOT USE_SHARED_FREEIMAGE)\n    find_library(LIBRARY_PNG png)\n    find_library(LIBRARY_JPEG jpeg)\n\n    if(NOT LIBRARY_PNG OR NOT LIBRARY_JPEG)\n        message(FATAL_ERROR \"Required libPNG and libJPEG libraries are not found!\")\n    endif()\n\n    target_link_libraries(PGE_FreeImage INTERFACE\n        \"${LIBRARY_PNG}\"\n        \"${LIBRARY_JPEG}\"\n    )\nendif()\n\nif(USE_SHARED_FREEIMAGE AND NOT WIN32)\n    install(FILES ${libFreeImage_Libs} DESTINATION ${PGE_INSTALL_DIRECTORY})\nendif()\n"
  },
  {
    "path": "cmake/library_FreeType.cmake",
    "content": "option(FREETYPE_PIC \"Enable -fPIC flag for libFreeType\" ON)\n\nif(VITA OR NINTENDO_SWITCH OR NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU)\n    set(FREETYPE_PIC OFF)\nendif()\n\nadd_library(PGE_FreeType INTERFACE)\n\nif(USE_SYSTEM_LIBS)\n    add_library(FREETYPE_Local INTERFACE)\n\n    find_package(Freetype REQUIRED)\n    message(\"-- Found FreeType: ${FREETYPE_LIBRARIES} --\")\n    target_link_libraries(PGE_FreeType INTERFACE ${FREETYPE_LIBRARIES})\n    target_include_directories(PGE_FreeType INTERFACE ${FREETYPE_INCLUDE_DIRS})\n\n    set(libFreeType_Libs \"${FREETYPE_LIBRARIES}\")\n\nelse()\n    set(libFreeType_Libs \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}freetype${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}\")\n\n    if(NOT THEXTECH_NO_SDL_BUILD)\n        # Use Zlib from AudioCodecs kit\n        if(USE_SYSTEM_ZLIB)\n            set(FREETYPE_ZLIB_INCLUDE ${ZLIB_INCLUDE_DIRS})\n            set(FREETYPE_ZLIB_LIBS ${ZLIB_LIBRARIES})\n        else()\n            set(FREETYPE_ZLIB_INCLUDE \"${DEPENDENCIES_INSTALL_DIR}/include\")\n            set(FREETYPE_ZLIB_LIBS ${AC_ZLIB})\n        endif()\n    endif()\n\n    if(NOT DEFINED FT_DISABLE_HARFBUZZ)\n        set(FT_DISABLE_HARFBUZZ OFF)\n    endif()\n\n    if(TARGET HARFBUZZ_Local)\n        set(FT_HARFBUZZ_TARGET HARFBUZZ_Local)\n    endif()\n\n    # FreeType to render TTF fonts\n    ExternalProject_Add(\n        FREETYPE_Local\n        PREFIX ${CMAKE_BINARY_DIR}/external/FreeType\n        DOWNLOAD_COMMAND \"\"\n        SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../3rdparty/freetype\n        CMAKE_ARGS\n            \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"\n            \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n            \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\"\n            \"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}\"\n#            \"-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}\"\n            \"-DCMAKE_POSITION_INDEPENDENT_CODE=${FREETYPE_PIC}\"\n            \"-DDISABLE_FORCE_DEBUG_POSTFIX=ON\"\n            \"-DCMAKE_DEBUG_POSTFIX=${PGE_LIBS_DEBUG_SUFFIX}\"\n            ${ANDROID_CMAKE_FLAGS}\n            -DFT_ENABLE_ERROR_STRINGS=ON\n            -DFT_DISABLE_ZLIB=OFF\n            -DFT_DISABLE_BZIP2=ON\n            -DFT_DISABLE_BROTLI=ON\n            -DFT_DISABLE_PNG=ON\n            -DFT_DISABLE_BROTLI=ON\n            \"-DZLIB_INCLUDE_DIR=${FREETYPE_ZLIB_INCLUDE}\"\n            \"-DZLIB_LIBRARY=${AC_ZLIB}\"\n            -DFT_DISABLE_HARFBUZZ=${FT_DISABLE_HARFBUZZ}\n            \"-DHARFBUZZ_LIBRARIES=${libHarfBuzz_Libs}\"\n            -DCMAKE_DISABLE_FIND_PACKAGE_PNG=TRUE\n            -DCMAKE_DISABLE_FIND_PACKAGE_BZip2=TRUE\n            -DCMAKE_DISABLE_FIND_PACKAGE_HarfBuzz=TRUE\n            $<$<BOOL:APPLE>:-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}>\n            $<$<BOOL:APPLE>:-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}>\n        BUILD_BYPRODUCTS\n            \"${libFreeType_Libs}\"\n        DEPENDS\n            ${FT_HARFBUZZ_TARGET}\n    )\n\n    message(\"-- FreeType will be built: ${libFreeType_Libs} --\")\n\n    target_link_libraries(PGE_FreeType INTERFACE \"${libFreeType_Libs}\")\n    target_include_directories(PGE_FreeType INTERFACE \"${DEPENDENCIES_INSTALL_DIR}/include\" \"${DEPENDENCIES_INSTALL_DIR}/include/freetype2\")\n\nendif()\n"
  },
  {
    "path": "cmake/library_HarfBuzz.cmake",
    "content": "# HarfBuzz is a text shaping engine\n\noption(HARFBUZZ_PIC \"Enable -fPIC flag for libHarfBuzz\" ON)\n\nif(VITA)\n    set(HARFBUZZ_PIC OFF)\nendif()\n\n\nadd_library(PGE_HarfBuzz INTERFACE)\n\nif(USE_SYSTEM_LIBS)\n    add_library(HARFBUZZ_Local INTERFACE)\n\n    find_package(HarfBuzz REQUIRED)\n    message(\"-- Found FreeType: ${HARFBUZZ_LIBRARIES} --\")\n    target_link_libraries(PGE_HarfBuzz INTERFACE ${HARFBUZZ_LIBRARIES})\n    target_include_directories(PGE_HarfBuzz INTERFACE ${HARFBUZZ_INCLUDE_DIRS})\n\n    set(libFreeType_Libs \"${HARFBUZZ_LIBRARIES}\")\n\nelse()\n    set(libHarfBuzz_Libs \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}harfbuzz${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}\")\n\n    # FreeType to render TTF fonts\n    ExternalProject_Add(\n        HARFBUZZ_Local\n        PREFIX ${CMAKE_BINARY_DIR}/external/harfbuzz\n        DOWNLOAD_COMMAND \"\"\n        SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../3rdparty/harfbuzz\n        CMAKE_ARGS\n            \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"\n            \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n            \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\"\n            \"-DCMAKE_POSITION_INDEPENDENT_CODE=${HARFBUZZ_PIC}\"\n            \"-DCMAKE_DEBUG_POSTFIX=${PGE_LIBS_DEBUG_SUFFIX}\"\n            ${ANDROID_CMAKE_FLAGS}\n            -DFT_WITH_ZLIB=ON -DFT_WITH_BZIP2=OFF -DFT_WITH_PNG=ON -DFT_WITH_HARFBUZZ=OFF\n            $<$<BOOL:APPLE>:-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}>\n        BUILD_BYPRODUCTS\n            \"${libHarfBuzz_Libs}\"\n    )\n\n    message(\"-- HarfBuzz will be built: ${libHarfBuzz_Libs} --\")\n\n    target_link_libraries(PGE_HarfBuzz INTERFACE \"${libHarfBuzz_Libs}\")\n    target_include_directories(PGE_HarfBuzz INTERFACE \"${DEPENDENCIES_INSTALL_DIR}/include/harfbuzz\")\n\nendif()\n\n"
  },
  {
    "path": "cmake/library_SDLMixerX.cmake",
    "content": "\n# Note: You must also include \"library_AudioCodecs.cmake\" too!\n\nadd_library(PGE_SDLMixerX        INTERFACE)\nadd_library(PGE_SDLMixerX_static INTERFACE)\n\nset(SDL_BRANCH \"release-2.0.12\")\nset(SDL_GIT_BRANCH \"origin/release-2.0.12\")\n\nif(APPLE)\n    set(PGE_SHARED_SDLMIXER_DEFAULT OFF)\nelseif(EMSCRIPTEN OR ANDROID OR VITA OR NINTENDO_SWITCH OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_3DS)\n    set(PGE_SHARED_SDLMIXER_FORCE_OFF ON)\nelse()\n    set(PGE_SHARED_SDLMIXER_DEFAULT ON)\nendif()\n\nif(NOT PGE_SHARED_SDLMIXER_FORCE_OFF)\n    option(PGE_SHARED_SDLMIXER \"Link MixerX as a shared library (dll/so/dylib)\" ${PGE_SHARED_SDLMIXER_DEFAULT})\nelse()\n    set(PGE_SHARED_SDLMIXER OFF)\nendif()\n\noption(MIXERX_ENABLE_WAVPACK \"Enable the WavPack codec support [Support is experimental, doesn't builds on some platforms]\" OFF)\noption(AUDIOCODECS_DISABLE_ASM_OPTIMIZATIONS \"Disable hardware optimizations for libopus and other AudioCodecs-built libraries\" OFF)\n\nif(NOT NINTENDO_WII AND NOT NINTENDO_WIIU AND NOT XTECH_MACOSX_TIGER)\n    option(PGE_USE_LOCAL_SDL2 \"Do use the locally-built SDL2 library from the AudioCodecs set. Otherwise, download and build the development top main version.\" ON)\nelse()\n    option(PGE_USE_LOCAL_SDL2 \"Do use the locally-built SDL2 library from the AudioCodecs set. Otherwise, download and build the development top main version.\" OFF)\nendif()\n\nset(MIXER_USE_OGG_VORBIS_FILE OFF)\nset(MIXER_USE_OGG_VORBIS_STB ON)\nset(MIXER_USE_OGG_VORBIS_TREMOR OFF)\nset(AUDIOCODECS_OGG_UNSAFE_DISABLE_CRC OFF)\n\nif(NINTENDO_3DS)\n    set(MIXER_USE_OGG_VORBIS_STB OFF)\n    set(MIXER_USE_OGG_VORBIS_TREMOR ON)\n    set(AUDIOCODECS_OGG_UNSAFE_DISABLE_CRC ON)\nendif()\n\n#if(WIN32)\n#    if(MSVC)\n#        set(SDL_MixerX_SO_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}SDL2_mixer_ext${PGE_LIBS_DEBUG_SUFFIX}.lib\")\n#        set(SDL2_SO_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}SDL2${PGE_LIBS_DEBUG_SUFFIX}.lib\")\n#    else()\n#        # Note: implibs should use static library prefix\n#        set(SDL_MixerX_SO_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}SDL2_mixer_ext${PGE_LIBS_DEBUG_SUFFIX}.dll.a\")\n#        set(SDL2_SO_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}SDL2${PGE_LIBS_DEBUG_SUFFIX}.dll.a\")\n#    endif()\n#else()\n#    set(SDL_MixerX_SO_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}SDL2_mixer_ext${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}\")\n#    set(SDL2_SO_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}SDL2${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}\")\n#endif()\nset_shared_lib(SDL_MixerX_SO_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib\" SDL2_mixer_ext)\nset_shared_lib(SDL2_SO_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib\" SDL2)\n\n# NO LONGER REQUIRED SINCE SDL 2.0.18 (https://github.com/libsdl-org/SDL/issues/4955#issuecomment-968366436)\n# set_shared_lib(SDLHIDAPI_SO_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib\" hidapi)\n\nset_static_lib(SDL2_main_A_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib\" SDL2main)\n\nset_static_lib(SDL_MixerX_A_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib\" SDL2_mixer_ext${LIBRARY_STATIC_NAME_SUFFIX})\nset_static_lib(SDL2_A_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib\" SDL2${LIBRARY_STATIC_NAME_SUFFIX})\n\nset(CODECS_LIBRARIES_DIR ${DEPENDENCIES_INSTALL_DIR}/lib)\n\nif(USE_SYSTEM_SDL2)\n    set(USE_LOCAL_SDL2 OFF)\n    if(HAIKU)\n        find_library(SDL2_LIBRARY SDL2)\n        find_path(SDL2_INCLUDE_DIR \"SDL.h\" PATH_SUFFIXES SDL2)\n        if(NOT SDL2_LIBRARY AND NOT SDL2_INCLUDE_DIR)\n            message(FATAL_ERROR \"The SDL2 Library was not found!\")\n        endif()\n        set(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR})\n        set(SDL2_LIBRARIES ${SDL2_LIBRARY})\n    else()\n        find_package(SDL2 REQUIRED)\n        if(TARGET SDL2::SDL2)\n            set(SDL2_LIBRARIES SDL2::SDL2main SDL2::SDL2)\n        elseif(TARGET SDL2::SDL2-static)\n            set(SDL2_LIBRARIES SDL2::SDL2main SDL2::SDL2-static)\n        endif()\n    endif()\nelse()\n    set(USE_LOCAL_SDL2 ${PGE_USE_LOCAL_SDL2})\n    set(SDL2_INCLUDE_DIRS \"${DEPENDENCIES_INSTALL_DIR}/include/\" \"${DEPENDENCIES_INSTALL_DIR}/include/SDL2\")\nendif()\n\nset(MixerX_SysLibs ${THEXTECH_SYSLIBS})\n\nset_static_lib(AC_FLAC         \"${CODECS_LIBRARIES_DIR}\" FLAC)\nset_static_lib(AC_FLUIDLITE    \"${CODECS_LIBRARIES_DIR}\" fluidlite)\nset_static_lib(AC_VORBISFILE   \"${CODECS_LIBRARIES_DIR}\" vorbisfile)\nset_static_lib(AC_VORBISIDEC   \"${CODECS_LIBRARIES_DIR}\" vorbisidec)\nset_static_lib(AC_VORBIS       \"${CODECS_LIBRARIES_DIR}\" vorbis)\nset_static_lib(AC_OPUSFILE     \"${CODECS_LIBRARIES_DIR}\" opusfile)\nset_static_lib(AC_OPUS         \"${CODECS_LIBRARIES_DIR}\" opus)\nset_static_lib(AC_OGG          \"${CODECS_LIBRARIES_DIR}\" ogg)\nset_static_lib(AC_WAVPACK      \"${CODECS_LIBRARIES_DIR}\" wavpack)\nset_static_lib(AC_ADLMIDI      \"${CODECS_LIBRARIES_DIR}\" ADLMIDI)\nset_static_lib(AC_OPNMIDI      \"${CODECS_LIBRARIES_DIR}\" OPNMIDI)\nset_static_lib(AC_EDMIDI       \"${CODECS_LIBRARIES_DIR}\" EDMIDI)\nset_static_lib(AC_TIMIDITYSDL  \"${CODECS_LIBRARIES_DIR}\" timidity_sdl2)\nset_static_lib(AC_GME          \"${CODECS_LIBRARIES_DIR}\" gme)\nif(MSVC) # MSVC-built libxmp has the \"libxmp\" name\n    set_static_lib(AC_LIBXMP       \"${CODECS_LIBRARIES_DIR}\" libxmp)\nelse()\n    set_static_lib(AC_LIBXMP       \"${CODECS_LIBRARIES_DIR}\" xmp)\nendif()\nset_static_lib(AC_MODPLUG      \"${CODECS_LIBRARIES_DIR}\" modplug)\n# set_static_lib(AC_ZLIB         \"${CODECS_LIBRARIES_DIR}\" zlib)   # Moved to own ZLib header\n\nset(MixerX_CodecLibs\n#    \"${AC_FLAC}\"\n    \"${AC_FLUIDLITE}\"\n)\n\nif(MIXER_USE_OGG_VORBIS_FILE)\n    list(APPEND MixerX_CodecLibs ${AC_VORBISFILE})\n    list(APPEND MixerX_CodecLibs ${AC_VORBIS})\nendif()\n\nif(MIXER_USE_OGG_VORBIS_TREMOR)\n    list(APPEND MixerX_CodecLibs ${AC_VORBISIDEC})\nendif()\n\nlist(APPEND MixerX_CodecLibs\n    \"${AC_OPUSFILE}\"\n    \"${AC_OPUS}\"\n    \"${AC_OGG}\")\n\nif(MIXERX_ENABLE_WAVPACK)\n    list(APPEND MixerX_CodecLibs \"${AC_WAVPACK}\")\nendif()\n\nlist(APPEND MixerX_CodecLibs\n    \"${AC_ADLMIDI}\"\n    \"${AC_OPNMIDI}\"\n    \"${AC_EDMIDI}\"\n    \"${AC_TIMIDITYSDL}\"\n    \"${AC_GME}\"\n    \"${AC_LIBXMP}\"\n    \"${AC_MODPLUG}\"\n)\n\nif(VITA)\n    set(VITA_AUDIOCODECS_CMAKE_FLAGS\n        \"-DBUILD_OGG_VORBIS=OFF\"\n        \"-DBUILD_FLAC=OFF\"\n        \"-DBUILD_OPUS=ON\"\n    )\n\n    if(THEXTECH_BUILD_GL_ES_MODERN)\n        list(APPEND VITA_AUDIOCODECS_CMAKE_FLAGS\n            \"-DVITA_SDL2_PVR=ON\"\n        )\n    endif()\n\n    set(VITA_MIXERX_CMAKE_FLAGS\n        \"-DUSE_OGG_VORBIS_TREMOR=OFF\"\n        \"-DUSE_SYSTEM_AUDIO_LIBRARIES_DEFAULT=ON\"\n        \"-DSDL_MIXER_X_SHARED=OFF\"\n        \"-DFLAC_LIBRARIES=FLAC\"\n        \"-DOGG_LIBRARIES=ogg\"\n        \"-DLIBOPUSFILE_LIB=opusfile\"\n        \"-DLIBOPUS_LIB=opus\"\n        \"-DCMAKE_C_FLAGS=-I$ENV{VITASDK}/arm-vita-eabi/include/opus\"\n        \"-DLIBVORBISIDEC_LIB=vorbisidec\"\n        \"-DLIBVORBIS_LIB=vorbis\"\n        \"-DLIBVORBISFILE_LIB=vorbisfile\"\n    )\n\n    # Minimal list of libraries to link\n    set(MixerX_CodecLibs \"${AC_FLUIDLITE}\")\n\n    if(MIXERX_ENABLE_WAVPACK)\n        list(append MixerX_CodecLibs \"${AC_WAVPACK}\")\n    endif()\n\n    list(APPEND MixerX_CodecLibs\n        \"${AC_ADLMIDI}\"\n        \"${AC_OPNMIDI}\"\n        \"${AC_EDMIDI}\"\n        \"${AC_TIMIDITYSDL}\"\n        \"${AC_GME}\"\n        \"${AC_LIBXMP}\"\n        \"${AC_MODPLUG}\"\n    )\nendif()\n\nset(MixerX_Deps)\nset(AudioCodecs_Deps)\nset(AUDIO_CODECS_BUILD_ARGS)\n\nif(THEXTECH_NO_MIXER_X)\n    # Disable everything except of SDL2\n    list(APPEND AUDIO_CODECS_BUILD_ARGS\n        \"-DBUILD_FLAC=OFF\"\n        \"-DBUILD_MPG123=OFF\"\n        \"-DBUILD_MODPLUG=OFF\"\n        \"-DBUILD_LIBXMP=OFF\"\n        \"-DBUILD_OPUS=OFF\"\n        \"-DBUILD_WAVPACK=OFF\"\n        \"-DBUILD_FLUIDLITE=OFF\"\n        \"-DBUILD_ADLMIDI=OFF\"\n        \"-DBUILD_OPNMIDI=OFF\"\n        \"-DBUILD_EDMIDI=OFF\"\n    )\n\n    if(NOT NINTENDO_3DS)\n        list(APPEND AUDIO_CODECS_BUILD_ARGS\n            \"-DBUILD_GME=OFF\"\n            \"-DBUILD_OGG_VORBIS=OFF\"\n        )\n    endif()\nelse()\n    list(APPEND AUDIO_CODECS_BUILD_ARGS\n        \"-DBUILD_OGG_VORBIS=${MIXER_USE_OGG_VORBIS_TREMOR}\"\n        \"-DOGG_UNSAFE_DISABLE_CRC=${AUDIOCODECS_OGG_UNSAFE_DISABLE_CRC}\"\n        \"-DBUILD_FLAC=OFF\"\n        \"-DBUILD_MPG123=OFF\"\n        \"-DBUILD_GME_SYSTEM_ZLIB=${USE_SYSTEM_ZLIB}\"\n        \"-DBUILD_WAVPACK=${MIXERX_ENABLE_WAVPACK}\"\n    )\nendif()\n\nif(NINTENDO_WII OR NINTENDO_DS OR NINTENDO_3DS)\n    list(APPEND AUDIO_CODECS_BUILD_ARGS\n        \"-DDISABLE_HEAVY_SYNTHS=ON\"\n    )\nendif()\n\nlist(APPEND AUDIO_CODECS_BUILD_ARGS\n    \"-DUSE_LOCAL_SDL2=${USE_LOCAL_SDL2}\"\n    \"-DBUILD_SDL2_SHARED=${PGE_SHARED_SDLMIXER}\"\n    \"-DCMAKE_DEBUG_POSTFIX=${PGE_LIBS_DEBUG_SUFFIX}\"\n    ${ANDROID_CMAKE_FLAGS}\n    ${VITA_CMAKE_FLAGS}\n    ${VITA_AUDIOCODECS_CMAKE_FLAGS}\n)\n\nif(AUDIOCODECS_DISABLE_ASM_OPTIMIZATIONS)\n    list(APPEND AUDIO_CODECS_BUILD_ARGS\n        \"-DBUILD_OPUS_DISABLE_RTCD=ON\"\n        \"-DDISABLE_RTCD=ON\"\n    )\nendif()\n\nif(VITA OR NINTENDO_DS OR NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_SWITCH)\n    list(APPEND AUDIO_CODECS_BUILD_ARGS\n        -DENABLE_FPIC=OFF\n    )\nendif()\n\nif(WIN32 AND \"${TARGET_PROCESSOR}\" STREQUAL \"i386\")\n    # Disable SIMD on 32-bit Windows architecture to allow running on old computers\n    list(APPEND AUDIO_CODECS_BUILD_ARGS\n        -DDISABLE_SIMD=ON\n    )\nendif()\n\nif(USE_SYSTEM_SDL2)\n    # Ensure the SAME SDL2 directory will be used\n    list(APPEND AUDIO_CODECS_BUILD_ARGS\n        \"-DSDL2_DIR=${SDL2_DIR}\"\n    )\nendif()\n\nlist(REMOVE_DUPLICATES AUDIO_CODECS_BUILD_ARGS)\n\n#message(\"DEBUG: Audio Codecs CMake: arguments: ${AUDIO_CODECS_BUILD_ARGS}\")\n\nExternalProject_Add(\n    AudioCodecs_Local\n    PREFIX ${CMAKE_BINARY_DIR}/external/AudioCodecs\n#    GIT_REPOSITORY https://github.com/WohlSoft/AudioCodecs.git\n#   UPDATE_COMMAND \"\"\n    DOWNLOAD_COMMAND \"\"\n    SOURCE_DIR ${CMAKE_SOURCE_DIR}/3rdparty/AudioCodecs\n    CMAKE_ARGS\n        \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"\n        \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n        \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\"\n        \"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}\"\n        ${AUDIO_CODECS_BUILD_ARGS}\n        $<$<BOOL:APPLE>:-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}>\n        $<$<BOOL:APPLE>:-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}>\n        $<$<BOOL:WIN32>:-DCMAKE_SHARED_LIBRARY_PREFIX=\"\">\n    DEPENDS ${AudioCodecs_Deps}\n    BUILD_BYPRODUCTS\n        \"${SDL2_SO_Lib}\"\n        \"${SDL2_A_Lib}\"\n        \"${SDL2_main_A_Lib}\"\n        \"${AC_ZLIB}\"\n#        \"${SDLHIDAPI_SO_Lib}\" # No longer needed since SDL 2.0.18\n        ${MixerX_CodecLibs}\n)\n\nlist(APPEND MixerX_Deps AudioCodecs_Local)\n\nif(NOT THEXTECH_NO_MIXER_X)\n    set(MIXERX_CMAKE_FLAGS)\n    if(USE_SYSTEM_SDL2)\n        # Ensure the SAME SDL2 directory will be used\n        list(APPEND MIXERX_CMAKE_FLAGS\n            \"-DSDL2_DIR=${SDL2_DIR}\"\n        )\n    endif()\n\n    # SDL Mixer X - an audio library, fork of SDL Mixer\n    ExternalProject_Add(\n        SDLMixerX_Local\n        PREFIX ${CMAKE_BINARY_DIR}/external/SDLMixerX\n    #    GIT_REPOSITORY https://github.com/WohlSoft/SDL-Mixer-X.git\n    #    UPDATE_COMMAND \"\"\n        DOWNLOAD_COMMAND \"\"\n        SOURCE_DIR ${CMAKE_SOURCE_DIR}/3rdparty/SDL-Mixer-X\n        CMAKE_ARGS\n            \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"\n            \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n            \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\"\n            \"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}\"\n            \"-DAUDIO_CODECS_REPO_PATH=${CMAKE_BINARY_DIR}/external/AudioCodecs\"\n            \"-DAUDIO_CODECS_INSTALL_PATH=${DEPENDENCIES_INSTALL_DIR}\"\n            \"-DUSE_SYSTEM_SDL2=${USE_SYSTEM_SDL2}\"\n            \"-DCMAKE_DEBUG_POSTFIX=${PGE_LIBS_DEBUG_SUFFIX}\"\n            \"-DSDL_MIXER_X_SHARED=${PGE_SHARED_SDLMIXER}\"\n            \"-DAUDIO_CODECS_SDL2_HG_BRANCH=${SDL_BRANCH}\"\n            \"-DAUDIO_CODECS_SDL2_GIT_BRANCH=${SDL_GIT_BRANCH}\"\n            \"-DMIXERX_ENABLE_GPL=ON\"\n            \"-DWITH_SDL2_WASAPI=ON\"\n            \"-DUSE_MIDI_FLUIDLITE_OGG_STB=ON\"\n            \"-DUSE_DRFLAC=ON\"\n            \"-DUSE_FLAC=OFF\"\n            \"-DUSE_WAVPACK=${MIXERX_ENABLE_WAVPACK}\"\n            \"-DUSE_OGG_VORBIS_STB=${MIXER_USE_OGG_VORBIS_STB}\"\n            \"-DUSE_OGG_VORBIS_TREMOR=${MIXER_USE_OGG_VORBIS_TREMOR}\"\n            \"-DUSE_MP3_DRMP3=ON\"\n            \"-DUSE_MP3_MPG123=OFF\"\n            \"-DUSE_SYSTEM_ZLIB=${USE_SYSTEM_ZLIB}\"\n            ${MIXERX_CMAKE_FLAGS}\n            ${ANDROID_CMAKE_FLAGS}\n            ${VITA_CMAKE_FLAGS}\n            ${VITA_MIXERX_CMAKE_FLAGS}\n            $<$<BOOL:APPLE>:-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}>\n            $<$<BOOL:APPLE>:-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}>\n            $<$<BOOL:WIN32>:-DCMAKE_SHARED_LIBRARY_PREFIX=\"\">\n        DEPENDS ${MixerX_Deps}\n        BUILD_BYPRODUCTS\n            \"${SDL_MixerX_SO_Lib}\"\n            \"${SDL_MixerX_A_Lib}\"\n    )\n\n    target_link_libraries(PGE_SDLMixerX INTERFACE \"${SDL_MixerX_SO_Lib}\")\n    target_link_libraries(PGE_SDLMixerX_static INTERFACE \"${SDL_MixerX_A_Lib}\")\nendif()\n\nif(USE_SYSTEM_SDL2)\n    target_link_libraries(PGE_SDLMixerX INTERFACE ${SDL2_LIBRARIES})\nelseif(WIN32 AND MINGW)\n    target_link_libraries(PGE_SDLMixerX INTERFACE mingw32 \"${SDL2_main_A_Lib}\" \"${SDL2_SO_Lib}\" )\nelseif(WIN32 AND MSVC)\n    target_link_libraries(PGE_SDLMixerX INTERFACE \"${SDL2_main_A_Lib}\" \"${SDL2_SO_Lib}\")\nelse()\n    target_link_libraries(PGE_SDLMixerX INTERFACE \"${SDL2_SO_Lib}\")\nendif()\n\n\nmessage(\"--- Detected system libraries list: ${MixerX_SysLibs} ---\")\nif(NOT THEXTECH_CLI_BUILD AND NOT THEXTECH_NO_MIXER_X)\n    target_link_libraries(PGE_SDLMixerX_static INTERFACE ${MixerX_CodecLibs})\nendif()\n\nif(USE_SYSTEM_SDL2)\n    target_link_libraries(PGE_SDLMixerX_static INTERFACE ${SDL2_LIBRARIES})\nelseif(WIN32 AND MINGW)\n    target_link_libraries(PGE_SDLMixerX_static INTERFACE mingw32 \"${SDL2_main_A_Lib}\" \"${SDL2_A_Lib}\" )\nelseif((WIN32 AND MSVC) OR NINTENDO_3DS OR VITA)\n    target_link_libraries(PGE_SDLMixerX_static INTERFACE \"${SDL2_main_A_Lib}\" \"${SDL2_A_Lib}\")\nelse()\n    target_link_libraries(PGE_SDLMixerX_static INTERFACE \"${SDL2_A_Lib}\")\nendif()\n\ntarget_link_libraries(PGE_SDLMixerX_static INTERFACE ${MixerX_SysLibs})\n\n#if(ANDROID) # No longer required since SDL 2.0.18\n#    target_link_libraries(PGE_SDLMixerX_static INTERFACE \"${SDLHIDAPI_SO_Lib}\")\n#endif()\n\nif(PGE_SHARED_SDLMIXER AND NOT WIN32)\n    install(FILES ${SDL_MixerX_SO_Lib} DESTINATION \"${PGE_INSTALL_DIRECTORY}\")\nendif()\n"
  },
  {
    "path": "cmake/library_discord_rpc.cmake",
    "content": "add_library(PGE_DiscordRPC INTERFACE)\n\nif(WIN32)\n    if(\"${TARGET_PROCESSOR}\" STREQUAL \"i386\" OR \"${TARGET_PROCESSOR}\" STREQUAL \"x86_64\")\n        set(DISCORD_RPC_SUPPORTED ON)\n    endif()\nelseif(APPLE)\n    if(NOT XTECH_MACOSX_TIGER AND (\"${TARGET_PROCESSOR}\" STREQUAL \"x86_64\" OR \"${TARGET_PROCESSOR}\" STREQUAL \"arm64\"))\n        set(DISCORD_RPC_SUPPORTED ON)\n    endif()\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Linux\")\n    if(\"${TARGET_PROCESSOR}\" STREQUAL \"x86_64\")\n        set(DISCORD_RPC_SUPPORTED ON)\n    endif()\nendif()\n\nif(DISCORD_RPC_SUPPORTED)\n    option(THEXTECH_ENABLE_DISCORD_RPC \"Enable Discord RPC support\" OFF)\nelse()\n    set(THEXTECH_ENABLE_DISCORD_RPC OFF CACHE BOOL \"\" FORCE)\n    mark_as_advanced(THEXTECH_ENABLE_DISCORD_RPC)\nendif()\n\nif(DISCORD_RPC_SUPPORTED AND THEXTECH_ENABLE_DISCORD_RPC)\n    set(libDiscordRpc_Libs \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}discord-rpc${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}\")\n    set(THEXTECH_DISCORD_APPID nullptr CACHE STRING \"Discord Application ID for the Risch Presence work\")\n\n    ExternalProject_Add(\n        DiscordPRC_Local\n        PREFIX ${CMAKE_BINARY_DIR}/external/DiscordRPC\n        DOWNLOAD_COMMAND \"\"\n        SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../3rdparty/thextech-discord-rpc\n        CMAKE_ARGS\n            \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"\n            \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n            \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\"\n            \"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}\"\n            \"-DCMAKE_POSITION_INDEPENDENT_CODE=${FREETYPE_PIC}\"\n            \"-DCMAKE_DEBUG_POSTFIX=${PGE_LIBS_DEBUG_SUFFIX}\"\n            \"-DBUILD_EXAMPLES=OFF\"\n            ${ANDROID_CMAKE_FLAGS}\n            $<$<BOOL:APPLE>:-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}>\n            $<$<BOOL:APPLE>:-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}>\n        BUILD_BYPRODUCTS\n            \"${libDiscordRpc_Libs}\"\n    )\n\n    message(STATUS \"Discord RPC will be built: ${libDiscordRpc_Libs}\")\n\n    target_link_libraries(PGE_DiscordRPC INTERFACE \"${libDiscordRpc_Libs}\")\n    target_include_directories(PGE_DiscordRPC INTERFACE \"${DEPENDENCIES_INSTALL_DIR}/include\")\nelse()\n    if(NOT DISCORD_RPC_SUPPORTED)\n        message(STATUS \"Discord RPC will be disabled: unsupported platform [SYS=${CMAKE_SYSTEM_NAME}, ARCH=${TARGET_PROCESSOR}]\")\n    else()\n        message(STATUS \"Discord RPC disabled by user's CMake option\")\n    endif()\nendif()\n"
  },
  {
    "path": "cmake/library_glew.cmake",
    "content": "add_library(PGE_GLEW INTERFACE)\n\n# GLEW is only used for the Windows builds\nif(WIN32)\n    set(GLEW_SUPPORTED ON)\nelse()\n    set(GLEW_SUPPORTED OFF)\nendif()\n\nif(GLEW_SUPPORTED)\n    option(THEXTECH_ENABLE_VENDORED_GLEW \"Enable building of the vendored GLEW library\" ON)\n    mark_as_advanced(THEXTECH_ENABLE_VENDORED_GLEW)\n\n    if(NOT THEXTECH_BUILD_GL_DESKTOP_MODERN)\n        set(THEXTECH_ENABLE_VENDORED_GLEW OFF CACHE BOOL \"\")\n    endif()\nelse()\n    set(THEXTECH_ENABLE_VENDORED_GLEW OFF CACHE BOOL \"\" FORCE)\nendif()\n\nif(GLEW_SUPPORTED AND THEXTECH_ENABLE_VENDORED_GLEW)\n    set(libglew_Libs \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}glew${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}\")\n\n    ExternalProject_Add(\n        GLEW_Local\n        PREFIX ${CMAKE_BINARY_DIR}/external/glew-cmake\n        DOWNLOAD_COMMAND \"\"\n        SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../3rdparty/glew-cmake\n        CMAKE_ARGS\n            \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"\n            \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n            \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\"\n            \"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}\"\n            \"-DCMAKE_POSITION_INDEPENDENT_CODE=${FREETYPE_PIC}\"\n            \"-DCMAKE_DEBUG_POSTFIX=${PGE_LIBS_DEBUG_SUFFIX}\"\n            \"-Dglew-cmake_BUILD_SHARED=OFF\"\n            \"-DONLY_LIBS=ON\"\n            ${ANDROID_CMAKE_FLAGS}\n            $<$<BOOL:APPLE>:-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}>\n            $<$<BOOL:APPLE>:-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}>\n        BUILD_BYPRODUCTS\n            \"${libglew_Libs}\"\n    )\n\n    message(STATUS \"GLEW will be built in a place: ${libglew_Libs}\")\n\n    target_link_libraries(PGE_GLEW INTERFACE \"${libglew_Libs}\")\n    target_include_directories(PGE_GLEW INTERFACE \"${DEPENDENCIES_INSTALL_DIR}/include\")\nelse()\n    message(STATUS \"GLEW will be disabled: unsupported platform\")\nendif()\n"
  },
  {
    "path": "cmake/library_luabind.cmake",
    "content": "\n# Note: You must also include \"library_luajit.cmake\" too!\n\nadd_library(PGE_LuaBind INTERFACE)\n\nif(PGE_USE_LUAJIT)\n    set(LUAJIT_USE_CMAKE_FLAG -DUSE_LUAJIT=ON)\n    message(\"***** LuaJIT Lua in use! *****\")\nelse()\n    set(LUAJIT_USE_CMAKE_FLAG -DUSE_LUAJIT=OFF)\n    message(\"***** PUC-Rio Lua in use! *****\")\nendif()\n\nset(libLuaBind_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}luabind${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}\")\n\nif(NOT LUABIND_SOURCE_DIR)\n    set(LUABIND_SOURCE_DIR \"${CMAKE_CURRENT_LIST_DIR}/../3rdparty/luabind\")\nendif()\n\n# LuaBind is a powerful lua binding library for C++\nExternalProject_Add(\n    LuaBind_Local\n    PREFIX ${CMAKE_BINARY_DIR}/external/luabind\n    DOWNLOAD_COMMAND \"\"\n    SOURCE_DIR \"${LUABIND_SOURCE_DIR}\"\n    CMAKE_ARGS\n        \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"\n        \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n        \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\"\n        \"-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}\"\n        \"-DCMAKE_DEBUG_POSTFIX=d\"\n        \"-DCMAKE_POSITION_INDEPENDENT_CODE=ON\"\n        ${ANDROID_CMAKE_FLAGS}\n        ${LUAJIT_USE_CMAKE_FLAG}\n        $<$<BOOL:APPLE>:-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}>\n    BUILD_BYPRODUCTS\n        \"${libLuaBind_Lib}\"\n)\n\nif(PGE_USE_LUAJIT)\n    add_dependencies(LuaBind_Local LuaJIT_local)\nendif()\n\ntarget_link_libraries(PGE_LuaBind INTERFACE \"${libLuaBind_Lib}\" PGE_LuaJIT)\n"
  },
  {
    "path": "cmake/library_luajit.cmake",
    "content": "add_library(PGE_LuaJIT INTERFACE)\n\nset(PGE_USE_LUAJIT_ENABLED_BY_DEFAULT ON)\nif(EMSCRIPTEN)\n    # Disable LuaJIT for unsupported platforms\n    set(PGE_USE_LUAJIT_ENABLED_BY_DEFAULT OFF)\nendif()\n\noption(PGE_USE_LUAJIT \"Use LuaJIT lua engine\" ${PGE_USE_LUAJIT_ENABLED_BY_DEFAULT})\n\n#set(luajitArchive ${CMAKE_SOURCE_DIR}/_Libs/_sources/luajit.tar.gz)\n#file(SHA256 ${luajitArchive} luajitArchive_hash)\n\nset(libLuaJit_Lib \"${DEPENDENCIES_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}luajit${PGE_LIBS_DEBUG_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}\")\n\nset(LUAJIT_SOURCE_DIR \"${CMAKE_CURRENT_LIST_DIR}/../3rdparty/LuaJIT\")\n\nif(PGE_USE_LUAJIT)\n    ExternalProject_Add(\n        LuaJIT_local\n        PREFIX ${CMAKE_BINARY_DIR}/external/luabind\n        DOWNLOAD_COMMAND \"\"\n        SOURCE_DIR \"${LUAJIT_SOURCE_DIR}\"\n        CMAKE_ARGS\n        \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"\n        \"-DCMAKE_INSTALL_PREFIX=${DEPENDENCIES_INSTALL_DIR}\"\n        \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\"\n        \"-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}\"\n        \"-DCMAKE_DEBUG_POSTFIX=d\"\n        \"-DCMAKE_POSITION_INDEPENDENT_CODE=ON\"\n        \"-DLUAJIT_FORCE_UTF8_FOPEN=ON\"\n        ${ANDROID_CMAKE_FLAGS}\n        ${LUAJIT_USE_CMAKE_FLAG}\n        $<$<BOOL:APPLE>:-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}>\n    BUILD_BYPRODUCTS\n        \"${libLuaJit_Lib}\"\n    )\n\n    target_link_libraries(PGE_LuaJIT INTERFACE ${libLuaJit_Lib})\n\nelseif(PGE_USE_LUAJIT_LEGACY_BUILD)\n    if(WIN32)\n        set(MAKECMD \"mingw32-make\")\n    else()\n        set(MAKECMD \"make\")\n    endif()\n    if(APPLE)\n        set(ENV{MACOSX_DEPLOYMENT_TARGET} \"${CMAKE_OSX_DEPLOYMENT_TARGET}\")\n    endif()\n    set(LUAJIT_LIBNAME libluajit$<$<CONFIG:Debug>:d>.a)\n    string(TOLOWER \"${CMAKE_BUILD_TYPE}\" CMAKE_BUILD_TYPE_LOWER)\n    if(${CMAKE_BUILD_TYPE_LOWER} STREQUAL \"debug\")\n        message(\"== LuaJIT will be built in debug mode\")\n        set(BUILD_FLAGS \"CCDEBUG=-g -O0\")\n    endif()\n\n    if(WIN32)\n        set(LUAJIT_INSTALL_COMMAND \"\")\n    else()\n        set(LUAJIT_INSTALL_COMMAND\n            ${MAKECMD}\n            install\n            \"INSTALL_ANAME=${LUAJIT_LIBNAME}\"\n            \"BUILDMODE=static\"\n            \"PREFIX=${CMAKE_BINARY_DIR}\"\n        )\n    endif()\n\n    set(LUAJIT_INSTALL_DIR ${CMAKE_BINARY_DIR})\n    set(LUAJIT_LOCAL_SOURCE_DIR ${CMAKE_BINARY_DIR}/external/luajit/LuaJIT-src)\n\n    ExternalProject_Add(\n        LuaJIT_local\n        PREFIX ${CMAKE_BINARY_DIR}/external/luajit\n        URL ${luajitArchive}\n        URL_HASH SHA256=${luajitArchive_hash}\n        UPDATE_COMMAND \"\"\n        CONFIGURE_COMMAND \"\"\n        SOURCE_DIR ${LUAJIT_LOCAL_SOURCE_DIR}\n        BUILD_IN_SOURCE 1\n        BUILD_COMMAND\n            ${MAKECMD} -s\n            \"BUILDMODE=static\"\n            \"PREFIX=${CMAKE_BINARY_DIR}\"\n            \"CCWARN+=-Wno-unused-function\"\n            ${BUILD_FLAGS}\n        INSTALL_COMMAND \"${LUAJIT_INSTALL_COMMAND}\"\n    BUILD_BYPRODUCTS\n        \"${libLuaJit_Lib}\"\n    )\n    if(WIN32)\n        set(LUAJIT_BINARY_FILES \"luajit.exe\")\n\n        ExternalProject_Add_Step(\n            LuaJIT_local CreateLuaJitInstallFolder\n            COMMAND ${CMAKE_COMMAND} -E make_directory ${LUAJIT_INSTALL_DIR}/include/luajit-2.1 ${LUAJIT_INSTALL_DIR}/lib ${LUAJIT_INSTALL_DIR}/bin\n            COMMENT \"Creating install folder\"\n            DEPENDEES BUILD\n        )\n\n        ExternalProject_Add_Step(\n            LuaJIT_local InstallLuaJitHeaders\n            COMMAND ${CMAKE_COMMAND} -E copy lua.h lua.hpp luaconf.h luajit.h lualib.h lauxlib.h ${LUAJIT_INSTALL_DIR}/include/luajit-2.1\n            WORKING_DIRECTORY ${LUAJIT_LOCAL_SOURCE_DIR}/src\n            COMMENT \"Installing luajit headers\"\n            DEPENDEES BUILD CreateLuaJitInstallFolder\n        )\n\n        ExternalProject_Add_Step(\n            LuaJIT_local InstallLuaJitLib\n            COMMAND ${CMAKE_COMMAND} -E copy libluajit.a \"${LUAJIT_INSTALL_DIR}/lib/${LUAJIT_LIBNAME}\"\n            WORKING_DIRECTORY ${LUAJIT_LOCAL_SOURCE_DIR}/src\n            COMMENT \"Installing luajit lib\"\n            DEPENDEES BUILD CreateLuaJitInstallFolder\n        )\n\n        ExternalProject_Add_Step(\n            LuaJIT_local InstallLuaJitDLL\n            COMMAND ${CMAKE_COMMAND} -E copy ${LUAJIT_BINARY_FILES} ${LUAJIT_INSTALL_DIR}/bin\n            WORKING_DIRECTORY ${LUAJIT_LOCAL_SOURCE_DIR}/src\n            COMMENT \"Installing luajit shared library\"\n            DEPENDEES BUILD CreateLuaJitInstallFolder\n        )\n    endif() #NOT WIN32\n\n    target_link_libraries(PGE_LuaJIT INTERFACE ${libLuaJit_Lib})\n\nendif()\n"
  },
  {
    "path": "cmake/library_syslibs.cmake",
    "content": "# In this file all the generic platform specific system libraries should be listed\n\nset(THEXTECH_SYSLIBS)\n\nif(WIN32 AND NOT EMSCRIPTEN)\n    list(APPEND THEXTECH_SYSLIBS\n        \"version\" opengl32 dbghelp advapi32 kernel32 winmm imm32 setupapi\n    )\nendif()\n\nif(NINTENDO_SWITCH)\n    list(APPEND THEXTECH_SYSLIBS\n        EGL glapi drm_nouveau nx pthread GLESv2\n    )\nendif()\n\nif(NINTENDO_WII)\n    set(CMAKE_STANDARD_LIBRARIES \"\")\n    set(CMAKE_C_STANDARD_LIBRARIES \"\")\n    set(CMAKE_CXX_STANDARD_LIBRARIES \"\")\n    list(APPEND THEXTECH_SYSLIBS\n        db wiiuse fat bte asnd ogc m\n        # vorbisidec ogg\n    )\nendif()\n\nif(NINTENDO_WIIU)\n    set(CMAKE_STANDARD_LIBRARIES \"\")\n    set(CMAKE_C_STANDARD_LIBRARIES \"\")\n    set(CMAKE_CXX_STANDARD_LIBRARIES \"\")\n    list(APPEND THEXTECH_SYSLIBS\n        wut m\n    )\nendif()\n\nif(NINTENDO_3DS)\n    list(APPEND THEXTECH_SYSLIBS\n        citro2d citro3d ctru #vorbisidec ogg\n    )\nendif()\n\nif(NOT WIN32 AND NOT EMSCRIPTEN AND NOT APPLE AND NOT ANDROID AND NOT NINTENDO_SWITCH AND NOT NINTENDO_WIIU)\n    if(THEXTECH_BUILD_GL_ES_MODERN AND NOT THEXTECH_BUILD_GL_DESKTOP_MODERN AND NOT THEXTECH_BUILD_GL_DESKTOP_LEGACY)\n        find_library(_LIB_GL GLESv2)\n    else()\n        find_library(_LIB_GL GL)\n    endif()\n\n    if(_LIB_GL)\n        list(APPEND THEXTECH_SYSLIBS ${_LIB_GL})\n    endif()\n\n    if(BCMHOST_H) # Raspberry Pi dependencies\n        find_library(BCM_HOST_LIBRARY bcm_host)\n        if(BCM_HOST_LIBRARY)\n            list(APPEND THEXTECH_SYSLIBS ${BCM_HOST_LIBRARY})\n        endif()\n\n        find_library(VCOS_LIBRARY vcos)\n        if(VCOS_LIBRARY)\n            list(APPEND THEXTECH_SYSLIBS ${VCOS_LIBRARY})\n        endif()\n\n        find_library(VCHIQ_ARM_LIBRARY vchiq_arm)\n        if(VCHIQ_ARM_LIBRARY)\n            list(APPEND THEXTECH_SYSLIBS ${VCHIQ_ARM_LIBRARY})\n        endif()\n    endif()\n\n    find_library(_lib_pthread pthread)\n    if(_lib_pthread)\n        list(APPEND THEXTECH_SYSLIBS ${_lib_pthread})\n    endif()\n\n    find_library(_lib_dl dl)\n    if(_lib_dl)\n        list(APPEND THEXTECH_SYSLIBS ${_lib_dl})\n    endif()\nendif()\n\nif(ANDROID)\n    list(APPEND THEXTECH_SYSLIBS\n        GLESv1_CM GLESv2 OpenSLES log dl android\n    )\nendif()\n\nif(HAIKU)\n    list(APPEND THEXTECH_SYSLIBS\n        be device game media\n    )\nendif()\n\nif(APPLE)\n    macro(xtech_add_macos_library LIBRARY_NAME)\n        find_library(MACOS_LIBRARY_${LIBRARY_NAME} ${LIBRARY_NAME})\n        if(MACOS_LIBRARY_${LIBRARY_NAME})\n            list(APPEND THEXTECH_SYSLIBS ${MACOS_LIBRARY_${LIBRARY_NAME}})\n            message(\"-- Library ${LIBRARY_NAME} found\")\n        else()\n            message(\"-- Library ${LIBRARY_NAME} NOT found\")\n        endif()\n    endmacro()\n\n    xtech_add_macos_library(CoreAudio)\n    xtech_add_macos_library(CoreVideo)\n    xtech_add_macos_library(CoreHaptics)\n    xtech_add_macos_library(GameController)\n    xtech_add_macos_library(IOKit)\n    xtech_add_macos_library(Carbon)\n    xtech_add_macos_library(Cocoa)\n    xtech_add_macos_library(ForceFeedback)\n    xtech_add_macos_library(Metal)\n    xtech_add_macos_library(CoreFoundation)\n    xtech_add_macos_library(AudioToolbox)\n    xtech_add_macos_library(AudioUnit)\n    xtech_add_macos_library(OpenGL)\nendif()\n"
  },
  {
    "path": "cmake/library_zlib.cmake",
    "content": "\nif(NINTENDO_SWITCH OR XTECH_MACOSX_TIGER)\n    set(PGE_SYSTEM_ZLIB_DEFAULT ON)\nelse()\n    set(PGE_SYSTEM_ZLIB_DEFAULT OFF)\nendif()\n\noption(USE_SYSTEM_ZLIB \"Use zlib library from the system\" ${PGE_SYSTEM_ZLIB_DEFAULT})\n\nadd_library(PGE_ZLib INTERFACE)\n\nif(USE_SYSTEM_ZLIB)\n    find_package(ZLIB REQUIRED)\n    target_include_directories(PGE_ZLib INTERFACE \"${ZLIB_INCLUDE_DIRS}\")\n    target_link_libraries(PGE_ZLib INTERFACE ${ZLIB_LIBRARIES})\nelse()\n    # ZLib copy from the AudioCodecs package\n    set_static_lib(AC_ZLIB  \"${DEPENDENCIES_INSTALL_DIR}/lib\"   zlib)\n    target_include_directories(PGE_ZLib INTERFACE \"${DEPENDENCIES_INSTALL_DIR}/include\")\n    target_link_libraries(PGE_ZLib INTERFACE \"${AC_ZLIB}\")\nendif()\n"
  },
  {
    "path": "cmake/ndk-stl-config.cmake",
    "content": "# Copy shared STL files to Android Studio output directory so they can be\n# packaged in the APK.\n# Usage:\n#\n#   find_package(ndk-stl REQUIRED)\n#\n# or\n#\n#   find_package(ndk-stl REQUIRED PATHS \".\")\n\nif(NOT ${ANDROID_STL} MATCHES \"_shared\")\n  return()\nendif()\n\nfunction(configure_shared_stl lib_path so_base)\n  message(\"Configuring STL ${so_base} for ${ANDROID_ABI}\")\n  configure_file(\n    \"${ANDROID_NDK}/sources/cxx-stl/${lib_path}/libs/${ANDROID_ABI}/lib${so_base}.so\" \n    \"${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${so_base}.so\" \n    COPYONLY)\nendfunction()\n\nif(\"${ANDROID_STL}\" STREQUAL \"libstdc++\")\n  # The default minimal system C++ runtime library.\nelseif(\"${ANDROID_STL}\" STREQUAL \"gabi++_shared\")\n  # The GAbi++ runtime (shared).\n  message(FATAL_ERROR \"gabi++_shared was not configured by ndk-stl package\")\nelseif(\"${ANDROID_STL}\" STREQUAL \"stlport_shared\")\n  # The STLport runtime (shared).\n  configure_shared_stl(\"stlport\" \"stlport_shared\")\nelseif(\"${ANDROID_STL}\" STREQUAL \"gnustl_shared\")\n  # The GNU STL (shared).\n  configure_shared_stl(\"gnu-libstdc++/4.9\" \"gnustl_shared\")\nelseif(\"${ANDROID_STL}\" STREQUAL \"c++_shared\")\n  # The LLVM libc++ runtime (static).\n  configure_shared_stl(\"llvm-libc++\" \"c++_shared\")\nelse()\n   message(FATAL_ERROR \"STL configuration ANDROID_STL=${ANDROID_STL} is not supported\")\nendif()\n\n"
  },
  {
    "path": "cmake/overlays-16m.ld",
    "content": "/*--------------------------------------------------------------------------------\n    ** Example linker script for ARM9 overlays **\n--------------------------------------------------------------------------------*/\n\nMEMORY {\n    /* Placeholder LMA for overlays */\n    ovlmem : ORIGIN = 0xb0000000, LENGTH = 0x10000000\n}\n\nPHDRS {\n    /* Program header for the overlay table */\n    ovltable  PT_LOAD FLAGS(0x200007);\n\n    /* Program headers for each overlay (in order) */\n    ovl_dsi PT_LOAD FLAGS(0x200007);\n}\n\n/* Overlays in main RAM */\nSECTIONS {\n\n    PROVIDE_HIDDEN( __ovlarea_main_start = . );\n    PROVIDE_HIDDEN( __ovlarea_main_end = . );\n\n    /* ======== Overlay 'dsi' ======== */\n\n    . = __ovlarea_main_start;\n\n    .ovl.dsi ALIGN(32) : {\n        EXCLUDE_FILE(*.a *libnds9*.a *libcalico_ds9*.a *16m.o) *(.text .text.* .gnu.linkonce.t.* .glue_7t .glue_7 .vfp11_veneer .v4_bx)\n        EXCLUDE_FILE(*.a *libnds9*.a *libcalico_ds9*.a *16m.o) *(.rodata .rodata1 .rodata.* .gnu.linkonce.r.*)\n        EXCLUDE_FILE(*.a *libnds9*.a *libcalico_ds9*.a *16m.o) *(.data .data1 .data.* .gnu.linkonce.d.*)\n        . = ALIGN(4);\n\n        PROVIDE_HIDDEN( __ovl_ctor_start_dsi = . );\n        KEEP( EXCLUDE_FILE(*.a *libnds9*.a *libcalico_ds9*.a *16m.o) *(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*) .init_array .ctors) )\n        PROVIDE_HIDDEN( __ovl_ctor_end_dsi = . );\n        KEEP( EXCLUDE_FILE(*.a *libnds9*.a *libcalico_ds9*.a *16m.o) *(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*) .fini_array .dtors) )\n        PROVIDE_HIDDEN( __ovl_dtor_end_dsi = . );\n    } AT>ovlmem :ovl_dsi = 0xff\n\n    .ovl.dsi.bss . (NOLOAD) : {\n        EXCLUDE_FILE(*.a *libnds9*.a *libcalico_ds9*.a *boot_16m.o) *(.bss .bss.* .dynbss .gnu.linkonce.b.* COMMON)\n        . = ALIGN(4);\n    } AT>ovlmem :ovl_dsi\n\n    PROVIDE_HIDDEN( __ovl_dsi_end = . );\n    __ovlarea_main_end = MAX(__ovlarea_main_end, .);\n\n}\nINSERT AFTER .twl.bss;\n\nSECTIONS {\n\n    .ovltable 0 : {\n        /* Static ctors/dtors for overlay 'dsi' */\n        LONG(__ovl_ctor_start_dsi)\n        LONG(__ovl_ctor_end_dsi)\n        LONG(__ovl_dtor_end_dsi)\n\n    } AT>ovlmem :ovltable\n\n}\n"
  },
  {
    "path": "cmake/package_switch.cmake",
    "content": "find_program(NACPTOOL nacptool REQUIRED)\nmessage(\"-- napctool found: ${NACPTOOL}\")\n\nfind_program(ELF2NRO elf2nro REQUIRED)\nmessage(\"-- elf2nro found: ${ELF2NRO}\")\n\nadd_custom_target(thextech_nacp ALL\n    \"${NACPTOOL}\" --create\n    \"${THEXTECH_PACKAGE_NAME}\"\n    \"${CPACK_PACKAGE_VENDOR}\"\n    \"${THEXTECH_VERSION_1}.${THEXTECH_VERSION_2}.${THEXTECH_VERSION_3}\"\n    \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/thextech.nacp\"\n    DEPENDS thextech\n    BYPRODUCTS \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/thextech.nacp\"\n)\n\nadd_custom_target(thextech_nro ALL\n    \"${ELF2NRO}\"\n    \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/thextech.elf\"\n    \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/thextech.nro\"\n    \"--icon=${TheXTech_SOURCE_DIR}/resources/switch/thextech-logo.jpg\"\n    \"--nacp=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/thextech.nacp\"\n    DEPENDS thextech thextech_nacp\n)\n"
  },
  {
    "path": "cmake/package_vita.cmake",
    "content": "\nset(VITA_APP_NAME \"TheXTech ${THEXTECH_VERSION_STRING}\")\nset(VITA_TITLEID \"THEXTECH0\")\n\nset(VITA_VERSION \"${THEXTECH_VERSION_1}${THEXTECH_VERSION_2}.${THEXTECH_VERSION_3}${THEXTECH_VERSION_4}\")\n\n# the next two variables are used by template.xml.in\nset(XTECH_VITA_AUTHORS \"By Wohlstand and ds-sloth. Ported by Axiom.\")\n\nif(NOT \"${THEXTECH_VERSION_REL}\" STREQUAL \"\")\n    set(XTECH_VITA_SHORT_DESC \"git ${GIT_BRANCH} #${GIT_COMMIT_HASH}\")\nelse()\n    set(XTECH_VITA_SHORT_DESC \"Based on SMBX 1.3 by Redigit\")\nendif()\n\nmessage(\"Configuring Packaging for PS Vita\")\nmessage(\"THEXTECH_EXECUTABLE_NAME = ${THEXTECH_EXECUTABLE_NAME}\")\nmessage(\"VITA_APP_NAME = ${VITA_APP_NAME}\")\nmessage(\"VITA_TITLEID = ${VITA_TITLEID}\")\nmessage(\"VITA_VERSION = ${VITA_VERSION}\")\nmessage(\"VITA_AUTHORS = ${XTECH_VITA_AUTHORS}\")\nmessage(\"VITA_DESC = ${XTECH_VITA_SHORT_DESC}\")\ninstall(TARGETS thextech DESTINATION .)\n\nconfigure_file(${CMAKE_SOURCE_DIR}/resources/vita/sce_sys/livearea/contents/template.xml.in ${CMAKE_BINARY_DIR}/template.xml)\n\nvita_create_self(${THEXTECH_EXECUTABLE_NAME}.self output/bin/thextech UNSAFE)\nvita_create_vpk(${THEXTECH_EXECUTABLE_NAME}.vpk ${VITA_TITLEID} ${THEXTECH_EXECUTABLE_NAME}.self\n    VERSION ${VITA_VERSION}\n    NAME ${VITA_APP_NAME}\n    FILE    ${CMAKE_SOURCE_DIR}/resources/vita/sce_sys/icon0.png sce_sys/icon0.png\n            ${CMAKE_SOURCE_DIR}/resources/vita/sce_sys/livearea/contents/bg.png sce_sys/livearea/contents/bg.png\n            ${CMAKE_SOURCE_DIR}/resources/vita/sce_sys/livearea/contents/startup.png sce_sys/livearea/contents/startup.png\n            ${CMAKE_BINARY_DIR}/template.xml sce_sys/livearea/contents/template.xml\n)\n\nadd_custom_target(copy \n    COMMAND cp ${THEXTECH_EXECUTABLE_NAME}.self eboot.bin\n    DEPENDS ${THEXTECH_EXECUTABLE_NAME}.self\n)\n"
  },
  {
    "path": "cmake/vita_buildprops.cmake",
    "content": "message(\"Set CMAKE Flags for Vita.\")\ninclude(\"$ENV{VITASDK}/share/vita.cmake\" REQUIRED)\n\nif(POLICY CMP0077)\n    cmake_policy(SET CMP0077 NEW)\nendif()\n\nif(NOT VITA_MKSFOEX_FLAGS)\n    set(VITA_MKSFOEX_FLAGS \"-d ATTRIBUTE2=12\") # ATTRIBUTE2=12 specifies we need more RAM.  \nendif()\n\n\nset(VITA_ADDTL_LIBS\n    FLAC\n    modplug\n    opusfile\n    opus\n    vorbisfile\n    vorbis\n    ogg\n    mpg123\n    debugnet\n    mathneon\n    SceCtrl_stub\n    SceMotion_stub\n    SceHid_stub\n    SceRtc_stub\n    SceNetCtl_stub\n    SceNet_stub\n    SceLibKernel_stub\n    ScePvf_stub\n    SceAppMgr_stub\n    SceAppUtil_stub\n    ScePgf_stub\n    ScePower_stub\n    freetype\n    png\n    jpeg\n    SceCommonDialog_stub\n    m\n    zip\n    z\n    pthread\n    SceGxm_stub\n    SceDisplay_stub\n    SceSysmodule_stub\n    SceTouch_stub\n    SceAudio_stub\n    SceAudioIn_stub\n    vitashark\n    SceShaccCg_stub\n    SceSysmem_stub\n    SceIofilemgr_stub\n    SceKernelThreadMgr_stub\n    SceKernelDmacMgr_stub\n    SceVshBridge_stub\n)\n\nif(THEXTECH_BUILD_GL_ES_MODERN)\n    list(APPEND VITA_ADDTL_LIBS\n        SceShaccCgExt\n        taihen_stub\n        SceIme_stub\n     )\nendif()\n\n# VITA_CMAKE_FLAGS is used in conjunction with ExternalProject_Add\nset(VITA_CMAKE_FLAGS\n    # General/TheXTech\n    \"-DVITA=1\"\n    \"-DENABLE_FPIC=0\"\n    \"-DUSE_STATIC_LIBC=OFF\"\n\n    # Free Image\n    \"-DCMAKE_POSITION_INDEPENDENT_CODE=OFF\"\n    \"-DFREEIMAGE_SHARED=OFF\"\n    \"-DFREEIMAGE_USE_SYSTEM_LIBPNG=ON\"\n    \"-DFREEIMAGE_USE_SYSTEM_LIBJPEG=ON\"\n\n    # Audio Mixer\n    \"-DPGE_SHARED_SDLMIXER=OFF\"\n    \"-DUSE_GME=ON\"\n    \"-DUSE_MIDI=ON\"\n    \"-DADLMIDI_LIBRARY=\"\n    \"-DBUILD_OGG_VORBIS=1\"\n    \"-DLIBXMP_PIC=0\"\n)\n\n\n\n"
  },
  {
    "path": "debian/changelog",
    "content": "thextech-smbx (1.3.6-1ppa0) oneiric; urgency=low\n\n  * All changes shown at the included changelog.txt file\n\n -- Vitaly Novichkov <admin@wohlnet.ru>  Tue, 04 Jan 2022 22:16:54 +0300\n"
  },
  {
    "path": "debian/control",
    "content": "Source: thextech-smbx\nSection: games\nPriority: optional\nMaintainer: Vitaly Novichkov <admin@wohlnet.ru>\nBuild-Depends: cmake, build-essential, ninja-build, libsdl2-dev, libpng-dev, libjpeg-dev\nHomepage: https://wohlsoft.ru\n\nPackage: thextech-smbx\nArchitecture: any\nDepends: libsdl2-2.0-0, libpng16-16, libjpeg8\nDescription: The Mario fan game\n The Super Mario fan game developed by Andrew Spinks in the past\n"
  },
  {
    "path": "debian/copyright",
    "content": "####################################################################\n#                      thextech-smbx                               #\n####################################################################\n\nCopyright (C) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\nGeneral Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "debian/rules",
    "content": "#!/usr/bin/make -f\n\nBUILDDIR = build_dir\n\n# secondly called by launchpad\nbuild:\n\tmkdir $(BUILDDIR);\n\tcd $(BUILDDIR); cmake -DPGE_SHARED_SDLMIXER=OFF \\\n    -DUSE_SYSTEM_SDL2=ON \\\n    -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \\\n    -DCMAKE_INSTALL_PREFIX=../debian/tmp/usr \\\n    -DCMAKE_BUILD_TYPE=MinSizeRel \\\n    -DTHEXTECH_FIXED_ASSETS_PATH=/usr/share/games/smbx \\\n    -DTHEXTECH_SOURCE_ASSETS_PATH=\"/full/path/to/unpacket/assets/thextech-smbx13-assets-full\" \\\n    -DTHEXTECH_USER_DIR_NAME=\".thextech-smbx\" \\\n    -DDESKTOP_NAME=\"Super Mario Bros. X\" \\\n    -DDESKTOP_GENERIC_NAME=\"Super Mario Bros. X\" \\\n    -DDESKTOP_COMMENT=\"The Mario fan game\" \\\n    -DTHEXTECH_EXECUTABLE_NAME=\"thextech-smbx\" \\\n    -DTHEXTECH_PACKAGE_NAME=\"thextech-smbx\" \\\n    ..\n\tmake -C $(BUILDDIR)\n\n# thirdly called by launchpad\nbinary: binary-indep binary-arch\n\nbinary-indep:\n\t# nothing to be done\n\nbinary-arch:\n\tcd $(BUILDDIR); cmake -P cmake_install.cmake\n\tmkdir debian/tmp/DEBIAN\n\tdpkg-gencontrol -pthextech-smbx\n\tdpkg --build debian/tmp ..\n\n# firstly called by launchpad\nclean:\n\trm -f build\n\trm -rf $(BUILDDIR)\n\n.PHONY: binary binary-arch binary-indep clean\n"
  },
  {
    "path": "docs/README_3DS.md",
    "content": "# About TheXTech & its Nintendo 3DS Port\n\nTheXTech is an open-source rewrite of Super Mario Bros X. in C/++.\nThe original was written in VB6, targeting Windows only and eating up 2-3Gb of resources easily.\n\n## 3DS Installation Guide\n\n0. You are required to use the hacked 3DS.\n1. Download and place the `thextech.3dsx` at the \"3ds\" directory at the root of SD card.\n2. Download one of the game assets packages for TheXTech from here:\n  - Original packages (will work slow without conversion into native 3DS format) https://github.com/Wohlstand/TheXTech/wiki/Game-assets-packages\n  - Pre-converted packages as ROMFS (Optimized for 3DS):\n    - Super Mario Bros. X: https://builds.wohlsoft.ru/3ds/assets-smbx13-3ds.zip\n    - Adventures of Demo: https://builds.wohlsoft.ru/3ds/assets-aod-3ds.zip\n3. Extract the downloaded archive with all it's content\n4. Created a folder on your 3ds named “3ds/thextech” (case matters) in root of your SD card\n    sdmc:/3ds/thextech\n5. Move all extracted folders & files from the computer to sdmc:/3ds/thextech.\n    Upon completion, if you use the original (not optmizied for 3DS) package, your folder structure should look like this:\n        sdmc:/3ds/thextech/battle/\n        sdmc:/3ds/thextech/graphics/\n        sdmc:/3ds/thextech/music/\n        sdmc:/3ds/thextech/sound/\n        sdmc:/3ds/thextech/worlds/\n        sdmc:/3ds/thextech/gameinfo.ini\n        sdmc:/3ds/thextech/intro.lvl\n        sdmc:/3ds/thextech/music.ini\n        sdmc:/3ds/thextech/outro.lvl\n        sdmc:/3ds/thextech/sounds.ini\n    Otherwise, if you downloaded a .romfs package, simply place it into `sdmc:/3ds/thextech/` directory and rename it into `sdmc:/3ds/thextech/assets.romfs`.\n\n6. Run `TheXTech` application via Homebrew Launcher and enjoy.\n\n\n### KNOWN ISSUES\n\n- LunaLua/LuaJIT episodes are not supported. (ATWE: A Tiny World Episode immediately comes to mind. The Episode shows in the world list, but upon trying to load the game will gracefully exit.)\n- Has <1s delays when loading music (can cause in-level stuttering)\n- Has <1s delays when loading PNG or GIF images (can cause in-level stuttering)\n- Minor graphical glitches (images sometimes do not line up correctly, leaving gaps)\n- Has several performance issues related to 3DS's slow SD card filesystem access\n  - Has >10s delays when loading unpacked episodes\n  - Has ~2s delays when loading images from unpacked episodes (will cause in-level stuttering)\n  - Has ~2s delays when storing game saves or settings to the SD card\n- Has several performance issues on the Old 3DS\n  - Low framerates when playing with SPC and non-44.1 KHz music\n  - Low framerates when section echo effects are active\n  - Occasional crashes have been reported due to the Old 3DS's limited memory\n\n# TheXTech Standard Readme\n"
  },
  {
    "path": "docs/README_DSI.md",
    "content": "# About TheXTech & its Nintendo DSi Port\n\nTheXTech is an open-source rewrite of Super Mario Bros X. in C++.\nThe original was written in VB6, targeting Windows only and eating up 2-3 GB of memory easily.\n\nThe DSi has 16 MB of system memory and 512 KB of video memory.\n\n## DSi Installation Guide\n\n0. You are required to use a hacked DSi. The game runs best through hbmenu but may work with other launchers. It is confirmed not to work with unlaunch.\n1. Download and place the `thextech.nds` at any convenient directory on your SD.\n2. Create a folder on your DSi named “/TheXTech/assets/” from the root of your SD card:\n    sd:/TheXTech/assets/\n3. Download one of the game assets packages for TheXTech from here:\n  - Pre-converted packages in the dsi.xta format (optimized for DSi):\n    - Super Mario Bros. X: https://www.wohlsoft.ru/projects/TheXTech/_downloads/assets/thextech-smbx13.dsi.xta\n    - Adventures of Demo: https://www.wohlsoft.ru/projects/TheXTech/_downloads/assets/thextech-aod.dsi.xta\n  - Place the file into the assets folder you created in the previous step:\n    For instance, sd:/TheXTech/assets/thextech-aod.dsi.xta\n4. Wait for the XTConvert tool to be released to convert your episodes to the dsi.xte format.\n5. Place any converted episodes in sd:/TheXTech/worlds/smbx/ or sd:/TheXTech/worlds/aod/.\n6. Run `TheXTech` application via Homebrew Launcher and enjoy.\n\n## DSi Usage Guide\n\n- For consistency with other platforms, the game uses B for jump/accept and Y for run/back. The keybindings can be configured in the options menu.\n- The DSi screen resolution is 256x192 which yields a significantly smaller playfield than SMBX 1.3's 800x600. The L shoulder button toggles the viewport to match SMBX 1.3's. You can further customize the resolution in the options.\n\n### KNOWN ISSUES\n\n- This is a build straight off of TheXTech's development tree and may contain unexpected bugs\n- Most content is playable, but depending on level content, performance may drop as low as 1 FPS\n- On launchers other than hbmenu, the system crashes on attempting to exit the game\n- Music/sound volume may be unbalanced at times\n- Has <1s delays when loading music (can cause in-level stuttering)\n- Minor graphical glitches (images sometimes do not line up correctly, leaving gaps)\n\n# TheXTech Standard Readme\n"
  },
  {
    "path": "docs/README_SWITCH.md",
    "content": "# About TheXTech & its Nintendo Switch Port\n\nTheXTech is an open-source rewrite of Super Mario Bros X. in C/++.\nThe original was written in VB6, targeting Windows only and eating up 2-3Gb of resources easily.\n\n## Switch Installation Guide\n\n0. You are required to use the hacked Switch.\n1. Download and place the `thextech.nro` at the \"switch\" directory at the root of SD card.\n2. Download one of the game assets packages for TheXTech from here: https://github.com/Wohlstand/TheXTech/wiki/Game-assets-packages\n3. Extract the downloaded archive with all it's content\n4. Created a folder on your Switch named “TheXTech” (case matters) in root of your SD card\n        sdmc:/TheXTech\n5. Move all extracted folders & files from the computer to sdmc:/TheXTech.\n    Upon completion, your folder structure should look like this:\n        sdmc:/TheXTech/battle/\n        sdmc:/TheXTech/graphics/\n        sdmc:/TheXTech/music/\n        sdmc:/TheXTech/sound/\n        sdmc:/TheXTech/worlds/\n        sdmc:/TheXTech/gameinfo.ini\n        sdmc:/TheXTech/intro.lvl\n        sdmc:/TheXTech/music.ini\n        sdmc:/TheXTech/outro.lvl\n        sdmc:/TheXTech/sounds.ini\n\n6. Run `thextech.nro` via homebrew and enjoy.\n\n\n### KNOWN ISSUES\n\n- LunaLua/LuaJIT episodes are not supported. (ATWE: A Tiny World Episode immediately comes to mind. The Episode shows in the world list, but upon trying to load the game will gracefully exit.)\n\n\n# TheXTech Standard Readme\n"
  },
  {
    "path": "docs/README_VITA.ESP.md",
    "content": "# Acerca de TheXTech y su puerto para Vita\n\n<La traducción aún está en desarrollo, lea la versión en inglés en el archivo README.md por ahora>\n\n\n# Descripción básica de TheXTech\n"
  },
  {
    "path": "docs/README_VITA.RUS.md",
    "content": "# О TheXTech и его порте под Vita\n\n<Перевод ещё в разработке, пожалуйста, почитайте пока английскую версию в файле README.md>\n\n\n# Основное описание TheXTech\n"
  },
  {
    "path": "docs/README_VITA.md",
    "content": "# About TheXTech & its Vita Port\n\nTheXTech is an open-source rewrite of Super Mario Bros X. in C/++.\nThe original was written in VB6, targeting Windows only and eating up 2-3Gb of resources easily.\n\n*Almost* every other piece of code compiled with little fighting for the 333 Mhz RISC handheld. Performance is also *pretty good* if I do say so myself. SDL2 is being used for *everything* in this current version. Though, support for a more Vita-specific rendering engine may come at a later date.\n\n## Vita Installation Guide\n\n0. While possible to move all assets over FTP, it’s much faster and more reliable to mount your Vita over USB or move your Vita2SD to your computer and copy that way. You’ve been warned.\n1. Download and install `thextech.vpk`.\n2. Download one of the game assets packages for TheXTech from here: https://github.com/Wohlstand/TheXTech/wiki/Game-assets-packages\n3. Extract the downloaded archive with all it's content\n4. Created a folder on your Vita named “TheXTech” (case matters) in ux0:data\n        ux0:data/TheXTech\n5. Move all extracted folders & files from the computer to ux0:data/TheXTech.\n    Upon completion, your folder structure should look like this:\n        ux0:data/TheXTech/battle/\n        ux0:data/TheXTech/graphics/\n        ux0:data/TheXTech/music/\n        ux0:data/TheXTech/sound/\n        ux0:data/TheXTech/worlds/\n        ux0:data/TheXTech/gameinfo.ini\n        ux0:data/TheXTech/intro.lvl\n        ux0:data/TheXTech/music.ini\n        ux0:data/TheXTech/outro.lvl\n        ux0:data/TheXTech/sounds.ini\n\n6. Install `thextech.vpk` and enjoy.\n\n\n### KNOWN ISSUES\n\n- Slowdowns may happen.\n- SOME Midi tracks may kill performance (specifcially: using the libOPNMidi backend. this is pretty rare and midi support through libOPN and libADL are all relatively new to SMBX)\n- LunaLua/LuaJIT episodes are not supported. (ATWE: A Tiny World Episode immediately comes to mind. The Episode shows in the world list, but upon trying to load the game will gracefully exit.)\n\n\n## Addendum\n\nI was a huge fan, and VERY involved, in the original Super Mario Bros. X community as **Luigifan2010**.\nWhen Wohlstand first came to me mid 2020 teasing TheXTech, I immediately thought about porting it\nto the Vita. Well now, here it is. The stars finally aligned and I've taught myself enough C to figure\nthis out!\n\nIf you want to learn how to code, just do it! There are tons of resources out there.\n\nAnd most of all, have FUN while playing this port!\n\n- Axiom\n\n\n# TheXTech Standard Readme\n"
  },
  {
    "path": "docs/README_WII.md",
    "content": "# About TheXTech & its Nintendo Wii Port\n\nTheXTech is an open-source rewrite of Super Mario Bros X. in C/++.\nThe original was written in VB6, targeting Windows only and eating up 2-3Gb of resources easily.\n\n## Wii Installation Guide\n\n0. You are required to use the hacked Wii with installed Homebrew Channel.\n1. Download and place files `boot.dol`, `icon.png`, and `meta.xml` at the \"apps/thextech-wii\" directory at the root of SD card.\n2. Download one of the game assets packages for TheXTech from here:\n  - Original packages (will work slow without conversion into native Wii format) https://github.com/Wohlstand/TheXTech/wiki/Game-assets-packages\n  - Pre-converted packages (Optimized for Wii):\n    - Super Mario Bros. X: https://builds.wohlsoft.ru/wii/assets-smbx13-wii.zip\n    - Adventures of Demo: https://builds.wohlsoft.ru/wii/assets-aod-wii.zip\n3. Extract the downloaded archive with all it's content\n4. Created a folder on your Wii named “thextech” (case matters) in root of your SD card\n    sdmc:/thextech\n5. Move all extracted folders & files from the computer to sdmc:/thextech.\n    Upon completion, your folder structure should look like this:\n        sdmc:/thextech/battle/\n        sdmc:/thextech/graphics/\n        sdmc:/thextech/music/\n        sdmc:/thextech/sound/\n        sdmc:/thextech/worlds/\n        sdmc:/thextech/gameinfo.ini\n        sdmc:/thextech/intro.lvl\n        sdmc:/thextech/music.ini\n        sdmc:/thextech/outro.lvl\n        sdmc:/thextech/sounds.ini\n\n6. Run `TheXTech` application via Homebrew Channel and enjoy.\n\n\n### KNOWN ISSUES\n\n- **The Wii port is currently experimental and not fully tested.**\n- LunaLua/LuaJIT episodes are not supported. (ATWE: A Tiny World Episode immediately comes to mind. The Episode shows in the world list, but upon trying to load the game will gracefully exit.)\n- Background pictures (especially animated) may sometimes fail to load because of insufficient memory.\n- Minor graphical glitches (images sometimes do not line up correctly, leaving gaps)\n- Has several performance issues related to Wii's slow SD card access\n  - Has <1s delays when loading images (will cause in-level stuttering)\n  - Has ~1s delays when storing game saves or settings to the SD card\n\n# TheXTech Standard Readme\n"
  },
  {
    "path": "docs/README_WIIU.md",
    "content": "# About TheXTech & its Nintendo Wii U Port\n\nTheXTech is an open-source rewrite of Super Mario Bros X. in C/++.\nThe original was written in VB6, targeting Windows only and eating up 2-3Gb of resources easily.\n\n## Wii U Installation Guide\n\n0. You are required to use the hacked Wii U.\n\n### Generic way (resources on SD card as a directory)\nThis is a simplest way to get the game work on Wii U, and this way even allows you to customise the stuff on the fly as you do on the PC.\nHOWEVER, with this way, the game LOADS WERY SLOW on hardware because of the how SD card file system works on Wii U.\n\n1. Download and place files `thextech.rpx`, `icon.png`, and `meta.xml` at the \"wiiu/apps/thextech-wiiu\" directory at the root of SD card.\n2. Download one of the game assets packages for TheXTech from here: https://github.com/Wohlstand/TheXTech/wiki/Game-assets-packages\n3. Extract the downloaded archive with all it's content\n4. Created a folder \"wiiu\" in root of your SD card if not exist, and then, inside, make another directory named \"thextech\" (case matters)\n    `sd:/wiiu/thextech`\n5. Move all extracted folders & files from the computer to `sd:/wiiu/thextech`.\n    Upon completion, your folder structure should look like this:\n        sd:/wiiu/thextech/battle/\n        sd:/wiiu/thextech/graphics/\n        sd:/wiiu/thextech/music/\n        sd:/wiiu/thextech/sound/\n        sd:/wiiu/thextech/worlds/\n        sd:/wiiu/thextech/gameinfo.ini\n        sd:/wiiu/thextech/intro.lvl\n        sd:/wiiu/thextech/music.ini\n        sd:/wiiu/thextech/outro.lvl\n        sd:/wiiu/thextech/sounds.ini\n\n6. Run `TheXTech` application via Homebrew Launcher and enjoy.\n\n### Packing into WOHB\nTo run game via this way, you are required to have the `wohbtool` program (a part of DevkitPro: https://github.com/devkitPro/wut-tools).\n\n**Important note:** Currently CEMU doesn't support WOHB files yet (see details: https://github.com/cemu-project/Cemu/issues/962), so, use this way to play on hardware. In CEMU, the Generic way should work good and load fast.\n\n1. Download the `thextech.rpx` file and place it at any convenient place on your PC.\n2. Download one of the game assets packages for TheXTech from here: https://github.com/Wohlstand/TheXTech/wiki/Game-assets-packages\n3. Extract the downloaded archive with all its content into new empty directory. If you want to add custom episodes, do that now: place any custom episodes that you want to play into the \"worlds\" sub-directory at the directory of extracted assets.\n4. Run the `wuhbtool` with next arguments:\n```\nwuhbtool <path-to-RPX-file> <path-to-the-output-WOBH-file> --content=<path-to-assets-directory> --name=\"<Name-of-the-game>\" --short-name=\"<NoTG>\" --icon=<path-to-icon-file> --author=\"Put-the-name-of-game-creator\"\n```\n\n**Where:**\n- **path-to-RPX-file** - an absolute path to the `thextech.rpx` file that you downloaded recently.\n- **path-to-the-output-WOBH-file** - An absolute or relative path to the output filename that will be created. It should end with the \".wohb\" suffix.\n- **Name-of-the-game** - The understandible name of the game. Give it name from an assets name, for example, \"Adventures of Demo\", \"Lowser's Conquest\", etc.\n- **NoTG** - The shortened name of the game, can be just an acronym.\n- **path-to-icon-file** - The absolute path to the icon file that will be used as game icon: Give an absolute path to assets' \"graphics/ui/icon/thextech_128.png\" file that is being used as a game icon of the assets package.\n- **Put-the-name-of-game-creator** - The name of the author who created the game of this assets pack. For example, \"Redigit\" (SMBX), \"Wohlstand\" (AoD), \"Talkhaus\" ('Analogue Funk' or 'Prelude To The Stupid'), \"Sednaiur\" (Lowser's Conquest), etc.\n\nAfter you run the tool with giving proper arguments values, you will get the .wobh file that you can run on your hardware Wii U and have faster loading time.\n\nAnd now, you can try to launch the package throgh the Aroma or something also.\n\nIn addition, there is a note that unlike the generic way, game saves and other stuff appears in different places:\n- Settings and game saves will appear at the system-wide gamesave directory. The name of directory is directly depends on the name of the WOHB file, and if you rename it, the gamesave directory will be changed!\n- If you plugged an SD card or USB stick, the `wiiu/thextech-user/<title-id>` sub-directory will appear on it: here are screenshots and logs will be saved.\n- Inside `wiiu/thextech-user` directory, there are sub-directories with the \"Title-ID\" value (a hexidecial number) that will contain the different stuff depending on currently running assets package.\n\n\n### KNOWN ISSUES\n\n- **The Wii U port is currently experimental and not fully tested.**\n- Has a very long loading when placing all resources on SD card: the file system of SD card is slow. Suggested to make a WUHB package and run it as a monolythig game package.\n- Running the release build on CEMU, you may notice that control gets lost after returning back to the world map. The source of error are bugs in the CEMU's recompiler. To escape this until these bugs gets fixed later, it's need to run the CEMU with the `--force-interpreter` command-line argument.\n- Attempt to change the scaling mode will lead a crash (at least on CEMU, not tested on hardware).\n\n# TheXTech Standard Readme\n"
  },
  {
    "path": "docs/editor.ini",
    "content": "[exit-codes]\nany = \"Any\"\nnone = \"None\"\ncode1 = \"Roulette\"\ncode2 = \"Orb #16\"\ncode3 = \"Leave\"\ncode4 = \"Key\"\ncode5 = \"Orb #41\"\ncode6 = \"Warp\"\ncode7 = \"Collect\"\ncode8 = \"Bar end\"\n\n[blkfam-overworld1]\nname = \"Overworld\"\ncategory = 1\nicon = 63\nblk332-adj = 236\nblk332-slope = 1\nblk332-group = 3\nblk333-adj = 124\nblk333-slope = 1\nblk333-group = 3\nblk335-adj = 896\nblk335-slope = 1\nblk335-group = 3\nblk334-adj = 874\nblk334-slope = 1\nblk334-group = 3\nblk65-adj = 12346789\nblk65-altadj =\nblk65-altgroup = 3\nblk93-adj = 12346789\nblk93-w = 4\nblk93-h = 4\nblk62-adj = 6\nblk63-adj = 46\nblk64-adj = 4\nblk62-group = 1\nblk63-group = 1\nblk64-group = 1\nblk189-adj = 6\nblk190-adj = 46\nblk191-adj = 4\nblk189-group = 2\nblk190-group = 2\nblk191-group = 2\n\n[blkfam-underground1]\nname = \"Underground\"\ncategory = 1\nblk56-adj = 623\nblk56-group = 1\nblk57-adj = 46123\nblk57-group = 1\nblk58-adj = 412\nblk58-group = 1\nblk331-adj = 62389\nblk331-group = 1\nblk323-adj = 46123789\nblk323-group = 1\nblk330-adj = 41278\nblk330-group = 1\nblk324-adj = 236\nblk324-group = 1\nblk324-width = 2\nblk324-slope = 2\nblk325-adj = 214\nblk325-group = 1\nblk325-width = 2\nblk325-slope = 2\nblk326-adj = 236\nblk326-slope = 1\nblk326-group = 2\nblk327-adj = 124\nblk327-slope = 1\nblk327-group = 2\nblk329-adj = 896\nblk329-slope = 1\nblk329-group = 2\nblk328-adj = 874\nblk328-slope = 1\nblk328-group = 2\nblk59-adj = 12346789\nblk59-altadj =\nblk59-altgroup = 2\nblk61-adj = 12346789\nblk61-w = 4\nblk61-h = 4\n\n[blkfam-special1]\nname = \"Special\"\ncategory = 1\nmembers = 193,192,60,188\n\n[blkfam-sized1]\nname = \"Sized\"\ncategory = 1\nmembers = 287\n\n[blkfam-dungeon1]\nname = \"Dungeon\"\ncategory = 1\nblk396-adj = 623\nblk387-adj = 46123\nblk402-adj = 412\nblk395-adj = 62389\nblk394-adj = 46123789\nblk393-adj = 41278\nblk386-adj = 1234689\nblk390-adj = 1234678\nblk385-adj = 46123\nblk385-group = 1\nblk403-adj = 412\nblk403-group = 1\nblk384-adj = 46123789\nblk384-group = 1\nblk383-adj = 41278\nblk383-altadj = 1234678\nblk383-group = 1\nblk400-group = 2\nblk399-group = 2\nblk398-group = 2\nblk401-group = 2\nblk397-group = 3\n\n[blkfam-clouds1]\nname = \"Clouds\"\ncategory = 1\nblk372-group = 2\nblk373-adj = 6\nblk373-group = 1\nblk374-adj = 46\nblk374-group = 1\nblk375-adj = 4\nblk375-group = 1\nblk379-adj = 6\nblk380-adj = 46\nblk381-adj = 46\nblk382-adj = 4\n\n[blkfam-misc1]\nname = \"Misc\"\ncategory = 1\nblk66-adj = 6\nblk67-adj = 46\nblk68-adj = 4\nblk576-adj = 12346\nblk576-group = 1\nblk577-adj = 12346789\nblk577-group = 1\nblk369-group = 2\nblk388-group = 2\nblk69-group = 2\nblk371-adj = 12346\nblk371-group = 3\nblk405-adj = 12346789\nblk405-group = 3\n\n[blkfam-pipes1]\nname = \"Pipes\"\ncategory = 1\nblk196-width = 2\nblk196-adj = 2\nblk196-altadj = 8\nblk197-width = 2\nblk197-adj = 28\nblk376-height = 2\nblk376-adj = 4\nblk376-altadj = 6\nblk377-height = 2\nblk377-adj = 46\nblk378-height = 2\nblk378-adj = 428\nblk378-width = 2\nblk194-width = 2\nblk194-adj = 2\nblk194-altadj = 8\nblk194-group = 1\nblk195-width = 2\nblk195-adj = 28\nblk195-group = 1\n\n[blkfam-overworld2]\nname = \"Overworld\"\ncategory = 2\nicon = 40\nblk39-adj = 623\nblk40-adj = 46123\nblk41-adj = 412\nblk198-adj = 62389\nblk200-adj = 46123789\nblk200-altadj = 4612389\nblk200-altadj = 4612378\nblk199-adj = 41278\nblk341-adj = 236\nblk341-slope = 1\nblk343-adj = 124\nblk343-slope = 1\nblk337-adj = 1234689\nblk337-slope = 1\nblk339-adj = 1234678\nblk339-slope = 1\nblk340-adj = 236\nblk340-slope = 2\nblk340-width = 2\nblk342-adj = 124\nblk342-slope = 2\nblk342-width = 2\nblk336-adj = 1234689\nblk336-slope = 2\nblk336-width = 2\nblk338-adj = 1234678\nblk338-slope = 2\nblk338-width = 2\n\n[blkfam-wood2]\nname = \"Wood\"\ncategory = 2\nblk42-adj = 6\nblk43-adj = 46\nblk44-adj = 4\nblk45-adj = 2\nblk46-adj = 28\n\n[blkfam-underground2]\nname = \"Underground\"\ncategory = 2\nblk207-adj = 623\nblk201-adj = 46123\nblk208-adj = 412\nblk203-adj = 62389\nblk205-adj = 46123789\nblk204-adj = 41278\nblk214-adj = 689\nblk213-adj = 46789\nblk215-adj = 478\nblk211-adj = 62389\nblk211-height = 2\nblk212-adj = 41278\nblk212-height = 2\nblk206-adj = 46123789\nblk206-width = 2\nblk206-height = 2\nblk216-group = 1\nblk216-adj = 46123\n\n[blkfam-special2]\nname = \"Special\"\ncategory = 2\nmembers = 370,293\n\n[blkfam-sized2]\nname = \"Sized\"\ncategory = 2\nmembers = 38,288,243,242\n\n[blkfam-castle2]\nname = \"Castle\"\ncategory = 2\nmembers = 504,502,491,511,498,490,493,494,495,496,492,499,497,500,501,503,506,507,508,510\n\n[blkfam-misc2]\nname = \"Misc\"\ncategory = 2\nmembers = 131,505,509\nblk391-group = 1\nblk391-adj = 6\nblk392-group = 1\nblk392-adj = 46\nblk389-group = 1\nblk389-adj = 4\nblk573-group = 2\nblk573-adj = 2\nblk574-group = 2\nblk574-adj = 28\n\nblk209-group = 3\nblk209-adj = 2\nblk202-group = 3\nblk202-altgroup = 4\nblk202-adj = 28\nblk210-group = 3\nblk210-adj = 8\nblk297-group = 4\nblk297-adj = 2\nblk298-group = 4\nblk298-adj = 8\n\n\n[blkfam-wood3]\nname = \"Wood\"\ncategory = 3\nicon = 3\n\nblk7-adj = 236\nblk3-adj = 12346\nblk6-adj = 124\nblk15-adj = 23689\nblk16-adj = 12346789\nblk17-adj = 12478\nblk275-adj = 478\nblk276-adj = 46789\nblk274-adj = 689\n\n\nblk600-adj = 236\nblk600-slope = 1\n\nblk601-adj = 124\nblk601-slope = 1\n\nblk602-adj = 1234689\nblk602-slope = 1\n\nblk603-adj = 1234678\nblk603-slope = 1\n\n\nblk604-adj = 1236\nblk604-slope = 2\nblk604-width = 2\n\nblk605-adj = 1234\nblk605-slope = 2\nblk605-width = 2\n\nblk606-adj = 1234689\nblk606-slope = 2\n\nblk607-adj = 1234678\nblk607-slope = 2\n\n\nblk1-adj =\nblk1-group = 1\n\nblk92-adj =\nblk92-width = 4\nblk92-height = 4\nblk92-group = 1\n\n\nblk128-adj = 6\nblk128-group = 4\n\nblk127-adj = 46\nblk127-group = 4\n\nblk129-adj = 4\nblk129-group = 4\n\n\nblk595-adj = 6\nblk595-height = 2\nblk595-group = 4\n\nblk596-adj = 46\nblk596-height = 2\nblk596-group = 4\n\nblk597-adj = 4\nblk597-height = 2\nblk597-group = 4\n\n\nblk270-adj = 236\nblk270-group = 2\n\nblk272-adj = 12346\nblk272-group = 2\n\nblk271-adj = 124\nblk271-group = 2\n\n\nblk633-adj =\nblk633-group = 3\n\nblk634-adj =\nblk634-width = 2\nblk634-height = 2\nblk634-group = 3\n\n\n[blkfam-desert3]\nname = \"Desert\"\ncategory = 3\n\nblk162-adj = 236\nblk163-adj = 12346\nblk164-adj = 124\nblk165-adj = 23689\nblk166-adj = 12346789\nblk167-adj = 12478\nblk286-adj = 689\nblk285-adj = 46789\nblk284-adj = 478\n\nblk183-adj = 12346\nblk183-group = 2\n\n\nblk635-adj = 236\nblk635-slope = 1\n\nblk637-adj = 124\nblk637-slope = 1\n\n\nblk636-adj = 1236\nblk636-slope = 2\nblk636-width = 2\n\nblk638-adj = 1234\nblk638-slope = 2\nblk638-width = 2\n\n\nblk94-adj = 236\nblk94-group = 1\nblk95-adj = 12346\nblk95-group = 1\nblk96-adj = 124\nblk96-group = 1\nblk97-adj = 23689\nblk97-group = 1\nblk98-adj = 12346789\nblk98-group = 1\nblk99-adj = 12478\nblk99-group = 1\nblk100-adj = 689\nblk100-group = 1\nblk101-adj = 46789\nblk101-group = 1\nblk102-adj = 478\nblk102-group = 1\n\n\n[blkfam-bonus3]\nname = \"Bonus\"\ncategory = 3\nicon = 52\n\nblk49-adj = 236\nblk52-adj = 12346\nblk610-adj = 124\nblk50-adj = 23689\nblk54-adj = 12346789\nblk47-adj = 12478\nblk608-adj = 689\nblk48-adj = 46789\nblk609-adj = 478\n\nblk611-adj = 1246789\nblk53-adj = 1234689\nblk612-adj = 2346789\nblk51-adj = 1234678\n\n\n[blkfam-grass3]\nname = \"Grass\"\ncategory = 3\nicon = 10\n\nblk9-adj = 236\nblk10-adj = 12346\nblk11-adj = 124\nblk18-adj = 23689\nblk19-adj = 12346789\nblk20-adj = 12478\nblk279-adj = 689\nblk278-adj = 46789\nblk277-adj = 478\n\nblk12-adj = 689\nblk12-group = 1\nblk13-adj = 46789\nblk13-group = 1\nblk14-adj = 478\nblk14-group = 1\n\n\nblk305-adj = 236\nblk305-slope = 1\n\nblk307-adj = 124\nblk307-slope = 1\n\nblk313-adj = 478\nblk313-slope = 1\n\nblk311-adj = 689\nblk311-slope = 1\n\n\nblk306-adj = 1236\nblk306-slope = 2\nblk306-width = 2\n\nblk308-adj = 1234\nblk308-slope = 2\nblk308-width = 2\n\nblk314-adj = 4789\nblk314-slope = 2\nblk314-width = 2\n\nblk312-adj = 6789\nblk312-slope = 2\nblk312-width = 2\n\n\n[blkfam-cave3]\nname = \"Cave\"\ncategory = 3\n\n\nblk344-adj = 236\nblk345-adj = 12346\nblk346-adj = 124\nblk347-adj = 23689\nblk348-adj = 12346789\nblk349-adj = 12478\nblk350-adj = 689\nblk351-adj = 46789\nblk352-adj = 478\n\nblk357-adj = 1236\nblk357-slope = 2\nblk357-width = 2\n\nblk360-adj = 1234\nblk360-slope = 2\nblk360-width = 2\n\nblk364-adj = 4789\nblk364-slope = 2\nblk364-width = 2\n\nblk361-adj = 6789\nblk361-slope = 2\nblk361-width = 2\n\n\nblk358-adj = 236\nblk358-slope = 1\n\nblk359-adj = 124\nblk359-slope = 1\n\nblk363-adj = 478\nblk363-slope = 1\n\nblk362-adj = 689\nblk362-slope = 1\n\nblk353-adj = 1246789\nblk354-adj = 2346789\nblk355-adj = 1234678\nblk356-adj = 1234689\n\n\n[blkfam-house3]\nname = \"House\"\ncategory = 3\n\nblk219-adj = 236\nblk220-adj = 124\nblk221-adj = 689\nblk222-adj = 478\nblk217-adj = 28\nblk218-adj = 46\n\n\n[blkfam-water3]\nname = \"Water\"\ncategory = 3\n\nblk586-adj = 236\nblk587-adj = 12346\nblk588-adj = 124\nblk589-adj = 23689\nblk590-adj = 12346789\nblk591-adj = 12478\nblk592-adj = 689\nblk593-adj = 46789\nblk594-adj = 478\n\nblk580-adj = 236\nblk580-group = 1\nblk581-adj = 12346\nblk581-group = 1\nblk582-adj = 124\nblk582-group = 1\nblk583-adj = 23689\nblk583-group = 1\nblk584-adj = 12346789\nblk584-group = 1\nblk585-adj = 12478\nblk585-group = 1\n\nblk578-adj =\nblk578-group = 2\n\nblk599-adj =\nblk599-width = 2\nblk599-height = 2\nblk599-group = 2\n\nblk598-adj =\nblk598-group = 3\n\n\n[blkfam-pipes3]\nname = \"Pipes\"\ncategory = 3\n\nblk22-adj = 28\nblk22-width = 2\nblk21-adj = 2\nblk21-width = 2\nblk21-adj = 8\nblk21-width = 2\n\nblk24-adj = 46\nblk24-height = 2\nblk23-adj = 4\nblk23-height = 2\nblk23-adj = 6\nblk23-height = 2\n\nblk182-adj =\nblk182-width = 3\nblk182-height = 3\n\nblk31-adj = 6\nblk32-adj = 46\nblk33-adj = 4\n\nblk294-adj = 2\nblk295-adj = 28\nblk296-adj = 8\n\nblk35-adj = 28\nblk35-width = 2\nblk35-group = 1\nblk34-adj = 2\nblk34-width = 2\nblk34-group = 1\nblk34-adj = 8\nblk34-width = 2\nblk34-group = 1\n\nblk570-adj = 46\nblk570-height = 2\nblk570-group = 1\nblk569-adj = 4\nblk569-height = 2\nblk569-group = 1\nblk569-adj = 6\nblk569-height = 2\nblk569-group = 1\n\nblk571-adj =\nblk571-width = 2\nblk571-height = 2\nblk571-group = 1\n\nblk37-adj = 28\nblk37-width = 2\nblk37-group = 2\nblk36-adj = 2\nblk36-width = 2\nblk36-group = 2\nblk36-adj = 8\nblk36-width = 2\nblk36-group = 2\n\nblk104-adj = 28\nblk104-width = 2\nblk104-group = 3\nblk103-adj = 2\nblk103-width = 2\nblk103-group = 3\nblk103-adj = 8\nblk103-width = 2\nblk103-group = 3\n\n\n[blkfam-steampunk3]\nname = \"Steampunk\"\ncategory = 3\n\nblk70-adj = 6\nblk71-adj = 46\nblk72-adj = 4\n\nblk71-altgroup = 2\n\nblk73-adj = 2\nblk74-adj = 28\nblk75-adj = 8\n\nblk76-adj = 46\nblk76-group = 1\n\nblk77-adj = 6\nblk77-group = 2\nblk614-adj = 4\nblk614-group = 2\n\nblk78-adj = 6\nblk78-width = 2\nblk78-group = 2\nblk613-adj = 4\nblk613-width = 2\nblk613-group = 2\n\nblk185-adj = 8\nblk185-width = 4\nblk185-group = 3\n\n\n[blkfam-dungeon3]\nname = \"Dungeon\"\ncategory = 3\nmembers = 630,91,136,126,29\n\n[blkfam-hurts3]\nname = \"Hurts\"\ncategory = 3\nmembers = 406,267,268,269,30,110,109\nblk30-group = 1\nblk30-adj = 12346\nblk406-group = 1\nblk406-adj = 12346789\n\n[blkfam-special3]\nname = \"Special\"\ncategory = 3\nmembers = 2,4,5,55,159,186,224,225,226,620,621\n\n[blkfam-sized3]\nname = \"Sized 3\"\ncategory = 3\nmembers = 579,575,568,130,108,260,240,241,26,25,27,28\n\n[blkfam-misc3]\nname = \"Misc 3\"\ncategory = 3\nmembers = 132,572,457,458,168,8,290,289,280,107,106,105,184,223,187,160,112,111\n\n\n[blkfam-sized4]\nname = \"Sized\"\ncategory = 4\nmembers = 79,161,442,441,259,244,261,245,444,445,443,440,439,437,438\n\n[blkfam-special4]\nname = \"Special\"\ncategory = 4\nmembers = 88,89,90,169,281,282,283,615\n\n[blkfam-grass4]\nname = \"Grass\"\ncategory = 4\nicon = 81\n\nblk80-adj = 623\nblk81-adj = 46123\nblk82-adj = 412\nblk83-adj = 62389\nblk87-adj = 46123789\nblk84-adj = 41278\nblk265-adj = 689\nblk264-adj = 46789\nblk266-adj = 478\n\nblk86-adj = 1234689\nblk86-altslope = 1\nblk85-adj = 1234678\nblk85-altslope = 1\nblk263-adj = 2346789\nblk263-altslope = 1\nblk273-adj = 1246789\nblk273-altslope = 1\n\nblk488-adj = 46123\nblk488-group = 1\nblk489-adj = 46123789\nblk489-group = 1\n\nblk618-adj = 1234689\nblk618-slope = 2\nblk619-adj = 1234678\nblk619-slope = 2\n\nblk304-adj = 1234689\nblk304-slope = 4\nblk304-width = 4\nblk303-adj = 1234678\nblk303-slope = 4\nblk303-width = 4\n\nblk299-adj = 236\nblk299-slope = 1\n\nblk300-adj = 124\nblk300-slope = 1\n\nblk616-adj = 236\nblk616-slope = 2\nblk616-width = 2\n\nblk617-adj = 124\nblk617-slope = 2\nblk617-width = 2\n\nblk302-adj = 236\nblk302-slope = 4\nblk302-width = 4\n\nblk301-adj = 124\nblk301-slope = 4\nblk301-width = 4\n\nblk309-adj = 896\nblk309-slope = 1\n\nblk310-adj = 784\nblk310-slope = 1\n\n\n[blkfam-cave4]\nname = \"Cave\"\ncategory = 4\n\nblk246-adj = 236\nblk250-adj = 12346\nblk247-adj = 124\nblk252-adj = 23689\nblk251-adj = 12346789\nblk253-adj = 12478\nblk248-adj = 689\nblk254-adj = 46789\nblk249-adj = 478\n\nblk258-adj = 1234689\nblk255-adj = 2346789\nblk256-adj = 1246789\nblk257-adj = 1234678\n\n\nblk262-adj = 12346789\nblk262-width = 4\nblk262-height = 4\n\n\nblk316-adj = 236\nblk316-slope = 1\n\nblk315-adj = 124\nblk315-slope = 1\n\nblk318-adj = 478\nblk318-slope = 1\n\nblk317-adj = 689\nblk317-slope = 1\n\n\nblk365-adj = 1236\nblk365-slope = 2\nblk365-width = 2\n\nblk366-adj = 1234\nblk366-slope = 2\nblk366-width = 2\n\nblk367-adj = 4789\nblk367-slope = 2\nblk367-width = 2\n\nblk368-adj = 6789\nblk368-slope = 2\nblk368-width = 2\n\n\nblk321-adj = 1236\nblk321-slope = 4\nblk321-width = 4\n\nblk319-adj = 1234\nblk319-slope = 4\nblk319-width = 4\n\nblk322-adj = 1234689\nblk322-slope = 4\nblk322-width = 4\n\nblk320-adj = 1234678\nblk320-slope = 4\nblk320-width = 4\n\n\n[blkfam-bonus4]\nname = \"Bonus\"\ncategory = 4\nblk227-adj = 623\nblk228-adj = 46123\nblk229-adj = 412\nblk230-adj = 62389\nblk231-adj = 46123789\nblk232-adj = 41278\nblk233-adj = 689\nblk234-adj = 46789\nblk235-adj = 478\n\nblk238-adj = 1234689\nblk239-adj = 1234678\nblk237-adj = 2346789\nblk236-adj = 1246789\n\n[blkfam-woods4]\nname = \"Woods\"\ncategory = 4\nblk116-adj = 623\nblk119-adj = 46123\nblk117-adj = 412\nblk120-adj = 62389\nblk118-adj = 41278\n\n[blkfam-misc4]\nname = \"Misc\"\ncategory = 4\nmembers = 115,446,447,448\nblk133-adj = 6\nblk133-group = 1\nblk134-adj = 46\nblk134-group = 1\nblk135-adj = 4\nblk135-group = 1\n\n[blkfam-lava4]\nname = \"Lava and Hurts\"\ncategory = 4\nicon = 459\n\nblk466-adj = 623\nblk459-adj = 46123\nblk460-adj = 412\nblk463-adj = 62389\nblk467-adj = 46123789\nblk461-adj = 41278\nblk465-adj = 689\nblk462-adj = 46789\nblk464-adj = 478\n\nblk471-adj = 1234689\nblk468-adj = 1234678\nblk470-adj = 2346789\nblk469-adj = 1246789\n\nblk404-adj = 46123\nblk404-group = 1\nblk420-adj = 46123789\nblk420-group = 1\n\nblk481-adj = 1234689\nblk481-slope = 1\nblk483-adj = 1234678\nblk483-slope = 1\nblk487-adj = 2346789\nblk487-slope = 1\nblk484-adj = 1246789\nblk484-slope = 1\n\nblk473-adj = 1234689\nblk473-slope = 2\nblk475-adj = 1234678\nblk475-slope = 2\nblk477-adj = 2346789\nblk477-slope = 2\nblk478-adj = 1246789\nblk478-slope = 2\n\nblk480-adj = 236\nblk480-slope = 1\n\nblk482-adj = 124\nblk482-slope = 1\n\nblk472-adj = 236\nblk472-slope = 2\nblk472-width = 2\n\nblk474-adj = 124\nblk474-slope = 2\nblk474-width = 2\n\nblk486-adj = 896\nblk486-slope = 1\n\nblk485-adj = 784\nblk485-slope = 1\n\nblk476-adj = 896\nblk476-slope = 2\nblk476-width = 2\n\nblk479-adj = 784\nblk479-slope = 2\nblk479-width = 2\n\nblk408-adj = 2\nblk408-group = 2\nblk409-adj = 28\nblk409-group = 2\nblk407-adj = 8\nblk407-group = 2\n\nblk429-group = 3\nblk428-group = 3\nblk430-group = 3\nblk431-group = 3\n\n[blkfam-mansion4]\nname = \"Mansion\"\ncategory = 4\nicon = 122\n\nblk121-adj = 6\nblk122-adj = 46\nblk123-adj = 4\nblk124-group = 2\nblk125-group = 2\nblk125-width = 2\nblk125-height = 2\n\n[blkfam-switch4]\nname = \"Switch\"\ncategory = 4\nmembers = 170,171,172,173,174,175,176,177,178,179,180,181\n\n[blkfam-castle4]\nname = \"Castle\"\ncategory = 4\nblk425-adj = 623\nblk424-adj = 46123\nblk426-adj = 412\nblk423-adj = 62389\nblk422-adj = 46123789\nblk421-adj = 41278\nblk427-adj = 689\nblk419-adj = 46789\nblk436-adj = 478\n\nblk415-adj = 1234689\nblk416-adj = 1234678\nblk417-adj = 2346789\nblk418-adj = 1246789\n\nblk450-adj = 1234689\nblk450-slope = 1\nblk449-adj = 1234678\nblk449-slope = 1\n\nblk452-adj = 236\nblk452-slope = 1\n\nblk451-adj = 124\nblk451-slope = 1\n\nblk453-group = 2\nblk454-group = 2\nblk456-group = 2\nblk455-group = 2\n\nblk435-adj = 623\nblk435-group = 1\nblk414-adj = 46123\nblk414-group = 1\nblk434-adj = 412\nblk434-group = 1\nblk412-adj = 62389\nblk412-group = 1\nblk413-adj = 46123789\nblk413-group = 1\nblk411-adj = 41278\nblk411-group = 1\nblk433-adj = 689\nblk433-group = 1\nblk410-adj = 46789\nblk410-group = 1\nblk432-adj = 478\nblk432-group = 1\n\n[blkfam-pipes4]\nname = \"Pipes\"\ncategory = 4\nblk138-width = 2\nblk138-adj = 2\nblk138-altadj = 8\nblk143-width = 2\nblk143-adj = 28\n\nblk147-height = 2\nblk147-adj = 4\nblk147-altadj = 6\nblk153-height = 2\nblk153-adj = 46\n\nblk139-width = 2\nblk139-adj = 2\nblk139-altadj = 8\nblk139-group = 1\nblk144-width = 2\nblk144-adj = 28\nblk144-group = 1\n\nblk148-height = 2\nblk148-adj = 4\nblk148-altadj = 6\nblk148-group = 1\nblk154-height = 2\nblk154-adj = 46\nblk154-group = 1\n\nblk140-width = 2\nblk140-adj = 2\nblk140-altadj = 8\nblk140-group = 2\nblk145-width = 2\nblk145-adj = 28\nblk145-group = 2\n\nblk149-height = 2\nblk149-adj = 4\nblk149-altadj = 6\nblk149-group = 2\nblk155-height = 2\nblk155-adj = 46\nblk155-group = 2\n\nblk141-width = 2\nblk141-adj = 2\nblk141-altadj = 8\nblk141-group = 3\nblk146-width = 2\nblk146-adj = 28\nblk146-group = 3\n\nblk150-height = 2\nblk150-adj = 4\nblk150-altadj = 6\nblk150-group = 3\nblk156-height = 2\nblk156-adj = 46\nblk156-group = 3\n\nblk137-width = 2\nblk137-adj = 2\nblk137-altadj = 8\nblk137-group = 4\nblk142-width = 2\nblk142-adj = 28\nblk142-group = 4\n\nblk151-height = 2\nblk151-adj = 4\nblk151-altadj = 6\nblk151-group = 4\nblk157-height = 2\nblk157-adj = 46\nblk157-group = 4\n\nblk113-width = 2\nblk113-adj = 2\nblk113-altadj = 8\nblk113-group = 5\nblk114-width = 2\nblk114-adj = 28\nblk114-group = 5\n\nblk152-height = 2\nblk152-adj = 4\nblk152-altadj = 6\nblk152-group = 5\nblk158-height = 2\nblk158-adj = 46\nblk158-group = 5\n\n[blkfam-bricks5]\nname = \"Brick\"\ncategory = 5\nblk291-adj = 12346\nblk292-adj = 12346789\n\n[blkfam-metal5-1]\nname = \"Plates\"\ncategory = 5\nblk515-adj = 6\nblk513-adj = 46\nblk533-adj = 4\nblk517-adj = 6\nblk517-group = 1\nblk514-adj = 46\nblk514-group = 1\nblk518-adj = 4\nblk518-group = 1\n\n\n\n[blkfam-pipes5-2]\nname = \"Pipes\"\ncategory = 5\nblk564-adj = 2\nblk555-adj = 28\nblk552-adj = 8\n\nblk557-adj = 2\nblk557-group = 1\nblk558-adj = 28\nblk558-group = 1\nblk566-adj = 8\nblk566-group = 1\n\n\n[blkfam-pipes5-3]\nname = \"Long Pipes\"\ncategory = 5\nblk561-adj = 28\nblk561-altadj = 2\nblk561-altadj = 8\nblk563-adj = 4\nblk563-group = 1\nblk559-adj = 6\nblk551-adj = 46\nblk560-adj = 4\n\nblk550-adj = 62\nblk556-adj = 24\nblk553-adj = 268\nblk554-adj = 248\nblk565-adj = 68\nblk567-adj = 48\n\nblk546-adj = 2468\nblk546-altadj =\n\nblk516-adj = 2\nblk516-group = 2\nblk532-adj = 28\nblk532-group = 2\nblk531-adj = 8\nblk531-group = 2\n\n\n[blkfam-metal5-3]\nname = \"Misc\"\ncategory = 5\nicon = 562\nmembers = 512,539,538,542,543,544,545,547,548,549,562,534,535,536,537,540,541\n\n[blkfam-ruins5]\nname = \"Ruins\"\ncategory = 5\nmembers = 524,525,526,530,527,529\nblk528-adj = 869\nblk528-group = 1\nblk519-adj = 2346789\nblk519-altadj = 46789\nblk519-group = 1\nblk523-adj = 847\nblk523-group = 1\nblk522-adj = 1246789\nblk522-altadj = 46789\nblk522-group = 1\nblk520-adj = 12346789\nblk520-group = 1\nblk521-adj =\nblk521-altadj = 12346789\nblk521-group = 1\n\n[blkfam-char5]\nname = \"Char\"\ncategory = 5\nmembers = 622,626,623,627,624,628,625,629,631,632\n\n\n\n[bgofam-fences1]\nname = \"Fences\"\ncategory = 1\nmembers = 23,24,25,86,129,130,131,147\n\n[bgofam-hut1]\nname = \"Hut\"\ncategory = 1\nicon = 16\nmembers = 16,17\n\n[bgofam-plants1]\nname = \"Plants\"\ncategory = 1\nmembers = 21,22,127,128,18,20,84,85,19\n\nbgo21-group = 1\nbgo21-adj = 2\nbgo22-group = 1\nbgo22-adj = 28\n\n[bgofam-water1]\nname = \"Water\"\ncategory = 1\nmembers = 168,26,169,164\nbgo168-adj = 46123\nbgo169-adj = 46123789\nbgo26-group = 1\nbgo26-adj = 46123\nbgo164-group = 1\nbgo164-adj = 46123789\n\n[bgofam-clouds1]\nname = \"Clouds\"\ncategory = 1\nmembers = 161\n\n\n[bgofam-art2]\nname = \"Art\"\ncategory = 2\nmembers = 148,149,150\n\n[bgofam-doors2]\nname = \"Doors\"\ncategory = 2\nicon = 88\nmembers = 87,88\n\n[bgofam-plants2]\nname = \"Plants\"\ncategory = 2\nmembers = 111,112,113,110,62,108,109,63\n\n[bgofam-water2]\nname = \"Water\"\ncategory = 2\nbgo159-adj = 46123\nbgo158-adj = 46123789\n\n\n[bgofam-structures3]\nname = \"Structures\"\ncategory = 3\nicon = 103\nmembers = 89,94,80,67,81,46,91,95,106,101,102,45,90,96,36,68,69,93,97,99,162,163,103,107\nbgo89-group = 1\nbgo89-adj = 2\nbgo91-group = 1\nbgo91-adj = 28\nbgo90-group = 1\nbgo90-adj = 8\n\nbgo94-group = 2\nbgo94-adj = 2\nbgo95-group = 2\nbgo95-adj = 28\nbgo96-group = 2\nbgo96-adj = 8\n\nbgo46-group = 3\nbgo46-adj = 28\nbgo45-group = 3\nbgo45-adj = 8\n\nbgo80-group = 4\nbgo80-adj = 6\nbgo67-group = 4\nbgo67-adj = 46\nbgo81-group = 4\nbgo81-adj = 4\n\n\n[bgofam-exit3]\nname = \"Exit\"\ncategory = 3\nmembers = 33,34,13,61,60,12,11\n\n[bgofam-tiles3]\nname = \"Tiles\"\ncategory = 3\nmembers = 75,76,77,78,79,40,39,64,15,14\n\n[bgofam-plants3]\nname = \"Plants\"\ncategory = 3\nmembers = 1,2,3,4,5,6,7,8,9,10,114,37,38\n\n[bgofam-water3]\nname = \"Water\"\ncategory = 3\nmembers = 172,170,65,82,66,171,165,83\n\nbgo172-adj = 12346\nbgo66-adj = 12346789\n\nbgo170-group = 1\nbgo170-adj = 12346\nbgo171-group = 1\nbgo171-adj = 12346789\n\nbgo65-group = 2\nbgo65-adj = 12346\nbgo165-group = 2\nbgo165-adj = 12346789\n\nbgo82-group = 3\nbgo82-adj = 12346\nbgo83-group = 3\nbgo83-adj = 12346789\n\n[bgofam-sandbg3]\nname = \"Sand BG\"\ncategory = 3\nbgo189-adj = 12346\nbgo190-adj = 12346789\n\n[bgofam-sandfg3]\nname = \"FG\"\ncategory = 3\nbgo188-adj = 12346\nbgo187-adj = 12346789\n\n\n[bgofam-fences4]\nname = \"Fences\"\ncategory = 4\nicon = 179\n\nbgo178-adj = 623\nbgo179-adj = 46123\nbgo180-adj = 412\nbgo181-adj = 62389\nbgo182-adj = 46123789\nbgo183-adj = 41278\nbgo184-adj = 689\nbgo185-adj = 46789\nbgo186-adj = 478\n\nbgo177-adj = 1234689\nbgo176-adj = 1234678\nbgo175-adj = 2346789\nbgo174-adj = 1246789\n\n[bgofam-bars4]\nname = \"Bars\"\ncategory = 4\nmembers = 134,138,142,143\n\nbgo136-adj = 2\nbgo134-adj = 28\n\nbgo135-adj = 28\nbgo135-group = 4\n\nbgo138-group = 1\nbgo138-adj = 2\nbgo137-group = 1\nbgo137-adj = 28\n\nbgo142-group = 2\nbgo142-adj = 2\nbgo144-group = 2\nbgo144-adj = 28\n\nbgo143-group = 3\nbgo143-adj = 2\nbgo145-group = 3\nbgo145-adj = 28\n\n\n[bgofam-plants4]\nname = \"Plants\"\ncategory = 4\nmembers = 59,52,53,54,55,56,57,58,32,31,28,27\n\n[bgofam-ghost4]\nname = \"Ghost\"\ncategory = 4\nmembers = 146,140,139,43,44,47,48,49,50,51\n\nbgo43-group = 2\nbgo43-adj = 2\nbgo44-group = 2\nbgo44-adj = 28\n\n[bgofam-water4]\nname = \"Water\"\ncategory = 4\nmembers = 173\nbgo166-adj = 12346\nbgo167-adj = 12346789\n\n[bgofam-misc4]\nname = \"Misc\"\ncategory = 4\nmembers = 133,132,29,42,41,30\n\n[bgofam-platform4]\nname = \"Plat\"\ncategory = 4\nmembers = 71,72,70,73,74,100\n\n[bgofam-doors4]\nname = \"Doors\"\ncategory = 4\nmembers = 141,104,105,92\n\n[bgofam-key4]\nname = \"Key\"\ncategory = 4\nicon = 35\n\nmembers = 35\n\n\n[bgofam-space5]\nname = \"Space Bug\"\ncategory = 5\nmembers = 153,155,154,151,156,157,152\n\nbgo153-group = 1\nbgo153-adj = 2\nbgo151-group = 1\nbgo151-adj = 28\nbgo152-group = 1\nbgo152-adj = 8\n\n[bgofam-temple5]\nname = \"Temple\"\ncategory = 5\nicon = 126\nmembers = 122,121,115,116,117,123,119,118,126,125,124,120\n\nbgo122-group = 1\nbgo122-adj = 2\nbgo123-group = 1\nbgo123-adj = 28\nbgo124-group = 1\nbgo124-adj = 8\n\nbgo115-group = 2\nbgo115-adj = 6\nbgo116-group = 2\nbgo116-adj = 46\nbgo117-group = 2\nbgo117-adj = 4\n\nbgo119-group = 3\nbgo119-adj = 28\nbgo120-group = 3\nbgo120-adj = 8\n\n\n[npcfam-enemies1]\nname = \"Enemies\"\nmembers = 28,233,235,260,177,176,175,173,153,93,89,29,27\ncategory = 1\nicon = 89\n\n[npcfam-vine1]\nname = \"Vine\"\nmembers = 223,222\ncategory = 1\n\n[npcfam-shell1]\nname = \"Shell\"\nmembers = 174,172\ncategory = 1\n\n[npcfam-plat1]\nname = \"Plat\"\nmembers = 106\ncategory = 1\n\n[npcfam-items1]\nname = \"Items\"\nmembers = 186,182,184,178,88\ncategory = 1\n\n[npcfam-boss1]\nname = \"Boss\"\nmembers = 200\ncategory = 1\n\n\n[npcfam-warp2]\nname = \"Warp\"\nmembers = 289,288\ncategory = 2\n\n[npcfam-vine2]\nname = \"Vine\"\nmembers = 215,216,217,218,219,220,221\ncategory = 2\n\n[npcfam-veg2]\nname = \"Veg\"\nmembers = 147,146,145,144,143,141,140,139,142,92\ncategory = 2\n\n[npcfam-blocks2]\nname = \"Blocks\"\nmembers = 157,156,155,154\ncategory = 2\n\n[npcfam-items2]\nname = \"Items\"\nmembers = 249,241,240,138,134\ncategory = 2\n\n[npcfam-exit2]\nname = \"Exit\"\nmembers = 41\ncategory = 2\n\n[npcfam-boss2]\nname = \"Boss\"\nmembers = 262,201,39\ncategory = 2\n\n[npcfam-enemies2]\nname = \"Enemies\"\nmembers = 272,247,206,135,132,131,130,129,25,19,20\ncategory = 2\nicon = 20\n\n[npcfam-vine3]\nname = \"Vine\"\nmembers = 226,225,214,213\ncategory = 3\n\n[npcfam-exit3]\nname = \"Exit\"\nmembers = 11,16,97\ncategory = 3\n\n[npcfam-plat3]\nname = \"Plat\"\nmembers = 212,57,46,104\ncategory = 3\n\n[npcfam-char3]\nname = \"Char\"\nmembers = 198,101,75,94\ncategory = 3\n\n[npcfam-plant3]\nname = \"Plant\"\nmembers = 261,245,74,52,8,51\ncategory = 3\n\n[npcfam-shell3]\nname = \"Shell\"\nmembers = 5,7,24,73\ncategory = 3\n\n[npcfam-blocks3]\nname = \"Blocks\"\nmembers = 45,160,84,21,79,80,83,82,78,81,70,69,68,58,67\ncategory = 3\n\n[npcfam-boss3]\nname = \"Boss\"\nmembers = 267,86,15\ncategory = 3\nicon = 15\n\n[npcfam-items3]\nname = \"Items\"\nmembers = 287,273,264,238,248,49,193,191,170,169,103,34,90,35,22,14,10,9\ncategory = 3\n\n[npcfam-enemies3]\nname = \"Enemies\"\nmembers = 259,244,161,137,136,76,72,71,54,53,48,47,38,37,36,23,17,12,6,2,1,3,4,231,230,229\ncategory = 3\nicon = 1\n\n[npcfam-boss4]\nname = \"Boss\"\nmembers = 280\ncategory = 4\n\n[npcfam-vine4]\nname = \"Vine\"\nmembers = 227,224\ncategory = 4\n\n[npcfam-ckpt4]\nname = \"Ckpt\"\ncategory = 4\nmembers = 192\n\n[npcfam-sign4]\nname = \"Sign\"\ncategory = 4\nmembers = 151\n\n[npcfam-pet4]\nname = \"Pet\"\nmembers = 228,150,149,148,95,98,99,100\ncategory = 4\n\n[npcfam-plat4]\nname = \"Plat\"\nmembers = 190,105,66,64,62,60\ncategory = 4\n\n[npcfam-exit4]\nname = \"Exit\"\nmembers = 196,197,31\ncategory = 4\nicon = 31\n\n[npcfam-items4]\nname = \"Items\"\nmembers = 279,278,277,274,258,239,195,188,187,183,185,96,56,33,32,26\ncategory = 4\n\n[npcfam-enemies4]\nname = \"Enemies\"\nmembers = 236,232,234,286,285,275,271,270,207,199,189,179,181,180,167,166,165,164,163,162,77,43,42,44,18,194,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109\ncategory = 4\nicon = 165\n\n[npcfam-boss5]\nname = \"Boss\"\nmembers = 209,208\ncategory = 5\n\n[npcfam-switch5]\nname = \"Switch\"\nmembers = 65,63,61,59\ncategory = 5\n\n[npcfam-items5]\nname = \"Items\"\nmembers = 255,254,253,252,251,250,158,152\ncategory = 5\n\n[npcfam-enemies5]\nname = \"Enemies\"\nmembers = 257,256,243,242,211,205,204,203,168,128,127,126,125\ncategory = 5\nicon = 242\n\n[npcfam-char5]\nname = \"Char\"\nmembers = 107,102\ncategory = 5\n\n\n[tilefam-grass1]\nname = \"Old Grass\"\ncategory = 1\ntile20-adj = 623\ntile21-adj = 46123\ntile22-adj = 412\ntile23-adj = 62389\ntile11-adj = 46123789\ntile24-adj = 41278\ntile25-adj = 689\ntile26-adj = 46789\ntile17-adj = 478\n\ntile19-adj = 1234689\ntile18-adj = 1234678\ntile16-adj = 2346789\ntile15-adj = 1246789\n\ntile40-adj =\ntile41-group = 1\ntile12-adj = 46123789\ntile12-width = 2\ntile12-height = 2\ntile13-adj = 46123789\ntile13-width = 2\ntile13-height = 2\n\n\n[tilefam-desert1]\nname = \"Dust\"\ncategory = 1\nicon = 1\ntile28-adj = 623\ntile32-adj = 46123\ntile29-adj = 412\ntile33-adj = 62389\ntile1-adj = 46123789\ntile35-adj = 41278\ntile30-adj = 689\ntile34-adj = 46789\ntile31-adj = 478\n\ntile38-adj = 1234689\ntile39-adj = 1234678\ntile37-adj = 2346789\ntile36-adj = 1246789\n\ntile8-adj = 46123789\ntile8-width = 2\ntile8-height = 2\ntile9-adj = 46123789\ntile9-width = 2\ntile9-height = 2\n\ntile42-adj =\ntile42-group = 1\ntile6-adj = 46\ntile6-group = 1\ntile7-adj = 28\ntile7-group = 1\ntile5-adj = 26\ntile5-group = 1\ntile3-adj = 24\ntile3-group = 1\ntile2-adj = 86\ntile2-group = 1\ntile4-adj = 48\ntile4-group = 1\n\n\n[tilefam-snow1]\nname = \"Old Snow\"\ncategory = 1\ntile48-adj = 623\ntile53-adj = 46123\ntile49-adj = 412\ntile52-adj = 62389\ntile10-adj = 46123789\ntile54-adj = 41278\ntile50-adj = 689\ntile55-adj = 46789\ntile51-adj = 478\n\ntile46-adj = 1234689\ntile44-adj = 1234678\ntile45-adj = 2346789\ntile43-adj = 1246789\n\ntile47-adj =\n\n\n[tilefam-grass2]\nname = \"Happy Grass\"\ncategory = 2\nicon = 81\n\npod1-members = 79,80,81,82,83,67,56,73,69,66,72,57,89,102,61,70,74,68,76,59,65,71,62,64,58\npod1-width = 5\npod2-members = 92,93,90,91,106,105,96,94,104,103\npod2-width = 2\n\n[tilefam-dirt2]\nname = \"Dead Grass\"\ncategory = 2\npod1-members = 137,144,126,125,132,122,114,134,119,121,127,112,143,135,116,145,138,118,133,115,120,128,117,111,113\npod1-width = 5\npod2-members = 139,141,136,142,123,124,131,140,129,130\npod2-width = 2\n\n[tilefam-wall2]\nname = \"Dirt Wall\"\ncategory = 2\nmembers = 95,240,14,27\npod1-members = 77,63,60,78,75,84,85,86,87,88\npod1-width = 5\npod2-members = 97,98,101,99,100,109,107,108,110\npod2-width = 3\n\n[tilefam-granite3]\nname = \"Granite\"\ncategory = 3\nicon = 256\npod1-members = 255,251,256,254,253,216,233,207,221,227,213,214,225,204,215,206,210,232,224,203,234,223,201,226,200,208,212,202,211,209\npod1-width = 5\npod2-members = 252,244,229,150,228,231,219,218,220,230,217,222\npod2-width = 2\n\n[tilefam-marble3]\nname = \"Marble\"\ncategory = 3\npod1-members = 238,239,235,237,236,170,171,180,172,173,161,154,157,164,159,152,155,179,197,147,165,158,162,167,163,156,151,148,153,160\npod1-width = 5\npod2-members = 0,0,196,182,181,183,189,198,185,184,191,190\npod2-width = 2\n\n[tilefam-stonewall3]\nname = \"Stone Wall\"\ncategory = 3\nmembers = 192,193,199,205,195\npod1-members = 168,149,146,169,166,174,175,176,177,178,250,249,248,247,246\npod1-width = 5\npod2-members = 186,187,188,194,243,245\npod2-width = 2\n\n[tilefam-lava3]\nname = \"Lava\"\ncategory = 3\nmembers = 242,241,257,261,260,259,262,258,264,263\n\n[tilefam-clouds4]\nname = \"Clouds\"\ncategory = 4\nicon = 284\npod1-members = 318,319,323,320,322,279,285,284,291,288,272,268,298,278,271,280,267,286,302,283,289,287,273,275,265,270,281,290,269,266\npod1-width = 5\npod2-members = 324,321,293,309,292,294,306,314,304,305,313,312\npod2-width = 2\n\n[tilefam-lilac4]\nname = \"Lilac Wall\"\ncategory = 4\npod1-members = 277,307,276,282,274,296,300,299,308,310,297,303,326,325,327\npod1-width = 5\npod2-members = 301,295,311,315,316,317,328\npod2-width = 3\n\n[bg2fam-set1]\nname = \"Set 1\"\nsort-index = 1\nmembers = 7,8,9,10,41,50,51\nbg2-7-name = \"Underground\"\nbg2-8-name = \"Night\"\nbg2-9-name = \"Night 2\"\nbg2-10-name = \"Overworld\"\nbg2-41-name = \"Castle\"\nbg2-50-name = \"Mushrooms\"\nbg2-51-name = \"Desert\"\n\n[bg2fam-set2]\nname = \"Set 2\"\nsort-index = 2\nmembers = 5,25,44,48,49,52,53,54,57\nbg2-5-name = \"Trees\"\nbg2-25-name = \"Underground\"\nbg2-44-name = \"Castle\"\nbg2-48-name = \"Clouds\"\nbg2-49-name = \"Night - Hills\"\nbg2-52-name = \"Night - Desert\"\nbg2-53-name = \"Cliff\"\nbg2-54-name = \"Warehouse\"\nbg2-57-name = \"Dungeon\"\n\n[bg2fam-set3]\nname = \"Set 3\"\nsort-index = 3\nmembers = 1,2,3,4,6,13,14,15,17,20,21,22,23,24,26,27,35,36,37,38,39,56\nbg2-1-name = \"Blocks\"\nbg2-2-name = \"Hills\"\nbg2-3-name = \"Dungeon\"\nbg2-4-name = \"Pipes\"\nbg2-6-name = \"Bonus\"\nbg2-13-name = \"Clouds\"\nbg2-14-name = \"Desert\"\nbg2-15-name = \"Dungeon 2\"\nbg2-17-name = \"Ship\"\nbg2-20-name = \"Forest\"\nbg2-21-name = \"Battle\"\nbg2-22-name = \"Waterfall\"\nbg2-23-name = \"Tanks\"\nbg2-24-name = \"Final Boss\"\nbg2-26-name = \"Shroom Dealer\"\nbg2-27-name = \"Castle\"\nbg2-35-name = \"Snow Trees\"\nbg2-36-name = \"Clouds 2\"\nbg2-37-name = \"Snow Hills\"\nbg2-38-name = \"Cave\"\nbg2-39-name = \"Cave 2\"\nbg2-56-name = \"Underwater\"\n\n[bg2fam-set4]\nname = \"Set 4\"\nsort-index = 4\nmembers = 12,18,19,28,29,30,31,11,32,34,33,42,43,55,58\nbg2-12-name = \"Trees\"\nbg2-18-name = \"Mansion\"\nbg2-19-name = \"Forest\"\nbg2-28-name = \"Bonus\"\nbg2-29-name = \"Night\"\nbg2-30-name = \"Cave\"\nbg2-31-name = \"Clouds\"\nbg2-11-name = \"Hills\"\nbg2-32-name = \"Hills 2\"\nbg2-34-name = \"Hills 3\"\nbg2-33-name = \"Hills 4\"\nbg2-42-name = \"Castle\"\nbg2-43-name = \"Castle 2\"\nbg2-55-name = \"Underwater\"\nbg2-58-name = \"Desert Night\"\n\n[bg2fam-misc]\nname = \"Misc.\"\nsort-index = 5\nmembers = 47,46,45,16,40\nbg2-47-name = \"Space Base\"\nbg2-46-name = \"Space Ship\"\nbg2-45-name = \"Space Swamp\"\nbg2-16-name = \"Space Crater\"\nbg2-40-name = \"Secret Mine\"\n\n[sndfam-player]\nname = \"Player\"\nsort-index = 1\nsnd1-name = \"Jump\"\nsnd2-name = \"Stomp\"\nsnd5-name = \"Shrink\"\nsnd6-name = \"Grow\"\nsnd10-name = \"Skid\"\nsnd23-name = \"Grab\"\nsnd25-name = \"Hammer Toss\"\nsnd71-name = \"Climbing\"\nsnd72-name = \"Swim\"\nsnd73-name = \"Light Grab\"\nsnd75-name = \"Throw Veggie\"\nsnd76-name = \"Lose Heart\"\nsnd33-name = \"Tail\"\nsnd17-name = \"Warp\"\nsnd24-name = \"Spring\"\nsnd46-name = \"Door\"\nsnd54-name = \"Player Died 2\"\nsnd15-name = \"1up\"\nsnd12-name = \"Got Item\"\nsnd34-name = \"Racoon\"\nsnd18-name = \"Fireball\"\nsnd35-name = \"Lose Boot\"\n\n[sndfam-item]\nname = \"Item\"\nsort-index = 2\nsnd3-name = \"Block Hit\"\nsnd4-name = \"Block Smashed\"\nsnd7-name = \"Powerup\"\nsnd9-name = \"Shell Kick\"\nsnd14-name = \"Coin\"\nsnd16-name = \"Lava\"\nsnd22-name = \"Bullet\"\nsnd32-name = \"Switch\"\nsnd37-name = \"Crusher\"\nsnd56-name = \"Ring\"\nsnd57-name = \"Skeleton\"\nsnd58-name = \"Checkpoint\"\nsnd59-name = \"Collectible\"\nsnd61-name = \"Lava Creature\"\nsnd42-name = \"Big Fireball\"\nsnd74-name = \"Saw\"\nsnd43-name = \"Fireworks\"\nsnd64-name = \"Space Block Hit\"\nsnd65-name = \"Space NPC Kill\"\nsnd66-name = \"Space NPC Hurt\"\nsnd51-name = \"Egg Hatch\"\n\n\n[sndfam-boss]\nname = \"Boss\"\nsort-index = 3\nsnd38-name = \"Bird Spit\"\nsnd39-name = \"Bird Hit\"\nsnd41-name = \"Bird Beat\"\nsnd44-name = \"Turtle Killed\"\nsnd62-name = \"Frog Bubbles\"\nsnd63-name = \"Frog Killed\"\nsnd67-name = \"Glass Break\"\nsnd68-name = \"Space Boss Hit\"\nsnd69-name = \"Space Boss Cry\"\nsnd70-name = \"Space Boss Kill\"\n\n\n[sndfam-jingle]\nname = \"Jingle\"\nsort-index = 4\nsnd8-name = \"Player Died\"\nsnd19-name = \"Roulette Exit\"\nsnd20-name = \"Defeat Boss\"\nsnd21-name = \"Dungeon Clear\"\nsnd31-name = \"Key Exit\"\nsnd40-name = \"Ball Exit\"\nsnd45-name = \"Game Beat\"\nsnd52-name = \"Got Star Exit\"\nsnd60-name = \"Bar Exit\"\n\n\n[sndfam-world]\nname = \"World\"\nsort-index = 5\nsnd27-name = \"New Path\"\nsnd28-name = \"Level Select\"\n\n\n[sndfam-ui]\nname = \"UI\"\nsort-index = 6\nsnd11-name = \"Drop Item\"\nsnd13-name = \"Camera\"\nsnd26-name = \"Slide\"\nsnd29-name = \"Do\"\nsnd30-name = \"Pause\"\nsnd47-name = \"Message\"\n\n\n[sndfam-hero]\nname = \"Hero\"\nsort-index = 7\nsnd53-name = \"Hero NPC Kill\"\nsnd77-name = \"Hero Stab\"\nsnd78-name = \"Hero Hurt\"\nsnd79-name = \"Get Heart\"\nsnd80-name = \"Hero Died\"\nsnd81-name = \"Get Gem\"\nsnd82-name = \"Hero Fire\"\nsnd83-name = \"Hero Item\"\nsnd84-name = \"Get Key\"\nsnd85-name = \"Shield Block\"\nsnd86-name = \"Hermes Boots\"\nsnd87-name = \"Fly\"\nsnd88-name = \"Mow Lawn\"\n\n\n[sndfam-pet]\nname = \"Pet\"\nsort-index = 8\nsnd48-name = \"Pet Mount\"\nsnd49-name = \"Pet Hurt\"\nsnd50-name = \"Pet Tongue\"\nsnd55-name = \"Pet Swallow\"\nsnd36-name = \"Pet Stomp\"\n\n\n[musfam-set1]\nname = \"Set 1\"\nsort-index = 1\nmus9-name = \"Overworld\"\nmus7-name = \"Underground\"\nmus42-name = \"Dungeon\"\nmus46-name = \"Water\"\n\n[musfam-set2]\nname = \"Set 2\"\nsort-index = 2\nmus5-name = \"Overworld\"\nmus25-name = \"Underground\"\nmus15-name = \"Boss\"\nmus43-name = \"Final Boss\"\n\n[musfam-set3]\nname = \"Set 3\"\nsort-index = 3\nmus1-name = \"Overworld\"\nmus2-name = \"Sky\"\nmus4-name = \"Underground\"\nmus3-name = \"Dungeon\"\nmus47-name = \"Water\"\nmus54-name = \"Roaming Enemy\"\nmus6-name = \"Boss\"\n\n[musfam-world]\nname = \"World\"\nsort-index = 4\nmus10-name = \"Overworld\"\nmus17-name = \"Mansion\"\nmus28-name = \"Sky\"\nmus29-name = \"Cave\"\nmus41-name = \"Dungeon\"\nmus48-name = \"Water\"\nmus51-name = \"Boss\"\n\n[musfam-rpg]\nname = \"RPG\"\nsort-index = 5\nmus30-name = \"Bachelor Pad\"\nmus34-name = \"Town\"\nmus16-name = \"Forest\"\nmus31-name = \"Seaside\"\nmus32-name = \"Pond\"\nmus33-name = \"Clouds\"\nmus21-name = \"Battle\"\n\n[musfam-64]\nname = \"64\"\nsort-index = 6\nmus27-name = \"Main Theme\"\nmus50-name = \"Cave\"\nmus35-name = \"Snow\"\nmus14-name = \"Desert\"\nmus49-name = \"Water\"\nmus26-name = \"Castle\"\nmus36-name = \"Boss\"\n\n[musfam-fight]\nname = \"Fight\"\nsort-index = 7\nmus40-name = \"Knight\"\nmus52-name = \"Underground\"\nmus39-name = \"Temple\"\nmus19-name = \"Steampunk\"\nmus53-name = \"Pinball\"\n\n[musfam-space]\nname = \"Space\"\nsort-index = 8\nmus11-name = \"Red Swamp\"\nmus12-name = \"Crater\"\nmus44-name = \"Item Room\"\nmus45-name = \"Final Boss\"\nmus22-name = \"Space Charge\"\n\n[musfam-misc]\nname = \"Misc.\"\nsort-index = 9\nmus38-name = \"Jungle Village\"\nmus37-name = \"Ice Mountain\"\nmus55-name = \"Title Theme\"\nmus18-name = \"Beach\"\nmus20-name = \"Fusion Reactor\"\nmus56-name = \"Bouncy Race\"\nmus13-name = \"New Overworld\"\nmus23-name = \"Heroic Woods\"\nmus8-name = \"Cornered!\"\n\n[wmusfam-set3]\nname = \"Set 3\"\nmembers = 1,6,8,2,11,10,3,9\nsort-index = 1\nwmus1-name = \"World 1\"\nwmus6-name = \"World 2\"\nwmus8-name = \"World 3\"\nwmus2-name = \"World 4\"\nwmus11-name = \"World 5\"\nwmus10-name = \"World 6\"\nwmus3-name = \"World 7\"\nwmus9-name = \"World 8\"\n\n[wmusfam-world]\nname = \"World\"\nmembers = 4,16,15,7,13,14,12\nsort-index = 2\nwmus4-name = \"Theme\"\nwmus16-name = \"Cave\"\nwmus15-name = \"Island\"\nwmus7-name = \"Forest\"\nwmus13-name = \"Dungeon\"\nwmus14-name = \"Sky\"\nwmus12-name = \"Special\"\n\n[wmusfam-new]\nname = \"New\"\nsort-index = 3\nwmus5-name = \"Theme\"\n"
  },
  {
    "path": "fastlane/metadata/android/en-GB/changelogs/1030700.txt",
    "content": "This is a large update. We worked long and hard on this update, and we finally ready to present this big update for you all! This update will be the biggest in TheXTech's history and features a huge set of features, bugfixes, and improvements.\n\nThank you to @0lhi who served on our core development team for design and quality assurance from version v1.3.4 (2021) to v1.3.7 (2025). His contributions are much appreciated.\n\nRead more here: https://github.com/Wohlstand/TheXTech/releases/tag/v1.3.7"
  },
  {
    "path": "fastlane/metadata/android/en-GB/changelogs/1030701.txt",
    "content": "This update features a lot of platform-related improvements and stabilisation. Since this update the exclusive full-screen modes on some platforms (such as retro computers) are supported to maintain the graphics performance which is impossible with the regular configuration and windowed mode.\n\nRead more here: https://github.com/Wohlstand/TheXTech/releases/tag/v1.3.7.1\n"
  },
  {
    "path": "fastlane/metadata/android/en-GB/full_description.txt",
    "content": "This is a game engine for platforming games. This is a complete and extended port of source code of the SMBX 1.3 game engine originally writtein in MS Visual Basic 6, and its direct unofficial continuation after development of original halted in the 2011th year. This engine preserves full compatibility with levels and episodes made for the original SMBX game, including its repacks. And it's allowed to create brand-new Levels, Episodes, and content packs. Unlike the original SMBX game that depends on Windows and x86, TheXTech can work on many operating systems (including Linux distros, macOS, xBSD, Android, Haiku, etc.) and processor architectures (including x86_64, ARM, PowerPC, MIPS, etc.).\n\n**NOTE:** This is a runtime engine only. It requires game packages to be downloaded and unpacked properly. Please follow this instruction in order to get any game work:\nhttps://github.com/TheXTech/TheXTech/wiki/Running-game-assets-on-Android\n"
  },
  {
    "path": "fastlane/metadata/android/en-GB/short_description.txt",
    "content": "2D-platformer game engine"
  },
  {
    "path": "fastlane/metadata/android/en-GB/title.txt",
    "content": "TheXTech\n"
  },
  {
    "path": "fastlane/metadata/android/ru/full_description.txt",
    "content": "Это игровой движок для игр-платформеров. Это полный и расширенный порт из исходного кода движка SMBX 1.3, который изначально был создан через MS Visual Basic 6, а также, это его прямое неофициальное продолжение с тех пор, как разработка оригинала была прекращена в 2011м году. Данный движок сохраняет полную совместимость с уровнями и эпизодами для оригинальной SMBX-игры, включая её модифицированные издания. Также, под данный движок можно создавать совершенно новые уровни, эпизоды и наборы ресурсов. В отличии от первоначальной игры SMBX, которая зависит от Windows и архитектуры x86, TheXTech способен работать на широком спектре операционных систем (включая различные дистрибутивы Linux, macOS, xBSD, Android, Haiku и т.п.) и процессорных архитектурах (включая x86_64, ARM, PowerPC, MIPS, и т.п.).\n\n**ПРИМЕЧАНИЕ:** Это чистый исполняемый движок, без каких либо игровых ресурсов. Чтобы на нём играть, нужно отдельно загружать и правильно распаковывать пакеты игровых ресурсов. Следуйте данной инструкции, чтобы узнать, как надо устанавливать игровые ресурсы и запустить игру:\nhttps://github.com/TheXTech/TheXTech/wiki/Running-game-assets-on-Android\n"
  },
  {
    "path": "fastlane/metadata/android/ru/short_description.txt",
    "content": "Игровой движок для 2D-платформеров\n"
  },
  {
    "path": "lib/Allocator/Allocator.h",
    "content": "#ifndef ALLOCATOR_H\n#define ALLOCATOR_H\n\n#include <cstddef> // size_t\n\nclass Allocator\n{\nprotected:\n    std::size_t m_totalSize;\n    std::size_t m_used;\n    std::size_t m_peak;\npublic:\n\n    Allocator(const std::size_t totalSize) : m_totalSize { totalSize }, m_used { 0 }, m_peak { 0 } { }\n\n    virtual ~Allocator()\n    {\n        m_totalSize = 0;\n    }\n\n    virtual void* Allocate(const std::size_t size, const std::size_t alignment = 0) = 0;\n\n    virtual void Free(void* ptr) = 0;\n\n    virtual void Init() = 0;\n\n    inline std::size_t getUsed()\n    {\n        return m_used;\n    }\n\n    friend class Benchmark;\n};\n\n#endif /* ALLOCATOR_H */\n\n"
  },
  {
    "path": "lib/Allocator/LICENSE",
    "content": "MIT License\r\n\r\nCopyright (c) 2016 Mariano Trebino\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "lib/Allocator/LinearAllocator.cpp",
    "content": "#include \"LinearAllocator.h\"\n#include \"Utils.h\"  /* CalculatePadding */\n#include <stdlib.h>     /* malloc, free */\n#include <cassert>   /*assert       */\n#include <algorithm>    // max\n#ifdef _DEBUG\n#include <iostream>\n#endif\n\nLinearAllocator::LinearAllocator(const std::size_t totalSize)\n    : Allocator(totalSize)\n{}\n\nvoid LinearAllocator::Init()\n{\n    if(m_start_ptr != nullptr)\n    {\n        free(m_start_ptr);\n    }\n\n    m_start_ptr = malloc(m_totalSize);\n    m_offset = 0;\n}\n\nLinearAllocator::~LinearAllocator()\n{\n    free(m_start_ptr);\n    m_start_ptr = nullptr;\n}\n\nvoid* LinearAllocator::Allocate(const std::size_t size, const std::size_t alignment)\n{\n    std::size_t padding = 0;\n    // std::size_t paddedAddress = 0;\n    const std::size_t currentAddress = (std::size_t)m_start_ptr + m_offset;\n\n    if(alignment != 0 && m_offset % alignment != 0)\n    {\n        // Alignment is required. Find the next aligned memory address and update offset\n        padding = Utils::CalculatePadding(currentAddress, alignment);\n    }\n\n    if(m_offset + padding + size > m_totalSize)\n    {\n        return nullptr;\n    }\n\n    m_offset += padding;\n    const std::size_t nextAddress = currentAddress + padding;\n    m_offset += size;\n\n#ifdef _DEBUG\n    std::cout << \"A\" << \"\\t@C \" << (void*) currentAddress << \"\\t@R \" << (void*) nextAddress << \"\\tO \" << m_offset << \"\\tP \" << padding << std::endl;\n#endif\n\n    m_used = m_offset;\n    m_peak = std::max(m_peak, m_used);\n\n    return (void*) nextAddress;\n}\n\nvoid LinearAllocator::Free(void* /*ptr*/)\n{\n    assert(false && \"Use Reset() method\");\n}\n\nvoid LinearAllocator::Reset()\n{\n    m_offset = 0;\n    m_used = 0;\n    m_peak = 0;\n}\n"
  },
  {
    "path": "lib/Allocator/LinearAllocator.h",
    "content": "#ifndef LINEARALLOCATOR_H\n#define LINEARALLOCATOR_H\n\n#include \"Allocator.h\"\n\nclass LinearAllocator : public Allocator\n{\nprotected:\n    void* m_start_ptr = nullptr;\n    std::size_t m_offset;\npublic:\n    LinearAllocator(const std::size_t totalSize);\n\n    virtual ~LinearAllocator();\n\n    virtual void* Allocate(const std::size_t size, const std::size_t alignment = 0) override;\n\n    virtual void Free(void* ptr) override;\n\n    virtual void Init() override;\n\n    virtual void Reset();\nprivate:\n    LinearAllocator(LinearAllocator& linearAllocator);\n};\n\n#endif /* LINEARALLOCATOR_H */\n"
  },
  {
    "path": "lib/Allocator/PoolAllocator.cpp",
    "content": "#include \"PoolAllocator.h\"\n#include <assert.h>\n#include <stdint.h>\n#include <stdlib.h>     /* malloc, free */\n#include <fmt_format_ne.h>\n#ifdef _BUILD\n#include <iostream>\n#endif\n#include \"core/msgbox.h\"\n#include \"../sdl_proxy/sdl_stdinc.h\"\n#include \"../sdl_proxy/sdl_assert.h\"\n\n\nPoolAllocator::PoolAllocator(const std::size_t totalSize, const std::size_t chunkSize)\n    : Allocator(totalSize)\n{\n    assert(chunkSize >= 8 && \"Chunk size must be greater or equal to 8\");\n    assert(totalSize % chunkSize == 0 && \"Total Size must be a multiple of Chunk Size\");\n\n    this->m_chunkSize = chunkSize;\n    this->m_chunkSizeH = chunkSize + sizeof(Node);\n    this->m_capacity = totalSize / chunkSize;\n}\n\nvoid PoolAllocator::Init()\n{\n    m_start_ptr = SDL_malloc(m_totalSize + (m_capacity * sizeof(Node)));\n    SDL_assert_release(m_start_ptr && \"Out of memory: Can't allocate the pool allocator\");\n    this->Reset();\n}\n\nPoolAllocator::~PoolAllocator()\n{\n    SDL_free(m_start_ptr);\n}\n\nvoid* PoolAllocator::Allocate(const std::size_t allocationSize, const std::size_t /*alignment*/)\n{\n#ifdef NDEBUG\n    (void)allocationSize;\n#endif\n    SDL_assert(allocationSize == this->m_chunkSize && \"Allocation size must be equal to chunk size\");\n\n    Node* freePosition = m_freeList.pop();\n\n    if(!freePosition)\n    {\n        // Fatal error\n        XMsgBox::errorMsgBox(\"Fatal error\",\n            fmt::sprintf_ne(\"The pool allocator is full:\\n\"\n            \"- Total capacity: %zu items\\n\"\n            \"- Current number of free list: %zu\\n\"\n            \"- Maximum number of taken items: %zu\\n\"\n            \"\\n\"\n            \"Game will be closed. Please check logs for details.\",\n            m_capacity,\n            m_freeList.count(),\n            m_freeList.maximum())\n        );\n        abort();\n    }\n\n    m_used += m_chunkSize;\n    m_peak = SDL_max(m_peak, m_used);\n\n#ifdef _BUILD\n    std::cout << \"A\" << \"\\t@S \" << m_start_ptr << \"\\t@R \" << (void*) freePosition << \"\\tM \" << m_used << std::endl;\n    std::cout.flush();\n#endif\n\n    ++m_count;\n    if(m_count > m_maximum)\n        m_maximum = m_count;\n\n    return reinterpret_cast<void*>(freePosition->data);\n}\n\nvoid PoolAllocator::Free(void* ptr)\n{\n    if(reinterpret_cast<uintptr_t>(ptr) == 0)\n        return;\n\n    --m_count;\n    m_used -= m_chunkSizeH;\n\n    Node *n = reinterpret_cast<Node*>(reinterpret_cast<uintptr_t>(ptr) + m_chunkSize);\n    if(n->data != reinterpret_cast<uintptr_t>(ptr))\n    {\n        // Fatal error\n        XMsgBox::errorMsgBox(\"Fatal error\",\n            fmt::sprintf_ne(\"Attempt to free wrong memory block at the pool allocator:\\n\"\n            \"- Desired address: 0x%08llX\\n\"\n            \"- Reported in the node address: 0x%08llX\\n\"\n            \"\\n\"\n            \"Game will be closed. Please check logs for details.\",\n            reinterpret_cast<unsigned long long>(ptr),\n            static_cast<unsigned long long>(n->data))\n        );\n        abort();\n    }\n\n    n->next = nullptr;\n\n    m_freeList.push(n);\n\n#ifdef _BUILD\n    std::cout << \"F\" << \"\\t@S \" << m_start_ptr << \"\\t@F \" << ptr << \"\\tM \" << m_used << std::endl;\n    std::cout.flush();\n#endif\n}\n\nvoid PoolAllocator::Reset()\n{\n    m_maximumPrev = m_maximum;\n    m_count = 0;\n    m_maximum = 0;\n    m_used = 0;\n    m_peak = 0;\n    // Create a linked-list with all free positions\n    const size_t nChunks = m_capacity;\n\n    uintptr_t front = reinterpret_cast<uintptr_t>(m_start_ptr);\n    uintptr_t back = reinterpret_cast<uintptr_t>(m_start_ptr) + (m_capacity * m_chunkSizeH);\n\n    m_freeList.set_edges(reinterpret_cast<Node*>(front), reinterpret_cast<Node*>(back));\n\n    uintptr_t address = reinterpret_cast<uintptr_t>(m_start_ptr);\n    for(size_t i = 0; i < nChunks; ++i)\n    {\n        Node *n = reinterpret_cast<Node*>(address + m_chunkSize);\n        n->data = address;\n        n->next = nullptr;\n\n        m_freeList.push(n);\n\n        address += m_chunkSizeH;\n    }\n}\n\nsize_t PoolAllocator::maximum() const\n{\n    return m_maximum;\n}\n\nsize_t PoolAllocator::prevMaximum() const\n{\n    return m_maximumPrev;\n}\n"
  },
  {
    "path": "lib/Allocator/PoolAllocator.h",
    "content": "#pragma once\n#ifndef POOL_ALLOCATOR_H\n#define POOL_ALLOCATOR_H\n\n#include <stdint.h>\n\n#include \"Allocator.h\"\n#include \"StackLinkedList.h\"\n\nclass PoolAllocator : public Allocator\n{\nprivate:\n    typedef uintptr_t FreeHeader;\n    using Node = StackLinkedList<FreeHeader>::Node;\n    StackLinkedList<FreeHeader> m_freeList;\n\n    void* m_start_ptr = nullptr;\n    std::size_t m_chunkSize;\n    std::size_t m_chunkSizeH;\n    std::size_t m_capacity;\n\n    size_t m_count = 0;\n    size_t m_maximum = 0;\n    size_t m_maximumPrev = 0;\npublic:\n    PoolAllocator(const std::size_t totalSize, const std::size_t chunkSize);\n\n    virtual ~PoolAllocator();\n\n    virtual void* Allocate(const std::size_t size, const std::size_t alignment = 0) override;\n\n    virtual void Free(void* ptr) override;\n\n    virtual void Init() override;\n\n    virtual void Reset();\n\n    size_t maximum() const;\n    size_t prevMaximum() const;\nprivate:\n    PoolAllocator(PoolAllocator& poolAllocator);\n\n};\n\n#endif // POOL_ALLOCATOR_H\n"
  },
  {
    "path": "lib/Allocator/README.md",
    "content": "Stuff taken from the repository: https://github.com/mtrebi/memory-allocators\r\n\r\n<a href='http://www.recurse.com' title='Made with love at the Recurse Center'><img src='https://cloud.githubusercontent.com/assets/2883345/11325206/336ea5f4-9150-11e5-9e90-d86ad31993d8.png' height='20px'/></a>\r\n\r\n# Table of Contents\r\n&nbsp;[Introduction](https://github.com/mtrebi/memory-allocators#introduction)  <br/>\r\n&nbsp;[Build instructions](https://github.com/mtrebi/memory-allocators#build-instructions)  <br/>\r\n&nbsp;[What's wrong with Malloc?](https://github.com/mtrebi/memory-allocators#whats-wrong-with-malloc)  <br/>\r\n&nbsp;[Custom allocators](https://github.com/mtrebi/memory-allocators#custom-allocators)  <br/>\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Linear Allocator](https://github.com/mtrebi/memory-allocators#linear-allocator)  <br/>\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Stack Allocator](https://github.com/mtrebi/memory-allocators#stack-allocator)  <br/>\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Pool Allocator](https://github.com/mtrebi/memory-allocators#pool-allocator)  <br/>\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Free list Allocator](https://github.com/mtrebi/memory-allocators#free-list-allocator)  <br/>\r\n&nbsp;[Benchmarks](https://github.com/mtrebi/memory-allocators#benchmarks)  <br/>\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Time complexity](https://github.com/mtrebi/memory-allocators#time-complexity)  <br/>\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Space complexity](https://github.com/mtrebi/memory-allocators#space-complexity)  <br/>\r\n&nbsp;[Summary](https://github.com/mtrebi/memory-allocators#summary)  <br/>\r\n&nbsp;[Last thoughts](https://github.com/mtrebi/memory-allocators#last-thoughts)  <br/>\r\n&nbsp;[Acknowledgments](https://github.com/mtrebi/memory-allocators#acknowledgments)  <br/>\r\n\r\n# Introduction\r\nWhen applications need more memory this can be allocated in the heap (rather than in the stack) in _runtime_. This memory is called 'dynamic memory' because it can't be known at compile time and its need changes during the execution. Our programs can ask for dynamic memory usin 'malloc'. Malloc returns an address to a position in memory where we can store our data. Once we're done with that data, we can call 'free' to free the memory and let others processes use it.\r\n\r\nFor this project I've implemented different ways to manage by ourselves dynamic memory in C++.This means that instead of using native calls like 'malloc' or 'free' we're going to use a custom memory allocator that will do this for us but in a more efficient way.\r\nThe goal, then, is to understand how the most common allocators work, what they offer and compare them to see which one performs better.\r\n\r\n# Build instructions\r\n\r\n```c\r\ngit clone https://github.com/mtrebi/memory-allocators.git\r\ncmake -S memory-allocator -B build\r\ncmake --build build\r\n```\r\n\r\n# What's wrong with Malloc?\r\n* **General purpose**: Being a general purpose operation means that it must work in all cases (from 1byte to 1GB or more...). For this reason the implementation is not as efficient as it could be if the needs were more specific.\r\n* **Slow**: Sometimes, when allocating memory, malloc needs to change from user to kernel mode to get more memory from the system. When this happens, malloc turns out to be super slow!\r\n\r\n# Custom allocators\r\nBecause every program has specific needs, it makes no sense to use a general purpose allocator. We can choose the right allocator that works best for us. This way we can increase our **performance**.\r\n\r\nIn general, custom allocators share some features:\r\n* **Low number of mallocs**: Any custom allocator tries to keep the number of mallocs low. To do that, they malloc _big chunks of memory_ and then, they manage this chunk internally to provide smaller allocations.\r\n* **Data structures**: Secondary data structures like _Linked Lists_, _Trees_, _Stacks_ to manage these big chunks of memory. Usually they are used to keep track of the allocated and/or free portions of memory to _speed up_ operations.\r\n* **Constraints**: Some allocators are very specific and have constraints over the data or operations that can be performed. This allows them to achieve a high performance but can only be used in some applications.\r\n\r\n## Linear allocator\r\nThis is the simplest kind of allocator. The idea is to keep a pointer at the first memory address of your memory chunk and move it every time an allocation is done. In this allocator, the internal fragmentation is kept to a minimum because all elements are sequentially (spatial locality) inserted and the only fragmentation between them is the alignment.\r\n\r\n### Data structure\r\nThis allocator only requires a pointer (or an offset) to tell us the position of the last allocation. It doesn't require any extra information or data structure.\r\n\r\n![Data structure of a Linear Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/linear1.png)\r\n\r\n_Complexity: **O(1)**_\r\n\r\n### Allocate\r\nSimply move the pointer (or offset) forward.\r\n\r\n![Allocating memory in a Linear Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/linear2.png)\r\n\r\n_Complexity: **O(1)**_\r\n\r\n### Free\r\nDue to its simplicity, this allocator doesn't allow specific positions of memory to be freed. Usually, all memory is freed together.\r\n\r\n## Stack allocator\r\nThis is a smart evolution of the Linear Allocator. The idea is to manage the memory as a Stack. So, as before, we keep a pointer to the current memory address and we move it forward for every allocation. However, we also can move it backwards when a free operation is done. As before, we keep the spatial locality principle and the fragmentation is still very low.\r\n\r\n### Data structure\r\nAs I said, we need the pointer (or offset) to keep track of the last allocation. In order to be able to free memory, we also need to store a _header_ for each allocation that tell us the size of the allocated block. Thus, when we free, we know how many positions we have to move back the pointer.\r\n\r\n![Data structure of a Stack Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/stack1.png)\r\n\r\n_Complexity: **O(N*H) --> O(N)**_ where H is the Header size and N is the number of allocations\r\n\r\n### Allocate\r\nSimply move the pointer (or offset) forward and place a header right before the memory block indicating its size.\r\n\r\n![Allocating memory in a Stack Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/stack2.png)\r\n\r\n_Complexity: **O(1)**_\r\n\r\n### Free\r\nSimply read the block size from the header and move the pointer backwards given that size.\r\n\r\n![Freeing memory in a Stack Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/stack3.png)\r\n\r\n_Complexity: **O(1)**_\r\n\r\n## Pool allocator\r\nA Pool allocator is quite different from the previous ones. It splits the big memory chunk in smaller chunks of the same size and keeps track of which of them are free. When an allocation is requested it returns the free chunk size. When a freed is done, it just stores it to be used in the next allocation. This way, allocations work super fast and the fragmentation is still very low.\r\n\r\n![Splitting scheme in a Pool Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/pool1.png)\r\n\r\n### Data structure\r\nTo keep track of the free blocks of memory, the Pool allocator uses a Linked List that links the address of each free memory block.\r\n\r\n![Linked List used in a Pool Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/pool2.png)\r\n\r\nTo reduce the space needed, this **Linked List is stored in the same free blocks** (smart right?). However, this set the constraint that the data chunks must be at least as big as our nodes in the Linked List (so that, we can store the Linked List in the free memory blocks).\r\n\r\n![In memory Linked List used in a Pool Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/pool3.png)\r\n\r\n_Complexity: **O(1)**_\r\n\r\n### Allocate\r\nAn allocation simply means to take (pop) the first free block of the Linked List.\r\n\r\n![Allocation in a Pool Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/pool4.png)\r\n\r\nThe linked list doesn't have to be sorted. Its order its determined by the how the allocations and free are done.\r\n\r\n![Random State of a Linked List in a Pool Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/pool5.png)\r\n\r\n_Complexity: **O(1)**_\r\n\r\n### Free\r\nFree means to add (push) the freed element as the first element in the Linked List.\r\n\r\n_Complexity: **O(1)**_\r\n\r\n## Free list allocator\r\n\r\nThis is a general purpose allocator that, contrary to the others, doesn't impose any restriction. It allows allocations and deallocations to be done in any order. For this reason, its performance is not as good as its predecessors. Depending on the data structure used to speed up this allocator, there are two common implementations: one that uses a Linked List and one that uses a Red black tree.\r\n\r\n### Linked list data structure\r\nAs the name says, this implementation uses a Linked List to store, in a sorted manner, the start address of each free contiguous block in memory and its size.\r\nWhen an allocation is requested, it searches in the linked list for a block where the data can fit. Then it removes the element from the linked list and places an allocation header (used on deallocations) right before the data (as we did in the Stack allocator).\r\nOn deallocations, we get back the allocation header to know the size of the block that we are going to free. Once we free it we insert it into the sorted linked list and we try to merge contiguous memory blocks together creating bigger blocks.\r\n\r\n*Notes: My implementation has some constraints on the size and alignment of the data that can be allocated using **this** allocator. For example, the minimum size that can be allocated should be equals or bigger than the size required of a Free Node. Otherwise, we would be wasting more space in meta-data than in real data (Something similar happens with the alignment) These constraints are related with my implementation. A better implementation would probably handle these cases. I decided not to do so because performance would be drastically affected. In these cases its probably better to use a different allocator.*\r\n\r\n![Data structure in a Free list Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/freelist_seq1.png)\r\n\r\n_Complexity: **O(N*HF + M*HA)--> O(M)**_ where N is the number of free blocks, HF is the size of the header of free blocks, M the number of allocated blocks and HA the size of the header of allocated blocks\r\n\r\n\r\n### Linked list Allocate\r\n When an allocation is requested, we look for a block in memory where our data can fit. This means that we have to iterate our linked list until we find a block that has a size equal or bigger than the size requested (it can store this data plus the allocation header) and remove it from the linked list. This would be a **first-fit** allocation because it stops when it finds the first block where the memory fits. There is another type of search called **best-fit** that looks for the free memory block of smaller size that can handle our data. The latter operation may take more time because is always iterating through all elements but it can reduce fragmentation.\r\n\r\n![Allocating in a Free list Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/freelist_seq2.png)\r\n\r\n_Complexity: **O(N)**_ where N is the number of free blocks\r\n\r\n### Linked list Free\r\nFirst of all we get back the information about the allocation from the header. Then, we iterate the Linked List to intert the free block in the right position (because is sorted by address). Once we've inserted it, we merge contiguous blocks. We can merge in _O(1) because our Linked List is sorted. We only need to look at the previous and next elements in the linked list to see if we can merge this contiguous blocks. This operation of merging contiguous memory blocks to create bigger blocks is called _Coalescence_\r\nIf we used instead a Sorted Linked List of free and allocated blocks, the complexity would be *O(1)* but the allocation compleixity would be *O(N) where N is the number of free and allocated blocks and space complexity would be much higher. When we free a memory block we also look at the previous and next blocks to see if we can merge them into one bigger block.\r\n\r\n![Freeing in a Free list Allocator](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/freelist_seq3.png)\r\n\r\n_Complexity: **O(N)**_ where N is the number of free blocks\r\n\r\n### Red black tree data structure\r\nThe purpose of using a Red black tree is to speed up allocations and deallocations. In the Linked List (or sequential) implementation every time an operation was made we needed to iterate the Linked List. This was O(N) in all cases.\r\n\r\nUsing Red Black trees we can reduce its complexity to O(log N) while keeping space complexity quite low because the tree data is stored inside the free memory blocks. In addition, this structure allows a **best-fit** algorithm to be used, reducing the fragmentation and keeping performance. However, an additional sorted Doubly Linked list is required to store allocated and free elements in order to be able to do coalescence operations in O(1).\r\nThis implementation is the most common and most used in real systems because it offers high flexibility while keeping performance very high.\r\n\r\n# Benchmarks\r\nNow its time to make sure that all the effort in designing and implementing custom memory allocators is worth.\r\nI've made several benchmarks with different block sizes, number of operations, random order, etc. The time benchmark measures the time execution that takes initializing the allocator 'Init()' (malloc big chunk, setup additional data structures...) and untill the last operation (allocation or free) is performed.\r\n\r\nHere I'm only showing what I believe is relevant for the goal of this project.\r\n\r\n## Time complexity\r\n* **Malloc** is without doubt the **worst allocator**.Due to its general and flexible use. _**O(n)**_\r\n* **Free list allocator** is **A much better choice than malloc** as a general purpose allocator.It uses Linked List to speed up allocations/free. It's about three times better than malloc _**O(n)**_\r\n\r\nThe next allocator are even better BUT they are no longer general purpose allocators. They **impose restrictions** in how we can use them:\r\n* **Pool allocator** forces us to always allocate the same size but then we can allocate and deallocate in any order. The complexity of this one is slightly better than the free list allocator, wait what? The complexity of the pool allocator was supposed to be constant not linear! And that's true. What its happening here is that the initialization of the additional data structure (the linked list) is _**O(n)**_. It has to create all memory chunks in then linked them in the linked list. This operation is hiding the truly complexity of the allocation and free operations that is _**O(1)**_.  So, take into account to initialize the Pool allocator (and all the allocators in general) before to avoid this kind of behaviors.\r\n* **Stack allocator** can allocate any size, but deallocations must be done in a LIFO fashion with a _**O(1)**_ complexity. In the chart the complexity is not completely constant due to init function that has to allocate the first big chunk of memory, similarly as before in the pool allocator.\r\n* **Linear allocator** is the simplest and the best performant allocator with a _**O(1)**_ complexity but its also the most restrictive because single free operations are not allowed. As with the stack, the complexity doesn't look completely constant due to the init function.\r\n\r\n![Time complexity of different allocators](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/operations_over_time.png)\r\n\r\nIn the next chart we can see that if we don't include the Init() function in the benchmark, the overall execution time is reduced and as a consequence we can effectively see that the Linear, Stack and Pool allocators are constant while malloc and free list are clearly linear. The free list implementation using black tree can reduce the complexity to _**O(log n)**_ and therefore its position in the chart would be between the pool allocator and the free list.\r\n\r\n![Time complexity of different allocators](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/operations_over_time_no_init.png)\r\n\r\n_Note: The time complexity (in general) scales following a linear fashion regarding the size of the allocation request.\r\n\r\n## Space complexity\r\nAs we can see, even that the space complexity for each allocator is slightly different(due to constants), in the end, all of them have the same space complexity **O(N)**. It is very clear, then, why when denoting big O, constants can be ignored: because its weight in the overall equation is very low when N grows.\r\n\r\n![Space complexity of different allocators](https://github.com/mtrebi/memory-allocators/blob/master/docs/images/operations_over_space.png)\r\n\r\n# Summary\r\nThis is a brief summary describing when you should use each allocator. From more restrictive and efficient allocators to less efficient and general.\r\n\r\n* **Linear allocator**. If your data does not follows any specific structure. However, there's a common behavior in time: all data \"expires\" after a certain time and then is no longer useful and thus can be freed. Think about games for example, you can allocate data in one frame using a this allocator and free all data at the start of the next frame.\r\n* **Stack allocator**. The same as the Linear allocator but think if it useful to free elements in a LIFO fashion.\r\n* **Pool allocator**. Your data has definitely a structure. All elements of your data have the same size. This is your choice, fast and no fragmentation.\r\n* **Buddy allocator** (_Not implemented here_). Your data is organized in exponential sizes power-of-two (1,2,4,8,16,32...). This allocator performs extremely well when data is structure in that way, being fast and wasting little space.\r\n* **Free list allocator**. No structure or common behavior. This allocator allows you to allocate and free memory as you wish. This is a general purpose allocator that works much better than malloc, but is not as good as the previous allocators, given its flexibility to work in all situations.\r\n\r\n# Last thoughts\r\n* Avoid dynamic memory as much as possible. Its behavior is unexpected and a source of problems\r\n* If you are worried about performance and your application uses dynamic memory, think about using a custom allocator instead of malloc\r\n* Try to understand your data and its behavior to choose the right allocator for you. Specific allocators (that impose restrictions on how we can structure/use our data) are far more better than the generic ones. We saw a huge gap between the specific purpose allocator: **Linear, Stack and Pool** and the general purpose: **Free list and Malloc**\r\n* Always choose a less restrictive (or more general) allocator if unsure. If you later see that your data is structured you can always change to use a more restrictive one.\r\n\r\n# Future work\r\n* Implement every memory allocator assuming that the alignment is always 8 bytes and thus everything is always align (we no longer need headers).\r\n* Implement a Free list allocator using Red Black Trees to improve performance from O(N) to O(log N)\r\n* Implement a Buddy allocator\r\n* Implement a Slab allocator\r\n* Benchmark internal fragmentation\r\n* Benchmark spatial location (cache misses)\r\n\r\n# Acknowledgments\r\n\r\nThanks to [Vanessa](https://github.com/vipyne) and [Krish](https://github.com/sigmasleep) for helping me in the different stages of this project.\r\n"
  },
  {
    "path": "lib/Allocator/StackLinkedList.h",
    "content": "#ifndef STACKLINKEDLIST_H\n#define STACKLINKEDLIST_H\n\n#include <stddef.h>\n\ntemplate <class T>\nclass StackLinkedList\n{\npublic:\n    struct Node\n    {\n        T data;\n        Node* next = nullptr;\n    };\n\n    Node* head = nullptr;\n    Node* front = nullptr;\n    Node* back = nullptr;\n    size_t m_counter = 0;\n    size_t m_taken = 0;\n    size_t m_maximum = 0;\n\npublic:\n    StackLinkedList() = default;\n    StackLinkedList(StackLinkedList& stackLinkedList) = delete;\n    void set_edges(Node *front, Node *back);\n    void push(Node* newNode);\n    Node* pop();\n\n    size_t count() const\n    {\n        return m_counter;\n    }\n\n    size_t maximum() const\n    {\n        return m_maximum;\n    }\n};\n\n#include \"StackLinkedListImpl.h\"\n\n#endif /* STACKLINKEDLIST_H */\n"
  },
  {
    "path": "lib/Allocator/StackLinkedListImpl.h",
    "content": "#pragma once\n#ifndef STACK_LINKED_LIST_IMPL_H\n#define STACK_LINKED_LIST_IMPL_H\n\n#include \"StackLinkedList.h\"\n\ntemplate <class T>\nvoid StackLinkedList<T>::push(Node* newNode)\n{\n    newNode->next = head;\n    head = newNode;\n    ++m_counter;\n\n    if(m_taken > 0)\n        --m_taken;\n}\n\ntemplate <class T>\ntypename StackLinkedList<T>::Node* StackLinkedList<T>::pop()\n{\n    if(!head || head < front || head > back)\n        return nullptr;\n\n    Node* top = head;\n    head = head->next;\n\n    ++m_taken;\n    if(m_taken > m_maximum)\n        m_maximum = m_taken;\n\n    if(m_counter > 0)\n        --m_counter;\n\n    return top;\n}\n\ntemplate <class T>\nvoid StackLinkedList<T>::set_edges(Node *i_front, Node *i_back)\n{\n    m_counter = 0;\n    front = i_front;\n    back = i_back;\n}\n\n#endif // STACK_LINKED_LIST_IMPL_H\n"
  },
  {
    "path": "lib/Allocator/Utils.h",
    "content": "#ifndef UTILS_H\n#define UTILS_H\n\n#include <cstddef>\n\nclass Utils\n{\npublic:\n    static std::size_t CalculatePadding(const std::size_t baseAddress, const std::size_t alignment)\n    {\n        const std::size_t multiplier = (baseAddress / alignment) + 1;\n        const std::size_t alignedAddress = multiplier * alignment;\n        const std::size_t padding = alignedAddress - baseAddress;\n        return padding;\n    }\n\n    static std::size_t CalculatePaddingWithHeader(const std::size_t baseAddress, const std::size_t alignment, const std::size_t headerSize)\n    {\n        std::size_t padding = CalculatePadding(baseAddress, alignment);\n        std::size_t neededSpace = headerSize;\n\n        if(padding < neededSpace)\n        {\n            // Header does not fit - Calculate next aligned address that header fits\n            neededSpace -= padding;\n\n            // How many alignments I need to fit the header\n            if(neededSpace % alignment > 0)\n            {\n                padding += alignment * (1 + (neededSpace / alignment));\n            }\n            else\n            {\n                padding += alignment * (neededSpace / alignment);\n            }\n        }\n\n        return padding;\n    }\n};\n\n#endif /* UTILS_H */\n"
  },
  {
    "path": "lib/Allocator/linear-allocator.cmake",
    "content": "include_directories(${CMAKE_CURRENT_LIST_DIR})\n\nset(LINALLOC_SRCS)\n\nlist(APPEND LINALLOC_SRCS\n    ${CMAKE_CURRENT_LIST_DIR}/LinearAllocator.cpp\n)\n"
  },
  {
    "path": "lib/Allocator/pool-allocator.cmake",
    "content": "include_directories(${CMAKE_CURRENT_LIST_DIR})\n\nset(POOLALLOC_SRCS)\n\nlist(APPEND POOLALLOC_SRCS\n    ${CMAKE_CURRENT_LIST_DIR}/PoolAllocator.cpp\n    ${CMAKE_CURRENT_LIST_DIR}/PoolAllocator.h\n    ${CMAKE_CURRENT_LIST_DIR}/Allocator.h\n    ${CMAKE_CURRENT_LIST_DIR}/StackLinkedList.h\n    ${CMAKE_CURRENT_LIST_DIR}/StackLinkedListImpl.h\n)\n"
  },
  {
    "path": "lib/AppPath/app_path.cmake",
    "content": "set(APPPATH_SRCS)\n\nlist(APPEND APPPATH_SRCS\n    ${CMAKE_CURRENT_LIST_DIR}/app_path.h\n    ${CMAKE_CURRENT_LIST_DIR}/private/app_path_private.h\n    ${CMAKE_CURRENT_LIST_DIR}/private/app_path.cpp\n)\n\nif(APPLE)\n    message(\"-- AppPath for Apple\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_macos.cpp\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_macos_dirs.h\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_macos_dirs.m\n    )\nelseif(ANDROID)\n    message(\"-- AppPath for Android\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_android.cpp\n    )\nelseif(EMSCRIPTEN)\n    message(\"-- AppPath for Emscripten\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_emscripten.cpp\n    )\nelseif(VITA)\n    message(\"-- AppPath for Vita\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_vita.cpp\n    )\nelseif(NINTENDO_3DS)\n    message(\"-- AppPath for 3DS\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_3ds.cpp\n    )\nelseif(NINTENDO_DS)\n    message(\"-- AppPath for 16M\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_16m.cpp\n    )\nelseif(NINTENDO_WII)\n    message(\"-- AppPath for Wii\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_wii.cpp\n    )\nelseif(NINTENDO_WIIU)\n    message(\"-- AppPath for Wii\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_wiiu.cpp\n    )\nelseif(NINTENDO_SWITCH)\n    message(\"-- AppPath for Switch\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_switch.cpp\n    )\nelseif(WIN32)\n    message(\"-- AppPath for Windows\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_win32.cpp\n    )\nelseif(UNIX OR HAIKU)\n    message(\"-- AppPath for UNIX-like operating systems\")\n    list(APPEND APPPATH_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/app_path_unix.cpp\n    )\nelse()\n    message(WARNING \"-- AppPath: Possibly unsupported platform detected\")\nendif()\n"
  },
  {
    "path": "lib/AppPath/app_path.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef APP_PATH_H\n#define APP_PATH_H\n\n#include <string>\n#include <vector>\n\n#if defined(__3DS__)\n#   define APP_PATH_HAS_EXTRA_WORLDS\n#endif\n\n\n/*!\n * \\brief Set of possible asset pack search path types\n */\nenum class AssetsPathType\n{\n    Legacy,   // search the path directly for asset pack (as legacy), and check for additional packs in assets subdirectory\n    Single,   // single modern asset pack\n    Multiple, // directory containing multiple subdirectories with modern asset packs\n};\n\n\nclass AppPathManager\n{\npublic:\n    /*!\n     * \\brief Prepend a single custom assets directory to the search path\n     * \\param root Path to the assets directory to use\n     *\n     * This function must be called BEFORE calling the initAppPath()\n     */\n    static void addAssetsRoot(const std::string &root);\n\n    /*!\n     * \\brief Custom user directory (will be used instead of default)\n     * \\param root Path to the writable user directory to use\n     *\n     * The custom user directory is prepended to the search path but has lower precedence than an added assets root.\n     * Overrides a custom-set GameDirName\n     *\n     * This function must be called BEFORE calling the initAppPath()\n     */\n    static void setUserDirectory(const std::string &root);\n\n    /*!\n     * \\brief Custom game directory name for default locations\n     * \\param dirName Name of directory name that will be used for game initialisation\n     *\n     * This function must be called BEFORE calling the initAppPath()\n     */\n    static void setGameDirName(const std::string &dirName);\n\n    /*!\n     * \\brief Initialise all paths\n     */\n    static void initAppPath();\n\n    /*!\n     * \\brief Check the user added assets root (so that it can be treated differently than other members of the search path)\n     * \\return root passed to addAssetsRoot()\n     */\n    static std::string userAddedAssetsRoot(); // Read-Only\n\n    /*!\n     * \\brief Get a list of folders that should be searched for asset packs (each folder directly, and also each subdirectory in folder + \"/assets\"). Does not change after initAppPath() is called.\n     * \\return Vector of paths to the read-only assets directory, always ends with a slash. Type of each path is specified.\n     */\n    static std::vector<std::pair<std::string, AssetsPathType>> assetsSearchPath(); // Read-Only\n\n    /*!\n     * \\brief Set the custom assets directory by ID and absolute path (will be used instead of default)\n     * \\param id Name of the asset pack (used to postfix user paths if not empty)\n     * \\param path Path to the assets directory to use\n     *\n     * This function should be called AFTER calling the initAppPath(), and it will update all other paths\n     */\n    static void setCurrentAssetPack(const std::string &id, const std::string &path);\n\n    /*!\n     * \\brief Get the path to the game settings file\n     * \\return Path to the game settings file\n     */\n    static std::string settingsFileSTD(); // Must be writable\n\n    /*!\n     * \\brief Get the settings directory path\n     * \\return Settings directory path, always ends with a slash\n     */\n    static std::string settingsRoot(); // Must be writable\n\n    /*!\n     * \\brief Get the path to the controls settings file\n     * \\return Path to the controls settings file\n     */\n    static std::string settingsControlsFileSTD(); // Must be writable\n\n    /*!\n     * \\brief Get the path to the writable user directory\n     * \\return Path to ther writable user directory, always ends with a slash\n     */\n    static std::string userAppDirSTD(); // Must be writable\n\n    /*!\n     * \\brief Get the path to the read-only assets directory\n     * \\return Path to ther read-only assets directory, always ends with a slash\n     */\n    static std::string assetsRoot(); // Read-Only\n\n    /*!\n     * \\brief Get the path to the logs output directory\n     * \\return Path to ther logs output directory, always ends with a slash\n     */\n    static std::string logsDir(); // Must be writable\n\n    /*!\n     * \\brief Get the path to engine languages directory\n     * \\return Path to ther engine languages directory, always ends with a slash\n     */\n    static std::string languagesDir(); // Read-Only\n\n    /*!\n     * \\brief Get the path to the screenshots output directory\n     * \\return Path to ther screenshots output directory, always ends with a slash\n     */\n    static std::string screenshotsDir(); // Must be writable\n\n    /*!\n     * \\brief Get the path to the GIF recordings output directory\n     * \\return Path to ther GIF recordings output directory, always ends with a slash\n     */\n    static std::string gifRecordsDir(); // Must be writable\n\n    /*!\n     * \\brief Get the path to the game saves directory\n     * \\return Path to ther game saves directory, always ends with a slash\n     */\n    static std::string gameSaveRootDir(); // Must be writable\n\n    static std::string gameplayRecordsRootDir(); // Must be writable\n\n    static std::string userWorldsRootDir(); // Read-Only, appears at writable directory\n\n    static std::string userBattleRootDir(); // Read-Only, appears at writable directory\n\n#ifdef APP_PATH_HAS_EXTRA_WORLDS\n    static const std::vector<std::string>& worldRootDirs(); // Read-Only, appears at writable directory\n#endif\n\n    static void install();\n    static bool userDirIsAvailable();\n\n    /*!\n     * \\brief Emscripten-only file system synchronization request\n     */\n    static void syncFs();\n\nprivate:\n    static bool checkPortable();\n    /**\n     * @brief Makes settings path if not exists\n     */\n    static void initSettingsPath();\n\n    static void initString(std::string &text, const std::string& inValue, const std::string &defValue);\n\n    //! Location for writable settings\n    static std::string m_settingsPath;\n    //! Location for writable gamesaves\n    static std::string m_gamesavesPath;\n    //! Location for writable user directory\n    static std::string m_userPath;\n    //! Screenshots output directory\n    static std::string m_screenshotsPath;\n    //! GIF recordings output directory\n    static std::string m_gifrecordingsPath;\n    //! Logs output directory\n    static std::string m_logsPath;\n\n    //! Location for read-only current asset pack\n    static std::string m_currentAssetPackPath;\n    //! Asset pack-specific postfix for some user directories (either \"id/\" or \"\")\n    static std::string m_assetPackPostfix;\n\n    //! Location for read-only custom assets root\n    static std::string m_customAssetsRoot;\n    //! Location for writable custom user directory\n    static std::string m_customUserDirectory;\n    //! Custom name for the game directory name at default locations\n    static std::string m_customGameDirName;\n    //! Is portable configuration active?\n    static bool m_isPortable;\n};\n\n#endif // APP_PATH_H\n"
  },
  {
    "path": "lib/AppPath/private/app_path.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include <DirManager/dirman.h>\n#include <Utils/files.h>\n#include <IniProcessor/ini_processing.h>\n#define FMT_NOEXCEPT\n#include <fmt/fmt_format.h>\n\n#include \"../app_path.h\"\n#include \"../../version.h\"\n#include \"app_path_private.h\"\n\n#include \"sdl_proxy/sdl_assert.h\"\n\nstd::string AppPathManager::m_settingsPath;\nstd::string AppPathManager::m_gamesavesPath;\nstd::string AppPathManager::m_userPath;\n\nstd::string AppPathManager::m_screenshotsPath;\nstd::string AppPathManager::m_gifrecordingsPath;\nstd::string AppPathManager::m_logsPath;\n\nstd::string AppPathManager::m_customAssetsRoot;\nstd::string AppPathManager::m_customUserDirectory;\nstd::string AppPathManager::m_customGameDirName;\n\nstd::string AppPathManager::m_currentAssetPackPath;\nstd::string AppPathManager::m_assetPackPostfix;\n\nbool AppPathManager::m_isPortable = false;\n\n\nstatic void appendSlash(std::string &path)\n{\n#if defined(__EMSCRIPTEN__)\n    // fix emscripten bug of duplicated worlds\n    if(path.empty() || path.back() != '/')\n        path.push_back('/');\n#else\n    if(!path.empty() && path.back() != '/')\n        path.push_back('/');\n#endif\n}\n\nvoid AppPathManager::addAssetsRoot(const std::string &root)\n{\n    m_customAssetsRoot = root;\n    appendSlash(m_customAssetsRoot);\n}\n\nvoid AppPathManager::setUserDirectory(const std::string& root)\n{\n    m_customUserDirectory = root;\n    appendSlash(m_customUserDirectory);\n}\n\nvoid AppPathManager::setGameDirName(const std::string& dirName)\n{\n    m_customGameDirName = dirName;\n    appendSlash(m_customGameDirName);\n    // Also append to front\n    if(!m_customGameDirName.empty() && m_customGameDirName.front() != '/')\n        m_customGameDirName.insert(0, 1, '/');\n}\n\nvoid AppPathManager::initAppPath()\n{\n    AppPathP::initDefaultPaths(m_customGameDirName.empty() ? THEXTECH_DIRECTORY_PREFIX : m_customGameDirName);\n\n    // When user directory is redefined externally\n    if(!m_customUserDirectory.empty())\n    {\n        m_userPath = m_customUserDirectory;\n        // Assets root matches to user directory if not specified other\n        initSettingsPath();\n        return;\n    }\n\n    if(AppPathP::portableAvailable() && checkPortable())\n        return;\n\n    std::string userDirPath = AppPathP::userDirectory();\n\n    if(userDirPath.empty())\n        goto defaultSettingsPath;\n    else\n    {\n        DirMan appDir(userDirPath);\n        if(userDirPath != \"/\" && !appDir.exists() && !appDir.mkpath())\n            goto defaultSettingsPath;\n\n        m_userPath = appDir.absolutePath();\n    }\n\n    appendSlash(m_userPath);\n\n    initSettingsPath();\n\n    return;\n\ndefaultSettingsPath:\n    m_userPath = AppPathP::appDirectory();\n    initSettingsPath();\n\n#if defined(__EMSCRIPTEN__) && !defined(DISABLE_LOGGING)\n    std::printf(\"== Default User Path is %s\\n\", m_userPath.c_str());\n    fflush(stdout);\n#endif\n}\n\nstd::string AppPathManager::userAddedAssetsRoot()\n{\n    return m_customAssetsRoot;\n}\n\nstd::vector<std::pair<std::string, AssetsPathType>> AppPathManager::assetsSearchPath()\n{\n    std::vector<std::pair<std::string, AssetsPathType>> out;\n\n    if(!m_customAssetsRoot.empty())\n        out.push_back({m_customAssetsRoot, AssetsPathType::Single});\n\n    if(!m_customUserDirectory.empty())\n        out.push_back({m_customUserDirectory, AssetsPathType::Legacy});\n\n    if(!AppPathP::appDirectory().empty())\n        out.push_back({AppPathP::appDirectory(), AssetsPathType::Legacy});\n\n    if(!m_isPortable)\n    {\n        if(m_customUserDirectory.empty())\n            out.push_back({m_userPath, AssetsPathType::Legacy});\n\n        if(!AppPathP::assetsRoot().empty())\n            out.push_back({AppPathP::assetsRoot(), AppPathP::assetsRootType()});\n\n#ifdef APP_PATH_HAS_EXTRA_WORLDS\n        // 3DS: add assets from additional romfs packages\n        for(const std::string& root : AppPathManager::worldRootDirs())\n            out.push_back({root, AssetsPathType::Single});\n#endif\n    }\n\n    // add slashes to all strings\n    for(size_t i = 0; i < out.size(); i++)\n        appendSlash(out[i].first);\n\n    // remove any duplicates\n    for(size_t i = 0; i < out.size(); i++)\n    {\n        for(size_t j = i + 1; j < out.size();)\n        {\n            if(out[i].first == out[j].first)\n                out.erase(out.begin() + j);\n            else\n                j++;\n        }\n    }\n\n    return out;\n}\n\nvoid AppPathManager::setCurrentAssetPack(const std::string &id, const std::string &path)\n{\n    m_currentAssetPackPath = path;\n    appendSlash(m_currentAssetPackPath);\n\n    if(!id.empty())\n        m_assetPackPostfix = id + \"/\";\n    else\n        m_assetPackPostfix = \"\";\n\n    initSettingsPath();\n}\n\nbool AppPathManager::checkPortable()\n{\n    std::string appPath = AppPathP::appDirectory();\n\n    if(appPath.empty())\n        return false;\n\n    if(m_settingsPath.empty())\n        m_settingsPath = appPath;\n\n    if(m_userPath.empty())\n        m_userPath = appPath;\n\n    if(!Files::fileExists(settingsFileSTD()))\n        return false;\n\n    m_isPortable = false;\n\n    IniProcessing checkForPort(settingsFileSTD());\n    checkForPort.beginGroup(\"Main\");\n    m_isPortable = checkForPort.value(\"force-portable\", false).toBool();\n    checkForPort.endGroup();\n\n    if(m_isPortable)\n        initSettingsPath();\n\n    return m_isPortable;\n}\n\nvoid AppPathManager::initSettingsPath()\n{\n    // Default settings path\n    initString(m_settingsPath, AppPathP::settingsRoot(), m_userPath + \"settings/\");\n\n    // Default settings path\n    initString(m_gamesavesPath, AppPathP::gamesavesRoot(), m_settingsPath + \"gamesaves/\" + m_assetPackPostfix);\n\n    // Check if need to use system-wide screenshots directory\n    initString(m_screenshotsPath, AppPathP::screenshotsRoot(), m_userPath + \"screenshots/\" + m_assetPackPostfix);\n\n    // Check if need to use system-wide gif recording directory\n    initString(m_gifrecordingsPath, AppPathP::gifRecsRoot(), m_userPath + \"gif-recordings/\" + m_assetPackPostfix);\n\n    // Check if need to use system-wide logs directory\n    initString(m_logsPath, AppPathP::logsRoot(), m_userPath + \"logs/\");\n\n#ifndef VITA\n    // Just in case, avoid mad jokes with making name-sake file as a settings folder\n    if(Files::fileExists(m_settingsPath))\n        Files::deleteFile(m_settingsPath);\n#endif\n\n    // Create the settings directory\n    if(!DirMan::exists(m_settingsPath))\n        DirMan::mkAbsPath(m_settingsPath);\n\n    // Also create the game saves root folder to be exist\n    if(!DirMan::exists(gameSaveRootDir()))\n        DirMan::mkAbsPath(gameSaveRootDir());\n\n    // Create the user assets directory, as a hint for the user\n    if(!DirMan::exists(m_userPath + \"assets/\"))\n        DirMan::mkAbsPath(m_userPath + \"assets/\");\n\n    // And create empty worlds and battle directories too, make a hint for user\n    if(!DirMan::exists(userWorldsRootDir()))\n        DirMan::mkAbsPath(userWorldsRootDir());\n    if(!DirMan::exists(userBattleRootDir()))\n        DirMan::mkAbsPath(userBattleRootDir());\n}\n\nvoid AppPathManager::initString(std::string& text, const std::string& inValue, const std::string& defValue)\n{\n    if(m_isPortable)\n        text = defValue;\n    else\n    {\n        text = inValue;\n        if(text.empty())\n            text = defValue;\n    }\n}\n\n\nstd::string AppPathManager::settingsFileSTD() // Writable\n{\n    return m_settingsPath + \"thextech.ini\";\n}\n\nstd::string AppPathManager::settingsRoot() // Writable\n{\n    return m_settingsPath;\n}\n\nstd::string AppPathManager::settingsControlsFileSTD() // Writable\n{\n    return m_settingsPath + \"controls.ini\";\n}\n\nstd::string AppPathManager::userAppDirSTD() // Writable\n{\n    return m_userPath;\n}\n\nstd::string AppPathManager::assetsRoot() // Readable\n{\n    SDL_assert_release(!m_currentAssetPackPath.empty());\n    return m_currentAssetPackPath;\n}\n\nstd::string AppPathManager::logsDir() // Writable\n{\n    return m_logsPath;\n}\n\nstd::string AppPathManager::languagesDir() // Readable\n{\n    SDL_assert_release(!m_currentAssetPackPath.empty());\n    return m_currentAssetPackPath + \"languages/\";\n}\n\nstd::string AppPathManager::screenshotsDir() // Writable\n{\n    return m_screenshotsPath;\n}\n\nstd::string AppPathManager::gifRecordsDir() // Writable\n{\n    return m_gifrecordingsPath;\n}\n\nstd::string AppPathManager::gameSaveRootDir() // Writable\n{\n    return m_gamesavesPath;\n}\n\nstd::string AppPathManager::gameplayRecordsRootDir() // Writable\n{\n    return m_userPath + \"gameplay-records/\" + m_assetPackPostfix;\n}\n\nstd::string AppPathManager::userWorldsRootDir() // Readable\n{\n    return m_userPath + \"worlds/\" + m_assetPackPostfix;\n}\n\nstd::string AppPathManager::userBattleRootDir() // Readable\n{\n    return m_userPath + \"battle/\" + m_assetPackPostfix;\n}\n\nvoid AppPathManager::install()\n{\n    std::string path = AppPathP::userDirectory();\n\n    if(!path.empty())\n    {\n        DirMan appDir(path);\n        if(!appDir.exists())\n            appDir.mkpath(path);\n    }\n}\n\nbool AppPathManager::userDirIsAvailable()\n{\n    SDL_assert_release(!m_currentAssetPackPath.empty());\n    return (m_userPath != m_currentAssetPackPath);\n}\n\nvoid AppPathManager::syncFs()\n{\n    AppPathP::syncFS();\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_16m.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <filesystem.h>\n#include <fat.h>\n\n#include <vector>\n#include <string>\n\n#include <DirManager/dirman.h>\n\n#include \"../app_path.h\"\n#include \"app_path_private.h\"\n\nvoid AppPathP::initDefaultPaths(const std::string &userDirName)\n{\n    (void)userDirName;\n\n    fatInitDefault();\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return std::string();\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return \"sd:/\" THEXTECH_DIRECTORY_PREFIX \"/\";\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return std::string();\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Single;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    /*\n     * Report, does this platfor support portable mode or not\n     */\n    return false;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_3ds.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <3ds.h>\n\n#include <vector>\n#include <string>\n\n#include <DirManager/dirman.h>\n\n#include \"../app_path.h\"\n#include \"app_path_private.h\"\n\nstatic std::vector<std::string> s_worldRootDirs;\nstatic std::string s_assetRoot;\n\n// special 3DS functions to handle fast romfs archives\nstatic bool mountRomfsFile(const char* path, const char* mount_label)\n{\n    // for some reason dirent returns a very strange format that appears to be\n    // utf-8 expressed as utf-16 and then converted back to utf-8.\n    uint16_t utf16_buf[4096 + 1];\n    uint8_t utf8_buf[4096 + 1];\n\n    ssize_t units = utf8_to_utf16(utf16_buf, (const uint8_t*)path, 4096);\n\n    if((units < 0) || (units > 4096))\n        return false;\n\n    utf16_buf[units] = 0;\n\n    for(int i = 0; i < units; i++)\n        utf8_buf[i] = (uint8_t)(0xff & utf16_buf[i]);\n\n    utf8_buf[units] = 0;\n\n    Handle fd = 0;\n    FS_Path archPath = { PATH_EMPTY, 1, \"\" };\n    FS_Path filePath = { PATH_ASCII, (size_t)units + 1, utf8_buf };\n\n    Result rc = FSUSER_OpenFileDirectly(&fd, ARCHIVE_SDMC, archPath, filePath, FS_OPEN_READ, 0);\n\n    if(R_FAILED(rc))\n        return false;\n\n    rc = romfsMountFromFile(fd, 0, mount_label);\n\n    if(R_FAILED(rc))\n        return false;\n\n    return true;\n}\n\n// find additional user worlds packaged in .romfs files\nstatic void findUserWorlds()\n{\n    std::vector<std::string> romfsFiles;\n    static const std::vector<std::string> romfsExt = {\".romfs\"};\n\n    DirMan userDir(AppPathP::userDirectory());\n    userDir.getListOfFiles(romfsFiles, romfsExt);\n\n    std::string fullPath;\n    char mount_label[9] = \"romfsA:/\";\n\n    for(std::string& s : romfsFiles)\n    {\n        if(s == \"assets.romfs\")\n            continue;\n\n        fullPath = userDir.absolutePath() + \"/\" + s;\n        mount_label[6] = '\\0';\n\n        if(!mountRomfsFile(fullPath.c_str(), mount_label))\n            continue;\n\n        mount_label[6] = ':';\n        s_worldRootDirs.push_back(std::string(mount_label));\n        mount_label[5] ++;\n\n        // from Z to a.\n        if(mount_label[5] == 91)\n            mount_label[5] = 97;\n\n        // max of 52 mounts.\n        if(mount_label[5] == 123)\n            break;\n    }\n}\n\nvoid AppPathP::initDefaultPaths(const std::string& userDirName)\n{\n    (void)userDirName;\n\n    s_assetRoot = \"romfs:/\";\n\n    // try to mount packaged files\n    if(R_FAILED(romfsInit()))\n    {\n        // fallback to assets.romfs\n        if(!mountRomfsFile(\"/3ds/\" THEXTECH_DIRECTORY_PREFIX \"/assets.romfs\", \"romfs\"))\n        {\n            // just use user dir for assets. this is really bad. everything will be insanely slow.\n            s_assetRoot.clear();\n        }\n    }\n\n    findUserWorlds();\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return std::string();\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return \"/3ds/\" THEXTECH_DIRECTORY_PREFIX \"/\";\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return s_assetRoot;\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Single;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    /*\n     * Report, does this platfor support portable mode or not\n     */\n    return false;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n\nconst std::vector<std::string>& AppPathManager::worldRootDirs() // Read-Only, appears at writable directory\n{\n    return s_worldRootDirs;\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_android.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <unistd.h>\n//#   include <md5/md5.h>\n#include <jni.h>\n\n#include <SDL2/SDL_stdinc.h>\n#include <SDL2/SDL_rwops.h>\n\n#include <DirManager/dirman.h>\n#include <Utils/files.h>\n#include <IniProcessor/ini_processing.h>\n#define FMT_NOEXCEPT\n#include <fmt/fmt_format.h>\n\n#include \"app_path_private.h\"\n\nstatic std::string s_assetsRoot;\nstatic std::string s_userDirectory;\nstatic std::string s_applicationPath;\n\n//! Default path to the internal sotrage directory\nstatic std::string m_androidSdCardPath = \"/storage/emulated/0\";\n//! Customized absolute path to the game assets directory\nstatic std::string m_androidGameAssetsPath;\n//! Application data absolute directory path (may be inaccessible from file managers since Android 11, etc.)\nstatic std::string m_androidAppDataPath;\n\n\nextern \"C\" JNIEXPORT void JNICALL\nJava_ru_wohlsoft_thextech_thextechActivity_setSdCardPath(\n    JNIEnv *env,\n    jclass type,\n    jstring sdcardPath_j\n)\n{\n    const char *sdcardPath;\n    (void)type;\n    sdcardPath = env->GetStringUTFChars(sdcardPath_j, nullptr);\n    m_androidSdCardPath = sdcardPath;\n    env->ReleaseStringUTFChars(sdcardPath_j, sdcardPath);\n}\n\nextern \"C\" JNIEXPORT void JNICALL\nJava_ru_wohlsoft_thextech_thextechActivity_setAppDataPath(\n    JNIEnv *env,\n    jclass type,\n    jstring appDataPath_j\n)\n{\n    const char *appDataPath;\n    (void)type;\n    appDataPath = env->GetStringUTFChars(appDataPath_j, nullptr);\n    m_androidAppDataPath = appDataPath;\n    env->ReleaseStringUTFChars(appDataPath_j, appDataPath);\n}\n\nextern \"C\" JNIEXPORT void JNICALL\nJava_ru_wohlsoft_thextech_thextechActivity_setGameAssetsPath(\n    JNIEnv *env,\n    jclass type,\n    jstring gameAssetsPath_j\n)\n{\n    const char *gameAssetsPath;\n    (void)type;\n    gameAssetsPath = env->GetStringUTFChars(gameAssetsPath_j, nullptr);\n    m_androidGameAssetsPath = gameAssetsPath;\n    env->ReleaseStringUTFChars(gameAssetsPath_j, gameAssetsPath);\n}\n\n\nvoid AppPathP::initDefaultPaths(const std::string &userDirName)\n{\n    // Application path\n    s_applicationPath = m_androidAppDataPath;\n\n    // Detect the user directory\n    if(!m_androidGameAssetsPath.empty())\n    {\n        s_userDirectory = m_androidGameAssetsPath; // Don't search the path, simply re-use the defined path\n        s_assetsRoot = m_androidGameAssetsPath;\n    }\n    else\n    {\n        s_userDirectory = m_androidSdCardPath;\n        DirMan homeDir(s_userDirectory);\n        if(s_userDirectory.empty() || !homeDir.exists())\n            s_userDirectory = \"/sdcard\";\n\n        s_userDirectory += userDirName;\n    }\n\n    // Check does user directory exists\n    DirMan appDir(s_userDirectory);\n    if(!appDir.exists() && !appDir.mkpath())\n        s_userDirectory = s_applicationPath;\n\n    if(!s_userDirectory.empty() && s_userDirectory.back() != '/')\n        s_userDirectory.push_back('/');\n\n    // Check is selected user directory writable\n    if(access(s_userDirectory.c_str(), W_OK) != 0) // If assets directory is not writable\n    {\n        // Use the application's data directory for logs / settings / screenshots / GIFs / gamesaves\n        s_userDirectory = fmt::format(\"{0}/{1}\",\n                                      m_androidAppDataPath,\n                                      Files::basename(s_userDirectory));\n    }\n\n    // Check presence for \".nomedia\" file\n    std::string noMediaFile = s_userDirectory + \".nomedia\";\n    if(!Files::fileExists(noMediaFile))\n    {\n        // If doesn't exists, make it\n        SDL_RWops *noMediaRWops = SDL_RWFromFile(noMediaFile.c_str(), \"w\");\n        if(noMediaRWops)\n        {\n            SDL_RWwrite(noMediaRWops, \"\\0\", 1, 1);\n            SDL_RWclose(noMediaRWops);\n        }\n    }\n\n    // Choose the assets directory\n    if(!m_androidGameAssetsPath.empty())\n        s_assetsRoot = m_androidGameAssetsPath;\n    else\n        s_assetsRoot.clear();\n\n    if(!s_assetsRoot.empty() && s_assetsRoot.back() != '/')\n        s_assetsRoot.push_back('/');\n\n    if(s_assetsRoot == s_userDirectory)\n        s_assetsRoot.clear();\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return s_applicationPath;\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return s_userDirectory;\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return s_assetsRoot;\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Legacy;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    return false;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_emscripten.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <emscripten.h>\n\n#include <SDL2/SDL_stdinc.h>\n#include <SDL2/SDL_rwops.h>\n#include <SDL2/SDL_filesystem.h>\n\n#include <Logger/logger.h>\n\n#include \"app_path_private.h\"\n\n\nstatic std::string s_assetsRoot;\nstatic std::string s_userDirectory;\nstatic std::string s_applicationPath;\n\nstatic bool loadingLocked = false;\n\n\nextern \"C\" void unlockLoadingCustomState()\n{\n    loadingLocked = false;\n}\n\nstatic void loadCustomState()\n{\n    loadingLocked = true;\n    EM_ASM(\n        FS.mkdir('/settings');\n        FS.mount(IDBFS, {}, '/settings');\n\n        FS.mkdir('/user');\n        FS.mount(IDBFS, {}, '/user');\n\n        // sync from persisted state into memory and then\n        // run the 'test' function\n        FS.syncfs(true, function (err) {\n            assert(!err);\n            Module.ccall('unlockLoadingCustomState', 'v', null, null, {async: true});\n        });\n    );\n\n    while(loadingLocked)\n        emscripten_sleep(10);\n}\n\nstatic void saveCustomState()\n{\n    loadingLocked = true;\n    pLogDebug(\"Synchronizing user file data...\");\n\n    EM_ASM(\n        FS.syncfs(function (err) {\n            assert(!err);\n            Module.ccall('unlockLoadingCustomState', 'v');\n        });\n    );\n\n    while(loadingLocked)\n        emscripten_sleep(10);\n}\n\n\n\n\nvoid AppPathP::initDefaultPaths(const std::string & /*userDirName*/)\n{\n    char *path = SDL_GetBasePath();\n    if(!path)\n    {\n#   ifndef DISABLE_LOGGING\n        std::fprintf(stderr, \"== Failed to recognize application path by using of SDL_GetBasePath! Using current working directory \\\"./\\\" instead.\\n\");\n        std::fflush(stderr);\n#   endif\n        path = SDL_strdup(\"./\");\n    }\n\n    s_applicationPath = std::string(path);\n    SDL_free(path);\n\n    if(!s_applicationPath.empty() && s_applicationPath.back() != '/')\n        s_applicationPath.push_back('/');\n\n    s_assetsRoot = s_applicationPath;\n    s_userDirectory = \"/user/\";\n\n    loadCustomState();\n\n#if !defined(DISABLE_LOGGING)\n    std::printf(\"== App Path is %s\\n\", s_applicationPath.c_str());\n    std::printf(\"== Assets Path is %s\\n\", s_assetsRoot.c_str());\n    std::printf(\"== User Path is %s\\n\", s_userDirectory.c_str());\n    fflush(stdout);\n#endif\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return s_applicationPath;\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return s_userDirectory;\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return s_assetsRoot;\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Legacy;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return \"/settings/\";\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    /*\n     * Report, does this platfor support portable mode or not\n     */\n    return false;\n}\n\nvoid AppPathP::syncFS()\n{\n    saveCustomState();\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_macos.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <CoreFoundation/CoreFoundation.h>\n#include <CoreServices/CoreServices.h>\n#include <PGE_File_Formats/pge_file_lib_globs.h> // For URL-Decode.\n\n#include <SDL2/SDL_stdinc.h>\n\n#include <DirManager/dirman.h>\n\n#include \"app_path_macos_dirs.h\"\n#include \"app_path_private.h\"\n\n\nstatic std::string s_assetsRoot;\nstatic std::string s_userDirectory;\nstatic std::string s_applicationPath;\nstatic std::string s_screenshotsPath;\nstatic std::string s_gifRecordPath;\n//! The name of application bundle to be re-used as the user directory name\nstatic std::string s_bundleName;\nstatic std::string s_sysHomeDir;\n\nstatic void decodeHome(std::string &path)\n{\n    if(!s_sysHomeDir.empty() && !path.empty() && path[0] == '~')\n    {\n        path.erase(path.begin());\n        path = s_sysHomeDir + path;\n    }\n}\n\n\nvoid AppPathP::initDefaultPaths(const std::string &userDirName)\n{\n    // Initialize the application path\n    {\n        CFURLRef appUrlRef;\n        appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());\n        CFStringRef filePathRef = CFURLGetString(appUrlRef);\n        char temporaryCString[PATH_MAX];\n        bzero(temporaryCString, PATH_MAX);\n        CFStringGetCString(filePathRef, temporaryCString, PATH_MAX, kCFStringEncodingUTF8);\n        s_applicationPath = PGE_FileFormats_misc::url_decode(std::string(temporaryCString));\n        {\n            s_bundleName.clear();\n\n            auto i = s_applicationPath.find_last_of(\".app\");\n\n            auto j = s_applicationPath.find_last_of('/', i);\n            if(j != std::string::npos)\n            {\n                s_bundleName = s_applicationPath.substr(j + 1);\n                auto k = s_bundleName.find_last_of(\".app\") - 3;\n                s_bundleName.erase(k, s_bundleName.size() - k);\n            }\n\n            i = s_applicationPath.find_last_of('/', i);\n\n            s_applicationPath.erase(i, s_applicationPath.size() - i);\n            if(s_applicationPath.compare(0, 7, \"file://\") == 0)\n                s_applicationPath.erase(0, 7);\n\n            if(!s_applicationPath.empty() && (s_applicationPath.back() != '/'))\n                s_applicationPath.push_back('/');\n        }\n        CFRelease(appUrlRef);\n    }\n\n    // Assets directory\n    s_assetsRoot.clear();\n\n#if defined(USE_BUNDLED_ASSETS) // When its release game with assets shipped with a game\n    {\n        CFURLRef appUrlRef;\n        appUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR(\"assets\"), NULL, NULL);\n        CFStringRef filePathRef = CFURLGetString(appUrlRef);\n        char temporaryCString[PATH_MAX];\n        bzero(temporaryCString, PATH_MAX);\n        CFStringGetCString(filePathRef, temporaryCString, PATH_MAX, kCFStringEncodingUTF8);\n        std::string path = PGE_FileFormats_misc::url_decode(std::string(temporaryCString));\n        if(path.compare(0, 7, \"file://\") == 0)\n            path.erase(0, 7);\n        s_assetsRoot = path;\n    }\n#endif\n\n    // Initialize the user directory\n    {\n        const char *homeDir;\n        char *home_dir = getHomeDir();\n\n        if(home_dir)\n        {\n            s_sysHomeDir = home_dir;\n            SDL_free(home_dir);\n            homeDir = s_sysHomeDir.c_str();\n        }\n        else\n            homeDir = SDL_getenv(\"HOME\");\n\n\n        std::string appSupport;\n\n        char *base_path = getAppSupportDir();\n        if(base_path)\n        {\n            appSupport.append(base_path);\n            SDL_free(base_path);\n        }\n\n        if(appSupport.empty())\n            appSupport = std::string(\"/Library/Application Support/\");\n\n        appSupport += userDirName;\n\n        if(!appSupport.empty() && appSupport.back() != '/')\n            appSupport.push_back('/');\n\n        if(homeDir)\n        {\n            // Current TheXTech distribution: per-bundle directories\n            if(!s_assetsRoot.empty() && !s_bundleName.empty())\n                s_userDirectory = std::string(homeDir) + \"/TheXTech Games/\" + s_bundleName;\n            // Shared userdata directory\n            else\n            {\n                s_userDirectory = std::string(homeDir) + userDirName;\n\n                // fallback to legacy directory if there is no custom directory set\n                std::string legacyUserDirectory = std::string(homeDir) + \"/TheXTech Games/\" + s_bundleName;\n                std::string legacyAssetsDirectory = std::string(homeDir) + \"/TheXTech Games/Debug Assets\";\n                if(userDirName == \"TheXTech\" && !DirMan::exists(s_userDirectory) && DirMan::exists(legacyUserDirectory))\n                {\n                    s_userDirectory = legacyUserDirectory;\n                    s_assetsRoot = legacyAssetsDirectory;\n                }\n            }\n        }\n        else\n            s_userDirectory = appSupport;\n    }\n\n    // Screenshots directory\n    {\n        std::string path = s_userDirectory;\n        char *base_path = getScreenCaptureDir();\n        if(base_path)\n        {\n            path = base_path;\n            SDL_free(base_path);\n        }\n\n        decodeHome(path);\n\n        s_screenshotsPath = path + \"/TheXTech Game Screenshots/\";\n        s_gifRecordPath = path + \"/TheXTech Game Screenshots/gif-recordings/\";\n    }\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return s_applicationPath;\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return s_userDirectory;\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return s_assetsRoot;\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Legacy;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    return s_screenshotsPath;\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    return s_gifRecordPath;\n}\n\nstd::string AppPathP::logsRoot()\n{\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    return false;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_macos_dirs.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef APP_PATH_MACOSX_H\n#define APP_PATH_MACOSX_H\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\nchar * getHomeDir(void);\nchar * getAppSupportDir(void);\nchar * getScreenCaptureDir(void);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* APP_PATH_MACOSX_H */\n"
  },
  {
    "path": "lib/AppPath/private/app_path_macos_dirs.m",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <Foundation/Foundation.h>\n#include <SDL2/SDL_error.h>\n#include <SDL2/SDL_filesystem.h>\n\n#include \"app_path_macos_dirs.h\"\n\nchar *getHomeDir(void)\n{\n    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];\n    NSString *str;\n    char *retval = NULL;\n    const char *base;\n\n    str = NSHomeDirectory();\n    base = [str fileSystemRepresentation];\n\n    if(base)\n    {\n        const size_t len = SDL_strlen(base) + 4;\n        retval = (char *)SDL_malloc(len);\n\n        if(retval == NULL)\n            SDL_OutOfMemory();\n        else\n            SDL_snprintf(retval, len, \"%s\", base);\n    }\n\n    [pool drain];\n    return retval;\n}\n\nchar * getAppSupportDir(void)\n{\n    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];\n    char *retval = NULL;\n    NSString *str;\n    const char *base;\n\n    NSArray *array = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);\n\n    if([array count] > 0) /* we only want the first item in the list. */\n    {\n        str = [array objectAtIndex:0];\n        base = [str fileSystemRepresentation];\n\n        if(base)\n        {\n            const size_t len = SDL_strlen(base) + 4;\n            retval = (char *)SDL_malloc(len);\n\n            if(retval == NULL)\n                SDL_OutOfMemory();\n            else\n                SDL_snprintf(retval, len, \"%s\", base);\n        }\n    }\n\n    [pool drain];\n    return retval;\n}\n\nchar * getScreenCaptureDir(void)\n{\n    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];\n    char *retval = NULL;\n    NSUserDefaults *appUserDefaults;\n    NSDictionary *prefsDict;\n    NSString *str;\n    const char *base;\n\n    /* Get current screencapture location */\n    appUserDefaults = [[NSUserDefaults alloc] init];\n    [appUserDefaults addSuiteNamed:@\"com.apple.screencapture\"];\n    prefsDict = [appUserDefaults dictionaryRepresentation];\n\n    str = [prefsDict valueForKey:@\"location\"];\n    base = [str fileSystemRepresentation];\n\n    if(base)\n    {\n        const size_t len = SDL_strlen(base) + 4;\n        retval = (char *)SDL_malloc(len);\n\n        if(retval == NULL)\n            SDL_OutOfMemory();\n        else\n            SDL_snprintf(retval, len, \"%s\", base);\n    }\n\n    [pool drain];\n    return retval;\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_old.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include <SDL2/SDL_stdinc.h>\n#include <SDL2/SDL_rwops.h>\n#include <SDL2/SDL_filesystem.h>\n\n#include <DirManager/dirman.h>\n#include <Utils/files.h>\n#include <IniProcessor/ini_processing.h>\n#define FMT_NOEXCEPT\n#include <fmt/fmt_format.h>\n\n#ifdef __ANDROID__\n#   include <unistd.h>\n//#   include <md5/md5.h>\n#   include <jni.h>\n#   if 1\n#       undef JNIEXPORT\n#       undef JNICALL\n#       define JNIEXPORT extern \"C\"\n#       define JNICALL\n#   endif\n#endif\n\n#ifdef __APPLE__\n#include <CoreFoundation/CoreFoundation.h>\n#include <CoreServices/CoreServices.h>\n#include <PGE_File_Formats/pge_file_lib_private.h>//It's only exception for macOS here to get URL-Decode. Never include this!\n#include \"app_path_macos_dirs.h\"\n#endif\n\n#ifdef __gnu_linux__\n#include <unistd.h>\n#include <sys/types.h>\n#include <pwd.h>\n#endif\n\n#ifdef _WIN32\n#include <windows.h>\n#include <winreg.h>\n#include <algorithm> // std::replace from \\\\ into /\n#endif\n\n#ifdef __EMSCRIPTEN__\n#include <emscripten.h>\n\nstatic bool loadingLocked = false;\n\nextern \"C\" void unlockLoadingCustomState()\n{\n    loadingLocked = false;\n}\n\nstatic void loadCustomState()\n{\n    loadingLocked = true;\n    EM_ASM(\n        FS.mkdir('/settings');\n        FS.mount(IDBFS, {}, '/settings');\n\n        // sync from persisted state into memory and then\n        // run the 'test' function\n        FS.syncfs(true, function (err) {\n            assert(!err);\n            ccall('unlockLoadingCustomState', 'v', null, null, {async: true});\n        });\n    );\n\n    while(loadingLocked)\n        emscripten_sleep(10);\n}\n\nstatic void saveCustomState()\n{\n    loadingLocked = true;\n    EM_ASM(\n        FS.syncfs(function (err) {\n            assert(!err);\n            ccall('unlockLoadingCustomState', 'v');\n        });\n    );\n\n    while(loadingLocked)\n        emscripten_sleep(10);\n}\n#endif\n\n#include \"../app_path.h\"\n#include \"../../version.h\"\n\nstatic std::string ApplicationPathSTD;\n\nstd::string AppPathManager::m_settingsPath;\nstd::string AppPathManager::m_assetsPath;\nstd::string AppPathManager::m_userPath;\n\nstd::string AppPathManager::m_screenshotsPath;\nstd::string AppPathManager::m_gifrecordingsPath;\nstd::string AppPathManager::m_logsPath;\n\nstd::string AppPathManager::m_customAssetsRoot;\nstd::string AppPathManager::m_customUserDirectory;\n\n#if defined(__APPLE__)\n//! The name of application bundle to be re-used as the user directory name\nstatic std::string s_bundleName;\nstatic std::string s_defaultAssetsRoot;\n#endif\n\n#if defined(VITA)\nstatic std::string m_vitaAppDataPath = \"ux0:data/TheXTech/\";\n#endif\n\n#if defined(__ANDROID__)\n//! Default path to the internal sotrage directory\nstatic std::string m_androidSdCardPath = \"/storage/emulated/0\";\n//! Customized absolute path to the game assets directory\nstatic std::string m_androidGameAssetsPath;\n//! Application data absolute directory path (may be inaccessible from file managers since Android 11, etc.)\nstatic std::string m_androidAppDataPath;\n\nJNIEXPORT void JNICALL\nJava_ru_wohlsoft_thextech_thextechActivity_setSdCardPath(\n    JNIEnv *env,\n    jclass type,\n    jstring sdcardPath_j\n)\n{\n    const char *sdcardPath;\n    (void)type;\n    sdcardPath = env->GetStringUTFChars(sdcardPath_j, nullptr);\n    m_androidSdCardPath = sdcardPath;\n    env->ReleaseStringUTFChars(sdcardPath_j, sdcardPath);\n}\n\nJNIEXPORT void JNICALL\nJava_ru_wohlsoft_thextech_thextechActivity_setAppDataPath(\n    JNIEnv *env,\n    jclass type,\n    jstring appDataPath_j\n)\n{\n    const char *appDataPath;\n    (void)type;\n    appDataPath = env->GetStringUTFChars(appDataPath_j, nullptr);\n    m_androidAppDataPath = appDataPath;\n    env->ReleaseStringUTFChars(appDataPath_j, appDataPath);\n}\n\nJNIEXPORT void JNICALL\nJava_ru_wohlsoft_thextech_thextechActivity_setGameAssetsPath(\n    JNIEnv *env,\n    jclass type,\n    jstring gameAssetsPath_j\n)\n{\n    const char *gameAssetsPath;\n    (void)type;\n    gameAssetsPath = env->GetStringUTFChars(gameAssetsPath_j, nullptr);\n    m_androidGameAssetsPath = gameAssetsPath;\n    env->ReleaseStringUTFChars(gameAssetsPath_j, gameAssetsPath);\n}\n#endif\n\n#ifdef __APPLE__\n\n#   ifndef USERDATA_ROOT_NAME\n#       define USERDATA_ROOT_NAME \"TheXTech\"\n#   endif\n\nstd::string AppPathManager::m_userDataRoot;\n#endif\nbool AppPathManager::m_isPortable = false;\n\n#if defined(USER_DIR_NAME)\n#define UserDirName \"/\" USER_DIR_NAME\n#elif defined(__ANDROID__) || defined(__APPLE__) || defined(__HAIKU__)\n#define UserDirName \"/PGE Project\"\n#else\n#define UserDirName \"/.PGE_Project\"\n#endif\n\n/**\n * @brief Retreive User Home directory with appending of the PGE user data directory\n * @return Absolute directory path\n */\nstatic std::string getPgeUserDirectory()\n{\n    std::string path;\n\n#if defined(__APPLE__)\n    {\n        char *base_path = getAppSupportDir();\n        if(base_path)\n        {\n            path.append(base_path);\n            SDL_free(base_path);\n        }\n    }\n\n#elif defined(_WIN32)\n    {\n        wchar_t pathW[MAX_PATH];\n        DWORD path_len = GetEnvironmentVariableW(L\"UserProfile\", pathW, MAX_PATH);\n        assert(path_len);\n        path.resize(path_len * 2);\n        path_len = WideCharToMultiByte(CP_UTF8, 0,\n                                       pathW,       path_len,\n                                       &path[0],    path.size(),\n                                       0, FALSE);\n        path.resize(path_len);\n    }\n\n#elif defined(__ANDROID__)\n    if(!m_androidGameAssetsPath.empty())\n    {\n        if(access(m_androidGameAssetsPath.c_str(), W_OK) != 0) // If assets directory is not writable\n        {\n            // Use the application's data directory for logs / settings / screenshots / GIFs\n            return fmt::format(\"{0}/{1}\",\n                               m_androidAppDataPath,\n                               Files::basename(m_androidGameAssetsPath));\n        }\n\n        return m_androidGameAssetsPath; // Don't search the path, simply re-use the defined path\n    }\n\n    path = m_androidSdCardPath;\n\n    DirMan homeDir(path);\n    if(!homeDir.exists())\n        path = \"/sdcard\";\n\n#elif defined(__HAIKU__)\n    {\n        const char *home = SDL_getenv(\"HOME\");\n        path.append(home);\n    }\n\n#elif defined(__gnu_linux__)\n    {\n        passwd *pw = getpwuid(getuid());\n        path.append(pw->pw_dir);\n    }\n#endif\n\n    return path.empty() ? std::string(\".\") : (path + UserDirName);\n}\n\n\nvoid AppPathManager::initAppPath()\n{\n#if defined(__APPLE__)\n    {\n        CFURLRef appUrlRef;\n        appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());\n        CFStringRef filePathRef = CFURLGetString(appUrlRef);\n        char temporaryCString[PATH_MAX];\n        bzero(temporaryCString, PATH_MAX);\n        CFStringGetCString(filePathRef, temporaryCString, PATH_MAX, kCFStringEncodingUTF8);\n        ApplicationPathSTD = PGE_URLDEC(std::string(temporaryCString));\n        {\n            s_bundleName = USERDATA_ROOT_NAME;\n\n            auto i = ApplicationPathSTD.find_last_of(\".app\");\n\n            auto j = ApplicationPathSTD.find_last_of('/', i);\n            if(j != std::string::npos)\n            {\n                s_bundleName = ApplicationPathSTD.substr(j + 1);\n                auto k = s_bundleName.find_last_of(\".app\") - 3;\n                s_bundleName.erase(k, s_bundleName.size() - k);\n            }\n\n            i = ApplicationPathSTD.find_last_of('/', i);\n\n            ApplicationPathSTD.erase(i, ApplicationPathSTD.size() - i);\n            if(ApplicationPathSTD.compare(0, 7, \"file://\") == 0)\n                ApplicationPathSTD.erase(0, 7);\n\n            if(!ApplicationPathSTD.empty() && (ApplicationPathSTD.back() != '/'))\n                ApplicationPathSTD.push_back('/');\n        }\n        CFRelease(appUrlRef);\n    }\n\n#elif defined(__ANDROID__)\n    ApplicationPathSTD = m_androidAppDataPath;\n\n#elif defined(VITA)\n    ApplicationPathSTD = m_vitaAppDataPath;\n\n#else // all other platforms (Windows, Linux, Haiku, etc.)\n    char *path = SDL_GetBasePath();\n    if(!path)\n    {\n#   ifndef DISABLE_LOGGING\n        std::fprintf(stderr, \"== Failed to recognize application path by using of SDL_GetBasePath! Using current working directory \\\"./\\\" instead.\\n\");\n        std::fflush(stderr);\n#   endif\n        path = SDL_strdup(\"./\");\n    }\n    ApplicationPathSTD = std::string(path);\n#   if defined(_WIN32)\n    std::replace(ApplicationPathSTD.begin(), ApplicationPathSTD.end(), '\\\\', '/');\n#   endif\n    SDL_free(path);\n\n#endif // __APPLE__/__ANDROID__\n\n\n#ifdef __EMSCRIPTEN__\n    loadCustomState();\n#endif\n\n    // When user directory is redefined externally\n    if(!m_customUserDirectory.empty())\n    {\n        m_userPath = m_customUserDirectory;\n        initSettingsPath();\n        return;\n    }\n\n    if(checkPortable())\n        return;\n\n    std::string userDirPath = getPgeUserDirectory();\n    if(!userDirPath.empty())\n    {\n        DirMan appDir(userDirPath);\n        if(!appDir.exists() && !appDir.mkpath())\n            goto defaultSettingsPath;\n// #ifdef __APPLE__\n//         if(!DirMan::exists(ApplicationPathSTD + \"/Data directory\"))\n//             symlink((userDirPath).c_str(), (ApplicationPathSTD + \"/Data directory\").c_str());\n// #endif\n\n#ifdef __ANDROID__\n        std::string noMediaFile = userDirPath + \"/.nomedia\";\n        if(!Files::fileExists(noMediaFile))\n        {\n            SDL_RWops *noMediaRWops = SDL_RWFromFile(noMediaFile.c_str(), \"w\");\n            if(noMediaRWops)\n            {\n                SDL_RWwrite(noMediaRWops, \"\\0\", 1, 1);\n                SDL_RWclose(noMediaRWops);\n            }\n        }\n#endif\n        m_userPath = appDir.absolutePath();\n\n#if !defined(__EMSCRIPTEN__) && !defined(USER_DIR_NAME)\n#   if defined(__ANDROID__)\n        if(m_androidGameAssetsPath.empty())\n            m_userPath.append(\"/thextech/\");\n        else\n            m_userPath.append(\"/\");\n#   else\n        m_userPath.append(\"/thextech/\");\n#   endif\n#else\n        m_userPath.append(\"/\");\n#endif\n        initSettingsPath();\n    }\n    else\n    {\n        goto defaultSettingsPath;\n    }\n\n    return;\ndefaultSettingsPath:\n    m_userPath = ApplicationPathSTD;\n    initSettingsPath();\n#if defined(__EMSCRIPTEN__) && !defined(DISABLE_LOGGING)\n    std::printf(\"== App Path is %s\\n\", ApplicationPathSTD.c_str());\n    std::printf(\"== User Path is %s\\n\", m_userPath.c_str());\n    fflush(stdout);\n#endif\n}\n\nstd::string AppPathManager::settingsFileSTD() // Writable\n{\n    return m_settingsPath + \"thextech.ini\";\n}\n\nstd::string AppPathManager::settingsRoot() // Writable\n{\n    return m_settingsPath;\n}\n\nstd::string AppPathManager::settingsControlsFileSTD() // Writable\n{\n    return m_settingsPath + \"controls.ini\";\n}\n\nstd::string AppPathManager::userAppDirSTD() // Writable\n{\n    return m_userPath;\n}\n\nstd::string AppPathManager::assetsRoot() // Readable\n{\n    if(!m_customAssetsRoot.empty())\n        return m_customAssetsRoot;\n\n#if defined(FIXED_ASSETS_PATH) // Fixed assets path, for the rest of UNIX-like OS packages\n    std::string assets(FIXED_ASSETS_PATH);\n    if(!assets.empty() && assets.back() != '/')\n        assets.push_back('/');\n    return assets;\n\n#elif defined(__APPLE__)\n#   if defined(USE_BUNDLED_ASSETS) // When its release game with assets shipped with a game\n    CFURLRef appUrlRef;\n    appUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR(\"assets\"), NULL, NULL);\n    CFStringRef filePathRef = CFURLGetString(appUrlRef);\n    char temporaryCString[PATH_MAX];\n    bzero(temporaryCString, PATH_MAX);\n    CFStringGetCString(filePathRef, temporaryCString, PATH_MAX, kCFStringEncodingUTF8);\n    std::string path = PGE_URLDEC(std::string(temporaryCString));\n    if(path.compare(0, 7, \"file://\") == 0)\n        path.erase(0, 7);\n    return path;\n#   else // When it's debug-mode built bundle, external assets will be used\n    return s_defaultAssetsRoot;\n#   endif\n\n#elif defined(__ANDROID__)\n    std::string assets = m_androidGameAssetsPath;\n    if(!assets.empty() && assets.back() != '/')\n        assets.push_back('/');\n    return assets;\n\n#else\n    return m_userPath;\n\n#endif\n}\n\nvoid AppPathManager::setAssetsRoot(const std::string &root)\n{\n    m_customAssetsRoot = root;\n    if(!m_customAssetsRoot.empty() && m_customAssetsRoot.back() != '/')\n        m_customAssetsRoot.push_back('/');\n}\n\nvoid AppPathManager::setUserDirectory(const std::string& root)\n{\n    m_customUserDirectory = root;\n    if(!m_customUserDirectory.empty() && m_customUserDirectory.back() != '/')\n        m_customUserDirectory.push_back('/');\n}\n\nstd::string AppPathManager::logsDir() // Writable\n{\n    return m_userPath + \"logs/\";\n}\n\nstd::string AppPathManager::languagesDir() // Readable\n{\n#if defined(__APPLE__)\n    CFURLRef appUrlRef;\n    appUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR(\"languages\"), NULL, NULL);\n    CFStringRef filePathRef = CFURLGetString(appUrlRef);\n    char temporaryCString[PATH_MAX];\n    bzero(temporaryCString, PATH_MAX);\n    CFStringGetCString(filePathRef, temporaryCString, PATH_MAX, kCFStringEncodingUTF8);\n    std::string path = PGE_URLDEC(std::string(temporaryCString));\n    if(path.compare(0, 7, \"file://\") == 0)\n        path.erase(0, 7);\n    return path;\n\n#elif defined(__ANDROID__)\n    return AppPathManager::assetsRoot() + \"languages/\";\n\n#else\n    return ApplicationPathSTD + \"languages/\";\n\n#endif\n}\n\nstd::string AppPathManager::screenshotsDir() // Writable\n{\n#ifndef __APPLE__\n    return m_userPath + \"screenshots/\";\n\n#else\n    std::string path = m_userPath;\n    char *base_path = getScreenCaptureDir();\n    if(base_path)\n    {\n        path = base_path;\n        SDL_free(base_path);\n    }\n    return path + \"/TheXTech Game Screenshots/\";\n\n#endif\n}\n\nstd::string AppPathManager::gifRecordsDir() // Writable\n{\n#ifndef __APPLE__\n    return m_userPath + \"gif-recordings/\";\n\n#else\n    std::string path = m_userPath;\n    char *base_path = getScreenCaptureDir();\n    if(base_path)\n    {\n        path = base_path;\n        SDL_free(base_path);\n    }\n    return path + \"/TheXTech Game Screenshots/gif-recordings/\";\n\n#endif\n}\n\nstd::string AppPathManager::gameSaveRootDir() // Writable\n{\n    return m_settingsPath + \"gamesaves/\";\n}\n\nstd::string AppPathManager::gameplayRecordsRootDir() // Writable\n{\n    return m_userPath + \"gameplay-records/\";\n}\n\nstd::string AppPathManager::userWorldsRootDir() // Readable\n{\n#ifdef __APPLE__\n    return m_userDataRoot + \"worlds/\";\n#else\n    return m_userPath + \"worlds/\";\n#endif\n}\n\nstd::string AppPathManager::userBattleRootDir() // Readable\n{\n#ifdef __APPLE__\n    return m_userDataRoot + \"battle/\";\n#else\n    return m_userPath + \"battle/\";\n#endif\n}\n\nvoid AppPathManager::install()\n{\n    std::string path = getPgeUserDirectory();\n\n    if(!path.empty())\n    {\n        DirMan appDir(path);\n        if(!appDir.exists())\n            appDir.mkpath(path);\n    }\n}\n\n//bool AppPathManager::isPortable()\n//{\n//    return m_isPortable;\n//}\n\nbool AppPathManager::checkPortable()\n{\n    if(m_settingsPath.empty())\n        m_settingsPath = ApplicationPathSTD;\n\n    if(m_userPath.empty())\n        m_userPath = ApplicationPathSTD;\n\n    if(!Files::fileExists(settingsFileSTD()))\n        return false;\n\n    m_isPortable = false;\n\n    IniProcessing checkForPort(settingsFileSTD());\n    checkForPort.beginGroup(\"Main\");\n    m_isPortable = checkForPort.value(\"force-portable\", false).toBool();\n    checkForPort.endGroup();\n\n    if(m_isPortable)\n        initSettingsPath();\n\n    return m_isPortable;\n}\n\nbool AppPathManager::userDirIsAvailable()\n{\n    return (m_userPath != assetsRoot());\n}\n\n#ifdef __EMSCRIPTEN__\nvoid AppPathManager::syncFs()\n{\n    saveCustomState();\n}\n#endif\n\n\nvoid AppPathManager::initSettingsPath()\n{\n    m_settingsPath = m_userPath + \"settings/\";\n\n#ifdef __APPLE__\n    {\n        const char *homeDir = std::getenv(\"HOME\");\n        if(homeDir)\n        {\n            m_userDataRoot = std::string(homeDir) + \"/TheXTech Games/\" + s_bundleName;\n            m_userDataRoot.append(\"/\");\n            // Automatically create an infrastructure\n            if(!DirMan::exists(m_userDataRoot))\n                DirMan::mkAbsPath(m_userDataRoot);\n            if(!DirMan::exists(m_userDataRoot + \"worlds\"))\n                DirMan::mkAbsPath(m_userDataRoot + \"worlds\");\n            if(!DirMan::exists(m_userDataRoot + \"battle\"))\n                DirMan::mkAbsPath(m_userDataRoot + \"battle\");\n            m_settingsPath = m_userDataRoot + \"settings/\";\n\n            s_defaultAssetsRoot = std::string(homeDir) + \"/TheXTech Games/Debug Assets/\";\n        }\n        else\n            m_userDataRoot = m_userPath;\n    }\n#endif\n\n    if(Files::fileExists(m_settingsPath))\n        Files::deleteFile(m_settingsPath);//Just in case, avoid mad jokes with making same-named file as settings folder\n\n    if(!DirMan::exists(m_settingsPath))\n        DirMan::mkAbsPath(m_settingsPath);\n\n    // Also make the game saves root folder to be exist\n    if(!DirMan::exists(gameSaveRootDir()))\n        DirMan::mkAbsPath(gameSaveRootDir());\n\n    // And make empty worlds and battle directories too, make a hint for user\n    if(!DirMan::exists(userWorldsRootDir()))\n        DirMan::mkAbsPath(userWorldsRootDir());\n    if(!DirMan::exists(userBattleRootDir()))\n        DirMan::mkAbsPath(userBattleRootDir());\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_private.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef APP_PATH_PRIVATE_H\n#define APP_PATH_PRIVATE_H\n\n#include <string>\n\n#include \"AppPath/app_path.h\"\n\nnamespace AppPathP\n{\n\n/*!\n * \\brief Initialize all default internals\n */\nextern void initDefaultPaths(const std::string &userDirName);\n\n/*!\n * \\brief Get the path to the executable's directory (on some platforms may be empty)\n * \\return Path to the executable's directory, or an empty string if unsupported\n */\nextern std::string appDirectory();\n\n/*!\n * \\brief Get the default path to the writable user directory\n * \\return Path to the user directory\n */\nextern std::string userDirectory();\n\n/*!\n * \\brief Get the default path to the system's read-only assets root\n * \\return Path to the assets root directory, or an empty string if none is present\n */\nextern std::string assetsRoot();\n\n/*!\n * \\brief Returns the type of the assets root directory\n *\n * - Legacy should point to a single asset pack and will result in non-nested user directories\n * - Single should point to a single modern asset pack and will result in nested user directories\n * - Multiple should point to a directory containing multiple modern asset packs and will result in nested user directories\n */\nextern AssetsPathType assetsRootType();\n\n/*!\n * \\brief Get the default path to the writable settings directory (if empty, store at the usre directory)\n * \\return Path to the system-wide settings directory\n */\nextern std::string settingsRoot();\n\n/*!\n * \\brief Get the default path to the writable gamesaves directory (if empty, store at the usre directory)\n * \\return Path to the system-wide gamesaves directory\n */\nextern std::string gamesavesRoot();\n\n/*!\n * \\brief Default directory for screenshots (if empty, store at the user directory)\n * \\return Path to the system-wide screenshots directory\n */\nextern std::string screenshotsRoot();\n\n/*!\n * \\brief Default directory for GIF recordings (if empty, store at the usre directory)\n * \\return Path to the system-wide GIF recording directory\n */\nextern std::string gifRecsRoot();\n\n/*!\n * \\brief Default directory for logs (if empty, store at the usre directory)\n * \\return Path to the system-wide logs directory\n */\nextern std::string logsRoot();\n\n/*!\n * \\brief Reports does platform supports portable mode\n * \\return true if Portable mode is supported\n *\n * When it returns false, the \"thextech.ini\" with \"force-portable\" flag will never been checked\n */\nextern bool portableAvailable();\n\n/*!\n * \\brief Run the FS synchronization (Emscripten only)\n */\nextern void syncFS();\n\n}\n\n#endif // APP_PATH_PRIVATE_H\n"
  },
  {
    "path": "lib/AppPath/private/app_path_skeleton.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include \"app_path_private.h\"\n\n\nvoid AppPathP::initDefaultPaths(const std::string &userDirName)\n{\n\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return std::string();\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return std::string();\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return std::string();\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Single;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    /*\n     * Report, does this platfor support portable mode or not\n     */\n    return true;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_switch.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include \"app_path_private.h\"\n\nstatic const char* s_userDirectory = \"/TheXTech/\";\n\n\nvoid AppPathP::initDefaultPaths(const std::string & /*userDirName*/)\n{\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return std::string();\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return s_userDirectory;\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return std::string();\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Single;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    return false;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_unix.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <unistd.h>\n#include <sys/types.h>\n#include <pwd.h>\n\n#include <DirManager/dirman.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_filesystem.h\"\n\n#include \"app_path_private.h\"\n\nstatic std::string s_userDirectory;\n\nstatic std::string s_gameInstallDirectory;\n\nstatic std::string s_applicationPath;\n\n//! The root for installed data (note: asset packs are placed at /usr/share/games/TheXTech/<pack-name> by default)\n#ifdef __HAIKU__\n    // FIXME: check that this Haiku-specific logic is correct\n    static const char* s_gamesSysDir = \"/boot/system/data/\" THEXTECH_DIRECTORY_PREFIX \"/\";\n#else\n    static const char* s_gamesSysDir = \"/usr/share/games/\" THEXTECH_DIRECTORY_PREFIX \"/\";\n#endif\n\nstatic std::string s_getEnvNotNull(const char *env)\n{\n    const char *e = SDL_getenv(env);\n    if(e)\n        return std::string(e);\n    else\n        return std::string();\n}\n\nvoid AppPathP::initDefaultPaths(const std::string &userDirName)\n{\n    std::string homePath;\n    std::string userDir;\n\n    // check for deployment as AppImage when looking for system-installed assets\n    s_gameInstallDirectory = s_getEnvNotNull(\"APPDIR\");\n    s_gameInstallDirectory += s_gamesSysDir;\n\n    // Environment\n    const char *env_home = SDL_getenv(\"HOME\");\n    userDir = s_getEnvNotNull(\"XDG_DATA_HOME\");\n\n    // Init home directory\n#if defined(__HAIKU__)\n    if(env_home)\n        homePath.append(env_home);\n#else\n    passwd *pw = getpwuid(getuid());\n    if(pw)\n        homePath.append(pw->pw_dir);\n    else if(env_home)\n        homePath.append(env_home);\n#endif\n\n    // Set default paths if environments aren't defined\n    if(homePath.empty())\n        homePath = std::string(\".\");\n\n    if(userDir.empty())\n        userDir = homePath + \"/.local/share\";\n\n    userDir += \"/\";\n\n    // use modern user directory by default\n    s_userDirectory = userDir + userDirName;\n\n    // fallback to legacy directory if there is no custom directory set\n    std::string legacyUserDirectory = homePath + \"/.PGE_Project/thextech\";\n    if(userDirName == \"TheXTech\" && !DirMan::exists(s_userDirectory) && DirMan::exists(legacyUserDirectory))\n        s_userDirectory = legacyUserDirectory;\n\n    // find the application's own path\n    char *appPath = SDL_GetBasePath();\n    if(!appPath)\n    {\n#ifndef DISABLE_LOGGING\n        std::fprintf(stderr, \"== Failed to recognize application path by using of SDL_GetBasePath! Using current working directory \\\"./\\\" instead.\\n\");\n        std::fflush(stderr);\n#endif\n        appPath = SDL_strdup(\"./\");\n    }\n\n    s_applicationPath = std::string(appPath);\n    SDL_free(appPath);\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return s_applicationPath;\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return s_userDirectory;\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return s_gameInstallDirectory;\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Multiple;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    /*\n     * Report, does this platfor support portable mode or not\n     */\n    return true;\n}\n\nvoid AppPathP::syncFS()\n{}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_vita.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include \"app_path_private.h\"\n\nstatic std::string s_userDirectory;\n\n\nvoid AppPathP::initDefaultPaths(const std::string & /*userDirName*/)\n{\n    s_userDirectory = \"ux0:data/\" THEXTECH_DIRECTORY_PREFIX \"/\";\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return std::string();\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return s_userDirectory;\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return std::string();\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Single;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    return false;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_wii.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <vector>\n#include <string>\n#include <fat.h>\n\n#include <DirManager/dirman.h>\n\n#include \"../app_path.h\"\n#include \"app_path_private.h\"\n\nconstexpr const char* s_assetRoot = \"/\" THEXTECH_DIRECTORY_PREFIX \"/\";\n\nvoid AppPathP::initDefaultPaths(const std::string &userDirName)\n{\n    fatInitDefault();\n\n    (void)userDirName;\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return std::string();\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return s_assetRoot;\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return std::string();\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Single;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    /*\n     * Report, does this platfor support portable mode or not\n     */\n    return false;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_wiiu.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <vector>\n#include <string>\n\n#include <nn/save.h>\n#include <coreinit/title.h>\n\n#include \"../app_path.h\"\n#include <DirManager/dirman.h>\n#include <fmt/fmt_printf.h>\n#include \"app_path_private.h\"\n\n//! The assets&user directory for standalone RPX when SD card is plugged\nconstexpr const char* s_assetRootSD = \"fs:/vol/external01/wiiu/\" THEXTECH_DIRECTORY_PREFIX \"/\";\n//! The assets&user directory for standalone RPX when USB stick is plugged\nconstexpr const char* s_assetRootUSB = \"usb:/wiiu/\" THEXTECH_DIRECTORY_PREFIX \"/\";\n//! The assets directory for WUHB/installable package of the game\nconstexpr const char* s_assetRootContent = \"fs:/vol/content/\";\n\n//! Settings and gamesave directory for WUHB and installable packages\nconstexpr const char* s_assetSaveRoot = \"fs:/vol/save/common/\";\n\n//! Read-only assets directory (may be separated when WUHB or an installable thing)\nstatic std::string s_assetRoot;\n//! Writeable user directory for logs, screenshots, settings and extra user's episodes\nstatic std::string s_userDir;\n\n\nvoid AppPathP::initDefaultPaths(const std::string &userDirName)\n{\n    (void)userDirName;\n\n    // When running game under WUHB package or as an installable game (/vol/content and /vol/save dirs do exist)\n    // Also check that /vol/content/graphics/ exists (assets non-empty)\n    if(DirMan::exists(s_assetRootContent) && DirMan::exists(std::string(s_assetRootContent) + \"graphics/\"))\n    {\n        SAVEInit();\n        SAVEInitCommonSaveDir();\n\n        // Consider /vol/content (WUHB resources) as read-only Assets directory\n        s_assetRoot = s_assetRootContent;\n        // And consider /vol/save directory as default User Directory\n        s_userDir = s_assetSaveRoot;\n\n        // When USB stick is presented, store logs, screenshots and find user's episodes on it\n        if(DirMan::exists(\"usb:/\"))\n            s_userDir = s_assetRootUSB;\n        // When SD card is presented, store logs, screenshots and find user's episodes on it\n        else if(DirMan::exists(\"fs:/vol/external01/\"))\n            s_userDir = s_assetRootSD;\n    }\n    else if(DirMan::exists(s_assetRootUSB))\n    {\n        s_assetRoot.clear();\n        s_userDir = s_assetRootUSB;\n    }\n    else\n    {\n        s_assetRoot.clear();\n        s_userDir = s_assetRootSD;\n    }\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return std::string();\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return s_userDir;\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return s_assetRoot;\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Single;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    /*\n     * Report, does this platfor support portable mode or not\n     */\n    return false;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n"
  },
  {
    "path": "lib/AppPath/private/app_path_win32.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <windows.h>\n#include <winreg.h>\n#include <shlobj.h>\n#include <algorithm> // std::replace from \\\\ into /\n\n#include <SDL2/SDL_stdinc.h>\n#include <SDL2/SDL_assert.h>\n#include <SDL2/SDL_filesystem.h>\n\n#include <DirManager/dirman.h>\n\n#include \"app_path_private.h\"\n\nstatic std::string s_userDirectory;\nstatic std::string s_applicationPath;\n\nstatic void s_toUtf8(std::string &ret, wchar_t *str, DWORD len)\n{\n    int outLen;\n    ret.clear();\n    ret.resize(len * 4); // 4x size to ensure size is enough\n    outLen = WideCharToMultiByte(CP_UTF8, 0,\n                                 str,       len,\n                                 &ret[0],   (int)ret.size(),\n                                 0, FALSE);\n    ret.resize(outLen);\n\n    // Convert slashes\n    for(auto &c : ret)\n    {\n        if(c == '\\\\')\n            c = '/';\n    }\n}\n\nvoid AppPathP::initDefaultPaths(const std::string &userDirName)\n{\n    wchar_t pathBuffer[MAX_PATH] = L\"\";\n\n    std::string roamingPath, homePath;\n\n    if(FAILED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, pathBuffer)))\n    {\n#   ifndef DISABLE_LOGGING\n        std::fprintf(stderr, \"== Failed to retrieve the common AppData\\n\");\n        std::fflush(stderr);\n#   endif\n    }\n    else\n        s_toUtf8(roamingPath, pathBuffer, (DWORD)SDL_wcslen(pathBuffer));\n\n    // Application path\n    char *path = SDL_GetBasePath();\n    if(!path)\n    {\n#   ifndef DISABLE_LOGGING\n        std::fprintf(stderr, \"== Failed to recognize application path by using of SDL_GetBasePath! Using current working directory \\\"./\\\" instead.\\n\");\n        std::fflush(stderr);\n#   endif\n        path = SDL_strdup(\"./\");\n    }\n\n    s_applicationPath = std::string(path);\n    std::replace(s_applicationPath.begin(), s_applicationPath.end(), '\\\\', '/');\n    SDL_free(path);\n\n    // User directory\n    DWORD path_len = GetEnvironmentVariableW(L\"UserProfile\", pathBuffer, MAX_PATH);\n    SDL_assert_release(path_len);\n    s_toUtf8(homePath, pathBuffer, path_len);\n\n    if(homePath.empty() && roamingPath.empty())\n        s_userDirectory = s_applicationPath;\n    else\n    {\n        s_userDirectory = roamingPath + \"/\" + userDirName;\n\n        // fallback to legacy directory if there is no custom directory set\n        std::string legacyUserDirectory = homePath + \"/.PGE_Project/thextech\";\n        if(userDirName == \"TheXTech\" && !DirMan::exists(s_userDirectory) && DirMan::exists(legacyUserDirectory))\n            s_userDirectory = legacyUserDirectory;\n    }\n}\n\nstd::string AppPathP::appDirectory()\n{\n    return s_applicationPath;\n}\n\nstd::string AppPathP::userDirectory()\n{\n    return s_userDirectory;\n}\n\nstd::string AppPathP::assetsRoot()\n{\n    return std::string();\n}\n\nAssetsPathType AppPathP::assetsRootType()\n{\n    return AssetsPathType::Single;\n}\n\nstd::string AppPathP::settingsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide settings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. settings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gamesavesRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide gamesaves\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. gamesaves saved at the settings directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::screenshotsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide screenshots\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. screenshots saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::gifRecsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide GIF recordings\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. GIF recordings saved at the user directory)\n     */\n    return std::string();\n}\n\nstd::string AppPathP::logsRoot()\n{\n    /*\n     * Fill this in only condition when you want to use the system-wide logs\n     * directory out of user directory. Keep it empty if you want to keep the\n     * default behaviour (i.e. logs saved at the user directory)\n     */\n    return std::string();\n}\n\nbool AppPathP::portableAvailable()\n{\n    /*\n     * Report, does this platfor support portable mode or not\n     */\n    return true;\n}\n\nvoid AppPathP::syncFS()\n{\n    /* Run the FS synchronization (Implement this for Emscripten only) */\n}\n"
  },
  {
    "path": "lib/Archives/archives.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#pragma once\n\n#ifndef THEXTECH_ARCHIVES_H\n#define THEXTECH_ARCHIVES_H\n\n#include <string>\n\nstruct SDL_RWops;\nstruct mbediso_fs;\nstruct mbediso_dir;\n\nnamespace Archives\n{\n\nenum PathType\n{\n    PATH_NONE = 0,\n    PATH_FILE,\n    PATH_DIR,\n};\n\nstruct DirEntry\n{\n    const char* name;\n    PathType type;\n};\n\nstruct DirIterator\n{\n    mbediso_dir* dir = nullptr;\n    bool has_temp_ref = false;\n    bool expired = false;\n\n    struct DirIter\n    {\n        bool is_end = false;\n        mbediso_dir* dir = nullptr;\n        DirEntry entry;\n\n        DirEntry& operator*();\n        DirIter& operator++();\n        bool operator!=(const DirIter& o) const;\n    };\n\n    DirIter begin();\n    DirIter end();\n    ~DirIterator();\n};\n\ninline bool is_prefix(char letter)\n{\n    return letter == ':' || letter == '@';\n}\n\ninline bool has_prefix(const std::string& s)\n{\n    return is_prefix(s.c_str()[0]);\n}\n\nbool mount_assets(const char* archive_path);\nvoid unmount_assets();\nconst std::string& assets_archive_path();\n\nbool mount_episode(const char* archive_path);\nvoid unmount_episode();\nconst std::string& episode_archive_path();\n\nvoid unmount_temp();\n\nSDL_RWops* open_file(const char* name);\nDirIterator list_dir(const char* name);\nPathType exists(const char* name);\n\n} // namespace Archives\n\n#endif // #ifndef THEXTECH_ARCHIVES_H\n"
  },
  {
    "path": "lib/Archives/archives_dir.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <vector>\n\n#include <cstring>\n#include <cstddef>\n\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"archives.h\"\n#include \"archives_priv.h\"\n\n#include \"mbediso.h\"\n\nnamespace Archives\n{\n\nstatic void s_next_iter(DirIterator::DirIter& iter)\n{\n    const auto* ent = mbediso_readdir(iter.dir);\n    if(!ent)\n    {\n        iter.is_end = true;\n        return;\n    }\n\n    iter.entry.name = (char*)ent->d_name;\n\n    if(ent->d_type == MBEDISO_DT_REG)\n        iter.entry.type = PATH_FILE;\n    else if(ent->d_type == MBEDISO_DT_DIR)\n        iter.entry.type = PATH_DIR;\n    else\n        iter.entry.type = PATH_NONE;\n}\n\nDirIterator::DirIter DirIterator::begin()\n{\n    DirIter ret;\n    if(!dir || expired)\n    {\n        ret.is_end = true;\n        return ret;\n    }\n\n    expired = true;\n\n    ret.dir = dir;\n    s_next_iter(ret);\n    return ret;\n}\n\nDirIterator::DirIter DirIterator::end()\n{\n    DirIter ret;\n    ret.is_end = true;\n    return ret;\n}\n\nDirIterator::~DirIterator()\n{\n    if(dir)\n        mbediso_closedir(dir);\n    if(has_temp_ref)\n    {\n        SDL_assert_release(temp_refs > 0);\n        temp_refs--;\n    }\n\n    dir = nullptr;\n    has_temp_ref = false;\n}\n\nDirEntry& DirIterator::DirIter::operator*()\n{\n    return entry;\n}\n\nDirIterator::DirIter& DirIterator::DirIter::operator++()\n{\n    s_next_iter(*this);\n    return *this;\n}\n\nbool DirIterator::DirIter::operator!=(const DirIterator::DirIter& o) const\n{\n    return !is_end || !o.is_end;\n}\n\nDirIterator list_dir(const char* name)\n{\n    DirIterator ret;\n\n    if(!is_prefix(name[0]))\n        return ret;\n\n    mbediso_dir* d = nullptr;\n\n    if(name[0] == '@')\n    {\n        const char* archive_path_start = name + 1;\n        const char* archive_path_end = archive_path_start;\n        // don't check for path end until after the first slash\n        while(*archive_path_end != '\\0' && *archive_path_end != '/' && *archive_path_end != '\\\\')\n            ++archive_path_end;\n        while(*archive_path_end != '\\0' && *archive_path_end != ':')\n            ++archive_path_end;\n\n        if(archive_path_end == archive_path_start || *archive_path_end == '\\0')\n            return ret;\n\n        ptrdiff_t archive_path_size = archive_path_end - archive_path_start;\n\n        char* archive_path = (char*)malloc(archive_path_size + 1);\n        memcpy(archive_path, archive_path_start, archive_path_size);\n        archive_path[archive_path_size] = '\\0';\n\n        bool mounted = mount_temp(archive_path);\n        free(archive_path);\n\n        if(!mounted || !temp_mount)\n            return ret;\n\n        const char* dir_path_begin = archive_path_end + 1;\n\n        d = mbediso_opendir(temp_mount, dir_path_begin);\n\n        if(d)\n        {\n            temp_refs++;\n            ret.has_temp_ref = true;\n        }\n    }\n    else if(name[0] == ':' && (name[1] == 'a' || name[1] == 'e'))\n    {\n        mbediso_fs* fs = (name[1] == 'a') ? assets_mount : episode_mount;\n\n        if(fs)\n            d = mbediso_opendir(fs, name + 2);\n    }\n\n    ret.dir = d;\n\n    return ret;\n}\n\nPathType exists(const char* name)\n{\n    if(!is_prefix(name[0]))\n        return PATH_NONE;\n\n    int found = 0;\n\n    if(name[0] == '@')\n    {\n        const char* archive_path_start = name + 1;\n        const char* archive_path_end = archive_path_start;\n        // don't check for path end until after the first slash\n        while(*archive_path_end != '\\0' && *archive_path_end != '/' && *archive_path_end != '\\\\')\n            ++archive_path_end;\n        while(*archive_path_end != '\\0' && *archive_path_end != ':')\n            ++archive_path_end;\n\n        if(archive_path_end == archive_path_start || *archive_path_end == '\\0')\n            return PATH_NONE;\n\n        ptrdiff_t archive_path_size = archive_path_end - archive_path_start;\n\n        char* archive_path = (char*)malloc(archive_path_size + 1);\n        memcpy(archive_path, archive_path_start, archive_path_size);\n        archive_path[archive_path_size] = '\\0';\n\n        bool mounted = mount_temp(archive_path);\n        free(archive_path);\n\n        if(!mounted || !temp_mount)\n            return PATH_NONE;\n\n        const char* dir_path_begin = archive_path_end + 1;\n\n        found = mbediso_exists(temp_mount, dir_path_begin);\n    }\n    else if(name[0] == ':' && (name[1] == 'a' || name[1] == 'e'))\n    {\n        mbediso_fs* fs = (name[1] == 'a') ? assets_mount : episode_mount;\n\n        if(fs)\n            found = mbediso_exists(fs, name + 2);\n    }\n\n    if(found == MBEDISO_DT_DIR)\n        return PATH_DIR;\n    else if(found == MBEDISO_DT_REG)\n        return PATH_FILE;\n    else\n        return PATH_NONE;\n}\n\n} // namespace Archives\n"
  },
  {
    "path": "lib/Archives/archives_mount.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include <string>\n#include <Logger/logger.h>\n\n#include \"archives.h\"\n#include \"archives_priv.h\"\n\n#include \"mbediso.h\"\n\nnamespace Archives\n{\n\nmbediso_fs* assets_mount = nullptr;\nmbediso_fs* episode_mount = nullptr;\nmbediso_fs* temp_mount = nullptr;\nstatic std::string s_assets_archive_path;\nstatic std::string s_episode_archive_path;\nstatic std::string s_temp_archive_path;\n\nint temp_refs;\n\nstatic void s_unmount(mbediso_fs*& target, std::string& loaded_path)\n{\n    loaded_path.clear();\n\n    if(!target)\n        return;\n\n    mbediso_closefs(target);\n    target = nullptr;\n}\n\nstatic bool s_mount(mbediso_fs*& target, std::string& loaded_path, const char* archive_path)\n{\n    if(loaded_path == archive_path)\n        return true;\n\n    s_unmount(target, loaded_path);\n\n    if(s_temp_archive_path == archive_path && temp_refs == 0 && temp_mount != nullptr && mbediso_scanfs(temp_mount) == 0)\n    {\n        pLogDebug(\"Upgrading temporary mount to real mount\");\n\n        target = temp_mount;\n        temp_mount = nullptr;\n\n        loaded_path = std::move(s_temp_archive_path);\n        s_temp_archive_path.clear();\n\n        return true;\n    }\n\n    target = mbediso_openfs_file(archive_path, target != temp_mount);\n\n    if(target)\n        loaded_path = archive_path;\n\n    return target;\n}\n\nbool mount_assets(const char* archive_path)\n{\n    return s_mount(assets_mount, s_assets_archive_path, archive_path);\n}\n\nvoid unmount_assets()\n{\n    return s_unmount(assets_mount, s_assets_archive_path);\n}\n\nconst std::string& assets_archive_path()\n{\n    return s_assets_archive_path;\n}\n\nbool mount_episode(const char* archive_path)\n{\n    return s_mount(episode_mount, s_episode_archive_path, archive_path);\n}\n\nvoid unmount_episode()\n{\n    return s_unmount(episode_mount, s_episode_archive_path);\n}\n\nconst std::string& episode_archive_path()\n{\n    return s_episode_archive_path;\n}\n\nbool mount_temp(const char* archive_path)\n{\n    if(s_temp_archive_path == archive_path)\n        return true;\n\n    if(temp_refs > 0)\n    {\n        pLogCritical(\"Can't mount new archive [%s]; items still open from prev archive [%s]\", archive_path, s_temp_archive_path.c_str());\n        return false;\n    }\n\n    return s_mount(temp_mount, s_temp_archive_path, archive_path);\n}\n\nvoid unmount_temp()\n{\n    if(temp_refs > 0)\n    {\n        pLogCritical(\"Can't unmount temp archive [%s]; %d items still open.\", s_temp_archive_path.c_str(), (int)temp_refs);\n        return;\n    }\n\n    return s_unmount(temp_mount, s_temp_archive_path);\n}\n\n} // namespace Archives\n"
  },
  {
    "path": "lib/Archives/archives_priv.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#pragma once\n\n#ifndef THEXTECH_ARCHIVES_PRIV_H\n#define THEXTECH_ARCHIVES_PRIV_H\n\nnamespace Archives\n{\n\nextern mbediso_fs* assets_mount;\nextern mbediso_fs* episode_mount;\nextern mbediso_fs* temp_mount;\nextern int temp_refs;\n\nbool mount_temp(const char* archive_path);\n\n} // namespace Archives\n\n#endif // #ifndef THEXTECH_ARCHIVES_PRIV_H\n"
  },
  {
    "path": "lib/Archives/archives_rwops.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include <cstring>\n\n#include <SDL2/SDL_rwops.h>\n#include \"Logger/logger.h\"\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"mbediso.h\"\n\n#include \"archives.h\"\n#include \"archives_priv.h\"\n\n// define specific warnings here until we have full specific warning support via CMake\n#ifdef __PSP__\n#    define archives_pLogWarning pLogWarning\n#else\n#    define archives_pLogWarning D_pLogWarning\n#endif\n\nnamespace Archives\n{\n\nstatic int64_t s_file_size(SDL_RWops* stream)\n{\n    if(!stream || !stream->hidden.unknown.data1)\n        return -1;\n\n    mbediso_file* f = static_cast<mbediso_file*>(stream->hidden.unknown.data1);\n\n    return mbediso_fsize(f);\n}\n\nstatic int64_t s_file_seek(SDL_RWops* stream, int64_t offset, int whence)\n{\n    if(!stream || !stream->hidden.unknown.data1)\n        return -1;\n\n    mbediso_file* f = static_cast<mbediso_file*>(stream->hidden.unknown.data1);\n\n    return mbediso_fseek(f, offset, whence);\n}\n\nstatic size_t s_file_read(SDL_RWops* stream, void* ptr, size_t size, size_t nmemb)\n{\n    if(!stream || !stream->hidden.unknown.data1)\n        return 0;\n\n    mbediso_file* f = static_cast<mbediso_file*>(stream->hidden.unknown.data1);\n\n    return (size_t)mbediso_fread(f, ptr, size, nmemb);\n}\n\nstatic size_t s_file_write(SDL_RWops*, const void*, size_t, size_t)\n{\n    return 0;\n}\n\nstatic int s_file_close_normal(SDL_RWops* stream)\n{\n    if(!stream || !stream->hidden.unknown.data1)\n        return -1;\n\n    mbediso_file* f = static_cast<mbediso_file*>(stream->hidden.unknown.data1);\n\n    mbediso_fclose(f);\n\n    return 0;\n}\n\nstatic int s_file_close_decref(SDL_RWops* stream)\n{\n    if(s_file_close_normal(stream))\n        return -1;\n\n    SDL_assert_release(temp_refs > 0);\n    temp_refs--;\n\n    return 0;\n}\n\nSDL_RWops* open_file(const char* name)\n{\n    if(!is_prefix(name[0]))\n    {\n        archives_pLogWarning(\"Archives: Unsupported prefix at path [%s]\", name);\n        return nullptr;\n    }\n\n    bool has_temp_ref = false;\n    mbediso_file* f = nullptr;\n\n    if(name[0] == '@')\n    {\n        const char* archive_path_start = name + 1;\n        const char* archive_path_end = archive_path_start;\n        // don't check for path end until after the first slash\n        while(*archive_path_end != '\\0' && *archive_path_end != '/' && *archive_path_end != '\\\\')\n            ++archive_path_end;\n        while(*archive_path_end != '\\0' && *archive_path_end != ':')\n            ++archive_path_end;\n\n        if(archive_path_end == archive_path_start || *archive_path_end == '\\0')\n        {\n            archives_pLogWarning(\"Archives: invalid composite path [%s]\", name);\n            return nullptr;\n        }\n\n        ptrdiff_t archive_path_size = archive_path_end - archive_path_start;\n\n        char* archive_path = (char*)malloc(archive_path_size + 1);\n        memcpy(archive_path, archive_path_start, archive_path_size);\n        archive_path[archive_path_size] = '\\0';\n\n        bool mounted = mount_temp(archive_path);\n\n        if(!mounted || !temp_mount)\n        {\n            archives_pLogWarning(\"Archives: Failed to mount archive [%s]\", archive_path);\n            free(archive_path);\n            return nullptr;\n        }\n\n        free(archive_path);\n\n        const char* file_path_begin = archive_path_end + 1;\n\n        f = mbediso_fopen(temp_mount, file_path_begin);\n        has_temp_ref = true;\n    }\n    else if(name[0] == ':' && (name[1] == 'a' || name[1] == 'e'))\n    {\n        mbediso_fs* fs = (name[1] == 'a') ? assets_mount : episode_mount;\n\n        if(fs)\n            f = mbediso_fopen(fs, name + 2);\n    }\n\n    if(!f)\n    {\n        archives_pLogWarning(\"Archives: File [%s] doesn't exists\", name);\n        return nullptr;\n    }\n\n    SDL_RWops* ret = SDL_AllocRW();\n\n    if(!ret)\n    {\n        mbediso_fclose(f);\n        archives_pLogWarning(\"Archives: Failed attempt to open [%s]: SDL_RWops error: %s\", name, SDL_GetError());\n        return nullptr;\n    }\n\n    ret->size = s_file_size;\n    ret->seek = s_file_seek;\n    ret->read = s_file_read;\n    ret->write = s_file_write;\n    ret->close = (has_temp_ref) ? s_file_close_decref : s_file_close_normal;\n\n    if(has_temp_ref)\n        temp_refs++;\n\n    ret->type = SDL_RWOPS_UNKNOWN;\n    ret->hidden.unknown.data1 = f;\n\n    return ret;\n}\n\n} // namespace Archives\n"
  },
  {
    "path": "lib/CrashHandler/StackWalker/StackWalker.cmake",
    "content": "# message(\"Path to StackWalker is [${CMAKE_CURRENT_LIST_DIR}]\")\ninclude_directories(${CMAKE_CURRENT_LIST_DIR}/)\n\nset(STACK_WALKER_SRCS)\n\nif(WIN32)\n    list(APPEND STACK_WALKER_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/StackWalker.cpp\n    )\nelse()\n    message(\"StackWalker is Windows-only module, nothing was used.\")\nendif()\n\n"
  },
  {
    "path": "lib/CrashHandler/StackWalker/StackWalker.cpp",
    "content": "#ifdef _WIN32\n/**********************************************************************\n *\n * StackWalker.cpp\n * http://stackwalker.codeplex.com/\n *\n *\n * History:\n *  2005-07-27   v1    - First public release on http://www.codeproject.com/\n *                       http://www.codeproject.com/threads/StackWalker.asp\n *  2005-07-28   v2    - Changed the params of the constructor and ShowCallstack\n *                       (to simplify the usage)\n *  2005-08-01   v3    - Changed to use 'CONTEXT_FULL' instead of CONTEXT_ALL\n *                       (should also be enough)\n *                     - Changed to compile correctly with the PSDK of VC7.0\n *                       (GetFileVersionInfoSizeA and GetFileVersionInfoA is wrongly defined:\n *                        it uses LPSTR instead of LPCSTR as first paremeter)\n *                     - Added declarations to support VC5/6 without using 'dbghelp.h'\n *                     - Added a 'pUserData' member to the ShowCallstack function and the\n *                       PReadProcessMemoryRoutine declaration (to pass some user-defined data,\n *                       which can be used in the readMemoryFunction-callback)\n *  2005-08-02   v4    - OnSymInit now also outputs the OS-Version by default\n *                     - Added example for doing an exception-callstack-walking in main.cpp\n *                       (thanks to owillebo: http://www.codeproject.com/script/profile/whos_who.asp?id=536268)\n *  2005-08-05   v5    - Removed most Lint (http://www.gimpel.com/) errors... thanks to Okko Willeboordse!\n *  2008-08-04   v6    - Fixed Bug: Missing LEAK-end-tag\n *                       http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2502890#xx2502890xx\n *                       Fixed Bug: Compiled with \"WIN32_LEAN_AND_MEAN\"\n *                       http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=1824718#xx1824718xx\n *                       Fixed Bug: Compiling with \"/Wall\"\n *                       http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2638243#xx2638243xx\n *                       Fixed Bug: Now checking SymUseSymSrv\n *                       http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1388979#xx1388979xx\n *                       Fixed Bug: Support for recursive function calls\n *                       http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1434538#xx1434538xx\n *                       Fixed Bug: Missing FreeLibrary call in \"GetModuleListTH32\"\n *                       http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1326923#xx1326923xx\n *                       Fixed Bug: SymDia is number 7, not 9!\n *  2008-09-11   v7      For some (undocumented) reason, dbhelp.h is needing a packing of 8!\n *                       Thanks to Teajay which reported the bug...\n *                       http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2718933#xx2718933xx\n *  2008-11-27   v8      Debugging Tools for Windows are now stored in a different directory\n *                       Thanks to Luiz Salamon which reported this \"bug\"...\n *                       http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2822736#xx2822736xx\n *  2009-04-10   v9      License slihtly corrected (<ORGANIZATION> replaced)\n *  2009-11-01   v10     Moved to http://stackwalker.codeplex.com/\n *  2009-11-02   v11     Now try to use IMAGEHLP_MODULE64_V3 if available\n *  2010-04-15   v12     Added support for VS2010 RTM\n *  2010-05-25   v13     Now using secure MyStrcCpy. Thanks to luke.simon:\n *                       http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=3477467#xx3477467xx\n *  2013-01-07   v14     Runtime Check Error VS2010 Debug Builds fixed:\n *                       http://stackwalker.codeplex.com/workitem/10511\n *\n *\n * LICENSE (http://www.opensource.org/licenses/bsd-license.php)\n *\n *   Copyright (c) 2005-2013, Jochen Kalmbach\n *   All rights reserved.\n *\n *   Redistribution and use in source and binary forms, with or without modification,\n *   are permitted provided that the following conditions are met:\n *\n *   Redistributions of source code must retain the above copyright notice,\n *   this list of conditions and the following disclaimer.\n *   Redistributions in binary form must reproduce the above copyright notice,\n *   this list of conditions and the following disclaimer in the documentation\n *   and/or other materials provided with the distribution.\n *   Neither the name of Jochen Kalmbach nor the names of its contributors may be\n *   used to endorse or promote products derived from this software without\n *   specific prior written permission.\n *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n *   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n *   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\n *   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n *   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n *   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n **********************************************************************/\n#include <windows.h>\n#include <wchar.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n//#pragma comment(lib, \"version.lib\")  // for \"VerQueryValue\"\n//#pragma warning(disable:4826)\n\n#include \"StackWalker.h\"\n#include <string>\n\nvoid ansi2utf8(char *inout, const DWORD maxLen)\n{\n    std::wstring tmpW;\n    tmpW.resize(maxLen);\n    memset(&tmpW[0], 0, sizeof(wchar_t) * maxLen);\n    int len = (int)strlen(inout);\n    len = MultiByteToWideChar(CP_ACP, 0, inout, len, &tmpW[0], maxLen);\n    len = WideCharToMultiByte(CP_UTF8, 0, &tmpW[0], len, inout, (maxLen - 1), 0, 0);\n    inout[len] = '\\0';\n}\n\n// If VC7 and later, then use the shipped 'dbghelp.h'-file\n#pragma pack(push,8)\n#if _MSC_VER >= 1300\n#include <dbghelp.h>\n#else\n// inline the important dbghelp.h-declarations...\ntypedef enum\n{\n    SymNone = 0,\n    SymCoff,\n    SymCv,\n    SymPdb,\n    SymExport,\n    SymDeferred,\n    SymSym,\n    SymDia,\n    SymVirtual,\n    NumSymTypes\n} SYM_TYPE;\ntypedef struct _IMAGEHLP_LINE64\n{\n    DWORD                       SizeOfStruct;           // set to sizeof(IMAGEHLP_LINE64)\n    PVOID                       Key;                    // internal\n    DWORD                       LineNumber;             // line number in file\n    PCHAR                       FileName;               // full filename\n    DWORD64                     Address;                // first instruction of line\n} IMAGEHLP_LINE64, *PIMAGEHLP_LINE64;\ntypedef struct _IMAGEHLP_MODULE64\n{\n    DWORD                       SizeOfStruct;           // set to sizeof(IMAGEHLP_MODULE64)\n    DWORD64                     BaseOfImage;            // base load address of module\n    DWORD                       ImageSize;              // virtual size of the loaded module\n    DWORD                       TimeDateStamp;          // date/time stamp from pe header\n    DWORD                       CheckSum;               // checksum from the pe header\n    DWORD                       NumSyms;                // number of symbols in the symbol table\n    SYM_TYPE                    SymType;                // type of symbols loaded\n    CHAR                        ModuleName[32];         // module name\n    CHAR                        ImageName[256];         // image name\n    CHAR                        LoadedImageName[256];   // symbol file name\n} IMAGEHLP_MODULE64, *PIMAGEHLP_MODULE64;\ntypedef struct _IMAGEHLP_SYMBOL64\n{\n    DWORD                       SizeOfStruct;           // set to sizeof(IMAGEHLP_SYMBOL64)\n    DWORD64                     Address;                // virtual address including dll base address\n    DWORD                       Size;                   // estimated size of symbol, can be zero\n    DWORD                       Flags;                  // info about the symbols, see the SYMF defines\n    DWORD                       MaxNameLength;          // maximum size of symbol name in 'Name'\n    CHAR                        Name[1];                // symbol name (null terminated string)\n} IMAGEHLP_SYMBOL64, *PIMAGEHLP_SYMBOL64;\ntypedef enum\n{\n    AddrMode1616,\n    AddrMode1632,\n    AddrModeReal,\n    AddrModeFlat\n} ADDRESS_MODE;\ntypedef struct _tagADDRESS64\n{\n    DWORD64       Offset;\n    WORD          Segment;\n    ADDRESS_MODE  Mode;\n} ADDRESS64, *LPADDRESS64;\ntypedef struct _KDHELP64\n{\n    DWORD64   Thread;\n    DWORD   ThCallbackStack;\n    DWORD   ThCallbackBStore;\n    DWORD   NextCallback;\n    DWORD   FramePointer;\n    DWORD64   KiCallUserMode;\n    DWORD64   KeUserCallbackDispatcher;\n    DWORD64   SystemRangeStart;\n    DWORD64  Reserved[8];\n} KDHELP64, *PKDHELP64;\ntypedef struct _tagSTACKFRAME64\n{\n    ADDRESS64   AddrPC;               // program counter\n    ADDRESS64   AddrReturn;           // return address\n    ADDRESS64   AddrFrame;            // frame pointer\n    ADDRESS64   AddrStack;            // stack pointer\n    ADDRESS64   AddrBStore;           // backing store pointer\n    PVOID       FuncTableEntry;       // pointer to pdata/fpo or NULL\n    DWORD64     Params[4];            // possible arguments to the function\n    BOOL        Far;                  // WOW far call\n    BOOL        Virtual;              // is this a virtual frame?\n    DWORD64     Reserved[3];\n    KDHELP64    KdHelp;\n} STACKFRAME64, *LPSTACKFRAME64;\ntypedef\nBOOL\n(__stdcall *PREAD_PROCESS_MEMORY_ROUTINE64)(\n    HANDLE      hProcess,\n    DWORD64     qwBaseAddress,\n    PVOID       lpBuffer,\n    DWORD       nSize,\n    LPDWORD     lpNumberOfBytesRead\n);\ntypedef\nPVOID\n(__stdcall *PFUNCTION_TABLE_ACCESS_ROUTINE64)(\n    HANDLE  hProcess,\n    DWORD64 AddrBase\n);\ntypedef\nDWORD64\n(__stdcall *PGET_MODULE_BASE_ROUTINE64)(\n    HANDLE  hProcess,\n    DWORD64 Address\n);\ntypedef\nDWORD64\n(__stdcall *PTRANSLATE_ADDRESS_ROUTINE64)(\n    HANDLE    hProcess,\n    HANDLE    hThread,\n    LPADDRESS64 lpaddr\n);\n#define SYMOPT_CASE_INSENSITIVE         0x00000001\n#define SYMOPT_UNDNAME                  0x00000002\n#define SYMOPT_DEFERRED_LOADS           0x00000004\n#define SYMOPT_NO_CPP                   0x00000008\n#define SYMOPT_LOAD_LINES               0x00000010\n#define SYMOPT_OMAP_FIND_NEAREST        0x00000020\n#define SYMOPT_LOAD_ANYTHING            0x00000040\n#define SYMOPT_IGNORE_CVREC             0x00000080\n#define SYMOPT_NO_UNQUALIFIED_LOADS     0x00000100\n#define SYMOPT_FAIL_CRITICAL_ERRORS     0x00000200\n#define SYMOPT_EXACT_SYMBOLS            0x00000400\n#define SYMOPT_ALLOW_ABSOLUTE_SYMBOLS   0x00000800\n#define SYMOPT_IGNORE_NT_SYMPATH        0x00001000\n#define SYMOPT_INCLUDE_32BIT_MODULES    0x00002000\n#define SYMOPT_PUBLICS_ONLY             0x00004000\n#define SYMOPT_NO_PUBLICS               0x00008000\n#define SYMOPT_AUTO_PUBLICS             0x00010000\n#define SYMOPT_NO_IMAGE_SEARCH          0x00020000\n#define SYMOPT_SECURE                   0x00040000\n#define SYMOPT_DEBUG                    0x80000000\n#define UNDNAME_COMPLETE                 (0x0000)  // Enable full undecoration\n#define UNDNAME_NAME_ONLY                (0x1000)  // Crack only the name for primary declaration;\n#endif  // _MSC_VER < 1300\n#pragma pack(pop)\n\n// Some missing defines (for VC5/6):\n#ifndef INVALID_FILE_ATTRIBUTES\n#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)\n#endif\n\n\n// secure-CRT_functions are only available starting with VC8\n#define strcpy_s(dst, len, src) strcpy(dst, src)\n#define strncpy_s(dst, len, src, maxLen) strncpy(dst, src, maxLen)\n#define strcat_s(dst, len, src) strcat(dst, src)\n#define sprintf_s snprintf\n//#define _tcscat_s _tcscat\n\n\n\nstatic void MyStrCpy(char *szDest, size_t nMaxDestSize, const char *szSrc)\n{\n    if(nMaxDestSize <= 0)\n        return;\n    memset(szDest, 0, nMaxDestSize);\n    strncpy(szDest, szSrc, nMaxDestSize);\n    szDest[nMaxDestSize - 1] = 0;// INFO: _TRUNCATE will ensure that it is nul-terminated; but with older compilers (<1400) it uses \"strncpy\" and this does not!)\n}  // MyStrCpy\n\n// Normally it should be enough to use 'CONTEXT_FULL' (better would be 'CONTEXT_ALL')\n#define USED_CONTEXT_FLAGS CONTEXT_FULL\n\n\nclass StackWalkerInternal\n{\npublic:\n    StackWalkerInternal(StackWalker *parent, HANDLE hProcess)\n    {\n        m_parent = parent;\n        m_hDbhHelp = NULL;\n        pSC = NULL;\n        m_hProcess = hProcess;\n        m_szSymPath = NULL;\n        pSFTA = NULL;\n        pSGLFA = NULL;\n        pSGMB = NULL;\n        pSGMI = NULL;\n        pSGO = NULL;\n        pSGSFA = NULL;\n        pSI = NULL;\n        pSLM = NULL;\n        pSSO = NULL;\n        pSW = NULL;\n        pUDSN = NULL;\n        pSGSP = NULL;\n    }\n    ~StackWalkerInternal()\n    {\n        if(pSC != NULL)\n            pSC(m_hProcess);  // SymCleanup\n        if(m_hDbhHelp != NULL)\n            FreeLibrary(m_hDbhHelp);\n        m_hDbhHelp = NULL;\n        m_parent = NULL;\n        if(m_szSymPath != NULL)\n            free(m_szSymPath);\n        m_szSymPath = NULL;\n    }\n    BOOL Init(LPCSTR szSymPath)\n    {\n        if(m_parent == NULL)\n            return FALSE;\n        // Dynamically load the Entry-Points for dbghelp.dll:\n        // First try to load the newsest one from\n\n        std::wstring szTemp;\n        szTemp.resize(4100);\n        // But before wqe do this, we first check if the \".local\" file exists\n        DWORD szTempSize = GetModuleFileNameW(NULL, &szTemp[0], 4096);\n        szTemp.resize(szTempSize);\n        if(szTempSize > 0)\n        {\n            szTemp += L\".local\";\n            if(GetFileAttributesW(&szTemp[0]) == INVALID_FILE_ATTRIBUTES)\n            {\n                // \".local\" file does not exist, so we can try to load the dbghelp.dll from the \"Debugging Tools for Windows\"\n                // Ok, first try the new path according to the archtitecture:\n                if(!m_hDbhHelp)\n                {\n                    szTempSize = GetEnvironmentVariableW(L\"ProgramFiles\", &szTemp[0], 4096);\n                    szTemp.resize(szTempSize);\n                }\n#ifdef _M_IX86\n                if((m_hDbhHelp == NULL) && (szTempSize > 0))\n                {\n                    szTemp = szTemp + L\"\\\\Debugging Tools for Windows (x86)\\\\dbghelp.dll\";\n                    // now check if the file exists:\n                    if(GetFileAttributesW(szTemp.c_str()) != INVALID_FILE_ATTRIBUTES)\n                    {\n                        m_hDbhHelp = LoadLibraryW(szTemp.c_str());\n                    }\n                }\n#elif _M_X64\n                if((m_hDbhHelp == NULL) && (szTempSize > 0))\n                {\n                    szTemp += L\"\\\\Debugging Tools for Windows (x64)\\\\dbghelp.dll\";\n                    // now check if the file exists:\n                    if(GetFileAttributesW(szTemp.c_str()) != INVALID_FILE_ATTRIBUTES)\n                    {\n                        m_hDbhHelp = LoadLibraryW(szTemp.c_str());\n                    }\n                }\n#elif _M_IA64\n                if((m_hDbhHelp == NULL) && (szTempSize > 0))\n                {\n                    szTemp += L\"\\\\Debugging Tools for Windows (ia64)\\\\dbghelp.dll\";\n                    // now check if the file exists:\n                    if(GetFileAttributes(szTempS) != INVALID_FILE_ATTRIBUTES)\n                    {\n                        m_hDbhHelp = LoadLibrary(szTempS);\n                    }\n                }\n#endif\n\n                if(!m_hDbhHelp)\n                {\n                    szTempSize = GetEnvironmentVariableW(L\"ProgramFiles\", &szTemp[0], 4096);\n                    szTemp.resize(szTempSize);\n                }\n\n                // If still not found, try the old directories...\n                if((m_hDbhHelp == NULL) && (szTempSize > 0))\n                {\n                    szTemp += L\"\\\\Debugging Tools for Windows\\\\dbghelp.dll\";\n                    // now check if the file exists:\n                    if(GetFileAttributesW(&szTemp[0]) != INVALID_FILE_ATTRIBUTES)\n                    {\n                        m_hDbhHelp = LoadLibraryW(&szTemp[0]);\n                    }\n                }\n\n#if defined _M_X64 || defined _M_IA64\n                if(!m_hDbhHelp)\n                {\n                    szTempSize = GetEnvironmentVariableW(L\"ProgramFiles\", &szTemp[0], 4096);\n                    szTemp.resize(szTempSize);\n                }\n                // Still not found? Then try to load the (old) 64-Bit version:\n                if((m_hDbhHelp == NULL) && (szTempSize > 0))\n                {\n                    szTemp += L\"\\\\Debugging Tools for Windows 64-Bit\\\\dbghelp.dll\";\n                    const wchar_t *szTempS = (wchar_t *)szTemp.c_str();\n                    if(GetFileAttributesW(szTempS) != INVALID_FILE_ATTRIBUTES)\n                    {\n                        m_hDbhHelp = LoadLibraryW(szTempS);\n                    }\n                }\n#endif\n            }\n        }\n\n        if(m_hDbhHelp == NULL)   // if not already loaded, try to load a default-one\n            m_hDbhHelp = LoadLibraryW(L\"dbghelp.dll\");\n\n        if(m_hDbhHelp == NULL)\n            return FALSE;\n\n        pSI = (tSI)(void*)GetProcAddress(m_hDbhHelp, \"SymInitialize\");\n        pSC = (tSC)(void*)GetProcAddress(m_hDbhHelp, \"SymCleanup\");\n\n        pSW = (tSW)(void*)GetProcAddress(m_hDbhHelp, \"StackWalk64\");\n        pSGO = (tSGO)(void*)GetProcAddress(m_hDbhHelp, \"SymGetOptions\");\n        pSSO = (tSSO)(void*)GetProcAddress(m_hDbhHelp, \"SymSetOptions\");\n\n        pSFTA = (tSFTA)(void*)GetProcAddress(m_hDbhHelp, \"SymFunctionTableAccess64\");\n        pSGLFA = (tSGLFA)(void*)GetProcAddress(m_hDbhHelp, \"SymGetLineFromAddr64\");\n        pSGMB = (tSGMB)(void*)GetProcAddress(m_hDbhHelp, \"SymGetModuleBase64\");\n        pSGMI = (tSGMI)(void*)GetProcAddress(m_hDbhHelp, \"SymGetModuleInfo64\");\n        pSGSFA = (tSGSFA)(void*)GetProcAddress(m_hDbhHelp, \"SymGetSymFromAddr64\");\n        pUDSN = (tUDSN)(void*)GetProcAddress(m_hDbhHelp, \"UnDecorateSymbolName\");\n        pSLM = (tSLM)(void*)GetProcAddress(m_hDbhHelp, \"SymLoadModule64\");\n        pSGSP = (tSGSP)(void*)GetProcAddress(m_hDbhHelp, \"SymGetSearchPath\");\n\n        if(pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL ||\n           pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL ||\n           pSW == NULL || pUDSN == NULL || pSLM == NULL)\n        {\n            FreeLibrary(m_hDbhHelp);\n            m_hDbhHelp = NULL;\n            pSC = NULL;\n            return FALSE;\n        }\n\n        // SymInitialize\n        if(szSymPath != NULL)\n            m_szSymPath = _strdup(szSymPath);\n        if(this->pSI(m_hProcess, m_szSymPath, FALSE) == FALSE)\n            this->m_parent->OnDbgHelpErr(\"SymInitialize\", GetLastError(), 0);\n\n        DWORD symOptions = this->pSGO();  // SymGetOptions\n        symOptions |= SYMOPT_LOAD_LINES;\n        symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;\n        //symOptions |= SYMOPT_NO_PROMPTS;\n        // SymSetOptions\n        symOptions = this->pSSO(symOptions);\n\n        char   buf[StackWalker::STACKWALK_MAX_NAMELEN] = {0};\n        if(this->pSGSP != NULL)\n        {\n            if(this->pSGSP(m_hProcess, buf, StackWalker::STACKWALK_MAX_NAMELEN) == FALSE)\n                this->m_parent->OnDbgHelpErr(\"SymGetSearchPath\", GetLastError(), 0);\n        }\n        char    szUserName[1024] = {0};\n        DWORD   dwSize = 1024;\n#ifdef THEXTECH_WINRT\n        dwSize = sprintf(szUserName, \"<unknown>\");\n#else\n        GetUserNameA(szUserName, &dwSize);\n#endif\n\n        this->m_parent->OnSymInit(buf, symOptions, szUserName);\n\n        return TRUE;\n    }\n\n    StackWalker *m_parent;\n\n    HMODULE m_hDbhHelp;\n    HANDLE m_hProcess;\n    LPSTR m_szSymPath;\n\n#pragma pack(push,8)\n    struct IMAGEHLP_MODULE64_V3\n    {\n        DWORD    SizeOfStruct;           // set to sizeof(IMAGEHLP_MODULE64)\n        DWORD64  BaseOfImage;            // base load address of module\n        DWORD    ImageSize;              // virtual size of the loaded module\n        DWORD    TimeDateStamp;          // date/time stamp from pe header\n        DWORD    CheckSum;               // checksum from the pe header\n        DWORD    NumSyms;                // number of symbols in the symbol table\n        SYM_TYPE SymType;                // type of symbols loaded\n        CHAR     ModuleName[32];         // module name\n        CHAR     ImageName[256];         // image name\n        CHAR     LoadedImageName[256];   // symbol file name\n        // new elements: 07-Jun-2002\n        CHAR     LoadedPdbName[256];     // pdb file name\n        DWORD    CVSig;                  // Signature of the CV record in the debug directories\n        CHAR     CVData[MAX_PATH * 3];   // Contents of the CV record\n        DWORD    PdbSig;                 // Signature of PDB\n        GUID     PdbSig70;               // Signature of PDB (VC 7 and up)\n        DWORD    PdbAge;                 // DBI age of pdb\n        BOOL     PdbUnmatched;           // loaded an unmatched pdb\n        BOOL     DbgUnmatched;           // loaded an unmatched dbg\n        BOOL     LineNumbers;            // we have line number information\n        BOOL     GlobalSymbols;          // we have internal symbol information\n        BOOL     TypeInfo;               // we have type information\n        // new elements: 17-Dec-2003\n        BOOL     SourceIndexed;          // pdb supports source server\n        BOOL     Publics;                // contains public symbols\n    };\n\n    struct IMAGEHLP_MODULE64_V2\n    {\n        DWORD    SizeOfStruct;           // set to sizeof(IMAGEHLP_MODULE64)\n        DWORD64  BaseOfImage;            // base load address of module\n        DWORD    ImageSize;              // virtual size of the loaded module\n        DWORD    TimeDateStamp;          // date/time stamp from pe header\n        DWORD    CheckSum;               // checksum from the pe header\n        DWORD    NumSyms;                // number of symbols in the symbol table\n        SYM_TYPE SymType;                // type of symbols loaded\n        CHAR     ModuleName[32];         // module name\n        CHAR     ImageName[256];         // image name\n        CHAR     LoadedImageName[256];   // symbol file name\n    };\n#pragma pack(pop)\n\n\n    // SymCleanup()\n    typedef BOOL (__stdcall *tSC)(IN HANDLE hProcess);\n    tSC pSC;\n\n    // SymFunctionTableAccess64()\n    typedef PVOID(__stdcall *tSFTA)(HANDLE hProcess, DWORD64 AddrBase);\n    tSFTA pSFTA;\n\n    // SymGetLineFromAddr64()\n    typedef BOOL (__stdcall *tSGLFA)(IN HANDLE hProcess, IN DWORD64 dwAddr,\n                                     OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line);\n    tSGLFA pSGLFA;\n\n    // SymGetModuleBase64()\n    typedef DWORD64(__stdcall *tSGMB)(IN HANDLE hProcess, IN DWORD64 dwAddr);\n    tSGMB pSGMB;\n\n    // SymGetModuleInfo64()\n    typedef BOOL (__stdcall *tSGMI)(IN HANDLE hProcess, IN DWORD64 dwAddr, OUT IMAGEHLP_MODULE64_V3 *ModuleInfo);\n    tSGMI pSGMI;\n\n    // SymGetOptions()\n    typedef DWORD (__stdcall *tSGO)(VOID);\n    tSGO pSGO;\n\n    // SymGetSymFromAddr64()\n    typedef BOOL (__stdcall *tSGSFA)(IN HANDLE hProcess, IN DWORD64 dwAddr,\n                                     OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol);\n    tSGSFA pSGSFA;\n\n    // SymInitialize()\n    typedef BOOL (__stdcall *tSI)(IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess);\n    tSI pSI;\n\n    // SymLoadModule64()\n    typedef DWORD64(__stdcall *tSLM)(IN HANDLE hProcess, IN HANDLE hFile,\n                                     IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll);\n    tSLM pSLM;\n\n    // SymSetOptions()\n    typedef DWORD (__stdcall *tSSO)(IN DWORD SymOptions);\n    tSSO pSSO;\n\n    // StackWalk64()\n    typedef BOOL (__stdcall *tSW)(\n        DWORD MachineType,\n        HANDLE hProcess,\n        HANDLE hThread,\n        LPSTACKFRAME64 StackFrame,\n        PVOID ContextRecord,\n        PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,\n        PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,\n        PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,\n        PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress);\n    tSW pSW;\n\n    // UnDecorateSymbolName()\n    typedef DWORD (__stdcall WINAPI *tUDSN)(PCSTR DecoratedName, PSTR UnDecoratedName,\n                                            DWORD UndecoratedLength, DWORD Flags);\n    tUDSN pUDSN;\n\n    typedef BOOL (__stdcall WINAPI *tSGSP)(HANDLE hProcess, PSTR SearchPath, DWORD SearchPathLength);\n    tSGSP pSGSP;\n\n\nprivate:\n    // **************************************** ToolHelp32 ************************\n#define MAX_MODULE_NAME32 255\n#define TH32CS_SNAPMODULE   0x00000008\n#pragma pack( push, 8 )\n    typedef struct tagMODULEENTRY32\n    {\n        DWORD   dwSize;\n        DWORD   th32ModuleID;       // This module\n        DWORD   th32ProcessID;      // owning process\n        DWORD   GlblcntUsage;       // Global usage count on the module\n        DWORD   ProccntUsage;       // Module usage count in th32ProcessID's context\n        BYTE   *modBaseAddr;        // Base address of module in th32ProcessID's context\n        DWORD   modBaseSize;        // Size in bytes of module starting at modBaseAddr\n        HMODULE hModule;            // The hModule of this module in th32ProcessID's context\n        char    szModule[MAX_MODULE_NAME32 + 1];\n        char    szExePath[MAX_PATH];\n    } MODULEENTRY32;\n    typedef MODULEENTRY32   *PMODULEENTRY32;\n    typedef MODULEENTRY32   *LPMODULEENTRY32;\n#pragma pack( pop )\n\n    BOOL GetModuleListTH32(HANDLE hProcess, DWORD pid)\n    {\n        // CreateToolhelp32Snapshot()\n        typedef HANDLE(__stdcall * tCT32S)(DWORD dwFlags, DWORD th32ProcessID);\n        // Module32First()\n        typedef BOOL (__stdcall * tM32F)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);\n        // Module32Next()\n        typedef BOOL (__stdcall * tM32N)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);\n\n        // try both dlls...\n        const wchar_t *dllname[] = { L\"kernel32.dll\", L\"tlhelp32.dll\" };\n        HINSTANCE hToolhelp = NULL;\n        tCT32S pCT32S = NULL;\n        tM32F pM32F = NULL;\n        tM32N pM32N = NULL;\n\n        HANDLE hSnap;\n        MODULEENTRY32 me;\n        me.dwSize = sizeof(me);\n        BOOL keepGoing;\n        size_t i;\n\n        for(i = 0; i < (sizeof(dllname) / sizeof(dllname[0])); i++)\n        {\n            hToolhelp = LoadLibraryW(dllname[i]);\n            if(hToolhelp == NULL)\n                continue;\n            pCT32S = (tCT32S)(void*)GetProcAddress(hToolhelp, \"CreateToolhelp32Snapshot\");\n            pM32F = (tM32F)(void*)GetProcAddress(hToolhelp, \"Module32First\");\n            pM32N = (tM32N)(void*)GetProcAddress(hToolhelp, \"Module32Next\");\n            if((pCT32S != NULL) && (pM32F != NULL) && (pM32N != NULL))\n                break; // found the functions!\n            FreeLibrary(hToolhelp);\n            hToolhelp = NULL;\n        }\n\n        if(hToolhelp == NULL)\n            return FALSE;\n\n        hSnap = pCT32S(TH32CS_SNAPMODULE, pid);\n        if(hSnap == (HANDLE) - 1)\n        {\n            FreeLibrary(hToolhelp);\n            return FALSE;\n        }\n\n        keepGoing = !!pM32F(hSnap, &me);\n        int cnt = 0;\n        while(keepGoing)\n        {\n            this->LoadModule(hProcess, me.szExePath, me.szModule, (DWORD64) me.modBaseAddr, me.modBaseSize);\n            cnt++;\n            keepGoing = !!pM32N(hSnap, &me);\n        }\n        CloseHandle(hSnap);\n        FreeLibrary(hToolhelp);\n        if(cnt <= 0)\n            return FALSE;\n        return TRUE;\n    }  // GetModuleListTH32\n\n    // **************************************** PSAPI ************************\n    typedef struct _MODULEINFO\n    {\n        LPVOID lpBaseOfDll;\n        DWORD SizeOfImage;\n        LPVOID EntryPoint;\n    } MODULEINFO, *LPMODULEINFO;\n\n    BOOL GetModuleListPSAPI(HANDLE hProcess)\n    {\n        // EnumProcessModules()\n        typedef BOOL (__stdcall * tEPM)(HANDLE hProcess, HMODULE * lphModule, DWORD cb, LPDWORD lpcbNeeded);\n        // GetModuleFileNameEx()\n        typedef DWORD (__stdcall * tGMFNE)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize);\n        // GetModuleBaseName()\n        typedef DWORD (__stdcall * tGMBN)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize);\n        // GetModuleInformation()\n        typedef BOOL (__stdcall * tGMI)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize);\n\n        HINSTANCE hPsapi;\n        tEPM pEPM;\n        tGMFNE pGMFNE;\n        tGMBN pGMBN;\n        tGMI pGMI;\n\n        DWORD i;\n        //ModuleEntry e;\n        DWORD cbNeeded;\n        MODULEINFO mi;\n        HMODULE *hMods = 0;\n        char *tt = NULL;\n        char *tt2 = NULL;\n        const SIZE_T TTBUFLEN = 8096;\n        int cnt = 0;\n\n        hPsapi = LoadLibraryW(L\"psapi.dll\");\n        if(hPsapi == NULL)\n            return FALSE;\n        pEPM = (tEPM)(void*)GetProcAddress(hPsapi, \"EnumProcessModules\");\n        pGMFNE = (tGMFNE)(void*)GetProcAddress(hPsapi, \"GetModuleFileNameExA\");\n        pGMBN = (tGMFNE)(void*)GetProcAddress(hPsapi, \"GetModuleBaseNameA\");\n        pGMI = (tGMI)(void*)GetProcAddress(hPsapi, \"GetModuleInformation\");\n        if((pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL))\n        {\n            // we couldn? find all functions\n            FreeLibrary(hPsapi);\n            return FALSE;\n        }\n\n        hMods = (HMODULE *) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof(HMODULE)));\n        tt = (char *) malloc(sizeof(char) * TTBUFLEN);\n        tt2 = (char *) malloc(sizeof(char) * TTBUFLEN);\n        if((hMods == NULL) || (tt == NULL) || (tt2 == NULL))\n            goto cleanup;\n\n        if(!pEPM(hProcess, hMods, TTBUFLEN, &cbNeeded))\n        {\n            //_ftprintf(fLogFile, _T(\"%lu: EPM failed, GetLastError = %lu\\n\"), g_dwShowCount, gle );\n            goto cleanup;\n        }\n\n        if(cbNeeded > TTBUFLEN)\n        {\n            //_ftprintf(fLogFile, _T(\"%lu: More than %lu module handles. Huh?\\n\"), g_dwShowCount, lenof( hMods ) );\n            goto cleanup;\n        }\n\n        for(i = 0; i < cbNeeded / sizeof(hMods[0]); i++)\n        {\n            // base address, size\n            pGMI(hProcess, hMods[i], &mi, sizeof(mi));\n            // image file name\n            tt[0] = 0;\n            pGMFNE(hProcess, hMods[i], tt, TTBUFLEN);\n            // module name\n            tt2[0] = 0;\n            pGMBN(hProcess, hMods[i], tt2, TTBUFLEN);\n\n            DWORD dwRes = this->LoadModule(hProcess, tt, tt2, (DWORD64) mi.lpBaseOfDll, mi.SizeOfImage);\n            if(dwRes != ERROR_SUCCESS)\n                this->m_parent->OnDbgHelpErr(\"LoadModule\", dwRes, 0);\n            cnt++;\n        }\n\ncleanup:\n        if(hPsapi != NULL) FreeLibrary(hPsapi);\n        if(tt2 != NULL) free(tt2);\n        if(tt != NULL) free(tt);\n        if(hMods != NULL) free(hMods);\n\n        return cnt != 0;\n    }// GetModuleListPSAPI\n\n    DWORD LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size)\n    {\n        CHAR *szImg = _strdup(img);\n        CHAR *szMod = _strdup(mod);\n        DWORD result = ERROR_SUCCESS;\n        if((szImg == NULL) || (szMod == NULL))\n            result = ERROR_NOT_ENOUGH_MEMORY;\n        else\n        {\n            if(pSLM(hProcess, 0, szImg, szMod, baseAddr, size) == 0)\n                result = GetLastError();\n        }\n        ULONGLONG fileVersion = 0;\n        if((m_parent != NULL) && (szImg != NULL))\n        {\n            // try to retrive the file-version:\n            if((this->m_parent->m_options & StackWalker::RetrieveFileVersion) != 0)\n            {\n                VS_FIXEDFILEINFO *fInfo = NULL;\n                DWORD dwHandle = 0;\n                DWORD dwSize = GetFileVersionInfoSizeA(szImg, &dwHandle);\n                if(dwSize > 0)\n                {\n                    LPVOID vData = malloc(dwSize);\n                    if(vData != NULL)\n                    {\n                        //#pragma warning(suppress: 6388)\n                        if(GetFileVersionInfoA(szImg, dwHandle, dwSize, vData) != 0)\n                        {\n                            UINT len;\n                            wchar_t szSubBlock[] = L\"\\\\\";\n                            if(VerQueryValueW(vData, szSubBlock, (LPVOID *) &fInfo, &len) == 0)\n                                fInfo = NULL;\n                            else\n                            {\n                                fileVersion = ((ULONGLONG)fInfo->dwFileVersionLS) + ((ULONGLONG)fInfo->dwFileVersionMS << 32);\n                            }\n                        }\n                        free(vData);\n                    }\n                }\n            }\n\n            // Retrive some additional-infos about the module\n            IMAGEHLP_MODULE64_V3 Module;\n            const char *szSymType = \"-unknown-\";\n            if(this->GetModuleInfo(hProcess, baseAddr, &Module) != FALSE)\n            {\n                switch(Module.SymType)\n                {\n                case SymNone:\n                    szSymType = \"-nosymbols-\";\n                    break;\n                case SymCoff:  // 1\n                    szSymType = \"COFF\";\n                    break;\n                case SymCv:  // 2\n                    szSymType = \"CV\";\n                    break;\n                case SymPdb:  // 3\n                    szSymType = \"PDB\";\n                    break;\n                case SymExport:  // 4\n                    szSymType = \"-exported-\";\n                    break;\n                case SymDeferred:  // 5\n                    szSymType = \"-deferred-\";\n                    break;\n                case SymSym:  // 6\n                    szSymType = \"SYM\";\n                    break;\n                case 7: // SymDia:\n                    szSymType = \"DIA\";\n                    break;\n                case 8: //SymVirtual:\n                    szSymType = \"Virtual\";\n                    break;\n                default:\n                    break;\n                }\n            }\n            LPCSTR pdbName = Module.LoadedImageName;\n            if(Module.LoadedPdbName[0] != 0)\n                pdbName = Module.LoadedPdbName;\n            this->m_parent->OnLoadModule(img, mod, baseAddr, size, result, szSymType, pdbName, fileVersion);\n        }\n        if(szImg != NULL) free(szImg);\n        if(szMod != NULL) free(szMod);\n        return result;\n    }\npublic:\n    BOOL LoadModules(HANDLE hProcess, DWORD dwProcessId)\n    {\n        // first try toolhelp32\n        if(GetModuleListTH32(hProcess, dwProcessId))\n            return true;\n        // then try psapi\n        return GetModuleListPSAPI(hProcess);\n    }\n\n\n    BOOL GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V3 *pModuleInfo)\n    {\n        memset(pModuleInfo, 0, sizeof(IMAGEHLP_MODULE64_V3));\n        if(this->pSGMI == NULL)\n        {\n            SetLastError(ERROR_DLL_INIT_FAILED);\n            return FALSE;\n        }\n        // First try to use the larger ModuleInfo-Structure\n        pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3);\n        void *pData = malloc(4096); // reserve enough memory, so the bug in v6.3.5.1 does not lead to memory-overwrites...\n        if(pData == NULL)\n        {\n            SetLastError(ERROR_NOT_ENOUGH_MEMORY);\n            return FALSE;\n        }\n        memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V3));\n        static bool s_useV3Version = true;\n        if(s_useV3Version)\n        {\n            if(this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3 *) pData) != FALSE)\n            {\n                // only copy as much memory as is reserved...\n                memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V3));\n                pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3);\n                free(pData);\n                return TRUE;\n            }\n            s_useV3Version = false;  // to prevent unneccessarry calls with the larger struct...\n        }\n\n        // could not retrive the bigger structure, try with the smaller one (as defined in VC7.1)...\n        pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2);\n        memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V2));\n        if(this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3 *) pData) != FALSE)\n        {\n            // only copy as much memory as is reserved...\n            memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V2));\n            pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2);\n            free(pData);\n            return TRUE;\n        }\n        free(pData);\n        SetLastError(ERROR_DLL_INIT_FAILED);\n        return FALSE;\n    }\n};\n\n// #############################################################\nStackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess)\n{\n    this->m_options = OptionsAll;\n    this->m_modulesLoaded = FALSE;\n    this->m_hProcess = hProcess;\n    this->m_sw = new StackWalkerInternal(this, this->m_hProcess);\n    this->m_dwProcessId = dwProcessId;\n    this->m_szSymPath = NULL;\n    this->m_MaxRecursionCount = 1000;\n}\nStackWalker::StackWalker(int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess)\n{\n    this->m_options = options;\n    this->m_modulesLoaded = FALSE;\n    this->m_hProcess = hProcess;\n    this->m_sw = new StackWalkerInternal(this, this->m_hProcess);\n    this->m_dwProcessId = dwProcessId;\n    if(szSymPath != NULL)\n    {\n        this->m_szSymPath = _strdup(szSymPath);\n        this->m_options |= SymBuildPath;\n    }\n    else\n        this->m_szSymPath = NULL;\n    this->m_MaxRecursionCount = 1000;\n}\n\nStackWalker::~StackWalker()\n{\n    if(m_szSymPath != NULL)\n        free(m_szSymPath);\n    m_szSymPath = NULL;\n    if(this->m_sw != NULL)\n        delete this->m_sw;\n    this->m_sw = NULL;\n}\n\nBOOL StackWalker::LoadModules()\n{\n    if(this->m_sw == NULL)\n    {\n        SetLastError(ERROR_DLL_INIT_FAILED);\n        return FALSE;\n    }\n    if(m_modulesLoaded != FALSE)\n        return TRUE;\n\n    // Build the sym-path:\n    char *szSymPath = NULL;\n    if((this->m_options & SymBuildPath) != 0)\n    {\n        const size_t nSymPathLen = 4096;\n        szSymPath = (char *) malloc(nSymPathLen);\n        if(szSymPath == NULL)\n        {\n            SetLastError(ERROR_NOT_ENOUGH_MEMORY);\n            return FALSE;\n        }\n        szSymPath[0] = 0;\n        // Now first add the (optional) provided sympath:\n        if(this->m_szSymPath != NULL)\n        {\n            strcat_s(szSymPath, nSymPathLen, this->m_szSymPath);\n            strcat_s(szSymPath, nSymPathLen, \";\");\n        }\n\n        strcat_s(szSymPath, nSymPathLen, \".;\");\n\n        const size_t nTempLen = 1024;\n        char szTemp[nTempLen];\n        // Now add the current directory:\n        if(GetCurrentDirectoryA(nTempLen, szTemp) > 0)\n        {\n            szTemp[nTempLen - 1] = 0;\n            strcat_s(szSymPath, nSymPathLen, szTemp);\n            strcat_s(szSymPath, nSymPathLen, \";\");\n        }\n\n        // Now add the path for the main-module:\n        if(GetModuleFileNameA(NULL, szTemp, nTempLen) > 0)\n        {\n            szTemp[nTempLen - 1] = 0;\n            for(char *p = (szTemp + strlen(szTemp) - 1); p >= szTemp; --p)\n            {\n                // locate the rightmost path separator\n                if((*p == '\\\\') || (*p == '/') || (*p == ':'))\n                {\n                    *p = 0;\n                    break;\n                }\n            }  // for (search for path separator...)\n            if(strlen(szTemp) > 0)\n            {\n                strcat_s(szSymPath, nSymPathLen, szTemp);\n                strcat_s(szSymPath, nSymPathLen, \";\");\n            }\n        }\n        if(GetEnvironmentVariableA(\"_NT_SYMBOL_PATH\", szTemp, nTempLen) > 0)\n        {\n            szTemp[nTempLen - 1] = 0;\n            strcat_s(szSymPath, nSymPathLen, szTemp);\n            strcat_s(szSymPath, nSymPathLen, \";\");\n        }\n        if(GetEnvironmentVariableA(\"_NT_ALTERNATE_SYMBOL_PATH\", szTemp, nTempLen) > 0)\n        {\n            szTemp[nTempLen - 1] = 0;\n            strcat_s(szSymPath, nSymPathLen, szTemp);\n            strcat_s(szSymPath, nSymPathLen, \";\");\n        }\n        if(GetEnvironmentVariableA(\"SYSTEMROOT\", szTemp, nTempLen) > 0)\n        {\n            szTemp[nTempLen - 1] = 0;\n            strcat_s(szSymPath, nSymPathLen, szTemp);\n            strcat_s(szSymPath, nSymPathLen, \";\");\n            // also add the \"system32\"-directory:\n            strcat_s(szTemp, nTempLen, \"\\\\system32\");\n            strcat_s(szSymPath, nSymPathLen, szTemp);\n            strcat_s(szSymPath, nSymPathLen, \";\");\n        }\n\n        if((this->m_options & SymUseSymSrv) != 0)\n        {\n            if(GetEnvironmentVariableA(\"SYSTEMDRIVE\", szTemp, nTempLen) > 0)\n            {\n                szTemp[nTempLen - 1] = 0;\n                strcat_s(szSymPath, nSymPathLen, \"SRV*\");\n                strcat_s(szSymPath, nSymPathLen, szTemp);\n                strcat_s(szSymPath, nSymPathLen, \"\\\\websymbols\");\n                strcat_s(szSymPath, nSymPathLen, \"*http://msdl.microsoft.com/download/symbols;\");\n            }\n            else\n                strcat_s(szSymPath, nSymPathLen, \"SRV*c:\\\\websymbols*http://msdl.microsoft.com/download/symbols;\");\n        }\n    }  // if SymBuildPath\n\n    this->OnOutput(\"\\n\\n**** LIBRARY INFORMATION ****\\n\");\n    // First Init the whole stuff...\n    BOOL bRet = this->m_sw->Init(szSymPath);\n    if(szSymPath != NULL) free(szSymPath);\n    szSymPath = NULL;\n    if(bRet == FALSE)\n    {\n        this->OnDbgHelpErr(\"Error while initializing dbghelp.dll\", 0, 0);\n        SetLastError(ERROR_DLL_INIT_FAILED);\n        return FALSE;\n    }\n\n    bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId);\n    this->OnOutput(\"\\n\\n**** THE STACKTRACE ****\\n\");\n    if(bRet != FALSE)\n        m_modulesLoaded = TRUE;\n    return bRet;\n}\n\n// The following is used to pass the \"userData\"-Pointer to the user-provided readMemoryFunction\n// This has to be done due to a problem with the \"hProcess\"-parameter in x64...\n// Because this class is in no case multi-threading-enabled (because of the limitations\n// of dbghelp.dll) it is \"safe\" to use a static-variable\nstatic StackWalker::PReadProcessMemoryRoutine s_readMemoryFunction = NULL;\nstatic LPVOID s_readMemoryFunction_UserData = NULL;\n\nBOOL StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT *context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData)\n{\n    CONTEXT c;\n    CallstackEntry csEntry;\n    IMAGEHLP_SYMBOL64 *pSym = NULL;\n    StackWalkerInternal::IMAGEHLP_MODULE64_V3 Module;\n    IMAGEHLP_LINE64 Line;\n    int frameNum;\n    bool bLastEntryCalled = true;\n    int curRecursionCount = 0;\n\n    if(m_modulesLoaded == FALSE)\n        this->LoadModules();  // ignore the result...\n\n    if(this->m_sw->m_hDbhHelp == NULL)\n    {\n        SetLastError(ERROR_DLL_INIT_FAILED);\n        return FALSE;\n    }\n\n    s_readMemoryFunction = readMemoryFunction;\n    s_readMemoryFunction_UserData = pUserData;\n\n    if(context == NULL)\n    {\n        // If no context is provided, capture the context\n        // See: https://stackwalker.codeplex.com/discussions/446958\n        //#if _WIN32_WINNT <= 0x0501\n        // If we need to support XP, we need to use the \"old way\", because \"GetThreadId\" is not available!\n        if(hThread == GetCurrentThread())\n            //#else\n            //    if (GetThreadId_My(hThread) == GetCurrentThreadId())\n            //#endif\n        {\n            GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, USED_CONTEXT_FLAGS);\n        }\n        else\n        {\n            SuspendThread(hThread);\n            memset(&c, 0, sizeof(CONTEXT));\n            c.ContextFlags = USED_CONTEXT_FLAGS;\n            if(GetThreadContext(hThread, &c) == FALSE)\n            {\n                ResumeThread(hThread);\n                return FALSE;\n            }\n        }\n    }\n    else\n        c = *context;\n\n    // init STACKFRAME for first call\n    STACKFRAME64 s; // in/out stackframe\n    memset(&s, 0, sizeof(s));\n    DWORD imageType;\n    #ifdef _M_IX86\n    // normally, call ImageNtHeader() and use machine info from PE header\n    imageType = IMAGE_FILE_MACHINE_I386;\n    s.AddrPC.Offset = c.Eip;\n    s.AddrPC.Mode = AddrModeFlat;\n    s.AddrFrame.Offset = c.Ebp;\n    s.AddrFrame.Mode = AddrModeFlat;\n    s.AddrStack.Offset = c.Esp;\n    s.AddrStack.Mode = AddrModeFlat;\n    #elif _M_X64\n    imageType = IMAGE_FILE_MACHINE_AMD64;\n    s.AddrPC.Offset = c.Rip;\n    s.AddrPC.Mode = AddrModeFlat;\n    s.AddrFrame.Offset = c.Rsp;\n    s.AddrFrame.Mode = AddrModeFlat;\n    s.AddrStack.Offset = c.Rsp;\n    s.AddrStack.Mode = AddrModeFlat;\n    #elif _M_IA64\n    imageType = IMAGE_FILE_MACHINE_IA64;\n    s.AddrPC.Offset = c.StIIP;\n    s.AddrPC.Mode = AddrModeFlat;\n    s.AddrFrame.Offset = c.IntSp;\n    s.AddrFrame.Mode = AddrModeFlat;\n    s.AddrBStore.Offset = c.RsBSP;\n    s.AddrBStore.Mode = AddrModeFlat;\n    s.AddrStack.Offset = c.IntSp;\n    s.AddrStack.Mode = AddrModeFlat;\n    #elif _M_ARM64\n    imageType = IMAGE_FILE_MACHINE_ARM64;\n    s.AddrPC.Offset = c.Pc;\n    s.AddrPC.Mode = AddrModeFlat;\n    s.AddrFrame.Offset = c.Fp;\n    s.AddrFrame.Mode = AddrModeFlat;\n    s.AddrStack.Offset = c.Sp;\n    s.AddrStack.Mode = AddrModeFlat;\n    #else\n#error \"Platform not supported!\"\n    #endif\n\n    pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);\n    if(!pSym) goto cleanup;   // not enough memory...\n    memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);\n    pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);\n    pSym->MaxNameLength = STACKWALK_MAX_NAMELEN;\n\n    memset(&Line, 0, sizeof(Line));\n    Line.SizeOfStruct = sizeof(Line);\n\n    memset(&Module, 0, sizeof(Module));\n    Module.SizeOfStruct = sizeof(Module);\n\n    for(frameNum = 0; ; ++frameNum)\n    {\n        // get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64())\n        // if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can\n        // assume that either you are done, or that the stack is so hosed that the next\n        // deeper frame could not be found.\n        // CONTEXT need not to be suplied if imageTyp is IMAGE_FILE_MACHINE_I386!\n        if(! this->m_sw->pSW(imageType, this->m_hProcess, hThread, &s, &c, myReadProcMem, this->m_sw->pSFTA, this->m_sw->pSGMB, NULL))\n        {\n            // INFO: \"StackWalk64\" does not set \"GetLastError\"...\n            this->OnDbgHelpErr(\"StackWalk64\", 0, s.AddrPC.Offset);\n            break;\n        }\n\n        csEntry.offset = s.AddrPC.Offset;\n        csEntry.name[0] = 0;\n        csEntry.undName[0] = 0;\n        csEntry.undFullName[0] = 0;\n        csEntry.offsetFromSmybol = 0;\n        csEntry.offsetFromLine = 0;\n        csEntry.lineFileName[0] = 0;\n        csEntry.lineNumber = 0;\n        csEntry.loadedImageName[0] = 0;\n        csEntry.moduleName[0] = 0;\n        if(s.AddrPC.Offset == s.AddrReturn.Offset)\n        {\n            if((this->m_MaxRecursionCount > 0) && (curRecursionCount > m_MaxRecursionCount))\n            {\n                this->OnDbgHelpErr(\"StackWalk64-Endless-Callstack!\", 0, s.AddrPC.Offset);\n                break;\n            }\n            curRecursionCount++;\n        }\n        else\n            curRecursionCount = 0;\n        if(s.AddrPC.Offset != 0)\n        {\n            // we seem to have a valid PC\n            // show procedure info (SymGetSymFromAddr64())\n            if(this->m_sw->pSGSFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromSmybol), pSym) != FALSE)\n            {\n                MyStrCpy(csEntry.name, STACKWALK_MAX_NAMELEN, pSym->Name);\n                // UnDecorateSymbolName()\n                this->m_sw->pUDSN(pSym->Name, csEntry.undName, STACKWALK_MAX_NAMELEN, UNDNAME_NAME_ONLY);\n                this->m_sw->pUDSN(pSym->Name, csEntry.undFullName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE);\n            }\n            else\n            {\n                this->OnDbgHelpErr(\"SymGetSymFromAddr64\", GetLastError(), s.AddrPC.Offset);\n            }\n\n            // show line number info, NT5.0-method (SymGetLineFromAddr64())\n            if(this->m_sw->pSGLFA != NULL)\n            {\n                // yes, we have SymGetLineFromAddr64()\n                if(this->m_sw->pSGLFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromLine), &Line) != FALSE)\n                {\n                    csEntry.lineNumber = Line.LineNumber;\n                    MyStrCpy(csEntry.lineFileName, STACKWALK_MAX_NAMELEN, Line.FileName);\n                }\n                else\n                {\n                    this->OnDbgHelpErr(\"SymGetLineFromAddr64\", GetLastError(), s.AddrPC.Offset);\n                }\n            } // yes, we have SymGetLineFromAddr64()\n\n            // show module info (SymGetModuleInfo64())\n            if(this->m_sw->GetModuleInfo(this->m_hProcess, s.AddrPC.Offset, &Module) != FALSE)\n            {\n                // got module info OK\n                switch(Module.SymType)\n                {\n                case SymNone:\n                    csEntry.symTypeString = \"-nosymbols-\";\n                    break;\n                case SymCoff:\n                    csEntry.symTypeString = \"COFF\";\n                    break;\n                case SymCv:\n                    csEntry.symTypeString = \"CV\";\n                    break;\n                case SymPdb:\n                    csEntry.symTypeString = \"PDB\";\n                    break;\n                case SymExport:\n                    csEntry.symTypeString = \"-exported-\";\n                    break;\n                case SymDeferred:\n                    csEntry.symTypeString = \"-deferred-\";\n                    break;\n                case SymSym:\n                    csEntry.symTypeString = \"SYM\";\n                    break;\n                    #if API_VERSION_NUMBER >= 9\n                case SymDia:\n                    csEntry.symTypeString = \"DIA\";\n                    break;\n                    #endif\n                case 8: //SymVirtual:\n                    csEntry.symTypeString = \"Virtual\";\n                    break;\n                default:\n                    //_snprintf( ty, sizeof(ty), \"symtype=%ld\", (long) Module.SymType );\n                    csEntry.symTypeString = NULL;\n                    break;\n                }\n\n                MyStrCpy(csEntry.moduleName, STACKWALK_MAX_NAMELEN, Module.ModuleName);\n                csEntry.baseOfImage = Module.BaseOfImage;\n                MyStrCpy(csEntry.loadedImageName, STACKWALK_MAX_NAMELEN, Module.LoadedImageName);\n            } // got module info OK\n            else\n            {\n                this->OnDbgHelpErr(\"SymGetModuleInfo64\", GetLastError(), s.AddrPC.Offset);\n            }\n        } // we seem to have a valid PC\n\n        this->OnOutput((std::to_string(frameNum) + std::string(\": \")).c_str());\n\n        CallstackEntryType et = nextEntry;\n        if(frameNum == 0)\n            et = firstEntry;\n        bLastEntryCalled = false;\n        this->OnCallstackEntry(et, csEntry);\n\n        if(s.AddrReturn.Offset == 0)\n        {\n            bLastEntryCalled = true;\n            this->OnCallstackEntry(lastEntry, csEntry);\n            SetLastError(ERROR_SUCCESS);\n            break;\n        }\n    } // for ( frameNum )\n\ncleanup:\n    if(pSym) free(pSym);\n\n    if(bLastEntryCalled == false)\n        this->OnCallstackEntry(lastEntry, csEntry);\n\n    if(context == NULL)\n        ResumeThread(hThread);\n\n    return TRUE;\n}\n\nBOOL __stdcall StackWalker::myReadProcMem(\n    HANDLE      hProcess,\n    DWORD64     qwBaseAddress,\n    PVOID       lpBuffer,\n    DWORD       nSize,\n    LPDWORD     lpNumberOfBytesRead\n)\n{\n    if(s_readMemoryFunction == NULL)\n    {\n        SIZE_T st;\n        BOOL bRet = ReadProcessMemory(hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, &st);\n        //#pragma warning(suppress: 6102) //API\n        *lpNumberOfBytesRead = (DWORD) st;\n        //printf(\"ReadMemory: hProcess: %p, baseAddr: %p, buffer: %p, size: %d, read: %d, result: %d\\n\", hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, (DWORD) st, (DWORD) bRet);\n        return bRet;\n    }\n    else\n    {\n        return s_readMemoryFunction(hProcess, qwBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead, s_readMemoryFunction_UserData);\n    }\n}\n\nvoid StackWalker::OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion)\n{\n    CHAR buffer[STACKWALK_MAX_NAMELEN];\n    if(fileVersion == 0)\n        sprintf_s(buffer, STACKWALK_MAX_NAMELEN, \"%s:%s (%p), size: %ld (result: %ld), SymType: '%s', PDB: '%s'\\n\", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName);\n    else\n    {\n        DWORD v4 = (DWORD)(fileVersion & 0xFFFF);\n        DWORD v3 = (DWORD)((fileVersion >> 16) & 0xFFFF);\n        DWORD v2 = (DWORD)((fileVersion >> 32) & 0xFFFF);\n        DWORD v1 = (DWORD)((fileVersion >> 48) & 0xFFFF);\n        sprintf_s(buffer, STACKWALK_MAX_NAMELEN, \"%s:%s (%p), size: %ld (result: %ld), SymType: '%s', PDB: '%s', fileVersion: %ld.%ld.%ld.%ld\\n\", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName, v1, v2, v3, v4);\n    }\n    ansi2utf8(buffer, STACKWALK_MAX_NAMELEN);\n    OnOutput(buffer);\n}\n\nvoid StackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry)\n{\n    CHAR buffer[STACKWALK_MAX_NAMELEN];\n    if((eType != lastEntry) && (entry.offset != 0))\n    {\n        if(entry.name[0] == 0)\n            MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, \"(function-name not available)\");\n        if(entry.undName[0] != 0)\n            MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undName);\n        if(entry.undFullName[0] != 0)\n            MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undFullName);\n        if(entry.lineFileName[0] == 0)\n        {\n            MyStrCpy(entry.lineFileName, STACKWALK_MAX_NAMELEN, \"(filename not available)\");\n            if(entry.moduleName[0] == 0)\n                MyStrCpy(entry.moduleName, STACKWALK_MAX_NAMELEN, \"(module-name not available)\");\n            sprintf_s(buffer, STACKWALK_MAX_NAMELEN, \"%p (%s): %s: %s\\n\", (LPVOID) entry.offset, entry.moduleName, entry.lineFileName, entry.name);\n        }\n        else\n            sprintf_s(buffer, STACKWALK_MAX_NAMELEN, \"%s (%ld): %s\\n\", entry.lineFileName, entry.lineNumber, entry.name);\n        buffer[STACKWALK_MAX_NAMELEN - 1] = 0;\n        ansi2utf8(buffer, STACKWALK_MAX_NAMELEN);\n        OnOutput(buffer);\n    }\n}\n\nvoid StackWalker::OnDbgHelpErr(LPCSTR /*szFuncName*/, DWORD /*gle*/, DWORD64 /*addr*/)\n{\n    /*\n    CHAR buffer[STACKWALK_MAX_NAMELEN];\n    sprintf_s(buffer, STACKWALK_MAX_NAMELEN, \"ERROR: %s, GetLastError: %d (Address: %p)\\n\", szFuncName, gle, (LPVOID) addr);\n    OnOutput(buffer);\n    */\n}\n\nvoid StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName)\n{\n    char buffer[STACKWALK_MAX_NAMELEN];\n    sprintf_s(buffer, STACKWALK_MAX_NAMELEN, \"SymInit: Symbol-SearchPath: '%s', symOptions: %ld, UserName: '%s'\\n\", szSearchPath, symOptions, szUserName);\n    ansi2utf8(buffer, STACKWALK_MAX_NAMELEN);\n    OnOutput(buffer);\n\n    // Also display the OS-version\n#if !defined(_MSC_VER) || _MSC_VER <= 1200\n    OSVERSIONINFOA ver;\n    ZeroMemory(&ver, sizeof(OSVERSIONINFOA));\n    ver.dwOSVersionInfoSize = sizeof(ver);\n\n    if(GetVersionExA(&ver) != FALSE)\n    {\n        sprintf_s(buffer, STACKWALK_MAX_NAMELEN, \"OS-Version: %ld.%ld.%ld (%s)\\n\",\n                  (long)ver.dwMajorVersion,\n                  (long)ver.dwMinorVersion,\n                  (long)ver.dwBuildNumber,\n                  ver.szCSDVersion);\n        ansi2utf8(buffer, STACKWALK_MAX_NAMELEN);\n        OnOutput(buffer);\n    }\n#else\n    OSVERSIONINFOEXA ver;\n    ZeroMemory(&ver, sizeof(OSVERSIONINFOEXA));\n    ver.dwOSVersionInfoSize = sizeof(ver);\n\n#pragma warning(suppress: 28159) // API\n#pragma warning(suppress: 4996)  // The \"GetVersionExA\" is deprecated\n    if(GetVersionExA((OSVERSIONINFOA *) &ver) != FALSE)\n    {\n        sprintf_s(buffer, STACKWALK_MAX_NAMELEN, \"OS-Version: %u.%u.%u (%s) 0x%x-0x%x\\n\",\n                  (uint32_t)ver.dwMajorVersion,\n                  (uint32_t)ver.dwMinorVersion,\n                  (uint32_t)ver.dwBuildNumber,\n                  ver.szCSDVersion,\n                  ver.wSuiteMask,\n                  ver.wProductType);\n        ansi2utf8(buffer, STACKWALK_MAX_NAMELEN);\n        OnOutput(buffer);\n    }\n#endif\n}\n\nvoid StackWalker::OnOutput(LPCSTR buffer)\n{\n    OutputDebugStringA(buffer);\n}\n#endif\n"
  },
  {
    "path": "lib/CrashHandler/StackWalker/StackWalker.h",
    "content": "#ifdef _WIN32\n/**********************************************************************\n *\n * StackWalker.h\n *\n *\n *\n * LICENSE (http://www.opensource.org/licenses/bsd-license.php)\n *\n *   Copyright (c) 2005-2009, Jochen Kalmbach\n *   All rights reserved.\n *\n *   Redistribution and use in source and binary forms, with or without modification,\n *   are permitted provided that the following conditions are met:\n *\n *   Redistributions of source code must retain the above copyright notice,\n *   this list of conditions and the following disclaimer.\n *   Redistributions in binary form must reproduce the above copyright notice,\n *   this list of conditions and the following disclaimer in the documentation\n *   and/or other materials provided with the distribution.\n *   Neither the name of Jochen Kalmbach nor the names of its contributors may be\n *   used to endorse or promote products derived from this software without\n *   specific prior written permission.\n *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n *   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n *   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\n *   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n *   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n *   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * **********************************************************************/\n// #pragma once is supported starting with _MCS_VER 1000,\n// so we need not to check the version (because we only support _MSC_VER >= 1100)!\n#ifndef StackWalker_hhhhh\n#define StackWalker_hhhhh\n#include <windows.h>\n#include <string>\n\n// special defines for VC5/6 (if no actual PSDK is installed):\n#if _MSC_VER < 1300\ntypedef unsigned __int64 DWORD64, *PDWORD64;\n#if defined(_WIN64)\ntypedef unsigned __int64 SIZE_T, *PSIZE_T;\n#else\ntypedef unsigned long SIZE_T, *PSIZE_T;\n#endif\n#endif  // _MSC_VER < 1300\n\nclass StackWalkerInternal;  // forward\nclass StackWalker\n{\npublic:\n    typedef enum StackWalkOptions\n    {\n        // No addition info will be retrived\n        // (only the address is available)\n        RetrieveNone = 0,\n\n        // Try to get the symbol-name\n        RetrieveSymbol = 1,\n\n        // Try to get the line for this symbol\n        RetrieveLine = 2,\n\n        // Try to retrieve the module-infos\n        RetrieveModuleInfo = 4,\n\n        // Also retrieve the version for the DLL/EXE\n        RetrieveFileVersion = 8,\n\n        // Contains all the abouve\n        RetrieveVerbose = 0xF,\n\n        // Generate a \"good\" symbol-search-path\n        SymBuildPath = 0x10,\n\n        // Also use the public Microsoft-Symbol-Server\n        SymUseSymSrv = 0x20,\n\n        // Contains all the abouve \"Sym\"-options\n        SymAll = 0x30,\n\n        // Contains all options (default)\n        OptionsAll = 0x3F\n    } StackWalkOptions;\n\n    StackWalker(\n        int options = OptionsAll, // 'int' is by design, to combine the enum-flags\n        LPCSTR szSymPath = NULL,\n        DWORD dwProcessId = GetCurrentProcessId(),\n        HANDLE hProcess = GetCurrentProcess()\n    );\n    StackWalker(DWORD dwProcessId, HANDLE hProcess);\n    virtual ~StackWalker();\n\n    typedef BOOL (__stdcall *PReadProcessMemoryRoutine)(\n        HANDLE      hProcess,\n        DWORD64     qwBaseAddress,\n        PVOID       lpBuffer,\n        DWORD       nSize,\n        LPDWORD     lpNumberOfBytesRead,\n        LPVOID      pUserData  // optional data, which was passed in \"ShowCallstack\"\n    );\n\n    BOOL LoadModules();\n\n    BOOL ShowCallstack(\n        HANDLE hThread = GetCurrentThread(),\n        const CONTEXT *context = NULL,\n        PReadProcessMemoryRoutine readMemoryFunction = NULL,\n        LPVOID pUserData = NULL  // optional to identify some data in the 'readMemoryFunction'-callback\n    );\n\n    #if _MSC_VER >= 1300\n    // due to some reasons, the \"STACKWALK_MAX_NAMELEN\" must be declared as \"public\"\n    // in older compilers in order to use it... starting with VC7 we can declare it as \"protected\"\nprotected:\n    #endif\n    enum { STACKWALK_MAX_NAMELEN = 1024 }; // max name length for found symbols\n\nprotected:\n    // Entry for each Callstack-Entry\n    typedef struct CallstackEntry\n    {\n        DWORD64 offset;  // if 0, we have no valid entry\n        CHAR name[STACKWALK_MAX_NAMELEN];\n        CHAR undName[STACKWALK_MAX_NAMELEN];\n        CHAR undFullName[STACKWALK_MAX_NAMELEN];\n        DWORD64 offsetFromSmybol;\n        DWORD offsetFromLine;\n        DWORD lineNumber;\n        CHAR lineFileName[STACKWALK_MAX_NAMELEN];\n        DWORD symType;\n        LPCSTR symTypeString;\n        CHAR moduleName[STACKWALK_MAX_NAMELEN];\n        DWORD64 baseOfImage;\n        CHAR loadedImageName[STACKWALK_MAX_NAMELEN];\n    } CallstackEntry;\n\n    enum CallstackEntryType {firstEntry, nextEntry, lastEntry};\n\n    virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);\n    virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);\n    virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry);\n    virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);\n    virtual void OnOutput(LPCSTR szText);\n\n    StackWalkerInternal *m_sw;\n    HANDLE m_hProcess;\n    DWORD m_dwProcessId;\n    BOOL m_modulesLoaded;\n    LPSTR m_szSymPath;\n\n    int m_options;\n    int m_MaxRecursionCount;\n\n    static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);\n\n    friend StackWalkerInternal;\n};  // class StackWalker\n\n\n// The \"ugly\" assembler-implementation is needed for systems before XP\n// If you have a new PSDK and you only compile for XP and later, then you can use\n// the \"RtlCaptureContext\"\n// Currently there is no define which determines the PSDK-Version...\n// So we just use the compiler-version (and assumes that the PSDK is\n// the one which was installed by the VS-IDE)\n\n// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later...\n//       But I currently use it in x64/IA64 environments...\n//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400)\n\n\n\n#if 0\n#ifdef CURRENT_THREAD_VIA_EXCEPTION\n// TODO: The following is not a \"good\" implementation,\n// because the callstack is only valid in the \"__except\" block...\n#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \\\n    do { \\\n        memset(&c, 0, sizeof(CONTEXT)); \\\n        EXCEPTION_POINTERS *pExp = NULL; \\\n        __try { \\\n            throw 0; \\\n        } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \\\n        if (pExp != NULL) \\\n            memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \\\n        c.ContextFlags = contextFlags; \\\n    } while(0);\n#else\n// The following should be enough for walking the callstack...\n#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \\\n    do { \\\n        memset(&c, 0, sizeof(CONTEXT)); \\\n        c.ContextFlags = contextFlags; \\\n        __asm    call x \\\n        __asm x: pop eax \\\n        __asm    mov c.Eip, eax \\\n        __asm    mov c.Ebp, ebp \\\n        __asm    mov c.Esp, esp \\\n    } while(0);\n#endif\n\n#else\n\n// The following is defined for x86 (XP and higher), x64 and IA64:\n#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \\\n    do { \\\n        memset(&c, 0, sizeof(CONTEXT)); \\\n        c.ContextFlags = contextFlags; \\\n        RtlCaptureContext(&c); \\\n    } while(0);\n#endif\n\n\nclass StackWalkerToString : public StackWalker\n{\n    std::string *m_out;\npublic:\n    StackWalkerToString(std::string &out) : m_out(&out) {}\nprotected:\n    virtual void OnOutput(LPCSTR szText)\n    {\n        m_out->append(szText);\n    }\n};\n\n#endif//StackWalker_hhhhh\n#endif//_WIN32\n"
  },
  {
    "path": "lib/CrashHandler/backtrace.h.in",
    "content": "#cmakedefine01 Backtrace_FOUND\n#if Backtrace_FOUND\n#include <${Backtrace_HEADER}>\n#endif\n"
  },
  {
    "path": "lib/CrashHandler/crash_handler.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef SDL_HEAD_HHHHHH\n#   include \"sdl_proxy/sdl_head.h\"\n#endif\n#ifndef SDL_SDL_ASSERT_H\n#   include \"sdl_proxy/sdl_assert.h\"\n#endif\n\n#if !defined(__16M__) && !defined(__WII__) && !defined(__3DS__) && !defined(__SWITCH__) && !defined(VITA)\n#   define PGE_ENABLE_SIGNAL_HOOKS\n#endif\n\n#ifndef THEXTECH_NO_SDL_BUILD\n#   include <SDL2/SDL_version.h>\n#   if !defined(THEXTECH_CLI_BUILD) && !defined(CUSTOM_AUDIO)\n#       include <SDL2/SDL_mixer_ext.h>\n#   endif\n#endif\n\n#include <exception>\n#include <cstdlib>\n#include <signal.h>\n\n#include <lib/CrashHandler/backtrace.h>\n\n#if defined(DEBUG_BUILD) && (Backtrace_FOUND || defined(_WIN32))\n#   define PGE_ENGINE_DEBUG\n#endif\n\n#ifdef PGE_ENGINE_DEBUG\n\n#ifdef __gnu_linux__\n#include <sys/stat.h>\n#include <string.h>\n#include <fcntl.h>\n#include <unistd.h>\n#endif\n\n#ifdef __APPLE__\n#include <stdbool.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <sys/sysctl.h>\n#endif\n\n#ifdef _WIN32\n#include <windows.h>\n#endif\n\n#endif //PGE_ENGINE_DEBUG\n\n// Exclude platforms that don't have SIG_INFO support\n#if    !defined(_WIN32) \\\n    && !defined(__16M__) \\\n    && !defined(__3DS__) \\\n    && !defined(__WII__) \\\n    && !defined(__WIIU__) \\\n    && !defined(__SWITCH__) \\\n    && !defined(VITA)\n#   define HAS_SIG_INFO\n#endif\n\n// Exclude personal data removal from platforms where API doesn't allows to recognise the user and/or home directory\n#if    !defined(VITA) \\\n    && !defined(__16M__) \\\n    && !defined(__3DS__) \\\n    && !defined(__WII__) \\\n    && !defined(__WIIU__)  \\\n    && !defined(__SWITCH__) \\\n    && !defined(__EMSCRIPTEN__) \\\n    && !defined(__ANDROID__) \\\n    && !defined(__HAIKU__)\n#   define DO_REMOVE_PERSONAL_DATA\n#endif\n\n\n#ifdef PGE_ENGINE_DEBUG\nstatic int isDebuggerPresent()\n{\n#if defined(__gnu_linux__)\n    const size_t buf_size = 2048;\n    char buf[buf_size];\n    int debugger_present = 0;\n\n    int status_fd = open(\"/proc/self/status\", O_RDONLY);\n    if(status_fd == -1)\n        return 0;\n\n    ssize_t num_read = read(status_fd, buf, buf_size);\n\n    if(num_read > 0)\n    {\n        static const char TracerPid[] = \"TracerPid:\";\n        char *tracer_pid;\n\n        if(num_read < (ssize_t)buf_size)\n            buf[num_read] = 0;\n        else\n            buf[buf_size - 1] = 0;\n        tracer_pid    = strstr(buf, TracerPid);\n        if(tracer_pid)\n            debugger_present = static_cast<bool>(SDL_atoi(tracer_pid + sizeof(TracerPid) - 1));\n    }\n    return debugger_present;\n\n#elif defined(__APPLE__)\n    // https://stackoverflow.com/a/2200786/5618998\n    int                 junk;\n    int                 mib[4];\n    struct kinfo_proc   info;\n    size_t              size;\n\n    // Initialize the flags so that, if sysctl fails for some bizarre\n    // reason, we get a predictable result.\n\n    info.kp_proc.p_flag = 0;\n\n    // Initialize mib, which tells sysctl the info we want, in this case\n    // we're looking for information about a specific process ID.\n\n    mib[0] = CTL_KERN;\n    mib[1] = KERN_PROC;\n    mib[2] = KERN_PROC_PID;\n    mib[3] = getpid();\n\n    // Call sysctl.\n\n    size = sizeof(info);\n    junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);\n    SDL_assert(junk == 0);\n\n    // We're being debugged if the P_TRACED flag is set.\n\n    return ( (info.kp_proc.p_flag & P_TRACED) != 0 );\n\n#elif defined(_WIN32)\n    return IsDebuggerPresent();\n\n#else\n    return 0; // Unknown platform\n#endif\n}\n#endif\n\n#ifdef _WIN32\n#   include <windows.h>\n#   include <dbghelp.h>\n#   include <shlobj.h>\n#elif (defined(__linux__) && !defined(__ANDROID__) || defined(__APPLE__))\n#   include <pwd.h>\n#   include <unistd.h>\n#elif defined(__ANDROID__)\n#   include <unwind.h>\n#   include <dlfcn.h>\n#   include <sstream>\n#   include <iomanip>\n#endif\n\n#include \"crash_handler.h\"\n#include \"../Logger/logger.h\"\n\n#include \"../../version.h\"\n#include \"core/msgbox.h\"\n\n#define STACK_FORMAT    \\\n    \"====Stack trace====\" OS_NEWLINE \\\n    \"%s\" OS_NEWLINE \\\n    \"===================\" OS_NEWLINE \\\n    \"%s\"\n\n#define STR_EXPAND(tok) #tok\n#define STRR(tok) STR_EXPAND(tok)\n\nstatic const char *g_messageToUser =\n    \"================================================\" OS_NEWLINE\n    \"            Additional information:\" OS_NEWLINE\n    \"================================================\" OS_NEWLINE\n    V_FILE_DESC \"\\n\"\n    \"- Version:           \" V_FILE_VERSION V_FILE_RELEASE OS_NEWLINE\n    \"- Architecture:      \" FILE_CPU OS_NEWLINE\n    \"- Operating system:  \" OPERATION_SYSTEM OS_NEWLINE\n    \"- GIT Revision code: #\" V_BUILD_VER OS_NEWLINE\n    \"- GIT branch:        \" V_BUILD_BRANCH OS_NEWLINE\n#ifndef DISABLE_XTECH_BUILD_DATE\n    \"- Build date:        \" V_DATE_OF_BUILD OS_NEWLINE\n#endif\n    \"================================================\" OS_NEWLINE\n#ifndef THEXTECH_NO_SDL_BUILD\n    \"SDL2 version:        \" STRR(SDL_MAJOR_VERSION) \".\" STRR(SDL_MINOR_VERSION) \".\" STRR(SDL_PATCHLEVEL) OS_NEWLINE\n#endif\n#if !defined(THEXTECH_NO_SDL_BUILD) && !defined(THEXTECH_CLI_BUILD) && !defined(CUSTOM_AUDIO)\n    \"SDL Mixer X version: \" STRR(SDL_MIXER_MAJOR_VERSION) \".\" STRR(SDL_MIXER_MINOR_VERSION) \".\" STRR(SDL_MIXER_PATCHLEVEL) OS_NEWLINE\n#endif\n    \"================================================\" OS_NEWLINE\n    \" Please send this log file to the developers by one of ways:\" OS_NEWLINE\n    \" - Via contact form:          https://wohlsoft.ru/contacts/\" OS_NEWLINE\n    \" - Official forums:           https://wohlsoft.ru/forum/\" OS_NEWLINE\n    \" - Official Discord server:   https://wohlsoft.ru/chat/\" OS_NEWLINE\n    \" - Make issue at GitHub repo: https://github.com/Wohlstand/TheXTech\" OS_NEWLINE OS_NEWLINE\n    \"================================================\" OS_NEWLINE;\n\n#ifdef _WIN32\n//\n// http://blog.aaronballman.com/2011/04/generating-a-stack-crawl/\n//\nstatic bool GetStackWalk(std::string &outWalk)\n{\n    // Set up the symbol options so that we can gather information from the current\n    // executable's PDB files, as well as the Microsoft symbol servers.  We also want\n    // to undecorate the symbol names we're returned.  If you want, you can add other\n    // symbol servers or paths via a semi-colon separated list in SymInitialized.\n    ::SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_INCLUDE_32BIT_MODULES | SYMOPT_UNDNAME);\n\n    if(!::SymInitialize(::GetCurrentProcess(), \"http://msdl.microsoft.com/download/symbols\", TRUE)) return false;\n\n    // Capture up to 25 stack frames from the current call stack.  We're going to\n    // skip the first stack frame returned because that's the GetStackWalk function\n    // itself, which we don't care about.\n    PVOID addrs[ 400 ] = { 0 };\n    USHORT frames = CaptureStackBackTrace(1, 400, addrs, nullptr);\n\n    for(USHORT i = 0; i < frames; i++)\n    {\n        // Allocate a buffer large enough to hold the symbol information on the stack and get\n        // a pointer to the buffer.  We also have to set the size of the symbol structure itself\n        // and the number of bytes reserved for the name.\n        ULONG64 buffer[(sizeof(SYMBOL_INFO) + 1024 + sizeof(ULONG64) - 1) / sizeof(ULONG64) ] = { 0 };\n        SYMBOL_INFO *info = (SYMBOL_INFO *)buffer;\n        info->SizeOfStruct = sizeof(SYMBOL_INFO);\n        info->MaxNameLen = 1024;\n        // Attempt to get information about the symbol and add it to our output parameter.\n        DWORD64 displacement = 0;\n\n        if(::SymFromAddr(::GetCurrentProcess(), (DWORD64)addrs[ i ], &displacement, info))\n        {\n            outWalk.append(info->Name, info->NameLen);\n            outWalk.append(OS_NEWLINE);\n        }\n    }\n\n    ::SymCleanup(::GetCurrentProcess());\n    return true;\n}\n#endif\n\n#ifdef __ANDROID__\nnamespace AndroidStackTrace\n{\nstruct BacktraceState\n{\n    void **current;\n    void **end;\n};\n\nstatic _Unwind_Reason_Code unwindCallback(struct _Unwind_Context *context, void *arg)\n{\n    BacktraceState *state = static_cast<BacktraceState *>(arg);\n    uintptr_t pc = _Unwind_GetIP(context);\n    if(pc)\n    {\n        if(state->current == state->end)\n            return _URC_END_OF_STACK;\n        else\n            *state->current++ = reinterpret_cast<void *>(pc);\n    }\n    return _URC_NO_REASON;\n}\n\n}\n\nstatic size_t captureBacktrace(void **buffer, size_t max)\n{\n    AndroidStackTrace::BacktraceState state = {buffer, buffer + max};\n    _Unwind_Backtrace(AndroidStackTrace::unwindCallback, &state);\n\n    return state.current - buffer;\n}\n\nstatic void androidDumpBacktrace(std::ostringstream &os, void **buffer, size_t count)\n{\n    for(size_t idx = 0; idx < count; ++idx)\n    {\n        const void *addr = buffer[idx];\n        const char *symbol = \"\";\n\n        Dl_info info;\n        if(dladdr(addr, &info) && info.dli_sname)\n            symbol = info.dli_sname;\n\n        os << \"  #\" << std::setw(2) << idx << \": \" << addr << \"  \" << symbol << \"\\n\";\n    }\n}\n#endif\n\n#ifdef DO_REMOVE_PERSONAL_DATA\nstatic std::string getCurrentUserName()\n{\n    std::string user;\n\n#if defined(_WIN32)\n#   if defined(THEXTECH_WINRT)\n    user = \"UnknownUser\";  // FIXME: Implement for WinRT\n#   else\n    char    userName[256];\n    wchar_t userNameW[256];\n    DWORD usernameLen = 256;\n    GetUserNameW(userNameW, &usernameLen);\n    userNameW[usernameLen--] = L'\\0';\n    size_t nCnt = WideCharToMultiByte(CP_UTF8, 0, userNameW, usernameLen, userName, 256, 0, 0);\n    userName[nCnt] = '\\0';\n    user = std::string(userName);\n#   endif\n#else\n    struct passwd *pwd = getpwuid(getuid());\n    if(pwd == nullptr)\n        return \"UnknownUser\"; // Failed to get a user name!\n    user = std::string(pwd->pw_name);\n#endif\n\n    return user;\n}\n\nstatic std::string getCurrentHomePath()\n{\n    std::string homedir;\n\n#ifdef _WIN32\n#   if defined(THEXTECH_WINRT)\n    homedir = \"C:\\\\Users\\\\<unknown>\"; // FIXME: Implement for WinRT\n#   else\n    char    homeDir[MAX_PATH * 4];\n    wchar_t homeDirW[MAX_PATH];\n    SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, homeDirW);\n    size_t nCnt = WideCharToMultiByte(CP_UTF8, 0, homeDirW, -1, homeDir, MAX_PATH * 4, 0, 0);\n    homeDir[nCnt] = '\\0';\n    homedir = std::string(homeDir);\n#   endif\n#elif defined(__HAIKU__)\n    {\n        const char *home = SDL_getenv(\"HOME\");\n        if(home == nullptr)\n            return \"/home/<unknown>\"; // Failed to get a user name!\n        homedir = std::string(home);\n    }\n#else\n    struct passwd *pwd = getpwuid(getuid());\n    if(pwd == nullptr)\n        return \"/home/<unknown>\"; // Failed to get a user name!\n    homedir = std::string(pwd->pw_dir);\n\n#endif\n\n    return homedir;\n}\n\nstatic void replaceStr(std::string &data, const std::string &toSearch, const std::string &replaceStr)\n{\n    size_t pos = data.find(toSearch);\n\n    while(pos != std::string::npos)\n    {\n        data.replace(pos, toSearch.size(), replaceStr);\n        pos = data.find(toSearch, pos + replaceStr.size());\n    }\n}\n\nstatic void removePersonalData(std::string &log)\n{\n    std::string user = getCurrentUserName();\n    std::string homePath = getCurrentHomePath();\n\n    // Replace username\n    if(!homePath.empty())\n    {\n        replaceStr(log, homePath, \"{...}\");\n#ifdef _WIN32\n        replaceStr(homePath, \"\\\\\", \"/\");\n        replaceStr(log, homePath, \"{...}\");\n#endif\n    }\n\n    replaceStr(log, user, \"anonymouse\");\n}\n\n#endif // DO_REMOVE_PERSONAL_DATA\n\n#if defined(__WII__) || defined(__WIIU__)\n#   define USE_PPC_BACKTRACE\nint ppc_backtrace(void **buffer, int size)\n{\n    int depth;\n    uint32_t stackptr = 0, lr, *addr;\n\n    pLogDebug(\"Stack trace: PPC: Get link register...\");\n\n    // get link register\n    asm volatile (\"mflr %0\" : \"=r\"(lr));\n\n    // link register is assigned to depth[0]\n    buffer[0] = (void *) (lr - 4);\n\n    pLogDebug(\"Stack trace: PPC: Get link stack pointer...\");\n\n    // get stackpointer\n    asm volatile(\"stw %%sp, 0(%0)\" : : \"b\" ((uint32_t)&stackptr));\n\n    pLogDebug(\"Stack trace: PPC: Assign...\");\n\n    // assign stack ptr to address\n    addr = reinterpret_cast<uint32_t *>(stackptr);\n\n    // get the frames\n    if (*addr)\n    {\n        // skip first two frames because this function\n        // does create a stackframe but doesn't set lr on\n        // the previous one.\n        addr = (uint32_t *)*addr;\n        if (*addr)\n            addr = (uint32_t *)*addr;\n\n    }\n\n    D_pLogDebugNA(\"Stack trace: PPC: Loop...\");\n\n    for(depth = 1; (depth < size && *addr); ++depth)\n    {\n        uint32_t * next = (uint32_t *) *addr;\n        buffer[depth] = (void *) (*(addr + 1) - 4);\n        addr = next;\n    }\n\n    D_pLogDebugNA(\"Stack trace: PPC: Return...\");\n\n    return depth;\n}\n#endif\n\nstatic std::string getStacktrace()\n{\n    D_pLogDebugNA(\"Stack trace: Initializing std::string...\");\n    std::string bkTrace;\n    D_pLogDebugNA(\"Stack trace: String done...\");\n\n#if defined(_WIN32)\n    GetStackWalk(bkTrace);\n\n#elif defined(USE_PPC_BACKTRACE)\n\n    D_pLogDebugNA(\"Stack trace: Static arrays...\");\n    void  *array[400];\n    char stack_entry[25];\n    int size;\n    D_pLogDebugNA(\"Stack trace: Done...\");\n\n    D_pLogDebugNA(\"Stack trace: Requesting backtrace...\");\n    size = ppc_backtrace(array, 400);\n\n    D_pLogDebugNA(\"Stack trace: Filling std::string...\");\n    for(int j = 0; j < size; j++)\n    {\n        SDL_snprintf(stack_entry, 25, \"- 0x%08x\\n\", (uint32_t)array[j]);\n        bkTrace.append(stack_entry);\n    }\n\n    D_pLogDebugNA(\"Stack trace: DONE!\");\n\n#elif Backtrace_FOUND\n    void  *array[400];\n    int size;\n    char **strings;\n\n    D_pLogDebugNA(\"Requesting backtrace...\");\n    size = backtrace(array, 400);\n    D_pLogDebugNA(\"Converting...\");\n    strings = backtrace_symbols(array, size);\n    D_pLogDebugNA(\"Filling std::string...\");\n\n    for(int j = 0; j < size; j++)\n    {\n        bkTrace.append(strings[j]);\n        bkTrace.push_back('\\n');\n    }\n\n    D_pLogDebugNA(\"DONE!\");\n\n#elif defined(__ANDROID__)\n    const size_t max = 400;\n    void *buffer[max];\n    std::ostringstream oss;\n    androidDumpBacktrace(oss, buffer, captureBacktrace(buffer, max));\n    D_pLogDebugNA(\"DONE!\");\n    bkTrace = oss.str();\n\n#else\n    bkTrace = \"<Stack trace not supported for this platform!>\";\n#endif\n\n#ifdef DO_REMOVE_PERSONAL_DATA\n    removePersonalData(bkTrace);\n#endif // DO_REMOVE_PERSONAL_DATA\n\n    return bkTrace;\n}\n\n\n#ifdef __GNUC__\n#   define LLVM_ATTRIBUTE_NORETURN __attribute__((noreturn))\n#elif defined(_MSC_VER)\n#   define LLVM_ATTRIBUTE_NORETURN //__declspec(noreturn)\n#else\n#   define LLVM_ATTRIBUTE_NORETURN\n#endif\n\nstatic LLVM_ATTRIBUTE_NORETURN void abortEngine(int sig)\n{\n    CloseLog();\n    signal(SIGABRT, SIG_DFL);\n#ifndef THEXTECH_NO_SDL_BUILD\n    SDL_Quit();\n#endif\n    exit(sig);\n}\n\nstatic CrashHandler::MsgBoxHook_t g_msgBoxHook = &XMsgBox::errorMsgBox;\n\nvoid CrashHandler::setCrashMsgBoxHook(MsgBoxHook_t hook)\n{\n    g_msgBoxHook = hook;\n}\n\nvoid LLVM_ATTRIBUTE_NORETURN CrashHandler::crashByUnhandledException()\n{\n    std::string stack = getStacktrace();\n    std::string exc;\n    std::string exc_line;\n\n    try\n    {\n        std::rethrow_exception(std::current_exception());\n    }\n    catch(const std::exception &e)\n    {\n        exc_line = e.what();\n        exc.append(\" caught an unhandled exception. [\");\n        exc.append(exc_line);\n        exc.append(\"]\");\n    }\n    catch(...)\n    {\n        exc_line = \"<unknown>\";\n        exc.append(\" caught unhandled exception. (unknown) \");\n    }\n\n    pLogFatal(\"<Unhandled exception! %s>\" OS_NEWLINE\n              STACK_FORMAT, exc.c_str(),\n              stack.c_str(), g_messageToUser);\n    g_msgBoxHook(\n        \"Unhandled exception!\",\n        \"The engine has crashed because of an unhandled exception!\" OS_NEWLINE OS_NEWLINE\n        \"--Exception message:----\" OS_NEWLINE +\n        exc_line + OS_NEWLINE\n        \"------------------------\"\n    );\n    abortEngine(-1);\n}\n\nvoid LLVM_ATTRIBUTE_NORETURN CrashHandler::crashByFlood()\n{\n    std::string stack = getStacktrace();\n    pLogFatal(\"<Out of memory!>\" OS_NEWLINE\n              STACK_FORMAT,\n              stack.c_str(), g_messageToUser);\n    g_msgBoxHook(\n        \"Out of memory!\",\n        \"Engine has crashed because out of memory! Try to close other applications and restart game.\");\n    abortEngine(-2);\n}\n\n#if defined(PGE_ENABLE_SIGNAL_HOOKS)\n#if defined(_WIN32) // Unsupported signals by Windows\nstruct siginfo_t;\n#endif\n\nstatic void handle_signal(int signal, siginfo_t *siginfo, void * /*context*/)\n{\n#if !defined(_WIN32) || !defined(__SWITCH__)  // Unsupported signals by Windows and Switch\n    (void)siginfo;\n#endif\n\n    // Find out which signal we're handling\n    switch(signal)\n    {\n#ifndef _WIN32  //Unsupported signals by Windows\n\n    case SIGHUP:\n        pLogWarning(\"Terminal was closed\");\n        abortEngine(signal);\n\n    case SIGQUIT:\n        pLogWarning(\"<Quit command>\");\n        abortEngine(signal);\n\n    case SIGKILL:\n        pLogFatal(\"<killed>\");\n        abortEngine(signal);\n\n    case SIGALRM:\n    {\n        pLogFatal(\"<alarm() time out!>\");\n        g_msgBoxHook(\n            \"Time out!\",\n            \"Engine has abourted because alarm() time out!\");\n        abortEngine(signal);\n    }\n\n    case SIGBUS:\n    {\n        std::string stack = getStacktrace();\n\n#   if defined(HAS_SIG_INFO)\n        if(siginfo)\n        {\n            switch(siginfo->si_code)\n            {\n            case BUS_ADRALN:\n                pLogFatal(\"<Physical memory address error: wrong address alignment>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n\n            case BUS_ADRERR:\n                pLogFatal(\"<Physical memory address error: physical address is not exists>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n\n            case BUS_OBJERR:\n                pLogFatal(\"<Physical memory address error: object specific hardware error>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n\n            default:\n                pLogFatal(\"<Physical memory address error>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n            }\n        }\n        else\n#   endif // HAS_SIG_INFO\n        {\n            pLogFatal(\"<Physical memory address error>\\n\"\n                      STACK_FORMAT,\n                      stack.c_str(), g_messageToUser);\n        }\n\n        g_msgBoxHook(\n            \"Physical memory address error!\",\n            \"Engine has crashed because a physical memory address error\");\n        abortEngine(signal);\n    }\n\n    case SIGURG:\n    case SIGUSR1:\n    case SIGUSR2:\n        break;\n\n    case SIGILL:\n    {\n        std::string stack = getStacktrace();\n        pLogFatal(\"<Wrong CPU Instruction>\\n\"\n                  STACK_FORMAT,\n                  stack.c_str(), g_messageToUser);\n        g_msgBoxHook(\n            \"Wrong CPU Instruction!\",\n            \"Engine has crashed because a wrong CPU instruction\");\n        abortEngine(signal);\n    }\n\n#endif // NOT _WIN32\n\n    case SIGFPE:\n    {\n        std::string stack = getStacktrace();\n\n#if defined(HAS_SIG_INFO)  //Unsupported signals by Windows and Switch\n        if(siginfo)\n        {\n            switch(siginfo->si_code)\n            {\n            case FPE_INTDIV:\n                pLogFatal(\"<wrong arithmetical operation: division of integer number by zero>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n\n            case FPE_FLTDIV:\n                pLogFatal(\"<wrong arithmetical operation: division of floating point number by zero>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n\n            case FPE_INTOVF:\n                pLogFatal(\"<wrong arithmetical operation: integer number max bits size overflot>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n\n            case FPE_FLTOVF:\n                pLogFatal(\"<wrong arithmetical operation: floating point number max bits size overflot>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n\n            default:\n                pLogFatal(\"<wrong arithmetical operation>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n            }\n        }\n        else\n#endif // HAS_SIG_INFO\n        {\n            pLogFatal(\"<wrong arithmetical operation>\" OS_NEWLINE\n                      STACK_FORMAT,\n                      stack.c_str(), g_messageToUser);\n        }\n\n        g_msgBoxHook(\n            \"Wrong arithmetical operation\",\n            \"Engine has crashed because of a wrong arithmetical operation!\");\n        abortEngine(signal);\n    }\n\n    case SIGABRT:\n    {\n        std::string stack = getStacktrace();\n        pLogFatal(\"<Aborted!>\" OS_NEWLINE\n                  STACK_FORMAT,\n                  stack.c_str(), g_messageToUser);\n        g_msgBoxHook(\n            \"Aborted\",\n            \"Engine has been aborted because critical error was occouped.\");\n        abortEngine(signal);\n    }\n\n    case SIGSEGV:\n    {\n        D_pLogDebugNA(OS_NEWLINE \"===========================================================\" OS_NEWLINE\n                      \"Attempt to take a backtrace...\"\n                      \"(if log ends before \\\"DONE\\\" will be shown, seems also trouble in the backtracing function too...)\");\n        std::string stack = getStacktrace();\n\n#if defined(HAS_SIG_INFO)  //Unsupported signals by Windows\n        if(siginfo)\n        {\n            switch(siginfo->si_code)\n            {\n            case SEGV_MAPERR:\n                pLogFatal(\"<Segmentation fault crash!: Address is not pointing to object!!!>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n\n            case SEGV_ACCERR:\n                pLogFatal(\"<Segmentation fault crash!: Wrong access rights for address!!!>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n\n            default:\n                pLogFatal(\"<Segmentation fault crash!>\\n\"\n                          STACK_FORMAT,\n                          stack.c_str(), g_messageToUser);\n                break;\n            }\n        }\n        else\n#endif // HAS_SIG_INFO\n        {\n            pLogFatal(\"<Segmentation fault crash!>\" OS_NEWLINE\n                      STACK_FORMAT,\n                      stack.c_str(), g_messageToUser);\n        }\n\n        g_msgBoxHook(\n            \"Segmentation fault\",\n            \"Engine has crashed because of a Segmentation fault.\\n\"\n            \"Run debugging with a built in debug mode application\\n\"\n            \"and retry your recent actions to get more detailed information.\");\n        abortEngine(signal);\n    }\n\n    case SIGINT:\n    {\n        pLogFatal(\"<Interrupted!>\");\n        g_msgBoxHook(\n            \"Interrupt\",\n            \"Engine has been interrupted\");\n        abortEngine(signal);\n    }\n\n    default:\n        return;\n    }\n}\n#endif // defined(PGE_ENABLE_SIGNAL_HOOKS)\n\n#ifndef THEXTECH_NO_SDL_BUILD\nstatic SDL_AssertState custom_sdl_handler(const SDL_AssertData *data, void *userdata)\n{\n    CrashHandler::logAssertInfo(data);\n    return SDL_GetDefaultAssertionHandler()(data, userdata);\n}\n\nvoid CrashHandler::logAssertInfo(const void* data_p)\n{\n    const SDL_AssertData *data = reinterpret_cast<const SDL_AssertData*>(data_p);\n    CrashHandler::logAssertInfo(data->condition, data->filename, data->function, data->linenum);\n}\n#endif // #ifndef THEXTECH_NO_SDL_BUILD\n\nvoid CrashHandler::logAssertInfo(const char *condition, const char *file, const char *func, int line_number)\n{\n    std::string stack = getStacktrace();\n    pLogFatal(\"<Assertion condition has failed>:\" OS_NEWLINE\n              \"---------------------------------------------------------\" OS_NEWLINE\n              \"File: %s(%d)\" OS_NEWLINE\n              \"Function: %s\" OS_NEWLINE\n              \"Condition: %s\" OS_NEWLINE\n              \"---------------------------------------------------------\" OS_NEWLINE\n              STACK_FORMAT,\n              file, line_number,\n              func,\n              condition,\n              stack.c_str(),\n              g_messageToUser);\n\n    // Finalize logger\n    CloseLog();\n}\n\n#if defined(HAS_SIG_INFO)\nstatic struct sigaction act;\n#elif defined(PGE_ENABLE_SIGNAL_HOOKS)\n#   if _WIN32\nstruct siginfo_t;\n#   endif\n\nstatic void handle_signalWIN32(int signal)\n{\n    handle_signal(signal, nullptr, nullptr);\n}\n#endif\n\n\nvoid CrashHandler::initSigs()\n{\n#ifdef PGE_ENGINE_DEBUG\n    if(isDebuggerPresent())\n        return; // Don't initialize crash handlers on attached debugger\n#endif\n\n#ifndef THEXTECH_NO_SDL_BUILD\n    SDL_SetAssertionHandler(&custom_sdl_handler, NULL);\n#endif\n\n    std::set_new_handler(&crashByFlood);\n    std::set_terminate(&crashByUnhandledException);\n#if defined(HAS_SIG_INFO) && defined(PGE_ENABLE_SIGNAL_HOOKS) // Unsupported signals by Windows\n    memset(&act, 0, sizeof(struct sigaction));\n    sigemptyset(&act.sa_mask);\n    act.sa_sigaction = handle_signal;\n    act.sa_flags = SA_SIGINFO;\n    sigaction(SIGHUP,  &act, nullptr);\n    sigaction(SIGQUIT, &act, nullptr);\n    //sigaction(SIGKILL, &act, nullptr); This signal is unhandlable\n    sigaction(SIGALRM, &act, nullptr);\n    sigaction(SIGURG,  &act, nullptr);\n    sigaction(SIGUSR1, &act, nullptr);\n    sigaction(SIGUSR2, &act, nullptr);\n    sigaction(SIGBUS,  &act, nullptr);\n    sigaction(SIGILL,  &act, nullptr);\n    sigaction(SIGFPE,  &act, nullptr);\n    sigaction(SIGSEGV, &act, nullptr);\n    sigaction(SIGINT,  &act, nullptr);\n    sigaction(SIGABRT, &act, nullptr);\n#elif defined(PGE_ENABLE_SIGNAL_HOOKS) // HAS_SIG_INFO\n    signal(SIGILL,  &handle_signalWIN32);\n    signal(SIGFPE,  &handle_signalWIN32);\n    signal(SIGSEGV, &handle_signalWIN32);\n    signal(SIGINT,  &handle_signalWIN32);\n    signal(SIGABRT, &handle_signalWIN32);\n#   ifndef _WIN32\n    signal(SIGHUP, &handle_signalWIN32);\n    signal(SIGQUIT, &handle_signalWIN32);\n    signal(SIGALRM, &handle_signalWIN32);\n    signal(SIGBUS, &handle_signalWIN32);\n    signal(SIGURG, &handle_signalWIN32);\n#   endif\n#endif // HAS_SIG_INFO\n}\n\n/* Signals End */\n"
  },
  {
    "path": "lib/CrashHandler/crash_handler.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef CRASHHANDLER_H\n#define CRASHHANDLER_H\n\n#include <string>\n\nclass CrashHandler\n{\npublic:\n    CrashHandler() = default;\n    typedef void (*MsgBoxHook_t)(const std::string &title, const std::string &message);\n    static void setCrashMsgBoxHook(MsgBoxHook_t hook);\n    static void crashByUnhandledException();\n    static void crashByFlood();\n    static void initSigs();\n#ifndef THEXTECH_NO_SDL_BUILD\n    static void logAssertInfo(const void *data);\n#endif\n    static void logAssertInfo(const char *condition, const char *file, const char *func, int line_number);\n};\n\n#endif // CRASHHANDLER_H\n"
  },
  {
    "path": "lib/FixPointCS/Fixed64.h",
    "content": "//\n// FixPointCS\n//\n// Copyright(c) Jere Sanisalo, Petri Kero\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY INT64_C(C)AIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER INT64_C(DEA)INGS IN THE\n// SOFTWARE.\n//\n\n//\n// GENERATED FILE!!!\n//\n// Generated from Fixed64.cs, part of the FixPointCS project (MIT license).\n//\n#pragma once\n#ifndef __FIXED64_H\n#define __FIXED64_H\n\n// Include numeric types\n#include <stdint.h>\n#include \"FixedUtil.h\"\n\n\n// If FP_ASSERT is not custom-defined, then use the standard one\n#ifndef FP_ASSERT\n#   define FP_ASSERT(x) (void)(x)\n#endif\n\n// If FP_CUSTOM_INVALID_ARGS is defined, then the used is expected to implement the following functions in\n// the FixedUtil namespace:\n//  void InvalidArgument(const char* funcName, const char* argName, FP_INT argValue);\n//  void InvalidArgument(const char* funcName, const char* argName, FP_INT argValue1, FP_INT argValue2);\n//\tvoid InvalidArgument(const char* funcName, const char* argName, FP_LONG argValue);\n//\tvoid InvalidArgument(const char* funcName, const char* argName, FP_LONG argValue1, FP_LONG argValue2);\n// These functions should handle the cases for invalid arguments in any desired way (assert, exception, log, ignore etc).\n//#define FP_CUSTOM_INVALID_ARGS\n\nnamespace Fixed64\n{\n    typedef int32_t FP_INT;\n    typedef uint32_t FP_UINT;\n    typedef int64_t FP_LONG;\n    typedef uint64_t FP_ULONG;\n\n    static_assert(sizeof(FP_INT) == 4, \"Wrong bytesize for FP_INT\");\n    static_assert(sizeof(FP_UINT) == 4, \"Wrong bytesize for FP_UINT\");\n    static_assert(sizeof(FP_LONG) == 8, \"Wrong bytesize for FP_LONG\");\n    static_assert(sizeof(FP_ULONG) == 8, \"Wrong bytesize for FP_ULONG\");\n\n\n\n    const FP_INT Shift = 32;\n    const FP_LONG FractionMask = ( INT64_C(1) << Shift ) - 1; // Space before INT64_C(1) needed because of hacky C++ code generator\n    const FP_LONG IntegerMask = ~FractionMask;\n\n    // Constants\n    const FP_LONG Zero = INT64_C(0);\n    const FP_LONG Neg1 = -(INT64_C(1) << Shift);\n    const FP_LONG One = INT64_C(1) << Shift;\n    const FP_LONG Two = INT64_C(2) << Shift;\n    const FP_LONG Three = INT64_C(3) << Shift;\n    const FP_LONG Four = INT64_C(4) << Shift;\n    const FP_LONG Half = One >> 1;\n    const FP_LONG Pi = INT64_C(13493037705); //(FP_LONG)(Math.PI * 65536.0) << 16;\n    const FP_LONG Pi2 = INT64_C(26986075409);\n    const FP_LONG PiHalf = INT64_C(6746518852);\n    const FP_LONG E = INT64_C(11674931555);\n\n    const FP_LONG MinValue = INT64_MIN;\n    const FP_LONG MaxValue = INT64_MAX;\n\n    // Private constants\n    const FP_LONG RCP_LN2      = INT64_C(0x171547652); // 1.0 / log(2.0) ~= 1.4426950408889634\n    const FP_LONG RCP_LOG2_E   = INT64_C(2977044471);  // 1.0 / log2(e) ~= 0.6931471805599453\n    const FP_INT  RCP_HALF_PI  = 683565276; // 1.0 / (4.0 * 0.5 * Math.PI);  // the 4.0 factor converts directly to s2.30\n\n    /// <summary>\n    /// Converts an integer to a fixed-point value.\n    /// </summary>\n    FP_LONG FromInt(FP_INT v)\n    {\n        return (FP_LONG)v << Shift;\n    }\n\n    /// <summary>\n    /// Converts a double to a fixed-point value.\n    /// </summary>\n    FP_LONG FromDouble(double v)\n    {\n        return (FP_LONG)(v * 4294967296.0);\n    }\n\n    /// <summary>\n    /// Converts a float to a fixed-point value.\n    /// </summary>\n    FP_LONG FromFloat(float v)\n    {\n        return (FP_LONG)(v * 4294967296.0f);\n    }\n\n    /// <summary>\n    /// Converts a fixed-point value into an integer by rounding it up to nearest integer.\n    /// </summary>\n    FP_INT CeilToInt(FP_LONG v)\n    {\n        return (FP_INT)((v + (One - 1)) >> Shift);\n    }\n\n    /// <summary>\n    /// Converts a fixed-point value into an integer by rounding it down to nearest integer.\n    /// </summary>\n    FP_INT FloorToInt(FP_LONG v)\n    {\n        return (FP_INT)(v >> Shift);\n    }\n\n    /// <summary>\n    /// Converts a fixed-point value into an integer by rounding it to nearest integer.\n    /// </summary>\n    FP_INT RoundToInt(FP_LONG v)\n    {\n        return (FP_INT)((v + Half) >> Shift);\n    }\n\n    /// <summary>\n    /// Converts a fixed-point value into a double.\n    /// </summary>\n    double ToDouble(FP_LONG v)\n    {\n        return (double)v * (1.0 / 4294967296.0);\n    }\n\n    /// <summary>\n    /// Converts a FP value into a float.\n    /// </summary>\n    float ToFloat(FP_LONG v)\n    {\n        return (float)v * (1.0f / 4294967296.0f);\n    }\n\n    /// <summary>\n    /// Converts the value to a human readable string.\n    /// </summary>\n\n    /// <summary>\n    /// Returns the absolute (positive) value of x.\n    /// </summary>\n    FP_LONG Abs(FP_LONG x)\n    {\n        // \\note fails with LONG_MIN\n        FP_LONG mask = x >> 63;\n        return (x + mask) ^ mask;\n    }\n\n    /// <summary>\n    /// Negative absolute value (returns -abs(x)).\n    /// </summary>\n    FP_LONG Nabs(FP_LONG x)\n    {\n        return -Abs(x);\n    }\n\n    /// <summary>\n    /// Round up to nearest integer.\n    /// </summary>\n    FP_LONG Ceil(FP_LONG x)\n    {\n        return (x + FractionMask) & IntegerMask;\n    }\n\n    /// <summary>\n    /// Round down to nearest integer.\n    /// </summary>\n    FP_LONG Floor(FP_LONG x)\n    {\n        return x & IntegerMask;\n    }\n\n    /// <summary>\n    /// Round to nearest integer.\n    /// </summary>\n    FP_LONG Round(FP_LONG x)\n    {\n        return (x + Half) & IntegerMask;\n    }\n\n    /// <summary>\n    /// Returns the fractional part of x. Equal to 'x - floor(x)'.\n    /// </summary>\n    FP_LONG Fract(FP_LONG x)\n    {\n        return x & FractionMask;\n    }\n\n    /// <summary>\n    /// Returns the minimum of the two values.\n    /// </summary>\n    FP_LONG Min(FP_LONG a, FP_LONG b)\n    {\n        return (a < b) ? a : b;\n    }\n\n    /// <summary>\n    /// Returns the maximum of the two values.\n    /// </summary>\n    FP_LONG Max(FP_LONG a, FP_LONG b)\n    {\n        return (a > b) ? a : b;\n    }\n\n    /// <summary>\n    /// Returns the value clamped between min and max.\n    /// </summary>\n    FP_LONG Clamp(FP_LONG a, FP_LONG min, FP_LONG max)\n    {\n        return (a > max) ? max : (a < min) ? min : a;\n    }\n\n    /// <summary>\n    /// Returns the sign of the value (-1 if negative, 0 if zero, 1 if positive).\n    /// </summary>\n    FP_INT Sign(FP_LONG x)\n    {\n        // https://stackoverflow.com/questions/14579920/fast-sign-of-integer-in-c/14612418#14612418\n        return (FP_INT)((x >> 63) | (FP_LONG)(((FP_ULONG)-x) >> 63));\n    }\n\n    /// <summary>\n    /// Adds the two FP numbers together.\n    /// </summary>\n    FP_LONG Add(FP_LONG a, FP_LONG b)\n    {\n        return a + b;\n    }\n\n    /// <summary>\n    /// Subtracts the two FP numbers from each other.\n    /// </summary>\n    FP_LONG Sub(FP_LONG a, FP_LONG b)\n    {\n        return a - b;\n    }\n\n    /// <summary>\n    /// Multiplies two FP values together.\n    /// </summary>\n    FP_LONG Mul(FP_LONG a, FP_LONG b)\n    {\n        FP_LONG ai = a >> Shift;\n        FP_LONG af = (a & FractionMask);\n        FP_LONG bi = b >> Shift;\n        FP_LONG bf = (b & FractionMask);\n        return FixedUtil::LogicalShiftRight(af * bf, Shift) + ai * b + af * bi;\n    }\n\n    FP_INT MulIntLongLow(FP_INT a, FP_LONG b)\n    {\n        FP_ASSERT(a >= 0);\n        FP_INT bi = (FP_INT)(b >> Shift);\n        FP_LONG bf = b & FractionMask;\n        return (FP_INT)FixedUtil::LogicalShiftRight(a * bf, Shift) + a * bi;\n    }\n\n    FP_LONG MulIntLongLong(FP_INT a, FP_LONG b)\n    {\n        FP_ASSERT(a >= 0);\n        FP_LONG bi = b >> Shift;\n        FP_LONG bf = b & FractionMask;\n        return FixedUtil::LogicalShiftRight(a * bf, Shift) + a * bi;\n    }\n\n    /// <summary>\n    /// Linearly interpolate from a to b by t.\n    /// </summary>\n    FP_LONG Lerp(FP_LONG a, FP_LONG b, FP_LONG t)\n    {\n        return Mul(a, t) + Mul(b, One - t);\n    }\n\n    FP_INT Nlz(FP_ULONG x)\n    {\n    #if NET5_0_OR_GREATER\n        return System.Numerics.BitOperations.LeadingZeroCount(x);\n    #else\n        FP_INT n = 0;\n        if (x <= INT64_C(0x00000000FFFFFFFF)) { n = n + 32; x = x << 32; }\n        if (x <= INT64_C(0x0000FFFFFFFFFFFF)) { n = n + 16; x = x << 16; }\n        if (x <= INT64_C(0x00FFFFFFFFFFFFFF)) { n = n + 8; x = x << 8; }\n        if (x <= INT64_C(0x0FFFFFFFFFFFFFFF)) { n = n + 4; x = x << 4; }\n        if (x <= INT64_C(0x3FFFFFFFFFFFFFFF)) { n = n + 2; x = x << 2; }\n        if (x <= INT64_C(0x7FFFFFFFFFFFFFFF)) { n = n + 1; }\n        if (x == 0) return 64;\n        return n;\n    #endif\n    }\n\n    /// <summary>\n    /// Divides two FP values.\n    /// </summary>\n    FP_LONG DivPrecise(FP_LONG arg_a, FP_LONG arg_b)\n    {\n        // From http://www.hackersdelight.org/hdcodetxt/divlu.c.txt\n\n        FP_LONG sign_dif = arg_a ^ arg_b;\n\n        const FP_ULONG b = INT64_C(0x100000000); // Number base (32 bits)\n        FP_ULONG abs_arg_a = (FP_ULONG)((arg_a < 0) ? -arg_a : arg_a);\n        FP_ULONG u1 = abs_arg_a >> 32;\n        FP_ULONG u0 = abs_arg_a << 32;\n        FP_ULONG v = (FP_ULONG)((arg_b < 0) ? -arg_b : arg_b);\n\n        // Overflow?\n        if (u1 >= v)\n        {\n            //rem = 0;\n            return INT64_C(0x7fffffffffffffff);\n        }\n\n        // Shift amount for norm\n        FP_INT s = Nlz(v); // 0 <= s <= 63\n        v = v << s; // Normalize the divisor\n        FP_ULONG vn1 = v >> 32; // Break the divisor into two 32-bit digits\n        FP_ULONG vn0 = v & INT64_C(0xffffffff);\n\n        FP_ULONG un32 = (u1 << s) | ((u0 >> (64 - s)) & (FP_ULONG)((FP_LONG)-s >> 63));\n        FP_ULONG un10 = u0 << s; // Shift dividend left\n\n        FP_ULONG un1 = un10 >> 32; // Break the right half of dividend into two digits\n        FP_ULONG un0 = un10 & INT64_C(0xffffffff);\n\n        // Compute the first quotient digit, q1\n        FP_ULONG q1 = un32 / vn1;\n        FP_ULONG rhat = un32 - q1 * vn1;\n        do\n        {\n            if ((q1 >= b) || ((q1 * vn0) > (b * rhat + un1)))\n            {\n                q1 = q1 - 1;\n                rhat = rhat + vn1;\n            }\n            else break;\n        } while (rhat < b);\n\n        FP_ULONG un21 = un32 * b + un1 - q1 * v; // Multiply and subtract\n\n        // Compute the second quotient digit, q0\n        FP_ULONG q0 = un21 / vn1;\n        rhat = un21 - q0 * vn1;\n        do\n        {\n            if ((q0 >= b) || ((q0 * vn0) > (b * rhat + un0)))\n            {\n                q0 = q0 - 1;\n                rhat = rhat + vn1;\n            }\n            else break;\n        } while (rhat < b);\n\n        // Calculate the remainder\n        // FP_ULONG r = (un21 * b + un0 - q0 * v) >> s;\n        // rem = (FP_LONG)r;\n\n        FP_ULONG ret = q1 * b + q0;\n        return (sign_dif < 0) ? -(FP_LONG)ret : (FP_LONG)ret;\n    }\n\n    /// <summary>\n    /// Calculates division approximation.\n    /// </summary>\n    FP_LONG Div(FP_LONG a, FP_LONG b)\n    {\n        if (b == MinValue || b == 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::Div\", \"b\", b);\n            return 0;\n        }\n\n        // Handle negative values.\n        FP_INT sign = (b < 0) ? -1 : 1;\n        b *= sign;\n\n        // Normalize input into [1.0, 2.0( range (convert to s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)b);\n        FP_INT n = (FP_INT)FixedUtil::ShiftRight(b, offset + 2);\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT(n >= ONE);\n\n        // Polynomial approximation.\n        FP_INT res = FixedUtil::RcpPoly4Lut8(n - ONE);\n\n        // Apply exponent, convert back to s32.32.\n        FP_LONG y = MulIntLongLong(res, a) << 2;\n        return FixedUtil::ShiftRight(sign * y, offset);\n    }\n\n    /// <summary>\n    /// Calculates division approximation.\n    /// </summary>\n    FP_LONG DivFast(FP_LONG a, FP_LONG b)\n    {\n        if (b == MinValue || b == 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::DivFast\", \"b\", b);\n            return 0;\n        }\n\n        // Handle negative values.\n        FP_INT sign = (b < 0) ? -1 : 1;\n        b *= sign;\n\n        // Normalize input into [1.0, 2.0( range (convert to s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)b);\n        FP_INT n = (FP_INT)FixedUtil::ShiftRight(b, offset + 2);\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT(n >= ONE);\n\n        // Polynomial approximation.\n        FP_INT res = FixedUtil::RcpPoly6(n - ONE);\n\n        // Apply exponent, convert back to s32.32.\n        FP_LONG y = MulIntLongLong(res, a) << 2;\n        return FixedUtil::ShiftRight(sign * y, offset);\n    }\n\n    /// <summary>\n    /// Calculates division approximation.\n    /// </summary>\n    FP_LONG DivFastest(FP_LONG a, FP_LONG b)\n    {\n        if (b == MinValue || b == 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::DivFastest\", \"b\", b);\n            return 0;\n        }\n\n        // Handle negative values.\n        FP_INT sign = (b < 0) ? -1 : 1;\n        b *= sign;\n\n        // Normalize input into [1.0, 2.0( range (convert to s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)b);\n        FP_INT n = (FP_INT)FixedUtil::ShiftRight(b, offset + 2);\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT(n >= ONE);\n\n        // Polynomial approximation.\n        FP_INT res = FixedUtil::RcpPoly4(n - ONE);\n\n        // Apply exponent, convert back to s32.32.\n        FP_LONG y = MulIntLongLong(res, a) << 2;\n        return FixedUtil::ShiftRight(sign * y, offset);\n    }\n\n    /// <summary>\n    /// Divides two FP values and returns the modulus.\n    /// </summary>\n    FP_LONG Mod(FP_LONG a, FP_LONG b)\n    {\n        if (b == 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::Mod\", \"b\", b);\n            return 0;\n        }\n\n        return a % b;\n    }\n\n    /// <summary>\n    /// Calculates the square root of the given number.\n    /// </summary>\n    FP_LONG SqrtPrecise(FP_LONG a)\n    {\n        // Adapted from https://github.com/chmike/fpsqrt\n        if (a <= 0)\n        {\n            if (a < 0)\n                FixedUtil::InvalidArgument(\"Fixed64::SqrtPrecise\", \"a\", a);\n            return 0;\n        }\n\n        FP_ULONG r = (FP_ULONG)a;\n        FP_ULONG b = INT64_C(0x4000000000000000);\n        FP_ULONG q = INT64_C(0);\n        while (b > INT64_C(0x40))\n        {\n            FP_ULONG t = q + b;\n            if (r >= t)\n            {\n                r -= t;\n                q = t + b;\n            }\n            r <<= 1;\n            b >>= 1;\n        }\n        q >>= 16;\n        return (FP_LONG)q;\n    }\n\n    FP_LONG Sqrt(FP_LONG x)\n    {\n        // Return 0 for all non-positive values.\n        if (x <= 0)\n        {\n            if (x < 0)\n                FixedUtil::InvalidArgument(\"Fixed64::Sqrt\", \"x\", x);\n            return 0;\n        }\n\n        // Constants (s2.30).\n        const FP_INT ONE = (1 << 30);\n        const FP_INT SQRT2 = 1518500249; // sqrt(2.0)\n\n        // Normalize input into [1.0, 2.0( range (as s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_ASSERT(n >= ONE);\n        FP_INT y = FixedUtil::SqrtPoly3Lut8(n - ONE);\n\n        // Divide offset by 2 (to get sqrt), compute adjust value for odd exponents.\n        FP_INT adjust = ((offset & 1) != 0) ? SQRT2 : ONE;\n        offset = offset >> 1;\n\n        // Apply exponent, convert back to s32.32.\n        FP_LONG yr = (FP_LONG)FixedUtil::Qmul30(adjust, y) << 2;\n        return (offset >= 0) ? (yr << offset) : (yr >> -offset);\n    }\n\n    FP_LONG SqrtFast(FP_LONG x)\n    {\n        // Return 0 for all non-positive values.\n        if (x <= 0)\n        {\n            if (x < 0)\n                FixedUtil::InvalidArgument(\"Fixed64::SqrtFast\", \"x\", x);\n            return 0;\n        }\n\n        // Constants (s2.30).\n        const FP_INT ONE = (1 << 30);\n        const FP_INT SQRT2 = 1518500249; // sqrt(2.0)\n\n        // Normalize input into [1.0, 2.0( range (as s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_ASSERT(n >= ONE);\n        FP_INT y = FixedUtil::SqrtPoly4(n - ONE);\n\n        // Divide offset by 2 (to get sqrt), compute adjust value for odd exponents.\n        FP_INT adjust = ((offset & 1) != 0) ? SQRT2 : ONE;\n        offset = offset >> 1;\n\n        // Apply exponent, convert back to s32.32.\n        FP_LONG yr = (FP_LONG)FixedUtil::Qmul30(adjust, y) << 2;\n        return (offset >= 0) ? (yr << offset) : (yr >> -offset);\n    }\n\n    FP_LONG SqrtFastest(FP_LONG x)\n    {\n        // Return 0 for all non-positive values.\n        if (x <= 0)\n        {\n            if (x < 0)\n                FixedUtil::InvalidArgument(\"Fixed64::SqrtFastest\", \"x\", x);\n            return 0;\n        }\n\n        // Constants (s2.30).\n        const FP_INT ONE = (1 << 30);\n        const FP_INT SQRT2 = 1518500249; // sqrt(2.0)\n\n        // Normalize input into [1.0, 2.0( range (as s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_ASSERT(n >= ONE);\n        FP_INT y = FixedUtil::SqrtPoly3(n - ONE);\n\n        // Divide offset by 2 (to get sqrt), compute adjust value for odd exponents.\n        FP_INT adjust = ((offset & 1) != 0) ? SQRT2 : ONE;\n        offset = offset >> 1;\n\n        // Apply exponent, convert back to s32.32.\n        FP_LONG yr = (FP_LONG)FixedUtil::Qmul30(adjust, y) << 2;\n        return (offset >= 0) ? (yr << offset) : (yr >> -offset);\n    }\n\n    /// <summary>\n    /// Calculates the reciprocal square root.\n    /// </summary>\n    FP_LONG RSqrt(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::RSqrt\", \"x\", x);\n            return 0;\n        }\n\n        // Constants (s2.30).\n        const FP_INT ONE = (1 << 30);\n        const FP_INT HALF_SQRT2 = 759250125; // 0.5 * sqrt(2.0)\n\n        // Normalize input into [1.0, 2.0( range (as s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_ASSERT(n >= ONE);\n        FP_INT y = FixedUtil::RSqrtPoly3Lut16(n - ONE);\n\n        // Divide offset by 2 (to get sqrt), compute adjust value for odd exponents.\n        FP_INT adjust = ((offset & 1) != 0) ? HALF_SQRT2 : ONE;\n        offset = offset >> 1;\n\n        // Apply exponent, convert back to s32.32.\n        FP_LONG yr = (FP_LONG)FixedUtil::Qmul30(adjust, y) << 2;\n        return (offset >= 0) ? (yr >> offset) : (yr << -offset);\n    }\n\n    /// <summary>\n    /// Calculates the reciprocal square root.\n    /// </summary>\n    FP_LONG RSqrtFast(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::RSqrtFast\", \"x\", x);\n            return 0;\n        }\n\n        // Constants (s2.30).\n        const FP_INT ONE = (1 << 30);\n        const FP_INT HALF_SQRT2 = 759250125; // 0.5 * sqrt(2.0)\n\n        // Normalize input into [1.0, 2.0( range (as s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_ASSERT(n >= ONE);\n        FP_INT y = FixedUtil::RSqrtPoly5(n - ONE);\n\n        // Divide offset by 2 (to get sqrt), compute adjust value for odd exponents.\n        FP_INT adjust = ((offset & 1) != 0) ? HALF_SQRT2 : ONE;\n        offset = offset >> 1;\n\n        // Apply exponent, convert back to s32.32.\n        FP_LONG yr = (FP_LONG)FixedUtil::Qmul30(adjust, y) << 2;\n        return (offset >= 0) ? (yr >> offset) : (yr << -offset);\n    }\n\n    /// <summary>\n    /// Calculates the reciprocal square root.\n    /// </summary>\n    FP_LONG RSqrtFastest(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::RSqrtFastest\", \"x\", x);\n            return 0;\n        }\n\n        // Constants (s2.30).\n        const FP_INT ONE = (1 << 30);\n        const FP_INT HALF_SQRT2 = 759250125; // 0.5 * sqrt(2.0)\n\n        // Normalize input into [1.0, 2.0( range (as s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_ASSERT(n >= ONE);\n        FP_INT y = FixedUtil::RSqrtPoly3(n - ONE);\n\n        // Divide offset by 2 (to get sqrt), compute adjust value for odd exponents.\n        FP_INT adjust = ((offset & 1) != 0) ? HALF_SQRT2 : ONE;\n        offset = offset >> 1;\n\n        // Apply exponent, convert back to s32.32.\n        FP_LONG yr = (FP_LONG)FixedUtil::Qmul30(adjust, y) << 2;\n        return (offset >= 0) ? (yr >> offset) : (yr << -offset);\n    }\n\n    /// <summary>\n    /// Calculates reciprocal approximation.\n    /// </summary>\n    FP_LONG Rcp(FP_LONG x)\n    {\n        if (x == MinValue || x == 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::Rcp\", \"x\", x);\n            return 0;\n        }\n\n        // Handle negative values.\n        FP_INT sign = (x < 0) ? -1 : 1;\n        x *= sign;\n\n        // Normalize input into [1.0, 2.0( range (convert to s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)FixedUtil::ShiftRight(x, offset + 2);\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT(n >= ONE);\n\n        // Polynomial approximation.\n        FP_INT res = FixedUtil::RcpPoly4Lut8(n - ONE);\n        FP_LONG y = (FP_LONG)(sign * res) << 2;\n\n        // Apply exponent, convert back to s32.32.\n        return FixedUtil::ShiftRight(y, offset);\n    }\n\n    /// <summary>\n    /// Calculates reciprocal approximation.\n    /// </summary>\n    FP_LONG RcpFast(FP_LONG x)\n    {\n        if (x == MinValue || x == 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::RcpFast\", \"x\", x);\n            return 0;\n        }\n\n        // Handle negative values.\n        FP_INT sign = (x < 0) ? -1 : 1;\n        x *= sign;\n\n        // Normalize input into [1.0, 2.0( range (convert to s2.30).\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)FixedUtil::ShiftRight(x, offset + 2);\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT(n >= ONE);\n\n        // Polynomial approximation.\n        FP_INT res = FixedUtil::RcpPoly6(n - ONE);\n        FP_LONG y = (FP_LONG)(sign * res) << 2;\n\n        // Apply exponent, convert back to s32.32.\n        return FixedUtil::ShiftRight(y, offset);\n    }\n\n    /// <summary>\n    /// Calculates reciprocal approximation.\n    /// </summary>\n    FP_LONG RcpFastest(FP_LONG x)\n    {\n        if (x == MinValue || x == 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::RcpFastest\", \"x\", x);\n            return 0;\n        }\n\n        // Handle negative values.\n        FP_INT sign = (x < 0) ? -1 : 1;\n        x *= sign;\n\n        // Normalize input into [1.0, 2.0( range (convert to s2.30).\n        const FP_INT ONE = (1 << 30);\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)FixedUtil::ShiftRight(x, offset + 2);\n        //FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n\n        // Polynomial approximation.\n        FP_INT res = FixedUtil::RcpPoly4(n - ONE);\n        FP_LONG y = (FP_LONG)(sign * res) << 2;\n\n        // Apply exponent, convert back to s32.32.\n        return FixedUtil::ShiftRight(y, offset);\n    }\n\n    /// <summary>\n    /// Calculates the base 2 exponent.\n    /// </summary>\n    FP_LONG Exp2(FP_LONG x)\n    {\n        // Handle values that would under or overflow.\n        if (x >= 32 * One) return MaxValue;\n        if (x <= -32 * One) return 0;\n\n        // Compute exp2 for fractional part.\n        FP_INT k = (FP_INT)((x & FractionMask) >> 2);\n        FP_LONG y = (FP_LONG)FixedUtil::Exp2Poly5(k) << 2;\n\n        // Combine integer and fractional result, and convert back to s32.32.\n        FP_INT intPart = (FP_INT)(x >> Shift);\n        return (intPart >= 0) ? (y << intPart) : (y >> -intPart);\n    }\n\n    /// <summary>\n    /// Calculates the base 2 exponent.\n    /// </summary>\n    FP_LONG Exp2Fast(FP_LONG x)\n    {\n        // Handle values that would under or overflow.\n        if (x >= 32 * One) return MaxValue;\n        if (x <= -32 * One) return 0;\n\n        // Compute exp2 for fractional part.\n        FP_INT k = (FP_INT)((x & FractionMask) >> 2);\n        FP_LONG y = (FP_LONG)FixedUtil::Exp2Poly4(k) << 2;\n\n        // Combine integer and fractional result, and convert back to s32.32.\n        FP_INT intPart = (FP_INT)(x >> Shift);\n        return (intPart >= 0) ? (y << intPart) : (y >> -intPart);\n    }\n\n    /// <summary>\n    /// Calculates the base 2 exponent.\n    /// </summary>\n    FP_LONG Exp2Fastest(FP_LONG x)\n    {\n        // Handle values that would under or overflow.\n        if (x >= 32 * One) return MaxValue;\n        if (x <= -32 * One) return 0;\n\n        // Compute exp2 for fractional part.\n        FP_INT k = (FP_INT)((x & FractionMask) >> 2);\n        FP_LONG y = (FP_LONG)FixedUtil::Exp2Poly3(k) << 2;\n\n        // Combine integer and fractional result, and convert back to s32.32.\n        FP_INT intPart = (FP_INT)(x >> Shift);\n        return (intPart >= 0) ? (y << intPart) : (y >> -intPart);\n    }\n\n    FP_LONG Exp(FP_LONG x)\n    {\n        // e^x == 2^(x / ln(2))\n        return Exp2(Mul(x, RCP_LN2));\n    }\n\n    FP_LONG ExpFast(FP_LONG x)\n    {\n        // e^x == 2^(x / ln(2))\n        return Exp2Fast(Mul(x, RCP_LN2));\n    }\n\n    FP_LONG ExpFastest(FP_LONG x)\n    {\n        // e^x == 2^(x / ln(2))\n        return Exp2Fastest(Mul(x, RCP_LN2));\n    }\n\n    // Natural logarithm (base e).\n    FP_LONG Log(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::Log\", \"x\", x);\n            return 0;\n        }\n\n        // Normalize value to range [1.0, 2.0( as s2.30 and extract exponent.\n        const FP_INT ONE = (1 << 30);\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_ASSERT(n >= ONE);\n        FP_LONG y = (FP_LONG)FixedUtil::LogPoly5Lut8(n - ONE) << 2;\n\n        // Combine integer and fractional parts (into s32.32).\n        return (FP_LONG)offset * RCP_LOG2_E + y;\n    }\n\n    FP_LONG LogFast(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::LogFast\", \"x\", x);\n            return 0;\n        }\n\n        // Normalize value to range [1.0, 2.0( as s2.30 and extract exponent.\n        const FP_INT ONE = (1 << 30);\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_ASSERT(n >= ONE);\n        FP_LONG y = (FP_LONG)FixedUtil::LogPoly3Lut8(n - ONE) << 2;\n\n        // Combine integer and fractional parts (into s32.32).\n        return (FP_LONG)offset * RCP_LOG2_E + y;\n    }\n\n    FP_LONG LogFastest(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::LogFastest\", \"x\", x);\n            return 0;\n        }\n\n        // Normalize value to range [1.0, 2.0( as s2.30 and extract exponent.\n        const FP_INT ONE = (1 << 30);\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_ASSERT(n >= ONE);\n        FP_LONG y = (FP_LONG)FixedUtil::LogPoly5(n - ONE) << 2;\n\n        // Combine integer and fractional parts (into s32.32).\n        return (FP_LONG)offset * RCP_LOG2_E + y;\n    }\n\n    FP_LONG Log2(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::Log2\", \"x\", x);\n            return 0;\n        }\n\n        // Normalize value to range [1.0, 2.0( as s2.30 and extract exponent.\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n\n        // Polynomial approximation of mantissa.\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT(n >= ONE);\n        FP_LONG y = (FP_LONG)FixedUtil::Log2Poly4Lut16(n - ONE) << 2;\n\n        // Combine integer and fractional parts (into s32.32).\n        return ((FP_LONG)offset << Shift) + y;\n    }\n\n    FP_LONG Log2Fast(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::Log2Fast\", \"x\", x);\n            return 0;\n        }\n\n        // Normalize value to range [1.0, 2.0( as s2.30 and extract exponent.\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n\n        // Polynomial approximation of mantissa.\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT(n >= ONE);\n        FP_LONG y = (FP_LONG)FixedUtil::Log2Poly3Lut16(n - ONE) << 2;\n\n        // Combine integer and fractional parts (into s32.32).\n        return ((FP_LONG)offset << Shift) + y;\n    }\n\n    FP_LONG Log2Fastest(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::Log2Fastest\", \"x\", x);\n            return 0;\n        }\n\n        // Normalize value to range [1.0, 2.0( as s2.30 and extract exponent.\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n\n        // Polynomial approximation of mantissa.\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT(n >= ONE);\n        FP_LONG y = (FP_LONG)FixedUtil::Log2Poly5(n - ONE) << 2;\n\n        // Combine integer and fractional parts (into s32.32).\n        return ((FP_LONG)offset << Shift) + y;\n    }\n\n    /// <summary>\n    /// Calculates x to the power of the exponent.\n    /// </summary>\n    FP_LONG Pow(FP_LONG x, FP_LONG exponent)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            if (x < 0)\n                FixedUtil::InvalidArgument(\"Fixed64::Pow\", \"x\", x);\n            return 0;\n        }\n\n        return Exp(Mul(exponent, Log(x)));\n    }\n\n    /// <summary>\n    /// Calculates x to the power of the exponent.\n    /// </summary>\n    FP_LONG PowFast(FP_LONG x, FP_LONG exponent)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            if (x < 0)\n                FixedUtil::InvalidArgument(\"Fixed64::PowFast\", \"x\", x);\n            return 0;\n        }\n\n        return ExpFast(Mul(exponent, LogFast(x)));\n    }\n\n    /// <summary>\n    /// Calculates x to the power of the exponent.\n    /// </summary>\n    FP_LONG PowFastest(FP_LONG x, FP_LONG exponent)\n    {\n        // Return 0 for invalid values\n        if (x <= 0)\n        {\n            if (x < 0)\n                FixedUtil::InvalidArgument(\"Fixed64::PowFastest\", \"x\", x);\n            return 0;\n        }\n\n        return ExpFastest(Mul(exponent, LogFastest(x)));\n    }\n\n    FP_INT UnitSin(FP_INT z)\n    {\n        // See: http://www.coranac.com/2009/07/sines/\n\n        // Handle quadrants 1 and 2 by mirroring the [1, 3] range to [-1, 1] (by calculating 2 - z).\n        // The if condition uses the fact that for the quadrants of interest are 0b01 and 0b10 (top two bits are different).\n        if ((z ^ (z << 1)) < 0)\n            z = (1 << 31) - z;\n\n        // Now z is in range [-1, 1].\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT((z >= -ONE) && (z <= ONE));\n\n        // Polynomial approximation.\n        FP_INT zz = FixedUtil::Qmul30(z, z);\n        FP_INT res = FixedUtil::Qmul30(FixedUtil::SinPoly4(zz), z);\n\n        // Return s2.30 value.\n        return res;\n    }\n\n    FP_INT UnitSinFast(FP_INT z)\n    {\n        // See: http://www.coranac.com/2009/07/sines/\n\n        // Handle quadrants 1 and 2 by mirroring the [1, 3] range to [-1, 1] (by calculating 2 - z).\n        // The if condition uses the fact that for the quadrants of interest are 0b01 and 0b10 (top two bits are different).\n        if ((z ^ (z << 1)) < 0)\n            z = (1 << 31) - z;\n\n        // Now z is in range [-1, 1].\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT((z >= -ONE) && (z <= ONE));\n\n        // Polynomial approximation.\n        FP_INT zz = FixedUtil::Qmul30(z, z);\n        FP_INT res = FixedUtil::Qmul30(FixedUtil::SinPoly3(zz), z);\n\n        // Return s2.30 value.\n        return res;\n    }\n\n    FP_INT UnitSinFastest(FP_INT z)\n    {\n        // See: http://www.coranac.com/2009/07/sines/\n\n        // Handle quadrants 1 and 2 by mirroring the [1, 3] range to [-1, 1] (by calculating 2 - z).\n        // The if condition uses the fact that for the quadrants of interest are 0b01 and 0b10 (top two bits are different).\n        if ((z ^ (z << 1)) < 0)\n            z = (1 << 31) - z;\n\n        // Now z is in range [-1, 1].\n        const FP_INT ONE = (1 << 30);\n        FP_ASSERT((z >= -ONE) && (z <= ONE));\n\n        // Polynomial approximation.\n        FP_INT zz = FixedUtil::Qmul30(z, z);\n        FP_INT res = FixedUtil::Qmul30(FixedUtil::SinPoly2(zz), z);\n\n        // Return s2.30 value.\n        return res;\n    }\n\n    FP_LONG Sin(FP_LONG x)\n    {\n        // Map [0, 2pi] to [0, 4] (as s2.30).\n        // This also wraps the values into one period.\n        FP_INT z = MulIntLongLow(RCP_HALF_PI, x);\n\n        // Compute sine and convert to s32.32.\n        return (FP_LONG)UnitSin(z) << 2;\n    }\n\n    FP_LONG SinFast(FP_LONG x)\n    {\n        // Map [0, 2pi] to [0, 4] (as s2.30).\n        // This also wraps the values into one period.\n        FP_INT z = MulIntLongLow(RCP_HALF_PI, x);\n\n        // Compute sine and convert to s32.32.\n        return (FP_LONG)UnitSinFast(z) << 2;\n    }\n\n    FP_LONG SinFastest(FP_LONG x)\n    {\n        // Map [0, 2pi] to [0, 4] (as s2.30).\n        // This also wraps the values into one period.\n        FP_INT z = MulIntLongLow(RCP_HALF_PI, x);\n\n        // Compute sine and convert to s32.32.\n        return (FP_LONG)UnitSinFastest(z) << 2;\n    }\n\n    FP_LONG Cos(FP_LONG x)\n    {\n        return Sin(x + PiHalf);\n    }\n\n    FP_LONG CosFast(FP_LONG x)\n    {\n        return SinFast(x + PiHalf);\n    }\n\n    FP_LONG CosFastest(FP_LONG x)\n    {\n        return SinFastest(x + PiHalf);\n    }\n\n    FP_LONG Tan(FP_LONG x)\n    {\n        FP_INT z = MulIntLongLow(RCP_HALF_PI, x);\n        FP_LONG sinX = (FP_LONG)UnitSin(z) << 32;\n        FP_LONG cosX = (FP_LONG)UnitSin(z + (1 << 30)) << 32;\n        return Div(sinX, cosX);\n    }\n\n    FP_LONG TanFast(FP_LONG x)\n    {\n        FP_INT z = MulIntLongLow(RCP_HALF_PI, x);\n        FP_LONG sinX = (FP_LONG)UnitSinFast(z) << 32;\n        FP_LONG cosX = (FP_LONG)UnitSinFast(z + (1 << 30)) << 32;\n        return DivFast(sinX, cosX);\n    }\n\n    FP_LONG TanFastest(FP_LONG x)\n    {\n        FP_INT z = MulIntLongLow(RCP_HALF_PI, x);\n        FP_LONG sinX = (FP_LONG)UnitSinFastest(z) << 32;\n        FP_LONG cosX = (FP_LONG)UnitSinFastest(z + (1 << 30)) << 32;\n        return DivFastest(sinX, cosX);\n    }\n\n    FP_INT Atan2Div(FP_LONG y, FP_LONG x)\n    {\n        FP_ASSERT(y >= 0 && x > 0 && x >= y);\n\n        // Normalize input into [1.0, 2.0( range (convert to s2.30).\n        const FP_INT ONE = (1 << 30);\n        const FP_INT HALF = (1 << 29);\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_INT k = n - ONE;\n\n        // Polynomial approximation of reciprocal.\n        FP_INT oox = FixedUtil::RcpPoly4Lut8(k);\n        FP_ASSERT(oox >= HALF && oox <= ONE);\n\n        // Apply exponent and multiply.\n        FP_LONG yr = (offset >= 0) ? (y >> offset) : (y << -offset);\n        return FixedUtil::Qmul30((FP_INT)(yr >> 2), oox);\n    }\n\n    FP_LONG Atan2(FP_LONG y, FP_LONG x)\n    {\n        // See: https://www.dsprelated.com/showarticle/1052.php\n\n        if (x == 0)\n        {\n            if (y > 0) return PiHalf;\n            if (y < 0) return -PiHalf;\n\n            FixedUtil::InvalidArgument(\"Fixed64::Atan2\", \"y, x\", y, x);\n            return 0;\n        }\n\n        // \\note these round negative numbers slightly\n        FP_LONG nx = x ^ (x >> 63);\n        FP_LONG ny = y ^ (y >> 63);\n        FP_LONG negMask = ((x ^ y) >> 63);\n\n        if (nx >= ny)\n        {\n            FP_INT k = Atan2Div(ny, nx);\n            FP_INT z = FixedUtil::AtanPoly5Lut8(k);\n            FP_LONG angle = negMask ^ ((FP_LONG)z << 2);\n            if (x > 0) return angle;\n            if (y >= 0) return angle + Pi;\n            return angle - Pi;\n        }\n        else\n        {\n            FP_INT k = Atan2Div(nx, ny);\n            FP_INT z = FixedUtil::AtanPoly5Lut8(k);\n            FP_LONG angle = negMask ^ ((FP_LONG)z << 2);\n            return ((y > 0) ? PiHalf : -PiHalf) - angle;\n        }\n    }\n\n    FP_INT Atan2DivFast(FP_LONG y, FP_LONG x)\n    {\n        FP_ASSERT(y >= 0 && x > 0 && x >= y);\n\n        // Normalize input into [1.0, 2.0( range (convert to s2.30).\n        const FP_INT ONE = (1 << 30);\n        const FP_INT HALF = (1 << 29);\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_INT k = n - ONE;\n\n        // Polynomial approximation.\n        FP_INT oox = FixedUtil::RcpPoly6(k);\n        FP_ASSERT(oox >= HALF && oox <= ONE);\n\n        // Apply exponent and multiply.\n        FP_LONG yr = (offset >= 0) ? (y >> offset) : (y << -offset);\n        return FixedUtil::Qmul30((FP_INT)(yr >> 2), oox);\n    }\n\n    FP_LONG Atan2Fast(FP_LONG y, FP_LONG x)\n    {\n        // See: https://www.dsprelated.com/showarticle/1052.php\n\n        if (x == 0)\n        {\n            if (y > 0) return PiHalf;\n            if (y < 0) return -PiHalf;\n\n            FixedUtil::InvalidArgument(\"Fixed64::Atan2Fast\", \"y, x\", y, x);\n            return 0;\n        }\n\n        // \\note these round negative numbers slightly\n        FP_LONG nx = x ^ (x >> 63);\n        FP_LONG ny = y ^ (y >> 63);\n        FP_LONG negMask = ((x ^ y) >> 63);\n\n        if (nx >= ny)\n        {\n            FP_INT k = Atan2DivFast(ny, nx);\n            FP_INT z = FixedUtil::AtanPoly3Lut8(k);\n            FP_LONG angle = negMask ^ ((FP_LONG)z << 2);\n            if (x > 0) return angle;\n            if (y >= 0) return angle + Pi;\n            return angle - Pi;\n        }\n        else\n        {\n            FP_INT k = Atan2DivFast(nx, ny);\n            FP_INT z = FixedUtil::AtanPoly3Lut8(k);\n            FP_LONG angle = negMask ^ ((FP_LONG)z << 2);\n            return ((y > 0) ? PiHalf : -PiHalf) - angle;\n        }\n    }\n\n    FP_INT Atan2DivFastest(FP_LONG y, FP_LONG x)\n    {\n        FP_ASSERT(y >= 0 && x > 0 && x >= y);\n\n        // Normalize input into [1.0, 2.0( range (convert to s2.30).\n        const FP_INT ONE = (1 << 30);\n        const FP_INT HALF = (1 << 29);\n        FP_INT offset = 31 - Nlz((FP_ULONG)x);\n        FP_INT n = (FP_INT)(((offset >= 0) ? (x >> offset) : (x << -offset)) >> 2);\n        FP_INT k = n - ONE;\n\n        // Polynomial approximation.\n        FP_INT oox = FixedUtil::RcpPoly4(k);\n        FP_ASSERT(oox >= HALF && oox <= ONE);\n\n        // Apply exponent and multiply.\n        FP_LONG yr = (offset >= 0) ? (y >> offset) : (y << -offset);\n        return FixedUtil::Qmul30((FP_INT)(yr >> 2), oox);\n    }\n\n    FP_LONG Atan2Fastest(FP_LONG y, FP_LONG x)\n    {\n        // See: https://www.dsprelated.com/showarticle/1052.php\n\n        if (x == 0)\n        {\n            if (y > 0) return PiHalf;\n            if (y < 0) return -PiHalf;\n\n            FixedUtil::InvalidArgument(\"Fixed64::Atan2Fastest\", \"y, x\", y, x);\n            return 0;\n        }\n\n        // \\note these round negative numbers slightly\n        FP_LONG nx = x ^ (x >> 63);\n        FP_LONG ny = y ^ (y >> 63);\n        FP_LONG negMask = ((x ^ y) >> 63);\n\n        if (nx >= ny)\n        {\n            FP_INT z = Atan2DivFastest(ny, nx);\n            FP_INT res = FixedUtil::AtanPoly4(z);\n            FP_LONG angle = negMask ^ ((FP_LONG)res << 2);\n            if (x > 0) return angle;\n            if (y >= 0) return angle + Pi;\n            return angle - Pi;\n        }\n        else\n        {\n            FP_INT z = Atan2DivFastest(nx, ny);\n            FP_INT res = FixedUtil::AtanPoly4(z);\n            FP_LONG angle = negMask ^ ((FP_LONG)res << 2);\n            return ((y > 0) ? PiHalf : -PiHalf) - angle;\n        }\n    }\n\n    FP_LONG Asin(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x < -One || x > One)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::Asin\", \"x\", x);\n            return 0;\n        }\n\n        return Atan2(x, Sqrt(Mul(One + x, One - x)));\n    }\n\n    FP_LONG AsinFast(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x < -One || x > One)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::AsinFast\", \"x\", x);\n            return 0;\n        }\n\n        return Atan2Fast(x, SqrtFast(Mul(One + x, One - x)));\n    }\n\n    FP_LONG AsinFastest(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x < -One || x > One)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::AsinFastest\", \"x\", x);\n            return 0;\n        }\n\n        return Atan2Fastest(x, SqrtFastest(Mul(One + x, One - x)));\n    }\n\n    FP_LONG Acos(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x < -One || x > One)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::Acos\", \"x\", x);\n            return 0;\n        }\n\n        return Atan2(Sqrt(Mul(One + x, One - x)), x);\n    }\n\n    FP_LONG AcosFast(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x < -One || x > One)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::AcosFast\", \"x\", x);\n            return 0;\n        }\n\n        return Atan2Fast(SqrtFast(Mul(One + x, One - x)), x);\n    }\n\n    FP_LONG AcosFastest(FP_LONG x)\n    {\n        // Return 0 for invalid values\n        if (x < -One || x > One)\n        {\n            FixedUtil::InvalidArgument(\"Fixed64::AcosFastest\", \"x\", x);\n            return 0;\n        }\n\n        return Atan2Fastest(SqrtFastest(Mul(One + x, One - x)), x);\n    }\n\n    FP_LONG Atan(FP_LONG x)\n    {\n        return Atan2(x, One);\n    }\n\n    FP_LONG AtanFast(FP_LONG x)\n    {\n        return Atan2Fast(x, One);\n    }\n\n    FP_LONG AtanFastest(FP_LONG x)\n    {\n        return Atan2Fastest(x, One);\n    }\n\n\n\n\n    #undef FP_ASSERT\n}\n#endif // __FIXED64_H\n"
  },
  {
    "path": "lib/FixPointCS/FixedUtil.h",
    "content": "//\n// FixPointCS\n//\n// Copyright(c) Jere Sanisalo, Petri Kero\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY INT64_C(C)AIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER INT64_C(DEA)INGS IN THE\n// SOFTWARE.\n//\n\n//\n// GENERATED FILE!!!\n//\n// Generated from FixedUtil.cs, part of the FixPointCS project (MIT license).\n//\n#pragma once\n#ifndef __FIXEDUTIL_H\n#define __FIXEDUTIL_H\n\n// Include numeric types\n#include <stdint.h>\n\n\n// If FP_ASSERT is not custom-defined, then use the standard one\n#ifndef FP_ASSERT\n#   include <assert.h>\n#   define FP_ASSERT(x) assert(x)\n#endif\n\n// If FP_CUSTOM_INVALID_ARGS is defined, then the used is expected to implement the following functions in\n// the FixedUtil namespace:\n//  void InvalidArgument(const char* funcName, const char* argName, FP_INT argValue);\n//  void InvalidArgument(const char* funcName, const char* argName, FP_INT argValue1, FP_INT argValue2);\n//\tvoid InvalidArgument(const char* funcName, const char* argName, FP_LONG argValue);\n//\tvoid InvalidArgument(const char* funcName, const char* argName, FP_LONG argValue1, FP_LONG argValue2);\n// These functions should handle the cases for invalid arguments in any desired way (assert, exception, log, ignore etc).\n//#define FP_CUSTOM_INVALID_ARGS\n\nnamespace FixedUtil\n{\n    typedef int32_t FP_INT;\n    typedef uint32_t FP_UINT;\n    typedef int64_t FP_LONG;\n    typedef uint64_t FP_ULONG;\n\n    static_assert(sizeof(FP_INT) == 4, \"Wrong bytesize for FP_INT\");\n    static_assert(sizeof(FP_UINT) == 4, \"Wrong bytesize for FP_UINT\");\n    static_assert(sizeof(FP_LONG) == 8, \"Wrong bytesize for FP_LONG\");\n    static_assert(sizeof(FP_ULONG) == 8, \"Wrong bytesize for FP_ULONG\");\n\n#ifdef FP_CUSTOM_INVALID_ARGS\n    extern void InvalidArgument(const char* funcName, const char* argName, FP_INT argValue);\n    extern void InvalidArgument(const char* funcName, const char* argName, FP_INT argValue1, FP_INT argValue2);\n    extern void InvalidArgument(const char* funcName, const char* argName, FP_LONG argValue);\n    extern void InvalidArgument(const char* funcName, const char* argName, FP_LONG argValue1, FP_LONG argValue2);\n#else\n    inline void InvalidArgument(const char*, const char*, FP_INT) { }\n    inline void InvalidArgument(const char*, const char*, FP_INT, FP_INT) { }\n    inline void InvalidArgument(const char*, const char*, FP_LONG) { }\n    inline void InvalidArgument(const char*, const char*, FP_LONG, FP_LONG) { }\n#endif\n\n\n\n\n    // InvalidArgument function defined in the transpiler generated header\n\n    FP_INT Qmul29(FP_INT a, FP_INT b)\n    {\n        return (FP_INT)((FP_LONG)a * (FP_LONG)b >> 29);\n    }\n\n    FP_INT Qmul30(FP_INT a, FP_INT b)\n    {\n        return (FP_INT)((FP_LONG)a * (FP_LONG)b >> 30);\n    }\n\n    FP_INT ShiftLeft(FP_INT v, FP_INT shift)\n    {\n        return (shift >= 0) ? (v << shift) : (v >> -shift);\n    }\n\n    FP_INT ShiftRight(FP_INT v, FP_INT shift)\n    {\n        return (shift >= 0) ? (v >> shift) : (v << -shift);\n    }\n\n    FP_LONG ShiftRight(FP_LONG v, FP_INT shift)\n    {\n        return (shift >= 0) ? (v >> shift) : (v << -shift);\n    }\n\n    FP_LONG LogicalShiftRight(FP_LONG v, FP_INT shift)\n    {\n        return (FP_LONG)((FP_ULONG)v >> shift);\n    }\n\n    // Exp2()\n\n    // Precision: 13.24 bits\n    FP_INT Exp2Poly3(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 84039593); // 0.0782679701835315868647357253725971674790033117245148781445598202137415363194904317749528903660739148499430948967629357887\n        y = Qmul30(a, y + 242996024); // 0.226307682289372255347421644246257966273699535419878898050811760122384683941875786929647503217974831952486347597791720611\n        y = Qmul30(a, y + 746706207); // 0.695424347527096157787842630381144866247297152855606223804628419663873779738633781295399606415951253197570557505445343601\n        y = y + 1073741824;\n        return y;\n    }\n\n    // Precision: 18.19 bits\n    FP_INT Exp2Poly4(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 14555373); // 0.0135557472348149177040307931905578544538124307723745221579881209426474911809748672636364432116420009120178935332926148611\n        y = Qmul30(a, y + 55869331); // 0.0520323690084328924674487312215472415900450170687696511359785661622616863440911035364584944748959228308174520922142865995\n        y = Qmul30(a, y + 259179547); // 0.241379762937091639018661074809242143033914474070411268883151785382581196588939527676019951096295687987322799846420118765\n        y = Qmul30(a, y + 744137573); // 0.693032120819660550809859400778652760922228078088444557822881527512509625885994501523885111217166388269841854528072979774\n        y = y + 1073741824;\n        return y;\n    }\n\n    // Precision: 23.37 bits\n    FP_INT Exp2Poly5(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 2017903); // 0.00187931864849444079178064366523643962831734445578833344828943266930262096728457318136293441770024748382988959051143223706\n        y = Qmul30(a, y + 9654007); // 0.0089909950956369787948425038952611903126353369666002380364841111113291819538448433335270460143993823536893996134420311419\n        y = Qmul30(a, y + 59934847); // 0.0558186759615980431203104787377782200574008231751237416643519892213181468390965300572726745159591410385167709971364406724\n        y = Qmul30(a, y + 257869054); // 0.240159271464382269128561549965297376067896183861587452990582800168980375711748974675500420263841816476095364031528449763\n        y = Qmul30(a, y + 744266012); // 0.693151738829888268164504823736426773933750311540900233860291666829069674528025078752336924788099412647868575767381646186\n        y = y + 1073741824;\n        return y;\n    }\n\n    // Rcp()\n\n    // Precision: 11.33 bits\n    FP_INT RcpPoly4(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 166123244); // 0.154714327545457094588979713106287560782537959277436051827019427328357322113481152734370734443893548182187731839301875899\n        y = Qmul30(a, y + -581431354); // -0.54150014640909983106142899587200646273888285747102618139456799564925062739718403457029757055362741863765712410515426083\n        y = Qmul30(a, y + 939345296); // 0.874833479742433164394762329205339796072216190804359514727901328982583960730517367903630903886960751970990489874952162084\n        y = Qmul30(a, y + -1060908097); // -0.988047660878790427922313046439620894115871292610769385160352760661690655446814486067704067777226881515521097609099777152\n        y = y + 1073741824;\n        return y;\n    }\n\n    // Precision: 16.53 bits\n    FP_INT RcpPoly6(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 77852993); // 0.0725062501842326696626758301282171253618850679805450684783331254738896577827939599454470990870969993306249485759929666981\n        y = Qmul30(a, y + -350338469); // -0.326278125829047013482041235576977064128482805912452808152499064632503460022572819754511945891936496987812268591968349959\n        y = Qmul30(a, y + 723231606); // 0.673561921455982545223734340382301840739167811454662043292934558465813280438563313561682188125190962075365760110255605888\n        y = Qmul30(a, y + -974250754); // -0.907341721411285515029553588773713935349385980480415781958608445152553325879092398556945676696228380651281188866035287881\n        y = Qmul30(a, y + 1059679220); // 0.986903179099804504543521516658287329916121575881841375617775839327272856972646872081214163224722216327427202183393238047\n        y = Qmul30(a, y + -1073045505); // -0.999351503499687190918336862818115296539305668924179897277936013481919009292338927276885827848845300094324453411638172793\n        y = y + 1073741824;\n        return y;\n    }\n\n    FP_INT RcpPoly3Lut4Table[] =\n    {\n        -678697788, 1018046684, -1071069948, 1073721112,\n        -302893157, 757232894, -1008066289, 1068408287,\n        -154903745, 542163110, -902798392, 1051046118,\n        -87262610, 392681750, -792180891, 1023631333,\n    };\n\n    // Precision: 15.66 bits\n    FP_INT RcpPoly3Lut4(FP_INT a)\n    {\n        FP_INT offset = (a >> 28) * 4;\n        FP_INT y = Qmul30(a, RcpPoly3Lut4Table[offset + 0]);\n        y = Qmul30(a, y + RcpPoly3Lut4Table[offset + 1]);\n        y = Qmul30(a, y + RcpPoly3Lut4Table[offset + 2]);\n        y = y + RcpPoly3Lut4Table[offset + 3];\n        return y;\n    }\n\n    FP_INT RcpPoly4Lut8Table[] =\n    {\n        796773553, -1045765287, 1072588028, -1073726795, 1073741824,\n        456453183, -884378041, 1042385791, -1071088216, 1073651788,\n        276544830, -708646126, 977216564, -1060211779, 1072962711,\n        175386455, -559044324, 893798171, -1039424537, 1071009496,\n        115547530, -440524957, 805500803, -1010097984, 1067345574,\n        78614874, -348853503, 720007233, -974591889, 1061804940,\n        54982413, -278348465, 641021491, -935211003, 1054431901,\n        39383664, -223994590, 569927473, -893840914, 1045395281,\n    };\n\n    // Precision: 24.07 bits\n    FP_INT RcpPoly4Lut8(FP_INT a)\n    {\n        FP_INT offset = (a >> 27) * 5;\n        FP_INT y = Qmul30(a, RcpPoly4Lut8Table[offset + 0]);\n        y = Qmul30(a, y + RcpPoly4Lut8Table[offset + 1]);\n        y = Qmul30(a, y + RcpPoly4Lut8Table[offset + 2]);\n        y = Qmul30(a, y + RcpPoly4Lut8Table[offset + 3]);\n        y = y + RcpPoly4Lut8Table[offset + 4];\n        return y;\n    }\n\n    // Sqrt()\n\n    // Precision: 13.36 bits\n    FP_INT SqrtPoly3(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 26809804); // 0.0249685755493961204934845015323729712245958715357182065425848552518546416164312449413742280712638308483065114885417147904\n        y = Qmul30(a, y + -116435772); // -0.108439263715492087333244576730247754908569708153374339944951137491994192013534152641012071161185446185655458733810736431\n        y = Qmul30(a, y + 534384395); // 0.497684250539191015641448799407572862253645711994604206579046020230872028859209946550025377417563188072362793476181318665\n        y = y + 1073741824; // 1.0\n        return y;\n    }\n\n    // Precision: 16.50 bits\n    FP_INT SqrtPoly4(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, -11559524); // -0.0107656468280005064933278905326776959702034851444407595549875999349858889266381514341825269487372902092181743561671344361\n        y = Qmul30(a, y + 49235626); // 0.0458542501550120083313075597659725264999808459122954966477604412728019257521420334516113399358029950852981420572751187192\n        y = Qmul30(a, y + -129356986); // -0.120473082434524586846319215086079446047783981719931015048359535322204769538816469963000080874223090148906445903268633479\n        y = Qmul30(a, y + 536439312); // 0.499598041480608133810028270062482694087678496329024351132266431975121211175419626795958802214798958007840324433072946221\n        y = y + 1073741824; // 1.0\n        return y;\n    }\n\n    FP_INT SqrtPoly3Lut8Table[] =\n    {\n        57835763, -133550637, 536857054, 1073741824,\n        43771091, -128445855, 536217068, 1073769530,\n        34067722, -121273511, 534434402, 1073918540,\n        27129178, -113536005, 531547139, 1074279077,\n        22019236, -105917226, 527752485, 1074910452,\n        18161894, -98716852, 523266057, 1075843557,\n        15188335, -92049348, 518277843, 1077088717,\n        12854281, -85939307, 512942507, 1078642770,\n    };\n\n    // Precision: 23.56 bits\n    FP_INT SqrtPoly3Lut8(FP_INT a)\n    {\n        FP_INT offset = (a >> 27) * 4;\n        FP_INT y = Qmul30(a, SqrtPoly3Lut8Table[offset + 0]);\n        y = Qmul30(a, y + SqrtPoly3Lut8Table[offset + 1]);\n        y = Qmul30(a, y + SqrtPoly3Lut8Table[offset + 2]);\n        y = y + SqrtPoly3Lut8Table[offset + 3];\n        return y;\n    }\n\n    // RSqrt()\n\n    // Precision: 10.55 bits\n    FP_INT RSqrtPoly3(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, -91950555); // -0.0856356289309618075724442347978716997984112060739604608172096078728382955692378474864988406402256175535135909431476122756\n        y = Qmul30(a, y + 299398639); // 0.278836710932968623313626076681628936089988230155462820435332822241435754263689225928134347217644388668377307808008581711\n        y = Qmul30(a, y + -521939780); // -0.486094300815459291340337479778908197006741086393028323029783345373231219463397859016441739413597984747356793749404820923\n        y = y + 1073741824; // 1.0\n        return y;\n    }\n\n    // Precision: 16.08 bits\n    FP_INT RSqrtPoly5(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, -34036183); // -0.0316986662178132948125724057457789067274319219669948992806572724657733410288354401675056668794389506376695226173434879395\n        y = Qmul30(a, y + 140361627); // 0.130721952132469025002475913996909202114937889568059538633961150597311078891698356228999013320515864372663894767082977274\n        y = Qmul30(a, y + -276049470); // -0.257091104134768572313864227992129218223743238765168155465701243532365537275047394354439662434976402678179717893043406024\n        y = Qmul30(a, y + 391366758); // 0.364488696521754181874287383887070965401384329257252476170774837632778316357675818762347533724407215505228711973415321601\n        y = Qmul30(a, y + -536134428); // -0.4993140971150938153494823020412230032803111204046749234700376032365842777144378210442074505666869401945364431146552564\n        y = y + 1073741824; // 1.0\n        return y;\n    }\n\n    FP_INT RSqrtPoly3Lut16Table[] =\n    {\n        -301579590, 401404709, -536857690, 1073741824,\n        -245423010, 391086820, -536203235, 1073727515,\n        -202026137, 374967334, -534189977, 1073642965,\n        -168017146, 355951863, -530632261, 1073420226,\n        -141028602, 335796841, -525604155, 1073001192,\n        -119367482, 315555573, -519290609, 1072343850,\n        -101802870, 295846496, -511911750, 1071422108,\n        -87426328, 277017299, -503685655, 1070223323,\n        -75558212, 259246781, -494811415, 1068745317,\n        -65683680, 242608795, -485462769, 1066993613,\n        -57408255, 227112748, -475787122, 1064979109,\n        -50426484, 212729399, -465907121, 1062716254,\n        -44499541, 199407328, -455923331, 1060221646,\n        -39439007, 187083448, -445917204, 1057513002,\n        -35094980, 175689646, -435953979, 1054608400,\n        -31347269, 165156947, -426085312, 1051525761,\n    };\n\n    // Precision: 24.59 bits\n    FP_INT RSqrtPoly3Lut16(FP_INT a)\n    {\n        FP_INT offset = (a >> 26) * 4;\n        FP_INT y = Qmul30(a, RSqrtPoly3Lut16Table[offset + 0]);\n        y = Qmul30(a, y + RSqrtPoly3Lut16Table[offset + 1]);\n        y = Qmul30(a, y + RSqrtPoly3Lut16Table[offset + 2]);\n        y = y + RSqrtPoly3Lut16Table[offset + 3];\n        return y;\n    }\n\n    // Log()\n\n    // Precision: 12.18 bits\n    FP_INT LogPoly5(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 34835446); // 0.0324430374324099257645920506145091908173169505782530351933872568452187970039716570286755191899094832608276898590172296967\n        y = Qmul30(a, y + -149023176); // -0.138788648453891138663259214948877985710758551758834443319382469349215457727435900740974302256302169487791331019735819359\n        y = Qmul30(a, y + 315630515); // 0.293953823490661881198275484636301174125635657531784266710660462843103551518793294127904893642006610982580864921150987405\n        y = Qmul30(a, y + -530763208); // -0.494311758014893283441267083938639942320000878038159975670627731298854764041757060977673894877216298188619864197654458825\n        y = Qmul30(a, y + 1073581542); // 0.999850726105657924558890885094884131163156956047212371206642490453141495216122729917931111298021060974997472559962156274\n        return y;\n    }\n\n    FP_INT LogPoly3Lut4Table[] =\n    {\n        270509931, -528507852, 1073614348, 0,\n        139305305, -442070189, 1053671695, 1633382,\n        83615845, -360802306, 1013781196, 8222843,\n        52639154, -291267388, 961502851, 21386502,\n    };\n\n    // Precision: 12.51 bits\n    FP_INT LogPoly3Lut4(FP_INT a)\n    {\n        FP_INT offset = (a >> 28) * 4;\n        FP_INT y = Qmul30(a, LogPoly3Lut4Table[offset + 0]);\n        y = Qmul30(a, y + LogPoly3Lut4Table[offset + 1]);\n        y = Qmul30(a, y + LogPoly3Lut4Table[offset + 2]);\n        y = y + LogPoly3Lut4Table[offset + 3];\n        return y;\n    }\n\n    FP_INT LogPoly3Lut8Table[] =\n    {\n        309628536, -534507419, 1073724054, 0,\n        215207992, -502390266, 1069897914, 160852,\n        158892020, -461029083, 1059680319, 1010114,\n        120758300, -418592578, 1043877151, 2979626,\n        93932535, -378620013, 1023979692, 6288435,\n        74487828, -342313729, 1001351633, 10996073,\n        60012334, -309817259, 977010327, 17079637,\n        48377690, -279159893, 950059138, 24984183,\n    };\n\n    // Precision: 15.35 bits\n    FP_INT LogPoly3Lut8(FP_INT a)\n    {\n        FP_INT offset = (a >> 27) * 4;\n        FP_INT y = Qmul30(a, LogPoly3Lut8Table[offset + 0]);\n        y = Qmul30(a, y + LogPoly3Lut8Table[offset + 1]);\n        y = Qmul30(a, y + LogPoly3Lut8Table[offset + 2]);\n        y = y + LogPoly3Lut8Table[offset + 3];\n        return y;\n    }\n\n    FP_INT LogPoly5Lut8Table[] =\n    {\n        166189159, -263271008, 357682461, -536867223, 1073741814, 0,\n        91797130, -221452381, 347549389, -535551692, 1073651718, 2559,\n        55429773, -177286543, 325776420, -530104991, 1072960646, 38103,\n        35101911, -139778071, 297915163, -519690478, 1071001695, 186416,\n        23102252, -110088504, 268427087, -504993810, 1067326167, 555414,\n        15701243, -87124604, 239861114, -487185708, 1061762610, 1252264,\n        10960108, -69430156, 213404033, -467374507, 1054333366, 2368437,\n        7703441, -55178389, 188423866, -445453304, 1044702281, 4063226,\n    };\n\n    // Precision: 26.22 bits\n    FP_INT LogPoly5Lut8(FP_INT a)\n    {\n        FP_INT offset = (a >> 27) * 6;\n        FP_INT y = Qmul30(a, LogPoly5Lut8Table[offset + 0]);\n        y = Qmul30(a, y + LogPoly5Lut8Table[offset + 1]);\n        y = Qmul30(a, y + LogPoly5Lut8Table[offset + 2]);\n        y = Qmul30(a, y + LogPoly5Lut8Table[offset + 3]);\n        y = Qmul30(a, y + LogPoly5Lut8Table[offset + 4]);\n        y = y + LogPoly5Lut8Table[offset + 5];\n        return y;\n    }\n\n    // Log2()\n\n    // Precision: 12.29 bits\n    FP_INT Log2Poly5(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 47840369); // 0.0445548155276207896995334754162140597637031202974591126199168774393873986289641382244343408731171726931757539068975485089\n        y = Qmul30(a, y + -208941842); // -0.194592255208938416591621284205816720732140050852301947258138293025978577320103558315407526014074332839410207729682281855\n        y = Qmul30(a, y + 450346773); // 0.419418116511448143225544710148490988337404380945888758986024844824480954559055561814904948371254539592688384332290775469\n        y = Qmul30(a, y + -764275149); // -0.711786700405071059895411856470396704111669190613765834655920030051222814749610509844938951593547905280264475803542602324\n        y = Qmul30(a, y + 1548771675); // 1.44240602357494054356195495511150837674248533596658656579701261211118275506108037663799064380674298602249584492474438398\n        return y;\n    }\n\n    FP_INT Log2Poly4Lut4Table[] =\n    {\n        -262388804, 497357316, -773551400, 1549073482, 0,\n        -109627834, 364448809, -727169110, 1541348674, 512282,\n        -55606812, 259947350, -650393145, 1515947800, 3705096,\n        -30193295, 184276844, -565362946, 1473209058, 11812165,\n    };\n\n    // Precision: 17.47 bits\n    FP_INT Log2Poly4Lut4(FP_INT a)\n    {\n        FP_INT offset = (a >> 28) * 5;\n        FP_INT y = Qmul30(a, Log2Poly4Lut4Table[offset + 0]);\n        y = Qmul30(a, y + Log2Poly4Lut4Table[offset + 1]);\n        y = Qmul30(a, y + Log2Poly4Lut4Table[offset + 2]);\n        y = Qmul30(a, y + Log2Poly4Lut4Table[offset + 3]);\n        y = y + Log2Poly4Lut4Table[offset + 4];\n        return y;\n    }\n\n    FP_INT Log2Poly5Lut4Table[] =\n    {\n        188232988, -362436158, 514145569, -774469188, 1549081618, 0,\n        63930491, -229184904, 452495120, -759064000, 1547029186, 114449,\n        27404630, -141534019, 367122541, -716855295, 1536437358, 1193011,\n        12852334, -87700426, 286896922, -656644341, 1513678972, 4658365,\n    };\n\n    // Precision: 21.93 bits\n    FP_INT Log2Poly5Lut4(FP_INT a)\n    {\n        FP_INT offset = (a >> 28) * 6;\n        FP_INT y = Qmul30(a, Log2Poly5Lut4Table[offset + 0]);\n        y = Qmul30(a, y + Log2Poly5Lut4Table[offset + 1]);\n        y = Qmul30(a, y + Log2Poly5Lut4Table[offset + 2]);\n        y = Qmul30(a, y + Log2Poly5Lut4Table[offset + 3]);\n        y = Qmul30(a, y + Log2Poly5Lut4Table[offset + 4]);\n        y = y + Log2Poly5Lut4Table[offset + 5];\n        return y;\n    }\n\n    FP_INT Log2Poly3Lut8Table[] =\n    {\n        446326382, -771076074, 1549055308, 0,\n        310260104, -724673704, 1543514571, 233309,\n        229088935, -664989874, 1528754169, 1461470,\n        174118266, -603771378, 1505939900, 4306814,\n        135444733, -546112897, 1477222993, 9084839,\n        107410065, -493744566, 1444569702, 15881168,\n        86538496, -446871661, 1409446548, 24662718,\n        69761446, -402649011, 1370556774, 36072616,\n    };\n\n    // Precision: 15.82 bits\n    FP_INT Log2Poly3Lut8(FP_INT a)\n    {\n        FP_INT offset = (a >> 27) * 4;\n        FP_INT y = Qmul30(a, Log2Poly3Lut8Table[offset + 0]);\n        y = Qmul30(a, y + Log2Poly3Lut8Table[offset + 1]);\n        y = Qmul30(a, y + Log2Poly3Lut8Table[offset + 2]);\n        y = y + Log2Poly3Lut8Table[offset + 3];\n        return y;\n    }\n\n    FP_INT Log2Poly3Lut16Table[] =\n    {\n        479498023, -773622327, 1549078527, 0,\n        395931761, -759118188, 1548197526, 18808,\n        334661898, -736470659, 1545381846, 136568,\n        285596493, -709076642, 1540263722, 456574,\n        245720905, -679311878, 1532841693, 1074840,\n        212953734, -648695298, 1523292726, 2068966,\n        185770248, -618189987, 1511870714, 3495916,\n        163026328, -588395848, 1498851584, 5393582,\n        143849516, -559673988, 1484504546, 7783737,\n        127565758, -532227925, 1469077963, 10675243,\n        113648249, -506157040, 1452793288, 14067055,\n        101680803, -481491750, 1435843119, 17950929,\n        91330868, -458215848, 1418390572, 22314023,\n        82328154, -436276909, 1400565714, 27142441,\n        74439828, -415566448, 1382437636, 32432624,\n        67062062, -394757211, 1362869483, 38567491,\n    };\n\n    // Precision: 18.77 bits\n    FP_INT Log2Poly3Lut16(FP_INT a)\n    {\n        FP_INT offset = (a >> 26) * 4;\n        FP_INT y = Qmul30(a, Log2Poly3Lut16Table[offset + 0]);\n        y = Qmul30(a, y + Log2Poly3Lut16Table[offset + 1]);\n        y = Qmul30(a, y + Log2Poly3Lut16Table[offset + 2]);\n        y = y + Log2Poly3Lut16Table[offset + 3];\n        return y;\n    }\n\n    FP_INT Log2Poly4Lut16Table[] =\n    {\n        -349683705, 514860252, -774521507, 1549081965, 0,\n        -271658431, 496776802, -772844764, 1549008620, 1259,\n        -217158937, 469966332, -767835780, 1548587446, 14699,\n        -175799370, 439219304, -759216789, 1547507699, 65699,\n        -143866844, 407471403, -747343665, 1545528123, 189847,\n        -118877791, 376365258, -732794890, 1542497870, 426993,\n        -99090809, 346778829, -716182669, 1538346679, 816522,\n        -83256460, 319137771, -698070351, 1533066538, 1394329,\n        -70462839, 293601763, -678942086, 1526693477, 2191193,\n        -60034672, 270176585, -659197359, 1519292323, 3232171,\n        -51465396, 248781811, -639156567, 1510944906, 4536639,\n        -44370441, 229291517, -619070546, 1501741200, 6118756,\n        -38454405, 211558058, -599130091, 1491772420, 7988267,\n        -33487114, 195423423, -579471329, 1481123710, 10151959,\n        -29282549, 180709967, -560158338, 1469854024, 12618653,\n        -25515190, 166551747, -540200057, 1457346639, 15558687,\n    };\n\n    // Precision: 25.20 bits\n    FP_INT Log2Poly4Lut16(FP_INT a)\n    {\n        FP_INT offset = (a >> 26) * 5;\n        FP_INT y = Qmul30(a, Log2Poly4Lut16Table[offset + 0]);\n        y = Qmul30(a, y + Log2Poly4Lut16Table[offset + 1]);\n        y = Qmul30(a, y + Log2Poly4Lut16Table[offset + 2]);\n        y = Qmul30(a, y + Log2Poly4Lut16Table[offset + 3]);\n        y = y + Log2Poly4Lut16Table[offset + 4];\n        return y;\n    }\n\n    // Sin()\n\n    // Precision: 12.55 bits\n    FP_INT SinPoly2(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 78160664); // 0.072792791246675240806633584756838912025391316324690126147664432597740012658387971002826696503964998382073099859493224924\n        y = Qmul30(a, y + -691048553); // -0.643589118041571860037955276396590354123911419602492412009771153095258421228154501762591444328997849123819708031503216569\n        y = y + 1686629713; // 1.57079632679489661923132169163975144209852010327780228586210672049751840856976653075976474782503285074174660817200999164\n        return y;\n    }\n\n    // Precision: 19.56 bits\n    FP_INT SinPoly3(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, -4685819); // -0.00436400981703153243210864997931625819052350492882668525242722064533389220603470732171385204753335364507030843902034709469\n        y = Qmul30(a, y + 85358772); // 0.0794965509242783578799016950654626792792298788902324903830739535612665082075477776612291621671450318813032241372211405835\n        y = Qmul30(a, y + -693560840); // -0.645928867902143444679114736725897863187226477239208090992753453413451024571279601099280057944644528977979523870210785134\n        y = y + 1686629713; // 1.57079632679489661923132169163975144209852010327780228586210672049751840856976653075976474782503285074174660817200999164\n        return y;\n    }\n\n    // Precision: 27.13 bits\n    FP_INT SinPoly4(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 162679); // 0.000151506641710145430212560273580165931825591912723771559939880958777921352896251494433561036087921925941339032487946104446\n        y = Qmul30(a, y + -5018587); // -0.0046739239118693360423625115440933405485555388758012378155538229669555462190128366781129325889847935291248353457031014355\n        y = Qmul30(a, y + 85566362); // 0.0796898846149471415166275702814129714699583291426024010731469442497475447581642697337742897122044339073717901878121832219\n        y = Qmul30(a, y + -693598342); // -0.645963794139684570135799310650651238951748485457327220679639722739088328461814215309859665984340413045934902046607019536\n        y = y + 1686629713; // 1.57079632679489661923132169163975144209852010327780228586210672049751840856976653075976474782503285074174660817200999164\n        return y;\n    }\n\n    // Atan()\n\n    // Precision: 11.51 bits\n    FP_INT AtanPoly4(FP_INT a)\n    {\n        FP_INT y = Qmul30(a, 160726798); // 0.149688495302819745936382180128149414212975169816783327757105073455364913850052796368792673611118203908491930788482514717\n        y = Qmul30(a, y + -389730008); // -0.3629643552067315751294669187222720090413427534177140297655271624082990667114095804438257977266614399793827935382192301\n        y = Qmul30(a, y + -1791887); // -0.00166882600835556487643157555251563341464517039273944961175492629580374127544152356827950414733023151629754542508176777682\n        y = Qmul30(a, y + 1074109956); // 1.00034284930971570368517715996651394929230510383744660686391316332569199570835055730032133459840273458272542980313905982\n        return y;\n    }\n\n    FP_INT AtanPoly5Lut8Table[] =\n    {\n        204464916, 1544566, -357994250, 1395, 1073741820, 0,\n        119369854, 56362968, -372884915, 2107694, 1073588633, 4534,\n        10771151, 190921163, -440520632, 19339556, 1071365339, 120610,\n        -64491917, 329189978, -542756389, 57373179, 1064246365, 656900,\n        -89925028, 390367074, -601765924, 85907899, 1057328034, 1329793,\n        -80805750, 360696628, -563142238, 60762238, 1065515580, 263159,\n        -58345538, 276259197, -435975641, -35140679, 1101731779, -5215389,\n        -36116738, 179244146, -266417331, -183483381, 1166696761, -16608596,\n        0, 0, 0, 0, 0, 843314857 // Atan(1.0)\n\t};\n\n    // Precision: 28.06 bits\n    FP_INT AtanPoly5Lut8(FP_INT a)\n    {\n        FP_INT offset = (a >> 27) * 6;\n        FP_INT y = Qmul30(a, AtanPoly5Lut8Table[offset + 0]);\n        y = Qmul30(a, y + AtanPoly5Lut8Table[offset + 1]);\n        y = Qmul30(a, y + AtanPoly5Lut8Table[offset + 2]);\n        y = Qmul30(a, y + AtanPoly5Lut8Table[offset + 3]);\n        y = Qmul30(a, y + AtanPoly5Lut8Table[offset + 4]);\n        y = y + AtanPoly5Lut8Table[offset + 5];\n        return y;\n    }\n\n    FP_INT AtanPoly3Lut8Table[] =\n    {\n        -351150132, -463916, 1073745980, 0,\n        -289359685, -24349242, 1076929105, -145366,\n        -192305259, -97257464, 1095342438, -1708411,\n        -91138684, -210466171, 1137733496, -7020039,\n        -8856969, -332956892, 1198647251, -17139451,\n        46187514, -435267135, 1262120294, -30283758,\n        76277334, -502284461, 1311919661, -42630181,\n        88081006, -532824470, 1338273149, -50214826,\n        0, 0, 0, 843314857 // Atan(1.0)\n    };\n\n    // Precision: 17.98 bits\n    FP_INT AtanPoly3Lut8(FP_INT a)\n    {\n        FP_INT offset = (a >> 27) * 4;\n        FP_INT y = Qmul30(a, AtanPoly3Lut8Table[offset + 0]);\n        y = Qmul30(a, y + AtanPoly3Lut8Table[offset + 1]);\n        y = Qmul30(a, y + AtanPoly3Lut8Table[offset + 2]);\n        y = y + AtanPoly3Lut8Table[offset + 3];\n        return y;\n    }\n\n\n\n\n    #undef FP_ASSERT\n}\n#endif // __FIXEDUTIL_H\n"
  },
  {
    "path": "lib/Graphics/PGEString.h",
    "content": "/*\n * Used for compatibiltiy of common code with both QString and std::string\n */\n#ifndef PGEString_HHH\n#define PGEString_HHH\n#pragma once\n\n#ifdef PGE_EDITOR\n#include <QString>\n#include <QStringList>\n#include <QList>\ntypedef QString     PGEString;\ntypedef QStringList PGEStringList;\n#define PGEList     QList\n#define StdToPGEString(str)     QString::fromStdString(str)\n#define PGEStringToStd(str)     (str).toStdString()\n#define PGEStringLit(str)       QStringLiteral(str)\ntypedef int pge_size_t;\n#else\n#include <string>\n#include <vector>\ntypedef std::string                 PGEString;\ntypedef std::vector<std::string>    PGEStringList;\n#define PGEList                     std::vector\n#define StdToPGEString(str)         (str)\n#define PGEStringToStd(str)         (str)\n#define PGEStringLit(str)\ntypedef size_t pge_size_t;\n#endif\n\ntemplate <class T>\ninline const T &pgeConstReference(const T &t)\n{\n    return t;\n}\n\n#endif /*PGEString_HHH*/\n"
  },
  {
    "path": "lib/Graphics/bitmask2rgba.c",
    "content": "/*\n * Bitmask to RGBA converter: allows to convert graphics with a front (image with\n * a black background) and mask (a dark shape of image on a white background)\n * into RGBA with a transparent background\n *\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include <FreeImageLite.h>\n\n#include \"bitmask2rgba.h\"\n\n\nvoid bitmask_to_rgba(FIBITMAP *front, FIBITMAP *mask)\n{\n    unsigned int x, y, ym, img_w, img_h, mask_w, mask_h, img_pitch, mask_pitch;\n\n    BYTE *img_bits, *mask_bits, *FPixP, *SPixP;\n    RGBQUAD Npix = {0x00, 0x00, 0x00, 0xFF};   /* Destination pixel color */\n    BYTE Wpix[] = {0xFF, 0xFF, 0xFF, 0xFF};   /* Dummy white pixel */\n    unsigned short newAlpha = 0xFF; /* Calculated destination alpha-value*/\n\n    BOOL endOfY = FALSE;\n\n    if(!mask)\n        return; /* Nothing to do */\n\n    img_w  = FreeImage_GetWidth(front);\n    img_h  = FreeImage_GetHeight(front);\n    img_pitch = FreeImage_GetPitch(front);\n    mask_w = FreeImage_GetWidth(mask);\n    mask_h = FreeImage_GetHeight(mask);\n    mask_pitch = FreeImage_GetPitch(mask);\n\n    img_bits  = FreeImage_GetBits(front);\n    mask_bits = FreeImage_GetBits(mask);\n    FPixP = img_bits;\n    SPixP = mask_bits;\n\n    ym = mask_h - 1;\n    y = img_h - 1;\n\n    while(1)\n    {\n        FPixP = img_bits + (img_pitch * y);\n        if(!endOfY)\n            SPixP = mask_bits + (mask_pitch * ym);\n\n        for(x = 0; (x < img_w); x++)\n        {\n            Npix.rgbBlue = ((SPixP[FI_RGBA_BLUE] & 0x7F) | FPixP[FI_RGBA_BLUE]);\n            Npix.rgbGreen = ((SPixP[FI_RGBA_GREEN] & 0x7F) | FPixP[FI_RGBA_GREEN]);\n            Npix.rgbRed = ((SPixP[FI_RGBA_RED] & 0x7F) | FPixP[FI_RGBA_RED]);\n            newAlpha = 255 - (((unsigned short)(SPixP[FI_RGBA_RED]) +\n                               (unsigned short)(SPixP[FI_RGBA_GREEN]) +\n                               (unsigned short)(SPixP[FI_RGBA_BLUE])) / 3);\n\n            if((SPixP[FI_RGBA_RED] > 240u) //is almost White\n               && (SPixP[FI_RGBA_GREEN] > 240u)\n               && (SPixP[FI_RGBA_BLUE] > 240u))\n                newAlpha = 0;\n\n            newAlpha += (((unsigned short)(FPixP[FI_RGBA_RED]) +\n                          (unsigned short)(FPixP[FI_RGBA_GREEN]) +\n                          (unsigned short)(FPixP[FI_RGBA_BLUE])) / 3);\n\n            if(newAlpha > 255)\n                newAlpha = 255;\n\n            FPixP[FI_RGBA_BLUE]  = Npix.rgbBlue;\n            FPixP[FI_RGBA_GREEN] = Npix.rgbGreen;\n            FPixP[FI_RGBA_RED]   = Npix.rgbRed;\n            FPixP[FI_RGBA_ALPHA] = (BYTE)(newAlpha);\n            FPixP += 4;\n\n            if(x >= mask_w - 1 || endOfY)\n                SPixP = Wpix;\n            else\n                SPixP += 4;\n        }\n\n        if(ym == 0)\n        {\n            endOfY = TRUE;\n            SPixP = Wpix;\n        }\n        else\n            ym--;\n\n        if(y == 0)\n            break;\n        y--;\n    }\n}\n\nvoid bitmask_get_mask_from_rgba(FIBITMAP *image, FIBITMAP **outmask)\n{\n    unsigned int img_w, img_h, x, y;\n    RGBQUAD Fpix;\n    RGBQUAD Npix = {0x0, 0x0, 0x0, 0xFF};\n    BYTE gray;\n\n    if(!image)\n        *outmask = NULL;\n\n    img_w = FreeImage_GetWidth(image);\n    img_h = FreeImage_GetHeight(image);\n\n    *outmask = FreeImage_AllocateT(FIT_BITMAP,\n                                   (int)(img_w), (int)(img_h),\n                                   (int)(FreeImage_GetBPP(image)),\n                                   FreeImage_GetRedMask(image),\n                                   FreeImage_GetGreenMask(image),\n                                   FreeImage_GetBlueMask(image));\n\n    for(y = 0; (y < img_h); y++)\n    {\n        for(x = 0; (x < img_w); x++)\n        {\n            FreeImage_GetPixelColor(image, x, y, &Fpix);\n\n            gray = (255 - Fpix.rgbReserved);\n            Npix.rgbRed  = gray;\n            Npix.rgbGreen = gray;\n            Npix.rgbBlue = gray;\n            Npix.rgbReserved = 0xFF;\n            FreeImage_SetPixelColor(*outmask,  x, y, &Npix);\n        }\n    }\n}\n"
  },
  {
    "path": "lib/Graphics/bitmask2rgba.cmake",
    "content": "# message(\"Path to UTF8-Main is [${CMAKE_CURRENT_LIST_DIR}]\")\ninclude_directories(${CMAKE_CURRENT_LIST_DIR}/)\n\nset(BITMASK2RGBA_SRCS ${CMAKE_CURRENT_LIST_DIR}/bitmask2rgba.c)\n\n"
  },
  {
    "path": "lib/Graphics/bitmask2rgba.h",
    "content": "/*\n * Bitmask to RGBA converter: allows to convert graphics with a front (image with\n * a black background) and mask (a dark shape of image on a white background)\n * into RGBA with a transparent background\n *\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct FIBITMAP FIBITMAP;\n\n/**\n * @brief Merge front and mask image into united RGBA image\n * @param [InOut] front\n * @param [In] mask\n */\nextern void bitmask_to_rgba(FIBITMAP *front, FIBITMAP *mask);\n\n/**\n * @brief Extract mask from RGBA image\n * @param [In] image RGBA image\n * @param [Out] outmask Output mask image\n */\nextern void bitmask_get_mask_from_rgba(FIBITMAP *image, FIBITMAP **outmask);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "lib/Graphics/graphics_funcs.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <array>\n\n#include <SDL2/SDL_video.h>\n#ifndef SDL_SDL_ASSERT_H\n#   include \"sdl_proxy/sdl_assert.h\"\n#endif\n\n#include <Utils/files.h>\n#ifdef THEXTECH_FILEMAPPER_SUPPORTED\n#   include <FileMapper/file_mapper.h>\n#endif\n\n#include \"graphics_funcs.h\"\n#include <Logger/logger.h>\n\n#ifdef DEBUG_BUILD\n#   include <Utils/elapsed_timer.h>\n#endif\n\n#include \"image_size.h\"\n\n//#include <common_features/engine_resources.h>\n\n#ifdef _WIN32\n#   include <SDL2/SDL_syswm.h>\n#endif\n#include <FreeImageLite.h>\n\nstatic unsigned DLL_CALLCONV\n_RWopsReadProc(void *buffer, unsigned size, unsigned count, fi_handle handle) {\n    return (unsigned)SDL_RWread ((SDL_RWops *)handle, buffer, size, count);\n}\n\nstatic unsigned DLL_CALLCONV\n_RWopsWriteProc(void *buffer, unsigned size, unsigned count, fi_handle handle) {\n    return (unsigned)SDL_RWwrite((SDL_RWops *)handle, buffer, size, count);\n}\n\nstatic int DLL_CALLCONV\n_RWopsSeekProc(fi_handle handle, long offset, int origin) {\n    int use_origin = origin;\n\n    if(origin == SEEK_CUR)\n        use_origin = RW_SEEK_CUR;\n    else if(origin == SEEK_SET)\n        use_origin = RW_SEEK_SET;\n    else if(origin == SEEK_END)\n        use_origin = RW_SEEK_END;\n\n    return SDL_RWseek((SDL_RWops *)handle, offset, use_origin);\n}\n\nstatic long DLL_CALLCONV\n_RWopsTellProc(fi_handle handle) {\n    return SDL_RWseek((SDL_RWops *)handle, 0, RW_SEEK_CUR);\n}\n\n// strangely undocumented import necessary to use the FreeImage handle functions\nextern void SetDefaultIO(FreeImageIO *io);\n\nvoid GraphicsHelps::initFreeImage()\n{\n    FreeImage_Initialise();\n}\n\nvoid GraphicsHelps::closeFreeImage()\n{\n    FreeImage_DeInitialise();\n}\n\nvoid GraphicsHelps::SetRWopsIO(FreeImageIO *io) {\n    io->read_proc  = _RWopsReadProc;\n    io->seek_proc  = _RWopsSeekProc;\n    io->tell_proc  = _RWopsTellProc;\n    io->write_proc = _RWopsWriteProc;\n}\n\nFIBITMAP *GraphicsHelps::loadImage(const std::string &file, bool convertTo32bit)\n{\n#ifdef DEBUG_BUILD\n    ElapsedTimer loadingTime;\n    ElapsedTimer fReadTime;\n    ElapsedTimer imgConvTime;\n    loadingTime.start();\n    fReadTime.start();\n#endif\n\n    // disabled filemapper because it does not support archive contents\n#if 0 // #if defined(THEXTECH_FILEMAPPER_SUPPORTED)\n    FileMapper fileMap;\n\n    if(!fileMap.open_file(file))\n        return nullptr;\n\n    FIMEMORY *imgMEM = FreeImage_OpenMemory(reinterpret_cast<unsigned char *>(fileMap.data()),\n                                            static_cast<unsigned int>(fileMap.size()));\n    FREE_IMAGE_FORMAT formato = FreeImage_GetFileTypeFromMemory(imgMEM);\n\n    if(formato  == FIF_UNKNOWN)\n        return nullptr;\n\n    FIBITMAP *img = FreeImage_LoadFromMemory(formato, imgMEM, 0);\n    FreeImage_CloseMemory(imgMEM);\n    fileMap.close_file();\n\n    if(!img)\n        return nullptr;\n\n#else\n    FreeImageIO io;\n    SetRWopsIO(&io);\n    SDL_RWops *handle = Files::open_file(file, \"rb\");\n\n    FREE_IMAGE_FORMAT formato = FreeImage_GetFileTypeFromHandle(&io, (fi_handle)handle);\n\n    if(formato == FIF_UNKNOWN)\n    {\n        SDL_RWclose(handle);\n        return NULL;\n    }\n\n    FIBITMAP *img = FreeImage_LoadFromHandle(formato, &io, (fi_handle)handle);\n\n    SDL_RWclose(handle);\n\n    if(!img)\n        return NULL;\n\n#endif\n#ifdef DEBUG_BUILD\n    long long fReadTimeElapsed = static_cast<long long>(fReadTime.nanoelapsed());\n    long long imgConvertElapsed = 0;\n#endif\n\n    if(convertTo32bit && (FreeImage_GetBPP(img) != 32))\n    {\n#ifdef DEBUG_BUILD\n        imgConvTime.start();\n#endif\n        FIBITMAP *temp = nullptr;\n\n#ifdef THEXTECH_WIP_FEATURES\n        temp = fastConvertTo32Bit(img);\n#endif\n\n        if(!temp)\n            temp = FreeImage_ConvertTo32Bits(img);\n\n        FreeImage_Unload(img);\n\n        if(!temp)\n            return nullptr;\n\n        img = temp;\n#ifdef DEBUG_BUILD\n        imgConvertElapsed = static_cast<long long>(imgConvTime.nanoelapsed());\n#endif\n    }\n\n#ifdef DEBUG_BUILD\n    D_pLogDebug(\"File read of texture %s passed in %d nanoseconds\", file.c_str(), static_cast<int>(fReadTimeElapsed));\n    D_pLogDebug(\"Conv to 32-bit of %s passed in %d nanoseconds\", file.c_str(), static_cast<int>(imgConvertElapsed));\n    D_pLogDebug(\"Total Loading of image %s passed in %d nanoseconds\", file.c_str(), static_cast<int>(loadingTime.nanoelapsed()));\n#endif\n    return img;\n}\n\nFIBITMAP *GraphicsHelps::loadImage(const Files::Data &raw, bool convertTo32bit)\n{\n    FIMEMORY *imgMEM = FreeImage_OpenMemory(const_cast<unsigned char*>(raw.begin()),\n                                            static_cast<unsigned int>(raw.size()));\n    FREE_IMAGE_FORMAT formato = FreeImage_GetFileTypeFromMemory(imgMEM);\n\n    if(formato  == FIF_UNKNOWN)\n        return nullptr;\n\n    FIBITMAP *img = FreeImage_LoadFromMemory(formato, imgMEM, 0);\n    FreeImage_CloseMemory(imgMEM);\n\n    if(!img)\n        return nullptr;\n\n    if(convertTo32bit && (FreeImage_GetBPP(img) != 32))\n    {\n        FIBITMAP *temp = nullptr;\n\n#ifdef THEXTECH_WIP_FEATURES\n        temp = fastConvertTo32Bit(img);\n#endif\n\n        if(!temp)\n            temp = FreeImage_ConvertTo32Bits(img);\n\n        FreeImage_Unload(img);\n\n        if(!temp)\n            return nullptr;\n        img = temp;\n    }\n\n    return img;\n}\n\nFIBITMAP *GraphicsHelps::loadMask(const std::string &file, bool maskIsPng, bool convertTo32bit)\n{\n    FIBITMAP *mask;\n\n    if(file.empty())\n        return nullptr; //Nothing to do\n    mask = loadImage(file, convertTo32bit);\n\n    if(!mask)\n        return nullptr;//Nothing to do\n\n    // this is the main reason that we have a separate call: extract a bitmask from a PNG RGBA image\n    if(maskIsPng)\n        RGBAToMask(mask);\n\n    return mask;\n}\n\nFIBITMAP *GraphicsHelps::loadMask(const Files::Data &raw, bool maskIsPng, bool convertTo32bit)\n{\n    FIBITMAP *mask;\n\n    if(raw.empty())\n        return nullptr; //Nothing to do\n    mask = loadImage(raw, convertTo32bit);\n\n    if(!mask)\n        return nullptr;//Nothing to do\n\n    // this is the main reason that we have a separate call: extract a bitmask from a PNG RGBA image\n    if(maskIsPng)\n        RGBAToMask(mask);\n\n    return mask;\n}\n\n//FIBITMAP *GraphicsHelps::loadImageRC(const char *file)\n//{\n//    unsigned char *memory = nullptr;\n//    size_t fileSize = 0;\n//    SDL_assert_release(RES_getMem(file, memory, fileSize));\n//    //{\n//        //pLogCritical(\"Resource file \\\"%s\\\" is not found!\", file);\n//        //return nullptr;\n//    //}\n//\n//    FIMEMORY *imgMEM = FreeImage_OpenMemory(memory, static_cast<FI_DWORD>(fileSize));\n//    FREE_IMAGE_FORMAT formato = FreeImage_GetFileTypeFromMemory(imgMEM);\n//\n//    if(formato == FIF_UNKNOWN)\n//        return nullptr;\n//\n//    FIBITMAP *img = FreeImage_LoadFromMemory(formato, imgMEM, 0);\n//    FreeImage_CloseMemory(imgMEM);\n//\n//    if(!img)\n//        return nullptr;\n//\n//    FIBITMAP *temp;\n//    temp = FreeImage_ConvertTo32Bits(img);\n//\n//    if(!temp)\n//        return nullptr;\n//\n//    FreeImage_Unload(img);\n//    img = temp;\n//    return img;\n//}\n\nvoid GraphicsHelps::closeImage(FIBITMAP *img)\n{\n    FreeImage_Unload(img);\n}\n\nvoid GraphicsHelps::RGBAToMask(FIBITMAP *mask)\n{\n    if(!mask)\n        return;\n\n    // convert alpha channel to luminance\n    unsigned int img_w  = FreeImage_GetWidth(mask);\n    unsigned int img_h  = FreeImage_GetHeight(mask);\n\n    uint8_t *pixel_data = reinterpret_cast<uint8_t*>(FreeImage_GetBits(mask));\n    auto dest_px_stride = static_cast<uint32_t>(FreeImage_GetPitch(mask));\n\n    for(unsigned int y = 0; (y < img_h); y++)\n    {\n        for(unsigned int x = 0; (x < img_w); x++)\n        {\n            uint8_t* pixel = &pixel_data[dest_px_stride * y + 4 * x];\n\n            uint8_t grey = 0xFF - pixel[3];\n            pixel[0] = grey;\n            pixel[1] = grey;\n            pixel[2] = grey;\n            pixel[3] = 0xFF;\n        }\n    }\n}\n\nSDL_Surface *GraphicsHelps::fi2sdl(FIBITMAP *img)\n{\n    int h = static_cast<int>(FreeImage_GetHeight(img));\n    int w = static_cast<int>(FreeImage_GetWidth(img));\n    FreeImage_FlipVertical(img);\n    SDL_Surface *surf = SDL_CreateRGBSurfaceFrom(FreeImage_GetBits(img),\n                        w, h, 32, w * 4, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, FI_RGBA_ALPHA_MASK);\n    return surf;\n}\n\nvoid GraphicsHelps::mergeWithMask(FIBITMAP *image,\n                                  const std::string &pathToMask,\n                                  const std::string &pathToMaskFallback)\n{\n    if(!image)\n        return;\n\n    if(!Files::fileExists(pathToMask) && pathToMaskFallback.empty())\n        return; //Nothing to do\n\n    FIBITMAP *mask = pathToMask.empty() ? nullptr : loadImage(pathToMask, true);\n    if(!mask && !pathToMaskFallback.empty())\n    {\n        mask = loadImage(pathToMaskFallback, true);\n        RGBAToMask(mask);\n    }\n\n    if(!mask)\n        return;//Nothing to do\n\n    mergeWithMask(image, mask);\n\n    FreeImage_Unload(mask);\n}\n\nvoid GraphicsHelps::mergeWithMask(FIBITMAP *image, const Files::Data &maskRaw, bool maskIsPng)\n{\n    if(!image)\n        return;\n\n    if(maskRaw.empty())\n        return; //Nothing to do\n\n    FIBITMAP *mask = loadImage(maskRaw, true);\n\n    if(!mask)\n        return;//Nothing to do\n\n    if(maskIsPng)\n        RGBAToMask(mask);\n\n    mergeWithMask(image, mask);\n\n    FreeImage_Unload(mask);\n}\n\n\nstatic RGBQUAD s_bitblitBG = {0, 0, 0, 0xFF};\n\nvoid GraphicsHelps::mergeWithMask(FIBITMAP *image, FIBITMAP *mask)\n{\n    unsigned int img_w = FreeImage_GetWidth(image);\n    unsigned int img_h = FreeImage_GetHeight(image);\n    unsigned int img_pitch = FreeImage_GetPitch(image);\n    unsigned int mask_w = FreeImage_GetWidth(mask);\n    unsigned int mask_h = FreeImage_GetHeight(mask);\n    unsigned int mask_pitch = FreeImage_GetPitch(mask);\n    BYTE *img_bits  = FreeImage_GetBits(image);\n    BYTE *mask_bits = FreeImage_GetBits(mask);\n    BYTE *FPixP = nullptr;\n    BYTE *SPixP = mask_bits;\n    RGBQUAD Npix = {0x00, 0x00, 0x00, 0xFF};   //Destination pixel color\n    BYTE Wpix[] = {0xFF, 0xFF, 0xFF, 0xFF};   //Dummy white pixel\n    unsigned short newAlpha = 0xFF; //Calculated destination alpha-value\n\n    bool endOfY = false;\n    unsigned int ym = mask_h - 1;\n    unsigned int y = img_h - 1;\n\n    while(1)\n    {\n        FPixP = img_bits + (img_pitch * y);\n        if(!endOfY)\n            SPixP = mask_bits + (mask_pitch * ym);\n\n        for(unsigned int x = 0; (x < img_w); x++)\n        {\n            Npix.rgbBlue = ((SPixP[FI_RGBA_BLUE] & s_bitblitBG.rgbBlue) | FPixP[FI_RGBA_BLUE]);\n            Npix.rgbGreen = ((SPixP[FI_RGBA_GREEN] & s_bitblitBG.rgbGreen) | FPixP[FI_RGBA_GREEN]);\n            Npix.rgbRed = ((SPixP[FI_RGBA_RED] & s_bitblitBG.rgbRed) | FPixP[FI_RGBA_RED]);\n\n            // these terms (mask pixel and not front pixel, bitwise) represent the values that could be seen in SMBX 1.3\n            newAlpha = 255 - ((static_cast<unsigned short>(SPixP[FI_RGBA_RED] & ~FPixP[FI_RGBA_RED]) +\n                               static_cast<unsigned short>(SPixP[FI_RGBA_GREEN] & ~FPixP[FI_RGBA_GREEN]) +\n                               static_cast<unsigned short>(SPixP[FI_RGBA_BLUE] & ~FPixP[FI_RGBA_BLUE])) / 3);\n\n            if(newAlpha < 16)\n                newAlpha = 0;\n\n            if(newAlpha > 240)\n                newAlpha = 255;\n\n            FPixP[FI_RGBA_BLUE]  = Npix.rgbBlue;\n            FPixP[FI_RGBA_GREEN] = Npix.rgbGreen;\n            FPixP[FI_RGBA_RED]   = Npix.rgbRed;\n            FPixP[FI_RGBA_ALPHA] = static_cast<BYTE>(newAlpha);\n            FPixP += 4;\n\n            if(x >= mask_w - 1 || endOfY)\n                SPixP = Wpix;\n            else\n                SPixP += 4;\n        }\n\n        if(ym == 0)\n        {\n            endOfY = true;\n            SPixP = Wpix;\n        }\n        else\n            ym--;\n\n        if(y == 0)\n            break;\n        y--;\n    }\n}\n\nvoid GraphicsHelps::setBitBlitBG(uint8_t red, uint8_t green, uint8_t blue)\n{\n    s_bitblitBG.rgbRed = red;\n    s_bitblitBG.rgbGreen = green;\n    s_bitblitBG.rgbBlue = blue;\n}\n\nvoid GraphicsHelps::resetBitBlitBG()\n{\n    s_bitblitBG.rgbRed = 0;\n    s_bitblitBG.rgbGreen = 0;\n    s_bitblitBG.rgbBlue = 0;\n}\n\nvoid GraphicsHelps::replaceColor(FIBITMAP* image, const PGE_Pix& src, const PGE_Pix& dst)\n{\n    if(!image)\n        return;\n\n    unsigned int img_w = FreeImage_GetWidth(image);\n    unsigned int img_h = FreeImage_GetHeight(image);\n    unsigned int img_pitch = FreeImage_GetPitch(image);\n\n    BYTE *img_bits  = FreeImage_GetBits(image);\n    BYTE *FPixP = nullptr;\n\n    for(unsigned int y = 0; y < img_h; ++y)\n    {\n        FPixP = img_bits + (img_pitch * y);\n\n        for(unsigned int x = 0; (x < img_w); ++x)\n        {\n            if(FPixP[FI_RGBA_GREEN] == src.g &&\n               FPixP[FI_RGBA_BLUE] == src.b &&\n               FPixP[FI_RGBA_RED] == src.a &&\n               FPixP[FI_RGBA_ALPHA] == src.a)\n            {\n                FPixP[FI_RGBA_RED]   = dst.r;\n                FPixP[FI_RGBA_GREEN] = dst.g;\n                FPixP[FI_RGBA_BLUE]  = dst.b;\n                FPixP[FI_RGBA_ALPHA] = dst.a;\n            }\n            FPixP += 4;\n        }\n    }\n}\n\nbool GraphicsHelps::getImageMetrics(const std::string &imageFile, PGE_Size* imgSize)\n{\n    if(!imgSize)\n        return false;\n\n    int errorCode;\n    uint32_t w, h;\n\n    if(!PGE_ImageInfo::getImageSize(imageFile, &w, &h, &errorCode))\n        return false;\n\n    imgSize->setSize(int(w), int(h));\n    return true;\n}\n\nvoid GraphicsHelps::getMaskedImageInfo(const std::string &rootDir,\n                                       const std::string &in_imgName,\n                                       std::string& out_maskName,\n                                       std::string& out_errStr,\n                                       PGE_Size* imgSize)\n{\n    if(in_imgName.empty())\n    {\n        out_errStr = \"Image filename isn't defined\";\n        return;\n    }\n\n    int errorCode;\n    uint32_t w, h;\n\n    if(!PGE_ImageInfo::getImageSize(rootDir + in_imgName, &w, &h, &errorCode))\n    {\n        switch(errorCode)\n        {\n        default:\n        case PGE_ImageInfo::ERR_UNKNOWN:\n            out_errStr = \"Unknown error has occurred: \" + rootDir + in_imgName;\n            break;\n\n        case PGE_ImageInfo::ERR_UNSUPPORTED_FILETYPE:\n            out_errStr = \"Unsupported or corrupted file format: \" + rootDir + in_imgName;\n            break;\n\n        case PGE_ImageInfo::ERR_NOT_EXISTS:\n            out_errStr = \"image file is not exist: \" + rootDir + in_imgName;\n            break;\n\n        case PGE_ImageInfo::ERR_CANT_OPEN:\n            out_errStr = \"Can't open image file: \" + rootDir + in_imgName;\n            break;\n        }\n\n        return;\n    }\n\n    out_maskName = PGE_ImageInfo::getMaskName(in_imgName);\n    out_errStr = \"\";\n\n    if(imgSize)\n    {\n        imgSize->setWidth(int(w));\n        imgSize->setHeight(int(h));\n    }\n}\n\nbool GraphicsHelps::validateFor2xScaleDown(FIBITMAP *image, const std::string &origPath)\n{\n    if(!image)\n        return false;\n\n    (void)origPath; // supress warning when build the release build\n\n    SDL_assert_release(FreeImage_GetBPP(image) == 32);\n    if(FreeImage_GetBPP(image) != 32)\n        return false;\n\n    auto w = static_cast<uint32_t>(FreeImage_GetWidth(image));\n    auto h = static_cast<uint32_t>(FreeImage_GetHeight(image));\n    const uint32_t *img_pixels = reinterpret_cast<uint32_t*>(FreeImage_GetBits(image));\n    auto pitch_px = static_cast<uint32_t>(FreeImage_GetPitch(image)) / 4;\n\n    if(w % 2 || h % 2)\n    {\n        D_pLogWarning(\"Texture can't be shrank, non-multiple size: %\" PRIu32 \" x %\" PRIu32 \" (%s)\", w, h, origPath.c_str());\n        return false; // Not multiple two!\n    }\n\n    for(uint32_t y = 0; y < h; y += 2)\n    {\n        for(uint32_t x = 0; x < w; x += 2)\n        {\n            if(img_pixels[y * pitch_px + x] != img_pixels[y * pitch_px + (x + 1)]\n                || img_pixels[y * pitch_px + x] != img_pixels[(y + 1) * pitch_px + x]\n                || img_pixels[y * pitch_px + x] != img_pixels[(y + 1) * pitch_px + (x + 1)])\n            {\n                D_pLogWarning(\"Texture can't be shrank: Pixel colors at the %\" PRIu32 \" x %\" PRIu32 \" sector (2x2 square) aren't equal (%s)\", x, y, origPath.c_str());\n                return false;\n            }\n        }\n    }\n\n    D_pLogDebug(\"Texture CAN be shrank (%s)\", origPath.c_str());\n    return true;\n}\n\nFIBITMAP *GraphicsHelps::fast2xScaleDown(FIBITMAP *image)\n{\n    if(!image)\n        return nullptr;\n\n    if(FreeImage_GetBPP(image) != 32)\n        return nullptr;\n\n    auto src_w = static_cast<uint32_t>(FreeImage_GetWidth(image));\n    auto src_h = static_cast<uint32_t>(FreeImage_GetHeight(image));\n    const uint32_t *src_pixels  = reinterpret_cast<uint32_t*>(FreeImage_GetBits(image));\n    auto src_pitch_px = static_cast<uint32_t>(FreeImage_GetPitch(image)) / 4;\n\n    auto dest_w = src_w / 2;\n    auto dest_h = src_h / 2;\n\n    FIBITMAP *dest = FreeImage_Allocate(dest_w, dest_h, 32);\n\n    if(!dest)\n        return nullptr;\n\n    uint32_t *dest_pixels  = reinterpret_cast<uint32_t*>(FreeImage_GetBits(dest));\n    auto dest_pitch_px = static_cast<uint32_t>(FreeImage_GetPitch(dest)) / 4;\n\n    for(uint32_t src_y = 0, dest_y = 0; dest_y < dest_h; src_y += 2, dest_y += 1)\n    {\n        for(uint32_t src_x = 0, dest_x = 0; dest_x < dest_w; src_x += 2, dest_x += 1)\n        {\n            dest_pixels[dest_y * dest_pitch_px + dest_x] = src_pixels[src_y * src_pitch_px + src_x];\n        }\n    }\n\n    return dest;\n}\n\nFIBITMAP *GraphicsHelps::fastConvertTo32Bit(FIBITMAP *image)\n{\n    // two kilobytes of stack isn't affordable? make these static, then.\n\n    // palette for high nybble (ignores low nybble)\n    std::array<uint32_t, 256> palette_high;\n\n    // palette for low nybble (ignores high nybble)\n    std::array<uint32_t, 256> palette_low;\n\n\n    if(!image)\n        return nullptr;\n\n    if(FreeImage_GetBPP(image) != 1 && FreeImage_GetBPP(image) != 4 && FreeImage_GetBPP(image) != 8)\n        return nullptr;\n\n    auto src_w = static_cast<uint32_t>(FreeImage_GetWidth(image));\n    auto src_h = static_cast<uint32_t>(FreeImage_GetHeight(image));\n    const uint8_t *src_pixels  = reinterpret_cast<uint8_t*>(FreeImage_GetBits(image));\n    const uint32_t *src_palette = reinterpret_cast<uint32_t*>(FreeImage_GetPalette(image));\n    const uint8_t *src_trans = reinterpret_cast<uint8_t*>(FreeImage_GetTransparencyTable(image));\n    auto src_pitch = static_cast<uint32_t>(FreeImage_GetPitch(image));\n\n    FIBITMAP *dest = FreeImage_Allocate(src_w, src_h, 32);\n\n    if(!dest)\n        return nullptr;\n\n    uint32_t *dest_pixels  = reinterpret_cast<uint32_t*>(FreeImage_GetBits(dest));\n    auto dest_pitch_px = static_cast<uint32_t>(FreeImage_GetPitch(dest)) / 4;\n\n    // special logic for 1 BPP\n    if(FreeImage_GetBPP(image) == 1)\n    {\n        // fill first two entries\n        for(int i = 0; i < 2; i++)\n        {\n            palette_high[i] = src_palette[i];\n\n            if(src_trans)\n                ((uint8_t*)&palette_high[i])[3] = src_trans[i];\n            else\n                ((uint8_t*)&palette_high[i])[3] = 255;\n        }\n\n        // perform lookups\n        for(uint32_t y = 0; y < src_h; y++)\n        {\n            for(uint32_t x = 0; x < src_w; x++)\n            {\n                uint8_t which_bit = 128 >> (x % 8);\n                bool lit = src_pixels[y * src_pitch + x / 8] & which_bit;\n                dest_pixels[y * dest_pitch_px + x] = palette_high[lit];\n            }\n        }\n\n        return dest;\n    }\n\n    if(FreeImage_GetBPP(image) == 8)\n    {\n        for(int i = 0; i < 256; i++)\n        {\n            palette_high[i] = src_palette[i];\n\n            if(src_trans)\n                ((uint8_t*)&palette_high[i])[3] = src_trans[i];\n            else\n                ((uint8_t*)&palette_high[i])[3] = 255;\n        }\n\n        for(uint32_t y = 0; y < src_h; y++)\n        {\n            for(uint32_t x = 0; x < src_w; x++)\n            {\n                dest_pixels[y * dest_pitch_px + x] = palette_high[src_pixels[y * src_pitch + x]];\n            }\n        }\n    }\n    else\n    {\n        // fill low and high nybble palettes\n        for(int i = 0; i < 16; i++)\n        {\n            for(int j = 0; j < 16; j++)\n            {\n                palette_high[i * 16 + j] = src_palette[i];\n                if(src_trans)\n                    ((uint8_t*)&palette_high[i * 16 + j])[3] = src_trans[i];\n                else\n                    ((uint8_t*)&palette_high[i * 16 + j])[3] = 255;\n\n                palette_low[i * 16 + j] = src_palette[j];\n                if(src_trans)\n                    ((uint8_t*)&palette_low[i * 16 + j])[3] = src_trans[j];\n                else\n                    ((uint8_t*)&palette_low[i * 16 + j])[3] = 255;\n            }\n        }\n\n        for(uint32_t y = 0; y < src_h; y++)\n        {\n            for(uint32_t x = 0; x < src_w - 1; x += 2)\n            {\n                dest_pixels[y * dest_pitch_px + x] = palette_high[src_pixels[y * src_pitch + (x / 2)]];\n                dest_pixels[y * dest_pitch_px + x + 1] = palette_low[src_pixels[y * src_pitch + (x / 2)]];\n            }\n\n            if(src_w % 2)\n                dest_pixels[y * dest_pitch_px + src_w - 1] = palette_high[src_pixels[y * src_pitch + (src_w / 2)]];\n        }\n    }\n\n    return dest;\n}\n\nbool GraphicsHelps::validateBitmaskRequired(FIBITMAP *image, FIBITMAP *mask, const std::string &origPath)\n{\n    if(!image || !mask)\n        return false;\n\n    (void)origPath; // supress warning when build the release build\n\n    auto fw = static_cast<uint32_t>(FreeImage_GetWidth(image));\n    auto fh = static_cast<uint32_t>(FreeImage_GetHeight(image));\n    auto fpitch = static_cast<uint32_t>(FreeImage_GetPitch(image));\n    BYTE *fimg_bits  = FreeImage_GetBits(image);\n\n    auto bw = static_cast<uint32_t>(FreeImage_GetWidth(mask));\n    auto bh = static_cast<uint32_t>(FreeImage_GetHeight(mask));\n    auto bpitch = static_cast<uint32_t>(FreeImage_GetPitch(mask));\n    BYTE *bimg_bits  = FreeImage_GetBits(mask);\n\n    BYTE *line1 = fimg_bits;\n    BYTE *line2 = bimg_bits;\n\n    int blend_bits = 0;\n\n    BYTE fp_default[4] = {0, 0, 0, 0};\n    BYTE bp_default[4] = {255, 255, 255, 255};\n\n    for(uint32_t y = 0; y < fh || y < bh; ++y)\n    {\n        for(uint32_t x = 0; x < fw || x < bw; ++x)\n        {\n            bool bp_present = y < bh && x < bw;\n            bool fp_present = y < fh && x < fw;\n\n            BYTE *fp = line1 + (y * fpitch) + (x * 4);\n            BYTE *bp = line2 + (y * bpitch) + (x * 4);\n\n            if(!fp_present)\n                fp = fp_default;\n\n            if(!bp_present)\n                bp = bp_default;\n\n            // accept vanilla GIFs that target 16-bit color depth\n            // note that missing back pixels are white and absent front pixels are black\n            bool bp_is_white = bp[0] >= 0xf0 && bp[1] >= 0xf0 && bp[2] >= 0xf0;\n            bool fp_is_black = fp[0] <  0x10 && fp[1] <  0x10 && fp[2] <  0x10;\n\n            // back pixel is white and front pixel is black: buffer preserved\n            if(bp_is_white && fp_is_black)\n                continue;\n\n            // front contains back: buffer replaced with front pixel\n            // (this subsumes mask pixel black, front pixel white, and matching cases)\n            if((bp[0] & ~fp[0] & 0xf8) == 0 && (bp[1] & ~fp[1] & 0xf8) == 0 && (bp[2] & ~fp[2] & 0xf8) == 0)\n                continue;\n\n            blend_bits += (bp[0] & ~fp[0]) / 0x08;\n            blend_bits += (bp[1] & ~fp[1]) / 0x08;\n            blend_bits += (bp[2] & ~fp[2]) / 0x08;\n        }\n    }\n\n    if(blend_bits >= 10000)\n    {\n        D_pLogDebug(\"Texture REQUIRES the bitmask render (%s)\", origPath.c_str());\n        return true;\n    }\n    else\n    {\n        D_pLogDebug(\"Texture doesn't require bitmask render (%s)\", origPath.c_str());\n        return false;\n    }\n}\n\nbool GraphicsHelps::validateForDepthTest(FIBITMAP *image, const std::string &origPath)\n{\n    if(!image)\n        return false;\n\n    (void)origPath; // supress warning when build the release build\n\n    auto w = static_cast<uint32_t>(FreeImage_GetWidth(image));\n    auto h = static_cast<uint32_t>(FreeImage_GetHeight(image));\n    auto pitch = static_cast<uint32_t>(FreeImage_GetPitch(image));\n    BYTE *img_bits  = FreeImage_GetBits(image);\n\n    for(uint32_t y = 0; y < h; ++y)\n    {\n        for(uint32_t x = 0; x < w; ++x)\n        {\n            BYTE *alpha = img_bits + (y * pitch) + (x * 4) + 3;\n\n            // vanilla game used 5 bits per channel, so we set the alpha test as >= 0x08\n            if(*alpha < 0x08 || *alpha >= 0xf8)\n                continue;\n\n            D_pLogDebug(\"Texture CANNOT use depth test (%s)\", origPath.c_str());\n            return false;\n        }\n    }\n\n    D_pLogDebug(\"Texture can use depth test (%s)\", origPath.c_str());\n    return true;\n}\n\nFIBITMAP *GraphicsHelps::fastScaleDownAnd32Bit(FIBITMAP *image, bool do_scale_down)\n{\n    // one kilobytes of stack isn't affordable? make this static, then.\n\n    // palette for full byte or high nybble (ignores low nybble if 4bpp)\n    std::array<uint32_t, 256> palette;\n\n\n    if(!image)\n        return nullptr;\n\n    if(!do_scale_down)\n        return fastConvertTo32Bit(image);\n\n    if(FreeImage_GetBPP(image) == 32)\n        return fast2xScaleDown(image);\n\n    if(FreeImage_GetBPP(image) != 1 && FreeImage_GetBPP(image) != 4 && FreeImage_GetBPP(image) != 8 && FreeImage_GetBPP(image) != 24)\n        return nullptr;\n\n    auto src_w = static_cast<uint32_t>(FreeImage_GetWidth(image));\n    auto src_h = static_cast<uint32_t>(FreeImage_GetHeight(image));\n    const uint8_t *src_pixels  = reinterpret_cast<uint8_t*>(FreeImage_GetBits(image));\n    const uint32_t *src_palette = reinterpret_cast<uint32_t*>(FreeImage_GetPalette(image));\n    const uint8_t *src_trans = reinterpret_cast<uint8_t*>(FreeImage_GetTransparencyTable(image));\n\n    auto src_stride = static_cast<uint32_t>(FreeImage_GetPitch(image)) * 2;\n    auto src_pixel_stride = (FreeImage_GetBPP(image) == 8) ? 2 : 1;\n\n    auto dest_w = src_w / 2;\n    auto dest_h = src_h / 2;\n\n    FIBITMAP *dest = FreeImage_Allocate(dest_w, dest_h, 32);\n\n    if(!dest)\n        return nullptr;\n\n    uint32_t *dest_pixels = reinterpret_cast<uint32_t*>(FreeImage_GetBits(dest));\n    auto dest_px_stride = static_cast<uint32_t>(FreeImage_GetPitch(dest)) / 4;\n\n    // special logic for 1 BPP\n    if(FreeImage_GetBPP(image) == 1)\n    {\n        // fill first two entries\n        for(int i = 0; i < 2; i++)\n        {\n            palette[i] = src_palette[i];\n\n            if(src_trans)\n                ((uint8_t*)&palette[i])[3] = src_trans[i];\n            else\n                ((uint8_t*)&palette[i])[3] = 255;\n        }\n\n        // perform lookups\n        for(uint32_t y = 0; y < dest_h; y++)\n        {\n            for(uint32_t x = 0; x < dest_w; x++)\n            {\n                uint8_t which_bit = 64 >> ((x % 4) * 2);\n                bool lit = src_pixels[y * src_stride + x / 4] & which_bit;\n                dest_pixels[y * dest_px_stride + x] = palette[lit];\n            }\n        }\n\n        return dest;\n    }\n\n    // special logic for 24 BPP\n    if(FreeImage_GetBPP(image) == 24)\n    {\n        src_pixel_stride = 6;\n        uint8_t* dest_pixel_components = reinterpret_cast<uint8_t*>(dest_pixels);\n\n        for(uint32_t y = 0; y < dest_h; y++)\n        {\n            for(uint32_t x = 0; x < dest_w; x++)\n            {\n                dest_pixel_components[(y * dest_px_stride + x) * 4 + 0] = src_pixels[y * src_stride + x * src_pixel_stride + 0];\n                dest_pixel_components[(y * dest_px_stride + x) * 4 + 1] = src_pixels[y * src_stride + x * src_pixel_stride + 1];\n                dest_pixel_components[(y * dest_px_stride + x) * 4 + 2] = src_pixels[y * src_stride + x * src_pixel_stride + 2];\n                dest_pixel_components[(y * dest_px_stride + x) * 4 + 3] = 255;\n            }\n        }\n\n        return dest;\n    }\n\n    // fill palette\n    if(FreeImage_GetBPP(image) == 8)\n    {\n        for(int i = 0; i < 256; i++)\n        {\n            palette[i] = src_palette[i];\n\n            if(src_trans)\n                ((uint8_t*)&palette[i])[3] = src_trans[i];\n            else\n                ((uint8_t*)&palette[i])[3] = 255;\n        }\n    }\n    else if(FreeImage_GetBPP(image) == 4)\n    {\n        // fill high nybble palettes\n        for(int i = 0; i < 16; i++)\n        {\n            for(int j = 0; j < 16; j++)\n            {\n                palette[i * 16 + j] = src_palette[i];\n                if(src_trans)\n                    ((uint8_t*)&palette[i * 16 + j])[3] = src_trans[i];\n                else\n                    ((uint8_t*)&palette[i * 16 + j])[3] = 255;\n            }\n        }\n    }\n\n    // perform lookups\n    for(uint32_t y = 0; y < dest_h; y++)\n    {\n        for(uint32_t x = 0; x < dest_w; x++)\n        {\n            dest_pixels[y * dest_px_stride + x] = palette[src_pixels[y * src_stride + x * src_pixel_stride]];\n        }\n    }\n\n    return dest;\n}\n\nstatic uint16_t s_RGB24_to_RGB565(uint32_t RGB24)\n{\n#ifdef __WII__\n    uint16_t r = ((uint8_t*)(&RGB24))[0];\n    uint16_t g = ((uint8_t*)(&RGB24))[1];\n    uint16_t b = ((uint8_t*)(&RGB24))[2];\n#else\n    uint16_t r = ((uint8_t*)(&RGB24))[2];\n    uint16_t g = ((uint8_t*)(&RGB24))[1];\n    uint16_t b = ((uint8_t*)(&RGB24))[0];\n#endif\n\n    return ((r >> 3) << FI16_565_RED_SHIFT) | ((g >> 2) << FI16_565_GREEN_SHIFT) | ((b >> 3) << FI16_565_BLUE_SHIFT);\n}\n\nFIBITMAP *GraphicsHelps::fastConvertToRGB565AndFlip(FIBITMAP *image, bool /*unused*/)\n{\n    // one kilobytes of stack isn't affordable? make this static, then.\n\n    // palette for full bytes\n    std::array<uint16_t, 256> palette;\n\n\n    if(!image)\n        return nullptr;\n\n    if(FreeImage_GetBPP(image) != 1 && FreeImage_GetBPP(image) != 8)\n        return nullptr;\n\n\n    auto src_w = static_cast<uint32_t>(FreeImage_GetWidth(image));\n    auto src_h = static_cast<uint32_t>(FreeImage_GetHeight(image));\n    const uint8_t *src_pixels  = reinterpret_cast<uint8_t*>(FreeImage_GetBits(image));\n    const uint32_t *src_palette = reinterpret_cast<uint32_t*>(FreeImage_GetPalette(image));\n    auto src_stride = static_cast<uint32_t>(FreeImage_GetPitch(image));\n\n    FIBITMAP *dest = FreeImage_Allocate(src_w, src_h, 16, FI16_565_RED_MASK, FI16_565_GREEN_MASK, FI16_565_BLUE_MASK);\n\n    if(!dest)\n        return nullptr;\n\n    uint16_t *dest_pixels  = reinterpret_cast<uint16_t*>(FreeImage_GetBits(dest));\n    auto dest_px_stride = static_cast<uint32_t>(FreeImage_GetPitch(dest)) / 2;\n\n    // special logic for 1 BPP\n    if(FreeImage_GetBPP(image) == 1)\n    {\n        // fill first two entries\n        for(int i = 0; i < 2; i++)\n            palette[i] = s_RGB24_to_RGB565(src_palette[i]);\n\n        // perform lookups\n        for(uint32_t y = 0; y < src_h; y++)\n        {\n            int src_y = src_h - (y + 1);\n            for(uint32_t x = 0; x < src_w; x++)\n            {\n                uint8_t which_bit = 128 >> (x % 8);\n                bool lit = src_pixels[src_y * src_stride + x / 8] & which_bit;\n                dest_pixels[y * dest_px_stride + x] = palette[lit];\n            }\n        }\n\n        return dest;\n    }\n\n    // fill 8-bit palette\n    for(int i = 0; i < 256; i++)\n        palette[i] = s_RGB24_to_RGB565(src_palette[i]);\n\n    // perform lookups\n    for(uint32_t y = 0; y < src_h; y++)\n    {\n        int src_y = src_h - (y + 1);\n        for(uint32_t x = 0; x < src_w; x++)\n        {\n            dest_pixels[y * dest_px_stride + x] = palette[src_pixels[src_y * src_stride + x]];\n        }\n    }\n\n    return dest;\n}\n\nbool GraphicsHelps::setWindowIcon(SDL_Window *window, FIBITMAP *img, int iconSize)\n{\n#if defined(_WIN32) && !defined(THEXTECH_WINRT)\n    struct SDL_SysWMinfo wmInfo;\n    SDL_VERSION(&wmInfo.version)\n\n    if(SDL_FALSE == SDL_GetWindowWMInfo(window, &wmInfo))\n        return false;\n\n    if(wmInfo.subsystem != SDL_SYSWM_WINDOWS)\n        return false;\n\n    HWND windowH = wmInfo.info.win.window;\n    HICON hicon = nullptr;\n    BYTE *icon_bmp = nullptr;\n    unsigned int icon_len, y;\n    SDL_RWops *dst;\n    unsigned int w = FreeImage_GetWidth(img);\n    unsigned int h = FreeImage_GetWidth(img);\n    Uint8 *bits = (Uint8 *)FreeImage_GetBits(img);\n    unsigned int pitch = FreeImage_GetPitch(img);\n    /* Create temporary bitmap buffer */\n    icon_len = 40 + h * w * 4;\n    icon_bmp = SDL_stack_alloc(BYTE, icon_len);\n    dst = SDL_RWFromMem(icon_bmp, icon_len);\n\n    if(!dst)\n    {\n        SDL_stack_free(icon_bmp);\n        return false;\n    }\n\n    /* Write the BITMAPINFO header */\n    SDL_WriteLE32(dst, 40);\n    SDL_WriteLE32(dst, w);\n    SDL_WriteLE32(dst, h * 2);\n    SDL_WriteLE16(dst, 1);\n    SDL_WriteLE16(dst, 32);\n    SDL_WriteLE32(dst, BI_RGB);\n    SDL_WriteLE32(dst, h * w * 4);\n    SDL_WriteLE32(dst, 0);\n    SDL_WriteLE32(dst, 0);\n    SDL_WriteLE32(dst, 0);\n    SDL_WriteLE32(dst, 0);\n    y = 0;\n\n    do\n    {\n        Uint8 *src = bits + y * pitch;\n        SDL_RWwrite(dst, src, pitch, 1);\n    }\n    while(++y < h);\n\n    hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);\n    SDL_RWclose(dst);\n    SDL_stack_free(icon_bmp);\n    /* Set the icon for the window */\n    SendMessage(windowH, WM_SETICON, (iconSize < 32) ? ICON_SMALL : ICON_BIG, (LPARAM) hicon);\n#else\n    (void)iconSize;\n    SDL_Surface *sicon = GraphicsHelps::fi2sdl(img);\n    SDL_SetWindowIcon(window, sicon);\n    SDL_FreeSurface(sicon);\n    const char *error = SDL_GetError();\n\n    if(*error != '\\0')\n        return false;\n\n#endif\n    return true;\n}\n\n\n/*********************Code from Qt**end****************/\n"
  },
  {
    "path": "lib/Graphics/graphics_funcs.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef GRAPHICS_FUNCS_H\n#define GRAPHICS_FUNCS_H\n\n#ifndef SDL_SDL_STDINC_H\n#   include \"sdl_proxy/sdl_stdinc.h\"\n#endif\n\n#include <string>\n#include <vector>\n\n#include \"size.h\"\n#include \"Utils/files.h\"\n\nstruct PGE_Pix\n{\n    Uint8 r;\n    Uint8 g;\n    Uint8 b;\n    Uint8 a;\n};\n\ntypedef struct SDL_Window SDL_Window;\ntypedef struct SDL_Surface SDL_Surface;\n\nstruct FreeImageIO;\n\n/*!\n * \\brief Helpful graphical functions which are doing various work: I/O, Front+Mask blending, etc.\n */\nstruct FIBITMAP;\nclass GraphicsHelps\n{\npublic:\n    /*!\n     * \\brief Initializes FreeImage\n     */\n    static void  initFreeImage();\n    /*!\n     * \\brief DeInitializes FreeImage\n     */\n    static void  closeFreeImage();\n\n    /*!\n     * \\brief initializes a FreeImageIO with operations to load from an SDL_RWops object\n     */\n    static void SetRWopsIO(FreeImageIO *io);\n\n    /*!\n     * \\brief Loads image from a disk\n     * \\param file full or relative path to the file\n     * \\param convertTo32bit need to convert image into 32bit RGBA\n     * \\return FreeImage descriptor to loaded image\n     */\n    static FIBITMAP *loadImage(const std::string &file, bool convertTo32bit = true);\n    static FIBITMAP *loadImage(const Files::Data &raw, bool convertTo32bit = true);\n\n    /*!\n     * \\brief Loads QOI image from a memory buffer\n     * \\param raw - QOI buffer\n     * \\param[out] depthTestSupported - whether QOI indicates that depth test is supported (sets channels flag to 3)\n     * \\return FreeImage descriptor to loaded image\n     *\n     * Implemented in xt_qoi.cpp\n     */\n    static FIBITMAP *loadQOI(const Files::Data &raw, bool &depthTestSupported);\n\n    /*!\n     * \\brief Loads mask image from a disk\n     * \\param file full or relative path to the file\n     * \\param maskIsPng if set, set the RGB channels of the mask to the inverse alpha channel of the loaded image\n     * \\param convertTo32bit need to convert image into 32bit RGBA\n     * \\return FreeImage descriptor to loaded mask\n     */\n    static FIBITMAP *loadMask(const std::string &file, bool maskIsPng, bool convertTo32bit = true);\n    static FIBITMAP *loadMask(const Files::Data &raw, bool maskIsPng, bool convertTo32bit = true);\n\n    /*!\n     * \\brief Loads image from application resources\n     * \\param file in-resource path to the file\n     * \\return FreeImage descriptor to loaded image\n     */\n    //static FIBITMAP *loadImageRC(const char *file);\n\n    /*!\n     * \\brief Converts FreeImage into SDL_Surface\n     * \\param img Source FreeImage descriptor to loaded image\n     * \\return SDL_Surface pointer\n     */\n    static SDL_Surface *fi2sdl(FIBITMAP *img);\n\n    /*!\n     * \\brief Closes image and frees memory\n     * \\param FreeImage descriptor to loaded image\n     */\n    static void closeImage(FIBITMAP *img);\n\n    /**\n     * @brief Generate mask from an RGBA source\n     * @param mask [inout] Mask initially in RGBA format\n     */\n    static void RGBAToMask(FIBITMAP* mask);\n\n    /*!\n     * \\brief Merges mask and foreground image with bit blitting algorithm\n     * 1) draw mask over grey-filled image with bitwise AND per each pixel (white pixels are will not change background)\n     * 2) draw foreground over same image with bitwise OR per each pixel (black pixels are will not change background)\n     * 3) Calculate alpha-channel level dependent to black-white difference on the mask and on the foreground:\n     *    white on the mask is a full transparency, black - is a solid pixels area.\n     * \\param image\n     * \\param pathToMask\n     * \\param pathToMaskFallback\n     */\n    static void mergeWithMask(FIBITMAP *image,\n                              const std::string &pathToMask,\n                              const std::string &pathToMaskFallback = std::string());\n    static void mergeWithMask(FIBITMAP *image, const Files::Data &maskRaw, bool maskIsPng = false);\n    static void mergeWithMask(FIBITMAP *image, FIBITMAP *mask);\n\n    static void setBitBlitBG(uint8_t red, uint8_t green, uint8_t blue);\n    static void resetBitBlitBG();\n\n    /*!\n     * \\brief Replaces one color with another\n     * \\param image Image\n     * \\param src Original pixel color\n     * \\param dst New pixel color\n     */\n    static void replaceColor(FIBITMAP *image, const PGE_Pix &src, const PGE_Pix &dst);\n\n    /*!\n     * \\brief Gets metric from image file\n     * \\param [__in] imageFile Path to image file\n     * \\param [__out] imgSize Pointer to PGE_Size value\n     */\n    static bool getImageMetrics(const std::string &imageFile, PGE_Size *imgSize);\n    static void getMaskedImageInfo(const std::string &rootDir,\n                                   const std::string &in_imgName,\n                                   std::string &out_maskName,\n                                   std::string &out_errStr,\n                                   PGE_Size *imgSize = nullptr);\n\n    /*!\n     * \\brief Checks the possibility to shrink down the image with 2x factor without losses\n     * This call checks the ability to scale-down an image without getting distortions after process.\n     * There are next conditions required:\n     * - Image should have metrics being multiple 2\n     * - Every 2x2 sector at the image should contain the same color.\n     * This means, this image was been scaled up with 2x factor in the past to get this image\n     * \\param image Image to check\n     * \\param origPath The optional original path to the texture, needed for log printing\n     * \\return true - image will be shrank without damages\n     */\n    static bool validateFor2xScaleDown(FIBITMAP *image, const std::string &origPath = std::string());\n\n    /*!\n     * \\brief Quickly scales down the image 2x using nearest-neighbor downsampling\n     * \\param image Image to downscale\n     * \\return FreeImage descriptor to scaled image\n     */\n    static FIBITMAP *fast2xScaleDown(FIBITMAP *image);\n\n    /*!\n     * \\brief Quickly convert the image from 16-color or 256-color to 32-bit RGBA\n     * \\param image Image to convert\n     * \\return FreeImage descriptor to converted image\n     */\n    static FIBITMAP *fastConvertTo32Bit(FIBITMAP *image);\n\n    /*!\n     * \\brief Check textures are they require the bitmask render or not\n     * \\param image Front image\n     * \\param mask Mask image\n     * \\param origPath The optional original path to the texture, needed for log printing\n     * \\return true if bitmask render suggested for better accuracy\n     */\n    static bool validateBitmaskRequired(FIBITMAP *image, FIBITMAP *mask, const std::string &origPath = std::string());\n\n    /*!\n     * \\brief Checks the possibility to render the image using a depth test without losses\n     * There are next conditions required:\n     * - Every pixel in the image must be either fully transparent or fully opaque\n     * \\param image Image to check\n     * \\param origPath The optional original path to the texture, needed for log printing\n     * \\return true - image may be included in draws using the depth test\n     */\n    static bool validateForDepthTest(FIBITMAP *image, const std::string &origPath = std::string());\n\n    /*!\n     * \\brief Quickly convert the image from 16-color or 256-color to 32-bit RGBA and simultaneously downscale\n     * \\param image           Image to convert\n     * \\param do_scale_down   Whether to actually scale down image\n     * \\return FreeImage descriptor to converted image\n     */\n    static FIBITMAP *fastScaleDownAnd32Bit(FIBITMAP *image, bool do_scale_down = true);\n\n    /*!\n     * \\brief Quickly convert the image from 1-color or 256-color to 16-bit RGB565 (3DS-exclusive). Also does a vertical flip.\n     * \\param image           Image to convert\n     * \\return FreeImage descriptor to converted image\n     */\n    static FIBITMAP *fastConvertToRGB565AndFlip(FIBITMAP *image, bool /*unused*/);\n\n    /*!\n     * \\brief Set the icon for the SDL Window\n     * \\param window Target window instance\n     * \\param img Icon image to set\n     * \\param iconSize The desired size for the icon\n     * \\return true if success\n     */\n    static bool setWindowIcon(SDL_Window *window, FIBITMAP *img, int iconSize);\n};\n\n#endif // GRAPHICS_FUNCS_H\n"
  },
  {
    "path": "lib/Graphics/image_size.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include \"image_size.h\"\n\n#include <SDL2/SDL_rwops.h>\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n\n#include <Utils/files.h>\n\nstatic bool tryGIF(SDL_RWops* file, uint32_t *w, uint32_t *h)\n{\n    SDL_RWseek(file, 0, RW_SEEK_SET);\n    const char *GIF1 = \"GIF87a\";\n    const char *GIF2 = \"GIF89a\";\n    char magic[6];\n\n    if(SDL_RWread(file, magic, 1, 6) != 6)\n        return false;\n\n    bool found = false;\n\n    if(SDL_strncmp(magic, GIF1, 6) == 0)\n        found = true;\n\n    if(SDL_strncmp(magic, GIF2, 6) == 0)\n        found = true;\n\n    if(!found)\n        return false;\n\n    unsigned char size[4];\n\n    if(SDL_RWread(file, reinterpret_cast<char *>(size), 1, 4) != 4)\n        return false;\n\n#define UINT(d) static_cast<unsigned int>(d)\n    *w = ((UINT(size[0]) & 0x00FF) | ((UINT(size[1]) << 8) & 0xFF00));\n    *h = ((UINT(size[2]) & 0x00FF) | ((UINT(size[3]) << 8) & 0xFF00));\n#undef UINT\n\n    return true;\n}\n\nstatic bool tryBMP(SDL_RWops* file, uint32_t *w, uint32_t *h)\n{\n    SDL_RWseek(file, 0, RW_SEEK_SET);\n    const char *BMP = \"BM\";\n    char magic[2];\n\n    if(SDL_RWread(file, magic, 1, 2) != 2)\n        return false;\n\n    if(SDL_strncmp(magic, BMP, 2) != 0)\n        return false;\n\n    unsigned char size[8];\n\n    if(SDL_RWseek(file, 18, RW_SEEK_SET) < 0)\n        return false;\n\n    if(SDL_RWread(file, reinterpret_cast<char *>(size), 1, 8) != 8)\n        return false;\n\n#define UINT(d) static_cast<unsigned int>(d)\n    *w = ((UINT(size[0]) & 0xFF) | ((UINT(size[1]) << 8) & 0xFF00) |\n         ((UINT(size[2]) << 16) & 0xFF0000) | ((UINT(size[3]) << 24) & 0xFF000000));\n\n    *h = ((UINT(size[4]) & 0xFF) | ((UINT(size[5]) << 8) & 0xFF00) |\n         ((UINT(size[6]) << 16) & 0xFF0000) | ((UINT(size[7]) << 24) & 0xFF000000));\n#undef UINT\n\n    return true;\n}\n\nstatic bool tryPNG(SDL_RWops* file, uint32_t *w, uint32_t *h)\n{\n    SDL_RWseek(file, 0, RW_SEEK_SET);\n    const char *PNG  = \"\\211PNG\\r\\n\\032\\n\";\n    const char *IHDR = \"IHDR\";\n    char magic[8];\n\n    if(SDL_RWread(file, magic, 1, 8) != 8)\n        return false;\n\n    if(SDL_strncmp(magic, PNG, 8) != 0)\n        return false;\n\n    if(SDL_RWread(file, magic, 1, 8) != 8)\n        return false;\n\n    if(SDL_strncmp(magic + 4, IHDR, 4) != 0)\n        return false;\n\n    unsigned char size[8];\n\n    if(SDL_RWread(file, reinterpret_cast<char *>(size), 1, 8) != 8)\n        return false;\n\n#define UINT(d) static_cast<unsigned int>(d)\n    *w = ((UINT(size[3]) & 0xFF) | ((UINT(size[2]) << 8) & 0xFF00) | ((UINT(size[1]) << 16) & 0xFF0000) | ((UINT(size[0]) << 24) & 0xFF000000));\n    *h = ((UINT(size[7]) & 0xFF) | ((UINT(size[6]) << 8) & 0xFF00) | ((UINT(size[5]) << 16) & 0xFF0000) | ((UINT(size[4]) << 24) & 0xFF000000));\n#undef UINT\n\n    return true;\n}\n\nstatic bool tryQOI(SDL_RWops* file, uint32_t *w, uint32_t *h)\n{\n    SDL_RWseek(file, 0, RW_SEEK_SET);\n    const char *qoif = \"qoif\";\n    char header[14];\n\n    if(SDL_RWread(file, header, 1, 14) != 14)\n        return false;\n\n    if(SDL_memcmp(&header[0], qoif, 4) != 0)\n        return false;\n\n    // check channels valid\n    if(header[12] != 3 && header[12] != 4)\n        return false;\n\n    // check colorspace valid\n    if(header[13] != 0 && header[13] != 1)\n        return false;\n\n#define UINT(d) static_cast<unsigned int>(d)\n    *w = ((UINT(header[7]) & 0xFF) | ((UINT(header[6]) << 8) & 0xFF00) |\n         ((UINT(header[5]) << 16) & 0xFF0000) | ((UINT(header[4]) << 24) & 0xFF000000));\n\n    *h = ((UINT(header[11]) & 0xFF) | ((UINT(header[10]) << 8) & 0xFF00) |\n         ((UINT(header[9]) << 16) & 0xFF0000) | ((UINT(header[8]) << 24) & 0xFF000000));\n#undef UINT\n\n    // mark of a 1x texture\n    if(header[13] == 1)\n    {\n        *w *= 2;\n        *h *= 2;\n    }\n\n    return true;\n}\n\nstatic char *findJpegHead(char *src, size_t src_size)\n{\n    char *cur = src;\n    const char **nd;\n\n    const size_t hSize = 2;\n\n    const char *heads[] =\n    {\n        /*EXIF*/ \"\\xFF\\xE1\", /* needed for bytes to skip */\n        /*SOF0*/ \"\\xff\\xc0\", /*SOF1*/ \"\\xff\\xc1\", /*SOF2*/ \"\\xff\\xc2\",\n        /*SOF9*/ \"\\xff\\xc9\", /*SOF10*/ \"\\xff\\xca\",\n        nullptr\n    };\n\n    while(src_size > 2)\n    {\n        nd = heads;\n        while(*nd)\n        {\n            if(SDL_memcmp(cur, *nd, hSize) == 0)\n            {\n#ifdef IMGSIZE_DEBUG\n                if(SDL_memcmp(*nd, \"\\xFF\\xE1\", 2) != 0)\n                    printf(\"JPEG: got header: [%02X %02X]\\n\", (*nd)[0], (*nd)[1]);\n#endif\n                return cur;\n            }\n            nd++;\n        }\n        cur++;\n        src_size -= 1;\n    }\n    return nullptr;\n}\n\nstatic bool tryJPEG(SDL_RWops* file, uint32_t *w, uint32_t *h)\n{\n#define JPEG_BUFFER_SIZE 1024\n#define UINT(d) static_cast<unsigned int>(d)\n#define BE16(arr, i) (((UINT(arr[i]) << 8) & 0xFF00) | (UINT(arr[i + 1]) & 0xFF))\n    const char *JPG1  = \"\\xFF\\xD8\\xFF\\xDB\";\n    const char *JPG2_1  = \"\\xFF\\xD8\\xFF\\xE0\", *JPG2_2  = \"\\x4A\\x46\\x49\\x46\\x00\\x01\";\n    const char *JPG3_1  = \"\\xFF\\xD8\\xFF\\xE1\", *JPG3_2  = \"\\x45\\x78\\x69\\x66\\x00\\x00\";\n    char magic[12], raw[JPEG_BUFFER_SIZE];\n    char *head = nullptr;\n    size_t chunk_size = 0;\n    Sint64 pos;\n\n    SDL_RWseek(file, 0, RW_SEEK_SET);\n\n    if(SDL_RWread(file, magic, 1, 10) != 10)\n        return false;\n\n    if(SDL_strncmp(magic, JPG1, 4) != 0 &&\n      (SDL_strncmp(magic, JPG2_1, 4) != 0 && SDL_strncmp(magic + 6, JPG2_2, 6) != 0) &&\n      (SDL_strncmp(magic, JPG3_1, 4) != 0 && SDL_strncmp(magic + 6, JPG3_2, 6) != 0))\n        return false;\n\n    while(true)\n    {\n        SDL_memset(raw, 0, JPEG_BUFFER_SIZE);\n        pos = SDL_RWtell(file);\n        chunk_size = SDL_RWread(file, raw, 1, JPEG_BUFFER_SIZE);\n        if(chunk_size == 0)\n            break;\n\n        head = findJpegHead(raw, JPEG_BUFFER_SIZE);\n        if(head)\n        {\n            if(head + 20 >= raw + JPEG_BUFFER_SIZE)\n            {\n                SDL_RWseek(file, -20, RW_SEEK_CUR);\n                continue; /* re-scan this place */\n            }\n\n            if(SDL_memcmp(head, \"\\xFF\\xE1\", 2) == 0) /* EXIF, skip it!*/\n            {\n                const Sint64 curPos = pos + (head - raw);\n                unsigned int toSkip = BE16(head, 2);\n                SDL_RWseek(file, curPos + toSkip + 2, RW_SEEK_SET);\n                continue;\n            }\n\n            *h = BE16(head, 5);\n            *w = BE16(head, 7);\n            return true;\n        }\n    }\n\n    return false;\n#undef BE16\n#undef UINT\n#undef JPEG_BUFFER_SIZE\n}\n\nbool PGE_ImageInfo::getImageSizeRW(SDL_RWops *image, uint32_t *w, uint32_t *h, int *errCode)\n{\n    bool ret = false;\n    if(tryPNG(image, w, h))\n        ret = true;\n    else\n    if(tryQOI(image, w, h))\n        ret = true;\n    else\n    if(tryGIF(image, w, h))\n        ret = true;\n    else\n    if(tryBMP(image, w, h))\n        ret = true;\n    else\n    if(tryJPEG(image, w, h))\n        ret = true;\n\n    SDL_RWclose(image);\n\n    if(!ret)\n    {\n        if(errCode)\n            *errCode = ERR_UNSUPPORTED_FILETYPE;\n    }\n\n    return ret;\n}\n\nbool PGE_ImageInfo::getImageSize(const PGEString &imagePath, uint32_t *w, uint32_t *h, int *errCode)\n{\n    if(errCode)\n        *errCode = ERR_OK;\n\n    std::string imgPath = PGEStringToStd(imagePath);\n\n    SDL_RWops* image = Files::open_file(imgPath, \"rb\");\n\n    if(!image)\n    {\n        if(errCode)\n            *errCode = ERR_NOT_EXISTS;\n\n        return false;\n    }\n\n    return getImageSizeRW(image, w, h, errCode);\n}\n\n\nPGEString PGE_ImageInfo::getMaskName(const PGEString &imageFileName)\n{\n    std::string mask = PGEStringToStd(imageFileName);\n    //Make mask filename\n    size_t dotPos = mask.find_last_of('.');\n\n    if(dotPos == std::string::npos)\n        mask.push_back('m');\n    else\n        mask.insert(mask.begin() + std::string::difference_type(dotPos), 'm');\n\n    return StdToPGEString(mask);\n}\n"
  },
  {
    "path": "lib/Graphics/image_size.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef IMAGESIZE_H\n#define IMAGESIZE_H\n\n#include \"PGEString.h\"\n#include <cstdint>\n\nstruct SDL_RWops;\n\n/**\n * @brief Basic image information misc. functions\n */\nnamespace PGE_ImageInfo\n{\n\n/**\n * @brief Error codes of image information retrieving\n */\nenum ErrCodes\n{\n    //! No errors, everything is OK\n    ERR_OK,\n    //! Unknown error\n    ERR_UNKNOWN,\n    //! Image file type is not supported\n    ERR_UNSUPPORTED_FILETYPE,\n    //! File not exists\n    ERR_NOT_EXISTS,\n    //! Can't open the file\n    ERR_CANT_OPEN,\n};\n\nbool getImageSizeRW(SDL_RWops *image, uint32_t *w, uint32_t *h, int *errCode=nullptr);\n\n/**\n * @brief Quickly get image size (width and height) from image file.\n * @param [in] imagePath Path to image file\n * @param [out] w Width of image\n * @param [out] h Height of image\n * @param [out] errCode Error code\n * @return true if successfully finished, false if error occouped\n */\nbool getImageSize(const PGEString &imagePath, uint32_t *w, uint32_t *h, int *errCode = nullptr);\n\n/**\n * @brief Returns filename of masked image\n * @param [in] imageFileName foreground image file-name\n * @return masked image filename\n */\nPGEString getMaskName(const PGEString &imageFileName);\n\n}//PGE_ImageInfo\n\n#endif // IMAGESIZE_H\n"
  },
  {
    "path": "lib/Graphics/size.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include \"size.h\"\n\nPGE_Size::PGE_Size()\n{\n    setSize(0, 0);\n}\n\nPGE_Size::PGE_Size(int w, int h)\n{\n    m_w = w;\n    m_h = h;\n}\n\nvoid PGE_Size::setSize(int w, int h)\n{\n    m_w = w;\n    m_h = h;\n}\n\nvoid PGE_Size::setWidth(int w)\n{\n    m_w = w;\n}\n\nvoid PGE_Size::setHeight(int h)\n{\n    m_h = h;\n}\n\nbool PGE_Size::isNull() const\n{\n    return ((m_w == 0) && (m_h == 0));\n}\n\nint PGE_Size::w() const\n{\n    return m_w;\n}\n\nint PGE_Size::h() const\n{\n    return m_h;\n}\n"
  },
  {
    "path": "lib/Graphics/size.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef PGE_Size_H\n#define PGE_Size_H\n\nclass PGE_Size\n{\npublic:\n    PGE_Size();\n    PGE_Size(const PGE_Size &p) = default;\n    PGE_Size(int w, int h);\n    ~PGE_Size() = default;\n    void setSize(int w, int h);\n    void setWidth(int w);\n    void setHeight(int h);\n    bool isNull() const;\n    int w() const;\n    int h() const;\nprivate:\n    int m_w;\n    int m_h;\n};\n\n#endif // PGE_Size_H\n"
  },
  {
    "path": "lib/Graphics/xt_qoi.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru> and ds-sloth\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*\n\nIncludes material from qoi.h\n\nCopyright (c) 2021, Dominic Szablewski - https://phoboslab.org\nSPDX-License-Identifier: MIT\n\n*/\n\n#include <stdlib.h>\n#include <string.h>\n\n#include <Utils/files.h>\n\n#include \"graphics_funcs.h\"\n#include <Logger/logger.h>\n\n#include <FreeImageLite.h>\n\n\n#ifndef QOI_MALLOC\n    #define QOI_MALLOC(sz) malloc(sz)\n    #define QOI_FREE(p)    free(p)\n#endif\n#ifndef QOI_ZEROARR\n    #define QOI_ZEROARR(a) memset((a),0,sizeof(a))\n#endif\n\n#define QOI_SRGB   0\n#define QOI_LINEAR 1\n#define BUF_SIZE 4096\n\ntypedef struct {\n    unsigned int width;\n    unsigned int height;\n    unsigned char channels;\n    unsigned char colorspace;\n} qoi_desc;\n\n\n#define QOI_OP_INDEX  0x00 /* 00xxxxxx */\n#define QOI_OP_DIFF   0x40 /* 01xxxxxx */\n#define QOI_OP_LUMA   0x80 /* 10xxxxxx */\n#define QOI_OP_RUN    0xc0 /* 11xxxxxx */\n#define QOI_OP_RGB    0xfe /* 11111110 */\n#define QOI_OP_RGBA   0xff /* 11111111 */\n\n#define QOI_MASK_2    0xc0 /* 11000000 */\n\n#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)\n#define QOI_COLOR_HASH_RGBQUAD(C) (C.rgbRed*3 + C.rgbGreen*5 + C.rgbBlue*7 + C.rgbReserved*11)\n#define QOI_MAGIC \\\n    (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \\\n     ((unsigned int)'i') <<  8 | ((unsigned int)'f'))\n#define QOI_HEADER_SIZE 14\n\n/* 2GB is the max file size that this implementation can safely handle. We guard\nagainst anything larger than that, assuming the worst case with 5 bytes per\npixel, rounded down to a nice clean value. 400 million pixels ought to be\nenough for anybody. */\n#define QOI_PIXELS_MAX ((unsigned int)400000000)\n\nstatic const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};\n\nstatic unsigned int qoi_read_32(const unsigned char*& read_ptr) {\n    unsigned int a = *(read_ptr++);\n    unsigned int b = *(read_ptr++);\n    unsigned int c = *(read_ptr++);\n    unsigned int d = *(read_ptr++);\n    return a << 24 | b << 16 | c << 8 | d;\n}\n\nFIBITMAP *GraphicsHelps::loadQOI(const Files::Data &raw, bool &depthTestSupported)\n{\n    if(raw.size() < QOI_HEADER_SIZE + 8)\n        return nullptr;\n\n    FIBITMAP *dib = nullptr;\n    const unsigned char *read_ptr = raw.begin();\n    const unsigned char *read_bound = raw.end() - 8;\n\n    // qoi_decode: local declarations\n    qoi_desc _desc;\n    qoi_desc* const desc = &_desc;\n\n    // qoi_decode: header read\n    unsigned int header_magic = qoi_read_32(read_ptr);\n    desc->width = qoi_read_32(read_ptr);\n    desc->height = qoi_read_32(read_ptr);\n    desc->channels = *(read_ptr++);\n    desc->colorspace = *(read_ptr++);\n\n    if(desc->width == 0 || desc->height == 0 ||\n       desc->channels < 3 || desc->channels > 4 ||\n       desc->colorspace > 1 ||\n       header_magic != QOI_MAGIC ||\n       desc->height >= QOI_PIXELS_MAX / desc->width)\n    {\n        return nullptr;\n    }\n\n    // allocate bitmap\n    dib = FreeImage_Allocate(desc->width, desc->height, 32);\n\n    if(!dib)\n        return nullptr;\n\n    FreeImage_SetTransparent(dib, TRUE);\n\n    // store whether the depth test is supported\n    depthTestSupported = (desc->channels == 3);\n\n    // start actually decoding\n    int run = 0;\n\n    RGBQUAD index[64];\n    RGBQUAD px;\n\n    QOI_ZEROARR(index);\n    px.rgbRed = 0;\n    px.rgbGreen = 0;\n    px.rgbBlue = 0;\n    px.rgbReserved = 255;\n\n    RGBQUAD* pixels = reinterpret_cast<RGBQUAD*>(FreeImage_GetBits(dib));\n    RGBQUAD* pixels_end = reinterpret_cast<RGBQUAD*>(FreeImage_GetBits(dib) + FreeImage_GetPitch(dib) * desc->height);\n    RGBQUAD* pixels_risk = pixels_end - 0x40; // optimization: moment after which we need to check that runs won't overrun the pixel buffer\n\n    while(pixels < pixels_end)\n    {\n        // if we're going to overrun bytes array, that's a bug!\n        if(read_ptr >= read_bound)\n            goto error;\n\n        unsigned char b1 = *(read_ptr++);\n        unsigned char fam = (b1 & QOI_MASK_2);\n\n        if(fam == QOI_OP_RUN)\n        {\n            // QOI_OP_RUN\n            if(b1 < QOI_OP_RGB)\n            {\n                run = (b1 & 0x3f) + 1; // add one because we will subtract one for this pixel below\n\n                if(pixels > pixels_risk && pixels + run > pixels_end)\n                    goto error;\n\n                for(; run > 0; run--)\n                    *(pixels++) = px;\n\n                continue;\n            }\n            else if(b1 == QOI_OP_RGB)\n            {\n                px.rgbRed   = *(read_ptr++);\n                px.rgbGreen = *(read_ptr++);\n                px.rgbBlue  = *(read_ptr++);\n            }\n            // QOI_OP_RGBA\n            else\n            {\n                px.rgbRed      = *(read_ptr++);\n                px.rgbGreen    = *(read_ptr++);\n                px.rgbBlue     = *(read_ptr++);\n                px.rgbReserved = *(read_ptr++);\n            }\n        }\n        else if(fam == QOI_OP_INDEX)\n        {\n            px = index[b1];\n        }\n        else if(fam == QOI_OP_DIFF)\n        {\n            px.rgbRed   += ((b1 >> 4) & 0x03) - 2;\n            px.rgbGreen += ((b1 >> 2) & 0x03) - 2;\n            px.rgbBlue  += ( b1       & 0x03) - 2;\n        }\n        else if(fam == QOI_OP_LUMA)\n        {\n            unsigned char b2 = *(read_ptr++);\n            char vg = (char)(b1 & 0x3f) - 32;\n            px.rgbRed   += vg - 8 + (char)((b2 >> 4) & 0x0f);\n            px.rgbGreen += vg;\n            px.rgbBlue  += vg - 8 +  (char)(b2       & 0x0f);\n        }\n\n        index[QOI_COLOR_HASH_RGBQUAD(px) & (64 - 1)] = px;\n\n        *(pixels++) = px;\n    }\n\n    // if there aren't exactly 8 bytes (padding) left, or the padding doesn't match, that's a bug!\n    if(read_ptr != read_bound || memcmp(read_ptr, qoi_padding, 8) != 0)\n        goto error;\n\n    return dib;\n\nerror:\n    if(dib)\n        FreeImage_Unload(dib);\n\n    return nullptr;\n}\n"
  },
  {
    "path": "lib/Integrator/int_discorcrpc.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_timer.h\"\n#include \"int_discorcrpc.h\"\n#include <Logger/logger.h>\n\n#include <time.h>\n#include <discord_rpc.h>\n\n\nstatic constexpr const char* APPLICATION_ID = XTECH_DISCORD_APPID;\nstatic_assert(APPLICATION_ID != nullptr, \"Discord Application ID is undefined. Please set the -DTHEXTECH_DISCORD_APPID=<app-id> CMake argument.\");\n\n\nstatic void handleDiscordReady(const DiscordUser* connectedUser)\n{\n    pLogDebug(\"Discord: connected to user %s#%s - %s\",\n              connectedUser->username,\n              connectedUser->discriminator,\n              connectedUser->userId);\n}\n\nstatic void handleDiscordDisconnected(int errcode, const char* message)\n{\n    pLogDebug(\"Discord: disconnected (%d: %s)\", errcode, message);\n}\n\nstatic void handleDiscordError(int errcode, const char* message)\n{\n    pLogDebug(\"Discord: error (%d: %s)\", errcode, message);\n}\n\nstatic void handleDiscordJoin(const char* secret)\n{\n    pLogDebug(\"Discord: join (%s)\", secret);\n}\n\nstatic void handleDiscordSpectate(const char* secret)\n{\n    pLogDebug(\"Discord: spectate (%s)\", secret);\n}\n\nstatic void handleDiscordJoinRequest(const DiscordUser* request)\n{\n    (void)request;\n}\n\n\nvoid DiscorcRPC::clearAllLabels()\n{\n    episodeName.clear();\n    levelName.clear();\n    editorFile.clear();\n}\n\nDiscorcRPC::DiscorcRPC()\n{\n    m_last_sync = SDL_GetTicks();\n}\n\nDiscorcRPC::~DiscorcRPC()\n{\n    clear();\n    quit();\n}\n\nvoid DiscorcRPC::init()\n{\n    pLogDebug(\"Discord: Initializing...\");\n\n    DiscordEventHandlers handlers;\n    SDL_memset(&handlers, 0, sizeof(handlers));\n    handlers.ready = handleDiscordReady;\n    handlers.disconnected = handleDiscordDisconnected;\n    handlers.errored = handleDiscordError;\n    handlers.joinGame = handleDiscordJoin;\n    handlers.spectateGame = handleDiscordSpectate;\n    handlers.joinRequest = handleDiscordJoinRequest;\n    Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL);\n}\n\nvoid DiscorcRPC::quit()\n{\n    pLogDebug(\"Discord: Quitting...\");\n    Discord_Shutdown();\n}\n\nvoid DiscorcRPC::setGameName(const std::string& game)\n{\n    gameName = game;\n    if(gameName.size() > 128)\n        gameName.resize(128);\n\n    // HEURISTIC icon name (for assets packs that has \"status-icon-name\" unset):\n    //   it's impossible to dynamiclally upload custom images,\n    //   So, there is a pre-defined list of available assets\n    if(gameName == \"Adventures of Demo\")\n        iconName = \"assets_aod\";\n    else if(gameName == \"A Super Mario Bros. X Thing\" || gameName == \"A Second Mario Bros. X Thing\")\n        iconName = \"assets_a2xt_ptts\";\n    else if(gameName == \"Nostalgic Paradise\")\n        iconName = \"assets_nostalgic_paradise\";\n    else if(gameName == \"Super Talking Time Bros.\" || gameName == \"Super Talking Time Bros. 2.5\")\n        iconName = \"assets_talking_time_bros\";\n    else if(gameName == \"Super Mario Bros. X Nostalgie\")\n        iconName = \"assets_smbx_nostalgie\";\n    else if(gameName == \"Super Mario Bros. X\")\n        iconName = \"assets_smbx\";\n    else\n        iconName.clear();\n}\n\nvoid DiscorcRPC::setEpisodeName(const std::string& ep)\n{\n    clearAllLabels();\n    episodeName = ep;\n    if(episodeName.size() > 128)\n        episodeName.resize(128);\n}\n\nvoid DiscorcRPC::setLevelName(const std::string& lev)\n{\n    clearAllLabels();\n    levelName = lev;\n    if(levelName.size() > 128)\n        levelName.resize(128);\n}\n\nvoid DiscorcRPC::setEditorFile(const std::string& fil)\n{\n    clearAllLabels();\n    editorFile = fil;\n    if(editorFile.size() > 128)\n        editorFile.resize(128);\n}\n\nvoid DiscorcRPC::setIconName(const std::string& icon)\n{\n    iconName = icon;\n    if(iconName.size() > 32)\n        iconName.resize(32);\n}\n\nvoid DiscorcRPC::sync()\n{\n    if(SDL_GetTicks() - m_last_sync < 100)\n        return; // 10 calls per second is allowed!\n\n    m_last_sync = SDL_GetTicks();\n#ifdef DISCORD_DISABLE_IO_THREAD\n    Discord_UpdateConnection();\n#endif\n    Discord_RunCallbacks();\n}\n\nvoid DiscorcRPC::update()\n{\n    pLogDebug(\"Discord: Updating state...\");\n\n    char buffer[128];\n\n    DiscordRichPresence discordPresence;\n    SDL_memset(&discordPresence, 0, sizeof(discordPresence));\n\n    discordPresence.details = gameName.c_str();\n\n    if(!levelName.empty())\n    {\n        SDL_snprintf(buffer, 128, \"Playing level: %s\", levelName.c_str());\n        discordPresence.state = buffer;\n    }\n    else if(!episodeName.empty())\n    {\n        SDL_snprintf(buffer, 128, \"Playing Episode: %s\", episodeName.c_str());\n        discordPresence.state = buffer;\n    }\n    else if(!editorFile.empty())\n    {\n        SDL_snprintf(buffer, 128, \"Editing file: %s\", editorFile.c_str());\n        discordPresence.state = buffer;\n    }\n\n    discordPresence.startTimestamp = time(0);\n    discordPresence.endTimestamp = 0;\n    discordPresence.largeImageKey = iconName.empty() ? \"thextech_default_icon\" : iconName.c_str();\n    discordPresence.largeImageText = \"TheXTech\";\n    discordPresence.smallImageKey = iconName.empty() ? \"\" : \"thextech_default_icon\";\n    discordPresence.smallImageText = gameName.c_str();\n    discordPresence.partyId = \"\";\n    discordPresence.partySize = 0;\n    discordPresence.partyMax = 0;\n    discordPresence.partyPrivacy = DISCORD_PARTY_PRIVATE;\n    discordPresence.matchSecret = \"\";\n    discordPresence.joinSecret = \"\";\n    discordPresence.spectateSecret = \"\";\n    discordPresence.instance = 0;\n    Discord_UpdatePresence(&discordPresence);\n    sync();\n}\n\nvoid DiscorcRPC::clear()\n{\n    pLogDebug(\"Discord: Clearing the presence...\");\n    Discord_ClearPresence();\n    sync();\n}\n"
  },
  {
    "path": "lib/Integrator/int_discorcrpc.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef DISCORCRPC_H\n#define DISCORCRPC_H\n\n#include <stdint.h>\n#include <string>\n\nclass DiscorcRPC\n{\n    int FrustrationLevel = 0;\n    int SendPresence = 1;\n    uint32_t m_last_sync = 0;\n\n    std::string episodeName;\n    std::string levelName;\n    std::string editorFile;\n\n    std::string gameName;\n    std::string iconName;\n\n    void clearAllLabels();\n\npublic:\n    DiscorcRPC();\n    ~DiscorcRPC();\n\n    void init();\n    void quit();\n\n    void setGameName(const std::string &game);\n    void setEpisodeName(const std::string &ep);\n    void setLevelName(const std::string &lev);\n    void setEditorFile(const std::string &fil);\n    void setIconName(const std::string &icon);\n\n    void sync();\n\n    void update();\n    void clear();\n};\n\n#endif // DISCORCRPC_H\n"
  },
  {
    "path": "lib/Integrator/integrator.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"integrator.h\"\n#include \"config.h\"\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n#include \"int_discorcrpc.h\"\nstatic DiscorcRPC s_discord;\nstatic bool s_discordLoaded = false;\n#endif\n\nstatic bool s_integrationsLoaded = false;\nstatic std::string s_prevEpisodeName;\nstatic std::string s_prevLevelName;\nstatic std::string s_prevEditorFile;\n\nstatic void clearAllPrev()\n{\n    s_prevEpisodeName.clear();\n    s_prevLevelName.clear();\n    s_prevEditorFile.clear();\n}\n\nvoid Integrator::initIntegrations()\n{\n    if(s_integrationsLoaded)\n        return;\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(g_config.discord_rpc)\n    {\n        s_discord.init();\n        s_discordLoaded = true;\n    }\n#endif\n\n    s_integrationsLoaded = true;\n}\n\nvoid Integrator::quitIntegrations()\n{\n    if(!s_integrationsLoaded)\n        return;\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(s_discordLoaded)\n    {\n        s_discord.clear();\n        s_discord.quit();\n    }\n#endif\n\n    s_integrationsLoaded = false;\n}\n\nvoid Integrator::setGameName(const std::string& gameName, const std::string& iconName)\n{\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(s_discordLoaded)\n    {\n        s_discord.setGameName(gameName);\n        if(!iconName.empty())\n            s_discord.setIconName(iconName);\n        s_discord.update();\n    }\n#endif\n}\n\nvoid Integrator::setEpisodeName(const std::string& episodeName)\n{\n    if(s_prevEpisodeName == episodeName)\n        return;\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(s_discordLoaded)\n    {\n        s_discord.setEpisodeName(episodeName);\n        s_discord.update();\n    }\n#endif\n\n    clearAllPrev();\n    s_prevEpisodeName = episodeName;\n}\n\nvoid Integrator::clearEpisodeName()\n{\n    if(s_prevEpisodeName.empty())\n        return;\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(s_discordLoaded)\n    {\n        s_discord.setEpisodeName(std::string());\n        s_discord.update();\n    }\n#endif\n\n    s_prevEpisodeName.clear();\n}\n\nvoid Integrator::setLevelName(const std::string& levelName)\n{\n    if(s_prevLevelName == levelName)\n        return;\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(s_discordLoaded)\n    {\n        s_discord.setLevelName(levelName);\n        s_discord.update();\n    }\n#endif\n\n    clearAllPrev();\n    s_prevLevelName = levelName;\n}\n\nvoid Integrator::clearLevelName()\n{\n    if(s_prevLevelName.empty())\n        return;\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(s_discordLoaded)\n    {\n        s_discord.setLevelName(std::string());\n        s_discord.update();\n    }\n#endif\n\n    s_prevLevelName.clear();\n}\n\nvoid Integrator::setEditorFile(const std::string& editorFile)\n{\n    if(s_prevEditorFile == editorFile)\n        return;\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(s_discordLoaded)\n    {\n        s_discord.setEditorFile(editorFile);\n        s_discord.update();\n    }\n#endif\n\n    clearAllPrev();\n    s_prevEditorFile = editorFile;\n}\n\nvoid Integrator::clearEditorFile()\n{\n    if(s_prevEditorFile.empty())\n        return;\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(s_discordLoaded)\n    {\n        s_discord.setEditorFile(std::string());\n        s_discord.update();\n    }\n#endif\n\n    s_prevEditorFile.clear();\n}\n\nvoid Integrator::sync()\n{\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    if(s_discordLoaded)\n        s_discord.sync();\n#endif\n}\n"
  },
  {
    "path": "lib/Integrator/integrator.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef INTEGRATOR_H\n#define INTEGRATOR_H\n\n#include <string>\n\nnamespace Integrator\n{\n#ifdef ENABLE_XTECH_INTEGRATOR\n\nvoid initIntegrations();\nvoid quitIntegrations();\n\nvoid setGameName(const std::string &gameName, const std::string &iconName);\n\nvoid setEpisodeName(const std::string &episodeName);\nvoid clearEpisodeName();\n\nvoid setLevelName(const std::string &levelName);\nvoid clearLevelName();\n\nvoid setEditorFile(const std::string &editorFile);\nvoid clearEditorFile();\n\nvoid sync();\n\n#else\n\n// Empty calls that does nothing\n\nstatic inline void initIntegrations() {}\nstatic inline void quitIntegrations() {}\n\nstatic inline void setGameName(const std::string &, const std::string &) {}\n\nstatic inline void setEpisodeName(const std::string &) {}\nstatic inline void clearEpisodeName() {}\n\nstatic inline void setLevelName(const std::string &) {}\nstatic inline void clearLevelName() {}\n\nstatic inline void setEditorFile(const std::string &) {}\nstatic inline void clearEditorFile() {}\n\nstatic inline void sync() {}\n\n#endif\n}\n\n#endif // INTEGRATOR_H\n"
  },
  {
    "path": "lib/InterProcess/editor_pipe.cpp",
    "content": "#include \"editor_pipe.h\"\n#include \"../Logger/logger.h\"\n#include \"../util.h\"\n\n#include <Utils/files.h>\n#include <Utils/elapsed_timer.h>\n#include <Utils/strings.h>\n\n#include <iostream>\n#include <cstdio>\n#include \"../AppPath/app_path.h\"\n#include \"intproc.h\"\n#include <fmt_format_ne.h>\n\n#include \"main/game_strings.h\"\n\n/**************************************************************************************************************/\n\nint EditorPipe::run(void *self)\n{\n    EditorPipe &me = *reinterpret_cast<EditorPipe *>(self);\n\n    while(me.m_thread_isAlive)\n    {\n        std::string buffer;\n        std::string out;\n        std::cin >> buffer;\n\n        if(me.m_thread_isAlive)\n        {\n            util::base64_decode(out, buffer);\n            me.icomingData(out);\n        }\n    }\n\n    return 0;\n}\n\nvoid EditorPipe::start()\n{\n    std::cin.sync_with_stdio(false);\n#ifndef PGE_NO_THREADING\n    m_thread_isAlive = true;\n    m_thread = SDL_CreateThread(&run, \"EditorPipe_std\", this);\n    SDL_DetachThread(m_thread);\n#endif\n}\n\nvoid EditorPipe::stop()\n{\n    m_thread_isAlive = false;\n    //int status = 0;\n    //SDL_WaitThread(m_thread, &status);\n}\n\nvoid EditorPipe::sendMessage(const std::string &in)\n{\n    std::string outO;\n    util::base64_encode(outO, reinterpret_cast<const unsigned char *>(in.c_str()), in.size());\n    D_pLogDebug(\"EditorPipe::sendMessage: sending data [%s]\", outO.c_str());\n    std::fprintf(stdout, \"%s\\n\", outO.c_str());\n    std::fflush(stdout);\n}\n\nvoid EditorPipe::sendMessage(const char *in)\n{\n    std::string outO;\n    util::base64_encode(outO, reinterpret_cast<const unsigned char *>(in), strlen(in));\n    D_pLogDebug(\"EditorPipe::sendMessage: sending data [%s]\", outO.c_str());\n    std::fprintf(stdout, \"%s\\n\", outO.c_str());\n    std::fflush(stdout);\n}\n/**************************************************************************************************************/\n\n\n/**\n * @brief EditorPipe::LocalServer\n *  Constructor\n */\n\nEditorPipe::EditorPipe():\n    m_thread(nullptr),\n    m_thread_isAlive(false),\n    m_isWorking(false),\n    m_doAcceptLevelData(false),\n    m_doParseLevelData(false),\n    m_levelAccepted(false)\n{\n    m_acceptedRawData.clear();\n    pLogDebug(\"Construct interprocess pipe...\");\n    start();\n}\n\n/**\n * @brief EditorPipe::~LocalServer\n *  Destructor\n */\nEditorPipe::~EditorPipe()\n{\n    pLogDebug(\"Destroying interprocess pipe...\");\n    stop();\n}\n\nvoid EditorPipe::sendStarsNumber(int numStars)\n{\n    sendMessage(fmt::sprintf_ne(\"CMD:NUM_STARS %d\", numStars));\n}\n\nvoid EditorPipe::sendTakenBlock(const LevelBlock &block)\n{\n    std::string encoded;\n    LevelData buffer;\n    FileFormats::CreateLevelData(buffer);\n    buffer.blocks.push_back(block);\n    buffer.layers.clear();\n    buffer.events.clear();\n    if(FileFormats::WriteExtendedLvlFileRaw(buffer, encoded))\n        sendMessage(fmt::format_ne(\"CMD:TAKEN_BLOCK\\nTAKEN_BLOCK_END\\n{0}\", encoded));\n}\n\nvoid EditorPipe::sendTakenBGO(const LevelBGO &bgo)\n{\n    std::string encoded;\n    LevelData buffer;\n    FileFormats::CreateLevelData(buffer);\n    buffer.bgo.push_back(bgo);\n    buffer.layers.clear();\n    buffer.events.clear();\n    if(FileFormats::WriteExtendedLvlFileRaw(buffer, encoded))\n        sendMessage(fmt::format_ne(\"CMD:TAKEN_BGO\\nTAKEN_BGO_END\\n{0}\", encoded));\n}\n\nvoid EditorPipe::sendTakenNPC(const LevelNPC &npc)\n{\n    std::string encoded;\n    LevelData buffer;\n    FileFormats::CreateLevelData(buffer);\n    buffer.npc.push_back(npc);\n    buffer.layers.clear();\n    buffer.events.clear();\n    if(FileFormats::WriteExtendedLvlFileRaw(buffer, encoded))\n        sendMessage(fmt::format_ne(\"CMD:TAKEN_NPC\\nTAKEN_NPC_END\\n{0}\", encoded));\n}\n\nvoid EditorPipe::sendCloseProperties()\n{\n    sendMessage(\"CMD:CLOSE_PROPERTIES\");\n}\n\nvoid EditorPipe::sendPlayerSettings(int playerId, int character, int state, int vehicleID, int vehicleState)\n{\n    sendMessage(fmt::sprintf_ne(\"CMD:PLAYER_SETUP_UPDATE %d %d %d %d %d\", playerId, character, state, vehicleID, vehicleState));\n}\n\nvoid EditorPipe::sendPlayerSettings2(int playerId, int health, int reservedItem)\n{\n    sendMessage(fmt::sprintf_ne(\"CMD:PLAYER_SETUP_UPDATE2 %d %d %d\", playerId, health, reservedItem));\n}\n\nvoid EditorPipe::shut()\n{\n    sendMessage(\"CMD:ENGINE_CLOSED\");\n    stop();\n}\n\nbool EditorPipe::hasLevelData()\n{\n    m_levelAccepted_lock.lock();\n    bool state = m_levelAccepted;\n    m_levelAccepted = false;\n    m_levelAccepted_lock.unlock();\n    return state;\n}\n\nbool EditorPipe::levelReceivingInProcess()\n{\n    return m_doAcceptLevelData;\n}\n\nvoid EditorPipe::icomingData(const std::string &in)\n{\n    if(in.compare(0, 10, \"PARSE_LVLX\") == 0)\n    {\n        m_doAcceptLevelData = false;\n        pLogDebug(\"LVLX accepting is done!\");\n        IntProc::setState(g_gameStrings.ipcStatusDataAccepted);\n    }\n\n    if(m_doAcceptLevelData)\n    {\n        m_acceptedRawData.append(in);\n\n        if(m_doAcceptLevelDataParts)\n            sendMessage(\"LVLX_NEXT\\n\\n\");\n\n        pLogDebug(\"Append LVLX data...\");\n    }\n    else if(in.compare(0, 11, \"SEND_LVLX: \") == 0 || in.compare(0, 17, \"SEND_LVLX_PARTS: \") == 0)\n    {\n        m_doAcceptLevelDataParts = (in.compare(0, 17, \"SEND_LVLX_PARTS: \") == 0);\n        size_t offset = m_doAcceptLevelDataParts ? 17 : 11;\n        //Delete old cached stuff\n        m_acceptedRawData.clear();\n        D_pLogDebug(\"IN: >> %s\",\n                    (in.size() > 30 ?\n                     in.substr(0, 30).c_str() :\n                     in.c_str())\n                   );\n        m_accepted_lvl_path   = std::string(in.c_str() + offset, (in.size() - offset));//skip \"SEND_LVLX: \"\n        Strings::doTrim(m_accepted_lvl_path);\n        m_doAcceptLevelData  = true;\n        IntProc::setState(g_gameStrings.ipcStatusDataTransferStarted);\n        sendMessage(m_doAcceptLevelDataParts ? \"READY_PARTS\\n\\n\" : \"READY\\n\\n\");\n    }\n    else if(in.compare(0, 10, \"PARSE_LVLX\") == 0)\n    {\n        pLogDebug(\"do Parse LVLX: PARSE_LVLX\");\n        m_doParseLevelData = true;\n        m_doAcceptLevelDataParts = false;\n        m_acceptedLevel.open(&m_acceptedRawData, m_accepted_lvl_path);\n        IntProc::setState(g_gameStrings.ipcStatusDataValid);\n\n        m_levelAccepted_lock.lock();\n        m_levelAccepted = true;\n        m_levelAccepted_lock.unlock();\n    }\n    else if(in.compare(0, 11, \"PLACEITEM: \") == 0)\n    {\n        D_pLogDebugNA(\"Accepted Placing item!\");\n        IntProc::storeCommand(in.c_str() + 11, in.size() - 11, IntProc::PlaceItem);\n    }\n    else if(in.compare(0, 11, \"SET_LAYER: \") == 0)\n    {\n        D_pLogDebugNA(\"Accepted layer change!\");\n        IntProc::storeCommand(in.c_str() + 11, in.size() - 11, IntProc::SetLayer);\n    }\n    else if(in.compare(0, 14, \"SET_NUMSTARS: \") == 0)\n    {\n        D_pLogDebugNA(\"Accepted stars number change!\");\n        IntProc::storeCommand(in.c_str() + 14, in.size() - 14, IntProc::SetNumStars);\n    }\n    else if(in.compare(0, 8, \"MSGBOX: \") == 0)\n    {\n        pLogDebug(\"Accepted Message box: %s\", in.c_str());\n        IntProc::storeCommand(in.c_str() + 8, in.size() - 8, IntProc::MsgBox);\n    }\n    else if(in.compare(0, 7, \"CHEAT: \") == 0)\n    {\n        pLogDebug(\"Accepted cheat code: %s\", in.c_str());\n        IntProc::storeCommand(in.c_str() + 7, in.size() - 7, IntProc::Cheat);\n    }\n    else if(in.compare(0, 4, \"PING\") == 0)\n    {\n        D_pLogDebugNA(\"IN: >> PING\");\n        sendMessage(\"PONG\\n\\n\");\n        pLogDebug(\"Ping-Pong!\");\n    }\n}\n"
  },
  {
    "path": "lib/InterProcess/editor_pipe.h",
    "content": "#ifndef EDITOR_PIPE_H\n#define EDITOR_PIPE_H\n\n#include <SDL2/SDL_thread.h>\n#include <vector>\n#include <atomic>\n#include <mutex>\n#include <PGE_File_Formats/file_formats.h>\n\nclass EditorPipe\n{\n    friend class EditorPipe_std;\n    SDL_Thread *m_thread = nullptr;\n    bool        m_thread_isAlive = false;\npublic:\n    EditorPipe();\n    ~EditorPipe();\n    void sendStarsNumber(int numStars);\n    void sendTakenBlock(const LevelBlock &block);\n    void sendTakenBGO(const LevelBGO &bgo);\n    void sendTakenNPC(const LevelNPC &npc);\n    void sendCloseProperties();\n    void sendPlayerSettings(int playerId, int character, int state, int vehicleID, int vehicleState);\n    void sendPlayerSettings2(int playerId, int health, int reservedItem);\n    void shut();\n\n    bool        m_isWorking;\n\n    static int run(void *self);\n    static int sendMessageAsync(void *outString);\n    void start();\n    void stop();\n\n    using RawTextInput = PGE_FileFormats_misc::RawTextInput;\n\n    // SEND_LVLX: /some/path/to/level file\\n\\n\n    std::string     m_accepted_lvl_path; // Send to client the \"READY\\n\\n\" before accent raw data\n    bool            m_doAcceptLevelData = false;\n    bool            m_doAcceptLevelDataParts = false;\n    std::string     m_acceptedRawData;  // accept any raw data before will be accepted '\\n\\n'\n    bool            m_doParseLevelData = false;\n    RawTextInput    m_acceptedLevel;    // When accepted PARSE_LVLX\\n\\n, parse data and call signal\n\n    bool hasLevelData();\n    bool levelReceivingInProcess();\n\n    void sendMessage(const std::string &in);\n    void sendMessage(const char *in);\n\nprivate:\n    std::atomic_bool    m_levelAccepted;\n    std::mutex          m_levelAccepted_lock;\n\n    void icomingData(const std::string &in);\n};\n\n#endif // EDITOR_PIPE_H\n"
  },
  {
    "path": "lib/InterProcess/intproc.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include \"intproc.h\"\n#include <SDL2/SDL_atomic.h>\n#include \"../Logger/logger.h\"\n\nEditorPipe              *IntProc::editor = nullptr;\nstatic bool             s_ipc_enabled = false;\nstatic SDL_atomic_t     s_has_command;\n\nstatic std::string      s_state;\nstatic std::mutex       s_state_lock;\n\nstatic IntProc::ExternalCommands        s_cmd_recentType = IntProc::MsgBox;\n\nstatic std::deque<IntProc::cmdEntry>    s_cmd_queue;\nstatic std::mutex                       s_cmd_mutex;\n\nvoid IntProc::init()\n{\n    SDL_AtomicSet(&s_has_command, 0);\n    pLogDebug(\"IntProc constructing...\");\n    editor = new EditorPipe();\n    editor->m_isWorking = true;\n    pLogDebug(\"IntProc started!\");\n    s_ipc_enabled = true;\n}\n\nvoid IntProc::quit()\n{\n    if(editor != nullptr)\n    {\n        editor->m_isWorking = false;\n        editor->shut();\n        delete editor;\n    }\n\n    editor  = nullptr;\n    s_ipc_enabled = false;\n}\n\nbool IntProc::isEnabled()\n{\n    return s_ipc_enabled;\n}\n\nbool IntProc::isWorking()\n{\n    return (editor != nullptr);\n}\n\nstd::string IntProc::getState()\n{\n    s_state_lock.lock();\n    std::string tmp = s_state;\n    s_state_lock.unlock();\n    return tmp;\n}\n\nvoid IntProc::setState(const std::string &instate)\n{\n    s_state_lock.lock();\n    s_state = instate;\n    s_state_lock.unlock();\n}\n\nvoid IntProc::storeCommand(const char *cmd, size_t length, IntProc::ExternalCommands type)\n{\n    s_cmd_mutex.lock();\n    std::string in;\n    in.reserve(length);\n    bool escape = false;\n\n    for(size_t i = 0; i < length; i++)\n    {\n        const char &c = cmd[i];\n\n        if(escape)\n        {\n            if(c == 'n')\n                in.push_back('\\n');\n\n            escape = false;\n        }\n        else if(c == '\\\\')\n        {\n            escape = true;\n            continue;\n        }\n        else\n            in.push_back(c);\n    }\n\n    s_cmd_queue.push_back({in, type});\n    s_cmd_recentType = type;\n    SDL_AtomicSet(&s_has_command, 1);\n    s_cmd_mutex.unlock();\n}\n\nvoid IntProc::cmdLock()\n{\n    s_cmd_mutex.lock();\n}\n\nvoid IntProc::cmdUnLock()\n{\n    s_cmd_mutex.unlock();\n}\n\nbool IntProc::hasCommand()\n{\n    return SDL_AtomicGet(&s_has_command);\n}\n\nIntProc::ExternalCommands IntProc::commandType()\n{\n    return s_cmd_recentType;\n}\n\nstd::string IntProc::getCMD()\n{\n    cmdEntry tmp = s_cmd_queue.front();\n    s_cmd_queue.pop_front();\n    if(!s_cmd_queue.empty())\n        s_cmd_recentType = s_cmd_queue.front().type;\n    else\n        SDL_AtomicSet(&s_has_command, 0);\n    return tmp.cmd;\n}\n\nbool IntProc::sendMessage(const std::string &command)\n{\n    if(!editor)\n    {\n        pLogWarning(\"IntProc::sendMessage: `editor` is not initialized!\");\n        return false;\n    }\n\n    editor->sendMessage(command);\n    return true;\n}\n\nbool IntProc::sendMessageS(const std::string &command)\n{\n    if(!editor)\n    {\n        pLogWarning(\"IntProc::sendMessageS: `editor` is not initialized!\");\n        return false;\n    }\n\n    editor->sendMessage(command);\n    return true;\n}\n\nbool IntProc::sendMessage(const char *command)\n{\n    if(!editor)\n    {\n        pLogWarning(\"IntProc::sendMessage: `editor` is not initialized!\");\n        return false;\n    }\n\n    editor->sendMessage(command);\n    return true;\n}\n\nbool IntProc::hasLevelData()\n{\n    if(!editor)\n        return false;\n    return editor->hasLevelData();\n}\n\nbool IntProc::levelReceivingInProcess()\n{\n    if(!editor)\n        return false;\n    return editor->levelReceivingInProcess();\n}\n\nvoid IntProc::sendStarsNumber(int numStars)\n{\n    if(!editor)\n        return;\n    editor->sendStarsNumber(numStars);\n}\n\nvoid IntProc::sendTakenBlock(const LevelBlock &block)\n{\n    if(!editor)\n        return;\n    editor->sendTakenBlock(block);\n}\n\nvoid IntProc::sendTakenBGO(const LevelBGO &bgo)\n{\n    if(!editor)\n        return;\n    editor->sendTakenBGO(bgo);\n}\n\nvoid IntProc::sendTakenNPC(const LevelNPC &npc)\n{\n    if(!editor)\n        return;\n    editor->sendTakenNPC(npc);\n}\n\nvoid IntProc::sendPlayerSettings(int playerId, int character, int state, int vehicleID, int vehicleState)\n{\n    if(!editor)\n        return;\n    editor->sendPlayerSettings(playerId, character, state, vehicleID, vehicleState);\n}\n\nvoid IntProc::sendPlayerSettings2(int playerId, int health, int reservedItem)\n{\n    if(!editor)\n        return;\n    editor->sendPlayerSettings2(playerId, health, reservedItem);\n}\n\nvoid IntProc::sendCloseProperties()\n{\n    if(!editor)\n        return;\n    editor->sendCloseProperties();\n}\n\n"
  },
  {
    "path": "lib/InterProcess/intproc.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef INTPROC_H\n#define INTPROC_H\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n\n#include <mutex>\n#include <deque>\n#include <string>\n#include \"editor_pipe.h\"\n\nnamespace IntProc\n{\n    void init();\n    void quit();\n    bool isEnabled();\n    bool isWorking();\n\n    void sendStarsNumber(int numStars);\n    void sendTakenBlock(const LevelBlock &block);\n    void sendTakenBGO(const LevelBGO &bgo);\n    void sendTakenNPC(const LevelNPC &npc);\n    void sendPlayerSettings(int playerId, int character, int state, int vehicleID, int vehicleState);\n    void sendPlayerSettings2(int playerId, int health, int reservedItem);\n    void sendCloseProperties();\n\n    std::string getState();\n    void        setState(const std::string &instate);\n\n    enum ExternalCommands\n    {\n        //! Show messag ebox\n        MsgBox = 0,\n        //! Cheat code proxy\n        Cheat = 1,\n        //! Place item (magic-hand only)\n        PlaceItem = 2,\n        //! Toggle a name of current\n        SetLayer = 3,\n        //! Set number of taken stars\n        SetNumStars = 4\n    };\n\n    struct cmdEntry\n    {\n        std::string cmd;\n        ExternalCommands type;\n    };\n\n    /**\n     * @brief IPC interface has accepted level data\n     * @return true if IPC interface keeps a complete level data buffer\n     */\n    bool hasLevelData();\n    /**\n     * @brief Is level data in process of receiving\n     * @return true if receiving is in process\n     */\n    bool levelReceivingInProcess();\n\n    bool sendMessage(const char *command);\n    bool sendMessageS(const std::string &command);\n    bool sendMessage(const std::string &command);\n\n    void             storeCommand(const char *cmd, size_t length, ExternalCommands type);\n    void             cmdLock();\n    void             cmdUnLock();\n    bool             hasCommand();\n    ExternalCommands commandType();\n    std::string getCMD();\n\n    extern EditorPipe *editor;\n\n}// namespace IntProc\n\n#endif // THEXTECH_INTERPROC_SUPPORTED\n\n#endif // INTPROC_H\n"
  },
  {
    "path": "lib/Logger/logger.cmake",
    "content": "set(LOGGER_SRCS)\n\nlist(APPEND LOGGER_SRCS\n    ${CMAKE_CURRENT_LIST_DIR}/logger.h\n    ${CMAKE_CURRENT_LIST_DIR}/private/logger.cpp\n    ${CMAKE_CURRENT_LIST_DIR}/private/logger_sets.h\n    ${CMAKE_CURRENT_LIST_DIR}/private/logger_private.h\n)\n\nif(ANDROID)\n    message(\"-- Logger for Android\")\n    list(APPEND LOGGER_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/logger_android.cpp\n    )\nelseif(EMSCRIPTEN)\n    message(\"-- Logger for Emscripten\")\n    list(APPEND LOGGER_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/logger_emscripten.cpp\n    )\nelseif(VITA)\n    message(\"-- Logger for PS Vita\")\n    list(APPEND LOGGER_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/logger_vita.cpp\n    )\nelseif(NINTENDO_3DS OR NINTENDO_WII OR THEXTECH_NO_SDL_BUILD OR PGE_MIN_PORT)\n    message(\"-- Logger for Minimal Port (no SDL)\")\n    list(APPEND LOGGER_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/logger_min.cpp\n    )\nelseif(NINTENDO_WIIU)\n    message(\"-- Logger for WiiU\")\n    list(APPEND LOGGER_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/logger_wiiu.cpp\n    )\nelse()\n    message(\"-- Logger for Desktop\")\n    list(APPEND LOGGER_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/private/logger_desktop.cpp\n    )\nendif()\n"
  },
  {
    "path": "lib/Logger/logger.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LOGGER_H\n#define LOGGER_H\n\n#ifdef __cplusplus\n#include <string>\n\n#ifdef _WIN32\n#   define OS_NEWLINE \"\\r\\n\"\n#else\n#   define OS_NEWLINE \"\\n\"\n#endif\n\n#include \"logger_level.h\"\n\n#if defined(__GNUC__) || defined(__clang__)\n#   define FORMAT_ATTRIBUTE_PRINTF_12  __attribute__ ((format (printf, 1, 2)))\n#else\n#   define FORMAT_ATTRIBUTE_PRINTF_12\n#endif\n\nextern struct PGE_LogSetup\n{\n    //! The logging level\n    PGE_LogLevel::Level level = PGE_LogLevel::Debug;\n    //! Maximum number of log files in the directory\n    int maxFilesCount = 10;\n    //! Default logs directory\n    std::string logPathDefault;\n    //! Fallback log directory when default log directory is inaccessible\n    std::string logPathFallBack;\n    //! Custom log directory, specified by user\n    std::string logPathCustom;\n} g_pLogGlobalSetup;\n\nextern void LoadLogSettings(bool disableStdOut = false, bool verboseLogs = false);\nextern void CloseLog();\n#endif//__cplusplus\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\nextern void pLogDebug(const char *format, ...) FORMAT_ATTRIBUTE_PRINTF_12;\nextern void pLogWarning(const char *format, ...) FORMAT_ATTRIBUTE_PRINTF_12;\nextern void pLogCritical(const char *format, ...) FORMAT_ATTRIBUTE_PRINTF_12;\nextern void pLogInfo(const char *format, ...) FORMAT_ATTRIBUTE_PRINTF_12;\nextern void pLogFatal(const char *format, ...) FORMAT_ATTRIBUTE_PRINTF_12;\n\n#define pLogDebugS(str) pLogDebug(\"%s\", str)\n#define pLogWarningS(str) pLogWarning(\"%s\", str)\n#define pLogCriticalS(str) pLogCritical(\"%s\", str)\n#define pLogInfoS(str) pLogInfo(\"%s\", str)\n#define pLogFatalS(str) pLogFatal(\"%s\", str)\n\n#ifdef __cplusplus\n}\n#endif\n\n#ifdef __cplusplus\nextern void pLogDebug(const std::string &line);\nextern void pLogWarning(const std::string &line);\nextern void pLogCritical(const std::string &line);\nextern void pLogInfo(const std::string &line);\nextern void pLogFatal(const std::string &line);\nextern std::string getLogFilePath();\n#endif\n\n#ifdef DEBUG_BUILD\n// Variadic with arguments\n#define D_pLogDebug(fmt, ...) pLogDebug(fmt, __VA_ARGS__)\n#define D_pLogWarning(fmt, ...) pLogWarning(fmt, __VA_ARGS__)\n#define D_pLogCritical(fmt, ...) pLogCritical(fmt, __VA_ARGS__)\n#define D_pLogInfo(fmt, ...) pLogInfo(fmt, __VA_ARGS__)\n#define D_pLogFatal(fmt, ...) pLogFatal(fmt, __VA_ARGS__)\n// Standard without arguments to avoid \"ISO C++11 requires at least one argument of the '...' in a variadic macro\"\n#define D_pLogDebugNA(fmt) pLogDebug(fmt)\n#define D_pLogWarningNA(fmt) pLogWarning(fmt)\n#define D_pLogCriticalNA(fmt) pLogCritical(fmt)\n#define D_pLogInfoNA(fmt) pLogInfo(fmt)\n#define D_pLogFatalNA(fmt) pLogFatal(fmt)\n#else\n#define D_pLogDebug(fmt, ...) (void)0;\n#define D_pLogWarning(fmt, ...) (void)0;\n#define D_pLogCritical(fmt, ...) (void)0;\n#define D_pLogFatal(fmt, ...) (void)0;\n#define D_pLogInfo(fmt, ...) (void)0;\n#define D_pLogDebugNA(fmt) (void)0;\n#define D_pLogWarningNA(fmt) (void)0;\n#define D_pLogCriticalNA(fmt) (void)0;\n#define D_pLogFatalNA(fmt) (void)0;\n#define D_pLogInfoNA(fmt) (void)0;\n#endif\n\n#endif // LOGGER_H\n"
  },
  {
    "path": "lib/Logger/logger_level.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LOGGER_LEVEL_H\n#define LOGGER_LEVEL_H\n\nstruct PGE_LogLevel\n{\n    enum Level : int\n    {\n        Debug    = 5,\n        Info     = 4,\n        Warning  = 3,\n        Critical = 2,\n        Fatal    = 1,\n        NoLog    = 0,\n    };\n};\n\n#endif // LOGGER_LEVEL_H\n"
  },
  {
    "path": "lib/Logger/private/logger.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <stdarg.h>\n#include \"../logger.h\"\n#include \"logger_sets.h\"\n\n#ifndef NO_FILE_LOGGING\n#   include <chrono>  // chrono::system_clock\n#   include <ctime>   // localtime\n#   include <DirManager/dirman.h>\n#   include <Utils/files.h>\n#   include <sorting/tinysort.h>\n#endif\n\n#include <fmt_format_ne.h>\n#include <fmt/fmt_printf.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_timer.h\"\n\n#ifndef MOONDUST_LOGGER_FILENAME_PREFIX\n#   error Please define the \"-DMOONDUST_LOGGER_FILENAME_PREFIX=<name-of-application>\" to specify the log filename prefix\n#endif\n\nPGE_LogSetup    g_pLogGlobalSetup;\n\nstd::string     LogWriter::m_logDirPath;\nstd::string     LogWriter::m_logFilePath;\nPGE_LogLevel::Level LogWriter::m_logLevel = PGE_LogLevel::NoLog;\nint             LogWriter::m_maxFilesCount = 10;\nbool            LogWriter::m_enabled = false;\nbool            LogWriter::m_enabledStdOut = false;\nbool            LogWriter::m_enabledVerboseLogs = false;\nuint64_t        LogWriter::m_appStartTicks = 0;\n\nstatic std::string getRunTimeString()\n{\n    uint64_t ticks_fms = SDL_GetTicks64() - LogWriter::m_appStartTicks;\n    uint64_t ticks_fs = ticks_fms / 1000;\n    uint64_t ticks_fm = ticks_fs / 60;\n    uint64_t ticks_h = ticks_fm / 60;\n\n    uint64_t ticks_ms = ticks_fms % 1000;\n    uint64_t ticks_s = ticks_fs % 60;\n    uint64_t ticks_m = ticks_fm % 60;\n\n    if(ticks_h > 0)\n        return fmt::sprintf_ne(\"%02d:%02d:%02d.%03d\",\n                               (int)ticks_h, (int)ticks_m, (int)ticks_s, (int)ticks_ms);\n    else\n        return fmt::sprintf_ne(\"%02d:%02d.%03d\",\n                               (int)ticks_m, (int)ticks_s, (int)ticks_ms);\n}\n\n#if !defined(NO_FILE_LOGGING)\nstatic std::string return_current_time_and_date()\n{\n    auto now = std::chrono::system_clock::now();\n    auto in_time_t = std::chrono::system_clock::to_time_t(now);\n\n#ifdef PGE_MIN_PORT\n    // chrono doesn't work reliably here\n    time(&in_time_t);\n#endif\n\n    char out[24];\n    SDL_memset(out, 0, sizeof(out));\n    if(0 < strftime(out, sizeof(out), \"%Y_%m_%d_%H_%M_%S\", std::localtime(&in_time_t)))\n        return std::string(out);\n    else\n        return \"0000_00_00_00_00_00\";\n}\n\nstatic void cleanUpLogs(const std::string &logsPath, int maxLogs)\n{\n    if(maxLogs <= 0)\n        return; // No limit\n\n    DirMan d(logsPath);\n    std::vector<std::string> files, filesPre;\n    d.getListOfFiles(filesPre, {\".txt\"});\n\n    // Be sure that we are looking for our log files and don't touch others\n    for(auto &s : filesPre)\n    {\n        if(SDL_strncasecmp(s.c_str(), MOONDUST_LOGGER_FILENAME_PREFIX \"_log_\", 13) == 0)\n            files.push_back(s);\n    }\n\n    // nothing to do, count of log files is fine\n    if(static_cast<int>(files.size()) <= (maxLogs - 1))\n        return;\n\n    // Sort array\n    tinysort(files.begin(), files.end());\n\n    // Keep these files (remove from a deletion list)\n    files.erase(files.end() - (maxLogs - 1), files.end());\n\n    for(auto &s : files)\n        Files::deleteFile(logsPath + \"/\" + s);\n}\n#endif // !NO_FILE_LOGGING\n\n\nvoid LoadLogSettings(bool disableStdOut, bool verboseLogs)\n{\n#if defined(DEBUG_BUILD) || defined(__WIIU__)\n    (void)verboseLogs; // unused\n    LogWriter::m_enabledVerboseLogs = true; // Enforce verbose log for debug builds or on some platforms like WiiU\n#else\n    LogWriter::m_enabledVerboseLogs = verboseLogs;\n#endif\n\n    LogWriter::m_enabledStdOut = !disableStdOut;\n    LogWriter::m_logLevel = g_pLogGlobalSetup.level;\n\n#if !defined(NO_FILE_LOGGING)\n    std::string logFileName = fmt::format_ne(MOONDUST_LOGGER_FILENAME_PREFIX \"_log_{0}.txt\", return_current_time_and_date());\n#endif\n\n    LogWriter::m_appStartTicks = SDL_GetTicks64();\n\n#if !defined(NO_FILE_LOGGING)\n    const std::string logPath = g_pLogGlobalSetup.logPathDefault;\n    DirMan defLogDir(logPath);\n\n    if(!defLogDir.exists())\n    {\n        if(!defLogDir.mkpath())\n            defLogDir.setPath(g_pLogGlobalSetup.logPathFallBack);\n    }\n#endif // !NO_FILE_LOGGING\n\n#if !defined(NO_FILE_LOGGING)\n    LogWriter::m_logDirPath = g_pLogGlobalSetup.logPathCustom.empty() ? defLogDir.absolutePath() : g_pLogGlobalSetup.logPathCustom;\n    LogWriter::m_maxFilesCount = g_pLogGlobalSetup.maxFilesCount;\n\n    if(!DirMan::exists(LogWriter::m_logDirPath))\n        LogWriter::m_logDirPath = defLogDir.absolutePath();\n#endif // !NO_FILE_LOGGING\n\n    LogWriter::m_logLevel = g_pLogGlobalSetup.level;\n    LogWriter::m_enabled   = (LogWriter::m_logLevel != PGE_LogLevel::NoLog);\n\n#if !defined(NO_FILE_LOGGING)\n    LogWriter::m_logFilePath = LogWriter::m_logDirPath + \"/\" + logFileName;\n    cleanUpLogs(LogWriter::m_logDirPath, LogWriter::m_maxFilesCount);\n#else\n    LogWriter::m_logFilePath.clear();\n#endif // !NO_FILE_LOGGING\n\n    LogWriter::OpenLogFile();\n}\n\nvoid CloseLog()\n{\n    LogWriter::CloseLog();\n}\n\nstd::string getLogFilePath()\n{\n#ifdef NO_FILE_LOGGING\n    return \"<No log file: Logging was been disabled in this build>\";\n#else\n    return LogWriter::m_logFilePath;\n#endif\n}\n\nstatic inline void pLogGeneric(int level, const char *label, const char *format, va_list arg)\n{\n    if(LogWriter::m_enabledStdOut && LogWriter::m_enabledVerboseLogs)\n        LoggerPrivate_pLogConsole(level, label, format, arg);\n\n#ifndef NO_FILE_LOGGING\n    if(LogWriter::m_logLevel >= level)\n    {\n        auto t = getRunTimeString();\n        LoggerPrivate_pLogFile(level, label, t.c_str(), format, arg);\n    }\n#endif\n}\n\nvoid pLogDebug(const char *format, ...)\n{\n    va_list arg;\n    va_start(arg, format);\n    pLogGeneric(PGE_LogLevel::Debug, \"Debug\", format, arg);\n    va_end(arg);\n}\n\nvoid pLogWarning(const char *format, ...)\n{\n    va_list arg;\n    va_start(arg, format);\n    pLogGeneric(PGE_LogLevel::Warning, \"Warning\", format, arg);\n    va_end(arg);\n}\n\nvoid pLogCritical(const char *format, ...)\n{\n    va_list arg;\n    va_start(arg, format);\n    pLogGeneric(PGE_LogLevel::Critical, \"Critical\", format, arg);\n    va_end(arg);\n}\n\nvoid pLogFatal(const char *format, ...)\n{\n    va_list arg;\n    va_start(arg, format);\n    pLogGeneric(PGE_LogLevel::Fatal, \"Fatal\", format, arg);\n    va_end(arg);\n}\n\nvoid pLogInfo(const char *format, ...)\n{\n    va_list arg;\n    va_start(arg, format);\n    pLogGeneric(PGE_LogLevel::Info, \"Info\", format, arg);\n    va_end(arg);\n}\n\nvoid pLogDebug(const std::string &line)\n{\n    pLogDebug(\"%s\", line.c_str());\n}\n\nvoid pLogWarning(const std::string &line)\n{\n    pLogWarning(\"%s\", line.c_str());\n}\n\nvoid pLogCritical(const std::string &line)\n{\n    pLogCritical(\"%s\", line.c_str());\n}\n\nvoid pLogInfo(const std::string &line)\n{\n    pLogInfo(\"%s\", line.c_str());\n}\n\nvoid pLogFatal(const std::string &line)\n{\n    pLogFatal(\"%s\", line.c_str());\n}\n"
  },
  {
    "path": "lib/Logger/private/logger_android.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#define LOGGER_INTERNAL\n#include \"logger_sets.h\"\n#include \"logger_private.h\"\n\n#include <mutex>\n#include <SDL2/SDL_rwops.h>\n\n#include <android/log.h>\n\n#ifndef NO_FILE_LOGGING\nstatic std::mutex g_lockLocker;\n#   define OUT_BUFFER_SIZE 10240\nstatic char       g_outputBuffer[OUT_BUFFER_SIZE];\n#   define OUT_BUFFER_STRING_SIZE 10239\n//! Output file\nstatic SDL_RWops *s_logout = nullptr;\n#endif // NO_FILE_LOGGING\n\nvoid LogWriter::OpenLogFile()\n{\n#ifndef NO_FILE_LOGGING\n    MUTEXLOCK(mutex);\n\n    if(LogWriter::m_enabledStdOut)\n        __android_log_print(ANDROID_LOG_ERROR, \"TRACKERS\", \"LogLevel %d, log file %s\\n\\n\", m_logLevel, m_logFilePath.c_str());\n\n    if(m_enabled)\n    {\n        s_logout = SDL_RWFromFile(m_logFilePath.c_str(), \"a\");\n        if(!s_logout)\n            __android_log_print(ANDROID_LOG_ERROR, \"TRACKERS\", \"Impossible to open %s for write, log printing into the file is disabled!\\n\", m_logFilePath.c_str());\n    }\n#endif // NO_FILE_LOGGING\n}\n\nvoid LogWriter::CloseLog()\n{\n#ifndef NO_FILE_LOGGING\n    MUTEXLOCK(mutex);\n    if(s_logout)\n        SDL_RWclose(s_logout);\n    s_logout = nullptr;\n#endif // NO_FILE_LOGGING\n}\n\nstatic int pgeToAndroidLL(int level)\n{\n    switch(level)\n    {\n    case PGE_LogLevel::NoLog:\n        return ANDROID_LOG_INFO;\n    case PGE_LogLevel::Fatal:\n        return ANDROID_LOG_FATAL;\n    default:\n    case PGE_LogLevel::Info:\n        return ANDROID_LOG_INFO;\n    case PGE_LogLevel::Critical:\n        return ANDROID_LOG_ERROR;\n    case PGE_LogLevel::Warning:\n        return ANDROID_LOG_WARN;\n    case PGE_LogLevel::Debug:\n        return ANDROID_LOG_DEBUG;\n    }\n}\n\nvoid LoggerPrivate_pLogConsole(int level, const char *label, const char *format, va_list arg)\n{\n    (void)label;\n    va_list arg_in;\n    va_copy(arg_in, arg);\n    __android_log_vprint(pgeToAndroidLL(level), \"TRACKERS\", format, arg_in);\n    va_end(arg_in);\n}\n\n#ifndef NO_FILE_LOGGING\nvoid LoggerPrivate_pLogFile(int level, const char *label, const char *in_time, const char *format, va_list arg)\n{\n    va_list arg_in;\n    (void)level;\n\n    if(!s_logout)\n        return;\n\n    MUTEXLOCK(mutex);\n\n    va_copy(arg_in, arg);\n\n    int len = SDL_snprintf(g_outputBuffer, OUT_BUFFER_SIZE, \"%s [%s]: \", in_time, label);\n    if(len > 0)\n        SDL_RWwrite(s_logout, g_outputBuffer, 1, (size_t)(len < OUT_BUFFER_STRING_SIZE ? len : OUT_BUFFER_STRING_SIZE));\n\n    len = SDL_vsnprintf(g_outputBuffer, OUT_BUFFER_SIZE, format, arg_in);\n    if(len > 0)\n        SDL_RWwrite(s_logout, g_outputBuffer, 1, (size_t)(len < OUT_BUFFER_STRING_SIZE ? len : OUT_BUFFER_STRING_SIZE));\n\n    SDL_RWwrite(s_logout, reinterpret_cast<const void *>(OS_NEWLINE), 1, OS_NEWLINE_LEN);\n    va_end(arg_in);\n\n/* WORKAROUNDS: flush the output of SDL RWops */\n#ifdef HAVE_STDIO_H\n    if(s_logout->hidden.stdio.fp)\n        fflush(s_logout->hidden.stdio.fp);\n#endif\n}\n#endif // NO_FILE_LOGGING\n"
  },
  {
    "path": "lib/Logger/private/logger_desktop.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#if defined(_WIN32) || defined(__GDK__)\n#include <windows.h>\n#include <handleapi.h>\n#include <fileapi.h>\n#endif\n\n#define LOGGER_INTERNAL\n#include <SDL2/SDL_rwops.h>\n\n#include \"logger_sets.h\"\n#include \"logger_private.h\"\n\n#ifndef NO_FILE_LOGGING\n\n#ifndef PGE_NO_THREADING\nstatic std::mutex g_lockLocker;\n#endif\n\n#   define OUT_BUFFER_SIZE 10240\nstatic char       g_outputBuffer[OUT_BUFFER_SIZE];\n#   define OUT_BUFFER_STRING_SIZE 10239\n//! Output file\nstatic SDL_RWops *s_logout = nullptr;\n#endif // NO_FILE_LOGGING\n\n\nvoid LogWriter::OpenLogFile()\n{\n#ifndef NO_FILE_LOGGING\n    MUTEXLOCK(mutex);\n\n    if(LogWriter::m_enabledStdOut)\n    {\n        try\n        {\n            fmt::print(stderr, \"LogLevel {0}, log file {1}\\n\\n\", static_cast<int>(m_logLevel), m_logFilePath);\n            std::fflush(stderr);\n        }\n        catch(const fmt::FormatError &err)\n        {\n            std::fprintf(stderr, \"fmt::print failed with exception: %s\\n\", err.what());\n            std::fflush(stderr);\n            abort();\n        }\n    }\n\n    if(m_enabled)\n    {\n        s_logout = SDL_RWFromFile(m_logFilePath.c_str(), \"a\");\n        if(!s_logout)\n        {\n            std::fprintf(stderr, \"Impossible to open %s for write, log printing into the file is disabled!\\n\", m_logFilePath.c_str());\n            std::fflush(stderr);\n        }\n    }\n#endif // NO_FILE_LOGGING\n}\n\nvoid LogWriter::CloseLog()\n{\n#ifndef NO_FILE_LOGGING\n    MUTEXLOCK(mutex);\n    if(s_logout)\n        SDL_RWclose(s_logout);\n    s_logout = nullptr;\n#endif // NO_FILE_LOGGING\n}\n\nvoid LoggerPrivate_pLogConsole(int level, const char *label, const char *format, va_list arg)\n{\n    va_list arg_in;\n    (void)level;\n\n    va_copy(arg_in, arg);\n    std::fprintf(stderr, \"%s: \", label);\n    std::vfprintf(stderr, format, arg_in);\n    std::fprintf(stderr, OS_NEWLINE);\n    std::fflush(stderr);\n    va_end(arg_in);\n}\n\n#ifndef NO_FILE_LOGGING\nvoid LoggerPrivate_pLogFile(int level, const char *label, const char *in_time, const char *format, va_list arg)\n{\n    va_list arg_in;\n    (void)level;\n\n    if(!s_logout)\n        return;\n\n    MUTEXLOCK(mutex);\n\n    va_copy(arg_in, arg);\n\n    int len = SDL_snprintf(g_outputBuffer, OUT_BUFFER_SIZE, \"%s [%s]: \", in_time, label);\n    if(len > 0)\n        SDL_RWwrite(s_logout, g_outputBuffer, 1, (size_t)(len < OUT_BUFFER_STRING_SIZE ? len : OUT_BUFFER_STRING_SIZE));\n\n    len = SDL_vsnprintf(g_outputBuffer, OUT_BUFFER_SIZE, format, arg_in);\n    if(len > 0)\n        SDL_RWwrite(s_logout, g_outputBuffer, 1, (size_t)(len < OUT_BUFFER_STRING_SIZE ? len : OUT_BUFFER_STRING_SIZE));\n\n    SDL_RWwrite(s_logout, reinterpret_cast<const void *>(OS_NEWLINE), 1, OS_NEWLINE_LEN);\n    va_end(arg_in);\n\n/* WORKAROUNDS: flush the output of SDL RWops */\n#if defined(_WIN32) || defined(__GDK__)\n    if(s_logout->hidden.windowsio.h != INVALID_HANDLE_VALUE)\n        FlushFileBuffers(s_logout->hidden.windowsio.h);\n#elif HAVE_STDIO_H\n    if(s_logout->hidden.stdio.fp)\n        fflush(s_logout->hidden.stdio.fp);\n#endif\n\n}\n\n#endif // NO_FILE_LOGGING\n"
  },
  {
    "path": "lib/Logger/private/logger_dummy.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#define LOGGER_INTERNAL\n#include \"logger_sets.h\"\n\n\nvoid LogWriter::OpenLogFile()\n{}\n\nvoid LogWriter::CloseLog()\n{}\n\nvoid LoggerPrivate_pLogConsole(int level, const char *label, const char *format, va_list arg)\n{\n    (void)level;\n    (void)label;\n    (void)format;\n    (void)arg;\n}\n\n#ifndef NO_FILE_LOGGING\nvoid LoggerPrivate_pLogFile(int level, const char *label, const char *in_time, const char *format, va_list arg)\n{\n    (void)level;\n    (void)label;\n    (void)in_time;\n    (void)format;\n    (void)arg;\n}\n#endif\n"
  },
  {
    "path": "lib/Logger/private/logger_emscripten.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#define LOGGER_INTERNAL\n#include \"logger_sets.h\"\n#include \"logger_private.h\"\n\n#ifdef __EMSCRIPTEN__\n#include <emscripten/trace.h>\n#include <emscripten/html5.h>\n#define LOG_CHANNEL \"Application\"\n#endif // __EMSCRIPTEN__\n\n#ifndef PGE_NO_THREADING\nstatic std::mutex g_lockLocker;\n#endif\n\n#define OUT_BUFFER_SIZE 10240\nstatic char       g_outputBuffer[OUT_BUFFER_SIZE];\nstatic char       g_outputBuffer2[OUT_BUFFER_SIZE];\n\n\nvoid LogWriter::OpenLogFile()\n{}\n\nvoid LogWriter::CloseLog()\n{}\n\nvoid LoggerPrivate_pLogConsole(int level, const char *label, const char *format, va_list arg)\n{\n    va_list arg_in;\n\n#ifndef PGE_NO_THREADING\n    MUTEXLOCK(mutex);\n#endif\n\n    va_copy(arg_in, arg);\n    int len = std::vsnprintf(g_outputBuffer, OUT_BUFFER_SIZE, format, arg_in);\n    if(len > 0)\n    {\n        int len = std::snprintf(g_outputBuffer2, OUT_BUFFER_SIZE, \"%s: %s\\n\", label, g_outputBuffer);\n        if(len > 0)\n        {\n            if(level == PGE_LogLevel::Critical || level == PGE_LogLevel::Fatal)\n                emscripten_console_error(g_outputBuffer2);\n            else if(level == PGE_LogLevel::Warning)\n                emscripten_console_warn(g_outputBuffer2);\n            else\n                emscripten_console_log(g_outputBuffer2);\n        }\n    }\n    va_end(arg_in);\n    (void)len;\n}\n\n#ifndef NO_FILE_LOGGING\nvoid LoggerPrivate_pLogFile(int, const char *, const char *, const char *, va_list)\n{}\n#endif // NO_FILE_LOGGING\n"
  },
  {
    "path": "lib/Logger/private/logger_min.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#define LOGGER_INTERNAL\n#include \"logger_sets.h\"\n#include \"logger_private.h\"\n#include <cstdio>\n\n#ifndef NO_FILE_LOGGING\n//! Output file\nstatic FILE* s_logout;\n#endif // #ifndef NO_FILE_LOGGING\n\n#ifndef PGE_NO_THREADING\n#   ifdef PGE_SDL_MUTEX\n    static SDL_mutex *g_lockLocker = nullptr;\n#   else\n    static std::mutex g_lockLocker;\n#   endif\n#endif\n\nvoid LogWriter::OpenLogFile()\n{\n#if !defined(PGE_NO_THREADING) && defined(PGE_SDL_MUTEX)\n    g_lockLocker = SDL_CreateMutex();\n#endif\n\n    if(m_enabled)\n    {\n#ifndef NO_FILE_LOGGING\n        s_logout = std::fopen(m_logFilePath.c_str(), \"a\");\n#endif // #ifndef NO_FILE_LOGGING\n    }\n}\n\nvoid LogWriter::CloseLog()\n{\n#if !defined(PGE_NO_THREADING) && defined(PGE_SDL_MUTEX)\n    SDL_DestroyMutex(g_lockLocker);\n    g_lockLocker = nullptr;\n#endif\n\n#ifndef NO_FILE_LOGGING\n    if(s_logout)\n        std::fclose(s_logout);\n    s_logout = nullptr;\n#endif // #ifndef NO_FILE_LOGGING\n}\n\nvoid LoggerPrivate_pLogConsole(int level, const char *label, const char *format, va_list arg)\n{\n    va_list arg_in;\n    (void)level;\n\n    va_copy(arg_in, arg);\n    std::printf(\"%s: \", label);\n    std::vprintf(format, arg_in);\n    std::printf(OS_NEWLINE);\n    va_end(arg_in);\n}\n\n#ifndef NO_FILE_LOGGING\nvoid LoggerPrivate_pLogFile(int level, const char *label, const char *in_time, const char *format, va_list arg)\n{\n    MUTEXLOCK(mutex);\n    if(!s_logout)\n        return;\n\n    va_list arg_in;\n    (void)level;\n\n    va_copy(arg_in, arg);\n    std::fprintf(s_logout, \"%s [%s]: \", in_time, label);\n    std::vfprintf(s_logout, format, arg_in);\n    std::fprintf(s_logout, OS_NEWLINE);\n    std::fflush(s_logout);\n    va_end(arg_in);\n}\n#endif\n"
  },
  {
    "path": "lib/Logger/private/logger_private.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef LOGGER_INTERNAL\n#   error THIS FILE MUST NOT BE INCLUDED OUTSIDE THE LOGGER CODE ITSELF\n#endif\n\n#ifndef LOGGER_SETS_H\n#include \"logger_sets.h\"\n#endif\n\n#include <fmt_format_ne.h>\n#include <fmt/fmt_printf.h>\n#include <stdarg.h>\n#include <cstdio>\n\n#ifndef PGE_NO_THREADING\n#   ifdef PGE_SDL_MUTEX\n#       include <SDL2/SDL_mutex.h>\n#   else\n#       include <mutex>\n#   endif\n#endif\n\n#include <sstream>\n#include <algorithm>\n\n#ifdef _WIN32\n#define OS_NEWLINE \"\\r\\n\"\n#define OS_NEWLINE_LEN 2\n#else\n#define OS_NEWLINE \"\\n\"\n#define OS_NEWLINE_LEN 1\n#endif\n\n#ifdef PGE_NO_THREADING\n\n#   define MUTEXLOCK(mn) (void)0\n\n#else\n\nclass MutexLocker\n{\n#ifdef PGE_SDL_MUTEX\n    SDL_mutex *m_mutex;\n#else\n    std::mutex *m_mutex;\n#endif\n\npublic:\n#ifdef PGE_SDL_MUTEX\n    MutexLocker(SDL_mutex **mutex)\n    {\n        m_mutex = *mutex;\n        SDL_LockMutex(m_mutex);\n    }\n#else\n    MutexLocker(std::mutex *mutex)\n    {\n        m_mutex = mutex;\n        m_mutex->lock();\n    }\n#endif\n\n    ~MutexLocker()\n    {\n#ifdef PGE_SDL_MUTEX\n        SDL_UnlockMutex(m_mutex);\n#else\n        m_mutex->unlock();\n#endif\n    }\n};\n\n#   define MUTEXLOCK(mn) \\\n    MutexLocker mn(&g_lockLocker); \\\n    (void)mn\n\n#endif\n"
  },
  {
    "path": "lib/Logger/private/logger_sets.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef LOGGER_SETS_H\n#define LOGGER_SETS_H\n#pragma once\n\n#include <string>\n#include <memory>\n#include <stdarg.h>\n#include <stdint.h>\n\n#include \"../logger.h\"\n\nclass LogWriter\n{\npublic:\n    static std::string  m_logDirPath;\n    static std::string  m_logFilePath;\n    static PGE_LogLevel::Level m_logLevel;\n    static int          m_maxFilesCount;\n    //! Is logging system is enabled\n    static bool       m_enabled;\n    //! Is logging system allowed to output into `stdout`\n    static bool       m_enabledStdOut;\n    //! Verbose logs to stdOut if possible\n    static bool       m_enabledVerboseLogs;\n\n    static uint64_t   m_appStartTicks;\n\n    static void OpenLogFile();\n    static void CloseLog();\n};\n\nextern void LoggerPrivate_pLogConsole(int level, const char *label, const char *format, va_list arg);\n\n#ifndef NO_FILE_LOGGING\nextern void LoggerPrivate_pLogFile(int level, const char *label, const char *in_time, const char *format, va_list arg);\n#endif\n\n#endif // LOGGER_SETS_H\n"
  },
  {
    "path": "lib/Logger/private/logger_vita.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#define LOGGER_INTERNAL\n#include \"logger_sets.h\"\n#include \"logger_private.h\"\n#include <cstdio>\n\n#ifdef DEBUG_BUILD\n#   include <debugnet.h>\n#   ifndef NETDBG_IP_SERVER\n#       define NETDBG_IP_SERVER \"192.168.1.183\"\n#   endif\n#   ifndef NETDBG_PORT_SERVER\n#       define NETDBG_PORT_SERVER 18194\n#   endif\n\n#   define VITA_TEMP_BUFFER_SIZE (1024 * 1024)\n\nstatic char __string_buffer[VITA_TEMP_BUFFER_SIZE - 3];\nstatic char __string_buffer2[VITA_TEMP_BUFFER_SIZE];\nstatic int __vita_debug_setup = 0;\n#endif\n\n#ifndef NO_FILE_LOGGING\n//! Output file\nstatic FILE* s_logout;\n#endif // #ifndef NO_FILE_LOGGING\n\nvoid LogWriter::OpenLogFile()\n{\n#ifndef NO_FILE_LOGGING\n    if(m_enabled)\n        s_logout = std::fopen(m_logFilePath.c_str(), \"a\");\n#endif // #ifndef NO_FILE_LOGGING\n}\n\nvoid LogWriter::CloseLog()\n{\n#ifndef NO_FILE_LOGGING\n    if(s_logout)\n        std::fclose(s_logout);\n    s_logout = nullptr;\n#endif // #ifndef NO_FILE_LOGGING\n}\n\nvoid LoggerPrivate_pLogConsole(int level, const char *label, const char *format, va_list arg)\n{\n#ifdef DEBUG_BUILD\n    va_list arg_in;\n    (void)level;\n    (void)label;\n\n    if(__vita_debug_setup == 0)\n        debugNetInit(NETDBG_IP_SERVER, NETDBG_PORT_SERVER, DEBUG);\n\n    // Print arg list to first string buffer.\n    va_copy(arg_in, arg);\n    vsnprintf(__string_buffer, VITA_TEMP_BUFFER_SIZE - 4, format, arg_in);\n    va_end(arg_in);\n    // Print that string buffer into second string buffer with new line & null termination.\n    snprintf(__string_buffer2, VITA_TEMP_BUFFER_SIZE, \"%s\\n\", __string_buffer);\n    \n    // Print to network.\n    debugNetPrintf(DEBUG, __string_buffer2);\n#else\n    (void)level;\n    (void)label;\n    (void)format;\n    (void)arg;\n#endif\n}\n\n#ifndef NO_FILE_LOGGING\nvoid LoggerPrivate_pLogFile(int level, const char *label, const char *in_time, const char *format, va_list arg)\n{\n    if(!s_logout)\n        return;\n\n    va_list arg_in;\n    (void)level;\n\n    va_copy(arg_in, arg);\n    std::fprintf(s_logout, \"%s [%s]: \", in_time, label);\n    std::vfprintf(s_logout, format, arg_in);\n    std::fprintf(s_logout, OS_NEWLINE);\n    std::fflush(s_logout);\n    va_end(arg_in);\n}\n#endif\n"
  },
  {
    "path": "lib/Logger/private/logger_wiiu.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#define LOGGER_INTERNAL\n#include \"logger_sets.h\"\n#include \"logger_private.h\"\n#include <fmt/fmt_printf.h>\n#include <coreinit/debug.h>\n\n#define VITA_TEMP_BUFFER_SIZE (1024 * 1024)\n\nstatic char s_string_buffer[VITA_TEMP_BUFFER_SIZE - 3];\n\n#ifdef DEBUG_BUILD\n#include <stdio.h>\n#include <string>\n\n// #define ENABLE_NET_LOG\n\n#   ifdef ENABLE_NET_LOG\n#       include <unistd.h>\n#       include <sys/socket.h>\n#       include <arpa/inet.h>\n\n#       ifndef NETDBG_IP_SERVER\n#           define NETDBG_IP_SERVER \"172.19.9.141\"\n#       endif\n#       ifndef NETDBG_PORT_SERVER\n#           define NETDBG_PORT_SERVER 18194\n#       endif\n\nstatic char s_string_buffer2[VITA_TEMP_BUFFER_SIZE];\nstatic int s_wut_debug_setup = 0;\nstatic int s_socket_desc = 0;\nstatic struct sockaddr_in s_server;\n#   endif // ENABLE_NET_LOG\n#endif // DEBUG_BUILD\n\n#ifndef NO_FILE_LOGGING\n//! Output file\nstatic FILE* s_logout;\n#endif // #ifndef NO_FILE_LOGGING\n\n#ifndef PGE_NO_THREADING\n#   ifdef PGE_SDL_MUTEX\n    static SDL_mutex *g_lockLocker = nullptr;\n#   else\n    static std::mutex g_lockLocker;\n#   endif\n#endif\n\nvoid LogWriter::OpenLogFile()\n{\n#if !defined(PGE_NO_THREADING) && defined(PGE_SDL_MUTEX)\n    OSReport(\"Debug: Creating the SDL Mutex\");\n    g_lockLocker = SDL_CreateMutex();\n#endif\n\n    if(m_enabled)\n    {\n#ifndef NO_FILE_LOGGING\n        OSReport(\"Debug: Opening a log file for APPEND: %s\\n\", m_logFilePath.c_str());\n        s_logout = std::fopen(m_logFilePath.c_str(), \"a\");\n#endif // #ifndef NO_FILE_LOGGING\n    }\n}\n\nvoid LogWriter::CloseLog()\n{\n#if !defined(PGE_NO_THREADING) && defined(PGE_SDL_MUTEX)\n    OSReport(\"Debug: Destroying the SDL Mutex\");\n    SDL_DestroyMutex(g_lockLocker);\n    g_lockLocker = nullptr;\n#endif\n\n#ifndef NO_FILE_LOGGING\n    if(s_logout)\n        std::fclose(s_logout);\n    s_logout = nullptr;\n#endif // #ifndef NO_FILE_LOGGING\n\n\n#ifdef ENABLE_NET_LOG\n    if(s_wut_debug_setup)\n    {\n        const char *msg = \"---- Game disconnected ----\\n\";\n        send(s_socket_desc, msg, strlen(msg) + 1, 0);\n        usleep(2000);\n        shutdown(s_socket_desc, 2);\n        s_socket_desc = 0;\n        s_wut_debug_setup = 0;\n    }\n#endif\n}\n\nvoid LoggerPrivate_pLogConsole(int level, const char *label, const char *format, va_list arg)\n{\n    va_list arg_in;\n    (void)level;\n    MUTEXLOCK(mutex);\n\n#ifdef ENABLE_NET_LOG\n\n    // Try to connect the netcat server (run as `nc -nklv 18194`)\n    if(!s_wut_debug_setup && s_socket_desc == 0)\n    {\n        s_socket_desc = socket(AF_INET , SOCK_STREAM , 0);\n        if(s_socket_desc != -1)\n        {\n            s_server.sin_addr.s_addr = inet_addr(NETDBG_IP_SERVER);\n            s_server.sin_family = AF_INET;\n            s_server.sin_port = htons(NETDBG_PORT_SERVER);\n\n            if(connect(s_socket_desc , (struct sockaddr *)&s_server , sizeof(s_server)) >= 0)\n                s_wut_debug_setup = 1;\n            else\n            {\n                shutdown(s_socket_desc, 2);\n                s_socket_desc = -1;\n            }\n        }\n    }\n\n    // If success, send log lines to it\n    if(s_socket_desc > 0)\n    {\n        va_copy(arg_in, arg);\n        // Print arg list to first string buffer.\n        std::vsnprintf(s_string_buffer, VITA_TEMP_BUFFER_SIZE - 4, format, arg_in);\n        va_end(arg_in);\n\n        // Print that string buffer into second string buffer with new line & null termination.\n        std::snprintf(s_string_buffer2, VITA_TEMP_BUFFER_SIZE, \"%s: %s\\n\", label, s_string_buffer);\n\n        send(s_socket_desc, s_string_buffer2, strlen(s_string_buffer2) + 1, 0);\n    }\n#else\n    va_copy(arg_in, arg);\n    // Print arg list to first string buffer.\n    std::vsnprintf(s_string_buffer, VITA_TEMP_BUFFER_SIZE - 4, format, arg_in);\n    va_end(arg_in);\n\n    // Print that string buffer into the output with new line & null termination.\n    OSReport(\"%s: %s\\n\", label, s_string_buffer);\n#endif\n}\n\n#ifndef NO_FILE_LOGGING\nvoid LoggerPrivate_pLogFile(int level, const char *label, const char *in_time, const char *format, va_list arg)\n{\n    MUTEXLOCK(mutex);\n    if(!s_logout)\n        return;\n\n    va_list arg_in;\n    (void)level;\n\n    va_copy(arg_in, arg);\n    std::fprintf(s_logout, \"%s [%s]: \", in_time, label);\n    std::vfprintf(s_logout, format, arg_in);\n    std::fprintf(s_logout, OS_NEWLINE);\n    std::fflush(s_logout);\n    va_end(arg_in);\n\n}\n#endif\n"
  },
  {
    "path": "lib/Utf8Main/utf8main.cmake",
    "content": "# message(\"Path to UTF8-Main is [${CMAKE_CURRENT_LIST_DIR}]\")\ninclude_directories(${CMAKE_CURRENT_LIST_DIR}/)\n\nset(UTF8MAIN_SRCS)\n\nif(WIN32)\n    list(APPEND UTF8MAIN_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/utf8main_win32.cpp\n    )\nendif()\n"
  },
  {
    "path": "lib/Utf8Main/utf8main.h",
    "content": "/*\n * utf8main - a small wrapper over entry points\n * to provide the UTF8 encoded main function\n *\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef UTF8MAIN_H\n#define UTF8MAIN_H\n\n#ifndef PGE_ENGINE//Don't use in PGE Engine where SDL Main is already in use\n//Windows platform\n#ifdef _WIN32\n//Avoid Qt to steal Main function\n#if defined(QT_NEEDS_QMAIN)\n#undef main\n#endif\n//Define custom UTF8 main function which will convert command line arguments into UTF8 and will pass them\n#define main UTF8_Main\n#endif\n\n#endif\n\n#endif // UTF8MAIN_H\n"
  },
  {
    "path": "lib/Utf8Main/utf8main_win32.cpp",
    "content": "/*\n * utf8main - a small wrapper over entry points\n * to provide the UTF8 encoded main function\n *\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifdef _WIN32\n#include <windows.h>\n#include <shellapi.h>\n#include <string>\n#include <vector>\n\n#ifdef PGE_ENGINE\n#   include <SDL2/SDL_main.h>\n#else\n#   include \"utf8main.h\"\n#endif\n\n#ifndef _In_\n#   define _In_\n#endif\n#ifndef _In_opt_\n#   define _In_opt_\n#endif\n\nextern int  main(int argc, char *argv[]);\n\nstatic void buildUtf8Args(std::vector<std::string> &utf8_args)\n{\n    //Get raw UTF-16 command line\n    wchar_t    *cmdLineW = GetCommandLineW();\n    int         argc     = 0;\n    //Convert it into array of strings\n    wchar_t   **argvW    = CommandLineToArgvW(cmdLineW, &argc);\n\n    utf8_args.reserve(argc);\n    //Convert every argument into UTF-8\n    for(int i = 0; i < argc; i++)\n    {\n        wchar_t *argW = argvW[i];\n        size_t argWlen = wcslen(argW);\n        std::string arg;\n        arg.resize(argWlen * 2);\n        size_t newLen = WideCharToMultiByte(CP_UTF8, 0, argW, static_cast<int>(argWlen), &arg[0], static_cast<int>(arg.size()), 0, 0);\n        arg.resize(newLen);\n        utf8_args.push_back(arg);\n    }\n}\n\n#ifdef WIN32_CONSOLE\n#undef main\nint main()\n#   define main UTF8_Main\n#else\nint WINAPI WinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PSTR, _In_ int)\n#endif\n{\n    //! Storage of UTF8-encoded command line arguments\n    std::vector<std::string>  g_utf8_args;\n    //! Storage of the pointers to begin of every argument\n    std::vector<char *>       g_utf8_argvV;\n\n    buildUtf8Args(g_utf8_args);\n\n#ifdef UTF8Main_Debug\n    printf(\"UTF8 ARGS RAN!\\n\");\n    fflush(stdout);\n#endif\n\n    size_t argc = g_utf8_args.size();\n    g_utf8_argvV.reserve(argc);\n    for(size_t i = 0; i < argc; i++)\n        g_utf8_argvV.push_back(&g_utf8_args[i][0]);\n\n    return  main((int)argc, g_utf8_argvV.data());\n}\n#endif\n"
  },
  {
    "path": "lib/Utils/Utils.cmake",
    "content": "# message(\"Path to Utils is [${CMAKE_CURRENT_LIST_DIR}]\")\ninclude_directories(${CMAKE_CURRENT_LIST_DIR}/..)\n\nset(UTILS_SRCS)\nset(UTILS_LIBS)\n\nlist(APPEND UTILS_SRCS\n    ${CMAKE_CURRENT_LIST_DIR}/files.cpp\n    ${CMAKE_CURRENT_LIST_DIR}/files.h\n    ${CMAKE_CURRENT_LIST_DIR}/strings.cpp\n    ${CMAKE_CURRENT_LIST_DIR}/strings.h\n    ${CMAKE_CURRENT_LIST_DIR}/elapsed_timer.cpp\n    ${CMAKE_CURRENT_LIST_DIR}/elapsed_timer.h\n    ${CMAKE_CURRENT_LIST_DIR}/dir_list_ci.cpp\n    ${CMAKE_CURRENT_LIST_DIR}/dir_list_ci.h\n)\n\nif(WIN32)\n    list(APPEND UTILS_LIBS shlwapi)\nendif()\n"
  },
  {
    "path": "lib/Utils/UtilsSDL.cmake",
    "content": "# message(\"Path to UtilsSDL is [${CMAKE_CURRENT_LIST_DIR}]\")\ninclude_directories(${CMAKE_CURRENT_LIST_DIR}/../)\n\nset(UTILS_SDL_SRCS)\n\nlist(APPEND UTILS_SDL_SRCS\n    ${CMAKE_CURRENT_LIST_DIR}/sdl_file.cpp\n)\n\n"
  },
  {
    "path": "lib/Utils/dir_list_ci.cpp",
    "content": "#include <DirManager/dirman.h>\n#include \"strings.h\"\n#include \"dir_list_ci.h\"\n\n#include <cstring>\n#include <algorithm>\n#include <utility>\n\n\nDirListCI::DirListCI(std::string curDir) noexcept\n    : m_curDir(std::move(curDir))\n{\n    if(!m_curDir.empty() && m_curDir.back() != '/')\n        m_curDir.push_back('/');\n\n    rescan();\n}\n\nvoid DirListCI::setCurDir(const std::string &path)\n{\n    auto nPath = path;\n\n    if(!nPath.empty() && nPath.back() != '/')\n        nPath.push_back('/');\n\n    if(nPath != m_curDir)\n    {\n        m_curDir = std::move(nPath);\n        rescan();\n    }\n}\n\nconst std::string& DirListCI::getCurDir()\n{\n    return m_curDir;\n}\n\nstatic void replaceSlashes(std::string &str, const std::string &from)\n{\n    str.clear();\n    if(from.empty())\n        return;\n\n    str.reserve(from.size());\n\n    char prevC = '\\0';\n\n    for(char c : from)\n    {\n        if(c == '\\\\')\n            c = '/'; // Replace backslashes\n        if(c == '/' && prevC == '/')\n            continue; // skip duplicated slashes\n        prevC = c;\n        str.push_back(c);\n    }\n}\n\nbool DirListCI::existsCI(const std::string &in_name)\n{\n    if(in_name.empty())\n        return false;\n\n    std::string name;\n    replaceSlashes(name, in_name);\n\n    // For sub-directory path, look deeply\n    auto subDir = name.find('/');\n\n    if(subDir != std::string::npos)\n    {\n        auto sdName = resolveDirCase(name.substr(0, subDir));\n        auto file = name.substr(subDir + 1);\n        auto sdf = m_subDirs.find(sdName);\n        // std::string found;\n\n        if(sdf == m_subDirs.end())\n        {\n            auto f = m_subDirs.emplace(sdName, DirListCIPtr(new DirListCI(m_curDir + sdName)));\n            return f.first->second->existsCI(file);\n        }\n        else\n            return sdf->second->existsCI(file);\n    }\n\n    // keep MixerX path arguments untouched\n    auto pathArgs = name.find('|');\n    if(pathArgs != std::string::npos)\n    {\n        auto n = name.substr(0, pathArgs);\n        std::string uppercase_string;\n        uppercase_string.resize(n.length());\n        std::transform(n.begin(), n.end(), uppercase_string.begin(),\n            [](unsigned char c){ return std::toupper(c); });\n        auto found = m_fileMap.find(uppercase_string);\n        return found != m_fileMap.end();\n    }\n    else\n    {\n        std::string uppercase_string;\n        uppercase_string.resize(name.length());\n        std::transform(name.begin(), name.end(), uppercase_string.begin(),\n            [](unsigned char c){ return std::toupper(c); });\n        auto found = m_fileMap.find(uppercase_string);\n        return found != m_fileMap.end();\n    }\n}\n\nbool DirListCI::dirExistsCI(const std::string& in_name)\n{\n    if(in_name.empty())\n        return false;\n\n    std::string name;\n    replaceSlashes(name, in_name);\n\n    // For sub-directory path, look deeply\n    auto subDir = name.find('/');\n\n    if(subDir != std::string::npos)\n    {\n        auto sdName = resolveDirCase(name.substr(0, subDir));\n        auto sdir = name.substr(subDir + 1);\n        auto sdf = m_subDirs.find(sdName);\n        // std::string found;\n\n        if(sdf == m_subDirs.end())\n        {\n            auto f = m_subDirs.emplace(sdName, DirListCIPtr(new DirListCI(m_curDir + sdName)));\n            return f.first->second->dirExistsCI(sdir);\n        }\n        else\n            return sdf->second->dirExistsCI(sdir);\n    }\n\n    std::string uppercase_string;\n    uppercase_string.resize(name.length());\n    std::transform(name.begin(), name.end(), uppercase_string.begin(),\n                   [](unsigned char c){ return std::toupper(c); });\n    auto found = m_dirMap.find(uppercase_string);\n    return found != m_dirMap.end();\n}\n\nstd::vector<std::string> DirListCI::getFilesList(const std::string& subDir,\n                                                 const std::vector<std::string>& suffix_filters)\n{\n    std::vector<std::string> ret;\n\n    std::string name;\n    replaceSlashes(name, subDir);\n\n    if(!subDir.empty())\n    {\n        std::string sdName, sdir;\n        // For sub-directory path, look deeply\n        auto subDirI = name.find('/');\n\n        if(subDirI != std::string::npos)\n        {\n            sdName = resolveDirCase(name.substr(0, subDirI));\n            sdir = name.substr(subDirI + 1);\n        }\n        else\n            sdName = name;\n\n        auto sdf = m_subDirs.find(sdName);\n        // std::string found;\n\n        if(sdf == m_subDirs.end())\n        {\n            auto f = m_subDirs.emplace(sdName, DirListCIPtr(new DirListCI(m_curDir + sdName)));\n            return f.first->second->getFilesList(sdir);\n        }\n        else\n            return sdf->second->getFilesList(sdir);\n    }\n\n    for(auto &f : m_fileMap)\n    {\n        if(DirMan::matchSuffixFilters(f.second, suffix_filters))\n            ret.push_back(f.second);\n    }\n\n    return ret;\n}\n\nstd::vector<std::string> DirListCI::getFilesList(const std::vector<std::string>& suffix_filters)\n{\n    return getFilesList(std::string(), suffix_filters);\n}\n\nstd::string DirListCI::resolveFileCaseExists(const std::string &in_name)\n{\n    if(in_name.empty())\n        return in_name;\n\n    std::string name;\n    replaceSlashes(name, in_name);\n\n    // For sub-directory path, look deeply\n    auto subDir = name.find('/');\n    if(subDir != std::string::npos)\n    {\n        auto sdName = resolveDirCase(name.substr(0, subDir));\n        auto file = name.substr(subDir + 1);\n        auto sdf = m_subDirs.find(sdName);\n        std::string found;\n\n        if(sdf == m_subDirs.end())\n        {\n            auto f = m_subDirs.emplace(sdName, DirListCIPtr(new DirListCI(m_curDir + sdName)));\n            found = f.first->second->resolveFileCaseExists(file);\n        }\n        else\n            found = sdf->second->resolveFileCaseExists(file);\n\n        if(!sdName.empty() && sdName.back() != '/')\n            sdName.push_back('/');\n\n        if(found.empty())\n            return std::string();\n        else\n            return sdName + found;\n    }\n\n    // keep MixerX path arguments untouched\n    bool hasArgs = true;\n    auto fnLen = name.find('|');\n    if(fnLen == std::string::npos)\n    {\n        fnLen = name.size();\n        hasArgs = false;\n    }\n\n    std::string uppercase_string;\n    uppercase_string.resize(fnLen);\n    std::transform(name.begin(), name.begin() + fnLen, uppercase_string.begin(),\n        [](unsigned char c){ return std::toupper(c); });\n\n    auto found = m_fileMap.find(uppercase_string);\n    if(found != m_fileMap.end())\n    {\n        if(hasArgs)\n            return found->second + name.substr(fnLen);\n        else\n            return found->second;\n    }\n\n    return std::string();\n}\n\nstd::string DirListCI::resolveFileCase(const std::string &in_name)\n{\n    if(in_name.empty())\n        return in_name;\n\n    std::string found = resolveFileCaseExists(in_name);\n\n    // if not found, overwrite with the replace-slash version of the name\n    // and return\n    if(found.empty())\n        replaceSlashes(found, in_name);\n\n    return found;\n}\n\nstd::string DirListCI::resolveFileCaseAbs(const std::string &in_name)\n{\n    if(in_name.empty())\n        return in_name;\n\n    std::string found = resolveFileCaseExists(in_name);\n\n    // if not found, overwrite with the replace-slash version of the name\n    // and return\n    if(found.empty())\n        replaceSlashes(found, in_name);\n\n    return m_curDir + found;\n}\n\nstd::string DirListCI::resolveFileCaseExistsAbs(const std::string &in_name)\n{\n    if(in_name.empty())\n        return in_name;\n\n    std::string found = resolveFileCaseExists(in_name);\n\n    // if not found, overwrite with the replace-slash version of the name\n    // and return\n    if(found.empty())\n        return found;\n\n    return m_curDir + found;\n}\n\nstd::string DirListCI::resolveDirCase(const std::string &name)\n{\n    if(name.empty())\n        return name;\n\n    std::string uppercase_string;\n    uppercase_string.resize(name.length());\n    std::transform(name.begin(), name.end(), uppercase_string.begin(),\n        [](unsigned char c){ return std::toupper(c); });\n\n    auto found = m_dirMap.find(uppercase_string);\n    if(found == m_dirMap.end())\n        return name;\n\n    return found->second;\n}\n\nvoid DirListCI::rescan()\n{\n    m_fileMap.clear();\n    m_dirMap.clear();\n    m_subDirs.clear();\n\n    if(m_curDir.empty())\n        return;\n\n    DirMan d(m_curDir);\n    std::vector<std::string> fileList;\n    std::vector<std::string> dirList;\n    d.getListOfFiles(fileList);\n    d.getListOfFolders(dirList);\n\n    std::string uppercase_string;\n\n    for(std::string& file : fileList)\n    {\n        uppercase_string.resize(file.length());\n        std::transform(file.begin(), file.end(), uppercase_string.begin(),\n            [](unsigned char c){ return std::toupper(c); });\n        m_fileMap.emplace(std::make_pair(uppercase_string, file));\n    }\n\n    for(std::string& dir : dirList)\n    {\n        uppercase_string.resize(dir.length());\n        std::transform(dir.begin(), dir.end(), uppercase_string.begin(),\n            [](unsigned char c){ return std::toupper(c); });\n        m_dirMap.emplace(std::make_pair(uppercase_string, dir));\n    }\n}\n"
  },
  {
    "path": "lib/Utils/dir_list_ci.h",
    "content": "#ifndef DIRLISTCI_H\n#define DIRLISTCI_H\n\n#include <string>\n#include <vector>\n#include <unordered_map>\n#include <memory>\n\n/**\n * @brief Case-Insensitive directory list\n */\nclass DirListCI\n{\n    std::string m_curDir;\n    std::unordered_map<std::string, std::string> m_fileMap;\n    std::unordered_map<std::string, std::string> m_dirMap;\n    typedef std::unique_ptr<DirListCI> DirListCIPtr;\n    std::unordered_map<std::string, DirListCIPtr> m_subDirs;\n\npublic:\n    DirListCI(std::string curDir = std::string()) noexcept;\n    void setCurDir(const std::string &path);\n    const std::string& getCurDir();\n\n    // checks whether a file exists (case-insensitive)\n    bool existsCI(const std::string &name);\n\n    // checks whether a directory exists (case-insensitive)\n    bool dirExistsCI(const std::string &name);\n\n    // get a list of files at the selected directory\n    std::vector<std::string> getFilesList(const std::string &subDir,\n                                          const std::vector<std::string> &suffix_filters = std::vector<std::string>());\n\n    std::vector<std::string> getFilesList(const std::vector<std::string> &suffix_filters = std::vector<std::string>());\n\n    // resolves the file's case and returns the original string if failed\n    std::string resolveFileCase(const std::string &name);\n\n    // resolves the file's case and returns the original string if failed.\n    // returns an absolute path in either case.\n    std::string resolveFileCaseAbs(const std::string &name);\n\n    // resolves the file's case and returns an empty string if failed\n    std::string resolveFileCaseExists(const std::string &name);\n\n    // resolves the file's case. resturns an absolute path if succeeded, an empty string if failed\n    std::string resolveFileCaseExistsAbs(const std::string &name);\n\n    // resolves the dir's case and returns the original string if failed\n    std::string resolveDirCase(const std::string &name);\n\n    void rescan();\n\n    inline void invalidate()\n    {\n        m_curDir.clear();\n        rescan();\n    }\n};\n\n#endif // DIRLISTCI_H\n"
  },
  {
    "path": "lib/Utils/elapsed_timer.cpp",
    "content": "#include \"elapsed_timer.h\"\n\n#include <chrono>\n#include <assert.h>\n\nstruct ElapsedTimer_p\n{\n    typedef std::chrono::nanoseconds TimeT;\n    std::chrono::high_resolution_clock::time_point recent;\n};\n\n\nElapsedTimer::ElapsedTimer()\n{\n    p = new ElapsedTimer_p;\n    assert(p);\n}\n\nElapsedTimer::ElapsedTimer(const ElapsedTimer &et)\n{\n    p = new ElapsedTimer_p;\n    assert(p);\n    p->recent = et.p->recent;\n}\n\nElapsedTimer::~ElapsedTimer()\n{\n    assert(p);\n    delete p;\n}\n\nElapsedTimer & ElapsedTimer::operator=(const ElapsedTimer &et)\n{\n    assert(this != &et);\n    p = new ElapsedTimer_p;\n    assert(p);\n    p->recent = et.p->recent;\n    return *this;\n}\n\nvoid ElapsedTimer::start()\n{\n    assert(p);\n    p->recent = std::chrono::high_resolution_clock::now();\n}\n\nvoid ElapsedTimer::restart()\n{\n    assert(p);\n    p->recent = std::chrono::high_resolution_clock::now();\n}\n\nint ElapsedTimer::elapsed() const\n{\n    assert(p);\n    using std::chrono::milliseconds;\n    using std::chrono::duration_cast;\n    return static_cast<int>(duration_cast<milliseconds>(std::chrono::high_resolution_clock::now() - p->recent).count());\n}\n\nint64_t ElapsedTimer::nanoelapsed() const\n{\n    assert(p);\n    using std::chrono::nanoseconds;\n    using std::chrono::duration_cast;\n    return duration_cast<nanoseconds>(std::chrono::high_resolution_clock::now() - p->recent).count();\n}\n"
  },
  {
    "path": "lib/Utils/elapsed_timer.h",
    "content": "#ifndef ELAPSED_TIMER_H\n#define ELAPSED_TIMER_H\n\n#include <cstdint>\n\nstruct ElapsedTimer_p;\n\nclass ElapsedTimer\n{\n    ElapsedTimer_p *p = nullptr;\npublic:\n    ElapsedTimer();\n    ElapsedTimer(const ElapsedTimer &et);\n    ~ElapsedTimer();\n    ElapsedTimer & operator=(const ElapsedTimer &et);\n\n    void start();\n    void restart();\n\n    int     elapsed() const;\n    int64_t nanoelapsed() const;\n};\n\n#endif // ELAPSED_TIMER_H\n"
  },
  {
    "path": "lib/Utils/files.cpp",
    "content": "/*\n * A small crossplatform set of file manipulation functions.\n * All input/output strings are UTF-8 encoded, even on Windows!\n *\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"files.h\"\n#include \"Logger/logger.h\"\n#include \"Archives/archives.h\"\n#include <stdio.h>\n#include <sdl_proxy/sdl_stdinc.h>\n#include <SDL2/SDL_rwops.h>\n\n#ifdef _WIN32\n#include <windows.h>\n#include <shlwapi.h>\n\nstatic std::wstring Str2WStr(const std::string &path)\n{\n    std::wstring wpath;\n    wpath.resize(path.size());\n    int newlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), static_cast<int>(path.length()), &wpath[0], static_cast<int>(path.length()));\n    wpath.resize(newlen);\n    return wpath;\n}\n#else\n#include <unistd.h>\n#include <fcntl.h>         // open\n#include <string.h>\n#include <sys/stat.h>      // fstat\n#include <sys/types.h>     // fstat\n#include <cstdio>          // BUFSIZ\n#endif\n\n#if defined(__CYGWIN__) || defined(__DJGPP__) || defined(__MINGW32__)\n#   define IS_PATH_SEPARATOR(c) (((c) == '/') || ((c) == '\\\\'))\n#else\n#   define IS_PATH_SEPARATOR(c) ((c) == '/')\n#endif\n\nstatic char fi_path_dot[] = \".\";\nstatic char fi_path_root[] = \"/\";\n\nstatic char *fi_basename(char *s)\n{\n    char *rv;\n\n    if(!s || !*s)\n        return fi_path_dot;\n\n    rv = s + strlen(s) - 1;\n\n    do\n    {\n        if(IS_PATH_SEPARATOR(*rv))\n            return rv + 1;\n        --rv;\n    }\n    while(rv >= s);\n\n    return s;\n}\n\nstatic char *fi_dirname(char *path)\n{\n    char *p;\n\n    if(path == NULL || *path == '\\0')\n        return fi_path_dot;\n\n    p = path + strlen(path) - 1;\n    while(IS_PATH_SEPARATOR(*p))\n    {\n        if(p == path)\n            return path;\n        *p-- = '\\0';\n    }\n\n    while(p >= path && !IS_PATH_SEPARATOR(*p))\n        p--;\n\n    if(p < path)\n        return fi_path_dot;\n\n    if(p == path)\n        return fi_path_root;\n\n    *p = '\\0';\n    return path;\n}\n\nFiles::Data::Data(Files::Data&& o)\n{\n    *this = std::move(o);\n}\n\nFiles::Data::~Data()\n{\n    if(m_free_me)\n        free(const_cast<unsigned char*>(m_data));\n}\n\nconst Files::Data& Files::Data::operator=(Files::Data&& o)\n{\n    if(m_free_me)\n        free(const_cast<unsigned char*>(m_data));\n\n    m_data = o.m_data;\n    m_length = o.m_length;\n    m_free_me = o.m_free_me;\n\n    o.m_data = nullptr;\n    o.m_length = -1;\n    o.m_free_me = false;\n\n    return *this;\n}\n\nvoid Files::Data::init_from_mem(const unsigned char* data, size_t size)\n{\n    if(m_free_me)\n        free(const_cast<unsigned char*>(data));\n\n    m_free_me = false;\n    m_data = data;\n    m_length = static_cast<long long int>(size);\n}\n\n#ifdef FILES_DISOWN_NEEDED\nvoid* Files::Data::disown()\n{\n    if(!m_free_me)\n        return nullptr;\n\n    void* ret = const_cast<uint8_t*>(m_data);\n\n    m_data = nullptr;\n    m_length = -1;\n    m_free_me = false;\n\n    return ret;\n}\n#endif\n\nFILE *Files::utf8_fopen(const char *filePath, const char *modes)\n{\n#ifndef _WIN32\n    return ::fopen(filePath, modes);\n#else\n    wchar_t wfile[MAX_PATH + 1];\n    wchar_t wmode[21];\n    int wfile_len = (int)strlen(filePath);\n    int wmode_len = (int)strlen(modes);\n    wfile_len = MultiByteToWideChar(CP_UTF8, 0, filePath, wfile_len, wfile, MAX_PATH);\n    wmode_len = MultiByteToWideChar(CP_UTF8, 0, modes, wmode_len, wmode, 20);\n    wfile[wfile_len] = L'\\0';\n    wmode[wmode_len] = L'\\0';\n    return ::_wfopen(wfile, wmode);\n#endif\n}\n\nSDL_RWops *Files::open_file(const char *filePath, const char *modes)\n{\n    if(Archives::is_prefix(filePath[0]))\n    {\n        if(modes[0] != 'r' || (modes[1] != 'b' && modes[1] != '\\0'))\n            return nullptr;\n\n        return Archives::open_file(filePath);\n    }\n\n    return SDL_RWFromFile(filePath, modes);\n}\n\nFiles::Data Files::load_file(const char *filePath)\n{\n    Files::Data ret;\n\n    SDL_RWops *in = Files::open_file(filePath, \"rb\");\n    if(!in)\n        return ret;\n\n    off_t size = SDL_RWsize(in);\n\n    // allocate extra byte for null terminator, but don't count it towards size of contained item\n    unsigned char* target = (unsigned char*)malloc(size + 1);\n    off_t to_read = size;\n\n    if(!target)\n    {\n        SDL_RWclose(in);\n        return ret;\n    }\n\n    ret.m_free_me = true;\n    ret.m_data = target;\n    ret.m_length = size;\n\n    while(to_read)\n    {\n        size_t bytes_read = SDL_RWread(in, target, 1, to_read);\n        if(!bytes_read)\n        {\n            pLogCritical(\"I/O error when reading [%s]\", filePath);\n            SDL_RWclose(in);\n\n            // resets the return value and frees the malloc'd buffer\n            ret = Files::Data();\n\n            return ret;\n        }\n\n        to_read -= bytes_read;\n        target += bytes_read;\n    }\n\n    // set null terminator\n    *target = 0;\n\n    SDL_RWclose(in);\n\n    return ret;\n}\n\nvoid Files::flush_file(SDL_RWops *f)\n{\n#ifdef THEXTECH_NO_SDL_BUILD\n    if(f->type == SDL_RWOPS_STDFILE)\n        ::fflush((FILE*)f->hidden.unknown.data1);\n#elif defined(HAVE_STDIO_H)\n    if(f->type == SDL_RWOPS_STDFILE)\n        ::fflush(f->hidden.stdio.fp);\n#endif\n}\n\nint Files::skipBom(SDL_RWops* file, const char** charset)\n{\n    char buf[4];\n    auto pos = SDL_RWtell(file);\n\n    // Check for a BOM marker\n    if(SDL_RWread(file, buf, 1, 4) == 4)\n    {\n        if(::memcmp(buf, \"\\xEF\\xBB\\xBF\", 3) == 0) // UTF-8 is only supported\n        {\n            if(charset)\n                *charset = \"[UTF-8 BOM]\";\n            SDL_RWseek(file, pos + 3, RW_SEEK_SET);\n            return CHARSET_UTF8;\n        }\n        // Unsupported charsets\n        else if(::memcmp(buf, \"\\xFE\\xFF\", 2) == 0)\n        {\n            if(charset)\n                *charset = \"[UTF16-BE BOM]\";\n            SDL_RWseek(file, pos + 2, RW_SEEK_SET);\n            return CHARSET_UTF16BE;\n        }\n        else if(::memcmp(buf, \"\\xFF\\xFE\", 2) == 0)\n        {\n            if(charset)\n                *charset = \"[UTF16-LE BOM]\";\n            SDL_RWseek(file, pos + 2, RW_SEEK_SET);\n            return CHARSET_UTF16LE;\n        }\n        else if(::memcmp(buf, \"\\x00\\x00\\xFE\\xFF\", 4) == 0)\n        {\n            if(charset)\n                *charset = \"[UTF32-BE BOM]\";\n            SDL_RWseek(file, pos + 4, RW_SEEK_SET);\n            return CHARSET_UTF32BE;\n        }\n        else if(::memcmp(buf, \"\\x00\\x00\\xFF\\xFE\", 4) == 0)\n        {\n            if(charset)\n                *charset = \"[UTF32-LE BOM]\";\n            SDL_RWseek(file, pos + 4, RW_SEEK_SET);\n            return CHARSET_UTF32LE;\n        }\n    }\n\n    if(charset)\n        *charset = \"[NO BOM]\";\n\n    // No BOM detected, seek to begining of the file\n    SDL_RWseek(file, pos, RW_SEEK_SET);\n\n    return CHARSET_UTF8;\n}\n\nbool Files::fileExists(const std::string &path)\n{\n#if defined(_WIN32)\n    if(!Archives::has_prefix(path))\n    {\n        std::wstring wpath = Str2WStr(path);\n        return PathFileExistsW(wpath.c_str()) == TRUE;\n    }\n#endif\n\n    SDL_RWops *ops = Files::open_file(path, \"rb\");\n    if(ops)\n    {\n        SDL_RWclose(ops);\n        return true;\n    }\n\n    return false;\n}\n\nbool Files::deleteFile(const std::string &path)\n{\n#ifdef _WIN32\n    std::wstring wpath = Str2WStr(path);\n    return (DeleteFileW(wpath.c_str()) == TRUE);\n#else\n    return ::unlink(path.c_str()) == 0;\n#endif\n}\n\nbool Files::copyFile(const std::string &to, const std::string &from, bool override)\n{\n    if(!override && fileExists(to))\n        return false;// Don't override exist target if not requested\n\n    bool ret = true;\n\n#ifdef _WIN32\n\n    std::wstring wfrom  = Str2WStr(from);\n    std::wstring wto    = Str2WStr(to);\n    ret = (bool)CopyFileW(wfrom.c_str(), wto.c_str(), !override);\n\n#else\n\n    char    buf[BUFSIZ];\n    ssize_t size;\n    ssize_t sizeOut;\n\n    int source  = open(from.c_str(), O_RDONLY, 0);\n    if(source == -1)\n        return false;\n\n    int dest    = open(to.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0640);\n    if(dest == -1)\n    {\n        close(source);\n        return false;\n    }\n\n    while((size = read(source, buf, BUFSIZ)) > 0)\n    {\n        sizeOut = write(dest, buf, static_cast<size_t>(size));\n        if(sizeOut != size)\n        {\n            ret = false;\n            break;\n        }\n    }\n\n    close(source);\n    close(dest);\n#endif\n\n    return ret;\n}\n\nbool Files::moveFile(const std::string& to, const std::string& from, bool override)\n{\n    bool ret = copyFile(to, from, override);\n    if(ret)\n        ret &= deleteFile(from);\n    return ret;\n}\n\n\nstd::string Files::dirname(std::string path)\n{\n    char *p = strdup(path.c_str());\n    char *d = ::fi_dirname(p);\n    path = d;\n    free(p);\n    return path;\n}\n\nstd::string Files::basename(std::string path)\n{\n    char *p = strdup(path.c_str());\n    char *d = ::fi_basename(p);\n    path = d;\n    free(p);\n    return path;\n}\n\nstd::string Files::basenameNoSuffix(std::string path)\n{\n    char *p = strdup(path.c_str());\n    char *d = ::fi_basename(p);\n    path = d;\n    free(p);\n    std::string::size_type dot = path.find_last_of('.');\n    if(dot != std::string::npos)\n        path.resize(dot);\n    return path;\n}\n\n\nstd::string Files::changeSuffix(std::string path, const std::string &suffix)\n{\n    size_t pos = path.find_last_of('.');// Find dot\n    if((path.size() < suffix.size()) || (pos == std::string::npos))\n        path.append(suffix);\n    else\n        path.replace(pos, suffix.size(), suffix);\n    return path;\n}\n\nbool Files::hasSuffix(const std::string &path, const std::string &suffix)\n{\n    if(suffix.size() > path.size())\n        return false;\n\n    return (SDL_strncasecmp(path.c_str() + path.size() - suffix.size(), suffix.c_str(), suffix.size()) == 0);\n}\n\n\nbool Files::isAbsolute(const std::string& path)\n{\n    if(Archives::has_prefix(path))\n        return true;\n\n    bool firstCharIsSlash = (path.size() > 0) ? path[0] == '/' : false;\n#ifdef _WIN32\n    bool containsWinChars = (path.size() > 2) ? (path[1] == ':') && ((path[2] == '\\\\') || (path[2] == '/')) : false;\n    if(firstCharIsSlash || containsWinChars)\n    {\n        return true;\n    }\n    return false;\n#else\n    return firstCharIsSlash;\n#endif\n}\n\nvoid Files::getGifMask(std::string& mask, const std::string& front)\n{\n    mask = front;\n    //Make mask filename\n    size_t dotPos = mask.find_last_of('.');\n    if(dotPos == std::string::npos)\n        mask.push_back('m');\n    else\n        mask.insert(mask.begin() + dotPos, 'm');\n}\n"
  },
  {
    "path": "lib/Utils/files.h",
    "content": "/*\n * A small crossplatform set of file manipulation functions.\n * All input/output strings are UTF-8 encoded, even on Windows!\n *\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef FILES_H\n#define FILES_H\n\n#include <string>\n\nstruct SDL_RWops;\n\n#if defined(__WII__)\n#   define FILES_DISOWN_NEEDED 1\n#   define FILES_NODISCARD_ATTR [[nodiscard]]\n#else\n#   define FILES_NODISCARD_ATTR // Nothing\n#endif\n\nnamespace Files\n{\n    // Points to memory representing a loaded file. Does not need to own the memory.\n    struct Data\n    {\nprivate:\n        const unsigned char* m_data = nullptr;\n        long long int m_length = -1;\n        bool m_free_me = false;\npublic:\n        Data() = default;\n        Data(const Data&) = delete;\n        Data(Data&&);\n        ~Data();\n\n        const Data& operator=(const Data&) = delete;\n        const Data& operator=(Data&&);\n\n        void init_from_mem(const unsigned char* data, size_t size);\n\n        inline void take_ownership_of_mem(const unsigned char* data, size_t size)\n        {\n            init_from_mem(data, size);\n            m_free_me = true;\n        }\n\n#ifdef FILES_DISOWN_NEEDED\n        // if the buffer is malloc-allocated, disowns it and allows the client to take management of it\n        FILES_NODISCARD_ATTR void* disown();\n#endif\n\n        inline bool valid() const\n        {\n            return m_length >= 0;\n        }\n\n        inline const unsigned char* begin() const\n        {\n            return m_data;\n        }\n\n        inline const unsigned char* end() const\n        {\n            return m_data + m_length;\n        }\n\n        inline const char* c_str() const\n        {\n            return reinterpret_cast<const char*>(m_data);\n        }\n\n        inline size_t size() const\n        {\n            return (m_length >= 0) ? (size_t)m_length : 0;\n        }\n\n        inline size_t empty() const\n        {\n            return m_length <= 0;\n        }\n\n        friend Data load_file(const char *filePath);\n    };\n\n    FILE *utf8_fopen(const char *filePath, const char *modes);\n    SDL_RWops *open_file(const char *filePath, const char *modes);\n    Data load_file(const char *filePath);\n\n    inline SDL_RWops *open_file(const std::string& filePath, const char *modes)\n    {\n        return open_file(filePath.c_str(), modes);\n    }\n    inline Data load_file(const std::string& filePath)\n    {\n        return load_file(filePath.c_str());\n    }\n\n    void flush_file(SDL_RWops *f);\n\n    enum Charsets\n    {\n        CHARSET_UTF8 = 0,\n        CHARSET_UTF16BE,\n        CHARSET_UTF16LE,\n        CHARSET_UTF32BE,\n        CHARSET_UTF32LE\n    };\n    int skipBom(SDL_RWops *file, const char **charset = nullptr);\n\n    bool fileExists(const std::string &path);\n    bool deleteFile(const std::string &path);\n    bool copyFile(const std::string &to, const std::string &from, bool override = false);\n    bool moveFile(const std::string &to, const std::string &from, bool override = false);\n    bool isAbsolute(const std::string &path);\n    std::string basename(std::string path);\n    std::string basenameNoSuffix(std::string path);\n    std::string dirname(std::string path);\n    std::string changeSuffix(std::string path, const std::string& suffix);\n    bool hasSuffix(const std::string &path, const std::string &suffix);\n    //Appends \"m\" into basename of the file name before last dot\n    void getGifMask(std::string &mask, const std::string &front);\n}\n\n#endif // FILES_H\n"
  },
  {
    "path": "lib/Utils/files_ini.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef THEXTECH_FILES_INI_H\n#define THEXTECH_FILES_INI_H\n\n#include <string>\n#include <IniProcessor/ini_processing.h>\n#include <Utils/files.h>\n\nnamespace Files\n{\n    inline IniProcessing load_ini(const char* name)\n    {\n        Files::Data data = Files::load_file(name);\n        return IniProcessing(data.c_str(), data.size());\n    }\n\n    inline IniProcessing load_ini(const std::string& name)\n    {\n        return load_ini(name.c_str());\n    }\n}\n\n#endif\n"
  },
  {
    "path": "lib/Utils/openUrl.cmake",
    "content": "# message(\"Path to openUrl is [${CMAKE_CURRENT_LIST_DIR}]\")\ninclude_directories(${CMAKE_CURRENT_LIST_DIR}/../)\n\nset(OPENURL_SRCS)\n\nlist(APPEND OPENURL_SRCS\n    ${CMAKE_CURRENT_LIST_DIR}/open_url.cpp\n)\n\n"
  },
  {
    "path": "lib/Utils/open_url.cpp",
    "content": "/*\n * A small set of additional math functions and templates\n *\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"open_url.h\"\n\n/*\n    WinAPI implementation\n*/\n#ifdef _WIN32\n#define OPENURL_SUPPORTED\n#include <windows.h>\n#include <shellapi.h>\n\nvoid Utils::openUrl(const std::string &url)\n{\n    std::wstring urlW;\n    urlW.resize(url.size());\n    int newlen = MultiByteToWideChar(CP_UTF8, 0, url.c_str(), url.length(), &urlW[0], urlW.length());\n    urlW.resize(newlen);\n    ShellExecuteW(NULL, L\"open\", urlW.c_str(), NULL, NULL, SW_SHOW);\n}\n#endif\n\n/*\n    Cocoa implementation\n*/\n#ifdef __APPLE__\n#define OPENURL_SUPPORTED\n#include <CoreFoundation/CFBundle.h>\n#include <ApplicationServices/ApplicationServices.h>\n\nvoid Utils::openUrl(const std::string &url)\n{\n    CFURLRef cfurl = CFURLCreateWithBytes(\n                         NULL,                        // allocator\n                         (UInt8 *)url.c_str(),    // URLBytes\n                         url.length(),            // length\n                         kCFStringEncodingASCII,      // encoding\n                         NULL                         // baseURL\n                     );\n    LSOpenCFURLRef(cfurl, 0);\n    CFRelease(cfurl);\n}\n#endif\n\n/*\n    Linux/FreeBSD implementation\n*/\n#if defined(__unix__) && (defined(__gnu_linux__) || defined(__FreeBSD__))\n#define OPENURL_SUPPORTED\n#include <stdlib.h>\n#include <string.h>\n#include \"files.h\"\n\nstatic const char *defaultPaths[] =\n{\n    \"/usr/local/bin/\",\n    \"/usr/bin/\",\n    \"/bin/\"\n};\n\nstatic const char *browsers[] =\n{\n    \"firefox\",\n    \"chromium-browser\",\n    \"google-chrome\",\n    \"mozilla\",\n    \"opera\"\n};\n\nstatic bool findExec(std::string &exec, const std::string &target)\n{\n    size_t len = sizeof(defaultPaths)/sizeof(const char*);\n    //Is absolute path\n    if((target.compare(0, 1, \"/\") == 0) && (Files::fileExists(target)))\n    {\n        exec = target;\n        return true;\n    }\n\n    for(size_t i = 0; i < len; i++)\n    {\n        if(Files::fileExists(defaultPaths[i] + target))\n        {\n            exec = defaultPaths[i] + target;\n            return true;\n        }\n    }\n    return false;\n}\n\nstatic void execApp(const std::string &prog, const std::string &args)\n{\n    std::string ex = prog + \" \" + args + \" &\";\n    if(system(ex.c_str()) != 0)\n    {\n        fprintf(stderr, \"Warning: Opening of URL %s finished with errors\\n\", args.c_str());\n        fflush(stderr);\n    }\n}\n\nvoid Utils::openUrl(const std::string &url)\n{\n    std::string browser;\n\n    if(findExec(browser, \"xdg-open\"))\n    {\n        execApp(browser, url);\n        return;\n    }\n    char* envBrowser = nullptr;\n    char* envDesktop = nullptr;\n\n    envBrowser = getenv(\"DEFAULT_BROWSER\");\n    if(envBrowser)\n        envBrowser = getenv(\"BROWSER\");\n    if(envBrowser && findExec(browser, envBrowser))\n    {\n        execApp(browser, url);\n        return;\n    }\n\n    envDesktop = getenv(\"XDG_CURRENT_DESKTOP\");\n    if(envDesktop)\n    {\n        if(strcmp(envDesktop, \"KDE\") == 0)\n        {\n            if(findExec(browser, \"kfmclient\"))\n            {\n                std::string args = \" exec \" + url;\n                execApp(browser, args);\n                return;\n            }\n        }\n\n        if(strcmp(envDesktop, \"GNOME\") == 0)\n        {\n            if(findExec(browser, \"gnome-open\"))\n            {\n                execApp(browser, url);\n                return;\n            }\n        }\n    }\n\n    {\n        size_t len = sizeof(browsers)/sizeof(const char*);\n        for(size_t i = 0; i < len; i++)\n        {\n            if(findExec(browser, browsers[i]))\n            {\n                execApp(browser, url);\n                return;\n            }\n        }\n    }\n}\n#endif\n\n/*\n    Haiku implementation\n*/\n#ifdef __HAIKU__\n#define OPENURL_SUPPORTED\nvoid Utils::openUrl(const std::string &url)\n{\n\t(void)url;\n    //FIXME: Implement this!\n}\n#endif\n\n/*\n    Dummy for unsupported operating systems\n*/\n#ifndef OPENURL_SUPPORTED\n#include <stdio.h>\n\nvoid Utils::openUrl(const std::string &url)\n{\n\t(void)url;\n    fprintf(stderr, \"Warning: Opening of URLs by Utils::openUrl() is not supported on this operating system\\n\");\n    fflush(stderr);\n}\n#endif\n\n"
  },
  {
    "path": "lib/Utils/open_url.h",
    "content": "/*\n * A small set of additional math functions and templates\n *\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef OPEN_URL_H\n#define OPEN_URL_H\n\n#include <string>\n\nnamespace Utils\n{\nvoid openUrl(const std::string &url);\n}\n\n#endif // OPEN_URL_H\n"
  },
  {
    "path": "lib/Utils/strings.cpp",
    "content": "/*\n * A small set of extra string processing functions\n *\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"strings.h\"\n#include <algorithm>\n#include <functional>\n#include <string>\n#include <cstring>\n#include <cctype>\n\n\nbool Strings::startsWith(const std::string &str, char what)\n{\n    if(str.empty())\n        return false;\n    return (str.front() == what);\n}\n\nbool Strings::startsWith(const std::string &str, const std::string &what)\n{\n    if(str.size() < what.size())\n        return false;\n    return (str.substr(0, what.size()).compare(what) == 0);\n}\n\nbool Strings::endsWith(const std::string& str, char what)\n{\n    if(str.empty())\n        return false;\n    return (str.back() == what);\n}\n\nbool Strings::endsWith(const std::string& str, const std::string& what)\n{\n    if(str.size() < what.size())\n        return false;\n    return (str.substr( str.size() - what.size(), what.size()).compare(what) == 0);\n}\n\n// trim from start (in place)\nstatic inline void ltrim(std::string &s)\n{\n    s.erase(s.begin(), std::find_if(s.begin(), s.end(),\n        [](int c)\n        {\n            return !std::isspace(c);\n        }\n    ));\n}\n\n// trim from end (in place)\nstatic inline void rtrim(std::string &s)\n{\n    s.erase(std::find_if(s.rbegin(), s.rend(),\n        [](int c)\n        {\n            return !std::isspace(c);\n        }\n    ).base(), s.end());\n}\n\nstd::string Strings::trim(std::string str)\n{\n    ::ltrim(str);\n    ::rtrim(str);\n    return str;\n}\nstd::string Strings::ltrim(std::string str)\n{\n    ::rtrim(str);\n    return str;\n}\n\nstd::string Strings::rtrim(std::string str)\n{\n    ::ltrim(str);\n    ::rtrim(str);\n    return str;\n}\n\nvoid Strings::doTrim(std::string& str)\n{\n    ::ltrim(str);\n    ::rtrim(str);\n}\n\nvoid Strings::doLTrim(std::string &str)\n{\n    ::ltrim(str);\n}\n\nvoid Strings::doRTrim(std::string &str)\n{\n    ::rtrim(str);\n}\n\n\nvoid Strings::split(Strings::List& out, const std::string& str, char delimiter)\n{\n    if(str.empty())\n        return;\n\n    std::string::size_type beg = 0;\n    std::string::size_type end = 0;\n    do\n    {\n        end = str.find(delimiter, beg);\n        if(end == std::string::npos)\n            end = str.size();\n        out.emplace_back(str.substr(beg, end-beg));\n        beg = end + 1;\n    }\n    while(end < str.size() - 1);\n}\n\nvoid Strings::split(Strings::List& out, const std::string& str, const std::string& delimiter)\n{\n    if(str.empty())\n        return;\n\n    std::string::size_type beg = 0;\n    std::string::size_type end = 0;\n    do\n    {\n        end = str.find(delimiter, beg);\n        if(end == std::string::npos)\n            end = str.size();\n        out.emplace_back(str.substr(beg, end-beg));\n        beg = end + delimiter.size();\n    }\n    while(end < str.size() - 1);\n}\n\nStrings::List Strings::split(const std::string& str, char delimiter)\n{\n    List res;\n    split(res, str, delimiter);\n    return res;\n}\n\nStrings::List Strings::split(const std::string& str, const std::string& delimiter)\n{\n    List res;\n    split(res, str, delimiter);\n    return res;\n}\n\nvoid Strings::replaceInAll(std::string &src, const std::string &from, const std::string &to)\n{\n    if(from.empty())\n        return;\n\n    size_t start_pos = 0;\n    while((start_pos = src.find(from, start_pos)) != std::string::npos)\n    {\n        src.replace(start_pos, from.length(), to);\n        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'\n    }\n}\n\nvoid Strings::removeInAll(std::string &src, const std::string &substr)\n{\n    std::string::size_type foundpos = src.find(substr);\n    if(foundpos != std::string::npos)\n        src.erase(src.begin() + std::string::difference_type(foundpos),\n                     src.begin() + std::string::difference_type(foundpos + substr.length()));\n}\n\nstd::string Strings::removeAll(std::string src, const std::string &substr)\n{\n    removeInAll(src, substr);\n    return src;\n}\n\nstd::string Strings::replaceAll(std::string src, const std::string &from, const std::string &to)\n{\n    replaceInAll(src, from, to);\n    return src;\n}\n\nvoid Strings::dealloc(std::string& out)\n{\n    std::string deleter; // Deallocate the string\n    std::swap(out, deleter);\n}\n\nvoid Strings::dealloc(std::vector<std::string>& out)\n{\n    std::vector<std::string> deleter; // Deallocate the string\n    std::swap(out, deleter);\n}\n"
  },
  {
    "path": "lib/Utils/strings.h",
    "content": "/*\n * A small set of extra string processing functions\n *\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following\n * conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies\n * or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\n * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef STRINGS_H\n#define STRINGS_H\n\n#include <string>\n#include <vector>\n\nnamespace Strings\n{\n    typedef std::vector<std::string> List;\n    bool startsWith(const std::string &str, char what);\n    bool startsWith(const std::string &str, const std::string &what);\n\n    bool endsWith(const std::string &str, char what);\n    bool endsWith(const std::string &str, const std::string &what);\n\n    std::string trim(std::string str);\n    std::string ltrim(std::string str);\n    std::string rtrim(std::string str);\n\n    void doTrim(std::string &str);\n    void doLTrim(std::string &str);\n    void doRTrim(std::string &str);\n\n    void split(List &out, const std::string &str, char delimiter);\n    void split(List &out, const std::string &str, const std::string &delimiter);\n    List split(const std::string &str, char delimiter);\n    List split(const std::string &str, const std::string &delimiter);\n\n    void replaceInAll(std::string &src, const std::string &from, const std::string &to);\n    void removeInAll(std::string &src, const std::string &substr);\n    std::string replaceAll(std::string src, const std::string &from, const std::string &to);\n    std::string removeAll(std::string src, const std::string &substr);\n\n    /**\n     * @brief Clears the std::string with the deallocation of the heap\n     * @param out The string to deallocate\n     */\n    void dealloc(std::string &out);\n\n    void dealloc(std::vector<std::string> &out);\n}\n#endif // STRINGS_H\n"
  },
  {
    "path": "lib/Utils/vptrlist.h",
    "content": "/*\nVPtrList - implementation of PtrList based on std::vector<std::shared_ptr<T>> inherence\nimplements same API as PtrList, but works faster and has things as std::vector has\n\nCopyright (c) 2017-2026 Vitaliy Novichkov <admin@wohlnet.ru>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation the\nrights to use, copy, modify, merge, publish, distribute, sublicense, and/or\nsell copies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\n#ifndef VPTRLIST_H\n#define VPTRLIST_H\n\n#include <vector>\n#include <memory>\n#include <assert.h>\n#include <stddef.h>\n\n#if defined(__EMSCRIPTEN__) || !defined(_WIN32)\n#include <sys/types.h>\n#endif\n\n#ifdef _MSC_VER\n#ifdef _WIN64\ntypedef long long ssize_t;\n#else\ntypedef int ssize_t;\n#endif\n#endif\n\ntemplate<class T, typename _Alloc = std::allocator<T>>\nclass VPtrList : private std::vector<std::unique_ptr<T>>\n{\npublic:\n    typedef std::unique_ptr<T> SHptr;\n    typedef std::vector<SHptr>              vecPTR;\n    typedef typename vecPTR::iterator       S_iterator;\n    typedef typename vecPTR::const_iterator S_const_iterator;\n    typedef typename vecPTR::reverse_iterator       SR_iterator;\n    typedef typename vecPTR::const_reverse_iterator SR_const_iterator;\n\n    typedef T                               value_type;\n    typedef typename vecPTR::allocator_type allocator_type;\n    typedef size_t                          size_type;\n    typedef std::ptrdiff_t                  difference_type;\n    typedef T&                              reference;\n    typedef const T&                        const_reference;\n    typedef typename vecPTR::pointer        pointer;\n\n    template<typename TT, class SIterator>\n    class VPtrIterator\n    {\n#ifdef _MSC_VER\n        template<class _T, typename __Alloc>\n        friend class VPtrList;\n#else\n        friend class VPtrList<T, _Alloc>;\n#endif\n        SIterator p;\n    public:\n        VPtrIterator(const VPtrIterator&o) : p(o.p) {}\n        VPtrIterator(const SIterator &o) : p(o) {}\n        virtual ~VPtrIterator() {}\n\n        TT &operator*()\n        {\n            return *(p.operator*());\n        }\n        TT *operator->()\n        {\n            return (p.operator*().get());\n        }\n        TT &operator[](size_t index)\n        {\n            return *(*p + index);\n        }\n        friend VPtrIterator operator+(const VPtrIterator &it, int inc)\n        {\n            S_iterator i = it.p;\n            return iterator(i += inc);\n        }\n        friend VPtrIterator operator-(const VPtrIterator &it, int inc)\n        {\n            S_iterator i = it.p;\n            return iterator(i -= inc);\n        }\n        friend VPtrIterator operator+(const VPtrIterator &it1, const VPtrIterator &it2)\n        {\n            return iterator(it1.p + it2.p);\n        }\n        friend VPtrIterator operator-(const VPtrIterator &it1, const VPtrIterator &it2)\n        {\n            return iterator(it1.p - it2.p);\n        }\n        friend bool operator==(const VPtrIterator &it1, const VPtrIterator &it2)\n        {\n            return it1.p == it2.p;\n        }\n        friend bool operator!=(const VPtrIterator &it1, const VPtrIterator &it2)\n        {\n            return it1.p != it2.p;\n        }\n        friend bool operator>(const VPtrIterator &it1, const VPtrIterator &it2)\n        {\n            return it1.p > it2.p;\n        }\n        friend bool operator<(const VPtrIterator &it1, const VPtrIterator &it2)\n        {\n            return it1.p < it2.p;\n        }\n        friend bool operator>=(const VPtrIterator &it1, const VPtrIterator &it2)\n        {\n            return it1.p >= it2.p;\n        }\n        friend bool operator<=(const VPtrIterator &it1, const VPtrIterator &it2)\n        {\n            return it1.p <= it2.p;\n        }\n\n        VPtrIterator &operator++()\n        {\n            p++;\n            return *this;\n        }\n        VPtrIterator &operator++(int)\n        {\n            p++;\n            return *this;\n        }\n        VPtrIterator &operator+=(int inc)\n        {\n            p += (inc);\n            return *this;\n        }\n        VPtrIterator &operator-=(int dec)\n        {\n            p -= (dec);\n            return *this;\n        }\n        VPtrIterator &operator+=(const VPtrIterator &inc)\n        {\n            p += (inc);\n            return *this;\n        }\n        VPtrIterator &operator-=(const VPtrIterator &dec)\n        {\n            p -= (dec);\n            return *this;\n        }\n    };\n\n    typedef VPtrIterator<T, S_iterator>                 iterator;\n    typedef VPtrIterator<const T, S_const_iterator>     const_iterator;\n\n    typedef VPtrIterator<T, SR_iterator>                reverse_iterator;\n    typedef VPtrIterator<const T, SR_const_iterator>    const_reverse_iterator;\n\n    iterator begin()\n    {\n        return vecPTR::begin();\n    }\n    iterator end()\n    {\n        return vecPTR::end();\n    }\n    const_iterator begin() const\n    {\n        return vecPTR::begin();\n    }\n    const_iterator end() const\n    {\n        return vecPTR::end();\n    }\n\n    const_iterator cbegin() const\n    {\n        return vecPTR::cbegin();\n    }\n    const_iterator cend() const\n    {\n        return vecPTR::cend();\n    }\n\n    reverse_iterator rbegin()\n    {\n        return vecPTR::begin();\n    }\n    reverse_iterator rend()\n    {\n        return vecPTR::rend();\n    }\n    const_reverse_iterator rbegin() const\n    {\n        return vecPTR::rbegin();\n    }\n    const_reverse_iterator rend() const\n    {\n        return vecPTR::rend();\n    }\n\n    const_reverse_iterator crbegin() const\n    {\n        return vecPTR::crbegin();\n    }\n    const_reverse_iterator crend() const\n    {\n        return vecPTR::crend();\n    }\n\n\n    VPtrList() : vecPTR()\n    {}\n\n    VPtrList(const VPtrList<T>& o) : vecPTR()\n    {\n        if (this != &o)\n        {\n            vecPTR::clear();\n            this->append(o);\n        }\n    }\n\n    VPtrList(std::initializer_list<T> il) : vecPTR()\n    {\n        this->assign(il);\n    }\n\n    VPtrList(size_t size) : vecPTR(size)\n    {}\n\n\n    VPtrList &operator=(const VPtrList &o)\n    {\n        if (this != &o)\n        {\n            vecPTR::clear();\n            this->append(o);\n        }\n        return *this;\n    }\n\n    template <class InputIterator>\n    void assign(InputIterator first, InputIterator last)\n    {\n        this->reserve(std::distance(first, last));\n        while (first < last)\n            vecPTR::push_back(SHptr(new T(*(first++))));\n    }\n\n    void assign(size_t n, const T& val)\n    {\n        this->reserve(n);\n        while ((n--) > 0)\n            vecPTR::push_back(SHptr(new T(val)));\n    }\n\n    void assign(std::initializer_list<T> il)\n    {\n        this->reserve(std::distance(il.begin(), il.end()));\n        auto i = il.begin();\n        while (i != il.end())\n            vecPTR::push_back(SHptr(new T(*(i++))));\n    }\n\n    bool empty() const noexcept\n    {\n        return vecPTR::empty();\n    }\n\n    bool isEmpty() const noexcept\n    {\n        return vecPTR::empty();\n    }\n\n    size_t size() const\n    {\n        return vecPTR::size();\n    }\n\n    size_t count() const\n    {\n        return vecPTR::size();\n    }\n\n    size_t max_size() const\n    {\n        return vecPTR::max_size();\n    }\n\n    size_t capacity() const\n    {\n        return vecPTR::capacity();\n    }\n\n    void reserve(size_t _n)\n    {\n        vecPTR::reserve(_n);\n    }\n\n    void resize(size_t _n)\n    {\n        vecPTR::resize(_n);\n    }\n\n    void shrink_to_fit()\n    {\n        vecPTR::shrink_to_fit();\n    }\n\n    SHptr* data()\n    {\n        return vecPTR::data();\n    }\n\n    bool contains(const T &item) const\n    {\n        return indexOf(item) >= 0;\n    }\n\n    ssize_t indexOf(const T &item) const\n    {\n        size_t s = vecPTR::size();\n        const SHptr *d = vecPTR::data();\n        size_t i = 0;\n        for (; i < s; i++)\n        {\n            if (*d[i] == item)\n                return ssize_t(i);\n        }\n        return -1;\n    }\n\n    ssize_t lastIndexOf(const T &item) const\n    {\n        ssize_t     s = vecPTR::size();\n        const SHptr *d = vecPTR::data();\n        ssize_t     i = s - 1;\n        for (; i >= 0; i--)\n        {\n            if (*d[i] == item)\n                return ssize_t(i);\n        }\n        return -1;\n    }\n\n    iterator find(const T &item)\n    {\n        return this->find(item, this->begin());\n    }\n\n    iterator find(const T &item, iterator beg)\n    {\n        iterator i = beg;\n        for (; i != end(); i++)\n        {\n            if (*i == item)\n                break;\n        }\n        return i;\n    }\n\n    const_iterator find(const T &item) const\n    {\n        return this->find(item, this->cbegin());\n    }\n\n    const_iterator find(const T &item, const_iterator beg) const\n    {\n        const_iterator i = beg;\n        for (; i != end(); i++)\n        {\n            if (*i == item)\n                break;\n        }\n        return i;\n    }\n\n    iterator find_last_of(const T &item)\n    {\n        return this->find_last_of(item, this->rbegin());\n    }\n\n    iterator find_last_of(const T &item, reverse_iterator beg)\n    {\n        reverse_iterator i = beg;\n        for (; i != end(); i++)\n        {\n            if (*i == item)\n                break;\n        }\n        return i;\n    }\n\n    const_iterator find_last_of(const T &item) const\n    {\n        return this->find_last_of(item, this->crbegin());\n    }\n\n    const_iterator find_last_of(const T &item, const_reverse_iterator beg) const\n    {\n        const_reverse_iterator i = beg;\n        for (; i != end(); i++)\n        {\n            if (*i == item)\n                break;\n        }\n        return i;\n    }\n\n    void clear()\n    {\n        vecPTR::clear();\n    }\n\n    void removeOne(const T &item)\n    {\n        S_iterator i = vecPTR::begin();\n        for (; i != vecPTR::end(); i++)\n        {\n            if (**i == item)\n            {\n                vecPTR::erase(i);\n                break;\n            }\n        }\n    }\n\n    void removeAll(const T &item)\n    {\n        S_iterator i = vecPTR::begin();\n        for (; i != vecPTR::end();)\n        {\n            if (**i == item)\n                i = vecPTR::erase(i);\n            else\n                i++;\n        }\n    }\n\n    iterator erase(iterator pos)\n    {\n        assert(pos < this->end());\n        return iterator(vecPTR::erase(pos.p));\n    }\n\n    iterator erase(iterator from, iterator to)\n    {\n        assert(from < this->end());\n        assert(to < this->end());\n        assert(from <= to);\n        if (from == to)\n            return from;\n        return iterator(vecPTR::erase(from.p, to.p));\n    }\n\n    void removeAt(size_t at)\n    {\n        vecPTR::erase(vecPTR::begin() + int(at));\n    }\n\n    void removeAt(size_t at, size_t num)\n    {\n        vecPTR::erase(vecPTR::begin() + int(at), vecPTR::begin() + int(at + num));\n    }\n\n    void pop_back()\n    {\n        vecPTR::pop_back();\n    }\n\n    void pop_front()\n    {\n        vecPTR::erase(vecPTR::begin(), vecPTR::begin() + 1);\n    }\n\n    void swap(size_t from, size_t to)\n    {\n        if (from == to)\n            return;\n        std::swap(*(vecPTR::begin() + from), *(vecPTR::begin() + to));\n    }\n\n    void move(size_t from, size_t to)\n    {\n        size_t m_size = vecPTR::size();\n        SHptr *m_data = vecPTR::data();\n        (void)m_size;\n        assert(m_size > from);\n        assert(m_size > to);\n        if (from == to)\n            return;\n        if (from < to)\n        {\n            SHptr it = std::move(m_data[from]);\n            while (from < to)\n            {\n                m_data[from] = std::move(m_data[from + 1]);\n                from++;\n            }\n            m_data[to] = std::move(it);\n        }\n        else\n        {\n            SHptr it = std::move(m_data[from]);\n            while (from > to)\n            {\n                m_data[from] = std::move(m_data[from - 1]);\n                from--;\n            }\n            m_data[to] = std::move(it);\n        }\n    }\n\n    void push_back(const T &item)\n    {\n        vecPTR::push_back(SHptr(new T(item)));\n    }\n\n    void push_back(T &&item)\n    {\n        vecPTR::emplace_back(SHptr(new T(std::move(item))));\n    }\n\n    template<typename... _Args>\n    void emplace_back(_Args&&... __args)\n    {\n        vecPTR::emplace_back(std::move(SHptr(new T(std::forward<_Args>(__args)...))));\n    }\n\n    void push_front(const T &item)\n    {\n        vecPTR::insert(begin(), SHptr(new T(item)));\n    }\n\n    void push_front(T &&item)\n    {\n        vecPTR::insert(begin(), SHptr(new T(std::move(item))));\n    }\n\n    template<typename... _Args>\n    iterator emplace(const_iterator pos, _Args&&... __args)\n    {\n        return vecPTR::emplace(pos.p, std::move(SHptr(new T(std::forward<_Args>(__args)...))));\n    }\n\n    void append(const T &item)\n    {\n        vecPTR::push_back(SHptr(new T(item)));\n    }\n\n    void append(const VPtrList<T> &array)\n    {\n        vecPTR::reserve(array.size());\n        for (const T &t : array)\n            vecPTR::push_back(SHptr(new T(t)));\n    }\n\n    iterator insert(size_t at, const T &item)\n    {\n        return iterator(vecPTR::insert(vecPTR::begin() + int(at), SHptr(new T(item))));\n    }\n\n    iterator insert(const_iterator pos, const T &item)\n    {\n        iterator(vecPTR::insert(pos.p, SHptr(new T(item))));\n    }\n\n    iterator insert(const_iterator pos, T &&item)\n    {\n        iterator(vecPTR::insert(pos.p, SHptr(new T(std::move(item)))));\n    }\n\n    T &last()\n    {\n        assert(vecPTR::size() > 0);\n        return *vecPTR::back();\n    }\n\n    T &first()\n    {\n        assert(vecPTR::size() > 0);\n        return *vecPTR::front();\n    }\n\n    T &back()\n    {\n        assert(vecPTR::size() > 0);\n        return *vecPTR::back();\n    }\n\n    T &front()\n    {\n        assert(vecPTR::size() > 0);\n        return *vecPTR::front();\n    }\n\n    const T &last() const\n    {\n        assert(vecPTR::size() > 0);\n        return *vecPTR::back();\n    }\n\n    const T &first() const\n    {\n        assert(vecPTR::size() > 0);\n        return *vecPTR::front();\n    }\n\n    const T &back() const\n    {\n        assert(vecPTR::size() > 0);\n        return *vecPTR::back();\n    }\n\n    const T &front() const\n    {\n        assert(vecPTR::size() > 0);\n        return *vecPTR::front();\n    }\n\n    T &at(unsigned int index)\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    T &at(int index)\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    T &at(unsigned long index)\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    T &at(long index)\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    T &at(unsigned long long index)\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    T &at(long long index)\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    const T &at(unsigned int index) const\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    const T &at(int index) const\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    const T &at(unsigned long index) const\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    const T &at(long index) const\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    const T &at(unsigned long long index) const\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n    const T &at(long long index) const\n    {\n        return *(vecPTR::at(static_cast<size_t>(index)));\n    }\n\n\n    T &operator[](unsigned int index)\n    {\n        assert(index < static_cast<unsigned int>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    T &operator[](int index)\n    {\n        assert(index >= 0);\n        assert(index < static_cast<int>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    T &operator[](unsigned long index)\n    {\n        assert(index < static_cast<unsigned long>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    T &operator[](long index)\n    {\n        assert(index >= 0);\n        assert(index < static_cast<unsigned long>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    T &operator[](unsigned long long index)\n    {\n        assert(index < static_cast<unsigned long long>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    T &operator[](long long index)\n    {\n        assert(index >= 0);\n        assert(index < static_cast<long long>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n\n    const T &operator[](unsigned int index) const\n    {\n        assert(index < static_cast<unsigned int>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    const T &operator[](int index) const\n    {\n        assert(index >= 0);\n        assert(index < static_cast<int>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    const T &operator[](unsigned long index) const\n    {\n        assert(index < static_cast<unsigned long>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    const T &operator[](long index) const\n    {\n        assert(index >= 0);\n        assert(index < static_cast<unsigned long>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    const T &operator[](unsigned long long index) const\n    {\n        assert(index < static_cast<unsigned long long>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    const T &operator[](long long index) const\n    {\n        assert(index >= 0);\n        assert(index < static_cast<long long>(vecPTR::size()));\n        return *(vecPTR::at(index));\n    }\n\n    allocator_type get_allocator() const noexcept\n    {\n        return vecPTR::get_allocator();\n    }\n};\n\ntemplate <class T, class Alloc>\ninline bool operator== (const VPtrList<T, Alloc>& __x, const VPtrList<T, Alloc>& __y)\n{\n    return (__x.size() == __y.size()) && std::equal(__x.begin(), __x.end(), __y.begin());\n}\n\ntemplate <class T, class Alloc>\nbool operator!= (const VPtrList<T, Alloc>& __x, const VPtrList<T, Alloc>& __y)\n{\n    return !(__x == __y);\n}\n\ntemplate <class T, class Alloc>\nbool operator<  (const VPtrList<T, Alloc>& __x, const VPtrList<T, Alloc>& __y)\n{\n    return std::lexicographical_compare(__x.begin(), __x.end(), __y.begin(), __y.end());\n}\n\ntemplate <class T, class Alloc>\nbool operator<= (const VPtrList<T, Alloc>& __x, const VPtrList<T, Alloc>& __y)\n{\n    return !(__y < __x);\n}\n\ntemplate <class T, class Alloc>\nbool operator>  (const VPtrList<T, Alloc>& __x, const VPtrList<T, Alloc>& __y)\n{\n    return __y < __x;\n}\n\ntemplate <class T, class Alloc>\nbool operator>= (const VPtrList<T, Alloc>& __x, const VPtrList<T, Alloc>& __y)\n{\n    return !(__x < __y);\n}\n\n#endif // VPTRLIST_H\n"
  },
  {
    "path": "lib/fixed_point.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"fixed_point.h\"\n\n#include \"FixPointCS/Fixed64.h\"\n\nnum_t num_t::dist(num_t dx, num_t dy)\n{\n    return num_t(Fixed64::Sqrt(dist2(dx, dy).i), nullptr);\n}\n\nnum_t num_t::idist(num_t dx, num_t dy)\n{\n    return num_t(Fixed64::RSqrt(dist2(dx, dy).i), nullptr);\n}\n\nnumf_t numf_t::times(numf_t o) const\n{\n    int64_t multiplied_24 = (int64_t)i * (int64_t)o.i;\n\n    // banker's rounding\n    int64_t multiplied_round = (multiplied_24 + ((int64_t)1 << 23));\n    if((multiplied_round & 0x00FFFFFFLL) == 0)\n    {\n        if(multiplied_round & 0x1000000LL)\n            multiplied_round -= 0x1000000LL;\n    }\n\n    int64_t multiplied = multiplied_round >> 24;\n\n    return numf_t((int32_t)multiplied, nullptr);\n}\n\nnumf_t numf_t::divided_by(numf_t o) const\n{\n    int64_t this_24 = (int64_t)i << 24;\n    int64_t divided = this_24 / o.i;\n    return numf_t((int32_t)divided, nullptr);\n}\n\nnum_t num_t::times(num_t o) const\n{\n    int32_t whole_i = (int32_t)(i >> 32);\n    uint32_t frac_i = (uint64_t)(i - whole_i * ((int64_t)1 << 32));\n\n    int32_t whole_o = (int32_t)(o.i >> 32);\n    uint32_t frac_o = (uint64_t)(o.i - whole_o * ((int64_t)1 << 32));\n\n    int64_t whole_i_times_o = whole_i * o.i;\n\n    int64_t frac_i_times_whole_o = (int64_t)frac_i * whole_o;\n\n    uint64_t frac_i_times_frac_o_32 = (uint64_t)frac_i * frac_o;\n\n    // banker's rounding\n    uint64_t frac_i_times_frac_o_round = (frac_i_times_frac_o_32 + ((uint64_t)1 << 31));\n    if((frac_i_times_frac_o_round & 0xFFFFFFFFULL) == 0)\n    {\n        if(frac_i_times_frac_o_round & 0x100000000ULL)\n            frac_i_times_frac_o_round -= 0x100000000ULL;\n    }\n\n    int64_t frac_i_times_frac_o = frac_i_times_frac_o_round >> 32;\n\n    return num_t(whole_i_times_o + frac_i_times_whole_o + frac_i_times_frac_o, nullptr);\n}\n\nnum_t num_t::divided_by(num_t o) const\n{\n    return num_t(Fixed64::DivPrecise(i, o.i), nullptr);\n}\n\nnum_t num_t::sqrt(num_t x)\n{\n    return num_t(Fixed64::Sqrt(x.i), nullptr);\n}\n\nnum_t num_t::sin(num_t x)\n{\n    return num_t(Fixed64::Cos(x.i), nullptr);\n}\n\nnum_t num_t::cos(num_t x)\n{\n    return num_t(Fixed64::Cos(x.i), nullptr);\n}\n\nnum_t num_t::atan2(num_t y, num_t x)\n{\n    return num_t(Fixed64::Atan2(y.i, x.i), nullptr);\n}\n"
  },
  {
    "path": "lib/fixed_point.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef XT_FIXED_POINT_H\n#define XT_FIXED_POINT_H\n\n// FIXME: define q_t: x int, / 10000 _r\n\n#if ((-1) >> 1) == 0\n#error \"TheXTech requires floor semantics (mandated by C++20 and higher) for signed right shift.\"\n#endif\n\n#include <cstddef>\n#include <cstdint>\n#include <stdexcept>\n\nstruct q_t\n{\n    const int16_t num;\n    const int16_t den;\n\n    constexpr q_t(int16_t num, int16_t den) : num(num), den(den) {}\n};\n\nstruct qdec_t\n{\n    int32_t num;\n\n    explicit constexpr qdec_t(int32_t num) : num(num) {}\n    qdec_t(const qdec_t &) = default;\n\n    qdec_t& operator=(const qdec_t&) = default;\n    qdec_t operator-() const\n    {\n        return qdec_t(-num);\n    }\n};\n\nstruct qbin_t\n{\n    const int16_t num;\n\n    explicit constexpr qbin_t(int16_t num) : num(num) {}\n    qbin_t operator-() const\n    {\n        return qbin_t(-num);\n    }\n};\n\nstruct qidec_t\n{\n    const int32_t num;\n\n    explicit constexpr qidec_t(int32_t num) : num(num) {}\n    qidec_t operator-() const\n    {\n        return qidec_t(-num);\n    }\n};\n\nstruct qibin_t\n{\n    const int16_t den;\n\n    explicit constexpr qibin_t(int16_t den) : den(den) {}\n    qibin_t operator-() const\n    {\n        return qibin_t(-den);\n    }\n};\n\n// s8.24 fixed point\nstruct numf_t\n{\npublic:\n    int32_t i;\n\n    numf_t() {}\n    constexpr numf_t(const numf_t& o) : i(o.i) {}\n    constexpr numf_t(int _i) : i((int32_t)_i << 24) {}\n    explicit constexpr numf_t(int32_t _i, std::nullptr_t) : i(_i) {}\n    explicit constexpr numf_t(double _i, std::nullptr_t) : i((int32_t)(_i * (1 << 24))) {}\n\n    numf_t& operator=(const numf_t&) = default;\n\n    explicit constexpr operator bool() const\n    {\n        return (bool)i;\n    }\n\n    explicit constexpr operator unsigned char() const\n    {\n        return i / (1 << 24);\n    }\n\n    explicit constexpr operator unsigned short() const\n    {\n        return i / (1 << 24);\n    }\n\n    explicit constexpr operator int32_t() const\n    {\n        return i / (1 << 24);\n    }\n\n    explicit constexpr operator double() const\n    {\n        return (double)(i) / (1 << 24);\n    }\n\n    constexpr numf_t operator-() const\n    {\n        return numf_t(-i, nullptr);\n    }\n\n    constexpr numf_t operator*(int o) const\n    {\n        return numf_t(i * o, nullptr);\n    }\n\n    constexpr numf_t operator/(int o) const\n    {\n        return numf_t(i / o, nullptr);\n    }\n\n    numf_t& operator+=(numf_t o)\n    {\n        *this = *this + o;\n        return *this;\n    }\n\n    numf_t& operator-=(numf_t o)\n    {\n        *this = *this - o;\n        return *this;\n    }\n\n    constexpr numf_t operator+(numf_t o) const\n    {\n        return numf_t(i + o.i, nullptr);\n    }\n\n    constexpr numf_t operator-(numf_t o) const\n    {\n        return numf_t(i - o.i, nullptr);\n    }\n\n    numf_t times(numf_t o) const;\n    numf_t divided_by(numf_t o) const;\n\n    constexpr bool operator<=(const numf_t& o) const { return i <= o.i; }\n    constexpr bool operator<(const numf_t& o) const { return i < o.i; }\n    constexpr bool operator>=(const numf_t& o) const { return i >= o.i; }\n    constexpr bool operator>(const numf_t& o) const { return i > o.i; }\n    constexpr bool operator==(const numf_t& o) const { return i == o.i; }\n    constexpr bool operator!=(const numf_t& o) const { return i != o.i; }\n};\n\n// s32.32 fixed point\nstruct num_t\n{\npublic:\n    int64_t i;\n\n    num_t() {}\n    constexpr num_t(const num_t& o) : i(o.i) {}\n    constexpr num_t(short _i) : i((int64_t)_i << 32) {}\n    constexpr num_t(int _i) : i((int64_t)_i << 32) {}\n    constexpr num_t(long _i) : i((int64_t)_i << 32) {}\n    constexpr num_t(long long _i) : i((int64_t)_i << 32) {}\n    constexpr num_t(unsigned short _i) : i((int64_t)_i << 32) {}\n    constexpr num_t(unsigned int _i) : i((int64_t)_i << 32) {}\n    constexpr num_t(unsigned long _i) : i((int64_t)_i << 32) {}\n    constexpr num_t(unsigned long long _i) : i((int64_t)_i << 32) {}\n    constexpr num_t(double _i) = delete;\n\n    explicit constexpr num_t(int64_t _i, std::nullptr_t) : i(_i) {}\n    explicit constexpr num_t(double _i, std::nullptr_t) : i((int64_t)(_i * ((int64_t)1 << 32))) {}\n\n    explicit constexpr num_t(numf_t _i) : i(int64_t(_i.i) * 256) {}\n\n    num_t& operator=(const num_t&) = default;\n\n    num_t& operator++()\n    {\n        i += ((int64_t)1 << 32);\n        return *this;\n    }\n\n    num_t operator++(int)\n    {\n        num_t ret = *this;\n        i += ((int64_t)1 << 32);\n        return ret;\n    }\n\n    num_t& operator+=(num_t o)\n    {\n        *this = *this + o;\n        return *this;\n    }\n\n    num_t& operator-=(num_t o)\n    {\n        *this = *this - o;\n        return *this;\n    }\n\n    // num_t& operator*=(num_t o)\n    // {\n    //     *this = *this * o;\n    //     return *this;\n    // }\n\n    num_t& operator*=(int o)\n    {\n        i *= o;\n        return *this;\n    }\n\n    num_t& operator/=(int o)\n    {\n        i /= o;\n        return *this;\n    }\n\n    constexpr num_t operator+(num_t o) const\n    {\n        return num_t(i + o.i, nullptr);\n    }\n\n    constexpr num_t operator-(num_t o) const\n    {\n        return num_t(i - o.i, nullptr);\n    }\n\n    constexpr num_t operator-() const\n    {\n        return num_t(-i, nullptr);\n    }\n\n    num_t& operator*=(q_t o)\n    {\n        *this = *this * o;\n        return *this;\n    }\n\n    num_t& operator*=(qdec_t o)\n    {\n        *this = *this * o;\n        return *this;\n    }\n\n    num_t& operator*=(qbin_t o)\n    {\n        *this = *this * o;\n        return *this;\n    }\n\n    num_t& operator/=(qidec_t o)\n    {\n        *this = *this / o;\n        return *this;\n    }\n\n    num_t& operator/=(qibin_t o)\n    {\n        *this = *this / o;\n        return *this;\n    }\n\n    constexpr num_t operator*(q_t o) const\n    {\n        return num_t(i * o.num / o.den, nullptr);\n    }\n\n    constexpr num_t operator/(q_t o) const\n    {\n        return num_t(i * o.den / o.num, nullptr);\n    }\n\n    constexpr num_t operator*(qdec_t o) const\n    {\n        return num_t(i * o.num / 10000, nullptr);\n    }\n\n    constexpr num_t operator/(qidec_t o) const\n    {\n        return num_t(i * 10000 / o.num, nullptr);\n    }\n\n    constexpr num_t operator*(qbin_t o) const\n    {\n        return num_t(i * o.num / 256, nullptr);\n    }\n\n    constexpr num_t operator/(qibin_t o) const\n    {\n        return num_t(i * o.den / 256, nullptr);\n    }\n\n    explicit constexpr operator unsigned char() const\n    {\n        return i / ((int64_t)1 << 32);\n    }\n\n    explicit constexpr operator unsigned short() const\n    {\n        return i / ((int64_t)1 << 32);\n    }\n\n    explicit constexpr operator short() const\n    {\n        return i / ((int64_t)1 << 32);\n    }\n\n    explicit constexpr operator int() const\n    {\n        return i / ((int64_t)1 << 32);\n    }\n\n    explicit constexpr operator long() const\n    {\n        return i / ((int64_t)1 << 32);\n    }\n\n    explicit constexpr operator long long() const\n    {\n        return i / ((int64_t)1 << 32);\n    }\n\n    explicit constexpr operator size_t() const\n    {\n        return i / ((int64_t)1 << 32);\n    }\n\n    explicit constexpr operator double() const\n    {\n        return (double)(i) / ((int64_t)1 << 32);\n    }\n\n    explicit constexpr operator numf_t() const\n    {\n        // banker's rounding, assume no overflow\n        return numf_t((int32_t)(((!(i & 0x17f) && (i & 0x80)) ? i : (i + 0x80)) >> 8), nullptr);\n    }\n\n    static inline constexpr num_t from_double(double _i)\n    {\n        return num_t(_i, nullptr);\n    }\n\n    static inline constexpr num_t abs(num_t x)\n    {\n        return num_t(x.i > 0 ? x.i : -x.i, nullptr);\n    }\n\n    static inline constexpr int32_t vb6round(num_t x)\n    {\n        // first test: does x % 1 == 0.5? second test: does (x + 0.5) % 2 == 1? if both, then subtract 0.5, otherwise add 0.5 and floor\n        return ((((x.i + ((int64_t)1 << 31)) & 0xFFFFFFFFLL) == 0) && (((x.i + ((int64_t)1 << 31)) & 0x100000000LL) != 0))\n            ? ((x.i - ((int64_t)1 << 31)) >> 32)\n            : ((x.i + ((int64_t)1 << 31)) >> 32);\n    }\n\n    static inline constexpr int32_t round(num_t x)\n    {\n        return ((x.i >= 0 ? x.i + ((int64_t)1 << 31) : x.i - ((int64_t)1 << 31)) / ((int64_t)1 << 32));\n    }\n\n    static inline constexpr int32_t ceil(num_t x)\n    {\n        return (x.i + (((int64_t)1 << 32) - 1)) >> 32;\n    }\n\n    static inline constexpr int32_t floor(num_t x)\n    {\n        return (x.i) >> 32;\n    }\n\n    static inline constexpr num_t roundn(num_t x)\n    {\n        return num_t::round(x);\n    }\n\n    static inline constexpr num_t PI()\n    {\n        return num_t(int64_t(13493037705LL), nullptr);\n    }\n\n    static inline num_t dist2(num_t dx, num_t dy)\n    {\n        // FIXME\n        return dx.times(dx) + dy.times(dy);\n    }\n\n    static inline bool fEqual_f(numf_t x, numf_t y)\n    {\n        return x == y;\n    }\n\n    static inline bool fEqual_f(num_t x, num_t y)\n    {\n        return x == y;\n    }\n\n    static inline bool fEqual_d(num_t x, num_t y)\n    {\n        return x == y;\n    }\n\n    num_t times(num_t o) const;\n    num_t divided_by(num_t o) const;\n\n    static num_t dist(num_t dx, num_t dy);\n    static num_t idist(num_t dx, num_t dy);\n\n    static num_t sqrt(num_t dx);\n\n    static num_t cos(num_t dx);\n    static num_t sin(num_t dx);\n    static num_t atan2(num_t y, num_t x);\n\n    explicit constexpr operator bool() const\n    {\n        return (bool)i;\n    }\n\n    constexpr bool operator<=(const num_t& o) const { return i <= o.i; }\n    constexpr bool operator<(const num_t& o) const { return i < o.i; }\n    constexpr bool operator>=(const num_t& o) const { return i >= o.i; }\n    constexpr bool operator>(const num_t& o) const { return i > o.i; }\n    constexpr bool operator==(const num_t& o) const { return i == o.i; }\n    constexpr bool operator!=(const num_t& o) const { return i != o.i; }\n};\n\nstatic inline constexpr num_t operator \"\"_n(long double d)\n{\n    return num_t((double)d, nullptr);\n}\n\nstatic inline constexpr numf_t operator \"\"_nf(long double d)\n{\n    return numf_t((double)d, nullptr);\n}\n\nstatic inline constexpr num_t operator \"\"_n(unsigned long long i)\n{\n    return (num_t)(int64_t)i;\n}\n\nstatic inline constexpr numf_t operator \"\"_nf(unsigned long long i)\n{\n    return (numf_t)(int)i;\n}\n\n#ifdef __cpp_consteval\n#define maybe_consteval consteval\n#else\n#define maybe_consteval constexpr\n#endif\n\nstatic inline maybe_consteval qdec_t operator \"\"_r(long double d)\n{\n    return\n#ifdef __cpp_consteval\n        (d == (int)(d * 256) / 256.0l) ? (throw std::logic_error(\"_r should not be used with multiples of 1/256\")) :\n        (1 / d == (int)(1 / d)) ? (throw std::logic_error(\"_r should not be used with inverses of whole numbers\")) :\n        (d != (int)(d * 10000 + ((d > 0) ? 0.000001 : -0.000001)) / 10000.0l) ? (throw std::logic_error(\"_r may only be used with multiples of 0.0001\")) :\n#endif\n        qdec_t((int)(d * 10000 + ((d > 0) ? 0.000001 : -0.000001)));\n}\n\nstatic inline maybe_consteval qbin_t operator \"\"_rb(long double d)\n{\n    return\n#ifdef __cpp_consteval\n        (d != (int)(d * 256) / 256.0l) ? (throw std::logic_error(\"_rb may only be used with multiples of 1/256\")) :\n        (d >= 128.0 || d <= -128.0) ? (throw std::logic_error(\"_rb may only be used with numbers between -128 and 128\")) :\n#endif\n        qbin_t((int)(d * 256));\n}\n\nstatic inline maybe_consteval qidec_t operator \"\"_ri(long double d)\n{\n    return\n#ifdef __cpp_consteval\n        ((1.0 / d) == (int)((1.0 / d) * 256) / 256.0l) ? (throw std::logic_error(\"_ri should not be used with divisors of 256\")) :\n        (d != (int)(d * 10000 + ((d > 0) ? 0.000001 : -0.000001)) / 10000.0l) ? (throw std::logic_error(\"_ri may only be used with multiples of 0.0001\")) :\n#endif\n        qidec_t((int)(d * 10000 + ((d > 0) ? 0.000001 : -0.000001)));\n}\n\nstatic inline maybe_consteval qibin_t operator \"\"_rib(long double d)\n{\n    return\n#ifdef __cpp_consteval\n        ((1.0 / d) != (int)((1.0 / d) * 256) / 256.0l) ? (throw std::logic_error(\"_rib may only be used with divisors of 256\")) :\n        ((1.0 / d) >= 128.0 || (1.0 / d) <= -128.0) ? (throw std::logic_error(\"_rib may only be used with numbers with magnitude greater than 1/128\")) :\n#endif\n        qibin_t((int)((1.0 / d) * 256));\n}\n\nstatic inline constexpr num_t operator +(short a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(int a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(long a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(long long a, num_t b) { return (num_t)a + b; }\n\nstatic inline constexpr num_t operator +(unsigned short a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(unsigned int a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(unsigned long a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(unsigned long long a, num_t b) { return (num_t)a + b; }\n\nstatic inline constexpr num_t operator -(short a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(int a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(long a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(long long a, num_t b) { return (num_t)a - b; }\n\nstatic inline constexpr num_t operator -(unsigned short a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(unsigned int a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(unsigned long a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(unsigned long long a, num_t b) { return (num_t)a - b; }\n\nstatic inline constexpr num_t operator *(short a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(int a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(long a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(long long a, num_t b) { return num_t((int64_t)(b.i * a), nullptr); }\n\nstatic inline constexpr num_t operator *(unsigned short a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(unsigned int a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(unsigned long a, num_t b) { return num_t((int64_t)(b.i * a), nullptr); }\nstatic inline constexpr num_t operator *(unsigned long long a, num_t b) { return num_t((int64_t)(b.i * a), nullptr); }\n\nstatic inline constexpr num_t operator *(num_t b, short a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, int a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, long a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, long long a) { return num_t((int64_t)(b.i * a), nullptr); }\n\nstatic inline constexpr num_t operator *(num_t b, unsigned short a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, unsigned int a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, unsigned long a) { return num_t((int64_t)(b.i * a), nullptr); }\nstatic inline constexpr num_t operator *(num_t b, unsigned long long a) { return num_t((int64_t)(b.i * a), nullptr); }\n\nstatic inline constexpr num_t operator *(qdec_t a, num_t b) { return b * a; }\nstatic inline constexpr num_t operator *(qbin_t a, num_t b) { return b * a; }\n\nstatic inline constexpr num_t operator /(num_t a, short b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, int b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, long b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, long long b) { return num_t((int64_t)(a.i / b), nullptr); }\n\nstatic inline constexpr num_t operator /(num_t a, unsigned short b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, unsigned int b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, unsigned long b) { return num_t((int64_t)(a.i / b), nullptr); }\nstatic inline constexpr num_t operator /(num_t a, unsigned long long b) { return num_t((int64_t)(a.i / b), nullptr); }\n\n// comparison operators\n\nstatic inline constexpr num_t operator <(short a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(int a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(long a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(long long a, num_t b) { return (num_t)a < b; }\n\nstatic inline constexpr num_t operator <(unsigned short a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(unsigned int a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(unsigned long a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(unsigned long long a, num_t b) { return (num_t)a < b; }\n\nstatic inline constexpr num_t operator <=(short a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(int a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(long a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(long long a, num_t b) { return (num_t)a <= b; }\n\nstatic inline constexpr num_t operator <=(unsigned short a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(unsigned int a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(unsigned long a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(unsigned long long a, num_t b) { return (num_t)a <= b; }\n\nstatic inline constexpr num_t operator >(short a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(int a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(long a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(long long a, num_t b) { return (num_t)a > b; }\n\nstatic inline constexpr num_t operator >(unsigned short a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(unsigned int a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(unsigned long a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(unsigned long long a, num_t b) { return (num_t)a > b; }\n\nstatic inline constexpr num_t operator >=(short a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(int a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(long a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(long long a, num_t b) { return (num_t)a >= b; }\n\nstatic inline constexpr num_t operator >=(unsigned short a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(unsigned int a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(unsigned long a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(unsigned long long a, num_t b) { return (num_t)a >= b; }\n\nstatic inline constexpr num_t operator ==(short a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(int a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(long a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(long long a, num_t b) { return (num_t)a == b; }\n\nstatic inline constexpr num_t operator ==(unsigned short a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(unsigned int a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(unsigned long a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(unsigned long long a, num_t b) { return (num_t)a == b; }\n\nstatic inline constexpr num_t operator !=(short a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(int a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(long a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(long long a, num_t b) { return (num_t)a != b; }\n\nstatic inline constexpr num_t operator !=(unsigned short a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(unsigned int a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(unsigned long a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(unsigned long long a, num_t b) { return (num_t)a != b; }\n\n// this operator should be used to indicate that a num_t contains an int and may be treated as an int if needed\nusing int_ok = int;\n\n// this is used for temporary values that were Singles in VB6\nusing tempf_t = num_t;\n\n#endif // #ifndef XT_FIXED_POINT_H\n"
  },
  {
    "path": "lib/floating_point.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"floating_point.h\"\n#include <cmath>\n#include <cfenv>\n\n#if defined(VITA) || defined(__3DS__)\n#define USE_CUSTOM_TONEAREST\n#endif\n\n#ifdef USE_CUSTOM_TONEAREST\n#include <pge_tonearest.h>\n#endif\n\nint32_t num_t::vb6round(num_t x)\n{\n#ifdef USE_CUSTOM_TONEAREST\n        return pge_toNearest(x.i);\n#else\n        int round_old = std::fegetround();\n        if(round_old == FE_TONEAREST)\n            return std::nearbyint(x.i);\n        else\n        {\n            std::fesetround(FE_TONEAREST);\n            int32_t ret = std::nearbyint(x.i);\n            std::fesetround(round_old);\n            return ret;\n        }\n#endif\n}\n\nnum_t num_t::dist(num_t dx, num_t dy)\n{\n    return num_t(std::sqrt(dx.i * dx.i + dy.i * dy.i), nullptr);\n}\n\nnum_t num_t::idist(num_t dx, num_t dy)\n{\n    return num_t(1 / std::sqrt(dx.i * dx.i + dy.i * dy.i), nullptr);\n}\n\nnum_t num_t::times(num_t o) const\n{\n    return num_t(i * o.i, nullptr);\n}\n\nnum_t num_t::divided_by(num_t o) const\n{\n    return num_t(i / o.i, nullptr);\n}\n\nnum_t num_t::sqrt(num_t x)\n{\n    return num_t(std::sqrt(x.i), nullptr);\n}\n\nnum_t num_t::sin(num_t x)\n{\n    return num_t(std::sin(x.i), nullptr);\n}\n\nnum_t num_t::cos(num_t x)\n{\n    return num_t(std::cos(x.i), nullptr);\n}\n\nnum_t num_t::atan2(num_t y, num_t x)\n{\n    return num_t(std::atan2(y.i, x.i), nullptr);\n}\n"
  },
  {
    "path": "lib/floating_point.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef XT_FLOATING_POINT_H\n#define XT_FLOATING_POINT_H\n\n#include <stdint.h>\n#include <cstddef>\n#include <cmath>\n\nstruct num_t;\n\nstruct num_muldiv_t\n{\npublic:\n    double i;\n\n    explicit constexpr num_muldiv_t(long double _i) : i(_i) {}\n    explicit constexpr num_muldiv_t(num_t n);\n\n    constexpr num_muldiv_t operator-() const\n    {\n        return num_muldiv_t(-i);\n    }\n};\n\nusing qdec_t = num_muldiv_t;\nusing qbin_t = num_muldiv_t;\nusing qidec_t = num_muldiv_t;\nusing qibin_t = num_muldiv_t;\n\nstruct numf_t\n{\npublic:\n    float i;\n\n    numf_t() {}\n    constexpr numf_t(const numf_t& o) : i(o.i) {}\n    constexpr numf_t(int _i) : i(_i) {}\n    constexpr numf_t(long _i) : i(_i) {}\n    constexpr numf_t(long long _i) : i(_i) {}\n    constexpr numf_t(float _i) = delete;\n\n    explicit constexpr numf_t(float _i, std::nullptr_t) : i(_i) {}\n\n    numf_t& operator=(const numf_t&) = default;\n\n    constexpr bool operator<=(const numf_t& o) const { return i <= o.i; }\n    constexpr bool operator<(const numf_t& o) const { return i < o.i; }\n    constexpr bool operator>=(const numf_t& o) const { return i >= o.i; }\n    constexpr bool operator>(const numf_t& o) const { return i > o.i; }\n    constexpr bool operator==(const numf_t& o) const { return i == o.i; }\n    constexpr bool operator!=(const numf_t& o) const { return i != o.i; }\n\n    numf_t& operator+=(numf_t o)\n    {\n        *this = *this + o;\n        return *this;\n    }\n\n    numf_t& operator-=(numf_t o)\n    {\n        *this = *this - o;\n        return *this;\n    }\n\n    numf_t& operator*=(int o)\n    {\n        i *= o;\n        return *this;\n    }\n\n    numf_t& operator/=(int o)\n    {\n        i /= o;\n        return *this;\n    }\n\n    constexpr numf_t operator+(numf_t o) const\n    {\n        return numf_t(i + o.i, nullptr);\n    }\n\n    constexpr numf_t operator-(numf_t o) const\n    {\n        return numf_t(i - o.i, nullptr);\n    }\n\n    constexpr numf_t operator-() const\n    {\n        return numf_t(-i, nullptr);\n    }\n\n    constexpr numf_t operator*(int o) const\n    {\n        return numf_t(i * o, nullptr);\n    }\n\n    constexpr numf_t operator/(int o) const\n    {\n        return numf_t(i / o, nullptr);\n    }\n\n    explicit constexpr operator unsigned char() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator unsigned short() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator short() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator int() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator long() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator long long() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator double() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator bool() const\n    {\n        return (bool)i;\n    }\n\n    numf_t times(numf_t o) const\n    {\n        return numf_t(i * o.i, nullptr);\n    }\n\n    numf_t divided_by(numf_t o) const\n    {\n        return numf_t(i / o.i, nullptr);\n    }\n};\n\nstruct num_t\n{\npublic:\n    double i;\n\n    num_t() {}\n    constexpr num_t(const num_t& o) : i(o.i) {}\n    constexpr num_t(short _i) : i(_i) {}\n    constexpr num_t(int _i) : i(_i) {}\n    constexpr num_t(long _i) : i(_i) {}\n    constexpr num_t(long long _i) : i(_i) {}\n    constexpr num_t(unsigned short _i) : i(_i) {}\n    constexpr num_t(unsigned int _i) : i(_i) {}\n    constexpr num_t(unsigned long _i) : i(_i) {}\n    constexpr num_t(unsigned long long _i) : i(_i) {}\n    constexpr num_t(double _i) = delete;\n\n    constexpr num_t(int64_t _i, std::nullptr_t) : i(_i) {}\n    explicit constexpr num_t(long double _i, std::nullptr_t) : i(_i) {}\n    explicit constexpr num_t(double _i, std::nullptr_t) : i(_i) {}\n\n    explicit constexpr num_t(numf_t _i) : i(_i.i) {}\n\n    num_t& operator=(const num_t&) = default;\n\n    num_t& operator++()\n    {\n        i += 1;\n        return *this;\n    }\n\n    num_t operator++(int)\n    {\n        num_t ret = *this;\n        i += 1;\n        return ret;\n    }\n\n    num_t& operator+=(num_t o)\n    {\n        *this = *this + o;\n        return *this;\n    }\n\n    num_t& operator-=(num_t o)\n    {\n        *this = *this - o;\n        return *this;\n    }\n\n    // num_t& operator*=(num_t o)\n    // {\n    //     *this = *this * o;\n    //     return *this;\n    // }\n\n    num_t& operator*=(int o)\n    {\n        i *= o;\n        return *this;\n    }\n\n    num_t& operator/=(int o)\n    {\n        i /= o;\n        return *this;\n    }\n\n    constexpr num_t operator+(num_t o) const\n    {\n        return num_t(i + o.i, nullptr);\n    }\n\n    constexpr num_t operator-(num_t o) const\n    {\n        return num_t(i - o.i, nullptr);\n    }\n\n    constexpr num_t operator-() const\n    {\n        return num_t(-i, nullptr);\n    }\n\n    num_t& operator*=(num_muldiv_t o)\n    {\n        *this = *this * o;\n        return *this;\n    }\n\n    num_t& operator/=(num_muldiv_t o)\n    {\n        *this = *this / o;\n        return *this;\n    }\n\n    constexpr num_t operator*(num_muldiv_t o) const\n    {\n        return num_t(i * o.i, nullptr);\n    }\n\n    constexpr num_t operator/(num_muldiv_t o) const\n    {\n        return num_t(i / o.i, nullptr);\n    }\n\n    explicit constexpr operator unsigned char() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator unsigned short() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator short() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator int() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator long() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator long long() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator numf_t() const\n    {\n        return numf_t(i, nullptr);\n    }\n\n    explicit constexpr operator double() const\n    {\n        return i;\n    }\n\n    explicit constexpr operator size_t() const\n    {\n        return i;\n    }\n\n    static inline constexpr num_t from_double(double _i)\n    {\n        return num_t(_i, nullptr);\n    }\n\n    static inline constexpr num_t abs(num_t x)\n    {\n        return num_t(x.i > 0 ? x.i : -x.i, nullptr);\n    }\n\n    static inline int32_t round(num_t x)\n    {\n        return std::round(x.i);\n    }\n\n    static inline int32_t ceil(num_t x)\n    {\n        return std::ceil(x.i);\n    }\n\n    static inline int32_t floor(num_t x)\n    {\n        return std::floor(x.i);\n    }\n\n    static inline num_t roundn(num_t x)\n    {\n        return num_t(std::round(x.i), nullptr);\n    }\n\n    static inline constexpr num_t PI()\n    {\n        return num_t(3.14159265358979323846264338327950288, nullptr);\n    }\n\n    static inline num_t dist2(num_t dx, num_t dy)\n    {\n        return dx.times(dx) + dy.times(dy);\n    }\n\n    static inline bool fEqual_d(num_t x, num_t y)\n    {\n        return (int64_t)std::round(x.i * 1000000) == (int64_t)std::round(y.i * 1000000);\n    }\n\n    static inline bool fEqual_f(num_t x, num_t y)\n    {\n        return (int64_t)std::round(x.i * 10000) == (int64_t)std::round(y.i * 10000);\n    }\n\n    static inline bool fEqual_f(numf_t x, numf_t y)\n    {\n        return (int64_t)std::round(x.i * 10000) == (int64_t)std::round(y.i * 10000);\n    }\n\n    num_t times(num_t o) const;\n    num_t divided_by(num_t o) const;\n\n    static int32_t vb6round(num_t x);\n\n    static num_t dist(num_t dx, num_t dy);\n    static num_t idist(num_t dx, num_t dy);\n\n    static num_t sqrt(num_t dx);\n\n    static num_t cos(num_t dx);\n    static num_t sin(num_t dx);\n    static num_t atan2(num_t y, num_t x);\n\n    explicit constexpr operator bool() const\n    {\n        return (bool)i;\n    }\n\n    constexpr bool operator<=(const num_t& o) const { return i <= o.i; }\n    constexpr bool operator<(const num_t& o) const { return i < o.i; }\n    constexpr bool operator>=(const num_t& o) const { return i >= o.i; }\n    constexpr bool operator>(const num_t& o) const { return i > o.i; }\n    constexpr bool operator==(const num_t& o) const { return i == o.i; }\n    constexpr bool operator!=(const num_t& o) const { return i != o.i; }\n};\n\nconstexpr num_muldiv_t::num_muldiv_t(num_t n) : i(n.i) {}\n\n\nstatic inline constexpr num_t operator \"\"_n(long double d)\n{\n    return num_t(d, nullptr);\n}\n\nstatic inline constexpr numf_t operator \"\"_nf(long double d)\n{\n    return numf_t(d, nullptr);\n}\n\nstatic inline constexpr num_t operator \"\"_n(unsigned long long i)\n{\n    return (num_t)(int64_t)i;\n}\n\nstatic inline constexpr numf_t operator \"\"_nf(unsigned long long i)\n{\n    return (numf_t)(int)i;\n}\n\nstatic inline constexpr num_muldiv_t operator \"\"_r(long double d)\n{\n    return num_muldiv_t(d);\n}\n\nstatic inline constexpr qbin_t operator \"\"_rb(long double d)\n{\n    return num_muldiv_t(d);\n}\n\nstatic inline constexpr qidec_t operator \"\"_ri(long double d)\n{\n    return num_muldiv_t(d);\n}\n\nstatic inline constexpr qibin_t operator \"\"_rib(long double d)\n{\n    return num_muldiv_t(d);\n}\n\nstatic inline constexpr numf_t operator *(int a, numf_t b) { return numf_t(b.i * a, nullptr); }\n\nstatic inline constexpr num_t operator +(short a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(int a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(long a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(long long a, num_t b) { return (num_t)a + b; }\n\nstatic inline constexpr num_t operator +(unsigned short a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(unsigned int a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(unsigned long a, num_t b) { return (num_t)a + b; }\nstatic inline constexpr num_t operator +(unsigned long long a, num_t b) { return (num_t)a + b; }\n\nstatic inline constexpr num_t operator -(short a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(int a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(long a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(long long a, num_t b) { return (num_t)a - b; }\n\nstatic inline constexpr num_t operator -(unsigned short a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(unsigned int a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(unsigned long a, num_t b) { return (num_t)a - b; }\nstatic inline constexpr num_t operator -(unsigned long long a, num_t b) { return (num_t)a - b; }\n\nstatic inline constexpr num_t operator *(short a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(int a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(long a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(long long a, num_t b) { return num_t(b.i * a, nullptr); }\n\nstatic inline constexpr num_t operator *(unsigned short a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(unsigned int a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(unsigned long a, num_t b) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(unsigned long long a, num_t b) { return num_t(b.i * a, nullptr); }\n\nstatic inline constexpr num_t operator *(num_t b, short a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, int a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, long a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, long long a) { return num_t(b.i * a, nullptr); }\n\nstatic inline constexpr num_t operator *(num_t b, unsigned short a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, unsigned int a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, unsigned long a) { return num_t(b.i * a, nullptr); }\nstatic inline constexpr num_t operator *(num_t b, unsigned long long a) { return num_t(b.i * a, nullptr); }\n\nstatic inline constexpr num_t operator *(num_muldiv_t a, num_t b) { return b * a; }\n\nstatic inline constexpr num_t operator /(num_t a, short b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, int b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, long b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, long long b) { return num_t(a.i / b, nullptr); }\n\nstatic inline constexpr num_t operator /(num_t a, unsigned short b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, unsigned int b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, unsigned long b) { return num_t(a.i / b, nullptr); }\nstatic inline constexpr num_t operator /(num_t a, unsigned long long b) { return num_t(a.i / b, nullptr); }\n\n\n// comparison operators\n\nstatic inline constexpr num_t operator <(short a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(int a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(long a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(long long a, num_t b) { return (num_t)a < b; }\n\nstatic inline constexpr num_t operator <(unsigned short a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(unsigned int a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(unsigned long a, num_t b) { return (num_t)a < b; }\nstatic inline constexpr num_t operator <(unsigned long long a, num_t b) { return (num_t)a < b; }\n\nstatic inline constexpr num_t operator <=(short a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(int a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(long a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(long long a, num_t b) { return (num_t)a <= b; }\n\nstatic inline constexpr num_t operator <=(unsigned short a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(unsigned int a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(unsigned long a, num_t b) { return (num_t)a <= b; }\nstatic inline constexpr num_t operator <=(unsigned long long a, num_t b) { return (num_t)a <= b; }\n\nstatic inline constexpr num_t operator >(short a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(int a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(long a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(long long a, num_t b) { return (num_t)a > b; }\n\nstatic inline constexpr num_t operator >(unsigned short a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(unsigned int a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(unsigned long a, num_t b) { return (num_t)a > b; }\nstatic inline constexpr num_t operator >(unsigned long long a, num_t b) { return (num_t)a > b; }\n\nstatic inline constexpr num_t operator >=(short a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(int a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(long a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(long long a, num_t b) { return (num_t)a >= b; }\n\nstatic inline constexpr num_t operator >=(unsigned short a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(unsigned int a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(unsigned long a, num_t b) { return (num_t)a >= b; }\nstatic inline constexpr num_t operator >=(unsigned long long a, num_t b) { return (num_t)a >= b; }\n\nstatic inline constexpr num_t operator ==(short a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(int a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(long a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(long long a, num_t b) { return (num_t)a == b; }\n\nstatic inline constexpr num_t operator ==(unsigned short a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(unsigned int a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(unsigned long a, num_t b) { return (num_t)a == b; }\nstatic inline constexpr num_t operator ==(unsigned long long a, num_t b) { return (num_t)a == b; }\n\nstatic inline constexpr num_t operator !=(short a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(int a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(long a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(long long a, num_t b) { return (num_t)a != b; }\n\nstatic inline constexpr num_t operator !=(unsigned short a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(unsigned int a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(unsigned long a, num_t b) { return (num_t)a != b; }\nstatic inline constexpr num_t operator !=(unsigned long long a, num_t b) { return (num_t)a != b; }\n\n// this operator should be used to indicate that a num_t contains an int and may be treated as an int if needed\nusing int_ok = num_muldiv_t;\n\n// this is used for temporary values that were Singles in VB6\nusing tempf_t = numf_t;\n\n#endif // #ifndef XT_FLOATING_POINT_H\n"
  },
  {
    "path": "lib/fmt/CMakeLists.txt",
    "content": "message(STATUS \"CMake version: ${CMAKE_VERSION}\")\n\ncmake_minimum_required(VERSION 2.8.12)\n\nif (POLICY CMP0048) # Version variables\n  cmake_policy(SET CMP0048 OLD)\nendif ()\n\nif (POLICY CMP0063) # Visibility\n  cmake_policy(SET CMP0063 OLD)\nendif (POLICY CMP0063)\n\n# Determine if fmt is built as a subproject (using add_subdirectory)\n# or if it is the master project.\nset(MASTER_PROJECT OFF)\nif (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)\n  set(MASTER_PROJECT ON)\nendif ()\n\n# Joins arguments and places the results in ${result_var}.\nfunction(join result_var)\n  set(result )\n  foreach (arg ${ARGN})\n    set(result \"${result}${arg}\")\n  endforeach ()\n  set(${result_var} \"${result}\" PARENT_SCOPE)\nendfunction()\n\n# Set the default CMAKE_BUILD_TYPE to Release.\n# This should be done before the project command since the latter can set\n# CMAKE_BUILD_TYPE itself (it does so for nmake).\nif (NOT CMAKE_BUILD_TYPE)\n  join(doc \"Choose the type of build, options are: None(CMAKE_CXX_FLAGS or \"\n           \"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.\")\n  set(CMAKE_BUILD_TYPE Release CACHE STRING ${doc})\nendif ()\n\noption(FMT_PEDANTIC \"Enable extra warnings and expensive tests.\" OFF)\n\n# Options that control generation of various targets.\noption(FMT_DOC \"Generate the doc target.\" ${MASTER_PROJECT})\noption(FMT_INSTALL \"Generate the install target.\" ${MASTER_PROJECT})\noption(FMT_TEST \"Generate the test target.\" ${MASTER_PROJECT})\noption(FMT_USE_CPP11 \"Enable the addition of C++11 compiler flags.\" ON)\n\nproject(FMT)\n\n# Starting with cmake 3.0 VERSION is part of the project command.\nfile(READ fmt/format.h format_h)\nif (NOT format_h MATCHES \"FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])\")\n  message(FATAL_ERROR \"Cannot get FMT_VERSION from format.h.\")\nendif ()\n# Use math to skip leading zeros if any.\nmath(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})\nmath(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})\nmath(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})\njoin(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.\n                 ${CPACK_PACKAGE_VERSION_PATCH})\nmessage(STATUS \"Version: ${FMT_VERSION}\")\n\nmessage(STATUS \"Build type: ${CMAKE_BUILD_TYPE}\")\n\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)\n\nset(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}\n  \"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake\")\n\ninclude(cxx11)\n\nif (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES \"Clang\"))\n  set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wshadow -pedantic)\nendif ()\n\nif (MASTER_PROJECT AND CMAKE_GENERATOR MATCHES \"Visual Studio\")\n  # If Microsoft SDK is installed create script run-msbuild.bat that\n  # calls SetEnv.cmd to set up build environment and runs msbuild.\n  # It is useful when building Visual Studio projects with the SDK\n  # toolchain rather than Visual Studio.\n  include(FindSetEnv)\n  if (WINSDK_SETENV)\n    set(MSBUILD_SETUP \"call \\\"${WINSDK_SETENV}\\\"\")\n  endif ()\n  # Set FrameworkPathOverride to get rid of MSB3644 warnings.\n  set(netfxpath \"C:\\\\Program Files\\\\Reference Assemblies\\\\Microsoft\\\\Framework\\\\.NETFramework\\\\v4.0\")\n  file(WRITE run-msbuild.bat \"\n    ${MSBUILD_SETUP}\n    ${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\\\"${netfxpath}\\\" %*\")\nendif ()\n\ninclude(CheckSymbolExists)\nif (WIN32)\n  check_symbol_exists(open io.h HAVE_OPEN)\nelse ()\n  check_symbol_exists(open fcntl.h HAVE_OPEN)\nendif ()\n\nadd_subdirectory(fmt)\n\nif (FMT_DOC)\n  add_subdirectory(doc)\nendif ()\n\nif (FMT_TEST)\n  enable_testing()\n  add_subdirectory(test)\nendif ()\n\nset(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)\nif (MASTER_PROJECT AND EXISTS ${gitignore})\n  # Get the list of ignored files from .gitignore.\n  file (STRINGS ${gitignore} lines)\n  LIST(REMOVE_ITEM lines /doc/html)\n  foreach (line ${lines})\n    string(REPLACE \".\" \"[.]\" line \"${line}\")\n    string(REPLACE \"*\" \".*\" line \"${line}\")\n    set(ignored_files ${ignored_files} \"${line}$\" \"${line}/\")\n  endforeach ()\n  set(ignored_files ${ignored_files}\n    /.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees)\n\n  set(CPACK_SOURCE_GENERATOR ZIP)\n  set(CPACK_SOURCE_IGNORE_FILES ${ignored_files})\n  set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION})\n  set(CPACK_PACKAGE_NAME fmt)\n  set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst)\n  include(CPack)\nendif ()\n"
  },
  {
    "path": "lib/fmt/CONTRIBUTING.rst",
    "content": "Contributing to fmt\n===================\n\nAll C++ code must adhere to `Google C++ Style Guide\n<https://google.github.io/styleguide/cppguide.html>`_ with the following\nexceptions:\n\n* Exceptions are permitted\n* snake_case should be used instead of UpperCamelCase for function names\n\nThanks for contributing!\n"
  },
  {
    "path": "lib/fmt/LICENSE.rst",
    "content": "Copyright (c) 2012 - 2016, Victor Zverovich\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "lib/fmt/README.rst",
    "content": "{fmt}\n=====\n\n.. image:: https://travis-ci.org/fmtlib/fmt.png?branch=master\n   :target: https://travis-ci.org/fmtlib/fmt\n\n.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v\n   :target: https://ci.appveyor.com/project/vitaut/fmt\n   \n.. image:: https://badges.gitter.im/Join%20Chat.svg\n   :alt: Join the chat at https://gitter.im/fmtlib/fmt\n   :target: https://gitter.im/fmtlib/fmt\n\n**fmt** is an open-source formatting library for C++.\nIt can be used as a safe alternative to printf or as a fast\nalternative to IOStreams.\n\n`Documentation <http://fmtlib.net/latest/>`_\n\nFeatures\n--------\n\n* Two APIs: faster concatenation-based `write API\n  <http://fmtlib.net/latest/api.html#write-api>`_ and slower,\n  but still very fast, replacement-based `format API\n  <http://fmtlib.net/latest/api.html#format-api>`_ with positional arguments\n  for localization.\n* Write API similar to the one used by IOStreams but stateless allowing\n  faster implementation.\n* Format API with `format string syntax\n  <http://fmtlib.net/latest/syntax.html>`_\n  similar to the one used by `str.format\n  <https://docs.python.org/2/library/stdtypes.html#str.format>`_ in Python.\n* Safe `printf implementation\n  <http://fmtlib.net/latest/api.html#printf-formatting-functions>`_\n  including the POSIX extension for positional arguments.\n* Support for user-defined types.\n* High speed: performance of the format API is close to that of\n  glibc's `printf <http://en.cppreference.com/w/cpp/io/c/fprintf>`_\n  and better than the performance of IOStreams. See `Speed tests`_ and\n  `Fast integer to string conversion in C++\n  <http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.\n* Small code size both in terms of source code (the core library consists of a single\n  header file and a single source file) and compiled code.\n  See `Compile time and code bloat`_.\n* Reliability: the library has an extensive set of `unit tests\n  <https://github.com/fmtlib/fmt/tree/master/test>`_.\n* Safety: the library is fully type safe, errors in format strings are\n  reported using exceptions, automatic memory management prevents buffer\n  overflow errors.\n* Ease of use: small self-contained code base, no external dependencies,\n  permissive BSD `license\n  <https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_\n* `Portability <http://fmtlib.net/latest/index.html#portability>`_ with consistent output\n  across platforms and support for older compilers.\n* Clean warning-free codebase even on high warning levels\n  (-Wall -Wextra -pedantic).\n* Support for wide strings.\n* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro.\n\nSee the `documentation <http://fmtlib.net/latest/>`_ for more details.\n\nExamples\n--------\n\nThis prints ``Hello, world!`` to stdout:\n\n.. code:: c++\n\n    fmt::print(\"Hello, {}!\", \"world\");  // uses Python-like format string syntax\n    fmt::printf(\"Hello, %s!\", \"world\"); // uses printf format string syntax\n\nArguments can be accessed by position and arguments' indices can be repeated:\n\n.. code:: c++\n\n    std::string s = fmt::format(\"{0}{1}{0}\", \"abra\", \"cad\");\n    // s == \"abracadabra\"\n\nfmt can be used as a safe portable replacement for ``itoa``:\n\n.. code:: c++\n\n    fmt::MemoryWriter w;\n    w << 42;           // replaces itoa(42, buffer, 10)\n    w << fmt::hex(42); // replaces itoa(42, buffer, 16)\n    // access the string using w.str() or w.c_str()\n\nAn object of any user-defined type for which there is an overloaded\n:code:`std::ostream` insertion operator (``operator<<``) can be formatted:\n\n.. code:: c++\n\n    #include \"fmt/ostream.h\"\n\n    class Date {\n      int year_, month_, day_;\n     public:\n      Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}\n\n      friend std::ostream &operator<<(std::ostream &os, const Date &d) {\n        return os << d.year_ << '-' << d.month_ << '-' << d.day_;\n      }\n    };\n\n    std::string s = fmt::format(\"The date is {}\", Date(2012, 12, 9));\n    // s == \"The date is 2012-12-9\"\n\nYou can use the `FMT_VARIADIC\n<http://fmtlib.net/latest/api.html#utilities>`_\nmacro to create your own functions similar to `format\n<http://fmtlib.net/latest/api.html#format>`_ and\n`print <http://fmtlib.net/latest/api.html#print>`_\nwhich take arbitrary arguments:\n\n.. code:: c++\n\n    // Prints formatted error message.\n    void report_error(const char *format, fmt::ArgList args) {\n      fmt::print(\"Error: \");\n      fmt::print(format, args);\n    }\n    FMT_VARIADIC(void, report_error, const char *)\n\n    report_error(\"file not found: {}\", path);\n\nNote that you only need to define one function that takes ``fmt::ArgList``\nargument. ``FMT_VARIADIC`` automatically defines necessary wrappers that\naccept variable number of arguments.\n\nProjects using this library\n---------------------------\n\n* `0 A.D. <http://play0ad.com/>`_: A free, open-source, cross-platform real-time strategy game\n\n* `AMPL/MP <https://github.com/ampl/mp>`_:\n  An open-source library for mathematical programming\n\n* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater vehicle\n\n* `Drake <http://drake.mit.edu/>`_: A planning, control, and analysis toolbox for nonlinear dynamical systems (MIT)\n\n* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus (Lyft)\n\n* `FiveM <https://fivem.net/>`_: a modification framework for GTA V\n\n* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:\n  Player vs Player Gaming Network with tweaks\n\n* `KBEngine <http://kbengine.org/>`_: An open-source MMOG server engine\n\n* `Keypirinha <http://keypirinha.com/>`_: A semantic launcher for Windows\n\n* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software\n\n* `Lifeline <https://github.com/peter-clark/lifeline>`_: A 2D game\n\n* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to generate randomized datasets\n\n* `OpenSpace <http://openspaceproject.com/>`_: An open-source astrovisualization framework\n\n* `PenUltima Online (POL) <http://www.polserver.com/>`_:\n  An MMO server, compatible with most Ultima Online clients\n\n* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance, associative database\n\n* `readpe <https://bitbucket.org/sys_dev/readpe>`_: Read Portable Executable\n\n* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster proxy\n\n* `Saddy <https://github.com/mamontov-cpp/saddy-graphics-engine-2d>`_:\n  Small crossplatform 2D graphic engine\n\n* `Salesforce Analytics Cloud <http://www.salesforce.com/analytics-cloud/overview/>`_:\n  Business intelligence software\n\n* `Scylla <http://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store that can handle\n  1 million transactions per second on a single server\n\n* `Seastar <http://www.seastar-project.org/>`_: An advanced, open-source C++ framework for\n  high-performance server applications on modern hardware\n\n* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library\n\n* `Stellar <https://www.stellar.org/>`_: Financial platform\n\n* `Touch Surgery <https://www.touchsurgery.com/>`_: Surgery simulator\n\n* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source MMORPG framework\n\n`More... <https://github.com/search?q=cppformat&type=Code>`_\n\nIf you are aware of other projects using this library, please let me know\nby `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an\n`issue <https://github.com/fmtlib/fmt/issues>`_.\n\nMotivation\n----------\n\nSo why yet another formatting library?\n\nThere are plenty of methods for doing this task, from standard ones like\nthe printf family of function and IOStreams to Boost Format library and\nFastFormat. The reason for creating a new library is that every existing\nsolution that I found either had serious issues or didn't provide\nall the features I needed.\n\nPrintf\n~~~~~~\n\nThe good thing about printf is that it is pretty fast and readily available\nbeing a part of the C standard library. The main drawback is that it\ndoesn't support user-defined types. Printf also has safety issues although\nthey are mostly solved with `__attribute__ ((format (printf, ...))\n<http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.\nThere is a POSIX extension that adds positional arguments required for\n`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_\nto printf but it is not a part of C99 and may not be available on some\nplatforms.\n\nIOStreams\n~~~~~~~~~\n\nThe main issue with IOStreams is best illustrated with an example:\n\n.. code:: c++\n\n    std::cout << std::setprecision(2) << std::fixed << 1.23456 << \"\\n\";\n\nwhich is a lot of typing compared to printf:\n\n.. code:: c++\n\n    printf(\"%.2f\\n\", 1.23456);\n\nMatthew Wilson, the author of FastFormat, referred to this situation with\nIOStreams as \"chevron hell\". IOStreams doesn't support positional arguments\nby design.\n\nThe good part is that IOStreams supports user-defined types and is safe\nalthough error reporting is awkward.\n\nBoost Format library\n~~~~~~~~~~~~~~~~~~~~\n\nThis is a very powerful library which supports both printf-like format\nstrings and positional arguments. The main its drawback is performance.\nAccording to various benchmarks it is much slower than other methods\nconsidered here. Boost Format also has excessive build times and severe\ncode bloat issues (see `Benchmarks`_).\n\nFastFormat\n~~~~~~~~~~\n\nThis is an interesting library which is fast, safe and has positional\narguments. However it has significant limitations, citing its author:\n\n    Three features that have no hope of being accommodated within the\n    current design are:\n\n    * Leading zeros (or any other non-space padding)\n    * Octal/hexadecimal encoding\n    * Runtime width/alignment specification\n\nIt is also quite big and has a heavy dependency, STLSoft, which might be\ntoo restrictive for using it in some projects.\n\nLoki SafeFormat\n~~~~~~~~~~~~~~~\n\nSafeFormat is a formatting library which uses printf-like format strings\nand is type safe. It doesn't support user-defined types or positional\narguments. It makes unconventional use of ``operator()`` for passing\nformat arguments.\n\nTinyformat\n~~~~~~~~~~\n\nThis library supports printf-like format strings and is very small and\nfast. Unfortunately it doesn't support positional arguments and wrapping\nit in C++98 is somewhat difficult. Also its performance and code compactness\nare limited by IOStreams.\n\nBoost Spirit.Karma\n~~~~~~~~~~~~~~~~~~\n\nThis is not really a formatting library but I decided to include it here\nfor completeness. As IOStreams it suffers from the problem of mixing\nverbatim text with arguments. The library is pretty fast, but slower\non integer formatting than ``fmt::Writer`` on Karma's own benchmark,\nsee `Fast integer to string conversion in C++\n<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.\n\nBenchmarks\n----------\n\nSpeed tests\n~~~~~~~~~~~\n\nThe following speed tests results were generated by building\n``tinyformat_test.cpp`` on Ubuntu GNU/Linux 14.04.1 with\n``g++-4.8.2 -O3 -DSPEED_TEST -DHAVE_FORMAT``, and taking the best of three\nruns.  In the test, the format string ``\"%0.10f:%04d:%+g:%s:%p:%c:%%\\n\"`` or\nequivalent is filled 2000000 times with output sent to ``/dev/null``; for\nfurther details see the `source\n<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.\n\n================= ============= ===========\nLibrary           Method        Run Time, s\n================= ============= ===========\nEGLIBC 2.19       printf          1.30\nlibstdc++ 4.8.2   std::ostream    1.85\nfmt 1.0           fmt::print      1.42\ntinyformat 2.0.1  tfm::printf     2.25\nBoost Format 1.54 boost::format   9.94\n================= ============= ===========\n\nAs you can see ``boost::format`` is much slower than the alternative methods; this\nis confirmed by `other tests <http://accu.org/index.php/journals/1539>`_.\nTinyformat is quite good coming close to IOStreams.  Unfortunately tinyformat\ncannot be faster than the IOStreams because it uses them internally.\nPerformance of fmt is close to that of printf, being `faster than printf on integer\nformatting <http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_,\nbut slower on floating-point formatting which dominates this benchmark.\n\nCompile time and code bloat\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe script `bloat-test.py\n<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_\nfrom `format-benchmark <https://github.com/fmtlib/format-benchmark>`_\ntests compile time and code bloat for nontrivial projects.\nIt generates 100 translation units and uses ``printf()`` or its alternative\nfive times in each to simulate a medium sized project.  The resulting\nexecutable size and compile time (g++-4.8.1, Ubuntu GNU/Linux 13.10,\nbest of three) is shown in the following tables.\n\n**Optimized build (-O3)**\n\n============ =============== ==================== ==================\nMethod       Compile Time, s Executable size, KiB Stripped size, KiB\n============ =============== ==================== ==================\nprintf                   2.6                   41                 30\nIOStreams               19.4                   92                 70\nfmt                     46.8                   46                 34\ntinyformat              64.6                  418                386\nBoost Format           222.8                  990                923\n============ =============== ==================== ==================\n\nAs you can see, fmt has two times less overhead in terms of resulting\ncode size compared to IOStreams and comes pretty close to ``printf``.\nBoost Format has by far the largest overheads.\n\n**Non-optimized build**\n\n============ =============== ==================== ==================\nMethod       Compile Time, s Executable size, KiB Stripped size, KiB\n============ =============== ==================== ==================\nprintf                   2.1                   41                 30\nIOStreams               19.7                   86                 62\nfmt                     47.9                  108                 86\ntinyformat              27.7                  234                190\nBoost Format           122.6                  884                763\n============ =============== ==================== ==================\n\n``libc``, ``libstdc++`` and ``libfmt`` are all linked as shared\nlibraries to compare formatting function overhead only. Boost Format\nand tinyformat are header-only libraries so they don't provide any\nlinkage options.\n\nRunning the tests\n~~~~~~~~~~~~~~~~~\n\nPlease refer to `Building the library`__ for the instructions on how to build\nthe library and run the unit tests.\n\n__ http://fmtlib.net/latest/usage.html#building-the-library\n\nBenchmarks reside in a separate repository,\n`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_,\nso to run the benchmarks you first need to clone this repository and\ngenerate Makefiles with CMake::\n\n    $ git clone --recursive https://github.com/fmtlib/format-benchmark.git\n    $ cd format-benchmark\n    $ cmake .\n\nThen you can run the speed test::\n\n    $ make speed-test\n\nor the bloat test::\n\n    $ make bloat-test\n\nLicense\n-------\n\nfmt is distributed under the BSD `license\n<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.\n\nThe `Format String Syntax\n<http://fmtlib.net/latest/syntax.html>`_\nsection in the documentation is based on the one from Python `string module\ndocumentation <https://docs.python.org/3/library/string.html#module-string>`_\nadapted for the current library. For this reason the documentation is\ndistributed under the Python Software Foundation license available in\n`doc/python-license.txt\n<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.\nIt only applies if you distribute the documentation of fmt.\n\nAcknowledgments\n---------------\n\nThe fmt library is maintained by Victor Zverovich (`vitaut <https://github.com/vitaut>`_)\nand Jonathan Müller (`foonathan <https://github.com/foonathan>`_) with contributions from many\nother people. See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and `Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names. Let us know if your contribution\nis not listed or mentioned incorrectly and we'll make it right.\n\nThe benchmark section of this readme file and the performance tests are taken\nfrom the excellent `tinyformat <https://github.com/c42f/tinyformat>`_ library\nwritten by Chris Foster.  Boost Format library is acknowledged transitively\nsince it had some influence on tinyformat.\nSome ideas used in the implementation are borrowed from `Loki\n<http://loki-lib.sourceforge.net/>`_ SafeFormat and `Diagnostic API\n<http://clang.llvm.org/doxygen/classclang_1_1Diagnostic.html>`_ in\n`Clang <http://clang.llvm.org/>`_.\nFormat string syntax and the documentation are based on Python's `str.format\n<http://docs.python.org/2/library/stdtypes.html#str.format>`_.\nThanks `Doug Turnbull <https://github.com/softwaredoug>`_ for his valuable\ncomments and contribution to the design of the type-safe API and\n`Gregory Czajkowski <https://github.com/gcflymoto>`_ for implementing binary\nformatting. Thanks `Ruslan Baratov <https://github.com/ruslo>`_ for comprehensive\n`comparison of integer formatting algorithms <https://github.com/ruslo/int-dec-format-tests>`_\nand useful comments regarding performance, `Boris Kaul <https://github.com/localvoid>`_ for\n`C++ counting digits benchmark <https://github.com/localvoid/cxx-benchmark-count-digits>`_.\nThanks to `CarterLi <https://github.com/CarterLi>`_ for contributing various\nimprovements to the code.\n"
  },
  {
    "path": "lib/fmt/README.txt",
    "content": "===============================================================\nIt's FMT library to easier format your stuff!\nhttps://github.com/fmtlib/fmt\n===============================================================\n\nCurrent version (which is here at now) is 4.1.0!\n\n===============================================================\nWhat changed from orig version:\n===============================================================\n- every file begins with \"fmt_\" and \".cc\" has been replaced with \".cpp\"\n  to avoid any possible conflicts with some project sources\n- added `fmt_qformat.h` to support Qt-like formatting (\"%1, %2, %3....\")\n\n===============================================================\nHow to easily upgrade this with newer version\n===============================================================\n1) Download a tarball or repo from official download\n2) copy content of \"fmt\" folder into \"orig\" you see here\n3) run from bash the \"update.sh\"\n\nNote 1: You must have `sed` as GNU-sed. If you are running BSD or macOS, possibly,\n        scrilt will fail to update filename references in each file you see here.\n\nNote 2: If any files are added, please modify `update.sh` to refer them too!\n\n"
  },
  {
    "path": "lib/fmt/fmt.cmake",
    "content": "# message(\"Path to FMT is [${CMAKE_CURRENT_LIST_DIR}]\")\ninclude_directories(${CMAKE_CURRENT_LIST_DIR})\n\nset(FMT_SRCS)\n\nlist(APPEND FMT_SRCS\n    ${CMAKE_CURRENT_LIST_DIR}/fmt_format.cpp\n    ${CMAKE_CURRENT_LIST_DIR}/fmt_ostream.cpp\n    ${CMAKE_CURRENT_LIST_DIR}/fmt_posix.cpp\n)\n\n"
  },
  {
    "path": "lib/fmt/fmt_container.h",
    "content": "/*\n Formatting library for C++ - standard container utilities\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_CONTAINER_H_\n#define FMT_CONTAINER_H_\n\n#include \"fmt_format.h\"\n\nnamespace fmt {\n\nnamespace internal {\n\n/**\n  \\rst\n  A \"buffer\" that appends data to a standard container (e.g. typically a\n  ``std::vector`` or ``std::basic_string``).\n  \\endrst\n */\ntemplate <typename Container>\nclass ContainerBuffer : public Buffer<typename Container::value_type> {\n private:\n  Container& container_;\n\n protected:\n  virtual void grow(std::size_t size) FMT_OVERRIDE {\n    container_.resize(size);\n    this->ptr_ = &container_[0];\n    this->capacity_ = size;\n  }\n\n public:\n  explicit ContainerBuffer(Container& container) : container_(container) {\n    this->size_ = container_.size();\n    if (this->size_ > 0) {\n      this->ptr_ = &container_[0];\n      this->capacity_ = this->size_;\n    }\n  }\n};\n}  // namespace internal\n\n/**\n  \\rst\n  This class template provides operations for formatting and appending data\n  to a standard *container* like ``std::vector`` or ``std::basic_string``.\n\n  **Example**::\n\n    void vecformat(std::vector<char>& dest, fmt::BasicCStringRef<char> format,\n                   fmt::ArgList args) {\n      fmt::BasicContainerWriter<std::vector<char> > appender(dest);\n      appender.write(format, args);\n    }\n    FMT_VARIADIC(void, vecformat, std::vector<char>&,\n                 fmt::BasicCStringRef<char>);\n  \\endrst\n */\ntemplate <class Container>\nclass BasicContainerWriter\n  : public BasicWriter<typename Container::value_type> {\n private:\n  internal::ContainerBuffer<Container> buffer_;\n\n public:\n  /**\n    \\rst\n    Constructs a :class:`fmt::BasicContainerWriter` object.\n    \\endrst\n   */\n  explicit BasicContainerWriter(Container& dest)\n  : BasicWriter<typename Container::value_type>(buffer_), buffer_(dest) {}\n};\n\n} // namespace fmt\n\n#endif  // FMT_CONTAINER_H_\n"
  },
  {
    "path": "lib/fmt/fmt_format.cpp",
    "content": "/*\n Formatting library for C++\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n Redistribution and use in source and binary forms, with or without\n modification, are permitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice, this\n    list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright notice,\n    this list of conditions and the following disclaimer in the documentation\n    and/or other materials provided with the distribution.\n\n THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"fmt_format.h\"\n\n#include <string.h>\n\n#include <cctype>\n#include <cerrno>\n#include <climits>\n#include <cmath>\n#include <cstdarg>\n#include <cstddef>  // for std::ptrdiff_t\n\n#if defined(_WIN32) && defined(__MINGW32__)\n# include <cstring>\n#endif\n\n#if FMT_USE_WINDOWS_H\n# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN)\n#  define WIN32_LEAN_AND_MEAN\n# endif\n# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX)\n#  include <windows.h>\n# else\n#  define NOMINMAX\n#  include <windows.h>\n#  undef NOMINMAX\n# endif\n#endif\n\n#if FMT_EXCEPTIONS\n# define FMT_TRY try\n# define FMT_CATCH(x) catch (x)\n#else\n# define FMT_TRY if (true)\n# define FMT_CATCH(x) if (false)\n#endif\n\n#ifdef _MSC_VER\n# pragma warning(push)\n# pragma warning(disable: 4127)  // conditional expression is constant\n# pragma warning(disable: 4702)  // unreachable code\n// Disable deprecation warning for strerror. The latter is not called but\n// MSVC fails to detect it.\n# pragma warning(disable: 4996)\n#endif\n\n// Dummy implementations of strerror_r and strerror_s called if corresponding\n// system functions are not available.\nFMT_MAYBE_UNUSED\nstatic inline fmt::internal::Null<> strerror_r(int, char *, ...) {\n  return fmt::internal::Null<>();\n}\nFMT_MAYBE_UNUSED\nstatic inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) {\n  return fmt::internal::Null<>();\n}\n\nnamespace fmt {\n\nFMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT {}\nFMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT {}\nFMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT {}\n\nnamespace {\n\n#ifndef _MSC_VER\n# define FMT_SNPRINTF snprintf\n#else  // _MSC_VER\ninline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {\n  va_list args;\n  va_start(args, format);\n  int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);\n  va_end(args);\n  return result;\n}\n# define FMT_SNPRINTF fmt_snprintf\n#endif  // _MSC_VER\n\n#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)\n# define FMT_SWPRINTF snwprintf\n#else\n# define FMT_SWPRINTF swprintf\n#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)\n\nconst char RESET_COLOR[] = \"\\x1b[0m\";\n\ntypedef void (*FormatFunc)(Writer &, int, StringRef);\n\n// Portable thread-safe version of strerror.\n// Sets buffer to point to a string describing the error code.\n// This can be either a pointer to a string stored in buffer,\n// or a pointer to some static immutable string.\n// Returns one of the following values:\n//   0      - success\n//   ERANGE - buffer is not large enough to store the error message\n//   other  - failure\n// Buffer should be at least of size 1.\nint safe_strerror(\n    int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT {\n  FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, \"invalid buffer\");\n\n  class StrError {\n   private:\n    int error_code_;\n    char *&buffer_;\n    std::size_t buffer_size_;\n\n    // A noop assignment operator to avoid bogus warnings.\n    void operator=(const StrError &) {}\n\n    // Handle the result of XSI-compliant version of strerror_r.\n    int handle(int result) {\n      // glibc versions before 2.13 return result in errno.\n      return result == -1 ? errno : result;\n    }\n\n    // Handle the result of GNU-specific version of strerror_r.\n    int handle(char *message) {\n      // If the buffer is full then the message is probably truncated.\n      if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1)\n        return ERANGE;\n      buffer_ = message;\n      return 0;\n    }\n\n    // Handle the case when strerror_r is not available.\n    int handle(internal::Null<>) {\n      return fallback(strerror_s(buffer_, buffer_size_, error_code_));\n    }\n\n    // Fallback to strerror_s when strerror_r is not available.\n    int fallback(int result) {\n      // If the buffer is full then the message is probably truncated.\n      return result == 0 && strlen(buffer_) == buffer_size_ - 1 ?\n            ERANGE : result;\n    }\n\n#ifdef __c2__\n# pragma clang diagnostic push\n# pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n#endif\n\n    // Fallback to strerror if strerror_r and strerror_s are not available.\n    int fallback(internal::Null<>) {\n      errno = 0;\n      buffer_ = strerror(error_code_);\n      return errno;\n    }\n\n#ifdef __c2__\n# pragma clang diagnostic pop\n#endif\n\n   public:\n    StrError(int err_code, char *&buf, std::size_t buf_size)\n      : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}\n\n    int run() {\n      return handle(strerror_r(error_code_, buffer_, buffer_size_));\n    }\n  };\n  return StrError(error_code, buffer, buffer_size).run();\n}\n\nvoid format_error_code(Writer &out, int error_code,\n                       StringRef message) FMT_NOEXCEPT {\n  // Report error code making sure that the output fits into\n  // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential\n  // bad_alloc.\n  out.clear();\n  static const char SEP[] = \": \";\n  static const char ERROR_STR[] = \"error \";\n  // Subtract 2 to account for terminating null characters in SEP and ERROR_STR.\n  std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;\n  typedef internal::IntTraits<int>::MainType MainType;\n  MainType abs_value = static_cast<MainType>(error_code);\n  if (internal::is_negative(error_code)) {\n    abs_value = 0 - abs_value;\n    ++error_code_size;\n  }\n  error_code_size += internal::count_digits(abs_value);\n  if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size)\n    out << message << SEP;\n  out << ERROR_STR << error_code;\n  assert(out.size() <= internal::INLINE_BUFFER_SIZE);\n}\n\nvoid report_error(FormatFunc func, int error_code,\n                  StringRef message) FMT_NOEXCEPT {\n  MemoryWriter full_message;\n  func(full_message, error_code, message);\n  // Use Writer::data instead of Writer::c_str to avoid potential memory\n  // allocation.\n  std::fwrite(full_message.data(), full_message.size(), 1, stderr);\n  std::fputc('\\n', stderr);\n}\n}  // namespace\n\nFMT_FUNC void SystemError::init(\n    int err_code, CStringRef format_str, ArgList args) {\n  error_code_ = err_code;\n  MemoryWriter w;\n  format_system_error(w, err_code, format(format_str, args));\n  std::runtime_error &base = *this;\n  base = std::runtime_error(w.str());\n}\n\ntemplate <typename T>\nint internal::CharTraits<char>::format_float(\n    char *buffer, std::size_t size, const char *format,\n    unsigned width, int precision, T value) {\n  if (width == 0) {\n    return precision < 0 ?\n        FMT_SNPRINTF(buffer, size, format, value) :\n        FMT_SNPRINTF(buffer, size, format, precision, value);\n  }\n  return precision < 0 ?\n      FMT_SNPRINTF(buffer, size, format, width, value) :\n      FMT_SNPRINTF(buffer, size, format, width, precision, value);\n}\n\ntemplate <typename T>\nint internal::CharTraits<wchar_t>::format_float(\n    wchar_t *buffer, std::size_t size, const wchar_t *format,\n    unsigned width, int precision, T value) {\n  if (width == 0) {\n    return precision < 0 ?\n        FMT_SWPRINTF(buffer, size, format, value) :\n        FMT_SWPRINTF(buffer, size, format, precision, value);\n  }\n  return precision < 0 ?\n      FMT_SWPRINTF(buffer, size, format, width, value) :\n      FMT_SWPRINTF(buffer, size, format, width, precision, value);\n}\n\ntemplate <typename T>\nconst char internal::BasicData<T>::DIGITS[] =\n    \"0001020304050607080910111213141516171819\"\n    \"2021222324252627282930313233343536373839\"\n    \"4041424344454647484950515253545556575859\"\n    \"6061626364656667686970717273747576777879\"\n    \"8081828384858687888990919293949596979899\";\n\n#define FMT_POWERS_OF_10(factor) \\\n  factor * 10, \\\n  factor * 100, \\\n  factor * 1000, \\\n  factor * 10000, \\\n  factor * 100000, \\\n  factor * 1000000, \\\n  factor * 10000000, \\\n  factor * 100000000, \\\n  factor * 1000000000\n\ntemplate <typename T>\nconst uint32_t internal::BasicData<T>::POWERS_OF_10_32[] = {\n  0, FMT_POWERS_OF_10(1)\n};\n\ntemplate <typename T>\nconst uint64_t internal::BasicData<T>::POWERS_OF_10_64[] = {\n  0,\n  FMT_POWERS_OF_10(1),\n  FMT_POWERS_OF_10(ULongLong(1000000000)),\n  // Multiply several constants instead of using a single long long constant\n  // to avoid warnings about C++98 not supporting long long.\n  ULongLong(1000000000) * ULongLong(1000000000) * 10\n};\n\nFMT_FUNC void internal::report_unknown_type(char code, const char *type) {\n  (void)type;\n  if (std::isprint(static_cast<unsigned char>(code))) {\n    FMT_THROW(FormatError(\n        format(\"unknown format code '{}' for {}\", code, type)));\n  }\n  FMT_THROW(FormatError(\n      format(\"unknown format code '\\\\x{:02x}' for {}\",\n        static_cast<unsigned>(code), type)));\n}\n\n#if FMT_USE_WINDOWS_H\n\nFMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) {\n  static const char ERROR_MSG[] = \"cannot convert string from UTF-8 to UTF-16\";\n  if (s.size() > INT_MAX)\n    FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG));\n  int s_size = static_cast<int>(s.size());\n  int length = MultiByteToWideChar(\n      CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0);\n  if (length == 0)\n    FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));\n  buffer_.resize(length + 1);\n  length = MultiByteToWideChar(\n    CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length);\n  if (length == 0)\n    FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));\n  buffer_[length] = 0;\n}\n\nFMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) {\n  if (int error_code = convert(s)) {\n    FMT_THROW(WindowsError(error_code,\n        \"cannot convert string from UTF-16 to UTF-8\"));\n  }\n}\n\nFMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) {\n  if (s.size() > INT_MAX)\n    return ERROR_INVALID_PARAMETER;\n  int s_size = static_cast<int>(s.size());\n  int length = WideCharToMultiByte(\n    CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL);\n  if (length == 0)\n    return GetLastError();\n  buffer_.resize(length + 1);\n  length = WideCharToMultiByte(\n    CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL);\n  if (length == 0)\n    return GetLastError();\n  buffer_[length] = 0;\n  return 0;\n}\n\nFMT_FUNC void WindowsError::init(\n    int err_code, CStringRef format_str, ArgList args) {\n  error_code_ = err_code;\n  MemoryWriter w;\n  internal::format_windows_error(w, err_code, format(format_str, args));\n  std::runtime_error &base = *this;\n  base = std::runtime_error(w.str());\n}\n\nFMT_FUNC void internal::format_windows_error(\n    Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {\n  FMT_TRY {\n    MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer;\n    buffer.resize(INLINE_BUFFER_SIZE);\n    for (;;) {\n      wchar_t *system_message = &buffer[0];\n      int result = FormatMessageW(\n        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,\n        FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n        system_message, static_cast<uint32_t>(buffer.size()), FMT_NULL);\n      if (result != 0) {\n        UTF16ToUTF8 utf8_message;\n        if (utf8_message.convert(system_message) == ERROR_SUCCESS) {\n          out << message << \": \" << utf8_message;\n          return;\n        }\n        break;\n      }\n      if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)\n        break;  // Can't get error message, report error code instead.\n      buffer.resize(buffer.size() * 2);\n    }\n  } FMT_CATCH(...) {}\n  fmt::format_error_code(out, error_code, message);  // 'fmt::' is for bcc32.\n}\n\n#endif  // FMT_USE_WINDOWS_H\n\nFMT_FUNC void format_system_error(\n    Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {\n  FMT_TRY {\n    internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> buffer;\n    buffer.resize(internal::INLINE_BUFFER_SIZE);\n    for (;;) {\n      char *system_message = &buffer[0];\n      int result = safe_strerror(error_code, system_message, buffer.size());\n      if (result == 0) {\n        out << message << \": \" << system_message;\n        return;\n      }\n      if (result != ERANGE)\n        break;  // Can't get error message, report error code instead.\n      buffer.resize(buffer.size() * 2);\n    }\n  } FMT_CATCH(...) {}\n  fmt::format_error_code(out, error_code, message);  // 'fmt::' is for bcc32.\n}\n\ntemplate <typename Char>\nvoid internal::FixedBuffer<Char>::grow(std::size_t) {\n  FMT_THROW(std::runtime_error(\"buffer overflow\"));\n}\n\nFMT_FUNC internal::Arg internal::FormatterBase::do_get_arg(\n    unsigned arg_index, const char *&error) {\n  internal::Arg arg = args_[arg_index];\n  switch (arg.type) {\n  case internal::Arg::NONE:\n    error = \"argument index out of range\";\n    break;\n  case internal::Arg::NAMED_ARG:\n    arg = *static_cast<const internal::Arg*>(arg.pointer);\n    break;\n  default:\n    /*nothing*/;\n  }\n  return arg;\n}\n\nFMT_FUNC void report_system_error(\n    int error_code, fmt::StringRef message) FMT_NOEXCEPT {\n  // 'fmt::' is for bcc32.\n  report_error(format_system_error, error_code, message);\n}\n\n#if FMT_USE_WINDOWS_H\nFMT_FUNC void report_windows_error(\n    int error_code, fmt::StringRef message) FMT_NOEXCEPT {\n  // 'fmt::' is for bcc32.\n  report_error(internal::format_windows_error, error_code, message);\n}\n#endif\n\nFMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) {\n  MemoryWriter w;\n  w.write(format_str, args);\n  std::fwrite(w.data(), 1, w.size(), f);\n}\n\nFMT_FUNC void print(CStringRef format_str, ArgList args) {\n  print(stdout, format_str, args);\n}\n\nFMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) {\n  char escape[] = \"\\x1b[30m\";\n  escape[3] = static_cast<char>('0' + c);\n  std::fputs(escape, stdout);\n  print(format, args);\n  std::fputs(RESET_COLOR, stdout);\n}\n\n#ifndef FMT_HEADER_ONLY\n\ntemplate struct internal::BasicData<void>;\n\n// Explicit instantiations for char.\n\ntemplate void internal::FixedBuffer<char>::grow(std::size_t);\n\ntemplate FMT_API int internal::CharTraits<char>::format_float(\n    char *buffer, std::size_t size, const char *format,\n    unsigned width, int precision, double value);\n\ntemplate FMT_API int internal::CharTraits<char>::format_float(\n    char *buffer, std::size_t size, const char *format,\n    unsigned width, int precision, long double value);\n\n// Explicit instantiations for wchar_t.\n\ntemplate void internal::FixedBuffer<wchar_t>::grow(std::size_t);\n\ntemplate FMT_API int internal::CharTraits<wchar_t>::format_float(\n    wchar_t *buffer, std::size_t size, const wchar_t *format,\n    unsigned width, int precision, double value);\n\ntemplate FMT_API int internal::CharTraits<wchar_t>::format_float(\n    wchar_t *buffer, std::size_t size, const wchar_t *format,\n    unsigned width, int precision, long double value);\n\n#endif  // FMT_HEADER_ONLY\n\n}  // namespace fmt\n\n#ifdef _MSC_VER\n# pragma warning(pop)\n#endif\n"
  },
  {
    "path": "lib/fmt/fmt_format.h",
    "content": "/*\n Formatting library for C++\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n Redistribution and use in source and binary forms, with or without\n modification, are permitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice, this\n    list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright notice,\n    this list of conditions and the following disclaimer in the documentation\n    and/or other materials provided with the distribution.\n\n THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef FMT_FORMAT_H_\n#define FMT_FORMAT_H_\n\n#define FMT_INCLUDE\n#include <cassert>\n#include <clocale>\n#include <cmath>\n#include <cstdio>\n#include <cstring>\n#include <limits>\n#include <memory>\n#include <stdexcept>\n#include <string>\n#include <vector>\n#include <utility>  // for std::pair\n#undef FMT_INCLUDE\n\n// The fmt library version in the form major * 10000 + minor * 100 + patch.\n#define FMT_VERSION 40100\n\n#if defined(__has_include)\n# define FMT_HAS_INCLUDE(x) __has_include(x)\n#else\n# define FMT_HAS_INCLUDE(x) 0\n#endif\n\n#if (FMT_HAS_INCLUDE(<string_view>) && __cplusplus > 201402L) || \\\n    (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910)\n# include <string_view>\n# define FMT_HAS_STRING_VIEW 1\n#else\n# define FMT_HAS_STRING_VIEW 0\n#endif\n\n#if defined _SECURE_SCL && _SECURE_SCL\n# define FMT_SECURE_SCL _SECURE_SCL\n#else\n# define FMT_SECURE_SCL 0\n#endif\n\n#if FMT_SECURE_SCL\n# include <iterator>\n#endif\n\n#ifdef _MSC_VER\n# define FMT_MSC_VER _MSC_VER\n#else\n# define FMT_MSC_VER 0\n#endif\n\n#if FMT_MSC_VER && FMT_MSC_VER <= 1500\ntypedef unsigned __int32 uint32_t;\ntypedef unsigned __int64 uint64_t;\ntypedef __int64          intmax_t;\n#else\n#include <stdint.h>\n#endif\n\n#if !defined(FMT_HEADER_ONLY) && defined(_WIN32)\n# ifdef FMT_EXPORT\n#  define FMT_API __declspec(dllexport)\n# elif defined(FMT_SHARED)\n#  define FMT_API __declspec(dllimport)\n# endif\n#endif\n#ifndef FMT_API\n# define FMT_API\n#endif\n\n#ifdef __GNUC__\n# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)\n# define FMT_GCC_EXTENSION __extension__\n# if FMT_GCC_VERSION >= 406\n#  pragma GCC diagnostic push\n// Disable the warning about \"long long\" which is sometimes reported even\n// when using __extension__.\n#  pragma GCC diagnostic ignored \"-Wlong-long\"\n// Disable the warning about declaration shadowing because it affects too\n// many valid cases.\n#  pragma GCC diagnostic ignored \"-Wshadow\"\n// Disable the warning about implicit conversions that may change the sign of\n// an integer; silencing it otherwise would require many explicit casts.\n#  pragma GCC diagnostic ignored \"-Wsign-conversion\"\n# endif\n# if __cplusplus >= 201103L || defined __GXX_EXPERIMENTAL_CXX0X__\n#  define FMT_HAS_GXX_CXX11 1\n# endif\n#else\n# define FMT_GCC_VERSION 0\n# define FMT_GCC_EXTENSION\n# define FMT_HAS_GXX_CXX11 0\n#endif\n\n#if defined(__INTEL_COMPILER)\n# define FMT_ICC_VERSION __INTEL_COMPILER\n#elif defined(__ICL)\n# define FMT_ICC_VERSION __ICL\n#endif\n\n#if defined(__clang__) && !defined(FMT_ICC_VERSION)\n# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)\n# pragma clang diagnostic push\n# pragma clang diagnostic ignored \"-Wdocumentation-unknown-command\"\n# pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\n#ifdef __GNUC_LIBSTD__\n# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__)\n#endif\n\n#ifdef __has_feature\n# define FMT_HAS_FEATURE(x) __has_feature(x)\n#else\n# define FMT_HAS_FEATURE(x) 0\n#endif\n\n#ifdef __has_builtin\n# define FMT_HAS_BUILTIN(x) __has_builtin(x)\n#else\n# define FMT_HAS_BUILTIN(x) 0\n#endif\n\n#ifdef __has_cpp_attribute\n# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)\n#else\n# define FMT_HAS_CPP_ATTRIBUTE(x) 0\n#endif\n\n#if FMT_HAS_CPP_ATTRIBUTE(maybe_unused) && __cplusplus >= 201703L\n# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED\n// VC++ 1910 support /std: option and that will set _MSVC_LANG macro\n// Clang with Microsoft CodeGen doesn't define _MSVC_LANG macro\n#elif defined(_MSVC_LANG) && _MSVC_LANG > 201402 && _MSC_VER >= 1910\n# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED\n#endif\n\n#ifdef FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED\n# define FMT_MAYBE_UNUSED [[maybe_unused]]\n// g++/clang++ also support [[gnu::unused]]. However, we don't use it.\n#elif defined(__GNUC__)\n# define FMT_MAYBE_UNUSED __attribute__((unused))\n#else\n# define FMT_MAYBE_UNUSED\n#endif\n\n// Use the compiler's attribute noreturn\n#if defined(__MINGW32__) || defined(__MINGW64__)\n# define FMT_NORETURN __attribute__((noreturn))\n#elif FMT_HAS_CPP_ATTRIBUTE(noreturn) && __cplusplus >= 201103L\n# define FMT_NORETURN [[noreturn]]\n#else\n# define FMT_NORETURN\n#endif\n\n#ifndef FMT_USE_VARIADIC_TEMPLATES\n// Variadic templates are available in GCC since version 4.4\n// (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++\n// since version 2013.\n# define FMT_USE_VARIADIC_TEMPLATES \\\n   (FMT_HAS_FEATURE(cxx_variadic_templates) || \\\n       (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800)\n#endif\n\n#ifndef FMT_USE_RVALUE_REFERENCES\n// Don't use rvalue references when compiling with clang and an old libstdc++\n// as the latter doesn't provide std::move.\n# if defined(FMT_GNUC_LIBSTD_VERSION) && FMT_GNUC_LIBSTD_VERSION <= 402\n#  define FMT_USE_RVALUE_REFERENCES 0\n# else\n#  define FMT_USE_RVALUE_REFERENCES \\\n    (FMT_HAS_FEATURE(cxx_rvalue_references) || \\\n        (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600)\n# endif\n#endif\n\n#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700\n# define FMT_USE_ALLOCATOR_TRAITS 1\n#else\n# define FMT_USE_ALLOCATOR_TRAITS 0\n#endif\n\n// Check if exceptions are disabled.\n#if defined(__GNUC__) && !defined(__EXCEPTIONS)\n# define FMT_EXCEPTIONS 0\n#endif\n#if FMT_MSC_VER && !_HAS_EXCEPTIONS\n# define FMT_EXCEPTIONS 0\n#endif\n#ifndef FMT_EXCEPTIONS\n# define FMT_EXCEPTIONS 1\n#endif\n\n#ifndef FMT_THROW\n# if FMT_EXCEPTIONS\n#  define FMT_THROW(x) throw x\n# else\n#  define FMT_THROW(x) assert(false)\n# endif\n#endif\n\n// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature).\n#ifndef FMT_USE_NOEXCEPT\n# define FMT_USE_NOEXCEPT 0\n#endif\n\n#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \\\n    (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \\\n    FMT_MSC_VER >= 1900\n# define FMT_DETECTED_NOEXCEPT noexcept\n#else\n# define FMT_DETECTED_NOEXCEPT throw()\n#endif\n\n#ifndef FMT_NOEXCEPT\n# if FMT_EXCEPTIONS\n#  define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT\n# else\n#  define FMT_NOEXCEPT\n# endif\n#endif\n\n// This is needed because GCC still uses throw() in its headers when exceptions\n// are disabled.\n#if FMT_GCC_VERSION\n# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT\n#else\n# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT\n#endif\n\n#ifndef FMT_OVERRIDE\n# if (defined(FMT_USE_OVERRIDE) && FMT_USE_OVERRIDE) || FMT_HAS_FEATURE(cxx_override) || \\\n   (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \\\n   FMT_MSC_VER >= 1900\n#  define FMT_OVERRIDE override\n# else\n#  define FMT_OVERRIDE\n# endif\n#endif\n\n#ifndef FMT_NULL\n# if FMT_HAS_FEATURE(cxx_nullptr) || \\\n   (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \\\n   FMT_MSC_VER >= 1600\n#  define FMT_NULL nullptr\n# else\n#  define FMT_NULL NULL\n# endif\n#endif\n\n// A macro to disallow the copy constructor and operator= functions\n// This should be used in the private: declarations for a class\n#ifndef FMT_USE_DELETED_FUNCTIONS\n# define FMT_USE_DELETED_FUNCTIONS 0\n#endif\n\n#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \\\n  (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800\n# define FMT_DELETED_OR_UNDEFINED  = delete\n# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \\\n    TypeName(const TypeName&) = delete; \\\n    TypeName& operator=(const TypeName&) = delete\n#else\n# define FMT_DELETED_OR_UNDEFINED\n# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \\\n    TypeName(const TypeName&); \\\n    TypeName& operator=(const TypeName&)\n#endif\n\n#ifndef FMT_USE_DEFAULTED_FUNCTIONS\n# define FMT_USE_DEFAULTED_FUNCTIONS 0\n#endif\n\n#ifndef FMT_DEFAULTED_COPY_CTOR\n# if FMT_USE_DEFAULTED_FUNCTIONS || FMT_HAS_FEATURE(cxx_defaulted_functions) || \\\n   (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800\n#  define FMT_DEFAULTED_COPY_CTOR(TypeName) \\\n    TypeName(const TypeName&) = default;\n# else\n#  define FMT_DEFAULTED_COPY_CTOR(TypeName)\n# endif\n#endif\n\n#ifndef FMT_USE_USER_DEFINED_LITERALS\n// All compilers which support UDLs also support variadic templates. This\n// makes the fmt::literals implementation easier. However, an explicit check\n// for variadic templates is added here just in case.\n// For Intel's compiler both it and the system gcc/msc must support UDLs.\n# if FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES && \\\n   (FMT_HAS_FEATURE(cxx_user_literals) || \\\n     (FMT_GCC_VERSION >= 407 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900) && \\\n   (!defined(FMT_ICC_VERSION) || FMT_ICC_VERSION >= 1500)\n#  define FMT_USE_USER_DEFINED_LITERALS 1\n# else\n#  define FMT_USE_USER_DEFINED_LITERALS 0\n# endif\n#endif\n\n#ifndef FMT_USE_EXTERN_TEMPLATES\n# define FMT_USE_EXTERN_TEMPLATES \\\n    (FMT_CLANG_VERSION >= 209 || (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11))\n#endif\n\n#ifdef FMT_HEADER_ONLY\n// If header only do not use extern templates.\n# undef FMT_USE_EXTERN_TEMPLATES\n# define FMT_USE_EXTERN_TEMPLATES 0\n#endif\n\n#ifndef FMT_ASSERT\n# define FMT_ASSERT(condition, message) assert((condition) && message)\n#endif\n\n// __builtin_clz is broken in clang with Microsoft CodeGen:\n// https://github.com/fmtlib/fmt/issues/519\n#ifndef _MSC_VER\n# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz)\n#  define FMT_BUILTIN_CLZ(n) __builtin_clz(n)\n# endif\n\n# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll)\n#  define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n)\n# endif\n#endif\n\n// Some compilers masquerade as both MSVC and GCC-likes or\n// otherwise support __builtin_clz and __builtin_clzll, so\n// only define FMT_BUILTIN_CLZ using the MSVC intrinsics\n// if the clz and clzll builtins are not available.\n#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED)\n# include <intrin.h>  // _BitScanReverse, _BitScanReverse64\n\nnamespace fmt {\nnamespace internal {\n// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning\n# ifndef __clang__\n#  pragma intrinsic(_BitScanReverse)\n# endif\ninline uint32_t clz(uint32_t x) {\n  unsigned long r = 0;\n  _BitScanReverse(&r, x);\n\n  assert(x != 0);\n  // Static analysis complains about using uninitialized data\n  // \"r\", but the only way that can happen is if \"x\" is 0,\n  // which the callers guarantee to not happen.\n# pragma warning(suppress: 6102)\n  return 31 - r;\n}\n# define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n)\n\n// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning\n# if defined(_WIN64) && !defined(__clang__)\n#  pragma intrinsic(_BitScanReverse64)\n# endif\n\ninline uint32_t clzll(uint64_t x) {\n  unsigned long r = 0;\n# ifdef _WIN64\n  _BitScanReverse64(&r, x);\n# else\n  // Scan the high 32 bits.\n  if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32)))\n    return 63 - (r + 32);\n\n  // Scan the low 32 bits.\n  _BitScanReverse(&r, static_cast<uint32_t>(x));\n# endif\n\n  assert(x != 0);\n  // Static analysis complains about using uninitialized data\n  // \"r\", but the only way that can happen is if \"x\" is 0,\n  // which the callers guarantee to not happen.\n# pragma warning(suppress: 6102)\n  return 63 - r;\n}\n# define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n)\n}\n}\n#endif\n\nnamespace fmt {\nnamespace internal {\nstruct DummyInt {\n  int data[2];\n  operator int() const { return 0; }\n};\ntypedef std::numeric_limits<fmt::internal::DummyInt> FPUtil;\n\n// Dummy implementations of system functions such as signbit and ecvt called\n// if the latter are not available.\ninline DummyInt signbit(...) { return DummyInt(); }\ninline DummyInt _ecvt_s(...) { return DummyInt(); }\ninline DummyInt isinf(...) { return DummyInt(); }\ninline DummyInt _finite(...) { return DummyInt(); }\ninline DummyInt isnan(...) { return DummyInt(); }\ninline DummyInt _isnan(...) { return DummyInt(); }\n\n// A helper function to suppress bogus \"conditional expression is constant\"\n// warnings.\ntemplate <typename T>\ninline T const_check(T value) { return value; }\n}\n}  // namespace fmt\n\nnamespace std {\n// Standard permits specialization of std::numeric_limits. This specialization\n// is used to resolve ambiguity between isinf and std::isinf in glibc:\n// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891\n// and the same for isnan and signbit.\ntemplate <>\nclass numeric_limits<fmt::internal::DummyInt> :\n    public std::numeric_limits<int> {\n public:\n  // Portable version of isinf.\n  template <typename T>\n  static bool isinfinity(T x) {\n    using namespace fmt::internal;\n    // The resolution \"priority\" is:\n    // isinf macro > std::isinf > ::isinf > fmt::internal::isinf\n    if (const_check(sizeof(isinf(x)) == sizeof(bool) ||\n                    sizeof(isinf(x)) == sizeof(int))) {\n      return isinf(x) != 0;\n    }\n    return !_finite(static_cast<double>(x));\n  }\n\n  // Portable version of isnan.\n  template <typename T>\n  static bool isnotanumber(T x) {\n    using namespace fmt::internal;\n    if (const_check(sizeof(isnan(x)) == sizeof(bool) ||\n                    sizeof(isnan(x)) == sizeof(int))) {\n      return isnan(x) != 0;\n    }\n    return _isnan(static_cast<double>(x)) != 0;\n  }\n\n  // Portable version of signbit.\n  static bool isnegative(double x) {\n    using namespace fmt::internal;\n    if (const_check(sizeof(signbit(x)) == sizeof(bool) ||\n                    sizeof(signbit(x)) == sizeof(int))) {\n      return signbit(x) != 0;\n    }\n    if (x < 0) return true;\n    if (!isnotanumber(x)) return false;\n#ifndef _WIN32\n    int dec = 0, sign = 0;\n    char buffer[2];  // The buffer size must be >= 2 or _ecvt_s will fail.\n    _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign);\n    return sign != 0;\n#else\n    char buffer[2];\n    snprintf(buffer, 2, \"%g\", x);\n    return buffer[0] == '-';\n#endif\n  }\n};\n}  // namespace std\n\nnamespace fmt {\n\n// Fix the warning about long long on older versions of GCC\n// that don't support the diagnostic pragma.\nFMT_GCC_EXTENSION typedef long long LongLong;\nFMT_GCC_EXTENSION typedef unsigned long long ULongLong;\n\n#if FMT_USE_RVALUE_REFERENCES\nusing std::move;\n#endif\n\ntemplate <typename Char>\nclass BasicWriter;\n\ntypedef BasicWriter<char> Writer;\ntypedef BasicWriter<wchar_t> WWriter;\n\ntemplate <typename Char>\nclass ArgFormatter;\n\nstruct FormatSpec;\n\ntemplate <typename Impl, typename Char, typename Spec = fmt::FormatSpec>\nclass BasicPrintfArgFormatter;\n\ntemplate <typename CharType,\n          typename ArgFormatter = fmt::ArgFormatter<CharType> >\nclass BasicFormatter;\n\n/**\n  \\rst\n  A string reference. It can be constructed from a C string or\n  ``std::basic_string``.\n\n  You can use one of the following typedefs for common character types:\n\n  +------------+-------------------------+\n  | Type       | Definition              |\n  +============+=========================+\n  | StringRef  | BasicStringRef<char>    |\n  +------------+-------------------------+\n  | WStringRef | BasicStringRef<wchar_t> |\n  +------------+-------------------------+\n\n  This class is most useful as a parameter type to allow passing\n  different types of strings to a function, for example::\n\n    template <typename... Args>\n    std::string format(StringRef format_str, const Args & ... args);\n\n    format(\"{}\", 42);\n    format(std::string(\"{}\"), 42);\n  \\endrst\n */\ntemplate <typename Char>\nclass BasicStringRef {\n private:\n  const Char *data_;\n  std::size_t size_;\n\n public:\n  /** Constructs a string reference object from a C string and a size. */\n  BasicStringRef(const Char *s, std::size_t size) : data_(s), size_(size) {}\n\n  /**\n    \\rst\n    Constructs a string reference object from a C string computing\n    the size with ``std::char_traits<Char>::length``.\n    \\endrst\n   */\n  BasicStringRef(const Char *s)\n    : data_(s), size_(std::char_traits<Char>::length(s)) {}\n\n  /**\n    \\rst\n    Constructs a string reference from a ``std::basic_string`` object.\n    \\endrst\n   */\n  template <typename Allocator>\n  BasicStringRef(\n      const std::basic_string<Char, std::char_traits<Char>, Allocator> &s)\n  : data_(s.c_str()), size_(s.size()) {}\n\n#if FMT_HAS_STRING_VIEW\n  /**\n    \\rst\n    Constructs a string reference from a ``std::basic_string_view`` object.\n    \\endrst\n   */\n  BasicStringRef(\n      const std::basic_string_view<Char, std::char_traits<Char>> &s)\n  : data_(s.data()), size_(s.size()) {}\n\n  /**\n   \\rst\n   Converts a string reference to an ``std::string_view`` object.\n   \\endrst\n  */\n  explicit operator std::basic_string_view<Char>() const FMT_NOEXCEPT {\n    return std::basic_string_view<Char>(data_, size_);\n  }\n#endif\n\n  /**\n    \\rst\n    Converts a string reference to an ``std::string`` object.\n    \\endrst\n   */\n  std::basic_string<Char> to_string() const {\n    return std::basic_string<Char>(data_, size_);\n  }\n\n  /** Returns a pointer to the string data. */\n  const Char *data() const { return data_; }\n\n  /** Returns the string size. */\n  std::size_t size() const { return size_; }\n\n  // Lexicographically compare this string reference to other.\n  int compare(BasicStringRef other) const {\n    std::size_t size = size_ < other.size_ ? size_ : other.size_;\n    int result = std::char_traits<Char>::compare(data_, other.data_, size);\n    if (result == 0)\n      result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1);\n    return result;\n  }\n\n  friend bool operator==(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) == 0;\n  }\n  friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) != 0;\n  }\n  friend bool operator<(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) < 0;\n  }\n  friend bool operator<=(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) <= 0;\n  }\n  friend bool operator>(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) > 0;\n  }\n  friend bool operator>=(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) >= 0;\n  }\n};\n\ntypedef BasicStringRef<char> StringRef;\ntypedef BasicStringRef<wchar_t> WStringRef;\n\n/**\n  \\rst\n  A reference to a null terminated string. It can be constructed from a C\n  string or ``std::basic_string``.\n\n  You can use one of the following typedefs for common character types:\n\n  +-------------+--------------------------+\n  | Type        | Definition               |\n  +=============+==========================+\n  | CStringRef  | BasicCStringRef<char>    |\n  +-------------+--------------------------+\n  | WCStringRef | BasicCStringRef<wchar_t> |\n  +-------------+--------------------------+\n\n  This class is most useful as a parameter type to allow passing\n  different types of strings to a function, for example::\n\n    template <typename... Args>\n    std::string format(CStringRef format_str, const Args & ... args);\n\n    format(\"{}\", 42);\n    format(std::string(\"{}\"), 42);\n  \\endrst\n */\ntemplate <typename Char>\nclass BasicCStringRef {\n private:\n  const Char *data_;\n\n public:\n  /** Constructs a string reference object from a C string. */\n  BasicCStringRef(const Char *s) : data_(s) {}\n\n  /**\n    \\rst\n    Constructs a string reference from a ``std::basic_string`` object.\n    \\endrst\n   */\n  template <typename Allocator>\n  BasicCStringRef(\n      const std::basic_string<Char, std::char_traits<Char>, Allocator> &s)\n  : data_(s.c_str()) {}\n\n  /** Returns the pointer to a C string. */\n  const Char *c_str() const { return data_; }\n};\n\ntypedef BasicCStringRef<char> CStringRef;\ntypedef BasicCStringRef<wchar_t> WCStringRef;\n\n/** A formatting error such as invalid format string. */\nclass FormatError : public std::runtime_error {\n public:\n  explicit FormatError(CStringRef message)\n  : std::runtime_error(message.c_str()) {}\n  FormatError(const FormatError &ferr) : std::runtime_error(ferr) {}\n  FMT_API ~FormatError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE;\n};\n\nnamespace internal {\n\n// MakeUnsigned<T>::Type gives an unsigned type corresponding to integer type T.\ntemplate <typename T>\nstruct MakeUnsigned { typedef T Type; };\n\n#define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \\\n  template <> \\\n  struct MakeUnsigned<T> { typedef U Type; }\n\nFMT_SPECIALIZE_MAKE_UNSIGNED(char, unsigned char);\nFMT_SPECIALIZE_MAKE_UNSIGNED(signed char, unsigned char);\nFMT_SPECIALIZE_MAKE_UNSIGNED(short, unsigned short);\nFMT_SPECIALIZE_MAKE_UNSIGNED(int, unsigned);\nFMT_SPECIALIZE_MAKE_UNSIGNED(long, unsigned long);\nFMT_SPECIALIZE_MAKE_UNSIGNED(LongLong, ULongLong);\n\n// Casts nonnegative integer to unsigned.\ntemplate <typename Int>\ninline typename MakeUnsigned<Int>::Type to_unsigned(Int value) {\n  FMT_ASSERT(value >= 0, \"negative value\");\n  return static_cast<typename MakeUnsigned<Int>::Type>(value);\n}\n\n// The number of characters to store in the MemoryBuffer object itself\n// to avoid dynamic memory allocation.\nenum { INLINE_BUFFER_SIZE = 500 };\n\n#if FMT_SECURE_SCL\n// Use checked iterator to avoid warnings on MSVC.\ntemplate <typename T>\ninline stdext::checked_array_iterator<T*> make_ptr(T *ptr, std::size_t size) {\n  return stdext::checked_array_iterator<T*>(ptr, size);\n}\n#else\ntemplate <typename T>\ninline T *make_ptr(T *ptr, std::size_t) { return ptr; }\n#endif\n}  // namespace internal\n\n/**\n  \\rst\n  A buffer supporting a subset of ``std::vector``'s operations.\n  \\endrst\n */\ntemplate <typename T>\nclass Buffer {\n private:\n  FMT_DISALLOW_COPY_AND_ASSIGN(Buffer);\n\n protected:\n  T *ptr_;\n  std::size_t size_;\n  std::size_t capacity_;\n\n  Buffer(T *ptr = FMT_NULL, std::size_t capacity = 0)\n    : ptr_(ptr), size_(0), capacity_(capacity) {}\n\n  /**\n    \\rst\n    Increases the buffer capacity to hold at least *size* elements updating\n    ``ptr_`` and ``capacity_``.\n    \\endrst\n   */\n  virtual void grow(std::size_t size) = 0;\n\n public:\n  virtual ~Buffer() {}\n\n  /** Returns the size of this buffer. */\n  std::size_t size() const { return size_; }\n\n  /** Returns the capacity of this buffer. */\n  std::size_t capacity() const { return capacity_; }\n\n  /**\n    Resizes the buffer. If T is a POD type new elements may not be initialized.\n   */\n  void resize(std::size_t new_size) {\n    if (new_size > capacity_)\n      grow(new_size);\n    size_ = new_size;\n  }\n\n  /**\n    \\rst\n    Reserves space to store at least *capacity* elements.\n    \\endrst\n   */\n  void reserve(std::size_t capacity) {\n    if (capacity > capacity_)\n      grow(capacity);\n  }\n\n  void clear() FMT_NOEXCEPT { size_ = 0; }\n\n  void push_back(const T &value) {\n    if (size_ == capacity_)\n      grow(size_ + 1);\n    ptr_[size_++] = value;\n  }\n\n  /** Appends data to the end of the buffer. */\n  template <typename U>\n  void append(const U *begin, const U *end);\n\n  T &operator[](std::size_t index) { return ptr_[index]; }\n  const T &operator[](std::size_t index) const { return ptr_[index]; }\n};\n\ntemplate <typename T>\ntemplate <typename U>\nvoid Buffer<T>::append(const U *begin, const U *end) {\n  FMT_ASSERT(end >= begin, \"negative value\");\n  std::size_t new_size = size_ + static_cast<std::size_t>(end - begin);\n  if (new_size > capacity_)\n    grow(new_size);\n  std::uninitialized_copy(begin, end,\n                          internal::make_ptr(ptr_, capacity_) + size_);\n  size_ = new_size;\n}\n\nnamespace internal {\n\n// A memory buffer for trivially copyable/constructible types with the first\n// SIZE elements stored in the object itself.\ntemplate <typename T, std::size_t SIZE, typename Allocator = std::allocator<T> >\nclass MemoryBuffer : private Allocator, public Buffer<T> {\n private:\n  T data_[SIZE];\n\n  // Deallocate memory allocated by the buffer.\n  void deallocate() {\n    if (this->ptr_ != data_) Allocator::deallocate(this->ptr_, this->capacity_);\n  }\n\n protected:\n  void grow(std::size_t size) FMT_OVERRIDE;\n\n public:\n  explicit MemoryBuffer(const Allocator &alloc = Allocator())\n      : Allocator(alloc), Buffer<T>(data_, SIZE) {}\n  ~MemoryBuffer() FMT_OVERRIDE { deallocate(); }\n\n#if FMT_USE_RVALUE_REFERENCES\n private:\n  // Move data from other to this buffer.\n  void move(MemoryBuffer &other) {\n    Allocator &this_alloc = *this, &other_alloc = other;\n    this_alloc = std::move(other_alloc);\n    this->size_ = other.size_;\n    this->capacity_ = other.capacity_;\n    if (other.ptr_ == other.data_) {\n      this->ptr_ = data_;\n      std::uninitialized_copy(other.data_, other.data_ + this->size_,\n                              make_ptr(data_, this->capacity_));\n    } else {\n      this->ptr_ = other.ptr_;\n      // Set pointer to the inline array so that delete is not called\n      // when deallocating.\n      other.ptr_ = other.data_;\n    }\n  }\n\n public:\n  MemoryBuffer(MemoryBuffer &&other) {\n    move(other);\n  }\n\n  MemoryBuffer &operator=(MemoryBuffer &&other) {\n    assert(this != &other);\n    deallocate();\n    move(other);\n    return *this;\n  }\n#endif\n\n  // Returns a copy of the allocator associated with this buffer.\n  Allocator get_allocator() const { return *this; }\n};\n\ntemplate <typename T, std::size_t SIZE, typename Allocator>\nvoid MemoryBuffer<T, SIZE, Allocator>::grow(std::size_t size) {\n  std::size_t new_capacity = this->capacity_ + this->capacity_ / 2;\n  if (size > new_capacity)\n      new_capacity = size;\n#if FMT_USE_ALLOCATOR_TRAITS\n  T *new_ptr =\n      std::allocator_traits<Allocator>::allocate(*this, new_capacity, FMT_NULL);\n#else\n  T *new_ptr = this->allocate(new_capacity, FMT_NULL);\n#endif\n  // The following code doesn't throw, so the raw pointer above doesn't leak.\n  std::uninitialized_copy(this->ptr_, this->ptr_ + this->size_,\n                          make_ptr(new_ptr, new_capacity));\n  std::size_t old_capacity = this->capacity_;\n  T *old_ptr = this->ptr_;\n  this->capacity_ = new_capacity;\n  this->ptr_ = new_ptr;\n  // deallocate may throw (at least in principle), but it doesn't matter since\n  // the buffer already uses the new storage and will deallocate it in case\n  // of exception.\n  if (old_ptr != data_)\n    Allocator::deallocate(old_ptr, old_capacity);\n}\n\n// A fixed-size buffer.\ntemplate <typename Char>\nclass FixedBuffer : public fmt::Buffer<Char> {\n public:\n  FixedBuffer(Char *array, std::size_t size) : fmt::Buffer<Char>(array, size) {}\n\n protected:\n  FMT_API void grow(std::size_t size) FMT_OVERRIDE;\n};\n\ntemplate <typename Char>\nclass BasicCharTraits {\n public:\n#if FMT_SECURE_SCL\n  typedef stdext::checked_array_iterator<Char*> CharPtr;\n#else\n  typedef Char *CharPtr;\n#endif\n  static Char cast(int value) { return static_cast<Char>(value); }\n};\n\ntemplate <typename Char>\nclass CharTraits;\n\ntemplate <>\nclass CharTraits<char> : public BasicCharTraits<char> {\n private:\n  // Conversion from wchar_t to char is not allowed.\n  static char convert(wchar_t);\n\n public:\n  static char convert(char value) { return value; }\n\n  // Formats a floating-point number.\n  template <typename T>\n  FMT_API static int format_float(char *buffer, std::size_t size,\n      const char *format, unsigned width, int precision, T value);\n};\n\n#if FMT_USE_EXTERN_TEMPLATES\nextern template int CharTraits<char>::format_float<double>\n        (char *buffer, std::size_t size,\n         const char* format, unsigned width, int precision, double value);\nextern template int CharTraits<char>::format_float<long double>\n        (char *buffer, std::size_t size,\n         const char* format, unsigned width, int precision, long double value);\n#endif\n\ntemplate <>\nclass CharTraits<wchar_t> : public BasicCharTraits<wchar_t> {\n public:\n  static wchar_t convert(char value) { return value; }\n  static wchar_t convert(wchar_t value) { return value; }\n\n  template <typename T>\n  FMT_API static int format_float(wchar_t *buffer, std::size_t size,\n      const wchar_t *format, unsigned width, int precision, T value);\n};\n\n#if FMT_USE_EXTERN_TEMPLATES\nextern template int CharTraits<wchar_t>::format_float<double>\n        (wchar_t *buffer, std::size_t size,\n         const wchar_t* format, unsigned width, int precision, double value);\nextern template int CharTraits<wchar_t>::format_float<long double>\n        (wchar_t *buffer, std::size_t size,\n         const wchar_t* format, unsigned width, int precision, long double value);\n#endif\n\n// Checks if a number is negative - used to avoid warnings.\ntemplate <bool IsSigned>\nstruct SignChecker {\n  template <typename T>\n  static bool is_negative(T value) { return value < 0; }\n};\n\ntemplate <>\nstruct SignChecker<false> {\n  template <typename T>\n  static bool is_negative(T) { return false; }\n};\n\n// Returns true if value is negative, false otherwise.\n// Same as (value < 0) but doesn't produce warnings if T is an unsigned type.\ntemplate <typename T>\ninline bool is_negative(T value) {\n  return SignChecker<std::numeric_limits<T>::is_signed>::is_negative(value);\n}\n\n// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise.\ntemplate <bool FitsIn32Bits>\nstruct TypeSelector { typedef uint32_t Type; };\n\ntemplate <>\nstruct TypeSelector<false> { typedef uint64_t Type; };\n\ntemplate <typename T>\nstruct IntTraits {\n  // Smallest of uint32_t and uint64_t that is large enough to represent\n  // all values of T.\n  typedef typename\n    TypeSelector<std::numeric_limits<T>::digits <= 32>::Type MainType;\n};\n\nFMT_API FMT_NORETURN void report_unknown_type(char code, const char *type);\n\n// Static data is placed in this class template to allow header-only\n// configuration.\ntemplate <typename T = void>\nstruct FMT_API BasicData {\n  static const uint32_t POWERS_OF_10_32[];\n  static const uint64_t POWERS_OF_10_64[];\n  static const char DIGITS[];\n};\n\n#if FMT_USE_EXTERN_TEMPLATES\nextern template struct BasicData<void>;\n#endif\n\ntypedef BasicData<> Data;\n\n#ifdef FMT_BUILTIN_CLZLL\n// Returns the number of decimal digits in n. Leading zeros are not counted\n// except for n == 0 in which case count_digits returns 1.\ninline unsigned count_digits(uint64_t n) {\n  // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10\n  // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits.\n  int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12;\n  return to_unsigned(t) - (n < Data::POWERS_OF_10_64[t]) + 1;\n}\n#else\n// Fallback version of count_digits used when __builtin_clz is not available.\ninline unsigned count_digits(uint64_t n) {\n  unsigned count = 1;\n  for (;;) {\n    // Integer division is slow so do it for a group of four digits instead\n    // of for every digit. The idea comes from the talk by Alexandrescu\n    // \"Three Optimization Tips for C++\". See speed-test for a comparison.\n    if (n < 10) return count;\n    if (n < 100) return count + 1;\n    if (n < 1000) return count + 2;\n    if (n < 10000) return count + 3;\n    n /= 10000u;\n    count += 4;\n  }\n}\n#endif\n\n#ifdef FMT_BUILTIN_CLZ\n// Optional version of count_digits for better performance on 32-bit platforms.\ninline unsigned count_digits(uint32_t n) {\n  int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12;\n  return to_unsigned(t) - (n < Data::POWERS_OF_10_32[t]) + 1;\n}\n#endif\n\n// A functor that doesn't add a thousands separator.\nstruct NoThousandsSep {\n  template <typename Char>\n  void operator()(Char *) {}\n};\n\n// A functor that adds a thousands separator.\nclass ThousandsSep {\n private:\n  fmt::StringRef sep_;\n\n  // Index of a decimal digit with the least significant digit having index 0.\n  unsigned digit_index_;\n\n public:\n  explicit ThousandsSep(fmt::StringRef sep) : sep_(sep), digit_index_(0) {}\n\n  template <typename Char>\n  void operator()(Char *&buffer) {\n    if (++digit_index_ % 3 != 0)\n      return;\n    buffer -= sep_.size();\n    std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(),\n                            internal::make_ptr(buffer, sep_.size()));\n  }\n};\n\n// Formats a decimal unsigned integer value writing into buffer.\n// thousands_sep is a functor that is called after writing each char to\n// add a thousands separator if necessary.\ntemplate <typename UInt, typename Char, typename ThousandsSep>\ninline void format_decimal(Char *buffer, UInt value, unsigned num_digits,\n                           ThousandsSep thousands_sep) {\n  buffer += num_digits;\n  while (value >= 100) {\n    // Integer division is slow so do it for a group of two digits instead\n    // of for every digit. The idea comes from the talk by Alexandrescu\n    // \"Three Optimization Tips for C++\". See speed-test for a comparison.\n    unsigned index = static_cast<unsigned>((value % 100) * 2);\n    value /= 100;\n    *--buffer = Data::DIGITS[index + 1];\n    thousands_sep(buffer);\n    *--buffer = Data::DIGITS[index];\n    thousands_sep(buffer);\n  }\n  if (value < 10) {\n    *--buffer = static_cast<char>('0' + value);\n    return;\n  }\n  unsigned index = static_cast<unsigned>(value * 2);\n  *--buffer = Data::DIGITS[index + 1];\n  thousands_sep(buffer);\n  *--buffer = Data::DIGITS[index];\n}\n\ntemplate <typename UInt, typename Char>\ninline void format_decimal(Char *buffer, UInt value, unsigned num_digits) {\n  format_decimal(buffer, value, num_digits, NoThousandsSep());\n  return;\n}\n\n#ifndef _WIN32\n# define FMT_USE_WINDOWS_H 0\n#elif !defined(FMT_USE_WINDOWS_H)\n# define FMT_USE_WINDOWS_H 1\n#endif\n\n// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h.\n// All the functionality that relies on it will be disabled too.\n#if FMT_USE_WINDOWS_H\n// A converter from UTF-8 to UTF-16.\n// It is only provided for Windows since other systems support UTF-8 natively.\nclass UTF8ToUTF16 {\n private:\n  MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer_;\n\n public:\n  FMT_API explicit UTF8ToUTF16(StringRef s);\n  operator WStringRef() const { return WStringRef(&buffer_[0], size()); }\n  size_t size() const { return buffer_.size() - 1; }\n  const wchar_t *c_str() const { return &buffer_[0]; }\n  std::wstring str() const { return std::wstring(&buffer_[0], size()); }\n};\n\n// A converter from UTF-16 to UTF-8.\n// It is only provided for Windows since other systems support UTF-8 natively.\nclass UTF16ToUTF8 {\n private:\n  MemoryBuffer<char, INLINE_BUFFER_SIZE> buffer_;\n\n public:\n  UTF16ToUTF8() {}\n  FMT_API explicit UTF16ToUTF8(WStringRef s);\n  operator StringRef() const { return StringRef(&buffer_[0], size()); }\n  size_t size() const { return buffer_.size() - 1; }\n  const char *c_str() const { return &buffer_[0]; }\n  std::string str() const { return std::string(&buffer_[0], size()); }\n\n  // Performs conversion returning a system error code instead of\n  // throwing exception on conversion error. This method may still throw\n  // in case of memory allocation error.\n  FMT_API int convert(WStringRef s);\n};\n\nFMT_API void format_windows_error(fmt::Writer &out, int error_code,\n                                  fmt::StringRef message) FMT_NOEXCEPT;\n#endif\n\n// A formatting argument value.\nstruct Value {\n  template <typename Char>\n  struct StringValue {\n    const Char *value;\n    std::size_t size;\n  };\n\n  typedef void (*FormatFunc)(\n      void *formatter, const void *arg, void *format_str_ptr);\n\n  struct CustomValue {\n    const void *value;\n    FormatFunc format;\n  };\n\n  union {\n    int int_value;\n    unsigned uint_value;\n    LongLong long_long_value;\n    ULongLong ulong_long_value;\n    double double_value;\n    long double long_double_value;\n    const void *pointer;\n    StringValue<char> string;\n    StringValue<signed char> sstring;\n    StringValue<unsigned char> ustring;\n    StringValue<wchar_t> wstring;\n    CustomValue custom;\n  };\n\n  enum Type {\n    NONE, NAMED_ARG,\n    // Integer types should go first,\n    INT, UINT, LONG_LONG, ULONG_LONG, BOOL, CHAR, LAST_INTEGER_TYPE = CHAR,\n    // followed by floating-point types.\n    DOUBLE, LONG_DOUBLE, LAST_NUMERIC_TYPE = LONG_DOUBLE,\n    CSTRING, STRING, WSTRING, POINTER, CUSTOM\n  };\n};\n\n// A formatting argument. It is a trivially copyable/constructible type to\n// allow storage in internal::MemoryBuffer.\nstruct Arg : Value {\n  Type type;\n};\n\ntemplate <typename Char>\nstruct NamedArg;\ntemplate <typename Char, typename T>\nstruct NamedArgWithType;\n\ntemplate <typename T = void>\nstruct Null {};\n\n// A helper class template to enable or disable overloads taking wide\n// characters and strings in MakeValue.\ntemplate <typename T, typename Char>\nstruct WCharHelper {\n  typedef Null<T> Supported;\n  typedef T Unsupported;\n};\n\ntemplate <typename T>\nstruct WCharHelper<T, wchar_t> {\n  typedef T Supported;\n  typedef Null<T> Unsupported;\n};\n\ntypedef char Yes[1];\ntypedef char No[2];\n\ntemplate <typename T>\nT &get();\n\n// These are non-members to workaround an overload resolution bug in bcc32.\nYes &convert(fmt::ULongLong);\nNo &convert(...);\n\ntemplate <typename T, bool ENABLE_CONVERSION>\nstruct ConvertToIntImpl {\n  enum { value = ENABLE_CONVERSION };\n};\n\ntemplate <typename T, bool ENABLE_CONVERSION>\nstruct ConvertToIntImpl2 {\n  enum { value = false };\n};\n\ntemplate <typename T>\nstruct ConvertToIntImpl2<T, true> {\n  enum {\n    // Don't convert numeric types.\n    value = ConvertToIntImpl<T, !std::numeric_limits<T>::is_specialized>::value\n  };\n};\n\ntemplate <typename T>\nstruct ConvertToInt {\n  enum {\n    enable_conversion = sizeof(fmt::internal::convert(get<T>())) == sizeof(Yes)\n  };\n  enum { value = ConvertToIntImpl2<T, enable_conversion>::value };\n};\n\n#define FMT_DISABLE_CONVERSION_TO_INT(Type) \\\n  template <> \\\n  struct ConvertToInt<Type> {  enum { value = 0 }; }\n\n// Silence warnings about convering float to int.\nFMT_DISABLE_CONVERSION_TO_INT(float);\nFMT_DISABLE_CONVERSION_TO_INT(double);\nFMT_DISABLE_CONVERSION_TO_INT(long double);\n\ntemplate <bool B, class T = void>\nstruct EnableIf {};\n\ntemplate <class T>\nstruct EnableIf<true, T> { typedef T type; };\n\ntemplate <bool B, class T, class F>\nstruct Conditional { typedef T type; };\n\ntemplate <class T, class F>\nstruct Conditional<false, T, F> { typedef F type; };\n\n// For bcc32 which doesn't understand ! in template arguments.\ntemplate <bool>\nstruct Not { enum { value = 0 }; };\n\ntemplate <>\nstruct Not<false> { enum { value = 1 }; };\n\ntemplate <typename T>\nstruct FalseType { enum { value = 0 }; };\n\ntemplate <typename T, T> struct LConvCheck {\n  LConvCheck(int) {}\n};\n\n// Returns the thousands separator for the current locale.\n// We check if ``lconv`` contains ``thousands_sep`` because on Android\n// ``lconv`` is stubbed as an empty struct.\ntemplate <typename LConv>\ninline StringRef thousands_sep(\n    LConv *lc, LConvCheck<char *LConv::*, &LConv::thousands_sep> = 0) {\n  return lc->thousands_sep;\n}\n\ninline fmt::StringRef thousands_sep(...) { return \"\"; }\n\n#define FMT_CONCAT(a, b) a##b\n\n#if FMT_GCC_VERSION >= 303\n# define FMT_UNUSED __attribute__((unused))\n#else\n# define FMT_UNUSED\n#endif\n\n#ifndef FMT_USE_STATIC_ASSERT\n# define FMT_USE_STATIC_ASSERT 0\n#endif\n\n#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \\\n  (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600\n# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message)\n#else\n# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b)\n# define FMT_STATIC_ASSERT(cond, message) \\\n  typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED\n#endif\n\ntemplate <typename Formatter>\nvoid format_arg(Formatter&, ...) {\n  FMT_STATIC_ASSERT(FalseType<Formatter>::value,\n                    \"Cannot format argument. To enable the use of ostream \"\n                    \"operator<< include fmt/ostream.h. Otherwise provide \"\n                    \"an overload of format_arg.\");\n}\n\n// Makes an Arg object from any type.\ntemplate <typename Formatter>\nclass MakeValue : public Arg {\n public:\n  typedef typename Formatter::Char Char;\n\n private:\n  // The following two methods are private to disallow formatting of\n  // arbitrary pointers. If you want to output a pointer cast it to\n  // \"void *\" or \"const void *\". In particular, this forbids formatting\n  // of \"[const] volatile char *\" which is printed as bool by iostreams.\n  // Do not implement!\n  template <typename T>\n  MakeValue(const T *value);\n  template <typename T>\n  MakeValue(T *value);\n\n  // The following methods are private to disallow formatting of wide\n  // characters and strings into narrow strings as in\n  //   fmt::format(\"{}\", L\"test\");\n  // To fix this, use a wide format string: fmt::format(L\"{}\", L\"test\").\n#if !FMT_MSC_VER || defined(_NATIVE_WCHAR_T_DEFINED)\n  MakeValue(typename WCharHelper<wchar_t, Char>::Unsupported);\n#endif\n  MakeValue(typename WCharHelper<wchar_t *, Char>::Unsupported);\n  MakeValue(typename WCharHelper<const wchar_t *, Char>::Unsupported);\n  MakeValue(typename WCharHelper<const std::wstring &, Char>::Unsupported);\n#if FMT_HAS_STRING_VIEW\n  MakeValue(typename WCharHelper<const std::wstring_view &, Char>::Unsupported);\n#endif\n  MakeValue(typename WCharHelper<WStringRef, Char>::Unsupported);\n\n  void set_string(StringRef str) {\n    string.value = str.data();\n    string.size = str.size();\n  }\n\n  void set_string(WStringRef str) {\n    wstring.value = str.data();\n    wstring.size = str.size();\n  }\n\n  // Formats an argument of a custom type, such as a user-defined class.\n  template <typename T>\n  static void format_custom_arg(\n      void *formatter, const void *arg, void *format_str_ptr) {\n    format_arg(*static_cast<Formatter*>(formatter),\n               *static_cast<const Char**>(format_str_ptr),\n               *static_cast<const T*>(arg));\n  }\n\n public:\n  MakeValue() {}\n\n#define FMT_MAKE_VALUE_(Type, field, TYPE, rhs) \\\n  MakeValue(Type value) { field = rhs; } \\\n  static uint64_t type(Type) { return Arg::TYPE; }\n\n#define FMT_MAKE_VALUE(Type, field, TYPE) \\\n  FMT_MAKE_VALUE_(Type, field, TYPE, value)\n\n  FMT_MAKE_VALUE(bool, int_value, BOOL)\n  FMT_MAKE_VALUE(short, int_value, INT)\n  FMT_MAKE_VALUE(unsigned short, uint_value, UINT)\n  FMT_MAKE_VALUE(int, int_value, INT)\n  FMT_MAKE_VALUE(unsigned, uint_value, UINT)\n\n  MakeValue(long value) {\n    // To minimize the number of types we need to deal with, long is\n    // translated either to int or to long long depending on its size.\n    if (const_check(sizeof(long) == sizeof(int)))\n      int_value = static_cast<int>(value);\n    else\n      long_long_value = value;\n  }\n  static uint64_t type(long) {\n    return sizeof(long) == sizeof(int) ? Arg::INT : Arg::LONG_LONG;\n  }\n\n  MakeValue(unsigned long value) {\n    if (const_check(sizeof(unsigned long) == sizeof(unsigned)))\n      uint_value = static_cast<unsigned>(value);\n    else\n      ulong_long_value = value;\n  }\n  static uint64_t type(unsigned long) {\n    return sizeof(unsigned long) == sizeof(unsigned) ?\n          Arg::UINT : Arg::ULONG_LONG;\n  }\n\n  FMT_MAKE_VALUE(LongLong, long_long_value, LONG_LONG)\n  FMT_MAKE_VALUE(ULongLong, ulong_long_value, ULONG_LONG)\n  FMT_MAKE_VALUE(float, double_value, DOUBLE)\n  FMT_MAKE_VALUE(double, double_value, DOUBLE)\n  FMT_MAKE_VALUE(long double, long_double_value, LONG_DOUBLE)\n  FMT_MAKE_VALUE(signed char, int_value, INT)\n  FMT_MAKE_VALUE(unsigned char, uint_value, UINT)\n  FMT_MAKE_VALUE(char, int_value, CHAR)\n\n#if __cplusplus >= 201103L\n  template <\n    typename T,\n    typename = typename std::enable_if<\n      std::is_enum<T>::value && ConvertToInt<T>::value>::type>\n   MakeValue(T value) { int_value = value; }\n\n  template <\n    typename T,\n    typename = typename std::enable_if<\n      std::is_enum<T>::value && ConvertToInt<T>::value>::type>\n  static uint64_t type(T) { return Arg::INT; }\n#endif\n\n#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED)\n  MakeValue(typename WCharHelper<wchar_t, Char>::Supported value) {\n    int_value = value;\n  }\n  static uint64_t type(wchar_t) { return Arg::CHAR; }\n#endif\n\n#define FMT_MAKE_STR_VALUE(Type, TYPE) \\\n  MakeValue(Type value) { set_string(value); } \\\n  static uint64_t type(Type) { return Arg::TYPE; }\n\n  FMT_MAKE_VALUE(char *, string.value, CSTRING)\n  FMT_MAKE_VALUE(const char *, string.value, CSTRING)\n  FMT_MAKE_VALUE(signed char *, sstring.value, CSTRING)\n  FMT_MAKE_VALUE(const signed char *, sstring.value, CSTRING)\n  FMT_MAKE_VALUE(unsigned char *, ustring.value, CSTRING)\n  FMT_MAKE_VALUE(const unsigned char *, ustring.value, CSTRING)\n  FMT_MAKE_STR_VALUE(const std::string &, STRING)\n#if FMT_HAS_STRING_VIEW\n  FMT_MAKE_STR_VALUE(const std::string_view &, STRING)\n#endif\n  FMT_MAKE_STR_VALUE(StringRef, STRING)\n  FMT_MAKE_VALUE_(CStringRef, string.value, CSTRING, value.c_str())\n\n#define FMT_MAKE_WSTR_VALUE(Type, TYPE) \\\n  MakeValue(typename WCharHelper<Type, Char>::Supported value) { \\\n    set_string(value); \\\n  } \\\n  static uint64_t type(Type) { return Arg::TYPE; }\n\n  FMT_MAKE_WSTR_VALUE(wchar_t *, WSTRING)\n  FMT_MAKE_WSTR_VALUE(const wchar_t *, WSTRING)\n  FMT_MAKE_WSTR_VALUE(const std::wstring &, WSTRING)\n#if FMT_HAS_STRING_VIEW\n  FMT_MAKE_WSTR_VALUE(const std::wstring_view &, WSTRING)\n#endif\n  FMT_MAKE_WSTR_VALUE(WStringRef, WSTRING)\n\n  FMT_MAKE_VALUE(void *, pointer, POINTER)\n  FMT_MAKE_VALUE(const void *, pointer, POINTER)\n\n  template <typename T>\n  MakeValue(const T &value,\n            typename EnableIf<Not<\n              ConvertToInt<T>::value>::value, int>::type = 0) {\n    custom.value = &value;\n    custom.format = &format_custom_arg<T>;\n  }\n\n  template <typename T>\n  static typename EnableIf<Not<ConvertToInt<T>::value>::value, uint64_t>::type\n      type(const T &) {\n    return Arg::CUSTOM;\n  }\n\n  // Additional template param `Char_` is needed here because make_type always\n  // uses char.\n  template <typename Char_>\n  MakeValue(const NamedArg<Char_> &value) { pointer = &value; }\n  template <typename Char_, typename T>\n  MakeValue(const NamedArgWithType<Char_, T> &value) { pointer = &value; }\n\n  template <typename Char_>\n  static uint64_t type(const NamedArg<Char_> &) { return Arg::NAMED_ARG; }\n  template <typename Char_, typename T>\n  static uint64_t type(const NamedArgWithType<Char_, T> &) { return Arg::NAMED_ARG; }\n};\n\ntemplate <typename Formatter>\nclass MakeArg : public Arg {\npublic:\n  MakeArg() {\n    type = Arg::NONE;\n  }\n\n  template <typename T>\n  MakeArg(const T &value)\n  : Arg(MakeValue<Formatter>(value)) {\n    type = static_cast<Arg::Type>(MakeValue<Formatter>::type(value));\n  }\n};\n\ntemplate <typename Char>\nstruct NamedArg : Arg {\n  BasicStringRef<Char> name;\n\n  template <typename T>\n  NamedArg(BasicStringRef<Char> argname, const T &value)\n  : Arg(MakeArg< BasicFormatter<Char> >(value)), name(argname) {}\n};\n\ntemplate <typename Char, typename T>\nstruct NamedArgWithType : NamedArg<Char> {\n  NamedArgWithType(BasicStringRef<Char> argname, const T &value)\n  : NamedArg<Char>(argname, value) {}\n};\n\nclass RuntimeError : public std::runtime_error {\n protected:\n  RuntimeError() : std::runtime_error(\"\") {}\n  RuntimeError(const RuntimeError &rerr) : std::runtime_error(rerr) {}\n  FMT_API ~RuntimeError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE;\n};\n\ntemplate <typename Char>\nclass ArgMap;\n}  // namespace internal\n\n/** An argument list. */\nclass ArgList {\n private:\n  // To reduce compiled code size per formatting function call, types of first\n  // MAX_PACKED_ARGS arguments are passed in the types_ field.\n  uint64_t types_;\n  union {\n    // If the number of arguments is less than MAX_PACKED_ARGS, the argument\n    // values are stored in values_, otherwise they are stored in args_.\n    // This is done to reduce compiled code size as storing larger objects\n    // may require more code (at least on x86-64) even if the same amount of\n    // data is actually copied to stack. It saves ~10% on the bloat test.\n    const internal::Value *values_;\n    const internal::Arg *args_;\n  };\n\n  internal::Arg::Type type(unsigned index) const {\n    return type(types_, index);\n  }\n\n  template <typename Char>\n  friend class internal::ArgMap;\n\n public:\n  // Maximum number of arguments with packed types.\n  enum { MAX_PACKED_ARGS = 16 };\n\n  ArgList() : types_(0) {}\n\n  ArgList(ULongLong types, const internal::Value *values)\n  : types_(types), values_(values) {}\n  ArgList(ULongLong types, const internal::Arg *args)\n  : types_(types), args_(args) {}\n\n  uint64_t types() const { return types_; }\n\n  /** Returns the argument at specified index. */\n  internal::Arg operator[](unsigned index) const {\n    using internal::Arg;\n    Arg arg;\n    bool use_values = type(MAX_PACKED_ARGS - 1) == Arg::NONE;\n    if (index < MAX_PACKED_ARGS) {\n      Arg::Type arg_type = type(index);\n      internal::Value &val = arg;\n      if (arg_type != Arg::NONE)\n        val = use_values ? values_[index] : args_[index];\n      arg.type = arg_type;\n      return arg;\n    }\n    if (use_values) {\n      // The index is greater than the number of arguments that can be stored\n      // in values, so return a \"none\" argument.\n      arg.type = Arg::NONE;\n      return arg;\n    }\n    for (unsigned i = MAX_PACKED_ARGS; i <= index; ++i) {\n      if (args_[i].type == Arg::NONE)\n        return args_[i];\n    }\n    return args_[index];\n  }\n\n  static internal::Arg::Type type(uint64_t types, unsigned index) {\n    unsigned shift = index * 4;\n    uint64_t mask = 0xf;\n    return static_cast<internal::Arg::Type>(\n          (types & (mask << shift)) >> shift);\n  }\n};\n\n#define FMT_DISPATCH(call) static_cast<Impl*>(this)->call\n\n/**\n  \\rst\n  An argument visitor based on the `curiously recurring template pattern\n  <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_.\n\n  To use `~fmt::ArgVisitor` define a subclass that implements some or all of the\n  visit methods with the same signatures as the methods in `~fmt::ArgVisitor`,\n  for example, `~fmt::ArgVisitor::visit_int()`.\n  Pass the subclass as the *Impl* template parameter. Then calling\n  `~fmt::ArgVisitor::visit` for some argument will dispatch to a visit method\n  specific to the argument type. For example, if the argument type is\n  ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass\n  will be called. If the subclass doesn't contain a method with this signature,\n  then a corresponding method of `~fmt::ArgVisitor` will be called.\n\n  **Example**::\n\n    class MyArgVisitor : public fmt::ArgVisitor<MyArgVisitor, void> {\n     public:\n      void visit_int(int value) { fmt::print(\"{}\", value); }\n      void visit_double(double value) { fmt::print(\"{}\", value ); }\n    };\n  \\endrst\n */\ntemplate <typename Impl, typename Result>\nclass ArgVisitor {\n private:\n  typedef internal::Arg Arg;\n\n public:\n  void report_unhandled_arg() {}\n\n  Result visit_unhandled_arg() {\n    FMT_DISPATCH(report_unhandled_arg());\n    return Result();\n  }\n\n  /** Visits an ``int`` argument. **/\n  Result visit_int(int value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits a ``long long`` argument. **/\n  Result visit_long_long(LongLong value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits an ``unsigned`` argument. **/\n  Result visit_uint(unsigned value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits an ``unsigned long long`` argument. **/\n  Result visit_ulong_long(ULongLong value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits a ``bool`` argument. **/\n  Result visit_bool(bool value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits a ``char`` or ``wchar_t`` argument. **/\n  Result visit_char(int value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits an argument of any integral type. **/\n  template <typename T>\n  Result visit_any_int(T) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a ``double`` argument. **/\n  Result visit_double(double value) {\n    return FMT_DISPATCH(visit_any_double(value));\n  }\n\n  /** Visits a ``long double`` argument. **/\n  Result visit_long_double(long double value) {\n    return FMT_DISPATCH(visit_any_double(value));\n  }\n\n  /** Visits a ``double`` or ``long double`` argument. **/\n  template <typename T>\n  Result visit_any_double(T) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a null-terminated C string (``const char *``) argument. **/\n  Result visit_cstring(const char *) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a string argument. **/\n  Result visit_string(Arg::StringValue<char>) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a wide string argument. **/\n  Result visit_wstring(Arg::StringValue<wchar_t>) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a pointer argument. **/\n  Result visit_pointer(const void *) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits an argument of a custom (user-defined) type. **/\n  Result visit_custom(Arg::CustomValue) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /**\n    \\rst\n    Visits an argument dispatching to the appropriate visit method based on\n    the argument type. For example, if the argument type is ``double`` then\n    the `~fmt::ArgVisitor::visit_double()` method of the *Impl* class will be\n    called.\n    \\endrst\n   */\n  Result visit(const Arg &arg) {\n    switch (arg.type) {\n    case Arg::NONE:\n    case Arg::NAMED_ARG:\n      FMT_ASSERT(false, \"invalid argument type\");\n      break;\n    case Arg::INT:\n      return FMT_DISPATCH(visit_int(arg.int_value));\n    case Arg::UINT:\n      return FMT_DISPATCH(visit_uint(arg.uint_value));\n    case Arg::LONG_LONG:\n      return FMT_DISPATCH(visit_long_long(arg.long_long_value));\n    case Arg::ULONG_LONG:\n      return FMT_DISPATCH(visit_ulong_long(arg.ulong_long_value));\n    case Arg::BOOL:\n      return FMT_DISPATCH(visit_bool(arg.int_value != 0));\n    case Arg::CHAR:\n      return FMT_DISPATCH(visit_char(arg.int_value));\n    case Arg::DOUBLE:\n      return FMT_DISPATCH(visit_double(arg.double_value));\n    case Arg::LONG_DOUBLE:\n      return FMT_DISPATCH(visit_long_double(arg.long_double_value));\n    case Arg::CSTRING:\n      return FMT_DISPATCH(visit_cstring(arg.string.value));\n    case Arg::STRING:\n      return FMT_DISPATCH(visit_string(arg.string));\n    case Arg::WSTRING:\n      return FMT_DISPATCH(visit_wstring(arg.wstring));\n    case Arg::POINTER:\n      return FMT_DISPATCH(visit_pointer(arg.pointer));\n    case Arg::CUSTOM:\n      return FMT_DISPATCH(visit_custom(arg.custom));\n    }\n    return Result();\n  }\n};\n\nenum Alignment {\n  ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC\n};\n\n// Flags.\nenum {\n  SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8,\n  CHAR_FLAG = 0x10  // Argument has char type - used in error reporting.\n};\n\n// An empty format specifier.\nstruct EmptySpec {};\n\n// A type specifier.\ntemplate <char TYPE>\nstruct TypeSpec : EmptySpec {\n  Alignment align() const { return ALIGN_DEFAULT; }\n  unsigned width() const { return 0; }\n  int precision() const { return -1; }\n  bool flag(unsigned) const { return false; }\n  char type() const { return TYPE; }\n  char type_prefix() const { return TYPE; }\n  char fill() const { return ' '; }\n};\n\n// A width specifier.\nstruct WidthSpec {\n  unsigned width_;\n  // Fill is always wchar_t and cast to char if necessary to avoid having\n  // two specialization of WidthSpec and its subclasses.\n  wchar_t fill_;\n\n  WidthSpec(unsigned width, wchar_t fill) : width_(width), fill_(fill) {}\n\n  unsigned width() const { return width_; }\n  wchar_t fill() const { return fill_; }\n};\n\n// An alignment specifier.\nstruct AlignSpec : WidthSpec {\n  Alignment align_;\n\n  AlignSpec(unsigned width, wchar_t fill, Alignment align = ALIGN_DEFAULT)\n  : WidthSpec(width, fill), align_(align) {}\n\n  Alignment align() const { return align_; }\n\n  int precision() const { return -1; }\n};\n\n// An alignment and type specifier.\ntemplate <char TYPE>\nstruct AlignTypeSpec : AlignSpec {\n  AlignTypeSpec(unsigned width, wchar_t fill) : AlignSpec(width, fill) {}\n\n  bool flag(unsigned) const { return false; }\n  char type() const { return TYPE; }\n  char type_prefix() const { return TYPE; }\n};\n\n// A full format specifier.\nstruct FormatSpec : AlignSpec {\n  unsigned flags_;\n  int precision_;\n  char type_;\n\n  FormatSpec(\n    unsigned width = 0, char type = 0, wchar_t fill = ' ')\n  : AlignSpec(width, fill), flags_(0), precision_(-1), type_(type) {}\n\n  bool flag(unsigned f) const { return (flags_ & f) != 0; }\n  int precision() const { return precision_; }\n  char type() const { return type_; }\n  char type_prefix() const { return type_; }\n};\n\n// An integer format specifier.\ntemplate <typename T, typename SpecT = TypeSpec<0>, typename Char = char>\nclass IntFormatSpec : public SpecT {\n private:\n  T value_;\n\n public:\n  IntFormatSpec(T val, const SpecT &spec = SpecT())\n  : SpecT(spec), value_(val) {}\n\n  T value() const { return value_; }\n};\n\n// A string format specifier.\ntemplate <typename Char>\nclass StrFormatSpec : public AlignSpec {\n private:\n  const Char *str_;\n\n public:\n  template <typename FillChar>\n  StrFormatSpec(const Char *str, unsigned width, FillChar fill)\n  : AlignSpec(width, fill), str_(str) {\n    internal::CharTraits<Char>::convert(FillChar());\n  }\n\n  const Char *str() const { return str_; }\n};\n\n/**\n  Returns an integer format specifier to format the value in base 2.\n */\nIntFormatSpec<int, TypeSpec<'b'> > bin(int value);\n\n/**\n  Returns an integer format specifier to format the value in base 8.\n */\nIntFormatSpec<int, TypeSpec<'o'> > oct(int value);\n\n/**\n  Returns an integer format specifier to format the value in base 16 using\n  lower-case letters for the digits above 9.\n */\nIntFormatSpec<int, TypeSpec<'x'> > hex(int value);\n\n/**\n  Returns an integer formatter format specifier to format in base 16 using\n  upper-case letters for the digits above 9.\n */\nIntFormatSpec<int, TypeSpec<'X'> > hexu(int value);\n\n/**\n  \\rst\n  Returns an integer format specifier to pad the formatted argument with the\n  fill character to the specified width using the default (right) numeric\n  alignment.\n\n  **Example**::\n\n    MemoryWriter out;\n    out << pad(hex(0xcafe), 8, '0');\n    // out.str() == \"0000cafe\"\n\n  \\endrst\n */\ntemplate <char TYPE_CODE, typename Char>\nIntFormatSpec<int, AlignTypeSpec<TYPE_CODE>, Char> pad(\n    int value, unsigned width, Char fill = ' ');\n\n#define FMT_DEFINE_INT_FORMATTERS(TYPE) \\\ninline IntFormatSpec<TYPE, TypeSpec<'b'> > bin(TYPE value) { \\\n  return IntFormatSpec<TYPE, TypeSpec<'b'> >(value, TypeSpec<'b'>()); \\\n} \\\n \\\ninline IntFormatSpec<TYPE, TypeSpec<'o'> > oct(TYPE value) { \\\n  return IntFormatSpec<TYPE, TypeSpec<'o'> >(value, TypeSpec<'o'>()); \\\n} \\\n \\\ninline IntFormatSpec<TYPE, TypeSpec<'x'> > hex(TYPE value) { \\\n  return IntFormatSpec<TYPE, TypeSpec<'x'> >(value, TypeSpec<'x'>()); \\\n} \\\n \\\ninline IntFormatSpec<TYPE, TypeSpec<'X'> > hexu(TYPE value) { \\\n  return IntFormatSpec<TYPE, TypeSpec<'X'> >(value, TypeSpec<'X'>()); \\\n} \\\n \\\ntemplate <char TYPE_CODE> \\\ninline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> > pad( \\\n    IntFormatSpec<TYPE, TypeSpec<TYPE_CODE> > f, unsigned width) { \\\n  return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> >( \\\n      f.value(), AlignTypeSpec<TYPE_CODE>(width, ' ')); \\\n} \\\n \\\n/* For compatibility with older compilers we provide two overloads for pad, */ \\\n/* one that takes a fill character and one that doesn't. In the future this */ \\\n/* can be replaced with one overload making the template argument Char      */ \\\n/* default to char (C++11). */ \\\ntemplate <char TYPE_CODE, typename Char> \\\ninline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char> pad( \\\n    IntFormatSpec<TYPE, TypeSpec<TYPE_CODE>, Char> f, \\\n    unsigned width, Char fill) { \\\n  return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char>( \\\n      f.value(), AlignTypeSpec<TYPE_CODE>(width, fill)); \\\n} \\\n \\\ninline IntFormatSpec<TYPE, AlignTypeSpec<0> > pad( \\\n    TYPE value, unsigned width) { \\\n  return IntFormatSpec<TYPE, AlignTypeSpec<0> >( \\\n      value, AlignTypeSpec<0>(width, ' ')); \\\n} \\\n \\\ntemplate <typename Char> \\\ninline IntFormatSpec<TYPE, AlignTypeSpec<0>, Char> pad( \\\n   TYPE value, unsigned width, Char fill) { \\\n return IntFormatSpec<TYPE, AlignTypeSpec<0>, Char>( \\\n     value, AlignTypeSpec<0>(width, fill)); \\\n}\n\nFMT_DEFINE_INT_FORMATTERS(int)\nFMT_DEFINE_INT_FORMATTERS(long)\nFMT_DEFINE_INT_FORMATTERS(unsigned)\nFMT_DEFINE_INT_FORMATTERS(unsigned long)\nFMT_DEFINE_INT_FORMATTERS(LongLong)\nFMT_DEFINE_INT_FORMATTERS(ULongLong)\n\n/**\n  \\rst\n  Returns a string formatter that pads the formatted argument with the fill\n  character to the specified width using the default (left) string alignment.\n\n  **Example**::\n\n    std::string s = str(MemoryWriter() << pad(\"abc\", 8));\n    // s == \"abc     \"\n\n  \\endrst\n */\ntemplate <typename Char>\ninline StrFormatSpec<Char> pad(\n    const Char *str, unsigned width, Char fill = ' ') {\n  return StrFormatSpec<Char>(str, width, fill);\n}\n\ninline StrFormatSpec<wchar_t> pad(\n    const wchar_t *str, unsigned width, char fill = ' ') {\n  return StrFormatSpec<wchar_t>(str, width, fill);\n}\n\nnamespace internal {\n\ntemplate <typename Char>\nclass ArgMap {\n private:\n  typedef std::vector<\n    std::pair<fmt::BasicStringRef<Char>, internal::Arg> > MapType;\n  typedef typename MapType::value_type Pair;\n\n  MapType map_;\n\n public:\n  void init(const ArgList &args);\n\n  const internal::Arg *find(const fmt::BasicStringRef<Char> &name) const {\n    // The list is unsorted, so just return the first matching name.\n    for (typename MapType::const_iterator it = map_.begin(), end = map_.end();\n         it != end; ++it) {\n      if (it->first == name)\n        return &it->second;\n    }\n    return FMT_NULL;\n  }\n};\n\ntemplate <typename Char>\nvoid ArgMap<Char>::init(const ArgList &args) {\n  if (!map_.empty())\n    return;\n  typedef internal::NamedArg<Char> NamedArg;\n  const NamedArg *named_arg = FMT_NULL;\n  bool use_values =\n      args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE;\n  if (use_values) {\n    for (unsigned i = 0;/*nothing*/; ++i) {\n      internal::Arg::Type arg_type = args.type(i);\n      switch (arg_type) {\n      case internal::Arg::NONE:\n        return;\n      case internal::Arg::NAMED_ARG:\n        named_arg = static_cast<const NamedArg*>(args.values_[i].pointer);\n        map_.push_back(Pair(named_arg->name, *named_arg));\n        break;\n      default:\n        /*nothing*/;\n      }\n    }\n    return;\n  }\n  for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) {\n    internal::Arg::Type arg_type = args.type(i);\n    if (arg_type == internal::Arg::NAMED_ARG) {\n      named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);\n      map_.push_back(Pair(named_arg->name, *named_arg));\n    }\n  }\n  for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) {\n    switch (args.args_[i].type) {\n    case internal::Arg::NONE:\n      return;\n    case internal::Arg::NAMED_ARG:\n      named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);\n      map_.push_back(Pair(named_arg->name, *named_arg));\n      break;\n    default:\n      /*nothing*/;\n    }\n  }\n}\n\ntemplate <typename Impl, typename Char, typename Spec = fmt::FormatSpec>\nclass ArgFormatterBase : public ArgVisitor<Impl, void> {\n private:\n  BasicWriter<Char> &writer_;\n  Spec &spec_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(ArgFormatterBase);\n\n  void write_pointer(const void *p) {\n    spec_.flags_ = HASH_FLAG;\n    spec_.type_ = 'x';\n    writer_.write_int(reinterpret_cast<uintptr_t>(p), spec_);\n  }\n\n  // workaround MSVC two-phase lookup issue\n  typedef internal::Arg Arg;\n\n protected:\n  BasicWriter<Char> &writer() { return writer_; }\n  Spec &spec() { return spec_; }\n\n  void write(bool value) {\n    const char *str_value = value ? \"true\" : \"false\";\n    Arg::StringValue<char> str = { str_value, std::strlen(str_value) };\n    writer_.write_str(str, spec_);\n  }\n\n  void write(const char *value) {\n    Arg::StringValue<char> str = {value, value ? std::strlen(value) : 0};\n    writer_.write_str(str, spec_);\n  }\n\n public:\n  typedef Spec SpecType;\n\n  ArgFormatterBase(BasicWriter<Char> &w, Spec &s)\n  : writer_(w), spec_(s) {}\n\n  template <typename T>\n  void visit_any_int(T value) { writer_.write_int(value, spec_); }\n\n  template <typename T>\n  void visit_any_double(T value) { writer_.write_double(value, spec_); }\n\n  void visit_bool(bool value) {\n    if (spec_.type_) {\n      visit_any_int(value);\n      return;\n    }\n    write(value);\n  }\n\n  void visit_char(int value) {\n    if (spec_.type_ && spec_.type_ != 'c') {\n      spec_.flags_ |= CHAR_FLAG;\n      writer_.write_int(value, spec_);\n      return;\n    }\n    if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0)\n      FMT_THROW(FormatError(\"invalid format specifier for char\"));\n    typedef typename BasicWriter<Char>::CharPtr CharPtr;\n    Char fill = internal::CharTraits<Char>::cast(spec_.fill());\n    CharPtr out = CharPtr();\n    const unsigned CHAR_SIZE = 1;\n    if (spec_.width_ > CHAR_SIZE) {\n      out = writer_.grow_buffer(spec_.width_);\n      if (spec_.align_ == ALIGN_RIGHT) {\n        std::uninitialized_fill_n(out, spec_.width_ - CHAR_SIZE, fill);\n        out += spec_.width_ - CHAR_SIZE;\n      } else if (spec_.align_ == ALIGN_CENTER) {\n        out = writer_.fill_padding(out, spec_.width_,\n                                   internal::const_check(CHAR_SIZE), fill);\n      } else {\n        std::uninitialized_fill_n(out + CHAR_SIZE,\n                                  spec_.width_ - CHAR_SIZE, fill);\n      }\n    } else {\n      out = writer_.grow_buffer(CHAR_SIZE);\n    }\n    *out = internal::CharTraits<Char>::cast(value);\n  }\n\n  void visit_cstring(const char *value) {\n    if (spec_.type_ == 'p')\n      return write_pointer(value);\n    write(value);\n  }\n\n  // Qualification with \"internal\" here and below is a workaround for nvcc.\n  void visit_string(internal::Arg::StringValue<char> value) {\n    writer_.write_str(value, spec_);\n  }\n\n  using ArgVisitor<Impl, void>::visit_wstring;\n\n  void visit_wstring(internal::Arg::StringValue<Char> value) {\n    writer_.write_str(value, spec_);\n  }\n\n  void visit_pointer(const void *value) {\n    if (spec_.type_ && spec_.type_ != 'p')\n      report_unknown_type(spec_.type_, \"pointer\");\n    write_pointer(value);\n  }\n};\n\nclass FormatterBase {\n private:\n  ArgList args_;\n  int next_arg_index_;\n\n  // Returns the argument with specified index.\n  FMT_API Arg do_get_arg(unsigned arg_index, const char *&error);\n\n protected:\n  const ArgList &args() const { return args_; }\n\n  explicit FormatterBase(const ArgList &args) {\n    args_ = args;\n    next_arg_index_ = 0;\n  }\n\n  // Returns the next argument.\n  Arg next_arg(const char *&error) {\n    if (next_arg_index_ >= 0)\n      return do_get_arg(internal::to_unsigned(next_arg_index_++), error);\n    error = \"cannot switch from manual to automatic argument indexing\";\n    return Arg();\n  }\n\n  // Checks if manual indexing is used and returns the argument with\n  // specified index.\n  Arg get_arg(unsigned arg_index, const char *&error) {\n    return check_no_auto_index(error) ? do_get_arg(arg_index, error) : Arg();\n  }\n\n  bool check_no_auto_index(const char *&error) {\n    if (next_arg_index_ > 0) {\n      error = \"cannot switch from automatic to manual argument indexing\";\n      return false;\n    }\n    next_arg_index_ = -1;\n    return true;\n  }\n\n  template <typename Char>\n  void write(BasicWriter<Char> &w, const Char *start, const Char *end) {\n    if (start != end)\n      w << BasicStringRef<Char>(start, internal::to_unsigned(end - start));\n  }\n};\n}  // namespace internal\n\n/**\n  \\rst\n  An argument formatter based on the `curiously recurring template pattern\n  <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_.\n\n  To use `~fmt::BasicArgFormatter` define a subclass that implements some or\n  all of the visit methods with the same signatures as the methods in\n  `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`.\n  Pass the subclass as the *Impl* template parameter. When a formatting\n  function processes an argument, it will dispatch to a visit method\n  specific to the argument type. For example, if the argument type is\n  ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass\n  will be called. If the subclass doesn't contain a method with this signature,\n  then a corresponding method of `~fmt::BasicArgFormatter` or its superclass\n  will be called.\n  \\endrst\n */\ntemplate <typename Impl, typename Char, typename Spec = fmt::FormatSpec>\nclass BasicArgFormatter : public internal::ArgFormatterBase<Impl, Char, Spec> {\n private:\n  BasicFormatter<Char, Impl> &formatter_;\n  const Char *format_;\n\n public:\n  /**\n    \\rst\n    Constructs an argument formatter object.\n    *formatter* is a reference to the main formatter object, *spec* contains\n    format specifier information for standard argument types, and *fmt* points\n    to the part of the format string being parsed for custom argument types.\n    \\endrst\n   */\n  BasicArgFormatter(BasicFormatter<Char, Impl> &formatter,\n                    Spec &spec, const Char *fmt)\n  : internal::ArgFormatterBase<Impl, Char, Spec>(formatter.writer(), spec),\n    formatter_(formatter), format_(fmt) {}\n\n  /** Formats an argument of a custom (user-defined) type. */\n  void visit_custom(internal::Arg::CustomValue c) {\n    c.format(&formatter_, c.value, &format_);\n  }\n};\n\n/** The default argument formatter. */\ntemplate <typename Char>\nclass ArgFormatter :\n    public BasicArgFormatter<ArgFormatter<Char>, Char, FormatSpec> {\n public:\n  /** Constructs an argument formatter object. */\n  ArgFormatter(BasicFormatter<Char> &formatter,\n               FormatSpec &spec, const Char *fmt)\n  : BasicArgFormatter<ArgFormatter<Char>,\n                      Char, FormatSpec>(formatter, spec, fmt) {}\n};\n\n/** This template formats data and writes the output to a writer. */\ntemplate <typename CharType, typename ArgFormatter>\nclass BasicFormatter : private internal::FormatterBase {\n public:\n  /** The character type for the output. */\n  typedef CharType Char;\n\n private:\n  BasicWriter<Char> &writer_;\n  internal::ArgMap<Char> map_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter);\n\n  using internal::FormatterBase::get_arg;\n\n  // Checks if manual indexing is used and returns the argument with\n  // specified name.\n  internal::Arg get_arg(BasicStringRef<Char> arg_name, const char *&error);\n\n  // Parses argument index and returns corresponding argument.\n  internal::Arg parse_arg_index(const Char *&s);\n\n  // Parses argument name and returns corresponding argument.\n  internal::Arg parse_arg_name(const Char *&s);\n\n public:\n  /**\n   \\rst\n   Constructs a ``BasicFormatter`` object. References to the arguments and\n   the writer are stored in the formatter object so make sure they have\n   appropriate lifetimes.\n   \\endrst\n   */\n  BasicFormatter(const ArgList &args, BasicWriter<Char> &w)\n    : internal::FormatterBase(args), writer_(w) {}\n\n  /** Returns a reference to the writer associated with this formatter. */\n  BasicWriter<Char> &writer() { return writer_; }\n\n  /** Formats stored arguments and writes the output to the writer. */\n  void format(BasicCStringRef<Char> format_str);\n\n  // Formats a single argument and advances format_str, a format string pointer.\n  const Char *format(const Char *&format_str, const internal::Arg &arg);\n};\n\n// Generates a comma-separated list with results of applying f to\n// numbers 0..n-1.\n# define FMT_GEN(n, f) FMT_GEN##n(f)\n# define FMT_GEN1(f)  f(0)\n# define FMT_GEN2(f)  FMT_GEN1(f),  f(1)\n# define FMT_GEN3(f)  FMT_GEN2(f),  f(2)\n# define FMT_GEN4(f)  FMT_GEN3(f),  f(3)\n# define FMT_GEN5(f)  FMT_GEN4(f),  f(4)\n# define FMT_GEN6(f)  FMT_GEN5(f),  f(5)\n# define FMT_GEN7(f)  FMT_GEN6(f),  f(6)\n# define FMT_GEN8(f)  FMT_GEN7(f),  f(7)\n# define FMT_GEN9(f)  FMT_GEN8(f),  f(8)\n# define FMT_GEN10(f) FMT_GEN9(f),  f(9)\n# define FMT_GEN11(f) FMT_GEN10(f), f(10)\n# define FMT_GEN12(f) FMT_GEN11(f), f(11)\n# define FMT_GEN13(f) FMT_GEN12(f), f(12)\n# define FMT_GEN14(f) FMT_GEN13(f), f(13)\n# define FMT_GEN15(f) FMT_GEN14(f), f(14)\n\nnamespace internal {\ninline uint64_t make_type() { return 0; }\n\ntemplate <typename T>\ninline uint64_t make_type(const T &arg) {\n  return MakeValue< BasicFormatter<char> >::type(arg);\n}\n\ntemplate <std::size_t N, bool/*IsPacked*/= (N < ArgList::MAX_PACKED_ARGS)>\nstruct ArgArray;\n\ntemplate <std::size_t N>\nstruct ArgArray<N, true/*IsPacked*/> {\n  // '+' is used to silence GCC -Wduplicated-branches warning.\n  typedef Value Type[N > 0 ? N : +1];\n\n  template <typename Formatter, typename T>\n  static Value make(const T &value) {\n#ifdef __clang__\n    Value result = MakeValue<Formatter>(value);\n    // Workaround a bug in Apple LLVM version 4.2 (clang-425.0.28) of clang:\n    // https://github.com/fmtlib/fmt/issues/276\n    (void)result.custom.format;\n    return result;\n#else\n    return MakeValue<Formatter>(value);\n#endif\n  }\n};\n\ntemplate <std::size_t N>\nstruct ArgArray<N, false/*IsPacked*/> {\n  typedef Arg Type[N + 1]; // +1 for the list end Arg::NONE\n\n  template <typename Formatter, typename T>\n  static Arg make(const T &value) { return MakeArg<Formatter>(value); }\n};\n\n#if FMT_USE_VARIADIC_TEMPLATES\ntemplate <typename Arg, typename... Args>\ninline uint64_t make_type(const Arg &first, const Args & ... tail) {\n  return make_type(first) | (make_type(tail...) << 4);\n}\n\n#else\n\nstruct ArgType {\n  uint64_t type;\n\n  ArgType() : type(0) {}\n\n  template <typename T>\n  ArgType(const T &arg) : type(make_type(arg)) {}\n};\n\n# define FMT_ARG_TYPE_DEFAULT(n) ArgType t##n = ArgType()\n\ninline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) {\n  return t0.type | (t1.type << 4) | (t2.type << 8) | (t3.type << 12) |\n      (t4.type << 16) | (t5.type << 20) | (t6.type << 24) | (t7.type << 28) |\n      (t8.type << 32) | (t9.type << 36) | (t10.type << 40) | (t11.type << 44) |\n      (t12.type << 48) | (t13.type << 52) | (t14.type << 56);\n}\n#endif\n}  // namespace internal\n\n# define FMT_MAKE_TEMPLATE_ARG(n) typename T##n\n# define FMT_MAKE_ARG_TYPE(n) T##n\n# define FMT_MAKE_ARG(n) const T##n &v##n\n# define FMT_ASSIGN_char(n) \\\n  arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter<char> >(v##n)\n# define FMT_ASSIGN_wchar_t(n) \\\n  arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter<wchar_t> >(v##n)\n\n#if FMT_USE_VARIADIC_TEMPLATES\n// Defines a variadic function returning void.\n# define FMT_VARIADIC_VOID(func, arg_type) \\\n  template <typename... Args> \\\n  void func(arg_type arg0, const Args & ... args) { \\\n    typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \\\n    typename ArgArray::Type array{ \\\n      ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \\\n    func(arg0, fmt::ArgList(fmt::internal::make_type(args...), array)); \\\n  }\n\n// Defines a variadic constructor.\n# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \\\n  template <typename... Args> \\\n  ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \\\n    typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \\\n    typename ArgArray::Type array{ \\\n      ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \\\n    func(arg0, arg1, fmt::ArgList(fmt::internal::make_type(args...), array)); \\\n  }\n\n#else\n\n# define FMT_MAKE_REF(n) \\\n  fmt::internal::MakeValue< fmt::BasicFormatter<Char> >(v##n)\n# define FMT_MAKE_REF2(n) v##n\n\n// Defines a wrapper for a function taking one argument of type arg_type\n// and n additional arguments of arbitrary types.\n# define FMT_WRAP1(func, arg_type, n) \\\n  template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \\\n  inline void func(arg_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \\\n    const fmt::internal::ArgArray<n>::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \\\n    func(arg1, fmt::ArgList( \\\n      fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \\\n  }\n\n// Emulates a variadic function returning void on a pre-C++11 compiler.\n# define FMT_VARIADIC_VOID(func, arg_type) \\\n  inline void func(arg_type arg) { func(arg, fmt::ArgList()); } \\\n  FMT_WRAP1(func, arg_type, 1) FMT_WRAP1(func, arg_type, 2) \\\n  FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) \\\n  FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \\\n  FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \\\n  FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10)\n\n# define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \\\n  template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \\\n  ctor(arg0_type arg0, arg1_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \\\n    const fmt::internal::ArgArray<n>::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \\\n    func(arg0, arg1, fmt::ArgList( \\\n      fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \\\n  }\n\n// Emulates a variadic constructor on a pre-C++11 compiler.\n# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 1) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 2) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 3) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 4) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 5) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 6) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 7) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 8) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 9) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 10)\n#endif\n\n// Generates a comma-separated list with results of applying f to pairs\n// (argument, index).\n#define FMT_FOR_EACH1(f, x0) f(x0, 0)\n#define FMT_FOR_EACH2(f, x0, x1) \\\n  FMT_FOR_EACH1(f, x0), f(x1, 1)\n#define FMT_FOR_EACH3(f, x0, x1, x2) \\\n  FMT_FOR_EACH2(f, x0 ,x1), f(x2, 2)\n#define FMT_FOR_EACH4(f, x0, x1, x2, x3) \\\n  FMT_FOR_EACH3(f, x0, x1, x2), f(x3, 3)\n#define FMT_FOR_EACH5(f, x0, x1, x2, x3, x4) \\\n  FMT_FOR_EACH4(f, x0, x1, x2, x3), f(x4, 4)\n#define FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5) \\\n  FMT_FOR_EACH5(f, x0, x1, x2, x3, x4), f(x5, 5)\n#define FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6) \\\n  FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5), f(x6, 6)\n#define FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7) \\\n  FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6), f(x7, 7)\n#define FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8) \\\n  FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7), f(x8, 8)\n#define FMT_FOR_EACH10(f, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) \\\n  FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8), f(x9, 9)\n\n/**\n An error returned by an operating system or a language runtime,\n for example a file opening error.\n*/\nclass SystemError : public internal::RuntimeError {\n private:\n  FMT_API void init(int err_code, CStringRef format_str, ArgList args);\n\n protected:\n  int error_code_;\n\n  typedef char Char;  // For FMT_VARIADIC_CTOR.\n\n  SystemError() {}\n\n public:\n  /**\n   \\rst\n   Constructs a :class:`fmt::SystemError` object with a description\n   formatted with `fmt::format_system_error`. *message* and additional\n   arguments passed into the constructor are formatted similarly to\n   `fmt::format`.\n\n   **Example**::\n\n     // This throws a SystemError with the description\n     //   cannot open file 'madeup': No such file or directory\n     // or similar (system message may vary).\n     const char *filename = \"madeup\";\n     std::FILE *file = std::fopen(filename, \"r\");\n     if (!file)\n       throw fmt::SystemError(errno, \"cannot open file '{}'\", filename);\n   \\endrst\n  */\n  SystemError(int error_code, CStringRef message) {\n    init(error_code, message, ArgList());\n  }\n  FMT_DEFAULTED_COPY_CTOR(SystemError)\n  FMT_VARIADIC_CTOR(SystemError, init, int, CStringRef)\n\n  FMT_API ~SystemError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE;\n\n  int error_code() const { return error_code_; }\n};\n\n/**\n  \\rst\n  Formats an error returned by an operating system or a language runtime,\n  for example a file opening error, and writes it to *out* in the following\n  form:\n\n  .. parsed-literal::\n     *<message>*: *<system-message>*\n\n  where *<message>* is the passed message and *<system-message>* is\n  the system message corresponding to the error code.\n  *error_code* is a system error code as given by ``errno``.\n  If *error_code* is not a valid error code such as -1, the system message\n  may look like \"Unknown error -1\" and is platform-dependent.\n  \\endrst\n */\nFMT_API void format_system_error(fmt::Writer &out, int error_code,\n                                 fmt::StringRef message) FMT_NOEXCEPT;\n\n/**\n  \\rst\n  This template provides operations for formatting and writing data into\n  a character stream. The output is stored in a buffer provided by a subclass\n  such as :class:`fmt::BasicMemoryWriter`.\n\n  You can use one of the following typedefs for common character types:\n\n  +---------+----------------------+\n  | Type    | Definition           |\n  +=========+======================+\n  | Writer  | BasicWriter<char>    |\n  +---------+----------------------+\n  | WWriter | BasicWriter<wchar_t> |\n  +---------+----------------------+\n\n  \\endrst\n */\ntemplate <typename Char>\nclass BasicWriter {\n private:\n  // Output buffer.\n  Buffer<Char> &buffer_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter);\n\n  typedef typename internal::CharTraits<Char>::CharPtr CharPtr;\n\n#if FMT_SECURE_SCL\n  // Returns pointer value.\n  static Char *get(CharPtr p) { return p.base(); }\n#else\n  static Char *get(Char *p) { return p; }\n#endif\n\n  // Fills the padding around the content and returns the pointer to the\n  // content area.\n  static CharPtr fill_padding(CharPtr buffer,\n      unsigned total_size, std::size_t content_size, wchar_t fill);\n\n  // Grows the buffer by n characters and returns a pointer to the newly\n  // allocated area.\n  CharPtr grow_buffer(std::size_t n) {\n    std::size_t size = buffer_.size();\n    buffer_.resize(size + n);\n    return internal::make_ptr(&buffer_[size], n);\n  }\n\n  // Writes an unsigned decimal integer.\n  template <typename UInt>\n  Char *write_unsigned_decimal(UInt value, unsigned prefix_size = 0) {\n    unsigned num_digits = internal::count_digits(value);\n    Char *ptr = get(grow_buffer(prefix_size + num_digits));\n    internal::format_decimal(ptr + prefix_size, value, num_digits);\n    return ptr;\n  }\n\n  // Writes a decimal integer.\n  template <typename Int>\n  void write_decimal(Int value) {\n    typedef typename internal::IntTraits<Int>::MainType MainType;\n    MainType abs_value = static_cast<MainType>(value);\n    if (internal::is_negative(value)) {\n      abs_value = 0 - abs_value;\n      *write_unsigned_decimal(abs_value, 1) = '-';\n    } else {\n      write_unsigned_decimal(abs_value, 0);\n    }\n  }\n\n  // Prepare a buffer for integer formatting.\n  CharPtr prepare_int_buffer(unsigned num_digits,\n      const EmptySpec &, const char *prefix, unsigned prefix_size) {\n    unsigned size = prefix_size + num_digits;\n    CharPtr p = grow_buffer(size);\n    std::uninitialized_copy(prefix, prefix + prefix_size, p);\n    return p + size - 1;\n  }\n\n  template <typename Spec>\n  CharPtr prepare_int_buffer(unsigned num_digits,\n    const Spec &spec, const char *prefix, unsigned prefix_size);\n\n  // Formats an integer.\n  template <typename T, typename Spec>\n  void write_int(T value, Spec spec);\n\n  // Formats a floating-point number (double or long double).\n  template <typename T, typename Spec>\n  void write_double(T value, const Spec &spec);\n\n  // Writes a formatted string.\n  template <typename StrChar>\n  CharPtr write_str(const StrChar *s, std::size_t size, const AlignSpec &spec);\n\n  template <typename StrChar, typename Spec>\n  void write_str(const internal::Arg::StringValue<StrChar> &str,\n                 const Spec &spec);\n\n  // This following methods are private to disallow writing wide characters\n  // and strings to a char stream. If you want to print a wide string as a\n  // pointer as std::ostream does, cast it to const void*.\n  // Do not implement!\n  void operator<<(typename internal::WCharHelper<wchar_t, Char>::Unsupported);\n  void operator<<(\n      typename internal::WCharHelper<const wchar_t *, Char>::Unsupported);\n\n  // Appends floating-point length specifier to the format string.\n  // The second argument is only used for overload resolution.\n  void append_float_length(Char *&format_ptr, long double) {\n    *format_ptr++ = 'L';\n  }\n\n  template<typename T>\n  void append_float_length(Char *&, T) {}\n\n  template <typename Impl, typename Char_, typename Spec_>\n  friend class internal::ArgFormatterBase;\n\n  template <typename Impl, typename Char_, typename Spec_>\n  friend class BasicPrintfArgFormatter;\n\n protected:\n  /**\n    Constructs a ``BasicWriter`` object.\n   */\n  explicit BasicWriter(Buffer<Char> &b) : buffer_(b) {}\n\n public:\n  /**\n    \\rst\n    Destroys a ``BasicWriter`` object.\n    \\endrst\n   */\n  virtual ~BasicWriter() {}\n\n  /**\n    Returns the total number of characters written.\n   */\n  std::size_t size() const { return buffer_.size(); }\n\n  /**\n    Returns a pointer to the output buffer content. No terminating null\n    character is appended.\n   */\n  const Char *data() const FMT_NOEXCEPT { return &buffer_[0]; }\n\n  /**\n    Returns a pointer to the output buffer content with terminating null\n    character appended.\n   */\n  const Char *c_str() const {\n    std::size_t size = buffer_.size();\n    buffer_.reserve(size + 1);\n    buffer_[size] = '\\0';\n    return &buffer_[0];\n  }\n\n  /**\n    \\rst\n    Returns the content of the output buffer as an `std::string`.\n    \\endrst\n   */\n  std::basic_string<Char> str() const {\n    return std::basic_string<Char>(&buffer_[0], buffer_.size());\n  }\n\n  /**\n    \\rst\n    Writes formatted data.\n\n    *args* is an argument list representing arbitrary arguments.\n\n    **Example**::\n\n       MemoryWriter out;\n       out.write(\"Current point:\\n\");\n       out.write(\"({:+f}, {:+f})\", -3.14, 3.14);\n\n    This will write the following output to the ``out`` object:\n\n    .. code-block:: none\n\n       Current point:\n       (-3.140000, +3.140000)\n\n    The output can be accessed using :func:`data()`, :func:`c_str` or\n    :func:`str` methods.\n\n    See also :ref:`syntax`.\n    \\endrst\n   */\n  void write(BasicCStringRef<Char> format, ArgList args) {\n    BasicFormatter<Char>(args, *this).format(format);\n  }\n  FMT_VARIADIC_VOID(write, BasicCStringRef<Char>)\n\n  BasicWriter &operator<<(int value) {\n    write_decimal(value);\n    return *this;\n  }\n  BasicWriter &operator<<(unsigned value) {\n    return *this << IntFormatSpec<unsigned>(value);\n  }\n  BasicWriter &operator<<(long value) {\n    write_decimal(value);\n    return *this;\n  }\n  BasicWriter &operator<<(unsigned long value) {\n    return *this << IntFormatSpec<unsigned long>(value);\n  }\n  BasicWriter &operator<<(LongLong value) {\n    write_decimal(value);\n    return *this;\n  }\n\n  /**\n    \\rst\n    Formats *value* and writes it to the stream.\n    \\endrst\n   */\n  BasicWriter &operator<<(ULongLong value) {\n    return *this << IntFormatSpec<ULongLong>(value);\n  }\n\n  BasicWriter &operator<<(double value) {\n    write_double(value, FormatSpec());\n    return *this;\n  }\n\n  /**\n    \\rst\n    Formats *value* using the general format for floating-point numbers\n    (``'g'``) and writes it to the stream.\n    \\endrst\n   */\n  BasicWriter &operator<<(long double value) {\n    write_double(value, FormatSpec());\n    return *this;\n  }\n\n  /**\n    Writes a character to the stream.\n   */\n  BasicWriter &operator<<(char value) {\n    buffer_.push_back(value);\n    return *this;\n  }\n\n  BasicWriter &operator<<(\n      typename internal::WCharHelper<wchar_t, Char>::Supported value) {\n    buffer_.push_back(value);\n    return *this;\n  }\n\n  /**\n    \\rst\n    Writes *value* to the stream.\n    \\endrst\n   */\n  BasicWriter &operator<<(fmt::BasicStringRef<Char> value) {\n    const Char *str = value.data();\n    buffer_.append(str, str + value.size());\n    return *this;\n  }\n\n  BasicWriter &operator<<(\n      typename internal::WCharHelper<StringRef, Char>::Supported value) {\n    const char *str = value.data();\n    buffer_.append(str, str + value.size());\n    return *this;\n  }\n\n  template <typename T, typename Spec, typename FillChar>\n  BasicWriter &operator<<(IntFormatSpec<T, Spec, FillChar> spec) {\n    internal::CharTraits<Char>::convert(FillChar());\n    write_int(spec.value(), spec);\n    return *this;\n  }\n\n  template <typename StrChar>\n  BasicWriter &operator<<(const StrFormatSpec<StrChar> &spec) {\n    const StrChar *s = spec.str();\n    write_str(s, std::char_traits<Char>::length(s), spec);\n    return *this;\n  }\n\n  void clear() FMT_NOEXCEPT { buffer_.clear(); }\n\n  Buffer<Char> &buffer() FMT_NOEXCEPT { return buffer_; }\n};\n\ntemplate <typename Char>\ntemplate <typename StrChar>\ntypename BasicWriter<Char>::CharPtr BasicWriter<Char>::write_str(\n      const StrChar *s, std::size_t size, const AlignSpec &spec) {\n  CharPtr out = CharPtr();\n  if (spec.width() > size) {\n    out = grow_buffer(spec.width());\n    Char fill = internal::CharTraits<Char>::cast(spec.fill());\n    if (spec.align() == ALIGN_RIGHT) {\n      std::uninitialized_fill_n(out, spec.width() - size, fill);\n      out += spec.width() - size;\n    } else if (spec.align() == ALIGN_CENTER) {\n      out = fill_padding(out, spec.width(), size, fill);\n    } else {\n      std::uninitialized_fill_n(out + size, spec.width() - size, fill);\n    }\n  } else {\n    out = grow_buffer(size);\n  }\n  std::uninitialized_copy(s, s + size, out);\n  return out;\n}\n\ntemplate <typename Char>\ntemplate <typename StrChar, typename Spec>\nvoid BasicWriter<Char>::write_str(\n    const internal::Arg::StringValue<StrChar> &s, const Spec &spec) {\n  // Check if StrChar is convertible to Char.\n  internal::CharTraits<Char>::convert(StrChar());\n  if (spec.type_ && spec.type_ != 's')\n    internal::report_unknown_type(spec.type_, \"string\");\n  const StrChar *str_value = s.value;\n  std::size_t str_size = s.size;\n  if (str_size == 0) {\n    if (!str_value) {\n      FMT_THROW(FormatError(\"string pointer is null\"));\n    }\n  }\n  std::size_t precision = static_cast<std::size_t>(spec.precision_);\n  if (spec.precision_ >= 0 && precision < str_size)\n    str_size = precision;\n  write_str(str_value, str_size, spec);\n}\n\ntemplate <typename Char>\ntypename BasicWriter<Char>::CharPtr\n  BasicWriter<Char>::fill_padding(\n    CharPtr buffer, unsigned total_size,\n    std::size_t content_size, wchar_t fill) {\n  std::size_t padding = total_size - content_size;\n  std::size_t left_padding = padding / 2;\n  Char fill_char = internal::CharTraits<Char>::cast(fill);\n  std::uninitialized_fill_n(buffer, left_padding, fill_char);\n  buffer += left_padding;\n  CharPtr content = buffer;\n  std::uninitialized_fill_n(buffer + content_size,\n                            padding - left_padding, fill_char);\n  return content;\n}\n\ntemplate <typename Char>\ntemplate <typename Spec>\ntypename BasicWriter<Char>::CharPtr\n  BasicWriter<Char>::prepare_int_buffer(\n    unsigned num_digits, const Spec &spec,\n    const char *prefix, unsigned prefix_size) {\n  unsigned width = spec.width();\n  Alignment align = spec.align();\n  Char fill = internal::CharTraits<Char>::cast(spec.fill());\n  if (spec.precision() > static_cast<int>(num_digits)) {\n    // Octal prefix '0' is counted as a digit, so ignore it if precision\n    // is specified.\n    if (prefix_size > 0 && prefix[prefix_size - 1] == '0')\n      --prefix_size;\n    unsigned number_size =\n        prefix_size + internal::to_unsigned(spec.precision());\n    AlignSpec subspec(number_size, '0', ALIGN_NUMERIC);\n    if (number_size >= width)\n      return prepare_int_buffer(num_digits, subspec, prefix, prefix_size);\n    buffer_.reserve(width);\n    unsigned fill_size = width - number_size;\n    if (align != ALIGN_LEFT) {\n      CharPtr p = grow_buffer(fill_size);\n      std::uninitialized_fill(p, p + fill_size, fill);\n    }\n    CharPtr result = prepare_int_buffer(\n        num_digits, subspec, prefix, prefix_size);\n    if (align == ALIGN_LEFT) {\n      CharPtr p = grow_buffer(fill_size);\n      std::uninitialized_fill(p, p + fill_size, fill);\n    }\n    return result;\n  }\n  unsigned size = prefix_size + num_digits;\n  if (width <= size) {\n    CharPtr p = grow_buffer(size);\n    std::uninitialized_copy(prefix, prefix + prefix_size, p);\n    return p + size - 1;\n  }\n  CharPtr p = grow_buffer(width);\n  CharPtr end = p + width;\n  if (align == ALIGN_LEFT) {\n    std::uninitialized_copy(prefix, prefix + prefix_size, p);\n    p += size;\n    std::uninitialized_fill(p, end, fill);\n  } else if (align == ALIGN_CENTER) {\n    p = fill_padding(p, width, size, fill);\n    std::uninitialized_copy(prefix, prefix + prefix_size, p);\n    p += size;\n  } else {\n    if (align == ALIGN_NUMERIC) {\n      if (prefix_size != 0) {\n        p = std::uninitialized_copy(prefix, prefix + prefix_size, p);\n        size -= prefix_size;\n      }\n    } else {\n      std::uninitialized_copy(prefix, prefix + prefix_size, end - size);\n    }\n    std::uninitialized_fill(p, end - size, fill);\n    p = end;\n  }\n  return p - 1;\n}\n\ntemplate <typename Char>\ntemplate <typename T, typename Spec>\nvoid BasicWriter<Char>::write_int(T value, Spec spec) {\n  unsigned prefix_size = 0;\n  typedef typename internal::IntTraits<T>::MainType UnsignedType;\n  UnsignedType abs_value = static_cast<UnsignedType>(value);\n  char prefix[4] = \"\";\n  if (internal::is_negative(value)) {\n    prefix[0] = '-';\n    ++prefix_size;\n    abs_value = 0 - abs_value;\n  } else if (spec.flag(SIGN_FLAG)) {\n    prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' ';\n    ++prefix_size;\n  }\n  switch (spec.type()) {\n  case 0: case 'd': {\n    unsigned num_digits = internal::count_digits(abs_value);\n    CharPtr p = prepare_int_buffer(num_digits, spec, prefix, prefix_size) + 1;\n    internal::format_decimal(get(p), abs_value, 0);\n    break;\n  }\n  case 'x': case 'X': {\n    UnsignedType n = abs_value;\n    if (spec.flag(HASH_FLAG)) {\n      prefix[prefix_size++] = '0';\n      prefix[prefix_size++] = spec.type_prefix();\n    }\n    unsigned num_digits = 0;\n    do {\n      ++num_digits;\n    } while ((n >>= 4) != 0);\n    Char *p = get(prepare_int_buffer(\n      num_digits, spec, prefix, prefix_size));\n    n = abs_value;\n    const char *digits = spec.type() == 'x' ?\n        \"0123456789abcdef\" : \"0123456789ABCDEF\";\n    do {\n      *p-- = digits[n & 0xf];\n    } while ((n >>= 4) != 0);\n    break;\n  }\n  case 'b': case 'B': {\n    UnsignedType n = abs_value;\n    if (spec.flag(HASH_FLAG)) {\n      prefix[prefix_size++] = '0';\n      prefix[prefix_size++] = spec.type_prefix();\n    }\n    unsigned num_digits = 0;\n    do {\n      ++num_digits;\n    } while ((n >>= 1) != 0);\n    Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size));\n    n = abs_value;\n    do {\n      *p-- = static_cast<Char>('0' + (n & 1));\n    } while ((n >>= 1) != 0);\n    break;\n  }\n  case 'o': {\n    UnsignedType n = abs_value;\n    if (spec.flag(HASH_FLAG))\n      prefix[prefix_size++] = '0';\n    unsigned num_digits = 0;\n    do {\n      ++num_digits;\n    } while ((n >>= 3) != 0);\n    Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size));\n    n = abs_value;\n    do {\n      *p-- = static_cast<Char>('0' + (n & 7));\n    } while ((n >>= 3) != 0);\n    break;\n  }\n  case 'n': {\n    unsigned num_digits = internal::count_digits(abs_value);\n    fmt::StringRef sep = \"\";\n#if !(defined(ANDROID) || defined(__ANDROID__))\n    sep = internal::thousands_sep(std::localeconv());\n#endif\n    unsigned size = static_cast<unsigned>(\n          num_digits + sep.size() * ((num_digits - 1) / 3));\n    CharPtr p = prepare_int_buffer(size, spec, prefix, prefix_size) + 1;\n    internal::format_decimal(get(p), abs_value, 0, internal::ThousandsSep(sep));\n    break;\n  }\n  default:\n    internal::report_unknown_type(\n      spec.type(), spec.flag(CHAR_FLAG) ? \"char\" : \"integer\");\n    break;\n  }\n}\n\ntemplate <typename Char>\ntemplate <typename T, typename Spec>\nvoid BasicWriter<Char>::write_double(T value, const Spec &spec) {\n  // Check type.\n  char type = spec.type();\n  bool upper = false;\n  switch (type) {\n  case 0:\n    type = 'g';\n    break;\n  case 'e': case 'f': case 'g': case 'a':\n    break;\n  case 'F':\n#if FMT_MSC_VER\n    // MSVC's printf doesn't support 'F'.\n    type = 'f';\n#endif\n    // Fall through.\n  case 'E': case 'G': case 'A':\n    upper = true;\n    break;\n  default:\n    internal::report_unknown_type(type, \"double\");\n    break;\n  }\n\n  char sign = 0;\n  // Use isnegative instead of value < 0 because the latter is always\n  // false for NaN.\n  if (internal::FPUtil::isnegative(static_cast<double>(value))) {\n    sign = '-';\n    value = -value;\n  } else if (spec.flag(SIGN_FLAG)) {\n    sign = spec.flag(PLUS_FLAG) ? '+' : ' ';\n  }\n\n  if (internal::FPUtil::isnotanumber(value)) {\n    // Format NaN ourselves because sprintf's output is not consistent\n    // across platforms.\n    std::size_t nan_size = 4;\n    const char *nan = upper ? \" NAN\" : \" nan\";\n    if (!sign) {\n      --nan_size;\n      ++nan;\n    }\n    CharPtr out = write_str(nan, nan_size, spec);\n    if (sign)\n      *out = sign;\n    return;\n  }\n\n  if (internal::FPUtil::isinfinity(value)) {\n    // Format infinity ourselves because sprintf's output is not consistent\n    // across platforms.\n    std::size_t inf_size = 4;\n    const char *inf = upper ? \" INF\" : \" inf\";\n    if (!sign) {\n      --inf_size;\n      ++inf;\n    }\n    CharPtr out = write_str(inf, inf_size, spec);\n    if (sign)\n      *out = sign;\n    return;\n  }\n\n  std::size_t offset = buffer_.size();\n  unsigned width = spec.width();\n  if (sign) {\n    buffer_.reserve(buffer_.size() + (width > 1u ? width : 1u));\n    if (width > 0)\n      --width;\n    ++offset;\n  }\n\n  // Build format string.\n  enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg\n  Char format[MAX_FORMAT_SIZE];\n  Char *format_ptr = format;\n  *format_ptr++ = '%';\n  unsigned width_for_sprintf = width;\n  if (spec.flag(HASH_FLAG))\n    *format_ptr++ = '#';\n  if (spec.align() == ALIGN_CENTER) {\n    width_for_sprintf = 0;\n  } else {\n    if (spec.align() == ALIGN_LEFT)\n      *format_ptr++ = '-';\n    if (width != 0)\n      *format_ptr++ = '*';\n  }\n  if (spec.precision() >= 0) {\n    *format_ptr++ = '.';\n    *format_ptr++ = '*';\n  }\n\n  append_float_length(format_ptr, value);\n  *format_ptr++ = type;\n  *format_ptr = '\\0';\n\n  // Format using snprintf.\n  Char fill = internal::CharTraits<Char>::cast(spec.fill());\n  unsigned n = 0;\n  Char *start = FMT_NULL;\n  for (;;) {\n    std::size_t buffer_size = buffer_.capacity() - offset;\n#if FMT_MSC_VER\n    // MSVC's vsnprintf_s doesn't work with zero size, so reserve\n    // space for at least one extra character to make the size non-zero.\n    // Note that the buffer's capacity will increase by more than 1.\n    if (buffer_size == 0) {\n      buffer_.reserve(offset + 1);\n      buffer_size = buffer_.capacity() - offset;\n    }\n#endif\n    start = &buffer_[offset];\n    int result = internal::CharTraits<Char>::format_float(\n        start, buffer_size, format, width_for_sprintf, spec.precision(), value);\n    if (result >= 0) {\n      n = internal::to_unsigned(result);\n      if (offset + n < buffer_.capacity())\n        break;  // The buffer is large enough - continue with formatting.\n      buffer_.reserve(offset + n + 1);\n    } else {\n      // If result is negative we ask to increase the capacity by at least 1,\n      // but as std::vector, the buffer grows exponentially.\n      buffer_.reserve(buffer_.capacity() + 1);\n    }\n  }\n  if (sign) {\n    if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) ||\n        *start != ' ') {\n      *(start - 1) = sign;\n      sign = 0;\n    } else {\n      *(start - 1) = fill;\n    }\n    ++n;\n  }\n  if (spec.align() == ALIGN_CENTER && spec.width() > n) {\n    width = spec.width();\n    CharPtr p = grow_buffer(width);\n    std::memmove(get(p) + (width - n) / 2, get(p), n * sizeof(Char));\n    fill_padding(p, spec.width(), n, fill);\n    return;\n  }\n  if (spec.fill() != ' ' || sign) {\n    while (*start == ' ')\n      *start++ = fill;\n    if (sign)\n      *(start - 1) = sign;\n  }\n  grow_buffer(n);\n}\n\n/**\n  \\rst\n  This class template provides operations for formatting and writing data\n  into a character stream. The output is stored in a memory buffer that grows\n  dynamically.\n\n  You can use one of the following typedefs for common character types\n  and the standard allocator:\n\n  +---------------+-----------------------------------------------------+\n  | Type          | Definition                                          |\n  +===============+=====================================================+\n  | MemoryWriter  | BasicMemoryWriter<char, std::allocator<char>>       |\n  +---------------+-----------------------------------------------------+\n  | WMemoryWriter | BasicMemoryWriter<wchar_t, std::allocator<wchar_t>> |\n  +---------------+-----------------------------------------------------+\n\n  **Example**::\n\n     MemoryWriter out;\n     out << \"The answer is \" << 42 << \"\\n\";\n     out.write(\"({:+f}, {:+f})\", -3.14, 3.14);\n\n  This will write the following output to the ``out`` object:\n\n  .. code-block:: none\n\n     The answer is 42\n     (-3.140000, +3.140000)\n\n  The output can be converted to an ``std::string`` with ``out.str()`` or\n  accessed as a C string with ``out.c_str()``.\n  \\endrst\n */\ntemplate <typename Char, typename Allocator = std::allocator<Char> >\nclass BasicMemoryWriter : public BasicWriter<Char> {\n private:\n  internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE, Allocator> buffer_;\n\n public:\n  explicit BasicMemoryWriter(const Allocator& alloc = Allocator())\n    : BasicWriter<Char>(buffer_), buffer_(alloc) {}\n\n#if FMT_USE_RVALUE_REFERENCES\n  /**\n    \\rst\n    Constructs a :class:`fmt::BasicMemoryWriter` object moving the content\n    of the other object to it.\n    \\endrst\n   */\n  BasicMemoryWriter(BasicMemoryWriter &&other)\n    : BasicWriter<Char>(buffer_), buffer_(std::move(other.buffer_)) {\n  }\n\n  /**\n    \\rst\n    Moves the content of the other ``BasicMemoryWriter`` object to this one.\n    \\endrst\n   */\n  BasicMemoryWriter &operator=(BasicMemoryWriter &&other) {\n    buffer_ = std::move(other.buffer_);\n    return *this;\n  }\n#endif\n};\n\ntypedef BasicMemoryWriter<char> MemoryWriter;\ntypedef BasicMemoryWriter<wchar_t> WMemoryWriter;\n\n/**\n  \\rst\n  This class template provides operations for formatting and writing data\n  into a fixed-size array. For writing into a dynamically growing buffer\n  use :class:`fmt::BasicMemoryWriter`.\n\n  Any write method will throw ``std::runtime_error`` if the output doesn't fit\n  into the array.\n\n  You can use one of the following typedefs for common character types:\n\n  +--------------+---------------------------+\n  | Type         | Definition                |\n  +==============+===========================+\n  | ArrayWriter  | BasicArrayWriter<char>    |\n  +--------------+---------------------------+\n  | WArrayWriter | BasicArrayWriter<wchar_t> |\n  +--------------+---------------------------+\n  \\endrst\n */\ntemplate <typename Char>\nclass BasicArrayWriter : public BasicWriter<Char> {\n private:\n  internal::FixedBuffer<Char> buffer_;\n\n public:\n  /**\n   \\rst\n   Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the\n   given size.\n   \\endrst\n   */\n  BasicArrayWriter(Char *array, std::size_t size)\n    : BasicWriter<Char>(buffer_), buffer_(array, size) {}\n\n  /**\n   \\rst\n   Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the\n   size known at compile time.\n   \\endrst\n   */\n  template <std::size_t SIZE>\n  explicit BasicArrayWriter(Char (&array)[SIZE])\n    : BasicWriter<Char>(buffer_), buffer_(array, SIZE) {}\n};\n\ntypedef BasicArrayWriter<char> ArrayWriter;\ntypedef BasicArrayWriter<wchar_t> WArrayWriter;\n\n// Reports a system error without throwing an exception.\n// Can be used to report errors from destructors.\nFMT_API void report_system_error(int error_code,\n                                 StringRef message) FMT_NOEXCEPT;\n\n#if FMT_USE_WINDOWS_H\n\n/** A Windows error. */\nclass WindowsError : public SystemError {\n private:\n  FMT_API void init(int error_code, CStringRef format_str, ArgList args);\n\n public:\n  /**\n   \\rst\n   Constructs a :class:`fmt::WindowsError` object with the description\n   of the form\n\n   .. parsed-literal::\n     *<message>*: *<system-message>*\n\n   where *<message>* is the formatted message and *<system-message>* is the\n   system message corresponding to the error code.\n   *error_code* is a Windows error code as given by ``GetLastError``.\n   If *error_code* is not a valid error code such as -1, the system message\n   will look like \"error -1\".\n\n   **Example**::\n\n     // This throws a WindowsError with the description\n     //   cannot open file 'madeup': The system cannot find the file specified.\n     // or similar (system message may vary).\n     const char *filename = \"madeup\";\n     LPOFSTRUCT of = LPOFSTRUCT();\n     HFILE file = OpenFile(filename, &of, OF_READ);\n     if (file == HFILE_ERROR) {\n       throw fmt::WindowsError(GetLastError(),\n                               \"cannot open file '{}'\", filename);\n     }\n   \\endrst\n  */\n  WindowsError(int error_code, CStringRef message) {\n    init(error_code, message, ArgList());\n  }\n  FMT_VARIADIC_CTOR(WindowsError, init, int, CStringRef)\n};\n\n// Reports a Windows error without throwing an exception.\n// Can be used to report errors from destructors.\nFMT_API void report_windows_error(int error_code,\n                                  StringRef message) FMT_NOEXCEPT;\n\n#endif\n\nenum Color { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE };\n\n/**\n  Formats a string and prints it to stdout using ANSI escape sequences\n  to specify color (experimental).\n  Example:\n    print_colored(fmt::RED, \"Elapsed time: {0:.2f} seconds\", 1.23);\n */\nFMT_API void print_colored(Color c, CStringRef format, ArgList args);\n\n/**\n  \\rst\n  Formats arguments and returns the result as a string.\n\n  **Example**::\n\n    std::string message = format(\"The answer is {}\", 42);\n  \\endrst\n*/\ninline std::string format(CStringRef format_str, ArgList args) {\n  MemoryWriter w;\n  w.write(format_str, args);\n  return w.str();\n}\n\ninline std::wstring format(WCStringRef format_str, ArgList args) {\n  WMemoryWriter w;\n  w.write(format_str, args);\n  return w.str();\n}\n\n/**\n  \\rst\n  Prints formatted data to the file *f*.\n\n  **Example**::\n\n    print(stderr, \"Don't {}!\", \"panic\");\n  \\endrst\n */\nFMT_API void print(std::FILE *f, CStringRef format_str, ArgList args);\n\n/**\n  \\rst\n  Prints formatted data to ``stdout``.\n\n  **Example**::\n\n    print(\"Elapsed time: {0:.2f} seconds\", 1.23);\n  \\endrst\n */\nFMT_API void print(CStringRef format_str, ArgList args);\n\n/**\n  Fast integer formatter.\n */\nclass FormatInt {\n private:\n  // Buffer should be large enough to hold all digits (digits10 + 1),\n  // a sign and a null character.\n  enum {BUFFER_SIZE = std::numeric_limits<ULongLong>::digits10 + 3};\n  mutable char buffer_[BUFFER_SIZE];\n  char *str_;\n\n  // Formats value in reverse and returns the number of digits.\n  char *format_decimal(ULongLong value) {\n    char *buffer_end = buffer_ + BUFFER_SIZE - 1;\n    while (value >= 100) {\n      // Integer division is slow so do it for a group of two digits instead\n      // of for every digit. The idea comes from the talk by Alexandrescu\n      // \"Three Optimization Tips for C++\". See speed-test for a comparison.\n      unsigned index = static_cast<unsigned>((value % 100) * 2);\n      value /= 100;\n      *--buffer_end = internal::Data::DIGITS[index + 1];\n      *--buffer_end = internal::Data::DIGITS[index];\n    }\n    if (value < 10) {\n      *--buffer_end = static_cast<char>('0' + value);\n      return buffer_end;\n    }\n    unsigned index = static_cast<unsigned>(value * 2);\n    *--buffer_end = internal::Data::DIGITS[index + 1];\n    *--buffer_end = internal::Data::DIGITS[index];\n    return buffer_end;\n  }\n\n  void FormatSigned(LongLong value) {\n    ULongLong abs_value = static_cast<ULongLong>(value);\n    bool negative = value < 0;\n    if (negative)\n      abs_value = 0 - abs_value;\n    str_ = format_decimal(abs_value);\n    if (negative)\n      *--str_ = '-';\n  }\n\n public:\n  explicit FormatInt(int value) { FormatSigned(value); }\n  explicit FormatInt(long value) { FormatSigned(value); }\n  explicit FormatInt(LongLong value) { FormatSigned(value); }\n  explicit FormatInt(unsigned value) : str_(format_decimal(value)) {}\n  explicit FormatInt(unsigned long value) : str_(format_decimal(value)) {}\n  explicit FormatInt(ULongLong value) : str_(format_decimal(value)) {}\n\n  /** Returns the number of characters written to the output buffer. */\n  std::size_t size() const {\n    return internal::to_unsigned(buffer_ - str_ + BUFFER_SIZE - 1);\n  }\n\n  /**\n    Returns a pointer to the output buffer content. No terminating null\n    character is appended.\n   */\n  const char *data() const { return str_; }\n\n  /**\n    Returns a pointer to the output buffer content with terminating null\n    character appended.\n   */\n  const char *c_str() const {\n    buffer_[BUFFER_SIZE - 1] = '\\0';\n    return str_;\n  }\n\n  /**\n    \\rst\n    Returns the content of the output buffer as an ``std::string``.\n    \\endrst\n   */\n  std::string str() const { return std::string(str_, size()); }\n};\n\n// Formats a decimal integer value writing into buffer and returns\n// a pointer to the end of the formatted string. This function doesn't\n// write a terminating null character.\ntemplate <typename T>\ninline void format_decimal(char *&buffer, T value) {\n  typedef typename internal::IntTraits<T>::MainType MainType;\n  MainType abs_value = static_cast<MainType>(value);\n  if (internal::is_negative(value)) {\n    *buffer++ = '-';\n    abs_value = 0 - abs_value;\n  }\n  if (abs_value < 100) {\n    if (abs_value < 10) {\n      *buffer++ = static_cast<char>('0' + abs_value);\n      return;\n    }\n    unsigned index = static_cast<unsigned>(abs_value * 2);\n    *buffer++ = internal::Data::DIGITS[index];\n    *buffer++ = internal::Data::DIGITS[index + 1];\n    return;\n  }\n  unsigned num_digits = internal::count_digits(abs_value);\n  internal::format_decimal(buffer, abs_value, num_digits);\n  buffer += num_digits;\n}\n\n/**\n  \\rst\n  Returns a named argument for formatting functions.\n\n  **Example**::\n\n    print(\"Elapsed time: {s:.2f} seconds\", arg(\"s\", 1.23));\n\n  \\endrst\n */\ntemplate <typename T>\ninline internal::NamedArgWithType<char, T> arg(StringRef name, const T &arg) {\n  return internal::NamedArgWithType<char, T>(name, arg);\n}\n\ntemplate <typename T>\ninline internal::NamedArgWithType<wchar_t, T> arg(WStringRef name, const T &arg) {\n  return internal::NamedArgWithType<wchar_t, T>(name, arg);\n}\n\n// The following two functions are deleted intentionally to disable\n// nested named arguments as in ``format(\"{}\", arg(\"a\", arg(\"b\", 42)))``.\ntemplate <typename Char>\nvoid arg(StringRef, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED;\ntemplate <typename Char>\nvoid arg(WStringRef, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED;\n}\n\n#if FMT_GCC_VERSION\n// Use the system_header pragma to suppress warnings about variadic macros\n// because suppressing -Wvariadic-macros with the diagnostic pragma doesn't\n// work. It is used at the end because we want to suppress as little warnings\n// as possible.\n# pragma GCC system_header\n#endif\n\n// This is used to work around VC++ bugs in handling variadic macros.\n#define FMT_EXPAND(args) args\n\n// Returns the number of arguments.\n// Based on https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s.\n#define FMT_NARG(...) FMT_NARG_(__VA_ARGS__, FMT_RSEQ_N())\n#define FMT_NARG_(...) FMT_EXPAND(FMT_ARG_N(__VA_ARGS__))\n#define FMT_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N\n#define FMT_RSEQ_N() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0\n\n#define FMT_FOR_EACH_(N, f, ...) \\\n  FMT_EXPAND(FMT_CONCAT(FMT_FOR_EACH, N)(f, __VA_ARGS__))\n#define FMT_FOR_EACH(f, ...) \\\n  FMT_EXPAND(FMT_FOR_EACH_(FMT_NARG(__VA_ARGS__), f, __VA_ARGS__))\n\n#define FMT_ADD_ARG_NAME(type, index) type arg##index\n#define FMT_GET_ARG_NAME(type, index) arg##index\n\n#if FMT_USE_VARIADIC_TEMPLATES\n# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \\\n  template <typename... Args> \\\n  ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \\\n      const Args & ... args) Const { \\\n    typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \\\n    typename ArgArray::Type array{ \\\n      ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \\\n    call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \\\n      fmt::ArgList(fmt::internal::make_type(args...), array)); \\\n  }\n#else\n// Defines a wrapper for a function taking __VA_ARGS__ arguments\n// and n additional arguments of arbitrary types.\n# define FMT_WRAP(Const, Char, ReturnType, func, call, n, ...) \\\n  template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \\\n  inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \\\n      FMT_GEN(n, FMT_MAKE_ARG)) Const { \\\n    fmt::internal::ArgArray<n>::Type arr; \\\n    FMT_GEN(n, FMT_ASSIGN_##Char); \\\n    call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \\\n      fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), arr)); \\\n  }\n\n# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \\\n  inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) Const { \\\n    call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \\\n  } \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 1, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 2, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 3, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 4, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 5, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 6, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 7, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 8, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 9, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 10, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 11, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 12, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 13, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 14, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 15, __VA_ARGS__)\n#endif  // FMT_USE_VARIADIC_TEMPLATES\n\n/**\n  \\rst\n  Defines a variadic function with the specified return type, function name\n  and argument types passed as variable arguments to this macro.\n\n  **Example**::\n\n    void print_error(const char *file, int line, const char *format,\n                     fmt::ArgList args) {\n      fmt::print(\"{}: {}: \", file, line);\n      fmt::print(format, args);\n    }\n    FMT_VARIADIC(void, print_error, const char *, int, const char *)\n\n  ``FMT_VARIADIC`` is used for compatibility with legacy C++ compilers that\n  don't implement variadic templates. You don't have to use this macro if\n  you don't need legacy compiler support and can use variadic templates\n  directly::\n\n    template <typename... Args>\n    void print_error(const char *file, int line, const char *format,\n                     const Args & ... args) {\n      fmt::print(\"{}: {}: \", file, line);\n      fmt::print(format, args...);\n    }\n  \\endrst\n */\n#define FMT_VARIADIC(ReturnType, func, ...) \\\n  FMT_VARIADIC_(, char, ReturnType, func, return func, __VA_ARGS__)\n\n#define FMT_VARIADIC_CONST(ReturnType, func, ...) \\\n  FMT_VARIADIC_(const, char, ReturnType, func, return func, __VA_ARGS__)\n\n#define FMT_VARIADIC_W(ReturnType, func, ...) \\\n  FMT_VARIADIC_(, wchar_t, ReturnType, func, return func, __VA_ARGS__)\n\n#define FMT_VARIADIC_CONST_W(ReturnType, func, ...) \\\n  FMT_VARIADIC_(const, wchar_t, ReturnType, func, return func, __VA_ARGS__)\n\n#define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id)\n\n#define FMT_CAPTURE_ARG_W_(id, index) ::fmt::arg(L###id, id)\n\n/**\n  \\rst\n  Convenient macro to capture the arguments' names and values into several\n  ``fmt::arg(name, value)``.\n\n  **Example**::\n\n    int x = 1, y = 2;\n    print(\"point: ({x}, {y})\", FMT_CAPTURE(x, y));\n    // same as:\n    // print(\"point: ({x}, {y})\", arg(\"x\", x), arg(\"y\", y));\n\n  \\endrst\n */\n#define FMT_CAPTURE(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_, __VA_ARGS__)\n\n#define FMT_CAPTURE_W(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_W_, __VA_ARGS__)\n\nnamespace fmt {\nFMT_VARIADIC(std::string, format, CStringRef)\nFMT_VARIADIC_W(std::wstring, format, WCStringRef)\nFMT_VARIADIC(void, print, CStringRef)\nFMT_VARIADIC(void, print, std::FILE *, CStringRef)\nFMT_VARIADIC(void, print_colored, Color, CStringRef)\n\nnamespace internal {\ntemplate <typename Char>\ninline bool is_name_start(Char c) {\n  return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c;\n}\n\n// Parses an unsigned integer advancing s to the end of the parsed input.\n// This function assumes that the first character of s is a digit.\ntemplate <typename Char>\nunsigned parse_nonnegative_int(const Char *&s) {\n  assert('0' <= *s && *s <= '9');\n  unsigned value = 0;\n  // Convert to unsigned to prevent a warning.\n  unsigned max_int = (std::numeric_limits<int>::max)();\n  unsigned big = max_int / 10;\n  do {\n    // Check for overflow.\n    if (value > big) {\n      value = max_int + 1;\n      break;\n    }\n    value = value * 10 + (*s - '0');\n    ++s;\n  } while ('0' <= *s && *s <= '9');\n  // Convert to unsigned to prevent a warning.\n  if (value > max_int)\n    FMT_THROW(FormatError(\"number is too big\"));\n  return value;\n}\n\ninline void require_numeric_argument(const Arg &arg, char spec) {\n  if (arg.type > Arg::LAST_NUMERIC_TYPE) {\n    std::string message =\n        fmt::format(\"format specifier '{}' requires numeric argument\", spec);\n    FMT_THROW(fmt::FormatError(message));\n  }\n}\n\ntemplate <typename Char>\nvoid check_sign(const Char *&s, const Arg &arg) {\n  char sign = static_cast<char>(*s);\n  require_numeric_argument(arg, sign);\n  if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) {\n    FMT_THROW(FormatError(fmt::format(\n      \"format specifier '{}' requires signed argument\", sign)));\n  }\n  ++s;\n}\n}  // namespace internal\n\ntemplate <typename Char, typename AF>\ninline internal::Arg BasicFormatter<Char, AF>::get_arg(\n    BasicStringRef<Char> arg_name, const char *&error) {\n  if (check_no_auto_index(error)) {\n    map_.init(args());\n    const internal::Arg *arg = map_.find(arg_name);\n    if (arg)\n      return *arg;\n    error = \"argument not found\";\n  }\n  return internal::Arg();\n}\n\ntemplate <typename Char, typename AF>\ninline internal::Arg BasicFormatter<Char, AF>::parse_arg_index(const Char *&s) {\n  const char *error = FMT_NULL;\n  internal::Arg arg = *s < '0' || *s > '9' ?\n        next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error);\n  if (error) {\n    FMT_THROW(FormatError(\n                *s != '}' && *s != ':' ? \"invalid format string\" : error));\n  }\n  return arg;\n}\n\ntemplate <typename Char, typename AF>\ninline internal::Arg BasicFormatter<Char, AF>::parse_arg_name(const Char *&s) {\n  assert(internal::is_name_start(*s));\n  const Char *start = s;\n  Char c;\n  do {\n    c = *++s;\n  } while (internal::is_name_start(c) || ('0' <= c && c <= '9'));\n  const char *error = FMT_NULL;\n  internal::Arg arg = get_arg(BasicStringRef<Char>(start, s - start), error);\n  if (error)\n    FMT_THROW(FormatError(error));\n  return arg;\n}\n\ntemplate <typename Char, typename ArgFormatter>\nconst Char *BasicFormatter<Char, ArgFormatter>::format(\n    const Char *&format_str, const internal::Arg &arg) {\n  using internal::Arg;\n  const Char *s = format_str;\n  typename ArgFormatter::SpecType spec;\n  if (*s == ':') {\n    if (arg.type == Arg::CUSTOM) {\n      arg.custom.format(this, arg.custom.value, &s);\n      return s;\n    }\n    ++s;\n    // Parse fill and alignment.\n    if (Char c = *s) {\n      const Char *p = s + 1;\n      spec.align_ = ALIGN_DEFAULT;\n      do {\n        switch (*p) {\n          case '<':\n            spec.align_ = ALIGN_LEFT;\n            break;\n          case '>':\n            spec.align_ = ALIGN_RIGHT;\n            break;\n          case '=':\n            spec.align_ = ALIGN_NUMERIC;\n            break;\n          case '^':\n            spec.align_ = ALIGN_CENTER;\n            break;\n        }\n        if (spec.align_ != ALIGN_DEFAULT) {\n          if (p != s) {\n            if (c == '}') break;\n            if (c == '{')\n              FMT_THROW(FormatError(\"invalid fill character '{'\"));\n            s += 2;\n            spec.fill_ = c;\n          } else ++s;\n          if (spec.align_ == ALIGN_NUMERIC)\n            require_numeric_argument(arg, '=');\n          break;\n        }\n      } while (--p >= s);\n    }\n\n    // Parse sign.\n    switch (*s) {\n      case '+':\n        check_sign(s, arg);\n        spec.flags_ |= SIGN_FLAG | PLUS_FLAG;\n        break;\n      case '-':\n        check_sign(s, arg);\n        spec.flags_ |= MINUS_FLAG;\n        break;\n      case ' ':\n        check_sign(s, arg);\n        spec.flags_ |= SIGN_FLAG;\n        break;\n    }\n\n    if (*s == '#') {\n      require_numeric_argument(arg, '#');\n      spec.flags_ |= HASH_FLAG;\n      ++s;\n    }\n\n    // Parse zero flag.\n    if (*s == '0') {\n      require_numeric_argument(arg, '0');\n      spec.align_ = ALIGN_NUMERIC;\n      spec.fill_ = '0';\n      ++s;\n    }\n\n    // Parse width.\n    if ('0' <= *s && *s <= '9') {\n      spec.width_ = internal::parse_nonnegative_int(s);\n    } else if (*s == '{') {\n      ++s;\n      Arg width_arg = internal::is_name_start(*s) ?\n            parse_arg_name(s) : parse_arg_index(s);\n      if (*s++ != '}')\n        FMT_THROW(FormatError(\"invalid format string\"));\n      ULongLong value = 0;\n      switch (width_arg.type) {\n      case Arg::INT:\n        if (width_arg.int_value < 0)\n          FMT_THROW(FormatError(\"negative width\"));\n        value = width_arg.int_value;\n        break;\n      case Arg::UINT:\n        value = width_arg.uint_value;\n        break;\n      case Arg::LONG_LONG:\n        if (width_arg.long_long_value < 0)\n          FMT_THROW(FormatError(\"negative width\"));\n        value = width_arg.long_long_value;\n        break;\n      case Arg::ULONG_LONG:\n        value = width_arg.ulong_long_value;\n        break;\n      default:\n        FMT_THROW(FormatError(\"width is not integer\"));\n      }\n      unsigned max_int = (std::numeric_limits<int>::max)();\n      if (value > max_int)\n        FMT_THROW(FormatError(\"number is too big\"));\n      spec.width_ = static_cast<int>(value);\n    }\n\n    // Parse precision.\n    if (*s == '.') {\n      ++s;\n      spec.precision_ = 0;\n      if ('0' <= *s && *s <= '9') {\n        spec.precision_ = internal::parse_nonnegative_int(s);\n      } else if (*s == '{') {\n        ++s;\n        Arg precision_arg = internal::is_name_start(*s) ?\n              parse_arg_name(s) : parse_arg_index(s);\n        if (*s++ != '}')\n          FMT_THROW(FormatError(\"invalid format string\"));\n        ULongLong value = 0;\n        switch (precision_arg.type) {\n          case Arg::INT:\n            if (precision_arg.int_value < 0)\n              FMT_THROW(FormatError(\"negative precision\"));\n            value = precision_arg.int_value;\n            break;\n          case Arg::UINT:\n            value = precision_arg.uint_value;\n            break;\n          case Arg::LONG_LONG:\n            if (precision_arg.long_long_value < 0)\n              FMT_THROW(FormatError(\"negative precision\"));\n            value = precision_arg.long_long_value;\n            break;\n          case Arg::ULONG_LONG:\n            value = precision_arg.ulong_long_value;\n            break;\n          default:\n            FMT_THROW(FormatError(\"precision is not integer\"));\n        }\n        unsigned max_int = (std::numeric_limits<int>::max)();\n        if (value > max_int)\n          FMT_THROW(FormatError(\"number is too big\"));\n        spec.precision_ = static_cast<int>(value);\n      } else {\n        FMT_THROW(FormatError(\"missing precision specifier\"));\n      }\n      if (arg.type <= Arg::LAST_INTEGER_TYPE || arg.type == Arg::POINTER) {\n        FMT_THROW(FormatError(\n            fmt::format(\"precision not allowed in {} format specifier\",\n            arg.type == Arg::POINTER ? \"pointer\" : \"integer\")));\n      }\n    }\n\n    // Parse type.\n    if (*s != '}' && *s)\n      spec.type_ = static_cast<char>(*s++);\n  }\n\n  if (*s++ != '}')\n    FMT_THROW(FormatError(\"missing '}' in format string\"));\n\n  // Format argument.\n  ArgFormatter(*this, spec, s - 1).visit(arg);\n  return s;\n}\n\ntemplate <typename Char, typename AF>\nvoid BasicFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {\n  const Char *s = format_str.c_str();\n  const Char *start = s;\n  while (*s) {\n    Char c = *s++;\n    if (c != '{' && c != '}') continue;\n    if (*s == c) {\n      write(writer_, start, s);\n      start = ++s;\n      continue;\n    }\n    if (c == '}')\n      FMT_THROW(FormatError(\"unmatched '}' in format string\"));\n    write(writer_, start, s - 1);\n    internal::Arg arg = internal::is_name_start(*s) ?\n          parse_arg_name(s) : parse_arg_index(s);\n    start = s = format(s, arg);\n  }\n  write(writer_, start, s);\n}\n\ntemplate <typename Char, typename It>\nstruct ArgJoin {\n  It first;\n  It last;\n  BasicCStringRef<Char> sep;\n\n  ArgJoin(It first, It last, const BasicCStringRef<Char>& sep) :\n    first(first),\n    last(last),\n    sep(sep) {}\n};\n\ntemplate <typename It>\nArgJoin<char, It> join(It first, It last, const BasicCStringRef<char>& sep) {\n  return ArgJoin<char, It>(first, last, sep);\n}\n\ntemplate <typename It>\nArgJoin<wchar_t, It> join(It first, It last, const BasicCStringRef<wchar_t>& sep) {\n  return ArgJoin<wchar_t, It>(first, last, sep);\n}\n\n#if FMT_HAS_GXX_CXX11\ntemplate <typename Range>\nauto join(const Range& range, const BasicCStringRef<char>& sep)\n    -> ArgJoin<char, decltype(std::begin(range))> {\n  return join(std::begin(range), std::end(range), sep);\n}\n\ntemplate <typename Range>\nauto join(const Range& range, const BasicCStringRef<wchar_t>& sep)\n    -> ArgJoin<wchar_t, decltype(std::begin(range))> {\n  return join(std::begin(range), std::end(range), sep);\n}\n#endif\n\ntemplate <typename ArgFormatter, typename Char, typename It>\nvoid format_arg(fmt::BasicFormatter<Char, ArgFormatter> &f,\n    const Char *&format_str, const ArgJoin<Char, It>& e) {\n  const Char* end = format_str;\n  if (*end == ':')\n    ++end;\n  while (*end && *end != '}')\n    ++end;\n  if (*end != '}')\n    FMT_THROW(FormatError(\"missing '}' in format string\"));\n\n  It it = e.first;\n  if (it != e.last) {\n    const Char* save = format_str;\n    f.format(format_str, internal::MakeArg<fmt::BasicFormatter<Char, ArgFormatter> >(*it++));\n    while (it != e.last) {\n      f.writer().write(e.sep);\n      format_str = save;\n      f.format(format_str, internal::MakeArg<fmt::BasicFormatter<Char, ArgFormatter> >(*it++));\n    }\n  }\n  format_str = end + 1;\n}\n}  // namespace fmt\n\n#if FMT_USE_USER_DEFINED_LITERALS\nnamespace fmt {\nnamespace internal {\n\ntemplate <typename Char>\nstruct UdlFormat {\n  const Char *str;\n\n  template <typename... Args>\n  auto operator()(Args && ... args) const\n                  -> decltype(format(str, std::forward<Args>(args)...)) {\n    return format(str, std::forward<Args>(args)...);\n  }\n};\n\ntemplate <typename Char>\nstruct UdlArg {\n  const Char *str;\n\n  template <typename T>\n  NamedArgWithType<Char, T> operator=(T &&value) const {\n    return {str, std::forward<T>(value)};\n  }\n};\n\n} // namespace internal\n\ninline namespace literals {\n\n/**\n  \\rst\n  C++11 literal equivalent of :func:`fmt::format`.\n\n  **Example**::\n\n    using namespace fmt::literals;\n    std::string message = \"The answer is {}\"_format(42);\n  \\endrst\n */\ninline internal::UdlFormat<char>\noperator\"\" _format(const char *s, std::size_t) { return {s}; }\ninline internal::UdlFormat<wchar_t>\noperator\"\" _format(const wchar_t *s, std::size_t) { return {s}; }\n\n/**\n  \\rst\n  C++11 literal equivalent of :func:`fmt::arg`.\n\n  **Example**::\n\n    using namespace fmt::literals;\n    print(\"Elapsed time: {s:.2f} seconds\", \"s\"_a=1.23);\n  \\endrst\n */\ninline internal::UdlArg<char>\noperator\"\" _a(const char *s, std::size_t) { return {s}; }\ninline internal::UdlArg<wchar_t>\noperator\"\" _a(const wchar_t *s, std::size_t) { return {s}; }\n\n} // inline namespace literals\n} // namespace fmt\n#endif // FMT_USE_USER_DEFINED_LITERALS\n\n// Restore warnings.\n#if FMT_GCC_VERSION >= 406\n# pragma GCC diagnostic pop\n#endif\n\n#if defined(__clang__) && !defined(FMT_ICC_VERSION)\n# pragma clang diagnostic pop\n#endif\n\n#ifdef FMT_HEADER_ONLY\n# define FMT_FUNC inline\n# include \"format.cc\"\n#else\n# define FMT_FUNC\n#endif\n\n#endif  // FMT_FORMAT_H_\n"
  },
  {
    "path": "lib/fmt/fmt_ostream.cpp",
    "content": "/*\n Formatting library for C++ - std::ostream support\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#include \"fmt_ostream.h\"\n\nnamespace fmt {\n\nnamespace internal {\nFMT_FUNC void write(std::ostream &os, Writer &w) {\n  const char *data = w.data();\n  typedef internal::MakeUnsigned<std::streamsize>::Type UnsignedStreamSize;\n  UnsignedStreamSize size = w.size();\n  UnsignedStreamSize max_size =\n      internal::to_unsigned((std::numeric_limits<std::streamsize>::max)());\n  do {\n    UnsignedStreamSize n = size <= max_size ? size : max_size;\n    os.write(data, static_cast<std::streamsize>(n));\n    data += n;\n    size -= n;\n  } while (size != 0);\n}\n}\n\nFMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) {\n  MemoryWriter w;\n  w.write(format_str, args);\n  internal::write(os, w);\n}\n}  // namespace fmt\n"
  },
  {
    "path": "lib/fmt/fmt_ostream.h",
    "content": "/*\n Formatting library for C++ - std::ostream support\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_OSTREAM_H_\n#define FMT_OSTREAM_H_\n\n#include \"fmt_format.h\"\n#include <ostream>\n\nnamespace fmt {\n\nnamespace internal {\n\ntemplate <class Char>\nclass FormatBuf : public std::basic_streambuf<Char> {\n private:\n  typedef typename std::basic_streambuf<Char>::int_type int_type;\n  typedef typename std::basic_streambuf<Char>::traits_type traits_type;\n\n  Buffer<Char> &buffer_;\n\n public:\n  FormatBuf(Buffer<Char> &buffer) : buffer_(buffer) {}\n\n protected:\n  // The put-area is actually always empty. This makes the implementation\n  // simpler and has the advantage that the streambuf and the buffer are always\n  // in sync and sputc never writes into uninitialized memory. The obvious\n  // disadvantage is that each call to sputc always results in a (virtual) call\n  // to overflow. There is no disadvantage here for sputn since this always\n  // results in a call to xsputn.\n\n  int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {\n    if (!traits_type::eq_int_type(ch, traits_type::eof()))\n      buffer_.push_back(static_cast<Char>(ch));\n    return ch;\n  }\n\n  std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE {\n    buffer_.append(s, s + count);\n    return count;\n  }\n};\n\nYes &convert(std::ostream &);\n\nstruct DummyStream : std::ostream {\n  DummyStream();  // Suppress a bogus warning in MSVC.\n\n  // Hide all operator<< overloads from std::ostream.\n  template <typename T>\n  typename EnableIf<sizeof(T) == 0>::type operator<<(const T &);\n};\n\nNo &operator<<(std::ostream &, int);\n\ntemplate <typename T>\nstruct ConvertToIntImpl<T, true> {\n  // Convert to int only if T doesn't have an overloaded operator<<.\n  enum {\n    value = sizeof(convert(get<DummyStream>() << get<T>())) == sizeof(No)\n  };\n};\n\n// Write the content of w to os.\nFMT_API void write(std::ostream &os, Writer &w);\n}  // namespace internal\n\n// Formats a value.\ntemplate <typename Char, typename ArgFormatter_, typename T>\nvoid format_arg(BasicFormatter<Char, ArgFormatter_> &f,\n                const Char *&format_str, const T &value) {\n  internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE> buffer;\n\n  internal::FormatBuf<Char> format_buf(buffer);\n  std::basic_ostream<Char> output(&format_buf);\n  output.exceptions(std::ios_base::failbit | std::ios_base::badbit);\n  output << value;\n\n  BasicStringRef<Char> str(&buffer[0], buffer.size());\n  typedef internal::MakeArg< BasicFormatter<Char> > MakeArg;\n  format_str = f.format(format_str, MakeArg(str));\n}\n\n/**\n  \\rst\n  Prints formatted data to the stream *os*.\n\n  **Example**::\n\n    print(cerr, \"Don't {}!\", \"panic\");\n  \\endrst\n */\nFMT_API void print(std::ostream &os, CStringRef format_str, ArgList args);\nFMT_VARIADIC(void, print, std::ostream &, CStringRef)\n}  // namespace fmt\n\n#ifdef FMT_HEADER_ONLY\n# include \"ostream.cc\"\n#endif\n\n#endif  // FMT_OSTREAM_H_\n"
  },
  {
    "path": "lib/fmt/fmt_posix.cpp",
    "content": "/*\n A C++ interface to POSIX functions.\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n// Disable bogus MSVC warnings.\n#ifndef _CRT_SECURE_NO_WARNINGS\n# define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#include \"fmt_posix.h\"\n\n#include <limits.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n\n#ifndef _WIN32\n# include <unistd.h>\n#else\n# ifndef WIN32_LEAN_AND_MEAN\n#  define WIN32_LEAN_AND_MEAN\n# endif\n# include <windows.h>\n# include <io.h>\n\n# define O_CREAT _O_CREAT\n# define O_TRUNC _O_TRUNC\n\n# ifndef S_IRUSR\n#  define S_IRUSR _S_IREAD\n# endif\n\n# ifndef S_IWUSR\n#  define S_IWUSR _S_IWRITE\n# endif\n\n# ifdef __MINGW32__\n#  define _SH_DENYNO 0x40\n# endif\n\n#endif  // _WIN32\n\n#ifdef fileno\n# undef fileno\n#endif\n\nnamespace {\n#ifdef _WIN32\n// Return type of read and write functions.\ntypedef int RWResult;\n\n// On Windows the count argument to read and write is unsigned, so convert\n// it from size_t preventing integer overflow.\ninline unsigned convert_rwcount(std::size_t count) {\n  return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;\n}\n#else\n// Return type of read and write functions.\ntypedef ssize_t RWResult;\n\ninline std::size_t convert_rwcount(std::size_t count) { return count; }\n#endif\n}\n\nfmt::BufferedFile::~BufferedFile() FMT_NOEXCEPT {\n  if (file_ && FMT_SYSTEM(fclose(file_)) != 0)\n    fmt::report_system_error(errno, \"cannot close file\");\n}\n\nfmt::BufferedFile::BufferedFile(\n    fmt::CStringRef filename, fmt::CStringRef mode) {\n  FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), 0);\n  if (!file_)\n    FMT_THROW(SystemError(errno, \"cannot open file {}\", filename));\n}\n\nvoid fmt::BufferedFile::close() {\n  if (!file_)\n    return;\n  int result = FMT_SYSTEM(fclose(file_));\n  file_ = FMT_NULL;\n  if (result != 0)\n    FMT_THROW(SystemError(errno, \"cannot close file\"));\n}\n\n// A macro used to prevent expansion of fileno on broken versions of MinGW.\n#define FMT_ARGS\n\nint fmt::BufferedFile::fileno() const {\n  int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));\n  if (fd == -1)\n    FMT_THROW(SystemError(errno, \"cannot get file descriptor\"));\n  return fd;\n}\n\nfmt::File::File(fmt::CStringRef path, int oflag) {\n  int mode = S_IRUSR | S_IWUSR;\n#if defined(_WIN32) && !defined(__MINGW32__)\n  fd_ = -1;\n  FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));\n#else\n  FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));\n#endif\n  if (fd_ == -1)\n    FMT_THROW(SystemError(errno, \"cannot open file {}\", path));\n}\n\nfmt::File::~File() FMT_NOEXCEPT {\n  // Don't retry close in case of EINTR!\n  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html\n  if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)\n    fmt::report_system_error(errno, \"cannot close file\");\n}\n\nvoid fmt::File::close() {\n  if (fd_ == -1)\n    return;\n  // Don't retry close in case of EINTR!\n  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html\n  int result = FMT_POSIX_CALL(close(fd_));\n  fd_ = -1;\n  if (result != 0)\n    FMT_THROW(SystemError(errno, \"cannot close file\"));\n}\n\nfmt::LongLong fmt::File::size() const {\n#ifdef _WIN32\n  // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT\n  // is less than 0x0500 as is the case with some default MinGW builds.\n  // Both functions support large file sizes.\n  DWORD size_upper = 0;\n  HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));\n  DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));\n  if (size_lower == INVALID_FILE_SIZE) {\n    DWORD error = GetLastError();\n    if (error != NO_ERROR)\n      FMT_THROW(WindowsError(GetLastError(), \"cannot get file size\"));\n  }\n  fmt::ULongLong long_size = size_upper;\n  return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;\n#else\n  typedef struct stat Stat;\n  Stat file_stat = Stat();\n  if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)\n    FMT_THROW(SystemError(errno, \"cannot get file attributes\"));\n  FMT_STATIC_ASSERT(sizeof(fmt::LongLong) >= sizeof(file_stat.st_size),\n      \"return type of File::size is not large enough\");\n  return file_stat.st_size;\n#endif\n}\n\nstd::size_t fmt::File::read(void *buffer, std::size_t count) {\n  RWResult result = 0;\n  FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));\n  if (result < 0)\n    FMT_THROW(SystemError(errno, \"cannot read from file\"));\n  return internal::to_unsigned(result);\n}\n\nstd::size_t fmt::File::write(const void *buffer, std::size_t count) {\n  RWResult result = 0;\n  FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));\n  if (result < 0)\n    FMT_THROW(SystemError(errno, \"cannot write to file\"));\n  return internal::to_unsigned(result);\n}\n\nfmt::File fmt::File::dup(int fd) {\n  // Don't retry as dup doesn't return EINTR.\n  // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html\n  int new_fd = FMT_POSIX_CALL(dup(fd));\n  if (new_fd == -1)\n    FMT_THROW(SystemError(errno, \"cannot duplicate file descriptor {}\", fd));\n  return File(new_fd);\n}\n\nvoid fmt::File::dup2(int fd) {\n  int result = 0;\n  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));\n  if (result == -1) {\n    FMT_THROW(SystemError(errno,\n      \"cannot duplicate file descriptor {} to {}\", fd_, fd));\n  }\n}\n\nvoid fmt::File::dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT {\n  int result = 0;\n  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));\n  if (result == -1)\n    ec = ErrorCode(errno);\n}\n\nvoid fmt::File::pipe(File &read_end, File &write_end) {\n  // Close the descriptors first to make sure that assignments don't throw\n  // and there are no leaks.\n  read_end.close();\n  write_end.close();\n  int fds[2] = {};\n#ifdef _WIN32\n  // Make the default pipe capacity same as on Linux 2.6.11+.\n  enum { DEFAULT_CAPACITY = 65536 };\n  int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));\n#else\n  // Don't retry as the pipe function doesn't return EINTR.\n  // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html\n  int result = FMT_POSIX_CALL(pipe(fds));\n#endif\n  if (result != 0)\n    FMT_THROW(SystemError(errno, \"cannot create pipe\"));\n  // The following assignments don't throw because read_fd and write_fd\n  // are closed.\n  read_end = File(fds[0]);\n  write_end = File(fds[1]);\n}\n\nfmt::BufferedFile fmt::File::fdopen(const char *mode) {\n  // Don't retry as fdopen doesn't return EINTR.\n  FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode));\n  if (!f)\n    FMT_THROW(SystemError(errno, \"cannot associate stream with file descriptor\"));\n  BufferedFile file(f);\n  fd_ = -1;\n  return file;\n}\n\nlong fmt::getpagesize() {\n#ifdef _WIN32\n  SYSTEM_INFO si;\n  GetSystemInfo(&si);\n  return si.dwPageSize;\n#else\n  long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));\n  if (size < 0)\n    FMT_THROW(SystemError(errno, \"cannot get memory page size\"));\n  return size;\n#endif\n}\n"
  },
  {
    "path": "lib/fmt/fmt_posix.h",
    "content": "/*\n A C++ interface to POSIX functions.\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_POSIX_H_\n#define FMT_POSIX_H_\n\n#if defined(__MINGW32__) || defined(__CYGWIN__)\n// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.\n# undef __STRICT_ANSI__\n#endif\n\n#include <errno.h>\n#include <fcntl.h>   // for O_RDONLY\n#include <locale.h>  // for locale_t\n#include <stdio.h>\n#include <stdlib.h>  // for strtod_l\n\n#include <cstddef>\n\n#if defined(VITA)\n// Under certain circumstances, this function cannot be found on PS Vita.\n// Seems to only happen on a CI build.\n// extern double strtod_l(const char* _a, const char ** _b, locale_t c);\n#endif\n\n#if defined __APPLE__ || defined(__FreeBSD__)\n# include <xlocale.h>  // for LC_NUMERIC_MASK on OS X\n#endif\n\n#include \"fmt_format.h\"\n\n#ifndef FMT_POSIX\n# if defined(_WIN32) && !defined(__MINGW32__)\n// Fix warnings about deprecated symbols.\n#  define FMT_POSIX(call) _##call\n# else\n#  define FMT_POSIX(call) call\n# endif\n#endif\n\n// Calls to system functions are wrapped in FMT_SYSTEM for testability.\n#ifdef FMT_SYSTEM\n# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)\n#else\n# define FMT_SYSTEM(call) call\n# ifdef _WIN32\n// Fix warnings about deprecated symbols.\n#  define FMT_POSIX_CALL(call) ::_##call\n# else\n#  define FMT_POSIX_CALL(call) ::call\n# endif\n#endif\n\n// Retries the expression while it evaluates to error_result and errno\n// equals to EINTR.\n#ifndef _WIN32\n# define FMT_RETRY_VAL(result, expression, error_result) \\\n  do { \\\n    result = (expression); \\\n  } while (result == error_result && errno == EINTR)\n#else\n# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)\n#endif\n\n#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)\n\nnamespace fmt {\n\n// An error code.\nclass ErrorCode {\n private:\n  int value_;\n\n public:\n  explicit ErrorCode(int value = 0) FMT_NOEXCEPT : value_(value) {}\n\n  int get() const FMT_NOEXCEPT { return value_; }\n};\n\n// A buffered file.\nclass BufferedFile {\n private:\n  FILE *file_;\n\n  friend class File;\n\n  explicit BufferedFile(FILE *f) : file_(f) {}\n\n public:\n  // Constructs a BufferedFile object which doesn't represent any file.\n  BufferedFile() FMT_NOEXCEPT : file_(FMT_NULL) {}\n\n  // Destroys the object closing the file it represents if any.\n  FMT_API ~BufferedFile() FMT_NOEXCEPT;\n\n#if !FMT_USE_RVALUE_REFERENCES\n  // Emulate a move constructor and a move assignment operator if rvalue\n  // references are not supported.\n\n private:\n  // A proxy object to emulate a move constructor.\n  // It is private to make it impossible call operator Proxy directly.\n  struct Proxy {\n    FILE *file;\n  };\n\npublic:\n  // A \"move constructor\" for moving from a temporary.\n  BufferedFile(Proxy p) FMT_NOEXCEPT : file_(p.file) {}\n\n  // A \"move constructor\" for moving from an lvalue.\n  BufferedFile(BufferedFile &f) FMT_NOEXCEPT : file_(f.file_) {\n    f.file_ = FMT_NULL;\n  }\n\n  // A \"move assignment operator\" for moving from a temporary.\n  BufferedFile &operator=(Proxy p) {\n    close();\n    file_ = p.file;\n    return *this;\n  }\n\n  // A \"move assignment operator\" for moving from an lvalue.\n  BufferedFile &operator=(BufferedFile &other) {\n    close();\n    file_ = other.file_;\n    other.file_ = FMT_NULL;\n    return *this;\n  }\n\n  // Returns a proxy object for moving from a temporary:\n  //   BufferedFile file = BufferedFile(...);\n  operator Proxy() FMT_NOEXCEPT {\n    Proxy p = {file_};\n    file_ = FMT_NULL;\n    return p;\n  }\n\n#else\n private:\n  FMT_DISALLOW_COPY_AND_ASSIGN(BufferedFile);\n\n public:\n  BufferedFile(BufferedFile &&other) FMT_NOEXCEPT : file_(other.file_) {\n    other.file_ = FMT_NULL;\n  }\n\n  BufferedFile& operator=(BufferedFile &&other) {\n    close();\n    file_ = other.file_;\n    other.file_ = FMT_NULL;\n    return *this;\n  }\n#endif\n\n  // Opens a file.\n  FMT_API BufferedFile(CStringRef filename, CStringRef mode);\n\n  // Closes the file.\n  FMT_API void close();\n\n  // Returns the pointer to a FILE object representing this file.\n  FILE *get() const FMT_NOEXCEPT { return file_; }\n\n  // We place parentheses around fileno to workaround a bug in some versions\n  // of MinGW that define fileno as a macro.\n  FMT_API int (fileno)() const;\n\n  void print(CStringRef format_str, const ArgList &args) {\n    fmt::print(file_, format_str, args);\n  }\n  FMT_VARIADIC(void, print, CStringRef)\n};\n\n// A file. Closed file is represented by a File object with descriptor -1.\n// Methods that are not declared with FMT_NOEXCEPT may throw\n// fmt::SystemError in case of failure. Note that some errors such as\n// closing the file multiple times will cause a crash on Windows rather\n// than an exception. You can get standard behavior by overriding the\n// invalid parameter handler with _set_invalid_parameter_handler.\nclass File {\n private:\n  int fd_;  // File descriptor.\n\n  // Constructs a File object with a given descriptor.\n  explicit File(int fd) : fd_(fd) {}\n\n public:\n  // Possible values for the oflag argument to the constructor.\n  enum {\n    RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.\n    WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.\n    RDWR   = FMT_POSIX(O_RDWR)    // Open for reading and writing.\n  };\n\n  // Constructs a File object which doesn't represent any file.\n  File() FMT_NOEXCEPT : fd_(-1) {}\n\n  // Opens a file and constructs a File object representing this file.\n  FMT_API File(CStringRef path, int oflag);\n\n#if !FMT_USE_RVALUE_REFERENCES\n  // Emulate a move constructor and a move assignment operator if rvalue\n  // references are not supported.\n\n private:\n  // A proxy object to emulate a move constructor.\n  // It is private to make it impossible call operator Proxy directly.\n  struct Proxy {\n    int fd;\n  };\n\n public:\n  // A \"move constructor\" for moving from a temporary.\n  File(Proxy p) FMT_NOEXCEPT : fd_(p.fd) {}\n\n  // A \"move constructor\" for moving from an lvalue.\n  File(File &other) FMT_NOEXCEPT : fd_(other.fd_) {\n    other.fd_ = -1;\n  }\n\n  // A \"move assignment operator\" for moving from a temporary.\n  File &operator=(Proxy p) {\n    close();\n    fd_ = p.fd;\n    return *this;\n  }\n\n  // A \"move assignment operator\" for moving from an lvalue.\n  File &operator=(File &other) {\n    close();\n    fd_ = other.fd_;\n    other.fd_ = -1;\n    return *this;\n  }\n\n  // Returns a proxy object for moving from a temporary:\n  //   File file = File(...);\n  operator Proxy() FMT_NOEXCEPT {\n    Proxy p = {fd_};\n    fd_ = -1;\n    return p;\n  }\n\n#else\n private:\n  FMT_DISALLOW_COPY_AND_ASSIGN(File);\n\n public:\n  File(File &&other) FMT_NOEXCEPT : fd_(other.fd_) {\n    other.fd_ = -1;\n  }\n\n  File& operator=(File &&other) {\n    close();\n    fd_ = other.fd_;\n    other.fd_ = -1;\n    return *this;\n  }\n#endif\n\n  // Destroys the object closing the file it represents if any.\n  FMT_API ~File() FMT_NOEXCEPT;\n\n  // Returns the file descriptor.\n  int descriptor() const FMT_NOEXCEPT { return fd_; }\n\n  // Closes the file.\n  FMT_API void close();\n\n  // Returns the file size. The size has signed type for consistency with\n  // stat::st_size.\n  FMT_API LongLong size() const;\n\n  // Attempts to read count bytes from the file into the specified buffer.\n  FMT_API std::size_t read(void *buffer, std::size_t count);\n\n  // Attempts to write count bytes from the specified buffer to the file.\n  FMT_API std::size_t write(const void *buffer, std::size_t count);\n\n  // Duplicates a file descriptor with the dup function and returns\n  // the duplicate as a file object.\n  FMT_API static File dup(int fd);\n\n  // Makes fd be the copy of this file descriptor, closing fd first if\n  // necessary.\n  FMT_API void dup2(int fd);\n\n  // Makes fd be the copy of this file descriptor, closing fd first if\n  // necessary.\n  FMT_API void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT;\n\n  // Creates a pipe setting up read_end and write_end file objects for reading\n  // and writing respectively.\n  FMT_API static void pipe(File &read_end, File &write_end);\n\n  // Creates a BufferedFile object associated with this file and detaches\n  // this File object from the file.\n  FMT_API BufferedFile fdopen(const char *mode);\n};\n\n// Returns the memory page size.\nlong getpagesize();\n\n#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \\\n    !defined(__ANDROID__) && !defined(__CYGWIN__) && \\\n    !defined(VITA) && \\\n    !defined(__HAIKU__) && \\\n    !defined(__SWITCH__) && !defined(__3DS__) && !defined(__WII__) && !defined(__WIIU__) && \\\n    !defined(PGE_MIN_PORT)\n# define FMT_LOCALE\n#endif\n\n\n#ifdef FMT_LOCALE\n\n// A \"C\" numeric locale.\nclass Locale {\n private:\n# ifdef _MSC_VER\n  typedef _locale_t locale_t;\n\n  enum { LC_NUMERIC_MASK = LC_NUMERIC };\n\n  static locale_t newlocale(int category_mask, const char *locale, locale_t) {\n    return _create_locale(category_mask, locale);\n  }\n\n  static void freelocale(locale_t locale) {\n    _free_locale(locale);\n  }\n\n  static double strtod_l(const char *nptr, char **endptr, _locale_t locale) {\n    return _strtod_l(nptr, endptr, locale);\n  }\n# endif\n\n  locale_t locale_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(Locale);\n\n public:\n  typedef locale_t Type;\n\n  Locale() : locale_(newlocale(LC_NUMERIC_MASK, \"C\", FMT_NULL)) {\n    if (!locale_)\n      FMT_THROW(fmt::SystemError(errno, \"cannot create locale\"));\n  }\n  ~Locale() { freelocale(locale_); }\n\n  Type get() const { return locale_; }\n\n  // Converts string to floating-point number and advances str past the end\n  // of the parsed input.\n  double strtod(const char *&str) const {\n    char *end = FMT_NULL;\n\n    double result = strtod_l(str, &end, locale_);\n    str = end;\n    return result;\n  }\n};\n#endif  // FMT_LOCALE\n}  // namespace fmt\n\n#if !FMT_USE_RVALUE_REFERENCES\nnamespace std {\n// For compatibility with C++98.\ninline fmt::BufferedFile &move(fmt::BufferedFile &f) { return f; }\ninline fmt::File &move(fmt::File &f) { return f; }\n}\n#endif\n\n#endif  // FMT_POSIX_H_\n"
  },
  {
    "path": "lib/fmt/fmt_printf.cpp",
    "content": "/*\n Formatting library for C++\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#include \"fmt_format.h\"\n#include \"fmt_printf.h\"\n\nnamespace fmt {\n\ntemplate <typename Char>\nvoid printf(BasicWriter<Char> &w, BasicCStringRef<Char> format, ArgList args);\n\nFMT_FUNC int fprintf(std::FILE *f, CStringRef format, ArgList args) {\n  MemoryWriter w;\n  printf(w, format, args);\n  std::size_t size = w.size();\n  return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast<int>(size);\n}\n\n#ifndef FMT_HEADER_ONLY\n\ntemplate void PrintfFormatter<char>::format(CStringRef format);\ntemplate void PrintfFormatter<wchar_t>::format(WCStringRef format);\n\n#endif  // FMT_HEADER_ONLY\n\n}  // namespace fmt\n"
  },
  {
    "path": "lib/fmt/fmt_printf.h",
    "content": "/*\n Formatting library for C++\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_PRINTF_H_\n#define FMT_PRINTF_H_\n\n#include <algorithm>  // std::fill_n\n#include <limits>     // std::numeric_limits\n\n#include \"fmt_ostream.h\"\n\nnamespace fmt {\nnamespace internal {\n\n// Checks if a value fits in int - used to avoid warnings about comparing\n// signed and unsigned integers.\ntemplate <bool IsSigned>\nstruct IntChecker {\n  template <typename T>\n  static bool fits_in_int(T value) {\n    unsigned max = std::numeric_limits<int>::max();\n    return value <= max;\n  }\n  static bool fits_in_int(bool) { return true; }\n};\n\ntemplate <>\nstruct IntChecker<true> {\n  template <typename T>\n  static bool fits_in_int(T value) {\n    return value >= std::numeric_limits<int>::min() &&\n           value <= std::numeric_limits<int>::max();\n  }\n  static bool fits_in_int(int) { return true; }\n};\n\nclass PrecisionHandler : public ArgVisitor<PrecisionHandler, int> {\n public:\n  void report_unhandled_arg() {\n    FMT_THROW(FormatError(\"precision is not integer\"));\n  }\n\n  template <typename T>\n  int visit_any_int(T value) {\n    if (!IntChecker<std::numeric_limits<T>::is_signed>::fits_in_int(value))\n      FMT_THROW(FormatError(\"number is too big\"));\n    return static_cast<int>(value);\n  }\n};\n\n// IsZeroInt::visit(arg) returns true iff arg is a zero integer.\nclass IsZeroInt : public ArgVisitor<IsZeroInt, bool> {\n public:\n  template <typename T>\n  bool visit_any_int(T value) { return value == 0; }\n};\n\n// returns the default type for format specific \"%s\"\nclass DefaultType : public ArgVisitor<DefaultType, char> {\n public:\n  char visit_char(int) { return 'c'; }\n\n  char visit_bool(bool) { return 's'; }\n\n  char visit_pointer(const void *) { return 'p'; }\n\n  template <typename T>\n  char visit_any_int(T) { return 'd'; }\n\n  template <typename T>\n  char visit_any_double(T) { return 'g'; }\n\n  char visit_unhandled_arg() { return 's'; }\n};\n\ntemplate <typename T, typename U>\nstruct is_same {\n  enum { value = 0 };\n};\n\ntemplate <typename T>\nstruct is_same<T, T> {\n  enum { value = 1 };\n};\n\n// An argument visitor that converts an integer argument to T for printf,\n// if T is an integral type. If T is void, the argument is converted to\n// corresponding signed or unsigned type depending on the type specifier:\n// 'd' and 'i' - signed, other - unsigned)\ntemplate <typename T = void>\nclass ArgConverter : public ArgVisitor<ArgConverter<T>, void> {\n private:\n  internal::Arg &arg_;\n  wchar_t type_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter);\n\n public:\n  ArgConverter(internal::Arg &arg, wchar_t type)\n    : arg_(arg), type_(type) {}\n\n  void visit_bool(bool value) {\n    if (type_ != 's')\n      visit_any_int(value);\n  }\n\n  void visit_char(int value) {\n    if (type_ != 's')\n      visit_any_int(value);\n  }\n\n  template <typename U>\n  void visit_any_int(U value) {\n    bool is_signed = type_ == 'd' || type_ == 'i';\n    if (type_ == 's') {\n      is_signed = std::numeric_limits<U>::is_signed;\n    }\n\n    using internal::Arg;\n    typedef typename internal::Conditional<\n        is_same<T, void>::value, U, T>::type TargetType;\n    if (const_check(sizeof(TargetType) <= sizeof(int))) {\n      // Extra casts are used to silence warnings.\n      if (is_signed) {\n        arg_.type = Arg::INT;\n        arg_.int_value = static_cast<int>(static_cast<TargetType>(value));\n      } else {\n        arg_.type = Arg::UINT;\n        typedef typename internal::MakeUnsigned<TargetType>::Type Unsigned;\n        arg_.uint_value = static_cast<unsigned>(static_cast<Unsigned>(value));\n      }\n    } else {\n      if (is_signed) {\n        arg_.type = Arg::LONG_LONG;\n        // glibc's printf doesn't sign extend arguments of smaller types:\n        //   std::printf(\"%lld\", -42);  // prints \"4294967254\"\n        // but we don't have to do the same because it's a UB.\n        arg_.long_long_value = static_cast<LongLong>(value);\n      } else {\n        arg_.type = Arg::ULONG_LONG;\n        arg_.ulong_long_value =\n            static_cast<typename internal::MakeUnsigned<U>::Type>(value);\n      }\n    }\n  }\n};\n\n// Converts an integer argument to char for printf.\nclass CharConverter : public ArgVisitor<CharConverter, void> {\n private:\n  internal::Arg &arg_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter);\n\n public:\n  explicit CharConverter(internal::Arg &arg) : arg_(arg) {}\n\n  template <typename T>\n  void visit_any_int(T value) {\n    arg_.type = internal::Arg::CHAR;\n    arg_.int_value = static_cast<char>(value);\n  }\n};\n\n// Checks if an argument is a valid printf width specifier and sets\n// left alignment if it is negative.\nclass WidthHandler : public ArgVisitor<WidthHandler, unsigned> {\n private:\n  FormatSpec &spec_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler);\n\n public:\n  explicit WidthHandler(FormatSpec &spec) : spec_(spec) {}\n\n  void report_unhandled_arg() {\n    FMT_THROW(FormatError(\"width is not integer\"));\n  }\n\n  template <typename T>\n  unsigned visit_any_int(T value) {\n    typedef typename internal::IntTraits<T>::MainType UnsignedType;\n    UnsignedType width = static_cast<UnsignedType>(value);\n    if (internal::is_negative(value)) {\n      spec_.align_ = ALIGN_LEFT;\n      width = 0 - width;\n    }\n    unsigned int_max = std::numeric_limits<int>::max();\n    if (width > int_max)\n      FMT_THROW(FormatError(\"number is too big\"));\n    return static_cast<unsigned>(width);\n  }\n};\n}  // namespace internal\n\n/**\n  \\rst\n  A ``printf`` argument formatter based on the `curiously recurring template\n  pattern <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_.\n\n  To use `~fmt::BasicPrintfArgFormatter` define a subclass that implements some\n  or all of the visit methods with the same signatures as the methods in\n  `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`.\n  Pass the subclass as the *Impl* template parameter. When a formatting\n  function processes an argument, it will dispatch to a visit method\n  specific to the argument type. For example, if the argument type is\n  ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass\n  will be called. If the subclass doesn't contain a method with this signature,\n  then a corresponding method of `~fmt::BasicPrintfArgFormatter` or its\n  superclass will be called.\n  \\endrst\n */\ntemplate <typename Impl, typename Char, typename Spec>\nclass BasicPrintfArgFormatter :\n    public internal::ArgFormatterBase<Impl, Char, Spec> {\n private:\n  void write_null_pointer() {\n    this->spec().type_ = 0;\n    this->write(\"(nil)\");\n  }\n\n  typedef internal::ArgFormatterBase<Impl, Char, Spec> Base;\n\n public:\n  /**\n    \\rst\n    Constructs an argument formatter object.\n    *writer* is a reference to the output writer and *spec* contains format\n    specifier information for standard argument types.\n    \\endrst\n   */\n  BasicPrintfArgFormatter(BasicWriter<Char> &w, Spec &s)\n  : internal::ArgFormatterBase<Impl, Char, Spec>(w, s) {}\n\n  /** Formats an argument of type ``bool``. */\n  void visit_bool(bool value) {\n    Spec &fmt_spec = this->spec();\n    if (fmt_spec.type_ != 's')\n      return this->visit_any_int(value);\n    fmt_spec.type_ = 0;\n    this->write(value);\n  }\n\n  /** Formats a character. */\n  void visit_char(int value) {\n    const Spec &fmt_spec = this->spec();\n    BasicWriter<Char> &w = this->writer();\n    if (fmt_spec.type_ && fmt_spec.type_ != 'c')\n      w.write_int(value, fmt_spec);\n    typedef typename BasicWriter<Char>::CharPtr CharPtr;\n    CharPtr out = CharPtr();\n    if (fmt_spec.width_ > 1) {\n      Char fill = ' ';\n      out = w.grow_buffer(fmt_spec.width_);\n      if (fmt_spec.align_ != ALIGN_LEFT) {\n        std::fill_n(out, fmt_spec.width_ - 1, fill);\n        out += fmt_spec.width_ - 1;\n      } else {\n        std::fill_n(out + 1, fmt_spec.width_ - 1, fill);\n      }\n    } else {\n      out = w.grow_buffer(1);\n    }\n    *out = static_cast<Char>(value);\n  }\n\n  /** Formats a null-terminated C string. */\n  void visit_cstring(const char *value) {\n    if (value)\n      Base::visit_cstring(value);\n    else if (this->spec().type_ == 'p')\n      write_null_pointer();\n    else\n      this->write(\"(null)\");\n  }\n\n  /** Formats a pointer. */\n  void visit_pointer(const void *value) {\n    if (value)\n      return Base::visit_pointer(value);\n    this->spec().type_ = 0;\n    write_null_pointer();\n  }\n\n  /** Formats an argument of a custom (user-defined) type. */\n  void visit_custom(internal::Arg::CustomValue c) {\n    BasicFormatter<Char> formatter(ArgList(), this->writer());\n    const Char format_str[] = {'}', 0};\n    const Char *format = format_str;\n    c.format(&formatter, c.value, &format);\n  }\n};\n\n/** The default printf argument formatter. */\ntemplate <typename Char>\nclass PrintfArgFormatter :\n    public BasicPrintfArgFormatter<PrintfArgFormatter<Char>, Char, FormatSpec> {\n public:\n  /** Constructs an argument formatter object. */\n  PrintfArgFormatter(BasicWriter<Char> &w, FormatSpec &s)\n  : BasicPrintfArgFormatter<PrintfArgFormatter<Char>, Char, FormatSpec>(w, s) {}\n};\n\n/** This template formats data and writes the output to a writer. */\ntemplate <typename Char, typename ArgFormatter = PrintfArgFormatter<Char> >\nclass PrintfFormatter : private internal::FormatterBase {\n private:\n  BasicWriter<Char> &writer_;\n\n  void parse_flags(FormatSpec &spec, const Char *&s);\n\n  // Returns the argument with specified index or, if arg_index is equal\n  // to the maximum unsigned value, the next argument.\n  internal::Arg get_arg(\n      const Char *s,\n      unsigned arg_index = (std::numeric_limits<unsigned>::max)());\n\n  // Parses argument index, flags and width and returns the argument index.\n  unsigned parse_header(const Char *&s, FormatSpec &spec);\n\n public:\n  /**\n   \\rst\n   Constructs a ``PrintfFormatter`` object. References to the arguments and\n   the writer are stored in the formatter object so make sure they have\n   appropriate lifetimes.\n   \\endrst\n   */\n  explicit PrintfFormatter(const ArgList &al, BasicWriter<Char> &w)\n    : FormatterBase(al), writer_(w) {}\n\n  /** Formats stored arguments and writes the output to the writer. */\n  void format(BasicCStringRef<Char> format_str);\n};\n\ntemplate <typename Char, typename AF>\nvoid PrintfFormatter<Char, AF>::parse_flags(FormatSpec &spec, const Char *&s) {\n  for (;;) {\n    switch (*s++) {\n      case '-':\n        spec.align_ = ALIGN_LEFT;\n        break;\n      case '+':\n        spec.flags_ |= SIGN_FLAG | PLUS_FLAG;\n        break;\n      case '0':\n        spec.fill_ = '0';\n        break;\n      case ' ':\n        spec.flags_ |= SIGN_FLAG;\n        break;\n      case '#':\n        spec.flags_ |= HASH_FLAG;\n        break;\n      default:\n        --s;\n        return;\n    }\n  }\n}\n\ntemplate <typename Char, typename AF>\ninternal::Arg PrintfFormatter<Char, AF>::get_arg(const Char *s,\n                                                 unsigned arg_index) {\n  (void)s;\n  const char *error = FMT_NULL;\n  internal::Arg arg = arg_index == std::numeric_limits<unsigned>::max() ?\n    next_arg(error) : FormatterBase::get_arg(arg_index - 1, error);\n  if (error)\n    FMT_THROW(FormatError(!*s ? \"invalid format string\" : error));\n  return arg;\n}\n\ntemplate <typename Char, typename AF>\nunsigned PrintfFormatter<Char, AF>::parse_header(\n  const Char *&s, FormatSpec &spec) {\n  unsigned arg_index = std::numeric_limits<unsigned>::max();\n  Char c = *s;\n  if (c >= '0' && c <= '9') {\n    // Parse an argument index (if followed by '$') or a width possibly\n    // preceded with '0' flag(s).\n    unsigned value = internal::parse_nonnegative_int(s);\n    if (*s == '$') {  // value is an argument index\n      ++s;\n      arg_index = value;\n    } else {\n      if (c == '0')\n        spec.fill_ = '0';\n      if (value != 0) {\n        // Nonzero value means that we parsed width and don't need to\n        // parse it or flags again, so return now.\n        spec.width_ = value;\n        return arg_index;\n      }\n    }\n  }\n  parse_flags(spec, s);\n  // Parse width.\n  if (*s >= '0' && *s <= '9') {\n    spec.width_ = internal::parse_nonnegative_int(s);\n  } else if (*s == '*') {\n    ++s;\n    spec.width_ = internal::WidthHandler(spec).visit(get_arg(s));\n  }\n  return arg_index;\n}\n\ntemplate <typename Char, typename AF>\nvoid PrintfFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {\n  const Char *start = format_str.c_str();\n  const Char *s = start;\n  while (*s) {\n    Char c = *s++;\n    if (c != '%') continue;\n    if (*s == c) {\n      write(writer_, start, s);\n      start = ++s;\n      continue;\n    }\n    write(writer_, start, s - 1);\n\n    FormatSpec spec;\n    spec.align_ = ALIGN_RIGHT;\n\n    // Parse argument index, flags and width.\n    unsigned arg_index = parse_header(s, spec);\n\n    // Parse precision.\n    if (*s == '.') {\n      ++s;\n      if ('0' <= *s && *s <= '9') {\n        spec.precision_ = static_cast<int>(internal::parse_nonnegative_int(s));\n      } else if (*s == '*') {\n        ++s;\n        spec.precision_ = internal::PrecisionHandler().visit(get_arg(s));\n      } else {\n        spec.precision_ = 0;\n      }\n    }\n\n    using internal::Arg;\n    Arg arg = get_arg(s, arg_index);\n    if (spec.flag(HASH_FLAG) && internal::IsZeroInt().visit(arg))\n      spec.flags_ &= ~internal::to_unsigned<int>(HASH_FLAG);\n    if (spec.fill_ == '0') {\n      if (arg.type <= Arg::LAST_NUMERIC_TYPE)\n        spec.align_ = ALIGN_NUMERIC;\n      else\n        spec.fill_ = ' ';  // Ignore '0' flag for non-numeric types.\n    }\n\n    // Parse length and convert the argument to the required type.\n    using internal::ArgConverter;\n    switch (*s++) {\n    case 'h':\n      if (*s == 'h')\n        ArgConverter<signed char>(arg, *++s).visit(arg);\n      else\n        ArgConverter<short>(arg, *s).visit(arg);\n      break;\n    case 'l':\n      if (*s == 'l')\n        ArgConverter<fmt::LongLong>(arg, *++s).visit(arg);\n      else\n        ArgConverter<long>(arg, *s).visit(arg);\n      break;\n    case 'j':\n      ArgConverter<intmax_t>(arg, *s).visit(arg);\n      break;\n    case 'z':\n      ArgConverter<std::size_t>(arg, *s).visit(arg);\n      break;\n    case 't':\n      ArgConverter<std::ptrdiff_t>(arg, *s).visit(arg);\n      break;\n    case 'L':\n      // printf produces garbage when 'L' is omitted for long double, no\n      // need to do the same.\n      break;\n    default:\n      --s;\n      ArgConverter<void>(arg, *s).visit(arg);\n    }\n\n    // Parse type.\n    if (!*s)\n      FMT_THROW(FormatError(\"invalid format string\"));\n    spec.type_ = static_cast<char>(*s++);\n\n    if (spec.type_ == 's') {\n      // set the format type to the default if 's' is specified\n      spec.type_ = internal::DefaultType().visit(arg);\n    }\n\n    if (arg.type <= Arg::LAST_INTEGER_TYPE) {\n      // Normalize type.\n      switch (spec.type_) {\n      case 'i': case 'u':\n        spec.type_ = 'd';\n        break;\n      case 'c':\n        // TODO: handle wchar_t\n        internal::CharConverter(arg).visit(arg);\n        break;\n      }\n    }\n\n    start = s;\n\n    // Format argument.\n    AF(writer_, spec).visit(arg);\n  }\n  write(writer_, start, s);\n}\n\ninline void printf(Writer &w, CStringRef format, ArgList args) {\n  PrintfFormatter<char>(args, w).format(format);\n}\nFMT_VARIADIC(void, printf, Writer &, CStringRef)\n\ninline void printf(WWriter &w, WCStringRef format, ArgList args) {\n  PrintfFormatter<wchar_t>(args, w).format(format);\n}\nFMT_VARIADIC(void, printf, WWriter &, WCStringRef)\n\n/**\n  \\rst\n  Formats arguments and returns the result as a string.\n\n  **Example**::\n\n    std::string message = fmt::sprintf(\"The answer is %d\", 42);\n  \\endrst\n*/\ninline std::string sprintf(CStringRef format, ArgList args) {\n  MemoryWriter w;\n  printf(w, format, args);\n  return w.str();\n}\nFMT_VARIADIC(std::string, sprintf, CStringRef)\n\ninline std::wstring sprintf(WCStringRef format, ArgList args) {\n  WMemoryWriter w;\n  printf(w, format, args);\n  return w.str();\n}\nFMT_VARIADIC_W(std::wstring, sprintf, WCStringRef)\n\n/**\n  \\rst\n  Prints formatted data to the file *f*.\n\n  **Example**::\n\n    fmt::fprintf(stderr, \"Don't %s!\", \"panic\");\n  \\endrst\n */\nFMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args);\nFMT_VARIADIC(int, fprintf, std::FILE *, CStringRef)\n\n/**\n  \\rst\n  Prints formatted data to ``stdout``.\n\n  **Example**::\n\n    fmt::printf(\"Elapsed time: %.2f seconds\", 1.23);\n  \\endrst\n */\ninline int printf(CStringRef format, ArgList args) {\n  return fprintf(stdout, format, args);\n}\nFMT_VARIADIC(int, printf, CStringRef)\n\n/**\n  \\rst\n  Prints formatted data to the stream *os*.\n\n  **Example**::\n\n    fprintf(cerr, \"Don't %s!\", \"panic\");\n  \\endrst\n */\ninline int fprintf(std::ostream &os, CStringRef format_str, ArgList args) {\n  MemoryWriter w;\n  printf(w, format_str, args);\n  internal::write(os, w);\n  return static_cast<int>(w.size());\n}\nFMT_VARIADIC(int, fprintf, std::ostream &, CStringRef)\n}  // namespace fmt\n\n#ifdef FMT_HEADER_ONLY\n# include \"printf.cc\"\n#endif\n\n#endif  // FMT_PRINTF_H_\n"
  },
  {
    "path": "lib/fmt/fmt_qformat.h",
    "content": "/*\n    A small wrapper from QString's %1....%99 arguments into fmt::format arguments\n*/\n\n#ifndef FMT_QFORMAT_H\n#define FMT_QFORMAT_H\n\n#include \"fmt_format.h\"\n\nnamespace fmt\n{\n\ninline uint32_t getNum(char* &c)\n{\n    char numbuff[3] = {0, 0, 0};\n    size_t len = 0;\n    uint32_t num = 0;\n    while((len < 2) && isdigit(*c) && *c != '\\0')\n        numbuff[len++] = *(c++);\n    if(len == 1)\n        num = uint32_t(numbuff[0] - '0');\n    else if(len == 2)\n        num = uint32_t(numbuff[0] - '0') + uint32_t(numbuff[1] - '0') * 10;\n    return num;\n}\n\ntemplate <typename... Args>\nstd::string qformat(CStringRef format_str, const Args & ... args)\n{\n    std::string fmt(format_str.c_str());\n\n    for(size_t i = fmt.size() - 1; true; i--)\n    {\n        if(fmt[i] == '%')\n        {\n            char* c = &fmt[i + 1];\n            uint32_t num = getNum(c);\n            if(c == &fmt[i + 1])\n                continue;\n            if(num == 0)\n                continue;\n            num--;\n            fmt.erase(i, size_t(c - &fmt[i - 1]));\n            fmt.insert(i, \"{\" + std::to_string(num) + \"}\");\n        }\n        if(i == 0)\n            break;\n    }\n\n    std::string out;\n    try\n    {\n        out = format(fmt, std::forward<const Args&>(args)...);\n    }\n    catch(const FormatError &e)\n    {\n        out.append(e.what());\n        out.append(\" [\");\n        out.append(fmt);\n        out.push_back(']');\n    }\n    return out;\n}\n\n}\n#endif // FMT_QFORMAT_H\n"
  },
  {
    "path": "lib/fmt/fmt_string.h",
    "content": "/*\n Formatting library for C++ - string utilities\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifdef FMT_INCLUDE\n# error \"Add the fmt's parent directory and not fmt itself to includes.\"\n#endif\n\n#ifndef FMT_STRING_H_\n#define FMT_STRING_H_\n\n#include \"fmt_format.h\"\n\nnamespace fmt {\n\nnamespace internal {\n\n// A buffer that stores data in ``std::basic_string``.\ntemplate <typename Char, typename Allocator = std::allocator<Char> >\nclass StringBuffer : public Buffer<Char> {\n public:\n  typedef std::basic_string<Char, std::char_traits<Char>, Allocator> StringType;\n\n private:\n  StringType data_;\n\n protected:\n  virtual void grow(std::size_t size) FMT_OVERRIDE {\n    data_.resize(size);\n    this->ptr_ = &data_[0];\n    this->capacity_ = size;\n  }\n\n public:\n  explicit StringBuffer(const Allocator &allocator = Allocator())\n  : data_(allocator) {}\n\n  // Moves the data to ``str`` clearing the buffer.\n  void move_to(StringType &str) {\n    data_.resize(this->size_);\n    str.swap(data_);\n    this->capacity_ = this->size_ = 0;\n    this->ptr_ = FMT_NULL;\n  }\n};\n}  // namespace internal\n\n/**\n  \\rst\n  This class template provides operations for formatting and writing data\n  into a character stream. The output is stored in a ``std::basic_string``\n  that grows dynamically.\n\n  You can use one of the following typedefs for common character types\n  and the standard allocator:\n\n  +---------------+----------------------------+\n  | Type          | Definition                 |\n  +===============+============================+\n  | StringWriter  | BasicStringWriter<char>    |\n  +---------------+----------------------------+\n  | WStringWriter | BasicStringWriter<wchar_t> |\n  +---------------+----------------------------+\n\n  **Example**::\n\n     StringWriter out;\n     out << \"The answer is \" << 42 << \"\\n\";\n\n  This will write the following output to the ``out`` object:\n\n  .. code-block:: none\n\n     The answer is 42\n\n  The output can be moved to a ``std::basic_string`` with ``out.move_to()``.\n  \\endrst\n */\ntemplate <typename Char, typename Allocator = std::allocator<Char> >\nclass BasicStringWriter : public BasicWriter<Char> {\n private:\n  internal::StringBuffer<Char, Allocator> buffer_;\n\n public:\n  /**\n    \\rst\n    Constructs a :class:`fmt::BasicStringWriter` object.\n    \\endrst\n   */\n  explicit BasicStringWriter(const Allocator &allocator = Allocator())\n  : BasicWriter<Char>(buffer_), buffer_(allocator) {}\n\n  /**\n    \\rst\n    Moves the buffer content to *str* clearing the buffer.\n    \\endrst\n   */\n  void move_to(std::basic_string<Char, std::char_traits<Char>, Allocator> &str) {\n    buffer_.move_to(str);\n  }\n};\n\ntypedef BasicStringWriter<char> StringWriter;\ntypedef BasicStringWriter<wchar_t> WStringWriter;\n\n/**\n  \\rst\n  Converts *value* to ``std::string`` using the default format for type *T*.\n\n  **Example**::\n\n    #include \"fmt/string.h\"\n\n    std::string answer = fmt::to_string(42);\n  \\endrst\n */\ntemplate <typename T>\nstd::string to_string(const T &value) {\n  fmt::MemoryWriter w;\n  w << value;\n  return w.str();\n}\n\n/**\n  \\rst\n  Converts *value* to ``std::wstring`` using the default format for type *T*.\n\n  **Example**::\n\n    #include \"fmt/string.h\"\n\n    std::wstring answer = fmt::to_wstring(42);\n  \\endrst\n */\ntemplate <typename T>\nstd::wstring to_wstring(const T &value) {\n  fmt::WMemoryWriter w;\n  w << value;\n  return w.str();\n}\n}\n\n#endif  // FMT_STRING_H_\n"
  },
  {
    "path": "lib/fmt/fmt_time.h",
    "content": "/*\n Formatting library for C++ - time formatting\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_TIME_H_\n#define FMT_TIME_H_\n\n#include \"fmt_format.h\"\n#include <ctime>\n\n#ifdef _MSC_VER\n# pragma warning(push)\n# pragma warning(disable: 4702)  // unreachable code\n# pragma warning(disable: 4996)  // \"deprecated\" functions\n#endif\n\nnamespace fmt {\ntemplate <typename ArgFormatter>\nvoid format_arg(BasicFormatter<char, ArgFormatter> &f,\n                const char *&format_str, const std::tm &tm) {\n  if (*format_str == ':')\n    ++format_str;\n  const char *end = format_str;\n  while (*end && *end != '}')\n    ++end;\n  if (*end != '}')\n    FMT_THROW(FormatError(\"missing '}' in format string\"));\n  internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> format;\n  format.append(format_str, end + 1);\n  format[format.size() - 1] = '\\0';\n  Buffer<char> &buffer = f.writer().buffer();\n  std::size_t start = buffer.size();\n  for (;;) {\n    std::size_t size = buffer.capacity() - start;\n    std::size_t count = std::strftime(&buffer[start], size, &format[0], &tm);\n    if (count != 0) {\n      buffer.resize(start + count);\n      break;\n    }\n    if (size >= format.size() * 256) {\n      // If the buffer is 256 times larger than the format string, assume\n      // that `strftime` gives an empty result. There doesn't seem to be a\n      // better way to distinguish the two cases:\n      // https://github.com/fmtlib/fmt/issues/367\n      break;\n    }\n    const std::size_t MIN_GROWTH = 10;\n    buffer.reserve(buffer.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));\n  }\n  format_str = end + 1;\n}\n\nnamespace internal{\ninline Null<> localtime_r(...) { return Null<>(); }\ninline Null<> localtime_s(...) { return Null<>(); }\ninline Null<> gmtime_r(...) { return Null<>(); }\ninline Null<> gmtime_s(...) { return Null<>(); }\n}\n\n// Thread-safe replacement for std::localtime\ninline std::tm localtime(std::time_t time) {\n  struct LocalTime {\n    std::time_t time_;\n    std::tm tm_;\n\n    LocalTime(std::time_t t): time_(t) {}\n\n    bool run() {\n      using namespace fmt::internal;\n      return handle(localtime_r(&time_, &tm_));\n    }\n\n    bool handle(std::tm *tm) { return tm != FMT_NULL; }\n\n    bool handle(internal::Null<>) {\n      using namespace fmt::internal;\n      return fallback(localtime_s(&tm_, &time_));\n    }\n\n    bool fallback(int res) { return res == 0; }\n\n    bool fallback(internal::Null<>) {\n      using namespace fmt::internal;\n      std::tm *tm = std::localtime(&time_);\n      if (tm) tm_ = *tm;\n      return tm != FMT_NULL;\n    }\n  };\n  LocalTime lt(time);\n  if (lt.run())\n    return lt.tm_;\n  // Too big time values may be unsupported.\n  FMT_THROW(fmt::FormatError(\"time_t value out of range\"));\n  return std::tm();\n}\n\n// Thread-safe replacement for std::gmtime\ninline std::tm gmtime(std::time_t time) {\n  struct GMTime {\n    std::time_t time_;\n    std::tm tm_;\n\n    GMTime(std::time_t t): time_(t) {}\n\n    bool run() {\n      using namespace fmt::internal;\n      return handle(gmtime_r(&time_, &tm_));\n    }\n\n    bool handle(std::tm *tm) { return tm != FMT_NULL; }\n\n    bool handle(internal::Null<>) {\n      using namespace fmt::internal;\n      return fallback(gmtime_s(&tm_, &time_));\n    }\n\n    bool fallback(int res) { return res == 0; }\n\n    bool fallback(internal::Null<>) {\n      std::tm *tm = std::gmtime(&time_);\n      if (tm != FMT_NULL) tm_ = *tm;\n      return tm != FMT_NULL;\n    }\n  };\n  GMTime gt(time);\n  if (gt.run())\n    return gt.tm_;\n  // Too big time values may be unsupported.\n  FMT_THROW(fmt::FormatError(\"time_t value out of range\"));\n  return std::tm();\n}\n} //namespace fmt\n\n#ifdef _MSC_VER\n# pragma warning(pop)\n#endif\n\n#endif  // FMT_TIME_H_\n"
  },
  {
    "path": "lib/fmt/orig/CMakeLists.txt",
    "content": "# Define the fmt library, its includes and the needed defines.\n# *.cc are added to FMT_HEADERS for the header-only configuration.\nset(FMT_HEADERS container.h format.h format.cc ostream.h ostream.cc printf.h\n                printf.cc string.h time.h)\nif (HAVE_OPEN)\n  set(FMT_HEADERS ${FMT_HEADERS} posix.h)\n  set(FMT_SOURCES ${FMT_SOURCES} posix.cc)\nendif ()\n\nadd_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} ../README.rst ../ChangeLog.rst)\nadd_library(fmt::fmt ALIAS fmt)\n\n# Starting with cmake 3.1 the CXX_STANDARD property can be used instead.\n# Note: Don't make -std=c++11 public or interface, since it breaks projects\n# that use C++14.\ntarget_compile_options(fmt PRIVATE ${CPP11_FLAG})\nif (FMT_PEDANTIC)\n  target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})\nendif ()\n\ntarget_include_directories(fmt PUBLIC\n  $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>\n  $<INSTALL_INTERFACE:include>)\n\nset_target_properties(fmt PROPERTIES\n  VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR})\n\nif (BUILD_SHARED_LIBS)\n  if (UNIX AND NOT APPLE)\n    # Fix rpmlint warning:\n    # unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.\n    target_link_libraries(fmt -Wl,--as-needed)\n  endif ()\n  target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)\nendif ()\n\n#------------------------------------------------------------------------------\n# additionally define a header only library when cmake is new enough\nif (CMAKE_VERSION VERSION_GREATER 3.1.0 OR CMAKE_VERSION VERSION_EQUAL 3.1.0)\n  add_library(fmt-header-only INTERFACE)\n  add_library(fmt::fmt-header-only ALIAS fmt-header-only)\n\n  target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)\n\n  target_include_directories(fmt-header-only INTERFACE\n    $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>\n    $<INSTALL_INTERFACE:include>)\nendif ()\n\n# Install targets.\nif (FMT_INSTALL)\n  include(GNUInstallDirs)\n  include(CMakePackageConfigHelpers)\n  set(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING\n    \"Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.\")\n  set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)\n  set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)\n  set(targets_export_name fmt-targets)\n\n  set (INSTALL_TARGETS fmt)\n  if (TARGET fmt-header-only)\n    set(INSTALL_TARGETS ${INSTALL_TARGETS} fmt-header-only)\n  endif ()\n\n  set(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING\n    \"Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.\")\n\n  set(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING\n    \"Installation directory for include files, relative to ${CMAKE_INSTALL_PREFIX}.\")\n\n  # Generate the version, config and target files into the build directory.\n  write_basic_package_version_file(\n    ${version_config}\n    VERSION ${FMT_VERSION}\n    COMPATIBILITY AnyNewerVersion)\n  configure_package_config_file(\n    ${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in\n    ${project_config}\n    INSTALL_DESTINATION ${FMT_CMAKE_DIR})\n  export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::\n         FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)\n\n  # Install version, config and target files.\n  install(\n    FILES ${project_config} ${version_config}\n    DESTINATION ${FMT_CMAKE_DIR})\n  install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}\n    NAMESPACE fmt::)\n\n  # Install the library and headers.\n  install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}\n          DESTINATION ${FMT_LIB_DIR})\n  install(FILES ${FMT_HEADERS} DESTINATION ${FMT_INC_DIR})\nendif ()\n"
  },
  {
    "path": "lib/fmt/orig/container.h",
    "content": "/*\n Formatting library for C++ - standard container utilities\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_CONTAINER_H_\n#define FMT_CONTAINER_H_\n\n#include \"format.h\"\n\nnamespace fmt {\n\nnamespace internal {\n\n/**\n  \\rst\n  A \"buffer\" that appends data to a standard container (e.g. typically a\n  ``std::vector`` or ``std::basic_string``).\n  \\endrst\n */\ntemplate <typename Container>\nclass ContainerBuffer : public Buffer<typename Container::value_type> {\n private:\n  Container& container_;\n\n protected:\n  virtual void grow(std::size_t size) FMT_OVERRIDE {\n    container_.resize(size);\n    this->ptr_ = &container_[0];\n    this->capacity_ = size;\n  }\n\n public:\n  explicit ContainerBuffer(Container& container) : container_(container) {\n    this->size_ = container_.size();\n    if (this->size_ > 0) {\n      this->ptr_ = &container_[0];\n      this->capacity_ = this->size_;\n    }\n  }\n};\n}  // namespace internal\n\n/**\n  \\rst\n  This class template provides operations for formatting and appending data\n  to a standard *container* like ``std::vector`` or ``std::basic_string``.\n\n  **Example**::\n\n    void vecformat(std::vector<char>& dest, fmt::BasicCStringRef<char> format,\n                   fmt::ArgList args) {\n      fmt::BasicContainerWriter<std::vector<char> > appender(dest);\n      appender.write(format, args);\n    }\n    FMT_VARIADIC(void, vecformat, std::vector<char>&,\n                 fmt::BasicCStringRef<char>);\n  \\endrst\n */\ntemplate <class Container>\nclass BasicContainerWriter\n  : public BasicWriter<typename Container::value_type> {\n private:\n  internal::ContainerBuffer<Container> buffer_;\n\n public:\n  /**\n    \\rst\n    Constructs a :class:`fmt::BasicContainerWriter` object.\n    \\endrst\n   */\n  explicit BasicContainerWriter(Container& dest)\n  : BasicWriter<typename Container::value_type>(buffer_), buffer_(dest) {}\n};\n\n} // namespace fmt\n\n#endif  // FMT_CONTAINER_H_\n"
  },
  {
    "path": "lib/fmt/orig/format.cc",
    "content": "/*\n Formatting library for C++\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n Redistribution and use in source and binary forms, with or without\n modification, are permitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice, this\n    list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright notice,\n    this list of conditions and the following disclaimer in the documentation\n    and/or other materials provided with the distribution.\n\n THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"format.h\"\n\n#include <string.h>\n\n#include <cctype>\n#include <cerrno>\n#include <climits>\n#include <cmath>\n#include <cstdarg>\n#include <cstddef>  // for std::ptrdiff_t\n\n#if defined(_WIN32) && defined(__MINGW32__)\n# include <cstring>\n#endif\n\n#if FMT_USE_WINDOWS_H\n# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN)\n#  define WIN32_LEAN_AND_MEAN\n# endif\n# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX)\n#  include <windows.h>\n# else\n#  define NOMINMAX\n#  include <windows.h>\n#  undef NOMINMAX\n# endif\n#endif\n\n#if FMT_EXCEPTIONS\n# define FMT_TRY try\n# define FMT_CATCH(x) catch (x)\n#else\n# define FMT_TRY if (true)\n# define FMT_CATCH(x) if (false)\n#endif\n\n#ifdef _MSC_VER\n# pragma warning(push)\n# pragma warning(disable: 4127)  // conditional expression is constant\n# pragma warning(disable: 4702)  // unreachable code\n// Disable deprecation warning for strerror. The latter is not called but\n// MSVC fails to detect it.\n# pragma warning(disable: 4996)\n#endif\n\n// Dummy implementations of strerror_r and strerror_s called if corresponding\n// system functions are not available.\nFMT_MAYBE_UNUSED\nstatic inline fmt::internal::Null<> strerror_r(int, char *, ...) {\n  return fmt::internal::Null<>();\n}\nFMT_MAYBE_UNUSED\nstatic inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) {\n  return fmt::internal::Null<>();\n}\n\nnamespace fmt {\n\nFMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT {}\nFMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT {}\nFMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT {}\n\nnamespace {\n\n#ifndef _MSC_VER\n# define FMT_SNPRINTF snprintf\n#else  // _MSC_VER\ninline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {\n  va_list args;\n  va_start(args, format);\n  int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);\n  va_end(args);\n  return result;\n}\n# define FMT_SNPRINTF fmt_snprintf\n#endif  // _MSC_VER\n\n#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)\n# define FMT_SWPRINTF snwprintf\n#else\n# define FMT_SWPRINTF swprintf\n#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)\n\nconst char RESET_COLOR[] = \"\\x1b[0m\";\n\ntypedef void (*FormatFunc)(Writer &, int, StringRef);\n\n// Portable thread-safe version of strerror.\n// Sets buffer to point to a string describing the error code.\n// This can be either a pointer to a string stored in buffer,\n// or a pointer to some static immutable string.\n// Returns one of the following values:\n//   0      - success\n//   ERANGE - buffer is not large enough to store the error message\n//   other  - failure\n// Buffer should be at least of size 1.\nint safe_strerror(\n    int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT {\n  FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, \"invalid buffer\");\n\n  class StrError {\n   private:\n    int error_code_;\n    char *&buffer_;\n    std::size_t buffer_size_;\n\n    // A noop assignment operator to avoid bogus warnings.\n    void operator=(const StrError &) {}\n\n    // Handle the result of XSI-compliant version of strerror_r.\n    int handle(int result) {\n      // glibc versions before 2.13 return result in errno.\n      return result == -1 ? errno : result;\n    }\n\n    // Handle the result of GNU-specific version of strerror_r.\n    int handle(char *message) {\n      // If the buffer is full then the message is probably truncated.\n      if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1)\n        return ERANGE;\n      buffer_ = message;\n      return 0;\n    }\n\n    // Handle the case when strerror_r is not available.\n    int handle(internal::Null<>) {\n      return fallback(strerror_s(buffer_, buffer_size_, error_code_));\n    }\n\n    // Fallback to strerror_s when strerror_r is not available.\n    int fallback(int result) {\n      // If the buffer is full then the message is probably truncated.\n      return result == 0 && strlen(buffer_) == buffer_size_ - 1 ?\n            ERANGE : result;\n    }\n\n#ifdef __c2__\n# pragma clang diagnostic push\n# pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n#endif\n\n    // Fallback to strerror if strerror_r and strerror_s are not available.\n    int fallback(internal::Null<>) {\n      errno = 0;\n      buffer_ = strerror(error_code_);\n      return errno;\n    }\n\n#ifdef __c2__\n# pragma clang diagnostic pop\n#endif\n\n   public:\n    StrError(int err_code, char *&buf, std::size_t buf_size)\n      : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}\n\n    int run() {\n      return handle(strerror_r(error_code_, buffer_, buffer_size_));\n    }\n  };\n  return StrError(error_code, buffer, buffer_size).run();\n}\n\nvoid format_error_code(Writer &out, int error_code,\n                       StringRef message) FMT_NOEXCEPT {\n  // Report error code making sure that the output fits into\n  // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential\n  // bad_alloc.\n  out.clear();\n  static const char SEP[] = \": \";\n  static const char ERROR_STR[] = \"error \";\n  // Subtract 2 to account for terminating null characters in SEP and ERROR_STR.\n  std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;\n  typedef internal::IntTraits<int>::MainType MainType;\n  MainType abs_value = static_cast<MainType>(error_code);\n  if (internal::is_negative(error_code)) {\n    abs_value = 0 - abs_value;\n    ++error_code_size;\n  }\n  error_code_size += internal::count_digits(abs_value);\n  if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size)\n    out << message << SEP;\n  out << ERROR_STR << error_code;\n  assert(out.size() <= internal::INLINE_BUFFER_SIZE);\n}\n\nvoid report_error(FormatFunc func, int error_code,\n                  StringRef message) FMT_NOEXCEPT {\n  MemoryWriter full_message;\n  func(full_message, error_code, message);\n  // Use Writer::data instead of Writer::c_str to avoid potential memory\n  // allocation.\n  std::fwrite(full_message.data(), full_message.size(), 1, stderr);\n  std::fputc('\\n', stderr);\n}\n}  // namespace\n\nFMT_FUNC void SystemError::init(\n    int err_code, CStringRef format_str, ArgList args) {\n  error_code_ = err_code;\n  MemoryWriter w;\n  format_system_error(w, err_code, format(format_str, args));\n  std::runtime_error &base = *this;\n  base = std::runtime_error(w.str());\n}\n\ntemplate <typename T>\nint internal::CharTraits<char>::format_float(\n    char *buffer, std::size_t size, const char *format,\n    unsigned width, int precision, T value) {\n  if (width == 0) {\n    return precision < 0 ?\n        FMT_SNPRINTF(buffer, size, format, value) :\n        FMT_SNPRINTF(buffer, size, format, precision, value);\n  }\n  return precision < 0 ?\n      FMT_SNPRINTF(buffer, size, format, width, value) :\n      FMT_SNPRINTF(buffer, size, format, width, precision, value);\n}\n\ntemplate <typename T>\nint internal::CharTraits<wchar_t>::format_float(\n    wchar_t *buffer, std::size_t size, const wchar_t *format,\n    unsigned width, int precision, T value) {\n  if (width == 0) {\n    return precision < 0 ?\n        FMT_SWPRINTF(buffer, size, format, value) :\n        FMT_SWPRINTF(buffer, size, format, precision, value);\n  }\n  return precision < 0 ?\n      FMT_SWPRINTF(buffer, size, format, width, value) :\n      FMT_SWPRINTF(buffer, size, format, width, precision, value);\n}\n\ntemplate <typename T>\nconst char internal::BasicData<T>::DIGITS[] =\n    \"0001020304050607080910111213141516171819\"\n    \"2021222324252627282930313233343536373839\"\n    \"4041424344454647484950515253545556575859\"\n    \"6061626364656667686970717273747576777879\"\n    \"8081828384858687888990919293949596979899\";\n\n#define FMT_POWERS_OF_10(factor) \\\n  factor * 10, \\\n  factor * 100, \\\n  factor * 1000, \\\n  factor * 10000, \\\n  factor * 100000, \\\n  factor * 1000000, \\\n  factor * 10000000, \\\n  factor * 100000000, \\\n  factor * 1000000000\n\ntemplate <typename T>\nconst uint32_t internal::BasicData<T>::POWERS_OF_10_32[] = {\n  0, FMT_POWERS_OF_10(1)\n};\n\ntemplate <typename T>\nconst uint64_t internal::BasicData<T>::POWERS_OF_10_64[] = {\n  0,\n  FMT_POWERS_OF_10(1),\n  FMT_POWERS_OF_10(ULongLong(1000000000)),\n  // Multiply several constants instead of using a single long long constant\n  // to avoid warnings about C++98 not supporting long long.\n  ULongLong(1000000000) * ULongLong(1000000000) * 10\n};\n\nFMT_FUNC void internal::report_unknown_type(char code, const char *type) {\n  (void)type;\n  if (std::isprint(static_cast<unsigned char>(code))) {\n    FMT_THROW(FormatError(\n        format(\"unknown format code '{}' for {}\", code, type)));\n  }\n  FMT_THROW(FormatError(\n      format(\"unknown format code '\\\\x{:02x}' for {}\",\n        static_cast<unsigned>(code), type)));\n}\n\n#if FMT_USE_WINDOWS_H\n\nFMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) {\n  static const char ERROR_MSG[] = \"cannot convert string from UTF-8 to UTF-16\";\n  if (s.size() > INT_MAX)\n    FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG));\n  int s_size = static_cast<int>(s.size());\n  int length = MultiByteToWideChar(\n      CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0);\n  if (length == 0)\n    FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));\n  buffer_.resize(length + 1);\n  length = MultiByteToWideChar(\n    CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length);\n  if (length == 0)\n    FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));\n  buffer_[length] = 0;\n}\n\nFMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) {\n  if (int error_code = convert(s)) {\n    FMT_THROW(WindowsError(error_code,\n        \"cannot convert string from UTF-16 to UTF-8\"));\n  }\n}\n\nFMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) {\n  if (s.size() > INT_MAX)\n    return ERROR_INVALID_PARAMETER;\n  int s_size = static_cast<int>(s.size());\n  int length = WideCharToMultiByte(\n    CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL);\n  if (length == 0)\n    return GetLastError();\n  buffer_.resize(length + 1);\n  length = WideCharToMultiByte(\n    CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL);\n  if (length == 0)\n    return GetLastError();\n  buffer_[length] = 0;\n  return 0;\n}\n\nFMT_FUNC void WindowsError::init(\n    int err_code, CStringRef format_str, ArgList args) {\n  error_code_ = err_code;\n  MemoryWriter w;\n  internal::format_windows_error(w, err_code, format(format_str, args));\n  std::runtime_error &base = *this;\n  base = std::runtime_error(w.str());\n}\n\nFMT_FUNC void internal::format_windows_error(\n    Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {\n  FMT_TRY {\n    MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer;\n    buffer.resize(INLINE_BUFFER_SIZE);\n    for (;;) {\n      wchar_t *system_message = &buffer[0];\n      int result = FormatMessageW(\n        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,\n        FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n        system_message, static_cast<uint32_t>(buffer.size()), FMT_NULL);\n      if (result != 0) {\n        UTF16ToUTF8 utf8_message;\n        if (utf8_message.convert(system_message) == ERROR_SUCCESS) {\n          out << message << \": \" << utf8_message;\n          return;\n        }\n        break;\n      }\n      if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)\n        break;  // Can't get error message, report error code instead.\n      buffer.resize(buffer.size() * 2);\n    }\n  } FMT_CATCH(...) {}\n  fmt::format_error_code(out, error_code, message);  // 'fmt::' is for bcc32.\n}\n\n#endif  // FMT_USE_WINDOWS_H\n\nFMT_FUNC void format_system_error(\n    Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {\n  FMT_TRY {\n    internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> buffer;\n    buffer.resize(internal::INLINE_BUFFER_SIZE);\n    for (;;) {\n      char *system_message = &buffer[0];\n      int result = safe_strerror(error_code, system_message, buffer.size());\n      if (result == 0) {\n        out << message << \": \" << system_message;\n        return;\n      }\n      if (result != ERANGE)\n        break;  // Can't get error message, report error code instead.\n      buffer.resize(buffer.size() * 2);\n    }\n  } FMT_CATCH(...) {}\n  fmt::format_error_code(out, error_code, message);  // 'fmt::' is for bcc32.\n}\n\ntemplate <typename Char>\nvoid internal::FixedBuffer<Char>::grow(std::size_t) {\n  FMT_THROW(std::runtime_error(\"buffer overflow\"));\n}\n\nFMT_FUNC internal::Arg internal::FormatterBase::do_get_arg(\n    unsigned arg_index, const char *&error) {\n  internal::Arg arg = args_[arg_index];\n  switch (arg.type) {\n  case internal::Arg::NONE:\n    error = \"argument index out of range\";\n    break;\n  case internal::Arg::NAMED_ARG:\n    arg = *static_cast<const internal::Arg*>(arg.pointer);\n    break;\n  default:\n    /*nothing*/;\n  }\n  return arg;\n}\n\nFMT_FUNC void report_system_error(\n    int error_code, fmt::StringRef message) FMT_NOEXCEPT {\n  // 'fmt::' is for bcc32.\n  report_error(format_system_error, error_code, message);\n}\n\n#if FMT_USE_WINDOWS_H\nFMT_FUNC void report_windows_error(\n    int error_code, fmt::StringRef message) FMT_NOEXCEPT {\n  // 'fmt::' is for bcc32.\n  report_error(internal::format_windows_error, error_code, message);\n}\n#endif\n\nFMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) {\n  MemoryWriter w;\n  w.write(format_str, args);\n  std::fwrite(w.data(), 1, w.size(), f);\n}\n\nFMT_FUNC void print(CStringRef format_str, ArgList args) {\n  print(stdout, format_str, args);\n}\n\nFMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) {\n  char escape[] = \"\\x1b[30m\";\n  escape[3] = static_cast<char>('0' + c);\n  std::fputs(escape, stdout);\n  print(format, args);\n  std::fputs(RESET_COLOR, stdout);\n}\n\n#ifndef FMT_HEADER_ONLY\n\ntemplate struct internal::BasicData<void>;\n\n// Explicit instantiations for char.\n\ntemplate void internal::FixedBuffer<char>::grow(std::size_t);\n\ntemplate FMT_API int internal::CharTraits<char>::format_float(\n    char *buffer, std::size_t size, const char *format,\n    unsigned width, int precision, double value);\n\ntemplate FMT_API int internal::CharTraits<char>::format_float(\n    char *buffer, std::size_t size, const char *format,\n    unsigned width, int precision, long double value);\n\n// Explicit instantiations for wchar_t.\n\ntemplate void internal::FixedBuffer<wchar_t>::grow(std::size_t);\n\ntemplate FMT_API int internal::CharTraits<wchar_t>::format_float(\n    wchar_t *buffer, std::size_t size, const wchar_t *format,\n    unsigned width, int precision, double value);\n\ntemplate FMT_API int internal::CharTraits<wchar_t>::format_float(\n    wchar_t *buffer, std::size_t size, const wchar_t *format,\n    unsigned width, int precision, long double value);\n\n#endif  // FMT_HEADER_ONLY\n\n}  // namespace fmt\n\n#ifdef _MSC_VER\n# pragma warning(pop)\n#endif\n"
  },
  {
    "path": "lib/fmt/orig/format.h",
    "content": "/*\n Formatting library for C++\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n Redistribution and use in source and binary forms, with or without\n modification, are permitted provided that the following conditions are met:\n\n 1. Redistributions of source code must retain the above copyright notice, this\n    list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright notice,\n    this list of conditions and the following disclaimer in the documentation\n    and/or other materials provided with the distribution.\n\n THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef FMT_FORMAT_H_\n#define FMT_FORMAT_H_\n\n#define FMT_INCLUDE\n#include <cassert>\n#include <clocale>\n#include <cmath>\n#include <cstdio>\n#include <cstring>\n#include <limits>\n#include <memory>\n#include <stdexcept>\n#include <string>\n#include <vector>\n#include <utility>  // for std::pair\n#undef FMT_INCLUDE\n\n// The fmt library version in the form major * 10000 + minor * 100 + patch.\n#define FMT_VERSION 40100\n\n#if defined(__has_include)\n# define FMT_HAS_INCLUDE(x) __has_include(x)\n#else\n# define FMT_HAS_INCLUDE(x) 0\n#endif\n\n#if (FMT_HAS_INCLUDE(<string_view>) && __cplusplus > 201402L) || \\\n    (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910)\n# include <string_view>\n# define FMT_HAS_STRING_VIEW 1\n#else\n# define FMT_HAS_STRING_VIEW 0\n#endif\n\n#if defined _SECURE_SCL && _SECURE_SCL\n# define FMT_SECURE_SCL _SECURE_SCL\n#else\n# define FMT_SECURE_SCL 0\n#endif\n\n#if FMT_SECURE_SCL\n# include <iterator>\n#endif\n\n#ifdef _MSC_VER\n# define FMT_MSC_VER _MSC_VER\n#else\n# define FMT_MSC_VER 0\n#endif\n\n#if FMT_MSC_VER && FMT_MSC_VER <= 1500\ntypedef unsigned __int32 uint32_t;\ntypedef unsigned __int64 uint64_t;\ntypedef __int64          intmax_t;\n#else\n#include <stdint.h>\n#endif\n\n#if !defined(FMT_HEADER_ONLY) && defined(_WIN32)\n# ifdef FMT_EXPORT\n#  define FMT_API __declspec(dllexport)\n# elif defined(FMT_SHARED)\n#  define FMT_API __declspec(dllimport)\n# endif\n#endif\n#ifndef FMT_API\n# define FMT_API\n#endif\n\n#ifdef __GNUC__\n# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)\n# define FMT_GCC_EXTENSION __extension__\n# if FMT_GCC_VERSION >= 406\n#  pragma GCC diagnostic push\n// Disable the warning about \"long long\" which is sometimes reported even\n// when using __extension__.\n#  pragma GCC diagnostic ignored \"-Wlong-long\"\n// Disable the warning about declaration shadowing because it affects too\n// many valid cases.\n#  pragma GCC diagnostic ignored \"-Wshadow\"\n// Disable the warning about implicit conversions that may change the sign of\n// an integer; silencing it otherwise would require many explicit casts.\n#  pragma GCC diagnostic ignored \"-Wsign-conversion\"\n# endif\n# if __cplusplus >= 201103L || defined __GXX_EXPERIMENTAL_CXX0X__\n#  define FMT_HAS_GXX_CXX11 1\n# endif\n#else\n# define FMT_GCC_VERSION 0\n# define FMT_GCC_EXTENSION\n# define FMT_HAS_GXX_CXX11 0\n#endif\n\n#if defined(__INTEL_COMPILER)\n# define FMT_ICC_VERSION __INTEL_COMPILER\n#elif defined(__ICL)\n# define FMT_ICC_VERSION __ICL\n#endif\n\n#if defined(__clang__) && !defined(FMT_ICC_VERSION)\n# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)\n# pragma clang diagnostic push\n# pragma clang diagnostic ignored \"-Wdocumentation-unknown-command\"\n# pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\n#ifdef __GNUC_LIBSTD__\n# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__)\n#endif\n\n#ifdef __has_feature\n# define FMT_HAS_FEATURE(x) __has_feature(x)\n#else\n# define FMT_HAS_FEATURE(x) 0\n#endif\n\n#ifdef __has_builtin\n# define FMT_HAS_BUILTIN(x) __has_builtin(x)\n#else\n# define FMT_HAS_BUILTIN(x) 0\n#endif\n\n#ifdef __has_cpp_attribute\n# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)\n#else\n# define FMT_HAS_CPP_ATTRIBUTE(x) 0\n#endif\n\n#if FMT_HAS_CPP_ATTRIBUTE(maybe_unused)\n# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED\n// VC++ 1910 support /std: option and that will set _MSVC_LANG macro\n// Clang with Microsoft CodeGen doesn't define _MSVC_LANG macro\n#elif defined(_MSVC_LANG) && _MSVC_LANG > 201402 && _MSC_VER >= 1910\n# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED\n#endif\n\n#ifdef FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED\n# define FMT_MAYBE_UNUSED [[maybe_unused]]\n// g++/clang++ also support [[gnu::unused]]. However, we don't use it.\n#elif defined(__GNUC__)\n# define FMT_MAYBE_UNUSED __attribute__((unused))\n#else\n# define FMT_MAYBE_UNUSED\n#endif\n\n// Use the compiler's attribute noreturn\n#if defined(__MINGW32__) || defined(__MINGW64__)\n# define FMT_NORETURN __attribute__((noreturn))\n#elif FMT_HAS_CPP_ATTRIBUTE(noreturn) && __cplusplus >= 201103L\n# define FMT_NORETURN [[noreturn]]\n#else\n# define FMT_NORETURN\n#endif\n\n#ifndef FMT_USE_VARIADIC_TEMPLATES\n// Variadic templates are available in GCC since version 4.4\n// (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++\n// since version 2013.\n# define FMT_USE_VARIADIC_TEMPLATES \\\n   (FMT_HAS_FEATURE(cxx_variadic_templates) || \\\n       (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800)\n#endif\n\n#ifndef FMT_USE_RVALUE_REFERENCES\n// Don't use rvalue references when compiling with clang and an old libstdc++\n// as the latter doesn't provide std::move.\n# if defined(FMT_GNUC_LIBSTD_VERSION) && FMT_GNUC_LIBSTD_VERSION <= 402\n#  define FMT_USE_RVALUE_REFERENCES 0\n# else\n#  define FMT_USE_RVALUE_REFERENCES \\\n    (FMT_HAS_FEATURE(cxx_rvalue_references) || \\\n        (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600)\n# endif\n#endif\n\n#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700\n# define FMT_USE_ALLOCATOR_TRAITS 1\n#else\n# define FMT_USE_ALLOCATOR_TRAITS 0\n#endif\n\n// Check if exceptions are disabled.\n#if defined(__GNUC__) && !defined(__EXCEPTIONS)\n# define FMT_EXCEPTIONS 0\n#endif\n#if FMT_MSC_VER && !_HAS_EXCEPTIONS\n# define FMT_EXCEPTIONS 0\n#endif\n#ifndef FMT_EXCEPTIONS\n# define FMT_EXCEPTIONS 1\n#endif\n\n#ifndef FMT_THROW\n# if FMT_EXCEPTIONS\n#  define FMT_THROW(x) throw x\n# else\n#  define FMT_THROW(x) assert(false)\n# endif\n#endif\n\n// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature).\n#ifndef FMT_USE_NOEXCEPT\n# define FMT_USE_NOEXCEPT 0\n#endif\n\n#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \\\n    (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \\\n    FMT_MSC_VER >= 1900\n# define FMT_DETECTED_NOEXCEPT noexcept\n#else\n# define FMT_DETECTED_NOEXCEPT throw()\n#endif\n\n#ifndef FMT_NOEXCEPT\n# if FMT_EXCEPTIONS\n#  define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT\n# else\n#  define FMT_NOEXCEPT\n# endif\n#endif\n\n// This is needed because GCC still uses throw() in its headers when exceptions\n// are disabled.\n#if FMT_GCC_VERSION\n# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT\n#else\n# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT\n#endif\n\n#ifndef FMT_OVERRIDE\n# if (defined(FMT_USE_OVERRIDE) && FMT_USE_OVERRIDE) || FMT_HAS_FEATURE(cxx_override) || \\\n   (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \\\n   FMT_MSC_VER >= 1900\n#  define FMT_OVERRIDE override\n# else\n#  define FMT_OVERRIDE\n# endif\n#endif\n\n#ifndef FMT_NULL\n# if FMT_HAS_FEATURE(cxx_nullptr) || \\\n   (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \\\n   FMT_MSC_VER >= 1600\n#  define FMT_NULL nullptr\n# else\n#  define FMT_NULL NULL\n# endif\n#endif\n\n// A macro to disallow the copy constructor and operator= functions\n// This should be used in the private: declarations for a class\n#ifndef FMT_USE_DELETED_FUNCTIONS\n# define FMT_USE_DELETED_FUNCTIONS 0\n#endif\n\n#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \\\n  (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800\n# define FMT_DELETED_OR_UNDEFINED  = delete\n# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \\\n    TypeName(const TypeName&) = delete; \\\n    TypeName& operator=(const TypeName&) = delete\n#else\n# define FMT_DELETED_OR_UNDEFINED\n# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \\\n    TypeName(const TypeName&); \\\n    TypeName& operator=(const TypeName&)\n#endif\n\n#ifndef FMT_USE_DEFAULTED_FUNCTIONS\n# define FMT_USE_DEFAULTED_FUNCTIONS 0\n#endif\n\n#ifndef FMT_DEFAULTED_COPY_CTOR\n# if FMT_USE_DEFAULTED_FUNCTIONS || FMT_HAS_FEATURE(cxx_defaulted_functions) || \\\n   (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800\n#  define FMT_DEFAULTED_COPY_CTOR(TypeName) \\\n    TypeName(const TypeName&) = default;\n# else\n#  define FMT_DEFAULTED_COPY_CTOR(TypeName)\n# endif\n#endif\n\n#ifndef FMT_USE_USER_DEFINED_LITERALS\n// All compilers which support UDLs also support variadic templates. This\n// makes the fmt::literals implementation easier. However, an explicit check\n// for variadic templates is added here just in case.\n// For Intel's compiler both it and the system gcc/msc must support UDLs.\n# if FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES && \\\n   (FMT_HAS_FEATURE(cxx_user_literals) || \\\n     (FMT_GCC_VERSION >= 407 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900) && \\\n   (!defined(FMT_ICC_VERSION) || FMT_ICC_VERSION >= 1500)\n#  define FMT_USE_USER_DEFINED_LITERALS 1\n# else\n#  define FMT_USE_USER_DEFINED_LITERALS 0\n# endif\n#endif\n\n#ifndef FMT_USE_EXTERN_TEMPLATES\n# define FMT_USE_EXTERN_TEMPLATES \\\n    (FMT_CLANG_VERSION >= 209 || (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11))\n#endif\n\n#ifdef FMT_HEADER_ONLY\n// If header only do not use extern templates.\n# undef FMT_USE_EXTERN_TEMPLATES\n# define FMT_USE_EXTERN_TEMPLATES 0\n#endif\n\n#ifndef FMT_ASSERT\n# define FMT_ASSERT(condition, message) assert((condition) && message)\n#endif\n\n// __builtin_clz is broken in clang with Microsoft CodeGen:\n// https://github.com/fmtlib/fmt/issues/519\n#ifndef _MSC_VER\n# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz)\n#  define FMT_BUILTIN_CLZ(n) __builtin_clz(n)\n# endif\n\n# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll)\n#  define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n)\n# endif\n#endif\n\n// Some compilers masquerade as both MSVC and GCC-likes or\n// otherwise support __builtin_clz and __builtin_clzll, so\n// only define FMT_BUILTIN_CLZ using the MSVC intrinsics\n// if the clz and clzll builtins are not available.\n#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED)\n# include <intrin.h>  // _BitScanReverse, _BitScanReverse64\n\nnamespace fmt {\nnamespace internal {\n// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning\n# ifndef __clang__\n#  pragma intrinsic(_BitScanReverse)\n# endif\ninline uint32_t clz(uint32_t x) {\n  unsigned long r = 0;\n  _BitScanReverse(&r, x);\n\n  assert(x != 0);\n  // Static analysis complains about using uninitialized data\n  // \"r\", but the only way that can happen is if \"x\" is 0,\n  // which the callers guarantee to not happen.\n# pragma warning(suppress: 6102)\n  return 31 - r;\n}\n# define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n)\n\n// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning\n# if defined(_WIN64) && !defined(__clang__)\n#  pragma intrinsic(_BitScanReverse64)\n# endif\n\ninline uint32_t clzll(uint64_t x) {\n  unsigned long r = 0;\n# ifdef _WIN64\n  _BitScanReverse64(&r, x);\n# else\n  // Scan the high 32 bits.\n  if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32)))\n    return 63 - (r + 32);\n\n  // Scan the low 32 bits.\n  _BitScanReverse(&r, static_cast<uint32_t>(x));\n# endif\n\n  assert(x != 0);\n  // Static analysis complains about using uninitialized data\n  // \"r\", but the only way that can happen is if \"x\" is 0,\n  // which the callers guarantee to not happen.\n# pragma warning(suppress: 6102)\n  return 63 - r;\n}\n# define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n)\n}\n}\n#endif\n\nnamespace fmt {\nnamespace internal {\nstruct DummyInt {\n  int data[2];\n  operator int() const { return 0; }\n};\ntypedef std::numeric_limits<fmt::internal::DummyInt> FPUtil;\n\n// Dummy implementations of system functions such as signbit and ecvt called\n// if the latter are not available.\ninline DummyInt signbit(...) { return DummyInt(); }\ninline DummyInt _ecvt_s(...) { return DummyInt(); }\ninline DummyInt isinf(...) { return DummyInt(); }\ninline DummyInt _finite(...) { return DummyInt(); }\ninline DummyInt isnan(...) { return DummyInt(); }\ninline DummyInt _isnan(...) { return DummyInt(); }\n\n// A helper function to suppress bogus \"conditional expression is constant\"\n// warnings.\ntemplate <typename T>\ninline T const_check(T value) { return value; }\n}\n}  // namespace fmt\n\nnamespace std {\n// Standard permits specialization of std::numeric_limits. This specialization\n// is used to resolve ambiguity between isinf and std::isinf in glibc:\n// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891\n// and the same for isnan and signbit.\ntemplate <>\nclass numeric_limits<fmt::internal::DummyInt> :\n    public std::numeric_limits<int> {\n public:\n  // Portable version of isinf.\n  template <typename T>\n  static bool isinfinity(T x) {\n    using namespace fmt::internal;\n    // The resolution \"priority\" is:\n    // isinf macro > std::isinf > ::isinf > fmt::internal::isinf\n    if (const_check(sizeof(isinf(x)) == sizeof(bool) ||\n                    sizeof(isinf(x)) == sizeof(int))) {\n      return isinf(x) != 0;\n    }\n    return !_finite(static_cast<double>(x));\n  }\n\n  // Portable version of isnan.\n  template <typename T>\n  static bool isnotanumber(T x) {\n    using namespace fmt::internal;\n    if (const_check(sizeof(isnan(x)) == sizeof(bool) ||\n                    sizeof(isnan(x)) == sizeof(int))) {\n      return isnan(x) != 0;\n    }\n    return _isnan(static_cast<double>(x)) != 0;\n  }\n\n  // Portable version of signbit.\n  static bool isnegative(double x) {\n    using namespace fmt::internal;\n    if (const_check(sizeof(signbit(x)) == sizeof(bool) ||\n                    sizeof(signbit(x)) == sizeof(int))) {\n      return signbit(x) != 0;\n    }\n    if (x < 0) return true;\n    if (!isnotanumber(x)) return false;\n    int dec = 0, sign = 0;\n    char buffer[2];  // The buffer size must be >= 2 or _ecvt_s will fail.\n    _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign);\n    return sign != 0;\n  }\n};\n}  // namespace std\n\nnamespace fmt {\n\n// Fix the warning about long long on older versions of GCC\n// that don't support the diagnostic pragma.\nFMT_GCC_EXTENSION typedef long long LongLong;\nFMT_GCC_EXTENSION typedef unsigned long long ULongLong;\n\n#if FMT_USE_RVALUE_REFERENCES\nusing std::move;\n#endif\n\ntemplate <typename Char>\nclass BasicWriter;\n\ntypedef BasicWriter<char> Writer;\ntypedef BasicWriter<wchar_t> WWriter;\n\ntemplate <typename Char>\nclass ArgFormatter;\n\nstruct FormatSpec;\n\ntemplate <typename Impl, typename Char, typename Spec = fmt::FormatSpec>\nclass BasicPrintfArgFormatter;\n\ntemplate <typename CharType,\n          typename ArgFormatter = fmt::ArgFormatter<CharType> >\nclass BasicFormatter;\n\n/**\n  \\rst\n  A string reference. It can be constructed from a C string or\n  ``std::basic_string``.\n\n  You can use one of the following typedefs for common character types:\n\n  +------------+-------------------------+\n  | Type       | Definition              |\n  +============+=========================+\n  | StringRef  | BasicStringRef<char>    |\n  +------------+-------------------------+\n  | WStringRef | BasicStringRef<wchar_t> |\n  +------------+-------------------------+\n\n  This class is most useful as a parameter type to allow passing\n  different types of strings to a function, for example::\n\n    template <typename... Args>\n    std::string format(StringRef format_str, const Args & ... args);\n\n    format(\"{}\", 42);\n    format(std::string(\"{}\"), 42);\n  \\endrst\n */\ntemplate <typename Char>\nclass BasicStringRef {\n private:\n  const Char *data_;\n  std::size_t size_;\n\n public:\n  /** Constructs a string reference object from a C string and a size. */\n  BasicStringRef(const Char *s, std::size_t size) : data_(s), size_(size) {}\n\n  /**\n    \\rst\n    Constructs a string reference object from a C string computing\n    the size with ``std::char_traits<Char>::length``.\n    \\endrst\n   */\n  BasicStringRef(const Char *s)\n    : data_(s), size_(std::char_traits<Char>::length(s)) {}\n\n  /**\n    \\rst\n    Constructs a string reference from a ``std::basic_string`` object.\n    \\endrst\n   */\n  template <typename Allocator>\n  BasicStringRef(\n      const std::basic_string<Char, std::char_traits<Char>, Allocator> &s)\n  : data_(s.c_str()), size_(s.size()) {}\n\n#if FMT_HAS_STRING_VIEW\n  /**\n    \\rst\n    Constructs a string reference from a ``std::basic_string_view`` object.\n    \\endrst\n   */\n  BasicStringRef(\n      const std::basic_string_view<Char, std::char_traits<Char>> &s)\n  : data_(s.data()), size_(s.size()) {}\n\n  /**\n   \\rst\n   Converts a string reference to an ``std::string_view`` object.\n   \\endrst\n  */\n  explicit operator std::basic_string_view<Char>() const FMT_NOEXCEPT {\n    return std::basic_string_view<Char>(data_, size_);\n  }\n#endif\n\n  /**\n    \\rst\n    Converts a string reference to an ``std::string`` object.\n    \\endrst\n   */\n  std::basic_string<Char> to_string() const {\n    return std::basic_string<Char>(data_, size_);\n  }\n\n  /** Returns a pointer to the string data. */\n  const Char *data() const { return data_; }\n\n  /** Returns the string size. */\n  std::size_t size() const { return size_; }\n\n  // Lexicographically compare this string reference to other.\n  int compare(BasicStringRef other) const {\n    std::size_t size = size_ < other.size_ ? size_ : other.size_;\n    int result = std::char_traits<Char>::compare(data_, other.data_, size);\n    if (result == 0)\n      result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1);\n    return result;\n  }\n\n  friend bool operator==(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) == 0;\n  }\n  friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) != 0;\n  }\n  friend bool operator<(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) < 0;\n  }\n  friend bool operator<=(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) <= 0;\n  }\n  friend bool operator>(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) > 0;\n  }\n  friend bool operator>=(BasicStringRef lhs, BasicStringRef rhs) {\n    return lhs.compare(rhs) >= 0;\n  }\n};\n\ntypedef BasicStringRef<char> StringRef;\ntypedef BasicStringRef<wchar_t> WStringRef;\n\n/**\n  \\rst\n  A reference to a null terminated string. It can be constructed from a C\n  string or ``std::basic_string``.\n\n  You can use one of the following typedefs for common character types:\n\n  +-------------+--------------------------+\n  | Type        | Definition               |\n  +=============+==========================+\n  | CStringRef  | BasicCStringRef<char>    |\n  +-------------+--------------------------+\n  | WCStringRef | BasicCStringRef<wchar_t> |\n  +-------------+--------------------------+\n\n  This class is most useful as a parameter type to allow passing\n  different types of strings to a function, for example::\n\n    template <typename... Args>\n    std::string format(CStringRef format_str, const Args & ... args);\n\n    format(\"{}\", 42);\n    format(std::string(\"{}\"), 42);\n  \\endrst\n */\ntemplate <typename Char>\nclass BasicCStringRef {\n private:\n  const Char *data_;\n\n public:\n  /** Constructs a string reference object from a C string. */\n  BasicCStringRef(const Char *s) : data_(s) {}\n\n  /**\n    \\rst\n    Constructs a string reference from a ``std::basic_string`` object.\n    \\endrst\n   */\n  template <typename Allocator>\n  BasicCStringRef(\n      const std::basic_string<Char, std::char_traits<Char>, Allocator> &s)\n  : data_(s.c_str()) {}\n\n  /** Returns the pointer to a C string. */\n  const Char *c_str() const { return data_; }\n};\n\ntypedef BasicCStringRef<char> CStringRef;\ntypedef BasicCStringRef<wchar_t> WCStringRef;\n\n/** A formatting error such as invalid format string. */\nclass FormatError : public std::runtime_error {\n public:\n  explicit FormatError(CStringRef message)\n  : std::runtime_error(message.c_str()) {}\n  FormatError(const FormatError &ferr) : std::runtime_error(ferr) {}\n  FMT_API ~FormatError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE;\n};\n\nnamespace internal {\n\n// MakeUnsigned<T>::Type gives an unsigned type corresponding to integer type T.\ntemplate <typename T>\nstruct MakeUnsigned { typedef T Type; };\n\n#define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \\\n  template <> \\\n  struct MakeUnsigned<T> { typedef U Type; }\n\nFMT_SPECIALIZE_MAKE_UNSIGNED(char, unsigned char);\nFMT_SPECIALIZE_MAKE_UNSIGNED(signed char, unsigned char);\nFMT_SPECIALIZE_MAKE_UNSIGNED(short, unsigned short);\nFMT_SPECIALIZE_MAKE_UNSIGNED(int, unsigned);\nFMT_SPECIALIZE_MAKE_UNSIGNED(long, unsigned long);\nFMT_SPECIALIZE_MAKE_UNSIGNED(LongLong, ULongLong);\n\n// Casts nonnegative integer to unsigned.\ntemplate <typename Int>\ninline typename MakeUnsigned<Int>::Type to_unsigned(Int value) {\n  FMT_ASSERT(value >= 0, \"negative value\");\n  return static_cast<typename MakeUnsigned<Int>::Type>(value);\n}\n\n// The number of characters to store in the MemoryBuffer object itself\n// to avoid dynamic memory allocation.\nenum { INLINE_BUFFER_SIZE = 500 };\n\n#if FMT_SECURE_SCL\n// Use checked iterator to avoid warnings on MSVC.\ntemplate <typename T>\ninline stdext::checked_array_iterator<T*> make_ptr(T *ptr, std::size_t size) {\n  return stdext::checked_array_iterator<T*>(ptr, size);\n}\n#else\ntemplate <typename T>\ninline T *make_ptr(T *ptr, std::size_t) { return ptr; }\n#endif\n}  // namespace internal\n\n/**\n  \\rst\n  A buffer supporting a subset of ``std::vector``'s operations.\n  \\endrst\n */\ntemplate <typename T>\nclass Buffer {\n private:\n  FMT_DISALLOW_COPY_AND_ASSIGN(Buffer);\n\n protected:\n  T *ptr_;\n  std::size_t size_;\n  std::size_t capacity_;\n\n  Buffer(T *ptr = FMT_NULL, std::size_t capacity = 0)\n    : ptr_(ptr), size_(0), capacity_(capacity) {}\n\n  /**\n    \\rst\n    Increases the buffer capacity to hold at least *size* elements updating\n    ``ptr_`` and ``capacity_``.\n    \\endrst\n   */\n  virtual void grow(std::size_t size) = 0;\n\n public:\n  virtual ~Buffer() {}\n\n  /** Returns the size of this buffer. */\n  std::size_t size() const { return size_; }\n\n  /** Returns the capacity of this buffer. */\n  std::size_t capacity() const { return capacity_; }\n\n  /**\n    Resizes the buffer. If T is a POD type new elements may not be initialized.\n   */\n  void resize(std::size_t new_size) {\n    if (new_size > capacity_)\n      grow(new_size);\n    size_ = new_size;\n  }\n\n  /**\n    \\rst\n    Reserves space to store at least *capacity* elements.\n    \\endrst\n   */\n  void reserve(std::size_t capacity) {\n    if (capacity > capacity_)\n      grow(capacity);\n  }\n\n  void clear() FMT_NOEXCEPT { size_ = 0; }\n\n  void push_back(const T &value) {\n    if (size_ == capacity_)\n      grow(size_ + 1);\n    ptr_[size_++] = value;\n  }\n\n  /** Appends data to the end of the buffer. */\n  template <typename U>\n  void append(const U *begin, const U *end);\n\n  T &operator[](std::size_t index) { return ptr_[index]; }\n  const T &operator[](std::size_t index) const { return ptr_[index]; }\n};\n\ntemplate <typename T>\ntemplate <typename U>\nvoid Buffer<T>::append(const U *begin, const U *end) {\n  FMT_ASSERT(end >= begin, \"negative value\");\n  std::size_t new_size = size_ + static_cast<std::size_t>(end - begin);\n  if (new_size > capacity_)\n    grow(new_size);\n  std::uninitialized_copy(begin, end,\n                          internal::make_ptr(ptr_, capacity_) + size_);\n  size_ = new_size;\n}\n\nnamespace internal {\n\n// A memory buffer for trivially copyable/constructible types with the first\n// SIZE elements stored in the object itself.\ntemplate <typename T, std::size_t SIZE, typename Allocator = std::allocator<T> >\nclass MemoryBuffer : private Allocator, public Buffer<T> {\n private:\n  T data_[SIZE];\n\n  // Deallocate memory allocated by the buffer.\n  void deallocate() {\n    if (this->ptr_ != data_) Allocator::deallocate(this->ptr_, this->capacity_);\n  }\n\n protected:\n  void grow(std::size_t size) FMT_OVERRIDE;\n\n public:\n  explicit MemoryBuffer(const Allocator &alloc = Allocator())\n      : Allocator(alloc), Buffer<T>(data_, SIZE) {}\n  ~MemoryBuffer() FMT_OVERRIDE { deallocate(); }\n\n#if FMT_USE_RVALUE_REFERENCES\n private:\n  // Move data from other to this buffer.\n  void move(MemoryBuffer &other) {\n    Allocator &this_alloc = *this, &other_alloc = other;\n    this_alloc = std::move(other_alloc);\n    this->size_ = other.size_;\n    this->capacity_ = other.capacity_;\n    if (other.ptr_ == other.data_) {\n      this->ptr_ = data_;\n      std::uninitialized_copy(other.data_, other.data_ + this->size_,\n                              make_ptr(data_, this->capacity_));\n    } else {\n      this->ptr_ = other.ptr_;\n      // Set pointer to the inline array so that delete is not called\n      // when deallocating.\n      other.ptr_ = other.data_;\n    }\n  }\n\n public:\n  MemoryBuffer(MemoryBuffer &&other) {\n    move(other);\n  }\n\n  MemoryBuffer &operator=(MemoryBuffer &&other) {\n    assert(this != &other);\n    deallocate();\n    move(other);\n    return *this;\n  }\n#endif\n\n  // Returns a copy of the allocator associated with this buffer.\n  Allocator get_allocator() const { return *this; }\n};\n\ntemplate <typename T, std::size_t SIZE, typename Allocator>\nvoid MemoryBuffer<T, SIZE, Allocator>::grow(std::size_t size) {\n  std::size_t new_capacity = this->capacity_ + this->capacity_ / 2;\n  if (size > new_capacity)\n      new_capacity = size;\n#if FMT_USE_ALLOCATOR_TRAITS\n  T *new_ptr =\n      std::allocator_traits<Allocator>::allocate(*this, new_capacity, FMT_NULL);\n#else\n  T *new_ptr = this->allocate(new_capacity, FMT_NULL);\n#endif\n  // The following code doesn't throw, so the raw pointer above doesn't leak.\n  std::uninitialized_copy(this->ptr_, this->ptr_ + this->size_,\n                          make_ptr(new_ptr, new_capacity));\n  std::size_t old_capacity = this->capacity_;\n  T *old_ptr = this->ptr_;\n  this->capacity_ = new_capacity;\n  this->ptr_ = new_ptr;\n  // deallocate may throw (at least in principle), but it doesn't matter since\n  // the buffer already uses the new storage and will deallocate it in case\n  // of exception.\n  if (old_ptr != data_)\n    Allocator::deallocate(old_ptr, old_capacity);\n}\n\n// A fixed-size buffer.\ntemplate <typename Char>\nclass FixedBuffer : public fmt::Buffer<Char> {\n public:\n  FixedBuffer(Char *array, std::size_t size) : fmt::Buffer<Char>(array, size) {}\n\n protected:\n  FMT_API void grow(std::size_t size) FMT_OVERRIDE;\n};\n\ntemplate <typename Char>\nclass BasicCharTraits {\n public:\n#if FMT_SECURE_SCL\n  typedef stdext::checked_array_iterator<Char*> CharPtr;\n#else\n  typedef Char *CharPtr;\n#endif\n  static Char cast(int value) { return static_cast<Char>(value); }\n};\n\ntemplate <typename Char>\nclass CharTraits;\n\ntemplate <>\nclass CharTraits<char> : public BasicCharTraits<char> {\n private:\n  // Conversion from wchar_t to char is not allowed.\n  static char convert(wchar_t);\n\n public:\n  static char convert(char value) { return value; }\n\n  // Formats a floating-point number.\n  template <typename T>\n  FMT_API static int format_float(char *buffer, std::size_t size,\n      const char *format, unsigned width, int precision, T value);\n};\n\n#if FMT_USE_EXTERN_TEMPLATES\nextern template int CharTraits<char>::format_float<double>\n        (char *buffer, std::size_t size,\n         const char* format, unsigned width, int precision, double value);\nextern template int CharTraits<char>::format_float<long double>\n        (char *buffer, std::size_t size,\n         const char* format, unsigned width, int precision, long double value);\n#endif\n\ntemplate <>\nclass CharTraits<wchar_t> : public BasicCharTraits<wchar_t> {\n public:\n  static wchar_t convert(char value) { return value; }\n  static wchar_t convert(wchar_t value) { return value; }\n\n  template <typename T>\n  FMT_API static int format_float(wchar_t *buffer, std::size_t size,\n      const wchar_t *format, unsigned width, int precision, T value);\n};\n\n#if FMT_USE_EXTERN_TEMPLATES\nextern template int CharTraits<wchar_t>::format_float<double>\n        (wchar_t *buffer, std::size_t size,\n         const wchar_t* format, unsigned width, int precision, double value);\nextern template int CharTraits<wchar_t>::format_float<long double>\n        (wchar_t *buffer, std::size_t size,\n         const wchar_t* format, unsigned width, int precision, long double value);\n#endif\n\n// Checks if a number is negative - used to avoid warnings.\ntemplate <bool IsSigned>\nstruct SignChecker {\n  template <typename T>\n  static bool is_negative(T value) { return value < 0; }\n};\n\ntemplate <>\nstruct SignChecker<false> {\n  template <typename T>\n  static bool is_negative(T) { return false; }\n};\n\n// Returns true if value is negative, false otherwise.\n// Same as (value < 0) but doesn't produce warnings if T is an unsigned type.\ntemplate <typename T>\ninline bool is_negative(T value) {\n  return SignChecker<std::numeric_limits<T>::is_signed>::is_negative(value);\n}\n\n// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise.\ntemplate <bool FitsIn32Bits>\nstruct TypeSelector { typedef uint32_t Type; };\n\ntemplate <>\nstruct TypeSelector<false> { typedef uint64_t Type; };\n\ntemplate <typename T>\nstruct IntTraits {\n  // Smallest of uint32_t and uint64_t that is large enough to represent\n  // all values of T.\n  typedef typename\n    TypeSelector<std::numeric_limits<T>::digits <= 32>::Type MainType;\n};\n\nFMT_API FMT_NORETURN void report_unknown_type(char code, const char *type);\n\n// Static data is placed in this class template to allow header-only\n// configuration.\ntemplate <typename T = void>\nstruct FMT_API BasicData {\n  static const uint32_t POWERS_OF_10_32[];\n  static const uint64_t POWERS_OF_10_64[];\n  static const char DIGITS[];\n};\n\n#if FMT_USE_EXTERN_TEMPLATES\nextern template struct BasicData<void>;\n#endif\n\ntypedef BasicData<> Data;\n\n#ifdef FMT_BUILTIN_CLZLL\n// Returns the number of decimal digits in n. Leading zeros are not counted\n// except for n == 0 in which case count_digits returns 1.\ninline unsigned count_digits(uint64_t n) {\n  // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10\n  // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits.\n  int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12;\n  return to_unsigned(t) - (n < Data::POWERS_OF_10_64[t]) + 1;\n}\n#else\n// Fallback version of count_digits used when __builtin_clz is not available.\ninline unsigned count_digits(uint64_t n) {\n  unsigned count = 1;\n  for (;;) {\n    // Integer division is slow so do it for a group of four digits instead\n    // of for every digit. The idea comes from the talk by Alexandrescu\n    // \"Three Optimization Tips for C++\". See speed-test for a comparison.\n    if (n < 10) return count;\n    if (n < 100) return count + 1;\n    if (n < 1000) return count + 2;\n    if (n < 10000) return count + 3;\n    n /= 10000u;\n    count += 4;\n  }\n}\n#endif\n\n#ifdef FMT_BUILTIN_CLZ\n// Optional version of count_digits for better performance on 32-bit platforms.\ninline unsigned count_digits(uint32_t n) {\n  int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12;\n  return to_unsigned(t) - (n < Data::POWERS_OF_10_32[t]) + 1;\n}\n#endif\n\n// A functor that doesn't add a thousands separator.\nstruct NoThousandsSep {\n  template <typename Char>\n  void operator()(Char *) {}\n};\n\n// A functor that adds a thousands separator.\nclass ThousandsSep {\n private:\n  fmt::StringRef sep_;\n\n  // Index of a decimal digit with the least significant digit having index 0.\n  unsigned digit_index_;\n\n public:\n  explicit ThousandsSep(fmt::StringRef sep) : sep_(sep), digit_index_(0) {}\n\n  template <typename Char>\n  void operator()(Char *&buffer) {\n    if (++digit_index_ % 3 != 0)\n      return;\n    buffer -= sep_.size();\n    std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(),\n                            internal::make_ptr(buffer, sep_.size()));\n  }\n};\n\n// Formats a decimal unsigned integer value writing into buffer.\n// thousands_sep is a functor that is called after writing each char to\n// add a thousands separator if necessary.\ntemplate <typename UInt, typename Char, typename ThousandsSep>\ninline void format_decimal(Char *buffer, UInt value, unsigned num_digits,\n                           ThousandsSep thousands_sep) {\n  buffer += num_digits;\n  while (value >= 100) {\n    // Integer division is slow so do it for a group of two digits instead\n    // of for every digit. The idea comes from the talk by Alexandrescu\n    // \"Three Optimization Tips for C++\". See speed-test for a comparison.\n    unsigned index = static_cast<unsigned>((value % 100) * 2);\n    value /= 100;\n    *--buffer = Data::DIGITS[index + 1];\n    thousands_sep(buffer);\n    *--buffer = Data::DIGITS[index];\n    thousands_sep(buffer);\n  }\n  if (value < 10) {\n    *--buffer = static_cast<char>('0' + value);\n    return;\n  }\n  unsigned index = static_cast<unsigned>(value * 2);\n  *--buffer = Data::DIGITS[index + 1];\n  thousands_sep(buffer);\n  *--buffer = Data::DIGITS[index];\n}\n\ntemplate <typename UInt, typename Char>\ninline void format_decimal(Char *buffer, UInt value, unsigned num_digits) {\n  format_decimal(buffer, value, num_digits, NoThousandsSep());\n  return;\n}\n\n#ifndef _WIN32\n# define FMT_USE_WINDOWS_H 0\n#elif !defined(FMT_USE_WINDOWS_H)\n# define FMT_USE_WINDOWS_H 1\n#endif\n\n// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h.\n// All the functionality that relies on it will be disabled too.\n#if FMT_USE_WINDOWS_H\n// A converter from UTF-8 to UTF-16.\n// It is only provided for Windows since other systems support UTF-8 natively.\nclass UTF8ToUTF16 {\n private:\n  MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer_;\n\n public:\n  FMT_API explicit UTF8ToUTF16(StringRef s);\n  operator WStringRef() const { return WStringRef(&buffer_[0], size()); }\n  size_t size() const { return buffer_.size() - 1; }\n  const wchar_t *c_str() const { return &buffer_[0]; }\n  std::wstring str() const { return std::wstring(&buffer_[0], size()); }\n};\n\n// A converter from UTF-16 to UTF-8.\n// It is only provided for Windows since other systems support UTF-8 natively.\nclass UTF16ToUTF8 {\n private:\n  MemoryBuffer<char, INLINE_BUFFER_SIZE> buffer_;\n\n public:\n  UTF16ToUTF8() {}\n  FMT_API explicit UTF16ToUTF8(WStringRef s);\n  operator StringRef() const { return StringRef(&buffer_[0], size()); }\n  size_t size() const { return buffer_.size() - 1; }\n  const char *c_str() const { return &buffer_[0]; }\n  std::string str() const { return std::string(&buffer_[0], size()); }\n\n  // Performs conversion returning a system error code instead of\n  // throwing exception on conversion error. This method may still throw\n  // in case of memory allocation error.\n  FMT_API int convert(WStringRef s);\n};\n\nFMT_API void format_windows_error(fmt::Writer &out, int error_code,\n                                  fmt::StringRef message) FMT_NOEXCEPT;\n#endif\n\n// A formatting argument value.\nstruct Value {\n  template <typename Char>\n  struct StringValue {\n    const Char *value;\n    std::size_t size;\n  };\n\n  typedef void (*FormatFunc)(\n      void *formatter, const void *arg, void *format_str_ptr);\n\n  struct CustomValue {\n    const void *value;\n    FormatFunc format;\n  };\n\n  union {\n    int int_value;\n    unsigned uint_value;\n    LongLong long_long_value;\n    ULongLong ulong_long_value;\n    double double_value;\n    long double long_double_value;\n    const void *pointer;\n    StringValue<char> string;\n    StringValue<signed char> sstring;\n    StringValue<unsigned char> ustring;\n    StringValue<wchar_t> wstring;\n    CustomValue custom;\n  };\n\n  enum Type {\n    NONE, NAMED_ARG,\n    // Integer types should go first,\n    INT, UINT, LONG_LONG, ULONG_LONG, BOOL, CHAR, LAST_INTEGER_TYPE = CHAR,\n    // followed by floating-point types.\n    DOUBLE, LONG_DOUBLE, LAST_NUMERIC_TYPE = LONG_DOUBLE,\n    CSTRING, STRING, WSTRING, POINTER, CUSTOM\n  };\n};\n\n// A formatting argument. It is a trivially copyable/constructible type to\n// allow storage in internal::MemoryBuffer.\nstruct Arg : Value {\n  Type type;\n};\n\ntemplate <typename Char>\nstruct NamedArg;\ntemplate <typename Char, typename T>\nstruct NamedArgWithType;\n\ntemplate <typename T = void>\nstruct Null {};\n\n// A helper class template to enable or disable overloads taking wide\n// characters and strings in MakeValue.\ntemplate <typename T, typename Char>\nstruct WCharHelper {\n  typedef Null<T> Supported;\n  typedef T Unsupported;\n};\n\ntemplate <typename T>\nstruct WCharHelper<T, wchar_t> {\n  typedef T Supported;\n  typedef Null<T> Unsupported;\n};\n\ntypedef char Yes[1];\ntypedef char No[2];\n\ntemplate <typename T>\nT &get();\n\n// These are non-members to workaround an overload resolution bug in bcc32.\nYes &convert(fmt::ULongLong);\nNo &convert(...);\n\ntemplate <typename T, bool ENABLE_CONVERSION>\nstruct ConvertToIntImpl {\n  enum { value = ENABLE_CONVERSION };\n};\n\ntemplate <typename T, bool ENABLE_CONVERSION>\nstruct ConvertToIntImpl2 {\n  enum { value = false };\n};\n\ntemplate <typename T>\nstruct ConvertToIntImpl2<T, true> {\n  enum {\n    // Don't convert numeric types.\n    value = ConvertToIntImpl<T, !std::numeric_limits<T>::is_specialized>::value\n  };\n};\n\ntemplate <typename T>\nstruct ConvertToInt {\n  enum {\n    enable_conversion = sizeof(fmt::internal::convert(get<T>())) == sizeof(Yes)\n  };\n  enum { value = ConvertToIntImpl2<T, enable_conversion>::value };\n};\n\n#define FMT_DISABLE_CONVERSION_TO_INT(Type) \\\n  template <> \\\n  struct ConvertToInt<Type> {  enum { value = 0 }; }\n\n// Silence warnings about convering float to int.\nFMT_DISABLE_CONVERSION_TO_INT(float);\nFMT_DISABLE_CONVERSION_TO_INT(double);\nFMT_DISABLE_CONVERSION_TO_INT(long double);\n\ntemplate <bool B, class T = void>\nstruct EnableIf {};\n\ntemplate <class T>\nstruct EnableIf<true, T> { typedef T type; };\n\ntemplate <bool B, class T, class F>\nstruct Conditional { typedef T type; };\n\ntemplate <class T, class F>\nstruct Conditional<false, T, F> { typedef F type; };\n\n// For bcc32 which doesn't understand ! in template arguments.\ntemplate <bool>\nstruct Not { enum { value = 0 }; };\n\ntemplate <>\nstruct Not<false> { enum { value = 1 }; };\n\ntemplate <typename T>\nstruct FalseType { enum { value = 0 }; };\n\ntemplate <typename T, T> struct LConvCheck {\n  LConvCheck(int) {}\n};\n\n// Returns the thousands separator for the current locale.\n// We check if ``lconv`` contains ``thousands_sep`` because on Android\n// ``lconv`` is stubbed as an empty struct.\ntemplate <typename LConv>\ninline StringRef thousands_sep(\n    LConv *lc, LConvCheck<char *LConv::*, &LConv::thousands_sep> = 0) {\n  return lc->thousands_sep;\n}\n\ninline fmt::StringRef thousands_sep(...) { return \"\"; }\n\n#define FMT_CONCAT(a, b) a##b\n\n#if FMT_GCC_VERSION >= 303\n# define FMT_UNUSED __attribute__((unused))\n#else\n# define FMT_UNUSED\n#endif\n\n#ifndef FMT_USE_STATIC_ASSERT\n# define FMT_USE_STATIC_ASSERT 0\n#endif\n\n#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \\\n  (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600\n# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message)\n#else\n# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b)\n# define FMT_STATIC_ASSERT(cond, message) \\\n  typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED\n#endif\n\ntemplate <typename Formatter>\nvoid format_arg(Formatter&, ...) {\n  FMT_STATIC_ASSERT(FalseType<Formatter>::value,\n                    \"Cannot format argument. To enable the use of ostream \"\n                    \"operator<< include fmt/ostream.h. Otherwise provide \"\n                    \"an overload of format_arg.\");\n}\n\n// Makes an Arg object from any type.\ntemplate <typename Formatter>\nclass MakeValue : public Arg {\n public:\n  typedef typename Formatter::Char Char;\n\n private:\n  // The following two methods are private to disallow formatting of\n  // arbitrary pointers. If you want to output a pointer cast it to\n  // \"void *\" or \"const void *\". In particular, this forbids formatting\n  // of \"[const] volatile char *\" which is printed as bool by iostreams.\n  // Do not implement!\n  template <typename T>\n  MakeValue(const T *value);\n  template <typename T>\n  MakeValue(T *value);\n\n  // The following methods are private to disallow formatting of wide\n  // characters and strings into narrow strings as in\n  //   fmt::format(\"{}\", L\"test\");\n  // To fix this, use a wide format string: fmt::format(L\"{}\", L\"test\").\n#if !FMT_MSC_VER || defined(_NATIVE_WCHAR_T_DEFINED)\n  MakeValue(typename WCharHelper<wchar_t, Char>::Unsupported);\n#endif\n  MakeValue(typename WCharHelper<wchar_t *, Char>::Unsupported);\n  MakeValue(typename WCharHelper<const wchar_t *, Char>::Unsupported);\n  MakeValue(typename WCharHelper<const std::wstring &, Char>::Unsupported);\n#if FMT_HAS_STRING_VIEW\n  MakeValue(typename WCharHelper<const std::wstring_view &, Char>::Unsupported);\n#endif\n  MakeValue(typename WCharHelper<WStringRef, Char>::Unsupported);\n\n  void set_string(StringRef str) {\n    string.value = str.data();\n    string.size = str.size();\n  }\n\n  void set_string(WStringRef str) {\n    wstring.value = str.data();\n    wstring.size = str.size();\n  }\n\n  // Formats an argument of a custom type, such as a user-defined class.\n  template <typename T>\n  static void format_custom_arg(\n      void *formatter, const void *arg, void *format_str_ptr) {\n    format_arg(*static_cast<Formatter*>(formatter),\n               *static_cast<const Char**>(format_str_ptr),\n               *static_cast<const T*>(arg));\n  }\n\n public:\n  MakeValue() {}\n\n#define FMT_MAKE_VALUE_(Type, field, TYPE, rhs) \\\n  MakeValue(Type value) { field = rhs; } \\\n  static uint64_t type(Type) { return Arg::TYPE; }\n\n#define FMT_MAKE_VALUE(Type, field, TYPE) \\\n  FMT_MAKE_VALUE_(Type, field, TYPE, value)\n\n  FMT_MAKE_VALUE(bool, int_value, BOOL)\n  FMT_MAKE_VALUE(short, int_value, INT)\n  FMT_MAKE_VALUE(unsigned short, uint_value, UINT)\n  FMT_MAKE_VALUE(int, int_value, INT)\n  FMT_MAKE_VALUE(unsigned, uint_value, UINT)\n\n  MakeValue(long value) {\n    // To minimize the number of types we need to deal with, long is\n    // translated either to int or to long long depending on its size.\n    if (const_check(sizeof(long) == sizeof(int)))\n      int_value = static_cast<int>(value);\n    else\n      long_long_value = value;\n  }\n  static uint64_t type(long) {\n    return sizeof(long) == sizeof(int) ? Arg::INT : Arg::LONG_LONG;\n  }\n\n  MakeValue(unsigned long value) {\n    if (const_check(sizeof(unsigned long) == sizeof(unsigned)))\n      uint_value = static_cast<unsigned>(value);\n    else\n      ulong_long_value = value;\n  }\n  static uint64_t type(unsigned long) {\n    return sizeof(unsigned long) == sizeof(unsigned) ?\n          Arg::UINT : Arg::ULONG_LONG;\n  }\n\n  FMT_MAKE_VALUE(LongLong, long_long_value, LONG_LONG)\n  FMT_MAKE_VALUE(ULongLong, ulong_long_value, ULONG_LONG)\n  FMT_MAKE_VALUE(float, double_value, DOUBLE)\n  FMT_MAKE_VALUE(double, double_value, DOUBLE)\n  FMT_MAKE_VALUE(long double, long_double_value, LONG_DOUBLE)\n  FMT_MAKE_VALUE(signed char, int_value, INT)\n  FMT_MAKE_VALUE(unsigned char, uint_value, UINT)\n  FMT_MAKE_VALUE(char, int_value, CHAR)\n\n#if __cplusplus >= 201103L\n  template <\n    typename T,\n    typename = typename std::enable_if<\n      std::is_enum<T>::value && ConvertToInt<T>::value>::type>\n   MakeValue(T value) { int_value = value; }\n\n  template <\n    typename T,\n    typename = typename std::enable_if<\n      std::is_enum<T>::value && ConvertToInt<T>::value>::type>\n  static uint64_t type(T) { return Arg::INT; }\n#endif\n\n#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED)\n  MakeValue(typename WCharHelper<wchar_t, Char>::Supported value) {\n    int_value = value;\n  }\n  static uint64_t type(wchar_t) { return Arg::CHAR; }\n#endif\n\n#define FMT_MAKE_STR_VALUE(Type, TYPE) \\\n  MakeValue(Type value) { set_string(value); } \\\n  static uint64_t type(Type) { return Arg::TYPE; }\n\n  FMT_MAKE_VALUE(char *, string.value, CSTRING)\n  FMT_MAKE_VALUE(const char *, string.value, CSTRING)\n  FMT_MAKE_VALUE(signed char *, sstring.value, CSTRING)\n  FMT_MAKE_VALUE(const signed char *, sstring.value, CSTRING)\n  FMT_MAKE_VALUE(unsigned char *, ustring.value, CSTRING)\n  FMT_MAKE_VALUE(const unsigned char *, ustring.value, CSTRING)\n  FMT_MAKE_STR_VALUE(const std::string &, STRING)\n#if FMT_HAS_STRING_VIEW\n  FMT_MAKE_STR_VALUE(const std::string_view &, STRING)\n#endif\n  FMT_MAKE_STR_VALUE(StringRef, STRING)\n  FMT_MAKE_VALUE_(CStringRef, string.value, CSTRING, value.c_str())\n\n#define FMT_MAKE_WSTR_VALUE(Type, TYPE) \\\n  MakeValue(typename WCharHelper<Type, Char>::Supported value) { \\\n    set_string(value); \\\n  } \\\n  static uint64_t type(Type) { return Arg::TYPE; }\n\n  FMT_MAKE_WSTR_VALUE(wchar_t *, WSTRING)\n  FMT_MAKE_WSTR_VALUE(const wchar_t *, WSTRING)\n  FMT_MAKE_WSTR_VALUE(const std::wstring &, WSTRING)\n#if FMT_HAS_STRING_VIEW\n  FMT_MAKE_WSTR_VALUE(const std::wstring_view &, WSTRING)\n#endif\n  FMT_MAKE_WSTR_VALUE(WStringRef, WSTRING)\n\n  FMT_MAKE_VALUE(void *, pointer, POINTER)\n  FMT_MAKE_VALUE(const void *, pointer, POINTER)\n\n  template <typename T>\n  MakeValue(const T &value,\n            typename EnableIf<Not<\n              ConvertToInt<T>::value>::value, int>::type = 0) {\n    custom.value = &value;\n    custom.format = &format_custom_arg<T>;\n  }\n\n  template <typename T>\n  static typename EnableIf<Not<ConvertToInt<T>::value>::value, uint64_t>::type\n      type(const T &) {\n    return Arg::CUSTOM;\n  }\n\n  // Additional template param `Char_` is needed here because make_type always\n  // uses char.\n  template <typename Char_>\n  MakeValue(const NamedArg<Char_> &value) { pointer = &value; }\n  template <typename Char_, typename T>\n  MakeValue(const NamedArgWithType<Char_, T> &value) { pointer = &value; }\n\n  template <typename Char_>\n  static uint64_t type(const NamedArg<Char_> &) { return Arg::NAMED_ARG; }\n  template <typename Char_, typename T>\n  static uint64_t type(const NamedArgWithType<Char_, T> &) { return Arg::NAMED_ARG; }\n};\n\ntemplate <typename Formatter>\nclass MakeArg : public Arg {\npublic:\n  MakeArg() {\n    type = Arg::NONE;\n  }\n\n  template <typename T>\n  MakeArg(const T &value)\n  : Arg(MakeValue<Formatter>(value)) {\n    type = static_cast<Arg::Type>(MakeValue<Formatter>::type(value));\n  }\n};\n\ntemplate <typename Char>\nstruct NamedArg : Arg {\n  BasicStringRef<Char> name;\n\n  template <typename T>\n  NamedArg(BasicStringRef<Char> argname, const T &value)\n  : Arg(MakeArg< BasicFormatter<Char> >(value)), name(argname) {}\n};\n\ntemplate <typename Char, typename T>\nstruct NamedArgWithType : NamedArg<Char> {\n  NamedArgWithType(BasicStringRef<Char> argname, const T &value)\n  : NamedArg<Char>(argname, value) {}\n};\n\nclass RuntimeError : public std::runtime_error {\n protected:\n  RuntimeError() : std::runtime_error(\"\") {}\n  RuntimeError(const RuntimeError &rerr) : std::runtime_error(rerr) {}\n  FMT_API ~RuntimeError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE;\n};\n\ntemplate <typename Char>\nclass ArgMap;\n}  // namespace internal\n\n/** An argument list. */\nclass ArgList {\n private:\n  // To reduce compiled code size per formatting function call, types of first\n  // MAX_PACKED_ARGS arguments are passed in the types_ field.\n  uint64_t types_;\n  union {\n    // If the number of arguments is less than MAX_PACKED_ARGS, the argument\n    // values are stored in values_, otherwise they are stored in args_.\n    // This is done to reduce compiled code size as storing larger objects\n    // may require more code (at least on x86-64) even if the same amount of\n    // data is actually copied to stack. It saves ~10% on the bloat test.\n    const internal::Value *values_;\n    const internal::Arg *args_;\n  };\n\n  internal::Arg::Type type(unsigned index) const {\n    return type(types_, index);\n  }\n\n  template <typename Char>\n  friend class internal::ArgMap;\n\n public:\n  // Maximum number of arguments with packed types.\n  enum { MAX_PACKED_ARGS = 16 };\n\n  ArgList() : types_(0) {}\n\n  ArgList(ULongLong types, const internal::Value *values)\n  : types_(types), values_(values) {}\n  ArgList(ULongLong types, const internal::Arg *args)\n  : types_(types), args_(args) {}\n\n  uint64_t types() const { return types_; }\n\n  /** Returns the argument at specified index. */\n  internal::Arg operator[](unsigned index) const {\n    using internal::Arg;\n    Arg arg;\n    bool use_values = type(MAX_PACKED_ARGS - 1) == Arg::NONE;\n    if (index < MAX_PACKED_ARGS) {\n      Arg::Type arg_type = type(index);\n      internal::Value &val = arg;\n      if (arg_type != Arg::NONE)\n        val = use_values ? values_[index] : args_[index];\n      arg.type = arg_type;\n      return arg;\n    }\n    if (use_values) {\n      // The index is greater than the number of arguments that can be stored\n      // in values, so return a \"none\" argument.\n      arg.type = Arg::NONE;\n      return arg;\n    }\n    for (unsigned i = MAX_PACKED_ARGS; i <= index; ++i) {\n      if (args_[i].type == Arg::NONE)\n        return args_[i];\n    }\n    return args_[index];\n  }\n\n  static internal::Arg::Type type(uint64_t types, unsigned index) {\n    unsigned shift = index * 4;\n    uint64_t mask = 0xf;\n    return static_cast<internal::Arg::Type>(\n          (types & (mask << shift)) >> shift);\n  }\n};\n\n#define FMT_DISPATCH(call) static_cast<Impl*>(this)->call\n\n/**\n  \\rst\n  An argument visitor based on the `curiously recurring template pattern\n  <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_.\n\n  To use `~fmt::ArgVisitor` define a subclass that implements some or all of the\n  visit methods with the same signatures as the methods in `~fmt::ArgVisitor`,\n  for example, `~fmt::ArgVisitor::visit_int()`.\n  Pass the subclass as the *Impl* template parameter. Then calling\n  `~fmt::ArgVisitor::visit` for some argument will dispatch to a visit method\n  specific to the argument type. For example, if the argument type is\n  ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass\n  will be called. If the subclass doesn't contain a method with this signature,\n  then a corresponding method of `~fmt::ArgVisitor` will be called.\n\n  **Example**::\n\n    class MyArgVisitor : public fmt::ArgVisitor<MyArgVisitor, void> {\n     public:\n      void visit_int(int value) { fmt::print(\"{}\", value); }\n      void visit_double(double value) { fmt::print(\"{}\", value ); }\n    };\n  \\endrst\n */\ntemplate <typename Impl, typename Result>\nclass ArgVisitor {\n private:\n  typedef internal::Arg Arg;\n\n public:\n  void report_unhandled_arg() {}\n\n  Result visit_unhandled_arg() {\n    FMT_DISPATCH(report_unhandled_arg());\n    return Result();\n  }\n\n  /** Visits an ``int`` argument. **/\n  Result visit_int(int value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits a ``long long`` argument. **/\n  Result visit_long_long(LongLong value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits an ``unsigned`` argument. **/\n  Result visit_uint(unsigned value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits an ``unsigned long long`` argument. **/\n  Result visit_ulong_long(ULongLong value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits a ``bool`` argument. **/\n  Result visit_bool(bool value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits a ``char`` or ``wchar_t`` argument. **/\n  Result visit_char(int value) {\n    return FMT_DISPATCH(visit_any_int(value));\n  }\n\n  /** Visits an argument of any integral type. **/\n  template <typename T>\n  Result visit_any_int(T) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a ``double`` argument. **/\n  Result visit_double(double value) {\n    return FMT_DISPATCH(visit_any_double(value));\n  }\n\n  /** Visits a ``long double`` argument. **/\n  Result visit_long_double(long double value) {\n    return FMT_DISPATCH(visit_any_double(value));\n  }\n\n  /** Visits a ``double`` or ``long double`` argument. **/\n  template <typename T>\n  Result visit_any_double(T) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a null-terminated C string (``const char *``) argument. **/\n  Result visit_cstring(const char *) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a string argument. **/\n  Result visit_string(Arg::StringValue<char>) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a wide string argument. **/\n  Result visit_wstring(Arg::StringValue<wchar_t>) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits a pointer argument. **/\n  Result visit_pointer(const void *) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /** Visits an argument of a custom (user-defined) type. **/\n  Result visit_custom(Arg::CustomValue) {\n    return FMT_DISPATCH(visit_unhandled_arg());\n  }\n\n  /**\n    \\rst\n    Visits an argument dispatching to the appropriate visit method based on\n    the argument type. For example, if the argument type is ``double`` then\n    the `~fmt::ArgVisitor::visit_double()` method of the *Impl* class will be\n    called.\n    \\endrst\n   */\n  Result visit(const Arg &arg) {\n    switch (arg.type) {\n    case Arg::NONE:\n    case Arg::NAMED_ARG:\n      FMT_ASSERT(false, \"invalid argument type\");\n      break;\n    case Arg::INT:\n      return FMT_DISPATCH(visit_int(arg.int_value));\n    case Arg::UINT:\n      return FMT_DISPATCH(visit_uint(arg.uint_value));\n    case Arg::LONG_LONG:\n      return FMT_DISPATCH(visit_long_long(arg.long_long_value));\n    case Arg::ULONG_LONG:\n      return FMT_DISPATCH(visit_ulong_long(arg.ulong_long_value));\n    case Arg::BOOL:\n      return FMT_DISPATCH(visit_bool(arg.int_value != 0));\n    case Arg::CHAR:\n      return FMT_DISPATCH(visit_char(arg.int_value));\n    case Arg::DOUBLE:\n      return FMT_DISPATCH(visit_double(arg.double_value));\n    case Arg::LONG_DOUBLE:\n      return FMT_DISPATCH(visit_long_double(arg.long_double_value));\n    case Arg::CSTRING:\n      return FMT_DISPATCH(visit_cstring(arg.string.value));\n    case Arg::STRING:\n      return FMT_DISPATCH(visit_string(arg.string));\n    case Arg::WSTRING:\n      return FMT_DISPATCH(visit_wstring(arg.wstring));\n    case Arg::POINTER:\n      return FMT_DISPATCH(visit_pointer(arg.pointer));\n    case Arg::CUSTOM:\n      return FMT_DISPATCH(visit_custom(arg.custom));\n    }\n    return Result();\n  }\n};\n\nenum Alignment {\n  ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC\n};\n\n// Flags.\nenum {\n  SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8,\n  CHAR_FLAG = 0x10  // Argument has char type - used in error reporting.\n};\n\n// An empty format specifier.\nstruct EmptySpec {};\n\n// A type specifier.\ntemplate <char TYPE>\nstruct TypeSpec : EmptySpec {\n  Alignment align() const { return ALIGN_DEFAULT; }\n  unsigned width() const { return 0; }\n  int precision() const { return -1; }\n  bool flag(unsigned) const { return false; }\n  char type() const { return TYPE; }\n  char type_prefix() const { return TYPE; }\n  char fill() const { return ' '; }\n};\n\n// A width specifier.\nstruct WidthSpec {\n  unsigned width_;\n  // Fill is always wchar_t and cast to char if necessary to avoid having\n  // two specialization of WidthSpec and its subclasses.\n  wchar_t fill_;\n\n  WidthSpec(unsigned width, wchar_t fill) : width_(width), fill_(fill) {}\n\n  unsigned width() const { return width_; }\n  wchar_t fill() const { return fill_; }\n};\n\n// An alignment specifier.\nstruct AlignSpec : WidthSpec {\n  Alignment align_;\n\n  AlignSpec(unsigned width, wchar_t fill, Alignment align = ALIGN_DEFAULT)\n  : WidthSpec(width, fill), align_(align) {}\n\n  Alignment align() const { return align_; }\n\n  int precision() const { return -1; }\n};\n\n// An alignment and type specifier.\ntemplate <char TYPE>\nstruct AlignTypeSpec : AlignSpec {\n  AlignTypeSpec(unsigned width, wchar_t fill) : AlignSpec(width, fill) {}\n\n  bool flag(unsigned) const { return false; }\n  char type() const { return TYPE; }\n  char type_prefix() const { return TYPE; }\n};\n\n// A full format specifier.\nstruct FormatSpec : AlignSpec {\n  unsigned flags_;\n  int precision_;\n  char type_;\n\n  FormatSpec(\n    unsigned width = 0, char type = 0, wchar_t fill = ' ')\n  : AlignSpec(width, fill), flags_(0), precision_(-1), type_(type) {}\n\n  bool flag(unsigned f) const { return (flags_ & f) != 0; }\n  int precision() const { return precision_; }\n  char type() const { return type_; }\n  char type_prefix() const { return type_; }\n};\n\n// An integer format specifier.\ntemplate <typename T, typename SpecT = TypeSpec<0>, typename Char = char>\nclass IntFormatSpec : public SpecT {\n private:\n  T value_;\n\n public:\n  IntFormatSpec(T val, const SpecT &spec = SpecT())\n  : SpecT(spec), value_(val) {}\n\n  T value() const { return value_; }\n};\n\n// A string format specifier.\ntemplate <typename Char>\nclass StrFormatSpec : public AlignSpec {\n private:\n  const Char *str_;\n\n public:\n  template <typename FillChar>\n  StrFormatSpec(const Char *str, unsigned width, FillChar fill)\n  : AlignSpec(width, fill), str_(str) {\n    internal::CharTraits<Char>::convert(FillChar());\n  }\n\n  const Char *str() const { return str_; }\n};\n\n/**\n  Returns an integer format specifier to format the value in base 2.\n */\nIntFormatSpec<int, TypeSpec<'b'> > bin(int value);\n\n/**\n  Returns an integer format specifier to format the value in base 8.\n */\nIntFormatSpec<int, TypeSpec<'o'> > oct(int value);\n\n/**\n  Returns an integer format specifier to format the value in base 16 using\n  lower-case letters for the digits above 9.\n */\nIntFormatSpec<int, TypeSpec<'x'> > hex(int value);\n\n/**\n  Returns an integer formatter format specifier to format in base 16 using\n  upper-case letters for the digits above 9.\n */\nIntFormatSpec<int, TypeSpec<'X'> > hexu(int value);\n\n/**\n  \\rst\n  Returns an integer format specifier to pad the formatted argument with the\n  fill character to the specified width using the default (right) numeric\n  alignment.\n\n  **Example**::\n\n    MemoryWriter out;\n    out << pad(hex(0xcafe), 8, '0');\n    // out.str() == \"0000cafe\"\n\n  \\endrst\n */\ntemplate <char TYPE_CODE, typename Char>\nIntFormatSpec<int, AlignTypeSpec<TYPE_CODE>, Char> pad(\n    int value, unsigned width, Char fill = ' ');\n\n#define FMT_DEFINE_INT_FORMATTERS(TYPE) \\\ninline IntFormatSpec<TYPE, TypeSpec<'b'> > bin(TYPE value) { \\\n  return IntFormatSpec<TYPE, TypeSpec<'b'> >(value, TypeSpec<'b'>()); \\\n} \\\n \\\ninline IntFormatSpec<TYPE, TypeSpec<'o'> > oct(TYPE value) { \\\n  return IntFormatSpec<TYPE, TypeSpec<'o'> >(value, TypeSpec<'o'>()); \\\n} \\\n \\\ninline IntFormatSpec<TYPE, TypeSpec<'x'> > hex(TYPE value) { \\\n  return IntFormatSpec<TYPE, TypeSpec<'x'> >(value, TypeSpec<'x'>()); \\\n} \\\n \\\ninline IntFormatSpec<TYPE, TypeSpec<'X'> > hexu(TYPE value) { \\\n  return IntFormatSpec<TYPE, TypeSpec<'X'> >(value, TypeSpec<'X'>()); \\\n} \\\n \\\ntemplate <char TYPE_CODE> \\\ninline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> > pad( \\\n    IntFormatSpec<TYPE, TypeSpec<TYPE_CODE> > f, unsigned width) { \\\n  return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> >( \\\n      f.value(), AlignTypeSpec<TYPE_CODE>(width, ' ')); \\\n} \\\n \\\n/* For compatibility with older compilers we provide two overloads for pad, */ \\\n/* one that takes a fill character and one that doesn't. In the future this */ \\\n/* can be replaced with one overload making the template argument Char      */ \\\n/* default to char (C++11). */ \\\ntemplate <char TYPE_CODE, typename Char> \\\ninline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char> pad( \\\n    IntFormatSpec<TYPE, TypeSpec<TYPE_CODE>, Char> f, \\\n    unsigned width, Char fill) { \\\n  return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char>( \\\n      f.value(), AlignTypeSpec<TYPE_CODE>(width, fill)); \\\n} \\\n \\\ninline IntFormatSpec<TYPE, AlignTypeSpec<0> > pad( \\\n    TYPE value, unsigned width) { \\\n  return IntFormatSpec<TYPE, AlignTypeSpec<0> >( \\\n      value, AlignTypeSpec<0>(width, ' ')); \\\n} \\\n \\\ntemplate <typename Char> \\\ninline IntFormatSpec<TYPE, AlignTypeSpec<0>, Char> pad( \\\n   TYPE value, unsigned width, Char fill) { \\\n return IntFormatSpec<TYPE, AlignTypeSpec<0>, Char>( \\\n     value, AlignTypeSpec<0>(width, fill)); \\\n}\n\nFMT_DEFINE_INT_FORMATTERS(int)\nFMT_DEFINE_INT_FORMATTERS(long)\nFMT_DEFINE_INT_FORMATTERS(unsigned)\nFMT_DEFINE_INT_FORMATTERS(unsigned long)\nFMT_DEFINE_INT_FORMATTERS(LongLong)\nFMT_DEFINE_INT_FORMATTERS(ULongLong)\n\n/**\n  \\rst\n  Returns a string formatter that pads the formatted argument with the fill\n  character to the specified width using the default (left) string alignment.\n\n  **Example**::\n\n    std::string s = str(MemoryWriter() << pad(\"abc\", 8));\n    // s == \"abc     \"\n\n  \\endrst\n */\ntemplate <typename Char>\ninline StrFormatSpec<Char> pad(\n    const Char *str, unsigned width, Char fill = ' ') {\n  return StrFormatSpec<Char>(str, width, fill);\n}\n\ninline StrFormatSpec<wchar_t> pad(\n    const wchar_t *str, unsigned width, char fill = ' ') {\n  return StrFormatSpec<wchar_t>(str, width, fill);\n}\n\nnamespace internal {\n\ntemplate <typename Char>\nclass ArgMap {\n private:\n  typedef std::vector<\n    std::pair<fmt::BasicStringRef<Char>, internal::Arg> > MapType;\n  typedef typename MapType::value_type Pair;\n\n  MapType map_;\n\n public:\n  void init(const ArgList &args);\n\n  const internal::Arg *find(const fmt::BasicStringRef<Char> &name) const {\n    // The list is unsorted, so just return the first matching name.\n    for (typename MapType::const_iterator it = map_.begin(), end = map_.end();\n         it != end; ++it) {\n      if (it->first == name)\n        return &it->second;\n    }\n    return FMT_NULL;\n  }\n};\n\ntemplate <typename Char>\nvoid ArgMap<Char>::init(const ArgList &args) {\n  if (!map_.empty())\n    return;\n  typedef internal::NamedArg<Char> NamedArg;\n  const NamedArg *named_arg = FMT_NULL;\n  bool use_values =\n      args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE;\n  if (use_values) {\n    for (unsigned i = 0;/*nothing*/; ++i) {\n      internal::Arg::Type arg_type = args.type(i);\n      switch (arg_type) {\n      case internal::Arg::NONE:\n        return;\n      case internal::Arg::NAMED_ARG:\n        named_arg = static_cast<const NamedArg*>(args.values_[i].pointer);\n        map_.push_back(Pair(named_arg->name, *named_arg));\n        break;\n      default:\n        /*nothing*/;\n      }\n    }\n    return;\n  }\n  for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) {\n    internal::Arg::Type arg_type = args.type(i);\n    if (arg_type == internal::Arg::NAMED_ARG) {\n      named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);\n      map_.push_back(Pair(named_arg->name, *named_arg));\n    }\n  }\n  for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) {\n    switch (args.args_[i].type) {\n    case internal::Arg::NONE:\n      return;\n    case internal::Arg::NAMED_ARG:\n      named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);\n      map_.push_back(Pair(named_arg->name, *named_arg));\n      break;\n    default:\n      /*nothing*/;\n    }\n  }\n}\n\ntemplate <typename Impl, typename Char, typename Spec = fmt::FormatSpec>\nclass ArgFormatterBase : public ArgVisitor<Impl, void> {\n private:\n  BasicWriter<Char> &writer_;\n  Spec &spec_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(ArgFormatterBase);\n\n  void write_pointer(const void *p) {\n    spec_.flags_ = HASH_FLAG;\n    spec_.type_ = 'x';\n    writer_.write_int(reinterpret_cast<uintptr_t>(p), spec_);\n  }\n\n  // workaround MSVC two-phase lookup issue\n  typedef internal::Arg Arg;\n\n protected:\n  BasicWriter<Char> &writer() { return writer_; }\n  Spec &spec() { return spec_; }\n\n  void write(bool value) {\n    const char *str_value = value ? \"true\" : \"false\";\n    Arg::StringValue<char> str = { str_value, std::strlen(str_value) };\n    writer_.write_str(str, spec_);\n  }\n\n  void write(const char *value) {\n    Arg::StringValue<char> str = {value, value ? std::strlen(value) : 0};\n    writer_.write_str(str, spec_);\n  }\n\n public:\n  typedef Spec SpecType;\n\n  ArgFormatterBase(BasicWriter<Char> &w, Spec &s)\n  : writer_(w), spec_(s) {}\n\n  template <typename T>\n  void visit_any_int(T value) { writer_.write_int(value, spec_); }\n\n  template <typename T>\n  void visit_any_double(T value) { writer_.write_double(value, spec_); }\n\n  void visit_bool(bool value) {\n    if (spec_.type_) {\n      visit_any_int(value);\n      return;\n    }\n    write(value);\n  }\n\n  void visit_char(int value) {\n    if (spec_.type_ && spec_.type_ != 'c') {\n      spec_.flags_ |= CHAR_FLAG;\n      writer_.write_int(value, spec_);\n      return;\n    }\n    if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0)\n      FMT_THROW(FormatError(\"invalid format specifier for char\"));\n    typedef typename BasicWriter<Char>::CharPtr CharPtr;\n    Char fill = internal::CharTraits<Char>::cast(spec_.fill());\n    CharPtr out = CharPtr();\n    const unsigned CHAR_SIZE = 1;\n    if (spec_.width_ > CHAR_SIZE) {\n      out = writer_.grow_buffer(spec_.width_);\n      if (spec_.align_ == ALIGN_RIGHT) {\n        std::uninitialized_fill_n(out, spec_.width_ - CHAR_SIZE, fill);\n        out += spec_.width_ - CHAR_SIZE;\n      } else if (spec_.align_ == ALIGN_CENTER) {\n        out = writer_.fill_padding(out, spec_.width_,\n                                   internal::const_check(CHAR_SIZE), fill);\n      } else {\n        std::uninitialized_fill_n(out + CHAR_SIZE,\n                                  spec_.width_ - CHAR_SIZE, fill);\n      }\n    } else {\n      out = writer_.grow_buffer(CHAR_SIZE);\n    }\n    *out = internal::CharTraits<Char>::cast(value);\n  }\n\n  void visit_cstring(const char *value) {\n    if (spec_.type_ == 'p')\n      return write_pointer(value);\n    write(value);\n  }\n\n  // Qualification with \"internal\" here and below is a workaround for nvcc.\n  void visit_string(internal::Arg::StringValue<char> value) {\n    writer_.write_str(value, spec_);\n  }\n\n  using ArgVisitor<Impl, void>::visit_wstring;\n\n  void visit_wstring(internal::Arg::StringValue<Char> value) {\n    writer_.write_str(value, spec_);\n  }\n\n  void visit_pointer(const void *value) {\n    if (spec_.type_ && spec_.type_ != 'p')\n      report_unknown_type(spec_.type_, \"pointer\");\n    write_pointer(value);\n  }\n};\n\nclass FormatterBase {\n private:\n  ArgList args_;\n  int next_arg_index_;\n\n  // Returns the argument with specified index.\n  FMT_API Arg do_get_arg(unsigned arg_index, const char *&error);\n\n protected:\n  const ArgList &args() const { return args_; }\n\n  explicit FormatterBase(const ArgList &args) {\n    args_ = args;\n    next_arg_index_ = 0;\n  }\n\n  // Returns the next argument.\n  Arg next_arg(const char *&error) {\n    if (next_arg_index_ >= 0)\n      return do_get_arg(internal::to_unsigned(next_arg_index_++), error);\n    error = \"cannot switch from manual to automatic argument indexing\";\n    return Arg();\n  }\n\n  // Checks if manual indexing is used and returns the argument with\n  // specified index.\n  Arg get_arg(unsigned arg_index, const char *&error) {\n    return check_no_auto_index(error) ? do_get_arg(arg_index, error) : Arg();\n  }\n\n  bool check_no_auto_index(const char *&error) {\n    if (next_arg_index_ > 0) {\n      error = \"cannot switch from automatic to manual argument indexing\";\n      return false;\n    }\n    next_arg_index_ = -1;\n    return true;\n  }\n\n  template <typename Char>\n  void write(BasicWriter<Char> &w, const Char *start, const Char *end) {\n    if (start != end)\n      w << BasicStringRef<Char>(start, internal::to_unsigned(end - start));\n  }\n};\n}  // namespace internal\n\n/**\n  \\rst\n  An argument formatter based on the `curiously recurring template pattern\n  <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_.\n\n  To use `~fmt::BasicArgFormatter` define a subclass that implements some or\n  all of the visit methods with the same signatures as the methods in\n  `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`.\n  Pass the subclass as the *Impl* template parameter. When a formatting\n  function processes an argument, it will dispatch to a visit method\n  specific to the argument type. For example, if the argument type is\n  ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass\n  will be called. If the subclass doesn't contain a method with this signature,\n  then a corresponding method of `~fmt::BasicArgFormatter` or its superclass\n  will be called.\n  \\endrst\n */\ntemplate <typename Impl, typename Char, typename Spec = fmt::FormatSpec>\nclass BasicArgFormatter : public internal::ArgFormatterBase<Impl, Char, Spec> {\n private:\n  BasicFormatter<Char, Impl> &formatter_;\n  const Char *format_;\n\n public:\n  /**\n    \\rst\n    Constructs an argument formatter object.\n    *formatter* is a reference to the main formatter object, *spec* contains\n    format specifier information for standard argument types, and *fmt* points\n    to the part of the format string being parsed for custom argument types.\n    \\endrst\n   */\n  BasicArgFormatter(BasicFormatter<Char, Impl> &formatter,\n                    Spec &spec, const Char *fmt)\n  : internal::ArgFormatterBase<Impl, Char, Spec>(formatter.writer(), spec),\n    formatter_(formatter), format_(fmt) {}\n\n  /** Formats an argument of a custom (user-defined) type. */\n  void visit_custom(internal::Arg::CustomValue c) {\n    c.format(&formatter_, c.value, &format_);\n  }\n};\n\n/** The default argument formatter. */\ntemplate <typename Char>\nclass ArgFormatter :\n    public BasicArgFormatter<ArgFormatter<Char>, Char, FormatSpec> {\n public:\n  /** Constructs an argument formatter object. */\n  ArgFormatter(BasicFormatter<Char> &formatter,\n               FormatSpec &spec, const Char *fmt)\n  : BasicArgFormatter<ArgFormatter<Char>,\n                      Char, FormatSpec>(formatter, spec, fmt) {}\n};\n\n/** This template formats data and writes the output to a writer. */\ntemplate <typename CharType, typename ArgFormatter>\nclass BasicFormatter : private internal::FormatterBase {\n public:\n  /** The character type for the output. */\n  typedef CharType Char;\n\n private:\n  BasicWriter<Char> &writer_;\n  internal::ArgMap<Char> map_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter);\n\n  using internal::FormatterBase::get_arg;\n\n  // Checks if manual indexing is used and returns the argument with\n  // specified name.\n  internal::Arg get_arg(BasicStringRef<Char> arg_name, const char *&error);\n\n  // Parses argument index and returns corresponding argument.\n  internal::Arg parse_arg_index(const Char *&s);\n\n  // Parses argument name and returns corresponding argument.\n  internal::Arg parse_arg_name(const Char *&s);\n\n public:\n  /**\n   \\rst\n   Constructs a ``BasicFormatter`` object. References to the arguments and\n   the writer are stored in the formatter object so make sure they have\n   appropriate lifetimes.\n   \\endrst\n   */\n  BasicFormatter(const ArgList &args, BasicWriter<Char> &w)\n    : internal::FormatterBase(args), writer_(w) {}\n\n  /** Returns a reference to the writer associated with this formatter. */\n  BasicWriter<Char> &writer() { return writer_; }\n\n  /** Formats stored arguments and writes the output to the writer. */\n  void format(BasicCStringRef<Char> format_str);\n\n  // Formats a single argument and advances format_str, a format string pointer.\n  const Char *format(const Char *&format_str, const internal::Arg &arg);\n};\n\n// Generates a comma-separated list with results of applying f to\n// numbers 0..n-1.\n# define FMT_GEN(n, f) FMT_GEN##n(f)\n# define FMT_GEN1(f)  f(0)\n# define FMT_GEN2(f)  FMT_GEN1(f),  f(1)\n# define FMT_GEN3(f)  FMT_GEN2(f),  f(2)\n# define FMT_GEN4(f)  FMT_GEN3(f),  f(3)\n# define FMT_GEN5(f)  FMT_GEN4(f),  f(4)\n# define FMT_GEN6(f)  FMT_GEN5(f),  f(5)\n# define FMT_GEN7(f)  FMT_GEN6(f),  f(6)\n# define FMT_GEN8(f)  FMT_GEN7(f),  f(7)\n# define FMT_GEN9(f)  FMT_GEN8(f),  f(8)\n# define FMT_GEN10(f) FMT_GEN9(f),  f(9)\n# define FMT_GEN11(f) FMT_GEN10(f), f(10)\n# define FMT_GEN12(f) FMT_GEN11(f), f(11)\n# define FMT_GEN13(f) FMT_GEN12(f), f(12)\n# define FMT_GEN14(f) FMT_GEN13(f), f(13)\n# define FMT_GEN15(f) FMT_GEN14(f), f(14)\n\nnamespace internal {\ninline uint64_t make_type() { return 0; }\n\ntemplate <typename T>\ninline uint64_t make_type(const T &arg) {\n  return MakeValue< BasicFormatter<char> >::type(arg);\n}\n\ntemplate <std::size_t N, bool/*IsPacked*/= (N < ArgList::MAX_PACKED_ARGS)>\nstruct ArgArray;\n\ntemplate <std::size_t N>\nstruct ArgArray<N, true/*IsPacked*/> {\n  // '+' is used to silence GCC -Wduplicated-branches warning.\n  typedef Value Type[N > 0 ? N : +1];\n\n  template <typename Formatter, typename T>\n  static Value make(const T &value) {\n#ifdef __clang__\n    Value result = MakeValue<Formatter>(value);\n    // Workaround a bug in Apple LLVM version 4.2 (clang-425.0.28) of clang:\n    // https://github.com/fmtlib/fmt/issues/276\n    (void)result.custom.format;\n    return result;\n#else\n    return MakeValue<Formatter>(value);\n#endif\n  }\n};\n\ntemplate <std::size_t N>\nstruct ArgArray<N, false/*IsPacked*/> {\n  typedef Arg Type[N + 1]; // +1 for the list end Arg::NONE\n\n  template <typename Formatter, typename T>\n  static Arg make(const T &value) { return MakeArg<Formatter>(value); }\n};\n\n#if FMT_USE_VARIADIC_TEMPLATES\ntemplate <typename Arg, typename... Args>\ninline uint64_t make_type(const Arg &first, const Args & ... tail) {\n  return make_type(first) | (make_type(tail...) << 4);\n}\n\n#else\n\nstruct ArgType {\n  uint64_t type;\n\n  ArgType() : type(0) {}\n\n  template <typename T>\n  ArgType(const T &arg) : type(make_type(arg)) {}\n};\n\n# define FMT_ARG_TYPE_DEFAULT(n) ArgType t##n = ArgType()\n\ninline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) {\n  return t0.type | (t1.type << 4) | (t2.type << 8) | (t3.type << 12) |\n      (t4.type << 16) | (t5.type << 20) | (t6.type << 24) | (t7.type << 28) |\n      (t8.type << 32) | (t9.type << 36) | (t10.type << 40) | (t11.type << 44) |\n      (t12.type << 48) | (t13.type << 52) | (t14.type << 56);\n}\n#endif\n}  // namespace internal\n\n# define FMT_MAKE_TEMPLATE_ARG(n) typename T##n\n# define FMT_MAKE_ARG_TYPE(n) T##n\n# define FMT_MAKE_ARG(n) const T##n &v##n\n# define FMT_ASSIGN_char(n) \\\n  arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter<char> >(v##n)\n# define FMT_ASSIGN_wchar_t(n) \\\n  arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter<wchar_t> >(v##n)\n\n#if FMT_USE_VARIADIC_TEMPLATES\n// Defines a variadic function returning void.\n# define FMT_VARIADIC_VOID(func, arg_type) \\\n  template <typename... Args> \\\n  void func(arg_type arg0, const Args & ... args) { \\\n    typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \\\n    typename ArgArray::Type array{ \\\n      ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \\\n    func(arg0, fmt::ArgList(fmt::internal::make_type(args...), array)); \\\n  }\n\n// Defines a variadic constructor.\n# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \\\n  template <typename... Args> \\\n  ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \\\n    typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \\\n    typename ArgArray::Type array{ \\\n      ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \\\n    func(arg0, arg1, fmt::ArgList(fmt::internal::make_type(args...), array)); \\\n  }\n\n#else\n\n# define FMT_MAKE_REF(n) \\\n  fmt::internal::MakeValue< fmt::BasicFormatter<Char> >(v##n)\n# define FMT_MAKE_REF2(n) v##n\n\n// Defines a wrapper for a function taking one argument of type arg_type\n// and n additional arguments of arbitrary types.\n# define FMT_WRAP1(func, arg_type, n) \\\n  template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \\\n  inline void func(arg_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \\\n    const fmt::internal::ArgArray<n>::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \\\n    func(arg1, fmt::ArgList( \\\n      fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \\\n  }\n\n// Emulates a variadic function returning void on a pre-C++11 compiler.\n# define FMT_VARIADIC_VOID(func, arg_type) \\\n  inline void func(arg_type arg) { func(arg, fmt::ArgList()); } \\\n  FMT_WRAP1(func, arg_type, 1) FMT_WRAP1(func, arg_type, 2) \\\n  FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) \\\n  FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \\\n  FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \\\n  FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10)\n\n# define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \\\n  template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \\\n  ctor(arg0_type arg0, arg1_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \\\n    const fmt::internal::ArgArray<n>::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \\\n    func(arg0, arg1, fmt::ArgList( \\\n      fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \\\n  }\n\n// Emulates a variadic constructor on a pre-C++11 compiler.\n# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 1) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 2) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 3) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 4) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 5) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 6) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 7) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 8) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 9) \\\n  FMT_CTOR(ctor, func, arg0_type, arg1_type, 10)\n#endif\n\n// Generates a comma-separated list with results of applying f to pairs\n// (argument, index).\n#define FMT_FOR_EACH1(f, x0) f(x0, 0)\n#define FMT_FOR_EACH2(f, x0, x1) \\\n  FMT_FOR_EACH1(f, x0), f(x1, 1)\n#define FMT_FOR_EACH3(f, x0, x1, x2) \\\n  FMT_FOR_EACH2(f, x0 ,x1), f(x2, 2)\n#define FMT_FOR_EACH4(f, x0, x1, x2, x3) \\\n  FMT_FOR_EACH3(f, x0, x1, x2), f(x3, 3)\n#define FMT_FOR_EACH5(f, x0, x1, x2, x3, x4) \\\n  FMT_FOR_EACH4(f, x0, x1, x2, x3), f(x4, 4)\n#define FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5) \\\n  FMT_FOR_EACH5(f, x0, x1, x2, x3, x4), f(x5, 5)\n#define FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6) \\\n  FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5), f(x6, 6)\n#define FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7) \\\n  FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6), f(x7, 7)\n#define FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8) \\\n  FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7), f(x8, 8)\n#define FMT_FOR_EACH10(f, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) \\\n  FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8), f(x9, 9)\n\n/**\n An error returned by an operating system or a language runtime,\n for example a file opening error.\n*/\nclass SystemError : public internal::RuntimeError {\n private:\n  FMT_API void init(int err_code, CStringRef format_str, ArgList args);\n\n protected:\n  int error_code_;\n\n  typedef char Char;  // For FMT_VARIADIC_CTOR.\n\n  SystemError() {}\n\n public:\n  /**\n   \\rst\n   Constructs a :class:`fmt::SystemError` object with a description\n   formatted with `fmt::format_system_error`. *message* and additional\n   arguments passed into the constructor are formatted similarly to\n   `fmt::format`.\n\n   **Example**::\n\n     // This throws a SystemError with the description\n     //   cannot open file 'madeup': No such file or directory\n     // or similar (system message may vary).\n     const char *filename = \"madeup\";\n     std::FILE *file = std::fopen(filename, \"r\");\n     if (!file)\n       throw fmt::SystemError(errno, \"cannot open file '{}'\", filename);\n   \\endrst\n  */\n  SystemError(int error_code, CStringRef message) {\n    init(error_code, message, ArgList());\n  }\n  FMT_DEFAULTED_COPY_CTOR(SystemError)\n  FMT_VARIADIC_CTOR(SystemError, init, int, CStringRef)\n\n  FMT_API ~SystemError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE;\n\n  int error_code() const { return error_code_; }\n};\n\n/**\n  \\rst\n  Formats an error returned by an operating system or a language runtime,\n  for example a file opening error, and writes it to *out* in the following\n  form:\n\n  .. parsed-literal::\n     *<message>*: *<system-message>*\n\n  where *<message>* is the passed message and *<system-message>* is\n  the system message corresponding to the error code.\n  *error_code* is a system error code as given by ``errno``.\n  If *error_code* is not a valid error code such as -1, the system message\n  may look like \"Unknown error -1\" and is platform-dependent.\n  \\endrst\n */\nFMT_API void format_system_error(fmt::Writer &out, int error_code,\n                                 fmt::StringRef message) FMT_NOEXCEPT;\n\n/**\n  \\rst\n  This template provides operations for formatting and writing data into\n  a character stream. The output is stored in a buffer provided by a subclass\n  such as :class:`fmt::BasicMemoryWriter`.\n\n  You can use one of the following typedefs for common character types:\n\n  +---------+----------------------+\n  | Type    | Definition           |\n  +=========+======================+\n  | Writer  | BasicWriter<char>    |\n  +---------+----------------------+\n  | WWriter | BasicWriter<wchar_t> |\n  +---------+----------------------+\n\n  \\endrst\n */\ntemplate <typename Char>\nclass BasicWriter {\n private:\n  // Output buffer.\n  Buffer<Char> &buffer_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter);\n\n  typedef typename internal::CharTraits<Char>::CharPtr CharPtr;\n\n#if FMT_SECURE_SCL\n  // Returns pointer value.\n  static Char *get(CharPtr p) { return p.base(); }\n#else\n  static Char *get(Char *p) { return p; }\n#endif\n\n  // Fills the padding around the content and returns the pointer to the\n  // content area.\n  static CharPtr fill_padding(CharPtr buffer,\n      unsigned total_size, std::size_t content_size, wchar_t fill);\n\n  // Grows the buffer by n characters and returns a pointer to the newly\n  // allocated area.\n  CharPtr grow_buffer(std::size_t n) {\n    std::size_t size = buffer_.size();\n    buffer_.resize(size + n);\n    return internal::make_ptr(&buffer_[size], n);\n  }\n\n  // Writes an unsigned decimal integer.\n  template <typename UInt>\n  Char *write_unsigned_decimal(UInt value, unsigned prefix_size = 0) {\n    unsigned num_digits = internal::count_digits(value);\n    Char *ptr = get(grow_buffer(prefix_size + num_digits));\n    internal::format_decimal(ptr + prefix_size, value, num_digits);\n    return ptr;\n  }\n\n  // Writes a decimal integer.\n  template <typename Int>\n  void write_decimal(Int value) {\n    typedef typename internal::IntTraits<Int>::MainType MainType;\n    MainType abs_value = static_cast<MainType>(value);\n    if (internal::is_negative(value)) {\n      abs_value = 0 - abs_value;\n      *write_unsigned_decimal(abs_value, 1) = '-';\n    } else {\n      write_unsigned_decimal(abs_value, 0);\n    }\n  }\n\n  // Prepare a buffer for integer formatting.\n  CharPtr prepare_int_buffer(unsigned num_digits,\n      const EmptySpec &, const char *prefix, unsigned prefix_size) {\n    unsigned size = prefix_size + num_digits;\n    CharPtr p = grow_buffer(size);\n    std::uninitialized_copy(prefix, prefix + prefix_size, p);\n    return p + size - 1;\n  }\n\n  template <typename Spec>\n  CharPtr prepare_int_buffer(unsigned num_digits,\n    const Spec &spec, const char *prefix, unsigned prefix_size);\n\n  // Formats an integer.\n  template <typename T, typename Spec>\n  void write_int(T value, Spec spec);\n\n  // Formats a floating-point number (double or long double).\n  template <typename T, typename Spec>\n  void write_double(T value, const Spec &spec);\n\n  // Writes a formatted string.\n  template <typename StrChar>\n  CharPtr write_str(const StrChar *s, std::size_t size, const AlignSpec &spec);\n\n  template <typename StrChar, typename Spec>\n  void write_str(const internal::Arg::StringValue<StrChar> &str,\n                 const Spec &spec);\n\n  // This following methods are private to disallow writing wide characters\n  // and strings to a char stream. If you want to print a wide string as a\n  // pointer as std::ostream does, cast it to const void*.\n  // Do not implement!\n  void operator<<(typename internal::WCharHelper<wchar_t, Char>::Unsupported);\n  void operator<<(\n      typename internal::WCharHelper<const wchar_t *, Char>::Unsupported);\n\n  // Appends floating-point length specifier to the format string.\n  // The second argument is only used for overload resolution.\n  void append_float_length(Char *&format_ptr, long double) {\n    *format_ptr++ = 'L';\n  }\n\n  template<typename T>\n  void append_float_length(Char *&, T) {}\n\n  template <typename Impl, typename Char_, typename Spec_>\n  friend class internal::ArgFormatterBase;\n\n  template <typename Impl, typename Char_, typename Spec_>\n  friend class BasicPrintfArgFormatter;\n\n protected:\n  /**\n    Constructs a ``BasicWriter`` object.\n   */\n  explicit BasicWriter(Buffer<Char> &b) : buffer_(b) {}\n\n public:\n  /**\n    \\rst\n    Destroys a ``BasicWriter`` object.\n    \\endrst\n   */\n  virtual ~BasicWriter() {}\n\n  /**\n    Returns the total number of characters written.\n   */\n  std::size_t size() const { return buffer_.size(); }\n\n  /**\n    Returns a pointer to the output buffer content. No terminating null\n    character is appended.\n   */\n  const Char *data() const FMT_NOEXCEPT { return &buffer_[0]; }\n\n  /**\n    Returns a pointer to the output buffer content with terminating null\n    character appended.\n   */\n  const Char *c_str() const {\n    std::size_t size = buffer_.size();\n    buffer_.reserve(size + 1);\n    buffer_[size] = '\\0';\n    return &buffer_[0];\n  }\n\n  /**\n    \\rst\n    Returns the content of the output buffer as an `std::string`.\n    \\endrst\n   */\n  std::basic_string<Char> str() const {\n    return std::basic_string<Char>(&buffer_[0], buffer_.size());\n  }\n\n  /**\n    \\rst\n    Writes formatted data.\n\n    *args* is an argument list representing arbitrary arguments.\n\n    **Example**::\n\n       MemoryWriter out;\n       out.write(\"Current point:\\n\");\n       out.write(\"({:+f}, {:+f})\", -3.14, 3.14);\n\n    This will write the following output to the ``out`` object:\n\n    .. code-block:: none\n\n       Current point:\n       (-3.140000, +3.140000)\n\n    The output can be accessed using :func:`data()`, :func:`c_str` or\n    :func:`str` methods.\n\n    See also :ref:`syntax`.\n    \\endrst\n   */\n  void write(BasicCStringRef<Char> format, ArgList args) {\n    BasicFormatter<Char>(args, *this).format(format);\n  }\n  FMT_VARIADIC_VOID(write, BasicCStringRef<Char>)\n\n  BasicWriter &operator<<(int value) {\n    write_decimal(value);\n    return *this;\n  }\n  BasicWriter &operator<<(unsigned value) {\n    return *this << IntFormatSpec<unsigned>(value);\n  }\n  BasicWriter &operator<<(long value) {\n    write_decimal(value);\n    return *this;\n  }\n  BasicWriter &operator<<(unsigned long value) {\n    return *this << IntFormatSpec<unsigned long>(value);\n  }\n  BasicWriter &operator<<(LongLong value) {\n    write_decimal(value);\n    return *this;\n  }\n\n  /**\n    \\rst\n    Formats *value* and writes it to the stream.\n    \\endrst\n   */\n  BasicWriter &operator<<(ULongLong value) {\n    return *this << IntFormatSpec<ULongLong>(value);\n  }\n\n  BasicWriter &operator<<(double value) {\n    write_double(value, FormatSpec());\n    return *this;\n  }\n\n  /**\n    \\rst\n    Formats *value* using the general format for floating-point numbers\n    (``'g'``) and writes it to the stream.\n    \\endrst\n   */\n  BasicWriter &operator<<(long double value) {\n    write_double(value, FormatSpec());\n    return *this;\n  }\n\n  /**\n    Writes a character to the stream.\n   */\n  BasicWriter &operator<<(char value) {\n    buffer_.push_back(value);\n    return *this;\n  }\n\n  BasicWriter &operator<<(\n      typename internal::WCharHelper<wchar_t, Char>::Supported value) {\n    buffer_.push_back(value);\n    return *this;\n  }\n\n  /**\n    \\rst\n    Writes *value* to the stream.\n    \\endrst\n   */\n  BasicWriter &operator<<(fmt::BasicStringRef<Char> value) {\n    const Char *str = value.data();\n    buffer_.append(str, str + value.size());\n    return *this;\n  }\n\n  BasicWriter &operator<<(\n      typename internal::WCharHelper<StringRef, Char>::Supported value) {\n    const char *str = value.data();\n    buffer_.append(str, str + value.size());\n    return *this;\n  }\n\n  template <typename T, typename Spec, typename FillChar>\n  BasicWriter &operator<<(IntFormatSpec<T, Spec, FillChar> spec) {\n    internal::CharTraits<Char>::convert(FillChar());\n    write_int(spec.value(), spec);\n    return *this;\n  }\n\n  template <typename StrChar>\n  BasicWriter &operator<<(const StrFormatSpec<StrChar> &spec) {\n    const StrChar *s = spec.str();\n    write_str(s, std::char_traits<Char>::length(s), spec);\n    return *this;\n  }\n\n  void clear() FMT_NOEXCEPT { buffer_.clear(); }\n\n  Buffer<Char> &buffer() FMT_NOEXCEPT { return buffer_; }\n};\n\ntemplate <typename Char>\ntemplate <typename StrChar>\ntypename BasicWriter<Char>::CharPtr BasicWriter<Char>::write_str(\n      const StrChar *s, std::size_t size, const AlignSpec &spec) {\n  CharPtr out = CharPtr();\n  if (spec.width() > size) {\n    out = grow_buffer(spec.width());\n    Char fill = internal::CharTraits<Char>::cast(spec.fill());\n    if (spec.align() == ALIGN_RIGHT) {\n      std::uninitialized_fill_n(out, spec.width() - size, fill);\n      out += spec.width() - size;\n    } else if (spec.align() == ALIGN_CENTER) {\n      out = fill_padding(out, spec.width(), size, fill);\n    } else {\n      std::uninitialized_fill_n(out + size, spec.width() - size, fill);\n    }\n  } else {\n    out = grow_buffer(size);\n  }\n  std::uninitialized_copy(s, s + size, out);\n  return out;\n}\n\ntemplate <typename Char>\ntemplate <typename StrChar, typename Spec>\nvoid BasicWriter<Char>::write_str(\n    const internal::Arg::StringValue<StrChar> &s, const Spec &spec) {\n  // Check if StrChar is convertible to Char.\n  internal::CharTraits<Char>::convert(StrChar());\n  if (spec.type_ && spec.type_ != 's')\n    internal::report_unknown_type(spec.type_, \"string\");\n  const StrChar *str_value = s.value;\n  std::size_t str_size = s.size;\n  if (str_size == 0) {\n    if (!str_value) {\n      FMT_THROW(FormatError(\"string pointer is null\"));\n    }\n  }\n  std::size_t precision = static_cast<std::size_t>(spec.precision_);\n  if (spec.precision_ >= 0 && precision < str_size)\n    str_size = precision;\n  write_str(str_value, str_size, spec);\n}\n\ntemplate <typename Char>\ntypename BasicWriter<Char>::CharPtr\n  BasicWriter<Char>::fill_padding(\n    CharPtr buffer, unsigned total_size,\n    std::size_t content_size, wchar_t fill) {\n  std::size_t padding = total_size - content_size;\n  std::size_t left_padding = padding / 2;\n  Char fill_char = internal::CharTraits<Char>::cast(fill);\n  std::uninitialized_fill_n(buffer, left_padding, fill_char);\n  buffer += left_padding;\n  CharPtr content = buffer;\n  std::uninitialized_fill_n(buffer + content_size,\n                            padding - left_padding, fill_char);\n  return content;\n}\n\ntemplate <typename Char>\ntemplate <typename Spec>\ntypename BasicWriter<Char>::CharPtr\n  BasicWriter<Char>::prepare_int_buffer(\n    unsigned num_digits, const Spec &spec,\n    const char *prefix, unsigned prefix_size) {\n  unsigned width = spec.width();\n  Alignment align = spec.align();\n  Char fill = internal::CharTraits<Char>::cast(spec.fill());\n  if (spec.precision() > static_cast<int>(num_digits)) {\n    // Octal prefix '0' is counted as a digit, so ignore it if precision\n    // is specified.\n    if (prefix_size > 0 && prefix[prefix_size - 1] == '0')\n      --prefix_size;\n    unsigned number_size =\n        prefix_size + internal::to_unsigned(spec.precision());\n    AlignSpec subspec(number_size, '0', ALIGN_NUMERIC);\n    if (number_size >= width)\n      return prepare_int_buffer(num_digits, subspec, prefix, prefix_size);\n    buffer_.reserve(width);\n    unsigned fill_size = width - number_size;\n    if (align != ALIGN_LEFT) {\n      CharPtr p = grow_buffer(fill_size);\n      std::uninitialized_fill(p, p + fill_size, fill);\n    }\n    CharPtr result = prepare_int_buffer(\n        num_digits, subspec, prefix, prefix_size);\n    if (align == ALIGN_LEFT) {\n      CharPtr p = grow_buffer(fill_size);\n      std::uninitialized_fill(p, p + fill_size, fill);\n    }\n    return result;\n  }\n  unsigned size = prefix_size + num_digits;\n  if (width <= size) {\n    CharPtr p = grow_buffer(size);\n    std::uninitialized_copy(prefix, prefix + prefix_size, p);\n    return p + size - 1;\n  }\n  CharPtr p = grow_buffer(width);\n  CharPtr end = p + width;\n  if (align == ALIGN_LEFT) {\n    std::uninitialized_copy(prefix, prefix + prefix_size, p);\n    p += size;\n    std::uninitialized_fill(p, end, fill);\n  } else if (align == ALIGN_CENTER) {\n    p = fill_padding(p, width, size, fill);\n    std::uninitialized_copy(prefix, prefix + prefix_size, p);\n    p += size;\n  } else {\n    if (align == ALIGN_NUMERIC) {\n      if (prefix_size != 0) {\n        p = std::uninitialized_copy(prefix, prefix + prefix_size, p);\n        size -= prefix_size;\n      }\n    } else {\n      std::uninitialized_copy(prefix, prefix + prefix_size, end - size);\n    }\n    std::uninitialized_fill(p, end - size, fill);\n    p = end;\n  }\n  return p - 1;\n}\n\ntemplate <typename Char>\ntemplate <typename T, typename Spec>\nvoid BasicWriter<Char>::write_int(T value, Spec spec) {\n  unsigned prefix_size = 0;\n  typedef typename internal::IntTraits<T>::MainType UnsignedType;\n  UnsignedType abs_value = static_cast<UnsignedType>(value);\n  char prefix[4] = \"\";\n  if (internal::is_negative(value)) {\n    prefix[0] = '-';\n    ++prefix_size;\n    abs_value = 0 - abs_value;\n  } else if (spec.flag(SIGN_FLAG)) {\n    prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' ';\n    ++prefix_size;\n  }\n  switch (spec.type()) {\n  case 0: case 'd': {\n    unsigned num_digits = internal::count_digits(abs_value);\n    CharPtr p = prepare_int_buffer(num_digits, spec, prefix, prefix_size) + 1;\n    internal::format_decimal(get(p), abs_value, 0);\n    break;\n  }\n  case 'x': case 'X': {\n    UnsignedType n = abs_value;\n    if (spec.flag(HASH_FLAG)) {\n      prefix[prefix_size++] = '0';\n      prefix[prefix_size++] = spec.type_prefix();\n    }\n    unsigned num_digits = 0;\n    do {\n      ++num_digits;\n    } while ((n >>= 4) != 0);\n    Char *p = get(prepare_int_buffer(\n      num_digits, spec, prefix, prefix_size));\n    n = abs_value;\n    const char *digits = spec.type() == 'x' ?\n        \"0123456789abcdef\" : \"0123456789ABCDEF\";\n    do {\n      *p-- = digits[n & 0xf];\n    } while ((n >>= 4) != 0);\n    break;\n  }\n  case 'b': case 'B': {\n    UnsignedType n = abs_value;\n    if (spec.flag(HASH_FLAG)) {\n      prefix[prefix_size++] = '0';\n      prefix[prefix_size++] = spec.type_prefix();\n    }\n    unsigned num_digits = 0;\n    do {\n      ++num_digits;\n    } while ((n >>= 1) != 0);\n    Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size));\n    n = abs_value;\n    do {\n      *p-- = static_cast<Char>('0' + (n & 1));\n    } while ((n >>= 1) != 0);\n    break;\n  }\n  case 'o': {\n    UnsignedType n = abs_value;\n    if (spec.flag(HASH_FLAG))\n      prefix[prefix_size++] = '0';\n    unsigned num_digits = 0;\n    do {\n      ++num_digits;\n    } while ((n >>= 3) != 0);\n    Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size));\n    n = abs_value;\n    do {\n      *p-- = static_cast<Char>('0' + (n & 7));\n    } while ((n >>= 3) != 0);\n    break;\n  }\n  case 'n': {\n    unsigned num_digits = internal::count_digits(abs_value);\n    fmt::StringRef sep = \"\";\n#if !(defined(ANDROID) || defined(__ANDROID__))\n    sep = internal::thousands_sep(std::localeconv());\n#endif\n    unsigned size = static_cast<unsigned>(\n          num_digits + sep.size() * ((num_digits - 1) / 3));\n    CharPtr p = prepare_int_buffer(size, spec, prefix, prefix_size) + 1;\n    internal::format_decimal(get(p), abs_value, 0, internal::ThousandsSep(sep));\n    break;\n  }\n  default:\n    internal::report_unknown_type(\n      spec.type(), spec.flag(CHAR_FLAG) ? \"char\" : \"integer\");\n    break;\n  }\n}\n\ntemplate <typename Char>\ntemplate <typename T, typename Spec>\nvoid BasicWriter<Char>::write_double(T value, const Spec &spec) {\n  // Check type.\n  char type = spec.type();\n  bool upper = false;\n  switch (type) {\n  case 0:\n    type = 'g';\n    break;\n  case 'e': case 'f': case 'g': case 'a':\n    break;\n  case 'F':\n#if FMT_MSC_VER\n    // MSVC's printf doesn't support 'F'.\n    type = 'f';\n#endif\n    // Fall through.\n  case 'E': case 'G': case 'A':\n    upper = true;\n    break;\n  default:\n    internal::report_unknown_type(type, \"double\");\n    break;\n  }\n\n  char sign = 0;\n  // Use isnegative instead of value < 0 because the latter is always\n  // false for NaN.\n  if (internal::FPUtil::isnegative(static_cast<double>(value))) {\n    sign = '-';\n    value = -value;\n  } else if (spec.flag(SIGN_FLAG)) {\n    sign = spec.flag(PLUS_FLAG) ? '+' : ' ';\n  }\n\n  if (internal::FPUtil::isnotanumber(value)) {\n    // Format NaN ourselves because sprintf's output is not consistent\n    // across platforms.\n    std::size_t nan_size = 4;\n    const char *nan = upper ? \" NAN\" : \" nan\";\n    if (!sign) {\n      --nan_size;\n      ++nan;\n    }\n    CharPtr out = write_str(nan, nan_size, spec);\n    if (sign)\n      *out = sign;\n    return;\n  }\n\n  if (internal::FPUtil::isinfinity(value)) {\n    // Format infinity ourselves because sprintf's output is not consistent\n    // across platforms.\n    std::size_t inf_size = 4;\n    const char *inf = upper ? \" INF\" : \" inf\";\n    if (!sign) {\n      --inf_size;\n      ++inf;\n    }\n    CharPtr out = write_str(inf, inf_size, spec);\n    if (sign)\n      *out = sign;\n    return;\n  }\n\n  std::size_t offset = buffer_.size();\n  unsigned width = spec.width();\n  if (sign) {\n    buffer_.reserve(buffer_.size() + (width > 1u ? width : 1u));\n    if (width > 0)\n      --width;\n    ++offset;\n  }\n\n  // Build format string.\n  enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg\n  Char format[MAX_FORMAT_SIZE];\n  Char *format_ptr = format;\n  *format_ptr++ = '%';\n  unsigned width_for_sprintf = width;\n  if (spec.flag(HASH_FLAG))\n    *format_ptr++ = '#';\n  if (spec.align() == ALIGN_CENTER) {\n    width_for_sprintf = 0;\n  } else {\n    if (spec.align() == ALIGN_LEFT)\n      *format_ptr++ = '-';\n    if (width != 0)\n      *format_ptr++ = '*';\n  }\n  if (spec.precision() >= 0) {\n    *format_ptr++ = '.';\n    *format_ptr++ = '*';\n  }\n\n  append_float_length(format_ptr, value);\n  *format_ptr++ = type;\n  *format_ptr = '\\0';\n\n  // Format using snprintf.\n  Char fill = internal::CharTraits<Char>::cast(spec.fill());\n  unsigned n = 0;\n  Char *start = FMT_NULL;\n  for (;;) {\n    std::size_t buffer_size = buffer_.capacity() - offset;\n#if FMT_MSC_VER\n    // MSVC's vsnprintf_s doesn't work with zero size, so reserve\n    // space for at least one extra character to make the size non-zero.\n    // Note that the buffer's capacity will increase by more than 1.\n    if (buffer_size == 0) {\n      buffer_.reserve(offset + 1);\n      buffer_size = buffer_.capacity() - offset;\n    }\n#endif\n    start = &buffer_[offset];\n    int result = internal::CharTraits<Char>::format_float(\n        start, buffer_size, format, width_for_sprintf, spec.precision(), value);\n    if (result >= 0) {\n      n = internal::to_unsigned(result);\n      if (offset + n < buffer_.capacity())\n        break;  // The buffer is large enough - continue with formatting.\n      buffer_.reserve(offset + n + 1);\n    } else {\n      // If result is negative we ask to increase the capacity by at least 1,\n      // but as std::vector, the buffer grows exponentially.\n      buffer_.reserve(buffer_.capacity() + 1);\n    }\n  }\n  if (sign) {\n    if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) ||\n        *start != ' ') {\n      *(start - 1) = sign;\n      sign = 0;\n    } else {\n      *(start - 1) = fill;\n    }\n    ++n;\n  }\n  if (spec.align() == ALIGN_CENTER && spec.width() > n) {\n    width = spec.width();\n    CharPtr p = grow_buffer(width);\n    std::memmove(get(p) + (width - n) / 2, get(p), n * sizeof(Char));\n    fill_padding(p, spec.width(), n, fill);\n    return;\n  }\n  if (spec.fill() != ' ' || sign) {\n    while (*start == ' ')\n      *start++ = fill;\n    if (sign)\n      *(start - 1) = sign;\n  }\n  grow_buffer(n);\n}\n\n/**\n  \\rst\n  This class template provides operations for formatting and writing data\n  into a character stream. The output is stored in a memory buffer that grows\n  dynamically.\n\n  You can use one of the following typedefs for common character types\n  and the standard allocator:\n\n  +---------------+-----------------------------------------------------+\n  | Type          | Definition                                          |\n  +===============+=====================================================+\n  | MemoryWriter  | BasicMemoryWriter<char, std::allocator<char>>       |\n  +---------------+-----------------------------------------------------+\n  | WMemoryWriter | BasicMemoryWriter<wchar_t, std::allocator<wchar_t>> |\n  +---------------+-----------------------------------------------------+\n\n  **Example**::\n\n     MemoryWriter out;\n     out << \"The answer is \" << 42 << \"\\n\";\n     out.write(\"({:+f}, {:+f})\", -3.14, 3.14);\n\n  This will write the following output to the ``out`` object:\n\n  .. code-block:: none\n\n     The answer is 42\n     (-3.140000, +3.140000)\n\n  The output can be converted to an ``std::string`` with ``out.str()`` or\n  accessed as a C string with ``out.c_str()``.\n  \\endrst\n */\ntemplate <typename Char, typename Allocator = std::allocator<Char> >\nclass BasicMemoryWriter : public BasicWriter<Char> {\n private:\n  internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE, Allocator> buffer_;\n\n public:\n  explicit BasicMemoryWriter(const Allocator& alloc = Allocator())\n    : BasicWriter<Char>(buffer_), buffer_(alloc) {}\n\n#if FMT_USE_RVALUE_REFERENCES\n  /**\n    \\rst\n    Constructs a :class:`fmt::BasicMemoryWriter` object moving the content\n    of the other object to it.\n    \\endrst\n   */\n  BasicMemoryWriter(BasicMemoryWriter &&other)\n    : BasicWriter<Char>(buffer_), buffer_(std::move(other.buffer_)) {\n  }\n\n  /**\n    \\rst\n    Moves the content of the other ``BasicMemoryWriter`` object to this one.\n    \\endrst\n   */\n  BasicMemoryWriter &operator=(BasicMemoryWriter &&other) {\n    buffer_ = std::move(other.buffer_);\n    return *this;\n  }\n#endif\n};\n\ntypedef BasicMemoryWriter<char> MemoryWriter;\ntypedef BasicMemoryWriter<wchar_t> WMemoryWriter;\n\n/**\n  \\rst\n  This class template provides operations for formatting and writing data\n  into a fixed-size array. For writing into a dynamically growing buffer\n  use :class:`fmt::BasicMemoryWriter`.\n\n  Any write method will throw ``std::runtime_error`` if the output doesn't fit\n  into the array.\n\n  You can use one of the following typedefs for common character types:\n\n  +--------------+---------------------------+\n  | Type         | Definition                |\n  +==============+===========================+\n  | ArrayWriter  | BasicArrayWriter<char>    |\n  +--------------+---------------------------+\n  | WArrayWriter | BasicArrayWriter<wchar_t> |\n  +--------------+---------------------------+\n  \\endrst\n */\ntemplate <typename Char>\nclass BasicArrayWriter : public BasicWriter<Char> {\n private:\n  internal::FixedBuffer<Char> buffer_;\n\n public:\n  /**\n   \\rst\n   Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the\n   given size.\n   \\endrst\n   */\n  BasicArrayWriter(Char *array, std::size_t size)\n    : BasicWriter<Char>(buffer_), buffer_(array, size) {}\n\n  /**\n   \\rst\n   Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the\n   size known at compile time.\n   \\endrst\n   */\n  template <std::size_t SIZE>\n  explicit BasicArrayWriter(Char (&array)[SIZE])\n    : BasicWriter<Char>(buffer_), buffer_(array, SIZE) {}\n};\n\ntypedef BasicArrayWriter<char> ArrayWriter;\ntypedef BasicArrayWriter<wchar_t> WArrayWriter;\n\n// Reports a system error without throwing an exception.\n// Can be used to report errors from destructors.\nFMT_API void report_system_error(int error_code,\n                                 StringRef message) FMT_NOEXCEPT;\n\n#if FMT_USE_WINDOWS_H\n\n/** A Windows error. */\nclass WindowsError : public SystemError {\n private:\n  FMT_API void init(int error_code, CStringRef format_str, ArgList args);\n\n public:\n  /**\n   \\rst\n   Constructs a :class:`fmt::WindowsError` object with the description\n   of the form\n\n   .. parsed-literal::\n     *<message>*: *<system-message>*\n\n   where *<message>* is the formatted message and *<system-message>* is the\n   system message corresponding to the error code.\n   *error_code* is a Windows error code as given by ``GetLastError``.\n   If *error_code* is not a valid error code such as -1, the system message\n   will look like \"error -1\".\n\n   **Example**::\n\n     // This throws a WindowsError with the description\n     //   cannot open file 'madeup': The system cannot find the file specified.\n     // or similar (system message may vary).\n     const char *filename = \"madeup\";\n     LPOFSTRUCT of = LPOFSTRUCT();\n     HFILE file = OpenFile(filename, &of, OF_READ);\n     if (file == HFILE_ERROR) {\n       throw fmt::WindowsError(GetLastError(),\n                               \"cannot open file '{}'\", filename);\n     }\n   \\endrst\n  */\n  WindowsError(int error_code, CStringRef message) {\n    init(error_code, message, ArgList());\n  }\n  FMT_VARIADIC_CTOR(WindowsError, init, int, CStringRef)\n};\n\n// Reports a Windows error without throwing an exception.\n// Can be used to report errors from destructors.\nFMT_API void report_windows_error(int error_code,\n                                  StringRef message) FMT_NOEXCEPT;\n\n#endif\n\nenum Color { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE };\n\n/**\n  Formats a string and prints it to stdout using ANSI escape sequences\n  to specify color (experimental).\n  Example:\n    print_colored(fmt::RED, \"Elapsed time: {0:.2f} seconds\", 1.23);\n */\nFMT_API void print_colored(Color c, CStringRef format, ArgList args);\n\n/**\n  \\rst\n  Formats arguments and returns the result as a string.\n\n  **Example**::\n\n    std::string message = format(\"The answer is {}\", 42);\n  \\endrst\n*/\ninline std::string format(CStringRef format_str, ArgList args) {\n  MemoryWriter w;\n  w.write(format_str, args);\n  return w.str();\n}\n\ninline std::wstring format(WCStringRef format_str, ArgList args) {\n  WMemoryWriter w;\n  w.write(format_str, args);\n  return w.str();\n}\n\n/**\n  \\rst\n  Prints formatted data to the file *f*.\n\n  **Example**::\n\n    print(stderr, \"Don't {}!\", \"panic\");\n  \\endrst\n */\nFMT_API void print(std::FILE *f, CStringRef format_str, ArgList args);\n\n/**\n  \\rst\n  Prints formatted data to ``stdout``.\n\n  **Example**::\n\n    print(\"Elapsed time: {0:.2f} seconds\", 1.23);\n  \\endrst\n */\nFMT_API void print(CStringRef format_str, ArgList args);\n\n/**\n  Fast integer formatter.\n */\nclass FormatInt {\n private:\n  // Buffer should be large enough to hold all digits (digits10 + 1),\n  // a sign and a null character.\n  enum {BUFFER_SIZE = std::numeric_limits<ULongLong>::digits10 + 3};\n  mutable char buffer_[BUFFER_SIZE];\n  char *str_;\n\n  // Formats value in reverse and returns the number of digits.\n  char *format_decimal(ULongLong value) {\n    char *buffer_end = buffer_ + BUFFER_SIZE - 1;\n    while (value >= 100) {\n      // Integer division is slow so do it for a group of two digits instead\n      // of for every digit. The idea comes from the talk by Alexandrescu\n      // \"Three Optimization Tips for C++\". See speed-test for a comparison.\n      unsigned index = static_cast<unsigned>((value % 100) * 2);\n      value /= 100;\n      *--buffer_end = internal::Data::DIGITS[index + 1];\n      *--buffer_end = internal::Data::DIGITS[index];\n    }\n    if (value < 10) {\n      *--buffer_end = static_cast<char>('0' + value);\n      return buffer_end;\n    }\n    unsigned index = static_cast<unsigned>(value * 2);\n    *--buffer_end = internal::Data::DIGITS[index + 1];\n    *--buffer_end = internal::Data::DIGITS[index];\n    return buffer_end;\n  }\n\n  void FormatSigned(LongLong value) {\n    ULongLong abs_value = static_cast<ULongLong>(value);\n    bool negative = value < 0;\n    if (negative)\n      abs_value = 0 - abs_value;\n    str_ = format_decimal(abs_value);\n    if (negative)\n      *--str_ = '-';\n  }\n\n public:\n  explicit FormatInt(int value) { FormatSigned(value); }\n  explicit FormatInt(long value) { FormatSigned(value); }\n  explicit FormatInt(LongLong value) { FormatSigned(value); }\n  explicit FormatInt(unsigned value) : str_(format_decimal(value)) {}\n  explicit FormatInt(unsigned long value) : str_(format_decimal(value)) {}\n  explicit FormatInt(ULongLong value) : str_(format_decimal(value)) {}\n\n  /** Returns the number of characters written to the output buffer. */\n  std::size_t size() const {\n    return internal::to_unsigned(buffer_ - str_ + BUFFER_SIZE - 1);\n  }\n\n  /**\n    Returns a pointer to the output buffer content. No terminating null\n    character is appended.\n   */\n  const char *data() const { return str_; }\n\n  /**\n    Returns a pointer to the output buffer content with terminating null\n    character appended.\n   */\n  const char *c_str() const {\n    buffer_[BUFFER_SIZE - 1] = '\\0';\n    return str_;\n  }\n\n  /**\n    \\rst\n    Returns the content of the output buffer as an ``std::string``.\n    \\endrst\n   */\n  std::string str() const { return std::string(str_, size()); }\n};\n\n// Formats a decimal integer value writing into buffer and returns\n// a pointer to the end of the formatted string. This function doesn't\n// write a terminating null character.\ntemplate <typename T>\ninline void format_decimal(char *&buffer, T value) {\n  typedef typename internal::IntTraits<T>::MainType MainType;\n  MainType abs_value = static_cast<MainType>(value);\n  if (internal::is_negative(value)) {\n    *buffer++ = '-';\n    abs_value = 0 - abs_value;\n  }\n  if (abs_value < 100) {\n    if (abs_value < 10) {\n      *buffer++ = static_cast<char>('0' + abs_value);\n      return;\n    }\n    unsigned index = static_cast<unsigned>(abs_value * 2);\n    *buffer++ = internal::Data::DIGITS[index];\n    *buffer++ = internal::Data::DIGITS[index + 1];\n    return;\n  }\n  unsigned num_digits = internal::count_digits(abs_value);\n  internal::format_decimal(buffer, abs_value, num_digits);\n  buffer += num_digits;\n}\n\n/**\n  \\rst\n  Returns a named argument for formatting functions.\n\n  **Example**::\n\n    print(\"Elapsed time: {s:.2f} seconds\", arg(\"s\", 1.23));\n\n  \\endrst\n */\ntemplate <typename T>\ninline internal::NamedArgWithType<char, T> arg(StringRef name, const T &arg) {\n  return internal::NamedArgWithType<char, T>(name, arg);\n}\n\ntemplate <typename T>\ninline internal::NamedArgWithType<wchar_t, T> arg(WStringRef name, const T &arg) {\n  return internal::NamedArgWithType<wchar_t, T>(name, arg);\n}\n\n// The following two functions are deleted intentionally to disable\n// nested named arguments as in ``format(\"{}\", arg(\"a\", arg(\"b\", 42)))``.\ntemplate <typename Char>\nvoid arg(StringRef, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED;\ntemplate <typename Char>\nvoid arg(WStringRef, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED;\n}\n\n#if FMT_GCC_VERSION\n// Use the system_header pragma to suppress warnings about variadic macros\n// because suppressing -Wvariadic-macros with the diagnostic pragma doesn't\n// work. It is used at the end because we want to suppress as little warnings\n// as possible.\n# pragma GCC system_header\n#endif\n\n// This is used to work around VC++ bugs in handling variadic macros.\n#define FMT_EXPAND(args) args\n\n// Returns the number of arguments.\n// Based on https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s.\n#define FMT_NARG(...) FMT_NARG_(__VA_ARGS__, FMT_RSEQ_N())\n#define FMT_NARG_(...) FMT_EXPAND(FMT_ARG_N(__VA_ARGS__))\n#define FMT_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N\n#define FMT_RSEQ_N() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0\n\n#define FMT_FOR_EACH_(N, f, ...) \\\n  FMT_EXPAND(FMT_CONCAT(FMT_FOR_EACH, N)(f, __VA_ARGS__))\n#define FMT_FOR_EACH(f, ...) \\\n  FMT_EXPAND(FMT_FOR_EACH_(FMT_NARG(__VA_ARGS__), f, __VA_ARGS__))\n\n#define FMT_ADD_ARG_NAME(type, index) type arg##index\n#define FMT_GET_ARG_NAME(type, index) arg##index\n\n#if FMT_USE_VARIADIC_TEMPLATES\n# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \\\n  template <typename... Args> \\\n  ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \\\n      const Args & ... args) Const { \\\n    typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \\\n    typename ArgArray::Type array{ \\\n      ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \\\n    call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \\\n      fmt::ArgList(fmt::internal::make_type(args...), array)); \\\n  }\n#else\n// Defines a wrapper for a function taking __VA_ARGS__ arguments\n// and n additional arguments of arbitrary types.\n# define FMT_WRAP(Const, Char, ReturnType, func, call, n, ...) \\\n  template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \\\n  inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \\\n      FMT_GEN(n, FMT_MAKE_ARG)) Const { \\\n    fmt::internal::ArgArray<n>::Type arr; \\\n    FMT_GEN(n, FMT_ASSIGN_##Char); \\\n    call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \\\n      fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), arr)); \\\n  }\n\n# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \\\n  inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) Const { \\\n    call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \\\n  } \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 1, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 2, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 3, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 4, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 5, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 6, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 7, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 8, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 9, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 10, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 11, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 12, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 13, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 14, __VA_ARGS__) \\\n  FMT_WRAP(Const, Char, ReturnType, func, call, 15, __VA_ARGS__)\n#endif  // FMT_USE_VARIADIC_TEMPLATES\n\n/**\n  \\rst\n  Defines a variadic function with the specified return type, function name\n  and argument types passed as variable arguments to this macro.\n\n  **Example**::\n\n    void print_error(const char *file, int line, const char *format,\n                     fmt::ArgList args) {\n      fmt::print(\"{}: {}: \", file, line);\n      fmt::print(format, args);\n    }\n    FMT_VARIADIC(void, print_error, const char *, int, const char *)\n\n  ``FMT_VARIADIC`` is used for compatibility with legacy C++ compilers that\n  don't implement variadic templates. You don't have to use this macro if\n  you don't need legacy compiler support and can use variadic templates\n  directly::\n\n    template <typename... Args>\n    void print_error(const char *file, int line, const char *format,\n                     const Args & ... args) {\n      fmt::print(\"{}: {}: \", file, line);\n      fmt::print(format, args...);\n    }\n  \\endrst\n */\n#define FMT_VARIADIC(ReturnType, func, ...) \\\n  FMT_VARIADIC_(, char, ReturnType, func, return func, __VA_ARGS__)\n\n#define FMT_VARIADIC_CONST(ReturnType, func, ...) \\\n  FMT_VARIADIC_(const, char, ReturnType, func, return func, __VA_ARGS__)\n\n#define FMT_VARIADIC_W(ReturnType, func, ...) \\\n  FMT_VARIADIC_(, wchar_t, ReturnType, func, return func, __VA_ARGS__)\n\n#define FMT_VARIADIC_CONST_W(ReturnType, func, ...) \\\n  FMT_VARIADIC_(const, wchar_t, ReturnType, func, return func, __VA_ARGS__)\n\n#define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id)\n\n#define FMT_CAPTURE_ARG_W_(id, index) ::fmt::arg(L###id, id)\n\n/**\n  \\rst\n  Convenient macro to capture the arguments' names and values into several\n  ``fmt::arg(name, value)``.\n\n  **Example**::\n\n    int x = 1, y = 2;\n    print(\"point: ({x}, {y})\", FMT_CAPTURE(x, y));\n    // same as:\n    // print(\"point: ({x}, {y})\", arg(\"x\", x), arg(\"y\", y));\n\n  \\endrst\n */\n#define FMT_CAPTURE(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_, __VA_ARGS__)\n\n#define FMT_CAPTURE_W(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_W_, __VA_ARGS__)\n\nnamespace fmt {\nFMT_VARIADIC(std::string, format, CStringRef)\nFMT_VARIADIC_W(std::wstring, format, WCStringRef)\nFMT_VARIADIC(void, print, CStringRef)\nFMT_VARIADIC(void, print, std::FILE *, CStringRef)\nFMT_VARIADIC(void, print_colored, Color, CStringRef)\n\nnamespace internal {\ntemplate <typename Char>\ninline bool is_name_start(Char c) {\n  return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c;\n}\n\n// Parses an unsigned integer advancing s to the end of the parsed input.\n// This function assumes that the first character of s is a digit.\ntemplate <typename Char>\nunsigned parse_nonnegative_int(const Char *&s) {\n  assert('0' <= *s && *s <= '9');\n  unsigned value = 0;\n  // Convert to unsigned to prevent a warning.\n  unsigned max_int = (std::numeric_limits<int>::max)();\n  unsigned big = max_int / 10;\n  do {\n    // Check for overflow.\n    if (value > big) {\n      value = max_int + 1;\n      break;\n    }\n    value = value * 10 + (*s - '0');\n    ++s;\n  } while ('0' <= *s && *s <= '9');\n  // Convert to unsigned to prevent a warning.\n  if (value > max_int)\n    FMT_THROW(FormatError(\"number is too big\"));\n  return value;\n}\n\ninline void require_numeric_argument(const Arg &arg, char spec) {\n  if (arg.type > Arg::LAST_NUMERIC_TYPE) {\n    std::string message =\n        fmt::format(\"format specifier '{}' requires numeric argument\", spec);\n    FMT_THROW(fmt::FormatError(message));\n  }\n}\n\ntemplate <typename Char>\nvoid check_sign(const Char *&s, const Arg &arg) {\n  char sign = static_cast<char>(*s);\n  require_numeric_argument(arg, sign);\n  if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) {\n    FMT_THROW(FormatError(fmt::format(\n      \"format specifier '{}' requires signed argument\", sign)));\n  }\n  ++s;\n}\n}  // namespace internal\n\ntemplate <typename Char, typename AF>\ninline internal::Arg BasicFormatter<Char, AF>::get_arg(\n    BasicStringRef<Char> arg_name, const char *&error) {\n  if (check_no_auto_index(error)) {\n    map_.init(args());\n    const internal::Arg *arg = map_.find(arg_name);\n    if (arg)\n      return *arg;\n    error = \"argument not found\";\n  }\n  return internal::Arg();\n}\n\ntemplate <typename Char, typename AF>\ninline internal::Arg BasicFormatter<Char, AF>::parse_arg_index(const Char *&s) {\n  const char *error = FMT_NULL;\n  internal::Arg arg = *s < '0' || *s > '9' ?\n        next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error);\n  if (error) {\n    FMT_THROW(FormatError(\n                *s != '}' && *s != ':' ? \"invalid format string\" : error));\n  }\n  return arg;\n}\n\ntemplate <typename Char, typename AF>\ninline internal::Arg BasicFormatter<Char, AF>::parse_arg_name(const Char *&s) {\n  assert(internal::is_name_start(*s));\n  const Char *start = s;\n  Char c;\n  do {\n    c = *++s;\n  } while (internal::is_name_start(c) || ('0' <= c && c <= '9'));\n  const char *error = FMT_NULL;\n  internal::Arg arg = get_arg(BasicStringRef<Char>(start, s - start), error);\n  if (error)\n    FMT_THROW(FormatError(error));\n  return arg;\n}\n\ntemplate <typename Char, typename ArgFormatter>\nconst Char *BasicFormatter<Char, ArgFormatter>::format(\n    const Char *&format_str, const internal::Arg &arg) {\n  using internal::Arg;\n  const Char *s = format_str;\n  typename ArgFormatter::SpecType spec;\n  if (*s == ':') {\n    if (arg.type == Arg::CUSTOM) {\n      arg.custom.format(this, arg.custom.value, &s);\n      return s;\n    }\n    ++s;\n    // Parse fill and alignment.\n    if (Char c = *s) {\n      const Char *p = s + 1;\n      spec.align_ = ALIGN_DEFAULT;\n      do {\n        switch (*p) {\n          case '<':\n            spec.align_ = ALIGN_LEFT;\n            break;\n          case '>':\n            spec.align_ = ALIGN_RIGHT;\n            break;\n          case '=':\n            spec.align_ = ALIGN_NUMERIC;\n            break;\n          case '^':\n            spec.align_ = ALIGN_CENTER;\n            break;\n        }\n        if (spec.align_ != ALIGN_DEFAULT) {\n          if (p != s) {\n            if (c == '}') break;\n            if (c == '{')\n              FMT_THROW(FormatError(\"invalid fill character '{'\"));\n            s += 2;\n            spec.fill_ = c;\n          } else ++s;\n          if (spec.align_ == ALIGN_NUMERIC)\n            require_numeric_argument(arg, '=');\n          break;\n        }\n      } while (--p >= s);\n    }\n\n    // Parse sign.\n    switch (*s) {\n      case '+':\n        check_sign(s, arg);\n        spec.flags_ |= SIGN_FLAG | PLUS_FLAG;\n        break;\n      case '-':\n        check_sign(s, arg);\n        spec.flags_ |= MINUS_FLAG;\n        break;\n      case ' ':\n        check_sign(s, arg);\n        spec.flags_ |= SIGN_FLAG;\n        break;\n    }\n\n    if (*s == '#') {\n      require_numeric_argument(arg, '#');\n      spec.flags_ |= HASH_FLAG;\n      ++s;\n    }\n\n    // Parse zero flag.\n    if (*s == '0') {\n      require_numeric_argument(arg, '0');\n      spec.align_ = ALIGN_NUMERIC;\n      spec.fill_ = '0';\n      ++s;\n    }\n\n    // Parse width.\n    if ('0' <= *s && *s <= '9') {\n      spec.width_ = internal::parse_nonnegative_int(s);\n    } else if (*s == '{') {\n      ++s;\n      Arg width_arg = internal::is_name_start(*s) ?\n            parse_arg_name(s) : parse_arg_index(s);\n      if (*s++ != '}')\n        FMT_THROW(FormatError(\"invalid format string\"));\n      ULongLong value = 0;\n      switch (width_arg.type) {\n      case Arg::INT:\n        if (width_arg.int_value < 0)\n          FMT_THROW(FormatError(\"negative width\"));\n        value = width_arg.int_value;\n        break;\n      case Arg::UINT:\n        value = width_arg.uint_value;\n        break;\n      case Arg::LONG_LONG:\n        if (width_arg.long_long_value < 0)\n          FMT_THROW(FormatError(\"negative width\"));\n        value = width_arg.long_long_value;\n        break;\n      case Arg::ULONG_LONG:\n        value = width_arg.ulong_long_value;\n        break;\n      default:\n        FMT_THROW(FormatError(\"width is not integer\"));\n      }\n      unsigned max_int = (std::numeric_limits<int>::max)();\n      if (value > max_int)\n        FMT_THROW(FormatError(\"number is too big\"));\n      spec.width_ = static_cast<int>(value);\n    }\n\n    // Parse precision.\n    if (*s == '.') {\n      ++s;\n      spec.precision_ = 0;\n      if ('0' <= *s && *s <= '9') {\n        spec.precision_ = internal::parse_nonnegative_int(s);\n      } else if (*s == '{') {\n        ++s;\n        Arg precision_arg = internal::is_name_start(*s) ?\n              parse_arg_name(s) : parse_arg_index(s);\n        if (*s++ != '}')\n          FMT_THROW(FormatError(\"invalid format string\"));\n        ULongLong value = 0;\n        switch (precision_arg.type) {\n          case Arg::INT:\n            if (precision_arg.int_value < 0)\n              FMT_THROW(FormatError(\"negative precision\"));\n            value = precision_arg.int_value;\n            break;\n          case Arg::UINT:\n            value = precision_arg.uint_value;\n            break;\n          case Arg::LONG_LONG:\n            if (precision_arg.long_long_value < 0)\n              FMT_THROW(FormatError(\"negative precision\"));\n            value = precision_arg.long_long_value;\n            break;\n          case Arg::ULONG_LONG:\n            value = precision_arg.ulong_long_value;\n            break;\n          default:\n            FMT_THROW(FormatError(\"precision is not integer\"));\n        }\n        unsigned max_int = (std::numeric_limits<int>::max)();\n        if (value > max_int)\n          FMT_THROW(FormatError(\"number is too big\"));\n        spec.precision_ = static_cast<int>(value);\n      } else {\n        FMT_THROW(FormatError(\"missing precision specifier\"));\n      }\n      if (arg.type <= Arg::LAST_INTEGER_TYPE || arg.type == Arg::POINTER) {\n        FMT_THROW(FormatError(\n            fmt::format(\"precision not allowed in {} format specifier\",\n            arg.type == Arg::POINTER ? \"pointer\" : \"integer\")));\n      }\n    }\n\n    // Parse type.\n    if (*s != '}' && *s)\n      spec.type_ = static_cast<char>(*s++);\n  }\n\n  if (*s++ != '}')\n    FMT_THROW(FormatError(\"missing '}' in format string\"));\n\n  // Format argument.\n  ArgFormatter(*this, spec, s - 1).visit(arg);\n  return s;\n}\n\ntemplate <typename Char, typename AF>\nvoid BasicFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {\n  const Char *s = format_str.c_str();\n  const Char *start = s;\n  while (*s) {\n    Char c = *s++;\n    if (c != '{' && c != '}') continue;\n    if (*s == c) {\n      write(writer_, start, s);\n      start = ++s;\n      continue;\n    }\n    if (c == '}')\n      FMT_THROW(FormatError(\"unmatched '}' in format string\"));\n    write(writer_, start, s - 1);\n    internal::Arg arg = internal::is_name_start(*s) ?\n          parse_arg_name(s) : parse_arg_index(s);\n    start = s = format(s, arg);\n  }\n  write(writer_, start, s);\n}\n\ntemplate <typename Char, typename It>\nstruct ArgJoin {\n  It first;\n  It last;\n  BasicCStringRef<Char> sep;\n\n  ArgJoin(It first, It last, const BasicCStringRef<Char>& sep) :\n    first(first),\n    last(last),\n    sep(sep) {}\n};\n\ntemplate <typename It>\nArgJoin<char, It> join(It first, It last, const BasicCStringRef<char>& sep) {\n  return ArgJoin<char, It>(first, last, sep);\n}\n\ntemplate <typename It>\nArgJoin<wchar_t, It> join(It first, It last, const BasicCStringRef<wchar_t>& sep) {\n  return ArgJoin<wchar_t, It>(first, last, sep);\n}\n\n#if FMT_HAS_GXX_CXX11\ntemplate <typename Range>\nauto join(const Range& range, const BasicCStringRef<char>& sep)\n    -> ArgJoin<char, decltype(std::begin(range))> {\n  return join(std::begin(range), std::end(range), sep);\n}\n\ntemplate <typename Range>\nauto join(const Range& range, const BasicCStringRef<wchar_t>& sep)\n    -> ArgJoin<wchar_t, decltype(std::begin(range))> {\n  return join(std::begin(range), std::end(range), sep);\n}\n#endif\n\ntemplate <typename ArgFormatter, typename Char, typename It>\nvoid format_arg(fmt::BasicFormatter<Char, ArgFormatter> &f,\n    const Char *&format_str, const ArgJoin<Char, It>& e) {\n  const Char* end = format_str;\n  if (*end == ':')\n    ++end;\n  while (*end && *end != '}')\n    ++end;\n  if (*end != '}')\n    FMT_THROW(FormatError(\"missing '}' in format string\"));\n\n  It it = e.first;\n  if (it != e.last) {\n    const Char* save = format_str;\n    f.format(format_str, internal::MakeArg<fmt::BasicFormatter<Char, ArgFormatter> >(*it++));\n    while (it != e.last) {\n      f.writer().write(e.sep);\n      format_str = save;\n      f.format(format_str, internal::MakeArg<fmt::BasicFormatter<Char, ArgFormatter> >(*it++));\n    }\n  }\n  format_str = end + 1;\n}\n}  // namespace fmt\n\n#if FMT_USE_USER_DEFINED_LITERALS\nnamespace fmt {\nnamespace internal {\n\ntemplate <typename Char>\nstruct UdlFormat {\n  const Char *str;\n\n  template <typename... Args>\n  auto operator()(Args && ... args) const\n                  -> decltype(format(str, std::forward<Args>(args)...)) {\n    return format(str, std::forward<Args>(args)...);\n  }\n};\n\ntemplate <typename Char>\nstruct UdlArg {\n  const Char *str;\n\n  template <typename T>\n  NamedArgWithType<Char, T> operator=(T &&value) const {\n    return {str, std::forward<T>(value)};\n  }\n};\n\n} // namespace internal\n\ninline namespace literals {\n\n/**\n  \\rst\n  C++11 literal equivalent of :func:`fmt::format`.\n\n  **Example**::\n\n    using namespace fmt::literals;\n    std::string message = \"The answer is {}\"_format(42);\n  \\endrst\n */\ninline internal::UdlFormat<char>\noperator\"\" _format(const char *s, std::size_t) { return {s}; }\ninline internal::UdlFormat<wchar_t>\noperator\"\" _format(const wchar_t *s, std::size_t) { return {s}; }\n\n/**\n  \\rst\n  C++11 literal equivalent of :func:`fmt::arg`.\n\n  **Example**::\n\n    using namespace fmt::literals;\n    print(\"Elapsed time: {s:.2f} seconds\", \"s\"_a=1.23);\n  \\endrst\n */\ninline internal::UdlArg<char>\noperator\"\" _a(const char *s, std::size_t) { return {s}; }\ninline internal::UdlArg<wchar_t>\noperator\"\" _a(const wchar_t *s, std::size_t) { return {s}; }\n\n} // inline namespace literals\n} // namespace fmt\n#endif // FMT_USE_USER_DEFINED_LITERALS\n\n// Restore warnings.\n#if FMT_GCC_VERSION >= 406\n# pragma GCC diagnostic pop\n#endif\n\n#if defined(__clang__) && !defined(FMT_ICC_VERSION)\n# pragma clang diagnostic pop\n#endif\n\n#ifdef FMT_HEADER_ONLY\n# define FMT_FUNC inline\n# include \"format.cc\"\n#else\n# define FMT_FUNC\n#endif\n\n#endif  // FMT_FORMAT_H_\n"
  },
  {
    "path": "lib/fmt/orig/ostream.cc",
    "content": "/*\n Formatting library for C++ - std::ostream support\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#include \"ostream.h\"\n\nnamespace fmt {\n\nnamespace internal {\nFMT_FUNC void write(std::ostream &os, Writer &w) {\n  const char *data = w.data();\n  typedef internal::MakeUnsigned<std::streamsize>::Type UnsignedStreamSize;\n  UnsignedStreamSize size = w.size();\n  UnsignedStreamSize max_size =\n      internal::to_unsigned((std::numeric_limits<std::streamsize>::max)());\n  do {\n    UnsignedStreamSize n = size <= max_size ? size : max_size;\n    os.write(data, static_cast<std::streamsize>(n));\n    data += n;\n    size -= n;\n  } while (size != 0);\n}\n}\n\nFMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) {\n  MemoryWriter w;\n  w.write(format_str, args);\n  internal::write(os, w);\n}\n}  // namespace fmt\n"
  },
  {
    "path": "lib/fmt/orig/ostream.h",
    "content": "/*\n Formatting library for C++ - std::ostream support\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_OSTREAM_H_\n#define FMT_OSTREAM_H_\n\n#include \"format.h\"\n#include <ostream>\n\nnamespace fmt {\n\nnamespace internal {\n\ntemplate <class Char>\nclass FormatBuf : public std::basic_streambuf<Char> {\n private:\n  typedef typename std::basic_streambuf<Char>::int_type int_type;\n  typedef typename std::basic_streambuf<Char>::traits_type traits_type;\n\n  Buffer<Char> &buffer_;\n\n public:\n  FormatBuf(Buffer<Char> &buffer) : buffer_(buffer) {}\n\n protected:\n  // The put-area is actually always empty. This makes the implementation\n  // simpler and has the advantage that the streambuf and the buffer are always\n  // in sync and sputc never writes into uninitialized memory. The obvious\n  // disadvantage is that each call to sputc always results in a (virtual) call\n  // to overflow. There is no disadvantage here for sputn since this always\n  // results in a call to xsputn.\n\n  int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {\n    if (!traits_type::eq_int_type(ch, traits_type::eof()))\n      buffer_.push_back(static_cast<Char>(ch));\n    return ch;\n  }\n\n  std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE {\n    buffer_.append(s, s + count);\n    return count;\n  }\n};\n\nYes &convert(std::ostream &);\n\nstruct DummyStream : std::ostream {\n  DummyStream();  // Suppress a bogus warning in MSVC.\n\n  // Hide all operator<< overloads from std::ostream.\n  template <typename T>\n  typename EnableIf<sizeof(T) == 0>::type operator<<(const T &);\n};\n\nNo &operator<<(std::ostream &, int);\n\ntemplate <typename T>\nstruct ConvertToIntImpl<T, true> {\n  // Convert to int only if T doesn't have an overloaded operator<<.\n  enum {\n    value = sizeof(convert(get<DummyStream>() << get<T>())) == sizeof(No)\n  };\n};\n\n// Write the content of w to os.\nFMT_API void write(std::ostream &os, Writer &w);\n}  // namespace internal\n\n// Formats a value.\ntemplate <typename Char, typename ArgFormatter_, typename T>\nvoid format_arg(BasicFormatter<Char, ArgFormatter_> &f,\n                const Char *&format_str, const T &value) {\n  internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE> buffer;\n\n  internal::FormatBuf<Char> format_buf(buffer);\n  std::basic_ostream<Char> output(&format_buf);\n  output.exceptions(std::ios_base::failbit | std::ios_base::badbit);\n  output << value;\n\n  BasicStringRef<Char> str(&buffer[0], buffer.size());\n  typedef internal::MakeArg< BasicFormatter<Char> > MakeArg;\n  format_str = f.format(format_str, MakeArg(str));\n}\n\n/**\n  \\rst\n  Prints formatted data to the stream *os*.\n\n  **Example**::\n\n    print(cerr, \"Don't {}!\", \"panic\");\n  \\endrst\n */\nFMT_API void print(std::ostream &os, CStringRef format_str, ArgList args);\nFMT_VARIADIC(void, print, std::ostream &, CStringRef)\n}  // namespace fmt\n\n#ifdef FMT_HEADER_ONLY\n# include \"ostream.cc\"\n#endif\n\n#endif  // FMT_OSTREAM_H_\n"
  },
  {
    "path": "lib/fmt/orig/posix.cc",
    "content": "/*\n A C++ interface to POSIX functions.\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n// Disable bogus MSVC warnings.\n#ifndef _CRT_SECURE_NO_WARNINGS\n# define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#include \"posix.h\"\n\n#include <limits.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n\n#ifndef _WIN32\n# include <unistd.h>\n#else\n# ifndef WIN32_LEAN_AND_MEAN\n#  define WIN32_LEAN_AND_MEAN\n# endif\n# include <windows.h>\n# include <io.h>\n\n# define O_CREAT _O_CREAT\n# define O_TRUNC _O_TRUNC\n\n# ifndef S_IRUSR\n#  define S_IRUSR _S_IREAD\n# endif\n\n# ifndef S_IWUSR\n#  define S_IWUSR _S_IWRITE\n# endif\n\n# ifdef __MINGW32__\n#  define _SH_DENYNO 0x40\n# endif\n\n#endif  // _WIN32\n\n#ifdef fileno\n# undef fileno\n#endif\n\nnamespace {\n#ifdef _WIN32\n// Return type of read and write functions.\ntypedef int RWResult;\n\n// On Windows the count argument to read and write is unsigned, so convert\n// it from size_t preventing integer overflow.\ninline unsigned convert_rwcount(std::size_t count) {\n  return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;\n}\n#else\n// Return type of read and write functions.\ntypedef ssize_t RWResult;\n\ninline std::size_t convert_rwcount(std::size_t count) { return count; }\n#endif\n}\n\nfmt::BufferedFile::~BufferedFile() FMT_NOEXCEPT {\n  if (file_ && FMT_SYSTEM(fclose(file_)) != 0)\n    fmt::report_system_error(errno, \"cannot close file\");\n}\n\nfmt::BufferedFile::BufferedFile(\n    fmt::CStringRef filename, fmt::CStringRef mode) {\n  FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), 0);\n  if (!file_)\n    FMT_THROW(SystemError(errno, \"cannot open file {}\", filename));\n}\n\nvoid fmt::BufferedFile::close() {\n  if (!file_)\n    return;\n  int result = FMT_SYSTEM(fclose(file_));\n  file_ = FMT_NULL;\n  if (result != 0)\n    FMT_THROW(SystemError(errno, \"cannot close file\"));\n}\n\n// A macro used to prevent expansion of fileno on broken versions of MinGW.\n#define FMT_ARGS\n\nint fmt::BufferedFile::fileno() const {\n  int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));\n  if (fd == -1)\n    FMT_THROW(SystemError(errno, \"cannot get file descriptor\"));\n  return fd;\n}\n\nfmt::File::File(fmt::CStringRef path, int oflag) {\n  int mode = S_IRUSR | S_IWUSR;\n#if defined(_WIN32) && !defined(__MINGW32__)\n  fd_ = -1;\n  FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));\n#else\n  FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));\n#endif\n  if (fd_ == -1)\n    FMT_THROW(SystemError(errno, \"cannot open file {}\", path));\n}\n\nfmt::File::~File() FMT_NOEXCEPT {\n  // Don't retry close in case of EINTR!\n  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html\n  if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)\n    fmt::report_system_error(errno, \"cannot close file\");\n}\n\nvoid fmt::File::close() {\n  if (fd_ == -1)\n    return;\n  // Don't retry close in case of EINTR!\n  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html\n  int result = FMT_POSIX_CALL(close(fd_));\n  fd_ = -1;\n  if (result != 0)\n    FMT_THROW(SystemError(errno, \"cannot close file\"));\n}\n\nfmt::LongLong fmt::File::size() const {\n#ifdef _WIN32\n  // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT\n  // is less than 0x0500 as is the case with some default MinGW builds.\n  // Both functions support large file sizes.\n  DWORD size_upper = 0;\n  HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));\n  DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));\n  if (size_lower == INVALID_FILE_SIZE) {\n    DWORD error = GetLastError();\n    if (error != NO_ERROR)\n      FMT_THROW(WindowsError(GetLastError(), \"cannot get file size\"));\n  }\n  fmt::ULongLong long_size = size_upper;\n  return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;\n#else\n  typedef struct stat Stat;\n  Stat file_stat = Stat();\n  if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)\n    FMT_THROW(SystemError(errno, \"cannot get file attributes\"));\n  FMT_STATIC_ASSERT(sizeof(fmt::LongLong) >= sizeof(file_stat.st_size),\n      \"return type of File::size is not large enough\");\n  return file_stat.st_size;\n#endif\n}\n\nstd::size_t fmt::File::read(void *buffer, std::size_t count) {\n  RWResult result = 0;\n  FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));\n  if (result < 0)\n    FMT_THROW(SystemError(errno, \"cannot read from file\"));\n  return internal::to_unsigned(result);\n}\n\nstd::size_t fmt::File::write(const void *buffer, std::size_t count) {\n  RWResult result = 0;\n  FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));\n  if (result < 0)\n    FMT_THROW(SystemError(errno, \"cannot write to file\"));\n  return internal::to_unsigned(result);\n}\n\nfmt::File fmt::File::dup(int fd) {\n  // Don't retry as dup doesn't return EINTR.\n  // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html\n  int new_fd = FMT_POSIX_CALL(dup(fd));\n  if (new_fd == -1)\n    FMT_THROW(SystemError(errno, \"cannot duplicate file descriptor {}\", fd));\n  return File(new_fd);\n}\n\nvoid fmt::File::dup2(int fd) {\n  int result = 0;\n  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));\n  if (result == -1) {\n    FMT_THROW(SystemError(errno,\n      \"cannot duplicate file descriptor {} to {}\", fd_, fd));\n  }\n}\n\nvoid fmt::File::dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT {\n  int result = 0;\n  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));\n  if (result == -1)\n    ec = ErrorCode(errno);\n}\n\nvoid fmt::File::pipe(File &read_end, File &write_end) {\n  // Close the descriptors first to make sure that assignments don't throw\n  // and there are no leaks.\n  read_end.close();\n  write_end.close();\n  int fds[2] = {};\n#ifdef _WIN32\n  // Make the default pipe capacity same as on Linux 2.6.11+.\n  enum { DEFAULT_CAPACITY = 65536 };\n  int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));\n#else\n  // Don't retry as the pipe function doesn't return EINTR.\n  // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html\n  int result = FMT_POSIX_CALL(pipe(fds));\n#endif\n  if (result != 0)\n    FMT_THROW(SystemError(errno, \"cannot create pipe\"));\n  // The following assignments don't throw because read_fd and write_fd\n  // are closed.\n  read_end = File(fds[0]);\n  write_end = File(fds[1]);\n}\n\nfmt::BufferedFile fmt::File::fdopen(const char *mode) {\n  // Don't retry as fdopen doesn't return EINTR.\n  FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode));\n  if (!f)\n    FMT_THROW(SystemError(errno, \"cannot associate stream with file descriptor\"));\n  BufferedFile file(f);\n  fd_ = -1;\n  return file;\n}\n\nlong fmt::getpagesize() {\n#ifdef _WIN32\n  SYSTEM_INFO si;\n  GetSystemInfo(&si);\n  return si.dwPageSize;\n#else\n  long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));\n  if (size < 0)\n    FMT_THROW(SystemError(errno, \"cannot get memory page size\"));\n  return size;\n#endif\n}\n"
  },
  {
    "path": "lib/fmt/orig/posix.h",
    "content": "/*\n A C++ interface to POSIX functions.\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_POSIX_H_\n#define FMT_POSIX_H_\n\n#if defined(__MINGW32__) || defined(__CYGWIN__)\n// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.\n# undef __STRICT_ANSI__\n#endif\n\n#include <errno.h>\n#include <fcntl.h>   // for O_RDONLY\n#include <locale.h>  // for locale_t\n#include <stdio.h>\n#include <stdlib.h>  // for strtod_l\n\n#include <cstddef>\n\n#if defined __APPLE__ || defined(__FreeBSD__)\n# include <xlocale.h>  // for LC_NUMERIC_MASK on OS X\n#endif\n\n#include \"format.h\"\n\n#ifndef FMT_POSIX\n# if defined(_WIN32) && !defined(__MINGW32__)\n// Fix warnings about deprecated symbols.\n#  define FMT_POSIX(call) _##call\n# else\n#  define FMT_POSIX(call) call\n# endif\n#endif\n\n// Calls to system functions are wrapped in FMT_SYSTEM for testability.\n#ifdef FMT_SYSTEM\n# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)\n#else\n# define FMT_SYSTEM(call) call\n# ifdef _WIN32\n// Fix warnings about deprecated symbols.\n#  define FMT_POSIX_CALL(call) ::_##call\n# else\n#  define FMT_POSIX_CALL(call) ::call\n# endif\n#endif\n\n// Retries the expression while it evaluates to error_result and errno\n// equals to EINTR.\n#ifndef _WIN32\n# define FMT_RETRY_VAL(result, expression, error_result) \\\n  do { \\\n    result = (expression); \\\n  } while (result == error_result && errno == EINTR)\n#else\n# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)\n#endif\n\n#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)\n\nnamespace fmt {\n\n// An error code.\nclass ErrorCode {\n private:\n  int value_;\n\n public:\n  explicit ErrorCode(int value = 0) FMT_NOEXCEPT : value_(value) {}\n\n  int get() const FMT_NOEXCEPT { return value_; }\n};\n\n// A buffered file.\nclass BufferedFile {\n private:\n  FILE *file_;\n\n  friend class File;\n\n  explicit BufferedFile(FILE *f) : file_(f) {}\n\n public:\n  // Constructs a BufferedFile object which doesn't represent any file.\n  BufferedFile() FMT_NOEXCEPT : file_(FMT_NULL) {}\n\n  // Destroys the object closing the file it represents if any.\n  FMT_API ~BufferedFile() FMT_NOEXCEPT;\n\n#if !FMT_USE_RVALUE_REFERENCES\n  // Emulate a move constructor and a move assignment operator if rvalue\n  // references are not supported.\n\n private:\n  // A proxy object to emulate a move constructor.\n  // It is private to make it impossible call operator Proxy directly.\n  struct Proxy {\n    FILE *file;\n  };\n\npublic:\n  // A \"move constructor\" for moving from a temporary.\n  BufferedFile(Proxy p) FMT_NOEXCEPT : file_(p.file) {}\n\n  // A \"move constructor\" for moving from an lvalue.\n  BufferedFile(BufferedFile &f) FMT_NOEXCEPT : file_(f.file_) {\n    f.file_ = FMT_NULL;\n  }\n\n  // A \"move assignment operator\" for moving from a temporary.\n  BufferedFile &operator=(Proxy p) {\n    close();\n    file_ = p.file;\n    return *this;\n  }\n\n  // A \"move assignment operator\" for moving from an lvalue.\n  BufferedFile &operator=(BufferedFile &other) {\n    close();\n    file_ = other.file_;\n    other.file_ = FMT_NULL;\n    return *this;\n  }\n\n  // Returns a proxy object for moving from a temporary:\n  //   BufferedFile file = BufferedFile(...);\n  operator Proxy() FMT_NOEXCEPT {\n    Proxy p = {file_};\n    file_ = FMT_NULL;\n    return p;\n  }\n\n#else\n private:\n  FMT_DISALLOW_COPY_AND_ASSIGN(BufferedFile);\n\n public:\n  BufferedFile(BufferedFile &&other) FMT_NOEXCEPT : file_(other.file_) {\n    other.file_ = FMT_NULL;\n  }\n\n  BufferedFile& operator=(BufferedFile &&other) {\n    close();\n    file_ = other.file_;\n    other.file_ = FMT_NULL;\n    return *this;\n  }\n#endif\n\n  // Opens a file.\n  FMT_API BufferedFile(CStringRef filename, CStringRef mode);\n\n  // Closes the file.\n  FMT_API void close();\n\n  // Returns the pointer to a FILE object representing this file.\n  FILE *get() const FMT_NOEXCEPT { return file_; }\n\n  // We place parentheses around fileno to workaround a bug in some versions\n  // of MinGW that define fileno as a macro.\n  FMT_API int (fileno)() const;\n\n  void print(CStringRef format_str, const ArgList &args) {\n    fmt::print(file_, format_str, args);\n  }\n  FMT_VARIADIC(void, print, CStringRef)\n};\n\n// A file. Closed file is represented by a File object with descriptor -1.\n// Methods that are not declared with FMT_NOEXCEPT may throw\n// fmt::SystemError in case of failure. Note that some errors such as\n// closing the file multiple times will cause a crash on Windows rather\n// than an exception. You can get standard behavior by overriding the\n// invalid parameter handler with _set_invalid_parameter_handler.\nclass File {\n private:\n  int fd_;  // File descriptor.\n\n  // Constructs a File object with a given descriptor.\n  explicit File(int fd) : fd_(fd) {}\n\n public:\n  // Possible values for the oflag argument to the constructor.\n  enum {\n    RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.\n    WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.\n    RDWR   = FMT_POSIX(O_RDWR)    // Open for reading and writing.\n  };\n\n  // Constructs a File object which doesn't represent any file.\n  File() FMT_NOEXCEPT : fd_(-1) {}\n\n  // Opens a file and constructs a File object representing this file.\n  FMT_API File(CStringRef path, int oflag);\n\n#if !FMT_USE_RVALUE_REFERENCES\n  // Emulate a move constructor and a move assignment operator if rvalue\n  // references are not supported.\n\n private:\n  // A proxy object to emulate a move constructor.\n  // It is private to make it impossible call operator Proxy directly.\n  struct Proxy {\n    int fd;\n  };\n\n public:\n  // A \"move constructor\" for moving from a temporary.\n  File(Proxy p) FMT_NOEXCEPT : fd_(p.fd) {}\n\n  // A \"move constructor\" for moving from an lvalue.\n  File(File &other) FMT_NOEXCEPT : fd_(other.fd_) {\n    other.fd_ = -1;\n  }\n\n  // A \"move assignment operator\" for moving from a temporary.\n  File &operator=(Proxy p) {\n    close();\n    fd_ = p.fd;\n    return *this;\n  }\n\n  // A \"move assignment operator\" for moving from an lvalue.\n  File &operator=(File &other) {\n    close();\n    fd_ = other.fd_;\n    other.fd_ = -1;\n    return *this;\n  }\n\n  // Returns a proxy object for moving from a temporary:\n  //   File file = File(...);\n  operator Proxy() FMT_NOEXCEPT {\n    Proxy p = {fd_};\n    fd_ = -1;\n    return p;\n  }\n\n#else\n private:\n  FMT_DISALLOW_COPY_AND_ASSIGN(File);\n\n public:\n  File(File &&other) FMT_NOEXCEPT : fd_(other.fd_) {\n    other.fd_ = -1;\n  }\n\n  File& operator=(File &&other) {\n    close();\n    fd_ = other.fd_;\n    other.fd_ = -1;\n    return *this;\n  }\n#endif\n\n  // Destroys the object closing the file it represents if any.\n  FMT_API ~File() FMT_NOEXCEPT;\n\n  // Returns the file descriptor.\n  int descriptor() const FMT_NOEXCEPT { return fd_; }\n\n  // Closes the file.\n  FMT_API void close();\n\n  // Returns the file size. The size has signed type for consistency with\n  // stat::st_size.\n  FMT_API LongLong size() const;\n\n  // Attempts to read count bytes from the file into the specified buffer.\n  FMT_API std::size_t read(void *buffer, std::size_t count);\n\n  // Attempts to write count bytes from the specified buffer to the file.\n  FMT_API std::size_t write(const void *buffer, std::size_t count);\n\n  // Duplicates a file descriptor with the dup function and returns\n  // the duplicate as a file object.\n  FMT_API static File dup(int fd);\n\n  // Makes fd be the copy of this file descriptor, closing fd first if\n  // necessary.\n  FMT_API void dup2(int fd);\n\n  // Makes fd be the copy of this file descriptor, closing fd first if\n  // necessary.\n  FMT_API void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT;\n\n  // Creates a pipe setting up read_end and write_end file objects for reading\n  // and writing respectively.\n  FMT_API static void pipe(File &read_end, File &write_end);\n\n  // Creates a BufferedFile object associated with this file and detaches\n  // this File object from the file.\n  FMT_API BufferedFile fdopen(const char *mode);\n};\n\n// Returns the memory page size.\nlong getpagesize();\n\n#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \\\n    !defined(__ANDROID__) && !defined(__CYGWIN__)\n# define FMT_LOCALE\n#endif\n\n#ifdef FMT_LOCALE\n// A \"C\" numeric locale.\nclass Locale {\n private:\n# ifdef _MSC_VER\n  typedef _locale_t locale_t;\n\n  enum { LC_NUMERIC_MASK = LC_NUMERIC };\n\n  static locale_t newlocale(int category_mask, const char *locale, locale_t) {\n    return _create_locale(category_mask, locale);\n  }\n\n  static void freelocale(locale_t locale) {\n    _free_locale(locale);\n  }\n\n  static double strtod_l(const char *nptr, char **endptr, _locale_t locale) {\n    return _strtod_l(nptr, endptr, locale);\n  }\n# endif\n\n  locale_t locale_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(Locale);\n\n public:\n  typedef locale_t Type;\n\n  Locale() : locale_(newlocale(LC_NUMERIC_MASK, \"C\", FMT_NULL)) {\n    if (!locale_)\n      FMT_THROW(fmt::SystemError(errno, \"cannot create locale\"));\n  }\n  ~Locale() { freelocale(locale_); }\n\n  Type get() const { return locale_; }\n\n  // Converts string to floating-point number and advances str past the end\n  // of the parsed input.\n  double strtod(const char *&str) const {\n    char *end = FMT_NULL;\n    double result = strtod_l(str, &end, locale_);\n    str = end;\n    return result;\n  }\n};\n#endif  // FMT_LOCALE\n}  // namespace fmt\n\n#if !FMT_USE_RVALUE_REFERENCES\nnamespace std {\n// For compatibility with C++98.\ninline fmt::BufferedFile &move(fmt::BufferedFile &f) { return f; }\ninline fmt::File &move(fmt::File &f) { return f; }\n}\n#endif\n\n#endif  // FMT_POSIX_H_\n"
  },
  {
    "path": "lib/fmt/orig/printf.cc",
    "content": "/*\n Formatting library for C++\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#include \"format.h\"\n#include \"printf.h\"\n\nnamespace fmt {\n\ntemplate <typename Char>\nvoid printf(BasicWriter<Char> &w, BasicCStringRef<Char> format, ArgList args);\n\nFMT_FUNC int fprintf(std::FILE *f, CStringRef format, ArgList args) {\n  MemoryWriter w;\n  printf(w, format, args);\n  std::size_t size = w.size();\n  return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast<int>(size);\n}\n\n#ifndef FMT_HEADER_ONLY\n\ntemplate void PrintfFormatter<char>::format(CStringRef format);\ntemplate void PrintfFormatter<wchar_t>::format(WCStringRef format);\n\n#endif  // FMT_HEADER_ONLY\n\n}  // namespace fmt\n"
  },
  {
    "path": "lib/fmt/orig/printf.h",
    "content": "/*\n Formatting library for C++\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_PRINTF_H_\n#define FMT_PRINTF_H_\n\n#include <algorithm>  // std::fill_n\n#include <limits>     // std::numeric_limits\n\n#include \"ostream.h\"\n\nnamespace fmt {\nnamespace internal {\n\n// Checks if a value fits in int - used to avoid warnings about comparing\n// signed and unsigned integers.\ntemplate <bool IsSigned>\nstruct IntChecker {\n  template <typename T>\n  static bool fits_in_int(T value) {\n    unsigned max = std::numeric_limits<int>::max();\n    return value <= max;\n  }\n  static bool fits_in_int(bool) { return true; }\n};\n\ntemplate <>\nstruct IntChecker<true> {\n  template <typename T>\n  static bool fits_in_int(T value) {\n    return value >= std::numeric_limits<int>::min() &&\n           value <= std::numeric_limits<int>::max();\n  }\n  static bool fits_in_int(int) { return true; }\n};\n\nclass PrecisionHandler : public ArgVisitor<PrecisionHandler, int> {\n public:\n  void report_unhandled_arg() {\n    FMT_THROW(FormatError(\"precision is not integer\"));\n  }\n\n  template <typename T>\n  int visit_any_int(T value) {\n    if (!IntChecker<std::numeric_limits<T>::is_signed>::fits_in_int(value))\n      FMT_THROW(FormatError(\"number is too big\"));\n    return static_cast<int>(value);\n  }\n};\n\n// IsZeroInt::visit(arg) returns true iff arg is a zero integer.\nclass IsZeroInt : public ArgVisitor<IsZeroInt, bool> {\n public:\n  template <typename T>\n  bool visit_any_int(T value) { return value == 0; }\n};\n\n// returns the default type for format specific \"%s\"\nclass DefaultType : public ArgVisitor<DefaultType, char> {\n public:\n  char visit_char(int) { return 'c'; }\n\n  char visit_bool(bool) { return 's'; }\n\n  char visit_pointer(const void *) { return 'p'; }\n\n  template <typename T>\n  char visit_any_int(T) { return 'd'; }\n\n  template <typename T>\n  char visit_any_double(T) { return 'g'; }\n\n  char visit_unhandled_arg() { return 's'; }\n};\n\ntemplate <typename T, typename U>\nstruct is_same {\n  enum { value = 0 };\n};\n\ntemplate <typename T>\nstruct is_same<T, T> {\n  enum { value = 1 };\n};\n\n// An argument visitor that converts an integer argument to T for printf,\n// if T is an integral type. If T is void, the argument is converted to\n// corresponding signed or unsigned type depending on the type specifier:\n// 'd' and 'i' - signed, other - unsigned)\ntemplate <typename T = void>\nclass ArgConverter : public ArgVisitor<ArgConverter<T>, void> {\n private:\n  internal::Arg &arg_;\n  wchar_t type_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter);\n\n public:\n  ArgConverter(internal::Arg &arg, wchar_t type)\n    : arg_(arg), type_(type) {}\n\n  void visit_bool(bool value) {\n    if (type_ != 's')\n      visit_any_int(value);\n  }\n\n  void visit_char(int value) {\n    if (type_ != 's')\n      visit_any_int(value);\n  }\n\n  template <typename U>\n  void visit_any_int(U value) {\n    bool is_signed = type_ == 'd' || type_ == 'i';\n    if (type_ == 's') {\n      is_signed = std::numeric_limits<U>::is_signed;\n    }\n\n    using internal::Arg;\n    typedef typename internal::Conditional<\n        is_same<T, void>::value, U, T>::type TargetType;\n    if (const_check(sizeof(TargetType) <= sizeof(int))) {\n      // Extra casts are used to silence warnings.\n      if (is_signed) {\n        arg_.type = Arg::INT;\n        arg_.int_value = static_cast<int>(static_cast<TargetType>(value));\n      } else {\n        arg_.type = Arg::UINT;\n        typedef typename internal::MakeUnsigned<TargetType>::Type Unsigned;\n        arg_.uint_value = static_cast<unsigned>(static_cast<Unsigned>(value));\n      }\n    } else {\n      if (is_signed) {\n        arg_.type = Arg::LONG_LONG;\n        // glibc's printf doesn't sign extend arguments of smaller types:\n        //   std::printf(\"%lld\", -42);  // prints \"4294967254\"\n        // but we don't have to do the same because it's a UB.\n        arg_.long_long_value = static_cast<LongLong>(value);\n      } else {\n        arg_.type = Arg::ULONG_LONG;\n        arg_.ulong_long_value =\n            static_cast<typename internal::MakeUnsigned<U>::Type>(value);\n      }\n    }\n  }\n};\n\n// Converts an integer argument to char for printf.\nclass CharConverter : public ArgVisitor<CharConverter, void> {\n private:\n  internal::Arg &arg_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter);\n\n public:\n  explicit CharConverter(internal::Arg &arg) : arg_(arg) {}\n\n  template <typename T>\n  void visit_any_int(T value) {\n    arg_.type = internal::Arg::CHAR;\n    arg_.int_value = static_cast<char>(value);\n  }\n};\n\n// Checks if an argument is a valid printf width specifier and sets\n// left alignment if it is negative.\nclass WidthHandler : public ArgVisitor<WidthHandler, unsigned> {\n private:\n  FormatSpec &spec_;\n\n  FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler);\n\n public:\n  explicit WidthHandler(FormatSpec &spec) : spec_(spec) {}\n\n  void report_unhandled_arg() {\n    FMT_THROW(FormatError(\"width is not integer\"));\n  }\n\n  template <typename T>\n  unsigned visit_any_int(T value) {\n    typedef typename internal::IntTraits<T>::MainType UnsignedType;\n    UnsignedType width = static_cast<UnsignedType>(value);\n    if (internal::is_negative(value)) {\n      spec_.align_ = ALIGN_LEFT;\n      width = 0 - width;\n    }\n    unsigned int_max = std::numeric_limits<int>::max();\n    if (width > int_max)\n      FMT_THROW(FormatError(\"number is too big\"));\n    return static_cast<unsigned>(width);\n  }\n};\n}  // namespace internal\n\n/**\n  \\rst\n  A ``printf`` argument formatter based on the `curiously recurring template\n  pattern <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_.\n\n  To use `~fmt::BasicPrintfArgFormatter` define a subclass that implements some\n  or all of the visit methods with the same signatures as the methods in\n  `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`.\n  Pass the subclass as the *Impl* template parameter. When a formatting\n  function processes an argument, it will dispatch to a visit method\n  specific to the argument type. For example, if the argument type is\n  ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass\n  will be called. If the subclass doesn't contain a method with this signature,\n  then a corresponding method of `~fmt::BasicPrintfArgFormatter` or its\n  superclass will be called.\n  \\endrst\n */\ntemplate <typename Impl, typename Char, typename Spec>\nclass BasicPrintfArgFormatter :\n    public internal::ArgFormatterBase<Impl, Char, Spec> {\n private:\n  void write_null_pointer() {\n    this->spec().type_ = 0;\n    this->write(\"(nil)\");\n  }\n\n  typedef internal::ArgFormatterBase<Impl, Char, Spec> Base;\n\n public:\n  /**\n    \\rst\n    Constructs an argument formatter object.\n    *writer* is a reference to the output writer and *spec* contains format\n    specifier information for standard argument types.\n    \\endrst\n   */\n  BasicPrintfArgFormatter(BasicWriter<Char> &w, Spec &s)\n  : internal::ArgFormatterBase<Impl, Char, Spec>(w, s) {}\n\n  /** Formats an argument of type ``bool``. */\n  void visit_bool(bool value) {\n    Spec &fmt_spec = this->spec();\n    if (fmt_spec.type_ != 's')\n      return this->visit_any_int(value);\n    fmt_spec.type_ = 0;\n    this->write(value);\n  }\n\n  /** Formats a character. */\n  void visit_char(int value) {\n    const Spec &fmt_spec = this->spec();\n    BasicWriter<Char> &w = this->writer();\n    if (fmt_spec.type_ && fmt_spec.type_ != 'c')\n      w.write_int(value, fmt_spec);\n    typedef typename BasicWriter<Char>::CharPtr CharPtr;\n    CharPtr out = CharPtr();\n    if (fmt_spec.width_ > 1) {\n      Char fill = ' ';\n      out = w.grow_buffer(fmt_spec.width_);\n      if (fmt_spec.align_ != ALIGN_LEFT) {\n        std::fill_n(out, fmt_spec.width_ - 1, fill);\n        out += fmt_spec.width_ - 1;\n      } else {\n        std::fill_n(out + 1, fmt_spec.width_ - 1, fill);\n      }\n    } else {\n      out = w.grow_buffer(1);\n    }\n    *out = static_cast<Char>(value);\n  }\n\n  /** Formats a null-terminated C string. */\n  void visit_cstring(const char *value) {\n    if (value)\n      Base::visit_cstring(value);\n    else if (this->spec().type_ == 'p')\n      write_null_pointer();\n    else\n      this->write(\"(null)\");\n  }\n\n  /** Formats a pointer. */\n  void visit_pointer(const void *value) {\n    if (value)\n      return Base::visit_pointer(value);\n    this->spec().type_ = 0;\n    write_null_pointer();\n  }\n\n  /** Formats an argument of a custom (user-defined) type. */\n  void visit_custom(internal::Arg::CustomValue c) {\n    BasicFormatter<Char> formatter(ArgList(), this->writer());\n    const Char format_str[] = {'}', 0};\n    const Char *format = format_str;\n    c.format(&formatter, c.value, &format);\n  }\n};\n\n/** The default printf argument formatter. */\ntemplate <typename Char>\nclass PrintfArgFormatter :\n    public BasicPrintfArgFormatter<PrintfArgFormatter<Char>, Char, FormatSpec> {\n public:\n  /** Constructs an argument formatter object. */\n  PrintfArgFormatter(BasicWriter<Char> &w, FormatSpec &s)\n  : BasicPrintfArgFormatter<PrintfArgFormatter<Char>, Char, FormatSpec>(w, s) {}\n};\n\n/** This template formats data and writes the output to a writer. */\ntemplate <typename Char, typename ArgFormatter = PrintfArgFormatter<Char> >\nclass PrintfFormatter : private internal::FormatterBase {\n private:\n  BasicWriter<Char> &writer_;\n\n  void parse_flags(FormatSpec &spec, const Char *&s);\n\n  // Returns the argument with specified index or, if arg_index is equal\n  // to the maximum unsigned value, the next argument.\n  internal::Arg get_arg(\n      const Char *s,\n      unsigned arg_index = (std::numeric_limits<unsigned>::max)());\n\n  // Parses argument index, flags and width and returns the argument index.\n  unsigned parse_header(const Char *&s, FormatSpec &spec);\n\n public:\n  /**\n   \\rst\n   Constructs a ``PrintfFormatter`` object. References to the arguments and\n   the writer are stored in the formatter object so make sure they have\n   appropriate lifetimes.\n   \\endrst\n   */\n  explicit PrintfFormatter(const ArgList &al, BasicWriter<Char> &w)\n    : FormatterBase(al), writer_(w) {}\n\n  /** Formats stored arguments and writes the output to the writer. */\n  void format(BasicCStringRef<Char> format_str);\n};\n\ntemplate <typename Char, typename AF>\nvoid PrintfFormatter<Char, AF>::parse_flags(FormatSpec &spec, const Char *&s) {\n  for (;;) {\n    switch (*s++) {\n      case '-':\n        spec.align_ = ALIGN_LEFT;\n        break;\n      case '+':\n        spec.flags_ |= SIGN_FLAG | PLUS_FLAG;\n        break;\n      case '0':\n        spec.fill_ = '0';\n        break;\n      case ' ':\n        spec.flags_ |= SIGN_FLAG;\n        break;\n      case '#':\n        spec.flags_ |= HASH_FLAG;\n        break;\n      default:\n        --s;\n        return;\n    }\n  }\n}\n\ntemplate <typename Char, typename AF>\ninternal::Arg PrintfFormatter<Char, AF>::get_arg(const Char *s,\n                                                 unsigned arg_index) {\n  (void)s;\n  const char *error = FMT_NULL;\n  internal::Arg arg = arg_index == std::numeric_limits<unsigned>::max() ?\n    next_arg(error) : FormatterBase::get_arg(arg_index - 1, error);\n  if (error)\n    FMT_THROW(FormatError(!*s ? \"invalid format string\" : error));\n  return arg;\n}\n\ntemplate <typename Char, typename AF>\nunsigned PrintfFormatter<Char, AF>::parse_header(\n  const Char *&s, FormatSpec &spec) {\n  unsigned arg_index = std::numeric_limits<unsigned>::max();\n  Char c = *s;\n  if (c >= '0' && c <= '9') {\n    // Parse an argument index (if followed by '$') or a width possibly\n    // preceded with '0' flag(s).\n    unsigned value = internal::parse_nonnegative_int(s);\n    if (*s == '$') {  // value is an argument index\n      ++s;\n      arg_index = value;\n    } else {\n      if (c == '0')\n        spec.fill_ = '0';\n      if (value != 0) {\n        // Nonzero value means that we parsed width and don't need to\n        // parse it or flags again, so return now.\n        spec.width_ = value;\n        return arg_index;\n      }\n    }\n  }\n  parse_flags(spec, s);\n  // Parse width.\n  if (*s >= '0' && *s <= '9') {\n    spec.width_ = internal::parse_nonnegative_int(s);\n  } else if (*s == '*') {\n    ++s;\n    spec.width_ = internal::WidthHandler(spec).visit(get_arg(s));\n  }\n  return arg_index;\n}\n\ntemplate <typename Char, typename AF>\nvoid PrintfFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {\n  const Char *start = format_str.c_str();\n  const Char *s = start;\n  while (*s) {\n    Char c = *s++;\n    if (c != '%') continue;\n    if (*s == c) {\n      write(writer_, start, s);\n      start = ++s;\n      continue;\n    }\n    write(writer_, start, s - 1);\n\n    FormatSpec spec;\n    spec.align_ = ALIGN_RIGHT;\n\n    // Parse argument index, flags and width.\n    unsigned arg_index = parse_header(s, spec);\n\n    // Parse precision.\n    if (*s == '.') {\n      ++s;\n      if ('0' <= *s && *s <= '9') {\n        spec.precision_ = static_cast<int>(internal::parse_nonnegative_int(s));\n      } else if (*s == '*') {\n        ++s;\n        spec.precision_ = internal::PrecisionHandler().visit(get_arg(s));\n      } else {\n        spec.precision_ = 0;\n      }\n    }\n\n    using internal::Arg;\n    Arg arg = get_arg(s, arg_index);\n    if (spec.flag(HASH_FLAG) && internal::IsZeroInt().visit(arg))\n      spec.flags_ &= ~internal::to_unsigned<int>(HASH_FLAG);\n    if (spec.fill_ == '0') {\n      if (arg.type <= Arg::LAST_NUMERIC_TYPE)\n        spec.align_ = ALIGN_NUMERIC;\n      else\n        spec.fill_ = ' ';  // Ignore '0' flag for non-numeric types.\n    }\n\n    // Parse length and convert the argument to the required type.\n    using internal::ArgConverter;\n    switch (*s++) {\n    case 'h':\n      if (*s == 'h')\n        ArgConverter<signed char>(arg, *++s).visit(arg);\n      else\n        ArgConverter<short>(arg, *s).visit(arg);\n      break;\n    case 'l':\n      if (*s == 'l')\n        ArgConverter<fmt::LongLong>(arg, *++s).visit(arg);\n      else\n        ArgConverter<long>(arg, *s).visit(arg);\n      break;\n    case 'j':\n      ArgConverter<intmax_t>(arg, *s).visit(arg);\n      break;\n    case 'z':\n      ArgConverter<std::size_t>(arg, *s).visit(arg);\n      break;\n    case 't':\n      ArgConverter<std::ptrdiff_t>(arg, *s).visit(arg);\n      break;\n    case 'L':\n      // printf produces garbage when 'L' is omitted for long double, no\n      // need to do the same.\n      break;\n    default:\n      --s;\n      ArgConverter<void>(arg, *s).visit(arg);\n    }\n\n    // Parse type.\n    if (!*s)\n      FMT_THROW(FormatError(\"invalid format string\"));\n    spec.type_ = static_cast<char>(*s++);\n\n    if (spec.type_ == 's') {\n      // set the format type to the default if 's' is specified\n      spec.type_ = internal::DefaultType().visit(arg);\n    }\n\n    if (arg.type <= Arg::LAST_INTEGER_TYPE) {\n      // Normalize type.\n      switch (spec.type_) {\n      case 'i': case 'u':\n        spec.type_ = 'd';\n        break;\n      case 'c':\n        // TODO: handle wchar_t\n        internal::CharConverter(arg).visit(arg);\n        break;\n      }\n    }\n\n    start = s;\n\n    // Format argument.\n    AF(writer_, spec).visit(arg);\n  }\n  write(writer_, start, s);\n}\n\ninline void printf(Writer &w, CStringRef format, ArgList args) {\n  PrintfFormatter<char>(args, w).format(format);\n}\nFMT_VARIADIC(void, printf, Writer &, CStringRef)\n\ninline void printf(WWriter &w, WCStringRef format, ArgList args) {\n  PrintfFormatter<wchar_t>(args, w).format(format);\n}\nFMT_VARIADIC(void, printf, WWriter &, WCStringRef)\n\n/**\n  \\rst\n  Formats arguments and returns the result as a string.\n\n  **Example**::\n\n    std::string message = fmt::sprintf(\"The answer is %d\", 42);\n  \\endrst\n*/\ninline std::string sprintf(CStringRef format, ArgList args) {\n  MemoryWriter w;\n  printf(w, format, args);\n  return w.str();\n}\nFMT_VARIADIC(std::string, sprintf, CStringRef)\n\ninline std::wstring sprintf(WCStringRef format, ArgList args) {\n  WMemoryWriter w;\n  printf(w, format, args);\n  return w.str();\n}\nFMT_VARIADIC_W(std::wstring, sprintf, WCStringRef)\n\n/**\n  \\rst\n  Prints formatted data to the file *f*.\n\n  **Example**::\n\n    fmt::fprintf(stderr, \"Don't %s!\", \"panic\");\n  \\endrst\n */\nFMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args);\nFMT_VARIADIC(int, fprintf, std::FILE *, CStringRef)\n\n/**\n  \\rst\n  Prints formatted data to ``stdout``.\n\n  **Example**::\n\n    fmt::printf(\"Elapsed time: %.2f seconds\", 1.23);\n  \\endrst\n */\ninline int printf(CStringRef format, ArgList args) {\n  return fprintf(stdout, format, args);\n}\nFMT_VARIADIC(int, printf, CStringRef)\n\n/**\n  \\rst\n  Prints formatted data to the stream *os*.\n\n  **Example**::\n\n    fprintf(cerr, \"Don't %s!\", \"panic\");\n  \\endrst\n */\ninline int fprintf(std::ostream &os, CStringRef format_str, ArgList args) {\n  MemoryWriter w;\n  printf(w, format_str, args);\n  internal::write(os, w);\n  return static_cast<int>(w.size());\n}\nFMT_VARIADIC(int, fprintf, std::ostream &, CStringRef)\n}  // namespace fmt\n\n#ifdef FMT_HEADER_ONLY\n# include \"printf.cc\"\n#endif\n\n#endif  // FMT_PRINTF_H_\n"
  },
  {
    "path": "lib/fmt/orig/string.h",
    "content": "/*\n Formatting library for C++ - string utilities\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifdef FMT_INCLUDE\n# error \"Add the fmt's parent directory and not fmt itself to includes.\"\n#endif\n\n#ifndef FMT_STRING_H_\n#define FMT_STRING_H_\n\n#include \"format.h\"\n\nnamespace fmt {\n\nnamespace internal {\n\n// A buffer that stores data in ``std::basic_string``.\ntemplate <typename Char, typename Allocator = std::allocator<Char> >\nclass StringBuffer : public Buffer<Char> {\n public:\n  typedef std::basic_string<Char, std::char_traits<Char>, Allocator> StringType;\n\n private:\n  StringType data_;\n\n protected:\n  virtual void grow(std::size_t size) FMT_OVERRIDE {\n    data_.resize(size);\n    this->ptr_ = &data_[0];\n    this->capacity_ = size;\n  }\n\n public:\n  explicit StringBuffer(const Allocator &allocator = Allocator())\n  : data_(allocator) {}\n\n  // Moves the data to ``str`` clearing the buffer.\n  void move_to(StringType &str) {\n    data_.resize(this->size_);\n    str.swap(data_);\n    this->capacity_ = this->size_ = 0;\n    this->ptr_ = FMT_NULL;\n  }\n};\n}  // namespace internal\n\n/**\n  \\rst\n  This class template provides operations for formatting and writing data\n  into a character stream. The output is stored in a ``std::basic_string``\n  that grows dynamically.\n\n  You can use one of the following typedefs for common character types\n  and the standard allocator:\n\n  +---------------+----------------------------+\n  | Type          | Definition                 |\n  +===============+============================+\n  | StringWriter  | BasicStringWriter<char>    |\n  +---------------+----------------------------+\n  | WStringWriter | BasicStringWriter<wchar_t> |\n  +---------------+----------------------------+\n\n  **Example**::\n\n     StringWriter out;\n     out << \"The answer is \" << 42 << \"\\n\";\n\n  This will write the following output to the ``out`` object:\n\n  .. code-block:: none\n\n     The answer is 42\n\n  The output can be moved to a ``std::basic_string`` with ``out.move_to()``.\n  \\endrst\n */\ntemplate <typename Char, typename Allocator = std::allocator<Char> >\nclass BasicStringWriter : public BasicWriter<Char> {\n private:\n  internal::StringBuffer<Char, Allocator> buffer_;\n\n public:\n  /**\n    \\rst\n    Constructs a :class:`fmt::BasicStringWriter` object.\n    \\endrst\n   */\n  explicit BasicStringWriter(const Allocator &allocator = Allocator())\n  : BasicWriter<Char>(buffer_), buffer_(allocator) {}\n\n  /**\n    \\rst\n    Moves the buffer content to *str* clearing the buffer.\n    \\endrst\n   */\n  void move_to(std::basic_string<Char, std::char_traits<Char>, Allocator> &str) {\n    buffer_.move_to(str);\n  }\n};\n\ntypedef BasicStringWriter<char> StringWriter;\ntypedef BasicStringWriter<wchar_t> WStringWriter;\n\n/**\n  \\rst\n  Converts *value* to ``std::string`` using the default format for type *T*.\n\n  **Example**::\n\n    #include \"fmt/string.h\"\n\n    std::string answer = fmt::to_string(42);\n  \\endrst\n */\ntemplate <typename T>\nstd::string to_string(const T &value) {\n  fmt::MemoryWriter w;\n  w << value;\n  return w.str();\n}\n\n/**\n  \\rst\n  Converts *value* to ``std::wstring`` using the default format for type *T*.\n\n  **Example**::\n\n    #include \"fmt/string.h\"\n\n    std::wstring answer = fmt::to_wstring(42);\n  \\endrst\n */\ntemplate <typename T>\nstd::wstring to_wstring(const T &value) {\n  fmt::WMemoryWriter w;\n  w << value;\n  return w.str();\n}\n}\n\n#endif  // FMT_STRING_H_\n"
  },
  {
    "path": "lib/fmt/orig/time.h",
    "content": "/*\n Formatting library for C++ - time formatting\n\n Copyright (c) 2012 - 2016, Victor Zverovich\n All rights reserved.\n\n For the license information refer to format.h.\n */\n\n#ifndef FMT_TIME_H_\n#define FMT_TIME_H_\n\n#include \"format.h\"\n#include <ctime>\n\n#ifdef _MSC_VER\n# pragma warning(push)\n# pragma warning(disable: 4702)  // unreachable code\n# pragma warning(disable: 4996)  // \"deprecated\" functions\n#endif\n\nnamespace fmt {\ntemplate <typename ArgFormatter>\nvoid format_arg(BasicFormatter<char, ArgFormatter> &f,\n                const char *&format_str, const std::tm &tm) {\n  if (*format_str == ':')\n    ++format_str;\n  const char *end = format_str;\n  while (*end && *end != '}')\n    ++end;\n  if (*end != '}')\n    FMT_THROW(FormatError(\"missing '}' in format string\"));\n  internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> format;\n  format.append(format_str, end + 1);\n  format[format.size() - 1] = '\\0';\n  Buffer<char> &buffer = f.writer().buffer();\n  std::size_t start = buffer.size();\n  for (;;) {\n    std::size_t size = buffer.capacity() - start;\n    std::size_t count = std::strftime(&buffer[start], size, &format[0], &tm);\n    if (count != 0) {\n      buffer.resize(start + count);\n      break;\n    }\n    if (size >= format.size() * 256) {\n      // If the buffer is 256 times larger than the format string, assume\n      // that `strftime` gives an empty result. There doesn't seem to be a\n      // better way to distinguish the two cases:\n      // https://github.com/fmtlib/fmt/issues/367\n      break;\n    }\n    const std::size_t MIN_GROWTH = 10;\n    buffer.reserve(buffer.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));\n  }\n  format_str = end + 1;\n}\n\nnamespace internal{\ninline Null<> localtime_r(...) { return Null<>(); }\ninline Null<> localtime_s(...) { return Null<>(); }\ninline Null<> gmtime_r(...) { return Null<>(); }\ninline Null<> gmtime_s(...) { return Null<>(); }\n}\n\n// Thread-safe replacement for std::localtime\ninline std::tm localtime(std::time_t time) {\n  struct LocalTime {\n    std::time_t time_;\n    std::tm tm_;\n\n    LocalTime(std::time_t t): time_(t) {}\n\n    bool run() {\n      using namespace fmt::internal;\n      return handle(localtime_r(&time_, &tm_));\n    }\n\n    bool handle(std::tm *tm) { return tm != FMT_NULL; }\n\n    bool handle(internal::Null<>) {\n      using namespace fmt::internal;\n      return fallback(localtime_s(&tm_, &time_));\n    }\n\n    bool fallback(int res) { return res == 0; }\n\n    bool fallback(internal::Null<>) {\n      using namespace fmt::internal;\n      std::tm *tm = std::localtime(&time_);\n      if (tm) tm_ = *tm;\n      return tm != FMT_NULL;\n    }\n  };\n  LocalTime lt(time);\n  if (lt.run())\n    return lt.tm_;\n  // Too big time values may be unsupported.\n  FMT_THROW(fmt::FormatError(\"time_t value out of range\"));\n  return std::tm();\n}\n\n// Thread-safe replacement for std::gmtime\ninline std::tm gmtime(std::time_t time) {\n  struct GMTime {\n    std::time_t time_;\n    std::tm tm_;\n\n    GMTime(std::time_t t): time_(t) {}\n\n    bool run() {\n      using namespace fmt::internal;\n      return handle(gmtime_r(&time_, &tm_));\n    }\n\n    bool handle(std::tm *tm) { return tm != FMT_NULL; }\n\n    bool handle(internal::Null<>) {\n      using namespace fmt::internal;\n      return fallback(gmtime_s(&tm_, &time_));\n    }\n\n    bool fallback(int res) { return res == 0; }\n\n    bool fallback(internal::Null<>) {\n      std::tm *tm = std::gmtime(&time_);\n      if (tm != FMT_NULL) tm_ = *tm;\n      return tm != FMT_NULL;\n    }\n  };\n  GMTime gt(time);\n  if (gt.run())\n    return gt.tm_;\n  // Too big time values may be unsupported.\n  FMT_THROW(fmt::FormatError(\"time_t value out of range\"));\n  return std::tm();\n}\n} //namespace fmt\n\n#ifdef _MSC_VER\n# pragma warning(pop)\n#endif\n\n#endif  // FMT_TIME_H_\n"
  },
  {
    "path": "lib/fmt/update.sh",
    "content": "#!/bin/bash\n\nfunction cpM()\n{\n    cp orig/$1 $2\n}\n\nHEADS=\"\"\nfunction cpH()\n{\n    cp orig/$1 $2\n    HEADS=\"${HEADS} $1\"\n}\n\n\ncpM format.cc       fmt_format.cpp\ncpM ostream.cc      fmt_ostream.cpp\ncpM posix.cc        fmt_posix.cpp\ncpM printf.cc       fmt_printf.cpp\n\ncpH container.h     fmt_container.h\ncpH format.h        fmt_format.h\ncpH ostream.h       fmt_ostream.h\ncpH posix.h         fmt_posix.h\ncpH printf.h        fmt_printf.h\ncpH string.h        fmt_string.h\ncpH time.h          fmt_time.h\n\nfor h in $HEADS; do\n    for f in *.cpp; do\n        sed -i 's/\\\"'$h'\\\"/\\\"fmt_'$h'\\\"/g' $f\n    done\n    for f in *.h; do\n        sed -i 's/\\\"'$h'\\\"/\\\"fmt_'$h'\\\"/g' $f\n    done\ndone\n\n"
  },
  {
    "path": "lib/fmt_format_ne.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef FMT_FORMAT_NE_H\n#define FMT_FORMAT_NE_H\n\n#include <fmt/fmt_format.h>\n#include <fmt/fmt_printf.h>\n#include \"Logger/logger.h\"\n\nnamespace fmt\n{\n\n/*\n    Exception-less fmt::format version. Instead of exception, the error message will be logged into file\n*/\nstd::string sprintf_ne(const char *fstr, ...) FORMAT_ATTRIBUTE_PRINTF_12;\n\ntemplate <typename... Args>\nstd::string format_ne(CStringRef format_str, const Args & ... args)\n{\n    try\n    {\n        return format(format_str, std::forward<const Args&>(args)...);\n    }\n    catch(const FormatError &e)\n    {\n        std::string out;\n        pLogWarning(\"fmt::format error: Thrown exception [%s] on attempt to process string [%s]\",\n                    e.what(),\n                    format_str.c_str());\n        out.append(e.what());\n        out.append(\" [\");\n        out.append(format_str.c_str());\n        out.push_back(']');\n        return out;\n    }\n    catch(...)\n    {\n        pLogWarning(\"fmt::format error: Thrown unknown exception on attempt to process string [%s]\", format_str.c_str());\n        return \"<ERROR OF \" + std::string(format_str.c_str()) + \">\";\n    }\n}\n\n}//namespace fmt\n\n#endif // FMT_FORMAT_NE_H\n"
  },
  {
    "path": "lib/fmt_impl.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <stdarg.h>\n#include <fmt/fmt_time.h>\n#include <fmt/fmt_printf.h>\n#include <fmt/fmt_format.h>\n#include <fmt/fmt_printf.h>\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"Logger/logger.h\"\n\n#include \"fmt_format_ne.h\"\n#include \"fmt_time_ne.h\"\n\nnamespace fmt\n{\n\nstd::tm localtime_ne(std::time_t time)\n{\n    try\n    {\n        return localtime(time);\n    }\n    catch(const FormatError &e)\n    {\n        std::tm t;\n        std::memset(&t, 0, sizeof(std::tm));\n        pLogFatal(\"fmt::format error: Thrown exception [%s] on attempt to process localtime\", e.what());\n        return t;\n    }\n    catch(...)\n    {\n        std::tm t;\n        std::memset(&t, 0, sizeof(std::tm));\n        pLogFatal(\"fmt::format error: Thrown unknown exception on attempt to process localtime\");\n        return t;\n    }\n}\n\nstd::string sprintf_ne(const char *fstr, ...)\n{\n    std::string ret;\n    va_list arg;\n    int len = SDL_strlen(fstr);\n    int argLen = 0;\n\n    // Calculate coarse length of arguments\n    for(const char *b = fstr; *b; ++b)\n    {\n        if(*b == '%')\n        {\n            ++b;\n            if(*b == 's')\n                argLen += 128; // Reserve ~1000 for every string\n            else if(*b == '%')\n                continue; // Not an arg!\n            else\n                argLen += 10;\n        }\n    }\n\n    ret.resize(len + argLen);\n\n    va_start(arg, fstr);\n    len = SDL_vsnprintf(&ret[0], ret.size(), fstr, arg);\n    va_end(arg);\n\n    if(len > (int)ret.size())\n    {\n        ret.resize(len + 1);\n        va_start(arg, fstr);\n        SDL_vsnprintf(&ret[0], ret.size(), fstr, arg);\n        va_end(arg);\n    }\n    else\n        ret.resize(len);\n\n    return ret;\n}\n\n}\n"
  },
  {
    "path": "lib/fmt_time_ne.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <fmt/fmt_time.h>\n#include <fmt/fmt_printf.h>\n#include <Logger/logger.h>\n\nnamespace fmt\n{\nstd::tm localtime_ne(std::time_t time);\n}\n"
  },
  {
    "path": "lib/forced_int.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef FORCED_INT_H\n#define FORCED_INT_H\n\ntemplate<class t_vbint>\nstruct forced_int\n{\nprivate:\n    t_vbint i;\npublic:\n    forced_int(t_vbint _i) : i(_i) {}\n    forced_int(double _i) = delete;\n\n    forced_int& operator=(t_vbint o)\n    {\n        i = o;\n        return *this;\n    }\n\n    forced_int& operator=(double o) = delete;\n\n    forced_int& operator++()\n    {\n        i++;\n        return *this;\n    }\n\n    forced_int operator++(int)\n    {\n        forced_int ret(i);\n        i++;\n        return ret;\n    }\n\n    forced_int& operator+=(t_vbint o)\n    {\n        i += o;\n        return *this;\n    }\n\n    forced_int& operator+=(double o) = delete;\n\n    forced_int& operator-=(t_vbint o)\n    {\n        i -= o;\n        return *this;\n    }\n\n    forced_int& operator-=(double o) = delete;\n\n    operator t_vbint() const\n    {\n        return i;\n    }\n};\n\n#endif // #ifndef FORCED_INT_H\n"
  },
  {
    "path": "lib/gif.h",
    "content": "//\n// gif.h\n// by Charlie Tangora\n// Public domain.\n// Email me : ctangora -at- gmail -dot- com\n//\n// This file offers a simple, very limited way to create animated GIFs directly in code.\n//\n// Those looking for particular cleverness are likely to be disappointed; it's pretty\n// much a straight-ahead implementation of the GIF format with optional Floyd-Steinberg\n// dithering. (It does at least use delta encoding - only the changed portions of each\n// frame are saved.)\n//\n// So resulting files are often quite large. The hope is that it will be handy nonetheless\n// as a quick and easily-integrated way for programs to spit out animations.\n//\n// Only RGBA8 is currently supported as an input format. (The alpha is ignored.)\n//\n// USAGE:\n// Create a GifWriter struct. Pass it to GifBegin() to initialize and write the header.\n// Pass subsequent frames to GifWriteFrame().\n// Finally, call GifEnd() to close the file handle and free memory.\n//\n\n#ifndef gif_h\n#define gif_h\n\n#include <SDL2/SDL_rwops.h>   // for SDL_RWops*\n#include <string.h>  // for memcpy and bzero\n#include <stdint.h>  // for integer typedefs\n#include <assert.h>\n\n// Define these macros to hook into a custom memory allocator.\n// TEMP_MALLOC and TEMP_FREE will only be called in stack fashion - frees in the reverse order of mallocs\n// and any temp memory allocated by a function will be freed before it exits.\n// MALLOC and FREE are used only by GifBegin and GifEnd respectively (to allocate a buffer the size of the image, which\n// is used to find changed pixels for delta-encoding.)\n\n#ifndef GIF_TEMP_MALLOC\n#include <stdlib.h>\n#define GIF_TEMP_MALLOC malloc\n#endif\n\n#ifndef GIF_TEMP_FREE\n#include <stdlib.h>\n#define GIF_TEMP_FREE free\n#endif\n\n#ifndef GIF_MALLOC\n#include <stdlib.h>\n#define GIF_MALLOC malloc\n#endif\n\n#ifndef GIF_FREE\n#include <stdlib.h>\n#define GIF_FREE free\n#endif\n\n#include \"gif_writer.h\"\n\nnamespace GIF_H\n{\n\nstatic const int kGifTransIndex = 0;\n\n#ifdef GIF_H_BIG_ENDIAN\nstatic const int PIX_R = 3;\nstatic const int PIX_G = 2;\nstatic const int PIX_B = 1;\nstatic const int PIX_I = 0;\n#else\nstatic const int PIX_R = 0;\nstatic const int PIX_G = 1;\nstatic const int PIX_B = 2;\nstatic const int PIX_I = 3;\n#endif\n\nstruct GifPalette\n{\n    int bitDepth;\n\n    uint8_t r[256];\n    uint8_t g[256];\n    uint8_t b[256];\n\n    // k-d tree over RGB space, organized in heap fashion\n    // i.e. left child of node i is node i*2, right child is node i*2+1\n    // nodes 256-511 are implicitly the leaves, containing a color\n    uint8_t treeSplitElt[256];\n    uint8_t treeSplit[256];\n};\n\n// max, min, and abs functions\nstatic int GifIMax(int l, int r) { return l>r?l:r; }\nstatic int GifIMin(int l, int r) { return l<r?l:r; }\nstatic int GifIAbs(int i) { return i<0?-i:i; }\n\nstatic const int colorDimScales[3] = {14, 20, 17};\n\n// walks the k-d tree to pick the palette entry for a desired color.\n// Takes as in/out parameters the current best color and its error -\n// only changes them if it finds a better color in its subtree.\n// this is the major hotspot in the code at the moment.\nstatic void GifGetClosestPaletteColor(GifPalette* pPal, int r, int g, int b, int& bestInd, int& bestDiff, int treeRoot = 1)\n{\n    // base case, reached the bottom of the tree\n    if(treeRoot > (1<<pPal->bitDepth)-1)\n    {\n        int ind = treeRoot-(1<<pPal->bitDepth);\n        if(ind == kGifTransIndex) return;\n\n        // check whether this color is better than the current winner\n        int r_err = r - ((int32_t)pPal->r[ind]);\n        int g_err = g - ((int32_t)pPal->g[ind]);\n        int b_err = b - ((int32_t)pPal->b[ind]);\n        // RED: Increase green importance\n        int diff = colorDimScales[0] * GifIAbs(r_err) + colorDimScales[1] * GifIAbs(g_err) + colorDimScales[2] * GifIAbs(b_err);\n\n        if(diff < bestDiff)\n        {\n            bestInd = ind;\n            bestDiff = diff;\n        }\n\n        return;\n    }\n\n    // take the appropriate color (r, g, or b) for this node of the k-d tree\n    int comps[3]; comps[0] = r; comps[1] = g; comps[2] = b;\n    int splitComp = comps[pPal->treeSplitElt[treeRoot]];\n    int dimScale = colorDimScales[pPal->treeSplitElt[treeRoot]];\n\n    int splitPos = pPal->treeSplit[treeRoot];\n    if(splitPos > splitComp)\n    {\n        // check the left subtree\n        GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2);\n        if( bestDiff > dimScale*(splitPos - splitComp) )\n        {\n            // cannot prove there's not a better value in the right subtree, check that too\n            GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1);\n        }\n    }\n    else\n    {\n        GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1);\n        if( bestDiff > dimScale*(splitComp - splitPos) )\n        {\n            GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2);\n        }\n    }\n}\n\nstatic void GifSwapPixels(uint8_t* image, int pixA, int pixB)\n{\n    uint8_t rA = image[pixA*4];\n    uint8_t gA = image[pixA*4+1];\n    uint8_t bA = image[pixA*4+2];\n    uint8_t aA = image[pixA*4+3];\n\n    uint8_t rB = image[pixB*4];\n    uint8_t gB = image[pixB*4+1];\n    uint8_t bB = image[pixB*4+2];\n    uint8_t aB = image[pixA*4+3];\n\n    image[pixA*4] = rB;\n    image[pixA*4+1] = gB;\n    image[pixA*4+2] = bB;\n    image[pixA*4+3] = aB;\n\n    image[pixB*4] = rA;\n    image[pixB*4+1] = gA;\n    image[pixB*4+2] = bA;\n    image[pixB*4+3] = aA;\n}\n\n// just the partition operation from quicksort\nstatic int GifPartition(uint8_t* image, const int left, const int right, const int elt, int pivotIndex)\n{\n    const int pivotValue = image[(pivotIndex)*4+elt];\n    GifSwapPixels(image, pivotIndex, right-1);\n    int storeIndex = left;\n    bool split = 0;\n    for(int ii=left; ii<right-1; ++ii)\n    {\n        int arrayVal = image[ii*4+elt];\n        if( arrayVal < pivotValue )\n        {\n            GifSwapPixels(image, ii, storeIndex);\n            ++storeIndex;\n        }\n        else if( arrayVal == pivotValue )\n        {\n            if(split)\n            {\n                GifSwapPixels(image, ii, storeIndex);\n                ++storeIndex;\n            }\n            split = !split;\n        }\n    }\n    GifSwapPixels(image, storeIndex, right-1);\n    return storeIndex;\n}\n\n// Perform an incomplete sort, finding all elements above and below the desired median\nstatic void GifPartitionByMedian(uint8_t* image, int left, int right, int com, int neededCenter)\n{\n    if(left < right-1)\n    {\n        int pivotIndex = left + (right-left)/2;\n\n        pivotIndex = GifPartition(image, left, right, com, pivotIndex);\n\n        // Only \"sort\" the section of the array that contains the median\n        if(pivotIndex > neededCenter)\n            GifPartitionByMedian(image, left, pivotIndex, com, neededCenter);\n\n        if(pivotIndex < neededCenter)\n            GifPartitionByMedian(image, pivotIndex+1, right, com, neededCenter);\n    }\n}\n\n// Builds a palette by creating a balanced k-d tree of all pixels in the image\nstatic void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int lastElt, int splitElt, int splitDist, int treeNode, bool buildForDither, GifPalette* pal)\n{\n    if(lastElt <= firstElt || numPixels == 0)\n        return;\n\n    // base case, bottom of the tree\n    if(lastElt == firstElt+1)\n    {\n        if(buildForDither)\n        {\n            // Dithering needs at least one color as dark as anything\n            // in the image and at least one brightest color -\n            // otherwise it builds up error and produces strange artifacts\n            if( firstElt == 1 )\n            {\n                // special case: the darkest color in the image\n                uint32_t r=255, g=255, b=255;\n                for(int ii=0; ii<numPixels; ++ii)\n                {\n                    r = GifIMin(r, image[ii*4+PIX_R]);\n                    g = GifIMin(g, image[ii*4+PIX_G]);\n                    b = GifIMin(b, image[ii*4+PIX_B]);\n                }\n\n                pal->r[firstElt] = r;\n                pal->g[firstElt] = g;\n                pal->b[firstElt] = b;\n\n                return;\n            }\n\n            if( firstElt == (1 << pal->bitDepth)-1 )\n            {\n                // special case: the lightest color in the image\n                uint32_t r=0, g=0, b=0;\n                for(int ii=0; ii<numPixels; ++ii)\n                {\n                    r = GifIMax(r, image[ii*4+PIX_R]);\n                    g = GifIMax(g, image[ii*4+PIX_G]);\n                    b = GifIMax(b, image[ii*4+PIX_B]);\n                }\n\n                pal->r[firstElt] = r;\n                pal->g[firstElt] = g;\n                pal->b[firstElt] = b;\n\n                return;\n            }\n        }\n\n        // otherwise, take the average of all colors in this subcube\n        uint64_t r=0, g=0, b=0;\n        for(int ii=0; ii<numPixels; ++ii)\n        {\n            r += image[ii*4+PIX_R];\n            g += image[ii*4+PIX_G];\n            b += image[ii*4+PIX_B];\n        }\n\n        r += numPixels / 2;  // round to nearest\n        g += numPixels / 2;\n        b += numPixels / 2;\n\n        r /= numPixels;\n        g /= numPixels;\n        b /= numPixels;\n\n        pal->r[firstElt] = (uint8_t)r;\n        pal->g[firstElt] = (uint8_t)g;\n        pal->b[firstElt] = (uint8_t)b;\n\n        return;\n    }\n\n    // Find the axis with the largest range\n    int minR = 255, maxR = 0;\n    int minG = 255, maxG = 0;\n    int minB = 255, maxB = 0;\n    for(int ii=0; ii<numPixels; ++ii)\n    {\n        int r = image[ii*4+PIX_R];\n        int g = image[ii*4+PIX_G];\n        int b = image[ii*4+PIX_B];\n\n        if(r > maxR) maxR = r;\n        if(r < minR) minR = r;\n\n        if(g > maxG) maxG = g;\n        if(g < minG) minG = g;\n\n        if(b > maxB) maxB = b;\n        if(b < minB) minB = b;\n    }\n\n    int rRange = maxR - minR;\n    int gRange = maxG - minG;\n    int bRange = maxB - minB;\n    rRange *= colorDimScales[0];\n    gRange *= colorDimScales[1]; // RED: Modification to increase green importance\n    bRange *= colorDimScales[2];\n\n    // and split along that axis. (incidentally, this means this isn't a \"proper\" k-d tree but I don't know what else to call it)\n    int splitCom = 1;\n    if(bRange > gRange) splitCom = 2;\n    if(rRange > bRange && rRange > gRange) splitCom = 0;\n\n    int subPixelsA = numPixels * (splitElt - firstElt) / (lastElt - firstElt);\n    int subPixelsB = numPixels-subPixelsA;\n\n    GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA);\n\n    pal->treeSplitElt[treeNode] = splitCom;\n    pal->treeSplit[treeNode] = image[subPixelsA*4+splitCom];\n\n    GifSplitPalette(image,              subPixelsA, firstElt, splitElt, splitElt-splitDist, splitDist/2, treeNode*2,   buildForDither, pal);\n    GifSplitPalette(image+subPixelsA*4, subPixelsB, splitElt, lastElt,  splitElt+splitDist, splitDist/2, treeNode*2+1, buildForDither, pal);\n}\n\n// Finds all pixels that have changed from the previous image and\n// moves them to the fromt of th buffer.\n// This allows us to build a palette optimized for the colors of the\n// changed pixels only.\nstatic int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int numPixels )\n{\n    int numChanged = 0;\n    uint8_t* writeIter = frame;\n\n    for (int ii=0; ii<numPixels; ++ii)\n    {\n        if(lastFrame[PIX_R] != frame[PIX_R] ||\n           lastFrame[PIX_G] != frame[PIX_G] ||\n           lastFrame[PIX_B] != frame[PIX_B])\n        {\n            writeIter[PIX_R] = frame[PIX_R];\n            writeIter[PIX_G] = frame[PIX_G];\n            writeIter[PIX_B] = frame[PIX_B];\n            ++numChanged;\n            writeIter += 4;\n        }\n        lastFrame += 4;\n        frame += 4;\n    }\n\n    return numChanged;\n}\n\n// Creates a palette by placing all the image pixels in a k-d tree and then averaging the blocks at the bottom.\n// This is known as the \"modified median split\" technique\nstatic void GifMakePalette( const uint8_t* lastFrame, const uint8_t* nextFrame, uint32_t width, uint32_t height, int bitDepth, bool buildForDither, GifPalette* pPal )\n{\n    pPal->bitDepth = bitDepth;\n\n    // SplitPalette is destructive (it sorts the pixels by color) so\n    // we must create a copy of the image for it to destroy\n    int imageSize = width*height*4*sizeof(uint8_t);\n    uint8_t* destroyableImage = (uint8_t*)GIF_TEMP_MALLOC(imageSize);\n    assert(destroyableImage);\n    assert(nextFrame);\n    memcpy(destroyableImage, nextFrame, imageSize);\n\n    int numPixels = width*height;\n    if(lastFrame)\n        numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels);\n\n    const int lastElt = 1 << bitDepth;\n    const int splitElt = lastElt/2;\n    const int splitDist = splitElt/2;\n\n    GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist, 1, buildForDither, pPal);\n\n    GIF_TEMP_FREE(destroyableImage);\n\n    // add the bottom node for the transparency index\n    pPal->treeSplit[1 << (bitDepth-1)] = 0;\n    pPal->treeSplitElt[1 << (bitDepth-1)] = 0;\n\n    pPal->r[0] = pPal->g[0] = pPal->b[0] = 0;\n}\n\n// Implements Floyd-Steinberg dithering, writes palette value to alpha\nstatic void GifDitherImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal )\n{\n    int numPixels = width*height;\n\n    // quantPixels initially holds color*256 for all pixels\n    // The extra 8 bits of precision allow for sub-single-color error values\n    // to be propagated\n    int32_t* quantPixels = (int32_t*)GIF_TEMP_MALLOC(sizeof(int32_t)*numPixels*4);\n    assert(quantPixels);\n\n    for( int ii=0; ii<numPixels*4; ++ii )\n    {\n        uint8_t pix = nextFrame[ii];\n        int32_t pix16 = int32_t(pix) * 256;\n        quantPixels[ii] = pix16;\n    }\n\n    for( uint32_t yy=0; yy<height; ++yy )\n    {\n        for( uint32_t xx=0; xx<width; ++xx )\n        {\n            int32_t* nextPix = quantPixels + 4*(yy*width+xx);\n            const uint8_t* lastPix = lastFrame? lastFrame + 4*(yy*width+xx) : NULL;\n\n            // Compute the colors we want (rounding to nearest)\n            int32_t rr = (nextPix[0] + 127) / 256;\n            int32_t gg = (nextPix[1] + 127) / 256;\n            int32_t bb = (nextPix[2] + 127) / 256;\n\n            // if it happens that we want the color from last frame, then just write out\n            // a transparent pixel\n            if( lastFrame &&\n               lastPix[PIX_R] == rr &&\n               lastPix[PIX_G] == gg &&\n               lastPix[PIX_B] == bb )\n            {\n                nextPix[0] = rr;\n                nextPix[1] = gg;\n                nextPix[2] = bb;\n                nextPix[3] = kGifTransIndex;\n                continue;\n            }\n\n            int32_t bestDiff = 1000000;\n            int32_t bestInd = kGifTransIndex;\n\n            // Search the palete\n            GifGetClosestPaletteColor(pPal, rr, gg, bb, bestInd, bestDiff);\n\n            // Write the result to the temp buffer\n            int32_t r_err = nextPix[0] - int32_t(pPal->r[bestInd]) * 256;\n            int32_t g_err = nextPix[1] - int32_t(pPal->g[bestInd]) * 256;\n            int32_t b_err = nextPix[2] - int32_t(pPal->b[bestInd]) * 256;\n\n            nextPix[0] = pPal->r[bestInd];\n            nextPix[1] = pPal->g[bestInd];\n            nextPix[2] = pPal->b[bestInd];\n            nextPix[3] = bestInd;\n\n            // Propagate the error to the four adjacent locations\n            // that we haven't touched yet\n            int quantloc_7 = (yy*width+xx+1);\n            int quantloc_3 = (yy*width+width+xx-1);\n            int quantloc_5 = (yy*width+width+xx);\n            int quantloc_1 = (yy*width+width+xx+1);\n\n            if(quantloc_7 < numPixels)\n            {\n                int32_t* pix7 = quantPixels+4*quantloc_7;\n                pix7[0] += GifIMax( -pix7[0], r_err * 7 / 16 );\n                pix7[1] += GifIMax( -pix7[1], g_err * 7 / 16 );\n                pix7[2] += GifIMax( -pix7[2], b_err * 7 / 16 );\n            }\n\n            if(quantloc_3 < numPixels)\n            {\n                int32_t* pix3 = quantPixels+4*quantloc_3;\n                pix3[0] += GifIMax( -pix3[0], r_err * 3 / 16 );\n                pix3[1] += GifIMax( -pix3[1], g_err * 3 / 16 );\n                pix3[2] += GifIMax( -pix3[2], b_err * 3 / 16 );\n            }\n\n            if(quantloc_5 < numPixels)\n            {\n                int32_t* pix5 = quantPixels+4*quantloc_5;\n                pix5[0] += GifIMax( -pix5[0], r_err * 5 / 16 );\n                pix5[1] += GifIMax( -pix5[1], g_err * 5 / 16 );\n                pix5[2] += GifIMax( -pix5[2], b_err * 5 / 16 );\n            }\n\n            if(quantloc_1 < numPixels)\n            {\n                int32_t* pix1 = quantPixels+4*quantloc_1;\n                pix1[0] += GifIMax( -pix1[0], r_err / 16 );\n                pix1[1] += GifIMax( -pix1[1], g_err / 16 );\n                pix1[2] += GifIMax( -pix1[2], b_err / 16 );\n            }\n        }\n    }\n\n    // Copy the palettized result to the output buffer\n    for( int ii=0; ii<numPixels*4; ++ii )\n    {\n        outFrame[ii] = quantPixels[ii];\n    }\n\n    GIF_TEMP_FREE(quantPixels);\n}\n\n// Picks palette colors for the image using simple thresholding, no dithering\nstatic void GifThresholdImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal )\n{\n    uint32_t numPixels = width*height;\n    for( uint32_t ii=0; ii<numPixels; ++ii )\n    {\n        // if a previous color is available, and it matches the current color,\n        // set the pixel to transparent\n        if(lastFrame &&\n           lastFrame[PIX_R] == nextFrame[PIX_R] &&\n           lastFrame[PIX_G] == nextFrame[PIX_G] &&\n           lastFrame[PIX_B] == nextFrame[PIX_B])\n        {\n            outFrame[0] = lastFrame[PIX_R];\n            outFrame[1] = lastFrame[PIX_G];\n            outFrame[2] = lastFrame[PIX_B];\n            outFrame[3] = kGifTransIndex;\n        }\n        else\n        {\n            // palettize the pixel\n            int32_t bestDiff = 1000000;\n            int32_t bestInd = 1;\n            GifGetClosestPaletteColor(pPal, nextFrame[PIX_R], nextFrame[PIX_G], nextFrame[PIX_B], bestInd, bestDiff);\n\n            bool usedOld = false;\n            if (lastFrame)\n            {\n                // RED: If the chosen one is worse than the old one, don't go with it\n                int r_err = (int)lastFrame[PIX_R] - (int)nextFrame[PIX_R];\n                int g_err = (int)lastFrame[PIX_G] - (int)nextFrame[PIX_G];\n                int b_err = (int)lastFrame[PIX_B] - (int)nextFrame[PIX_B];\n                int oldDiff = colorDimScales[0] * GifIAbs(r_err) + colorDimScales[1] * GifIAbs(g_err) + colorDimScales[2] * GifIAbs(b_err);\n                if (oldDiff <= bestDiff)\n                {\n                    outFrame[0] = lastFrame[PIX_R];\n                    outFrame[1] = lastFrame[PIX_G];\n                    outFrame[2] = lastFrame[PIX_B];\n                    outFrame[3] = kGifTransIndex;\n                    usedOld = true;\n                }\n            }\n\n            if (!usedOld)\n            {\n                // Write the resulting color to the output buffer\n                outFrame[0] = pPal->r[bestInd];\n                outFrame[1] = pPal->g[bestInd];\n                outFrame[2] = pPal->b[bestInd];\n                outFrame[3] = bestInd;\n            }\n        }\n\n        if(lastFrame) lastFrame += 4;\n        outFrame += 4;\n        nextFrame += 4;\n    }\n}\n\n// Simple structure to write out the LZW-compressed portion of the image\n// one bit at a time\nstruct GifBitStatus\n{\n    uint8_t bitIndex;  // how many bits in the partial byte written so far\n    uint8_t byte;      // current partial byte\n\n    uint32_t chunkIndex;\n    uint8_t chunk[256];   // bytes are written in here until we have 256 of them, then written to the file\n};\n\n// insert a single bit\nstatic void GifWriteBit( GifBitStatus& stat, uint32_t bit )\n{\n    bit = bit & 1;\n    bit = bit << stat.bitIndex;\n    stat.byte |= bit;\n\n    ++stat.bitIndex;\n    if( stat.bitIndex > 7 )\n    {\n        // move the newly-finished byte to the chunk buffer\n        stat.chunk[stat.chunkIndex++] = stat.byte;\n        // and start a new byte\n        stat.bitIndex = 0;\n        stat.byte = 0;\n    }\n}\n\n// write all bytes so far to the file\nstatic void GifWriteChunk( buf_t& buffer, GifBitStatus& stat )\n{\n    buffer.push_back(stat.chunkIndex);\n    buffer.append(stat.chunk, stat.chunkIndex);\n\n    stat.bitIndex = 0;\n    stat.byte = 0;\n    stat.chunkIndex = 0;\n}\n\nstatic void GifWriteCode( buf_t& buffer, GifBitStatus& stat, uint32_t code, uint32_t length )\n{\n    for( uint32_t ii=0; ii<length; ++ii )\n    {\n        GifWriteBit(stat, code);\n        code = code >> 1;\n\n        if( stat.chunkIndex == 255 )\n        {\n            GifWriteChunk(buffer, stat);\n        }\n    }\n}\n\n// The LZW dictionary is a 256-ary tree constructed as the file is encoded,\n// this is one node\nstruct GifLzwNode\n{\n    uint16_t m_next[256];\n};\n\n// write a 256-color (8-bit) image palette to the file\nstatic void GifWritePalette( const GifPalette* pPal, buf_t& buffer )\n{\n    buffer.push_back(0);  // first color: transparency\n    buffer.push_back(0);\n    buffer.push_back(0);\n\n    for(int ii=1; ii<(1 << pPal->bitDepth); ++ii)\n    {\n        uint32_t r = pPal->r[ii];\n        uint32_t g = pPal->g[ii];\n        uint32_t b = pPal->b[ii];\n\n        buffer.push_back(r);\n        buffer.push_back(g);\n        buffer.push_back(b);\n    }\n}\n\n// write the image header, LZW-compress and write out the image\nstatic void GifWriteLzwImage(SDL_RWops* f, uint8_t* image, uint32_t left, uint32_t top,  uint32_t width, uint32_t height, uint32_t delay, GifPalette* pPal, long int *delaypos, buf_t& buffer)\n{\n    buffer.clear();\n\n    // graphics control extension\n    buffer.push_back(0x21);\n    buffer.push_back(0xf9);\n    buffer.push_back(0x04);\n    buffer.push_back(0x05); // leave prev frame in place, this frame has transparency\n    if (delaypos) *delaypos = SDL_RWtell(f);\n    buffer.push_back(delay & 0xff);\n    buffer.push_back((delay >> 8) & 0xff);\n    buffer.push_back(kGifTransIndex); // transparent color index\n    buffer.push_back(0);\n\n    buffer.push_back(0x2c); // image descriptor block\n\n    buffer.push_back(left & 0xff);           // corner of image in canvas space\n    buffer.push_back((left >> 8) & 0xff);\n    buffer.push_back(top & 0xff);\n    buffer.push_back((top >> 8) & 0xff);\n\n    buffer.push_back(width & 0xff);          // width and height of image\n    buffer.push_back((width >> 8) & 0xff);\n    buffer.push_back(height & 0xff);\n    buffer.push_back((height >> 8) & 0xff);\n\n    //fputc(0, f); // no local color table, no transparency\n    //fputc(0x80, f); // no local color table, but transparency\n\n    buffer.push_back(0x80 + pPal->bitDepth-1); // local color table present, 2 ^ bitDepth entries\n    GifWritePalette(pPal, buffer);\n\n    const int minCodeSize = pPal->bitDepth;\n    const uint32_t clearCode = 1 << pPal->bitDepth;\n\n    buffer.push_back(minCodeSize); // min code size 8 bits\n\n    GifLzwNode* codetree = (GifLzwNode*)GIF_TEMP_MALLOC(sizeof(GifLzwNode)*4096);\n    assert(codetree);\n\n    memset(codetree, 0, sizeof(GifLzwNode)*4096);\n    int32_t curCode = -1;\n    uint32_t codeSize = minCodeSize+1;\n    uint32_t maxCode = clearCode+1;\n\n    GifBitStatus stat;\n    stat.byte = 0;\n    stat.bitIndex = 0;\n    stat.chunkIndex = 0;\n\n    GifWriteCode(buffer, stat, clearCode, codeSize);  // start with a fresh LZW dictionary\n\n    for(uint32_t yy=0; yy<height; ++yy)\n    {\n        for(uint32_t xx=0; xx<width; ++xx)\n        {\n            uint8_t nextValue = image[(yy*width+xx)*4+3];\n\n            // \"loser mode\" - no compression, every single code is followed immediately by a clear\n            //WriteCode( f, stat, nextValue, codeSize );\n            //WriteCode( f, stat, 256, codeSize );\n\n            if( curCode < 0 )\n            {\n                // first value in a new run\n                curCode = nextValue;\n            }\n            else if( codetree[curCode].m_next[nextValue] )\n            {\n                // current run already in the dictionary\n                curCode = codetree[curCode].m_next[nextValue];\n            }\n            else\n            {\n                // finish the current run, write a code\n                GifWriteCode( buffer, stat, curCode, codeSize );\n\n                // insert the new run into the dictionary\n                codetree[curCode].m_next[nextValue] = ++maxCode;\n\n                if( maxCode >= (1ul << codeSize) )\n                {\n                    // dictionary entry count has broken a size barrier,\n                    // we need more bits for codes\n                    codeSize++;\n                }\n                if( maxCode == 4095 )\n                {\n                    // the dictionary is full, clear it out and begin anew\n                    GifWriteCode(buffer, stat, clearCode, codeSize); // clear tree\n\n                    memset(codetree, 0, sizeof(GifLzwNode)*4096);\n                    curCode = -1;\n                    codeSize = minCodeSize+1;\n                    maxCode = clearCode+1;\n                }\n\n                curCode = nextValue;\n            }\n        }\n    }\n\n    // compression footer\n    GifWriteCode( buffer, stat, curCode, codeSize );\n    GifWriteCode( buffer, stat, clearCode, codeSize );\n    GifWriteCode( buffer, stat, clearCode+1, minCodeSize+1 );\n\n    // write out the last partial chunk\n    while( stat.bitIndex ) GifWriteBit(stat, 0);\n    if( stat.chunkIndex ) GifWriteChunk(buffer, stat);\n\n    buffer.push_back(0); // image block terminator\n\n    SDL_RWwrite(f, &buffer[0], 1, buffer.size());\n\n    GIF_TEMP_FREE(codetree);\n}\n\n\n// GifWriter moved into the own header\n\n// Creates a gif file.\n// The input GIFWriter is assumed to be uninitialized.\n// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value.\nstatic bool GifBegin( GifWriter* writer, SDL_RWops *file, uint32_t width, uint32_t height, uint32_t delay, int32_t bitDepth = 8, bool dither = false )\n{\n    (void)bitDepth;\n    (void)dither;\n    writer->f = file;\n    if(!writer->f) return false;\n\n    writer->firstFrame = true;\n    writer->delaypos = -1;\n\n    // allocate\n    writer->oldImage = (uint8_t*)GIF_MALLOC(width*height*4);\n\n    auto& buffer = writer->buffer;\n    buffer.clear();\n\n    buffer.append((const uint8_t*)\"GIF89a\");\n\n    // screen descriptor\n    buffer.push_back(width & 0xff);\n    buffer.push_back((width >> 8) & 0xff);\n    buffer.push_back(height & 0xff);\n    buffer.push_back((height >> 8) & 0xff);\n\n    buffer.push_back(0xf0);  // there is an unsorted global color table of 2 entries\n    buffer.push_back(0);     // background color\n    buffer.push_back(0);     // pixels are square (we need to specify this because it's 1989)\n\n    // now the \"global\" palette (really just a dummy palette)\n    // color 0: black\n    buffer.push_back(0);\n    buffer.push_back(0);\n    buffer.push_back(0);\n    // color 1: also black\n    buffer.push_back(0);\n    buffer.push_back(0);\n    buffer.push_back(0);\n\n    if( delay != 0 )\n    {\n        // animation header\n        buffer.push_back(0x21); // extension\n        buffer.push_back(0xff); // application specific\n        buffer.push_back(11); // length 11\n        buffer.append((const uint8_t*)\"NETSCAPE2.0\"); // yes, really\n        buffer.push_back(3); // 3 bytes of NETSCAPE2.0 data\n\n        buffer.push_back(1); // JUST BECAUSE\n        buffer.push_back(0); // loop infinitely (byte 0)\n        buffer.push_back(0); // loop infinitely (byte 1)\n\n        buffer.push_back(0); // block terminator\n    }\n\n    SDL_RWwrite(writer->f, &buffer[0], 1, buffer.size());\n\n    return true;\n}\n\n// Writes out a new frame to a GIF in progress.\n// The GIFWriter should have been created by GIFBegin.\n// AFAIK, it is legal to use different bit depths for different frames of an image -\n// this may be handy to save bits in animations that don't change much.\nstatic bool GifWriteFrame( GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, int bitDepth = 8, bool dither = false )\n{\n    if(!writer->f) return false;\n\n    const uint8_t* oldImage = writer->firstFrame? NULL : writer->oldImage;\n    writer->firstFrame = false;\n\n    GifPalette pal;\n    GifMakePalette((dither? NULL : oldImage), image, width, height, bitDepth, dither, &pal);\n\n    if(dither)\n        GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal);\n    else\n        GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal);\n\n    GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal, &writer->delaypos, writer->buffer);\n\n    return true;\n}\n\nstatic void GifOverwriteLastDelay(GifWriter* writer, uint32_t delay)\n{\n    if (writer->delaypos == -1) return;\n    SDL_RWops* f = writer->f;\n\n    long int pos = SDL_RWtell(f);\n    SDL_RWseek(f, writer->delaypos, RW_SEEK_SET);\n    uint8_t buf[2] = {uint8_t(delay & 0xff), uint8_t((delay >> 8) & 0xff)};\n    SDL_RWwrite(f, buf, 1, 2);\n    SDL_RWseek(f, pos, RW_SEEK_SET);\n}\n\n// Writes the EOF code, closes the file handle, and frees temp memory used by a GIF.\n// Many if not most viewers will still display a GIF properly if the EOF code is missing,\n// but it's still a good idea to write it out.\nstatic bool GifEnd( GifWriter* writer )\n{\n    if(!writer->f) return false;\n\n    SDL_RWwrite(writer->f, \"\\x3b\", 1, 1); // end of file\n    SDL_RWclose(writer->f);\n    GIF_FREE(writer->oldImage);\n\n    writer->f = NULL;\n    writer->oldImage = NULL;\n    writer->delaypos = -1;\n\n    return true;\n}\n\n} // namespace\n\n#endif\n"
  },
  {
    "path": "lib/gif_writer.h",
    "content": "#ifndef GIF_WRITER_HHHH\n#define GIF_WRITER_HHHH\n\n#include <stdint.h>  // for integer typedefs\n#include <string>\n\nstruct SDL_RWops;\n\nnamespace GIF_H\n{\n\nusing buf_t = std::basic_string<uint8_t>;\n\nstruct GifWriter\n{\n    SDL_RWops* f;\n    uint8_t* oldImage;\n    bool firstFrame;\n    long int delaypos;\n    buf_t buffer;\n};\n\n\n}\n\n#endif // GIF_WRITER_HHHH\n"
  },
  {
    "path": "lib/json/LICENSE.MIT",
    "content": "MIT License \n\nCopyright (c) 2013-2022 Niels Lohmann\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "lib/json/README.md",
    "content": "[![JSON for Modern C++](docs/json.gif)](https://github.com/nlohmann/json/releases)\r\n\r\n[![Build Status](https://ci.appveyor.com/api/projects/status/1acb366xfyg3qybk/branch/develop?svg=true)](https://ci.appveyor.com/project/nlohmann/json)\r\n[![Ubuntu](https://github.com/nlohmann/json/workflows/Ubuntu/badge.svg)](https://github.com/nlohmann/json/actions?query=workflow%3AUbuntu)\r\n[![macOS](https://github.com/nlohmann/json/workflows/macOS/badge.svg)](https://github.com/nlohmann/json/actions?query=workflow%3AmacOS)\r\n[![Windows](https://github.com/nlohmann/json/workflows/Windows/badge.svg)](https://github.com/nlohmann/json/actions?query=workflow%3AWindows)\r\n[![Coverage Status](https://coveralls.io/repos/github/nlohmann/json/badge.svg?branch=develop)](https://coveralls.io/github/nlohmann/json?branch=develop)\r\n[![Coverity Scan Build Status](https://scan.coverity.com/projects/5550/badge.svg)](https://scan.coverity.com/projects/nlohmann-json)\r\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/e0d1a9d5d6fd46fcb655c4cb930bb3e8)](https://www.codacy.com/gh/nlohmann/json/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=nlohmann/json&amp;utm_campaign=Badge_Grade)\r\n[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/nlohmann/json.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/nlohmann/json/context:cpp)\r\n[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/json.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:json)\r\n[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/1mp10JbaANo6FUc7)\r\n[![Documentation](https://img.shields.io/badge/docs-mkdocs-blue.svg)](https://json.nlohmann.me)\r\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nlohmann/json/master/LICENSE.MIT)\r\n[![GitHub Releases](https://img.shields.io/github/release/nlohmann/json.svg)](https://github.com/nlohmann/json/releases)\r\n[![Packaging status](https://repology.org/badge/tiny-repos/nlohmann-json.svg)](https://repology.org/project/nlohmann-json/versions)\r\n[![GitHub Downloads](https://img.shields.io/github/downloads/nlohmann/json/total)](https://github.com/nlohmann/json/releases)\r\n[![GitHub Issues](https://img.shields.io/github/issues/nlohmann/json.svg)](https://github.com/nlohmann/json/issues)\r\n[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/nlohmann/json.svg)](https://isitmaintained.com/project/nlohmann/json \"Average time to resolve an issue\")\r\n[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/289/badge)](https://bestpractices.coreinfrastructure.org/projects/289)\r\n[![GitHub Sponsors](https://img.shields.io/badge/GitHub-Sponsors-ff69b4)](https://github.com/sponsors/nlohmann)\r\n[![REUSE status](https://api.reuse.software/badge/github.com/nlohmann/json)](https://api.reuse.software/info/github.com/nlohmann/json)\r\n[![Discord](https://img.shields.io/discord/1003743314341793913)](https://discord.gg/6mrGXKvX7y)\r\n\r\n- [Design goals](#design-goals)\r\n- [Sponsors](#sponsors)\r\n- [Support](#support) ([documentation](https://json.nlohmann.me), [FAQ](https://json.nlohmann.me/home/faq/), [discussions](https://github.com/nlohmann/json/discussions), [API](https://json.nlohmann.me/api/basic_json/), [bug issues](https://github.com/nlohmann/json/issues))\r\n- [Examples](#examples)\r\n  - [Read JSON from a file](#read-json-from-a-file)\r\n  - [Creating `json` objects from JSON literals](#creating-json-objects-from-json-literals)\r\n  - [JSON as first-class data type](#json-as-first-class-data-type)\r\n  - [Serialization / Deserialization](#serialization--deserialization)\r\n  - [STL-like access](#stl-like-access)\r\n  - [Conversion from STL containers](#conversion-from-stl-containers)\r\n  - [JSON Pointer and JSON Patch](#json-pointer-and-json-patch)\r\n  - [JSON Merge Patch](#json-merge-patch)\r\n  - [Implicit conversions](#implicit-conversions)\r\n  - [Conversions to/from arbitrary types](#arbitrary-types-conversions)\r\n  - [Specializing enum conversion](#specializing-enum-conversion)\r\n  - [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata)\r\n- [Supported compilers](#supported-compilers)\r\n- [Integration](#integration)\r\n  - [CMake](#cmake)\r\n  - [Package Managers](#package-managers)\r\n  - [Pkg-config](#pkg-config)\r\n- [License](#license)\r\n- [Contact](#contact)\r\n- [Thanks](#thanks)\r\n- [Used third-party tools](#used-third-party-tools)\r\n- [Projects using JSON for Modern C++](#projects-using-json-for-modern-c)\r\n- [Notes](#notes)\r\n- [Execute unit tests](#execute-unit-tests)\r\n\r\n## Design goals\r\n\r\nThere are myriads of [JSON](https://json.org) libraries out there, and each may even have its reason to exist. Our class had these design goals:\r\n\r\n- **Intuitive syntax**. In languages such as Python, JSON feels like a first class data type. We used all the operator magic of modern C++ to achieve the same feeling in your code. Check out the [examples below](#examples) and you'll know what I mean.\r\n\r\n- **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings.\r\n\r\n- **Serious testing**. Our code is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/tests/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](https://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests against all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [Core Infrastructure Initiative (CII) best practices](https://bestpractices.coreinfrastructure.org/projects/289).\r\n\r\nOther aspects were not so important to us:\r\n\r\n- **Memory efficiency**. Each JSON object has an overhead of one pointer (the maximal size of a union) and one enumeration element (1 byte). The default generalization uses the following C++ data types: `std::string` for strings, `int64_t`, `uint64_t` or `double` for numbers, `std::map` for objects, `std::vector` for arrays, and `bool` for Booleans. However, you can template the generalized class `basic_json` to your needs.\r\n\r\n- **Speed**. There are certainly [faster JSON libraries](https://github.com/miloyip/nativejson-benchmark#parsing-time) out there. However, if your goal is to speed up your development by adding JSON support with a single header, then this library is the way to go. If you know how to use a `std::vector` or `std::map`, you are already set.\r\n\r\nSee the [contribution guidelines](https://github.com/nlohmann/json/blob/master/.github/CONTRIBUTING.md#please-dont) for more information.\r\n\r\n\r\n## Sponsors\r\n\r\nYou can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nlohmann).\r\n\r\n### :office: Corporate Sponsor\r\n\r\n[![](https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/Codacy-logo-black.svg/320px-Codacy-logo-black.svg.png)](https://github.com/codacy/About)\r\n\r\n### :label: Named Sponsors\r\n\r\n- [Michael Hartmann](https://github.com/reFX-Mike)\r\n- [Stefan Hagen](https://github.com/sthagen)\r\n- [Steve Sperandeo](https://github.com/homer6)\r\n- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)\r\n- [Steve Wagner](https://github.com/ciroque)\r\n\r\nThanks everyone!\r\n\r\n## Support\r\n\r\n:question: If you have a **question**, please check if it is already answered in the [**FAQ**](https://json.nlohmann.me/home/faq/) or the [**Q&A**](https://github.com/nlohmann/json/discussions/categories/q-a) section. If not, please [**ask a new question**](https://github.com/nlohmann/json/discussions/new) there.\r\n\r\n:books: If you want to **learn more** about how to use the library, check out the rest of the [**README**](#examples), have a look at [**code examples**](https://github.com/nlohmann/json/tree/develop/docs/examples), or browse through the [**help pages**](https://json.nlohmann.me).\r\n\r\n:construction: If you want to understand the **API** better, check out the [**API Reference**](https://json.nlohmann.me/api/basic_json/).\r\n\r\n:bug: If you found a **bug**, please check the [**FAQ**](https://json.nlohmann.me/home/faq/) if it is a known issue or the result of a design decision. Please also have a look at the [**issue list**](https://github.com/nlohmann/json/issues) before you [**create a new issue**](https://github.com/nlohmann/json/issues/new/choose). Please provide as much information as possible to help us understand and reproduce your issue.\r\n\r\nThere is also a [**docset**](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B) for the documentation browsers [Dash](https://kapeli.com/dash), [Velocity](https://velocity.silverlakesoftware.com), and [Zeal](https://zealdocs.org) that contains the full [documentation](https://json.nlohmann.me) as offline resource.\r\n\r\n## Examples\r\n\r\nHere are some examples to give you an idea how to use the class.\r\n\r\nBeside the examples below, you may want to:\r\n\r\n→ Check the [documentation](https://json.nlohmann.me/)\\\r\n→ Browse the [standalone example files](https://github.com/nlohmann/json/tree/develop/docs/examples)\r\n\r\nEvery API function (documented in the [API Documentation](https://json.nlohmann.me/api/basic_json/)) has a corresponding standalone example file. For example, the [`emplace()`](https://json.nlohmann.me/api/basic_json/emplace/) function has a matching [emplace.cpp](https://github.com/nlohmann/json/blob/develop/docs/examples/emplace.cpp) example file.\r\n\r\n### Read JSON from a file\r\n\r\nThe `json` class provides an API for manipulating a JSON value. To create a `json` object by reading a JSON file:\r\n\r\n```cpp\r\n#include <fstream>\r\n#include <nlohmann/json.hpp>\r\nusing json = nlohmann::json;\r\n\r\n// ...\r\n\r\nstd::ifstream f(\"example.json\");\r\njson data = json::parse(f);\r\n```\r\n\r\n### Creating `json` objects from JSON literals\r\n\r\nAssume you want to create hard-code this literal JSON value in a file, as a `json` object:\r\n\r\n```json\r\n{\r\n  \"pi\": 3.141,\r\n  \"happy\": true\r\n}\r\n```\r\n\r\nThere are various options:\r\n\r\n```cpp\r\n// Using (raw) string literals and json::parse\r\njson ex1 = json::parse(R\"(\r\n  {\r\n    \"pi\": 3.141,\r\n    \"happy\": true\r\n  }\r\n)\");\r\n\r\n// Using user-defined (raw) string literals\r\nusing namespace nlohmann::literals;\r\njson ex2 = R\"(\r\n  {\r\n    \"pi\": 3.141,\r\n    \"happy\": true\r\n  }\r\n)\"_json;\r\n\r\n// Using initializer lists\r\njson ex3 = {\r\n  {\"happy\", true},\r\n  {\"pi\", 3.141},\r\n};\r\n```\r\n\r\n### JSON as first-class data type\r\n\r\nHere are some examples to give you an idea how to use the class.\r\n\r\nAssume you want to create the JSON object\r\n\r\n```json\r\n{\r\n  \"pi\": 3.141,\r\n  \"happy\": true,\r\n  \"name\": \"Niels\",\r\n  \"nothing\": null,\r\n  \"answer\": {\r\n    \"everything\": 42\r\n  },\r\n  \"list\": [1, 0, 2],\r\n  \"object\": {\r\n    \"currency\": \"USD\",\r\n    \"value\": 42.99\r\n  }\r\n}\r\n```\r\n\r\nWith this library, you could write:\r\n\r\n```cpp\r\n// create an empty structure (null)\r\njson j;\r\n\r\n// add a number that is stored as double (note the implicit conversion of j to an object)\r\nj[\"pi\"] = 3.141;\r\n\r\n// add a Boolean that is stored as bool\r\nj[\"happy\"] = true;\r\n\r\n// add a string that is stored as std::string\r\nj[\"name\"] = \"Niels\";\r\n\r\n// add another null object by passing nullptr\r\nj[\"nothing\"] = nullptr;\r\n\r\n// add an object inside the object\r\nj[\"answer\"][\"everything\"] = 42;\r\n\r\n// add an array that is stored as std::vector (using an initializer list)\r\nj[\"list\"] = { 1, 0, 2 };\r\n\r\n// add another object (using an initializer list of pairs)\r\nj[\"object\"] = { {\"currency\", \"USD\"}, {\"value\", 42.99} };\r\n\r\n// instead, you could also write (which looks very similar to the JSON above)\r\njson j2 = {\r\n  {\"pi\", 3.141},\r\n  {\"happy\", true},\r\n  {\"name\", \"Niels\"},\r\n  {\"nothing\", nullptr},\r\n  {\"answer\", {\r\n    {\"everything\", 42}\r\n  }},\r\n  {\"list\", {1, 0, 2}},\r\n  {\"object\", {\r\n    {\"currency\", \"USD\"},\r\n    {\"value\", 42.99}\r\n  }}\r\n};\r\n```\r\n\r\nNote that in all these cases, you never need to \"tell\" the compiler which JSON value type you want to use. If you want to be explicit or express some edge cases, the functions [`json::array()`](https://json.nlohmann.me/api/basic_json/array/) and [`json::object()`](https://json.nlohmann.me/api/basic_json/object/) will help:\r\n\r\n```cpp\r\n// a way to express the empty array []\r\njson empty_array_explicit = json::array();\r\n\r\n// ways to express the empty object {}\r\njson empty_object_implicit = json({});\r\njson empty_object_explicit = json::object();\r\n\r\n// a way to express an _array_ of key/value pairs [[\"currency\", \"USD\"], [\"value\", 42.99]]\r\njson array_not_object = json::array({ {\"currency\", \"USD\"}, {\"value\", 42.99} });\r\n```\r\n\r\n### Serialization / Deserialization\r\n\r\n#### To/from strings\r\n\r\nYou can create a JSON value (deserialization) by appending `_json` to a string literal:\r\n\r\n```cpp\r\n// create object from string literal\r\njson j = \"{ \\\"happy\\\": true, \\\"pi\\\": 3.141 }\"_json;\r\n\r\n// or even nicer with a raw string literal\r\nauto j2 = R\"(\r\n  {\r\n    \"happy\": true,\r\n    \"pi\": 3.141\r\n  }\r\n)\"_json;\r\n```\r\n\r\nNote that without appending the `_json` suffix, the passed string literal is not parsed, but just used as JSON string\r\nvalue. That is, `json j = \"{ \\\"happy\\\": true, \\\"pi\\\": 3.141 }\"` would just store the string\r\n`\"{ \"happy\": true, \"pi\": 3.141 }\"` rather than parsing the actual object.\r\n\r\nThe string literal should be brought into scope with `using namespace nlohmann::literals;`\r\n(see [`json::parse()`](https://json.nlohmann.me/api/operator_literal_json/)).\r\n\r\nThe above example can also be expressed explicitly using [`json::parse()`](https://json.nlohmann.me/api/basic_json/parse/):\r\n\r\n```cpp\r\n// parse explicitly\r\nauto j3 = json::parse(R\"({\"happy\": true, \"pi\": 3.141})\");\r\n```\r\n\r\nYou can also get a string representation of a JSON value (serialize):\r\n\r\n```cpp\r\n// explicit conversion to string\r\nstd::string s = j.dump();    // {\"happy\":true,\"pi\":3.141}\r\n\r\n// serialization with pretty printing\r\n// pass in the amount of spaces to indent\r\nstd::cout << j.dump(4) << std::endl;\r\n// {\r\n//     \"happy\": true,\r\n//     \"pi\": 3.141\r\n// }\r\n```\r\n\r\nNote the difference between serialization and assignment:\r\n\r\n```cpp\r\n// store a string in a JSON value\r\njson j_string = \"this is a string\";\r\n\r\n// retrieve the string value\r\nauto cpp_string = j_string.get<std::string>();\r\n// retrieve the string value (alternative when a variable already exists)\r\nstd::string cpp_string2;\r\nj_string.get_to(cpp_string2);\r\n\r\n// retrieve the serialized value (explicit JSON serialization)\r\nstd::string serialized_string = j_string.dump();\r\n\r\n// output of original string\r\nstd::cout << cpp_string << \" == \" << cpp_string2 << \" == \" << j_string.get<std::string>() << '\\n';\r\n// output of serialized value\r\nstd::cout << j_string << \" == \" << serialized_string << std::endl;\r\n```\r\n\r\n[`.dump()`](https://json.nlohmann.me/api/basic_json/dump/) returns the originally stored string value.\r\n\r\nNote the library only supports UTF-8. When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers.\r\n\r\n#### To/from streams (e.g. files, string streams)\r\n\r\nYou can also use streams to serialize and deserialize:\r\n\r\n```cpp\r\n// deserialize from standard input\r\njson j;\r\nstd::cin >> j;\r\n\r\n// serialize to standard output\r\nstd::cout << j;\r\n\r\n// the setw manipulator was overloaded to set the indentation for pretty printing\r\nstd::cout << std::setw(4) << j << std::endl;\r\n```\r\n\r\nThese operators work for any subclasses of `std::istream` or `std::ostream`. Here is the same example with files:\r\n\r\n```cpp\r\n// read a JSON file\r\nstd::ifstream i(\"file.json\");\r\njson j;\r\ni >> j;\r\n\r\n// write prettified JSON to another file\r\nstd::ofstream o(\"pretty.json\");\r\no << std::setw(4) << j << std::endl;\r\n```\r\n\r\nPlease note that setting the exception bit for `failbit` is inappropriate for this use case. It will result in program termination due to the `noexcept` specifier in use.\r\n\r\n#### Read from iterator range\r\n\r\nYou can also parse JSON from an iterator range; that is, from any container accessible by iterators whose `value_type` is an integral type of 1, 2 or 4 bytes, which will be interpreted as UTF-8, UTF-16 and UTF-32 respectively. For instance, a `std::vector<std::uint8_t>`, or a `std::list<std::uint16_t>`:\r\n\r\n```cpp\r\nstd::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};\r\njson j = json::parse(v.begin(), v.end());\r\n```\r\n\r\nYou may leave the iterators for the range [begin, end):\r\n\r\n```cpp\r\nstd::vector<std::uint8_t> v = {'t', 'r', 'u', 'e'};\r\njson j = json::parse(v);\r\n```\r\n\r\n#### Custom data source\r\n\r\nSince the parse function accepts arbitrary iterator ranges, you can provide your own data sources by implementing the `LegacyInputIterator` concept.\r\n\r\n```cpp\r\nstruct MyContainer {\r\n  void advance();\r\n  const char& get_current();\r\n};\r\n\r\nstruct MyIterator {\r\n    using difference_type = std::ptrdiff_t;\r\n    using value_type = char;\r\n    using pointer = const char*;\r\n    using reference = const char&;\r\n    using iterator_category = std::input_iterator_tag;\r\n\r\n    MyIterator& operator++() {\r\n        MyContainer.advance();\r\n        return *this;\r\n    }\r\n\r\n    bool operator!=(const MyIterator& rhs) const {\r\n        return rhs.target != target;\r\n    }\r\n\r\n    reference operator*() const {\r\n        return target.get_current();\r\n    }\r\n\r\n    MyContainer* target = nullptr;\r\n};\r\n\r\nMyIterator begin(MyContainer& tgt) {\r\n    return MyIterator{&tgt};\r\n}\r\n\r\nMyIterator end(const MyContainer&) {\r\n    return {};\r\n}\r\n\r\nvoid foo() {\r\n    MyContainer c;\r\n    json j = json::parse(c);\r\n}\r\n```\r\n\r\n#### SAX interface\r\n\r\nThe library uses a SAX-like interface with the following functions:\r\n\r\n```cpp\r\n// called when null is parsed\r\nbool null();\r\n\r\n// called when a boolean is parsed; value is passed\r\nbool boolean(bool val);\r\n\r\n// called when a signed or unsigned integer number is parsed; value is passed\r\nbool number_integer(number_integer_t val);\r\nbool number_unsigned(number_unsigned_t val);\r\n\r\n// called when a floating-point number is parsed; value and original string is passed\r\nbool number_float(number_float_t val, const string_t& s);\r\n\r\n// called when a string is parsed; value is passed and can be safely moved away\r\nbool string(string_t& val);\r\n// called when a binary value is parsed; value is passed and can be safely moved away\r\nbool binary(binary_t& val);\r\n\r\n// called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)\r\nbool start_object(std::size_t elements);\r\nbool end_object();\r\nbool start_array(std::size_t elements);\r\nbool end_array();\r\n// called when an object key is parsed; value is passed and can be safely moved away\r\nbool key(string_t& val);\r\n\r\n// called when a parse error occurs; byte position, the last token, and an exception is passed\r\nbool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);\r\n```\r\n\r\nThe return value of each function determines whether parsing should proceed.\r\n\r\nTo implement your own SAX handler, proceed as follows:\r\n\r\n1. Implement the SAX interface in a class. You can use class `nlohmann::json_sax<json>` as base class, but you can also use any class where the functions described above are implemented and public.\r\n2. Create an object of your SAX interface class, e.g. `my_sax`.\r\n3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.\r\n\r\nNote the `sax_parse` function only returns a `bool` indicating the result of the last executed SAX event. It does not return a  `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).\r\n\r\n### STL-like access\r\n\r\nWe designed the JSON class to behave just like an STL container. In fact, it satisfies the [**ReversibleContainer**](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) requirement.\r\n\r\n```cpp\r\n// create an array using push_back\r\njson j;\r\nj.push_back(\"foo\");\r\nj.push_back(1);\r\nj.push_back(true);\r\n\r\n// also use emplace_back\r\nj.emplace_back(1.78);\r\n\r\n// iterate the array\r\nfor (json::iterator it = j.begin(); it != j.end(); ++it) {\r\n  std::cout << *it << '\\n';\r\n}\r\n\r\n// range-based for\r\nfor (auto& element : j) {\r\n  std::cout << element << '\\n';\r\n}\r\n\r\n// getter/setter\r\nconst auto tmp = j[0].get<std::string>();\r\nj[1] = 42;\r\nbool foo = j.at(2);\r\n\r\n// comparison\r\nj == R\"([\"foo\", 1, true, 1.78])\"_json;  // true\r\n\r\n// other stuff\r\nj.size();     // 4 entries\r\nj.empty();    // false\r\nj.type();     // json::value_t::array\r\nj.clear();    // the array is empty again\r\n\r\n// convenience type checkers\r\nj.is_null();\r\nj.is_boolean();\r\nj.is_number();\r\nj.is_object();\r\nj.is_array();\r\nj.is_string();\r\n\r\n// create an object\r\njson o;\r\no[\"foo\"] = 23;\r\no[\"bar\"] = false;\r\no[\"baz\"] = 3.141;\r\n\r\n// also use emplace\r\no.emplace(\"weather\", \"sunny\");\r\n\r\n// special iterator member functions for objects\r\nfor (json::iterator it = o.begin(); it != o.end(); ++it) {\r\n  std::cout << it.key() << \" : \" << it.value() << \"\\n\";\r\n}\r\n\r\n// the same code as range for\r\nfor (auto& el : o.items()) {\r\n  std::cout << el.key() << \" : \" << el.value() << \"\\n\";\r\n}\r\n\r\n// even easier with structured bindings (C++17)\r\nfor (auto& [key, value] : o.items()) {\r\n  std::cout << key << \" : \" << value << \"\\n\";\r\n}\r\n\r\n// find an entry\r\nif (o.contains(\"foo\")) {\r\n  // there is an entry with key \"foo\"\r\n}\r\n\r\n// or via find and an iterator\r\nif (o.find(\"foo\") != o.end()) {\r\n  // there is an entry with key \"foo\"\r\n}\r\n\r\n// or simpler using count()\r\nint foo_present = o.count(\"foo\"); // 1\r\nint fob_present = o.count(\"fob\"); // 0\r\n\r\n// delete an entry\r\no.erase(\"foo\");\r\n```\r\n\r\n\r\n### Conversion from STL containers\r\n\r\nAny sequence container (`std::array`, `std::vector`, `std::deque`, `std::forward_list`, `std::list`) whose values can be used to construct JSON values (e.g., integers, floating point numbers, Booleans, string types, or again STL containers described in this section) can be used to create a JSON array. The same holds for similar associative containers (`std::set`, `std::multiset`, `std::unordered_set`, `std::unordered_multiset`), but in these cases the order of the elements of the array depends on how the elements are ordered in the respective STL container.\r\n\r\n```cpp\r\nstd::vector<int> c_vector {1, 2, 3, 4};\r\njson j_vec(c_vector);\r\n// [1, 2, 3, 4]\r\n\r\nstd::deque<double> c_deque {1.2, 2.3, 3.4, 5.6};\r\njson j_deque(c_deque);\r\n// [1.2, 2.3, 3.4, 5.6]\r\n\r\nstd::list<bool> c_list {true, true, false, true};\r\njson j_list(c_list);\r\n// [true, true, false, true]\r\n\r\nstd::forward_list<int64_t> c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543};\r\njson j_flist(c_flist);\r\n// [12345678909876, 23456789098765, 34567890987654, 45678909876543]\r\n\r\nstd::array<unsigned long, 4> c_array {{1, 2, 3, 4}};\r\njson j_array(c_array);\r\n// [1, 2, 3, 4]\r\n\r\nstd::set<std::string> c_set {\"one\", \"two\", \"three\", \"four\", \"one\"};\r\njson j_set(c_set); // only one entry for \"one\" is used\r\n// [\"four\", \"one\", \"three\", \"two\"]\r\n\r\nstd::unordered_set<std::string> c_uset {\"one\", \"two\", \"three\", \"four\", \"one\"};\r\njson j_uset(c_uset); // only one entry for \"one\" is used\r\n// maybe [\"two\", \"three\", \"four\", \"one\"]\r\n\r\nstd::multiset<std::string> c_mset {\"one\", \"two\", \"one\", \"four\"};\r\njson j_mset(c_mset); // both entries for \"one\" are used\r\n// maybe [\"one\", \"two\", \"one\", \"four\"]\r\n\r\nstd::unordered_multiset<std::string> c_umset {\"one\", \"two\", \"one\", \"four\"};\r\njson j_umset(c_umset); // both entries for \"one\" are used\r\n// maybe [\"one\", \"two\", \"one\", \"four\"]\r\n```\r\n\r\nLikewise, any associative key-value containers (`std::map`, `std::multimap`, `std::unordered_map`, `std::unordered_multimap`) whose keys can construct an `std::string` and whose values can be used to construct JSON values (see examples above) can be used to create a JSON object. Note that in case of multimaps only one key is used in the JSON object and the value depends on the internal order of the STL container.\r\n\r\n```cpp\r\nstd::map<std::string, int> c_map { {\"one\", 1}, {\"two\", 2}, {\"three\", 3} };\r\njson j_map(c_map);\r\n// {\"one\": 1, \"three\": 3, \"two\": 2 }\r\n\r\nstd::unordered_map<const char*, double> c_umap { {\"one\", 1.2}, {\"two\", 2.3}, {\"three\", 3.4} };\r\njson j_umap(c_umap);\r\n// {\"one\": 1.2, \"two\": 2.3, \"three\": 3.4}\r\n\r\nstd::multimap<std::string, bool> c_mmap { {\"one\", true}, {\"two\", true}, {\"three\", false}, {\"three\", true} };\r\njson j_mmap(c_mmap); // only one entry for key \"three\" is used\r\n// maybe {\"one\": true, \"two\": true, \"three\": true}\r\n\r\nstd::unordered_multimap<std::string, bool> c_ummap { {\"one\", true}, {\"two\", true}, {\"three\", false}, {\"three\", true} };\r\njson j_ummap(c_ummap); // only one entry for key \"three\" is used\r\n// maybe {\"one\": true, \"two\": true, \"three\": true}\r\n```\r\n\r\n### JSON Pointer and JSON Patch\r\n\r\nThe library supports **JSON Pointer** ([RFC 6901](https://tools.ietf.org/html/rfc6901)) as alternative means to address structured values. On top of this, **JSON Patch** ([RFC 6902](https://tools.ietf.org/html/rfc6902)) allows describing differences between two JSON values - effectively allowing patch and diff operations known from Unix.\r\n\r\n```cpp\r\n// a JSON value\r\njson j_original = R\"({\r\n  \"baz\": [\"one\", \"two\", \"three\"],\r\n  \"foo\": \"bar\"\r\n})\"_json;\r\n\r\n// access members with a JSON pointer (RFC 6901)\r\nj_original[\"/baz/1\"_json_pointer];\r\n// \"two\"\r\n\r\n// a JSON patch (RFC 6902)\r\njson j_patch = R\"([\r\n  { \"op\": \"replace\", \"path\": \"/baz\", \"value\": \"boo\" },\r\n  { \"op\": \"add\", \"path\": \"/hello\", \"value\": [\"world\"] },\r\n  { \"op\": \"remove\", \"path\": \"/foo\"}\r\n])\"_json;\r\n\r\n// apply the patch\r\njson j_result = j_original.patch(j_patch);\r\n// {\r\n//    \"baz\": \"boo\",\r\n//    \"hello\": [\"world\"]\r\n// }\r\n\r\n// calculate a JSON patch from two JSON values\r\njson::diff(j_result, j_original);\r\n// [\r\n//   { \"op\":\" replace\", \"path\": \"/baz\", \"value\": [\"one\", \"two\", \"three\"] },\r\n//   { \"op\": \"remove\",\"path\": \"/hello\" },\r\n//   { \"op\": \"add\", \"path\": \"/foo\", \"value\": \"bar\" }\r\n// ]\r\n```\r\n\r\n### JSON Merge Patch\r\n\r\nThe library supports **JSON Merge Patch** ([RFC 7386](https://tools.ietf.org/html/rfc7386)) as a patch format. Instead of using JSON Pointer (see above) to specify values to be manipulated, it describes the changes using a syntax that closely mimics the document being modified.\r\n\r\n```cpp\r\n// a JSON value\r\njson j_document = R\"({\r\n  \"a\": \"b\",\r\n  \"c\": {\r\n    \"d\": \"e\",\r\n    \"f\": \"g\"\r\n  }\r\n})\"_json;\r\n\r\n// a patch\r\njson j_patch = R\"({\r\n  \"a\":\"z\",\r\n  \"c\": {\r\n    \"f\": null\r\n  }\r\n})\"_json;\r\n\r\n// apply the patch\r\nj_document.merge_patch(j_patch);\r\n// {\r\n//  \"a\": \"z\",\r\n//  \"c\": {\r\n//    \"d\": \"e\"\r\n//  }\r\n// }\r\n```\r\n\r\n### Implicit conversions\r\n\r\nSupported types can be implicitly converted to JSON values.\r\n\r\nIt is recommended to **NOT USE** implicit conversions **FROM** a JSON value.\r\nYou can find more details about this recommendation [here](https://www.github.com/nlohmann/json/issues/958).\r\nYou can switch off implicit conversions by defining `JSON_USE_IMPLICIT_CONVERSIONS` to `0` before including the `json.hpp` header. When using CMake, you can also achieve this by setting the option `JSON_ImplicitConversions` to `OFF`.\r\n\r\n```cpp\r\n// strings\r\nstd::string s1 = \"Hello, world!\";\r\njson js = s1;\r\nauto s2 = js.get<std::string>();\r\n// NOT RECOMMENDED\r\nstd::string s3 = js;\r\nstd::string s4;\r\ns4 = js;\r\n\r\n// Booleans\r\nbool b1 = true;\r\njson jb = b1;\r\nauto b2 = jb.get<bool>();\r\n// NOT RECOMMENDED\r\nbool b3 = jb;\r\nbool b4;\r\nb4 = jb;\r\n\r\n// numbers\r\nint i = 42;\r\njson jn = i;\r\nauto f = jn.get<double>();\r\n// NOT RECOMMENDED\r\ndouble f2 = jb;\r\ndouble f3;\r\nf3 = jb;\r\n\r\n// etc.\r\n```\r\n\r\nNote that `char` types are not automatically converted to JSON strings, but to integer numbers. A conversion to a string must be specified explicitly:\r\n\r\n```cpp\r\nchar ch = 'A';                       // ASCII value 65\r\njson j_default = ch;                 // stores integer number 65\r\njson j_string = std::string(1, ch);  // stores string \"A\"\r\n```\r\n\r\n### Arbitrary types conversions\r\n\r\nEvery type can be serialized in JSON, not just STL containers and scalar types. Usually, you would do something along those lines:\r\n\r\n```cpp\r\nnamespace ns {\r\n    // a simple struct to model a person\r\n    struct person {\r\n        std::string name;\r\n        std::string address;\r\n        int age;\r\n    };\r\n}\r\n\r\nns::person p = {\"Ned Flanders\", \"744 Evergreen Terrace\", 60};\r\n\r\n// convert to JSON: copy each value into the JSON object\r\njson j;\r\nj[\"name\"] = p.name;\r\nj[\"address\"] = p.address;\r\nj[\"age\"] = p.age;\r\n\r\n// ...\r\n\r\n// convert from JSON: copy each value from the JSON object\r\nns::person p {\r\n    j[\"name\"].get<std::string>(),\r\n    j[\"address\"].get<std::string>(),\r\n    j[\"age\"].get<int>()\r\n};\r\n```\r\n\r\nIt works, but that's quite a lot of boilerplate... Fortunately, there's a better way:\r\n\r\n```cpp\r\n// create a person\r\nns::person p {\"Ned Flanders\", \"744 Evergreen Terrace\", 60};\r\n\r\n// conversion: person -> json\r\njson j = p;\r\n\r\nstd::cout << j << std::endl;\r\n// {\"address\":\"744 Evergreen Terrace\",\"age\":60,\"name\":\"Ned Flanders\"}\r\n\r\n// conversion: json -> person\r\nauto p2 = j.get<ns::person>();\r\n\r\n// that's it\r\nassert(p == p2);\r\n```\r\n\r\n#### Basic usage\r\n\r\nTo make this work with one of your types, you only need to provide two functions:\r\n\r\n```cpp\r\nusing json = nlohmann::json;\r\n\r\nnamespace ns {\r\n    void to_json(json& j, const person& p) {\r\n        j = json{{\"name\", p.name}, {\"address\", p.address}, {\"age\", p.age}};\r\n    }\r\n\r\n    void from_json(const json& j, person& p) {\r\n        j.at(\"name\").get_to(p.name);\r\n        j.at(\"address\").get_to(p.address);\r\n        j.at(\"age\").get_to(p.age);\r\n    }\r\n} // namespace ns\r\n```\r\n\r\nThat's all! When calling the `json` constructor with your type, your custom `to_json` method will be automatically called.\r\nLikewise, when calling `get<your_type>()` or `get_to(your_type&)`, the `from_json` method will be called.\r\n\r\nSome important things:\r\n\r\n* Those methods **MUST** be in your type's namespace (which can be the global namespace), or the library will not be able to locate them (in this example, they are in namespace `ns`, where `person` is defined).\r\n* Those methods **MUST** be available (e.g., proper headers must be included) everywhere you use these conversions. Look at [issue 1108](https://github.com/nlohmann/json/issues/1108) for errors that may occur otherwise.\r\n* When using `get<your_type>()`, `your_type` **MUST** be [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). (There is a way to bypass this requirement described later.)\r\n* In function `from_json`, use function [`at()`](https://json.nlohmann.me/api/basic_json/at/) to access the object values rather than `operator[]`. In case a key does not exist, `at` throws an exception that you can handle, whereas `operator[]` exhibits undefined behavior.\r\n* You do not need to add serializers or deserializers for STL types like `std::vector`: the library already implements these.\r\n\r\n#### Simplify your life with macros\r\n\r\nIf you just want to serialize/deserialize some structs, the `to_json`/`from_json` functions can be a lot of boilerplate.\r\n\r\nThere are two macros to make your life easier as long as you (1) want to use a JSON object as serialization and (2) want to use the member variable names as object keys in that object:\r\n\r\n- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...)` is to be defined inside the namespace of the class/struct to create code for.\r\n- `NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...)` is to be defined inside the class/struct to create code for. This macro can also access private members.\r\n\r\nIn both macros, the first parameter is the name of the class/struct, and all remaining parameters name the members.\r\n\r\n##### Examples\r\n\r\nThe `to_json`/`from_json` functions for the `person` struct above can be created with:\r\n\r\n```cpp\r\nnamespace ns {\r\n    NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, name, address, age)\r\n}\r\n```\r\n\r\nHere is an example with private members, where `NLOHMANN_DEFINE_TYPE_INTRUSIVE` is needed:\r\n\r\n```cpp\r\nnamespace ns {\r\n    class address {\r\n      private:\r\n        std::string street;\r\n        int housenumber;\r\n        int postcode;\r\n\r\n      public:\r\n        NLOHMANN_DEFINE_TYPE_INTRUSIVE(address, street, housenumber, postcode)\r\n    };\r\n}\r\n```\r\n\r\n#### How do I convert third-party types?\r\n\r\nThis requires a bit more advanced technique. But first, let's see how this conversion mechanism works:\r\n\r\nThe library uses **JSON Serializers** to convert types to json.\r\nThe default serializer for `nlohmann::json` is `nlohmann::adl_serializer` (ADL means [Argument-Dependent Lookup](https://en.cppreference.com/w/cpp/language/adl)).\r\n\r\nIt is implemented like this (simplified):\r\n\r\n```cpp\r\ntemplate <typename T>\r\nstruct adl_serializer {\r\n    static void to_json(json& j, const T& value) {\r\n        // calls the \"to_json\" method in T's namespace\r\n    }\r\n\r\n    static void from_json(const json& j, T& value) {\r\n        // same thing, but with the \"from_json\" method\r\n    }\r\n};\r\n```\r\n\r\nThis serializer works fine when you have control over the type's namespace. However, what about `boost::optional` or `std::filesystem::path` (C++17)? Hijacking the `boost` namespace is pretty bad, and it's illegal to add something other than template specializations to `std`...\r\n\r\nTo solve this, you need to add a specialization of `adl_serializer` to the `nlohmann` namespace, here's an example:\r\n\r\n```cpp\r\n// partial specialization (full specialization works too)\r\nnamespace nlohmann {\r\n    template <typename T>\r\n    struct adl_serializer<boost::optional<T>> {\r\n        static void to_json(json& j, const boost::optional<T>& opt) {\r\n            if (opt == boost::none) {\r\n                j = nullptr;\r\n            } else {\r\n              j = *opt; // this will call adl_serializer<T>::to_json which will\r\n                        // find the free function to_json in T's namespace!\r\n            }\r\n        }\r\n\r\n        static void from_json(const json& j, boost::optional<T>& opt) {\r\n            if (j.is_null()) {\r\n                opt = boost::none;\r\n            } else {\r\n                opt = j.get<T>(); // same as above, but with\r\n                                  // adl_serializer<T>::from_json\r\n            }\r\n        }\r\n    };\r\n}\r\n```\r\n\r\n#### How can I use `get()` for non-default constructible/non-copyable types?\r\n\r\nThere is a way, if your type is [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible). You will need to specialize the `adl_serializer` as well, but with a special `from_json` overload:\r\n\r\n```cpp\r\nstruct move_only_type {\r\n    move_only_type() = delete;\r\n    move_only_type(int ii): i(ii) {}\r\n    move_only_type(const move_only_type&) = delete;\r\n    move_only_type(move_only_type&&) = default;\r\n\r\n    int i;\r\n};\r\n\r\nnamespace nlohmann {\r\n    template <>\r\n    struct adl_serializer<move_only_type> {\r\n        // note: the return type is no longer 'void', and the method only takes\r\n        // one argument\r\n        static move_only_type from_json(const json& j) {\r\n            return {j.get<int>()};\r\n        }\r\n\r\n        // Here's the catch! You must provide a to_json method! Otherwise, you\r\n        // will not be able to convert move_only_type to json, since you fully\r\n        // specialized adl_serializer on that type\r\n        static void to_json(json& j, move_only_type t) {\r\n            j = t.i;\r\n        }\r\n    };\r\n}\r\n```\r\n\r\n#### Can I write my own serializer? (Advanced use)\r\n\r\nYes. You might want to take a look at [`unit-udt.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-udt.cpp) in the test suite, to see a few examples.\r\n\r\nIf you write your own serializer, you'll need to do a few things:\r\n\r\n- use a different `basic_json` alias than `nlohmann::json` (the last template parameter of `basic_json` is the `JSONSerializer`)\r\n- use your `basic_json` alias (or a template parameter) in all your `to_json`/`from_json` methods\r\n- use `nlohmann::to_json` and `nlohmann::from_json` when you need ADL\r\n\r\nHere is an example, without simplifications, that only accepts types with a size <= 32, and uses ADL.\r\n\r\n```cpp\r\n// You should use void as a second template argument\r\n// if you don't need compile-time checks on T\r\ntemplate<typename T, typename SFINAE = typename std::enable_if<sizeof(T) <= 32>::type>\r\nstruct less_than_32_serializer {\r\n    template <typename BasicJsonType>\r\n    static void to_json(BasicJsonType& j, T value) {\r\n        // we want to use ADL, and call the correct to_json overload\r\n        using nlohmann::to_json; // this method is called by adl_serializer,\r\n                                 // this is where the magic happens\r\n        to_json(j, value);\r\n    }\r\n\r\n    template <typename BasicJsonType>\r\n    static void from_json(const BasicJsonType& j, T& value) {\r\n        // same thing here\r\n        using nlohmann::from_json;\r\n        from_json(j, value);\r\n    }\r\n};\r\n```\r\n\r\nBe **very** careful when reimplementing your serializer, you can stack overflow if you don't pay attention:\r\n\r\n```cpp\r\ntemplate <typename T, void>\r\nstruct bad_serializer\r\n{\r\n    template <typename BasicJsonType>\r\n    static void to_json(BasicJsonType& j, const T& value) {\r\n      // this calls BasicJsonType::json_serializer<T>::to_json(j, value);\r\n      // if BasicJsonType::json_serializer == bad_serializer ... oops!\r\n      j = value;\r\n    }\r\n\r\n    template <typename BasicJsonType>\r\n    static void to_json(const BasicJsonType& j, T& value) {\r\n      // this calls BasicJsonType::json_serializer<T>::from_json(j, value);\r\n      // if BasicJsonType::json_serializer == bad_serializer ... oops!\r\n      value = j.template get<T>(); // oops!\r\n    }\r\n};\r\n```\r\n\r\n### Specializing enum conversion\r\n\r\nBy default, enum values are serialized to JSON as integers. In some cases this could result in undesired behavior. If an enum is modified or re-ordered after data has been serialized to JSON, the later de-serialized JSON data may be undefined or a different enum value than was originally intended.\r\n\r\nIt is possible to more precisely specify how a given enum is mapped to and from JSON as shown below:\r\n\r\n```cpp\r\n// example enum type declaration\r\nenum TaskState {\r\n    TS_STOPPED,\r\n    TS_RUNNING,\r\n    TS_COMPLETED,\r\n    TS_INVALID=-1,\r\n};\r\n\r\n// map TaskState values to JSON as strings\r\nNLOHMANN_JSON_SERIALIZE_ENUM( TaskState, {\r\n    {TS_INVALID, nullptr},\r\n    {TS_STOPPED, \"stopped\"},\r\n    {TS_RUNNING, \"running\"},\r\n    {TS_COMPLETED, \"completed\"},\r\n})\r\n```\r\n\r\nThe `NLOHMANN_JSON_SERIALIZE_ENUM()` macro declares a set of `to_json()` / `from_json()` functions for type `TaskState` while avoiding repetition and boilerplate serialization code.\r\n\r\n**Usage:**\r\n\r\n```cpp\r\n// enum to JSON as string\r\njson j = TS_STOPPED;\r\nassert(j == \"stopped\");\r\n\r\n// json string to enum\r\njson j3 = \"running\";\r\nassert(j3.get<TaskState>() == TS_RUNNING);\r\n\r\n// undefined json value to enum (where the first map entry above is the default)\r\njson jPi = 3.14;\r\nassert(jPi.get<TaskState>() == TS_INVALID );\r\n```\r\n\r\nJust as in [Arbitrary Type Conversions](#arbitrary-types-conversions) above,\r\n- `NLOHMANN_JSON_SERIALIZE_ENUM()` MUST be declared in your enum type's namespace (which can be the global namespace), or the library will not be able to locate it, and it will default to integer serialization.\r\n- It MUST be available (e.g., proper headers must be included) everywhere you use the conversions.\r\n\r\nOther Important points:\r\n- When using `get<ENUM_TYPE>()`, undefined JSON values will default to the first pair specified in your map. Select this default pair carefully.\r\n- If an enum or JSON value is specified more than once in your map, the first matching occurrence from the top of the map will be returned when converting to or from JSON.\r\n\r\n### Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)\r\n\r\nThough JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [BSON](https://bsonspec.org) (Binary JSON), [CBOR](https://cbor.io) (Concise Binary Object Representation), [MessagePack](https://msgpack.org), [UBJSON](https://ubjson.org) (Universal Binary JSON Specification) and [BJData](https://neurojson.org/bjdata) (Binary JData) to efficiently encode JSON values to byte vectors and to decode such vectors.\r\n\r\n```cpp\r\n// create a JSON value\r\njson j = R\"({\"compact\": true, \"schema\": 0})\"_json;\r\n\r\n// serialize to BSON\r\nstd::vector<std::uint8_t> v_bson = json::to_bson(j);\r\n\r\n// 0x1B, 0x00, 0x00, 0x00, 0x08, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x00, 0x01, 0x10, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00\r\n\r\n// roundtrip\r\njson j_from_bson = json::from_bson(v_bson);\r\n\r\n// serialize to CBOR\r\nstd::vector<std::uint8_t> v_cbor = json::to_cbor(j);\r\n\r\n// 0xA2, 0x67, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xF5, 0x66, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00\r\n\r\n// roundtrip\r\njson j_from_cbor = json::from_cbor(v_cbor);\r\n\r\n// serialize to MessagePack\r\nstd::vector<std::uint8_t> v_msgpack = json::to_msgpack(j);\r\n\r\n// 0x82, 0xA7, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xC3, 0xA6, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x00\r\n\r\n// roundtrip\r\njson j_from_msgpack = json::from_msgpack(v_msgpack);\r\n\r\n// serialize to UBJSON\r\nstd::vector<std::uint8_t> v_ubjson = json::to_ubjson(j);\r\n\r\n// 0x7B, 0x69, 0x07, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x54, 0x69, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x69, 0x00, 0x7D\r\n\r\n// roundtrip\r\njson j_from_ubjson = json::from_ubjson(v_ubjson);\r\n```\r\n\r\nThe library also supports binary types from BSON, CBOR (byte strings), and MessagePack (bin, ext, fixext). They are stored by default as `std::vector<std::uint8_t>` to be processed outside the library.\r\n\r\n```cpp\r\n// CBOR byte string with payload 0xCAFE\r\nstd::vector<std::uint8_t> v = {0x42, 0xCA, 0xFE};\r\n\r\n// read value\r\njson j = json::from_cbor(v);\r\n\r\n// the JSON value has type binary\r\nj.is_binary(); // true\r\n\r\n// get reference to stored binary value\r\nauto& binary = j.get_binary();\r\n\r\n// the binary value has no subtype (CBOR has no binary subtypes)\r\nbinary.has_subtype(); // false\r\n\r\n// access std::vector<std::uint8_t> member functions\r\nbinary.size(); // 2\r\nbinary[0]; // 0xCA\r\nbinary[1]; // 0xFE\r\n\r\n// set subtype to 0x10\r\nbinary.set_subtype(0x10);\r\n\r\n// serialize to MessagePack\r\nauto cbor = json::to_msgpack(j); // 0xD5 (fixext2), 0x10, 0xCA, 0xFE\r\n```\r\n\r\n\r\n## Supported compilers\r\n\r\nThough it's 2022 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:\r\n\r\n- GCC 4.8 - 12.0 (and possibly later)\r\n- Clang 3.4 - 15.0 (and possibly later)\r\n- Apple Clang 9.1 - 13.1 (and possibly later)\r\n- Intel C++ Compiler 17.0.2 (and possibly later)\r\n- Nvidia CUDA Compiler 11.0.221 (and possibly later)\r\n- Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0 (and possibly later)\r\n- Microsoft Visual C++ 2017 / Build Tools 15.5.180.51428 (and possibly later)\r\n- Microsoft Visual C++ 2019 / Build Tools 16.3.1+1def00d3d (and possibly later)\r\n- Microsoft Visual C++ 2022 / Build Tools 19.30.30709.0 (and possibly later)\r\n\r\nI would be happy to learn about other compilers/versions.\r\n\r\nPlease note:\r\n\r\n- GCC 4.8 has a bug [57824](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824)): multiline raw strings cannot be the arguments to macros. Don't use multiline raw strings directly in macros with this compiler.\r\n- Android defaults to using very old compilers and C++ libraries. To fix this, add the following to your `Application.mk`. This will switch to the LLVM C++ library, the Clang compiler, and enable C++11 and other features disabled by default.\r\n\r\n    ```\r\n    APP_STL := c++_shared\r\n    NDK_TOOLCHAIN_VERSION := clang3.6\r\n    APP_CPPFLAGS += -frtti -fexceptions\r\n    ```\r\n\r\n    The code compiles successfully with [Android NDK](https://developer.android.com/ndk/index.html?hl=ml), Revision 9 - 11 (and possibly later) and [CrystaX's Android NDK](https://www.crystax.net/en/android/ndk) version 10.\r\n\r\n- For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod` or `strtof`) may occur. Note this is not an issue with the code,  but rather with the compiler itself. On Android, see above to build with a newer environment.  For MinGW, please refer to [this site](https://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. For Android NDK using `APP_STL := gnustl_static`, please refer to [this discussion](https://github.com/nlohmann/json/issues/219).\r\n\r\n- Unsupported versions of GCC and Clang are rejected by `#error` directives. This can be switched off by defining `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK`. Note that you can expect no support in this case.\r\n\r\nThe following compilers are currently used in continuous integration at [AppVeyor](https://ci.appveyor.com/project/nlohmann/json), [Drone CI](https://cloud.drone.io/nlohmann/json), and [GitHub Actions](https://github.com/nlohmann/json/actions):\r\n\r\n| Compiler                                                                                               | Operating System   | CI Provider    |\r\n|--------------------------------------------------------------------------------------------------------|--------------------|----------------|\r\n| Apple Clang 11.0.3 (clang-1103.0.32.62);  Xcode 11.7                                                   | macOS 11.7.1       | GitHub Actions |\r\n| Apple Clang 12.0.0 (clang-1200.0.32.29);  Xcode 12.4                                                   | macOS 11.7.1       | GitHub Actions |\r\n| Apple Clang 12.0.5 (clang-1205.0.22.11);  Xcode 12.5.1                                                 | macOS 11.7.1       | GitHub Actions |\r\n| Apple Clang 13.0.0 (clang-1300.0.29.3);   Xcode 13.0                                                   | macOS 11.7.1       | GitHub Actions |\r\n| Apple Clang 13.0.0 (clang-1300.0.29.3);   Xcode 13.1                                                   | macOS 12.6.1       | GitHub Actions |\r\n| Apple Clang 13.0.0 (clang-1300.0.29.30);  Xcode 13.2.1                                                 | macOS 12.6.1       | GitHub Actions |\r\n| Apple Clang 13.1.6 (clang-1316.0.21.2.3); Xcode 13.3.1                                                 | macOS 12.6.1       | GitHub Actions |\r\n| Apple Clang 13.1.6 (clang-1316.0.21.2.5); Xcode 13.4.1                                                 | macOS 12.6.1       | GitHub Actions |\r\n| Apple Clang 14.0.0 (clang-1400.0.29.102); Xcode 14.0                                                   | macOS 12.6.1       | GitHub Actions |\r\n| Apple Clang 14.0.0 (clang-1400.0.29.102); Xcode 14.0.1                                                 | macOS 12.6.1       | GitHub Actions |\r\n| Apple Clang 14.0.0 (clang-1400.0.29.202); Xcode 14.1                                                   | macOS 12.6.1       | GitHub Actions |\r\n| Clang 3.5.2                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 3.6.2                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 3.7.1                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 3.8.1                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 3.9.1                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 4.0.1                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 5.0.2                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 6.0.1                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 7.0.1                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 8.0.0                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 9.0.0                                                                                            | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 10.0.0                                                                                           | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 10.0.0 with GNU-like command-line                                                                | Windows-10.0.17763 | GitHub Actions |\r\n| Clang 11.0.0 with GNU-like command-line                                                                | Windows-10.0.17763 | GitHub Actions |\r\n| Clang 11.0.0 with MSVC-like command-line                                                               | Windows-10.0.17763 | GitHub Actions |\r\n| Clang 11.0.0                                                                                           | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 12.0.0                                                                                           | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 12.0.0 with GNU-like command-line                                                                | Windows-10.0.17763 | GitHub Actions |\r\n| Clang 13.0.0                                                                                           | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 13.0.0 with GNU-like command-line                                                                | Windows-10.0.17763 | GitHub Actions |\r\n| Clang 14.0.0                                                                                           | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 14.0.0 with GNU-like command-line                                                                | Windows-10.0.17763 | GitHub Actions |\r\n| Clang 15.0.0 with GNU-like command-line                                                                | Windows-10.0.17763 | GitHub Actions |\r\n| Clang 15.0.4                                                                                           | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Clang 16.0.0 (16.0.0-++20221031071727+500876226c60-1~exp1~20221031071831.439)                          | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 4.8.5 (Ubuntu 4.8.5-4ubuntu2)                                                                      | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 4.9.4                                                                                              | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 5.5.0                                                                                              | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 6.5.0                                                                                              | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 7.5.0                                                                                              | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 8.1.0 (i686-posix-dwarf-rev0, Built by MinGW-W64 project)                                          | Windows-10.0.17763 | GitHub Actions |\r\n| GCC 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)                                          | Windows-10.0.17763 | GitHub Actions |\r\n| GCC 8.5.0                                                                                              | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 9.5.0                                                                                              | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 10.4.0                                                                                             | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 11.1.0                                                                                             | Ubuntu (aarch64)   | Drone CI       |\r\n| GCC 11.3.0                                                                                             | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 12.2.0                                                                                             | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| GCC 13.0.0 20220605 (experimental)                                                                     | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Intel C++ Compiler 2021.5.0.20211109                                                                   | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| NVCC 11.0.221                                                                                          | Ubuntu 20.04.3 LTS | GitHub Actions |\r\n| Visual Studio 14 2015 MSVC 19.0.24241.7 (Build Engine version 14.0.25420.1)                            | Windows-6.3.9600   | AppVeyor       |\r\n| Visual Studio 15 2017 MSVC 19.16.27035.0 (Build Engine version 15.9.21+g9802d43bc3 for .NET Framework) | Windows-10.0.14393 | AppVeyor       |\r\n| Visual Studio 16 2019 MSVC 19.28.29912.0 (Build Engine version 16.9.0+57a23d249 for .NET Framework)    | Windows-10.0.17763 | GitHub Actions |\r\n| Visual Studio 16 2019 MSVC 19.28.29912.0 (Build Engine version 16.9.0+57a23d249 for .NET Framework)    | Windows-10.0.17763 | AppVeyor       |\r\n| Visual Studio 17 2022 MSVC 19.30.30709.0 (Build Engine version 17.0.31804.368 for .NET Framework)      | Windows-10.0.20348 | GitHub Actions |\r\n\r\n\r\n## Integration\r\n\r\n[`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp) is the single required file in `single_include/nlohmann` or [released here](https://github.com/nlohmann/json/releases). You need to add\r\n\r\n```cpp\r\n#include <nlohmann/json.hpp>\r\n\r\n// for convenience\r\nusing json = nlohmann::json;\r\n```\r\n\r\nto the files you want to process JSON and set the necessary switches to enable C++11 (e.g., `-std=c++11` for GCC and Clang).\r\n\r\nYou can further use file [`include/nlohmann/json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/json_fwd.hpp) for forward-declarations. The installation of json_fwd.hpp (as part of cmake's install step), can be achieved by setting `-DJSON_MultipleHeaders=ON`.\r\n\r\n### CMake\r\n\r\nYou can also use the `nlohmann_json::nlohmann_json` interface target in CMake.  This target populates the appropriate usage requirements for `INTERFACE_INCLUDE_DIRECTORIES` to point to the appropriate include directories and `INTERFACE_COMPILE_FEATURES` for the necessary C++11 flags.\r\n\r\n#### External\r\n\r\nTo use this library from a CMake project, you can locate it directly with `find_package()` and use the namespaced imported target from the generated package configuration:\r\n\r\n```cmake\r\n# CMakeLists.txt\r\nfind_package(nlohmann_json 3.2.0 REQUIRED)\r\n...\r\nadd_library(foo ...)\r\n...\r\ntarget_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)\r\n```\r\n\r\nThe package configuration file, `nlohmann_jsonConfig.cmake`, can be used either from an install tree or directly out of the build tree.\r\n\r\n#### Embedded\r\n\r\nTo embed the library directly into an existing CMake project, place the entire source tree in a subdirectory and call `add_subdirectory()` in your `CMakeLists.txt` file:\r\n\r\n```cmake\r\n# Typically you don't care so much for a third party library's tests to be\r\n# run from your own project's code.\r\nset(JSON_BuildTests OFF CACHE INTERNAL \"\")\r\n\r\n# If you only include this third party in PRIVATE source files, you do not\r\n# need to install it when your main project gets installed.\r\n# set(JSON_Install OFF CACHE INTERNAL \"\")\r\n\r\n# Don't use include(nlohmann_json/CMakeLists.txt) since that carries with it\r\n# unintended consequences that will break the build.  It's generally\r\n# discouraged (although not necessarily well documented as such) to use\r\n# include(...) for pulling in other CMake projects anyways.\r\nadd_subdirectory(nlohmann_json)\r\n...\r\nadd_library(foo ...)\r\n...\r\ntarget_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)\r\n```\r\n\r\n##### Embedded (FetchContent)\r\n\r\nSince CMake v3.11,\r\n[FetchContent](https://cmake.org/cmake/help/v3.11/module/FetchContent.html) can\r\nbe used to automatically download a release as a dependency at configure time.\r\n\r\nExample:\r\n```cmake\r\ninclude(FetchContent)\r\n\r\nFetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz)\r\nFetchContent_MakeAvailable(json)\r\n\r\ntarget_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)\r\n```\r\n\r\n**Note**: It is recommended to use the URL approach described above which is supported as of version 3.10.0. See\r\n<https://json.nlohmann.me/integration/cmake/#fetchcontent> for more information.\r\n\r\n#### Supporting Both\r\n\r\nTo allow your project to support either an externally supplied or an embedded JSON library, you can use a pattern akin to the following:\r\n\r\n``` cmake\r\n# Top level CMakeLists.txt\r\nproject(FOO)\r\n...\r\noption(FOO_USE_EXTERNAL_JSON \"Use an external JSON library\" OFF)\r\n...\r\nadd_subdirectory(thirdparty)\r\n...\r\nadd_library(foo ...)\r\n...\r\n# Note that the namespaced target will always be available regardless of the\r\n# import method\r\ntarget_link_libraries(foo PRIVATE nlohmann_json::nlohmann_json)\r\n```\r\n```cmake\r\n# thirdparty/CMakeLists.txt\r\n...\r\nif(FOO_USE_EXTERNAL_JSON)\r\n  find_package(nlohmann_json 3.2.0 REQUIRED)\r\nelse()\r\n  set(JSON_BuildTests OFF CACHE INTERNAL \"\")\r\n  add_subdirectory(nlohmann_json)\r\nendif()\r\n...\r\n```\r\n\r\n`thirdparty/nlohmann_json` is then a complete copy of this source tree.\r\n\r\n### Package Managers\r\n\r\n:beer: If you are using OS X and [Homebrew](https://brew.sh), just type `brew install nlohmann-json` and you're set. If you want the bleeding edge rather than the latest release, use `brew install nlohmann-json --HEAD`. See [nlohmann-json](https://formulae.brew.sh/formula/nlohmann-json) for more information.\r\n\r\nIf you are using the [Meson Build System](https://mesonbuild.com), add this source tree as a [meson subproject](https://mesonbuild.com/Subprojects.html#using-a-subproject). You may also use the `include.zip` published in this project's [Releases](https://github.com/nlohmann/json/releases) to reduce the size of the vendored source tree. Alternatively, you can get a wrap file by downloading it from [Meson WrapDB](https://wrapdb.mesonbuild.com/nlohmann_json), or simply use `meson wrap install nlohmann_json`. Please see the meson project for any issues regarding the packaging.\r\n\r\nThe provided `meson.build` can also be used as an alternative to CMake for installing `nlohmann_json` system-wide in which case a pkg-config file is installed. To use it, simply have your build system require the `nlohmann_json` pkg-config dependency. In Meson, it is preferred to use the [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a subproject fallback, rather than using the subproject directly.\r\n\r\nIf you are using [Bazel](https://bazel.build/) you can simply reference this repository using `http_archive` or `git_repository` and depend on `@nlohmann_json//:json`.\r\n\r\nIf you are using [Conan](https://www.conan.io/) to manage your dependencies, merely add [`nlohmann_json/x.y.z`](https://conan.io/center/nlohmann_json) to your `conanfile`'s requires, where `x.y.z` is the release version you want to use. Please file issues [here](https://github.com/conan-io/conan-center-index/issues) if you experience problems with the packages.\r\n\r\nIf you are using [Spack](https://www.spack.io/) to manage your dependencies, you can use the [`nlohmann-json` package](https://spack.readthedocs.io/en/latest/package_list.html#nlohmann-json). Please see the [spack project](https://github.com/spack/spack) for any issues regarding the packaging.\r\n\r\nIf you are using [hunter](https://github.com/cpp-pm/hunter) on your project for external dependencies, then you can use the [nlohmann_json package](https://hunter.readthedocs.io/en/latest/packages/pkg/nlohmann_json.html). Please see the hunter project for any issues regarding the packaging.\r\n\r\nIf you are using [Buckaroo](https://buckaroo.pm), you can install this library's module with `buckaroo add github.com/buckaroo-pm/nlohmann-json`. Please file issues [here](https://github.com/buckaroo-pm/nlohmann-json). There is a demo repo [here](https://github.com/njlr/buckaroo-nholmann-json-example).\r\n\r\nIf you are using [vcpkg](https://github.com/Microsoft/vcpkg/) on your project for external dependencies, then you can install the [nlohmann-json package](https://github.com/Microsoft/vcpkg/tree/master/ports/nlohmann-json) with `vcpkg install nlohmann-json` and follow the then displayed descriptions. Please see the vcpkg project for any issues regarding the packaging.\r\n\r\nIf you are using [cget](https://cget.readthedocs.io/en/latest/), you can install the latest development version with `cget install nlohmann/json`. A specific version can be installed with `cget install nlohmann/json@v3.1.0`. Also, the multiple header version can be installed by adding the `-DJSON_MultipleHeaders=ON` flag (i.e., `cget install nlohmann/json -DJSON_MultipleHeaders=ON`).\r\n\r\nIf you are using [CocoaPods](https://cocoapods.org), you can use the library by adding pod `\"nlohmann_json\", '~>3.1.2'` to your podfile (see [an example](https://bitbucket.org/benman/nlohmann_json-cocoapod/src/master/)). Please file issues [here](https://bitbucket.org/benman/nlohmann_json-cocoapod/issues?status=new&status=open).\r\n\r\nIf you are using [NuGet](https://www.nuget.org), you can use the package [nlohmann.json](https://www.nuget.org/packages/nlohmann.json/). Please check [this extensive description](https://github.com/nlohmann/json/issues/1132#issuecomment-452250255) on how to use the package. Please file issues [here](https://github.com/hnkb/nlohmann-json-nuget/issues).\r\n\r\nIf you are using [conda](https://conda.io/), you can use the package [nlohmann_json](https://github.com/conda-forge/nlohmann_json-feedstock) from [conda-forge](https://conda-forge.org) executing `conda install -c conda-forge nlohmann_json`. Please file issues [here](https://github.com/conda-forge/nlohmann_json-feedstock/issues).\r\n\r\nIf you are using [MSYS2](https://www.msys2.org/), you can use the [mingw-w64-nlohmann-json](https://packages.msys2.org/base/mingw-w64-nlohmann-json) package, just type `pacman -S mingw-w64-i686-nlohmann-json` or `pacman -S mingw-w64-x86_64-nlohmann-json` for installation. Please file issues [here](https://github.com/msys2/MINGW-packages/issues/new?title=%5Bnlohmann-json%5D) if you experience problems with the packages.\r\n\r\nIf you are using [MacPorts](https://ports.macports.org), execute `sudo port install nlohmann-json` to install the [nlohmann-json](https://ports.macports.org/port/nlohmann-json/) package.\r\n\r\nIf you are using [`build2`](https://build2.org), you can use the [`nlohmann-json`](https://cppget.org/nlohmann-json) package from the public repository https://cppget.org or directly from the [package's sources repository](https://github.com/build2-packaging/nlohmann-json). In your project's `manifest` file, just add `depends: nlohmann-json` (probably with some [version constraints](https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml#guide-add-remove-deps)). If you are not familiar with using dependencies in `build2`, [please read this introduction](https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml).\r\nPlease file issues [here](https://github.com/build2-packaging/nlohmann-json) if you experience problems with the packages.\r\n\r\nIf you are using [`wsjcpp`](https://wsjcpp.org), you can use the command `wsjcpp install \"https://github.com/nlohmann/json:develop\"` to get the latest version. Note you can change the branch \":develop\" to an existing tag or another branch.\r\n\r\nIf you are using [`CPM.cmake`](https://github.com/TheLartians/CPM.cmake), you can check this [`example`](https://github.com/TheLartians/CPM.cmake/tree/master/examples/json). After [adding CPM script](https://github.com/TheLartians/CPM.cmake#adding-cpm) to your project, implement the following snippet to your CMake:\r\n\r\n```cmake\r\nCPMAddPackage(\r\n    NAME nlohmann_json\r\n    GITHUB_REPOSITORY nlohmann/json\r\n    VERSION 3.9.1)\r\n```\r\n\r\n### Pkg-config\r\n\r\nIf you are using bare Makefiles, you can use `pkg-config` to generate the include flags that point to where the library is installed:\r\n\r\n```sh\r\npkg-config nlohmann_json --cflags\r\n```\r\n\r\nUsers of the Meson build system will also be able to use a system-wide library, which will be found by `pkg-config`:\r\n\r\n```meson\r\njson = dependency('nlohmann_json', required: true)\r\n```\r\n\r\n\r\n## License\r\n\r\n<img align=\"right\" src=\"https://opensource.org/trademarks/opensource/OSI-Approved-License-100x137.png\">\r\n\r\nThe class is licensed under the [MIT License](https://opensource.org/licenses/MIT):\r\n\r\nCopyright &copy; 2013-2022 [Niels Lohmann](https://nlohmann.me)\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n\r\n* * *\r\n\r\nThe class contains the UTF-8 Decoder from Bjoern Hoehrmann which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2008-2009 [Björn Hoehrmann](https://bjoern.hoehrmann.de/) <bjoern@hoehrmann.de>\r\n\r\nThe class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2009 [Florian Loitsch](https://florian.loitsch.com/)\r\n\r\nThe class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).\r\n\r\nThe class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).\r\n\r\n## Contact\r\n\r\nIf you have questions regarding the library, I would like to invite you to [open an issue at GitHub](https://github.com/nlohmann/json/issues/new/choose). Please describe your request, problem, or question as detailed as possible, and also mention the version of the library you are using as well as the version of your compiler and operating system. Opening an issue at GitHub allows other users and contributors to this library to collaborate. For instance, I have little experience with MSVC, and most issues in this regard have been solved by a growing community. If you have a look at the [closed issues](https://github.com/nlohmann/json/issues?q=is%3Aissue+is%3Aclosed), you will see that we react quite timely in most cases.\r\n\r\nOnly if your request would contain confidential information, please [send me an email](mailto:mail@nlohmann.me). For encrypted messages, please use [this key](https://keybase.io/nlohmann/pgp_keys.asc).\r\n\r\n## Security\r\n\r\n[Commits by Niels Lohmann](https://github.com/nlohmann/json/commits) and [releases](https://github.com/nlohmann/json/releases) are signed with this [PGP Key](https://keybase.io/nlohmann/pgp_keys.asc?fingerprint=797167ae41c0a6d9232e48457f3cea63ae251b69).\r\n\r\n## Thanks\r\n\r\nI deeply appreciate the help of the following people.\r\n\r\n<img src=\"https://raw.githubusercontent.com/nlohmann/json/develop/docs/avatars.png\" align=\"right\">\r\n\r\n1. [Teemperor](https://github.com/Teemperor) implemented CMake support and lcov integration, realized escape and Unicode handling in the string parser, and fixed the JSON serialization.\r\n2. [elliotgoodrich](https://github.com/elliotgoodrich) fixed an issue with double deletion in the iterator classes.\r\n3. [kirkshoop](https://github.com/kirkshoop) made the iterators of the class composable to other libraries.\r\n4. [wancw](https://github.com/wanwc) fixed a bug that hindered the class to compile with Clang.\r\n5. Tomas Åblad found a bug in the iterator implementation.\r\n6. [Joshua C. Randall](https://github.com/jrandall) fixed a bug in the floating-point serialization.\r\n7. [Aaron Burghardt](https://github.com/aburgh) implemented code to parse streams incrementally. Furthermore, he greatly improved the parser class by allowing the definition of a filter function to discard undesired elements while parsing.\r\n8. [Daniel Kopeček](https://github.com/dkopecek) fixed a bug in the compilation with GCC 5.0.\r\n9. [Florian Weber](https://github.com/Florianjw) fixed a bug in and improved the performance of the comparison operators.\r\n10. [Eric Cornelius](https://github.com/EricMCornelius) pointed out a bug in the handling with NaN and infinity values. He also improved the performance of the string escaping.\r\n11. [易思龙](https://github.com/likebeta) implemented a conversion from anonymous enums.\r\n12. [kepkin](https://github.com/kepkin) patiently pushed forward the support for Microsoft Visual studio.\r\n13. [gregmarr](https://github.com/gregmarr) simplified the implementation of reverse iterators and helped with numerous hints and improvements. In particular, he pushed forward the implementation of user-defined types.\r\n14. [Caio Luppi](https://github.com/caiovlp) fixed a bug in the Unicode handling.\r\n15. [dariomt](https://github.com/dariomt) fixed some typos in the examples.\r\n16. [Daniel Frey](https://github.com/d-frey) cleaned up some pointers and implemented exception-safe memory allocation.\r\n17. [Colin Hirsch](https://github.com/ColinH) took care of a small namespace issue.\r\n18. [Huu Nguyen](https://github.com/whoshuu) correct a variable name in the documentation.\r\n19. [Silverweed](https://github.com/silverweed) overloaded `parse()` to accept an rvalue reference.\r\n20. [dariomt](https://github.com/dariomt) fixed a subtlety in MSVC type support and implemented the `get_ref()` function to get a reference to stored values.\r\n21. [ZahlGraf](https://github.com/ZahlGraf) added a workaround that allows compilation using Android NDK.\r\n22. [whackashoe](https://github.com/whackashoe) replaced a function that was marked as unsafe by Visual Studio.\r\n23. [406345](https://github.com/406345) fixed two small warnings.\r\n24. [Glen Fernandes](https://github.com/glenfe) noted a potential portability problem in the `has_mapped_type` function.\r\n25. [Corbin Hughes](https://github.com/nibroc) fixed some typos in the contribution guidelines.\r\n26. [twelsby](https://github.com/twelsby) fixed the array subscript operator, an issue that failed the MSVC build, and floating-point parsing/dumping. He further added support for unsigned integer numbers and implemented better roundtrip support for parsed numbers.\r\n27. [Volker Diels-Grabsch](https://github.com/vog) fixed a link in the README file.\r\n28. [msm-](https://github.com/msm-) added support for American Fuzzy Lop.\r\n29. [Annihil](https://github.com/Annihil) fixed an example in the README file.\r\n30. [Themercee](https://github.com/Themercee) noted a wrong URL in the README file.\r\n31. [Lv Zheng](https://github.com/lv-zheng) fixed a namespace issue with `int64_t` and `uint64_t`.\r\n32. [abc100m](https://github.com/abc100m) analyzed the issues with GCC 4.8 and proposed a [partial solution](https://github.com/nlohmann/json/pull/212).\r\n33. [zewt](https://github.com/zewt) added useful notes to the README file about Android.\r\n34. [Róbert Márki](https://github.com/robertmrk) added a fix to use move iterators and improved the integration via CMake.\r\n35. [Chris Kitching](https://github.com/ChrisKitching) cleaned up the CMake files.\r\n36. [Tom Needham](https://github.com/06needhamt) fixed a subtle bug with MSVC 2015 which was also proposed by [Michael K.](https://github.com/Epidal).\r\n37. [Mário Feroldi](https://github.com/thelostt) fixed a small typo.\r\n38. [duncanwerner](https://github.com/duncanwerner) found a really embarrassing performance regression in the 2.0.0 release.\r\n39. [Damien](https://github.com/dtoma) fixed one of the last conversion warnings.\r\n40. [Thomas Braun](https://github.com/t-b) fixed a warning in a test case and adjusted MSVC calls in the CI.\r\n41. [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290). He also implemented the magic behind the serialization/deserialization of user-defined types and split the single header file into smaller chunks.\r\n42. [Stefan](https://github.com/5tefan) fixed a minor issue in the documentation.\r\n43. [Vasil Dimov](https://github.com/vasild) fixed the documentation regarding conversions from `std::multiset`.\r\n44. [ChristophJud](https://github.com/ChristophJud) overworked the CMake files to ease project inclusion.\r\n45. [Vladimir Petrigo](https://github.com/vpetrigo) made a SFINAE hack more readable and added Visual Studio 17 to the build matrix.\r\n46. [Denis Andrejew](https://github.com/seeekr) fixed a grammar issue in the README file.\r\n47. [Pierre-Antoine Lacaze](https://github.com/palacaze) found a subtle bug in the `dump()` function.\r\n48. [TurpentineDistillery](https://github.com/TurpentineDistillery) pointed to [`std::locale::classic()`](https://en.cppreference.com/w/cpp/locale/locale/classic) to avoid too much locale joggling, found some nice performance improvements in the parser, improved the benchmarking code, and realized locale-independent number parsing and printing.\r\n49. [cgzones](https://github.com/cgzones) had an idea how to fix the Coverity scan.\r\n50. [Jared Grubb](https://github.com/jaredgrubb) silenced a nasty documentation warning.\r\n51. [Yixin Zhang](https://github.com/qwename) fixed an integer overflow check.\r\n52. [Bosswestfalen](https://github.com/Bosswestfalen) merged two iterator classes into a smaller one.\r\n53. [Daniel599](https://github.com/Daniel599) helped to get Travis execute the tests with Clang's sanitizers.\r\n54. [Jonathan Lee](https://github.com/vjon) fixed an example in the README file.\r\n55. [gnzlbg](https://github.com/gnzlbg) supported the implementation of user-defined types.\r\n56. [Alexej Harm](https://github.com/qis) helped to get the user-defined types working with Visual Studio.\r\n57. [Jared Grubb](https://github.com/jaredgrubb) supported the implementation of user-defined types.\r\n58. [EnricoBilla](https://github.com/EnricoBilla) noted a typo in an example.\r\n59. [Martin Hořeňovský](https://github.com/horenmar) found a way for a 2x speedup for the compilation time of the test suite.\r\n60. [ukhegg](https://github.com/ukhegg) found proposed an improvement for the examples section.\r\n61. [rswanson-ihi](https://github.com/rswanson-ihi) noted a typo in the README.\r\n62. [Mihai Stan](https://github.com/stanmihai4) fixed a bug in the comparison with `nullptr`s.\r\n63. [Tushar Maheshwari](https://github.com/tusharpm) added [cotire](https://github.com/sakra/cotire) support to speed up the compilation.\r\n64. [TedLyngmo](https://github.com/TedLyngmo) noted a typo in the README, removed unnecessary bit arithmetic, and fixed some `-Weffc++` warnings.\r\n65. [Krzysztof Woś](https://github.com/krzysztofwos) made exceptions more visible.\r\n66. [ftillier](https://github.com/ftillier) fixed a compiler warning.\r\n67. [tinloaf](https://github.com/tinloaf) made sure all pushed warnings are properly popped.\r\n68. [Fytch](https://github.com/Fytch) found a bug in the documentation.\r\n69. [Jay Sistar](https://github.com/Type1J) implemented a Meson build description.\r\n70. [Henry Lee](https://github.com/HenryRLee) fixed a warning in ICC and improved the iterator implementation.\r\n71. [Vincent Thiery](https://github.com/vthiery) maintains a package for the Conan package manager.\r\n72. [Steffen](https://github.com/koemeet) fixed a potential issue with MSVC and `std::min`.\r\n73. [Mike Tzou](https://github.com/Chocobo1) fixed some typos.\r\n74. [amrcode](https://github.com/amrcode) noted a misleading documentation about comparison of floats.\r\n75. [Oleg Endo](https://github.com/olegendo) reduced the memory consumption by replacing `<iostream>` with `<iosfwd>`.\r\n76. [dan-42](https://github.com/dan-42) cleaned up the CMake files to simplify including/reusing of the library.\r\n77. [Nikita Ofitserov](https://github.com/himikof) allowed for moving values from initializer lists.\r\n78. [Greg Hurrell](https://github.com/wincent) fixed a typo.\r\n79. [Dmitry Kukovinets](https://github.com/DmitryKuk) fixed a typo.\r\n80. [kbthomp1](https://github.com/kbthomp1) fixed an issue related to the Intel OSX compiler.\r\n81. [Markus Werle](https://github.com/daixtrose) fixed a typo.\r\n82. [WebProdPP](https://github.com/WebProdPP) fixed a subtle error in a precondition check.\r\n83. [Alex](https://github.com/leha-bot) noted an error in a code sample.\r\n84. [Tom de Geus](https://github.com/tdegeus) reported some warnings with ICC and helped to fix them.\r\n85. [Perry Kundert](https://github.com/pjkundert) simplified reading from input streams.\r\n86. [Sonu Lohani](https://github.com/sonulohani) fixed a small compilation error.\r\n87. [Jamie Seward](https://github.com/jseward) fixed all MSVC warnings.\r\n88. [Nate Vargas](https://github.com/eld00d) added a Doxygen tag file.\r\n89. [pvleuven](https://github.com/pvleuven) helped to fix a warning in ICC.\r\n90. [Pavel](https://github.com/crea7or) helped to fix some warnings in MSVC.\r\n91. [Jamie Seward](https://github.com/jseward) avoided unnecessary string copies in `find()` and `count()`.\r\n92. [Mitja](https://github.com/Itja) fixed some typos.\r\n93. [Jorrit Wronski](https://github.com/jowr) updated the Hunter package links.\r\n94. [Matthias Möller](https://github.com/TinyTinni) added a `.natvis` for the MSVC debug view.\r\n95. [bogemic](https://github.com/bogemic) fixed some C++17 deprecation warnings.\r\n96. [Eren Okka](https://github.com/erengy) fixed some MSVC warnings.\r\n97. [abolz](https://github.com/abolz) integrated the Grisu2 algorithm for proper floating-point formatting, allowing more roundtrip checks to succeed.\r\n98. [Vadim Evard](https://github.com/Pipeliner) fixed a Markdown issue in the README.\r\n99. [zerodefect](https://github.com/zerodefect) fixed a compiler warning.\r\n100. [Kert](https://github.com/kaidokert) allowed to template the string type in the serialization and added the possibility to override the exceptional behavior.\r\n101. [mark-99](https://github.com/mark-99) helped fixing an ICC error.\r\n102. [Patrik Huber](https://github.com/patrikhuber) fixed links in the README file.\r\n103. [johnfb](https://github.com/johnfb) found a bug in the implementation of CBOR's indefinite length strings.\r\n104. [Paul Fultz II](https://github.com/pfultz2) added a note on the cget package manager.\r\n105. [Wilson Lin](https://github.com/wla80) made the integration section of the README more concise.\r\n106. [RalfBielig](https://github.com/ralfbielig) detected and fixed a memory leak in the parser callback.\r\n107. [agrianius](https://github.com/agrianius) allowed to dump JSON to an alternative string type.\r\n108. [Kevin Tonon](https://github.com/ktonon) overworked the C++11 compiler checks in CMake.\r\n109. [Axel Huebl](https://github.com/ax3l) simplified a CMake check and added support for the [Spack package manager](https://spack.io).\r\n110. [Carlos O'Ryan](https://github.com/coryan) fixed a typo.\r\n111. [James Upjohn](https://github.com/jammehcow) fixed a version number in the compilers section.\r\n112. [Chuck Atkins](https://github.com/chuckatkins) adjusted the CMake files to the CMake packaging guidelines and provided documentation for the CMake integration.\r\n113. [Jan Schöppach](https://github.com/dns13) fixed a typo.\r\n114. [martin-mfg](https://github.com/martin-mfg) fixed a typo.\r\n115. [Matthias Möller](https://github.com/TinyTinni) removed the dependency from `std::stringstream`.\r\n116. [agrianius](https://github.com/agrianius) added code to use alternative string implementations.\r\n117. [Daniel599](https://github.com/Daniel599) allowed to use more algorithms with the `items()` function.\r\n118. [Julius Rakow](https://github.com/jrakow) fixed the Meson include directory and fixed the links to [cppreference.com](cppreference.com).\r\n119. [Sonu Lohani](https://github.com/sonulohani) fixed the compilation with MSVC 2015 in debug mode.\r\n120. [grembo](https://github.com/grembo) fixed the test suite and re-enabled several test cases.\r\n121. [Hyeon Kim](https://github.com/simnalamburt) introduced the macro `JSON_INTERNAL_CATCH` to control the exception handling inside the library.\r\n122. [thyu](https://github.com/thyu) fixed a compiler warning.\r\n123. [David Guthrie](https://github.com/LEgregius) fixed a subtle compilation error with Clang 3.4.2.\r\n124. [Dennis Fischer](https://github.com/dennisfischer) allowed to call `find_package` without installing the library.\r\n125. [Hyeon Kim](https://github.com/simnalamburt) fixed an issue with a double macro definition.\r\n126. [Ben Berman](https://github.com/rivertam) made some error messages more understandable.\r\n127. [zakalibit](https://github.com/zakalibit) fixed a compilation problem with the Intel C++ compiler.\r\n128. [mandreyel](https://github.com/mandreyel) fixed a compilation problem.\r\n129. [Kostiantyn Ponomarenko](https://github.com/koponomarenko) added version and license information to the Meson build file.\r\n130. [Henry Schreiner](https://github.com/henryiii) added support for GCC 4.8.\r\n131. [knilch](https://github.com/knilch0r) made sure the test suite does not stall when run in the wrong directory.\r\n132. [Antonio Borondo](https://github.com/antonioborondo) fixed an MSVC 2017 warning.\r\n133. [Dan Gendreau](https://github.com/dgendreau) implemented the `NLOHMANN_JSON_SERIALIZE_ENUM` macro to quickly define an enum/JSON mapping.\r\n134. [efp](https://github.com/efp) added line and column information to parse errors.\r\n135. [julian-becker](https://github.com/julian-becker) added BSON support.\r\n136. [Pratik Chowdhury](https://github.com/pratikpc) added support for structured bindings.\r\n137. [David Avedissian](https://github.com/davedissian) added support for Clang 5.0.1 (PS4 version).\r\n138. [Jonathan Dumaresq](https://github.com/dumarjo) implemented an input adapter to read from `FILE*`.\r\n139. [kjpus](https://github.com/kjpus) fixed a link in the documentation.\r\n140. [Manvendra Singh](https://github.com/manu-chroma) fixed a typo in the documentation.\r\n141. [ziggurat29](https://github.com/ziggurat29) fixed an MSVC warning.\r\n142. [Sylvain Corlay](https://github.com/SylvainCorlay) added code to avoid an issue with MSVC.\r\n143. [mefyl](https://github.com/mefyl) fixed a bug when JSON was parsed from an input stream.\r\n144. [Millian Poquet](https://github.com/mpoquet) allowed to install the library via Meson.\r\n145. [Michael Behrns-Miller](https://github.com/moodboom) found an issue with a missing namespace.\r\n146. [Nasztanovics Ferenc](https://github.com/naszta) fixed a compilation issue with libc 2.12.\r\n147. [Andreas Schwab](https://github.com/andreas-schwab) fixed the endian conversion.\r\n148. [Mark-Dunning](https://github.com/Mark-Dunning) fixed a warning in MSVC.\r\n149. [Gareth Sylvester-Bradley](https://github.com/garethsb-sony) added `operator/` for JSON Pointers.\r\n150. [John-Mark](https://github.com/johnmarkwayve) noted a missing header.\r\n151. [Vitaly Zaitsev](https://github.com/xvitaly) fixed compilation with GCC 9.0.\r\n152. [Laurent Stacul](https://github.com/stac47) fixed compilation with GCC 9.0.\r\n153. [Ivor Wanders](https://github.com/iwanders) helped to reduce the CMake requirement to version 3.1.\r\n154. [njlr](https://github.com/njlr) updated the Buckaroo instructions.\r\n155. [Lion](https://github.com/lieff) fixed a compilation issue with GCC 7 on CentOS.\r\n156. [Isaac Nickaein](https://github.com/nickaein) improved the integer serialization performance and  implemented the `contains()` function.\r\n157. [past-due](https://github.com/past-due) suppressed an unfixable warning.\r\n158. [Elvis Oric](https://github.com/elvisoric) improved Meson support.\r\n159. [Matěj Plch](https://github.com/Afforix) fixed an example in the README.\r\n160. [Mark Beckwith](https://github.com/wythe) fixed a typo.\r\n161. [scinart](https://github.com/scinart) fixed bug in the serializer.\r\n162. [Patrick Boettcher](https://github.com/pboettch) implemented `push_back()` and `pop_back()` for JSON Pointers.\r\n163. [Bruno Oliveira](https://github.com/nicoddemus) added support for Conda.\r\n164. [Michele Caini](https://github.com/skypjack) fixed links in the README.\r\n165. [Hani](https://github.com/hnkb) documented how to install the library with NuGet.\r\n166. [Mark Beckwith](https://github.com/wythe) fixed a typo.\r\n167. [yann-morin-1998](https://github.com/yann-morin-1998) helped to reduce the CMake requirement to version 3.1.\r\n168. [Konstantin Podsvirov](https://github.com/podsvirov) maintains a package for the MSYS2 software distro.\r\n169. [remyabel](https://github.com/remyabel) added GNUInstallDirs to the CMake files.\r\n170. [Taylor Howard](https://github.com/taylorhoward92) fixed a unit test.\r\n171. [Gabe Ron](https://github.com/Macr0Nerd) implemented the `to_string` method.\r\n172. [Watal M. Iwasaki](https://github.com/heavywatal) fixed a Clang warning.\r\n173. [Viktor Kirilov](https://github.com/onqtam) switched the unit tests from [Catch](https://github.com/philsquared/Catch) to [doctest](https://github.com/onqtam/doctest)\r\n174. [Juncheng E](https://github.com/ejcjason) fixed a typo.\r\n175. [tete17](https://github.com/tete17) fixed a bug in the `contains` function.\r\n176. [Xav83](https://github.com/Xav83) fixed some cppcheck warnings.\r\n177. [0xflotus](https://github.com/0xflotus) fixed some typos.\r\n178. [Christian Deneke](https://github.com/chris0x44) added a const version of `json_pointer::back`.\r\n179. [Julien Hamaide](https://github.com/crazyjul) made the `items()` function work with custom string types.\r\n180. [Evan Nemerson](https://github.com/nemequ) updated fixed a bug in Hedley and updated this library accordingly.\r\n181. [Florian Pigorsch](https://github.com/flopp) fixed a lot of typos.\r\n182. [Camille Bégué](https://github.com/cbegue) fixed an issue in the conversion from  `std::pair` and `std::tuple` to `json`.\r\n183. [Anthony VH](https://github.com/AnthonyVH) fixed a compile error in an enum deserialization.\r\n184. [Yuriy Vountesmery](https://github.com/ua-code-dragon) noted a subtle bug in a preprocessor check.\r\n185. [Chen](https://github.com/dota17) fixed numerous issues in the library.\r\n186. [Antony Kellermann](https://github.com/aokellermann) added a CI step for GCC 10.1.\r\n187. [Alex](https://github.com/gistrec) fixed an MSVC warning.\r\n188. [Rainer](https://github.com/rvjr) proposed an improvement in the floating-point serialization in CBOR.\r\n189. [Francois Chabot](https://github.com/FrancoisChabot) made performance improvements in the input adapters.\r\n190. [Arthur Sonzogni](https://github.com/ArthurSonzogni) documented how the library can be included via `FetchContent`.\r\n191. [Rimas Misevičius](https://github.com/rmisev) fixed an error message.\r\n192. [Alexander Myasnikov](https://github.com/alexandermyasnikov) fixed some examples and a link in the README.\r\n193. [Hubert Chathi](https://github.com/uhoreg) made CMake's version config file architecture-independent.\r\n194. [OmnipotentEntity](https://github.com/OmnipotentEntity) implemented the binary values for CBOR, MessagePack, BSON, and UBJSON.\r\n195. [ArtemSarmini](https://github.com/ArtemSarmini) fixed a compilation issue with GCC 10 and fixed a leak.\r\n196. [Evgenii Sopov](https://github.com/sea-kg) integrated the library to the wsjcpp package manager.\r\n197. [Sergey Linev](https://github.com/linev) fixed a compiler warning.\r\n198. [Miguel Magalhães](https://github.com/magamig) fixed the year in the copyright.\r\n199. [Gareth Sylvester-Bradley](https://github.com/garethsb-sony) fixed a compilation issue with MSVC.\r\n200. [Alexander “weej” Jones](https://github.com/alex-weej) fixed an example in the README.\r\n201. [Antoine Cœur](https://github.com/Coeur) fixed some typos in the documentation.\r\n202. [jothepro](https://github.com/jothepro) updated links to the Hunter package.\r\n203. [Dave Lee](https://github.com/kastiglione) fixed link in the README.\r\n204. [Joël Lamotte](https://github.com/Klaim) added instruction for using Build2's package manager.\r\n205. [Paul Jurczak](https://github.com/pauljurczak) fixed an example in the README.\r\n206. [Sonu Lohani](https://github.com/sonulohani) fixed a warning.\r\n207. [Carlos Gomes Martinho](https://github.com/gocarlos) updated the Conan package source.\r\n208. [Konstantin Podsvirov](https://github.com/podsvirov) fixed the MSYS2 package documentation.\r\n209. [Tridacnid](https://github.com/Tridacnid) improved the CMake tests.\r\n210. [Michael](https://github.com/MBalszun) fixed MSVC warnings.\r\n211. [Quentin Barbarat](https://github.com/quentin-dev) fixed an example in the documentation.\r\n212. [XyFreak](https://github.com/XyFreak) fixed a compiler warning.\r\n213. [TotalCaesar659](https://github.com/TotalCaesar659) fixed links in the README.\r\n214. [Tanuj Garg](https://github.com/tanuj208) improved the fuzzer coverage for UBSAN input.\r\n215. [AODQ](https://github.com/AODQ) fixed a compiler warning.\r\n216. [jwittbrodt](https://github.com/jwittbrodt) made `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` inline.\r\n217. [pfeatherstone](https://github.com/pfeatherstone) improved the upper bound of arguments of the `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`/`NLOHMANN_DEFINE_TYPE_INTRUSIVE` macros.\r\n218. [Jan Procházka](https://github.com/jprochazk) fixed a bug in the CBOR parser for binary and string values.\r\n219. [T0b1-iOS](https://github.com/T0b1-iOS) fixed a bug in the new hash implementation.\r\n220. [Matthew Bauer](https://github.com/matthewbauer) adjusted the CBOR writer to create tags for binary subtypes.\r\n221. [gatopeich](https://github.com/gatopeich) implemented an ordered map container for `nlohmann::ordered_json`.\r\n222. [Érico Nogueira Rolim](https://github.com/ericonr) added support for pkg-config.\r\n223. [KonanM](https://github.com/KonanM) proposed an implementation for the `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`/`NLOHMANN_DEFINE_TYPE_INTRUSIVE` macros.\r\n224. [Guillaume Racicot](https://github.com/gracicot) implemented `string_view` support and allowed C++20 support.\r\n225. [Alex Reinking](https://github.com/alexreinking) improved CMake support for `FetchContent`.\r\n226. [Hannes Domani](https://github.com/ssbssa) provided a GDB pretty printer.\r\n227. Lars Wirzenius reviewed the README file.\r\n228. [Jun Jie](https://github.com/ongjunjie) fixed a compiler path in the CMake scripts.\r\n229. [Ronak Buch](https://github.com/rbuch) fixed typos in the documentation.\r\n230. [Alexander Karzhenkov](https://github.com/karzhenkov) fixed a move constructor and the Travis builds.\r\n231. [Leonardo Lima](https://github.com/leozz37) added CPM.Cmake support.\r\n232. [Joseph Blackman](https://github.com/jbzdarkid) fixed a warning.\r\n233. [Yaroslav](https://github.com/YarikTH) updated doctest and implemented unit tests.\r\n234. [Martin Stump](https://github.com/globberwops) fixed a bug in the CMake files.\r\n235. [Jaakko Moisio](https://github.com/jasujm) fixed a bug in the input adapters.\r\n236. [bl-ue](https://github.com/bl-ue) fixed some Markdown issues in the README file.\r\n237. [William A. Wieselquist](https://github.com/wawiesel) fixed an example from the README.\r\n238. [abbaswasim](https://github.com/abbaswasim) fixed an example from the README.\r\n239. [Remy Jette](https://github.com/remyjette) fixed a warning.\r\n240. [Fraser](https://github.com/frasermarlow) fixed the documentation.\r\n241. [Ben Beasley](https://github.com/musicinmybrain) updated doctest.\r\n242. [Doron Behar](https://github.com/doronbehar) fixed pkg-config.pc.\r\n243. [raduteo](https://github.com/raduteo) fixed a warning.\r\n244. [David Pfahler](https://github.com/theShmoo) added the possibility to compile the library without I/O support.\r\n245. [Morten Fyhn Amundsen](https://github.com/mortenfyhn) fixed a typo.\r\n246. [jpl-mac](https://github.com/jpl-mac) allowed to treat the library as a system header in CMake.\r\n247. [Jason Dsouza](https://github.com/jasmcaus) fixed the indentation of the CMake file.\r\n248. [offa](https://github.com/offa) added a link to Conan Center to the documentation.\r\n249. [TotalCaesar659](https://github.com/TotalCaesar659) updated the links in the documentation to use HTTPS.\r\n250. [Rafail Giavrimis](https://github.com/grafail) fixed the Google Benchmark default branch.\r\n251. [Louis Dionne](https://github.com/ldionne) fixed a conversion operator.\r\n252. [justanotheranonymoususer](https://github.com/justanotheranonymoususer) made the examples in the README more consistent.\r\n253. [Finkman](https://github.com/Finkman) suppressed some `-Wfloat-equal` warnings.\r\n254. [Ferry Huberts](https://github.com/fhuberts) fixed `-Wswitch-enum` warnings.\r\n255. [Arseniy Terekhin](https://github.com/senyai) made the GDB pretty-printer robust against unset variable names.\r\n256. [Amir Masoud Abdol](https://github.com/amirmasoudabdol) updated the Homebrew command as nlohmann/json is now in homebrew-core.\r\n257. [Hallot](https://github.com/Hallot) fixed some `-Wextra-semi-stmt warnings`.\r\n258. [Giovanni Cerretani](https://github.com/gcerretani) fixed `-Wunused` warnings on `JSON_DIAGNOSTICS`.\r\n259. [Bogdan Popescu](https://github.com/Kapeli) hosts the [docset](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B) for offline documentation viewers.\r\n260. [Carl Smedstad](https://github.com/carlsmedstad) fixed an assertion error when using `JSON_DIAGNOSTICS`.\r\n261. [miikka75](https://github.com/miikka75) provided an important fix to compile C++17 code with Clang 9.\r\n262. [Maarten Becker](https://github.com/kernie) fixed a warning for shadowed variables.\r\n263. [Cristi Vîjdea](https://github.com/axnsan12) fixed typos in the `operator[]` documentation.\r\n264. [Alex Beregszaszi](https://github.com/axic) fixed spelling mistakes in comments.\r\n265. [Dirk Stolle](https://github.com/striezel) fixed typos in documentation.\r\n266. [Daniel Albuschat](https://github.com/daniel-kun) corrected the parameter name in the `parse` documentation.\r\n267. [Prince Mendiratta](https://github.com/Prince-Mendiratta) fixed a link to the FAQ.\r\n268. [Florian Albrechtskirchinger](https://github.com/falbrechtskirchinger) implemented `std::string_view` support for object keys and made dozens of other improvements.\r\n269. [Qianqian Fang](https://github.com/fangq) implemented the Binary JData (BJData) format.\r\n270. [pketelsen](https://github.com/pketelsen) added macros `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` and `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT`.\r\n271. [DarkZeros](https://github.com/DarkZeros) adjusted to code to not clash with Arduino defines.\r\n272. [flagarde](https://github.com/flagarde) fixed the output of `meta()` for MSVC.\r\n273. [Giovanni Cerretani](https://github.com/gcerretani) fixed a check for `std::filesystem`.\r\n274. [Dimitris Apostolou](https://github.com/rex4539) fixed a typo.\r\n275. [Ferry Huberts](https://github.com/fhuberts) fixed a typo.\r\n276. [Michael Nosthoff](https://github.com/heinemml) fixed a typo.\r\n277. [JungHoon Lee](https://github.com/jhnlee) fixed a typo.\r\n278. [Faruk D.](https://github.com/fdiblen) fixed the CITATION.CFF file.\r\n279. [Andrea Cocito](https://github.com/puffetto) added a clarification on macro usage to the documentation.\r\n280. [Krzysiek Karbowiak](https://github.com/kkarbowiak) refactored the tests to use `CHECK_THROWS_WITH_AS`.\r\n281. [Chaoqi Zhang](https://github.com/prncoprs) fixed a typo.\r\n282. [ivanovmp](https://github.com/ivanovmp) fixed a whitespace error.\r\n283. [KsaNL](https://github.com/KsaNL) fixed a build error when including `<windows.h>`.\r\n284. [Andrea Pappacoda](https://github.com/Tachi107) moved `.pc` and `.cmake` files to `share` directory.\r\n285. [Wolf Vollprecht](https://github.com/wolfv) added the `patch_inplace` function.\r\n286. [Jake Zimmerman](https://github.com/jez) highlighted common usage patterns in the README file.\r\n287. [NN](https://github.com/NN---) added the Visual Studio output directory to `.gitignore`.\r\n288. [Romain Reignier](https://github.com/romainreignier) improved the performance the vector output adapter.\r\n289. [Mike](https://github.com/Mike-Leo-Smith) fixed the `std::iterator_traits`.\r\n290. [Richard Hozák](https://github.com/zxey) added macro `JSON_NO_ENUM` to disable default enum conversions.\r\n291. [vakokako](https://github.com/vakokako) fixed tests when compiling with C++20.\r\n292. [Alexander “weej” Jones](https://github.com/alexweej) fixed an example in the README.\r\n293. [Eli Schwartz](https://github.com/eli-schwartz) added more files to the `include.zip` archive.\r\n294. [Kevin Lu](https://github.com/kevinlul) fixed a compilation issue when typedefs with certain names were present.\r\n295. [Trevor Hickey](https://github.com/luxe) improved the description of an example.\r\n296. [Jef LeCompte](https://github.com/jef) updated the year in the README file.\r\n297. [Alexandre Hamez](https://github.com/ahamez) fixed a warning.\r\n298. [Maninderpal Badhan](https://github.com/mbadhan) fixed a typo.\r\n299. [kevin--](https://github.com/kevin--) added a note to an example in the README file.\r\n300. [I](https://github.com/wx257osn2) fixed a typo.\r\n301. [Gregorio Litenstein](https://github.com/Lord-Kamina) fixed the Clang detection.\r\n302. [Andreas Smas](https://github.com/andoma) added a Doozer badge.\r\n303. [WanCW](https://github.com/wancw) fixed the string conversion with Clang.\r\n304. [zhaohuaxishi](https://github.com/zhaohuaxishi) fixed a Doxygen error.\r\n305. [emvivre](https://github.com/emvivre) removed an invalid parameter from CMake.\r\n306. [Tobias Hermann](https://github.com/Dobiasd) fixed a link in the README file.\r\n307. [Michael](https://github.com/traits) fixed a warning.\r\n308. [Ryan Mulder](https://github.com/ryanjmulder) added `ensure_ascii` to the `dump` function.\r\n309. [Muri Nicanor](https://github.com/murinicanor) fixed the `sed` discovery in the Makefile.\r\n310. [David Avedissian](https://github.com/dgavedissian) implemented SFINAE-friendly `iterator_traits`.\r\n311. [AQNOUCH Mohammed](https://github.com/aqnouch) fixed a typo in the README.\r\n312. [Gareth Sylvester-Bradley](https://github.com/garethsb) added `operator/=` and `operator/` to construct JSON pointers.\r\n313. [Michael Macnair](https://github.com/mykter) added support for afl-fuzz testing.\r\n314. [Berkus Decker](https://github.com/berkus) fixed a typo in the README.\r\n315. [Illia Polishchuk](https://github.com/effolkronium) improved the CMake testing.\r\n316. [Ikko Ashimine](https://github.com/eltociear) fixed a typo.\r\n\r\nThanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone.\r\n\r\n\r\n## Used third-party tools\r\n\r\nThe library itself consists of a single header file licensed under the MIT license. However, it is built, tested, documented, and whatnot using a lot of third-party tools and services. Thanks a lot!\r\n\r\n- [**amalgamate.py - Amalgamate C source and header files**](https://github.com/edlund/amalgamate) to create a single header file\r\n- [**American fuzzy lop**](https://lcamtuf.coredump.cx/afl/) for fuzz testing\r\n- [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows\r\n- [**Artistic Style**](http://astyle.sourceforge.net) for automatic source code indentation\r\n- [**Clang**](https://clang.llvm.org) for compilation with code sanitizers\r\n- [**CMake**](https://cmake.org) for build automation\r\n- [**Codacy**](https://www.codacy.com) for further [code analysis](https://www.codacy.com/app/nlohmann/json)\r\n- [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json)\r\n- [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json)\r\n- [**cppcheck**](http://cppcheck.sourceforge.net) for static analysis\r\n- [**doctest**](https://github.com/onqtam/doctest) for the unit tests\r\n- [**git-update-ghpages**](https://github.com/rstacruz/git-update-ghpages) to upload the documentation to gh-pages\r\n- [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)\r\n- [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks\r\n- [**Hedley**](https://nemequ.github.io/hedley/) to avoid re-inventing several compiler-agnostic feature macros\r\n- [**lcov**](http://ltp.sourceforge.net/coverage/lcov.php) to process coverage information and create an HTML view\r\n- [**libFuzzer**](https://llvm.org/docs/LibFuzzer.html) to implement fuzz testing for OSS-Fuzz\r\n- [**Material for MkDocs**](https://squidfunk.github.io/mkdocs-material/) for the style of the documentation site\r\n- [**MkDocs**](https://www.mkdocs.org) for the documentation site\r\n- [**OSS-Fuzz**](https://github.com/google/oss-fuzz) for continuous fuzz testing of the library ([project repository](https://github.com/google/oss-fuzz/tree/master/projects/json))\r\n- [**Probot**](https://probot.github.io) for automating maintainer tasks such as closing stale issues, requesting missing information, or detecting toxic comments.\r\n- [**Valgrind**](https://valgrind.org) to check for correct memory management\r\n\r\n\r\n## Projects using JSON for Modern C++\r\n\r\nThe library is currently used in Apple macOS Sierra-Monterey and iOS 10-15. I am not sure what they are using the library for, but I am happy that it runs on so many devices.\r\n\r\n\r\n## Notes\r\n\r\n### Character encoding\r\n\r\nThe library supports **Unicode input** as follows:\r\n\r\n- Only **UTF-8** encoded input is supported which is the default encoding for JSON according to [RFC 8259](https://tools.ietf.org/html/rfc8259.html#section-8.1).\r\n- `std::u16string` and `std::u32string` can be parsed, assuming UTF-16 and UTF-32 encoding, respectively. These encodings are not supported when reading from files or other input containers.\r\n- Other encodings such as Latin-1 or ISO 8859-1 are **not** supported and will yield parse or serialization errors.\r\n- [Unicode noncharacters](https://www.unicode.org/faq/private_use.html#nonchar1) will not be replaced by the library.\r\n- Invalid surrogates (e.g., incomplete pairs such as `\\uDEAD`) will yield parse errors.\r\n- The strings stored in the library are UTF-8 encoded. When using the default string type (`std::string`), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs.\r\n- When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers.\r\n- To store wide strings (e.g., `std::wstring`), you need to convert them to a UTF-8 encoded `std::string` before, see [an example](https://json.nlohmann.me/home/faq/#wide-string-handling).\r\n\r\n### Comments in JSON\r\n\r\nThis library does not support comments by default. It does so for three reasons:\r\n\r\n1. Comments are not part of the [JSON specification](https://tools.ietf.org/html/rfc8259). You may argue that `//` or `/* */` are allowed in JavaScript, but JSON is not JavaScript.\r\n2. This was not an oversight: Douglas Crockford [wrote on this](https://plus.google.com/118095276221607585885/posts/RK8qyGVaGSr) in May 2012:\r\n\r\n\t> I removed comments from JSON because I saw people were using them to hold parsing directives, a practice which would have destroyed interoperability.  I know that the lack of comments makes some people sad, but it shouldn't.\r\n\r\n\t> Suppose you are using JSON to keep configuration files, which you would like to annotate. Go ahead and insert all the comments you like. Then pipe it through JSMin before handing it to your JSON parser.\r\n\r\n3. It is dangerous for interoperability if some libraries would add comment support while others don't. Please check [The Harmful Consequences of the Robustness Principle](https://tools.ietf.org/html/draft-iab-protocol-maintenance-01) on this.\r\n\r\nHowever, you can pass set parameter `ignore_comments` to true in the `parse` function to ignore `//` or `/* */` comments. Comments will then be treated as whitespace.\r\n\r\n### Order of object keys\r\n\r\nBy default, the library does not preserve the **insertion order of object elements**. This is standards-compliant, as the [JSON standard](https://tools.ietf.org/html/rfc8259.html) defines objects as \"an unordered collection of zero or more name/value pairs\".\r\n\r\nIf you do want to preserve the insertion order, you can try the type [`nlohmann::ordered_json`](https://github.com/nlohmann/json/issues/2179). Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).\r\n\r\n### Memory Release\r\n\r\nWe checked with Valgrind and the Address Sanitizer (ASAN) that there are no memory leaks.\r\n\r\nIf you find that a parsing program with this library does not release memory, please consider the following case, and it may be unrelated to this library.\r\n\r\n**Your program is compiled with glibc.** There is a tunable threshold that glibc uses to decide whether to actually return memory to the system or whether to cache it for later reuse. If in your program you make lots of small allocations and those small allocations are not a contiguous block and are presumably below the threshold, then they will not get returned to the OS.\r\nHere is a related issue [#1924](https://github.com/nlohmann/json/issues/1924).\r\n\r\n### Further notes\r\n\r\n- The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](https://en.cppreference.com/w/cpp/error/assert). In particular, note [`operator[]`](https://json.nlohmann.me/api/basic_json/operator%5B%5D/) implements **unchecked access** for const objects: If the given key is not present, the behavior is undefined (think of a dereferenced null pointer) and yields an [assertion failure](https://github.com/nlohmann/json/issues/289) if assertions are switched on. If you are not sure whether an element in an object exists, use checked access with the [`at()` function](https://json.nlohmann.me/api/basic_json/at/). Furthermore, you can define `JSON_ASSERT(x)` to replace calls to `assert(x)`.\r\n- As the exact number type is not defined in the [JSON specification](https://tools.ietf.org/html/rfc8259.html), this library tries to choose the best fitting C++ number type automatically. As a result, the type `double` may be used to store numbers which may yield [**floating-point exceptions**](https://github.com/nlohmann/json/issues/181) in certain rare situations if floating-point exceptions have been unmasked in the calling code. These exceptions are not caused by the library and need to be fixed in the calling code, such as by re-masking the exceptions prior to calling library functions.\r\n- The code can be compiled without C++ **runtime type identification** features; that is, you can use the `-fno-rtti` compiler flag.\r\n- **Exceptions** are used widely within the library. They can, however, be switched off with either using the compiler flag `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION`. In this case, exceptions are replaced by `abort()` calls. You can further control this behavior by defining `JSON_THROW_USER` (overriding `throw`), `JSON_TRY_USER` (overriding `try`), and `JSON_CATCH_USER` (overriding `catch`). Note that `JSON_THROW_USER` should leave the current scope (e.g., by throwing or aborting), as continuing after it may yield undefined behavior. Note the explanatory [`what()`](https://en.cppreference.com/w/cpp/error/exception/what) string of exceptions is not available for MSVC if exceptions are disabled, see [#2824](https://github.com/nlohmann/json/discussions/2824).\r\n\r\n## Execute unit tests\r\n\r\nTo compile and run the tests, you need to execute\r\n\r\n```sh\r\n$ mkdir build\r\n$ cd build\r\n$ cmake .. -DJSON_BuildTests=On\r\n$ cmake --build .\r\n$ ctest --output-on-failure\r\n```\r\n\r\nNote that during the `ctest` stage, several JSON test files are downloaded from an [external repository](https://github.com/nlohmann/json_test_data). If policies forbid downloading artifacts during testing, you can download the files yourself and pass the directory with the test files via `-DJSON_TestDataDirectory=path` to CMake. Then, no Internet connectivity is required. See [issue #2189](https://github.com/nlohmann/json/issues/2189) for more information.\r\n\r\nIf the test suite is not found, several test suites will fail like this:\r\n\r\n```\r\n===============================================================================\r\njson/tests/src/make_test_data_available.hpp:21:\r\nTEST CASE:  check test suite is downloaded\r\n\r\njson/tests/src/make_test_data_available.hpp:23: FATAL ERROR: REQUIRE( utils::check_testsuite_downloaded() ) is NOT correct!\r\n  values: REQUIRE( false )\r\n  logged: Test data not found in 'json/cmake-build-debug/json_test_data'.\r\n          Please execute target 'download_test_data' before running this test suite.\r\n          See <https://github.com/nlohmann/json#execute-unit-tests> for more information.\r\n\r\n===============================================================================\r\n```\r\n\r\nIn case you have downloaded the library rather than checked out the code via Git, test `cmake_fetch_content_configure` will fail. Please execute `ctest -LE git_required` to skip these tests. See [issue #2189](https://github.com/nlohmann/json/issues/2189) for more information.\r\n\r\nSome tests change the installed files and hence make the whole process not reproducible. Please execute `ctest -LE not_reproducible` to skip these tests. See [issue #2324](https://github.com/nlohmann/json/issues/2324) for more information.\r\n\r\nNote you need to call `cmake -LE \"not_reproducible|git_required\"` to exclude both labels. See [issue #2596](https://github.com/nlohmann/json/issues/2596) for more information.\r\n\r\nAs Intel compilers use unsafe floating point optimization by default, the unit tests may fail. Use flag [`/fp:precise`](https://software.intel.com/content/www/us/en/develop/documentation/cpp-compiler-developer-guide-and-reference/top/compiler-reference/compiler-options/compiler-option-details/floating-point-options/fp-model-fp.html) then.\r\n"
  },
  {
    "path": "lib/json/json.hpp",
    "content": "//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n/****************************************************************************\\\n * Note on documentation: The source files contain links to the online      *\n * documentation of the public API at https://json.nlohmann.me. This URL    *\n * contains the most recent documentation and should also be applicable to  *\n * previous versions; documentation for deprecated functions is not         *\n * removed, but marked deprecated. See \"Generate documentation\" section in  *\n * file docs/README.md.                                                     *\n\\****************************************************************************/\n\n#ifndef INCLUDE_NLOHMANN_JSON_HPP_\n#define INCLUDE_NLOHMANN_JSON_HPP_\n\n#include <algorithm> // all_of, find, for_each\n#include <cstddef> // nullptr_t, ptrdiff_t, size_t\n#include <functional> // hash, less\n#include <initializer_list> // initializer_list\n#ifndef JSON_NO_IO\n    #include <iosfwd> // istream, ostream\n#endif  // JSON_NO_IO\n#include <iterator> // random_access_iterator_tag\n#include <memory> // unique_ptr\n#include <string> // string, stoi, to_string\n#include <utility> // declval, forward, move, pair, swap\n#include <vector> // vector\n\n// #include <nlohmann/adl_serializer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <utility>\n\n// #include <nlohmann/detail/abi_macros.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// This file contains all macro definitions affecting or depending on the ABI\n\n#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK\n    #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)\n        #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3\n            #warning \"Already included a different version of the library!\"\n        #endif\n    #endif\n#endif\n\n#define NLOHMANN_JSON_VERSION_MAJOR 3   // NOLINT(modernize-macro-to-enum)\n#define NLOHMANN_JSON_VERSION_MINOR 11  // NOLINT(modernize-macro-to-enum)\n#define NLOHMANN_JSON_VERSION_PATCH 3   // NOLINT(modernize-macro-to-enum)\n\n#ifndef JSON_DIAGNOSTICS\n    #define JSON_DIAGNOSTICS 0\n#endif\n\n#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n    #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0\n#endif\n\n#if JSON_DIAGNOSTICS\n    #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag\n#else\n    #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS\n#endif\n\n#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n    #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp\n#else\n    #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON\n#endif\n\n#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION\n    #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0\n#endif\n\n// Construct the namespace ABI tags component\n#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b\n#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \\\n    NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)\n\n#define NLOHMANN_JSON_ABI_TAGS                                       \\\n    NLOHMANN_JSON_ABI_TAGS_CONCAT(                                   \\\n            NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS,                       \\\n            NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)\n\n// Construct the namespace version component\n#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \\\n    _v ## major ## _ ## minor ## _ ## patch\n#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \\\n    NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)\n\n#if NLOHMANN_JSON_NAMESPACE_NO_VERSION\n#define NLOHMANN_JSON_NAMESPACE_VERSION\n#else\n#define NLOHMANN_JSON_NAMESPACE_VERSION                                 \\\n    NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \\\n                                           NLOHMANN_JSON_VERSION_MINOR, \\\n                                           NLOHMANN_JSON_VERSION_PATCH)\n#endif\n\n// Combine namespace components\n#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b\n#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \\\n    NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)\n\n#ifndef NLOHMANN_JSON_NAMESPACE\n#define NLOHMANN_JSON_NAMESPACE               \\\n    nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \\\n            NLOHMANN_JSON_ABI_TAGS,           \\\n            NLOHMANN_JSON_NAMESPACE_VERSION)\n#endif\n\n#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN\n#define NLOHMANN_JSON_NAMESPACE_BEGIN                \\\n    namespace nlohmann                               \\\n    {                                                \\\n    inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \\\n                NLOHMANN_JSON_ABI_TAGS,              \\\n                NLOHMANN_JSON_NAMESPACE_VERSION)     \\\n    {\n#endif\n\n#ifndef NLOHMANN_JSON_NAMESPACE_END\n#define NLOHMANN_JSON_NAMESPACE_END                                     \\\n    }  /* namespace (inline namespace) NOLINT(readability/namespace) */ \\\n    }  // namespace nlohmann\n#endif\n\n// #include <nlohmann/detail/conversions/from_json.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // transform\n#include <array> // array\n#include <forward_list> // forward_list\n#include <iterator> // inserter, front_inserter, end\n#include <map> // map\n#include <string> // string\n#include <tuple> // tuple, make_tuple\n#include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible\n#include <unordered_map> // unordered_map\n#include <utility> // pair, declval\n#include <valarray> // valarray\n\n// #include <nlohmann/detail/exceptions.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // nullptr_t\n#include <exception> // exception\n#if JSON_DIAGNOSTICS\n    #include <numeric> // accumulate\n#endif\n#include <stdexcept> // runtime_error\n#include <string> // to_string\n#include <vector> // vector\n\n// #include <nlohmann/detail/value_t.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cstddef> // size_t\n#include <cstdint> // uint8_t\n#include <string> // string\n\n// #include <nlohmann/detail/macro_scope.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <utility> // declval, pair\n// #include <nlohmann/detail/meta/detected.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <type_traits>\n\n// #include <nlohmann/detail/meta/void_t.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename ...Ts> struct make_void\n{\n    using type = void;\n};\ntemplate<typename ...Ts> using void_t = typename make_void<Ts...>::type;\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// https://en.cppreference.com/w/cpp/experimental/is_detected\nstruct nonesuch\n{\n    nonesuch() = delete;\n    ~nonesuch() = delete;\n    nonesuch(nonesuch const&) = delete;\n    nonesuch(nonesuch const&&) = delete;\n    void operator=(nonesuch const&) = delete;\n    void operator=(nonesuch&&) = delete;\n};\n\ntemplate<class Default,\n         class AlwaysVoid,\n         template<class...> class Op,\n         class... Args>\nstruct detector\n{\n    using value_t = std::false_type;\n    using type = Default;\n};\n\ntemplate<class Default, template<class...> class Op, class... Args>\nstruct detector<Default, void_t<Op<Args...>>, Op, Args...>\n{\n    using value_t = std::true_type;\n    using type = Op<Args...>;\n};\n\ntemplate<template<class...> class Op, class... Args>\nusing is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;\n\ntemplate<template<class...> class Op, class... Args>\nstruct is_detected_lazy : is_detected<Op, Args...> { };\n\ntemplate<template<class...> class Op, class... Args>\nusing detected_t = typename detector<nonesuch, void, Op, Args...>::type;\n\ntemplate<class Default, template<class...> class Op, class... Args>\nusing detected_or = detector<Default, void, Op, Args...>;\n\ntemplate<class Default, template<class...> class Op, class... Args>\nusing detected_or_t = typename detected_or<Default, Op, Args...>::type;\n\ntemplate<class Expected, template<class...> class Op, class... Args>\nusing is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;\n\ntemplate<class To, template<class...> class Op, class... Args>\nusing is_detected_convertible =\n    std::is_convertible<detected_t<Op, Args...>, To>;\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/thirdparty/hedley/hedley.hpp>\n\n\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson <evan@nemerson.com>\n// SPDX-License-Identifier: MIT\n\n/* Hedley - https://nemequ.github.io/hedley\n * Created by Evan Nemerson <evan@nemerson.com>\n */\n\n#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15)\n#if defined(JSON_HEDLEY_VERSION)\n    #undef JSON_HEDLEY_VERSION\n#endif\n#define JSON_HEDLEY_VERSION 15\n\n#if defined(JSON_HEDLEY_STRINGIFY_EX)\n    #undef JSON_HEDLEY_STRINGIFY_EX\n#endif\n#define JSON_HEDLEY_STRINGIFY_EX(x) #x\n\n#if defined(JSON_HEDLEY_STRINGIFY)\n    #undef JSON_HEDLEY_STRINGIFY\n#endif\n#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x)\n\n#if defined(JSON_HEDLEY_CONCAT_EX)\n    #undef JSON_HEDLEY_CONCAT_EX\n#endif\n#define JSON_HEDLEY_CONCAT_EX(a,b) a##b\n\n#if defined(JSON_HEDLEY_CONCAT)\n    #undef JSON_HEDLEY_CONCAT\n#endif\n#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b)\n\n#if defined(JSON_HEDLEY_CONCAT3_EX)\n    #undef JSON_HEDLEY_CONCAT3_EX\n#endif\n#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c\n\n#if defined(JSON_HEDLEY_CONCAT3)\n    #undef JSON_HEDLEY_CONCAT3\n#endif\n#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c)\n\n#if defined(JSON_HEDLEY_VERSION_ENCODE)\n    #undef JSON_HEDLEY_VERSION_ENCODE\n#endif\n#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision))\n\n#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR)\n    #undef JSON_HEDLEY_VERSION_DECODE_MAJOR\n#endif\n#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000)\n\n#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR)\n    #undef JSON_HEDLEY_VERSION_DECODE_MINOR\n#endif\n#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000)\n\n#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION)\n    #undef JSON_HEDLEY_VERSION_DECODE_REVISION\n#endif\n#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000)\n\n#if defined(JSON_HEDLEY_GNUC_VERSION)\n    #undef JSON_HEDLEY_GNUC_VERSION\n#endif\n#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__)\n    #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)\n#elif defined(__GNUC__)\n    #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK)\n    #undef JSON_HEDLEY_GNUC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_GNUC_VERSION)\n    #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_MSVC_VERSION)\n    #undef JSON_HEDLEY_MSVC_VERSION\n#endif\n#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL)\n    #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100)\n#elif defined(_MSC_FULL_VER) && !defined(__ICL)\n    #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10)\n#elif defined(_MSC_VER) && !defined(__ICL)\n    #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0)\n#endif\n\n#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK)\n    #undef JSON_HEDLEY_MSVC_VERSION_CHECK\n#endif\n#if !defined(JSON_HEDLEY_MSVC_VERSION)\n    #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0)\n#elif defined(_MSC_VER) && (_MSC_VER >= 1400)\n    #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch)))\n#elif defined(_MSC_VER) && (_MSC_VER >= 1200)\n    #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch)))\n#else\n    #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor)))\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_VERSION)\n    #undef JSON_HEDLEY_INTEL_VERSION\n#endif\n#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL)\n    #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE)\n#elif defined(__INTEL_COMPILER) && !defined(__ICL)\n    #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0)\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK)\n    #undef JSON_HEDLEY_INTEL_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_INTEL_VERSION)\n    #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_CL_VERSION)\n    #undef JSON_HEDLEY_INTEL_CL_VERSION\n#endif\n#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL)\n    #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0)\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK)\n    #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_INTEL_CL_VERSION)\n    #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_PGI_VERSION)\n    #undef JSON_HEDLEY_PGI_VERSION\n#endif\n#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__)\n    #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__)\n#endif\n\n#if defined(JSON_HEDLEY_PGI_VERSION_CHECK)\n    #undef JSON_HEDLEY_PGI_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_PGI_VERSION)\n    #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_SUNPRO_VERSION)\n    #undef JSON_HEDLEY_SUNPRO_VERSION\n#endif\n#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000)\n    #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10)\n#elif defined(__SUNPRO_C)\n    #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf)\n#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000)\n    #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10)\n#elif defined(__SUNPRO_CC)\n    #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf)\n#endif\n\n#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK)\n    #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_SUNPRO_VERSION)\n    #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION)\n    #undef JSON_HEDLEY_EMSCRIPTEN_VERSION\n#endif\n#if defined(__EMSCRIPTEN__)\n    #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__)\n#endif\n\n#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK)\n    #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION)\n    #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_ARM_VERSION)\n    #undef JSON_HEDLEY_ARM_VERSION\n#endif\n#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION)\n    #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100)\n#elif defined(__CC_ARM) && defined(__ARMCC_VERSION)\n    #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100)\n#endif\n\n#if defined(JSON_HEDLEY_ARM_VERSION_CHECK)\n    #undef JSON_HEDLEY_ARM_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_ARM_VERSION)\n    #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_IBM_VERSION)\n    #undef JSON_HEDLEY_IBM_VERSION\n#endif\n#if defined(__ibmxl__)\n    #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__)\n#elif defined(__xlC__) && defined(__xlC_ver__)\n    #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff)\n#elif defined(__xlC__)\n    #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0)\n#endif\n\n#if defined(JSON_HEDLEY_IBM_VERSION_CHECK)\n    #undef JSON_HEDLEY_IBM_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_IBM_VERSION)\n    #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_VERSION)\n    #undef JSON_HEDLEY_TI_VERSION\n#endif\n#if \\\n    defined(__TI_COMPILER_VERSION__) && \\\n    ( \\\n      defined(__TMS470__) || defined(__TI_ARM__) || \\\n      defined(__MSP430__) || \\\n      defined(__TMS320C2000__) \\\n    )\n#if (__TI_COMPILER_VERSION__ >= 16000000)\n    #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n#endif\n\n#if defined(JSON_HEDLEY_TI_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_VERSION)\n    #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL2000_VERSION)\n    #undef JSON_HEDLEY_TI_CL2000_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__)\n    #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL2000_VERSION)\n    #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL430_VERSION)\n    #undef JSON_HEDLEY_TI_CL430_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__)\n    #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL430_VERSION)\n    #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_ARMCL_VERSION)\n    #undef JSON_HEDLEY_TI_ARMCL_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__))\n    #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_ARMCL_VERSION)\n    #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL6X_VERSION)\n    #undef JSON_HEDLEY_TI_CL6X_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__)\n    #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL6X_VERSION)\n    #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL7X_VERSION)\n    #undef JSON_HEDLEY_TI_CL7X_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__)\n    #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL7X_VERSION)\n    #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CLPRU_VERSION)\n    #undef JSON_HEDLEY_TI_CLPRU_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__)\n    #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CLPRU_VERSION)\n    #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_CRAY_VERSION)\n    #undef JSON_HEDLEY_CRAY_VERSION\n#endif\n#if defined(_CRAYC)\n    #if defined(_RELEASE_PATCHLEVEL)\n        #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL)\n    #else\n        #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0)\n    #endif\n#endif\n\n#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK)\n    #undef JSON_HEDLEY_CRAY_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_CRAY_VERSION)\n    #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_IAR_VERSION)\n    #undef JSON_HEDLEY_IAR_VERSION\n#endif\n#if defined(__IAR_SYSTEMS_ICC__)\n    #if __VER__ > 1000\n        #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000))\n    #else\n        #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0)\n    #endif\n#endif\n\n#if defined(JSON_HEDLEY_IAR_VERSION_CHECK)\n    #undef JSON_HEDLEY_IAR_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_IAR_VERSION)\n    #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TINYC_VERSION)\n    #undef JSON_HEDLEY_TINYC_VERSION\n#endif\n#if defined(__TINYC__)\n    #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100)\n#endif\n\n#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK)\n    #undef JSON_HEDLEY_TINYC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TINYC_VERSION)\n    #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_DMC_VERSION)\n    #undef JSON_HEDLEY_DMC_VERSION\n#endif\n#if defined(__DMC__)\n    #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf)\n#endif\n\n#if defined(JSON_HEDLEY_DMC_VERSION_CHECK)\n    #undef JSON_HEDLEY_DMC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_DMC_VERSION)\n    #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_COMPCERT_VERSION)\n    #undef JSON_HEDLEY_COMPCERT_VERSION\n#endif\n#if defined(__COMPCERT_VERSION__)\n    #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100)\n#endif\n\n#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK)\n    #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_COMPCERT_VERSION)\n    #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_PELLES_VERSION)\n    #undef JSON_HEDLEY_PELLES_VERSION\n#endif\n#if defined(__POCC__)\n    #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0)\n#endif\n\n#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK)\n    #undef JSON_HEDLEY_PELLES_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_PELLES_VERSION)\n    #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_MCST_LCC_VERSION)\n    #undef JSON_HEDLEY_MCST_LCC_VERSION\n#endif\n#if defined(__LCC__) && defined(__LCC_MINOR__)\n    #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__)\n#endif\n\n#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK)\n    #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_MCST_LCC_VERSION)\n    #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_VERSION)\n    #undef JSON_HEDLEY_GCC_VERSION\n#endif\n#if \\\n    defined(JSON_HEDLEY_GNUC_VERSION) && \\\n    !defined(__clang__) && \\\n    !defined(JSON_HEDLEY_INTEL_VERSION) && \\\n    !defined(JSON_HEDLEY_PGI_VERSION) && \\\n    !defined(JSON_HEDLEY_ARM_VERSION) && \\\n    !defined(JSON_HEDLEY_CRAY_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL430_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \\\n    !defined(__COMPCERT__) && \\\n    !defined(JSON_HEDLEY_MCST_LCC_VERSION)\n    #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION\n#endif\n\n#if defined(JSON_HEDLEY_GCC_VERSION_CHECK)\n    #undef JSON_HEDLEY_GCC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_GCC_VERSION)\n    #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_ATTRIBUTE)\n    #undef JSON_HEDLEY_HAS_ATTRIBUTE\n#endif\n#if \\\n  defined(__has_attribute) && \\\n  ( \\\n    (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \\\n  )\n#  define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute)\n#else\n#  define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE)\n    #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE\n#endif\n#if defined(__has_attribute)\n    #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE)\n    #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE\n#endif\n#if defined(__has_attribute)\n    #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)\n#else\n    #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE)\n    #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE\n#endif\n#if \\\n    defined(__has_cpp_attribute) && \\\n    defined(__cplusplus) && \\\n    (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0))\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute)\n#else\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS)\n    #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS\n#endif\n#if !defined(__cplusplus) || !defined(__has_cpp_attribute)\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)\n#elif \\\n    !defined(JSON_HEDLEY_PGI_VERSION) && \\\n    !defined(JSON_HEDLEY_IAR_VERSION) && \\\n    (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \\\n    (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0))\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute)\n#else\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE)\n    #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE\n#endif\n#if defined(__has_cpp_attribute) && defined(__cplusplus)\n    #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE)\n    #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE\n#endif\n#if defined(__has_cpp_attribute) && defined(__cplusplus)\n    #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)\n#else\n    #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_BUILTIN)\n    #undef JSON_HEDLEY_HAS_BUILTIN\n#endif\n#if defined(__has_builtin)\n    #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin)\n#else\n    #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN)\n    #undef JSON_HEDLEY_GNUC_HAS_BUILTIN\n#endif\n#if defined(__has_builtin)\n    #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN)\n    #undef JSON_HEDLEY_GCC_HAS_BUILTIN\n#endif\n#if defined(__has_builtin)\n    #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)\n#else\n    #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_FEATURE)\n    #undef JSON_HEDLEY_HAS_FEATURE\n#endif\n#if defined(__has_feature)\n    #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature)\n#else\n    #define JSON_HEDLEY_HAS_FEATURE(feature) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE)\n    #undef JSON_HEDLEY_GNUC_HAS_FEATURE\n#endif\n#if defined(__has_feature)\n    #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_FEATURE)\n    #undef JSON_HEDLEY_GCC_HAS_FEATURE\n#endif\n#if defined(__has_feature)\n    #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)\n#else\n    #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_EXTENSION)\n    #undef JSON_HEDLEY_HAS_EXTENSION\n#endif\n#if defined(__has_extension)\n    #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension)\n#else\n    #define JSON_HEDLEY_HAS_EXTENSION(extension) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION)\n    #undef JSON_HEDLEY_GNUC_HAS_EXTENSION\n#endif\n#if defined(__has_extension)\n    #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION)\n    #undef JSON_HEDLEY_GCC_HAS_EXTENSION\n#endif\n#if defined(__has_extension)\n    #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)\n#else\n    #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE)\n    #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE\n#endif\n#if defined(__has_declspec_attribute)\n    #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute)\n#else\n    #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE)\n    #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE\n#endif\n#if defined(__has_declspec_attribute)\n    #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE)\n    #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE\n#endif\n#if defined(__has_declspec_attribute)\n    #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)\n#else\n    #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_WARNING)\n    #undef JSON_HEDLEY_HAS_WARNING\n#endif\n#if defined(__has_warning)\n    #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning)\n#else\n    #define JSON_HEDLEY_HAS_WARNING(warning) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_WARNING)\n    #undef JSON_HEDLEY_GNUC_HAS_WARNING\n#endif\n#if defined(__has_warning)\n    #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_WARNING)\n    #undef JSON_HEDLEY_GCC_HAS_WARNING\n#endif\n#if defined(__has_warning)\n    #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)\n#else\n    #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if \\\n    (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \\\n    defined(__clang__) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \\\n    JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \\\n    (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR))\n    #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value)\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)\n    #define JSON_HEDLEY_PRAGMA(value) __pragma(value)\n#else\n    #define JSON_HEDLEY_PRAGMA(value)\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH)\n    #undef JSON_HEDLEY_DIAGNOSTIC_PUSH\n#endif\n#if defined(JSON_HEDLEY_DIAGNOSTIC_POP)\n    #undef JSON_HEDLEY_DIAGNOSTIC_POP\n#endif\n#if defined(__clang__)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"clang diagnostic push\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"clang diagnostic pop\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"warning(push)\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"warning(pop)\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"GCC diagnostic push\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"GCC diagnostic pop\")\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push))\n    #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop))\n#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"push\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"pop\")\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"diag_push\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"diag_pop\")\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"warning(push)\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"warning(pop)\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH\n    #define JSON_HEDLEY_DIAGNOSTIC_POP\n#endif\n\n/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for\n   HEDLEY INTERNAL USE ONLY.  API subject to change without notice. */\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_\n#endif\n#if defined(__cplusplus)\n#  if JSON_HEDLEY_HAS_WARNING(\"-Wc++98-compat\")\n#    if JSON_HEDLEY_HAS_WARNING(\"-Wc++17-extensions\")\n#      if JSON_HEDLEY_HAS_WARNING(\"-Wc++1z-extensions\")\n#        define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++98-compat\\\"\") \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++17-extensions\\\"\") \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++1z-extensions\\\"\") \\\n    xpr \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#      else\n#        define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++98-compat\\\"\") \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++17-extensions\\\"\") \\\n    xpr \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#      endif\n#    else\n#      define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++98-compat\\\"\") \\\n    xpr \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#    endif\n#  endif\n#endif\n#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x\n#endif\n\n#if defined(JSON_HEDLEY_CONST_CAST)\n    #undef JSON_HEDLEY_CONST_CAST\n#endif\n#if defined(__cplusplus)\n#  define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast<T>(expr))\n#elif \\\n  JSON_HEDLEY_HAS_WARNING(\"-Wcast-qual\") || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#  define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \\\n        JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n        JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \\\n        ((T) (expr)); \\\n        JSON_HEDLEY_DIAGNOSTIC_POP \\\n    }))\n#else\n#  define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr))\n#endif\n\n#if defined(JSON_HEDLEY_REINTERPRET_CAST)\n    #undef JSON_HEDLEY_REINTERPRET_CAST\n#endif\n#if defined(__cplusplus)\n    #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast<T>(expr))\n#else\n    #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr))\n#endif\n\n#if defined(JSON_HEDLEY_STATIC_CAST)\n    #undef JSON_HEDLEY_STATIC_CAST\n#endif\n#if defined(__cplusplus)\n    #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast<T>(expr))\n#else\n    #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr))\n#endif\n\n#if defined(JSON_HEDLEY_CPP_CAST)\n    #undef JSON_HEDLEY_CPP_CAST\n#endif\n#if defined(__cplusplus)\n#  if JSON_HEDLEY_HAS_WARNING(\"-Wold-style-cast\")\n#    define JSON_HEDLEY_CPP_CAST(T, expr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wold-style-cast\\\"\") \\\n    ((T) (expr)) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#  elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0)\n#    define JSON_HEDLEY_CPP_CAST(T, expr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"diag_suppress=Pe137\") \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#  else\n#    define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr))\n#  endif\n#else\n#  define JSON_HEDLEY_CPP_CAST(T, expr) (expr)\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wdeprecated-declarations\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"clang diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"warning(disable:1478 1786)\")\n#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786))\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1215,1216,1444,1445\")\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1215,1444\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"GCC diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996))\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1215,1444\")\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1291,1718\")\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)\")\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"error_messages(off,symdeprecated,symdeprecated2)\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress=Pe1444,Pe1215\")\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"warn(disable:2241)\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-pragmas\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"clang diagnostic ignored \\\"-Wunknown-pragmas\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"warning(disable:161)\")\n#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161))\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 1675\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"GCC diagnostic ignored \\\"-Wunknown-pragmas\\\"\")\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068))\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 163\")\n#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 163\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress=Pe161\")\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 161\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-attributes\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"clang diagnostic ignored \\\"-Wunknown-attributes\\\"\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"GCC diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"warning(disable:1292)\")\n#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292))\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030))\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1097,1098\")\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1097\")\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"error_messages(off,attrskipunsup)\")\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1173\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress=Pe1097\")\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1097\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wcast-qual\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma(\"clang diagnostic ignored \\\"-Wcast-qual\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma(\"warning(disable:2203 2331)\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma(\"GCC diagnostic ignored \\\"-Wcast-qual\\\"\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunused-function\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma(\"clang diagnostic ignored \\\"-Wunused-function\\\"\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma(\"GCC diagnostic ignored \\\"-Wunused-function\\\"\")\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505))\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma(\"diag_suppress 3142\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION\n#endif\n\n#if defined(JSON_HEDLEY_DEPRECATED)\n    #undef JSON_HEDLEY_DEPRECATED\n#endif\n#if defined(JSON_HEDLEY_DEPRECATED_FOR)\n    #undef JSON_HEDLEY_DEPRECATED_FOR\n#endif\n#if \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated(\"Since \" # since))\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated(\"Since \" #since \"; use \" #replacement))\n#elif \\\n    (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__(\"Since \" #since)))\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__(\"Since \" #since \"; use \" #replacement)))\n#elif defined(__cplusplus) && (__cplusplus >= 201402L)\n    #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated(\"Since \" #since)]])\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated(\"Since \" #since \"; use \" #replacement)]])\n#elif \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n    #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__))\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__))\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated)\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated)\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DEPRECATED(since) _Pragma(\"deprecated\")\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma(\"deprecated\")\n#else\n    #define JSON_HEDLEY_DEPRECATED(since)\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement)\n#endif\n\n#if defined(JSON_HEDLEY_UNAVAILABLE)\n    #undef JSON_HEDLEY_UNAVAILABLE\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__(\"Not available until \" #available_since)))\n#else\n    #define JSON_HEDLEY_UNAVAILABLE(available_since)\n#endif\n\n#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT)\n    #undef JSON_HEDLEY_WARN_UNUSED_RESULT\n#endif\n#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG)\n    #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__))\n#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L)\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]])\n#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard)\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])\n#elif defined(_Check_return_) /* SAL */\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_\n#else\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg)\n#endif\n\n#if defined(JSON_HEDLEY_SENTINEL)\n    #undef JSON_HEDLEY_SENTINEL\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position)))\n#else\n    #define JSON_HEDLEY_SENTINEL(position)\n#endif\n\n#if defined(JSON_HEDLEY_NO_RETURN)\n    #undef JSON_HEDLEY_NO_RETURN\n#endif\n#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_NO_RETURN __noreturn\n#elif \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__))\n#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L\n    #define JSON_HEDLEY_NO_RETURN _Noreturn\n#elif defined(__cplusplus) && (__cplusplus >= 201103L)\n    #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]])\n#elif \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n    #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__))\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n    #define JSON_HEDLEY_NO_RETURN _Pragma(\"does_not_return\")\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_NO_RETURN __declspec(noreturn)\n#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)\n    #define JSON_HEDLEY_NO_RETURN _Pragma(\"FUNC_NEVER_RETURNS;\")\n#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)\n    #define JSON_HEDLEY_NO_RETURN __attribute((noreturn))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0)\n    #define JSON_HEDLEY_NO_RETURN __declspec(noreturn)\n#else\n    #define JSON_HEDLEY_NO_RETURN\n#endif\n\n#if defined(JSON_HEDLEY_NO_ESCAPE)\n    #undef JSON_HEDLEY_NO_ESCAPE\n#endif\n#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape)\n    #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__))\n#else\n    #define JSON_HEDLEY_NO_ESCAPE\n#endif\n\n#if defined(JSON_HEDLEY_UNREACHABLE)\n    #undef JSON_HEDLEY_UNREACHABLE\n#endif\n#if defined(JSON_HEDLEY_UNREACHABLE_RETURN)\n    #undef JSON_HEDLEY_UNREACHABLE_RETURN\n#endif\n#if defined(JSON_HEDLEY_ASSUME)\n    #undef JSON_HEDLEY_ASSUME\n#endif\n#if \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_ASSUME(expr) __assume(expr)\n#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume)\n    #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr)\n#elif \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0)\n    #if defined(__cplusplus)\n        #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr)\n    #else\n        #define JSON_HEDLEY_ASSUME(expr) _nassert(expr)\n    #endif\n#endif\n#if \\\n    (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \\\n    JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable()\n#elif defined(JSON_HEDLEY_ASSUME)\n    #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0)\n#endif\n#if !defined(JSON_HEDLEY_ASSUME)\n    #if defined(JSON_HEDLEY_UNREACHABLE)\n        #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1)))\n    #else\n        #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr)\n    #endif\n#endif\n#if defined(JSON_HEDLEY_UNREACHABLE)\n    #if  \\\n        JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \\\n        JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0)\n        #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value))\n    #else\n        #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE()\n    #endif\n#else\n    #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value)\n#endif\n#if !defined(JSON_HEDLEY_UNREACHABLE)\n    #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0)\n#endif\n\nJSON_HEDLEY_DIAGNOSTIC_PUSH\n#if JSON_HEDLEY_HAS_WARNING(\"-Wpedantic\")\n    #pragma clang diagnostic ignored \"-Wpedantic\"\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wc++98-compat-pedantic\") && defined(__cplusplus)\n    #pragma clang diagnostic ignored \"-Wc++98-compat-pedantic\"\n#endif\n#if JSON_HEDLEY_GCC_HAS_WARNING(\"-Wvariadic-macros\",4,0,0)\n    #if defined(__clang__)\n        #pragma clang diagnostic ignored \"-Wvariadic-macros\"\n    #elif defined(JSON_HEDLEY_GCC_VERSION)\n        #pragma GCC diagnostic ignored \"-Wvariadic-macros\"\n    #endif\n#endif\n#if defined(JSON_HEDLEY_NON_NULL)\n    #undef JSON_HEDLEY_NON_NULL\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0)\n    #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__)))\n#else\n    #define JSON_HEDLEY_NON_NULL(...)\n#endif\nJSON_HEDLEY_DIAGNOSTIC_POP\n\n#if defined(JSON_HEDLEY_PRINTF_FORMAT)\n    #undef JSON_HEDLEY_PRINTF_FORMAT\n#endif\n#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO)\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check)))\n#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO)\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check)))\n#elif \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(format) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check)))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0)\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check))\n#else\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check)\n#endif\n\n#if defined(JSON_HEDLEY_CONSTEXPR)\n    #undef JSON_HEDLEY_CONSTEXPR\n#endif\n#if defined(__cplusplus)\n    #if __cplusplus >= 201103L\n        #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr)\n    #endif\n#endif\n#if !defined(JSON_HEDLEY_CONSTEXPR)\n    #define JSON_HEDLEY_CONSTEXPR\n#endif\n\n#if defined(JSON_HEDLEY_PREDICT)\n    #undef JSON_HEDLEY_PREDICT\n#endif\n#if defined(JSON_HEDLEY_LIKELY)\n    #undef JSON_HEDLEY_LIKELY\n#endif\n#if defined(JSON_HEDLEY_UNLIKELY)\n    #undef JSON_HEDLEY_UNLIKELY\n#endif\n#if defined(JSON_HEDLEY_UNPREDICTABLE)\n    #undef JSON_HEDLEY_UNPREDICTABLE\n#endif\n#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable)\n    #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr))\n#endif\n#if \\\n  (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#  define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability(  (expr), (value), (probability))\n#  define JSON_HEDLEY_PREDICT_TRUE(expr, probability)   __builtin_expect_with_probability(!!(expr),    1   , (probability))\n#  define JSON_HEDLEY_PREDICT_FALSE(expr, probability)  __builtin_expect_with_probability(!!(expr),    0   , (probability))\n#  define JSON_HEDLEY_LIKELY(expr)                      __builtin_expect                 (!!(expr),    1                  )\n#  define JSON_HEDLEY_UNLIKELY(expr)                    __builtin_expect                 (!!(expr),    0                  )\n#elif \\\n  (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n  (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \\\n  JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n  JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n  JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n  JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \\\n  JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \\\n  JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \\\n  JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \\\n  JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n  JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n  JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \\\n  JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#  define JSON_HEDLEY_PREDICT(expr, expected, probability) \\\n    (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)))\n#  define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \\\n    (__extension__ ({ \\\n        double hedley_probability_ = (probability); \\\n        ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \\\n    }))\n#  define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \\\n    (__extension__ ({ \\\n        double hedley_probability_ = (probability); \\\n        ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \\\n    }))\n#  define JSON_HEDLEY_LIKELY(expr)   __builtin_expect(!!(expr), 1)\n#  define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0)\n#else\n#  define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))\n#  define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr))\n#  define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr))\n#  define JSON_HEDLEY_LIKELY(expr) (!!(expr))\n#  define JSON_HEDLEY_UNLIKELY(expr) (!!(expr))\n#endif\n#if !defined(JSON_HEDLEY_UNPREDICTABLE)\n    #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5)\n#endif\n\n#if defined(JSON_HEDLEY_MALLOC)\n    #undef JSON_HEDLEY_MALLOC\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_MALLOC __attribute__((__malloc__))\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n    #define JSON_HEDLEY_MALLOC _Pragma(\"returns_new_memory\")\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_MALLOC __declspec(restrict)\n#else\n    #define JSON_HEDLEY_MALLOC\n#endif\n\n#if defined(JSON_HEDLEY_PURE)\n    #undef JSON_HEDLEY_PURE\n#endif\n#if \\\n  JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n  JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n  JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n  JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n  JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n  (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n  (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n  (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n  (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n  JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n  JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n  JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#  define JSON_HEDLEY_PURE __attribute__((__pure__))\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n#  define JSON_HEDLEY_PURE _Pragma(\"does_not_write_global_data\")\n#elif defined(__cplusplus) && \\\n    ( \\\n      JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \\\n      JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \\\n      JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \\\n    )\n#  define JSON_HEDLEY_PURE _Pragma(\"FUNC_IS_PURE;\")\n#else\n#  define JSON_HEDLEY_PURE\n#endif\n\n#if defined(JSON_HEDLEY_CONST)\n    #undef JSON_HEDLEY_CONST\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(const) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_CONST __attribute__((__const__))\n#elif \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n    #define JSON_HEDLEY_CONST _Pragma(\"no_side_effect\")\n#else\n    #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE\n#endif\n\n#if defined(JSON_HEDLEY_RESTRICT)\n    #undef JSON_HEDLEY_RESTRICT\n#endif\n#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus)\n    #define JSON_HEDLEY_RESTRICT restrict\n#elif \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \\\n    defined(__clang__) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_RESTRICT __restrict\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus)\n    #define JSON_HEDLEY_RESTRICT _Restrict\n#else\n    #define JSON_HEDLEY_RESTRICT\n#endif\n\n#if defined(JSON_HEDLEY_INLINE)\n    #undef JSON_HEDLEY_INLINE\n#endif\n#if \\\n    (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \\\n    (defined(__cplusplus) && (__cplusplus >= 199711L))\n    #define JSON_HEDLEY_INLINE inline\n#elif \\\n    defined(JSON_HEDLEY_GCC_VERSION) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0)\n    #define JSON_HEDLEY_INLINE __inline__\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_INLINE __inline\n#else\n    #define JSON_HEDLEY_INLINE\n#endif\n\n#if defined(JSON_HEDLEY_ALWAYS_INLINE)\n    #undef JSON_HEDLEY_ALWAYS_INLINE\n#endif\n#if \\\n  JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n  JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n  JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n  JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n  JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n  (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n  (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n  (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n  (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n  JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n  JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \\\n  JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n#  define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE\n#elif \\\n  JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \\\n  JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#  define JSON_HEDLEY_ALWAYS_INLINE __forceinline\n#elif defined(__cplusplus) && \\\n    ( \\\n      JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n      JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n      JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n      JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \\\n      JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n      JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \\\n    )\n#  define JSON_HEDLEY_ALWAYS_INLINE _Pragma(\"FUNC_ALWAYS_INLINE;\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#  define JSON_HEDLEY_ALWAYS_INLINE _Pragma(\"inline=forced\")\n#else\n#  define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE\n#endif\n\n#if defined(JSON_HEDLEY_NEVER_INLINE)\n    #undef JSON_HEDLEY_NEVER_INLINE\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n    #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__))\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline)\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0)\n    #define JSON_HEDLEY_NEVER_INLINE _Pragma(\"noinline\")\n#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)\n    #define JSON_HEDLEY_NEVER_INLINE _Pragma(\"FUNC_CANNOT_INLINE;\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_NEVER_INLINE _Pragma(\"inline=never\")\n#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)\n    #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0)\n    #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline)\n#else\n    #define JSON_HEDLEY_NEVER_INLINE\n#endif\n\n#if defined(JSON_HEDLEY_PRIVATE)\n    #undef JSON_HEDLEY_PRIVATE\n#endif\n#if defined(JSON_HEDLEY_PUBLIC)\n    #undef JSON_HEDLEY_PUBLIC\n#endif\n#if defined(JSON_HEDLEY_IMPORT)\n    #undef JSON_HEDLEY_IMPORT\n#endif\n#if defined(_WIN32) || defined(__CYGWIN__)\n#  define JSON_HEDLEY_PRIVATE\n#  define JSON_HEDLEY_PUBLIC   __declspec(dllexport)\n#  define JSON_HEDLEY_IMPORT   __declspec(dllimport)\n#else\n#  if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \\\n    ( \\\n      defined(__TI_EABI__) && \\\n      ( \\\n        (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n        JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \\\n      ) \\\n    ) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#    define JSON_HEDLEY_PRIVATE __attribute__((__visibility__(\"hidden\")))\n#    define JSON_HEDLEY_PUBLIC  __attribute__((__visibility__(\"default\")))\n#  else\n#    define JSON_HEDLEY_PRIVATE\n#    define JSON_HEDLEY_PUBLIC\n#  endif\n#  define JSON_HEDLEY_IMPORT    extern\n#endif\n\n#if defined(JSON_HEDLEY_NO_THROW)\n    #undef JSON_HEDLEY_NO_THROW\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__))\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0)\n    #define JSON_HEDLEY_NO_THROW __declspec(nothrow)\n#else\n    #define JSON_HEDLEY_NO_THROW\n#endif\n\n#if defined(JSON_HEDLEY_FALL_THROUGH)\n    #undef JSON_HEDLEY_FALL_THROUGH\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__))\n#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough)\n    #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]])\n#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough)\n    #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]])\n#elif defined(__fallthrough) /* SAL */\n    #define JSON_HEDLEY_FALL_THROUGH __fallthrough\n#else\n    #define JSON_HEDLEY_FALL_THROUGH\n#endif\n\n#if defined(JSON_HEDLEY_RETURNS_NON_NULL)\n    #undef JSON_HEDLEY_RETURNS_NON_NULL\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__))\n#elif defined(_Ret_notnull_) /* SAL */\n    #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_\n#else\n    #define JSON_HEDLEY_RETURNS_NON_NULL\n#endif\n\n#if defined(JSON_HEDLEY_ARRAY_PARAM)\n    #undef JSON_HEDLEY_ARRAY_PARAM\n#endif\n#if \\\n    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \\\n    !defined(__STDC_NO_VLA__) && \\\n    !defined(__cplusplus) && \\\n    !defined(JSON_HEDLEY_PGI_VERSION) && \\\n    !defined(JSON_HEDLEY_TINYC_VERSION)\n    #define JSON_HEDLEY_ARRAY_PARAM(name) (name)\n#else\n    #define JSON_HEDLEY_ARRAY_PARAM(name)\n#endif\n\n#if defined(JSON_HEDLEY_IS_CONSTANT)\n    #undef JSON_HEDLEY_IS_CONSTANT\n#endif\n#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR)\n    #undef JSON_HEDLEY_REQUIRE_CONSTEXPR\n#endif\n/* JSON_HEDLEY_IS_CONSTEXPR_ is for\n   HEDLEY INTERNAL USE ONLY.  API subject to change without notice. */\n#if defined(JSON_HEDLEY_IS_CONSTEXPR_)\n    #undef JSON_HEDLEY_IS_CONSTEXPR_\n#endif\n#if \\\n    JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \\\n    (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \\\n    JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr)\n#endif\n#if !defined(__cplusplus)\n#  if \\\n       JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \\\n       JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \\\n       JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n       JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \\\n       JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \\\n       JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \\\n       JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24)\n#if defined(__INTPTR_TYPE__)\n    #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)\n#else\n    #include <stdint.h>\n    #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*)\n#endif\n#  elif \\\n       ( \\\n          defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \\\n          !defined(JSON_HEDLEY_SUNPRO_VERSION) && \\\n          !defined(JSON_HEDLEY_PGI_VERSION) && \\\n          !defined(JSON_HEDLEY_IAR_VERSION)) || \\\n       (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \\\n       JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \\\n       JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \\\n       JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \\\n       JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0)\n#if defined(__INTPTR_TYPE__)\n    #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)\n#else\n    #include <stdint.h>\n    #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0)\n#endif\n#  elif \\\n       defined(JSON_HEDLEY_GCC_VERSION) || \\\n       defined(JSON_HEDLEY_INTEL_VERSION) || \\\n       defined(JSON_HEDLEY_TINYC_VERSION) || \\\n       defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \\\n       JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \\\n       defined(JSON_HEDLEY_TI_CL2000_VERSION) || \\\n       defined(JSON_HEDLEY_TI_CL6X_VERSION) || \\\n       defined(JSON_HEDLEY_TI_CL7X_VERSION) || \\\n       defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \\\n       defined(__clang__)\n#    define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \\\n        sizeof(void) != \\\n        sizeof(*( \\\n                  1 ? \\\n                  ((void*) ((expr) * 0L) ) : \\\n((struct { char v[sizeof(void) * 2]; } *) 1) \\\n                ) \\\n              ) \\\n                                            )\n#  endif\n#endif\n#if defined(JSON_HEDLEY_IS_CONSTEXPR_)\n    #if !defined(JSON_HEDLEY_IS_CONSTANT)\n        #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr)\n    #endif\n    #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1))\n#else\n    #if !defined(JSON_HEDLEY_IS_CONSTANT)\n        #define JSON_HEDLEY_IS_CONSTANT(expr) (0)\n    #endif\n    #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr)\n#endif\n\n#if defined(JSON_HEDLEY_BEGIN_C_DECLS)\n    #undef JSON_HEDLEY_BEGIN_C_DECLS\n#endif\n#if defined(JSON_HEDLEY_END_C_DECLS)\n    #undef JSON_HEDLEY_END_C_DECLS\n#endif\n#if defined(JSON_HEDLEY_C_DECL)\n    #undef JSON_HEDLEY_C_DECL\n#endif\n#if defined(__cplusplus)\n    #define JSON_HEDLEY_BEGIN_C_DECLS extern \"C\" {\n    #define JSON_HEDLEY_END_C_DECLS }\n    #define JSON_HEDLEY_C_DECL extern \"C\"\n#else\n    #define JSON_HEDLEY_BEGIN_C_DECLS\n    #define JSON_HEDLEY_END_C_DECLS\n    #define JSON_HEDLEY_C_DECL\n#endif\n\n#if defined(JSON_HEDLEY_STATIC_ASSERT)\n    #undef JSON_HEDLEY_STATIC_ASSERT\n#endif\n#if \\\n  !defined(__cplusplus) && ( \\\n      (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \\\n      (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \\\n      JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \\\n      JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n      defined(_Static_assert) \\\n    )\n#  define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message)\n#elif \\\n  (defined(__cplusplus) && (__cplusplus >= 201103L)) || \\\n  JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \\\n  JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#  define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message))\n#else\n#  define JSON_HEDLEY_STATIC_ASSERT(expr, message)\n#endif\n\n#if defined(JSON_HEDLEY_NULL)\n    #undef JSON_HEDLEY_NULL\n#endif\n#if defined(__cplusplus)\n    #if __cplusplus >= 201103L\n        #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr)\n    #elif defined(NULL)\n        #define JSON_HEDLEY_NULL NULL\n    #else\n        #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0)\n    #endif\n#elif defined(NULL)\n    #define JSON_HEDLEY_NULL NULL\n#else\n    #define JSON_HEDLEY_NULL ((void*) 0)\n#endif\n\n#if defined(JSON_HEDLEY_MESSAGE)\n    #undef JSON_HEDLEY_MESSAGE\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-pragmas\")\n#  define JSON_HEDLEY_MESSAGE(msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \\\n    JSON_HEDLEY_PRAGMA(message msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#elif \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg)\n#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg)\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg))\n#else\n#  define JSON_HEDLEY_MESSAGE(msg)\n#endif\n\n#if defined(JSON_HEDLEY_WARNING)\n    #undef JSON_HEDLEY_WARNING\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-pragmas\")\n#  define JSON_HEDLEY_WARNING(msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \\\n    JSON_HEDLEY_PRAGMA(clang warning msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#elif \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \\\n  JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#  define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg)\n#elif \\\n  JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \\\n  JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#  define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg))\n#else\n#  define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg)\n#endif\n\n#if defined(JSON_HEDLEY_REQUIRE)\n    #undef JSON_HEDLEY_REQUIRE\n#endif\n#if defined(JSON_HEDLEY_REQUIRE_MSG)\n    #undef JSON_HEDLEY_REQUIRE_MSG\n#endif\n#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if)\n#  if JSON_HEDLEY_HAS_WARNING(\"-Wgcc-compat\")\n#    define JSON_HEDLEY_REQUIRE(expr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wgcc-compat\\\"\") \\\n    __attribute__((diagnose_if(!(expr), #expr, \"error\"))) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#    define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wgcc-compat\\\"\") \\\n    __attribute__((diagnose_if(!(expr), msg, \"error\"))) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#  else\n#    define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, \"error\")))\n#    define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, \"error\")))\n#  endif\n#else\n#  define JSON_HEDLEY_REQUIRE(expr)\n#  define JSON_HEDLEY_REQUIRE_MSG(expr,msg)\n#endif\n\n#if defined(JSON_HEDLEY_FLAGS)\n    #undef JSON_HEDLEY_FLAGS\n#endif\n#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING(\"-Wbitfield-enum-conversion\"))\n    #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__))\n#else\n    #define JSON_HEDLEY_FLAGS\n#endif\n\n#if defined(JSON_HEDLEY_FLAGS_CAST)\n    #undef JSON_HEDLEY_FLAGS_CAST\n#endif\n#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0)\n#  define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \\\n        JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n        _Pragma(\"warning(disable:188)\") \\\n        ((T) (expr)); \\\n        JSON_HEDLEY_DIAGNOSTIC_POP \\\n    }))\n#else\n#  define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr)\n#endif\n\n#if defined(JSON_HEDLEY_EMPTY_BASES)\n    #undef JSON_HEDLEY_EMPTY_BASES\n#endif\n#if \\\n    (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases)\n#else\n    #define JSON_HEDLEY_EMPTY_BASES\n#endif\n\n/* Remaining macros are deprecated. */\n\n#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK)\n    #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK\n#endif\n#if defined(__clang__)\n    #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0)\n#else\n    #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE)\n    #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE)\n    #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN)\n    #undef JSON_HEDLEY_CLANG_HAS_BUILTIN\n#endif\n#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE)\n    #undef JSON_HEDLEY_CLANG_HAS_FEATURE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION)\n    #undef JSON_HEDLEY_CLANG_HAS_EXTENSION\n#endif\n#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE)\n    #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_WARNING)\n    #undef JSON_HEDLEY_CLANG_HAS_WARNING\n#endif\n#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning)\n\n#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */\n\n\n// This file contains all internal macro definitions (except those affecting ABI)\n// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\n// exclude unsupported compilers\n#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK)\n    #if defined(__clang__)\n        #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400\n            #error \"unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers\"\n        #endif\n    #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER))\n        #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800\n            #error \"unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers\"\n        #endif\n    #endif\n#endif\n\n// C++ language standard detection\n// if the user manually specified the used c++ version this is skipped\n#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11)\n    #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)\n        #define JSON_HAS_CPP_20\n        #define JSON_HAS_CPP_17\n        #define JSON_HAS_CPP_14\n    #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464\n        #define JSON_HAS_CPP_17\n        #define JSON_HAS_CPP_14\n    #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)\n        #define JSON_HAS_CPP_14\n    #endif\n    // the cpp 11 flag is always specified because it is the minimal required version\n    #define JSON_HAS_CPP_11\n#endif\n\n#ifdef __has_include\n    #if __has_include(<version>)\n        #include <version>\n    #endif\n#endif\n\n#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM)\n    #ifdef JSON_HAS_CPP_17\n        #if defined(__cpp_lib_filesystem)\n            #define JSON_HAS_FILESYSTEM 1\n        #elif defined(__cpp_lib_experimental_filesystem)\n            #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1\n        #elif !defined(__has_include)\n            #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1\n        #elif __has_include(<filesystem>)\n            #define JSON_HAS_FILESYSTEM 1\n        #elif __has_include(<experimental/filesystem>)\n            #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1\n        #endif\n\n        // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/\n        #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support\n        #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support\n        #if defined(__clang_major__) && __clang_major__ < 7\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support\n        #if defined(_MSC_VER) && _MSC_VER < 1914\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before iOS 13\n        #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before macOS Catalina\n        #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n    #endif\n#endif\n\n#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n    #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0\n#endif\n\n#ifndef JSON_HAS_FILESYSTEM\n    #define JSON_HAS_FILESYSTEM 0\n#endif\n\n#ifndef JSON_HAS_THREE_WAY_COMPARISON\n    #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \\\n        && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L\n        #define JSON_HAS_THREE_WAY_COMPARISON 1\n    #else\n        #define JSON_HAS_THREE_WAY_COMPARISON 0\n    #endif\n#endif\n\n#ifndef JSON_HAS_RANGES\n    // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error\n    #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427\n        #define JSON_HAS_RANGES 0\n    #elif defined(__cpp_lib_ranges)\n        #define JSON_HAS_RANGES 1\n    #else\n        #define JSON_HAS_RANGES 0\n    #endif\n#endif\n\n#ifndef JSON_HAS_STATIC_RTTI\n    #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0\n        #define JSON_HAS_STATIC_RTTI 1\n    #else\n        #define JSON_HAS_STATIC_RTTI 0\n    #endif\n#endif\n\n#ifdef JSON_HAS_CPP_17\n    #define JSON_INLINE_VARIABLE inline\n#else\n    #define JSON_INLINE_VARIABLE\n#endif\n\n#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address)\n    #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]]\n#else\n    #define JSON_NO_UNIQUE_ADDRESS\n#endif\n\n// disable documentation warnings on clang\n#if defined(__clang__)\n    #pragma clang diagnostic push\n    #pragma clang diagnostic ignored \"-Wdocumentation\"\n    #pragma clang diagnostic ignored \"-Wdocumentation-unknown-command\"\n#endif\n\n// allow disabling exceptions\n#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)\n    #define JSON_THROW(exception) throw exception\n    #define JSON_TRY try\n    #define JSON_CATCH(exception) catch(exception)\n    #define JSON_INTERNAL_CATCH(exception) catch(exception)\n#else\n    #include <cstdlib>\n    #define JSON_THROW(exception) std::abort()\n    #define JSON_TRY if(true)\n    #define JSON_CATCH(exception) if(false)\n    #define JSON_INTERNAL_CATCH(exception) if(false)\n#endif\n\n// override exception macros\n#if defined(JSON_THROW_USER)\n    #undef JSON_THROW\n    #define JSON_THROW JSON_THROW_USER\n#endif\n#if defined(JSON_TRY_USER)\n    #undef JSON_TRY\n    #define JSON_TRY JSON_TRY_USER\n#endif\n#if defined(JSON_CATCH_USER)\n    #undef JSON_CATCH\n    #define JSON_CATCH JSON_CATCH_USER\n    #undef JSON_INTERNAL_CATCH\n    #define JSON_INTERNAL_CATCH JSON_CATCH_USER\n#endif\n#if defined(JSON_INTERNAL_CATCH_USER)\n    #undef JSON_INTERNAL_CATCH\n    #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER\n#endif\n\n// allow overriding assert\n#if !defined(JSON_ASSERT)\n    #include <cassert> // assert\n    #define JSON_ASSERT(x) assert(x)\n#endif\n\n// allow to access some private functions (needed by the test suite)\n#if defined(JSON_TESTS_PRIVATE)\n    #define JSON_PRIVATE_UNLESS_TESTED public\n#else\n    #define JSON_PRIVATE_UNLESS_TESTED private\n#endif\n\n/*!\n@brief macro to briefly define a mapping between an enum and JSON\n@def NLOHMANN_JSON_SERIALIZE_ENUM\n@since version 3.4.0\n*/\n#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...)                                            \\\n    template<typename BasicJsonType>                                                            \\\n    inline void to_json(BasicJsonType& j, const ENUM_TYPE& e)                                   \\\n    {                                                                                           \\\n        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE \" must be an enum!\");          \\\n        static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                     \\\n        auto it = std::find_if(std::begin(m), std::end(m),                                      \\\n                               [e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool  \\\n        {                                                                                       \\\n            return ej_pair.first == e;                                                          \\\n        });                                                                                     \\\n        j = ((it != std::end(m)) ? it : std::begin(m))->second;                                 \\\n    }                                                                                           \\\n    template<typename BasicJsonType>                                                            \\\n    inline void from_json(const BasicJsonType& j, ENUM_TYPE& e)                                 \\\n    {                                                                                           \\\n        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE \" must be an enum!\");          \\\n        static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                     \\\n        auto it = std::find_if(std::begin(m), std::end(m),                                      \\\n                               [&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \\\n        {                                                                                       \\\n            return ej_pair.second == j;                                                         \\\n        });                                                                                     \\\n        e = ((it != std::end(m)) ? it : std::begin(m))->first;                                  \\\n    }\n\n// Ugly macros to avoid uglier copy-paste when specializing basic_json. They\n// may be removed in the future once the class is split.\n\n#define NLOHMANN_BASIC_JSON_TPL_DECLARATION                                \\\n    template<template<typename, typename, typename...> class ObjectType,   \\\n             template<typename, typename...> class ArrayType,              \\\n             class StringType, class BooleanType, class NumberIntegerType, \\\n             class NumberUnsignedType, class NumberFloatType,              \\\n             template<typename> class AllocatorType,                       \\\n             template<typename, typename = void> class JSONSerializer,     \\\n             class BinaryType,                                             \\\n             class CustomBaseClass>\n\n#define NLOHMANN_BASIC_JSON_TPL                                            \\\n    basic_json<ObjectType, ArrayType, StringType, BooleanType,             \\\n    NumberIntegerType, NumberUnsignedType, NumberFloatType,                \\\n    AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>\n\n// Macros to simplify conversion from/to types\n\n#define NLOHMANN_JSON_EXPAND( x ) x\n#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME\n#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \\\n        NLOHMANN_JSON_PASTE64, \\\n        NLOHMANN_JSON_PASTE63, \\\n        NLOHMANN_JSON_PASTE62, \\\n        NLOHMANN_JSON_PASTE61, \\\n        NLOHMANN_JSON_PASTE60, \\\n        NLOHMANN_JSON_PASTE59, \\\n        NLOHMANN_JSON_PASTE58, \\\n        NLOHMANN_JSON_PASTE57, \\\n        NLOHMANN_JSON_PASTE56, \\\n        NLOHMANN_JSON_PASTE55, \\\n        NLOHMANN_JSON_PASTE54, \\\n        NLOHMANN_JSON_PASTE53, \\\n        NLOHMANN_JSON_PASTE52, \\\n        NLOHMANN_JSON_PASTE51, \\\n        NLOHMANN_JSON_PASTE50, \\\n        NLOHMANN_JSON_PASTE49, \\\n        NLOHMANN_JSON_PASTE48, \\\n        NLOHMANN_JSON_PASTE47, \\\n        NLOHMANN_JSON_PASTE46, \\\n        NLOHMANN_JSON_PASTE45, \\\n        NLOHMANN_JSON_PASTE44, \\\n        NLOHMANN_JSON_PASTE43, \\\n        NLOHMANN_JSON_PASTE42, \\\n        NLOHMANN_JSON_PASTE41, \\\n        NLOHMANN_JSON_PASTE40, \\\n        NLOHMANN_JSON_PASTE39, \\\n        NLOHMANN_JSON_PASTE38, \\\n        NLOHMANN_JSON_PASTE37, \\\n        NLOHMANN_JSON_PASTE36, \\\n        NLOHMANN_JSON_PASTE35, \\\n        NLOHMANN_JSON_PASTE34, \\\n        NLOHMANN_JSON_PASTE33, \\\n        NLOHMANN_JSON_PASTE32, \\\n        NLOHMANN_JSON_PASTE31, \\\n        NLOHMANN_JSON_PASTE30, \\\n        NLOHMANN_JSON_PASTE29, \\\n        NLOHMANN_JSON_PASTE28, \\\n        NLOHMANN_JSON_PASTE27, \\\n        NLOHMANN_JSON_PASTE26, \\\n        NLOHMANN_JSON_PASTE25, \\\n        NLOHMANN_JSON_PASTE24, \\\n        NLOHMANN_JSON_PASTE23, \\\n        NLOHMANN_JSON_PASTE22, \\\n        NLOHMANN_JSON_PASTE21, \\\n        NLOHMANN_JSON_PASTE20, \\\n        NLOHMANN_JSON_PASTE19, \\\n        NLOHMANN_JSON_PASTE18, \\\n        NLOHMANN_JSON_PASTE17, \\\n        NLOHMANN_JSON_PASTE16, \\\n        NLOHMANN_JSON_PASTE15, \\\n        NLOHMANN_JSON_PASTE14, \\\n        NLOHMANN_JSON_PASTE13, \\\n        NLOHMANN_JSON_PASTE12, \\\n        NLOHMANN_JSON_PASTE11, \\\n        NLOHMANN_JSON_PASTE10, \\\n        NLOHMANN_JSON_PASTE9, \\\n        NLOHMANN_JSON_PASTE8, \\\n        NLOHMANN_JSON_PASTE7, \\\n        NLOHMANN_JSON_PASTE6, \\\n        NLOHMANN_JSON_PASTE5, \\\n        NLOHMANN_JSON_PASTE4, \\\n        NLOHMANN_JSON_PASTE3, \\\n        NLOHMANN_JSON_PASTE2, \\\n        NLOHMANN_JSON_PASTE1)(__VA_ARGS__))\n#define NLOHMANN_JSON_PASTE2(func, v1) func(v1)\n#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2)\n#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3)\n#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4)\n#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5)\n#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6)\n#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7)\n#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8)\n#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9)\n#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10)\n#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11)\n#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12)\n#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13)\n#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14)\n#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15)\n#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16)\n#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17)\n#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18)\n#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19)\n#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20)\n#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21)\n#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22)\n#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23)\n#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24)\n#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)\n#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26)\n#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27)\n#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28)\n#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29)\n#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30)\n#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31)\n#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32)\n#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33)\n#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34)\n#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35)\n#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36)\n#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37)\n#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38)\n#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39)\n#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40)\n#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41)\n#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42)\n#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43)\n#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44)\n#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45)\n#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46)\n#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47)\n#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48)\n#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49)\n#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50)\n#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51)\n#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52)\n#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53)\n#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54)\n#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55)\n#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56)\n#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57)\n#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58)\n#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59)\n#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60)\n#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61)\n#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62)\n#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63)\n\n#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1;\n#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1);\n#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1);\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_INTRUSIVE\n@since version 3.9.0\n*/\n#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...)  \\\n    friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }\n\n#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...)  \\\n    friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }\n\n#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...)  \\\n    friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE\n@since version 3.9.0\n*/\n#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...)  \\\n    inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }\n\n#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...)  \\\n    inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }\n\n#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...)  \\\n    inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }\n\n// inspired from https://stackoverflow.com/a/26745591\n// allows to call any std function as if (e.g. with begin):\n// using std::begin; begin(x);\n//\n// it allows using the detected idiom to retrieve the return type\n// of such an expression\n#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name)                                 \\\n    namespace detail {                                                            \\\n    using std::std_name;                                                          \\\n    \\\n    template<typename... T>                                                       \\\n    using result_of_##std_name = decltype(std_name(std::declval<T>()...));        \\\n    }                                                                             \\\n    \\\n    namespace detail2 {                                                           \\\n    struct std_name##_tag                                                         \\\n    {                                                                             \\\n    };                                                                            \\\n    \\\n    template<typename... T>                                                       \\\n    std_name##_tag std_name(T&&...);                                              \\\n    \\\n    template<typename... T>                                                       \\\n    using result_of_##std_name = decltype(std_name(std::declval<T>()...));        \\\n    \\\n    template<typename... T>                                                       \\\n    struct would_call_std_##std_name                                              \\\n    {                                                                             \\\n        static constexpr auto const value = ::nlohmann::detail::                  \\\n                                            is_detected_exact<std_name##_tag, result_of_##std_name, T...>::value; \\\n    };                                                                            \\\n    } /* namespace detail2 */ \\\n    \\\n    template<typename... T>                                                       \\\n    struct would_call_std_##std_name : detail2::would_call_std_##std_name<T...>   \\\n    {                                                                             \\\n    }\n\n#ifndef JSON_USE_IMPLICIT_CONVERSIONS\n    #define JSON_USE_IMPLICIT_CONVERSIONS 1\n#endif\n\n#if JSON_USE_IMPLICIT_CONVERSIONS\n    #define JSON_EXPLICIT\n#else\n    #define JSON_EXPLICIT explicit\n#endif\n\n#ifndef JSON_DISABLE_ENUM_SERIALIZATION\n    #define JSON_DISABLE_ENUM_SERIALIZATION 0\n#endif\n\n#ifndef JSON_USE_GLOBAL_UDLS\n    #define JSON_USE_GLOBAL_UDLS 1\n#endif\n\n#if JSON_HAS_THREE_WAY_COMPARISON\n    #include <compare> // partial_ordering\n#endif\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////////////////////\n// JSON type enumeration //\n///////////////////////////\n\n/*!\n@brief the JSON type enumeration\n\nThis enumeration collects the different JSON types. It is internally used to\ndistinguish the stored values, and the functions @ref basic_json::is_null(),\n@ref basic_json::is_object(), @ref basic_json::is_array(),\n@ref basic_json::is_string(), @ref basic_json::is_boolean(),\n@ref basic_json::is_number() (with @ref basic_json::is_number_integer(),\n@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()),\n@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and\n@ref basic_json::is_structured() rely on it.\n\n@note There are three enumeration entries (number_integer, number_unsigned, and\nnumber_float), because the library distinguishes these three types for numbers:\n@ref basic_json::number_unsigned_t is used for unsigned integers,\n@ref basic_json::number_integer_t is used for signed integers, and\n@ref basic_json::number_float_t is used for floating-point numbers or to\napproximate integers which do not fit in the limits of their respective type.\n\n@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON\nvalue with the default value for a given type\n\n@since version 1.0.0\n*/\nenum class value_t : std::uint8_t\n{\n    null,             ///< null value\n    object,           ///< object (unordered set of name/value pairs)\n    array,            ///< array (ordered collection of values)\n    string,           ///< string value\n    boolean,          ///< boolean value\n    number_integer,   ///< number value (signed integer)\n    number_unsigned,  ///< number value (unsigned integer)\n    number_float,     ///< number value (floating-point)\n    binary,           ///< binary array (ordered collection of bytes)\n    discarded         ///< discarded by the parser callback function\n};\n\n/*!\n@brief comparison operator for JSON types\n\nReturns an ordering that is similar to Python:\n- order: null < boolean < number < object < array < string < binary\n- furthermore, each type is not smaller than itself\n- discarded values are not comparable\n- binary is represented as a b\"\" string in python and directly comparable to a\n  string; however, making a binary array directly comparable with a string would\n  be surprising behavior in a JSON file.\n\n@since version 1.0.0\n*/\n#if JSON_HAS_THREE_WAY_COMPARISON\n    inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD*\n#else\n    inline bool operator<(const value_t lhs, const value_t rhs) noexcept\n#endif\n{\n    static constexpr std::array<std::uint8_t, 9> order = {{\n            0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */,\n            1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */,\n            6 /* binary */\n        }\n    };\n\n    const auto l_index = static_cast<std::size_t>(lhs);\n    const auto r_index = static_cast<std::size_t>(rhs);\n#if JSON_HAS_THREE_WAY_COMPARISON\n    if (l_index < order.size() && r_index < order.size())\n    {\n        return order[l_index] <=> order[r_index]; // *NOPAD*\n    }\n    return std::partial_ordering::unordered;\n#else\n    return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index];\n#endif\n}\n\n// GCC selects the built-in operator< over an operator rewritten from\n// a user-defined spaceship operator\n// Clang, MSVC, and ICC select the rewritten candidate\n// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200)\n#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__)\ninline bool operator<(const value_t lhs, const value_t rhs) noexcept\n{\n    return std::is_lt(lhs <=> rhs); // *NOPAD*\n}\n#endif\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/string_escape.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief replace all occurrences of a substring by another string\n\n@param[in,out] s  the string to manipulate; changed so that all\n               occurrences of @a f are replaced with @a t\n@param[in]     f  the substring to replace with @a t\n@param[in]     t  the string to replace @a f\n\n@pre The search string @a f must not be empty. **This precondition is\nenforced with an assertion.**\n\n@since version 2.0.0\n*/\ntemplate<typename StringType>\ninline void replace_substring(StringType& s, const StringType& f,\n                              const StringType& t)\n{\n    JSON_ASSERT(!f.empty());\n    for (auto pos = s.find(f);                // find first occurrence of f\n            pos != StringType::npos;          // make sure f was found\n            s.replace(pos, f.size(), t),      // replace with t, and\n            pos = s.find(f, pos + t.size()))  // find next occurrence of f\n    {}\n}\n\n/*!\n * @brief string escaping as described in RFC 6901 (Sect. 4)\n * @param[in] s string to escape\n * @return    escaped string\n *\n * Note the order of escaping \"~\" to \"~0\" and \"/\" to \"~1\" is important.\n */\ntemplate<typename StringType>\ninline StringType escape(StringType s)\n{\n    replace_substring(s, StringType{\"~\"}, StringType{\"~0\"});\n    replace_substring(s, StringType{\"/\"}, StringType{\"~1\"});\n    return s;\n}\n\n/*!\n * @brief string unescaping as described in RFC 6901 (Sect. 4)\n * @param[in] s string to unescape\n * @return    unescaped string\n *\n * Note the order of escaping \"~1\" to \"/\" and \"~0\" to \"~\" is important.\n */\ntemplate<typename StringType>\nstatic void unescape(StringType& s)\n{\n    replace_substring(s, StringType{\"~1\"}, StringType{\"/\"});\n    replace_substring(s, StringType{\"~0\"}, StringType{\"~\"});\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/position_t.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // size_t\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// struct to capture the start position of the current token\nstruct position_t\n{\n    /// the total number of characters read\n    std::size_t chars_read_total = 0;\n    /// the number of characters read in the current line\n    std::size_t chars_read_current_line = 0;\n    /// the number of lines read\n    std::size_t lines_read = 0;\n\n    /// conversion to size_t to preserve SAX interface\n    constexpr operator size_t() const\n    {\n        return chars_read_total;\n    }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-FileCopyrightText: 2018 The Abseil Authors\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cstddef> // size_t\n#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type\n#include <utility> // index_sequence, make_index_sequence, index_sequence_for\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename T>\nusing uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;\n\n#ifdef JSON_HAS_CPP_14\n\n// the following utilities are natively available in C++14\nusing std::enable_if_t;\nusing std::index_sequence;\nusing std::make_index_sequence;\nusing std::index_sequence_for;\n\n#else\n\n// alias templates to reduce boilerplate\ntemplate<bool B, typename T = void>\nusing enable_if_t = typename std::enable_if<B, T>::type;\n\n// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h\n// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0.\n\n//// START OF CODE FROM GOOGLE ABSEIL\n\n// integer_sequence\n//\n// Class template representing a compile-time integer sequence. An instantiation\n// of `integer_sequence<T, Ints...>` has a sequence of integers encoded in its\n// type through its template arguments (which is a common need when\n// working with C++11 variadic templates). `absl::integer_sequence` is designed\n// to be a drop-in replacement for C++14's `std::integer_sequence`.\n//\n// Example:\n//\n//   template< class T, T... Ints >\n//   void user_function(integer_sequence<T, Ints...>);\n//\n//   int main()\n//   {\n//     // user_function's `T` will be deduced to `int` and `Ints...`\n//     // will be deduced to `0, 1, 2, 3, 4`.\n//     user_function(make_integer_sequence<int, 5>());\n//   }\ntemplate <typename T, T... Ints>\nstruct integer_sequence\n{\n    using value_type = T;\n    static constexpr std::size_t size() noexcept\n    {\n        return sizeof...(Ints);\n    }\n};\n\n// index_sequence\n//\n// A helper template for an `integer_sequence` of `size_t`,\n// `absl::index_sequence` is designed to be a drop-in replacement for C++14's\n// `std::index_sequence`.\ntemplate <size_t... Ints>\nusing index_sequence = integer_sequence<size_t, Ints...>;\n\nnamespace utility_internal\n{\n\ntemplate <typename Seq, size_t SeqSize, size_t Rem>\nstruct Extend;\n\n// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency.\ntemplate <typename T, T... Ints, size_t SeqSize>\nstruct Extend<integer_sequence<T, Ints...>, SeqSize, 0>\n{\n    using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >;\n};\n\ntemplate <typename T, T... Ints, size_t SeqSize>\nstruct Extend<integer_sequence<T, Ints...>, SeqSize, 1>\n{\n    using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >;\n};\n\n// Recursion helper for 'make_integer_sequence<T, N>'.\n// 'Gen<T, N>::type' is an alias for 'integer_sequence<T, 0, 1, ... N-1>'.\ntemplate <typename T, size_t N>\nstruct Gen\n{\n    using type =\n        typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type;\n};\n\ntemplate <typename T>\nstruct Gen<T, 0>\n{\n    using type = integer_sequence<T>;\n};\n\n}  // namespace utility_internal\n\n// Compile-time sequences of integers\n\n// make_integer_sequence\n//\n// This template alias is equivalent to\n// `integer_sequence<int, 0, 1, ..., N-1>`, and is designed to be a drop-in\n// replacement for C++14's `std::make_integer_sequence`.\ntemplate <typename T, T N>\nusing make_integer_sequence = typename utility_internal::Gen<T, N>::type;\n\n// make_index_sequence\n//\n// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`,\n// and is designed to be a drop-in replacement for C++14's\n// `std::make_index_sequence`.\ntemplate <size_t N>\nusing make_index_sequence = make_integer_sequence<size_t, N>;\n\n// index_sequence_for\n//\n// Converts a typename pack into an index sequence of the same length, and\n// is designed to be a drop-in replacement for C++14's\n// `std::index_sequence_for()`\ntemplate <typename... Ts>\nusing index_sequence_for = make_index_sequence<sizeof...(Ts)>;\n\n//// END OF CODE FROM GOOGLE ABSEIL\n\n#endif\n\n// dispatch utility (taken from ranges-v3)\ntemplate<unsigned N> struct priority_tag : priority_tag < N - 1 > {};\ntemplate<> struct priority_tag<0> {};\n\n// taken from ranges-v3\ntemplate<typename T>\nstruct static_const\n{\n    static JSON_INLINE_VARIABLE constexpr T value{};\n};\n\n#ifndef JSON_HAS_CPP_17\n    template<typename T>\n    constexpr T static_const<T>::value;\n#endif\n\ntemplate<typename T, typename... Args>\ninline constexpr std::array<T, sizeof...(Args)> make_array(Args&& ... args)\n{\n    return std::array<T, sizeof...(Args)> {{static_cast<T>(std::forward<Args>(args))...}};\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <limits> // numeric_limits\n#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type\n#include <utility> // declval\n#include <tuple> // tuple\n#include <string> // char_traits\n\n// #include <nlohmann/detail/iterators/iterator_traits.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <iterator> // random_access_iterator_tag\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/void_t.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename It, typename = void>\nstruct iterator_types {};\n\ntemplate<typename It>\nstruct iterator_types <\n    It,\n    void_t<typename It::difference_type, typename It::value_type, typename It::pointer,\n    typename It::reference, typename It::iterator_category >>\n{\n    using difference_type = typename It::difference_type;\n    using value_type = typename It::value_type;\n    using pointer = typename It::pointer;\n    using reference = typename It::reference;\n    using iterator_category = typename It::iterator_category;\n};\n\n// This is required as some compilers implement std::iterator_traits in a way that\n// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341.\ntemplate<typename T, typename = void>\nstruct iterator_traits\n{\n};\n\ntemplate<typename T>\nstruct iterator_traits < T, enable_if_t < !std::is_pointer<T>::value >>\n            : iterator_types<T>\n{\n};\n\ntemplate<typename T>\nstruct iterator_traits<T*, enable_if_t<std::is_object<T>::value>>\n{\n    using iterator_category = std::random_access_iterator_tag;\n    using value_type = T;\n    using difference_type = ptrdiff_t;\n    using pointer = T*;\n    using reference = T&;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/call_std/begin.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\nNLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin);\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/call_std/end.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\nNLOHMANN_CAN_CALL_STD_FUNC_IMPL(end);\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/detected.hpp>\n\n// #include <nlohmann/json_fwd.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_\n    #define INCLUDE_NLOHMANN_JSON_FWD_HPP_\n\n    #include <cstdint> // int64_t, uint64_t\n    #include <map> // map\n    #include <memory> // allocator\n    #include <string> // string\n    #include <vector> // vector\n\n    // #include <nlohmann/detail/abi_macros.hpp>\n\n\n    /*!\n    @brief namespace for Niels Lohmann\n    @see https://github.com/nlohmann\n    @since version 1.0.0\n    */\n    NLOHMANN_JSON_NAMESPACE_BEGIN\n\n    /*!\n    @brief default JSONSerializer template argument\n\n    This serializer ignores the template arguments and uses ADL\n    ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))\n    for serialization.\n    */\n    template<typename T = void, typename SFINAE = void>\n    struct adl_serializer;\n\n    /// a class to store JSON values\n    /// @sa https://json.nlohmann.me/api/basic_json/\n    template<template<typename U, typename V, typename... Args> class ObjectType =\n    std::map,\n    template<typename U, typename... Args> class ArrayType = std::vector,\n    class StringType = std::string, class BooleanType = bool,\n    class NumberIntegerType = std::int64_t,\n    class NumberUnsignedType = std::uint64_t,\n    class NumberFloatType = double,\n    template<typename U> class AllocatorType = std::allocator,\n    template<typename T, typename SFINAE = void> class JSONSerializer =\n    adl_serializer,\n    class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError\n    class CustomBaseClass = void>\n    class basic_json;\n\n    /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document\n    /// @sa https://json.nlohmann.me/api/json_pointer/\n    template<typename RefStringType>\n    class json_pointer;\n\n    /*!\n    @brief default specialization\n    @sa https://json.nlohmann.me/api/json/\n    */\n    using json = basic_json<>;\n\n    /// @brief a minimal map-like container that preserves insertion order\n    /// @sa https://json.nlohmann.me/api/ordered_map/\n    template<class Key, class T, class IgnoredLess, class Allocator>\n    struct ordered_map;\n\n    /// @brief specialization that maintains the insertion order of object keys\n    /// @sa https://json.nlohmann.me/api/ordered_json/\n    using ordered_json = basic_json<nlohmann::ordered_map>;\n\n    NLOHMANN_JSON_NAMESPACE_END\n\n#endif  // INCLUDE_NLOHMANN_JSON_FWD_HPP_\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n/*!\n@brief detail namespace with internal helper functions\n\nThis namespace collects functions that should not be exposed,\nimplementations of some @ref basic_json methods, and meta-programming helpers.\n\n@since version 2.1.0\n*/\nnamespace detail\n{\n\n/////////////\n// helpers //\n/////////////\n\n// Note to maintainers:\n//\n// Every trait in this file expects a non CV-qualified type.\n// The only exceptions are in the 'aliases for detected' section\n// (i.e. those of the form: decltype(T::member_function(std::declval<T>())))\n//\n// In this case, T has to be properly CV-qualified to constraint the function arguments\n// (e.g. to_json(BasicJsonType&, const T&))\n\ntemplate<typename> struct is_basic_json : std::false_type {};\n\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nstruct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};\n\n// used by exceptions create() member functions\n// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t\n// false_type otherwise\ntemplate<typename BasicJsonContext>\nstruct is_basic_json_context :\n    std::integral_constant < bool,\n    is_basic_json<typename std::remove_cv<typename std::remove_pointer<BasicJsonContext>::type>::type>::value\n    || std::is_same<BasicJsonContext, std::nullptr_t>::value >\n{};\n\n//////////////////////\n// json_ref helpers //\n//////////////////////\n\ntemplate<typename>\nclass json_ref;\n\ntemplate<typename>\nstruct is_json_ref : std::false_type {};\n\ntemplate<typename T>\nstruct is_json_ref<json_ref<T>> : std::true_type {};\n\n//////////////////////////\n// aliases for detected //\n//////////////////////////\n\ntemplate<typename T>\nusing mapped_type_t = typename T::mapped_type;\n\ntemplate<typename T>\nusing key_type_t = typename T::key_type;\n\ntemplate<typename T>\nusing value_type_t = typename T::value_type;\n\ntemplate<typename T>\nusing difference_type_t = typename T::difference_type;\n\ntemplate<typename T>\nusing pointer_t = typename T::pointer;\n\ntemplate<typename T>\nusing reference_t = typename T::reference;\n\ntemplate<typename T>\nusing iterator_category_t = typename T::iterator_category;\n\ntemplate<typename T, typename... Args>\nusing to_json_function = decltype(T::to_json(std::declval<Args>()...));\n\ntemplate<typename T, typename... Args>\nusing from_json_function = decltype(T::from_json(std::declval<Args>()...));\n\ntemplate<typename T, typename U>\nusing get_template_function = decltype(std::declval<T>().template get<U>());\n\n// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists\ntemplate<typename BasicJsonType, typename T, typename = void>\nstruct has_from_json : std::false_type {};\n\n// trait checking if j.get<T> is valid\n// use this trait instead of std::is_constructible or std::is_convertible,\n// both rely on, or make use of implicit conversions, and thus fail when T\n// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958)\ntemplate <typename BasicJsonType, typename T>\nstruct is_getable\n{\n    static constexpr bool value = is_detected<get_template_function, const BasicJsonType&, T>::value;\n};\n\ntemplate<typename BasicJsonType, typename T>\nstruct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>\n{\n    using serializer = typename BasicJsonType::template json_serializer<T, void>;\n\n    static constexpr bool value =\n        is_detected_exact<void, from_json_function, serializer,\n        const BasicJsonType&, T&>::value;\n};\n\n// This trait checks if JSONSerializer<T>::from_json(json const&) exists\n// this overload is used for non-default-constructible user-defined-types\ntemplate<typename BasicJsonType, typename T, typename = void>\nstruct has_non_default_from_json : std::false_type {};\n\ntemplate<typename BasicJsonType, typename T>\nstruct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>\n{\n    using serializer = typename BasicJsonType::template json_serializer<T, void>;\n\n    static constexpr bool value =\n        is_detected_exact<T, from_json_function, serializer,\n        const BasicJsonType&>::value;\n};\n\n// This trait checks if BasicJsonType::json_serializer<T>::to_json exists\n// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion.\ntemplate<typename BasicJsonType, typename T, typename = void>\nstruct has_to_json : std::false_type {};\n\ntemplate<typename BasicJsonType, typename T>\nstruct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>\n{\n    using serializer = typename BasicJsonType::template json_serializer<T, void>;\n\n    static constexpr bool value =\n        is_detected_exact<void, to_json_function, serializer, BasicJsonType&,\n        T>::value;\n};\n\ntemplate<typename T>\nusing detect_key_compare = typename T::key_compare;\n\ntemplate<typename T>\nstruct has_key_compare : std::integral_constant<bool, is_detected<detect_key_compare, T>::value> {};\n\n// obtains the actual object key comparator\ntemplate<typename BasicJsonType>\nstruct actual_object_comparator\n{\n    using object_t = typename BasicJsonType::object_t;\n    using object_comparator_t = typename BasicJsonType::default_object_comparator_t;\n    using type = typename std::conditional < has_key_compare<object_t>::value,\n          typename object_t::key_compare, object_comparator_t>::type;\n};\n\ntemplate<typename BasicJsonType>\nusing actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type;\n\n/////////////////\n// char_traits //\n/////////////////\n\n// Primary template of char_traits calls std char_traits\ntemplate<typename T>\nstruct char_traits : std::char_traits<T>\n{};\n\n// Explicitly define char traits for unsigned char since it is not standard\ntemplate<>\nstruct char_traits<unsigned char> : std::char_traits<char>\n{\n    using char_type = unsigned char;\n    using int_type = uint64_t;\n\n    // Redefine to_int_type function\n    static int_type to_int_type(char_type c) noexcept\n    {\n        return static_cast<int_type>(c);\n    }\n\n    static char_type to_char_type(int_type i) noexcept\n    {\n        return static_cast<char_type>(i);\n    }\n\n    static constexpr int_type eof() noexcept\n    {\n        return static_cast<int_type>(EOF);\n    }\n};\n\n// Explicitly define char traits for signed char since it is not standard\ntemplate<>\nstruct char_traits<signed char> : std::char_traits<char>\n{\n    using char_type = signed char;\n    using int_type = uint64_t;\n\n    // Redefine to_int_type function\n    static int_type to_int_type(char_type c) noexcept\n    {\n        return static_cast<int_type>(c);\n    }\n\n    static char_type to_char_type(int_type i) noexcept\n    {\n        return static_cast<char_type>(i);\n    }\n\n    static constexpr int_type eof() noexcept\n    {\n        return static_cast<int_type>(EOF);\n    }\n};\n\n///////////////////\n// is_ functions //\n///////////////////\n\n// https://en.cppreference.com/w/cpp/types/conjunction\ntemplate<class...> struct conjunction : std::true_type { };\ntemplate<class B> struct conjunction<B> : B { };\ntemplate<class B, class... Bn>\nstruct conjunction<B, Bn...>\n: std::conditional<static_cast<bool>(B::value), conjunction<Bn...>, B>::type {};\n\n// https://en.cppreference.com/w/cpp/types/negation\ntemplate<class B> struct negation : std::integral_constant < bool, !B::value > { };\n\n// Reimplementation of is_constructible and is_default_constructible, due to them being broken for\n// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).\n// This causes compile errors in e.g. clang 3.5 or gcc 4.9.\ntemplate <typename T>\nstruct is_default_constructible : std::is_default_constructible<T> {};\n\ntemplate <typename T1, typename T2>\nstruct is_default_constructible<std::pair<T1, T2>>\n            : conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {};\n\ntemplate <typename T1, typename T2>\nstruct is_default_constructible<const std::pair<T1, T2>>\n            : conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {};\n\ntemplate <typename... Ts>\nstruct is_default_constructible<std::tuple<Ts...>>\n            : conjunction<is_default_constructible<Ts>...> {};\n\ntemplate <typename... Ts>\nstruct is_default_constructible<const std::tuple<Ts...>>\n            : conjunction<is_default_constructible<Ts>...> {};\n\ntemplate <typename T, typename... Args>\nstruct is_constructible : std::is_constructible<T, Args...> {};\n\ntemplate <typename T1, typename T2>\nstruct is_constructible<std::pair<T1, T2>> : is_default_constructible<std::pair<T1, T2>> {};\n\ntemplate <typename T1, typename T2>\nstruct is_constructible<const std::pair<T1, T2>> : is_default_constructible<const std::pair<T1, T2>> {};\n\ntemplate <typename... Ts>\nstruct is_constructible<std::tuple<Ts...>> : is_default_constructible<std::tuple<Ts...>> {};\n\ntemplate <typename... Ts>\nstruct is_constructible<const std::tuple<Ts...>> : is_default_constructible<const std::tuple<Ts...>> {};\n\ntemplate<typename T, typename = void>\nstruct is_iterator_traits : std::false_type {};\n\ntemplate<typename T>\nstruct is_iterator_traits<iterator_traits<T>>\n{\n  private:\n    using traits = iterator_traits<T>;\n\n  public:\n    static constexpr auto value =\n        is_detected<value_type_t, traits>::value &&\n        is_detected<difference_type_t, traits>::value &&\n        is_detected<pointer_t, traits>::value &&\n        is_detected<iterator_category_t, traits>::value &&\n        is_detected<reference_t, traits>::value;\n};\n\ntemplate<typename T>\nstruct is_range\n{\n  private:\n    using t_ref = typename std::add_lvalue_reference<T>::type;\n\n    using iterator = detected_t<result_of_begin, t_ref>;\n    using sentinel = detected_t<result_of_end, t_ref>;\n\n    // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator\n    // and https://en.cppreference.com/w/cpp/iterator/sentinel_for\n    // but reimplementing these would be too much work, as a lot of other concepts are used underneath\n    static constexpr auto is_iterator_begin =\n        is_iterator_traits<iterator_traits<iterator>>::value;\n\n  public:\n    static constexpr bool value = !std::is_same<iterator, nonesuch>::value && !std::is_same<sentinel, nonesuch>::value && is_iterator_begin;\n};\n\ntemplate<typename R>\nusing iterator_t = enable_if_t<is_range<R>::value, result_of_begin<decltype(std::declval<R&>())>>;\n\ntemplate<typename T>\nusing range_value_t = value_type_t<iterator_traits<iterator_t<T>>>;\n\n// The following implementation of is_complete_type is taken from\n// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/\n// and is written by Xiang Fan who agreed to using it in this library.\n\ntemplate<typename T, typename = void>\nstruct is_complete_type : std::false_type {};\n\ntemplate<typename T>\nstruct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleObjectType,\n         typename = void>\nstruct is_compatible_object_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleObjectType>\nstruct is_compatible_object_type_impl <\n    BasicJsonType, CompatibleObjectType,\n    enable_if_t < is_detected<mapped_type_t, CompatibleObjectType>::value&&\n    is_detected<key_type_t, CompatibleObjectType>::value >>\n{\n    using object_t = typename BasicJsonType::object_t;\n\n    // macOS's is_constructible does not play well with nonesuch...\n    static constexpr bool value =\n        is_constructible<typename object_t::key_type,\n        typename CompatibleObjectType::key_type>::value &&\n        is_constructible<typename object_t::mapped_type,\n        typename CompatibleObjectType::mapped_type>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleObjectType>\nstruct is_compatible_object_type\n    : is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType,\n         typename = void>\nstruct is_constructible_object_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType>\nstruct is_constructible_object_type_impl <\n    BasicJsonType, ConstructibleObjectType,\n    enable_if_t < is_detected<mapped_type_t, ConstructibleObjectType>::value&&\n    is_detected<key_type_t, ConstructibleObjectType>::value >>\n{\n    using object_t = typename BasicJsonType::object_t;\n\n    static constexpr bool value =\n        (is_default_constructible<ConstructibleObjectType>::value &&\n         (std::is_move_assignable<ConstructibleObjectType>::value ||\n          std::is_copy_assignable<ConstructibleObjectType>::value) &&\n         (is_constructible<typename ConstructibleObjectType::key_type,\n          typename object_t::key_type>::value &&\n          std::is_same <\n          typename object_t::mapped_type,\n          typename ConstructibleObjectType::mapped_type >::value)) ||\n        (has_from_json<BasicJsonType,\n         typename ConstructibleObjectType::mapped_type>::value ||\n         has_non_default_from_json <\n         BasicJsonType,\n         typename ConstructibleObjectType::mapped_type >::value);\n};\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType>\nstruct is_constructible_object_type\n    : is_constructible_object_type_impl<BasicJsonType,\n      ConstructibleObjectType> {};\n\ntemplate<typename BasicJsonType, typename CompatibleStringType>\nstruct is_compatible_string_type\n{\n    static constexpr auto value =\n        is_constructible<typename BasicJsonType::string_t, CompatibleStringType>::value;\n};\n\ntemplate<typename BasicJsonType, typename ConstructibleStringType>\nstruct is_constructible_string_type\n{\n    // launder type through decltype() to fix compilation failure on ICPC\n#ifdef __INTEL_COMPILER\n    using laundered_type = decltype(std::declval<ConstructibleStringType>());\n#else\n    using laundered_type = ConstructibleStringType;\n#endif\n\n    static constexpr auto value =\n        conjunction <\n        is_constructible<laundered_type, typename BasicJsonType::string_t>,\n        is_detected_exact<typename BasicJsonType::string_t::value_type,\n        value_type_t, laundered_type >>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleArrayType, typename = void>\nstruct is_compatible_array_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleArrayType>\nstruct is_compatible_array_type_impl <\n    BasicJsonType, CompatibleArrayType,\n    enable_if_t <\n    is_detected<iterator_t, CompatibleArrayType>::value&&\n    is_iterator_traits<iterator_traits<detected_t<iterator_t, CompatibleArrayType>>>::value&&\n// special case for types like std::filesystem::path whose iterator's value_type are themselves\n// c.f. https://github.com/nlohmann/json/pull/3073\n    !std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value >>\n{\n    static constexpr bool value =\n        is_constructible<BasicJsonType,\n        range_value_t<CompatibleArrayType>>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleArrayType>\nstruct is_compatible_array_type\n    : is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType, typename = void>\nstruct is_constructible_array_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType>\nstruct is_constructible_array_type_impl <\n    BasicJsonType, ConstructibleArrayType,\n    enable_if_t<std::is_same<ConstructibleArrayType,\n    typename BasicJsonType::value_type>::value >>\n            : std::true_type {};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType>\nstruct is_constructible_array_type_impl <\n    BasicJsonType, ConstructibleArrayType,\n    enable_if_t < !std::is_same<ConstructibleArrayType,\n    typename BasicJsonType::value_type>::value&&\n    !is_compatible_string_type<BasicJsonType, ConstructibleArrayType>::value&&\n    is_default_constructible<ConstructibleArrayType>::value&&\n(std::is_move_assignable<ConstructibleArrayType>::value ||\n std::is_copy_assignable<ConstructibleArrayType>::value)&&\nis_detected<iterator_t, ConstructibleArrayType>::value&&\nis_iterator_traits<iterator_traits<detected_t<iterator_t, ConstructibleArrayType>>>::value&&\nis_detected<range_value_t, ConstructibleArrayType>::value&&\n// special case for types like std::filesystem::path whose iterator's value_type are themselves\n// c.f. https://github.com/nlohmann/json/pull/3073\n!std::is_same<ConstructibleArrayType, detected_t<range_value_t, ConstructibleArrayType>>::value&&\n        is_complete_type <\n        detected_t<range_value_t, ConstructibleArrayType >>::value >>\n{\n    using value_type = range_value_t<ConstructibleArrayType>;\n\n    static constexpr bool value =\n        std::is_same<value_type,\n        typename BasicJsonType::array_t::value_type>::value ||\n        has_from_json<BasicJsonType,\n        value_type>::value ||\n        has_non_default_from_json <\n        BasicJsonType,\n        value_type >::value;\n};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType>\nstruct is_constructible_array_type\n    : is_constructible_array_type_impl<BasicJsonType, ConstructibleArrayType> {};\n\ntemplate<typename RealIntegerType, typename CompatibleNumberIntegerType,\n         typename = void>\nstruct is_compatible_integer_type_impl : std::false_type {};\n\ntemplate<typename RealIntegerType, typename CompatibleNumberIntegerType>\nstruct is_compatible_integer_type_impl <\n    RealIntegerType, CompatibleNumberIntegerType,\n    enable_if_t < std::is_integral<RealIntegerType>::value&&\n    std::is_integral<CompatibleNumberIntegerType>::value&&\n    !std::is_same<bool, CompatibleNumberIntegerType>::value >>\n{\n    // is there an assert somewhere on overflows?\n    using RealLimits = std::numeric_limits<RealIntegerType>;\n    using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;\n\n    static constexpr auto value =\n        is_constructible<RealIntegerType,\n        CompatibleNumberIntegerType>::value &&\n        CompatibleLimits::is_integer &&\n        RealLimits::is_signed == CompatibleLimits::is_signed;\n};\n\ntemplate<typename RealIntegerType, typename CompatibleNumberIntegerType>\nstruct is_compatible_integer_type\n    : is_compatible_integer_type_impl<RealIntegerType,\n      CompatibleNumberIntegerType> {};\n\ntemplate<typename BasicJsonType, typename CompatibleType, typename = void>\nstruct is_compatible_type_impl: std::false_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleType>\nstruct is_compatible_type_impl <\n    BasicJsonType, CompatibleType,\n    enable_if_t<is_complete_type<CompatibleType>::value >>\n{\n    static constexpr bool value =\n        has_to_json<BasicJsonType, CompatibleType>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleType>\nstruct is_compatible_type\n    : is_compatible_type_impl<BasicJsonType, CompatibleType> {};\n\ntemplate<typename T1, typename T2>\nstruct is_constructible_tuple : std::false_type {};\n\ntemplate<typename T1, typename... Args>\nstruct is_constructible_tuple<T1, std::tuple<Args...>> : conjunction<is_constructible<T1, Args>...> {};\n\ntemplate<typename BasicJsonType, typename T>\nstruct is_json_iterator_of : std::false_type {};\n\ntemplate<typename BasicJsonType>\nstruct is_json_iterator_of<BasicJsonType, typename BasicJsonType::iterator> : std::true_type {};\n\ntemplate<typename BasicJsonType>\nstruct is_json_iterator_of<BasicJsonType, typename BasicJsonType::const_iterator> : std::true_type\n{};\n\n// checks if a given type T is a template specialization of Primary\ntemplate<template <typename...> class Primary, typename T>\nstruct is_specialization_of : std::false_type {};\n\ntemplate<template <typename...> class Primary, typename... Args>\nstruct is_specialization_of<Primary, Primary<Args...>> : std::true_type {};\n\ntemplate<typename T>\nusing is_json_pointer = is_specialization_of<::nlohmann::json_pointer, uncvref_t<T>>;\n\n// checks if A and B are comparable using Compare functor\ntemplate<typename Compare, typename A, typename B, typename = void>\nstruct is_comparable : std::false_type {};\n\ntemplate<typename Compare, typename A, typename B>\nstruct is_comparable<Compare, A, B, void_t<\ndecltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>())),\ndecltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>()))\n>> : std::true_type {};\n\ntemplate<typename T>\nusing detect_is_transparent = typename T::is_transparent;\n\n// type trait to check if KeyType can be used as object key (without a BasicJsonType)\n// see is_usable_as_basic_json_key_type below\ntemplate<typename Comparator, typename ObjectKeyType, typename KeyTypeCVRef, bool RequireTransparentComparator = true,\n         bool ExcludeObjectKeyType = RequireTransparentComparator, typename KeyType = uncvref_t<KeyTypeCVRef>>\nusing is_usable_as_key_type = typename std::conditional <\n                              is_comparable<Comparator, ObjectKeyType, KeyTypeCVRef>::value\n                              && !(ExcludeObjectKeyType && std::is_same<KeyType,\n                                   ObjectKeyType>::value)\n                              && (!RequireTransparentComparator\n                                  || is_detected <detect_is_transparent, Comparator>::value)\n                              && !is_json_pointer<KeyType>::value,\n                              std::true_type,\n                              std::false_type >::type;\n\n// type trait to check if KeyType can be used as object key\n// true if:\n//   - KeyType is comparable with BasicJsonType::object_t::key_type\n//   - if ExcludeObjectKeyType is true, KeyType is not BasicJsonType::object_t::key_type\n//   - the comparator is transparent or RequireTransparentComparator is false\n//   - KeyType is not a JSON iterator or json_pointer\ntemplate<typename BasicJsonType, typename KeyTypeCVRef, bool RequireTransparentComparator = true,\n         bool ExcludeObjectKeyType = RequireTransparentComparator, typename KeyType = uncvref_t<KeyTypeCVRef>>\nusing is_usable_as_basic_json_key_type = typename std::conditional <\n        is_usable_as_key_type<typename BasicJsonType::object_comparator_t,\n        typename BasicJsonType::object_t::key_type, KeyTypeCVRef,\n        RequireTransparentComparator, ExcludeObjectKeyType>::value\n        && !is_json_iterator_of<BasicJsonType, KeyType>::value,\n        std::true_type,\n        std::false_type >::type;\n\ntemplate<typename ObjectType, typename KeyType>\nusing detect_erase_with_key_type = decltype(std::declval<ObjectType&>().erase(std::declval<KeyType>()));\n\n// type trait to check if object_t has an erase() member functions accepting KeyType\ntemplate<typename BasicJsonType, typename KeyType>\nusing has_erase_with_key_type = typename std::conditional <\n                                is_detected <\n                                detect_erase_with_key_type,\n                                typename BasicJsonType::object_t, KeyType >::value,\n                                std::true_type,\n                                std::false_type >::type;\n\n// a naive helper to check if a type is an ordered_map (exploits the fact that\n// ordered_map inherits capacity() from std::vector)\ntemplate <typename T>\nstruct is_ordered_map\n{\n    using one = char;\n\n    struct two\n    {\n        char x[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n    };\n\n    template <typename C> static one test( decltype(&C::capacity) ) ;\n    template <typename C> static two test(...);\n\n    enum { value = sizeof(test<T>(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n};\n\n// to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324)\ntemplate < typename T, typename U, enable_if_t < !std::is_same<T, U>::value, int > = 0 >\nT conditional_static_cast(U value)\n{\n    return static_cast<T>(value);\n}\n\ntemplate<typename T, typename U, enable_if_t<std::is_same<T, U>::value, int> = 0>\nT conditional_static_cast(U value)\n{\n    return value;\n}\n\ntemplate<typename... Types>\nusing all_integral = conjunction<std::is_integral<Types>...>;\n\ntemplate<typename... Types>\nusing all_signed = conjunction<std::is_signed<Types>...>;\n\ntemplate<typename... Types>\nusing all_unsigned = conjunction<std::is_unsigned<Types>...>;\n\n// there's a disjunction trait in another PR; replace when merged\ntemplate<typename... Types>\nusing same_sign = std::integral_constant < bool,\n      all_signed<Types...>::value || all_unsigned<Types...>::value >;\n\ntemplate<typename OfType, typename T>\nusing never_out_of_range = std::integral_constant < bool,\n      (std::is_signed<OfType>::value && (sizeof(T) < sizeof(OfType)))\n      || (same_sign<OfType, T>::value && sizeof(OfType) == sizeof(T)) >;\n\ntemplate<typename OfType, typename T,\n         bool OfTypeSigned = std::is_signed<OfType>::value,\n         bool TSigned = std::is_signed<T>::value>\nstruct value_in_range_of_impl2;\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, false, false>\n{\n    static constexpr bool test(T val)\n    {\n        using CommonType = typename std::common_type<OfType, T>::type;\n        return static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)());\n    }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, true, false>\n{\n    static constexpr bool test(T val)\n    {\n        using CommonType = typename std::common_type<OfType, T>::type;\n        return static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)());\n    }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, false, true>\n{\n    static constexpr bool test(T val)\n    {\n        using CommonType = typename std::common_type<OfType, T>::type;\n        return val >= 0 && static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)());\n    }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, true, true>\n{\n    static constexpr bool test(T val)\n    {\n        using CommonType = typename std::common_type<OfType, T>::type;\n        return static_cast<CommonType>(val) >= static_cast<CommonType>((std::numeric_limits<OfType>::min)())\n               && static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)());\n    }\n};\n\ntemplate<typename OfType, typename T,\n         bool NeverOutOfRange = never_out_of_range<OfType, T>::value,\n         typename = detail::enable_if_t<all_integral<OfType, T>::value>>\nstruct value_in_range_of_impl1;\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl1<OfType, T, false>\n{\n    static constexpr bool test(T val)\n    {\n        return value_in_range_of_impl2<OfType, T>::test(val);\n    }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl1<OfType, T, true>\n{\n    static constexpr bool test(T /*val*/)\n    {\n        return true;\n    }\n};\n\ntemplate<typename OfType, typename T>\ninline constexpr bool value_in_range_of(T val)\n{\n    return value_in_range_of_impl1<OfType, T>::test(val);\n}\n\ntemplate<bool Value>\nusing bool_constant = std::integral_constant<bool, Value>;\n\n///////////////////////////////////////////////////////////////////////////////\n// is_c_string\n///////////////////////////////////////////////////////////////////////////////\n\nnamespace impl\n{\n\ntemplate<typename T>\ninline constexpr bool is_c_string()\n{\n    using TUnExt = typename std::remove_extent<T>::type;\n    using TUnCVExt = typename std::remove_cv<TUnExt>::type;\n    using TUnPtr = typename std::remove_pointer<T>::type;\n    using TUnCVPtr = typename std::remove_cv<TUnPtr>::type;\n    return\n        (std::is_array<T>::value && std::is_same<TUnCVExt, char>::value)\n        || (std::is_pointer<T>::value && std::is_same<TUnCVPtr, char>::value);\n}\n\n}  // namespace impl\n\n// checks whether T is a [cv] char */[cv] char[] C string\ntemplate<typename T>\nstruct is_c_string : bool_constant<impl::is_c_string<T>()> {};\n\ntemplate<typename T>\nusing is_c_string_uncvref = is_c_string<uncvref_t<T>>;\n\n///////////////////////////////////////////////////////////////////////////////\n// is_transparent\n///////////////////////////////////////////////////////////////////////////////\n\nnamespace impl\n{\n\ntemplate<typename T>\ninline constexpr bool is_transparent()\n{\n    return is_detected<detect_is_transparent, T>::value;\n}\n\n}  // namespace impl\n\n// checks whether T has a member named is_transparent\ntemplate<typename T>\nstruct is_transparent : bool_constant<impl::is_transparent<T>()> {};\n\n///////////////////////////////////////////////////////////////////////////////\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/string_concat.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstring> // strlen\n#include <string> // string\n#include <utility> // forward\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/detected.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ninline std::size_t concat_length()\n{\n    return 0;\n}\n\ntemplate<typename... Args>\ninline std::size_t concat_length(const char* cstr, const Args& ... rest);\n\ntemplate<typename StringType, typename... Args>\ninline std::size_t concat_length(const StringType& str, const Args& ... rest);\n\ntemplate<typename... Args>\ninline std::size_t concat_length(const char /*c*/, const Args& ... rest)\n{\n    return 1 + concat_length(rest...);\n}\n\ntemplate<typename... Args>\ninline std::size_t concat_length(const char* cstr, const Args& ... rest)\n{\n    // cppcheck-suppress ignoredReturnValue\n    return ::strlen(cstr) + concat_length(rest...);\n}\n\ntemplate<typename StringType, typename... Args>\ninline std::size_t concat_length(const StringType& str, const Args& ... rest)\n{\n    return str.size() + concat_length(rest...);\n}\n\ntemplate<typename OutStringType>\ninline void concat_into(OutStringType& /*out*/)\n{}\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append = decltype(std::declval<StringType&>().append(std::declval < Arg && > ()));\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append = is_detected<string_can_append, StringType, Arg>;\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append_op = decltype(std::declval<StringType&>() += std::declval < Arg && > ());\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append_op = is_detected<string_can_append_op, StringType, Arg>;\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append_iter = decltype(std::declval<StringType&>().append(std::declval<const Arg&>().begin(), std::declval<const Arg&>().end()));\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append_iter = is_detected<string_can_append_iter, StringType, Arg>;\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append_data = decltype(std::declval<StringType&>().append(std::declval<const Arg&>().data(), std::declval<const Arg&>().size()));\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append_data = is_detected<string_can_append_data, StringType, Arg>;\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && detect_string_can_append_op<OutStringType, Arg>::value, int > = 0 >\ninline void concat_into(OutStringType& out, Arg && arg, Args && ... rest);\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && detect_string_can_append_iter<OutStringType, Arg>::value, int > = 0 >\ninline void concat_into(OutStringType& out, const Arg& arg, Args && ... rest);\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && !detect_string_can_append_iter<OutStringType, Arg>::value\n                         && detect_string_can_append_data<OutStringType, Arg>::value, int > = 0 >\ninline void concat_into(OutStringType& out, const Arg& arg, Args && ... rest);\n\ntemplate<typename OutStringType, typename Arg, typename... Args,\n         enable_if_t<detect_string_can_append<OutStringType, Arg>::value, int> = 0>\ninline void concat_into(OutStringType& out, Arg && arg, Args && ... rest)\n{\n    out.append(std::forward<Arg>(arg));\n    concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && detect_string_can_append_op<OutStringType, Arg>::value, int > >\ninline void concat_into(OutStringType& out, Arg&& arg, Args&& ... rest)\n{\n    out += std::forward<Arg>(arg);\n    concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && detect_string_can_append_iter<OutStringType, Arg>::value, int > >\ninline void concat_into(OutStringType& out, const Arg& arg, Args&& ... rest)\n{\n    out.append(arg.begin(), arg.end());\n    concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && !detect_string_can_append_iter<OutStringType, Arg>::value\n                         && detect_string_can_append_data<OutStringType, Arg>::value, int > >\ninline void concat_into(OutStringType& out, const Arg& arg, Args&& ... rest)\n{\n    out.append(arg.data(), arg.size());\n    concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate<typename OutStringType = std::string, typename... Args>\ninline OutStringType concat(Args && ... args)\n{\n    OutStringType str;\n    str.reserve(concat_length(args...));\n    concat_into(str, std::forward<Args>(args)...);\n    return str;\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n////////////////\n// exceptions //\n////////////////\n\n/// @brief general exception of the @ref basic_json class\n/// @sa https://json.nlohmann.me/api/basic_json/exception/\nclass exception : public std::exception\n{\n  public:\n    /// returns the explanatory string\n    const char* what() const noexcept override\n    {\n        return m.what();\n    }\n\n    /// the id of the exception\n    const int id; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)\n\n  protected:\n    JSON_HEDLEY_NON_NULL(3)\n    exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} // NOLINT(bugprone-throw-keyword-missing)\n\n    static std::string name(const std::string& ename, int id_)\n    {\n        return concat(\"[json.exception.\", ename, '.', std::to_string(id_), \"] \");\n    }\n\n    static std::string diagnostics(std::nullptr_t /*leaf_element*/)\n    {\n        return \"\";\n    }\n\n    template<typename BasicJsonType>\n    static std::string diagnostics(const BasicJsonType* leaf_element)\n    {\n#if JSON_DIAGNOSTICS\n        std::vector<std::string> tokens;\n        for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)\n        {\n            switch (current->m_parent->type())\n            {\n                case value_t::array:\n                {\n                    for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i)\n                    {\n                        if (&current->m_parent->m_data.m_value.array->operator[](i) == current)\n                        {\n                            tokens.emplace_back(std::to_string(i));\n                            break;\n                        }\n                    }\n                    break;\n                }\n\n                case value_t::object:\n                {\n                    for (const auto& element : *current->m_parent->m_data.m_value.object)\n                    {\n                        if (&element.second == current)\n                        {\n                            tokens.emplace_back(element.first.c_str());\n                            break;\n                        }\n                    }\n                    break;\n                }\n\n                case value_t::null: // LCOV_EXCL_LINE\n                case value_t::string: // LCOV_EXCL_LINE\n                case value_t::boolean: // LCOV_EXCL_LINE\n                case value_t::number_integer: // LCOV_EXCL_LINE\n                case value_t::number_unsigned: // LCOV_EXCL_LINE\n                case value_t::number_float: // LCOV_EXCL_LINE\n                case value_t::binary: // LCOV_EXCL_LINE\n                case value_t::discarded: // LCOV_EXCL_LINE\n                default:   // LCOV_EXCL_LINE\n                    break; // LCOV_EXCL_LINE\n            }\n        }\n\n        if (tokens.empty())\n        {\n            return \"\";\n        }\n\n        auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},\n                                   [](const std::string & a, const std::string & b)\n        {\n            return concat(a, '/', detail::escape(b));\n        });\n        return concat('(', str, \") \");\n#else\n        static_cast<void>(leaf_element);\n        return \"\";\n#endif\n    }\n\n  private:\n    /// an exception object as storage for error messages\n    std::runtime_error m;\n};\n\n/// @brief exception indicating a parse error\n/// @sa https://json.nlohmann.me/api/basic_json/parse_error/\nclass parse_error : public exception\n{\n  public:\n    /*!\n    @brief create a parse error exception\n    @param[in] id_       the id of the exception\n    @param[in] pos       the position where the error occurred (or with\n                         chars_read_total=0 if the position cannot be\n                         determined)\n    @param[in] what_arg  the explanatory string\n    @return parse_error object\n    */\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static parse_error create(int id_, const position_t& pos, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"parse_error\", id_), \"parse error\",\n                                     position_string(pos), \": \", exception::diagnostics(context), what_arg);\n        return {id_, pos.chars_read_total, w.c_str()};\n    }\n\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static parse_error create(int id_, std::size_t byte_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"parse_error\", id_), \"parse error\",\n                                     (byte_ != 0 ? (concat(\" at byte \", std::to_string(byte_))) : \"\"),\n                                     \": \", exception::diagnostics(context), what_arg);\n        return {id_, byte_, w.c_str()};\n    }\n\n    /*!\n    @brief byte index of the parse error\n\n    The byte index of the last read character in the input file.\n\n    @note For an input with n bytes, 1 is the index of the first character and\n          n+1 is the index of the terminating null byte or the end of file.\n          This also holds true when reading a byte vector (CBOR or MessagePack).\n    */\n    const std::size_t byte;\n\n  private:\n    parse_error(int id_, std::size_t byte_, const char* what_arg)\n        : exception(id_, what_arg), byte(byte_) {}\n\n    static std::string position_string(const position_t& pos)\n    {\n        return concat(\" at line \", std::to_string(pos.lines_read + 1),\n                      \", column \", std::to_string(pos.chars_read_current_line));\n    }\n};\n\n/// @brief exception indicating errors with iterators\n/// @sa https://json.nlohmann.me/api/basic_json/invalid_iterator/\nclass invalid_iterator : public exception\n{\n  public:\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"invalid_iterator\", id_), exception::diagnostics(context), what_arg);\n        return {id_, w.c_str()};\n    }\n\n  private:\n    JSON_HEDLEY_NON_NULL(3)\n    invalid_iterator(int id_, const char* what_arg)\n        : exception(id_, what_arg) {}\n};\n\n/// @brief exception indicating executing a member function with a wrong type\n/// @sa https://json.nlohmann.me/api/basic_json/type_error/\nclass type_error : public exception\n{\n  public:\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"type_error\", id_), exception::diagnostics(context), what_arg);\n        return {id_, w.c_str()};\n    }\n\n  private:\n    JSON_HEDLEY_NON_NULL(3)\n    type_error(int id_, const char* what_arg) : exception(id_, what_arg) {}\n};\n\n/// @brief exception indicating access out of the defined range\n/// @sa https://json.nlohmann.me/api/basic_json/out_of_range/\nclass out_of_range : public exception\n{\n  public:\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"out_of_range\", id_), exception::diagnostics(context), what_arg);\n        return {id_, w.c_str()};\n    }\n\n  private:\n    JSON_HEDLEY_NON_NULL(3)\n    out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {}\n};\n\n/// @brief exception indicating other library errors\n/// @sa https://json.nlohmann.me/api/basic_json/other_error/\nclass other_error : public exception\n{\n  public:\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"other_error\", id_), exception::diagnostics(context), what_arg);\n        return {id_, w.c_str()};\n    }\n\n  private:\n    JSON_HEDLEY_NON_NULL(3)\n    other_error(int id_, const char* what_arg) : exception(id_, what_arg) {}\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/identity_tag.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// dispatching helper struct\ntemplate <class T> struct identity_tag {};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/std_fs.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\n#if JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#include <experimental/filesystem>\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\nnamespace std_fs = std::experimental::filesystem;\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n#elif JSON_HAS_FILESYSTEM\n#include <filesystem>\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\nnamespace std_fs = std::filesystem;\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n#endif\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename std::nullptr_t& n)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_null()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be null, but is \", j.type_name()), &j));\n    }\n    n = nullptr;\n}\n\n// overloads for basic_json template parameters\ntemplate < typename BasicJsonType, typename ArithmeticType,\n           enable_if_t < std::is_arithmetic<ArithmeticType>::value&&\n                         !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,\n                         int > = 0 >\nvoid get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)\n{\n    switch (static_cast<value_t>(j))\n    {\n        case value_t::number_unsigned:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());\n            break;\n        }\n        case value_t::number_integer:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());\n            break;\n        }\n        case value_t::number_float:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());\n            break;\n        }\n\n        case value_t::null:\n        case value_t::object:\n        case value_t::array:\n        case value_t::string:\n        case value_t::boolean:\n        case value_t::binary:\n        case value_t::discarded:\n        default:\n            JSON_THROW(type_error::create(302, concat(\"type must be number, but is \", j.type_name()), &j));\n    }\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_boolean()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be boolean, but is \", j.type_name()), &j));\n    }\n    b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_string()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be string, but is \", j.type_name()), &j));\n    }\n    s = *j.template get_ptr<const typename BasicJsonType::string_t*>();\n}\n\ntemplate <\n    typename BasicJsonType, typename StringType,\n    enable_if_t <\n        std::is_assignable<StringType&, const typename BasicJsonType::string_t>::value\n        && is_detected_exact<typename BasicJsonType::string_t::value_type, value_type_t, StringType>::value\n        && !std::is_same<typename BasicJsonType::string_t, StringType>::value\n        && !is_json_ref<StringType>::value, int > = 0 >\ninline void from_json(const BasicJsonType& j, StringType& s)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_string()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be string, but is \", j.type_name()), &j));\n    }\n\n    s = *j.template get_ptr<const typename BasicJsonType::string_t*>();\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val)\n{\n    get_arithmetic_value(j, val);\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)\n{\n    get_arithmetic_value(j, val);\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val)\n{\n    get_arithmetic_value(j, val);\n}\n\n#if !JSON_DISABLE_ENUM_SERIALIZATION\ntemplate<typename BasicJsonType, typename EnumType,\n         enable_if_t<std::is_enum<EnumType>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, EnumType& e)\n{\n    typename std::underlying_type<EnumType>::type val;\n    get_arithmetic_value(j, val);\n    e = static_cast<EnumType>(val);\n}\n#endif  // JSON_DISABLE_ENUM_SERIALIZATION\n\n// forward_list doesn't have an insert method\ntemplate<typename BasicJsonType, typename T, typename Allocator,\n         enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n    l.clear();\n    std::transform(j.rbegin(), j.rend(),\n                   std::front_inserter(l), [](const BasicJsonType & i)\n    {\n        return i.template get<T>();\n    });\n}\n\n// valarray doesn't have an insert method\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, std::valarray<T>& l)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n    l.resize(j.size());\n    std::transform(j.begin(), j.end(), std::begin(l),\n                   [](const BasicJsonType & elem)\n    {\n        return elem.template get<T>();\n    });\n}\n\ntemplate<typename BasicJsonType, typename T, std::size_t N>\nauto from_json(const BasicJsonType& j, T (&arr)[N])  // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n-> decltype(j.template get<T>(), void())\n{\n    for (std::size_t i = 0; i < N; ++i)\n    {\n        arr[i] = j.at(i).template get<T>();\n    }\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/)\n{\n    arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();\n}\n\ntemplate<typename BasicJsonType, typename T, std::size_t N>\nauto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,\n                          priority_tag<2> /*unused*/)\n-> decltype(j.template get<T>(), void())\n{\n    for (std::size_t i = 0; i < N; ++i)\n    {\n        arr[i] = j.at(i).template get<T>();\n    }\n}\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType,\n         enable_if_t<\n             std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,\n             int> = 0>\nauto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/)\n-> decltype(\n    arr.reserve(std::declval<typename ConstructibleArrayType::size_type>()),\n    j.template get<typename ConstructibleArrayType::value_type>(),\n    void())\n{\n    using std::end;\n\n    ConstructibleArrayType ret;\n    ret.reserve(j.size());\n    std::transform(j.begin(), j.end(),\n                   std::inserter(ret, end(ret)), [](const BasicJsonType & i)\n    {\n        // get<BasicJsonType>() returns *this, this won't call a from_json\n        // method when value_type is BasicJsonType\n        return i.template get<typename ConstructibleArrayType::value_type>();\n    });\n    arr = std::move(ret);\n}\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType,\n         enable_if_t<\n             std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,\n             int> = 0>\ninline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr,\n                                 priority_tag<0> /*unused*/)\n{\n    using std::end;\n\n    ConstructibleArrayType ret;\n    std::transform(\n        j.begin(), j.end(), std::inserter(ret, end(ret)),\n        [](const BasicJsonType & i)\n    {\n        // get<BasicJsonType>() returns *this, this won't call a from_json\n        // method when value_type is BasicJsonType\n        return i.template get<typename ConstructibleArrayType::value_type>();\n    });\n    arr = std::move(ret);\n}\n\ntemplate < typename BasicJsonType, typename ConstructibleArrayType,\n           enable_if_t <\n               is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value&&\n               !is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&\n               !is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&\n               !std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&\n               !is_basic_json<ConstructibleArrayType>::value,\n               int > = 0 >\nauto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)\n-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}),\nj.template get<typename ConstructibleArrayType::value_type>(),\nvoid())\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n\n    from_json_array_impl(j, arr, priority_tag<3> {});\n}\n\ntemplate < typename BasicJsonType, typename T, std::size_t... Idx >\nstd::array<T, sizeof...(Idx)> from_json_inplace_array_impl(BasicJsonType&& j,\n        identity_tag<std::array<T, sizeof...(Idx)>> /*unused*/, index_sequence<Idx...> /*unused*/)\n{\n    return { { std::forward<BasicJsonType>(j).at(Idx).template get<T>()... } };\n}\n\ntemplate < typename BasicJsonType, typename T, std::size_t N >\nauto from_json(BasicJsonType&& j, identity_tag<std::array<T, N>> tag)\n-> decltype(from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {}))\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n\n    return from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {});\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_binary()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be binary, but is \", j.type_name()), &j));\n    }\n\n    bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();\n}\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType,\n         enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_object()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be object, but is \", j.type_name()), &j));\n    }\n\n    ConstructibleObjectType ret;\n    const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();\n    using value_type = typename ConstructibleObjectType::value_type;\n    std::transform(\n        inner_object->begin(), inner_object->end(),\n        std::inserter(ret, ret.begin()),\n        [](typename BasicJsonType::object_t::value_type const & p)\n    {\n        return value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());\n    });\n    obj = std::move(ret);\n}\n\n// overload for arithmetic types, not chosen for basic_json template arguments\n// (BooleanType, etc..); note: Is it really necessary to provide explicit\n// overloads for boolean_t etc. in case of a custom BooleanType which is not\n// an arithmetic type?\ntemplate < typename BasicJsonType, typename ArithmeticType,\n           enable_if_t <\n               std::is_arithmetic<ArithmeticType>::value&&\n               !std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value&&\n               !std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value&&\n               !std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value&&\n               !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,\n               int > = 0 >\ninline void from_json(const BasicJsonType& j, ArithmeticType& val)\n{\n    switch (static_cast<value_t>(j))\n    {\n        case value_t::number_unsigned:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());\n            break;\n        }\n        case value_t::number_integer:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());\n            break;\n        }\n        case value_t::number_float:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());\n            break;\n        }\n        case value_t::boolean:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());\n            break;\n        }\n\n        case value_t::null:\n        case value_t::object:\n        case value_t::array:\n        case value_t::string:\n        case value_t::binary:\n        case value_t::discarded:\n        default:\n            JSON_THROW(type_error::create(302, concat(\"type must be number, but is \", j.type_name()), &j));\n    }\n}\n\ntemplate<typename BasicJsonType, typename... Args, std::size_t... Idx>\nstd::tuple<Args...> from_json_tuple_impl_base(BasicJsonType&& j, index_sequence<Idx...> /*unused*/)\n{\n    return std::make_tuple(std::forward<BasicJsonType>(j).at(Idx).template get<Args>()...);\n}\n\ntemplate < typename BasicJsonType, class A1, class A2 >\nstd::pair<A1, A2> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::pair<A1, A2>> /*unused*/, priority_tag<0> /*unused*/)\n{\n    return {std::forward<BasicJsonType>(j).at(0).template get<A1>(),\n            std::forward<BasicJsonType>(j).at(1).template get<A2>()};\n}\n\ntemplate<typename BasicJsonType, typename A1, typename A2>\ninline void from_json_tuple_impl(BasicJsonType&& j, std::pair<A1, A2>& p, priority_tag<1> /*unused*/)\n{\n    p = from_json_tuple_impl(std::forward<BasicJsonType>(j), identity_tag<std::pair<A1, A2>> {}, priority_tag<0> {});\n}\n\ntemplate<typename BasicJsonType, typename... Args>\nstd::tuple<Args...> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::tuple<Args...>> /*unused*/, priority_tag<2> /*unused*/)\n{\n    return from_json_tuple_impl_base<BasicJsonType, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});\n}\n\ntemplate<typename BasicJsonType, typename... Args>\ninline void from_json_tuple_impl(BasicJsonType&& j, std::tuple<Args...>& t, priority_tag<3> /*unused*/)\n{\n    t = from_json_tuple_impl_base<BasicJsonType, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});\n}\n\ntemplate<typename BasicJsonType, typename TupleRelated>\nauto from_json(BasicJsonType&& j, TupleRelated&& t)\n-> decltype(from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {}))\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n\n    return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {});\n}\n\ntemplate < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,\n           typename = enable_if_t < !std::is_constructible <\n                                        typename BasicJsonType::string_t, Key >::value >>\ninline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n    m.clear();\n    for (const auto& p : j)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!p.is_array()))\n        {\n            JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", p.type_name()), &j));\n        }\n        m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());\n    }\n}\n\ntemplate < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator,\n           typename = enable_if_t < !std::is_constructible <\n                                        typename BasicJsonType::string_t, Key >::value >>\ninline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n    m.clear();\n    for (const auto& p : j)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!p.is_array()))\n        {\n            JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", p.type_name()), &j));\n        }\n        m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());\n    }\n}\n\n#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, std_fs::path& p)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_string()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be string, but is \", j.type_name()), &j));\n    }\n    p = *j.template get_ptr<const typename BasicJsonType::string_t*>();\n}\n#endif\n\nstruct from_json_fn\n{\n    template<typename BasicJsonType, typename T>\n    auto operator()(const BasicJsonType& j, T&& val) const\n    noexcept(noexcept(from_json(j, std::forward<T>(val))))\n    -> decltype(from_json(j, std::forward<T>(val)))\n    {\n        return from_json(j, std::forward<T>(val));\n    }\n};\n\n}  // namespace detail\n\n#ifndef JSON_HAS_CPP_17\n/// namespace to hold default `from_json` function\n/// to see why this is required:\n/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html\nnamespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces)\n{\n#endif\nJSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers)\n    detail::static_const<detail::from_json_fn>::value;\n#ifndef JSON_HAS_CPP_17\n}  // namespace\n#endif\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/conversions/to_json.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // copy\n#include <iterator> // begin, end\n#include <string> // string\n#include <tuple> // tuple, get\n#include <type_traits> // is_same, is_constructible, is_floating_point, is_enum, underlying_type\n#include <utility> // move, forward, declval, pair\n#include <valarray> // valarray\n#include <vector> // vector\n\n// #include <nlohmann/detail/iterators/iteration_proxy.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // size_t\n#include <iterator> // input_iterator_tag\n#include <string> // string, to_string\n#include <tuple> // tuple_size, get, tuple_element\n#include <utility> // move\n\n#if JSON_HAS_RANGES\n    #include <ranges> // enable_borrowed_range\n#endif\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename string_type>\nvoid int_to_string( string_type& target, std::size_t value )\n{\n    // For ADL\n    using std::to_string;\n    target = to_string(value);\n}\ntemplate<typename IteratorType> class iteration_proxy_value\n{\n  public:\n    using difference_type = std::ptrdiff_t;\n    using value_type = iteration_proxy_value;\n    using pointer = value_type *;\n    using reference = value_type &;\n    using iterator_category = std::input_iterator_tag;\n    using string_type = typename std::remove_cv< typename std::remove_reference<decltype( std::declval<IteratorType>().key() ) >::type >::type;\n\n  private:\n    /// the iterator\n    IteratorType anchor{};\n    /// an index for arrays (used to create key names)\n    std::size_t array_index = 0;\n    /// last stringified array index\n    mutable std::size_t array_index_last = 0;\n    /// a string representation of the array index\n    mutable string_type array_index_str = \"0\";\n    /// an empty string (to return a reference for primitive values)\n    string_type empty_str{};\n\n  public:\n    explicit iteration_proxy_value() = default;\n    explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0)\n    noexcept(std::is_nothrow_move_constructible<IteratorType>::value\n             && std::is_nothrow_default_constructible<string_type>::value)\n        : anchor(std::move(it))\n        , array_index(array_index_)\n    {}\n\n    iteration_proxy_value(iteration_proxy_value const&) = default;\n    iteration_proxy_value& operator=(iteration_proxy_value const&) = default;\n    // older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions\n    iteration_proxy_value(iteration_proxy_value&&)\n    noexcept(std::is_nothrow_move_constructible<IteratorType>::value\n             && std::is_nothrow_move_constructible<string_type>::value) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)\n    iteration_proxy_value& operator=(iteration_proxy_value&&)\n    noexcept(std::is_nothrow_move_assignable<IteratorType>::value\n             && std::is_nothrow_move_assignable<string_type>::value) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)\n    ~iteration_proxy_value() = default;\n\n    /// dereference operator (needed for range-based for)\n    const iteration_proxy_value& operator*() const\n    {\n        return *this;\n    }\n\n    /// increment operator (needed for range-based for)\n    iteration_proxy_value& operator++()\n    {\n        ++anchor;\n        ++array_index;\n\n        return *this;\n    }\n\n    iteration_proxy_value operator++(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        auto tmp = iteration_proxy_value(anchor, array_index);\n        ++anchor;\n        ++array_index;\n        return tmp;\n    }\n\n    /// equality operator (needed for InputIterator)\n    bool operator==(const iteration_proxy_value& o) const\n    {\n        return anchor == o.anchor;\n    }\n\n    /// inequality operator (needed for range-based for)\n    bool operator!=(const iteration_proxy_value& o) const\n    {\n        return anchor != o.anchor;\n    }\n\n    /// return key of the iterator\n    const string_type& key() const\n    {\n        JSON_ASSERT(anchor.m_object != nullptr);\n\n        switch (anchor.m_object->type())\n        {\n            // use integer array index as key\n            case value_t::array:\n            {\n                if (array_index != array_index_last)\n                {\n                    int_to_string( array_index_str, array_index );\n                    array_index_last = array_index;\n                }\n                return array_index_str;\n            }\n\n            // use key from the object\n            case value_t::object:\n                return anchor.key();\n\n            // use an empty key for all primitive types\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                return empty_str;\n        }\n    }\n\n    /// return value of the iterator\n    typename IteratorType::reference value() const\n    {\n        return anchor.value();\n    }\n};\n\n/// proxy class for the items() function\ntemplate<typename IteratorType> class iteration_proxy\n{\n  private:\n    /// the container to iterate\n    typename IteratorType::pointer container = nullptr;\n\n  public:\n    explicit iteration_proxy() = default;\n\n    /// construct iteration proxy from a container\n    explicit iteration_proxy(typename IteratorType::reference cont) noexcept\n        : container(&cont) {}\n\n    iteration_proxy(iteration_proxy const&) = default;\n    iteration_proxy& operator=(iteration_proxy const&) = default;\n    iteration_proxy(iteration_proxy&&) noexcept = default;\n    iteration_proxy& operator=(iteration_proxy&&) noexcept = default;\n    ~iteration_proxy() = default;\n\n    /// return iterator begin (needed for range-based for)\n    iteration_proxy_value<IteratorType> begin() const noexcept\n    {\n        return iteration_proxy_value<IteratorType>(container->begin());\n    }\n\n    /// return iterator end (needed for range-based for)\n    iteration_proxy_value<IteratorType> end() const noexcept\n    {\n        return iteration_proxy_value<IteratorType>(container->end());\n    }\n};\n\n// Structured Bindings Support\n// For further reference see https://blog.tartanllama.xyz/structured-bindings/\n// And see https://github.com/nlohmann/json/pull/1391\ntemplate<std::size_t N, typename IteratorType, enable_if_t<N == 0, int> = 0>\nauto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) -> decltype(i.key())\n{\n    return i.key();\n}\n// Structured Bindings Support\n// For further reference see https://blog.tartanllama.xyz/structured-bindings/\n// And see https://github.com/nlohmann/json/pull/1391\ntemplate<std::size_t N, typename IteratorType, enable_if_t<N == 1, int> = 0>\nauto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) -> decltype(i.value())\n{\n    return i.value();\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// The Addition to the STD Namespace is required to add\n// Structured Bindings Support to the iteration_proxy_value class\n// For further reference see https://blog.tartanllama.xyz/structured-bindings/\n// And see https://github.com/nlohmann/json/pull/1391\nnamespace std\n{\n\n#if defined(__clang__)\n    // Fix: https://github.com/nlohmann/json/issues/1401\n    #pragma clang diagnostic push\n    #pragma clang diagnostic ignored \"-Wmismatched-tags\"\n#endif\ntemplate<typename IteratorType>\nclass tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)\n            : public std::integral_constant<std::size_t, 2> {};\n\ntemplate<std::size_t N, typename IteratorType>\nclass tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >> // NOLINT(cert-dcl58-cpp)\n{\n  public:\n    using type = decltype(\n                     get<N>(std::declval <\n                            ::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));\n};\n#if defined(__clang__)\n    #pragma clang diagnostic pop\n#endif\n\n}  // namespace std\n\n#if JSON_HAS_RANGES\n    template <typename IteratorType>\n    inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy<IteratorType>> = true;\n#endif\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/std_fs.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n//////////////////\n// constructors //\n//////////////////\n\n/*\n * Note all external_constructor<>::construct functions need to call\n * j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an\n * allocated value (e.g., a string). See bug issue\n * https://github.com/nlohmann/json/issues/2865 for more information.\n */\n\ntemplate<value_t> struct external_constructor;\n\ntemplate<>\nstruct external_constructor<value_t::boolean>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::boolean;\n        j.m_data.m_value = b;\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::string>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::string;\n        j.m_data.m_value = s;\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::string;\n        j.m_data.m_value = std::move(s);\n        j.assert_invariant();\n    }\n\n    template < typename BasicJsonType, typename CompatibleStringType,\n               enable_if_t < !std::is_same<CompatibleStringType, typename BasicJsonType::string_t>::value,\n                             int > = 0 >\n    static void construct(BasicJsonType& j, const CompatibleStringType& str)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::string;\n        j.m_data.m_value.string = j.template create<typename BasicJsonType::string_t>(str);\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::binary>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::binary;\n        j.m_data.m_value = typename BasicJsonType::binary_t(b);\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::binary;\n        j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b));\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::number_float>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::number_float;\n        j.m_data.m_value = val;\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::number_unsigned>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::number_unsigned;\n        j.m_data.m_value = val;\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::number_integer>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::number_integer;\n        j.m_data.m_value = val;\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::array>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value = arr;\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value = std::move(arr);\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template < typename BasicJsonType, typename CompatibleArrayType,\n               enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,\n                             int > = 0 >\n    static void construct(BasicJsonType& j, const CompatibleArrayType& arr)\n    {\n        using std::begin;\n        using std::end;\n\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const std::vector<bool>& arr)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value = value_t::array;\n        j.m_data.m_value.array->reserve(arr.size());\n        for (const bool x : arr)\n        {\n            j.m_data.m_value.array->push_back(x);\n            j.set_parent(j.m_data.m_value.array->back());\n        }\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType, typename T,\n             enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>\n    static void construct(BasicJsonType& j, const std::valarray<T>& arr)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value = value_t::array;\n        j.m_data.m_value.array->resize(arr.size());\n        if (arr.size() > 0)\n        {\n            std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin());\n        }\n        j.set_parents();\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::object>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::object;\n        j.m_data.m_value = obj;\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::object;\n        j.m_data.m_value = std::move(obj);\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template < typename BasicJsonType, typename CompatibleObjectType,\n               enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >\n    static void construct(BasicJsonType& j, const CompatibleObjectType& obj)\n    {\n        using std::begin;\n        using std::end;\n\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::object;\n        j.m_data.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));\n        j.set_parents();\n        j.assert_invariant();\n    }\n};\n\n/////////////\n// to_json //\n/////////////\n\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0>\ninline void to_json(BasicJsonType& j, T b) noexcept\n{\n    external_constructor<value_t::boolean>::construct(j, b);\n}\n\ntemplate < typename BasicJsonType, typename BoolRef,\n           enable_if_t <\n               ((std::is_same<std::vector<bool>::reference, BoolRef>::value\n                 && !std::is_same <std::vector<bool>::reference, typename BasicJsonType::boolean_t&>::value)\n                || (std::is_same<std::vector<bool>::const_reference, BoolRef>::value\n                    && !std::is_same <detail::uncvref_t<std::vector<bool>::const_reference>,\n                                      typename BasicJsonType::boolean_t >::value))\n               && std::is_convertible<const BoolRef&, typename BasicJsonType::boolean_t>::value, int > = 0 >\ninline void to_json(BasicJsonType& j, const BoolRef& b) noexcept\n{\n    external_constructor<value_t::boolean>::construct(j, static_cast<typename BasicJsonType::boolean_t>(b));\n}\n\ntemplate<typename BasicJsonType, typename CompatibleString,\n         enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0>\ninline void to_json(BasicJsonType& j, const CompatibleString& s)\n{\n    external_constructor<value_t::string>::construct(j, s);\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s)\n{\n    external_constructor<value_t::string>::construct(j, std::move(s));\n}\n\ntemplate<typename BasicJsonType, typename FloatType,\n         enable_if_t<std::is_floating_point<FloatType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, FloatType val) noexcept\n{\n    external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val));\n}\n\ntemplate<typename BasicJsonType, typename CompatibleNumberUnsignedType,\n         enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept\n{\n    external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));\n}\n\ntemplate<typename BasicJsonType, typename CompatibleNumberIntegerType,\n         enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept\n{\n    external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));\n}\n\n#if !JSON_DISABLE_ENUM_SERIALIZATION\ntemplate<typename BasicJsonType, typename EnumType,\n         enable_if_t<std::is_enum<EnumType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, EnumType e) noexcept\n{\n    using underlying_type = typename std::underlying_type<EnumType>::type;\n    external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e));\n}\n#endif  // JSON_DISABLE_ENUM_SERIALIZATION\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, const std::vector<bool>& e)\n{\n    external_constructor<value_t::array>::construct(j, e);\n}\n\ntemplate < typename BasicJsonType, typename CompatibleArrayType,\n           enable_if_t < is_compatible_array_type<BasicJsonType,\n                         CompatibleArrayType>::value&&\n                         !is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&\n                         !is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&\n                         !std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&\n                         !is_basic_json<CompatibleArrayType>::value,\n                         int > = 0 >\ninline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)\n{\n    external_constructor<value_t::array>::construct(j, arr);\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin)\n{\n    external_constructor<value_t::binary>::construct(j, bin);\n}\n\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, const std::valarray<T>& arr)\n{\n    external_constructor<value_t::array>::construct(j, std::move(arr));\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr)\n{\n    external_constructor<value_t::array>::construct(j, std::move(arr));\n}\n\ntemplate < typename BasicJsonType, typename CompatibleObjectType,\n           enable_if_t < is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value&& !is_basic_json<CompatibleObjectType>::value, int > = 0 >\ninline void to_json(BasicJsonType& j, const CompatibleObjectType& obj)\n{\n    external_constructor<value_t::object>::construct(j, obj);\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)\n{\n    external_constructor<value_t::object>::construct(j, std::move(obj));\n}\n\ntemplate <\n    typename BasicJsonType, typename T, std::size_t N,\n    enable_if_t < !std::is_constructible<typename BasicJsonType::string_t,\n                  const T(&)[N]>::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n                  int > = 0 >\ninline void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n{\n    external_constructor<value_t::array>::construct(j, arr);\n}\n\ntemplate < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible<BasicJsonType, T1>::value&& std::is_constructible<BasicJsonType, T2>::value, int > = 0 >\ninline void to_json(BasicJsonType& j, const std::pair<T1, T2>& p)\n{\n    j = { p.first, p.second };\n}\n\n// for https://github.com/nlohmann/json/pull/1134\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_same<T, iteration_proxy_value<typename BasicJsonType::iterator>>::value, int> = 0>\ninline void to_json(BasicJsonType& j, const T& b)\n{\n    j = { {b.key(), b.value()} };\n}\n\ntemplate<typename BasicJsonType, typename Tuple, std::size_t... Idx>\ninline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...> /*unused*/)\n{\n    j = { std::get<Idx>(t)... };\n}\n\ntemplate<typename BasicJsonType, typename T, enable_if_t<is_constructible_tuple<BasicJsonType, T>::value, int > = 0>\ninline void to_json(BasicJsonType& j, const T& t)\n{\n    to_json_tuple_impl(j, t, make_index_sequence<std::tuple_size<T>::value> {});\n}\n\n#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, const std_fs::path& p)\n{\n    j = p.string();\n}\n#endif\n\nstruct to_json_fn\n{\n    template<typename BasicJsonType, typename T>\n    auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward<T>(val))))\n    -> decltype(to_json(j, std::forward<T>(val)), void())\n    {\n        return to_json(j, std::forward<T>(val));\n    }\n};\n}  // namespace detail\n\n#ifndef JSON_HAS_CPP_17\n/// namespace to hold default `to_json` function\n/// to see why this is required:\n/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html\nnamespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces)\n{\n#endif\nJSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers)\n    detail::static_const<detail::to_json_fn>::value;\n#ifndef JSON_HAS_CPP_17\n}  // namespace\n#endif\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/identity_tag.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// @sa https://json.nlohmann.me/api/adl_serializer/\ntemplate<typename ValueType, typename>\nstruct adl_serializer\n{\n    /// @brief convert a JSON value to any value type\n    /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/\n    template<typename BasicJsonType, typename TargetType = ValueType>\n    static auto from_json(BasicJsonType && j, TargetType& val) noexcept(\n        noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))\n    -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void())\n    {\n        ::nlohmann::from_json(std::forward<BasicJsonType>(j), val);\n    }\n\n    /// @brief convert a JSON value to any value type\n    /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/\n    template<typename BasicJsonType, typename TargetType = ValueType>\n    static auto from_json(BasicJsonType && j) noexcept(\n    noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {})))\n    -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {}))\n    {\n        return ::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {});\n    }\n\n    /// @brief convert any value type to a JSON value\n    /// @sa https://json.nlohmann.me/api/adl_serializer/to_json/\n    template<typename BasicJsonType, typename TargetType = ValueType>\n    static auto to_json(BasicJsonType& j, TargetType && val) noexcept(\n        noexcept(::nlohmann::to_json(j, std::forward<TargetType>(val))))\n    -> decltype(::nlohmann::to_json(j, std::forward<TargetType>(val)), void())\n    {\n        ::nlohmann::to_json(j, std::forward<TargetType>(val));\n    }\n};\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/byte_container_with_subtype.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstdint> // uint8_t, uint64_t\n#include <tuple> // tie\n#include <utility> // move\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// @brief an internal type for a backed binary type\n/// @sa https://json.nlohmann.me/api/byte_container_with_subtype/\ntemplate<typename BinaryType>\nclass byte_container_with_subtype : public BinaryType\n{\n  public:\n    using container_type = BinaryType;\n    using subtype_type = std::uint64_t;\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype() noexcept(noexcept(container_type()))\n        : container_type()\n    {}\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b)))\n        : container_type(b)\n    {}\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b))))\n        : container_type(std::move(b))\n    {}\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype(const container_type& b, subtype_type subtype_) noexcept(noexcept(container_type(b)))\n        : container_type(b)\n        , m_subtype(subtype_)\n        , m_has_subtype(true)\n    {}\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype(container_type&& b, subtype_type subtype_) noexcept(noexcept(container_type(std::move(b))))\n        : container_type(std::move(b))\n        , m_subtype(subtype_)\n        , m_has_subtype(true)\n    {}\n\n    bool operator==(const byte_container_with_subtype& rhs) const\n    {\n        return std::tie(static_cast<const BinaryType&>(*this), m_subtype, m_has_subtype) ==\n               std::tie(static_cast<const BinaryType&>(rhs), rhs.m_subtype, rhs.m_has_subtype);\n    }\n\n    bool operator!=(const byte_container_with_subtype& rhs) const\n    {\n        return !(rhs == *this);\n    }\n\n    /// @brief sets the binary subtype\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/set_subtype/\n    void set_subtype(subtype_type subtype_) noexcept\n    {\n        m_subtype = subtype_;\n        m_has_subtype = true;\n    }\n\n    /// @brief return the binary subtype\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/subtype/\n    constexpr subtype_type subtype() const noexcept\n    {\n        return m_has_subtype ? m_subtype : static_cast<subtype_type>(-1);\n    }\n\n    /// @brief return whether the value has a subtype\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/has_subtype/\n    constexpr bool has_subtype() const noexcept\n    {\n        return m_has_subtype;\n    }\n\n    /// @brief clears the binary subtype\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/clear_subtype/\n    void clear_subtype() noexcept\n    {\n        m_subtype = 0;\n        m_has_subtype = false;\n    }\n\n  private:\n    subtype_type m_subtype = 0;\n    bool m_has_subtype = false;\n};\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/conversions/from_json.hpp>\n\n// #include <nlohmann/detail/conversions/to_json.hpp>\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/hash.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstdint> // uint8_t\n#include <cstddef> // size_t\n#include <functional> // hash\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// boost::hash_combine\ninline std::size_t combine(std::size_t seed, std::size_t h) noexcept\n{\n    seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U);\n    return seed;\n}\n\n/*!\n@brief hash a JSON value\n\nThe hash function tries to rely on std::hash where possible. Furthermore, the\ntype of the JSON value is taken into account to have different hash values for\nnull, 0, 0U, and false, etc.\n\n@tparam BasicJsonType basic_json specialization\n@param j JSON value to hash\n@return hash value of j\n*/\ntemplate<typename BasicJsonType>\nstd::size_t hash(const BasicJsonType& j)\n{\n    using string_t = typename BasicJsonType::string_t;\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n\n    const auto type = static_cast<std::size_t>(j.type());\n    switch (j.type())\n    {\n        case BasicJsonType::value_t::null:\n        case BasicJsonType::value_t::discarded:\n        {\n            return combine(type, 0);\n        }\n\n        case BasicJsonType::value_t::object:\n        {\n            auto seed = combine(type, j.size());\n            for (const auto& element : j.items())\n            {\n                const auto h = std::hash<string_t> {}(element.key());\n                seed = combine(seed, h);\n                seed = combine(seed, hash(element.value()));\n            }\n            return seed;\n        }\n\n        case BasicJsonType::value_t::array:\n        {\n            auto seed = combine(type, j.size());\n            for (const auto& element : j)\n            {\n                seed = combine(seed, hash(element));\n            }\n            return seed;\n        }\n\n        case BasicJsonType::value_t::string:\n        {\n            const auto h = std::hash<string_t> {}(j.template get_ref<const string_t&>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::boolean:\n        {\n            const auto h = std::hash<bool> {}(j.template get<bool>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::number_integer:\n        {\n            const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::number_unsigned:\n        {\n            const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::number_float:\n        {\n            const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::binary:\n        {\n            auto seed = combine(type, j.get_binary().size());\n            const auto h = std::hash<bool> {}(j.get_binary().has_subtype());\n            seed = combine(seed, h);\n            seed = combine(seed, static_cast<std::size_t>(j.get_binary().subtype()));\n            for (const auto byte : j.get_binary())\n            {\n                seed = combine(seed, std::hash<std::uint8_t> {}(byte));\n            }\n            return seed;\n        }\n\n        default:                   // LCOV_EXCL_LINE\n            JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n            return 0;              // LCOV_EXCL_LINE\n    }\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/binary_reader.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // generate_n\n#include <array> // array\n#include <cmath> // ldexp\n#include <cstddef> // size_t\n#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t\n#include <cstdio> // snprintf\n#include <cstring> // memcpy\n#include <iterator> // back_inserter\n#include <limits> // numeric_limits\n#include <string> // char_traits, string\n#include <utility> // make_pair, move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cstddef> // size_t\n#include <cstring> // strlen\n#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next\n#include <memory> // shared_ptr, make_shared, addressof\n#include <numeric> // accumulate\n#include <string> // string, char_traits\n#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer\n#include <utility> // pair, declval\n\n#ifndef JSON_NO_IO\n    #include <cstdio>   // FILE *\n    #include <istream>  // istream\n#endif                  // JSON_NO_IO\n\n// #include <nlohmann/detail/iterators/iterator_traits.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// the supported input formats\nenum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata };\n\n////////////////////\n// input adapters //\n////////////////////\n\n#ifndef JSON_NO_IO\n/*!\nInput adapter for stdio file access. This adapter read only 1 byte and do not use any\n buffer. This adapter is a very low level adapter.\n*/\nclass file_input_adapter\n{\n  public:\n    using char_type = char;\n\n    JSON_HEDLEY_NON_NULL(2)\n    explicit file_input_adapter(std::FILE* f) noexcept\n        : m_file(f)\n    {\n        JSON_ASSERT(m_file != nullptr);\n    }\n\n    // make class move-only\n    file_input_adapter(const file_input_adapter&) = delete;\n    file_input_adapter(file_input_adapter&&) noexcept = default;\n    file_input_adapter& operator=(const file_input_adapter&) = delete;\n    file_input_adapter& operator=(file_input_adapter&&) = delete;\n    ~file_input_adapter() = default;\n\n    std::char_traits<char>::int_type get_character() noexcept\n    {\n        return std::fgetc(m_file);\n    }\n\n  private:\n    /// the file pointer to read from\n    std::FILE* m_file;\n};\n\n/*!\nInput adapter for a (caching) istream. Ignores a UFT Byte Order Mark at\nbeginning of input. Does not support changing the underlying std::streambuf\nin mid-input. Maintains underlying std::istream and std::streambuf to support\nsubsequent use of standard std::istream operations to process any input\ncharacters following those used in parsing the JSON input.  Clears the\nstd::istream flags; any input errors (e.g., EOF) will be detected by the first\nsubsequent call for input from the std::istream.\n*/\nclass input_stream_adapter\n{\n  public:\n    using char_type = char;\n\n    ~input_stream_adapter()\n    {\n        // clear stream flags; we use underlying streambuf I/O, do not\n        // maintain ifstream flags, except eof\n        if (is != nullptr)\n        {\n            is->clear(is->rdstate() & std::ios::eofbit);\n        }\n    }\n\n    explicit input_stream_adapter(std::istream& i)\n        : is(&i), sb(i.rdbuf())\n    {}\n\n    // delete because of pointer members\n    input_stream_adapter(const input_stream_adapter&) = delete;\n    input_stream_adapter& operator=(input_stream_adapter&) = delete;\n    input_stream_adapter& operator=(input_stream_adapter&&) = delete;\n\n    input_stream_adapter(input_stream_adapter&& rhs) noexcept\n        : is(rhs.is), sb(rhs.sb)\n    {\n        rhs.is = nullptr;\n        rhs.sb = nullptr;\n    }\n\n    // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to\n    // ensure that std::char_traits<char>::eof() and the character 0xFF do not\n    // end up as the same value, e.g. 0xFFFFFFFF.\n    std::char_traits<char>::int_type get_character()\n    {\n        auto res = sb->sbumpc();\n        // set eof manually, as we don't use the istream interface.\n        if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))\n        {\n            is->clear(is->rdstate() | std::ios::eofbit);\n        }\n        return res;\n    }\n\n  private:\n    /// the associated input stream\n    std::istream* is = nullptr;\n    std::streambuf* sb = nullptr;\n};\n#endif  // JSON_NO_IO\n\n// General-purpose iterator-based adapter. It might not be as fast as\n// theoretically possible for some containers, but it is extremely versatile.\ntemplate<typename IteratorType>\nclass iterator_input_adapter\n{\n  public:\n    using char_type = typename std::iterator_traits<IteratorType>::value_type;\n\n    iterator_input_adapter(IteratorType first, IteratorType last)\n        : current(std::move(first)), end(std::move(last))\n    {}\n\n    typename char_traits<char_type>::int_type get_character()\n    {\n        if (JSON_HEDLEY_LIKELY(current != end))\n        {\n            auto result = char_traits<char_type>::to_int_type(*current);\n            std::advance(current, 1);\n            return result;\n        }\n\n        return char_traits<char_type>::eof();\n    }\n\n  private:\n    IteratorType current;\n    IteratorType end;\n\n    template<typename BaseInputAdapter, size_t T>\n    friend struct wide_string_input_helper;\n\n    bool empty() const\n    {\n        return current == end;\n    }\n};\n\ntemplate<typename BaseInputAdapter, size_t T>\nstruct wide_string_input_helper;\n\ntemplate<typename BaseInputAdapter>\nstruct wide_string_input_helper<BaseInputAdapter, 4>\n{\n    // UTF-32\n    static void fill_buffer(BaseInputAdapter& input,\n                            std::array<std::char_traits<char>::int_type, 4>& utf8_bytes,\n                            size_t& utf8_bytes_index,\n                            size_t& utf8_bytes_filled)\n    {\n        utf8_bytes_index = 0;\n\n        if (JSON_HEDLEY_UNLIKELY(input.empty()))\n        {\n            utf8_bytes[0] = std::char_traits<char>::eof();\n            utf8_bytes_filled = 1;\n        }\n        else\n        {\n            // get the current character\n            const auto wc = input.get_character();\n\n            // UTF-32 to UTF-8 encoding\n            if (wc < 0x80)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n                utf8_bytes_filled = 1;\n            }\n            else if (wc <= 0x7FF)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u) & 0x1Fu));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 2;\n            }\n            else if (wc <= 0xFFFF)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u) & 0x0Fu));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));\n                utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 3;\n            }\n            else if (wc <= 0x10FFFF)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | ((static_cast<unsigned int>(wc) >> 18u) & 0x07u));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 12u) & 0x3Fu));\n                utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));\n                utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 4;\n            }\n            else\n            {\n                // unknown character\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n                utf8_bytes_filled = 1;\n            }\n        }\n    }\n};\n\ntemplate<typename BaseInputAdapter>\nstruct wide_string_input_helper<BaseInputAdapter, 2>\n{\n    // UTF-16\n    static void fill_buffer(BaseInputAdapter& input,\n                            std::array<std::char_traits<char>::int_type, 4>& utf8_bytes,\n                            size_t& utf8_bytes_index,\n                            size_t& utf8_bytes_filled)\n    {\n        utf8_bytes_index = 0;\n\n        if (JSON_HEDLEY_UNLIKELY(input.empty()))\n        {\n            utf8_bytes[0] = std::char_traits<char>::eof();\n            utf8_bytes_filled = 1;\n        }\n        else\n        {\n            // get the current character\n            const auto wc = input.get_character();\n\n            // UTF-16 to UTF-8 encoding\n            if (wc < 0x80)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n                utf8_bytes_filled = 1;\n            }\n            else if (wc <= 0x7FF)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u)));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 2;\n            }\n            else if (0xD800 > wc || wc >= 0xE000)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u)));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));\n                utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 3;\n            }\n            else\n            {\n                if (JSON_HEDLEY_UNLIKELY(!input.empty()))\n                {\n                    const auto wc2 = static_cast<unsigned int>(input.get_character());\n                    const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));\n                    utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u));\n                    utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));\n                    utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));\n                    utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));\n                    utf8_bytes_filled = 4;\n                }\n                else\n                {\n                    utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n                    utf8_bytes_filled = 1;\n                }\n            }\n        }\n    }\n};\n\n// Wraps another input adapter to convert wide character types into individual bytes.\ntemplate<typename BaseInputAdapter, typename WideCharType>\nclass wide_string_input_adapter\n{\n  public:\n    using char_type = char;\n\n    wide_string_input_adapter(BaseInputAdapter base)\n        : base_adapter(base) {}\n\n    typename std::char_traits<char>::int_type get_character() noexcept\n    {\n        // check if buffer needs to be filled\n        if (utf8_bytes_index == utf8_bytes_filled)\n        {\n            fill_buffer<sizeof(WideCharType)>();\n\n            JSON_ASSERT(utf8_bytes_filled > 0);\n            JSON_ASSERT(utf8_bytes_index == 0);\n        }\n\n        // use buffer\n        JSON_ASSERT(utf8_bytes_filled > 0);\n        JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled);\n        return utf8_bytes[utf8_bytes_index++];\n    }\n\n  private:\n    BaseInputAdapter base_adapter;\n\n    template<size_t T>\n    void fill_buffer()\n    {\n        wide_string_input_helper<BaseInputAdapter, T>::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled);\n    }\n\n    /// a buffer for UTF-8 bytes\n    std::array<std::char_traits<char>::int_type, 4> utf8_bytes = {{0, 0, 0, 0}};\n\n    /// index to the utf8_codes array for the next valid byte\n    std::size_t utf8_bytes_index = 0;\n    /// number of valid bytes in the utf8_codes array\n    std::size_t utf8_bytes_filled = 0;\n};\n\ntemplate<typename IteratorType, typename Enable = void>\nstruct iterator_input_adapter_factory\n{\n    using iterator_type = IteratorType;\n    using char_type = typename std::iterator_traits<iterator_type>::value_type;\n    using adapter_type = iterator_input_adapter<iterator_type>;\n\n    static adapter_type create(IteratorType first, IteratorType last)\n    {\n        return adapter_type(std::move(first), std::move(last));\n    }\n};\n\ntemplate<typename T>\nstruct is_iterator_of_multibyte\n{\n    using value_type = typename std::iterator_traits<T>::value_type;\n    enum\n    {\n        value = sizeof(value_type) > 1\n    };\n};\n\ntemplate<typename IteratorType>\nstruct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>\n{\n    using iterator_type = IteratorType;\n    using char_type = typename std::iterator_traits<iterator_type>::value_type;\n    using base_adapter_type = iterator_input_adapter<iterator_type>;\n    using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>;\n\n    static adapter_type create(IteratorType first, IteratorType last)\n    {\n        return adapter_type(base_adapter_type(std::move(first), std::move(last)));\n    }\n};\n\n// General purpose iterator-based input\ntemplate<typename IteratorType>\ntypename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last)\n{\n    using factory_type = iterator_input_adapter_factory<IteratorType>;\n    return factory_type::create(first, last);\n}\n\n// Convenience shorthand from container to iterator\n// Enables ADL on begin(container) and end(container)\n// Encloses the using declarations in namespace for not to leak them to outside scope\n\nnamespace container_input_adapter_factory_impl\n{\n\nusing std::begin;\nusing std::end;\n\ntemplate<typename ContainerType, typename Enable = void>\nstruct container_input_adapter_factory {};\n\ntemplate<typename ContainerType>\nstruct container_input_adapter_factory< ContainerType,\n       void_t<decltype(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))>>\n       {\n           using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));\n\n           static adapter_type create(const ContainerType& container)\n{\n    return input_adapter(begin(container), end(container));\n}\n       };\n\n}  // namespace container_input_adapter_factory_impl\n\ntemplate<typename ContainerType>\ntypename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container)\n{\n    return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container);\n}\n\n#ifndef JSON_NO_IO\n// Special cases with fast paths\ninline file_input_adapter input_adapter(std::FILE* file)\n{\n    return file_input_adapter(file);\n}\n\ninline input_stream_adapter input_adapter(std::istream& stream)\n{\n    return input_stream_adapter(stream);\n}\n\ninline input_stream_adapter input_adapter(std::istream&& stream)\n{\n    return input_stream_adapter(stream);\n}\n#endif  // JSON_NO_IO\n\nusing contiguous_bytes_input_adapter = decltype(input_adapter(std::declval<const char*>(), std::declval<const char*>()));\n\n// Null-delimited strings, and the like.\ntemplate < typename CharT,\n           typename std::enable_if <\n               std::is_pointer<CharT>::value&&\n               !std::is_array<CharT>::value&&\n               std::is_integral<typename std::remove_pointer<CharT>::type>::value&&\n               sizeof(typename std::remove_pointer<CharT>::type) == 1,\n               int >::type = 0 >\ncontiguous_bytes_input_adapter input_adapter(CharT b)\n{\n    auto length = std::strlen(reinterpret_cast<const char*>(b));\n    const auto* ptr = reinterpret_cast<const char*>(b);\n    return input_adapter(ptr, ptr + length);\n}\n\ntemplate<typename T, std::size_t N>\nauto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n{\n    return input_adapter(array, array + N);\n}\n\n// This class only handles inputs of input_buffer_adapter type.\n// It's required so that expressions like {ptr, len} can be implicitly cast\n// to the correct adapter.\nclass span_input_adapter\n{\n  public:\n    template < typename CharT,\n               typename std::enable_if <\n                   std::is_pointer<CharT>::value&&\n                   std::is_integral<typename std::remove_pointer<CharT>::type>::value&&\n                   sizeof(typename std::remove_pointer<CharT>::type) == 1,\n                   int >::type = 0 >\n    span_input_adapter(CharT b, std::size_t l)\n        : ia(reinterpret_cast<const char*>(b), reinterpret_cast<const char*>(b) + l) {}\n\n    template<class IteratorType,\n             typename std::enable_if<\n                 std::is_same<typename iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value,\n                 int>::type = 0>\n    span_input_adapter(IteratorType first, IteratorType last)\n        : ia(input_adapter(first, last)) {}\n\n    contiguous_bytes_input_adapter&& get()\n    {\n        return std::move(ia); // NOLINT(hicpp-move-const-arg,performance-move-const-arg)\n    }\n\n  private:\n    contiguous_bytes_input_adapter ia;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/json_sax.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef>\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/*!\n@brief SAX interface\n\nThis class describes the SAX interface used by @ref nlohmann::json::sax_parse.\nEach function is called in different situations while the input is parsed. The\nboolean return value informs the parser whether to continue processing the\ninput.\n*/\ntemplate<typename BasicJsonType>\nstruct json_sax\n{\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n\n    /*!\n    @brief a null value was read\n    @return whether parsing should proceed\n    */\n    virtual bool null() = 0;\n\n    /*!\n    @brief a boolean value was read\n    @param[in] val  boolean value\n    @return whether parsing should proceed\n    */\n    virtual bool boolean(bool val) = 0;\n\n    /*!\n    @brief an integer number was read\n    @param[in] val  integer value\n    @return whether parsing should proceed\n    */\n    virtual bool number_integer(number_integer_t val) = 0;\n\n    /*!\n    @brief an unsigned integer number was read\n    @param[in] val  unsigned integer value\n    @return whether parsing should proceed\n    */\n    virtual bool number_unsigned(number_unsigned_t val) = 0;\n\n    /*!\n    @brief a floating-point number was read\n    @param[in] val  floating-point value\n    @param[in] s    raw token value\n    @return whether parsing should proceed\n    */\n    virtual bool number_float(number_float_t val, const string_t& s) = 0;\n\n    /*!\n    @brief a string value was read\n    @param[in] val  string value\n    @return whether parsing should proceed\n    @note It is safe to move the passed string value.\n    */\n    virtual bool string(string_t& val) = 0;\n\n    /*!\n    @brief a binary value was read\n    @param[in] val  binary value\n    @return whether parsing should proceed\n    @note It is safe to move the passed binary value.\n    */\n    virtual bool binary(binary_t& val) = 0;\n\n    /*!\n    @brief the beginning of an object was read\n    @param[in] elements  number of object elements or -1 if unknown\n    @return whether parsing should proceed\n    @note binary formats may report the number of elements\n    */\n    virtual bool start_object(std::size_t elements) = 0;\n\n    /*!\n    @brief an object key was read\n    @param[in] val  object key\n    @return whether parsing should proceed\n    @note It is safe to move the passed string.\n    */\n    virtual bool key(string_t& val) = 0;\n\n    /*!\n    @brief the end of an object was read\n    @return whether parsing should proceed\n    */\n    virtual bool end_object() = 0;\n\n    /*!\n    @brief the beginning of an array was read\n    @param[in] elements  number of array elements or -1 if unknown\n    @return whether parsing should proceed\n    @note binary formats may report the number of elements\n    */\n    virtual bool start_array(std::size_t elements) = 0;\n\n    /*!\n    @brief the end of an array was read\n    @return whether parsing should proceed\n    */\n    virtual bool end_array() = 0;\n\n    /*!\n    @brief a parse error occurred\n    @param[in] position    the position in the input where the error occurs\n    @param[in] last_token  the last read token\n    @param[in] ex          an exception object describing the error\n    @return whether parsing should proceed (must return false)\n    */\n    virtual bool parse_error(std::size_t position,\n                             const std::string& last_token,\n                             const detail::exception& ex) = 0;\n\n    json_sax() = default;\n    json_sax(const json_sax&) = default;\n    json_sax(json_sax&&) noexcept = default;\n    json_sax& operator=(const json_sax&) = default;\n    json_sax& operator=(json_sax&&) noexcept = default;\n    virtual ~json_sax() = default;\n};\n\nnamespace detail\n{\n/*!\n@brief SAX implementation to create a JSON value from SAX events\n\nThis class implements the @ref json_sax interface and processes the SAX events\nto create a JSON value which makes it basically a DOM parser. The structure or\nhierarchy of the JSON value is managed by the stack `ref_stack` which contains\na pointer to the respective array or object for each recursion depth.\n\nAfter successful parsing, the value that is passed by reference to the\nconstructor contains the parsed value.\n\n@tparam BasicJsonType  the JSON type\n*/\ntemplate<typename BasicJsonType>\nclass json_sax_dom_parser\n{\n  public:\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n\n    /*!\n    @param[in,out] r  reference to a JSON value that is manipulated while\n                       parsing\n    @param[in] allow_exceptions_  whether parse errors yield exceptions\n    */\n    explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true)\n        : root(r), allow_exceptions(allow_exceptions_)\n    {}\n\n    // make class move-only\n    json_sax_dom_parser(const json_sax_dom_parser&) = delete;\n    json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete;\n    json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    ~json_sax_dom_parser() = default;\n\n    bool null()\n    {\n        handle_value(nullptr);\n        return true;\n    }\n\n    bool boolean(bool val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_integer(number_integer_t val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_float(number_float_t val, const string_t& /*unused*/)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool string(string_t& val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool binary(binary_t& val)\n    {\n        handle_value(std::move(val));\n        return true;\n    }\n\n    bool start_object(std::size_t len)\n    {\n        ref_stack.push_back(handle_value(BasicJsonType::value_t::object));\n\n        if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size()))\n        {\n            JSON_THROW(out_of_range::create(408, concat(\"excessive object size: \", std::to_string(len)), ref_stack.back()));\n        }\n\n        return true;\n    }\n\n    bool key(string_t& val)\n    {\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(ref_stack.back()->is_object());\n\n        // add null at given key and store the reference for later\n        object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val));\n        return true;\n    }\n\n    bool end_object()\n    {\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(ref_stack.back()->is_object());\n\n        ref_stack.back()->set_parents();\n        ref_stack.pop_back();\n        return true;\n    }\n\n    bool start_array(std::size_t len)\n    {\n        ref_stack.push_back(handle_value(BasicJsonType::value_t::array));\n\n        if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size()))\n        {\n            JSON_THROW(out_of_range::create(408, concat(\"excessive array size: \", std::to_string(len)), ref_stack.back()));\n        }\n\n        return true;\n    }\n\n    bool end_array()\n    {\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(ref_stack.back()->is_array());\n\n        ref_stack.back()->set_parents();\n        ref_stack.pop_back();\n        return true;\n    }\n\n    template<class Exception>\n    bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,\n                     const Exception& ex)\n    {\n        errored = true;\n        static_cast<void>(ex);\n        if (allow_exceptions)\n        {\n            JSON_THROW(ex);\n        }\n        return false;\n    }\n\n    constexpr bool is_errored() const\n    {\n        return errored;\n    }\n\n  private:\n    /*!\n    @invariant If the ref stack is empty, then the passed value will be the new\n               root.\n    @invariant If the ref stack contains a value, then it is an array or an\n               object to which we can add elements\n    */\n    template<typename Value>\n    JSON_HEDLEY_RETURNS_NON_NULL\n    BasicJsonType* handle_value(Value&& v)\n    {\n        if (ref_stack.empty())\n        {\n            root = BasicJsonType(std::forward<Value>(v));\n            return &root;\n        }\n\n        JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());\n\n        if (ref_stack.back()->is_array())\n        {\n            ref_stack.back()->m_data.m_value.array->emplace_back(std::forward<Value>(v));\n            return &(ref_stack.back()->m_data.m_value.array->back());\n        }\n\n        JSON_ASSERT(ref_stack.back()->is_object());\n        JSON_ASSERT(object_element);\n        *object_element = BasicJsonType(std::forward<Value>(v));\n        return object_element;\n    }\n\n    /// the parsed JSON value\n    BasicJsonType& root;\n    /// stack to model hierarchy of values\n    std::vector<BasicJsonType*> ref_stack {};\n    /// helper to hold the reference for the next object element\n    BasicJsonType* object_element = nullptr;\n    /// whether a syntax error occurred\n    bool errored = false;\n    /// whether to throw exceptions in case of errors\n    const bool allow_exceptions = true;\n};\n\ntemplate<typename BasicJsonType>\nclass json_sax_dom_callback_parser\n{\n  public:\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using parser_callback_t = typename BasicJsonType::parser_callback_t;\n    using parse_event_t = typename BasicJsonType::parse_event_t;\n\n    json_sax_dom_callback_parser(BasicJsonType& r,\n                                 const parser_callback_t cb,\n                                 const bool allow_exceptions_ = true)\n        : root(r), callback(cb), allow_exceptions(allow_exceptions_)\n    {\n        keep_stack.push_back(true);\n    }\n\n    // make class move-only\n    json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete;\n    json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete;\n    json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    ~json_sax_dom_callback_parser() = default;\n\n    bool null()\n    {\n        handle_value(nullptr);\n        return true;\n    }\n\n    bool boolean(bool val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_integer(number_integer_t val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_float(number_float_t val, const string_t& /*unused*/)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool string(string_t& val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool binary(binary_t& val)\n    {\n        handle_value(std::move(val));\n        return true;\n    }\n\n    bool start_object(std::size_t len)\n    {\n        // check callback for object start\n        const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);\n        keep_stack.push_back(keep);\n\n        auto val = handle_value(BasicJsonType::value_t::object, true);\n        ref_stack.push_back(val.second);\n\n        // check object limit\n        if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size()))\n        {\n            JSON_THROW(out_of_range::create(408, concat(\"excessive object size: \", std::to_string(len)), ref_stack.back()));\n        }\n\n        return true;\n    }\n\n    bool key(string_t& val)\n    {\n        BasicJsonType k = BasicJsonType(val);\n\n        // check callback for key\n        const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);\n        key_keep_stack.push_back(keep);\n\n        // add discarded value at given key and store the reference for later\n        if (keep && ref_stack.back())\n        {\n            object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded);\n        }\n\n        return true;\n    }\n\n    bool end_object()\n    {\n        if (ref_stack.back())\n        {\n            if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))\n            {\n                // discard object\n                *ref_stack.back() = discarded;\n            }\n            else\n            {\n                ref_stack.back()->set_parents();\n            }\n        }\n\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(!keep_stack.empty());\n        ref_stack.pop_back();\n        keep_stack.pop_back();\n\n        if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())\n        {\n            // remove discarded value\n            for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)\n            {\n                if (it->is_discarded())\n                {\n                    ref_stack.back()->erase(it);\n                    break;\n                }\n            }\n        }\n\n        return true;\n    }\n\n    bool start_array(std::size_t len)\n    {\n        const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);\n        keep_stack.push_back(keep);\n\n        auto val = handle_value(BasicJsonType::value_t::array, true);\n        ref_stack.push_back(val.second);\n\n        // check array limit\n        if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size()))\n        {\n            JSON_THROW(out_of_range::create(408, concat(\"excessive array size: \", std::to_string(len)), ref_stack.back()));\n        }\n\n        return true;\n    }\n\n    bool end_array()\n    {\n        bool keep = true;\n\n        if (ref_stack.back())\n        {\n            keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());\n            if (keep)\n            {\n                ref_stack.back()->set_parents();\n            }\n            else\n            {\n                // discard array\n                *ref_stack.back() = discarded;\n            }\n        }\n\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(!keep_stack.empty());\n        ref_stack.pop_back();\n        keep_stack.pop_back();\n\n        // remove discarded value\n        if (!keep && !ref_stack.empty() && ref_stack.back()->is_array())\n        {\n            ref_stack.back()->m_data.m_value.array->pop_back();\n        }\n\n        return true;\n    }\n\n    template<class Exception>\n    bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,\n                     const Exception& ex)\n    {\n        errored = true;\n        static_cast<void>(ex);\n        if (allow_exceptions)\n        {\n            JSON_THROW(ex);\n        }\n        return false;\n    }\n\n    constexpr bool is_errored() const\n    {\n        return errored;\n    }\n\n  private:\n    /*!\n    @param[in] v  value to add to the JSON value we build during parsing\n    @param[in] skip_callback  whether we should skip calling the callback\n               function; this is required after start_array() and\n               start_object() SAX events, because otherwise we would call the\n               callback function with an empty array or object, respectively.\n\n    @invariant If the ref stack is empty, then the passed value will be the new\n               root.\n    @invariant If the ref stack contains a value, then it is an array or an\n               object to which we can add elements\n\n    @return pair of boolean (whether value should be kept) and pointer (to the\n            passed value in the ref_stack hierarchy; nullptr if not kept)\n    */\n    template<typename Value>\n    std::pair<bool, BasicJsonType*> handle_value(Value&& v, const bool skip_callback = false)\n    {\n        JSON_ASSERT(!keep_stack.empty());\n\n        // do not handle this value if we know it would be added to a discarded\n        // container\n        if (!keep_stack.back())\n        {\n            return {false, nullptr};\n        }\n\n        // create value\n        auto value = BasicJsonType(std::forward<Value>(v));\n\n        // check callback\n        const bool keep = skip_callback || callback(static_cast<int>(ref_stack.size()), parse_event_t::value, value);\n\n        // do not handle this value if we just learnt it shall be discarded\n        if (!keep)\n        {\n            return {false, nullptr};\n        }\n\n        if (ref_stack.empty())\n        {\n            root = std::move(value);\n            return {true, & root};\n        }\n\n        // skip this value if we already decided to skip the parent\n        // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360)\n        if (!ref_stack.back())\n        {\n            return {false, nullptr};\n        }\n\n        // we now only expect arrays and objects\n        JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());\n\n        // array\n        if (ref_stack.back()->is_array())\n        {\n            ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value));\n            return {true, & (ref_stack.back()->m_data.m_value.array->back())};\n        }\n\n        // object\n        JSON_ASSERT(ref_stack.back()->is_object());\n        // check if we should store an element for the current key\n        JSON_ASSERT(!key_keep_stack.empty());\n        const bool store_element = key_keep_stack.back();\n        key_keep_stack.pop_back();\n\n        if (!store_element)\n        {\n            return {false, nullptr};\n        }\n\n        JSON_ASSERT(object_element);\n        *object_element = std::move(value);\n        return {true, object_element};\n    }\n\n    /// the parsed JSON value\n    BasicJsonType& root;\n    /// stack to model hierarchy of values\n    std::vector<BasicJsonType*> ref_stack {};\n    /// stack to manage which values to keep\n    std::vector<bool> keep_stack {};\n    /// stack to manage which object keys to keep\n    std::vector<bool> key_keep_stack {};\n    /// helper to hold the reference for the next object element\n    BasicJsonType* object_element = nullptr;\n    /// whether a syntax error occurred\n    bool errored = false;\n    /// callback function\n    const parser_callback_t callback = nullptr;\n    /// whether to throw exceptions in case of errors\n    const bool allow_exceptions = true;\n    /// a discarded value for the callback\n    BasicJsonType discarded = BasicJsonType::value_t::discarded;\n};\n\ntemplate<typename BasicJsonType>\nclass json_sax_acceptor\n{\n  public:\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n\n    bool null()\n    {\n        return true;\n    }\n\n    bool boolean(bool /*unused*/)\n    {\n        return true;\n    }\n\n    bool number_integer(number_integer_t /*unused*/)\n    {\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t /*unused*/)\n    {\n        return true;\n    }\n\n    bool number_float(number_float_t /*unused*/, const string_t& /*unused*/)\n    {\n        return true;\n    }\n\n    bool string(string_t& /*unused*/)\n    {\n        return true;\n    }\n\n    bool binary(binary_t& /*unused*/)\n    {\n        return true;\n    }\n\n    bool start_object(std::size_t /*unused*/ = static_cast<std::size_t>(-1))\n    {\n        return true;\n    }\n\n    bool key(string_t& /*unused*/)\n    {\n        return true;\n    }\n\n    bool end_object()\n    {\n        return true;\n    }\n\n    bool start_array(std::size_t /*unused*/ = static_cast<std::size_t>(-1))\n    {\n        return true;\n    }\n\n    bool end_array()\n    {\n        return true;\n    }\n\n    bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/)\n    {\n        return false;\n    }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/lexer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <clocale> // localeconv\n#include <cstddef> // size_t\n#include <cstdio> // snprintf\n#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull\n#include <initializer_list> // initializer_list\n#include <string> // char_traits, string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n\n// #include <nlohmann/detail/input/position_t.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////\n// lexer //\n///////////\n\ntemplate<typename BasicJsonType>\nclass lexer_base\n{\n  public:\n    /// token types for the parser\n    enum class token_type\n    {\n        uninitialized,    ///< indicating the scanner is uninitialized\n        literal_true,     ///< the `true` literal\n        literal_false,    ///< the `false` literal\n        literal_null,     ///< the `null` literal\n        value_string,     ///< a string -- use get_string() for actual value\n        value_unsigned,   ///< an unsigned integer -- use get_number_unsigned() for actual value\n        value_integer,    ///< a signed integer -- use get_number_integer() for actual value\n        value_float,      ///< an floating point number -- use get_number_float() for actual value\n        begin_array,      ///< the character for array begin `[`\n        begin_object,     ///< the character for object begin `{`\n        end_array,        ///< the character for array end `]`\n        end_object,       ///< the character for object end `}`\n        name_separator,   ///< the name separator `:`\n        value_separator,  ///< the value separator `,`\n        parse_error,      ///< indicating a parse error\n        end_of_input,     ///< indicating the end of the input buffer\n        literal_or_value  ///< a literal or the begin of a value (only for diagnostics)\n    };\n\n    /// return name of values of type token_type (only used for errors)\n    JSON_HEDLEY_RETURNS_NON_NULL\n    JSON_HEDLEY_CONST\n    static const char* token_type_name(const token_type t) noexcept\n    {\n        switch (t)\n        {\n            case token_type::uninitialized:\n                return \"<uninitialized>\";\n            case token_type::literal_true:\n                return \"true literal\";\n            case token_type::literal_false:\n                return \"false literal\";\n            case token_type::literal_null:\n                return \"null literal\";\n            case token_type::value_string:\n                return \"string literal\";\n            case token_type::value_unsigned:\n            case token_type::value_integer:\n            case token_type::value_float:\n                return \"number literal\";\n            case token_type::begin_array:\n                return \"'['\";\n            case token_type::begin_object:\n                return \"'{'\";\n            case token_type::end_array:\n                return \"']'\";\n            case token_type::end_object:\n                return \"'}'\";\n            case token_type::name_separator:\n                return \"':'\";\n            case token_type::value_separator:\n                return \"','\";\n            case token_type::parse_error:\n                return \"<parse error>\";\n            case token_type::end_of_input:\n                return \"end of input\";\n            case token_type::literal_or_value:\n                return \"'[', '{', or a literal\";\n            // LCOV_EXCL_START\n            default: // catch non-enum values\n                return \"unknown token\";\n                // LCOV_EXCL_STOP\n        }\n    }\n};\n/*!\n@brief lexical analysis\n\nThis class organizes the lexical analysis during JSON deserialization.\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType>\nclass lexer : public lexer_base<BasicJsonType>\n{\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using char_type = typename InputAdapterType::char_type;\n    using char_int_type = typename char_traits<char_type>::int_type;\n\n  public:\n    using token_type = typename lexer_base<BasicJsonType>::token_type;\n\n    explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept\n        : ia(std::move(adapter))\n        , ignore_comments(ignore_comments_)\n        , decimal_point_char(static_cast<char_int_type>(get_decimal_point()))\n    {}\n\n    // delete because of pointer members\n    lexer(const lexer&) = delete;\n    lexer(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    lexer& operator=(lexer&) = delete;\n    lexer& operator=(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    ~lexer() = default;\n\n  private:\n    /////////////////////\n    // locales\n    /////////////////////\n\n    /// return the locale-dependent decimal point\n    JSON_HEDLEY_PURE\n    static char get_decimal_point() noexcept\n    {\n        const auto* loc = localeconv();\n        JSON_ASSERT(loc != nullptr);\n        return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);\n    }\n\n    /////////////////////\n    // scan functions\n    /////////////////////\n\n    /*!\n    @brief get codepoint from 4 hex characters following `\\u`\n\n    For input \"\\u c1 c2 c3 c4\" the codepoint is:\n      (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4\n    = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)\n\n    Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f'\n    must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The\n    conversion is done by subtracting the offset (0x30, 0x37, and 0x57)\n    between the ASCII value of the character and the desired integer value.\n\n    @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or\n            non-hex character)\n    */\n    int get_codepoint()\n    {\n        // this function only makes sense after reading `\\u`\n        JSON_ASSERT(current == 'u');\n        int codepoint = 0;\n\n        const auto factors = { 12u, 8u, 4u, 0u };\n        for (const auto factor : factors)\n        {\n            get();\n\n            if (current >= '0' && current <= '9')\n            {\n                codepoint += static_cast<int>((static_cast<unsigned int>(current) - 0x30u) << factor);\n            }\n            else if (current >= 'A' && current <= 'F')\n            {\n                codepoint += static_cast<int>((static_cast<unsigned int>(current) - 0x37u) << factor);\n            }\n            else if (current >= 'a' && current <= 'f')\n            {\n                codepoint += static_cast<int>((static_cast<unsigned int>(current) - 0x57u) << factor);\n            }\n            else\n            {\n                return -1;\n            }\n        }\n\n        JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF);\n        return codepoint;\n    }\n\n    /*!\n    @brief check if the next byte(s) are inside a given range\n\n    Adds the current byte and, for each passed range, reads a new byte and\n    checks if it is inside the range. If a violation was detected, set up an\n    error message and return false. Otherwise, return true.\n\n    @param[in] ranges  list of integers; interpreted as list of pairs of\n                       inclusive lower and upper bound, respectively\n\n    @pre The passed list @a ranges must have 2, 4, or 6 elements; that is,\n         1, 2, or 3 pairs. This precondition is enforced by an assertion.\n\n    @return true if and only if no range violation was detected\n    */\n    bool next_byte_in_range(std::initializer_list<char_int_type> ranges)\n    {\n        JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);\n        add(current);\n\n        for (auto range = ranges.begin(); range != ranges.end(); ++range)\n        {\n            get();\n            if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)\n            {\n                add(current);\n            }\n            else\n            {\n                error_message = \"invalid string: ill-formed UTF-8 byte\";\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /*!\n    @brief scan a string literal\n\n    This function scans a string according to Sect. 7 of RFC 8259. While\n    scanning, bytes are escaped and copied into buffer token_buffer. Then the\n    function returns successfully, token_buffer is *not* null-terminated (as it\n    may contain \\0 bytes), and token_buffer.size() is the number of bytes in the\n    string.\n\n    @return token_type::value_string if string could be successfully scanned,\n            token_type::parse_error otherwise\n\n    @note In case of errors, variable error_message contains a textual\n          description.\n    */\n    token_type scan_string()\n    {\n        // reset token_buffer (ignore opening quote)\n        reset();\n\n        // we entered the function by reading an open quote\n        JSON_ASSERT(current == '\\\"');\n\n        while (true)\n        {\n            // get next character\n            switch (get())\n            {\n                // end of file while parsing string\n                case char_traits<char_type>::eof():\n                {\n                    error_message = \"invalid string: missing closing quote\";\n                    return token_type::parse_error;\n                }\n\n                // closing quote\n                case '\\\"':\n                {\n                    return token_type::value_string;\n                }\n\n                // escapes\n                case '\\\\':\n                {\n                    switch (get())\n                    {\n                        // quotation mark\n                        case '\\\"':\n                            add('\\\"');\n                            break;\n                        // reverse solidus\n                        case '\\\\':\n                            add('\\\\');\n                            break;\n                        // solidus\n                        case '/':\n                            add('/');\n                            break;\n                        // backspace\n                        case 'b':\n                            add('\\b');\n                            break;\n                        // form feed\n                        case 'f':\n                            add('\\f');\n                            break;\n                        // line feed\n                        case 'n':\n                            add('\\n');\n                            break;\n                        // carriage return\n                        case 'r':\n                            add('\\r');\n                            break;\n                        // tab\n                        case 't':\n                            add('\\t');\n                            break;\n\n                        // unicode escapes\n                        case 'u':\n                        {\n                            const int codepoint1 = get_codepoint();\n                            int codepoint = codepoint1; // start with codepoint1\n\n                            if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1))\n                            {\n                                error_message = \"invalid string: '\\\\u' must be followed by 4 hex digits\";\n                                return token_type::parse_error;\n                            }\n\n                            // check if code point is a high surrogate\n                            if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF)\n                            {\n                                // expect next \\uxxxx entry\n                                if (JSON_HEDLEY_LIKELY(get() == '\\\\' && get() == 'u'))\n                                {\n                                    const int codepoint2 = get_codepoint();\n\n                                    if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1))\n                                    {\n                                        error_message = \"invalid string: '\\\\u' must be followed by 4 hex digits\";\n                                        return token_type::parse_error;\n                                    }\n\n                                    // check if codepoint2 is a low surrogate\n                                    if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF))\n                                    {\n                                        // overwrite codepoint\n                                        codepoint = static_cast<int>(\n                                                        // high surrogate occupies the most significant 22 bits\n                                                        (static_cast<unsigned int>(codepoint1) << 10u)\n                                                        // low surrogate occupies the least significant 15 bits\n                                                        + static_cast<unsigned int>(codepoint2)\n                                                        // there is still the 0xD800, 0xDC00 and 0x10000 noise\n                                                        // in the result, so we have to subtract with:\n                                                        // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00\n                                                        - 0x35FDC00u);\n                                    }\n                                    else\n                                    {\n                                        error_message = \"invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF\";\n                                        return token_type::parse_error;\n                                    }\n                                }\n                                else\n                                {\n                                    error_message = \"invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF\";\n                                    return token_type::parse_error;\n                                }\n                            }\n                            else\n                            {\n                                if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF))\n                                {\n                                    error_message = \"invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF\";\n                                    return token_type::parse_error;\n                                }\n                            }\n\n                            // result of the above calculation yields a proper codepoint\n                            JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);\n\n                            // translate codepoint into bytes\n                            if (codepoint < 0x80)\n                            {\n                                // 1-byte characters: 0xxxxxxx (ASCII)\n                                add(static_cast<char_int_type>(codepoint));\n                            }\n                            else if (codepoint <= 0x7FF)\n                            {\n                                // 2-byte characters: 110xxxxx 10xxxxxx\n                                add(static_cast<char_int_type>(0xC0u | (static_cast<unsigned int>(codepoint) >> 6u)));\n                                add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu)));\n                            }\n                            else if (codepoint <= 0xFFFF)\n                            {\n                                // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx\n                                add(static_cast<char_int_type>(0xE0u | (static_cast<unsigned int>(codepoint) >> 12u)));\n                                add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 6u) & 0x3Fu)));\n                                add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu)));\n                            }\n                            else\n                            {\n                                // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx\n                                add(static_cast<char_int_type>(0xF0u | (static_cast<unsigned int>(codepoint) >> 18u)));\n                                add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 12u) & 0x3Fu)));\n                                add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 6u) & 0x3Fu)));\n                                add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu)));\n                            }\n\n                            break;\n                        }\n\n                        // other characters after escape\n                        default:\n                            error_message = \"invalid string: forbidden character after backslash\";\n                            return token_type::parse_error;\n                    }\n\n                    break;\n                }\n\n                // invalid control characters\n                case 0x00:\n                {\n                    error_message = \"invalid string: control character U+0000 (NUL) must be escaped to \\\\u0000\";\n                    return token_type::parse_error;\n                }\n\n                case 0x01:\n                {\n                    error_message = \"invalid string: control character U+0001 (SOH) must be escaped to \\\\u0001\";\n                    return token_type::parse_error;\n                }\n\n                case 0x02:\n                {\n                    error_message = \"invalid string: control character U+0002 (STX) must be escaped to \\\\u0002\";\n                    return token_type::parse_error;\n                }\n\n                case 0x03:\n                {\n                    error_message = \"invalid string: control character U+0003 (ETX) must be escaped to \\\\u0003\";\n                    return token_type::parse_error;\n                }\n\n                case 0x04:\n                {\n                    error_message = \"invalid string: control character U+0004 (EOT) must be escaped to \\\\u0004\";\n                    return token_type::parse_error;\n                }\n\n                case 0x05:\n                {\n                    error_message = \"invalid string: control character U+0005 (ENQ) must be escaped to \\\\u0005\";\n                    return token_type::parse_error;\n                }\n\n                case 0x06:\n                {\n                    error_message = \"invalid string: control character U+0006 (ACK) must be escaped to \\\\u0006\";\n                    return token_type::parse_error;\n                }\n\n                case 0x07:\n                {\n                    error_message = \"invalid string: control character U+0007 (BEL) must be escaped to \\\\u0007\";\n                    return token_type::parse_error;\n                }\n\n                case 0x08:\n                {\n                    error_message = \"invalid string: control character U+0008 (BS) must be escaped to \\\\u0008 or \\\\b\";\n                    return token_type::parse_error;\n                }\n\n                case 0x09:\n                {\n                    error_message = \"invalid string: control character U+0009 (HT) must be escaped to \\\\u0009 or \\\\t\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0A:\n                {\n                    error_message = \"invalid string: control character U+000A (LF) must be escaped to \\\\u000A or \\\\n\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0B:\n                {\n                    error_message = \"invalid string: control character U+000B (VT) must be escaped to \\\\u000B\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0C:\n                {\n                    error_message = \"invalid string: control character U+000C (FF) must be escaped to \\\\u000C or \\\\f\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0D:\n                {\n                    error_message = \"invalid string: control character U+000D (CR) must be escaped to \\\\u000D or \\\\r\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0E:\n                {\n                    error_message = \"invalid string: control character U+000E (SO) must be escaped to \\\\u000E\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0F:\n                {\n                    error_message = \"invalid string: control character U+000F (SI) must be escaped to \\\\u000F\";\n                    return token_type::parse_error;\n                }\n\n                case 0x10:\n                {\n                    error_message = \"invalid string: control character U+0010 (DLE) must be escaped to \\\\u0010\";\n                    return token_type::parse_error;\n                }\n\n                case 0x11:\n                {\n                    error_message = \"invalid string: control character U+0011 (DC1) must be escaped to \\\\u0011\";\n                    return token_type::parse_error;\n                }\n\n                case 0x12:\n                {\n                    error_message = \"invalid string: control character U+0012 (DC2) must be escaped to \\\\u0012\";\n                    return token_type::parse_error;\n                }\n\n                case 0x13:\n                {\n                    error_message = \"invalid string: control character U+0013 (DC3) must be escaped to \\\\u0013\";\n                    return token_type::parse_error;\n                }\n\n                case 0x14:\n                {\n                    error_message = \"invalid string: control character U+0014 (DC4) must be escaped to \\\\u0014\";\n                    return token_type::parse_error;\n                }\n\n                case 0x15:\n                {\n                    error_message = \"invalid string: control character U+0015 (NAK) must be escaped to \\\\u0015\";\n                    return token_type::parse_error;\n                }\n\n                case 0x16:\n                {\n                    error_message = \"invalid string: control character U+0016 (SYN) must be escaped to \\\\u0016\";\n                    return token_type::parse_error;\n                }\n\n                case 0x17:\n                {\n                    error_message = \"invalid string: control character U+0017 (ETB) must be escaped to \\\\u0017\";\n                    return token_type::parse_error;\n                }\n\n                case 0x18:\n                {\n                    error_message = \"invalid string: control character U+0018 (CAN) must be escaped to \\\\u0018\";\n                    return token_type::parse_error;\n                }\n\n                case 0x19:\n                {\n                    error_message = \"invalid string: control character U+0019 (EM) must be escaped to \\\\u0019\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1A:\n                {\n                    error_message = \"invalid string: control character U+001A (SUB) must be escaped to \\\\u001A\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1B:\n                {\n                    error_message = \"invalid string: control character U+001B (ESC) must be escaped to \\\\u001B\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1C:\n                {\n                    error_message = \"invalid string: control character U+001C (FS) must be escaped to \\\\u001C\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1D:\n                {\n                    error_message = \"invalid string: control character U+001D (GS) must be escaped to \\\\u001D\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1E:\n                {\n                    error_message = \"invalid string: control character U+001E (RS) must be escaped to \\\\u001E\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1F:\n                {\n                    error_message = \"invalid string: control character U+001F (US) must be escaped to \\\\u001F\";\n                    return token_type::parse_error;\n                }\n\n                // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace))\n                case 0x20:\n                case 0x21:\n                case 0x23:\n                case 0x24:\n                case 0x25:\n                case 0x26:\n                case 0x27:\n                case 0x28:\n                case 0x29:\n                case 0x2A:\n                case 0x2B:\n                case 0x2C:\n                case 0x2D:\n                case 0x2E:\n                case 0x2F:\n                case 0x30:\n                case 0x31:\n                case 0x32:\n                case 0x33:\n                case 0x34:\n                case 0x35:\n                case 0x36:\n                case 0x37:\n                case 0x38:\n                case 0x39:\n                case 0x3A:\n                case 0x3B:\n                case 0x3C:\n                case 0x3D:\n                case 0x3E:\n                case 0x3F:\n                case 0x40:\n                case 0x41:\n                case 0x42:\n                case 0x43:\n                case 0x44:\n                case 0x45:\n                case 0x46:\n                case 0x47:\n                case 0x48:\n                case 0x49:\n                case 0x4A:\n                case 0x4B:\n                case 0x4C:\n                case 0x4D:\n                case 0x4E:\n                case 0x4F:\n                case 0x50:\n                case 0x51:\n                case 0x52:\n                case 0x53:\n                case 0x54:\n                case 0x55:\n                case 0x56:\n                case 0x57:\n                case 0x58:\n                case 0x59:\n                case 0x5A:\n                case 0x5B:\n                case 0x5D:\n                case 0x5E:\n                case 0x5F:\n                case 0x60:\n                case 0x61:\n                case 0x62:\n                case 0x63:\n                case 0x64:\n                case 0x65:\n                case 0x66:\n                case 0x67:\n                case 0x68:\n                case 0x69:\n                case 0x6A:\n                case 0x6B:\n                case 0x6C:\n                case 0x6D:\n                case 0x6E:\n                case 0x6F:\n                case 0x70:\n                case 0x71:\n                case 0x72:\n                case 0x73:\n                case 0x74:\n                case 0x75:\n                case 0x76:\n                case 0x77:\n                case 0x78:\n                case 0x79:\n                case 0x7A:\n                case 0x7B:\n                case 0x7C:\n                case 0x7D:\n                case 0x7E:\n                case 0x7F:\n                {\n                    add(current);\n                    break;\n                }\n\n                // U+0080..U+07FF: bytes C2..DF 80..BF\n                case 0xC2:\n                case 0xC3:\n                case 0xC4:\n                case 0xC5:\n                case 0xC6:\n                case 0xC7:\n                case 0xC8:\n                case 0xC9:\n                case 0xCA:\n                case 0xCB:\n                case 0xCC:\n                case 0xCD:\n                case 0xCE:\n                case 0xCF:\n                case 0xD0:\n                case 0xD1:\n                case 0xD2:\n                case 0xD3:\n                case 0xD4:\n                case 0xD5:\n                case 0xD6:\n                case 0xD7:\n                case 0xD8:\n                case 0xD9:\n                case 0xDA:\n                case 0xDB:\n                case 0xDC:\n                case 0xDD:\n                case 0xDE:\n                case 0xDF:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF})))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+0800..U+0FFF: bytes E0 A0..BF 80..BF\n                case 0xE0:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF\n                // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF\n                case 0xE1:\n                case 0xE2:\n                case 0xE3:\n                case 0xE4:\n                case 0xE5:\n                case 0xE6:\n                case 0xE7:\n                case 0xE8:\n                case 0xE9:\n                case 0xEA:\n                case 0xEB:\n                case 0xEC:\n                case 0xEE:\n                case 0xEF:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+D000..U+D7FF: bytes ED 80..9F 80..BF\n                case 0xED:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF\n                case 0xF0:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF\n                case 0xF1:\n                case 0xF2:\n                case 0xF3:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF\n                case 0xF4:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // remaining bytes (80..C1 and F5..FF) are ill-formed\n                default:\n                {\n                    error_message = \"invalid string: ill-formed UTF-8 byte\";\n                    return token_type::parse_error;\n                }\n            }\n        }\n    }\n\n    /*!\n     * @brief scan a comment\n     * @return whether comment could be scanned successfully\n     */\n    bool scan_comment()\n    {\n        switch (get())\n        {\n            // single-line comments skip input until a newline or EOF is read\n            case '/':\n            {\n                while (true)\n                {\n                    switch (get())\n                    {\n                        case '\\n':\n                        case '\\r':\n                        case char_traits<char_type>::eof():\n                        case '\\0':\n                            return true;\n\n                        default:\n                            break;\n                    }\n                }\n            }\n\n            // multi-line comments skip input until */ is read\n            case '*':\n            {\n                while (true)\n                {\n                    switch (get())\n                    {\n                        case char_traits<char_type>::eof():\n                        case '\\0':\n                        {\n                            error_message = \"invalid comment; missing closing '*/'\";\n                            return false;\n                        }\n\n                        case '*':\n                        {\n                            switch (get())\n                            {\n                                case '/':\n                                    return true;\n\n                                default:\n                                {\n                                    unget();\n                                    continue;\n                                }\n                            }\n                        }\n\n                        default:\n                            continue;\n                    }\n                }\n            }\n\n            // unexpected character after reading '/'\n            default:\n            {\n                error_message = \"invalid comment; expecting '/' or '*' after '/'\";\n                return false;\n            }\n        }\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    static void strtof(float& f, const char* str, char** endptr) noexcept\n    {\n        f = std::strtof(str, endptr);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    static void strtof(double& f, const char* str, char** endptr) noexcept\n    {\n        f = std::strtod(str, endptr);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    static void strtof(long double& f, const char* str, char** endptr) noexcept\n    {\n        f = std::strtold(str, endptr);\n    }\n\n    /*!\n    @brief scan a number literal\n\n    This function scans a string according to Sect. 6 of RFC 8259.\n\n    The function is realized with a deterministic finite state machine derived\n    from the grammar described in RFC 8259. Starting in state \"init\", the\n    input is read and used to determined the next state. Only state \"done\"\n    accepts the number. State \"error\" is a trap state to model errors. In the\n    table below, \"anything\" means any character but the ones listed before.\n\n    state    | 0        | 1-9      | e E      | +       | -       | .        | anything\n    ---------|----------|----------|----------|---------|---------|----------|-----------\n    init     | zero     | any1     | [error]  | [error] | minus   | [error]  | [error]\n    minus    | zero     | any1     | [error]  | [error] | [error] | [error]  | [error]\n    zero     | done     | done     | exponent | done    | done    | decimal1 | done\n    any1     | any1     | any1     | exponent | done    | done    | decimal1 | done\n    decimal1 | decimal2 | decimal2 | [error]  | [error] | [error] | [error]  | [error]\n    decimal2 | decimal2 | decimal2 | exponent | done    | done    | done     | done\n    exponent | any2     | any2     | [error]  | sign    | sign    | [error]  | [error]\n    sign     | any2     | any2     | [error]  | [error] | [error] | [error]  | [error]\n    any2     | any2     | any2     | done     | done    | done    | done     | done\n\n    The state machine is realized with one label per state (prefixed with\n    \"scan_number_\") and `goto` statements between them. The state machine\n    contains cycles, but any cycle can be left when EOF is read. Therefore,\n    the function is guaranteed to terminate.\n\n    During scanning, the read bytes are stored in token_buffer. This string is\n    then converted to a signed integer, an unsigned integer, or a\n    floating-point number.\n\n    @return token_type::value_unsigned, token_type::value_integer, or\n            token_type::value_float if number could be successfully scanned,\n            token_type::parse_error otherwise\n\n    @note The scanner is independent of the current locale. Internally, the\n          locale's decimal point is used instead of `.` to work with the\n          locale-dependent converters.\n    */\n    token_type scan_number()  // lgtm [cpp/use-of-goto]\n    {\n        // reset token_buffer to store the number's bytes\n        reset();\n\n        // the type of the parsed number; initially set to unsigned; will be\n        // changed if minus sign, decimal point or exponent is read\n        token_type number_type = token_type::value_unsigned;\n\n        // state (init): we just found out we need to scan a number\n        switch (current)\n        {\n            case '-':\n            {\n                add(current);\n                goto scan_number_minus;\n            }\n\n            case '0':\n            {\n                add(current);\n                goto scan_number_zero;\n            }\n\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any1;\n            }\n\n            // all other characters are rejected outside scan_number()\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n\nscan_number_minus:\n        // state: we just parsed a leading minus sign\n        number_type = token_type::value_integer;\n        switch (get())\n        {\n            case '0':\n            {\n                add(current);\n                goto scan_number_zero;\n            }\n\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any1;\n            }\n\n            default:\n            {\n                error_message = \"invalid number; expected digit after '-'\";\n                return token_type::parse_error;\n            }\n        }\n\nscan_number_zero:\n        // state: we just parse a zero (maybe with a leading minus sign)\n        switch (get())\n        {\n            case '.':\n            {\n                add(decimal_point_char);\n                goto scan_number_decimal1;\n            }\n\n            case 'e':\n            case 'E':\n            {\n                add(current);\n                goto scan_number_exponent;\n            }\n\n            default:\n                goto scan_number_done;\n        }\n\nscan_number_any1:\n        // state: we just parsed a number 0-9 (maybe with a leading minus sign)\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any1;\n            }\n\n            case '.':\n            {\n                add(decimal_point_char);\n                goto scan_number_decimal1;\n            }\n\n            case 'e':\n            case 'E':\n            {\n                add(current);\n                goto scan_number_exponent;\n            }\n\n            default:\n                goto scan_number_done;\n        }\n\nscan_number_decimal1:\n        // state: we just parsed a decimal point\n        number_type = token_type::value_float;\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_decimal2;\n            }\n\n            default:\n            {\n                error_message = \"invalid number; expected digit after '.'\";\n                return token_type::parse_error;\n            }\n        }\n\nscan_number_decimal2:\n        // we just parsed at least one number after a decimal point\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_decimal2;\n            }\n\n            case 'e':\n            case 'E':\n            {\n                add(current);\n                goto scan_number_exponent;\n            }\n\n            default:\n                goto scan_number_done;\n        }\n\nscan_number_exponent:\n        // we just parsed an exponent\n        number_type = token_type::value_float;\n        switch (get())\n        {\n            case '+':\n            case '-':\n            {\n                add(current);\n                goto scan_number_sign;\n            }\n\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any2;\n            }\n\n            default:\n            {\n                error_message =\n                    \"invalid number; expected '+', '-', or digit after exponent\";\n                return token_type::parse_error;\n            }\n        }\n\nscan_number_sign:\n        // we just parsed an exponent sign\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any2;\n            }\n\n            default:\n            {\n                error_message = \"invalid number; expected digit after exponent sign\";\n                return token_type::parse_error;\n            }\n        }\n\nscan_number_any2:\n        // we just parsed a number after the exponent or exponent sign\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any2;\n            }\n\n            default:\n                goto scan_number_done;\n        }\n\nscan_number_done:\n        // unget the character after the number (we only read it to know that\n        // we are done scanning a number)\n        unget();\n\n        char* endptr = nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n        errno = 0;\n\n        // try to parse integers first and fall back to floats\n        if (number_type == token_type::value_unsigned)\n        {\n            const auto x = std::strtoull(token_buffer.data(), &endptr, 10);\n\n            // we checked the number format before\n            JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());\n\n            if (errno == 0)\n            {\n                value_unsigned = static_cast<number_unsigned_t>(x);\n                if (value_unsigned == x)\n                {\n                    return token_type::value_unsigned;\n                }\n            }\n        }\n        else if (number_type == token_type::value_integer)\n        {\n            const auto x = std::strtoll(token_buffer.data(), &endptr, 10);\n\n            // we checked the number format before\n            JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());\n\n            if (errno == 0)\n            {\n                value_integer = static_cast<number_integer_t>(x);\n                if (value_integer == x)\n                {\n                    return token_type::value_integer;\n                }\n            }\n        }\n\n        // this code is reached if we parse a floating-point number or if an\n        // integer conversion above failed\n        strtof(value_float, token_buffer.data(), &endptr);\n\n        // we checked the number format before\n        JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());\n\n        return token_type::value_float;\n    }\n\n    /*!\n    @param[in] literal_text  the literal text to expect\n    @param[in] length        the length of the passed literal text\n    @param[in] return_type   the token type to return on success\n    */\n    JSON_HEDLEY_NON_NULL(2)\n    token_type scan_literal(const char_type* literal_text, const std::size_t length,\n                            token_type return_type)\n    {\n        JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]);\n        for (std::size_t i = 1; i < length; ++i)\n        {\n            if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != literal_text[i]))\n            {\n                error_message = \"invalid literal\";\n                return token_type::parse_error;\n            }\n        }\n        return return_type;\n    }\n\n    /////////////////////\n    // input management\n    /////////////////////\n\n    /// reset token_buffer; current character is beginning of token\n    void reset() noexcept\n    {\n        token_buffer.clear();\n        token_string.clear();\n        token_string.push_back(char_traits<char_type>::to_char_type(current));\n    }\n\n    /*\n    @brief get next character from the input\n\n    This function provides the interface to the used input adapter. It does\n    not throw in case the input reached EOF, but returns a\n    `char_traits<char>::eof()` in that case.  Stores the scanned characters\n    for use in error messages.\n\n    @return character read from the input\n    */\n    char_int_type get()\n    {\n        ++position.chars_read_total;\n        ++position.chars_read_current_line;\n\n        if (next_unget)\n        {\n            // just reset the next_unget variable and work with current\n            next_unget = false;\n        }\n        else\n        {\n            current = ia.get_character();\n        }\n\n        if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))\n        {\n            token_string.push_back(char_traits<char_type>::to_char_type(current));\n        }\n\n        if (current == '\\n')\n        {\n            ++position.lines_read;\n            position.chars_read_current_line = 0;\n        }\n\n        return current;\n    }\n\n    /*!\n    @brief unget current character (read it again on next get)\n\n    We implement unget by setting variable next_unget to true. The input is not\n    changed - we just simulate ungetting by modifying chars_read_total,\n    chars_read_current_line, and token_string. The next call to get() will\n    behave as if the unget character is read again.\n    */\n    void unget()\n    {\n        next_unget = true;\n\n        --position.chars_read_total;\n\n        // in case we \"unget\" a newline, we have to also decrement the lines_read\n        if (position.chars_read_current_line == 0)\n        {\n            if (position.lines_read > 0)\n            {\n                --position.lines_read;\n            }\n        }\n        else\n        {\n            --position.chars_read_current_line;\n        }\n\n        if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))\n        {\n            JSON_ASSERT(!token_string.empty());\n            token_string.pop_back();\n        }\n    }\n\n    /// add a character to token_buffer\n    void add(char_int_type c)\n    {\n        token_buffer.push_back(static_cast<typename string_t::value_type>(c));\n    }\n\n  public:\n    /////////////////////\n    // value getters\n    /////////////////////\n\n    /// return integer value\n    constexpr number_integer_t get_number_integer() const noexcept\n    {\n        return value_integer;\n    }\n\n    /// return unsigned integer value\n    constexpr number_unsigned_t get_number_unsigned() const noexcept\n    {\n        return value_unsigned;\n    }\n\n    /// return floating-point value\n    constexpr number_float_t get_number_float() const noexcept\n    {\n        return value_float;\n    }\n\n    /// return current string value (implicitly resets the token; useful only once)\n    string_t& get_string()\n    {\n        return token_buffer;\n    }\n\n    /////////////////////\n    // diagnostics\n    /////////////////////\n\n    /// return position of last read token\n    constexpr position_t get_position() const noexcept\n    {\n        return position;\n    }\n\n    /// return the last read token (for errors only).  Will never contain EOF\n    /// (an arbitrary value that is not a valid char value, often -1), because\n    /// 255 may legitimately occur.  May contain NUL, which should be escaped.\n    std::string get_token_string() const\n    {\n        // escape control characters\n        std::string result;\n        for (const auto c : token_string)\n        {\n            if (static_cast<unsigned char>(c) <= '\\x1F')\n            {\n                // escape control characters\n                std::array<char, 9> cs{{}};\n                static_cast<void>((std::snprintf)(cs.data(), cs.size(), \"<U+%.4X>\", static_cast<unsigned char>(c))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n                result += cs.data();\n            }\n            else\n            {\n                // add character as is\n                result.push_back(static_cast<std::string::value_type>(c));\n            }\n        }\n\n        return result;\n    }\n\n    /// return syntax error message\n    JSON_HEDLEY_RETURNS_NON_NULL\n    constexpr const char* get_error_message() const noexcept\n    {\n        return error_message;\n    }\n\n    /////////////////////\n    // actual scanner\n    /////////////////////\n\n    /*!\n    @brief skip the UTF-8 byte order mark\n    @return true iff there is no BOM or the correct BOM has been skipped\n    */\n    bool skip_bom()\n    {\n        if (get() == 0xEF)\n        {\n            // check if we completely parse the BOM\n            return get() == 0xBB && get() == 0xBF;\n        }\n\n        // the first character is not the beginning of the BOM; unget it to\n        // process is later\n        unget();\n        return true;\n    }\n\n    void skip_whitespace()\n    {\n        do\n        {\n            get();\n        }\n        while (current == ' ' || current == '\\t' || current == '\\n' || current == '\\r');\n    }\n\n    token_type scan()\n    {\n        // initially, skip the BOM\n        if (position.chars_read_total == 0 && !skip_bom())\n        {\n            error_message = \"invalid BOM; must be 0xEF 0xBB 0xBF if given\";\n            return token_type::parse_error;\n        }\n\n        // read next character and ignore whitespace\n        skip_whitespace();\n\n        // ignore comments\n        while (ignore_comments && current == '/')\n        {\n            if (!scan_comment())\n            {\n                return token_type::parse_error;\n            }\n\n            // skip following whitespace\n            skip_whitespace();\n        }\n\n        switch (current)\n        {\n            // structural characters\n            case '[':\n                return token_type::begin_array;\n            case ']':\n                return token_type::end_array;\n            case '{':\n                return token_type::begin_object;\n            case '}':\n                return token_type::end_object;\n            case ':':\n                return token_type::name_separator;\n            case ',':\n                return token_type::value_separator;\n\n            // literals\n            case 't':\n            {\n                std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}};\n                return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true);\n            }\n            case 'f':\n            {\n                std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}};\n                return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false);\n            }\n            case 'n':\n            {\n                std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}};\n                return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null);\n            }\n\n            // string\n            case '\\\"':\n                return scan_string();\n\n            // number\n            case '-':\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n                return scan_number();\n\n            // end of input (the null byte is needed when parsing from\n            // string literals)\n            case '\\0':\n            case char_traits<char_type>::eof():\n                return token_type::end_of_input;\n\n            // error\n            default:\n                error_message = \"invalid literal\";\n                return token_type::parse_error;\n        }\n    }\n\n  private:\n    /// input adapter\n    InputAdapterType ia;\n\n    /// whether comments should be ignored (true) or signaled as errors (false)\n    const bool ignore_comments = false;\n\n    /// the current character\n    char_int_type current = char_traits<char_type>::eof();\n\n    /// whether the next get() call should just return current\n    bool next_unget = false;\n\n    /// the start position of the current token\n    position_t position {};\n\n    /// raw input token string (for error messages)\n    std::vector<char_type> token_string {};\n\n    /// buffer for variable-length tokens (numbers, strings)\n    string_t token_buffer {};\n\n    /// a description of occurred lexer errors\n    const char* error_message = \"\";\n\n    // number values\n    number_integer_t value_integer = 0;\n    number_unsigned_t value_unsigned = 0;\n    number_float_t value_float = 0;\n\n    /// the decimal point\n    const char_int_type decimal_point_char = '.';\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/is_sax.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstdint> // size_t\n#include <utility> // declval\n#include <string> // string\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/detected.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename T>\nusing null_function_t = decltype(std::declval<T&>().null());\n\ntemplate<typename T>\nusing boolean_function_t =\n    decltype(std::declval<T&>().boolean(std::declval<bool>()));\n\ntemplate<typename T, typename Integer>\nusing number_integer_function_t =\n    decltype(std::declval<T&>().number_integer(std::declval<Integer>()));\n\ntemplate<typename T, typename Unsigned>\nusing number_unsigned_function_t =\n    decltype(std::declval<T&>().number_unsigned(std::declval<Unsigned>()));\n\ntemplate<typename T, typename Float, typename String>\nusing number_float_function_t = decltype(std::declval<T&>().number_float(\n                                    std::declval<Float>(), std::declval<const String&>()));\n\ntemplate<typename T, typename String>\nusing string_function_t =\n    decltype(std::declval<T&>().string(std::declval<String&>()));\n\ntemplate<typename T, typename Binary>\nusing binary_function_t =\n    decltype(std::declval<T&>().binary(std::declval<Binary&>()));\n\ntemplate<typename T>\nusing start_object_function_t =\n    decltype(std::declval<T&>().start_object(std::declval<std::size_t>()));\n\ntemplate<typename T, typename String>\nusing key_function_t =\n    decltype(std::declval<T&>().key(std::declval<String&>()));\n\ntemplate<typename T>\nusing end_object_function_t = decltype(std::declval<T&>().end_object());\n\ntemplate<typename T>\nusing start_array_function_t =\n    decltype(std::declval<T&>().start_array(std::declval<std::size_t>()));\n\ntemplate<typename T>\nusing end_array_function_t = decltype(std::declval<T&>().end_array());\n\ntemplate<typename T, typename Exception>\nusing parse_error_function_t = decltype(std::declval<T&>().parse_error(\n        std::declval<std::size_t>(), std::declval<const std::string&>(),\n        std::declval<const Exception&>()));\n\ntemplate<typename SAX, typename BasicJsonType>\nstruct is_sax\n{\n  private:\n    static_assert(is_basic_json<BasicJsonType>::value,\n                  \"BasicJsonType must be of type basic_json<...>\");\n\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using exception_t = typename BasicJsonType::exception;\n\n  public:\n    static constexpr bool value =\n        is_detected_exact<bool, null_function_t, SAX>::value &&\n        is_detected_exact<bool, boolean_function_t, SAX>::value &&\n        is_detected_exact<bool, number_integer_function_t, SAX, number_integer_t>::value &&\n        is_detected_exact<bool, number_unsigned_function_t, SAX, number_unsigned_t>::value &&\n        is_detected_exact<bool, number_float_function_t, SAX, number_float_t, string_t>::value &&\n        is_detected_exact<bool, string_function_t, SAX, string_t>::value &&\n        is_detected_exact<bool, binary_function_t, SAX, binary_t>::value &&\n        is_detected_exact<bool, start_object_function_t, SAX>::value &&\n        is_detected_exact<bool, key_function_t, SAX, string_t>::value &&\n        is_detected_exact<bool, end_object_function_t, SAX>::value &&\n        is_detected_exact<bool, start_array_function_t, SAX>::value &&\n        is_detected_exact<bool, end_array_function_t, SAX>::value &&\n        is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value;\n};\n\ntemplate<typename SAX, typename BasicJsonType>\nstruct is_sax_static_asserts\n{\n  private:\n    static_assert(is_basic_json<BasicJsonType>::value,\n                  \"BasicJsonType must be of type basic_json<...>\");\n\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using exception_t = typename BasicJsonType::exception;\n\n  public:\n    static_assert(is_detected_exact<bool, null_function_t, SAX>::value,\n                  \"Missing/invalid function: bool null()\");\n    static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,\n                  \"Missing/invalid function: bool boolean(bool)\");\n    static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,\n                  \"Missing/invalid function: bool boolean(bool)\");\n    static_assert(\n        is_detected_exact<bool, number_integer_function_t, SAX,\n        number_integer_t>::value,\n        \"Missing/invalid function: bool number_integer(number_integer_t)\");\n    static_assert(\n        is_detected_exact<bool, number_unsigned_function_t, SAX,\n        number_unsigned_t>::value,\n        \"Missing/invalid function: bool number_unsigned(number_unsigned_t)\");\n    static_assert(is_detected_exact<bool, number_float_function_t, SAX,\n                  number_float_t, string_t>::value,\n                  \"Missing/invalid function: bool number_float(number_float_t, const string_t&)\");\n    static_assert(\n        is_detected_exact<bool, string_function_t, SAX, string_t>::value,\n        \"Missing/invalid function: bool string(string_t&)\");\n    static_assert(\n        is_detected_exact<bool, binary_function_t, SAX, binary_t>::value,\n        \"Missing/invalid function: bool binary(binary_t&)\");\n    static_assert(is_detected_exact<bool, start_object_function_t, SAX>::value,\n                  \"Missing/invalid function: bool start_object(std::size_t)\");\n    static_assert(is_detected_exact<bool, key_function_t, SAX, string_t>::value,\n                  \"Missing/invalid function: bool key(string_t&)\");\n    static_assert(is_detected_exact<bool, end_object_function_t, SAX>::value,\n                  \"Missing/invalid function: bool end_object()\");\n    static_assert(is_detected_exact<bool, start_array_function_t, SAX>::value,\n                  \"Missing/invalid function: bool start_array(std::size_t)\");\n    static_assert(is_detected_exact<bool, end_array_function_t, SAX>::value,\n                  \"Missing/invalid function: bool end_array()\");\n    static_assert(\n        is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value,\n        \"Missing/invalid function: bool parse_error(std::size_t, const \"\n        \"std::string&, const exception&)\");\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// how to treat CBOR tags\nenum class cbor_tag_handler_t\n{\n    error,   ///< throw a parse_error exception in case of a tag\n    ignore,  ///< ignore tags\n    store    ///< store tags as binary type\n};\n\n/*!\n@brief determine system byte order\n\n@return true if and only if system's byte order is little endian\n\n@note from https://stackoverflow.com/a/1001328/266378\n*/\nstatic inline bool little_endianness(int num = 1) noexcept\n{\n    return *reinterpret_cast<char*>(&num) == 1;\n}\n\n///////////////////\n// binary reader //\n///////////////////\n\n/*!\n@brief deserialization of CBOR, MessagePack, and UBJSON values\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType>>\nclass binary_reader\n{\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using json_sax_t = SAX;\n    using char_type = typename InputAdapterType::char_type;\n    using char_int_type = typename char_traits<char_type>::int_type;\n\n  public:\n    /*!\n    @brief create a binary reader\n\n    @param[in] adapter  input adapter to read from\n    */\n    explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json) noexcept : ia(std::move(adapter)), input_format(format)\n    {\n        (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};\n    }\n\n    // make class move-only\n    binary_reader(const binary_reader&) = delete;\n    binary_reader(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    binary_reader& operator=(const binary_reader&) = delete;\n    binary_reader& operator=(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    ~binary_reader() = default;\n\n    /*!\n    @param[in] format  the binary format to parse\n    @param[in] sax_    a SAX event processor\n    @param[in] strict  whether to expect the input to be consumed completed\n    @param[in] tag_handler  how to treat CBOR tags\n\n    @return whether parsing was successful\n    */\n    JSON_HEDLEY_NON_NULL(3)\n    bool sax_parse(const input_format_t format,\n                   json_sax_t* sax_,\n                   const bool strict = true,\n                   const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        sax = sax_;\n        bool result = false;\n\n        switch (format)\n        {\n            case input_format_t::bson:\n                result = parse_bson_internal();\n                break;\n\n            case input_format_t::cbor:\n                result = parse_cbor_internal(true, tag_handler);\n                break;\n\n            case input_format_t::msgpack:\n                result = parse_msgpack_internal();\n                break;\n\n            case input_format_t::ubjson:\n            case input_format_t::bjdata:\n                result = parse_ubjson_internal();\n                break;\n\n            case input_format_t::json: // LCOV_EXCL_LINE\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n\n        // strict mode: next byte must be EOF\n        if (result && strict)\n        {\n            if (input_format == input_format_t::ubjson || input_format == input_format_t::bjdata)\n            {\n                get_ignore_noop();\n            }\n            else\n            {\n                get();\n            }\n\n            if (JSON_HEDLEY_UNLIKELY(current != char_traits<char_type>::eof()))\n            {\n                return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read,\n                                        exception_message(input_format, concat(\"expected end of input; last byte: 0x\", get_token_string()), \"value\"), nullptr));\n            }\n        }\n\n        return result;\n    }\n\n  private:\n    //////////\n    // BSON //\n    //////////\n\n    /*!\n    @brief Reads in a BSON-object and passes it to the SAX-parser.\n    @return whether a valid BSON-value was passed to the SAX parser\n    */\n    bool parse_bson_internal()\n    {\n        std::int32_t document_size{};\n        get_number<std::int32_t, true>(input_format_t::bson, document_size);\n\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1))))\n        {\n            return false;\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false)))\n        {\n            return false;\n        }\n\n        return sax->end_object();\n    }\n\n    /*!\n    @brief Parses a C-style string from the BSON input.\n    @param[in,out] result  A reference to the string variable where the read\n                            string is to be stored.\n    @return `true` if the \\x00-byte indicating the end of the string was\n             encountered before the EOF; false` indicates an unexpected EOF.\n    */\n    bool get_bson_cstr(string_t& result)\n    {\n        auto out = std::back_inserter(result);\n        while (true)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, \"cstring\")))\n            {\n                return false;\n            }\n            if (current == 0x00)\n            {\n                return true;\n            }\n            *out++ = static_cast<typename string_t::value_type>(current);\n        }\n    }\n\n    /*!\n    @brief Parses a zero-terminated string of length @a len from the BSON\n           input.\n    @param[in] len  The length (including the zero-byte at the end) of the\n                    string to be read.\n    @param[in,out] result  A reference to the string variable where the read\n                            string is to be stored.\n    @tparam NumberType The type of the length @a len\n    @pre len >= 1\n    @return `true` if the string was successfully parsed\n    */\n    template<typename NumberType>\n    bool get_bson_string(const NumberType len, string_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(len < 1))\n        {\n            auto last_token = get_token_string();\n            return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                    exception_message(input_format_t::bson, concat(\"string length must be at least 1, is \", std::to_string(len)), \"string\"), nullptr));\n        }\n\n        return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) && get() != char_traits<char_type>::eof();\n    }\n\n    /*!\n    @brief Parses a byte array input of length @a len from the BSON input.\n    @param[in] len  The length of the byte array to be read.\n    @param[in,out] result  A reference to the binary variable where the read\n                            array is to be stored.\n    @tparam NumberType The type of the length @a len\n    @pre len >= 0\n    @return `true` if the byte array was successfully parsed\n    */\n    template<typename NumberType>\n    bool get_bson_binary(const NumberType len, binary_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(len < 0))\n        {\n            auto last_token = get_token_string();\n            return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                    exception_message(input_format_t::bson, concat(\"byte array length cannot be negative, is \", std::to_string(len)), \"binary\"), nullptr));\n        }\n\n        // All BSON binary values have a subtype\n        std::uint8_t subtype{};\n        get_number<std::uint8_t>(input_format_t::bson, subtype);\n        result.set_subtype(subtype);\n\n        return get_binary(input_format_t::bson, len, result);\n    }\n\n    /*!\n    @brief Read a BSON document element of the given @a element_type.\n    @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html\n    @param[in] element_type_parse_position The position in the input stream,\n               where the `element_type` was read.\n    @warning Not all BSON element types are supported yet. An unsupported\n             @a element_type will give rise to a parse_error.114:\n             Unsupported BSON record type 0x...\n    @return whether a valid BSON-object/array was passed to the SAX parser\n    */\n    bool parse_bson_element_internal(const char_int_type element_type,\n                                     const std::size_t element_type_parse_position)\n    {\n        switch (element_type)\n        {\n            case 0x01: // double\n            {\n                double number{};\n                return get_number<double, true>(input_format_t::bson, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 0x02: // string\n            {\n                std::int32_t len{};\n                string_t value;\n                return get_number<std::int32_t, true>(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value);\n            }\n\n            case 0x03: // object\n            {\n                return parse_bson_internal();\n            }\n\n            case 0x04: // array\n            {\n                return parse_bson_array();\n            }\n\n            case 0x05: // binary\n            {\n                std::int32_t len{};\n                binary_t value;\n                return get_number<std::int32_t, true>(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value);\n            }\n\n            case 0x08: // boolean\n            {\n                return sax->boolean(get() != 0);\n            }\n\n            case 0x0A: // null\n            {\n                return sax->null();\n            }\n\n            case 0x10: // int32\n            {\n                std::int32_t value{};\n                return get_number<std::int32_t, true>(input_format_t::bson, value) && sax->number_integer(value);\n            }\n\n            case 0x12: // int64\n            {\n                std::int64_t value{};\n                return get_number<std::int64_t, true>(input_format_t::bson, value) && sax->number_integer(value);\n            }\n\n            default: // anything else not supported (yet)\n            {\n                std::array<char, 3> cr{{}};\n                static_cast<void>((std::snprintf)(cr.data(), cr.size(), \"%.2hhX\", static_cast<unsigned char>(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n                const std::string cr_str{cr.data()};\n                return sax->parse_error(element_type_parse_position, cr_str,\n                                        parse_error::create(114, element_type_parse_position, concat(\"Unsupported BSON record type 0x\", cr_str), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief Read a BSON element list (as specified in the BSON-spec)\n\n    The same binary layout is used for objects and arrays, hence it must be\n    indicated with the argument @a is_array which one is expected\n    (true --> array, false --> object).\n\n    @param[in] is_array Determines if the element list being read is to be\n                        treated as an object (@a is_array == false), or as an\n                        array (@a is_array == true).\n    @return whether a valid BSON-object/array was passed to the SAX parser\n    */\n    bool parse_bson_element_list(const bool is_array)\n    {\n        string_t key;\n\n        while (auto element_type = get())\n        {\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, \"element list\")))\n            {\n                return false;\n            }\n\n            const std::size_t element_type_parse_position = chars_read;\n            if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))\n            {\n                return false;\n            }\n\n            if (!is_array && !sax->key(key))\n            {\n                return false;\n            }\n\n            if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position)))\n            {\n                return false;\n            }\n\n            // get_bson_cstr only appends\n            key.clear();\n        }\n\n        return true;\n    }\n\n    /*!\n    @brief Reads an array from the BSON input and passes it to the SAX-parser.\n    @return whether a valid BSON-array was passed to the SAX parser\n    */\n    bool parse_bson_array()\n    {\n        std::int32_t document_size{};\n        get_number<std::int32_t, true>(input_format_t::bson, document_size);\n\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1))))\n        {\n            return false;\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true)))\n        {\n            return false;\n        }\n\n        return sax->end_array();\n    }\n\n    //////////\n    // CBOR //\n    //////////\n\n    /*!\n    @param[in] get_char  whether a new character should be retrieved from the\n                         input (true) or whether the last read character should\n                         be considered instead (false)\n    @param[in] tag_handler how CBOR tags should be treated\n\n    @return whether a valid CBOR value was passed to the SAX parser\n    */\n    bool parse_cbor_internal(const bool get_char,\n                             const cbor_tag_handler_t tag_handler)\n    {\n        switch (get_char ? get() : current)\n        {\n            // EOF\n            case char_traits<char_type>::eof():\n                return unexpect_eof(input_format_t::cbor, \"value\");\n\n            // Integer 0x00..0x17 (0..23)\n            case 0x00:\n            case 0x01:\n            case 0x02:\n            case 0x03:\n            case 0x04:\n            case 0x05:\n            case 0x06:\n            case 0x07:\n            case 0x08:\n            case 0x09:\n            case 0x0A:\n            case 0x0B:\n            case 0x0C:\n            case 0x0D:\n            case 0x0E:\n            case 0x0F:\n            case 0x10:\n            case 0x11:\n            case 0x12:\n            case 0x13:\n            case 0x14:\n            case 0x15:\n            case 0x16:\n            case 0x17:\n                return sax->number_unsigned(static_cast<number_unsigned_t>(current));\n\n            case 0x18: // Unsigned integer (one-byte uint8_t follows)\n            {\n                std::uint8_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n            }\n\n            case 0x19: // Unsigned integer (two-byte uint16_t follows)\n            {\n                std::uint16_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n            }\n\n            case 0x1A: // Unsigned integer (four-byte uint32_t follows)\n            {\n                std::uint32_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n            }\n\n            case 0x1B: // Unsigned integer (eight-byte uint64_t follows)\n            {\n                std::uint64_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n            }\n\n            // Negative integer -1-0x00..-1-0x17 (-1..-24)\n            case 0x20:\n            case 0x21:\n            case 0x22:\n            case 0x23:\n            case 0x24:\n            case 0x25:\n            case 0x26:\n            case 0x27:\n            case 0x28:\n            case 0x29:\n            case 0x2A:\n            case 0x2B:\n            case 0x2C:\n            case 0x2D:\n            case 0x2E:\n            case 0x2F:\n            case 0x30:\n            case 0x31:\n            case 0x32:\n            case 0x33:\n            case 0x34:\n            case 0x35:\n            case 0x36:\n            case 0x37:\n                return sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - current));\n\n            case 0x38: // Negative integer (one-byte uint8_t follows)\n            {\n                std::uint8_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);\n            }\n\n            case 0x39: // Negative integer -1-n (two-byte uint16_t follows)\n            {\n                std::uint16_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);\n            }\n\n            case 0x3A: // Negative integer -1-n (four-byte uint32_t follows)\n            {\n                std::uint32_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);\n            }\n\n            case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)\n            {\n                std::uint64_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1)\n                        - static_cast<number_integer_t>(number));\n            }\n\n            // Binary data (0x00..0x17 bytes follow)\n            case 0x40:\n            case 0x41:\n            case 0x42:\n            case 0x43:\n            case 0x44:\n            case 0x45:\n            case 0x46:\n            case 0x47:\n            case 0x48:\n            case 0x49:\n            case 0x4A:\n            case 0x4B:\n            case 0x4C:\n            case 0x4D:\n            case 0x4E:\n            case 0x4F:\n            case 0x50:\n            case 0x51:\n            case 0x52:\n            case 0x53:\n            case 0x54:\n            case 0x55:\n            case 0x56:\n            case 0x57:\n            case 0x58: // Binary data (one-byte uint8_t for n follows)\n            case 0x59: // Binary data (two-byte uint16_t for n follow)\n            case 0x5A: // Binary data (four-byte uint32_t for n follow)\n            case 0x5B: // Binary data (eight-byte uint64_t for n follow)\n            case 0x5F: // Binary data (indefinite length)\n            {\n                binary_t b;\n                return get_cbor_binary(b) && sax->binary(b);\n            }\n\n            // UTF-8 string (0x00..0x17 bytes follow)\n            case 0x60:\n            case 0x61:\n            case 0x62:\n            case 0x63:\n            case 0x64:\n            case 0x65:\n            case 0x66:\n            case 0x67:\n            case 0x68:\n            case 0x69:\n            case 0x6A:\n            case 0x6B:\n            case 0x6C:\n            case 0x6D:\n            case 0x6E:\n            case 0x6F:\n            case 0x70:\n            case 0x71:\n            case 0x72:\n            case 0x73:\n            case 0x74:\n            case 0x75:\n            case 0x76:\n            case 0x77:\n            case 0x78: // UTF-8 string (one-byte uint8_t for n follows)\n            case 0x79: // UTF-8 string (two-byte uint16_t for n follow)\n            case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)\n            case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)\n            case 0x7F: // UTF-8 string (indefinite length)\n            {\n                string_t s;\n                return get_cbor_string(s) && sax->string(s);\n            }\n\n            // array (0x00..0x17 data items follow)\n            case 0x80:\n            case 0x81:\n            case 0x82:\n            case 0x83:\n            case 0x84:\n            case 0x85:\n            case 0x86:\n            case 0x87:\n            case 0x88:\n            case 0x89:\n            case 0x8A:\n            case 0x8B:\n            case 0x8C:\n            case 0x8D:\n            case 0x8E:\n            case 0x8F:\n            case 0x90:\n            case 0x91:\n            case 0x92:\n            case 0x93:\n            case 0x94:\n            case 0x95:\n            case 0x96:\n            case 0x97:\n                return get_cbor_array(\n                           conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu), tag_handler);\n\n            case 0x98: // array (one-byte uint8_t for n follows)\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0x99: // array (two-byte uint16_t for n follow)\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0x9A: // array (four-byte uint32_t for n follow)\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0x9B: // array (eight-byte uint64_t for n follow)\n            {\n                std::uint64_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0x9F: // array (indefinite length)\n                return get_cbor_array(static_cast<std::size_t>(-1), tag_handler);\n\n            // map (0x00..0x17 pairs of data items follow)\n            case 0xA0:\n            case 0xA1:\n            case 0xA2:\n            case 0xA3:\n            case 0xA4:\n            case 0xA5:\n            case 0xA6:\n            case 0xA7:\n            case 0xA8:\n            case 0xA9:\n            case 0xAA:\n            case 0xAB:\n            case 0xAC:\n            case 0xAD:\n            case 0xAE:\n            case 0xAF:\n            case 0xB0:\n            case 0xB1:\n            case 0xB2:\n            case 0xB3:\n            case 0xB4:\n            case 0xB5:\n            case 0xB6:\n            case 0xB7:\n                return get_cbor_object(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu), tag_handler);\n\n            case 0xB8: // map (one-byte uint8_t for n follows)\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0xB9: // map (two-byte uint16_t for n follow)\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0xBA: // map (four-byte uint32_t for n follow)\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0xBB: // map (eight-byte uint64_t for n follow)\n            {\n                std::uint64_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0xBF: // map (indefinite length)\n                return get_cbor_object(static_cast<std::size_t>(-1), tag_handler);\n\n            case 0xC6: // tagged item\n            case 0xC7:\n            case 0xC8:\n            case 0xC9:\n            case 0xCA:\n            case 0xCB:\n            case 0xCC:\n            case 0xCD:\n            case 0xCE:\n            case 0xCF:\n            case 0xD0:\n            case 0xD1:\n            case 0xD2:\n            case 0xD3:\n            case 0xD4:\n            case 0xD8: // tagged item (1 bytes follow)\n            case 0xD9: // tagged item (2 bytes follow)\n            case 0xDA: // tagged item (4 bytes follow)\n            case 0xDB: // tagged item (8 bytes follow)\n            {\n                switch (tag_handler)\n                {\n                    case cbor_tag_handler_t::error:\n                    {\n                        auto last_token = get_token_string();\n                        return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                                exception_message(input_format_t::cbor, concat(\"invalid byte: 0x\", last_token), \"value\"), nullptr));\n                    }\n\n                    case cbor_tag_handler_t::ignore:\n                    {\n                        // ignore binary subtype\n                        switch (current)\n                        {\n                            case 0xD8:\n                            {\n                                std::uint8_t subtype_to_ignore{};\n                                get_number(input_format_t::cbor, subtype_to_ignore);\n                                break;\n                            }\n                            case 0xD9:\n                            {\n                                std::uint16_t subtype_to_ignore{};\n                                get_number(input_format_t::cbor, subtype_to_ignore);\n                                break;\n                            }\n                            case 0xDA:\n                            {\n                                std::uint32_t subtype_to_ignore{};\n                                get_number(input_format_t::cbor, subtype_to_ignore);\n                                break;\n                            }\n                            case 0xDB:\n                            {\n                                std::uint64_t subtype_to_ignore{};\n                                get_number(input_format_t::cbor, subtype_to_ignore);\n                                break;\n                            }\n                            default:\n                                break;\n                        }\n                        return parse_cbor_internal(true, tag_handler);\n                    }\n\n                    case cbor_tag_handler_t::store:\n                    {\n                        binary_t b;\n                        // use binary subtype and store in binary container\n                        switch (current)\n                        {\n                            case 0xD8:\n                            {\n                                std::uint8_t subtype{};\n                                get_number(input_format_t::cbor, subtype);\n                                b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));\n                                break;\n                            }\n                            case 0xD9:\n                            {\n                                std::uint16_t subtype{};\n                                get_number(input_format_t::cbor, subtype);\n                                b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));\n                                break;\n                            }\n                            case 0xDA:\n                            {\n                                std::uint32_t subtype{};\n                                get_number(input_format_t::cbor, subtype);\n                                b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));\n                                break;\n                            }\n                            case 0xDB:\n                            {\n                                std::uint64_t subtype{};\n                                get_number(input_format_t::cbor, subtype);\n                                b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));\n                                break;\n                            }\n                            default:\n                                return parse_cbor_internal(true, tag_handler);\n                        }\n                        get();\n                        return get_cbor_binary(b) && sax->binary(b);\n                    }\n\n                    default:                 // LCOV_EXCL_LINE\n                        JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n                        return false;        // LCOV_EXCL_LINE\n                }\n            }\n\n            case 0xF4: // false\n                return sax->boolean(false);\n\n            case 0xF5: // true\n                return sax->boolean(true);\n\n            case 0xF6: // null\n                return sax->null();\n\n            case 0xF9: // Half-Precision Float (two-byte IEEE 754)\n            {\n                const auto byte1_raw = get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"number\")))\n                {\n                    return false;\n                }\n                const auto byte2_raw = get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"number\")))\n                {\n                    return false;\n                }\n\n                const auto byte1 = static_cast<unsigned char>(byte1_raw);\n                const auto byte2 = static_cast<unsigned char>(byte2_raw);\n\n                // code from RFC 7049, Appendix D, Figure 3:\n                // As half-precision floating-point numbers were only added\n                // to IEEE 754 in 2008, today's programming platforms often\n                // still only have limited support for them. It is very\n                // easy to include at least decoding support for them even\n                // without such support. An example of a small decoder for\n                // half-precision floating-point numbers in the C language\n                // is shown in Fig. 3.\n                const auto half = static_cast<unsigned int>((byte1 << 8u) + byte2);\n                const double val = [&half]\n                {\n                    const int exp = (half >> 10u) & 0x1Fu;\n                    const unsigned int mant = half & 0x3FFu;\n                    JSON_ASSERT(0 <= exp&& exp <= 32);\n                    JSON_ASSERT(mant <= 1024);\n                    switch (exp)\n                    {\n                        case 0:\n                            return std::ldexp(mant, -24);\n                        case 31:\n                            return (mant == 0)\n                            ? std::numeric_limits<double>::infinity()\n                            : std::numeric_limits<double>::quiet_NaN();\n                        default:\n                            return std::ldexp(mant + 1024, exp - 25);\n                    }\n                }();\n                return sax->number_float((half & 0x8000u) != 0\n                                         ? static_cast<number_float_t>(-val)\n                                         : static_cast<number_float_t>(val), \"\");\n            }\n\n            case 0xFA: // Single-Precision Float (four-byte IEEE 754)\n            {\n                float number{};\n                return get_number(input_format_t::cbor, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 0xFB: // Double-Precision Float (eight-byte IEEE 754)\n            {\n                double number{};\n                return get_number(input_format_t::cbor, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            default: // anything else (0xFF is handled inside the other types)\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format_t::cbor, concat(\"invalid byte: 0x\", last_token), \"value\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief reads a CBOR string\n\n    This function first reads starting bytes to determine the expected\n    string length and then copies this number of bytes into a string.\n    Additionally, CBOR's strings with indefinite lengths are supported.\n\n    @param[out] result  created string\n\n    @return whether string creation completed\n    */\n    bool get_cbor_string(string_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"string\")))\n        {\n            return false;\n        }\n\n        switch (current)\n        {\n            // UTF-8 string (0x00..0x17 bytes follow)\n            case 0x60:\n            case 0x61:\n            case 0x62:\n            case 0x63:\n            case 0x64:\n            case 0x65:\n            case 0x66:\n            case 0x67:\n            case 0x68:\n            case 0x69:\n            case 0x6A:\n            case 0x6B:\n            case 0x6C:\n            case 0x6D:\n            case 0x6E:\n            case 0x6F:\n            case 0x70:\n            case 0x71:\n            case 0x72:\n            case 0x73:\n            case 0x74:\n            case 0x75:\n            case 0x76:\n            case 0x77:\n            {\n                return get_string(input_format_t::cbor, static_cast<unsigned int>(current) & 0x1Fu, result);\n            }\n\n            case 0x78: // UTF-8 string (one-byte uint8_t for n follows)\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);\n            }\n\n            case 0x79: // UTF-8 string (two-byte uint16_t for n follow)\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);\n            }\n\n            case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);\n            }\n\n            case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)\n            {\n                std::uint64_t len{};\n                return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);\n            }\n\n            case 0x7F: // UTF-8 string (indefinite length)\n            {\n                while (get() != 0xFF)\n                {\n                    string_t chunk;\n                    if (!get_cbor_string(chunk))\n                    {\n                        return false;\n                    }\n                    result.append(chunk);\n                }\n                return true;\n            }\n\n            default:\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,\n                                        exception_message(input_format_t::cbor, concat(\"expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x\", last_token), \"string\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief reads a CBOR byte array\n\n    This function first reads starting bytes to determine the expected\n    byte array length and then copies this number of bytes into the byte array.\n    Additionally, CBOR's byte arrays with indefinite lengths are supported.\n\n    @param[out] result  created byte array\n\n    @return whether byte array creation completed\n    */\n    bool get_cbor_binary(binary_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"binary\")))\n        {\n            return false;\n        }\n\n        switch (current)\n        {\n            // Binary data (0x00..0x17 bytes follow)\n            case 0x40:\n            case 0x41:\n            case 0x42:\n            case 0x43:\n            case 0x44:\n            case 0x45:\n            case 0x46:\n            case 0x47:\n            case 0x48:\n            case 0x49:\n            case 0x4A:\n            case 0x4B:\n            case 0x4C:\n            case 0x4D:\n            case 0x4E:\n            case 0x4F:\n            case 0x50:\n            case 0x51:\n            case 0x52:\n            case 0x53:\n            case 0x54:\n            case 0x55:\n            case 0x56:\n            case 0x57:\n            {\n                return get_binary(input_format_t::cbor, static_cast<unsigned int>(current) & 0x1Fu, result);\n            }\n\n            case 0x58: // Binary data (one-byte uint8_t for n follows)\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::cbor, len) &&\n                       get_binary(input_format_t::cbor, len, result);\n            }\n\n            case 0x59: // Binary data (two-byte uint16_t for n follow)\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::cbor, len) &&\n                       get_binary(input_format_t::cbor, len, result);\n            }\n\n            case 0x5A: // Binary data (four-byte uint32_t for n follow)\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::cbor, len) &&\n                       get_binary(input_format_t::cbor, len, result);\n            }\n\n            case 0x5B: // Binary data (eight-byte uint64_t for n follow)\n            {\n                std::uint64_t len{};\n                return get_number(input_format_t::cbor, len) &&\n                       get_binary(input_format_t::cbor, len, result);\n            }\n\n            case 0x5F: // Binary data (indefinite length)\n            {\n                while (get() != 0xFF)\n                {\n                    binary_t chunk;\n                    if (!get_cbor_binary(chunk))\n                    {\n                        return false;\n                    }\n                    result.insert(result.end(), chunk.begin(), chunk.end());\n                }\n                return true;\n            }\n\n            default:\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,\n                                        exception_message(input_format_t::cbor, concat(\"expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x\", last_token), \"binary\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @param[in] len  the length of the array or static_cast<std::size_t>(-1) for an\n                    array of indefinite size\n    @param[in] tag_handler how CBOR tags should be treated\n    @return whether array creation completed\n    */\n    bool get_cbor_array(const std::size_t len,\n                        const cbor_tag_handler_t tag_handler)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len)))\n        {\n            return false;\n        }\n\n        if (len != static_cast<std::size_t>(-1))\n        {\n            for (std::size_t i = 0; i < len; ++i)\n            {\n                if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler)))\n                {\n                    return false;\n                }\n            }\n        }\n        else\n        {\n            while (get() != 0xFF)\n            {\n                if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler)))\n                {\n                    return false;\n                }\n            }\n        }\n\n        return sax->end_array();\n    }\n\n    /*!\n    @param[in] len  the length of the object or static_cast<std::size_t>(-1) for an\n                    object of indefinite size\n    @param[in] tag_handler how CBOR tags should be treated\n    @return whether object creation completed\n    */\n    bool get_cbor_object(const std::size_t len,\n                         const cbor_tag_handler_t tag_handler)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len)))\n        {\n            return false;\n        }\n\n        if (len != 0)\n        {\n            string_t key;\n            if (len != static_cast<std::size_t>(-1))\n            {\n                for (std::size_t i = 0; i < len; ++i)\n                {\n                    get();\n                    if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))\n                    {\n                        return false;\n                    }\n\n                    if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler)))\n                    {\n                        return false;\n                    }\n                    key.clear();\n                }\n            }\n            else\n            {\n                while (get() != 0xFF)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))\n                    {\n                        return false;\n                    }\n\n                    if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler)))\n                    {\n                        return false;\n                    }\n                    key.clear();\n                }\n            }\n        }\n\n        return sax->end_object();\n    }\n\n    /////////////\n    // MsgPack //\n    /////////////\n\n    /*!\n    @return whether a valid MessagePack value was passed to the SAX parser\n    */\n    bool parse_msgpack_internal()\n    {\n        switch (get())\n        {\n            // EOF\n            case char_traits<char_type>::eof():\n                return unexpect_eof(input_format_t::msgpack, \"value\");\n\n            // positive fixint\n            case 0x00:\n            case 0x01:\n            case 0x02:\n            case 0x03:\n            case 0x04:\n            case 0x05:\n            case 0x06:\n            case 0x07:\n            case 0x08:\n            case 0x09:\n            case 0x0A:\n            case 0x0B:\n            case 0x0C:\n            case 0x0D:\n            case 0x0E:\n            case 0x0F:\n            case 0x10:\n            case 0x11:\n            case 0x12:\n            case 0x13:\n            case 0x14:\n            case 0x15:\n            case 0x16:\n            case 0x17:\n            case 0x18:\n            case 0x19:\n            case 0x1A:\n            case 0x1B:\n            case 0x1C:\n            case 0x1D:\n            case 0x1E:\n            case 0x1F:\n            case 0x20:\n            case 0x21:\n            case 0x22:\n            case 0x23:\n            case 0x24:\n            case 0x25:\n            case 0x26:\n            case 0x27:\n            case 0x28:\n            case 0x29:\n            case 0x2A:\n            case 0x2B:\n            case 0x2C:\n            case 0x2D:\n            case 0x2E:\n            case 0x2F:\n            case 0x30:\n            case 0x31:\n            case 0x32:\n            case 0x33:\n            case 0x34:\n            case 0x35:\n            case 0x36:\n            case 0x37:\n            case 0x38:\n            case 0x39:\n            case 0x3A:\n            case 0x3B:\n            case 0x3C:\n            case 0x3D:\n            case 0x3E:\n            case 0x3F:\n            case 0x40:\n            case 0x41:\n            case 0x42:\n            case 0x43:\n            case 0x44:\n            case 0x45:\n            case 0x46:\n            case 0x47:\n            case 0x48:\n            case 0x49:\n            case 0x4A:\n            case 0x4B:\n            case 0x4C:\n            case 0x4D:\n            case 0x4E:\n            case 0x4F:\n            case 0x50:\n            case 0x51:\n            case 0x52:\n            case 0x53:\n            case 0x54:\n            case 0x55:\n            case 0x56:\n            case 0x57:\n            case 0x58:\n            case 0x59:\n            case 0x5A:\n            case 0x5B:\n            case 0x5C:\n            case 0x5D:\n            case 0x5E:\n            case 0x5F:\n            case 0x60:\n            case 0x61:\n            case 0x62:\n            case 0x63:\n            case 0x64:\n            case 0x65:\n            case 0x66:\n            case 0x67:\n            case 0x68:\n            case 0x69:\n            case 0x6A:\n            case 0x6B:\n            case 0x6C:\n            case 0x6D:\n            case 0x6E:\n            case 0x6F:\n            case 0x70:\n            case 0x71:\n            case 0x72:\n            case 0x73:\n            case 0x74:\n            case 0x75:\n            case 0x76:\n            case 0x77:\n            case 0x78:\n            case 0x79:\n            case 0x7A:\n            case 0x7B:\n            case 0x7C:\n            case 0x7D:\n            case 0x7E:\n            case 0x7F:\n                return sax->number_unsigned(static_cast<number_unsigned_t>(current));\n\n            // fixmap\n            case 0x80:\n            case 0x81:\n            case 0x82:\n            case 0x83:\n            case 0x84:\n            case 0x85:\n            case 0x86:\n            case 0x87:\n            case 0x88:\n            case 0x89:\n            case 0x8A:\n            case 0x8B:\n            case 0x8C:\n            case 0x8D:\n            case 0x8E:\n            case 0x8F:\n                return get_msgpack_object(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));\n\n            // fixarray\n            case 0x90:\n            case 0x91:\n            case 0x92:\n            case 0x93:\n            case 0x94:\n            case 0x95:\n            case 0x96:\n            case 0x97:\n            case 0x98:\n            case 0x99:\n            case 0x9A:\n            case 0x9B:\n            case 0x9C:\n            case 0x9D:\n            case 0x9E:\n            case 0x9F:\n                return get_msgpack_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));\n\n            // fixstr\n            case 0xA0:\n            case 0xA1:\n            case 0xA2:\n            case 0xA3:\n            case 0xA4:\n            case 0xA5:\n            case 0xA6:\n            case 0xA7:\n            case 0xA8:\n            case 0xA9:\n            case 0xAA:\n            case 0xAB:\n            case 0xAC:\n            case 0xAD:\n            case 0xAE:\n            case 0xAF:\n            case 0xB0:\n            case 0xB1:\n            case 0xB2:\n            case 0xB3:\n            case 0xB4:\n            case 0xB5:\n            case 0xB6:\n            case 0xB7:\n            case 0xB8:\n            case 0xB9:\n            case 0xBA:\n            case 0xBB:\n            case 0xBC:\n            case 0xBD:\n            case 0xBE:\n            case 0xBF:\n            case 0xD9: // str 8\n            case 0xDA: // str 16\n            case 0xDB: // str 32\n            {\n                string_t s;\n                return get_msgpack_string(s) && sax->string(s);\n            }\n\n            case 0xC0: // nil\n                return sax->null();\n\n            case 0xC2: // false\n                return sax->boolean(false);\n\n            case 0xC3: // true\n                return sax->boolean(true);\n\n            case 0xC4: // bin 8\n            case 0xC5: // bin 16\n            case 0xC6: // bin 32\n            case 0xC7: // ext 8\n            case 0xC8: // ext 16\n            case 0xC9: // ext 32\n            case 0xD4: // fixext 1\n            case 0xD5: // fixext 2\n            case 0xD6: // fixext 4\n            case 0xD7: // fixext 8\n            case 0xD8: // fixext 16\n            {\n                binary_t b;\n                return get_msgpack_binary(b) && sax->binary(b);\n            }\n\n            case 0xCA: // float 32\n            {\n                float number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 0xCB: // float 64\n            {\n                double number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 0xCC: // uint 8\n            {\n                std::uint8_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number);\n            }\n\n            case 0xCD: // uint 16\n            {\n                std::uint16_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number);\n            }\n\n            case 0xCE: // uint 32\n            {\n                std::uint32_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number);\n            }\n\n            case 0xCF: // uint 64\n            {\n                std::uint64_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number);\n            }\n\n            case 0xD0: // int 8\n            {\n                std::int8_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_integer(number);\n            }\n\n            case 0xD1: // int 16\n            {\n                std::int16_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_integer(number);\n            }\n\n            case 0xD2: // int 32\n            {\n                std::int32_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_integer(number);\n            }\n\n            case 0xD3: // int 64\n            {\n                std::int64_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_integer(number);\n            }\n\n            case 0xDC: // array 16\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast<std::size_t>(len));\n            }\n\n            case 0xDD: // array 32\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::msgpack, len) && get_msgpack_array(conditional_static_cast<std::size_t>(len));\n            }\n\n            case 0xDE: // map 16\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast<std::size_t>(len));\n            }\n\n            case 0xDF: // map 32\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::msgpack, len) && get_msgpack_object(conditional_static_cast<std::size_t>(len));\n            }\n\n            // negative fixint\n            case 0xE0:\n            case 0xE1:\n            case 0xE2:\n            case 0xE3:\n            case 0xE4:\n            case 0xE5:\n            case 0xE6:\n            case 0xE7:\n            case 0xE8:\n            case 0xE9:\n            case 0xEA:\n            case 0xEB:\n            case 0xEC:\n            case 0xED:\n            case 0xEE:\n            case 0xEF:\n            case 0xF0:\n            case 0xF1:\n            case 0xF2:\n            case 0xF3:\n            case 0xF4:\n            case 0xF5:\n            case 0xF6:\n            case 0xF7:\n            case 0xF8:\n            case 0xF9:\n            case 0xFA:\n            case 0xFB:\n            case 0xFC:\n            case 0xFD:\n            case 0xFE:\n            case 0xFF:\n                return sax->number_integer(static_cast<std::int8_t>(current));\n\n            default: // anything else\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format_t::msgpack, concat(\"invalid byte: 0x\", last_token), \"value\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief reads a MessagePack string\n\n    This function first reads starting bytes to determine the expected\n    string length and then copies this number of bytes into a string.\n\n    @param[out] result  created string\n\n    @return whether string creation completed\n    */\n    bool get_msgpack_string(string_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, \"string\")))\n        {\n            return false;\n        }\n\n        switch (current)\n        {\n            // fixstr\n            case 0xA0:\n            case 0xA1:\n            case 0xA2:\n            case 0xA3:\n            case 0xA4:\n            case 0xA5:\n            case 0xA6:\n            case 0xA7:\n            case 0xA8:\n            case 0xA9:\n            case 0xAA:\n            case 0xAB:\n            case 0xAC:\n            case 0xAD:\n            case 0xAE:\n            case 0xAF:\n            case 0xB0:\n            case 0xB1:\n            case 0xB2:\n            case 0xB3:\n            case 0xB4:\n            case 0xB5:\n            case 0xB6:\n            case 0xB7:\n            case 0xB8:\n            case 0xB9:\n            case 0xBA:\n            case 0xBB:\n            case 0xBC:\n            case 0xBD:\n            case 0xBE:\n            case 0xBF:\n            {\n                return get_string(input_format_t::msgpack, static_cast<unsigned int>(current) & 0x1Fu, result);\n            }\n\n            case 0xD9: // str 8\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result);\n            }\n\n            case 0xDA: // str 16\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result);\n            }\n\n            case 0xDB: // str 32\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result);\n            }\n\n            default:\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,\n                                        exception_message(input_format_t::msgpack, concat(\"expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x\", last_token), \"string\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief reads a MessagePack byte array\n\n    This function first reads starting bytes to determine the expected\n    byte array length and then copies this number of bytes into a byte array.\n\n    @param[out] result  created byte array\n\n    @return whether byte array creation completed\n    */\n    bool get_msgpack_binary(binary_t& result)\n    {\n        // helper function to set the subtype\n        auto assign_and_return_true = [&result](std::int8_t subtype)\n        {\n            result.set_subtype(static_cast<std::uint8_t>(subtype));\n            return true;\n        };\n\n        switch (current)\n        {\n            case 0xC4: // bin 8\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_binary(input_format_t::msgpack, len, result);\n            }\n\n            case 0xC5: // bin 16\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_binary(input_format_t::msgpack, len, result);\n            }\n\n            case 0xC6: // bin 32\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_binary(input_format_t::msgpack, len, result);\n            }\n\n            case 0xC7: // ext 8\n            {\n                std::uint8_t len{};\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, len, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xC8: // ext 16\n            {\n                std::uint16_t len{};\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, len, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xC9: // ext 32\n            {\n                std::uint32_t len{};\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, len, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD4: // fixext 1\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 1, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD5: // fixext 2\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 2, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD6: // fixext 4\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 4, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD7: // fixext 8\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 8, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD8: // fixext 16\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 16, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            default:           // LCOV_EXCL_LINE\n                return false;  // LCOV_EXCL_LINE\n        }\n    }\n\n    /*!\n    @param[in] len  the length of the array\n    @return whether array creation completed\n    */\n    bool get_msgpack_array(const std::size_t len)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len)))\n        {\n            return false;\n        }\n\n        for (std::size_t i = 0; i < len; ++i)\n        {\n            if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal()))\n            {\n                return false;\n            }\n        }\n\n        return sax->end_array();\n    }\n\n    /*!\n    @param[in] len  the length of the object\n    @return whether object creation completed\n    */\n    bool get_msgpack_object(const std::size_t len)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len)))\n        {\n            return false;\n        }\n\n        string_t key;\n        for (std::size_t i = 0; i < len; ++i)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))\n            {\n                return false;\n            }\n\n            if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal()))\n            {\n                return false;\n            }\n            key.clear();\n        }\n\n        return sax->end_object();\n    }\n\n    ////////////\n    // UBJSON //\n    ////////////\n\n    /*!\n    @param[in] get_char  whether a new character should be retrieved from the\n                         input (true, default) or whether the last read\n                         character should be considered instead\n\n    @return whether a valid UBJSON value was passed to the SAX parser\n    */\n    bool parse_ubjson_internal(const bool get_char = true)\n    {\n        return get_ubjson_value(get_char ? get_ignore_noop() : current);\n    }\n\n    /*!\n    @brief reads a UBJSON string\n\n    This function is either called after reading the 'S' byte explicitly\n    indicating a string, or in case of an object key where the 'S' byte can be\n    left out.\n\n    @param[out] result   created string\n    @param[in] get_char  whether a new character should be retrieved from the\n                         input (true, default) or whether the last read\n                         character should be considered instead\n\n    @return whether string creation completed\n    */\n    bool get_ubjson_string(string_t& result, const bool get_char = true)\n    {\n        if (get_char)\n        {\n            get();  // TODO(niels): may we ignore N here?\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"value\")))\n        {\n            return false;\n        }\n\n        switch (current)\n        {\n            case 'U':\n            {\n                std::uint8_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'i':\n            {\n                std::int8_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'I':\n            {\n                std::int16_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'l':\n            {\n                std::int32_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'L':\n            {\n                std::int64_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'u':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint16_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'm':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint32_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'M':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint64_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            default:\n                break;\n        }\n        auto last_token = get_token_string();\n        std::string message;\n\n        if (input_format != input_format_t::bjdata)\n        {\n            message = \"expected length type specification (U, i, I, l, L); last byte: 0x\" + last_token;\n        }\n        else\n        {\n            message = \"expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x\" + last_token;\n        }\n        return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, \"string\"), nullptr));\n    }\n\n    /*!\n    @param[out] dim  an integer vector storing the ND array dimensions\n    @return whether reading ND array size vector is successful\n    */\n    bool get_ubjson_ndarray_size(std::vector<size_t>& dim)\n    {\n        std::pair<std::size_t, char_int_type> size_and_type;\n        size_t dimlen = 0;\n        bool no_ndarray = true;\n\n        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, no_ndarray)))\n        {\n            return false;\n        }\n\n        if (size_and_type.first != npos)\n        {\n            if (size_and_type.second != 0)\n            {\n                if (size_and_type.second != 'N')\n                {\n                    for (std::size_t i = 0; i < size_and_type.first; ++i)\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))\n                        {\n                            return false;\n                        }\n                        dim.push_back(dimlen);\n                    }\n                }\n            }\n            else\n            {\n                for (std::size_t i = 0; i < size_and_type.first; ++i)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray)))\n                    {\n                        return false;\n                    }\n                    dim.push_back(dimlen);\n                }\n            }\n        }\n        else\n        {\n            while (current != ']')\n            {\n                if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, current)))\n                {\n                    return false;\n                }\n                dim.push_back(dimlen);\n                get_ignore_noop();\n            }\n        }\n        return true;\n    }\n\n    /*!\n    @param[out] result  determined size\n    @param[in,out] is_ndarray  for input, `true` means already inside an ndarray vector\n                               or ndarray dimension is not allowed; `false` means ndarray\n                               is allowed; for output, `true` means an ndarray is found;\n                               is_ndarray can only return `true` when its initial value\n                               is `false`\n    @param[in] prefix  type marker if already read, otherwise set to 0\n\n    @return whether size determination completed\n    */\n    bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0)\n    {\n        if (prefix == 0)\n        {\n            prefix = get_ignore_noop();\n        }\n\n        switch (prefix)\n        {\n            case 'U':\n            {\n                std::uint8_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'i':\n            {\n                std::int8_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (number < 0)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,\n                                            exception_message(input_format, \"count in an optimized container must be positive\", \"size\"), nullptr));\n                }\n                result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char\n                return true;\n            }\n\n            case 'I':\n            {\n                std::int16_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (number < 0)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,\n                                            exception_message(input_format, \"count in an optimized container must be positive\", \"size\"), nullptr));\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'l':\n            {\n                std::int32_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (number < 0)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,\n                                            exception_message(input_format, \"count in an optimized container must be positive\", \"size\"), nullptr));\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'L':\n            {\n                std::int64_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (number < 0)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,\n                                            exception_message(input_format, \"count in an optimized container must be positive\", \"size\"), nullptr));\n                }\n                if (!value_in_range_of<std::size_t>(number))\n                {\n                    return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408,\n                                            exception_message(input_format, \"integer value overflow\", \"size\"), nullptr));\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'u':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint16_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'm':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint32_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                result = conditional_static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'M':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint64_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (!value_in_range_of<std::size_t>(number))\n                {\n                    return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408,\n                                            exception_message(input_format, \"integer value overflow\", \"size\"), nullptr));\n                }\n                result = detail::conditional_static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case '[':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, \"ndarray dimensional vector is not allowed\", \"size\"), nullptr));\n                }\n                std::vector<size_t> dim;\n                if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim)))\n                {\n                    return false;\n                }\n                if (dim.size() == 1 || (dim.size() == 2 && dim.at(0) == 1)) // return normal array size if 1D row vector\n                {\n                    result = dim.at(dim.size() - 1);\n                    return true;\n                }\n                if (!dim.empty())  // if ndarray, convert to an object in JData annotated array format\n                {\n                    for (auto i : dim) // test if any dimension in an ndarray is 0, if so, return a 1D empty container\n                    {\n                        if ( i == 0 )\n                        {\n                            result = 0;\n                            return true;\n                        }\n                    }\n\n                    string_t key = \"_ArraySize_\";\n                    if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size())))\n                    {\n                        return false;\n                    }\n                    result = 1;\n                    for (auto i : dim)\n                    {\n                        result *= i;\n                        if (result == 0 || result == npos) // because dim elements shall not have zeros, result = 0 means overflow happened; it also can't be npos as it is used to initialize size in get_ubjson_size_type()\n                        {\n                            return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, \"excessive ndarray size caused overflow\", \"size\"), nullptr));\n                        }\n                        if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast<number_unsigned_t>(i))))\n                        {\n                            return false;\n                        }\n                    }\n                    is_ndarray = true;\n                    return sax->end_array();\n                }\n                result = 0;\n                return true;\n            }\n\n            default:\n                break;\n        }\n        auto last_token = get_token_string();\n        std::string message;\n\n        if (input_format != input_format_t::bjdata)\n        {\n            message = \"expected length type specification (U, i, I, l, L) after '#'; last byte: 0x\" + last_token;\n        }\n        else\n        {\n            message = \"expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x\" + last_token;\n        }\n        return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, \"size\"), nullptr));\n    }\n\n    /*!\n    @brief determine the type and size for a container\n\n    In the optimized UBJSON format, a type and a size can be provided to allow\n    for a more compact representation.\n\n    @param[out] result  pair of the size and the type\n    @param[in] inside_ndarray  whether the parser is parsing an ND array dimensional vector\n\n    @return whether pair creation completed\n    */\n    bool get_ubjson_size_type(std::pair<std::size_t, char_int_type>& result, bool inside_ndarray = false)\n    {\n        result.first = npos; // size\n        result.second = 0; // type\n        bool is_ndarray = false;\n\n        get_ignore_noop();\n\n        if (current == '$')\n        {\n            result.second = get();  // must not ignore 'N', because 'N' maybe the type\n            if (input_format == input_format_t::bjdata\n                    && JSON_HEDLEY_UNLIKELY(std::binary_search(bjd_optimized_type_markers.begin(), bjd_optimized_type_markers.end(), result.second)))\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format, concat(\"marker 0x\", last_token, \" is not a permitted optimized array type\"), \"type\"), nullptr));\n            }\n\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"type\")))\n            {\n                return false;\n            }\n\n            get_ignore_noop();\n            if (JSON_HEDLEY_UNLIKELY(current != '#'))\n            {\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"value\")))\n                {\n                    return false;\n                }\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format, concat(\"expected '#' after type information; last byte: 0x\", last_token), \"size\"), nullptr));\n            }\n\n            const bool is_error = get_ubjson_size_value(result.first, is_ndarray);\n            if (input_format == input_format_t::bjdata && is_ndarray)\n            {\n                if (inside_ndarray)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read,\n                                            exception_message(input_format, \"ndarray can not be recursive\", \"size\"), nullptr));\n                }\n                result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters\n            }\n            return is_error;\n        }\n\n        if (current == '#')\n        {\n            const bool is_error = get_ubjson_size_value(result.first, is_ndarray);\n            if (input_format == input_format_t::bjdata && is_ndarray)\n            {\n                return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read,\n                                        exception_message(input_format, \"ndarray requires both type and size\", \"size\"), nullptr));\n            }\n            return is_error;\n        }\n\n        return true;\n    }\n\n    /*!\n    @param prefix  the previously read or set type prefix\n    @return whether value creation completed\n    */\n    bool get_ubjson_value(const char_int_type prefix)\n    {\n        switch (prefix)\n        {\n            case char_traits<char_type>::eof():  // EOF\n                return unexpect_eof(input_format, \"value\");\n\n            case 'T':  // true\n                return sax->boolean(true);\n            case 'F':  // false\n                return sax->boolean(false);\n\n            case 'Z':  // null\n                return sax->null();\n\n            case 'U':\n            {\n                std::uint8_t number{};\n                return get_number(input_format, number) && sax->number_unsigned(number);\n            }\n\n            case 'i':\n            {\n                std::int8_t number{};\n                return get_number(input_format, number) && sax->number_integer(number);\n            }\n\n            case 'I':\n            {\n                std::int16_t number{};\n                return get_number(input_format, number) && sax->number_integer(number);\n            }\n\n            case 'l':\n            {\n                std::int32_t number{};\n                return get_number(input_format, number) && sax->number_integer(number);\n            }\n\n            case 'L':\n            {\n                std::int64_t number{};\n                return get_number(input_format, number) && sax->number_integer(number);\n            }\n\n            case 'u':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint16_t number{};\n                return get_number(input_format, number) && sax->number_unsigned(number);\n            }\n\n            case 'm':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint32_t number{};\n                return get_number(input_format, number) && sax->number_unsigned(number);\n            }\n\n            case 'M':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint64_t number{};\n                return get_number(input_format, number) && sax->number_unsigned(number);\n            }\n\n            case 'h':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                const auto byte1_raw = get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"number\")))\n                {\n                    return false;\n                }\n                const auto byte2_raw = get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"number\")))\n                {\n                    return false;\n                }\n\n                const auto byte1 = static_cast<unsigned char>(byte1_raw);\n                const auto byte2 = static_cast<unsigned char>(byte2_raw);\n\n                // code from RFC 7049, Appendix D, Figure 3:\n                // As half-precision floating-point numbers were only added\n                // to IEEE 754 in 2008, today's programming platforms often\n                // still only have limited support for them. It is very\n                // easy to include at least decoding support for them even\n                // without such support. An example of a small decoder for\n                // half-precision floating-point numbers in the C language\n                // is shown in Fig. 3.\n                const auto half = static_cast<unsigned int>((byte2 << 8u) + byte1);\n                const double val = [&half]\n                {\n                    const int exp = (half >> 10u) & 0x1Fu;\n                    const unsigned int mant = half & 0x3FFu;\n                    JSON_ASSERT(0 <= exp&& exp <= 32);\n                    JSON_ASSERT(mant <= 1024);\n                    switch (exp)\n                    {\n                        case 0:\n                            return std::ldexp(mant, -24);\n                        case 31:\n                            return (mant == 0)\n                            ? std::numeric_limits<double>::infinity()\n                            : std::numeric_limits<double>::quiet_NaN();\n                        default:\n                            return std::ldexp(mant + 1024, exp - 25);\n                    }\n                }();\n                return sax->number_float((half & 0x8000u) != 0\n                                         ? static_cast<number_float_t>(-val)\n                                         : static_cast<number_float_t>(val), \"\");\n            }\n\n            case 'd':\n            {\n                float number{};\n                return get_number(input_format, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 'D':\n            {\n                double number{};\n                return get_number(input_format, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 'H':\n            {\n                return get_ubjson_high_precision_number();\n            }\n\n            case 'C':  // char\n            {\n                get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"char\")))\n                {\n                    return false;\n                }\n                if (JSON_HEDLEY_UNLIKELY(current > 127))\n                {\n                    auto last_token = get_token_string();\n                    return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,\n                                            exception_message(input_format, concat(\"byte after 'C' must be in range 0x00..0x7F; last byte: 0x\", last_token), \"char\"), nullptr));\n                }\n                string_t s(1, static_cast<typename string_t::value_type>(current));\n                return sax->string(s);\n            }\n\n            case 'S':  // string\n            {\n                string_t s;\n                return get_ubjson_string(s) && sax->string(s);\n            }\n\n            case '[':  // array\n                return get_ubjson_array();\n\n            case '{':  // object\n                return get_ubjson_object();\n\n            default: // anything else\n                break;\n        }\n        auto last_token = get_token_string();\n        return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format, \"invalid byte: 0x\" + last_token, \"value\"), nullptr));\n    }\n\n    /*!\n    @return whether array creation completed\n    */\n    bool get_ubjson_array()\n    {\n        std::pair<std::size_t, char_int_type> size_and_type;\n        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type)))\n        {\n            return false;\n        }\n\n        // if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):\n        // {\"_ArrayType_\" : \"typeid\", \"_ArraySize_\" : [n1, n2, ...], \"_ArrayData_\" : [v1, v2, ...]}\n\n        if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)\n        {\n            size_and_type.second &= ~(static_cast<char_int_type>(1) << 8);  // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker\n            auto it = std::lower_bound(bjd_types_map.begin(), bjd_types_map.end(), size_and_type.second, [](const bjd_type & p, char_int_type t)\n            {\n                return p.first < t;\n            });\n            string_t key = \"_ArrayType_\";\n            if (JSON_HEDLEY_UNLIKELY(it == bjd_types_map.end() || it->first != size_and_type.second))\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format, \"invalid byte: 0x\" + last_token, \"type\"), nullptr));\n            }\n\n            string_t type = it->second; // sax->string() takes a reference\n            if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(type)))\n            {\n                return false;\n            }\n\n            if (size_and_type.second == 'C')\n            {\n                size_and_type.second = 'U';\n            }\n\n            key = \"_ArrayData_\";\n            if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) ))\n            {\n                return false;\n            }\n\n            for (std::size_t i = 0; i < size_and_type.first; ++i)\n            {\n                if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))\n                {\n                    return false;\n                }\n            }\n\n            return (sax->end_array() && sax->end_object());\n        }\n\n        if (size_and_type.first != npos)\n        {\n            if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first)))\n            {\n                return false;\n            }\n\n            if (size_and_type.second != 0)\n            {\n                if (size_and_type.second != 'N')\n                {\n                    for (std::size_t i = 0; i < size_and_type.first; ++i)\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))\n                        {\n                            return false;\n                        }\n                    }\n                }\n            }\n            else\n            {\n                for (std::size_t i = 0; i < size_and_type.first; ++i)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal()))\n                    {\n                        return false;\n                    }\n                }\n            }\n        }\n        else\n        {\n            if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1))))\n            {\n                return false;\n            }\n\n            while (current != ']')\n            {\n                if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false)))\n                {\n                    return false;\n                }\n                get_ignore_noop();\n            }\n        }\n\n        return sax->end_array();\n    }\n\n    /*!\n    @return whether object creation completed\n    */\n    bool get_ubjson_object()\n    {\n        std::pair<std::size_t, char_int_type> size_and_type;\n        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type)))\n        {\n            return false;\n        }\n\n        // do not accept ND-array size in objects in BJData\n        if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)\n        {\n            auto last_token = get_token_string();\n            return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                    exception_message(input_format, \"BJData object does not support ND-array size in optimized format\", \"object\"), nullptr));\n        }\n\n        string_t key;\n        if (size_and_type.first != npos)\n        {\n            if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first)))\n            {\n                return false;\n            }\n\n            if (size_and_type.second != 0)\n            {\n                for (std::size_t i = 0; i < size_and_type.first; ++i)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key)))\n                    {\n                        return false;\n                    }\n                    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))\n                    {\n                        return false;\n                    }\n                    key.clear();\n                }\n            }\n            else\n            {\n                for (std::size_t i = 0; i < size_and_type.first; ++i)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key)))\n                    {\n                        return false;\n                    }\n                    if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal()))\n                    {\n                        return false;\n                    }\n                    key.clear();\n                }\n            }\n        }\n        else\n        {\n            if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1))))\n            {\n                return false;\n            }\n\n            while (current != '}')\n            {\n                if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key)))\n                {\n                    return false;\n                }\n                if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal()))\n                {\n                    return false;\n                }\n                get_ignore_noop();\n                key.clear();\n            }\n        }\n\n        return sax->end_object();\n    }\n\n    // Note, no reader for UBJSON binary types is implemented because they do\n    // not exist\n\n    bool get_ubjson_high_precision_number()\n    {\n        // get size of following number string\n        std::size_t size{};\n        bool no_ndarray = true;\n        auto res = get_ubjson_size_value(size, no_ndarray);\n        if (JSON_HEDLEY_UNLIKELY(!res))\n        {\n            return res;\n        }\n\n        // get number string\n        std::vector<char> number_vector;\n        for (std::size_t i = 0; i < size; ++i)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"number\")))\n            {\n                return false;\n            }\n            number_vector.push_back(static_cast<char>(current));\n        }\n\n        // parse number string\n        using ia_type = decltype(detail::input_adapter(number_vector));\n        auto number_lexer = detail::lexer<BasicJsonType, ia_type>(detail::input_adapter(number_vector), false);\n        const auto result_number = number_lexer.scan();\n        const auto number_string = number_lexer.get_token_string();\n        const auto result_remainder = number_lexer.scan();\n\n        using token_type = typename detail::lexer_base<BasicJsonType>::token_type;\n\n        if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input))\n        {\n            return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read,\n                                    exception_message(input_format, concat(\"invalid number text: \", number_lexer.get_token_string()), \"high-precision number\"), nullptr));\n        }\n\n        switch (result_number)\n        {\n            case token_type::value_integer:\n                return sax->number_integer(number_lexer.get_number_integer());\n            case token_type::value_unsigned:\n                return sax->number_unsigned(number_lexer.get_number_unsigned());\n            case token_type::value_float:\n                return sax->number_float(number_lexer.get_number_float(), std::move(number_string));\n            case token_type::uninitialized:\n            case token_type::literal_true:\n            case token_type::literal_false:\n            case token_type::literal_null:\n            case token_type::value_string:\n            case token_type::begin_array:\n            case token_type::begin_object:\n            case token_type::end_array:\n            case token_type::end_object:\n            case token_type::name_separator:\n            case token_type::value_separator:\n            case token_type::parse_error:\n            case token_type::end_of_input:\n            case token_type::literal_or_value:\n            default:\n                return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read,\n                                        exception_message(input_format, concat(\"invalid number text: \", number_lexer.get_token_string()), \"high-precision number\"), nullptr));\n        }\n    }\n\n    ///////////////////////\n    // Utility functions //\n    ///////////////////////\n\n    /*!\n    @brief get next character from the input\n\n    This function provides the interface to the used input adapter. It does\n    not throw in case the input reached EOF, but returns a -'ve valued\n    `char_traits<char_type>::eof()` in that case.\n\n    @return character read from the input\n    */\n    char_int_type get()\n    {\n        ++chars_read;\n        return current = ia.get_character();\n    }\n\n    /*!\n    @return character read from the input after ignoring all 'N' entries\n    */\n    char_int_type get_ignore_noop()\n    {\n        do\n        {\n            get();\n        }\n        while (current == 'N');\n\n        return current;\n    }\n\n    /*\n    @brief read a number from the input\n\n    @tparam NumberType the type of the number\n    @param[in] format   the current format (for diagnostics)\n    @param[out] result  number of type @a NumberType\n\n    @return whether conversion completed\n\n    @note This function needs to respect the system's endianness, because\n          bytes in CBOR, MessagePack, and UBJSON are stored in network order\n          (big endian) and therefore need reordering on little endian systems.\n          On the other hand, BSON and BJData use little endian and should reorder\n          on big endian systems.\n    */\n    template<typename NumberType, bool InputIsLittleEndian = false>\n    bool get_number(const input_format_t format, NumberType& result)\n    {\n        // step 1: read input into array with system's byte order\n        std::array<std::uint8_t, sizeof(NumberType)> vec{};\n        for (std::size_t i = 0; i < sizeof(NumberType); ++i)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, \"number\")))\n            {\n                return false;\n            }\n\n            // reverse byte order prior to conversion if necessary\n            if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata))\n            {\n                vec[sizeof(NumberType) - i - 1] = static_cast<std::uint8_t>(current);\n            }\n            else\n            {\n                vec[i] = static_cast<std::uint8_t>(current); // LCOV_EXCL_LINE\n            }\n        }\n\n        // step 2: convert array into number of type T and return\n        std::memcpy(&result, vec.data(), sizeof(NumberType));\n        return true;\n    }\n\n    /*!\n    @brief create a string by reading characters from the input\n\n    @tparam NumberType the type of the number\n    @param[in] format the current format (for diagnostics)\n    @param[in] len number of characters to read\n    @param[out] result string created by reading @a len bytes\n\n    @return whether string creation completed\n\n    @note We can not reserve @a len bytes for the result, because @a len\n          may be too large. Usually, @ref unexpect_eof() detects the end of\n          the input before we run out of string memory.\n    */\n    template<typename NumberType>\n    bool get_string(const input_format_t format,\n                    const NumberType len,\n                    string_t& result)\n    {\n        bool success = true;\n        for (NumberType i = 0; i < len; i++)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, \"string\")))\n            {\n                success = false;\n                break;\n            }\n            result.push_back(static_cast<typename string_t::value_type>(current));\n        }\n        return success;\n    }\n\n    /*!\n    @brief create a byte array by reading bytes from the input\n\n    @tparam NumberType the type of the number\n    @param[in] format the current format (for diagnostics)\n    @param[in] len number of bytes to read\n    @param[out] result byte array created by reading @a len bytes\n\n    @return whether byte array creation completed\n\n    @note We can not reserve @a len bytes for the result, because @a len\n          may be too large. Usually, @ref unexpect_eof() detects the end of\n          the input before we run out of memory.\n    */\n    template<typename NumberType>\n    bool get_binary(const input_format_t format,\n                    const NumberType len,\n                    binary_t& result)\n    {\n        bool success = true;\n        for (NumberType i = 0; i < len; i++)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, \"binary\")))\n            {\n                success = false;\n                break;\n            }\n            result.push_back(static_cast<std::uint8_t>(current));\n        }\n        return success;\n    }\n\n    /*!\n    @param[in] format   the current format (for diagnostics)\n    @param[in] context  further context information (for diagnostics)\n    @return whether the last read character is not EOF\n    */\n    JSON_HEDLEY_NON_NULL(3)\n    bool unexpect_eof(const input_format_t format, const char* context) const\n    {\n        if (JSON_HEDLEY_UNLIKELY(current == char_traits<char_type>::eof()))\n        {\n            return sax->parse_error(chars_read, \"<end of file>\",\n                                    parse_error::create(110, chars_read, exception_message(format, \"unexpected end of input\", context), nullptr));\n        }\n        return true;\n    }\n\n    /*!\n    @return a string representation of the last read byte\n    */\n    std::string get_token_string() const\n    {\n        std::array<char, 3> cr{{}};\n        static_cast<void>((std::snprintf)(cr.data(), cr.size(), \"%.2hhX\", static_cast<unsigned char>(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n        return std::string{cr.data()};\n    }\n\n    /*!\n    @param[in] format   the current format\n    @param[in] detail   a detailed error message\n    @param[in] context  further context information\n    @return a message string to use in the parse_error exceptions\n    */\n    std::string exception_message(const input_format_t format,\n                                  const std::string& detail,\n                                  const std::string& context) const\n    {\n        std::string error_msg = \"syntax error while parsing \";\n\n        switch (format)\n        {\n            case input_format_t::cbor:\n                error_msg += \"CBOR\";\n                break;\n\n            case input_format_t::msgpack:\n                error_msg += \"MessagePack\";\n                break;\n\n            case input_format_t::ubjson:\n                error_msg += \"UBJSON\";\n                break;\n\n            case input_format_t::bson:\n                error_msg += \"BSON\";\n                break;\n\n            case input_format_t::bjdata:\n                error_msg += \"BJData\";\n                break;\n\n            case input_format_t::json: // LCOV_EXCL_LINE\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n\n        return concat(error_msg, ' ', context, \": \", detail);\n    }\n\n  private:\n    static JSON_INLINE_VARIABLE constexpr std::size_t npos = static_cast<std::size_t>(-1);\n\n    /// input adapter\n    InputAdapterType ia;\n\n    /// the current character\n    char_int_type current = char_traits<char_type>::eof();\n\n    /// the number of characters read\n    std::size_t chars_read = 0;\n\n    /// whether we can assume little endianness\n    const bool is_little_endian = little_endianness();\n\n    /// input format\n    const input_format_t input_format = input_format_t::json;\n\n    /// the SAX parser\n    json_sax_t* sax = nullptr;\n\n    // excluded markers in bjdata optimized type\n#define JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ \\\n    make_array<char_int_type>('F', 'H', 'N', 'S', 'T', 'Z', '[', '{')\n\n#define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \\\n    make_array<bjd_type>(                      \\\n    bjd_type{'C', \"char\"},                     \\\n    bjd_type{'D', \"double\"},                   \\\n    bjd_type{'I', \"int16\"},                    \\\n    bjd_type{'L', \"int64\"},                    \\\n    bjd_type{'M', \"uint64\"},                   \\\n    bjd_type{'U', \"uint8\"},                    \\\n    bjd_type{'d', \"single\"},                   \\\n    bjd_type{'i', \"int8\"},                     \\\n    bjd_type{'l', \"int32\"},                    \\\n    bjd_type{'m', \"uint32\"},                   \\\n    bjd_type{'u', \"uint16\"})\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    // lookup tables\n    // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)\n    const decltype(JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_) bjd_optimized_type_markers =\n        JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_;\n\n    using bjd_type = std::pair<char_int_type, string_t>;\n    // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)\n    const decltype(JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_) bjd_types_map =\n        JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_;\n\n#undef JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_\n#undef JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_\n};\n\n#ifndef JSON_HAS_CPP_17\n    template<typename BasicJsonType, typename InputAdapterType, typename SAX>\n    constexpr std::size_t binary_reader<BasicJsonType, InputAdapterType, SAX>::npos;\n#endif\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n\n// #include <nlohmann/detail/input/lexer.hpp>\n\n// #include <nlohmann/detail/input/parser.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cmath> // isfinite\n#include <cstdint> // uint8_t\n#include <functional> // function\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n\n// #include <nlohmann/detail/input/json_sax.hpp>\n\n// #include <nlohmann/detail/input/lexer.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/is_sax.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n////////////\n// parser //\n////////////\n\nenum class parse_event_t : std::uint8_t\n{\n    /// the parser read `{` and started to process a JSON object\n    object_start,\n    /// the parser read `}` and finished processing a JSON object\n    object_end,\n    /// the parser read `[` and started to process a JSON array\n    array_start,\n    /// the parser read `]` and finished processing a JSON array\n    array_end,\n    /// the parser read a key of a value in an object\n    key,\n    /// the parser finished reading a JSON value\n    value\n};\n\ntemplate<typename BasicJsonType>\nusing parser_callback_t =\n    std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;\n\n/*!\n@brief syntax analysis\n\nThis class implements a recursive descent parser.\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType>\nclass parser\n{\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using lexer_t = lexer<BasicJsonType, InputAdapterType>;\n    using token_type = typename lexer_t::token_type;\n\n  public:\n    /// a parser reading from an input adapter\n    explicit parser(InputAdapterType&& adapter,\n                    const parser_callback_t<BasicJsonType> cb = nullptr,\n                    const bool allow_exceptions_ = true,\n                    const bool skip_comments = false)\n        : callback(cb)\n        , m_lexer(std::move(adapter), skip_comments)\n        , allow_exceptions(allow_exceptions_)\n    {\n        // read first token\n        get_token();\n    }\n\n    /*!\n    @brief public parser interface\n\n    @param[in] strict      whether to expect the last token to be EOF\n    @param[in,out] result  parsed JSON value\n\n    @throw parse_error.101 in case of an unexpected token\n    @throw parse_error.102 if to_unicode fails or surrogate error\n    @throw parse_error.103 if to_unicode fails\n    */\n    void parse(const bool strict, BasicJsonType& result)\n    {\n        if (callback)\n        {\n            json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback, allow_exceptions);\n            sax_parse_internal(&sdp);\n\n            // in strict mode, input must be completely read\n            if (strict && (get_token() != token_type::end_of_input))\n            {\n                sdp.parse_error(m_lexer.get_position(),\n                                m_lexer.get_token_string(),\n                                parse_error::create(101, m_lexer.get_position(),\n                                                    exception_message(token_type::end_of_input, \"value\"), nullptr));\n            }\n\n            // in case of an error, return discarded value\n            if (sdp.is_errored())\n            {\n                result = value_t::discarded;\n                return;\n            }\n\n            // set top-level value to null if it was discarded by the callback\n            // function\n            if (result.is_discarded())\n            {\n                result = nullptr;\n            }\n        }\n        else\n        {\n            json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions);\n            sax_parse_internal(&sdp);\n\n            // in strict mode, input must be completely read\n            if (strict && (get_token() != token_type::end_of_input))\n            {\n                sdp.parse_error(m_lexer.get_position(),\n                                m_lexer.get_token_string(),\n                                parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, \"value\"), nullptr));\n            }\n\n            // in case of an error, return discarded value\n            if (sdp.is_errored())\n            {\n                result = value_t::discarded;\n                return;\n            }\n        }\n\n        result.assert_invariant();\n    }\n\n    /*!\n    @brief public accept interface\n\n    @param[in] strict  whether to expect the last token to be EOF\n    @return whether the input is a proper JSON text\n    */\n    bool accept(const bool strict = true)\n    {\n        json_sax_acceptor<BasicJsonType> sax_acceptor;\n        return sax_parse(&sax_acceptor, strict);\n    }\n\n    template<typename SAX>\n    JSON_HEDLEY_NON_NULL(2)\n    bool sax_parse(SAX* sax, const bool strict = true)\n    {\n        (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};\n        const bool result = sax_parse_internal(sax);\n\n        // strict mode: next byte must be EOF\n        if (result && strict && (get_token() != token_type::end_of_input))\n        {\n            return sax->parse_error(m_lexer.get_position(),\n                                    m_lexer.get_token_string(),\n                                    parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, \"value\"), nullptr));\n        }\n\n        return result;\n    }\n\n  private:\n    template<typename SAX>\n    JSON_HEDLEY_NON_NULL(2)\n    bool sax_parse_internal(SAX* sax)\n    {\n        // stack to remember the hierarchy of structured values we are parsing\n        // true = array; false = object\n        std::vector<bool> states;\n        // value to avoid a goto (see comment where set to true)\n        bool skip_to_state_evaluation = false;\n\n        while (true)\n        {\n            if (!skip_to_state_evaluation)\n            {\n                // invariant: get_token() was called before each iteration\n                switch (last_token)\n                {\n                    case token_type::begin_object:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1))))\n                        {\n                            return false;\n                        }\n\n                        // closing } -> we are done\n                        if (get_token() == token_type::end_object)\n                        {\n                            if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))\n                            {\n                                return false;\n                            }\n                            break;\n                        }\n\n                        // parse key\n                        if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))\n                        {\n                            return sax->parse_error(m_lexer.get_position(),\n                                                    m_lexer.get_token_string(),\n                                                    parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, \"object key\"), nullptr));\n                        }\n                        if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))\n                        {\n                            return false;\n                        }\n\n                        // parse separator (:)\n                        if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))\n                        {\n                            return sax->parse_error(m_lexer.get_position(),\n                                                    m_lexer.get_token_string(),\n                                                    parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, \"object separator\"), nullptr));\n                        }\n\n                        // remember we are now inside an object\n                        states.push_back(false);\n\n                        // parse values\n                        get_token();\n                        continue;\n                    }\n\n                    case token_type::begin_array:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1))))\n                        {\n                            return false;\n                        }\n\n                        // closing ] -> we are done\n                        if (get_token() == token_type::end_array)\n                        {\n                            if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))\n                            {\n                                return false;\n                            }\n                            break;\n                        }\n\n                        // remember we are now inside an array\n                        states.push_back(true);\n\n                        // parse values (no need to call get_token)\n                        continue;\n                    }\n\n                    case token_type::value_float:\n                    {\n                        const auto res = m_lexer.get_number_float();\n\n                        if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))\n                        {\n                            return sax->parse_error(m_lexer.get_position(),\n                                                    m_lexer.get_token_string(),\n                                                    out_of_range::create(406, concat(\"number overflow parsing '\", m_lexer.get_token_string(), '\\''), nullptr));\n                        }\n\n                        if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))\n                        {\n                            return false;\n                        }\n\n                        break;\n                    }\n\n                    case token_type::literal_false:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false)))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::literal_null:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->null()))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::literal_true:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true)))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::value_integer:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer())))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::value_string:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string())))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::value_unsigned:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned())))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::parse_error:\n                    {\n                        // using \"uninitialized\" to avoid \"expected\" message\n                        return sax->parse_error(m_lexer.get_position(),\n                                                m_lexer.get_token_string(),\n                                                parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, \"value\"), nullptr));\n                    }\n                    case token_type::end_of_input:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))\n                        {\n                            return sax->parse_error(m_lexer.get_position(),\n                                                    m_lexer.get_token_string(),\n                                                    parse_error::create(101, m_lexer.get_position(),\n                                                            \"attempting to parse an empty input; check that your input string or stream contains the expected JSON\", nullptr));\n                        }\n\n                        return sax->parse_error(m_lexer.get_position(),\n                                                m_lexer.get_token_string(),\n                                                parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, \"value\"), nullptr));\n                    }\n                    case token_type::uninitialized:\n                    case token_type::end_array:\n                    case token_type::end_object:\n                    case token_type::name_separator:\n                    case token_type::value_separator:\n                    case token_type::literal_or_value:\n                    default: // the last token was unexpected\n                    {\n                        return sax->parse_error(m_lexer.get_position(),\n                                                m_lexer.get_token_string(),\n                                                parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, \"value\"), nullptr));\n                    }\n                }\n            }\n            else\n            {\n                skip_to_state_evaluation = false;\n            }\n\n            // we reached this line after we successfully parsed a value\n            if (states.empty())\n            {\n                // empty stack: we reached the end of the hierarchy: done\n                return true;\n            }\n\n            if (states.back())  // array\n            {\n                // comma -> next value\n                if (get_token() == token_type::value_separator)\n                {\n                    // parse a new value\n                    get_token();\n                    continue;\n                }\n\n                // closing ]\n                if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array))\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))\n                    {\n                        return false;\n                    }\n\n                    // We are done with this array. Before we can parse a\n                    // new value, we need to evaluate the new state first.\n                    // By setting skip_to_state_evaluation to false, we\n                    // are effectively jumping to the beginning of this if.\n                    JSON_ASSERT(!states.empty());\n                    states.pop_back();\n                    skip_to_state_evaluation = true;\n                    continue;\n                }\n\n                return sax->parse_error(m_lexer.get_position(),\n                                        m_lexer.get_token_string(),\n                                        parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, \"array\"), nullptr));\n            }\n\n            // states.back() is false -> object\n\n            // comma -> next value\n            if (get_token() == token_type::value_separator)\n            {\n                // parse key\n                if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string))\n                {\n                    return sax->parse_error(m_lexer.get_position(),\n                                            m_lexer.get_token_string(),\n                                            parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, \"object key\"), nullptr));\n                }\n\n                if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))\n                {\n                    return false;\n                }\n\n                // parse separator (:)\n                if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))\n                {\n                    return sax->parse_error(m_lexer.get_position(),\n                                            m_lexer.get_token_string(),\n                                            parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, \"object separator\"), nullptr));\n                }\n\n                // parse values\n                get_token();\n                continue;\n            }\n\n            // closing }\n            if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object))\n            {\n                if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))\n                {\n                    return false;\n                }\n\n                // We are done with this object. Before we can parse a\n                // new value, we need to evaluate the new state first.\n                // By setting skip_to_state_evaluation to false, we\n                // are effectively jumping to the beginning of this if.\n                JSON_ASSERT(!states.empty());\n                states.pop_back();\n                skip_to_state_evaluation = true;\n                continue;\n            }\n\n            return sax->parse_error(m_lexer.get_position(),\n                                    m_lexer.get_token_string(),\n                                    parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, \"object\"), nullptr));\n        }\n    }\n\n    /// get next token from lexer\n    token_type get_token()\n    {\n        return last_token = m_lexer.scan();\n    }\n\n    std::string exception_message(const token_type expected, const std::string& context)\n    {\n        std::string error_msg = \"syntax error \";\n\n        if (!context.empty())\n        {\n            error_msg += concat(\"while parsing \", context, ' ');\n        }\n\n        error_msg += \"- \";\n\n        if (last_token == token_type::parse_error)\n        {\n            error_msg += concat(m_lexer.get_error_message(), \"; last read: '\",\n                                m_lexer.get_token_string(), '\\'');\n        }\n        else\n        {\n            error_msg += concat(\"unexpected \", lexer_t::token_type_name(last_token));\n        }\n\n        if (expected != token_type::uninitialized)\n        {\n            error_msg += concat(\"; expected \", lexer_t::token_type_name(expected));\n        }\n\n        return error_msg;\n    }\n\n  private:\n    /// callback function\n    const parser_callback_t<BasicJsonType> callback = nullptr;\n    /// the type of the last read token\n    token_type last_token = token_type::uninitialized;\n    /// the lexer\n    lexer_t m_lexer;\n    /// whether to throw exceptions in case of errors\n    const bool allow_exceptions = true;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/internal_iterator.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/iterators/primitive_iterator.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // ptrdiff_t\n#include <limits>  // numeric_limits\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*\n@brief an iterator for primitive JSON types\n\nThis class models an iterator for primitive JSON types (boolean, number,\nstring). It's only purpose is to allow the iterator/const_iterator classes\nto \"iterate\" over primitive values. Internally, the iterator is modeled by\na `difference_type` variable. Value begin_value (`0`) models the begin,\nend_value (`1`) models past the end.\n*/\nclass primitive_iterator_t\n{\n  private:\n    using difference_type = std::ptrdiff_t;\n    static constexpr difference_type begin_value = 0;\n    static constexpr difference_type end_value = begin_value + 1;\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /// iterator as signed integer type\n    difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)();\n\n  public:\n    constexpr difference_type get_value() const noexcept\n    {\n        return m_it;\n    }\n\n    /// set iterator to a defined beginning\n    void set_begin() noexcept\n    {\n        m_it = begin_value;\n    }\n\n    /// set iterator to a defined past the end\n    void set_end() noexcept\n    {\n        m_it = end_value;\n    }\n\n    /// return whether the iterator can be dereferenced\n    constexpr bool is_begin() const noexcept\n    {\n        return m_it == begin_value;\n    }\n\n    /// return whether the iterator is at end\n    constexpr bool is_end() const noexcept\n    {\n        return m_it == end_value;\n    }\n\n    friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept\n    {\n        return lhs.m_it == rhs.m_it;\n    }\n\n    friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept\n    {\n        return lhs.m_it < rhs.m_it;\n    }\n\n    primitive_iterator_t operator+(difference_type n) noexcept\n    {\n        auto result = *this;\n        result += n;\n        return result;\n    }\n\n    friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept\n    {\n        return lhs.m_it - rhs.m_it;\n    }\n\n    primitive_iterator_t& operator++() noexcept\n    {\n        ++m_it;\n        return *this;\n    }\n\n    primitive_iterator_t operator++(int)& noexcept // NOLINT(cert-dcl21-cpp)\n    {\n        auto result = *this;\n        ++m_it;\n        return result;\n    }\n\n    primitive_iterator_t& operator--() noexcept\n    {\n        --m_it;\n        return *this;\n    }\n\n    primitive_iterator_t operator--(int)& noexcept // NOLINT(cert-dcl21-cpp)\n    {\n        auto result = *this;\n        --m_it;\n        return result;\n    }\n\n    primitive_iterator_t& operator+=(difference_type n) noexcept\n    {\n        m_it += n;\n        return *this;\n    }\n\n    primitive_iterator_t& operator-=(difference_type n) noexcept\n    {\n        m_it -= n;\n        return *this;\n    }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief an iterator value\n\n@note This structure could easily be a union, but MSVC currently does not allow\nunions members with complex constructors, see https://github.com/nlohmann/json/pull/105.\n*/\ntemplate<typename BasicJsonType> struct internal_iterator\n{\n    /// iterator for JSON objects\n    typename BasicJsonType::object_t::iterator object_iterator {};\n    /// iterator for JSON arrays\n    typename BasicJsonType::array_t::iterator array_iterator {};\n    /// generic iterator for all other types\n    primitive_iterator_t primitive_iterator {};\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/iter_impl.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <iterator> // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next\n#include <type_traits> // conditional, is_const, remove_const\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/iterators/internal_iterator.hpp>\n\n// #include <nlohmann/detail/iterators/primitive_iterator.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// forward declare, to be able to friend it later on\ntemplate<typename IteratorType> class iteration_proxy;\ntemplate<typename IteratorType> class iteration_proxy_value;\n\n/*!\n@brief a template for a bidirectional iterator for the @ref basic_json class\nThis class implements a both iterators (iterator and const_iterator) for the\n@ref basic_json class.\n@note An iterator is called *initialized* when a pointer to a JSON value has\n      been set (e.g., by a constructor or a copy assignment). If the iterator is\n      default-constructed, it is *uninitialized* and most methods are undefined.\n      **The library uses assertions to detect calls on uninitialized iterators.**\n@requirement The class satisfies the following concept requirements:\n-\n[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):\n  The iterator that can be moved can be moved in both directions (i.e.\n  incremented and decremented).\n@since version 1.0.0, simplified in version 2.0.9, change to bidirectional\n       iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593)\n*/\ntemplate<typename BasicJsonType>\nclass iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)\n{\n    /// the iterator with BasicJsonType of different const-ness\n    using other_iter_impl = iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>;\n    /// allow basic_json to access private members\n    friend other_iter_impl;\n    friend BasicJsonType;\n    friend iteration_proxy<iter_impl>;\n    friend iteration_proxy_value<iter_impl>;\n\n    using object_t = typename BasicJsonType::object_t;\n    using array_t = typename BasicJsonType::array_t;\n    // make sure BasicJsonType is basic_json or const basic_json\n    static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,\n                  \"iter_impl only accepts (const) basic_json\");\n    // superficial check for the LegacyBidirectionalIterator named requirement\n    static_assert(std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value\n                  &&  std::is_base_of<std::bidirectional_iterator_tag, typename std::iterator_traits<typename array_t::iterator>::iterator_category>::value,\n                  \"basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement.\");\n\n  public:\n    /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.\n    /// The C++ Standard has never required user-defined iterators to derive from std::iterator.\n    /// A user-defined iterator should provide publicly accessible typedefs named\n    /// iterator_category, value_type, difference_type, pointer, and reference.\n    /// Note that value_type is required to be non-const, even for constant iterators.\n    using iterator_category = std::bidirectional_iterator_tag;\n\n    /// the type of the values when the iterator is dereferenced\n    using value_type = typename BasicJsonType::value_type;\n    /// a type to represent differences between iterators\n    using difference_type = typename BasicJsonType::difference_type;\n    /// defines a pointer to the type iterated over (value_type)\n    using pointer = typename std::conditional<std::is_const<BasicJsonType>::value,\n          typename BasicJsonType::const_pointer,\n          typename BasicJsonType::pointer>::type;\n    /// defines a reference to the type iterated over (value_type)\n    using reference =\n        typename std::conditional<std::is_const<BasicJsonType>::value,\n        typename BasicJsonType::const_reference,\n        typename BasicJsonType::reference>::type;\n\n    iter_impl() = default;\n    ~iter_impl() = default;\n    iter_impl(iter_impl&&) noexcept = default;\n    iter_impl& operator=(iter_impl&&) noexcept = default;\n\n    /*!\n    @brief constructor for a given JSON instance\n    @param[in] object  pointer to a JSON object for this iterator\n    @pre object != nullptr\n    @post The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    explicit iter_impl(pointer object) noexcept : m_object(object)\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                m_it.object_iterator = typename object_t::iterator();\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_it.array_iterator = typename array_t::iterator();\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                m_it.primitive_iterator = primitive_iterator_t();\n                break;\n            }\n        }\n    }\n\n    /*!\n    @note The conventional copy constructor and copy assignment are implicitly\n          defined. Combined with the following converting constructor and\n          assignment, they support: (1) copy from iterator to iterator, (2)\n          copy from const iterator to const iterator, and (3) conversion from\n          iterator to const iterator. However conversion from const iterator\n          to iterator is not defined.\n    */\n\n    /*!\n    @brief const copy constructor\n    @param[in] other const iterator to copy from\n    @note This copy constructor had to be defined explicitly to circumvent a bug\n          occurring on msvc v19.0 compiler (VS 2015) debug build. For more\n          information refer to: https://github.com/nlohmann/json/issues/1608\n    */\n    iter_impl(const iter_impl<const BasicJsonType>& other) noexcept\n        : m_object(other.m_object), m_it(other.m_it)\n    {}\n\n    /*!\n    @brief converting assignment\n    @param[in] other const iterator to copy from\n    @return const/non-const iterator\n    @note It is not checked whether @a other is initialized.\n    */\n    iter_impl& operator=(const iter_impl<const BasicJsonType>& other) noexcept\n    {\n        if (&other != this)\n        {\n            m_object = other.m_object;\n            m_it = other.m_it;\n        }\n        return *this;\n    }\n\n    /*!\n    @brief converting constructor\n    @param[in] other  non-const iterator to copy from\n    @note It is not checked whether @a other is initialized.\n    */\n    iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept\n        : m_object(other.m_object), m_it(other.m_it)\n    {}\n\n    /*!\n    @brief converting assignment\n    @param[in] other  non-const iterator to copy from\n    @return const/non-const iterator\n    @note It is not checked whether @a other is initialized.\n    */\n    iter_impl& operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept // NOLINT(cert-oop54-cpp)\n    {\n        m_object = other.m_object;\n        m_it = other.m_it;\n        return *this;\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /*!\n    @brief set the iterator to the first value\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    void set_begin() noexcept\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                m_it.object_iterator = m_object->m_data.m_value.object->begin();\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_it.array_iterator = m_object->m_data.m_value.array->begin();\n                break;\n            }\n\n            case value_t::null:\n            {\n                // set to end so begin()==end() is true: null is empty\n                m_it.primitive_iterator.set_end();\n                break;\n            }\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                m_it.primitive_iterator.set_begin();\n                break;\n            }\n        }\n    }\n\n    /*!\n    @brief set the iterator past the last value\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    void set_end() noexcept\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                m_it.object_iterator = m_object->m_data.m_value.object->end();\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_it.array_iterator = m_object->m_data.m_value.array->end();\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                m_it.primitive_iterator.set_end();\n                break;\n            }\n        }\n    }\n\n  public:\n    /*!\n    @brief return a reference to the value pointed to by the iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    reference operator*() const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());\n                return m_it.object_iterator->second;\n            }\n\n            case value_t::array:\n            {\n                JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());\n                return *m_it.array_iterator;\n            }\n\n            case value_t::null:\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))\n                {\n                    return *m_object;\n                }\n\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n            }\n        }\n    }\n\n    /*!\n    @brief dereference the iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    pointer operator->() const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());\n                return &(m_it.object_iterator->second);\n            }\n\n            case value_t::array:\n            {\n                JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());\n                return &*m_it.array_iterator;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))\n                {\n                    return m_object;\n                }\n\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n            }\n        }\n    }\n\n    /*!\n    @brief post-increment (it++)\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl operator++(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        auto result = *this;\n        ++(*this);\n        return result;\n    }\n\n    /*!\n    @brief pre-increment (++it)\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl& operator++()\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                std::advance(m_it.object_iterator, 1);\n                break;\n            }\n\n            case value_t::array:\n            {\n                std::advance(m_it.array_iterator, 1);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                ++m_it.primitive_iterator;\n                break;\n            }\n        }\n\n        return *this;\n    }\n\n    /*!\n    @brief post-decrement (it--)\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl operator--(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        auto result = *this;\n        --(*this);\n        return result;\n    }\n\n    /*!\n    @brief pre-decrement (--it)\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl& operator--()\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                std::advance(m_it.object_iterator, -1);\n                break;\n            }\n\n            case value_t::array:\n            {\n                std::advance(m_it.array_iterator, -1);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                --m_it.primitive_iterator;\n                break;\n            }\n        }\n\n        return *this;\n    }\n\n    /*!\n    @brief comparison: equal\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    template < typename IterImpl, detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr >\n    bool operator==(const IterImpl& other) const\n    {\n        // if objects are not the same, the comparison is undefined\n        if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(212, \"cannot compare iterators of different containers\", m_object));\n        }\n\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                return (m_it.object_iterator == other.m_it.object_iterator);\n\n            case value_t::array:\n                return (m_it.array_iterator == other.m_it.array_iterator);\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                return (m_it.primitive_iterator == other.m_it.primitive_iterator);\n        }\n    }\n\n    /*!\n    @brief comparison: not equal\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    template < typename IterImpl, detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr >\n    bool operator!=(const IterImpl& other) const\n    {\n        return !operator==(other);\n    }\n\n    /*!\n    @brief comparison: smaller\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    bool operator<(const iter_impl& other) const\n    {\n        // if objects are not the same, the comparison is undefined\n        if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(212, \"cannot compare iterators of different containers\", m_object));\n        }\n\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                JSON_THROW(invalid_iterator::create(213, \"cannot compare order of object iterators\", m_object));\n\n            case value_t::array:\n                return (m_it.array_iterator < other.m_it.array_iterator);\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                return (m_it.primitive_iterator < other.m_it.primitive_iterator);\n        }\n    }\n\n    /*!\n    @brief comparison: less than or equal\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    bool operator<=(const iter_impl& other) const\n    {\n        return !other.operator < (*this);\n    }\n\n    /*!\n    @brief comparison: greater than\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    bool operator>(const iter_impl& other) const\n    {\n        return !operator<=(other);\n    }\n\n    /*!\n    @brief comparison: greater than or equal\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    bool operator>=(const iter_impl& other) const\n    {\n        return !operator<(other);\n    }\n\n    /*!\n    @brief add to iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl& operator+=(difference_type i)\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                JSON_THROW(invalid_iterator::create(209, \"cannot use offsets with object iterators\", m_object));\n\n            case value_t::array:\n            {\n                std::advance(m_it.array_iterator, i);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                m_it.primitive_iterator += i;\n                break;\n            }\n        }\n\n        return *this;\n    }\n\n    /*!\n    @brief subtract from iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl& operator-=(difference_type i)\n    {\n        return operator+=(-i);\n    }\n\n    /*!\n    @brief add to iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl operator+(difference_type i) const\n    {\n        auto result = *this;\n        result += i;\n        return result;\n    }\n\n    /*!\n    @brief addition of distance and iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    friend iter_impl operator+(difference_type i, const iter_impl& it)\n    {\n        auto result = it;\n        result += i;\n        return result;\n    }\n\n    /*!\n    @brief subtract from iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl operator-(difference_type i) const\n    {\n        auto result = *this;\n        result -= i;\n        return result;\n    }\n\n    /*!\n    @brief return difference\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    difference_type operator-(const iter_impl& other) const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                JSON_THROW(invalid_iterator::create(209, \"cannot use offsets with object iterators\", m_object));\n\n            case value_t::array:\n                return m_it.array_iterator - other.m_it.array_iterator;\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                return m_it.primitive_iterator - other.m_it.primitive_iterator;\n        }\n    }\n\n    /*!\n    @brief access to successor\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    reference operator[](difference_type n) const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                JSON_THROW(invalid_iterator::create(208, \"cannot use operator[] for object iterators\", m_object));\n\n            case value_t::array:\n                return *std::next(m_it.array_iterator, n);\n\n            case value_t::null:\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n))\n                {\n                    return *m_object;\n                }\n\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n            }\n        }\n    }\n\n    /*!\n    @brief return the key of an object iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    const typename object_t::key_type& key() const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        if (JSON_HEDLEY_LIKELY(m_object->is_object()))\n        {\n            return m_it.object_iterator->first;\n        }\n\n        JSON_THROW(invalid_iterator::create(207, \"cannot use key() for non-object iterators\", m_object));\n    }\n\n    /*!\n    @brief return the value of an iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    reference value() const\n    {\n        return operator*();\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /// associated JSON instance\n    pointer m_object = nullptr;\n    /// the actual iterator of the associated instance\n    internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it {};\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/iteration_proxy.hpp>\n\n// #include <nlohmann/detail/iterators/json_reverse_iterator.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // ptrdiff_t\n#include <iterator> // reverse_iterator\n#include <utility> // declval\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n//////////////////////\n// reverse_iterator //\n//////////////////////\n\n/*!\n@brief a template for a reverse iterator class\n\n@tparam Base the base iterator type to reverse. Valid types are @ref\niterator (to create @ref reverse_iterator) and @ref const_iterator (to\ncreate @ref const_reverse_iterator).\n\n@requirement The class satisfies the following concept requirements:\n-\n[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):\n  The iterator that can be moved can be moved in both directions (i.e.\n  incremented and decremented).\n- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator):\n  It is possible to write to the pointed-to element (only if @a Base is\n  @ref iterator).\n\n@since version 1.0.0\n*/\ntemplate<typename Base>\nclass json_reverse_iterator : public std::reverse_iterator<Base>\n{\n  public:\n    using difference_type = std::ptrdiff_t;\n    /// shortcut to the reverse iterator adapter\n    using base_iterator = std::reverse_iterator<Base>;\n    /// the reference type for the pointed-to element\n    using reference = typename Base::reference;\n\n    /// create reverse iterator from iterator\n    explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept\n        : base_iterator(it) {}\n\n    /// create reverse iterator from base class\n    explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {}\n\n    /// post-increment (it++)\n    json_reverse_iterator operator++(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        return static_cast<json_reverse_iterator>(base_iterator::operator++(1));\n    }\n\n    /// pre-increment (++it)\n    json_reverse_iterator& operator++()\n    {\n        return static_cast<json_reverse_iterator&>(base_iterator::operator++());\n    }\n\n    /// post-decrement (it--)\n    json_reverse_iterator operator--(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        return static_cast<json_reverse_iterator>(base_iterator::operator--(1));\n    }\n\n    /// pre-decrement (--it)\n    json_reverse_iterator& operator--()\n    {\n        return static_cast<json_reverse_iterator&>(base_iterator::operator--());\n    }\n\n    /// add to iterator\n    json_reverse_iterator& operator+=(difference_type i)\n    {\n        return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i));\n    }\n\n    /// add to iterator\n    json_reverse_iterator operator+(difference_type i) const\n    {\n        return static_cast<json_reverse_iterator>(base_iterator::operator+(i));\n    }\n\n    /// subtract from iterator\n    json_reverse_iterator operator-(difference_type i) const\n    {\n        return static_cast<json_reverse_iterator>(base_iterator::operator-(i));\n    }\n\n    /// return difference\n    difference_type operator-(const json_reverse_iterator& other) const\n    {\n        return base_iterator(*this) - base_iterator(other);\n    }\n\n    /// access to successor\n    reference operator[](difference_type n) const\n    {\n        return *(this->operator+(n));\n    }\n\n    /// return the key of an object iterator\n    auto key() const -> decltype(std::declval<Base>().key())\n    {\n        auto it = --this->base();\n        return it.key();\n    }\n\n    /// return the value of an iterator\n    reference value() const\n    {\n        auto it = --this->base();\n        return it.operator * ();\n    }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/primitive_iterator.hpp>\n\n// #include <nlohmann/detail/json_custom_base_class.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <type_traits> // conditional, is_same\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief Default base class of the @ref basic_json class.\n\nSo that the correct implementations of the copy / move ctors / assign operators\nof @ref basic_json do not require complex case distinctions\n(no base class / custom base class used as customization point),\n@ref basic_json always has a base class.\nBy default, this class is used because it is empty and thus has no effect\non the behavior of @ref basic_json.\n*/\nstruct json_default_base {};\n\ntemplate<class T>\nusing json_base_class = typename std::conditional <\n                        std::is_same<T, void>::value,\n                        json_default_base,\n                        T\n                        >::type;\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/json_pointer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // all_of\n#include <cctype> // isdigit\n#include <cerrno> // errno, ERANGE\n#include <cstdlib> // strtoull\n#ifndef JSON_NO_IO\n    #include <iosfwd> // ostream\n#endif  // JSON_NO_IO\n#include <limits> // max\n#include <numeric> // accumulate\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/string_escape.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document\n/// @sa https://json.nlohmann.me/api/json_pointer/\ntemplate<typename RefStringType>\nclass json_pointer\n{\n    // allow basic_json to access private members\n    NLOHMANN_BASIC_JSON_TPL_DECLARATION\n    friend class basic_json;\n\n    template<typename>\n    friend class json_pointer;\n\n    template<typename T>\n    struct string_t_helper\n    {\n        using type = T;\n    };\n\n    NLOHMANN_BASIC_JSON_TPL_DECLARATION\n    struct string_t_helper<NLOHMANN_BASIC_JSON_TPL>\n    {\n        using type = StringType;\n    };\n\n  public:\n    // for backwards compatibility accept BasicJsonType\n    using string_t = typename string_t_helper<RefStringType>::type;\n\n    /// @brief create JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/\n    explicit json_pointer(const string_t& s = \"\")\n        : reference_tokens(split(s))\n    {}\n\n    /// @brief return a string representation of the JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/to_string/\n    string_t to_string() const\n    {\n        return std::accumulate(reference_tokens.begin(), reference_tokens.end(),\n                               string_t{},\n                               [](const string_t& a, const string_t& b)\n        {\n            return detail::concat(a, '/', detail::escape(b));\n        });\n    }\n\n    /// @brief return a string representation of the JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_string/\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())\n    operator string_t() const\n    {\n        return to_string();\n    }\n\n#ifndef JSON_NO_IO\n    /// @brief write string representation of the JSON pointer to stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/\n    friend std::ostream& operator<<(std::ostream& o, const json_pointer& ptr)\n    {\n        o << ptr.to_string();\n        return o;\n    }\n#endif\n\n    /// @brief append another JSON pointer at the end of this JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/\n    json_pointer& operator/=(const json_pointer& ptr)\n    {\n        reference_tokens.insert(reference_tokens.end(),\n                                ptr.reference_tokens.begin(),\n                                ptr.reference_tokens.end());\n        return *this;\n    }\n\n    /// @brief append an unescaped reference token at the end of this JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/\n    json_pointer& operator/=(string_t token)\n    {\n        push_back(std::move(token));\n        return *this;\n    }\n\n    /// @brief append an array index at the end of this JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/\n    json_pointer& operator/=(std::size_t array_idx)\n    {\n        return *this /= std::to_string(array_idx);\n    }\n\n    /// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/\n    friend json_pointer operator/(const json_pointer& lhs,\n                                  const json_pointer& rhs)\n    {\n        return json_pointer(lhs) /= rhs;\n    }\n\n    /// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/\n    friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)\n    {\n        return json_pointer(lhs) /= std::move(token);\n    }\n\n    /// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/\n    friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)\n    {\n        return json_pointer(lhs) /= array_idx;\n    }\n\n    /// @brief returns the parent of this JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/\n    json_pointer parent_pointer() const\n    {\n        if (empty())\n        {\n            return *this;\n        }\n\n        json_pointer res = *this;\n        res.pop_back();\n        return res;\n    }\n\n    /// @brief remove last reference token\n    /// @sa https://json.nlohmann.me/api/json_pointer/pop_back/\n    void pop_back()\n    {\n        if (JSON_HEDLEY_UNLIKELY(empty()))\n        {\n            JSON_THROW(detail::out_of_range::create(405, \"JSON pointer has no parent\", nullptr));\n        }\n\n        reference_tokens.pop_back();\n    }\n\n    /// @brief return last reference token\n    /// @sa https://json.nlohmann.me/api/json_pointer/back/\n    const string_t& back() const\n    {\n        if (JSON_HEDLEY_UNLIKELY(empty()))\n        {\n            JSON_THROW(detail::out_of_range::create(405, \"JSON pointer has no parent\", nullptr));\n        }\n\n        return reference_tokens.back();\n    }\n\n    /// @brief append an unescaped token at the end of the reference pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/push_back/\n    void push_back(const string_t& token)\n    {\n        reference_tokens.push_back(token);\n    }\n\n    /// @brief append an unescaped token at the end of the reference pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/push_back/\n    void push_back(string_t&& token)\n    {\n        reference_tokens.push_back(std::move(token));\n    }\n\n    /// @brief return whether pointer points to the root document\n    /// @sa https://json.nlohmann.me/api/json_pointer/empty/\n    bool empty() const noexcept\n    {\n        return reference_tokens.empty();\n    }\n\n  private:\n    /*!\n    @param[in] s  reference token to be converted into an array index\n\n    @return integer representation of @a s\n\n    @throw parse_error.106  if an array index begins with '0'\n    @throw parse_error.109  if an array index begins not with a digit\n    @throw out_of_range.404 if string @a s could not be converted to an integer\n    @throw out_of_range.410 if an array index exceeds size_type\n    */\n    template<typename BasicJsonType>\n    static typename BasicJsonType::size_type array_index(const string_t& s)\n    {\n        using size_type = typename BasicJsonType::size_type;\n\n        // error condition (cf. RFC 6901, Sect. 4)\n        if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))\n        {\n            JSON_THROW(detail::parse_error::create(106, 0, detail::concat(\"array index '\", s, \"' must not begin with '0'\"), nullptr));\n        }\n\n        // error condition (cf. RFC 6901, Sect. 4)\n        if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))\n        {\n            JSON_THROW(detail::parse_error::create(109, 0, detail::concat(\"array index '\", s, \"' is not a number\"), nullptr));\n        }\n\n        const char* p = s.c_str();\n        char* p_end = nullptr;\n        errno = 0; // strtoull doesn't reset errno\n        const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)\n        if (p == p_end // invalid input or empty string\n                || errno == ERANGE // out of range\n                || JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read\n        {\n            JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", s, \"'\"), nullptr));\n        }\n\n        // only triggered on special platforms (like 32bit), see also\n        // https://github.com/nlohmann/json/pull/2203\n        if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)()))  // NOLINT(runtime/int)\n        {\n            JSON_THROW(detail::out_of_range::create(410, detail::concat(\"array index \", s, \" exceeds size_type\"), nullptr));   // LCOV_EXCL_LINE\n        }\n\n        return static_cast<size_type>(res);\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    json_pointer top() const\n    {\n        if (JSON_HEDLEY_UNLIKELY(empty()))\n        {\n            JSON_THROW(detail::out_of_range::create(405, \"JSON pointer has no parent\", nullptr));\n        }\n\n        json_pointer result = *this;\n        result.reference_tokens = {reference_tokens[0]};\n        return result;\n    }\n\n  private:\n    /*!\n    @brief create and return a reference to the pointed to value\n\n    @complexity Linear in the number of reference tokens.\n\n    @throw parse_error.109 if array index is not a number\n    @throw type_error.313 if value cannot be unflattened\n    */\n    template<typename BasicJsonType>\n    BasicJsonType& get_and_create(BasicJsonType& j) const\n    {\n        auto* result = &j;\n\n        // in case no reference tokens exist, return a reference to the JSON value\n        // j which will be overwritten by a primitive value\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (result->type())\n            {\n                case detail::value_t::null:\n                {\n                    if (reference_token == \"0\")\n                    {\n                        // start a new array if reference token is 0\n                        result = &result->operator[](0);\n                    }\n                    else\n                    {\n                        // start a new object otherwise\n                        result = &result->operator[](reference_token);\n                    }\n                    break;\n                }\n\n                case detail::value_t::object:\n                {\n                    // create an entry in the object\n                    result = &result->operator[](reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    // create an entry in the array\n                    result = &result->operator[](array_index<BasicJsonType>(reference_token));\n                    break;\n                }\n\n                /*\n                The following code is only reached if there exists a reference\n                token _and_ the current value is primitive. In this case, we have\n                an error situation, because primitive values may only occur as\n                single value; that is, with an empty list of reference tokens.\n                */\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::type_error::create(313, \"invalid value to unflatten\", &j));\n            }\n        }\n\n        return *result;\n    }\n\n    /*!\n    @brief return a reference to the pointed to value\n\n    @note This version does not throw if a value is not present, but tries to\n          create nested values instead. For instance, calling this function\n          with pointer `\"/this/that\"` on a null value is equivalent to calling\n          `operator[](\"this\").operator[](\"that\")` on that value, effectively\n          changing the null value to an object.\n\n    @param[in] ptr  a JSON value\n\n    @return reference to the JSON value pointed to by the JSON pointer\n\n    @complexity Linear in the length of the JSON pointer.\n\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    @throw out_of_range.404  if the JSON pointer can not be resolved\n    */\n    template<typename BasicJsonType>\n    BasicJsonType& get_unchecked(BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            // convert null values to arrays or objects before continuing\n            if (ptr->is_null())\n            {\n                // check if reference token is a number\n                const bool nums =\n                    std::all_of(reference_token.begin(), reference_token.end(),\n                                [](const unsigned char x)\n                {\n                    return std::isdigit(x);\n                });\n\n                // change value to array for numbers or \"-\" or to object otherwise\n                *ptr = (nums || reference_token == \"-\")\n                       ? detail::value_t::array\n                       : detail::value_t::object;\n            }\n\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    // use unchecked object access\n                    ptr = &ptr->operator[](reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (reference_token == \"-\")\n                    {\n                        // explicitly treat \"-\" as index beyond the end\n                        ptr = &ptr->operator[](ptr->m_data.m_value.array->size());\n                    }\n                    else\n                    {\n                        // convert array index to number; unchecked access\n                        ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));\n                    }\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n            }\n        }\n\n        return *ptr;\n    }\n\n    /*!\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    @throw out_of_range.402  if the array index '-' is used\n    @throw out_of_range.404  if the JSON pointer can not be resolved\n    */\n    template<typename BasicJsonType>\n    BasicJsonType& get_checked(BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    // note: at performs range check\n                    ptr = &ptr->at(reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\"))\n                    {\n                        // \"-\" always fails the range check\n                        JSON_THROW(detail::out_of_range::create(402, detail::concat(\n                                \"array index '-' (\", std::to_string(ptr->m_data.m_value.array->size()),\n                                \") is out of range\"), ptr));\n                    }\n\n                    // note: at performs range check\n                    ptr = &ptr->at(array_index<BasicJsonType>(reference_token));\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n            }\n        }\n\n        return *ptr;\n    }\n\n    /*!\n    @brief return a const reference to the pointed to value\n\n    @param[in] ptr  a JSON value\n\n    @return const reference to the JSON value pointed to by the JSON\n    pointer\n\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    @throw out_of_range.402  if the array index '-' is used\n    @throw out_of_range.404  if the JSON pointer can not be resolved\n    */\n    template<typename BasicJsonType>\n    const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    // use unchecked object access\n                    ptr = &ptr->operator[](reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\"))\n                    {\n                        // \"-\" cannot be used for const access\n                        JSON_THROW(detail::out_of_range::create(402, detail::concat(\"array index '-' (\", std::to_string(ptr->m_data.m_value.array->size()), \") is out of range\"), ptr));\n                    }\n\n                    // use unchecked array access\n                    ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n            }\n        }\n\n        return *ptr;\n    }\n\n    /*!\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    @throw out_of_range.402  if the array index '-' is used\n    @throw out_of_range.404  if the JSON pointer can not be resolved\n    */\n    template<typename BasicJsonType>\n    const BasicJsonType& get_checked(const BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    // note: at performs range check\n                    ptr = &ptr->at(reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\"))\n                    {\n                        // \"-\" always fails the range check\n                        JSON_THROW(detail::out_of_range::create(402, detail::concat(\n                                \"array index '-' (\", std::to_string(ptr->m_data.m_value.array->size()),\n                                \") is out of range\"), ptr));\n                    }\n\n                    // note: at performs range check\n                    ptr = &ptr->at(array_index<BasicJsonType>(reference_token));\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n            }\n        }\n\n        return *ptr;\n    }\n\n    /*!\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    */\n    template<typename BasicJsonType>\n    bool contains(const BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    if (!ptr->contains(reference_token))\n                    {\n                        // we did not find the key in the object\n                        return false;\n                    }\n\n                    ptr = &ptr->operator[](reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\"))\n                    {\n                        // \"-\" always fails the range check\n                        return false;\n                    }\n                    if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !(\"0\" <= reference_token && reference_token <= \"9\")))\n                    {\n                        // invalid char\n                        return false;\n                    }\n                    if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1))\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9')))\n                        {\n                            // first char should be between '1' and '9'\n                            return false;\n                        }\n                        for (std::size_t i = 1; i < reference_token.size(); i++)\n                        {\n                            if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9')))\n                            {\n                                // other char should be between '0' and '9'\n                                return false;\n                            }\n                        }\n                    }\n\n                    const auto idx = array_index<BasicJsonType>(reference_token);\n                    if (idx >= ptr->size())\n                    {\n                        // index out of range\n                        return false;\n                    }\n\n                    ptr = &ptr->operator[](idx);\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                {\n                    // we do not expect primitive values if there is still a\n                    // reference token to process\n                    return false;\n                }\n            }\n        }\n\n        // no reference token left means we found a primitive value\n        return true;\n    }\n\n    /*!\n    @brief split the string input to reference tokens\n\n    @note This function is only called by the json_pointer constructor.\n          All exceptions below are documented there.\n\n    @throw parse_error.107  if the pointer is not empty or begins with '/'\n    @throw parse_error.108  if character '~' is not followed by '0' or '1'\n    */\n    static std::vector<string_t> split(const string_t& reference_string)\n    {\n        std::vector<string_t> result;\n\n        // special case: empty reference string -> no reference tokens\n        if (reference_string.empty())\n        {\n            return result;\n        }\n\n        // check if nonempty reference string begins with slash\n        if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))\n        {\n            JSON_THROW(detail::parse_error::create(107, 1, detail::concat(\"JSON pointer must be empty or begin with '/' - was: '\", reference_string, \"'\"), nullptr));\n        }\n\n        // extract the reference tokens:\n        // - slash: position of the last read slash (or end of string)\n        // - start: position after the previous slash\n        for (\n            // search for the first slash after the first character\n            std::size_t slash = reference_string.find_first_of('/', 1),\n            // set the beginning of the first reference token\n            start = 1;\n            // we can stop if start == 0 (if slash == string_t::npos)\n            start != 0;\n            // set the beginning of the next reference token\n            // (will eventually be 0 if slash == string_t::npos)\n            start = (slash == string_t::npos) ? 0 : slash + 1,\n            // find next slash\n            slash = reference_string.find_first_of('/', start))\n        {\n            // use the text between the beginning of the reference token\n            // (start) and the last slash (slash).\n            auto reference_token = reference_string.substr(start, slash - start);\n\n            // check reference tokens are properly escaped\n            for (std::size_t pos = reference_token.find_first_of('~');\n                    pos != string_t::npos;\n                    pos = reference_token.find_first_of('~', pos + 1))\n            {\n                JSON_ASSERT(reference_token[pos] == '~');\n\n                // ~ must be followed by 0 or 1\n                if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||\n                                         (reference_token[pos + 1] != '0' &&\n                                          reference_token[pos + 1] != '1')))\n                {\n                    JSON_THROW(detail::parse_error::create(108, 0, \"escape character '~' must be followed with '0' or '1'\", nullptr));\n                }\n            }\n\n            // finally, store the reference token\n            detail::unescape(reference_token);\n            result.push_back(reference_token);\n        }\n\n        return result;\n    }\n\n  private:\n    /*!\n    @param[in] reference_string  the reference string to the current value\n    @param[in] value             the value to consider\n    @param[in,out] result        the result object to insert values to\n\n    @note Empty objects or arrays are flattened to `null`.\n    */\n    template<typename BasicJsonType>\n    static void flatten(const string_t& reference_string,\n                        const BasicJsonType& value,\n                        BasicJsonType& result)\n    {\n        switch (value.type())\n        {\n            case detail::value_t::array:\n            {\n                if (value.m_data.m_value.array->empty())\n                {\n                    // flatten empty array as null\n                    result[reference_string] = nullptr;\n                }\n                else\n                {\n                    // iterate array and use index as reference string\n                    for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i)\n                    {\n                        flatten(detail::concat(reference_string, '/', std::to_string(i)),\n                                value.m_data.m_value.array->operator[](i), result);\n                    }\n                }\n                break;\n            }\n\n            case detail::value_t::object:\n            {\n                if (value.m_data.m_value.object->empty())\n                {\n                    // flatten empty object as null\n                    result[reference_string] = nullptr;\n                }\n                else\n                {\n                    // iterate object and use keys as reference string\n                    for (const auto& element : *value.m_data.m_value.object)\n                    {\n                        flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result);\n                    }\n                }\n                break;\n            }\n\n            case detail::value_t::null:\n            case detail::value_t::string:\n            case detail::value_t::boolean:\n            case detail::value_t::number_integer:\n            case detail::value_t::number_unsigned:\n            case detail::value_t::number_float:\n            case detail::value_t::binary:\n            case detail::value_t::discarded:\n            default:\n            {\n                // add primitive value with its reference string\n                result[reference_string] = value;\n                break;\n            }\n        }\n    }\n\n    /*!\n    @param[in] value  flattened JSON\n\n    @return unflattened JSON\n\n    @throw parse_error.109 if array index is not a number\n    @throw type_error.314  if value is not an object\n    @throw type_error.315  if object values are not primitive\n    @throw type_error.313  if value cannot be unflattened\n    */\n    template<typename BasicJsonType>\n    static BasicJsonType\n    unflatten(const BasicJsonType& value)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!value.is_object()))\n        {\n            JSON_THROW(detail::type_error::create(314, \"only objects can be unflattened\", &value));\n        }\n\n        BasicJsonType result;\n\n        // iterate the JSON object values\n        for (const auto& element : *value.m_data.m_value.object)\n        {\n            if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))\n            {\n                JSON_THROW(detail::type_error::create(315, \"values in object must be primitive\", &element.second));\n            }\n\n            // assign value to reference pointed to by JSON pointer; Note that if\n            // the JSON pointer is \"\" (i.e., points to the whole value), function\n            // get_and_create returns a reference to result itself. An assignment\n            // will then create a primitive value.\n            json_pointer(element.first).get_and_create(result) = element.second;\n        }\n\n        return result;\n    }\n\n    // can't use conversion operator because of ambiguity\n    json_pointer<string_t> convert() const&\n    {\n        json_pointer<string_t> result;\n        result.reference_tokens = reference_tokens;\n        return result;\n    }\n\n    json_pointer<string_t> convert()&&\n    {\n        json_pointer<string_t> result;\n        result.reference_tokens = std::move(reference_tokens);\n        return result;\n    }\n\n  public:\n#if JSON_HAS_THREE_WAY_COMPARISON\n    /// @brief compares two JSON pointers for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    template<typename RefStringTypeRhs>\n    bool operator==(const json_pointer<RefStringTypeRhs>& rhs) const noexcept\n    {\n        return reference_tokens == rhs.reference_tokens;\n    }\n\n    /// @brief compares JSON pointer and string for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))\n    bool operator==(const string_t& rhs) const\n    {\n        return *this == json_pointer(rhs);\n    }\n\n    /// @brief 3-way compares two JSON pointers\n    template<typename RefStringTypeRhs>\n    std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs) const noexcept // *NOPAD*\n    {\n        return  reference_tokens <=> rhs.reference_tokens; // *NOPAD*\n    }\n#else\n    /// @brief compares two JSON pointers for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    template<typename RefStringTypeLhs, typename RefStringTypeRhs>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                           const json_pointer<RefStringTypeRhs>& rhs) noexcept;\n\n    /// @brief compares JSON pointer and string for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    template<typename RefStringTypeLhs, typename StringType>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                           const StringType& rhs);\n\n    /// @brief compares string and JSON pointer for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    template<typename RefStringTypeRhs, typename StringType>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator==(const StringType& lhs,\n                           const json_pointer<RefStringTypeRhs>& rhs);\n\n    /// @brief compares two JSON pointers for inequality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/\n    template<typename RefStringTypeLhs, typename RefStringTypeRhs>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                           const json_pointer<RefStringTypeRhs>& rhs) noexcept;\n\n    /// @brief compares JSON pointer and string for inequality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/\n    template<typename RefStringTypeLhs, typename StringType>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                           const StringType& rhs);\n\n    /// @brief compares string and JSON pointer for inequality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/\n    template<typename RefStringTypeRhs, typename StringType>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator!=(const StringType& lhs,\n                           const json_pointer<RefStringTypeRhs>& rhs);\n\n    /// @brief compares two JSON pointer for less-than\n    template<typename RefStringTypeLhs, typename RefStringTypeRhs>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator<(const json_pointer<RefStringTypeLhs>& lhs,\n                          const json_pointer<RefStringTypeRhs>& rhs) noexcept;\n#endif\n\n  private:\n    /// the reference tokens\n    std::vector<string_t> reference_tokens;\n};\n\n#if !JSON_HAS_THREE_WAY_COMPARISON\n// functions cannot be defined inside class due to ODR violations\ntemplate<typename RefStringTypeLhs, typename RefStringTypeRhs>\ninline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs) noexcept\n{\n    return lhs.reference_tokens == rhs.reference_tokens;\n}\n\ntemplate<typename RefStringTypeLhs,\n         typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))\ninline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                       const StringType& rhs)\n{\n    return lhs == json_pointer<RefStringTypeLhs>(rhs);\n}\n\ntemplate<typename RefStringTypeRhs,\n         typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))\ninline bool operator==(const StringType& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs)\n{\n    return json_pointer<RefStringTypeRhs>(lhs) == rhs;\n}\n\ntemplate<typename RefStringTypeLhs, typename RefStringTypeRhs>\ninline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs) noexcept\n{\n    return !(lhs == rhs);\n}\n\ntemplate<typename RefStringTypeLhs,\n         typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))\ninline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                       const StringType& rhs)\n{\n    return !(lhs == rhs);\n}\n\ntemplate<typename RefStringTypeRhs,\n         typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))\ninline bool operator!=(const StringType& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs)\n{\n    return !(lhs == rhs);\n}\n\ntemplate<typename RefStringTypeLhs, typename RefStringTypeRhs>\ninline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,\n                      const json_pointer<RefStringTypeRhs>& rhs) noexcept\n{\n    return lhs.reference_tokens < rhs.reference_tokens;\n}\n#endif\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/json_ref.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <initializer_list>\n#include <utility>\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename BasicJsonType>\nclass json_ref\n{\n  public:\n    using value_type = BasicJsonType;\n\n    json_ref(value_type&& value)\n        : owned_value(std::move(value))\n    {}\n\n    json_ref(const value_type& value)\n        : value_ref(&value)\n    {}\n\n    json_ref(std::initializer_list<json_ref> init)\n        : owned_value(init)\n    {}\n\n    template <\n        class... Args,\n        enable_if_t<std::is_constructible<value_type, Args...>::value, int> = 0 >\n    json_ref(Args && ... args)\n        : owned_value(std::forward<Args>(args)...)\n    {}\n\n    // class should be movable only\n    json_ref(json_ref&&) noexcept = default;\n    json_ref(const json_ref&) = delete;\n    json_ref& operator=(const json_ref&) = delete;\n    json_ref& operator=(json_ref&&) = delete;\n    ~json_ref() = default;\n\n    value_type moved_or_copied() const\n    {\n        if (value_ref == nullptr)\n        {\n            return std::move(owned_value);\n        }\n        return *value_ref;\n    }\n\n    value_type const& operator*() const\n    {\n        return value_ref ? *value_ref : owned_value;\n    }\n\n    value_type const* operator->() const\n    {\n        return &** this;\n    }\n\n  private:\n    mutable value_type owned_value = nullptr;\n    value_type const* value_ref = nullptr;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/string_escape.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/output/binary_writer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // reverse\n#include <array> // array\n#include <map> // map\n#include <cmath> // isnan, isinf\n#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t\n#include <cstring> // memcpy\n#include <limits> // numeric_limits\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/input/binary_reader.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/output/output_adapters.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // copy\n#include <cstddef> // size_t\n#include <iterator> // back_inserter\n#include <memory> // shared_ptr, make_shared\n#include <string> // basic_string\n#include <vector> // vector\n\n#ifndef JSON_NO_IO\n    #include <ios>      // streamsize\n    #include <ostream>  // basic_ostream\n#endif  // JSON_NO_IO\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// abstract output adapter interface\ntemplate<typename CharType> struct output_adapter_protocol\n{\n    virtual void write_character(CharType c) = 0;\n    virtual void write_characters(const CharType* s, std::size_t length) = 0;\n    virtual ~output_adapter_protocol() = default;\n\n    output_adapter_protocol() = default;\n    output_adapter_protocol(const output_adapter_protocol&) = default;\n    output_adapter_protocol(output_adapter_protocol&&) noexcept = default;\n    output_adapter_protocol& operator=(const output_adapter_protocol&) = default;\n    output_adapter_protocol& operator=(output_adapter_protocol&&) noexcept = default;\n};\n\n/// a type to simplify interfaces\ntemplate<typename CharType>\nusing output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;\n\n/// output adapter for byte vectors\ntemplate<typename CharType, typename AllocatorType = std::allocator<CharType>>\nclass output_vector_adapter : public output_adapter_protocol<CharType>\n{\n  public:\n    explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept\n        : v(vec)\n    {}\n\n    void write_character(CharType c) override\n    {\n        v.push_back(c);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    void write_characters(const CharType* s, std::size_t length) override\n    {\n        v.insert(v.end(), s, s + length);\n    }\n\n  private:\n    std::vector<CharType, AllocatorType>& v;\n};\n\n#ifndef JSON_NO_IO\n/// output adapter for output streams\ntemplate<typename CharType>\nclass output_stream_adapter : public output_adapter_protocol<CharType>\n{\n  public:\n    explicit output_stream_adapter(std::basic_ostream<CharType>& s) noexcept\n        : stream(s)\n    {}\n\n    void write_character(CharType c) override\n    {\n        stream.put(c);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    void write_characters(const CharType* s, std::size_t length) override\n    {\n        stream.write(s, static_cast<std::streamsize>(length));\n    }\n\n  private:\n    std::basic_ostream<CharType>& stream;\n};\n#endif  // JSON_NO_IO\n\n/// output adapter for basic_string\ntemplate<typename CharType, typename StringType = std::basic_string<CharType>>\nclass output_string_adapter : public output_adapter_protocol<CharType>\n{\n  public:\n    explicit output_string_adapter(StringType& s) noexcept\n        : str(s)\n    {}\n\n    void write_character(CharType c) override\n    {\n        str.push_back(c);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    void write_characters(const CharType* s, std::size_t length) override\n    {\n        str.append(s, length);\n    }\n\n  private:\n    StringType& str;\n};\n\ntemplate<typename CharType, typename StringType = std::basic_string<CharType>>\nclass output_adapter\n{\n  public:\n    template<typename AllocatorType = std::allocator<CharType>>\n    output_adapter(std::vector<CharType, AllocatorType>& vec)\n        : oa(std::make_shared<output_vector_adapter<CharType, AllocatorType>>(vec)) {}\n\n#ifndef JSON_NO_IO\n    output_adapter(std::basic_ostream<CharType>& s)\n        : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}\n#endif  // JSON_NO_IO\n\n    output_adapter(StringType& s)\n        : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}\n\n    operator output_adapter_t<CharType>()\n    {\n        return oa;\n    }\n\n  private:\n    output_adapter_t<CharType> oa = nullptr;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////////////\n// binary writer //\n///////////////////\n\n/*!\n@brief serialization to CBOR and MessagePack values\n*/\ntemplate<typename BasicJsonType, typename CharType>\nclass binary_writer\n{\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n\n  public:\n    /*!\n    @brief create a binary writer\n\n    @param[in] adapter  output adapter to write to\n    */\n    explicit binary_writer(output_adapter_t<CharType> adapter) : oa(std::move(adapter))\n    {\n        JSON_ASSERT(oa);\n    }\n\n    /*!\n    @param[in] j  JSON value to serialize\n    @pre       j.type() == value_t::object\n    */\n    void write_bson(const BasicJsonType& j)\n    {\n        switch (j.type())\n        {\n            case value_t::object:\n            {\n                write_bson_object(*j.m_data.m_value.object);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::array:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                JSON_THROW(type_error::create(317, concat(\"to serialize to BSON, top-level type must be object, but is \", j.type_name()), &j));\n            }\n        }\n    }\n\n    /*!\n    @param[in] j  JSON value to serialize\n    */\n    void write_cbor(const BasicJsonType& j)\n    {\n        switch (j.type())\n        {\n            case value_t::null:\n            {\n                oa->write_character(to_char_type(0xF6));\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                oa->write_character(j.m_data.m_value.boolean\n                                    ? to_char_type(0xF5)\n                                    : to_char_type(0xF4));\n                break;\n            }\n\n            case value_t::number_integer:\n            {\n                if (j.m_data.m_value.number_integer >= 0)\n                {\n                    // CBOR does not differentiate between positive signed\n                    // integers and unsigned integers. Therefore, we used the\n                    // code from the value_t::number_unsigned case here.\n                    if (j.m_data.m_value.number_integer <= 0x17)\n                    {\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x18));\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x19));\n                        write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x1A));\n                        write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));\n                    }\n                    else\n                    {\n                        oa->write_character(to_char_type(0x1B));\n                        write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));\n                    }\n                }\n                else\n                {\n                    // The conversions below encode the sign in the first\n                    // byte, and the value is converted to a positive number.\n                    const auto positive_number = -1 - j.m_data.m_value.number_integer;\n                    if (j.m_data.m_value.number_integer >= -24)\n                    {\n                        write_number(static_cast<std::uint8_t>(0x20 + positive_number));\n                    }\n                    else if (positive_number <= (std::numeric_limits<std::uint8_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x38));\n                        write_number(static_cast<std::uint8_t>(positive_number));\n                    }\n                    else if (positive_number <= (std::numeric_limits<std::uint16_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x39));\n                        write_number(static_cast<std::uint16_t>(positive_number));\n                    }\n                    else if (positive_number <= (std::numeric_limits<std::uint32_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x3A));\n                        write_number(static_cast<std::uint32_t>(positive_number));\n                    }\n                    else\n                    {\n                        oa->write_character(to_char_type(0x3B));\n                        write_number(static_cast<std::uint64_t>(positive_number));\n                    }\n                }\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                if (j.m_data.m_value.number_unsigned <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x18));\n                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x19));\n                    write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_unsigned));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x1A));\n                    write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_unsigned));\n                }\n                else\n                {\n                    oa->write_character(to_char_type(0x1B));\n                    write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));\n                }\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                if (std::isnan(j.m_data.m_value.number_float))\n                {\n                    // NaN is 0xf97e00 in CBOR\n                    oa->write_character(to_char_type(0xF9));\n                    oa->write_character(to_char_type(0x7E));\n                    oa->write_character(to_char_type(0x00));\n                }\n                else if (std::isinf(j.m_data.m_value.number_float))\n                {\n                    // Infinity is 0xf97c00, -Infinity is 0xf9fc00\n                    oa->write_character(to_char_type(0xf9));\n                    oa->write_character(j.m_data.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC));\n                    oa->write_character(to_char_type(0x00));\n                }\n                else\n                {\n                    write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::cbor);\n                }\n                break;\n            }\n\n            case value_t::string:\n            {\n                // step 1: write control byte and the string length\n                const auto N = j.m_data.m_value.string->size();\n                if (N <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(0x60 + N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x78));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x79));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x7A));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n                // LCOV_EXCL_START\n                else if (N <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x7B));\n                    write_number(static_cast<std::uint64_t>(N));\n                }\n                // LCOV_EXCL_STOP\n\n                // step 2: write the string\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()),\n                    j.m_data.m_value.string->size());\n                break;\n            }\n\n            case value_t::array:\n            {\n                // step 1: write control byte and the array size\n                const auto N = j.m_data.m_value.array->size();\n                if (N <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(0x80 + N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x98));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x99));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x9A));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n                // LCOV_EXCL_START\n                else if (N <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x9B));\n                    write_number(static_cast<std::uint64_t>(N));\n                }\n                // LCOV_EXCL_STOP\n\n                // step 2: write each element\n                for (const auto& el : *j.m_data.m_value.array)\n                {\n                    write_cbor(el);\n                }\n                break;\n            }\n\n            case value_t::binary:\n            {\n                if (j.m_data.m_value.binary->has_subtype())\n                {\n                    if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint8_t>::max)())\n                    {\n                        write_number(static_cast<std::uint8_t>(0xd8));\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.binary->subtype()));\n                    }\n                    else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint16_t>::max)())\n                    {\n                        write_number(static_cast<std::uint8_t>(0xd9));\n                        write_number(static_cast<std::uint16_t>(j.m_data.m_value.binary->subtype()));\n                    }\n                    else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint32_t>::max)())\n                    {\n                        write_number(static_cast<std::uint8_t>(0xda));\n                        write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype()));\n                    }\n                    else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())\n                    {\n                        write_number(static_cast<std::uint8_t>(0xdb));\n                        write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype()));\n                    }\n                }\n\n                // step 1: write control byte and the binary array size\n                const auto N = j.m_data.m_value.binary->size();\n                if (N <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(0x40 + N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x58));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x59));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x5A));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n                // LCOV_EXCL_START\n                else if (N <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x5B));\n                    write_number(static_cast<std::uint64_t>(N));\n                }\n                // LCOV_EXCL_STOP\n\n                // step 2: write each element\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),\n                    N);\n\n                break;\n            }\n\n            case value_t::object:\n            {\n                // step 1: write control byte and the object size\n                const auto N = j.m_data.m_value.object->size();\n                if (N <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(0xA0 + N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0xB8));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0xB9));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0xBA));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n                // LCOV_EXCL_START\n                else if (N <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    oa->write_character(to_char_type(0xBB));\n                    write_number(static_cast<std::uint64_t>(N));\n                }\n                // LCOV_EXCL_STOP\n\n                // step 2: write each element\n                for (const auto& el : *j.m_data.m_value.object)\n                {\n                    write_cbor(el.first);\n                    write_cbor(el.second);\n                }\n                break;\n            }\n\n            case value_t::discarded:\n            default:\n                break;\n        }\n    }\n\n    /*!\n    @param[in] j  JSON value to serialize\n    */\n    void write_msgpack(const BasicJsonType& j)\n    {\n        switch (j.type())\n        {\n            case value_t::null: // nil\n            {\n                oa->write_character(to_char_type(0xC0));\n                break;\n            }\n\n            case value_t::boolean: // true and false\n            {\n                oa->write_character(j.m_data.m_value.boolean\n                                    ? to_char_type(0xC3)\n                                    : to_char_type(0xC2));\n                break;\n            }\n\n            case value_t::number_integer:\n            {\n                if (j.m_data.m_value.number_integer >= 0)\n                {\n                    // MessagePack does not differentiate between positive\n                    // signed integers and unsigned integers. Therefore, we used\n                    // the code from the value_t::number_unsigned case here.\n                    if (j.m_data.m_value.number_unsigned < 128)\n                    {\n                        // positive fixnum\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())\n                    {\n                        // uint 8\n                        oa->write_character(to_char_type(0xCC));\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())\n                    {\n                        // uint 16\n                        oa->write_character(to_char_type(0xCD));\n                        write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())\n                    {\n                        // uint 32\n                        oa->write_character(to_char_type(0xCE));\n                        write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())\n                    {\n                        // uint 64\n                        oa->write_character(to_char_type(0xCF));\n                        write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));\n                    }\n                }\n                else\n                {\n                    if (j.m_data.m_value.number_integer >= -32)\n                    {\n                        // negative fixnum\n                        write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&\n                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())\n                    {\n                        // int 8\n                        oa->write_character(to_char_type(0xD0));\n                        write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&\n                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())\n                    {\n                        // int 16\n                        oa->write_character(to_char_type(0xD1));\n                        write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&\n                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())\n                    {\n                        // int 32\n                        oa->write_character(to_char_type(0xD2));\n                        write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() &&\n                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())\n                    {\n                        // int 64\n                        oa->write_character(to_char_type(0xD3));\n                        write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));\n                    }\n                }\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                if (j.m_data.m_value.number_unsigned < 128)\n                {\n                    // positive fixnum\n                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    // uint 8\n                    oa->write_character(to_char_type(0xCC));\n                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    // uint 16\n                    oa->write_character(to_char_type(0xCD));\n                    write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    // uint 32\n                    oa->write_character(to_char_type(0xCE));\n                    write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    // uint 64\n                    oa->write_character(to_char_type(0xCF));\n                    write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));\n                }\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::msgpack);\n                break;\n            }\n\n            case value_t::string:\n            {\n                // step 1: write control byte and the string length\n                const auto N = j.m_data.m_value.string->size();\n                if (N <= 31)\n                {\n                    // fixstr\n                    write_number(static_cast<std::uint8_t>(0xA0 | N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    // str 8\n                    oa->write_character(to_char_type(0xD9));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    // str 16\n                    oa->write_character(to_char_type(0xDA));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    // str 32\n                    oa->write_character(to_char_type(0xDB));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n\n                // step 2: write the string\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()),\n                    j.m_data.m_value.string->size());\n                break;\n            }\n\n            case value_t::array:\n            {\n                // step 1: write control byte and the array size\n                const auto N = j.m_data.m_value.array->size();\n                if (N <= 15)\n                {\n                    // fixarray\n                    write_number(static_cast<std::uint8_t>(0x90 | N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    // array 16\n                    oa->write_character(to_char_type(0xDC));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    // array 32\n                    oa->write_character(to_char_type(0xDD));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n\n                // step 2: write each element\n                for (const auto& el : *j.m_data.m_value.array)\n                {\n                    write_msgpack(el);\n                }\n                break;\n            }\n\n            case value_t::binary:\n            {\n                // step 0: determine if the binary type has a set subtype to\n                // determine whether or not to use the ext or fixext types\n                const bool use_ext = j.m_data.m_value.binary->has_subtype();\n\n                // step 1: write control byte and the byte string length\n                const auto N = j.m_data.m_value.binary->size();\n                if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    std::uint8_t output_type{};\n                    bool fixed = true;\n                    if (use_ext)\n                    {\n                        switch (N)\n                        {\n                            case 1:\n                                output_type = 0xD4; // fixext 1\n                                break;\n                            case 2:\n                                output_type = 0xD5; // fixext 2\n                                break;\n                            case 4:\n                                output_type = 0xD6; // fixext 4\n                                break;\n                            case 8:\n                                output_type = 0xD7; // fixext 8\n                                break;\n                            case 16:\n                                output_type = 0xD8; // fixext 16\n                                break;\n                            default:\n                                output_type = 0xC7; // ext 8\n                                fixed = false;\n                                break;\n                        }\n\n                    }\n                    else\n                    {\n                        output_type = 0xC4; // bin 8\n                        fixed = false;\n                    }\n\n                    oa->write_character(to_char_type(output_type));\n                    if (!fixed)\n                    {\n                        write_number(static_cast<std::uint8_t>(N));\n                    }\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    const std::uint8_t output_type = use_ext\n                                                     ? 0xC8 // ext 16\n                                                     : 0xC5; // bin 16\n\n                    oa->write_character(to_char_type(output_type));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    const std::uint8_t output_type = use_ext\n                                                     ? 0xC9 // ext 32\n                                                     : 0xC6; // bin 32\n\n                    oa->write_character(to_char_type(output_type));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n\n                // step 1.5: if this is an ext type, write the subtype\n                if (use_ext)\n                {\n                    write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype()));\n                }\n\n                // step 2: write the byte string\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),\n                    N);\n\n                break;\n            }\n\n            case value_t::object:\n            {\n                // step 1: write control byte and the object size\n                const auto N = j.m_data.m_value.object->size();\n                if (N <= 15)\n                {\n                    // fixmap\n                    write_number(static_cast<std::uint8_t>(0x80 | (N & 0xF)));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    // map 16\n                    oa->write_character(to_char_type(0xDE));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    // map 32\n                    oa->write_character(to_char_type(0xDF));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n\n                // step 2: write each element\n                for (const auto& el : *j.m_data.m_value.object)\n                {\n                    write_msgpack(el.first);\n                    write_msgpack(el.second);\n                }\n                break;\n            }\n\n            case value_t::discarded:\n            default:\n                break;\n        }\n    }\n\n    /*!\n    @param[in] j  JSON value to serialize\n    @param[in] use_count   whether to use '#' prefixes (optimized format)\n    @param[in] use_type    whether to use '$' prefixes (optimized format)\n    @param[in] add_prefix  whether prefixes need to be used for this value\n    @param[in] use_bjdata  whether write in BJData format, default is false\n    */\n    void write_ubjson(const BasicJsonType& j, const bool use_count,\n                      const bool use_type, const bool add_prefix = true,\n                      const bool use_bjdata = false)\n    {\n        switch (j.type())\n        {\n            case value_t::null:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('Z'));\n                }\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(j.m_data.m_value.boolean\n                                        ? to_char_type('T')\n                                        : to_char_type('F'));\n                }\n                break;\n            }\n\n            case value_t::number_integer:\n            {\n                write_number_with_ubjson_prefix(j.m_data.m_value.number_integer, add_prefix, use_bjdata);\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                write_number_with_ubjson_prefix(j.m_data.m_value.number_unsigned, add_prefix, use_bjdata);\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                write_number_with_ubjson_prefix(j.m_data.m_value.number_float, add_prefix, use_bjdata);\n                break;\n            }\n\n            case value_t::string:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('S'));\n                }\n                write_number_with_ubjson_prefix(j.m_data.m_value.string->size(), true, use_bjdata);\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()),\n                    j.m_data.m_value.string->size());\n                break;\n            }\n\n            case value_t::array:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('['));\n                }\n\n                bool prefix_required = true;\n                if (use_type && !j.m_data.m_value.array->empty())\n                {\n                    JSON_ASSERT(use_count);\n                    const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);\n                    const bool same_prefix = std::all_of(j.begin() + 1, j.end(),\n                                                         [this, first_prefix, use_bjdata](const BasicJsonType & v)\n                    {\n                        return ubjson_prefix(v, use_bjdata) == first_prefix;\n                    });\n\n                    std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type\n\n                    if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end()))\n                    {\n                        prefix_required = false;\n                        oa->write_character(to_char_type('$'));\n                        oa->write_character(first_prefix);\n                    }\n                }\n\n                if (use_count)\n                {\n                    oa->write_character(to_char_type('#'));\n                    write_number_with_ubjson_prefix(j.m_data.m_value.array->size(), true, use_bjdata);\n                }\n\n                for (const auto& el : *j.m_data.m_value.array)\n                {\n                    write_ubjson(el, use_count, use_type, prefix_required, use_bjdata);\n                }\n\n                if (!use_count)\n                {\n                    oa->write_character(to_char_type(']'));\n                }\n\n                break;\n            }\n\n            case value_t::binary:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('['));\n                }\n\n                if (use_type && !j.m_data.m_value.binary->empty())\n                {\n                    JSON_ASSERT(use_count);\n                    oa->write_character(to_char_type('$'));\n                    oa->write_character('U');\n                }\n\n                if (use_count)\n                {\n                    oa->write_character(to_char_type('#'));\n                    write_number_with_ubjson_prefix(j.m_data.m_value.binary->size(), true, use_bjdata);\n                }\n\n                if (use_type)\n                {\n                    oa->write_characters(\n                        reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),\n                        j.m_data.m_value.binary->size());\n                }\n                else\n                {\n                    for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i)\n                    {\n                        oa->write_character(to_char_type('U'));\n                        oa->write_character(j.m_data.m_value.binary->data()[i]);\n                    }\n                }\n\n                if (!use_count)\n                {\n                    oa->write_character(to_char_type(']'));\n                }\n\n                break;\n            }\n\n            case value_t::object:\n            {\n                if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find(\"_ArrayType_\") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find(\"_ArraySize_\") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find(\"_ArrayData_\") != j.m_data.m_value.object->end())\n                {\n                    if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type))  // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata)\n                    {\n                        break;\n                    }\n                }\n\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('{'));\n                }\n\n                bool prefix_required = true;\n                if (use_type && !j.m_data.m_value.object->empty())\n                {\n                    JSON_ASSERT(use_count);\n                    const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);\n                    const bool same_prefix = std::all_of(j.begin(), j.end(),\n                                                         [this, first_prefix, use_bjdata](const BasicJsonType & v)\n                    {\n                        return ubjson_prefix(v, use_bjdata) == first_prefix;\n                    });\n\n                    std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type\n\n                    if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end()))\n                    {\n                        prefix_required = false;\n                        oa->write_character(to_char_type('$'));\n                        oa->write_character(first_prefix);\n                    }\n                }\n\n                if (use_count)\n                {\n                    oa->write_character(to_char_type('#'));\n                    write_number_with_ubjson_prefix(j.m_data.m_value.object->size(), true, use_bjdata);\n                }\n\n                for (const auto& el : *j.m_data.m_value.object)\n                {\n                    write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata);\n                    oa->write_characters(\n                        reinterpret_cast<const CharType*>(el.first.c_str()),\n                        el.first.size());\n                    write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata);\n                }\n\n                if (!use_count)\n                {\n                    oa->write_character(to_char_type('}'));\n                }\n\n                break;\n            }\n\n            case value_t::discarded:\n            default:\n                break;\n        }\n    }\n\n  private:\n    //////////\n    // BSON //\n    //////////\n\n    /*!\n    @return The size of a BSON document entry header, including the id marker\n            and the entry name size (and its null-terminator).\n    */\n    static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j)\n    {\n        const auto it = name.find(static_cast<typename string_t::value_type>(0));\n        if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos))\n        {\n            JSON_THROW(out_of_range::create(409, concat(\"BSON key cannot contain code point U+0000 (at byte \", std::to_string(it), \")\"), &j));\n            static_cast<void>(j);\n        }\n\n        return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;\n    }\n\n    /*!\n    @brief Writes the given @a element_type and @a name to the output adapter\n    */\n    void write_bson_entry_header(const string_t& name,\n                                 const std::uint8_t element_type)\n    {\n        oa->write_character(to_char_type(element_type)); // boolean\n        oa->write_characters(\n            reinterpret_cast<const CharType*>(name.c_str()),\n            name.size() + 1u);\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and boolean value @a value\n    */\n    void write_bson_boolean(const string_t& name,\n                            const bool value)\n    {\n        write_bson_entry_header(name, 0x08);\n        oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00));\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and double value @a value\n    */\n    void write_bson_double(const string_t& name,\n                           const double value)\n    {\n        write_bson_entry_header(name, 0x01);\n        write_number<double>(value, true);\n    }\n\n    /*!\n    @return The size of the BSON-encoded string in @a value\n    */\n    static std::size_t calc_bson_string_size(const string_t& value)\n    {\n        return sizeof(std::int32_t) + value.size() + 1ul;\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and string value @a value\n    */\n    void write_bson_string(const string_t& name,\n                           const string_t& value)\n    {\n        write_bson_entry_header(name, 0x02);\n\n        write_number<std::int32_t>(static_cast<std::int32_t>(value.size() + 1ul), true);\n        oa->write_characters(\n            reinterpret_cast<const CharType*>(value.c_str()),\n            value.size() + 1);\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and null value\n    */\n    void write_bson_null(const string_t& name)\n    {\n        write_bson_entry_header(name, 0x0A);\n    }\n\n    /*!\n    @return The size of the BSON-encoded integer @a value\n    */\n    static std::size_t calc_bson_integer_size(const std::int64_t value)\n    {\n        return (std::numeric_limits<std::int32_t>::min)() <= value && value <= (std::numeric_limits<std::int32_t>::max)()\n               ? sizeof(std::int32_t)\n               : sizeof(std::int64_t);\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and integer @a value\n    */\n    void write_bson_integer(const string_t& name,\n                            const std::int64_t value)\n    {\n        if ((std::numeric_limits<std::int32_t>::min)() <= value && value <= (std::numeric_limits<std::int32_t>::max)())\n        {\n            write_bson_entry_header(name, 0x10); // int32\n            write_number<std::int32_t>(static_cast<std::int32_t>(value), true);\n        }\n        else\n        {\n            write_bson_entry_header(name, 0x12); // int64\n            write_number<std::int64_t>(static_cast<std::int64_t>(value), true);\n        }\n    }\n\n    /*!\n    @return The size of the BSON-encoded unsigned integer in @a j\n    */\n    static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept\n    {\n        return (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))\n               ? sizeof(std::int32_t)\n               : sizeof(std::int64_t);\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and unsigned @a value\n    */\n    void write_bson_unsigned(const string_t& name,\n                             const BasicJsonType& j)\n    {\n        if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))\n        {\n            write_bson_entry_header(name, 0x10 /* int32 */);\n            write_number<std::int32_t>(static_cast<std::int32_t>(j.m_data.m_value.number_unsigned), true);\n        }\n        else if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))\n        {\n            write_bson_entry_header(name, 0x12 /* int64 */);\n            write_number<std::int64_t>(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned), true);\n        }\n        else\n        {\n            JSON_THROW(out_of_range::create(407, concat(\"integer number \", std::to_string(j.m_data.m_value.number_unsigned), \" cannot be represented by BSON as it does not fit int64\"), &j));\n        }\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and object @a value\n    */\n    void write_bson_object_entry(const string_t& name,\n                                 const typename BasicJsonType::object_t& value)\n    {\n        write_bson_entry_header(name, 0x03); // object\n        write_bson_object(value);\n    }\n\n    /*!\n    @return The size of the BSON-encoded array @a value\n    */\n    static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value)\n    {\n        std::size_t array_index = 0ul;\n\n        const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), static_cast<std::size_t>(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el)\n        {\n            return result + calc_bson_element_size(std::to_string(array_index++), el);\n        });\n\n        return sizeof(std::int32_t) + embedded_document_size + 1ul;\n    }\n\n    /*!\n    @return The size of the BSON-encoded binary array @a value\n    */\n    static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value)\n    {\n        return sizeof(std::int32_t) + value.size() + 1ul;\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and array @a value\n    */\n    void write_bson_array(const string_t& name,\n                          const typename BasicJsonType::array_t& value)\n    {\n        write_bson_entry_header(name, 0x04); // array\n        write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_array_size(value)), true);\n\n        std::size_t array_index = 0ul;\n\n        for (const auto& el : value)\n        {\n            write_bson_element(std::to_string(array_index++), el);\n        }\n\n        oa->write_character(to_char_type(0x00));\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and binary value @a value\n    */\n    void write_bson_binary(const string_t& name,\n                           const binary_t& value)\n    {\n        write_bson_entry_header(name, 0x05);\n\n        write_number<std::int32_t>(static_cast<std::int32_t>(value.size()), true);\n        write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));\n\n        oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());\n    }\n\n    /*!\n    @brief Calculates the size necessary to serialize the JSON value @a j with its @a name\n    @return The calculated size for the BSON document entry for @a j with the given @a name.\n    */\n    static std::size_t calc_bson_element_size(const string_t& name,\n            const BasicJsonType& j)\n    {\n        const auto header_size = calc_bson_entry_header_size(name, j);\n        switch (j.type())\n        {\n            case value_t::object:\n                return header_size + calc_bson_object_size(*j.m_data.m_value.object);\n\n            case value_t::array:\n                return header_size + calc_bson_array_size(*j.m_data.m_value.array);\n\n            case value_t::binary:\n                return header_size + calc_bson_binary_size(*j.m_data.m_value.binary);\n\n            case value_t::boolean:\n                return header_size + 1ul;\n\n            case value_t::number_float:\n                return header_size + 8ul;\n\n            case value_t::number_integer:\n                return header_size + calc_bson_integer_size(j.m_data.m_value.number_integer);\n\n            case value_t::number_unsigned:\n                return header_size + calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);\n\n            case value_t::string:\n                return header_size + calc_bson_string_size(*j.m_data.m_value.string);\n\n            case value_t::null:\n                return header_size + 0ul;\n\n            // LCOV_EXCL_START\n            case value_t::discarded:\n            default:\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)\n                return 0ul;\n                // LCOV_EXCL_STOP\n        }\n    }\n\n    /*!\n    @brief Serializes the JSON value @a j to BSON and associates it with the\n           key @a name.\n    @param name The name to associate with the JSON entity @a j within the\n                current BSON document\n    */\n    void write_bson_element(const string_t& name,\n                            const BasicJsonType& j)\n    {\n        switch (j.type())\n        {\n            case value_t::object:\n                return write_bson_object_entry(name, *j.m_data.m_value.object);\n\n            case value_t::array:\n                return write_bson_array(name, *j.m_data.m_value.array);\n\n            case value_t::binary:\n                return write_bson_binary(name, *j.m_data.m_value.binary);\n\n            case value_t::boolean:\n                return write_bson_boolean(name, j.m_data.m_value.boolean);\n\n            case value_t::number_float:\n                return write_bson_double(name, j.m_data.m_value.number_float);\n\n            case value_t::number_integer:\n                return write_bson_integer(name, j.m_data.m_value.number_integer);\n\n            case value_t::number_unsigned:\n                return write_bson_unsigned(name, j);\n\n            case value_t::string:\n                return write_bson_string(name, *j.m_data.m_value.string);\n\n            case value_t::null:\n                return write_bson_null(name);\n\n            // LCOV_EXCL_START\n            case value_t::discarded:\n            default:\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)\n                return;\n                // LCOV_EXCL_STOP\n        }\n    }\n\n    /*!\n    @brief Calculates the size of the BSON serialization of the given\n           JSON-object @a j.\n    @param[in] value  JSON value to serialize\n    @pre       value.type() == value_t::object\n    */\n    static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value)\n    {\n        const std::size_t document_size = std::accumulate(value.begin(), value.end(), static_cast<std::size_t>(0),\n                                          [](size_t result, const typename BasicJsonType::object_t::value_type & el)\n        {\n            return result += calc_bson_element_size(el.first, el.second);\n        });\n\n        return sizeof(std::int32_t) + document_size + 1ul;\n    }\n\n    /*!\n    @param[in] value  JSON value to serialize\n    @pre       value.type() == value_t::object\n    */\n    void write_bson_object(const typename BasicJsonType::object_t& value)\n    {\n        write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_object_size(value)), true);\n\n        for (const auto& el : value)\n        {\n            write_bson_element(el.first, el.second);\n        }\n\n        oa->write_character(to_char_type(0x00));\n    }\n\n    //////////\n    // CBOR //\n    //////////\n\n    static constexpr CharType get_cbor_float_prefix(float /*unused*/)\n    {\n        return to_char_type(0xFA);  // Single-Precision Float\n    }\n\n    static constexpr CharType get_cbor_float_prefix(double /*unused*/)\n    {\n        return to_char_type(0xFB);  // Double-Precision Float\n    }\n\n    /////////////\n    // MsgPack //\n    /////////////\n\n    static constexpr CharType get_msgpack_float_prefix(float /*unused*/)\n    {\n        return to_char_type(0xCA);  // float 32\n    }\n\n    static constexpr CharType get_msgpack_float_prefix(double /*unused*/)\n    {\n        return to_char_type(0xCB);  // float 64\n    }\n\n    ////////////\n    // UBJSON //\n    ////////////\n\n    // UBJSON: write number (floating point)\n    template<typename NumberType, typename std::enable_if<\n                 std::is_floating_point<NumberType>::value, int>::type = 0>\n    void write_number_with_ubjson_prefix(const NumberType n,\n                                         const bool add_prefix,\n                                         const bool use_bjdata)\n    {\n        if (add_prefix)\n        {\n            oa->write_character(get_ubjson_float_prefix(n));\n        }\n        write_number(n, use_bjdata);\n    }\n\n    // UBJSON: write number (unsigned integer)\n    template<typename NumberType, typename std::enable_if<\n                 std::is_unsigned<NumberType>::value, int>::type = 0>\n    void write_number_with_ubjson_prefix(const NumberType n,\n                                         const bool add_prefix,\n                                         const bool use_bjdata)\n    {\n        if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('i'));  // int8\n            }\n            write_number(static_cast<std::uint8_t>(n), use_bjdata);\n        }\n        else if (n <= (std::numeric_limits<std::uint8_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('U'));  // uint8\n            }\n            write_number(static_cast<std::uint8_t>(n), use_bjdata);\n        }\n        else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('I'));  // int16\n            }\n            write_number(static_cast<std::int16_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint16_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('u'));  // uint16 - bjdata only\n            }\n            write_number(static_cast<std::uint16_t>(n), use_bjdata);\n        }\n        else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('l'));  // int32\n            }\n            write_number(static_cast<std::int32_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint32_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('m'));  // uint32 - bjdata only\n            }\n            write_number(static_cast<std::uint32_t>(n), use_bjdata);\n        }\n        else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('L'));  // int64\n            }\n            write_number(static_cast<std::int64_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && n <= (std::numeric_limits<uint64_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('M'));  // uint64 - bjdata only\n            }\n            write_number(static_cast<std::uint64_t>(n), use_bjdata);\n        }\n        else\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('H'));  // high-precision number\n            }\n\n            const auto number = BasicJsonType(n).dump();\n            write_number_with_ubjson_prefix(number.size(), true, use_bjdata);\n            for (std::size_t i = 0; i < number.size(); ++i)\n            {\n                oa->write_character(to_char_type(static_cast<std::uint8_t>(number[i])));\n            }\n        }\n    }\n\n    // UBJSON: write number (signed integer)\n    template < typename NumberType, typename std::enable_if <\n                   std::is_signed<NumberType>::value&&\n                   !std::is_floating_point<NumberType>::value, int >::type = 0 >\n    void write_number_with_ubjson_prefix(const NumberType n,\n                                         const bool add_prefix,\n                                         const bool use_bjdata)\n    {\n        if ((std::numeric_limits<std::int8_t>::min)() <= n && n <= (std::numeric_limits<std::int8_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('i'));  // int8\n            }\n            write_number(static_cast<std::int8_t>(n), use_bjdata);\n        }\n        else if (static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('U'));  // uint8\n            }\n            write_number(static_cast<std::uint8_t>(n), use_bjdata);\n        }\n        else if ((std::numeric_limits<std::int16_t>::min)() <= n && n <= (std::numeric_limits<std::int16_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('I'));  // int16\n            }\n            write_number(static_cast<std::int16_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::max)())))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('u'));  // uint16 - bjdata only\n            }\n            write_number(static_cast<uint16_t>(n), use_bjdata);\n        }\n        else if ((std::numeric_limits<std::int32_t>::min)() <= n && n <= (std::numeric_limits<std::int32_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('l'));  // int32\n            }\n            write_number(static_cast<std::int32_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::max)())))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('m'));  // uint32 - bjdata only\n            }\n            write_number(static_cast<uint32_t>(n), use_bjdata);\n        }\n        else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('L'));  // int64\n            }\n            write_number(static_cast<std::int64_t>(n), use_bjdata);\n        }\n        // LCOV_EXCL_START\n        else\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('H'));  // high-precision number\n            }\n\n            const auto number = BasicJsonType(n).dump();\n            write_number_with_ubjson_prefix(number.size(), true, use_bjdata);\n            for (std::size_t i = 0; i < number.size(); ++i)\n            {\n                oa->write_character(to_char_type(static_cast<std::uint8_t>(number[i])));\n            }\n        }\n        // LCOV_EXCL_STOP\n    }\n\n    /*!\n    @brief determine the type prefix of container values\n    */\n    CharType ubjson_prefix(const BasicJsonType& j, const bool use_bjdata) const noexcept\n    {\n        switch (j.type())\n        {\n            case value_t::null:\n                return 'Z';\n\n            case value_t::boolean:\n                return j.m_data.m_value.boolean ? 'T' : 'F';\n\n            case value_t::number_integer:\n            {\n                if ((std::numeric_limits<std::int8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())\n                {\n                    return 'i';\n                }\n                if ((std::numeric_limits<std::uint8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    return 'U';\n                }\n                if ((std::numeric_limits<std::int16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())\n                {\n                    return 'I';\n                }\n                if (use_bjdata && ((std::numeric_limits<std::uint16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)()))\n                {\n                    return 'u';\n                }\n                if ((std::numeric_limits<std::int32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())\n                {\n                    return 'l';\n                }\n                if (use_bjdata && ((std::numeric_limits<std::uint32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)()))\n                {\n                    return 'm';\n                }\n                if ((std::numeric_limits<std::int64_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())\n                {\n                    return 'L';\n                }\n                // anything else is treated as high-precision number\n                return 'H'; // LCOV_EXCL_LINE\n            }\n\n            case value_t::number_unsigned:\n            {\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))\n                {\n                    return 'i';\n                }\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint8_t>::max)()))\n                {\n                    return 'U';\n                }\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))\n                {\n                    return 'I';\n                }\n                if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint16_t>::max)()))\n                {\n                    return 'u';\n                }\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))\n                {\n                    return 'l';\n                }\n                if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)()))\n                {\n                    return 'm';\n                }\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))\n                {\n                    return 'L';\n                }\n                if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    return 'M';\n                }\n                // anything else is treated as high-precision number\n                return 'H'; // LCOV_EXCL_LINE\n            }\n\n            case value_t::number_float:\n                return get_ubjson_float_prefix(j.m_data.m_value.number_float);\n\n            case value_t::string:\n                return 'S';\n\n            case value_t::array: // fallthrough\n            case value_t::binary:\n                return '[';\n\n            case value_t::object:\n                return '{';\n\n            case value_t::discarded:\n            default:  // discarded values\n                return 'N';\n        }\n    }\n\n    static constexpr CharType get_ubjson_float_prefix(float /*unused*/)\n    {\n        return 'd';  // float 32\n    }\n\n    static constexpr CharType get_ubjson_float_prefix(double /*unused*/)\n    {\n        return 'D';  // float 64\n    }\n\n    /*!\n    @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid\n    */\n    bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type)\n    {\n        std::map<string_t, CharType> bjdtype = {{\"uint8\", 'U'},  {\"int8\", 'i'},  {\"uint16\", 'u'}, {\"int16\", 'I'},\n            {\"uint32\", 'm'}, {\"int32\", 'l'}, {\"uint64\", 'M'}, {\"int64\", 'L'}, {\"single\", 'd'}, {\"double\", 'D'}, {\"char\", 'C'}\n        };\n\n        string_t key = \"_ArrayType_\";\n        auto it = bjdtype.find(static_cast<string_t>(value.at(key)));\n        if (it == bjdtype.end())\n        {\n            return true;\n        }\n        CharType dtype = it->second;\n\n        key = \"_ArraySize_\";\n        std::size_t len = (value.at(key).empty() ? 0 : 1);\n        for (const auto& el : value.at(key))\n        {\n            len *= static_cast<std::size_t>(el.m_data.m_value.number_unsigned);\n        }\n\n        key = \"_ArrayData_\";\n        if (value.at(key).size() != len)\n        {\n            return true;\n        }\n\n        oa->write_character('[');\n        oa->write_character('$');\n        oa->write_character(dtype);\n        oa->write_character('#');\n\n        key = \"_ArraySize_\";\n        write_ubjson(value.at(key), use_count, use_type, true,  true);\n\n        key = \"_ArrayData_\";\n        if (dtype == 'U' || dtype == 'C')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::uint8_t>(el.m_data.m_value.number_unsigned), true);\n            }\n        }\n        else if (dtype == 'i')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::int8_t>(el.m_data.m_value.number_integer), true);\n            }\n        }\n        else if (dtype == 'u')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::uint16_t>(el.m_data.m_value.number_unsigned), true);\n            }\n        }\n        else if (dtype == 'I')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::int16_t>(el.m_data.m_value.number_integer), true);\n            }\n        }\n        else if (dtype == 'm')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::uint32_t>(el.m_data.m_value.number_unsigned), true);\n            }\n        }\n        else if (dtype == 'l')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::int32_t>(el.m_data.m_value.number_integer), true);\n            }\n        }\n        else if (dtype == 'M')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::uint64_t>(el.m_data.m_value.number_unsigned), true);\n            }\n        }\n        else if (dtype == 'L')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::int64_t>(el.m_data.m_value.number_integer), true);\n            }\n        }\n        else if (dtype == 'd')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<float>(el.m_data.m_value.number_float), true);\n            }\n        }\n        else if (dtype == 'D')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<double>(el.m_data.m_value.number_float), true);\n            }\n        }\n        return false;\n    }\n\n    ///////////////////////\n    // Utility functions //\n    ///////////////////////\n\n    /*\n    @brief write a number to output input\n    @param[in] n number of type @a NumberType\n    @param[in] OutputIsLittleEndian Set to true if output data is\n                                 required to be little endian\n    @tparam NumberType the type of the number\n\n    @note This function needs to respect the system's endianness, because bytes\n          in CBOR, MessagePack, and UBJSON are stored in network order (big\n          endian) and therefore need reordering on little endian systems.\n          On the other hand, BSON and BJData use little endian and should reorder\n          on big endian systems.\n    */\n    template<typename NumberType>\n    void write_number(const NumberType n, const bool OutputIsLittleEndian = false)\n    {\n        // step 1: write number to array of length NumberType\n        std::array<CharType, sizeof(NumberType)> vec{};\n        std::memcpy(vec.data(), &n, sizeof(NumberType));\n\n        // step 2: write array to output (with possible reordering)\n        if (is_little_endian != OutputIsLittleEndian)\n        {\n            // reverse byte order prior to conversion if necessary\n            std::reverse(vec.begin(), vec.end());\n        }\n\n        oa->write_characters(vec.data(), sizeof(NumberType));\n    }\n\n    void write_compact_float(const number_float_t n, detail::input_format_t format)\n    {\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n        if (static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) &&\n                static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) &&\n                static_cast<double>(static_cast<float>(n)) == static_cast<double>(n))\n        {\n            oa->write_character(format == detail::input_format_t::cbor\n                                ? get_cbor_float_prefix(static_cast<float>(n))\n                                : get_msgpack_float_prefix(static_cast<float>(n)));\n            write_number(static_cast<float>(n));\n        }\n        else\n        {\n            oa->write_character(format == detail::input_format_t::cbor\n                                ? get_cbor_float_prefix(n)\n                                : get_msgpack_float_prefix(n));\n            write_number(n);\n        }\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n    }\n\n  public:\n    // The following to_char_type functions are implement the conversion\n    // between uint8_t and CharType. In case CharType is not unsigned,\n    // such a conversion is required to allow values greater than 128.\n    // See <https://github.com/nlohmann/json/issues/1286> for a discussion.\n    template < typename C = CharType,\n               enable_if_t < std::is_signed<C>::value && std::is_signed<char>::value > * = nullptr >\n    static constexpr CharType to_char_type(std::uint8_t x) noexcept\n    {\n        return *reinterpret_cast<char*>(&x);\n    }\n\n    template < typename C = CharType,\n               enable_if_t < std::is_signed<C>::value && std::is_unsigned<char>::value > * = nullptr >\n    static CharType to_char_type(std::uint8_t x) noexcept\n    {\n        static_assert(sizeof(std::uint8_t) == sizeof(CharType), \"size of CharType must be equal to std::uint8_t\");\n        static_assert(std::is_trivial<CharType>::value, \"CharType must be trivial\");\n        CharType result;\n        std::memcpy(&result, &x, sizeof(x));\n        return result;\n    }\n\n    template<typename C = CharType,\n             enable_if_t<std::is_unsigned<C>::value>* = nullptr>\n    static constexpr CharType to_char_type(std::uint8_t x) noexcept\n    {\n        return x;\n    }\n\n    template < typename InputCharType, typename C = CharType,\n               enable_if_t <\n                   std::is_signed<C>::value &&\n                   std::is_signed<char>::value &&\n                   std::is_same<char, typename std::remove_cv<InputCharType>::type>::value\n                   > * = nullptr >\n    static constexpr CharType to_char_type(InputCharType x) noexcept\n    {\n        return x;\n    }\n\n  private:\n    /// whether we can assume little endianness\n    const bool is_little_endian = little_endianness();\n\n    /// the output\n    output_adapter_t<CharType> oa = nullptr;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/output/output_adapters.hpp>\n\n// #include <nlohmann/detail/output/serializer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2008-2009 Björn Hoehrmann <bjoern@hoehrmann.de>\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // reverse, remove, fill, find, none_of\n#include <array> // array\n#include <clocale> // localeconv, lconv\n#include <cmath> // labs, isfinite, isnan, signbit\n#include <cstddef> // size_t, ptrdiff_t\n#include <cstdint> // uint8_t\n#include <cstdio> // snprintf\n#include <limits> // numeric_limits\n#include <string> // string, char_traits\n#include <iomanip> // setfill, setw\n#include <type_traits> // is_same\n#include <utility> // move\n\n// #include <nlohmann/detail/conversions/to_chars.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2009 Florian Loitsch <https://florian.loitsch.com/>\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cmath>   // signbit, isfinite\n#include <cstdint> // intN_t, uintN_t\n#include <cstring> // memcpy, memmove\n#include <limits> // numeric_limits\n#include <type_traits> // conditional\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief implements the Grisu2 algorithm for binary to decimal floating-point\nconversion.\n\nThis implementation is a slightly modified version of the reference\nimplementation which may be obtained from\nhttp://florian.loitsch.com/publications (bench.tar.gz).\n\nThe code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch.\n\nFor a detailed description of the algorithm see:\n\n[1] Loitsch, \"Printing Floating-Point Numbers Quickly and Accurately with\n    Integers\", Proceedings of the ACM SIGPLAN 2010 Conference on Programming\n    Language Design and Implementation, PLDI 2010\n[2] Burger, Dybvig, \"Printing Floating-Point Numbers Quickly and Accurately\",\n    Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language\n    Design and Implementation, PLDI 1996\n*/\nnamespace dtoa_impl\n{\n\ntemplate<typename Target, typename Source>\nTarget reinterpret_bits(const Source source)\n{\n    static_assert(sizeof(Target) == sizeof(Source), \"size mismatch\");\n\n    Target target;\n    std::memcpy(&target, &source, sizeof(Source));\n    return target;\n}\n\nstruct diyfp // f * 2^e\n{\n    static constexpr int kPrecision = 64; // = q\n\n    std::uint64_t f = 0;\n    int e = 0;\n\n    constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {}\n\n    /*!\n    @brief returns x - y\n    @pre x.e == y.e and x.f >= y.f\n    */\n    static diyfp sub(const diyfp& x, const diyfp& y) noexcept\n    {\n        JSON_ASSERT(x.e == y.e);\n        JSON_ASSERT(x.f >= y.f);\n\n        return {x.f - y.f, x.e};\n    }\n\n    /*!\n    @brief returns x * y\n    @note The result is rounded. (Only the upper q bits are returned.)\n    */\n    static diyfp mul(const diyfp& x, const diyfp& y) noexcept\n    {\n        static_assert(kPrecision == 64, \"internal error\");\n\n        // Computes:\n        //  f = round((x.f * y.f) / 2^q)\n        //  e = x.e + y.e + q\n\n        // Emulate the 64-bit * 64-bit multiplication:\n        //\n        // p = u * v\n        //   = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi)\n        //   = (u_lo v_lo         ) + 2^32 ((u_lo v_hi         ) + (u_hi v_lo         )) + 2^64 (u_hi v_hi         )\n        //   = (p0                ) + 2^32 ((p1                ) + (p2                )) + 2^64 (p3                )\n        //   = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3                )\n        //   = (p0_lo             ) + 2^32 (p0_hi + p1_lo + p2_lo                      ) + 2^64 (p1_hi + p2_hi + p3)\n        //   = (p0_lo             ) + 2^32 (Q                                          ) + 2^64 (H                 )\n        //   = (p0_lo             ) + 2^32 (Q_lo + 2^32 Q_hi                           ) + 2^64 (H                 )\n        //\n        // (Since Q might be larger than 2^32 - 1)\n        //\n        //   = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H)\n        //\n        // (Q_hi + H does not overflow a 64-bit int)\n        //\n        //   = p_lo + 2^64 p_hi\n\n        const std::uint64_t u_lo = x.f & 0xFFFFFFFFu;\n        const std::uint64_t u_hi = x.f >> 32u;\n        const std::uint64_t v_lo = y.f & 0xFFFFFFFFu;\n        const std::uint64_t v_hi = y.f >> 32u;\n\n        const std::uint64_t p0 = u_lo * v_lo;\n        const std::uint64_t p1 = u_lo * v_hi;\n        const std::uint64_t p2 = u_hi * v_lo;\n        const std::uint64_t p3 = u_hi * v_hi;\n\n        const std::uint64_t p0_hi = p0 >> 32u;\n        const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu;\n        const std::uint64_t p1_hi = p1 >> 32u;\n        const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu;\n        const std::uint64_t p2_hi = p2 >> 32u;\n\n        std::uint64_t Q = p0_hi + p1_lo + p2_lo;\n\n        // The full product might now be computed as\n        //\n        // p_hi = p3 + p2_hi + p1_hi + (Q >> 32)\n        // p_lo = p0_lo + (Q << 32)\n        //\n        // But in this particular case here, the full p_lo is not required.\n        // Effectively we only need to add the highest bit in p_lo to p_hi (and\n        // Q_hi + 1 does not overflow).\n\n        Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up\n\n        const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u);\n\n        return {h, x.e + y.e + 64};\n    }\n\n    /*!\n    @brief normalize x such that the significand is >= 2^(q-1)\n    @pre x.f != 0\n    */\n    static diyfp normalize(diyfp x) noexcept\n    {\n        JSON_ASSERT(x.f != 0);\n\n        while ((x.f >> 63u) == 0)\n        {\n            x.f <<= 1u;\n            x.e--;\n        }\n\n        return x;\n    }\n\n    /*!\n    @brief normalize x such that the result has the exponent E\n    @pre e >= x.e and the upper e - x.e bits of x.f must be zero.\n    */\n    static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept\n    {\n        const int delta = x.e - target_exponent;\n\n        JSON_ASSERT(delta >= 0);\n        JSON_ASSERT(((x.f << delta) >> delta) == x.f);\n\n        return {x.f << delta, target_exponent};\n    }\n};\n\nstruct boundaries\n{\n    diyfp w;\n    diyfp minus;\n    diyfp plus;\n};\n\n/*!\nCompute the (normalized) diyfp representing the input number 'value' and its\nboundaries.\n\n@pre value must be finite and positive\n*/\ntemplate<typename FloatType>\nboundaries compute_boundaries(FloatType value)\n{\n    JSON_ASSERT(std::isfinite(value));\n    JSON_ASSERT(value > 0);\n\n    // Convert the IEEE representation into a diyfp.\n    //\n    // If v is denormal:\n    //      value = 0.F * 2^(1 - bias) = (          F) * 2^(1 - bias - (p-1))\n    // If v is normalized:\n    //      value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1))\n\n    static_assert(std::numeric_limits<FloatType>::is_iec559,\n                  \"internal error: dtoa_short requires an IEEE-754 floating-point implementation\");\n\n    constexpr int      kPrecision = std::numeric_limits<FloatType>::digits; // = p (includes the hidden bit)\n    constexpr int      kBias      = std::numeric_limits<FloatType>::max_exponent - 1 + (kPrecision - 1);\n    constexpr int      kMinExp    = 1 - kBias;\n    constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1)\n\n    using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;\n\n    const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value));\n    const std::uint64_t E = bits >> (kPrecision - 1);\n    const std::uint64_t F = bits & (kHiddenBit - 1);\n\n    const bool is_denormal = E == 0;\n    const diyfp v = is_denormal\n                    ? diyfp(F, kMinExp)\n                    : diyfp(F + kHiddenBit, static_cast<int>(E) - kBias);\n\n    // Compute the boundaries m- and m+ of the floating-point value\n    // v = f * 2^e.\n    //\n    // Determine v- and v+, the floating-point predecessor and successor if v,\n    // respectively.\n    //\n    //      v- = v - 2^e        if f != 2^(p-1) or e == e_min                (A)\n    //         = v - 2^(e-1)    if f == 2^(p-1) and e > e_min                (B)\n    //\n    //      v+ = v + 2^e\n    //\n    // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_\n    // between m- and m+ round to v, regardless of how the input rounding\n    // algorithm breaks ties.\n    //\n    //      ---+-------------+-------------+-------------+-------------+---  (A)\n    //         v-            m-            v             m+            v+\n    //\n    //      -----------------+------+------+-------------+-------------+---  (B)\n    //                       v-     m-     v             m+            v+\n\n    const bool lower_boundary_is_closer = F == 0 && E > 1;\n    const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1);\n    const diyfp m_minus = lower_boundary_is_closer\n                          ? diyfp(4 * v.f - 1, v.e - 2)  // (B)\n                          : diyfp(2 * v.f - 1, v.e - 1); // (A)\n\n    // Determine the normalized w+ = m+.\n    const diyfp w_plus = diyfp::normalize(m_plus);\n\n    // Determine w- = m- such that e_(w-) = e_(w+).\n    const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e);\n\n    return {diyfp::normalize(v), w_minus, w_plus};\n}\n\n// Given normalized diyfp w, Grisu needs to find a (normalized) cached\n// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies\n// within a certain range [alpha, gamma] (Definition 3.2 from [1])\n//\n//      alpha <= e = e_c + e_w + q <= gamma\n//\n// or\n//\n//      f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q\n//                          <= f_c * f_w * 2^gamma\n//\n// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies\n//\n//      2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma\n//\n// or\n//\n//      2^(q - 2 + alpha) <= c * w < 2^(q + gamma)\n//\n// The choice of (alpha,gamma) determines the size of the table and the form of\n// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well\n// in practice:\n//\n// The idea is to cut the number c * w = f * 2^e into two parts, which can be\n// processed independently: An integral part p1, and a fractional part p2:\n//\n//      f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e\n//              = (f div 2^-e) + (f mod 2^-e) * 2^e\n//              = p1 + p2 * 2^e\n//\n// The conversion of p1 into decimal form requires a series of divisions and\n// modulos by (a power of) 10. These operations are faster for 32-bit than for\n// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be\n// achieved by choosing\n//\n//      -e >= 32   or   e <= -32 := gamma\n//\n// In order to convert the fractional part\n//\n//      p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ...\n//\n// into decimal form, the fraction is repeatedly multiplied by 10 and the digits\n// d[-i] are extracted in order:\n//\n//      (10 * p2) div 2^-e = d[-1]\n//      (10 * p2) mod 2^-e = d[-2] / 10^1 + ...\n//\n// The multiplication by 10 must not overflow. It is sufficient to choose\n//\n//      10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64.\n//\n// Since p2 = f mod 2^-e < 2^-e,\n//\n//      -e <= 60   or   e >= -60 := alpha\n\nconstexpr int kAlpha = -60;\nconstexpr int kGamma = -32;\n\nstruct cached_power // c = f * 2^e ~= 10^k\n{\n    std::uint64_t f;\n    int e;\n    int k;\n};\n\n/*!\nFor a normalized diyfp w = f * 2^e, this function returns a (normalized) cached\npower-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c\nsatisfies (Definition 3.2 from [1])\n\n     alpha <= e_c + e + q <= gamma.\n*/\ninline cached_power get_cached_power_for_binary_exponent(int e)\n{\n    // Now\n    //\n    //      alpha <= e_c + e + q <= gamma                                    (1)\n    //      ==> f_c * 2^alpha <= c * 2^e * 2^q\n    //\n    // and since the c's are normalized, 2^(q-1) <= f_c,\n    //\n    //      ==> 2^(q - 1 + alpha) <= c * 2^(e + q)\n    //      ==> 2^(alpha - e - 1) <= c\n    //\n    // If c were an exact power of ten, i.e. c = 10^k, one may determine k as\n    //\n    //      k = ceil( log_10( 2^(alpha - e - 1) ) )\n    //        = ceil( (alpha - e - 1) * log_10(2) )\n    //\n    // From the paper:\n    // \"In theory the result of the procedure could be wrong since c is rounded,\n    //  and the computation itself is approximated [...]. In practice, however,\n    //  this simple function is sufficient.\"\n    //\n    // For IEEE double precision floating-point numbers converted into\n    // normalized diyfp's w = f * 2^e, with q = 64,\n    //\n    //      e >= -1022      (min IEEE exponent)\n    //           -52        (p - 1)\n    //           -52        (p - 1, possibly normalize denormal IEEE numbers)\n    //           -11        (normalize the diyfp)\n    //         = -1137\n    //\n    // and\n    //\n    //      e <= +1023      (max IEEE exponent)\n    //           -52        (p - 1)\n    //           -11        (normalize the diyfp)\n    //         = 960\n    //\n    // This binary exponent range [-1137,960] results in a decimal exponent\n    // range [-307,324]. One does not need to store a cached power for each\n    // k in this range. For each such k it suffices to find a cached power\n    // such that the exponent of the product lies in [alpha,gamma].\n    // This implies that the difference of the decimal exponents of adjacent\n    // table entries must be less than or equal to\n    //\n    //      floor( (gamma - alpha) * log_10(2) ) = 8.\n    //\n    // (A smaller distance gamma-alpha would require a larger table.)\n\n    // NB:\n    // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34.\n\n    constexpr int kCachedPowersMinDecExp = -300;\n    constexpr int kCachedPowersDecStep = 8;\n\n    static constexpr std::array<cached_power, 79> kCachedPowers =\n    {\n        {\n            { 0xAB70FE17C79AC6CA, -1060, -300 },\n            { 0xFF77B1FCBEBCDC4F, -1034, -292 },\n            { 0xBE5691EF416BD60C, -1007, -284 },\n            { 0x8DD01FAD907FFC3C,  -980, -276 },\n            { 0xD3515C2831559A83,  -954, -268 },\n            { 0x9D71AC8FADA6C9B5,  -927, -260 },\n            { 0xEA9C227723EE8BCB,  -901, -252 },\n            { 0xAECC49914078536D,  -874, -244 },\n            { 0x823C12795DB6CE57,  -847, -236 },\n            { 0xC21094364DFB5637,  -821, -228 },\n            { 0x9096EA6F3848984F,  -794, -220 },\n            { 0xD77485CB25823AC7,  -768, -212 },\n            { 0xA086CFCD97BF97F4,  -741, -204 },\n            { 0xEF340A98172AACE5,  -715, -196 },\n            { 0xB23867FB2A35B28E,  -688, -188 },\n            { 0x84C8D4DFD2C63F3B,  -661, -180 },\n            { 0xC5DD44271AD3CDBA,  -635, -172 },\n            { 0x936B9FCEBB25C996,  -608, -164 },\n            { 0xDBAC6C247D62A584,  -582, -156 },\n            { 0xA3AB66580D5FDAF6,  -555, -148 },\n            { 0xF3E2F893DEC3F126,  -529, -140 },\n            { 0xB5B5ADA8AAFF80B8,  -502, -132 },\n            { 0x87625F056C7C4A8B,  -475, -124 },\n            { 0xC9BCFF6034C13053,  -449, -116 },\n            { 0x964E858C91BA2655,  -422, -108 },\n            { 0xDFF9772470297EBD,  -396, -100 },\n            { 0xA6DFBD9FB8E5B88F,  -369,  -92 },\n            { 0xF8A95FCF88747D94,  -343,  -84 },\n            { 0xB94470938FA89BCF,  -316,  -76 },\n            { 0x8A08F0F8BF0F156B,  -289,  -68 },\n            { 0xCDB02555653131B6,  -263,  -60 },\n            { 0x993FE2C6D07B7FAC,  -236,  -52 },\n            { 0xE45C10C42A2B3B06,  -210,  -44 },\n            { 0xAA242499697392D3,  -183,  -36 },\n            { 0xFD87B5F28300CA0E,  -157,  -28 },\n            { 0xBCE5086492111AEB,  -130,  -20 },\n            { 0x8CBCCC096F5088CC,  -103,  -12 },\n            { 0xD1B71758E219652C,   -77,   -4 },\n            { 0x9C40000000000000,   -50,    4 },\n            { 0xE8D4A51000000000,   -24,   12 },\n            { 0xAD78EBC5AC620000,     3,   20 },\n            { 0x813F3978F8940984,    30,   28 },\n            { 0xC097CE7BC90715B3,    56,   36 },\n            { 0x8F7E32CE7BEA5C70,    83,   44 },\n            { 0xD5D238A4ABE98068,   109,   52 },\n            { 0x9F4F2726179A2245,   136,   60 },\n            { 0xED63A231D4C4FB27,   162,   68 },\n            { 0xB0DE65388CC8ADA8,   189,   76 },\n            { 0x83C7088E1AAB65DB,   216,   84 },\n            { 0xC45D1DF942711D9A,   242,   92 },\n            { 0x924D692CA61BE758,   269,  100 },\n            { 0xDA01EE641A708DEA,   295,  108 },\n            { 0xA26DA3999AEF774A,   322,  116 },\n            { 0xF209787BB47D6B85,   348,  124 },\n            { 0xB454E4A179DD1877,   375,  132 },\n            { 0x865B86925B9BC5C2,   402,  140 },\n            { 0xC83553C5C8965D3D,   428,  148 },\n            { 0x952AB45CFA97A0B3,   455,  156 },\n            { 0xDE469FBD99A05FE3,   481,  164 },\n            { 0xA59BC234DB398C25,   508,  172 },\n            { 0xF6C69A72A3989F5C,   534,  180 },\n            { 0xB7DCBF5354E9BECE,   561,  188 },\n            { 0x88FCF317F22241E2,   588,  196 },\n            { 0xCC20CE9BD35C78A5,   614,  204 },\n            { 0x98165AF37B2153DF,   641,  212 },\n            { 0xE2A0B5DC971F303A,   667,  220 },\n            { 0xA8D9D1535CE3B396,   694,  228 },\n            { 0xFB9B7CD9A4A7443C,   720,  236 },\n            { 0xBB764C4CA7A44410,   747,  244 },\n            { 0x8BAB8EEFB6409C1A,   774,  252 },\n            { 0xD01FEF10A657842C,   800,  260 },\n            { 0x9B10A4E5E9913129,   827,  268 },\n            { 0xE7109BFBA19C0C9D,   853,  276 },\n            { 0xAC2820D9623BF429,   880,  284 },\n            { 0x80444B5E7AA7CF85,   907,  292 },\n            { 0xBF21E44003ACDD2D,   933,  300 },\n            { 0x8E679C2F5E44FF8F,   960,  308 },\n            { 0xD433179D9C8CB841,   986,  316 },\n            { 0x9E19DB92B4E31BA9,  1013,  324 },\n        }\n    };\n\n    // This computation gives exactly the same results for k as\n    //      k = ceil((kAlpha - e - 1) * 0.30102999566398114)\n    // for |e| <= 1500, but doesn't require floating-point operations.\n    // NB: log_10(2) ~= 78913 / 2^18\n    JSON_ASSERT(e >= -1500);\n    JSON_ASSERT(e <=  1500);\n    const int f = kAlpha - e - 1;\n    const int k = (f * 78913) / (1 << 18) + static_cast<int>(f > 0);\n\n    const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep;\n    JSON_ASSERT(index >= 0);\n    JSON_ASSERT(static_cast<std::size_t>(index) < kCachedPowers.size());\n\n    const cached_power cached = kCachedPowers[static_cast<std::size_t>(index)];\n    JSON_ASSERT(kAlpha <= cached.e + e + 64);\n    JSON_ASSERT(kGamma >= cached.e + e + 64);\n\n    return cached;\n}\n\n/*!\nFor n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k.\nFor n == 0, returns 1 and sets pow10 := 1.\n*/\ninline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10)\n{\n    // LCOV_EXCL_START\n    if (n >= 1000000000)\n    {\n        pow10 = 1000000000;\n        return 10;\n    }\n    // LCOV_EXCL_STOP\n    if (n >= 100000000)\n    {\n        pow10 = 100000000;\n        return  9;\n    }\n    if (n >= 10000000)\n    {\n        pow10 = 10000000;\n        return  8;\n    }\n    if (n >= 1000000)\n    {\n        pow10 = 1000000;\n        return  7;\n    }\n    if (n >= 100000)\n    {\n        pow10 = 100000;\n        return  6;\n    }\n    if (n >= 10000)\n    {\n        pow10 = 10000;\n        return  5;\n    }\n    if (n >= 1000)\n    {\n        pow10 = 1000;\n        return  4;\n    }\n    if (n >= 100)\n    {\n        pow10 = 100;\n        return  3;\n    }\n    if (n >= 10)\n    {\n        pow10 = 10;\n        return  2;\n    }\n\n    pow10 = 1;\n    return 1;\n}\n\ninline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta,\n                         std::uint64_t rest, std::uint64_t ten_k)\n{\n    JSON_ASSERT(len >= 1);\n    JSON_ASSERT(dist <= delta);\n    JSON_ASSERT(rest <= delta);\n    JSON_ASSERT(ten_k > 0);\n\n    //               <--------------------------- delta ---->\n    //                                  <---- dist --------->\n    // --------------[------------------+-------------------]--------------\n    //               M-                 w                   M+\n    //\n    //                                  ten_k\n    //                                <------>\n    //                                       <---- rest ---->\n    // --------------[------------------+----+--------------]--------------\n    //                                  w    V\n    //                                       = buf * 10^k\n    //\n    // ten_k represents a unit-in-the-last-place in the decimal representation\n    // stored in buf.\n    // Decrement buf by ten_k while this takes buf closer to w.\n\n    // The tests are written in this order to avoid overflow in unsigned\n    // integer arithmetic.\n\n    while (rest < dist\n            && delta - rest >= ten_k\n            && (rest + ten_k < dist || dist - rest > rest + ten_k - dist))\n    {\n        JSON_ASSERT(buf[len - 1] != '0');\n        buf[len - 1]--;\n        rest += ten_k;\n    }\n}\n\n/*!\nGenerates V = buffer * 10^decimal_exponent, such that M- <= V <= M+.\nM- and M+ must be normalized and share the same exponent -60 <= e <= -32.\n*/\ninline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent,\n                             diyfp M_minus, diyfp w, diyfp M_plus)\n{\n    static_assert(kAlpha >= -60, \"internal error\");\n    static_assert(kGamma <= -32, \"internal error\");\n\n    // Generates the digits (and the exponent) of a decimal floating-point\n    // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's\n    // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma.\n    //\n    //               <--------------------------- delta ---->\n    //                                  <---- dist --------->\n    // --------------[------------------+-------------------]--------------\n    //               M-                 w                   M+\n    //\n    // Grisu2 generates the digits of M+ from left to right and stops as soon as\n    // V is in [M-,M+].\n\n    JSON_ASSERT(M_plus.e >= kAlpha);\n    JSON_ASSERT(M_plus.e <= kGamma);\n\n    std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e)\n    std::uint64_t dist  = diyfp::sub(M_plus, w      ).f; // (significand of (M+ - w ), implicit exponent is e)\n\n    // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0):\n    //\n    //      M+ = f * 2^e\n    //         = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e\n    //         = ((p1        ) * 2^-e + (p2        )) * 2^e\n    //         = p1 + p2 * 2^e\n\n    const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e);\n\n    auto p1 = static_cast<std::uint32_t>(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.)\n    std::uint64_t p2 = M_plus.f & (one.f - 1);                    // p2 = f mod 2^-e\n\n    // 1)\n    //\n    // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0]\n\n    JSON_ASSERT(p1 > 0);\n\n    std::uint32_t pow10{};\n    const int k = find_largest_pow10(p1, pow10);\n\n    //      10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1)\n    //\n    //      p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1))\n    //         = (d[k-1]         ) * 10^(k-1) + (p1 mod 10^(k-1))\n    //\n    //      M+ = p1                                             + p2 * 2^e\n    //         = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1))          + p2 * 2^e\n    //         = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e\n    //         = d[k-1] * 10^(k-1) + (                         rest) * 2^e\n    //\n    // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0)\n    //\n    //      p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0]\n    //\n    // but stop as soon as\n    //\n    //      rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e\n\n    int n = k;\n    while (n > 0)\n    {\n        // Invariants:\n        //      M+ = buffer * 10^n + (p1 + p2 * 2^e)    (buffer = 0 for n = k)\n        //      pow10 = 10^(n-1) <= p1 < 10^n\n        //\n        const std::uint32_t d = p1 / pow10;  // d = p1 div 10^(n-1)\n        const std::uint32_t r = p1 % pow10;  // r = p1 mod 10^(n-1)\n        //\n        //      M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e\n        //         = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e)\n        //\n        JSON_ASSERT(d <= 9);\n        buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d\n        //\n        //      M+ = buffer * 10^(n-1) + (r + p2 * 2^e)\n        //\n        p1 = r;\n        n--;\n        //\n        //      M+ = buffer * 10^n + (p1 + p2 * 2^e)\n        //      pow10 = 10^n\n        //\n\n        // Now check if enough digits have been generated.\n        // Compute\n        //\n        //      p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e\n        //\n        // Note:\n        // Since rest and delta share the same exponent e, it suffices to\n        // compare the significands.\n        const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2;\n        if (rest <= delta)\n        {\n            // V = buffer * 10^n, with M- <= V <= M+.\n\n            decimal_exponent += n;\n\n            // We may now just stop. But instead look if the buffer could be\n            // decremented to bring V closer to w.\n            //\n            // pow10 = 10^n is now 1 ulp in the decimal representation V.\n            // The rounding procedure works with diyfp's with an implicit\n            // exponent of e.\n            //\n            //      10^n = (10^n * 2^-e) * 2^e = ulp * 2^e\n            //\n            const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e;\n            grisu2_round(buffer, length, dist, delta, rest, ten_n);\n\n            return;\n        }\n\n        pow10 /= 10;\n        //\n        //      pow10 = 10^(n-1) <= p1 < 10^n\n        // Invariants restored.\n    }\n\n    // 2)\n    //\n    // The digits of the integral part have been generated:\n    //\n    //      M+ = d[k-1]...d[1]d[0] + p2 * 2^e\n    //         = buffer            + p2 * 2^e\n    //\n    // Now generate the digits of the fractional part p2 * 2^e.\n    //\n    // Note:\n    // No decimal point is generated: the exponent is adjusted instead.\n    //\n    // p2 actually represents the fraction\n    //\n    //      p2 * 2^e\n    //          = p2 / 2^-e\n    //          = d[-1] / 10^1 + d[-2] / 10^2 + ...\n    //\n    // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...)\n    //\n    //      p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m\n    //                      + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...)\n    //\n    // using\n    //\n    //      10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e)\n    //                = (                   d) * 2^-e + (                   r)\n    //\n    // or\n    //      10^m * p2 * 2^e = d + r * 2^e\n    //\n    // i.e.\n    //\n    //      M+ = buffer + p2 * 2^e\n    //         = buffer + 10^-m * (d + r * 2^e)\n    //         = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e\n    //\n    // and stop as soon as 10^-m * r * 2^e <= delta * 2^e\n\n    JSON_ASSERT(p2 > delta);\n\n    int m = 0;\n    for (;;)\n    {\n        // Invariant:\n        //      M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e\n        //         = buffer * 10^-m + 10^-m * (p2                                 ) * 2^e\n        //         = buffer * 10^-m + 10^-m * (1/10 * (10 * p2)                   ) * 2^e\n        //         = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e\n        //\n        JSON_ASSERT(p2 <= (std::numeric_limits<std::uint64_t>::max)() / 10);\n        p2 *= 10;\n        const std::uint64_t d = p2 >> -one.e;     // d = (10 * p2) div 2^-e\n        const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e\n        //\n        //      M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e\n        //         = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e))\n        //         = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e\n        //\n        JSON_ASSERT(d <= 9);\n        buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d\n        //\n        //      M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e\n        //\n        p2 = r;\n        m++;\n        //\n        //      M+ = buffer * 10^-m + 10^-m * p2 * 2^e\n        // Invariant restored.\n\n        // Check if enough digits have been generated.\n        //\n        //      10^-m * p2 * 2^e <= delta * 2^e\n        //              p2 * 2^e <= 10^m * delta * 2^e\n        //                    p2 <= 10^m * delta\n        delta *= 10;\n        dist  *= 10;\n        if (p2 <= delta)\n        {\n            break;\n        }\n    }\n\n    // V = buffer * 10^-m, with M- <= V <= M+.\n\n    decimal_exponent -= m;\n\n    // 1 ulp in the decimal representation is now 10^-m.\n    // Since delta and dist are now scaled by 10^m, we need to do the\n    // same with ulp in order to keep the units in sync.\n    //\n    //      10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e\n    //\n    const std::uint64_t ten_m = one.f;\n    grisu2_round(buffer, length, dist, delta, p2, ten_m);\n\n    // By construction this algorithm generates the shortest possible decimal\n    // number (Loitsch, Theorem 6.2) which rounds back to w.\n    // For an input number of precision p, at least\n    //\n    //      N = 1 + ceil(p * log_10(2))\n    //\n    // decimal digits are sufficient to identify all binary floating-point\n    // numbers (Matula, \"In-and-Out conversions\").\n    // This implies that the algorithm does not produce more than N decimal\n    // digits.\n    //\n    //      N = 17 for p = 53 (IEEE double precision)\n    //      N = 9  for p = 24 (IEEE single precision)\n}\n\n/*!\nv = buf * 10^decimal_exponent\nlen is the length of the buffer (number of decimal digits)\nThe buffer must be large enough, i.e. >= max_digits10.\n*/\nJSON_HEDLEY_NON_NULL(1)\ninline void grisu2(char* buf, int& len, int& decimal_exponent,\n                   diyfp m_minus, diyfp v, diyfp m_plus)\n{\n    JSON_ASSERT(m_plus.e == m_minus.e);\n    JSON_ASSERT(m_plus.e == v.e);\n\n    //  --------(-----------------------+-----------------------)--------    (A)\n    //          m-                      v                       m+\n    //\n    //  --------------------(-----------+-----------------------)--------    (B)\n    //                      m-          v                       m+\n    //\n    // First scale v (and m- and m+) such that the exponent is in the range\n    // [alpha, gamma].\n\n    const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e);\n\n    const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k\n\n    // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma]\n    const diyfp w       = diyfp::mul(v,       c_minus_k);\n    const diyfp w_minus = diyfp::mul(m_minus, c_minus_k);\n    const diyfp w_plus  = diyfp::mul(m_plus,  c_minus_k);\n\n    //  ----(---+---)---------------(---+---)---------------(---+---)----\n    //          w-                      w                       w+\n    //          = c*m-                  = c*v                   = c*m+\n    //\n    // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and\n    // w+ are now off by a small amount.\n    // In fact:\n    //\n    //      w - v * 10^k < 1 ulp\n    //\n    // To account for this inaccuracy, add resp. subtract 1 ulp.\n    //\n    //  --------+---[---------------(---+---)---------------]---+--------\n    //          w-  M-                  w                   M+  w+\n    //\n    // Now any number in [M-, M+] (bounds included) will round to w when input,\n    // regardless of how the input rounding algorithm breaks ties.\n    //\n    // And digit_gen generates the shortest possible such number in [M-, M+].\n    // Note that this does not mean that Grisu2 always generates the shortest\n    // possible number in the interval (m-, m+).\n    const diyfp M_minus(w_minus.f + 1, w_minus.e);\n    const diyfp M_plus (w_plus.f  - 1, w_plus.e );\n\n    decimal_exponent = -cached.k; // = -(-k) = k\n\n    grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus);\n}\n\n/*!\nv = buf * 10^decimal_exponent\nlen is the length of the buffer (number of decimal digits)\nThe buffer must be large enough, i.e. >= max_digits10.\n*/\ntemplate<typename FloatType>\nJSON_HEDLEY_NON_NULL(1)\nvoid grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)\n{\n    static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3,\n                  \"internal error: not enough precision\");\n\n    JSON_ASSERT(std::isfinite(value));\n    JSON_ASSERT(value > 0);\n\n    // If the neighbors (and boundaries) of 'value' are always computed for double-precision\n    // numbers, all float's can be recovered using strtod (and strtof). However, the resulting\n    // decimal representations are not exactly \"short\".\n    //\n    // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars)\n    // says \"value is converted to a string as if by std::sprintf in the default (\"C\") locale\"\n    // and since sprintf promotes floats to doubles, I think this is exactly what 'std::to_chars'\n    // does.\n    // On the other hand, the documentation for 'std::to_chars' requires that \"parsing the\n    // representation using the corresponding std::from_chars function recovers value exactly\". That\n    // indicates that single precision floating-point numbers should be recovered using\n    // 'std::strtof'.\n    //\n    // NB: If the neighbors are computed for single-precision numbers, there is a single float\n    //     (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision\n    //     value is off by 1 ulp.\n#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if)\n    const boundaries w = compute_boundaries(static_cast<double>(value));\n#else\n    const boundaries w = compute_boundaries(value);\n#endif\n\n    grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);\n}\n\n/*!\n@brief appends a decimal representation of e to buf\n@return a pointer to the element following the exponent.\n@pre -1000 < e < 1000\n*/\nJSON_HEDLEY_NON_NULL(1)\nJSON_HEDLEY_RETURNS_NON_NULL\ninline char* append_exponent(char* buf, int e)\n{\n    JSON_ASSERT(e > -1000);\n    JSON_ASSERT(e <  1000);\n\n    if (e < 0)\n    {\n        e = -e;\n        *buf++ = '-';\n    }\n    else\n    {\n        *buf++ = '+';\n    }\n\n    auto k = static_cast<std::uint32_t>(e);\n    if (k < 10)\n    {\n        // Always print at least two digits in the exponent.\n        // This is for compatibility with printf(\"%g\").\n        *buf++ = '0';\n        *buf++ = static_cast<char>('0' + k);\n    }\n    else if (k < 100)\n    {\n        *buf++ = static_cast<char>('0' + k / 10);\n        k %= 10;\n        *buf++ = static_cast<char>('0' + k);\n    }\n    else\n    {\n        *buf++ = static_cast<char>('0' + k / 100);\n        k %= 100;\n        *buf++ = static_cast<char>('0' + k / 10);\n        k %= 10;\n        *buf++ = static_cast<char>('0' + k);\n    }\n\n    return buf;\n}\n\n/*!\n@brief prettify v = buf * 10^decimal_exponent\n\nIf v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point\nnotation. Otherwise it will be printed in exponential notation.\n\n@pre min_exp < 0\n@pre max_exp > 0\n*/\nJSON_HEDLEY_NON_NULL(1)\nJSON_HEDLEY_RETURNS_NON_NULL\ninline char* format_buffer(char* buf, int len, int decimal_exponent,\n                           int min_exp, int max_exp)\n{\n    JSON_ASSERT(min_exp < 0);\n    JSON_ASSERT(max_exp > 0);\n\n    const int k = len;\n    const int n = len + decimal_exponent;\n\n    // v = buf * 10^(n-k)\n    // k is the length of the buffer (number of decimal digits)\n    // n is the position of the decimal point relative to the start of the buffer.\n\n    if (k <= n && n <= max_exp)\n    {\n        // digits[000]\n        // len <= max_exp + 2\n\n        std::memset(buf + k, '0', static_cast<size_t>(n) - static_cast<size_t>(k));\n        // Make it look like a floating-point number (#362, #378)\n        buf[n + 0] = '.';\n        buf[n + 1] = '0';\n        return buf + (static_cast<size_t>(n) + 2);\n    }\n\n    if (0 < n && n <= max_exp)\n    {\n        // dig.its\n        // len <= max_digits10 + 1\n\n        JSON_ASSERT(k > n);\n\n        std::memmove(buf + (static_cast<size_t>(n) + 1), buf + n, static_cast<size_t>(k) - static_cast<size_t>(n));\n        buf[n] = '.';\n        return buf + (static_cast<size_t>(k) + 1U);\n    }\n\n    if (min_exp < n && n <= 0)\n    {\n        // 0.[000]digits\n        // len <= 2 + (-min_exp - 1) + max_digits10\n\n        std::memmove(buf + (2 + static_cast<size_t>(-n)), buf, static_cast<size_t>(k));\n        buf[0] = '0';\n        buf[1] = '.';\n        std::memset(buf + 2, '0', static_cast<size_t>(-n));\n        return buf + (2U + static_cast<size_t>(-n) + static_cast<size_t>(k));\n    }\n\n    if (k == 1)\n    {\n        // dE+123\n        // len <= 1 + 5\n\n        buf += 1;\n    }\n    else\n    {\n        // d.igitsE+123\n        // len <= max_digits10 + 1 + 5\n\n        std::memmove(buf + 2, buf + 1, static_cast<size_t>(k) - 1);\n        buf[1] = '.';\n        buf += 1 + static_cast<size_t>(k);\n    }\n\n    *buf++ = 'e';\n    return append_exponent(buf, n - 1);\n}\n\n}  // namespace dtoa_impl\n\n/*!\n@brief generates a decimal representation of the floating-point number value in [first, last).\n\nThe format of the resulting decimal representation is similar to printf's %g\nformat. Returns an iterator pointing past-the-end of the decimal representation.\n\n@note The input number must be finite, i.e. NaN's and Inf's are not supported.\n@note The buffer must be large enough.\n@note The result is NOT null-terminated.\n*/\ntemplate<typename FloatType>\nJSON_HEDLEY_NON_NULL(1, 2)\nJSON_HEDLEY_RETURNS_NON_NULL\nchar* to_chars(char* first, const char* last, FloatType value)\n{\n    static_cast<void>(last); // maybe unused - fix warning\n    JSON_ASSERT(std::isfinite(value));\n\n    // Use signbit(value) instead of (value < 0) since signbit works for -0.\n    if (std::signbit(value))\n    {\n        value = -value;\n        *first++ = '-';\n    }\n\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n    if (value == 0) // +-0\n    {\n        *first++ = '0';\n        // Make it look like a floating-point number (#362, #378)\n        *first++ = '.';\n        *first++ = '0';\n        return first;\n    }\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n\n    JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);\n\n    // Compute v = buffer * 10^decimal_exponent.\n    // The decimal digits are stored in the buffer, which needs to be interpreted\n    // as an unsigned decimal integer.\n    // len is the length of the buffer, i.e. the number of decimal digits.\n    int len = 0;\n    int decimal_exponent = 0;\n    dtoa_impl::grisu2(first, len, decimal_exponent, value);\n\n    JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10);\n\n    // Format the buffer like printf(\"%.*g\", prec, value)\n    constexpr int kMinExp = -4;\n    // Use digits10 here to increase compatibility with version 2.\n    constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;\n\n    JSON_ASSERT(last - first >= kMaxExp + 2);\n    JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);\n    JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);\n\n    return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/output/binary_writer.hpp>\n\n// #include <nlohmann/detail/output/output_adapters.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////////////\n// serialization //\n///////////////////\n\n/// how to treat decoding errors\nenum class error_handler_t\n{\n    strict,  ///< throw a type_error exception in case of invalid UTF-8\n    replace, ///< replace invalid UTF-8 sequences with U+FFFD\n    ignore   ///< ignore invalid UTF-8 sequences\n};\n\ntemplate<typename BasicJsonType>\nclass serializer\n{\n    using string_t = typename BasicJsonType::string_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using binary_char_t = typename BasicJsonType::binary_t::value_type;\n    static constexpr std::uint8_t UTF8_ACCEPT = 0;\n    static constexpr std::uint8_t UTF8_REJECT = 1;\n\n  public:\n    /*!\n    @param[in] s  output stream to serialize to\n    @param[in] ichar  indentation character to use\n    @param[in] error_handler_  how to react on decoding errors\n    */\n    serializer(output_adapter_t<char> s, const char ichar,\n               error_handler_t error_handler_ = error_handler_t::strict)\n        : o(std::move(s))\n        , loc(std::localeconv())\n        , thousands_sep(loc->thousands_sep == nullptr ? '\\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))\n        , decimal_point(loc->decimal_point == nullptr ? '\\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))\n        , indent_char(ichar)\n        , indent_string(512, indent_char)\n        , error_handler(error_handler_)\n    {}\n\n    // delete because of pointer members\n    serializer(const serializer&) = delete;\n    serializer& operator=(const serializer&) = delete;\n    serializer(serializer&&) = delete;\n    serializer& operator=(serializer&&) = delete;\n    ~serializer() = default;\n\n    /*!\n    @brief internal implementation of the serialization function\n\n    This function is called by the public member function dump and organizes\n    the serialization internally. The indentation level is propagated as\n    additional parameter. In case of arrays and objects, the function is\n    called recursively.\n\n    - strings and object keys are escaped using `escape_string()`\n    - integer numbers are converted implicitly via `operator<<`\n    - floating-point numbers are converted to a string using `\"%g\"` format\n    - binary values are serialized as objects containing the subtype and the\n      byte array\n\n    @param[in] val               value to serialize\n    @param[in] pretty_print      whether the output shall be pretty-printed\n    @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters\n    in the output are escaped with `\\uXXXX` sequences, and the result consists\n    of ASCII characters only.\n    @param[in] indent_step       the indent level\n    @param[in] current_indent    the current indent level (only used internally)\n    */\n    void dump(const BasicJsonType& val,\n              const bool pretty_print,\n              const bool ensure_ascii,\n              const unsigned int indent_step,\n              const unsigned int current_indent = 0)\n    {\n        switch (val.m_data.m_type)\n        {\n            case value_t::object:\n            {\n                if (val.m_data.m_value.object->empty())\n                {\n                    o->write_characters(\"{}\", 2);\n                    return;\n                }\n\n                if (pretty_print)\n                {\n                    o->write_characters(\"{\\n\", 2);\n\n                    // variable to hold indentation for recursive calls\n                    const auto new_indent = current_indent + indent_step;\n                    if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))\n                    {\n                        indent_string.resize(indent_string.size() * 2, ' ');\n                    }\n\n                    // first n-1 elements\n                    auto i = val.m_data.m_value.object->cbegin();\n                    for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)\n                    {\n                        o->write_characters(indent_string.c_str(), new_indent);\n                        o->write_character('\\\"');\n                        dump_escaped(i->first, ensure_ascii);\n                        o->write_characters(\"\\\": \", 3);\n                        dump(i->second, true, ensure_ascii, indent_step, new_indent);\n                        o->write_characters(\",\\n\", 2);\n                    }\n\n                    // last element\n                    JSON_ASSERT(i != val.m_data.m_value.object->cend());\n                    JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());\n                    o->write_characters(indent_string.c_str(), new_indent);\n                    o->write_character('\\\"');\n                    dump_escaped(i->first, ensure_ascii);\n                    o->write_characters(\"\\\": \", 3);\n                    dump(i->second, true, ensure_ascii, indent_step, new_indent);\n\n                    o->write_character('\\n');\n                    o->write_characters(indent_string.c_str(), current_indent);\n                    o->write_character('}');\n                }\n                else\n                {\n                    o->write_character('{');\n\n                    // first n-1 elements\n                    auto i = val.m_data.m_value.object->cbegin();\n                    for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)\n                    {\n                        o->write_character('\\\"');\n                        dump_escaped(i->first, ensure_ascii);\n                        o->write_characters(\"\\\":\", 2);\n                        dump(i->second, false, ensure_ascii, indent_step, current_indent);\n                        o->write_character(',');\n                    }\n\n                    // last element\n                    JSON_ASSERT(i != val.m_data.m_value.object->cend());\n                    JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());\n                    o->write_character('\\\"');\n                    dump_escaped(i->first, ensure_ascii);\n                    o->write_characters(\"\\\":\", 2);\n                    dump(i->second, false, ensure_ascii, indent_step, current_indent);\n\n                    o->write_character('}');\n                }\n\n                return;\n            }\n\n            case value_t::array:\n            {\n                if (val.m_data.m_value.array->empty())\n                {\n                    o->write_characters(\"[]\", 2);\n                    return;\n                }\n\n                if (pretty_print)\n                {\n                    o->write_characters(\"[\\n\", 2);\n\n                    // variable to hold indentation for recursive calls\n                    const auto new_indent = current_indent + indent_step;\n                    if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))\n                    {\n                        indent_string.resize(indent_string.size() * 2, ' ');\n                    }\n\n                    // first n-1 elements\n                    for (auto i = val.m_data.m_value.array->cbegin();\n                            i != val.m_data.m_value.array->cend() - 1; ++i)\n                    {\n                        o->write_characters(indent_string.c_str(), new_indent);\n                        dump(*i, true, ensure_ascii, indent_step, new_indent);\n                        o->write_characters(\",\\n\", 2);\n                    }\n\n                    // last element\n                    JSON_ASSERT(!val.m_data.m_value.array->empty());\n                    o->write_characters(indent_string.c_str(), new_indent);\n                    dump(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);\n\n                    o->write_character('\\n');\n                    o->write_characters(indent_string.c_str(), current_indent);\n                    o->write_character(']');\n                }\n                else\n                {\n                    o->write_character('[');\n\n                    // first n-1 elements\n                    for (auto i = val.m_data.m_value.array->cbegin();\n                            i != val.m_data.m_value.array->cend() - 1; ++i)\n                    {\n                        dump(*i, false, ensure_ascii, indent_step, current_indent);\n                        o->write_character(',');\n                    }\n\n                    // last element\n                    JSON_ASSERT(!val.m_data.m_value.array->empty());\n                    dump(val.m_data.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);\n\n                    o->write_character(']');\n                }\n\n                return;\n            }\n\n            case value_t::string:\n            {\n                o->write_character('\\\"');\n                dump_escaped(*val.m_data.m_value.string, ensure_ascii);\n                o->write_character('\\\"');\n                return;\n            }\n\n            case value_t::binary:\n            {\n                if (pretty_print)\n                {\n                    o->write_characters(\"{\\n\", 2);\n\n                    // variable to hold indentation for recursive calls\n                    const auto new_indent = current_indent + indent_step;\n                    if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))\n                    {\n                        indent_string.resize(indent_string.size() * 2, ' ');\n                    }\n\n                    o->write_characters(indent_string.c_str(), new_indent);\n\n                    o->write_characters(\"\\\"bytes\\\": [\", 10);\n\n                    if (!val.m_data.m_value.binary->empty())\n                    {\n                        for (auto i = val.m_data.m_value.binary->cbegin();\n                                i != val.m_data.m_value.binary->cend() - 1; ++i)\n                        {\n                            dump_integer(*i);\n                            o->write_characters(\", \", 2);\n                        }\n                        dump_integer(val.m_data.m_value.binary->back());\n                    }\n\n                    o->write_characters(\"],\\n\", 3);\n                    o->write_characters(indent_string.c_str(), new_indent);\n\n                    o->write_characters(\"\\\"subtype\\\": \", 11);\n                    if (val.m_data.m_value.binary->has_subtype())\n                    {\n                        dump_integer(val.m_data.m_value.binary->subtype());\n                    }\n                    else\n                    {\n                        o->write_characters(\"null\", 4);\n                    }\n                    o->write_character('\\n');\n                    o->write_characters(indent_string.c_str(), current_indent);\n                    o->write_character('}');\n                }\n                else\n                {\n                    o->write_characters(\"{\\\"bytes\\\":[\", 10);\n\n                    if (!val.m_data.m_value.binary->empty())\n                    {\n                        for (auto i = val.m_data.m_value.binary->cbegin();\n                                i != val.m_data.m_value.binary->cend() - 1; ++i)\n                        {\n                            dump_integer(*i);\n                            o->write_character(',');\n                        }\n                        dump_integer(val.m_data.m_value.binary->back());\n                    }\n\n                    o->write_characters(\"],\\\"subtype\\\":\", 12);\n                    if (val.m_data.m_value.binary->has_subtype())\n                    {\n                        dump_integer(val.m_data.m_value.binary->subtype());\n                        o->write_character('}');\n                    }\n                    else\n                    {\n                        o->write_characters(\"null}\", 5);\n                    }\n                }\n                return;\n            }\n\n            case value_t::boolean:\n            {\n                if (val.m_data.m_value.boolean)\n                {\n                    o->write_characters(\"true\", 4);\n                }\n                else\n                {\n                    o->write_characters(\"false\", 5);\n                }\n                return;\n            }\n\n            case value_t::number_integer:\n            {\n                dump_integer(val.m_data.m_value.number_integer);\n                return;\n            }\n\n            case value_t::number_unsigned:\n            {\n                dump_integer(val.m_data.m_value.number_unsigned);\n                return;\n            }\n\n            case value_t::number_float:\n            {\n                dump_float(val.m_data.m_value.number_float);\n                return;\n            }\n\n            case value_t::discarded:\n            {\n                o->write_characters(\"<discarded>\", 11);\n                return;\n            }\n\n            case value_t::null:\n            {\n                o->write_characters(\"null\", 4);\n                return;\n            }\n\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /*!\n    @brief dump escaped string\n\n    Escape a string by replacing certain special characters by a sequence of an\n    escape character (backslash) and another character and other control\n    characters by a sequence of \"\\u\" followed by a four-digit hex\n    representation. The escaped string is written to output stream @a o.\n\n    @param[in] s  the string to escape\n    @param[in] ensure_ascii  whether to escape non-ASCII characters with\n                             \\uXXXX sequences\n\n    @complexity Linear in the length of string @a s.\n    */\n    void dump_escaped(const string_t& s, const bool ensure_ascii)\n    {\n        std::uint32_t codepoint{};\n        std::uint8_t state = UTF8_ACCEPT;\n        std::size_t bytes = 0;  // number of bytes written to string_buffer\n\n        // number of bytes written at the point of the last valid byte\n        std::size_t bytes_after_last_accept = 0;\n        std::size_t undumped_chars = 0;\n\n        for (std::size_t i = 0; i < s.size(); ++i)\n        {\n            const auto byte = static_cast<std::uint8_t>(s[i]);\n\n            switch (decode(state, codepoint, byte))\n            {\n                case UTF8_ACCEPT:  // decode found a new code point\n                {\n                    switch (codepoint)\n                    {\n                        case 0x08: // backspace\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 'b';\n                            break;\n                        }\n\n                        case 0x09: // horizontal tab\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 't';\n                            break;\n                        }\n\n                        case 0x0A: // newline\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 'n';\n                            break;\n                        }\n\n                        case 0x0C: // formfeed\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 'f';\n                            break;\n                        }\n\n                        case 0x0D: // carriage return\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 'r';\n                            break;\n                        }\n\n                        case 0x22: // quotation mark\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = '\\\"';\n                            break;\n                        }\n\n                        case 0x5C: // reverse solidus\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = '\\\\';\n                            break;\n                        }\n\n                        default:\n                        {\n                            // escape control characters (0x00..0x1F) or, if\n                            // ensure_ascii parameter is used, non-ASCII characters\n                            if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F)))\n                            {\n                                if (codepoint <= 0xFFFF)\n                                {\n                                    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n                                    static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, \"\\\\u%04x\",\n                                                                      static_cast<std::uint16_t>(codepoint)));\n                                    bytes += 6;\n                                }\n                                else\n                                {\n                                    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n                                    static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, \"\\\\u%04x\\\\u%04x\",\n                                                                      static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),\n                                                                      static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));\n                                    bytes += 12;\n                                }\n                            }\n                            else\n                            {\n                                // copy byte to buffer (all previous bytes\n                                // been copied have in default case above)\n                                string_buffer[bytes++] = s[i];\n                            }\n                            break;\n                        }\n                    }\n\n                    // write buffer and reset index; there must be 13 bytes\n                    // left, as this is the maximal number of bytes to be\n                    // written (\"\\uxxxx\\uxxxx\\0\") for one code point\n                    if (string_buffer.size() - bytes < 13)\n                    {\n                        o->write_characters(string_buffer.data(), bytes);\n                        bytes = 0;\n                    }\n\n                    // remember the byte position of this accept\n                    bytes_after_last_accept = bytes;\n                    undumped_chars = 0;\n                    break;\n                }\n\n                case UTF8_REJECT:  // decode found invalid UTF-8 byte\n                {\n                    switch (error_handler)\n                    {\n                        case error_handler_t::strict:\n                        {\n                            JSON_THROW(type_error::create(316, concat(\"invalid UTF-8 byte at index \", std::to_string(i), \": 0x\", hex_bytes(byte | 0)), nullptr));\n                        }\n\n                        case error_handler_t::ignore:\n                        case error_handler_t::replace:\n                        {\n                            // in case we saw this character the first time, we\n                            // would like to read it again, because the byte\n                            // may be OK for itself, but just not OK for the\n                            // previous sequence\n                            if (undumped_chars > 0)\n                            {\n                                --i;\n                            }\n\n                            // reset length buffer to the last accepted index;\n                            // thus removing/ignoring the invalid characters\n                            bytes = bytes_after_last_accept;\n\n                            if (error_handler == error_handler_t::replace)\n                            {\n                                // add a replacement character\n                                if (ensure_ascii)\n                                {\n                                    string_buffer[bytes++] = '\\\\';\n                                    string_buffer[bytes++] = 'u';\n                                    string_buffer[bytes++] = 'f';\n                                    string_buffer[bytes++] = 'f';\n                                    string_buffer[bytes++] = 'f';\n                                    string_buffer[bytes++] = 'd';\n                                }\n                                else\n                                {\n                                    string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\\xEF');\n                                    string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\\xBF');\n                                    string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\\xBD');\n                                }\n\n                                // write buffer and reset index; there must be 13 bytes\n                                // left, as this is the maximal number of bytes to be\n                                // written (\"\\uxxxx\\uxxxx\\0\") for one code point\n                                if (string_buffer.size() - bytes < 13)\n                                {\n                                    o->write_characters(string_buffer.data(), bytes);\n                                    bytes = 0;\n                                }\n\n                                bytes_after_last_accept = bytes;\n                            }\n\n                            undumped_chars = 0;\n\n                            // continue processing the string\n                            state = UTF8_ACCEPT;\n                            break;\n                        }\n\n                        default:            // LCOV_EXCL_LINE\n                            JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n                    }\n                    break;\n                }\n\n                default:  // decode found yet incomplete multi-byte code point\n                {\n                    if (!ensure_ascii)\n                    {\n                        // code point will not be escaped - copy byte to buffer\n                        string_buffer[bytes++] = s[i];\n                    }\n                    ++undumped_chars;\n                    break;\n                }\n            }\n        }\n\n        // we finished processing the string\n        if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT))\n        {\n            // write buffer\n            if (bytes > 0)\n            {\n                o->write_characters(string_buffer.data(), bytes);\n            }\n        }\n        else\n        {\n            // we finish reading, but do not accept: string was incomplete\n            switch (error_handler)\n            {\n                case error_handler_t::strict:\n                {\n                    JSON_THROW(type_error::create(316, concat(\"incomplete UTF-8 string; last byte: 0x\", hex_bytes(static_cast<std::uint8_t>(s.back() | 0))), nullptr));\n                }\n\n                case error_handler_t::ignore:\n                {\n                    // write all accepted bytes\n                    o->write_characters(string_buffer.data(), bytes_after_last_accept);\n                    break;\n                }\n\n                case error_handler_t::replace:\n                {\n                    // write all accepted bytes\n                    o->write_characters(string_buffer.data(), bytes_after_last_accept);\n                    // add a replacement character\n                    if (ensure_ascii)\n                    {\n                        o->write_characters(\"\\\\ufffd\", 6);\n                    }\n                    else\n                    {\n                        o->write_characters(\"\\xEF\\xBF\\xBD\", 3);\n                    }\n                    break;\n                }\n\n                default:            // LCOV_EXCL_LINE\n                    JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n            }\n        }\n    }\n\n  private:\n    /*!\n    @brief count digits\n\n    Count the number of decimal (base 10) digits for an input unsigned integer.\n\n    @param[in] x  unsigned integer number to count its digits\n    @return    number of decimal digits\n    */\n    inline unsigned int count_digits(number_unsigned_t x) noexcept\n    {\n        unsigned int n_digits = 1;\n        for (;;)\n        {\n            if (x < 10)\n            {\n                return n_digits;\n            }\n            if (x < 100)\n            {\n                return n_digits + 1;\n            }\n            if (x < 1000)\n            {\n                return n_digits + 2;\n            }\n            if (x < 10000)\n            {\n                return n_digits + 3;\n            }\n            x = x / 10000u;\n            n_digits += 4;\n        }\n    }\n\n    /*!\n     * @brief convert a byte to a uppercase hex representation\n     * @param[in] byte byte to represent\n     * @return representation (\"00\"..\"FF\")\n     */\n    static std::string hex_bytes(std::uint8_t byte)\n    {\n        std::string result = \"FF\";\n        constexpr const char* nibble_to_hex = \"0123456789ABCDEF\";\n        result[0] = nibble_to_hex[byte / 16];\n        result[1] = nibble_to_hex[byte % 16];\n        return result;\n    }\n\n    // templates to avoid warnings about useless casts\n    template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>\n    bool is_negative_number(NumberType x)\n    {\n        return x < 0;\n    }\n\n    template < typename NumberType, enable_if_t <std::is_unsigned<NumberType>::value, int > = 0 >\n    bool is_negative_number(NumberType /*unused*/)\n    {\n        return false;\n    }\n\n    /*!\n    @brief dump an integer\n\n    Dump a given integer to output stream @a o. Works internally with\n    @a number_buffer.\n\n    @param[in] x  integer number (signed or unsigned) to dump\n    @tparam NumberType either @a number_integer_t or @a number_unsigned_t\n    */\n    template < typename NumberType, detail::enable_if_t <\n                   std::is_integral<NumberType>::value ||\n                   std::is_same<NumberType, number_unsigned_t>::value ||\n                   std::is_same<NumberType, number_integer_t>::value ||\n                   std::is_same<NumberType, binary_char_t>::value,\n                   int > = 0 >\n    void dump_integer(NumberType x)\n    {\n        static constexpr std::array<std::array<char, 2>, 100> digits_to_99\n        {\n            {\n                {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}},\n                {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}},\n                {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}},\n                {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}},\n                {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}},\n                {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}},\n                {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}},\n                {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}},\n                {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}},\n                {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}},\n            }\n        };\n\n        // special case for \"0\"\n        if (x == 0)\n        {\n            o->write_character('0');\n            return;\n        }\n\n        // use a pointer to fill the buffer\n        auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n\n        number_unsigned_t abs_value;\n\n        unsigned int n_chars{};\n\n        if (is_negative_number(x))\n        {\n            *buffer_ptr = '-';\n            abs_value = remove_sign(static_cast<number_integer_t>(x));\n\n            // account one more byte for the minus sign\n            n_chars = 1 + count_digits(abs_value);\n        }\n        else\n        {\n            abs_value = static_cast<number_unsigned_t>(x);\n            n_chars = count_digits(abs_value);\n        }\n\n        // spare 1 byte for '\\0'\n        JSON_ASSERT(n_chars < number_buffer.size() - 1);\n\n        // jump to the end to generate the string from backward,\n        // so we later avoid reversing the result\n        buffer_ptr += n_chars;\n\n        // Fast int2ascii implementation inspired by \"Fastware\" talk by Andrei Alexandrescu\n        // See: https://www.youtube.com/watch?v=o4-CwDo2zpg\n        while (abs_value >= 100)\n        {\n            const auto digits_index = static_cast<unsigned>((abs_value % 100));\n            abs_value /= 100;\n            *(--buffer_ptr) = digits_to_99[digits_index][1];\n            *(--buffer_ptr) = digits_to_99[digits_index][0];\n        }\n\n        if (abs_value >= 10)\n        {\n            const auto digits_index = static_cast<unsigned>(abs_value);\n            *(--buffer_ptr) = digits_to_99[digits_index][1];\n            *(--buffer_ptr) = digits_to_99[digits_index][0];\n        }\n        else\n        {\n            *(--buffer_ptr) = static_cast<char>('0' + abs_value);\n        }\n\n        o->write_characters(number_buffer.data(), n_chars);\n    }\n\n    /*!\n    @brief dump a floating-point number\n\n    Dump a given floating-point number to output stream @a o. Works internally\n    with @a number_buffer.\n\n    @param[in] x  floating-point number to dump\n    */\n    void dump_float(number_float_t x)\n    {\n        // NaN / inf\n        if (!std::isfinite(x))\n        {\n            o->write_characters(\"null\", 4);\n            return;\n        }\n\n        // If number_float_t is an IEEE-754 single or double precision number,\n        // use the Grisu2 algorithm to produce short numbers which are\n        // guaranteed to round-trip, using strtof and strtod, resp.\n        //\n        // NB: The test below works if <long double> == <double>.\n        static constexpr bool is_ieee_single_or_double\n            = (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128) ||\n              (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024);\n\n        dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());\n    }\n\n    void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)\n    {\n        auto* begin = number_buffer.data();\n        auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);\n\n        o->write_characters(begin, static_cast<size_t>(end - begin));\n    }\n\n    void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)\n    {\n        // get number of digits for a float -> text -> float round-trip\n        static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;\n\n        // the actual conversion\n        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n        std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), \"%.*g\", d, x);\n\n        // negative value indicates an error\n        JSON_ASSERT(len > 0);\n        // check if buffer was large enough\n        JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());\n\n        // erase thousands separator\n        if (thousands_sep != '\\0')\n        {\n            // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081\n            const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);\n            std::fill(end, number_buffer.end(), '\\0');\n            JSON_ASSERT((end - number_buffer.begin()) <= len);\n            len = (end - number_buffer.begin());\n        }\n\n        // convert decimal point to '.'\n        if (decimal_point != '\\0' && decimal_point != '.')\n        {\n            // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081\n            const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);\n            if (dec_pos != number_buffer.end())\n            {\n                *dec_pos = '.';\n            }\n        }\n\n        o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));\n\n        // determine if we need to append \".0\"\n        const bool value_is_int_like =\n            std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,\n                         [](char c)\n        {\n            return c == '.' || c == 'e';\n        });\n\n        if (value_is_int_like)\n        {\n            o->write_characters(\".0\", 2);\n        }\n    }\n\n    /*!\n    @brief check whether a string is UTF-8 encoded\n\n    The function checks each byte of a string whether it is UTF-8 encoded. The\n    result of the check is stored in the @a state parameter. The function must\n    be called initially with state 0 (accept). State 1 means the string must\n    be rejected, because the current byte is not allowed. If the string is\n    completely processed, but the state is non-zero, the string ended\n    prematurely; that is, the last byte indicated more bytes should have\n    followed.\n\n    @param[in,out] state  the state of the decoding\n    @param[in,out] codep  codepoint (valid only if resulting state is UTF8_ACCEPT)\n    @param[in] byte       next byte to decode\n    @return               new state\n\n    @note The function has been edited: a std::array is used.\n\n    @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>\n    @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/\n    */\n    static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept\n    {\n        static const std::array<std::uint8_t, 400> utf8d =\n        {\n            {\n                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F\n                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F\n                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F\n                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F\n                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F\n                7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF\n                8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF\n                0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF\n                0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF\n                0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0\n                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2\n                1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4\n                1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6\n                1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8\n            }\n        };\n\n        JSON_ASSERT(byte < utf8d.size());\n        const std::uint8_t type = utf8d[byte];\n\n        codep = (state != UTF8_ACCEPT)\n                ? (byte & 0x3fu) | (codep << 6u)\n                : (0xFFu >> type) & (byte);\n\n        const std::size_t index = 256u + static_cast<size_t>(state) * 16u + static_cast<size_t>(type);\n        JSON_ASSERT(index < utf8d.size());\n        state = utf8d[index];\n        return state;\n    }\n\n    /*\n     * Overload to make the compiler happy while it is instantiating\n     * dump_integer for number_unsigned_t.\n     * Must never be called.\n     */\n    number_unsigned_t remove_sign(number_unsigned_t x)\n    {\n        JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        return x; // LCOV_EXCL_LINE\n    }\n\n    /*\n     * Helper function for dump_integer\n     *\n     * This function takes a negative signed integer and returns its absolute\n     * value as unsigned integer. The plus/minus shuffling is necessary as we can\n     * not directly remove the sign of an arbitrary signed integer as the\n     * absolute values of INT_MIN and INT_MAX are usually not the same. See\n     * #1708 for details.\n     */\n    inline number_unsigned_t remove_sign(number_integer_t x) noexcept\n    {\n        JSON_ASSERT(x < 0 && x < (std::numeric_limits<number_integer_t>::max)()); // NOLINT(misc-redundant-expression)\n        return static_cast<number_unsigned_t>(-(x + 1)) + 1;\n    }\n\n  private:\n    /// the output of the serializer\n    output_adapter_t<char> o = nullptr;\n\n    /// a (hopefully) large enough character buffer\n    std::array<char, 64> number_buffer{{}};\n\n    /// the locale\n    const std::lconv* loc = nullptr;\n    /// the locale's thousand separator character\n    const char thousands_sep = '\\0';\n    /// the locale's decimal point character\n    const char decimal_point = '\\0';\n\n    /// string buffer\n    std::array<char, 512> string_buffer{{}};\n\n    /// the indentation character\n    const char indent_char;\n    /// the indentation string\n    string_t indent_string;\n\n    /// error_handler how to react on decoding errors\n    const error_handler_t error_handler;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/value_t.hpp>\n\n// #include <nlohmann/json_fwd.hpp>\n\n// #include <nlohmann/ordered_map.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <functional> // equal_to, less\n#include <initializer_list> // initializer_list\n#include <iterator> // input_iterator_tag, iterator_traits\n#include <memory> // allocator\n#include <stdexcept> // for out_of_range\n#include <type_traits> // enable_if, is_convertible\n#include <utility> // pair\n#include <vector> // vector\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// ordered_map: a minimal map-like container that preserves insertion order\n/// for use within nlohmann::basic_json<ordered_map>\ntemplate <class Key, class T, class IgnoredLess = std::less<Key>,\n          class Allocator = std::allocator<std::pair<const Key, T>>>\n                  struct ordered_map : std::vector<std::pair<const Key, T>, Allocator>\n{\n    using key_type = Key;\n    using mapped_type = T;\n    using Container = std::vector<std::pair<const Key, T>, Allocator>;\n    using iterator = typename Container::iterator;\n    using const_iterator = typename Container::const_iterator;\n    using size_type = typename Container::size_type;\n    using value_type = typename Container::value_type;\n#ifdef JSON_HAS_CPP_14\n    using key_compare = std::equal_to<>;\n#else\n    using key_compare = std::equal_to<Key>;\n#endif\n\n    // Explicit constructors instead of `using Container::Container`\n    // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4)\n    ordered_map() noexcept(noexcept(Container())) : Container{} {}\n    explicit ordered_map(const Allocator& alloc) noexcept(noexcept(Container(alloc))) : Container{alloc} {}\n    template <class It>\n    ordered_map(It first, It last, const Allocator& alloc = Allocator())\n        : Container{first, last, alloc} {}\n    ordered_map(std::initializer_list<value_type> init, const Allocator& alloc = Allocator() )\n        : Container{init, alloc} {}\n\n    std::pair<iterator, bool> emplace(const key_type& key, T&& t)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return {it, false};\n            }\n        }\n        Container::emplace_back(key, std::forward<T>(t));\n        return {std::prev(this->end()), true};\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    std::pair<iterator, bool> emplace(KeyType && key, T && t)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return {it, false};\n            }\n        }\n        Container::emplace_back(std::forward<KeyType>(key), std::forward<T>(t));\n        return {std::prev(this->end()), true};\n    }\n\n    T& operator[](const key_type& key)\n    {\n        return emplace(key, T{}).first->second;\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    T & operator[](KeyType && key)\n    {\n        return emplace(std::forward<KeyType>(key), T{}).first->second;\n    }\n\n    const T& operator[](const key_type& key) const\n    {\n        return at(key);\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    const T & operator[](KeyType && key) const\n    {\n        return at(std::forward<KeyType>(key));\n    }\n\n    T& at(const key_type& key)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it->second;\n            }\n        }\n\n        JSON_THROW(std::out_of_range(\"key not found\"));\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it->second;\n            }\n        }\n\n        JSON_THROW(std::out_of_range(\"key not found\"));\n    }\n\n    const T& at(const key_type& key) const\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it->second;\n            }\n        }\n\n        JSON_THROW(std::out_of_range(\"key not found\"));\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it->second;\n            }\n        }\n\n        JSON_THROW(std::out_of_range(\"key not found\"));\n    }\n\n    size_type erase(const key_type& key)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                // Since we cannot move const Keys, re-construct them in place\n                for (auto next = it; ++next != this->end(); ++it)\n                {\n                    it->~value_type(); // Destroy but keep allocation\n                    new (&*it) value_type{std::move(*next)};\n                }\n                Container::pop_back();\n                return 1;\n            }\n        }\n        return 0;\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                // Since we cannot move const Keys, re-construct them in place\n                for (auto next = it; ++next != this->end(); ++it)\n                {\n                    it->~value_type(); // Destroy but keep allocation\n                    new (&*it) value_type{std::move(*next)};\n                }\n                Container::pop_back();\n                return 1;\n            }\n        }\n        return 0;\n    }\n\n    iterator erase(iterator pos)\n    {\n        return erase(pos, std::next(pos));\n    }\n\n    iterator erase(iterator first, iterator last)\n    {\n        if (first == last)\n        {\n            return first;\n        }\n\n        const auto elements_affected = std::distance(first, last);\n        const auto offset = std::distance(Container::begin(), first);\n\n        // This is the start situation. We need to delete elements_affected\n        // elements (3 in this example: e, f, g), and need to return an\n        // iterator past the last deleted element (h in this example).\n        // Note that offset is the distance from the start of the vector\n        // to first. We will need this later.\n\n        // [ a, b, c, d, e, f, g, h, i, j ]\n        //               ^        ^\n        //             first    last\n\n        // Since we cannot move const Keys, we re-construct them in place.\n        // We start at first and re-construct (viz. copy) the elements from\n        // the back of the vector. Example for first iteration:\n\n        //               ,--------.\n        //               v        |   destroy e and re-construct with h\n        // [ a, b, c, d, e, f, g, h, i, j ]\n        //               ^        ^\n        //               it       it + elements_affected\n\n        for (auto it = first; std::next(it, elements_affected) != Container::end(); ++it)\n        {\n            it->~value_type(); // destroy but keep allocation\n            new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // \"move\" next element to it\n        }\n\n        // [ a, b, c, d, h, i, j, h, i, j ]\n        //               ^        ^\n        //             first    last\n\n        // remove the unneeded elements at the end of the vector\n        Container::resize(this->size() - static_cast<size_type>(elements_affected));\n\n        // [ a, b, c, d, h, i, j ]\n        //               ^        ^\n        //             first    last\n\n        // first is now pointing past the last deleted element, but we cannot\n        // use this iterator, because it may have been invalidated by the\n        // resize call. Instead, we can return begin() + offset.\n        return Container::begin() + offset;\n    }\n\n    size_type count(const key_type& key) const\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return 1;\n            }\n        }\n        return 0;\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return 1;\n            }\n        }\n        return 0;\n    }\n\n    iterator find(const key_type& key)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it;\n            }\n        }\n        return Container::end();\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it;\n            }\n        }\n        return Container::end();\n    }\n\n    const_iterator find(const key_type& key) const\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it;\n            }\n        }\n        return Container::end();\n    }\n\n    std::pair<iterator, bool> insert( value_type&& value )\n    {\n        return emplace(value.first, std::move(value.second));\n    }\n\n    std::pair<iterator, bool> insert( const value_type& value )\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, value.first))\n            {\n                return {it, false};\n            }\n        }\n        Container::push_back(value);\n        return {--this->end(), true};\n    }\n\n    template<typename InputIt>\n    using require_input_iter = typename std::enable_if<std::is_convertible<typename std::iterator_traits<InputIt>::iterator_category,\n            std::input_iterator_tag>::value>::type;\n\n    template<typename InputIt, typename = require_input_iter<InputIt>>\n    void insert(InputIt first, InputIt last)\n    {\n        for (auto it = first; it != last; ++it)\n        {\n            insert(*it);\n        }\n    }\n\nprivate:\n    JSON_NO_UNIQUE_ADDRESS key_compare m_compare = key_compare();\n};\n\nNLOHMANN_JSON_NAMESPACE_END\n\n\n#if defined(JSON_HAS_CPP_17)\n    #if JSON_HAS_STATIC_RTTI\n        #include <any>\n    #endif\n    #include <string_view>\n#endif\n\n/*!\n@brief namespace for Niels Lohmann\n@see https://github.com/nlohmann\n@since version 1.0.0\n*/\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/*!\n@brief a class to store JSON values\n\n@internal\n@invariant The member variables @a m_value and @a m_type have the following\nrelationship:\n- If `m_type == value_t::object`, then `m_value.object != nullptr`.\n- If `m_type == value_t::array`, then `m_value.array != nullptr`.\n- If `m_type == value_t::string`, then `m_value.string != nullptr`.\nThe invariants are checked by member function assert_invariant().\n\n@note ObjectType trick from https://stackoverflow.com/a/9860911\n@endinternal\n\n@since version 1.0.0\n\n@nosubgrouping\n*/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nclass basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)\n    : public ::nlohmann::detail::json_base_class<CustomBaseClass>\n{\n  private:\n    template<detail::value_t> friend struct detail::external_constructor;\n\n    template<typename>\n    friend class ::nlohmann::json_pointer;\n    // can be restored when json_pointer backwards compatibility is removed\n    // friend ::nlohmann::json_pointer<StringType>;\n\n    template<typename BasicJsonType, typename InputType>\n    friend class ::nlohmann::detail::parser;\n    friend ::nlohmann::detail::serializer<basic_json>;\n    template<typename BasicJsonType>\n    friend class ::nlohmann::detail::iter_impl;\n    template<typename BasicJsonType, typename CharType>\n    friend class ::nlohmann::detail::binary_writer;\n    template<typename BasicJsonType, typename InputType, typename SAX>\n    friend class ::nlohmann::detail::binary_reader;\n    template<typename BasicJsonType>\n    friend class ::nlohmann::detail::json_sax_dom_parser;\n    template<typename BasicJsonType>\n    friend class ::nlohmann::detail::json_sax_dom_callback_parser;\n    friend class ::nlohmann::detail::exception;\n\n    /// workaround type for MSVC\n    using basic_json_t = NLOHMANN_BASIC_JSON_TPL;\n    using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    // convenience aliases for types residing in namespace detail;\n    using lexer = ::nlohmann::detail::lexer_base<basic_json>;\n\n    template<typename InputAdapterType>\n    static ::nlohmann::detail::parser<basic_json, InputAdapterType> parser(\n        InputAdapterType adapter,\n        detail::parser_callback_t<basic_json>cb = nullptr,\n        const bool allow_exceptions = true,\n        const bool ignore_comments = false\n                                 )\n    {\n        return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),\n                std::move(cb), allow_exceptions, ignore_comments);\n    }\n\n  private:\n    using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t;\n    template<typename BasicJsonType>\n    using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>;\n    template<typename BasicJsonType>\n    using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>;\n    template<typename Iterator>\n    using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>;\n    template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>;\n\n    template<typename CharType>\n    using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>;\n\n    template<typename InputType>\n    using binary_reader = ::nlohmann::detail::binary_reader<basic_json, InputType>;\n    template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>;\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    using serializer = ::nlohmann::detail::serializer<basic_json>;\n\n  public:\n    using value_t = detail::value_t;\n    /// JSON Pointer, see @ref nlohmann::json_pointer\n    using json_pointer = ::nlohmann::json_pointer<StringType>;\n    template<typename T, typename SFINAE>\n    using json_serializer = JSONSerializer<T, SFINAE>;\n    /// how to treat decoding errors\n    using error_handler_t = detail::error_handler_t;\n    /// how to treat CBOR tags\n    using cbor_tag_handler_t = detail::cbor_tag_handler_t;\n    /// helper type for initializer lists of basic_json values\n    using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;\n\n    using input_format_t = detail::input_format_t;\n    /// SAX interface type, see @ref nlohmann::json_sax\n    using json_sax_t = json_sax<basic_json>;\n\n    ////////////////\n    // exceptions //\n    ////////////////\n\n    /// @name exceptions\n    /// Classes to implement user-defined exceptions.\n    /// @{\n\n    using exception = detail::exception;\n    using parse_error = detail::parse_error;\n    using invalid_iterator = detail::invalid_iterator;\n    using type_error = detail::type_error;\n    using out_of_range = detail::out_of_range;\n    using other_error = detail::other_error;\n\n    /// @}\n\n    /////////////////////\n    // container types //\n    /////////////////////\n\n    /// @name container types\n    /// The canonic container types to use @ref basic_json like any other STL\n    /// container.\n    /// @{\n\n    /// the type of elements in a basic_json container\n    using value_type = basic_json;\n\n    /// the type of an element reference\n    using reference = value_type&;\n    /// the type of an element const reference\n    using const_reference = const value_type&;\n\n    /// a type to represent differences between iterators\n    using difference_type = std::ptrdiff_t;\n    /// a type to represent container sizes\n    using size_type = std::size_t;\n\n    /// the allocator type\n    using allocator_type = AllocatorType<basic_json>;\n\n    /// the type of an element pointer\n    using pointer = typename std::allocator_traits<allocator_type>::pointer;\n    /// the type of an element const pointer\n    using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;\n\n    /// an iterator for a basic_json container\n    using iterator = iter_impl<basic_json>;\n    /// a const iterator for a basic_json container\n    using const_iterator = iter_impl<const basic_json>;\n    /// a reverse iterator for a basic_json container\n    using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>;\n    /// a const reverse iterator for a basic_json container\n    using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>;\n\n    /// @}\n\n    /// @brief returns the allocator associated with the container\n    /// @sa https://json.nlohmann.me/api/basic_json/get_allocator/\n    static allocator_type get_allocator()\n    {\n        return allocator_type();\n    }\n\n    /// @brief returns version information on the library\n    /// @sa https://json.nlohmann.me/api/basic_json/meta/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json meta()\n    {\n        basic_json result;\n\n        result[\"copyright\"] = \"(C) 2013-2023 Niels Lohmann\";\n        result[\"name\"] = \"JSON for Modern C++\";\n        result[\"url\"] = \"https://github.com/nlohmann/json\";\n        result[\"version\"][\"string\"] =\n            detail::concat(std::to_string(NLOHMANN_JSON_VERSION_MAJOR), '.',\n                           std::to_string(NLOHMANN_JSON_VERSION_MINOR), '.',\n                           std::to_string(NLOHMANN_JSON_VERSION_PATCH));\n        result[\"version\"][\"major\"] = NLOHMANN_JSON_VERSION_MAJOR;\n        result[\"version\"][\"minor\"] = NLOHMANN_JSON_VERSION_MINOR;\n        result[\"version\"][\"patch\"] = NLOHMANN_JSON_VERSION_PATCH;\n\n#ifdef _WIN32\n        result[\"platform\"] = \"win32\";\n#elif defined __linux__\n        result[\"platform\"] = \"linux\";\n#elif defined __APPLE__\n        result[\"platform\"] = \"apple\";\n#elif defined __unix__\n        result[\"platform\"] = \"unix\";\n#else\n        result[\"platform\"] = \"unknown\";\n#endif\n\n#if defined(__ICC) || defined(__INTEL_COMPILER)\n        result[\"compiler\"] = {{\"family\", \"icc\"}, {\"version\", __INTEL_COMPILER}};\n#elif defined(__clang__)\n        result[\"compiler\"] = {{\"family\", \"clang\"}, {\"version\", __clang_version__}};\n#elif defined(__GNUC__) || defined(__GNUG__)\n        result[\"compiler\"] = {{\"family\", \"gcc\"}, {\"version\", detail::concat(\n                    std::to_string(__GNUC__), '.',\n                    std::to_string(__GNUC_MINOR__), '.',\n                    std::to_string(__GNUC_PATCHLEVEL__))\n            }\n        };\n#elif defined(__HP_cc) || defined(__HP_aCC)\n        result[\"compiler\"] = \"hp\"\n#elif defined(__IBMCPP__)\n        result[\"compiler\"] = {{\"family\", \"ilecpp\"}, {\"version\", __IBMCPP__}};\n#elif defined(_MSC_VER)\n        result[\"compiler\"] = {{\"family\", \"msvc\"}, {\"version\", _MSC_VER}};\n#elif defined(__PGI)\n        result[\"compiler\"] = {{\"family\", \"pgcpp\"}, {\"version\", __PGI}};\n#elif defined(__SUNPRO_CC)\n        result[\"compiler\"] = {{\"family\", \"sunpro\"}, {\"version\", __SUNPRO_CC}};\n#else\n        result[\"compiler\"] = {{\"family\", \"unknown\"}, {\"version\", \"unknown\"}};\n#endif\n\n#if defined(_MSVC_LANG)\n        result[\"compiler\"][\"c++\"] = std::to_string(_MSVC_LANG);\n#elif defined(__cplusplus)\n        result[\"compiler\"][\"c++\"] = std::to_string(__cplusplus);\n#else\n        result[\"compiler\"][\"c++\"] = \"unknown\";\n#endif\n        return result;\n    }\n\n    ///////////////////////////\n    // JSON value data types //\n    ///////////////////////////\n\n    /// @name JSON value data types\n    /// The data types to store a JSON value. These types are derived from\n    /// the template arguments passed to class @ref basic_json.\n    /// @{\n\n    /// @brief default object key comparator type\n    /// The actual object key comparator type (@ref object_comparator_t) may be\n    /// different.\n    /// @sa https://json.nlohmann.me/api/basic_json/default_object_comparator_t/\n#if defined(JSON_HAS_CPP_14)\n    // use of transparent comparator avoids unnecessary repeated construction of temporaries\n    // in functions involving lookup by key with types other than object_t::key_type (aka. StringType)\n    using default_object_comparator_t = std::less<>;\n#else\n    using default_object_comparator_t = std::less<StringType>;\n#endif\n\n    /// @brief a type for an object\n    /// @sa https://json.nlohmann.me/api/basic_json/object_t/\n    using object_t = ObjectType<StringType,\n          basic_json,\n          default_object_comparator_t,\n          AllocatorType<std::pair<const StringType,\n          basic_json>>>;\n\n    /// @brief a type for an array\n    /// @sa https://json.nlohmann.me/api/basic_json/array_t/\n    using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;\n\n    /// @brief a type for a string\n    /// @sa https://json.nlohmann.me/api/basic_json/string_t/\n    using string_t = StringType;\n\n    /// @brief a type for a boolean\n    /// @sa https://json.nlohmann.me/api/basic_json/boolean_t/\n    using boolean_t = BooleanType;\n\n    /// @brief a type for a number (integer)\n    /// @sa https://json.nlohmann.me/api/basic_json/number_integer_t/\n    using number_integer_t = NumberIntegerType;\n\n    /// @brief a type for a number (unsigned)\n    /// @sa https://json.nlohmann.me/api/basic_json/number_unsigned_t/\n    using number_unsigned_t = NumberUnsignedType;\n\n    /// @brief a type for a number (floating-point)\n    /// @sa https://json.nlohmann.me/api/basic_json/number_float_t/\n    using number_float_t = NumberFloatType;\n\n    /// @brief a type for a packed binary type\n    /// @sa https://json.nlohmann.me/api/basic_json/binary_t/\n    using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;\n\n    /// @brief object key comparator type\n    /// @sa https://json.nlohmann.me/api/basic_json/object_comparator_t/\n    using object_comparator_t = detail::actual_object_comparator_t<basic_json>;\n\n    /// @}\n\n  private:\n\n    /// helper for exception-safe object creation\n    template<typename T, typename... Args>\n    JSON_HEDLEY_RETURNS_NON_NULL\n    static T* create(Args&& ... args)\n    {\n        AllocatorType<T> alloc;\n        using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;\n\n        auto deleter = [&](T * obj)\n        {\n            AllocatorTraits::deallocate(alloc, obj, 1);\n        };\n        std::unique_ptr<T, decltype(deleter)> obj(AllocatorTraits::allocate(alloc, 1), deleter);\n        AllocatorTraits::construct(alloc, obj.get(), std::forward<Args>(args)...);\n        JSON_ASSERT(obj != nullptr);\n        return obj.release();\n    }\n\n    ////////////////////////\n    // JSON value storage //\n    ////////////////////////\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /*!\n    @brief a JSON value\n\n    The actual storage for a JSON value of the @ref basic_json class. This\n    union combines the different storage types for the JSON value types\n    defined in @ref value_t.\n\n    JSON type | value_t type    | used type\n    --------- | --------------- | ------------------------\n    object    | object          | pointer to @ref object_t\n    array     | array           | pointer to @ref array_t\n    string    | string          | pointer to @ref string_t\n    boolean   | boolean         | @ref boolean_t\n    number    | number_integer  | @ref number_integer_t\n    number    | number_unsigned | @ref number_unsigned_t\n    number    | number_float    | @ref number_float_t\n    binary    | binary          | pointer to @ref binary_t\n    null      | null            | *no value is stored*\n\n    @note Variable-length types (objects, arrays, and strings) are stored as\n    pointers. The size of the union should not exceed 64 bits if the default\n    value types are used.\n\n    @since version 1.0.0\n    */\n    union json_value\n    {\n        /// object (stored with pointer to save storage)\n        object_t* object;\n        /// array (stored with pointer to save storage)\n        array_t* array;\n        /// string (stored with pointer to save storage)\n        string_t* string;\n        /// binary (stored with pointer to save storage)\n        binary_t* binary;\n        /// boolean\n        boolean_t boolean;\n        /// number (integer)\n        number_integer_t number_integer;\n        /// number (unsigned integer)\n        number_unsigned_t number_unsigned;\n        /// number (floating-point)\n        number_float_t number_float;\n\n        /// default constructor (for null values)\n        json_value() = default;\n        /// constructor for booleans\n        json_value(boolean_t v) noexcept : boolean(v) {}\n        /// constructor for numbers (integer)\n        json_value(number_integer_t v) noexcept : number_integer(v) {}\n        /// constructor for numbers (unsigned)\n        json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}\n        /// constructor for numbers (floating-point)\n        json_value(number_float_t v) noexcept : number_float(v) {}\n        /// constructor for empty values of a given type\n        json_value(value_t t)\n        {\n            switch (t)\n            {\n                case value_t::object:\n                {\n                    object = create<object_t>();\n                    break;\n                }\n\n                case value_t::array:\n                {\n                    array = create<array_t>();\n                    break;\n                }\n\n                case value_t::string:\n                {\n                    string = create<string_t>(\"\");\n                    break;\n                }\n\n                case value_t::binary:\n                {\n                    binary = create<binary_t>();\n                    break;\n                }\n\n                case value_t::boolean:\n                {\n                    boolean = static_cast<boolean_t>(false);\n                    break;\n                }\n\n                case value_t::number_integer:\n                {\n                    number_integer = static_cast<number_integer_t>(0);\n                    break;\n                }\n\n                case value_t::number_unsigned:\n                {\n                    number_unsigned = static_cast<number_unsigned_t>(0);\n                    break;\n                }\n\n                case value_t::number_float:\n                {\n                    number_float = static_cast<number_float_t>(0.0);\n                    break;\n                }\n\n                case value_t::null:\n                {\n                    object = nullptr;  // silence warning, see #821\n                    break;\n                }\n\n                case value_t::discarded:\n                default:\n                {\n                    object = nullptr;  // silence warning, see #821\n                    if (JSON_HEDLEY_UNLIKELY(t == value_t::null))\n                    {\n                        JSON_THROW(other_error::create(500, \"961c151d2e87f2686a955a9be24d316f1362bf21 3.11.3\", nullptr)); // LCOV_EXCL_LINE\n                    }\n                    break;\n                }\n            }\n        }\n\n        /// constructor for strings\n        json_value(const string_t& value) : string(create<string_t>(value)) {}\n\n        /// constructor for rvalue strings\n        json_value(string_t&& value) : string(create<string_t>(std::move(value))) {}\n\n        /// constructor for objects\n        json_value(const object_t& value) : object(create<object_t>(value)) {}\n\n        /// constructor for rvalue objects\n        json_value(object_t&& value) : object(create<object_t>(std::move(value))) {}\n\n        /// constructor for arrays\n        json_value(const array_t& value) : array(create<array_t>(value)) {}\n\n        /// constructor for rvalue arrays\n        json_value(array_t&& value) : array(create<array_t>(std::move(value))) {}\n\n        /// constructor for binary arrays\n        json_value(const typename binary_t::container_type& value) : binary(create<binary_t>(value)) {}\n\n        /// constructor for rvalue binary arrays\n        json_value(typename binary_t::container_type&& value) : binary(create<binary_t>(std::move(value))) {}\n\n        /// constructor for binary arrays (internal type)\n        json_value(const binary_t& value) : binary(create<binary_t>(value)) {}\n\n        /// constructor for rvalue binary arrays (internal type)\n        json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}\n\n        void destroy(value_t t)\n        {\n            if (\n                (t == value_t::object && object == nullptr) ||\n                (t == value_t::array && array == nullptr) ||\n                (t == value_t::string && string == nullptr) ||\n                (t == value_t::binary && binary == nullptr)\n            )\n            {\n                //not initialized (e.g. due to exception in the ctor)\n                return;\n            }\n            if (t == value_t::array || t == value_t::object)\n            {\n                // flatten the current json_value to a heap-allocated stack\n                std::vector<basic_json> stack;\n\n                // move the top-level items to stack\n                if (t == value_t::array)\n                {\n                    stack.reserve(array->size());\n                    std::move(array->begin(), array->end(), std::back_inserter(stack));\n                }\n                else\n                {\n                    stack.reserve(object->size());\n                    for (auto&& it : *object)\n                    {\n                        stack.push_back(std::move(it.second));\n                    }\n                }\n\n                while (!stack.empty())\n                {\n                    // move the last item to local variable to be processed\n                    basic_json current_item(std::move(stack.back()));\n                    stack.pop_back();\n\n                    // if current_item is array/object, move\n                    // its children to the stack to be processed later\n                    if (current_item.is_array())\n                    {\n                        std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));\n\n                        current_item.m_data.m_value.array->clear();\n                    }\n                    else if (current_item.is_object())\n                    {\n                        for (auto&& it : *current_item.m_data.m_value.object)\n                        {\n                            stack.push_back(std::move(it.second));\n                        }\n\n                        current_item.m_data.m_value.object->clear();\n                    }\n\n                    // it's now safe that current_item get destructed\n                    // since it doesn't have any children\n                }\n            }\n\n            switch (t)\n            {\n                case value_t::object:\n                {\n                    AllocatorType<object_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, object);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);\n                    break;\n                }\n\n                case value_t::array:\n                {\n                    AllocatorType<array_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, array);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);\n                    break;\n                }\n\n                case value_t::string:\n                {\n                    AllocatorType<string_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, string);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);\n                    break;\n                }\n\n                case value_t::binary:\n                {\n                    AllocatorType<binary_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);\n                    break;\n                }\n\n                case value_t::null:\n                case value_t::boolean:\n                case value_t::number_integer:\n                case value_t::number_unsigned:\n                case value_t::number_float:\n                case value_t::discarded:\n                default:\n                {\n                    break;\n                }\n            }\n        }\n    };\n\n  private:\n    /*!\n    @brief checks the class invariants\n\n    This function asserts the class invariants. It needs to be called at the\n    end of every constructor to make sure that created objects respect the\n    invariant. Furthermore, it has to be called each time the type of a JSON\n    value is changed, because the invariant expresses a relationship between\n    @a m_type and @a m_value.\n\n    Furthermore, the parent relation is checked for arrays and objects: If\n    @a check_parents true and the value is an array or object, then the\n    container's elements must have the current value as parent.\n\n    @param[in] check_parents  whether the parent relation should be checked.\n               The value is true by default and should only be set to false\n               during destruction of objects when the invariant does not\n               need to hold.\n    */\n    void assert_invariant(bool check_parents = true) const noexcept\n    {\n        JSON_ASSERT(m_data.m_type != value_t::object || m_data.m_value.object != nullptr);\n        JSON_ASSERT(m_data.m_type != value_t::array || m_data.m_value.array != nullptr);\n        JSON_ASSERT(m_data.m_type != value_t::string || m_data.m_value.string != nullptr);\n        JSON_ASSERT(m_data.m_type != value_t::binary || m_data.m_value.binary != nullptr);\n\n#if JSON_DIAGNOSTICS\n        JSON_TRY\n        {\n            // cppcheck-suppress assertWithSideEffect\n            JSON_ASSERT(!check_parents || !is_structured() || std::all_of(begin(), end(), [this](const basic_json & j)\n            {\n                return j.m_parent == this;\n            }));\n        }\n        JSON_CATCH(...) {} // LCOV_EXCL_LINE\n#endif\n        static_cast<void>(check_parents);\n    }\n\n    void set_parents()\n    {\n#if JSON_DIAGNOSTICS\n        switch (m_data.m_type)\n        {\n            case value_t::array:\n            {\n                for (auto& element : *m_data.m_value.array)\n                {\n                    element.m_parent = this;\n                }\n                break;\n            }\n\n            case value_t::object:\n            {\n                for (auto& element : *m_data.m_value.object)\n                {\n                    element.second.m_parent = this;\n                }\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                break;\n        }\n#endif\n    }\n\n    iterator set_parents(iterator it, typename iterator::difference_type count_set_parents)\n    {\n#if JSON_DIAGNOSTICS\n        for (typename iterator::difference_type i = 0; i < count_set_parents; ++i)\n        {\n            (it + i)->m_parent = this;\n        }\n#else\n        static_cast<void>(count_set_parents);\n#endif\n        return it;\n    }\n\n    reference set_parent(reference j, std::size_t old_capacity = static_cast<std::size_t>(-1))\n    {\n#if JSON_DIAGNOSTICS\n        if (old_capacity != static_cast<std::size_t>(-1))\n        {\n            // see https://github.com/nlohmann/json/issues/2838\n            JSON_ASSERT(type() == value_t::array);\n            if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))\n            {\n                // capacity has changed: update all parents\n                set_parents();\n                return j;\n            }\n        }\n\n        // ordered_json uses a vector internally, so pointers could have\n        // been invalidated; see https://github.com/nlohmann/json/issues/2962\n#ifdef JSON_HEDLEY_MSVC_VERSION\n#pragma warning(push )\n#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr\n#endif\n        if (detail::is_ordered_map<object_t>::value)\n        {\n            set_parents();\n            return j;\n        }\n#ifdef JSON_HEDLEY_MSVC_VERSION\n#pragma warning( pop )\n#endif\n\n        j.m_parent = this;\n#else\n        static_cast<void>(j);\n        static_cast<void>(old_capacity);\n#endif\n        return j;\n    }\n\n  public:\n    //////////////////////////\n    // JSON parser callback //\n    //////////////////////////\n\n    /// @brief parser event types\n    /// @sa https://json.nlohmann.me/api/basic_json/parse_event_t/\n    using parse_event_t = detail::parse_event_t;\n\n    /// @brief per-element parser callback type\n    /// @sa https://json.nlohmann.me/api/basic_json/parser_callback_t/\n    using parser_callback_t = detail::parser_callback_t<basic_json>;\n\n    //////////////////\n    // constructors //\n    //////////////////\n\n    /// @name constructors and destructors\n    /// Constructors of class @ref basic_json, copy/move constructor, copy\n    /// assignment, static functions creating objects, and the destructor.\n    /// @{\n\n    /// @brief create an empty value with a given type\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(const value_t v)\n        : m_data(v)\n    {\n        assert_invariant();\n    }\n\n    /// @brief create a null object\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(std::nullptr_t = nullptr) noexcept // NOLINT(bugprone-exception-escape)\n        : basic_json(value_t::null)\n    {\n        assert_invariant();\n    }\n\n    /// @brief create a JSON value from compatible types\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    template < typename CompatibleType,\n               typename U = detail::uncvref_t<CompatibleType>,\n               detail::enable_if_t <\n                   !detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >\n    basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)\n                JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),\n                                           std::forward<CompatibleType>(val))))\n    {\n        JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief create a JSON value from an existing one\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    template < typename BasicJsonType,\n               detail::enable_if_t <\n                   detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value, int > = 0 >\n    basic_json(const BasicJsonType& val)\n    {\n        using other_boolean_t = typename BasicJsonType::boolean_t;\n        using other_number_float_t = typename BasicJsonType::number_float_t;\n        using other_number_integer_t = typename BasicJsonType::number_integer_t;\n        using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n        using other_string_t = typename BasicJsonType::string_t;\n        using other_object_t = typename BasicJsonType::object_t;\n        using other_array_t = typename BasicJsonType::array_t;\n        using other_binary_t = typename BasicJsonType::binary_t;\n\n        switch (val.type())\n        {\n            case value_t::boolean:\n                JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());\n                break;\n            case value_t::number_float:\n                JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());\n                break;\n            case value_t::number_integer:\n                JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());\n                break;\n            case value_t::number_unsigned:\n                JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());\n                break;\n            case value_t::string:\n                JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());\n                break;\n            case value_t::object:\n                JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());\n                break;\n            case value_t::array:\n                JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());\n                break;\n            case value_t::binary:\n                JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());\n                break;\n            case value_t::null:\n                *this = nullptr;\n                break;\n            case value_t::discarded:\n                m_data.m_type = value_t::discarded;\n                break;\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n        JSON_ASSERT(m_data.m_type == val.type());\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief create a container (array or object) from an initializer list\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(initializer_list_t init,\n               bool type_deduction = true,\n               value_t manual_type = value_t::array)\n    {\n        // check if each element is an array with two elements whose first\n        // element is a string\n        bool is_an_object = std::all_of(init.begin(), init.end(),\n                                        [](const detail::json_ref<basic_json>& element_ref)\n        {\n            // The cast is to ensure op[size_type] is called, bearing in mind size_type may not be int;\n            // (many string types can be constructed from 0 via its null-pointer guise, so we get a\n            // broken call to op[key_type], the wrong semantics and a 4804 warning on Windows)\n            return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[static_cast<size_type>(0)].is_string();\n        });\n\n        // adjust type if type deduction is not wanted\n        if (!type_deduction)\n        {\n            // if array is wanted, do not create an object though possible\n            if (manual_type == value_t::array)\n            {\n                is_an_object = false;\n            }\n\n            // if object is wanted but impossible, throw an exception\n            if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object))\n            {\n                JSON_THROW(type_error::create(301, \"cannot create object from initializer list\", nullptr));\n            }\n        }\n\n        if (is_an_object)\n        {\n            // the initializer list is a list of pairs -> create object\n            m_data.m_type = value_t::object;\n            m_data.m_value = value_t::object;\n\n            for (auto& element_ref : init)\n            {\n                auto element = element_ref.moved_or_copied();\n                m_data.m_value.object->emplace(\n                    std::move(*((*element.m_data.m_value.array)[0].m_data.m_value.string)),\n                    std::move((*element.m_data.m_value.array)[1]));\n            }\n        }\n        else\n        {\n            // the initializer list describes an array -> create array\n            m_data.m_type = value_t::array;\n            m_data.m_value.array = create<array_t>(init.begin(), init.end());\n        }\n\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief explicitly create a binary array (without subtype)\n    /// @sa https://json.nlohmann.me/api/basic_json/binary/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json binary(const typename binary_t::container_type& init)\n    {\n        auto res = basic_json();\n        res.m_data.m_type = value_t::binary;\n        res.m_data.m_value = init;\n        return res;\n    }\n\n    /// @brief explicitly create a binary array (with subtype)\n    /// @sa https://json.nlohmann.me/api/basic_json/binary/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)\n    {\n        auto res = basic_json();\n        res.m_data.m_type = value_t::binary;\n        res.m_data.m_value = binary_t(init, subtype);\n        return res;\n    }\n\n    /// @brief explicitly create a binary array\n    /// @sa https://json.nlohmann.me/api/basic_json/binary/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json binary(typename binary_t::container_type&& init)\n    {\n        auto res = basic_json();\n        res.m_data.m_type = value_t::binary;\n        res.m_data.m_value = std::move(init);\n        return res;\n    }\n\n    /// @brief explicitly create a binary array (with subtype)\n    /// @sa https://json.nlohmann.me/api/basic_json/binary/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)\n    {\n        auto res = basic_json();\n        res.m_data.m_type = value_t::binary;\n        res.m_data.m_value = binary_t(std::move(init), subtype);\n        return res;\n    }\n\n    /// @brief explicitly create an array from an initializer list\n    /// @sa https://json.nlohmann.me/api/basic_json/array/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json array(initializer_list_t init = {})\n    {\n        return basic_json(init, false, value_t::array);\n    }\n\n    /// @brief explicitly create an object from an initializer list\n    /// @sa https://json.nlohmann.me/api/basic_json/object/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json object(initializer_list_t init = {})\n    {\n        return basic_json(init, false, value_t::object);\n    }\n\n    /// @brief construct an array with count copies of given value\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(size_type cnt, const basic_json& val):\n        m_data{cnt, val}\n    {\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief construct a JSON container given an iterator range\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    template < class InputIT, typename std::enable_if <\n                   std::is_same<InputIT, typename basic_json_t::iterator>::value ||\n                   std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int >::type = 0 >\n    basic_json(InputIT first, InputIT last)\n    {\n        JSON_ASSERT(first.m_object != nullptr);\n        JSON_ASSERT(last.m_object != nullptr);\n\n        // make sure iterator fits the current value\n        if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(201, \"iterators are not compatible\", nullptr));\n        }\n\n        // copy type from first iterator\n        m_data.m_type = first.m_object->m_data.m_type;\n\n        // check if iterator range is complete for primitive values\n        switch (m_data.m_type)\n        {\n            case value_t::boolean:\n            case value_t::number_float:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::string:\n            {\n                if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin()\n                                         || !last.m_it.primitive_iterator.is_end()))\n                {\n                    JSON_THROW(invalid_iterator::create(204, \"iterators out of range\", first.m_object));\n                }\n                break;\n            }\n\n            case value_t::null:\n            case value_t::object:\n            case value_t::array:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                break;\n        }\n\n        switch (m_data.m_type)\n        {\n            case value_t::number_integer:\n            {\n                m_data.m_value.number_integer = first.m_object->m_data.m_value.number_integer;\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                m_data.m_value.number_unsigned = first.m_object->m_data.m_value.number_unsigned;\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                m_data.m_value.number_float = first.m_object->m_data.m_value.number_float;\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                m_data.m_value.boolean = first.m_object->m_data.m_value.boolean;\n                break;\n            }\n\n            case value_t::string:\n            {\n                m_data.m_value = *first.m_object->m_data.m_value.string;\n                break;\n            }\n\n            case value_t::object:\n            {\n                m_data.m_value.object = create<object_t>(first.m_it.object_iterator,\n                                        last.m_it.object_iterator);\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_data.m_value.array = create<array_t>(first.m_it.array_iterator,\n                                                       last.m_it.array_iterator);\n                break;\n            }\n\n            case value_t::binary:\n            {\n                m_data.m_value = *first.m_object->m_data.m_value.binary;\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                JSON_THROW(invalid_iterator::create(206, detail::concat(\"cannot construct with iterators from \", first.m_object->type_name()), first.m_object));\n        }\n\n        set_parents();\n        assert_invariant();\n    }\n\n    ///////////////////////////////////////\n    // other constructors and destructor //\n    ///////////////////////////////////////\n\n    template<typename JsonRef,\n             detail::enable_if_t<detail::conjunction<detail::is_json_ref<JsonRef>,\n                                 std::is_same<typename JsonRef::value_type, basic_json>>::value, int> = 0 >\n    basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {}\n\n    /// @brief copy constructor\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(const basic_json& other)\n        : json_base_class_t(other)\n    {\n        m_data.m_type = other.m_data.m_type;\n        // check of passed value is valid\n        other.assert_invariant();\n\n        switch (m_data.m_type)\n        {\n            case value_t::object:\n            {\n                m_data.m_value = *other.m_data.m_value.object;\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_data.m_value = *other.m_data.m_value.array;\n                break;\n            }\n\n            case value_t::string:\n            {\n                m_data.m_value = *other.m_data.m_value.string;\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                m_data.m_value = other.m_data.m_value.boolean;\n                break;\n            }\n\n            case value_t::number_integer:\n            {\n                m_data.m_value = other.m_data.m_value.number_integer;\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                m_data.m_value = other.m_data.m_value.number_unsigned;\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                m_data.m_value = other.m_data.m_value.number_float;\n                break;\n            }\n\n            case value_t::binary:\n            {\n                m_data.m_value = *other.m_data.m_value.binary;\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                break;\n        }\n\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief move constructor\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(basic_json&& other) noexcept\n        : json_base_class_t(std::forward<json_base_class_t>(other)),\n          m_data(std::move(other.m_data))\n    {\n        // check that passed value is valid\n        other.assert_invariant(false);\n\n        // invalidate payload\n        other.m_data.m_type = value_t::null;\n        other.m_data.m_value = {};\n\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief copy assignment\n    /// @sa https://json.nlohmann.me/api/basic_json/operator=/\n    basic_json& operator=(basic_json other) noexcept (\n        std::is_nothrow_move_constructible<value_t>::value&&\n        std::is_nothrow_move_assignable<value_t>::value&&\n        std::is_nothrow_move_constructible<json_value>::value&&\n        std::is_nothrow_move_assignable<json_value>::value&&\n        std::is_nothrow_move_assignable<json_base_class_t>::value\n    )\n    {\n        // check that passed value is valid\n        other.assert_invariant();\n\n        using std::swap;\n        swap(m_data.m_type, other.m_data.m_type);\n        swap(m_data.m_value, other.m_data.m_value);\n        json_base_class_t::operator=(std::move(other));\n\n        set_parents();\n        assert_invariant();\n        return *this;\n    }\n\n    /// @brief destructor\n    /// @sa https://json.nlohmann.me/api/basic_json/~basic_json/\n    ~basic_json() noexcept\n    {\n        assert_invariant(false);\n    }\n\n    /// @}\n\n  public:\n    ///////////////////////\n    // object inspection //\n    ///////////////////////\n\n    /// @name object inspection\n    /// Functions to inspect the type of a JSON value.\n    /// @{\n\n    /// @brief serialization\n    /// @sa https://json.nlohmann.me/api/basic_json/dump/\n    string_t dump(const int indent = -1,\n                  const char indent_char = ' ',\n                  const bool ensure_ascii = false,\n                  const error_handler_t error_handler = error_handler_t::strict) const\n    {\n        string_t result;\n        serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler);\n\n        if (indent >= 0)\n        {\n            s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));\n        }\n        else\n        {\n            s.dump(*this, false, ensure_ascii, 0);\n        }\n\n        return result;\n    }\n\n    /// @brief return the type of the JSON value (explicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/type/\n    constexpr value_t type() const noexcept\n    {\n        return m_data.m_type;\n    }\n\n    /// @brief return whether type is primitive\n    /// @sa https://json.nlohmann.me/api/basic_json/is_primitive/\n    constexpr bool is_primitive() const noexcept\n    {\n        return is_null() || is_string() || is_boolean() || is_number() || is_binary();\n    }\n\n    /// @brief return whether type is structured\n    /// @sa https://json.nlohmann.me/api/basic_json/is_structured/\n    constexpr bool is_structured() const noexcept\n    {\n        return is_array() || is_object();\n    }\n\n    /// @brief return whether value is null\n    /// @sa https://json.nlohmann.me/api/basic_json/is_null/\n    constexpr bool is_null() const noexcept\n    {\n        return m_data.m_type == value_t::null;\n    }\n\n    /// @brief return whether value is a boolean\n    /// @sa https://json.nlohmann.me/api/basic_json/is_boolean/\n    constexpr bool is_boolean() const noexcept\n    {\n        return m_data.m_type == value_t::boolean;\n    }\n\n    /// @brief return whether value is a number\n    /// @sa https://json.nlohmann.me/api/basic_json/is_number/\n    constexpr bool is_number() const noexcept\n    {\n        return is_number_integer() || is_number_float();\n    }\n\n    /// @brief return whether value is an integer number\n    /// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/\n    constexpr bool is_number_integer() const noexcept\n    {\n        return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;\n    }\n\n    /// @brief return whether value is an unsigned integer number\n    /// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/\n    constexpr bool is_number_unsigned() const noexcept\n    {\n        return m_data.m_type == value_t::number_unsigned;\n    }\n\n    /// @brief return whether value is a floating-point number\n    /// @sa https://json.nlohmann.me/api/basic_json/is_number_float/\n    constexpr bool is_number_float() const noexcept\n    {\n        return m_data.m_type == value_t::number_float;\n    }\n\n    /// @brief return whether value is an object\n    /// @sa https://json.nlohmann.me/api/basic_json/is_object/\n    constexpr bool is_object() const noexcept\n    {\n        return m_data.m_type == value_t::object;\n    }\n\n    /// @brief return whether value is an array\n    /// @sa https://json.nlohmann.me/api/basic_json/is_array/\n    constexpr bool is_array() const noexcept\n    {\n        return m_data.m_type == value_t::array;\n    }\n\n    /// @brief return whether value is a string\n    /// @sa https://json.nlohmann.me/api/basic_json/is_string/\n    constexpr bool is_string() const noexcept\n    {\n        return m_data.m_type == value_t::string;\n    }\n\n    /// @brief return whether value is a binary array\n    /// @sa https://json.nlohmann.me/api/basic_json/is_binary/\n    constexpr bool is_binary() const noexcept\n    {\n        return m_data.m_type == value_t::binary;\n    }\n\n    /// @brief return whether value is discarded\n    /// @sa https://json.nlohmann.me/api/basic_json/is_discarded/\n    constexpr bool is_discarded() const noexcept\n    {\n        return m_data.m_type == value_t::discarded;\n    }\n\n    /// @brief return the type of the JSON value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_value_t/\n    constexpr operator value_t() const noexcept\n    {\n        return m_data.m_type;\n    }\n\n    /// @}\n\n  private:\n    //////////////////\n    // value access //\n    //////////////////\n\n    /// get a boolean (explicit)\n    boolean_t get_impl(boolean_t* /*unused*/) const\n    {\n        if (JSON_HEDLEY_LIKELY(is_boolean()))\n        {\n            return m_data.m_value.boolean;\n        }\n\n        JSON_THROW(type_error::create(302, detail::concat(\"type must be boolean, but is \", type_name()), this));\n    }\n\n    /// get a pointer to the value (object)\n    object_t* get_impl_ptr(object_t* /*unused*/) noexcept\n    {\n        return is_object() ? m_data.m_value.object : nullptr;\n    }\n\n    /// get a pointer to the value (object)\n    constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept\n    {\n        return is_object() ? m_data.m_value.object : nullptr;\n    }\n\n    /// get a pointer to the value (array)\n    array_t* get_impl_ptr(array_t* /*unused*/) noexcept\n    {\n        return is_array() ? m_data.m_value.array : nullptr;\n    }\n\n    /// get a pointer to the value (array)\n    constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept\n    {\n        return is_array() ? m_data.m_value.array : nullptr;\n    }\n\n    /// get a pointer to the value (string)\n    string_t* get_impl_ptr(string_t* /*unused*/) noexcept\n    {\n        return is_string() ? m_data.m_value.string : nullptr;\n    }\n\n    /// get a pointer to the value (string)\n    constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept\n    {\n        return is_string() ? m_data.m_value.string : nullptr;\n    }\n\n    /// get a pointer to the value (boolean)\n    boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept\n    {\n        return is_boolean() ? &m_data.m_value.boolean : nullptr;\n    }\n\n    /// get a pointer to the value (boolean)\n    constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept\n    {\n        return is_boolean() ? &m_data.m_value.boolean : nullptr;\n    }\n\n    /// get a pointer to the value (integer number)\n    number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept\n    {\n        return is_number_integer() ? &m_data.m_value.number_integer : nullptr;\n    }\n\n    /// get a pointer to the value (integer number)\n    constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept\n    {\n        return is_number_integer() ? &m_data.m_value.number_integer : nullptr;\n    }\n\n    /// get a pointer to the value (unsigned number)\n    number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept\n    {\n        return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;\n    }\n\n    /// get a pointer to the value (unsigned number)\n    constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept\n    {\n        return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;\n    }\n\n    /// get a pointer to the value (floating-point number)\n    number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept\n    {\n        return is_number_float() ? &m_data.m_value.number_float : nullptr;\n    }\n\n    /// get a pointer to the value (floating-point number)\n    constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept\n    {\n        return is_number_float() ? &m_data.m_value.number_float : nullptr;\n    }\n\n    /// get a pointer to the value (binary)\n    binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept\n    {\n        return is_binary() ? m_data.m_value.binary : nullptr;\n    }\n\n    /// get a pointer to the value (binary)\n    constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept\n    {\n        return is_binary() ? m_data.m_value.binary : nullptr;\n    }\n\n    /*!\n    @brief helper function to implement get_ref()\n\n    This function helps to implement get_ref() without code duplication for\n    const and non-const overloads\n\n    @tparam ThisType will be deduced as `basic_json` or `const basic_json`\n\n    @throw type_error.303 if ReferenceType does not match underlying value\n    type of the current JSON\n    */\n    template<typename ReferenceType, typename ThisType>\n    static ReferenceType get_ref_impl(ThisType& obj)\n    {\n        // delegate the call to get_ptr<>()\n        auto* ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>();\n\n        if (JSON_HEDLEY_LIKELY(ptr != nullptr))\n        {\n            return *ptr;\n        }\n\n        JSON_THROW(type_error::create(303, detail::concat(\"incompatible ReferenceType for get_ref, actual type is \", obj.type_name()), &obj));\n    }\n\n  public:\n    /// @name value access\n    /// Direct access to the stored value of a JSON value.\n    /// @{\n\n    /// @brief get a pointer value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/\n    template<typename PointerType, typename std::enable_if<\n                 std::is_pointer<PointerType>::value, int>::type = 0>\n    auto get_ptr() noexcept -> decltype(std::declval<basic_json_t&>().get_impl_ptr(std::declval<PointerType>()))\n    {\n        // delegate the call to get_impl_ptr<>()\n        return get_impl_ptr(static_cast<PointerType>(nullptr));\n    }\n\n    /// @brief get a pointer value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/\n    template < typename PointerType, typename std::enable_if <\n                   std::is_pointer<PointerType>::value&&\n                   std::is_const<typename std::remove_pointer<PointerType>::type>::value, int >::type = 0 >\n    constexpr auto get_ptr() const noexcept -> decltype(std::declval<const basic_json_t&>().get_impl_ptr(std::declval<PointerType>()))\n    {\n        // delegate the call to get_impl_ptr<>() const\n        return get_impl_ptr(static_cast<PointerType>(nullptr));\n    }\n\n  private:\n    /*!\n    @brief get a value (explicit)\n\n    Explicit type conversion between the JSON value and a compatible value\n    which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)\n    and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).\n    The value is converted by calling the @ref json_serializer<ValueType>\n    `from_json()` method.\n\n    The function is equivalent to executing\n    @code {.cpp}\n    ValueType ret;\n    JSONSerializer<ValueType>::from_json(*this, ret);\n    return ret;\n    @endcode\n\n    This overloads is chosen if:\n    - @a ValueType is not @ref basic_json,\n    - @ref json_serializer<ValueType> has a `from_json()` method of the form\n      `void from_json(const basic_json&, ValueType&)`, and\n    - @ref json_serializer<ValueType> does not have a `from_json()` method of\n      the form `ValueType from_json(const basic_json&)`\n\n    @tparam ValueType the returned value type\n\n    @return copy of the JSON value, converted to @a ValueType\n\n    @throw what @ref json_serializer<ValueType> `from_json()` method throws\n\n    @liveexample{The example below shows several conversions from JSON values\n    to other types. There a few things to note: (1) Floating-point numbers can\n    be converted to integers\\, (2) A JSON array can be converted to a standard\n    `std::vector<short>`\\, (3) A JSON object can be converted to C++\n    associative containers such as `std::unordered_map<std::string\\,\n    json>`.,get__ValueType_const}\n\n    @since version 2.1.0\n    */\n    template < typename ValueType,\n               detail::enable_if_t <\n                   detail::is_default_constructible<ValueType>::value&&\n                   detail::has_from_json<basic_json_t, ValueType>::value,\n                   int > = 0 >\n    ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept(\n                JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))\n    {\n        auto ret = ValueType();\n        JSONSerializer<ValueType>::from_json(*this, ret);\n        return ret;\n    }\n\n    /*!\n    @brief get a value (explicit); special case\n\n    Explicit type conversion between the JSON value and a compatible value\n    which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)\n    and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).\n    The value is converted by calling the @ref json_serializer<ValueType>\n    `from_json()` method.\n\n    The function is equivalent to executing\n    @code {.cpp}\n    return JSONSerializer<ValueType>::from_json(*this);\n    @endcode\n\n    This overloads is chosen if:\n    - @a ValueType is not @ref basic_json and\n    - @ref json_serializer<ValueType> has a `from_json()` method of the form\n      `ValueType from_json(const basic_json&)`\n\n    @note If @ref json_serializer<ValueType> has both overloads of\n    `from_json()`, this one is chosen.\n\n    @tparam ValueType the returned value type\n\n    @return copy of the JSON value, converted to @a ValueType\n\n    @throw what @ref json_serializer<ValueType> `from_json()` method throws\n\n    @since version 2.1.0\n    */\n    template < typename ValueType,\n               detail::enable_if_t <\n                   detail::has_non_default_from_json<basic_json_t, ValueType>::value,\n                   int > = 0 >\n    ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept(\n                JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>())))\n    {\n        return JSONSerializer<ValueType>::from_json(*this);\n    }\n\n    /*!\n    @brief get special-case overload\n\n    This overloads converts the current @ref basic_json in a different\n    @ref basic_json type\n\n    @tparam BasicJsonType == @ref basic_json\n\n    @return a copy of *this, converted into @a BasicJsonType\n\n    @complexity Depending on the implementation of the called `from_json()`\n                method.\n\n    @since version 3.2.0\n    */\n    template < typename BasicJsonType,\n               detail::enable_if_t <\n                   detail::is_basic_json<BasicJsonType>::value,\n                   int > = 0 >\n    BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const\n    {\n        return *this;\n    }\n\n    /*!\n    @brief get special-case overload\n\n    This overloads avoids a lot of template boilerplate, it can be seen as the\n    identity method\n\n    @tparam BasicJsonType == @ref basic_json\n\n    @return a copy of *this\n\n    @complexity Constant.\n\n    @since version 2.1.0\n    */\n    template<typename BasicJsonType,\n             detail::enable_if_t<\n                 std::is_same<BasicJsonType, basic_json_t>::value,\n                 int> = 0>\n    basic_json get_impl(detail::priority_tag<3> /*unused*/) const\n    {\n        return *this;\n    }\n\n    /*!\n    @brief get a pointer value (explicit)\n    @copydoc get()\n    */\n    template<typename PointerType,\n             detail::enable_if_t<\n                 std::is_pointer<PointerType>::value,\n                 int> = 0>\n    constexpr auto get_impl(detail::priority_tag<4> /*unused*/) const noexcept\n    -> decltype(std::declval<const basic_json_t&>().template get_ptr<PointerType>())\n    {\n        // delegate the call to get_ptr\n        return get_ptr<PointerType>();\n    }\n\n  public:\n    /*!\n    @brief get a (pointer) value (explicit)\n\n    Performs explicit type conversion between the JSON value and a compatible value if required.\n\n    - If the requested type is a pointer to the internally stored JSON value that pointer is returned.\n    No copies are made.\n\n    - If the requested type is the current @ref basic_json, or a different @ref basic_json convertible\n    from the current @ref basic_json.\n\n    - Otherwise the value is converted by calling the @ref json_serializer<ValueType> `from_json()`\n    method.\n\n    @tparam ValueTypeCV the provided value type\n    @tparam ValueType the returned value type\n\n    @return copy of the JSON value, converted to @tparam ValueType if necessary\n\n    @throw what @ref json_serializer<ValueType> `from_json()` method throws if conversion is required\n\n    @since version 2.1.0\n    */\n    template < typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>>\n#if defined(JSON_HAS_CPP_14)\n    constexpr\n#endif\n    auto get() const noexcept(\n    noexcept(std::declval<const basic_json_t&>().template get_impl<ValueType>(detail::priority_tag<4> {})))\n    -> decltype(std::declval<const basic_json_t&>().template get_impl<ValueType>(detail::priority_tag<4> {}))\n    {\n        // we cannot static_assert on ValueTypeCV being non-const, because\n        // there is support for get<const basic_json_t>(), which is why we\n        // still need the uncvref\n        static_assert(!std::is_reference<ValueTypeCV>::value,\n                      \"get() cannot be used with reference types, you might want to use get_ref()\");\n        return get_impl<ValueType>(detail::priority_tag<4> {});\n    }\n\n    /*!\n    @brief get a pointer value (explicit)\n\n    Explicit pointer access to the internally stored JSON value. No copies are\n    made.\n\n    @warning The pointer becomes invalid if the underlying JSON object\n    changes.\n\n    @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref\n    object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,\n    @ref number_unsigned_t, or @ref number_float_t.\n\n    @return pointer to the internally stored JSON value if the requested\n    pointer type @a PointerType fits to the JSON value; `nullptr` otherwise\n\n    @complexity Constant.\n\n    @liveexample{The example below shows how pointers to internal values of a\n    JSON value can be requested. Note that no type conversions are made and a\n    `nullptr` is returned if the value and the requested pointer type does not\n    match.,get__PointerType}\n\n    @sa see @ref get_ptr() for explicit pointer-member access\n\n    @since version 1.0.0\n    */\n    template<typename PointerType, typename std::enable_if<\n                 std::is_pointer<PointerType>::value, int>::type = 0>\n    auto get() noexcept -> decltype(std::declval<basic_json_t&>().template get_ptr<PointerType>())\n    {\n        // delegate the call to get_ptr\n        return get_ptr<PointerType>();\n    }\n\n    /// @brief get a value (explicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_to/\n    template < typename ValueType,\n               detail::enable_if_t <\n                   !detail::is_basic_json<ValueType>::value&&\n                   detail::has_from_json<basic_json_t, ValueType>::value,\n                   int > = 0 >\n    ValueType & get_to(ValueType& v) const noexcept(noexcept(\n                JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))\n    {\n        JSONSerializer<ValueType>::from_json(*this, v);\n        return v;\n    }\n\n    // specialization to allow calling get_to with a basic_json value\n    // see https://github.com/nlohmann/json/issues/2175\n    template<typename ValueType,\n             detail::enable_if_t <\n                 detail::is_basic_json<ValueType>::value,\n                 int> = 0>\n    ValueType & get_to(ValueType& v) const\n    {\n        v = *this;\n        return v;\n    }\n\n    template <\n        typename T, std::size_t N,\n        typename Array = T (&)[N], // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n        detail::enable_if_t <\n            detail::has_from_json<basic_json_t, Array>::value, int > = 0 >\n    Array get_to(T (&v)[N]) const // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n    noexcept(noexcept(JSONSerializer<Array>::from_json(\n                          std::declval<const basic_json_t&>(), v)))\n    {\n        JSONSerializer<Array>::from_json(*this, v);\n        return v;\n    }\n\n    /// @brief get a reference value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_ref/\n    template<typename ReferenceType, typename std::enable_if<\n                 std::is_reference<ReferenceType>::value, int>::type = 0>\n    ReferenceType get_ref()\n    {\n        // delegate call to get_ref_impl\n        return get_ref_impl<ReferenceType>(*this);\n    }\n\n    /// @brief get a reference value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_ref/\n    template < typename ReferenceType, typename std::enable_if <\n                   std::is_reference<ReferenceType>::value&&\n                   std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int >::type = 0 >\n    ReferenceType get_ref() const\n    {\n        // delegate call to get_ref_impl\n        return get_ref_impl<ReferenceType>(*this);\n    }\n\n    /*!\n    @brief get a value (implicit)\n\n    Implicit type conversion between the JSON value and a compatible value.\n    The call is realized by calling @ref get() const.\n\n    @tparam ValueType non-pointer type compatible to the JSON value, for\n    instance `int` for JSON integer numbers, `bool` for JSON booleans, or\n    `std::vector` types for JSON arrays. The character type of @ref string_t\n    as well as an initializer list of this type is excluded to avoid\n    ambiguities as these types implicitly convert to `std::string`.\n\n    @return copy of the JSON value, converted to type @a ValueType\n\n    @throw type_error.302 in case passed type @a ValueType is incompatible\n    to the JSON value type (e.g., the JSON value is of type boolean, but a\n    string is requested); see example below\n\n    @complexity Linear in the size of the JSON value.\n\n    @liveexample{The example below shows several conversions from JSON values\n    to other types. There a few things to note: (1) Floating-point numbers can\n    be converted to integers\\, (2) A JSON array can be converted to a standard\n    `std::vector<short>`\\, (3) A JSON object can be converted to C++\n    associative containers such as `std::unordered_map<std::string\\,\n    json>`.,operator__ValueType}\n\n    @since version 1.0.0\n    */\n    template < typename ValueType, typename std::enable_if <\n                   detail::conjunction <\n                       detail::negation<std::is_pointer<ValueType>>,\n                       detail::negation<std::is_same<ValueType, std::nullptr_t>>,\n                       detail::negation<std::is_same<ValueType, detail::json_ref<basic_json>>>,\n                                        detail::negation<std::is_same<ValueType, typename string_t::value_type>>,\n                                        detail::negation<detail::is_basic_json<ValueType>>,\n                                        detail::negation<std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>>,\n#if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914))\n                                                detail::negation<std::is_same<ValueType, std::string_view>>,\n#endif\n#if defined(JSON_HAS_CPP_17) && JSON_HAS_STATIC_RTTI\n                                                detail::negation<std::is_same<ValueType, std::any>>,\n#endif\n                                                detail::is_detected_lazy<detail::get_template_function, const basic_json_t&, ValueType>\n                                                >::value, int >::type = 0 >\n                                        JSON_EXPLICIT operator ValueType() const\n    {\n        // delegate the call to get<>() const\n        return get<ValueType>();\n    }\n\n    /// @brief get a binary value\n    /// @sa https://json.nlohmann.me/api/basic_json/get_binary/\n    binary_t& get_binary()\n    {\n        if (!is_binary())\n        {\n            JSON_THROW(type_error::create(302, detail::concat(\"type must be binary, but is \", type_name()), this));\n        }\n\n        return *get_ptr<binary_t*>();\n    }\n\n    /// @brief get a binary value\n    /// @sa https://json.nlohmann.me/api/basic_json/get_binary/\n    const binary_t& get_binary() const\n    {\n        if (!is_binary())\n        {\n            JSON_THROW(type_error::create(302, detail::concat(\"type must be binary, but is \", type_name()), this));\n        }\n\n        return *get_ptr<const binary_t*>();\n    }\n\n    /// @}\n\n    ////////////////////\n    // element access //\n    ////////////////////\n\n    /// @name element access\n    /// Access to the JSON value.\n    /// @{\n\n    /// @brief access specified array element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    reference at(size_type idx)\n    {\n        // at only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            JSON_TRY\n            {\n                return set_parent(m_data.m_value.array->at(idx));\n            }\n            JSON_CATCH (std::out_of_range&)\n            {\n                // create better exception explanation\n                JSON_THROW(out_of_range::create(401, detail::concat(\"array index \", std::to_string(idx), \" is out of range\"), this));\n            }\n        }\n        else\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n    }\n\n    /// @brief access specified array element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    const_reference at(size_type idx) const\n    {\n        // at only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            JSON_TRY\n            {\n                return m_data.m_value.array->at(idx);\n            }\n            JSON_CATCH (std::out_of_range&)\n            {\n                // create better exception explanation\n                JSON_THROW(out_of_range::create(401, detail::concat(\"array index \", std::to_string(idx), \" is out of range\"), this));\n            }\n        }\n        else\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n    }\n\n    /// @brief access specified object element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    reference at(const typename object_t::key_type& key)\n    {\n        // at only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n\n        auto it = m_data.m_value.object->find(key);\n        if (it == m_data.m_value.object->end())\n        {\n            JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", key, \"' not found\"), this));\n        }\n        return set_parent(it->second);\n    }\n\n    /// @brief access specified object element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    reference at(KeyType && key)\n    {\n        // at only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n\n        auto it = m_data.m_value.object->find(std::forward<KeyType>(key));\n        if (it == m_data.m_value.object->end())\n        {\n            JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", string_t(std::forward<KeyType>(key)), \"' not found\"), this));\n        }\n        return set_parent(it->second);\n    }\n\n    /// @brief access specified object element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    const_reference at(const typename object_t::key_type& key) const\n    {\n        // at only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n\n        auto it = m_data.m_value.object->find(key);\n        if (it == m_data.m_value.object->end())\n        {\n            JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", key, \"' not found\"), this));\n        }\n        return it->second;\n    }\n\n    /// @brief access specified object element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    const_reference at(KeyType && key) const\n    {\n        // at only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n\n        auto it = m_data.m_value.object->find(std::forward<KeyType>(key));\n        if (it == m_data.m_value.object->end())\n        {\n            JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", string_t(std::forward<KeyType>(key)), \"' not found\"), this));\n        }\n        return it->second;\n    }\n\n    /// @brief access specified array element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    reference operator[](size_type idx)\n    {\n        // implicitly convert null value to an empty array\n        if (is_null())\n        {\n            m_data.m_type = value_t::array;\n            m_data.m_value.array = create<array_t>();\n            assert_invariant();\n        }\n\n        // operator[] only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            // fill up array with null values if given idx is outside range\n            if (idx >= m_data.m_value.array->size())\n            {\n#if JSON_DIAGNOSTICS\n                // remember array size & capacity before resizing\n                const auto old_size = m_data.m_value.array->size();\n                const auto old_capacity = m_data.m_value.array->capacity();\n#endif\n                m_data.m_value.array->resize(idx + 1);\n\n#if JSON_DIAGNOSTICS\n                if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))\n                {\n                    // capacity has changed: update all parents\n                    set_parents();\n                }\n                else\n                {\n                    // set parent for values added above\n                    set_parents(begin() + static_cast<typename iterator::difference_type>(old_size), static_cast<typename iterator::difference_type>(idx + 1 - old_size));\n                }\n#endif\n                assert_invariant();\n            }\n\n            return m_data.m_value.array->operator[](idx);\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a numeric argument with \", type_name()), this));\n    }\n\n    /// @brief access specified array element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    const_reference operator[](size_type idx) const\n    {\n        // const operator[] only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            return m_data.m_value.array->operator[](idx);\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a numeric argument with \", type_name()), this));\n    }\n\n    /// @brief access specified object element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    reference operator[](typename object_t::key_type key)\n    {\n        // implicitly convert null value to an empty object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value.object = create<object_t>();\n            assert_invariant();\n        }\n\n        // operator[] only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            auto result = m_data.m_value.object->emplace(std::move(key), nullptr);\n            return set_parent(result.first->second);\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a string argument with \", type_name()), this));\n    }\n\n    /// @brief access specified object element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    const_reference operator[](const typename object_t::key_type& key) const\n    {\n        // const operator[] only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            auto it = m_data.m_value.object->find(key);\n            JSON_ASSERT(it != m_data.m_value.object->end());\n            return it->second;\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a string argument with \", type_name()), this));\n    }\n\n    // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC\n    // (they seemingly cannot be constrained to resolve the ambiguity)\n    template<typename T>\n    reference operator[](T* key)\n    {\n        return operator[](typename object_t::key_type(key));\n    }\n\n    template<typename T>\n    const_reference operator[](T* key) const\n    {\n        return operator[](typename object_t::key_type(key));\n    }\n\n    /// @brief access specified object element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int > = 0 >\n    reference operator[](KeyType && key)\n    {\n        // implicitly convert null value to an empty object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value.object = create<object_t>();\n            assert_invariant();\n        }\n\n        // operator[] only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            auto result = m_data.m_value.object->emplace(std::forward<KeyType>(key), nullptr);\n            return set_parent(result.first->second);\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a string argument with \", type_name()), this));\n    }\n\n    /// @brief access specified object element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int > = 0 >\n    const_reference operator[](KeyType && key) const\n    {\n        // const operator[] only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            auto it = m_data.m_value.object->find(std::forward<KeyType>(key));\n            JSON_ASSERT(it != m_data.m_value.object->end());\n            return it->second;\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a string argument with \", type_name()), this));\n    }\n\n  private:\n    template<typename KeyType>\n    using is_comparable_with_object_key = detail::is_comparable <\n        object_comparator_t, const typename object_t::key_type&, KeyType >;\n\n    template<typename ValueType>\n    using value_return_type = std::conditional <\n        detail::is_c_string_uncvref<ValueType>::value,\n        string_t, typename std::decay<ValueType>::type >;\n\n  public:\n    /// @brief access specified object element with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, detail::enable_if_t <\n                   !detail::is_transparent<object_comparator_t>::value\n                   && detail::is_getable<basic_json_t, ValueType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if key is found, return value and given default value otherwise\n            const auto it = find(key);\n            if (it != end())\n            {\n                return it->template get<ValueType>();\n            }\n\n            return default_value;\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, class ReturnType = typename value_return_type<ValueType>::type,\n               detail::enable_if_t <\n                   !detail::is_transparent<object_comparator_t>::value\n                   && detail::is_getable<basic_json_t, ReturnType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ReturnType value(const typename object_t::key_type& key, ValueType && default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if key is found, return value and given default value otherwise\n            const auto it = find(key);\n            if (it != end())\n            {\n                return it->template get<ReturnType>();\n            }\n\n            return std::forward<ValueType>(default_value);\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, class KeyType, detail::enable_if_t <\n                   detail::is_transparent<object_comparator_t>::value\n                   && !detail::is_json_pointer<KeyType>::value\n                   && is_comparable_with_object_key<KeyType>::value\n                   && detail::is_getable<basic_json_t, ValueType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ValueType value(KeyType && key, const ValueType& default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if key is found, return value and given default value otherwise\n            const auto it = find(std::forward<KeyType>(key));\n            if (it != end())\n            {\n                return it->template get<ValueType>();\n            }\n\n            return default_value;\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element via JSON Pointer with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, class KeyType, class ReturnType = typename value_return_type<ValueType>::type,\n               detail::enable_if_t <\n                   detail::is_transparent<object_comparator_t>::value\n                   && !detail::is_json_pointer<KeyType>::value\n                   && is_comparable_with_object_key<KeyType>::value\n                   && detail::is_getable<basic_json_t, ReturnType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ReturnType value(KeyType && key, ValueType && default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if key is found, return value and given default value otherwise\n            const auto it = find(std::forward<KeyType>(key));\n            if (it != end())\n            {\n                return it->template get<ReturnType>();\n            }\n\n            return std::forward<ValueType>(default_value);\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element via JSON Pointer with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, detail::enable_if_t <\n                   detail::is_getable<basic_json_t, ValueType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ValueType value(const json_pointer& ptr, const ValueType& default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if pointer resolves a value, return it or use default value\n            JSON_TRY\n            {\n                return ptr.get_checked(this).template get<ValueType>();\n            }\n            JSON_INTERNAL_CATCH (out_of_range&)\n            {\n                return default_value;\n            }\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element via JSON Pointer with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, class ReturnType = typename value_return_type<ValueType>::type,\n               detail::enable_if_t <\n                   detail::is_getable<basic_json_t, ReturnType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ReturnType value(const json_pointer& ptr, ValueType && default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if pointer resolves a value, return it or use default value\n            JSON_TRY\n            {\n                return ptr.get_checked(this).template get<ReturnType>();\n            }\n            JSON_INTERNAL_CATCH (out_of_range&)\n            {\n                return std::forward<ValueType>(default_value);\n            }\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    template < class ValueType, class BasicJsonType, detail::enable_if_t <\n                   detail::is_basic_json<BasicJsonType>::value\n                   && detail::is_getable<basic_json_t, ValueType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    ValueType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, const ValueType& default_value) const\n    {\n        return value(ptr.convert(), default_value);\n    }\n\n    template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type<ValueType>::type,\n               detail::enable_if_t <\n                   detail::is_basic_json<BasicJsonType>::value\n                   && detail::is_getable<basic_json_t, ReturnType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    ReturnType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, ValueType && default_value) const\n    {\n        return value(ptr.convert(), std::forward<ValueType>(default_value));\n    }\n\n    /// @brief access the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/front/\n    reference front()\n    {\n        return *begin();\n    }\n\n    /// @brief access the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/front/\n    const_reference front() const\n    {\n        return *cbegin();\n    }\n\n    /// @brief access the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/back/\n    reference back()\n    {\n        auto tmp = end();\n        --tmp;\n        return *tmp;\n    }\n\n    /// @brief access the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/back/\n    const_reference back() const\n    {\n        auto tmp = cend();\n        --tmp;\n        return *tmp;\n    }\n\n    /// @brief remove element given an iterator\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    template < class IteratorType, detail::enable_if_t <\n                   std::is_same<IteratorType, typename basic_json_t::iterator>::value ||\n                   std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int > = 0 >\n    IteratorType erase(IteratorType pos)\n    {\n        // make sure iterator fits the current value\n        if (JSON_HEDLEY_UNLIKELY(this != pos.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n        }\n\n        IteratorType result = end();\n\n        switch (m_data.m_type)\n        {\n            case value_t::boolean:\n            case value_t::number_float:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::string:\n            case value_t::binary:\n            {\n                if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin()))\n                {\n                    JSON_THROW(invalid_iterator::create(205, \"iterator out of range\", this));\n                }\n\n                if (is_string())\n                {\n                    AllocatorType<string_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.string);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.string, 1);\n                    m_data.m_value.string = nullptr;\n                }\n                else if (is_binary())\n                {\n                    AllocatorType<binary_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.binary);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.binary, 1);\n                    m_data.m_value.binary = nullptr;\n                }\n\n                m_data.m_type = value_t::null;\n                assert_invariant();\n                break;\n            }\n\n            case value_t::object:\n            {\n                result.m_it.object_iterator = m_data.m_value.object->erase(pos.m_it.object_iterator);\n                break;\n            }\n\n            case value_t::array:\n            {\n                result.m_it.array_iterator = m_data.m_value.array->erase(pos.m_it.array_iterator);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n\n        return result;\n    }\n\n    /// @brief remove elements given an iterator range\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    template < class IteratorType, detail::enable_if_t <\n                   std::is_same<IteratorType, typename basic_json_t::iterator>::value ||\n                   std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int > = 0 >\n    IteratorType erase(IteratorType first, IteratorType last)\n    {\n        // make sure iterator fits the current value\n        if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(203, \"iterators do not fit current value\", this));\n        }\n\n        IteratorType result = end();\n\n        switch (m_data.m_type)\n        {\n            case value_t::boolean:\n            case value_t::number_float:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::string:\n            case value_t::binary:\n            {\n                if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin()\n                                       || !last.m_it.primitive_iterator.is_end()))\n                {\n                    JSON_THROW(invalid_iterator::create(204, \"iterators out of range\", this));\n                }\n\n                if (is_string())\n                {\n                    AllocatorType<string_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.string);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.string, 1);\n                    m_data.m_value.string = nullptr;\n                }\n                else if (is_binary())\n                {\n                    AllocatorType<binary_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.binary);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.binary, 1);\n                    m_data.m_value.binary = nullptr;\n                }\n\n                m_data.m_type = value_t::null;\n                assert_invariant();\n                break;\n            }\n\n            case value_t::object:\n            {\n                result.m_it.object_iterator = m_data.m_value.object->erase(first.m_it.object_iterator,\n                                              last.m_it.object_iterator);\n                break;\n            }\n\n            case value_t::array:\n            {\n                result.m_it.array_iterator = m_data.m_value.array->erase(first.m_it.array_iterator,\n                                             last.m_it.array_iterator);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n\n        return result;\n    }\n\n  private:\n    template < typename KeyType, detail::enable_if_t <\n                   detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 >\n    size_type erase_internal(KeyType && key)\n    {\n        // this erase only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n\n        return m_data.m_value.object->erase(std::forward<KeyType>(key));\n    }\n\n    template < typename KeyType, detail::enable_if_t <\n                   !detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 >\n    size_type erase_internal(KeyType && key)\n    {\n        // this erase only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n\n        const auto it = m_data.m_value.object->find(std::forward<KeyType>(key));\n        if (it != m_data.m_value.object->end())\n        {\n            m_data.m_value.object->erase(it);\n            return 1;\n        }\n        return 0;\n    }\n\n  public:\n\n    /// @brief remove element from a JSON object given a key\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    size_type erase(const typename object_t::key_type& key)\n    {\n        // the indirection via erase_internal() is added to avoid making this\n        // function a template and thus de-rank it during overload resolution\n        return erase_internal(key);\n    }\n\n    /// @brief remove element from a JSON object given a key\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    size_type erase(KeyType && key)\n    {\n        return erase_internal(std::forward<KeyType>(key));\n    }\n\n    /// @brief remove element from a JSON array given an index\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    void erase(const size_type idx)\n    {\n        // this erase only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            if (JSON_HEDLEY_UNLIKELY(idx >= size()))\n            {\n                JSON_THROW(out_of_range::create(401, detail::concat(\"array index \", std::to_string(idx), \" is out of range\"), this));\n            }\n\n            m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast<difference_type>(idx));\n        }\n        else\n        {\n            JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n    }\n\n    /// @}\n\n    ////////////\n    // lookup //\n    ////////////\n\n    /// @name lookup\n    /// @{\n\n    /// @brief find an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/find/\n    iterator find(const typename object_t::key_type& key)\n    {\n        auto result = end();\n\n        if (is_object())\n        {\n            result.m_it.object_iterator = m_data.m_value.object->find(key);\n        }\n\n        return result;\n    }\n\n    /// @brief find an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/find/\n    const_iterator find(const typename object_t::key_type& key) const\n    {\n        auto result = cend();\n\n        if (is_object())\n        {\n            result.m_it.object_iterator = m_data.m_value.object->find(key);\n        }\n\n        return result;\n    }\n\n    /// @brief find an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/find/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    iterator find(KeyType && key)\n    {\n        auto result = end();\n\n        if (is_object())\n        {\n            result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));\n        }\n\n        return result;\n    }\n\n    /// @brief find an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/find/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    const_iterator find(KeyType && key) const\n    {\n        auto result = cend();\n\n        if (is_object())\n        {\n            result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));\n        }\n\n        return result;\n    }\n\n    /// @brief returns the number of occurrences of a key in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/count/\n    size_type count(const typename object_t::key_type& key) const\n    {\n        // return 0 for all nonobject types\n        return is_object() ? m_data.m_value.object->count(key) : 0;\n    }\n\n    /// @brief returns the number of occurrences of a key in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/count/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    size_type count(KeyType && key) const\n    {\n        // return 0 for all nonobject types\n        return is_object() ? m_data.m_value.object->count(std::forward<KeyType>(key)) : 0;\n    }\n\n    /// @brief check the existence of an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/contains/\n    bool contains(const typename object_t::key_type& key) const\n    {\n        return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end();\n    }\n\n    /// @brief check the existence of an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/contains/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    bool contains(KeyType && key) const\n    {\n        return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();\n    }\n\n    /// @brief check the existence of an element in a JSON object given a JSON pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/contains/\n    bool contains(const json_pointer& ptr) const\n    {\n        return ptr.contains(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const\n    {\n        return ptr.contains(this);\n    }\n\n    /// @}\n\n    ///////////////\n    // iterators //\n    ///////////////\n\n    /// @name iterators\n    /// @{\n\n    /// @brief returns an iterator to the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/begin/\n    iterator begin() noexcept\n    {\n        iterator result(this);\n        result.set_begin();\n        return result;\n    }\n\n    /// @brief returns an iterator to the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/begin/\n    const_iterator begin() const noexcept\n    {\n        return cbegin();\n    }\n\n    /// @brief returns a const iterator to the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/cbegin/\n    const_iterator cbegin() const noexcept\n    {\n        const_iterator result(this);\n        result.set_begin();\n        return result;\n    }\n\n    /// @brief returns an iterator to one past the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/end/\n    iterator end() noexcept\n    {\n        iterator result(this);\n        result.set_end();\n        return result;\n    }\n\n    /// @brief returns an iterator to one past the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/end/\n    const_iterator end() const noexcept\n    {\n        return cend();\n    }\n\n    /// @brief returns an iterator to one past the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/cend/\n    const_iterator cend() const noexcept\n    {\n        const_iterator result(this);\n        result.set_end();\n        return result;\n    }\n\n    /// @brief returns an iterator to the reverse-beginning\n    /// @sa https://json.nlohmann.me/api/basic_json/rbegin/\n    reverse_iterator rbegin() noexcept\n    {\n        return reverse_iterator(end());\n    }\n\n    /// @brief returns an iterator to the reverse-beginning\n    /// @sa https://json.nlohmann.me/api/basic_json/rbegin/\n    const_reverse_iterator rbegin() const noexcept\n    {\n        return crbegin();\n    }\n\n    /// @brief returns an iterator to the reverse-end\n    /// @sa https://json.nlohmann.me/api/basic_json/rend/\n    reverse_iterator rend() noexcept\n    {\n        return reverse_iterator(begin());\n    }\n\n    /// @brief returns an iterator to the reverse-end\n    /// @sa https://json.nlohmann.me/api/basic_json/rend/\n    const_reverse_iterator rend() const noexcept\n    {\n        return crend();\n    }\n\n    /// @brief returns a const reverse iterator to the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/crbegin/\n    const_reverse_iterator crbegin() const noexcept\n    {\n        return const_reverse_iterator(cend());\n    }\n\n    /// @brief returns a const reverse iterator to one before the first\n    /// @sa https://json.nlohmann.me/api/basic_json/crend/\n    const_reverse_iterator crend() const noexcept\n    {\n        return const_reverse_iterator(cbegin());\n    }\n\n  public:\n    /// @brief wrapper to access iterator member functions in range-based for\n    /// @sa https://json.nlohmann.me/api/basic_json/items/\n    /// @deprecated This function is deprecated since 3.1.0 and will be removed in\n    ///             version 4.0.0 of the library. Please use @ref items() instead;\n    ///             that is, replace `json::iterator_wrapper(j)` with `j.items()`.\n    JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())\n    static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept\n    {\n        return ref.items();\n    }\n\n    /// @brief wrapper to access iterator member functions in range-based for\n    /// @sa https://json.nlohmann.me/api/basic_json/items/\n    /// @deprecated This function is deprecated since 3.1.0 and will be removed in\n    ///         version 4.0.0 of the library. Please use @ref items() instead;\n    ///         that is, replace `json::iterator_wrapper(j)` with `j.items()`.\n    JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())\n    static iteration_proxy<const_iterator> iterator_wrapper(const_reference ref) noexcept\n    {\n        return ref.items();\n    }\n\n    /// @brief helper to access iterator member functions in range-based for\n    /// @sa https://json.nlohmann.me/api/basic_json/items/\n    iteration_proxy<iterator> items() noexcept\n    {\n        return iteration_proxy<iterator>(*this);\n    }\n\n    /// @brief helper to access iterator member functions in range-based for\n    /// @sa https://json.nlohmann.me/api/basic_json/items/\n    iteration_proxy<const_iterator> items() const noexcept\n    {\n        return iteration_proxy<const_iterator>(*this);\n    }\n\n    /// @}\n\n    //////////////\n    // capacity //\n    //////////////\n\n    /// @name capacity\n    /// @{\n\n    /// @brief checks whether the container is empty.\n    /// @sa https://json.nlohmann.me/api/basic_json/empty/\n    bool empty() const noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::null:\n            {\n                // null values are empty\n                return true;\n            }\n\n            case value_t::array:\n            {\n                // delegate call to array_t::empty()\n                return m_data.m_value.array->empty();\n            }\n\n            case value_t::object:\n            {\n                // delegate call to object_t::empty()\n                return m_data.m_value.object->empty();\n            }\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                // all other types are nonempty\n                return false;\n            }\n        }\n    }\n\n    /// @brief returns the number of elements\n    /// @sa https://json.nlohmann.me/api/basic_json/size/\n    size_type size() const noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::null:\n            {\n                // null values are empty\n                return 0;\n            }\n\n            case value_t::array:\n            {\n                // delegate call to array_t::size()\n                return m_data.m_value.array->size();\n            }\n\n            case value_t::object:\n            {\n                // delegate call to object_t::size()\n                return m_data.m_value.object->size();\n            }\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                // all other types have size 1\n                return 1;\n            }\n        }\n    }\n\n    /// @brief returns the maximum possible number of elements\n    /// @sa https://json.nlohmann.me/api/basic_json/max_size/\n    size_type max_size() const noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::array:\n            {\n                // delegate call to array_t::max_size()\n                return m_data.m_value.array->max_size();\n            }\n\n            case value_t::object:\n            {\n                // delegate call to object_t::max_size()\n                return m_data.m_value.object->max_size();\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                // all other types have max_size() == size()\n                return size();\n            }\n        }\n    }\n\n    /// @}\n\n    ///////////////\n    // modifiers //\n    ///////////////\n\n    /// @name modifiers\n    /// @{\n\n    /// @brief clears the contents\n    /// @sa https://json.nlohmann.me/api/basic_json/clear/\n    void clear() noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::number_integer:\n            {\n                m_data.m_value.number_integer = 0;\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                m_data.m_value.number_unsigned = 0;\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                m_data.m_value.number_float = 0.0;\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                m_data.m_value.boolean = false;\n                break;\n            }\n\n            case value_t::string:\n            {\n                m_data.m_value.string->clear();\n                break;\n            }\n\n            case value_t::binary:\n            {\n                m_data.m_value.binary->clear();\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_data.m_value.array->clear();\n                break;\n            }\n\n            case value_t::object:\n            {\n                m_data.m_value.object->clear();\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                break;\n        }\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n    void push_back(basic_json&& val)\n    {\n        // push_back only works for null objects or arrays\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))\n        {\n            JSON_THROW(type_error::create(308, detail::concat(\"cannot use push_back() with \", type_name()), this));\n        }\n\n        // transform null object into an array\n        if (is_null())\n        {\n            m_data.m_type = value_t::array;\n            m_data.m_value = value_t::array;\n            assert_invariant();\n        }\n\n        // add element to array (move semantics)\n        const auto old_capacity = m_data.m_value.array->capacity();\n        m_data.m_value.array->push_back(std::move(val));\n        set_parent(m_data.m_value.array->back(), old_capacity);\n        // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n    reference operator+=(basic_json&& val)\n    {\n        push_back(std::move(val));\n        return *this;\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n    void push_back(const basic_json& val)\n    {\n        // push_back only works for null objects or arrays\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))\n        {\n            JSON_THROW(type_error::create(308, detail::concat(\"cannot use push_back() with \", type_name()), this));\n        }\n\n        // transform null object into an array\n        if (is_null())\n        {\n            m_data.m_type = value_t::array;\n            m_data.m_value = value_t::array;\n            assert_invariant();\n        }\n\n        // add element to array\n        const auto old_capacity = m_data.m_value.array->capacity();\n        m_data.m_value.array->push_back(val);\n        set_parent(m_data.m_value.array->back(), old_capacity);\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n    reference operator+=(const basic_json& val)\n    {\n        push_back(val);\n        return *this;\n    }\n\n    /// @brief add an object to an object\n    /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n    void push_back(const typename object_t::value_type& val)\n    {\n        // push_back only works for null objects or objects\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object())))\n        {\n            JSON_THROW(type_error::create(308, detail::concat(\"cannot use push_back() with \", type_name()), this));\n        }\n\n        // transform null object into an object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value = value_t::object;\n            assert_invariant();\n        }\n\n        // add element to object\n        auto res = m_data.m_value.object->insert(val);\n        set_parent(res.first->second);\n    }\n\n    /// @brief add an object to an object\n    /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n    reference operator+=(const typename object_t::value_type& val)\n    {\n        push_back(val);\n        return *this;\n    }\n\n    /// @brief add an object to an object\n    /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n    void push_back(initializer_list_t init)\n    {\n        if (is_object() && init.size() == 2 && (*init.begin())->is_string())\n        {\n            basic_json&& key = init.begin()->moved_or_copied();\n            push_back(typename object_t::value_type(\n                          std::move(key.get_ref<string_t&>()), (init.begin() + 1)->moved_or_copied()));\n        }\n        else\n        {\n            push_back(basic_json(init));\n        }\n    }\n\n    /// @brief add an object to an object\n    /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n    reference operator+=(initializer_list_t init)\n    {\n        push_back(init);\n        return *this;\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/emplace_back/\n    template<class... Args>\n    reference emplace_back(Args&& ... args)\n    {\n        // emplace_back only works for null objects or arrays\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))\n        {\n            JSON_THROW(type_error::create(311, detail::concat(\"cannot use emplace_back() with \", type_name()), this));\n        }\n\n        // transform null object into an array\n        if (is_null())\n        {\n            m_data.m_type = value_t::array;\n            m_data.m_value = value_t::array;\n            assert_invariant();\n        }\n\n        // add element to array (perfect forwarding)\n        const auto old_capacity = m_data.m_value.array->capacity();\n        m_data.m_value.array->emplace_back(std::forward<Args>(args)...);\n        return set_parent(m_data.m_value.array->back(), old_capacity);\n    }\n\n    /// @brief add an object to an object if key does not exist\n    /// @sa https://json.nlohmann.me/api/basic_json/emplace/\n    template<class... Args>\n    std::pair<iterator, bool> emplace(Args&& ... args)\n    {\n        // emplace only works for null objects or arrays\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object())))\n        {\n            JSON_THROW(type_error::create(311, detail::concat(\"cannot use emplace() with \", type_name()), this));\n        }\n\n        // transform null object into an object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value = value_t::object;\n            assert_invariant();\n        }\n\n        // add element to array (perfect forwarding)\n        auto res = m_data.m_value.object->emplace(std::forward<Args>(args)...);\n        set_parent(res.first->second);\n\n        // create result iterator and set iterator to the result of emplace\n        auto it = begin();\n        it.m_it.object_iterator = res.first;\n\n        // return pair of iterator and boolean\n        return {it, res.second};\n    }\n\n    /// Helper for insertion of an iterator\n    /// @note: This uses std::distance to support GCC 4.8,\n    ///        see https://github.com/nlohmann/json/pull/1257\n    template<typename... Args>\n    iterator insert_iterator(const_iterator pos, Args&& ... args)\n    {\n        iterator result(this);\n        JSON_ASSERT(m_data.m_value.array != nullptr);\n\n        auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);\n        m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);\n        result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;\n\n        // This could have been written as:\n        // result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);\n        // but the return value of insert is missing in GCC 4.8, so it is written this way instead.\n\n        set_parents();\n        return result;\n    }\n\n    /// @brief inserts element into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, const basic_json& val)\n    {\n        // insert only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            // check if iterator pos fits to this JSON value\n            if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))\n            {\n                JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n            }\n\n            // insert to array and return iterator\n            return insert_iterator(pos, val);\n        }\n\n        JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n    }\n\n    /// @brief inserts element into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, basic_json&& val)\n    {\n        return insert(pos, val);\n    }\n\n    /// @brief inserts copies of element into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, size_type cnt, const basic_json& val)\n    {\n        // insert only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            // check if iterator pos fits to this JSON value\n            if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))\n            {\n                JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n            }\n\n            // insert to array and return iterator\n            return insert_iterator(pos, cnt, val);\n        }\n\n        JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n    }\n\n    /// @brief inserts range of elements into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, const_iterator first, const_iterator last)\n    {\n        // insert only works for arrays\n        if (JSON_HEDLEY_UNLIKELY(!is_array()))\n        {\n            JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n        }\n\n        // check if iterator pos fits to this JSON value\n        if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))\n        {\n            JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n        }\n\n        // check if range iterators belong to the same JSON object\n        if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(210, \"iterators do not fit\", this));\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(first.m_object == this))\n        {\n            JSON_THROW(invalid_iterator::create(211, \"passed iterators may not belong to container\", this));\n        }\n\n        // insert to array and return iterator\n        return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator);\n    }\n\n    /// @brief inserts elements from initializer list into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, initializer_list_t ilist)\n    {\n        // insert only works for arrays\n        if (JSON_HEDLEY_UNLIKELY(!is_array()))\n        {\n            JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n        }\n\n        // check if iterator pos fits to this JSON value\n        if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))\n        {\n            JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n        }\n\n        // insert to array and return iterator\n        return insert_iterator(pos, ilist.begin(), ilist.end());\n    }\n\n    /// @brief inserts range of elements into object\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    void insert(const_iterator first, const_iterator last)\n    {\n        // insert only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n        }\n\n        // check if range iterators belong to the same JSON object\n        if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(210, \"iterators do not fit\", this));\n        }\n\n        // passed iterators must belong to objects\n        if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()))\n        {\n            JSON_THROW(invalid_iterator::create(202, \"iterators first and last must point to objects\", this));\n        }\n\n        m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);\n    }\n\n    /// @brief updates a JSON object from another object, overwriting existing keys\n    /// @sa https://json.nlohmann.me/api/basic_json/update/\n    void update(const_reference j, bool merge_objects = false)\n    {\n        update(j.begin(), j.end(), merge_objects);\n    }\n\n    /// @brief updates a JSON object from another object, overwriting existing keys\n    /// @sa https://json.nlohmann.me/api/basic_json/update/\n    void update(const_iterator first, const_iterator last, bool merge_objects = false)\n    {\n        // implicitly convert null value to an empty object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value.object = create<object_t>();\n            assert_invariant();\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(312, detail::concat(\"cannot use update() with \", type_name()), this));\n        }\n\n        // check if range iterators belong to the same JSON object\n        if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(210, \"iterators do not fit\", this));\n        }\n\n        // passed iterators must belong to objects\n        if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()))\n        {\n            JSON_THROW(type_error::create(312, detail::concat(\"cannot use update() with \", first.m_object->type_name()), first.m_object));\n        }\n\n        for (auto it = first; it != last; ++it)\n        {\n            if (merge_objects && it.value().is_object())\n            {\n                auto it2 = m_data.m_value.object->find(it.key());\n                if (it2 != m_data.m_value.object->end())\n                {\n                    it2->second.update(it.value(), true);\n                    continue;\n                }\n            }\n            m_data.m_value.object->operator[](it.key()) = it.value();\n#if JSON_DIAGNOSTICS\n            m_data.m_value.object->operator[](it.key()).m_parent = this;\n#endif\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(reference other) noexcept (\n        std::is_nothrow_move_constructible<value_t>::value&&\n        std::is_nothrow_move_assignable<value_t>::value&&\n        std::is_nothrow_move_constructible<json_value>::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n        std::is_nothrow_move_assignable<json_value>::value\n    )\n    {\n        std::swap(m_data.m_type, other.m_data.m_type);\n        std::swap(m_data.m_value, other.m_data.m_value);\n\n        set_parents();\n        other.set_parents();\n        assert_invariant();\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    friend void swap(reference left, reference right) noexcept (\n        std::is_nothrow_move_constructible<value_t>::value&&\n        std::is_nothrow_move_assignable<value_t>::value&&\n        std::is_nothrow_move_constructible<json_value>::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n        std::is_nothrow_move_assignable<json_value>::value\n    )\n    {\n        left.swap(right);\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(array_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    {\n        // swap only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.array), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(array_t&) with \", type_name()), this));\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(object_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    {\n        // swap only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.object), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(object_t&) with \", type_name()), this));\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(string_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    {\n        // swap only works for strings\n        if (JSON_HEDLEY_LIKELY(is_string()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.string), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(string_t&) with \", type_name()), this));\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    {\n        // swap only works for strings\n        if (JSON_HEDLEY_LIKELY(is_binary()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.binary), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(binary_t&) with \", type_name()), this));\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape)\n    {\n        // swap only works for strings\n        if (JSON_HEDLEY_LIKELY(is_binary()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.binary), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(binary_t::container_type&) with \", type_name()), this));\n        }\n    }\n\n    /// @}\n\n    //////////////////////////////////////////\n    // lexicographical comparison operators //\n    //////////////////////////////////////////\n\n    /// @name lexicographical comparison operators\n    /// @{\n\n    // note parentheses around operands are necessary; see\n    // https://github.com/nlohmann/json/issues/1530\n#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result)                       \\\n    const auto lhs_type = lhs.type();                                                                    \\\n    const auto rhs_type = rhs.type();                                                                    \\\n    \\\n    if (lhs_type == rhs_type) /* NOLINT(readability/braces) */                                           \\\n    {                                                                                                    \\\n        switch (lhs_type)                                                                                \\\n        {                                                                                                \\\n            case value_t::array:                                                                         \\\n                return (*lhs.m_data.m_value.array) op (*rhs.m_data.m_value.array);                                     \\\n                \\\n            case value_t::object:                                                                        \\\n                return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object);                                   \\\n                \\\n            case value_t::null:                                                                          \\\n                return (null_result);                                                                    \\\n                \\\n            case value_t::string:                                                                        \\\n                return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string);                                   \\\n                \\\n            case value_t::boolean:                                                                       \\\n                return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean);                                   \\\n                \\\n            case value_t::number_integer:                                                                \\\n                return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer);                     \\\n                \\\n            case value_t::number_unsigned:                                                               \\\n                return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned);                   \\\n                \\\n            case value_t::number_float:                                                                  \\\n                return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float);                         \\\n                \\\n            case value_t::binary:                                                                        \\\n                return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary);                                   \\\n                \\\n            case value_t::discarded:                                                                     \\\n            default:                                                                                     \\\n                return (unordered_result);                                                               \\\n        }                                                                                                \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float)                   \\\n    {                                                                                                    \\\n        return static_cast<number_float_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_float;      \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer)                   \\\n    {                                                                                                    \\\n        return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_integer);      \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float)                  \\\n    {                                                                                                    \\\n        return static_cast<number_float_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_float;     \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned)                  \\\n    {                                                                                                    \\\n        return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_unsigned);     \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer)                \\\n    {                                                                                                    \\\n        return static_cast<number_integer_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_integer; \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned)                \\\n    {                                                                                                    \\\n        return lhs.m_data.m_value.number_integer op static_cast<number_integer_t>(rhs.m_data.m_value.number_unsigned); \\\n    }                                                                                                    \\\n    else if(compares_unordered(lhs, rhs))\\\n    {\\\n        return (unordered_result);\\\n    }\\\n    \\\n    return (default_result);\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    // returns true if:\n    // - any operand is NaN and the other operand is of number type\n    // - any operand is discarded\n    // in legacy mode, discarded values are considered ordered if\n    // an operation is computed as an odd number of inverses of others\n    static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept\n    {\n        if ((lhs.is_number_float() && std::isnan(lhs.m_data.m_value.number_float) && rhs.is_number())\n                || (rhs.is_number_float() && std::isnan(rhs.m_data.m_value.number_float) && lhs.is_number()))\n        {\n            return true;\n        }\n#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n        return (lhs.is_discarded() || rhs.is_discarded()) && !inverse;\n#else\n        static_cast<void>(inverse);\n        return lhs.is_discarded() || rhs.is_discarded();\n#endif\n    }\n\n  private:\n    bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept\n    {\n        return compares_unordered(*this, rhs, inverse);\n    }\n\n  public:\n#if JSON_HAS_THREE_WAY_COMPARISON\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    bool operator==(const_reference rhs) const noexcept\n    {\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n        const_reference lhs = *this;\n        JSON_IMPLEMENT_OPERATOR( ==, true, false, false)\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n    }\n\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    template<typename ScalarType>\n    requires std::is_scalar_v<ScalarType>\n    bool operator==(ScalarType rhs) const noexcept\n    {\n        return *this == basic_json(rhs);\n    }\n\n    /// @brief comparison: not equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n    bool operator!=(const_reference rhs) const noexcept\n    {\n        if (compares_unordered(rhs, true))\n        {\n            return false;\n        }\n        return !operator==(rhs);\n    }\n\n    /// @brief comparison: 3-way\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/\n    std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*\n    {\n        const_reference lhs = *this;\n        // default_result is used if we cannot compare values. In that case,\n        // we compare types.\n        JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*\n                                std::partial_ordering::equivalent,\n                                std::partial_ordering::unordered,\n                                lhs_type <=> rhs_type) // *NOPAD*\n    }\n\n    /// @brief comparison: 3-way\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/\n    template<typename ScalarType>\n    requires std::is_scalar_v<ScalarType>\n    std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD*\n    {\n        return *this <=> basic_json(rhs); // *NOPAD*\n    }\n\n#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n    // all operators that are computed as an odd number of inverses of others\n    // need to be overloaded to emulate the legacy comparison behavior\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)\n    bool operator<=(const_reference rhs) const noexcept\n    {\n        if (compares_unordered(rhs, true))\n        {\n            return false;\n        }\n        return !(rhs < *this);\n    }\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    template<typename ScalarType>\n    requires std::is_scalar_v<ScalarType>\n    bool operator<=(ScalarType rhs) const noexcept\n    {\n        return *this <= basic_json(rhs);\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)\n    bool operator>=(const_reference rhs) const noexcept\n    {\n        if (compares_unordered(rhs, true))\n        {\n            return false;\n        }\n        return !(*this < rhs);\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    template<typename ScalarType>\n    requires std::is_scalar_v<ScalarType>\n    bool operator>=(ScalarType rhs) const noexcept\n    {\n        return *this >= basic_json(rhs);\n    }\n#endif\n#else\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    friend bool operator==(const_reference lhs, const_reference rhs) noexcept\n    {\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n        JSON_IMPLEMENT_OPERATOR( ==, true, false, false)\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n    }\n\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator==(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs == basic_json(rhs);\n    }\n\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator==(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) == rhs;\n    }\n\n    /// @brief comparison: not equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n    friend bool operator!=(const_reference lhs, const_reference rhs) noexcept\n    {\n        if (compares_unordered(lhs, rhs, true))\n        {\n            return false;\n        }\n        return !(lhs == rhs);\n    }\n\n    /// @brief comparison: not equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs != basic_json(rhs);\n    }\n\n    /// @brief comparison: not equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) != rhs;\n    }\n\n    /// @brief comparison: less than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/\n    friend bool operator<(const_reference lhs, const_reference rhs) noexcept\n    {\n        // default_result is used if we cannot compare values. In that case,\n        // we compare types. Note we have to call the operator explicitly,\n        // because MSVC has problems otherwise.\n        JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type))\n    }\n\n    /// @brief comparison: less than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator<(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs < basic_json(rhs);\n    }\n\n    /// @brief comparison: less than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator<(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) < rhs;\n    }\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    friend bool operator<=(const_reference lhs, const_reference rhs) noexcept\n    {\n        if (compares_unordered(lhs, rhs, true))\n        {\n            return false;\n        }\n        return !(rhs < lhs);\n    }\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs <= basic_json(rhs);\n    }\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) <= rhs;\n    }\n\n    /// @brief comparison: greater than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/\n    friend bool operator>(const_reference lhs, const_reference rhs) noexcept\n    {\n        // double inverse\n        if (compares_unordered(lhs, rhs))\n        {\n            return false;\n        }\n        return !(lhs <= rhs);\n    }\n\n    /// @brief comparison: greater than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator>(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs > basic_json(rhs);\n    }\n\n    /// @brief comparison: greater than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator>(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) > rhs;\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    friend bool operator>=(const_reference lhs, const_reference rhs) noexcept\n    {\n        if (compares_unordered(lhs, rhs, true))\n        {\n            return false;\n        }\n        return !(lhs < rhs);\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs >= basic_json(rhs);\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) >= rhs;\n    }\n#endif\n\n#undef JSON_IMPLEMENT_OPERATOR\n\n    /// @}\n\n    ///////////////////\n    // serialization //\n    ///////////////////\n\n    /// @name serialization\n    /// @{\n#ifndef JSON_NO_IO\n    /// @brief serialize to stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/\n    friend std::ostream& operator<<(std::ostream& o, const basic_json& j)\n    {\n        // read width member and use it as indentation parameter if nonzero\n        const bool pretty_print = o.width() > 0;\n        const auto indentation = pretty_print ? o.width() : 0;\n\n        // reset width to 0 for subsequent calls to this stream\n        o.width(0);\n\n        // do the actual serialization\n        serializer s(detail::output_adapter<char>(o), o.fill());\n        s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation));\n        return o;\n    }\n\n    /// @brief serialize to stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/\n    /// @deprecated This function is deprecated since 3.0.0 and will be removed in\n    ///             version 4.0.0 of the library. Please use\n    ///             operator<<(std::ostream&, const basic_json&) instead; that is,\n    ///             replace calls like `j >> o;` with `o << j;`.\n    JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&))\n    friend std::ostream& operator>>(const basic_json& j, std::ostream& o)\n    {\n        return o << j;\n    }\n#endif  // JSON_NO_IO\n    /// @}\n\n    /////////////////////\n    // deserialization //\n    /////////////////////\n\n    /// @name deserialization\n    /// @{\n\n    /// @brief deserialize from a compatible input\n    /// @sa https://json.nlohmann.me/api/basic_json/parse/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json parse(InputType&& i,\n                            const parser_callback_t cb = nullptr,\n                            const bool allow_exceptions = true,\n                            const bool ignore_comments = false)\n    {\n        basic_json result;\n        parser(detail::input_adapter(std::forward<InputType>(i)), cb, allow_exceptions, ignore_comments).parse(true, result);\n        return result;\n    }\n\n    /// @brief deserialize from a pair of character iterators\n    /// @sa https://json.nlohmann.me/api/basic_json/parse/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json parse(IteratorType first,\n                            IteratorType last,\n                            const parser_callback_t cb = nullptr,\n                            const bool allow_exceptions = true,\n                            const bool ignore_comments = false)\n    {\n        basic_json result;\n        parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result);\n        return result;\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len))\n    static basic_json parse(detail::span_input_adapter&& i,\n                            const parser_callback_t cb = nullptr,\n                            const bool allow_exceptions = true,\n                            const bool ignore_comments = false)\n    {\n        basic_json result;\n        parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result);\n        return result;\n    }\n\n    /// @brief check if the input is valid JSON\n    /// @sa https://json.nlohmann.me/api/basic_json/accept/\n    template<typename InputType>\n    static bool accept(InputType&& i,\n                       const bool ignore_comments = false)\n    {\n        return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments).accept(true);\n    }\n\n    /// @brief check if the input is valid JSON\n    /// @sa https://json.nlohmann.me/api/basic_json/accept/\n    template<typename IteratorType>\n    static bool accept(IteratorType first, IteratorType last,\n                       const bool ignore_comments = false)\n    {\n        return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len))\n    static bool accept(detail::span_input_adapter&& i,\n                       const bool ignore_comments = false)\n    {\n        return parser(i.get(), nullptr, false, ignore_comments).accept(true);\n    }\n\n    /// @brief generate SAX events\n    /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/\n    template <typename InputType, typename SAX>\n    JSON_HEDLEY_NON_NULL(2)\n    static bool sax_parse(InputType&& i, SAX* sax,\n                          input_format_t format = input_format_t::json,\n                          const bool strict = true,\n                          const bool ignore_comments = false)\n    {\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        return format == input_format_t::json\n               ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict)\n               : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);\n    }\n\n    /// @brief generate SAX events\n    /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/\n    template<class IteratorType, class SAX>\n    JSON_HEDLEY_NON_NULL(3)\n    static bool sax_parse(IteratorType first, IteratorType last, SAX* sax,\n                          input_format_t format = input_format_t::json,\n                          const bool strict = true,\n                          const bool ignore_comments = false)\n    {\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        return format == input_format_t::json\n               ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict)\n               : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);\n    }\n\n    /// @brief generate SAX events\n    /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/\n    /// @deprecated This function is deprecated since 3.8.0 and will be removed in\n    ///             version 4.0.0 of the library. Please use\n    ///             sax_parse(ptr, ptr + len) instead.\n    template <typename SAX>\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...))\n    JSON_HEDLEY_NON_NULL(2)\n    static bool sax_parse(detail::span_input_adapter&& i, SAX* sax,\n                          input_format_t format = input_format_t::json,\n                          const bool strict = true,\n                          const bool ignore_comments = false)\n    {\n        auto ia = i.get();\n        return format == input_format_t::json\n               // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n               ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict)\n               // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n               : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);\n    }\n#ifndef JSON_NO_IO\n    /// @brief deserialize from stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/\n    /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in\n    ///             version 4.0.0 of the library. Please use\n    ///             operator>>(std::istream&, basic_json&) instead; that is,\n    ///             replace calls like `j << i;` with `i >> j;`.\n    JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&))\n    friend std::istream& operator<<(basic_json& j, std::istream& i)\n    {\n        return operator>>(i, j);\n    }\n\n    /// @brief deserialize from stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/\n    friend std::istream& operator>>(std::istream& i, basic_json& j)\n    {\n        parser(detail::input_adapter(i)).parse(false, j);\n        return i;\n    }\n#endif  // JSON_NO_IO\n    /// @}\n\n    ///////////////////////////\n    // convenience functions //\n    ///////////////////////////\n\n    /// @brief return the type as string\n    /// @sa https://json.nlohmann.me/api/basic_json/type_name/\n    JSON_HEDLEY_RETURNS_NON_NULL\n    const char* type_name() const noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::null:\n                return \"null\";\n            case value_t::object:\n                return \"object\";\n            case value_t::array:\n                return \"array\";\n            case value_t::string:\n                return \"string\";\n            case value_t::boolean:\n                return \"boolean\";\n            case value_t::binary:\n                return \"binary\";\n            case value_t::discarded:\n                return \"discarded\";\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            default:\n                return \"number\";\n        }\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    //////////////////////\n    // member variables //\n    //////////////////////\n\n    struct data\n    {\n        /// the type of the current element\n        value_t m_type = value_t::null;\n\n        /// the value of the current element\n        json_value m_value = {};\n\n        data(const value_t v)\n            : m_type(v), m_value(v)\n        {\n        }\n\n        data(size_type cnt, const basic_json& val)\n            : m_type(value_t::array)\n        {\n            m_value.array = create<array_t>(cnt, val);\n        }\n\n        data() noexcept = default;\n        data(data&&) noexcept = default;\n        data(const data&) noexcept = delete;\n        data& operator=(data&&) noexcept = delete;\n        data& operator=(const data&) noexcept = delete;\n\n        ~data() noexcept\n        {\n            m_value.destroy(m_type);\n        }\n    };\n\n    data m_data = {};\n\n#if JSON_DIAGNOSTICS\n    /// a pointer to a parent value (for debugging purposes)\n    basic_json* m_parent = nullptr;\n#endif\n\n    //////////////////////////////////////////\n    // binary serialization/deserialization //\n    //////////////////////////////////////////\n\n    /// @name binary serialization/deserialization support\n    /// @{\n\n  public:\n    /// @brief create a CBOR serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/\n    static std::vector<std::uint8_t> to_cbor(const basic_json& j)\n    {\n        std::vector<std::uint8_t> result;\n        to_cbor(j, result);\n        return result;\n    }\n\n    /// @brief create a CBOR serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/\n    static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o)\n    {\n        binary_writer<std::uint8_t>(o).write_cbor(j);\n    }\n\n    /// @brief create a CBOR serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/\n    static void to_cbor(const basic_json& j, detail::output_adapter<char> o)\n    {\n        binary_writer<char>(o).write_cbor(j);\n    }\n\n    /// @brief create a MessagePack serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/\n    static std::vector<std::uint8_t> to_msgpack(const basic_json& j)\n    {\n        std::vector<std::uint8_t> result;\n        to_msgpack(j, result);\n        return result;\n    }\n\n    /// @brief create a MessagePack serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/\n    static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o)\n    {\n        binary_writer<std::uint8_t>(o).write_msgpack(j);\n    }\n\n    /// @brief create a MessagePack serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/\n    static void to_msgpack(const basic_json& j, detail::output_adapter<char> o)\n    {\n        binary_writer<char>(o).write_msgpack(j);\n    }\n\n    /// @brief create a UBJSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/\n    static std::vector<std::uint8_t> to_ubjson(const basic_json& j,\n            const bool use_size = false,\n            const bool use_type = false)\n    {\n        std::vector<std::uint8_t> result;\n        to_ubjson(j, result, use_size, use_type);\n        return result;\n    }\n\n    /// @brief create a UBJSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/\n    static void to_ubjson(const basic_json& j, detail::output_adapter<std::uint8_t> o,\n                          const bool use_size = false, const bool use_type = false)\n    {\n        binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type);\n    }\n\n    /// @brief create a UBJSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/\n    static void to_ubjson(const basic_json& j, detail::output_adapter<char> o,\n                          const bool use_size = false, const bool use_type = false)\n    {\n        binary_writer<char>(o).write_ubjson(j, use_size, use_type);\n    }\n\n    /// @brief create a BJData serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/\n    static std::vector<std::uint8_t> to_bjdata(const basic_json& j,\n            const bool use_size = false,\n            const bool use_type = false)\n    {\n        std::vector<std::uint8_t> result;\n        to_bjdata(j, result, use_size, use_type);\n        return result;\n    }\n\n    /// @brief create a BJData serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/\n    static void to_bjdata(const basic_json& j, detail::output_adapter<std::uint8_t> o,\n                          const bool use_size = false, const bool use_type = false)\n    {\n        binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type, true, true);\n    }\n\n    /// @brief create a BJData serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/\n    static void to_bjdata(const basic_json& j, detail::output_adapter<char> o,\n                          const bool use_size = false, const bool use_type = false)\n    {\n        binary_writer<char>(o).write_ubjson(j, use_size, use_type, true, true);\n    }\n\n    /// @brief create a BSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bson/\n    static std::vector<std::uint8_t> to_bson(const basic_json& j)\n    {\n        std::vector<std::uint8_t> result;\n        to_bson(j, result);\n        return result;\n    }\n\n    /// @brief create a BSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bson/\n    static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o)\n    {\n        binary_writer<std::uint8_t>(o).write_bson(j);\n    }\n\n    /// @brief create a BSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bson/\n    static void to_bson(const basic_json& j, detail::output_adapter<char> o)\n    {\n        binary_writer<char>(o).write_bson(j);\n    }\n\n    /// @brief create a JSON value from an input in CBOR format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_cbor(InputType&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true,\n                                const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in CBOR format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_cbor(IteratorType first, IteratorType last,\n                                const bool strict = true,\n                                const bool allow_exceptions = true,\n                                const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    template<typename T>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))\n    static basic_json from_cbor(const T* ptr, std::size_t len,\n                                const bool strict = true,\n                                const bool allow_exceptions = true,\n                                const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))\n    static basic_json from_cbor(detail::span_input_adapter&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true,\n                                const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = i.get();\n        // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in MessagePack format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_msgpack(InputType&& i,\n                                   const bool strict = true,\n                                   const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in MessagePack format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_msgpack(IteratorType first, IteratorType last,\n                                   const bool strict = true,\n                                   const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    template<typename T>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))\n    static basic_json from_msgpack(const T* ptr, std::size_t len,\n                                   const bool strict = true,\n                                   const bool allow_exceptions = true)\n    {\n        return from_msgpack(ptr, ptr + len, strict, allow_exceptions);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))\n    static basic_json from_msgpack(detail::span_input_adapter&& i,\n                                   const bool strict = true,\n                                   const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = i.get();\n        // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in UBJSON format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_ubjson(InputType&& i,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in UBJSON format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_ubjson(IteratorType first, IteratorType last,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    template<typename T>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))\n    static basic_json from_ubjson(const T* ptr, std::size_t len,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        return from_ubjson(ptr, ptr + len, strict, allow_exceptions);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))\n    static basic_json from_ubjson(detail::span_input_adapter&& i,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = i.get();\n        // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in BJData format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_bjdata(InputType&& i,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in BJData format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_bjdata(IteratorType first, IteratorType last,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in BSON format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_bson/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_bson(InputType&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in BSON format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_bson/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_bson(IteratorType first, IteratorType last,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    template<typename T>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))\n    static basic_json from_bson(const T* ptr, std::size_t len,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n    {\n        return from_bson(ptr, ptr + len, strict, allow_exceptions);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))\n    static basic_json from_bson(detail::span_input_adapter&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n    {\n        basic_json result;\n        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n        auto ia = i.get();\n        // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict);\n        return res ? result : basic_json(value_t::discarded);\n    }\n    /// @}\n\n    //////////////////////////\n    // JSON Pointer support //\n    //////////////////////////\n\n    /// @name JSON Pointer functions\n    /// @{\n\n    /// @brief access specified element via JSON Pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    reference operator[](const json_pointer& ptr)\n    {\n        return ptr.get_unchecked(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr)\n    {\n        return ptr.get_unchecked(this);\n    }\n\n    /// @brief access specified element via JSON Pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    const_reference operator[](const json_pointer& ptr) const\n    {\n        return ptr.get_unchecked(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) const\n    {\n        return ptr.get_unchecked(this);\n    }\n\n    /// @brief access specified element via JSON Pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    reference at(const json_pointer& ptr)\n    {\n        return ptr.get_checked(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr)\n    {\n        return ptr.get_checked(this);\n    }\n\n    /// @brief access specified element via JSON Pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    const_reference at(const json_pointer& ptr) const\n    {\n        return ptr.get_checked(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    const_reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) const\n    {\n        return ptr.get_checked(this);\n    }\n\n    /// @brief return flattened JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/flatten/\n    basic_json flatten() const\n    {\n        basic_json result(value_t::object);\n        json_pointer::flatten(\"\", *this, result);\n        return result;\n    }\n\n    /// @brief unflatten a previously flattened JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/unflatten/\n    basic_json unflatten() const\n    {\n        return json_pointer::unflatten(*this);\n    }\n\n    /// @}\n\n    //////////////////////////\n    // JSON Patch functions //\n    //////////////////////////\n\n    /// @name JSON Patch functions\n    /// @{\n\n    /// @brief applies a JSON patch in-place without copying the object\n    /// @sa https://json.nlohmann.me/api/basic_json/patch/\n    void patch_inplace(const basic_json& json_patch)\n    {\n        basic_json& result = *this;\n        // the valid JSON Patch operations\n        enum class patch_operations {add, remove, replace, move, copy, test, invalid};\n\n        const auto get_op = [](const std::string & op)\n        {\n            if (op == \"add\")\n            {\n                return patch_operations::add;\n            }\n            if (op == \"remove\")\n            {\n                return patch_operations::remove;\n            }\n            if (op == \"replace\")\n            {\n                return patch_operations::replace;\n            }\n            if (op == \"move\")\n            {\n                return patch_operations::move;\n            }\n            if (op == \"copy\")\n            {\n                return patch_operations::copy;\n            }\n            if (op == \"test\")\n            {\n                return patch_operations::test;\n            }\n\n            return patch_operations::invalid;\n        };\n\n        // wrapper for \"add\" operation; add value at ptr\n        const auto operation_add = [&result](json_pointer & ptr, basic_json val)\n        {\n            // adding to the root of the target document means replacing it\n            if (ptr.empty())\n            {\n                result = val;\n                return;\n            }\n\n            // make sure the top element of the pointer exists\n            json_pointer const top_pointer = ptr.top();\n            if (top_pointer != ptr)\n            {\n                result.at(top_pointer);\n            }\n\n            // get reference to parent of JSON pointer ptr\n            const auto last_path = ptr.back();\n            ptr.pop_back();\n            // parent must exist when performing patch add per RFC6902 specs\n            basic_json& parent = result.at(ptr);\n\n            switch (parent.m_data.m_type)\n            {\n                case value_t::null:\n                case value_t::object:\n                {\n                    // use operator[] to add value\n                    parent[last_path] = val;\n                    break;\n                }\n\n                case value_t::array:\n                {\n                    if (last_path == \"-\")\n                    {\n                        // special case: append to back\n                        parent.push_back(val);\n                    }\n                    else\n                    {\n                        const auto idx = json_pointer::template array_index<basic_json_t>(last_path);\n                        if (JSON_HEDLEY_UNLIKELY(idx > parent.size()))\n                        {\n                            // avoid undefined behavior\n                            JSON_THROW(out_of_range::create(401, detail::concat(\"array index \", std::to_string(idx), \" is out of range\"), &parent));\n                        }\n\n                        // default case: insert add offset\n                        parent.insert(parent.begin() + static_cast<difference_type>(idx), val);\n                    }\n                    break;\n                }\n\n                // if there exists a parent it cannot be primitive\n                case value_t::string: // LCOV_EXCL_LINE\n                case value_t::boolean: // LCOV_EXCL_LINE\n                case value_t::number_integer: // LCOV_EXCL_LINE\n                case value_t::number_unsigned: // LCOV_EXCL_LINE\n                case value_t::number_float: // LCOV_EXCL_LINE\n                case value_t::binary: // LCOV_EXCL_LINE\n                case value_t::discarded: // LCOV_EXCL_LINE\n                default:            // LCOV_EXCL_LINE\n                    JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n            }\n        };\n\n        // wrapper for \"remove\" operation; remove value at ptr\n        const auto operation_remove = [this, & result](json_pointer & ptr)\n        {\n            // get reference to parent of JSON pointer ptr\n            const auto last_path = ptr.back();\n            ptr.pop_back();\n            basic_json& parent = result.at(ptr);\n\n            // remove child\n            if (parent.is_object())\n            {\n                // perform range check\n                auto it = parent.find(last_path);\n                if (JSON_HEDLEY_LIKELY(it != parent.end()))\n                {\n                    parent.erase(it);\n                }\n                else\n                {\n                    JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", last_path, \"' not found\"), this));\n                }\n            }\n            else if (parent.is_array())\n            {\n                // note erase performs range check\n                parent.erase(json_pointer::template array_index<basic_json_t>(last_path));\n            }\n        };\n\n        // type check: top level value must be an array\n        if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array()))\n        {\n            JSON_THROW(parse_error::create(104, 0, \"JSON patch must be an array of objects\", &json_patch));\n        }\n\n        // iterate and apply the operations\n        for (const auto& val : json_patch)\n        {\n            // wrapper to get a value for an operation\n            const auto get_value = [&val](const std::string & op,\n                                          const std::string & member,\n                                          bool string_type) -> basic_json &\n            {\n                // find value\n                auto it = val.m_data.m_value.object->find(member);\n\n                // context-sensitive error message\n                const auto error_msg = (op == \"op\") ? \"operation\" : detail::concat(\"operation '\", op, '\\'');\n\n                // check if desired value is present\n                if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))\n                {\n                    // NOLINTNEXTLINE(performance-inefficient-string-concatenation)\n                    JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, \" must have member '\", member, \"'\"), &val));\n                }\n\n                // check if result is of type string\n                if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))\n                {\n                    // NOLINTNEXTLINE(performance-inefficient-string-concatenation)\n                    JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, \" must have string member '\", member, \"'\"), &val));\n                }\n\n                // no error: return value\n                return it->second;\n            };\n\n            // type check: every element of the array must be an object\n            if (JSON_HEDLEY_UNLIKELY(!val.is_object()))\n            {\n                JSON_THROW(parse_error::create(104, 0, \"JSON patch must be an array of objects\", &val));\n            }\n\n            // collect mandatory members\n            const auto op = get_value(\"op\", \"op\", true).template get<std::string>();\n            const auto path = get_value(op, \"path\", true).template get<std::string>();\n            json_pointer ptr(path);\n\n            switch (get_op(op))\n            {\n                case patch_operations::add:\n                {\n                    operation_add(ptr, get_value(\"add\", \"value\", false));\n                    break;\n                }\n\n                case patch_operations::remove:\n                {\n                    operation_remove(ptr);\n                    break;\n                }\n\n                case patch_operations::replace:\n                {\n                    // the \"path\" location must exist - use at()\n                    result.at(ptr) = get_value(\"replace\", \"value\", false);\n                    break;\n                }\n\n                case patch_operations::move:\n                {\n                    const auto from_path = get_value(\"move\", \"from\", true).template get<std::string>();\n                    json_pointer from_ptr(from_path);\n\n                    // the \"from\" location must exist - use at()\n                    basic_json const v = result.at(from_ptr);\n\n                    // The move operation is functionally identical to a\n                    // \"remove\" operation on the \"from\" location, followed\n                    // immediately by an \"add\" operation at the target\n                    // location with the value that was just removed.\n                    operation_remove(from_ptr);\n                    operation_add(ptr, v);\n                    break;\n                }\n\n                case patch_operations::copy:\n                {\n                    const auto from_path = get_value(\"copy\", \"from\", true).template get<std::string>();\n                    const json_pointer from_ptr(from_path);\n\n                    // the \"from\" location must exist - use at()\n                    basic_json const v = result.at(from_ptr);\n\n                    // The copy is functionally identical to an \"add\"\n                    // operation at the target location using the value\n                    // specified in the \"from\" member.\n                    operation_add(ptr, v);\n                    break;\n                }\n\n                case patch_operations::test:\n                {\n                    bool success = false;\n                    JSON_TRY\n                    {\n                        // check if \"value\" matches the one at \"path\"\n                        // the \"path\" location must exist - use at()\n                        success = (result.at(ptr) == get_value(\"test\", \"value\", false));\n                    }\n                    JSON_INTERNAL_CATCH (out_of_range&)\n                    {\n                        // ignore out of range errors: success remains false\n                    }\n\n                    // throw an exception if test fails\n                    if (JSON_HEDLEY_UNLIKELY(!success))\n                    {\n                        JSON_THROW(other_error::create(501, detail::concat(\"unsuccessful: \", val.dump()), &val));\n                    }\n\n                    break;\n                }\n\n                case patch_operations::invalid:\n                default:\n                {\n                    // op must be \"add\", \"remove\", \"replace\", \"move\", \"copy\", or\n                    // \"test\"\n                    JSON_THROW(parse_error::create(105, 0, detail::concat(\"operation value '\", op, \"' is invalid\"), &val));\n                }\n            }\n        }\n    }\n\n    /// @brief applies a JSON patch to a copy of the current object\n    /// @sa https://json.nlohmann.me/api/basic_json/patch/\n    basic_json patch(const basic_json& json_patch) const\n    {\n        basic_json result = *this;\n        result.patch_inplace(json_patch);\n        return result;\n    }\n\n    /// @brief creates a diff as a JSON patch\n    /// @sa https://json.nlohmann.me/api/basic_json/diff/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json diff(const basic_json& source, const basic_json& target,\n                           const std::string& path = \"\")\n    {\n        // the patch\n        basic_json result(value_t::array);\n\n        // if the values are the same, return empty patch\n        if (source == target)\n        {\n            return result;\n        }\n\n        if (source.type() != target.type())\n        {\n            // different types: replace value\n            result.push_back(\n            {\n                {\"op\", \"replace\"}, {\"path\", path}, {\"value\", target}\n            });\n            return result;\n        }\n\n        switch (source.type())\n        {\n            case value_t::array:\n            {\n                // first pass: traverse common elements\n                std::size_t i = 0;\n                while (i < source.size() && i < target.size())\n                {\n                    // recursive call to compare array values at index i\n                    auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', std::to_string(i)));\n                    result.insert(result.end(), temp_diff.begin(), temp_diff.end());\n                    ++i;\n                }\n\n                // We now reached the end of at least one array\n                // in a second pass, traverse the remaining elements\n\n                // remove my remaining elements\n                const auto end_index = static_cast<difference_type>(result.size());\n                while (i < source.size())\n                {\n                    // add operations in reverse order to avoid invalid\n                    // indices\n                    result.insert(result.begin() + end_index, object(\n                    {\n                        {\"op\", \"remove\"},\n                        {\"path\", detail::concat(path, '/', std::to_string(i))}\n                    }));\n                    ++i;\n                }\n\n                // add other remaining elements\n                while (i < target.size())\n                {\n                    result.push_back(\n                    {\n                        {\"op\", \"add\"},\n                        {\"path\", detail::concat(path, \"/-\")},\n                        {\"value\", target[i]}\n                    });\n                    ++i;\n                }\n\n                break;\n            }\n\n            case value_t::object:\n            {\n                // first pass: traverse this object's elements\n                for (auto it = source.cbegin(); it != source.cend(); ++it)\n                {\n                    // escape the key name to be used in a JSON patch\n                    const auto path_key = detail::concat(path, '/', detail::escape(it.key()));\n\n                    if (target.find(it.key()) != target.end())\n                    {\n                        // recursive call to compare object values at key it\n                        auto temp_diff = diff(it.value(), target[it.key()], path_key);\n                        result.insert(result.end(), temp_diff.begin(), temp_diff.end());\n                    }\n                    else\n                    {\n                        // found a key that is not in o -> remove it\n                        result.push_back(object(\n                        {\n                            {\"op\", \"remove\"}, {\"path\", path_key}\n                        }));\n                    }\n                }\n\n                // second pass: traverse other object's elements\n                for (auto it = target.cbegin(); it != target.cend(); ++it)\n                {\n                    if (source.find(it.key()) == source.end())\n                    {\n                        // found a key that is not in this -> add it\n                        const auto path_key = detail::concat(path, '/', detail::escape(it.key()));\n                        result.push_back(\n                        {\n                            {\"op\", \"add\"}, {\"path\", path_key},\n                            {\"value\", it.value()}\n                        });\n                    }\n                }\n\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                // both primitive type: replace value\n                result.push_back(\n                {\n                    {\"op\", \"replace\"}, {\"path\", path}, {\"value\", target}\n                });\n                break;\n            }\n        }\n\n        return result;\n    }\n    /// @}\n\n    ////////////////////////////////\n    // JSON Merge Patch functions //\n    ////////////////////////////////\n\n    /// @name JSON Merge Patch functions\n    /// @{\n\n    /// @brief applies a JSON Merge Patch\n    /// @sa https://json.nlohmann.me/api/basic_json/merge_patch/\n    void merge_patch(const basic_json& apply_patch)\n    {\n        if (apply_patch.is_object())\n        {\n            if (!is_object())\n            {\n                *this = object();\n            }\n            for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it)\n            {\n                if (it.value().is_null())\n                {\n                    erase(it.key());\n                }\n                else\n                {\n                    operator[](it.key()).merge_patch(it.value());\n                }\n            }\n        }\n        else\n        {\n            *this = apply_patch;\n        }\n    }\n\n    /// @}\n};\n\n/// @brief user-defined to_string function for JSON values\n/// @sa https://json.nlohmann.me/api/basic_json/to_string/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nstd::string to_string(const NLOHMANN_BASIC_JSON_TPL& j)\n{\n    return j.dump();\n}\n\ninline namespace literals\n{\ninline namespace json_literals\n{\n\n/// @brief user-defined string literal for JSON values\n/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json/\nJSON_HEDLEY_NON_NULL(1)\n#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0)\n    inline nlohmann::json operator \"\"_json(const char* s, std::size_t n)\n#else\n    inline nlohmann::json operator \"\" _json(const char* s, std::size_t n)\n#endif\n{\n    return nlohmann::json::parse(s, s + n);\n}\n\n/// @brief user-defined string literal for JSON pointer\n/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json_pointer/\nJSON_HEDLEY_NON_NULL(1)\n#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0)\n    inline nlohmann::json::json_pointer operator \"\"_json_pointer(const char* s, std::size_t n)\n#else\n    inline nlohmann::json::json_pointer operator \"\" _json_pointer(const char* s, std::size_t n)\n#endif\n{\n    return nlohmann::json::json_pointer(std::string(s, n));\n}\n\n}  // namespace json_literals\n}  // namespace literals\nNLOHMANN_JSON_NAMESPACE_END\n\n///////////////////////\n// nonmember support //\n///////////////////////\n\nnamespace std // NOLINT(cert-dcl58-cpp)\n{\n\n/// @brief hash value for JSON objects\n/// @sa https://json.nlohmann.me/api/basic_json/std_hash/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nstruct hash<nlohmann::NLOHMANN_BASIC_JSON_TPL> // NOLINT(cert-dcl58-cpp)\n{\n    std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const\n    {\n        return nlohmann::detail::hash(j);\n    }\n};\n\n// specialization for std::less<value_t>\ntemplate<>\nstruct less< ::nlohmann::detail::value_t> // do not remove the space after '<', see https://github.com/nlohmann/json/pull/679\n{\n    /*!\n    @brief compare two value_t enum values\n    @since version 3.0.0\n    */\n    bool operator()(::nlohmann::detail::value_t lhs,\n                    ::nlohmann::detail::value_t rhs) const noexcept\n    {\n#if JSON_HAS_THREE_WAY_COMPARISON\n        return std::is_lt(lhs <=> rhs); // *NOPAD*\n#else\n        return ::nlohmann::detail::operator<(lhs, rhs);\n#endif\n    }\n};\n\n// C++20 prohibit function specialization in the std namespace.\n#ifndef JSON_HAS_CPP_20\n\n/// @brief exchanges the values of two JSON objects\n/// @sa https://json.nlohmann.me/api/basic_json/std_swap/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\ninline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept(  // NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp)\n    is_nothrow_move_constructible<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value&&                          // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    is_nothrow_move_assignable<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value)\n{\n    j1.swap(j2);\n}\n\n#endif\n\n}  // namespace std\n\n#if JSON_USE_GLOBAL_UDLS\n    #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0)\n        using nlohmann::literals::json_literals::operator \"\"_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers)\n        using nlohmann::literals::json_literals::operator \"\"_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers)\n    #else\n        using nlohmann::literals::json_literals::operator \"\" _json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers)\n        using nlohmann::literals::json_literals::operator \"\" _json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers)\n    #endif\n#endif\n\n// #include <nlohmann/detail/macro_unscope.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// restore clang diagnostic settings\n#if defined(__clang__)\n    #pragma clang diagnostic pop\n#endif\n\n// clean up\n#undef JSON_ASSERT\n#undef JSON_INTERNAL_CATCH\n#undef JSON_THROW\n#undef JSON_PRIVATE_UNLESS_TESTED\n#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION\n#undef NLOHMANN_BASIC_JSON_TPL\n#undef JSON_EXPLICIT\n#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL\n#undef JSON_INLINE_VARIABLE\n#undef JSON_NO_UNIQUE_ADDRESS\n#undef JSON_DISABLE_ENUM_SERIALIZATION\n#undef JSON_USE_GLOBAL_UDLS\n\n#ifndef JSON_TEST_KEEP_MACROS\n    #undef JSON_CATCH\n    #undef JSON_TRY\n    #undef JSON_HAS_CPP_11\n    #undef JSON_HAS_CPP_14\n    #undef JSON_HAS_CPP_17\n    #undef JSON_HAS_CPP_20\n    #undef JSON_HAS_FILESYSTEM\n    #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n    #undef JSON_HAS_THREE_WAY_COMPARISON\n    #undef JSON_HAS_RANGES\n    #undef JSON_HAS_STATIC_RTTI\n    #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n#endif\n\n// #include <nlohmann/thirdparty/hedley/hedley_undef.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.3\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#undef JSON_HEDLEY_ALWAYS_INLINE\n#undef JSON_HEDLEY_ARM_VERSION\n#undef JSON_HEDLEY_ARM_VERSION_CHECK\n#undef JSON_HEDLEY_ARRAY_PARAM\n#undef JSON_HEDLEY_ASSUME\n#undef JSON_HEDLEY_BEGIN_C_DECLS\n#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_CLANG_HAS_BUILTIN\n#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_CLANG_HAS_EXTENSION\n#undef JSON_HEDLEY_CLANG_HAS_FEATURE\n#undef JSON_HEDLEY_CLANG_HAS_WARNING\n#undef JSON_HEDLEY_COMPCERT_VERSION\n#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK\n#undef JSON_HEDLEY_CONCAT\n#undef JSON_HEDLEY_CONCAT3\n#undef JSON_HEDLEY_CONCAT3_EX\n#undef JSON_HEDLEY_CONCAT_EX\n#undef JSON_HEDLEY_CONST\n#undef JSON_HEDLEY_CONSTEXPR\n#undef JSON_HEDLEY_CONST_CAST\n#undef JSON_HEDLEY_CPP_CAST\n#undef JSON_HEDLEY_CRAY_VERSION\n#undef JSON_HEDLEY_CRAY_VERSION_CHECK\n#undef JSON_HEDLEY_C_DECL\n#undef JSON_HEDLEY_DEPRECATED\n#undef JSON_HEDLEY_DEPRECATED_FOR\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION\n#undef JSON_HEDLEY_DIAGNOSTIC_POP\n#undef JSON_HEDLEY_DIAGNOSTIC_PUSH\n#undef JSON_HEDLEY_DMC_VERSION\n#undef JSON_HEDLEY_DMC_VERSION_CHECK\n#undef JSON_HEDLEY_EMPTY_BASES\n#undef JSON_HEDLEY_EMSCRIPTEN_VERSION\n#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK\n#undef JSON_HEDLEY_END_C_DECLS\n#undef JSON_HEDLEY_FLAGS\n#undef JSON_HEDLEY_FLAGS_CAST\n#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_GCC_HAS_BUILTIN\n#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_GCC_HAS_EXTENSION\n#undef JSON_HEDLEY_GCC_HAS_FEATURE\n#undef JSON_HEDLEY_GCC_HAS_WARNING\n#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK\n#undef JSON_HEDLEY_GCC_VERSION\n#undef JSON_HEDLEY_GCC_VERSION_CHECK\n#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_GNUC_HAS_BUILTIN\n#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_GNUC_HAS_EXTENSION\n#undef JSON_HEDLEY_GNUC_HAS_FEATURE\n#undef JSON_HEDLEY_GNUC_HAS_WARNING\n#undef JSON_HEDLEY_GNUC_VERSION\n#undef JSON_HEDLEY_GNUC_VERSION_CHECK\n#undef JSON_HEDLEY_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_HAS_BUILTIN\n#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS\n#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_HAS_EXTENSION\n#undef JSON_HEDLEY_HAS_FEATURE\n#undef JSON_HEDLEY_HAS_WARNING\n#undef JSON_HEDLEY_IAR_VERSION\n#undef JSON_HEDLEY_IAR_VERSION_CHECK\n#undef JSON_HEDLEY_IBM_VERSION\n#undef JSON_HEDLEY_IBM_VERSION_CHECK\n#undef JSON_HEDLEY_IMPORT\n#undef JSON_HEDLEY_INLINE\n#undef JSON_HEDLEY_INTEL_CL_VERSION\n#undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK\n#undef JSON_HEDLEY_INTEL_VERSION\n#undef JSON_HEDLEY_INTEL_VERSION_CHECK\n#undef JSON_HEDLEY_IS_CONSTANT\n#undef JSON_HEDLEY_IS_CONSTEXPR_\n#undef JSON_HEDLEY_LIKELY\n#undef JSON_HEDLEY_MALLOC\n#undef JSON_HEDLEY_MCST_LCC_VERSION\n#undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK\n#undef JSON_HEDLEY_MESSAGE\n#undef JSON_HEDLEY_MSVC_VERSION\n#undef JSON_HEDLEY_MSVC_VERSION_CHECK\n#undef JSON_HEDLEY_NEVER_INLINE\n#undef JSON_HEDLEY_NON_NULL\n#undef JSON_HEDLEY_NO_ESCAPE\n#undef JSON_HEDLEY_NO_RETURN\n#undef JSON_HEDLEY_NO_THROW\n#undef JSON_HEDLEY_NULL\n#undef JSON_HEDLEY_PELLES_VERSION\n#undef JSON_HEDLEY_PELLES_VERSION_CHECK\n#undef JSON_HEDLEY_PGI_VERSION\n#undef JSON_HEDLEY_PGI_VERSION_CHECK\n#undef JSON_HEDLEY_PREDICT\n#undef JSON_HEDLEY_PRINTF_FORMAT\n#undef JSON_HEDLEY_PRIVATE\n#undef JSON_HEDLEY_PUBLIC\n#undef JSON_HEDLEY_PURE\n#undef JSON_HEDLEY_REINTERPRET_CAST\n#undef JSON_HEDLEY_REQUIRE\n#undef JSON_HEDLEY_REQUIRE_CONSTEXPR\n#undef JSON_HEDLEY_REQUIRE_MSG\n#undef JSON_HEDLEY_RESTRICT\n#undef JSON_HEDLEY_RETURNS_NON_NULL\n#undef JSON_HEDLEY_SENTINEL\n#undef JSON_HEDLEY_STATIC_ASSERT\n#undef JSON_HEDLEY_STATIC_CAST\n#undef JSON_HEDLEY_STRINGIFY\n#undef JSON_HEDLEY_STRINGIFY_EX\n#undef JSON_HEDLEY_SUNPRO_VERSION\n#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK\n#undef JSON_HEDLEY_TINYC_VERSION\n#undef JSON_HEDLEY_TINYC_VERSION_CHECK\n#undef JSON_HEDLEY_TI_ARMCL_VERSION\n#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL2000_VERSION\n#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL430_VERSION\n#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL6X_VERSION\n#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL7X_VERSION\n#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CLPRU_VERSION\n#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK\n#undef JSON_HEDLEY_TI_VERSION\n#undef JSON_HEDLEY_TI_VERSION_CHECK\n#undef JSON_HEDLEY_UNAVAILABLE\n#undef JSON_HEDLEY_UNLIKELY\n#undef JSON_HEDLEY_UNPREDICTABLE\n#undef JSON_HEDLEY_UNREACHABLE\n#undef JSON_HEDLEY_UNREACHABLE_RETURN\n#undef JSON_HEDLEY_VERSION\n#undef JSON_HEDLEY_VERSION_DECODE_MAJOR\n#undef JSON_HEDLEY_VERSION_DECODE_MINOR\n#undef JSON_HEDLEY_VERSION_DECODE_REVISION\n#undef JSON_HEDLEY_VERSION_ENCODE\n#undef JSON_HEDLEY_WARNING\n#undef JSON_HEDLEY_WARN_UNUSED_RESULT\n#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG\n#undef JSON_HEDLEY_FALL_THROUGH\n\n\n\n#endif  // INCLUDE_NLOHMANN_JSON_HPP_\n"
  },
  {
    "path": "lib/json/json_rwops_input.hpp",
    "content": "#ifndef PRIV_RVOPS_PATCH\n#define PRIV_RVOPS_PATCH\n\n#   ifndef JSON_NO_IO\n#include <string>\n#include <SDL2/SDL_rwops.h>\n\n#ifdef NLOHMANN_JSON_NAMESPACE_BEGIN\n#   error Include this file BEFORE the <json/json.hpp>!\n#endif\n\n#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 1 // Disable versioning\n\nnamespace nlohmann\n{\n#if NLOHMANN_JSON_NAMESPACE_NO_VERSION\ninline namespace json_abi\n#else\ninline namespace json_abi_v3_11_3\n#endif\n{\nnamespace detail\n{\n\n\nstatic constexpr size_t s_rwops_input_adapter_buffer_size = 2048;\n\nnamespace rwops\n{\n    // Primary template of char_traits calls std char_traits\n    template<typename T>\n    struct char_traits : std::char_traits<T>\n    {};\n\n    // Explicitly define char traits for unsigned char since it is not standard\n    template<>\n    struct char_traits<unsigned char> : std::char_traits<char>\n    {\n        using char_type = unsigned char;\n        using int_type = uint64_t;\n\n        // Redefine to_int_type function\n        static int_type to_int_type(char_type c) noexcept\n        {\n            return static_cast<int_type>(c);\n        }\n\n        static char_type to_char_type(int_type i) noexcept\n        {\n            return static_cast<char_type>(i);\n        }\n\n        static constexpr int_type eof() noexcept\n        {\n            return static_cast<int_type>(EOF);\n        }\n    };\n}\n\n/*!\nInput adapter for SDL_RWops file access. Uses a 2KB cache.\n*/\nclass rwops_input_adapter\n{\npublic:\n    using char_type = uint8_t;\n\n    explicit rwops_input_adapter(SDL_RWops* rwops) noexcept\n        : m_rwops(rwops) {}\n\n    // make class move-only\n    rwops_input_adapter(const rwops_input_adapter&) = delete;\n    rwops_input_adapter(rwops_input_adapter&&) noexcept = default;\n    rwops_input_adapter& operator=(const rwops_input_adapter&) = delete;\n    rwops_input_adapter& operator=(rwops_input_adapter&&) = delete;\n    ~rwops_input_adapter() = default;\n\n    rwops::char_traits<uint8_t>::int_type get_character() noexcept\n    {\n        if(m_temp_buf_cur >= m_temp_buf_end)\n        {\n            m_temp_buf_cur = 0;\n            m_temp_buf_end = SDL_RWread(m_rwops, &m_temp_buf[0], 1, sizeof(m_temp_buf));\n\n            if(m_temp_buf_end == 0)\n                return rwops::char_traits<uint8_t>::eof();\n        }\n\n        return m_temp_buf[m_temp_buf_cur++];\n    }\n\nprivate:\n    /// the rwops stream to read from\n    SDL_RWops* m_rwops;\n\n    uint8_t m_temp_buf[s_rwops_input_adapter_buffer_size];\n    int m_temp_buf_cur = 0;\n    int m_temp_buf_end = 0;\n};\n\ninline rwops_input_adapter input_adapter(SDL_RWops* rwops)\n{\n    return rwops_input_adapter(rwops);\n}\n\n\n\n} // namespace detail\n} // inline namespace json_abi\n} // namespace nlohmann\n\n#   endif // #ifndef JSON_NO_IO\n#endif // PRIV_RVOPS_PATCH\n"
  },
  {
    "path": "lib/md5/LICENSE",
    "content": "The MIT License (MIT)\r\n\r\nCopyright (c) 2015 Michael\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n\r\n"
  },
  {
    "path": "lib/md5/README",
    "content": "https://github.com/CommanderBubble/MD5\n\n========================================================\n\nThis library is inspired by the one found at http://256.com/sources/md5/ by Gray Watson. It was written as the aforementioned library is under the GPL license and has the annoying RSA preamble; this has been written from reading the specification document, and does NOT use the provided code, and is distributed under the MIT licence. This notice serves as indication of the program's origins.\n\nThis SHOULD be cross-compatible, but is untested on anything other than windows under msys2/mingw.\n\n* Introduction\n\nThis is a C++ class that implements the Message Digest Algorithm MD5.  The purpose of the algorithm is to calculate a hash of given bit string. MD5 produces a 16 byte (128 bit) hash.\nThe MD5 hash has been proven to be cryptographically vulnerable to various attacks (google them), and as such should not be used for security purposes. The intended purpose of this library is for file verification.\n\n* Building The Library\n\nThe provided makefile will produce a static lib to link against.\nTo build it follow these steps:\n\t1) autoreconf\n\t2) configure\n\t3) make\n\t4) make install\n\t\nYou can then 'make check' to run the library's self checks\n\nBy default the library will compile with the CXXFLAGS '-g -O2'. For a production library without the debugging symbols, call\nmake CXXFLAGS=<your_flags_here>\n\nAdditionally, the test program can be used as a simple hashing application.\nCalling 'md5_t -r -' will generate a signature on the data read from standard in.\nCalling 'md5_t -r filename' will generate a signature from the contents of a file.\n\n* Class Usage\n\nAll functions and constants live in the md5 namespace, or are prefaced by MD5_ if they are in the public namespace.\n\nThe class is called md5_t.  The API consists of five methods:\n\nmd5::md5_t() is the class constructor.\n\nvoid md5_t::process(const void* input, const unsigned int input_length)\n  Processes bytes into the hash.\n  - input is a pointer to the first byte to be added. The bytes are not modified.\n  - input_length is the number of bytes to process. The value is not modified.\n\nvoid md5_t::finish(void* signature_ = NULL)\n  Completes the hashing process and (optionally) returns the final hash.  The md5 instance cannot be used to calculate a new hash after this call.\n  - signature_ is a pointer to a (minimum) 20-byte char array.\n\nif you have all the data available to process at initialisation time, the c=object can be constructed as\nvoid md5_t::md5(const void* input, const unsigned int input_length, void* signature = NULL)\n  - input is a pointer to the first byte to be added. The bytes are not modified.\n  - input_length is the number of bytes to process. The value is not modified.\n\nThere are two functions to retrieve the stored signature and string from the object:\n\nvoid md5_t::get_sig(void* signature_)\n  Returns the previously calculated signature. can only be used after calling finish().\n  - signature_ is a pointer to a (minimum) 16-byte char array.\n  \nvoid md5_t::get_string(void* str_)\n  Returns the previously calculated signature in readable hex format.\n  - str_ is a pointer to a (minimum) 33-byte char array.\n  \nWe also provide two auxiliary functions for converting hashes to strings and strings to hashes, for example if the object has expired. These are not class functions, and can be used without ever creating an object if you already have a signature or string.\n\nvoid md5::sig_to_string(const void* signature_, char* str_)\n  Is a utility method that turns a digest into a human-readable string\n  - signature_ is a pointer to the char array containing the signature. The bytes are not modified.\n  - str_ is a pointer to the char array for the string to be placed into. it must have room for at least 33 bytes; this is the caller's responsibility.\n\nvoid md5::sig_from_string(void* signature_, const char* str_)\n  Is a utility method that turns a digest into a human-readable string\n  - signature_ is a pointer to the char array to place the signature in. it must behave room for at least 16 bytes; this is the caller's responsibility.\n  - str_ is a pointer to the char array containing the string. The bytes are not modified.\n\n\n* Example\n\nThe following program will print one line to stdout:\n900150983cd24fb0d6963f7d28e17f72\n\n#include <string.h>\n#include <cstdlib>\n#include <iostream>\n\n#include <md5.h>\n\nint main(int argc, char** argv) {\n\tconst char* BYTES = \"abc\";\n\n\tmd5::md5_t md5;\n\tmd5.process(BYTES, strlen(BYTES));\n\tmd5.finish();\n\n\tchar str[MD5_STRING_SIZE];\n\n\tmd5.get_string(str);\n\n\tfor (unsigned int i = 0; i < MD5_STRING_SIZE; i++)\n\t\tstd::cout << str[i];\n}\n"
  },
  {
    "path": "lib/md5/md5.cmake",
    "content": "include_directories(${CMAKE_CURRENT_LIST_DIR}/)\n\nset(MD5_SRCS)\n\nlist(APPEND MD5_SRCS\n    ${CMAKE_CURRENT_LIST_DIR}/md5.cpp\n    ${CMAKE_CURRENT_LIST_DIR}/md5tools.cpp\n)\n"
  },
  {
    "path": "lib/md5/md5.cpp",
    "content": "#include <cassert>\n#include <cstring>\n#include <iostream>\n\n// #include \"conf.h\" //TheXTech Absolutely useless header, no macros used from it\n#include \"md5.h\"\n#include \"md5_loc.h\"\n\nnamespace md5 {\n    /****************************** Public Functions ******************************/\n\n    /*\n     * md5_t\n     *\n     * DESCRIPTION:\n     *\n     * Initialize structure containing state of MD5 computation. (RFC 1321,\n     * 3.3: Step 3).  This is for progressive MD5 calculations only.  If\n     * you have the complete string available, call it as below.\n     * process should be called for each bunch of bytes and after the\n     * last process call, finish should be called to get the signature.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * None.\n     */\n    md5_t::md5_t() {\n        initialise();\n    }\n\n    /*\n     * md5_t\n     *\n     * DESCRIPTION:\n     *\n     * This function is used to calculate a MD5 signature for a buffer of\n     * bytes.  If you only have part of a buffer that you want to process\n     * then md5_t, process, and finish should be used.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * buffer - A buffer of bytes whose MD5 signature we are calculating.\n     *\n     * input_length - The length of the buffer.\n     *\n     * signature - A 16 byte buffer that will contain the MD5 signature.\n     */\n    md5_t::md5_t(const void* input, const unsigned int input_length, void* signature) {\n        /* initialize the computation context */\n        initialise();\n\n        /* process whole buffer but last input_length % MD5_BLOCK bytes */\n        process(input, input_length);\n\n        /* put result in desired memory area */\n        finish(signature);\n    }\n\n    /*\n     * process\n     *\n     * DESCRIPTION:\n     *\n     * This function is used to progressively calculate a MD5 signature some\n     * number of bytes at a time.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * buffer - A buffer of bytes whose MD5 signature we are calculating.\n     *\n     * input_length - The length of the buffer.\n     */\n    void md5_t::process(const void* input, const unsigned int input_length) {\n        if (!finished) {\n            unsigned int processed = 0;\n\n            /*\n             * If we have any data stored from a previous call to process then we use these\n             * bytes first, and the new data is large enough to create a complete block then\n             * we process these bytes first.\n             */\n            if (stored_size && input_length + stored_size >= md5::BLOCK_SIZE) {\n                unsigned char block[md5::BLOCK_SIZE];\n                memcpy(block, stored, stored_size);\n                memcpy(block + stored_size, input, md5::BLOCK_SIZE - stored_size);\n                processed = md5::BLOCK_SIZE - stored_size;\n                stored_size = 0;\n                process_block(block);\n            }\n\n            /*\n             * While there is enough data to create a complete block, process it.\n             */\n            while (processed + md5::BLOCK_SIZE <= input_length) {\n                process_block((unsigned char*)input + processed);\n                processed += md5::BLOCK_SIZE;\n            }\n\n            /*\n             * If there are any unprocessed bytes left over that do not create a complete block\n             * then we store these bytes for processing next time.\n             */\n            if (processed != input_length) {\n                memcpy(stored + stored_size, (char*)input + processed, input_length - processed);\n                stored_size += input_length - processed;\n            } else {\n                stored_size = 0;\n            }\n        } else {\n            // throw error when trying to process after completion?\n        }\n    }\n\n    /*\n     * finish\n     *\n     * DESCRIPTION:\n     *\n     * Finish a progressing MD5 calculation and copy the resulting MD5\n     * signature into the result buffer which should be 16 bytes\n     * (MD5_SIZE).  After this call, the MD5 structure cannot process\n\t * additional bytes.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * signature - A 16 byte buffer that will contain the MD5 signature.\n     */\n    void md5_t::finish(void* signature_) {\n        if (!finished) {\n            if (message_length[0] + stored_size < message_length[0])\n                message_length[1]++;\n            message_length[0] += stored_size;\n\n            int pad = md5::BLOCK_SIZE - (sizeof(unsigned int) * 2) - stored_size;\n            if (pad <= 0)\n                pad += md5::BLOCK_SIZE;\n\n            /*\n             * Modified from a fixed array to this assignment and memset to be\n             * more flexible with block-sizes -- Gray 10/97.\n             */\n            if (pad > 0) {\n                stored[stored_size] = 0x80;\n                if (pad > 1)\n                    memset(stored + stored_size + 1, 0, pad - 1);\n                stored_size += pad;\n            }\n\n            /*\n             * Put the 64-bit file length in _bits_ (i.e. *8) at the end of the\n             * buffer. appears to be in beg-endian format in the buffer?\n             */\n            unsigned int size_low = ((message_length[0] & 0x1FFFFFFF) << 3);\n            memcpy(stored + stored_size, &size_low, sizeof(unsigned int));\n            stored_size += sizeof(unsigned int);\n\n            /* shift the high word over by 3 and add in the top 3 bits from the low */\n            unsigned int size_high = (message_length[1] << 3) | ((message_length[0] & 0xE0000000) >> 29);\n            memcpy(stored + stored_size, &size_high, sizeof(unsigned int));\n            stored_size += sizeof(unsigned int);\n\n            /*\n             * process the last block of data.\n             * if the length of the message was already exactly sized, then we have\n             * 2 messages to process\n             */\n            process_block(stored);\n            if (stored_size > md5::BLOCK_SIZE)\n                process_block(stored + md5::BLOCK_SIZE);\n\n            /* Arrange the results into a signature */\n            get_result(static_cast<void*>(signature));\n\n            /* store the signature into a readable sring */\n            sig_to_string(signature, str, MD5_STRING_SIZE);\n\n            if (signature_ != NULL) {\n                memcpy(signature_, static_cast<void*>(signature), MD5_SIZE);\n            }\n\n            finished = true;\n        } else {\n            // add error?\n        }\n    }\n\n    /*\n     * get_sig\n     *\n     * DESCRIPTION:\n     *\n     * Retrieves the previously calculated signature from the MD5 object.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * signature_ - A 16 byte buffer that will contain the MD5 signature.\n     */\n    void md5_t::get_sig(void* signature_) {\n        if (finished) {\n            memcpy(signature_, signature, MD5_SIZE);\n        } else {\n            //error?\n        }\n    }\n\n    /*\n     * get_string\n     *\n     * DESCRIPTION:\n     *\n     * Retrieves the previously calculated signature from the MD5 object in\n     * printable format.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * str_ - a string of characters which should be at least 33 bytes long\n     * (2 characters per MD5 byte and 1 for the \\0).\n     */\n    void md5_t::get_string(void* str_) {\n        if (finished) {\n            memcpy(str_, str, MD5_STRING_SIZE);\n        } else {\n            // error?\n        }\n    }\n\n    /****************************** Private Functions ******************************/\n\n    /*\n     * initialise\n     *\n     * DESCRIPTION:\n     *\n     * Initialize structure containing state of MD5 computation. (RFC 1321,\n     * 3.3: Step 3).\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * None.\n     */\n    void md5_t::initialise() {\n        /*\n         * ensures that unsigned int is 4 bytes on this platform, will need modifying\n         * if we are to use on a different sized platform.\n         */\n        assert(MD5_SIZE == 16);\n\n        A = 0x67452301;\n        B = 0xefcdab89;\n        C = 0x98badcfe;\n        D = 0x10325476;\n\n        message_length[0] = 0;\n        message_length[1] = 0;\n        stored_size = 0;\n\n        memset(stored, 0,BLOCK_SIZE * 2);\n        memset(signature, 0,MD5_SIZE);\n        memset(str, 0,MD5_STRING_SIZE);\n\n        finished = false;\n    }\n\n    /*\n     * process_block\n     *\n     * DESCRIPTION:\n     *\n     * Process a block of bytes into a MD5 state structure.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * buffer - A buffer of bytes whose MD5 signature we are calculating.\n     *\n     * input_length - The length of the buffer.\n     */\n    void md5_t::process_block(const unsigned char* block) {\n    /* Process each 16-word block. */\n\n        /*\n         * we check for when the lower word rolls over, and increment the\n         * higher word. we do not need to worry if the higher word rolls over\n         * as only the two words we maintain are needed in the function later\n         */\n        if (message_length[0] + md5::BLOCK_SIZE < message_length[0])\n            message_length[1]++;\n        message_length[0] += BLOCK_SIZE;\n\n        // Copy the block into X. */\n        unsigned int X[16];\n        for (unsigned int i = 0; i < 16; i++) {\n            memcpy(X + i, block + 4 * i, 4);\n        }\n\n        /* Save A as AA, B as BB, C as CC, and D as DD. */\n        unsigned int AA = A, BB = B, CC = C, DD = D;\n\n        /* Round 1\n         * Let [abcd k s i] denote the operation\n         * a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s)\n         * Do the following 16 operations\n         * [ABCD  0  7  1]  [DABC  1 12  2]  [CDAB  2 17  3]  [BCDA  3 22  4]\n         * [ABCD  4  7  5]  [DABC  5 12  6]  [CDAB  6 17  7]  [BCDA  7 22  8]\n         * [ABCD  8  7  9]  [DABC  9 12 10]  [CDAB 10 17 11]  [BCDA 11 22 12]\n         * [ABCD 12  7 13]  [DABC 13 12 14]  [CDAB 14 17 15]  [BCDA 15 22 16]\n         */\n        md5::FF(A, B, C, D, X[0 ], 0, 0 );\n        md5::FF(D, A, B, C, X[1 ], 1, 1 );\n        md5::FF(C, D, A, B, X[2 ], 2, 2 );\n        md5::FF(B, C, D, A, X[3 ], 3, 3 );\n        md5::FF(A, B, C, D, X[4 ], 0, 4 );\n        md5::FF(D, A, B, C, X[5 ], 1, 5 );\n        md5::FF(C, D, A, B, X[6 ], 2, 6 );\n        md5::FF(B, C, D, A, X[7 ], 3, 7 );\n        md5::FF(A, B, C, D, X[8 ], 0, 8 );\n        md5::FF(D, A, B, C, X[9 ], 1, 9 );\n        md5::FF(C, D, A, B, X[10], 2, 10);\n        md5::FF(B, C, D, A, X[11], 3, 11);\n        md5::FF(A, B, C, D, X[12], 0, 12);\n        md5::FF(D, A, B, C, X[13], 1, 13);\n        md5::FF(C, D, A, B, X[14], 2, 14);\n        md5::FF(B, C, D, A, X[15], 3, 15);\n\n        /* Round 2\n         * Let [abcd k s i] denote the operation\n         * a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s)\n         * Do the following 16 operations\n         * [ABCD  1  5 17]  [DABC  6  9 18]  [CDAB 11 14 19]  [BCDA  0 20 20]\n         * [ABCD  5  5 21]  [DABC 10  9 22]  [CDAB 15 14 23]  [BCDA  4 20 24]\n         * [ABCD  9  5 25]  [DABC 14  9 26]  [CDAB  3 14 27]  [BCDA  8 20 28]\n         * [ABCD 13  5 29]  [DABC  2  9 30]  [CDAB  7 14 31]  [BCDA 12 20 32]\n         */\n        md5::GG(A, B, C, D, X[1 ], 0, 16);\n        md5::GG(D, A, B, C, X[6 ], 1, 17);\n        md5::GG(C, D, A, B, X[11], 2, 18);\n        md5::GG(B, C, D, A, X[0 ], 3, 19);\n        md5::GG(A, B, C, D, X[5 ], 0, 20);\n        md5::GG(D, A, B, C, X[10], 1, 21);\n        md5::GG(C, D, A, B, X[15], 2, 22);\n        md5::GG(B, C, D, A, X[4 ], 3, 23);\n        md5::GG(A, B, C, D, X[9 ], 0, 24);\n        md5::GG(D, A, B, C, X[14], 1, 25);\n        md5::GG(C, D, A, B, X[3 ], 2, 26);\n        md5::GG(B, C, D, A, X[8 ], 3, 27);\n        md5::GG(A, B, C, D, X[13], 0, 28);\n        md5::GG(D, A, B, C, X[2 ], 1, 29);\n        md5::GG(C, D, A, B, X[7 ], 2, 30);\n        md5::GG(B, C, D, A, X[12], 3, 31);\n\n        /* Round 3\n         * Let [abcd k s i] denote the operation\n         * a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s)\n         * Do the following 16 operations\n         * [ABCD  5  4 33]  [DABC  8 11 34]  [CDAB 11 16 35]  [BCDA 14 23 36]\n         * [ABCD  1  4 37]  [DABC  4 11 38]  [CDAB  7 16 39]  [BCDA 10 23 40]\n         * [ABCD 13  4 41]  [DABC  0 11 42]  [CDAB  3 16 43]  [BCDA  6 23 44]\n         * [ABCD  9  4 45]  [DABC 12 11 46]  [CDAB 15 16 47]  [BCDA  2 23 48]\n         */\n        md5::HH(A, B, C, D, X[5 ], 0, 32);\n        md5::HH(D, A, B, C, X[8 ], 1, 33);\n        md5::HH(C, D, A, B, X[11], 2, 34);\n        md5::HH(B, C, D, A, X[14], 3, 35);\n        md5::HH(A, B, C, D, X[1 ], 0, 36);\n        md5::HH(D, A, B, C, X[4 ], 1, 37);\n        md5::HH(C, D, A, B, X[7 ], 2, 38);\n        md5::HH(B, C, D, A, X[10], 3, 39);\n        md5::HH(A, B, C, D, X[13], 0, 40);\n        md5::HH(D, A, B, C, X[0 ], 1, 41);\n        md5::HH(C, D, A, B, X[3 ], 2, 42);\n        md5::HH(B, C, D, A, X[6 ], 3, 43);\n        md5::HH(A, B, C, D, X[9 ], 0, 44);\n        md5::HH(D, A, B, C, X[12], 1, 45);\n        md5::HH(C, D, A, B, X[15], 2, 46);\n        md5::HH(B, C, D, A, X[2 ], 3, 47);\n\n        /* Round 4\n         * Let [abcd k s i] denote the operation\n         * a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s)\n         * Do the following 16 operations\n         * [ABCD  0  6 49]  [DABC  7 10 50]  [CDAB 14 15 51]  [BCDA  5 21 52]\n         * [ABCD 12  6 53]  [DABC  3 10 54]  [CDAB 10 15 55]  [BCDA  1 21 56]\n         * [ABCD  8  6 57]  [DABC 15 10 58]  [CDAB  6 15 59]  [BCDA 13 21 60]\n         * [ABCD  4  6 61]  [DABC 11 10 62]  [CDAB  2 15 63]  [BCDA  9 21 64]\n         */\n        md5::II(A, B, C, D, X[0 ], 0, 48);\n        md5::II(D, A, B, C, X[7 ], 1, 49);\n        md5::II(C, D, A, B, X[14], 2, 50);\n        md5::II(B, C, D, A, X[5 ], 3, 51);\n        md5::II(A, B, C, D, X[12], 0, 52);\n        md5::II(D, A, B, C, X[3 ], 1, 53);\n        md5::II(C, D, A, B, X[10], 2, 54);\n        md5::II(B, C, D, A, X[1 ], 3, 55);\n        md5::II(A, B, C, D, X[8 ], 0, 56);\n        md5::II(D, A, B, C, X[15], 1, 57);\n        md5::II(C, D, A, B, X[6 ], 2, 58);\n        md5::II(B, C, D, A, X[13], 3, 59);\n        md5::II(A, B, C, D, X[4 ], 0, 60);\n        md5::II(D, A, B, C, X[11], 1, 61);\n        md5::II(C, D, A, B, X[2 ], 2, 62);\n        md5::II(B, C, D, A, X[9 ], 3, 63);\n\n        /* Then perform the following additions. (That is increment each\n        of the four registers by the value it had before this block\n        was started.) */\n        A += AA;\n        B += BB;\n        C += CC;\n        D += DD;\n    }\n\n    /*\n     * get_result\n     *\n     * DESCRIPTION:\n     *\n     * Copy the resulting MD5 signature into the first 16 bytes (MD5_SIZE)\n     * of the result buffer.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * result - A 16 byte buffer that will contain the MD5 signature.\n     */\n    void md5_t::get_result(void *result) {\n        memcpy((char*)result, &A, sizeof(unsigned int));\n        memcpy((char*)result + sizeof(unsigned int), &B, sizeof(unsigned int));\n        memcpy((char*)result + 2 * sizeof(unsigned int), &C, sizeof(unsigned int));\n        memcpy((char*)result + 3 * sizeof(unsigned int), &D, sizeof(unsigned int));\n    }\n\n    /****************************** Exported Functions ******************************/\n\n    /*\n     * sig_to_string\n     *\n     * DESCRIPTION:\n     *\n     * Convert a MD5 signature in a 16 byte buffer into a hexadecimal string\n     * representation.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * signature_ - a 16 byte buffer that contains the MD5 signature.\n     *\n     * str_ - a string of charactes which should be at least 33 bytes long (2\n     * characters per MD5 byte and 1 for the \\0).\n     *\n     * str_len - the length of the string.\n     */\n    void sig_to_string(const void* signature_, char* str_, const int str_len) {\n        unsigned char* sig_p;\n        char* str_p;\n        char* max_p;\n        unsigned int high, low;\n\n        str_p = str_;\n        max_p = str_ + str_len;\n\n        for (sig_p = (unsigned char*)signature_; sig_p < (unsigned char*)signature_ + MD5_SIZE; sig_p++) {\n            high = *sig_p / 16;\n            low = *sig_p % 16;\n            /* account for 2 chars */\n            if (str_p + 1 >= max_p) {\n                break;\n            }\n            *str_p++ = md5::HEX_STRING[high];\n            *str_p++ = md5::HEX_STRING[low];\n        }\n        /* account for 2 chars */\n        if (str_p < max_p) {\n            *str_p++ = '\\0';\n        }\n    }\n\n    /*\n     * sig_from_string\n     *\n     * DESCRIPTION:\n     *\n     * Convert a MD5 signature from a hexadecimal string representation into\n     * a 16 byte buffer.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * signature_ - A 16 byte buffer that will contain the MD5 signature.\n     *\n     * str_ - A string of charactes which _must_ be at least 32 bytes long (2\n     * characters per MD5 byte).\n     */\n    void sig_from_string(void* signature_, const char* str_) {\n        unsigned char *sig_p;\n        const char *str_p;\n        char* hex;\n        unsigned int high, low, val;\n\n        hex = (char*)md5::HEX_STRING;\n        sig_p = static_cast<unsigned char*>(signature_);\n\n        for (str_p = str_; str_p < str_ + MD5_SIZE * 2; str_p += 2) {\n            high = strchr(hex, *str_p) - hex;\n            low = strchr(hex, *(str_p + 1)) - hex;\n            val = high * 16 + low;\n            *sig_p++ = val;\n        }\n    }\n} // namespace md5\n\n"
  },
  {
    "path": "lib/md5/md5.h",
    "content": "#ifndef __MD5_H__\n#define __MD5_H__\n\n#include <stddef.h>\n\n/*\n * Size of a standard MD5 signature in bytes.  This definition is for\n * external programs only.  The MD5 routines themselves reference the\n * signature as 4 unsigned 32-bit integers.\n */\nconst unsigned int MD5_SIZE = (4 * sizeof(unsigned int));   /* 16 */\nconst unsigned int MD5_STRING_SIZE = 2 * MD5_SIZE + 1;      /* 33 */\n\n namespace md5 {\n    /*\n     * The MD5 algorithm works on blocks of characters of 64 bytes.  This\n     * is an internal value only and is not necessary for external use.\n     */\n    const unsigned int BLOCK_SIZE = 64;\n\n    class md5_t {\n        public:\n            /*\n             * md5_t\n             *\n             * DESCRIPTION:\n             *\n             * Initialize structure containing state of MD5 computation. (RFC 1321,\n             * 3.3: Step 3).  This is for progressive MD5 calculations only.  If\n             * you have the complete string available, call it as below.\n             * process should be called for each bunch of bytes and after the last\n             * process call, finish should be called to get the signature.\n             *\n             * RETURNS:\n             *\n             * None.\n             *\n             * ARGUMENTS:\n             *\n             * None.\n             */\n            md5_t();\n\n            /*\n             * md5_t\n             *\n             * DESCRIPTION:\n             *\n             * This function is used to calculate a MD5 signature for a buffer of\n             * bytes.  If you only have part of a buffer that you want to process\n             * then md5_t, process, and finish should be used.\n             *\n             * RETURNS:\n             *\n             * None.\n             *\n             * ARGUMENTS:\n             *\n             * input - A buffer of bytes whose MD5 signature we are calculating.\n             *\n             * input_length - The length of the buffer.\n             *\n             * signature_ - A 16 byte buffer that will contain the MD5 signature.\n             */\n            md5_t(const void* input, const unsigned int input_length, void* signature_ = NULL);\n\n            /*\n             * process\n             *\n             * DESCRIPTION:\n             *\n             * This function is used to progressively calculate an MD5 signature some\n             * number of bytes at a time.\n             *\n             * RETURNS:\n             *\n             * None.\n             *\n             * ARGUMENTS:\n             *\n             * input - A buffer of bytes whose MD5 signature we are calculating.\n             *\n             * input_length - The length of the buffer.\n             */\n            void process(const void* input, const unsigned int input_length);\n\n            /*\n             * finish\n             *\n             * DESCRIPTION:\n             *\n             * Finish a progressing MD5 calculation and copy the resulting MD5\n             * signature into the result buffer which should be 16 bytes\n             * (MD5_SIZE).  After this call, the MD5 structure cannot be used\n             * to calculate a new md5, it can only return its signature.\n             *\n             * RETURNS:\n             *\n             * None.\n             *\n             * ARGUMENTS:\n             *\n             * signature_ - A 16 byte buffer that will contain the MD5 signature.\n             */\n            void finish(void* signature_ = NULL);\n\n            /*\n             * get_sig\n             *\n             * DESCRIPTION:\n             *\n             * Retrieves the previously calculated signature from the MD5 object.\n             *\n             * RETURNS:\n             *\n             * None.\n             *\n             * ARGUMENTS:\n             *\n             * signature_ - A 16 byte buffer that will contain the MD5 signature.\n             */\n            void get_sig(void* signature_);\n\n            /*\n             * get_string\n             *\n             * DESCRIPTION:\n             *\n             * Retrieves the previously calculated signature from the MD5 object in\n             * printable format.\n             *\n             * RETURNS:\n             *\n             * None.\n             *\n             * ARGUMENTS:\n             *\n             * str_ - a string of characters which should be at least 33 bytes long\n             * (2 characters per MD5 byte and 1 for the \\0).\n             */\n            void get_string(void* str_);\n\n        private:\n            /* internal functions */\n            void initialise();\n            void process_block(const unsigned char*);\n            void get_result(void*);\n\n            unsigned int A;                             /* accumulator 1 */\n            unsigned int B;                             /* accumulator 2 */\n            unsigned int C;                             /* accumulator 3 */\n            unsigned int D;                             /* accumulator 4 */\n\n            unsigned int message_length[2];             /* length of data */\n            unsigned int stored_size;                   /* length of stored bytes */\n            unsigned char stored[md5::BLOCK_SIZE * 2];  /* stored bytes */\n\n            bool finished;                              /* object state */\n\n            char signature[MD5_SIZE];                   /* stored signature */\n            char str[MD5_STRING_SIZE];                  /* stored plain text hash */\n    };\n\n    /*\n     * sig_to_string\n     *\n     * DESCRIPTION:\n     *\n     * Convert a MD5 signature in a 16 byte buffer into a hexadecimal string\n     * representation.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * signature - a 16 byte buffer that contains the MD5 signature.\n     *\n     * str - a string of characters which should be at least 33 bytes long (2\n     * characters per MD5 byte and 1 for the \\0).\n     *\n     * str_len - the length of the string.\n     */\n    extern void sig_to_string(const void* signature, char* str, const int str_len);\n\n    /*\n     * sig_from_string\n     *\n     * DESCRIPTION:\n     *\n     * Convert a MD5 signature from a hexadecimal string representation into\n     * a 16 byte buffer.\n     *\n     * RETURNS:\n     *\n     * None.\n     *\n     * ARGUMENTS:\n     *\n     * signature - A 16 byte buffer that will contain the MD5 signature.\n     *\n     * str - A string of charactes which _must_ be at least 32 bytes long (2\n     * characters per MD5 byte).\n     */\n    extern void sig_from_string(void* signature, const char* str);\n} // namespace md5\n\n#endif /* ! __MD5_H__ */\n"
  },
  {
    "path": "lib/md5/md5_loc.h",
    "content": "/*\n * Local defines for the md5 functions.\n *\n * $Id: md5_loc.h,v 1.5 2010-05-07 13:58:18 gray Exp $\n */\n\n#ifndef __MD5_LOC_H__\n#define __MD5_LOC_H__\n\n/*\n * We don't include \"conf.h\" here because it gets included before this file in md5.cpp so the defines\n * are correctly determing before they are checked.\n */\n #if MD5_DEBUG\n    #include <iostream>\n#endif // MD5_DEBUG\n\n/// For now we are assuming everything is in little endian byte-order\n\nnamespace md5\n{\n    /*\n     * NOTE: the following is assumed to generate a 32-bit unsigned data\n     * type.\n     */\n    const unsigned int X_UINT32_MAX = 4294967295U;\n\n    /*\n     * T denotes the integer part of the i-th element of the function:\n     * T[i] = 4294967296 * abs(sin(i)), where i is in radians.\n     */\n    const unsigned int T[64] = {\n        0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,\n        0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,\n        0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,\n        0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,\n        0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,\n        0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,\n        0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,\n        0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391\n    };\n\n    /*\n     * Constants for the MD5 Transform routine as defined in RFC 1321\n     */\n    const unsigned int S1[4] = {7, 12, 17, 22};\n    const unsigned int S2[4] = {5, 9,  14, 20};\n    const unsigned int S3[4] = {4, 11, 16, 23};\n    const unsigned int S4[4] = {6, 10, 15, 21};\n\n    /*\n     * Function to perform the cyclic left rotation of blocks of data\n     */\n    inline unsigned int cyclic_left_rotate(unsigned int data, unsigned int shift_bits) {\n        return (data << shift_bits) | (data >> (32 - shift_bits));\n    }\n\n    inline unsigned int F(unsigned int x, unsigned int y, unsigned int z) {return (x & y) | (~x & z);}\n    inline unsigned int G(unsigned int x, unsigned int y, unsigned int z) {return (x & z) | (y & ~z);}\n    inline unsigned int H(unsigned int x, unsigned int y, unsigned int z) {return x ^ y ^ z;}\n    inline unsigned int I(unsigned int x, unsigned int y, unsigned int z) {return y ^ (x | ~z);}\n\n    inline void FF(unsigned int &a, unsigned int b, unsigned int c, unsigned int d, unsigned int Xk, unsigned int s, unsigned int i) {\n        #if MD5_DEBUG\n            std::cout << \"\\nA: \" << a << \"\\nB: \" << b << \"\\nC: \" << c << \"\\nD: \" << d << \"\\nX[\" << i << \"]: \" << Xk << \"\\ns: \" << S1[s] << \"\\nT: \" << T[i] << \"\\n\";\n        #endif\n\n        a += F(b,c,d) + Xk + T[i];\n        a = cyclic_left_rotate(a, S1[s]);\n        a += b;\n\n        #if MD5_DEBUG\n            std::cout << \"A = \" << a << \"\\n\";\n        #endif\n    }\n\n    inline void GG(unsigned int &a, unsigned int b, unsigned int c, unsigned int d, unsigned int Xk, unsigned int s, unsigned int i) {\n        #if MD5_DEBUG\n            std::cout << \"\\nA: \" << a << \"\\nB: \" << b << \"\\nC: \" << c << \"\\nD: \" << d << \"\\nX[\" << i - 16 << \"]: \" << Xk << \"\\ns: \" << S2[s] << \"\\nT: \" << T[i] << \"\\n\";\n        #endif // MD5_DEBUG\n\n        a += G(b,c,d) + Xk + T[i];\n        a = cyclic_left_rotate(a, S2[s]);\n        a += b;\n\n        #if MD5_DEBUG\n            std::cout << \"A = \" << a << \"\\n\";\n        #endif // MD5_DEBUG\n    }\n\n    inline void HH(unsigned int &a, unsigned int b, unsigned int c, unsigned int d, unsigned int Xk, unsigned int s, unsigned int i) {\n        #if MD5_DEBUG\n            std::cout << \"\\nA: \" << a << \"\\nB: \" << b << \"\\nC: \" << c << \"\\nD: \" << d << \"\\nX[\" << i - 32 << \"]: \" << Xk << \"\\ns: \" << S3[s] << \"\\nT: \" << T[i] << \"\\n\";\n        #endif // MD5_DEBUG\n\n        a += H(b,c,d) + Xk + T[i];\n        a = cyclic_left_rotate(a, S3[s]);\n        a += b;\n\n        #if MD5_DEBUG\n            std::cout << \"A = \" << a << \"\\n\";\n        #endif // MD5_DEBUG\n    }\n    inline void II(unsigned int &a, unsigned int b, unsigned int c, unsigned int d, unsigned int Xk, unsigned int s, unsigned int i) {\n        #if MD5_DEBUG\n            std::cout << \"\\nA: \" << a << \"\\nB: \" << b << \"\\nC: \" << c << \"\\nD: \" << d << \"\\nX[\" << i - 48 << \"]: \" << Xk << \"\\ns: \" << S4[s] << \"\\nT: \" << T[i] << \"\\n\";\n        #endif // MD5_DEBUG\n\n        a += I(b,c,d) + Xk + T[i];\n        a = cyclic_left_rotate(a, S4[s]);\n        a += b;\n\n        #if MD5_DEBUG\n            std::cout << \"A = \" << a << \"\\n\";\n        #endif // MD5_DEBUG\n    }\n\n    /*\n     * Define my endian-ness.  Could not do in a portable manner using the\n     * include files -- grumble.\n     */\n    #if MD5_BIG_ENDIAN\n\n    /*\n     * big endian - big is better\n     */\n    #define MD5_SWAP(n) (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))\n\n    #else\n\n    /*\n     * little endian\n     */\n    #define MD5_SWAP(n) (n)\n\n    #endif // MD5_BIG_ENDIAN\n\n    const char* HEX_STRING = \"0123456789abcdef\";    /* to convert to hex */\n}\n\n#endif /* ! __MD5_LOC_H__ */\n"
  },
  {
    "path": "lib/md5/md5tools.cpp",
    "content": "#include \"md5tools.hpp\"\n#include \"../Utils/files.h\"\n#include <SDL2/SDL_rwops.h>\n\n\nstd::string md5::string_to_hash(const std::string &input)\n{\n    std::string out;\n    md5::md5_t h;\n\n    h.process((const void*)input.c_str(), (int)input.size());\n    h.finish();\n\n    out.resize(MD5_STRING_SIZE - 1);\n\n    h.get_string(&out[0]);\n\n    return out;\n}\n\nuint32_t md5::string_to_u32(const std::string &input)\n{\n    md5::md5_t h;\n\n    h.process((const void*)input.c_str(), (int)input.size());\n    h.finish();\n\n    uint8_t out[16];\n\n    h.get_sig(out);\n\n    return out[0] | ((uint32_t)out[1] << 8) | ((uint32_t)out[2] << 16) | ((uint32_t)out[3] << 24);\n}\n\nstd::string md5::file_to_hash(const std::string &filePath)\n{\n    char block[BLOCK_SIZE];\n    std::string out;\n    SDL_RWops *f = Files::open_file(filePath, \"rb\");\n    size_t got;\n\n    if(!f)\n        return out;\n\n    md5::md5_t h;\n\n    do\n    {\n        got = SDL_RWread(f, (void*)block, 1, 64);\n        h.process((const void*)block, (int)got);\n    } while(got == 64);\n\n    h.finish();\n    out.resize(MD5_STRING_SIZE - 1);\n    h.get_string(&out[0]);\n\n    SDL_RWclose(f);\n\n    return out;\n}\n\nstd::string md5::mem_to_hash(const void *data, size_t size)\n{\n    std::string out;\n    md5::md5_t h;\n\n    h.process((const void*)data, (int)size);\n    h.finish();\n    out.resize(MD5_STRING_SIZE - 1);\n    h.get_string(&out[0]);\n\n    return out;\n}\n\nstd::string md5::file_to_hashGC(const std::string &filePath)\n{\n    std::string out = file_to_hash(filePath);\n\n    for(size_t i = 0; i < out.size(); ++i)\n        out[i] = std::toupper(out[i]);\n\n    return out;\n}\n"
  },
  {
    "path": "lib/md5/md5tools.hpp",
    "content": "#include <string>\n#include <cstdint>\n#include \"md5.h\"\n\nnamespace md5\n{\nextern uint32_t    string_to_u32(const std::string &input);\nextern std::string string_to_hash(const std::string &input);\nextern std::string file_to_hash(const std::string &filePath);\nextern std::string file_to_hashGC(const std::string &filePath);\nextern std::string mem_to_hash(const void *data, size_t size);\n}\n"
  },
  {
    "path": "lib/numeric_types.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef XT_NUMERIC_TYPES_H\n#define XT_NUMERIC_TYPES_H\n\n#ifdef THEXTECH_FIXED_POINT\n#   include \"fixed_point.h\"\n#else\n#\tinclude \"floating_point.h\"\n#endif\n\n#endif // #ifndef XT_NUMERIC_TYPES_H\n"
  },
  {
    "path": "lib/pcg/LICENSE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "lib/pcg/README.md",
    "content": "# PCG Random Number Generation, C++ Edition\r\n\r\n[PCG-Random website]: http://www.pcg-random.org\r\n\r\nThis code provides an implementation of the PCG family of random number\r\ngenerators, which are fast, statistically excellent, and offer a number of\r\nuseful features.\r\n\r\nFull details can be found at the [PCG-Random website].  This version\r\nof the code provides many family members -- if you just want one\r\nsimple generator, you may prefer the minimal C version of the library.\r\n\r\nThere are two kinds of generator, normal generators and extended generators.\r\nExtended generators provide *k* dimensional equidistribution and can perform\r\nparty tricks, but generally speaking most people only need the normal\r\ngenerators.\r\n\r\nThere are two ways to access the generators, using a convenience typedef\r\nor by using the underlying templates directly (similar to C++11's `std::mt19937` typedef vs its `std::mersenne_twister_engine` template).  For most users, the convenience typedef is what you want, and probably you're fine with `pcg32` for 32-bit numbers.  If you want 64-bit numbers, either use `pcg64` (or, if you're on a 32-bit system, making 64 bits from two calls to `pcg32_k2` may be faster).\r\n\r\n## Documentation and Examples\r\n\r\nVisit [PCG-Random website] for information on how to use this library, or look\r\nat the sample code in the `sample` directory -- hopefully it should be fairly\r\nself explanatory.\r\n\r\n## Building\r\n\r\nThe code is written in C++11, as an include-only library (i.e., there is\r\nnothing you need to build).  There are some provided demo programs and tests\r\nhowever.  On a Unix-style system (e.g., Linux, Mac OS X) you should be able\r\nto just type type\r\n\r\n    make\r\n\r\nTo build the demo programs.\r\n\r\n## Testing\r\n\r\nRun\r\n\r\n    make test\r\n\r\n## Directory Structure\r\n\r\nThe directories are arranged as follows:\r\n\r\n* `include` -- contains `pcg_random.hpp` and supporting include files\r\n* `test-high` -- test code for the high-level API where the functions have\r\n  shorter, less scary-looking names.\r\n* `sample` -- sample code, some similar to the code in `test-high` but more \r\n  human readable, some other examples too\r\n"
  },
  {
    "path": "lib/pcg/pcg_extras.hpp",
    "content": "/*\n * PCG Random Number Generation for C++\n *\n * Copyright 2014 Melissa O'Neill <oneill@pcg-random.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * For additional information about the PCG random number generation scheme,\n * including its license and other licensing options, visit\n *\n *     http://www.pcg-random.org\n */\n\n/*\n * This file provides support code that is useful for random-number generation\n * but not specific to the PCG generation scheme, including:\n *      - 128-bit int support for platforms where it isn't available natively\n *      - bit twiddling operations\n *      - I/O of 128-bit and 8-bit integers\n *      - Handling the evilness of SeedSeq\n *      - Support for efficiently producing random numbers less than a given\n *        bound\n */\n\n#ifndef PCG_EXTRAS_HPP_INCLUDED\n#define PCG_EXTRAS_HPP_INCLUDED 1\n\n#include <cinttypes>\n#include <cstddef>\n#include <cstdlib>\n#include <cstring>\n#include <cassert>\n#include <limits>\n// #include <iostream>\n#include <type_traits>\n#include <utility>\n// #include <locale>\n#include <iterator>\n#include <utility>\n\n#ifdef __GNUC__\n    #include <cxxabi.h>\n#endif\n\n/*\n * Abstractions for compiler-specific directives\n */\n\n#ifdef __GNUC__\n    #define PCG_NOINLINE __attribute__((noinline))\n#else\n    #define PCG_NOINLINE\n#endif\n\n\nnamespace pcg_extras {\n\n/*\n * We often need to represent a \"number of bits\".  When used normally, these\n * numbers are never greater than 128, so an unsigned char is plenty.\n * If you're using a nonstandard generator of a larger size, you can set\n * PCG_BITCOUNT_T to have it define it as a larger size.  (Some compilers\n * might produce faster code if you set it to an unsigned int.)\n */\n\n#ifndef PCG_BITCOUNT_T\n    typedef uint8_t bitcount_t;\n#else\n    typedef PCG_BITCOUNT_T bitcount_t;\n#endif\n\n/*\n * C++ requires us to be able to serialize RNG state by printing or reading\n * it from a stream.  Because we use 128-bit ints, we also need to be able\n * ot print them, so here is code to do so.\n *\n * This code provides enough functionality to print 128-bit ints in decimal\n * and zero-padded in hex.  It's not a full-featured implementation.\n */\n\n/*\n * Likewise, if people use tiny rngs, we'll be serializing uint8_t.\n * If we just used the provided IO operators, they'd read/write chars,\n * not ints, so we need to define our own.  We *can* redefine this operator\n * here because we're in our own namespace.\n */\n\n/* Unfortunately, the above functions don't get found in preference to the\n * built in ones, so we create some more specific overloads that will.\n * Ugh.\n */\n\n\n\n/*\n * Useful bitwise operations.\n */\n\n/*\n * XorShifts are invertable, but they are someting of a pain to invert.\n * This function backs them out.  It's used by the whacky \"inside out\"\n * generator defined later.\n */\n\ntemplate <typename itype>\ninline itype unxorshift(itype x, bitcount_t bits, bitcount_t shift)\n{\n    if (2*shift >= bits) {\n        return x ^ (x >> shift);\n    }\n    itype lowmask1 = (itype(1U) << (bits - shift*2)) - 1;\n    itype highmask1 = ~lowmask1;\n    itype top1 = x;\n    itype bottom1 = x & lowmask1;\n    top1 ^= top1 >> shift;\n    top1 &= highmask1;\n    x = top1 | bottom1;\n    itype lowmask2 = (itype(1U) << (bits - shift)) - 1;\n    itype bottom2 = x & lowmask2;\n    bottom2 = unxorshift(bottom2, bits - shift, shift);\n    bottom2 &= lowmask1;\n    return top1 | bottom2;\n}\n\n/*\n * Rotate left and right.\n *\n * In ideal world, compilers would spot idiomatic rotate code and convert it\n * to a rotate instruction.  Of course, opinions vary on what the correct\n * idiom is and how to spot it.  For clang, sometimes it generates better\n * (but still crappy) code if you define PCG_USE_ZEROCHECK_ROTATE_IDIOM.\n */\n\ntemplate <typename itype>\ninline itype rotl(itype value, bitcount_t rot)\n{\n    constexpr bitcount_t bits = sizeof(itype) * 8;\n    constexpr bitcount_t mask = bits - 1;\n#if PCG_USE_ZEROCHECK_ROTATE_IDIOM\n    return rot ? (value << rot) | (value >> (bits - rot)) : value;\n#else\n    return (value << rot) | (value >> ((- rot) & mask));\n#endif\n}\n\ntemplate <typename itype>\ninline itype rotr(itype value, bitcount_t rot)\n{\n    constexpr bitcount_t bits = sizeof(itype) * 8;\n    constexpr bitcount_t mask = bits - 1;\n#if PCG_USE_ZEROCHECK_ROTATE_IDIOM\n    return rot ? (value >> rot) | (value << (bits - rot)) : value;\n#else\n    return (value >> rot) | (value << ((- rot) & mask));\n#endif\n}\n\n/* Unfortunately, both Clang and GCC sometimes perform poorly when it comes\n * to properly recognizing idiomatic rotate code, so for we also provide\n * assembler directives (enabled with PCG_USE_INLINE_ASM).  Boo, hiss.\n * (I hope that these compilers get better so that this code can die.)\n *\n * These overloads will be preferred over the general template code above.\n */\n#if PCG_USE_INLINE_ASM && __GNUC__ && (__x86_64__  || __i386__)\n\ninline uint8_t rotr(uint8_t value, bitcount_t rot)\n{\n    asm (\"rorb   %%cl, %0\" : \"=r\" (value) : \"0\" (value), \"c\" (rot));\n    return value;\n}\n\ninline uint16_t rotr(uint16_t value, bitcount_t rot)\n{\n    asm (\"rorw   %%cl, %0\" : \"=r\" (value) : \"0\" (value), \"c\" (rot));\n    return value;\n}\n\ninline uint32_t rotr(uint32_t value, bitcount_t rot)\n{\n    asm (\"rorl   %%cl, %0\" : \"=r\" (value) : \"0\" (value), \"c\" (rot));\n    return value;\n}\n\n#if __x86_64__\ninline uint64_t rotr(uint64_t value, bitcount_t rot)\n{\n    asm (\"rorq   %%cl, %0\" : \"=r\" (value) : \"0\" (value), \"c\" (rot));\n    return value;\n}\n#endif // __x86_64__\n\n#endif // PCG_USE_INLINE_ASM\n\n\n/*\n * The C++ SeedSeq concept (modelled by seed_seq) can fill an array of\n * 32-bit integers with seed data, but sometimes we want to produce\n * larger or smaller integers.\n *\n * The following code handles this annoyance.\n *\n * uneven_copy will copy an array of 32-bit ints to an array of larger or\n * smaller ints (actually, the code is general it only needing forward\n * iterators).  The copy is identical to the one that would be performed if\n * we just did memcpy on a standard little-endian machine, but works\n * regardless of the endian of the machine (or the weirdness of the ints\n * involved).\n *\n * generate_to initializes an array of integers using a SeedSeq\n * object.  It is given the size as a static constant at compile time and\n * tries to avoid memory allocation.  If we're filling in 32-bit constants\n * we just do it directly.  If we need a separate buffer and it's small,\n * we allocate it on the stack.  Otherwise, we fall back to heap allocation.\n * Ugh.\n *\n * generate_one produces a single value of some integral type using a\n * SeedSeq object.\n */\n\n /* uneven_copy helper, case where destination ints are less than 32 bit. */\n\ntemplate<class SrcIter, class DestIter>\nSrcIter uneven_copy_impl(\n    SrcIter src_first, DestIter dest_first, DestIter dest_last,\n    std::true_type)\n{\n    typedef typename std::iterator_traits<SrcIter>::value_type  src_t;\n    typedef typename std::iterator_traits<DestIter>::value_type dest_t;\n\n    constexpr bitcount_t SRC_SIZE  = sizeof(src_t);\n    constexpr bitcount_t DEST_SIZE = sizeof(dest_t);\n    constexpr bitcount_t DEST_BITS = DEST_SIZE * 8;\n    constexpr bitcount_t SCALE     = SRC_SIZE / DEST_SIZE;\n\n    size_t count = 0;\n    src_t value;\n\n    while (dest_first != dest_last) {\n        if ((count++ % SCALE) == 0)\n            value = *src_first++;       // Get more bits\n        else\n            value >>= DEST_BITS;        // Move down bits\n\n        *dest_first++ = dest_t(value);  // Truncates, ignores high bits.\n    }\n    return src_first;\n}\n\n /* uneven_copy helper, case where destination ints are more than 32 bit. */\n\ntemplate<class SrcIter, class DestIter>\nSrcIter uneven_copy_impl(\n    SrcIter src_first, DestIter dest_first, DestIter dest_last,\n    std::false_type)\n{\n    typedef typename std::iterator_traits<SrcIter>::value_type  src_t;\n    typedef typename std::iterator_traits<DestIter>::value_type dest_t;\n\n    constexpr auto SRC_SIZE  = sizeof(src_t);\n    constexpr auto SRC_BITS  = SRC_SIZE * 8;\n    constexpr auto DEST_SIZE = sizeof(dest_t);\n    constexpr auto SCALE     = (DEST_SIZE+SRC_SIZE-1) / SRC_SIZE;\n\n    while (dest_first != dest_last) {\n        dest_t value(0UL);\n        unsigned int shift = 0;\n\n        for (size_t i = 0; i < SCALE; ++i) {\n            value |= dest_t(*src_first++) << shift;\n            shift += SRC_BITS;\n        }\n\n        *dest_first++ = value;\n    }\n    return src_first;\n}\n\n/* uneven_copy, call the right code for larger vs. smaller */\n\ntemplate<class SrcIter, class DestIter>\ninline SrcIter uneven_copy(SrcIter src_first,\n                           DestIter dest_first, DestIter dest_last)\n{\n    typedef typename std::iterator_traits<SrcIter>::value_type  src_t;\n    typedef typename std::iterator_traits<DestIter>::value_type dest_t;\n\n    constexpr bool DEST_IS_SMALLER = sizeof(dest_t) < sizeof(src_t);\n\n    return uneven_copy_impl(src_first, dest_first, dest_last,\n                            std::integral_constant<bool, DEST_IS_SMALLER>{});\n}\n\n/* generate_to, fill in a fixed-size array of integral type using a SeedSeq\n * (actually works for any random-access iterator)\n */\n\ntemplate <size_t size, typename SeedSeq, typename DestIter>\ninline void generate_to_impl(SeedSeq&& generator, DestIter dest,\n                             std::true_type)\n{\n    generator.generate(dest, dest+size);\n}\n\ntemplate <size_t size, typename SeedSeq, typename DestIter>\nvoid generate_to_impl(SeedSeq&& generator, DestIter dest,\n                      std::false_type)\n{\n    typedef typename std::iterator_traits<DestIter>::value_type dest_t;\n    constexpr auto DEST_SIZE = sizeof(dest_t);\n    constexpr auto GEN_SIZE  = sizeof(uint32_t);\n\n    constexpr bool GEN_IS_SMALLER = GEN_SIZE < DEST_SIZE;\n    constexpr size_t FROM_ELEMS =\n        GEN_IS_SMALLER\n            ? size * ((DEST_SIZE+GEN_SIZE-1) / GEN_SIZE)\n            : (size + (GEN_SIZE / DEST_SIZE) - 1)\n                / ((GEN_SIZE / DEST_SIZE) + GEN_IS_SMALLER);\n                        //  this odd code ^^^^^^^^^^^^^^^^^ is work-around for\n                        //  a bug: http://llvm.org/bugs/show_bug.cgi?id=21287\n\n    if (FROM_ELEMS <= 1024) {\n        uint32_t buffer[FROM_ELEMS];\n        generator.generate(buffer, buffer+FROM_ELEMS);\n        uneven_copy(buffer, dest, dest+size);\n    } else {\n        uint32_t* buffer = (uint32_t*) malloc(GEN_SIZE * FROM_ELEMS);\n        generator.generate(buffer, buffer+FROM_ELEMS);\n        uneven_copy(buffer, dest, dest+size);\n        free(buffer);\n    }\n}\n\ntemplate <size_t size, typename SeedSeq, typename DestIter>\ninline void generate_to(SeedSeq&& generator, DestIter dest)\n{\n    typedef typename std::iterator_traits<DestIter>::value_type dest_t;\n    constexpr bool IS_32BIT = sizeof(dest_t) == sizeof(uint32_t);\n\n    generate_to_impl<size>(std::forward<SeedSeq>(generator), dest,\n                           std::integral_constant<bool, IS_32BIT>{});\n}\n\n/* generate_one, produce a value of integral type using a SeedSeq\n * (optionally, we can have it produce more than one and pick which one\n * we want)\n */\n\ntemplate <typename UInt, size_t i = 0UL, size_t N = i+1UL, typename SeedSeq>\ninline UInt generate_one(SeedSeq&& generator)\n{\n    UInt result[N];\n    generate_to<N>(std::forward<SeedSeq>(generator), result);\n    return result[i];\n}\n\ntemplate <typename RngType>\nauto bounded_rand(RngType& rng, typename RngType::result_type upper_bound)\n        -> typename RngType::result_type\n{\n    typedef typename RngType::result_type rtype;\n    rtype threshold = (RngType::max() - RngType::min() + rtype(1) - upper_bound)\n                    % upper_bound;\n    for (;;) {\n        rtype r = rng() - RngType::min();\n        if (r >= threshold)\n            return r % upper_bound;\n    }\n}\n\ntemplate <typename Iter, typename RandType>\nvoid shuffle(Iter from, Iter to, RandType&& rng)\n{\n    typedef typename std::iterator_traits<Iter>::difference_type delta_t;\n    auto count = to - from;\n    while (count > 1) {\n        delta_t chosen(bounded_rand(rng, count));\n        --count;\n        --to;\n        using std::swap;\n        swap(*(from+chosen), *to);\n    }\n}\n\n/*\n * Although std::seed_seq is useful, it isn't everything.  Often we want to\n * initialize a random-number generator some other way, such as from a random\n * device.\n *\n * Technically, it does not meet the requirements of a SeedSequence because\n * it lacks some of the rarely-used member functions (some of which would\n * be impossible to provide).  However the C++ standard is quite specific\n * that actual engines only called the generate method, so it ought not to be\n * a problem in practice.\n */\n\ntemplate <typename RngType>\nclass seed_seq_from {\nprivate:\n    RngType rng_;\n\n    typedef uint_least32_t result_type;\n\npublic:\n    template<typename... Args>\n    seed_seq_from(Args&&... args) :\n        rng_(std::forward<Args>(args)...)\n    {\n        // Nothing (else) to do...\n    }\n\n    template<typename Iter>\n    void generate(Iter start, Iter finish)\n    {\n        for (auto i = start; i != finish; ++i)\n            *i = result_type(rng_());\n    }\n\n    constexpr size_t size() const\n    {\n        return (sizeof(typename RngType::result_type) > sizeof(result_type)\n                && RngType::max() > ~size_t(0UL))\n             ? ~size_t(0UL)\n             : size_t(RngType::max());\n    }\n};\n\n/*\n * Sometimes you might want a distinct seed based on when the program\n * was compiled.  That way, a particular instance of the program will\n * behave the same way, but when recompiled it'll produce a different\n * value.\n */\n\ntemplate <typename IntType>\nstruct static_arbitrary_seed {\nprivate:\n    static constexpr IntType fnv(IntType hash, const char* pos) {\n        return *pos == '\\0'\n             ? hash\n             : fnv((hash * IntType(16777619U)) ^ *pos, (pos+1));\n    }\n\npublic:\n    static constexpr IntType value = fnv(IntType(2166136261U ^ sizeof(IntType)),\n                        __DATE__ __TIME__ __FILE__);\n};\n\n// Sometimes, when debugging or testing, it's handy to be able print the name\n// of a (in human-readable form).  This code allows the idiom:\n//\n//      cout << printable_typename<my_foo_type_t>()\n//\n// to print out my_foo_type_t (or its concrete type if it is a synonym)\n\ntemplate <typename T>\nstruct printable_typename {};\n\n} // namespace pcg_extras\n\n#endif // PCG_EXTRAS_HPP_INCLUDED\n"
  },
  {
    "path": "lib/pcg/pcg_random.hpp",
    "content": "// Modified 2021 by ds-sloth, only to reduce the number of definitions to those needed for our use case.\n\n/*\n * PCG Random Number Generation for C++\n *\n * Copyright 2014 Melissa O'Neill <oneill@pcg-random.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * For additional information about the PCG random number generation scheme,\n * including its license and other licensing options, visit\n *\n *     http://www.pcg-random.org\n */\n\n/*\n * This code provides the reference implementation of the PCG family of\n * random number generators.  The code is complex because it implements\n *\n *      - several members of the PCG family, specifically members corresponding\n *        to the output functions:\n *             - XSH RR         (good for 64-bit state, 32-bit output)\n *             - XSH RS         (good for 64-bit state, 32-bit output)\n *             - XSL RR         (good for 128-bit state, 64-bit output)\n *             - RXS M XS       (statistically most powerful generator)\n *             - XSL RR RR      (good for 128-bit state, 128-bit output)\n *             - and RXS, RXS M, XSH, XSL       (mostly for testing)\n *      - at potentially *arbitrary* bit sizes\n *      - with four different techniques for random streams (MCG, one-stream\n *        LCG, settable-stream LCG, unique-stream LCG)\n *      - and the extended generation schemes allowing arbitrary periods\n *      - with all features of C++11 random number generation (and more),\n *        some of which are somewhat painful, including\n *            - initializing with a SeedSequence which writes 32-bit values\n *              to memory, even though the state of the generator may not\n *              use 32-bit values (it might use smaller or larger integers)\n *            - I/O for RNGs and a prescribed format, which needs to handle\n *              the issue that 8-bit and 128-bit integers don't have working\n *              I/O routines (e.g., normally 8-bit = char, not integer)\n *            - equality and inequality for RNGs\n *      - and a number of convenience typedefs to mask all the complexity\n *\n * The code employes a fairly heavy level of abstraction, and has to deal\n * with various C++ minutia.  If you're looking to learn about how the PCG\n * scheme works, you're probably best of starting with one of the other\n * codebases (see www.pcg-random.org).  But if you're curious about the\n * constants for the various output functions used in those other, simpler,\n * codebases, this code shows how they are calculated.\n *\n * On the positive side, at least there are convenience typedefs so that you\n * can say\n *\n *      pcg32 myRNG;\n *\n * rather than:\n *\n *      pcg_detail::engine<\n *          uint32_t,                                           // Output Type\n *          uint64_t,                                           // State Type\n *          pcg_detail::xsh_rr_mixin<uint32_t, uint64_t>, true, // Output Func\n *          pcg_detail::specific_stream<uint64_t>,              // Stream Kind\n *          pcg_detail::default_multiplier<uint64_t>            // LCG Mult\n *      > myRNG;\n *\n */\n\n#ifndef PCG_RAND_HPP_INCLUDED\n#define PCG_RAND_HPP_INCLUDED 1\n\n#include <cinttypes>\n#include <cstddef>\n#include <cstdlib>\n#include <cstring>\n#include <cassert>\n#include <limits>\n// #include <iostream>\n#include <type_traits>\n#include <utility>\n// #include <locale>\n#include <new>\n#include <stdexcept>\n\n/*\n * The pcg_extras namespace contains some support code that is likley to\n * be useful for a variety of RNGs, including:\n *      - 128-bit int support for platforms where it isn't available natively\n *      - bit twiddling operations\n *      - I/O of 128-bit and 8-bit integers\n *      - Handling the evilness of SeedSeq\n *      - Support for efficiently producing random numbers less than a given\n *        bound\n */\n\n#include \"pcg_extras.hpp\"\n\nnamespace pcg_detail {\n\nusing namespace pcg_extras;\n\n/*\n * The LCG generators need some constants to function.  This code lets you\n * look up the constant by *type*.  For example\n *\n *      default_multiplier<uint32_t>::multiplier()\n *\n * gives you the default multipler for 32-bit integers.  We use the name\n * of the constant and not a generic word like value to allow these classes\n * to be used as mixins.\n */\n\ntemplate <typename T>\nstruct default_multiplier {\n    // Not defined for an arbitrary type\n};\n\ntemplate <typename T>\nstruct default_increment {\n    // Not defined for an arbitrary type\n};\n\n#define PCG_DEFINE_CONSTANT(type, what, kind, constant) \\\n        template <>                                     \\\n        struct what ## _ ## kind<type> {                \\\n            static constexpr type kind() {              \\\n                return constant;                        \\\n            }                                           \\\n        };\n\nPCG_DEFINE_CONSTANT(uint8_t,  default, multiplier, 141U)\nPCG_DEFINE_CONSTANT(uint8_t,  default, increment,  77U)\n\nPCG_DEFINE_CONSTANT(uint16_t, default, multiplier, 12829U)\nPCG_DEFINE_CONSTANT(uint16_t, default, increment,  47989U)\n\nPCG_DEFINE_CONSTANT(uint32_t, default, multiplier, 747796405U)\nPCG_DEFINE_CONSTANT(uint32_t, default, increment,  2891336453U)\n\nPCG_DEFINE_CONSTANT(uint64_t, default, multiplier, 6364136223846793005ULL)\nPCG_DEFINE_CONSTANT(uint64_t, default, increment,  1442695040888963407ULL)\n\n#ifdef PCG_HAS_ALWAYS_INLINE\n#define PCG_ALWAYS_INLINE __attribute__((always_inline))\n#else\n#define PCG_ALWAYS_INLINE inline\n#endif\n\n\n\n/*\n * Each PCG generator is available in four variants, based on how it applies\n * the additive constant for its underlying LCG; the variations are:\n *\n *     single stream   - all instances use the same fixed constant, thus\n *                       the RNG always somewhere in same sequence\n *     mcg             - adds zero, resulting in a single stream and reduced\n *                       period\n *     specific stream - the constant can be changed at any time, selecting\n *                       a different random sequence\n *     unique stream   - the constant is based on the memory addresss of the\n *                       object, thus every RNG has its own unique sequence\n *\n * This variation is provided though mixin classes which define a function\n * value called increment() that returns the nesessary additive constant.\n */\n\n\n\n/*\n * unique stream\n */\n\n\ntemplate <typename itype>\nclass unique_stream {\nprotected:\n    static constexpr bool is_mcg = false;\n\n    // Is never called, but is provided for symmetry with specific_stream\n    void set_stream(...)\n    {\n        abort();\n    }\n\npublic:\n    typedef itype state_type;\n\n    constexpr itype increment() const {\n        return itype(reinterpret_cast<unsigned long>(this) | 1);\n    }\n\n    constexpr itype stream() const\n    {\n         return increment() >> 1;\n    }\n\n    static constexpr bool can_specify_stream = false;\n\n    static constexpr size_t streams_pow2()\n    {\n        return (sizeof(itype) < sizeof(size_t) ? sizeof(itype)\n                                               : sizeof(size_t))*8 - 1u;\n    }\n\nprotected:\n    constexpr unique_stream() = default;\n};\n\n\n/*\n * no stream (mcg)\n */\n\ntemplate <typename itype>\nclass no_stream {\nprotected:\n    static constexpr bool is_mcg = true;\n\n    // Is never called, but is provided for symmetry with specific_stream\n    void set_stream(...)\n    {\n        abort();\n    }\n\npublic:\n    typedef itype state_type;\n\n    static constexpr itype increment() {\n        return 0;\n    }\n\n    static constexpr bool can_specify_stream = false;\n\n    static constexpr size_t streams_pow2()\n    {\n        return 0u;\n    }\n\nprotected:\n    constexpr no_stream() = default;\n};\n\n\n/*\n * single stream/sequence (oneseq)\n */\n\ntemplate <typename itype>\nclass oneseq_stream : public default_increment<itype> {\nprotected:\n    static constexpr bool is_mcg = false;\n\n    // Is never called, but is provided for symmetry with specific_stream\n    void set_stream(...)\n    {\n        abort();\n    }\n\npublic:\n    typedef itype state_type;\n\n    static constexpr itype stream()\n    {\n         return default_increment<itype>::increment() >> 1;\n    }\n\n    static constexpr bool can_specify_stream = false;\n\n    static constexpr size_t streams_pow2()\n    {\n        return 0u;\n    }\n\nprotected:\n    constexpr oneseq_stream() = default;\n};\n\n\n/*\n * specific stream\n */\n\ntemplate <typename itype>\nclass specific_stream {\nprotected:\n    static constexpr bool is_mcg = false;\n\n    itype inc_ = default_increment<itype>::increment();\n\npublic:\n    typedef itype state_type;\n    typedef itype stream_state;\n\n    constexpr itype increment() const {\n        return inc_;\n    }\n\n    itype stream()\n    {\n         return inc_ >> 1;\n    }\n\n    void set_stream(itype specific_seq)\n    {\n         inc_ = (specific_seq << 1) | 1;\n    }\n\n    static constexpr bool can_specify_stream = true;\n\n    static constexpr size_t streams_pow2()\n    {\n        return (sizeof(itype)*8) - 1u;\n    }\n\nprotected:\n    specific_stream() = default;\n\n    specific_stream(itype specific_seq)\n        : inc_((specific_seq << 1) | itype(1U))\n    {\n        // Nothing (else) to do.\n    }\n};\n\n\n/*\n * This is where it all comes together.  This function joins together three\n * mixin classes which define\n *    - the LCG additive constant (the stream)\n *    - the LCG multiplier\n *    - the output function\n * in addition, we specify the type of the LCG state, and the result type,\n * and whether to use the pre-advance version of the state for the output\n * (increasing instruction-level parallelism) or the post-advance version\n * (reducing register pressure).\n *\n * Given the high level of parameterization, the code has to use some\n * template-metaprogramming tricks to handle some of the suble variations\n * involved.\n */\n\ntemplate <typename xtype, typename itype,\n          typename output_mixin,\n          bool output_previous = true,\n          typename stream_mixin = oneseq_stream<itype>,\n          typename multiplier_mixin = default_multiplier<itype> >\nclass engine : protected output_mixin,\n               public stream_mixin,\n               protected multiplier_mixin {\nprotected:\n    itype state_;\n\n    struct can_specify_stream_tag {};\n    struct no_specifiable_stream_tag {};\n\n    using stream_mixin::increment;\n    using multiplier_mixin::multiplier;\n\npublic:\n    typedef xtype result_type;\n    typedef itype state_type;\n\n    static constexpr size_t period_pow2()\n    {\n        return sizeof(state_type)*8 - 2*stream_mixin::is_mcg;\n    }\n\n    // It would be nice to use std::numeric_limits for these, but\n    // we can't be sure that it'd be defined for the 128-bit types.\n\n    static constexpr result_type min()\n    {\n        return result_type(0UL);\n    }\n\n    static constexpr result_type max()\n    {\n        return ~result_type(0UL);\n    }\n\nprotected:\n    itype bump(itype state)\n    {\n        return state * multiplier() + increment();\n    }\n\n    itype base_generate()\n    {\n        return state_ = bump(state_);\n    }\n\n    itype base_generate0()\n    {\n        itype old_state = state_;\n        state_ = bump(state_);\n        return old_state;\n    }\n\npublic:\n    result_type operator()()\n    {\n        if (output_previous)\n            return this->output(base_generate0());\n        else\n            return this->output(base_generate());\n    }\n\n    result_type operator()(result_type upper_bound)\n    {\n        return bounded_rand(*this, upper_bound);\n    }\n\nprotected:\n    static itype advance(itype state, itype delta,\n                         itype cur_mult, itype cur_plus);\n\n    static itype distance(itype cur_state, itype newstate, itype cur_mult,\n                          itype cur_plus, itype mask = ~itype(0U));\n\n    itype distance(itype newstate, itype mask = ~itype(0U)) const\n    {\n        return distance(state_, newstate, multiplier(), increment(), mask);\n    }\n\npublic:\n    void advance(itype delta)\n    {\n        state_ = advance(state_, delta, this->multiplier(), this->increment());\n    }\n\n    void backstep(itype delta)\n    {\n        advance(-delta);\n    }\n\n    void discard(itype delta)\n    {\n        advance(delta);\n    }\n\n    bool wrapped()\n    {\n        if (stream_mixin::is_mcg) {\n            // For MCGs, the low order two bits never change. In this\n            // implementation, we keep them fixed at 3 to make this test\n            // easier.\n            return state_ == 3;\n        } else {\n            return state_ == 0;\n        }\n    }\n\n    engine(itype state = itype(0xcafef00dd15ea5e5ULL))\n        : state_(this->is_mcg ? state|state_type(3U)\n                              : bump(state + this->increment()))\n    {\n        // Nothing else to do.\n    }\n\n    // This function may or may not exist.  It thus has to be a template\n    // to use SFINAE; users don't have to worry about its template-ness.\n\n    template <typename sm = stream_mixin>\n    engine(itype state, typename sm::stream_state stream_seed)\n        : stream_mixin(stream_seed),\n          state_(this->is_mcg ? state|state_type(3U)\n                              : bump(state + this->increment()))\n    {\n        // Nothing else to do.\n    }\n\n    template<typename SeedSeq>\n    engine(SeedSeq&& seedSeq, typename std::enable_if<\n                  !stream_mixin::can_specify_stream\n               && !std::is_convertible<SeedSeq, itype>::value\n               && !std::is_convertible<SeedSeq, engine>::value,\n               no_specifiable_stream_tag>::type = {})\n        : engine(generate_one<itype>(std::forward<SeedSeq>(seedSeq)))\n    {\n        // Nothing else to do.\n    }\n\n    template<typename SeedSeq>\n    engine(SeedSeq&& seedSeq, typename std::enable_if<\n                   stream_mixin::can_specify_stream\n               && !std::is_convertible<SeedSeq, itype>::value\n               && !std::is_convertible<SeedSeq, engine>::value,\n        can_specify_stream_tag>::type = {})\n        : engine(generate_one<itype,1,2>(seedSeq),\n                 generate_one<itype,0,2>(seedSeq))\n    {\n        // Nothing else to do.\n    }\n\n\n    template<typename... Args>\n    void seed(Args&&... args)\n    {\n        new (this) engine(std::forward<Args>(args)...);\n    }\n\n    template <typename xtype1, typename itype1,\n              typename output_mixin1, bool output_previous1,\n              typename stream_mixin_lhs, typename multiplier_mixin_lhs,\n              typename stream_mixin_rhs, typename multiplier_mixin_rhs>\n    friend bool operator==(const engine<xtype1,itype1,\n                                     output_mixin1,output_previous1,\n                                     stream_mixin_lhs, multiplier_mixin_lhs>&,\n                           const engine<xtype1,itype1,\n                                     output_mixin1,output_previous1,\n                                     stream_mixin_rhs, multiplier_mixin_rhs>&);\n\n    template <typename xtype1, typename itype1,\n              typename output_mixin1, bool output_previous1,\n              typename stream_mixin_lhs, typename multiplier_mixin_lhs,\n              typename stream_mixin_rhs, typename multiplier_mixin_rhs>\n    friend itype1 operator-(const engine<xtype1,itype1,\n                                     output_mixin1,output_previous1,\n                                     stream_mixin_lhs, multiplier_mixin_lhs>&,\n                            const engine<xtype1,itype1,\n                                     output_mixin1,output_previous1,\n                                     stream_mixin_rhs, multiplier_mixin_rhs>&);\n};\n\ntemplate <typename xtype, typename itype,\n          typename output_mixin, bool output_previous,\n          typename stream_mixin, typename multiplier_mixin>\nitype engine<xtype,itype,output_mixin,output_previous,stream_mixin,\n             multiplier_mixin>::advance(\n    itype state, itype delta, itype cur_mult, itype cur_plus)\n{\n    // The method used here is based on Brown, \"Random Number Generation\n    // with Arbitrary Stride,\", Transactions of the American Nuclear\n    // Society (Nov. 1994).  The algorithm is very similar to fast\n    // exponentiation.\n    //\n    // Even though delta is an unsigned integer, we can pass a\n    // signed integer to go backwards, it just goes \"the long way round\".\n\n    constexpr itype ZERO = 0u;  // itype may be a non-trivial types, so\n    constexpr itype ONE  = 1u;  // we define some ugly constants.\n    itype acc_mult = 1;\n    itype acc_plus = 0;\n    while (delta > ZERO) {\n       if (delta & ONE) {\n          acc_mult *= cur_mult;\n          acc_plus = acc_plus*cur_mult + cur_plus;\n       }\n       cur_plus = (cur_mult+ONE)*cur_plus;\n       cur_mult *= cur_mult;\n       delta >>= 1;\n    }\n    return acc_mult * state + acc_plus;\n}\n\ntemplate <typename xtype, typename itype,\n          typename output_mixin, bool output_previous,\n          typename stream_mixin, typename multiplier_mixin>\nitype engine<xtype,itype,output_mixin,output_previous,stream_mixin,\n               multiplier_mixin>::distance(\n    itype cur_state, itype newstate, itype cur_mult, itype cur_plus, itype mask)\n{\n    constexpr itype ONE  = 1u;  // itype could be weird, so use constant\n    itype the_bit = stream_mixin::is_mcg ? itype(4u) : itype(1u);\n    itype distance = 0u;\n    while ((cur_state & mask) != (newstate & mask)) {\n       if ((cur_state & the_bit) != (newstate & the_bit)) {\n           cur_state = cur_state * cur_mult + cur_plus;\n           distance |= the_bit;\n       }\n       assert((cur_state & the_bit) == (newstate & the_bit));\n       the_bit <<= 1;\n       cur_plus = (cur_mult+ONE)*cur_plus;\n       cur_mult *= cur_mult;\n    }\n    return stream_mixin::is_mcg ? distance >> 2 : distance;\n}\n\ntemplate <typename xtype, typename itype,\n          typename output_mixin, bool output_previous,\n          typename stream_mixin_lhs, typename multiplier_mixin_lhs,\n          typename stream_mixin_rhs, typename multiplier_mixin_rhs>\nitype operator-(const engine<xtype,itype,\n                               output_mixin,output_previous,\n                               stream_mixin_lhs, multiplier_mixin_lhs>& lhs,\n               const engine<xtype,itype,\n                               output_mixin,output_previous,\n                               stream_mixin_rhs, multiplier_mixin_rhs>& rhs)\n{\n    if (lhs.multiplier() != rhs.multiplier()\n        || lhs.increment() != rhs.increment())\n        throw std::logic_error(\"incomparable generators\");\n    return rhs.distance(lhs.state_);\n}\n\n\ntemplate <typename xtype, typename itype,\n          typename output_mixin, bool output_previous,\n          typename stream_mixin_lhs, typename multiplier_mixin_lhs,\n          typename stream_mixin_rhs, typename multiplier_mixin_rhs>\nbool operator==(const engine<xtype,itype,\n                               output_mixin,output_previous,\n                               stream_mixin_lhs, multiplier_mixin_lhs>& lhs,\n                const engine<xtype,itype,\n                               output_mixin,output_previous,\n                               stream_mixin_rhs, multiplier_mixin_rhs>& rhs)\n{\n    return    (lhs.multiplier() == rhs.multiplier())\n           && (lhs.increment()  == rhs.increment())\n           && (lhs.state_       == rhs.state_);\n}\n\ntemplate <typename xtype, typename itype,\n          typename output_mixin, bool output_previous,\n          typename stream_mixin_lhs, typename multiplier_mixin_lhs,\n          typename stream_mixin_rhs, typename multiplier_mixin_rhs>\ninline bool operator!=(const engine<xtype,itype,\n                               output_mixin,output_previous,\n                               stream_mixin_lhs, multiplier_mixin_lhs>& lhs,\n                       const engine<xtype,itype,\n                               output_mixin,output_previous,\n                               stream_mixin_rhs, multiplier_mixin_rhs>& rhs)\n{\n    return !operator==(lhs,rhs);\n}\n\n\ntemplate <typename xtype, typename itype,\n         template<typename XT,typename IT> class output_mixin,\n         bool output_previous = (sizeof(itype) <= 8)>\nusing oneseq_base  = engine<xtype, itype,\n                        output_mixin<xtype, itype>, output_previous,\n                        oneseq_stream<itype> >;\n\ntemplate <typename xtype, typename itype,\n         template<typename XT,typename IT> class output_mixin,\n         bool output_previous = (sizeof(itype) <= 8)>\nusing unique_base = engine<xtype, itype,\n                         output_mixin<xtype, itype>, output_previous,\n                         unique_stream<itype> >;\n\ntemplate <typename xtype, typename itype,\n         template<typename XT,typename IT> class output_mixin,\n         bool output_previous = (sizeof(itype) <= 8)>\nusing setseq_base = engine<xtype, itype,\n                         output_mixin<xtype, itype>, output_previous,\n                         specific_stream<itype> >;\n\ntemplate <typename xtype, typename itype,\n         template<typename XT,typename IT> class output_mixin,\n         bool output_previous = (sizeof(itype) <= 8)>\nusing mcg_base = engine<xtype, itype,\n                      output_mixin<xtype, itype>, output_previous,\n                      no_stream<itype> >;\n\n/*\n * OUTPUT FUNCTIONS.\n *\n * These are the core of the PCG generation scheme.  They specify how to\n * turn the base LCG's internal state into the output value of the final\n * generator.\n *\n * They're implemented as mixin classes.\n *\n * All of the classes have code that is written to allow it to be applied\n * at *arbitrary* bit sizes, although in practice they'll only be used at\n * standard sizes supported by C++.\n */\n\n/*\n * XSH RS -- high xorshift, followed by a random shift\n *\n * Fast.  A good performer.\n */\n\ntemplate <typename xtype, typename itype>\nstruct xsh_rs_mixin {\n    static xtype output(itype internal)\n    {\n        constexpr bitcount_t bits        = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t xtypebits   = bitcount_t(sizeof(xtype) * 8);\n        constexpr bitcount_t sparebits   = bits - xtypebits;\n        constexpr bitcount_t opbits =\n                              sparebits-5 >= 64 ? 5\n                            : sparebits-4 >= 32 ? 4\n                            : sparebits-3 >= 16 ? 3\n                            : sparebits-2 >= 4  ? 2\n                            : sparebits-1 >= 1  ? 1\n                            :                     0;\n        constexpr bitcount_t mask = (1 << opbits) - 1;\n        constexpr bitcount_t maxrandshift  = mask;\n        constexpr bitcount_t topspare     = opbits;\n        constexpr bitcount_t bottomspare = sparebits - topspare;\n        constexpr bitcount_t xshift     = topspare + (xtypebits+maxrandshift)/2;\n        bitcount_t rshift =\n            opbits ? bitcount_t(internal >> (bits - opbits)) & mask : 0;\n        internal ^= internal >> xshift;\n        xtype result = xtype(internal >> (bottomspare - maxrandshift + rshift));\n        return result;\n    }\n};\n\n/*\n * XSH RR -- high xorshift, followed by a random rotate\n *\n * Fast.  A good performer.  Slightly better statistically than XSH RS.\n */\n\ntemplate <typename xtype, typename itype>\nstruct xsh_rr_mixin {\n    static xtype output(itype internal)\n    {\n        constexpr bitcount_t bits        = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t xtypebits   = bitcount_t(sizeof(xtype)*8);\n        constexpr bitcount_t sparebits   = bits - xtypebits;\n        constexpr bitcount_t wantedopbits =\n                              xtypebits >= 128 ? 7\n                            : xtypebits >=  64 ? 6\n                            : xtypebits >=  32 ? 5\n                            : xtypebits >=  16 ? 4\n                            :                    3;\n        constexpr bitcount_t opbits =\n                              sparebits >= wantedopbits ? wantedopbits\n                                                        : sparebits;\n        constexpr bitcount_t amplifier = wantedopbits - opbits;\n        constexpr bitcount_t mask = (1 << opbits) - 1;\n        constexpr bitcount_t topspare    = opbits;\n        constexpr bitcount_t bottomspare = sparebits - topspare;\n        constexpr bitcount_t xshift      = (topspare + xtypebits)/2;\n        bitcount_t rot = opbits ? bitcount_t(internal >> (bits - opbits)) & mask\n                                : 0;\n        bitcount_t amprot = (rot << amplifier) & mask;\n        internal ^= internal >> xshift;\n        xtype result = xtype(internal >> bottomspare);\n        result = rotr(result, amprot);\n        return result;\n    }\n};\n\n/*\n * RXS -- random xorshift\n */\n\ntemplate <typename xtype, typename itype>\nstruct rxs_mixin {\nstatic xtype output_rxs(itype internal)\n    {\n        constexpr bitcount_t bits        = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t xtypebits   = bitcount_t(sizeof(xtype)*8);\n        constexpr bitcount_t shift       = bits - xtypebits;\n        constexpr bitcount_t extrashift  = (xtypebits - shift)/2;\n        bitcount_t rshift = shift > 64+8 ? (internal >> (bits - 6)) & 63\n                       : shift > 32+4 ? (internal >> (bits - 5)) & 31\n                       : shift > 16+2 ? (internal >> (bits - 4)) & 15\n                       : shift >  8+1 ? (internal >> (bits - 3)) & 7\n                       : shift >  4+1 ? (internal >> (bits - 2)) & 3\n                       : shift >  2+1 ? (internal >> (bits - 1)) & 1\n                       :              0;\n        internal ^= internal >> (shift + extrashift - rshift);\n        xtype result = internal >> rshift;\n        return result;\n    }\n};\n\n/*\n * RXS M XS -- random xorshift, mcg multiply, fixed xorshift\n *\n * The most statistically powerful generator, but all those steps\n * make it slower than some of the others.  We give it the rottenest jobs.\n *\n * Because it's usually used in contexts where the state type and the\n * result type are the same, it is a permutation and is thus invertable.\n * We thus provide a function to invert it.  This function is used to\n * for the \"inside out\" generator used by the extended generator.\n */\n\n/* Defined type-based concepts for the multiplication step.  They're actually\n * all derived by truncating the 128-bit, which was computed to be a good\n * \"universal\" constant.\n */\n\ntemplate <typename T>\nstruct mcg_multiplier {\n    // Not defined for an arbitrary type\n};\n\ntemplate <typename T>\nstruct mcg_unmultiplier {\n    // Not defined for an arbitrary type\n};\n\nPCG_DEFINE_CONSTANT(uint8_t,  mcg, multiplier,   217U)\nPCG_DEFINE_CONSTANT(uint8_t,  mcg, unmultiplier, 105U)\n\nPCG_DEFINE_CONSTANT(uint16_t, mcg, multiplier,   62169U)\nPCG_DEFINE_CONSTANT(uint16_t, mcg, unmultiplier, 28009U)\n\nPCG_DEFINE_CONSTANT(uint32_t, mcg, multiplier,   277803737U)\nPCG_DEFINE_CONSTANT(uint32_t, mcg, unmultiplier, 2897767785U)\n\nPCG_DEFINE_CONSTANT(uint64_t, mcg, multiplier,   12605985483714917081ULL)\nPCG_DEFINE_CONSTANT(uint64_t, mcg, unmultiplier, 15009553638781119849ULL)\n\n\ntemplate <typename xtype, typename itype>\nstruct rxs_m_xs_mixin {\n    static xtype output(itype internal)\n    {\n        constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8);\n        constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t opbits = xtypebits >= 128 ? 6\n                                 : xtypebits >=  64 ? 5\n                                 : xtypebits >=  32 ? 4\n                                 : xtypebits >=  16 ? 3\n                                 :                    2;\n        constexpr bitcount_t shift = bits - xtypebits;\n        constexpr bitcount_t mask = (1 << opbits) - 1;\n        bitcount_t rshift =\n            opbits ? bitcount_t(internal >> (bits - opbits)) & mask : 0;\n        internal ^= internal >> (opbits + rshift);\n        internal *= mcg_multiplier<itype>::multiplier();\n        xtype result = internal >> shift;\n        result ^= result >> ((2U*xtypebits+2U)/3U);\n        return result;\n    }\n\n    static itype unoutput(itype internal)\n    {\n        constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t opbits = bits >= 128 ? 6\n                                 : bits >=  64 ? 5\n                                 : bits >=  32 ? 4\n                                 : bits >=  16 ? 3\n                                 :               2;\n        constexpr bitcount_t mask = (1 << opbits) - 1;\n\n        internal = unxorshift(internal, bits, (2U*bits+2U)/3U);\n\n        internal *= mcg_unmultiplier<itype>::unmultiplier();\n\n        bitcount_t rshift = opbits ? (internal >> (bits - opbits)) & mask : 0;\n        internal = unxorshift(internal, bits, opbits + rshift);\n\n        return internal;\n    }\n};\n\n\n/*\n * RXS M -- random xorshift, mcg multiply\n */\n\ntemplate <typename xtype, typename itype>\nstruct rxs_m_mixin {\n    static xtype output(itype internal)\n    {\n        constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8);\n        constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t opbits = xtypebits >= 128 ? 6\n                                 : xtypebits >=  64 ? 5\n                                 : xtypebits >=  32 ? 4\n                                 : xtypebits >=  16 ? 3\n                                 :                    2;\n        constexpr bitcount_t shift = bits - xtypebits;\n        constexpr bitcount_t mask = (1 << opbits) - 1;\n        bitcount_t rshift = opbits ? (internal >> (bits - opbits)) & mask : 0;\n        internal ^= internal >> (opbits + rshift);\n        internal *= mcg_multiplier<itype>::multiplier();\n        xtype result = internal >> shift;\n        return result;\n    }\n};\n\n/*\n * XSL RR -- fixed xorshift (to low bits), random rotate\n *\n * Useful for 128-bit types that are split across two CPU registers.\n */\n\ntemplate <typename xtype, typename itype>\nstruct xsl_rr_mixin {\n    static xtype output(itype internal)\n    {\n        constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8);\n        constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t sparebits = bits - xtypebits;\n        constexpr bitcount_t wantedopbits = xtypebits >= 128 ? 7\n                                       : xtypebits >=  64 ? 6\n                                       : xtypebits >=  32 ? 5\n                                       : xtypebits >=  16 ? 4\n                                       :                    3;\n        constexpr bitcount_t opbits = sparebits >= wantedopbits ? wantedopbits\n                                                             : sparebits;\n        constexpr bitcount_t amplifier = wantedopbits - opbits;\n        constexpr bitcount_t mask = (1 << opbits) - 1;\n        constexpr bitcount_t topspare = sparebits;\n        constexpr bitcount_t bottomspare = sparebits - topspare;\n        constexpr bitcount_t xshift = (topspare + xtypebits) / 2;\n\n        bitcount_t rot =\n            opbits ? bitcount_t(internal >> (bits - opbits)) & mask : 0;\n        bitcount_t amprot = (rot << amplifier) & mask;\n        internal ^= internal >> xshift;\n        xtype result = xtype(internal >> bottomspare);\n        result = rotr(result, amprot);\n        return result;\n    }\n};\n\n\n/*\n * XSL RR RR -- fixed xorshift (to low bits), random rotate (both parts)\n *\n * Useful for 128-bit types that are split across two CPU registers.\n * If you really want an invertable 128-bit RNG, I guess this is the one.\n */\n\ntemplate <typename T> struct halfsize_trait {};\ntemplate <> struct halfsize_trait<uint64_t>  { typedef uint32_t type; };\ntemplate <> struct halfsize_trait<uint32_t>  { typedef uint16_t type; };\ntemplate <> struct halfsize_trait<uint16_t>  { typedef uint8_t type;  };\n\ntemplate <typename xtype, typename itype>\nstruct xsl_rr_rr_mixin {\n    typedef typename halfsize_trait<itype>::type htype;\n\n    static itype output(itype internal)\n    {\n        constexpr bitcount_t htypebits = bitcount_t(sizeof(htype) * 8);\n        constexpr bitcount_t bits      = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t sparebits = bits - htypebits;\n        constexpr bitcount_t wantedopbits = htypebits >= 128 ? 7\n                                       : htypebits >=  64 ? 6\n                                       : htypebits >=  32 ? 5\n                                       : htypebits >=  16 ? 4\n                                       :                    3;\n        constexpr bitcount_t opbits = sparebits >= wantedopbits ? wantedopbits\n                                                                : sparebits;\n        constexpr bitcount_t amplifier = wantedopbits - opbits;\n        constexpr bitcount_t mask = (1 << opbits) - 1;\n        constexpr bitcount_t topspare = sparebits;\n        constexpr bitcount_t xshift = (topspare + htypebits) / 2;\n\n        bitcount_t rot =\n            opbits ? bitcount_t(internal >> (bits - opbits)) & mask : 0;\n        bitcount_t amprot = (rot << amplifier) & mask;\n        internal ^= internal >> xshift;\n        htype lowbits = htype(internal);\n        lowbits = rotr(lowbits, amprot);\n        htype highbits = htype(internal >> topspare);\n        bitcount_t rot2 = lowbits & mask;\n        bitcount_t amprot2 = (rot2 << amplifier) & mask;\n        highbits = rotr(highbits, amprot2);\n        return (itype(highbits) << topspare) ^ itype(lowbits);\n    }\n};\n\n\n/*\n * XSH -- fixed xorshift (to high bits)\n *\n * You shouldn't use this at 64-bits or less.\n */\n\ntemplate <typename xtype, typename itype>\nstruct xsh_mixin {\n    static xtype output(itype internal)\n    {\n        constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8);\n        constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t sparebits = bits - xtypebits;\n        constexpr bitcount_t topspare = 0;\n        constexpr bitcount_t bottomspare = sparebits - topspare;\n        constexpr bitcount_t xshift = (topspare + xtypebits) / 2;\n\n        internal ^= internal >> xshift;\n        xtype result = internal >> bottomspare;\n        return result;\n    }\n};\n\n/*\n * XSL -- fixed xorshift (to low bits)\n *\n * You shouldn't use this at 64-bits or less.\n */\n\ntemplate <typename xtype, typename itype>\nstruct xsl_mixin {\n    inline xtype output(itype internal)\n    {\n        constexpr bitcount_t xtypebits = bitcount_t(sizeof(xtype) * 8);\n        constexpr bitcount_t bits = bitcount_t(sizeof(itype) * 8);\n        constexpr bitcount_t sparebits = bits - xtypebits;\n        constexpr bitcount_t topspare = sparebits;\n        constexpr bitcount_t bottomspare = sparebits - topspare;\n        constexpr bitcount_t xshift = (topspare + xtypebits) / 2;\n\n        internal ^= internal >> xshift;\n        xtype result = internal >> bottomspare;\n        return result;\n    }\n};\n\n/* ---- End of Output Functions ---- */\n\n\ntemplate <typename baseclass>\nstruct inside_out : private baseclass {\n    inside_out() = delete;\n\n    typedef typename baseclass::result_type result_type;\n    typedef typename baseclass::state_type  state_type;\n    static_assert(sizeof(result_type) == sizeof(state_type),\n                  \"Require a RNG whose output function is a permutation\");\n\n    static bool external_step(result_type& randval, size_t i)\n    {\n        state_type state = baseclass::unoutput(randval);\n        state = state * baseclass::multiplier() + baseclass::increment()\n                + state_type(i*2);\n        result_type result = baseclass::output(state);\n        randval = result;\n        state_type zero =\n            baseclass::is_mcg ? state & state_type(3U) : state_type(0U);\n        return result == zero;\n    }\n\n    static bool external_advance(result_type& randval, size_t i,\n                                 result_type delta, bool forwards = true)\n    {\n        state_type state = baseclass::unoutput(randval);\n        state_type mult  = baseclass::multiplier();\n        state_type inc   = baseclass::increment() + state_type(i*2);\n        state_type zero =\n            baseclass::is_mcg ? state & state_type(3U) : state_type(0U);\n        state_type dist_to_zero = baseclass::distance(state, zero, mult, inc);\n        bool crosses_zero =\n            forwards ? dist_to_zero <= delta\n                     : (-dist_to_zero) <= delta;\n        if (!forwards)\n            delta = -delta;\n        state = baseclass::advance(state, delta, mult, inc);\n        randval = baseclass::output(state);\n        return crosses_zero;\n    }\n};\n\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2, typename baseclass, typename extvalclass, bool kdd = true>\nclass extended : public baseclass {\npublic:\n    typedef typename baseclass::state_type  state_type;\n    typedef typename baseclass::result_type result_type;\n    typedef inside_out<extvalclass> insideout;\n\nprivate:\n    static constexpr bitcount_t rtypebits = sizeof(result_type)*8;\n    static constexpr bitcount_t stypebits = sizeof(state_type)*8;\n\n    static constexpr bitcount_t tick_limit_pow2 = 64U;\n\n    static constexpr size_t table_size  = 1UL << table_pow2;\n    static constexpr size_t table_shift = stypebits - table_pow2;\n    static constexpr state_type table_mask =\n        (state_type(1U) << table_pow2) - state_type(1U);\n\n    static constexpr bool   may_tick  =\n        (advance_pow2 < stypebits) && (advance_pow2 < tick_limit_pow2);\n    static constexpr size_t tick_shift = stypebits - advance_pow2;\n    static constexpr state_type tick_mask  =\n        may_tick ? state_type(\n                       (uint64_t(1) << (advance_pow2*may_tick)) - 1)\n                                        // ^-- stupidity to appease GCC warnings\n                 : ~state_type(0U);\n\n    static constexpr bool may_tock = stypebits < tick_limit_pow2;\n\n    result_type data_[table_size];\n\n    PCG_NOINLINE void advance_table();\n\n    PCG_NOINLINE void advance_table(state_type delta, bool isForwards = true);\n\n    result_type& get_extended_value()\n    {\n        state_type state = this->state_;\n        if (kdd && baseclass::is_mcg) {\n            // The low order bits of an MCG are constant, so drop them.\n            state >>= 2;\n        }\n        size_t index       = kdd ? state &  table_mask\n                                 : state >> table_shift;\n\n        if (may_tick) {\n            bool tick = kdd ? (state & tick_mask) == state_type(0u)\n                            : (state >> tick_shift) == state_type(0u);\n            if (tick)\n                    advance_table();\n        }\n        if (may_tock) {\n            bool tock = state == state_type(0u);\n            if (tock)\n                advance_table();\n        }\n        return data_[index];\n    }\n\npublic:\n    static constexpr size_t period_pow2()\n    {\n        return baseclass::period_pow2() + table_size*extvalclass::period_pow2();\n    }\n\n    PCG_ALWAYS_INLINE result_type operator()()\n    {\n        result_type rhs = get_extended_value();\n        result_type lhs = this->baseclass::operator()();\n        return lhs ^ rhs;\n    }\n\n    result_type operator()(result_type upper_bound)\n    {\n        return bounded_rand(*this, upper_bound);\n    }\n\n    void set(result_type wanted)\n    {\n        result_type& rhs = get_extended_value();\n        result_type lhs = this->baseclass::operator()();\n        rhs = lhs ^ wanted;\n    }\n\n    void advance(state_type distance, bool forwards = true);\n\n    void backstep(state_type distance)\n    {\n        advance(distance, false);\n    }\n\n    extended(const result_type* data)\n        : baseclass()\n    {\n        datainit(data);\n    }\n\n    extended(const result_type* data, state_type seed)\n        : baseclass(seed)\n    {\n        datainit(data);\n    }\n\n    // This function may or may not exist.  It thus has to be a template\n    // to use SFINAE; users don't have to worry about its template-ness.\n\n    template <typename bc = baseclass>\n    extended(const result_type* data, state_type seed,\n            typename bc::stream_state stream_seed)\n        : baseclass(seed, stream_seed)\n    {\n        datainit(data);\n    }\n\n    extended()\n        : baseclass()\n    {\n        selfinit();\n    }\n\n    extended(state_type seed)\n        : baseclass(seed)\n    {\n        selfinit();\n    }\n\n    // This function may or may not exist.  It thus has to be a template\n    // to use SFINAE; users don't have to worry about its template-ness.\n\n    template <typename bc = baseclass>\n    extended(state_type seed, typename bc::stream_state stream_seed)\n        : baseclass(seed, stream_seed)\n    {\n        selfinit();\n    }\n\nprivate:\n    void selfinit();\n    void datainit(const result_type* data);\n\npublic:\n\n    template<typename SeedSeq, typename = typename std::enable_if<\n           !std::is_convertible<SeedSeq, result_type>::value\n        && !std::is_convertible<SeedSeq, extended>::value>::type>\n    extended(SeedSeq&& seedSeq)\n        : baseclass(seedSeq)\n    {\n        generate_to<table_size>(seedSeq, data_);\n    }\n\n    template<typename... Args>\n    void seed(Args&&... args)\n    {\n        new (this) extended(std::forward<Args>(args)...);\n    }\n\n    template <bitcount_t table_pow2_, bitcount_t advance_pow2_,\n              typename baseclass_, typename extvalclass_, bool kdd_>\n    friend bool operator==(const extended<table_pow2_, advance_pow2_,\n                                              baseclass_, extvalclass_, kdd_>&,\n                           const extended<table_pow2_, advance_pow2_,\n                                              baseclass_, extvalclass_, kdd_>&);\n\n\n};\n\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename baseclass, typename extvalclass, bool kdd>\nvoid extended<table_pow2,advance_pow2,baseclass,extvalclass,kdd>::datainit(\n         const result_type* data)\n{\n    for (size_t i = 0; i < table_size; ++i)\n        data_[i] = data[i];\n}\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename baseclass, typename extvalclass, bool kdd>\nvoid extended<table_pow2,advance_pow2,baseclass,extvalclass,kdd>::selfinit()\n{\n    // We need to fill the extended table with something, and we have\n    // very little provided data, so we use the base generator to\n    // produce values.  Although not ideal (use a seed sequence, folks!),\n    // unexpected correlations are mitigated by\n    //      - using XOR differences rather than the number directly\n    //      - the way the table is accessed, its values *won't* be accessed\n    //        in the same order the were written.\n    //      - any strange correlations would only be apparent if we\n    //        were to backstep the generator so that the base generator\n    //        was generating the same values again\n    result_type xdiff = baseclass::operator()() - baseclass::operator()();\n    for (size_t i = 0; i < table_size; ++i) {\n        data_[i] = baseclass::operator()() ^ xdiff;\n    }\n}\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename baseclass, typename extvalclass, bool kdd>\nbool operator==(const extended<table_pow2, advance_pow2,\n                               baseclass, extvalclass, kdd>& lhs,\n                const extended<table_pow2, advance_pow2,\n                               baseclass, extvalclass, kdd>& rhs)\n{\n    auto& base_lhs = static_cast<const baseclass&>(lhs);\n    auto& base_rhs = static_cast<const baseclass&>(rhs);\n    return base_lhs == base_rhs\n        && !memcmp((void*) lhs.data_, (void*) rhs.data_, sizeof(lhs.data_));\n}\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename baseclass, typename extvalclass, bool kdd>\ninline bool operator!=(const extended<table_pow2, advance_pow2,\n                                      baseclass, extvalclass, kdd>& lhs,\n                       const extended<table_pow2, advance_pow2,\n                                      baseclass, extvalclass, kdd>& rhs)\n{\n    return lhs != rhs;\n}\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename baseclass, typename extvalclass, bool kdd>\nvoid\nextended<table_pow2,advance_pow2,baseclass,extvalclass,kdd>::advance_table()\n{\n    bool carry = false;\n    for (size_t i = 0; i < table_size; ++i) {\n        if (carry) {\n            carry = insideout::external_step(data_[i],i+1);\n        }\n        bool carry2 = insideout::external_step(data_[i],i+1);\n        carry = carry || carry2;\n    }\n}\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename baseclass, typename extvalclass, bool kdd>\nvoid\nextended<table_pow2,advance_pow2,baseclass,extvalclass,kdd>::advance_table(\n        state_type delta, bool isForwards)\n{\n    typedef typename baseclass::state_type   base_state_t;\n    typedef typename extvalclass::state_type ext_state_t;\n    constexpr bitcount_t basebits = sizeof(base_state_t)*8;\n    constexpr bitcount_t extbits  = sizeof(ext_state_t)*8;\n    static_assert(basebits <= extbits || advance_pow2 > 0,\n                  \"Current implementation might overflow its carry\");\n\n    base_state_t carry = 0;\n    for (size_t i = 0; i < table_size; ++i) {\n        base_state_t total_delta = carry + delta;\n        ext_state_t  trunc_delta = ext_state_t(total_delta);\n        if (basebits > extbits) {\n            carry = total_delta >> extbits;\n        } else {\n            carry = 0;\n        }\n        carry +=\n            insideout::external_advance(data_[i],i+1, trunc_delta, isForwards);\n    }\n}\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename baseclass, typename extvalclass, bool kdd>\nvoid extended<table_pow2,advance_pow2,baseclass,extvalclass,kdd>::advance(\n    state_type distance, bool forwards)\n{\n    static_assert(kdd,\n        \"Efficient advance is too hard for non-kdd extension. \"\n        \"For a weak advance, cast to base class\");\n    state_type zero =\n        baseclass::is_mcg ? this->state_ & state_type(3U) : state_type(0U);\n    if (may_tick) {\n        state_type ticks = distance >> (advance_pow2*may_tick);\n                                        // ^-- stupidity to appease GCC\n                                        // warnings\n        state_type adv_mask =\n            baseclass::is_mcg ? tick_mask << 2 : tick_mask;\n        state_type next_advance_distance = this->distance(zero, adv_mask);\n        if (!forwards)\n            next_advance_distance = (-next_advance_distance) & tick_mask;\n        if (next_advance_distance < (distance & tick_mask)) {\n            ++ticks;\n        }\n        if (ticks)\n            advance_table(ticks, forwards);\n    }\n    if (forwards) {\n        if (may_tock && this->distance(zero) <= distance)\n            advance_table();\n        baseclass::advance(distance);\n    } else {\n        if (may_tock && -(this->distance(zero)) <= distance)\n            advance_table(state_type(1U), false);\n        baseclass::advance(-distance);\n    }\n}\n\n} // namespace pcg_detail\n\nnamespace pcg_engines {\n\nusing namespace pcg_detail;\n\n/* Predefined types for XSH RS */\n\ntypedef oneseq_base<uint8_t,  uint16_t, xsh_rs_mixin>  oneseq_xsh_rs_16_8;\ntypedef oneseq_base<uint16_t, uint32_t, xsh_rs_mixin>  oneseq_xsh_rs_32_16;\ntypedef oneseq_base<uint32_t, uint64_t, xsh_rs_mixin>  oneseq_xsh_rs_64_32;\n\ntypedef unique_base<uint8_t,  uint16_t, xsh_rs_mixin>  unique_xsh_rs_16_8;\ntypedef unique_base<uint16_t, uint32_t, xsh_rs_mixin>  unique_xsh_rs_32_16;\ntypedef unique_base<uint32_t, uint64_t, xsh_rs_mixin>  unique_xsh_rs_64_32;\n\ntypedef setseq_base<uint8_t,  uint16_t, xsh_rs_mixin>  setseq_xsh_rs_16_8;\ntypedef setseq_base<uint16_t, uint32_t, xsh_rs_mixin>  setseq_xsh_rs_32_16;\ntypedef setseq_base<uint32_t, uint64_t, xsh_rs_mixin>  setseq_xsh_rs_64_32;\n\ntypedef mcg_base<uint8_t,  uint16_t, xsh_rs_mixin>  mcg_xsh_rs_16_8;\ntypedef mcg_base<uint16_t, uint32_t, xsh_rs_mixin>  mcg_xsh_rs_32_16;\ntypedef mcg_base<uint32_t, uint64_t, xsh_rs_mixin>  mcg_xsh_rs_64_32;\n\n/* Predefined types for XSH RR */\n\ntypedef oneseq_base<uint8_t,  uint16_t, xsh_rr_mixin>  oneseq_xsh_rr_16_8;\ntypedef oneseq_base<uint16_t, uint32_t, xsh_rr_mixin>  oneseq_xsh_rr_32_16;\ntypedef oneseq_base<uint32_t, uint64_t, xsh_rr_mixin>  oneseq_xsh_rr_64_32;\n\ntypedef unique_base<uint8_t,  uint16_t, xsh_rr_mixin>  unique_xsh_rr_16_8;\ntypedef unique_base<uint16_t, uint32_t, xsh_rr_mixin>  unique_xsh_rr_32_16;\ntypedef unique_base<uint32_t, uint64_t, xsh_rr_mixin>  unique_xsh_rr_64_32;\n\ntypedef setseq_base<uint8_t,  uint16_t, xsh_rr_mixin>  setseq_xsh_rr_16_8;\ntypedef setseq_base<uint16_t, uint32_t, xsh_rr_mixin>  setseq_xsh_rr_32_16;\ntypedef setseq_base<uint32_t, uint64_t, xsh_rr_mixin>  setseq_xsh_rr_64_32;\n\ntypedef mcg_base<uint8_t,  uint16_t, xsh_rr_mixin>  mcg_xsh_rr_16_8;\ntypedef mcg_base<uint16_t, uint32_t, xsh_rr_mixin>  mcg_xsh_rr_32_16;\ntypedef mcg_base<uint32_t, uint64_t, xsh_rr_mixin>  mcg_xsh_rr_64_32;\n\n\n/* Predefined types for RXS M XS */\n\ntypedef oneseq_base<uint8_t,  uint8_t, rxs_m_xs_mixin>   oneseq_rxs_m_xs_8_8;\ntypedef oneseq_base<uint16_t, uint16_t, rxs_m_xs_mixin>  oneseq_rxs_m_xs_16_16;\ntypedef oneseq_base<uint32_t, uint32_t, rxs_m_xs_mixin>  oneseq_rxs_m_xs_32_32;\ntypedef oneseq_base<uint64_t, uint64_t, rxs_m_xs_mixin>  oneseq_rxs_m_xs_64_64;\n\ntypedef unique_base<uint8_t,  uint8_t, rxs_m_xs_mixin>  unique_rxs_m_xs_8_8;\ntypedef unique_base<uint16_t, uint16_t, rxs_m_xs_mixin> unique_rxs_m_xs_16_16;\ntypedef unique_base<uint32_t, uint32_t, rxs_m_xs_mixin> unique_rxs_m_xs_32_32;\ntypedef unique_base<uint64_t, uint64_t, rxs_m_xs_mixin> unique_rxs_m_xs_64_64;\n\ntypedef setseq_base<uint8_t,  uint8_t, rxs_m_xs_mixin>  setseq_rxs_m_xs_8_8;\ntypedef setseq_base<uint16_t, uint16_t, rxs_m_xs_mixin> setseq_rxs_m_xs_16_16;\ntypedef setseq_base<uint32_t, uint32_t, rxs_m_xs_mixin> setseq_rxs_m_xs_32_32;\ntypedef setseq_base<uint64_t, uint64_t, rxs_m_xs_mixin> setseq_rxs_m_xs_64_64;\n\n                // MCG versions don't make sense here, so aren't defined.\n\n/* Predefined types for XSL RR (only defined for \"large\" types) */\n\ntypedef oneseq_base<uint32_t, uint64_t, xsl_rr_mixin>  oneseq_xsl_rr_64_32;\n\ntypedef unique_base<uint32_t, uint64_t, xsl_rr_mixin>  unique_xsl_rr_64_32;\n\ntypedef setseq_base<uint32_t, uint64_t, xsl_rr_mixin>  setseq_xsl_rr_64_32;\n\ntypedef mcg_base<uint32_t, uint64_t, xsl_rr_mixin>  mcg_xsl_rr_64_32;\n\n\n/* Predefined types for XSL RR RR (only defined for \"large\" types) */\n\ntypedef oneseq_base<uint64_t, uint64_t, xsl_rr_rr_mixin>\n    oneseq_xsl_rr_rr_64_64;\n\ntypedef unique_base<uint64_t, uint64_t, xsl_rr_rr_mixin>\n    unique_xsl_rr_rr_64_64;\n\ntypedef setseq_base<uint64_t, uint64_t, xsl_rr_rr_mixin>\n    setseq_xsl_rr_rr_64_64;\n\n                // MCG versions don't make sense here, so aren't defined.\n\n/* Extended generators */\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename BaseRNG, bool kdd = true>\nusing ext_std8 = extended<table_pow2, advance_pow2, BaseRNG,\n                          oneseq_rxs_m_xs_8_8, kdd>;\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename BaseRNG, bool kdd = true>\nusing ext_std16 = extended<table_pow2, advance_pow2, BaseRNG,\n                           oneseq_rxs_m_xs_16_16, kdd>;\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename BaseRNG, bool kdd = true>\nusing ext_std32 = extended<table_pow2, advance_pow2, BaseRNG,\n                           oneseq_rxs_m_xs_32_32, kdd>;\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2,\n          typename BaseRNG, bool kdd = true>\nusing ext_std64 = extended<table_pow2, advance_pow2, BaseRNG,\n                           oneseq_rxs_m_xs_64_64, kdd>;\n\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2, bool kdd = true>\nusing ext_oneseq_rxs_m_xs_32_32 =\n          ext_std32<table_pow2, advance_pow2, oneseq_rxs_m_xs_32_32, kdd>;\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2, bool kdd = true>\nusing ext_mcg_xsh_rs_64_32 =\n          ext_std32<table_pow2, advance_pow2, mcg_xsh_rs_64_32, kdd>;\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2, bool kdd = true>\nusing ext_oneseq_xsh_rs_64_32 =\n          ext_std32<table_pow2, advance_pow2, oneseq_xsh_rs_64_32, kdd>;\n\ntemplate <bitcount_t table_pow2, bitcount_t advance_pow2, bool kdd = true>\nusing ext_setseq_xsh_rr_64_32 =\n          ext_std32<table_pow2, advance_pow2, setseq_xsh_rr_64_32, kdd>;\n\n} // namespace pcg_engines\n\ntypedef pcg_engines::setseq_xsh_rr_64_32        pcg32;\ntypedef pcg_engines::oneseq_xsh_rr_64_32        pcg32_oneseq;\ntypedef pcg_engines::unique_xsh_rr_64_32        pcg32_unique;\ntypedef pcg_engines::mcg_xsh_rs_64_32           pcg32_fast;\n\ntypedef pcg_engines::setseq_rxs_m_xs_8_8        pcg8_once_insecure;\ntypedef pcg_engines::setseq_rxs_m_xs_16_16      pcg16_once_insecure;\ntypedef pcg_engines::setseq_rxs_m_xs_32_32      pcg32_once_insecure;\ntypedef pcg_engines::setseq_rxs_m_xs_64_64      pcg64_once_insecure;\n\ntypedef pcg_engines::oneseq_rxs_m_xs_8_8        pcg8_oneseq_once_insecure;\ntypedef pcg_engines::oneseq_rxs_m_xs_16_16      pcg16_oneseq_once_insecure;\ntypedef pcg_engines::oneseq_rxs_m_xs_32_32      pcg32_oneseq_once_insecure;\ntypedef pcg_engines::oneseq_rxs_m_xs_64_64      pcg64_oneseq_once_insecure;\n\n\n// These two extended RNGs provide two-dimensionally equidistributed\n// 32-bit generators.  pcg32_k2_fast occupies the same space as pcg64,\n// and can be called twice to generate 64 bits, but does not required\n// 128-bit math; on 32-bit systems, it's faster than pcg64 as well.\n\ntypedef pcg_engines::ext_setseq_xsh_rr_64_32<6,16,true>     pcg32_k2;\ntypedef pcg_engines::ext_oneseq_xsh_rs_64_32<6,32,true>     pcg32_k2_fast;\n\n// These eight extended RNGs have about as much state as arc4random\n//\n//  - the k variants are k-dimensionally equidistributed\n//  - the c variants offer better crypographic security\n//\n// (just how good the cryptographic security is is an open question)\n\ntypedef pcg_engines::ext_setseq_xsh_rr_64_32<6,16,true>     pcg32_k64;\ntypedef pcg_engines::ext_mcg_xsh_rs_64_32<6,32,true>        pcg32_k64_oneseq;\ntypedef pcg_engines::ext_oneseq_xsh_rs_64_32<6,32,true>     pcg32_k64_fast;\n\ntypedef pcg_engines::ext_setseq_xsh_rr_64_32<6,16,false>    pcg32_c64;\ntypedef pcg_engines::ext_oneseq_xsh_rs_64_32<6,32,false>    pcg32_c64_oneseq;\ntypedef pcg_engines::ext_mcg_xsh_rs_64_32<6,32,false>       pcg32_c64_fast;\n\n// These eight extended RNGs have more state than the Mersenne twister\n//\n//  - the k variants are k-dimensionally equidistributed\n//  - the c variants offer better crypographic security\n//\n// (just how good the cryptographic security is is an open question)\n\ntypedef pcg_engines::ext_setseq_xsh_rr_64_32<10,16,true>    pcg32_k1024;\ntypedef pcg_engines::ext_oneseq_xsh_rs_64_32<10,32,true>    pcg32_k1024_fast;\n\ntypedef pcg_engines::ext_setseq_xsh_rr_64_32<10,16,false>   pcg32_c1024;\ntypedef pcg_engines::ext_oneseq_xsh_rs_64_32<10,32,false>   pcg32_c1024_fast;\n\n// These generators have an insanely huge period (2^524352), and is suitable\n// for silly party tricks, such as dumping out 64 KB ZIP files at an arbitrary\n// point in the future.   [Actually, over the full period of the generator, it\n// will produce every 64 KB ZIP file 2^64 times!]\n\ntypedef pcg_engines::ext_setseq_xsh_rr_64_32<14,16,true>    pcg32_k16384;\ntypedef pcg_engines::ext_oneseq_xsh_rs_64_32<14,32,true>    pcg32_k16384_fast;\n\n#endif // PCG_RAND_HPP_INCLUDED\n"
  },
  {
    "path": "lib/pcg/pcg_uint128.hpp",
    "content": "/*\n * PCG Random Number Generation for C++\n *\n * Copyright 2014 Melissa O'Neill <oneill@pcg-random.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * For additional information about the PCG random number generation scheme,\n * including its license and other licensing options, visit\n *\n *     http://www.pcg-random.org\n */\n\n/*\n * This code provides a a C++ class that can provide 128-bit (or higher)\n * integers.  To produce 2K-bit integers, it uses two K-bit integers,\n * placed in a union that allowes the code to also see them as four K/2 bit\n * integers (and access them either directly name, or by index).\n *\n * It may seem like we're reinventing the wheel here, because several\n * libraries already exist that support large integers, but most existing\n * libraries provide a very generic multiprecision code, but here we're\n * operating at a fixed size.  Also, most other libraries are fairly\n * heavyweight.  So we use a direct implementation.  Sadly, it's much slower\n * than hand-coded assembly or direct CPU support.\n */\n\n#ifndef PCG_UINT128_HPP_INCLUDED\n#define PCG_UINT128_HPP_INCLUDED 1\n\n#include <cstdint>\n#include <cstdio>\n#include <cassert>\n#include <climits>\n#include <utility>\n#include <initializer_list>\n#include <type_traits>\n\n/*\n * We want to lay the type out the same way that a native type would be laid\n * out, which means we must know the machine's endian, at compile time.\n * This ugliness attempts to do so.\n */\n\n#ifndef PCG_LITTLE_ENDIAN\n    #if defined(__BYTE_ORDER__)\n        #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\n            #define PCG_LITTLE_ENDIAN 1\n        #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__\n            #define PCG_LITTLE_ENDIAN 0\n        #else\n            #error __BYTE_ORDER__ does not match a standard endian, pick a side\n        #endif\n    #elif __LITTLE_ENDIAN__ || _LITTLE_ENDIAN\n        #define PCG_LITTLE_ENDIAN 1\n    #elif __BIG_ENDIAN__ || _BIG_ENDIAN\n        #define PCG_LITTLE_ENDIAN 0\n    #elif __x86_64 || __x86_64__ || __i386 || __i386__\n        #define PCG_LITTLE_ENDIAN 1\n    #elif __powerpc__ || __POWERPC__ || __ppc__ || __PPC__ \\\n          || __m68k__ || __mc68000__\n        #define PCG_LITTLE_ENDIAN 0\n    #else\n        #error Unable to determine target endianness\n    #endif\n#endif\n\nnamespace pcg_extras {\n\n// Recent versions of GCC have intrinsics we can use to quickly calculate\n// the number of leading and trailing zeros in a number.  If possible, we\n// use them, otherwise we fall back to old-fashioned bit twiddling to figure\n// them out.\n\n#ifndef PCG_BITCOUNT_T\n    typedef uint8_t bitcount_t;\n#else\n    typedef PCG_BITCOUNT_T bitcount_t;\n#endif\n\n/*\n * Provide some useful helper functions\n *      * flog2                 floor(log2(x))\n *      * trailingzeros         number of trailing zero bits\n */\n\n#ifdef __GNUC__         // Any GNU-compatible compiler supporting C++11 has\n                        // some useful intrinsics we can use.\n\ninline bitcount_t flog2(uint32_t v)\n{\n    return 31 - __builtin_clz(v);\n}\n\ninline bitcount_t trailingzeros(uint32_t v)\n{\n    return __builtin_ctz(v);\n}\n\ninline bitcount_t flog2(uint64_t v)\n{\n#if UINT64_MAX == ULONG_MAX\n    return 63 - __builtin_clzl(v);\n#elif UINT64_MAX == ULLONG_MAX\n    return 63 - __builtin_clzll(v);\n#else\n    #error Cannot find a function for uint64_t\n#endif\n}\n\ninline bitcount_t trailingzeros(uint64_t v)\n{\n#if UINT64_MAX == ULONG_MAX\n    return __builtin_ctzl(v);\n#elif UINT64_MAX == ULLONG_MAX\n    return __builtin_ctzll(v);\n#else\n    #error Cannot find a function for uint64_t\n#endif\n}\n\n#else                   // Otherwise, we fall back to bit twiddling\n                        // implementations\n\ninline bitcount_t flog2(uint32_t v)\n{\n    // Based on code by Eric Cole and Mark Dickinson, which appears at\n    // https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn\n\n    static const uint8_t multiplyDeBruijnBitPos[32] = {\n      0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,\n      8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31\n    };\n\n    v |= v >> 1; // first round down to one less than a power of 2\n    v |= v >> 2;\n    v |= v >> 4;\n    v |= v >> 8;\n    v |= v >> 16;\n\n    return multiplyDeBruijnBitPos[(uint32_t)(v * 0x07C4ACDDU) >> 27];\n}\n\ninline bitcount_t trailingzeros(uint32_t v)\n{\n    static const uint8_t multiplyDeBruijnBitPos[32] = {\n      0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,\n      31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9\n    };\n\n    return multiplyDeBruijnBitPos[((uint32_t)((v & -v) * 0x077CB531U)) >> 27];\n}\n\ninline bitcount_t flog2(uint64_t v)\n{\n    uint32_t high = v >> 32;\n    uint32_t low  = uint32_t(v);\n\n    return high ? 32+flog2(high) : flog2(low);\n}\n\ninline bitcount_t trailingzeros(uint64_t v)\n{\n    uint32_t high = v >> 32;\n    uint32_t low  = uint32_t(v);\n\n    return low ? trailingzeros(low) : trailingzeros(high)+32;\n}\n\n#endif\n\ntemplate <typename UInt>\ninline bitcount_t clog2(UInt v)\n{\n    return flog2(v) + ((v & (-v)) != v);\n}\n\ntemplate <typename UInt>\ninline UInt addwithcarry(UInt x, UInt y, bool carryin, bool* carryout)\n{\n    UInt half_result = y + carryin;\n    UInt result = x + half_result;\n    *carryout = (half_result < y) || (result < x);\n    return result;\n}\n\ntemplate <typename UInt>\ninline UInt subwithcarry(UInt x, UInt y, bool carryin, bool* carryout)\n{\n    UInt half_result = y + carryin;\n    UInt result = x - half_result;\n    *carryout = (half_result < y) || (result > x);\n    return result;\n}\n\n\ntemplate <typename UInt, typename UIntX2>\nclass uint_x4 {\n// private:\npublic:\n    union {\n#if PCG_LITTLE_ENDIAN\n        struct {\n            UInt v0, v1, v2, v3;\n        } w;\n        struct {\n            UIntX2 v01, v23;\n        } d;\n#else\n        struct {\n            UInt v3, v2, v1, v0;\n        } w;\n        struct {\n            UIntX2 v23, v01;\n        } d;\n#endif\n        // For the array access versions, the code that uses the array\n        // must handle endian itself.  Yuck.\n        UInt wa[4];\n        UIntX2 da[2];\n    };\n\npublic:\n    uint_x4() = default;\n\n    constexpr uint_x4(UInt v3, UInt v2, UInt v1, UInt v0)\n#if PCG_LITTLE_ENDIAN\n       : w{v0, v1, v2, v3}\n#else\n       : w{v3, v2, v1, v0}\n#endif\n    {\n        // Nothing (else) to do\n    }\n\n    constexpr uint_x4(UIntX2 v23, UIntX2 v01)\n#if PCG_LITTLE_ENDIAN\n       : d{v01,v23}\n#else\n       : d{v23,v01}\n#endif\n    {\n        // Nothing (else) to do\n    }\n\n    template<class Integral,\n             typename std::enable_if<(std::is_integral<Integral>::value\n                                      && sizeof(Integral) <= sizeof(UIntX2))\n                                    >::type* = nullptr>\n    constexpr uint_x4(Integral v01)\n#if PCG_LITTLE_ENDIAN\n       : d{UIntX2(v01),0UL}\n#else\n       : d{0UL,UIntX2(v01)}\n#endif\n    {\n        // Nothing (else) to do\n    }\n\n    explicit constexpr operator uint64_t() const\n    {\n        return d.v01;\n    }\n\n    explicit constexpr operator uint32_t() const\n    {\n        return w.v0;\n    }\n\n    explicit constexpr operator int() const\n    {\n        return w.v0;\n    }\n\n    explicit constexpr operator uint16_t() const\n    {\n        return w.v0;\n    }\n\n    explicit constexpr operator uint8_t() const\n    {\n        return w.v0;\n    }\n\n    typedef typename std::conditional<std::is_same<uint64_t,\n                                                   unsigned long>::value,\n                                      unsigned long long,\n                                      unsigned long>::type\n            uint_missing_t;\n\n    explicit constexpr operator uint_missing_t() const\n    {\n        return d.v01;\n    }\n\n    explicit constexpr operator bool() const\n    {\n        return d.v01 || d.v23;\n    }\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator*(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend std::pair< uint_x4<U,V>,uint_x4<U,V> >\n        divmod(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator+(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator-(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator<<(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator>>(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator&(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator|(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator^(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend bool operator==(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend bool operator!=(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend bool operator<(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend bool operator<=(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend bool operator>(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend bool operator>=(const uint_x4<U,V>&, const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator~(const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend uint_x4<U,V> operator-(const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend bitcount_t flog2(const uint_x4<U,V>&);\n\n    template<typename U, typename V>\n    friend bitcount_t trailingzeros(const uint_x4<U,V>&);\n\n    uint_x4& operator*=(const uint_x4& rhs)\n    {\n        uint_x4 result = *this * rhs;\n        return *this = result;\n    }\n\n    uint_x4& operator/=(const uint_x4& rhs)\n    {\n        uint_x4 result = *this / rhs;\n        return *this = result;\n    }\n\n    uint_x4& operator%=(const uint_x4& rhs)\n    {\n        uint_x4 result = *this % rhs;\n        return *this = result;\n    }\n\n    uint_x4& operator+=(const uint_x4& rhs)\n    {\n        uint_x4 result = *this + rhs;\n        return *this = result;\n    }\n\n    uint_x4& operator-=(const uint_x4& rhs)\n    {\n        uint_x4 result = *this - rhs;\n        return *this = result;\n    }\n\n    uint_x4& operator&=(const uint_x4& rhs)\n    {\n        uint_x4 result = *this & rhs;\n        return *this = result;\n    }\n\n    uint_x4& operator|=(const uint_x4& rhs)\n    {\n        uint_x4 result = *this | rhs;\n        return *this = result;\n    }\n\n    uint_x4& operator^=(const uint_x4& rhs)\n    {\n        uint_x4 result = *this ^ rhs;\n        return *this = result;\n    }\n\n    uint_x4& operator>>=(bitcount_t shift)\n    {\n        uint_x4 result = *this >> shift;\n        return *this = result;\n    }\n\n    uint_x4& operator<<=(bitcount_t shift)\n    {\n        uint_x4 result = *this << shift;\n        return *this = result;\n    }\n\n};\n\ntemplate<typename U, typename V>\nbitcount_t flog2(const uint_x4<U,V>& v)\n{\n#if PCG_LITTLE_ENDIAN\n    for (uint8_t i = 4; i !=0; /* dec in loop */) {\n        --i;\n#else\n    for (uint8_t i = 0; i < 4; ++i) {\n#endif\n        if (v.wa[i] == 0)\n             continue;\n        return flog2(v.wa[i]) + (sizeof(U)*CHAR_BIT)*i;\n    }\n    abort();\n}\n\ntemplate<typename U, typename V>\nbitcount_t trailingzeros(const uint_x4<U,V>& v)\n{\n#if PCG_LITTLE_ENDIAN\n    for (uint8_t i = 0; i < 4; ++i) {\n#else\n    for (uint8_t i = 4; i !=0; /* dec in loop */) {\n        --i;\n#endif\n        if (v.wa[i] != 0)\n            return trailingzeros(v.wa[i]) + (sizeof(U)*CHAR_BIT)*i;\n    }\n    return (sizeof(U)*CHAR_BIT)*4;\n}\n\ntemplate <typename UInt, typename UIntX2>\nstd::pair< uint_x4<UInt,UIntX2>, uint_x4<UInt,UIntX2> >\n    divmod(const uint_x4<UInt,UIntX2>& orig_dividend,\n           const uint_x4<UInt,UIntX2>& divisor)\n{\n    // If the dividend is less than the divisor, the answer is always zero.\n    // This takes care of boundary cases like 0/x (which would otherwise be\n    // problematic because we can't take the log of zero.  (The boundary case\n    // of division by zero is undefined.)\n    if (orig_dividend < divisor)\n        return { uint_x4<UInt,UIntX2>(0UL), orig_dividend };\n\n    auto dividend = orig_dividend;\n\n    auto log2_divisor  = flog2(divisor);\n    auto log2_dividend = flog2(dividend);\n    // assert(log2_dividend >= log2_divisor);\n    bitcount_t logdiff = log2_dividend - log2_divisor;\n\n    constexpr uint_x4<UInt,UIntX2> ONE(1UL);\n    if (logdiff == 0)\n        return { ONE, dividend - divisor };\n\n    // Now we change the log difference to\n    //  floor(log2(divisor)) - ceil(log2(dividend))\n    // to ensure that we *underestimate* the result.\n    logdiff -= 1;\n\n    uint_x4<UInt,UIntX2> quotient(0UL);\n\n    auto qfactor = ONE << logdiff;\n    auto factor  = divisor << logdiff;\n\n    do {\n        dividend -= factor;\n        quotient += qfactor;\n        while (dividend < factor) {\n            factor  >>= 1;\n            qfactor >>= 1;\n        }\n    } while (dividend >= divisor);\n\n    return { quotient, dividend };\n}\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator/(const uint_x4<UInt,UIntX2>& dividend,\n                               const uint_x4<UInt,UIntX2>& divisor)\n{\n    return divmod(dividend, divisor).first;\n}\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator%(const uint_x4<UInt,UIntX2>& dividend,\n                               const uint_x4<UInt,UIntX2>& divisor)\n{\n    return divmod(dividend, divisor).second;\n}\n\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator*(const uint_x4<UInt,UIntX2>& a,\n                               const uint_x4<UInt,UIntX2>& b)\n{\n    uint_x4<UInt,UIntX2> r = {0U, 0U, 0U, 0U};\n    bool carryin = false;\n    bool carryout;\n    UIntX2 a0b0 = UIntX2(a.w.v0) * UIntX2(b.w.v0);\n    r.w.v0 = UInt(a0b0);\n    r.w.v1 = UInt(a0b0 >> 32);\n\n    UIntX2 a1b0 = UIntX2(a.w.v1) * UIntX2(b.w.v0);\n    r.w.v2 = UInt(a1b0 >> 32);\n    r.w.v1 = addwithcarry(r.w.v1, UInt(a1b0), carryin, &carryout);\n    carryin = carryout;\n    r.w.v2 = addwithcarry(r.w.v2, UInt(0U), carryin, &carryout);\n    carryin = carryout;\n    r.w.v3 = addwithcarry(r.w.v3, UInt(0U), carryin, &carryout);\n\n    UIntX2 a0b1 = UIntX2(a.w.v0) * UIntX2(b.w.v1);\n    carryin = false;\n    r.w.v2 = addwithcarry(r.w.v2, UInt(a0b1 >> 32), carryin, &carryout);\n    carryin = carryout;\n    r.w.v3 = addwithcarry(r.w.v3, UInt(0U), carryin, &carryout);\n\n    carryin = false;\n    r.w.v1 = addwithcarry(r.w.v1, UInt(a0b1), carryin, &carryout);\n    carryin = carryout;\n    r.w.v2 = addwithcarry(r.w.v2, UInt(0U), carryin, &carryout);\n    carryin = carryout;\n    r.w.v3 = addwithcarry(r.w.v3, UInt(0U), carryin, &carryout);\n\n    UIntX2 a1b1 = UIntX2(a.w.v1) * UIntX2(b.w.v1);\n    carryin = false;\n    r.w.v2 = addwithcarry(r.w.v2, UInt(a1b1), carryin, &carryout);\n    carryin = carryout;\n    r.w.v3 = addwithcarry(r.w.v3, UInt(a1b1 >> 32), carryin, &carryout);\n\n    r.d.v23 += a.d.v01 * b.d.v23 + a.d.v23 * b.d.v01;\n\n    return r;\n}\n\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator+(const uint_x4<UInt,UIntX2>& a,\n                               const uint_x4<UInt,UIntX2>& b)\n{\n    uint_x4<UInt,UIntX2> r = {0U, 0U, 0U, 0U};\n\n    bool carryin = false;\n    bool carryout;\n    r.w.v0 = addwithcarry(a.w.v0, b.w.v0, carryin, &carryout);\n    carryin = carryout;\n    r.w.v1 = addwithcarry(a.w.v1, b.w.v1, carryin, &carryout);\n    carryin = carryout;\n    r.w.v2 = addwithcarry(a.w.v2, b.w.v2, carryin, &carryout);\n    carryin = carryout;\n    r.w.v3 = addwithcarry(a.w.v3, b.w.v3, carryin, &carryout);\n\n    return r;\n}\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator-(const uint_x4<UInt,UIntX2>& a,\n                               const uint_x4<UInt,UIntX2>& b)\n{\n    uint_x4<UInt,UIntX2> r = {0U, 0U, 0U, 0U};\n\n    bool carryin = false;\n    bool carryout;\n    r.w.v0 = subwithcarry(a.w.v0, b.w.v0, carryin, &carryout);\n    carryin = carryout;\n    r.w.v1 = subwithcarry(a.w.v1, b.w.v1, carryin, &carryout);\n    carryin = carryout;\n    r.w.v2 = subwithcarry(a.w.v2, b.w.v2, carryin, &carryout);\n    carryin = carryout;\n    r.w.v3 = subwithcarry(a.w.v3, b.w.v3, carryin, &carryout);\n\n    return r;\n}\n\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator&(const uint_x4<UInt,UIntX2>& a,\n                               const uint_x4<UInt,UIntX2>& b)\n{\n    return uint_x4<UInt,UIntX2>(a.d.v23 & b.d.v23, a.d.v01 & b.d.v01);\n}\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator|(const uint_x4<UInt,UIntX2>& a,\n                               const uint_x4<UInt,UIntX2>& b)\n{\n    return uint_x4<UInt,UIntX2>(a.d.v23 | b.d.v23, a.d.v01 | b.d.v01);\n}\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator^(const uint_x4<UInt,UIntX2>& a,\n                               const uint_x4<UInt,UIntX2>& b)\n{\n    return uint_x4<UInt,UIntX2>(a.d.v23 ^ b.d.v23, a.d.v01 ^ b.d.v01);\n}\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator~(const uint_x4<UInt,UIntX2>& v)\n{\n    return uint_x4<UInt,UIntX2>(~v.d.v23, ~v.d.v01);\n}\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator-(const uint_x4<UInt,UIntX2>& v)\n{\n    return uint_x4<UInt,UIntX2>(0UL,0UL) - v;\n}\n\ntemplate <typename UInt, typename UIntX2>\nbool operator==(const uint_x4<UInt,UIntX2>& a, const uint_x4<UInt,UIntX2>& b)\n{\n    return (a.d.v01 == b.d.v01) && (a.d.v23 == b.d.v23);\n}\n\ntemplate <typename UInt, typename UIntX2>\nbool operator!=(const uint_x4<UInt,UIntX2>& a, const uint_x4<UInt,UIntX2>& b)\n{\n    return !operator==(a,b);\n}\n\n\ntemplate <typename UInt, typename UIntX2>\nbool operator<(const uint_x4<UInt,UIntX2>& a, const uint_x4<UInt,UIntX2>& b)\n{\n    return (a.d.v23 < b.d.v23)\n           || ((a.d.v23 == b.d.v23) && (a.d.v01 < b.d.v01));\n}\n\ntemplate <typename UInt, typename UIntX2>\nbool operator>(const uint_x4<UInt,UIntX2>& a, const uint_x4<UInt,UIntX2>& b)\n{\n    return operator<(b,a);\n}\n\ntemplate <typename UInt, typename UIntX2>\nbool operator<=(const uint_x4<UInt,UIntX2>& a, const uint_x4<UInt,UIntX2>& b)\n{\n    return !(operator<(b,a));\n}\n\ntemplate <typename UInt, typename UIntX2>\nbool operator>=(const uint_x4<UInt,UIntX2>& a, const uint_x4<UInt,UIntX2>& b)\n{\n    return !(operator<(a,b));\n}\n\n\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator<<(const uint_x4<UInt,UIntX2>& v,\n                                const bitcount_t shift)\n{\n    uint_x4<UInt,UIntX2> r = {0U, 0U, 0U, 0U};\n    const bitcount_t bits    = sizeof(UInt) * CHAR_BIT;\n    const bitcount_t bitmask = bits - 1;\n    const bitcount_t shiftdiv = shift / bits;\n    const bitcount_t shiftmod = shift & bitmask;\n\n    if (shiftmod) {\n        UInt carryover = 0;\n#if PCG_LITTLE_ENDIAN\n        for (uint8_t out = shiftdiv, in = 0; out < 4; ++out, ++in) {\n#else\n        for (uint8_t out = 4-shiftdiv, in = 4; out != 0; /* dec in loop */) {\n            --out, --in;\n#endif\n            r.wa[out] = (v.wa[in] << shiftmod) | carryover;\n            carryover = (v.wa[in] >> (bits - shiftmod));\n        }\n    } else {\n#if PCG_LITTLE_ENDIAN\n        for (uint8_t out = shiftdiv, in = 0; out < 4; ++out, ++in) {\n#else\n        for (uint8_t out = 4-shiftdiv, in = 4; out != 0; /* dec in loop */) {\n            --out, --in;\n#endif\n            r.wa[out] = v.wa[in];\n        }\n    }\n\n    return r;\n}\n\ntemplate <typename UInt, typename UIntX2>\nuint_x4<UInt,UIntX2> operator>>(const uint_x4<UInt,UIntX2>& v,\n                                const bitcount_t shift)\n{\n    uint_x4<UInt,UIntX2> r = {0U, 0U, 0U, 0U};\n    const bitcount_t bits    = sizeof(UInt) * CHAR_BIT;\n    const bitcount_t bitmask = bits - 1;\n    const bitcount_t shiftdiv = shift / bits;\n    const bitcount_t shiftmod = shift & bitmask;\n\n    if (shiftmod) {\n        UInt carryover = 0;\n#if PCG_LITTLE_ENDIAN\n        for (uint8_t out = 4-shiftdiv, in = 4; out != 0; /* dec in loop */) {\n            --out, --in;\n#else\n        for (uint8_t out = shiftdiv, in = 0; out < 4; ++out, ++in) {\n#endif\n            r.wa[out] = (v.wa[in] >> shiftmod) | carryover;\n            carryover = (v.wa[in] << (bits - shiftmod));\n        }\n    } else {\n#if PCG_LITTLE_ENDIAN\n        for (uint8_t out = 4-shiftdiv, in = 4; out != 0; /* dec in loop */) {\n            --out, --in;\n#else\n        for (uint8_t out = shiftdiv, in = 0; out < 4; ++out, ++in) {\n#endif\n            r.wa[out] = v.wa[in];\n        }\n    }\n\n    return r;\n}\n\n} // namespace pcg_extras\n\n#endif // PCG_UINT128_HPP_INCLUDED\n"
  },
  {
    "path": "lib/pge_cpu_arch.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#if defined(_X86_) || defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86)\n#   define PGE_CPU_x86\n#   define PGE_CPU_x86_32\n#elif defined(__x86_64__) || defined(__amd64__) || defined(_WIN64) || defined(_M_X64) || defined(_M_AMD64)\n#   define PGE_CPU_x86\n#   define PGE_CPU_x86_64\n#elif defined(__arm__) || defined(__TARGET_ARCH_ARM) || defined(__ARM_ARCH)\n#   if defined(__ARM_ARCH_8__) \\\n     || defined(__ARM_ARCH_8A) \\\n     || defined(__ARM_ARCH_8A__) \\\n     || defined(__ARM_ARCH_8R__) \\\n     || defined(__ARM_ARCH_8M__) \\\n     || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 8) \\\n     || (defined(__ARM_ARCH) && __ARM_ARCH >= 8) || defined(_M_ARM64) || defined(_M_ARM64EC)\n#       define PGE_CPU_ARM64\n#       define PGE_CPU_ARMv8\n#   elif defined(__ARM_ARCH_7__) \\\n       || defined(__ARM_ARCH_7A__) \\\n       || defined(__ARM_ARCH_7R__) \\\n       || defined(__ARM_ARCH_7M__) \\\n       || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 7) \\\n       || (defined(__ARM_ARCH) && __ARM_ARCH >= 7)\n#       define PGE_CPU_ARM32\n#       define PGE_CPU_ARMv7\n#   elif defined(__ARM_ARCH_6__) \\\n       || defined(__ARM_ARCH_6J__) \\\n       || defined(__ARM_ARCH_6T2__) \\\n       || defined(__ARM_ARCH_6Z__) \\\n       || defined(__ARM_ARCH_6K__) \\\n       || defined(__ARM_ARCH_6ZK__) \\\n       || defined(__ARM_ARCH_6M__) \\\n       || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 6) \\\n       || (defined(__ARM_ARCH) && __ARM_ARCH >= 6)\n#       define PGE_CPU_ARM32\n#       define PGE_CPU_ARMv6\n#   elif defined(__ARM_ARCH_5TEJ__) \\\n      || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 5)\n#       define PGE_CPU_ARM32\n#       define PGE_CPU_ARMv5\n#   else\n#       define PGE_CPU_ARM32\n#   endif\n#elif defined(__mips__) || defined(__mips) || defined(__MIPS__)\n#       define PGE_CPU_MIPS\n#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\\n   || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC)  \\\n   || defined(_M_MPPC) || defined(_M_PPC)\n#   if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__)\n#       define PGE_CPU_PPC\n#       define PGE_CPU_PPC64\n#   else\n#       define PGE_CPU_PPC\n#       define PGE_CPU_PPC32\n#   endif\n#elif defined(__riscv) // FIXME: Adjust if any extra macros needed\n#   if defined(__riscv64)\n#       define PGE_CPU_RISCV\n#       define PGE_CPU_RISCV64\n#   elif defined(__riscv32)\n#       define PGE_CPU_RISCV\n#       define PGE_CPU_RISCV32\n#   endif\n#elif defined(__loongarch64)\n#       define PGE_CPU_LOONGARCH\n#       define PGE_CPU_LOONGARCH64\n#elif defined(__loongarch)\n#       define PGE_CPU_LOONGARCH\n#       define PGE_CPU_LOONGARCH32\n#else\n#       define PGE_CPU_UNKNOWN\n#       warning CPU is unknown! Please add missing architecture!\n#endif\n"
  },
  {
    "path": "lib/pge_delay.h",
    "content": "#ifndef PGE_DELAY_H\n#define PGE_DELAY_H\n\n#ifdef __EMSCRIPTEN__\n#   include <emscripten.h>\n#   define PGE_Delay(x) emscripten_sleep(x)\n#elif defined(__3DS__)\n#   include <3ds.h>\n#   define PGE_Delay(x) svcSleepThread((uint64_t)(x) * 1000000)\n#elif defined(__WII__)\nextern \"C\" void udelay(unsigned us);\n#   define PGE_Delay(x) udelay((x) * 1000)\n#else\n#   include \"sdl_proxy/sdl_timer.h\"\n#   define PGE_Delay(x) SDL_Delay(x)\n#endif\n\n#endif // PGE_DELAY_H\n"
  },
  {
    "path": "lib/pge_tonearest.h",
    "content": "// This code was taken and adapted from LLVM project:\n\n//===-- Nearest integer floating-point operations ---------------*- C++ -*-===//\n//\n// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.\n// See https://llvm.org/LICENSE.txt for license information.\n// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception\n//\n//===----------------------------------------------------------------------===//\n\n// https://github.com/llvm/llvm-project/blob/main/libc/utils/FPUtil/NearestIntegerOperations.h\n\n#pragma once\n#ifndef PGE_TO_NEAREST\n#define PGE_TO_NEAREST\n\n#include <cstdint>\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#if defined(arm) && !defined(__SOFTFP__) && !defined(__VFP_FP__) && !defined(__MAVERICK__)\ninline void swap_halfes(uint64_t &x)\n{\n    uint64_t y = ((x & 0xFFFFFFFF00000000) >> 32) & 0xFFFFFFFF;\n    x = ((x << 32) & 0xFFFFFFFF00000000) & y;\n}\n#else // Do nothing\n#   define swap_halfes(x)\n#endif\n\nnamespace PgeFloatProp\n{\n\nstatic const uint32_t bitWidth = sizeof(uint64_t) << 3;\n\nstatic const uint32_t mantissaWidth = 52;\nstatic const uint32_t exponentWidth = 11;\nstatic const uint64_t mantissaMask = (uint64_t(1) << mantissaWidth) - 1;\nstatic const uint64_t signMask = uint64_t(1) << (exponentWidth + mantissaWidth);\nstatic const uint64_t exponentMask = ~(signMask | mantissaMask);\nstatic const uint32_t exponentBias = 1023;\n\n// If a number x is a NAN, then it is a quiet NAN if:\n//   QuietNaNMask & bits(x) != 0\n// Else, it is a signalling NAN.\nstatic const uint64_t quietNaNMask = 0x0008000000000000ULL;\n\n}\n\nunion PgeFPBits\n{\n    uint64_t bits;\n\n    void setMantissa(uint64_t mantVal)\n    {\n        swap_halfes(bits);\n        mantVal &= (PgeFloatProp::mantissaMask);\n        bits &= ~(PgeFloatProp::mantissaMask);\n        bits |= mantVal;\n        swap_halfes(bits);\n    }\n\n    uint64_t getMantissa() const\n    {\n        uint64_t ret = bits;\n        swap_halfes(ret);\n        return ret & PgeFloatProp::mantissaMask;\n    }\n\n    void setUnbiasedExponent(uint64_t expVal)\n    {\n        swap_halfes(bits);\n        expVal = (expVal << (PgeFloatProp::mantissaWidth)) & PgeFloatProp::exponentMask;\n        bits &= ~(PgeFloatProp::exponentMask);\n        bits |= expVal;\n        swap_halfes(bits);\n    }\n\n    uint16_t getUnbiasedExponent() const\n    {\n        uint64_t ret = bits;\n        swap_halfes(ret);\n        return uint16_t((ret & PgeFloatProp::exponentMask) >>\n                        (PgeFloatProp::mantissaWidth));\n    }\n\n    void setSign(bool signVal)\n    {\n        swap_halfes(bits);\n        bits &= ~(PgeFloatProp::signMask);\n        uint64_t sign = uint64_t(signVal) << (PgeFloatProp::bitWidth - 1);\n        bits |= sign;\n        swap_halfes(bits);\n    }\n\n    bool getSign() const\n    {\n        uint64_t ret = bits;\n        swap_halfes(ret);\n        return ((ret & PgeFloatProp::signMask) >> (PgeFloatProp::bitWidth - 1));\n    }\n    double val;\n\n    static const int exponentBias = (1 << (PgeFloatProp::exponentWidth - 1)) - 1;\n    static const int maxExponent = (1 << PgeFloatProp::exponentWidth) - 1;\n\n    static const uint64_t minSubnormal = uint64_t(1);\n    static const uint64_t maxSubnormal = (uint64_t(1) << PgeFloatProp::mantissaWidth) - 1;\n    static const uint64_t minNormal = (uint64_t(1) << PgeFloatProp::mantissaWidth);\n    static const uint64_t maxNormal = ((uint64_t(maxExponent) - 1) << PgeFloatProp::mantissaWidth) | maxSubnormal;\n\n    explicit PgeFPBits(double x) : val(x) {}\n    explicit PgeFPBits(uint64_t x) : bits(x) {}\n\n    PgeFPBits() : bits(0) {}\n\n    explicit operator double()\n    {\n        return val;\n    }\n\n    uint64_t uintval() const\n    {\n        uint64_t ret = bits;\n        swap_halfes(ret);\n        return ret;\n    }\n\n    int getExponent() const\n    {\n        return int(getUnbiasedExponent()) - exponentBias;\n    }\n\n    bool isZero() const\n    {\n        return getMantissa() == 0 && getUnbiasedExponent() == 0;\n    }\n\n    bool isInf() const\n    {\n        return getMantissa() == 0 && getUnbiasedExponent() == maxExponent;\n    }\n\n    bool isNaN() const\n    {\n        return getUnbiasedExponent() == maxExponent && getMantissa() != 0;\n    }\n\n    bool isInfOrNaN() const\n    {\n        return getUnbiasedExponent() == maxExponent;\n    }\n\n    static PgeFPBits zero()\n    {\n        return PgeFPBits();\n    }\n\n    static PgeFPBits negZero()\n    {\n        return PgeFPBits(uint64_t(1) << (sizeof(uint64_t) * 8 - 1));\n    }\n\n    static PgeFPBits inf()\n    {\n        PgeFPBits bits;\n        bits.setUnbiasedExponent(maxExponent);\n        return bits;\n    }\n\n    static PgeFPBits negInf()\n    {\n        PgeFPBits bits = inf();\n        bits.setSign(1);\n        return bits;\n    }\n\n    static double buildNaN(uint64_t v)\n    {\n        PgeFPBits bits = inf();\n        bits.setMantissa(v);\n        return double(bits);\n    }\n};\n\n\nSDL_FORCE_INLINE double pge_toNearest(double x)\n{\n    PgeFPBits bits(x);\n\n    // If x is infinity NaN or zero, return it.\n    if(bits.isInfOrNaN() || bits.isZero())\n        return x;\n\n    bool isNeg = bits.getSign();\n    int exponent = bits.getExponent();\n\n    // If the exponent is greater than the most negative mantissa\n    // exponent, then x is already an integer.\n    if(exponent >= (int)PgeFloatProp::mantissaWidth)\n        return x;\n\n    if(exponent <= -1)\n    {\n        if(exponent <= -2 || bits.getMantissa() == 0)\n            return isNeg ? (-0.0) : (0.0); // abs(x) <= 0.5\n        else\n            return isNeg ? (-1.0) : (1.0); // abs(x) > 0.5\n    }\n\n    uint32_t trimSize = PgeFloatProp::mantissaWidth - exponent;\n    PgeFPBits newBits = bits;\n    newBits.setMantissa((bits.getMantissa() >> trimSize) << trimSize);\n    double truncValue = double(newBits);\n\n    if(truncValue == x)\n        return x;\n\n    uint64_t trimValue = bits.getMantissa() & ((uint64_t(1) << trimSize) - 1);\n    uint64_t halfValue = (uint64_t(1) << (trimSize - 1));\n    // If exponent is 0, trimSize will be equal to the mantissa width, and\n    // truncIsOdd` will not be correct. So, we handle it as a special case\n    // below.\n    uint64_t truncIsOdd = newBits.getMantissa() & (uint64_t(1) << trimSize);\n\n    if(trimValue > halfValue)\n        return isNeg ? truncValue - double(1.0) : truncValue + double(1.0);\n    else if(trimValue == halfValue)\n    {\n        if(exponent == 0)\n            return isNeg ? double(-2.0) : double(2.0);\n        if(truncIsOdd)\n            return isNeg ? truncValue - double(1.0) : truncValue + double(1.0);\n        else\n            return truncValue;\n    }\n    else\n        return truncValue;\n}\n\n#endif // #ifndef PGE_TO_NEAREST\n"
  },
  {
    "path": "lib/pge_video_rec/pge-video-rec.cmake",
    "content": "set(VIDEO_REC_SRCS)\nset(VIDEO_REC_INCS)\nset(VIDEO_REC_LIBS)\nset(VIDEO_REC_DEPS)\n\nif(NOT EMSCRIPTEN AND\n   NOT NINTENDO_3DS AND\n   NOT NINTENDO_WII AND\n   NOT NINTENDO_WIIU AND\n   NOT PGE_MIN_PORT AND\n   NOT THEXTECH_CLI_BUILD)\n    option(PGE_ENABLE_VIDEO_REC \"Enable the PGE video recording system\" ON)\nelse()\n    set(PGE_ENABLE_VIDEO_REC OFF)\nendif()\n\n\nif(PGE_ENABLE_VIDEO_REC)\n    option(PGE_VIDEO_REC_PREFER_GIF \"Prefer GIF even if WEBM is available\" ON)\n\n    list(APPEND VIDEO_REC_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/pge_video_rec.h\n        ${CMAKE_CURRENT_LIST_DIR}/pge_video_sink.cpp\n        ${CMAKE_CURRENT_LIST_DIR}/pge_record_gif.cpp\n    )\n\n    set(PGE_VIDEO_REC_WEBM_SUPPORTED OFF)\n\n    if(NOT PGE_VIDEO_REC_PREFER_GIF)\n        find_path(AVCODEC_INCLUDE_DIR NAMES libavcodec/avcodec.h PATH_SUFFIXES ffmpeg)\n        find_library(AVCODEC_LIBRARY avcodec)\n\n        find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h PATH_SUFFIXES ffmpeg)\n        find_library(AVFORMAT_LIBRARY avformat)\n\n        find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h PATH_SUFFIXES ffmpeg)\n        find_library(AVUTIL_LIBRARY avutil)\n\n        find_path(SWSCALE_INCLUDE_DIR libswscale/swscale.h PATH_SUFFIXES ffmpeg)\n        find_library(SWSCALE_LIBRARY swscale)\n\n        find_path(SWRESAMPLE_INCLUDE_DIR libswresample/swresample.h PATH_SUFFIXES ffmpeg)\n        find_library(SWRESAMPLE_LIBRARY swresample)\n\n        if(AVCODEC_INCLUDE_DIR AND AVCODEC_LIBRARY AND\n           AVFORMAT_INCLUDE_DIR AND AVFORMAT_LIBRARY AND\n           SWSCALE_INCLUDE_DIR AND SWSCALE_LIBRARY AND\n           SWRESAMPLE_INCLUDE_DIR AND SWRESAMPLE_LIBRARY)\n            set(PGE_VIDEO_REC_WEBM_SUPPORTED ON)\n            set(VIDEO_REC_INCS\n                ${AVCODEC_INCLUDE_DIR}\n                ${AVFORMAT_INCLUDE_DIR}\n                ${AVUTIL_INCLUDE_DIR}\n                ${SWSCALE_INCLUDE_DIR}\n                ${SWRESAMPLE_INCLUDE_DIR}\n            )\n            set(VIDEO_REC_LIBS\n                ${SWSCALE_LIBRARY}\n                ${SWRESAMPLE_LIBRARY}\n                ${AVFORMAT_LIBRARY}\n                ${AVCODEC_LIBRARY}\n                ${AVUTIL_LIBRARY}\n            )\n\n            if(VITA)\n                find_library(FFMPEG_LAME_LIBRARY REQUIRED mp3lame)\n                list(APPEND VIDEO_REC_LIBS ${FFMPEG_LAME_LIBRARY})\n            endif()\n        endif()\n    endif()\n\n    if(PGE_VIDEO_REC_WEBM_SUPPORTED)\n        message(\"== PGE video record will be statically built using system FFMPEG libraries\")\n        list(APPEND VIDEO_REC_SRCS\n            ${CMAKE_CURRENT_LIST_DIR}/pge_record_vp8.cpp\n        )\n    else()\n        message(\"== PGE video record will be built without WEBM support\")\n    endif()\nendif()\n"
  },
  {
    "path": "lib/pge_video_rec/pge_record_gif.cpp",
    "content": "/*\n *\n * Copyright (c) 2020-2024 ds-sloth and Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <SDL2/SDL_mutex.h>\n#include <Utils/files.h>\n#include <pge_delay.h>\n\n#include \"gif.h\"\n\n#include \"pge_video_rec.h\"\n\n\nstruct PGE_VideoRecording_GIF : public PGE_VideoRecording\n{\n    PGE_VideoRecording_GIF() : PGE_VideoRecording() {}\n    virtual ~PGE_VideoRecording_GIF() = default;\n\n    GIF_H::GifWriter  writer      = {nullptr, nullptr, true, false, {}};\n    unsigned char padding[7] = {0, 0, 0, 0, 0, 0, 0};\n\n    // returns the best file extension for the recording type\n    virtual const char* extension() const override;\n\n    // should be called by a main thread, returns false on failure\n    virtual bool initialize(const char* filename) override;\n\n    // should be called by an encoding thread, terminates once the empty end frame has been dequeued.\n    virtual bool encoding_thread() override;\n};\n\nconst char* PGE_VideoRecording_GIF::extension() const\n{\n    return \"gif\";\n}\n\nbool PGE_VideoRecording_GIF::initialize(const char* filename)\n{\n    SDL_RWops *gifFile = Files::open_file(filename, \"wb\");\n\n    if(!gifFile)\n        return false;\n\n    return GIF_H::GifBegin(&writer, gifFile, spec.frame_w, spec.frame_h, 100 / spec.frame_rate, false);\n}\n\nbool PGE_VideoRecording_GIF::encoding_thread()\n{\n    (void)(GIF_H::GifOverwriteLastDelay);// shut up a warning about unused function\n\n    int frame_i = 0;\n\n    while(true)\n    {\n        if(!has_frame())\n        {\n            if(exit_requested)\n                break;\n\n            PGE_Delay(1);\n            continue;\n        }\n\n        auto sh = dequeue_frame();\n\n        if(sh.end_frame)\n            break;\n\n        GifWriteFrame(&writer, sh.pixels.data(),\n                      unsigned(spec.frame_w),\n                      unsigned(spec.frame_h),\n                      ((frame_i + 1) * 100 / spec.frame_rate) - (frame_i * 100 / spec.frame_rate), 8, false);\n\n        frame_i++;\n    }\n\n    // Once GIF recorder was been disabled, finalize it\n    GIF_H::GifEnd(&writer);\n\n    return true;\n}\n\nstd::unique_ptr<PGE_VideoRecording> PGE_new_recording_GIF(const PGE_VideoSpec& spec)\n{\n    std::unique_ptr<PGE_VideoRecording> ret(new PGE_VideoRecording_GIF());\n    ret->spec = spec;\n    ret->spec.audio_enabled = false;\n    return ret;\n}\n"
  },
  {
    "path": "lib/pge_video_rec/pge_record_vp8.cpp",
    "content": "/*\n *\n * Copyright (c) 2020-2024 ds-sloth and Vitaly Novichkov <admin@wohlnet.ru>\n *\n * THIS program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * THIS program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with THIS program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n/*\n * This file incorporates substantial portions of libavformat's example mux.c\n *\n * Copyright (c) 2003 Fabrice Bellard\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\nextern \"C\"\n{\n#include <stdint.h>\n#include <libavutil/avassert.h>\n#include <libavutil/channel_layout.h>\n#include <libavutil/opt.h>\n#include <libavutil/mathematics.h>\n#include <libavutil/timestamp.h>\n#include <libavcodec/avcodec.h>\n#include <libavformat/avformat.h>\n#include <libswscale/swscale.h>\n#include <libswresample/swresample.h>\n}\n\n#include <SDL2/SDL_assert.h>\n#include <SDL2/SDL_mutex.h>\n#include <SDL2/SDL_audio.h>\n\n#include <Utils/files.h>\n#include <pge_delay.h>\n#include <Logger/logger.h>\n\n#include \"pge_video_rec.h\"\n\n#define HAS_CHANNELLAYOUT (LIBAVUTIL_VERSION_MAJOR >= 58)\n\n#undef av_err2str\n\nstatic char s_errbuf[(AV_ERROR_MAX_STRING_SIZE > 512) ? AV_ERROR_MAX_STRING_SIZE : 512];\n\ninline const char* av_err2str(int errnum)\n{\n    return av_make_error_string(s_errbuf, AV_ERROR_MAX_STRING_SIZE, errnum);\n}\n\nstatic std::string av_log_buffer;\nstatic SDL_mutex* av_log_mutex = nullptr;\n\nstatic void av_log_callback_pLog(void* avcl, int level, const char* fmt, va_list vl)\n{\n    if(av_log_mutex)\n        SDL_LockMutex(av_log_mutex);\n\n    // prepare the log buffer with the label\n    static const char log_label[] = {'P', 'G', 'E', 'V', 'i', 'd', 'e', 'o', 'R', 'e', 'c', ':', ' ', '(', 'f', 'f', 'm', 'p', 'e', 'g', ')', ' '};\n\n    if(av_log_buffer.size() < sizeof(log_label))\n    {\n        av_log_buffer.resize(sizeof(log_label));\n        memcpy(&av_log_buffer[0], log_label, sizeof(log_label));\n    }\n\n    // add AV Class name to buffer\n    AVClass* avc = avcl ? *(AVClass**)avcl : NULL;\n\n    if(avc)\n    {\n        s_errbuf[0] = '\\0';\n        snprintf(s_errbuf, sizeof(s_errbuf), \"[%s @ %p] \", avc->item_name(avcl), avcl);\n\n        av_log_buffer += s_errbuf;\n    }\n\n    // add message to buffer\n    s_errbuf[0] = '\\0';\n    vsnprintf(s_errbuf, sizeof(s_errbuf), fmt, vl);\n\n    av_log_buffer += s_errbuf;\n\n    // look for newlines\n    size_t cutpoint = sizeof(log_label);\n\n    for(size_t checkpoint = 0; checkpoint < av_log_buffer.size(); ++checkpoint)\n    {\n        if(av_log_buffer[checkpoint] != '\\n')\n            continue;\n\n        // print the line from cutpoint to checkpoint\n        av_log_buffer[checkpoint] = '\\0';\n\n        // add \"PGEVideoRec: \"\n        if(cutpoint >= sizeof(log_label))\n        {\n            memcpy(&av_log_buffer[cutpoint - sizeof(log_label)], log_label, sizeof(log_label));\n            cutpoint -= sizeof(log_label);\n        }\n\n        switch(level)\n        {\n        case(AV_LOG_PANIC):\n            pLogFatal(\"%s\", av_log_buffer.c_str() + cutpoint);\n            break;\n\n        case(AV_LOG_FATAL):\n            pLogCritical(\"%s\", av_log_buffer.c_str() + cutpoint);\n            break;\n\n        case(AV_LOG_ERROR):\n        case(AV_LOG_WARNING):\n            pLogWarning(\"%s\", av_log_buffer.c_str() + cutpoint);\n            break;\n\n        case(AV_LOG_INFO):\n        case(AV_LOG_VERBOSE):\n            pLogDebug(\"%s\", av_log_buffer.c_str() + cutpoint);\n            break;\n\n        case(AV_LOG_DEBUG):\n        default:\n            // do nothing\n            break;\n        }\n\n        cutpoint = checkpoint + 1;\n    }\n\n    av_log_buffer.erase(av_log_buffer.begin() + sizeof(log_label), av_log_buffer.begin() + cutpoint);\n\n    if(av_log_mutex)\n        SDL_UnlockMutex(av_log_mutex);\n}\n\n\n// a wrapper around a single output AVStream\nstruct OutputStream\n{\n    AVStream* st = nullptr;\n    AVCodecContext* enc = nullptr;\n\n    /* pts of the next frame that will be generated */\n    int64_t next_pts = 0;\n    int samples_count = 0;\n\n    AVFrame* frame = nullptr;\n    AVFrame* tmp_frame = nullptr;\n\n    AVPacket* tmp_pkt = nullptr;\n\n    struct SwsContext* sws_ctx = nullptr;\n    struct SwrContext* swr_ctx = nullptr;\n};\n\nstruct PGE_VideoRecording_VP8 : public PGE_VideoRecording\n{\n    OutputStream video_st;\n    OutputStream audio_st;\n    AVFormatContext* oc = nullptr;\n#if HAS_CHANNELLAYOUT\n    AVChannelLayout src_ch_layout;\n#else\n    int64_t src_ch_layout;\n#endif\n    AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_NONE;\n\n    PGE_VideoFrame current_frame;\n    PGE_VideoFrame next_frame;\n    PGE_AudioChunk current_chunk;\n    decltype(current_chunk.audio_buffer.begin()) current_chunk_offset = current_chunk.audio_buffer.end();\n\n    uint64_t first_timestamp = 0;\n\n    PGE_VideoRecording_VP8();\n    virtual ~PGE_VideoRecording_VP8();\n\n    void clean_up();\n\n    // returns the best file extension for the recording type\n    virtual const char* extension() const override;\n\n    // should be called by a main thread, returns false on failure\n    virtual bool initialize(const char* filename) override;\n\n    // should be called by an encoding thread, terminates once the empty end frame has been dequeued.\n    virtual bool encoding_thread() override;\n\n    // returns true if the sample format is valid\n    bool set_src_sample_fmt();\n};\n\nbool PGE_VideoRecording_VP8::set_src_sample_fmt()\n{\n    switch(spec.audio_sample_format)\n    {\n    case AUDIO_U8:\n        src_sample_fmt = AV_SAMPLE_FMT_U8;\n        break;\n\n    case AUDIO_S16SYS:\n        src_sample_fmt = AV_SAMPLE_FMT_S16;\n        break;\n\n    case AUDIO_S32SYS:\n        src_sample_fmt = AV_SAMPLE_FMT_S32;\n        break;\n\n    case AUDIO_F32SYS:\n        src_sample_fmt = AV_SAMPLE_FMT_FLT;\n        break;\n\n    default:\n        return false;\n    }\n\n#if HAS_CHANNELLAYOUT\n    av_channel_layout_default(&src_ch_layout, spec.audio_channel_count);\n#else\n    src_ch_layout = av_get_default_channel_layout(spec.audio_channel_count);\n#endif\n\n    return true;\n}\n\nstatic int write_frame(AVFormatContext* fmt_ctx, AVCodecContext* c,\n                       AVStream* st, AVFrame* frame, AVPacket* pkt)\n{\n    int ret;\n\n    // send the frame to the encoder\n    ret = avcodec_send_frame(c, frame);\n\n    if(ret < 0)\n    {\n        pLogWarning(\"PGEVideoRec: error sending a frame to the encoder: %s\",\n                    av_err2str(ret));\n        return 1;\n    }\n\n    while(ret >= 0)\n    {\n        ret = avcodec_receive_packet(c, pkt);\n\n        if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)\n            break;\n        else if(ret < 0)\n        {\n            pLogWarning(\"PGEVideoRec: error encoding a frame: %s\", av_err2str(ret));\n            return 1;\n        }\n\n        /* rescale output packet timestamp values from codec to stream timebase */\n        av_packet_rescale_ts(pkt, c->time_base, st->time_base);\n        pkt->stream_index = st->index;\n\n        /* Write the compressed frame to the media file. */\n        // log_packet(fmt_ctx, pkt);\n        ret = av_interleaved_write_frame(fmt_ctx, pkt);\n\n        /* pkt is now blank (av_interleaved_write_frame() takes ownership of\n         * its contents and resets pkt), so that no unreferencing is necessary.\n         * THIS would be different if one used av_write_frame(). */\n        if(ret < 0)\n        {\n            pLogWarning(\"PGEVideoRec: error while writing output packet: %s\", av_err2str(ret));\n            return 1;\n        }\n    }\n\n    return ret == AVERROR_EOF ? 1 : 0;\n}\n\n/* Add an output stream. */\nstatic bool init_stream(OutputStream* ost, AVFormatContext* oc,\n                        const AVCodec** codec, enum AVCodecID codec_id)\n{\n    /* find the encoder */\n    *codec = avcodec_find_encoder(codec_id);\n\n    if(!(*codec))\n    {\n        pLogWarning(\"PGEVideoRec: Could not find encoder for '%s'\", avcodec_get_name(codec_id));\n        return false;\n    }\n\n    ost->tmp_pkt = av_packet_alloc();\n\n    if(!ost->tmp_pkt)\n    {\n        pLogWarning(\"PGEVideoRec: Could not allocate AVPacket\");\n        return false;\n    }\n\n    ost->st = avformat_new_stream(oc, NULL);\n\n    if(!ost->st)\n    {\n        pLogWarning(\"PGEVideoRec: Could not allocate stream\");\n        return false;\n    }\n\n    ost->st->id = oc->nb_streams - 1;\n\n    ost->enc = avcodec_alloc_context3(*codec);\n\n    if(!ost->enc)\n    {\n        pLogWarning(\"PGEVideoRec: Could not alloc an encoding context\");\n        return false;\n    }\n\n    /* Some formats want stream headers to be separate. */\n    if(oc->oformat->flags & AVFMT_GLOBALHEADER)\n        ost->enc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;\n\n    // uncertain whether this is needed\n    // ost->enc->codec_id = codec_id;\n\n    return true;\n}\n\n/**************************************************************/\n/* audio output */\n\nstatic AVFrame* alloc_audio_frame(AVCodecContext* enc, int nb_samples)\n{\n    AVFrame* frame = av_frame_alloc();\n\n    if(!frame)\n    {\n        pLogWarning(\"PGEVideoRec: Error allocating an audio frame\");\n        return NULL;\n    }\n\n\n    frame->format = enc->sample_fmt;\n#if HAS_CHANNELLAYOUT\n    av_channel_layout_copy(&frame->ch_layout, &enc->ch_layout);\n#else\n    frame->channel_layout = enc->channel_layout;\n#endif\n    frame->sample_rate = enc->sample_rate;\n    frame->nb_samples = nb_samples;\n\n    if(nb_samples)\n    {\n        if(av_frame_get_buffer(frame, 0) < 0)\n        {\n            pLogWarning(\"PGEVideoRec: Error allocating an audio buffer\");\n            return NULL;\n        }\n    }\n\n    return frame;\n}\n\nstatic bool open_audio(const PGE_VideoRecording_VP8* THIS,\n                       const AVCodec* codec,\n                       OutputStream* ost, AVDictionary* opt_arg)\n{\n    int nb_samples;\n    int ret;\n    AVDictionary* opt = NULL;\n\n    // fill parameters\n    ost->enc->sample_fmt  = codec->sample_fmts ? codec->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;\n    ost->enc->bit_rate    = 64000;\n    ost->enc->sample_rate = THIS->spec.audio_sample_rate;\n\n    // restrict sample rate to supported list\n    if(codec->supported_samplerates)\n    {\n        bool sample_rate_supported = false;\n\n        for(int i = 0; codec->supported_samplerates[i]; i++)\n        {\n            if(codec->supported_samplerates[i] == THIS->spec.audio_sample_rate)\n            {\n                sample_rate_supported = true;\n                break;\n            }\n        }\n\n        if(!sample_rate_supported)\n            ost->enc->sample_rate = codec->supported_samplerates[0];\n    }\n\n#if HAS_CHANNELLAYOUT\n    const AVChannelLayout& layout = (THIS->spec.audio_channel_count == 1) ? AVChannelLayout AV_CHANNEL_LAYOUT_MONO : AVChannelLayout AV_CHANNEL_LAYOUT_STEREO;\n    av_channel_layout_copy(&ost->enc->ch_layout, &layout);\n#else\n    ost->enc->channel_layout = (THIS->spec.audio_channel_count == 1) ? av_get_default_channel_layout(1) : av_get_default_channel_layout(2);\n#endif\n\n    ost->st->time_base = AVRational { 1, ost->enc->sample_rate };\n\n    /* open it */\n    av_dict_copy(&opt, opt_arg, 0);\n    ret = avcodec_open2(ost->enc, codec, &opt);\n    av_dict_free(&opt);\n\n    if(ret < 0)\n    {\n        pLogWarning(\"PGEVideoRec: Could not open audio codec: %s\", av_err2str(ret));\n        return false;\n    }\n\n    /* init signal generator */\n\n    if(ost->enc->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)\n        nb_samples = 10000;\n    else\n        nb_samples = ost->enc->frame_size;\n\n    ost->frame     = alloc_audio_frame(ost->enc, nb_samples);\n\n    if(!ost->frame)\n        return false;\n\n    /* copy the stream parameters to the muxer */\n    ret = avcodec_parameters_from_context(ost->st->codecpar, ost->enc);\n\n    if(ret < 0)\n    {\n        pLogWarning(\"PGEVideoRec: Could not copy the stream parameters\");\n        return false;\n    }\n\n    /* create resampler context */\n    ost->swr_ctx = swr_alloc();\n\n    if(!ost->swr_ctx)\n    {\n        pLogWarning(\"PGEVideoRec: Could not allocate resampler context\");\n        return false;\n    }\n\n    /* set options */\n#if HAS_CHANNELLAYOUT\n    av_opt_set_chlayout(ost->swr_ctx, \"in_chlayout\",       &THIS->src_ch_layout,          0);\n    av_opt_set_chlayout(ost->swr_ctx, \"out_chlayout\",      &ost->enc->ch_layout,          0);\n#else\n    av_opt_set_channel_layout(ost->swr_ctx, \"in_channel_layout\",  THIS->src_ch_layout,          0);\n    av_opt_set_channel_layout(ost->swr_ctx, \"out_channel_layout\", ost->enc->channel_layout,     0);\n#endif\n    av_opt_set_int(ost->swr_ctx, \"in_sample_rate\",     THIS->spec.audio_sample_rate, 0);\n    av_opt_set_sample_fmt(ost->swr_ctx, \"in_sample_fmt\",      THIS->src_sample_fmt,         0);\n    av_opt_set_int(ost->swr_ctx, \"out_sample_rate\",    ost->enc->sample_rate,        0);\n    av_opt_set_sample_fmt(ost->swr_ctx, \"out_sample_fmt\",     ost->enc->sample_fmt,         0);\n\n    /* initialize the resampling context */\n    if((ret = swr_init(ost->swr_ctx)) < 0)\n    {\n        pLogWarning(\"PGEVideoRec: Failed to initialize the resampling context\");\n        return false;\n    }\n\n    return true;\n}\n\n/* Prepare a 16 bit dummy audio frame of 'frame_size' samples and\n * 'nb_channels' channels. */\nstatic AVFrame* get_audio_frame(PGE_VideoRecording_VP8* THIS, OutputStream* ost)\n{\n    AVFrame* frame = ost->frame;\n\n    /* when we pass a frame to the encoder, it may keep a reference to it\n     * internally;\n     * make sure we do not overwrite it here\n     */\n    int ret = av_frame_make_writable(ost->frame);\n\n    if(ret < 0)\n    {\n        pLogWarning(\"PGEVideoRec: av_frame_make_writable failed\");\n        return NULL;\n    }\n\n    int samples_left = frame->nb_samples;\n\n    int dest_active = 0;\n\n    uint8_t* dest[8] = {0, 0, 0, 0, 0, 0, 0, 0};\n\n    for(int i = 0; i < 8 && i < AV_NUM_DATA_POINTERS; i++)\n    {\n        dest[i] = frame->data[i];\n\n        if(dest[i])\n            dest_active++;\n    }\n\n#if HAS_CHANNELLAYOUT\n    int num_channels = frame->ch_layout.nb_channels;\n#else\n    int num_channels = av_get_channel_layout_nb_channels(frame->channel_layout);\n#endif\n\n    if(dest_active != 1 && dest_active != num_channels)\n    {\n        pLogWarning(\"PGEVideoRec: invalid number of channels in dest buffer\");\n        return NULL;\n    }\n\n    ptrdiff_t dest_bytes_per_sample = av_get_bytes_per_sample((AVSampleFormat)frame->format);\n\n    if(dest_active == 1)\n        dest_bytes_per_sample *= num_channels;\n\n    ptrdiff_t src_bytes_per_sample = THIS->spec.audio_channel_count * av_get_bytes_per_sample(THIS->src_sample_fmt);\n    bool first_loop = true;\n\n    while(samples_left > 0)\n    {\n        const uint8_t* src[1] = {NULL};\n        int src_samples = 0;\n\n        // use remaining samples from buffer if possible\n        if(!first_loop || swr_get_out_samples(ost->swr_ctx, 0) < samples_left)\n        {\n            // poll from game audio if buffer is empty\n            while(!THIS->has_audio())\n            {\n                // no more samples\n                if(THIS->exit_requested)\n                    return NULL;\n\n                PGE_Delay(1);\n                continue;\n            }\n\n            THIS->current_chunk = THIS->dequeue_audio();\n            THIS->current_chunk_offset = THIS->current_chunk.audio_buffer.begin();\n\n            src[0] = {THIS->current_chunk.audio_buffer.data()};\n            src_samples = THIS->current_chunk.audio_buffer.size() / src_bytes_per_sample;\n        }\n\n        first_loop = false;\n\n        /* convert to destination format */\n        int samples_done = swr_convert(ost->swr_ctx,\n                                       dest, samples_left,\n                                       src, src_samples);\n\n        if(samples_done < 0)\n        {\n            pLogWarning(\"PGEVideoRec: error during resampling\");\n            return NULL;\n        }\n\n        for(int i = 0; i < 8; i++)\n        {\n            if(dest[i])\n                dest[i] += dest_bytes_per_sample * samples_done;\n        }\n\n        samples_left -= samples_done;\n    }\n\n    return frame;\n}\n\n/*\n * encode one audio frame and send it to the muxer\n * return 1 when encoding is finished, 0 otherwise\n */\nstatic int write_audio_frame(PGE_VideoRecording_VP8* THIS, AVFormatContext* oc, OutputStream* ost)\n{\n    AVCodecContext* c;\n    AVFrame* frame;\n\n    c = ost->enc;\n\n    frame = get_audio_frame(THIS, ost);\n\n    if(!frame)\n        return 1;\n\n    frame->pts = ost->next_pts;\n    ost->samples_count += frame->nb_samples;\n    ost->next_pts = av_rescale_q(ost->samples_count, AVRational {1, c->sample_rate}, c->time_base);\n\n    return write_frame(oc, c, ost->st, frame, ost->tmp_pkt);\n}\n\n/**************************************************************/\n/* video output */\n\nstatic AVFrame* alloc_frame(enum AVPixelFormat pix_fmt, int width, int height)\n{\n    AVFrame* frame;\n    int ret;\n\n    frame = av_frame_alloc();\n\n    if(!frame)\n        return NULL;\n\n    frame->format = pix_fmt;\n    frame->width  = width;\n    frame->height = height;\n\n    /* allocate the buffers for the frame data */\n    ret = av_frame_get_buffer(frame, 0);\n\n    if(ret < 0)\n    {\n        av_frame_free(&frame);\n        frame = NULL;\n    }\n\n    return frame;\n}\n\nstatic bool open_video(PGE_VideoRecording_VP8* THIS, const AVCodec* codec,\n                       OutputStream* ost, AVDictionary* opt_arg)\n{\n    // initialize parameters\n\n    /* Resolution must be a multiple of two. */\n    if(THIS->spec.downscale_video)\n    {\n        ost->enc->width    = THIS->spec.frame_w / 4 * 2;\n        ost->enc->height   = THIS->spec.frame_h / 4 * 2;\n    }\n    else\n    {\n        ost->enc->width    = THIS->spec.frame_w / 2 * 2;\n        ost->enc->height   = THIS->spec.frame_h / 2 * 2;\n    }\n\n    ost->enc->bit_rate = 8000000;\n    /* timebase: THIS is the fundamental unit of time (in seconds) in terms\n     * of which frame timestamps are represented. For fixed-fps content,\n     * timebase should be 1/framerate and timestamp increments should be\n     * identical to 1. */\n    ost->st->time_base = AVRational { 1, THIS->spec.frame_rate };\n    ost->enc->time_base       = ost->st->time_base;\n\n    // c->gop_size      = 12; /* emit one intra frame every twelve frames at most */\n    ost->enc->pix_fmt       = AV_PIX_FMT_YUV420P;\n\n    // initialize codec parameters\n    int ret;\n    AVCodecContext* c = ost->enc;\n    AVDictionary* opt = NULL;\n\n    av_dict_copy(&opt, opt_arg, 0);\n    av_dict_set(&opt, \"deadline\", \"good\", 0);\n    av_dict_set(&opt, \"cpu-used\", \"5\", 0);\n    av_dict_set_int(&opt, \"crf\", THIS->spec.video_quality, 0);\n\n    /* open the codec */\n    ret = avcodec_open2(c, codec, &opt);\n    av_dict_free(&opt);\n\n    if(ret < 0)\n    {\n        pLogWarning(\"PGEVideoRec: could not open video codec: %s\", av_err2str(ret));\n        return false;\n    }\n\n    /* allocate and init a re-usable frame */\n    ost->frame = alloc_frame(c->pix_fmt, c->width, c->height);\n\n    if(!ost->frame)\n    {\n        pLogWarning(\"PGEVideoRec: could not allocate output video frame\");\n        return false;\n    }\n\n    /* If the output format is not RGB24, then a temporary RGB24\n     * picture is needed too. It is then converted to the required\n     * output format. */\n    ost->tmp_frame = NULL;\n\n    if(c->pix_fmt != AV_PIX_FMT_RGB24)\n    {\n        ost->tmp_frame = alloc_frame(AV_PIX_FMT_RGB24, c->width, c->height);\n\n        if(!ost->tmp_frame)\n        {\n            pLogWarning(\"PGEVideoRec: could not allocate output video frame\");\n            return false;\n        }\n    }\n\n    /* copy the stream parameters to the muxer */\n    ret = avcodec_parameters_from_context(ost->st->codecpar, c);\n\n    if(ret < 0)\n    {\n        pLogWarning(\"PGEVideoRec: could not copy stream parameters\");\n        return false;\n    }\n\n    return true;\n}\n\n/* Prepare a dummy image. */\nstatic void fill_rgb_image(AVFrame* pict, uint8_t* src,\n                           int width, int height, int src_stride)\n{\n    int x, y;\n\n    // i = frame_index;\n\n    /* Y */\n    for(y = 0; y < height; y++)\n    {\n        for(x = 0; x < width; x++)\n        {\n            pict->data[0][y * pict->linesize[0] + x * 3 + 0] = src[y * src_stride + (x) * 4 + 0];\n            pict->data[0][y * pict->linesize[0] + x * 3 + 1] = src[y * src_stride + (x) * 4 + 1];\n            pict->data[0][y * pict->linesize[0] + x * 3 + 2] = src[y * src_stride + (x) * 4 + 2];\n        }\n    }\n}\n\n/* Prepare a dummy image. */\nstatic void fill_rgb_image_downscale(AVFrame* pict, uint8_t* src,\n                                     int width, int height, int src_stride)\n{\n    int x, y;\n\n    // i = frame_index;\n    int stride = 2 * src_stride;\n\n    /* Y */\n    for(y = 0; y < height; y++)\n    {\n        for(x = 0; x < width; x++)\n        {\n            pict->data[0][y * pict->linesize[0] + x * 3 + 0] = src[y * stride + x * 8 + 0];\n            pict->data[0][y * pict->linesize[0] + x * 3 + 1] = src[y * stride + x * 8 + 1];\n            pict->data[0][y * pict->linesize[0] + x * 3 + 2] = src[y * stride + x * 8 + 2];\n        }\n    }\n}\n\nstatic AVFrame* get_video_frame(PGE_VideoRecording_VP8* THIS, OutputStream* ost)\n{\n    AVCodecContext* c = ost->enc;\n\n    /* find a next frame */\n    while(THIS->first_timestamp == 0 || av_compare_ts(ost->next_pts, ost->enc->time_base,\n            THIS->next_frame.timestamp - THIS->first_timestamp, AVRational { 1, 1000000 }) >= 0)\n    {\n        if(THIS->next_frame.end_frame)\n            return NULL;\n\n        THIS->current_frame = std::move(THIS->next_frame);\n\n        while(!THIS->has_frame())\n        {\n            // no more samples\n            if(THIS->exit_requested)\n                return NULL;\n\n            PGE_Delay(1);\n            continue;\n        }\n\n        THIS->next_frame = THIS->dequeue_frame();\n\n        if(THIS->first_timestamp == 0)\n            THIS->first_timestamp = THIS->next_frame.timestamp;\n    }\n\n    /* check if we want to generate more frames */\n    // if (ost->next_pts > ost->input->frame_count)\n    //     return NULL;\n\n    /* when we pass a frame to the encoder, it may keep a reference to it\n     * internally; make sure we do not overwrite it here */\n    if(av_frame_make_writable(ost->frame) < 0)\n        return NULL;\n\n    if(c->pix_fmt != AV_PIX_FMT_RGB24)\n    {\n        /* as we only generate a RGB24 picture, we must convert it\n         * to the codec pixel format if needed */\n        if(!ost->sws_ctx)\n        {\n            ost->sws_ctx = sws_getContext(c->width, c->height,\n                                          AV_PIX_FMT_RGB24,\n                                          c->width, c->height,\n                                          c->pix_fmt,\n                                          SWS_BICUBIC, NULL, NULL, NULL);\n\n            if(!ost->sws_ctx)\n            {\n                pLogWarning(\"Could not initialize the conversion context\");\n                return NULL;\n            }\n        }\n\n        (THIS->spec.downscale_video ? fill_rgb_image_downscale : fill_rgb_image)(ost->tmp_frame, THIS->current_frame.pixels.data(), c->width, c->height, THIS->spec.frame_pitch);\n        sws_scale(ost->sws_ctx, (const uint8_t* const*) ost->tmp_frame->data,\n                  ost->tmp_frame->linesize, 0, c->height, ost->frame->data,\n                  ost->frame->linesize);\n    }\n    else\n    {\n        (THIS->spec.downscale_video ? fill_rgb_image_downscale : fill_rgb_image)(ost->frame, THIS->current_frame.pixels.data(), c->width, c->height, THIS->spec.frame_pitch);\n    }\n\n    ost->frame->pts = ost->next_pts;\n\n    while(av_compare_ts(ost->next_pts, ost->enc->time_base,\n                        THIS->next_frame.timestamp - THIS->first_timestamp, AVRational{ 1, 1000000 }) < 0)\n    {\n        ost->next_pts++;\n    }\n\n    return ost->frame;\n}\n\n/*\n * encode one video frame and send it to the muxer\n * return 1 when encoding is finished, 0 otherwise\n */\nstatic int write_video_frame(PGE_VideoRecording_VP8* THIS, AVFormatContext* oc, OutputStream* ost)\n{\n    auto video_frame = get_video_frame(THIS, ost);\n\n    if(!video_frame)\n        return 1;\n\n    return write_frame(oc, ost->enc, ost->st, video_frame, ost->tmp_pkt);\n}\n\nstatic void close_stream(OutputStream* ost)\n{\n    avcodec_free_context(&ost->enc);\n    av_frame_free(&ost->frame);\n    av_frame_free(&ost->tmp_frame);\n    av_packet_free(&ost->tmp_pkt);\n    sws_freeContext(ost->sws_ctx);\n    ost->sws_ctx = NULL;\n    swr_free(&ost->swr_ctx);\n    ost->st = nullptr;\n}\n\n\nconst char* PGE_VideoRecording_VP8::extension() const\n{\n    return \"webm\";\n}\n\nbool PGE_VideoRecording_VP8::initialize(const char* filename)\n{\n    pLogInfo(\"PGEVideoRec: starting VP8/opus recording to [%s]\", filename);\n\n    const AVCodec* audio_codec, *video_codec;\n    int ret;\n    AVDictionary* opt = NULL;\n\n    /* allocate the output media context */\n    avformat_alloc_output_context2(&oc, NULL, \"webm\", filename);\n\n    if(!oc)\n        return false;\n\n    const AVOutputFormat* fmt = oc->oformat;\n\n    /* Add the audio and video streams using the default format codecs\n     * and initialize the codecs. */\n    if(fmt->video_codec == AV_CODEC_ID_NONE || !init_stream(&video_st, oc, &video_codec, AV_CODEC_ID_VP8))\n    {\n        clean_up();\n        return false;\n    }\n\n    if(spec.audio_enabled && fmt->audio_codec != AV_CODEC_ID_NONE && set_src_sample_fmt())\n    {\n        if(!init_stream(&audio_st, oc, &audio_codec, fmt->audio_codec))\n        {\n            spec.audio_enabled = false;\n            close_stream(&audio_st);\n        }\n    }\n    else\n        spec.audio_enabled = false;\n\n    /* Now that all the parameters are set, we can open the audio and\n     * video codecs and allocate the necessary encode buffers. */\n    if(video_st.st)\n    {\n        if(!open_video(this, video_codec, &video_st, opt))\n        {\n            clean_up();\n            return false;\n        }\n    }\n\n    if(spec.audio_enabled && audio_st.st)\n    {\n        if(!open_audio(this, audio_codec, &audio_st, opt))\n            spec.audio_enabled = false;\n    }\n\n    av_dump_format(oc, 0, filename, 1);\n\n    /* open the output file, if needed */\n    if(!(fmt->flags & AVFMT_NOFILE))\n    {\n        ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);\n        if(ret < 0)\n        {\n            pLogWarning(\"PGEVideoRec: could not open destination [%s]: %s\", filename, av_err2str(ret));\n            clean_up();\n            return false;\n        }\n    }\n\n    /* Write the stream header, if any. */\n    ret = avformat_write_header(oc, &opt);\n\n    if(ret < 0)\n    {\n        pLogWarning(\"PGEVideoRec: could not write headers: %s\", av_err2str(ret));\n        clean_up();\n        return false;\n    }\n\n    return true;\n}\n\nbool PGE_VideoRecording_VP8::encoding_thread()\n{\n    bool have_video = video_st.st;\n    bool have_audio = audio_st.st;\n\n    bool encode_video = have_video;\n    bool encode_audio = have_audio;\n\n    while(encode_video || encode_audio)\n    {\n        /* select the stream to encode */\n        if(encode_video && (!encode_audio || av_compare_ts(video_st.next_pts, video_st.enc->time_base, audio_st.next_pts, audio_st.enc->time_base) <= 0))\n            encode_video = !write_video_frame(this, oc, &video_st);\n        else\n            encode_audio = !write_audio_frame(this, oc, &audio_st);\n    }\n\n    av_write_trailer(oc);\n\n    clean_up();\n\n    return true;\n}\n\nPGE_VideoRecording_VP8::PGE_VideoRecording_VP8() :\n    PGE_VideoRecording()\n{\n    SDL_assert_release(!av_log_mutex); // only one PGE_VideoRecording_VP8 instance may exist at once\n    av_log_mutex = SDL_CreateMutex();\n}\n\nPGE_VideoRecording_VP8::~PGE_VideoRecording_VP8()\n{\n    clean_up();\n\n    SDL_DestroyMutex(av_log_mutex);\n    av_log_mutex = nullptr;\n}\n\nvoid PGE_VideoRecording_VP8::clean_up()\n{\n    if(video_st.st)\n        close_stream(&video_st);\n\n    if(audio_st.st)\n        close_stream(&audio_st);\n\n    if(oc)\n    {\n        if(!(oc->oformat->flags & AVFMT_NOFILE))\n            avio_closep(&oc->pb); /* Close the output file. */\n\n        avformat_free_context(oc);\n        oc = nullptr;\n    }\n\n#if HAS_CHANNELLAYOUT\n    av_channel_layout_uninit(&src_ch_layout);\n#endif\n}\n\nstd::unique_ptr<PGE_VideoRecording> PGE_new_recording_VP8(const PGE_VideoSpec& spec)\n{\n    av_log_set_callback(av_log_callback_pLog);\n    std::unique_ptr<PGE_VideoRecording> ret(new PGE_VideoRecording_VP8());\n    ret->spec = spec;\n    // ret->spec.audio_enabled = false;\n    return ret;\n}\n"
  },
  {
    "path": "lib/pge_video_rec/pge_video_rec.h",
    "content": "/*\n *\n * Copyright (c) 2024 ds-sloth and Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef PGE_VIDEO_REC_H\n#define PGE_VIDEO_REC_H\n\n#include <cstdint>\n#include <memory>\n#include <vector>\n#include <deque>\n\nstruct SDL_mutex;\nstruct SDL_Thread;\n\nstruct PGE_VideoFrame\n{\n    std::vector<uint8_t> pixels;\n    uint64_t timestamp = 0;\n    bool end_frame = false;\n\n    PGE_VideoFrame(const PGE_VideoFrame&) = delete;\n    PGE_VideoFrame& operator=(const PGE_VideoFrame&) = delete;\n\n    PGE_VideoFrame() = default;\n    PGE_VideoFrame(PGE_VideoFrame&&) = default;\n    PGE_VideoFrame& operator=(PGE_VideoFrame&&) = default;\n};\n\nstruct PGE_AudioChunk\n{\n    std::vector<uint8_t> audio_buffer;\n\n    PGE_AudioChunk(const PGE_AudioChunk&) = delete;\n    PGE_AudioChunk& operator=(const PGE_AudioChunk&) = delete;\n\n    PGE_AudioChunk() = default;\n    PGE_AudioChunk(PGE_AudioChunk&&) = default;\n    PGE_AudioChunk& operator=(PGE_AudioChunk&&) = default;\n};\n\nstruct PGE_VideoSpec\n{\n    //! width of frame in pixels\n    int frame_w = 0;\n\n    //! height of frame in pixels\n    int frame_h = 0;\n\n    //! pitch of frame in bytes\n    int frame_pitch = 0;\n\n    //! video framerate in FPS\n    int frame_rate = 0;\n\n    //! video quality for FFMPEG. lower is better\n    int video_quality = 10;\n\n    //! whether video should be scaled down by 2x\n    bool downscale_video = false;\n\n    //! whether the video should include audio\n    bool audio_enabled = false;\n\n    //! audio sample rate in Hz\n    int audio_sample_rate = 0;\n\n    //! audio channel count\n    int audio_channel_count = 0;\n\n    //! audio sample format (uses SDL_MixerX sample formats)\n    int audio_sample_format = 0;\n};\n\n\n/*!\n * \\brief Abstract class representing a video sink (implemented in pge_video_sink.cpp)\n */\nclass PGE_VideoSink\n{\nprivate:\n    std::deque<PGE_VideoFrame> queue_frame;\n    SDL_mutex* mutex_frame = nullptr;\n\n    std::deque<PGE_AudioChunk> queue_audio;\n    SDL_mutex* mutex_audio = nullptr;\n\npublic:\n    PGE_VideoSpec spec;\n\n    PGE_VideoSink();\n    virtual ~PGE_VideoSink();\n\n    bool has_frame();\n    int frame_backlog();\n    int enqueue_frame(PGE_VideoFrame&& frame, int max_backlog);\n    PGE_VideoFrame dequeue_frame();\n\n    bool has_audio();\n    void enqueue_audio(PGE_AudioChunk&& chunk);\n    PGE_AudioChunk dequeue_audio();\n};\n\n/*!\n * \\brief Abstract class representing a video recording\n */\nstruct PGE_VideoRecording : public PGE_VideoSink\n{\n    bool exit_requested = false;\n\n    // returns the best file extension for the recording type\n    virtual const char* extension() const = 0;\n\n    // should be called by a main thread, returns false on failure\n    virtual bool initialize(const char* filename) = 0;\n\n    // should be called by an encoding thread, terminates once the empty end frame has been dequeued.\n    virtual bool encoding_thread() = 0;\n};\n\nstd::unique_ptr<PGE_VideoRecording> PGE_new_recording_GIF(const PGE_VideoSpec& spec);\nstd::unique_ptr<PGE_VideoRecording> PGE_new_recording_VP8(const PGE_VideoSpec& spec);\n\n#endif // PGE_VIDEO_REC_H\n"
  },
  {
    "path": "lib/pge_video_rec/pge_video_sink.cpp",
    "content": "/*\n *\n * Copyright (c) 2020-2024 ds-sloth and Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"pge_video_rec.h\"\n#include <SDL2/SDL_mutex.h>\n\nPGE_VideoSink::PGE_VideoSink()\n{\n    mutex_frame = SDL_CreateMutex();\n    mutex_audio = SDL_CreateMutex();\n}\n\nPGE_VideoSink::~PGE_VideoSink()\n{\n    if(mutex_frame)\n        SDL_DestroyMutex(mutex_frame);\n\n    mutex_frame = nullptr;\n\n    if(mutex_audio)\n        SDL_DestroyMutex(mutex_audio);\n\n    mutex_audio = nullptr;\n}\n\nbool PGE_VideoSink::has_frame()\n{\n    SDL_LockMutex(mutex_frame);\n    bool ret = !queue_frame.empty();\n    SDL_UnlockMutex(mutex_frame);\n    return ret;\n}\n\nint PGE_VideoSink::frame_backlog()\n{\n    SDL_LockMutex(mutex_frame);\n    int ret = queue_frame.size();\n    SDL_UnlockMutex(mutex_frame);\n    return ret;\n}\n\nint PGE_VideoSink::enqueue_frame(PGE_VideoFrame&& frame, int max_backlog)\n{\n    SDL_LockMutex(mutex_frame);\n    int backlog_size = queue_frame.size();\n\n    if(max_backlog >= 0 && backlog_size < max_backlog)\n    {\n        queue_frame.push_back(std::move(frame));\n        backlog_size += 1;\n    }\n\n    SDL_UnlockMutex(mutex_frame);\n\n    return backlog_size;\n}\n\nPGE_VideoFrame PGE_VideoSink::dequeue_frame()\n{\n    SDL_LockMutex(mutex_frame);\n    PGE_VideoFrame ret = std::move(queue_frame.front());\n    queue_frame.pop_front();\n    SDL_UnlockMutex(mutex_frame);\n    return ret;\n}\n\nbool PGE_VideoSink::has_audio()\n{\n    SDL_LockMutex(mutex_audio);\n    bool ret = !queue_audio.empty();\n    SDL_UnlockMutex(mutex_audio);\n    return ret;\n}\n\nvoid PGE_VideoSink::enqueue_audio(PGE_AudioChunk&& chunk)\n{\n    SDL_LockMutex(mutex_audio);\n    queue_audio.push_back(std::move(chunk));\n    SDL_UnlockMutex(mutex_audio);\n}\n\nPGE_AudioChunk PGE_VideoSink::dequeue_audio()\n{\n    SDL_LockMutex(mutex_audio);\n    PGE_AudioChunk ret = std::move(queue_audio.front());\n    queue_audio.pop_front();\n    SDL_UnlockMutex(mutex_audio);\n    return ret;\n}\n"
  },
  {
    "path": "lib/sdl_proxy/16m/std_16m.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../sdl_timer.h\"\n\n#include <nds.h>\n\n#ifndef __CALICO__\n\nuint32_t curTime_ticks = 0u;\nbool inited = false;\n\nvoid s_updateTimer()\n{\n    if(!inited)\n    {\n        timerStart(1, ClockDivider_1024, 0, nullptr);\n        timerStart(2, (ClockDivider)TIMER_CASCADE, 0, nullptr);\n        inited = true;\n    }\n\n    curTime_ticks = TIMER_DATA(1) + (TIMER_DATA(2) << 16);\n}\n\nuint32_t SDL_GetTicks()\n{\n    s_updateTimer();\n\n    return (uint64_t)curTime_ticks * 1000 / (BUS_CLOCK>>10);\n}\n\nuint64_t SDL_GetMicroTicks()\n{\n    s_updateTimer();\n\n    return (uint64_t)curTime_ticks * 1000000 / (BUS_CLOCK>>10);\n}\n\nvoid SDL_Delay(int ms)\n{\n    uint64_t want = SDL_GetMicroTicks() + ms * 1000;\n\n    while(SDL_GetMicroTicks() < want)\n        swiDelay(0x126f); // ~120us\n}\n\n#endif\n\n#ifdef __CALICO__\n\nuint32_t SDL_GetTicks()\n{\n    return (uint64_t)tickGetCount() * 1000 / TICK_FREQ;\n}\n\nuint64_t SDL_GetMicroTicks()\n{\n    return (uint64_t)tickGetCount() * 1000000 / TICK_FREQ;\n}\n\nvoid SDL_Delay(int ms)\n{\n    threadSleep(ms * 1000);\n}\n\n#endif\n"
  },
  {
    "path": "lib/sdl_proxy/3ds/3ds-audio-lib.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <string.h>\n#include \"3ds-audio-lib.h\"\n#include <tremor/ivorbisfile.h>\n#include <3ds.h>\n#include <cstdlib>\n#include <gme/gme.h>\n\n#include <Logger/logger.h>\n\nvolatile SimpleChannel channels[NUM_CHANNELS];\nchar* audio_buffer = NULL;\n\nLightEvent sound_event;\nThread sound_thread;\nvolatile bool sound_quit = false;  // Quit flag\nvoid (*channel_done_callback)(int) = nullptr;\n\n\nvoid myAudioCallback(void* const /*nul_*/)\n{\n    if(sound_quit)\n        return;\n\n    LightEvent_Signal(&sound_event);\n}\n\ninline SoundId getSoundId(volatile SimpleChannel* channel)\n{\n    return channel->channel_id + channel->index * 32;\n}\n\ninline volatile SimpleChannel* validateSoundId(SoundId id)\n{\n    if(id == INVALID_ID) return nullptr;\n\n    int channel_id = id % 32;\n    SoundId index = id / 32;\n\n    if(channels[channel_id].index == index)\n    {\n        return &channels[channel_id];\n    }\n\n    return nullptr;\n}\n\n// should only be called by the audio thread if it is running\nvoid closeChannel(volatile SimpleChannel* channel)\n{\n    if(channel->format == FORMAT_PCM_FILE && channel->data_pointer != nullptr)\n    {\n        fclose((FILE*)channel->data_pointer);\n    }\n\n    if(channel->format == FORMAT_OGG_FILE && channel->data_pointer != nullptr)\n    {\n        ov_clear((OggVorbis_File*)channel->data_pointer);\n        free(channel->data_pointer);\n        // OGG does not free its pointer.\n    }\n\n    if(channel->format == FORMAT_GME_FILE && channel->data_pointer != nullptr)\n    {\n        gme_delete((Music_Emu*)channel->data_pointer);\n        // GME frees its pointer.\n    }\n\n    // DO NOT free the data pointer if it is a PCM_MEM because it is shared.\n    channel->data_pointer = nullptr;\n    channel->index ++;\n\n    if(channel_done_callback)\n        channel_done_callback(channel->channel_id);\n}\n\n// returns true on success, false on failure\nbool fillBuffer(volatile SimpleChannel* channel, volatile ndspWaveBuf* wavebuf)\n{\n    if(wavebuf->status != NDSP_WBUF_DONE) return false;\n\n    // load data into wavebuf...\n    if(channel->format == FORMAT_PCM_FILE && channel->data_pointer != nullptr)\n    {\n        uint32_t* buffer_loc = (uint32_t*)wavebuf->data_pcm16;  // in 2x16-bit samples\n        wavebuf->nsamples = 0;\n        size_t samplesToRead = BUFFER_SIZE / sizeof(*buffer_loc);\n\n        while(samplesToRead > 0)\n        {\n            int samples = fread(buffer_loc, sizeof(*buffer_loc), samplesToRead, (FILE*)channel->data_pointer);\n\n            if(samples == 0)\n            {\n                if(channel->loops == 0)\n                {\n                    closeChannel(channel);\n                    break;\n                }\n                else\n                {\n                    if(channel->loops > 0)\n                        channel->loops -= 1;\n\n                    fseek((FILE*)channel->data_pointer, channel->data_idx, SEEK_SET);\n                }\n            }\n\n            wavebuf->nsamples += samples;\n            buffer_loc += samples;\n            samplesToRead -= samples;\n        }\n\n        if(channel->stereo)\n        {\n            ndspChnWaveBufAdd(channel->channel_id, (ndspWaveBuf*)wavebuf);\n            DSP_FlushDataCache(wavebuf->data_pcm16,\n                               wavebuf->nsamples * sizeof(*buffer_loc));\n        }\n        else\n        {\n            wavebuf->nsamples *= 2;\n            ndspChnWaveBufAdd(channel->channel_id, (ndspWaveBuf*)wavebuf);\n            DSP_FlushDataCache(wavebuf->data_pcm16,\n                               wavebuf->nsamples / 2 * sizeof(*buffer_loc));\n        }\n\n        return true;\n    }\n\n    if(channel->format == FORMAT_PCM_MEM && channel->data_pointer != nullptr)\n    {\n        size_t bytesToCopy = (channel->data_size - channel->data_idx);\n\n        if(bytesToCopy == 0)\n        {\n            if(channel->loops == 0)\n            {\n                closeChannel(channel);\n                return true;\n            }\n            else\n            {\n                if(channel->loops > 0)\n                    channel->loops -= 1;\n\n                channel->data_idx = 0;\n                bytesToCopy = channel->data_size;\n            }\n        }\n\n        if(bytesToCopy > BUFFER_SIZE) bytesToCopy = BUFFER_SIZE;\n\n        memcpy(wavebuf->data_pcm16, (char*)channel->data_pointer + channel->data_idx, bytesToCopy);\n        channel->data_idx += bytesToCopy;\n\n        if(channel->stereo)\n        {\n            wavebuf->nsamples = bytesToCopy / 4;\n            ndspChnWaveBufAdd(channel->channel_id, (ndspWaveBuf*)wavebuf);\n            DSP_FlushDataCache(wavebuf->data_pcm16, bytesToCopy);\n        }\n        else\n        {\n            wavebuf->nsamples = bytesToCopy / 2;\n            ndspChnWaveBufAdd(channel->channel_id, (ndspWaveBuf*)wavebuf);\n            DSP_FlushDataCache(wavebuf->data_pcm16, bytesToCopy);\n        }\n\n        return true;\n    }\n\n    if(channel->format == FORMAT_OGG_FILE && channel->data_pointer != nullptr)\n    {\n        char* buffer_loc = (char*)wavebuf->data_pcm16;  // in 2x16-bit samples\n        wavebuf->nsamples = 0;\n        size_t samplesToRead = BUFFER_SIZE;\n\n        while(samplesToRead > 0)\n        {\n            // Decode bufferSize samples from opusFile_ into buffer,\n            // storing the number of samples that were decoded (or error)\n            int samples = ov_read((OggVorbis_File*)channel->data_pointer, buffer_loc, samplesToRead, nullptr);\n\n            if(samples == 0)\n            {\n                if(channel->loops == 0)\n                {\n                    closeChannel(channel);\n                    break;\n                }\n                else\n                {\n                    if(channel->loops > 0)\n                        channel->loops -= 1;\n\n                    ov_pcm_seek((OggVorbis_File*)channel->data_pointer, 0);\n                }\n            }\n\n            wavebuf->nsamples += samples;\n            buffer_loc += samples;\n            samplesToRead -= samples;\n        }\n\n        // depends strongly on stereo-ness......\n        if(channel->stereo)\n        {\n            wavebuf->nsamples /= 4;\n            ndspChnWaveBufAdd(channel->channel_id, (ndspWaveBuf*)wavebuf);\n            DSP_FlushDataCache(wavebuf->data_pcm16,\n                               wavebuf->nsamples * 4);\n        }\n        else\n        {\n            wavebuf->nsamples /= 2;\n            ndspChnWaveBufAdd(channel->channel_id, (ndspWaveBuf*)wavebuf);\n            DSP_FlushDataCache(wavebuf->data_pcm16,\n                               wavebuf->nsamples * 2);\n        }\n\n        return true;\n    }\n\n    if(channel->format == FORMAT_GME_FILE && channel->data_pointer != nullptr)\n    {\n        short* buffer_loc = (short*)wavebuf->data_pcm16;  // in 2x16-bit samples\n        size_t samplesToRead = BUFFER_SIZE / sizeof(short);\n\n        if(gme_track_ended((Music_Emu*)channel->data_pointer))\n        {\n            if(channel->loops == 0)\n            {\n                closeChannel(channel);\n                return false;\n            }\n            else\n            {\n                if(channel->loops > 0)\n                    channel->loops -= 1;\n\n                gme_start_track((Music_Emu*)channel->data_pointer, 0);\n            }\n        }\n\n        gme_play((Music_Emu*)channel->data_pointer, samplesToRead, buffer_loc);\n\n        wavebuf->nsamples = samplesToRead / 2;\n        ndspChnWaveBufAdd(channel->channel_id, (ndspWaveBuf*)wavebuf);\n        DSP_FlushDataCache(wavebuf->data_pcm16,\n                           wavebuf->nsamples * 4);\n        return true;\n    }\n\n    return false;\n}\n\nvoid audioThread(void* /*nul_*/)\n{\n    while(!sound_quit)\n    {\n        for(size_t ci = 0; ci < NUM_CHANNELS; ci++)\n        {\n            volatile SimpleChannel* channel = &channels[ci];\n\n            if(channel->kill && channel->format != FORMAT_LOADING)\n            {\n                closeChannel(channel);\n                ndspChnWaveBufClear(channel->channel_id);\n\n                channel->format = FORMAT_FREE;\n                channel->kill = false;\n            }\n            else if(channel->format != FORMAT_FREE && channel->format != FORMAT_LOADING)\n            {\n                for(size_t wi = 0; wi < NUM_BUFFERS; wi++)\n                {\n                    ndspWaveBuf* wavebuf = (ndspWaveBuf*) & (channel->wavebufs[wi]);\n\n                    if(wavebuf->status != NDSP_WBUF_DONE) continue;\n\n                    if(!fillBuffer(channel, wavebuf) && !ndspChnIsPlaying(channel->channel_id))\n                        channel->format = FORMAT_FREE;\n\n                    // printf(\"Responding at %llu...\\n\", osGetTime());\n                }\n            }\n        }\n\n        LightEvent_Wait(&sound_event);\n    }\n}\n\nint loadWaveFileData(FILE* f, uint16_t* nChannels, uint32_t* sampleRate, uint32_t* length, uint32_t* restart_pos)\n{\n    // all of this is little endian code\n    fseek(f, 22, SEEK_SET);\n    fread(nChannels, 2, 1, f);\n    fread(sampleRate, 4, 1, f);\n\n    uint32_t chunktype = 0;\n    uint32_t chunksize = 0;\n\n    int i = 0;\n\n    // find correct header\n    uint32_t read_pos = 36;\n\n    do\n    {\n        read_pos += chunksize;\n        fseek(f, read_pos, SEEK_SET);\n        fread(&chunktype, 4, 1, f);\n        fread(&chunksize, 4, 1, f);\n        i++;\n        read_pos += 8;\n    }\n    while(chunktype != 0x61746164 && i < 10);\n\n    if(i == 10)\n    {\n        *nChannels = 0;\n        chunksize = 0;\n        read_pos = 0;\n    }\n\n    if(length)\n        *length = chunksize;\n\n    if(restart_pos)\n        *restart_pos = read_pos;\n\n    return 0;\n}\n\nSoundId playSoundWAV(const char* path, int loops)\n{\n    // printf(\"Playin sound %s\\n\", path);\n    for(size_t ci = 0; ci < NUM_CHANNELS; ci++)\n    {\n        volatile SimpleChannel* channel = &channels[ci];\n\n        if(channel->format != FORMAT_FREE) continue;\n\n        FILE* f = fopen(path, \"rb\");\n\n        if(!f)\n            return INVALID_ID;\n\n        uint16_t nChannels;\n        uint32_t sampleRate;\n        uint32_t restart_pos;\n\n        loadWaveFileData(f, &nChannels, &sampleRate, nullptr, &restart_pos);\n\n        if(nChannels == 0 || nChannels > 2 || restart_pos < 44)\n        {\n            fclose(f);\n            return INVALID_ID;\n        }\n\n        ndspChnReset(ci);\n        ndspChnSetInterp(ci, NDSP_INTERP_POLYPHASE);\n        ndspChnSetRate(channel->channel_id, sampleRate);\n\n        channel->data_pointer = f;\n        channel->data_idx = restart_pos;\n\n        if(nChannels == 2)\n        {\n            ndspChnSetFormat(channel->channel_id, NDSP_FORMAT_STEREO_PCM16);\n            channel->stereo = true;\n        }\n        else\n        {\n            ndspChnSetFormat(channel->channel_id, NDSP_FORMAT_MONO_PCM16);\n            channel->stereo = false;\n        }\n\n        channel->format = FORMAT_PCM_FILE;\n        channel->loops = loops;\n        LightEvent_Signal(&sound_event);\n        return getSoundId(channel);\n    }\n\n    return INVALID_ID;\n}\n\nSoundId playSoundMem(const WaveObject* wave, int loops)\n{\n    for(size_t ci = 0; ci < NUM_CHANNELS; ci++)\n    {\n        volatile SimpleChannel* channel = &channels[ci];\n\n        if(channel->format != FORMAT_FREE) continue;\n\n        ndspChnReset(ci);\n        ndspChnSetInterp(ci, NDSP_INTERP_POLYPHASE);\n        channel->data_pointer = (void*) wave->data;\n        channel->data_idx = 0;\n        channel->data_size = wave->length;\n\n        if(wave->stereo)\n        {\n            ndspChnSetFormat(channel->channel_id, NDSP_FORMAT_STEREO_PCM16);\n            channel->stereo = true;\n        }\n        else\n        {\n            ndspChnSetFormat(channel->channel_id, NDSP_FORMAT_MONO_PCM16);\n            channel->stereo = false;\n        }\n\n        ndspChnSetRate(channel->channel_id, wave->sampleRate);\n        channel->format = FORMAT_PCM_MEM;\n        channel->loops = loops;\n        LightEvent_Signal(&sound_event);\n        return getSoundId(channel);\n    }\n\n    return INVALID_ID;\n}\n\nvoid loadOGG_Thread(void* passed_val)\n{\n    volatile SimpleChannel* channel = (volatile SimpleChannel*) passed_val;\n    FILE* f = (FILE*) channel->data_pointer;\n    channel->data_pointer = nullptr;\n\n    OggVorbis_File* vf = (OggVorbis_File*)malloc(sizeof(OggVorbis_File));\n\n    if(!vf)\n    {\n        fclose(f);\n        channel->format = FORMAT_FREE;\n        return;\n    }\n\n    if(ov_open(f, vf, nullptr, 0))\n    {\n        free(vf);\n        fclose(f);\n        channel->format = FORMAT_FREE;\n        return;\n    }\n\n    int ci = channel->channel_id;\n\n    ndspChnReset(ci);\n    ndspChnSetInterp(ci, NDSP_INTERP_POLYPHASE);\n\n    vorbis_info* vi = ov_info(vf, -1);\n\n    if(vi->channels == 2)\n    {\n        ndspChnSetFormat(channel->channel_id, NDSP_FORMAT_STEREO_PCM16);\n        channel->stereo = true;\n    }\n    else if(vi->channels == 1)\n    {\n        ndspChnSetFormat(channel->channel_id, NDSP_FORMAT_MONO_PCM16);\n        channel->stereo = false;\n    }\n    else\n    {\n        ov_clear(vf);\n        free(vf);\n        channel->format = FORMAT_FREE;\n        return;\n    }\n\n    ndspChnSetRate(channel->channel_id, vi->rate);\n    channel->data_pointer = (void*)vf;\n    channel->format = FORMAT_OGG_FILE;\n    LightEvent_Signal(&sound_event);\n}\n\nSoundId playSoundOGG(const char* path, int loops)\n{\n    // printf(\"Playin music %s\\n\", path);\n    for(size_t ci = 0; ci < NUM_CHANNELS; ci++)\n    {\n        volatile SimpleChannel* channel = &channels[ci];\n\n        if(channel->format != FORMAT_FREE) continue;\n\n        FILE* f = fopen(path, \"rb\");\n\n        if(!f) return INVALID_ID;\n\n        channel->loops = loops;\n        channel->format = FORMAT_LOADING;\n        channel->data_pointer = (void*)f;\n        SoundId uniquePlayId = getSoundId(channel);\n\n        // Set the thread priority to the main thread's priority ...\n        int32_t priority = 0x30;\n        svcGetThreadPriority(&priority, CUR_THREAD_HANDLE);\n        // ... then add 1, as higher number => lower actual priority ...\n        priority += 1;\n        // ... finally, clamp it between 0x18 and 0x3F to guarantee that it's valid.\n        priority = priority < 0x18 ? 0x18 : priority;\n        priority = priority > 0x3F ? 0x3F : priority;\n\n        Thread load_thread;\n        load_thread = threadCreate(loadOGG_Thread, (void*)channel,\n                                   THREAD_STACK_SZ, priority,\n                                   0, true);\n\n        if(load_thread)\n            return uniquePlayId;\n        else\n        {\n            fclose(f);\n            channel->data_pointer = nullptr;\n            channel->format = FORMAT_FREE;\n        }\n    }\n\n    return INVALID_ID;\n}\n\n// void loadChannelGME(volatile SimpleChannel* channel)\n// {\n\n// }\n\nSoundId playSoundGME(const char* path, int loops)\n{\n    // printf(\"Playin music GME %s\\n\", path);\n    for(size_t ci = 0; ci < NUM_CHANNELS; ci++)\n    {\n        volatile SimpleChannel* channel = &channels[ci];\n\n        if(channel->format != FORMAT_FREE) continue;\n\n        channel->format = FORMAT_LOADING;\n        channel->loops = loops;\n        gme_open_file(path, (Music_Emu**) & (channel->data_pointer), 24000);\n\n        if(!channel->data_pointer)\n        {\n            channel->format = FORMAT_FREE;\n            return INVALID_ID;\n        }\n\n        ndspChnReset(ci);\n        ndspChnSetInterp(ci, NDSP_INTERP_POLYPHASE);\n\n        ndspChnSetFormat(ci, NDSP_FORMAT_STEREO_PCM16);\n        ndspChnSetRate(channel->channel_id, 24000);\n\n        channel->format = FORMAT_GME_FILE;\n        SoundId uniquePlayId = getSoundId(channel);\n        return uniquePlayId;\n    }\n\n    return INVALID_ID;\n}\n\n// Thanks ThiefMaster!\n// https://stackoverflow.com/questions/5309471/getting-file-extension-in-c\nconst char* get_filename_ext(const char* filename)\n{\n    const char* dot = strrchr(filename, '.');\n\n    if(!dot || dot == filename) return \"\";\n\n    return dot + 1;\n}\n\nSoundId playSoundAuto(const char* path, int loops)\n{\n    const char* ext = get_filename_ext(path);\n\n    if(!strcasecmp(ext, \"ogg\"))\n        return playSoundOGG(path, loops);\n\n    if(!strcasecmp(ext, \"mp3\"))\n        return playSoundOGG(path, loops);\n\n    if(!strcasecmp(ext, \"wav\"))\n        return playSoundWAV(path, loops);\n    else\n        return playSoundGME(path, loops);\n}\n\nbool audioInit()\n{\n    LightEvent_Init(&sound_event, RESET_ONESHOT);\n    ndspInit();\n\n    ndspSetOutputMode(NDSP_OUTPUT_STEREO);\n\n    // Allocate audio buffer\n    const size_t bufferSize = BUFFER_SIZE * NUM_CHANNELS * NUM_BUFFERS;\n    audio_buffer = (char*)linearAlloc(bufferSize);\n\n    if(!audio_buffer)\n    {\n        pLogWarning(\"Failed to allocate audio buffer\");\n        return false;\n    }\n\n    // Reset callback function\n    channel_done_callback = nullptr;\n\n    // initialize channels and wavebufs\n    for(size_t ci = 0; ci < NUM_CHANNELS; ci++)\n    {\n        channels[ci].channel_id = ci;\n        channels[ci].format = FORMAT_FREE;\n        channels[ci].index = 1;\n        channels[ci].data_pointer = NULL;\n\n        ndspChnReset(ci);\n        ndspChnSetInterp(ci, NDSP_INTERP_POLYPHASE);\n\n        memset((void*) &channels[ci].wavebufs, 0, sizeof(channels[ci].wavebufs));\n\n        for(size_t wi = 0; wi < NUM_BUFFERS; wi++)\n        {\n            channels[ci].wavebufs[wi].data_vaddr = audio_buffer + BUFFER_SIZE * (ci * NUM_BUFFERS + wi);\n            channels[ci].wavebufs[wi].status = NDSP_WBUF_DONE;\n        }\n    }\n\n    // begin setting up threading structure...\n    ndspSetCallback(myAudioCallback, NULL);\n\n    // Thanks to @mkst from sm64 3ds port\n    int cpu = 0; // application core\n\n    if(R_SUCCEEDED(APT_SetAppCpuTimeLimit(30)))\n        cpu = 1; // system core\n\n    // Set the thread priority to the main thread's priority ...\n    int32_t priority = 0x30;\n    svcGetThreadPriority(&priority, CUR_THREAD_HANDLE);\n    // ... then subtract 1, as lower number => higher actual priority ...\n    priority -= 1;\n    // ... finally, clamp it between 0x18 and 0x3F to guarantee that it's valid.\n    priority = priority < 0x18 ? 0x18 : priority;\n    priority = priority > 0x3F ? 0x3F : priority;\n\n    sound_thread = threadCreate(audioThread, NULL,\n                                THREAD_STACK_SZ, priority,\n                                cpu, true);\n\n    if(sound_thread)\n        pLogDebug(\"Created audio thread at %p on %s core\", (void*)sound_thread, cpu ? \"os\" : \"application\");\n    else\n        pLogWarning(\"Failed to create audio thread\");\n\n    return (bool)sound_thread;\n}\n\n// Audio de-initialisation code\n// Stops playback and frees the primary audio buffer\nvoid audioExit()\n{\n    sound_quit = true;\n    LightEvent_Signal(&sound_event);\n\n    // Free the audio thread\n    threadJoin(sound_thread, UINT64_MAX);\n    threadFree(sound_thread);\n\n    // reset channel done callback\n    channel_done_callback = nullptr;\n\n    for(size_t ci = 0; ci < NUM_CHANNELS; ci++)\n    {\n        closeChannel(&channels[ci]);\n        ndspChnReset(channels[ci].channel_id);\n    }\n\n    linearFree(audio_buffer);\n\n    ndspExit();\n}\n\nvoid audioPause()\n{\n    for(size_t ci = 0; ci < NUM_CHANNELS; ci++)\n    {\n        ndspChnSetPaused(ci, true);\n    }\n}\n\nvoid audioResume()\n{\n    for(size_t ci = 0; ci < NUM_CHANNELS; ci++)\n    {\n        ndspChnSetPaused(ci, false);\n    }\n}\n\nbool audioPauseSingle(SoundId soundId)\n{\n    volatile SimpleChannel* channel = validateSoundId(soundId);\n\n    if(channel)\n        ndspChnSetPaused(channel->channel_id, true);\n\n    return channel;\n}\n\nbool audioResumeSingle(SoundId soundId)\n{\n    volatile SimpleChannel* channel = validateSoundId(soundId);\n\n    if(channel)\n        ndspChnSetPaused(channel->channel_id, false);\n\n    return channel;\n}\n\nbool audioSoundPlaying(SoundId soundId)\n{\n    return validateSoundId(soundId);\n}\n\nvoid killSound(SoundId soundId)\n{\n    volatile SimpleChannel* channel = validateSoundId(soundId);\n\n    if(channel) channel->kill = true;\n}\n\nWaveObject* audioLoadWave(const char* path)\n{\n    FILE* f = fopen(path, \"rb\");\n\n    if(!f) return nullptr;\n\n    WaveObject* wave = (WaveObject*) malloc(sizeof(WaveObject));\n    uint16_t nChannels;\n\n    loadWaveFileData(f, &nChannels, &wave->sampleRate, &wave->length, nullptr);\n\n    if(nChannels == 2)\n        wave->stereo = true;\n    else if(nChannels == 1)\n        wave->stereo = false;\n    else\n    {\n        free(wave);\n        fclose(f);\n        return nullptr;\n    }\n\n    if(wave->length > 256 * 1024)\n    {\n        free(wave);\n        fclose(f);\n        return nullptr;\n    }\n\n    char* temp_buf = (char*) malloc(wave->length);\n\n    if(!temp_buf)\n    {\n        free(wave);\n        fclose(f);\n        return nullptr;\n    }\n\n    wave->data = temp_buf;\n    uint32_t to_read = wave->length;\n\n    while(to_read)\n    {\n        unsigned int samples_read = fread(temp_buf, 1, to_read, f);\n\n        if(!samples_read) break;\n\n        to_read -= samples_read;\n        temp_buf += samples_read;\n    }\n\n    fclose(f);\n\n    if(!to_read)\n    {\n        return wave;\n    }\n    else\n    {\n        free((void*)wave->data);\n        free(wave);\n        return nullptr;\n    }\n}\n\nvoid audioFreeWave(WaveObject* wave)\n{\n    free((void*)wave->data);\n    free(wave);\n}\n\nvoid audioSetChannelDoneCallback(void (*cb)(int))\n{\n    channel_done_callback = cb;\n}\n"
  },
  {
    "path": "lib/sdl_proxy/3ds/3ds-audio-lib.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <3ds.h>\n\n#ifndef NUM_CHANNELS\n// must *never* exceed 32\n#define NUM_CHANNELS 16\n#define NUM_BUFFERS 4\n#define BUFFER_SIZE (128 * (44100 / 1000))\n\n#define THREAD_STACK_SZ 32 * 1024\n\n#endif\n\ntypedef uintptr_t SoundId;\n\ntypedef enum _CHANNEL_FORMAT\n{\n    FORMAT_FREE = 1 << 0,\n    FORMAT_LOADING = 1 << 1,\n    FORMAT_PCM_FILE = 1 << 2,\n    FORMAT_OGG_FILE = 1 << 3,\n    FORMAT_PCM_MEM = 1 << 4,\n    FORMAT_GME_FILE = 1 << 5,\n} CHANNEL_FORMAT;\n\ntypedef struct _SimpleChannel\n{\n    int channel_id;\n    CHANNEL_FORMAT format;\n    void* data_pointer;\n    size_t data_idx;\n    size_t data_size;\n    ndspWaveBuf wavebufs[NUM_BUFFERS];\n    bool kill;\n    bool stereo;\n    int loops;\n    SoundId index;\n} SimpleChannel;\n\ntypedef struct _WaveObject\n{\n    bool stereo;\n    uint32_t sampleRate;\n    const char* data;\n    uint32_t length;\n} WaveObject;\n\nstatic const SoundId INVALID_ID = 0;\n\nbool audioInit();\n\n// Audio de-initialisation code\n// Stops playback and frees the primary audio buffer\nvoid audioExit();\n\nvoid audioPause();\nvoid audioResume();\n\nbool audioPauseSingle(SoundId soundId);\nbool audioResumeSingle(SoundId soundId);\n\nbool audioSoundPlaying(SoundId soundId);\n\nconst char* get_filename_ext(const char* filename);\n\nSoundId playSoundWAV(const char* path, int loops = 0);\n\nSoundId playSoundMem(const WaveObject* wave, int loops = 0);\n\nSoundId playSoundOGG(const char* path, int loops = -1);\n\nSoundId playSoundGME(const char* path, int loops = -1);\n\nSoundId playSoundAuto(const char* path, int loops = -1);\n\nvoid killSound(SoundId soundId);\n\nWaveObject* audioLoadWave(const char* path);\n\nvoid audioFreeWave(WaveObject* wave);\n\nvoid audioSetChannelDoneCallback(void (*cb)(int));\n"
  },
  {
    "path": "lib/sdl_proxy/3ds/mixer_3ds.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef __3DS__\n#error This file should only be built on 3DS.\n#endif\n\n#include <string>\n#include <set>\n#include <unordered_map>\n\n#include <cstring>\n#include <cstdlib>\n\n#include \"../mixer.h\"\n#include \"3ds-audio-lib.h\"\n\nstatic SoundId* cur_sound = nullptr;\n\nstatic std::set<const std::string*> sound_stream_paths;\nstatic std::set<const std::string*> music_stream_paths;\nstatic std::unordered_map<const std::string*, SoundId> sound_id_music_map;\n\n\nstatic bool MixPlatform_NoPreload(const char* path)\n{\n    // if path is not WAV, for now\n    const char* ext = get_filename_ext(path);\n\n    if(strcasecmp(ext, \"wav\"))\n        return true;\n    else\n        return false;\n}\n\nint MixPlatform_PlayStream(int channel, const char* path, int loops)\n{\n    if(!cur_sound || channel < 0)\n    {\n        playSoundAuto(path, loops);\n    }\n    else\n    {\n        killSound(cur_sound[channel]);\n        cur_sound[channel] = playSoundAuto(path, loops);\n    }\n\n    return 0;\n}\n\n\nint Mix_Init(int flags)\n{\n    // These codecs only supported\n    return flags & (MIX_INIT_OGG);\n}\n\nvoid Mix_Quit()\n{\n    for(auto* it : sound_stream_paths)\n        delete it;\n\n    sound_stream_paths.clear();\n\n    for(auto* it : music_stream_paths)\n        delete it;\n\n    music_stream_paths.clear();\n\n    sound_id_music_map.clear();\n}\n\nint Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)\n{\n    (void)frequency;\n    (void)format;\n    (void)channels;\n    (void)chunksize;\n\n    return audioInit() ? 0 : -1;\n}\n\nint Mix_QuerySpecEx(SDL_AudioSpec* out_spec)\n{\n    memset(out_spec, 0, sizeof(SDL_AudioSpec));\n    out_spec->channels = 2;\n    out_spec->samples = 2048;\n    out_spec->freq = 32728;\n    out_spec->format = AUDIO_S16SYS;\n    return 0;\n}\n\nvoid Mix_CloseAudio()\n{\n    if(cur_sound)\n    {\n        free(cur_sound);\n        cur_sound = nullptr;\n    }\n\n    audioExit();\n}\n\nint Mix_VolumeMusic(int volume)\n{\n    (void)volume;\n    return volume;\n}\n\nint Mix_AllocateChannels(int numchans)\n{\n    (void)numchans;\n    return 0;\n}\n\nMix_Chunk* Mix_LoadWAV(const char* path)\n{\n    // remove arguments from path\n    char* path2 = strdup(path);\n\n    if(path2)\n    {\n        for(int i = 0; path2[i] != '\\0'; i++)\n        {\n            if(path2[i] == '|')\n                path2[i] = '\\0';\n        }\n    }\n\n    const char* path_use = path2 ? path2 : path;\n\n    Mix_Chunk* ret;\n\n    if(MixPlatform_NoPreload(path_use) || (ret = (Mix_Chunk*)audioLoadWave(path_use)) == nullptr)\n    {\n        const std::string* s = new(std::nothrow) const std::string(path_use);\n\n        if(!s)\n            return nullptr;\n\n        sound_stream_paths.insert(s);\n        ret = (Mix_Chunk*)s;\n    }\n\n    if(path2)\n        free(path2);\n\n    return ret;\n}\n\nconst char* Mix_GetError()\n{\n    return \"\";\n}\n\ninline SoundId playSoundMaybeStream(Mix_Chunk* chunk, int loops)\n{\n    auto it = sound_stream_paths.find((const std::string*)chunk);\n\n    if(it != sound_stream_paths.end())\n    {\n        return playSoundAuto(((const std::string*)chunk)->c_str(), loops);\n    }\n\n    return playSoundMem((WaveObject*)chunk, loops);\n}\n\nint Mix_PlayChannel(int channel, Mix_Chunk* chunk, int loops)\n{\n    if(!cur_sound || channel < 0)\n    {\n        playSoundMaybeStream(chunk, loops);\n        return 0;\n    }\n\n    killSound(cur_sound[channel]);\n    cur_sound[channel] = playSoundMaybeStream(chunk, loops);\n    return 0;\n}\n\nint Mix_PlayChannelVol(int channel, Mix_Chunk* chunk, int loops, int volume)\n{\n    (void)volume;\n    return Mix_PlayChannel(channel, chunk, loops);\n}\n\nint Mix_SetPanning(int channel, uint8_t left, uint8_t right)\n{\n    UNUSED(channel);\n    UNUSED(left);\n    UNUSED(right);\n    return -1;\n}\n\nint Mix_ReserveChannels(int channels)\n{\n    cur_sound = (SoundId*)malloc(sizeof(SoundId) * channels);\n\n    for(int i = 0; i < channels; i++)\n    {\n        cur_sound[i] = INVALID_ID;\n    }\n\n    return 0;\n}\n\nvoid Mix_PauseAudio(int pause)\n{\n    if(pause)\n        audioPause();\n    else\n        audioResume();\n}\n\nMix_Music* Mix_LoadMUS(const char* path)\n{\n    Mix_Music* ret;\n    // remove arguments from path\n    char* path2 = strdup(path);\n\n    if(path2)\n    {\n        for(int i = 0; path2[i] != '\\0'; i++)\n        {\n            if(path2[i] == '|')\n                path2[i] = '\\0';\n        }\n    }\n\n    const char* path_use = path2 ? path2 : path;\n\n    const std::string* s = new(std::nothrow) const std::string(path_use);\n\n    if(!s)\n        return nullptr;\n\n    music_stream_paths.insert(s);\n    ret = (Mix_Music*)s;\n\n    if(path2)\n        free(path2);\n\n    return ret;\n}\n\nint Mix_VolumeMusicStream(Mix_Music* music, int volume)\n{\n    (void)music;\n    (void)volume;\n    return 0;\n}\n\nint Mix_HaltMusicStream(Mix_Music* music)\n{\n    killSound((SoundId)music);\n    return 0;\n}\nint Mix_FadeOutMusicStream(Mix_Music* music, int ms)\n{\n    (void)ms;\n    killSound((SoundId)music);\n    return 0;\n}\nint Mix_HaltChannel(int channel)\n{\n    if(cur_sound && channel >= 0)\n        killSound(cur_sound[channel]);\n\n    return 0;\n}\n\nint Mix_PlayingMusicStream(Mix_Music* music)\n{\n    return audioSoundPlaying((SoundId)music);\n}\n\nint Mix_PausedMusicStream(Mix_Music* music)\n{\n    return audioSoundPlaying((SoundId)music);\n}\n\nint Mix_PauseMusicStream(Mix_Music* music)\n{\n    return (bool)audioPauseSingle((SoundId)music);\n}\n\nint Mix_ResumeMusicStream(Mix_Music* music)\n{\n    return (bool)audioResumeSingle((SoundId)music);\n}\n\nint Mix_PlayMusic(Mix_Music* music, int loops)\n{\n    const std::string* path_use = reinterpret_cast<const std::string*>(music);\n\n    SoundId ret = playSoundAuto(path_use->c_str(), loops);\n    sound_id_music_map.insert({path_use, ret});\n\n    return 0;\n}\n\nint Mix_PlayMusicStream(Mix_Music* music, int loops)\n{\n    return Mix_PlayMusic(music, loops);\n}\n\nint Mix_SetFreeOnStop(Mix_Music* music, int free_on_stop)\n{\n    const std::string* path_use = reinterpret_cast<const std::string*>(music);\n\n    if(free_on_stop)\n        sound_id_music_map.erase(path_use);\n\n    return 0;\n}\n\nint Mix_FadeInMusic(Mix_Music* music, int loops, int fadeInMs)\n{\n    (void)fadeInMs;\n    return Mix_PlayMusic(music, loops);\n}\n\nconst char* Mix_GetMusicTitle(Mix_Music* music)\n{\n    (void)music;\n    return \"\";\n}\n\nint Mix_GetMusicTracks(Mix_Music* music)\n{\n    (void)music;\n    return 1;\n}\n\nint Mix_SetMusicTrackMute(Mix_Music* music, int track, int mute)\n{\n    (void)music;\n    (void)track;\n    (void)mute;\n    return 0;\n}\n\nvoid Mix_FreeMusic(Mix_Music* music)\n{\n    const std::string* path_use = reinterpret_cast<const std::string*>(music);\n    auto f = sound_id_music_map.find(path_use);\n\n    if(f != sound_id_music_map.end())\n    {\n        killSound(f->second);\n        sound_id_music_map.erase(f);\n    }\n\n    auto it = music_stream_paths.find(path_use);\n\n    if(it != music_stream_paths.end())\n    {\n        music_stream_paths.erase(it);\n        delete path_use;\n    }\n}\n\nvoid Mix_FreeChunk(Mix_Chunk* chunk)\n{\n    auto it = sound_stream_paths.find(reinterpret_cast<const std::string*>(chunk));\n\n    if(it != sound_stream_paths.end())\n    {\n        sound_stream_paths.erase(it);\n        delete(const std::string*)chunk;\n        return;\n    }\n\n    audioFreeWave(reinterpret_cast<WaveObject*>(chunk));\n}\n\nvoid Mix_GME_SetSpcEchoDisabled(Mix_Music* music, int disable)\n{\n    (void)music;\n    (void)disable;\n}\n\nvoid Mix_RegisterEffect(int chan, Mix_EffectFunc_t f, Mix_EffectDone_t d, void* arg)\n{\n    (void)chan;\n    (void)f;\n    (void)d;\n    (void)arg;\n}\n\nvoid Mix_UnregisterEffect(int chan, Mix_EffectFunc_t f)\n{\n    (void)chan;\n    (void)f;\n}\n\nvoid Mix_ChannelFinished(void (*cb)(int))\n{\n    audioSetChannelDoneCallback(cb);\n}\n\nvoid Mix_ADLMIDI_setEmulator(int emu)\n{\n    (void)emu;\n}\n\nvoid Mix_ADLMIDI_setChipsCount(int chips)\n{\n    (void)chips;\n}\n\nvoid Mix_OPNMIDI_setEmulator(int emu)\n{\n    (void)emu;\n}\n\nvoid Mix_OPNMIDI_setChipsCount(int chips)\n{\n    (void)chips;\n}\n\n"
  },
  {
    "path": "lib/sdl_proxy/3ds/std_3ds.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <ctime>\n#include <cstdio>\n#include <3ds.h>\n\n#include \"../sdl_timer.h\"\n\nuint32_t __stacksize__ = 0x00020000;\n\nuint64_t originalTime = -1u;\nuint64_t originalMicroTime = -1u;\n\nosTimeRef_s tr;\n\nuint64_t SDL_GetMicroTicks()\n{\n    if(originalMicroTime == -1u)\n    {\n        originalMicroTime = svcGetSystemTick();\n        tr = osGetTimeRef();\n    }\n\n    return (uint64_t)(svcGetSystemTick() - originalTime) * 1000000 / tr.sysclock_hz;\n}\n"
  },
  {
    "path": "lib/sdl_proxy/_trash/mixer_mixerx.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include \"sound.h\"\n\n#include <SDL2/SDL.h>\n#include <SDL2/SDL_timer.h>\n#include <SDL2/SDL_mixer_ext.h>\n#include <string>\n#include <Logger/logger.h>\n#include <fmt_format_ne.h>\n\n#include \"core/msgbox.h\"\n\nstatic const int maxSfxChannels = 91;\n\nstatic const char *audio_format_to_string(SDL_AudioFormat f)\n{\n    switch(f)\n    {\n    default:\n        return \"<unknown>\";\n    case AUDIO_U8:\n        return \"U8\";\n    case AUDIO_S8:\n        return \"S8\";\n    case AUDIO_S16LSB:\n        return \"S16-LE\";\n    case AUDIO_S16MSB:\n        return \"S16-BE\";\n    case AUDIO_U16LSB:\n        return \"U16-LE\";\n    case AUDIO_U16MSB:\n        return \"U16-BE\";\n    case AUDIO_S32LSB:\n        return \"S32-LE\";\n    case AUDIO_S32MSB:\n        return \"S32-BE\";\n    case AUDIO_F32LSB:\n        return \"F32-LE\";\n    case AUDIO_F32MSB:\n        return \"F32-BE\";\n    }\n}\n\nbool MixPlatform_Init(AudioSetup_t& obtained)\n{\n    int ret;\n    const int initFlags = MIX_INIT_MID | MIX_INIT_MOD | MIX_INIT_FLAC | MIX_INIT_OGG | MIX_INIT_OPUS | MIX_INIT_MP3;\n\n    pLogDebug(\"Opening sound (wanted: rate=%d hz, format=%s, channels=%d, buffer=%d frames)...\",\n              g_audioSetup.sampleRate,\n              audio_format_to_string(g_audioSetup.format),\n              g_audioSetup.channels,\n              g_audioSetup.bufferSize);\n\n    ret = Mix_Init(initFlags);\n\n    if(ret != initFlags)\n    {\n        pLogWarning(\"MixerX: Some modules aren't properly initialized\");\n        if((initFlags & MIX_INIT_MID) != MIX_INIT_MID)\n            pLogWarning(\"MixerX: Failed to initialize MIDI module\");\n        if((initFlags & MIX_INIT_MOD) != MIX_INIT_MOD)\n            pLogWarning(\"MixerX: Failed to initialize Tracker music module\");\n        if((initFlags & MIX_INIT_FLAC) != MIX_INIT_FLAC)\n            pLogWarning(\"MixerX: Failed to initialize FLAC module\");\n        if((initFlags & MIX_INIT_OGG) != MIX_INIT_OGG)\n            pLogWarning(\"MixerX: Failed to initialize OGG Vorbis module\");\n        if((initFlags & MIX_INIT_OPUS) != MIX_INIT_OPUS)\n            pLogWarning(\"MixerX: Failed to initialize Opus module\");\n        if((initFlags & MIX_INIT_MP3) != MIX_INIT_MP3)\n            pLogWarning(\"MixerX: Failed to initialize MP3 module\");\n    }\n\n    ret = Mix_OpenAudio(g_audioSetup.sampleRate,\n                        g_audioSetup.format,\n                        g_audioSetup.channels,\n                        g_audioSetup.bufferSize);\n\n    if(ret < 0)\n    {\n        std::string msg = fmt::format_ne(\"Can't open audio stream, continuing without audio: ({0})\", Mix_GetError());\n        pLogCritical(msg.c_str());\n        XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_ERROR, \"Sound opening error\", msg.c_str());\n        return false;\n    }\n\n    SDL_AudioSpec ob;\n    ret = Mix_QuerySpecEx(&ob);\n\n    if(ret == 0)\n    {\n        pLogCritical(\"Failed to call the Mix_QuerySpec!\");\n        obtained = g_audioSetup;\n    }\n    else\n    {\n        obtained.sampleRate = ob.freq;\n        obtained.format = ob.format;\n        obtained.channels = ob.channels;\n        obtained.bufferSize = ob.samples;\n\n        pLogDebug(\"Sound opened (obtained: rate=%d hz, format=%s, channels=%d, buffer=%d frames)...\",\n                  obtained.sampleRate,\n                  audio_format_to_string(obtained.format),\n                  obtained.channels,\n                  obtained.bufferSize);\n    }\n\n#ifdef __3DS__\n    // Set fastest emulators to be default\n    Mix_OPNMIDI_setEmulator(OPNMIDI_OPN2_EMU_GENS);\n    Mix_OPNMIDI_setChipsCount(2);\n    Mix_ADLMIDI_setEmulator(ADLMIDI_OPL3_EMU_DOSBOX);\n    Mix_ADLMIDI_setChipsCount(2);\n#endif\n\n    Mix_VolumeMusic(MIX_MAX_VOLUME);\n    Mix_AllocateChannels(maxSfxChannels);\n\n    return true;\n}\n\nvoid MixPlatform_Quit()\n{\n    Mix_CloseAudio();\n    Mix_Quit();\n}\n\nint MixPlatform_PlayStream(int channel, const char *path, int loops)\n{\n    int ret;\n    (void)channel;\n\n    Mix_Music *mus = Mix_LoadMUS(path);\n\n    if(!mus)\n        return -1;\n\n    ret = Mix_PlayMusicStream(mus, loops);\n\n    if(ret < 0)\n        Mix_FreeMusic(mus);\n    else\n        Mix_SetFreeOnStop(mus, 1);\n\n    return ret;\n}\n"
  },
  {
    "path": "lib/sdl_proxy/_trash/sdl_assert.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_ASSERT_HHHHHH\n#define SDL_ASSERT_HHHHHH\n\n#if defined(PGE_MIN_PORT) && !defined(__WII__)\n#include \"core/null/sdl_null_assert.h\"\n#else\n#include \"core/sdl/sdl_sdl_assert.h\"\n#endif\n\n#endif // #ifndef SDL_ASSERT_HHHHHH\n"
  },
  {
    "path": "lib/sdl_proxy/_trash/sdl_atomic.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_ATOMIC_HHHHHH\n#define SDL_ATOMIC_HHHHHH\n\n#if defined(PGE_MIN_PORT) && !defined(__WII__)\n#include \"core/null/sdl_null_atomic.h\"\n#else\n#include \"core/sdl/sdl_sdl_atomic.h\"\n#endif\n\n#endif // #ifndef SDL_ATOMIC_HHHHHH\n"
  },
  {
    "path": "lib/sdl_proxy/_trash/sdl_head.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_HEAD_HHHHHH\n#define SDL_HEAD_HHHHHH\n\n#if defined(PGE_MIN_PORT) && !defined(__WII__)\n#include \"core/null/sdl_null_atomic.h\"\n#else\n#include <SDL2/SDL.h>\n#endif\n\n#endif // #ifndef SDL_ATOMIC_HHHHHH\n"
  },
  {
    "path": "lib/sdl_proxy/_trash/sdl_stdinc.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_STDINC_HHHHHH\n#define SDL_STDINC_HHHHHH\n\n#if defined(PGE_MIN_PORT) && !defined(__WII__)\n#include \"core/null/sdl_null_stdinc.h\"\n#else\n#include \"core/sdl/sdl_sdl_stdinc.h\"\n#endif\n\n#include <cstdint>\n\n#ifdef SDL_min\n#   undef SDL_min\n#endif\n\n#ifdef SDL_max\n#   undef SDL_max\n#endif\n\n#ifdef SDL_SwapLE32\n#   undef SDL_SwapLE32\n#endif\n\n#ifdef SDL_SwapBE32\n#   undef SDL_SwapBE32\n#endif\n\n#ifdef THEXTECH_BIG_ENDIAN\n// based on SDL formula\ninline uint32_t SDL_SwapLE32(uint32_t x)\n{\n    return static_cast<uint32_t>(((x << 24) | ((x << 8) & 0x00FF0000) |\n                                ((x >> 8) & 0x0000FF00) | (x >> 24)));\n}\ninline uint32_t SDL_SwapBE32(uint32_t x)\n{\n    return x;\n}\n#else\ninline uint32_t SDL_SwapLE32(uint32_t x)\n{\n    return x;\n}\ninline uint32_t SDL_SwapBE32(uint32_t x)\n{\n    return static_cast<uint32_t>(((x << 24) | ((x << 8) & 0x00FF0000) |\n                                ((x >> 8) & 0x0000FF00) | (x >> 24)));\n}\n#endif\n\n\ntemplate<class value_t>\ninline value_t SDL_min(value_t x, value_t y)\n{\n    return x < y ? x : y;\n}\n\ntemplate<class value_t>\ninline value_t SDL_max(value_t x, value_t y)\n{\n    return x > y ? x : y;\n}\n\nSDL_IMPORT(free)\n\nSDL_IMPORT(memset)\nSDL_IMPORT(memcpy)\nSDL_IMPORT(memcmp)\nSDL_IMPORT(strdup)\nSDL_IMPORT(strlen)\nSDL_IMPORT(strtol)\nSDL_IMPORT(atoi)\nSDL_IMPORT(atof)\nSDL_IMPORT(sscanf)\nSDL_IMPORT(strcasecmp)\nSDL_IMPORT(strncasecmp)\n\nSDL_IMPORT(strlcpy)\nSDL_IMPORT(strstr)\n\nSDL_IMPORT(pow)\n\nSDL_IMPORT_MATH(fabs)\nSDL_IMPORT_MATH(floor)\nSDL_IMPORT_MATH(ceil)\n\n#endif // #ifndef SDL_STDINC_HHHHHH\n"
  },
  {
    "path": "lib/sdl_proxy/_trash/sdl_timer.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_TIMER_HHHHHH\n#define SDL_TIMER_HHHHHH\n\n#if defined(PGE_MIN_PORT) && !defined(__WII__)\n#include \"core/null/sdl_null.h\"\n#else\n#include \"core/sdl/sdl_sdl_timer.h\"\n#endif\n\n#ifndef SDL_timer_h_\nextern uint32_t SDL_GetTicks();\n#endif\nextern uint64_t SDL_GetMicroTicks();\n\n#endif // #ifndef SDL_TIMER_HHHHHH\n"
  },
  {
    "path": "lib/sdl_proxy/base/std_base.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Utils/elapsed_timer.h>\n\n#include \"../sdl_timer.h\"\n\nstatic ElapsedTimer s_timer;\nstatic bool s_init = false;\n\nuint64_t SDL_GetMicroTicks()\n{\n    if(!s_init)\n    {\n        s_timer.start();\n        s_init = true;\n    }\n\n    return s_timer.nanoelapsed() / 1000;\n}\n"
  },
  {
    "path": "lib/sdl_proxy/mixer.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef MIXER_HHHHH\n#define MIXER_HHHHH\n\n#include <cstdint>\n\n// for AudioSetup_t\n#include \"sound.h\"\n#include \"sdl_audio.h\"\n\n#ifdef CUSTOM_AUDIO\n\nstruct Mix_Music;\nstruct Mix_Chunk;\nstruct SDL_RWops;\n\n#ifndef MIX_CHANNELS\n#define MIX_CHANNELS    8\n#endif\n\n/* Good default values for a PC soundcard */\n#define MIX_DEFAULT_FREQUENCY   44100\n#if SDL_BYTEORDER == SDL_LIL_ENDIAN\n#define MIX_DEFAULT_FORMAT      AUDIO_S16LSB\n#else\n#define MIX_DEFAULT_FORMAT      AUDIO_S16MSB\n#endif\n#define MIX_DEFAULT_CHANNELS    2\n#define MIX_MAX_VOLUME          SDL_MIX_MAXVOLUME /* Volume of a chunk */\n\nextern \"C\" {\n\n/**\n * Initialization flags\n */\ntypedef enum\n{\n    MIX_INIT_FLAC   = 0x00000001,\n    MIX_INIT_MOD    = 0x00000002,\n    MIX_INIT_MP3    = 0x00000008,\n    MIX_INIT_OGG    = 0x00000010,\n    MIX_INIT_MID    = 0x00000020,\n    MIX_INIT_OPUS   = 0x00000040\n} MIX_InitFlags;\n\n/* OPL3 chip emulators for ADLMIDI */\ntypedef enum {\n    ADLMIDI_OPL3_EMU_DEFAULT = -1,\n    ADLMIDI_OPL3_EMU_NUKED = 0,\n    ADLMIDI_OPL3_EMU_NUKED_1_7_4,\n    ADLMIDI_OPL3_EMU_DOSBOX,\n    ADLMIDI_OPL3_EMU_OPAL,\n    ADLMIDI_OPL3_EMU_JAVA\n} Mix_ADLMIDI_Emulator;\n\n/* OPN2 chip emulators for OPNMIDI */\ntypedef enum {\n    OPNMIDI_OPN2_EMU_DEFAULT = -1,\n    OPNMIDI_OPN2_EMU_MAME_OPN2 = 0,\n    OPNMIDI_OPN2_EMU_NUKED,\n    OPNMIDI_OPN2_EMU_GENS,\n    OPNMIDI_OPN2_EMU_GX, /* Caution: THIS emulator is inavailable by default */\n    OPNMIDI_OPN2_EMU_NP2,\n    OPNMIDI_OPN2_EMU_MAME_OPNA,\n    OPNMIDI_OPN2_EMU_PMDWIN,\n    /* Deprecated */\n    OPNMIDI_OPN2_EMU_MIME = 0 /*!!!TYPO!!!*/\n} Mix_OPNMIDI_Emulator;\n\nextern int Mix_Init(int flags);\n\nextern void Mix_Quit(void);\n\nextern int Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize);\nextern void Mix_CloseAudio(void);\n\nextern int Mix_VolumeMusic(int volume);\nextern int Mix_AllocateChannels(int numchans);\n\nextern int Mix_QuerySpecEx(SDL_AudioSpec *out_spec);\n\nextern const char* Mix_GetError();\n\nextern int Mix_ReserveChannels(int channels);\nextern void Mix_PauseAudio(int pause);\n\n\nextern Mix_Music* Mix_LoadMUS(const char* path);\nextern Mix_Chunk* Mix_LoadWAV(const char* path);\n\nextern Mix_Music* Mix_LoadMUS_RW(struct SDL_RWops*, int);\nextern Mix_Chunk* Mix_LoadWAV_RW(struct SDL_RWops*, int);\n\nextern Mix_Music* Mix_LoadMUS_RW_ARG(struct SDL_RWops*, int, const char*);\n\nextern double Mix_MusicDuration(Mix_Music* music);\n\nextern int Mix_VolumeMusicStream(Mix_Music* music, int volume);\nextern int Mix_HaltMusicStream(Mix_Music* music);\nextern int Mix_FadeOutMusicStream(Mix_Music* music, int ms);\nextern int Mix_HaltChannel(int channel);\n\nextern int Mix_PlayChannel(int channel, Mix_Chunk *chunk, int loops);\nextern int Mix_PlayChannelVol(int which, Mix_Chunk *chunk, int loops, int volume);\n\nextern int Mix_SetPanning(int which, uint8_t left, uint8_t right);\n\nextern int Mix_PlayingMusicStream(Mix_Music* music);\n\nextern int Mix_RewindMusicStream(Mix_Music* music);\nextern int Mix_SetMusicEffectPanning(Mix_Music* music, uint8_t left, uint8_t right);\n\nextern int Mix_PlayMusic(Mix_Music *music, int loops);\nextern int Mix_FadeInMusic(Mix_Music* music, int loops, int fadeInMs);\nextern int Mix_SetFreeOnStop(Mix_Music *music, int free_on_stop);\n\nextern int Mix_FadeOutMusic(int fadeInMs);\n\nextern int Mix_PlayMusicStream(Mix_Music *music, int loops);\n\nextern int Mix_PausedMusicStream(Mix_Music *music);\nextern int Mix_PauseMusicStream(Mix_Music *music);\nextern int Mix_ResumeMusicStream(Mix_Music *music);\n\nextern int Mix_GetMusicTracks(Mix_Music* music);\nextern const char* Mix_GetMusicTitle(Mix_Music* music);\n\nextern int Mix_SetMusicTrackMute(Mix_Music* music, int track, int mute);\n\nextern void Mix_FreeMusic(Mix_Music* music);\nextern void Mix_FreeChunk(Mix_Chunk* chunk);\n\nextern void Mix_ChannelFinished(void (*callback)(int));\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\n\n#define MIX_CHANNEL_POST   -2\n\ntypedef void( *  Mix_EffectFunc_t) (int chan, void *stream, int len, void *udata);\ntypedef void( *  Mix_EffectDone_t) (int chan, void *udata);\n\nextern void Mix_GME_SetSpcEchoDisabled(Mix_Music* music, int disable);\nextern void Mix_RegisterEffect(int chan, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg);\nextern void Mix_UnregisterEffect(int chan, Mix_EffectFunc_t f);\n\nextern void Mix_ADLMIDI_setEmulator(int emu);\nextern void Mix_ADLMIDI_setChipsCount(int chips);\n\nextern void Mix_OPNMIDI_setEmulator(int emu);\nextern void Mix_OPNMIDI_setChipsCount(int chips);\n\n#endif // #ifdef THEXTECH_ENABLE_AUDIO_FX\n\n} // extern \"C\"\n\n#else // #ifdef CUSTOM_AUDIO\n\n#include <SDL2/SDL_mixer_ext.h>\n\n#endif\n\n#endif // #ifndef MIXER_HHHHH\n"
  },
  {
    "path": "lib/sdl_proxy/null/mixer_null.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include <string>\n#include <set>\n\n#include <cstring>\n#include <cstdlib>\n#include <SDL2/SDL_rwops.h>\n\n#include \"../mixer.h\"\n#include \"globals.h\"\n\nint Mix_Init(int flags)\n{\n    UNUSED(flags);\n    return 0; // Always fail\n}\n\nvoid Mix_Quit()\n{}\n\nint Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)\n{\n    (void)frequency;\n    (void)format;\n    (void)channels;\n    (void)chunksize;\n\n    return -1; // Always fail\n}\n\nint Mix_QuerySpecEx(SDL_AudioSpec* out_spec)\n{\n    memset(out_spec, 0, sizeof(SDL_AudioSpec));\n    out_spec->channels = 2;\n    out_spec->samples = 2048;\n    out_spec->freq = 32728;\n    out_spec->format = AUDIO_S16SYS;\n    return 0;\n}\n\nvoid Mix_CloseAudio()\n{}\n\nint Mix_VolumeMusic(int volume)\n{\n    (void)volume;\n    return volume;\n}\n\nint Mix_AllocateChannels(int numchans)\n{\n    (void)numchans;\n    return 0;\n}\n\nMix_Chunk* Mix_LoadWAV(const char* path)\n{\n    UNUSED(path);\n    return nullptr; // Always fail\n}\n\ndouble Mix_MusicDuration(Mix_Music* music)\n{\n    UNUSED(music);\n    return 0;\n}\n\nconst char* Mix_GetError()\n{\n    return \"\";\n}\n\nint Mix_PlayChannel(int channel, Mix_Chunk* chunk, int loops)\n{\n    UNUSED(channel);\n    UNUSED(chunk);\n    UNUSED(loops);\n    return 0;\n}\n\n#ifndef Mix_PlayChannelVol\nint Mix_PlayChannelVol(int channel, Mix_Chunk* chunk, int loops, int volume)\n{\n    UNUSED(channel);\n    UNUSED(chunk);\n    UNUSED(loops);\n    UNUSED(volume);\n    return -1;\n}\n#else\nint Mix_PlayChannelTimedVolume(int channel, Mix_Chunk* chunk, int loops, int ticks, int volume)\n{\n    UNUSED(channel);\n    UNUSED(chunk);\n    UNUSED(loops);\n    UNUSED(ticks);\n    UNUSED(volume);\n    return -1;\n}\n#endif\n\nint Mix_SetPanning(int channel, uint8_t left, uint8_t right)\n{\n    UNUSED(channel);\n    UNUSED(left);\n    UNUSED(right);\n    return -1;\n}\n\nint Mix_ReserveChannels(int channels)\n{\n    UNUSED(channels);\n    return 0;\n}\n\nvoid Mix_PauseAudio(int pause)\n{\n    UNUSED(pause);\n}\n\nMix_Music* Mix_LoadMUS(const char* path)\n{\n    UNUSED(path);\n    return nullptr; // Always fail\n}\n\nMix_Music* Mix_LoadMUS_RW(struct SDL_RWops* rwops, int free_me)\n{\n    if(free_me)\n        SDL_RWclose(rwops);\n\n    return nullptr;\n}\n\nMix_Chunk* Mix_LoadWAV_RW(struct SDL_RWops* rwops, int free_me)\n{\n    if(free_me)\n        SDL_RWclose(rwops);\n\n    return nullptr;\n}\n\nMix_Music* Mix_LoadMUS_RW_ARG(struct SDL_RWops* rwops, int free_me, const char*)\n{\n    if(free_me)\n        SDL_RWclose(rwops);\n\n    return nullptr;\n}\n\nint Mix_VolumeMusicStream(Mix_Music* music, int volume)\n{\n    (void)music;\n    (void)volume;\n    return 0;\n}\n\nint Mix_HaltMusicStream(Mix_Music* music)\n{\n    UNUSED(music);\n    return 0;\n}\nint Mix_FadeOutMusicStream(Mix_Music* music, int ms)\n{\n    UNUSED(ms);\n    UNUSED(music);\n    return 0;\n}\nint Mix_HaltChannel(int channel)\n{\n    UNUSED(channel);\n    return 0;\n}\n\nint Mix_PlayingMusicStream(Mix_Music* music)\n{\n    UNUSED(music);\n    return -1;\n}\n\nint Mix_PausedMusicStream(Mix_Music* music)\n{\n    UNUSED(music);\n    return -1;\n}\n\nint Mix_RewindMusicStream(Mix_Music* music)\n{\n    UNUSED(music);\n    return -1;\n}\n\nint Mix_SetMusicEffectPanning(Mix_Music* music, uint8_t left, uint8_t right)\n{\n    UNUSED(music);\n    UNUSED(left);\n    UNUSED(right);\n    return -1;\n}\n\nint Mix_PauseMusicStream(Mix_Music* music)\n{\n    UNUSED(music);\n    return 0;\n}\n\nint Mix_ResumeMusicStream(Mix_Music* music)\n{\n    UNUSED(music);\n    return 0;\n}\n\nint Mix_PlayMusic(Mix_Music* music, int loops)\n{\n    UNUSED(music);\n    UNUSED(loops);\n    return 0;\n}\n\nint Mix_PlayMusicStream(Mix_Music* music, int loops)\n{\n    UNUSED(music);\n    UNUSED(loops);\n    return -1;\n}\n\nint Mix_SetFreeOnStop(Mix_Music* music, int free_on_stop)\n{\n    UNUSED(music);\n    UNUSED(free_on_stop);\n    return 0;\n}\n\nint Mix_FadeInMusic(Mix_Music* music, int loops, int fadeInMs)\n{\n    UNUSED(music);\n    UNUSED(loops);\n    UNUSED(fadeInMs);\n    return -1;\n}\n\nconst char* Mix_GetMusicTitle(Mix_Music* music)\n{\n    UNUSED(music);\n    return \"\";\n}\n\nint Mix_GetMusicTracks(Mix_Music* music)\n{\n    UNUSED(music);\n    return 1;\n}\n\nint Mix_SetMusicTrackMute(Mix_Music* music, int track, int mute)\n{\n    (void)music;\n    (void)track;\n    (void)mute;\n    return 0;\n}\n\nvoid Mix_FreeMusic(Mix_Music* music)\n{\n    UNUSED(music);\n}\n\nvoid Mix_FreeChunk(Mix_Chunk* chunk)\n{\n    UNUSED(chunk);\n}\n\nvoid Mix_GME_SetSpcEchoDisabled(Mix_Music* music, int disable)\n{\n    (void)music;\n    (void)disable;\n}\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\nvoid Mix_RegisterEffect(int chan, Mix_EffectFunc_t f, Mix_EffectDone_t d, void* arg)\n{\n    (void)chan;\n    (void)f;\n    (void)d;\n    (void)arg;\n}\n\nvoid Mix_UnregisterEffect(int chan, Mix_EffectFunc_t f)\n{\n    (void)chan;\n    (void)f;\n}\n#endif\n\nvoid Mix_ChannelFinished(void (*cb)(int))\n{\n    UNUSED(cb);\n}\n\nvoid Mix_ADLMIDI_setEmulator(int emu)\n{\n    (void)emu;\n}\n\nvoid Mix_ADLMIDI_setChipsCount(int chips)\n{\n    (void)chips;\n}\n\nvoid Mix_OPNMIDI_setEmulator(int emu)\n{\n    (void)emu;\n}\n\nvoid Mix_OPNMIDI_setChipsCount(int chips)\n{\n    (void)chips;\n}\n"
  },
  {
    "path": "lib/sdl_proxy/null/sdl_null.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef STD_NULL_H\n#define STD_NULL_H\n\n#include <cstdio>\n#include <cstdint>\n#include <cmath>\n#include <cstring>\n\n#ifndef UNUSED // To avoid IDE-side errors\n#define UNUSED(x) (void)x\n#endif\n\ntypedef int64_t Sint64;\ntypedef uint64_t Uint64;\ntypedef int32_t Sint32;\ntypedef uint32_t Uint32;\ntypedef int16_t Sint16;\ntypedef uint16_t Uint16;\ntypedef int8_t Sint8;\ntypedef uint8_t Uint8;\n\n#define SDL_IMPORT(func) static constexpr auto& SDL_ ## func = func;\n#define SDL_IMPORT_MATH(func) inline double SDL_ ## func(double arg) { return std::func(arg); }\n\n#define SDL_INLINE inline\n#define SDL_FORCE_INLINE static inline\n\ninline const char* SDL_strstr(const char* haystack, const char* needle)\n{\n    return strstr(haystack, needle);\n}\n\ninline char* SDL_strstr(char* haystack, const char* needle)\n{\n    return strstr(haystack, needle);\n}\n\ninline size_t SDL_strlcpy(char* dst, const char* src, size_t maxlen)\n{\n    size_t srclen = strlen(src);\n    if(maxlen > 0)\n    {\n        size_t len = srclen < maxlen - 1 ? srclen : maxlen - 1;\n        memcpy(dst, src, len);\n        dst[len] = '\\0';\n    }\n    return srclen;\n}\n\ninline double SDL_pow(double x, double y)\n{\n    return std::pow(x, y);\n}\n\n#endif // #ifndef STD_NULL_H\n"
  },
  {
    "path": "lib/sdl_proxy/null/std_null.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../sdl_timer.h\"\n\nuint32_t curTime = 0u;\n\nuint32_t SDL_GetTicks()\n{\n   return ++curTime;\n}\n\nuint64_t SDL_GetMicroTicks()\n{\n    return curTime * 1000;\n}\n\nvoid SDL_Delay(int ms)\n{\n   (void)ms;\n}\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_assert.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_SDL_ASSERT_H\n#define SDL_SDL_ASSERT_H\n\n#if defined(SDLRPOXY_NULL)\n\n#   include <assert.h>\n\n#   define SDL_assert(x)         assert(x)\n\n// Doesn't work as intended (trigger a fail on release builds), this is just a dummy\n#ifndef NDEBUG\n#   define SDL_assert_release(x) assert(x)\n#else\n#   define SDL_assert_release(x) (void)(x)\n#endif\n\n#else\n\n#include <SDL2/SDL_assert.h>\n\n#endif\n\n#endif // #ifndef SDL_SDL_ASSERT_H\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_atomic.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_SDL_ATOMIC_H\n#define SDL_SDL_ATOMIC_H\n\n#if defined(SDLRPOXY_NULL)\nusing SDL_atomic_t = volatile int;\n\ninline void SDL_AtomicSet(SDL_atomic_t* loc, int value)\n{\n    *loc = value;\n}\n\ninline int SDL_AtomicGet(const SDL_atomic_t* loc)\n{\n    return *loc;\n}\n\n#else\n#   include <SDL2/SDL_atomic.h>\n#endif\n\n#endif // #ifndef SDL_SDL_ATOMIC_H\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_audio.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_SDL_AUDIO_H\n#define SDL_SDL_AUDIO_H\n\n#ifndef SDLRPOXY_NULL\n#   include <SDL2/SDL_audio.h>\n#endif\n\n\n#ifdef SDLRPOXY_NULL\n\n#include \"sdl_types.h\"\n\n/* @{ */\n#define AUDIO_U8        0x0008\n#define AUDIO_S8        0x8008\n#define AUDIO_U16LSB    0x0010\n#define AUDIO_S16LSB    0x8010\n#define AUDIO_U16MSB    0x1010\n#define AUDIO_S16MSB    0x9010\n#define AUDIO_U16       AUDIO_U16LSB\n#define AUDIO_S16       AUDIO_S16LSB\n/* @} */\n\n/* @{ */\n#define AUDIO_S32LSB    0x8020\n#define AUDIO_S32MSB    0x9020\n#define AUDIO_S32       AUDIO_S32LSB\n/* @} */\n\n/* @{ */\n#define AUDIO_F32LSB    0x8120\n#define AUDIO_F32MSB    0x9120\n#define AUDIO_F32       AUDIO_F32LSB\n/* @} */\n\n/* @{ */\n#ifndef THEXTECH_BIG_ENDIAN\n#define AUDIO_U16SYS    AUDIO_U16LSB\n#define AUDIO_S16SYS    AUDIO_S16LSB\n#define AUDIO_S32SYS    AUDIO_S32LSB\n#define AUDIO_F32SYS    AUDIO_F32LSB\n#else\n#define AUDIO_U16SYS    AUDIO_U16MSB\n#define AUDIO_S16SYS    AUDIO_S16MSB\n#define AUDIO_S32SYS    AUDIO_S32MSB\n#define AUDIO_F32SYS    AUDIO_F32MSB\n#endif\n/* @} */\n\n#define SDL_AudioFormat Uint16\ntypedef void* SDL_AudioCallback;\n\n#define SDL_MIX_MAXVOLUME 1\n\n/**\n *  The calculated values in this structure are calculated by SDL_OpenAudio().\n *\n *  For multi-channel audio, the default SDL channel mapping is:\n *  2:  FL FR                       (stereo)\n *  3:  FL FR LFE                   (2.1 surround)\n *  4:  FL FR BL BR                 (quad)\n *  5:  FL FR FC BL BR              (quad + center)\n *  6:  FL FR FC LFE SL SR          (5.1 surround - last two can also be BL BR)\n *  7:  FL FR FC LFE BC SL SR       (6.1 surround)\n *  8:  FL FR FC LFE BL BR SL SR    (7.1 surround)\n */\ntypedef struct SDL_AudioSpec\n{\n    int freq;                   /**< DSP frequency -- samples per second */\n    SDL_AudioFormat format;     /**< Audio data format */\n    Uint8 channels;             /**< Number of channels: 1 mono, 2 stereo */\n    Uint8 silence;              /**< Audio buffer silence value (calculated) */\n    Uint16 samples;             /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */\n    Uint16 padding;             /**< Necessary for some compile environments */\n    Uint32 size;                /**< Audio buffer size in bytes (calculated) */\n    SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */\n    void *userdata;             /**< Userdata passed to callback (ignored for NULL callbacks). */\n} SDL_AudioSpec;\n#endif /* SDLRPOXY_NULL */\n\n#endif // #ifndef SDL_SDL_AUDIO_H\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_common.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_COMMON_HHHHH\n#define SDL_COMMON_HHHHH\n\n#define SDL_IMPORT(func) /* func */\n#define SDL_IMPORT_MATH(func) /* func */\n\n#endif // #ifndef SDL_COMMON_HHHHH\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_filesystem.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_SDL_FILESYSTEM_H\n#define SDL_SDL_FILESYSTEM_H\n\n#if !defined(SDLRPOXY_NULL)\n\n#include <SDL2/SDL_filesystem.h>\n\n#else\n\ninline char* SDL_GetBasePath()\n{\n    return nullptr;\n}\n\n#endif\n\n#endif // #ifndef SDL_SDL_FILESYSTEM_H\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_head.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_HEAD_HHHHHH\n#define SDL_HEAD_HHHHHH\n\n#if defined(SDLRPOXY_NULL)\n#include \"sdl_types.h\"\n#include \"sdl_stdinc.h\"\n#include \"sdl_timer.h\"\n#include \"sdl_assert.h\"\n#include \"sdl_atomic.h\"\n#include \"sdl_audio.h\"\n#else\n#include <SDL2/SDL.h>\n#endif\n\n#endif // #ifndef SDL_ATOMIC_HHHHHH\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_proxy.cmake",
    "content": "include_directories(${CMAKE_CURRENT_LIST_DIR}/)\n\nset(SDLPROXY_SRCS)\n\nfile(GLOB SDLPROXY_HEADS\n    ${CMAKE_CURRENT_LIST_DIR}/*.h\n    ${CMAKE_CURRENT_LIST_DIR}/null/*.h\n)\n\nlist(APPEND SDLPROXY_SRCS ${SDLPROXY_HEADS})\n\nif(NINTENDO_3DS)\n    add_definitions(-DSDLRPOXY_3DS)\n    list(APPEND SDLPROXY_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/3ds/std_3ds.cpp\n    )\n\n    if(THEXTECH_CUSTOM_AUDIO_LIBRARY)\n        add_definitions(-DCUSTOM_AUDIO)\n        set(THEXTECH_NO_MIXER_X ON)\n        list(APPEND SDLPROXY_SRCS\n            ${CMAKE_CURRENT_LIST_DIR}/3ds/mixer_3ds.cpp\n            ${CMAKE_CURRENT_LIST_DIR}/3ds/3ds-audio-lib.cpp\n        )\n    endif()\nelseif(NINTENDO_WII)\n    add_definitions(-DSDLRPOXY_WII)\n    list(APPEND SDLPROXY_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/wii/std_wii.cpp\n    )\nelseif(NINTENDO_DS)\n    add_definitions(-DSDLRPOXY_NULL)\n    list(APPEND SDLPROXY_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/16m/std_16m.cpp\n    )\nelseif(THEXTECH_NO_SDL_BUILD)\n    add_definitions(-DSDLRPOXY_NULL)\n    add_definitions(-DCUSTOM_AUDIO)\n    set(THEXTECH_NO_MIXER_X ON)\n    list(APPEND SDLPROXY_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/null/std_null.cpp\n        ${CMAKE_CURRENT_LIST_DIR}/null/mixer_null.cpp\n    )\nelseif(THEXTECH_CLI_BUILD)\n    add_definitions(-DSDLRPOXY_SDL2)\n    add_definitions(-DCUSTOM_AUDIO)\n    set(THEXTECH_NO_MIXER_X ON)\n    list(APPEND SDLPROXY_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/base/std_base.cpp\n        ${CMAKE_CURRENT_LIST_DIR}/null/mixer_null.cpp\n    )\nelse()\n    add_definitions(-DSDLRPOXY_SDL2)\n    list(APPEND SDLPROXY_SRCS\n        ${CMAKE_CURRENT_LIST_DIR}/base/std_base.cpp\n    )\nendif()\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_stdinc.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_SDL_STDINC_H\n#define SDL_SDL_STDINC_H\n\n#if !defined(SDLRPOXY_NULL)\n\n#include <SDL2/SDL_stdinc.h>\n\n#ifdef _MSC_VER\n// Workaround for MSVC where SDL_sscanf fails to parse a string\n// See details: https://github.com/libsdl-org/SDL/issues/8423\n#   define XTECH_sscanf sscanf\n#else\n#   define XTECH_sscanf SDL_sscanf\n#endif\n\n#else // USING POSIX FUNCTIONS ONLY\n\n#include \"null/sdl_null.h\"\n\n#include <cstdint>\n\n#define XTECH_sscanf sscanf\n\n#define SDL_vsnprintf  vsnprintf\n#define SDL_snprintf   snprintf\n\n#ifdef SDL_min\n#   undef SDL_min\n#endif\n\n#ifdef SDL_max\n#   undef SDL_max\n#endif\n\n#ifdef SDL_SwapLE32\n#   undef SDL_SwapLE32\n#endif\n\n#ifdef SDL_SwapBE32\n#   undef SDL_SwapBE32\n#endif\n\n#ifdef THEXTECH_BIG_ENDIAN\n// based on SDL formula\ninline uint32_t SDL_SwapLE32(uint32_t x)\n{\n    return static_cast<uint32_t>(((x << 24) | ((x << 8) & 0x00FF0000) |\n                                ((x >> 8) & 0x0000FF00) | (x >> 24)));\n}\ninline uint32_t SDL_SwapBE32(uint32_t x)\n{\n    return x;\n}\n#else\ninline uint32_t SDL_SwapLE32(uint32_t x)\n{\n    return x;\n}\ninline uint32_t SDL_SwapBE32(uint32_t x)\n{\n    return static_cast<uint32_t>(((x << 24) | ((x << 8) & 0x00FF0000) |\n                                ((x >> 8) & 0x0000FF00) | (x >> 24)));\n}\n#endif\n\n\n#define SDL_GetError() \"<SDL Error API is unavailable>\"\n#define SDL_ClearError() (void)0\ninline void SDL_SetError(const char *fmt, ...) { (void)fmt; /* Do Nothing!*/ }\n\n\ntemplate<class value_t>\ninline value_t SDL_min(value_t x, value_t y)\n{\n    return x < y ? x : y;\n}\n\ntemplate<class value_t>\ninline value_t SDL_max(value_t x, value_t y)\n{\n    return x > y ? x : y;\n}\n\ninline double SDL_fmod(double x, double y)\n{\n    return fmod(x, y);\n}\n\ninline int SDL_abs(int i)\n{\n    return (i < 0) ? -i : i;\n}\n\nSDL_IMPORT(malloc)\nSDL_IMPORT(free)\n\nSDL_IMPORT(memset)\nSDL_IMPORT(memcpy)\nSDL_IMPORT(memcmp)\nSDL_IMPORT(strdup)\nSDL_IMPORT(strlen)\nSDL_IMPORT(strtol)\nSDL_IMPORT(atoi)\nSDL_IMPORT(atof)\nSDL_IMPORT(sscanf)\nSDL_IMPORT(strcmp)\nSDL_IMPORT(strncmp)\nSDL_IMPORT(strcasecmp)\nSDL_IMPORT(strncasecmp)\nSDL_IMPORT(getenv)\n\nSDL_IMPORT_MATH(fabs)\nSDL_IMPORT_MATH(floor)\nSDL_IMPORT_MATH(ceil)\nSDL_IMPORT_MATH(round)\nSDL_IMPORT_MATH(sqrt)\n\n#endif\n\n#endif // #ifndef SDL_SDL_STDINC_H\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_timer.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_SDL_TIMER_H\n#define SDL_SDL_TIMER_H\n\n#ifndef SDLRPOXY_NULL\n\n#include <SDL2/SDL_version.h>\n#include <SDL2/SDL_timer.h>\n\n#if !SDL_VERSION_ATLEAST(2, 0, 18)\n// This call has been introduced in SDL 2.0.18. For the older SDL2, have a fallback!\ninline uint64_t SDL_GetTicks64()\n{\n    return (uint64_t)SDL_GetTicks();\n}\n#endif\n\n#else\n\n#include <cstdint>\n\n#ifndef SDL_timer_h_\nextern uint32_t SDL_GetTicks();\n\ninline uint64_t SDL_GetTicks64()\n{\n    return (uint64_t)SDL_GetTicks();\n}\n#endif\n\nvoid SDL_Delay(int ms);\n\n#endif\n\nextern uint64_t SDL_GetMicroTicks();\n\n#endif // #ifndef SDL_SDL_TIMER_H\n"
  },
  {
    "path": "lib/sdl_proxy/sdl_types.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_SDL_TYPES_H\n#define SDL_SDL_TYPES_H\n\n#if defined(SDLRPOXY_NULL)\n#include <cstdint>\n\n#ifndef UNUSED // To avoid IDE-side errors\n#   define UNUSED(x) (void)x\n#endif\n\ntypedef int64_t Sint64;\ntypedef uint64_t Uint64;\ntypedef int32_t Sint32;\ntypedef uint32_t Uint32;\ntypedef int16_t Sint16;\ntypedef uint16_t Uint16;\ntypedef int8_t Sint8;\ntypedef uint8_t Uint8;\n\n#define SDL_INLINE inline\n#define SDL_FORCE_INLINE static inline\n\n#else\n#include <SDL2/SDL_types.h>\n#endif\n\n#endif // #ifndef SDL_SDL_TYPES_H\n"
  },
  {
    "path": "lib/sdl_proxy/wii/std_wii.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../sdl_timer.h\"\n\n#include <gccore.h>\n#include <ogc/lwp_watchdog.h>\n\nuint64_t startTime = -1;\n\n//uint32_t SDL_GetTicks()\n//{\n//    if(startTime == (uint64_t)-1)\n//    {\n//        startTime = gettime();\n//    }\n//    return diff_msec(startTime, gettime());\n//}\n\nuint64_t SDL_GetMicroTicks()\n{\n    if(startTime == (uint64_t)-1)\n        startTime = gettime();\n\n    return diff_usec(startTime, gettime());\n}\n"
  },
  {
    "path": "lib/sdl_proxy_rwops/CMakeLists.txt",
    "content": "# A minimal shim to allow a platform unsupported by SDL2 to use the SDL_RWops interface\n# Copyright 2024 ds-sloth\n# Released under GPLv3.0 or later\n\ncmake_minimum_required(VERSION 3.5...3.10)\n\nif(POLICY CMP0069) # Allow CMAKE_INTERPROCEDURAL_OPTIMIZATION (lto) to be set\n    cmake_policy(SET CMP0069 NEW)\nendif()\n\nproject(sdl_proxy_rwops CXX)\n\nadd_library(sdl_proxy_rwops STATIC EXCLUDE_FROM_ALL\n    sdl_proxy_rwops.c\n    sdl_proxy_rwops_file.c\n)\n\ntarget_include_directories(sdl_proxy_rwops PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)\n"
  },
  {
    "path": "lib/sdl_proxy_rwops/include/SDL2/SDL_rwops.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SDL_PROXY_RWOPS_H\n#define SDL_PROXY_RWOPS_H\n\n#include <stdint.h>\n#include <stddef.h>\n#include <stdio.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* RWops Types */\n#define SDL_RWOPS_UNKNOWN   0U  /**< Unknown stream type */\n#define SDL_RWOPS_STDFILE   2U  /**< Stdio file */\n\n#define RW_SEEK_CUR SEEK_CUR\n#define RW_SEEK_SET SEEK_SET\n#define RW_SEEK_END SEEK_END\n\ntypedef struct SDL_RWops\n{\n    /**\n     *  Return the size of the file in this rwops, or -1 if unknown\n     */\n    int64_t (* size) (struct SDL_RWops * context);\n\n    /**\n     *  Seek to `offset` relative to `whence`, one of stdio's whence values:\n     *  RW_SEEK_SET, RW_SEEK_CUR, RW_SEEK_END\n     *\n     *  \\return the final offset in the data stream, or -1 on error.\n     */\n    int64_t (* seek) (struct SDL_RWops * context, int64_t offset,\n                             int whence);\n\n    /**\n     *  Read up to `maxnum` objects each of size `size` from the data\n     *  stream to the area pointed at by `ptr`.\n     *\n     *  \\return the number of objects read, or 0 at error or end of file.\n     */\n    size_t (* read) (struct SDL_RWops * context, void *ptr,\n                             size_t size, size_t maxnum);\n\n    /**\n     *  Write exactly `num` objects each of size `size` from the area\n     *  pointed at by `ptr` to data stream.\n     *\n     *  \\return the number of objects written, or 0 at error or end of file.\n     */\n    size_t (* write) (struct SDL_RWops * context, const void *ptr,\n                              size_t size, size_t num);\n\n    /**\n     *  Close and free an allocated SDL_RWops structure.\n     *\n     *  \\return 0 if successful or -1 on write error when flushing data.\n     */\n    int (* close) (struct SDL_RWops * context);\n\n    int type;\n\n    union\n    {\n      struct\n      {\n          void *data1;\n      } unknown;\n    } hidden;\n} SDL_RWops;\n\nSDL_RWops* SDL_AllocRW();\nvoid SDL_FreeRW(SDL_RWops * area);\nSDL_RWops* SDL_RWFromFile(const char* pathname, const char* mode);\n\n#ifdef __cplusplus\n} // extern \"C\"\n#endif\n\n/* C declarations or C++ inline definitions of standard RWops functions */\n#include \"_priv_rwops_funcs.h\"\n\n#endif /* #ifndef SDL_PROXY_RWOPS_H */\n"
  },
  {
    "path": "lib/sdl_proxy_rwops/include/SDL2/_priv_rwops_funcs.h",
    "content": "#ifdef RWOPS_C_DEFINITIONS\n/* non-inline definitions of normal RWops functions for C */\n#    define RWOPS_PREFIX\n#    define RWOPS_BODY(x) x\n#elif defined(__cplusplus)\n// inline definitions of normal RWops functions for C++\n#    define RWOPS_PREFIX inline\n#    define RWOPS_BODY(x) x\n#else\n/* non-inline declarations of normal RWops functions for C */\n#    define RWOPS_PREFIX\n#    define RWOPS_BODY(x) ;\n#endif\n\nRWOPS_PREFIX int64_t SDL_RWsize(SDL_RWops* stream)\nRWOPS_BODY({\n    return stream->size(stream);\n})\n\nRWOPS_PREFIX int64_t SDL_RWseek(SDL_RWops* stream, int64_t offset, int whence)\nRWOPS_BODY({\n    return stream->seek(stream, offset, whence);\n})\n\nRWOPS_PREFIX size_t SDL_RWread(SDL_RWops* stream, void* ptr, size_t size, size_t nmemb)\nRWOPS_BODY({\n    return stream->read(stream, ptr, size, nmemb);\n})\n\nRWOPS_PREFIX size_t SDL_RWwrite(SDL_RWops* stream, const void* ptr, size_t size, size_t nmemb)\nRWOPS_BODY({\n    return stream->write(stream, ptr, size, nmemb);\n})\n\nRWOPS_PREFIX int SDL_RWclose(SDL_RWops* stream)\nRWOPS_BODY({\n    if(!stream)\n        return 0;\n\n    int ret = stream->close(stream);\n    SDL_FreeRW(stream);\n    return ret;\n})\n\nRWOPS_PREFIX long SDL_RWtell(SDL_RWops* stream)\nRWOPS_BODY({\n    return stream->seek(stream, 0, RW_SEEK_CUR);\n})\n\n#undef RWOPS_PREFIX\n#undef RWOPS_BODY\n"
  },
  {
    "path": "lib/sdl_proxy_rwops/sdl_proxy_rwops.c",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <malloc.h>\n#include <SDL2/SDL_rwops.h>\n\nSDL_RWops* SDL_AllocRW()\n{\n    return (SDL_RWops*)(malloc(sizeof(SDL_RWops)));\n}\n\nvoid SDL_FreeRW(SDL_RWops * area)\n{\n    free(area);\n}\n\n/* C definitions of standard RWops functions */\n#define RWOPS_C_DEFINITIONS\n#include <SDL2/_priv_rwops_funcs.h>\n"
  },
  {
    "path": "lib/sdl_proxy_rwops/sdl_proxy_rwops_file.c",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <SDL2/SDL_rwops.h>\n\n#ifdef _WIN32\n#include <windows.h>\n#include <shlwapi.h>\n#else\n#include <stdio.h>\n#endif\n\n/* from files.cpp */\nstatic inline FILE *utf8_fopen(const char *filePath, const char *modes)\n{\n#ifndef _WIN32\n    return fopen(filePath, modes);\n#else\n    wchar_t wfile[MAX_PATH + 1];\n    wchar_t wmode[21];\n    int wfile_len = (int)strlen(filePath);\n    int wmode_len = (int)strlen(modes);\n    wfile_len = MultiByteToWideChar(CP_UTF8, 0, filePath, wfile_len, wfile, MAX_PATH);\n    wmode_len = MultiByteToWideChar(CP_UTF8, 0, modes, wmode_len, wmode, 20);\n    wfile[wfile_len] = L'\\0';\n    wmode[wmode_len] = L'\\0';\n    return _wfopen(wfile, wmode);\n#endif\n}\n\nstatic int64_t s_file_size(SDL_RWops* stream)\n{\n    if(!stream || !stream->hidden.unknown.data1)\n        return -1;\n\n    long curpos = ftell(stream->hidden.unknown.data1);\n\n    fseek(stream->hidden.unknown.data1, 0, SEEK_END);\n    int64_t filesize = (int64_t)(ftell(stream->hidden.unknown.data1));\n\n    fseek(stream->hidden.unknown.data1, curpos, SEEK_SET);\n\n    return filesize;\n}\n\nstatic int64_t s_file_seek(SDL_RWops* stream, int64_t offset, int whence)\n{\n    if(whence == RW_SEEK_CUR && offset == 0)\n    {\n        // skip the seek\n    }\n    else if(fseek(stream->hidden.unknown.data1, offset, whence))\n        return -1;\n\n    return ftell(stream->hidden.unknown.data1);\n}\n\nstatic size_t s_file_read(SDL_RWops* stream, void* ptr, size_t size, size_t nmemb)\n{\n    return fread(ptr, size, nmemb, stream->hidden.unknown.data1);\n}\n\nstatic size_t s_file_write(SDL_RWops* stream, const void* ptr, size_t size, size_t nmemb)\n{\n    return fwrite(ptr, size, nmemb, stream->hidden.unknown.data1);\n}\n\nstatic int s_file_close(SDL_RWops* stream)\n{\n    return fclose(stream->hidden.unknown.data1);\n}\n\nSDL_RWops* SDL_RWFromFile(const char* pathname, const char* mode)\n{\n    FILE* f = utf8_fopen(pathname, mode);\n    if(!f)\n        return NULL;\n\n    SDL_RWops* ret = SDL_AllocRW();\n\n    if(!ret)\n    {\n        fclose(f);\n        return NULL;\n    }\n\n    ret->size = s_file_size;\n    ret->seek = s_file_seek;\n    ret->read = s_file_read;\n    ret->write = s_file_write;\n    ret->close = s_file_close;\n\n    ret->type = SDL_RWOPS_STDFILE;\n    ret->hidden.unknown.data1 = f;\n\n    return ret;\n}\n"
  },
  {
    "path": "lib/sorting/pdqsort.h",
    "content": "/*\n    pdqsort.h - Pattern-defeating quicksort.\n\n    Copyright (c) 2021 Orson Peters\n\n    This software is provided 'as-is', without any express or implied warranty. In no event will the\n    authors be held liable for any damages arising from the use of this software.\n\n    Permission is granted to anyone to use this software for any purpose, including commercial\n    applications, and to alter it and redistribute it freely, subject to the following restrictions:\n\n    1. The origin of this software must not be misrepresented; you must not claim that you wrote the\n       original software. If you use this software in a product, an acknowledgment in the product\n       documentation would be appreciated but is not required.\n\n    2. Altered source versions must be plainly marked as such, and must not be misrepresented as\n       being the original software.\n\n    3. This notice may not be removed or altered from any source distribution.\n\n    In use by TheXTech and/or Moondust projects. Sourced from https://github.com/orlp/pdqsort, revision b1ef26a5. Not yet modified.\n*/\n\n\n#ifndef PDQSORT_H\n#define PDQSORT_H\n\n#include <algorithm>\n#include <cstddef>\n#include <functional>\n#include <utility>\n#include <iterator>\n\n#if __cplusplus >= 201103L\n    #include <cstdint>\n    #include <type_traits>\n    #define PDQSORT_PREFER_MOVE(x) std::move(x)\n#else\n    #define PDQSORT_PREFER_MOVE(x) (x)\n#endif\n\n\nnamespace pdqsort_detail {\n    enum {\n        // Partitions below this size are sorted using insertion sort.\n        insertion_sort_threshold = 24,\n\n        // Partitions above this size use Tukey's ninther to select the pivot.\n        ninther_threshold = 128,\n\n        // When we detect an already sorted partition, attempt an insertion sort that allows this\n        // amount of element moves before giving up.\n        partial_insertion_sort_limit = 8,\n\n        // Must be multiple of 8 due to loop unrolling, and < 256 to fit in unsigned char.\n        block_size = 64,\n\n        // Cacheline size, assumes power of two.\n        cacheline_size = 64\n\n    };\n\n#if __cplusplus >= 201103L\n    template<class T> struct is_default_compare : std::false_type { };\n    template<class T> struct is_default_compare<std::less<T>> : std::true_type { };\n    template<class T> struct is_default_compare<std::greater<T>> : std::true_type { };\n#endif\n\n    // Returns floor(log2(n)), assumes n > 0.\n    template<class T>\n    inline int log2(T n) {\n        int log = 0;\n        while (n >>= 1) ++log;\n        return log;\n    }\n\n    // Sorts [begin, end) using insertion sort with the given comparison function.\n    template<class Iter, class Compare>\n    inline void insertion_sort(Iter begin, Iter end, Compare comp) {\n        typedef typename std::iterator_traits<Iter>::value_type T;\n        if (begin == end) return;\n\n        for (Iter cur = begin + 1; cur != end; ++cur) {\n            Iter sift = cur;\n            Iter sift_1 = cur - 1;\n\n            // Compare first so we can avoid 2 moves for an element already positioned correctly.\n            if (comp(*sift, *sift_1)) {\n                T tmp = PDQSORT_PREFER_MOVE(*sift);\n\n                do { *sift-- = PDQSORT_PREFER_MOVE(*sift_1); }\n                while (sift != begin && comp(tmp, *--sift_1));\n\n                *sift = PDQSORT_PREFER_MOVE(tmp);\n            }\n        }\n    }\n\n    // Sorts [begin, end) using insertion sort with the given comparison function. Assumes\n    // *(begin - 1) is an element smaller than or equal to any element in [begin, end).\n    template<class Iter, class Compare>\n    inline void unguarded_insertion_sort(Iter begin, Iter end, Compare comp) {\n        typedef typename std::iterator_traits<Iter>::value_type T;\n        if (begin == end) return;\n\n        for (Iter cur = begin + 1; cur != end; ++cur) {\n            Iter sift = cur;\n            Iter sift_1 = cur - 1;\n\n            // Compare first so we can avoid 2 moves for an element already positioned correctly.\n            if (comp(*sift, *sift_1)) {\n                T tmp = PDQSORT_PREFER_MOVE(*sift);\n\n                do { *sift-- = PDQSORT_PREFER_MOVE(*sift_1); }\n                while (comp(tmp, *--sift_1));\n\n                *sift = PDQSORT_PREFER_MOVE(tmp);\n            }\n        }\n    }\n\n    // Attempts to use insertion sort on [begin, end). Will return false if more than\n    // partial_insertion_sort_limit elements were moved, and abort sorting. Otherwise it will\n    // successfully sort and return true.\n    template<class Iter, class Compare>\n    inline bool partial_insertion_sort(Iter begin, Iter end, Compare comp) {\n        typedef typename std::iterator_traits<Iter>::value_type T;\n        if (begin == end) return true;\n        \n        std::size_t limit = 0;\n        for (Iter cur = begin + 1; cur != end; ++cur) {\n            Iter sift = cur;\n            Iter sift_1 = cur - 1;\n\n            // Compare first so we can avoid 2 moves for an element already positioned correctly.\n            if (comp(*sift, *sift_1)) {\n                T tmp = PDQSORT_PREFER_MOVE(*sift);\n\n                do { *sift-- = PDQSORT_PREFER_MOVE(*sift_1); }\n                while (sift != begin && comp(tmp, *--sift_1));\n\n                *sift = PDQSORT_PREFER_MOVE(tmp);\n                limit += cur - sift;\n            }\n            \n            if (limit > partial_insertion_sort_limit) return false;\n        }\n\n        return true;\n    }\n\n    template<class Iter, class Compare>\n    inline void sort2(Iter a, Iter b, Compare comp) {\n        if (comp(*b, *a)) std::iter_swap(a, b);\n    }\n\n    // Sorts the elements *a, *b and *c using comparison function comp.\n    template<class Iter, class Compare>\n    inline void sort3(Iter a, Iter b, Iter c, Compare comp) {\n        sort2(a, b, comp);\n        sort2(b, c, comp);\n        sort2(a, b, comp);\n    }\n\n    template<class T>\n    inline T* align_cacheline(T* p) {\n#if defined(UINTPTR_MAX) && __cplusplus >= 201103L\n        std::uintptr_t ip = reinterpret_cast<std::uintptr_t>(p);\n#else\n        std::size_t ip = reinterpret_cast<std::size_t>(p);\n#endif\n        ip = (ip + cacheline_size - 1) & -cacheline_size;\n        return reinterpret_cast<T*>(ip);\n    }\n\n    template<class Iter>\n    inline void swap_offsets(Iter first, Iter last,\n                             unsigned char* offsets_l, unsigned char* offsets_r,\n                             size_t num, bool use_swaps) {\n        typedef typename std::iterator_traits<Iter>::value_type T;\n        if (use_swaps) {\n            // This case is needed for the descending distribution, where we need\n            // to have proper swapping for pdqsort to remain O(n).\n            for (size_t i = 0; i < num; ++i) {\n                std::iter_swap(first + offsets_l[i], last - offsets_r[i]);\n            }\n        } else if (num > 0) {\n            Iter l = first + offsets_l[0]; Iter r = last - offsets_r[0];\n            T tmp(PDQSORT_PREFER_MOVE(*l)); *l = PDQSORT_PREFER_MOVE(*r);\n            for (size_t i = 1; i < num; ++i) {\n                l = first + offsets_l[i]; *r = PDQSORT_PREFER_MOVE(*l);\n                r = last - offsets_r[i]; *l = PDQSORT_PREFER_MOVE(*r);\n            }\n            *r = PDQSORT_PREFER_MOVE(tmp);\n        }\n    }\n\n    // Partitions [begin, end) around pivot *begin using comparison function comp. Elements equal\n    // to the pivot are put in the right-hand partition. Returns the position of the pivot after\n    // partitioning and whether the passed sequence already was correctly partitioned. Assumes the\n    // pivot is a median of at least 3 elements and that [begin, end) is at least\n    // insertion_sort_threshold long. Uses branchless partitioning.\n    template<class Iter, class Compare>\n    inline std::pair<Iter, bool> partition_right_branchless(Iter begin, Iter end, Compare comp) {\n        typedef typename std::iterator_traits<Iter>::value_type T;\n\n        // Move pivot into local for speed.\n        T pivot(PDQSORT_PREFER_MOVE(*begin));\n        Iter first = begin;\n        Iter last = end;\n\n        // Find the first element greater than or equal than the pivot (the median of 3 guarantees\n        // this exists).\n        while (comp(*++first, pivot));\n\n        // Find the first element strictly smaller than the pivot. We have to guard this search if\n        // there was no element before *first.\n        if (first - 1 == begin) while (first < last && !comp(*--last, pivot));\n        else                    while (                !comp(*--last, pivot));\n\n        // If the first pair of elements that should be swapped to partition are the same element,\n        // the passed in sequence already was correctly partitioned.\n        bool already_partitioned = first >= last;\n        if (!already_partitioned) {\n            std::iter_swap(first, last);\n            ++first;\n\n            // The following branchless partitioning is derived from \"BlockQuicksort: How Branch\n            // Mispredictions don’t affect Quicksort\" by Stefan Edelkamp and Armin Weiss, but\n            // heavily micro-optimized.\n            unsigned char offsets_l_storage[block_size + cacheline_size];\n            unsigned char offsets_r_storage[block_size + cacheline_size];\n            unsigned char* offsets_l = align_cacheline(offsets_l_storage);\n            unsigned char* offsets_r = align_cacheline(offsets_r_storage);\n\n            Iter offsets_l_base = first;\n            Iter offsets_r_base = last;\n            size_t num_l, num_r, start_l, start_r;\n            num_l = num_r = start_l = start_r = 0;\n            \n            while (first < last) {\n                // Fill up offset blocks with elements that are on the wrong side.\n                // First we determine how much elements are considered for each offset block.\n                size_t num_unknown = last - first;\n                size_t left_split = num_l == 0 ? (num_r == 0 ? num_unknown / 2 : num_unknown) : 0;\n                size_t right_split = num_r == 0 ? (num_unknown - left_split) : 0;\n\n                // Fill the offset blocks.\n                if (left_split >= block_size) {\n                    for (size_t i = 0; i < block_size;) {\n                        offsets_l[num_l] = i++; num_l += !comp(*first, pivot); ++first;\n                        offsets_l[num_l] = i++; num_l += !comp(*first, pivot); ++first;\n                        offsets_l[num_l] = i++; num_l += !comp(*first, pivot); ++first;\n                        offsets_l[num_l] = i++; num_l += !comp(*first, pivot); ++first;\n                        offsets_l[num_l] = i++; num_l += !comp(*first, pivot); ++first;\n                        offsets_l[num_l] = i++; num_l += !comp(*first, pivot); ++first;\n                        offsets_l[num_l] = i++; num_l += !comp(*first, pivot); ++first;\n                        offsets_l[num_l] = i++; num_l += !comp(*first, pivot); ++first;\n                    }\n                } else {\n                    for (size_t i = 0; i < left_split;) {\n                        offsets_l[num_l] = i++; num_l += !comp(*first, pivot); ++first;\n                    }\n                }\n\n                if (right_split >= block_size) {\n                    for (size_t i = 0; i < block_size;) {\n                        offsets_r[num_r] = ++i; num_r += comp(*--last, pivot);\n                        offsets_r[num_r] = ++i; num_r += comp(*--last, pivot);\n                        offsets_r[num_r] = ++i; num_r += comp(*--last, pivot);\n                        offsets_r[num_r] = ++i; num_r += comp(*--last, pivot);\n                        offsets_r[num_r] = ++i; num_r += comp(*--last, pivot);\n                        offsets_r[num_r] = ++i; num_r += comp(*--last, pivot);\n                        offsets_r[num_r] = ++i; num_r += comp(*--last, pivot);\n                        offsets_r[num_r] = ++i; num_r += comp(*--last, pivot);\n                    }\n                } else {\n                    for (size_t i = 0; i < right_split;) {\n                        offsets_r[num_r] = ++i; num_r += comp(*--last, pivot);\n                    }\n                }\n\n                // Swap elements and update block sizes and first/last boundaries.\n                size_t num = std::min(num_l, num_r);\n                swap_offsets(offsets_l_base, offsets_r_base,\n                             offsets_l + start_l, offsets_r + start_r,\n                             num, num_l == num_r);\n                num_l -= num; num_r -= num;\n                start_l += num; start_r += num;\n\n                if (num_l == 0) {\n                    start_l = 0;\n                    offsets_l_base = first;\n                }\n                \n                if (num_r == 0) {\n                    start_r = 0;\n                    offsets_r_base = last;\n                }\n            }\n\n            // We have now fully identified [first, last)'s proper position. Swap the last elements.\n            if (num_l) {\n                offsets_l += start_l;\n                while (num_l--) std::iter_swap(offsets_l_base + offsets_l[num_l], --last);\n                first = last;\n            }\n            if (num_r) {\n                offsets_r += start_r;\n                while (num_r--) std::iter_swap(offsets_r_base - offsets_r[num_r], first), ++first;\n                last = first;\n            }\n        }\n\n        // Put the pivot in the right place.\n        Iter pivot_pos = first - 1;\n        *begin = PDQSORT_PREFER_MOVE(*pivot_pos);\n        *pivot_pos = PDQSORT_PREFER_MOVE(pivot);\n\n        return std::make_pair(pivot_pos, already_partitioned);\n    }\n\n\n\n    // Partitions [begin, end) around pivot *begin using comparison function comp. Elements equal\n    // to the pivot are put in the right-hand partition. Returns the position of the pivot after\n    // partitioning and whether the passed sequence already was correctly partitioned. Assumes the\n    // pivot is a median of at least 3 elements and that [begin, end) is at least\n    // insertion_sort_threshold long.\n    template<class Iter, class Compare>\n    inline std::pair<Iter, bool> partition_right(Iter begin, Iter end, Compare comp) {\n        typedef typename std::iterator_traits<Iter>::value_type T;\n        \n        // Move pivot into local for speed.\n        T pivot(PDQSORT_PREFER_MOVE(*begin));\n\n        Iter first = begin;\n        Iter last = end;\n\n        // Find the first element greater than or equal than the pivot (the median of 3 guarantees\n        // this exists).\n        while (comp(*++first, pivot));\n\n        // Find the first element strictly smaller than the pivot. We have to guard this search if\n        // there was no element before *first.\n        if (first - 1 == begin) while (first < last && !comp(*--last, pivot));\n        else                    while (                !comp(*--last, pivot));\n\n        // If the first pair of elements that should be swapped to partition are the same element,\n        // the passed in sequence already was correctly partitioned.\n        bool already_partitioned = first >= last;\n        \n        // Keep swapping pairs of elements that are on the wrong side of the pivot. Previously\n        // swapped pairs guard the searches, which is why the first iteration is special-cased\n        // above.\n        while (first < last) {\n            std::iter_swap(first, last);\n            while (comp(*++first, pivot));\n            while (!comp(*--last, pivot));\n        }\n\n        // Put the pivot in the right place.\n        Iter pivot_pos = first - 1;\n        *begin = PDQSORT_PREFER_MOVE(*pivot_pos);\n        *pivot_pos = PDQSORT_PREFER_MOVE(pivot);\n\n        return std::make_pair(pivot_pos, already_partitioned);\n    }\n\n    // Similar function to the one above, except elements equal to the pivot are put to the left of\n    // the pivot and it doesn't check or return if the passed sequence already was partitioned.\n    // Since this is rarely used (the many equal case), and in that case pdqsort already has O(n)\n    // performance, no block quicksort is applied here for simplicity.\n    template<class Iter, class Compare>\n    inline Iter partition_left(Iter begin, Iter end, Compare comp) {\n        typedef typename std::iterator_traits<Iter>::value_type T;\n\n        T pivot(PDQSORT_PREFER_MOVE(*begin));\n        Iter first = begin;\n        Iter last = end;\n        \n        while (comp(pivot, *--last));\n\n        if (last + 1 == end) while (first < last && !comp(pivot, *++first));\n        else                 while (                !comp(pivot, *++first));\n\n        while (first < last) {\n            std::iter_swap(first, last);\n            while (comp(pivot, *--last));\n            while (!comp(pivot, *++first));\n        }\n\n        Iter pivot_pos = last;\n        *begin = PDQSORT_PREFER_MOVE(*pivot_pos);\n        *pivot_pos = PDQSORT_PREFER_MOVE(pivot);\n\n        return pivot_pos;\n    }\n\n\n    template<class Iter, class Compare, bool Branchless>\n    inline void pdqsort_loop(Iter begin, Iter end, Compare comp, int bad_allowed, bool leftmost = true) {\n        typedef typename std::iterator_traits<Iter>::difference_type diff_t;\n\n        // Use a while loop for tail recursion elimination.\n        while (true) {\n            diff_t size = end - begin;\n\n            // Insertion sort is faster for small arrays.\n            if (size < insertion_sort_threshold) {\n                if (leftmost) insertion_sort(begin, end, comp);\n                else unguarded_insertion_sort(begin, end, comp);\n                return;\n            }\n\n            // Choose pivot as median of 3 or pseudomedian of 9.\n            diff_t s2 = size / 2;\n            if (size > ninther_threshold) {\n                sort3(begin, begin + s2, end - 1, comp);\n                sort3(begin + 1, begin + (s2 - 1), end - 2, comp);\n                sort3(begin + 2, begin + (s2 + 1), end - 3, comp);\n                sort3(begin + (s2 - 1), begin + s2, begin + (s2 + 1), comp);\n                std::iter_swap(begin, begin + s2);\n            } else sort3(begin + s2, begin, end - 1, comp);\n\n            // If *(begin - 1) is the end of the right partition of a previous partition operation\n            // there is no element in [begin, end) that is smaller than *(begin - 1). Then if our\n            // pivot compares equal to *(begin - 1) we change strategy, putting equal elements in\n            // the left partition, greater elements in the right partition. We do not have to\n            // recurse on the left partition, since it's sorted (all equal).\n            if (!leftmost && !comp(*(begin - 1), *begin)) {\n                begin = partition_left(begin, end, comp) + 1;\n                continue;\n            }\n\n            // Partition and get results.\n            std::pair<Iter, bool> part_result =\n                Branchless ? partition_right_branchless(begin, end, comp)\n                           : partition_right(begin, end, comp);\n            Iter pivot_pos = part_result.first;\n            bool already_partitioned = part_result.second;\n\n            // Check for a highly unbalanced partition.\n            diff_t l_size = pivot_pos - begin;\n            diff_t r_size = end - (pivot_pos + 1);\n            bool highly_unbalanced = l_size < size / 8 || r_size < size / 8;\n\n            // If we got a highly unbalanced partition we shuffle elements to break many patterns.\n            if (highly_unbalanced) {\n                // If we had too many bad partitions, switch to heapsort to guarantee O(n log n).\n                if (--bad_allowed == 0) {\n                    std::make_heap(begin, end, comp);\n                    std::sort_heap(begin, end, comp);\n                    return;\n                }\n\n                if (l_size >= insertion_sort_threshold) {\n                    std::iter_swap(begin,             begin + l_size / 4);\n                    std::iter_swap(pivot_pos - 1, pivot_pos - l_size / 4);\n\n                    if (l_size > ninther_threshold) {\n                        std::iter_swap(begin + 1,         begin + (l_size / 4 + 1));\n                        std::iter_swap(begin + 2,         begin + (l_size / 4 + 2));\n                        std::iter_swap(pivot_pos - 2, pivot_pos - (l_size / 4 + 1));\n                        std::iter_swap(pivot_pos - 3, pivot_pos - (l_size / 4 + 2));\n                    }\n                }\n                \n                if (r_size >= insertion_sort_threshold) {\n                    std::iter_swap(pivot_pos + 1, pivot_pos + (1 + r_size / 4));\n                    std::iter_swap(end - 1,                   end - r_size / 4);\n                    \n                    if (r_size > ninther_threshold) {\n                        std::iter_swap(pivot_pos + 2, pivot_pos + (2 + r_size / 4));\n                        std::iter_swap(pivot_pos + 3, pivot_pos + (3 + r_size / 4));\n                        std::iter_swap(end - 2,             end - (1 + r_size / 4));\n                        std::iter_swap(end - 3,             end - (2 + r_size / 4));\n                    }\n                }\n            } else {\n                // If we were decently balanced and we tried to sort an already partitioned\n                // sequence try to use insertion sort.\n                if (already_partitioned && partial_insertion_sort(begin, pivot_pos, comp)\n                                        && partial_insertion_sort(pivot_pos + 1, end, comp)) return;\n            }\n                \n            // Sort the left partition first using recursion and do tail recursion elimination for\n            // the right-hand partition.\n            pdqsort_loop<Iter, Compare, Branchless>(begin, pivot_pos, comp, bad_allowed, leftmost);\n            begin = pivot_pos + 1;\n            leftmost = false;\n        }\n    }\n}\n\n\ntemplate<class Iter, class Compare>\ninline void pdqsort(Iter begin, Iter end, Compare comp) {\n    if (begin == end) return;\n\n#if __cplusplus >= 201103L\n    pdqsort_detail::pdqsort_loop<Iter, Compare,\n        pdqsort_detail::is_default_compare<typename std::decay<Compare>::type>::value &&\n        std::is_arithmetic<typename std::iterator_traits<Iter>::value_type>::value>(\n        begin, end, comp, pdqsort_detail::log2(end - begin));\n#else\n    pdqsort_detail::pdqsort_loop<Iter, Compare, false>(\n        begin, end, comp, pdqsort_detail::log2(end - begin));\n#endif\n}\n\ntemplate<class Iter>\ninline void pdqsort(Iter begin, Iter end) {\n    typedef typename std::iterator_traits<Iter>::value_type T;\n    pdqsort(begin, end, std::less<T>());\n}\n\ntemplate<class Iter, class Compare>\ninline void pdqsort_branchless(Iter begin, Iter end, Compare comp) {\n    if (begin == end) return;\n    pdqsort_detail::pdqsort_loop<Iter, Compare, true>(\n        begin, end, comp, pdqsort_detail::log2(end - begin));\n}\n\ntemplate<class Iter>\ninline void pdqsort_branchless(Iter begin, Iter end) {\n    typedef typename std::iterator_traits<Iter>::value_type T;\n    pdqsort_branchless(begin, end, std::less<T>());\n}\n\n\n#undef PDQSORT_PREFER_MOVE\n\n#endif\n"
  },
  {
    "path": "lib/sorting/tinysort.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef THEXTECH_TINY_SORT_H\n#define THEXTECH_TINY_SORT_H\n\n#include <functional>\n#include <iterator>\n\n// an extremely simple insertion sort implementation intended for correctness as a stable sort, and code size.\n// DO NOT USE IF PERFORMANCE IS IMPORTANT.\ntemplate<class it, class compare_func>\ninline void tinysort(it begin, it end, compare_func comp)\n{\n    if(begin == end)\n        return;\n\n    typedef typename std::iterator_traits<it>::value_type T;\n\n    for(it i = begin + 1; i != end; ++i)\n    {\n        T x = std::move(*i);\n\n        it j;\n        for(j = i; j > begin && comp(x, *(j - 1)); --j)\n            *j = std::move(*(j - 1));\n\n        *j = std::move(x);\n    }\n}\n\ntemplate<class it>\ninline void tinysort(it begin, it end)\n{\n    typedef typename std::iterator_traits<it>::value_type T;\n    tinysort(begin, end, std::less<T>());\n}\n\n#endif // #ifndef THEXTECH_TINY_SORT_H\n"
  },
  {
    "path": "lib/tclap/Arg.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  Arg.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno .\n *  Copyright (c) 2017 Google Inc.\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_ARG_H\n#define TCLAP_ARG_H\n\n#ifdef HAVE_TCLAP_CONFIG_H\n#include <tclap/TCLAPConfig.h>\n#endif\n\n#include <tclap/ArgException.h>\n#include <tclap/ArgTraits.h>\n#include <tclap/CmdLineInterface.h>\n#include <tclap/StandardTraits.h>\n#include <tclap/Visitor.h>\n#include <tclap/sstream.h>\n\n#include <cstdio>\n#include <iomanip>\n#include <iostream>\n#include <list>\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * A virtual base class that defines the essential data for all arguments.\n * This class, or one of its existing children, must be subclassed to do\n * anything.\n */\nclass Arg {\nprivate:\n    /**\n     * Prevent accidental copying.\n     */\n    Arg(const Arg &rhs);\n\n    /**\n     * Prevent accidental copying.\n     */\n    Arg &operator=(const Arg &rhs);\n\n    /**\n     * The delimiter that separates an argument flag/name from the\n     * value.\n     */\n    static char &delimiterRef() {\n        static char delim = ' ';\n        return delim;\n    }\n\nprotected:\n    /**\n     * The single char flag used to identify the argument.\n     * This value (preceded by a dash {-}), can be used to identify\n     * an argument on the command line.  The _flag can be blank,\n     * in fact this is how unlabeled args work.  Unlabeled args must\n     * override appropriate functions to get correct handling. Note\n     * that the _flag does NOT include the dash as part of the flag.\n     */\n    std::string _flag;\n\n    /**\n     * A single word namd identifying the argument.\n     * This value (preceded by two dashed {--}) can also be used\n     * to identify an argument on the command line.  Note that the\n     * _name does NOT include the two dashes as part of the _name. The\n     * _name cannot be blank.\n     */\n    std::string _name;\n\n    /**\n     * Description of the argument.\n     */\n    std::string _description;\n\n    /**\n     * Indicating whether the argument is required.\n     */\n    const bool _required;\n\n    /**\n     * Label to be used in usage description.  Normally set to\n     * \"required\", but can be changed when necessary.\n     */\n    std::string _requireLabel;\n\n    /**\n     * Indicates whether a value is required for the argument.\n     * Note that the value may be required but the argument/value\n     * combination may not be, as specified by _required.\n     */\n    bool _valueRequired;\n\n    /**\n     * Indicates whether the argument has been set.\n     * Indicates that a value on the command line has matched the\n     * name/flag of this argument and the values have been set accordingly.\n     */\n    bool _alreadySet;\n\n    /** Indicates the value specified to set this flag (like -a or --all).\n     */\n    std::string _setBy;\n\n    /**\n     * A pointer to a visitor object.\n     * The visitor allows special handling to occur as soon as the\n     * argument is matched.  This defaults to NULL and should not\n     * be used unless absolutely necessary.\n     */\n    Visitor *_visitor;\n\n    /**\n     * Whether this argument can be ignored, if desired.\n     */\n    bool _ignoreable;\n\n    bool _acceptsMultipleValues;\n\n    /**\n     * Indicates if the argument is visible in the help output (e.g.,\n     * when specifying --help).\n     */\n    bool _visibleInHelp;\n\n    /**\n     * Performs the special handling described by the Visitor.\n     */\n    void _checkWithVisitor() const;\n\n    /**\n     * Primary constructor. YOU (yes you) should NEVER construct an Arg\n     * directly, this is a base class that is extended by various children\n     * that are meant to be used.  Use SwitchArg, ValueArg, MultiArg,\n     * UnlabeledValueArg, or UnlabeledMultiArg instead.\n     *\n     * \\param flag - The flag identifying the argument.\n     * \\param name - The name identifying the argument.\n     * \\param desc - The description of the argument, used in the usage.\n     * \\param req - Whether the argument is required.\n     * \\param valreq - Whether the a value is required for the argument.\n     * \\param v - The visitor checked by the argument. Defaults to NULL.\n     */\n    Arg(const std::string &flag, const std::string &name,\n        const std::string &desc, bool req, bool valreq, Visitor *v = NULL);\n\npublic:\n    /**\n     * Destructor.\n     */\n    virtual ~Arg();\n\n    /**\n     * Adds this to the specified list of Args.\n     * \\param argList - The list to add this to.\n     */\n    virtual void addToList(std::list<Arg *> &argList) const;\n\n    /**\n     * The delimiter that separates an argument flag/name from the\n     * value.\n     */\n    static char delimiter() { return delimiterRef(); }\n\n    /**\n     * The char used as a place holder when SwitchArgs are combined.\n     * Currently set to the bell char (ASCII 7).\n     */\n    static char blankChar() { return '\\a'; }\n\n/**\n * The char that indicates the beginning of a flag.  Defaults to '-', but\n * clients can define TCLAP_FLAGSTARTCHAR to override.\n */\n#ifndef TCLAP_FLAGSTARTCHAR\n#define TCLAP_FLAGSTARTCHAR '-'\n#endif\n    static char flagStartChar() { return TCLAP_FLAGSTARTCHAR; }\n\n/**\n * The sting that indicates the beginning of a flag.  Defaults to \"-\", but\n * clients can define TCLAP_FLAGSTARTSTRING to override. Should be the same\n * as TCLAP_FLAGSTARTCHAR.\n */\n#ifndef TCLAP_FLAGSTARTSTRING\n#define TCLAP_FLAGSTARTSTRING \"-\"\n#endif\n    static const std::string flagStartString() { return TCLAP_FLAGSTARTSTRING; }\n\n/**\n * The sting that indicates the beginning of a name.  Defaults to \"--\", but\n *  clients can define TCLAP_NAMESTARTSTRING to override.\n */\n#ifndef TCLAP_NAMESTARTSTRING\n#define TCLAP_NAMESTARTSTRING \"--\"\n#endif\n    static const std::string nameStartString() { return TCLAP_NAMESTARTSTRING; }\n\n    /**\n     * The name used to identify the ignore rest argument.\n     */\n    static const std::string ignoreNameString() { return \"ignore_rest\"; }\n\n    /**\n     * Sets the delimiter for all arguments.\n     * \\param c - The character that delimits flags/names from values.\n     */\n    static void setDelimiter(char c) { delimiterRef() = c; }\n\n    /**\n     * Pure virtual method meant to handle the parsing and value assignment\n     * of the string on the command line.\n     * \\param i - Pointer the the current argument in the list.\n     * \\param args - Mutable list of strings. What is\n     * passed in from main.\n     */\n    virtual bool processArg(int *i, std::vector<std::string> &args) = 0;\n\n    /**\n     * Operator ==.\n     * Equality operator. Must be virtual to handle unlabeled args.\n     * \\param a - The Arg to be compared to this.\n     */\n    virtual bool operator==(const Arg &a) const;\n\n    /**\n     * Returns the argument flag.\n     */\n    const std::string &getFlag() const;\n\n    /**\n     * Returns the argument name.\n     */\n    const std::string &getName() const;\n\n    /**\n     * Returns the argument description.\n     */\n    std::string getDescription() const { return getDescription(_required); }\n\n    /**\n     * Returns the argument description.\n     *\n     * @param required if the argument should be treated as\n     * required when described.\n     */\n    std::string getDescription(bool required) const {\n        return (required ? \"(\" + _requireLabel + \") \" : \"\") + _description;\n    }\n\n    /**\n     * Indicates whether the argument is required.\n     */\n    virtual bool isRequired() const;\n\n    /**\n     * Indicates whether a value must be specified for argument.\n     */\n    bool isValueRequired() const;\n\n    /**\n     * Indicates whether the argument has already been set.  Only true\n     * if the arg has been matched on the command line.\n     */\n    bool isSet() const;\n\n    /**\n     * Returns the value specified to set this flag (like -a or --all).\n     */\n    const std::string &setBy() const { return _setBy; }\n\n    /**\n     * Indicates whether the argument can be ignored, if desired.\n     */\n    bool isIgnoreable() const;\n\n    /**\n     * A method that tests whether a string matches this argument.\n     * This is generally called by the processArg() method.  This\n     * method could be re-implemented by a child to change how\n     * arguments are specified on the command line.\n     * \\param s - The string to be compared to the flag/name to determine\n     * whether the arg matches.\n     */\n    virtual bool argMatches(const std::string &s) const;\n\n    /**\n     * Returns a simple string representation of the argument.\n     * Primarily for debugging.\n     */\n    virtual std::string toString() const;\n\n    /**\n     * Returns a short ID for the usage.\n     * \\param valueId - The value used in the id.\n     */\n    virtual std::string shortID(const std::string &valueId = \"val\") const;\n\n    /**\n     * Returns a long ID for the usage.\n     * \\param valueId - The value used in the id.\n     */\n    virtual std::string longID(const std::string &valueId = \"val\") const;\n\n    /**\n     * Trims a value off of the flag.\n     * \\param flag - The string from which the flag and value will be\n     * trimmed. Contains the flag once the value has been trimmed.\n     * \\param value - Where the value trimmed from the string will\n     * be stored.\n     */\n    virtual void trimFlag(std::string &flag, std::string &value) const;\n\n    /**\n     * Checks whether a given string has blank chars, indicating that\n     * it is a combined SwitchArg.  If so, return true, otherwise return\n     * false.\n     * \\param s - string to be checked.\n     */\n    bool _hasBlanks(const std::string &s) const;\n\n    /**\n     * Used for MultiArgs to determine whether args can still be\n     * set.\n     */\n    virtual bool allowMore();\n\n    /**\n     * Use by output classes to determine whether an Arg accepts\n     * multiple values.\n     */\n    virtual bool acceptsMultipleValues();\n\n    /**\n     * Clears the Arg object and allows it to be reused by new\n     * command lines.\n     */\n    virtual void reset();\n\n    /**\n     * Hide this argument from the help output (e.g., when\n     * specifying the --help flag or on error.\n     */\n    virtual void hideFromHelp(bool hide = true) { _visibleInHelp = !hide; }\n\n    /**\n     * Returns true if this Arg is visible in the help output.\n     */\n    virtual bool visibleInHelp() const { return _visibleInHelp; }\n\n    virtual bool hasLabel() const { return true; }\n};\n\n/**\n * Typedef of an Arg list iterator.\n */\ntypedef std::list<Arg *>::const_iterator ArgListIterator;\n\n/**\n * Typedef of an Arg vector iterator.\n */\ntypedef std::vector<Arg *>::const_iterator ArgVectorIterator;\n\n/**\n * Typedef of a Visitor list iterator.\n */\ntypedef std::list<Visitor *>::const_iterator VisitorListIterator;\n\n/*\n * Extract a value of type T from it's string representation contained\n * in strVal. The ValueLike parameter used to select the correct\n * specialization of ExtractValue depending on the value traits of T.\n * ValueLike traits use operator>> to assign the value from strVal.\n */\ntemplate <typename T>\nvoid ExtractValue(T &destVal, const std::string &strVal, ValueLike vl) {\n    static_cast<void>(vl);  // Avoid warning about unused vl\n    istringstream is(strVal.c_str());\n\n    int valuesRead = 0;\n    while (is.good()) {\n        if (is.peek() != EOF)\n#ifdef TCLAP_SETBASE_ZERO\n            is >> std::setbase(0) >> destVal;\n#else\n            is >> destVal;\n#endif\n        else\n            break;\n\n        valuesRead++;\n    }\n\n    if (is.fail())\n        throw(\n            ArgParseException(\"Couldn't read argument value \"\n                              \"from string '\" +\n                              strVal + \"'\"));\n\n    if (valuesRead > 1)\n        throw(\n            ArgParseException(\"More than one valid value parsed from \"\n                              \"string '\" +\n                              strVal + \"'\"));\n}\n\n/*\n * Extract a value of type T from it's string representation contained\n * in strVal. The ValueLike parameter used to select the correct\n * specialization of ExtractValue depending on the value traits of T.\n * StringLike uses assignment (operator=) to assign from strVal.\n */\ntemplate <typename T>\nvoid ExtractValue(T &destVal, const std::string &strVal, StringLike sl) {\n    static_cast<void>(sl);  // Avoid warning about unused sl\n    SetString(destVal, strVal);\n}\n\n//////////////////////////////////////////////////////////////////////\n// BEGIN Arg.cpp\n//////////////////////////////////////////////////////////////////////\n\ninline Arg::Arg(const std::string &flag, const std::string &name,\n                const std::string &desc, bool req, bool valreq, Visitor *v)\n    : _flag(flag),\n      _name(name),\n      _description(desc),\n      _required(req),\n      _requireLabel(\"required\"),\n      _valueRequired(valreq),\n      _alreadySet(false),\n      _setBy(),\n      _visitor(v),\n      _ignoreable(true),\n      _acceptsMultipleValues(false),\n      _visibleInHelp(true) {\n    if (_flag.length() > 1)\n        throw(SpecificationException(\n            \"Argument flag can only be one character long\", toString()));\n\n    if (_name != ignoreNameString() &&\n        (_flag == Arg::flagStartString() || _flag == Arg::nameStartString() ||\n         _flag == \" \"))\n        throw(SpecificationException(\n            \"Argument flag cannot be either '\" + Arg::flagStartString() +\n                \"' or '\" + Arg::nameStartString() + \"' or a space.\",\n            toString()));\n\n    if ((_name.substr(0, Arg::flagStartString().length()) ==\n         Arg::flagStartString()) ||\n        (_name.substr(0, Arg::nameStartString().length()) ==\n         Arg::nameStartString()) ||\n        (_name.find(\" \", 0) != std::string::npos))\n        throw(SpecificationException(\"Argument name begin with either '\" +\n                                         Arg::flagStartString() + \"' or '\" +\n                                         Arg::nameStartString() + \"' or space.\",\n                                     toString()));\n}\n\ninline Arg::~Arg() {}\n\ninline std::string Arg::shortID(const std::string &valueId) const {\n    std::string id = \"\";\n\n    if (_flag != \"\")\n        id = Arg::flagStartString() + _flag;\n    else\n        id = Arg::nameStartString() + _name;\n\n    if (_valueRequired) id += std::string(1, Arg::delimiter()) + valueId;\n\n    return id;\n}\n\ninline std::string Arg::longID(const std::string &valueId) const {\n    std::string id = \"\";\n\n    if (_flag != \"\") {\n        id += Arg::flagStartString() + _flag;\n\n        if (_valueRequired) id += std::string(1, Arg::delimiter()) + valueId;\n\n        id += \",  \";\n    }\n\n    id += Arg::nameStartString() + _name;\n\n    if (_valueRequired) id += std::string(1, Arg::delimiter()) + valueId;\n\n    return id;\n}\n\ninline bool Arg::operator==(const Arg &a) const {\n    if ((_flag != \"\" && _flag == a._flag) || _name == a._name)\n        return true;\n    else\n        return false;\n}\n\ninline const std::string &Arg::getFlag() const { return _flag; }\n\ninline const std::string &Arg::getName() const { return _name; }\n\ninline bool Arg::isRequired() const { return _required; }\n\ninline bool Arg::isValueRequired() const { return _valueRequired; }\n\ninline bool Arg::isSet() const { return _alreadySet; }\n\ninline bool Arg::isIgnoreable() const { return _ignoreable; }\n\ninline bool Arg::argMatches(const std::string &argFlag) const {\n    if ((argFlag == Arg::flagStartString() + _flag && _flag != \"\") ||\n        argFlag == Arg::nameStartString() + _name)\n        return true;\n    else\n        return false;\n}\n\ninline std::string Arg::toString() const {\n    std::string s = \"\";\n\n    if (_flag != \"\") s += Arg::flagStartString() + _flag + \" \";\n\n    s += \"(\" + Arg::nameStartString() + _name + \")\";\n\n    return s;\n}\n\ninline void Arg::_checkWithVisitor() const {\n    if (_visitor != NULL) _visitor->visit();\n}\n\n/**\n * Implementation of trimFlag.\n */\ninline void Arg::trimFlag(std::string &flag, std::string &value) const {\n    int stop = 0;\n    for (int i = 0; static_cast<unsigned int>(i) < flag.length(); i++)\n        if (flag[i] == Arg::delimiter()) {\n            stop = i;\n            break;\n        }\n\n    if (stop > 1) {\n        value = flag.substr(stop + 1);\n        flag = flag.substr(0, stop);\n    }\n}\n\n/**\n * Implementation of _hasBlanks.\n */\ninline bool Arg::_hasBlanks(const std::string &s) const {\n    for (int i = 1; static_cast<unsigned int>(i) < s.length(); i++)\n        if (s[i] == Arg::blankChar()) return true;\n\n    return false;\n}\n\n/**\n * Overridden by Args that need to added to the end of the list.\n */\ninline void Arg::addToList(std::list<Arg *> &argList) const {\n    argList.push_front(const_cast<Arg *>(this));\n}\n\ninline bool Arg::allowMore() { return false; }\n\ninline bool Arg::acceptsMultipleValues() { return _acceptsMultipleValues; }\n\ninline void Arg::reset() { _alreadySet = false; }\n\n//////////////////////////////////////////////////////////////////////\n// END Arg.cpp\n//////////////////////////////////////////////////////////////////////\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_ARG_H\n"
  },
  {
    "path": "lib/tclap/ArgContainer.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  ArgContainer.h\n *\n *  Copyright (c) 2018 Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_ARG_CONTAINER_H\n#define TCLAP_ARG_CONTAINER_H\n\nnamespace TCLAP {\n\nclass Arg;\n\n/**\n * Interface that allows adding an Arg to a \"container\".\n *\n * A container does not have to be a container in the C++ standard\n * library sense, just something that wants to hold on to references\n * to Arg's. The container does not own the added Arg's and it is the\n * user's responsibility to ensure the life time (scope) of the Arg's\n * outlives any operations on the container.\n */\nclass ArgContainer {\npublic:\n    virtual ~ArgContainer() {}\n\n    /**\n     * Adds an argument. Ownership is not transfered.\n     * \\param a - Argument to be added.\n     */\n    virtual ArgContainer &add(Arg &a) = 0;\n\n    /**\n     * Adds an argument. Ownership is not transfered.\n     * \\param a - Argument to be added.\n     */\n    virtual ArgContainer &add(Arg *a) = 0;\n};\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_ARG_CONTAINER_H\n"
  },
  {
    "path": "lib/tclap/ArgException.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  ArgException.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2017 Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_ARG_EXCEPTION_H\n#define TCLAP_ARG_EXCEPTION_H\n\n#include <exception>\n#include <string>\n\nnamespace TCLAP {\n\n/**\n * A simple class that defines and argument exception.  Should be caught\n * whenever a CmdLine is created and parsed.\n */\nclass ArgException : public std::exception {\npublic:\n    /**\n     * Constructor.\n     * \\param text - The text of the exception.\n     * \\param id - The text identifying the argument source.\n     * \\param td - Text describing the type of ArgException it is.\n     * of the exception.\n     */\n    ArgException(const std::string &text = \"undefined exception\",\n                 const std::string &id = \"undefined\",\n                 const std::string &td = \"Generic ArgException\")\n        : std::exception(),\n          _errorText(text),\n          _argId(id),\n          _typeDescription(td) {}\n\n    /**\n     * Destructor.\n     */\n    virtual ~ArgException() throw() {}\n\n    /**\n     * Returns the error text.\n     */\n    std::string error() const { return (_errorText); }\n\n    /**\n     * Returns the argument id.\n     */\n    std::string argId() const {\n        if (_argId == \"undefined\")\n            return \" \";\n        else\n            return (\"Argument: \" + _argId);\n    }\n\n    /**\n     * Returns the arg id and error text.\n     */\n    const char *what() const throw() {\n        static std::string ex;\n        ex = _argId + \" -- \" + _errorText;\n        return ex.c_str();\n    }\n\n    /**\n     * Returns the type of the exception.  Used to explain and distinguish\n     * between different child exceptions.\n     */\n    std::string typeDescription() const { return _typeDescription; }\n\nprivate:\n    /**\n     * The text of the exception message.\n     */\n    std::string _errorText;\n\n    /**\n     * The argument related to this exception.\n     */\n    std::string _argId;\n\n    /**\n     * Describes the type of the exception.  Used to distinguish\n     * between different child exceptions.\n     */\n    std::string _typeDescription;\n};\n\n/**\n * Thrown from within the child Arg classes when it fails to properly\n * parse the argument it has been passed.\n */\nclass ArgParseException : public ArgException {\npublic:\n    /**\n     * Constructor.\n     * \\param text - The text of the exception.\n     * \\param id - The text identifying the argument source\n     * of the exception.\n     */\n    ArgParseException(const std::string &text = \"undefined exception\",\n                      const std::string &id = \"undefined\")\n        : ArgException(text, id,\n                       std::string(\"Exception found while parsing \") +\n                           std::string(\"the value the Arg has been passed.\")) {}\n};\n\n/**\n * Thrown from CmdLine when the arguments on the command line are not\n * properly specified, e.g. too many arguments, required argument missing, etc.\n */\nclass CmdLineParseException : public ArgException {\npublic:\n    /**\n     * Constructor.\n     * \\param text - The text of the exception.\n     * \\param id - The text identifying the argument source\n     * of the exception.\n     */\n    CmdLineParseException(const std::string &text = \"undefined exception\",\n                          const std::string &id = \"undefined\")\n        : ArgException(text, id,\n                       std::string(\"Exception found when the values \") +\n                           std::string(\"on the command line do not meet \") +\n                           std::string(\"the requirements of the defined \") +\n                           std::string(\"Args.\")) {}\n};\n\n/**\n * Thrown from Arg and CmdLine when an Arg is improperly specified, e.g.\n * same flag as another Arg, same name, etc.\n */\nclass SpecificationException : public ArgException {\npublic:\n    /**\n     * Constructor.\n     * \\param text - The text of the exception.\n     * \\param id - The text identifying the argument source\n     * of the exception.\n     */\n    SpecificationException(const std::string &text = \"undefined exception\",\n                           const std::string &id = \"undefined\")\n        : ArgException(text, id,\n                       std::string(\"Exception found when an Arg object \") +\n                           std::string(\"is improperly defined by the \") +\n                           std::string(\"developer.\")) {}\n};\n\n/**\n * Thrown when TCLAP thinks the program should exit.\n *\n * For example after parse error this exception will be thrown (and\n * normally caught). This allows any resource to be clened properly\n * before exit.\n *\n * If exception handling is disabled (CmdLine::setExceptionHandling),\n * this exception will propagate to the call site, allowing the\n * program to catch it and avoid program termination, or do it's own\n * cleanup. See for example, https://sourceforge.net/p/tclap/bugs/29.\n */\nclass ExitException {\npublic:\n    explicit ExitException(int estat) : _estat(estat) {}\n\n    int getExitStatus() const { return _estat; }\n\nprivate:\n    int _estat;\n};\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_ARG_EXCEPTION_H\n"
  },
  {
    "path": "lib/tclap/ArgGroup.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  ArgGroup.h\n *\n *  Copyright (c) 2017 Google LLC.\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_ARG_GROUP_H\n#define TCLAP_ARG_GROUP_H\n\n#include <tclap/Arg.h>\n#include <tclap/ArgContainer.h>\n#include <tclap/CmdLineInterface.h>\n\n#include <list>\n#include <string>\n\nnamespace TCLAP {\n\n/**\n * ArgGroup is the base class for implementing groups of arguments\n * that are mutually exclusive (it replaces the deprecated xor\n * handler). It is not expected to be used directly, rather one of the\n * EitherOf or OneOf derived classes are used.\n */\nclass ArgGroup : public ArgContainer {\npublic:\n    typedef std::list<Arg *> Container;\n    typedef Container::iterator iterator;\n    typedef Container::const_iterator const_iterator;\n\n    virtual ~ArgGroup() {}\n\n    /// Add an argument to this arg group\n    virtual ArgContainer &add(Arg &arg) { return add(&arg); }\n\n    /// Add an argument to this arg group\n    virtual ArgContainer &add(Arg *arg);\n\n    /**\n     * Validates that the constraints of the ArgGroup are satisfied.\n     *\n     * @internal\n     * Throws an CmdLineParseException if there is an issue (except\n     * missing required argument, in which case true is returned).\n     *\n     * @retval true iff a required argument was missing.\n     */\n    virtual bool validate() = 0;\n\n    /**\n     * Returns true if this argument group is required\n     *\n     * @internal\n     */\n    virtual bool isRequired() const = 0;\n\n    /**\n     * Returns true if this argument group is exclusive.\n     *\n     * @internal\n     * Being exclusive means there is a constraint so that some\n     * arguments cannot be selected at the same time.\n     */\n    virtual bool isExclusive() const = 0;\n\n    /**\n     * Used by the parser to connect itself to this arg group.\n     *\n     * @internal\n     * This is needed so that existing and subsequently added args (in\n     * this arg group) are also added to the parser (and checked for\n     * consistency with other args).\n     */\n    void setParser(CmdLineInterface &parser) {\n        if (_parser) {\n            throw SpecificationException(\"Arg group can have only one parser\");\n        }\n\n        _parser = &parser;\n        for (iterator it = begin(); it != end(); ++it) {\n            parser.addToArgList(*it);\n        }\n    }\n\n    /**\n     * If arguments in this group should show up as grouped in help.\n     */\n    virtual bool showAsGroup() const { return true; }\n\n    /// Returns the argument group's name.\n    const std::string getName() const;\n\n    iterator begin() { return _args.begin(); }\n    iterator end() { return _args.end(); }\n    const_iterator begin() const { return _args.begin(); }\n    const_iterator end() const { return _args.end(); }\n\nprotected:\n    // No direct instantiation\n    ArgGroup() : _parser(0), _args() {}\n\nprivate:\n    explicit ArgGroup(const ArgGroup &);\n    ArgGroup &operator=(const ArgGroup &);  // no copy\n\nprotected:\n    CmdLineInterface *_parser;\n    Container _args;\n};\n\n/**\n * Implements common functionality for exclusive argument groups.\n *\n * @internal\n */\nclass ExclusiveArgGroup : public ArgGroup {\npublic:\n    inline bool validate();\n    bool isExclusive() const { return true; }\n    ArgContainer &add(Arg &arg) { return add(&arg); }\n    ArgContainer &add(Arg *arg) {\n        if (arg->isRequired()) {\n            throw SpecificationException(\n                \"Required arguments are not allowed\"\n                \" in an exclusive grouping.\",\n                arg->longID());\n        }\n\n        return ArgGroup::add(arg);\n    }\n\nprotected:\n    ExclusiveArgGroup() {}\n    explicit ExclusiveArgGroup(CmdLineInterface &parser) { parser.add(*this); }\n};\n\n/**\n * Implements a group of arguments where at most one can be selected.\n */\nclass EitherOf : public ExclusiveArgGroup {\npublic:\n    EitherOf() {}\n    explicit EitherOf(CmdLineInterface &parser) : ExclusiveArgGroup(parser) {}\n\n    bool isRequired() const { return false; }\n};\n\n/**\n * Implements a group of arguments where exactly one must be\n * selected. This corresponds to the deprecated \"xoradd\".\n */\nclass OneOf : public ExclusiveArgGroup {\npublic:\n    OneOf() {}\n    explicit OneOf(CmdLineInterface &parser) : ExclusiveArgGroup(parser) {}\n\n    bool isRequired() const { return true; }\n};\n\n/**\n * Implements a group of arguments where any combination is possible\n * (including all or none). This is mostly used in case one optional\n * argument allows additional arguments to be specified (for example\n * [-c [-de] [-n <int>]]).\n */\nclass AnyOf : public ArgGroup {\npublic:\n    AnyOf() {}\n    explicit AnyOf(CmdLineInterface &parser) { parser.add(*this); }\n\n    bool validate() { return false; /* All good */ }\n    bool isExclusive() const { return false; }\n    bool isRequired() const { return false; }\n};\n\ninline ArgContainer &ArgGroup::add(Arg *arg) {\n    for (iterator it = begin(); it != end(); it++) {\n        if (*arg == **it) {\n            throw SpecificationException(\n                \"Argument with same flag/name already exists!\", arg->longID());\n        }\n    }\n\n    _args.push_back(arg);\n    if (_parser) {\n        _parser->addToArgList(arg);\n    }\n\n    return *this;\n}\n\ninline bool ExclusiveArgGroup::validate() {\n    Arg *arg = NULL;\n    std::string flag;\n\n    for (const_iterator it = begin(); it != end(); ++it) {\n        if ((*it)->isSet()) {\n            if (arg != NULL && !(*arg == **it)) {\n                // We found a matching argument, but one was\n                // already found previously.\n                throw CmdLineParseException(\n                    \"Only one is allowed.\",\n                    flag + \" AND \" + (*it)->setBy() + \" provided.\");\n            }\n\n            arg = *it;\n            flag = arg->setBy();\n        }\n    }\n\n    return isRequired() && !arg;\n}\n\ninline const std::string ArgGroup::getName() const {\n    std::string name;\n    std::string sep = \"{\";  // TODO: this should change for\n                            // non-exclusive arg groups\n    for (const_iterator it = begin(); it != end(); ++it) {\n        name += sep + (*it)->getName();\n        sep = \" | \";\n    }\n\n    return name + '}';\n}\n\n/// @internal\ninline int CountVisibleArgs(const ArgGroup &g) {\n    int visible = 0;\n    for (ArgGroup::const_iterator it = g.begin(); it != g.end(); ++it) {\n        if ((*it)->visibleInHelp()) {\n            visible++;\n        }\n    }\n\n    return visible;\n}\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_ARG_GROUP_H\n"
  },
  {
    "path": "lib/tclap/ArgTraits.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  ArgTraits.h\n *\n *  Copyright (c) 2007, Daniel Aarno, Michael E. Smoot .\n *  Copyright (c) 2017 Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n// This is an internal tclap file, you should probably not have to\n// include this directly\n\n#ifndef TCLAP_ARG_TRAITS_H\n#define TCLAP_ARG_TRAITS_H\n\nnamespace TCLAP {\n\n// We use two empty structs to get compile type specialization\n// function to work\n\n/**\n * A value like argument value type is a value that can be set using\n * operator>>. This is the default value type.\n */\nstruct ValueLike {\n    typedef ValueLike ValueCategory;\n    virtual ~ValueLike() {}\n};\n\n/**\n * A string like argument value type is a value that can be set using\n * operator=(string). Useful if the value type contains spaces which\n * will be broken up into individual tokens by operator>>.\n */\nstruct StringLike {\n    virtual ~StringLike() {}\n};\n\n/**\n * A class can inherit from this object to make it have string like\n * traits. This is a compile time thing and does not add any overhead\n * to the inherenting class.\n */\nstruct StringLikeTrait {\n    typedef StringLike ValueCategory;\n    virtual ~StringLikeTrait() {}\n};\n\n/**\n * A class can inherit from this object to make it have value like\n * traits. This is a compile time thing and does not add any overhead\n * to the inherenting class.\n */\nstruct ValueLikeTrait {\n    typedef ValueLike ValueCategory;\n    virtual ~ValueLikeTrait() {}\n};\n\n/**\n * Arg traits are used to get compile type specialization when parsing\n * argument values. Using an ArgTraits you can specify the way that\n * values gets assigned to any particular type during parsing. The two\n * supported types are StringLike and ValueLike. ValueLike is the\n * default and means that operator>> will be used to assign values to\n * the type.\n */\ntemplate <typename T>\nclass ArgTraits {\n    // This is a bit silly, but what we want to do is:\n    // 1) If there exists a specialization of ArgTraits for type X,\n    // use it.\n    //\n    // 2) If no specialization exists but X has the typename\n    // X::ValueCategory, use the specialization for X::ValueCategory.\n    //\n    // 3) If neither (1) nor (2) defines the trait, use the default\n    // which is ValueLike.\n\n    // This is the \"how\":\n    //\n    // test<T>(0) (where 0 is the NULL ptr) will match\n    // test(typename C::ValueCategory*) iff type T has the\n    // corresponding typedef. If it does not test(...) will be\n    // matched. This allows us to determine if T::ValueCategory\n    // exists by checking the sizeof for the test function (return\n    // value must have different sizeof).\n    template <typename C>\n    static short test(typename C::ValueCategory *);  // NOLINT\n    template <typename C>\n    static long test(...);                                             // NOLINT\n    static const bool hasTrait = sizeof(test<T>(0)) == sizeof(short);  // NOLINT\n\n    template <typename C, bool>\n    struct DefaultArgTrait {\n        typedef ValueLike ValueCategory;\n    };\n\n    template <typename C>\n    struct DefaultArgTrait<C, true> {\n        typedef typename C::ValueCategory ValueCategory;\n    };\n\npublic:\n    typedef typename DefaultArgTrait<T, hasTrait>::ValueCategory ValueCategory;\n};\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_ARG_TRAITS_H\n"
  },
  {
    "path": "lib/tclap/CmdLine.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  CmdLine.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.\n *  Copyright (c) 2018, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_CMD_LINE_H\n#define TCLAP_CMD_LINE_H\n\n#include <tclap/MultiSwitchArg.h>\n#include <tclap/SwitchArg.h>\n#include <tclap/UnlabeledMultiArg.h>\n#include <tclap/UnlabeledValueArg.h>\n\n#include <tclap/HelpVisitor.h>\n#include <tclap/IgnoreRestVisitor.h>\n#include <tclap/VersionVisitor.h>\n\n#include <tclap/CmdLineOutput.h>\n#include <tclap/StdOutput.h>\n\n#include <tclap/Constraint.h>\n#include <tclap/ValuesConstraint.h>\n\n#include <tclap/ArgGroup.h>\n#include <tclap/DeferDelete.h>\n\n#include <algorithm>\n#include <cstdlib>\n#include <iomanip>\n#include <iostream>\n#include <list>\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\nclass StandaloneArgs : public AnyOf {\npublic:\n    StandaloneArgs() {}\n\n    ArgContainer &add(Arg *arg) {\n        // std::cerr << \"Adding \" << arg->getName() << \" to StandaloneArgs\\n\";\n        for (iterator it = begin(); it != end(); it++) {\n            if (*arg == **it) {\n                throw SpecificationException(\n                    \"Argument with same flag/name already exists!\",\n                    arg->longID());\n            }\n        }\n\n        _args.push_back(arg);\n\n        return *this;\n    }\n\n    bool showAsGroup() const { return false; }\n};\n\n/**\n * The base class that manages the command line definition and passes\n * along the parsing to the appropriate Arg classes.\n */\nclass CmdLine : public CmdLineInterface {\nprotected:\n    /**\n     * The list of arguments that will be tested against the\n     * command line.\n     */\n    std::list<Arg *> _argList;\n\n    StandaloneArgs _standaloneArgs;\n    StandaloneArgs _autoArgs;  // --help, --version, etc\n\n    /**\n     * Some args have set constraints on them (i.e., exactly or at\n     * most one must be specified.\n     */\n    std::list<ArgGroup *> _argGroups;\n\n    /**\n     * The name of the program.  Set to argv[0].\n     */\n    std::string _progName;\n\n    /**\n     * A message used to describe the program.  Used in the usage output.\n     */\n    std::string _message;\n\n    /**\n     * The version to be displayed with the --version switch.\n     */\n    std::string _version;\n\n    /**\n     * The number of arguments that are required to be present on\n     * the command line. This is set dynamically, based on the\n     * Args added to the CmdLine object.\n     */\n    int _numRequired;\n\n    /**\n     * The character that is used to separate the argument flag/name\n     * from the value.  Defaults to ' ' (space).\n     */\n    char _delimiter;\n\n    /**\n     * Add pointers that should be deleted as part of cleanup when\n     * this object is destroyed.\n     * @internal.\n     */\n    DeferDelete _deleteOnExit;\n\n    /**\n     * Default output handler if nothing is specified.\n     */\n    StdOutput _defaultOutput;\n\n    /**\n     * Object that handles all output for the CmdLine.\n     */\n    CmdLineOutput *_output;\n\n    /**\n     * Should CmdLine handle parsing exceptions internally?\n     */\n    bool _handleExceptions;\n\n    /**\n     * Throws an exception listing the missing args.\n     */\n    void missingArgsException(const std::list<ArgGroup *> &missing);\n\n    /**\n     * Checks whether a name/flag string matches entirely matches\n     * the Arg::blankChar.  Used when multiple switches are combined\n     * into a single argument.\n     * \\param s - The message to be used in the usage.\n     */\n    bool _emptyCombined(const std::string &s);\n\nprivate:\n    /**\n     * Prevent accidental copying.\n     */\n    CmdLine(const CmdLine &rhs);\n    CmdLine &operator=(const CmdLine &rhs);\n\n    /**\n     * Encapsulates the code common to the constructors\n     * (which is all of it).\n     */\n    void _constructor();\n\n    /**\n     * Whether or not to automatically create help and version switches.\n     */\n    bool _helpAndVersion;\n\n    /**\n     * Whether or not to ignore unmatched args.\n     */\n    bool _ignoreUnmatched;\n\n    /**\n     * Ignoring arguments (e.g., after we have seen \"--\")\n     */\n    bool _ignoring;\n\npublic:\n    /**\n     * Command line constructor. Defines how the arguments will be\n     * parsed.\n     * \\param message - The message to be used in the usage\n     * output.\n     * \\param delimiter - The character that is used to separate\n     * the argument flag/name from the value.  Defaults to ' ' (space).\n     * \\param version - The version number to be used in the\n     * --version switch.\n     * \\param helpAndVersion - Whether or not to create the Help and\n     * Version switches. Defaults to true.\n     */\n    CmdLine(const std::string &message, const char delimiter = ' ',\n            const std::string &version = \"none\", bool helpAndVersion = true);\n\n    /**\n     * Deletes any resources allocated by a CmdLine object.\n     */\n    virtual ~CmdLine() {}\n\n    /**\n     * Adds an argument to the list of arguments to be parsed.\n     *\n     * @param a - Argument to be added.\n     * @retval A reference to this so that add calls can be chained\n     */\n    ArgContainer &add(Arg &a);\n\n    /**\n     * An alternative add.  Functionally identical.\n     *\n     * @param a - Argument to be added.\n     * @retval A reference to this so that add calls can be chained\n     */\n    ArgContainer &add(Arg *a);\n\n    /**\n     * Adds an argument group to the list of arguments to be parsed.\n     *\n     * All arguments in the group are added and the ArgGroup\n     * object will validate that the input matches its\n     * constraints.\n     *\n     * @param args - Argument group to be added.\n     * @retval A reference to this so that add calls can be chained\n     */\n    ArgContainer &add(ArgGroup &args);\n\n    // Internal, do not use\n    void addToArgList(Arg *a);\n\n    /**\n     * \\deprecated Use OneOf instead.\n     */\n    void xorAdd(Arg &a, Arg &b);\n\n    /**\n     * \\deprecated Use OneOf instead.\n     */\n    void xorAdd(const std::vector<Arg *> &xors);\n\n    /**\n     * Parses the command line.\n     * \\param argc - Number of arguments.\n     * \\param argv - Array of arguments.\n     */\n    void parse(int argc, const char *const *argv);\n\n    /**\n     * Parses the command line.\n     * \\param args - A vector of strings representing the args.\n     * args[0] is still the program name.\n     */\n    void parse(std::vector<std::string> &args);\n\n    void setOutput(CmdLineOutput *co);\n\n    std::string getVersion() const { return _version; }\n\n    std::string getProgramName() const { return _progName; }\n\n    // TOOD: Get rid of getArgList\n    std::list<Arg *> getArgList() const { return _argList; }\n    std::list<ArgGroup *> getArgGroups() {\n        std::list<ArgGroup *> groups = _argGroups;\n        groups.push_back(&_autoArgs);\n        return groups;\n    }\n\n    char getDelimiter() const { return _delimiter; }\n    std::string getMessage() const { return _message; }\n    bool hasHelpAndVersion() const { return _helpAndVersion; }\n\n    /**\n     * Disables or enables CmdLine's internal parsing exception handling.\n     *\n     * @param state Should CmdLine handle parsing exceptions internally?\n     */\n    void setExceptionHandling(const bool state);\n\n    /**\n     * Returns the current state of the internal exception handling.\n     *\n     * @retval true Parsing exceptions are handled internally.\n     * @retval false Parsing exceptions are propagated to the caller.\n     */\n    bool hasExceptionHandling() const { return _handleExceptions; }\n\n    /**\n     * Allows the CmdLine object to be reused.\n     */\n    void reset();\n\n    /**\n     * Allows unmatched args to be ignored. By default false.\n     *\n     * @param ignore If true the cmdline will ignore any unmatched args\n     * and if false it will behave as normal.\n     */\n    void ignoreUnmatched(const bool ignore);\n\n    void beginIgnoring() { _ignoring = true; }\n    bool ignoreRest() { return _ignoring; }\n};\n\n///////////////////////////////////////////////////////////////////////////////\n// Begin CmdLine.cpp\n///////////////////////////////////////////////////////////////////////////////\n\ninline CmdLine::CmdLine(const std::string &m, char delim, const std::string &v,\n                        bool help)\n    : _argList(),\n      _standaloneArgs(),\n      _autoArgs(),\n      _argGroups(),\n      _progName(\"not_set_yet\"),\n      _message(m),\n      _version(v),\n      _numRequired(0),\n      _delimiter(delim),\n      _deleteOnExit(),\n      _defaultOutput(),\n      _output(&_defaultOutput),\n      _handleExceptions(true),\n      _helpAndVersion(help),\n      _ignoreUnmatched(false),\n      _ignoring(false) {\n    _constructor();\n}\n\ninline void CmdLine::_constructor() {\n    Arg::setDelimiter(_delimiter);\n\n    Visitor *v;\n    add(_standaloneArgs);\n    _autoArgs.setParser(*this);\n    // add(_autoArgs);\n\n    v = new IgnoreRestVisitor(*this);\n    SwitchArg *ignore = new SwitchArg(\n        Arg::flagStartString(), Arg::ignoreNameString(),\n        \"Ignores the rest of the labeled arguments following this flag.\", false,\n        v);\n    _deleteOnExit(ignore);\n    _deleteOnExit(v);\n    _autoArgs.add(ignore);\n    addToArgList(ignore);\n\n    if (_helpAndVersion) {\n        v = new HelpVisitor(this, &_output);\n        SwitchArg *help = new SwitchArg(\n            \"h\", \"help\", \"Displays usage information and exits.\", false, v);\n        _deleteOnExit(help);\n        _deleteOnExit(v);\n\n        v = new VersionVisitor(this, &_output);\n        SwitchArg *vers = new SwitchArg(\n            \"\", \"version\", \"Displays version information and exits.\", false, v);\n        _deleteOnExit(vers);\n        _deleteOnExit(v);\n\n        // A bit of a hack on the order to make tests easier to fix,\n        // to be reverted\n        _autoArgs.add(vers);\n        addToArgList(vers);\n        _autoArgs.add(help);\n        addToArgList(help);\n    }\n}\n\ninline void CmdLine::xorAdd(const std::vector<Arg *> &args) {\n    OneOf *group = new OneOf(*this);\n    _deleteOnExit(group);\n\n    for (std::vector<Arg *>::const_iterator it = args.begin(); it != args.end();\n         ++it) {\n        group->add(**it);\n    }\n}\n\ninline void CmdLine::xorAdd(Arg &a, Arg &b) {\n    std::vector<Arg *> ors;\n    ors.push_back(&a);\n    ors.push_back(&b);\n    xorAdd(ors);\n}\n\ninline ArgContainer &CmdLine::add(ArgGroup &args) {\n    args.setParser(*this);\n    _argGroups.push_back(&args);\n\n    return *this;\n}\n\ninline ArgContainer &CmdLine::add(Arg &a) { return add(&a); }\n\n// TODO: Rename this to something smarter or refactor this logic so\n// it's not needed.\ninline void CmdLine::addToArgList(Arg *a) {\n    for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++)\n        if (*a == *(*it))\n            throw(SpecificationException(\n                \"Argument with same flag/name already exists!\", a->longID()));\n\n    a->addToList(_argList);\n\n    if (a->isRequired()) _numRequired++;\n}\n\ninline ArgContainer &CmdLine::add(Arg *a) {\n    addToArgList(a);\n    _standaloneArgs.add(a);\n\n    return *this;\n}\n\ninline void CmdLine::parse(int argc, const char *const *argv) {\n    // this step is necessary so that we have easy access to\n    // mutable strings.\n    std::vector<std::string> args;\n    for (int i = 0; i < argc; i++) args.push_back(argv[i]);\n\n    parse(args);\n}\n\ninline void CmdLine::parse(std::vector<std::string> &args) {\n    bool shouldExit = false;\n    int estat = 0;\n\n    try {\n        if (args.empty()) {\n            // Workaround for some platforms such as consoles\n            _progName = \"program\";\n        } else {\n            // TODO(macbishop): Maybe store the full name somewhere?\n            _progName = basename(args.front());\n            args.erase(args.begin());\n        }\n\n        int requiredCount = 0;\n        std::list<ArgGroup *> missingArgGroups;\n\n        // Check that the right amount of arguments are provided for\n        // each ArgGroup, and if there are any required arguments\n        // missing, store them for later. Other errors will cause an\n        // exception to be thrown and parse will exit early.\n        /*\n        for (std::list<ArgGroup*>::iterator it = _argGroups.begin();\n             it != _argGroups.end(); ++it) {\n            bool missingRequired = (*it)->validate(args);\n            if (missingRequired) {\n                missingArgGroups.push_back(*it);\n            }\n        }\n        */\n\n        for (int i = 0; static_cast<unsigned int>(i) < args.size(); i++) {\n            bool matched = false;\n            for (ArgListIterator it = _argList.begin(); it != _argList.end();\n                 it++) {\n                Arg &arg = **it;\n                // We check if the argument was already set (e.g., for\n                // a Multi-Arg) since then we don't want to count it\n                // as required again. This is a hack/workaround to\n                // make isRequired() imutable so it can be used to\n                // display help correctly (also it's a good idea).\n                //\n                // TODO: This logic should probably be refactored to\n                // remove this logic from here.\n                bool alreadySet = arg.isSet();\n                bool ignore = arg.isIgnoreable() && ignoreRest();\n                if (!ignore && arg.processArg(&i, args)) {\n                    requiredCount += (!alreadySet && arg.isRequired()) ? 1 : 0;\n                    matched = true;\n                    break;\n                }\n            }\n\n            // checks to see if the argument is an empty combined\n            // switch and if so, then we've actually matched it\n            if (!matched && _emptyCombined(args[i])) matched = true;\n\n            if (!matched && !ignoreRest() && !_ignoreUnmatched)\n                throw(\n                    CmdLineParseException(\"Couldn't find match \"\n                                          \"for argument\",\n                                          args[i]));\n        }\n\n        // Once all arguments have been parsed, check that we don't\n        // violate any constraints.\n        for (std::list<ArgGroup *>::iterator it = _argGroups.begin();\n             it != _argGroups.end(); ++it) {\n            bool missingRequired = (*it)->validate();\n            if (missingRequired) {\n                missingArgGroups.push_back(*it);\n            }\n        }\n\n        if (requiredCount < _numRequired || !missingArgGroups.empty()) {\n            missingArgsException(missingArgGroups);\n        }\n\n        if (requiredCount > _numRequired) {\n            throw(CmdLineParseException(\"Too many arguments!\"));\n        }\n    } catch (ArgException &e) {\n        // If we're not handling the exceptions, rethrow.\n        if (!_handleExceptions) {\n            throw;\n        }\n\n        try {\n            _output->failure(*this, e);\n        } catch (ExitException &ee) {\n            estat = ee.getExitStatus();\n            shouldExit = true;\n        }\n    } catch (ExitException &ee) {\n        // If we're not handling the exceptions, rethrow.\n        if (!_handleExceptions) {\n            throw;\n        }\n\n        estat = ee.getExitStatus();\n        shouldExit = true;\n    }\n\n    if (shouldExit) exit(estat);\n}\n\ninline bool CmdLine::_emptyCombined(const std::string &s) {\n    if (s.length() > 0 && s[0] != Arg::flagStartChar()) return false;\n\n    for (int i = 1; static_cast<unsigned int>(i) < s.length(); i++)\n        if (s[i] != Arg::blankChar()) return false;\n\n    return true;\n}\n\ninline void CmdLine::missingArgsException(\n    const std::list<ArgGroup *> &missing) {\n    int count = 0;\n\n    std::string missingArgList;\n    for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++) {\n        if ((*it)->isRequired() && !(*it)->isSet()) {\n            missingArgList += (*it)->getName();\n            missingArgList += \", \";\n            count++;\n        }\n    }\n\n    for (std::list<ArgGroup *>::const_iterator it = missing.begin();\n         it != missing.end(); it++) {\n        missingArgList += (*it)->getName();\n        missingArgList += \", \";\n        count++;\n    }\n\n    missingArgList = missingArgList.substr(0, missingArgList.length() - 2);\n\n    std::string msg;\n    if (count > 1)\n        msg = \"Required arguments missing: \";\n    else\n        msg = \"Required argument missing: \";\n\n    msg += missingArgList;\n\n    throw(CmdLineParseException(msg));\n}\n\ninline void CmdLine::setOutput(CmdLineOutput *co) { _output = co; }\n\ninline void CmdLine::setExceptionHandling(const bool state) {\n    _handleExceptions = state;\n}\n\ninline void CmdLine::reset() {\n    // TODO: This is no longer correct (or perhaps we don't need \"reset\")\n    for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++)\n        (*it)->reset();\n\n    _progName.clear();\n}\n\ninline void CmdLine::ignoreUnmatched(const bool ignore) {\n    _ignoreUnmatched = ignore;\n}\n\n///////////////////////////////////////////////////////////////////////////////\n// End CmdLine.cpp\n///////////////////////////////////////////////////////////////////////////////\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_CMD_LINE_H\n"
  },
  {
    "path": "lib/tclap/CmdLineInterface.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  CmdLineInterface.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_CMD_LINE_INTERFACE_H\n#define TCLAP_CMD_LINE_INTERFACE_H\n\n#include <tclap/ArgContainer.h>\n\n#include <algorithm>\n#include <iostream>\n#include <list>\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\nclass Arg;\nclass ArgGroup;\nclass CmdLineOutput;\n\n/**\n * The base class that manages the command line definition and passes\n * along the parsing to the appropriate Arg classes.\n */\nclass CmdLineInterface : public ArgContainer {\npublic:\n    /**\n     * Destructor\n     */\n    virtual ~CmdLineInterface() {}\n\n    /**\n     * Adds an argument. Ownership is not transfered.\n     * @param a - Argument to be added.\n     * @retval A reference to this so that add calls can be chained\n     */\n    virtual ArgContainer &add(Arg &a) = 0;\n\n    /**\n     * Adds an argument. Ownership is not transfered.\n     * @param a - Argument to be added.\n     * @retval A reference to this so that add calls can be chained\n     */\n    virtual ArgContainer &add(Arg *a) = 0;\n\n    // TODO: Rename this to something smarter or refactor this logic so\n    // it's not needed.\n    // Internal - do not use\n    virtual void addToArgList(Arg *a) = 0;\n\n    /**\n     * Adds an argument group to the list of arguments to be parsed.\n     *\n     * All arguments in the group are added and the ArgGroup\n     * object will validate that the input matches its\n     * constraints.\n     *\n     * @param args - Argument group to be added.\n     * @retval A reference to this so that add calls can be chained\n     */\n    virtual ArgContainer &add(ArgGroup &args) = 0;\n\n    /**\n     * \\deprecated Use OneOf instead.\n     */\n    virtual void xorAdd(Arg &a, Arg &b) = 0;\n\n    /**\n     * \\deprecated Use OneOf instead.\n     */\n    virtual void xorAdd(const std::vector<Arg *> &xors) = 0;\n\n    /**\n     * Parses the command line.\n     * \\param argc - Number of arguments.\n     * \\param argv - Array of arguments.\n     */\n    virtual void parse(int argc, const char *const *argv) = 0;\n\n    /**\n     * Parses the command line.\n     * \\param args - A vector of strings representing the args.\n     * args[0] is still the program name.\n     */\n    void parse(std::vector<std::string> &args);\n\n    /**\n     * \\param co - CmdLineOutput object that we want to use instead.\n     */\n    virtual void setOutput(CmdLineOutput *co) = 0;\n\n    /**\n     * Returns the version string.\n     */\n    virtual std::string getVersion() const = 0;\n\n    /**\n     * Returns the program name string.\n     */\n    virtual std::string getProgramName() const = 0;\n\n    /**\n     * Returns the list of ArgGroups.\n     */\n    virtual std::list<ArgGroup *> getArgGroups() = 0;\n    virtual std::list<Arg *> getArgList() const = 0;  // TODO: get rid of this\n\n    /**\n     * Returns the delimiter string.\n     */\n    virtual char getDelimiter() const = 0;\n\n    /**\n     * Returns the message string.\n     */\n    virtual std::string getMessage() const = 0;\n\n    /**\n     * Indicates whether or not the help and version switches were created\n     * automatically.\n     */\n    virtual bool hasHelpAndVersion() const = 0;\n\n    /**\n     * Resets the instance as if it had just been constructed so that the\n     * instance can be reused.\n     */\n    virtual void reset() = 0;\n\n    /**\n     * Begin ignoring arguments since the \"--\" argument was specified.\n     * \\internal\n     */\n    virtual void beginIgnoring() = 0;\n\n    /**\n     * Whether to ignore the rest.\n     * \\internal\n     */\n    virtual bool ignoreRest() = 0;\n};\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_CMD_LINE_INTERFACE_H\n"
  },
  {
    "path": "lib/tclap/CmdLineOutput.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  CmdLineOutput.h\n *\n *  Copyright (c) 2004, Michael E. Smoot\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_CMD_LINE_OUTPUT_H\n#define TCLAP_CMD_LINE_OUTPUT_H\n\n#include <tclap/Arg.h>\n#include <tclap/ArgGroup.h>\n\n#include <algorithm>\n#include <iomanip>\n#include <iostream>\n#include <list>\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\nclass CmdLineInterface;\nclass ArgException;\n\n/**\n * The interface that any output object must implement.\n */\nclass CmdLineOutput {\npublic:\n    /**\n     * Virtual destructor.\n     */\n    virtual ~CmdLineOutput() {}\n\n    /**\n     * Generates some sort of output for the USAGE.\n     * \\param c - The CmdLine object the output is generated for.\n     */\n    virtual void usage(CmdLineInterface &c) = 0;\n\n    /**\n     * Generates some sort of output for the version.\n     * \\param c - The CmdLine object the output is generated for.\n     */\n    virtual void version(CmdLineInterface &c) = 0;\n\n    /**\n     * Generates some sort of output for a failure.\n     * \\param c - The CmdLine object the output is generated for.\n     * \\param e - The ArgException that caused the failure.\n     */\n    virtual void failure(CmdLineInterface &c, ArgException &e) = 0;\n};\n\ninline bool isInArgGroup(const Arg *arg, const std::list<ArgGroup *> &argSets) {\n    for (std::list<ArgGroup *>::const_iterator it = argSets.begin();\n         it != argSets.end(); ++it) {\n        if (std::find((*it)->begin(), (*it)->end(), arg) != (*it)->end()) {\n            return true;\n        }\n    }\n    return false;\n}\n\ninline void removeArgsInArgGroups(std::list<Arg *> &argList,\n                                  const std::list<ArgGroup *> &argSets) {\n    for (std::list<Arg *>::iterator it = argList.begin();\n         it != argList.end();) {\n        if (isInArgGroup(*it, argSets)) {\n            it = argList.erase(it);\n        } else {\n            ++it;\n        }\n    }\n}\n\ninline std::string basename(std::string s) {\n    // TODO(macbishop): See if we can make this more robust\n    size_t p = s.find_last_of(\"/\\\\\");\n    if (p != std::string::npos) {\n        s.erase(0, p + 1);\n    }\n\n    p = s.rfind(\".exe\");\n    if (p == s.length() - 4) {\n        s.erase(s.length() - 4);\n    }\n\n    return s;\n}\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_CMD_LINE_OUTPUT_H\n"
  },
  {
    "path": "lib/tclap/Constraint.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  Constraint.h\n *\n *  Copyright (c) 2005, Michael E. Smoot\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_CONSTRAINT_H\n#define TCLAP_CONSTRAINT_H\n\n#include <algorithm>\n#include <iomanip>\n#include <iostream>\n#include <list>\n#include <stdexcept>\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * The interface that defines the interaction between the Arg and Constraint.\n */\ntemplate <class T>\nclass Constraint {\npublic:\n    /**\n     * Returns a description of the Constraint.\n     */\n    virtual std::string description() const = 0;\n\n    /**\n     * Returns the short ID for the Constraint.\n     */\n    virtual std::string shortID() const = 0;\n\n    /**\n     * The method used to verify that the value parsed from the command\n     * line meets the constraint.\n     * \\param value - The value that will be checked.\n     */\n    virtual bool check(const T &value) const = 0;\n\n    /**\n     * Destructor.\n     * Silences warnings about Constraint being a base class with virtual\n     * functions but without a virtual destructor.\n     */\n    virtual ~Constraint() { ; }\n\n    static std::string shortID(Constraint<T> *constraint) {\n        if (!constraint)\n            throw std::logic_error(\n                \"Cannot create a ValueArg with a NULL constraint\");\n        return constraint->shortID();\n    }\n};\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_CONSTRAINT_H\n"
  },
  {
    "path": "lib/tclap/DeferDelete.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  DeferDelete.h\n *\n *  Copyright (c) 2020, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_DEFER_DELETE_H\n#define TCLAP_DEFER_DELETE_H\n\nnamespace TCLAP {\n\n/**\n * DeferDelete can be used by objects that need to allocate arbitrary other\n * objects to live for the duration of the first object. Any object\n * added to DeferDelete (by calling operator()) will be deleted when\n * the DeferDelete object is destroyed.\n */\nclass DeferDelete {\n    class DeletableBase {\n    public:\n        virtual ~DeletableBase() {}\n    };\n\n    template <typename T>\n    class Deletable : public DeletableBase {\n    public:\n        Deletable(T *o) : _o(o) {}\n        virtual ~Deletable() { delete _o; }\n\n    private:\n        Deletable(const Deletable<T> &) {}\n        Deletable<T> operator=(const Deletable<T> &) {}\n\n        T *_o;\n    };\n\n    std::list<DeletableBase *> _toBeDeleted;\n\npublic:\n    DeferDelete() : _toBeDeleted() {}\n    ~DeferDelete() {\n        for (std::list<DeletableBase *>::iterator it = _toBeDeleted.begin();\n             it != _toBeDeleted.end(); ++it) {\n            delete *it;\n        }\n    }\n\n    template <typename T>\n    void operator()(T *toDelete) {\n        _toBeDeleted.push_back(new Deletable<T>(toDelete));\n    }\n};\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_DEFER_DELETE_H\n"
  },
  {
    "path": "lib/tclap/DocBookOutput.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  DocBookOutput.h\n *\n *  Copyright (c) 2004, Michael E. Smoot\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_DOC_BOOK_OUTPUT_H\n#define TCLAP_DOC_BOOK_OUTPUT_H\n\n#include <tclap/Arg.h>\n#include <tclap/CmdLineInterface.h>\n#include <tclap/CmdLineOutput.h>\n\n#include <algorithm>\n#include <iostream>\n#include <list>\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * A class that generates DocBook output for usage() method for the\n * given CmdLine and its Args.\n */\nclass DocBookOutput : public CmdLineOutput {\npublic:\n    /**\n     * Prints the usage to stdout.  Can be overridden to\n     * produce alternative behavior.\n     * \\param c - The CmdLine object the output is generated for.\n     */\n    virtual void usage(CmdLineInterface &c);\n\n    /**\n     * Prints the version to stdout. Can be overridden\n     * to produce alternative behavior.\n     * \\param c - The CmdLine object the output is generated for.\n     */\n    virtual void version(CmdLineInterface &c);\n\n    /**\n     * Prints (to stderr) an error message, short usage\n     * Can be overridden to produce alternative behavior.\n     * \\param c - The CmdLine object the output is generated for.\n     * \\param e - The ArgException that caused the failure.\n     */\n    virtual void failure(CmdLineInterface &c, ArgException &e);\n\n    DocBookOutput() : theDelimiter('=') {}\n\nprotected:\n    /**\n     * Substitutes the char r for string x in string s.\n     * \\param s - The string to operate on.\n     * \\param r - The char to replace.\n     * \\param x - What to replace r with.\n     */\n    void substituteSpecialChars(std::string &s, char r,\n                                const std::string &x) const;\n    void removeChar(std::string &s, char r) const;\n\n    void printShortArg(Arg *it, bool required);\n    void printLongArg(const ArgGroup &it) const;\n\n    char theDelimiter;\n};\n\ninline void DocBookOutput::version(CmdLineInterface &_cmd) {\n    std::cout << _cmd.getVersion() << std::endl;\n}\n\nnamespace internal {\nconst char *GroupChoice(const ArgGroup &group) {\n    if (!group.showAsGroup()) {\n        return \"plain\";\n    }\n\n    if (group.isRequired()) {\n        return \"req\";\n    }\n\n    return \"opt\";\n}\n}  // namespace internal\n\ninline void DocBookOutput::usage(CmdLineInterface &_cmd) {\n    std::list<ArgGroup *> argSets = _cmd.getArgGroups();\n    std::string progName = _cmd.getProgramName();\n    std::string xversion = _cmd.getVersion();\n    theDelimiter = _cmd.getDelimiter();\n\n    std::cout << \"<?xml version='1.0'?>\\n\";\n    std::cout\n        << \"<!DOCTYPE refentry PUBLIC \\\"-//OASIS//DTD DocBook XML V4.2//EN\\\"\\n\";\n    std::cout\n        << \"\\t\\\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\\\">\\n\\n\";\n\n    std::cout << \"<refentry>\\n\";\n\n    std::cout << \"<refmeta>\\n\";\n    std::cout << \"<refentrytitle>\" << progName << \"</refentrytitle>\\n\";\n    std::cout << \"<manvolnum>1</manvolnum>\\n\";\n    std::cout << \"</refmeta>\\n\";\n\n    std::cout << \"<refnamediv>\\n\";\n    std::cout << \"<refname>\" << progName << \"</refname>\\n\";\n    std::cout << \"<refpurpose>\" << _cmd.getMessage() << \"</refpurpose>\\n\";\n    std::cout << \"</refnamediv>\\n\";\n\n    std::cout << \"<refsynopsisdiv>\\n\";\n    std::cout << \"<cmdsynopsis>\\n\";\n\n    std::cout << \"<command>\" << progName << \"</command>\\n\";\n\n    for (std::list<ArgGroup *>::iterator sit = argSets.begin();\n         sit != argSets.end(); ++sit) {\n        int visible = CountVisibleArgs(**sit);\n        if (visible > 1) {\n            std::cout << \"<group choice='\" << internal::GroupChoice(**sit)\n                      << \"'>\\n\";\n        }\n        for (ArgGroup::iterator it = (*sit)->begin(); it != (*sit)->end();\n             ++it) {\n            if (!(*it)->visibleInHelp()) {\n                continue;\n            }\n\n            printShortArg(*it, (*it)->isRequired() ||\n                                   (visible == 1 && (**sit).isRequired()));\n        }\n        if (visible > 1) {\n            std::cout << \"</group>\\n\";\n        }\n    }\n\n    std::cout << \"</cmdsynopsis>\\n\";\n    std::cout << \"</refsynopsisdiv>\\n\";\n\n    std::cout << \"<refsect1>\\n\";\n    std::cout << \"<title>Description</title>\\n\";\n    std::cout << \"<para>\\n\";\n    std::cout << _cmd.getMessage() << '\\n';\n    std::cout << \"</para>\\n\";\n    std::cout << \"</refsect1>\\n\";\n\n    std::cout << \"<refsect1>\\n\";\n    std::cout << \"<title>Options</title>\\n\";\n\n    std::cout << \"<variablelist>\\n\";\n\n    for (std::list<ArgGroup *>::iterator sit = argSets.begin();\n         sit != argSets.end(); ++sit) {\n        printLongArg(**sit);\n    }\n\n    std::cout << \"</variablelist>\\n\";\n    std::cout << \"</refsect1>\\n\";\n\n    std::cout << \"<refsect1>\\n\";\n    std::cout << \"<title>Version</title>\\n\";\n    std::cout << \"<para>\\n\";\n    std::cout << xversion << '\\n';\n    std::cout << \"</para>\\n\";\n    std::cout << \"</refsect1>\\n\";\n\n    std::cout << \"</refentry>\" << std::endl;\n}\n\ninline void DocBookOutput::failure(CmdLineInterface &_cmd, ArgException &e) {\n    static_cast<void>(_cmd);  // unused\n    std::cout << e.what() << std::endl;\n    throw ExitException(1);\n}\n\ninline void DocBookOutput::substituteSpecialChars(std::string &s, char r,\n                                                  const std::string &x) const {\n    size_t p;\n    while ((p = s.find_first_of(r)) != std::string::npos) {\n        s.erase(p, 1);\n        s.insert(p, x);\n    }\n}\n\ninline void DocBookOutput::removeChar(std::string &s, char r) const {\n    size_t p;\n    while ((p = s.find_first_of(r)) != std::string::npos) {\n        s.erase(p, 1);\n    }\n}\n\ninline void DocBookOutput::printShortArg(Arg *a, bool required) {\n    std::string lt = \"&lt;\";\n    std::string gt = \"&gt;\";\n\n    std::string id = a->shortID();\n    substituteSpecialChars(id, '<', lt);\n    substituteSpecialChars(id, '>', gt);\n    removeChar(id, '[');\n    removeChar(id, ']');\n\n    std::string choice = \"opt\";\n    if (required) {\n        choice = \"plain\";\n    }\n\n    std::cout << \"<arg choice='\" << choice << '\\'';\n    if (a->acceptsMultipleValues()) std::cout << \" rep='repeat'\";\n\n    std::cout << '>';\n    if (!a->getFlag().empty())\n        std::cout << a->flagStartChar() << a->getFlag();\n    else\n        std::cout << a->nameStartString() << a->getName();\n    if (a->isValueRequired()) {\n        std::string arg = a->shortID();\n        removeChar(arg, '[');\n        removeChar(arg, ']');\n        removeChar(arg, '<');\n        removeChar(arg, '>');\n        removeChar(arg, '.');\n        arg.erase(0, arg.find_last_of(theDelimiter) + 1);\n        std::cout << theDelimiter;\n        std::cout << \"<replaceable>\" << arg << \"</replaceable>\";\n    }\n    std::cout << \"</arg>\" << std::endl;\n}\n\ninline void DocBookOutput::printLongArg(const ArgGroup &group) const {\n    const std::string lt = \"&lt;\";\n    const std::string gt = \"&gt;\";\n\n    bool forceRequired = group.isRequired() && CountVisibleArgs(group) == 1;\n    for (ArgGroup::const_iterator it = group.begin(); it != group.end(); ++it) {\n        Arg &a = **it;\n        if (!a.visibleInHelp()) {\n            continue;\n        }\n\n        std::string desc = a.getDescription(forceRequired || a.isRequired());\n        substituteSpecialChars(desc, '<', lt);\n        substituteSpecialChars(desc, '>', gt);\n\n        std::cout << \"<varlistentry>\\n\";\n\n        if (!a.getFlag().empty()) {\n            std::cout << \"<term>\\n\";\n            std::cout << \"<option>\";\n            std::cout << a.flagStartChar() << a.getFlag();\n            std::cout << \"</option>\\n\";\n            std::cout << \"</term>\\n\";\n        }\n\n        std::cout << \"<term>\\n\";\n        std::cout << \"<option>\";\n        std::cout << a.nameStartString() << a.getName();\n        if (a.isValueRequired()) {\n            std::string arg = a.shortID();\n            removeChar(arg, '[');\n            removeChar(arg, ']');\n            removeChar(arg, '<');\n            removeChar(arg, '>');\n            removeChar(arg, '.');\n            arg.erase(0, arg.find_last_of(theDelimiter) + 1);\n            std::cout << theDelimiter;\n            std::cout << \"<replaceable>\" << arg << \"</replaceable>\";\n        }\n\n        std::cout << \"</option>\\n\";\n        std::cout << \"</term>\\n\";\n\n        std::cout << \"<listitem>\\n\";\n        std::cout << \"<para>\\n\";\n        std::cout << desc << '\\n';\n        std::cout << \"</para>\\n\";\n        std::cout << \"</listitem>\\n\";\n\n        std::cout << \"</varlistentry>\" << std::endl;\n    }\n}\n\n}  // namespace TCLAP\n#endif  // TCLAP_DOC_BOOK_OUTPUT_H\n"
  },
  {
    "path": "lib/tclap/HelpVisitor.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  HelpVisitor.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_HELP_VISITOR_H\n#define TCLAP_HELP_VISITOR_H\n\n#include <tclap/CmdLineInterface.h>\n#include <tclap/CmdLineOutput.h>\n#include <tclap/Visitor.h>\n\nnamespace TCLAP {\n\n/**\n * A Visitor object that calls the usage method of the given CmdLineOutput\n * object for the specified CmdLine object.\n */\nclass HelpVisitor : public Visitor {\nprivate:\n    /**\n     * Prevent accidental copying.\n     */\n    HelpVisitor(const HelpVisitor &rhs);\n    HelpVisitor &operator=(const HelpVisitor &rhs);\n\nprotected:\n    /**\n     * The CmdLine the output will be generated for.\n     */\n    CmdLineInterface *_cmd;\n\n    /**\n     * The output object.\n     */\n    CmdLineOutput **_out;\n\npublic:\n    /**\n     * Constructor.\n     * \\param cmd - The CmdLine the output will be generated for.\n     * \\param out - The type of output.\n     */\n    HelpVisitor(CmdLineInterface *cmd, CmdLineOutput **out)\n        : Visitor(), _cmd(cmd), _out(out) {}\n\n    /**\n     * Calls the usage method of the CmdLineOutput for the\n     * specified CmdLine.\n     */\n    void visit() {\n        (*_out)->usage(*_cmd);\n        throw ExitException(0);\n    }\n};\n}  // namespace TCLAP\n\n#endif  // TCLAP_HELP_VISITOR_H\n"
  },
  {
    "path": "lib/tclap/IgnoreRestVisitor.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  IgnoreRestVisitor.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2020, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_IGNORE_REST_VISITOR_H\n#define TCLAP_IGNORE_REST_VISITOR_H\n\n#include <tclap/CmdLineInterface.h>\n#include <tclap/Visitor.h>\n\nnamespace TCLAP {\n\n/**\n * A Visitor that tells the CmdLine to begin ignoring arguments after\n * this one is parsed.\n */\nclass IgnoreRestVisitor : public Visitor {\npublic:\n    IgnoreRestVisitor(CmdLineInterface &cmdLine)\n        : Visitor(), cmdLine_(cmdLine) {}\n    void visit() { cmdLine_.beginIgnoring(); }\n\nprivate:\n    CmdLineInterface &cmdLine_;\n};\n}  // namespace TCLAP\n\n#endif  // TCLAP_IGNORE_REST_VISITOR_H\n"
  },
  {
    "path": "lib/tclap/MultiArg.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  MultiArg.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_MULTI_ARG_H\n#define TCLAP_MULTI_ARG_H\n\n#include <tclap/Arg.h>\n#include <tclap/Constraint.h>\n\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n/**\n * An argument that allows multiple values of type T to be specified.  Very\n * similar to a ValueArg, except a vector of values will be returned\n * instead of just one.\n */\ntemplate <class T>\nclass MultiArg : public Arg {\npublic:\n    typedef std::vector<T> container_type;\n    typedef typename container_type::iterator iterator;\n    typedef typename container_type::const_iterator const_iterator;\n\nprotected:\n    /**\n     * The list of values parsed from the CmdLine.\n     */\n    std::vector<T> _values;\n\n    /**\n     * The description of type T to be used in the usage.\n     */\n    std::string _typeDesc;\n\n    /**\n     * A list of constraint on this Arg.\n     */\n    Constraint<T> *_constraint;\n\n    /**\n     * Extracts the value from the string.\n     * Attempts to parse string as type T, if this fails an exception\n     * is thrown.\n     * \\param val - The string to be read.\n     */\n    void _extractValue(const std::string &val);\n\n    /**\n     * Used by MultiArg to decide whether to keep parsing for this\n     * arg.\n     */\n    bool _allowMore;\n\npublic:\n    /**\n     * Constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param typeDesc - A short, human readable description of the\n     * type that this object expects.  This is used in the generation\n     * of the USAGE statement.  The goal is to be helpful to the end user\n     * of the program.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    MultiArg(const std::string &flag, const std::string &name,\n             const std::string &desc, bool req, const std::string &typeDesc,\n             Visitor *v = NULL);\n\n    /**\n     * Constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param typeDesc - A short, human readable description of the\n     * type that this object expects.  This is used in the generation\n     * of the USAGE statement.  The goal is to be helpful to the end user\n     * of the program.\n     * \\param parser - A CmdLine parser object to add this Arg to\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    MultiArg(const std::string &flag, const std::string &name,\n             const std::string &desc, bool req, const std::string &typeDesc,\n             ArgContainer &parser, Visitor *v = NULL);\n\n    /**\n     * Constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param constraint - A pointer to a Constraint object used\n     * to constrain this Arg.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    MultiArg(const std::string &flag, const std::string &name,\n             const std::string &desc, bool req, Constraint<T> *constraint,\n             Visitor *v = NULL);\n\n    /**\n     * Constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param constraint - A pointer to a Constraint object used\n     * to constrain this Arg.\n     * \\param parser - A CmdLine parser object to add this Arg to\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    MultiArg(const std::string &flag, const std::string &name,\n             const std::string &desc, bool req, Constraint<T> *constraint,\n             ArgContainer &parser, Visitor *v = NULL);\n\n    /**\n     * Handles the processing of the argument.\n     * This re-implements the Arg version of this method to set the\n     * _value of the argument appropriately.  It knows the difference\n     * between labeled and unlabeled.\n     * \\param i - Pointer the the current argument in the list.\n     * \\param args - Mutable list of strings. Passed from main().\n     */\n    virtual bool processArg(int *i, std::vector<std::string> &args);\n\n    /**\n     * Returns a vector of type T containing the values parsed from\n     * the command line.\n     */\n    const std::vector<T> &getValue() const { return _values; }\n\n    /**\n     * Returns an iterator over the values parsed from the command\n     * line.\n     */\n    const_iterator begin() const { return _values.begin(); }\n\n    /**\n     * Returns the end of the values parsed from the command\n     * line.\n     */\n    const_iterator end() const { return _values.end(); }\n\n    /**\n     * Returns the a short id string.  Used in the usage.\n     * \\param val - value to be used.\n     */\n    virtual std::string shortID(const std::string &val = \"val\") const;\n\n    /**\n     * Returns the a long id string.  Used in the usage.\n     * \\param val - value to be used.\n     */\n    virtual std::string longID(const std::string &val = \"val\") const;\n\n    virtual bool allowMore();\n\n    virtual void reset();\n\nprivate:\n    /**\n     * Prevent accidental copying\n     */\n    MultiArg<T>(const MultiArg<T> &rhs);\n    MultiArg<T> &operator=(const MultiArg<T> &rhs);\n};\n\ntemplate <class T>\nMultiArg<T>::MultiArg(const std::string &flag, const std::string &name,\n                      const std::string &desc, bool req,\n                      const std::string &typeDesc, Visitor *v)\n    : Arg(flag, name, desc, req, true, v),\n      _values(std::vector<T>()),\n      _typeDesc(typeDesc),\n      _constraint(NULL),\n      _allowMore(false) {\n    _acceptsMultipleValues = true;\n}\n\ntemplate <class T>\nMultiArg<T>::MultiArg(const std::string &flag, const std::string &name,\n                      const std::string &desc, bool req,\n                      const std::string &typeDesc, ArgContainer &parser,\n                      Visitor *v)\n    : Arg(flag, name, desc, req, true, v),\n      _values(std::vector<T>()),\n      _typeDesc(typeDesc),\n      _constraint(NULL),\n      _allowMore(false) {\n    parser.add(this);\n    _acceptsMultipleValues = true;\n}\n\n/**\n *\n */\ntemplate <class T>\nMultiArg<T>::MultiArg(const std::string &flag, const std::string &name,\n                      const std::string &desc, bool req,\n                      Constraint<T> *constraint, Visitor *v)\n    : Arg(flag, name, desc, req, true, v),\n      _values(std::vector<T>()),\n      _typeDesc(Constraint<T>::shortID(constraint)),\n      _constraint(constraint),\n      _allowMore(false) {\n    _acceptsMultipleValues = true;\n}\n\ntemplate <class T>\nMultiArg<T>::MultiArg(const std::string &flag, const std::string &name,\n                      const std::string &desc, bool req,\n                      Constraint<T> *constraint, ArgContainer &parser,\n                      Visitor *v)\n    : Arg(flag, name, desc, req, true, v),\n      _values(std::vector<T>()),\n      _typeDesc(Constraint<T>::shortID(constraint)),\n      _constraint(constraint),\n      _allowMore(false) {\n    parser.add(this);\n    _acceptsMultipleValues = true;\n}\n\ntemplate <class T>\nbool MultiArg<T>::processArg(int *i, std::vector<std::string> &args) {\n    if (_hasBlanks(args[*i])) return false;\n\n    std::string flag = args[*i];\n    std::string value = \"\";\n\n    trimFlag(flag, value);\n\n    if (argMatches(flag)) {\n        if (Arg::delimiter() != ' ' && value == \"\")\n            throw(ArgParseException(\n                \"Couldn't find delimiter for this argument!\", toString()));\n\n        // always take the first one, regardless of start string\n        if (value == \"\") {\n            (*i)++;\n            if (static_cast<unsigned int>(*i) < args.size())\n                _extractValue(args[*i]);\n            else\n                throw(ArgParseException(\"Missing a value for this argument!\",\n                                        toString()));\n        } else {\n            _extractValue(value);\n        }\n\n        _alreadySet = true;\n        _setBy = flag;\n        _checkWithVisitor();\n\n        return true;\n    } else {\n        return false;\n    }\n}\n\n/**\n *\n */\ntemplate <class T>\nstd::string MultiArg<T>::shortID(const std::string &val) const {\n    static_cast<void>(val);  // Ignore input, don't warn\n    return Arg::shortID(\"<\" + _typeDesc + \">\") + \" ...\";\n}\n\n/**\n *\n */\ntemplate <class T>\nstd::string MultiArg<T>::longID(const std::string &val) const {\n    static_cast<void>(val);  // Ignore input, don't warn\n    return Arg::longID(\"<\" + _typeDesc + \">\") + \"  (accepted multiple times)\";\n}\n\ntemplate <class T>\nvoid MultiArg<T>::_extractValue(const std::string &val) {\n    try {\n        T tmp;\n        ExtractValue(tmp, val, typename ArgTraits<T>::ValueCategory());\n        _values.push_back(tmp);\n    } catch (ArgParseException &e) {\n        throw ArgParseException(e.error(), toString());\n    }\n\n    if (_constraint != NULL)\n        if (!_constraint->check(_values.back()))\n            throw(CmdLineParseException(\n                \"Value '\" + val +\n                    \"' does not meet constraint: \" + _constraint->description(),\n                toString()));\n}\n\ntemplate <class T>\nbool MultiArg<T>::allowMore() {\n    bool am = _allowMore;\n    _allowMore = true;\n    return am;\n}\n\ntemplate <class T>\nvoid MultiArg<T>::reset() {\n    Arg::reset();\n    _values.clear();\n}\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_MULTI_ARG_H\n"
  },
  {
    "path": "lib/tclap/MultiSwitchArg.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  MultiSwitchArg.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.\n *  Copyright (c) 2005, Michael E. Smoot, Daniel Aarno, Erik Zeek.\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_MULTI_SWITCH_ARG_H\n#define TCLAP_MULTI_SWITCH_ARG_H\n\n#include <tclap/SwitchArg.h>\n\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * A multiple switch argument.  If the switch is set on the command line, then\n * the getValue method will return the number of times the switch appears.\n */\nclass MultiSwitchArg : public SwitchArg {\nprotected:\n    /**\n     * The value of the switch.\n     */\n    int _value;\n\n    /**\n     * Used to support the reset() method so that ValueArg can be\n     * reset to their constructed value.\n     */\n    int _default;\n\npublic:\n    /**\n     * MultiSwitchArg constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param init - Optional. The initial/default value of this Arg.\n     * Defaults to 0.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    MultiSwitchArg(const std::string &flag, const std::string &name,\n                   const std::string &desc, int init = 0, Visitor *v = NULL);\n\n    /**\n     * MultiSwitchArg constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param parser - A CmdLine parser object to add this Arg to\n     * \\param init - Optional. The initial/default value of this Arg.\n     * Defaults to 0.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    MultiSwitchArg(const std::string &flag, const std::string &name,\n                   const std::string &desc, ArgContainer &parser, int init = 0,\n                   Visitor *v = NULL);\n\n    /**\n     * Handles the processing of the argument.\n     * This re-implements the SwitchArg version of this method to set the\n     * _value of the argument appropriately.\n     * \\param i - Pointer the the current argument in the list.\n     * \\param args - Mutable list of strings. Passed\n     * in from main().\n     */\n    virtual bool processArg(int *i, std::vector<std::string> &args);\n\n    /**\n     * Returns int, the number of times the switch has been set.\n     */\n    int getValue() const { return _value; }\n\n    /**\n     * Returns the shortID for this Arg.\n     */\n    std::string shortID(const std::string &val) const;\n\n    /**\n     * Returns the longID for this Arg.\n     */\n    std::string longID(const std::string &val) const;\n\n    void reset();\n};\n\ninline MultiSwitchArg::MultiSwitchArg(const std::string &flag,\n                                      const std::string &name,\n                                      const std::string &desc, int init,\n                                      Visitor *v)\n    : SwitchArg(flag, name, desc, false, v), _value(init), _default(init) {}\n\ninline MultiSwitchArg::MultiSwitchArg(const std::string &flag,\n                                      const std::string &name,\n                                      const std::string &desc,\n                                      ArgContainer &parser, int init,\n                                      Visitor *v)\n    : SwitchArg(flag, name, desc, false, v), _value(init), _default(init) {\n    parser.add(this);\n}\n\ninline bool MultiSwitchArg::processArg(int *i, std::vector<std::string> &args) {\n    if (argMatches(args[*i])) {\n        // so the isSet() method will work\n        _alreadySet = true;\n        _setBy = args[*i];\n\n        // Matched argument: increment value.\n        ++_value;\n\n        _checkWithVisitor();\n\n        return true;\n    } else if (combinedSwitchesMatch(args[*i])) {\n        // so the isSet() method will work\n        _alreadySet = true;\n\n        // Matched argument: increment value.\n        ++_value;\n\n        // Check for more in argument and increment value.\n        while (combinedSwitchesMatch(args[*i])) ++_value;\n\n        _checkWithVisitor();\n\n        return false;\n    } else {\n        return false;\n    }\n}\n\ninline std::string MultiSwitchArg::shortID(const std::string &val) const {\n    return Arg::shortID(val) + \" ...\";\n}\n\ninline std::string MultiSwitchArg::longID(const std::string &val) const {\n    return Arg::longID(val) + \"  (accepted multiple times)\";\n}\n\ninline void MultiSwitchArg::reset() {\n    MultiSwitchArg::_value = MultiSwitchArg::_default;\n}\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_MULTI_SWITCH_ARG_H\n"
  },
  {
    "path": "lib/tclap/OptionalUnlabeledTracker.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  OptionalUnlabeledTracker.h\n *\n *  Copyright (c) 2005, Michael E. Smoot .\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_OPTIONAL_UNLABELED_TRACKER_H\n#define TCLAP_OPTIONAL_UNLABELED_TRACKER_H\n\n#include <string>\n\nnamespace TCLAP {\n\nclass OptionalUnlabeledTracker {\npublic:\n    static void check(bool req, const std::string &argName);\n\n    static void gotOptional() { alreadyOptionalRef() = true; }\n\n    static bool &alreadyOptional() { return alreadyOptionalRef(); }\n\nprivate:\n    static bool &alreadyOptionalRef() {\n        static bool ct = false;\n        return ct;\n    }\n};\n\ninline void OptionalUnlabeledTracker::check(bool req,\n                                            const std::string &argName) {\n    if (OptionalUnlabeledTracker::alreadyOptional())\n        throw(SpecificationException(\n            \"You can't specify ANY Unlabeled Arg following an optional \"\n            \"Unlabeled Arg\",\n            argName));\n\n    if (!req) OptionalUnlabeledTracker::gotOptional();\n}\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_OPTIONAL_UNLABELED_TRACKER_H\n"
  },
  {
    "path": "lib/tclap/README",
    "content": "TCLAP - Templatized Command Line Argument Parser\n\nThis is a simple C++ library that facilitates parsing command line\narguments in a type independent manner. It doesn't conform exactly\nto either the GNU or POSIX standards, although it is close. See\ndocs/manual.html for descriptions of how things work or look at the\nsimple examples in the examples dir.\n\nAny and all feedback is welcome at https://sf.net/p/tclap/discussion/\n"
  },
  {
    "path": "lib/tclap/StandardTraits.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  StandardTraits.h\n *\n *  Copyright (c) 2007, Daniel Aarno, Michael E. Smoot .\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n// This is an internal tclap file, you should probably not have to\n// include this directly\n\n#ifndef TCLAP_STANDARD_TRAITS_H\n#define TCLAP_STANDARD_TRAITS_H\n\n#include <string>\n\n// If Microsoft has already typedef'd wchar_t as an unsigned\n// short, then compiles will break because it's as if we're\n// creating ArgTraits twice for unsigned short. Thus...\n#ifdef _MSC_VER\n#ifndef _NATIVE_WCHAR_T_DEFINED\n#define TCLAP_DONT_DECLARE_WCHAR_T_ARGTRAITS\n#endif\n#endif\n\nnamespace TCLAP {\n\n// Integer types (signed, unsigned and bool) and floating point types all\n// have value-like semantics.\n\n// Strings have string like argument traits.\ntemplate <>\nstruct ArgTraits<std::string> {\n    typedef StringLike ValueCategory;\n};\n\ntemplate <typename T>\nvoid SetString(T &dst, const std::string &src) {\n    dst = src;\n}\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_STANDARD_TRAITS_H\n"
  },
  {
    "path": "lib/tclap/StdOutput.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  StdOutput.h\n *\n *  Copyright (c) 2004, Michael E. Smoot\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_STD_OUTPUT_H\n#define TCLAP_STD_OUTPUT_H\n\n#include <tclap/Arg.h>\n#include <tclap/ArgGroup.h>\n#include <tclap/CmdLineInterface.h>\n#include <tclap/CmdLineOutput.h>\n\n#include <algorithm>\n#include <cctype>\n#include <iostream>\n#include <list>\n#include <string>\n#include <utility>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * A class that isolates any output from the CmdLine object so that it\n * may be easily modified.\n */\nclass StdOutput : public CmdLineOutput {\npublic:\n    /**\n     * Prints the usage to stdout.  Can be overridden to\n     * produce alternative behavior.\n     * \\param c - The CmdLine object the output is generated for.\n     */\n    virtual void usage(CmdLineInterface &c);\n\n    /**\n     * Prints the version to stdout. Can be overridden\n     * to produce alternative behavior.\n     * \\param c - The CmdLine object the output is generated for.\n     */\n    virtual void version(CmdLineInterface &c);\n\n    /**\n     * Prints (to stderr) an error message, short usage\n     * Can be overridden to produce alternative behavior.\n     * \\param c - The CmdLine object the output is generated for.\n     * \\param e - The ArgException that caused the failure.\n     */\n    virtual void failure(CmdLineInterface &c, ArgException &e);\n\nprotected:\n    /**\n     * Writes a brief usage message with short args.\n     * \\param c - The CmdLine object the output is generated for.\n     * \\param os - The stream to write the message to.\n     */\n    void _shortUsage(CmdLineInterface &c, std::ostream &os) const;\n\n    /**\n     * Writes a longer usage message with long and short args,\n     * provides descriptions and prints message.\n     * \\param c - The CmdLine object the output is generated for.\n     * \\param os - The stream to write the message to.\n     */\n    void _longUsage(CmdLineInterface &c, std::ostream &os) const;\n\n    /**\n     * This function inserts line breaks and indents long strings\n     * according the  params input. It will only break lines at spaces,\n     * commas and pipes.\n     * \\param os - The stream to be printed to.\n     * \\param s - The string to be printed.\n     * \\param maxWidth - The maxWidth allowed for the output line.\n     * \\param indentSpaces - The number of spaces to indent the first line.\n     * \\param secondLineOffset - The number of spaces to indent the second\n     * and all subsequent lines in addition to indentSpaces.\n     */\n    void spacePrint(std::ostream &os, const std::string &s, int maxWidth,\n                    int indentSpaces, int secondLineOffset) const;\n};\n\ninline void StdOutput::version(CmdLineInterface &_cmd) {\n    std::string progName = _cmd.getProgramName();\n    std::string xversion = _cmd.getVersion();\n\n    std::cout << std::endl\n              << progName << \"  version: \" << xversion << std::endl\n              << std::endl;\n}\n\ninline void StdOutput::usage(CmdLineInterface &_cmd) {\n    std::cout << std::endl << \"USAGE: \" << std::endl << std::endl;\n\n    _shortUsage(_cmd, std::cout);\n\n    std::cout << std::endl << std::endl << \"Where: \" << std::endl << std::endl;\n\n    _longUsage(_cmd, std::cout);\n\n    std::cout << std::endl;\n}\n\ninline void StdOutput::failure(CmdLineInterface &_cmd, ArgException &e) {\n    std::string progName = _cmd.getProgramName();\n\n    std::cerr << \"PARSE ERROR: \" << e.argId() << std::endl\n              << \"             \" << e.error() << std::endl\n              << std::endl;\n\n    if (_cmd.hasHelpAndVersion()) {\n        std::cerr << \"Brief USAGE: \" << std::endl;\n\n        _shortUsage(_cmd, std::cerr);\n\n        std::cerr << std::endl\n                  << \"For complete USAGE and HELP type: \" << std::endl\n                  << \"   \" << progName << \" \" << Arg::nameStartString()\n                  << \"help\" << std::endl\n                  << std::endl;\n    } else {\n        usage(_cmd);\n    }\n\n    throw ExitException(1);\n}\n\n// TODO: Remove this\ninline void removeChar(std::string &s, char r) {\n    size_t p;\n    while ((p = s.find_first_of(r)) != std::string::npos) {\n        s.erase(p, 1);\n    }\n}\n\ninline bool cmpSwitch(const char &a, const char &b) {\n    int lowa = std::tolower(a);\n    int lowb = std::tolower(b);\n\n    if (lowa == lowb) {\n        return a < b;\n    }\n\n    return lowa < lowb;\n}\n\nnamespace internal {\ninline bool IsVisibleShortSwitch(const Arg &arg) {\n    return !(arg.getName() == Arg::ignoreNameString() ||\n             arg.isValueRequired() || arg.getFlag() == \"\") &&\n           arg.visibleInHelp();\n}\n\ninline bool IsVisibleLongSwitch(const Arg &arg) {\n    return (arg.getName() != Arg::ignoreNameString() &&\n            !arg.isValueRequired() && arg.getFlag() == \"\" &&\n            arg.visibleInHelp());\n}\n\ninline bool IsVisibleOption(const Arg &arg) {\n    return (arg.getName() != Arg::ignoreNameString() && arg.isValueRequired() &&\n            arg.hasLabel() && arg.visibleInHelp());\n}\n\ninline bool CompareShortID(const Arg *a, const Arg *b) {\n    if (a->getFlag() == \"\" && b->getFlag() != \"\") {\n        return false;\n    }\n    if (b->getFlag() == \"\" && a->getFlag() != \"\") {\n        return true;\n    }\n\n    return a->shortID() < b->shortID();\n}\n\n// TODO: Fix me not to put --gopt before -f\ninline bool CompareOptions(std::pair<const Arg *, bool> a,\n                           std::pair<const Arg *, bool> b) {\n    // First optional, then required\n    if (!a.second && b.second) {\n        return true;\n    }\n    if (a.second && !b.second) {\n        return false;\n    }\n\n    return CompareShortID(a.first, b.first);\n}\n}  // namespace internal\n\n/**\n * Usage statements should look like the manual pages.  Options w/o\n * operands come first, in alphabetical order inside a single set of\n * braces, upper case before lower case (AaBbCc...).  Next are options\n * with operands, in the same order, each in braces.  Then required\n * arguments in the order they are specified, followed by optional\n * arguments in the order they are specified.  A bar (`|') separates\n * either/or options/arguments, and multiple options/arguments which\n * are specified together are placed in a single set of braces.\n *\n * Use getprogname() instead of hardcoding the program name.\n *\n * \"usage: f [-aDde] [-b b_arg] [-m m_arg] req1 req2 [opt1 [opt2]]\\n\"\n * \"usage: f [-a | -b] [-c [-de] [-n number]]\\n\"\n */\ninline void StdOutput::_shortUsage(CmdLineInterface &_cmd,\n                                   std::ostream &os) const {\n    std::list<ArgGroup *> argSets = _cmd.getArgGroups();\n\n    std::ostringstream outp;\n    outp << _cmd.getProgramName() + \" \";\n\n    std::string switches = Arg::flagStartString();\n\n    std::list<ArgGroup *> exclusiveGroups;\n    std::list<ArgGroup *> nonExclusiveGroups;\n    for (std::list<ArgGroup *>::iterator sit = argSets.begin();\n         sit != argSets.end(); ++sit) {\n        if (CountVisibleArgs(**sit) <= 0) {\n            continue;\n        }\n\n        if ((*sit)->isExclusive()) {\n            exclusiveGroups.push_back(*sit);\n        } else {\n            nonExclusiveGroups.push_back(*sit);\n        }\n    }\n\n    // Move \"exclusive groups\" that have at most a single item to\n    // non-exclusive groups as exclusivit doesn't make sense with a\n    // single option. This can happen if args are hidden in help for\n    // example.\n    for (std::list<ArgGroup *>::iterator it = exclusiveGroups.begin();\n         it != exclusiveGroups.end();) {\n        if (CountVisibleArgs(**it) < 2) {\n            nonExclusiveGroups.push_back(*it);\n            it = exclusiveGroups.erase(it);\n        } else {\n            ++it;\n        }\n    }\n\n    // First short switches (needs to be special because they are all\n    // stuck together).\n    for (std::list<ArgGroup *>::iterator sit = nonExclusiveGroups.begin();\n         sit != nonExclusiveGroups.end(); ++sit) {\n        for (ArgGroup::iterator it = (*sit)->begin(); it != (*sit)->end();\n             ++it) {\n            if (internal::IsVisibleShortSwitch(**it)) {\n                switches += (*it)->getFlag();\n            }\n        }\n\n        std::sort(switches.begin(), switches.end(), cmpSwitch);\n    }\n\n    outp << \" [\" << switches << ']';\n\n    // Now do long switches (e.g., --version, but no -v)\n    std::vector<Arg *> longSwitches;\n    for (std::list<ArgGroup *>::iterator sit = nonExclusiveGroups.begin();\n         sit != nonExclusiveGroups.end(); ++sit) {\n        for (ArgGroup::iterator it = (*sit)->begin(); it != (*sit)->end();\n             ++it) {\n            Arg &arg = **it;\n            if (internal::IsVisibleLongSwitch(arg)) {\n                longSwitches.push_back(&arg);\n            }\n        }\n    }\n\n    std::sort(longSwitches.begin(), longSwitches.end(),\n              internal::CompareShortID);\n    for (std::vector<Arg *>::const_iterator it = longSwitches.begin();\n         it != longSwitches.end(); ++it) {\n        outp << \" [\" << (**it).shortID() << ']';\n    }\n\n    // Now do all exclusive groups\n    for (std::list<ArgGroup *>::iterator sit = exclusiveGroups.begin();\n         sit != exclusiveGroups.end(); ++sit) {\n        ArgGroup &argGroup = **sit;\n        outp << (argGroup.isRequired() ? \" {\" : \" [\");\n\n        std::vector<Arg *> args;\n        for (ArgGroup::iterator it = argGroup.begin(); it != argGroup.end();\n             ++it) {\n            if ((**it).visibleInHelp()) {\n                args.push_back(*it);\n            }\n        }\n\n        std::sort(args.begin(), args.end(), internal::CompareShortID);\n        std::string sep = \"\";\n        for (std::vector<Arg *>::const_iterator it = args.begin();\n             it != args.end(); ++it) {\n            outp << sep << (**it).shortID();\n            sep = \"|\";\n        }\n\n        outp << (argGroup.isRequired() ? '}' : ']');\n    }\n\n    // Next do options, we sort them later by optional first.\n    std::vector<std::pair<const Arg *, bool> > options;\n    for (std::list<ArgGroup *>::iterator sit = nonExclusiveGroups.begin();\n         sit != nonExclusiveGroups.end(); ++sit) {\n        for (ArgGroup::iterator it = (*sit)->begin(); it != (*sit)->end();\n             ++it) {\n            Arg &arg = **it;\n            int visible = CountVisibleArgs(**sit);\n            bool required = arg.isRequired();\n            if (internal::IsVisibleOption(arg)) {\n                if (visible == 1 && (**sit).isRequired()) {\n                    required = true;\n                }\n\n                options.push_back(std::make_pair(&arg, required));\n            }\n        }\n    }\n\n    std::sort(options.begin(), options.end(), internal::CompareOptions);\n    for (std::vector<std::pair<const Arg *, bool> >::const_iterator it =\n             options.begin();\n         it != options.end(); ++it) {\n        const Arg &arg = *it->first;\n        bool required = it->second;\n        outp << (required ? \" \" : \" [\");\n        outp << arg.shortID();\n        outp << (required ? \"\" : \"]\");\n    }\n\n    // Next do argsuments (\"unlabled\") in order of definition\n    for (std::list<ArgGroup *>::iterator sit = nonExclusiveGroups.begin();\n         sit != nonExclusiveGroups.end(); ++sit) {\n        for (ArgGroup::iterator it = (*sit)->begin(); it != (*sit)->end();\n             ++it) {\n            Arg &arg = **it;\n            if (arg.getName() == Arg::ignoreNameString()) {\n                continue;\n            }\n\n            if (arg.isValueRequired() && !arg.hasLabel() &&\n                arg.visibleInHelp()) {\n                outp << (arg.isRequired() ? \" \" : \" [\");\n                outp << arg.shortID();\n                outp << (arg.isRequired() ? \"\" : \"]\");\n            }\n        }\n    }\n\n    // if the program name is too long, then adjust the second line offset\n    int secondLineOffset = static_cast<int>(_cmd.getProgramName().length()) + 2;\n    if (secondLineOffset > 75 / 2) secondLineOffset = static_cast<int>(75 / 2);\n\n    spacePrint(os, outp.str(), 75, 3, secondLineOffset);\n}\n\ninline void StdOutput::_longUsage(CmdLineInterface &_cmd,\n                                  std::ostream &os) const {\n    std::string message = _cmd.getMessage();\n    std::list<ArgGroup *> argSets = _cmd.getArgGroups();\n\n    std::list<Arg *> unlabled;\n    for (std::list<ArgGroup *>::iterator sit = argSets.begin();\n         sit != argSets.end(); ++sit) {\n        ArgGroup &argGroup = **sit;\n\n        int visible = CountVisibleArgs(argGroup);\n        bool exclusive = visible > 1 && argGroup.isExclusive();\n        bool forceRequired = visible == 1 && argGroup.isRequired();\n        if (exclusive) {\n            spacePrint(os, argGroup.isRequired() ? \"One of:\" : \"Either of:\", 75,\n                       3, 0);\n        }\n\n        for (ArgGroup::iterator it = argGroup.begin(); it != argGroup.end();\n             ++it) {\n            Arg &arg = **it;\n            if (!arg.visibleInHelp()) {\n                continue;\n            }\n\n            if (!arg.hasLabel()) {\n                unlabled.push_back(&arg);\n                continue;\n            }\n\n            bool required = arg.isRequired() || forceRequired;\n            if (exclusive) {\n                spacePrint(os, arg.longID(), 75, 6, 3);\n                spacePrint(os, arg.getDescription(required), 75, 8, 0);\n            } else {\n                spacePrint(os, arg.longID(), 75, 3, 3);\n                spacePrint(os, arg.getDescription(required), 75, 5, 0);\n            }\n            os << '\\n';\n        }\n    }\n\n    for (ArgListIterator it = unlabled.begin(); it != unlabled.end(); ++it) {\n        const Arg &arg = **it;\n        spacePrint(os, arg.longID(), 75, 3, 3);\n        spacePrint(os, arg.getDescription(), 75, 5, 0);\n        os << '\\n';\n    }\n\n    if (!message.empty()) {\n        spacePrint(os, message, 75, 3, 0);\n    }\n\n    os.flush();\n}\n\nnamespace {\ninline void fmtPrintLine(std::ostream &os, const std::string &s, int maxWidth,\n                         int indentSpaces, int secondLineOffset) {\n    const std::string splitChars(\" ,|\");\n    int maxChars = maxWidth - indentSpaces;\n    std::string indentString(indentSpaces, ' ');\n    int from = 0;\n    int to = 0;\n    int end = (int)s.length();\n    for (;;) {\n        if (end - from <= maxChars) {\n            // Rest of string fits on line, just print the remainder\n            os << indentString << s.substr(from) << std::endl;\n            return;\n        }\n\n        // Find the next place where it is good to break the string\n        // (to) by finding the place where it is too late (tooFar) and\n        // taking the previous one.\n        int tooFar = to;\n        while (tooFar - from <= maxChars &&\n               static_cast<std::size_t>(tooFar) != std::string::npos) {\n            to = tooFar;\n            tooFar = s.find_first_of(splitChars, (int)to + 1);\n        }\n\n        if (to == from) {\n            // In case there was no good place to break the string,\n            // just break it in the middle of a word at line length.\n            to = from + maxChars - 1;\n        }\n\n        if (s[to] != ' ') {\n            // Include delimiter before line break, unless it's a space\n            to++;\n        }\n\n        os << indentString << s.substr(from, to - from) << '\\n';\n\n        // Avoid printing extra white space at start of a line\n        for (; s[to] == ' '; to++) {\n        }\n        from = to;\n\n        if (secondLineOffset != 0) {\n            // Adjust offset for following lines\n            indentString.insert(indentString.end(), secondLineOffset, ' ');\n            maxChars -= secondLineOffset;\n            secondLineOffset = 0;\n        }\n    }\n}\n}  // namespace\n\ninline void StdOutput::spacePrint(std::ostream &os, const std::string &s,\n                                  int maxWidth, int indentSpaces,\n                                  int secondLineOffset) const {\n    std::stringstream ss(s);\n    std::string line;\n    std::getline(ss, line);\n    fmtPrintLine(os, line, maxWidth, indentSpaces, secondLineOffset);\n    indentSpaces += secondLineOffset;\n\n    while (std::getline(ss, line)) {\n        fmtPrintLine(os, line, maxWidth, indentSpaces, 0);\n    }\n}\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_STD_OUTPUT_H\n"
  },
  {
    "path": "lib/tclap/SwitchArg.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  SwitchArg.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_SWITCH_ARG_H\n#define TCLAP_SWITCH_ARG_H\n\n#include <tclap/Arg.h>\n\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * A simple switch argument.  If the switch is set on the command line, then\n * the getValue method will return the opposite of the default value for the\n * switch.\n */\nclass SwitchArg : public Arg {\nprotected:\n    /**\n     * The value of the switch.\n     */\n    bool _value;\n\n    /**\n     * Used to support the reset() method so that ValueArg can be\n     * reset to their constructed value.\n     */\n    bool _default;\n\npublic:\n    /**\n     * SwitchArg constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param def - The default value for this Switch.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    SwitchArg(const std::string &flag, const std::string &name,\n              const std::string &desc, bool def = false, Visitor *v = NULL);\n\n    /**\n     * SwitchArg constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param parser - A CmdLine parser object to add this Arg to\n     * \\param def - The default value for this Switch.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    SwitchArg(const std::string &flag, const std::string &name,\n              const std::string &desc, ArgContainer &parser, bool def = false,\n              Visitor *v = NULL);\n\n    /**\n     * Handles the processing of the argument.\n     * This re-implements the Arg version of this method to set the\n     * _value of the argument appropriately.\n     * \\param i - Pointer the the current argument in the list.\n     * \\param args - Mutable list of strings. Passed\n     * in from main().\n     */\n    virtual bool processArg(int *i, std::vector<std::string> &args);\n\n    /**\n     * Checks a string to see if any of the chars in the string\n     * match the flag for this Switch.\n     */\n    bool combinedSwitchesMatch(std::string &combined);\n\n    /**\n     * Returns bool, whether or not the switch has been set.\n     */\n    bool getValue() const { return _value; }\n\n    /**\n     * A SwitchArg can be used as a boolean, indicating\n     * whether or not the switch has been set. This is the\n     * same as calling getValue()\n     */\n    operator bool() const { return _value; }\n\n    virtual void reset();\n\nprivate:\n    /**\n     * Checks to see if we've found the last match in\n     * a combined string.\n     */\n    bool lastCombined(std::string &combined);\n\n    /**\n     * Does the common processing of processArg.\n     */\n    void commonProcessing();\n};\n\n//////////////////////////////////////////////////////////////////////\n// BEGIN SwitchArg.cpp\n//////////////////////////////////////////////////////////////////////\ninline SwitchArg::SwitchArg(const std::string &flag, const std::string &name,\n                            const std::string &desc, bool default_val,\n                            Visitor *v)\n    : Arg(flag, name, desc, false, false, v),\n      _value(default_val),\n      _default(default_val) {}\n\ninline SwitchArg::SwitchArg(const std::string &flag, const std::string &name,\n                            const std::string &desc, ArgContainer &parser,\n                            bool default_val, Visitor *v)\n    : Arg(flag, name, desc, false, false, v),\n      _value(default_val),\n      _default(default_val) {\n    parser.add(this);\n}\n\ninline bool SwitchArg::lastCombined(std::string &combinedSwitches) {\n    for (unsigned int i = 1; i < combinedSwitches.length(); i++)\n        if (combinedSwitches[i] != Arg::blankChar()) return false;\n\n    return true;\n}\n\ninline bool SwitchArg::combinedSwitchesMatch(std::string &combinedSwitches) {\n    // make sure this is actually a combined switch\n    if (combinedSwitches.length() > 0 &&\n        combinedSwitches[0] != Arg::flagStartString()[0])\n        return false;\n\n    // make sure it isn't a long name\n    if (combinedSwitches.substr(0, Arg::nameStartString().length()) ==\n        Arg::nameStartString())\n        return false;\n\n    // make sure the delimiter isn't in the string\n    if (combinedSwitches.find_first_of(Arg::delimiter()) != std::string::npos)\n        return false;\n\n    // ok, we're not specifying a ValueArg, so we know that we have\n    // a combined switch list.\n    for (unsigned int i = 1; i < combinedSwitches.length(); i++) {\n        if (_flag.length() > 0 && combinedSwitches[i] == _flag[0] &&\n            _flag[0] != Arg::flagStartString()[0]) {\n            // update the combined switches so this one is no longer\n            // present this is necessary so that no unlabeled args are\n            // matched later in the processing.\n            // combinedSwitches.erase(i,1);\n            _setBy = Arg::flagStartString() + combinedSwitches[i];\n            combinedSwitches[i] = Arg::blankChar();\n            return true;\n        }\n    }\n\n    // none of the switches passed in the list match.\n    return false;\n}\n\ninline void SwitchArg::commonProcessing() {\n    if (_alreadySet)\n        throw(CmdLineParseException(\"Argument already set!\", toString()));\n\n    _alreadySet = true;\n\n    if (_value == true)\n        _value = false;\n    else\n        _value = true;\n\n    _checkWithVisitor();\n}\n\ninline bool SwitchArg::processArg(int *i, std::vector<std::string> &args) {\n    if (argMatches(args[*i])) {\n        // The whole string matches the flag or name string\n        _setBy = args[*i];\n        commonProcessing();\n\n        return true;\n    } else if (combinedSwitchesMatch(args[*i])) {\n        // A substring matches the flag as part of a combination\n        // check again to ensure we don't misinterpret\n        // this as a MultiSwitchArg\n        if (combinedSwitchesMatch(args[*i]))\n            throw(CmdLineParseException(\"Argument already set!\", toString()));\n\n        commonProcessing();\n\n        // We only want to return true if we've found the last combined\n        // match in the string, otherwise we return true so that other\n        // switches in the combination will have a chance to match.\n        return lastCombined(args[*i]);\n    }\n\n    return false;\n}\n\ninline void SwitchArg::reset() {\n    Arg::reset();\n    _value = _default;\n}\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_SWITCH_ARG_H\n"
  },
  {
    "path": "lib/tclap/UnlabeledMultiArg.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  UnlabeledMultiArg.h\n *\n *  Copyright (c) 2003, Michael E. Smoot.\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_UNLABELED_MULTI_ARG_H\n#define TCLAP_UNLABELED_MULTI_ARG_H\n\n#include <tclap/MultiArg.h>\n#include <tclap/OptionalUnlabeledTracker.h>\n\n#include <list>\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * Just like a MultiArg, except that the arguments are unlabeled.  Basically,\n * this Arg will slurp up everything that hasn't been matched to another\n * Arg.\n */\ntemplate <class T>\nclass UnlabeledMultiArg : public MultiArg<T> {\n    // If compiler has two stage name lookup (as gcc >= 3.4 does)\n    // this is required to prevent undef. symbols\n    using MultiArg<T>::_ignoreable;\n    using MultiArg<T>::_hasBlanks;\n    using MultiArg<T>::_extractValue;\n    using MultiArg<T>::_typeDesc;\n    using MultiArg<T>::_name;\n    using MultiArg<T>::_description;\n    using MultiArg<T>::_alreadySet;\n    using MultiArg<T>::_setBy;\n    using MultiArg<T>::toString;\n\npublic:\n    /**\n     * Constructor.\n     * \\param name - The name of the Arg. Note that this is used for\n     * identification, not as a long flag.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     *  line.\n     * \\param typeDesc - A short, human readable description of the\n     * type that this object expects.  This is used in the generation\n     * of the USAGE statement.  The goal is to be helpful to the end user\n     * of the program.\n     * \\param ignoreable - Whether or not this argument can be ignored\n     * using the \"--\" flag.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    UnlabeledMultiArg(const std::string &name, const std::string &desc,\n                      bool req, const std::string &typeDesc,\n                      bool ignoreable = false, Visitor *v = NULL);\n    /**\n     * Constructor.\n     * \\param name - The name of the Arg. Note that this is used for\n     * identification, not as a long flag.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     *  line.\n     * \\param typeDesc - A short, human readable description of the\n     * type that this object expects.  This is used in the generation\n     * of the USAGE statement.  The goal is to be helpful to the end user\n     * of the program.\n     * \\param parser - A CmdLine parser object to add this Arg to\n     * \\param ignoreable - Whether or not this argument can be ignored\n     * using the \"--\" flag.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    UnlabeledMultiArg(const std::string &name, const std::string &desc,\n                      bool req, const std::string &typeDesc,\n                      ArgContainer &parser, bool ignoreable = false,\n                      Visitor *v = NULL);\n\n    /**\n     * Constructor.\n     * \\param name - The name of the Arg. Note that this is used for\n     * identification, not as a long flag.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     *  line.\n     * \\param constraint - A pointer to a Constraint object used\n     * to constrain this Arg.\n     * \\param ignoreable - Whether or not this argument can be ignored\n     * using the \"--\" flag.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    UnlabeledMultiArg(const std::string &name, const std::string &desc,\n                      bool req, Constraint<T> *constraint,\n                      bool ignoreable = false, Visitor *v = NULL);\n\n    /**\n     * Constructor.\n     * \\param name - The name of the Arg. Note that this is used for\n     * identification, not as a long flag.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     *  line.\n     * \\param constraint - A pointer to a Constraint object used\n     * to constrain this Arg.\n     * \\param parser - A CmdLine parser object to add this Arg to\n     * \\param ignoreable - Whether or not this argument can be ignored\n     * using the \"--\" flag.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    UnlabeledMultiArg(const std::string &name, const std::string &desc,\n                      bool req, Constraint<T> *constraint, ArgContainer &parser,\n                      bool ignoreable = false, Visitor *v = NULL);\n\n    /**\n     * Handles the processing of the argument.\n     * This re-implements the Arg version of this method to set the\n     * _value of the argument appropriately.  It knows the difference\n     * between labeled and unlabeled.\n     * \\param i - Pointer the the current argument in the list.\n     * \\param args - Mutable list of strings. Passed from main().\n     */\n    virtual bool processArg(int *i, std::vector<std::string> &args);\n\n    /**\n     * Returns the a short id string.  Used in the usage.\n     */\n    virtual std::string shortID(const std::string &) const {\n        return Arg::getName() + \" ...\";\n    }\n\n    /**\n     * Returns the a long id string.  Used in the usage.\n     * \\param val - value to be used.\n     */\n    virtual std::string longID(const std::string &) const {\n        return Arg::getName() + \" (accepted multiple times) <\" + _typeDesc +\n               \">\";\n    }\n\n    /**\n     * Operator ==.\n     * \\param a - The Arg to be compared to this.\n     */\n    virtual bool operator==(const Arg &a) const;\n\n    /**\n     * Pushes this to back of list rather than front.\n     * \\param argList - The list this should be added to.\n     */\n    virtual void addToList(std::list<Arg *> &argList) const;\n\n    virtual bool hasLabel() const { return false; }\n};\n\ntemplate <class T>\nUnlabeledMultiArg<T>::UnlabeledMultiArg(const std::string &name,\n                                        const std::string &desc, bool req,\n                                        const std::string &typeDesc,\n                                        bool ignoreable, Visitor *v)\n    : MultiArg<T>(\"\", name, desc, req, typeDesc, v) {\n    _ignoreable = ignoreable;\n    OptionalUnlabeledTracker::check(true, toString());\n}\n\ntemplate <class T>\nUnlabeledMultiArg<T>::UnlabeledMultiArg(const std::string &name,\n                                        const std::string &desc, bool req,\n                                        const std::string &typeDesc,\n                                        ArgContainer &parser, bool ignoreable,\n                                        Visitor *v)\n    : MultiArg<T>(\"\", name, desc, req, typeDesc, v) {\n    _ignoreable = ignoreable;\n    OptionalUnlabeledTracker::check(true, toString());\n    parser.add(this);\n}\n\ntemplate <class T>\nUnlabeledMultiArg<T>::UnlabeledMultiArg(const std::string &name,\n                                        const std::string &desc, bool req,\n                                        Constraint<T> *constraint,\n                                        bool ignoreable, Visitor *v)\n    : MultiArg<T>(\"\", name, desc, req, constraint, v) {\n    _ignoreable = ignoreable;\n    OptionalUnlabeledTracker::check(true, toString());\n}\n\ntemplate <class T>\nUnlabeledMultiArg<T>::UnlabeledMultiArg(const std::string &name,\n                                        const std::string &desc, bool req,\n                                        Constraint<T> *constraint,\n                                        ArgContainer &parser, bool ignoreable,\n                                        Visitor *v)\n    : MultiArg<T>(\"\", name, desc, req, constraint, v) {\n    _ignoreable = ignoreable;\n    OptionalUnlabeledTracker::check(true, toString());\n    parser.add(this);\n}\n\ntemplate <class T>\nbool UnlabeledMultiArg<T>::processArg(int *i, std::vector<std::string> &args) {\n    if (_hasBlanks(args[*i])) return false;\n\n    // never ignore an unlabeled multi arg\n\n    // always take the first value, regardless of the start string\n    _extractValue(args[(*i)]);\n\n    /*\n    // continue taking args until we hit the end or a start string\n    while ( (unsigned int)(*i)+1 < args.size() &&\n            args[(*i)+1].find_first_of( Arg::flagStartString() ) != 0 &&\n            args[(*i)+1].find_first_of( Arg::nameStartString() ) != 0 )\n        _extractValue( args[++(*i)] );\n    */\n\n    _alreadySet = true;\n    _setBy = args[*i];\n\n    return true;\n}\n\ntemplate <class T>\nbool UnlabeledMultiArg<T>::operator==(const Arg &a) const {\n    if (_name == a.getName() || _description == a.getDescription())\n        return true;\n    else\n        return false;\n}\n\ntemplate <class T>\nvoid UnlabeledMultiArg<T>::addToList(std::list<Arg *> &argList) const {\n    argList.push_back(const_cast<Arg *>(static_cast<const Arg *const>(this)));\n}\n}  // namespace TCLAP\n\n#endif  // TCLAP_UNLABELED_MULTI_ARG_H\n"
  },
  {
    "path": "lib/tclap/UnlabeledValueArg.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  UnlabeledValueArg.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_UNLABELED_VALUE_ARG_H\n#define TCLAP_UNLABELED_VALUE_ARG_H\n\n#include <tclap/OptionalUnlabeledTracker.h>\n#include <tclap/ValueArg.h>\n\n#include <list>\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * The basic unlabeled argument that parses a value.\n * This is a template class, which means the type T defines the type\n * that a given object will attempt to parse when an UnlabeledValueArg\n * is reached in the list of args that the CmdLine iterates over.\n */\ntemplate <class T>\nclass UnlabeledValueArg : public ValueArg<T> {\n    // If compiler has two stage name lookup (as gcc >= 3.4 does)\n    // this is required to prevent undef. symbols\n    using ValueArg<T>::_ignoreable;\n    using ValueArg<T>::_hasBlanks;\n    using ValueArg<T>::_extractValue;\n    using ValueArg<T>::_typeDesc;\n    using ValueArg<T>::_name;\n    using ValueArg<T>::_description;\n    using ValueArg<T>::_alreadySet;\n    using ValueArg<T>::_setBy;\n    using ValueArg<T>::toString;\n\npublic:\n    /**\n     * UnlabeledValueArg constructor.\n     * \\param name - A one word name for the argument.  Note that this is used\n     * for\n     * identification, not as a long flag.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param value - The default value assigned to this argument if it\n     * is not present on the command line.\n     * \\param typeDesc - A short, human readable description of the\n     * type that this object expects.  This is used in the generation\n     * of the USAGE statement.  The goal is to be helpful to the end user\n     * of the program.\n     * \\param ignoreable - Allows you to specify that this argument can be\n     * ignored if the '--' flag is set.  This defaults to false (cannot\n     * be ignored) and should  generally stay that way unless you have\n     * some special need for certain arguments to be ignored.\n     * \\param v - Optional Visitor.  You should leave this blank unless\n     * you have a very good reason.\n     */\n    UnlabeledValueArg(const std::string &name, const std::string &desc,\n                      bool req, T value, const std::string &typeDesc,\n                      bool ignoreable = false, Visitor *v = NULL);\n\n    /**\n     * UnlabeledValueArg constructor.\n     * \\param name - A one word name for the argument.  Note that this is used\n     * for\n     * identification, not as a long flag.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param value - The default value assigned to this argument if it\n     * is not present on the command line.\n     * \\param typeDesc - A short, human readable description of the\n     * type that this object expects.  This is used in the generation\n     * of the USAGE statement.  The goal is to be helpful to the end user\n     * of the program.\n     * \\param parser - A CmdLine parser object to add this Arg to\n     * \\param ignoreable - Allows you to specify that this argument can be\n     * ignored if the '--' flag is set.  This defaults to false (cannot\n     * be ignored) and should  generally stay that way unless you have\n     * some special need for certain arguments to be ignored.\n     * \\param v - Optional Visitor.  You should leave this blank unless\n     * you have a very good reason.\n     */\n    UnlabeledValueArg(const std::string &name, const std::string &desc,\n                      bool req, T value, const std::string &typeDesc,\n                      CmdLineInterface &parser, bool ignoreable = false,\n                      Visitor *v = NULL);\n\n    /**\n     * UnlabeledValueArg constructor.\n     * \\param name - A one word name for the argument.  Note that this is used\n     * for\n     * identification, not as a long flag.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param value - The default value assigned to this argument if it\n     * is not present on the command line.\n     * \\param constraint - A pointer to a Constraint object used\n     * to constrain this Arg.\n     * \\param ignoreable - Allows you to specify that this argument can be\n     * ignored if the '--' flag is set.  This defaults to false (cannot\n     * be ignored) and should  generally stay that way unless you have\n     * some special need for certain arguments to be ignored.\n     * \\param v - Optional Visitor.  You should leave this blank unless\n     * you have a very good reason.\n     */\n    UnlabeledValueArg(const std::string &name, const std::string &desc,\n                      bool req, T value, Constraint<T> *constraint,\n                      bool ignoreable = false, Visitor *v = NULL);\n\n    /**\n     * UnlabeledValueArg constructor.\n     * \\param name - A one word name for the argument.  Note that this is used\n     * for\n     * identification, not as a long flag.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param value - The default value assigned to this argument if it\n     * is not present on the command line.\n     * \\param constraint - A pointer to a Constraint object used\n     * to constrain this Arg.\n     * \\param parser - A CmdLine parser object to add this Arg to\n     * \\param ignoreable - Allows you to specify that this argument can be\n     * ignored if the '--' flag is set.  This defaults to false (cannot\n     * be ignored) and should  generally stay that way unless you have\n     * some special need for certain arguments to be ignored.\n     * \\param v - Optional Visitor.  You should leave this blank unless\n     * you have a very good reason.\n     */\n    UnlabeledValueArg(const std::string &name, const std::string &desc,\n                      bool req, T value, Constraint<T> *constraint,\n                      CmdLineInterface &parser, bool ignoreable = false,\n                      Visitor *v = NULL);\n\n    /**\n     * Handles the processing of the argument.\n     * This re-implements the Arg version of this method to set the\n     * _value of the argument appropriately.  Handling specific to\n     * unlabeled arguments.\n     * \\param i - Pointer the the current argument in the list.\n     * \\param args - Mutable list of strings.\n     */\n    virtual bool processArg(int *i, std::vector<std::string> &args);\n\n    /**\n     * Overrides shortID for specific behavior.\n     */\n    virtual std::string shortID(const std::string &) const {\n        return Arg::getName();\n    }\n\n    /**\n     * Overrides longID for specific behavior.\n     */\n    virtual std::string longID(const std::string &) const {\n        return Arg::getName() + \" <\" + _typeDesc + \">\";\n    }\n\n    /**\n     * Overrides operator== for specific behavior.\n     */\n    virtual bool operator==(const Arg &a) const;\n\n    /**\n     * Instead of pushing to the front of list, push to the back.\n     * \\param argList - The list to add this to.\n     */\n    virtual void addToList(std::list<Arg *> &argList) const;\n\n    virtual bool hasLabel() const { return false; }\n};\n\n/**\n * Constructor implementation.\n */\ntemplate <class T>\nUnlabeledValueArg<T>::UnlabeledValueArg(const std::string &name,\n                                        const std::string &desc, bool req,\n                                        T val, const std::string &typeDesc,\n                                        bool ignoreable, Visitor *v)\n    : ValueArg<T>(\"\", name, desc, req, val, typeDesc, v) {\n    _ignoreable = ignoreable;\n\n    OptionalUnlabeledTracker::check(req, toString());\n}\n\ntemplate <class T>\nUnlabeledValueArg<T>::UnlabeledValueArg(const std::string &name,\n                                        const std::string &desc, bool req,\n                                        T val, const std::string &typeDesc,\n                                        CmdLineInterface &parser,\n                                        bool ignoreable, Visitor *v)\n    : ValueArg<T>(\"\", name, desc, req, val, typeDesc, v) {\n    _ignoreable = ignoreable;\n    OptionalUnlabeledTracker::check(req, toString());\n    parser.add(this);\n}\n\n/**\n * Constructor implementation.\n */\ntemplate <class T>\nUnlabeledValueArg<T>::UnlabeledValueArg(const std::string &name,\n                                        const std::string &desc, bool req,\n                                        T val, Constraint<T> *constraint,\n                                        bool ignoreable, Visitor *v)\n    : ValueArg<T>(\"\", name, desc, req, val, constraint, v) {\n    _ignoreable = ignoreable;\n    OptionalUnlabeledTracker::check(req, toString());\n}\n\ntemplate <class T>\nUnlabeledValueArg<T>::UnlabeledValueArg(const std::string &name,\n                                        const std::string &desc, bool req,\n                                        T val, Constraint<T> *constraint,\n                                        CmdLineInterface &parser,\n                                        bool ignoreable, Visitor *v)\n    : ValueArg<T>(\"\", name, desc, req, val, constraint, v) {\n    _ignoreable = ignoreable;\n    OptionalUnlabeledTracker::check(req, toString());\n    parser.add(this);\n}\n\n/**\n * Implementation of processArg().\n */\ntemplate <class T>\nbool UnlabeledValueArg<T>::processArg(int *i, std::vector<std::string> &args) {\n    if (_alreadySet) return false;\n\n    if (_hasBlanks(args[*i])) return false;\n\n    // never ignore an unlabeled arg\n\n    _extractValue(args[*i]);\n    _alreadySet = true;\n    _setBy = args[*i];\n    return true;\n}\n\n/**\n * Overriding operator== for specific behavior.\n */\ntemplate <class T>\nbool UnlabeledValueArg<T>::operator==(const Arg &a) const {\n    if (_name == a.getName() || _description == a.getDescription())\n        return true;\n    else\n        return false;\n}\n\ntemplate <class T>\nvoid UnlabeledValueArg<T>::addToList(std::list<Arg *> &argList) const {\n    argList.push_back(const_cast<Arg *>(static_cast<const Arg *const>(this)));\n}\n}  // namespace TCLAP\n\n#endif  // TCLAP_UNLABELED_VALUE_ARG_H\n"
  },
  {
    "path": "lib/tclap/ValueArg.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  ValueArg.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_VALUE_ARG_H\n#define TCLAP_VALUE_ARG_H\n\n#include <tclap/Arg.h>\n#include <tclap/Constraint.h>\n\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * The basic labeled argument that parses a value.\n * This is a template class, which means the type T defines the type\n * that a given object will attempt to parse when the flag/name is matched\n * on the command line.  While there is nothing stopping you from creating\n * an unflagged ValueArg, it is unwise and would cause significant problems.\n * Instead use an UnlabeledValueArg.\n */\ntemplate <class T>\nclass ValueArg : public Arg {\nprotected:\n    /**\n     * The value parsed from the command line.\n     * Can be of any type, as long as the >> operator for the type\n     * is defined.\n     */\n    T _value;\n\n    /**\n     * Used to support the reset() method so that ValueArg can be\n     * reset to their constructed value.\n     */\n    T _default;\n\n    /**\n     * A human readable description of the type to be parsed.\n     * This is a hack, plain and simple.  Ideally we would use RTTI to\n     * return the name of type T, but until there is some sort of\n     * consistent support for human readable names, we are left to our\n     * own devices.\n     */\n    std::string _typeDesc;\n\n    /**\n     * A Constraint this Arg must conform to.\n     */\n    Constraint<T> *_constraint;\n\n    /**\n     * Extracts the value from the string.\n     * Attempts to parse string as type T, if this fails an exception\n     * is thrown.\n     * \\param val - value to be parsed.\n     */\n    void _extractValue(const std::string &val);\n\npublic:\n    /**\n     * Labeled ValueArg constructor.\n     * You could conceivably call this constructor with a blank flag,\n     * but that would make you a bad person.  It would also cause\n     * an exception to be thrown.   If you want an unlabeled argument,\n     * use the other constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param value - The default value assigned to this argument if it\n     * is not present on the command line.\n     * \\param typeDesc - A short, human readable description of the\n     * type that this object expects.  This is used in the generation\n     * of the USAGE statement.  The goal is to be helpful to the end user\n     * of the program.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    ValueArg(const std::string &flag, const std::string &name,\n             const std::string &desc, bool req, T value,\n             const std::string &typeDesc, Visitor *v = NULL);\n\n    /**\n     * Labeled ValueArg constructor.\n     * You could conceivably call this constructor with a blank flag,\n     * but that would make you a bad person.  It would also cause\n     * an exception to be thrown.   If you want an unlabeled argument,\n     * use the other constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param value - The default value assigned to this argument if it\n     * is not present on the command line.\n     * \\param typeDesc - A short, human readable description of the\n     * type that this object expects.  This is used in the generation\n     * of the USAGE statement.  The goal is to be helpful to the end user\n     * of the program.\n     * \\param parser - A CmdLine parser object to add this Arg to\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    ValueArg(const std::string &flag, const std::string &name,\n             const std::string &desc, bool req, T value,\n             const std::string &typeDesc, ArgContainer &parser,\n             Visitor *v = NULL);\n\n    /**\n     * Labeled ValueArg constructor.\n     * You could conceivably call this constructor with a blank flag,\n     * but that would make you a bad person.  It would also cause\n     * an exception to be thrown.   If you want an unlabeled argument,\n     * use the other constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param value - The default value assigned to this argument if it\n     * is not present on the command line.\n     * \\param constraint - A pointer to a Constraint object used\n     * to constrain this Arg.\n     * \\param parser - A CmdLine parser object to add this Arg to.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    ValueArg(const std::string &flag, const std::string &name,\n             const std::string &desc, bool req, T value,\n             Constraint<T> *constraint, ArgContainer &parser,\n             Visitor *v = NULL);\n\n    /**\n     * Labeled ValueArg constructor.\n     * You could conceivably call this constructor with a blank flag,\n     * but that would make you a bad person.  It would also cause\n     * an exception to be thrown.   If you want an unlabeled argument,\n     * use the other constructor.\n     * \\param flag - The one character flag that identifies this\n     * argument on the command line.\n     * \\param name - A one word name for the argument.  Can be\n     * used as a long flag on the command line.\n     * \\param desc - A description of what the argument is for or\n     * does.\n     * \\param req - Whether the argument is required on the command\n     * line.\n     * \\param value - The default value assigned to this argument if it\n     * is not present on the command line.\n     * \\param constraint - A pointer to a Constraint object used\n     * to constrain this Arg.\n     * \\param v - An optional visitor.  You probably should not\n     * use this unless you have a very good reason.\n     */\n    ValueArg(const std::string &flag, const std::string &name,\n             const std::string &desc, bool req, T value,\n             Constraint<T> *constraint, Visitor *v = NULL);\n\n    /**\n     * Handles the processing of the argument.\n     * This re-implements the Arg version of this method to set the\n     * _value of the argument appropriately.  It knows the difference\n     * between labeled and unlabeled.\n     * \\param i - Pointer the the current argument in the list.\n     * \\param args - Mutable list of strings. Passed\n     * in from main().\n     */\n    virtual bool processArg(int *i, std::vector<std::string> &args);\n\n    /**\n     * Returns the value of the argument.\n     */\n    const T &getValue() const { return _value; }\n\n    /**\n     * A ValueArg can be used as as its value type (T) This is the\n     * same as calling getValue()\n     */\n    operator const T &() const { return getValue(); }\n\n    /**\n     * Specialization of shortID.\n     * \\param val - value to be used.\n     */\n    virtual std::string shortID(const std::string &val = \"val\") const;\n\n    /**\n     * Specialization of longID.\n     * \\param val - value to be used.\n     */\n    virtual std::string longID(const std::string &val = \"val\") const;\n\n    virtual void reset();\n\nprivate:\n    /**\n     * Prevent accidental copying\n     */\n    ValueArg<T>(const ValueArg<T> &rhs);\n    ValueArg<T> &operator=(const ValueArg<T> &rhs);\n};\n\n/**\n * Constructor implementation.\n */\ntemplate <class T>\nValueArg<T>::ValueArg(const std::string &flag, const std::string &name,\n                      const std::string &desc, bool req, T val,\n                      const std::string &typeDesc, Visitor *v)\n    : Arg(flag, name, desc, req, true, v),\n      _value(val),\n      _default(val),\n      _typeDesc(typeDesc),\n      _constraint(NULL) {}\n\ntemplate <class T>\nValueArg<T>::ValueArg(const std::string &flag, const std::string &name,\n                      const std::string &desc, bool req, T val,\n                      const std::string &typeDesc, ArgContainer &parser,\n                      Visitor *v)\n    : Arg(flag, name, desc, req, true, v),\n      _value(val),\n      _default(val),\n      _typeDesc(typeDesc),\n      _constraint(NULL) {\n    parser.add(this);\n}\n\ntemplate <class T>\nValueArg<T>::ValueArg(const std::string &flag, const std::string &name,\n                      const std::string &desc, bool req, T val,\n                      Constraint<T> *constraint, Visitor *v)\n    : Arg(flag, name, desc, req, true, v),\n      _value(val),\n      _default(val),\n      _typeDesc(Constraint<T>::shortID(constraint)),\n      _constraint(constraint) {}\n\ntemplate <class T>\nValueArg<T>::ValueArg(const std::string &flag, const std::string &name,\n                      const std::string &desc, bool req, T val,\n                      Constraint<T> *constraint, ArgContainer &parser,\n                      Visitor *v)\n    : Arg(flag, name, desc, req, true, v),\n      _value(val),\n      _default(val),\n      _typeDesc(Constraint<T>::shortID(constraint)),\n      _constraint(constraint) {\n    parser.add(this);\n}\n\n/**\n * Implementation of processArg().\n */\ntemplate <class T>\nbool ValueArg<T>::processArg(int *i, std::vector<std::string> &args) {\n    if (_hasBlanks(args[*i])) return false;\n\n    std::string flag = args[*i];\n\n    std::string value = \"\";\n    trimFlag(flag, value);\n\n    if (argMatches(flag)) {\n        if (_alreadySet) {\n            throw(CmdLineParseException(\"Argument already set!\", toString()));\n        }\n\n        if (Arg::delimiter() != ' ' && value == \"\")\n            throw(ArgParseException(\n                \"Couldn't find delimiter for this argument!\", toString()));\n\n        if (value == \"\") {\n            (*i)++;\n            if (static_cast<unsigned int>(*i) < args.size())\n                _extractValue(args[*i]);\n            else\n                throw(ArgParseException(\"Missing a value for this argument!\",\n                                        toString()));\n        } else {\n            _extractValue(value);\n        }\n\n        _alreadySet = true;\n        _setBy = flag;\n        _checkWithVisitor();\n        return true;\n    } else {\n        return false;\n    }\n}\n\n/**\n * Implementation of shortID.\n */\ntemplate <class T>\nstd::string ValueArg<T>::shortID(const std::string &) const {\n    return Arg::shortID(\"<\" + _typeDesc + \">\");\n}\n\n/**\n * Implementation of longID.\n */\ntemplate <class T>\nstd::string ValueArg<T>::longID(const std::string &) const {\n    return Arg::longID(\"<\" + _typeDesc + \">\");\n}\n\ntemplate <class T>\nvoid ValueArg<T>::_extractValue(const std::string &val) {\n    try {\n        ExtractValue(_value, val, typename ArgTraits<T>::ValueCategory());\n    } catch (ArgParseException &e) {\n        throw ArgParseException(e.error(), toString());\n    }\n\n    if (_constraint != NULL)\n        if (!_constraint->check(_value))\n            throw(CmdLineParseException(\"Value '\" + val +\n                                            +\"' does not meet constraint: \" +\n                                            _constraint->description(),\n                                        toString()));\n}\n\ntemplate <class T>\nvoid ValueArg<T>::reset() {\n    Arg::reset();\n    _value = _default;\n}\n\n}  // namespace TCLAP\n\n#endif  // TCLAP_VALUE_ARG_H\n"
  },
  {
    "path": "lib/tclap/ValuesConstraint.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  ValuesConstraint.h\n *\n *  Copyright (c) 2005, Michael E. Smoot\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_VALUES_CONSTRAINT_H\n#define TCLAP_VALUES_CONSTRAINT_H\n\n#include <tclap/Constraint.h>\n#include <tclap/sstream.h>\n\n#include <string>\n#include <vector>\n\nnamespace TCLAP {\n\n/**\n * A Constraint that constrains the Arg to only those values specified\n * in the constraint.\n */\ntemplate <class T>\nclass ValuesConstraint : public Constraint<T> {\npublic:\n    /**\n     * Constructor.\n     * \\param allowed - vector of allowed values.\n     */\n    explicit ValuesConstraint(std::vector<T> &allowed);\n\n    /**\n     * Virtual destructor.\n     */\n    virtual ~ValuesConstraint() {}\n\n    /**\n     * Returns a description of the Constraint.\n     */\n    virtual std::string description() const;\n\n    /**\n     * Returns the short ID for the Constraint.\n     */\n    virtual std::string shortID() const;\n\n    /**\n     * The method used to verify that the value parsed from the command\n     * line meets the constraint.\n     * \\param value - The value that will be checked.\n     */\n    virtual bool check(const T &value) const;\n\nprotected:\n    /**\n     * The list of valid values.\n     */\n    std::vector<T> _allowed;\n\n    /**\n     * The string used to describe the allowed values of this constraint.\n     */\n    std::string _typeDesc;\n};\n\ntemplate <class T>\nValuesConstraint<T>::ValuesConstraint(std::vector<T> &allowed)\n    : _allowed(allowed), _typeDesc(\"\") {\n    for (unsigned int i = 0; i < _allowed.size(); i++) {\n        std::ostringstream os;\n        os << _allowed[i];\n\n        std::string temp(os.str());\n\n        if (i > 0) _typeDesc += \"|\";\n        _typeDesc += temp;\n    }\n}\n\ntemplate <class T>\nbool ValuesConstraint<T>::check(const T &val) const {\n    if (std::find(_allowed.begin(), _allowed.end(), val) == _allowed.end())\n        return false;\n    else\n        return true;\n}\n\ntemplate <class T>\nstd::string ValuesConstraint<T>::shortID() const {\n    return _typeDesc;\n}\n\ntemplate <class T>\nstd::string ValuesConstraint<T>::description() const {\n    return _typeDesc;\n}\n\n}  // namespace TCLAP\n#endif  // TCLAP_VALUES_CONSTRAINT_H\n"
  },
  {
    "path": "lib/tclap/VersionVisitor.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  VersionVisitor.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_VERSION_VISITOR_H\n#define TCLAP_VERSION_VISITOR_H\n\n#include <tclap/CmdLineInterface.h>\n#include <tclap/CmdLineOutput.h>\n#include <tclap/Visitor.h>\n\nnamespace TCLAP {\n\n/**\n * A Visitor that will call the version method of the given CmdLineOutput\n * for the specified CmdLine object and then exit.\n */\nclass VersionVisitor : public Visitor {\nprivate:\n    /**\n     * Prevent accidental copying\n     */\n    VersionVisitor(const VersionVisitor &rhs);\n    VersionVisitor &operator=(const VersionVisitor &rhs);\n\nprotected:\n    /**\n     * The CmdLine of interest.\n     */\n    CmdLineInterface *_cmd;\n\n    /**\n     * The output object.\n     */\n    CmdLineOutput **_out;\n\npublic:\n    /**\n     * Constructor.\n     * \\param cmd - The CmdLine the output is generated for.\n     * \\param out - The type of output.\n     */\n    VersionVisitor(CmdLineInterface *cmd, CmdLineOutput **out)\n        : Visitor(), _cmd(cmd), _out(out) {}\n\n    /**\n     * Calls the version method of the output object using the\n     * specified CmdLine.\n     */\n    void visit() {\n        (*_out)->version(*_cmd);\n        throw ExitException(0);\n    }\n};\n}  // namespace TCLAP\n\n#endif  // TCLAP_VERSION_VISITOR_H\n"
  },
  {
    "path": "lib/tclap/Visitor.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  Visitor.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2017, Google LLC\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_VISITOR_H\n#define TCLAP_VISITOR_H\n\nnamespace TCLAP {\n\n/**\n * A base class that defines the interface for visitors.\n */\nclass Visitor {\npublic:\n    /**\n     * Constructor. Does nothing.\n     */\n    Visitor() {}\n\n    /**\n     * Destructor. Does nothing.\n     */\n    virtual ~Visitor() {}\n\n    /**\n     * This method (to implemented by children) will be\n     * called when the visitor is visited.\n     */\n    virtual void visit() = 0;\n};\n}  // namespace TCLAP\n\n#endif  // TCLAP_VISITOR_H\n"
  },
  {
    "path": "lib/tclap/sstream.h",
    "content": "// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-\n\n/******************************************************************************\n *\n *  file:  sstream.h\n *\n *  Copyright (c) 2003, Michael E. Smoot .\n *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno .\n *  Copyright (c) 2017 Google Inc.\n *  All rights reserved.\n *\n *  See the file COPYING in the top directory of this distribution for\n *  more information.\n *\n *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS\n *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n *  DEALINGS IN THE SOFTWARE.\n *\n *****************************************************************************/\n\n#ifndef TCLAP_SSTREAM_H\n#define TCLAP_SSTREAM_H\n\n#if !defined(TCLAP_HAVE_STRSTREAM)\n// Assume sstream is available if strstream is not specified\n// (https://sourceforge.net/p/tclap/bugs/23/)\n#define TCLAP_HAVE_SSTREAM\n#endif\n\n#if defined(TCLAP_HAVE_SSTREAM)\n#include <sstream>\nnamespace TCLAP {\ntypedef std::istringstream istringstream;\ntypedef std::ostringstream ostringstream;\n}  // namespace TCLAP\n#elif defined(TCLAP_HAVE_STRSTREAM)\n#include <strstream>\nnamespace TCLAP {\ntypedef std::istrstream istringstream;\ntypedef std::ostrstream ostringstream;\n}  // namespace TCLAP\n#else\n#error \"Need a stringstream (sstream or strstream) to compile!\"\n#endif\n\n#endif  // TCLAP_SSTREAM_H\n"
  },
  {
    "path": "lib/tclap/tclap.cmake",
    "content": "include(CheckCXXSourceCompiles)\n\nadd_definitions(-DTCLAP_VERSION_MAJOR=1 -DTCLAP_VERSION_MINOR=4)\n\ncheck_cxx_source_compiles(\"#include <strstream>\nint main() { std::istrstream iss; return 0; }\" TCLAP_HAVE_STRSTREAM)\nif(TCLAP_HAVE_STRSTREAM)\n    message(\"-- found TCLAP_HAVE_STRSTREAM\")\n    add_definitions(-DTCLAP_HAVE_STRSTREAM)\nendif()\n\n#check_cxx_source_compiles(\"#include <sstream>\n#int main() { std::istringstream iss; return 0; }\" TCLAP_HAVE_SSTREAM)\n#if(TCLAP_HAVE_SSTREAM)\n#    message(\"-- found TCLAP_HAVE_SSTREAM\")\n#    add_definitions(-DTCLAP_HAVE_SSTREAM)\n#endif()\n#"
  },
  {
    "path": "lib/util.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"util.h\"\n#include <Utils/files.h>\n#include <Utils/strings.h>\n#include <algorithm>\n#include <cstdlib>\n#include <array>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#ifdef _WIN32\n#include <windows.h>\n#endif\n\nstatic const std::array<unsigned char, 65> base64_chars{\n        \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n        \"abcdefghijklmnopqrstuvwxyz\"\n        \"0123456789+/\"};\n\nstatic inline bool is_base64(unsigned char c)\n{\n    return (isalnum(c) || (c == '+') || (c == '/'));\n}\n\nstd::string util::filePath(const std::string &s)\n{\n    std::string t = s;\n    std::replace(t.begin(), t.end(), '\\\\', '_');\n    std::replace(t.begin(), t.end(), '/', '_');\n    std::replace(t.begin(), t.end(), ':', '_');\n    std::replace(t.begin(), t.end(), '*', '_');\n    std::replace(t.begin(), t.end(), '?', '_');\n    std::replace(t.begin(), t.end(), '\\\"', '_');\n    std::replace(t.begin(), t.end(), '<', '_');\n    std::replace(t.begin(), t.end(), '>', '_');\n    std::replace(t.begin(), t.end(), '|', '_');\n    return t;\n}\n\nbool util::strempty(const char *str)\n{\n    if(str)\n    {\n        if(SDL_strlen(str))\n            return true;\n    }\n\n    return false;\n}\n\nstd::string util::resolveRelativeOrAbsolute(const std::string &path, const std::vector<std::string> &relativeLookup)\n{\n    if(path.empty())\n        return \"\";\n    if(path[0] == ':')\n        return path;\n\n    if(Files::isAbsolute(path))\n    {\n        if(Files::fileExists(path))\n            return path;\n    }\n    else\n    {\n        for(const std::string &nextpath : relativeLookup)\n        {\n            std::string newCompletePath = nextpath + \"/\" + path;\n            if(Files::fileExists(newCompletePath))\n                return newCompletePath;\n        }\n    }\n\n    return \"\";\n}\n\ntemplate<class TList>\ninline void CSV2IntArr_CODE(const std::string &source, TList &dest, const typename TList::value_type &def)\n{\n    typedef typename TList::value_type T;\n\n    if(!source.empty())\n    {\n        Strings::List tmlL;\n        Strings::split(tmlL, source, ',');\n\n        for(std::string &fr : tmlL)\n        {\n            try\n            {\n                if(std::is_same<T, int>::value)\n                    dest.push_back(std::atoi(fr.c_str()));\n                else\n                    dest.push_back(int(std::strtod(fr.c_str(), nullptr)));\n            }\n            catch(...)\n            {\n                continue;\n            }\n            dest.pop_back();\n        }\n\n        if(dest.empty()) dest.push_back(def);\n    }\n    else\n        dest.push_back(def);\n}\n\nvoid util::CSV2IntArr(const std::string &source, std::vector<int> &dest)\n{\n    CSV2IntArr_CODE(source, dest, 0);\n}\n\nvoid util::CSV2DoubleArr(const std::string &source, std::vector<double> &dest)\n{\n    CSV2IntArr_CODE(source, dest, 0.0);\n}\n\nvoid util::base64_encode(std::string &ret, const unsigned char *bytes_to_encode, size_t in_len)\n{\n    int i = 0;\n    int j = 0;\n    unsigned char char_array_3[3];\n    unsigned char char_array_4[4];\n\n    while(in_len--)\n    {\n        char_array_3[i++] = *(bytes_to_encode++);\n\n        if(i == 3)\n        {\n            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;\n            char_array_4[1] = static_cast<unsigned char>(\n                                  (static_cast<unsigned int>(char_array_3[0] & 0x03) << 4) +\n                                  (static_cast<unsigned int>(char_array_3[1] & 0xf0) >> 4)\n                              );\n            char_array_4[2] = static_cast<unsigned char>(\n                                  (static_cast<unsigned int>(char_array_3[1] & 0x0f) << 2) +\n                                  (static_cast<unsigned int>(char_array_3[2] & 0xc0) >> 6)\n                              );\n            char_array_4[3] = char_array_3[2] & 0x3f;\n\n            for(i = 0; (i < 4) ; i++)\n                ret += static_cast<char>(base64_chars[char_array_4[i]]);\n\n            i = 0;\n        }\n    }\n\n    if(i)\n    {\n        for(j = i; j < 3; j++)\n            char_array_3[j] = '\\0';\n\n        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;\n        char_array_4[1] = static_cast<unsigned char>(\n                              (static_cast<unsigned int>(char_array_3[0] & 0x03) << 4) +\n                              (static_cast<unsigned int>(char_array_3[1] & 0xf0) >> 4)\n                          );\n        char_array_4[2] = static_cast<unsigned char>(\n                              (static_cast<unsigned int>(char_array_3[1] & 0x0f) << 2) +\n                              (static_cast<unsigned int>(char_array_3[2] & 0xc0) >> 6)\n                          );\n        char_array_4[3] = char_array_3[2] & 0x3f;\n\n        for(j = 0; (j < i + 1); j++)\n            ret += static_cast<char>(base64_chars[char_array_4[j]]);\n\n        while((i++ < 3))\n            ret += '=';\n    }\n}\n\nvoid util::base64_decode(std::string &ret, const std::string &encoded_string)\n{\n    size_t in_len = encoded_string.size();\n    int i = 0;\n    int j = 0;\n    size_t in_ = 0;\n    unsigned char char_array_4[4], char_array_3[3];\n\n    while(in_len-- && (encoded_string[in_] != '=') && is_base64(static_cast<unsigned char>(encoded_string[in_])))\n    {\n        char_array_4[i++] = static_cast<unsigned char>(encoded_string[in_]);\n        in_++;\n\n        if(i == 4)\n        {\n            for(i = 0; i < 4; i++)\n                char_array_4[i] = static_cast<unsigned char>(std::find(base64_chars.begin(), base64_chars.end(), char_array_4[i]) - base64_chars.begin());\n\n            char_array_3[0] = static_cast<unsigned char>(\n                                  static_cast<unsigned int>(char_array_4[0] << 2) +\n                                  static_cast<unsigned int>((char_array_4[1] & 0x30) >> 4)\n                              );\n            char_array_3[1] = static_cast<unsigned char>(\n                                  static_cast<unsigned int>((char_array_4[1] & 0xf) << 4) +\n                                  static_cast<unsigned int>((char_array_4[2] & 0x3c) >> 2)\n                              );\n            char_array_3[2] = static_cast<unsigned char>(\n                                  static_cast<unsigned int>((char_array_4[2] & 0x3) << 6) +\n                                  static_cast<unsigned int>(char_array_4[3])\n                              );\n\n            for(i = 0; (i < 3); i++)\n                ret += static_cast<char>(char_array_3[i]);\n\n            i = 0;\n        }\n    }\n\n    if(i)\n    {\n        for(j = i; j < 4; j++)\n            char_array_4[j] = 0;\n\n        for(j = 0; j < 4; j++)\n            char_array_4[j] = static_cast<unsigned char>(std::find(base64_chars.begin(), base64_chars.end(), char_array_4[j]) - base64_chars.begin());\n\n        char_array_3[0] = static_cast<unsigned char>(\n                              static_cast<unsigned int>(char_array_4[0] << 2) +\n                              static_cast<unsigned int>((char_array_4[1] & 0x30) >> 4)\n                          );\n        char_array_3[1] = static_cast<unsigned char>(\n                              static_cast<unsigned int>((char_array_4[1] & 0xf) << 4) +\n                              static_cast<unsigned int>((char_array_4[2] & 0x3c) >> 2)\n                          );\n        char_array_3[2] = static_cast<unsigned char>(\n                              static_cast<unsigned int>((char_array_4[2] & 0x3) << 6) +\n                              static_cast<unsigned int>(char_array_4[3])\n                          );\n\n        for(j = 0; (j < i - 1); j++)\n            ret += static_cast<char>(char_array_3[j]);\n    }\n\n    //Remove zero from end\n    if(ret.size() > 0)\n    {\n        if(ret[ret.size() - 1] == '\\0')\n            ret.resize(ret.size() - 1);\n    }\n}\n\n\n\nsize_t charsets_utils::utf8len(const char *s)\n{\n    size_t len = 0;\n\n    while(*s)\n        len += (*(s++) & 0xC0) != 0x80;\n\n    return len;\n}\n\n\nint charsets_utils::UTF8Str_To_WStr(std::wstring &dest, const std::string &source)\n{\n#ifdef _WIN32\n    dest.resize(source.length());\n    int newSize = MultiByteToWideChar(CP_UTF8, 0, source.c_str(), (int)source.length(), (wchar_t *)dest.c_str(), (int)source.length());\n    dest.resize(newSize);\n    return newSize;\n#else\n    (void)dest;\n    (void)source;\n    return static_cast<int>(utf8len(source.c_str()));\n#endif\n}\n\nint charsets_utils::WStr_To_UTF8Str(std::string &dest, const std::wstring &source)\n{\n#ifdef _WIN32\n    int dest_len = (int)source.length() * 4;\n    dest.resize(dest_len);\n    dest_len = WideCharToMultiByte(CP_UTF8, 0, source.c_str(), (int)source.length(), (LPSTR)dest.c_str(), dest_len, NULL, NULL);\n    dest.resize(dest_len);\n    return dest_len;\n#else\n    (void)dest;\n    (void)source;\n    return static_cast<int>(source.size());\n#endif\n}\n"
  },
  {
    "path": "lib/util.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n\n#ifndef UTIL_H\n#define UTIL_H\n\n#include <string>\n#include <vector>\n\n//#include <luabind/luabind.hpp>\n//#include <lua_includes/lua.hpp>\n\n#ifdef Q_CC_GNU\n#define gcc_force_inline __attribute__((always_inline, gnu_inline))\n#else\n#define gcc_force_inline\n#endif\n\nnamespace util\n{\nstd::string filePath(const std::string &s);\nbool strempty(const char *str);\n\nstd::string resolveRelativeOrAbsolute(const std::string &path, const std::vector<std::string> &relativeLookup);\n\nvoid CSV2IntArr(const std::string &source, std::vector<int> &dest);\nvoid CSV2DoubleArr(const std::string &source, std::vector<double> &dest);\nvoid base64_encode(std::string &ret, const unsigned char *bytes_to_encode, size_t in_len);\nvoid base64_decode(std::string &ret, std::string const &encoded_string);\n\ntemplate<typename _Tp, typename _Alloc = std::allocator<_Tp> >\nvoid clear_mem(std::vector<_Tp, _Alloc> &v)\n{\n    std::vector<_Tp, _Alloc> blank;\n    std::swap(v, blank);\n}\n\ninline void clear_mem(std::string &v)\n{\n    std::string blank;\n    std::swap(v, blank);\n}\n\ninline void clear_mem(std::wstring &v)\n{\n    std::wstring blank;\n    std::swap(v, blank);\n}\n\n} // util\n\nnamespace varadic_util\n{\n\ntemplate<int ...>\nstruct seq { };\n\ntemplate<int N, int ...S>\nstruct gens : gens < N - 1, N - 1, S... > { };\n\ntemplate<int ...S>\nstruct gens<0, S...>\n{\n    typedef seq<S...> type;\n};\n\n} // varadic_util\n\n\n\n//namespace luabind_utils\n//{\n//template<typename T>\n//static inline gcc_force_inline std::vector<T> convArrayTo(luabind::object &obj)\n//{\n//    std::vector<T> container;\n//    for(luabind::iterator it(obj), end; it != end; ++it)\n//    {\n//        try\n//        {\n//            container.push_back(luabind::object_cast<T>(*it));\n//        }\n//        catch(luabind::cast_failed & /*e*/) { }\n//    }\n//    return container;\n//}\n//}\n\nnamespace charsets_utils\n{\n/*!\n * \\brief returns length of UTF8 string line\n * \\param Input 8-bit string in UTF8 codepage\n * \\return number of characters (not a bytes!)\n */\nsize_t utf8len(const char *s);\nint UTF8Str_To_WStr(std::wstring &dest, const std::string  &source);\nint WStr_To_UTF8Str(std::string  &dest, const std::wstring &source);\n\n} // charsets_utils\n\n#endif // UTIL_H\n"
  },
  {
    "path": "pge_version.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n\n#ifndef GLOBAL_PGE_VERSION\n#define GLOBAL_PGE_VERSION\n\n#ifdef PGE_TOOLCHAIN_VERSION_1\n#   define V_VP1 PGE_TOOLCHAIN_VERSION_1\n#else\n#   define V_VP1 0\n#endif\n\n#ifdef PGE_TOOLCHAIN_VERSION_2\n#   define V_VP2 PGE_TOOLCHAIN_VERSION_2\n#else\n#   define V_VP2 0\n#endif\n\n#ifdef PGE_TOOLCHAIN_VERSION_3\n#   define V_VP3 PGE_TOOLCHAIN_VERSION_3\n#else\n#   define V_VP3 0\n#endif\n\n#ifdef PGE_TOOLCHAIN_VERSION_4\n#   define V_VP4 PGE_TOOLCHAIN_VERSION_4\n#else\n#   define V_VP4 0\n#endif\n\n#ifdef PGE_CONFIG_PACK_API_VERSION\n#   define V_CP_API PGE_CONFIG_PACK_API_VERSION\n#else\n#   define V_CP_API 0\n#endif\n\n#ifdef PGE_TOOLCHAIN_VERSION_REL\n#   define V_TOOLCHAIN_RELEASE PGE_TOOLCHAIN_VERSION_REL\n#else\n#   define V_TOOLCHAIN_RELEASE \"-unk\" //\"-alpha\",\"-beta\",\"-dev\", or \"\" aka \"release\"\n#endif\n\n#define GEN_VERSION_NUMBER_2(v1,v2)     v1 \".\" v2\n#define GEN_VERSION_NUMBER_3(v1,v2,v3)  v1 \".\" v2 \".\" v3\n#define GEN_VERSION_NUMBER_4(v1,v2,v3,v4)  v1 \".\" v2 \".\" v3 \".\" v4\n\n#define STR_VALUE_REAL(arg)  #arg\n#define STR_VALUE(verf)      STR_VALUE_REAL(verf)\n#define V_VP1_s STR_VALUE(V_VP1)\n#define V_VP2_s STR_VALUE(V_VP2)\n#define V_VP3_s STR_VALUE(V_VP3)\n#define V_VP4_s STR_VALUE(V_VP4)\n#if V_VP4 == 0\n#   if V_VP3 == 0\n#       define V_PROJECT_VERSION_NUM GEN_VERSION_NUMBER_2(V_VP1_s, V_VP2_s)\n#   else\n#       define V_PROJECT_VERSION_NUM GEN_VERSION_NUMBER_3(V_VP1_s, V_VP2_s, V_VP3_s)\n#   endif\n#else\n#   define V_PROJECT_VERSION_NUM GEN_VERSION_NUMBER_4(V_VP1_s, V_VP2_s, V_VP3_s, V_VP4_s)\n#endif\n\n\n\n// --------------- Version of whole project ---------------\n\n#define V_VERSION V_PROJECT_VERSION_NUM\n#define V_RELEASE \"\" //Developing state (for release this field must be empty)\n\n#define V_COPYRIGHT \"2020-2026 by Wohlstand\"\n\n#define V_COMPANY \"Moondust Team\"\n\n#define V_PGE_URL \"wohlsoft.ru\"\n\n#define V_PRODUCT_NAME \"Moondust\"\n\n\n\n// --------------- OPERATING SYSTEM ---------------\n\n#if defined(_WIN64)\n#   if defined(_M_ARM64)\n#       define OPERATION_SYSTEM \"Windows ARM64\"\n#   else\n#       define OPERATION_SYSTEM \"Windows x64\"\n#   endif\n#elif defined(_WIN32)\n#   define OPERATION_SYSTEM \"Windows\"\n#elif defined __APPLE__\n#   include <TargetConditionals.h>\n#   if TARGET_IPHONE_SIMULATOR\n#       define OPERATION_SYSTEM \"iOS Simulator\"\n#   elif TARGET_OS_IPHONE\n#       define OPERATION_SYSTEM \"iOS\"\n#   else\n#       define OPERATION_SYSTEM \"macOS\"\n#   endif\n#elif defined __FreeBSD__\n#   define OPERATION_SYSTEM \"FreeBSD\"\n#elif defined __gnu_linux__\n#   define OPERATION_SYSTEM \"GNU/Linux\"\n#elif defined __HAIKU__\n#   define OPERATION_SYSTEM \"Haiku\"\n#elif defined __EMSCRIPTEN__\n#   define OPERATION_SYSTEM \"Emscripten\"\n#elif defined(__ANDROID__)\n#   define OPERATION_SYSTEM \"Android\"\n#elif defined __WII__\n#   define OPERATION_SYSTEM \"Wii System\"\n#elif defined __16M__\n#   define OPERATION_SYSTEM \"DSi System\"\n#elif defined __3DS__\n#   define OPERATION_SYSTEM \"3DS System\"\n#elif defined __SWITCH__\n#   define OPERATION_SYSTEM \"Nintendo Switch\"\n#elif defined __vita__\n#   define OPERATION_SYSTEM \"Live Area (PS Vita)\"\n#else\n#   define OPERATION_SYSTEM \"Other\"\n#endif\n\n\n\n\n// --------------- Processor Architecture ---------------\n\n#if defined(_X86_) || defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86)\n#   define FILE_CPU \"x86 (32-bit)\"\n#elif defined(__x86_64__) || defined(__amd64__) || defined(_WIN64) || defined(_M_X64) || defined(_M_AMD64)\n#   define FILE_CPU \"x86_64 (64-bit)\"\n#elif defined(__arm__) || defined(__TARGET_ARCH_ARM) || defined(__ARM_ARCH)\n#   if defined(__ARM_ARCH_8__) \\\n     || defined(__ARM_ARCH_8A) \\\n     || defined(__ARM_ARCH_8A__) \\\n     || defined(__ARM_ARCH_8R__) \\\n     || defined(__ARM_ARCH_8M__) \\\n     || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 8) \\\n     || (defined(__ARM_ARCH) && __ARM_ARCH >= 8) || defined(_M_ARM64) || defined(_M_ARM64EC)\n#       define FILE_CPU \"ARM64 (64-bit)\"\n#   elif defined(__ARM_ARCH_7__) \\\n       || defined(__ARM_ARCH_7A__) \\\n       || defined(__ARM_ARCH_7R__) \\\n       || defined(__ARM_ARCH_7M__) \\\n       || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 7) \\\n       || (defined(__ARM_ARCH) && __ARM_ARCH >= 7)\n#       define FILE_CPU \"ARMv7 (32-bit)\"\n#   elif defined(__ARM_ARCH_6__) \\\n       || defined(__ARM_ARCH_6J__) \\\n       || defined(__ARM_ARCH_6T2__) \\\n       || defined(__ARM_ARCH_6Z__) \\\n       || defined(__ARM_ARCH_6K__) \\\n       || defined(__ARM_ARCH_6ZK__) \\\n       || defined(__ARM_ARCH_6M__) \\\n       || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 6) \\\n       || (defined(__ARM_ARCH) && __ARM_ARCH >= 6)\n#       define FILE_CPU \"ARMv6 (32-bit)\"\n#   elif defined(__ARM_ARCH_5TEJ__) \\\n      || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM >= 5)\n#       define FILE_CPU \"ARMv5 (32-bit)\"\n#   else\n#       define FILE_CPU \"ARM (Unknown)\"\n#   endif\n#elif defined(__mips__) || defined(__mips) || defined(__MIPS__)\n#   define FILE_CPU \"MIPS\"\n#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\\n   || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC)  \\\n   || defined(_M_MPPC) || defined(_M_PPC)\n#   if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__)\n#       define FILE_CPU \"PowerPC (64-bit)\"\n#   else\n#       define FILE_CPU \"PowerPC (32-bit)\"\n#   endif\n#elif defined(__riscv) // FIXME: Adjust if any extra macros needed\n#   if __riscv_xlen == 64\n#       define FILE_CPU \"RISC-V (64-bit)\"\n#   elif __riscv_xlen == 32\n#       define FILE_CPU \"RISC-V (32-bit)\"\n#   endif\n#elif defined(__loongarch64)\n#       define FILE_CPU \"LoongArch (64-bit)\"\n#elif defined(__loongarch)\n#       define FILE_CPU \"LoongArch (32-bit)\"\n#else\n#   define FILE_CPU \"unknown\"\n#endif\n\n\n\n// --------------- Date and time of the build ---------------\n#ifndef DISABLE_XTECH_BUILD_DATE\n#   define V_DATE_OF_BUILD __DATE__ \" \" __TIME__\n#else\n#   define V_DATE_OF_BUILD \"<Build timestamp disabled>\"\n#endif\n\n\n\n#endif // GLOBAL_PGE_VERSION\n"
  },
  {
    "path": "repo.sh",
    "content": "#!/bin/bash\n\nbak=~+\n\nfunction checkState()\n{\n    if [[ $? -eq 0 ]]\n    then\n        printf \"=== \\E[37;42mOK!\\E[0m ===\\n\\n\"\n    else\n        printf \"\\n=== AN ERROR OCCURRED! ===\\n\\n\"\n        cd ${bak}\n        exit 1\n    fi\n}\n\nflag_pack_src=false\nflag_pack_src_gz=false\nflag_pack_src_zip=false\n\n\nfor var in \"$@\"\ndo\n    case \"$var\" in\n        lupdate)\n            #dummy\n        ;;\n        lrelease)\n            #dummy\n        ;;\n        --help)\n            echo \"\"\n            printf \"=== \\e[44mRepo maintenance helper script for TheXTech\\e[0m ===\\n\"\n            echo \"\"\n            echo \"--- Actions ---\"\n            printf \" \\E[1;4mupdate-submodules\\E[0m- Pull all submodules up to their latest states\\n\"\n            printf \" \\E[1;4mrepair-submodules\\E[0m- Repair invalid or broken submodules\\n\"\n            printf \" \\E[1;4m--help\\E[0m           - Print this manual\\n\"\n            printf \" \\E[1;4mpack-src\\E[0m         - Create the source code archive\\n\"\n            printf \"                    (git-archive-all is required!)\\n\"\n            if [[ \"$(which git-archive-all)\" == \"\" ]]; then\n                printf \" \\E[0;4;41;37m<git-archive-all is not installed!>\\E[0m\\n\"\n            fi\n            echo \"\"\n            exit 1\n            ;;\n\n        update-submodules)\n            PATH=${PATH}:$PWD/utils\n            git submodule foreach submodule-update.sh \"$PWD/.gitmodules\"\n\n            # EXTRA update Java files of Android project\n            printf \"\\n\\n\"\n            echo \"Synchronize SDL Java headers...\"\n            rm -vf android-project/thextech/src/main/java/org/libsdl/app/*.java\n            cp -v 3rdparty/AudioCodecs/SDL2/android-java-files/*.java android-project/thextech/src/main/java/org/libsdl/app\n            exit 0\n            ;;\n\n        repair-submodules)\n            PATH=${PATH}:$PWD/utils\n            echo \"=== Cleaning-up old state...\"\n            git submodule foreach 'pwd; rm -Rf * .git*;'\n            echo \"=== Fetching new submodules...\"\n            git submodule init\n            git submodule update\n            echo \"\"\n            git submodule foreach submodule-update.sh \"$PWD/.gitmodules\"\n            echo \"\"\n            echo \"==== Fixed! ====\"\n            exit 0;\n            ;;\n\n        pack-src)\n            flag_pack_src=true\n            ;;\n        gz)\n            flag_pack_src_gz=true\n            ;;\n        zip)\n            flag_pack_src_zip=true\n            ;;\n\n        *)\n            echo \"--------------------------------------------------------------------\"\n            echo \"Invalid argument '$var', please type '$0 --help' to get the usage.\"\n            echo \"--------------------------------------------------------------------\"\n            exit 1;\n        ;;\n    esac\ndone\n\n# ===== Source code packer =====\nif ${flag_pack_src} ; then\n    if [[ ! -d build-archives ]]; then\n        mkdir build-archives\n    fi\n    if ${flag_pack_src_gz} ; then\n        ARFORMAT=tar.gz\n    elif ${flag_pack_src_zip} ; then\n        ARFORMAT=zip\n    else\n        ARFORMAT=tar.bz2\n    fi\n\n    echo \"Packing source code...\"\n    git archive-all -v --force-submodules build-archives/thextech-full-src.${ARFORMAT}\n    checkState\n\n    printf \"\\n=== Packed! ===\\n\\n\"\n    cd ${bak}\n\n    exit 0;\nfi\n"
  },
  {
    "path": "resources/PkgInfo",
    "content": "APPLTXTH"
  },
  {
    "path": "resources/_dev_scripts/snd_id_2_enum.sh",
    "content": "#!/bin/bash\n\nARR=\"SFX_Jump SFX_Stomp SFX_BlockHit SFX_BlockSmashed SFX_PlayerShrink SFX_PlayerGrow SFX_Mushroom SFX_PlayerDied SFX_ShellHit SFX_Skid SFX_DropItem SFX_GotItem SFX_Camera SFX_Coin SFX_1up SFX_Lava SFX_Warp SFX_Fireball SFX_CardRouletteClear SFX_BossBeat SFX_DungeonClear SFX_Bullet SFX_Grab SFX_Spring SFX_HammerToss SFX_Slide SFX_NewPath SFX_LevelSelect SFX_Do SFX_Pause SFX_Key SFX_PSwitch SFX_Tail SFX_Raccoon SFX_Boot SFX_Smash SFX_Twomp SFX_BirdoSpit SFX_BirdoHit SFX_CrystalBallExit SFX_BirdoBeat SFX_BigFireball SFX_Fireworks SFX_BowserKilled SFX_GameBeat SFX_Door SFX_Message SFX_Yoshi SFX_YoshiHurt SFX_YoshiTongue SFX_YoshiEgg SFX_GotStar SFX_ZeldaKill SFX_PlayerDied2 SFX_YoshiSwallow SFX_SonicRing SFX_DryBones SFX_Checkpoint SFX_DraginCoin SFX_TapeExit SFX_Blaarg SFX_WartBubbles SFX_WartKilled SFX_SMBlockHit SFX_SMKilled SFX_SMHurt SFX_SMGlass SFX_SMBossHit SFX_SMCry SFX_SMExplosion SFX_Climbing SFX_Swim SFX_Grab2 SFX_Saw SFX_Throw SFX_PlayerHit SFX_ZeldaStab SFX_ZeldaHurt SFX_ZeldaHeart SFX_ZeldaDied SFX_ZeldaRupee SFX_ZeldaFire SFX_ZeldaItem SFX_ZeldaKey SFX_ZeldaShield SFX_ZeldaDash SFX_ZeldaFairy SFX_ZeldaGrass SFX_ZeldaHit SFX_ZeldaSwordBeam SFX_Bubble SFX_PSwitchTimeout SFX_SwooperFlap SFX_Iceball SFX_Freeze SFX_Icebreak SFX_PlayerHammer SFX_SproutVine\"\n\ncount=1\nfor q in $ARR; do\n    echo \"$q - $count\"\n    find . -type f -name \"*.cpp\" -exec ./snd_id_sed.sh {} $count $q \\;\n    count=$((count + 1))\ndone\n"
  },
  {
    "path": "resources/_dev_scripts/snd_id_sed.sh",
    "content": "#!/bin/bash\necho \"$1 ($2 -> $3)\"\nsed -i \"s/PlaySound($2)/PlaySound($3)/g\" $1\n"
  },
  {
    "path": "resources/_dev_scripts/xserver.py",
    "content": "#!/usr/bin/python3\n\n# Copyright (C) 2025 ds-sloth\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nimport selectors\nimport socket\nimport time\nimport heapq\nimport threading\nimport random\nimport os\n\n# roughly, an enum of header bytes for events (*control channel*)\n# HEADER_CLIENT_JOIN = 1\n# HEADER_CLIENT_LOSS = 2\n# HEADER_TEXT_EVENT = 3\nHEADER_FRAME_COMPLETE = 4\n# HEADER_YOU_ARE = 5\n# HEADER_RAND_SEED = 6\n# HEADER_TIME_IS = 7\nHEADER_LEFT_ROOM = 8\n\nHEADER_ROOM_KEY = 9\nHEADER_ROOM_INFO = 10\n\nHEADER_CREATE_ROOM = 11\nHEADER_JOIN_ROOM = 12\n\nHEADER_DATA_CHANNEL = 13\nHEADER_ACK = 14\n\nHEADER_PUT_SESSION = 15\nHEADER_GET_SESSION = 16\n\n# an enum of header bytes for messages (*data channel*)\nMESSAGE_FRAME_BEGIN = 32 # meta-message: the following messages belong to the named frame\nMESSAGE_ADD_CLIENT = 33\nMESSAGE_DROP_CLIENT = 34\nMESSAGE_FRAME_END = 35 # meta-message (TCP only): the previously named frame is now complete\nMESSAGE_TRANSMIT_START = 36 # meta-message (UDP only): transmission is only valid if this frame has passed, generally acknowledges receipt of messages up to the named frame\n\ndef display_room_key(room_key):\n    if len(room_key) != 4:\n        return ''\n\n    room_key_int = (room_key[0] << 24) + (room_key[1] << 16) + (room_key[2] << 8) + (room_key[3] << 0)\n\n    if (room_key_int & (192 << 24)):\n        return ''\n\n    chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ346789'\n\n    letters = ''.join([chars[(room_key_int >> off) % 32] for off in (25, 20, 15, 10, 5, 0)])\n    return letters[:3] + '-' + letters[3:]\n\nclass Connection:\n    def __init__(self, server, conn, address):\n        self.server = server\n        self.conn = conn\n        self.data_conn = None\n        self.address = address\n\n        self.session_key = b'\\0\\0\\0\\0'\n\n        # session config state\n        self.providing_session = False\n        self.receiving_session = False\n        self.session_received = 0\n\n        # room state\n        self.room = None\n        self.id = 0\n        self.acked_to = 0\n        self.acked_frame = -1\n        self.double_acked_frame = -60\n        self.warned_frame = 0\n\n        self.tcp_send_start = 0\n        self.tcp_send_end = 0\n        self.tcp_sent_frame = -1\n\n        self.tcp_frame_in_progress = []\n        self.tcp_frame_no = -1\n\n        # UDP state\n        self.udp_dest = None\n        self.udp_recv = []\n        self.udp_send = b''\n\n    def set_room(self, room, id):\n        self.providing_session = False\n        self.receiving_session = False\n        self.session_received = 0\n\n        self.room = room\n        self.id = id\n        self.acked_to = -1\n        self.acked_frame = -1\n        self.double_acked_frame = -60\n        self.warned_frame = 0\n\n        self.tcp_send_start = 0\n        self.tcp_send_end = 0\n        self.tcp_sent_frame = -1\n\n    def read_in_room(self):\n        # special case for when uploading a session object\n        if self.providing_session:\n            try:\n                self.room.session_config += self.conn.recv(self.room.session_config_length - len(self.room.session_config))\n            except BlockingIOError:\n                pass\n\n            if len(self.room.session_config) < self.room.session_config_length:\n                return\n\n            # acknowledge receipt of session config\n            self.conn.sendall(bytes([HEADER_GET_SESSION]))\n            self.receiving_session = False\n\n        # conn is the socket object\n        try:\n            header = self.conn.recv(1)\n        except BlockingIOError:\n            return\n\n        if len(header) != 1:\n            self.room.kill(self)\n            return\n\n        if header[0] == HEADER_PUT_SESSION:\n            session_config_length = self.conn.recv(4)\n            if len(session_config_length) != 4 or self.room.session_config_length != 0:\n                self.room.kill(self)\n                return\n\n            self.providing_session = True\n            self.room.session_config_length = (session_config_length[0] << 24) + (session_config_length[1] << 16) + (session_config_length[2] << 8) + (session_config_length[3] << 0)\n            return self.read_in_room()\n\n        if header[0] == HEADER_GET_SESSION:\n            self.receiving_session = True\n            self.session_received = -1\n            return\n\n        try:\n            header += self.conn.recv(3)\n        except BlockingIOError:\n            pass\n        if len(header) != 4:\n            self.room.kill(self)\n            return\n\n        if header == b'\\0\\0\\0\\0':\n            self.room.unregister(self)\n\n            self.room = None\n            self.server.register(self)\n\n            self.conn.sendall(bytes([HEADER_LEFT_ROOM]))\n            self.data_conn.close()\n            self.data_conn = None\n\n            return\n\n    def read_data(self):\n        # conn is the socket object\n        while True:\n            try:\n                message = self.data_conn.recv(4)\n            except BlockingIOError:\n                break\n\n            if len(message) != 4:\n                self.room.kill(self)\n                return\n\n            if message[0] == MESSAGE_FRAME_BEGIN or message[0] == MESSAGE_FRAME_END:\n                if self.tcp_frame_no > self.acked_frame:\n                    self.acked_frame = self.tcp_frame_no\n\n                    for msg in self.tcp_frame_in_progress:\n                        self.room.enqueue_text_event(self.tcp_frame_no, self.id, msg)\n                        print(\"Got 1 new event from TCP\")\n\n                    self.tcp_frame_in_progress = []\n\n                self.tcp_frame_no = -1\n\n            frame_no = message[1] * 256 * 256 + message[2] * 256 + message[3]\n            if message[0] == MESSAGE_FRAME_BEGIN:\n                self.tcp_frame_no = frame_no\n                if frame_no - 1 > self.acked_frame:\n                    self.acked_frame = frame_no - 1\n                else:\n                    if frame_no > self.acked_frame:\n                        self.acked_frame = frame_no\n            elif self.tcp_frame_no > self.acked_frame:\n                self.tcp_frame_in_progress.append(message)\n\n    def read_data_udp(self, msg):\n        if len(msg) % 4 != 0:\n            self.room.kill(self)\n            return\n\n        udp_frame_index = -1\n        for i in range(0, len(msg), 4):\n            message = msg[i : i + 4]\n\n            if message[0] == MESSAGE_FRAME_BEGIN or message[0] == MESSAGE_FRAME_END:\n                udp_frame_index = message[1] * 256 * 256 + message[2] * 256 + message[3]\n                if message[0] == MESSAGE_FRAME_END and udp_frame_index > self.acked_frame:\n                    self.acked_frame = udp_frame_index\n                elif udp_frame_index - 1 > self.acked_frame:\n                    self.acked_frame = udp_frame_index - 1\n            else:\n                if udp_frame_index > self.acked_frame:\n                    self.room.enqueue_text_event(udp_frame_index, self.id, message)\n                    print(\"Got 1 new event from UDP\")\n\n    def set_data_conn(self, data_conn):\n        if self.data_conn:\n            return\n\n        self.data_conn = data_conn\n        if self.room:\n            self.room.selector.register(self.data_conn, selectors.EVENT_READ, self.read_data)\n\n    def read_in_lobby(self):\n        header = self.conn.recv(1)\n        if len(header) != 1:\n            self.server.kill(self)\n            return\n\n        if header[0] == HEADER_ROOM_INFO:\n            room_key = self.conn.recv(4)\n            if len(room_key) != 4:\n                self.server.kill(self)\n                return\n\n            room = self.server.load_room(room_key)\n            if not room or room.room_key != room_key:\n                self.conn.sendall(bytes([HEADER_ROOM_INFO]) + room_key + b'\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0')\n                return\n\n            self.conn.sendall(bytes([HEADER_ROOM_INFO]) + room.room_key + room.room_info)\n        elif header[0] == HEADER_JOIN_ROOM:\n            room_key = self.conn.recv(4)\n            if len(room_key) != 4:\n                self.server.kill(self)\n                return\n\n            room = self.server.load_room(room_key)\n            if not room:\n                self.conn.sendall(bytes([HEADER_ROOM_KEY]) + b'\\0' * 12)\n                return\n\n            self.server.unregister(self)\n            room.add(self)\n        elif header[0] == HEADER_CREATE_ROOM:\n            room_info = self.conn.recv(12)\n\n            room = self.server.create_room(room_info)\n            if not room:\n                self.conn.sendall(bytes([HEADER_ROOM_KEY]) + b'\\0' * 12)\n                return\n\n            self.server.unregister(self)\n            room.add(self)\n        elif header[0] == HEADER_DATA_CHANNEL:\n            # unregister as a normal client\n            self.server.unregister(self)\n            self.server.unregister_session(self.session_key)\n            self.session_key = b'\\0\\0\\0\\0'\n\n            # transfer the conn to the correct client (if it exists)\n            session_key = self.conn.recv(4)\n            main_client = self.server.find_session(session_key)\n            if main_client:\n                main_client.set_data_conn(self.conn)\n\n            self.conn = None\n\n    def read(self):\n        if self.room is not None:\n            self.read_in_room()\n            return\n\n        self.read_in_lobby()\n\nclass Room:\n    def __init__(self, server, room_key):\n        # reference to the main server\n        self.server = server\n        self.frame_length = self.server.frame_length\n\n        # this object will tell us when any network events have occurred for this room's clients\n        self.selector = selectors.DefaultSelector()\n\n        # room information\n        self.room_key = room_key\n        assert(len(room_key) == 4)\n\n        self.room_info = b'\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0'\n\n        # room state\n        self.frame_no = 0\n        self.session_config_length = 0\n        self.session_config = bytes()\n        self.sent_history = bytes()\n\n        # this is a list of clients added by the main thread\n        self.added_clients = []\n        self.kill_timer = 0\n        self.killed = False\n        self.kill_lock = threading.Lock()\n\n        # this is a list of clients\n        self.clients = []\n        self.freed_clients = set()\n        self.newly_freed_clients = set()\n\n        # this is the current frame, and a queue of events\n        self.event_queue = []\n\n    def enqueue_text_event(self, frame_no, id, event_text):\n        # force event to happen in future (if client has lag)\n        if frame_no < self.frame_no:\n            frame_no = self.frame_no\n\n        # encode event\n        if len(event_text) != 4:\n            print(f\"Warning: invalid event length {len(event_text)}, 4 expected.\")\n            return\n        if event_text[1] != id:\n            # figure this out...\n            print(f\"Warning: invalid event client ID {event_text[1]}, {id} expected.\")\n            return\n\n        # add to queue\n        heapq.heappush(self.event_queue, (frame_no, event_text))\n\n    def unregister(self, client):\n        # free client's network state\n        try:\n            self.selector.unregister(client.conn)\n        except KeyError:\n            pass\n        except ValueError:\n            pass\n\n        try:\n            self.selector.unregister(client.data_conn)\n        except KeyError:\n            pass\n        except ValueError:\n            pass\n\n        # get client's ID\n        dead_id = client.id\n\n        # if the client is not in the correct location in the client list, return\n        if not self.clients[dead_id] is client:\n            return\n\n        # create loss event and add to queue\n        serialized_event = bytes([MESSAGE_DROP_CLIENT, 255, 0, dead_id])\n        heapq.heappush(self.event_queue, (self.frame_no, serialized_event))\n\n        # remove reference to this client\n        self.clients[dead_id] = None\n\n        # mark client as newly dead (can't re-use ID this frame)\n        self.newly_freed_clients.add(dead_id)\n\n    def kill(self, client):\n        self.unregister(client)\n        client.conn.close()\n        if client.data_conn:\n            client.data_conn.close()\n\n        if client.providing_session and len(self.session_config) < self.session_config_length:\n            self.session_config = bytes()\n            self.session_config_length = 0\n\n    def add(self, client):\n        # find an ID and slot for the client\n        if self.freed_clients:\n            new_id = min(self.freed_clients)\n            self.freed_clients.discard(new_id)\n        else:\n            new_id = len(self.clients)\n            self.clients.append(None)\n\n        # add the client to the room\n        client.set_room(self, new_id)\n        self.clients[new_id] = client\n\n        # create join event and add to queue\n        serialized_event = bytes([MESSAGE_ADD_CLIENT, 255, 0, new_id])\n        heapq.heappush(self.event_queue, (self.frame_no, serialized_event))\n\n        # tell the client who they are, what the current time is, and the room's key (may throw)\n        if client.session_key == b'\\0\\0\\0\\0':\n            self.server.create_session(client)\n\n        client.conn.sendall(bytes([\n            HEADER_ROOM_KEY, self.room_key[0], self.room_key[1], self.room_key[2], self.room_key[3],\n            new_id,\n            self.frame_no // (256 * 256), (self.frame_no // 256) % 256, self.frame_no % 256,\n            client.session_key[0], client.session_key[1], client.session_key[2], client.session_key[3],\n            ]))\n        client.acked_to = 0\n        client.acked_frame = -1\n        client.double_acked_frame = -60\n        client.warned_frame = self.frame_no\n        client.tcp_sent_frame = -1\n\n        # register this socket's events to go to the client\n        self.selector.register(client.conn, selectors.EVENT_READ, client.read)\n        if client.data_conn:\n            self.selector.register(client.data_conn, selectors.EVENT_READ, client.read_data)\n\n    def transmit(self, client):\n        # send UDP\n        if client.udp_send and client.udp_dest:\n            self.server.udp_socket.sendto(client.udp_send, client.udp_dest)\n            # print('Sending', list(client.udp_send))\n\n        # send TCP\n        if client.data_conn and (client.tcp_send_start < client.tcp_send_end or (not client.udp_dest and client.tcp_send_end == len(self.sent_history))):\n            try:\n                client.tcp_send_start += client.data_conn.send(self.sent_history[client.tcp_send_start:client.tcp_send_end])\n                if client.tcp_send_start == len(self.sent_history):\n                    client.data_conn.sendall(bytes([MESSAGE_FRAME_END, self.frame_no // (256 * 256), (self.frame_no // 256) % 256, self.frame_no % 256]))\n                    client.tcp_send_end = 0\n            except BlockingIOError:\n                pass\n            except ConnectionResetError:\n                pass\n            except BrokenPipeError:\n                pass\n\n    def process_frame(self):\n        # print(f\"Debug: starting processing for frame {self.frame_no}\")\n\n        # thread-safe way of adopting added clients queue\n        added_clients = self.added_clients\n        self.added_clients = []\n\n        for client in added_clients:\n            try:\n                self.add(client)\n            except Exception as e:\n                print(e)\n                self.kill(client)\n\n        # sync all network events\n        events = self.selector.select(0)\n\n        for key, mask in events:\n            # mask is the event that was triggered (always selectors.EVENT_READ)\n            callback = key.data\n            try:\n                callback()\n            except Exception as e:\n                print(e)\n                self.kill(callback.__self__)\n\n        # sync UDP events\n        for client in self.clients:\n            if client is None:\n                continue\n\n            got = client.udp_recv\n            client.udp_recv = []\n\n            for msg in got:\n                client.read_data_udp(msg)\n\n        # don't run anything until we have a session_config\n        if not self.session_config or len(self.session_config) < self.session_config_length:\n            return\n\n        # skip the frame if nobody is connected\n        if not self.newly_freed_clients:\n            has_any_client = False\n\n            for client in self.clients:\n                if client is None:\n                    continue\n\n                has_any_client = True\n                break\n\n            if not has_any_client:\n                return\n\n        # increment the frame number\n        self.frame_no += 1\n\n        # combine all events from this frame\n        serialized_events = bytes()\n\n        while self.event_queue and self.event_queue[0][0] <= self.frame_no:\n            event_to_send = heapq.heappop(self.event_queue)\n\n            event_frame = event_to_send[0]\n            serialized_event = event_to_send[1]\n\n            serialized_events += serialized_event\n\n            # debug info\n            # if serialized_event[0] == HEADER_CLIENT_JOIN:\n            #     print(f\"Debug: sending CLIENT_JOIN ({serialized_event[1]})\")\n            # elif serialized_event[0] == HEADER_CLIENT_LOSS:\n            #     print(f\"Debug: sending CLIENT_LOSS ({serialized_event[1]})\")\n            print(f\"Debug: sending TEXT_EVENT (from {serialized_event[1]}): {list(serialized_event)}\")\n\n            if event_frame != self.frame_no:\n                print(f\"Warning: sent an event for frame {event_to_send[0]} on frame {self.frame_no}. This is not a normal lag situation.\")\n\n        # add the frame-begin event\n        if serialized_events:\n            serialized_events = bytes([MESSAGE_FRAME_BEGIN, self.frame_no // (256 * 256), (self.frame_no // 256) % 256, self.frame_no % 256]) + serialized_events\n\n        # actually send the frame's events to all client sockets\n        self.sent_history += serialized_events\n\n        for client in self.clients:\n            if client is None:\n                continue\n\n            if client.receiving_session:\n                if client.session_received == -1:\n                    client.conn.sendall(bytes([HEADER_PUT_SESSION,\n                        (self.session_config_length // (256 * 256 * 256)) % 256,\n                        (self.session_config_length // (      256 * 256)) % 256,\n                        (self.session_config_length //             (256)) % 256,\n                        (self.session_config_length                     ) % 256]))\n                    client.session_received = 0\n\n                try:\n                    client.session_received += client.conn.send(self.session_config[client.session_received:])\n                except BlockingIOError:\n                    pass\n\n                if client.session_received == self.session_config_length:\n                    client.receiving_session = False\n                else:\n                    continue\n\n            if client.acked_to != -1 and (client.data_conn or client.udp_dest):\n                # adjust sent_to based on acked frame\n                if client.udp_dest and client.acked_frame >= 0:\n                    while client.acked_to + 4 <= len(self.sent_history):\n                        if self.sent_history[client.acked_to] == MESSAGE_FRAME_BEGIN and \\\n                                self.sent_history[client.acked_to + 1 : client.acked_to + 4] >= bytes([client.acked_frame // (256 * 256), (client.acked_frame // 256) % 256, client.acked_frame % 256]):\n                            break\n                        client.acked_to += 4\n\n                MTU_size = 250\n                if client.udp_dest and len(self.sent_history) - client.acked_to <= MTU_size - 8:\n                    start = max(client.acked_frame, client.tcp_sent_frame + 1)\n                    if start >= 0:\n                        ack = bytes([MESSAGE_TRANSMIT_START, start // (256 * 256), (start // 256) % 256, start % 256])\n                    else:\n                        ack = bytes()\n                    client.udp_send = ack + self.sent_history[client.acked_to:] + bytes([MESSAGE_FRAME_END, self.frame_no // (256 * 256), (self.frame_no // 256) % 256, self.frame_no % 256])\n                # add the tail of the room history to the TCP send queue\n                else:\n                    if client.tcp_send_start == client.tcp_send_end:\n                        client.tcp_send_start = client.acked_to\n                    client.tcp_send_end = len(self.sent_history)\n                    client.acked_to = client.tcp_send_end\n                    client.tcp_sent_frame = self.frame_no\n                    client.udp_send = bytes()\n\n                self.transmit(client)\n\n            # warn clients if they are falling behind (in the future, this will happen based on whether the UDP MTU has been exceeded)\n            if self.frame_no - max(client.acked_frame, client.warned_frame) > 20:\n                # print('Warning client of catchup', self.frame_no, client.acked_frame, client.warned_frame)\n                try:\n                    client.conn.sendall(bytes([HEADER_FRAME_COMPLETE, self.frame_no // (256 * 256), (self.frame_no // 256) % 256, self.frame_no % 256]))\n                except BlockingIOError:\n                    pass\n                except ConnectionResetError:\n                    pass\n                except BrokenPipeError:\n                    pass\n                client.warned_frame = self.frame_no\n\n            # TCP mode: double-ack clients to let them know their latency\n            if client.acked_frame - client.double_acked_frame > 60:\n                print('Client latency appears to be', self.frame_no - client.acked_frame)\n                try:\n                    client.conn.sendall(bytes([HEADER_ACK, client.acked_frame // (256 * 256), (client.acked_frame // 256) % 256, client.acked_frame % 256]))\n                except BlockingIOError:\n                    pass\n                except ConnectionResetError:\n                    pass\n                except BrokenPipeError:\n                    pass\n                client.double_acked_frame = client.acked_frame\n\n        # update the free client ID list\n        self.freed_clients.update(self.newly_freed_clients)\n        self.newly_freed_clients = set()\n\n    def frame_loop(self):\n        while True:\n            start_time = time.time()\n            self.process_frame()\n            end_time = time.time()\n\n            proc_time = end_time - start_time\n\n            if(proc_time > self.frame_length):\n                print(f'Warning: overlong frame {self.frame_no - 1} ({proc_time}s > {self.frame_length}s)')\n            else:\n                time.sleep(self.frame_length - proc_time)\n                # for i in range(2):\n                #     time.sleep((self.frame_length - proc_time) / 2)\n\n                #     for client in self.clients:\n                #         if client is None:\n                #             continue\n                #         self.transmit(client)\n\n\nclass Server:\n    def __init__(self, bind_address, bind_port, frame_length):\n        self.frame_length = frame_length\n\n        # this object will tell us when any network events have occurred\n        self.selector = selectors.DefaultSelector()\n\n        # this is the TCP server socket\n        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        self.socket.bind((bind_address, bind_port))\n        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)\n        self.socket.listen(100)\n        self.socket.setblocking(False)\n        self.selector.register(self.socket, selectors.EVENT_READ, self.accept)\n\n        self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        self.udp_socket.bind((bind_address, bind_port))\n        self.udp_socket.setblocking(False)\n        self.selector.register(self.udp_socket, selectors.EVENT_READ, self.read_udp)\n\n        # dictionary of loaded rooms\n        self.loaded_rooms = {}\n        self.room_insertion_lock = threading.Lock()\n\n        # dictionary of active sessions\n        self.sessions = {}\n        self.session_kill_list = [] # eventually, add a timeout list for unexpectedly disconnected sessions here\n        self.sessions_lock = threading.Lock()\n\n        # dictionary of UDP connections to clients (FIXME: need to timeout and deallocate these when client sessions are killed)\n        self.udp_connections = {}\n\n        # directory to store saved rooms\n        self.room_directory = '/tmp/py'\n        os.makedirs(self.room_directory, exist_ok=True)\n\n        # FIXME: add some rate-limiting system soon\n\n    def insert_room(self, new_room):\n        with self.room_insertion_lock:\n            if not new_room.room_key in self.loaded_rooms:\n                self.loaded_rooms[new_room.room_key] = new_room\n                threading.Thread(target=new_room.frame_loop, daemon=True).start()\n\n    def create_room(self, room_info):\n        while True:\n            room_key = bytes([random.randint(0, 63), random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)])\n            display_key = display_room_key(room_key)\n\n            if room_key == b'\\0\\0\\0\\0':\n                continue\n\n            try:\n                open(self.room_directory + display_key, 'x').close()\n            except FileExistsError:\n                pass\n            else:\n                break\n\n        print(\"Creating room\", display_key)\n\n        new_room = Room(self, room_key)\n        new_room.room_info = room_info\n        self.insert_room(new_room)\n\n        return new_room\n\n    def create_session(self, client):\n        with self.sessions_lock:\n            while True:\n                session_key = bytes([random.randint(128, 255), random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)])\n\n                if session_key in self.sessions:\n                    continue\n\n                break\n\n            self.sessions[session_key] = client\n            client.session_key = session_key\n\n    def find_session(self, session_key):\n        with self.sessions_lock:\n            if session_key in self.sessions:\n                return self.sessions[session_key]\n\n        return None\n\n    def unregister_session(self, session_key):\n        with self.sessions_lock:\n            if session_key in self.sessions:\n                del self.sessions[session_key]\n\n    def load_room(self, room_key):\n        display_key = display_room_key(room_key)\n\n        try:\n            room = self.loaded_rooms[room_key]\n\n            with room.kill_lock:\n                if not room.killed:\n                    room.kill_timer = 0\n                    return room\n\n            room = None\n        except KeyError:\n            pass\n\n        display_key = display_room_key(room_key)\n\n        if not display_key or not os.path.exists(self.room_directory + display_key) or os.path.getsize(self.room_directory + display_key) == 0:\n            return None\n\n        loading_room = Room(self, room_key)\n        loading_room.load_from_file()\n\n        self.insert_room(loading_room)\n        return self.loaded_rooms[room_key]\n\n    def accept(self):\n        # create and set up the new socket\n        conn, address = self.socket.accept()\n        conn.setblocking(False)\n        conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)\n\n        # add a client\n        new_client = Connection(self, conn, address)\n\n        # wait for events on this client\n        self.selector.register(new_client.conn, selectors.EVENT_READ, new_client.read)\n\n    def read_udp(self):\n        data, addr = self.udp_socket.recvfrom(256)\n\n        if addr in self.udp_connections and data[0] < 128:\n            self.udp_connections[addr].udp_recv.append(data)\n        elif len(data) == 4 and not addr in self.udp_connections:\n            print(data, addr)\n            client = self.find_session(data)\n            if client:\n                self.udp_connections[addr] = client\n                client.udp_dest = addr\n                client.udp_recv = []\n                client.udp_send = b''\n\n    def unregister(self, client):\n        try:\n            self.selector.unregister(client.conn)\n        except KeyError:\n            pass\n        except ValueError:\n            pass\n\n    def kill(self, client):\n        self.unregister(client)\n        client.conn.close()\n\n        if client.data_conn:\n            client.data_conn.close()\n\n    def process(self):\n        # sync all network events\n        events = self.selector.select(None)\n\n        for key, mask in events:\n            # mask is the event that was triggered (always selectors.EVENT_READ)\n            callback = key.data\n            try:\n                callback()\n            except Exception as e:\n                print(e)\n                # don't kill self on failed accept, just ignore\n                if callback.__self__ is not self:\n                    self.kill(callback.__self__)\n\n    def main_loop(self):\n        while True:\n            self.process()\n\n\n# this is what happens when you run the script directly\nif __name__ == '__main__':\n    import sys\n\n    server = Server(sys.argv[1] if len(sys.argv) >= 2 else '0.0.0.0', 4305, 0.0156)\n    server.main_loop()\n"
  },
  {
    "path": "resources/emscripten/manifest.json.in",
    "content": "{\n  \"name\": \"${THEXTECH_MANIFEST_NAME_OUT}\",\n  \"id\": \"${THEXTECH_MANIFEST_ID_OUT}\",\n  \"description\": \"${THEXTECH_MANIFEST_DESC_OUT}\",\n  \"display\": \"fullscreen\",\n  \"orientation\": \"landscape-primary\",\n  \"scope\": \"${THEXTECH_DEPLOY_URL}\",\n  \"start_url\": \"${THEXTECH_DEPLOY_URL}index.html\",\n  \"icons\": [\n    {\n      \"src\": \"thextech_32.png\",\n      \"sizes\": \"32x32\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"thextech_256.png\",\n      \"sizes\": \"256x256\",\n      \"type\": \"image/png\"\n    }\n  ]\n}\n"
  },
  {
    "path": "resources/emscripten/shell_minimal.html",
    "content": "<!doctype html>\n<html lang=\"en-us\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n    <link rel=\"shortcut icon\" href=\"favicon.ico\" type=\"image/x-icon\">\n    <link rel=\"manifest\" href=\"manifest.json\" />\n    <script type=\"text/javascript\">\n      if('serviceWorker' in navigator)\n      {\n        window.addEventListener('load', function()\n        {\n          navigator.serviceWorker.register('sw.js');\n        });\n      }\n    </script>\n    <title>TheXTech Engine</title>\n    <style>\n      .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }\n      textarea.emscripten { font-family: monospace; width: 80%; }\n      div.emscripten { text-align: center; }\n      div.emscripten_border { border: 1px solid black; }\n      /* the canvas *must not* have any border or padding, or mouse coords will be wrong */\n      canvas.emscripten { border: 0px none; background-color: black; }\n\n      body { margin: 0; }\n\n      .spinner {\n        height: 50px;\n        width: 50px;\n        margin: 0px auto;\n        -webkit-animation: rotation .8s linear infinite;\n        -moz-animation: rotation .8s linear infinite;\n        -o-animation: rotation .8s linear infinite;\n        animation: rotation 0.8s linear infinite;\n        border-left: 10px solid rgb(0,150,240);\n        border-right: 10px solid rgb(0,150,240);\n        border-bottom: 10px solid rgb(0,150,240);\n        border-top: 10px solid rgb(100,0,200);\n        border-radius: 100%;\n        background-color: rgb(200,100,250);\n      }\n      @-webkit-keyframes rotation {\n        from {-webkit-transform: rotate(0deg);}\n        to {-webkit-transform: rotate(360deg);}\n      }\n      @-moz-keyframes rotation {\n        from {-moz-transform: rotate(0deg);}\n        to {-moz-transform: rotate(360deg);}\n      }\n      @-o-keyframes rotation {\n        from {-o-transform: rotate(0deg);}\n        to {-o-transform: rotate(360deg);}\n      }\n      @keyframes rotation {\n        from {transform: rotate(0deg);}\n        to {transform: rotate(360deg);}\n      }\n\n    </style>\n  </head>\n  <body>\n    <hr/>\n    <figure style=\"overflow:visible;\" id=\"spinner\"><div class=\"spinner\"></div><center style=\"margin-top:0.5em\"><strong>TheXTech on emscripten</strong></center></figure>\n    <div class=\"emscripten\" id=\"status\">Downloading...</div>\n    <div class=\"emscripten\">\n      <progress value=\"0\" max=\"100\" id=\"progress\" hidden=1></progress>\n    </div>\n    <div class=\"emscripten_border\">\n      <canvas class=\"emscripten\" id=\"canvas\" oncontextmenu=\"event.preventDefault()\" tabindex=-1></canvas>\n      <div id=\"exit-msg\" style=\"position:absolute;top:25%;text-align:center;width:100%;display:none\">Game finished.<br/>Close or refresh the page to exit.</div>\n    </div>\n    <hr/>\n    <script type='text/javascript'>\n      var statusElement = document.getElementById('status');\n      var progressElement = document.getElementById('progress');\n      var spinnerElement = document.getElementById('spinner');\n\n      var Module = {\n        preRun: [],\n        postRun: [],\n        print: (function() {\n          return function(text) {\n            if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');\n            console.log(text);\n          };\n        })(),\n        canvas: (() => {\n          var canvas = document.getElementById('canvas');\n\n          // As a default initial behavior, pop up an alert when webgl context is lost. To make your\n          // application robust, you may want to override this behavior before shipping!\n          // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2\n          canvas.addEventListener(\"webglcontextlost\", (e) => { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);\n\n          return canvas;\n        })(),\n        setStatus: (text) => {\n          if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };\n          if (text === Module.setStatus.last.text) return;\n          var m = text.match(/([^(]+)\\((\\d+(\\.\\d+)?)\\/(\\d+)\\)/);\n          var now = Date.now();\n          if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon\n          Module.setStatus.last.time = now;\n          Module.setStatus.last.text = text;\n          if (m) {\n            text = m[1];\n            progressElement.value = parseInt(m[2])*100;\n            progressElement.max = parseInt(m[4])*100;\n            progressElement.hidden = false;\n            spinnerElement.hidden = false;\n          } else {\n            progressElement.value = null;\n            progressElement.max = null;\n            progressElement.hidden = true;\n            if (!text) spinnerElement.hidden = true;\n          }\n          statusElement.innerHTML = text;\n        },\n        totalDependencies: 0,\n        monitorRunDependencies: (left) => {\n          this.totalDependencies = Math.max(this.totalDependencies, left);\n          Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');\n        }\n      };\n      Module.setStatus('Downloading...');\n      window.onerror = () => {\n        Module.setStatus('Exception thrown, see JavaScript console');\n        spinnerElement.style.display = 'none';\n        Module.setStatus = (text) => {\n          if (text) console.error('[post-exception status] ' + text);\n        };\n      };\n    </script>\n    {{{ SCRIPT }}}\n  </body>\n</html>\n"
  },
  {
    "path": "resources/emscripten/sw.js.in",
    "content": "// service worker with a cache-first model, with atomic updates based on service worker version\n// based closely on the designs from https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers\n\n// configuration\nconst app_id = \"${THEXTECH_MANIFEST_ID_OUT}\"\nconst current_version = \"${THEXTECH_MANIFEST_ID_OUT}-${GIT_COMMIT_HASH}\";\nconst scope = \"${THEXTECH_DEPLOY_URL}\";\nconst executable_name = \"${THEXTECH_EXECUTABLE_NAME}\";\n\n\n// code for installation\nconst cache_resources = async (resources) => {\n  const cache = await caches.open(current_version);\n  console.info(\"TheXTech service worker update to \" + current_version + \" initialized: starting download. (\" + new Date() + \")\");\n  await cache.addAll(resources);\n  console.info(\"TheXTech service worker update to \" + current_version + \": download complete. (\" + new Date() + \")\");\n};\n\n// make sure that all resources have been downloaded / cached upon installation of new SW version\nself.addEventListener(\"install\", (event) => {\n  event.waitUntil(\n    cache_resources([\n      scope,\n      scope + \"favicon.ico\",\n      scope + \"index.html\",\n      scope + executable_name + \".js\",\n      scope + executable_name + \".wasm\",\n      scope + executable_name + \".data\",\n    ]),\n  );\n});\n\n\n// code for activation\n\n// clear previously cached versions of the game on update\nconst clear_cache = async (key) => {\n  await caches.delete(key);\n};\n\nconst clear_other_caches = async () => {\n  const keyList = await caches.keys();\n  const cachesToDelete = keyList.filter((key) => (key.startsWith(app_id) && key != current_version));\n  await Promise.all(cachesToDelete.map(clear_cache));\n  console.info(\"TheXTech service worker update to \" + current_version + \": update activated. (\" + new Date() + \")\");\n  console.info(\"Deleted old caches: \" + cachesToDelete);\n};\n\n// delete all other caches upon activation (do NOT claim clients; wait for the old service worker to die naturally)\nself.addEventListener(\"activate\", (event) => {\n  event.waitUntil(clear_other_caches());\n});\n\n\n// code for fetches\nconst cache_response = async (request, response) => {\n  const cache = await caches.open(current_version);\n  await cache.put(request, response);\n};\n\n// cache-first strategy based on MDN's tutorial\nconst cache_first = async (request) => {\n  // First try to get the resource from the cache\n  const cache = await caches.open(current_version);\n  const responseFromCache = await cache.match(request);\n  if (responseFromCache) {\n    console.info(\"Cache hit!\");\n    return responseFromCache;\n  }\n\n  // Next try to get the resource from the network\n  try {\n    const responseFromNetwork = await fetch(request);\n    // response may be used only once\n    // we need to save clone to put one copy in cache\n    // and serve second one\n    cache_response(request, responseFromNetwork.clone());\n    console.info(\"Cache miss, got!\");\n    return responseFromNetwork;\n  } catch (error) {\n    console.info(\"Cache miss, failed!\");\n\n    // there is nothing we can do, but we must always\n    // return a Response object\n    return new Response(\"Resource not available: \" + error, {\n      status: 408,\n      headers: { \"Content-Type\": \"text/plain\" },\n    });\n  }\n};\n\n// fetch handler, use cache first\nself.addEventListener(\"fetch\", function (event) {\n  console.info(\"TheXTech service worker. Fetch: \" + event.request.url + \" with mode:\" + event.request.mode);\n  event.respondWith(cache_first(event.request));\n});\n"
  },
  {
    "path": "resources/flatpak/appdata.xml.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop-application\">\n  <id>@THEXTECH_EXECUTABLE_NAME@</id>\n  \n  <name>@DESKTOP_NAME@</name>\n  <summary>@DESKTOP_COMMENT@</summary>\n  <developer_name>Wohlsoft</developer_name>\n  <url type=\"homepage\">https://github.com/Wohlstand/TheXTech/</url>\n\n  <metadata_license>CC-BY-SA-4.0</metadata_license>\n  <project_license>GPL-3.0-or-later</project_license>\n  \n  <recommends>\n    <display_length compare=\"ge\">800</display_length>\n  </recommends>\n  <supports>\n    <control>pointing</control>\n    <control>keyboard</control>\n    <control>touch</control>\n    <control>gamepad</control>\n  </supports>\n  \n  <description>\n    <p>\n      @FLATPAK_DESCRIPTION@\n    </p>\n  </description>\n  \n  <launchable type=\"desktop-id\">@THEXTECH_EXECUTABLE_NAME@.desktop</launchable>\n</component>\n"
  },
  {
    "path": "resources/flatpak/build.yml",
    "content": "id: ru.wohlsoft.TheXTech\ndefault-branch: dev\nruntime: org.freedesktop.Platform\nruntime-version: \"22.08\"\nsdk: org.freedesktop.Sdk\ncommand: ru.wohlsoft.TheXTech\nmodules:\n  - buildsystem: cmake-ninja\n    name: thextech\n    sources:\n      - type: git\n        url: https://github.com/TheXTech/TheXTech\n        branch: main\n    config-opts:\n      - -DCMAKE_BUILD_TYPE=MinSizeRel\n      - -DTHEXTECH_EXECUTABLE_NAME=ru.wohlsoft.TheXTech\n      - -DPGE_SHARED_SDLMIXER=Off\n      - -DFLATPAK_BUILD=On\n      - -DOVERRIDE_GIT_BRANCH=\nfinish-args:\n  # remove this after the new paths update, use $XDG_DATA_HOME inside the sandbox instead\n  - --filesystem=host\n  - --device=dri\n  # drop to --device=input once supported on Ubuntu and Debian\n  - --device=all\n  - --socket=x11\n  - --socket=wayland\n  # set fallback-x11 whenever we have SDL client-side decorations on Wayland GNOME\n  # - --socket=fallback-x11\n  - --socket=pulseaudio\n  - --share=ipc\n"
  },
  {
    "path": "resources/haiku/PackageInfo.in",
    "content": "name                  thextech_bin\nversion               @THEXTECH_HAIKU_VERSION_STRING@\narchitecture          @TARGET_PROCESSOR@\nsummary               \"TheXTech runtime package\"\ndescription           \"@XTECH_HAIKU_SHORT_DESC@\"\npackager              \"Vitaliy Novichkov <admin@wohlnet.ru>\"\nvendor                \"TheXTech Developers Team\"\nlicenses {\n    \"GNU GPL v3\"\n}\ncopyrights {\n    \"Copyright (C) 2020-@XTECH_HAIKU_COPYRIGHT_YEAR@ by Vitaliy Novichkov <admin@wohlnet.ru>\"\n}\nprovides {\n    thextech_bin = @THEXTECH_HAIKU_VERSION_STRING@\n}\nrequires {\n    haiku >= r1~beta5_hrev57937-129\n    libsdl2 >= 2.30.10-1\n}\nurls {\n# TheXTech Project page\n    \"https://wohlsoft.ru/projects/TheXTech/\"\n# Source code repository\n    \"https://github.com/TheXTech/TheXTech\"\n}\nsource-urls {\n# Source code repository\n    \"https://github.com/TheXTech/TheXTech\"\n}\n"
  },
  {
    "path": "resources/haiku/icon.rdef",
    "content": "resource app_flags B_SINGLE_LAUNCH;\n\nresource vector_icon array {\n\t$\"6E6369660E03191AAC01362A1B3303438BFF03FDF1B803FFAB8803D5BA66051A\"\n\t$\"05FF035D4C4802001602BC058BBCD61B3CD61BBC058B4BB02C48C0C9004DFFFF\"\n\t$\"05000566041AFD02001602BC058BBCD61B3CD61BBC058B4C0AD1470D12004DFF\"\n\t$\"FF1A0605FF01B450C4ADB450C4ADB868CA27CB3BC49DC825C9D7CE50BF13BFD6\"\n\t$\"B31CCA08B31CB532B31CB450C4ADB10ABFF4B450C4ADC4AD020FBFF6C299BFF6\"\n\t$\"C299BDB2C299B949C36BBB6DC2C9B837C3BBB674C4CDB736C41CB624C52EB634\"\n\t$\"C610B5F3C59FB674C6A1B7A653B715C701B888C7B3BA5BC834B96AC7F3BC4FC8\"\n\t$\"94C036C8B4BE43C8C5C24AC8B4C682C7E3C47EC874C753C7A2C8E6C6E1C83553\"\n\t$\"C947C690C9A8C59FC9B850C9B8C52EC8F6C47DC957C4CDC855C40CC6F3C38BC7\"\n\t$\"A4C3CBC570C30AC24AC2A9C3DDC2C9C189C299BFF6C299C0B7C299BFF6C299BF\"\n\t$\"F6C299BFF6C299BFF6C2990606FF07C5F8B9C7C5F8B9C7C5F8BD25BFB9BFDFC3\"\n\t$\"2CBFDFBC46BFDFB97BB9C7B97BBD25B97BB66ABFB9B3AFBC46B3AFC32CB3AFC5\"\n\t$\"F8B9C7C5F8B66AC5F8B9C7B9C70605FF01CA00B8E0CA00B8E0C988BA09C98DBD\"\n\t$\"39C959BB94C9D6BF84CBFDC252CAD0C16DCC8FBF75CA00B8E0CBD9BBDCCA00B8\"\n\t$\"E0B8E00605FF01B587B8F5B587B8F5B39EBBFCB360C247B2E3BF94B493C168B5\"\n\t$\"DFBD3EB58CBF84B61EBBA3B587B8F5B5F4BA23B587B8F5B8F50007B8F9CA27B8\"\n\t$\"F9CA27B8F9CA88BFE6CC2BBAFDCC2BC4BFCC2BC672CA27C611CAA8C753C946C7\"\n\t$\"8450C7A4C650C784C5CFC006C36BC43EC38BBB6DC34AB827C5EFB827C5EFB7C7\"\n\t$\"C732B8F9CA27B858C996B8F9CA270606FF07C626C859C626C859C626C91AC4CE\"\n\t$\"C9B5C58CC9B5C410C9B5C376C859C376C91AC376C799C4CEC6FEC410C6FEC58C\"\n\t$\"C6FEC626C859C626C799C626C859C8590606FF07BC03C859BC03C859BC03C91A\"\n\t$\"BAABC9B5BB69C9B5B9EDC9B5B953C859B953C91AB953C799BAABC6FEB9EDC6FE\"\n\t$\"BB69C6FEBC03C859BC03C799BC03C859C8590007B8F9CA27B8F9CA27B8F9CA88\"\n\t$\"BFE6CC2BBAFDCC2BC4BFCC2BC672CA27C611CAA8C753C946C78450C7A4C650C7\"\n\t$\"84C5CFC006C36BC43EC38BBB6DC34AB827C5EFB827C5EFB7C7C732B8F9CA27B8\"\n\t$\"58C996B8F9CA27060AFEFF07BCEBC4F6BCEBC4F6BCEBC4F6BD41C4F6BD87C53C\"\n\t$\"BD87C515BD87C617BD87C7CEBD87C6F2BD87C7F4BCEBC813BD41C813BCEBC813\"\n\t$\"BCEBC813BCEBC813BC94C813BC4FC7CEBC4FC7F4BC4FC6F2BC4FC53CBC4FC617\"\n\t$\"BC4FC515BCEBC4F6BC94C4F6BCEBC4F6C4F60209BC49C667BC49C667BC49C64A\"\n\t$\"BC49C60FBC49C62CBC65C61DBC9DC638BC81C62BBCCBC64DBD2EC67CBD0FC66C\"\n\t$\"BD3FC685BD62C696BD51C68DBD3CC69BBCF0C6A6BD16C6A0BCACC6B0BC63C6BA\"\n\t$\"BC6EC6BABC5BC6BABC49C6BABC52C6BABC49C69FBC49C667BC49C683BC49C667\"\n\t$\"060AFEFF07C232C4F6C232C4F6C232C4F6C288C4F6C2CEC53CC2CEC515C2CEC6\"\n\t$\"17C2CEC7CEC2CEC6F2C2CEC7F4C232C813C288C813C232C813C232C813C232C8\"\n\t$\"13C1DCC813C196C7CEC196C7F4C196C6F2C196C53CC196C617C196C515C232C4\"\n\t$\"F6C1DCC4F6C232C4F6C4F60209C191C667C191C667C191C64AC191C60FC191C6\"\n\t$\"2CC1ADC61DC1E4C638C1C9C62BC213C64DC276C67CC257C66CC287C685C2AAC6\"\n\t$\"96C299C68DC284C69BC238C6A6C25EC6A0C1F4C6B0C1ABC6BAC1B6C6BAC1A2C6\"\n\t$\"BAC191C6BAC19AC6BAC191C69FC191C667C191C683C191C6670004C0F3C7D9C0\"\n\t$\"F3C7D9C124C87AC07FC996C0F6C92FC009C9FDBEEEC99ABF66C9FFBE76C936BE\"\n\t$\"74C7E0BE45C882BE74C7E00221C66BC6E0C66BC6E0C6BBC6E1C75BC6E3C70BC6\"\n\t$\"E2C75AC722C75AC7A0C75AC761C789C7B3C7E8C7D954C7C6C811C7AEC864C756\"\n\t$\"C83BC782C89FC793C915C80EC8DAC7D1C8E9C835C89256C8BEC85CC8A7C8B3C8\"\n\t$\"D0C911C8BCC8E2C90FC911C98BC911C94DC911C98BC964C989CA0AC98AC9B7C9\"\n\t$\"4DCA0BC8D4CA0CC911CA0BC8C0CA38C897CA8EC8ABCA63C8C2CABBC919CB14C8\"\n\t$\"EE5CC8E0CB51C86DCBCAC8A6CB8DC841CB9CC7EACB42C815CB6FC7BBCB56C75E\"\n\t$\"CB7CC78DCB69C75DCBB9C75BCC33C75CCBF6C70BCC33C66BCC32C6BBCC32C66C\"\n\t$\"CBF7C66ECB80C66DCBBCC63DCB6BC5DDCB40C60DCB55C5B3CB6CC55DCBC4C588\"\n\t$\"CB98C523CB8BC4ADCB1AC4E8CB53C4DACAECC534CA90C507CABEC522CA63C4FD\"\n\t$\"CA09C50FCA36C4BECA09C441CA09C47FCA09C440C9B5C43EC90DC43FC961C47C\"\n\t$\"C90DC4F9C90DC4BAC90DC50DC8E2C535C88BC521C8B7C50CC85AC4BAC7F9C4E3\"\n\t$\"C82AC4EFC7C4C559C758C524C78EC584C784C5DBC7DBC5B0C7B0C60BC7C5C66B\"\n\t$\"C79AC63BC7B0C66BC75CC66BC6E0C66BC71EC66BC6E00221C66BC6E0C66BC6E0\"\n\t$\"C6BBC6E1C75BC6E3C70BC6E2C75AC722C75AC7A0C75AC761C789C7B3C7E8C7D9\"\n\t$\"54C7C6C811C7AEC864C756C83BC782C89FC793C915C80EC8DAC7D1C8E9C835C8\"\n\t$\"9256C8BEC85CC8A7C8B3C8D0C911C8BCC8E2C90FC911C98BC911C94DC911C98B\"\n\t$\"C964C989CA0AC98AC9B7C94DCA0BC8D4CA0CC911CA0BC8C0CA38C897CA8EC8AB\"\n\t$\"CA63C8C2CABBC919CB14C8EE5CC8E0CB51C86DCBCAC8A6CB8DC841CB9CC7EACB\"\n\t$\"42C815CB6FC7BBCB56C75ECB7CC78DCB69C75DCBB9C75BCC33C75CCBF6C70BCC\"\n\t$\"33C66BCC32C6BBCC32C66CCBF7C66ECB80C66DCBBCC63DCB6BC5DDCB40C60DCB\"\n\t$\"55C5B3CB6CC55DCBC4C588CB98C523CB8BC4ADCB1AC4E8CB53C4DACAECC534CA\"\n\t$\"90C507CABEC522CA63C4FDCA09C50FCA36C4BECA09C441CA09C47FCA09C440C9\"\n\t$\"B5C43EC90DC43FC961C47CC90DC4F9C90DC4BAC90DC50DC8E2C535C88BC521C8\"\n\t$\"B7C50CC85AC4BAC7F9C4E3C82AC4EFC7C4C559C758C524C78EC584C784C5DBC7\"\n\t$\"DBC5B0C7B0C60BC7C5C66BC79AC63BC7B0C66BC75CC66BC6E0C66BC71EC66BC6\"\n\t$\"E00606FF07C82EC98AC82EC98AC82ECA40C6E4CAD3C79BCAD3C62ECAD3C59BC9\"\n\t$\"8AC59BCA40C59BC8D3C6E4C840C62EC840C79BC840C82EC98AC82EC8D3C82EC9\"\n\t$\"8AC98A0606FF07C82EC98AC82EC98AC82ECA40C6E4CAD3C79BCAD3C62ECAD3C5\"\n\t$\"9BC98AC59BCA40C59BC8D3C6E4C840C62EC840C79BC840C82EC98AC82EC8D3C8\"\n\t$\"2EC98AC98A0606FF07C6FAC98AC6FAC98AC6FAC995C6E4C99FC6F0C99FC6D9C9\"\n\t$\"9FC6CFC98AC6CFC995C6CFC97EC6E4C974C6D9C974C6F0C974C6FAC98AC6FAC9\"\n\t$\"7EC6FAC98AC98A0606FF07C6FAC98AC6FAC98AC6FAC995C6E4C99FC6F0C99FC6\"\n\t$\"D9C99FC6CFC98AC6CFC995C6CFC97EC6E4C974C6D9C974C6F0C974C6FAC98AC6\"\n\t$\"FAC97EC6FAC98AC98A0221C8F2C2F6C8F2C2F6C942C2F8C9E1C2FAC992C2F9C9\"\n\t$\"E1C339C9E0C3B7C9E1C378CA10C3CACA6EC3F0CA3FC3DDCA98C3C4CAEBC36CCA\"\n\t$\"C1C398CB26C3AACB9CC424CB61C3E7CB70C44BCB19C49ACB45C473CB2EC4C9CB\"\n\t$\"57C527CB42C4F8CB95C527CC12C527CBD4C527CC11C57ACC1050CC11C5CDCBD4\"\n\t$\"C621CB5BC622CB97C622CB47C64ECB1DC6A5CB32C679CB49C6D1CBA0C72BCB75\"\n\t$\"C6FECB67C767CAF3C7E0CB2DC7A3CAC8C7B3CA71C758CA9CC786CA42C76CC9E5\"\n\t$\"C793CA14C77FC9E4C7CFC9E2C849C9E3C80CC992C849C8F2C848C942C849C8F3\"\n\t$\"C80DC8F4C797C8F4C7D2C8C4C781C864C756C894C76CC839C782C7E4C7DAC80F\"\n\t$\"C7AEC7AAC7A1C734C730C76FC769C761C702C7BBC6A7C78EC6D5C7A8C67AC784\"\n\t$\"50C796C64DC74550C6C750C70650C6C6C5CCC6C5C524C6C6C578C703C524C780\"\n\t$\"C524C741C524C794C4F8C7BCC4A1C7A8C4CDC793C471C741C40FC76AC440C776\"\n\t$\"C3DAC7E0C36FC7ABC3A4C80BC39AC862C3F2C837C3C6C892C3DCC8F2C3B0C8C2\"\n\t$\"C3C6C8F2C372C8F2C2F6C8F2C334C8F2C2F60221C8F2C2F6C8F2C2F6C942C2F8\"\n\t$\"C9E1C2FAC992C2F9C9E1C339C9E0C3B7C9E1C378CA10C3CACA6EC3F0CA3FC3DD\"\n\t$\"CA98C3C4CAEBC36CCAC1C398CB26C3AACB9CC424CB61C3E7CB70C44BCB19C49A\"\n\t$\"CB45C473CB2EC4C9CB57C527CB42C4F8CB95C527CC12C527CBD4C527CC11C57A\"\n\t$\"CC1050CC11C5CDCBD4C621CB5BC622CB97C622CB47C64ECB1DC6A5CB32C679CB\"\n\t$\"49C6D1CBA0C72BCB75C6FECB67C767CAF3C7E0CB2DC7A3CAC8C7B3CA71C758CA\"\n\t$\"9CC786CA42C76CC9E5C793CA14C77FC9E4C7CFC9E2C849C9E3C80CC992C849C8\"\n\t$\"F2C848C942C849C8F3C80DC8F4C797C8F4C7D2C8C4C781C864C756C894C76CC8\"\n\t$\"39C782C7E4C7DAC80FC7AEC7AAC7A1C734C730C76FC769C761C702C7BBC6A7C7\"\n\t$\"8EC6D5C7A8C67AC78450C796C64DC74550C6C750C70650C6C6C5CCC6C5C524C6\"\n\t$\"C6C578C703C524C780C524C741C524C794C4F8C7BCC4A1C7A8C4CDC793C471C7\"\n\t$\"41C40FC76AC440C776C3DAC7E0C36FC7ABC3A4C80BC39AC862C3F2C837C3C6C8\"\n\t$\"92C3DCC8F2C3B0C8C2C3C6C8F2C372C8F2C2F6C8F2C334C8F2C2F60606FF07CA\"\n\t$\"B5C5A0CAB5C5A0CAB5C656C96BC6EACA21C6EAC8B5C6EAC822C5A0C822C656C8\"\n\t$\"22C4EAC96BC456C8B5C456CA21C456CAB5C5A0CAB5C4EACAB5C5A0C5A00606FF\"\n\t$\"07CAB5C5A0CAB5C5A0CAB5C656C96BC6EACA21C6EAC8B5C6EAC822C5A0C822C6\"\n\t$\"56C822C4EAC96BC456C8B5C456CA21C456CAB5C5A0CAB5C4EACAB5C5A0C5A006\"\n\t$\"06FF07C981C5A0C981C5A0C981C5ACC96BC5B6C977C5B6C95FC5B6C956C5A0C9\"\n\t$\"56C5ACC956C594C96BC58AC95FC58AC977C58AC981C5A0C981C594C981C5A0C5\"\n\t$\"A00606FF07C981C5A0C981C5A0C981C5ACC96BC5B6C977C5B6C95FC5B6C956C5\"\n\t$\"A0C956C5ACC956C594C96BC58AC95FC58AC977C58AC981C5A0C981C594C981C5\"\n\t$\"A0C5A01A0A000100000A010101000A020102000A020103000A020104000A0301\"\n\t$\"05000A040106000A040107000A0501081001178000040A060109000A07010A00\"\n\t$\"0A06010B000A07010C000A08010D000A09010E000A0A010F1001178000040A0B\"\n\t$\"0110000A0C01111001178002040A0B0112000A0C01131001178002040A0D0114\"\n\t$\"000A0A01151001178000040A0B0116000A0C01171001178002040A0B0118000A\"\n\t$\"0C0119100117800204\"\n};\n"
  },
  {
    "path": "resources/icon/make_icns.sh",
    "content": "#!/bin/bash\n\nTYPE=\"thextech\"\nif [[ \"$TYPE\" == \"\" ]]; then\necho \"Error: empty name!\"\nexit 1\nfi\n\nmkdir $TYPE\".iconset\"\ncp $TYPE\"_16.png\" $TYPE\".iconset/icon_16x16.png\"\nsips -Z 32 $TYPE\".iconset/icon_16x16.png\" --out $TYPE\".iconset/icon_16x16@2x.png\"\ncp $TYPE\"_32.png\" $TYPE\".iconset/icon_32x32.png\"\nsips -Z 64 $TYPE\".iconset/icon_32x32.png\" --out $TYPE\".iconset/icon_32x32@2x.png\"\ncp $TYPE\"_48.png\" $TYPE\".iconset/icon_48x48.png\"\nsips -Z 96 $TYPE\".iconset/icon_48x48.png\" --out $TYPE\".iconset/icon_48x48@2x.png\"\ncp $TYPE\"_128.png\" $TYPE\".iconset/icon_128x128.png\"\ncp $TYPE\"_256.png\" $TYPE\".iconset/icon_256x256.png\"\ncp $TYPE\"_256.png\" $TYPE\".iconset/icon_128x128@2x.png\"\ncp $TYPE\"_512.png\" $TYPE\".iconset/icon_256x256@2x.png\"\n#sips -Z 512 $TYPE\".iconset/icon_256x256.png\" --out $TYPE\".iconset/icon_256x256@2x.png\"\n#sips -Z 512 $TYPE\".iconset/icon_256x256.png\" --out $TYPE\".iconset/icon_512x512.png\"\n#sips -Z 128 $TYPE\".iconset/icon_256x256.png\" --out $TYPE\".iconset/icon_128x128.png\"\n\necho \"makeIcon...\"\n\niconutil -c icns $TYPE\".iconset\"\nmv $TYPE\".icns\" ../\n\nrm -Rf $TYPE\".iconset\"\n"
  },
  {
    "path": "resources/icon/make_icns_linux.sh",
    "content": "#!/bin/bash\n\n# This script requires ImageMagick and icnsutils packages being installed\n\nTYPE=\"thextech\"\nif [[ \"$TYPE\" == \"\" ]]; then\necho \"Error: empty name!\"\nexit 1\nfi\n\nmkdir $TYPE\".iconset\"\ncp $TYPE\"_16.png\" $TYPE\".iconset/icon_16x16.png\"\ncp $TYPE\"_32.png\" $TYPE\".iconset/icon_32x32.png\"\n# convert $TYPE\".iconset/icon_32x32.png\" -scale 64 $TYPE\".iconset/icon_64x64.png\"\ncp $TYPE\"_48.png\" $TYPE\".iconset/icon_48x48.png\"\n# convert $TYPE\".iconset/icon_48x48.png\" -scale 96 $TYPE\".iconset/icon_96x96.png\"\ncp $TYPE\"_128.png\" $TYPE\".iconset/icon_128x128.png\"\ncp $TYPE\"_256.png\" $TYPE\".iconset/icon_256x256.png\"\ncp $TYPE\"_512.png\" $TYPE\".iconset/icon_512x512.png\"\n#convert $TYPE\".iconset/icon_256x256.png\" -scale 512 $TYPE\".iconset/icon_256x256@2x.png\"\n#sips -Z 512 $TYPE\".iconset/icon_256x256.png\" --out $TYPE\".iconset/icon_256x256@2x.png\"\n#sips -Z 512 $TYPE\".iconset/icon_256x256.png\" --out $TYPE\".iconset/icon_512x512.png\"\n#sips -Z 128 $TYPE\".iconset/icon_256x256.png\" --out $TYPE\".iconset/icon_128x128.png\"\n\n#read -n 1\n\necho \"makeIcon...\"\n\npng2icns $TYPE.icns $TYPE\".iconset/icon_\"*.png\nmv $TYPE\".icns\" ../\n\nrm -Rf $TYPE\".iconset\"\n"
  },
  {
    "path": "resources/languages/assets_bg.json",
    "content": "{\n    \"character\": {\n        \"name2\": \"\",\n        \"name5\": \"\",\n        \"name3\": \"\",\n        \"name1\": \"\",\n        \"name4\": \"\"\n    },\n    \"editor\": {\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"wmusic11\": \"\",\n                \"wmusic10\": \"\",\n                \"wmusic2\": \"\",\n                \"wmusic1\": \"\",\n                \"wmusic9\": \"\",\n                \"header\": \"\",\n                \"wmusic3\": \"\",\n                \"wmusic8\": \"\",\n                \"wmusic6\": \"\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"\",\n                \"wmusic4\": \"\",\n                \"header\": \"\",\n                \"wmusic12\": \"\",\n                \"wmusic16\": \"\",\n                \"wmusic14\": \"\",\n                \"wmusic15\": \"\",\n                \"wmusic7\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"\",\n                \"wmusic5\": \"\"\n            }\n        },\n        \"music\": {\n            \"fam-space\": {\n                \"music11\": \"\",\n                \"music22\": \"\",\n                \"header\": \"\",\n                \"music45\": \"\",\n                \"music44\": \"\",\n                \"music12\": \"\"\n            },\n            \"fam-64\": {\n                \"music50\": \"\",\n                \"header\": \"\",\n                \"music14\": \"\",\n                \"music35\": \"\",\n                \"music26\": \"\",\n                \"music36\": \"\",\n                \"music49\": \"\",\n                \"music27\": \"\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"\",\n                \"music56\": \"\",\n                \"music37\": \"\",\n                \"music38\": \"\",\n                \"music23\": \"\",\n                \"music18\": \"\",\n                \"music20\": \"\",\n                \"header\": \"\",\n                \"music8\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"\",\n                \"music30\": \"\",\n                \"music31\": \"\",\n                \"header\": \"\",\n                \"music34\": \"\",\n                \"music21\": \"\",\n                \"music16\": \"\",\n                \"music32\": \"\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"\",\n                \"music15\": \"\",\n                \"header\": \"\",\n                \"music5\": \"\",\n                \"music25\": \"\"\n            },\n            \"fam-world\": {\n                \"music41\": \"\",\n                \"music10\": \"\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"\",\n                \"music3\": \"\",\n                \"music1\": \"\",\n                \"music6\": \"\",\n                \"music54\": \"\",\n                \"header\": \"\",\n                \"music4\": \"\",\n                \"music47\": \"\"\n            },\n            \"fam-set1\": {\n                \"header\": \"\",\n                \"music9\": \"\",\n                \"music46\": \"\",\n                \"music42\": \"\",\n                \"music7\": \"\"\n            },\n            \"fam-fight\": {\n                \"music53\": \"\",\n                \"music39\": \"\",\n                \"music19\": \"\",\n                \"header\": \"\",\n                \"music40\": \"\",\n                \"music52\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"\",\n                \"music24\": \"\"\n            }\n        },\n        \"block\": {\n            \"fam-steampunk3\": \"\",\n            \"fam-cave4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-pipes3\": \"\",\n            \"fam-pipes1\": \"\",\n            \"fam-pipes5-3\": \"\",\n            \"fam-pipes4\": \"\",\n            \"fam-desert3\": \"\",\n            \"fam-overworld2\": \"\",\n            \"fam-wood2\": \"\",\n            \"fam-underground2\": \"\",\n            \"fam-overworld1\": \"\",\n            \"fam-dungeon3\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-dungeon1\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-bonus3\": \"\",\n            \"fam-grass4\": \"\",\n            \"fam-mansion4\": \"\",\n            \"fam-special2\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-pipes5-2\": \"\",\n            \"fam-bricks5\": \"\",\n            \"fam-special4\": \"\",\n            \"fam-woods4\": \"\",\n            \"fam-ruins5\": \"\",\n            \"fam-grass3\": \"\",\n            \"fam-misc2\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-misc3\": \"\",\n            \"fam-metal5-3\": \"\",\n            \"fam-house3\": \"\",\n            \"fam-special3\": \"\",\n            \"fam-bonus4\": \"\",\n            \"fam-underground1\": \"\",\n            \"fam-cave3\": \"\",\n            \"fam-wood3\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-misc1\": \"\",\n            \"fam-castle4\": \"\",\n            \"fam-special1\": \"\",\n            \"fam-sized2\": \"\",\n            \"fam-castle2\": \"\"\n        },\n        \"bgo\": {\n            \"fam-ghost4\": \"\",\n            \"fam-key4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sandbg3\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-water4\": \"\",\n            \"fam-water1\": \"\",\n            \"fam-tiles3\": \"\",\n            \"fam-temple5\": \"\",\n            \"fam-art2\": \"\",\n            \"fam-structures3\": \"\",\n            \"fam-plants2\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-water2\": \"\",\n            \"fam-plants4\": \"\",\n            \"fam-bars4\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-plants1\": \"\",\n            \"fam-doors4\": \"\",\n            \"fam-plants3\": \"\",\n            \"fam-fences1\": \"\",\n            \"fam-hut1\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\",\n            \"fam-fences4\": \"\",\n            \"fam-doors2\": \"\"\n        },\n        \"background2\": {\n            \"fam-set1\": {\n                \"bg2-10\": \"\",\n                \"bg2-7\": \"\",\n                \"bg2-9\": \"\",\n                \"bg2-8\": \"\",\n                \"bg2-50\": \"\",\n                \"header\": \"\",\n                \"bg2-41\": \"\",\n                \"bg2-51\": \"\"\n            },\n            \"fam-set2\": {\n                \"bg2-57\": \"\",\n                \"bg2-48\": \"\",\n                \"bg2-53\": \"\",\n                \"bg2-49\": \"\",\n                \"bg2-25\": \"\",\n                \"bg2-54\": \"\",\n                \"bg2-52\": \"\",\n                \"bg2-5\": \"\",\n                \"bg2-44\": \"\",\n                \"header\": \"\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"\",\n                \"bg2-21\": \"\",\n                \"bg2-4\": \"\",\n                \"bg2-15\": \"\",\n                \"bg2-36\": \"\",\n                \"header\": \"\",\n                \"bg2-6\": \"\",\n                \"bg2-3\": \"\",\n                \"bg2-39\": \"\",\n                \"bg2-20\": \"\",\n                \"bg2-38\": \"\",\n                \"bg2-26\": \"\",\n                \"bg2-1\": \"\",\n                \"bg2-24\": \"\",\n                \"bg2-23\": \"\",\n                \"bg2-27\": \"\",\n                \"bg2-22\": \"\",\n                \"bg2-37\": \"\",\n                \"bg2-35\": \"\",\n                \"bg2-2\": \"\",\n                \"bg2-14\": \"\",\n                \"bg2-17\": \"\",\n                \"bg2-56\": \"\"\n            },\n            \"fam-misc\": {\n                \"header\": \"\",\n                \"bg2-16\": \"\",\n                \"bg2-46\": \"\",\n                \"bg2-47\": \"\",\n                \"bg2-40\": \"\",\n                \"bg2-45\": \"\"\n            },\n            \"fam-set4\": {\n                \"bg2-28\": \"\",\n                \"bg2-32\": \"\",\n                \"header\": \"\",\n                \"bg2-33\": \"\",\n                \"bg2-30\": \"\",\n                \"bg2-55\": \"\",\n                \"bg2-58\": \"\",\n                \"bg2-11\": \"\",\n                \"bg2-12\": \"\",\n                \"bg2-19\": \"\",\n                \"bg2-18\": \"\",\n                \"bg2-42\": \"\",\n                \"bg2-29\": \"\",\n                \"bg2-43\": \"\",\n                \"bg2-31\": \"\",\n                \"bg2-34\": \"\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"\"\n            }\n        },\n        \"npc\": {\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-char3\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound75\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound76\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"\",\n                \"header\": \"\",\n                \"sound13\": \"\",\n                \"sound30\": \"\",\n                \"sound47\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-world\": {\n                \"header\": \"\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-dirt2\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-stonewall3\": \"\",\n            \"fam-clouds4\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\",\n            \"fam-wall2\": \"\",\n            \"fam-lava3\": \"\"\n        },\n        \"exit-codes\": {\n            \"7\": \"\",\n            \"5\": \"\",\n            \"any\": \"\",\n            \"6\": \"\",\n            \"3\": \"\",\n            \"4\": \"\",\n            \"8\": \"\",\n            \"1\": \"\",\n            \"2\": \"\",\n            \"none\": \"\",\n            \"9\": \"\"\n        }\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"\",\n        \"wordFails\": \"\",\n        \"wordStarAccusativePlural\": \"\",\n        \"wordStarAccusativeDualOrCounter\": \"\"\n    },\n    \"languageName\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/assets_de.json",
    "content": "{\n    \"languageName\": \"Deutsch\",\n    \"editor\": {\n        \"background2\": {\n            \"fam-misc\": {\n                \"bg2-40\": \"Geheime Mine\",\n                \"bg2-46\": \"Raumschiff\",\n                \"bg2-47\": \"Raumbasis\",\n                \"bg2-16\": \"Weltraum-Krater\",\n                \"bg2-45\": \"Weltraum-Sumpf\",\n                \"header\": \"Versch.\"\n            },\n            \"fam-set1\": {\n                \"bg2-41\": \"Burg\",\n                \"bg2-50\": \"Pilze\",\n                \"bg2-51\": \"Wüste\",\n                \"bg2-7\": \"Unterirdisch\",\n                \"bg2-8\": \"Nacht\",\n                \"bg2-9\": \"Nacht 2\",\n                \"bg2-10\": \"Oberwelt\",\n                \"header\": \"Set 1\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"Unterirdisch\",\n                \"bg2-44\": \"Burg\",\n                \"bg2-49\": \"Nacht - Hügel\",\n                \"bg2-5\": \"Bäume\",\n                \"bg2-52\": \"Nacht - Wüste\",\n                \"bg2-53\": \"Klippe\",\n                \"bg2-54\": \"Lagerhaus\",\n                \"bg2-57\": \"Kerker\",\n                \"bg2-48\": \"Wolken\",\n                \"header\": \"\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"Wolken\",\n                \"bg2-14\": \"Wüste\",\n                \"bg2-1\": \"Blöcke\",\n                \"bg2-15\": \"Dungeon 2\",\n                \"bg2-17\": \"Schiff\",\n                \"bg2-2\": \"Hügel\",\n                \"bg2-20\": \"Wald\",\n                \"bg2-21\": \"Kampf\",\n                \"bg2-22\": \"Wasserfall\",\n                \"bg2-23\": \"Panzer\",\n                \"bg2-24\": \"Endboss\",\n                \"bg2-26\": \"Pilz-Dealer\",\n                \"bg2-27\": \"Burg\",\n                \"bg2-35\": \"Schnee-Bäume\",\n                \"bg2-36\": \"Wolken 2\",\n                \"bg2-37\": \"Schnee-Hügel\",\n                \"bg2-38\": \"Höhle\",\n                \"bg2-39\": \"Höhle 2\",\n                \"bg2-4\": \"Röhren\",\n                \"bg2-56\": \"Unterwasser\",\n                \"header\": \"\",\n                \"bg2-6\": \"\",\n                \"bg2-3\": \"\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"Keine\"\n            },\n            \"fam-set4\": {\n                \"bg2-11\": \"Hügel\",\n                \"bg2-12\": \"Bäume\",\n                \"bg2-18\": \"Villa\",\n                \"bg2-19\": \"Wald\",\n                \"bg2-29\": \"Nacht\",\n                \"bg2-30\": \"Höhle\",\n                \"bg2-31\": \"Wolken\",\n                \"bg2-28\": \"\",\n                \"bg2-32\": \"\",\n                \"header\": \"\",\n                \"bg2-33\": \"\",\n                \"bg2-55\": \"\",\n                \"bg2-58\": \"\",\n                \"bg2-42\": \"\",\n                \"bg2-43\": \"\",\n                \"bg2-34\": \"\"\n            }\n        },\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"wmusic11\": \"\",\n                \"wmusic10\": \"\",\n                \"wmusic2\": \"\",\n                \"wmusic1\": \"\",\n                \"wmusic9\": \"\",\n                \"header\": \"\",\n                \"wmusic3\": \"\",\n                \"wmusic8\": \"\",\n                \"wmusic6\": \"\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"\",\n                \"wmusic4\": \"\",\n                \"header\": \"\",\n                \"wmusic12\": \"\",\n                \"wmusic16\": \"\",\n                \"wmusic14\": \"\",\n                \"wmusic15\": \"\",\n                \"wmusic7\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"\",\n                \"wmusic5\": \"\"\n            }\n        },\n        \"music\": {\n            \"fam-space\": {\n                \"music11\": \"\",\n                \"music22\": \"\",\n                \"header\": \"\",\n                \"music45\": \"\",\n                \"music44\": \"\",\n                \"music12\": \"\"\n            },\n            \"fam-64\": {\n                \"music50\": \"\",\n                \"header\": \"\",\n                \"music14\": \"\",\n                \"music35\": \"\",\n                \"music26\": \"\",\n                \"music36\": \"\",\n                \"music49\": \"\",\n                \"music27\": \"\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"\",\n                \"music56\": \"\",\n                \"music37\": \"\",\n                \"music38\": \"\",\n                \"music23\": \"\",\n                \"music18\": \"\",\n                \"music20\": \"\",\n                \"header\": \"\",\n                \"music8\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"\",\n                \"music30\": \"\",\n                \"music31\": \"\",\n                \"header\": \"\",\n                \"music34\": \"\",\n                \"music21\": \"\",\n                \"music16\": \"\",\n                \"music32\": \"\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"\",\n                \"music15\": \"\",\n                \"header\": \"\",\n                \"music5\": \"\",\n                \"music25\": \"\"\n            },\n            \"fam-world\": {\n                \"music41\": \"\",\n                \"music10\": \"\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"\",\n                \"music3\": \"\",\n                \"music1\": \"\",\n                \"music6\": \"\",\n                \"music54\": \"\",\n                \"header\": \"\",\n                \"music4\": \"\",\n                \"music47\": \"\"\n            },\n            \"fam-set1\": {\n                \"header\": \"\",\n                \"music9\": \"\",\n                \"music46\": \"\",\n                \"music42\": \"\",\n                \"music7\": \"\"\n            },\n            \"fam-fight\": {\n                \"music53\": \"\",\n                \"music39\": \"\",\n                \"music19\": \"\",\n                \"header\": \"\",\n                \"music40\": \"\",\n                \"music52\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"\",\n                \"music24\": \"\"\n            }\n        },\n        \"block\": {\n            \"fam-steampunk3\": \"\",\n            \"fam-cave4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-pipes3\": \"\",\n            \"fam-pipes1\": \"\",\n            \"fam-pipes5-3\": \"\",\n            \"fam-pipes4\": \"\",\n            \"fam-desert3\": \"\",\n            \"fam-overworld2\": \"\",\n            \"fam-wood2\": \"\",\n            \"fam-underground2\": \"\",\n            \"fam-overworld1\": \"\",\n            \"fam-dungeon3\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-dungeon1\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-bonus3\": \"\",\n            \"fam-grass4\": \"\",\n            \"fam-mansion4\": \"\",\n            \"fam-special2\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-pipes5-2\": \"\",\n            \"fam-bricks5\": \"\",\n            \"fam-special4\": \"\",\n            \"fam-woods4\": \"\",\n            \"fam-ruins5\": \"\",\n            \"fam-grass3\": \"\",\n            \"fam-misc2\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-misc3\": \"\",\n            \"fam-metal5-3\": \"\",\n            \"fam-house3\": \"\",\n            \"fam-special3\": \"\",\n            \"fam-bonus4\": \"\",\n            \"fam-underground1\": \"\",\n            \"fam-cave3\": \"\",\n            \"fam-wood3\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-misc1\": \"\",\n            \"fam-castle4\": \"\",\n            \"fam-special1\": \"\",\n            \"fam-sized2\": \"\",\n            \"fam-castle2\": \"\"\n        },\n        \"bgo\": {\n            \"fam-ghost4\": \"\",\n            \"fam-key4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sandbg3\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-water4\": \"\",\n            \"fam-water1\": \"\",\n            \"fam-tiles3\": \"\",\n            \"fam-temple5\": \"\",\n            \"fam-art2\": \"\",\n            \"fam-structures3\": \"\",\n            \"fam-plants2\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-water2\": \"\",\n            \"fam-plants4\": \"\",\n            \"fam-bars4\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-plants1\": \"\",\n            \"fam-doors4\": \"\",\n            \"fam-plants3\": \"\",\n            \"fam-fences1\": \"\",\n            \"fam-hut1\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\",\n            \"fam-fences4\": \"\",\n            \"fam-doors2\": \"\"\n        },\n        \"npc\": {\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-char3\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound75\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound76\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"\",\n                \"header\": \"\",\n                \"sound13\": \"\",\n                \"sound30\": \"\",\n                \"sound47\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-world\": {\n                \"header\": \"\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-dirt2\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-stonewall3\": \"\",\n            \"fam-clouds4\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\",\n            \"fam-wall2\": \"\",\n            \"fam-lava3\": \"\"\n        },\n        \"exit-codes\": {\n            \"7\": \"\",\n            \"5\": \"\",\n            \"any\": \"\",\n            \"6\": \"\",\n            \"3\": \"\",\n            \"4\": \"\",\n            \"8\": \"\",\n            \"1\": \"\",\n            \"2\": \"\",\n            \"none\": \"\",\n            \"9\": \"\"\n        }\n    },\n    \"character\": {\n        \"name1\": \"Charakter1\",\n        \"name2\": \"Charakter2\",\n        \"name3\": \"Charakter3\",\n        \"name4\": \"Character4\",\n        \"name5\": \"Charakter5\"\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"\",\n        \"wordStarAccusativeDualOrCounter\": \"\",\n        \"wordFails\": \"\",\n        \"wordStarAccusativePlural\": \"\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/assets_en.json",
    "content": "{\n    \"character\": {\n        \"name1\": \"Char1\",\n        \"name2\": \"Char2\",\n        \"name3\": \"Char3\",\n        \"name4\": \"Char4\",\n        \"name5\": \"Char5\"\n    },\n    \"editor\": {\n        \"background2\": {\n            \"auto\": {\n                \"bg2-0\": \"None\"\n            },\n            \"fam-misc\": {\n                \"bg2-16\": \"Space Crater\",\n                \"bg2-40\": \"Secret Mine\",\n                \"bg2-45\": \"Space Swamp\",\n                \"bg2-46\": \"Space Ship\",\n                \"bg2-47\": \"Space Base\",\n                \"header\": \"Misc.\"\n            },\n            \"fam-set1\": {\n                \"bg2-10\": \"Overworld\",\n                \"bg2-41\": \"Castle\",\n                \"bg2-50\": \"Mushrooms\",\n                \"bg2-51\": \"Desert\",\n                \"bg2-7\": \"Underground\",\n                \"bg2-8\": \"Night\",\n                \"bg2-9\": \"Night 2\",\n                \"header\": \"Set 1\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"Underground\",\n                \"bg2-44\": \"Castle\",\n                \"bg2-48\": \"Clouds\",\n                \"bg2-49\": \"Night - Hills\",\n                \"bg2-5\": \"Trees\",\n                \"bg2-52\": \"Night - Desert\",\n                \"bg2-53\": \"Cliff\",\n                \"bg2-54\": \"Warehouse\",\n                \"bg2-57\": \"Dungeon\",\n                \"header\": \"Set 2\"\n            },\n            \"fam-set3\": {\n                \"bg2-1\": \"Blocks\",\n                \"bg2-13\": \"Clouds\",\n                \"bg2-14\": \"Desert\",\n                \"bg2-15\": \"Dungeon 2\",\n                \"bg2-17\": \"Ship\",\n                \"bg2-2\": \"Hills\",\n                \"bg2-20\": \"Forest\",\n                \"bg2-21\": \"Battle\",\n                \"bg2-22\": \"Waterfall\",\n                \"bg2-23\": \"Tanks\",\n                \"bg2-24\": \"Final Boss\",\n                \"bg2-26\": \"Shroom Dealer\",\n                \"bg2-27\": \"Castle\",\n                \"bg2-3\": \"Dungeon\",\n                \"bg2-35\": \"Snow Trees\",\n                \"bg2-36\": \"Clouds 2\",\n                \"bg2-37\": \"Snow Hills\",\n                \"bg2-38\": \"Cave\",\n                \"bg2-39\": \"Cave 2\",\n                \"bg2-4\": \"Pipes\",\n                \"bg2-56\": \"Underwater\",\n                \"bg2-6\": \"Bonus\",\n                \"header\": \"Set 3\"\n            },\n            \"fam-set4\": {\n                \"bg2-11\": \"Hills\",\n                \"bg2-12\": \"Trees\",\n                \"bg2-18\": \"Mansion\",\n                \"bg2-19\": \"Forest\",\n                \"bg2-28\": \"Bonus\",\n                \"bg2-29\": \"Night\",\n                \"bg2-30\": \"Cave\",\n                \"bg2-31\": \"Clouds\",\n                \"bg2-32\": \"Hills 2\",\n                \"bg2-33\": \"Hills 4\",\n                \"bg2-34\": \"Hills 3\",\n                \"bg2-42\": \"Castle\",\n                \"bg2-43\": \"Castle 2\",\n                \"bg2-55\": \"Underwater\",\n                \"bg2-58\": \"Desert Night\",\n                \"header\": \"Set 4\"\n            }\n        },\n        \"bgo\": {\n            \"fam-art2\": \"Art\",\n            \"fam-bars4\": \"Bars\",\n            \"fam-clouds1\": \"Clouds\",\n            \"fam-doors2\": \"Doors\",\n            \"fam-doors4\": \"Doors\",\n            \"fam-exit3\": \"Exit\",\n            \"fam-fences1\": \"Fences\",\n            \"fam-fences4\": \"Fences\",\n            \"fam-ghost4\": \"Ghost\",\n            \"fam-hut1\": \"Hut\",\n            \"fam-key4\": \"Key\",\n            \"fam-misc4\": \"Misc\",\n            \"fam-plants1\": \"Plants\",\n            \"fam-plants2\": \"Plants\",\n            \"fam-plants3\": \"Plants\",\n            \"fam-plants4\": \"Plants\",\n            \"fam-platform4\": \"Plat\",\n            \"fam-sandbg3\": \"Sand BG\",\n            \"fam-sandfg3\": \"FG\",\n            \"fam-space5\": \"Space Bug\",\n            \"fam-structures3\": \"Structures\",\n            \"fam-temple5\": \"Temple\",\n            \"fam-tiles3\": \"Tiles\",\n            \"fam-water1\": \"Water\",\n            \"fam-water2\": \"Water\",\n            \"fam-water3\": \"Water\",\n            \"fam-water4\": \"Water\"\n        },\n        \"block\": {\n            \"fam-bonus3\": \"Bonus\",\n            \"fam-bonus4\": \"Bonus\",\n            \"fam-bricks5\": \"Brick\",\n            \"fam-castle2\": \"Castle\",\n            \"fam-castle4\": \"Castle\",\n            \"fam-cave3\": \"Cave\",\n            \"fam-cave4\": \"Cave\",\n            \"fam-char5\": \"Char\",\n            \"fam-clouds1\": \"Clouds\",\n            \"fam-desert3\": \"Desert\",\n            \"fam-dungeon1\": \"Dungeon\",\n            \"fam-dungeon3\": \"Dungeon\",\n            \"fam-grass3\": \"Grass\",\n            \"fam-grass4\": \"Grass\",\n            \"fam-house3\": \"House\",\n            \"fam-hurts3\": \"Hurts\",\n            \"fam-lava4\": \"Lava and Hurts\",\n            \"fam-mansion4\": \"Mansion\",\n            \"fam-metal5-1\": \"Plates\",\n            \"fam-metal5-3\": \"Misc\",\n            \"fam-misc1\": \"Misc\",\n            \"fam-misc2\": \"Misc\",\n            \"fam-misc3\": \"Misc 3\",\n            \"fam-misc4\": \"Misc\",\n            \"fam-overworld1\": \"Overworld\",\n            \"fam-overworld2\": \"Overworld\",\n            \"fam-pipes1\": \"Pipes\",\n            \"fam-pipes3\": \"Pipes\",\n            \"fam-pipes4\": \"Pipes\",\n            \"fam-pipes5-2\": \"Pipes\",\n            \"fam-pipes5-3\": \"Long Pipes\",\n            \"fam-ruins5\": \"Ruins\",\n            \"fam-sized1\": \"Sized\",\n            \"fam-sized2\": \"Sized\",\n            \"fam-sized3\": \"Sized 3\",\n            \"fam-sized4\": \"Sized\",\n            \"fam-special1\": \"Special\",\n            \"fam-special2\": \"Special\",\n            \"fam-special3\": \"Special\",\n            \"fam-special4\": \"Special\",\n            \"fam-steampunk3\": \"Steampunk\",\n            \"fam-switch4\": \"Switch\",\n            \"fam-underground1\": \"Underground\",\n            \"fam-underground2\": \"Underground\",\n            \"fam-water3\": \"Water\",\n            \"fam-wood2\": \"Wood\",\n            \"fam-wood3\": \"Wood\",\n            \"fam-woods4\": \"Woods\"\n        },\n        \"exit-codes\": {\n            \"1\": \"Roulette\",\n            \"2\": \"Orb #16\",\n            \"3\": \"Leave\",\n            \"4\": \"Key\",\n            \"5\": \"Orb #41\",\n            \"6\": \"Warp\",\n            \"7\": \"Collect\",\n            \"8\": \"Bar end\",\n            \"any\": \"Any\",\n            \"none\": \"None\",\n            \"9\": \"code9\"\n        },\n        \"music\": {\n            \"auto\": {\n                \"music0\": \"None\",\n                \"music24\": \"Custom\"\n            },\n            \"fam-64\": {\n                \"header\": \"64\",\n                \"music14\": \"Desert\",\n                \"music26\": \"Castle\",\n                \"music27\": \"Main Theme\",\n                \"music35\": \"Snow\",\n                \"music36\": \"Boss\",\n                \"music49\": \"Water\",\n                \"music50\": \"Cave\"\n            },\n            \"fam-fight\": {\n                \"header\": \"Fight\",\n                \"music19\": \"Steampunk\",\n                \"music39\": \"Temple\",\n                \"music40\": \"Knight\",\n                \"music52\": \"Underground\",\n                \"music53\": \"Pinball\"\n            },\n            \"fam-misc\": {\n                \"header\": \"Misc.\",\n                \"music13\": \"New Overworld\",\n                \"music18\": \"Beach\",\n                \"music20\": \"Fusion Reactor\",\n                \"music23\": \"Heroic Woods\",\n                \"music37\": \"Ice Mountain\",\n                \"music38\": \"Jungle Village\",\n                \"music55\": \"Title Theme\",\n                \"music56\": \"Bouncy Race\",\n                \"music8\": \"Cornered!\"\n            },\n            \"fam-rpg\": {\n                \"header\": \"RPG\",\n                \"music16\": \"Forest\",\n                \"music21\": \"Battle\",\n                \"music30\": \"Bachelor Pad\",\n                \"music31\": \"Seaside\",\n                \"music32\": \"Pond\",\n                \"music33\": \"Clouds\",\n                \"music34\": \"Town\"\n            },\n            \"fam-set1\": {\n                \"header\": \"Set 1\",\n                \"music42\": \"Dungeon\",\n                \"music46\": \"Water\",\n                \"music7\": \"Underground\",\n                \"music9\": \"Overworld\"\n            },\n            \"fam-set2\": {\n                \"header\": \"Set 2\",\n                \"music15\": \"Boss\",\n                \"music25\": \"Underground\",\n                \"music43\": \"Final Boss\",\n                \"music5\": \"Overworld\"\n            },\n            \"fam-set3\": {\n                \"header\": \"Set 3\",\n                \"music1\": \"Overworld\",\n                \"music2\": \"Sky\",\n                \"music3\": \"Dungeon\",\n                \"music4\": \"Underground\",\n                \"music47\": \"Water\",\n                \"music54\": \"Roaming Enemy\",\n                \"music6\": \"Boss\"\n            },\n            \"fam-space\": {\n                \"header\": \"Space\",\n                \"music11\": \"Red Swamp\",\n                \"music12\": \"Crater\",\n                \"music22\": \"Space Charge\",\n                \"music44\": \"Item Room\",\n                \"music45\": \"Final Boss\"\n            },\n            \"fam-world\": {\n                \"header\": \"World\",\n                \"music10\": \"Overworld\",\n                \"music17\": \"Mansion\",\n                \"music28\": \"Sky\",\n                \"music29\": \"Cave\",\n                \"music41\": \"Dungeon\",\n                \"music48\": \"Water\",\n                \"music51\": \"Boss\"\n            }\n        },\n        \"npc\": {\n            \"fam-blocks2\": \"Blocks\",\n            \"fam-blocks3\": \"Blocks\",\n            \"fam-boss1\": \"Boss\",\n            \"fam-boss2\": \"Boss\",\n            \"fam-boss3\": \"Boss\",\n            \"fam-boss4\": \"Boss\",\n            \"fam-boss5\": \"Boss\",\n            \"fam-char3\": \"Char\",\n            \"fam-char5\": \"Char\",\n            \"fam-ckpt4\": \"Ckpt\",\n            \"fam-enemies1\": \"Enemies\",\n            \"fam-enemies2\": \"Enemies\",\n            \"fam-enemies3\": \"Enemies\",\n            \"fam-enemies4\": \"Enemies\",\n            \"fam-enemies5\": \"Enemies\",\n            \"fam-exit2\": \"Exit\",\n            \"fam-exit3\": \"Exit\",\n            \"fam-exit4\": \"Exit\",\n            \"fam-items1\": \"Items\",\n            \"fam-items2\": \"Items\",\n            \"fam-items3\": \"Items\",\n            \"fam-items4\": \"Items\",\n            \"fam-items5\": \"Items\",\n            \"fam-pet4\": \"Pet\",\n            \"fam-plant3\": \"Plant\",\n            \"fam-plat1\": \"Plat\",\n            \"fam-plat3\": \"Plat\",\n            \"fam-plat4\": \"Plat\",\n            \"fam-shell1\": \"Shell\",\n            \"fam-shell3\": \"Shell\",\n            \"fam-sign4\": \"Sign\",\n            \"fam-switch5\": \"Switch\",\n            \"fam-veg2\": \"Veg\",\n            \"fam-vine1\": \"Vine\",\n            \"fam-vine2\": \"Vine\",\n            \"fam-vine3\": \"Vine\",\n            \"fam-vine4\": \"Vine\",\n            \"fam-warp2\": \"Warp\"\n        },\n        \"sound\": {\n            \"auto\": {\n                \"sound0\": \"None\"\n            },\n            \"fam-boss\": {\n                \"header\": \"Boss\",\n                \"sound38\": \"Bird Spit\",\n                \"sound39\": \"Bird Hit\",\n                \"sound41\": \"Bird Beat\",\n                \"sound44\": \"Turtle Killed\",\n                \"sound62\": \"Frog Bubbles\",\n                \"sound63\": \"Frog Killed\",\n                \"sound67\": \"Glass Break\",\n                \"sound68\": \"Space Boss Hit\",\n                \"sound69\": \"Space Boss Cry\",\n                \"sound70\": \"Space Boss Kill\"\n            },\n            \"fam-hero\": {\n                \"header\": \"Hero\",\n                \"sound53\": \"Hero NPC Kill\",\n                \"sound77\": \"Hero Stab\",\n                \"sound78\": \"Hero Hurt\",\n                \"sound79\": \"Get Heart\",\n                \"sound80\": \"Hero Died\",\n                \"sound81\": \"Get Gem\",\n                \"sound82\": \"Hero Fire\",\n                \"sound83\": \"Hero Item\",\n                \"sound84\": \"Get Key\",\n                \"sound85\": \"Shield Block\",\n                \"sound86\": \"Hermes Boots\",\n                \"sound87\": \"Fly\",\n                \"sound88\": \"Mow Lawn\"\n            },\n            \"fam-item\": {\n                \"header\": \"Item\",\n                \"sound14\": \"Coin\",\n                \"sound16\": \"Lava\",\n                \"sound22\": \"Bullet\",\n                \"sound3\": \"Block Hit\",\n                \"sound32\": \"Switch\",\n                \"sound37\": \"Crusher\",\n                \"sound4\": \"Block Smashed\",\n                \"sound42\": \"Big Fireball\",\n                \"sound43\": \"Fireworks\",\n                \"sound51\": \"Egg Hatch\",\n                \"sound56\": \"Ring\",\n                \"sound57\": \"Skeleton\",\n                \"sound58\": \"Checkpoint\",\n                \"sound59\": \"Collectible\",\n                \"sound61\": \"Lava Creature\",\n                \"sound64\": \"Space Block Hit\",\n                \"sound65\": \"Space NPC Kill\",\n                \"sound66\": \"Space NPC Hurt\",\n                \"sound7\": \"Powerup\",\n                \"sound74\": \"Saw\",\n                \"sound9\": \"Shell Kick\"\n            },\n            \"fam-jingle\": {\n                \"header\": \"Jingle\",\n                \"sound19\": \"Roulette Exit\",\n                \"sound20\": \"Defeat Boss\",\n                \"sound21\": \"Dungeon Clear\",\n                \"sound31\": \"Key Exit\",\n                \"sound40\": \"Ball Exit\",\n                \"sound45\": \"Game Beat\",\n                \"sound52\": \"Got Star Exit\",\n                \"sound60\": \"Bar Exit\",\n                \"sound8\": \"Player Died\"\n            },\n            \"fam-pet\": {\n                \"header\": \"Pet\",\n                \"sound36\": \"Pet Stomp\",\n                \"sound48\": \"Pet Mount\",\n                \"sound49\": \"Pet Hurt\",\n                \"sound50\": \"Pet Tongue\",\n                \"sound55\": \"Pet Swallow\"\n            },\n            \"fam-player\": {\n                \"header\": \"Player\",\n                \"sound1\": \"Jump\",\n                \"sound10\": \"Skid\",\n                \"sound12\": \"Got Item\",\n                \"sound15\": \"1up\",\n                \"sound17\": \"Warp\",\n                \"sound18\": \"Fireball\",\n                \"sound2\": \"Stomp\",\n                \"sound23\": \"Grab\",\n                \"sound24\": \"Spring\",\n                \"sound25\": \"Hammer Toss\",\n                \"sound33\": \"Tail\",\n                \"sound34\": \"Racoon\",\n                \"sound35\": \"Lose Boot\",\n                \"sound46\": \"Door\",\n                \"sound5\": \"Shrink\",\n                \"sound54\": \"Player Died 2\",\n                \"sound6\": \"Grow\",\n                \"sound71\": \"Climbing\",\n                \"sound72\": \"Swim\",\n                \"sound73\": \"Light Grab\",\n                \"sound75\": \"Throw Veggie\",\n                \"sound76\": \"Lose Heart\"\n            },\n            \"fam-ui\": {\n                \"header\": \"UI\",\n                \"sound11\": \"Drop Item\",\n                \"sound13\": \"Camera\",\n                \"sound26\": \"Slide\",\n                \"sound29\": \"Do\",\n                \"sound30\": \"Pause\",\n                \"sound47\": \"Message\"\n            },\n            \"fam-world\": {\n                \"header\": \"World\",\n                \"sound27\": \"New Path\",\n                \"sound28\": \"Level Select\"\n            }\n        },\n        \"tile\": {\n            \"fam-clouds4\": \"Clouds\",\n            \"fam-desert1\": \"Dust\",\n            \"fam-dirt2\": \"Dead Grass\",\n            \"fam-granite3\": \"Granite\",\n            \"fam-grass1\": \"Old Grass\",\n            \"fam-grass2\": \"Happy Grass\",\n            \"fam-lava3\": \"Lava\",\n            \"fam-lilac4\": \"Lilac Wall\",\n            \"fam-marble3\": \"Marble\",\n            \"fam-snow1\": \"Old Snow\",\n            \"fam-stonewall3\": \"Stone Wall\",\n            \"fam-wall2\": \"Dirt Wall\"\n        },\n        \"worldMusic\": {\n            \"auto\": {\n                \"wmusic0\": \"None\"\n            },\n            \"fam-new\": {\n                \"header\": \"New\",\n                \"wmusic5\": \"Theme\"\n            },\n            \"fam-set3\": {\n                \"header\": \"Set 3\",\n                \"wmusic1\": \"World 1\",\n                \"wmusic10\": \"World 6\",\n                \"wmusic11\": \"World 5\",\n                \"wmusic2\": \"World 4\",\n                \"wmusic3\": \"World 7\",\n                \"wmusic6\": \"World 2\",\n                \"wmusic8\": \"World 3\",\n                \"wmusic9\": \"World 8\"\n            },\n            \"fam-world\": {\n                \"header\": \"World\",\n                \"wmusic12\": \"Special\",\n                \"wmusic13\": \"Dungeon\",\n                \"wmusic14\": \"Sky\",\n                \"wmusic15\": \"Island\",\n                \"wmusic16\": \"Cave\",\n                \"wmusic4\": \"Theme\",\n                \"wmusic7\": \"Forest\"\n            }\n        }\n    },\n    \"languageName\": \"English\",\n    \"objects\": {\n        \"wordFails\": \"FAILS\",\n        \"wordStarAccusativeDualOrCounter\": \"<Unused in English, translate as \\\"stars\\\" in Dual form if your language has dual form (the number between plural and singular); If your language has counter word, use counter word instead>\",\n        \"wordStarAccusativePlural\": \"stars\",\n        \"wordStarAccusativeSingular\": \"star\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/assets_es.json",
    "content": "{\n    \"editor\": {\n        \"background2\": {\n            \"fam-set1\": {\n                \"bg2-50\": \"Champiñones\",\n                \"bg2-7\": \"Subterráneo\",\n                \"bg2-8\": \"Noche\",\n                \"bg2-41\": \"Castillo\",\n                \"bg2-51\": \"Desierto\",\n                \"bg2-9\": \"Noche 2\",\n                \"bg2-10\": \"\",\n                \"header\": \"\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"Subterráneo\",\n                \"bg2-44\": \"Castillo\",\n                \"bg2-48\": \"Nubes\",\n                \"bg2-57\": \"Calabozo\",\n                \"bg2-52\": \"Noche - Desierto\",\n                \"bg2-5\": \"Árboles\",\n                \"bg2-53\": \"\",\n                \"bg2-49\": \"\",\n                \"bg2-54\": \"\",\n                \"header\": \"\"\n            },\n            \"fam-misc\": {\n                \"bg2-16\": \"Cráter espacial\",\n                \"bg2-40\": \"Mina secreta\",\n                \"bg2-47\": \"Base espacial\",\n                \"bg2-46\": \"Nave espacial\",\n                \"bg2-45\": \"Pantano espacial\",\n                \"header\": \"\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"Ninguno\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"Nubes\",\n                \"bg2-21\": \"Batalla\",\n                \"bg2-4\": \"Tuberías\",\n                \"bg2-15\": \"Calabozo 2\",\n                \"bg2-36\": \"Nubes 2\",\n                \"bg2-3\": \"Calabozo\",\n                \"bg2-39\": \"Cueva 2\",\n                \"bg2-20\": \"Bosque\",\n                \"bg2-38\": \"Cueva\",\n                \"bg2-1\": \"Bloques\",\n                \"bg2-24\": \"Jefe Final\",\n                \"bg2-23\": \"Tanques\",\n                \"bg2-27\": \"Castillo\",\n                \"bg2-22\": \"Cascada\",\n                \"bg2-37\": \"Colinas de Nieve\",\n                \"bg2-35\": \"Árboles de Nieve\",\n                \"bg2-2\": \"Colinas\",\n                \"bg2-14\": \"Desierto\",\n                \"bg2-17\": \"Nave\",\n                \"bg2-56\": \"Bajo el agua\",\n                \"header\": \"\",\n                \"bg2-6\": \"\",\n                \"bg2-26\": \"\"\n            },\n            \"fam-set4\": {\n                \"bg2-32\": \"Colinas 2\",\n                \"bg2-33\": \"Colinas 4\",\n                \"bg2-30\": \"Cueva\",\n                \"bg2-55\": \"Bajo el agua\",\n                \"bg2-58\": \"Noche del Desierto\",\n                \"bg2-11\": \"Colinas\",\n                \"bg2-12\": \"Árboles\",\n                \"bg2-19\": \"Árboles\",\n                \"bg2-18\": \"Mansión\",\n                \"bg2-42\": \"Castillo\",\n                \"bg2-29\": \"Noche\",\n                \"bg2-43\": \"Castillo 2\",\n                \"bg2-31\": \"Nubes\",\n                \"bg2-34\": \"Colinas 3\",\n                \"bg2-28\": \"\",\n                \"header\": \"\"\n            }\n        },\n        \"music\": {\n            \"fam-64\": {\n                \"music50\": \"Cueva\",\n                \"music14\": \"Desierto\",\n                \"music35\": \"Nieve\",\n                \"music26\": \"Castillo\",\n                \"music36\": \"Jefe\",\n                \"music49\": \"Agua\",\n                \"music27\": \"Tema central\",\n                \"header\": \"\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"Tema del título\",\n                \"music38\": \"Aldea de la jungla\",\n                \"music18\": \"Playa\",\n                \"music56\": \"Carrera de obstáculos\",\n                \"music8\": \"¡Acorralado!\",\n                \"music37\": \"Montaña de Hielo\",\n                \"music23\": \"\",\n                \"music20\": \"\",\n                \"header\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-fight\": {\n                \"music39\": \"Templo\",\n                \"header\": \"Batalla\",\n                \"music40\": \"Caballero\",\n                \"music52\": \"Subterráneo\",\n                \"music53\": \"\",\n                \"music19\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"Ninguna\",\n                \"music24\": \"Personalizada\"\n            },\n            \"fam-space\": {\n                \"music11\": \"Pantano Rojo\",\n                \"music22\": \"Carga Espacial\",\n                \"header\": \"Espacio\",\n                \"music44\": \"Cuarto de Objeto\",\n                \"music12\": \"Cráter\",\n                \"music45\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"Nubes\",\n                \"music34\": \"Ciudad\",\n                \"music30\": \"\",\n                \"music31\": \"\",\n                \"header\": \"\",\n                \"music21\": \"\",\n                \"music16\": \"\",\n                \"music32\": \"\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"Jefe Final\",\n                \"music25\": \"Subterráneo\",\n                \"music15\": \"\",\n                \"header\": \"\",\n                \"music5\": \"\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"Cielo\",\n                \"music3\": \"Calabozo\",\n                \"music6\": \"Jefe\",\n                \"music4\": \"Subterráneo\",\n                \"music47\": \"Agua\",\n                \"music1\": \"\",\n                \"music54\": \"\",\n                \"header\": \"\"\n            },\n            \"fam-set1\": {\n                \"music46\": \"Agua\",\n                \"music42\": \"Calabozo\",\n                \"music7\": \"Subterráneo\",\n                \"header\": \"\",\n                \"music9\": \"\"\n            },\n            \"fam-world\": {\n                \"music41\": \"\",\n                \"music10\": \"\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            }\n        },\n        \"block\": {\n            \"fam-cave4\": \"Cueva\",\n            \"fam-water3\": \"Agua\",\n            \"fam-pipes3\": \"Tuberías\",\n            \"fam-pipes1\": \"Tuberías\",\n            \"fam-pipes4\": \"Tuberías\",\n            \"fam-desert3\": \"Desierto\",\n            \"fam-wood2\": \"Madera\",\n            \"fam-dungeon3\": \"Calabozo\",\n            \"fam-clouds1\": \"Nubes\",\n            \"fam-dungeon1\": \"Calabozo\",\n            \"fam-grass4\": \"Césped\",\n            \"fam-mansion4\": \"Mansión\",\n            \"fam-special2\": \"Especial\",\n            \"fam-pipes5-2\": \"Tuberías\",\n            \"fam-bricks5\": \"Ladrillo\",\n            \"fam-special4\": \"Especial\",\n            \"fam-woods4\": \"Maderas\",\n            \"fam-ruins5\": \"Ruinas\",\n            \"fam-grass3\": \"Césped\",\n            \"fam-house3\": \"Casa\",\n            \"fam-special3\": \"Especial\",\n            \"fam-cave3\": \"Cueva\",\n            \"fam-wood3\": \"Madera\",\n            \"fam-castle4\": \"Castillo\",\n            \"fam-special1\": \"Especial\",\n            \"fam-castle2\": \"Castillo\",\n            \"fam-underground2\": \"Subterráneo\",\n            \"fam-underground1\": \"Subterráneo\",\n            \"fam-steampunk3\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-pipes5-3\": \"\",\n            \"fam-overworld2\": \"\",\n            \"fam-overworld1\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-bonus3\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-misc2\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-misc3\": \"\",\n            \"fam-metal5-3\": \"\",\n            \"fam-bonus4\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-misc1\": \"\",\n            \"fam-sized2\": \"\"\n        },\n        \"bgo\": {\n            \"fam-key4\": \"Llave\",\n            \"fam-water3\": \"Agua\",\n            \"fam-water4\": \"Agua\",\n            \"fam-water1\": \"Agua\",\n            \"fam-tiles3\": \"Baldosas\",\n            \"fam-temple5\": \"Templo\",\n            \"fam-structures3\": \"Estructuras\",\n            \"fam-plants2\": \"Plantas 2\",\n            \"fam-exit3\": \"Salida\",\n            \"fam-water2\": \"Agua\",\n            \"fam-plants4\": \"Plantas\",\n            \"fam-bars4\": \"Barras\",\n            \"fam-clouds1\": \"Nubes\",\n            \"fam-plants1\": \"Plantas\",\n            \"fam-doors4\": \"Puertas\",\n            \"fam-plants3\": \"Plantas\",\n            \"fam-fences1\": \"Cercas\",\n            \"fam-fences4\": \"Cercas\",\n            \"fam-doors2\": \"Puertas\",\n            \"fam-ghost4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sandbg3\": \"\",\n            \"fam-art2\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-hut1\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\"\n        },\n        \"exit-codes\": {\n            \"5\": \"Orbe #41\",\n            \"any\": \"Cualquiera\",\n            \"6\": \"Pasaje\",\n            \"4\": \"Llave\",\n            \"1\": \"Ruleta\",\n            \"2\": \"Orbe #16\",\n            \"none\": \"Ninguno\",\n            \"8\": \"Final de Barra\",\n            \"7\": \"\",\n            \"3\": \"\",\n            \"9\": \"\"\n        },\n        \"worldMusic\": {\n            \"fam-world\": {\n                \"wmusic16\": \"Cueva\",\n                \"wmusic14\": \"Cielo\",\n                \"wmusic15\": \"Isla\",\n                \"wmusic7\": \"Bosque\",\n                \"header\": \"Mundo\",\n                \"wmusic12\": \"Especial\",\n                \"wmusic13\": \"\",\n                \"wmusic4\": \"\"\n            },\n            \"fam-set3\": {\n                \"wmusic1\": \"Mundo 1\",\n                \"wmusic3\": \"Mundo 7\",\n                \"wmusic10\": \"Mundo 6\",\n                \"wmusic11\": \"Mundo 5\",\n                \"wmusic2\": \"Mundo 4\",\n                \"wmusic9\": \"Mundo 8\",\n                \"wmusic8\": \"Mundo 3\",\n                \"wmusic6\": \"Mundo 2\",\n                \"header\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"Nuevo\",\n                \"wmusic5\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"Ninguno\"\n            }\n        },\n        \"sound\": {\n            \"fam-world\": {\n                \"header\": \"Mundo\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound13\": \"Cámara\",\n                \"sound11\": \"Soltar Objeto\",\n                \"sound47\": \"Mensaje\",\n                \"sound30\": \"Pausa\",\n                \"header\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-player\": {\n                \"sound75\": \"Lanzar Vegetal\",\n                \"sound76\": \"Perder Corazón\",\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-wall2\": \"Pared de Tierra\",\n            \"fam-stonewall3\": \"Pared de Piedra\",\n            \"fam-lava3\": \"Lava\",\n            \"fam-dirt2\": \"Césped Muerto\",\n            \"fam-clouds4\": \"Nubes\",\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\"\n        },\n        \"npc\": {\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-char3\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        }\n    },\n    \"languageName\": \"Español\",\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"estrella\",\n        \"wordFails\": \"Fallas\",\n        \"wordStarAccusativePlural\": \"estrellas\",\n        \"wordStarAccusativeDualOrCounter\": \"\"\n    },\n    \"character\": {\n        \"name2\": \"\",\n        \"name5\": \"\",\n        \"name3\": \"\",\n        \"name1\": \"\",\n        \"name4\": \"\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/assets_fr.json",
    "content": "{\n    \"editor\": {\n        \"background2\": {\n            \"fam-set3\": {\n                \"bg2-35\": \"Arbres Enneigés\",\n                \"bg2-36\": \"Nuages 2\",\n                \"bg2-39\": \"Grotte 2\",\n                \"bg2-37\": \"Collines Enneigées\",\n                \"bg2-4\": \"Tuyaux\",\n                \"bg2-6\": \"Bonus\",\n                \"bg2-56\": \"Sous-marin\",\n                \"header\": \"Set 3\",\n                \"bg2-1\": \"Blocs\",\n                \"bg2-13\": \"Nuages\",\n                \"bg2-14\": \"Désert\",\n                \"bg2-15\": \"Donjon 2\",\n                \"bg2-17\": \"Vaisseau\",\n                \"bg2-22\": \"Chute d'eau\",\n                \"bg2-23\": \"Tanks\",\n                \"bg2-2\": \"Collines\",\n                \"bg2-20\": \"Forêt\",\n                \"bg2-21\": \"Bataille\",\n                \"bg2-24\": \"Boss Final\",\n                \"bg2-26\": \"Marchand de Champis\",\n                \"bg2-27\": \"Château\",\n                \"bg2-3\": \"Donjon\",\n                \"bg2-38\": \"Grotte\"\n            },\n            \"fam-set4\": {\n                \"bg2-55\": \"Sous-marin\",\n                \"bg2-42\": \"Château\",\n                \"bg2-11\": \"Collines\",\n                \"bg2-12\": \"Arbres\",\n                \"bg2-30\": \"Grotte\",\n                \"bg2-29\": \"Nuit\",\n                \"bg2-58\": \"Nuit Déserte\",\n                \"bg2-32\": \"Collines 2\",\n                \"bg2-33\": \"Collines 4\",\n                \"bg2-34\": \"Collines 3\",\n                \"bg2-43\": \"Château 2\",\n                \"header\": \"Set 4\",\n                \"bg2-18\": \"Manoir\",\n                \"bg2-19\": \"Forêt\",\n                \"bg2-28\": \"Bonus\",\n                \"bg2-31\": \"Nuages\"\n            },\n            \"fam-misc\": {\n                \"bg2-16\": \"Cratère Spacial\",\n                \"bg2-40\": \"Mine Secrète\",\n                \"bg2-45\": \"Marais Spacial\",\n                \"bg2-46\": \"Vaisseau Spatial\",\n                \"bg2-47\": \"Base Spaciale\",\n                \"header\": \"Autre\"\n            },\n            \"fam-set1\": {\n                \"bg2-10\": \"Plein Air\",\n                \"bg2-41\": \"Château\",\n                \"bg2-50\": \"Champignons\",\n                \"bg2-51\": \"Désert\",\n                \"bg2-7\": \"Souterrain\",\n                \"bg2-8\": \"Nuit\",\n                \"bg2-9\": \"Nuit 2\",\n                \"header\": \"Set 1\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"Souterrain\",\n                \"bg2-44\": \"Château\",\n                \"bg2-48\": \"Nuages\",\n                \"bg2-49\": \"Nuit - Collines\",\n                \"bg2-5\": \"Arbres\",\n                \"bg2-52\": \"Nuit - Désert\",\n                \"bg2-53\": \"Falaise\",\n                \"bg2-54\": \"Entrepôt\",\n                \"bg2-57\": \"Donjon\",\n                \"header\": \"Set 2\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"Aucun\"\n            }\n        },\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"wmusic11\": \"\",\n                \"wmusic10\": \"\",\n                \"wmusic2\": \"\",\n                \"wmusic1\": \"\",\n                \"wmusic9\": \"\",\n                \"header\": \"\",\n                \"wmusic3\": \"\",\n                \"wmusic8\": \"\",\n                \"wmusic6\": \"\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"\",\n                \"wmusic4\": \"\",\n                \"header\": \"\",\n                \"wmusic12\": \"\",\n                \"wmusic16\": \"\",\n                \"wmusic14\": \"\",\n                \"wmusic15\": \"\",\n                \"wmusic7\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"\",\n                \"wmusic5\": \"\"\n            }\n        },\n        \"music\": {\n            \"fam-space\": {\n                \"music11\": \"\",\n                \"music22\": \"\",\n                \"header\": \"\",\n                \"music45\": \"\",\n                \"music44\": \"\",\n                \"music12\": \"\"\n            },\n            \"fam-64\": {\n                \"music50\": \"\",\n                \"header\": \"\",\n                \"music14\": \"\",\n                \"music35\": \"\",\n                \"music26\": \"\",\n                \"music36\": \"\",\n                \"music49\": \"\",\n                \"music27\": \"\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"\",\n                \"music56\": \"\",\n                \"music37\": \"\",\n                \"music38\": \"\",\n                \"music23\": \"\",\n                \"music18\": \"\",\n                \"music20\": \"\",\n                \"header\": \"\",\n                \"music8\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"\",\n                \"music30\": \"\",\n                \"music31\": \"\",\n                \"header\": \"\",\n                \"music34\": \"\",\n                \"music21\": \"\",\n                \"music16\": \"\",\n                \"music32\": \"\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"\",\n                \"music15\": \"\",\n                \"header\": \"\",\n                \"music5\": \"\",\n                \"music25\": \"\"\n            },\n            \"fam-world\": {\n                \"music41\": \"\",\n                \"music10\": \"\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"\",\n                \"music3\": \"\",\n                \"music1\": \"\",\n                \"music6\": \"\",\n                \"music54\": \"\",\n                \"header\": \"\",\n                \"music4\": \"\",\n                \"music47\": \"\"\n            },\n            \"fam-set1\": {\n                \"header\": \"\",\n                \"music9\": \"\",\n                \"music46\": \"\",\n                \"music42\": \"\",\n                \"music7\": \"\"\n            },\n            \"fam-fight\": {\n                \"music53\": \"\",\n                \"music39\": \"\",\n                \"music19\": \"\",\n                \"header\": \"\",\n                \"music40\": \"\",\n                \"music52\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"\",\n                \"music24\": \"\"\n            }\n        },\n        \"block\": {\n            \"fam-steampunk3\": \"\",\n            \"fam-cave4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-pipes3\": \"\",\n            \"fam-pipes1\": \"\",\n            \"fam-pipes5-3\": \"\",\n            \"fam-pipes4\": \"\",\n            \"fam-desert3\": \"\",\n            \"fam-overworld2\": \"\",\n            \"fam-wood2\": \"\",\n            \"fam-underground2\": \"\",\n            \"fam-overworld1\": \"\",\n            \"fam-dungeon3\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-dungeon1\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-bonus3\": \"\",\n            \"fam-grass4\": \"\",\n            \"fam-mansion4\": \"\",\n            \"fam-special2\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-pipes5-2\": \"\",\n            \"fam-bricks5\": \"\",\n            \"fam-special4\": \"\",\n            \"fam-woods4\": \"\",\n            \"fam-ruins5\": \"\",\n            \"fam-grass3\": \"\",\n            \"fam-misc2\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-misc3\": \"\",\n            \"fam-metal5-3\": \"\",\n            \"fam-house3\": \"\",\n            \"fam-special3\": \"\",\n            \"fam-bonus4\": \"\",\n            \"fam-underground1\": \"\",\n            \"fam-cave3\": \"\",\n            \"fam-wood3\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-misc1\": \"\",\n            \"fam-castle4\": \"\",\n            \"fam-special1\": \"\",\n            \"fam-sized2\": \"\",\n            \"fam-castle2\": \"\"\n        },\n        \"bgo\": {\n            \"fam-ghost4\": \"\",\n            \"fam-key4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sandbg3\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-water4\": \"\",\n            \"fam-water1\": \"\",\n            \"fam-tiles3\": \"\",\n            \"fam-temple5\": \"\",\n            \"fam-art2\": \"\",\n            \"fam-structures3\": \"\",\n            \"fam-plants2\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-water2\": \"\",\n            \"fam-plants4\": \"\",\n            \"fam-bars4\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-plants1\": \"\",\n            \"fam-doors4\": \"\",\n            \"fam-plants3\": \"\",\n            \"fam-fences1\": \"\",\n            \"fam-hut1\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\",\n            \"fam-fences4\": \"\",\n            \"fam-doors2\": \"\"\n        },\n        \"npc\": {\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-char3\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound75\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound76\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"\",\n                \"header\": \"\",\n                \"sound13\": \"\",\n                \"sound30\": \"\",\n                \"sound47\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-world\": {\n                \"header\": \"\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-dirt2\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-stonewall3\": \"\",\n            \"fam-clouds4\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\",\n            \"fam-wall2\": \"\",\n            \"fam-lava3\": \"\"\n        },\n        \"exit-codes\": {\n            \"7\": \"\",\n            \"5\": \"\",\n            \"any\": \"\",\n            \"6\": \"\",\n            \"3\": \"\",\n            \"4\": \"\",\n            \"8\": \"\",\n            \"1\": \"\",\n            \"2\": \"\",\n            \"none\": \"\",\n            \"9\": \"\"\n        }\n    },\n    \"character\": {\n        \"name1\": \"Pers1\",\n        \"name2\": \"Pers2\",\n        \"name3\": \"Pers3\",\n        \"name4\": \"Pers4\",\n        \"name5\": \"Pers5\"\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"\",\n        \"wordFails\": \"\",\n        \"wordStarAccusativePlural\": \"\",\n        \"wordStarAccusativeDualOrCounter\": \"\"\n    },\n    \"languageName\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/assets_hu.json",
    "content": "{\n    \"character\": {\n        \"name2\": \"\",\n        \"name5\": \"\",\n        \"name3\": \"\",\n        \"name1\": \"\",\n        \"name4\": \"\"\n    },\n    \"editor\": {\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"wmusic11\": \"\",\n                \"wmusic10\": \"\",\n                \"wmusic2\": \"\",\n                \"wmusic1\": \"\",\n                \"wmusic9\": \"\",\n                \"header\": \"\",\n                \"wmusic3\": \"\",\n                \"wmusic8\": \"\",\n                \"wmusic6\": \"\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"\",\n                \"wmusic4\": \"\",\n                \"header\": \"\",\n                \"wmusic12\": \"\",\n                \"wmusic16\": \"\",\n                \"wmusic14\": \"\",\n                \"wmusic15\": \"\",\n                \"wmusic7\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"\",\n                \"wmusic5\": \"\"\n            }\n        },\n        \"music\": {\n            \"fam-space\": {\n                \"music11\": \"\",\n                \"music22\": \"\",\n                \"header\": \"\",\n                \"music45\": \"\",\n                \"music44\": \"\",\n                \"music12\": \"\"\n            },\n            \"fam-64\": {\n                \"music50\": \"\",\n                \"header\": \"\",\n                \"music14\": \"\",\n                \"music35\": \"\",\n                \"music26\": \"\",\n                \"music36\": \"\",\n                \"music49\": \"\",\n                \"music27\": \"\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"\",\n                \"music56\": \"\",\n                \"music37\": \"\",\n                \"music38\": \"\",\n                \"music23\": \"\",\n                \"music18\": \"\",\n                \"music20\": \"\",\n                \"header\": \"\",\n                \"music8\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"\",\n                \"music30\": \"\",\n                \"music31\": \"\",\n                \"header\": \"\",\n                \"music34\": \"\",\n                \"music21\": \"\",\n                \"music16\": \"\",\n                \"music32\": \"\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"\",\n                \"music15\": \"\",\n                \"header\": \"\",\n                \"music5\": \"\",\n                \"music25\": \"\"\n            },\n            \"fam-world\": {\n                \"music41\": \"\",\n                \"music10\": \"\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"\",\n                \"music3\": \"\",\n                \"music1\": \"\",\n                \"music6\": \"\",\n                \"music54\": \"\",\n                \"header\": \"\",\n                \"music4\": \"\",\n                \"music47\": \"\"\n            },\n            \"fam-set1\": {\n                \"header\": \"\",\n                \"music9\": \"\",\n                \"music46\": \"\",\n                \"music42\": \"\",\n                \"music7\": \"\"\n            },\n            \"fam-fight\": {\n                \"music53\": \"\",\n                \"music39\": \"\",\n                \"music19\": \"\",\n                \"header\": \"\",\n                \"music40\": \"\",\n                \"music52\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"\",\n                \"music24\": \"\"\n            }\n        },\n        \"block\": {\n            \"fam-steampunk3\": \"\",\n            \"fam-cave4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-pipes3\": \"\",\n            \"fam-pipes1\": \"\",\n            \"fam-pipes5-3\": \"\",\n            \"fam-pipes4\": \"\",\n            \"fam-desert3\": \"\",\n            \"fam-overworld2\": \"\",\n            \"fam-wood2\": \"\",\n            \"fam-underground2\": \"\",\n            \"fam-overworld1\": \"\",\n            \"fam-dungeon3\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-dungeon1\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-bonus3\": \"\",\n            \"fam-grass4\": \"\",\n            \"fam-mansion4\": \"\",\n            \"fam-special2\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-pipes5-2\": \"\",\n            \"fam-bricks5\": \"\",\n            \"fam-special4\": \"\",\n            \"fam-woods4\": \"\",\n            \"fam-ruins5\": \"\",\n            \"fam-grass3\": \"\",\n            \"fam-misc2\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-misc3\": \"\",\n            \"fam-metal5-3\": \"\",\n            \"fam-house3\": \"\",\n            \"fam-special3\": \"\",\n            \"fam-bonus4\": \"\",\n            \"fam-underground1\": \"\",\n            \"fam-cave3\": \"\",\n            \"fam-wood3\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-misc1\": \"\",\n            \"fam-castle4\": \"\",\n            \"fam-special1\": \"\",\n            \"fam-sized2\": \"\",\n            \"fam-castle2\": \"\"\n        },\n        \"bgo\": {\n            \"fam-ghost4\": \"\",\n            \"fam-key4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sandbg3\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-water4\": \"\",\n            \"fam-water1\": \"\",\n            \"fam-tiles3\": \"\",\n            \"fam-temple5\": \"\",\n            \"fam-art2\": \"\",\n            \"fam-structures3\": \"\",\n            \"fam-plants2\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-water2\": \"\",\n            \"fam-plants4\": \"\",\n            \"fam-bars4\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-plants1\": \"\",\n            \"fam-doors4\": \"\",\n            \"fam-plants3\": \"\",\n            \"fam-fences1\": \"\",\n            \"fam-hut1\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\",\n            \"fam-fences4\": \"\",\n            \"fam-doors2\": \"\"\n        },\n        \"background2\": {\n            \"fam-set1\": {\n                \"bg2-10\": \"\",\n                \"bg2-7\": \"\",\n                \"bg2-9\": \"\",\n                \"bg2-8\": \"\",\n                \"bg2-50\": \"\",\n                \"header\": \"\",\n                \"bg2-41\": \"\",\n                \"bg2-51\": \"\"\n            },\n            \"fam-set2\": {\n                \"bg2-57\": \"\",\n                \"bg2-48\": \"\",\n                \"bg2-53\": \"\",\n                \"bg2-49\": \"\",\n                \"bg2-25\": \"\",\n                \"bg2-54\": \"\",\n                \"bg2-52\": \"\",\n                \"bg2-5\": \"\",\n                \"bg2-44\": \"\",\n                \"header\": \"\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"\",\n                \"bg2-21\": \"\",\n                \"bg2-4\": \"\",\n                \"bg2-15\": \"\",\n                \"bg2-36\": \"\",\n                \"header\": \"\",\n                \"bg2-6\": \"\",\n                \"bg2-3\": \"\",\n                \"bg2-39\": \"\",\n                \"bg2-20\": \"\",\n                \"bg2-38\": \"\",\n                \"bg2-26\": \"\",\n                \"bg2-1\": \"\",\n                \"bg2-24\": \"\",\n                \"bg2-23\": \"\",\n                \"bg2-27\": \"\",\n                \"bg2-22\": \"\",\n                \"bg2-37\": \"\",\n                \"bg2-35\": \"\",\n                \"bg2-2\": \"\",\n                \"bg2-14\": \"\",\n                \"bg2-17\": \"\",\n                \"bg2-56\": \"\"\n            },\n            \"fam-misc\": {\n                \"header\": \"\",\n                \"bg2-16\": \"\",\n                \"bg2-46\": \"\",\n                \"bg2-47\": \"\",\n                \"bg2-40\": \"\",\n                \"bg2-45\": \"\"\n            },\n            \"fam-set4\": {\n                \"bg2-28\": \"\",\n                \"bg2-32\": \"\",\n                \"header\": \"\",\n                \"bg2-33\": \"\",\n                \"bg2-30\": \"\",\n                \"bg2-55\": \"\",\n                \"bg2-58\": \"\",\n                \"bg2-11\": \"\",\n                \"bg2-12\": \"\",\n                \"bg2-19\": \"\",\n                \"bg2-18\": \"\",\n                \"bg2-42\": \"\",\n                \"bg2-29\": \"\",\n                \"bg2-43\": \"\",\n                \"bg2-31\": \"\",\n                \"bg2-34\": \"\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"\"\n            }\n        },\n        \"npc\": {\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-char3\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound75\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound76\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"\",\n                \"header\": \"\",\n                \"sound13\": \"\",\n                \"sound30\": \"\",\n                \"sound47\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-world\": {\n                \"header\": \"\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-dirt2\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-stonewall3\": \"\",\n            \"fam-clouds4\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\",\n            \"fam-wall2\": \"\",\n            \"fam-lava3\": \"\"\n        },\n        \"exit-codes\": {\n            \"7\": \"\",\n            \"5\": \"\",\n            \"any\": \"\",\n            \"6\": \"\",\n            \"3\": \"\",\n            \"4\": \"\",\n            \"8\": \"\",\n            \"1\": \"\",\n            \"2\": \"\",\n            \"none\": \"\",\n            \"9\": \"\"\n        }\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"\",\n        \"wordFails\": \"\",\n        \"wordStarAccusativePlural\": \"\",\n        \"wordStarAccusativeDualOrCounter\": \"\"\n    },\n    \"languageName\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/assets_it.json",
    "content": "{\n    \"editor\": {\n        \"background2\": {\n            \"fam-set1\": {\n                \"bg2-51\": \"Deserto\",\n                \"bg2-9\": \"Notte 2\",\n                \"bg2-10\": \"Aria aperta\",\n                \"bg2-41\": \"Castello\",\n                \"bg2-50\": \"Funghi\",\n                \"bg2-7\": \"Sotterraneo\",\n                \"bg2-8\": \"Notte\",\n                \"header\": \"Set 1\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"Nuvole\",\n                \"bg2-36\": \"Nuvole 2\",\n                \"bg2-38\": \"Caverna\",\n                \"bg2-39\": \"Caverna 2\",\n                \"bg2-4\": \"Tubi\",\n                \"bg2-6\": \"Bonus\",\n                \"bg2-56\": \"Sott'acqua\",\n                \"bg2-1\": \"Blocchi\",\n                \"bg2-14\": \"Deserto\",\n                \"bg2-17\": \"Nave\",\n                \"bg2-2\": \"Colline\",\n                \"bg2-20\": \"Foresta\",\n                \"bg2-21\": \"Battaglia\",\n                \"bg2-22\": \"Cascata\",\n                \"bg2-23\": \"Carri armati\",\n                \"bg2-24\": \"Boss finale\",\n                \"bg2-27\": \"Castello\",\n                \"bg2-35\": \"Alberi innevati\",\n                \"bg2-37\": \"Colline innevate\",\n                \"bg2-15\": \"Cripta 2\",\n                \"bg2-3\": \"Cripta\",\n                \"header\": \"Set 3\",\n                \"bg2-26\": \"\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"Nessuno\"\n            },\n            \"fam-misc\": {\n                \"bg2-16\": \"Cratere spaziale\",\n                \"bg2-40\": \"Miniera segreta\",\n                \"bg2-46\": \"Nave spaziale\",\n                \"bg2-45\": \"Palude spaziale\",\n                \"bg2-47\": \"Base spaziale\",\n                \"header\": \"Varie\"\n            },\n            \"fam-set4\": {\n                \"bg2-11\": \"Colline\",\n                \"bg2-12\": \"Alberi\",\n                \"bg2-18\": \"Villa\",\n                \"bg2-28\": \"Bonus\",\n                \"bg2-19\": \"Foresta\",\n                \"bg2-31\": \"Nuvole\",\n                \"bg2-29\": \"Notte\",\n                \"bg2-34\": \"Colline 3\",\n                \"bg2-30\": \"Caverna\",\n                \"bg2-32\": \"Colline 2\",\n                \"bg2-33\": \"Colline 4\",\n                \"bg2-42\": \"Castello\",\n                \"bg2-43\": \"Castello 2\",\n                \"bg2-55\": \"Sott'acqua\",\n                \"bg2-58\": \"Deserto (notte)\",\n                \"header\": \"Set 4\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"Sotterraneo\",\n                \"bg2-44\": \"Castello\",\n                \"bg2-48\": \"Nuvole\",\n                \"bg2-49\": \"Notte - Colline\",\n                \"bg2-5\": \"Alberi\",\n                \"bg2-52\": \"Notte - Deserto\",\n                \"bg2-53\": \"Scogliera\",\n                \"bg2-54\": \"Magazzino\",\n                \"bg2-57\": \"Cripta\",\n                \"header\": \"Set 2\"\n            }\n        },\n        \"bgo\": {\n            \"fam-key4\": \"Chiave\",\n            \"fam-hut1\": \"Capanna\",\n            \"fam-misc4\": \"Varie\",\n            \"fam-plants1\": \"Piante\",\n            \"fam-plants2\": \"Piante\",\n            \"fam-temple5\": \"Tempio\",\n            \"fam-plants3\": \"Piante\",\n            \"fam-plants4\": \"Piante\",\n            \"fam-structures3\": \"Strutture\",\n            \"fam-clouds1\": \"Nuvole\",\n            \"fam-doors2\": \"Porte\",\n            \"fam-doors4\": \"Porte\",\n            \"fam-exit3\": \"Uscita\",\n            \"fam-fences1\": \"Recinzioni\",\n            \"fam-fences4\": \"Recinzioni\",\n            \"fam-ghost4\": \"Fantasma\",\n            \"fam-tiles3\": \"Blocchi\",\n            \"fam-water1\": \"Acqua\",\n            \"fam-water2\": \"Acqua\",\n            \"fam-water3\": \"Acqua\",\n            \"fam-water4\": \"Acqua\",\n            \"fam-sandbg3\": \"\",\n            \"fam-art2\": \"\",\n            \"fam-bars4\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\"\n        },\n        \"block\": {\n            \"fam-special3\": \"Speciale\",\n            \"fam-misc1\": \"Varie\",\n            \"fam-misc4\": \"Varie\",\n            \"fam-pipes4\": \"Tubi\",\n            \"fam-special1\": \"Speciale\",\n            \"fam-pipes5-2\": \"Tubi\",\n            \"fam-special2\": \"Speciale\",\n            \"fam-special4\": \"Speciale\",\n            \"fam-underground1\": \"Sotterraneo\",\n            \"fam-underground2\": \"Sotterraneo\",\n            \"fam-wood2\": \"Legno\",\n            \"fam-water3\": \"Acqua\",\n            \"fam-bonus3\": \"Bonus\",\n            \"fam-bonus4\": \"Bonus\",\n            \"fam-bricks5\": \"Mattone\",\n            \"fam-castle2\": \"Castello\",\n            \"fam-castle4\": \"Castello\",\n            \"fam-cave3\": \"Caverna\",\n            \"fam-cave4\": \"Caverna\",\n            \"fam-clouds1\": \"Nuvole\",\n            \"fam-desert3\": \"Deserto\",\n            \"fam-grass3\": \"Erba\",\n            \"fam-grass4\": \"Erba\",\n            \"fam-house3\": \"Casa\",\n            \"fam-mansion4\": \"Villa\",\n            \"fam-metal5-3\": \"Varie\",\n            \"fam-dungeon1\": \"Cripta\",\n            \"fam-dungeon3\": \"Cripta\",\n            \"fam-char5\": \"Personaggio\",\n            \"fam-misc2\": \"Varie\",\n            \"fam-misc3\": \"Varie 3\",\n            \"fam-overworld1\": \"Aria aperta\",\n            \"fam-overworld2\": \"Aria aperta\",\n            \"fam-pipes1\": \"Tubi\",\n            \"fam-pipes3\": \"Tubi\",\n            \"fam-pipes5-3\": \"Tubi lunghi\",\n            \"fam-ruins5\": \"Rovine\",\n            \"fam-wood3\": \"Legno\",\n            \"fam-woods4\": \"Bosco\",\n            \"fam-steampunk3\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-sized2\": \"\"\n        },\n        \"music\": {\n            \"fam-misc\": {\n                \"header\": \"Varie\",\n                \"music37\": \"Montagna ghiacciata\",\n                \"music18\": \"Spiaggia\",\n                \"music23\": \"Bosco eroico\",\n                \"music38\": \"Villaggio della giungla\",\n                \"music55\": \"Tema del titolo\",\n                \"music56\": \"\",\n                \"music20\": \"\",\n                \"music8\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-fight\": {\n                \"music52\": \"Sotterraneo\",\n                \"music39\": \"Tempio\",\n                \"music40\": \"Cavaliere\",\n                \"music53\": \"\",\n                \"music19\": \"\",\n                \"header\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music34\": \"Città\",\n                \"header\": \"RPG\",\n                \"music16\": \"Foresta\",\n                \"music21\": \"Battaglia\",\n                \"music31\": \"Mare\",\n                \"music32\": \"Stagno\",\n                \"music33\": \"Nuvole\",\n                \"music30\": \"\"\n            },\n            \"fam-set3\": {\n                \"music1\": \"Aria aperta\",\n                \"music3\": \"Cripta\",\n                \"music4\": \"Sotterraneo\",\n                \"header\": \"Set 3\",\n                \"music2\": \"\",\n                \"music6\": \"\",\n                \"music54\": \"\",\n                \"music47\": \"\"\n            },\n            \"fam-world\": {\n                \"music10\": \"Aria aperta\",\n                \"music41\": \"Cripta\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            },\n            \"fam-64\": {\n                \"music14\": \"Deserto\",\n                \"music26\": \"Castello\",\n                \"music27\": \"Tema principale\",\n                \"music35\": \"Neve\",\n                \"music36\": \"Boss\",\n                \"music49\": \"Acqua\",\n                \"music50\": \"Caverna\",\n                \"header\": \"\"\n            },\n            \"fam-set1\": {\n                \"music42\": \"Cripta\",\n                \"music9\": \"Aria aperta\",\n                \"music7\": \"Sotterraneo\",\n                \"header\": \"Set 1\",\n                \"music46\": \"\"\n            },\n            \"fam-set2\": {\n                \"music5\": \"Aria aperta\",\n                \"music25\": \"Sotterraneo\",\n                \"header\": \"Set 2\",\n                \"music43\": \"\",\n                \"music15\": \"\"\n            },\n            \"fam-space\": {\n                \"music11\": \"\",\n                \"music22\": \"\",\n                \"header\": \"\",\n                \"music45\": \"\",\n                \"music44\": \"\",\n                \"music12\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"\",\n                \"music24\": \"\"\n            }\n        },\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"header\": \"Set 3\",\n                \"wmusic11\": \"\",\n                \"wmusic10\": \"\",\n                \"wmusic2\": \"\",\n                \"wmusic1\": \"\",\n                \"wmusic9\": \"\",\n                \"wmusic3\": \"\",\n                \"wmusic8\": \"\",\n                \"wmusic6\": \"\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"Cripta\",\n                \"wmusic4\": \"\",\n                \"header\": \"\",\n                \"wmusic12\": \"\",\n                \"wmusic16\": \"\",\n                \"wmusic14\": \"\",\n                \"wmusic15\": \"\",\n                \"wmusic7\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"\",\n                \"wmusic5\": \"\"\n            }\n        },\n        \"npc\": {\n            \"fam-char3\": \"Personaggio\",\n            \"fam-char5\": \"Personaggio\",\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        },\n        \"exit-codes\": {\n            \"4\": \"Chiave\",\n            \"7\": \"\",\n            \"5\": \"\",\n            \"any\": \"\",\n            \"6\": \"\",\n            \"3\": \"\",\n            \"8\": \"\",\n            \"1\": \"\",\n            \"2\": \"\",\n            \"none\": \"\",\n            \"9\": \"\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound75\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound76\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"\",\n                \"header\": \"\",\n                \"sound13\": \"\",\n                \"sound30\": \"\",\n                \"sound47\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-world\": {\n                \"header\": \"\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-dirt2\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-stonewall3\": \"\",\n            \"fam-clouds4\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\",\n            \"fam-wall2\": \"\",\n            \"fam-lava3\": \"\"\n        }\n    },\n    \"character\": {\n        \"name1\": \"Personaggio1\",\n        \"name2\": \"Personaggio2\",\n        \"name3\": \"Personaggio3\",\n        \"name4\": \"Personaggio4\",\n        \"name5\": \"Personaggio5\"\n    },\n    \"objects\": {\n        \"wordStarAccusativePlural\": \"stelle\",\n        \"wordStarAccusativeSingular\": \"stella\",\n        \"wordStarAccusativeDualOrCounter\": \"\",\n        \"wordFails\": \"\"\n    },\n    \"languageName\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/assets_ja.json",
    "content": "{\n    \"character\": {\n        \"name2\": \"Char2\",\n        \"name5\": \"Char5\",\n        \"name3\": \"Char3\",\n        \"name1\": \"Char1\",\n        \"name4\": \"Char4\"\n    },\n    \"editor\": {\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"wmusic11\": \"\",\n                \"wmusic10\": \"\",\n                \"wmusic2\": \"\",\n                \"wmusic1\": \"\",\n                \"wmusic9\": \"\",\n                \"header\": \"\",\n                \"wmusic3\": \"\",\n                \"wmusic8\": \"\",\n                \"wmusic6\": \"\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"\",\n                \"wmusic4\": \"\",\n                \"header\": \"\",\n                \"wmusic12\": \"\",\n                \"wmusic16\": \"\",\n                \"wmusic14\": \"\",\n                \"wmusic15\": \"\",\n                \"wmusic7\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"\",\n                \"wmusic5\": \"\"\n            }\n        },\n        \"music\": {\n            \"fam-space\": {\n                \"music11\": \"\",\n                \"music22\": \"\",\n                \"header\": \"\",\n                \"music45\": \"\",\n                \"music44\": \"\",\n                \"music12\": \"\"\n            },\n            \"fam-64\": {\n                \"music50\": \"\",\n                \"header\": \"\",\n                \"music14\": \"\",\n                \"music35\": \"\",\n                \"music26\": \"\",\n                \"music36\": \"\",\n                \"music49\": \"\",\n                \"music27\": \"\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"\",\n                \"music56\": \"\",\n                \"music37\": \"\",\n                \"music38\": \"\",\n                \"music23\": \"\",\n                \"music18\": \"\",\n                \"music20\": \"\",\n                \"header\": \"\",\n                \"music8\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"\",\n                \"music30\": \"\",\n                \"music31\": \"\",\n                \"header\": \"\",\n                \"music34\": \"\",\n                \"music21\": \"\",\n                \"music16\": \"\",\n                \"music32\": \"\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"\",\n                \"music15\": \"\",\n                \"header\": \"\",\n                \"music5\": \"\",\n                \"music25\": \"\"\n            },\n            \"fam-world\": {\n                \"music41\": \"\",\n                \"music10\": \"\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"\",\n                \"music3\": \"\",\n                \"music1\": \"\",\n                \"music6\": \"\",\n                \"music54\": \"\",\n                \"header\": \"\",\n                \"music4\": \"\",\n                \"music47\": \"\"\n            },\n            \"fam-set1\": {\n                \"header\": \"\",\n                \"music9\": \"\",\n                \"music46\": \"\",\n                \"music42\": \"\",\n                \"music7\": \"\"\n            },\n            \"fam-fight\": {\n                \"music53\": \"\",\n                \"music39\": \"\",\n                \"music19\": \"\",\n                \"header\": \"\",\n                \"music40\": \"\",\n                \"music52\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"\",\n                \"music24\": \"\"\n            }\n        },\n        \"block\": {\n            \"fam-steampunk3\": \"\",\n            \"fam-cave4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-pipes3\": \"\",\n            \"fam-pipes1\": \"\",\n            \"fam-pipes5-3\": \"\",\n            \"fam-pipes4\": \"\",\n            \"fam-desert3\": \"\",\n            \"fam-overworld2\": \"\",\n            \"fam-wood2\": \"\",\n            \"fam-underground2\": \"\",\n            \"fam-overworld1\": \"\",\n            \"fam-dungeon3\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-dungeon1\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-bonus3\": \"\",\n            \"fam-grass4\": \"\",\n            \"fam-mansion4\": \"\",\n            \"fam-special2\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-pipes5-2\": \"\",\n            \"fam-bricks5\": \"\",\n            \"fam-special4\": \"\",\n            \"fam-woods4\": \"\",\n            \"fam-ruins5\": \"\",\n            \"fam-grass3\": \"\",\n            \"fam-misc2\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-misc3\": \"\",\n            \"fam-metal5-3\": \"\",\n            \"fam-house3\": \"\",\n            \"fam-special3\": \"\",\n            \"fam-bonus4\": \"\",\n            \"fam-underground1\": \"\",\n            \"fam-cave3\": \"\",\n            \"fam-wood3\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-misc1\": \"\",\n            \"fam-castle4\": \"\",\n            \"fam-special1\": \"\",\n            \"fam-sized2\": \"\",\n            \"fam-castle2\": \"\"\n        },\n        \"bgo\": {\n            \"fam-ghost4\": \"\",\n            \"fam-key4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sandbg3\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-water4\": \"\",\n            \"fam-water1\": \"\",\n            \"fam-tiles3\": \"\",\n            \"fam-temple5\": \"\",\n            \"fam-art2\": \"オブジェ\",\n            \"fam-structures3\": \"\",\n            \"fam-plants2\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-water2\": \"\",\n            \"fam-plants4\": \"\",\n            \"fam-bars4\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-plants1\": \"\",\n            \"fam-doors4\": \"\",\n            \"fam-plants3\": \"\",\n            \"fam-fences1\": \"\",\n            \"fam-hut1\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\",\n            \"fam-fences4\": \"\",\n            \"fam-doors2\": \"\"\n        },\n        \"background2\": {\n            \"fam-set1\": {\n                \"bg2-10\": \"地上\",\n                \"bg2-7\": \"地下\",\n                \"bg2-9\": \"夜 2\",\n                \"bg2-8\": \"夜\",\n                \"bg2-50\": \"キノコ\",\n                \"header\": \"セット 1\",\n                \"bg2-41\": \"お城\",\n                \"bg2-51\": \"さばく\"\n            },\n            \"fam-set2\": {\n                \"bg2-57\": \"ダンジョン\",\n                \"bg2-48\": \"雲の上\",\n                \"bg2-53\": \"ガケ\",\n                \"bg2-49\": \"夜の丘\",\n                \"bg2-25\": \"地下\",\n                \"bg2-54\": \"倉庫\",\n                \"bg2-52\": \"夜のさばく\",\n                \"bg2-5\": \"森\",\n                \"bg2-44\": \"お城\",\n                \"header\": \"セット 2\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"くもの上\",\n                \"bg2-21\": \"バトルコース\",\n                \"bg2-4\": \"下水道\",\n                \"bg2-15\": \"ダンジョン 2\",\n                \"bg2-36\": \"くもの上 2\",\n                \"header\": \"セット 3\",\n                \"bg2-6\": \"ボーナス\",\n                \"bg2-3\": \"ダンジョン\",\n                \"bg2-39\": \"洞窟 2\",\n                \"bg2-20\": \"森\",\n                \"bg2-38\": \"洞窟\",\n                \"bg2-26\": \"キノピオハウス\",\n                \"bg2-1\": \"ブロック\",\n                \"bg2-24\": \"ラスボス\",\n                \"bg2-23\": \"戦車\",\n                \"bg2-27\": \"お城\",\n                \"bg2-22\": \"滝\",\n                \"bg2-37\": \"雪国の丘\",\n                \"bg2-35\": \"雪国の林\",\n                \"bg2-2\": \"丘\",\n                \"bg2-14\": \"さばく\",\n                \"bg2-17\": \"飛行船\",\n                \"bg2-56\": \"水中\"\n            },\n            \"fam-misc\": {\n                \"header\": \"その他\",\n                \"bg2-16\": \"宇宙クレーター\",\n                \"bg2-46\": \"宇宙船\",\n                \"bg2-47\": \"うちゅう基地\",\n                \"bg2-40\": \"ヒミツの坑道\",\n                \"bg2-45\": \"宇宙の沼地\"\n            },\n            \"fam-set4\": {\n                \"bg2-28\": \"宮殿\",\n                \"bg2-32\": \"丘 2\",\n                \"header\": \"セット 4\",\n                \"bg2-33\": \"丘 4\",\n                \"bg2-30\": \"洞窟\",\n                \"bg2-55\": \"水中\",\n                \"bg2-58\": \"夜のさばく\",\n                \"bg2-11\": \"丘\",\n                \"bg2-12\": \"森\",\n                \"bg2-19\": \"夜の森\",\n                \"bg2-18\": \"おばけやしき\",\n                \"bg2-42\": \"お城\",\n                \"bg2-29\": \"夜\",\n                \"bg2-43\": \"お城 2\",\n                \"bg2-31\": \"くもの上\",\n                \"bg2-34\": \"丘3\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"なし\"\n            }\n        },\n        \"npc\": {\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-char3\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound75\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound76\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"\",\n                \"header\": \"\",\n                \"sound13\": \"\",\n                \"sound30\": \"\",\n                \"sound47\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-world\": {\n                \"header\": \"\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-dirt2\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-stonewall3\": \"\",\n            \"fam-clouds4\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\",\n            \"fam-wall2\": \"\",\n            \"fam-lava3\": \"\"\n        },\n        \"exit-codes\": {\n            \"7\": \"\",\n            \"5\": \"\",\n            \"any\": \"\",\n            \"6\": \"\",\n            \"3\": \"\",\n            \"4\": \"\",\n            \"8\": \"\",\n            \"1\": \"\",\n            \"2\": \"\",\n            \"none\": \"\",\n            \"9\": \"\"\n        }\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"スター\",\n        \"wordStarAccusativeDualOrCounter\": \"スター\",\n        \"wordFails\": \"ミス数\",\n        \"wordStarAccusativePlural\": \"スター\"\n    },\n    \"languageName\": \"日本語\"\n}\n"
  },
  {
    "path": "resources/languages/assets_ko.json",
    "content": "{\n    \"editor\": {\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"wmusic11\": \"세계 5\",\n                \"wmusic10\": \"세계 6\",\n                \"wmusic2\": \"세계 4\",\n                \"wmusic1\": \"세계 1\",\n                \"wmusic9\": \"세계 8\",\n                \"header\": \"세트 3\",\n                \"wmusic3\": \"세계 7\",\n                \"wmusic8\": \"세계 3\",\n                \"wmusic6\": \"세계 2\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"지하 감옥\",\n                \"wmusic4\": \"테마\",\n                \"header\": \"세계\",\n                \"wmusic12\": \"특수\",\n                \"wmusic16\": \"동굴\",\n                \"wmusic14\": \"하늘\",\n                \"wmusic15\": \"섬\",\n                \"wmusic7\": \"숲\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"없음\"\n            },\n            \"fam-new\": {\n                \"header\": \"새로운\",\n                \"wmusic5\": \"테마\"\n            }\n        },\n        \"music\": {\n            \"fam-space\": {\n                \"music11\": \"붉은 늪\",\n                \"music22\": \"우주 임무\",\n                \"header\": \"우주\",\n                \"music45\": \"마지막 보스\",\n                \"music44\": \"아이템 방\",\n                \"music12\": \"분화구\"\n            },\n            \"fam-64\": {\n                \"music50\": \"동굴\",\n                \"music14\": \"사막\",\n                \"music35\": \"눈\",\n                \"music26\": \"성\",\n                \"music36\": \"보스\",\n                \"music49\": \"물\",\n                \"music27\": \"주요 테마\",\n                \"header\": \"64\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"타이틀 테마\",\n                \"music56\": \"활기 넘치는 경주\",\n                \"music37\": \"얼어붙은 산\",\n                \"music38\": \"정글 마을\",\n                \"music23\": \"영웅의 숲\",\n                \"music18\": \"해변\",\n                \"music20\": \"핵융합로\",\n                \"header\": \"기타\",\n                \"music8\": \"궁지에 몰렸다!\",\n                \"music13\": \"새로운 저승\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"구름\",\n                \"music30\": \"학사 패드\",\n                \"music31\": \"해안\",\n                \"header\": \"롤플레잉 게임\",\n                \"music34\": \"마을\",\n                \"music21\": \"전투\",\n                \"music16\": \"숲\",\n                \"music32\": \"연못\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"마지막 보스\",\n                \"music15\": \"보스\",\n                \"header\": \"세트 2\",\n                \"music5\": \"저승\",\n                \"music25\": \"지하\"\n            },\n            \"fam-world\": {\n                \"music41\": \"지하 감옥\",\n                \"music10\": \"저승\",\n                \"header\": \"세계\",\n                \"music28\": \"하늘\",\n                \"music51\": \"보스\",\n                \"music48\": \"물\",\n                \"music29\": \"동굴\",\n                \"music17\": \"맨션\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"하늘\",\n                \"music3\": \"지하 감옥\",\n                \"music1\": \"저승\",\n                \"music6\": \"보스\",\n                \"music54\": \"배회하는 적\",\n                \"header\": \"세트 3\",\n                \"music4\": \"지하\",\n                \"music47\": \"물\"\n            },\n            \"fam-set1\": {\n                \"header\": \"세트 1\",\n                \"music9\": \"저승\",\n                \"music46\": \"물\",\n                \"music42\": \"지하 감옥\",\n                \"music7\": \"지하\"\n            },\n            \"fam-fight\": {\n                \"music53\": \"핀볼\",\n                \"music39\": \"사원\",\n                \"music19\": \"스팀펑크\",\n                \"header\": \"싸움\",\n                \"music40\": \"기사\",\n                \"music52\": \"지하\"\n            },\n            \"auto\": {\n                \"music0\": \"없음\",\n                \"music24\": \"커스텀\"\n            }\n        },\n        \"block\": {\n            \"fam-steampunk3\": \"스팀펑크\",\n            \"fam-cave4\": \"동굴\",\n            \"fam-misc4\": \"기타\",\n            \"fam-water3\": \"물\",\n            \"fam-sized1\": \"크기\",\n            \"fam-pipes3\": \"파이프\",\n            \"fam-pipes1\": \"파이프\",\n            \"fam-pipes5-3\": \"긴 파이프\",\n            \"fam-pipes4\": \"파이프\",\n            \"fam-desert3\": \"사막\",\n            \"fam-overworld2\": \"저승\",\n            \"fam-wood2\": \"목재\",\n            \"fam-underground2\": \"지하\",\n            \"fam-overworld1\": \"저승\",\n            \"fam-dungeon3\": \"지하 감옥\",\n            \"fam-hurts3\": \"상처\",\n            \"fam-clouds1\": \"구름\",\n            \"fam-dungeon1\": \"지하 감옥\",\n            \"fam-sized4\": \"크기\",\n            \"fam-bonus3\": \"보너스\",\n            \"fam-grass4\": \"잔디\",\n            \"fam-mansion4\": \"맨션\",\n            \"fam-special2\": \"특수\",\n            \"fam-switch4\": \"스위치\",\n            \"fam-pipes5-2\": \"파이프\",\n            \"fam-bricks5\": \"벽돌\",\n            \"fam-special4\": \"특수\",\n            \"fam-woods4\": \"목재\",\n            \"fam-ruins5\": \"유적\",\n            \"fam-grass3\": \"잔디\",\n            \"fam-misc2\": \"기타\",\n            \"fam-char5\": \"캐릭터\",\n            \"fam-misc3\": \"기타 3\",\n            \"fam-metal5-3\": \"기타\",\n            \"fam-house3\": \"집\",\n            \"fam-special3\": \"특수\",\n            \"fam-bonus4\": \"보너스\",\n            \"fam-underground1\": \"지하\",\n            \"fam-cave3\": \"동굴\",\n            \"fam-wood3\": \"목재\",\n            \"fam-sized3\": \"크기 3\",\n            \"fam-lava4\": \"용암과 상처\",\n            \"fam-metal5-1\": \"접시\",\n            \"fam-misc1\": \"기타\",\n            \"fam-castle4\": \"성\",\n            \"fam-special1\": \"특수\",\n            \"fam-sized2\": \"크기\",\n            \"fam-castle2\": \"성\"\n        },\n        \"bgo\": {\n            \"fam-ghost4\": \"유령\",\n            \"fam-key4\": \"열쇠\",\n            \"fam-misc4\": \"기타\",\n            \"fam-sandbg3\": \"모래 BG\",\n            \"fam-water3\": \"물\",\n            \"fam-water4\": \"물\",\n            \"fam-water1\": \"물\",\n            \"fam-tiles3\": \"타일\",\n            \"fam-temple5\": \"사원\",\n            \"fam-art2\": \"예술\",\n            \"fam-structures3\": \"구조\",\n            \"fam-plants2\": \"식물\",\n            \"fam-exit3\": \"종료\",\n            \"fam-water2\": \"물\",\n            \"fam-plants4\": \"식물\",\n            \"fam-bars4\": \"막대\",\n            \"fam-platform4\": \"플랫폼\",\n            \"fam-clouds1\": \"구름\",\n            \"fam-plants1\": \"식물\",\n            \"fam-doors4\": \"문\",\n            \"fam-plants3\": \"식물\",\n            \"fam-fences1\": \"울타리\",\n            \"fam-hut1\": \"오두막\",\n            \"fam-space5\": \"우주벌레\",\n            \"fam-sandfg3\": \"전망\",\n            \"fam-fences4\": \"울타리\",\n            \"fam-doors2\": \"문\"\n        },\n        \"background2\": {\n            \"fam-set1\": {\n                \"bg2-10\": \"저승\",\n                \"bg2-7\": \"지하\",\n                \"bg2-9\": \"밤 2\",\n                \"bg2-8\": \"밤\",\n                \"bg2-50\": \"버섯\",\n                \"header\": \"세트 1\",\n                \"bg2-41\": \"성\",\n                \"bg2-51\": \"사막\"\n            },\n            \"fam-set2\": {\n                \"bg2-57\": \"지하 감옥\",\n                \"bg2-48\": \"구름\",\n                \"bg2-53\": \"절벽\",\n                \"bg2-49\": \"밤 - 언덕\",\n                \"bg2-25\": \"지하\",\n                \"bg2-54\": \"창고\",\n                \"bg2-52\": \"밤 - 사막\",\n                \"bg2-5\": \"나무\",\n                \"bg2-44\": \"성\",\n                \"header\": \"세트 2\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"구름\",\n                \"bg2-21\": \"전투\",\n                \"bg2-4\": \"파이프\",\n                \"bg2-15\": \"지하 감옥 2\",\n                \"bg2-36\": \"구름 2\",\n                \"header\": \"세트 3\",\n                \"bg2-6\": \"보너스\",\n                \"bg2-3\": \"지하 감옥\",\n                \"bg2-39\": \"동굴 2\",\n                \"bg2-20\": \"숲\",\n                \"bg2-38\": \"동굴\",\n                \"bg2-26\": \"버섯 딜러\",\n                \"bg2-1\": \"블록\",\n                \"bg2-24\": \"최종 보스\",\n                \"bg2-23\": \"탱크\",\n                \"bg2-27\": \"성\",\n                \"bg2-22\": \"폭포\",\n                \"bg2-37\": \"언 덮힌 언덕\",\n                \"bg2-35\": \"눈 덮힌 나무\",\n                \"bg2-2\": \"언덕\",\n                \"bg2-14\": \"사막\",\n                \"bg2-17\": \"배\",\n                \"bg2-56\": \"수중\"\n            },\n            \"fam-misc\": {\n                \"header\": \"기타\",\n                \"bg2-16\": \"우주 분화구\",\n                \"bg2-46\": \"우주선\",\n                \"bg2-47\": \"우주 기지\",\n                \"bg2-40\": \"비밀 광산\",\n                \"bg2-45\": \"우주 늪\"\n            },\n            \"fam-set4\": {\n                \"bg2-28\": \"보너스\",\n                \"bg2-32\": \"언덕 2\",\n                \"header\": \"세트 4\",\n                \"bg2-33\": \"언덕 4\",\n                \"bg2-30\": \"동굴\",\n                \"bg2-55\": \"수중\",\n                \"bg2-58\": \"사막의 밤\",\n                \"bg2-11\": \"언덕\",\n                \"bg2-12\": \"나무\",\n                \"bg2-19\": \"숲\",\n                \"bg2-18\": \"맨션\",\n                \"bg2-42\": \"성\",\n                \"bg2-29\": \"밤\",\n                \"bg2-43\": \"성 2\",\n                \"bg2-31\": \"구름\",\n                \"bg2-34\": \"언덕 3\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"없음\"\n            }\n        },\n        \"npc\": {\n            \"fam-sign4\": \"표시\",\n            \"fam-plat3\": \"플랫폼\",\n            \"fam-blocks2\": \"블록\",\n            \"fam-enemies5\": \"적\",\n            \"fam-shell1\": \"조개\",\n            \"fam-boss5\": \"보스\",\n            \"fam-veg2\": \"채소\",\n            \"fam-boss1\": \"보스\",\n            \"fam-boss4\": \"보스\",\n            \"fam-char3\": \"캐릭터\",\n            \"fam-warp2\": \"워프\",\n            \"fam-shell3\": \"조개\",\n            \"fam-plant3\": \"식물\",\n            \"fam-vine1\": \"덩굴\",\n            \"fam-items1\": \"아이템\",\n            \"fam-exit3\": \"종료\",\n            \"fam-boss2\": \"보스\",\n            \"fam-enemies3\": \"적\",\n            \"fam-exit4\": \"종료\",\n            \"fam-boss3\": \"보스\",\n            \"fam-vine3\": \"덩굴\",\n            \"fam-plat4\": \"플랫폼\",\n            \"fam-plat1\": \"플랫폼\",\n            \"fam-char5\": \"캐릭터\",\n            \"fam-pet4\": \"애완 동물\",\n            \"fam-items5\": \"아이템\",\n            \"fam-items3\": \"아이템\",\n            \"fam-blocks3\": \"블록\",\n            \"fam-enemies1\": \"적\",\n            \"fam-enemies2\": \"적\",\n            \"fam-ckpt4\": \"체크포인트\",\n            \"fam-vine4\": \"덩굴\",\n            \"fam-switch5\": \"스위치\",\n            \"fam-vine2\": \"덩굴\",\n            \"fam-exit2\": \"종료\",\n            \"fam-items2\": \"아이템\",\n            \"fam-items4\": \"아이템\",\n            \"fam-enemies4\": \"적\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"플레이어 사망 2\",\n                \"sound24\": \"스프링\",\n                \"sound73\": \"가볍게 붙잡다\",\n                \"sound12\": \"아이템 획득\",\n                \"sound34\": \"너구리\",\n                \"sound75\": \"채소 던지기\",\n                \"sound72\": \"수영\",\n                \"header\": \"플레이어\",\n                \"sound25\": \"망치 던지기\",\n                \"sound18\": \"파이어볼\",\n                \"sound35\": \"부츠 분실\",\n                \"sound46\": \"문\",\n                \"sound15\": \"생명력+1\",\n                \"sound76\": \"낙담\",\n                \"sound5\": \"수축\",\n                \"sound71\": \"등산\",\n                \"sound33\": \"꼬리\",\n                \"sound1\": \"점프\",\n                \"sound23\": \"붙잡다\",\n                \"sound17\": \"워프\",\n                \"sound10\": \"미끄러짐\",\n                \"sound6\": \"성장\",\n                \"sound2\": \"쿵쿵\"\n            },\n            \"auto\": {\n                \"sound0\": \"없음\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"플레이어 사망\",\n                \"sound20\": \"보스 격파\",\n                \"sound60\": \"막대 종료\",\n                \"sound45\": \"게임 우승\",\n                \"header\": \"징글\",\n                \"sound52\": \"별 종료 획득\",\n                \"sound40\": \"공 종료\",\n                \"sound31\": \"열쇠 종료\",\n                \"sound19\": \"룰렛 종료\",\n                \"sound21\": \"지하 감옥 클리어\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"깨진 유리\",\n                \"sound41\": \"새 퍼덕임\",\n                \"sound69\": \"우주 보스 울기\",\n                \"sound39\": \"새 공격\",\n                \"sound38\": \"새 침\",\n                \"sound70\": \"우주 보스 처치\",\n                \"sound44\": \"거북이 처치\",\n                \"header\": \"보스\",\n                \"sound63\": \"개구리 처치\",\n                \"sound68\": \"우주 보스 히트\",\n                \"sound62\": \"개구리 거품\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"영웅 아이템\",\n                \"sound84\": \"열쇠 획득\",\n                \"sound88\": \"잔디깎기\",\n                \"sound86\": \"헤르메스 부츠\",\n                \"sound87\": \"비행\",\n                \"header\": \"영웅\",\n                \"sound82\": \"영웅의 불\",\n                \"sound80\": \"영웅 사망\",\n                \"sound77\": \"영웅 찌르기\",\n                \"sound81\": \"보석 획득\",\n                \"sound85\": \"방패 블록\",\n                \"sound53\": \"영웅 NPC 처치\",\n                \"sound78\": \"영웅 상처\",\n                \"sound79\": \"하트 획득\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"블록 박살\",\n                \"sound14\": \"동전\",\n                \"sound22\": \"총알\",\n                \"sound16\": \"용암\",\n                \"sound61\": \"용암 생물\",\n                \"sound74\": \"톱\",\n                \"sound9\": \"조개 킥\",\n                \"sound64\": \"우주 블록 격파\",\n                \"sound3\": \"블록 격파\",\n                \"sound56\": \"고리\",\n                \"sound7\": \"파워업\",\n                \"sound59\": \"소장용\",\n                \"header\": \"아이템\",\n                \"sound65\": \"우주 NPC 처치\",\n                \"sound32\": \"스위치\",\n                \"sound51\": \"계란 해치\",\n                \"sound57\": \"해골\",\n                \"sound66\": \"우주 NPC 상처\",\n                \"sound43\": \"불꽃\",\n                \"sound42\": \"큰 파이어볼\",\n                \"sound58\": \"체크포인트\",\n                \"sound37\": \"파쇄기\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"애완 동물 제비\",\n                \"sound48\": \"애완동물 장착\",\n                \"sound49\": \"애완 동물 상처\",\n                \"sound50\": \"애완 동물 혀\",\n                \"header\": \"애완 동물\",\n                \"sound36\": \"애완동물 쿵쿵\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"아이템 내려놓기\",\n                \"header\": \"사용자 인터페이스\",\n                \"sound13\": \"카메라\",\n                \"sound30\": \"일시정지\",\n                \"sound47\": \"메세지\",\n                \"sound29\": \"행동\",\n                \"sound26\": \"스크롤\"\n            },\n            \"fam-world\": {\n                \"header\": \"세계\",\n                \"sound28\": \"레벨 선택\",\n                \"sound27\": \"새로운 경로\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"먼지\",\n            \"fam-lilac4\": \"라일락 벽\",\n            \"fam-grass1\": \"오래된 잔디\",\n            \"fam-dirt2\": \"죽은 잔디\",\n            \"fam-marble3\": \"대리석\",\n            \"fam-granite3\": \"화강암\",\n            \"fam-stonewall3\": \"돌벽\",\n            \"fam-clouds4\": \"구름\",\n            \"fam-grass2\": \"행복한 잔디\",\n            \"fam-snow1\": \"오래된 눈\",\n            \"fam-wall2\": \"흙벽\",\n            \"fam-lava3\": \"용암\"\n        },\n        \"exit-codes\": {\n            \"7\": \"수집\",\n            \"5\": \"오브 #41\",\n            \"any\": \"어느\",\n            \"6\": \"워프\",\n            \"3\": \"출구\",\n            \"4\": \"열쇠\",\n            \"8\": \"막대 끝\",\n            \"1\": \"룰렛\",\n            \"2\": \"오브 #16\",\n            \"none\": \"없음\",\n            \"9\": \"\"\n        }\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"별\",\n        \"wordStarAccusativeDualOrCounter\": \"<영어에서는 사용되지 않음, 귀하의 언어에 이중 형식 (복수와 단수 사이의 숫자)이 있는 경우 이중 형식에서 \\\"별\\\"로 번역하세요. 귀하의 언어에 반대 단어가 있는 경우 대신 반대 단어를 사용하세요.>\",\n        \"wordFails\": \"실패\",\n        \"wordStarAccusativePlural\": \"별\"\n    },\n    \"character\": {\n        \"name1\": \"캐릭터1\",\n        \"name2\": \"캐릭터2\",\n        \"name3\": \"캐릭터3\",\n        \"name4\": \"캐릭터4\",\n        \"name5\": \"캐릭터5\"\n    },\n    \"languageName\": \"한국어\"\n}\n"
  },
  {
    "path": "resources/languages/assets_nb-no.json",
    "content": "{\n    \"character\": {\n        \"name2\": \"\",\n        \"name5\": \"\",\n        \"name3\": \"\",\n        \"name1\": \"\",\n        \"name4\": \"\"\n    },\n    \"editor\": {\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"wmusic11\": \"\",\n                \"wmusic10\": \"\",\n                \"wmusic2\": \"\",\n                \"wmusic1\": \"\",\n                \"wmusic9\": \"\",\n                \"header\": \"\",\n                \"wmusic3\": \"\",\n                \"wmusic8\": \"\",\n                \"wmusic6\": \"\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"\",\n                \"wmusic4\": \"\",\n                \"header\": \"\",\n                \"wmusic12\": \"\",\n                \"wmusic16\": \"\",\n                \"wmusic14\": \"\",\n                \"wmusic15\": \"\",\n                \"wmusic7\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"\",\n                \"wmusic5\": \"\"\n            }\n        },\n        \"music\": {\n            \"fam-space\": {\n                \"music11\": \"\",\n                \"music22\": \"\",\n                \"header\": \"\",\n                \"music45\": \"\",\n                \"music44\": \"\",\n                \"music12\": \"\"\n            },\n            \"fam-64\": {\n                \"music50\": \"\",\n                \"header\": \"\",\n                \"music14\": \"\",\n                \"music35\": \"\",\n                \"music26\": \"\",\n                \"music36\": \"\",\n                \"music49\": \"\",\n                \"music27\": \"\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"\",\n                \"music56\": \"\",\n                \"music37\": \"\",\n                \"music38\": \"\",\n                \"music23\": \"\",\n                \"music18\": \"\",\n                \"music20\": \"\",\n                \"header\": \"\",\n                \"music8\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"\",\n                \"music30\": \"\",\n                \"music31\": \"\",\n                \"header\": \"\",\n                \"music34\": \"\",\n                \"music21\": \"\",\n                \"music16\": \"\",\n                \"music32\": \"\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"\",\n                \"music15\": \"\",\n                \"header\": \"\",\n                \"music5\": \"\",\n                \"music25\": \"\"\n            },\n            \"fam-world\": {\n                \"music41\": \"\",\n                \"music10\": \"\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"\",\n                \"music3\": \"\",\n                \"music1\": \"\",\n                \"music6\": \"\",\n                \"music54\": \"\",\n                \"header\": \"\",\n                \"music4\": \"\",\n                \"music47\": \"\"\n            },\n            \"fam-set1\": {\n                \"header\": \"\",\n                \"music9\": \"\",\n                \"music46\": \"\",\n                \"music42\": \"\",\n                \"music7\": \"\"\n            },\n            \"fam-fight\": {\n                \"music53\": \"\",\n                \"music39\": \"\",\n                \"music19\": \"\",\n                \"header\": \"\",\n                \"music40\": \"\",\n                \"music52\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"\",\n                \"music24\": \"\"\n            }\n        },\n        \"block\": {\n            \"fam-steampunk3\": \"\",\n            \"fam-cave4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-pipes3\": \"\",\n            \"fam-pipes1\": \"\",\n            \"fam-pipes5-3\": \"\",\n            \"fam-pipes4\": \"\",\n            \"fam-desert3\": \"\",\n            \"fam-overworld2\": \"\",\n            \"fam-wood2\": \"\",\n            \"fam-underground2\": \"\",\n            \"fam-overworld1\": \"\",\n            \"fam-dungeon3\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-dungeon1\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-bonus3\": \"\",\n            \"fam-grass4\": \"\",\n            \"fam-mansion4\": \"\",\n            \"fam-special2\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-pipes5-2\": \"\",\n            \"fam-bricks5\": \"\",\n            \"fam-special4\": \"\",\n            \"fam-woods4\": \"\",\n            \"fam-ruins5\": \"\",\n            \"fam-grass3\": \"\",\n            \"fam-misc2\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-misc3\": \"\",\n            \"fam-metal5-3\": \"\",\n            \"fam-house3\": \"\",\n            \"fam-special3\": \"\",\n            \"fam-bonus4\": \"\",\n            \"fam-underground1\": \"\",\n            \"fam-cave3\": \"\",\n            \"fam-wood3\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-misc1\": \"\",\n            \"fam-castle4\": \"\",\n            \"fam-special1\": \"\",\n            \"fam-sized2\": \"\",\n            \"fam-castle2\": \"\"\n        },\n        \"bgo\": {\n            \"fam-ghost4\": \"\",\n            \"fam-key4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sandbg3\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-water4\": \"\",\n            \"fam-water1\": \"\",\n            \"fam-tiles3\": \"\",\n            \"fam-temple5\": \"\",\n            \"fam-art2\": \"\",\n            \"fam-structures3\": \"\",\n            \"fam-plants2\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-water2\": \"\",\n            \"fam-plants4\": \"\",\n            \"fam-bars4\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-plants1\": \"\",\n            \"fam-doors4\": \"\",\n            \"fam-plants3\": \"\",\n            \"fam-fences1\": \"\",\n            \"fam-hut1\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\",\n            \"fam-fences4\": \"\",\n            \"fam-doors2\": \"\"\n        },\n        \"background2\": {\n            \"fam-set1\": {\n                \"bg2-10\": \"\",\n                \"bg2-7\": \"\",\n                \"bg2-9\": \"\",\n                \"bg2-8\": \"\",\n                \"bg2-50\": \"\",\n                \"header\": \"\",\n                \"bg2-41\": \"\",\n                \"bg2-51\": \"\"\n            },\n            \"fam-set2\": {\n                \"bg2-57\": \"\",\n                \"bg2-48\": \"\",\n                \"bg2-53\": \"\",\n                \"bg2-49\": \"\",\n                \"bg2-25\": \"\",\n                \"bg2-54\": \"\",\n                \"bg2-52\": \"\",\n                \"bg2-5\": \"\",\n                \"bg2-44\": \"\",\n                \"header\": \"\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"\",\n                \"bg2-21\": \"\",\n                \"bg2-4\": \"\",\n                \"bg2-15\": \"\",\n                \"bg2-36\": \"\",\n                \"header\": \"\",\n                \"bg2-6\": \"\",\n                \"bg2-3\": \"\",\n                \"bg2-39\": \"\",\n                \"bg2-20\": \"\",\n                \"bg2-38\": \"\",\n                \"bg2-26\": \"\",\n                \"bg2-1\": \"\",\n                \"bg2-24\": \"\",\n                \"bg2-23\": \"\",\n                \"bg2-27\": \"\",\n                \"bg2-22\": \"\",\n                \"bg2-37\": \"\",\n                \"bg2-35\": \"\",\n                \"bg2-2\": \"\",\n                \"bg2-14\": \"\",\n                \"bg2-17\": \"\",\n                \"bg2-56\": \"\"\n            },\n            \"fam-misc\": {\n                \"header\": \"\",\n                \"bg2-16\": \"\",\n                \"bg2-46\": \"\",\n                \"bg2-47\": \"\",\n                \"bg2-40\": \"\",\n                \"bg2-45\": \"\"\n            },\n            \"fam-set4\": {\n                \"bg2-28\": \"\",\n                \"bg2-32\": \"\",\n                \"header\": \"\",\n                \"bg2-33\": \"\",\n                \"bg2-30\": \"\",\n                \"bg2-55\": \"\",\n                \"bg2-58\": \"\",\n                \"bg2-11\": \"\",\n                \"bg2-12\": \"\",\n                \"bg2-19\": \"\",\n                \"bg2-18\": \"\",\n                \"bg2-42\": \"\",\n                \"bg2-29\": \"\",\n                \"bg2-43\": \"\",\n                \"bg2-31\": \"\",\n                \"bg2-34\": \"\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"\"\n            }\n        },\n        \"npc\": {\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-char3\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound75\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound76\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"\",\n                \"header\": \"\",\n                \"sound13\": \"\",\n                \"sound30\": \"\",\n                \"sound47\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-world\": {\n                \"header\": \"\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-dirt2\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-stonewall3\": \"\",\n            \"fam-clouds4\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\",\n            \"fam-wall2\": \"\",\n            \"fam-lava3\": \"\"\n        },\n        \"exit-codes\": {\n            \"7\": \"\",\n            \"5\": \"\",\n            \"any\": \"\",\n            \"6\": \"\",\n            \"3\": \"\",\n            \"4\": \"\",\n            \"8\": \"\",\n            \"1\": \"\",\n            \"2\": \"\",\n            \"none\": \"\",\n            \"9\": \"\"\n        }\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"\",\n        \"wordFails\": \"\",\n        \"wordStarAccusativePlural\": \"\",\n        \"wordStarAccusativeDualOrCounter\": \"\"\n    },\n    \"languageName\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/assets_pl.json",
    "content": "{\n    \"character\": {\n        \"name2\": \"\",\n        \"name5\": \"\",\n        \"name3\": \"\",\n        \"name1\": \"\",\n        \"name4\": \"\"\n    },\n    \"editor\": {\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"wmusic11\": \"\",\n                \"wmusic10\": \"\",\n                \"wmusic2\": \"\",\n                \"wmusic1\": \"\",\n                \"wmusic9\": \"\",\n                \"header\": \"\",\n                \"wmusic3\": \"\",\n                \"wmusic8\": \"\",\n                \"wmusic6\": \"\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"\",\n                \"wmusic4\": \"\",\n                \"header\": \"\",\n                \"wmusic12\": \"\",\n                \"wmusic16\": \"\",\n                \"wmusic14\": \"\",\n                \"wmusic15\": \"\",\n                \"wmusic7\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"\",\n                \"wmusic5\": \"\"\n            }\n        },\n        \"music\": {\n            \"fam-space\": {\n                \"music11\": \"\",\n                \"music22\": \"\",\n                \"header\": \"\",\n                \"music45\": \"\",\n                \"music44\": \"\",\n                \"music12\": \"\"\n            },\n            \"fam-64\": {\n                \"music50\": \"\",\n                \"header\": \"\",\n                \"music14\": \"\",\n                \"music35\": \"\",\n                \"music26\": \"\",\n                \"music36\": \"\",\n                \"music49\": \"\",\n                \"music27\": \"\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"\",\n                \"music56\": \"\",\n                \"music37\": \"\",\n                \"music38\": \"\",\n                \"music23\": \"\",\n                \"music18\": \"\",\n                \"music20\": \"\",\n                \"header\": \"\",\n                \"music8\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"\",\n                \"music30\": \"\",\n                \"music31\": \"\",\n                \"header\": \"\",\n                \"music34\": \"\",\n                \"music21\": \"\",\n                \"music16\": \"\",\n                \"music32\": \"\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"\",\n                \"music15\": \"\",\n                \"header\": \"\",\n                \"music5\": \"\",\n                \"music25\": \"\"\n            },\n            \"fam-world\": {\n                \"music41\": \"\",\n                \"music10\": \"\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"\",\n                \"music3\": \"\",\n                \"music1\": \"\",\n                \"music6\": \"\",\n                \"music54\": \"\",\n                \"header\": \"\",\n                \"music4\": \"\",\n                \"music47\": \"\"\n            },\n            \"fam-set1\": {\n                \"header\": \"\",\n                \"music9\": \"\",\n                \"music46\": \"\",\n                \"music42\": \"\",\n                \"music7\": \"\"\n            },\n            \"fam-fight\": {\n                \"music53\": \"\",\n                \"music39\": \"\",\n                \"music19\": \"\",\n                \"header\": \"\",\n                \"music40\": \"\",\n                \"music52\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"\",\n                \"music24\": \"\"\n            }\n        },\n        \"block\": {\n            \"fam-steampunk3\": \"\",\n            \"fam-cave4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-pipes3\": \"\",\n            \"fam-pipes1\": \"\",\n            \"fam-pipes5-3\": \"\",\n            \"fam-pipes4\": \"\",\n            \"fam-desert3\": \"\",\n            \"fam-overworld2\": \"\",\n            \"fam-wood2\": \"\",\n            \"fam-underground2\": \"\",\n            \"fam-overworld1\": \"\",\n            \"fam-dungeon3\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-dungeon1\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-bonus3\": \"\",\n            \"fam-grass4\": \"\",\n            \"fam-mansion4\": \"\",\n            \"fam-special2\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-pipes5-2\": \"\",\n            \"fam-bricks5\": \"\",\n            \"fam-special4\": \"\",\n            \"fam-woods4\": \"\",\n            \"fam-ruins5\": \"\",\n            \"fam-grass3\": \"\",\n            \"fam-misc2\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-misc3\": \"\",\n            \"fam-metal5-3\": \"\",\n            \"fam-house3\": \"\",\n            \"fam-special3\": \"\",\n            \"fam-bonus4\": \"\",\n            \"fam-underground1\": \"\",\n            \"fam-cave3\": \"\",\n            \"fam-wood3\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-misc1\": \"\",\n            \"fam-castle4\": \"\",\n            \"fam-special1\": \"\",\n            \"fam-sized2\": \"\",\n            \"fam-castle2\": \"\"\n        },\n        \"bgo\": {\n            \"fam-ghost4\": \"\",\n            \"fam-key4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sandbg3\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-water4\": \"\",\n            \"fam-water1\": \"\",\n            \"fam-tiles3\": \"\",\n            \"fam-temple5\": \"\",\n            \"fam-art2\": \"\",\n            \"fam-structures3\": \"\",\n            \"fam-plants2\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-water2\": \"\",\n            \"fam-plants4\": \"\",\n            \"fam-bars4\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-plants1\": \"\",\n            \"fam-doors4\": \"\",\n            \"fam-plants3\": \"\",\n            \"fam-fences1\": \"\",\n            \"fam-hut1\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\",\n            \"fam-fences4\": \"\",\n            \"fam-doors2\": \"\"\n        },\n        \"background2\": {\n            \"fam-set1\": {\n                \"bg2-10\": \"\",\n                \"bg2-7\": \"\",\n                \"bg2-9\": \"\",\n                \"bg2-8\": \"\",\n                \"bg2-50\": \"\",\n                \"header\": \"\",\n                \"bg2-41\": \"\",\n                \"bg2-51\": \"\"\n            },\n            \"fam-set2\": {\n                \"bg2-57\": \"\",\n                \"bg2-48\": \"\",\n                \"bg2-53\": \"\",\n                \"bg2-49\": \"\",\n                \"bg2-25\": \"\",\n                \"bg2-54\": \"\",\n                \"bg2-52\": \"\",\n                \"bg2-5\": \"\",\n                \"bg2-44\": \"\",\n                \"header\": \"\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"\",\n                \"bg2-21\": \"\",\n                \"bg2-4\": \"\",\n                \"bg2-15\": \"\",\n                \"bg2-36\": \"\",\n                \"header\": \"\",\n                \"bg2-6\": \"\",\n                \"bg2-3\": \"\",\n                \"bg2-39\": \"\",\n                \"bg2-20\": \"\",\n                \"bg2-38\": \"\",\n                \"bg2-26\": \"\",\n                \"bg2-1\": \"\",\n                \"bg2-24\": \"\",\n                \"bg2-23\": \"\",\n                \"bg2-27\": \"\",\n                \"bg2-22\": \"\",\n                \"bg2-37\": \"\",\n                \"bg2-35\": \"\",\n                \"bg2-2\": \"\",\n                \"bg2-14\": \"\",\n                \"bg2-17\": \"\",\n                \"bg2-56\": \"\"\n            },\n            \"fam-misc\": {\n                \"header\": \"\",\n                \"bg2-16\": \"\",\n                \"bg2-46\": \"\",\n                \"bg2-47\": \"\",\n                \"bg2-40\": \"\",\n                \"bg2-45\": \"\"\n            },\n            \"fam-set4\": {\n                \"bg2-28\": \"\",\n                \"bg2-32\": \"\",\n                \"header\": \"\",\n                \"bg2-33\": \"\",\n                \"bg2-30\": \"\",\n                \"bg2-55\": \"\",\n                \"bg2-58\": \"\",\n                \"bg2-11\": \"\",\n                \"bg2-12\": \"\",\n                \"bg2-19\": \"\",\n                \"bg2-18\": \"\",\n                \"bg2-42\": \"\",\n                \"bg2-29\": \"\",\n                \"bg2-43\": \"\",\n                \"bg2-31\": \"\",\n                \"bg2-34\": \"\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"\"\n            }\n        },\n        \"npc\": {\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-char3\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound75\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound76\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"\",\n                \"header\": \"\",\n                \"sound13\": \"\",\n                \"sound30\": \"\",\n                \"sound47\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-world\": {\n                \"header\": \"\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-dirt2\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-stonewall3\": \"\",\n            \"fam-clouds4\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\",\n            \"fam-wall2\": \"\",\n            \"fam-lava3\": \"\"\n        },\n        \"exit-codes\": {\n            \"7\": \"\",\n            \"5\": \"\",\n            \"any\": \"\",\n            \"6\": \"\",\n            \"3\": \"\",\n            \"4\": \"\",\n            \"8\": \"\",\n            \"1\": \"\",\n            \"2\": \"\",\n            \"none\": \"\",\n            \"9\": \"\"\n        }\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"\",\n        \"wordStarAccusativeDualOrCounter\": \"\",\n        \"wordFails\": \"\",\n        \"wordStarAccusativePlural\": \"\"\n    },\n    \"languageName\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/assets_pt-br.json",
    "content": "{\n    \"editor\": {\n        \"sound\": {\n            \"fam-boss\": {\n                \"sound67\": \"Quebra Vidro\",\n                \"sound70\": \"Morte do Chefe Espacial\",\n                \"sound38\": \"Cuspe de Pássaro\",\n                \"sound39\": \"Batida do Pássaro\",\n                \"sound41\": \"Pássaro Derrotado\",\n                \"sound44\": \"Tartaruga Morta\",\n                \"header\": \"Chefe\",\n                \"sound62\": \"Bolhas de Sapo\",\n                \"sound68\": \"Bateu Chefe Espacial\",\n                \"sound63\": \"Sapo Morto\",\n                \"sound69\": \"Grito do Chefe Espacial\"\n            },\n            \"fam-hero\": {\n                \"sound53\": \"Morte do NPC de Herói\",\n                \"sound79\": \"Pegar Coração\",\n                \"sound81\": \"Pegar Gema\",\n                \"sound82\": \"Fogo do Herói\",\n                \"sound83\": \"Item do Herói\",\n                \"sound84\": \"Pegar Chave\",\n                \"sound86\": \"Botas de Hermes\",\n                \"sound87\": \"Voar\",\n                \"header\": \"Herói\",\n                \"sound77\": \"Estocada do Herói\",\n                \"sound78\": \"Herói Machucado\",\n                \"sound80\": \"Morte do Herói\",\n                \"sound85\": \"Defesa com Escudo\",\n                \"sound88\": \"Cortar Grama\"\n            },\n            \"fam-item\": {\n                \"header\": \"Item\",\n                \"sound16\": \"Lava\",\n                \"sound22\": \"Bala\",\n                \"sound32\": \"Altern\",\n                \"sound37\": \"Quebrador\",\n                \"sound42\": \"Bola de Fogo Grande\",\n                \"sound43\": \"Fogos de Artificio\",\n                \"sound58\": \"Ponto de Ressurgimento\",\n                \"sound14\": \"Moeda\",\n                \"sound3\": \"Bater Bloco\",\n                \"sound4\": \"Bloco Esmagado\",\n                \"sound51\": \"Ovo Chocado\",\n                \"sound56\": \"Anel\",\n                \"sound57\": \"Esqueleto\",\n                \"sound59\": \"Coletável\",\n                \"sound66\": \"Machucar NPC Espacial\",\n                \"sound7\": \"Aumento de Poder\",\n                \"sound74\": \"Serra\",\n                \"sound9\": \"Chutar Casco\",\n                \"sound61\": \"Criatura de Lava\",\n                \"sound64\": \"Bateu Bloco Espacial\",\n                \"sound65\": \"NPC Espacial Morto\"\n            },\n            \"fam-jingle\": {\n                \"sound21\": \"Masmorra Completa\",\n                \"sound31\": \"Saída da Chave\",\n                \"header\": \"Toque\",\n                \"sound19\": \"Saída da Roleta\",\n                \"sound20\": \"Derrota do Chefe\",\n                \"sound40\": \"Saída da Bola\",\n                \"sound45\": \"Jogo Superado\",\n                \"sound52\": \"Pegou uma Estrela de Saída\",\n                \"sound60\": \"Barra de Saída\",\n                \"sound8\": \"Morte de Jogador\"\n            },\n            \"fam-pet\": {\n                \"sound36\": \"Pisoteada do Mascote\",\n                \"sound48\": \"Montar no Mascote\",\n                \"sound55\": \"Mascote Engolindo\",\n                \"header\": \"Mascote\",\n                \"sound50\": \"Língua do Mascote\",\n                \"sound49\": \"Mascote Machucado\"\n            },\n            \"fam-player\": {\n                \"sound1\": \"Pulo\",\n                \"sound10\": \"Desliza\",\n                \"sound12\": \"Pegou Item\",\n                \"sound18\": \"Bola de Fogo\",\n                \"sound25\": \"Jogada de Martelo\",\n                \"sound34\": \"Guaxinim\",\n                \"sound75\": \"Jogar Vegetal\",\n                \"sound17\": \"Teleporte\",\n                \"header\": \"Jogador\",\n                \"sound15\": \"1 Vida\",\n                \"sound2\": \"Pisoteada\",\n                \"sound24\": \"Mola\",\n                \"sound23\": \"Agarrar\",\n                \"sound33\": \"Cauda\",\n                \"sound35\": \"Perdeu Bota\",\n                \"sound5\": \"Encolheu\",\n                \"sound73\": \"Levemente Agarrado\",\n                \"sound76\": \"Perdeu Coração\",\n                \"sound6\": \"Cresceu\",\n                \"sound71\": \"Subindo\",\n                \"sound72\": \"Nadando\",\n                \"sound46\": \"Porta\",\n                \"sound54\": \"Morte de Jogador (2)\"\n            },\n            \"fam-ui\": {\n                \"header\": \"Interface\",\n                \"sound47\": \"Mensagem\",\n                \"sound11\": \"Queda de Item\",\n                \"sound13\": \"Câmera\",\n                \"sound26\": \"Escorregar\",\n                \"sound30\": \"Pausa\",\n                \"sound29\": \"Fazer\"\n            },\n            \"fam-world\": {\n                \"sound27\": \"Novo Caminho\",\n                \"sound28\": \"Seleção de Nível\",\n                \"header\": \"Mundo\"\n            },\n            \"auto\": {\n                \"sound0\": \"Nenhum\"\n            }\n        },\n        \"tile\": {\n            \"fam-dirt2\": \"Grama Morta\",\n            \"fam-granite3\": \"Granito\",\n            \"fam-lava3\": \"Lava\",\n            \"fam-lilac4\": \"Parede Lilás\",\n            \"fam-grass1\": \"Grama Antiga\",\n            \"fam-snow1\": \"Neve Antiga\",\n            \"fam-clouds4\": \"Nuvens\",\n            \"fam-desert1\": \"Areia\",\n            \"fam-grass2\": \"Grama Viva\",\n            \"fam-marble3\": \"Mármore\",\n            \"fam-stonewall3\": \"Parede de Pedra\",\n            \"fam-wall2\": \"Parede de Terra\"\n        },\n        \"worldMusic\": {\n            \"fam-world\": {\n                \"wmusic7\": \"Floresta\",\n                \"wmusic12\": \"Especial\",\n                \"header\": \"Mundo\",\n                \"wmusic14\": \"Céu\",\n                \"wmusic13\": \"Masmorra\",\n                \"wmusic15\": \"Ilha\",\n                \"wmusic16\": \"Caverna\",\n                \"wmusic4\": \"Tema\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"Nenhum\"\n            },\n            \"fam-new\": {\n                \"header\": \"Novo\",\n                \"wmusic5\": \"Tema\"\n            },\n            \"fam-set3\": {\n                \"wmusic11\": \"Mundo 5\",\n                \"header\": \"Conjunto 3\",\n                \"wmusic2\": \"Mundo 4\",\n                \"wmusic1\": \"Mundo 1\",\n                \"wmusic10\": \"Mundo 6\",\n                \"wmusic3\": \"Mundo 7\",\n                \"wmusic6\": \"Mundo 2\",\n                \"wmusic8\": \"Mundo 3\",\n                \"wmusic9\": \"Mundo 8\"\n            }\n        },\n        \"background2\": {\n            \"fam-misc\": {\n                \"bg2-16\": \"Cratera Espacial\",\n                \"bg2-40\": \"Mina Secreta\",\n                \"bg2-45\": \"Pântano Espacial\",\n                \"bg2-46\": \"Nave Espacial\",\n                \"header\": \"Outro\",\n                \"bg2-47\": \"Base Espacial\"\n            },\n            \"fam-set1\": {\n                \"bg2-41\": \"Castelo\",\n                \"bg2-50\": \"Cogumelos\",\n                \"bg2-51\": \"Deserto\",\n                \"bg2-7\": \"Subterrâneo\",\n                \"bg2-8\": \"Noite\",\n                \"bg2-9\": \"Noite (2)\",\n                \"bg2-10\": \"Mundo sup\",\n                \"header\": \"Conjunto 1\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"Subterrâneo\",\n                \"bg2-44\": \"Castelo\",\n                \"bg2-48\": \"Nuvens\",\n                \"bg2-49\": \"Noite - Colinas\",\n                \"bg2-5\": \"Árvores\",\n                \"bg2-53\": \"Penhasco\",\n                \"bg2-54\": \"Armazém\",\n                \"bg2-57\": \"Masmorra\",\n                \"bg2-52\": \"Noite - Deserto\",\n                \"header\": \"Conjunto 2\"\n            },\n            \"fam-set3\": {\n                \"bg2-1\": \"Blocos\",\n                \"bg2-15\": \"Masmorra (2)\",\n                \"bg2-20\": \"Floresta\",\n                \"bg2-22\": \"Cachoeira\",\n                \"bg2-23\": \"Tanques\",\n                \"bg2-27\": \"Castelo\",\n                \"bg2-3\": \"Masmorra\",\n                \"bg2-36\": \"Nuvens (2)\",\n                \"bg2-38\": \"Caverna\",\n                \"bg2-39\": \"Caverna (2)\",\n                \"bg2-56\": \"Subaquático\",\n                \"bg2-6\": \"Bônus\",\n                \"bg2-4\": \"Canos\",\n                \"header\": \"Conjunto 3\",\n                \"bg2-13\": \"Nuvens\",\n                \"bg2-14\": \"Deserto\",\n                \"bg2-17\": \"Navio\",\n                \"bg2-2\": \"Colinas\",\n                \"bg2-21\": \"Batalha\",\n                \"bg2-24\": \"Chefe Final\",\n                \"bg2-26\": \"Vendedor de Cogumelos\",\n                \"bg2-35\": \"Árvores na Neve\",\n                \"bg2-37\": \"Colinas com Neve\"\n            },\n            \"fam-set4\": {\n                \"bg2-18\": \"Mansão\",\n                \"bg2-31\": \"Nuvens\",\n                \"bg2-34\": \"Colinas (3)\",\n                \"bg2-12\": \"Árvores\",\n                \"bg2-11\": \"Colinas\",\n                \"bg2-19\": \"Floresta\",\n                \"bg2-28\": \"Bônus\",\n                \"bg2-29\": \"Noite\",\n                \"bg2-30\": \"Caverna\",\n                \"bg2-32\": \"Colinas (2)\",\n                \"bg2-33\": \"Colinas (4)\",\n                \"bg2-42\": \"Castelo\",\n                \"bg2-43\": \"Castelo (2)\",\n                \"bg2-55\": \"Subaquático\",\n                \"bg2-58\": \"Deserto a Noite\",\n                \"header\": \"Conjunto 4\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"Nenhum\"\n            }\n        },\n        \"bgo\": {\n            \"fam-art2\": \"Arte\",\n            \"fam-clouds1\": \"Nuvens\",\n            \"fam-fences1\": \"Cercas\",\n            \"fam-ghost4\": \"Fantasma\",\n            \"fam-hut1\": \"Cabana\",\n            \"fam-key4\": \"Chave\",\n            \"fam-misc4\": \"Outros\",\n            \"fam-plants1\": \"Plantas\",\n            \"fam-plants2\": \"Plantas\",\n            \"fam-sandbg3\": \"Areia\",\n            \"fam-temple5\": \"Templo\",\n            \"fam-water1\": \"Água\",\n            \"fam-water3\": \"Água\",\n            \"fam-water4\": \"Água\",\n            \"fam-bars4\": \"Barras\",\n            \"fam-doors2\": \"Portas\",\n            \"fam-doors4\": \"Portas\",\n            \"fam-exit3\": \"Saída\",\n            \"fam-fences4\": \"Cercas\",\n            \"fam-space5\": \"Inseto Espacial\",\n            \"fam-structures3\": \"Estruturas\",\n            \"fam-tiles3\": \"Peças\",\n            \"fam-water2\": \"Água\",\n            \"fam-platform4\": \"Plat\",\n            \"fam-plants3\": \"Plantas\",\n            \"fam-plants4\": \"Plantas\",\n            \"fam-sandfg3\": \"Primeiro Plano\"\n        },\n        \"block\": {\n            \"fam-bonus4\": \"Bônus\",\n            \"fam-bricks5\": \"Tijolo\",\n            \"fam-grass3\": \"Grama\",\n            \"fam-grass4\": \"Grama\",\n            \"fam-house3\": \"Casa\",\n            \"fam-mansion4\": \"Mansão\",\n            \"fam-metal5-3\": \"Outros\",\n            \"fam-misc1\": \"Outros\",\n            \"fam-misc2\": \"Outros\",\n            \"fam-misc3\": \"Outros (3)\",\n            \"fam-pipes1\": \"Canos\",\n            \"fam-pipes3\": \"Canos\",\n            \"fam-pipes4\": \"Canos\",\n            \"fam-pipes5-2\": \"Canos\",\n            \"fam-sized1\": \"Redimen\",\n            \"fam-sized2\": \"Redimen\",\n            \"fam-sized4\": \"Redimen\",\n            \"fam-special2\": \"Especial\",\n            \"fam-special4\": \"Especial\",\n            \"fam-steampunk3\": \"Tecnavapor\",\n            \"fam-switch4\": \"Altern\",\n            \"fam-underground1\": \"Subterrâneo\",\n            \"fam-water3\": \"Água\",\n            \"fam-wood2\": \"Madeira\",\n            \"fam-wood3\": \"Madeira\",\n            \"fam-bonus3\": \"Bônus\",\n            \"fam-castle2\": \"Castelo\",\n            \"fam-castle4\": \"Castelo\",\n            \"fam-cave3\": \"Caverna\",\n            \"fam-dungeon1\": \"Masmorra\",\n            \"fam-char5\": \"Personagem\",\n            \"fam-clouds1\": \"Nuvens\",\n            \"fam-desert3\": \"Deserto\",\n            \"fam-dungeon3\": \"Masmorra\",\n            \"fam-hurts3\": \"Danoso\",\n            \"fam-lava4\": \"Lava e Danosos\",\n            \"fam-special3\": \"Especial\",\n            \"fam-metal5-1\": \"Placas\",\n            \"fam-sized3\": \"Redimen(3)\",\n            \"fam-special1\": \"Especial\",\n            \"fam-misc4\": \"Outros\",\n            \"fam-overworld1\": \"Mundo sup\",\n            \"fam-overworld2\": \"Mundo sup\",\n            \"fam-underground2\": \"Subterrâneo\",\n            \"fam-ruins5\": \"Ruinas\",\n            \"fam-woods4\": \"Madeiras\",\n            \"fam-cave4\": \"Caverna\",\n            \"fam-pipes5-3\": \"Canos Longos\"\n        },\n        \"exit-codes\": {\n            \"1\": \"Roleta\",\n            \"2\": \"Esfera #16\",\n            \"4\": \"Chave\",\n            \"5\": \"Esfera #41\",\n            \"6\": \"Teleporte\",\n            \"3\": \"Deixar\",\n            \"7\": \"Coletar\",\n            \"8\": \"Barra de Fim\",\n            \"none\": \"Nenhum\",\n            \"any\": \"Qualquer\",\n            \"9\": \"Código9\"\n        },\n        \"music\": {\n            \"fam-64\": {\n                \"header\": \"SM64\",\n                \"music26\": \"Castelo\",\n                \"music36\": \"Chefe\",\n                \"music49\": \"Água\",\n                \"music50\": \"Caverna\",\n                \"music14\": \"Deserto\",\n                \"music27\": \"Tema Principal\",\n                \"music35\": \"Neve\"\n            },\n            \"fam-fight\": {\n                \"music19\": \"Tecnavapor\",\n                \"music40\": \"Cavaleiro\",\n                \"music52\": \"Subterrâneo\",\n                \"music53\": \"Pinball\",\n                \"header\": \"Luta\",\n                \"music39\": \"Templo\"\n            },\n            \"fam-misc\": {\n                \"header\": \"Outro\",\n                \"music13\": \"NSMB Mun.Sup\",\n                \"music18\": \"Praia\",\n                \"music20\": \"Reator de Fusão\",\n                \"music23\": \"Florestas Heróicas\",\n                \"music37\": \"Montanhas do Gelo\",\n                \"music38\": \"Aldeia da Selva\",\n                \"music55\": \"Tema do Título\",\n                \"music56\": \"Corrida Saltitante\",\n                \"music8\": \"Encurralado!\"\n            },\n            \"fam-rpg\": {\n                \"header\": \"RPG\",\n                \"music16\": \"Floresta\",\n                \"music21\": \"Batalha\",\n                \"music31\": \"Beira-mar\",\n                \"music32\": \"Lago\",\n                \"music33\": \"Nuvens\",\n                \"music34\": \"Cidade\",\n                \"music30\": \"Bacharelado\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"Chefe Final\",\n                \"music15\": \"Chefe\",\n                \"header\": \"Conjunto 2\",\n                \"music5\": \"Mundo sup\",\n                \"music25\": \"Subterrâneo\"\n            },\n            \"fam-set3\": {\n                \"music1\": \"Mundo sup\",\n                \"music2\": \"Céu\",\n                \"music4\": \"Subterrâneo\",\n                \"music54\": \"Inimigo Errante\",\n                \"music6\": \"Chefe\",\n                \"music3\": \"Masmorra\",\n                \"music47\": \"Água\",\n                \"header\": \"Conjunto 3\"\n            },\n            \"fam-space\": {\n                \"header\": \"Espaço\",\n                \"music11\": \"Pântano Vermelho\",\n                \"music12\": \"Cratera\",\n                \"music22\": \"Carga Espacial\",\n                \"music44\": \"Sala de Itens\",\n                \"music45\": \"Chefe Final\"\n            },\n            \"fam-world\": {\n                \"header\": \"Mundo\",\n                \"music10\": \"Mundo sup\",\n                \"music17\": \"Mansão\",\n                \"music28\": \"Céu\",\n                \"music29\": \"Caverna\",\n                \"music41\": \"Masmorra\",\n                \"music51\": \"Chefe\",\n                \"music48\": \"Água\"\n            },\n            \"auto\": {\n                \"music24\": \"Personalizado\",\n                \"music0\": \"Nenhum\"\n            },\n            \"fam-set1\": {\n                \"header\": \"Conjunto 1\",\n                \"music7\": \"Subterrâneo\",\n                \"music42\": \"Masmorra\",\n                \"music46\": \"Água\",\n                \"music9\": \"Mundo sup\"\n            }\n        },\n        \"npc\": {\n            \"fam-boss1\": \"Chefe\",\n            \"fam-boss3\": \"Chefe\",\n            \"fam-char3\": \"Personagem\",\n            \"fam-char5\": \"Personagem\",\n            \"fam-enemies4\": \"Inimigos\",\n            \"fam-exit2\": \"Saída\",\n            \"fam-exit3\": \"Saída\",\n            \"fam-exit4\": \"Saída\",\n            \"fam-items1\": \"Itens\",\n            \"fam-items4\": \"Itens\",\n            \"fam-pet4\": \"Mascote\",\n            \"fam-plant3\": \"Planta\",\n            \"fam-plat1\": \"Plat\",\n            \"fam-shell1\": \"Casco\",\n            \"fam-shell3\": \"Casco\",\n            \"fam-sign4\": \"Sinais\",\n            \"fam-veg2\": \"Vegetal\",\n            \"fam-vine1\": \"Vinha\",\n            \"fam-blocks2\": \"Blocos\",\n            \"fam-blocks3\": \"Blocos\",\n            \"fam-boss2\": \"Chefe\",\n            \"fam-boss4\": \"Chefe\",\n            \"fam-switch5\": \"Altern\",\n            \"fam-boss5\": \"Chefe\",\n            \"fam-ckpt4\": \"Ress\",\n            \"fam-enemies1\": \"Inimigos\",\n            \"fam-enemies2\": \"Inimigos\",\n            \"fam-enemies3\": \"Inimigos\",\n            \"fam-enemies5\": \"Inimigos\",\n            \"fam-items5\": \"Itens\",\n            \"fam-items2\": \"Itens\",\n            \"fam-items3\": \"Itens\",\n            \"fam-plat3\": \"Plat\",\n            \"fam-plat4\": \"Plat\",\n            \"fam-vine2\": \"Vinha\",\n            \"fam-vine3\": \"Vinha\",\n            \"fam-vine4\": \"Vinha\",\n            \"fam-warp2\": \"Telep\"\n        }\n    },\n    \"character\": {\n        \"name3\": \"Personagem 3\",\n        \"name4\": \"Personagem 4\",\n        \"name5\": \"Personagem 5\",\n        \"name1\": \"Personagem 1\",\n        \"name2\": \"Personagem 2\"\n    },\n    \"languageName\": \"Português-BR\",\n    \"objects\": {\n        \"wordFails\": \"FALHAS\",\n        \"wordStarAccusativePlural\": \"Estrelas\",\n        \"wordStarAccusativeSingular\": \"Estrela\",\n        \"wordStarAccusativeDualOrCounter\": \"<Não usado no Inglês e Português, Seria como \\\"Pares de Estrelas\\\" se a linguagem tiver isso especificado (Numeral entre Singular e Plural); Se sua linguagem tiver contra palavra, use-a aqui>\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/assets_pt.json",
    "content": "{\n    \"character\": {\n        \"name1\": \"Personagem 1\",\n        \"name2\": \"Personagem 2\",\n        \"name3\": \"Personagem 3\",\n        \"name4\": \"Personagem 4\",\n        \"name5\": \"Personagem 5\"\n    },\n    \"editor\": {\n        \"background2\": {\n            \"auto\": {\n                \"bg2-0\": \"Nenhum\"\n            },\n            \"fam-misc\": {\n                \"bg2-16\": \"Cratera Espacial\",\n                \"bg2-45\": \"Pântano Espacial\",\n                \"bg2-40\": \"Mina Secreta\",\n                \"bg2-46\": \"Nave Espacial\",\n                \"bg2-47\": \"Base Espacial\",\n                \"header\": \"Outro\"\n            },\n            \"fam-set1\": {\n                \"bg2-10\": \"Mundo sup\",\n                \"bg2-41\": \"Castelo\",\n                \"bg2-50\": \"Cogumelos\",\n                \"bg2-7\": \"Subterrâneo\",\n                \"bg2-8\": \"Noite\",\n                \"bg2-51\": \"Deserto\",\n                \"bg2-9\": \"Noite (2)\",\n                \"header\": \"Conjunto 1\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"Subterrâneo\",\n                \"bg2-44\": \"Castelo\",\n                \"bg2-48\": \"Nuvens\",\n                \"bg2-49\": \"Noite - Colinas\",\n                \"bg2-5\": \"Árvores\",\n                \"bg2-52\": \"Noite - Deserto\",\n                \"bg2-53\": \"Penhasco\",\n                \"bg2-54\": \"Armazém\",\n                \"bg2-57\": \"Masmorra\",\n                \"header\": \"Conjunto 2\"\n            },\n            \"fam-set3\": {\n                \"bg2-1\": \"Blocos\",\n                \"bg2-13\": \"Nuvens\",\n                \"bg2-14\": \"Deserto\",\n                \"bg2-15\": \"Masmorra (2)\",\n                \"bg2-17\": \"Navio\",\n                \"bg2-2\": \"Colinas\",\n                \"bg2-20\": \"Floresta\",\n                \"bg2-21\": \"Batalha\",\n                \"bg2-22\": \"Cachoeira\",\n                \"bg2-23\": \"Tanques\",\n                \"bg2-24\": \"Chefe Final\",\n                \"bg2-26\": \"Vendedor de Cogumelos\",\n                \"bg2-27\": \"Castelo\",\n                \"bg2-3\": \"Masmorra\",\n                \"bg2-35\": \"Árvores com Neve\",\n                \"bg2-36\": \"Nuvens (2)\",\n                \"bg2-37\": \"Colinas com Neve\",\n                \"bg2-38\": \"Caverna\",\n                \"bg2-39\": \"Caverna (2)\",\n                \"bg2-4\": \"Canos\",\n                \"bg2-56\": \"Subaquático\",\n                \"bg2-6\": \"Bônus\",\n                \"header\": \"Conjunto 3\"\n            },\n            \"fam-set4\": {\n                \"bg2-11\": \"Colinas\",\n                \"bg2-12\": \"Árvores\",\n                \"bg2-18\": \"Mansão\",\n                \"bg2-19\": \"Floresta\",\n                \"bg2-28\": \"Bônus\",\n                \"bg2-29\": \"Noite\",\n                \"bg2-30\": \"Caverna\",\n                \"bg2-31\": \"Nuvens\",\n                \"bg2-32\": \"Colinas (2)\",\n                \"bg2-33\": \"Colinas (4)\",\n                \"bg2-34\": \"Colinas (3)\",\n                \"bg2-42\": \"Castelo\",\n                \"bg2-43\": \"Castelo (2)\",\n                \"bg2-55\": \"Subaquático\",\n                \"bg2-58\": \"Deserto a Noite\",\n                \"header\": \"Conjunto 4\"\n            }\n        },\n        \"bgo\": {\n            \"fam-clouds1\": \"Nuvens\",\n            \"fam-art2\": \"Arte\",\n            \"fam-bars4\": \"Barras\",\n            \"fam-doors2\": \"Portas\",\n            \"fam-doors4\": \"Portas\",\n            \"fam-exit3\": \"Saída\",\n            \"fam-fences1\": \"Cercas\",\n            \"fam-fences4\": \"Cercas\",\n            \"fam-ghost4\": \"Fantasma\",\n            \"fam-hut1\": \"Cabana\",\n            \"fam-key4\": \"Chave\",\n            \"fam-misc4\": \"Outros\",\n            \"fam-plants1\": \"Plantas\",\n            \"fam-plants2\": \"Plantas\",\n            \"fam-plants3\": \"Plantas\",\n            \"fam-plants4\": \"Plantas\",\n            \"fam-platform4\": \"Plat\",\n            \"fam-sandbg3\": \"Areia\",\n            \"fam-sandfg3\": \"Primeiro Plano\",\n            \"fam-space5\": \"Inseto Espacial\",\n            \"fam-structures3\": \"Estruturas\",\n            \"fam-temple5\": \"Templo\",\n            \"fam-tiles3\": \"Peças\",\n            \"fam-water1\": \"Água\",\n            \"fam-water2\": \"Água\",\n            \"fam-water3\": \"Água\",\n            \"fam-water4\": \"Água\"\n        },\n        \"block\": {\n            \"fam-cave4\": \"Caverna\",\n            \"fam-bonus3\": \"Bônus\",\n            \"fam-bonus4\": \"Bônus\",\n            \"fam-bricks5\": \"Tijolo\",\n            \"fam-castle2\": \"Castelo\",\n            \"fam-castle4\": \"Castelo\",\n            \"fam-cave3\": \"Caverna\",\n            \"fam-char5\": \"Personagem\",\n            \"fam-clouds1\": \"Nuvens\",\n            \"fam-desert3\": \"Deserto\",\n            \"fam-dungeon1\": \"Masmorra\",\n            \"fam-house3\": \"Casa\",\n            \"fam-dungeon3\": \"Masmorra\",\n            \"fam-grass3\": \"Grama\",\n            \"fam-grass4\": \"Grama\",\n            \"fam-hurts3\": \"Danoso\",\n            \"fam-lava4\": \"Lava e Danosos\",\n            \"fam-mansion4\": \"Mansão\",\n            \"fam-metal5-1\": \"Placas\",\n            \"fam-metal5-3\": \"Outros\",\n            \"fam-misc1\": \"Outros\",\n            \"fam-misc2\": \"Outros\",\n            \"fam-misc3\": \"Outros (3)\",\n            \"fam-misc4\": \"Outros\",\n            \"fam-overworld1\": \"Mundo sup\",\n            \"fam-overworld2\": \"Mundo sup\",\n            \"fam-pipes1\": \"Canos\",\n            \"fam-pipes3\": \"Canos\",\n            \"fam-pipes4\": \"Canos\",\n            \"fam-pipes5-2\": \"Canos\",\n            \"fam-pipes5-3\": \"Canos Longos\",\n            \"fam-ruins5\": \"Ruinas\",\n            \"fam-sized1\": \"Redimen\",\n            \"fam-sized2\": \"Redimen\",\n            \"fam-sized3\": \"Redimen(3)\",\n            \"fam-sized4\": \"Redimen\",\n            \"fam-special1\": \"Especial\",\n            \"fam-special2\": \"Especial\",\n            \"fam-special3\": \"Especial\",\n            \"fam-special4\": \"Especial\",\n            \"fam-steampunk3\": \"Tecnavapor\",\n            \"fam-switch4\": \"Altern\",\n            \"fam-underground1\": \"Subterrâneo\",\n            \"fam-underground2\": \"Subterrâneo\",\n            \"fam-water3\": \"Água\",\n            \"fam-wood2\": \"Madeira\",\n            \"fam-wood3\": \"Madeira\",\n            \"fam-woods4\": \"Madeiras\"\n        },\n        \"exit-codes\": {\n            \"1\": \"Roleta\",\n            \"2\": \"Esfera #16\",\n            \"3\": \"Deixar\",\n            \"4\": \"Chave\",\n            \"5\": \"Esfera #41\",\n            \"6\": \"Teleporte\",\n            \"7\": \"Coletar\",\n            \"8\": \"Barra de Fim\",\n            \"any\": \"Qualquer\",\n            \"none\": \"Nenhum\",\n            \"9\": \"\"\n        },\n        \"music\": {\n            \"auto\": {\n                \"music0\": \"Nenhum\",\n                \"music24\": \"Personalizado\"\n            },\n            \"fam-64\": {\n                \"header\": \"SM64\",\n                \"music14\": \"Deserto\",\n                \"music26\": \"Castelo\",\n                \"music27\": \"Tema Principal\",\n                \"music35\": \"Neve\",\n                \"music36\": \"Chefe\",\n                \"music49\": \"Água\",\n                \"music50\": \"Caverna\"\n            },\n            \"fam-fight\": {\n                \"header\": \"Luta\",\n                \"music19\": \"Tecnavapor\",\n                \"music39\": \"Templo\",\n                \"music40\": \"Cavaleiro\",\n                \"music52\": \"Subterrâneo\",\n                \"music53\": \"Pinball\"\n            },\n            \"fam-misc\": {\n                \"music13\": \"NSMB Mun.Sup\",\n                \"header\": \"Outro\",\n                \"music18\": \"Praia\",\n                \"music20\": \"Reator de Fusão\",\n                \"music23\": \"Florestas Heróicas\",\n                \"music37\": \"Montanhas do Gelo\",\n                \"music8\": \"Encurralado!\",\n                \"music38\": \"Aldeia da Selva\",\n                \"music55\": \"Tema do Título\",\n                \"music56\": \"Corrida Saltitante\"\n            },\n            \"fam-rpg\": {\n                \"header\": \"RPG\",\n                \"music16\": \"Floresta\",\n                \"music21\": \"Batalha\",\n                \"music30\": \"Bacharelado\",\n                \"music31\": \"Beira-mar\",\n                \"music32\": \"Lago\",\n                \"music33\": \"Nuvens\",\n                \"music34\": \"Cidade\"\n            },\n            \"fam-set1\": {\n                \"header\": \"Conjunto 1\",\n                \"music42\": \"Masmorra\",\n                \"music46\": \"Água\",\n                \"music7\": \"Subterrâneo\",\n                \"music9\": \"Mundo sup\"\n            },\n            \"fam-set2\": {\n                \"header\": \"Conjunto 2\",\n                \"music15\": \"Chefe\",\n                \"music25\": \"Subterrâneo\",\n                \"music43\": \"Chefe Final\",\n                \"music5\": \"Mundo sup\"\n            },\n            \"fam-set3\": {\n                \"header\": \"Conjunto 3\",\n                \"music1\": \"Mundo sup\",\n                \"music2\": \"Céu\",\n                \"music3\": \"Masmorra\",\n                \"music4\": \"Subterrâneo\",\n                \"music47\": \"Água\",\n                \"music54\": \"Inimigo Errante\",\n                \"music6\": \"Chefe\"\n            },\n            \"fam-space\": {\n                \"header\": \"Espaço\",\n                \"music11\": \"Pântano Vermelho\",\n                \"music12\": \"Cratera\",\n                \"music22\": \"Carga Espacial\",\n                \"music44\": \"Sala de Itens\",\n                \"music45\": \"Chefe Final\"\n            },\n            \"fam-world\": {\n                \"header\": \"Mundo\",\n                \"music10\": \"Mundo sup\",\n                \"music17\": \"Mansão\",\n                \"music28\": \"Céu\",\n                \"music29\": \"Caverna\",\n                \"music41\": \"Masmorra\",\n                \"music48\": \"Água\",\n                \"music51\": \"Chefe\"\n            }\n        },\n        \"npc\": {\n            \"fam-blocks2\": \"Blocos\",\n            \"fam-blocks3\": \"Blocos\",\n            \"fam-boss1\": \"Chefe\",\n            \"fam-boss2\": \"Chefe\",\n            \"fam-boss3\": \"Chefe\",\n            \"fam-char5\": \"Personagem\",\n            \"fam-ckpt4\": \"Ress\",\n            \"fam-enemies1\": \"Inimigos\",\n            \"fam-boss4\": \"Chefe\",\n            \"fam-boss5\": \"Chefe\",\n            \"fam-char3\": \"Personagem\",\n            \"fam-enemies2\": \"Inimigos\",\n            \"fam-enemies3\": \"Inimigos\",\n            \"fam-enemies4\": \"Inimigos\",\n            \"fam-enemies5\": \"Inimigos\",\n            \"fam-items2\": \"Itens\",\n            \"fam-items3\": \"Itens\",\n            \"fam-exit2\": \"Saída\",\n            \"fam-exit3\": \"Saída\",\n            \"fam-exit4\": \"Saída\",\n            \"fam-items1\": \"Itens\",\n            \"fam-items4\": \"Itens\",\n            \"fam-items5\": \"Itens\",\n            \"fam-pet4\": \"Mascote\",\n            \"fam-plant3\": \"Planta\",\n            \"fam-plat1\": \"Plat\",\n            \"fam-plat3\": \"Plat\",\n            \"fam-plat4\": \"Plat\",\n            \"fam-shell1\": \"Casco\",\n            \"fam-shell3\": \"Casco\",\n            \"fam-sign4\": \"Sinais\",\n            \"fam-switch5\": \"Altern\",\n            \"fam-veg2\": \"Vegetal\",\n            \"fam-vine1\": \"Vinha\",\n            \"fam-vine2\": \"Vinha\",\n            \"fam-vine3\": \"Vinha\",\n            \"fam-vine4\": \"Vinha\",\n            \"fam-warp2\": \"Telep\"\n        },\n        \"sound\": {\n            \"auto\": {\n                \"sound0\": \"Nenhum\"\n            },\n            \"fam-boss\": {\n                \"sound38\": \"Cuspe de Pássaro\",\n                \"sound39\": \"Choque de Pássaro\",\n                \"header\": \"Chefe\",\n                \"sound41\": \"Pássaro Derrotado\",\n                \"sound44\": \"Tartaruga Morta\",\n                \"sound62\": \"Bolhas de Sapo\",\n                \"sound63\": \"Sapo Morto\",\n                \"sound67\": \"Quebra Vidro\",\n                \"sound68\": \"Bateu Chefe Espacial\",\n                \"sound69\": \"Grito do Chefe Espacial\",\n                \"sound70\": \"Morte do Chefe Espacial\"\n            },\n            \"fam-hero\": {\n                \"header\": \"Herói\",\n                \"sound53\": \"Morte do NPC de Herói\",\n                \"sound77\": \"Estocada do Herói\",\n                \"sound78\": \"Herói Machucado\",\n                \"sound79\": \"Apanhar Coração\",\n                \"sound80\": \"Morte do Herói\",\n                \"sound81\": \"Apanhar Gema\",\n                \"sound82\": \"Fogo do Herói\",\n                \"sound83\": \"Item do Herói\",\n                \"sound84\": \"Apanhar Chave\",\n                \"sound85\": \"Defesa com Escudo\",\n                \"sound86\": \"Botas de Hermes\",\n                \"sound87\": \"Voar\",\n                \"sound88\": \"Cortar Grama\"\n            },\n            \"fam-item\": {\n                \"header\": \"Item\",\n                \"sound14\": \"Moeda\",\n                \"sound32\": \"Altern\",\n                \"sound16\": \"Lava\",\n                \"sound22\": \"Bala\",\n                \"sound3\": \"Bater Bloco\",\n                \"sound37\": \"Quebrador\",\n                \"sound4\": \"Bloco Esmagado\",\n                \"sound42\": \"Bola de Fogo Grande\",\n                \"sound43\": \"Fogos de Artificio\",\n                \"sound56\": \"Anel\",\n                \"sound57\": \"Esqueleto\",\n                \"sound51\": \"Ovo Chocado\",\n                \"sound58\": \"Ponto de Ressurgimento\",\n                \"sound59\": \"Coletável\",\n                \"sound61\": \"Criatura de Lava\",\n                \"sound64\": \"Bateu Bloco Espacial\",\n                \"sound65\": \"NPC Espacial Morto\",\n                \"sound66\": \"Machucar NPC Espacial\",\n                \"sound7\": \"Aumento de Poder\",\n                \"sound74\": \"Serra\",\n                \"sound9\": \"Chutar Casco\"\n            },\n            \"fam-jingle\": {\n                \"header\": \"Toque\",\n                \"sound19\": \"Saída da Roleta\",\n                \"sound20\": \"Derrota do Chefe\",\n                \"sound21\": \"Masmorra Completa\",\n                \"sound31\": \"Saída da Chave\",\n                \"sound40\": \"Saída da Bola\",\n                \"sound45\": \"Jogo Superado\",\n                \"sound52\": \"Pegou uma Estrela de Saída\",\n                \"sound60\": \"Barra de Saída\",\n                \"sound8\": \"Morte de Jogador\"\n            },\n            \"fam-pet\": {\n                \"sound48\": \"Montar no Mascote\",\n                \"sound49\": \"Mascote Machucado\",\n                \"sound50\": \"Língua do Mascote\",\n                \"sound55\": \"Mascote Engolindo\",\n                \"header\": \"Mascote\",\n                \"sound36\": \"Pisoteada do Mascote\"\n            },\n            \"fam-player\": {\n                \"header\": \"Jogador\",\n                \"sound1\": \"Pulo\",\n                \"sound10\": \"Desliza\",\n                \"sound12\": \"Pegou Item\",\n                \"sound17\": \"Teleporte\",\n                \"sound15\": \"1 Vida\",\n                \"sound18\": \"Bola de Fogo\",\n                \"sound2\": \"Pisoteada\",\n                \"sound23\": \"Agarrar\",\n                \"sound24\": \"Mola\",\n                \"sound25\": \"Jogada de Martelo\",\n                \"sound33\": \"Cauda\",\n                \"sound34\": \"Guaxinim\",\n                \"sound35\": \"Perdeu Bota\",\n                \"sound46\": \"Porta\",\n                \"sound5\": \"Encolheu\",\n                \"sound54\": \"Morte de Jogador (2)\",\n                \"sound6\": \"Cresceu\",\n                \"sound71\": \"Subindo\",\n                \"sound72\": \"Nadando\",\n                \"sound73\": \"Levemente Agarrado\",\n                \"sound75\": \"Jogar Vegetal\",\n                \"sound76\": \"Perdeu Coração\"\n            },\n            \"fam-ui\": {\n                \"header\": \"Interface\",\n                \"sound11\": \"Queda de Item\",\n                \"sound13\": \"Câmara\",\n                \"sound26\": \"Escorregar\",\n                \"sound29\": \"Fazer\",\n                \"sound30\": \"Pausa\",\n                \"sound47\": \"Mensagem\"\n            },\n            \"fam-world\": {\n                \"header\": \"Mundo\",\n                \"sound27\": \"Novo Caminho\",\n                \"sound28\": \"Seleção de Nível\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"Areia\",\n            \"fam-dirt2\": \"Grama Morta\",\n            \"fam-granite3\": \"Granito\",\n            \"fam-grass1\": \"Grama Antiga\",\n            \"fam-grass2\": \"Grama Viva\",\n            \"fam-lava3\": \"Lava\",\n            \"fam-lilac4\": \"Parede Lilás\",\n            \"fam-clouds4\": \"Nuvens\",\n            \"fam-marble3\": \"Mármore\",\n            \"fam-snow1\": \"Neve Antiga\",\n            \"fam-stonewall3\": \"Parede de Pedra\",\n            \"fam-wall2\": \"Parede de Terra\"\n        },\n        \"worldMusic\": {\n            \"auto\": {\n                \"wmusic0\": \"Nenhum\"\n            },\n            \"fam-new\": {\n                \"header\": \"Novo\",\n                \"wmusic5\": \"Tema\"\n            },\n            \"fam-set3\": {\n                \"wmusic11\": \"Mundo 5\",\n                \"header\": \"Conjunto 3\",\n                \"wmusic1\": \"Mundo 1\",\n                \"wmusic10\": \"Mundo 6\",\n                \"wmusic2\": \"Mundo 4\",\n                \"wmusic3\": \"Mundo 7\",\n                \"wmusic6\": \"Mundo 2\",\n                \"wmusic8\": \"Mundo 3\",\n                \"wmusic9\": \"Mundo 8\"\n            },\n            \"fam-world\": {\n                \"header\": \"Mundo\",\n                \"wmusic12\": \"Especial\",\n                \"wmusic13\": \"Masmorra\",\n                \"wmusic14\": \"Céu\",\n                \"wmusic15\": \"Ilha\",\n                \"wmusic16\": \"Caverna\",\n                \"wmusic4\": \"Tema\",\n                \"wmusic7\": \"Floresta\"\n            }\n        }\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"Estrela\",\n        \"wordFails\": \"FALHAS\",\n        \"wordStarAccusativeDualOrCounter\": \"<Não usado no Inglês e Português, Seria como \\\"Pares de Estrelas\\\" se a língua o tiver especificado (Numeral entre Singular e Plural); Se a sua língua tiver contra palavra, use-a aqui>\",\n        \"wordStarAccusativePlural\": \"Estrelas\"\n    },\n    \"languageName\": \"Português\"\n}\n"
  },
  {
    "path": "resources/languages/assets_ru.json",
    "content": "{\n    \"character\": {\n        \"name1\": \"Перс1\",\n        \"name2\": \"Перс2\",\n        \"name3\": \"Перс3\",\n        \"name4\": \"Перс4\",\n        \"name5\": \"Перс5\"\n    },\n    \"editor\": {\n        \"background2\": {\n            \"auto\": {\n                \"bg2-0\": \"Нет\"\n            },\n            \"fam-misc\": {\n                \"bg2-16\": \"Космич. кратер\",\n                \"bg2-40\": \"Секретный рудник\",\n                \"bg2-45\": \"Космич. болото\",\n                \"bg2-46\": \"Космич. корабль\",\n                \"bg2-47\": \"Космич. база\",\n                \"header\": \"Прочее\"\n            },\n            \"fam-set1\": {\n                \"bg2-10\": \"Надземный мир\",\n                \"bg2-41\": \"Замок\",\n                \"bg2-50\": \"Грибы\",\n                \"bg2-51\": \"Пустыня\",\n                \"bg2-7\": \"Подземелье\",\n                \"bg2-8\": \"Ночь\",\n                \"bg2-9\": \"Ночь 2\",\n                \"header\": \"Набор 1\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"Подземелье\",\n                \"bg2-44\": \"Замок\",\n                \"bg2-48\": \"Облака\",\n                \"bg2-49\": \"Ночь - холмы\",\n                \"bg2-5\": \"Деревья\",\n                \"bg2-52\": \"Ночь - пустыня\",\n                \"bg2-53\": \"Утёс\",\n                \"bg2-54\": \"Склад\",\n                \"bg2-57\": \"Темница\",\n                \"header\": \"Набор 2\"\n            },\n            \"fam-set3\": {\n                \"bg2-1\": \"Блоки\",\n                \"bg2-13\": \"Облака\",\n                \"bg2-14\": \"Пустыня\",\n                \"bg2-15\": \"Темница 2\",\n                \"bg2-17\": \"Корабль\",\n                \"bg2-2\": \"Холмы\",\n                \"bg2-20\": \"Лес\",\n                \"bg2-21\": \"Битва\",\n                \"bg2-22\": \"Водопад\",\n                \"bg2-23\": \"Танки\",\n                \"bg2-24\": \"Финальный босс\",\n                \"bg2-26\": \"Грибной торговец\",\n                \"bg2-27\": \"Замок\",\n                \"bg2-3\": \"Темница\",\n                \"bg2-35\": \"Снежные деревья\",\n                \"bg2-36\": \"Облака 2\",\n                \"bg2-37\": \"Снежные холмы\",\n                \"bg2-38\": \"Пещера\",\n                \"bg2-39\": \"Пещера 2\",\n                \"bg2-4\": \"Трубы\",\n                \"bg2-56\": \"Под водой\",\n                \"bg2-6\": \"Бонус\",\n                \"header\": \"Набор 3\"\n            },\n            \"fam-set4\": {\n                \"bg2-11\": \"Холмы\",\n                \"bg2-12\": \"Деревья\",\n                \"bg2-18\": \"Особняк\",\n                \"bg2-19\": \"Лес\",\n                \"bg2-28\": \"Бонус\",\n                \"bg2-29\": \"Ночь\",\n                \"bg2-30\": \"Пещера\",\n                \"bg2-31\": \"Облака\",\n                \"bg2-32\": \"Холмы 2\",\n                \"bg2-33\": \"Холмы 4\",\n                \"bg2-34\": \"Холмы 3\",\n                \"bg2-42\": \"Замок\",\n                \"bg2-43\": \"Замок 2\",\n                \"bg2-55\": \"Под водой\",\n                \"bg2-58\": \"Ночь в пустыне\",\n                \"header\": \"Набор 4\"\n            }\n        },\n        \"bgo\": {\n            \"fam-art2\": \"Искусство\",\n            \"fam-bars4\": \"Брусья\",\n            \"fam-clouds1\": \"Облака\",\n            \"fam-doors2\": \"Двери\",\n            \"fam-doors4\": \"Двери\",\n            \"fam-exit3\": \"Выход\",\n            \"fam-fences1\": \"Заборы\",\n            \"fam-fences4\": \"Заборы\",\n            \"fam-ghost4\": \"Призрак\",\n            \"fam-hut1\": \"Хижина\",\n            \"fam-key4\": \"Ключ\",\n            \"fam-misc4\": \"Прочее\",\n            \"fam-plants1\": \"Растения\",\n            \"fam-plants2\": \"Растения\",\n            \"fam-plants3\": \"Растения\",\n            \"fam-plants4\": \"Растения\",\n            \"fam-platform4\": \"Плат.\",\n            \"fam-sandbg3\": \"Песочн. Ф.\",\n            \"fam-sandfg3\": \"П.\",\n            \"fam-space5\": \"Космич. жук\",\n            \"fam-structures3\": \"Структуры\",\n            \"fam-temple5\": \"Храм\",\n            \"fam-tiles3\": \"Плитки\",\n            \"fam-water1\": \"Вода\",\n            \"fam-water2\": \"Вода\",\n            \"fam-water3\": \"Вода\",\n            \"fam-water4\": \"Вода\"\n        },\n        \"block\": {\n            \"fam-bonus3\": \"Бонус\",\n            \"fam-bonus4\": \"Бонус\",\n            \"fam-bricks5\": \"Кирпич\",\n            \"fam-castle2\": \"Замок\",\n            \"fam-castle4\": \"Замок\",\n            \"fam-cave3\": \"Пещера\",\n            \"fam-cave4\": \"Пезера\",\n            \"fam-char5\": \"Перс.\",\n            \"fam-clouds1\": \"Облака\",\n            \"fam-desert3\": \"Пустыня\",\n            \"fam-dungeon1\": \"Темница\",\n            \"fam-dungeon3\": \"Темница\",\n            \"fam-grass3\": \"Трава\",\n            \"fam-grass4\": \"Трава\",\n            \"fam-house3\": \"Дом\",\n            \"fam-hurts3\": \"Боль\",\n            \"fam-lava4\": \"Лава и Боль\",\n            \"fam-mansion4\": \"Особняк\",\n            \"fam-metal5-1\": \"Пластины\",\n            \"fam-metal5-3\": \"Прочее\",\n            \"fam-misc1\": \"Прочее\",\n            \"fam-misc2\": \"Прочее\",\n            \"fam-misc3\": \"Прочее 3\",\n            \"fam-misc4\": \"Прочее\",\n            \"fam-overworld1\": \"Надземный мир\",\n            \"fam-overworld2\": \"Надземный мир\",\n            \"fam-pipes1\": \"Трубы\",\n            \"fam-pipes3\": \"Трубы\",\n            \"fam-pipes4\": \"Трубы\",\n            \"fam-pipes5-2\": \"Трубы\",\n            \"fam-pipes5-3\": \"Длинные трубы\",\n            \"fam-ruins5\": \"Руины\",\n            \"fam-sized1\": \"Измен.\",\n            \"fam-sized2\": \"Измен.\",\n            \"fam-sized3\": \"Измен. 3\",\n            \"fam-sized4\": \"Измен.\",\n            \"fam-special1\": \"Особый\",\n            \"fam-special2\": \"Особый\",\n            \"fam-special3\": \"Особый\",\n            \"fam-special4\": \"Особый\",\n            \"fam-steampunk3\": \"Стимпанк\",\n            \"fam-switch4\": \"Переключ.\",\n            \"fam-underground1\": \"Подземелье\",\n            \"fam-underground2\": \"Подземелье\",\n            \"fam-water3\": \"Вода\",\n            \"fam-wood2\": \"Дерево\",\n            \"fam-wood3\": \"Дерево\",\n            \"fam-woods4\": \"Рощи\"\n        },\n        \"exit-codes\": {\n            \"1\": \"Рулетка\",\n            \"2\": \"Сфера №16\",\n            \"3\": \"Выход\",\n            \"4\": \"Ключ\",\n            \"5\": \"Сфера №41\",\n            \"6\": \"Проход\",\n            \"7\": \"Сбор\",\n            \"8\": \"Брус\",\n            \"any\": \"Любой\",\n            \"none\": \"Нет\",\n            \"9\": \"код9\"\n        },\n        \"music\": {\n            \"auto\": {\n                \"music0\": \"Нет\",\n                \"music24\": \"Своя\"\n            },\n            \"fam-64\": {\n                \"header\": \"64\",\n                \"music14\": \"Пустыня\",\n                \"music26\": \"Замок\",\n                \"music27\": \"Главная тема\",\n                \"music35\": \"Снег\",\n                \"music36\": \"Босс\",\n                \"music49\": \"Вода\",\n                \"music50\": \"Пещера\"\n            },\n            \"fam-fight\": {\n                \"header\": \"Бой\",\n                \"music19\": \"Стимпанк\",\n                \"music39\": \"Храм\",\n                \"music40\": \"Рыцарь\",\n                \"music52\": \"Подземелье\",\n                \"music53\": \"Пинбол\"\n            },\n            \"fam-misc\": {\n                \"header\": \"Прочее.\",\n                \"music13\": \"Новый надземный мир\",\n                \"music18\": \"Пляж\",\n                \"music20\": \"Термояд. реактор\",\n                \"music23\": \"Героич. рощи\",\n                \"music37\": \"Ледяные горы\",\n                \"music38\": \"Джунгл. деревня\",\n                \"music55\": \"Титульная тема\",\n                \"music56\": \"Прыгучая гонка\",\n                \"music8\": \"В углу!\"\n            },\n            \"fam-rpg\": {\n                \"header\": \"Ролевая игра\",\n                \"music16\": \"Лес\",\n                \"music21\": \"Битва\",\n                \"music30\": \"Холостятский дом\",\n                \"music31\": \"Приморье\",\n                \"music32\": \"Пруд\",\n                \"music33\": \"Облака\",\n                \"music34\": \"Город\"\n            },\n            \"fam-set1\": {\n                \"header\": \"Набор 1\",\n                \"music42\": \"Темница\",\n                \"music46\": \"Вода\",\n                \"music7\": \"Подземелье\",\n                \"music9\": \"Надземный мир\"\n            },\n            \"fam-set2\": {\n                \"header\": \"Набор 2\",\n                \"music15\": \"Босс\",\n                \"music25\": \"Подземелье\",\n                \"music43\": \"Последний босс\",\n                \"music5\": \"Надземный мир\"\n            },\n            \"fam-set3\": {\n                \"header\": \"Набор 3\",\n                \"music1\": \"Надземный мир\",\n                \"music2\": \"Небо\",\n                \"music3\": \"Темница\",\n                \"music4\": \"Подземелье\",\n                \"music47\": \"Вода\",\n                \"music54\": \"Бродячий враг\",\n                \"music6\": \"Босс\"\n            },\n            \"fam-space\": {\n                \"header\": \"Космос\",\n                \"music11\": \"Красное болото\",\n                \"music12\": \"Кратер\",\n                \"music22\": \"Космич. заряд\",\n                \"music44\": \"Сокровищница\",\n                \"music45\": \"Последний босс\"\n            },\n            \"fam-world\": {\n                \"header\": \"Мир\",\n                \"music10\": \"Надземный мир\",\n                \"music17\": \"Особняк\",\n                \"music28\": \"Небо\",\n                \"music29\": \"Пещера\",\n                \"music41\": \"Темница\",\n                \"music48\": \"Вода\",\n                \"music51\": \"Босс\"\n            }\n        },\n        \"npc\": {\n            \"fam-blocks2\": \"Блоки\",\n            \"fam-blocks3\": \"Блоки\",\n            \"fam-boss1\": \"Босс\",\n            \"fam-boss2\": \"Босс\",\n            \"fam-boss3\": \"Босс\",\n            \"fam-boss4\": \"Босс\",\n            \"fam-boss5\": \"Босс\",\n            \"fam-char3\": \"Перс.\",\n            \"fam-char5\": \"Перс.\",\n            \"fam-ckpt4\": \"Конт.\",\n            \"fam-enemies1\": \"Враги\",\n            \"fam-enemies2\": \"Враги\",\n            \"fam-enemies3\": \"Враги\",\n            \"fam-enemies4\": \"Враги\",\n            \"fam-enemies5\": \"Враги\",\n            \"fam-exit2\": \"Выход\",\n            \"fam-exit3\": \"Выход\",\n            \"fam-exit4\": \"Выход\",\n            \"fam-items1\": \"Предметы\",\n            \"fam-items2\": \"Предметы\",\n            \"fam-items3\": \"Предметы\",\n            \"fam-items4\": \"Предметы\",\n            \"fam-items5\": \"Предметы\",\n            \"fam-pet4\": \"Питомец\",\n            \"fam-plant3\": \"Растение\",\n            \"fam-plat1\": \"Платформа\",\n            \"fam-plat3\": \"Платформа\",\n            \"fam-plat4\": \"Платформа\",\n            \"fam-shell1\": \"Панцирь\",\n            \"fam-shell3\": \"Панцирь\",\n            \"fam-sign4\": \"Знак\",\n            \"fam-switch5\": \"Перекл.\",\n            \"fam-veg2\": \"Овощ\",\n            \"fam-vine1\": \"Лоза\",\n            \"fam-vine2\": \"Лоза\",\n            \"fam-vine3\": \"Лоза\",\n            \"fam-vine4\": \"Лоза\",\n            \"fam-warp2\": \"Проход\"\n        },\n        \"sound\": {\n            \"auto\": {\n                \"sound0\": \"Нет\"\n            },\n            \"fam-boss\": {\n                \"header\": \"Босс\",\n                \"sound38\": \"Птичий плевок\",\n                \"sound39\": \"Удар по птице\",\n                \"sound41\": \"Птица повержена\",\n                \"sound44\": \"Черепаха убита\",\n                \"sound62\": \"Лягушачьи пузыри\",\n                \"sound63\": \"Лягушка убита\",\n                \"sound67\": \"Битое стекло\",\n                \"sound68\": \"Удар по космич. боссу\",\n                \"sound69\": \"Крик комсич. босса\",\n                \"sound70\": \"Комсмич. босс убит\"\n            },\n            \"fam-hero\": {\n                \"header\": \"Герой\",\n                \"sound53\": \"НИП убит ероем\",\n                \"sound77\": \"Удар героя\",\n                \"sound78\": \"Боль героя\",\n                \"sound79\": \"Взятие сердца\",\n                \"sound80\": \"Гибель героя\",\n                \"sound81\": \"Взятие др.камня\",\n                \"sound82\": \"Огонь героя\",\n                \"sound83\": \"Взятие предмета\",\n                \"sound84\": \"Взятие ключа\",\n                \"sound85\": \"Удар по щиту\",\n                \"sound86\": \"Сапоги Гермеса\",\n                \"sound87\": \"Полёт\",\n                \"sound88\": \"Стрижка газона\"\n            },\n            \"fam-item\": {\n                \"header\": \"Предмет\",\n                \"sound14\": \"Монетка\",\n                \"sound16\": \"Лава\",\n                \"sound22\": \"Пуля\",\n                \"sound3\": \"Удар по блоку\",\n                \"sound32\": \"Переключатель\",\n                \"sound37\": \"Давилка\",\n                \"sound4\": \"Блок разбит\",\n                \"sound42\": \"Большой огненный заряд\",\n                \"sound43\": \"Фейерверки\",\n                \"sound51\": \"Вылупление из яйца\",\n                \"sound56\": \"Кольцо\",\n                \"sound57\": \"Скелет\",\n                \"sound58\": \"Контрольная точка\",\n                \"sound59\": \"Собираемое\",\n                \"sound61\": \"Лавовое существо\",\n                \"sound64\": \"Удар по космич. блоку\",\n                \"sound65\": \"Гибель космич. НИПа\",\n                \"sound66\": \"Удар по космич. НИПу\",\n                \"sound7\": \"Усилитель\",\n                \"sound74\": \"Пила\",\n                \"sound9\": \"Пинок по панцирю\"\n            },\n            \"fam-jingle\": {\n                \"header\": \"Туш\",\n                \"sound19\": \"Выход рулеткой\",\n                \"sound20\": \"Победа над боссом\",\n                \"sound21\": \"Очистка темницы\",\n                \"sound31\": \"Выход ключём\",\n                \"sound40\": \"Выход шаром\",\n                \"sound45\": \"Игра пройдена\",\n                \"sound52\": \"Взятие звезды\",\n                \"sound60\": \"Выход брусом\",\n                \"sound8\": \"Игрок погиб\"\n            },\n            \"fam-pet\": {\n                \"header\": \"Питомец\",\n                \"sound36\": \"Топот питомца\",\n                \"sound48\": \"Оседлание питомца\",\n                \"sound49\": \"Питомцу больно\",\n                \"sound50\": \"Язык питомца\",\n                \"sound55\": \"Питомец проглотил\"\n            },\n            \"fam-player\": {\n                \"header\": \"Игрок\",\n                \"sound1\": \"Прыг\",\n                \"sound10\": \"Скольжение\",\n                \"sound12\": \"Взятие предмета\",\n                \"sound15\": \"+1 жизнь\",\n                \"sound17\": \"Проход\",\n                \"sound18\": \"Огненный шар\",\n                \"sound2\": \"Топ\",\n                \"sound23\": \"Захват\",\n                \"sound24\": \"Пружина\",\n                \"sound25\": \"Бросок молота\",\n                \"sound33\": \"Хвост\",\n                \"sound34\": \"Енот\",\n                \"sound35\": \"Потеря сапога\",\n                \"sound46\": \"Дверь\",\n                \"sound5\": \"Уменьшение\",\n                \"sound54\": \"Гибель игрока 2\",\n                \"sound6\": \"Рост\",\n                \"sound71\": \"Лазание\",\n                \"sound72\": \"Плавание\",\n                \"sound73\": \"Лёгкий захват\",\n                \"sound75\": \"Бросок овоща\",\n                \"sound76\": \"Потеря сердца\"\n            },\n            \"fam-ui\": {\n                \"header\": \"Польз. интерфейс\",\n                \"sound11\": \"Сброс предмета\",\n                \"sound13\": \"Камера\",\n                \"sound26\": \"Прокрутка\",\n                \"sound29\": \"Действие\",\n                \"sound30\": \"Пауза\",\n                \"sound47\": \"Сообщение\"\n            },\n            \"fam-world\": {\n                \"header\": \"Мир\",\n                \"sound27\": \"Новый путь\",\n                \"sound28\": \"Выбор уровня\"\n            }\n        },\n        \"tile\": {\n            \"fam-clouds4\": \"Облака\",\n            \"fam-desert1\": \"Пыль\",\n            \"fam-dirt2\": \"Мётрвая трава\",\n            \"fam-granite3\": \"Гранит\",\n            \"fam-grass1\": \"Старая трава\",\n            \"fam-grass2\": \"Счастливая трава\",\n            \"fam-lava3\": \"Лава\",\n            \"fam-lilac4\": \"Сиреневая стена\",\n            \"fam-marble3\": \"Мрамор\",\n            \"fam-snow1\": \"Старый снег\",\n            \"fam-stonewall3\": \"Каменная стена\",\n            \"fam-wall2\": \"Грязная стена\"\n        },\n        \"worldMusic\": {\n            \"auto\": {\n                \"wmusic0\": \"Нет\"\n            },\n            \"fam-new\": {\n                \"header\": \"Новый\",\n                \"wmusic5\": \"Тема\"\n            },\n            \"fam-set3\": {\n                \"header\": \"Набор 3\",\n                \"wmusic1\": \"Мир 1\",\n                \"wmusic10\": \"Мир 6\",\n                \"wmusic11\": \"Мир 5\",\n                \"wmusic2\": \"Мир 4\",\n                \"wmusic3\": \"Мир 7\",\n                \"wmusic6\": \"Мир 2\",\n                \"wmusic8\": \"Мир 3\",\n                \"wmusic9\": \"Мир 8\"\n            },\n            \"fam-world\": {\n                \"header\": \"Мир\",\n                \"wmusic12\": \"Особый\",\n                \"wmusic13\": \"Темница\",\n                \"wmusic14\": \"Небо\",\n                \"wmusic15\": \"Остров\",\n                \"wmusic16\": \"Пещера\",\n                \"wmusic4\": \"Тема\",\n                \"wmusic7\": \"Лес\"\n            }\n        }\n    },\n    \"languageName\": \"Русский\",\n    \"objects\": {\n        \"wordFails\": \"Неудачи\",\n        \"wordStarAccusativeDualOrCounter\": \"звезды\",\n        \"wordStarAccusativePlural\": \"звёзд\",\n        \"wordStarAccusativeSingular\": \"звезду\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/assets_ta.json",
    "content": "{\n    \"languageName\": \"தமிழ்\",\n    \"editor\": {\n        \"background2\": {\n            \"fam-misc\": {\n                \"bg2-40\": \"இரகசிய சுரங்கம்\",\n                \"bg2-45\": \"விண்வெளி சதுப்பு நிலம்\",\n                \"bg2-46\": \"விண்வெளி கப்பல்\",\n                \"bg2-47\": \"விண்வெளி அடிப்படை\",\n                \"header\": \"தவறான.\",\n                \"bg2-16\": \"விண்வெளி பள்ளம்\"\n            },\n            \"fam-set1\": {\n                \"bg2-10\": \"ஓவர் உலக\",\n                \"bg2-41\": \"கோட்டை\",\n                \"bg2-50\": \"காளான்கள்\",\n                \"bg2-51\": \"பாலைவனம்\",\n                \"bg2-7\": \"நிலத்தடி\",\n                \"bg2-8\": \"இரவு\",\n                \"bg2-9\": \"இரவு 2\",\n                \"header\": \"செட் 1\"\n            },\n            \"fam-set2\": {\n                \"bg2-44\": \"கோட்டை\",\n                \"bg2-48\": \"மேகங்கள்\",\n                \"bg2-49\": \"இரவு - மலைகள்\",\n                \"bg2-5\": \"மரங்கள்\",\n                \"bg2-52\": \"இரவு - பாலைவனம்\",\n                \"bg2-25\": \"நிலத்தடி\",\n                \"bg2-53\": \"குன்றின்\",\n                \"bg2-54\": \"கிடங்கு\",\n                \"bg2-57\": \"நிலவறை\",\n                \"header\": \"செட் 2\"\n            },\n            \"fam-set3\": {\n                \"bg2-26\": \"ச்ரூம் வணிகர்\",\n                \"bg2-27\": \"கோட்டை\",\n                \"bg2-3\": \"நிலவறை\",\n                \"bg2-37\": \"ச்னோ இல்ச்\",\n                \"bg2-38\": \"குகை\",\n                \"bg2-39\": \"குகை 2\",\n                \"bg2-1\": \"தொகுதிகள்\",\n                \"bg2-13\": \"மேகங்கள்\",\n                \"bg2-14\": \"பாலைவனம்\",\n                \"bg2-15\": \"நிலவறை 2\",\n                \"bg2-17\": \"கப்பல்\",\n                \"bg2-2\": \"மலைகள்\",\n                \"bg2-20\": \"காடு\",\n                \"bg2-21\": \"போர்\",\n                \"bg2-22\": \"நீர்வீழ்ச்சி\",\n                \"bg2-23\": \"டாங்கிகள்\",\n                \"bg2-24\": \"இறுதி முதலாளி\",\n                \"bg2-35\": \"பனி மரங்கள்\",\n                \"bg2-36\": \"மேகங்கள் 2\",\n                \"bg2-4\": \"குழல்கள்\",\n                \"bg2-56\": \"நீருக்கடியில்\",\n                \"bg2-6\": \"போனச்\",\n                \"header\": \"செட் 3\"\n            },\n            \"fam-set4\": {\n                \"bg2-11\": \"மலைகள்\",\n                \"bg2-58\": \"பாலைவன இரவு\",\n                \"bg2-30\": \"குகை\",\n                \"bg2-31\": \"மேகங்கள்\",\n                \"bg2-32\": \"இல்ச் 2\",\n                \"bg2-55\": \"நீருக்கடியில்\",\n                \"bg2-12\": \"மரங்கள்\",\n                \"bg2-18\": \"மாளிகை\",\n                \"bg2-19\": \"காடு\",\n                \"bg2-28\": \"போனச்\",\n                \"bg2-29\": \"இரவு\",\n                \"bg2-33\": \"மலைகள் 4\",\n                \"bg2-34\": \"மலைகள் 3\",\n                \"bg2-42\": \"கோட்டை\",\n                \"bg2-43\": \"கோட்டை 2\",\n                \"header\": \"செட் 4\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"எதுவுமில்லை\"\n            }\n        },\n        \"bgo\": {\n            \"fam-art2\": \"கலை\",\n            \"fam-bars4\": \"பார்கள்\",\n            \"fam-clouds1\": \"மேகங்கள்\",\n            \"fam-doors2\": \"கதவுகள்\",\n            \"fam-doors4\": \"கதவுகள்\",\n            \"fam-exit3\": \"வெளியேறு\",\n            \"fam-fences1\": \"வேலிகள்\",\n            \"fam-misc4\": \"இதர\",\n            \"fam-plants1\": \"தாவரங்கள்\",\n            \"fam-plants3\": \"தாவரங்கள்\",\n            \"fam-plants4\": \"தாவரங்கள்\",\n            \"fam-platform4\": \"தட்டு\",\n            \"fam-sandbg3\": \"மணல் பி.சி.\",\n            \"fam-sandfg3\": \"Fg\",\n            \"fam-space5\": \"விண்வெளி பிழை\",\n            \"fam-water4\": \"நீர்\",\n            \"fam-fences4\": \"வேலிகள்\",\n            \"fam-ghost4\": \"பேய்\",\n            \"fam-hut1\": \"குடிசை\",\n            \"fam-key4\": \"விசை\",\n            \"fam-plants2\": \"தாவரங்கள்\",\n            \"fam-structures3\": \"கட்டமைப்புகள்\",\n            \"fam-temple5\": \"கோயில்\",\n            \"fam-tiles3\": \"ஓடுகள்\",\n            \"fam-water1\": \"நீர்\",\n            \"fam-water2\": \"நீர்\",\n            \"fam-water3\": \"நீர்\"\n        },\n        \"block\": {\n            \"fam-bonus3\": \"போனச்\",\n            \"fam-bonus4\": \"போனச்\",\n            \"fam-clouds1\": \"மேகங்கள்\",\n            \"fam-desert3\": \"பாலைவனம்\",\n            \"fam-grass3\": \"புல்\",\n            \"fam-grass4\": \"புல்\",\n            \"fam-house3\": \"வீடு\",\n            \"fam-hurts3\": \"வலிக்கிறது\",\n            \"fam-lava4\": \"எரிமலை மற்றும் வலிக்கிறது\",\n            \"fam-mansion4\": \"மாளிகை\",\n            \"fam-metal5-1\": \"தட்டுகள்\",\n            \"fam-metal5-3\": \"இதர\",\n            \"fam-misc1\": \"இதர\",\n            \"fam-misc2\": \"இதர\",\n            \"fam-special1\": \"சிறப்பு\",\n            \"fam-special2\": \"சிறப்பு\",\n            \"fam-special3\": \"சிறப்பு\",\n            \"fam-steampunk3\": \"ச்டீம்பங்க்\",\n            \"fam-pipes5-2\": \"குழல்கள்\",\n            \"fam-switch4\": \"ஆளி, நிலைமாறி\",\n            \"fam-underground1\": \"நிலத்தடி\",\n            \"fam-underground2\": \"நிலத்தடி\",\n            \"fam-pipes5-3\": \"நீண்ட குழாய்கள்\",\n            \"fam-sized4\": \"அளவு\",\n            \"fam-water3\": \"நீர்\",\n            \"fam-wood2\": \"மரம்\",\n            \"fam-wood3\": \"மரம்\",\n            \"fam-woods4\": \"வூட்ச்\",\n            \"fam-bricks5\": \"செங்கல்\",\n            \"fam-castle2\": \"கோட்டை\",\n            \"fam-castle4\": \"கோட்டை\",\n            \"fam-cave3\": \"குகை\",\n            \"fam-cave4\": \"குகை\",\n            \"fam-char5\": \"சார்\",\n            \"fam-dungeon1\": \"நிலவறை\",\n            \"fam-dungeon3\": \"நிலவறை\",\n            \"fam-misc3\": \"மற்ற 3\",\n            \"fam-misc4\": \"இதர\",\n            \"fam-overworld1\": \"ஓவர் உலக\",\n            \"fam-overworld2\": \"ஓவர் உலக\",\n            \"fam-pipes1\": \"குழல்கள்\",\n            \"fam-pipes3\": \"குழல்கள்\",\n            \"fam-pipes4\": \"குழல்கள்\",\n            \"fam-ruins5\": \"இடிபாடுகள்\",\n            \"fam-sized1\": \"அளவு\",\n            \"fam-sized2\": \"அளவு\",\n            \"fam-sized3\": \"அளவு 3\",\n            \"fam-special4\": \"சிறப்பு\"\n        },\n        \"exit-codes\": {\n            \"1\": \"சில்லி\",\n            \"6\": \"வார்ப்\",\n            \"7\": \"சேகரிக்கவும்\",\n            \"8\": \"பார் முடிவு\",\n            \"any\": \"ஏதேனும்\",\n            \"none\": \"எதுவுமில்லை\",\n            \"2\": \"உருண்டை #16\",\n            \"3\": \"விடுப்பு\",\n            \"4\": \"விசை\",\n            \"5\": \"ORB #41\",\n            \"9\": \"குறியீடு9\"\n        },\n        \"music\": {\n            \"auto\": {\n                \"music0\": \"எதுவுமில்லை\",\n                \"music24\": \"தனிப்பயன்\"\n            },\n            \"fam-64\": {\n                \"music14\": \"பாலைவனம்\",\n                \"music26\": \"கோட்டை\",\n                \"music27\": \"முக்கிய கருப்பொருள்\",\n                \"music50\": \"குகை\",\n                \"header\": \"64\",\n                \"music35\": \"பனி\",\n                \"music36\": \"பாச்\",\n                \"music49\": \"நீர்\"\n            },\n            \"fam-fight\": {\n                \"header\": \"சண்டை\",\n                \"music19\": \"ச்டீம்பங்க்\",\n                \"music39\": \"கோயில்\",\n                \"music40\": \"நைட்\",\n                \"music52\": \"நிலத்தடி\",\n                \"music53\": \"பின்பால்\"\n            },\n            \"fam-misc\": {\n                \"music13\": \"புதிய ஓவர் உலக\",\n                \"music18\": \"கடற்கரை\",\n                \"music20\": \"இணைவு உலை\",\n                \"music37\": \"பனி மலை\",\n                \"music38\": \"சங்கிள் சிற்றூர்\",\n                \"music55\": \"தலைப்பு கருப்பொருள்\",\n                \"music56\": \"பவுன்சி ரேச்\",\n                \"music8\": \"மூலைவிட்ட!\",\n                \"header\": \"தவறான.\",\n                \"music23\": \"வீர காடுகள்\"\n            },\n            \"fam-rpg\": {\n                \"music30\": \"இளங்கலை திண்டு\",\n                \"music32\": \"குளம்\",\n                \"music33\": \"மேகங்கள்\",\n                \"music34\": \"நகரம்\",\n                \"header\": \"Rpg\",\n                \"music16\": \"காடு\",\n                \"music21\": \"போர்\",\n                \"music31\": \"கடலோர\"\n            },\n            \"fam-set1\": {\n                \"header\": \"செட் 1\",\n                \"music42\": \"நிலவறை\",\n                \"music46\": \"நீர்\",\n                \"music7\": \"நிலத்தடி\",\n                \"music9\": \"ஓவர் உலக\"\n            },\n            \"fam-set2\": {\n                \"header\": \"செட் 2\",\n                \"music15\": \"பாச்\",\n                \"music25\": \"நிலத்தடி\",\n                \"music43\": \"இறுதி முதலாளி\",\n                \"music5\": \"ஓவர் உலக\"\n            },\n            \"fam-set3\": {\n                \"music1\": \"ஓவர் உலக\",\n                \"music2\": \"வானம்\",\n                \"music3\": \"நிலவறை\",\n                \"music4\": \"நிலத்தடி\",\n                \"music54\": \"ரோமிங் எதிரி\",\n                \"music6\": \"பாச்\",\n                \"header\": \"செட் 3\",\n                \"music47\": \"நீர்\"\n            },\n            \"fam-space\": {\n                \"header\": \"இடைவெளி\",\n                \"music11\": \"சிவப்பு சதுப்பு நிலம்\",\n                \"music12\": \"க்ரேட்டர்\",\n                \"music22\": \"விண்வெளி கட்டணம்\",\n                \"music44\": \"உருப்படி அறை\",\n                \"music45\": \"இறுதி முதலாளி\"\n            },\n            \"fam-world\": {\n                \"music17\": \"மாளிகை\",\n                \"music28\": \"வானம்\",\n                \"music29\": \"குகை\",\n                \"music41\": \"நிலவறை\",\n                \"music48\": \"நீர்\",\n                \"music51\": \"பாச்\",\n                \"header\": \"உலகம்\",\n                \"music10\": \"ஓவர் உலக\"\n            }\n        },\n        \"npc\": {\n            \"fam-blocks2\": \"தொகுதிகள்\",\n            \"fam-boss4\": \"பாச்\",\n            \"fam-enemies1\": \"எதிரிகள்\",\n            \"fam-enemies2\": \"எதிரிகள்\",\n            \"fam-enemies3\": \"எதிரிகள்\",\n            \"fam-enemies5\": \"எதிரிகள்\",\n            \"fam-exit2\": \"வெளியேறு\",\n            \"fam-exit3\": \"வெளியேறு\",\n            \"fam-exit4\": \"வெளியேறு\",\n            \"fam-items1\": \"உருப்படிகள்\",\n            \"fam-items2\": \"உருப்படிகள்\",\n            \"fam-items5\": \"உருப்படிகள்\",\n            \"fam-pet4\": \"செல்லப்பிள்ளை\",\n            \"fam-plant3\": \"ஆலை\",\n            \"fam-plat1\": \"தட்டு\",\n            \"fam-plat3\": \"தட்டு\",\n            \"fam-veg2\": \"காய்கறி\",\n            \"fam-vine1\": \"கொடியின்\",\n            \"fam-vine2\": \"கொடியின்\",\n            \"fam-vine3\": \"கொடியின்\",\n            \"fam-vine4\": \"கொடியின்\",\n            \"fam-warp2\": \"வார்ப்\",\n            \"fam-blocks3\": \"தொகுதிகள்\",\n            \"fam-boss1\": \"பாச்\",\n            \"fam-boss2\": \"பாச்\",\n            \"fam-boss3\": \"பாச்\",\n            \"fam-boss5\": \"பாச்\",\n            \"fam-char3\": \"சார்\",\n            \"fam-char5\": \"சார்\",\n            \"fam-ckpt4\": \"நுகர்வு\",\n            \"fam-enemies4\": \"எதிரிகள்\",\n            \"fam-items3\": \"உருப்படிகள்\",\n            \"fam-items4\": \"உருப்படிகள்\",\n            \"fam-plat4\": \"தட்டு\",\n            \"fam-shell1\": \"ஓடு\",\n            \"fam-shell3\": \"ஓடு\",\n            \"fam-sign4\": \"அடையாளம்\",\n            \"fam-switch5\": \"ஆளி, நிலைமாறி\"\n        },\n        \"sound\": {\n            \"auto\": {\n                \"sound0\": \"எதுவுமில்லை\"\n            },\n            \"fam-boss\": {\n                \"header\": \"பாச்\",\n                \"sound44\": \"ஆமை கொல்லப்பட்டது\",\n                \"sound62\": \"தவளை குமிழ்கள்\",\n                \"sound38\": \"பறவை துப்புதல்\",\n                \"sound39\": \"பறவை செய்\",\n                \"sound41\": \"பறவை துடிப்பு\",\n                \"sound63\": \"தவளை கொல்லப்பட்டது\",\n                \"sound67\": \"கண்ணாடி இடைவெளி\",\n                \"sound68\": \"விண்வெளி முதலாளி செய்\",\n                \"sound69\": \"விண்வெளி முதலாளி அழுகை\",\n                \"sound70\": \"விண்வெளி முதலாளி கொலை\"\n            },\n            \"fam-hero\": {\n                \"header\": \"ஈரோ\",\n                \"sound53\": \"ஈரோ என்.பி.சி கொலை\",\n                \"sound80\": \"ஈரோ இறந்தார்\",\n                \"sound82\": \"ஈரோ தீ\",\n                \"sound83\": \"ஈரோ உருப்படி\",\n                \"sound84\": \"சாவியைப் பெறுங்கள்\",\n                \"sound85\": \"கேடயத் தொகுதி\",\n                \"sound86\": \"எர்ம்ச் பூட்ச்\",\n                \"sound87\": \"பறக்க\",\n                \"sound88\": \"மூவ் புல்வெளி\",\n                \"sound77\": \"ஈரோ குத்துங்கள்\",\n                \"sound78\": \"ஈரோ காயப்படுத்தினார்\",\n                \"sound79\": \"இதயத்தைப் பெறுங்கள்\",\n                \"sound81\": \"ரத்தினத்தைப் பெறுங்கள்\"\n            },\n            \"fam-item\": {\n                \"sound37\": \"நொறுக்கி\",\n                \"sound4\": \"தொகுதி அடித்து நொறுக்கப்பட்டது\",\n                \"sound43\": \"பட்டாசு\",\n                \"sound51\": \"முட்டை அட்ச்\",\n                \"sound56\": \"வளையம்\",\n                \"sound57\": \"எலும்புக் கூடு\",\n                \"sound58\": \"சோதனைச் சாவடி\",\n                \"sound59\": \"தொகுக்கக்கூடிய\",\n                \"sound61\": \"லாவா உயிரினங்கள்\",\n                \"sound64\": \"விண்வெளி தொகுதி செய்\",\n                \"sound7\": \"பவர்அப்\",\n                \"sound74\": \"பார்த்தேன்\",\n                \"sound9\": \"செல் கிக்\",\n                \"header\": \"உருப்படி\",\n                \"sound14\": \"நாணயம்\",\n                \"sound16\": \"எரிமலைக்குழம்பு, பாறைக்குழம்பு\",\n                \"sound22\": \"புல்லட்\",\n                \"sound3\": \"தொகுதி செய்\",\n                \"sound32\": \"ஆளி, நிலைமாறி\",\n                \"sound42\": \"பெரிய ஃபயர்பால்\",\n                \"sound65\": \"விண்வெளி NPC கொலை\",\n                \"sound66\": \"விண்வெளி NPC உடல்\"\n            },\n            \"fam-jingle\": {\n                \"sound20\": \"மொழி தோற்கடிக்கவும்\",\n                \"sound21\": \"நிலவறை தெளிவாக\",\n                \"sound31\": \"விசை வெளியேறுதல்\",\n                \"sound40\": \"பந்து வெளியேறுதல்\",\n                \"sound60\": \"பார் வெளியேறுதல்\",\n                \"sound8\": \"வீரர் இறந்தார்\",\n                \"header\": \"சிங்கிள்\",\n                \"sound19\": \"சில்லி வெளியேறுதல்\",\n                \"sound45\": \"விளையாட்டு துடிப்பு\",\n                \"sound52\": \"நட்சத்திர வெளியேறுதல் கிடைத்தது\"\n            },\n            \"fam-pet\": {\n                \"header\": \"செல்லப்பிள்ளை\",\n                \"sound48\": \"செல்லப்பிராணி மவுண்ட்\",\n                \"sound49\": \"செல்லப்பிராணி உடல்\",\n                \"sound50\": \"செல்லப்பிராணி நாக்கு\",\n                \"sound36\": \"செல்லப்பிராணி ச்டாம்ப்\",\n                \"sound55\": \"செல்லப்பிராணி விழுங்குதல்\"\n            },\n            \"fam-player\": {\n                \"sound1\": \"தாவு\",\n                \"sound10\": \"சறுக்கல்\",\n                \"sound12\": \"உருப்படி கிடைத்தது\",\n                \"sound2\": \"ச்டாம்ப்\",\n                \"sound23\": \"பிடுங்க\",\n                \"sound25\": \"சுத்தி டாச்\",\n                \"sound33\": \"வால்\",\n                \"sound34\": \"அணில்கரடி\",\n                \"sound35\": \"துவக்கத்தை இழக்க\",\n                \"sound46\": \"கதவு\",\n                \"sound5\": \"சுருங்கவும்\",\n                \"sound54\": \"வீரர் இறந்தார் 2\",\n                \"sound6\": \"வளர\",\n                \"sound71\": \"ஏறுதல்\",\n                \"sound72\": \"நீச்சல்\",\n                \"header\": \"வீரர்\",\n                \"sound15\": \"1up\",\n                \"sound17\": \"வார்ப்\",\n                \"sound18\": \"ஃபயர்பால்\",\n                \"sound24\": \"வேனில்\",\n                \"sound73\": \"ஒளி கிராப்\",\n                \"sound75\": \"காய்கறி எறியுங்கள்\",\n                \"sound76\": \"இதயத்தை இழக்க\"\n            },\n            \"fam-ui\": {\n                \"header\": \"இடைமுகம்\",\n                \"sound11\": \"உருப்படியை கைவிடுங்கள்\",\n                \"sound13\": \"கேமரா\",\n                \"sound26\": \"ச்லைடு\",\n                \"sound29\": \"வெற்றி\",\n                \"sound30\": \"இடைநிறுத்தம்\",\n                \"sound47\": \"செய்தி\"\n            },\n            \"fam-world\": {\n                \"header\": \"உலகம்\",\n                \"sound27\": \"புதிய பாதை\",\n                \"sound28\": \"நிலை தேர்ந்தெடுக்கவும்\"\n            }\n        },\n        \"tile\": {\n            \"fam-dirt2\": \"இறந்த புல்\",\n            \"fam-granite3\": \"கிரானைட்\",\n            \"fam-grass1\": \"பழைய புல்\",\n            \"fam-grass2\": \"மகிழ்ச்சியான புல்\",\n            \"fam-lava3\": \"எரிமலைக்குழம்பு, பாறைக்குழம்பு\",\n            \"fam-lilac4\": \"இளஞ்சிவப்பு சுவர்\",\n            \"fam-marble3\": \"பளிங்கு\",\n            \"fam-snow1\": \"பழைய பனி\",\n            \"fam-stonewall3\": \"கல் சுவர்\",\n            \"fam-wall2\": \"அழுக்கு சுவர்\",\n            \"fam-clouds4\": \"மேகங்கள்\",\n            \"fam-desert1\": \"தூசு\"\n        },\n        \"worldMusic\": {\n            \"auto\": {\n                \"wmusic0\": \"எதுவுமில்லை\"\n            },\n            \"fam-new\": {\n                \"header\": \"புதிய\",\n                \"wmusic5\": \"கருப்பொருள்\"\n            },\n            \"fam-set3\": {\n                \"wmusic2\": \"உலகம் 4\",\n                \"wmusic3\": \"உலகம் 7\",\n                \"header\": \"செட் 3\",\n                \"wmusic1\": \"உலகம் 1\",\n                \"wmusic10\": \"உலகம் 6\",\n                \"wmusic11\": \"உலகம் 5\",\n                \"wmusic6\": \"உலகம் 2\",\n                \"wmusic8\": \"உலகம் 3\",\n                \"wmusic9\": \"உலகம் 8\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"நிலவறை\",\n                \"wmusic14\": \"வானம்\",\n                \"wmusic15\": \"தீவு\",\n                \"wmusic16\": \"குகை\",\n                \"wmusic4\": \"கருப்பொருள்\",\n                \"wmusic7\": \"காடு\",\n                \"header\": \"உலகம்\",\n                \"wmusic12\": \"சிறப்பு\"\n            }\n        }\n    },\n    \"objects\": {\n        \"wordFails\": \"தோல்வியுற்றது\",\n        \"wordStarAccusativeDualOrCounter\": \"<ஆங்கிலத்தில் பயன்படுத்தப்படாதது, உங்கள் மொழியில் இரட்டை வடிவம் இருந்தால் (பன்மை மற்றும் ஒருமைப்பாட்டிற்கு இடையிலான எண்ணிக்கை) இருந்தால் \\\"நட்சத்திரங்கள்\\\" என்று மொழிபெயர்க்கவும்; உங்கள் மொழியில் எதிர் சொல் இருந்தால், அதற்கு பதிலாக எதிர் வார்த்தையைப் பயன்படுத்தவும்>\",\n        \"wordStarAccusativePlural\": \"நட்சத்திரங்கள்\",\n        \"wordStarAccusativeSingular\": \"விண்மீன்\"\n    },\n    \"character\": {\n        \"name1\": \"எழுத்து1\",\n        \"name2\": \"எழுத்து2\",\n        \"name3\": \"தெரு\",\n        \"name4\": \"எழுத்து4\",\n        \"name5\": \"கார்\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/assets_tr.json",
    "content": "{\n    \"character\": {\n        \"name1\": \"Karakter1\",\n        \"name2\": \"Karakter2\",\n        \"name3\": \"Karakter3\",\n        \"name4\": \"Karakter4\",\n        \"name5\": \"Karakter5\"\n    },\n    \"editor\": {\n        \"background2\": {\n            \"auto\": {\n                \"bg2-0\": \"Hiç\"\n            },\n            \"fam-misc\": {\n                \"bg2-16\": \"Uzay Krateri\",\n                \"bg2-40\": \"Gizli Maden\",\n                \"bg2-45\": \"Uzay Bataklığı\",\n                \"bg2-46\": \"Uzay Gemisi\",\n                \"bg2-47\": \"Uzay Üssü\",\n                \"header\": \"Diğer Şeyler\"\n            },\n            \"fam-set1\": {\n                \"bg2-10\": \"Ana Dünya\",\n                \"bg2-41\": \"Kale\",\n                \"bg2-50\": \"Mantarlar\",\n                \"bg2-51\": \"Çöl\",\n                \"bg2-7\": \"Yer Altı\",\n                \"bg2-8\": \"Gece\",\n                \"bg2-9\": \"Gece 2\",\n                \"header\": \"Set 1\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"Yer Altı\",\n                \"bg2-44\": \"Kale\",\n                \"bg2-48\": \"Bulutlar\",\n                \"bg2-49\": \"Bayır - Gece\",\n                \"bg2-5\": \"Koru\",\n                \"bg2-52\": \"Çöl - Gece\",\n                \"bg2-53\": \"Uçurum\",\n                \"bg2-54\": \"Depo\",\n                \"bg2-57\": \"Zindan\",\n                \"header\": \"Set 2\"\n            },\n            \"fam-set3\": {\n                \"bg2-1\": \"Bloklar\",\n                \"bg2-13\": \"Bulutlar\",\n                \"bg2-14\": \"Çöl\",\n                \"bg2-15\": \"Zindan 2\",\n                \"bg2-17\": \"Gemi\",\n                \"bg2-2\": \"Bayır\",\n                \"bg2-20\": \"Orman\",\n                \"bg2-21\": \"Çatışma\",\n                \"bg2-22\": \"Şelale\",\n                \"bg2-23\": \"Tanklar\",\n                \"bg2-24\": \"Son Boss\",\n                \"bg2-26\": \"Mantar Tüccarı\",\n                \"bg2-27\": \"Kale\",\n                \"bg2-3\": \"Zindan\",\n                \"bg2-35\": \"Karlı Koru\",\n                \"bg2-36\": \"Bulutlar 2\",\n                \"bg2-37\": \"Karlı Bayırlar\",\n                \"bg2-38\": \"Mağara\",\n                \"bg2-39\": \"Mağara 2\",\n                \"bg2-4\": \"Borular\",\n                \"bg2-56\": \"Su Altı\",\n                \"bg2-6\": \"Bonus\",\n                \"header\": \"Set 3\"\n            },\n            \"fam-set4\": {\n                \"bg2-11\": \"Bayır\",\n                \"bg2-12\": \"Koru\",\n                \"bg2-18\": \"Malikane\",\n                \"bg2-19\": \"Orman\",\n                \"bg2-28\": \"Bonus\",\n                \"bg2-29\": \"Gece\",\n                \"bg2-30\": \"Mağara\",\n                \"bg2-31\": \"Bulutlar\",\n                \"bg2-32\": \"Bayır 2\",\n                \"bg2-33\": \"Bayır 4\",\n                \"bg2-34\": \"Bayır 3\",\n                \"bg2-42\": \"Kale\",\n                \"bg2-43\": \"Kale 2\",\n                \"bg2-55\": \"Su Altı\",\n                \"bg2-58\": \"Çöl - Gece\",\n                \"header\": \"Set 4\"\n            }\n        },\n        \"bgo\": {\n            \"fam-art2\": \"Sanat\",\n            \"fam-bars4\": \"Direkler\",\n            \"fam-clouds1\": \"Bulutlar\",\n            \"fam-doors2\": \"Kapılar\",\n            \"fam-doors4\": \"Kapılar\",\n            \"fam-exit3\": \"Çıkış\",\n            \"fam-fences1\": \"Çit\",\n            \"fam-fences4\": \"Çit\",\n            \"fam-ghost4\": \"Hayalet\",\n            \"fam-hut1\": \"Baraka\",\n            \"fam-key4\": \"Anahtar\",\n            \"fam-misc4\": \"Diğer Şeyler\",\n            \"fam-plants1\": \"Bitkiler\",\n            \"fam-plants2\": \"Bitkiler\",\n            \"fam-plants3\": \"Bitkiler\",\n            \"fam-plants4\": \"Bitkiler\",\n            \"fam-platform4\": \"Platform\",\n            \"fam-sandbg3\": \"Kum AP\",\n            \"fam-sandfg3\": \"ÖP\",\n            \"fam-space5\": \"Uzay Böceği\",\n            \"fam-structures3\": \"Yapılar\",\n            \"fam-temple5\": \"Tapınak\",\n            \"fam-tiles3\": \"Tilelar\",\n            \"fam-water1\": \"Su\",\n            \"fam-water2\": \"Su\",\n            \"fam-water3\": \"Su\",\n            \"fam-water4\": \"Su\"\n        },\n        \"block\": {\n            \"fam-bonus3\": \"Bonus\",\n            \"fam-bonus4\": \"Bonus\",\n            \"fam-bricks5\": \"Tuğla\",\n            \"fam-castle2\": \"Kale\",\n            \"fam-castle4\": \"Kale\",\n            \"fam-cave3\": \"Mağara\",\n            \"fam-cave4\": \"Mağara\",\n            \"fam-char5\": \"Karakter\",\n            \"fam-clouds1\": \"Bulutlar\",\n            \"fam-desert3\": \"Çöl\",\n            \"fam-dungeon1\": \"Zindan\",\n            \"fam-dungeon3\": \"Zindan\",\n            \"fam-grass3\": \"Çimen\",\n            \"fam-grass4\": \"Çimen\",\n            \"fam-house3\": \"Ev\",\n            \"fam-hurts3\": \"Hasar\",\n            \"fam-lava4\": \"Lav ve Hasar\",\n            \"fam-mansion4\": \"Malikane\",\n            \"fam-metal5-1\": \"Demir Levha\",\n            \"fam-metal5-3\": \"Diğer Şeyler\",\n            \"fam-misc1\": \"Diğer Şeyler\",\n            \"fam-misc2\": \"Diğer Şeyler\",\n            \"fam-misc3\": \"Diğer Şeyler 3\",\n            \"fam-misc4\": \"Diğer Şeyler\",\n            \"fam-overworld1\": \"Ana Dünya\",\n            \"fam-overworld2\": \"Ana Dünya\",\n            \"fam-pipes1\": \"Borular\",\n            \"fam-pipes3\": \"Borular\",\n            \"fam-pipes4\": \"Borular\",\n            \"fam-pipes5-2\": \"Borular\",\n            \"fam-pipes5-3\": \"Uzun Borular\",\n            \"fam-ruins5\": \"Enkaz\",\n            \"fam-sized1\": \"Boyutlandır\",\n            \"fam-sized2\": \"Boyutlandır\",\n            \"fam-sized3\": \"Boyutlandır\",\n            \"fam-sized4\": \"Boyutlandır\",\n            \"fam-special1\": \"Özel\",\n            \"fam-special2\": \"Özel\",\n            \"fam-special3\": \"Özel\",\n            \"fam-special4\": \"Özel\",\n            \"fam-steampunk3\": \"Steampunk\",\n            \"fam-switch4\": \"Şalter\",\n            \"fam-underground1\": \"Yer Altı\",\n            \"fam-underground2\": \"Yer Altı\",\n            \"fam-water3\": \"Su\",\n            \"fam-wood2\": \"Odun\",\n            \"fam-wood3\": \"Odun\",\n            \"fam-woods4\": \"Odunlar\"\n        },\n        \"exit-codes\": {\n            \"1\": \"Rulet\",\n            \"2\": \"Orb #16\",\n            \"3\": \"Çık\",\n            \"4\": \"Anahtar\",\n            \"5\": \"Orb #41\",\n            \"6\": \"Işınlanma\",\n            \"7\": \"Topla\",\n            \"8\": \"Direk Sonu\",\n            \"any\": \"Herhangi\",\n            \"none\": \"Hiç\",\n            \"9\": \"\"\n        },\n        \"music\": {\n            \"auto\": {\n                \"music0\": \"Hiç\",\n                \"music24\": \"Custom\"\n            },\n            \"fam-64\": {\n                \"header\": \"64\",\n                \"music14\": \"Çöl\",\n                \"music26\": \"Kale\",\n                \"music27\": \"Ana Tema\",\n                \"music35\": \"Kar\",\n                \"music36\": \"Boss\",\n                \"music49\": \"Su\",\n                \"music50\": \"Mağara\"\n            },\n            \"fam-fight\": {\n                \"header\": \"Uçuş\",\n                \"music19\": \"Steampunk\",\n                \"music39\": \"Tapınak\",\n                \"music40\": \"Şovalye\",\n                \"music52\": \"Yer Altı\",\n                \"music53\": \"Pinball\"\n            },\n            \"fam-misc\": {\n                \"header\": \"Diğer Şeyler\",\n                \"music13\": \"Yeni Ana Dünya\",\n                \"music18\": \"Sahil\",\n                \"music20\": \"Füzyon Reaktörü\",\n                \"music23\": \"Yiğit Ormanı\",\n                \"music37\": \"Buz Dağı\",\n                \"music38\": \"Orman Dağı\",\n                \"music55\": \"Başlık Teması\",\n                \"music56\": \"Zıp Zıp Yarış\",\n                \"music8\": \"Köşeye Sıkıştın!\"\n            },\n            \"fam-rpg\": {\n                \"header\": \"RYO\",\n                \"music16\": \"Orman\",\n                \"music21\": \"Çatışma\",\n                \"music30\": \"Bekar Evi\",\n                \"music31\": \"Deniz Kenarı\",\n                \"music32\": \"Gölet\",\n                \"music33\": \"Bulutlar\",\n                \"music34\": \"Kasaba\"\n            },\n            \"fam-set1\": {\n                \"header\": \"Set 1\",\n                \"music42\": \"Zindan\",\n                \"music46\": \"Su\",\n                \"music7\": \"Yer Altı\",\n                \"music9\": \"Ana Dünya\"\n            },\n            \"fam-set2\": {\n                \"header\": \"Set 2\",\n                \"music15\": \"Boss\",\n                \"music25\": \"Yer Altı\",\n                \"music43\": \"Son Boss\",\n                \"music5\": \"Ana Dünya\"\n            },\n            \"fam-set3\": {\n                \"header\": \"Set 3\",\n                \"music1\": \"Ana Dünya\",\n                \"music2\": \"Gökyüzü\",\n                \"music3\": \"Zindan\",\n                \"music4\": \"Yer Altı\",\n                \"music47\": \"Su\",\n                \"music54\": \"Dolaşan Düşman\",\n                \"music6\": \"Boss\"\n            },\n            \"fam-space\": {\n                \"header\": \"Uzay\",\n                \"music11\": \"Kızıl Bataklık\",\n                \"music12\": \"Krater\",\n                \"music22\": \"Uzay Şarj\",\n                \"music44\": \"Eşya Odası\",\n                \"music45\": \"Son Boss\"\n            },\n            \"fam-world\": {\n                \"header\": \"Dünya\",\n                \"music10\": \"Ana Dünya\",\n                \"music17\": \"Malikane\",\n                \"music28\": \"Gökyüzü\",\n                \"music29\": \"Mağara\",\n                \"music41\": \"Zindan\",\n                \"music48\": \"Su\",\n                \"music51\": \"Boss\"\n            }\n        },\n        \"npc\": {\n            \"fam-blocks2\": \"Blok\",\n            \"fam-blocks3\": \"Blok\",\n            \"fam-boss1\": \"Boss\",\n            \"fam-boss2\": \"Boss\",\n            \"fam-boss3\": \"Boss\",\n            \"fam-boss4\": \"Boss\",\n            \"fam-boss5\": \"Boss\",\n            \"fam-char3\": \"Karakter\",\n            \"fam-char5\": \"Karakter\",\n            \"fam-ckpt4\": \"KntrlNkts\",\n            \"fam-enemies1\": \"Düşmanlar\",\n            \"fam-enemies2\": \"Düşmanlar\",\n            \"fam-enemies3\": \"Düşmanlar\",\n            \"fam-enemies4\": \"Düşmanlar\",\n            \"fam-enemies5\": \"Düşmanlar\",\n            \"fam-exit2\": \"Çıkış\",\n            \"fam-exit3\": \"Çıkış\",\n            \"fam-exit4\": \"Çıkış\",\n            \"fam-items1\": \"Eşyalar\",\n            \"fam-items2\": \"Eşyalar\",\n            \"fam-items3\": \"Eşyalar\",\n            \"fam-items4\": \"Eşyalar\",\n            \"fam-items5\": \"Eşyalar\",\n            \"fam-pet4\": \"Hayvan\",\n            \"fam-plant3\": \"Bitki\",\n            \"fam-plat1\": \"Platform\",\n            \"fam-plat3\": \"Platform\",\n            \"fam-plat4\": \"Platform\",\n            \"fam-shell1\": \"Kabuk\",\n            \"fam-shell3\": \"Kabuk\",\n            \"fam-sign4\": \"Tabela\",\n            \"fam-switch5\": \"Şalter\",\n            \"fam-veg2\": \"Sebze\",\n            \"fam-vine1\": \"Sarmaşık\",\n            \"fam-vine2\": \"Sarmaşık\",\n            \"fam-vine3\": \"Sarmaşık\",\n            \"fam-vine4\": \"Sarmaşık\",\n            \"fam-warp2\": \"Işınlanma\"\n        },\n        \"sound\": {\n            \"auto\": {\n                \"sound0\": \"Hiç\"\n            },\n            \"fam-boss\": {\n                \"header\": \"Boss\",\n                \"sound38\": \"Kuş Tükürüğü\",\n                \"sound39\": \"Kuş Vuruldu\",\n                \"sound41\": \"Kuş Yenildi\",\n                \"sound44\": \"Kaplumbağa Öldü\",\n                \"sound62\": \"Kurbağa Baloncuğu\",\n                \"sound63\": \"Kurbağa Öldü\",\n                \"sound67\": \"Cam Kırıldı\",\n                \"sound68\": \"Uzay Boss Vuruldu\",\n                \"sound69\": \"Uzay Boss Ağlama\",\n                \"sound70\": \"Uzay Boss Öldü\"\n            },\n            \"fam-hero\": {\n                \"header\": \"Kahraman\",\n                \"sound53\": \"Kahraman NPC Öldü\",\n                \"sound77\": \"Kahraman Bıçak\",\n                \"sound78\": \"Kahraman Hasar\",\n                \"sound79\": \"Kalp Al\",\n                \"sound80\": \"Kahraman Öldü\",\n                \"sound81\": \"Mücevher Al\",\n                \"sound82\": \"Kahraman Ateş\",\n                \"sound83\": \"Kahraman Eşya\",\n                \"sound84\": \"Anahtar Al\",\n                \"sound85\": \"Kalkan Bloklama\",\n                \"sound86\": \"Hermes Botları\",\n                \"sound87\": \"Uçmak\",\n                \"sound88\": \"Çimen Biç\"\n            },\n            \"fam-item\": {\n                \"header\": \"Eşya\",\n                \"sound14\": \"Para\",\n                \"sound16\": \"Lav\",\n                \"sound22\": \"Mermi\",\n                \"sound3\": \"Bloğa Vuruldu\",\n                \"sound32\": \"Şalter\",\n                \"sound37\": \"Parçalayıcı\",\n                \"sound4\": \"Blok Kırıldı\",\n                \"sound42\": \"Büyük Ateş Topu\",\n                \"sound43\": \"Havai Fişek\",\n                \"sound51\": \"Yumurta Kırılması\",\n                \"sound56\": \"Yüzük\",\n                \"sound57\": \"İskelet\",\n                \"sound58\": \"Checkpoint\",\n                \"sound59\": \"Toplanabilir\",\n                \"sound61\": \"Lav Canavarı\",\n                \"sound64\": \"Uzay Bloğa Vuruldu\",\n                \"sound65\": \"Uzay NPC Öldü\",\n                \"sound66\": \"Uzay NPC Hasar\",\n                \"sound7\": \"Powerup\",\n                \"sound74\": \"Testere\",\n                \"sound9\": \"Kabuğa Tekme\"\n            },\n            \"fam-jingle\": {\n                \"header\": \"Tını\",\n                \"sound19\": \"Rulet Çıkışı\",\n                \"sound20\": \"Boss Yenme\",\n                \"sound21\": \"Zinden Bitirme\",\n                \"sound31\": \"Anahtar Çıkışı\",\n                \"sound40\": \"Top Çıkışı\",\n                \"sound45\": \"Oyun Bitirme\",\n                \"sound52\": \"Yıldız Çıkışı\",\n                \"sound60\": \"Direk Çıkışı\",\n                \"sound8\": \"Oyuncu Öldü\"\n            },\n            \"fam-pet\": {\n                \"header\": \"Hayvan\",\n                \"sound36\": \"Hayvan Ezmek\",\n                \"sound48\": \"Hayvan Binmek\",\n                \"sound49\": \"Hayvan Hasar\",\n                \"sound50\": \"Hayvan Dili\",\n                \"sound55\": \"Hayvan Yutmak\"\n            },\n            \"fam-player\": {\n                \"header\": \"Oyuncu\",\n                \"sound1\": \"Atlamak\",\n                \"sound10\": \"Patinaj\",\n                \"sound12\": \"Eyşa Almak\",\n                \"sound15\": \"1up\",\n                \"sound17\": \"Işınlanma\",\n                \"sound18\": \"Ateş Topu\",\n                \"sound2\": \"Ezilme\",\n                \"sound23\": \"Tutmak\",\n                \"sound24\": \"Fırlamak\",\n                \"sound25\": \"Çekiç Fırlatmak\",\n                \"sound33\": \"Kuyruk\",\n                \"sound34\": \"Rakun\",\n                \"sound35\": \"Bot Kaybetmek\",\n                \"sound46\": \"Kapı\",\n                \"sound5\": \"Küçülmek\",\n                \"sound54\": \"Oyuncu Öldü 2\",\n                \"sound6\": \"Büyümek\",\n                \"sound71\": \"Tırmanmak\",\n                \"sound72\": \"Yüzmek\",\n                \"sound73\": \"Hafif Tutmak\",\n                \"sound75\": \"Sebze Fırlatmak\",\n                \"sound76\": \"Kalp Kaybetmek\"\n            },\n            \"fam-ui\": {\n                \"header\": \"Arayüz\",\n                \"sound11\": \"Eşya Bırakmak\",\n                \"sound13\": \"Kamera\",\n                \"sound26\": \"Kaymak\",\n                \"sound29\": \"Yap\",\n                \"sound30\": \"Durdurmak\",\n                \"sound47\": \"Mesaj\"\n            },\n            \"fam-world\": {\n                \"header\": \"Dünya\",\n                \"sound27\": \"Yeni Yol\",\n                \"sound28\": \"Seviye Seçmek\"\n            }\n        },\n        \"tile\": {\n            \"fam-clouds4\": \"Bulutlar\",\n            \"fam-desert1\": \"Toz\",\n            \"fam-dirt2\": \"Solmuş Çimen\",\n            \"fam-granite3\": \"Granit\",\n            \"fam-grass1\": \"Eski Çimen\",\n            \"fam-grass2\": \"Mutlu Çimen\",\n            \"fam-lava3\": \"Lav\",\n            \"fam-lilac4\": \"Lila duvar\",\n            \"fam-marble3\": \"Mermer\",\n            \"fam-snow1\": \"Eski Kar\",\n            \"fam-stonewall3\": \"Taş Duvar\",\n            \"fam-wall2\": \"Kum Duvar\"\n        },\n        \"worldMusic\": {\n            \"auto\": {\n                \"wmusic0\": \"Hiç\"\n            },\n            \"fam-new\": {\n                \"header\": \"Yeni\",\n                \"wmusic5\": \"Tema\"\n            },\n            \"fam-set3\": {\n                \"header\": \"Set 3\",\n                \"wmusic1\": \"Dünya 1\",\n                \"wmusic10\": \"Dünya 6\",\n                \"wmusic11\": \"Dünya 5\",\n                \"wmusic2\": \"Dünya 4\",\n                \"wmusic3\": \"Dünya 7\",\n                \"wmusic6\": \"Dünya 2\",\n                \"wmusic8\": \"Dünya 3\",\n                \"wmusic9\": \"Dünya 8\"\n            },\n            \"fam-world\": {\n                \"header\": \"Dünya\",\n                \"wmusic12\": \"Özel\",\n                \"wmusic13\": \"Zindan\",\n                \"wmusic14\": \"Gökyüzü\",\n                \"wmusic15\": \"Ada\",\n                \"wmusic16\": \"Mağara\",\n                \"wmusic4\": \"Tema\",\n                \"wmusic7\": \"Orman\"\n            }\n        }\n    },\n    \"languageName\": \"Türkçe\",\n    \"objects\": {\n        \"wordFails\": \"HATALAR\",\n        \"wordStarAccusativeDualOrCounter\": \"\",\n        \"wordStarAccusativePlural\": \"Yıldızlar\",\n        \"wordStarAccusativeSingular\": \"Yıldız\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/assets_uk.json",
    "content": "{\n    \"character\": {\n        \"name2\": \"\",\n        \"name5\": \"\",\n        \"name3\": \"\",\n        \"name1\": \"\",\n        \"name4\": \"\"\n    },\n    \"editor\": {\n        \"worldMusic\": {\n            \"fam-set3\": {\n                \"wmusic11\": \"\",\n                \"wmusic10\": \"\",\n                \"wmusic2\": \"\",\n                \"wmusic1\": \"\",\n                \"wmusic9\": \"\",\n                \"header\": \"\",\n                \"wmusic3\": \"\",\n                \"wmusic8\": \"\",\n                \"wmusic6\": \"\"\n            },\n            \"fam-world\": {\n                \"wmusic13\": \"\",\n                \"wmusic4\": \"\",\n                \"header\": \"\",\n                \"wmusic12\": \"\",\n                \"wmusic16\": \"\",\n                \"wmusic14\": \"\",\n                \"wmusic15\": \"\",\n                \"wmusic7\": \"\"\n            },\n            \"auto\": {\n                \"wmusic0\": \"\"\n            },\n            \"fam-new\": {\n                \"header\": \"\",\n                \"wmusic5\": \"\"\n            }\n        },\n        \"music\": {\n            \"fam-space\": {\n                \"music11\": \"\",\n                \"music22\": \"\",\n                \"header\": \"\",\n                \"music45\": \"\",\n                \"music44\": \"\",\n                \"music12\": \"\"\n            },\n            \"fam-64\": {\n                \"music50\": \"\",\n                \"header\": \"\",\n                \"music14\": \"\",\n                \"music35\": \"\",\n                \"music26\": \"\",\n                \"music36\": \"\",\n                \"music49\": \"\",\n                \"music27\": \"\"\n            },\n            \"fam-misc\": {\n                \"music55\": \"\",\n                \"music56\": \"\",\n                \"music37\": \"\",\n                \"music38\": \"\",\n                \"music23\": \"\",\n                \"music18\": \"\",\n                \"music20\": \"\",\n                \"header\": \"\",\n                \"music8\": \"\",\n                \"music13\": \"\"\n            },\n            \"fam-rpg\": {\n                \"music33\": \"\",\n                \"music30\": \"\",\n                \"music31\": \"\",\n                \"header\": \"\",\n                \"music34\": \"\",\n                \"music21\": \"\",\n                \"music16\": \"\",\n                \"music32\": \"\"\n            },\n            \"fam-set2\": {\n                \"music43\": \"\",\n                \"music15\": \"\",\n                \"header\": \"\",\n                \"music5\": \"\",\n                \"music25\": \"\"\n            },\n            \"fam-world\": {\n                \"music41\": \"\",\n                \"music10\": \"\",\n                \"header\": \"\",\n                \"music28\": \"\",\n                \"music51\": \"\",\n                \"music48\": \"\",\n                \"music29\": \"\",\n                \"music17\": \"\"\n            },\n            \"fam-set3\": {\n                \"music2\": \"\",\n                \"music3\": \"\",\n                \"music1\": \"\",\n                \"music6\": \"\",\n                \"music54\": \"\",\n                \"header\": \"\",\n                \"music4\": \"\",\n                \"music47\": \"\"\n            },\n            \"fam-set1\": {\n                \"header\": \"\",\n                \"music9\": \"\",\n                \"music46\": \"\",\n                \"music42\": \"\",\n                \"music7\": \"\"\n            },\n            \"fam-fight\": {\n                \"music53\": \"\",\n                \"music39\": \"\",\n                \"music19\": \"\",\n                \"header\": \"\",\n                \"music40\": \"\",\n                \"music52\": \"\"\n            },\n            \"auto\": {\n                \"music0\": \"\",\n                \"music24\": \"\"\n            }\n        },\n        \"block\": {\n            \"fam-steampunk3\": \"\",\n            \"fam-cave4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-sized1\": \"\",\n            \"fam-pipes3\": \"\",\n            \"fam-pipes1\": \"\",\n            \"fam-pipes5-3\": \"\",\n            \"fam-pipes4\": \"\",\n            \"fam-desert3\": \"\",\n            \"fam-overworld2\": \"\",\n            \"fam-wood2\": \"\",\n            \"fam-underground2\": \"\",\n            \"fam-overworld1\": \"\",\n            \"fam-dungeon3\": \"\",\n            \"fam-hurts3\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-dungeon1\": \"\",\n            \"fam-sized4\": \"\",\n            \"fam-bonus3\": \"\",\n            \"fam-grass4\": \"\",\n            \"fam-mansion4\": \"\",\n            \"fam-special2\": \"\",\n            \"fam-switch4\": \"\",\n            \"fam-pipes5-2\": \"\",\n            \"fam-bricks5\": \"\",\n            \"fam-special4\": \"\",\n            \"fam-woods4\": \"\",\n            \"fam-ruins5\": \"\",\n            \"fam-grass3\": \"\",\n            \"fam-misc2\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-misc3\": \"\",\n            \"fam-metal5-3\": \"\",\n            \"fam-house3\": \"\",\n            \"fam-special3\": \"\",\n            \"fam-bonus4\": \"\",\n            \"fam-underground1\": \"\",\n            \"fam-cave3\": \"\",\n            \"fam-wood3\": \"\",\n            \"fam-sized3\": \"\",\n            \"fam-lava4\": \"\",\n            \"fam-metal5-1\": \"\",\n            \"fam-misc1\": \"\",\n            \"fam-castle4\": \"\",\n            \"fam-special1\": \"\",\n            \"fam-sized2\": \"\",\n            \"fam-castle2\": \"\"\n        },\n        \"bgo\": {\n            \"fam-ghost4\": \"\",\n            \"fam-key4\": \"\",\n            \"fam-misc4\": \"\",\n            \"fam-sandbg3\": \"\",\n            \"fam-water3\": \"\",\n            \"fam-water4\": \"\",\n            \"fam-water1\": \"\",\n            \"fam-tiles3\": \"\",\n            \"fam-temple5\": \"\",\n            \"fam-art2\": \"\",\n            \"fam-structures3\": \"\",\n            \"fam-plants2\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-water2\": \"\",\n            \"fam-plants4\": \"\",\n            \"fam-bars4\": \"\",\n            \"fam-platform4\": \"\",\n            \"fam-clouds1\": \"\",\n            \"fam-plants1\": \"\",\n            \"fam-doors4\": \"\",\n            \"fam-plants3\": \"\",\n            \"fam-fences1\": \"\",\n            \"fam-hut1\": \"\",\n            \"fam-space5\": \"\",\n            \"fam-sandfg3\": \"\",\n            \"fam-fences4\": \"\",\n            \"fam-doors2\": \"\"\n        },\n        \"background2\": {\n            \"fam-set1\": {\n                \"bg2-10\": \"\",\n                \"bg2-7\": \"\",\n                \"bg2-9\": \"\",\n                \"bg2-8\": \"\",\n                \"bg2-50\": \"\",\n                \"header\": \"\",\n                \"bg2-41\": \"\",\n                \"bg2-51\": \"\"\n            },\n            \"fam-set2\": {\n                \"bg2-57\": \"\",\n                \"bg2-48\": \"\",\n                \"bg2-53\": \"\",\n                \"bg2-49\": \"\",\n                \"bg2-25\": \"\",\n                \"bg2-54\": \"\",\n                \"bg2-52\": \"\",\n                \"bg2-5\": \"\",\n                \"bg2-44\": \"\",\n                \"header\": \"\"\n            },\n            \"fam-set3\": {\n                \"bg2-13\": \"\",\n                \"bg2-21\": \"\",\n                \"bg2-4\": \"\",\n                \"bg2-15\": \"\",\n                \"bg2-36\": \"\",\n                \"header\": \"\",\n                \"bg2-6\": \"\",\n                \"bg2-3\": \"\",\n                \"bg2-39\": \"\",\n                \"bg2-20\": \"\",\n                \"bg2-38\": \"\",\n                \"bg2-26\": \"\",\n                \"bg2-1\": \"\",\n                \"bg2-24\": \"\",\n                \"bg2-23\": \"\",\n                \"bg2-27\": \"\",\n                \"bg2-22\": \"\",\n                \"bg2-37\": \"\",\n                \"bg2-35\": \"\",\n                \"bg2-2\": \"\",\n                \"bg2-14\": \"\",\n                \"bg2-17\": \"\",\n                \"bg2-56\": \"\"\n            },\n            \"fam-misc\": {\n                \"header\": \"\",\n                \"bg2-16\": \"\",\n                \"bg2-46\": \"\",\n                \"bg2-47\": \"\",\n                \"bg2-40\": \"\",\n                \"bg2-45\": \"\"\n            },\n            \"fam-set4\": {\n                \"bg2-28\": \"\",\n                \"bg2-32\": \"\",\n                \"header\": \"\",\n                \"bg2-33\": \"\",\n                \"bg2-30\": \"\",\n                \"bg2-55\": \"\",\n                \"bg2-58\": \"\",\n                \"bg2-11\": \"\",\n                \"bg2-12\": \"\",\n                \"bg2-19\": \"\",\n                \"bg2-18\": \"\",\n                \"bg2-42\": \"\",\n                \"bg2-29\": \"\",\n                \"bg2-43\": \"\",\n                \"bg2-31\": \"\",\n                \"bg2-34\": \"\"\n            },\n            \"auto\": {\n                \"bg2-0\": \"\"\n            }\n        },\n        \"npc\": {\n            \"fam-sign4\": \"\",\n            \"fam-plat3\": \"\",\n            \"fam-blocks2\": \"\",\n            \"fam-enemies5\": \"\",\n            \"fam-shell1\": \"\",\n            \"fam-boss5\": \"\",\n            \"fam-veg2\": \"\",\n            \"fam-boss1\": \"\",\n            \"fam-boss4\": \"\",\n            \"fam-char3\": \"\",\n            \"fam-warp2\": \"\",\n            \"fam-shell3\": \"\",\n            \"fam-plant3\": \"\",\n            \"fam-vine1\": \"\",\n            \"fam-items1\": \"\",\n            \"fam-exit3\": \"\",\n            \"fam-boss2\": \"\",\n            \"fam-enemies3\": \"\",\n            \"fam-exit4\": \"\",\n            \"fam-boss3\": \"\",\n            \"fam-vine3\": \"\",\n            \"fam-plat4\": \"\",\n            \"fam-plat1\": \"\",\n            \"fam-char5\": \"\",\n            \"fam-pet4\": \"\",\n            \"fam-items5\": \"\",\n            \"fam-items3\": \"\",\n            \"fam-blocks3\": \"\",\n            \"fam-enemies1\": \"\",\n            \"fam-enemies2\": \"\",\n            \"fam-ckpt4\": \"\",\n            \"fam-vine4\": \"\",\n            \"fam-switch5\": \"\",\n            \"fam-vine2\": \"\",\n            \"fam-exit2\": \"\",\n            \"fam-items2\": \"\",\n            \"fam-items4\": \"\",\n            \"fam-enemies4\": \"\"\n        },\n        \"sound\": {\n            \"fam-player\": {\n                \"sound54\": \"\",\n                \"sound24\": \"\",\n                \"sound73\": \"\",\n                \"sound12\": \"\",\n                \"sound34\": \"\",\n                \"sound75\": \"\",\n                \"sound72\": \"\",\n                \"header\": \"\",\n                \"sound25\": \"\",\n                \"sound18\": \"\",\n                \"sound35\": \"\",\n                \"sound46\": \"\",\n                \"sound15\": \"\",\n                \"sound76\": \"\",\n                \"sound5\": \"\",\n                \"sound71\": \"\",\n                \"sound33\": \"\",\n                \"sound1\": \"\",\n                \"sound23\": \"\",\n                \"sound17\": \"\",\n                \"sound10\": \"\",\n                \"sound6\": \"\",\n                \"sound2\": \"\"\n            },\n            \"auto\": {\n                \"sound0\": \"\"\n            },\n            \"fam-jingle\": {\n                \"sound8\": \"\",\n                \"sound20\": \"\",\n                \"sound60\": \"\",\n                \"sound45\": \"\",\n                \"header\": \"\",\n                \"sound52\": \"\",\n                \"sound40\": \"\",\n                \"sound31\": \"\",\n                \"sound19\": \"\",\n                \"sound21\": \"\"\n            },\n            \"fam-boss\": {\n                \"sound67\": \"\",\n                \"sound41\": \"\",\n                \"sound69\": \"\",\n                \"sound39\": \"\",\n                \"sound38\": \"\",\n                \"sound70\": \"\",\n                \"sound44\": \"\",\n                \"header\": \"\",\n                \"sound63\": \"\",\n                \"sound68\": \"\",\n                \"sound62\": \"\"\n            },\n            \"fam-hero\": {\n                \"sound83\": \"\",\n                \"sound84\": \"\",\n                \"sound88\": \"\",\n                \"sound86\": \"\",\n                \"sound87\": \"\",\n                \"header\": \"\",\n                \"sound82\": \"\",\n                \"sound80\": \"\",\n                \"sound77\": \"\",\n                \"sound81\": \"\",\n                \"sound85\": \"\",\n                \"sound53\": \"\",\n                \"sound78\": \"\",\n                \"sound79\": \"\"\n            },\n            \"fam-item\": {\n                \"sound4\": \"\",\n                \"sound14\": \"\",\n                \"sound22\": \"\",\n                \"sound16\": \"\",\n                \"sound61\": \"\",\n                \"sound74\": \"\",\n                \"sound9\": \"\",\n                \"sound64\": \"\",\n                \"sound3\": \"\",\n                \"sound56\": \"\",\n                \"sound7\": \"\",\n                \"sound59\": \"\",\n                \"header\": \"\",\n                \"sound65\": \"\",\n                \"sound32\": \"\",\n                \"sound51\": \"\",\n                \"sound57\": \"\",\n                \"sound66\": \"\",\n                \"sound43\": \"\",\n                \"sound42\": \"\",\n                \"sound58\": \"\",\n                \"sound37\": \"\"\n            },\n            \"fam-pet\": {\n                \"sound55\": \"\",\n                \"sound48\": \"\",\n                \"sound49\": \"\",\n                \"sound50\": \"\",\n                \"header\": \"\",\n                \"sound36\": \"\"\n            },\n            \"fam-ui\": {\n                \"sound11\": \"\",\n                \"header\": \"\",\n                \"sound13\": \"\",\n                \"sound30\": \"\",\n                \"sound47\": \"\",\n                \"sound29\": \"\",\n                \"sound26\": \"\"\n            },\n            \"fam-world\": {\n                \"header\": \"\",\n                \"sound28\": \"\",\n                \"sound27\": \"\"\n            }\n        },\n        \"tile\": {\n            \"fam-desert1\": \"\",\n            \"fam-lilac4\": \"\",\n            \"fam-grass1\": \"\",\n            \"fam-dirt2\": \"\",\n            \"fam-marble3\": \"\",\n            \"fam-granite3\": \"\",\n            \"fam-stonewall3\": \"\",\n            \"fam-clouds4\": \"\",\n            \"fam-grass2\": \"\",\n            \"fam-snow1\": \"\",\n            \"fam-wall2\": \"\",\n            \"fam-lava3\": \"\"\n        },\n        \"exit-codes\": {\n            \"7\": \"\",\n            \"5\": \"\",\n            \"any\": \"\",\n            \"6\": \"\",\n            \"3\": \"\",\n            \"4\": \"\",\n            \"8\": \"\",\n            \"1\": \"\",\n            \"2\": \"\",\n            \"none\": \"\",\n            \"9\": \"\"\n        }\n    },\n    \"objects\": {\n        \"wordStarAccusativeSingular\": \"\",\n        \"wordFails\": \"\",\n        \"wordStarAccusativePlural\": \"\",\n        \"wordStarAccusativeDualOrCounter\": \"\"\n    },\n    \"languageName\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/assets_zh-cn.json",
    "content": "{\n    \"character\": {\n        \"name1\": \"角色1\",\n        \"name2\": \"角色2\",\n        \"name3\": \"角色3\",\n        \"name4\": \"角色4\",\n        \"name5\": \"角色5\"\n    },\n    \"editor\": {\n        \"background2\": {\n            \"auto\": {\n                \"bg2-0\": \"无\"\n            },\n            \"fam-misc\": {\n                \"bg2-16\": \"宇宙中心\",\n                \"bg2-40\": \"隐秘矿洞\",\n                \"bg2-45\": \"太空沼泽\",\n                \"bg2-46\": \"宇宙飞船\",\n                \"bg2-47\": \"太空基地\",\n                \"header\": \"其他\"\n            },\n            \"fam-set1\": {\n                \"bg2-10\": \"山脉\",\n                \"bg2-41\": \"城堡\",\n                \"bg2-50\": \"蘑菇\",\n                \"bg2-51\": \"沙漠\",\n                \"bg2-7\": \"地下\",\n                \"bg2-8\": \"夜晚1\",\n                \"bg2-9\": \"夜晚2\",\n                \"header\": \"第一组\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"地下\",\n                \"bg2-44\": \"城堡\",\n                \"bg2-48\": \"天上遗迹\",\n                \"bg2-49\": \"夜晚山脉\",\n                \"bg2-5\": \"树林\",\n                \"bg2-52\": \"夜晚沙漠\",\n                \"bg2-53\": \"悬崖\",\n                \"bg2-54\": \"仓库\",\n                \"bg2-57\": \"地牢\",\n                \"header\": \"超马USA\"\n            },\n            \"fam-set3\": {\n                \"bg2-1\": \"砖块\",\n                \"bg2-13\": \"山丘\",\n                \"bg2-14\": \"沙漠\",\n                \"bg2-15\": \"地牢2\",\n                \"bg2-17\": \"飞船内部\",\n                \"bg2-2\": \"天空\",\n                \"bg2-20\": \"森林\",\n                \"bg2-21\": \"对战\",\n                \"bg2-22\": \"瀑布\",\n                \"bg2-23\": \"坦克\",\n                \"bg2-24\": \"最终Boss\",\n                \"bg2-26\": \"蘑菇小屋\",\n                \"bg2-27\": \"城堡\",\n                \"bg2-3\": \"城堡\",\n                \"bg2-35\": \"雪中树林\",\n                \"bg2-36\": \"天空2\",\n                \"bg2-37\": \"雪中山丘\",\n                \"bg2-38\": \"洞窟1\",\n                \"bg2-39\": \"洞窟2\",\n                \"bg2-4\": \"水管\",\n                \"bg2-56\": \"水下\",\n                \"bg2-6\": \"奖励\",\n                \"header\": \"超马三代\"\n            },\n            \"fam-set4\": {\n                \"bg2-11\": \"山脉1\",\n                \"bg2-12\": \"树林\",\n                \"bg2-18\": \"幽灵屋\",\n                \"bg2-19\": \"森林\",\n                \"bg2-28\": \"奖励\",\n                \"bg2-29\": \"夜晚\",\n                \"bg2-30\": \"洞窟\",\n                \"bg2-31\": \"天空\",\n                \"bg2-32\": \"山脉2\",\n                \"bg2-33\": \"山脉4\",\n                \"bg2-34\": \"山脉3\",\n                \"bg2-42\": \"城堡1\",\n                \"bg2-43\": \"城堡2\",\n                \"bg2-55\": \"水下\",\n                \"bg2-58\": \"夜晚沙漠\",\n                \"header\": \"超马世界\"\n            }\n        },\n        \"bgo\": {\n            \"fam-art2\": \"家具\",\n            \"fam-bars4\": \"栏杆\",\n            \"fam-clouds1\": \"云朵\",\n            \"fam-doors2\": \"门\",\n            \"fam-doors4\": \"门\",\n            \"fam-exit3\": \"出口\",\n            \"fam-fences1\": \"栅栏\",\n            \"fam-fences4\": \"网\",\n            \"fam-ghost4\": \"幽灵\",\n            \"fam-hut1\": \"小屋\",\n            \"fam-key4\": \"钥匙\",\n            \"fam-misc4\": \"其他\",\n            \"fam-plants1\": \"植物\",\n            \"fam-plants2\": \"植物\",\n            \"fam-plants3\": \"植物\",\n            \"fam-plants4\": \"植物\",\n            \"fam-platform4\": \"平台\",\n            \"fam-sandbg3\": \"流沙背景\",\n            \"fam-sandfg3\": \"前景\",\n            \"fam-space5\": \"宇宙生物\",\n            \"fam-structures3\": \"建筑\",\n            \"fam-temple5\": \"寺庙\",\n            \"fam-tiles3\": \"图块\",\n            \"fam-water1\": \"水\",\n            \"fam-water2\": \"水\",\n            \"fam-water3\": \"水\",\n            \"fam-water4\": \"水\"\n        },\n        \"block\": {\n            \"fam-bonus3\": \"奖励\",\n            \"fam-bonus4\": \"奖励\",\n            \"fam-bricks5\": \"砖块\",\n            \"fam-castle2\": \"城堡\",\n            \"fam-castle4\": \"城堡\",\n            \"fam-cave3\": \"洞窟\",\n            \"fam-cave4\": \"洞窟\",\n            \"fam-char5\": \"玩家\",\n            \"fam-clouds1\": \"云朵\",\n            \"fam-desert3\": \"沙漠\",\n            \"fam-dungeon1\": \"城堡\",\n            \"fam-dungeon3\": \"城堡\",\n            \"fam-grass3\": \"草地\",\n            \"fam-grass4\": \"草地\",\n            \"fam-house3\": \"小屋\",\n            \"fam-hurts3\": \"伤害\",\n            \"fam-lava4\": \"岩浆\",\n            \"fam-mansion4\": \"幽灵屋\",\n            \"fam-metal5-1\": \"金属\",\n            \"fam-metal5-3\": \"其他\",\n            \"fam-misc1\": \"其他\",\n            \"fam-misc2\": \"其他\",\n            \"fam-misc3\": \"其他\",\n            \"fam-misc4\": \"其他\",\n            \"fam-overworld1\": \"地面\",\n            \"fam-overworld2\": \"地面\",\n            \"fam-pipes1\": \"水管\",\n            \"fam-pipes3\": \"水管\",\n            \"fam-pipes4\": \"水管\",\n            \"fam-pipes5-2\": \"水管\",\n            \"fam-pipes5-3\": \"长水管\",\n            \"fam-ruins5\": \"遗迹\",\n            \"fam-sized1\": \"可调整\",\n            \"fam-sized2\": \"可调整\",\n            \"fam-sized3\": \"可调整\",\n            \"fam-sized4\": \"可调整\",\n            \"fam-special1\": \"特殊\",\n            \"fam-special2\": \"特殊\",\n            \"fam-special3\": \"特殊\",\n            \"fam-special4\": \"特殊\",\n            \"fam-steampunk3\": \"飞船\",\n            \"fam-switch4\": \"开关\",\n            \"fam-underground1\": \"地下\",\n            \"fam-underground2\": \"地下\",\n            \"fam-water3\": \"水下\",\n            \"fam-wood2\": \"木头\",\n            \"fam-wood3\": \"木头\",\n            \"fam-woods4\": \"森林\"\n        },\n        \"exit-codes\": {\n            \"1\": \"转盘\",\n            \"2\": \"16号水晶球\",\n            \"3\": \"离开\",\n            \"4\": \"钥匙\",\n            \"5\": \"41号水晶球\",\n            \"6\": \"传送\",\n            \"7\": \"收集星星\",\n            \"8\": \"终点杆\",\n            \"any\": \"任意\",\n            \"none\": \"无\",\n            \"9\": \"过关代码#9\"\n        },\n        \"music\": {\n            \"auto\": {\n                \"music0\": \"无音乐\",\n                \"music24\": \"自定义\"\n            },\n            \"fam-64\": {\n                \"header\": \"SM64\",\n                \"music14\": \"沙漠\",\n                \"music26\": \"城堡\",\n                \"music27\": \"主题曲\",\n                \"music35\": \"雪地\",\n                \"music36\": \"战斗\",\n                \"music49\": \"水下\",\n                \"music50\": \"洞窟\"\n            },\n            \"fam-fight\": {\n                \"header\": \"任斗\",\n                \"music19\": \"蒸汽机\",\n                \"music39\": \"寺庙\",\n                \"music40\": \"梅塔骑士的逆袭\",\n                \"music52\": \"地下\",\n                \"music53\": \"瓦路易吉弹珠台\"\n            },\n            \"fam-misc\": {\n                \"header\": \"其他\",\n                \"music13\": \"新大陆\",\n                \"music18\": \"沙滩\",\n                \"music20\": \"酷霸王的要塞\",\n                \"music23\": \"勇气森林\",\n                \"music37\": \"冰山\",\n                \"music38\": \"丛林村庄\",\n                \"music55\": \"标题画面\",\n                \"music56\": \"蘑菇峡谷\",\n                \"music8\": \"克尼利亚\"\n            },\n            \"fam-rpg\": {\n                \"header\": \"SMRPG\",\n                \"music16\": \"森林\",\n                \"music21\": \"战斗\",\n                \"music30\": \"小屋\",\n                \"music31\": \"海滨\",\n                \"music32\": \"池塘\",\n                \"music33\": \"天空\",\n                \"music34\": \"小镇\"\n            },\n            \"fam-set1\": {\n                \"header\": \"超马一代\",\n                \"music42\": \"城堡\",\n                \"music46\": \"水下\",\n                \"music7\": \"地下\",\n                \"music9\": \"地面\"\n            },\n            \"fam-set2\": {\n                \"header\": \"超马USA\",\n                \"music15\": \"战斗\",\n                \"music25\": \"地下\",\n                \"music43\": \"决战\",\n                \"music5\": \"地面\"\n            },\n            \"fam-set3\": {\n                \"header\": \"超马三代\",\n                \"music1\": \"地面\",\n                \"music2\": \"天空\",\n                \"music3\": \"城堡\",\n                \"music4\": \"地下\",\n                \"music47\": \"水下\",\n                \"music54\": \"遭遇敌人\",\n                \"music6\": \"战斗\"\n            },\n            \"fam-space\": {\n                \"header\": \"密特罗德\",\n                \"music11\": \"布林斯塔地下\",\n                \"music12\": \"火山口\",\n                \"music22\": \"布林斯塔\",\n                \"music44\": \"道具室\",\n                \"music45\": \"母脑\"\n            },\n            \"fam-world\": {\n                \"header\": \"超马世界\",\n                \"music10\": \"地面\",\n                \"music17\": \"幽灵屋\",\n                \"music28\": \"天空\",\n                \"music29\": \"洞窟\",\n                \"music41\": \"城堡\",\n                \"music48\": \"水下\",\n                \"music51\": \"战斗\"\n            }\n        },\n        \"npc\": {\n            \"fam-blocks2\": \"砖块\",\n            \"fam-blocks3\": \"砖块\",\n            \"fam-boss1\": \"Boss\",\n            \"fam-boss2\": \"Boss\",\n            \"fam-boss3\": \"Boss\",\n            \"fam-boss4\": \"Boss\",\n            \"fam-boss5\": \"Boss\",\n            \"fam-char3\": \"人物\",\n            \"fam-char5\": \"人物\",\n            \"fam-ckpt4\": \"检查点\",\n            \"fam-enemies1\": \"敌人\",\n            \"fam-enemies2\": \"敌人\",\n            \"fam-enemies3\": \"敌人\",\n            \"fam-enemies4\": \"敌人\",\n            \"fam-enemies5\": \"敌人\",\n            \"fam-exit2\": \"出口\",\n            \"fam-exit3\": \"出口\",\n            \"fam-exit4\": \"出口\",\n            \"fam-items1\": \"道具\",\n            \"fam-items2\": \"道具\",\n            \"fam-items3\": \"道具\",\n            \"fam-items4\": \"道具\",\n            \"fam-items5\": \"道具\",\n            \"fam-pet4\": \"坐骑\",\n            \"fam-plant3\": \"植物\",\n            \"fam-plat1\": \"平台\",\n            \"fam-plat3\": \"平台\",\n            \"fam-plat4\": \"平台\",\n            \"fam-shell1\": \"龟壳\",\n            \"fam-shell3\": \"龟壳\",\n            \"fam-sign4\": \"标牌\",\n            \"fam-switch5\": \"开关\",\n            \"fam-veg2\": \"蔬菜\",\n            \"fam-vine1\": \"藤蔓\",\n            \"fam-vine2\": \"藤蔓\",\n            \"fam-vine3\": \"藤蔓\",\n            \"fam-vine4\": \"藤蔓\",\n            \"fam-warp2\": \"传送门\"\n        },\n        \"sound\": {\n            \"auto\": {\n                \"sound0\": \"无\"\n            },\n            \"fam-boss\": {\n                \"header\": \"Boss\",\n                \"sound38\": \"鸟吐出物品\",\n                \"sound39\": \"撞击鸟\",\n                \"sound41\": \"鸟阵亡\",\n                \"sound44\": \"乌龟阵亡\",\n                \"sound62\": \"青蛙泡泡\",\n                \"sound63\": \"青蛙阵亡\",\n                \"sound67\": \"玻璃破碎\",\n                \"sound68\": \"太空Boss被撞击\",\n                \"sound69\": \"太空Boss哭泣\",\n                \"sound70\": \"太空Boss阵亡\"\n            },\n            \"fam-hero\": {\n                \"header\": \"英雄\",\n                \"sound53\": \"击杀英雄NPC\",\n                \"sound77\": \"英雄击剑\",\n                \"sound78\": \"英雄受伤\",\n                \"sound79\": \"获得体力\",\n                \"sound80\": \"英雄阵亡\",\n                \"sound81\": \"获得宝石\",\n                \"sound82\": \"英雄发射\",\n                \"sound83\": \"英雄获得道具\",\n                \"sound84\": \"获得钥匙\",\n                \"sound85\": \"护盾抵挡\",\n                \"sound86\": \"赫尔墨斯之靴\",\n                \"sound87\": \"飞行\",\n                \"sound88\": \"割草\"\n            },\n            \"fam-item\": {\n                \"header\": \"物品\",\n                \"sound14\": \"金币\",\n                \"sound16\": \"岩浆\",\n                \"sound22\": \"炮弹\",\n                \"sound3\": \"撞击砖块\",\n                \"sound32\": \"开关\",\n                \"sound37\": \"压饼机\",\n                \"sound4\": \"击碎砖块\",\n                \"sound42\": \"火焰\",\n                \"sound43\": \"焰火\",\n                \"sound51\": \"蛋孵化\",\n                \"sound56\": \"指环\",\n                \"sound57\": \"骷髅\",\n                \"sound58\": \"检查点\",\n                \"sound59\": \"可收集的物品\",\n                \"sound61\": \"岩浆生物\",\n                \"sound64\": \"撞击太空砖块\",\n                \"sound65\": \"击杀太空NPC\",\n                \"sound66\": \"太空NPC受伤\",\n                \"sound7\": \"力量提升\",\n                \"sound74\": \"链锯\",\n                \"sound9\": \"踢龟壳\"\n            },\n            \"fam-jingle\": {\n                \"header\": \"短曲\",\n                \"sound19\": \"转盘通关\",\n                \"sound20\": \"击败Boss\",\n                \"sound21\": \"城堡通关\",\n                \"sound31\": \"钥匙通关\",\n                \"sound40\": \"水晶球通关\",\n                \"sound45\": \"全游戏通关\",\n                \"sound52\": \"获得星星通关\",\n                \"sound60\": \"终点杆通关\",\n                \"sound8\": \"玩家阵亡\"\n            },\n            \"fam-pet\": {\n                \"header\": \"坐骑\",\n                \"sound36\": \"坐骑踩敌人\",\n                \"sound48\": \"骑上坐骑\",\n                \"sound49\": \"坐骑受伤\",\n                \"sound50\": \"坐骑伸出舌头\",\n                \"sound55\": \"坐骑吃东西\"\n            },\n            \"fam-player\": {\n                \"header\": \"玩家\",\n                \"sound1\": \"跳跃\",\n                \"sound10\": \"滑行\",\n                \"sound12\": \"获得道具\",\n                \"sound15\": \"生命+1\",\n                \"sound17\": \"传送\",\n                \"sound18\": \"火球发射\",\n                \"sound2\": \"踩敌人\",\n                \"sound23\": \"拿起道具\",\n                \"sound24\": \"弹簧\",\n                \"sound25\": \"投掷锤子\",\n                \"sound33\": \"尾巴\",\n                \"sound34\": \"浣熊\",\n                \"sound35\": \"靴子丢失\",\n                \"sound46\": \"门\",\n                \"sound5\": \"变小\",\n                \"sound54\": \"玩家阵亡2\",\n                \"sound6\": \"变大\",\n                \"sound71\": \"攀爬\",\n                \"sound72\": \"游泳\",\n                \"sound73\": \"轻拿道具\",\n                \"sound75\": \"投掷蔬菜\",\n                \"sound76\": \"损失体力\"\n            },\n            \"fam-ui\": {\n                \"header\": \"用户界面\",\n                \"sound11\": \"取出道具\",\n                \"sound13\": \"镜像\",\n                \"sound26\": \"选择\",\n                \"sound29\": \"确定\",\n                \"sound30\": \"暂停\",\n                \"sound47\": \"信息提示\"\n            },\n            \"fam-world\": {\n                \"header\": \"世界地图\",\n                \"sound27\": \"路线开启\",\n                \"sound28\": \"进入关卡\"\n            }\n        },\n        \"tile\": {\n            \"fam-clouds4\": \"云朵\",\n            \"fam-desert1\": \"灰尘\",\n            \"fam-dirt2\": \"枯草\",\n            \"fam-granite3\": \"花岗石\",\n            \"fam-grass1\": \"复古草地\",\n            \"fam-grass2\": \"快乐草地\",\n            \"fam-lava3\": \"岩浆\",\n            \"fam-lilac4\": \"紫色墙壁\",\n            \"fam-marble3\": \"大理石\",\n            \"fam-snow1\": \"复古雪地\",\n            \"fam-stonewall3\": \"石制墙壁\",\n            \"fam-wall2\": \"灰尘墙壁\"\n        },\n        \"worldMusic\": {\n            \"auto\": {\n                \"wmusic0\": \"无音乐\"\n            },\n            \"fam-new\": {\n                \"header\": \"新大陆\",\n                \"wmusic5\": \"主题曲\"\n            },\n            \"fam-set3\": {\n                \"header\": \"超马三代\",\n                \"wmusic1\": \"第一世界\",\n                \"wmusic10\": \"第六世界\",\n                \"wmusic11\": \"第五世界\",\n                \"wmusic2\": \"第四世界\",\n                \"wmusic3\": \"第七世界\",\n                \"wmusic6\": \"第二世界\",\n                \"wmusic8\": \"第三世界\",\n                \"wmusic9\": \"第八世界\"\n            },\n            \"fam-world\": {\n                \"header\": \"超马世界\",\n                \"wmusic12\": \"特殊\",\n                \"wmusic13\": \"城堡\",\n                \"wmusic14\": \"天空\",\n                \"wmusic15\": \"小岛\",\n                \"wmusic16\": \"洞窟\",\n                \"wmusic4\": \"主题曲\",\n                \"wmusic7\": \"森林\"\n            }\n        }\n    },\n    \"languageName\": \"简体中文\",\n    \"objects\": {\n        \"wordFails\": \"失败次数\",\n        \"wordStarAccusativeDualOrCounter\": \"颗\",\n        \"wordStarAccusativePlural\": \"星星\",\n        \"wordStarAccusativeSingular\": \"星星\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/assets_zh-tw.json",
    "content": "{\n    \"character\": {\n        \"name1\": \"角色1\",\n        \"name2\": \"角色2\",\n        \"name3\": \"角色3\",\n        \"name4\": \"角色4\",\n        \"name5\": \"角色5\"\n    },\n    \"editor\": {\n        \"background2\": {\n            \"auto\": {\n                \"bg2-0\": \"無\"\n            },\n            \"fam-misc\": {\n                \"bg2-16\": \"宇宙中心\",\n                \"bg2-40\": \"隱祕礦洞\",\n                \"bg2-45\": \"太空沼澤\",\n                \"bg2-46\": \"宇宙飛船\",\n                \"bg2-47\": \"太空基地\",\n                \"header\": \"其他\"\n            },\n            \"fam-set1\": {\n                \"bg2-10\": \"山脈\",\n                \"bg2-41\": \"城堡\",\n                \"bg2-50\": \"蘑菇\",\n                \"bg2-51\": \"沙漠\",\n                \"bg2-7\": \"地下\",\n                \"bg2-8\": \"夜晚1\",\n                \"bg2-9\": \"夜晚2\",\n                \"header\": \"超瑪一代\"\n            },\n            \"fam-set2\": {\n                \"bg2-25\": \"地下\",\n                \"bg2-44\": \"城堡\",\n                \"bg2-48\": \"天上遺蹟\",\n                \"bg2-49\": \"夜晚山脈\",\n                \"bg2-5\": \"樹林\",\n                \"bg2-52\": \"夜晚沙漠\",\n                \"bg2-53\": \"懸崖\",\n                \"bg2-54\": \"倉庫\",\n                \"bg2-57\": \"地牢\",\n                \"header\": \"超瑪USA\"\n            },\n            \"fam-set3\": {\n                \"bg2-1\": \"磚塊\",\n                \"bg2-13\": \"山丘\",\n                \"bg2-14\": \"沙漠\",\n                \"bg2-15\": \"地牢2\",\n                \"bg2-17\": \"輪船內部\",\n                \"bg2-2\": \"天空\",\n                \"bg2-20\": \"森林\",\n                \"bg2-21\": \"對戰\",\n                \"bg2-22\": \"瀑布\",\n                \"bg2-23\": \"坦克\",\n                \"bg2-24\": \"最終Boss\",\n                \"bg2-26\": \"蘑菇小屋\",\n                \"bg2-27\": \"城堡\",\n                \"bg2-3\": \"城堡\",\n                \"bg2-35\": \"雪中樹林\",\n                \"bg2-36\": \"天空2\",\n                \"bg2-37\": \"雪中山丘\",\n                \"bg2-38\": \"洞窟1\",\n                \"bg2-39\": \"洞窟2\",\n                \"bg2-4\": \"水管\",\n                \"bg2-56\": \"水下\",\n                \"bg2-6\": \"獎勵\",\n                \"header\": \"超瑪三代\"\n            },\n            \"fam-set4\": {\n                \"bg2-11\": \"山脈1\",\n                \"bg2-12\": \"樹林\",\n                \"bg2-18\": \"幽靈屋\",\n                \"bg2-19\": \"森林\",\n                \"bg2-28\": \"獎勵\",\n                \"bg2-29\": \"夜晚\",\n                \"bg2-30\": \"洞窟\",\n                \"bg2-31\": \"天空\",\n                \"bg2-32\": \"山脈2\",\n                \"bg2-33\": \"山脈4\",\n                \"bg2-34\": \"山脈3\",\n                \"bg2-42\": \"城堡1\",\n                \"bg2-43\": \"城堡2\",\n                \"bg2-55\": \"水下\",\n                \"bg2-58\": \"夜晚沙漠\",\n                \"header\": \"超瑪世界\"\n            }\n        },\n        \"bgo\": {\n            \"fam-art2\": \"傢俱\",\n            \"fam-bars4\": \"欄杆\",\n            \"fam-clouds1\": \"雲朵\",\n            \"fam-doors2\": \"門\",\n            \"fam-doors4\": \"門\",\n            \"fam-exit3\": \"出口\",\n            \"fam-fences1\": \"柵欄\",\n            \"fam-fences4\": \"網\",\n            \"fam-ghost4\": \"幽靈\",\n            \"fam-hut1\": \"小屋\",\n            \"fam-key4\": \"鑰匙\",\n            \"fam-misc4\": \"其他\",\n            \"fam-plants1\": \"植物\",\n            \"fam-plants2\": \"植物\",\n            \"fam-plants3\": \"植物\",\n            \"fam-plants4\": \"植物\",\n            \"fam-platform4\": \"平臺\",\n            \"fam-sandbg3\": \"流沙背景\",\n            \"fam-sandfg3\": \"前景\",\n            \"fam-space5\": \"宇宙生物\",\n            \"fam-structures3\": \"建築\",\n            \"fam-temple5\": \"寺廟\",\n            \"fam-tiles3\": \"圖塊\",\n            \"fam-water1\": \"水\",\n            \"fam-water2\": \"水\",\n            \"fam-water3\": \"水\",\n            \"fam-water4\": \"水\"\n        },\n        \"block\": {\n            \"fam-bonus3\": \"獎勵\",\n            \"fam-bonus4\": \"獎勵\",\n            \"fam-bricks5\": \"磚塊\",\n            \"fam-castle2\": \"城堡\",\n            \"fam-castle4\": \"城堡\",\n            \"fam-cave3\": \"洞窟\",\n            \"fam-cave4\": \"洞窟\",\n            \"fam-char5\": \"玩家\",\n            \"fam-clouds1\": \"雲朵\",\n            \"fam-desert3\": \"沙漠\",\n            \"fam-dungeon1\": \"城堡\",\n            \"fam-dungeon3\": \"城堡\",\n            \"fam-grass3\": \"草地\",\n            \"fam-grass4\": \"草地\",\n            \"fam-house3\": \"小屋\",\n            \"fam-hurts3\": \"傷害\",\n            \"fam-lava4\": \"岩漿\",\n            \"fam-mansion4\": \"幽靈屋\",\n            \"fam-metal5-1\": \"金屬\",\n            \"fam-metal5-3\": \"其他\",\n            \"fam-misc1\": \"其他\",\n            \"fam-misc2\": \"其他\",\n            \"fam-misc3\": \"其他\",\n            \"fam-misc4\": \"其他\",\n            \"fam-overworld1\": \"地面\",\n            \"fam-overworld2\": \"地面\",\n            \"fam-pipes1\": \"水管\",\n            \"fam-pipes3\": \"水管\",\n            \"fam-pipes4\": \"水管\",\n            \"fam-pipes5-2\": \"水管\",\n            \"fam-pipes5-3\": \"長水管\",\n            \"fam-ruins5\": \"遺蹟\",\n            \"fam-sized1\": \"可調整\",\n            \"fam-sized2\": \"可調整\",\n            \"fam-sized3\": \"可調整\",\n            \"fam-sized4\": \"可調整\",\n            \"fam-special1\": \"特殊\",\n            \"fam-special2\": \"特殊\",\n            \"fam-special3\": \"特殊\",\n            \"fam-special4\": \"特殊\",\n            \"fam-steampunk3\": \"飛船\",\n            \"fam-switch4\": \"開關\",\n            \"fam-underground1\": \"地下\",\n            \"fam-underground2\": \"地下\",\n            \"fam-water3\": \"水下\",\n            \"fam-wood2\": \"木頭\",\n            \"fam-wood3\": \"木頭\",\n            \"fam-woods4\": \"森林\"\n        },\n        \"exit-codes\": {\n            \"1\": \"轉盤\",\n            \"2\": \"16號水晶球\",\n            \"3\": \"離開\",\n            \"4\": \"鑰匙\",\n            \"5\": \"41號水晶球\",\n            \"6\": \"傳送\",\n            \"7\": \"收集星星\",\n            \"8\": \"終點杆\",\n            \"any\": \"任意\",\n            \"none\": \"無\",\n            \"9\": \"過關代碼#9\"\n        },\n        \"music\": {\n            \"auto\": {\n                \"music0\": \"無音樂\",\n                \"music24\": \"自定義\"\n            },\n            \"fam-64\": {\n                \"header\": \"64\",\n                \"music14\": \"沙漠\",\n                \"music26\": \"城堡\",\n                \"music27\": \"主題曲\",\n                \"music35\": \"雪地\",\n                \"music36\": \"戰鬥\",\n                \"music49\": \"水下\",\n                \"music50\": \"洞窟\"\n            },\n            \"fam-fight\": {\n                \"header\": \"任鬥\",\n                \"music19\": \"蒸汽機\",\n                \"music39\": \"寺廟\",\n                \"music40\": \"騎士\",\n                \"music52\": \"地下\",\n                \"music53\": \"彈珠台\"\n            },\n            \"fam-misc\": {\n                \"header\": \"其他\",\n                \"music13\": \"新大陸\",\n                \"music18\": \"沙灘\",\n                \"music20\": \"庫巴的要塞\",\n                \"music23\": \"勇氣森林\",\n                \"music37\": \"冰山\",\n                \"music38\": \"叢林村莊\",\n                \"music55\": \"標題畫面\",\n                \"music56\": \"蘑菇峽谷\",\n                \"music8\": \"絕境！\"\n            },\n            \"fam-rpg\": {\n                \"header\": \"RPG\",\n                \"music16\": \"森林\",\n                \"music21\": \"戰鬥\",\n                \"music30\": \"小屋\",\n                \"music31\": \"海濱\",\n                \"music32\": \"池塘\",\n                \"music33\": \"天空\",\n                \"music34\": \"小鎮\"\n            },\n            \"fam-set1\": {\n                \"header\": \"超瑪一代\",\n                \"music42\": \"城堡\",\n                \"music46\": \"水下\",\n                \"music7\": \"地下\",\n                \"music9\": \"地面\"\n            },\n            \"fam-set2\": {\n                \"header\": \"超瑪USA\",\n                \"music15\": \"戰鬥\",\n                \"music25\": \"地下\",\n                \"music43\": \"決戰\",\n                \"music5\": \"地面\"\n            },\n            \"fam-set3\": {\n                \"header\": \"超瑪三代\",\n                \"music1\": \"地面\",\n                \"music2\": \"天空\",\n                \"music3\": \"城堡\",\n                \"music4\": \"地下\",\n                \"music47\": \"水下\",\n                \"music54\": \"遭遇敵人\",\n                \"music6\": \"戰鬥\"\n            },\n            \"fam-space\": {\n                \"header\": \"超銀\",\n                \"music11\": \"紅色沼澤地\",\n                \"music12\": \"火山口\",\n                \"music22\": \"太空任務\",\n                \"music44\": \"道具室\",\n                \"music45\": \"決戰\"\n            },\n            \"fam-world\": {\n                \"header\": \"超瑪世界\",\n                \"music10\": \"地面\",\n                \"music17\": \"幽靈屋\",\n                \"music28\": \"天空\",\n                \"music29\": \"洞窟\",\n                \"music41\": \"城堡\",\n                \"music48\": \"水下\",\n                \"music51\": \"戰鬥\"\n            }\n        },\n        \"npc\": {\n            \"fam-blocks2\": \"磚塊\",\n            \"fam-blocks3\": \"磚塊\",\n            \"fam-boss1\": \"Boss\",\n            \"fam-boss2\": \"Boss\",\n            \"fam-boss3\": \"Boss\",\n            \"fam-boss4\": \"Boss\",\n            \"fam-boss5\": \"Boss\",\n            \"fam-char3\": \"人物\",\n            \"fam-char5\": \"人物\",\n            \"fam-ckpt4\": \"檢查點\",\n            \"fam-enemies1\": \"敵人\",\n            \"fam-enemies2\": \"敵人\",\n            \"fam-enemies3\": \"敵人\",\n            \"fam-enemies4\": \"敵人\",\n            \"fam-enemies5\": \"敵人\",\n            \"fam-exit2\": \"出口\",\n            \"fam-exit3\": \"出口\",\n            \"fam-exit4\": \"出口\",\n            \"fam-items1\": \"道具\",\n            \"fam-items2\": \"道具\",\n            \"fam-items3\": \"道具\",\n            \"fam-items4\": \"道具\",\n            \"fam-items5\": \"道具\",\n            \"fam-pet4\": \"坐騎\",\n            \"fam-plant3\": \"植物\",\n            \"fam-plat1\": \"平臺\",\n            \"fam-plat3\": \"平臺\",\n            \"fam-plat4\": \"平臺\",\n            \"fam-shell1\": \"龜殼\",\n            \"fam-shell3\": \"龜殼\",\n            \"fam-sign4\": \"標牌\",\n            \"fam-switch5\": \"開關\",\n            \"fam-veg2\": \"蔬菜\",\n            \"fam-vine1\": \"藤蔓\",\n            \"fam-vine2\": \"藤蔓\",\n            \"fam-vine3\": \"藤蔓\",\n            \"fam-vine4\": \"藤蔓\",\n            \"fam-warp2\": \"傳送門\"\n        },\n        \"sound\": {\n            \"auto\": {\n                \"sound0\": \"無\"\n            },\n            \"fam-boss\": {\n                \"header\": \"Boss\",\n                \"sound38\": \"鳥吐出物品\",\n                \"sound39\": \"撞擊鳥\",\n                \"sound41\": \"鳥陣亡\",\n                \"sound44\": \"烏龜陣亡\",\n                \"sound62\": \"青蛙泡泡\",\n                \"sound63\": \"青蛙陣亡\",\n                \"sound67\": \"玻璃破碎\",\n                \"sound68\": \"太空Boss被撞擊\",\n                \"sound69\": \"太空Boss哭泣\",\n                \"sound70\": \"太空Boss陣亡\"\n            },\n            \"fam-hero\": {\n                \"header\": \"英雄\",\n                \"sound53\": \"擊殺英雄NPC\",\n                \"sound77\": \"英雄擊劍\",\n                \"sound78\": \"英雄受傷\",\n                \"sound79\": \"獲得體力\",\n                \"sound80\": \"英雄陣亡\",\n                \"sound81\": \"獲得寶石\",\n                \"sound82\": \"英雄發射\",\n                \"sound83\": \"英雄獲得道具\",\n                \"sound84\": \"獲得鑰匙\",\n                \"sound85\": \"護盾抵擋\",\n                \"sound86\": \"赫爾墨斯之靴\",\n                \"sound87\": \"飛行\",\n                \"sound88\": \"割草\"\n            },\n            \"fam-item\": {\n                \"header\": \"物品\",\n                \"sound14\": \"金幣\",\n                \"sound16\": \"岩漿\",\n                \"sound22\": \"炮彈\",\n                \"sound3\": \"撞擊磚塊\",\n                \"sound32\": \"開關\",\n                \"sound37\": \"壓餅機\",\n                \"sound4\": \"擊碎磚塊\",\n                \"sound42\": \"火焰\",\n                \"sound43\": \"焰火\",\n                \"sound51\": \"蛋孵化\",\n                \"sound56\": \"指環\",\n                \"sound57\": \"骷髏\",\n                \"sound58\": \"檢查點\",\n                \"sound59\": \"可收集的物品\",\n                \"sound61\": \"岩漿生物\",\n                \"sound64\": \"撞擊太空磚塊\",\n                \"sound65\": \"擊殺太空NPC\",\n                \"sound66\": \"太空NPC受傷\",\n                \"sound7\": \"力量提升\",\n                \"sound74\": \"鏈鋸\",\n                \"sound9\": \"踢龜殼\"\n            },\n            \"fam-jingle\": {\n                \"header\": \"短曲\",\n                \"sound19\": \"轉盤通關\",\n                \"sound20\": \"擊敗Boss\",\n                \"sound21\": \"城堡通關\",\n                \"sound31\": \"鑰匙通關\",\n                \"sound40\": \"水晶球通關\",\n                \"sound45\": \"全遊戲通關\",\n                \"sound52\": \"獲得星星通關\",\n                \"sound60\": \"終點杆通關\",\n                \"sound8\": \"玩家陣亡\"\n            },\n            \"fam-pet\": {\n                \"header\": \"坐騎\",\n                \"sound36\": \"坐騎踩敵人\",\n                \"sound48\": \"騎上坐騎\",\n                \"sound49\": \"坐騎受傷\",\n                \"sound50\": \"坐騎伸出舌頭\",\n                \"sound55\": \"坐騎吃東西\"\n            },\n            \"fam-player\": {\n                \"header\": \"玩家\",\n                \"sound1\": \"跳躍\",\n                \"sound10\": \"滑行\",\n                \"sound12\": \"獲得道具\",\n                \"sound15\": \"生命+1\",\n                \"sound17\": \"傳送\",\n                \"sound18\": \"火球發射\",\n                \"sound2\": \"踩敵人\",\n                \"sound23\": \"拿起道具\",\n                \"sound24\": \"彈簧\",\n                \"sound25\": \"投擲錘子\",\n                \"sound33\": \"尾巴\",\n                \"sound34\": \"浣熊\",\n                \"sound35\": \"靴子丟失\",\n                \"sound46\": \"門\",\n                \"sound5\": \"變小\",\n                \"sound54\": \"玩家陣亡2\",\n                \"sound6\": \"變大\",\n                \"sound71\": \"攀爬\",\n                \"sound72\": \"游泳\",\n                \"sound73\": \"輕拿道具\",\n                \"sound75\": \"投擲蔬菜\",\n                \"sound76\": \"損失體力\"\n            },\n            \"fam-ui\": {\n                \"header\": \"用戶界面\",\n                \"sound11\": \"取出道具\",\n                \"sound13\": \"鏡像\",\n                \"sound26\": \"選擇\",\n                \"sound29\": \"確定\",\n                \"sound30\": \"暫停\",\n                \"sound47\": \"信息提示\"\n            },\n            \"fam-world\": {\n                \"header\": \"世界地圖\",\n                \"sound27\": \"路線開啟\",\n                \"sound28\": \"進入關卡\"\n            }\n        },\n        \"tile\": {\n            \"fam-clouds4\": \"雲朵\",\n            \"fam-desert1\": \"灰塵\",\n            \"fam-dirt2\": \"枯草\",\n            \"fam-granite3\": \"花崗石\",\n            \"fam-grass1\": \"復古草地\",\n            \"fam-grass2\": \"快樂草地\",\n            \"fam-lava3\": \"岩漿\",\n            \"fam-lilac4\": \"紫色牆壁\",\n            \"fam-marble3\": \"大理石\",\n            \"fam-snow1\": \"復古雪地\",\n            \"fam-stonewall3\": \"石制牆壁\",\n            \"fam-wall2\": \"灰塵牆壁\"\n        },\n        \"worldMusic\": {\n            \"auto\": {\n                \"wmusic0\": \"無音樂\"\n            },\n            \"fam-new\": {\n                \"header\": \"新大陸\",\n                \"wmusic5\": \"主題曲\"\n            },\n            \"fam-set3\": {\n                \"header\": \"超瑪三代\",\n                \"wmusic1\": \"第一世界\",\n                \"wmusic10\": \"第六世界\",\n                \"wmusic11\": \"第五世界\",\n                \"wmusic2\": \"第四世界\",\n                \"wmusic3\": \"第七世界\",\n                \"wmusic6\": \"第二世界\",\n                \"wmusic8\": \"第三世界\",\n                \"wmusic9\": \"第八世界\"\n            },\n            \"fam-world\": {\n                \"header\": \"世界\",\n                \"wmusic12\": \"特殊\",\n                \"wmusic13\": \"城堡\",\n                \"wmusic14\": \"天空\",\n                \"wmusic15\": \"小島\",\n                \"wmusic16\": \"洞窟\",\n                \"wmusic4\": \"主題曲\",\n                \"wmusic7\": \"森林\"\n            }\n        }\n    },\n    \"languageName\": \"繁體中文\",\n    \"objects\": {\n        \"wordFails\": \"失敗次數\",\n        \"wordStarAccusativeDualOrCounter\": \"顆\",\n        \"wordStarAccusativePlural\": \"星星\",\n        \"wordStarAccusativeSingular\": \"星星\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/thextech_bg.json",
    "content": "{\n    \"menu\": {\n        \"editor\": {\n            \"battles\": \"\",\n            \"errorMissingResources\": \"\",\n            \"newWorld\": \"\",\n            \"promptNewWorldName\": \"\",\n            \"makeFor\": \"\"\n        },\n        \"options\": {\n            \"advanced\": {\n                \"render\": {\n                    \"opengl11-tip\": \"\",\n                    \"hw\": \"\",\n                    \"_name\": \"\",\n                    \"opengles\": \"\",\n                    \"opengles-tip\": \"\",\n                    \"opengles11\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"sw\": \"\",\n                    \"opengles11-tip\": \"\",\n                    \"sdl\": \"\",\n                    \"opengl-tip\": \"\",\n                    \"opengl\": \"\",\n                    \"opengl11\": \"\"\n                },\n                \"_tooltip\": \"\",\n                \"log-level\": {\n                    \"warning\": \"\",\n                    \"debug\": \"\",\n                    \"none\": \"\",\n                    \"fatal\": \"\",\n                    \"info\": \"\",\n                    \"critical\": \"\",\n                    \"_name\": \"\"\n                },\n                \"webm-recording\": \"\",\n                \"internal-res-4p\": {\n                    \"default\": \"\",\n                    \"fhd\": \"\",\n                    \"qsmbx\": \"\",\n                    \"qhd\": \"\",\n                    \"_name\": \"\"\n                },\n                \"_header\": \"\",\n                \"record-gameplay-data\": \"\",\n                \"advanced-audio\": \"\",\n                \"advanced-video\": \"\",\n                \"audio-channels\": {\n                    \"mono\": \"\",\n                    \"_name\": \"\",\n                    \"stereo\": \"\"\n                },\n                \"audio-format\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"audio-sample-rate\": {\n                    \"22050\": \"\",\n                    \"16000\": \"\",\n                    \"32000\": \"\",\n                    \"11025\": \"\",\n                    \"44100\": \"\",\n                    \"48000\": \"\",\n                    \"_name\": \"\"\n                },\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"scale-down-textures\": {\n                    \"none-tip\": \"\",\n                    \"none\": \"\",\n                    \"all\": \"\",\n                    \"all-tip\": \"\",\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\",\n                    \"safe\": \"\",\n                    \"safe-tip\": \"\"\n                },\n                \"choose-assets-on-launch\": \"\",\n                \"audio-enable\": \"\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"video\": {\n                \"show-fails-counter\": \"\",\n                \"internal-res\": {\n                    \"3ds\": \"\",\n                    \"smbx-wide\": \"\",\n                    \"nds\": \"\",\n                    \"_tooltip\": \"\",\n                    \"vga\": \"\",\n                    \"hello\": \"\",\n                    \"snes\": \"\",\n                    \"hd\": \"\",\n                    \"dynamic\": \"\",\n                    \"gba\": \"\",\n                    \"smbx\": \"\",\n                    \"_name\": \"\"\n                },\n                \"battery-status\": {\n                    \"fullscreen-tip\": \"\",\n                    \"_name\": \"\",\n                    \"fullscreen\": \"\",\n                    \"off\": \"\",\n                    \"on\": \"\",\n                    \"low\": \"\",\n                    \"low-tip\": \"\"\n                },\n                \"scale-mode\": {\n                    \"2x\": \"\",\n                    \"1x\": \"\",\n                    \"0_5x\": \"\",\n                    \"full\": \"\",\n                    \"integer\": \"\",\n                    \"nearest\": \"\",\n                    \"center\": \"\",\n                    \"linear\": \"\",\n                    \"_name\": \"\",\n                    \"3x\": \"\"\n                },\n                \"show-fails-counter-tip\": \"\",\n                \"info-meta\": \"\",\n                \"info-run\": \"\",\n                \"info-game\": \"\",\n                \"enable-inter-level-fade-effect\": \"\",\n                \"fullscreen\": \"\",\n                \"effects\": \"\",\n                \"show-episode-title\": {\n                    \"off\": \"\",\n                    \"_name\": \"\",\n                    \"top\": \"\",\n                    \"bottom-tip\": \"\",\n                    \"bottom\": \"\",\n                    \"top-tip\": \"\"\n                },\n                \"_header\": \"\",\n                \"3d-compat-mode\": \"\",\n                \"show-playtime-counter\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"animated\": \"\",\n                    \"off\": \"\",\n                    \"off-tip\": \"\",\n                    \"animated-tip\": \"\",\n                    \"opaque\": \"\",\n                    \"transparent\": \"\"\n                },\n                \"show-medals-counter\": \"\",\n                \"show-medals-counter-tip\": \"\",\n                \"show-fps\": \"\",\n                \"video-system\": \"\",\n                \"show-screen-shake\": \"\",\n                \"world-map-stars-show\": \"\",\n                \"world-map-stars-show-tip\": \"\",\n                \"display-controllers\": \"\",\n                \"3d-compat-mode-tip\": \"\"\n            },\n            \"episode-options\": {\n                \"creator-compat\": {\n                    \"_tooltip\": \"\",\n                    \"disable\": \"\",\n                    \"enable\": \"\",\n                    \"file-only\": \"\",\n                    \"_name\": \"\"\n                },\n                \"playstyle\": {\n                    \"_name\": \"\",\n                    \"classic\": \"\",\n                    \"classic-tip\": \"\",\n                    \"modern-tip\": \"\",\n                    \"vanilla\": \"\",\n                    \"vanilla-tip\": \"\",\n                    \"modern\": \"\"\n                },\n                \"_header\": \"\",\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"main\": {\n                \"background-work\": \"\",\n                \"background-work-tip\": \"\",\n                \"four-screen-mode\": {\n                    \"_name\": \"\",\n                    \"split\": \"\",\n                    \"shared\": \"\"\n                },\n                \"multiplayer\": \"\",\n                \"two-screen-mode\": {\n                    \"smbx\": \"\",\n                    \"split\": \"\",\n                    \"shared\": \"\",\n                    \"_name\": \"\",\n                    \"topbottom\": \"\"\n                },\n                \"language\": \"\",\n                \"frame-skip\": \"\",\n                \"discord-rpc\": \"\",\n                \"timing\": \"\",\n                \"_header\": \"\",\n                \"vsync\": \"\",\n                \"unlimited-framerate\": \"\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"2\": \"\",\n                    \"1\": \"\",\n                    \"3\": \"\",\n                    \"_name\": \"\",\n                    \"0\": \"\"\n                }\n            },\n            \"restartEngine\": \"\",\n            \"audio\": {\n                \"sfx-pet-beat\": \"\",\n                \"audio-music-volume\": \"\",\n                \"audio-prefs\": \"\",\n                \"audio-sfx-volume\": \"\",\n                \"sfx-pet-beat-tip\": \"\",\n                \"sfx-spatial-audio\": \"\",\n                \"sfx-spatial-audio-tip\": \"\",\n                \"sfx-modern\": \"\",\n                \"_header\": \"\",\n                \"sfx-modern-tip\": \"\",\n                \"sfx-audio-fx\": \"\"\n            },\n            \"compatibility\": {\n                \"reset-all\": \"\",\n                \"autocode\": \"\",\n                \"_tooltip\": \"\",\n                \"bugfixes\": \"\",\n                \"features\": \"\",\n                \"_header\": \"\"\n            },\n            \"controls\": {\n                \"_header\": \"\"\n            },\n            \"view-credits\": {\n                \"_header\": \"\"\n            }\n        },\n        \"controls\": {\n            \"options\": {\n                \"batteryStatus\": \"\",\n                \"rumble\": \"\",\n                \"maxPlayers\": \"\",\n                \"textEntryStyle\": \"\"\n            },\n            \"hotkeys\": {\n                \"toggleHUD\": \"\",\n                \"fullscreen\": \"\",\n                \"recordGif\": \"\",\n                \"legacyPause\": \"\",\n                \"debugInfo\": \"\",\n                \"enterCheats\": \"\",\n                \"screenshot\": \"\",\n                \"vanillaCam\": \"\"\n            },\n            \"profile\": {\n                \"playerControls\": \"\",\n                \"deleteProfile\": \"\",\n                \"cursorControls\": \"\",\n                \"hotkeys\": \"\",\n                \"renameProfile\": \"\",\n                \"editorControls\": \"\"\n            },\n            \"tDS\": {\n                \"buttonA\": \"\",\n                \"tStick\": \"\",\n                \"dPad\": \"\",\n                \"buttonY\": \"\",\n                \"buttonSelect\": \"\",\n                \"buttonB\": \"\",\n                \"buttonZL\": \"\",\n                \"casePen\": \"\",\n                \"buttonX\": \"\",\n                \"buttonR\": \"\",\n                \"buttonL\": \"\",\n                \"buttonStart\": \"\",\n                \"cStick\": \"\",\n                \"buttonZR\": \"\"\n            },\n            \"touchscreen\": {\n                \"option\": {\n                    \"resetLayout\": \"\",\n                    \"scaleDPad\": \"\",\n                    \"showCodeButton\": \"\",\n                    \"feedbackLength\": \"\",\n                    \"layoutStyle\": \"\",\n                    \"feedbackStrength\": \"\",\n                    \"interfaceStyle\": \"\",\n                    \"holdRun\": \"\",\n                    \"sStartSpacing\": \"\",\n                    \"scaleFactor\": \"\",\n                    \"scaleButtons\": \"\"\n                },\n                \"style\": {\n                    \"XODA\": \"\",\n                    \"ABXY\": \"\",\n                    \"actions\": \"\"\n                },\n                \"layout\": {\n                    \"phoneOld\": \"\",\n                    \"standard\": \"\",\n                    \"tabletOld\": \"\",\n                    \"tinyOld\": \"\",\n                    \"tight\": \"\",\n                    \"longOld\": \"\",\n                    \"phabletOld\": \"\"\n                }\n            },\n            \"types\": {\n                \"keyboard\": \"\",\n                \"touchscreen\": \"\",\n                \"gamepad\": \"\",\n                \"oldJoystick\": \"\"\n            },\n            \"wii\": {\n                \"nunchuck\": {\n                    \"buttonC\": \"\",\n                    \"buttonZ\": \"\",\n                    \"prefixN\": \"\"\n                },\n                \"classic\": {\n                    \"rStick\": \"\",\n                    \"buttonLT\": \"\",\n                    \"buttonRT\": \"\",\n                    \"buttonZL\": \"\",\n                    \"lStick\": \"\",\n                    \"buttonZR\": \"\",\n                    \"buttonY\": \"\",\n                    \"buttonX\": \"\"\n                },\n                \"phraseNewNunchuck\": \"\",\n                \"wiimote\": {\n                    \"dPad\": \"\",\n                    \"caseIR\": \"\",\n                    \"shake\": \"\",\n                    \"button2\": \"\",\n                    \"buttonB\": \"\",\n                    \"buttonMinus\": \"\",\n                    \"buttonHome\": \"\",\n                    \"buttonPlus\": \"\",\n                    \"buttonA\": \"\",\n                    \"button1\": \"\"\n                },\n                \"typeWiimote\": \"\",\n                \"phraseNewClassic\": \"\",\n                \"typeNunchuck\": \"\",\n                \"typeClassic\": \"\",\n                \"typeGamecube\": \"\"\n            },\n            \"buttons\": {\n                \"altJump\": \"\",\n                \"left\": \"\",\n                \"jump\": \"\",\n                \"up\": \"\",\n                \"run\": \"\",\n                \"start\": \"\",\n                \"altRun\": \"\",\n                \"down\": \"\",\n                \"drop\": \"\",\n                \"right\": \"\"\n            },\n            \"caseInvalid\": \"\",\n            \"cursor\": {\n                \"primary\": \"\",\n                \"down\": \"\",\n                \"tertiary\": \"\",\n                \"right\": \"\",\n                \"secondary\": \"\",\n                \"up\": \"\",\n                \"left\": \"\"\n            },\n            \"editor\": {\n                \"scrollLeft\": \"\",\n                \"scrollDown\": \"\",\n                \"scrollRight\": \"\",\n                \"scrollUp\": \"\",\n                \"modeErase\": \"\",\n                \"switchScreens\": \"\",\n                \"testPlay\": \"\",\n                \"prevSection\": \"\",\n                \"nextSection\": \"\",\n                \"modeSelect\": \"\",\n                \"fastScroll\": \"\"\n            },\n            \"joystickSimpleEditor\": \"\",\n            \"phraseNewProfOldJoy\": \"\",\n            \"wordProfiles\": \"\",\n            \"controlsDeviceTypes\": \"\",\n            \"controlsReallyDeleteProfile\": \"\",\n            \"controlsNewProfile\": \"\",\n            \"controlsTitle\": \"\",\n            \"caseTouch\": \"\",\n            \"caseMouse\": \"\",\n            \"controlsConnected\": \"\",\n            \"controlsDeleteKey\": \"\",\n            \"wordButtons\": \"\",\n            \"controlsInUse\": \"\",\n            \"controlsNotInUse\": \"\"\n        },\n        \"battle\": {\n            \"errorNoLevels\": \"\"\n        },\n        \"abbrevMilliseconds\": \"\",\n        \"character\": {\n            \"selectCharacter\": \"\"\n        },\n        \"caseNone\": \"\",\n        \"main\": {\n            \"mainBattleGame\": \"\",\n            \"mainMultiplayerGame\": \"\",\n            \"mainExit\": \"\",\n            \"main1PlayerGame\": \"\",\n            \"mainEditor\": \"\",\n            \"mainPlayEpisode\": \"\",\n            \"mainOptions\": \"\"\n        },\n        \"game\": {\n            \"gameSlotContinue\": \"\",\n            \"gameSlotNew\": \"\",\n            \"gameBattleRandom\": \"\",\n            \"gameCopySave\": \"\",\n            \"gameEraseSlot\": \"\",\n            \"phraseTime\": \"\",\n            \"phraseScore\": \"\",\n            \"gameSourceSlot\": \"\",\n            \"gameNoEpisodesToPlay\": \"\",\n            \"gameEraseSave\": \"\",\n            \"gameNoBattleLevels\": \"\",\n            \"gameTargetSlot\": \"\",\n            \"warnEpCompat\": \"\"\n        },\n        \"wordPlayer\": \"\",\n        \"wordProfile\": \"\",\n        \"loading\": \"\",\n        \"wordOn\": \"\",\n        \"wordOff\": \"\",\n        \"wordNo\": \"\",\n        \"wordHide\": \"\",\n        \"wordBack\": \"\",\n        \"wordYes\": \"\",\n        \"wordResume\": \"\",\n        \"wordWaiting\": \"\",\n        \"wordShow\": \"\"\n    },\n    \"editor\": {\n        \"file\": {\n            \"convert\": {\n                \"descNew\": \"\"\n            },\n            \"formatModern\": \"\",\n            \"labelCurrentFile\": \"\",\n            \"sectionWorld\": \"\",\n            \"optionActionWithoutSave\": \"\",\n            \"confirmConfirmAction\": \"\",\n            \"actionClearWorld\": \"\",\n            \"actionClearLevel\": \"\",\n            \"confirmConvertFormatTo\": \"\",\n            \"commandOpen\": \"\",\n            \"commandSave\": \"\",\n            \"commandSaveAs\": \"\",\n            \"commandNew\": \"\",\n            \"formatLegacy\": \"\",\n            \"optionCancelConversion\": \"\",\n            \"sectionLevel\": \"\",\n            \"optionCancelAction\": \"\",\n            \"optionYesSaveThenAction\": \"\",\n            \"optionProceedWithConversion\": \"\",\n            \"actionOpen\": \"\",\n            \"confirmSaveBeforeAction\": \"\",\n            \"actionRevert\": \"\",\n            \"actionExit\": \"\",\n            \"labelFormat\": \"\"\n        },\n        \"labelSortLayer\": \"\",\n        \"labelSortOffset\": \"\",\n        \"layers\": {\n            \"abbrevAttLayer\": \"\",\n            \"deletion\": {\n                \"preserveLayerContents\": \"\",\n                \"confirmPreserve\": \"\",\n                \"confirmDelete\": \"\",\n                \"header\": \"\",\n                \"cancel\": \"\"\n            },\n            \"default\": \"\",\n            \"header\": \"\",\n            \"label\": \"\",\n            \"labelAttachedLayer\": \"\",\n            \"itemNewLayer\": \"\",\n            \"labelAttached\": \"\",\n            \"labelMoveLayer\": \"\",\n            \"promptLayerName\": \"\",\n            \"desc\": {\n                \"att\": \"\"\n            }\n        },\n        \"letterCoordY\": \"\",\n        \"letterCoordX\": \"\",\n        \"letterDown\": \"\",\n        \"npc\": {\n            \"ai\": {\n                \"jump\": \"\",\n                \"aiIs\": \"\",\n                \"headerCustomAi\": \"\",\n                \"target\": \"\",\n                \"swim\": \"\",\n                \"use1_0Ai\": \"\",\n                \"UD\": \"\",\n                \"leap\": \"\",\n                \"LR\": \"\"\n            },\n            \"gen\": {\n                \"header\": \"\",\n                \"effectIs\": \"\",\n                \"direction\": \"\",\n                \"effectShoot\": \"\",\n                \"effectWarp\": \"\"\n            },\n            \"inertNice\": \"\",\n            \"props\": {\n                \"active\": \"\",\n                \"facing\": \"\",\n                \"attachSurface\": \"\"\n            },\n            \"abbrevGen\": \"\",\n            \"stuckStop\": \"\",\n            \"inContainer\": \"\",\n            \"tooltipExpandSection\": \"\"\n        },\n        \"level\": {\n            \"gameStart\": \"\",\n            \"bigBG\": \"\",\n            \"alwaysVis\": \"\",\n            \"pathUnlocks\": \"\",\n            \"pathBG\": \"\",\n            \"levelName\": \"\",\n            \"startPos\": \"\"\n        },\n        \"phraseCountMore\": \"\",\n        \"phraseDelayIsMs\": \"\",\n        \"phraseSectionIndex\": \"\",\n        \"phraseTextOf\": \"\",\n        \"section\": {\n            \"offscreenExit\": \"\",\n            \"scroll\": \"\",\n            \"setBounds\": \"\",\n            \"vertWrap\": \"\",\n            \"noTurnBack\": \"\",\n            \"horizWrap\": \"\",\n            \"underwater\": \"\"\n        },\n        \"select\": {\n            \"sectionBlankPropBlank\": \"\",\n            \"soundForEventN\": \"\",\n            \"worldMusic\": \"\",\n            \"warpTransitEffect\": \"\",\n            \"pathBlankUnlock\": \"\",\n            \"allSectPropBlankForEventN\": \"\",\n            \"sectBlankPropBlankForEventN\": \"\"\n        },\n        \"testPlay\": {\n            \"char\": \"\",\n            \"magicHand\": \"\",\n            \"boot\": \"\",\n            \"power\": \"\",\n            \"pet\": \"\"\n        },\n        \"tooltip\": {\n            \"erase\": \"\",\n            \"blocks\": \"\",\n            \"layers\": \"\",\n            \"scenes\": \"\",\n            \"warps\": \"\",\n            \"file\": \"\",\n            \"area\": \"\",\n            \"music\": \"\",\n            \"settings\": \"\",\n            \"water\": \"\",\n            \"levels\": \"\",\n            \"NPCs\": \"\",\n            \"BGOs\": \"\",\n            \"events\": \"\",\n            \"paths\": \"\",\n            \"tiles\": \"\",\n            \"eraseAll\": \"\",\n            \"select\": \"\",\n            \"show\": \"\"\n        },\n        \"warp\": {\n            \"transit\": {\n                \"name3\": \"\",\n                \"name1\": \"\",\n                \"name2\": \"\",\n                \"name4\": \"\",\n                \"name5\": \"\",\n                \"name0\": \"\"\n            },\n            \"item\": \"\",\n            \"showStartScene\": \"\",\n            \"to\": \"\",\n            \"title\": \"\",\n            \"effect\": \"\",\n            \"cannonExit\": \"\",\n            \"toMap\": \"\",\n            \"twoWay\": \"\",\n            \"out\": \"\",\n            \"placing\": \"\",\n            \"needStarCount\": \"\",\n            \"speed\": \"\",\n            \"style\": {\n                \"blipInstant\": \"\",\n                \"pipe\": \"\",\n                \"door\": \"\",\n                \"style\": \"\",\n                \"portal\": \"\"\n            },\n            \"starLockMessage\": \"\",\n            \"target\": \"\",\n            \"in\": \"\",\n            \"lvlWarp\": \"\",\n            \"showStarCount\": \"\",\n            \"needKey\": \"\",\n            \"needFloor\": \"\",\n            \"ride\": \"\",\n            \"allow\": \"\",\n            \"dir\": \"\"\n        },\n        \"wordEvent\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\",\n            \"typeLabel\": \"\"\n        },\n        \"world\": {\n            \"allowChars\": \"\",\n            \"hubWorld\": \"\",\n            \"totalStars\": \"\",\n            \"retryOnFail\": \"\",\n            \"name\": \"\",\n            \"introLevel\": \"\",\n            \"phraseCreditIndex\": \"\"\n        },\n        \"water\": {\n            \"title\": \"\"\n        },\n        \"wordInstant\": \"\",\n        \"wordHeight\": \"\",\n        \"wordEnabled\": \"\",\n        \"wordWidth\": \"\",\n        \"events\": {\n            \"controlsForEventN\": \"\",\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"\",\n                \"changeSectionBoundsToCurrent\": \"\",\n                \"shouldEvent\": \"\"\n            },\n            \"deletion\": {\n                \"confirm\": \"\",\n                \"deletingEvent\": \"\",\n                \"cancel\": \"\"\n            },\n            \"header\": \"\",\n            \"headerTriggerEvent\": \"\",\n            \"letter\": {\n                \"destroy\": \"\",\n                \"hit\": \"\",\n                \"enter\": \"\",\n                \"activate\": \"\",\n                \"death\": \"\",\n                \"talk\": \"\",\n                \"layerClear\": \"\"\n            },\n            \"layers\": {\n                \"headerMove\": \"\",\n                \"headerHide\": \"\",\n                \"headerShow\": \"\",\n                \"headerToggle\": \"\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"\",\n                \"phraseAllSections\": \"\",\n                \"propBackground\": \"\",\n                \"propBounds\": \"\",\n                \"actionSet\": \"\",\n                \"actionReset\": \"\",\n                \"propMusic\": \"\"\n            },\n            \"props\": {\n                \"sound\": \"\",\n                \"autostart\": \"\",\n                \"layerSmoke\": \"\",\n                \"controls\": \"\",\n                \"endGame\": \"\"\n            },\n            \"desc\": {\n                \"destroy\": \"\",\n                \"death\": \"\",\n                \"phraseTriggersWhenTemplate\": \"\",\n                \"phraseTriggersAfterTemplate\": \"\",\n                \"activate\": \"\",\n                \"layerClear\": \"\",\n                \"enter\": \"\",\n                \"hit\": \"\",\n                \"talk\": \"\"\n            },\n            \"label\": {\n                \"death\": \"\",\n                \"talk\": \"\",\n                \"next\": \"\",\n                \"layerClear\": \"\",\n                \"enter\": \"\",\n                \"destroy\": \"\",\n                \"hit\": \"\",\n                \"activate\": \"\"\n            },\n            \"settingsForEvent\": \"\",\n            \"promptEventText\": \"\",\n            \"itemNewEvent\": \"\",\n            \"promptEventName\": \"\"\n        },\n        \"phraseGenericIndex\": \"\",\n        \"block\": {\n            \"letterWidth\": \"\",\n            \"slick\": \"\",\n            \"invis\": \"\",\n            \"letterHeight\": \"\",\n            \"canBreakTooltip\": \"\",\n            \"pickContents\": \"\",\n            \"inside\": \"\",\n            \"canBreak\": \"\"\n        },\n        \"browser\": {\n            \"newFile\": \"\",\n            \"askOverwriteFile\": \"\",\n            \"itemNewFile\": \"\",\n            \"saveFile\": \"\",\n            \"openFile\": \"\",\n            \"itemNewFolder\": \"\"\n        },\n        \"mapPos\": \"\",\n        \"wordMode\": \"\",\n        \"wordNPC\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\"\n        },\n        \"wordText\": \"\",\n        \"letterUp\": \"\",\n        \"letterLeft\": \"\",\n        \"letterRight\": \"\",\n        \"phraseWarpIndex\": \"\",\n        \"toggleMagicBlock\": \"\",\n        \"wordCoins\": \"\",\n        \"pageBlankOfBlank\": \"\",\n        \"phraseAreYouSure\": \"\",\n        \"phraseRadiusIndex\": \"\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"splitPressSelect_2\": \"\",\n            \"dropAddTitle\": \"\",\n            \"phraseDropMe\": \"\",\n            \"phrasePressAButton\": \"\",\n            \"phraseTestControls\": \"\",\n            \"phraseWaitingForInput\": \"\",\n            \"wordDisconnect\": \"\",\n            \"reconnectTitle\": \"\",\n            \"phraseTestProfile\": \"\",\n            \"phraseDropPX\": \"\",\n            \"phraseStartToResume\": \"\",\n            \"phraseStartToForceRes\": \"\",\n            \"phraseSetControls\": \"\",\n            \"phraseHoldStart\": \"\",\n            \"phraseChangeChar\": \"\",\n            \"phraseForceResume\": \"\",\n            \"splitPressSelect_1\": \"\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"\"\n        },\n        \"pause\": {\n            \"restartLevel\": \"\",\n            \"continue\": \"\",\n            \"saveAndContinue\": \"\",\n            \"saveAndQuit\": \"\",\n            \"returnToEditor\": \"\",\n            \"quit\": \"\",\n            \"enterCode\": \"\",\n            \"quitTesting\": \"\",\n            \"resetCheckpoints\": \"\",\n            \"playerSetup\": \"\"\n        },\n        \"hint\": {\n            \"no-lives-new\": \"\",\n            \"no-lives-old\": \"\",\n            \"char5-bombs\": \"\",\n            \"gray-bricks\": \"\",\n            \"rainbow-surf\": \"\",\n            \"heavy-duck\": \"\",\n            \"shoe-block\": \"\",\n            \"pound-altrun\": \"\",\n            \"pound-down\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"\"\n        },\n        \"error\": {\n            \"openFileFailed\": \"\",\n            \"warpNeedStarCount\": \"\",\n            \"errorInvalidEnterWarp\": \"\",\n            \"errorNoStartPoint\": \"\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"msgbox\": {\n            \"sysInfoTitle\": \"\",\n            \"sysInfoWarning\": \"\",\n            \"sysInfoError\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"languageName\": \"\",\n    \"outro\": {\n        \"nameVitalyNovichkov\": \"\",\n        \"originalBy\": \"\",\n        \"cppPortDevelopers\": \"\",\n        \"qualityControl\": \"\",\n        \"levelDesign\": \"\",\n        \"gameCredits\": \"\",\n        \"customSprites\": \"\",\n        \"specialThanks\": \"\",\n        \"engineCredits\": \"\",\n        \"nameAndrewSpinks\": \"\",\n        \"psVitaPortBy\": \"\"\n    },\n    \"pluralRules\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_de.json",
    "content": "{\n    \"languageName\": \"Deutsch\",\n    \"editor\": {\n        \"browser\": {\n            \"askOverwriteFile\": \"{0} überschreiben?\",\n            \"itemNewFile\": \"<Neue Datei>\",\n            \"itemNewFolder\": \"<Neuer Ordner>\",\n            \"newFile\": \"Neue Datei\",\n            \"openFile\": \"Datei öffnen\",\n            \"saveFile\": \"Datei speichern\"\n        },\n        \"events\": {\n            \"header\": \"Ereignisse:\",\n            \"itemNewEvent\": \"<Neues Ereignis>\",\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"Alle Sekt. Grenzen zur jetzigen ändern?\",\n                \"changeSectionBoundsToCurrent\": \"{0} Sekt. Grenzen zur Jetzigen ändern?\",\n                \"shouldEvent\": \"Sollte Event {0}\"\n            },\n            \"controlsForEventN\": \"Steuerung für Event {0}\",\n            \"deletion\": {\n                \"cancel\": \"Nein: Event nicht löschen\",\n                \"confirm\": \"Ja: Event löschen\",\n                \"deletingEvent\": \"Event {0} wird gelöscht\"\n            },\n            \"desc\": {\n                \"activate\": \"NPC betritt Bildschirm\",\n                \"death\": \"NPC stirbt\",\n                \"destroy\": \"Block wird zerstört\",\n                \"enter\": \"Warp wird betreten\",\n                \"hit\": \"Block wird getroffen\",\n                \"layerClear\": \"alles in der Objektebene ist verschwunden\",\n                \"phraseTriggersAfterTemplate\": \"{0} wird {1} ms nach Event {2} ausgelöst\",\n                \"phraseTriggersWhenTemplate\": \"{0} wird ausgelöst wenn {1}\",\n                \"talk\": \"Spieler redet mit NPC\"\n            },\n            \"headerTriggerEvent\": \"Auslöser:\",\n            \"label\": {\n                \"activate\": \"Aktivieren\",\n                \"death\": \"Tod\",\n                \"destroy\": \"Zerstören\",\n                \"enter\": \"Betreten\",\n                \"hit\": \"Treffer\",\n                \"layerClear\": \"Ebene leeren\",\n                \"next\": \"Weiter\",\n                \"talk\": \"Sprechen\"\n            },\n            \"layers\": {\n                \"headerHide\": \"Verstecken:\",\n                \"headerMove\": \"Bewegen:\",\n                \"headerShow\": \"Zeigen:\",\n                \"headerToggle\": \"Umschalten:\"\n            },\n            \"letter\": {\n                \"activate\": \"A:\",\n                \"death\": \"T:\",\n                \"destroy\": \"Z:\",\n                \"enter\": \"B:\",\n                \"hit\": \"G:\",\n                \"layerClear\": \"E:\",\n                \"talk\": \"S:\"\n            },\n            \"promptEventName\": \"Event Name\",\n            \"promptEventText\": \"Event Text\",\n            \"props\": {\n                \"controls\": \"Steuerung\",\n                \"endGame\": \"Spiel beenden\",\n                \"layerSmoke\": \"Rauch\",\n                \"autostart\": \"\",\n                \"sound\": \"\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"Behalten\",\n                \"actionReset\": \"Zurücksetzen\",\n                \"propBounds\": \"\",\n                \"propMusic\": \"\",\n                \"propBackground\": \"\",\n                \"actionSet\": \"\",\n                \"phraseAllSections\": \"\"\n            },\n            \"settingsForEvent\": \"\"\n        },\n        \"block\": {\n            \"canBreak\": \"Zerstörbar\",\n            \"canBreakTooltip\": \"Klassisch: Wird nach einem Treffer zerstört\",\n            \"inside\": \"Inhalt:\",\n            \"invis\": \"N. Sicht\",\n            \"pickContents\": \"Wähle Block Inhalt\",\n            \"slick\": \"Glatt\",\n            \"letterHeight\": \"H \",\n            \"letterWidth\": \"B \"\n        },\n        \"layers\": {\n            \"promptLayerName\": \"\",\n            \"label\": \"\",\n            \"deletion\": {\n                \"header\": \"\",\n                \"confirmDelete\": \"\",\n                \"preserveLayerContents\": \"\",\n                \"confirmPreserve\": \"\",\n                \"cancel\": \"\"\n            },\n            \"header\": \"\",\n            \"labelAttachedLayer\": \"\",\n            \"labelMoveLayer\": \"\",\n            \"desc\": {\n                \"att\": \"\"\n            },\n            \"default\": \"\",\n            \"itemNewLayer\": \"\",\n            \"abbrevAttLayer\": \"\",\n            \"labelAttached\": \"\"\n        },\n        \"warp\": {\n            \"needStarCount\": \"\",\n            \"item\": \"\",\n            \"lvlWarp\": \"\",\n            \"needKey\": \"\",\n            \"placing\": \"\",\n            \"to\": \"\",\n            \"showStartScene\": \"\",\n            \"twoWay\": \"\",\n            \"needFloor\": \"\",\n            \"style\": {\n                \"door\": \"\",\n                \"blipInstant\": \"\",\n                \"pipe\": \"\",\n                \"portal\": \"\",\n                \"style\": \"\"\n            },\n            \"transit\": {\n                \"name2\": \"\",\n                \"name1\": \"\",\n                \"name0\": \"\",\n                \"name3\": \"\",\n                \"name4\": \"\",\n                \"name5\": \"\"\n            },\n            \"dir\": \"\",\n            \"cannonExit\": \"\",\n            \"allow\": \"\",\n            \"in\": \"\",\n            \"out\": \"\",\n            \"starLockMessage\": \"\",\n            \"title\": \"\",\n            \"showStarCount\": \"\",\n            \"effect\": \"\",\n            \"target\": \"\",\n            \"speed\": \"\",\n            \"toMap\": \"\",\n            \"ride\": \"\"\n        },\n        \"letterRight\": \"\",\n        \"wordHeight\": \"\",\n        \"file\": {\n            \"commandSave\": \"\",\n            \"convert\": {\n                \"descNew\": \"\"\n            },\n            \"formatLegacy\": \"\",\n            \"commandOpen\": \"\",\n            \"labelFormat\": \"\",\n            \"actionOpen\": \"\",\n            \"optionCancelAction\": \"\",\n            \"commandSaveAs\": \"\",\n            \"actionRevert\": \"\",\n            \"optionYesSaveThenAction\": \"\",\n            \"optionActionWithoutSave\": \"\",\n            \"confirmConvertFormatTo\": \"\",\n            \"sectionWorld\": \"\",\n            \"commandNew\": \"\",\n            \"actionClearWorld\": \"\",\n            \"optionProceedWithConversion\": \"\",\n            \"confirmConfirmAction\": \"\",\n            \"confirmSaveBeforeAction\": \"\",\n            \"optionCancelConversion\": \"\",\n            \"actionClearLevel\": \"\",\n            \"actionExit\": \"\",\n            \"sectionLevel\": \"\",\n            \"labelCurrentFile\": \"\",\n            \"formatModern\": \"\"\n        },\n        \"wordWidth\": \"\",\n        \"select\": {\n            \"warpTransitEffect\": \"\",\n            \"soundForEventN\": \"\",\n            \"pathBlankUnlock\": \"\",\n            \"allSectPropBlankForEventN\": \"\",\n            \"worldMusic\": \"\",\n            \"sectBlankPropBlankForEventN\": \"\",\n            \"sectionBlankPropBlank\": \"\"\n        },\n        \"tooltip\": {\n            \"BGOs\": \"\",\n            \"events\": \"\",\n            \"eraseAll\": \"\",\n            \"music\": \"\",\n            \"erase\": \"\",\n            \"settings\": \"\",\n            \"NPCs\": \"\",\n            \"water\": \"\",\n            \"show\": \"\",\n            \"paths\": \"\",\n            \"tiles\": \"\",\n            \"levels\": \"\",\n            \"layers\": \"\",\n            \"warps\": \"\",\n            \"blocks\": \"\",\n            \"file\": \"\",\n            \"select\": \"\",\n            \"scenes\": \"\",\n            \"area\": \"\"\n        },\n        \"npc\": {\n            \"ai\": {\n                \"aiIs\": \"\",\n                \"target\": \"\",\n                \"swim\": \"\",\n                \"jump\": \"\",\n                \"leap\": \"\",\n                \"UD\": \"\",\n                \"headerCustomAi\": \"\",\n                \"use1_0Ai\": \"\",\n                \"LR\": \"\"\n            },\n            \"tooltipExpandSection\": \"\",\n            \"stuckStop\": \"\",\n            \"gen\": {\n                \"header\": \"\",\n                \"effectShoot\": \"\",\n                \"direction\": \"\",\n                \"effectWarp\": \"\",\n                \"effectIs\": \"\"\n            },\n            \"props\": {\n                \"attachSurface\": \"\",\n                \"facing\": \"\",\n                \"active\": \"\"\n            },\n            \"abbrevGen\": \"\",\n            \"inertNice\": \"\",\n            \"inContainer\": \"\"\n        },\n        \"letterLeft\": \"\",\n        \"level\": {\n            \"startPos\": \"\",\n            \"pathBG\": \"\",\n            \"pathUnlocks\": \"\",\n            \"levelName\": \"\",\n            \"gameStart\": \"\",\n            \"alwaysVis\": \"\",\n            \"bigBG\": \"\"\n        },\n        \"testPlay\": {\n            \"magicHand\": \"\",\n            \"power\": \"\",\n            \"char\": \"\",\n            \"pet\": \"\",\n            \"boot\": \"\"\n        },\n        \"wordInstant\": \"\",\n        \"letterDown\": \"\",\n        \"mapPos\": \"\",\n        \"world\": {\n            \"introLevel\": \"\",\n            \"hubWorld\": \"\",\n            \"name\": \"\",\n            \"phraseCreditIndex\": \"\",\n            \"totalStars\": \"\",\n            \"retryOnFail\": \"\",\n            \"allowChars\": \"\"\n        },\n        \"letterCoordX\": \"\",\n        \"water\": {\n            \"title\": \"\"\n        },\n        \"wordMode\": \"\",\n        \"phraseTextOf\": \"\",\n        \"wordEnabled\": \"\",\n        \"wordEvent\": {\n            \"nominative\": \"\",\n            \"typeLabel\": \"\",\n            \"genitive\": \"\"\n        },\n        \"phraseSectionIndex\": \"\",\n        \"wordCoins\": \"\",\n        \"section\": {\n            \"horizWrap\": \"\",\n            \"setBounds\": \"\",\n            \"noTurnBack\": \"\",\n            \"underwater\": \"\",\n            \"offscreenExit\": \"\",\n            \"vertWrap\": \"\",\n            \"scroll\": \"\"\n        },\n        \"phraseGenericIndex\": \"\",\n        \"letterUp\": \"\",\n        \"phraseCountMore\": \"\",\n        \"wordNPC\": {\n            \"nominative\": \"\",\n            \"genitive\": \"\"\n        },\n        \"phraseRadiusIndex\": \"\",\n        \"pageBlankOfBlank\": \"\",\n        \"phraseWarpIndex\": \"\",\n        \"labelSortLayer\": \"\",\n        \"phraseAreYouSure\": \"\",\n        \"toggleMagicBlock\": \"\",\n        \"phraseDelayIsMs\": \"\",\n        \"letterCoordY\": \"\",\n        \"wordText\": \"\",\n        \"labelSortOffset\": \"\"\n    },\n    \"menu\": {\n        \"controls\": {\n            \"touchscreen\": {\n                \"option\": {\n                    \"sStartSpacing\": \"\",\n                    \"scaleFactor\": \"\",\n                    \"resetLayout\": \"\",\n                    \"layoutStyle\": \"\",\n                    \"interfaceStyle\": \"\",\n                    \"feedbackStrength\": \"\",\n                    \"scaleButtons\": \"\",\n                    \"scaleDPad\": \"\",\n                    \"feedbackLength\": \"\",\n                    \"holdRun\": \"\",\n                    \"showCodeButton\": \"\"\n                },\n                \"layout\": {\n                    \"phabletOld\": \"\",\n                    \"longOld\": \"\",\n                    \"tight\": \"\",\n                    \"phoneOld\": \"\",\n                    \"standard\": \"\",\n                    \"tinyOld\": \"\",\n                    \"tabletOld\": \"\"\n                },\n                \"style\": {\n                    \"actions\": \"\",\n                    \"XODA\": \"\",\n                    \"ABXY\": \"\"\n                }\n            },\n            \"profile\": {\n                \"cursorControls\": \"\",\n                \"playerControls\": \"\",\n                \"renameProfile\": \"\",\n                \"hotkeys\": \"\",\n                \"deleteProfile\": \"\",\n                \"editorControls\": \"\"\n            },\n            \"wii\": {\n                \"typeClassic\": \"\",\n                \"typeNunchuck\": \"\",\n                \"wiimote\": {\n                    \"caseIR\": \"\",\n                    \"buttonPlus\": \"\",\n                    \"buttonMinus\": \"\",\n                    \"button1\": \"\",\n                    \"buttonHome\": \"\",\n                    \"buttonB\": \"\",\n                    \"shake\": \"\",\n                    \"button2\": \"\",\n                    \"buttonA\": \"\",\n                    \"dPad\": \"\"\n                },\n                \"classic\": {\n                    \"lStick\": \"\",\n                    \"buttonY\": \"\",\n                    \"buttonRT\": \"\",\n                    \"rStick\": \"\",\n                    \"buttonLT\": \"\",\n                    \"buttonX\": \"\",\n                    \"buttonZR\": \"\",\n                    \"buttonZL\": \"\"\n                },\n                \"phraseNewClassic\": \"\",\n                \"nunchuck\": {\n                    \"buttonC\": \"\",\n                    \"buttonZ\": \"\",\n                    \"prefixN\": \"\"\n                },\n                \"phraseNewNunchuck\": \"\",\n                \"typeGamecube\": \"\",\n                \"typeWiimote\": \"\"\n            },\n            \"tDS\": {\n                \"buttonY\": \"\",\n                \"cStick\": \"\",\n                \"buttonB\": \"\",\n                \"dPad\": \"\",\n                \"tStick\": \"\",\n                \"buttonZL\": \"\",\n                \"casePen\": \"\",\n                \"buttonStart\": \"\",\n                \"buttonZR\": \"\",\n                \"buttonA\": \"\",\n                \"buttonSelect\": \"\",\n                \"buttonX\": \"\",\n                \"buttonL\": \"\",\n                \"buttonR\": \"\"\n            },\n            \"buttons\": {\n                \"altJump\": \"\",\n                \"start\": \"\",\n                \"down\": \"\",\n                \"jump\": \"\",\n                \"drop\": \"\",\n                \"run\": \"\",\n                \"altRun\": \"\",\n                \"up\": \"\",\n                \"left\": \"\",\n                \"right\": \"\"\n            },\n            \"hotkeys\": {\n                \"debugInfo\": \"\",\n                \"vanillaCam\": \"\",\n                \"screenshot\": \"\",\n                \"recordGif\": \"\",\n                \"fullscreen\": \"\",\n                \"toggleHUD\": \"\",\n                \"legacyPause\": \"\",\n                \"enterCheats\": \"\"\n            },\n            \"types\": {\n                \"oldJoystick\": \"\",\n                \"keyboard\": \"\",\n                \"touchscreen\": \"\",\n                \"gamepad\": \"\"\n            },\n            \"editor\": {\n                \"modeSelect\": \"\",\n                \"scrollUp\": \"\",\n                \"prevSection\": \"\",\n                \"scrollRight\": \"\",\n                \"switchScreens\": \"\",\n                \"modeErase\": \"\",\n                \"nextSection\": \"\",\n                \"fastScroll\": \"\",\n                \"scrollLeft\": \"\",\n                \"testPlay\": \"\",\n                \"scrollDown\": \"\"\n            },\n            \"cursor\": {\n                \"left\": \"\",\n                \"up\": \"\",\n                \"down\": \"\",\n                \"tertiary\": \"\",\n                \"primary\": \"\",\n                \"right\": \"\",\n                \"secondary\": \"\"\n            },\n            \"wordProfiles\": \"\",\n            \"controlsInUse\": \"\",\n            \"controlsNewProfile\": \"\",\n            \"options\": {\n                \"batteryStatus\": \"\",\n                \"textEntryStyle\": \"\",\n                \"rumble\": \"\",\n                \"maxPlayers\": \"\"\n            },\n            \"controlsDeleteKey\": \"\",\n            \"controlsConnected\": \"\",\n            \"controlsNotInUse\": \"\",\n            \"controlsDeviceTypes\": \"\",\n            \"phraseNewProfOldJoy\": \"\",\n            \"caseInvalid\": \"\",\n            \"joystickSimpleEditor\": \"\",\n            \"caseTouch\": \"\",\n            \"controlsReallyDeleteProfile\": \"\",\n            \"controlsTitle\": \"\",\n            \"caseMouse\": \"\",\n            \"wordButtons\": \"\"\n        },\n        \"game\": {\n            \"gameNoBattleLevels\": \"\",\n            \"gameSourceSlot\": \"\",\n            \"gameEraseSave\": \"\",\n            \"warnEpCompat\": \"\",\n            \"gameBattleRandom\": \"\",\n            \"gameTargetSlot\": \"\",\n            \"gameSlotContinue\": \"\",\n            \"gameSlotNew\": \"\",\n            \"phraseScore\": \"\",\n            \"gameNoEpisodesToPlay\": \"\",\n            \"phraseTime\": \"\",\n            \"gameEraseSlot\": \"\",\n            \"gameCopySave\": \"\"\n        },\n        \"options\": {\n            \"advanced\": {\n                \"_header\": \"\",\n                \"choose-assets-on-launch\": \"\",\n                \"_tooltip\": \"\",\n                \"render\": {\n                    \"opengles11-tip\": \"\",\n                    \"opengl\": \"\",\n                    \"opengl11\": \"\",\n                    \"hw\": \"\",\n                    \"opengl11-tip\": \"\",\n                    \"opengles11\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"opengles-tip\": \"\",\n                    \"sw\": \"\",\n                    \"opengles\": \"\",\n                    \"_name\": \"\",\n                    \"sdl\": \"\",\n                    \"opengl-tip\": \"\"\n                },\n                \"audio-sample-rate\": {\n                    \"32000\": \"\",\n                    \"16000\": \"\",\n                    \"_name\": \"\",\n                    \"22050\": \"\",\n                    \"44100\": \"\",\n                    \"11025\": \"\",\n                    \"48000\": \"\"\n                },\n                \"audio-format\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"log-level\": {\n                    \"none\": \"\",\n                    \"warning\": \"\",\n                    \"fatal\": \"\",\n                    \"debug\": \"\",\n                    \"info\": \"\",\n                    \"_name\": \"\",\n                    \"critical\": \"\"\n                },\n                \"internal-res-4p\": {\n                    \"default\": \"\",\n                    \"qhd\": \"\",\n                    \"fhd\": \"\",\n                    \"_name\": \"\",\n                    \"qsmbx\": \"\"\n                },\n                \"scale-down-textures\": {\n                    \"safe\": \"\",\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\",\n                    \"all\": \"\",\n                    \"safe-tip\": \"\",\n                    \"none-tip\": \"\",\n                    \"none\": \"\",\n                    \"all-tip\": \"\"\n                },\n                \"audio-channels\": {\n                    \"mono\": \"\",\n                    \"stereo\": \"\",\n                    \"_name\": \"\"\n                },\n                \"webm-recording\": \"\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"record-gameplay-data\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"advanced-video\": \"\",\n                \"audio-enable\": \"\",\n                \"advanced-audio\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"video\": {\n                \"internal-res\": {\n                    \"smbx-wide\": \"\",\n                    \"_tooltip\": \"\",\n                    \"3ds\": \"\",\n                    \"vga\": \"\",\n                    \"snes\": \"\",\n                    \"gba\": \"\",\n                    \"hello\": \"\",\n                    \"dynamic\": \"\",\n                    \"hd\": \"\",\n                    \"nds\": \"\",\n                    \"_name\": \"\",\n                    \"smbx\": \"\"\n                },\n                \"effects\": \"\",\n                \"info-meta\": \"\",\n                \"show-fails-counter-tip\": \"\",\n                \"display-controllers\": \"\",\n                \"world-map-stars-show-tip\": \"\",\n                \"show-playtime-counter\": {\n                    \"_tooltip\": \"\",\n                    \"animated\": \"\",\n                    \"_name\": \"\",\n                    \"opaque\": \"\",\n                    \"off-tip\": \"\",\n                    \"off\": \"\",\n                    \"transparent\": \"\",\n                    \"animated-tip\": \"\"\n                },\n                \"_header\": \"\",\n                \"show-episode-title\": {\n                    \"top-tip\": \"\",\n                    \"bottom-tip\": \"\",\n                    \"bottom\": \"\",\n                    \"top\": \"\",\n                    \"_name\": \"\",\n                    \"off\": \"\"\n                },\n                \"battery-status\": {\n                    \"_name\": \"\",\n                    \"low-tip\": \"\",\n                    \"fullscreen-tip\": \"\",\n                    \"on\": \"\",\n                    \"low\": \"\",\n                    \"fullscreen\": \"\",\n                    \"off\": \"\"\n                },\n                \"scale-mode\": {\n                    \"full\": \"\",\n                    \"0_5x\": \"\",\n                    \"2x\": \"\",\n                    \"center\": \"\",\n                    \"nearest\": \"\",\n                    \"1x\": \"\",\n                    \"linear\": \"\",\n                    \"_name\": \"\",\n                    \"integer\": \"\",\n                    \"3x\": \"\"\n                },\n                \"fullscreen\": \"\",\n                \"3d-compat-mode-tip\": \"\",\n                \"info-game\": \"\",\n                \"show-medals-counter\": \"\",\n                \"info-run\": \"\",\n                \"show-fps\": \"\",\n                \"show-medals-counter-tip\": \"\",\n                \"show-screen-shake\": \"\",\n                \"3d-compat-mode\": \"\",\n                \"video-system\": \"\",\n                \"enable-inter-level-fade-effect\": \"\",\n                \"show-fails-counter\": \"\",\n                \"world-map-stars-show\": \"\"\n            },\n            \"compatibility\": {\n                \"_header\": \"\",\n                \"reset-all\": \"\",\n                \"autocode\": \"\",\n                \"_tooltip\": \"\",\n                \"features\": \"\",\n                \"bugfixes\": \"\"\n            },\n            \"main\": {\n                \"two-screen-mode\": {\n                    \"split\": \"\",\n                    \"shared\": \"\",\n                    \"topbottom\": \"\",\n                    \"smbx\": \"\",\n                    \"_name\": \"\"\n                },\n                \"background-work\": \"\",\n                \"language\": \"\",\n                \"unlimited-framerate\": \"\",\n                \"_header\": \"\",\n                \"four-screen-mode\": {\n                    \"split\": \"\",\n                    \"_name\": \"\",\n                    \"shared\": \"\"\n                },\n                \"background-work-tip\": \"\",\n                \"timing\": \"\",\n                \"vsync\": \"\",\n                \"multiplayer\": \"\",\n                \"discord-rpc\": \"\",\n                \"frame-skip\": \"\"\n            },\n            \"episode-options\": {\n                \"_header\": \"\",\n                \"playstyle\": {\n                    \"classic-tip\": \"\",\n                    \"vanilla-tip\": \"\",\n                    \"vanilla\": \"\",\n                    \"classic\": \"\",\n                    \"modern\": \"\",\n                    \"_name\": \"\",\n                    \"modern-tip\": \"\"\n                },\n                \"creator-compat\": {\n                    \"_name\": \"\",\n                    \"disable\": \"\",\n                    \"file-only\": \"\",\n                    \"enable\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"audio\": {\n                \"audio-sfx-volume\": \"\",\n                \"_header\": \"\",\n                \"audio-prefs\": \"\",\n                \"sfx-audio-fx\": \"\",\n                \"sfx-pet-beat\": \"\",\n                \"audio-music-volume\": \"\",\n                \"sfx-spatial-audio-tip\": \"\",\n                \"sfx-pet-beat-tip\": \"\",\n                \"sfx-spatial-audio\": \"\",\n                \"sfx-modern\": \"\",\n                \"sfx-modern-tip\": \"\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"2\": \"\",\n                    \"0\": \"\",\n                    \"_name\": \"\",\n                    \"3\": \"\",\n                    \"1\": \"\"\n                }\n            },\n            \"view-credits\": {\n                \"_header\": \"\"\n            },\n            \"restartEngine\": \"\",\n            \"controls\": {\n                \"_header\": \"\"\n            }\n        },\n        \"character\": {\n            \"selectCharacter\": \"\"\n        },\n        \"wordHide\": \"\",\n        \"wordWaiting\": \"\",\n        \"main\": {\n            \"mainEditor\": \"\",\n            \"main1PlayerGame\": \"\",\n            \"mainMultiplayerGame\": \"\",\n            \"mainBattleGame\": \"\",\n            \"mainPlayEpisode\": \"\",\n            \"mainOptions\": \"\",\n            \"mainExit\": \"\"\n        },\n        \"editor\": {\n            \"makeFor\": \"\",\n            \"newWorld\": \"\",\n            \"errorMissingResources\": \"\",\n            \"promptNewWorldName\": \"\",\n            \"battles\": \"\"\n        },\n        \"wordPlayer\": \"\",\n        \"wordBack\": \"\",\n        \"battle\": {\n            \"errorNoLevels\": \"\"\n        },\n        \"wordOn\": \"\",\n        \"wordProfile\": \"\",\n        \"loading\": \"\",\n        \"wordShow\": \"\",\n        \"caseNone\": \"\",\n        \"wordNo\": \"\",\n        \"wordYes\": \"\",\n        \"abbrevMilliseconds\": \"\",\n        \"wordResume\": \"\",\n        \"wordOff\": \"\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"phraseTestControls\": \"\",\n            \"phraseChangeChar\": \"\",\n            \"phraseTestProfile\": \"\",\n            \"phrasePressAButton\": \"\",\n            \"phraseSetControls\": \"\",\n            \"splitPressSelect_2\": \"\",\n            \"dropAddTitle\": \"\",\n            \"phraseDropPX\": \"\",\n            \"phraseStartToForceRes\": \"\",\n            \"phraseForceResume\": \"\",\n            \"splitPressSelect_1\": \"\",\n            \"wordDisconnect\": \"\",\n            \"phraseDropMe\": \"\",\n            \"phraseStartToResume\": \"\",\n            \"phraseHoldStart\": \"\",\n            \"phraseWaitingForInput\": \"\",\n            \"reconnectTitle\": \"\"\n        },\n        \"error\": {\n            \"errorInvalidEnterWarp\": \"\",\n            \"openFileFailed\": \"\",\n            \"warpNeedStarCount\": \"\",\n            \"errorNoStartPoint\": \"\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"pause\": {\n            \"playerSetup\": \"\",\n            \"restartLevel\": \"\",\n            \"quit\": \"\",\n            \"enterCode\": \"\",\n            \"saveAndContinue\": \"\",\n            \"resetCheckpoints\": \"\",\n            \"saveAndQuit\": \"\",\n            \"returnToEditor\": \"\",\n            \"continue\": \"\",\n            \"quitTesting\": \"\"\n        },\n        \"msgbox\": {\n            \"sysInfoWarning\": \"\",\n            \"sysInfoTitle\": \"\",\n            \"sysInfoError\": \"\"\n        },\n        \"hint\": {\n            \"heavy-duck\": \"\",\n            \"char5-bombs\": \"\",\n            \"shoe-block\": \"\",\n            \"pound-altrun\": \"\",\n            \"rainbow-surf\": \"\",\n            \"pound-down\": \"\",\n            \"no-lives-new\": \"\",\n            \"gray-bricks\": \"\",\n            \"no-lives-old\": \"\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"outro\": {\n        \"originalBy\": \"\",\n        \"qualityControl\": \"\",\n        \"cppPortDevelopers\": \"\",\n        \"nameAndrewSpinks\": \"\",\n        \"customSprites\": \"\",\n        \"specialThanks\": \"\",\n        \"psVitaPortBy\": \"\",\n        \"engineCredits\": \"\",\n        \"levelDesign\": \"\",\n        \"nameVitalyNovichkov\": \"\",\n        \"gameCredits\": \"\"\n    },\n    \"pluralRules\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_en.json",
    "content": "{\n    \"editor\": {\n        \"block\": {\n            \"canBreak\": \"Can Break\",\n            \"canBreakTooltip\": \"Legacy: breaks when hit\",\n            \"inside\": \"Inside:\",\n            \"invis\": \"Invis\",\n            \"letterHeight\": \"H \",\n            \"letterWidth\": \"W \",\n            \"pickContents\": \"Pick block contents\",\n            \"slick\": \"Slick\"\n        },\n        \"browser\": {\n            \"askOverwriteFile\": \"Overwrite {0}?\",\n            \"itemNewFile\": \"<New File>\",\n            \"itemNewFolder\": \"<New Folder>\",\n            \"newFile\": \"New file\",\n            \"openFile\": \"Open file\",\n            \"saveFile\": \"Save file\"\n        },\n        \"events\": {\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"Change all sect bounds to current?\",\n                \"changeSectionBoundsToCurrent\": \"Change sect {0} bounds to current?\",\n                \"shouldEvent\": \"Should Event {0}\"\n            },\n            \"controlsForEventN\": \"Controls for event {0}\",\n            \"deletion\": {\n                \"cancel\": \"No: do not delete event\",\n                \"confirm\": \"Yes: delete event\",\n                \"deletingEvent\": \"Deleting event {0}\"\n            },\n            \"desc\": {\n                \"activate\": \"NPC enters the screen\",\n                \"death\": \"NPC dies\",\n                \"destroy\": \"block is destroyed\",\n                \"enter\": \"warp is entered\",\n                \"hit\": \"block is hit\",\n                \"layerClear\": \"everything in object layer is gone\",\n                \"phraseTriggersAfterTemplate\": \"{0} triggers {1} ms after event {2} occurs\",\n                \"phraseTriggersWhenTemplate\": \"{0} triggers when {1}\",\n                \"talk\": \"player talks to NPC\"\n            },\n            \"header\": \"Events:\",\n            \"headerTriggerEvent\": \"Trigger:\",\n            \"itemNewEvent\": \"<New Event>\",\n            \"label\": {\n                \"activate\": \"Activate\",\n                \"death\": \"Death\",\n                \"destroy\": \"Destroy\",\n                \"enter\": \"Enter\",\n                \"hit\": \"Hit\",\n                \"layerClear\": \"Layer Clear\",\n                \"next\": \"Next\",\n                \"talk\": \"Talk\"\n            },\n            \"layers\": {\n                \"headerHide\": \"Hide:\",\n                \"headerMove\": \"Move:\",\n                \"headerShow\": \"Show:\",\n                \"headerToggle\": \"Toggle:\"\n            },\n            \"letter\": {\n                \"activate\": \"A:\",\n                \"death\": \"D:\",\n                \"destroy\": \"D:\",\n                \"enter\": \"E:\",\n                \"hit\": \"H:\",\n                \"layerClear\": \"L:\",\n                \"talk\": \"T:\"\n            },\n            \"promptEventName\": \"Event name\",\n            \"promptEventText\": \"Event text\",\n            \"props\": {\n                \"autostart\": \"Autostart\",\n                \"controls\": \"Controls\",\n                \"endGame\": \"End Game\",\n                \"layerSmoke\": \"Smoke\",\n                \"sound\": \"Sound\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"Keep\",\n                \"actionReset\": \"Reset\",\n                \"actionSet\": \"Set\",\n                \"phraseAllSections\": \"All Sections\",\n                \"propBackground\": \"BG\",\n                \"propBounds\": \"Bounds\",\n                \"propMusic\": \"Music\"\n            },\n            \"settingsForEvent\": \"Settings for event {0}\"\n        },\n        \"file\": {\n            \"actionClearLevel\": \"Clear Level\",\n            \"actionClearWorld\": \"Clear World\",\n            \"actionExit\": \"Exit\",\n            \"actionOpen\": \"Open\",\n            \"actionRevert\": \"Revert\",\n            \"commandNew\": \"New\",\n            \"commandOpen\": \"Open...\",\n            \"commandSave\": \"Save\",\n            \"commandSaveAs\": \"Save as...\",\n            \"confirmConfirmAction\": \"Are you sure you want to {0}?\",\n            \"confirmConvertFormatTo\": \"Convert format to {0}?\",\n            \"confirmSaveBeforeAction\": \"Save before you {0}?\",\n            \"convert\": {\n                \"descNew\": \"The file extension will change but the old file will NOT be deleted.\\n\\nPlease check for lost features after saving.\"\n            },\n            \"formatLegacy\": \"Legacy\",\n            \"formatModern\": \"Modern\",\n            \"labelCurrentFile\": \"Current file: \",\n            \"labelFormat\": \"Format:\",\n            \"optionActionWithoutSave\": \"{0} without saving\",\n            \"optionCancelAction\": \"Cancel: do not {0}\",\n            \"optionCancelConversion\": \"Cancel conversion\",\n            \"optionProceedWithConversion\": \"Proceed with conversion\",\n            \"optionYesSaveThenAction\": \"Yes: save then {0}\",\n            \"sectionLevel\": \"Level\",\n            \"sectionWorld\": \"World\"\n        },\n        \"labelSortLayer\": \"Sort Layer:\",\n        \"labelSortOffset\": \"Sort Offset:\",\n        \"layers\": {\n            \"abbrevAttLayer\": \"Att: \",\n            \"default\": \"Default\",\n            \"deletion\": {\n                \"cancel\": \"Cancel: do not delete layer\",\n                \"confirmDelete\": \"No: *DELETE ALL CONTENTS*\",\n                \"confirmPreserve\": \"Yes: move to default layer\",\n                \"header\": \"Deleting layer {0}\",\n                \"preserveLayerContents\": \"Preserve layer contents?\"\n            },\n            \"desc\": {\n                \"att\": \"Whenever the NPC moves, the attached layer moves following it\"\n            },\n            \"header\": \"Layers:\",\n            \"itemNewLayer\": \"<New Layer>\",\n            \"label\": \"Layer:\",\n            \"labelAttached\": \"Attached:\",\n            \"labelAttachedLayer\": \"Attached Layer:\",\n            \"labelMoveLayer\": \"Move Layer:\",\n            \"promptLayerName\": \"Layer name\"\n        },\n        \"letterCoordX\": \"X\",\n        \"letterCoordY\": \"Y\",\n        \"letterDown\": \"D\",\n        \"letterLeft\": \"L\",\n        \"letterRight\": \"R\",\n        \"letterUp\": \"U\",\n        \"level\": {\n            \"alwaysVis\": \"Always Vis\",\n            \"bigBG\": \"Big BG\",\n            \"gameStart\": \"Game Start\",\n            \"levelName\": \"Level Name\",\n            \"pathBG\": \"Path BG\",\n            \"pathUnlocks\": \"Path Unlocks\",\n            \"startPos\": \"Start Pos\"\n        },\n        \"mapPos\": \"Map Pos:\",\n        \"npc\": {\n            \"abbrevGen\": \"Gen\",\n            \"ai\": {\n                \"LR\": \"LR\",\n                \"UD\": \"UD\",\n                \"aiIs\": \"AI: {0}\",\n                \"headerCustomAi\": \"Custom AI:\",\n                \"jump\": \"Jump\",\n                \"leap\": \"Leap\",\n                \"swim\": \"Swim\",\n                \"target\": \"Target\",\n                \"use1_0Ai\": \"Use 1.0 AI?\"\n            },\n            \"gen\": {\n                \"direction\": \"Direction\",\n                \"effectIs\": \"Effect: {0}\",\n                \"effectShoot\": \"Shoot\",\n                \"effectWarp\": \"Warp\",\n                \"header\": \"Generator Settings\"\n            },\n            \"inContainer\": \"In\",\n            \"inertNice\": \"Nice\",\n            \"props\": {\n                \"active\": \"Active\",\n                \"attachSurface\": \"Attach\",\n                \"facing\": \"Facing\"\n            },\n            \"stuckStop\": \"Stop\",\n            \"tooltipExpandSection\": \"Expand section\"\n        },\n        \"pageBlankOfBlank\": \"Page {0} of {1}\",\n        \"phraseAreYouSure\": \"Are you sure?\",\n        \"phraseCountMore\": \"{0} More\",\n        \"phraseDelayIsMs\": \"Delay: {0} ms\",\n        \"phraseGenericIndex\": \"Number {0}\",\n        \"phraseRadiusIndex\": \"Radius {0}\",\n        \"phraseSectionIndex\": \"Section {0}\",\n        \"phraseTextOf\": \"{0} Text\",\n        \"phraseWarpIndex\": \"Warp {0}\",\n        \"section\": {\n            \"horizWrap\": \"Horiz. Wrap\",\n            \"vertWrap\": \"Vert. Wrap\",\n            \"noTurnBack\": \"No Turn Back\",\n            \"offscreenExit\": \"Offscreen Exit\",\n            \"scroll\": \"Scroll\",\n            \"setBounds\": \"Set Bounds\",\n            \"underwater\": \"Underwater\"\n        },\n        \"select\": {\n            \"allSectPropBlankForEventN\": \"All Sect {0} for {1}\",\n            \"pathBlankUnlock\": \"Path {0} Unlock By\",\n            \"sectBlankPropBlankForEventN\": \"Sect {0} {1} for {2}\",\n            \"sectionBlankPropBlank\": \"Section {0} {1}\",\n            \"soundForEventN\": \"Sound for Event {0}\",\n            \"warpTransitEffect\": \"Warp Transition Effect\",\n            \"worldMusic\": \"World Music\"\n        },\n        \"testPlay\": {\n            \"boot\": \"Boot\",\n            \"char\": \"Char\",\n            \"magicHand\": \"Magic Hand\",\n            \"pet\": \"Pet\",\n            \"power\": \"Power\"\n        },\n        \"toggleMagicBlock\": \"Magic Block\",\n        \"tooltip\": {\n            \"BGOs\": \"BGOs\",\n            \"NPCs\": \"NPCs\",\n            \"area\": \"Area\",\n            \"blocks\": \"Blocks\",\n            \"erase\": \"Erase\",\n            \"eraseAll\": \"Erase All\",\n            \"events\": \"Events\",\n            \"file\": \"File\",\n            \"layers\": \"Layers\",\n            \"levels\": \"Levels\",\n            \"music\": \"Music\",\n            \"paths\": \"Paths\",\n            \"scenes\": \"Scenes\",\n            \"select\": \"Select\",\n            \"settings\": \"Settings\",\n            \"show\": \"Show\",\n            \"tiles\": \"Tiles\",\n            \"warps\": \"Warps\",\n            \"water\": \"Water\"\n        },\n        \"warp\": {\n            \"allow\": \"Allow:\",\n            \"cannonExit\": \"Cannon Exit\",\n            \"dir\": \"Dir.\",\n            \"effect\": \"Effect:\",\n            \"in\": \"In\",\n            \"item\": \"Item\",\n            \"lvlWarp\": \"Lvl Warp\",\n            \"needFloor\": \"Need Floor\",\n            \"needKey\": \"Need Key\",\n            \"needStarCount\": \"Need {0} {1}\",\n            \"out\": \"Out\",\n            \"placing\": \"Placing:\",\n            \"ride\": \"Ride\",\n            \"showStarCount\": \"Show Star Count\",\n            \"showStartScene\": \"Show Start Scene\",\n            \"speed\": \"Speed \",\n            \"starLockMessage\": \"Star lock msg\",\n            \"style\": {\n                \"blipInstant\": \"Blip\",\n                \"door\": \"Door\",\n                \"pipe\": \"Pipe\",\n                \"portal\": \"Port\",\n                \"style\": \"Style: \"\n            },\n            \"target\": \"Target: \",\n            \"title\": \"Warp Settings\",\n            \"to\": \"To: {0}\",\n            \"toMap\": \"To Map\",\n            \"transit\": {\n                \"name0\": \"NONE\",\n                \"name1\": \"SCROLL\",\n                \"name2\": \"FADE\",\n                \"name3\": \"CIRCLE\",\n                \"name4\": \"FLIP (H)\",\n                \"name5\": \"FLIP (V)\"\n            },\n            \"twoWay\": \"Two-Way\"\n        },\n        \"water\": {\n            \"title\": \"Water Settings\"\n        },\n        \"wordCoins\": \"Coins\",\n        \"wordEnabled\": \"Enabled\",\n        \"wordEvent\": {\n            \"genitive\": \"Event\",\n            \"nominative\": \"Event\",\n            \"typeLabel\": \"{0} Event:\"\n        },\n        \"wordHeight\": \"Height\",\n        \"wordInstant\": \"Instant\",\n        \"wordMode\": \"Mode\",\n        \"wordNPC\": {\n            \"genitive\": \"NPC\",\n            \"nominative\": \"NPC\"\n        },\n        \"wordText\": \"Text\",\n        \"wordWidth\": \"Width\",\n        \"world\": {\n            \"allowChars\": \"Allow Chars\",\n            \"hubWorld\": \"Hub World\",\n            \"introLevel\": \"Intro Level\",\n            \"name\": \"World Name\",\n            \"phraseCreditIndex\": \"World Credit Line {0}:\",\n            \"retryOnFail\": \"Retry On Fail\",\n            \"totalStars\": \"Total Stars: \"\n        }\n    },\n    \"game\": {\n        \"connect\": {\n            \"dropAddTitle\": \"Drop/Add Players\",\n            \"phraseChangeChar\": \"Change Char\",\n            \"phraseDropMe\": \"Drop Me\",\n            \"phraseDropPX\": \"Drop P{0}\",\n            \"phraseForceResume\": \"Force Resume\",\n            \"phraseHoldStart\": \"Hold Start\",\n            \"phrasePressAButton\": \"Press A Button\",\n            \"phraseSetControls\": \"Set Controls\",\n            \"phraseStartToForceRes\": \"Press Start to Force Resume\",\n            \"phraseStartToResume\": \"Press Start to Resume\",\n            \"phraseTestControls\": \"Test Controls\",\n            \"phraseTestProfile\": \"Test Profile\",\n            \"phraseWaitingForInput\": \"Waiting for input device...\",\n            \"reconnectTitle\": \"Reconnect\",\n            \"splitPressSelect_1\": \"Press Select for\",\n            \"splitPressSelect_2\": \"Controls Options\",\n            \"wordDisconnect\": \"Disconnect\"\n        },\n        \"error\": {\n            \"errorInvalidEnterWarp\": \"Can't start the level because of an invalid entrance warp {1} was specified.\\nTotal warp entries: {2}\\n\\nFile: {0}\",\n            \"errorNoStartPoint\": \"Can't start the level because of no available start points placed or entrance warp specified.\\n\\nFile: {0}\",\n            \"openFileFailed\": \"Can't open \\\"{0}\\\": file doesn't exist or corrupted.\",\n            \"warpNeedStarCount\": \"You need {0} {1} to enter.\",\n            \"IPCTimeOut\": \"No responce from the connected Editor. Game will be closed.\",\n            \"openIPCDataFailed\": \"Can't proceed received file data because of corruption or other errors.\",\n            \"errorTooOldEngine\": \"Content requires a newer engine feature level ({0}) than the current version ({1}). Please update TheXTech.\",\n            \"errorTooOldGameAssets\": \"Content requires a newer asset pack feature level ({0}) than the current asset pack ({1}). Please upgrade your game asset pack.\"\n        },\n        \"hint\": {\n            \"pound-altrun\": \"Press Alt Run to pound downwards!\",\n            \"pound-down\": \"Press Down to pound downwards!\",\n            \"no-lives-new\": \"No coins left? Take a loan! Just watch your score...\",\n            \"no-lives-old\": \"Be careful - Game Over is imminent!\",\n            \"rainbow-surf\": \"Grab, run, hold down, and let go to surf.\",\n            \"char5-bombs\": \"Press Run to collect and Alt Run to throw.\",\n            \"heavy-duck\": \"Duck to block most - but not all - flames!\",\n            \"shoe-block\": \"Wearing this blocks most - but not all - flames!\",\n            \"gray-bricks\": \"Some blocks are vulnerable to special powers.\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"Scanning levels...\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0}m{1}s\"\n        },\n        \"pause\": {\n            \"continue\": \"Continue\",\n            \"playerSetup\": \"Player Setup\",\n            \"enterCode\": \"Enter Code\",\n            \"quit\": \"Quit\",\n            \"quitTesting\": \"Quit Testing\",\n            \"resetCheckpoints\": \"Reset Checkpoints\",\n            \"restartLevel\": \"Restart Level\",\n            \"returnToEditor\": \"Return to Editor\",\n            \"saveAndContinue\": \"Save and Continue\",\n            \"saveAndQuit\": \"Save and Quit\"\n        },\n        \"msgbox\": {\n            \"sysInfoError\": \"Error!\",\n            \"sysInfoTitle\": \"Info\",\n            \"sysInfoWarning\": \"Warning!\"\n        },\n        \"screenPaused\": \"Paused\",\n        \"ipcStatus\": {\n            \"dataAccepted\": \"Data accepted, the parsing started...\",\n            \"dataTransferStarted\": \"Started data tansfer...\",\n            \"dataValid\": \"Accepted data is valid\",\n            \"errorTimeout\": \"ERROR: Wait time out.\",\n            \"loadingdone\": \"Done. Starting game...\",\n            \"waitingInput\": \"Waiting for input data...\"\n        },\n        \"loader\": {\n            \"statusAssetPacks\": \"Asset packs\",\n            \"statusFinishing\": \"Finishing...\",\n            \"statusGameInfo\": \"Game info\",\n            \"statusLoadData\": \"Loading data...\",\n            \"statusLoadFile\": \"Load: {0}\",\n            \"statusTranslations\": \"Translations\",\n            \"loading\": \"Loading...\"\n        }\n    },\n    \"languageName\": \"English\",\n    \"menu\": {\n        \"abbrevMilliseconds\": \"MS\",\n        \"battle\": {\n            \"errorNoLevels\": \"Can't start battle because of no levels available\"\n        },\n        \"caseNone\": \"<None>\",\n        \"character\": {\n            \"selectCharacter\": \"{0} game\"\n        },\n        \"controls\": {\n            \"buttons\": {\n                \"altJump\": \"Alt Jump\",\n                \"altRun\": \"Alt Run\",\n                \"down\": \"Down\",\n                \"drop\": \"Drop Item\",\n                \"jump\": \"Jump\",\n                \"left\": \"Left\",\n                \"right\": \"Right\",\n                \"run\": \"Run\",\n                \"start\": \"Start\",\n                \"up\": \"Up\"\n            },\n            \"caseInvalid\": \"(Invalid)\",\n            \"caseMouse\": \"(Mouse)\",\n            \"caseTouch\": \"(Touch)\",\n            \"controlsConnected\": \"Connected:\",\n            \"controlsDeleteKey\": \"(Alt Jump to Delete)\",\n            \"controlsDeleteKey2\": \"(Alt Run to Delete)\",\n            \"controlsDeviceTypes\": \"Device Types\",\n            \"controlsInUse\": \"(In Use)\",\n            \"controlsNewProfile\": \"<New Profile>\",\n            \"controlsNotInUse\": \"(Not In Use)\",\n            \"controlsReallyDeleteProfile\": \"Really delete profile?\",\n            \"controlsTitle\": \"Controls\",\n            \"cursor\": {\n                \"down\": \"Mouse Down\",\n                \"left\": \"Mouse Left\",\n                \"primary\": \"Primary\",\n                \"right\": \"Mouse Right\",\n                \"secondary\": \"Secondary\",\n                \"tertiary\": \"Tertiary\",\n                \"up\": \"Mouse Up\"\n            },\n            \"editor\": {\n                \"fastScroll\": \"Fast Scrl\",\n                \"modeErase\": \"Mode Erase\",\n                \"modeSelect\": \"Mode Select\",\n                \"nextSection\": \"Next Sect\",\n                \"prevSection\": \"Prev Sect\",\n                \"scrollDown\": \"Scrl Down\",\n                \"scrollLeft\": \"Scrl Left\",\n                \"scrollRight\": \"Scrl Right\",\n                \"scrollUp\": \"Scrl Up\",\n                \"switchScreens\": \"Show Pane\",\n                \"testPlay\": \"Test Play\"\n            },\n            \"hotkeys\": {\n                \"debugInfo\": \"Debug Info\",\n                \"enterCheats\": \"Enter Code\",\n                \"fullscreen\": \"Fullscreen\",\n                \"legacyPause\": \"Old Pause\",\n                \"recordGif\": \"Record GIF\",\n                \"screenshot\": \"Screenshot\",\n                \"toggleHUD\": \"Toggle HUD\",\n                \"vanillaCam\": \"Vanilla Cam\"\n            },\n            \"options\": {\n                \"batteryStatus\": \"Battery Status\",\n                \"maxPlayers\": \"Max Players\",\n                \"rumble\": \"Rumble\",\n                \"altMenuControls\": \"Alt Menu Controls\",\n                \"textEntryStyle\": \"Text Entry Style\"\n            },\n            \"joystickSimpleEditor\": \"Simple Editor Controls\",\n            \"phraseNewProfOldJoy\": \"New Profile for Old Joystick\",\n            \"profile\": {\n                \"cursorControls\": \"Cursor controls\",\n                \"deleteProfile\": \"Delete profile\",\n                \"editorControls\": \"Editor controls\",\n                \"hotkeys\": \"Hotkeys\",\n                \"playerControls\": \"Player controls\",\n                \"renameProfile\": \"Rename profile\"\n            },\n            \"tDS\": {\n                \"buttonA\": \"A\",\n                \"buttonB\": \"B\",\n                \"buttonL\": \"L\",\n                \"buttonR\": \"R\",\n                \"buttonSelect\": \"Select\",\n                \"buttonStart\": \"Start\",\n                \"buttonX\": \"X\",\n                \"buttonY\": \"Y\",\n                \"buttonZL\": \"ZL\",\n                \"buttonZR\": \"ZR\",\n                \"cStick\": \"C-Stick\",\n                \"casePen\": \"(Pen)\",\n                \"dPad\": \"D-Pad\",\n                \"tStick\": \"Thumb\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"longOld\": \"Long (Old)\",\n                    \"phabletOld\": \"Phablet (Old)\",\n                    \"phoneOld\": \"Phone (Old)\",\n                    \"standard\": \"Standard\",\n                    \"tabletOld\": \"Tablet (Old)\",\n                    \"tight\": \"Tight\",\n                    \"tinyOld\": \"Tiny (Old)\"\n                },\n                \"option\": {\n                    \"feedbackLength\": \"Feedback Length\",\n                    \"feedbackStrength\": \"Feedback Strength\",\n                    \"holdRun\": \"Hold Run on Start\",\n                    \"interfaceStyle\": \"Interface Style\",\n                    \"layoutStyle\": \"Layout Style\",\n                    \"resetLayout\": \"Reset Layout\",\n                    \"sStartSpacing\": \"S-Start Spacing\",\n                    \"scaleButtons\": \"Scale Buttons\",\n                    \"scaleDPad\": \"Scale D-Pad\",\n                    \"scaleFactor\": \"Scale Factor\",\n                    \"showCodeButton\": \"Show Code Button\"\n                },\n                \"style\": {\n                    \"ABXY\": \"ABXY\",\n                    \"XODA\": \"XODA\",\n                    \"actions\": \"Actions\"\n                }\n            },\n            \"types\": {\n                \"gamepad\": \"Gamepad\",\n                \"keyboard\": \"Keyboard\",\n                \"oldJoystick\": \"Old Joy\",\n                \"touchscreen\": \"Touchscreen\"\n            },\n            \"wii\": {\n                \"classic\": {\n                    \"buttonLT\": \"LT\",\n                    \"buttonRT\": \"RT\",\n                    \"buttonX\": \"X\",\n                    \"buttonY\": \"Y\",\n                    \"buttonZL\": \"ZL\",\n                    \"buttonZR\": \"ZR\",\n                    \"lStick\": \"L-Pad\",\n                    \"rStick\": \"R-Pad\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"C\",\n                    \"buttonZ\": \"Z\",\n                    \"prefixN\": \"N\"\n                },\n                \"phraseNewClassic\": \"New Classic Profile\",\n                \"phraseNewNunchuck\": \"New Nunchuck Profile\",\n                \"typeClassic\": \"Classic\",\n                \"typeNunchuck\": \"Nunchuck\",\n                \"typeWiimote\": \"Wiimote\",\n                \"typeGamecube\": \"GameCube\",\n                \"wiimote\": {\n                    \"button1\": \"1\",\n                    \"button2\": \"2\",\n                    \"buttonA\": \"A\",\n                    \"buttonB\": \"B\",\n                    \"buttonHome\": \"Home\",\n                    \"buttonMinus\": \"-\",\n                    \"buttonPlus\": \"+\",\n                    \"caseIR\": \"(IR)\",\n                    \"dPad\": \"D-Pad\",\n                    \"shake\": \"Shake\"\n                }\n            },\n            \"wordButtons\": \"Buttons\",\n            \"wordProfiles\": \"Profiles\"\n        },\n        \"editor\": {\n            \"errorMissingResources\": \"Sorry! You are missing {0}, required for the in-game editor.\",\n            \"battles\": \"<Battle Levels>\",\n            \"makeFor\": \"Make for:\",\n            \"newWorld\": \"<New World>\",\n            \"promptNewWorldName\": \"New world name\"\n        },\n        \"game\": {\n            \"gameBattleRandom\": \"Random Level\",\n            \"gameCopySave\": \"Copy save\",\n            \"gameEraseSave\": \"Erase save\",\n            \"gameEraseSlot\": \"Select the slot to erase\",\n            \"gameNoBattleLevels\": \"<No battle levels>\",\n            \"gameNoEpisodesToPlay\": \"<No episodes to play>\",\n            \"warnEpCompat\": \"This episode was made for a different branch of SMBX and may not work properly.\",\n            \"gameSlotContinue\": \"SLOT {0} ... {1}%\",\n            \"gameSlotNew\": \"SLOT {0} ... NEW GAME\",\n            \"gameSourceSlot\": \"Select the source slot\",\n            \"gameTargetSlot\": \"Now select the target\",\n            \"phraseScore\": \"Score: {0}\",\n            \"phraseTime\": \"Time: {0}\"\n        },\n        \"loading\": \"Loading...\",\n        \"main\": {\n            \"main1PlayerGame\": \"1 Player Game\",\n            \"mainBattleGame\": \"Battle Game\",\n            \"mainEditor\": \"Editor\",\n            \"mainExit\": \"Exit\",\n            \"mainMultiplayerGame\": \"2 Player Game\",\n            \"mainOptions\": \"Options\",\n            \"mainPlayEpisode\": \"Play Episode\"\n        },\n        \"options\": {\n            \"restartEngine\": \"Restart engine for changes to take effect.\",\n            \"advanced\": {\n                \"_header\": \"Advanced\",\n                \"_tooltip\": \"Technical options for internal operations\",\n                \"advanced-audio\": \"Audio\",\n                \"advanced-video\": \"Video\",\n                \"audio-buffer-size\": {\n                    \"_name\": \"Buffer size\",\n                    \"_tooltip\": \"Increase for fewer pops but more lag\"\n                },\n                \"audio-channels\": {\n                    \"_name\": \"Channels\",\n                    \"mono\": \"Mono\",\n                    \"stereo\": \"Stereo\"\n                },\n                \"audio-enable\": \"Enable\",\n                \"audio-format\": {\n                    \"_name\": \"Audio format\",\n                    \"_tooltip\": \"Format for sound driver\"\n                },\n                \"audio-sample-rate\": {\n                    \"11025\": \"11025 Hz\",\n                    \"16000\": \"16000 Hz\",\n                    \"22050\": \"22050 Hz\",\n                    \"32000\": \"32000 Hz\",\n                    \"44100\": \"44100 Hz\",\n                    \"48000\": \"48000 Hz\",\n                    \"_name\": \"Sample rate\"\n                },\n                \"choose-assets-on-launch\": \"Choose assets on launch\",\n                \"log-level\": {\n                    \"_name\": \"Log level\",\n                    \"critical\": \"Critical\",\n                    \"debug\": \"Debug\",\n                    \"fatal\": \"Fatal\",\n                    \"info\": \"Info\",\n                    \"none\": \"None\",\n                    \"warning\": \"Warning\"\n                },\n                \"record-gameplay-data\": \"Record gameplay\",\n                \"render\": {\n                    \"_name\": \"Render mode\",\n                    \"hw\": \"Auto\",\n                    \"opengl\": \"OpenGL 3+\",\n                    \"opengl-tip\": \"Desktop API with support for all new visual effects and full accuracy to SMBX64\",\n                    \"opengl11\": \"OpenGL 1.1\",\n                    \"opengl11-tip\": \"Legacy desktop API with full accuracy to SMBX64\",\n                    \"opengles\": \"OpenGL ES 2+\",\n                    \"opengles-tip\": \"Mobile API with support for all new visual effects and high accuracy to SMBX64\",\n                    \"opengles11\": \"OpenGL ES 1.1+\",\n                    \"opengles11-tip\": \"Legacy mobile API with full accuracy to SMBX64\",\n                    \"sdl\": \"SDL2\",\n                    \"sdl-tip\": \"Basic cross-platform render API\",\n                    \"sw\": \"Software\"\n                },\n                \"scale-down-textures\": {\n                    \"_name\": \"Scale down images\",\n                    \"_tooltip\": \"Store images as 1x to save memory\",\n                    \"all\": \"All\",\n                    \"all-tip\": \"Less loading stutter than 'Safe'\",\n                    \"none\": \"None\",\n                    \"none-tip\": \"Least loading stutter\",\n                    \"safe\": \"Safe\",\n                    \"safe-tip\": \"Checks if images are in 2x format\"\n                },\n                \"use-native-osk\": \"Native OSK\",\n                \"inaccurate-gifs\": \"Inaccurate GIFs\",\n                \"inaccurate-gifs-tip\": \"Allows 3D effect when GIFs are present\",\n                \"webm-recording\": \"WEBM recording\",\n                \"internal-res-4p\": {\n                    \"_name\": \"4P split screen size\",\n                    \"default\": \"Default\",\n                    \"fhd\": \"1920x1080 (FHD)\",\n                    \"qhd\": \"2560x1440 (QHD)\",\n                    \"qsmbx\": \"1600x1200 (QSMBX)\"\n                },\n                \"fullscreen-type\": {\n                    \"_name\": \"Fullscreen type\",\n                    \"auto\": \"Auto\",\n                    \"desktop\": \"Desktop\",\n                    \"desktop-tip\": \"Keep the current screen resolution\",\n                    \"exclusive\": \"Exclusive\",\n                    \"exclusive-tip\": \"Set physical screen resolution\"\n                },\n                \"fullscreen-depth\": {\n                    \"16\": \"16-bit\",\n                    \"32\": \"32-bit\",\n                    \"_name\": \"Exclusive bit depth\",\n                    \"auto\": \"Auto\"\n                },\n                \"fullscreen-res\": \"Exclusive res\"\n            },\n            \"audio\": {\n                \"_header\": \"Audio\",\n                \"audio-music-volume\": \"Music volume\",\n                \"audio-prefs\": \"Preferences\",\n                \"audio-sfx-volume\": \"SFX volume\",\n                \"sfx-audio-fx\": \"Echo effects\",\n                \"sfx-modern\": \"Modern SFX\",\n                \"sfx-modern-tip\": \"Use sounds added in TheXTech\",\n                \"sfx-pet-beat\": \"Pet grooves\",\n                \"sfx-pet-beat-tip\": \"Play special groove when riding a pet\",\n                \"sfx-spatial-audio\": \"Spatial audio\",\n                \"sfx-spatial-audio-tip\": \"Reduce volume of offscreen sounds\"\n            },\n            \"compatibility\": {\n                \"_header\": \"Session Tweaks\",\n                \"_tooltip\": \"Options for compat or debugging\",\n                \"autocode\": \"Autocode\",\n                \"bugfixes\": \"Bugfixes\",\n                \"features\": \"Features\",\n                \"reset-all\": \"Reset All\"\n            },\n            \"controls\": {\n                \"_header\": \"Controls\"\n            },\n            \"episode-options\": {\n                \"_header\": \"Episode Options\",\n                \"creator-compat\": {\n                    \"_name\": \"Creator compat\",\n                    \"_tooltip\": \"Logic tweaks by content creator\",\n                    \"disable\": \"Disable\",\n                    \"enable\": \"Enable\",\n                    \"file-only\": \"File only\"\n                },\n                \"playstyle\": {\n                    \"_name\": \"Playstyle\",\n                    \"classic\": \"Classic\",\n                    \"classic-tip\": \"Minimal updates\",\n                    \"modern\": \"Modern\",\n                    \"modern-tip\": \"All updates\",\n                    \"vanilla\": \"Vanilla\",\n                    \"vanilla-tip\": \"No updates\"\n                },\n                \"hub-resume\": \"Save warp in hub\",\n                \"hub-resume-tip\": \"Resume play at the last warp you entered\"\n            },\n            \"main\": {\n                \"_header\": \"Main\",\n                \"background-work\": \"Run in background\",\n                \"background-work-tip\": \"Play with joystick while game is unfocused\",\n                \"four-screen-mode\": {\n                    \"_name\": \"4P screen mode\",\n                    \"shared\": \"Shared\",\n                    \"split\": \"Split\"\n                },\n                \"frame-skip\": \"Frameskip\",\n                \"discord-rpc\": \"Discord Integration\",\n                \"language\": \"Language\",\n                \"multiplayer\": \"Multiplayer\",\n                \"timing\": \"Frame Timing\",\n                \"two-screen-mode\": {\n                    \"_name\": \"2P screen mode\",\n                    \"shared\": \"Shared\",\n                    \"smbx\": \"SMBX\",\n                    \"split\": \"Left/Right\",\n                    \"topbottom\": \"Top/Bottom\"\n                },\n                \"unlimited-framerate\": \"Unlimited framerate\",\n                \"vsync\": \"V-Sync\"\n            },\n            \"video\": {\n                \"_header\": \"Video\",\n                \"battery-status\": {\n                    \"_name\": \"Device battery status\",\n                    \"fullscreen\": \"Fullscreen\",\n                    \"fullscreen-tip\": \"Show when the game is fullscreen\",\n                    \"low\": \"Low\",\n                    \"low-tip\": \"Show when the battery is low\",\n                    \"off\": \"Off\",\n                    \"on\": \"Always\"\n                },\n                \"display-controllers\": \"Controls activity\",\n                \"effects\": \"Screen Effects\",\n                \"enable-inter-level-fade-effect\": \"Fade transitions\",\n                \"fullscreen\": \"Fullscreen\",\n                \"info-game\": \"Onscreen game info\",\n                \"info-meta\": \"Onscreen meta info\",\n                \"info-run\": \"Onscreen run info\",\n                \"internal-res\": {\n                    \"3ds\": \"800x480 (3DS)\",\n                    \"_name\": \"Screen size\",\n                    \"_tooltip\": \"Resolution of gameplay field\",\n                    \"dynamic\": \"Dynamic\",\n                    \"gba\": \"480x320 (GBA)\",\n                    \"hd\": \"1280x720 (HD)\",\n                    \"hello\": \"768x432 (HELLO)\",\n                    \"nds\": \"512x384 (NDS)\",\n                    \"smbx\": \"800x600 (SMBX)\",\n                    \"smbx-wide\": \"SMBX (wide)\",\n                    \"snes\": \"512x448 (SNES)\",\n                    \"vga\": \"640x480 (VGA)\"\n                },\n                \"scale-mode\": {\n                    \"0_5x\": \"0.5x\",\n                    \"1x\": \"1x\",\n                    \"2x\": \"2x\",\n                    \"_name\": \"Scale mode\",\n                    \"integer\": \"Integer\",\n                    \"linear\": \"Smooth\",\n                    \"nearest\": \"Nearest\",\n                    \"full\": \"Full\",\n                    \"center\": \"Center\",\n                    \"3x\": \"3x\"\n                },\n                \"show-episode-title\": {\n                    \"_name\": \"Episode name\",\n                    \"bottom\": \"Bottom\",\n                    \"bottom-tip\": \"Show above speedrun timer\",\n                    \"off\": \"Off\",\n                    \"top\": \"Top\",\n                    \"top-tip\": \"Show above HUD at high resolution\"\n                },\n                \"3d-compat-mode\": \"3D compat mode\",\n                \"3d-compat-mode-tip\": \"Draw all objects in one 3D plane\",\n                \"show-fails-counter\": \"Fails counter\",\n                \"show-fails-counter-tip\": \"Show fails on the episode and current level\",\n                \"show-fps\": \"Framerate\",\n                \"show-medals-counter\": \"Medals counter\",\n                \"show-medals-counter-tip\": \"Show medals in HUD and at level entrance\",\n                \"show-playtime-counter\": {\n                    \"_name\": \"Playtime counter\",\n                    \"_tooltip\": \"Show time spent on the episode and current attempt\",\n                    \"animated\": \"Animated\",\n                    \"animated-tip\": \"Adds a rainbow effect on level end\",\n                    \"off\": \"Off\",\n                    \"off-tip\": \"Show only during speedruns\",\n                    \"opaque\": \"Opaque\",\n                    \"transparent\": \"Transparent\"\n                },\n                \"show-screen-shake\": \"Screen shake\",\n                \"video-system\": \"System\",\n                \"world-map-stars-show\": \"World map stars\",\n                \"world-map-stars-show-tip\": \"Show collected stars above levels\"\n            },\n            \"view-credits\": {\n                \"_header\": \"View Credits\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"0\": \"0\",\n                    \"1\": \"1\",\n                    \"2\": \"2\",\n                    \"3\": \"3\",\n                    \"_name\": \"Speedrun mode\"\n                }\n            }\n        },\n        \"wordBack\": \"Back\",\n        \"wordHide\": \"Hide\",\n        \"wordNo\": \"No\",\n        \"wordOff\": \"Off\",\n        \"wordOn\": \"On\",\n        \"wordPlayer\": \"Player\",\n        \"wordProfile\": \"Profile\",\n        \"wordResume\": \"Resume\",\n        \"wordShow\": \"Show\",\n        \"wordWaiting\": \"Waiting\",\n        \"wordYes\": \"Yes\"\n    },\n    \"outro\": {\n        \"cppPortDevelopers\": \"C++ port developers:\",\n        \"customSprites\": \"Custom Sprites:\",\n        \"engineCredits\": \"Engine credits:\",\n        \"gameCredits\": \"Game credits:\",\n        \"levelDesign\": \"Level Design:\",\n        \"nameAndrewSpinks\": \"Andrew Spinks\",\n        \"nameVitalyNovichkov\": \"Vitaly Novichkov\",\n        \"originalBy\": \"Original VB6 code By:\",\n        \"psVitaPortBy\": \"PS Vita Port By:\",\n        \"qualityControl\": \"Quality Control:\",\n        \"specialThanks\": \"Special Thanks:\"\n    },\n    \"pluralRules\": \"one-is-singular\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_es.json",
    "content": "{\n    \"editor\": {\n        \"block\": {\n            \"canBreak\": \"Se rompe\",\n            \"canBreakTooltip\": \"Legado: se rompe cuando es golpeado\",\n            \"inside\": \"Tiene:\",\n            \"invis\": \"Invis\",\n            \"slick\": \"Resbala\",\n            \"pickContents\": \"Elegir contenido del bloque\",\n            \"letterHeight\": \"Al\",\n            \"letterWidth\": \"An\"\n        },\n        \"browser\": {\n            \"askOverwriteFile\": \"Sobreescribir {0}?\",\n            \"itemNewFile\": \"<Nuevo archivo>\",\n            \"itemNewFolder\": \"<Nueva carpeta>\",\n            \"newFile\": \"Nuevo archivo\",\n            \"openFile\": \"Abrir archivo\",\n            \"saveFile\": \"Guardar archivo\"\n        },\n        \"events\": {\n            \"controlsForEventN\": \"Controles para evento {0}\",\n            \"header\": \"Eventos:\",\n            \"deletion\": {\n                \"cancel\": \"No: No eliminar evento\",\n                \"confirm\": \"Sí: Eliminar evento\",\n                \"deletingEvent\": \"Eliminando evento {0}\"\n            },\n            \"desc\": {\n                \"death\": \"NPC muere\",\n                \"destroy\": \"Bloque es destruido\",\n                \"hit\": \"Bloque es golpeado\",\n                \"phraseTriggersWhenTemplate\": \"{0} desencadena cuando {1}\",\n                \"talk\": \"Jugador habla a NPC\",\n                \"phraseTriggersAfterTemplate\": \"{0} se activa {1} ms después del evento {2}\",\n                \"activate\": \"NPC entra en la pantalla\",\n                \"enter\": \"se ingresa al pasaje\",\n                \"layerClear\": \"todo en la capa de objetos se ha ido\"\n            },\n            \"itemNewEvent\": \"<Nuevo evento>\",\n            \"label\": {\n                \"activate\": \"Activar\",\n                \"destroy\": \"Destruir\",\n                \"death\": \"Morir\",\n                \"enter\": \"Ingresar\",\n                \"hit\": \"Golpe\",\n                \"next\": \"Siguiente\",\n                \"talk\": \"Hablar\",\n                \"layerClear\": \"Capa limpia\"\n            },\n            \"sections\": {\n                \"phraseAllSections\": \"Todas las secciones\",\n                \"propMusic\": \"Música\",\n                \"actionReset\": \"Reiniciar\",\n                \"actionSet\": \"Establecer\",\n                \"actionKeep\": \"Mantener\",\n                \"propBackground\": \"Fondo\",\n                \"propBounds\": \"Límites\"\n            },\n            \"headerTriggerEvent\": \"Activa evento:\",\n            \"letter\": {\n                \"hit\": \"G:\",\n                \"death\": \"M:\",\n                \"talk\": \"H:\",\n                \"enter\": \"I:\",\n                \"destroy\": \"D:\",\n                \"layerClear\": \"C:\",\n                \"activate\": \"A:\"\n            },\n            \"promptEventText\": \"Texto del evento\",\n            \"props\": {\n                \"autostart\": \"Comenzar Automáticamente\",\n                \"controls\": \"Controles\",\n                \"endGame\": \"Final del Juego\",\n                \"sound\": \"Sonido\",\n                \"layerSmoke\": \"Humo\"\n            },\n            \"settingsForEvent\": \"Ajustes para el Evento {0}\",\n            \"bounds\": {\n                \"shouldEvent\": \"Debería el Evento {0}\",\n                \"changeAllSectionBoundsToCurrent\": \"Cambiar todos los límites de la sección a los actuales?\",\n                \"changeSectionBoundsToCurrent\": \"Cambiar los límites de la sección {0} a los actuales?\"\n            },\n            \"promptEventName\": \"Nombre del evento\",\n            \"layers\": {\n                \"headerMove\": \"Mover:\",\n                \"headerHide\": \"Esconde:\",\n                \"headerToggle\": \"Alternar:\",\n                \"headerShow\": \"Muestra:\"\n            }\n        },\n        \"file\": {\n            \"actionExit\": \"Salir\",\n            \"actionOpen\": \"Abrir\",\n            \"actionRevert\": \"Revertir\",\n            \"commandNew\": \"Nuevo\",\n            \"commandOpen\": \"Abrir...\",\n            \"commandSave\": \"Guardar\",\n            \"formatModern\": \"Moderno\",\n            \"optionCancelConversion\": \"Cancelar conversión\",\n            \"commandSaveAs\": \"Guardar como...\",\n            \"confirmConfirmAction\": \"¿Estás seguro de que quieres {0}?\",\n            \"formatLegacy\": \"Legado\",\n            \"optionActionWithoutSave\": \"{0} sin guardar\",\n            \"optionProceedWithConversion\": \"Proceder con la conversión\",\n            \"sectionLevel\": \"Nivel\",\n            \"sectionWorld\": \"Mundo\",\n            \"confirmConvertFormatTo\": \"Convertir formato a {0}?\",\n            \"confirmSaveBeforeAction\": \"¿Guardar antes de {0}?\",\n            \"labelFormat\": \"Formato:\",\n            \"optionCancelAction\": \"Cancelar: no {0}\",\n            \"optionYesSaveThenAction\": \"Sí: guardar entonces {0}\",\n            \"actionClearWorld\": \"Mundo terminado\",\n            \"actionClearLevel\": \"Nivel terminado\",\n            \"labelCurrentFile\": \"Archivo actual: \",\n            \"convert\": {\n                \"descNew\": \"La extensión de archivo cambiará, pero el archivo viejo NO SERÁ ELIMINADO.\\n\\nPor favor, revise las características perdidas después de guardar.\"\n            }\n        },\n        \"layers\": {\n            \"deletion\": {\n                \"cancel\": \"Cancelar: No eliminar capa\",\n                \"header\": \"Eliminando capa {0}\",\n                \"confirmDelete\": \"No: *BORRAR TODO EL CONTENIDO*\",\n                \"preserveLayerContents\": \"¿Conservar el contenido de las capas?\",\n                \"confirmPreserve\": \"Sí: pasar a la capa por defecto\"\n            },\n            \"header\": \"Capas:\",\n            \"itemNewLayer\": \"<Nueva capa>\",\n            \"label\": \"Capa:\",\n            \"promptLayerName\": \"Nombre de la capa\",\n            \"labelAttachedLayer\": \"Capa adjunta:\",\n            \"labelMoveLayer\": \"Mover capa:\",\n            \"desc\": {\n                \"att\": \"Cada vez que el NPC se mueve, la capa adjunta se mueve siguiéndolo\"\n            },\n            \"default\": \"Por defecto\",\n            \"abbrevAttLayer\": \"Att: \",\n            \"labelAttached\": \"Adjunto:\"\n        },\n        \"level\": {\n            \"levelName\": \"Nombre del nivel\",\n            \"startPos\": \"Pos Inicio\",\n            \"pathBG\": \"Ruta BG\",\n            \"pathUnlocks\": \"Desbloqueo de rutas\",\n            \"gameStart\": \"Inicio del juego\",\n            \"alwaysVis\": \"Siempre Vis\",\n            \"bigBG\": \"Gran BG\"\n        },\n        \"wordEvent\": {\n            \"typeLabel\": \"Evento {0}:\",\n            \"nominative\": \"Evento\",\n            \"genitive\": \"Evento\"\n        },\n        \"warp\": {\n            \"needStarCount\": \"Requiere {0} {1}\",\n            \"placing\": \"Colocación:\",\n            \"to\": \"Hacia: {0}\",\n            \"showStartScene\": \"Mostrar escena de inicio\",\n            \"twoWay\": \"Dos-vías\",\n            \"needFloor\": \"Necesita suelo\",\n            \"style\": {\n                \"door\": \"Puerta\",\n                \"pipe\": \"Tubería\",\n                \"portal\": \"Puerto\",\n                \"style\": \"Estilo: \",\n                \"blipInstant\": \"Instantáneo\"\n            },\n            \"cannonExit\": \"Salida del cañón\",\n            \"transit\": {\n                \"name1\": \"DESPLAZAMIENTO\",\n                \"name3\": \"CÍRCULO\",\n                \"name2\": \"DESVANECER\",\n                \"name0\": \"NADA\",\n                \"name4\": \"VOLTEAR (H)\",\n                \"name5\": \"VOLTEAR (V)\"\n            },\n            \"allow\": \"Permitir:\",\n            \"starLockMessage\": \"Mensaje de bloqueo de estrella\",\n            \"showStarCount\": \"Mostrar recuento de estrellas\",\n            \"effect\": \"Efecto:\",\n            \"target\": \"Objetivo: \",\n            \"speed\": \"Velocidad \",\n            \"toMap\": \"Hacia el Mapa\",\n            \"item\": \"Objeto\",\n            \"needKey\": \"Requiere Llave\",\n            \"in\": \"Entrada\",\n            \"out\": \"Salida\",\n            \"title\": \"Configuración de Pasaje\",\n            \"ride\": \"Montar\",\n            \"lvlWarp\": \"Pasaje de Nivel\",\n            \"dir\": \"Dir.\"\n        },\n        \"letterRight\": \"R\",\n        \"select\": {\n            \"warpTransitEffect\": \"Efecto de transición Warp\",\n            \"soundForEventN\": \"Sonido para Evento {0}\",\n            \"pathBlankUnlock\": \"Ruta {0} Desbloquear Por\",\n            \"worldMusic\": \"Música del mundo\",\n            \"sectionBlankPropBlank\": \"Sección {0} {1}\",\n            \"sectBlankPropBlankForEventN\": \"{1} de Secc {0} para {2}\",\n            \"allSectPropBlankForEventN\": \"{1} de Todas SS para {2}\"\n        },\n        \"tooltip\": {\n            \"BGOs\": \"BGOs\",\n            \"events\": \"Eventos\",\n            \"eraseAll\": \"Borrar todo\",\n            \"music\": \"Música\",\n            \"erase\": \"Borrar\",\n            \"settings\": \"Ajustes\",\n            \"water\": \"Agua\",\n            \"show\": \"Mostrar\",\n            \"paths\": \"Caminos\",\n            \"levels\": \"Niveles\",\n            \"layers\": \"Capas\",\n            \"file\": \"Archivo\",\n            \"select\": \"Seleccione\",\n            \"scenes\": \"Escenas\",\n            \"tiles\": \"Baldosas\",\n            \"warps\": \"Pasajes\",\n            \"blocks\": \"Bloques\",\n            \"area\": \"Área\",\n            \"NPCs\": \"NPCs\"\n        },\n        \"npc\": {\n            \"ai\": {\n                \"aiIs\": \"AI: {0}\",\n                \"target\": \"Objetivo\",\n                \"swim\": \"Nadar\",\n                \"jump\": \"Saltar\",\n                \"leap\": \"Salto\",\n                \"UD\": \"UD\",\n                \"headerCustomAi\": \"IA personalizada:\",\n                \"use1_0Ai\": \"¿Usar IA 1.0?\",\n                \"LR\": \"LR\"\n            },\n            \"tooltipExpandSection\": \"Ampliar sección\",\n            \"stuckStop\": \"Detener\",\n            \"gen\": {\n                \"header\": \"Ajustes del generador\",\n                \"effectShoot\": \"Dispara a\",\n                \"direction\": \"Dirección\",\n                \"effectWarp\": \"Warp\",\n                \"effectIs\": \"Efecto: {0}\"\n            },\n            \"props\": {\n                \"attachSurface\": \"Adjuntar\",\n                \"facing\": \"Frente a\",\n                \"active\": \"Activo\"\n            },\n            \"abbrevGen\": \"Gen\",\n            \"inertNice\": \"Bonito\",\n            \"inContainer\": \"En\"\n        },\n        \"letterLeft\": \"L\",\n        \"testPlay\": {\n            \"magicHand\": \"Mano mágica\",\n            \"power\": \"Potencia\",\n            \"char\": \"Pers\",\n            \"pet\": \"Mascota\",\n            \"boot\": \"Bota\"\n        },\n        \"letterDown\": \"D\",\n        \"mapPos\": \"Mapa Pos:\",\n        \"letterCoordX\": \"X\",\n        \"water\": {\n            \"title\": \"Ajustes de agua\"\n        },\n        \"phraseTextOf\": \"{0} Texto\",\n        \"wordEnabled\": \"Activado\",\n        \"phraseSectionIndex\": \"Sección {0}\",\n        \"wordCoins\": \"Monedas\",\n        \"section\": {\n            \"setBounds\": \"Establecer límites\",\n            \"noTurnBack\": \"Sin vuelta atrás\",\n            \"underwater\": \"Bajo el agua\",\n            \"offscreenExit\": \"Salida fuera de pantalla\",\n            \"scroll\": \"Desplácese por\",\n            \"horizWrap\": \"Env. Horizontal\",\n            \"vertWrap\": \"Env. Vertical\"\n        },\n        \"phraseGenericIndex\": \"Número {0}\",\n        \"letterUp\": \"U\",\n        \"phraseCountMore\": \"{0} Más\",\n        \"phraseRadiusIndex\": \"Radio {0}\",\n        \"pageBlankOfBlank\": \"Página {0} de {1}\",\n        \"phraseWarpIndex\": \"Pasaje {0}\",\n        \"phraseAreYouSure\": \"¿Seguro?\",\n        \"toggleMagicBlock\": \"Bloque mágico\",\n        \"phraseDelayIsMs\": \"Retraso: {0} ms\",\n        \"letterCoordY\": \"Y\",\n        \"wordHeight\": \"Alto\",\n        \"wordWidth\": \"Ancho\",\n        \"wordInstant\": \"Instante\",\n        \"world\": {\n            \"introLevel\": \"Nivel de Introducción\",\n            \"name\": \"Nombre del Mundo\",\n            \"phraseCreditIndex\": \"Línea {0} de Créditos del Mundo:\",\n            \"retryOnFail\": \"Reintentar En Fallo\",\n            \"allowChars\": \"Permitir Personajes\",\n            \"totalStars\": \"Estrellas en total: \",\n            \"hubWorld\": \"Mundo de Centro\"\n        },\n        \"wordMode\": \"Modo\",\n        \"wordText\": \"Texto\",\n        \"labelSortLayer\": \"Ordenar Capa:\",\n        \"labelSortOffset\": \"Ordenar Desplazamiento:\",\n        \"wordNPC\": {\n            \"genitive\": \"NPC\",\n            \"nominative\": \"NPC\"\n        }\n    },\n    \"menu\": {\n        \"game\": {\n            \"gameNoBattleLevels\": \"<Sin niveles de batalla>\",\n            \"gameSourceSlot\": \"Seleccione la ranura de origen\",\n            \"gameEraseSave\": \"Borrar guardar\",\n            \"gameBattleRandom\": \"Nivel aleatorio\",\n            \"gameTargetSlot\": \"Ahora seleccione el objetivo\",\n            \"gameSlotContinue\": \"SLOT {0} ... {1}%\",\n            \"gameSlotNew\": \"SLOT {0} ... NUEVO\",\n            \"phraseScore\": \"Puntuación: {0}\",\n            \"gameNoEpisodesToPlay\": \"<Sin episodios que reproducir>\",\n            \"phraseTime\": \"Tiempo: {0}\",\n            \"gameEraseSlot\": \"Seleccione la ranura que desea borrar\",\n            \"gameCopySave\": \"Copiar guardar\",\n            \"warnEpCompat\": \"Este episodio se hizo para una rama diferente de SMBX y tal vez no funcionará bien.\"\n        },\n        \"editor\": {\n            \"newWorld\": \"<Nuevo Mundo>\",\n            \"errorMissingResources\": \"Lo sentimos. Te falta {0}, necesario para el editor del juego.\",\n            \"promptNewWorldName\": \"Nombre del mundo nuevo\",\n            \"battles\": \"<Niveles de Batalla>\",\n            \"makeFor\": \"Hacer para:\"\n        },\n        \"wordHide\": \"Ocultar\",\n        \"options\": {\n            \"restartEngine\": \"Reinicie el motor para que los cambios surtan efecto.\",\n            \"advanced\": {\n                \"render\": {\n                    \"opengl\": \"OpenGL 3+\",\n                    \"opengles-tip\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"opengl11-tip\": \"\",\n                    \"sdl\": \"SDL2\",\n                    \"opengles\": \"OpenGL ES 2+\",\n                    \"_name\": \"Moda de renderizado\",\n                    \"opengles11\": \"OpenGL ES 1.1+\",\n                    \"opengles11-tip\": \"\",\n                    \"sw\": \"Software\",\n                    \"hw\": \"Auto\",\n                    \"opengl-tip\": \"\",\n                    \"opengl11\": \"OpenGL 1.1\"\n                },\n                \"scale-down-textures\": {\n                    \"all-tip\": \"Menos tartamudeo al cargar que 'Seguras'\",\n                    \"none\": \"Ningunas\",\n                    \"_tooltip\": \"Almacenar las imágenes a 1x para ahorrar la memoria\",\n                    \"all\": \"Todas\",\n                    \"_name\": \"Reducir imágenes\",\n                    \"none-tip\": \"Lo menos tartamudeo al cargar\",\n                    \"safe\": \"Seguras\",\n                    \"safe-tip\": \"Verifica que las imágenes estén en formato 2x\"\n                },\n                \"audio-sample-rate\": {\n                    \"48000\": \"48000 Hz\",\n                    \"32000\": \"32000 Hz\",\n                    \"11025\": \"11025 Hz\",\n                    \"44100\": \"44100 Hz\",\n                    \"_name\": \"Frecuencia de muestreo\",\n                    \"16000\": \"16000 Hz\",\n                    \"22050\": \"22050 Hz\"\n                },\n                \"choose-assets-on-launch\": \"Seleccionar activos en lanzar\",\n                \"log-level\": {\n                    \"_name\": \"Nivel de registrar\",\n                    \"critical\": \"Crítico\",\n                    \"none\": \"Nada\",\n                    \"info\": \"Info\",\n                    \"fatal\": \"Fatal\",\n                    \"warning\": \"Aviso\",\n                    \"debug\": \"Depurar\"\n                },\n                \"audio-channels\": {\n                    \"mono\": \"Mono\",\n                    \"stereo\": \"Stereo\",\n                    \"_name\": \"Canales\"\n                },\n                \"internal-res-4p\": {\n                    \"qhd\": \"2560x1440 (QHD)\",\n                    \"_name\": \"Tamaño de pantalla dividida 4P\",\n                    \"default\": \"Por defecto\",\n                    \"fhd\": \"1920x1080 (FHD)\",\n                    \"qsmbx\": \"1600x1200 (QSMBX)\"\n                },\n                \"webm-recording\": \"Grabación WEBM\",\n                \"advanced-audio\": \"Áudio\",\n                \"advanced-video\": \"Video\",\n                \"_header\": \"Avanzado\",\n                \"_tooltip\": \"Opciones tecnicales para las operaciones internales\",\n                \"record-gameplay-data\": \"Grabar jugación\",\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"Aumenta para menos estallidos pero más retraso\",\n                    \"_name\": \"Tamaño de búfer\"\n                },\n                \"audio-format\": {\n                    \"_name\": \"Formato de áudio\",\n                    \"_tooltip\": \"Formato para controlador de sondio\"\n                },\n                \"audio-enable\": \"Activar\",\n                \"use-native-osk\": \"Teclado del sistema\",\n                \"inaccurate-gifs-tip\": \"Permite efecto 3D cuando hay imagenes GIF\",\n                \"inaccurate-gifs\": \"GIF inexactos\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"audio\": {\n                \"sfx-audio-fx\": \"Efectos de eco\",\n                \"sfx-modern\": \"Sonidos modernos\",\n                \"audio-prefs\": \"Preferencias\",\n                \"sfx-modern-tip\": \"Usar sonidos añadidos en TheXTech\",\n                \"audio-sfx-volume\": \"Volumen de sonidos\",\n                \"sfx-spatial-audio-tip\": \"Reducir el volumen de sonidos lejos\",\n                \"sfx-spatial-audio\": \"Áudio espacial\",\n                \"sfx-pet-beat-tip\": \"Reproducir un ritmo especial a montar una mascota\",\n                \"sfx-pet-beat\": \"Ritmo de mascota\",\n                \"audio-music-volume\": \"Volumen de música\",\n                \"_header\": \"Áudio\"\n            },\n            \"episode-options\": {\n                \"creator-compat\": {\n                    \"_name\": \"Compat de creador\",\n                    \"file-only\": \"Sólo archivo\",\n                    \"enable\": \"Activar\",\n                    \"disable\": \"Desactivar\",\n                    \"_tooltip\": \"Ajustes logicales por el creador del contenido\"\n                },\n                \"_header\": \"Opciones de Episodio\",\n                \"playstyle\": {\n                    \"_name\": \"Estilo de juego\",\n                    \"modern\": \"Moderno\",\n                    \"classic\": \"Clásico\",\n                    \"classic-tip\": \"Actualizaciones minimales\",\n                    \"vanilla-tip\": \"Ningunas actualizaciones\",\n                    \"modern-tip\": \"Todas actualizaciones\",\n                    \"vanilla\": \"Vainilla\"\n                },\n                \"hub-resume-tip\": \"Resumir el juego en la próxima pasaja ingresada\",\n                \"hub-resume\": \"Grabar pasaja en centro\"\n            },\n            \"video\": {\n                \"effects\": \"Efectos de Pantalla\",\n                \"enable-inter-level-fade-effect\": \"Transiciones\",\n                \"info-meta\": \"Info. en pantalla de sistema\",\n                \"internal-res\": {\n                    \"vga\": \"640x480 (VGA)\",\n                    \"snes\": \"512x448 (SNES)\",\n                    \"hd\": \"1280x720 (HD)\",\n                    \"_tooltip\": \"Resolución del campo de jugación\",\n                    \"dynamic\": \"Dinámico\",\n                    \"hello\": \"768x432 (HELLO)\",\n                    \"gba\": \"480x320 (GBA)\",\n                    \"_name\": \"Tamaño de pantalla\",\n                    \"3ds\": \"800x480 (3DS)\",\n                    \"smbx\": \"800x600 (SMBX)\",\n                    \"smbx-wide\": \"SMBX (ancha)\",\n                    \"nds\": \"512x384 (NDS)\"\n                },\n                \"scale-mode\": {\n                    \"1x\": \"1x\",\n                    \"0_5x\": \"0,5x\",\n                    \"_name\": \"Modo de escala\",\n                    \"2x\": \"2x\",\n                    \"nearest\": \"Cercana\",\n                    \"full\": \"Completa\",\n                    \"center\": \"Centrada\",\n                    \"linear\": \"Lineal\",\n                    \"integer\": \"Entera\",\n                    \"3x\": \"\"\n                },\n                \"info-game\": \"Info. en pantalla de juego\",\n                \"show-playtime-counter\": {\n                    \"off-tip\": \"Muestra sólo en las carreras rápidas\",\n                    \"_tooltip\": \"Muestra el tiempo pasado en el episodio y el intento actual\",\n                    \"opaque\": \"Opaco\",\n                    \"_name\": \"Temporizador de carrera\",\n                    \"transparent\": \"Transparente\",\n                    \"animated-tip\": \"Añade un efecto arcoiris a completar un nivel\",\n                    \"animated\": \"Animado\",\n                    \"off\": \"Desactivado\"\n                },\n                \"3d-compat-mode-tip\": \"Dibuja todos objetos en un solo plano 3D\",\n                \"show-fails-counter\": \"Contador de fallos\",\n                \"show-episode-title\": {\n                    \"_name\": \"Nombre del episodio\",\n                    \"top\": \"Superior\",\n                    \"top-tip\": \"Muestra sobre HUD a alta resolución\",\n                    \"off\": \"Desactivado\",\n                    \"bottom-tip\": \"Muestra sobre temporizador de carrera\",\n                    \"bottom\": \"Inferior\"\n                },\n                \"video-system\": \"Sistema\",\n                \"world-map-stars-show-tip\": \"Muestra estrellas recogidas sobre los niveles\",\n                \"world-map-stars-show\": \"Estrellas en mapa mundial\",\n                \"battery-status\": {\n                    \"fullscreen\": \"Pantalla completa\",\n                    \"_name\": \"Estado de pila\",\n                    \"off\": \"Nunca\",\n                    \"fullscreen-tip\": \"Muestra cuando el juego está en pantalla completa\",\n                    \"on\": \"Siempre\",\n                    \"low\": \"Bajo\",\n                    \"low-tip\": \"Muestra cuando el nivel de la pila está bajo\"\n                },\n                \"_header\": \"Video\",\n                \"show-medals-counter\": \"Contador de medallas\",\n                \"show-fps\": \"Frecuencia de cuadros\",\n                \"show-medals-counter-tip\": \"Muestra las medallas en el HUD y en las entradas de nivel\",\n                \"show-fails-counter-tip\": \"Muestra los fallos en el episodio y el nivel actual\",\n                \"info-run\": \"Info. en pantalla de carrera\",\n                \"3d-compat-mode\": \"Modo de compat 3D\",\n                \"fullscreen\": \"Pantalla completa\",\n                \"show-screen-shake\": \"Movimiento de pantalla\",\n                \"display-controllers\": \"Actividad de controles\"\n            },\n            \"view-credits\": {\n                \"_header\": \"Ver Créditos\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"_name\": \"Modo de carrera rápida\",\n                    \"3\": \"3\",\n                    \"2\": \"2\",\n                    \"0\": \"0\",\n                    \"1\": \"1\"\n                }\n            },\n            \"compatibility\": {\n                \"_header\": \"Ajustes de sesión\",\n                \"_tooltip\": \"Opciones para compat o depuración\",\n                \"reset-all\": \"Restablecar todo\",\n                \"features\": \"Funciones nuevas\",\n                \"bugfixes\": \"Corrección de errores\",\n                \"autocode\": \"Autocode\"\n            },\n            \"controls\": {\n                \"_header\": \"Controles\"\n            },\n            \"main\": {\n                \"four-screen-mode\": {\n                    \"_name\": \"Modo de pantalla 4P\",\n                    \"split\": \"Dividida\",\n                    \"shared\": \"Compartida\"\n                },\n                \"background-work-tip\": \"Jugar con joystick cuando el juego está en segundo plano\",\n                \"background-work\": \"Continuar en fondo\",\n                \"vsync\": \"V-Sync\",\n                \"discord-rpc\": \"Integración con Discord\",\n                \"frame-skip\": \"Saltado de cuadro\",\n                \"_header\": \"Principal\",\n                \"language\": \"Idioma\",\n                \"two-screen-mode\": {\n                    \"_name\": \"Modo de pantalla 2P\",\n                    \"split\": \"Izq/Der\",\n                    \"topbottom\": \"Arriba/Abajo\",\n                    \"smbx\": \"SMBX\",\n                    \"shared\": \"Compartida\"\n                },\n                \"timing\": \"Sincronización de Cuadros\",\n                \"multiplayer\": \"Multijugador\",\n                \"unlimited-framerate\": \"Modo turbo\"\n            }\n        },\n        \"wordWaiting\": \"En espera\",\n        \"main\": {\n            \"mainEditor\": \"Editor\",\n            \"main1PlayerGame\": \"Juego para 1 jugador\",\n            \"mainMultiplayerGame\": \"Juego para 2 jugadores\",\n            \"mainBattleGame\": \"Juego de batalla\",\n            \"mainOptions\": \"Opciones\",\n            \"mainExit\": \"Salir\",\n            \"mainPlayEpisode\": \"Jugar Episodio\"\n        },\n        \"controls\": {\n            \"wii\": {\n                \"wiimote\": {\n                    \"caseIR\": \"(IR)\",\n                    \"buttonPlus\": \"+\",\n                    \"buttonMinus\": \"-\",\n                    \"buttonHome\": \"Inicio\",\n                    \"shake\": \"Agitar\",\n                    \"dPad\": \"D-Pad\",\n                    \"buttonA\": \"A\",\n                    \"buttonB\": \"B\",\n                    \"button1\": \"1\",\n                    \"button2\": \"2\"\n                },\n                \"classic\": {\n                    \"buttonLT\": \"LT\",\n                    \"buttonY\": \"Y\",\n                    \"rStick\": \"R-Joy\",\n                    \"buttonX\": \"X\",\n                    \"buttonZR\": \"ZR\",\n                    \"buttonRT\": \"RT\",\n                    \"buttonZL\": \"ZL\",\n                    \"lStick\": \"L-Joy\"\n                },\n                \"nunchuck\": {\n                    \"prefixN\": \"N\",\n                    \"buttonZ\": \"Z\",\n                    \"buttonC\": \"C\"\n                },\n                \"typeClassic\": \"Classic\",\n                \"typeNunchuck\": \"Nunchuk\",\n                \"phraseNewNunchuck\": \"Nuevo Perfil Nunchuk\",\n                \"typeWiimote\": \"Wii Remote\",\n                \"phraseNewClassic\": \"Nuevo Perfil Classic\",\n                \"typeGamecube\": \"\"\n            },\n            \"wordProfiles\": \"Perfiles\",\n            \"wordButtons\": \"Botones\",\n            \"buttons\": {\n                \"altJump\": \"Salto Alt\",\n                \"start\": \"Comenzar\",\n                \"down\": \"Abajo\",\n                \"jump\": \"Saltar\",\n                \"drop\": \"Soltar Objeto\",\n                \"run\": \"Correr\",\n                \"altRun\": \"Correr Alt\",\n                \"up\": \"Arriba\",\n                \"left\": \"Izquierda\",\n                \"right\": \"Derecha\"\n            },\n            \"controlsInUse\": \"(En uso)\",\n            \"controlsNewProfile\": \"<Nuevo Perfil>\",\n            \"controlsDeleteKey\": \"(Salto Alt para Borrar)\",\n            \"controlsDeleteKey2\": \"(Correr Alt para Borrar)\",\n            \"controlsNotInUse\": \"(No en uso)\",\n            \"tDS\": {\n                \"buttonStart\": \"Comenzar\",\n                \"casePen\": \"(Lápiz)\",\n                \"cStick\": \"Palanca C\",\n                \"buttonL\": \"L\",\n                \"buttonB\": \"B\",\n                \"buttonR\": \"R\",\n                \"dPad\": \"Cruz de control\",\n                \"buttonY\": \"Y\",\n                \"buttonZR\": \"ZR\",\n                \"buttonZL\": \"ZL\",\n                \"buttonA\": \"A\",\n                \"buttonSelect\": \"Select\",\n                \"buttonX\": \"X\",\n                \"tStick\": \"Deslizante\"\n            },\n            \"controlsDeviceTypes\": \"Tipos de Dispositivo\",\n            \"caseInvalid\": \"(Inválido)\",\n            \"controlsReallyDeleteProfile\": \"¿Realmente eliminar perfil?\",\n            \"controlsTitle\": \"Controles\",\n            \"touchscreen\": {\n                \"option\": {\n                    \"holdRun\": \"Mantener \\\"Correr\\\" al Comenzar\",\n                    \"showCodeButton\": \"Mostrar Botón de Código\",\n                    \"feedbackLength\": \"Duración de Vibración\",\n                    \"interfaceStyle\": \"Estilo de Botones\",\n                    \"resetLayout\": \"Restablecer Diseño\",\n                    \"layoutStyle\": \"Estilo de Diseño\",\n                    \"feedbackStrength\": \"Fuerza de Vibración\",\n                    \"scaleButtons\": \"Escala de Botones\",\n                    \"scaleDPad\": \"Escala de Cruceta\",\n                    \"sStartSpacing\": \"S-Start Espaciado\",\n                    \"scaleFactor\": \"Factor de Escala\"\n                },\n                \"layout\": {\n                    \"standard\": \"Estándar\",\n                    \"phabletOld\": \"Phablet (Legado)\",\n                    \"tight\": \"Ajustado\",\n                    \"tabletOld\": \"Tableta (Legado)\",\n                    \"phoneOld\": \"Móbil (Legado)\",\n                    \"tinyOld\": \"Diminuto (Old)\",\n                    \"longOld\": \"Largo (Legado)\"\n                },\n                \"style\": {\n                    \"ABXY\": \"ABXY\",\n                    \"actions\": \"Acciones\",\n                    \"XODA\": \"XODA\"\n                }\n            },\n            \"cursor\": {\n                \"secondary\": \"Secundario\",\n                \"tertiary\": \"Terciario\",\n                \"primary\": \"Primario\",\n                \"left\": \"Ratón Izquierdo\",\n                \"right\": \"Ratón Derecho\",\n                \"down\": \"Ratón Abajo\",\n                \"up\": \"Ratón Arriba\"\n            },\n            \"hotkeys\": {\n                \"screenshot\": \"Captura de Pantalla\",\n                \"debugInfo\": \"Información de Depuración\",\n                \"enterCheats\": \"Introducir Código\",\n                \"fullscreen\": \"Pantalla Completa\",\n                \"toggleHUD\": \"Alternar HUD\",\n                \"recordGif\": \"Grabar GIF\",\n                \"vanillaCam\": \"Vainilla Cam\",\n                \"legacyPause\": \"Pausa Legada\"\n            },\n            \"profile\": {\n                \"deleteProfile\": \"Eliminar perfil\",\n                \"cursorControls\": \"Controles de cursor\",\n                \"playerControls\": \"Controles del juego\",\n                \"renameProfile\": \"Renombrar perfil\",\n                \"editorControls\": \"Controles de editor\",\n                \"hotkeys\": \"Teclas rápidas\"\n            },\n            \"types\": {\n                \"keyboard\": \"Teclado\",\n                \"touchscreen\": \"Pantalla Táctil\",\n                \"oldJoystick\": \"Joy Legado\",\n                \"gamepad\": \"Joystick\"\n            },\n            \"caseTouch\": \"(Táctil)\",\n            \"options\": {\n                \"batteryStatus\": \"Estado de Batería\",\n                \"rumble\": \"Vibración\",\n                \"textEntryStyle\": \"Estilo de entrada de texto\",\n                \"maxPlayers\": \"Max de Jugadores\"\n            },\n            \"editor\": {\n                \"testPlay\": \"Juego de Prueba\",\n                \"scrollLeft\": \"Despl Izq\",\n                \"scrollRight\": \"Despl Der\",\n                \"scrollDown\": \"Despl Abajo\",\n                \"modeErase\": \"Modo Borrar\",\n                \"switchScreens\": \"Mostrar Panel\",\n                \"fastScroll\": \"Apurarse\",\n                \"modeSelect\": \"Modo Sel.\",\n                \"nextSection\": \"Secc Prox\",\n                \"prevSection\": \"Secc Ult\",\n                \"scrollUp\": \"Despl Arr\"\n            },\n            \"controlsConnected\": \"Conectado:\",\n            \"joystickSimpleEditor\": \"Controles de Editor Sensillos\",\n            \"caseMouse\": \"(Ratón)\",\n            \"phraseNewProfOldJoy\": \"Perfil nuevo para joystick legado\"\n        },\n        \"wordPlayer\": \"Jugador\",\n        \"wordBack\": \"Volver\",\n        \"wordOn\": \"Encendido\",\n        \"wordProfile\": \"Perfil\",\n        \"loading\": \"Cargando...\",\n        \"wordShow\": \"Mostrar\",\n        \"wordNo\": \"No\",\n        \"wordYes\": \"Si\",\n        \"wordOff\": \"Apagado\",\n        \"character\": {\n            \"selectCharacter\": \"Juego de {0}\"\n        },\n        \"battle\": {\n            \"errorNoLevels\": \"Imposible comenzar batalla debido a que no hay niveles disponibles\"\n        },\n        \"caseNone\": \"<Ninguno>\",\n        \"wordResume\": \"Reanudar\",\n        \"abbrevMilliseconds\": \"MS\"\n    },\n    \"outro\": {\n        \"originalBy\": \"Código original VB6 Por:\",\n        \"cppPortDevelopers\": \"Desarrolladores de puertos C++:\",\n        \"nameAndrewSpinks\": \"Andrew Spinks\",\n        \"customSprites\": \"Sprites personalizados:\",\n        \"specialThanks\": \"Agradecimientos especiales:\",\n        \"psVitaPortBy\": \"Puerto PS Vita Por:\",\n        \"engineCredits\": \"Créditos del motor:\",\n        \"levelDesign\": \"Diseño de niveles:\",\n        \"nameVitalyNovichkov\": \"Vitaly Novichkov\",\n        \"gameCredits\": \"Créditos del juego:\",\n        \"qualityControl\": \"Control de Calidad:\"\n    },\n    \"pluralRules\": \"one-is-singular\",\n    \"game\": {\n        \"connect\": {\n            \"phraseTestControls\": \"Probar Controles\",\n            \"phraseChangeChar\": \"Cambiar Personaje\",\n            \"phrasePressAButton\": \"Presiona un Botón\",\n            \"splitPressSelect_2\": \"Opciones de Controles\",\n            \"dropAddTitle\": \"Quitar/Añadir Jugadores\",\n            \"phraseStartToForceRes\": \"Presiona Comenzar para Forzar Resumir\",\n            \"splitPressSelect_1\": \"Presiona Select para\",\n            \"wordDisconnect\": \"Desconectar\",\n            \"phraseStartToResume\": \"Presiona Comenzar para Resumir\",\n            \"phraseHoldStart\": \"Mantén Comenzar\",\n            \"phraseWaitingForInput\": \"Esperando un dispositivo de entrada...\",\n            \"reconnectTitle\": \"Reconectar\",\n            \"phraseSetControls\": \"Establecer Controles\",\n            \"phraseDropMe\": \"Soltarme\",\n            \"phraseDropPX\": \"Soltar a P{0}\",\n            \"phraseTestProfile\": \"Probar Perfil\",\n            \"phraseForceResume\": \"Forzar Resumir\"\n        },\n        \"error\": {\n            \"errorInvalidEnterWarp\": \"Imposible comenzar el nivel debido a que un pasaje de entrada inválido {1} fue especificado.\\nTotal Pasajes de Entrada: {2}\\n\\nArchivo: {0}\",\n            \"openFileFailed\": \"Imposible abrir \\\"{0}\\\": El archivo está corrupto o no existe.\",\n            \"warpNeedStarCount\": \"Necesitas {0} {1} para entrar.\",\n            \"errorNoStartPoint\": \"Imposible empezar nivel debido a que no hay puntos de comienzo colocados ni pasajes de entrada especificados.\\n\\nArchivo: {0}\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"pause\": {\n            \"restartLevel\": \"Reiniciar Nivel\",\n            \"quit\": \"Salir\",\n            \"saveAndContinue\": \"Guardar y Continuar\",\n            \"resetCheckpoints\": \"Reiniciar Puntos de Control\",\n            \"saveAndQuit\": \"Guardar y Salir\",\n            \"returnToEditor\": \"Regresar al Editor\",\n            \"continue\": \"Continuar\",\n            \"quitTesting\": \"Dejar de Probar\",\n            \"enterCode\": \"Introducir Código\",\n            \"playerSetup\": \"Ordenar Jugadores\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"Escaneando niveles...\"\n        },\n        \"hint\": {\n            \"no-lives-old\": \"Caución - ¡el final se acerca!\",\n            \"gray-bricks\": \"Algunos bloques se pueden romper por poderes especiales.\",\n            \"char5-bombs\": \"Presiona Correr para recogerlas y Correr Alt para tirarlas.\",\n            \"heavy-duck\": \"¡Agáchate para bloquear muchas - pero no todas - llamas!\",\n            \"rainbow-surf\": \"Cógelo, corre, mantén pulsado abajo, y déjalo para hacer surf.\",\n            \"no-lives-new\": \"¿No tienes moneda? ¡Toma un préstamo! Pero cuídate el puntaje...\",\n            \"shoe-block\": \"¡Ponerse esto bloquea muchas - pero no todas - llamas!\",\n            \"pound-altrun\": \"¡Presiona Correr Alt para golpear el suelo!\",\n            \"pound-down\": \"¡Presiona Abajo para golpear el suelo!\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0}mm{1}ss\"\n        },\n        \"msgbox\": {\n            \"sysInfoWarning\": \"¡Aviso!\",\n            \"sysInfoTitle\": \"Información\",\n            \"sysInfoError\": \"¡Error!\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"languageName\": \"Español\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_fr.json",
    "content": "{\n    \"editor\": {\n        \"file\": {\n            \"convert\": {\n                \"descNew\": \"L'extension de fichier va changer mais l'ancien fichier ne vas PAS être supprimé.\\n\\nVeuillez vérifier des éventuelles pertes après sauvegarde.\"\n            },\n            \"actionClearWorld\": \"Écraser Monde\",\n            \"labelCurrentFile\": \"Fichier actuel :\",\n            \"labelFormat\": \"Format :\",\n            \"formatLegacy\": \"Classique\",\n            \"sectionWorld\": \"Monde\",\n            \"optionCancelConversion\": \"Annuler conversion\",\n            \"formatModern\": \"Moderne\",\n            \"sectionLevel\": \"Niveau\",\n            \"optionCancelAction\": \"Annuler : ne pas {0}\",\n            \"optionProceedWithConversion\": \"Continuer conversion\",\n            \"optionYesSaveThenAction\": \"Oui : sauvegarder puis {0}\",\n            \"actionRevert\": \"Annuler\",\n            \"commandSave\": \"Sauvegarder\",\n            \"commandNew\": \"Nouveau\",\n            \"actionOpen\": \"Ouvrir\",\n            \"actionExit\": \"Quitter\",\n            \"commandSaveAs\": \"Sauv. en tant que...\",\n            \"confirmSaveBeforeAction\": \"Sauvegarder avant d' {0} ?\",\n            \"actionClearLevel\": \"Écraser Niveau\",\n            \"confirmConvertFormatTo\": \"Convertir le format en {0} ?\",\n            \"optionActionWithoutSave\": \"{0} sans sauvegarder\",\n            \"confirmConfirmAction\": \"Êtes-vous sûr de vouloir {0} ?\",\n            \"commandOpen\": \"Ouvrir...\"\n        },\n        \"level\": {\n            \"startPos\": \"\",\n            \"alwaysVis\": \"\",\n            \"bigBG\": \"\",\n            \"pathBG\": \"\",\n            \"levelName\": \"\",\n            \"pathUnlocks\": \"\",\n            \"gameStart\": \"\"\n        },\n        \"mapPos\": \"\",\n        \"phraseDelayIsMs\": \"\",\n        \"phraseCountMore\": \"\",\n        \"section\": {\n            \"scroll\": \"\",\n            \"setBounds\": \"\",\n            \"vertWrap\": \"\",\n            \"offscreenExit\": \"\",\n            \"noTurnBack\": \"\",\n            \"underwater\": \"\",\n            \"horizWrap\": \"\"\n        },\n        \"warp\": {\n            \"style\": {\n                \"blipInstant\": \"\",\n                \"pipe\": \"\",\n                \"door\": \"\",\n                \"style\": \"\",\n                \"portal\": \"\"\n            },\n            \"needStarCount\": \"\",\n            \"placing\": \"\",\n            \"effect\": \"\",\n            \"dir\": \"\",\n            \"out\": \"\",\n            \"cannonExit\": \"\",\n            \"ride\": \"\",\n            \"item\": \"\",\n            \"allow\": \"\",\n            \"in\": \"\",\n            \"showStartScene\": \"\",\n            \"showStarCount\": \"\",\n            \"speed\": \"\",\n            \"starLockMessage\": \"\",\n            \"target\": \"\",\n            \"toMap\": \"\",\n            \"transit\": {\n                \"name0\": \"\",\n                \"name1\": \"\",\n                \"name4\": \"\",\n                \"name2\": \"\",\n                \"name3\": \"\",\n                \"name5\": \"\"\n            },\n            \"to\": \"\",\n            \"title\": \"\",\n            \"twoWay\": \"\",\n            \"needFloor\": \"\",\n            \"needKey\": \"\",\n            \"lvlWarp\": \"\"\n        },\n        \"tooltip\": {\n            \"area\": \"\",\n            \"eraseAll\": \"\",\n            \"erase\": \"\",\n            \"events\": \"\",\n            \"music\": \"\",\n            \"layers\": \"\",\n            \"file\": \"\",\n            \"paths\": \"\",\n            \"settings\": \"\",\n            \"scenes\": \"\",\n            \"NPCs\": \"\",\n            \"levels\": \"\",\n            \"BGOs\": \"\",\n            \"blocks\": \"\",\n            \"select\": \"\",\n            \"show\": \"\",\n            \"tiles\": \"\",\n            \"warps\": \"\",\n            \"water\": \"\"\n        },\n        \"testPlay\": {\n            \"magicHand\": \"\",\n            \"char\": \"\",\n            \"power\": \"\",\n            \"pet\": \"\",\n            \"boot\": \"\"\n        },\n        \"world\": {\n            \"retryOnFail\": \"\",\n            \"phraseCreditIndex\": \"\",\n            \"totalStars\": \"\",\n            \"introLevel\": \"\",\n            \"name\": \"\",\n            \"allowChars\": \"\",\n            \"hubWorld\": \"\"\n        },\n        \"events\": {\n            \"controlsForEventN\": \"Contrôles de l'événement {0}\",\n            \"deletion\": {\n                \"confirm\": \"Oui : supprimer l'événement\",\n                \"deletingEvent\": \"Suppression de l'évènement {0}\",\n                \"cancel\": \"Non : ne pas supprimer l'événement\"\n            },\n            \"header\": \"Événements :\",\n            \"label\": {\n                \"layerClear\": \"Nettoyer Couche\",\n                \"next\": \"Suivant\",\n                \"activate\": \"Activation\",\n                \"death\": \"Mort\",\n                \"talk\": \"Parler\",\n                \"destroy\": \"Détruire\",\n                \"enter\": \"Entrer\",\n                \"hit\": \"Frapper\"\n            },\n            \"headerTriggerEvent\": \"Déclencheur :\",\n            \"itemNewEvent\": \"<Nouvel Événement>\",\n            \"letter\": {\n                \"death\": \"M :\",\n                \"enter\": \"E :\",\n                \"destroy\": \"D :\",\n                \"activate\": \"A :\",\n                \"layerClear\": \"N :\",\n                \"talk\": \"P :\",\n                \"hit\": \"F :\"\n            },\n            \"desc\": {\n                \"phraseTriggersAfterTemplate\": \"{0} se déclenche {1} ms après l'événement {2}\",\n                \"talk\": \"joueur parle à PNJ\",\n                \"activate\": \"PNJ apparaît à l'écran\",\n                \"phraseTriggersWhenTemplate\": \"{0} we déclenche quand {1}\",\n                \"destroy\": \"bloc est détruit\",\n                \"layerClear\": \"tout dans la couche objet est parti\",\n                \"enter\": \"téléport est entré\",\n                \"hit\": \"bloc est frappé\",\n                \"death\": \"PNJ meurt\"\n            },\n            \"layers\": {\n                \"headerMove\": \"Déplacer :\",\n                \"headerHide\": \"Cacher :\",\n                \"headerShow\": \"Montrer :\",\n                \"headerToggle\": \"Activer :\"\n            },\n            \"props\": {\n                \"sound\": \"Son\",\n                \"layerSmoke\": \"Fumée\",\n                \"controls\": \"Contrôles\",\n                \"endGame\": \"Fin du Jeu\",\n                \"autostart\": \"Auto-démarrage\"\n            },\n            \"bounds\": {\n                \"shouldEvent\": \"L'Évènement {0} devrait-il\",\n                \"changeAllSectionBoundsToCurrent\": \"Changer toutes les sect. liées à l'actuel ?\",\n                \"changeSectionBoundsToCurrent\": \"Changer toutes les {0} sect. liées à l'actuel ?\"\n            },\n            \"sections\": {\n                \"phraseAllSections\": \"Toutes Sections\",\n                \"propBounds\": \"Bords\",\n                \"actionSet\": \"Set\",\n                \"actionReset\": \"Réinit\",\n                \"propBackground\": \"Arr-Plan\",\n                \"propMusic\": \"Musique\",\n                \"actionKeep\": \"Garder\"\n            },\n            \"promptEventText\": \"Texte de l'évènement\",\n            \"promptEventName\": \"Nom de l'évènement\",\n            \"settingsForEvent\": \"Paramètres pour l'événement {0}\"\n        },\n        \"phraseGenericIndex\": \"\",\n        \"select\": {\n            \"pathBlankUnlock\": \"\",\n            \"warpTransitEffect\": \"\",\n            \"sectionBlankPropBlank\": \"\",\n            \"allSectPropBlankForEventN\": \"\",\n            \"soundForEventN\": \"\",\n            \"sectBlankPropBlankForEventN\": \"\",\n            \"worldMusic\": \"\"\n        },\n        \"block\": {\n            \"invis\": \"Invis.\",\n            \"letterWidth\": \"L \",\n            \"canBreakTooltip\": \"Classique : se brise une fois frappé\",\n            \"pickContents\": \"Prendre contenu du bloc\",\n            \"slick\": \"Gliss.\",\n            \"canBreak\": \"Cassable\",\n            \"inside\": \"Cont. :\",\n            \"letterHeight\": \"H \"\n        },\n        \"browser\": {\n            \"openFile\": \"Ouvrir fichier\",\n            \"itemNewFolder\": \"<Nouveau Dossier>\",\n            \"newFile\": \"Nouveau fichier\",\n            \"saveFile\": \"Sauvegarder fichier\",\n            \"askOverwriteFile\": \"Écraser {0} ?\",\n            \"itemNewFile\": \"<Nouveau Fichier>\"\n        },\n        \"layers\": {\n            \"deletion\": {\n                \"cancel\": \"Annuler : ne pas supprimer couche\",\n                \"confirmDelete\": \"Non : *TOUT SUPPRIMER*\",\n                \"confirmPreserve\": \"Oui : déplacer à couche par défaut\",\n                \"header\": \"Supprimer couche {0}\",\n                \"preserveLayerContents\": \"Garder contenu de la couche ?\"\n            },\n            \"header\": \"\",\n            \"default\": \"Par Défaut\",\n            \"abbrevAttLayer\": \"Att : \",\n            \"desc\": {\n                \"att\": \"\"\n            },\n            \"label\": \"\",\n            \"labelAttached\": \"\",\n            \"labelAttachedLayer\": \"\",\n            \"promptLayerName\": \"\",\n            \"labelMoveLayer\": \"\",\n            \"itemNewLayer\": \"\"\n        },\n        \"npc\": {\n            \"ai\": {\n                \"UD\": \"\",\n                \"target\": \"\",\n                \"use1_0Ai\": \"\",\n                \"jump\": \"\",\n                \"swim\": \"\",\n                \"leap\": \"\",\n                \"headerCustomAi\": \"\",\n                \"LR\": \"\",\n                \"aiIs\": \"\"\n            },\n            \"gen\": {\n                \"direction\": \"\",\n                \"header\": \"\",\n                \"effectIs\": \"\",\n                \"effectShoot\": \"\",\n                \"effectWarp\": \"\"\n            },\n            \"inertNice\": \"\",\n            \"props\": {\n                \"facing\": \"\",\n                \"attachSurface\": \"\",\n                \"active\": \"\"\n            },\n            \"stuckStop\": \"\",\n            \"tooltipExpandSection\": \"\",\n            \"abbrevGen\": \"\",\n            \"inContainer\": \"\"\n        },\n        \"phraseRadiusIndex\": \"\",\n        \"wordEvent\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\",\n            \"typeLabel\": \"\"\n        },\n        \"wordEnabled\": \"\",\n        \"letterRight\": \"\",\n        \"letterLeft\": \"\",\n        \"wordCoins\": \"\",\n        \"labelSortLayer\": \"Trier Couche :\",\n        \"labelSortOffset\": \"Trier Décalage :\",\n        \"letterCoordX\": \"\",\n        \"letterDown\": \"\",\n        \"letterCoordY\": \"\",\n        \"phraseAreYouSure\": \"\",\n        \"pageBlankOfBlank\": \"\",\n        \"phraseTextOf\": \"\",\n        \"toggleMagicBlock\": \"\",\n        \"wordHeight\": \"\",\n        \"wordInstant\": \"\",\n        \"water\": {\n            \"title\": \"\"\n        },\n        \"wordNPC\": {\n            \"nominative\": \"\",\n            \"genitive\": \"\"\n        },\n        \"wordText\": \"\",\n        \"wordMode\": \"\",\n        \"wordWidth\": \"\",\n        \"phraseSectionIndex\": \"\",\n        \"phraseWarpIndex\": \"\",\n        \"letterUp\": \"\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"phraseWaitingForInput\": \"\",\n            \"reconnectTitle\": \"\",\n            \"splitPressSelect_2\": \"\",\n            \"phraseSetControls\": \"\",\n            \"phraseTestProfile\": \"\",\n            \"phraseChangeChar\": \"\",\n            \"phraseDropPX\": \"\",\n            \"phraseDropMe\": \"\",\n            \"dropAddTitle\": \"\",\n            \"phrasePressAButton\": \"\",\n            \"phraseTestControls\": \"\",\n            \"phraseForceResume\": \"\",\n            \"phraseHoldStart\": \"\",\n            \"phraseStartToResume\": \"\",\n            \"splitPressSelect_1\": \"\",\n            \"phraseStartToForceRes\": \"\",\n            \"wordDisconnect\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"\"\n        },\n        \"hint\": {\n            \"no-lives-new\": \"\",\n            \"no-lives-old\": \"\",\n            \"gray-bricks\": \"\",\n            \"char5-bombs\": \"\",\n            \"rainbow-surf\": \"\",\n            \"heavy-duck\": \"\",\n            \"shoe-block\": \"\",\n            \"pound-altrun\": \"\",\n            \"pound-down\": \"\"\n        },\n        \"error\": {\n            \"openFileFailed\": \"\",\n            \"errorNoStartPoint\": \"\",\n            \"warpNeedStarCount\": \"\",\n            \"errorInvalidEnterWarp\": \"\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"\"\n        },\n        \"pause\": {\n            \"enterCode\": \"\",\n            \"continue\": \"\",\n            \"quit\": \"\",\n            \"playerSetup\": \"\",\n            \"returnToEditor\": \"\",\n            \"saveAndContinue\": \"\",\n            \"saveAndQuit\": \"\",\n            \"quitTesting\": \"\",\n            \"restartLevel\": \"\",\n            \"resetCheckpoints\": \"\"\n        },\n        \"msgbox\": {\n            \"sysInfoWarning\": \"\",\n            \"sysInfoError\": \"\",\n            \"sysInfoTitle\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"menu\": {\n        \"battle\": {\n            \"errorNoLevels\": \"\"\n        },\n        \"controls\": {\n            \"buttons\": {\n                \"run\": \"\",\n                \"jump\": \"\",\n                \"left\": \"\",\n                \"altRun\": \"\",\n                \"altJump\": \"\",\n                \"drop\": \"\",\n                \"up\": \"\",\n                \"down\": \"\",\n                \"right\": \"\",\n                \"start\": \"\"\n            },\n            \"hotkeys\": {\n                \"legacyPause\": \"\",\n                \"fullscreen\": \"\",\n                \"screenshot\": \"\",\n                \"enterCheats\": \"\",\n                \"debugInfo\": \"\",\n                \"toggleHUD\": \"\",\n                \"recordGif\": \"\",\n                \"vanillaCam\": \"\"\n            },\n            \"editor\": {\n                \"scrollRight\": \"\",\n                \"modeErase\": \"\",\n                \"scrollLeft\": \"\",\n                \"prevSection\": \"\",\n                \"scrollDown\": \"\",\n                \"nextSection\": \"\",\n                \"modeSelect\": \"\",\n                \"switchScreens\": \"\",\n                \"testPlay\": \"\",\n                \"scrollUp\": \"\",\n                \"fastScroll\": \"\"\n            },\n            \"tDS\": {\n                \"buttonZR\": \"\",\n                \"buttonStart\": \"\",\n                \"buttonSelect\": \"\",\n                \"cStick\": \"\",\n                \"tStick\": \"\",\n                \"dPad\": \"\",\n                \"buttonZL\": \"\",\n                \"casePen\": \"\",\n                \"buttonA\": \"\",\n                \"buttonB\": \"\",\n                \"buttonY\": \"\",\n                \"buttonX\": \"\",\n                \"buttonL\": \"\",\n                \"buttonR\": \"\"\n            },\n            \"cursor\": {\n                \"tertiary\": \"\",\n                \"left\": \"\",\n                \"down\": \"\",\n                \"secondary\": \"\",\n                \"primary\": \"\",\n                \"right\": \"\",\n                \"up\": \"\"\n            },\n            \"profile\": {\n                \"playerControls\": \"\",\n                \"deleteProfile\": \"\",\n                \"editorControls\": \"\",\n                \"hotkeys\": \"\",\n                \"cursorControls\": \"\",\n                \"renameProfile\": \"\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"tabletOld\": \"\",\n                    \"phoneOld\": \"\",\n                    \"standard\": \"\",\n                    \"phabletOld\": \"\",\n                    \"tinyOld\": \"\",\n                    \"tight\": \"\",\n                    \"longOld\": \"\"\n                },\n                \"option\": {\n                    \"feedbackStrength\": \"\",\n                    \"scaleFactor\": \"\",\n                    \"holdRun\": \"\",\n                    \"interfaceStyle\": \"\",\n                    \"layoutStyle\": \"\",\n                    \"resetLayout\": \"\",\n                    \"scaleDPad\": \"\",\n                    \"scaleButtons\": \"\",\n                    \"showCodeButton\": \"\",\n                    \"sStartSpacing\": \"\",\n                    \"feedbackLength\": \"\"\n                },\n                \"style\": {\n                    \"ABXY\": \"\",\n                    \"XODA\": \"\",\n                    \"actions\": \"\"\n                }\n            },\n            \"types\": {\n                \"touchscreen\": \"\",\n                \"oldJoystick\": \"\",\n                \"gamepad\": \"\",\n                \"keyboard\": \"\"\n            },\n            \"wii\": {\n                \"nunchuck\": {\n                    \"buttonC\": \"\",\n                    \"prefixN\": \"\",\n                    \"buttonZ\": \"\"\n                },\n                \"classic\": {\n                    \"buttonLT\": \"\",\n                    \"buttonRT\": \"\",\n                    \"buttonX\": \"\",\n                    \"buttonY\": \"\",\n                    \"lStick\": \"\",\n                    \"buttonZL\": \"\",\n                    \"rStick\": \"\",\n                    \"buttonZR\": \"\"\n                },\n                \"wiimote\": {\n                    \"buttonB\": \"\",\n                    \"buttonA\": \"\",\n                    \"buttonHome\": \"\",\n                    \"buttonPlus\": \"\",\n                    \"buttonMinus\": \"\",\n                    \"caseIR\": \"\",\n                    \"shake\": \"\",\n                    \"dPad\": \"\",\n                    \"button2\": \"\",\n                    \"button1\": \"\"\n                },\n                \"typeClassic\": \"\",\n                \"typeWiimote\": \"\",\n                \"phraseNewNunchuck\": \"\",\n                \"phraseNewClassic\": \"\",\n                \"typeNunchuck\": \"\",\n                \"typeGamecube\": \"\"\n            },\n            \"joystickSimpleEditor\": \"\",\n            \"wordProfiles\": \"\",\n            \"caseInvalid\": \"\",\n            \"controlsTitle\": \"\",\n            \"options\": {\n                \"rumble\": \"\",\n                \"batteryStatus\": \"\",\n                \"maxPlayers\": \"\",\n                \"textEntryStyle\": \"\"\n            },\n            \"controlsInUse\": \"\",\n            \"phraseNewProfOldJoy\": \"\",\n            \"wordButtons\": \"\",\n            \"caseMouse\": \"\",\n            \"controlsDeleteKey\": \"\",\n            \"caseTouch\": \"\",\n            \"controlsNewProfile\": \"\",\n            \"controlsConnected\": \"\",\n            \"controlsReallyDeleteProfile\": \"\",\n            \"controlsNotInUse\": \"\",\n            \"controlsDeviceTypes\": \"\"\n        },\n        \"options\": {\n            \"advanced\": {\n                \"render\": {\n                    \"_name\": \"\",\n                    \"opengles11-tip\": \"\",\n                    \"opengles11\": \"\",\n                    \"opengles-tip\": \"\",\n                    \"opengl11\": \"\",\n                    \"opengl-tip\": \"\",\n                    \"sdl\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"sw\": \"\",\n                    \"opengles\": \"\",\n                    \"opengl11-tip\": \"\",\n                    \"hw\": \"\",\n                    \"opengl\": \"\"\n                },\n                \"audio-buffer-size\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"audio-channels\": {\n                    \"stereo\": \"\",\n                    \"mono\": \"\",\n                    \"_name\": \"\"\n                },\n                \"audio-enable\": \"\",\n                \"audio-format\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"audio-sample-rate\": {\n                    \"16000\": \"\",\n                    \"48000\": \"\",\n                    \"_name\": \"\",\n                    \"22050\": \"\",\n                    \"11025\": \"\",\n                    \"32000\": \"\",\n                    \"44100\": \"\"\n                },\n                \"log-level\": {\n                    \"info\": \"\",\n                    \"debug\": \"\",\n                    \"none\": \"\",\n                    \"fatal\": \"\",\n                    \"critical\": \"\",\n                    \"_name\": \"\",\n                    \"warning\": \"\"\n                },\n                \"scale-down-textures\": {\n                    \"_tooltip\": \"\",\n                    \"all-tip\": \"\",\n                    \"all\": \"\",\n                    \"_name\": \"\",\n                    \"safe\": \"\",\n                    \"none-tip\": \"\",\n                    \"safe-tip\": \"\",\n                    \"none\": \"\"\n                },\n                \"webm-recording\": \"\",\n                \"_header\": \"\",\n                \"advanced-video\": \"\",\n                \"advanced-audio\": \"\",\n                \"_tooltip\": \"\",\n                \"choose-assets-on-launch\": \"\",\n                \"internal-res-4p\": {\n                    \"qsmbx\": \"\",\n                    \"fhd\": \"\",\n                    \"_name\": \"\",\n                    \"qhd\": \"\",\n                    \"default\": \"\"\n                },\n                \"record-gameplay-data\": \"\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"episode-options\": {\n                \"playstyle\": {\n                    \"vanilla\": \"\",\n                    \"modern-tip\": \"\",\n                    \"modern\": \"\",\n                    \"vanilla-tip\": \"\",\n                    \"classic-tip\": \"\",\n                    \"_name\": \"\",\n                    \"classic\": \"\"\n                },\n                \"_header\": \"\",\n                \"creator-compat\": {\n                    \"enable\": \"\",\n                    \"file-only\": \"\",\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\",\n                    \"disable\": \"\"\n                },\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"main\": {\n                \"discord-rpc\": \"\",\n                \"four-screen-mode\": {\n                    \"_name\": \"\",\n                    \"split\": \"\",\n                    \"shared\": \"\"\n                },\n                \"background-work\": \"\",\n                \"frame-skip\": \"\",\n                \"_header\": \"\",\n                \"background-work-tip\": \"\",\n                \"two-screen-mode\": {\n                    \"_name\": \"\",\n                    \"shared\": \"\",\n                    \"smbx\": \"\",\n                    \"split\": \"\",\n                    \"topbottom\": \"\"\n                },\n                \"timing\": \"\",\n                \"multiplayer\": \"\",\n                \"language\": \"\",\n                \"unlimited-framerate\": \"\",\n                \"vsync\": \"\"\n            },\n            \"restartEngine\": \"\",\n            \"audio\": {\n                \"sfx-spatial-audio-tip\": \"\",\n                \"sfx-spatial-audio\": \"\",\n                \"sfx-pet-beat-tip\": \"\",\n                \"sfx-audio-fx\": \"\",\n                \"sfx-modern\": \"\",\n                \"_header\": \"\",\n                \"audio-prefs\": \"\",\n                \"audio-music-volume\": \"\",\n                \"audio-sfx-volume\": \"\",\n                \"sfx-pet-beat\": \"\",\n                \"sfx-modern-tip\": \"\"\n            },\n            \"video\": {\n                \"internal-res\": {\n                    \"dynamic\": \"\",\n                    \"gba\": \"\",\n                    \"hd\": \"\",\n                    \"vga\": \"\",\n                    \"snes\": \"\",\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"3ds\": \"\",\n                    \"hello\": \"\",\n                    \"smbx\": \"\",\n                    \"nds\": \"\",\n                    \"smbx-wide\": \"\"\n                },\n                \"scale-mode\": {\n                    \"integer\": \"\",\n                    \"nearest\": \"\",\n                    \"center\": \"\",\n                    \"2x\": \"\",\n                    \"full\": \"\",\n                    \"0_5x\": \"\",\n                    \"1x\": \"\",\n                    \"linear\": \"\",\n                    \"_name\": \"\",\n                    \"3x\": \"\"\n                },\n                \"show-episode-title\": {\n                    \"off\": \"\",\n                    \"bottom-tip\": \"\",\n                    \"_name\": \"\",\n                    \"bottom\": \"\",\n                    \"top\": \"\",\n                    \"top-tip\": \"\"\n                },\n                \"show-fails-counter-tip\": \"\",\n                \"show-fails-counter\": \"\",\n                \"show-fps\": \"\",\n                \"3d-compat-mode\": \"\",\n                \"3d-compat-mode-tip\": \"\",\n                \"show-playtime-counter\": {\n                    \"_name\": \"\",\n                    \"animated\": \"\",\n                    \"_tooltip\": \"\",\n                    \"off\": \"\",\n                    \"off-tip\": \"\",\n                    \"opaque\": \"\",\n                    \"transparent\": \"\",\n                    \"animated-tip\": \"\"\n                },\n                \"show-medals-counter-tip\": \"\",\n                \"show-medals-counter\": \"\",\n                \"show-screen-shake\": \"\",\n                \"battery-status\": {\n                    \"low-tip\": \"\",\n                    \"on\": \"\",\n                    \"fullscreen-tip\": \"\",\n                    \"fullscreen\": \"\",\n                    \"_name\": \"\",\n                    \"low\": \"\",\n                    \"off\": \"\"\n                },\n                \"_header\": \"\",\n                \"effects\": \"\",\n                \"display-controllers\": \"\",\n                \"info-meta\": \"\",\n                \"fullscreen\": \"\",\n                \"info-game\": \"\",\n                \"enable-inter-level-fade-effect\": \"\",\n                \"info-run\": \"\",\n                \"world-map-stars-show\": \"\",\n                \"video-system\": \"\",\n                \"world-map-stars-show-tip\": \"\"\n            },\n            \"compatibility\": {\n                \"features\": \"\",\n                \"_tooltip\": \"\",\n                \"_header\": \"\",\n                \"reset-all\": \"\",\n                \"autocode\": \"\",\n                \"bugfixes\": \"\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"3\": \"\",\n                    \"2\": \"\",\n                    \"_name\": \"\",\n                    \"1\": \"\",\n                    \"0\": \"\"\n                }\n            },\n            \"controls\": {\n                \"_header\": \"\"\n            },\n            \"view-credits\": {\n                \"_header\": \"\"\n            }\n        },\n        \"caseNone\": \"\",\n        \"game\": {\n            \"gameBattleRandom\": \"\",\n            \"gameCopySave\": \"\",\n            \"gameNoEpisodesToPlay\": \"\",\n            \"gameNoBattleLevels\": \"\",\n            \"gameSlotNew\": \"\",\n            \"gameTargetSlot\": \"\",\n            \"gameSourceSlot\": \"\",\n            \"phraseTime\": \"\",\n            \"gameEraseSlot\": \"\",\n            \"phraseScore\": \"\",\n            \"gameSlotContinue\": \"\",\n            \"gameEraseSave\": \"\",\n            \"warnEpCompat\": \"\"\n        },\n        \"editor\": {\n            \"battles\": \"\",\n            \"newWorld\": \"\",\n            \"makeFor\": \"\",\n            \"promptNewWorldName\": \"\",\n            \"errorMissingResources\": \"\"\n        },\n        \"wordOn\": \"\",\n        \"main\": {\n            \"mainMultiplayerGame\": \"\",\n            \"mainPlayEpisode\": \"\",\n            \"mainOptions\": \"\",\n            \"main1PlayerGame\": \"\",\n            \"mainBattleGame\": \"\",\n            \"mainExit\": \"\",\n            \"mainEditor\": \"\"\n        },\n        \"abbrevMilliseconds\": \"\",\n        \"character\": {\n            \"selectCharacter\": \"\"\n        },\n        \"loading\": \"\",\n        \"wordHide\": \"\",\n        \"wordNo\": \"\",\n        \"wordYes\": \"\",\n        \"wordBack\": \"\",\n        \"wordOff\": \"\",\n        \"wordResume\": \"\",\n        \"wordProfile\": \"\",\n        \"wordPlayer\": \"\",\n        \"wordWaiting\": \"\",\n        \"wordShow\": \"\"\n    },\n    \"pluralRules\": \"\",\n    \"languageName\": \"\",\n    \"outro\": {\n        \"levelDesign\": \"\",\n        \"nameVitalyNovichkov\": \"\",\n        \"psVitaPortBy\": \"\",\n        \"originalBy\": \"\",\n        \"qualityControl\": \"\",\n        \"specialThanks\": \"\",\n        \"gameCredits\": \"\",\n        \"nameAndrewSpinks\": \"\",\n        \"engineCredits\": \"\",\n        \"customSprites\": \"\",\n        \"cppPortDevelopers\": \"\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/thextech_hu.json",
    "content": "{\n    \"menu\": {\n        \"game\": {\n            \"gameTargetSlot\": \"\",\n            \"gameSourceSlot\": \"\",\n            \"gameSlotNew\": \"\",\n            \"gameNoEpisodesToPlay\": \"\",\n            \"gameCopySave\": \"\",\n            \"gameBattleRandom\": \"\",\n            \"phraseTime\": \"\",\n            \"gameSlotContinue\": \"\",\n            \"gameEraseSlot\": \"\",\n            \"gameEraseSave\": \"\",\n            \"phraseScore\": \"\",\n            \"gameNoBattleLevels\": \"\",\n            \"warnEpCompat\": \"\"\n        },\n        \"options\": {\n            \"advanced\": {\n                \"scale-down-textures\": {\n                    \"_tooltip\": \"\",\n                    \"none-tip\": \"\",\n                    \"none\": \"\",\n                    \"all-tip\": \"\",\n                    \"safe-tip\": \"\",\n                    \"safe\": \"\",\n                    \"all\": \"\",\n                    \"_name\": \"\"\n                },\n                \"render\": {\n                    \"sw\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"_name\": \"\",\n                    \"opengl\": \"\",\n                    \"opengl-tip\": \"\",\n                    \"hw\": \"\",\n                    \"opengles-tip\": \"\",\n                    \"opengles11-tip\": \"\",\n                    \"opengles\": \"\",\n                    \"opengl11-tip\": \"\",\n                    \"opengl11\": \"\",\n                    \"sdl\": \"\",\n                    \"opengles11\": \"\"\n                },\n                \"audio-buffer-size\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"log-level\": {\n                    \"info\": \"\",\n                    \"none\": \"\",\n                    \"critical\": \"\",\n                    \"fatal\": \"\",\n                    \"_name\": \"\",\n                    \"debug\": \"\",\n                    \"warning\": \"\"\n                },\n                \"_header\": \"\",\n                \"_tooltip\": \"\",\n                \"advanced-audio\": \"\",\n                \"advanced-video\": \"\",\n                \"audio-format\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"audio-enable\": \"\",\n                \"audio-sample-rate\": {\n                    \"32000\": \"\",\n                    \"22050\": \"\",\n                    \"_name\": \"\",\n                    \"44100\": \"\",\n                    \"48000\": \"\",\n                    \"16000\": \"\",\n                    \"11025\": \"\"\n                },\n                \"audio-channels\": {\n                    \"mono\": \"\",\n                    \"_name\": \"\",\n                    \"stereo\": \"\"\n                },\n                \"choose-assets-on-launch\": \"\",\n                \"internal-res-4p\": {\n                    \"qsmbx\": \"\",\n                    \"qhd\": \"\",\n                    \"default\": \"\",\n                    \"fhd\": \"\",\n                    \"_name\": \"\"\n                },\n                \"record-gameplay-data\": \"\",\n                \"webm-recording\": \"\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"2\": \"\",\n                    \"3\": \"\",\n                    \"1\": \"\",\n                    \"0\": \"\",\n                    \"_name\": \"\"\n                }\n            },\n            \"audio\": {\n                \"audio-music-volume\": \"\",\n                \"_header\": \"\",\n                \"sfx-spatial-audio-tip\": \"\",\n                \"sfx-spatial-audio\": \"\",\n                \"sfx-modern\": \"\",\n                \"sfx-pet-beat-tip\": \"\",\n                \"sfx-pet-beat\": \"\",\n                \"sfx-modern-tip\": \"\",\n                \"sfx-audio-fx\": \"\",\n                \"audio-sfx-volume\": \"\",\n                \"audio-prefs\": \"\"\n            },\n            \"compatibility\": {\n                \"_header\": \"\",\n                \"_tooltip\": \"\",\n                \"autocode\": \"\",\n                \"reset-all\": \"\",\n                \"bugfixes\": \"\",\n                \"features\": \"\"\n            },\n            \"episode-options\": {\n                \"_header\": \"\",\n                \"playstyle\": {\n                    \"_name\": \"\",\n                    \"classic\": \"\",\n                    \"classic-tip\": \"\",\n                    \"modern-tip\": \"\",\n                    \"vanilla\": \"\",\n                    \"modern\": \"\",\n                    \"vanilla-tip\": \"\"\n                },\n                \"creator-compat\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"file-only\": \"\",\n                    \"enable\": \"\",\n                    \"disable\": \"\"\n                },\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"main\": {\n                \"background-work-tip\": \"\",\n                \"background-work\": \"\",\n                \"two-screen-mode\": {\n                    \"shared\": \"\",\n                    \"smbx\": \"\",\n                    \"split\": \"\",\n                    \"topbottom\": \"\",\n                    \"_name\": \"\"\n                },\n                \"four-screen-mode\": {\n                    \"_name\": \"\",\n                    \"shared\": \"\",\n                    \"split\": \"\"\n                },\n                \"multiplayer\": \"\",\n                \"language\": \"\",\n                \"unlimited-framerate\": \"\",\n                \"vsync\": \"\",\n                \"discord-rpc\": \"\",\n                \"frame-skip\": \"\",\n                \"timing\": \"\",\n                \"_header\": \"\"\n            },\n            \"video\": {\n                \"enable-inter-level-fade-effect\": \"\",\n                \"info-game\": \"\",\n                \"battery-status\": {\n                    \"fullscreen-tip\": \"\",\n                    \"_name\": \"\",\n                    \"fullscreen\": \"\",\n                    \"on\": \"\",\n                    \"low-tip\": \"\",\n                    \"off\": \"\",\n                    \"low\": \"\"\n                },\n                \"internal-res\": {\n                    \"_name\": \"\",\n                    \"3ds\": \"\",\n                    \"hd\": \"\",\n                    \"smbx-wide\": \"\",\n                    \"vga\": \"\",\n                    \"snes\": \"\",\n                    \"_tooltip\": \"\",\n                    \"dynamic\": \"\",\n                    \"hello\": \"\",\n                    \"smbx\": \"\",\n                    \"nds\": \"\",\n                    \"gba\": \"\"\n                },\n                \"info-meta\": \"\",\n                \"info-run\": \"\",\n                \"scale-mode\": {\n                    \"0_5x\": \"\",\n                    \"nearest\": \"\",\n                    \"2x\": \"\",\n                    \"linear\": \"\",\n                    \"1x\": \"\",\n                    \"full\": \"\",\n                    \"center\": \"\",\n                    \"_name\": \"\",\n                    \"integer\": \"\",\n                    \"3x\": \"\"\n                },\n                \"show-episode-title\": {\n                    \"bottom-tip\": \"\",\n                    \"off\": \"\",\n                    \"top\": \"\",\n                    \"top-tip\": \"\",\n                    \"bottom\": \"\",\n                    \"_name\": \"\"\n                },\n                \"show-playtime-counter\": {\n                    \"_name\": \"\",\n                    \"animated\": \"\",\n                    \"_tooltip\": \"\",\n                    \"off\": \"\",\n                    \"animated-tip\": \"\",\n                    \"off-tip\": \"\",\n                    \"opaque\": \"\",\n                    \"transparent\": \"\"\n                },\n                \"show-screen-shake\": \"\",\n                \"world-map-stars-show\": \"\",\n                \"video-system\": \"\",\n                \"world-map-stars-show-tip\": \"\",\n                \"fullscreen\": \"\",\n                \"_header\": \"\",\n                \"3d-compat-mode\": \"\",\n                \"3d-compat-mode-tip\": \"\",\n                \"show-fails-counter\": \"\",\n                \"show-medals-counter-tip\": \"\",\n                \"show-fails-counter-tip\": \"\",\n                \"display-controllers\": \"\",\n                \"effects\": \"\",\n                \"show-fps\": \"\",\n                \"show-medals-counter\": \"\"\n            },\n            \"view-credits\": {\n                \"_header\": \"\"\n            },\n            \"restartEngine\": \"\",\n            \"controls\": {\n                \"_header\": \"\"\n            }\n        },\n        \"controls\": {\n            \"buttons\": {\n                \"altRun\": \"\",\n                \"start\": \"\",\n                \"altJump\": \"\",\n                \"left\": \"\",\n                \"right\": \"\",\n                \"drop\": \"\",\n                \"jump\": \"\",\n                \"up\": \"\",\n                \"down\": \"\",\n                \"run\": \"\"\n            },\n            \"controlsConnected\": \"\",\n            \"options\": {\n                \"batteryStatus\": \"\",\n                \"maxPlayers\": \"\",\n                \"rumble\": \"\",\n                \"textEntryStyle\": \"\"\n            },\n            \"hotkeys\": {\n                \"screenshot\": \"\",\n                \"toggleHUD\": \"\",\n                \"recordGif\": \"\",\n                \"debugInfo\": \"\",\n                \"enterCheats\": \"\",\n                \"fullscreen\": \"\",\n                \"legacyPause\": \"\",\n                \"vanillaCam\": \"\"\n            },\n            \"profile\": {\n                \"renameProfile\": \"\",\n                \"playerControls\": \"\",\n                \"deleteProfile\": \"\",\n                \"cursorControls\": \"\",\n                \"hotkeys\": \"\",\n                \"editorControls\": \"\"\n            },\n            \"tDS\": {\n                \"casePen\": \"\",\n                \"dPad\": \"\",\n                \"buttonZL\": \"\",\n                \"buttonA\": \"\",\n                \"buttonB\": \"\",\n                \"buttonZR\": \"\",\n                \"tStick\": \"\",\n                \"buttonSelect\": \"\",\n                \"cStick\": \"\",\n                \"buttonR\": \"\",\n                \"buttonL\": \"\",\n                \"buttonX\": \"\",\n                \"buttonY\": \"\",\n                \"buttonStart\": \"\"\n            },\n            \"controlsNewProfile\": \"\",\n            \"controlsInUse\": \"\",\n            \"touchscreen\": {\n                \"layout\": {\n                    \"tinyOld\": \"\",\n                    \"standard\": \"\",\n                    \"phoneOld\": \"\",\n                    \"phabletOld\": \"\",\n                    \"longOld\": \"\",\n                    \"tabletOld\": \"\",\n                    \"tight\": \"\"\n                },\n                \"option\": {\n                    \"interfaceStyle\": \"\",\n                    \"scaleFactor\": \"\",\n                    \"feedbackStrength\": \"\",\n                    \"layoutStyle\": \"\",\n                    \"holdRun\": \"\",\n                    \"feedbackLength\": \"\",\n                    \"sStartSpacing\": \"\",\n                    \"showCodeButton\": \"\",\n                    \"scaleButtons\": \"\",\n                    \"scaleDPad\": \"\",\n                    \"resetLayout\": \"\"\n                },\n                \"style\": {\n                    \"actions\": \"\",\n                    \"ABXY\": \"\",\n                    \"XODA\": \"\"\n                }\n            },\n            \"types\": {\n                \"touchscreen\": \"\",\n                \"gamepad\": \"\",\n                \"oldJoystick\": \"\",\n                \"keyboard\": \"\"\n            },\n            \"wii\": {\n                \"classic\": {\n                    \"lStick\": \"\",\n                    \"buttonX\": \"\",\n                    \"buttonRT\": \"\",\n                    \"buttonY\": \"\",\n                    \"buttonLT\": \"\",\n                    \"buttonZR\": \"\",\n                    \"buttonZL\": \"\",\n                    \"rStick\": \"\"\n                },\n                \"phraseNewNunchuck\": \"\",\n                \"wiimote\": {\n                    \"button1\": \"\",\n                    \"buttonMinus\": \"\",\n                    \"buttonA\": \"\",\n                    \"dPad\": \"\",\n                    \"caseIR\": \"\",\n                    \"buttonPlus\": \"\",\n                    \"shake\": \"\",\n                    \"button2\": \"\",\n                    \"buttonHome\": \"\",\n                    \"buttonB\": \"\"\n                },\n                \"typeWiimote\": \"\",\n                \"typeNunchuck\": \"\",\n                \"phraseNewClassic\": \"\",\n                \"nunchuck\": {\n                    \"prefixN\": \"\",\n                    \"buttonZ\": \"\",\n                    \"buttonC\": \"\"\n                },\n                \"typeClassic\": \"\",\n                \"typeGamecube\": \"\"\n            },\n            \"joystickSimpleEditor\": \"\",\n            \"caseMouse\": \"\",\n            \"cursor\": {\n                \"down\": \"\",\n                \"primary\": \"\",\n                \"left\": \"\",\n                \"secondary\": \"\",\n                \"up\": \"\",\n                \"right\": \"\",\n                \"tertiary\": \"\"\n            },\n            \"controlsTitle\": \"\",\n            \"editor\": {\n                \"scrollRight\": \"\",\n                \"scrollUp\": \"\",\n                \"scrollLeft\": \"\",\n                \"modeErase\": \"\",\n                \"fastScroll\": \"\",\n                \"testPlay\": \"\",\n                \"switchScreens\": \"\",\n                \"scrollDown\": \"\",\n                \"prevSection\": \"\",\n                \"modeSelect\": \"\",\n                \"nextSection\": \"\"\n            },\n            \"controlsReallyDeleteProfile\": \"\",\n            \"phraseNewProfOldJoy\": \"\",\n            \"wordButtons\": \"\",\n            \"wordProfiles\": \"\",\n            \"caseTouch\": \"\",\n            \"controlsNotInUse\": \"\",\n            \"controlsDeviceTypes\": \"\",\n            \"caseInvalid\": \"\",\n            \"controlsDeleteKey\": \"\"\n        },\n        \"editor\": {\n            \"battles\": \"\",\n            \"newWorld\": \"\",\n            \"promptNewWorldName\": \"\",\n            \"errorMissingResources\": \"\",\n            \"makeFor\": \"\"\n        },\n        \"character\": {\n            \"selectCharacter\": \"\"\n        },\n        \"main\": {\n            \"mainBattleGame\": \"\",\n            \"mainPlayEpisode\": \"\",\n            \"mainMultiplayerGame\": \"\",\n            \"mainExit\": \"\",\n            \"main1PlayerGame\": \"\",\n            \"mainOptions\": \"\",\n            \"mainEditor\": \"\"\n        },\n        \"wordBack\": \"\",\n        \"wordHide\": \"\",\n        \"wordResume\": \"\",\n        \"wordOff\": \"\",\n        \"wordProfile\": \"\",\n        \"battle\": {\n            \"errorNoLevels\": \"\"\n        },\n        \"caseNone\": \"\",\n        \"abbrevMilliseconds\": \"\",\n        \"wordOn\": \"\",\n        \"wordPlayer\": \"\",\n        \"wordNo\": \"\",\n        \"wordWaiting\": \"\",\n        \"wordYes\": \"\",\n        \"loading\": \"\",\n        \"wordShow\": \"\"\n    },\n    \"editor\": {\n        \"file\": {\n            \"formatLegacy\": \"\",\n            \"convert\": {\n                \"descNew\": \"\"\n            },\n            \"actionClearLevel\": \"\",\n            \"actionClearWorld\": \"\",\n            \"actionRevert\": \"\",\n            \"commandSave\": \"\",\n            \"commandSaveAs\": \"\",\n            \"commandOpen\": \"\",\n            \"commandNew\": \"\",\n            \"confirmConfirmAction\": \"\",\n            \"confirmSaveBeforeAction\": \"\",\n            \"confirmConvertFormatTo\": \"\",\n            \"optionCancelAction\": \"\",\n            \"sectionLevel\": \"\",\n            \"formatModern\": \"\",\n            \"labelCurrentFile\": \"\",\n            \"labelFormat\": \"\",\n            \"optionCancelConversion\": \"\",\n            \"actionExit\": \"\",\n            \"optionYesSaveThenAction\": \"\",\n            \"optionActionWithoutSave\": \"\",\n            \"sectionWorld\": \"\",\n            \"optionProceedWithConversion\": \"\",\n            \"actionOpen\": \"\"\n        },\n        \"layers\": {\n            \"desc\": {\n                \"att\": \"\"\n            },\n            \"labelMoveLayer\": \"\",\n            \"promptLayerName\": \"\",\n            \"deletion\": {\n                \"confirmPreserve\": \"\",\n                \"header\": \"\",\n                \"preserveLayerContents\": \"\",\n                \"confirmDelete\": \"\",\n                \"cancel\": \"\"\n            },\n            \"label\": \"\",\n            \"itemNewLayer\": \"\",\n            \"labelAttached\": \"\",\n            \"default\": \"\",\n            \"labelAttachedLayer\": \"\",\n            \"abbrevAttLayer\": \"\",\n            \"header\": \"\"\n        },\n        \"npc\": {\n            \"ai\": {\n                \"UD\": \"\",\n                \"LR\": \"\",\n                \"swim\": \"\",\n                \"leap\": \"\",\n                \"target\": \"\",\n                \"use1_0Ai\": \"\",\n                \"aiIs\": \"\",\n                \"jump\": \"\",\n                \"headerCustomAi\": \"\"\n            },\n            \"gen\": {\n                \"direction\": \"\",\n                \"effectIs\": \"\",\n                \"effectShoot\": \"\",\n                \"effectWarp\": \"\",\n                \"header\": \"\"\n            },\n            \"props\": {\n                \"attachSurface\": \"\",\n                \"facing\": \"\",\n                \"active\": \"\"\n            },\n            \"stuckStop\": \"\",\n            \"inContainer\": \"\",\n            \"inertNice\": \"\",\n            \"tooltipExpandSection\": \"\",\n            \"abbrevGen\": \"\"\n        },\n        \"pageBlankOfBlank\": \"\",\n        \"phraseAreYouSure\": \"\",\n        \"phraseCountMore\": \"\",\n        \"phraseGenericIndex\": \"\",\n        \"phraseWarpIndex\": \"\",\n        \"section\": {\n            \"underwater\": \"\",\n            \"vertWrap\": \"\",\n            \"horizWrap\": \"\",\n            \"noTurnBack\": \"\",\n            \"scroll\": \"\",\n            \"offscreenExit\": \"\",\n            \"setBounds\": \"\"\n        },\n        \"select\": {\n            \"sectBlankPropBlankForEventN\": \"\",\n            \"allSectPropBlankForEventN\": \"\",\n            \"soundForEventN\": \"\",\n            \"pathBlankUnlock\": \"\",\n            \"sectionBlankPropBlank\": \"\",\n            \"worldMusic\": \"\",\n            \"warpTransitEffect\": \"\"\n        },\n        \"testPlay\": {\n            \"magicHand\": \"\",\n            \"char\": \"\",\n            \"pet\": \"\",\n            \"boot\": \"\",\n            \"power\": \"\"\n        },\n        \"tooltip\": {\n            \"eraseAll\": \"\",\n            \"file\": \"\",\n            \"area\": \"\",\n            \"erase\": \"\",\n            \"blocks\": \"\",\n            \"paths\": \"\",\n            \"levels\": \"\",\n            \"layers\": \"\",\n            \"events\": \"\",\n            \"music\": \"\",\n            \"scenes\": \"\",\n            \"select\": \"\",\n            \"tiles\": \"\",\n            \"water\": \"\",\n            \"warps\": \"\",\n            \"BGOs\": \"\",\n            \"NPCs\": \"\",\n            \"show\": \"\",\n            \"settings\": \"\"\n        },\n        \"warp\": {\n            \"transit\": {\n                \"name2\": \"\",\n                \"name5\": \"\",\n                \"name4\": \"\",\n                \"name3\": \"\",\n                \"name1\": \"\",\n                \"name0\": \"\"\n            },\n            \"lvlWarp\": \"\",\n            \"ride\": \"\",\n            \"placing\": \"\",\n            \"in\": \"\",\n            \"twoWay\": \"\",\n            \"item\": \"\",\n            \"dir\": \"\",\n            \"needKey\": \"\",\n            \"out\": \"\",\n            \"needStarCount\": \"\",\n            \"title\": \"\",\n            \"effect\": \"\",\n            \"allow\": \"\",\n            \"cannonExit\": \"\",\n            \"toMap\": \"\",\n            \"style\": {\n                \"pipe\": \"\",\n                \"portal\": \"\",\n                \"blipInstant\": \"\",\n                \"door\": \"\",\n                \"style\": \"\"\n            },\n            \"target\": \"\",\n            \"to\": \"\",\n            \"needFloor\": \"\",\n            \"showStartScene\": \"\",\n            \"showStarCount\": \"\",\n            \"starLockMessage\": \"\",\n            \"speed\": \"\"\n        },\n        \"wordCoins\": \"\",\n        \"wordEnabled\": \"\",\n        \"water\": {\n            \"title\": \"\"\n        },\n        \"wordNPC\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\"\n        },\n        \"wordMode\": \"\",\n        \"world\": {\n            \"retryOnFail\": \"\",\n            \"totalStars\": \"\",\n            \"phraseCreditIndex\": \"\",\n            \"allowChars\": \"\",\n            \"hubWorld\": \"\",\n            \"name\": \"\",\n            \"introLevel\": \"\"\n        },\n        \"events\": {\n            \"controlsForEventN\": \"\",\n            \"bounds\": {\n                \"shouldEvent\": \"\",\n                \"changeSectionBoundsToCurrent\": \"\",\n                \"changeAllSectionBoundsToCurrent\": \"\"\n            },\n            \"deletion\": {\n                \"cancel\": \"\",\n                \"deletingEvent\": \"\",\n                \"confirm\": \"\"\n            },\n            \"header\": \"\",\n            \"label\": {\n                \"activate\": \"\",\n                \"enter\": \"\",\n                \"destroy\": \"\",\n                \"death\": \"\",\n                \"hit\": \"\",\n                \"next\": \"\",\n                \"layerClear\": \"\",\n                \"talk\": \"\"\n            },\n            \"itemNewEvent\": \"\",\n            \"headerTriggerEvent\": \"\",\n            \"layers\": {\n                \"headerMove\": \"\",\n                \"headerHide\": \"\",\n                \"headerShow\": \"\",\n                \"headerToggle\": \"\"\n            },\n            \"letter\": {\n                \"enter\": \"\",\n                \"layerClear\": \"\",\n                \"hit\": \"\",\n                \"activate\": \"\",\n                \"destroy\": \"\",\n                \"death\": \"\",\n                \"talk\": \"\"\n            },\n            \"promptEventText\": \"\",\n            \"props\": {\n                \"autostart\": \"\",\n                \"controls\": \"\",\n                \"endGame\": \"\",\n                \"layerSmoke\": \"\",\n                \"sound\": \"\"\n            },\n            \"promptEventName\": \"\",\n            \"sections\": {\n                \"actionReset\": \"\",\n                \"actionSet\": \"\",\n                \"propBackground\": \"\",\n                \"propBounds\": \"\",\n                \"propMusic\": \"\",\n                \"actionKeep\": \"\",\n                \"phraseAllSections\": \"\"\n            },\n            \"settingsForEvent\": \"\",\n            \"desc\": {\n                \"talk\": \"\",\n                \"phraseTriggersWhenTemplate\": \"\",\n                \"layerClear\": \"\",\n                \"death\": \"\",\n                \"enter\": \"\",\n                \"hit\": \"\",\n                \"destroy\": \"\",\n                \"activate\": \"\",\n                \"phraseTriggersAfterTemplate\": \"\"\n            }\n        },\n        \"block\": {\n            \"canBreak\": \"\",\n            \"inside\": \"\",\n            \"letterHeight\": \"\",\n            \"letterWidth\": \"\",\n            \"canBreakTooltip\": \"\",\n            \"invis\": \"\",\n            \"slick\": \"\",\n            \"pickContents\": \"\"\n        },\n        \"browser\": {\n            \"newFile\": \"\",\n            \"itemNewFolder\": \"\",\n            \"askOverwriteFile\": \"\",\n            \"itemNewFile\": \"\",\n            \"saveFile\": \"\",\n            \"openFile\": \"\"\n        },\n        \"letterCoordY\": \"\",\n        \"letterLeft\": \"\",\n        \"letterUp\": \"\",\n        \"letterRight\": \"\",\n        \"level\": {\n            \"pathUnlocks\": \"\",\n            \"startPos\": \"\",\n            \"gameStart\": \"\",\n            \"alwaysVis\": \"\",\n            \"bigBG\": \"\",\n            \"levelName\": \"\",\n            \"pathBG\": \"\"\n        },\n        \"phraseDelayIsMs\": \"\",\n        \"phraseSectionIndex\": \"\",\n        \"wordEvent\": {\n            \"nominative\": \"\",\n            \"genitive\": \"\",\n            \"typeLabel\": \"\"\n        },\n        \"labelSortLayer\": \"\",\n        \"mapPos\": \"\",\n        \"letterCoordX\": \"\",\n        \"letterDown\": \"\",\n        \"wordHeight\": \"\",\n        \"wordInstant\": \"\",\n        \"toggleMagicBlock\": \"\",\n        \"wordWidth\": \"\",\n        \"labelSortOffset\": \"\",\n        \"phraseTextOf\": \"\",\n        \"wordText\": \"\",\n        \"phraseRadiusIndex\": \"\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"dropAddTitle\": \"\",\n            \"phraseDropMe\": \"\",\n            \"phraseChangeChar\": \"\",\n            \"phraseForceResume\": \"\",\n            \"phraseDropPX\": \"\",\n            \"reconnectTitle\": \"\",\n            \"phraseStartToResume\": \"\",\n            \"phrasePressAButton\": \"\",\n            \"phraseStartToForceRes\": \"\",\n            \"phraseSetControls\": \"\",\n            \"phraseHoldStart\": \"\",\n            \"splitPressSelect_1\": \"\",\n            \"splitPressSelect_2\": \"\",\n            \"phraseTestProfile\": \"\",\n            \"phraseWaitingForInput\": \"\",\n            \"wordDisconnect\": \"\",\n            \"phraseTestControls\": \"\"\n        },\n        \"error\": {\n            \"errorInvalidEnterWarp\": \"\",\n            \"openFileFailed\": \"\",\n            \"errorNoStartPoint\": \"\",\n            \"warpNeedStarCount\": \"\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"hint\": {\n            \"no-lives-new\": \"\",\n            \"no-lives-old\": \"\",\n            \"char5-bombs\": \"\",\n            \"gray-bricks\": \"\",\n            \"heavy-duck\": \"\",\n            \"rainbow-surf\": \"\",\n            \"shoe-block\": \"\",\n            \"pound-altrun\": \"\",\n            \"pound-down\": \"\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"\"\n        },\n        \"pause\": {\n            \"playerSetup\": \"\",\n            \"continue\": \"\",\n            \"resetCheckpoints\": \"\",\n            \"returnToEditor\": \"\",\n            \"restartLevel\": \"\",\n            \"quitTesting\": \"\",\n            \"quit\": \"\",\n            \"enterCode\": \"\",\n            \"saveAndQuit\": \"\",\n            \"saveAndContinue\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"\"\n        },\n        \"msgbox\": {\n            \"sysInfoWarning\": \"\",\n            \"sysInfoTitle\": \"\",\n            \"sysInfoError\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"languageName\": \"\",\n    \"outro\": {\n        \"cppPortDevelopers\": \"\",\n        \"gameCredits\": \"\",\n        \"levelDesign\": \"\",\n        \"nameAndrewSpinks\": \"\",\n        \"nameVitalyNovichkov\": \"\",\n        \"specialThanks\": \"\",\n        \"customSprites\": \"\",\n        \"originalBy\": \"\",\n        \"engineCredits\": \"\",\n        \"qualityControl\": \"\",\n        \"psVitaPortBy\": \"\"\n    },\n    \"pluralRules\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_it.json",
    "content": "{\n    \"editor\": {\n        \"file\": {\n            \"convert\": {\n                \"descNew\": \"L'estensione del file cambierà, ma il vecchio file NON verrà eliminato.\\n\\nControlla se ci sono elementi persi dopo il salvataggio.\"\n            },\n            \"optionProceedWithConversion\": \"Procedi con la conversione\",\n            \"optionYesSaveThenAction\": \"Sì: salva e {0}\",\n            \"confirmConfirmAction\": \"Sei sicuro di {0}?\",\n            \"optionCancelConversion\": \"Cancella conversione\",\n            \"formatModern\": \"Versione Moderna (PGE-X)\",\n            \"formatLegacy\": \"Versione Vecchia (SMBX64)\",\n            \"labelFormat\": \"Formato:\",\n            \"optionActionWithoutSave\": \"{0} senza salvare\",\n            \"optionCancelAction\": \"Cancella: non {0}\",\n            \"labelCurrentFile\": \"File attuale:\",\n            \"sectionLevel\": \"Livello\",\n            \"sectionWorld\": \"Mondo\",\n            \"actionClearLevel\": \"Cancella Livello\",\n            \"actionClearWorld\": \"Cancella il mondo\",\n            \"actionExit\": \"Uscire\",\n            \"actionOpen\": \"Aprire\",\n            \"actionRevert\": \"Ritornare\",\n            \"commandNew\": \"Nuovo\",\n            \"commandOpen\": \"Apri...\",\n            \"commandSave\": \"Salva\",\n            \"commandSaveAs\": \"Salva come...\",\n            \"confirmConvertFormatTo\": \"Convertire il formato a {0}?\",\n            \"confirmSaveBeforeAction\": \"Salvare prima di {0}?\"\n        },\n        \"layers\": {\n            \"abbrevAttLayer\": \"Att:\",\n            \"deletion\": {\n                \"header\": \"Rimozione strato {0}\",\n                \"preserveLayerContents\": \"Preservare i contenuti dello strato?\",\n                \"confirmPreserve\": \"Sì: muovi sullo strato predefinito\",\n                \"cancel\": \"Cancella: non eliminare lo strato\",\n                \"confirmDelete\": \"No: *CANCELLA TUTTI I CONTENTUTI*\"\n            },\n            \"labelAttached\": \"Attaccato:\",\n            \"default\": \"Predefinito\",\n            \"desc\": {\n                \"att\": \"Ogni volta che NPC si muove, lo strato attaccato si muoverà seguendolo\"\n            },\n            \"header\": \"Strati:\",\n            \"label\": \"Strato:\",\n            \"itemNewLayer\": \"<Nuovo Strato>\",\n            \"labelMoveLayer\": \"Muovi Strato:\",\n            \"promptLayerName\": \"Nome dello strato\",\n            \"labelAttachedLayer\": \"Strati Attaccato:\"\n        },\n        \"letterCoordX\": \"X (orizzontale)\",\n        \"letterCoordY\": \"Y (verticale)\",\n        \"letterDown\": \"D\",\n        \"npc\": {\n            \"ai\": {\n                \"UD\": \"Su-Giù\",\n                \"target\": \"Bersaglio\",\n                \"LR\": \"Sinistra-Destra\",\n                \"swim\": \"Nuota\",\n                \"aiIs\": \"IA: {0}\",\n                \"headerCustomAi\": \"IA Perosonalizzata:\",\n                \"jump\": \"Salta\",\n                \"use1_0Ai\": \"Usare IA 1.0?\",\n                \"leap\": \"\"\n            },\n            \"gen\": {\n                \"effectWarp\": \"Warp\",\n                \"header\": \"Impostazioni Generatore\",\n                \"direction\": \"Direzione\",\n                \"effectIs\": \"Effetto: {0}\",\n                \"effectShoot\": \"Spara\"\n            },\n            \"inContainer\": \"In\",\n            \"inertNice\": \"Amichevole\",\n            \"props\": {\n                \"active\": \"Attivo\",\n                \"facing\": \"Affacciato\",\n                \"attachSurface\": \"Attacca\"\n            },\n            \"abbrevGen\": \"Generatore\",\n            \"stuckStop\": \"Fermo\",\n            \"tooltipExpandSection\": \"Sezione di espansione\"\n        },\n        \"events\": {\n            \"deletion\": {\n                \"deletingEvent\": \"Rimozione Evento {0}\",\n                \"cancel\": \"No: non eliminare l'evento\",\n                \"confirm\": \"Sì: elimina l'evento\"\n            },\n            \"desc\": {\n                \"activate\": \"Quando gli NPC entrano nello schermo\",\n                \"death\": \"Quando NPC muore\",\n                \"destroy\": \"Quando il blocco è distrutto\",\n                \"enter\": \"Quando si è entrati nella warp\",\n                \"hit\": \"Quando il blocco viene colpito\",\n                \"layerClear\": \"Quando lo strato è vuoto\",\n                \"phraseTriggersWhenTemplate\": \"{0} bersagli quando {1}\",\n                \"talk\": \"Quando il giocatore parla con NPC\",\n                \"phraseTriggersAfterTemplate\": \"\"\n            },\n            \"sections\": {\n                \"actionSet\": \"Imposta\",\n                \"actionKeep\": \"Mantieni\",\n                \"actionReset\": \"Ripristina\",\n                \"phraseAllSections\": \"Tutte le sezioni\",\n                \"propBackground\": \"Sfondo\",\n                \"propMusic\": \"Musica\",\n                \"propBounds\": \"\"\n            },\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"Vuoi cambiare tutte le sezioni collegate a quella attuale?\",\n                \"shouldEvent\": \"\",\n                \"changeSectionBoundsToCurrent\": \"Vuoi cambiare la sezione {0} collegate a quella attuale?\"\n            },\n            \"controlsForEventN\": \"Controlli per evento {0}\",\n            \"header\": \"Eventi:\",\n            \"headerTriggerEvent\": \"Bersaglio:\",\n            \"itemNewEvent\": \"<Nuovo Evento>\",\n            \"label\": {\n                \"activate\": \"Attiva\",\n                \"death\": \"Morte\",\n                \"destroy\": \"Distruggi\",\n                \"enter\": \"Entra\",\n                \"hit\": \"Colpisci\",\n                \"layerClear\": \"Strato Vuoto\",\n                \"next\": \"Prossimo\",\n                \"talk\": \"Parla\"\n            },\n            \"layers\": {\n                \"headerHide\": \"Nascondi:\",\n                \"headerMove\": \"Muovi:\",\n                \"headerShow\": \"Mostra:\",\n                \"headerToggle\": \"Attivazione:\"\n            },\n            \"letter\": {\n                \"activate\": \"A:\",\n                \"death\": \"D:\",\n                \"destroy\": \"D:\",\n                \"enter\": \"E:\",\n                \"hit\": \"H:\",\n                \"layerClear\": \"L:\",\n                \"talk\": \"T:\"\n            },\n            \"promptEventName\": \"Nome dell'evento\",\n            \"promptEventText\": \"Testo dell'evento\",\n            \"props\": {\n                \"autostart\": \"Avvio Automatico\",\n                \"controls\": \"Controlli\",\n                \"endGame\": \"Fine Gioco\",\n                \"layerSmoke\": \"Fumo\",\n                \"sound\": \"Suono\"\n            },\n            \"settingsForEvent\": \"Impostazioni per evento {0}\"\n        },\n        \"level\": {\n            \"gameStart\": \"Inizio Gioco\",\n            \"startPos\": \"Posizione d'inizio\",\n            \"levelName\": \"Nome Livello\",\n            \"pathUnlocks\": \"Quando il percorso si sblocca\",\n            \"alwaysVis\": \"\",\n            \"bigBG\": \"\",\n            \"pathBG\": \"\"\n        },\n        \"block\": {\n            \"pickContents\": \"Scegli il contenuto del blocco\",\n            \"canBreak\": \"Distrugge\",\n            \"canBreakTooltip\": \"Viene distrutto quando colpito\",\n            \"inside\": \"Dentro:\",\n            \"letterHeight\": \"H\",\n            \"letterWidth\": \"W\",\n            \"slick\": \"Scivola\",\n            \"invis\": \"Invisib\"\n        },\n        \"browser\": {\n            \"askOverwriteFile\": \"Vuoi Sovrascrivere{0}?\",\n            \"itemNewFile\": \"<Nuovo File>\",\n            \"itemNewFolder\": \"<Nuova Cartella>\",\n            \"newFile\": \"Nuovo File\",\n            \"openFile\": \"Apri File\",\n            \"saveFile\": \"Salva File\"\n        },\n        \"letterLeft\": \"L\",\n        \"letterRight\": \"R\",\n        \"letterUp\": \"U\",\n        \"mapPos\": \"Posizione Mappa:\",\n        \"pageBlankOfBlank\": \"Pagina {0} di {1}\",\n        \"phraseAreYouSure\": \"Sei sicuro?\",\n        \"phraseCountMore\": \"{0} Altro\",\n        \"phraseDelayIsMs\": \"Ritardo: {0} ms\",\n        \"phraseGenericIndex\": \"Numero {0}\",\n        \"labelSortLayer\": \"Livello Strato:\",\n        \"labelSortOffset\": \"\",\n        \"phraseSectionIndex\": \"\",\n        \"phraseWarpIndex\": \"\",\n        \"phraseTextOf\": \"\",\n        \"section\": {\n            \"horizWrap\": \"\",\n            \"vertWrap\": \"\",\n            \"offscreenExit\": \"\",\n            \"setBounds\": \"\",\n            \"scroll\": \"\",\n            \"underwater\": \"\",\n            \"noTurnBack\": \"\"\n        },\n        \"warp\": {\n            \"transit\": {\n                \"name3\": \"\",\n                \"name2\": \"\",\n                \"name0\": \"\",\n                \"name1\": \"\",\n                \"name5\": \"\",\n                \"name4\": \"\"\n            },\n            \"item\": \"\",\n            \"needKey\": \"\",\n            \"cannonExit\": \"\",\n            \"lvlWarp\": \"\",\n            \"needFloor\": \"\",\n            \"in\": \"\",\n            \"showStarCount\": \"\",\n            \"ride\": \"\",\n            \"showStartScene\": \"\",\n            \"speed\": \"\",\n            \"starLockMessage\": \"\",\n            \"allow\": \"\",\n            \"dir\": \"\",\n            \"effect\": \"\",\n            \"out\": \"\",\n            \"placing\": \"\",\n            \"needStarCount\": \"\",\n            \"style\": {\n                \"blipInstant\": \"\",\n                \"pipe\": \"\",\n                \"door\": \"\",\n                \"portal\": \"\",\n                \"style\": \"\"\n            },\n            \"toMap\": \"\",\n            \"twoWay\": \"\",\n            \"title\": \"\",\n            \"target\": \"\",\n            \"to\": \"\"\n        },\n        \"wordEnabled\": \"\",\n        \"tooltip\": {\n            \"settings\": \"\",\n            \"select\": \"\",\n            \"scenes\": \"\",\n            \"BGOs\": \"\",\n            \"events\": \"\",\n            \"NPCs\": \"\",\n            \"file\": \"\",\n            \"music\": \"\",\n            \"levels\": \"\",\n            \"paths\": \"\",\n            \"layers\": \"\",\n            \"show\": \"\",\n            \"tiles\": \"\",\n            \"warps\": \"\",\n            \"water\": \"\",\n            \"blocks\": \"\",\n            \"area\": \"\",\n            \"eraseAll\": \"\",\n            \"erase\": \"\"\n        },\n        \"select\": {\n            \"allSectPropBlankForEventN\": \"\",\n            \"sectionBlankPropBlank\": \"\",\n            \"warpTransitEffect\": \"\",\n            \"sectBlankPropBlankForEventN\": \"\",\n            \"soundForEventN\": \"\",\n            \"pathBlankUnlock\": \"\",\n            \"worldMusic\": \"\"\n        },\n        \"wordWidth\": \"\",\n        \"wordEvent\": {\n            \"typeLabel\": \"\",\n            \"nominative\": \"\",\n            \"genitive\": \"\"\n        },\n        \"wordInstant\": \"\",\n        \"wordMode\": \"\",\n        \"world\": {\n            \"allowChars\": \"\",\n            \"hubWorld\": \"\",\n            \"introLevel\": \"\",\n            \"phraseCreditIndex\": \"\",\n            \"name\": \"\",\n            \"totalStars\": \"\",\n            \"retryOnFail\": \"\"\n        },\n        \"phraseRadiusIndex\": \"\",\n        \"testPlay\": {\n            \"power\": \"\",\n            \"magicHand\": \"\",\n            \"pet\": \"\",\n            \"char\": \"\",\n            \"boot\": \"\"\n        },\n        \"toggleMagicBlock\": \"\",\n        \"wordCoins\": \"\",\n        \"wordHeight\": \"\",\n        \"wordNPC\": {\n            \"nominative\": \"\",\n            \"genitive\": \"\"\n        },\n        \"wordText\": \"\",\n        \"water\": {\n            \"title\": \"\"\n        }\n    },\n    \"menu\": {\n        \"options\": {\n            \"advanced\": {\n                \"audio-format\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"audio-enable\": \"\",\n                \"log-level\": {\n                    \"fatal\": \"\",\n                    \"info\": \"\",\n                    \"warning\": \"\",\n                    \"none\": \"\",\n                    \"critical\": \"\",\n                    \"debug\": \"\",\n                    \"_name\": \"\"\n                },\n                \"render\": {\n                    \"opengles-tip\": \"\",\n                    \"opengles11\": \"\",\n                    \"opengles11-tip\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"hw\": \"\",\n                    \"opengl-tip\": \"\",\n                    \"opengl\": \"\",\n                    \"opengl11\": \"\",\n                    \"opengl11-tip\": \"\",\n                    \"sdl\": \"\",\n                    \"_name\": \"\",\n                    \"opengles\": \"\",\n                    \"sw\": \"\"\n                },\n                \"_header\": \"\",\n                \"audio-channels\": {\n                    \"stereo\": \"\",\n                    \"_name\": \"\",\n                    \"mono\": \"\"\n                },\n                \"audio-sample-rate\": {\n                    \"16000\": \"\",\n                    \"22050\": \"\",\n                    \"32000\": \"\",\n                    \"48000\": \"\",\n                    \"44100\": \"\",\n                    \"11025\": \"\",\n                    \"_name\": \"\"\n                },\n                \"record-gameplay-data\": \"\",\n                \"scale-down-textures\": {\n                    \"all\": \"\",\n                    \"all-tip\": \"\",\n                    \"none\": \"\",\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\",\n                    \"none-tip\": \"\",\n                    \"safe\": \"\",\n                    \"safe-tip\": \"\"\n                },\n                \"internal-res-4p\": {\n                    \"fhd\": \"\",\n                    \"default\": \"\",\n                    \"_name\": \"\",\n                    \"qhd\": \"\",\n                    \"qsmbx\": \"\"\n                },\n                \"_tooltip\": \"\",\n                \"advanced-video\": \"\",\n                \"advanced-audio\": \"\",\n                \"audio-buffer-size\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"choose-assets-on-launch\": \"\",\n                \"webm-recording\": \"\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"audio\": {\n                \"sfx-audio-fx\": \"\",\n                \"sfx-modern\": \"\",\n                \"sfx-pet-beat\": \"\",\n                \"sfx-spatial-audio\": \"\",\n                \"sfx-spatial-audio-tip\": \"\",\n                \"_header\": \"\",\n                \"audio-prefs\": \"\",\n                \"audio-music-volume\": \"\",\n                \"audio-sfx-volume\": \"\",\n                \"sfx-modern-tip\": \"\",\n                \"sfx-pet-beat-tip\": \"\"\n            },\n            \"episode-options\": {\n                \"creator-compat\": {\n                    \"disable\": \"\",\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"file-only\": \"\",\n                    \"enable\": \"\"\n                },\n                \"playstyle\": {\n                    \"vanilla-tip\": \"\",\n                    \"vanilla\": \"\",\n                    \"modern-tip\": \"\",\n                    \"classic-tip\": \"\",\n                    \"classic\": \"\",\n                    \"_name\": \"\",\n                    \"modern\": \"\"\n                },\n                \"_header\": \"\",\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"video\": {\n                \"scale-mode\": {\n                    \"_name\": \"\",\n                    \"center\": \"\",\n                    \"0_5x\": \"\",\n                    \"integer\": \"\",\n                    \"2x\": \"\",\n                    \"1x\": \"\",\n                    \"nearest\": \"\",\n                    \"full\": \"\",\n                    \"linear\": \"\",\n                    \"3x\": \"\"\n                },\n                \"battery-status\": {\n                    \"low-tip\": \"\",\n                    \"on\": \"\",\n                    \"low\": \"\",\n                    \"off\": \"\",\n                    \"fullscreen-tip\": \"\",\n                    \"fullscreen\": \"\",\n                    \"_name\": \"\"\n                },\n                \"effects\": \"\",\n                \"info-run\": \"\",\n                \"show-medals-counter-tip\": \"\",\n                \"show-medals-counter\": \"\",\n                \"show-playtime-counter\": {\n                    \"animated\": \"\",\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"off\": \"\",\n                    \"animated-tip\": \"\",\n                    \"off-tip\": \"\",\n                    \"opaque\": \"\",\n                    \"transparent\": \"\"\n                },\n                \"show-screen-shake\": \"\",\n                \"world-map-stars-show-tip\": \"\",\n                \"video-system\": \"\",\n                \"world-map-stars-show\": \"\",\n                \"internal-res\": {\n                    \"hd\": \"\",\n                    \"_tooltip\": \"\",\n                    \"dynamic\": \"\",\n                    \"3ds\": \"\",\n                    \"_name\": \"\",\n                    \"gba\": \"\",\n                    \"snes\": \"\",\n                    \"vga\": \"\",\n                    \"smbx-wide\": \"\",\n                    \"nds\": \"\",\n                    \"hello\": \"\",\n                    \"smbx\": \"\"\n                },\n                \"info-game\": \"\",\n                \"info-meta\": \"\",\n                \"display-controllers\": \"\",\n                \"fullscreen\": \"\",\n                \"_header\": \"\",\n                \"enable-inter-level-fade-effect\": \"\",\n                \"show-episode-title\": {\n                    \"top\": \"\",\n                    \"_name\": \"\",\n                    \"bottom-tip\": \"\",\n                    \"off\": \"\",\n                    \"bottom\": \"\",\n                    \"top-tip\": \"\"\n                },\n                \"3d-compat-mode-tip\": \"\",\n                \"show-fails-counter-tip\": \"\",\n                \"show-fails-counter\": \"\",\n                \"show-fps\": \"\",\n                \"3d-compat-mode\": \"\"\n            },\n            \"main\": {\n                \"four-screen-mode\": {\n                    \"_name\": \"\",\n                    \"split\": \"\",\n                    \"shared\": \"\"\n                },\n                \"two-screen-mode\": {\n                    \"smbx\": \"\",\n                    \"split\": \"\",\n                    \"topbottom\": \"\",\n                    \"_name\": \"\",\n                    \"shared\": \"\"\n                },\n                \"_header\": \"\",\n                \"discord-rpc\": \"\",\n                \"frame-skip\": \"\",\n                \"timing\": \"\",\n                \"vsync\": \"\",\n                \"multiplayer\": \"\",\n                \"background-work\": \"\",\n                \"language\": \"\",\n                \"background-work-tip\": \"\",\n                \"unlimited-framerate\": \"\"\n            },\n            \"view-credits\": {\n                \"_header\": \"\"\n            },\n            \"restartEngine\": \"\",\n            \"speedrun\": {\n                \"mode\": {\n                    \"0\": \"\",\n                    \"1\": \"\",\n                    \"_name\": \"\",\n                    \"3\": \"\",\n                    \"2\": \"\"\n                }\n            },\n            \"compatibility\": {\n                \"bugfixes\": \"\",\n                \"autocode\": \"\",\n                \"_tooltip\": \"\",\n                \"_header\": \"\",\n                \"reset-all\": \"\",\n                \"features\": \"\"\n            },\n            \"controls\": {\n                \"_header\": \"\"\n            }\n        },\n        \"main\": {\n            \"mainMultiplayerGame\": \"\",\n            \"main1PlayerGame\": \"\",\n            \"mainEditor\": \"\",\n            \"mainExit\": \"\",\n            \"mainBattleGame\": \"\",\n            \"mainOptions\": \"\",\n            \"mainPlayEpisode\": \"\"\n        },\n        \"controls\": {\n            \"controlsInUse\": \"\",\n            \"controlsDeleteKey\": \"\",\n            \"controlsDeviceTypes\": \"\",\n            \"editor\": {\n                \"scrollDown\": \"\",\n                \"scrollLeft\": \"\",\n                \"prevSection\": \"\",\n                \"nextSection\": \"\",\n                \"switchScreens\": \"\",\n                \"modeErase\": \"\",\n                \"modeSelect\": \"\",\n                \"scrollRight\": \"\",\n                \"fastScroll\": \"\",\n                \"scrollUp\": \"\",\n                \"testPlay\": \"\"\n            },\n            \"tDS\": {\n                \"buttonZR\": \"\",\n                \"buttonL\": \"\",\n                \"buttonB\": \"\",\n                \"casePen\": \"\",\n                \"tStick\": \"\",\n                \"dPad\": \"\",\n                \"buttonY\": \"\",\n                \"buttonX\": \"\",\n                \"buttonA\": \"\",\n                \"buttonZL\": \"\",\n                \"buttonStart\": \"\",\n                \"buttonR\": \"\",\n                \"buttonSelect\": \"\",\n                \"cStick\": \"\"\n            },\n            \"wii\": {\n                \"typeWiimote\": \"\",\n                \"classic\": {\n                    \"buttonX\": \"\",\n                    \"buttonRT\": \"\",\n                    \"buttonLT\": \"\",\n                    \"buttonY\": \"\",\n                    \"lStick\": \"\",\n                    \"buttonZR\": \"\",\n                    \"rStick\": \"\",\n                    \"buttonZL\": \"\"\n                },\n                \"nunchuck\": {\n                    \"prefixN\": \"\",\n                    \"buttonZ\": \"\",\n                    \"buttonC\": \"\"\n                },\n                \"wiimote\": {\n                    \"button1\": \"\",\n                    \"caseIR\": \"\",\n                    \"shake\": \"\",\n                    \"dPad\": \"\",\n                    \"buttonMinus\": \"\",\n                    \"button2\": \"\",\n                    \"buttonHome\": \"\",\n                    \"buttonB\": \"\",\n                    \"buttonPlus\": \"\",\n                    \"buttonA\": \"\"\n                },\n                \"phraseNewClassic\": \"\",\n                \"phraseNewNunchuck\": \"\",\n                \"typeClassic\": \"\",\n                \"typeNunchuck\": \"\",\n                \"typeGamecube\": \"\"\n            },\n            \"caseMouse\": \"\",\n            \"profile\": {\n                \"cursorControls\": \"\",\n                \"editorControls\": \"\",\n                \"playerControls\": \"\",\n                \"hotkeys\": \"\",\n                \"renameProfile\": \"\",\n                \"deleteProfile\": \"\"\n            },\n            \"phraseNewProfOldJoy\": \"\",\n            \"hotkeys\": {\n                \"debugInfo\": \"\",\n                \"enterCheats\": \"\",\n                \"toggleHUD\": \"\",\n                \"legacyPause\": \"\",\n                \"fullscreen\": \"\",\n                \"recordGif\": \"\",\n                \"screenshot\": \"\",\n                \"vanillaCam\": \"\"\n            },\n            \"cursor\": {\n                \"left\": \"\",\n                \"primary\": \"\",\n                \"secondary\": \"\",\n                \"right\": \"\",\n                \"tertiary\": \"\",\n                \"up\": \"\",\n                \"down\": \"\"\n            },\n            \"buttons\": {\n                \"left\": \"\",\n                \"altJump\": \"\",\n                \"altRun\": \"\",\n                \"drop\": \"\",\n                \"jump\": \"\",\n                \"down\": \"\",\n                \"right\": \"\",\n                \"run\": \"\",\n                \"up\": \"\",\n                \"start\": \"\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"tinyOld\": \"\",\n                    \"tight\": \"\",\n                    \"longOld\": \"\",\n                    \"phoneOld\": \"\",\n                    \"phabletOld\": \"\",\n                    \"tabletOld\": \"\",\n                    \"standard\": \"\"\n                },\n                \"option\": {\n                    \"feedbackLength\": \"\",\n                    \"feedbackStrength\": \"\",\n                    \"holdRun\": \"\",\n                    \"interfaceStyle\": \"\",\n                    \"scaleDPad\": \"\",\n                    \"scaleButtons\": \"\",\n                    \"layoutStyle\": \"\",\n                    \"sStartSpacing\": \"\",\n                    \"resetLayout\": \"\",\n                    \"showCodeButton\": \"\",\n                    \"scaleFactor\": \"\"\n                },\n                \"style\": {\n                    \"ABXY\": \"\",\n                    \"actions\": \"\",\n                    \"XODA\": \"\"\n                }\n            },\n            \"wordButtons\": \"\",\n            \"joystickSimpleEditor\": \"\",\n            \"types\": {\n                \"touchscreen\": \"\",\n                \"gamepad\": \"\",\n                \"oldJoystick\": \"\",\n                \"keyboard\": \"\"\n            },\n            \"controlsNewProfile\": \"\",\n            \"controlsReallyDeleteProfile\": \"\",\n            \"controlsNotInUse\": \"\",\n            \"controlsTitle\": \"\",\n            \"wordProfiles\": \"\",\n            \"options\": {\n                \"batteryStatus\": \"\",\n                \"textEntryStyle\": \"\",\n                \"rumble\": \"\",\n                \"maxPlayers\": \"\"\n            },\n            \"controlsConnected\": \"\",\n            \"caseTouch\": \"\",\n            \"caseInvalid\": \"\"\n        },\n        \"wordPlayer\": \"\",\n        \"wordProfile\": \"\",\n        \"game\": {\n            \"gameSourceSlot\": \"\",\n            \"gameBattleRandom\": \"\",\n            \"gameEraseSave\": \"\",\n            \"gameNoEpisodesToPlay\": \"\",\n            \"gameSlotContinue\": \"\",\n            \"gameEraseSlot\": \"\",\n            \"gameNoBattleLevels\": \"\",\n            \"phraseTime\": \"\",\n            \"gameSlotNew\": \"\",\n            \"gameTargetSlot\": \"\",\n            \"phraseScore\": \"\",\n            \"gameCopySave\": \"\",\n            \"warnEpCompat\": \"\"\n        },\n        \"caseNone\": \"\",\n        \"editor\": {\n            \"battles\": \"\",\n            \"errorMissingResources\": \"\",\n            \"promptNewWorldName\": \"\",\n            \"newWorld\": \"\",\n            \"makeFor\": \"\"\n        },\n        \"wordOff\": \"\",\n        \"wordOn\": \"\",\n        \"wordNo\": \"\",\n        \"character\": {\n            \"selectCharacter\": \"\"\n        },\n        \"battle\": {\n            \"errorNoLevels\": \"\"\n        },\n        \"abbrevMilliseconds\": \"\",\n        \"loading\": \"\",\n        \"wordYes\": \"\",\n        \"wordWaiting\": \"\",\n        \"wordResume\": \"\",\n        \"wordShow\": \"\",\n        \"wordBack\": \"\",\n        \"wordHide\": \"\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"phraseDropPX\": \"\",\n            \"phraseForceResume\": \"\",\n            \"dropAddTitle\": \"\",\n            \"phraseHoldStart\": \"\",\n            \"phraseDropMe\": \"\",\n            \"phraseChangeChar\": \"\",\n            \"phraseStartToResume\": \"\",\n            \"phraseStartToForceRes\": \"\",\n            \"phraseSetControls\": \"\",\n            \"phrasePressAButton\": \"\",\n            \"phraseTestControls\": \"\",\n            \"splitPressSelect_2\": \"\",\n            \"splitPressSelect_1\": \"\",\n            \"reconnectTitle\": \"\",\n            \"phraseTestProfile\": \"\",\n            \"wordDisconnect\": \"\",\n            \"phraseWaitingForInput\": \"\"\n        },\n        \"pause\": {\n            \"saveAndContinue\": \"\",\n            \"continue\": \"\",\n            \"playerSetup\": \"\",\n            \"quit\": \"\",\n            \"enterCode\": \"\",\n            \"resetCheckpoints\": \"\",\n            \"restartLevel\": \"\",\n            \"quitTesting\": \"\",\n            \"returnToEditor\": \"\",\n            \"saveAndQuit\": \"\"\n        },\n        \"error\": {\n            \"warpNeedStarCount\": \"\",\n            \"openFileFailed\": \"\",\n            \"errorInvalidEnterWarp\": \"\",\n            \"errorNoStartPoint\": \"\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"hint\": {\n            \"no-lives-old\": \"\",\n            \"no-lives-new\": \"\",\n            \"char5-bombs\": \"\",\n            \"gray-bricks\": \"\",\n            \"rainbow-surf\": \"\",\n            \"heavy-duck\": \"\",\n            \"shoe-block\": \"\",\n            \"pound-altrun\": \"\",\n            \"pound-down\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"\"\n        },\n        \"msgbox\": {\n            \"sysInfoError\": \"\",\n            \"sysInfoTitle\": \"\",\n            \"sysInfoWarning\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"outro\": {\n        \"qualityControl\": \"\",\n        \"cppPortDevelopers\": \"\",\n        \"engineCredits\": \"\",\n        \"specialThanks\": \"\",\n        \"psVitaPortBy\": \"\",\n        \"originalBy\": \"\",\n        \"nameVitalyNovichkov\": \"\",\n        \"nameAndrewSpinks\": \"\",\n        \"levelDesign\": \"\",\n        \"gameCredits\": \"\",\n        \"customSprites\": \"\"\n    },\n    \"languageName\": \"\",\n    \"pluralRules\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_ja.json",
    "content": "{\n    \"game\": {\n        \"connect\": {\n            \"dropAddTitle\": \"プレイヤー数の へんこう\",\n            \"phraseChangeChar\": \"キャラクターの へんこう\",\n            \"phraseDropMe\": \"ゲームから ぬける\",\n            \"phraseDropPX\": \"P{0}をゲームから ぬけさせる\",\n            \"phraseForceResume\": \"強制的に 再開\",\n            \"phraseHoldStart\": \"スタートボタンを ながおし\",\n            \"phrasePressAButton\": \"Aボタンを おしてください\",\n            \"phraseSetControls\": \"操作を せっていする\",\n            \"phraseStartToForceRes\": \"スタートボタンをおして 強制的に再開\",\n            \"phraseStartToResume\": \"スタートボタンをおして 再開\",\n            \"phraseTestControls\": \"テスト コントロール\",\n            \"phraseTestProfile\": \"テスト プロファイル\",\n            \"phraseWaitingForInput\": \"入力を まっています...\",\n            \"reconnectTitle\": \"再せつぞく する\",\n            \"splitPressSelect_1\": \"選択してください\",\n            \"splitPressSelect_2\": \"そうさ 設定\",\n            \"wordDisconnect\": \"せつだん する\"\n        },\n        \"error\": {\n            \"warpNeedStarCount\": \"ここに入るには {0}個の{1}がひつようです。\",\n            \"errorInvalidEnterWarp\": \"無効な入口ワープ{1}が指定されたため、コースを開始できませんでした。\\nワープエントリの合計数: {2}\\n\\nファイルパス: {0}\",\n            \"openFileFailed\": \"\\\"{0}\\\"をひらけませんでした。\\nファイルが存在しないか、こわれています。\",\n            \"errorNoStartPoint\": \"利用可能なスタートポイント、または入口ワープがセットされていないため、コースを開始できませんでした。\\n\\nファイルパス:{0}\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"データが破損しているか、何らかの不具合があるため、受信したデータを処理できません。\",\n            \"IPCTimeOut\": \"エディターが応答しませんでした。ゲームを終了します。\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"hint\": {\n            \"pound-altrun\": \"オルトダッシュで ヒップドロップだ！\",\n            \"pound-down\": \"下を押して ヒップドロップだ！\",\n            \"no-lives-new\": \"ライフがもうない？ まえがりに なるよ！ スコアに きをつけて...\",\n            \"no-lives-old\": \"気を付けて...もうすぐ ゲームオーバーだぞ！\",\n            \"rainbow-surf\": \"つかんで、はしって、下に入力して コウラをはなせば サーフィンだ！\",\n            \"char5-bombs\": \"ダッシュボタンで バクダンを回収できる。オルトダッシュで はっしゃだ！\",\n            \"heavy-duck\": \"しゃがめば ほとんどの炎を ブロックできる！\",\n            \"shoe-block\": \"履いていれば ほとんどの炎を ブロックできる！\",\n            \"gray-bricks\": \"いくつかのブロックは\\nとくしゅなアイテムで\\nこわせるぞ！\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"レベルをスキャン中...\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0}分{1}秒\"\n        },\n        \"pause\": {\n            \"continue\": \"ゲームを つづける\",\n            \"playerSetup\": \"プレイヤー数の 変更\",\n            \"enterCode\": \"コードを 入力する\",\n            \"quit\": \"ゲームを やめる\",\n            \"quitTesting\": \"テストを やめる\",\n            \"resetCheckpoints\": \"チェックポイントを リセットする\",\n            \"restartLevel\": \"コースを やりなおす\",\n            \"returnToEditor\": \"エディターに もどる\",\n            \"saveAndContinue\": \"セーブして つづける\",\n            \"saveAndQuit\": \"セーブして やめる\"\n        },\n        \"screenPaused\": \"ポーズちゅう\",\n        \"msgbox\": {\n            \"sysInfoWarning\": \"注意！\",\n            \"sysInfoTitle\": \"情報\",\n            \"sysInfoError\": \"エラー！\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"データを読み込み中...\",\n            \"statusGameInfo\": \"ゲーム情報\",\n            \"statusLoadFile\": \"ロード中:{0}\",\n            \"statusFinishing\": \"完了中です...\",\n            \"statusAssetPacks\": \"アセットパック\",\n            \"statusTranslations\": \"翻訳\",\n            \"loading\": \"ロード中...\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"完了しました。ゲームを開始中です...\",\n            \"dataAccepted\": \"データを受信しました。解析中です...\",\n            \"dataTransferStarted\": \"データの移行を開始中...\",\n            \"errorTimeout\": \"エラー:タイムアウトしました。\",\n            \"dataValid\": \"受け取ったデータは有効です\",\n            \"waitingInput\": \"入力データをまっています...\"\n        }\n    },\n    \"languageName\": \"日本語\",\n    \"menu\": {\n        \"abbrevMilliseconds\": \"MS\",\n        \"battle\": {\n            \"errorNoLevels\": \"コースがないため バトルモードをあそべません。\"\n        },\n        \"caseNone\": \"<なし>\",\n        \"controls\": {\n            \"buttons\": {\n                \"altJump\": \"オルト ジャンプ\",\n                \"altRun\": \"オルト ダッシュ\",\n                \"down\": \"下\",\n                \"drop\": \"ストックアイテム\",\n                \"jump\": \"ジャンプ\",\n                \"left\": \"左\",\n                \"right\": \"右\",\n                \"run\": \"ダッシュ\",\n                \"start\": \"スタート\",\n                \"up\": \"上\"\n            },\n            \"caseInvalid\": \"(無効)\",\n            \"caseMouse\": \"(マウス)\",\n            \"caseTouch\": \"(タッチ)\",\n            \"controlsConnected\": \"接続中:\",\n            \"controlsDeleteKey\": \"(オルトジャンプで削除)\",\n            \"controlsDeviceTypes\": \"デバイスの 種類\",\n            \"controlsInUse\": \"(使用中)\",\n            \"controlsNewProfile\": \"<あたらしいプロファイルをつくる>\",\n            \"controlsNotInUse\": \"(使用していません)\",\n            \"controlsReallyDeleteProfile\": \"ほんとうにこのプロファイルを消しますか？\",\n            \"controlsTitle\": \"そうさ設定\",\n            \"cursor\": {\n                \"down\": \"マウス 下\",\n                \"left\": \"マウス 左\",\n                \"primary\": \"左クリック\",\n                \"right\": \"マウス 右\",\n                \"secondary\": \"右クリック\",\n                \"tertiary\": \"中クリック\",\n                \"up\": \"マウス 上\"\n            },\n            \"hotkeys\": {\n                \"debugInfo\": \"デバッグじょうほう\",\n                \"enterCheats\": \"コードにゅうりょく\",\n                \"fullscreen\": \"フルスクリーン\",\n                \"legacyPause\": \"旧ポーズ画面\",\n                \"recordGif\": \"GIFをほぞん\",\n                \"screenshot\": \"スクリーンショット\",\n                \"toggleHUD\": \"HUDのきりかえ\",\n                \"vanillaCam\": \"バニラ カメラ\"\n            },\n            \"options\": {\n                \"batteryStatus\": \"バッテリー状態\",\n                \"maxPlayers\": \"最大人数\",\n                \"rumble\": \"振動\",\n                \"textEntryStyle\": \"テキスト入力方法\",\n                \"altMenuControls\": \"メニューそうさのボタンを反転する\"\n            },\n            \"joystickSimpleEditor\": \"シンプルな エディター操作\",\n            \"phraseNewProfOldJoy\": \"古いジョイスティック向けプロファイルをつくる\",\n            \"profile\": {\n                \"cursorControls\": \"カーソルの そうさ設定\",\n                \"deleteProfile\": \"プロファイルを 削除\",\n                \"editorControls\": \"エディターの そうさ設定\",\n                \"hotkeys\": \"ホットキー\",\n                \"playerControls\": \"プレイヤーの そうさ設定\",\n                \"renameProfile\": \"プロファイルの おなまえ変更\"\n            },\n            \"tDS\": {\n                \"buttonSelect\": \"セレクト\",\n                \"buttonStart\": \"スタート\",\n                \"cStick\": \"Cスティック\",\n                \"dPad\": \"十字キー\",\n                \"tStick\": \"スライドパッド\",\n                \"buttonY\": \"Y\",\n                \"buttonB\": \"B\",\n                \"buttonZL\": \"ZL\",\n                \"casePen\": \"(タッチペン)\",\n                \"buttonZR\": \"ZR\",\n                \"buttonA\": \"A\",\n                \"buttonX\": \"X\",\n                \"buttonL\": \"L\",\n                \"buttonR\": \"R\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"longOld\": \"ロング(旧)\",\n                    \"phabletOld\": \"ファブレット(旧)\",\n                    \"phoneOld\": \"スマホ(旧)\",\n                    \"standard\": \"スタンダード\",\n                    \"tabletOld\": \"タブレット(旧)\",\n                    \"tight\": \"タイト\",\n                    \"tinyOld\": \"小さめ(旧)\"\n                },\n                \"option\": {\n                    \"feedbackLength\": \"振動の ながさ\",\n                    \"feedbackStrength\": \"振動の つよさ\",\n                    \"holdRun\": \"ゲーム開始時に ダッシュボタンをホールド\",\n                    \"interfaceStyle\": \"インターフェース スタイル\",\n                    \"layoutStyle\": \"レイアウト スタイル\",\n                    \"resetLayout\": \"レイアウトを リセット\",\n                    \"sStartSpacing\": \"スタート・セレクトボタンの幅\",\n                    \"scaleButtons\": \"ボタンの大きさをかえる\",\n                    \"scaleDPad\": \"十字キーの大きさをかえる\",\n                    \"scaleFactor\": \"拡大係数\",\n                    \"showCodeButton\": \"コード入力ボタンを表示する\"\n                },\n                \"style\": {\n                    \"actions\": \"アクションを表示\",\n                    \"XODA\": \"XODA\",\n                    \"ABXY\": \"ABXY\"\n                }\n            },\n            \"types\": {\n                \"gamepad\": \"コントローラー\",\n                \"keyboard\": \"キーボード\",\n                \"oldJoystick\": \"旧ジョイスティック\",\n                \"touchscreen\": \"タッチスクリーン\"\n            },\n            \"wii\": {\n                \"phraseNewClassic\": \"あたらしいクラシックコントローラー プロファイルをつくる\",\n                \"phraseNewNunchuck\": \"あたらしいヌンチャク プロファイルをつくる\",\n                \"typeClassic\": \"クラシック コントローラー\",\n                \"typeNunchuck\": \"ヌンチャク\",\n                \"typeWiimote\": \"Wiiリモコン\",\n                \"typeGamecube\": \"GCコントローラー\",\n                \"wiimote\": {\n                    \"buttonHome\": \"ホーム\",\n                    \"dPad\": \"十字キー\",\n                    \"shake\": \"リモコンを振る\",\n                    \"caseIR\": \"(センサー)\",\n                    \"buttonPlus\": \"\",\n                    \"buttonMinus\": \"\",\n                    \"button1\": \"\",\n                    \"buttonB\": \"\",\n                    \"button2\": \"\",\n                    \"buttonA\": \"\"\n                },\n                \"classic\": {\n                    \"lStick\": \"\",\n                    \"buttonY\": \"\",\n                    \"buttonRT\": \"\",\n                    \"rStick\": \"\",\n                    \"buttonLT\": \"\",\n                    \"buttonX\": \"\",\n                    \"buttonZR\": \"\",\n                    \"buttonZL\": \"\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"\",\n                    \"buttonZ\": \"\",\n                    \"prefixN\": \"\"\n                }\n            },\n            \"wordButtons\": \"ボタン\",\n            \"wordProfiles\": \"プロファイル\",\n            \"editor\": {\n                \"modeSelect\": \"モードせんたく\",\n                \"scrollUp\": \"上にスクロール\",\n                \"prevSection\": \"まえの領域\",\n                \"scrollRight\": \"右にスクロール\",\n                \"switchScreens\": \"パネルを表示\",\n                \"modeErase\": \"さくじょモード\",\n                \"nextSection\": \"つぎの領域\",\n                \"fastScroll\": \"スクロール速度アップ\",\n                \"scrollLeft\": \"左にスクロール\",\n                \"testPlay\": \"テストプレイ\",\n                \"scrollDown\": \"下にスクロール\"\n            },\n            \"controlsDeleteKey2\": \"(オルトダッシュで削除)\"\n        },\n        \"game\": {\n            \"gameBattleRandom\": \"ランダムコース\",\n            \"gameCopySave\": \"セーブをコピーする\",\n            \"gameEraseSave\": \"セーブをけす\",\n            \"gameEraseSlot\": \"削除するスロットを 選択してください\",\n            \"gameNoBattleLevels\": \"<バトルコースがありません>\",\n            \"gameNoEpisodesToPlay\": \"<エピソードがありません>\",\n            \"warnEpCompat\": \"このエピソードはほかのSMBXバージョン向けにつくられているため うまく遊べないかもしれません。\",\n            \"gameSlotContinue\": \"スロット {0} ... {1}%\",\n            \"gameSlotNew\": \"スロット {0} ... NEW GAME\",\n            \"gameSourceSlot\": \"コピーするセーブを えらんでください\",\n            \"gameTargetSlot\": \"どこに コピーする？\",\n            \"phraseScore\": \"スコア: {0}\",\n            \"phraseTime\": \"プレイ時間: {0}\"\n        },\n        \"loading\": \"ロード中...\",\n        \"main\": {\n            \"main1PlayerGame\": \"1プレイヤー ゲーム\",\n            \"mainBattleGame\": \"バトル ゲーム\",\n            \"mainEditor\": \"ステージ エディター\",\n            \"mainExit\": \"ゲームを やめる\",\n            \"mainMultiplayerGame\": \"2プレイヤー ゲーム\",\n            \"mainOptions\": \"設定\",\n            \"mainPlayEpisode\": \"エピソードを あそぶ\"\n        },\n        \"options\": {\n            \"restartEngine\": \"変更は再起動後に有効になります。\",\n            \"advanced\": {\n                \"_header\": \"高度な設定\",\n                \"_tooltip\": \"ゲームをこまかく変える 技術的な設定です。\",\n                \"advanced-audio\": \"音声\",\n                \"advanced-video\": \"映像\",\n                \"audio-buffer-size\": {\n                    \"_name\": \"バッファサイズ\",\n                    \"_tooltip\": \"あげればあげるほどポップ音がへりますが、ゲームの負荷がより重くなります\"\n                },\n                \"audio-channels\": {\n                    \"_name\": \"チャンネル\",\n                    \"mono\": \"モノラル\",\n                    \"stereo\": \"ステレオ\"\n                },\n                \"audio-enable\": \"有効\",\n                \"audio-format\": {\n                    \"_name\": \"オーディオフォーマット\",\n                    \"_tooltip\": \"サウンドドライバーのフォーマットをえらびます。\"\n                },\n                \"audio-sample-rate\": {\n                    \"_name\": \"サンプルレート\",\n                    \"32000\": \"\",\n                    \"16000\": \"\",\n                    \"22050\": \"\",\n                    \"44100\": \"\",\n                    \"11025\": \"\",\n                    \"48000\": \"\"\n                },\n                \"log-level\": {\n                    \"_name\": \"ログレベル\",\n                    \"critical\": \"致命的\",\n                    \"debug\": \"デバッグ\",\n                    \"fatal\": \"重大\",\n                    \"info\": \"情報のみ\",\n                    \"none\": \"なし\",\n                    \"warning\": \"警告\"\n                },\n                \"record-gameplay-data\": \"ゲームプレイを録画\",\n                \"render\": {\n                    \"_name\": \"描画モード\",\n                    \"hw\": \"自動\",\n                    \"opengl-tip\": \"すべてのあたらしい視覚エフェクトとSMBX64への完全な正確性をサポートしたデスクトップAPIです。\",\n                    \"opengl11-tip\": \"SMBX64への完全な正確性のみをサポートしたふるいデスクトップAPIです。\",\n                    \"opengles-tip\": \"すべてのあたらしい視覚エフェクトとSMBX64への高い正確性をサポートしたモバイルAPIです。\",\n                    \"opengles11-tip\": \"SMBX64への完全な正確性のみをサポートしたふるいモバイルAPIです。\",\n                    \"sdl-tip\": \"基本のクロスプラットフォーム描画APIです。\",\n                    \"opengl\": \"\",\n                    \"opengl11\": \"\",\n                    \"opengles11\": \"\",\n                    \"sw\": \"\",\n                    \"opengles\": \"\",\n                    \"sdl\": \"\"\n                },\n                \"scale-down-textures\": {\n                    \"_name\": \"画像をスケールダウンする\",\n                    \"_tooltip\": \"画像の解像度を1xとしてメモリを保全します。\",\n                    \"all\": \"すべて\",\n                    \"all-tip\": \"「安全」よりもスタッタリングがすくないです。\",\n                    \"none\": \"None\",\n                    \"none-tip\": \"ロード中のスタッタリングがもっともすくないです。\",\n                    \"safe\": \"安全\",\n                    \"safe-tip\": \"画像が2xになっているときチェックします。\"\n                },\n                \"inaccurate-gifs\": \"ふせいかくなGIF\",\n                \"inaccurate-gifs-tip\": \"GIFが描画されたときの3Dエフェクトを許可します。\",\n                \"webm-recording\": \"WEBMで録画\",\n                \"internal-res-4p\": {\n                    \"_name\": \"4人プレイ時の画面サイズ\",\n                    \"default\": \"デフォルト\",\n                    \"qhd\": \"\",\n                    \"fhd\": \"\",\n                    \"qsmbx\": \"\"\n                },\n                \"fullscreen-type\": {\n                    \"_name\": \"フルスクリーンタイプ\",\n                    \"auto\": \"自動\",\n                    \"desktop\": \"デスクトップ\",\n                    \"desktop-tip\": \"現在の画面解像度を保持します。いわゆる｢ボーダーレス｣です。\",\n                    \"exclusive\": \"占有モード\",\n                    \"exclusive-tip\": \"物理的に画面解像度を変更します。よくあるフルスクリーンです。\"\n                },\n                \"fullscreen-depth\": {\n                    \"auto\": \"自動\",\n                    \"16\": \"16ビット\",\n                    \"_name\": \"占有モード時の色深度\",\n                    \"32\": \"32ビット\"\n                },\n                \"choose-assets-on-launch\": \"起動時に ゲームをえらぶ\",\n                \"use-native-osk\": \"内部キーボードを優先する\",\n                \"fullscreen-res\": \"占有モード時の解像度\"\n            },\n            \"audio\": {\n                \"_header\": \"音声\",\n                \"audio-music-volume\": \"音楽のボリューム\",\n                \"audio-prefs\": \"オーディオせってい\",\n                \"audio-sfx-volume\": \"効果音のボリューム\",\n                \"sfx-audio-fx\": \"エコー効果\",\n                \"sfx-modern\": \"モダンな効果音\",\n                \"sfx-modern-tip\": \"TheXTechで追加された あたらしい効果音を使います。\",\n                \"sfx-pet-beat\": \"ペット・グルーブ\",\n                \"sfx-pet-beat-tip\": \"ペットにのっているあいだ、とくべつなグルーブが再生されます。\",\n                \"sfx-spatial-audio\": \"立体音響\",\n                \"sfx-spatial-audio-tip\": \"画面外の音をちいさくします。\"\n            },\n            \"controls\": {\n                \"_header\": \"そうさ設定\"\n            },\n            \"episode-options\": {\n                \"_header\": \"エピソードせってい\",\n                \"playstyle\": {\n                    \"_name\": \"プレイスタイル\",\n                    \"classic\": \"クラシック\",\n                    \"classic-tip\": \"限定的なアップデートのみ\",\n                    \"modern\": \"モダン\",\n                    \"modern-tip\": \"すべてのアップデート\",\n                    \"vanilla\": \"バニラ\",\n                    \"vanilla-tip\": \"アップデートなし\"\n                },\n                \"hub-resume\": \"ワールドマップでのワープをほぞん\",\n                \"hub-resume-tip\": \"さいごに入ったワープからゲームを再開します。\",\n                \"creator-compat\": {\n                    \"_name\": \"\",\n                    \"disable\": \"\",\n                    \"file-only\": \"\",\n                    \"enable\": \"\",\n                    \"_tooltip\": \"\"\n                }\n            },\n            \"main\": {\n                \"_header\": \"メイン\",\n                \"background-work\": \"バックグラウンドで実行\",\n                \"background-work-tip\": \"非フォーカス中でもジョイスティックでゲームをプレイできるようにします。\",\n                \"four-screen-mode\": {\n                    \"_name\": \"4Pゲームの画面モード\",\n                    \"shared\": \"共有\",\n                    \"split\": \"分割\"\n                },\n                \"frame-skip\": \"フレームスキップ\",\n                \"language\": \"言語\",\n                \"multiplayer\": \"マルチプレイヤー\",\n                \"timing\": \"フレームタイミング\",\n                \"two-screen-mode\": {\n                    \"_name\": \"2Pゲームの画面モード\",\n                    \"shared\": \"共有\",\n                    \"smbx\": \"SMBXスタイル\",\n                    \"split\": \"左右分割\",\n                    \"topbottom\": \"上下分割\"\n                },\n                \"unlimited-framerate\": \"フレームレートのせいげんを解除\",\n                \"vsync\": \"垂直同期\",\n                \"discord-rpc\": \"Discordに表示\"\n            },\n            \"video\": {\n                \"_header\": \"映像\",\n                \"battery-status\": {\n                    \"_name\": \"デバイスのバッテリー残量\",\n                    \"fullscreen\": \"フルスクリーン時\",\n                    \"fullscreen-tip\": \"フルスクリーンのときのみ表示します。\",\n                    \"low\": \"残量低下時\",\n                    \"low-tip\": \"バッテリー残量低下時にのみ表示します。\",\n                    \"off\": \"オフ\",\n                    \"on\": \"常時\"\n                },\n                \"display-controllers\": \"操作を表示\",\n                \"effects\": \"画面効果\",\n                \"enable-inter-level-fade-effect\": \"コース移行時にフェード\",\n                \"fullscreen\": \"フルスクリーン\",\n                \"info-game\": \"画面上のゲーム情報\",\n                \"info-meta\": \"画面上のメタ情報\",\n                \"info-run\": \"画面上のラン情報\",\n                \"internal-res\": {\n                    \"3ds\": \"800x480 (3DS)\",\n                    \"_name\": \"画面サイズ\",\n                    \"_tooltip\": \"ゲーム画面の解像度をへんこうします。\",\n                    \"dynamic\": \"ダイナミック\",\n                    \"smbx-wide\": \"\",\n                    \"vga\": \"\",\n                    \"snes\": \"\",\n                    \"gba\": \"\",\n                    \"hello\": \"\",\n                    \"hd\": \"\",\n                    \"nds\": \"\",\n                    \"smbx\": \"\"\n                },\n                \"scale-mode\": {\n                    \"_name\": \"スケールモード\",\n                    \"integer\": \"整数\",\n                    \"linear\": \"スムーズ\",\n                    \"nearest\": \"二アレスト\",\n                    \"full\": \"フル\",\n                    \"center\": \"中心\",\n                    \"0_5x\": \"\",\n                    \"2x\": \"\",\n                    \"1x\": \"\",\n                    \"3x\": \"\"\n                },\n                \"show-episode-title\": {\n                    \"_name\": \"エピソード名\",\n                    \"bottom\": \"下部\",\n                    \"bottom-tip\": \"スピードランタイマーの上に表示します。\",\n                    \"off\": \"オフ\",\n                    \"top\": \"上部\",\n                    \"top-tip\": \"画面上部に表示します。\"\n                },\n                \"3d-compat-mode\": \"3D互換モード\",\n                \"3d-compat-mode-tip\": \"1つの3D平面上にすべてのオブジェクトを描画します。\",\n                \"show-fails-counter\": \"ミスカウンター\",\n                \"show-fails-counter-tip\": \"現在のコースとエピソード全体それぞれの合計ミス数を表示します。\",\n                \"show-fps\": \"フレームレート\",\n                \"show-medals-counter\": \"メダルカウンター\",\n                \"show-medals-counter-tip\": \"コース入り口でHUDにメダルを表示します。\",\n                \"show-playtime-counter\": {\n                    \"_name\": \"プレイ時間カウンター\",\n                    \"_tooltip\": \"現在のコースとエピソード全体それぞれの経過時間を表示します。\",\n                    \"animated\": \"アニメーション\",\n                    \"animated-tip\": \"コースクリア時にカウンターが虹色に光ります。\",\n                    \"off\": \"オフ\",\n                    \"off-tip\": \"スピードラン中のみ表示します。\",\n                    \"opaque\": \"不透明\",\n                    \"transparent\": \"半透明\"\n                },\n                \"show-screen-shake\": \"画面の揺れ\",\n                \"video-system\": \"システム\",\n                \"world-map-stars-show\": \"ワールドマップでのスターカウンター\",\n                \"world-map-stars-show-tip\": \"コースで集めたスターのかずをワールドマップでも表示します。\"\n            },\n            \"view-credits\": {\n                \"_header\": \"クレジットを見る\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"_name\": \"スピードラン モード\",\n                    \"2\": \"\",\n                    \"0\": \"\",\n                    \"3\": \"\",\n                    \"1\": \"\"\n                }\n            },\n            \"compatibility\": {\n                \"_header\": \"セッションの調整\",\n                \"reset-all\": \"\",\n                \"autocode\": \"\",\n                \"_tooltip\": \"\",\n                \"features\": \"\",\n                \"bugfixes\": \"\"\n            }\n        },\n        \"wordBack\": \"もどる\",\n        \"wordHide\": \"非表示\",\n        \"wordNo\": \"いいえ\",\n        \"wordOff\": \"オフ\",\n        \"wordOn\": \"オン\",\n        \"wordPlayer\": \"プレイヤー\",\n        \"wordProfile\": \"プロファイル\",\n        \"wordResume\": \"再開\",\n        \"wordShow\": \"表示\",\n        \"wordWaiting\": \"待機中\",\n        \"wordYes\": \"はい\",\n        \"character\": {\n            \"selectCharacter\": \"{0}のゲーム\"\n        },\n        \"editor\": {\n            \"makeFor\": \"ワールドスタイル:\",\n            \"newWorld\": \"<あたらしいワールドを つくる>\",\n            \"errorMissingResources\": \"ざんねん！ゲーム内エディターに必要な {0}が見つかりませんでした。\",\n            \"promptNewWorldName\": \"あたらしいワールドの名前\",\n            \"battles\": \"<バトル コース>\"\n        }\n    },\n    \"outro\": {\n        \"cppPortDevelopers\": \"C++移植 開発者\",\n        \"customSprites\": \"カスタムスプライト\",\n        \"engineCredits\": \"エンジン\",\n        \"gameCredits\": \"ゲーム\",\n        \"levelDesign\": \"レベルデザイン\",\n        \"nameVitalyNovichkov\": \"ノヴィチコーフ・ヴィターリ\",\n        \"originalBy\": \"オリジナル VB6コード\",\n        \"psVitaPortBy\": \"PSVita 移植\",\n        \"qualityControl\": \"品質管理\",\n        \"specialThanks\": \"スペシャルサンクス\",\n        \"nameAndrewSpinks\": \"アンドリュー・スピンクス\"\n    },\n    \"pluralRules\": \"singular-only\",\n    \"editor\": {\n        \"layers\": {\n            \"promptLayerName\": \"\",\n            \"label\": \"\",\n            \"deletion\": {\n                \"header\": \"\",\n                \"confirmDelete\": \"\",\n                \"preserveLayerContents\": \"\",\n                \"confirmPreserve\": \"\",\n                \"cancel\": \"\"\n            },\n            \"header\": \"\",\n            \"labelAttachedLayer\": \"\",\n            \"labelMoveLayer\": \"\",\n            \"desc\": {\n                \"att\": \"\"\n            },\n            \"default\": \"\",\n            \"itemNewLayer\": \"\",\n            \"abbrevAttLayer\": \"\",\n            \"labelAttached\": \"\"\n        },\n        \"warp\": {\n            \"needStarCount\": \"\",\n            \"item\": \"\",\n            \"lvlWarp\": \"\",\n            \"needKey\": \"\",\n            \"placing\": \"\",\n            \"to\": \"\",\n            \"showStartScene\": \"\",\n            \"twoWay\": \"\",\n            \"needFloor\": \"\",\n            \"style\": {\n                \"door\": \"\",\n                \"blipInstant\": \"\",\n                \"pipe\": \"\",\n                \"portal\": \"\",\n                \"style\": \"\"\n            },\n            \"transit\": {\n                \"name2\": \"\",\n                \"name1\": \"\",\n                \"name0\": \"\",\n                \"name3\": \"\",\n                \"name4\": \"\",\n                \"name5\": \"\"\n            },\n            \"dir\": \"\",\n            \"cannonExit\": \"\",\n            \"allow\": \"\",\n            \"in\": \"\",\n            \"out\": \"\",\n            \"starLockMessage\": \"\",\n            \"title\": \"\",\n            \"showStarCount\": \"\",\n            \"effect\": \"\",\n            \"target\": \"\",\n            \"speed\": \"\",\n            \"toMap\": \"\",\n            \"ride\": \"\"\n        },\n        \"letterRight\": \"\",\n        \"wordHeight\": \"\",\n        \"file\": {\n            \"commandSave\": \"保存\",\n            \"convert\": {\n                \"descNew\": \"ファイル拡張子は変更されますが、ふるいファイルは削除されません。\\n\\n保存した後、消えた機能がないか確認してください。\"\n            },\n            \"formatLegacy\": \"レガシー\",\n            \"commandOpen\": \"開く...\",\n            \"labelFormat\": \"形式:\",\n            \"actionOpen\": \"ひらく\",\n            \"optionCancelAction\": \"キャンセル:{0}しない\",\n            \"commandSaveAs\": \"名前をつけて保存...\",\n            \"actionRevert\": \"もとにもどす\",\n            \"optionYesSaveThenAction\": \"はい:セーブして {0}\",\n            \"optionActionWithoutSave\": \"セーブしないで {0}\",\n            \"confirmConvertFormatTo\": \"形式を {0}にへんこうする？\",\n            \"sectionWorld\": \"ワールド\",\n            \"commandNew\": \"新規\",\n            \"actionClearWorld\": \"ワールドをリセット\",\n            \"optionProceedWithConversion\": \"変換する\",\n            \"confirmConfirmAction\": \"ほんとうに {0}？\",\n            \"confirmSaveBeforeAction\": \"{0}前に セーブする？\",\n            \"optionCancelConversion\": \"変換をやめる\",\n            \"actionClearLevel\": \"コースをリセット\",\n            \"actionExit\": \"やめる\",\n            \"sectionLevel\": \"コース\",\n            \"labelCurrentFile\": \"げんざいのファイル:\",\n            \"formatModern\": \"モダン\"\n        },\n        \"events\": {\n            \"sections\": {\n                \"propBounds\": \"境界\",\n                \"propMusic\": \"おんがく\",\n                \"propBackground\": \"背景\",\n                \"actionKeep\": \"そのまま\",\n                \"actionReset\": \"リセット\",\n                \"actionSet\": \"セット\",\n                \"phraseAllSections\": \"すべてのセクション\"\n            },\n            \"label\": {\n                \"next\": \"次\",\n                \"layerClear\": \"レイヤークリア\",\n                \"destroy\": \"破壊\",\n                \"death\": \"やられ\",\n                \"activate\": \"有効化\",\n                \"hit\": \"ヒット\",\n                \"enter\": \"入場\",\n                \"talk\": \"会話\"\n            },\n            \"letter\": {\n                \"enter\": \"入:\",\n                \"activate\": \"き:\",\n                \"talk\": \"会:\",\n                \"death\": \"や:\",\n                \"hit\": \"ヒ:\",\n                \"layerClear\": \"レ:\",\n                \"destroy\": \"破:\"\n            },\n            \"layers\": {\n                \"headerShow\": \"表示:\",\n                \"headerHide\": \"隠す:\",\n                \"headerMove\": \"移動:\",\n                \"headerToggle\": \"きりかえ:\"\n            },\n            \"desc\": {\n                \"activate\": \"NPCが画面に入ったとき\",\n                \"destroy\": \"ブロックが破壊されたとき\",\n                \"hit\": \"ブロックにヒットしたとき\",\n                \"layerClear\": \"オブジェクトレイヤーから すべてがなくなったとき\",\n                \"enter\": \"ワープに入ったとき\",\n                \"phraseTriggersAfterTemplate\": \"{0}が イベント{2}の発生後 {1}ミリ秒で はっせいする\",\n                \"death\": \"NPCがやられたとき\",\n                \"phraseTriggersWhenTemplate\": \"{0} は {1} のときにはっせいする\",\n                \"talk\": \"プレイヤーがNPCに話しかけたとき\"\n            },\n            \"promptEventText\": \"イベント文\",\n            \"props\": {\n                \"layerSmoke\": \"スモーク\",\n                \"endGame\": \"エピソードを終える\",\n                \"controls\": \"そうさ\",\n                \"autostart\": \"自動スタート\",\n                \"sound\": \"効果音\"\n            },\n            \"itemNewEvent\": \"<あたらしい イベント>\",\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"すべてのセクション領域の現在に変更しますか？\",\n                \"changeSectionBoundsToCurrent\": \"{0}のセクション境界の現在に変更しますか？\",\n                \"shouldEvent\": \"イベント {0}を\"\n            },\n            \"promptEventName\": \"イベント名\",\n            \"headerTriggerEvent\": \"トリガー:\",\n            \"header\": \"イベント:\",\n            \"deletion\": {\n                \"confirm\": \"はい:イベントを さくじょする\",\n                \"cancel\": \"いいえ:イベントを さくじょしない\",\n                \"deletingEvent\": \"イベント {0}をさくじょ中...\"\n            },\n            \"controlsForEventN\": \"イベント {0}を操作:\",\n            \"settingsForEvent\": \"イベント {0}の せってい\"\n        },\n        \"wordWidth\": \"\",\n        \"select\": {\n            \"warpTransitEffect\": \"\",\n            \"soundForEventN\": \"\",\n            \"pathBlankUnlock\": \"\",\n            \"allSectPropBlankForEventN\": \"\",\n            \"worldMusic\": \"\",\n            \"sectBlankPropBlankForEventN\": \"\",\n            \"sectionBlankPropBlank\": \"\"\n        },\n        \"tooltip\": {\n            \"BGOs\": \"\",\n            \"events\": \"\",\n            \"eraseAll\": \"\",\n            \"music\": \"\",\n            \"erase\": \"\",\n            \"settings\": \"\",\n            \"NPCs\": \"\",\n            \"water\": \"\",\n            \"show\": \"\",\n            \"paths\": \"\",\n            \"tiles\": \"\",\n            \"levels\": \"\",\n            \"layers\": \"\",\n            \"warps\": \"\",\n            \"blocks\": \"\",\n            \"file\": \"\",\n            \"select\": \"\",\n            \"scenes\": \"\",\n            \"area\": \"\"\n        },\n        \"npc\": {\n            \"ai\": {\n                \"aiIs\": \"\",\n                \"target\": \"\",\n                \"swim\": \"\",\n                \"jump\": \"\",\n                \"leap\": \"\",\n                \"UD\": \"\",\n                \"headerCustomAi\": \"\",\n                \"use1_0Ai\": \"\",\n                \"LR\": \"\"\n            },\n            \"tooltipExpandSection\": \"\",\n            \"stuckStop\": \"\",\n            \"gen\": {\n                \"header\": \"\",\n                \"effectShoot\": \"\",\n                \"direction\": \"\",\n                \"effectWarp\": \"\",\n                \"effectIs\": \"\"\n            },\n            \"props\": {\n                \"attachSurface\": \"\",\n                \"facing\": \"\",\n                \"active\": \"\"\n            },\n            \"abbrevGen\": \"\",\n            \"inertNice\": \"\",\n            \"inContainer\": \"\"\n        },\n        \"block\": {\n            \"invis\": \"とうめい化\",\n            \"canBreak\": \"破壊可能\",\n            \"letterHeight\": \"たて\",\n            \"letterWidth\": \"よこ\",\n            \"slick\": \"すべりやすい\",\n            \"canBreakTooltip\": \"レガシー:ヒット時に破壊\",\n            \"pickContents\": \"ブロックの中身を\\nえらんでください\",\n            \"inside\": \"なかみ:\"\n        },\n        \"letterLeft\": \"\",\n        \"browser\": {\n            \"askOverwriteFile\": \"{0}を\\n上書きして よろしいですか？\",\n            \"openFile\": \"ファイルを ひらく\",\n            \"itemNewFolder\": \"<あたらしい フォルダー>\",\n            \"itemNewFile\": \"<あたらしい ファイル>\",\n            \"saveFile\": \"ファイルを ほぞん\",\n            \"newFile\": \"あたらしい ファイル\"\n        },\n        \"level\": {\n            \"startPos\": \"\",\n            \"pathBG\": \"\",\n            \"pathUnlocks\": \"\",\n            \"levelName\": \"\",\n            \"gameStart\": \"\",\n            \"alwaysVis\": \"\",\n            \"bigBG\": \"\"\n        },\n        \"testPlay\": {\n            \"magicHand\": \"\",\n            \"power\": \"\",\n            \"char\": \"\",\n            \"pet\": \"\",\n            \"boot\": \"\"\n        },\n        \"wordInstant\": \"\",\n        \"letterDown\": \"\",\n        \"mapPos\": \"\",\n        \"world\": {\n            \"introLevel\": \"\",\n            \"hubWorld\": \"\",\n            \"name\": \"\",\n            \"phraseCreditIndex\": \"\",\n            \"totalStars\": \"\",\n            \"retryOnFail\": \"\",\n            \"allowChars\": \"\"\n        },\n        \"letterCoordX\": \"\",\n        \"water\": {\n            \"title\": \"\"\n        },\n        \"wordMode\": \"\",\n        \"phraseTextOf\": \"\",\n        \"wordEnabled\": \"\",\n        \"wordEvent\": {\n            \"nominative\": \"\",\n            \"typeLabel\": \"\",\n            \"genitive\": \"\"\n        },\n        \"phraseSectionIndex\": \"\",\n        \"wordCoins\": \"\",\n        \"section\": {\n            \"horizWrap\": \"\",\n            \"setBounds\": \"\",\n            \"noTurnBack\": \"\",\n            \"underwater\": \"\",\n            \"offscreenExit\": \"\",\n            \"vertWrap\": \"\",\n            \"scroll\": \"\"\n        },\n        \"phraseGenericIndex\": \"\",\n        \"letterUp\": \"\",\n        \"phraseCountMore\": \"\",\n        \"wordNPC\": {\n            \"nominative\": \"\",\n            \"genitive\": \"\"\n        },\n        \"phraseRadiusIndex\": \"\",\n        \"pageBlankOfBlank\": \"\",\n        \"phraseWarpIndex\": \"\",\n        \"labelSortLayer\": \"レイヤーソート:\",\n        \"phraseAreYouSure\": \"\",\n        \"toggleMagicBlock\": \"\",\n        \"phraseDelayIsMs\": \"\",\n        \"letterCoordY\": \"\",\n        \"wordText\": \"\",\n        \"labelSortOffset\": \"ソート補正:\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/thextech_ko.json",
    "content": "{\n    \"editor\": {\n        \"layers\": {\n            \"promptLayerName\": \"레이어 이름\",\n            \"label\": \"레이어:\",\n            \"deletion\": {\n                \"header\": \"레이어 {0} 삭제 중\",\n                \"confirmDelete\": \"아니요: *모든 콘텐츠 삭제*\",\n                \"preserveLayerContents\": \"레이어 내용을 보존하겠습니까?\",\n                \"confirmPreserve\": \"예: 기본 레이어로 이동\",\n                \"cancel\": \"취소: 레이어를 삭제하지 않음\"\n            },\n            \"header\": \"레이어:\",\n            \"labelAttachedLayer\": \"첨부 레이어:\",\n            \"labelMoveLayer\": \"이동 레이어:\",\n            \"desc\": {\n                \"att\": \"NPC가 움직일 때마다 부착된 레이어도 따라 움직임\"\n            },\n            \"default\": \"기본값\",\n            \"itemNewLayer\": \"<새로운 레이어>\",\n            \"abbrevAttLayer\": \"공격: \",\n            \"labelAttached\": \"첨부:\"\n        },\n        \"block\": {\n            \"invis\": \"보이지 않음\",\n            \"canBreak\": \"깨질 수 있음\",\n            \"letterHeight\": \"높이\",\n            \"letterWidth\": \"폭\",\n            \"canBreakTooltip\": \"구버전: 부딪히면 부서진다\",\n            \"pickContents\": \"블록 내용 선택\",\n            \"inside\": \"내부:\",\n            \"slick\": \"미끄러움\"\n        },\n        \"file\": {\n            \"labelFormat\": \"형식:\",\n            \"optionCancelAction\": \"취소: {0}하지 마세요\",\n            \"optionYesSaveThenAction\": \"예: 저장한 다음 {0}\",\n            \"optionActionWithoutSave\": \"저장하지 않고 {0}\",\n            \"sectionWorld\": \"세계\",\n            \"optionProceedWithConversion\": \"변환 진행\",\n            \"optionCancelConversion\": \"변환 취소\",\n            \"sectionLevel\": \"레벨\",\n            \"labelCurrentFile\": \"현재 파일: \",\n            \"formatLegacy\": \"레거시\",\n            \"formatModern\": \"최신\",\n            \"actionClearLevel\": \"레벨 지움\",\n            \"actionClearWorld\": \"세계 지움\",\n            \"actionExit\": \"종료\",\n            \"actionOpen\": \"열기\",\n            \"actionRevert\": \"복귀\",\n            \"commandNew\": \"새로운\",\n            \"commandOpen\": \"열기...\",\n            \"commandSave\": \"저장\",\n            \"commandSaveAs\": \"다른 이름으로 저장...\",\n            \"confirmConfirmAction\": \"정말 {0} 할까요?\",\n            \"confirmConvertFormatTo\": \"형식을 {0} 변환할까요?\",\n            \"confirmSaveBeforeAction\": \"{0} 전에 저장할까요?\",\n            \"convert\": {\n                \"descNew\": \"파일 확장명은 변경되지만 이전 파일은 삭제되지 않습니다.\\n\\n저장 후 손실된 기능을 확인하십시오.\"\n            }\n        },\n        \"warp\": {\n            \"speed\": \"속도 \",\n            \"title\": \"워프 설정\",\n            \"toMap\": \"지도로\",\n            \"transit\": {\n                \"name0\": \"없음\",\n                \"name1\": \"스크롤\",\n                \"name3\": \"원\",\n                \"name4\": \"뒤집기 (수평)\",\n                \"name2\": \"페이드\",\n                \"name5\": \"뒤집기 (수직)\"\n            },\n            \"twoWay\": \"양방향\",\n            \"style\": {\n                \"style\": \"스타일: \",\n                \"portal\": \"포트\",\n                \"blipInstant\": \"순간이동\",\n                \"door\": \"문\",\n                \"pipe\": \"파이프\"\n            },\n            \"target\": \"대상: \",\n            \"cannonExit\": \"대포 출구\",\n            \"dir\": \"방향\",\n            \"effect\": \"효과:\",\n            \"in\": \"입구\",\n            \"item\": \"아이템\",\n            \"lvlWarp\": \"레벨 워프\",\n            \"needFloor\": \"바닥 필요\",\n            \"needKey\": \"열쇠 필요\",\n            \"needStarCount\": \"{0} {1} 필요\",\n            \"out\": \"출구\",\n            \"placing\": \"장소:\",\n            \"ride\": \"타다\",\n            \"showStarCount\": \"별 개수 표시\",\n            \"showStartScene\": \"시작 장면 표시\",\n            \"starLockMessage\": \"별 잠금 메시지\",\n            \"to\": \"보내기: {0}\",\n            \"allow\": \"허용:\"\n        },\n        \"water\": {\n            \"title\": \"물 설정\"\n        },\n        \"wordEvent\": {\n            \"nominative\": \"이벤트\",\n            \"typeLabel\": \"{0} 이벤트:\",\n            \"genitive\": \"이벤트\"\n        },\n        \"wordInstant\": \"즉시\",\n        \"wordMode\": \"모드\",\n        \"wordNPC\": {\n            \"genitive\": \"NPC\",\n            \"nominative\": \"NPC\"\n        },\n        \"wordText\": \"텍스트\",\n        \"wordWidth\": \"폭\",\n        \"world\": {\n            \"hubWorld\": \"허브 월드\",\n            \"name\": \"월드 이름\",\n            \"retryOnFail\": \"실패 시 재시도\",\n            \"totalStars\": \"모은 별: \",\n            \"allowChars\": \"캐릭터 허용\",\n            \"phraseCreditIndex\": \"월드 크레딧 라인 {0}:\",\n            \"introLevel\": \"인트로 레벨\"\n        },\n        \"wordCoins\": \"동전\",\n        \"wordEnabled\": \"활성화\",\n        \"wordHeight\": \"높이\",\n        \"labelSortLayer\": \"레이어 정렬:\",\n        \"labelSortOffset\": \"오프셋 정렬:\",\n        \"letterCoordX\": \"X\",\n        \"letterCoordY\": \"Y\",\n        \"letterDown\": \"D\",\n        \"level\": {\n            \"levelName\": \"레벨 이름\",\n            \"pathUnlocks\": \"잠금 해제 경로\",\n            \"pathBG\": \"배경 경로\",\n            \"startPos\": \"시작 위치\",\n            \"alwaysVis\": \"항상 표시\",\n            \"bigBG\": \"큰 배경\",\n            \"gameStart\": \"게임 시작\"\n        },\n        \"mapPos\": \"지도 위치:\",\n        \"npc\": {\n            \"abbrevGen\": \"생성기\",\n            \"ai\": {\n                \"LR\": \"LR\",\n                \"UD\": \"UD\",\n                \"aiIs\": \"AI: {0}\",\n                \"headerCustomAi\": \"커스텀 AI:\",\n                \"jump\": \"점프\",\n                \"leap\": \"뛰다\",\n                \"swim\": \"수영\",\n                \"use1_0Ai\": \"1.0 AI 사용?\",\n                \"target\": \"대상\"\n            },\n            \"gen\": {\n                \"effectIs\": \"효과: {0}\",\n                \"effectShoot\": \"발사\",\n                \"effectWarp\": \"워프\",\n                \"direction\": \"방향\",\n                \"header\": \"생성기 설정\"\n            },\n            \"props\": {\n                \"active\": \"활동적\",\n                \"attachSurface\": \"위치\",\n                \"facing\": \"방향\"\n            },\n            \"inContainer\": \"포함\",\n            \"inertNice\": \"좋음\",\n            \"tooltipExpandSection\": \"확장 섹션\",\n            \"stuckStop\": \"정지\"\n        },\n        \"pageBlankOfBlank\": \"{0} 의 {1} 페이지\",\n        \"phraseAreYouSure\": \"확실한가요?\",\n        \"phraseCountMore\": \"{0} 더 많은\",\n        \"phraseDelayIsMs\": \"지연: {0} ms\",\n        \"phraseGenericIndex\": \"숫자 {0}\",\n        \"phraseRadiusIndex\": \"반경 {0}\",\n        \"phraseSectionIndex\": \"섹션 {0}\",\n        \"phraseTextOf\": \"{0} 텍스트\",\n        \"phraseWarpIndex\": \"워프 {0}\",\n        \"section\": {\n            \"horizWrap\": \"수평 워프\",\n            \"noTurnBack\": \"되돌릴 수 없음\",\n            \"offscreenExit\": \"오프스크린 종료\",\n            \"scroll\": \"스크롤\",\n            \"setBounds\": \"경계 설정\",\n            \"underwater\": \"수중\",\n            \"vertWrap\": \"수직 순환\"\n        },\n        \"select\": {\n            \"sectBlankPropBlankForEventN\": \"{2}에 대한 {0} {1} 섹션\",\n            \"sectionBlankPropBlank\": \"{0} {1} 섹션\",\n            \"soundForEventN\": \"이벤트 {0} 사운드\",\n            \"warpTransitEffect\": \"워프 전환 효과\",\n            \"worldMusic\": \"세계 음악\",\n            \"pathBlankUnlock\": \"경로 {0} 잠금 해제 기준\",\n            \"allSectPropBlankForEventN\": \"{1}에 대한 모든 섹션 {0}\"\n        },\n        \"testPlay\": {\n            \"boot\": \"부트\",\n            \"char\": \"캐릭터\",\n            \"magicHand\": \"매직 핸드\",\n            \"pet\": \"애완동물\",\n            \"power\": \"파워\"\n        },\n        \"toggleMagicBlock\": \"매직 블록\",\n        \"tooltip\": {\n            \"BGOs\": \"배경 개체\",\n            \"NPCs\": \"NPC\",\n            \"area\": \"영역\",\n            \"erase\": \"지움\",\n            \"events\": \"이벤트\",\n            \"file\": \"파일\",\n            \"levels\": \"레벨\",\n            \"music\": \"음악\",\n            \"paths\": \"경로\",\n            \"select\": \"선택\",\n            \"warps\": \"워프\",\n            \"water\": \"물\",\n            \"show\": \"표시\",\n            \"blocks\": \"블록\",\n            \"eraseAll\": \"모두 지움\",\n            \"layers\": \"레이어\",\n            \"scenes\": \"장면\",\n            \"settings\": \"설정\",\n            \"tiles\": \"타일\"\n        },\n        \"letterLeft\": \"L\",\n        \"letterRight\": \"R\",\n        \"events\": {\n            \"headerTriggerEvent\": \"전환:\",\n            \"itemNewEvent\": \"<새 이벤트>\",\n            \"layers\": {\n                \"headerShow\": \"표시:\",\n                \"headerMove\": \"이동:\",\n                \"headerHide\": \"숨김:\",\n                \"headerToggle\": \"전환:\"\n            },\n            \"label\": {\n                \"activate\": \"활성화\",\n                \"death\": \"죽음\",\n                \"destroy\": \"파괴\",\n                \"enter\": \"진입\",\n                \"hit\": \"영향\",\n                \"layerClear\": \"레이어 지움\",\n                \"next\": \"다음\",\n                \"talk\": \"대화\"\n            },\n            \"deletion\": {\n                \"confirm\": \"예: 이벤트 삭제\",\n                \"deletingEvent\": \"이벤트 {0} 삭제 중\",\n                \"cancel\": \"아니요: 이벤트를을 삭제하지 않음\"\n            },\n            \"desc\": {\n                \"activate\": \"NPC가 화면에 들어옴\",\n                \"death\": \"NPC 죽음\",\n                \"destroy\": \"블록이 파괴됨\",\n                \"enter\": \"워프가 입력됨\",\n                \"hit\": \"블록이 맞음\",\n                \"layerClear\": \"개체 레이어의 모든 항목이 사라짐\",\n                \"phraseTriggersAfterTemplate\": \"{0}은 이벤트 {2} 발생 후 {1}초 후에 전환\",\n                \"phraseTriggersWhenTemplate\": \"{0}는 {1}일 때 전환\",\n                \"talk\": \"플레이어가 NPC와 대화\"\n            },\n            \"header\": \"이벤트:\",\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"모든 섹션 경계를 현재 설정으로 변경할까요?\",\n                \"changeSectionBoundsToCurrent\": \"{0} 섹션 경계를 현재 설정으로 변경할까요?\",\n                \"shouldEvent\": \"이벤트가 {0}이어야 함\"\n            },\n            \"controlsForEventN\": \"이벤트 {0}에 대한 컨트롤\",\n            \"letter\": {\n                \"activate\": \"A:\",\n                \"death\": \"D:\",\n                \"destroy\": \"D:\",\n                \"enter\": \"E:\",\n                \"hit\": \"H:\",\n                \"layerClear\": \"L:\",\n                \"talk\": \"T:\"\n            },\n            \"props\": {\n                \"layerSmoke\": \"연기\",\n                \"sound\": \"사운드\",\n                \"autostart\": \"자동시작\",\n                \"controls\": \"컨트롤\",\n                \"endGame\": \"게임 클리어\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"유지\",\n                \"actionReset\": \"재설정\",\n                \"actionSet\": \"설정\",\n                \"phraseAllSections\": \"모든 섹션\",\n                \"propBackground\": \"배경\",\n                \"propBounds\": \"경계\",\n                \"propMusic\": \"음악\"\n            },\n            \"settingsForEvent\": \"이벤트 {0}에 대한 설정\",\n            \"promptEventName\": \"이벤트 이름\",\n            \"promptEventText\": \"이벤트 텍스트\"\n        },\n        \"browser\": {\n            \"saveFile\": \"저장 파일\",\n            \"itemNewFolder\": \"<새 폴더>\",\n            \"newFile\": \"새 파일\",\n            \"openFile\": \"파일 열기\",\n            \"askOverwriteFile\": \"{0}을(를) 덮어쓸까요?\",\n            \"itemNewFile\": \"<새 파일>\"\n        },\n        \"letterUp\": \"U\"\n    },\n    \"menu\": {\n        \"game\": {\n            \"gameNoBattleLevels\": \"<전투 레벨 없음>\",\n            \"gameSourceSlot\": \"소스 슬롯 선택\",\n            \"gameEraseSave\": \"저장 지우기\",\n            \"gameTargetSlot\": \"이제 대상을 선택\",\n            \"gameSlotContinue\": \"슬롯 {0} ... {1}%\",\n            \"gameSlotNew\": \"슬롯 {0} ... 새로운 게임\",\n            \"phraseScore\": \"점수: {0}\",\n            \"gameNoEpisodesToPlay\": \"<재생할 에피소드 없음>\",\n            \"phraseTime\": \"시간: {0}\",\n            \"gameEraseSlot\": \"삭제할 슬롯 선택\",\n            \"gameBattleRandom\": \"랜덤 레벨\",\n            \"gameCopySave\": \"저장 복사\",\n            \"warnEpCompat\": \"\"\n        },\n        \"wordHide\": \"숨김\",\n        \"options\": {\n            \"restartEngine\": \"변경 사항을 적용하려면 엔진을 다시 시작하세요.\",\n            \"advanced\": {\n                \"render\": {\n                    \"opengl11-tip\": \"\",\n                    \"sw\": \"\",\n                    \"opengles11-tip\": \"\",\n                    \"opengl-tip\": \"\",\n                    \"sdl\": \"\",\n                    \"_name\": \"렌더 모드\",\n                    \"opengl\": \"\",\n                    \"hw\": \"자동\",\n                    \"sdl-tip\": \"\",\n                    \"opengl11\": \"\",\n                    \"opengles\": \"\",\n                    \"opengles11\": \"\",\n                    \"opengles-tip\": \"\"\n                },\n                \"scale-down-textures\": {\n                    \"_name\": \"\",\n                    \"all\": \"\",\n                    \"safe\": \"\",\n                    \"all-tip\": \"\",\n                    \"none-tip\": \"\",\n                    \"none\": \"\",\n                    \"safe-tip\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"advanced-audio\": \"\",\n                \"webm-recording\": \"\",\n                \"_header\": \"\",\n                \"_tooltip\": \"\",\n                \"audio-channels\": {\n                    \"mono\": \"\",\n                    \"stereo\": \"\",\n                    \"_name\": \"\"\n                },\n                \"audio-format\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"audio-enable\": \"\",\n                \"audio-sample-rate\": {\n                    \"11025\": \"\",\n                    \"48000\": \"\",\n                    \"_name\": \"\",\n                    \"32000\": \"\",\n                    \"16000\": \"\",\n                    \"22050\": \"\",\n                    \"44100\": \"\"\n                },\n                \"log-level\": {\n                    \"debug\": \"\",\n                    \"_name\": \"\",\n                    \"fatal\": \"\",\n                    \"critical\": \"\",\n                    \"info\": \"\",\n                    \"warning\": \"\",\n                    \"none\": \"\"\n                },\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"record-gameplay-data\": \"\",\n                \"internal-res-4p\": {\n                    \"fhd\": \"\",\n                    \"qsmbx\": \"\",\n                    \"qhd\": \"\",\n                    \"default\": \"\",\n                    \"_name\": \"\"\n                },\n                \"choose-assets-on-launch\": \"\",\n                \"advanced-video\": \"\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"main\": {\n                \"unlimited-framerate\": \"\",\n                \"timing\": \"\",\n                \"four-screen-mode\": {\n                    \"_name\": \"\",\n                    \"split\": \"\",\n                    \"shared\": \"\"\n                },\n                \"multiplayer\": \"\",\n                \"language\": \"언어\",\n                \"vsync\": \"\",\n                \"two-screen-mode\": {\n                    \"shared\": \"\",\n                    \"split\": \"\",\n                    \"smbx\": \"\",\n                    \"topbottom\": \"\",\n                    \"_name\": \"\"\n                },\n                \"background-work-tip\": \"\",\n                \"background-work\": \"\",\n                \"_header\": \"\",\n                \"discord-rpc\": \"\",\n                \"frame-skip\": \"\"\n            },\n            \"video\": {\n                \"battery-status\": {\n                    \"low\": \"\",\n                    \"_name\": \"\",\n                    \"off\": \"\",\n                    \"on\": \"\",\n                    \"fullscreen\": \"전체화면\",\n                    \"fullscreen-tip\": \"\",\n                    \"low-tip\": \"\"\n                },\n                \"enable-inter-level-fade-effect\": \"\",\n                \"show-episode-title\": {\n                    \"_name\": \"\",\n                    \"bottom-tip\": \"\",\n                    \"top\": \"\",\n                    \"off\": \"\",\n                    \"bottom\": \"\",\n                    \"top-tip\": \"\"\n                },\n                \"internal-res\": {\n                    \"gba\": \"\",\n                    \"vga\": \"\",\n                    \"_name\": \"\",\n                    \"3ds\": \"\",\n                    \"_tooltip\": \"\",\n                    \"nds\": \"\",\n                    \"dynamic\": \"\",\n                    \"smbx\": \"\",\n                    \"hello\": \"\",\n                    \"hd\": \"\",\n                    \"snes\": \"\",\n                    \"smbx-wide\": \"\"\n                },\n                \"scale-mode\": {\n                    \"integer\": \"정수\",\n                    \"center\": \"\",\n                    \"_name\": \"스케일\",\n                    \"nearest\": \"가장 가까운\",\n                    \"1x\": \"\",\n                    \"0_5x\": \"\",\n                    \"full\": \"\",\n                    \"linear\": \"이중선형\",\n                    \"2x\": \"\",\n                    \"3x\": \"\"\n                },\n                \"_header\": \"\",\n                \"display-controllers\": \"\",\n                \"info-run\": \"\",\n                \"info-meta\": \"\",\n                \"effects\": \"\",\n                \"fullscreen\": \"전체화면\",\n                \"show-fails-counter\": \"\",\n                \"3d-compat-mode-tip\": \"\",\n                \"show-fails-counter-tip\": \"\",\n                \"3d-compat-mode\": \"\",\n                \"show-fps\": \"\",\n                \"show-playtime-counter\": {\n                    \"animated-tip\": \"\",\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\",\n                    \"off\": \"\",\n                    \"animated\": \"\",\n                    \"off-tip\": \"\",\n                    \"transparent\": \"\",\n                    \"opaque\": \"\"\n                },\n                \"show-medals-counter-tip\": \"\",\n                \"show-medals-counter\": \"\",\n                \"video-system\": \"\",\n                \"show-screen-shake\": \"\",\n                \"world-map-stars-show\": \"\",\n                \"world-map-stars-show-tip\": \"\",\n                \"info-game\": \"\"\n            },\n            \"controls\": {\n                \"_header\": \"\"\n            },\n            \"episode-options\": {\n                \"_header\": \"\",\n                \"creator-compat\": {\n                    \"_name\": \"\",\n                    \"file-only\": \"\",\n                    \"enable\": \"\",\n                    \"disable\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"playstyle\": {\n                    \"modern-tip\": \"\",\n                    \"modern\": \"\",\n                    \"classic\": \"\",\n                    \"classic-tip\": \"\",\n                    \"vanilla\": \"\",\n                    \"vanilla-tip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"_name\": \"\",\n                    \"3\": \"\",\n                    \"2\": \"\",\n                    \"1\": \"\",\n                    \"0\": \"\"\n                }\n            },\n            \"audio\": {\n                \"_header\": \"\",\n                \"audio-sfx-volume\": \"\",\n                \"audio-prefs\": \"\",\n                \"sfx-pet-beat\": \"\",\n                \"sfx-spatial-audio\": \"\",\n                \"sfx-pet-beat-tip\": \"\",\n                \"audio-music-volume\": \"\",\n                \"sfx-modern\": \"\",\n                \"sfx-modern-tip\": \"\",\n                \"sfx-spatial-audio-tip\": \"\",\n                \"sfx-audio-fx\": \"\"\n            },\n            \"compatibility\": {\n                \"_header\": \"\",\n                \"features\": \"\",\n                \"bugfixes\": \"\",\n                \"_tooltip\": \"\",\n                \"autocode\": \"\",\n                \"reset-all\": \"\"\n            },\n            \"view-credits\": {\n                \"_header\": \"크레딧 보기\"\n            }\n        },\n        \"wordWaiting\": \"대기 중\",\n        \"main\": {\n            \"mainEditor\": \"편집기\",\n            \"main1PlayerGame\": \"1 인용 게임\",\n            \"mainMultiplayerGame\": \"2 인용 게임\",\n            \"mainBattleGame\": \"전투 게임\",\n            \"mainOptions\": \"옵션\",\n            \"mainExit\": \"종료\",\n            \"mainPlayEpisode\": \"에피소드 재생\"\n        },\n        \"controls\": {\n            \"editor\": {\n                \"scrollUp\": \"위로 스크롤\",\n                \"prevSection\": \"이전 섹션\",\n                \"scrollRight\": \"오른쪽으로 스크롤\",\n                \"scrollLeft\": \"왼쪽으로 스크롤\",\n                \"scrollDown\": \"스크롤 다운\",\n                \"fastScroll\": \"빠른 스크롤\",\n                \"modeErase\": \"지우기 모드\",\n                \"modeSelect\": \"선택 모드\",\n                \"nextSection\": \"다음 선택\",\n                \"switchScreens\": \"창 표시\",\n                \"testPlay\": \"재생 테스트\"\n            },\n            \"buttons\": {\n                \"jump\": \"점프\",\n                \"altRun\": \"대체 실행\",\n                \"down\": \"아래\",\n                \"drop\": \"아이템 삭제\",\n                \"altJump\": \"대체 점프\",\n                \"right\": \"오른쪽\",\n                \"up\": \"위쪽\",\n                \"left\": \"왼쪽\",\n                \"run\": \"실행\",\n                \"start\": \"시작\"\n            },\n            \"controlsConnected\": \"연결됨:\",\n            \"controlsDeviceTypes\": \"장치 유형\",\n            \"controlsInUse\": \"(사용)\",\n            \"controlsNewProfile\": \"<새 프로파일>\",\n            \"cursor\": {\n                \"up\": \"마우스 위쪽\",\n                \"right\": \"마우스 오른쪽\",\n                \"tertiary\": \"3차\",\n                \"secondary\": \"2차\",\n                \"down\": \"마우스 아래쪽\",\n                \"left\": \"마우스 왼쪽\",\n                \"primary\": \"1차\"\n            },\n            \"controlsDeleteKey\": \"(대체 점프로 삭제)\",\n            \"controlsDeleteKey2\": \"(대체 실행로 삭제)\",\n            \"caseInvalid\": \"(유효하지 않음)\",\n            \"caseMouse\": \"(마우스)\",\n            \"caseTouch\": \"(터치)\",\n            \"controlsNotInUse\": \"(사용하지 않음)\",\n            \"controlsReallyDeleteProfile\": \"프로파일을 삭제하겠습니까?\",\n            \"controlsTitle\": \"컨트롤\",\n            \"hotkeys\": {\n                \"fullscreen\": \"전체화면\",\n                \"debugInfo\": \"디버그 정보\",\n                \"enterCheats\": \"코드 입력\",\n                \"legacyPause\": \"이전 일시 중지\",\n                \"recordGif\": \"GIF 녹화\",\n                \"screenshot\": \"스크린샷\",\n                \"toggleHUD\": \"상태 표시줄 토글\",\n                \"vanillaCam\": \"\"\n            },\n            \"tDS\": {\n                \"buttonA\": \"A\",\n                \"buttonB\": \"B\",\n                \"buttonY\": \"Y\",\n                \"buttonZL\": \"ZL\",\n                \"cStick\": \"C-스틱\",\n                \"casePen\": \"(펜)\",\n                \"dPad\": \"십자패드\",\n                \"tStick\": \"썸네일\",\n                \"buttonStart\": \"시작\",\n                \"buttonX\": \"X\",\n                \"buttonL\": \"L\",\n                \"buttonR\": \"R\",\n                \"buttonSelect\": \"선택\",\n                \"buttonZR\": \"ZR\"\n            },\n            \"phraseNewProfOldJoy\": \"이전 조이스틱의 새 프로파일\",\n            \"profile\": {\n                \"playerControls\": \"플레이어 컨트롤\",\n                \"renameProfile\": \"프로파일 이름바꾸기\",\n                \"editorControls\": \"편집기 컨트롤\",\n                \"cursorControls\": \"커서 컨트롤\",\n                \"hotkeys\": \"단축키\",\n                \"deleteProfile\": \"프로파일 삭제\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"longOld\": \"와이드 (이전)\",\n                    \"phabletOld\": \"패블릿 (이전)\",\n                    \"phoneOld\": \"폰 (이전)\",\n                    \"standard\": \"보통\",\n                    \"tabletOld\": \"태블릿 (이전)\",\n                    \"tight\": \"빽빽이\",\n                    \"tinyOld\": \"아주 작음 (이전)\"\n                },\n                \"option\": {\n                    \"feedbackStrength\": \"피드백 강도\",\n                    \"holdRun\": \"시작 시 실행 보류\",\n                    \"interfaceStyle\": \"인터페이스 스타일\",\n                    \"layoutStyle\": \"레이아웃 스타일\",\n                    \"scaleDPad\": \"스케일 십자패드\",\n                    \"scaleFactor\": \"스케일 팩터\",\n                    \"showCodeButton\": \"코드 버튼 표시\",\n                    \"scaleButtons\": \"스케일 버튼\",\n                    \"feedbackLength\": \"피드백 길이\",\n                    \"resetLayout\": \"레이아웃 재설정\",\n                    \"sStartSpacing\": \"S-시작 간격\"\n                },\n                \"style\": {\n                    \"actions\": \"동작\",\n                    \"ABXY\": \"ABXY\",\n                    \"XODA\": \"XODA\"\n                }\n            },\n            \"types\": {\n                \"touchscreen\": \"터치스크린\",\n                \"gamepad\": \"게임패드\",\n                \"keyboard\": \"키보드\",\n                \"oldJoystick\": \"오래된 조이스틱\"\n            },\n            \"wii\": {\n                \"classic\": {\n                    \"buttonLT\": \"LT\",\n                    \"buttonRT\": \"RT\",\n                    \"buttonX\": \"X\",\n                    \"buttonY\": \"Y\",\n                    \"buttonZL\": \"ZL\",\n                    \"buttonZR\": \"ZR\",\n                    \"lStick\": \"L-패드\",\n                    \"rStick\": \"R-패드\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"C\",\n                    \"buttonZ\": \"Z\",\n                    \"prefixN\": \"N\"\n                },\n                \"wiimote\": {\n                    \"buttonA\": \"A\",\n                    \"button1\": \"1\",\n                    \"button2\": \"2\",\n                    \"buttonB\": \"B\",\n                    \"buttonHome\": \"Home\",\n                    \"buttonMinus\": \"-\",\n                    \"buttonPlus\": \"+\",\n                    \"caseIR\": \"(IR)\",\n                    \"dPad\": \"십자패드\",\n                    \"shake\": \"진동\"\n                },\n                \"phraseNewNunchuck\": \"새 눈챠크 프로파일\",\n                \"phraseNewClassic\": \"새 클래식 프로파일\",\n                \"typeClassic\": \"클래식\",\n                \"typeNunchuck\": \"눈챠크\",\n                \"typeWiimote\": \"위모트\",\n                \"typeGamecube\": \"\"\n            },\n            \"options\": {\n                \"textEntryStyle\": \"텍스트 입력 스타일\",\n                \"maxPlayers\": \"최대 플레이어\",\n                \"rumble\": \"진동\",\n                \"batteryStatus\": \"배터리 상태\"\n            },\n            \"wordButtons\": \"버튼\",\n            \"wordProfiles\": \"프로파일\",\n            \"joystickSimpleEditor\": \"\"\n        },\n        \"wordPlayer\": \"플레이어\",\n        \"wordBack\": \"뒤로가기\",\n        \"wordOn\": \"켬\",\n        \"wordProfile\": \"프로파일\",\n        \"loading\": \"불러오기 중...\",\n        \"wordShow\": \"표시\",\n        \"wordNo\": \"아니오\",\n        \"wordYes\": \"예\",\n        \"wordResume\": \"다시 시작\",\n        \"wordOff\": \"끔\",\n        \"abbrevMilliseconds\": \"초\",\n        \"battle\": {\n            \"errorNoLevels\": \"레벨이 없어서 전투를 시작할 수 없음\"\n        },\n        \"caseNone\": \"<없음>\",\n        \"character\": {\n            \"selectCharacter\": \"{0} 게임\"\n        },\n        \"editor\": {\n            \"battles\": \"<전투 레벨>\",\n            \"errorMissingResources\": \"죄송합니다! 게임 내 편집기에 필요한 {0}이 누락되었습니다.\",\n            \"newWorld\": \"<새로운 세계>\",\n            \"promptNewWorldName\": \"새로운 세계 이름\",\n            \"makeFor\": \"\"\n        }\n    },\n    \"outro\": {\n        \"originalBy\": \"원본 VB6 코드 게시자:\",\n        \"cppPortDevelopers\": \"C++ 포트 개발자:\",\n        \"nameAndrewSpinks\": \"앤드류 스핑크스\",\n        \"customSprites\": \"커스텀 스프라이트:\",\n        \"specialThanks\": \"특별히 감사한 분들:\",\n        \"psVitaPortBy\": \"PS 비타 포트 게시자:\",\n        \"engineCredits\": \"엔진 크레딧:\",\n        \"levelDesign\": \"레벨 디자인:\",\n        \"nameVitalyNovichkov\": \"비탈리 노비치코프\",\n        \"gameCredits\": \"게임 크레딧:\",\n        \"qualityControl\": \"품질 제어:\"\n    },\n    \"pluralRules\": \"singular-only\",\n    \"languageName\": \"한국어\",\n    \"game\": {\n        \"connect\": {\n            \"phraseTestProfile\": \"프로파일 테스트\",\n            \"phraseDropMe\": \"게임에서 나가기\",\n            \"phraseDropPX\": \"플레이어 {0} 삭제\",\n            \"phraseForceResume\": \"강제 시작\",\n            \"phraseHoldStart\": \"시작 보류\",\n            \"phraseSetControls\": \"컨트롤 설정\",\n            \"phraseStartToForceRes\": \"강제 시작시 Start 누름\",\n            \"phraseStartToResume\": \"시작시 Start 누름\",\n            \"phraseWaitingForInput\": \"입력 장치를 기다리는 중...\",\n            \"reconnectTitle\": \"다시 연결\",\n            \"dropAddTitle\": \"플레이어 삭제/추가\",\n            \"phraseChangeChar\": \"캐릭터 변경\",\n            \"phrasePressAButton\": \"A 버튼 누름\",\n            \"phraseTestControls\": \"컨트롤 테스트\",\n            \"splitPressSelect_1\": \"선택을 누름\",\n            \"splitPressSelect_2\": \"컨트롤 옵션\",\n            \"wordDisconnect\": \"연결 끊기\"\n        },\n        \"error\": {\n            \"warpNeedStarCount\": \"입력하려면 {0} {1}이(가) 필요합니다.\",\n            \"errorInvalidEnterWarp\": \"잘못된 입구 워프 {1}이(가) 지정되었기 때문에 레벨을 시작할 수 없습니다.\\n총 워프 항목: {2}\\n\\n파일: {0}\",\n            \"openFileFailed\": \"\\\"{0}\\\"을(를) 열 수 없습니다. 파일이 존재하지 않거나 손상되었습니다.\",\n            \"errorNoStartPoint\": \"사용 가능한 시작점이 배치되지 않았거나 입구 워프가 지정되어 있지 않아 레벨을 시작할 수 없습니다.\\n\\n파일: {0}\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"pause\": {\n            \"continue\": \"계속하기\",\n            \"quit\": \"종료\",\n            \"quitTesting\": \"종료 테스트\",\n            \"enterCode\": \"코드 입력\",\n            \"saveAndQuit\": \"저장하고 종료\",\n            \"resetCheckpoints\": \"체크포인트 재설정\",\n            \"restartLevel\": \"레벨 재시작\",\n            \"returnToEditor\": \"편집기로 돌아가기\",\n            \"saveAndContinue\": \"저장하고 계속하기\",\n            \"playerSetup\": \"플레이어 설정\"\n        },\n        \"msgbox\": {\n            \"sysInfoError\": \"오류입니다!\",\n            \"sysInfoTitle\": \"\",\n            \"sysInfoWarning\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"레벨 스캔 중...\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0}분{1}초\"\n        },\n        \"hint\": {\n            \"no-lives-new\": \"실패하면 점수가 재설정됩니다.\",\n            \"no-lives-old\": \"실패하면 게임이 끝납니다.\",\n            \"rainbow-surf\": \"잡고, 달리고, 누르고, 놓아서 서핑하세요.\",\n            \"char5-bombs\": \"모으려면 Run을 누르고, 던지려면 Alt Run을 누르세요.\",\n            \"gray-bricks\": \"\",\n            \"heavy-duck\": \"\",\n            \"shoe-block\": \"\",\n            \"pound-altrun\": \"\",\n            \"pound-down\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    }\n}\n"
  },
  {
    "path": "resources/languages/thextech_nb-no.json",
    "content": "{\n    \"menu\": {\n        \"options\": {\n            \"audio\": {\n                \"sfx-pet-beat\": \"\",\n                \"audio-prefs\": \"\",\n                \"sfx-pet-beat-tip\": \"\",\n                \"_header\": \"\",\n                \"audio-music-volume\": \"\",\n                \"sfx-modern-tip\": \"\",\n                \"audio-sfx-volume\": \"\",\n                \"sfx-audio-fx\": \"\",\n                \"sfx-modern\": \"\",\n                \"sfx-spatial-audio-tip\": \"\",\n                \"sfx-spatial-audio\": \"\"\n            },\n            \"video\": {\n                \"battery-status\": {\n                    \"fullscreen-tip\": \"\",\n                    \"fullscreen\": \"\",\n                    \"on\": \"\",\n                    \"off\": \"\",\n                    \"_name\": \"\",\n                    \"low\": \"\",\n                    \"low-tip\": \"\"\n                },\n                \"3d-compat-mode-tip\": \"\",\n                \"enable-inter-level-fade-effect\": \"\",\n                \"show-medals-counter-tip\": \"\",\n                \"scale-mode\": {\n                    \"full\": \"\",\n                    \"center\": \"\",\n                    \"1x\": \"\",\n                    \"2x\": \"\",\n                    \"integer\": \"\",\n                    \"0_5x\": \"\",\n                    \"_name\": \"\",\n                    \"nearest\": \"\",\n                    \"linear\": \"\",\n                    \"3x\": \"\"\n                },\n                \"show-playtime-counter\": {\n                    \"off-tip\": \"\",\n                    \"_name\": \"\",\n                    \"off\": \"\",\n                    \"animated\": \"\",\n                    \"animated-tip\": \"\",\n                    \"transparent\": \"\",\n                    \"opaque\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"_header\": \"\",\n                \"info-game\": \"\",\n                \"fullscreen\": \"\",\n                \"info-meta\": \"\",\n                \"internal-res\": {\n                    \"dynamic\": \"\",\n                    \"3ds\": \"\",\n                    \"_name\": \"\",\n                    \"hd\": \"\",\n                    \"smbx\": \"\",\n                    \"nds\": \"\",\n                    \"smbx-wide\": \"\",\n                    \"hello\": \"\",\n                    \"vga\": \"\",\n                    \"snes\": \"\",\n                    \"gba\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"show-fails-counter\": \"\",\n                \"show-fails-counter-tip\": \"\",\n                \"world-map-stars-show\": \"\",\n                \"show-screen-shake\": \"\",\n                \"show-fps\": \"\",\n                \"world-map-stars-show-tip\": \"\",\n                \"show-medals-counter\": \"\",\n                \"info-run\": \"\",\n                \"display-controllers\": \"\",\n                \"effects\": \"\",\n                \"show-episode-title\": {\n                    \"_name\": \"\",\n                    \"bottom\": \"\",\n                    \"bottom-tip\": \"\",\n                    \"off\": \"\",\n                    \"top-tip\": \"\",\n                    \"top\": \"\"\n                },\n                \"video-system\": \"\",\n                \"3d-compat-mode\": \"\"\n            },\n            \"advanced\": {\n                \"advanced-audio\": \"\",\n                \"webm-recording\": \"\",\n                \"render\": {\n                    \"opengles11\": \"\",\n                    \"sdl\": \"\",\n                    \"opengles11-tip\": \"\",\n                    \"sw\": \"\",\n                    \"hw\": \"\",\n                    \"_name\": \"\",\n                    \"opengl\": \"\",\n                    \"opengl11\": \"\",\n                    \"opengl-tip\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"opengl11-tip\": \"\",\n                    \"opengles\": \"\",\n                    \"opengles-tip\": \"\"\n                },\n                \"log-level\": {\n                    \"none\": \"\",\n                    \"info\": \"\",\n                    \"critical\": \"\",\n                    \"fatal\": \"\",\n                    \"debug\": \"\",\n                    \"_name\": \"\",\n                    \"warning\": \"\"\n                },\n                \"_header\": \"\",\n                \"audio-format\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"audio-sample-rate\": {\n                    \"16000\": \"\",\n                    \"11025\": \"\",\n                    \"_name\": \"\",\n                    \"48000\": \"\",\n                    \"44100\": \"\",\n                    \"22050\": \"\",\n                    \"32000\": \"\"\n                },\n                \"choose-assets-on-launch\": \"\",\n                \"advanced-video\": \"\",\n                \"_tooltip\": \"\",\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"scale-down-textures\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\",\n                    \"all\": \"\",\n                    \"all-tip\": \"\",\n                    \"none-tip\": \"\",\n                    \"safe\": \"\",\n                    \"safe-tip\": \"\",\n                    \"none\": \"\"\n                },\n                \"record-gameplay-data\": \"\",\n                \"internal-res-4p\": {\n                    \"default\": \"\",\n                    \"fhd\": \"\",\n                    \"_name\": \"\",\n                    \"qsmbx\": \"\",\n                    \"qhd\": \"\"\n                },\n                \"audio-channels\": {\n                    \"mono\": \"\",\n                    \"_name\": \"\",\n                    \"stereo\": \"\"\n                },\n                \"audio-enable\": \"\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"main\": {\n                \"two-screen-mode\": {\n                    \"smbx\": \"\",\n                    \"_name\": \"\",\n                    \"shared\": \"\",\n                    \"topbottom\": \"\",\n                    \"split\": \"\"\n                },\n                \"frame-skip\": \"\",\n                \"_header\": \"\",\n                \"four-screen-mode\": {\n                    \"shared\": \"\",\n                    \"_name\": \"\",\n                    \"split\": \"\"\n                },\n                \"timing\": \"\",\n                \"unlimited-framerate\": \"\",\n                \"background-work-tip\": \"\",\n                \"background-work\": \"\",\n                \"language\": \"\",\n                \"multiplayer\": \"\",\n                \"discord-rpc\": \"\",\n                \"vsync\": \"\"\n            },\n            \"episode-options\": {\n                \"playstyle\": {\n                    \"modern-tip\": \"\",\n                    \"vanilla\": \"\",\n                    \"vanilla-tip\": \"\",\n                    \"modern\": \"\",\n                    \"_name\": \"\",\n                    \"classic-tip\": \"\",\n                    \"classic\": \"\"\n                },\n                \"creator-compat\": {\n                    \"file-only\": \"\",\n                    \"_name\": \"\",\n                    \"disable\": \"\",\n                    \"_tooltip\": \"\",\n                    \"enable\": \"\"\n                },\n                \"_header\": \"\",\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"0\": \"\",\n                    \"2\": \"\",\n                    \"1\": \"\",\n                    \"3\": \"\",\n                    \"_name\": \"\"\n                }\n            },\n            \"restartEngine\": \"\",\n            \"view-credits\": {\n                \"_header\": \"\"\n            },\n            \"controls\": {\n                \"_header\": \"\"\n            },\n            \"compatibility\": {\n                \"autocode\": \"\",\n                \"_tooltip\": \"\",\n                \"bugfixes\": \"\",\n                \"_header\": \"\",\n                \"features\": \"\",\n                \"reset-all\": \"\"\n            }\n        },\n        \"game\": {\n            \"gameSlotNew\": \"\",\n            \"gameEraseSlot\": \"\",\n            \"gameNoEpisodesToPlay\": \"\",\n            \"gameSlotContinue\": \"\",\n            \"phraseScore\": \"\",\n            \"gameCopySave\": \"\",\n            \"phraseTime\": \"\",\n            \"gameEraseSave\": \"\",\n            \"gameTargetSlot\": \"\",\n            \"gameBattleRandom\": \"\",\n            \"gameNoBattleLevels\": \"\",\n            \"gameSourceSlot\": \"\",\n            \"warnEpCompat\": \"\"\n        },\n        \"loading\": \"\",\n        \"wordPlayer\": \"\",\n        \"controls\": {\n            \"editor\": {\n                \"scrollLeft\": \"\",\n                \"scrollDown\": \"\",\n                \"scrollRight\": \"\",\n                \"nextSection\": \"\",\n                \"testPlay\": \"\",\n                \"fastScroll\": \"\",\n                \"modeErase\": \"\",\n                \"prevSection\": \"\",\n                \"switchScreens\": \"\",\n                \"modeSelect\": \"\",\n                \"scrollUp\": \"\"\n            },\n            \"tDS\": {\n                \"casePen\": \"\",\n                \"buttonStart\": \"\",\n                \"tStick\": \"\",\n                \"dPad\": \"\",\n                \"buttonA\": \"\",\n                \"buttonY\": \"\",\n                \"buttonB\": \"\",\n                \"buttonR\": \"\",\n                \"buttonSelect\": \"\",\n                \"buttonL\": \"\",\n                \"buttonZR\": \"\",\n                \"buttonX\": \"\",\n                \"buttonZL\": \"\",\n                \"cStick\": \"\"\n            },\n            \"touchscreen\": {\n                \"option\": {\n                    \"feedbackStrength\": \"\",\n                    \"scaleButtons\": \"\",\n                    \"showCodeButton\": \"\",\n                    \"feedbackLength\": \"\",\n                    \"holdRun\": \"\",\n                    \"interfaceStyle\": \"\",\n                    \"scaleFactor\": \"\",\n                    \"sStartSpacing\": \"\",\n                    \"resetLayout\": \"\",\n                    \"scaleDPad\": \"\",\n                    \"layoutStyle\": \"\"\n                },\n                \"style\": {\n                    \"actions\": \"\",\n                    \"XODA\": \"\",\n                    \"ABXY\": \"\"\n                },\n                \"layout\": {\n                    \"tight\": \"\",\n                    \"phabletOld\": \"\",\n                    \"longOld\": \"\",\n                    \"phoneOld\": \"\",\n                    \"tinyOld\": \"\",\n                    \"tabletOld\": \"\",\n                    \"standard\": \"\"\n                }\n            },\n            \"types\": {\n                \"keyboard\": \"\",\n                \"gamepad\": \"\",\n                \"oldJoystick\": \"\",\n                \"touchscreen\": \"\"\n            },\n            \"controlsNotInUse\": \"\",\n            \"wii\": {\n                \"classic\": {\n                    \"buttonY\": \"\",\n                    \"buttonZL\": \"\",\n                    \"lStick\": \"\",\n                    \"rStick\": \"\",\n                    \"buttonX\": \"\",\n                    \"buttonZR\": \"\",\n                    \"buttonLT\": \"\",\n                    \"buttonRT\": \"\"\n                },\n                \"phraseNewClassic\": \"\",\n                \"wiimote\": {\n                    \"buttonA\": \"\",\n                    \"button2\": \"\",\n                    \"button1\": \"\",\n                    \"dPad\": \"\",\n                    \"buttonB\": \"\",\n                    \"buttonHome\": \"\",\n                    \"buttonMinus\": \"\",\n                    \"buttonPlus\": \"\",\n                    \"shake\": \"\",\n                    \"caseIR\": \"\"\n                },\n                \"typeNunchuck\": \"\",\n                \"nunchuck\": {\n                    \"buttonC\": \"\",\n                    \"buttonZ\": \"\",\n                    \"prefixN\": \"\"\n                },\n                \"phraseNewNunchuck\": \"\",\n                \"typeClassic\": \"\",\n                \"typeWiimote\": \"\",\n                \"typeGamecube\": \"\"\n            },\n            \"wordProfiles\": \"\",\n            \"buttons\": {\n                \"jump\": \"\",\n                \"altJump\": \"\",\n                \"right\": \"\",\n                \"altRun\": \"\",\n                \"drop\": \"\",\n                \"up\": \"\",\n                \"down\": \"\",\n                \"left\": \"\",\n                \"run\": \"\",\n                \"start\": \"\"\n            },\n            \"cursor\": {\n                \"down\": \"\",\n                \"tertiary\": \"\",\n                \"left\": \"\",\n                \"right\": \"\",\n                \"up\": \"\",\n                \"secondary\": \"\",\n                \"primary\": \"\"\n            },\n            \"profile\": {\n                \"cursorControls\": \"\",\n                \"deleteProfile\": \"\",\n                \"renameProfile\": \"\",\n                \"playerControls\": \"\",\n                \"editorControls\": \"\",\n                \"hotkeys\": \"\"\n            },\n            \"hotkeys\": {\n                \"legacyPause\": \"\",\n                \"recordGif\": \"\",\n                \"toggleHUD\": \"\",\n                \"screenshot\": \"\",\n                \"vanillaCam\": \"\",\n                \"fullscreen\": \"\",\n                \"enterCheats\": \"\",\n                \"debugInfo\": \"\"\n            },\n            \"joystickSimpleEditor\": \"\",\n            \"controlsDeviceTypes\": \"\",\n            \"controlsConnected\": \"\",\n            \"controlsDeleteKey\": \"\",\n            \"options\": {\n                \"batteryStatus\": \"\",\n                \"textEntryStyle\": \"\",\n                \"maxPlayers\": \"\",\n                \"rumble\": \"\"\n            },\n            \"phraseNewProfOldJoy\": \"\",\n            \"wordButtons\": \"\",\n            \"controlsInUse\": \"\",\n            \"caseInvalid\": \"\",\n            \"caseMouse\": \"\",\n            \"caseTouch\": \"\",\n            \"controlsNewProfile\": \"\",\n            \"controlsReallyDeleteProfile\": \"\",\n            \"controlsTitle\": \"\"\n        },\n        \"main\": {\n            \"mainExit\": \"\",\n            \"mainBattleGame\": \"\",\n            \"main1PlayerGame\": \"\",\n            \"mainMultiplayerGame\": \"\",\n            \"mainOptions\": \"\",\n            \"mainEditor\": \"\",\n            \"mainPlayEpisode\": \"\"\n        },\n        \"wordResume\": \"\",\n        \"editor\": {\n            \"makeFor\": \"\",\n            \"errorMissingResources\": \"\",\n            \"newWorld\": \"\",\n            \"battles\": \"\",\n            \"promptNewWorldName\": \"\"\n        },\n        \"battle\": {\n            \"errorNoLevels\": \"\"\n        },\n        \"wordProfile\": \"\",\n        \"wordOff\": \"\",\n        \"wordNo\": \"\",\n        \"wordHide\": \"\",\n        \"wordWaiting\": \"\",\n        \"wordShow\": \"\",\n        \"wordOn\": \"\",\n        \"wordYes\": \"\",\n        \"character\": {\n            \"selectCharacter\": \"\"\n        },\n        \"caseNone\": \"\",\n        \"abbrevMilliseconds\": \"\",\n        \"wordBack\": \"\"\n    },\n    \"outro\": {\n        \"qualityControl\": \"\",\n        \"nameVitalyNovichkov\": \"\",\n        \"levelDesign\": \"\",\n        \"originalBy\": \"\",\n        \"cppPortDevelopers\": \"\",\n        \"engineCredits\": \"\",\n        \"specialThanks\": \"\",\n        \"nameAndrewSpinks\": \"\",\n        \"psVitaPortBy\": \"\",\n        \"gameCredits\": \"\",\n        \"customSprites\": \"\"\n    },\n    \"editor\": {\n        \"file\": {\n            \"convert\": {\n                \"descNew\": \"\"\n            },\n            \"optionCancelConversion\": \"\",\n            \"actionRevert\": \"\",\n            \"commandNew\": \"\",\n            \"optionCancelAction\": \"\",\n            \"optionYesSaveThenAction\": \"\",\n            \"sectionLevel\": \"\",\n            \"confirmConfirmAction\": \"\",\n            \"formatLegacy\": \"\",\n            \"labelCurrentFile\": \"\",\n            \"labelFormat\": \"\",\n            \"formatModern\": \"\",\n            \"optionProceedWithConversion\": \"\",\n            \"sectionWorld\": \"\",\n            \"optionActionWithoutSave\": \"\",\n            \"actionExit\": \"\",\n            \"actionClearWorld\": \"\",\n            \"confirmSaveBeforeAction\": \"\",\n            \"actionClearLevel\": \"\",\n            \"confirmConvertFormatTo\": \"\",\n            \"commandSave\": \"\",\n            \"commandOpen\": \"\",\n            \"commandSaveAs\": \"\",\n            \"actionOpen\": \"\"\n        },\n        \"phraseAreYouSure\": \"\",\n        \"npc\": {\n            \"ai\": {\n                \"target\": \"\",\n                \"LR\": \"\",\n                \"swim\": \"\",\n                \"use1_0Ai\": \"\",\n                \"jump\": \"\",\n                \"aiIs\": \"\",\n                \"headerCustomAi\": \"\",\n                \"leap\": \"\",\n                \"UD\": \"\"\n            },\n            \"inertNice\": \"\",\n            \"gen\": {\n                \"header\": \"\",\n                \"effectWarp\": \"\",\n                \"effectIs\": \"\",\n                \"effectShoot\": \"\",\n                \"direction\": \"\"\n            },\n            \"inContainer\": \"\",\n            \"props\": {\n                \"attachSurface\": \"\",\n                \"active\": \"\",\n                \"facing\": \"\"\n            },\n            \"stuckStop\": \"\",\n            \"tooltipExpandSection\": \"\",\n            \"abbrevGen\": \"\"\n        },\n        \"pageBlankOfBlank\": \"\",\n        \"section\": {\n            \"noTurnBack\": \"\",\n            \"horizWrap\": \"\",\n            \"setBounds\": \"\",\n            \"vertWrap\": \"\",\n            \"underwater\": \"\",\n            \"scroll\": \"\",\n            \"offscreenExit\": \"\"\n        },\n        \"phraseWarpIndex\": \"\",\n        \"toggleMagicBlock\": \"\",\n        \"tooltip\": {\n            \"area\": \"\",\n            \"file\": \"\",\n            \"erase\": \"\",\n            \"blocks\": \"\",\n            \"eraseAll\": \"\",\n            \"scenes\": \"\",\n            \"events\": \"\",\n            \"paths\": \"\",\n            \"select\": \"\",\n            \"settings\": \"\",\n            \"warps\": \"\",\n            \"water\": \"\",\n            \"music\": \"\",\n            \"levels\": \"\",\n            \"layers\": \"\",\n            \"show\": \"\",\n            \"tiles\": \"\",\n            \"BGOs\": \"\",\n            \"NPCs\": \"\"\n        },\n        \"warp\": {\n            \"allow\": \"\",\n            \"transit\": {\n                \"name1\": \"\",\n                \"name5\": \"\",\n                \"name3\": \"\",\n                \"name2\": \"\",\n                \"name4\": \"\",\n                \"name0\": \"\"\n            },\n            \"dir\": \"\",\n            \"cannonExit\": \"\",\n            \"effect\": \"\",\n            \"lvlWarp\": \"\",\n            \"showStarCount\": \"\",\n            \"speed\": \"\",\n            \"starLockMessage\": \"\",\n            \"style\": {\n                \"pipe\": \"\",\n                \"door\": \"\",\n                \"blipInstant\": \"\",\n                \"portal\": \"\",\n                \"style\": \"\"\n            },\n            \"target\": \"\",\n            \"toMap\": \"\",\n            \"twoWay\": \"\",\n            \"to\": \"\",\n            \"title\": \"\",\n            \"needStarCount\": \"\",\n            \"needFloor\": \"\",\n            \"out\": \"\",\n            \"needKey\": \"\",\n            \"showStartScene\": \"\",\n            \"item\": \"\",\n            \"ride\": \"\",\n            \"placing\": \"\",\n            \"in\": \"\"\n        },\n        \"world\": {\n            \"totalStars\": \"\",\n            \"name\": \"\",\n            \"retryOnFail\": \"\",\n            \"phraseCreditIndex\": \"\",\n            \"hubWorld\": \"\",\n            \"introLevel\": \"\",\n            \"allowChars\": \"\"\n        },\n        \"select\": {\n            \"pathBlankUnlock\": \"\",\n            \"allSectPropBlankForEventN\": \"\",\n            \"sectionBlankPropBlank\": \"\",\n            \"soundForEventN\": \"\",\n            \"warpTransitEffect\": \"\",\n            \"sectBlankPropBlankForEventN\": \"\",\n            \"worldMusic\": \"\"\n        },\n        \"water\": {\n            \"title\": \"\"\n        },\n        \"wordCoins\": \"\",\n        \"wordEvent\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\",\n            \"typeLabel\": \"\"\n        },\n        \"wordEnabled\": \"\",\n        \"wordNPC\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\"\n        },\n        \"events\": {\n            \"controlsForEventN\": \"\",\n            \"deletion\": {\n                \"deletingEvent\": \"\",\n                \"cancel\": \"\",\n                \"confirm\": \"\"\n            },\n            \"header\": \"\",\n            \"label\": {\n                \"activate\": \"\",\n                \"destroy\": \"\",\n                \"enter\": \"\",\n                \"talk\": \"\",\n                \"next\": \"\",\n                \"hit\": \"\",\n                \"layerClear\": \"\",\n                \"death\": \"\"\n            },\n            \"layers\": {\n                \"headerMove\": \"\",\n                \"headerHide\": \"\",\n                \"headerToggle\": \"\",\n                \"headerShow\": \"\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"\",\n                \"actionReset\": \"\",\n                \"propMusic\": \"\",\n                \"actionSet\": \"\",\n                \"phraseAllSections\": \"\",\n                \"propBackground\": \"\",\n                \"propBounds\": \"\"\n            },\n            \"settingsForEvent\": \"\",\n            \"desc\": {\n                \"phraseTriggersAfterTemplate\": \"\",\n                \"enter\": \"\",\n                \"talk\": \"\",\n                \"layerClear\": \"\",\n                \"activate\": \"\",\n                \"hit\": \"\",\n                \"death\": \"\",\n                \"destroy\": \"\",\n                \"phraseTriggersWhenTemplate\": \"\"\n            },\n            \"letter\": {\n                \"activate\": \"\",\n                \"hit\": \"\",\n                \"death\": \"\",\n                \"destroy\": \"\",\n                \"enter\": \"\",\n                \"layerClear\": \"\",\n                \"talk\": \"\"\n            },\n            \"props\": {\n                \"autostart\": \"\",\n                \"controls\": \"\",\n                \"endGame\": \"\",\n                \"sound\": \"\",\n                \"layerSmoke\": \"\"\n            },\n            \"promptEventText\": \"\",\n            \"bounds\": {\n                \"shouldEvent\": \"\",\n                \"changeSectionBoundsToCurrent\": \"\",\n                \"changeAllSectionBoundsToCurrent\": \"\"\n            },\n            \"headerTriggerEvent\": \"\",\n            \"itemNewEvent\": \"\",\n            \"promptEventName\": \"\"\n        },\n        \"block\": {\n            \"canBreak\": \"\",\n            \"inside\": \"\",\n            \"pickContents\": \"\",\n            \"canBreakTooltip\": \"\",\n            \"letterWidth\": \"\",\n            \"slick\": \"\",\n            \"letterHeight\": \"\",\n            \"invis\": \"\"\n        },\n        \"browser\": {\n            \"openFile\": \"\",\n            \"saveFile\": \"\",\n            \"askOverwriteFile\": \"\",\n            \"newFile\": \"\",\n            \"itemNewFile\": \"\",\n            \"itemNewFolder\": \"\"\n        },\n        \"layers\": {\n            \"deletion\": {\n                \"preserveLayerContents\": \"\",\n                \"cancel\": \"\",\n                \"confirmPreserve\": \"\",\n                \"header\": \"\",\n                \"confirmDelete\": \"\"\n            },\n            \"default\": \"\",\n            \"label\": \"\",\n            \"itemNewLayer\": \"\",\n            \"header\": \"\",\n            \"desc\": {\n                \"att\": \"\"\n            },\n            \"labelAttached\": \"\",\n            \"promptLayerName\": \"\",\n            \"labelMoveLayer\": \"\",\n            \"labelAttachedLayer\": \"\",\n            \"abbrevAttLayer\": \"\"\n        },\n        \"letterCoordY\": \"\",\n        \"phraseDelayIsMs\": \"\",\n        \"phraseTextOf\": \"\",\n        \"testPlay\": {\n            \"pet\": \"\",\n            \"boot\": \"\",\n            \"char\": \"\",\n            \"power\": \"\",\n            \"magicHand\": \"\"\n        },\n        \"wordHeight\": \"\",\n        \"level\": {\n            \"levelName\": \"\",\n            \"pathUnlocks\": \"\",\n            \"startPos\": \"\",\n            \"alwaysVis\": \"\",\n            \"bigBG\": \"\",\n            \"pathBG\": \"\",\n            \"gameStart\": \"\"\n        },\n        \"labelSortLayer\": \"\",\n        \"letterDown\": \"\",\n        \"letterCoordX\": \"\",\n        \"letterUp\": \"\",\n        \"letterRight\": \"\",\n        \"letterLeft\": \"\",\n        \"mapPos\": \"\",\n        \"phraseGenericIndex\": \"\",\n        \"phraseSectionIndex\": \"\",\n        \"phraseRadiusIndex\": \"\",\n        \"phraseCountMore\": \"\",\n        \"wordInstant\": \"\",\n        \"wordMode\": \"\",\n        \"wordText\": \"\",\n        \"wordWidth\": \"\",\n        \"labelSortOffset\": \"\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"splitPressSelect_1\": \"\",\n            \"reconnectTitle\": \"\",\n            \"dropAddTitle\": \"\",\n            \"wordDisconnect\": \"\",\n            \"phraseDropPX\": \"\",\n            \"phraseForceResume\": \"\",\n            \"phraseStartToResume\": \"\",\n            \"phraseTestControls\": \"\",\n            \"phraseTestProfile\": \"\",\n            \"phraseDropMe\": \"\",\n            \"splitPressSelect_2\": \"\",\n            \"phraseWaitingForInput\": \"\",\n            \"phraseStartToForceRes\": \"\",\n            \"phraseSetControls\": \"\",\n            \"phrasePressAButton\": \"\",\n            \"phraseChangeChar\": \"\",\n            \"phraseHoldStart\": \"\"\n        },\n        \"error\": {\n            \"warpNeedStarCount\": \"\",\n            \"openFileFailed\": \"\",\n            \"errorNoStartPoint\": \"\",\n            \"errorInvalidEnterWarp\": \"\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"hint\": {\n            \"no-lives-new\": \"\",\n            \"no-lives-old\": \"\",\n            \"char5-bombs\": \"\",\n            \"gray-bricks\": \"\",\n            \"rainbow-surf\": \"\",\n            \"heavy-duck\": \"\",\n            \"shoe-block\": \"\",\n            \"pound-altrun\": \"\",\n            \"pound-down\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"\"\n        },\n        \"pause\": {\n            \"quitTesting\": \"\",\n            \"continue\": \"\",\n            \"resetCheckpoints\": \"\",\n            \"quit\": \"\",\n            \"restartLevel\": \"\",\n            \"saveAndContinue\": \"\",\n            \"enterCode\": \"\",\n            \"saveAndQuit\": \"\",\n            \"returnToEditor\": \"\",\n            \"playerSetup\": \"\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"\"\n        },\n        \"msgbox\": {\n            \"sysInfoTitle\": \"\",\n            \"sysInfoWarning\": \"\",\n            \"sysInfoError\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"pluralRules\": \"\",\n    \"languageName\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_pl.json",
    "content": "{\n    \"menu\": {\n        \"options\": {\n            \"advanced\": {\n                \"advanced-video\": \"\",\n                \"choose-assets-on-launch\": \"\",\n                \"log-level\": {\n                    \"fatal\": \"\",\n                    \"critical\": \"\",\n                    \"warning\": \"\",\n                    \"info\": \"\",\n                    \"_name\": \"\",\n                    \"debug\": \"\",\n                    \"none\": \"\"\n                },\n                \"render\": {\n                    \"_name\": \"\",\n                    \"hw\": \"\",\n                    \"opengl11-tip\": \"\",\n                    \"opengl-tip\": \"\",\n                    \"opengl11\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"sdl\": \"\",\n                    \"opengles\": \"\",\n                    \"opengles11\": \"\",\n                    \"opengles-tip\": \"\",\n                    \"opengl\": \"\",\n                    \"sw\": \"\",\n                    \"opengles11-tip\": \"\"\n                },\n                \"audio-sample-rate\": {\n                    \"22050\": \"\",\n                    \"16000\": \"\",\n                    \"11025\": \"\",\n                    \"48000\": \"\",\n                    \"_name\": \"\",\n                    \"32000\": \"\",\n                    \"44100\": \"\"\n                },\n                \"audio-channels\": {\n                    \"mono\": \"\",\n                    \"_name\": \"\",\n                    \"stereo\": \"\"\n                },\n                \"scale-down-textures\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"safe\": \"\",\n                    \"none-tip\": \"\",\n                    \"none\": \"\",\n                    \"safe-tip\": \"\",\n                    \"all-tip\": \"\",\n                    \"all\": \"\"\n                },\n                \"record-gameplay-data\": \"\",\n                \"webm-recording\": \"\",\n                \"internal-res-4p\": {\n                    \"qhd\": \"\",\n                    \"qsmbx\": \"\",\n                    \"default\": \"\",\n                    \"_name\": \"\",\n                    \"fhd\": \"\"\n                },\n                \"_header\": \"\",\n                \"audio-buffer-size\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"audio-format\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"advanced-audio\": \"\",\n                \"_tooltip\": \"\",\n                \"audio-enable\": \"\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"episode-options\": {\n                \"creator-compat\": {\n                    \"disable\": \"\",\n                    \"file-only\": \"\",\n                    \"enable\": \"\",\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"playstyle\": {\n                    \"modern-tip\": \"\",\n                    \"vanilla\": \"\",\n                    \"_name\": \"\",\n                    \"classic-tip\": \"\",\n                    \"classic\": \"\",\n                    \"vanilla-tip\": \"\",\n                    \"modern\": \"\"\n                },\n                \"_header\": \"\",\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"1\": \"\",\n                    \"0\": \"\",\n                    \"_name\": \"\",\n                    \"3\": \"\",\n                    \"2\": \"\"\n                }\n            },\n            \"video\": {\n                \"world-map-stars-show-tip\": \"\",\n                \"show-playtime-counter\": {\n                    \"off-tip\": \"\",\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"animated-tip\": \"\",\n                    \"off\": \"\",\n                    \"animated\": \"\",\n                    \"opaque\": \"\",\n                    \"transparent\": \"\"\n                },\n                \"show-episode-title\": {\n                    \"top\": \"\",\n                    \"bottom\": \"\",\n                    \"_name\": \"\",\n                    \"bottom-tip\": \"\",\n                    \"off\": \"\",\n                    \"top-tip\": \"\"\n                },\n                \"show-medals-counter\": \"\",\n                \"effects\": \"\",\n                \"info-meta\": \"\",\n                \"internal-res\": {\n                    \"3ds\": \"\",\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"smbx\": \"\",\n                    \"hello\": \"\",\n                    \"hd\": \"\",\n                    \"nds\": \"\",\n                    \"smbx-wide\": \"\",\n                    \"vga\": \"\",\n                    \"snes\": \"\",\n                    \"gba\": \"\",\n                    \"dynamic\": \"\"\n                },\n                \"info-run\": \"\",\n                \"fullscreen\": \"\",\n                \"enable-inter-level-fade-effect\": \"\",\n                \"scale-mode\": {\n                    \"_name\": \"\",\n                    \"integer\": \"\",\n                    \"2x\": \"\",\n                    \"full\": \"\",\n                    \"center\": \"\",\n                    \"1x\": \"\",\n                    \"linear\": \"\",\n                    \"nearest\": \"\",\n                    \"0_5x\": \"\",\n                    \"3x\": \"\"\n                },\n                \"display-controllers\": \"\",\n                \"info-game\": \"\",\n                \"show-medals-counter-tip\": \"\",\n                \"battery-status\": {\n                    \"fullscreen-tip\": \"\",\n                    \"_name\": \"\",\n                    \"fullscreen\": \"\",\n                    \"low-tip\": \"\",\n                    \"on\": \"\",\n                    \"off\": \"\",\n                    \"low\": \"\"\n                },\n                \"_header\": \"\",\n                \"3d-compat-mode\": \"\",\n                \"show-fails-counter\": \"\",\n                \"show-fails-counter-tip\": \"\",\n                \"3d-compat-mode-tip\": \"\",\n                \"show-fps\": \"\",\n                \"show-screen-shake\": \"\",\n                \"video-system\": \"\",\n                \"world-map-stars-show\": \"\"\n            },\n            \"restartEngine\": \"\",\n            \"compatibility\": {\n                \"features\": \"\",\n                \"bugfixes\": \"\",\n                \"reset-all\": \"\",\n                \"autocode\": \"\",\n                \"_tooltip\": \"\",\n                \"_header\": \"\"\n            },\n            \"main\": {\n                \"two-screen-mode\": {\n                    \"topbottom\": \"\",\n                    \"smbx\": \"\",\n                    \"shared\": \"\",\n                    \"split\": \"\",\n                    \"_name\": \"\"\n                },\n                \"_header\": \"\",\n                \"background-work-tip\": \"\",\n                \"four-screen-mode\": {\n                    \"_name\": \"\",\n                    \"split\": \"\",\n                    \"shared\": \"\"\n                },\n                \"unlimited-framerate\": \"\",\n                \"background-work\": \"\",\n                \"frame-skip\": \"\",\n                \"discord-rpc\": \"\",\n                \"multiplayer\": \"\",\n                \"timing\": \"\",\n                \"language\": \"\",\n                \"vsync\": \"\"\n            },\n            \"audio\": {\n                \"_header\": \"\",\n                \"audio-music-volume\": \"\",\n                \"sfx-modern\": \"\",\n                \"sfx-audio-fx\": \"\",\n                \"audio-sfx-volume\": \"\",\n                \"sfx-modern-tip\": \"\",\n                \"sfx-pet-beat\": \"\",\n                \"sfx-spatial-audio-tip\": \"\",\n                \"sfx-pet-beat-tip\": \"\",\n                \"sfx-spatial-audio\": \"\",\n                \"audio-prefs\": \"\"\n            },\n            \"controls\": {\n                \"_header\": \"\"\n            },\n            \"view-credits\": {\n                \"_header\": \"\"\n            }\n        },\n        \"controls\": {\n            \"buttons\": {\n                \"altJump\": \"\",\n                \"down\": \"\",\n                \"drop\": \"\",\n                \"run\": \"\",\n                \"altRun\": \"\",\n                \"start\": \"\",\n                \"up\": \"\",\n                \"right\": \"\",\n                \"left\": \"\",\n                \"jump\": \"\"\n            },\n            \"hotkeys\": {\n                \"enterCheats\": \"\",\n                \"toggleHUD\": \"\",\n                \"fullscreen\": \"\",\n                \"debugInfo\": \"\",\n                \"legacyPause\": \"\",\n                \"screenshot\": \"\",\n                \"recordGif\": \"\",\n                \"vanillaCam\": \"\"\n            },\n            \"cursor\": {\n                \"down\": \"\",\n                \"primary\": \"\",\n                \"up\": \"\",\n                \"left\": \"\",\n                \"tertiary\": \"\",\n                \"secondary\": \"\",\n                \"right\": \"\"\n            },\n            \"editor\": {\n                \"testPlay\": \"\",\n                \"scrollUp\": \"\",\n                \"modeSelect\": \"\",\n                \"prevSection\": \"\",\n                \"nextSection\": \"\",\n                \"modeErase\": \"\",\n                \"switchScreens\": \"\",\n                \"scrollRight\": \"\",\n                \"fastScroll\": \"\",\n                \"scrollLeft\": \"\",\n                \"scrollDown\": \"\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"tight\": \"\",\n                    \"tabletOld\": \"\",\n                    \"phoneOld\": \"\",\n                    \"phabletOld\": \"\",\n                    \"standard\": \"\",\n                    \"longOld\": \"\",\n                    \"tinyOld\": \"\"\n                },\n                \"option\": {\n                    \"sStartSpacing\": \"\",\n                    \"layoutStyle\": \"\",\n                    \"resetLayout\": \"\",\n                    \"scaleDPad\": \"\",\n                    \"interfaceStyle\": \"\",\n                    \"holdRun\": \"\",\n                    \"scaleFactor\": \"\",\n                    \"showCodeButton\": \"\",\n                    \"feedbackStrength\": \"\",\n                    \"feedbackLength\": \"\",\n                    \"scaleButtons\": \"\"\n                },\n                \"style\": {\n                    \"XODA\": \"\",\n                    \"ABXY\": \"\",\n                    \"actions\": \"\"\n                }\n            },\n            \"types\": {\n                \"gamepad\": \"\",\n                \"keyboard\": \"\",\n                \"touchscreen\": \"\",\n                \"oldJoystick\": \"\"\n            },\n            \"caseMouse\": \"\",\n            \"options\": {\n                \"batteryStatus\": \"\",\n                \"textEntryStyle\": \"\",\n                \"maxPlayers\": \"\",\n                \"rumble\": \"\"\n            },\n            \"profile\": {\n                \"cursorControls\": \"\",\n                \"playerControls\": \"\",\n                \"deleteProfile\": \"\",\n                \"renameProfile\": \"\",\n                \"hotkeys\": \"\",\n                \"editorControls\": \"\"\n            },\n            \"tDS\": {\n                \"buttonX\": \"\",\n                \"buttonB\": \"\",\n                \"buttonL\": \"\",\n                \"buttonR\": \"\",\n                \"buttonStart\": \"\",\n                \"dPad\": \"\",\n                \"buttonA\": \"\",\n                \"buttonSelect\": \"\",\n                \"buttonY\": \"\",\n                \"tStick\": \"\",\n                \"buttonZL\": \"\",\n                \"casePen\": \"\",\n                \"buttonZR\": \"\",\n                \"cStick\": \"\"\n            },\n            \"wii\": {\n                \"wiimote\": {\n                    \"shake\": \"\",\n                    \"buttonA\": \"\",\n                    \"button1\": \"\",\n                    \"buttonMinus\": \"\",\n                    \"buttonPlus\": \"\",\n                    \"button2\": \"\",\n                    \"buttonB\": \"\",\n                    \"caseIR\": \"\",\n                    \"dPad\": \"\",\n                    \"buttonHome\": \"\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"\",\n                    \"buttonZ\": \"\",\n                    \"prefixN\": \"\"\n                },\n                \"phraseNewClassic\": \"\",\n                \"typeWiimote\": \"\",\n                \"typeNunchuck\": \"\",\n                \"typeClassic\": \"\",\n                \"classic\": {\n                    \"buttonLT\": \"\",\n                    \"buttonRT\": \"\",\n                    \"buttonY\": \"\",\n                    \"buttonZR\": \"\",\n                    \"buttonZL\": \"\",\n                    \"lStick\": \"\",\n                    \"buttonX\": \"\",\n                    \"rStick\": \"\"\n                },\n                \"phraseNewNunchuck\": \"\",\n                \"typeGamecube\": \"\"\n            },\n            \"controlsNewProfile\": \"\",\n            \"phraseNewProfOldJoy\": \"\",\n            \"joystickSimpleEditor\": \"\",\n            \"wordProfiles\": \"\",\n            \"controlsDeviceTypes\": \"\",\n            \"controlsInUse\": \"\",\n            \"controlsNotInUse\": \"\",\n            \"caseTouch\": \"\",\n            \"caseInvalid\": \"\",\n            \"controlsReallyDeleteProfile\": \"\",\n            \"controlsConnected\": \"\",\n            \"controlsDeleteKey\": \"\",\n            \"controlsTitle\": \"\",\n            \"wordButtons\": \"\"\n        },\n        \"game\": {\n            \"gameTargetSlot\": \"\",\n            \"gameNoBattleLevels\": \"\",\n            \"gameEraseSave\": \"\",\n            \"gameCopySave\": \"\",\n            \"gameEraseSlot\": \"\",\n            \"gameNoEpisodesToPlay\": \"\",\n            \"phraseTime\": \"\",\n            \"phraseScore\": \"\",\n            \"gameSlotNew\": \"\",\n            \"gameSlotContinue\": \"\",\n            \"gameSourceSlot\": \"\",\n            \"gameBattleRandom\": \"\",\n            \"warnEpCompat\": \"\"\n        },\n        \"wordProfile\": \"\",\n        \"abbrevMilliseconds\": \"\",\n        \"wordNo\": \"\",\n        \"wordWaiting\": \"\",\n        \"battle\": {\n            \"errorNoLevels\": \"\"\n        },\n        \"caseNone\": \"\",\n        \"character\": {\n            \"selectCharacter\": \"\"\n        },\n        \"main\": {\n            \"mainExit\": \"\",\n            \"mainEditor\": \"\",\n            \"mainMultiplayerGame\": \"\",\n            \"mainBattleGame\": \"\",\n            \"mainOptions\": \"\",\n            \"mainPlayEpisode\": \"\",\n            \"main1PlayerGame\": \"\"\n        },\n        \"wordYes\": \"\",\n        \"editor\": {\n            \"errorMissingResources\": \"\",\n            \"makeFor\": \"\",\n            \"battles\": \"\",\n            \"newWorld\": \"\",\n            \"promptNewWorldName\": \"\"\n        },\n        \"wordOff\": \"\",\n        \"wordPlayer\": \"\",\n        \"wordBack\": \"\",\n        \"wordOn\": \"\",\n        \"wordShow\": \"\",\n        \"wordResume\": \"\",\n        \"loading\": \"\",\n        \"wordHide\": \"\"\n    },\n    \"editor\": {\n        \"file\": {\n            \"convert\": {\n                \"descNew\": \"\"\n            },\n            \"actionRevert\": \"\",\n            \"confirmSaveBeforeAction\": \"\",\n            \"labelCurrentFile\": \"\",\n            \"formatModern\": \"\",\n            \"sectionLevel\": \"\",\n            \"sectionWorld\": \"\",\n            \"labelFormat\": \"\",\n            \"optionYesSaveThenAction\": \"\",\n            \"optionCancelConversion\": \"\",\n            \"optionProceedWithConversion\": \"\",\n            \"actionClearLevel\": \"\",\n            \"actionExit\": \"\",\n            \"actionOpen\": \"\",\n            \"confirmConvertFormatTo\": \"\",\n            \"commandOpen\": \"\",\n            \"commandSave\": \"\",\n            \"optionCancelAction\": \"\",\n            \"commandSaveAs\": \"\",\n            \"actionClearWorld\": \"\",\n            \"optionActionWithoutSave\": \"\",\n            \"formatLegacy\": \"\",\n            \"confirmConfirmAction\": \"\",\n            \"commandNew\": \"\"\n        },\n        \"layers\": {\n            \"deletion\": {\n                \"confirmDelete\": \"\",\n                \"preserveLayerContents\": \"\",\n                \"header\": \"\",\n                \"confirmPreserve\": \"\",\n                \"cancel\": \"\"\n            },\n            \"labelMoveLayer\": \"\",\n            \"promptLayerName\": \"\",\n            \"abbrevAttLayer\": \"\",\n            \"default\": \"\",\n            \"labelAttached\": \"\",\n            \"itemNewLayer\": \"\",\n            \"label\": \"\",\n            \"header\": \"\",\n            \"labelAttachedLayer\": \"\",\n            \"desc\": {\n                \"att\": \"\"\n            }\n        },\n        \"letterCoordX\": \"\",\n        \"level\": {\n            \"startPos\": \"\",\n            \"gameStart\": \"\",\n            \"bigBG\": \"\",\n            \"pathBG\": \"\",\n            \"levelName\": \"\",\n            \"alwaysVis\": \"\",\n            \"pathUnlocks\": \"\"\n        },\n        \"npc\": {\n            \"abbrevGen\": \"\",\n            \"ai\": {\n                \"LR\": \"\",\n                \"swim\": \"\",\n                \"target\": \"\",\n                \"jump\": \"\",\n                \"UD\": \"\",\n                \"use1_0Ai\": \"\",\n                \"leap\": \"\",\n                \"headerCustomAi\": \"\",\n                \"aiIs\": \"\"\n            },\n            \"inertNice\": \"\",\n            \"gen\": {\n                \"direction\": \"\",\n                \"effectIs\": \"\",\n                \"effectShoot\": \"\",\n                \"effectWarp\": \"\",\n                \"header\": \"\"\n            },\n            \"stuckStop\": \"\",\n            \"props\": {\n                \"facing\": \"\",\n                \"attachSurface\": \"\",\n                \"active\": \"\"\n            },\n            \"tooltipExpandSection\": \"\",\n            \"inContainer\": \"\"\n        },\n        \"select\": {\n            \"warpTransitEffect\": \"\",\n            \"worldMusic\": \"\",\n            \"sectBlankPropBlankForEventN\": \"\",\n            \"allSectPropBlankForEventN\": \"\",\n            \"sectionBlankPropBlank\": \"\",\n            \"soundForEventN\": \"\",\n            \"pathBlankUnlock\": \"\"\n        },\n        \"testPlay\": {\n            \"power\": \"\",\n            \"boot\": \"\",\n            \"char\": \"\",\n            \"pet\": \"\",\n            \"magicHand\": \"\"\n        },\n        \"tooltip\": {\n            \"erase\": \"\",\n            \"BGOs\": \"\",\n            \"NPCs\": \"\",\n            \"blocks\": \"\",\n            \"file\": \"\",\n            \"settings\": \"\",\n            \"layers\": \"\",\n            \"select\": \"\",\n            \"music\": \"\",\n            \"eraseAll\": \"\",\n            \"events\": \"\",\n            \"levels\": \"\",\n            \"scenes\": \"\",\n            \"paths\": \"\",\n            \"show\": \"\",\n            \"warps\": \"\",\n            \"water\": \"\",\n            \"tiles\": \"\",\n            \"area\": \"\"\n        },\n        \"warp\": {\n            \"allow\": \"\",\n            \"toMap\": \"\",\n            \"transit\": {\n                \"name0\": \"\",\n                \"name4\": \"\",\n                \"name5\": \"\",\n                \"name1\": \"\",\n                \"name3\": \"\",\n                \"name2\": \"\"\n            },\n            \"speed\": \"\",\n            \"cannonExit\": \"\",\n            \"dir\": \"\",\n            \"twoWay\": \"\",\n            \"title\": \"\",\n            \"effect\": \"\",\n            \"item\": \"\",\n            \"out\": \"\",\n            \"in\": \"\",\n            \"placing\": \"\",\n            \"lvlWarp\": \"\",\n            \"showStarCount\": \"\",\n            \"showStartScene\": \"\",\n            \"ride\": \"\",\n            \"starLockMessage\": \"\",\n            \"style\": {\n                \"blipInstant\": \"\",\n                \"style\": \"\",\n                \"door\": \"\",\n                \"pipe\": \"\",\n                \"portal\": \"\"\n            },\n            \"to\": \"\",\n            \"needFloor\": \"\",\n            \"needStarCount\": \"\",\n            \"target\": \"\",\n            \"needKey\": \"\"\n        },\n        \"wordText\": \"\",\n        \"wordWidth\": \"\",\n        \"events\": {\n            \"deletion\": {\n                \"deletingEvent\": \"\",\n                \"cancel\": \"\",\n                \"confirm\": \"\"\n            },\n            \"desc\": {\n                \"enter\": \"\",\n                \"death\": \"\",\n                \"hit\": \"\",\n                \"destroy\": \"\",\n                \"phraseTriggersAfterTemplate\": \"\",\n                \"layerClear\": \"\",\n                \"talk\": \"\",\n                \"phraseTriggersWhenTemplate\": \"\",\n                \"activate\": \"\"\n            },\n            \"label\": {\n                \"hit\": \"\",\n                \"talk\": \"\",\n                \"next\": \"\",\n                \"enter\": \"\",\n                \"layerClear\": \"\",\n                \"destroy\": \"\",\n                \"death\": \"\",\n                \"activate\": \"\"\n            },\n            \"letter\": {\n                \"activate\": \"\",\n                \"death\": \"\",\n                \"destroy\": \"\",\n                \"talk\": \"\",\n                \"enter\": \"\",\n                \"layerClear\": \"\",\n                \"hit\": \"\"\n            },\n            \"promptEventText\": \"\",\n            \"sections\": {\n                \"actionKeep\": \"\",\n                \"propMusic\": \"\",\n                \"actionSet\": \"\",\n                \"actionReset\": \"\",\n                \"propBackground\": \"\",\n                \"phraseAllSections\": \"\",\n                \"propBounds\": \"\"\n            },\n            \"props\": {\n                \"sound\": \"\",\n                \"controls\": \"\",\n                \"endGame\": \"\",\n                \"layerSmoke\": \"\",\n                \"autostart\": \"\"\n            },\n            \"bounds\": {\n                \"shouldEvent\": \"\",\n                \"changeSectionBoundsToCurrent\": \"\",\n                \"changeAllSectionBoundsToCurrent\": \"\"\n            },\n            \"header\": \"\",\n            \"headerTriggerEvent\": \"\",\n            \"layers\": {\n                \"headerShow\": \"\",\n                \"headerToggle\": \"\",\n                \"headerMove\": \"\",\n                \"headerHide\": \"\"\n            },\n            \"promptEventName\": \"\",\n            \"controlsForEventN\": \"\",\n            \"itemNewEvent\": \"\",\n            \"settingsForEvent\": \"\"\n        },\n        \"letterLeft\": \"\",\n        \"letterDown\": \"\",\n        \"mapPos\": \"\",\n        \"phraseGenericIndex\": \"\",\n        \"world\": {\n            \"retryOnFail\": \"\",\n            \"totalStars\": \"\",\n            \"introLevel\": \"\",\n            \"allowChars\": \"\",\n            \"hubWorld\": \"\",\n            \"phraseCreditIndex\": \"\",\n            \"name\": \"\"\n        },\n        \"section\": {\n            \"vertWrap\": \"\",\n            \"scroll\": \"\",\n            \"offscreenExit\": \"\",\n            \"noTurnBack\": \"\",\n            \"horizWrap\": \"\",\n            \"setBounds\": \"\",\n            \"underwater\": \"\"\n        },\n        \"toggleMagicBlock\": \"\",\n        \"wordCoins\": \"\",\n        \"wordEnabled\": \"\",\n        \"wordNPC\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\"\n        },\n        \"wordEvent\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\",\n            \"typeLabel\": \"\"\n        },\n        \"labelSortLayer\": \"\",\n        \"labelSortOffset\": \"\",\n        \"letterUp\": \"\",\n        \"letterRight\": \"\",\n        \"letterCoordY\": \"\",\n        \"pageBlankOfBlank\": \"\",\n        \"phraseCountMore\": \"\",\n        \"phraseRadiusIndex\": \"\",\n        \"phraseAreYouSure\": \"\",\n        \"phraseSectionIndex\": \"\",\n        \"phraseTextOf\": \"\",\n        \"phraseWarpIndex\": \"\",\n        \"wordInstant\": \"\",\n        \"wordHeight\": \"\",\n        \"wordMode\": \"\",\n        \"block\": {\n            \"letterWidth\": \"\",\n            \"slick\": \"\",\n            \"pickContents\": \"\",\n            \"canBreak\": \"\",\n            \"canBreakTooltip\": \"\",\n            \"inside\": \"\",\n            \"letterHeight\": \"\",\n            \"invis\": \"\"\n        },\n        \"browser\": {\n            \"itemNewFolder\": \"\",\n            \"newFile\": \"\",\n            \"openFile\": \"\",\n            \"saveFile\": \"\",\n            \"itemNewFile\": \"\",\n            \"askOverwriteFile\": \"\"\n        },\n        \"water\": {\n            \"title\": \"\"\n        },\n        \"phraseDelayIsMs\": \"\"\n    },\n    \"game\": {\n        \"format\": {\n            \"minutesSeconds\": \"\"\n        },\n        \"pause\": {\n            \"returnToEditor\": \"\",\n            \"continue\": \"\",\n            \"enterCode\": \"\",\n            \"restartLevel\": \"\",\n            \"quit\": \"\",\n            \"resetCheckpoints\": \"\",\n            \"saveAndContinue\": \"\",\n            \"playerSetup\": \"\",\n            \"quitTesting\": \"\",\n            \"saveAndQuit\": \"\"\n        },\n        \"connect\": {\n            \"phraseChangeChar\": \"\",\n            \"phraseHoldStart\": \"\",\n            \"phraseWaitingForInput\": \"\",\n            \"phraseStartToResume\": \"\",\n            \"phraseSetControls\": \"\",\n            \"phraseStartToForceRes\": \"\",\n            \"reconnectTitle\": \"\",\n            \"phrasePressAButton\": \"\",\n            \"wordDisconnect\": \"\",\n            \"splitPressSelect_1\": \"\",\n            \"splitPressSelect_2\": \"\",\n            \"dropAddTitle\": \"\",\n            \"phraseDropMe\": \"\",\n            \"phraseDropPX\": \"\",\n            \"phraseForceResume\": \"\",\n            \"phraseTestProfile\": \"\",\n            \"phraseTestControls\": \"\"\n        },\n        \"error\": {\n            \"warpNeedStarCount\": \"\",\n            \"errorInvalidEnterWarp\": \"\",\n            \"openFileFailed\": \"\",\n            \"errorNoStartPoint\": \"\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"hint\": {\n            \"no-lives-new\": \"\",\n            \"no-lives-old\": \"\",\n            \"char5-bombs\": \"\",\n            \"gray-bricks\": \"\",\n            \"shoe-block\": \"\",\n            \"rainbow-surf\": \"\",\n            \"heavy-duck\": \"\",\n            \"pound-altrun\": \"\",\n            \"pound-down\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"\"\n        },\n        \"msgbox\": {\n            \"sysInfoWarning\": \"\",\n            \"sysInfoTitle\": \"\",\n            \"sysInfoError\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"outro\": {\n        \"qualityControl\": \"\",\n        \"specialThanks\": \"\",\n        \"levelDesign\": \"\",\n        \"nameVitalyNovichkov\": \"\",\n        \"customSprites\": \"\",\n        \"nameAndrewSpinks\": \"\",\n        \"originalBy\": \"\",\n        \"engineCredits\": \"\",\n        \"gameCredits\": \"\",\n        \"cppPortDevelopers\": \"\",\n        \"psVitaPortBy\": \"\"\n    },\n    \"pluralRules\": \"\",\n    \"languageName\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_pt-br.json",
    "content": "{\n    \"editor\": {\n        \"file\": {\n            \"convert\": {\n                \"descNew\": \"A extensão será alterada, mas o arquivo NÃO será deletado.\\n\\nPor favor, confira os recursos que serão perdidos antes.\"\n            },\n            \"formatLegacy\": \"Antigo (SMBX-64)\",\n            \"formatModern\": \"Moderno (PGE-X)\",\n            \"labelCurrentFile\": \"Arquivo atual: \\\\\",\n            \"labelFormat\": \"Formato:\",\n            \"optionActionWithoutSave\": \"{0} sem guardar\",\n            \"optionCancelAction\": \"Cancelar: não {0}\",\n            \"optionCancelConversion\": \"Cancelar conversão\",\n            \"optionProceedWithConversion\": \"Confirmar conversão\",\n            \"optionYesSaveThenAction\": \"SIM: Guardar, depois {0}\",\n            \"sectionLevel\": \"Nível\",\n            \"actionOpen\": \"Abrir\",\n            \"confirmConfirmAction\": \"Certeza que quer {0}?\",\n            \"confirmConvertFormatTo\": \"Converter para {0}?\",\n            \"confirmSaveBeforeAction\": \"Guardar antes de {0}?\",\n            \"sectionWorld\": \"Mundo\",\n            \"actionClearLevel\": \"Limpar Nível\",\n            \"actionClearWorld\": \"Limpar Mundo\",\n            \"actionExit\": \"Sair\",\n            \"commandNew\": \"Novo\",\n            \"commandOpen\": \"Abrir...\",\n            \"commandSave\": \"Guardar\",\n            \"commandSaveAs\": \"Guardar como...\",\n            \"actionRevert\": \"Redefinir\"\n        },\n        \"labelSortLayer\": \"Organizar Camadas:\",\n        \"layers\": {\n            \"abbrevAttLayer\": \"Anexa: \",\n            \"default\": \"Padrão\",\n            \"deletion\": {\n                \"cancel\": \"Cancelar: Não apagar camada\",\n                \"confirmDelete\": \"NÃO: *APAGA TODO O CONTEÚDO*\",\n                \"preserveLayerContents\": \"Preservar o conteúdo da camada?\",\n                \"confirmPreserve\": \"SIM: Mover para a camada padrão\",\n                \"header\": \"Apagando camada {0}\"\n            },\n            \"header\": \"Camadas:\",\n            \"itemNewLayer\": \"<Nova Camada>\",\n            \"label\": \"Camada:\",\n            \"labelAttached\": \"Anexado:\",\n            \"labelAttachedLayer\": \"Camada Anexada:\",\n            \"labelMoveLayer\": \"Mover Camada:\",\n            \"promptLayerName\": \"Nome da camada\",\n            \"desc\": {\n                \"att\": \"Quando o NPC se mover, a camada anexada o seguira\"\n            }\n        },\n        \"letterCoordY\": \"Y\",\n        \"letterDown\": \"B\",\n        \"letterLeft\": \"E\",\n        \"letterUp\": \"C\",\n        \"level\": {\n            \"alwaysVis\": \"Sempre Vis\",\n            \"bigBG\": \"Cam.Grande\",\n            \"gameStart\": \"Iniciar Jogo\",\n            \"levelName\": \"Nome do Nível\",\n            \"pathBG\": \"Caminho\",\n            \"pathUnlocks\": \"Caminhos Desbloq.\",\n            \"startPos\": \"Pos. Inicial\"\n        },\n        \"mapPos\": \"Pos. Mapa:\",\n        \"npc\": {\n            \"inertNice\": \"Amig\",\n            \"props\": {\n                \"active\": \"Ativo\",\n                \"attachSurface\": \"Anexa\",\n                \"facing\": \"Olhando\"\n            },\n            \"inContainer\": \"-\",\n            \"stuckStop\": \"Fica\",\n            \"ai\": {\n                \"LR\": \"Esq-Dir\",\n                \"UD\": \"Cim-Bai\",\n                \"aiIs\": \"IA: {0}\",\n                \"headerCustomAi\": \"IA Person.:\",\n                \"leap\": \"Saltar\",\n                \"swim\": \"Nadar\",\n                \"target\": \"Perseguir\",\n                \"jump\": \"Pular\",\n                \"use1_0Ai\": \"Usar IA 1.0?\"\n            },\n            \"gen\": {\n                \"effectWarp\": \"Telep\",\n                \"effectIs\": \"Efeito: {0}\",\n                \"effectShoot\": \"Atirar\",\n                \"header\": \"Config. de Gerador\",\n                \"direction\": \"Direção\"\n            },\n            \"abbrevGen\": \"Gera.\",\n            \"tooltipExpandSection\": \"Expandir Seção\"\n        },\n        \"phraseAreYouSure\": \"Tem certeza?\",\n        \"phraseCountMore\": \"Mais {0}\",\n        \"phraseDelayIsMs\": \"Interv.: {0} ms\",\n        \"phraseRadiusIndex\": \"Raio {0}\",\n        \"phraseSectionIndex\": \"Seção {0}\",\n        \"section\": {\n            \"horizWrap\": \"Ligar Esq-Dir\",\n            \"noTurnBack\": \"Sem Volta\",\n            \"offscreenExit\": \"Sair Pela Borda\",\n            \"scroll\": \"Deslizar\",\n            \"setBounds\": \"Def. Bordas\",\n            \"underwater\": \"Subaquático\",\n            \"vertWrap\": \"TP. Vertical\"\n        },\n        \"select\": {\n            \"pathBlankUnlock\": \"Caminho {0} Desbloqueia Por\",\n            \"sectionBlankPropBlank\": \"Seção {0}{1}\",\n            \"allSectPropBlankForEventN\": \"Todas Seções {0} para {1}\",\n            \"sectBlankPropBlankForEventN\": \"Seção {0} {1} para {2}\",\n            \"soundForEventN\": \"Som para Evento {0}\",\n            \"warpTransitEffect\": \"Efeito de Transição do Teleporte\",\n            \"worldMusic\": \"Música do Mundo\"\n        },\n        \"testPlay\": {\n            \"boot\": \"Bota\",\n            \"char\": \"Pers\",\n            \"magicHand\": \"Mão Mágica\",\n            \"pet\": \"Mont.\",\n            \"power\": \"Poder\"\n        },\n        \"tooltip\": {\n            \"area\": \"Área\",\n            \"erase\": \"Apagar\",\n            \"NPCs\": \"NPCs\",\n            \"music\": \"Música\",\n            \"scenes\": \"Cenário\",\n            \"BGOs\": \"Obj. de Fundo\",\n            \"eraseAll\": \"Apagar Tudo\",\n            \"blocks\": \"Blocos\",\n            \"file\": \"Arquivo\",\n            \"layers\": \"Camadas\",\n            \"levels\": \"Níveis\",\n            \"paths\": \"Caminhos\",\n            \"select\": \"Selecionar\",\n            \"show\": \"Mostrar\",\n            \"settings\": \"Configurações\",\n            \"tiles\": \"Peças\",\n            \"warps\": \"Teleportes\",\n            \"water\": \"Água\",\n            \"events\": \"Eventos\"\n        },\n        \"warp\": {\n            \"effect\": \"Efeito:\",\n            \"in\": \"Ent.\",\n            \"out\": \"Saí.\",\n            \"placing\": \"Colocando:\",\n            \"ride\": \"Montar\",\n            \"speed\": \"Velocidade \",\n            \"style\": {\n                \"blipInstant\": \"Inst.\",\n                \"door\": \"Porta\",\n                \"portal\": \"Port\",\n                \"style\": \"Estilo: \",\n                \"pipe\": \"Cano\"\n            },\n            \"to\": \"A: {0}\",\n            \"toMap\": \"Para Mapa\",\n            \"transit\": {\n                \"name1\": \"DESLIZAR\",\n                \"name4\": \"VIRAR (H)\",\n                \"name0\": \"NENHUM\",\n                \"name2\": \"TRANSIÇÃO\",\n                \"name3\": \"CÍRCULO\",\n                \"name5\": \"VIRAR (V)\"\n            },\n            \"twoWay\": \"Ida-Volta\",\n            \"cannonExit\": \"Saí. Canhão\",\n            \"allow\": \"Permitir:\",\n            \"dir\": \"Dire.\",\n            \"item\": \"Item\",\n            \"needStarCount\": \"Precisa de {0} {1}\",\n            \"lvlWarp\": \"Tele.Nív.\",\n            \"needFloor\": \"Precisa de Chão\",\n            \"needKey\": \"Precisa de Chave\",\n            \"showStarCount\": \"Mostrar Estrelas\",\n            \"showStartScene\": \"Mostrar Cena Inicial\",\n            \"starLockMessage\": \"Mens. de Bloq. de Estrela\",\n            \"target\": \"Abre: \",\n            \"title\": \"Config. de Teleporte\"\n        },\n        \"water\": {\n            \"title\": \"Config. de Água\"\n        },\n        \"wordEvent\": {\n            \"typeLabel\": \"Evento {0}:\",\n            \"nominative\": \"Evento\",\n            \"genitive\": \"Evento\"\n        },\n        \"wordInstant\": \"Instantâneo\",\n        \"wordNPC\": {\n            \"nominative\": \"NPC\",\n            \"genitive\": \"NPC\"\n        },\n        \"wordText\": \"Texto\",\n        \"wordWidth\": \"Largura\",\n        \"world\": {\n            \"hubWorld\": \"Mundo Central\",\n            \"introLevel\": \"Nível Inicial\",\n            \"name\": \"Nome do Mundo\",\n            \"allowChars\": \"Jogáveis\",\n            \"phraseCreditIndex\": \"Linha {0} - Créditos do Mundo:\",\n            \"totalStars\": \"Núm.Estrelas:\",\n            \"retryOnFail\": \"AutoRessurgir\"\n        },\n        \"block\": {\n            \"canBreak\": \"Quebrável\",\n            \"pickContents\": \"Escolha o conteúdo do bloco\",\n            \"canBreakTooltip\": \"Antigo \\\"Quebrar ao bater\\\"\",\n            \"inside\": \"Contém:\",\n            \"letterHeight\": \"A-\",\n            \"letterWidth\": \"L-\",\n            \"slick\": \"Escorr.\",\n            \"invis\": \"Inv.\"\n        },\n        \"events\": {\n            \"desc\": {\n                \"hit\": \"bater no bloco\",\n                \"layerClear\": \"tudo na camada de objetos estiver limpa\",\n                \"phraseTriggersAfterTemplate\": \"{0} ativa {1} ms depois do evento {2}\",\n                \"phraseTriggersWhenTemplate\": \"{0} ativa quando {1}\",\n                \"talk\": \"o jogador fala com o NPC\",\n                \"death\": \"o NPC morre\",\n                \"destroy\": \"o bloco é destruído\",\n                \"enter\": \"entrar no teleporte\",\n                \"activate\": \"o NPC entrar na tela\"\n            },\n            \"deletion\": {\n                \"cancel\": \"NÃO: Não apagar o evento\",\n                \"confirm\": \"SIM: Apagar o evento\",\n                \"deletingEvent\": \"Apagando o evento {0}\"\n            },\n            \"header\": \"Evento:\",\n            \"headerTriggerEvent\": \"Ativação:\",\n            \"itemNewEvent\": \"<Novo Evento>\",\n            \"label\": {\n                \"activate\": \"Ativar\",\n                \"death\": \"Morte\",\n                \"layerClear\": \"Camada Limpa\",\n                \"destroy\": \"Destruir\",\n                \"enter\": \"Entrar\",\n                \"hit\": \"Bater\",\n                \"next\": \"Próximo\",\n                \"talk\": \"Falar\"\n            },\n            \"layers\": {\n                \"headerMove\": \"Mover:\",\n                \"headerHide\": \"Esconder:\",\n                \"headerShow\": \"Mostrar:\",\n                \"headerToggle\": \"Alternar:\"\n            },\n            \"letter\": {\n                \"enter\": \"E:\",\n                \"layerClear\": \"L:\",\n                \"activate\": \"A:\",\n                \"death\": \"M:\",\n                \"destroy\": \"D:\",\n                \"hit\": \"B:\",\n                \"talk\": \"F:\"\n            },\n            \"props\": {\n                \"sound\": \"Som\",\n                \"autostart\": \"Ao iniciar\",\n                \"controls\": \"Control.\",\n                \"endGame\": \"Fin.Jogo\",\n                \"layerSmoke\": \"Fumaça\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"Manter\",\n                \"actionReset\": \"Rein.\",\n                \"actionSet\": \"Def.\",\n                \"phraseAllSections\": \"Todas as Seções\",\n                \"propBackground\": \"Fundo\",\n                \"propBounds\": \"Bordas\",\n                \"propMusic\": \"Música\"\n            },\n            \"promptEventName\": \"Nome do evento\",\n            \"promptEventText\": \"Texto do evento\",\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"Mudar todos os limites de borda da seção atual?\",\n                \"changeSectionBoundsToCurrent\": \"Mudar os limites de borda da seção {0} aos valores atuais?\",\n                \"shouldEvent\": \"Editar Evento {0}\"\n            },\n            \"controlsForEventN\": \"Controles para o evento {0}\",\n            \"settingsForEvent\": \"Config. do evento {0}\"\n        },\n        \"browser\": {\n            \"itemNewFile\": \"<Novo Arquivo>\",\n            \"askOverwriteFile\": \"Sobrescrever {0}?\",\n            \"itemNewFolder\": \"<Nova Pasta>\",\n            \"openFile\": \"Abrir arquivo\",\n            \"newFile\": \"Criar arquivo\",\n            \"saveFile\": \"Guardar arquivo\"\n        },\n        \"labelSortOffset\": \"Organizar Deslocamento (Offset):\",\n        \"letterCoordX\": \"X\",\n        \"letterRight\": \"D\",\n        \"pageBlankOfBlank\": \"Pagina {0} de {1}\",\n        \"phraseGenericIndex\": \"Número {0}\",\n        \"phraseTextOf\": \"Texto - {0}\",\n        \"phraseWarpIndex\": \"Teleporte {0}\",\n        \"toggleMagicBlock\": \"Bloco Mágico\",\n        \"wordEnabled\": \"Ativado\",\n        \"wordCoins\": \"Moedas\",\n        \"wordHeight\": \"Altura\",\n        \"wordMode\": \"Modo\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"phraseHoldStart\": \"Pressione Start\",\n            \"splitPressSelect_2\": \"Opções de Controle\",\n            \"dropAddTitle\": \"Ger. Jogadores\",\n            \"phraseChangeChar\": \"Mudar Personagem\",\n            \"phraseDropMe\": \"Retirar\",\n            \"phraseDropPX\": \"Tirar J{0}\",\n            \"phraseForceResume\": \"Forçar Continuação\",\n            \"phrasePressAButton\": \"Pressione A\",\n            \"phraseSetControls\": \"Definir Controles\",\n            \"phraseStartToForceRes\": \"Pressione Start para Forçar Continuar\",\n            \"phraseStartToResume\": \"Pressione Start para Continuar\",\n            \"phraseTestControls\": \"Testar Controles\",\n            \"phraseTestProfile\": \"Testar Perfil\",\n            \"phraseWaitingForInput\": \"Esperando por Controles...\",\n            \"reconnectTitle\": \"Reconectar\",\n            \"splitPressSelect_1\": \"Pressione Select para\",\n            \"wordDisconnect\": \"Desconectar\"\n        },\n        \"error\": {\n            \"errorInvalidEnterWarp\": \"Não foi possível iniciar o nível devido a um teleporte de entrada invalido em {1}.\\nTotal de entradas no teleporte: {2}\\n\\nArquivo: {0}\",\n            \"errorNoStartPoint\": \"Não foi possível iniciar o nível por este nível não conter ponto de inicio ou entradas de teleporte especificadas.\\n\\nArquivo: {0}\",\n            \"openFileFailed\": \"\\\"{0}\\\" não foi aberto: Arquivo inexistente ou corrompido.\",\n            \"warpNeedStarCount\": \"Você precisa de\\n{0} {1} para entrar.\",\n            \"errorTooOldGameAssets\": \"O conteúdo requer um pacote de recursos de fase mais recente ({0}) do que o pacote de recursos atual ({1}). Atualize seu pacote de recursos do jogo.\",\n            \"IPCTimeOut\": \"Sem Resposta do editor conectado o jogo será fechado\",\n            \"errorTooOldEngine\": \"O conteúdo requer um nível de recurso de mecanismo mais recente ({0}) do que a versão atual ({1}). Atualize o TheXTech.\",\n            \"openIPCDataFailed\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"Buscando níveis...\"\n        },\n        \"pause\": {\n            \"playerSetup\": \"Configurar Jogadores\",\n            \"enterCode\": \"Comandos\",\n            \"returnToEditor\": \"Retornar ao Editor\",\n            \"saveAndContinue\": \"Guardar e Continuar\",\n            \"saveAndQuit\": \"Guardar e Sair\",\n            \"quit\": \"Sair\",\n            \"quitTesting\": \"Sair do Teste\",\n            \"resetCheckpoints\": \"Reiniciar Ponto de Ressurgimento\",\n            \"continue\": \"Continuar\",\n            \"restartLevel\": \"Reiniciar Nível\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0}m{1}s\"\n        },\n        \"hint\": {\n            \"no-lives-old\": \"Cuidado - Fim de Jogo iminente!\",\n            \"no-lives-new\": \"Sem moedas? Empréstimo! Olhe para sua pontuação...\",\n            \"char5-bombs\": \"Pressione Correr para segurar e Correr ALT para jogar.\",\n            \"gray-bricks\": \"Alguns blocos são vulneráveis a habilidades.\",\n            \"rainbow-surf\": \"Pegue, corra, abaixe, e então surfe.\",\n            \"shoe-block\": \"Vestindo isso ficará imune a - quase todos - fogos!\",\n            \"heavy-duck\": \"Abaixe para se proteger de - nem todos - fogos!\",\n            \"pound-altrun\": \"Use Correr ALT para Ground Pound!\",\n            \"pound-down\": \"Use Baixo para Ground Pound!\"\n        },\n        \"msgbox\": {\n            \"sysInfoError\": \"Erro!\",\n            \"sysInfoTitle\": \"Informação\",\n            \"sysInfoWarning\": \"Aviso!\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"menu\": {\n        \"controls\": {\n            \"touchscreen\": {\n                \"style\": {\n                    \"XODA\": \"𐄂○□△\",\n                    \"ABXY\": \"ABXY\",\n                    \"actions\": \"Ações\"\n                },\n                \"layout\": {\n                    \"tinyOld\": \"Pequeno (Antigo)\",\n                    \"longOld\": \"Longo (Antigo)\",\n                    \"phabletOld\": \"Phablet (Antigo)\",\n                    \"phoneOld\": \"Telefone (Antigo)\",\n                    \"tabletOld\": \"Tablet (Antigo)\",\n                    \"tight\": \"Apertado\",\n                    \"standard\": \"Def. Padrão\"\n                },\n                \"option\": {\n                    \"feedbackLength\": \"Duração da Vibração\",\n                    \"feedbackStrength\": \"Força da Vibração\",\n                    \"holdRun\": \"Segurar \\\"Correr\\\" ao Iniciar\",\n                    \"interfaceStyle\": \"Estilo da interface\",\n                    \"layoutStyle\": \"Estilo do Layout\",\n                    \"resetLayout\": \"Reiniciar Layout\",\n                    \"scaleButtons\": \"Escala dos Botões\",\n                    \"scaleDPad\": \"Escala do Direcional\",\n                    \"scaleFactor\": \"Fator de Escala\",\n                    \"showCodeButton\": \"Mostrar Botão de Comandos\",\n                    \"sStartSpacing\": \"Espaçamento S-Start\"\n                }\n            },\n            \"phraseNewProfOldJoy\": \"Novo Perfil para Joystick Antigo\",\n            \"wii\": {\n                \"typeNunchuck\": \"Nunchuck\",\n                \"wiimote\": {\n                    \"caseIR\": \"(Infravermelho)\",\n                    \"shake\": \"Sacudir\",\n                    \"button1\": \"1\",\n                    \"button2\": \"2\",\n                    \"buttonA\": \"A\",\n                    \"buttonB\": \"B\",\n                    \"buttonHome\": \"Bot. Home\",\n                    \"dPad\": \"Direcionais\",\n                    \"buttonMinus\": \"-\",\n                    \"buttonPlus\": \"+\"\n                },\n                \"classic\": {\n                    \"buttonLT\": \"LT\",\n                    \"buttonRT\": \"RT\",\n                    \"buttonX\": \"X\",\n                    \"buttonY\": \"Y\",\n                    \"buttonZL\": \"ZL\",\n                    \"buttonZR\": \"ZR\",\n                    \"lStick\": \"Direcional Esq\",\n                    \"rStick\": \"Direcional Dir\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"C\",\n                    \"buttonZ\": \"Z\",\n                    \"prefixN\": \"N\"\n                },\n                \"phraseNewClassic\": \"Novo Perfil - Classic\",\n                \"phraseNewNunchuck\": \"Novo Perfil - Nunchuck\",\n                \"typeClassic\": \"Classic\",\n                \"typeWiimote\": \"Wiimote\",\n                \"typeGamecube\": \"\"\n            },\n            \"hotkeys\": {\n                \"enterCheats\": \"Comandos\",\n                \"fullscreen\": \"Tela Cheia\",\n                \"debugInfo\": \"Depurar\",\n                \"legacyPause\": \"Pausa Alt\",\n                \"recordGif\": \"Gravar GIF\",\n                \"screenshot\": \"Capt. Tela\",\n                \"toggleHUD\": \"Interface\",\n                \"vanillaCam\": \"Câmera Vanilla\"\n            },\n            \"tDS\": {\n                \"buttonA\": \"A\",\n                \"buttonB\": \"B\",\n                \"buttonR\": \"R\",\n                \"buttonZL\": \"ZL\",\n                \"buttonZR\": \"ZR\",\n                \"casePen\": \"(Stylus)\",\n                \"buttonL\": \"L\",\n                \"buttonSelect\": \"Select\",\n                \"buttonStart\": \"Start\",\n                \"buttonX\": \"X\",\n                \"buttonY\": \"Y\",\n                \"cStick\": \"Analógico-C\",\n                \"dPad\": \"Direcionais\",\n                \"tStick\": \"Bot. do Analógico\"\n            },\n            \"cursor\": {\n                \"down\": \"Mouse Baixo\",\n                \"left\": \"Mouse Esq\",\n                \"right\": \"Mouse Dir\",\n                \"up\": \"Mouse Cima\",\n                \"primary\": \"Primário\",\n                \"secondary\": \"Secundário\",\n                \"tertiary\": \"Terciário\"\n            },\n            \"buttons\": {\n                \"altJump\": \"Pulo Alt\",\n                \"altRun\": \"Correr Alt\",\n                \"down\": \"Baixo\",\n                \"drop\": \"Cham.Item\",\n                \"jump\": \"Pular\",\n                \"left\": \"Esquerda\",\n                \"right\": \"Direita\",\n                \"start\": \"Iniciar\",\n                \"up\": \"Cima\",\n                \"run\": \"Correr\"\n            },\n            \"caseInvalid\": \"(Invalido)\",\n            \"caseMouse\": \"(Mouse)\",\n            \"caseTouch\": \"(Toque)\",\n            \"controlsConnected\": \"Conectado:\",\n            \"controlsDeviceTypes\": \"Tipos de Dispositivos\",\n            \"controlsInUse\": \"(Em Uso)\",\n            \"controlsNewProfile\": \"<Novo Perfil>\",\n            \"controlsNotInUse\": \"(Não Usado)\",\n            \"controlsReallyDeleteProfile\": \"Apagar perfil?\",\n            \"controlsTitle\": \"Controles\",\n            \"editor\": {\n                \"fastScroll\": \"Desl. Rápido\",\n                \"switchScreens\": \"Painel\",\n                \"nextSection\": \"Prox. Seção\",\n                \"scrollLeft\": \"Desl. Esq\",\n                \"scrollRight\": \"Desl. Dir\",\n                \"scrollUp\": \"Desl. Acima\",\n                \"testPlay\": \"Rodar Teste\",\n                \"modeErase\": \"M Apagar\",\n                \"modeSelect\": \"M Selec\",\n                \"prevSection\": \"Seção Anter\",\n                \"scrollDown\": \"Desl. Abaixo\"\n            },\n            \"options\": {\n                \"batteryStatus\": \"Estado da Bateria\",\n                \"maxPlayers\": \"Jogadores Máximos\",\n                \"rumble\": \"Vibrar\",\n                \"textEntryStyle\": \"Estilo de Entrada de Texto\"\n            },\n            \"profile\": {\n                \"hotkeys\": \"Atalhos\",\n                \"cursorControls\": \"Controles do cursor\",\n                \"deleteProfile\": \"Apagar Perfil\",\n                \"editorControls\": \"Controles do editor\",\n                \"playerControls\": \"Controles do jogador\",\n                \"renameProfile\": \"Renomear perfil\"\n            },\n            \"wordButtons\": \"Botões\",\n            \"wordProfiles\": \"Perfis\",\n            \"types\": {\n                \"touchscreen\": \"Tela de Toque\",\n                \"keyboard\": \"Teclado\",\n                \"gamepad\": \"Controle\",\n                \"oldJoystick\": \"Joystick Antigo\"\n            },\n            \"controlsDeleteKey\": \"(Pulo Alt para Apagar)\",\n            \"controlsDeleteKey2\": \"(Correr Alt para Apagar)\",\n            \"joystickSimpleEditor\": \"\"\n        },\n        \"game\": {\n            \"gameBattleRandom\": \"Nível Aleatório\",\n            \"gameCopySave\": \"Copiar Jogo\",\n            \"gameEraseSave\": \"Apagar Jogo\",\n            \"gameEraseSlot\": \"Selecione um Jogo para apagar\",\n            \"gameSlotNew\": \"JOGO {0} ... NOVO JOGO\",\n            \"gameNoBattleLevels\": \"<Sem níveis de batalha>\",\n            \"gameNoEpisodesToPlay\": \"<Sem episódios para jogar>\",\n            \"gameSourceSlot\": \"Selecione o Jogo de origem\",\n            \"gameTargetSlot\": \"Agora, o Jogo de destino\",\n            \"phraseScore\": \"Pontos: {0}\",\n            \"phraseTime\": \"Tempo: {0}\",\n            \"gameSlotContinue\": \"JOGO {0} ... {1}%\",\n            \"warnEpCompat\": \"Este episódio foi feito para uma versão diferente de SMBX e pode não funcionar como esperado.\"\n        },\n        \"main\": {\n            \"mainPlayEpisode\": \"Jogar Episódio\",\n            \"main1PlayerGame\": \"Um Jogador\",\n            \"mainEditor\": \"Editor\",\n            \"mainOptions\": \"Opções\",\n            \"mainBattleGame\": \"Modo Batalha\",\n            \"mainExit\": \"Sair\",\n            \"mainMultiplayerGame\": \"Dois Jogadores\"\n        },\n        \"options\": {\n            \"restartEngine\": \"Reinicie a Engine para aplicar as mudanças.\",\n            \"advanced\": {\n                \"audio-enable\": \"Ativar\",\n                \"audio-format\": {\n                    \"_tooltip\": \"Formato para driver de som\",\n                    \"_name\": \"Formato de áudio\"\n                },\n                \"audio-sample-rate\": {\n                    \"11025\": \"11025 Hz\",\n                    \"_name\": \"Taxa de amostragem\",\n                    \"22050\": \"22050 Hz\",\n                    \"16000\": \"16000 Hz\",\n                    \"32000\": \"32000 Hz\",\n                    \"44100\": \"44100 Hz\",\n                    \"48000\": \"48000 Hz\"\n                },\n                \"render\": {\n                    \"opengl11\": \"OpenGL 1.1\",\n                    \"hw\": \"Automático\",\n                    \"opengl\": \"OpenGL 3+\",\n                    \"opengles11-tip\": \"\",\n                    \"_name\": \"Modo de renderização\",\n                    \"opengl-tip\": \"\",\n                    \"opengles-tip\": \"\",\n                    \"opengles\": \"OpenGL ES 2+\",\n                    \"opengl11-tip\": \"\",\n                    \"sdl\": \"SDL2\",\n                    \"sdl-tip\": \"\",\n                    \"sw\": \"Software\",\n                    \"opengles11\": \"OpenGL ES 1.1+\"\n                },\n                \"webm-recording\": \"\",\n                \"internal-res-4p\": {\n                    \"qhd\": \"2560x1440 (QHD)\",\n                    \"fhd\": \"1920x1080 (FHD)\",\n                    \"default\": \"Padrão\",\n                    \"qsmbx\": \"1600x1200 (QSMBX)\",\n                    \"_name\": \"\"\n                },\n                \"_tooltip\": \"Opções técnicas para operações internas\",\n                \"_header\": \"Avançado\",\n                \"advanced-video\": \"Vídeo\",\n                \"advanced-audio\": \"Áudio\",\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"Aumente para menos engasgos a custo de desempenho\",\n                    \"_name\": \"Buffer\"\n                },\n                \"audio-channels\": {\n                    \"stereo\": \"Estéreo\",\n                    \"mono\": \"Mono\",\n                    \"_name\": \"Canais\"\n                },\n                \"log-level\": {\n                    \"_name\": \"Nível de depuração (log)\",\n                    \"debug\": \"Depuração\",\n                    \"critical\": \"Crítico\",\n                    \"fatal\": \"Fatal\",\n                    \"info\": \"Informação\",\n                    \"warning\": \"Aviso\",\n                    \"none\": \"Nada\"\n                },\n                \"choose-assets-on-launch\": \"Escolher assets ao iniciar\",\n                \"record-gameplay-data\": \"Gravar jogatina\",\n                \"scale-down-textures\": {\n                    \"_tooltip\": \"Armazena imagens como 1x para economizar memória\",\n                    \"_name\": \"Reduzir escala das imagens\",\n                    \"safe-tip\": \"Verifica se as imagens estão no formato 2x\",\n                    \"safe\": \"Seguro\",\n                    \"none-tip\": \"Menos travamentos\",\n                    \"none\": \"Nenhum\",\n                    \"all-tip\": \"Menos travamentos no carregamento do que 'Seguro'\",\n                    \"all\": \"Todas\"\n                },\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"main\": {\n                \"_header\": \"Principal\",\n                \"background-work\": \"\",\n                \"four-screen-mode\": {\n                    \"_name\": \"\",\n                    \"shared\": \"\",\n                    \"split\": \"\"\n                },\n                \"discord-rpc\": \"Integração com Discord\",\n                \"multiplayer\": \"\",\n                \"frame-skip\": \"\",\n                \"language\": \"Idioma\",\n                \"vsync\": \"\",\n                \"timing\": \"\",\n                \"two-screen-mode\": {\n                    \"split\": \"\",\n                    \"smbx\": \"\",\n                    \"topbottom\": \"\",\n                    \"_name\": \"\",\n                    \"shared\": \"\"\n                },\n                \"unlimited-framerate\": \"\",\n                \"background-work-tip\": \"\"\n            },\n            \"video\": {\n                \"internal-res\": {\n                    \"smbx-wide\": \"\",\n                    \"_name\": \"Tamanho da tela\",\n                    \"hello\": \"768x432 (HELLO)\",\n                    \"nds\": \"512x384 (NDS)\",\n                    \"_tooltip\": \"\",\n                    \"dynamic\": \"Dinâmico\",\n                    \"hd\": \"1280x720 (HD)\",\n                    \"gba\": \"480x320 (GBA)\",\n                    \"smbx\": \"800x600 (SMBX)\",\n                    \"snes\": \"512x448 (SNES)\",\n                    \"vga\": \"640x480 (VGA)\",\n                    \"3ds\": \"800x480 (3DS)\"\n                },\n                \"show-episode-title\": {\n                    \"bottom\": \"\",\n                    \"bottom-tip\": \"\",\n                    \"off\": \"\",\n                    \"_name\": \"\",\n                    \"top\": \"\",\n                    \"top-tip\": \"\"\n                },\n                \"scale-mode\": {\n                    \"integer\": \"\",\n                    \"1x\": \"1x\",\n                    \"center\": \"\",\n                    \"0_5x\": \"0,5x\",\n                    \"nearest\": \"\",\n                    \"full\": \"\",\n                    \"linear\": \"\",\n                    \"2x\": \"2x\",\n                    \"_name\": \"\",\n                    \"3x\": \"\"\n                },\n                \"show-fails-counter-tip\": \"\",\n                \"enable-inter-level-fade-effect\": \"\",\n                \"show-fails-counter\": \"\",\n                \"battery-status\": {\n                    \"off\": \"Desligado\",\n                    \"on\": \"Sempre\",\n                    \"low-tip\": \"\",\n                    \"_name\": \"\",\n                    \"fullscreen\": \"\",\n                    \"fullscreen-tip\": \"\",\n                    \"low\": \"\"\n                },\n                \"show-playtime-counter\": {\n                    \"opaque\": \"\",\n                    \"transparent\": \"\",\n                    \"_name\": \"\",\n                    \"animated\": \"\",\n                    \"animated-tip\": \"\",\n                    \"off\": \"\",\n                    \"off-tip\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"effects\": \"\",\n                \"display-controllers\": \"\",\n                \"info-game\": \"\",\n                \"info-run\": \"\",\n                \"info-meta\": \"\",\n                \"fullscreen\": \"\",\n                \"show-fps\": \"\",\n                \"video-system\": \"\",\n                \"show-screen-shake\": \"\",\n                \"world-map-stars-show\": \"\",\n                \"_header\": \"Vídeo\",\n                \"show-medals-counter-tip\": \"\",\n                \"3d-compat-mode-tip\": \"\",\n                \"world-map-stars-show-tip\": \"\",\n                \"show-medals-counter\": \"\",\n                \"3d-compat-mode\": \"\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"0\": \"0\",\n                    \"_name\": \"\",\n                    \"2\": \"2\",\n                    \"3\": \"3\",\n                    \"1\": \"1\"\n                }\n            },\n            \"audio\": {\n                \"sfx-audio-fx\": \"Efeitos de eco\",\n                \"sfx-spatial-audio\": \"Som espacial\",\n                \"sfx-modern\": \"Efeitos modernos\",\n                \"sfx-modern-tip\": \"\",\n                \"sfx-pet-beat\": \"\",\n                \"audio-sfx-volume\": \"Volume dos efeitos\",\n                \"_header\": \"Áudio\",\n                \"audio-prefs\": \"Referências\",\n                \"audio-music-volume\": \"Volume da música\",\n                \"sfx-spatial-audio-tip\": \"Reduz o volume de sons fora da tela\",\n                \"sfx-pet-beat-tip\": \"\"\n            },\n            \"episode-options\": {\n                \"creator-compat\": {\n                    \"_name\": \"\",\n                    \"file-only\": \"\",\n                    \"enable\": \"\",\n                    \"disable\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"_header\": \"\",\n                \"playstyle\": {\n                    \"modern\": \"\",\n                    \"_name\": \"\",\n                    \"classic-tip\": \"\",\n                    \"classic\": \"\",\n                    \"vanilla-tip\": \"\",\n                    \"vanilla\": \"\",\n                    \"modern-tip\": \"\"\n                },\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"compatibility\": {\n                \"reset-all\": \"\",\n                \"autocode\": \"\",\n                \"bugfixes\": \"\",\n                \"_tooltip\": \"\",\n                \"_header\": \"\",\n                \"features\": \"\"\n            },\n            \"controls\": {\n                \"_header\": \"Controles\"\n            },\n            \"view-credits\": {\n                \"_header\": \"Ver créditos\"\n            }\n        },\n        \"editor\": {\n            \"errorMissingResources\": \"Desculpe! {0} esta faltando, é necessário para o editor no jogo.\",\n            \"battles\": \"<Níveis de Batalha>\",\n            \"newWorld\": \"<Novo Mundo>\",\n            \"promptNewWorldName\": \"Nome do novo mundo\",\n            \"makeFor\": \"Fazer para:\"\n        },\n        \"wordBack\": \"Voltar\",\n        \"wordWaiting\": \"Esperando\",\n        \"loading\": \"Carregando...\",\n        \"abbrevMilliseconds\": \"MS\",\n        \"battle\": {\n            \"errorNoLevels\": \"Não foi possível iniciar uma batalha por falta de níveis\"\n        },\n        \"caseNone\": \"<Nenhum>\",\n        \"character\": {\n            \"selectCharacter\": \"Jogo do {0}\"\n        },\n        \"wordHide\": \"Esconder\",\n        \"wordNo\": \"Não\",\n        \"wordOff\": \"Desl.\",\n        \"wordOn\": \"Ligado\",\n        \"wordPlayer\": \"Jogador\",\n        \"wordProfile\": \"Perfil\",\n        \"wordResume\": \"Continuar\",\n        \"wordShow\": \"Mostrar\",\n        \"wordYes\": \"Sim\"\n    },\n    \"languageName\": \"Português-BR\",\n    \"outro\": {\n        \"specialThanks\": \"Agradecimentos Especiais:\",\n        \"cppPortDevelopers\": \"Desenvolvedores da versão C++:\",\n        \"customSprites\": \"Sprite Personalizado:\",\n        \"engineCredits\": \"Créditos da Engine:\",\n        \"gameCredits\": \"Créditos do jogo:\",\n        \"levelDesign\": \"Design de níveis:\",\n        \"nameAndrewSpinks\": \"Andrew Spinks\",\n        \"nameVitalyNovichkov\": \"Novichkov Vitaliy\",\n        \"originalBy\": \"Código original VB6 Por:\",\n        \"psVitaPortBy\": \"Port para PS Vita Por:\",\n        \"qualityControl\": \"Controle de Qualidade:\"\n    },\n    \"pluralRules\": \"one-is-singular\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_pt.json",
    "content": "{\n    \"editor\": {\n        \"block\": {\n            \"pickContents\": \"Escolha o conteúdo do bloco\",\n            \"canBreak\": \"Quebrável\",\n            \"canBreakTooltip\": \"Antigo \\\"Quebra ao bater\\\"\",\n            \"inside\": \"Contém:\",\n            \"invis\": \"Inv.\",\n            \"letterHeight\": \"A\",\n            \"letterWidth\": \"L\",\n            \"slick\": \"Escorr.\"\n        },\n        \"browser\": {\n            \"askOverwriteFile\": \"Sobrescrever {0}?\",\n            \"itemNewFile\": \"<Novo Ficheiro>\",\n            \"itemNewFolder\": \"<Nova Pasta>\",\n            \"newFile\": \"Criar ficheiro\",\n            \"openFile\": \"Abrir ficheiro\",\n            \"saveFile\": \"Guardar ficheiro\"\n        },\n        \"events\": {\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"Mudar todos os limites de borda da secção atual?\",\n                \"changeSectionBoundsToCurrent\": \"Mudar os limites de borda da secção {0} aos valores atuais?\",\n                \"shouldEvent\": \"Editar Evento {0}\"\n            },\n            \"controlsForEventN\": \"Controles para o evento {0}\",\n            \"deletion\": {\n                \"cancel\": \"NÃO: Não apagar o evento\",\n                \"confirm\": \"SIM: Apagar o evento\",\n                \"deletingEvent\": \"A apagar o evento {0}\"\n            },\n            \"desc\": {\n                \"activate\": \"o NPC entrar no ecrã\",\n                \"enter\": \"entrar no teleporte\",\n                \"death\": \"o NPC morre\",\n                \"destroy\": \"o bloco é destruído\",\n                \"hit\": \"bater no bloco\",\n                \"layerClear\": \"tudo na camada de objetos estiver limpa\",\n                \"phraseTriggersAfterTemplate\": \"{0} ativa {1} ms depois do evento {2}\",\n                \"phraseTriggersWhenTemplate\": \"{0} ativa quando {1}\",\n                \"talk\": \"o jogador fala com o NPC\"\n            },\n            \"header\": \"Evento:\",\n            \"headerTriggerEvent\": \"Ativação:\",\n            \"itemNewEvent\": \"<Novo Evento>\",\n            \"label\": {\n                \"activate\": \"Ativar\",\n                \"death\": \"Morte\",\n                \"destroy\": \"Destruir\",\n                \"enter\": \"Entrar\",\n                \"hit\": \"Bater\",\n                \"layerClear\": \"Camada Limpa\",\n                \"next\": \"Próximo\",\n                \"talk\": \"Falar\"\n            },\n            \"layers\": {\n                \"headerHide\": \"Esconder:\",\n                \"headerMove\": \"Mover:\",\n                \"headerShow\": \"Mostrar:\",\n                \"headerToggle\": \"Alternar:\"\n            },\n            \"letter\": {\n                \"activate\": \"A:\",\n                \"death\": \"M:\",\n                \"destroy\": \"D:\",\n                \"enter\": \"E:\",\n                \"hit\": \"B:\",\n                \"layerClear\": \"L:\",\n                \"talk\": \"F:\"\n            },\n            \"promptEventName\": \"Nome do evento\",\n            \"promptEventText\": \"Texto do evento\",\n            \"props\": {\n                \"autostart\": \"Ao iniciar\",\n                \"controls\": \"Control.\",\n                \"endGame\": \"Fin.Jogo\",\n                \"layerSmoke\": \"Fumo\",\n                \"sound\": \"Som\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"Manter\",\n                \"actionReset\": \"Rein.\",\n                \"actionSet\": \"Def.\",\n                \"phraseAllSections\": \"Todas as Secções\",\n                \"propBackground\": \"Fundo\",\n                \"propBounds\": \"Bordas\",\n                \"propMusic\": \"Música\"\n            },\n            \"settingsForEvent\": \"Config. do evento {0}\"\n        },\n        \"file\": {\n            \"actionClearLevel\": \"Limpar Nível\",\n            \"actionClearWorld\": \"Limpar Mundo\",\n            \"actionExit\": \"Sair\",\n            \"actionOpen\": \"Abrir\",\n            \"actionRevert\": \"Redefinir\",\n            \"commandNew\": \"Novo\",\n            \"commandOpen\": \"Abrir...\",\n            \"commandSave\": \"Guardar\",\n            \"commandSaveAs\": \"Guardar como...\",\n            \"confirmConfirmAction\": \"Certeza que quer {0}?\",\n            \"confirmConvertFormatTo\": \"Converter para {0}?\",\n            \"confirmSaveBeforeAction\": \"Guardar antes de {0}?\",\n            \"formatLegacy\": \"Antigo (SMBX-64)\",\n            \"convert\": {\n                \"descNew\": \"A extensão será alterada, mas o ficheiro NÃO será deletado.\\n\\nPor favor, confira os recursos que serão perdidos antes.\"\n            },\n            \"formatModern\": \"Moderno (PGE-X)\",\n            \"labelCurrentFile\": \"Ficheiro atual: \\\\\",\n            \"labelFormat\": \"Formato:\",\n            \"optionActionWithoutSave\": \"{0} sem guardar\",\n            \"optionCancelAction\": \"Cancelar: não {0}\",\n            \"optionCancelConversion\": \"Cancelar conversão\",\n            \"optionProceedWithConversion\": \"Confirmar conversão\",\n            \"optionYesSaveThenAction\": \"SIM: Guardar, depois {0}\",\n            \"sectionLevel\": \"Nível\",\n            \"sectionWorld\": \"Mundo\"\n        },\n        \"labelSortLayer\": \"Organizar Camadas:\",\n        \"labelSortOffset\": \"Organizar Deslocamento (Offset):\",\n        \"layers\": {\n            \"abbrevAttLayer\": \"Anexa: \",\n            \"default\": \"Padrão\",\n            \"deletion\": {\n                \"cancel\": \"Cancelar: Não apagar camada\",\n                \"confirmDelete\": \"NÃO: *APAGA TODO O CONTEÚDO*\",\n                \"confirmPreserve\": \"SIM: Mover para a camada padrão\",\n                \"header\": \"A apagar camada {0}\",\n                \"preserveLayerContents\": \"Preservar o conteúdo da camada?\"\n            },\n            \"desc\": {\n                \"att\": \"Quando o NPC se mover, a camada anexada o seguira\"\n            },\n            \"header\": \"Camadas:\",\n            \"itemNewLayer\": \"<Nova Camada>\",\n            \"label\": \"Camada:\",\n            \"labelAttached\": \"Anexado:\",\n            \"labelAttachedLayer\": \"Camada Anexada:\",\n            \"labelMoveLayer\": \"Mover Camada:\",\n            \"promptLayerName\": \"Nome da camada\"\n        },\n        \"letterCoordX\": \"X\",\n        \"letterCoordY\": \"Y\",\n        \"letterDown\": \"B\",\n        \"letterLeft\": \"E\",\n        \"letterRight\": \"D\",\n        \"letterUp\": \"C\",\n        \"level\": {\n            \"alwaysVis\": \"Sempre Vis\",\n            \"bigBG\": \"Cam.Grande\",\n            \"gameStart\": \"Iniciar Jogo\",\n            \"levelName\": \"Nome do Nível\",\n            \"pathBG\": \"Caminho\",\n            \"pathUnlocks\": \"Caminhos Desbloq.\",\n            \"startPos\": \"Pos. Inicial\"\n        },\n        \"mapPos\": \"Pos. Mapa:\",\n        \"npc\": {\n            \"abbrevGen\": \"Gera.\",\n            \"ai\": {\n                \"LR\": \"Esq-Dir\",\n                \"UD\": \"Cim-Bai\",\n                \"aiIs\": \"IA: {0}\",\n                \"headerCustomAi\": \"IA Person.:\",\n                \"jump\": \"Pular\",\n                \"leap\": \"Saltar\",\n                \"swim\": \"Nadar\",\n                \"target\": \"Perseguir\",\n                \"use1_0Ai\": \"Usar IA 1.0?\"\n            },\n            \"gen\": {\n                \"direction\": \"Direção\",\n                \"effectIs\": \"Efeito: {0}\",\n                \"effectShoot\": \"Atirar\",\n                \"effectWarp\": \"Telep\",\n                \"header\": \"Config. de Gerador\"\n            },\n            \"inContainer\": \"-\",\n            \"inertNice\": \"Amig\",\n            \"props\": {\n                \"active\": \"Ativo\",\n                \"attachSurface\": \"Anexa\",\n                \"facing\": \"A olhar\"\n            },\n            \"stuckStop\": \"Fica\",\n            \"tooltipExpandSection\": \"Expandir Secção\"\n        },\n        \"pageBlankOfBlank\": \"Pagina {0} de {1}\",\n        \"phraseAreYouSure\": \"Tem certeza?\",\n        \"section\": {\n            \"noTurnBack\": \"Sem Volta\",\n            \"offscreenExit\": \"Sair Pela Borda\",\n            \"scroll\": \"Deslizar\",\n            \"horizWrap\": \"Ligar Esq-Dir\",\n            \"setBounds\": \"Def. Bordas\",\n            \"underwater\": \"Subaquático\",\n            \"vertWrap\": \"TP. Vertical\"\n        },\n        \"select\": {\n            \"sectionBlankPropBlank\": \"Secção {0}{1}\",\n            \"soundForEventN\": \"Som para Evento {0}\",\n            \"warpTransitEffect\": \"Efeito de Transição do Teleporte\",\n            \"worldMusic\": \"Música do Mundo\",\n            \"allSectPropBlankForEventN\": \"Todas Secções {0} para {1}\",\n            \"pathBlankUnlock\": \"Caminho {0} Desbloqueia Por\",\n            \"sectBlankPropBlankForEventN\": \"Secção {0} {1} para {2}\"\n        },\n        \"testPlay\": {\n            \"boot\": \"Bota\",\n            \"char\": \"Pers\",\n            \"magicHand\": \"Mão Mágica\",\n            \"pet\": \"Mont.\",\n            \"power\": \"Poder\"\n        },\n        \"toggleMagicBlock\": \"Bloco Mágico\",\n        \"tooltip\": {\n            \"BGOs\": \"Obj. de Fundo\",\n            \"NPCs\": \"NPCs\",\n            \"area\": \"Área\",\n            \"blocks\": \"Blocos\",\n            \"erase\": \"Apagar\",\n            \"eraseAll\": \"Apagar Tudo\",\n            \"events\": \"Eventos\",\n            \"file\": \"Ficheiro\",\n            \"layers\": \"Camadas\",\n            \"levels\": \"Níveis\",\n            \"music\": \"Música\",\n            \"paths\": \"Caminhos\",\n            \"scenes\": \"Cenário\",\n            \"select\": \"Selecionar\",\n            \"settings\": \"Configurações\",\n            \"show\": \"Mostrar\",\n            \"tiles\": \"Peças\",\n            \"warps\": \"Teleportes\",\n            \"water\": \"Água\"\n        },\n        \"warp\": {\n            \"dir\": \"Dire.\",\n            \"effect\": \"Efeito:\",\n            \"in\": \"Ent.\",\n            \"item\": \"Item\",\n            \"lvlWarp\": \"Tele.Nív.\",\n            \"needFloor\": \"Precisa de Chão\",\n            \"needKey\": \"Precisa de Chave\",\n            \"needStarCount\": \"Precisa de {0} {1}\",\n            \"out\": \"Saí.\",\n            \"placing\": \"A pôr:\",\n            \"ride\": \"Montar\",\n            \"showStarCount\": \"Mostrar Estrelas\",\n            \"showStartScene\": \"Mostrar Cena Inicial\",\n            \"speed\": \"Velocidade \",\n            \"starLockMessage\": \"Mens. de Bloq. de Estrela\",\n            \"style\": {\n                \"blipInstant\": \"Inst.\",\n                \"door\": \"Porta\",\n                \"pipe\": \"Cano\",\n                \"portal\": \"Port\",\n                \"style\": \"Estilo: \"\n            },\n            \"target\": \"Abre: \",\n            \"title\": \"Config. de Teleporte\",\n            \"to\": \"A: {0}\",\n            \"toMap\": \"Para Mapa\",\n            \"transit\": {\n                \"name0\": \"NENHUM\",\n                \"name1\": \"DESLIZAR\",\n                \"name2\": \"TRANSIÇÃO\",\n                \"name3\": \"CÍRCULO\",\n                \"name4\": \"VIRAR (H)\",\n                \"name5\": \"VIRAR (V)\"\n            },\n            \"twoWay\": \"Ida-Volta\",\n            \"allow\": \"Permitir:\",\n            \"cannonExit\": \"Saí. Canhão\"\n        },\n        \"water\": {\n            \"title\": \"Config. de Água\"\n        },\n        \"wordCoins\": \"Moedas\",\n        \"wordEnabled\": \"Ativado\",\n        \"wordEvent\": {\n            \"genitive\": \"Evento\",\n            \"nominative\": \"Evento\",\n            \"typeLabel\": \"Evento {0}:\"\n        },\n        \"world\": {\n            \"phraseCreditIndex\": \"Linha {0} - Créditos do Mundo:\",\n            \"retryOnFail\": \"AutoRessurgir\",\n            \"totalStars\": \"Núm.Estrelas:\",\n            \"allowChars\": \"Jogáveis\",\n            \"hubWorld\": \"Mundo Central\",\n            \"introLevel\": \"Nível Inicial\",\n            \"name\": \"Nome do Mundo\"\n        },\n        \"phraseCountMore\": \"Mais {0}\",\n        \"phraseDelayIsMs\": \"Interv.: {0} ms\",\n        \"phraseGenericIndex\": \"Número {0}\",\n        \"phraseRadiusIndex\": \"Raio {0}\",\n        \"phraseSectionIndex\": \"Secção {0}\",\n        \"phraseTextOf\": \"Texto - {0}\",\n        \"phraseWarpIndex\": \"Teleporte {0}\",\n        \"wordHeight\": \"Altura\",\n        \"wordInstant\": \"Instantâneo\",\n        \"wordMode\": \"Modo\",\n        \"wordNPC\": {\n            \"genitive\": \"NPC\",\n            \"nominative\": \"NPC\"\n        },\n        \"wordText\": \"Texto\",\n        \"wordWidth\": \"Largura\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"dropAddTitle\": \"Ger. Jogadores\",\n            \"phraseChangeChar\": \"Mudar Personagem\",\n            \"phraseDropMe\": \"Retirar\",\n            \"phraseDropPX\": \"Tirar J{0}\",\n            \"phraseForceResume\": \"Forçar Continuação\",\n            \"phraseHoldStart\": \"Pressione Start\",\n            \"phrasePressAButton\": \"Pressione A\",\n            \"phraseSetControls\": \"Definir Controles\",\n            \"phraseStartToForceRes\": \"Pressione Start para Forçar Continuar\",\n            \"phraseStartToResume\": \"Pressione Start para Continuar\",\n            \"phraseTestControls\": \"Testar Controles\",\n            \"phraseTestProfile\": \"Testar Perfil\",\n            \"phraseWaitingForInput\": \"A esperar por Controles...\",\n            \"reconnectTitle\": \"Reconectar\",\n            \"splitPressSelect_1\": \"Pressione Select para\",\n            \"splitPressSelect_2\": \"Opções de Controle\",\n            \"wordDisconnect\": \"Desconectar\"\n        },\n        \"pause\": {\n            \"continue\": \"Continuar\",\n            \"restartLevel\": \"Reiniciar Nível\",\n            \"resetCheckpoints\": \"Reiniciar Ponto de Ressurgimento\",\n            \"returnToEditor\": \"Retornar ao Editor\",\n            \"saveAndContinue\": \"Guardar e Continuar\",\n            \"playerSetup\": \"Configurar Jogadores\",\n            \"enterCode\": \"Entrar Códigos\",\n            \"quit\": \"Sair\",\n            \"quitTesting\": \"Sair do Teste\",\n            \"saveAndQuit\": \"Guardar e Sair\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"A buscar níveis...\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0}m{1}s\"\n        },\n        \"error\": {\n            \"errorInvalidEnterWarp\": \"Não foi possível iniciar o nível devido a um teleporte de entrada invalido em {1}.\\nTotal de entradas no teleporte: {2}\\n\\nFicheiro: {0}\",\n            \"errorNoStartPoint\": \"Não foi possível iniciar o nível por este nível não conter ponto de inicio ou entradas de teleporte especificadas.\\n\\nFicheiro: {0}\",\n            \"openFileFailed\": \"\\\"{0}\\\" não foi aberto: Ficheiro inexistente ou corrompido.\",\n            \"warpNeedStarCount\": \"Precisa de\\n{0} {1} para entrar.\",\n            \"errorTooOldGameAssets\": \"O conteúdo requer um pacote de recursos de fase mais recente ({0}) do que o pacote de recursos atual ({1}). Atualize o seu pacote de recursos do jogo.\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"Sem resposta do editor conectado. O jogo irá ser fechado.\",\n            \"errorTooOldEngine\": \"O conteúdo requer um nível de recurso de mecanismo mais recente ({0}) do que a versão atual ({1}). Atualize o TheXTech.\"\n        },\n        \"hint\": {\n            \"no-lives-new\": \"Sem moedas? Empréstimo! Olhe à sua pontuação...\",\n            \"no-lives-old\": \"Cuidado - Fim de Jogo iminente!\",\n            \"gray-bricks\": \"Alguns blocos são vulneráveis a habilidades.\",\n            \"char5-bombs\": \"Pressione Correr para segurar e Correr ALT para jogar.\",\n            \"rainbow-surf\": \"Apanhe, corra, abaixe e depois surfe.\",\n            \"shoe-block\": \"Usar isto bloqueia a maioria das - mas não todas - chamas!\",\n            \"heavy-duck\": \"Pato para bloquear a maioria das - mas não todas - chamas!\",\n            \"pound-altrun\": \"Pressione Alt Run para bater para baixo!\",\n            \"pound-down\": \"Pressione para baixo para bater para baixo!\"\n        },\n        \"msgbox\": {\n            \"sysInfoTitle\": \"Informação\",\n            \"sysInfoWarning\": \"Aviso!\",\n            \"sysInfoError\": \"Erro!\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"A carregar dados…\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"A terminar...\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"Traduções\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"Dados aceites, a análise começou...\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"À espera de dados de entrada...\"\n        },\n        \"screenPaused\": \"Pausado\"\n    },\n    \"menu\": {\n        \"abbrevMilliseconds\": \"MS\",\n        \"battle\": {\n            \"errorNoLevels\": \"Não foi possível iniciar uma batalha por falta de níveis\"\n        },\n        \"caseNone\": \"<Nenhum>\",\n        \"character\": {\n            \"selectCharacter\": \"Jogo do {0}\"\n        },\n        \"controls\": {\n            \"buttons\": {\n                \"altJump\": \"Pulo Alt\",\n                \"altRun\": \"Correr Alt\",\n                \"down\": \"Baixo\",\n                \"drop\": \"Cham.Item\",\n                \"jump\": \"Pular\",\n                \"left\": \"Esquerda\",\n                \"right\": \"Direita\",\n                \"run\": \"Correr\",\n                \"start\": \"Iniciar\",\n                \"up\": \"Cima\"\n            },\n            \"editor\": {\n                \"scrollDown\": \"Desl. Abaixo\",\n                \"scrollLeft\": \"Desl. Esq\",\n                \"fastScroll\": \"Desl. Rápido\",\n                \"modeErase\": \"M Apagar\",\n                \"modeSelect\": \"M Selec\",\n                \"nextSection\": \"Prox. Secção\",\n                \"prevSection\": \"Secção Anter\",\n                \"scrollRight\": \"Desl. Dir\",\n                \"scrollUp\": \"Desl. Acima\",\n                \"switchScreens\": \"Painel\",\n                \"testPlay\": \"Jogo Teste\"\n            },\n            \"hotkeys\": {\n                \"enterCheats\": \"Entrar códigos\",\n                \"fullscreen\": \"Ecrã interio\",\n                \"recordGif\": \"Gravar GIF\",\n                \"screenshot\": \"Capt. Ecrã\",\n                \"toggleHUD\": \"Interface\",\n                \"debugInfo\": \"Depurar\",\n                \"legacyPause\": \"Pausa Alt\",\n                \"vanillaCam\": \"Câmara vanilla\"\n            },\n            \"options\": {\n                \"batteryStatus\": \"Estado da Pilha\",\n                \"maxPlayers\": \"Jogadores Máximos\",\n                \"rumble\": \"Vibrar\",\n                \"textEntryStyle\": \"Estilo de Entrada de Texto\"\n            },\n            \"profile\": {\n                \"editorControls\": \"Controles do editor\",\n                \"cursorControls\": \"Controles do cursor\",\n                \"deleteProfile\": \"Apagar Perfil\",\n                \"hotkeys\": \"Atalhos\",\n                \"playerControls\": \"Controles do jogador\",\n                \"renameProfile\": \"Renomear perfil\"\n            },\n            \"tDS\": {\n                \"buttonL\": \"L\",\n                \"buttonR\": \"R\",\n                \"buttonSelect\": \"Select\",\n                \"buttonA\": \"A\",\n                \"buttonB\": \"B\",\n                \"buttonStart\": \"Start\",\n                \"buttonX\": \"X\",\n                \"buttonY\": \"Y\",\n                \"buttonZL\": \"ZL\",\n                \"buttonZR\": \"ZR\",\n                \"cStick\": \"Analógico-C\",\n                \"casePen\": \"(Stylus)\",\n                \"dPad\": \"Direcionais\",\n                \"tStick\": \"Bot. do Analógico\"\n            },\n            \"phraseNewProfOldJoy\": \"Novo Perfil para Joystick Antigo\",\n            \"touchscreen\": {\n                \"option\": {\n                    \"feedbackLength\": \"Duração da Vibração\",\n                    \"feedbackStrength\": \"Força da Vibração\",\n                    \"holdRun\": \"Segurar \\\"Correr\\\" ao Iniciar\",\n                    \"interfaceStyle\": \"Estilo da interface\",\n                    \"layoutStyle\": \"Estilo do Layout\",\n                    \"resetLayout\": \"Reiniciar Layout\",\n                    \"scaleButtons\": \"Escala dos Botões\",\n                    \"scaleDPad\": \"Escala do Direcional\",\n                    \"scaleFactor\": \"Fator de Escala\",\n                    \"sStartSpacing\": \"Espaçamento S-Start\",\n                    \"showCodeButton\": \"Mostrar Botão de Comandos\"\n                },\n                \"layout\": {\n                    \"tight\": \"Apertado\",\n                    \"tinyOld\": \"Pequeno (Antigo)\",\n                    \"longOld\": \"Longo (Antigo)\",\n                    \"phabletOld\": \"Phablet (Antigo)\",\n                    \"phoneOld\": \"Telefone (Antigo)\",\n                    \"standard\": \"Def. Padrão\",\n                    \"tabletOld\": \"Tablet (Antigo)\"\n                },\n                \"style\": {\n                    \"ABXY\": \"ABXY\",\n                    \"XODA\": \"𐄂○□△\",\n                    \"actions\": \"Ações\"\n                }\n            },\n            \"wii\": {\n                \"wiimote\": {\n                    \"buttonA\": \"A\",\n                    \"buttonB\": \"B\",\n                    \"buttonHome\": \"Bot. Home\",\n                    \"buttonMinus\": \"-\",\n                    \"button1\": \"1\",\n                    \"button2\": \"2\",\n                    \"buttonPlus\": \"+\",\n                    \"caseIR\": \"(Infravermelho)\",\n                    \"dPad\": \"Direcionais\",\n                    \"shake\": \"Sacudir\"\n                },\n                \"classic\": {\n                    \"buttonLT\": \"LT\",\n                    \"buttonRT\": \"RT\",\n                    \"buttonX\": \"X\",\n                    \"buttonY\": \"Y\",\n                    \"buttonZL\": \"ZL\",\n                    \"buttonZR\": \"ZR\",\n                    \"lStick\": \"Direcional Esq\",\n                    \"rStick\": \"Direcional Dir\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"C\",\n                    \"buttonZ\": \"Z\",\n                    \"prefixN\": \"N\"\n                },\n                \"phraseNewClassic\": \"Novo Perfil - Classic\",\n                \"phraseNewNunchuck\": \"Novo Perfil - Nunchuck\",\n                \"typeClassic\": \"Classic\",\n                \"typeNunchuck\": \"Nunchuck\",\n                \"typeWiimote\": \"Wiimote\",\n                \"typeGamecube\": \"GameCube\"\n            },\n            \"cursor\": {\n                \"up\": \"Rato Cima\",\n                \"down\": \"Rato Baixo\",\n                \"left\": \"Rato Esq\",\n                \"primary\": \"Primário\",\n                \"right\": \"Rato Dir\",\n                \"secondary\": \"Secundário\",\n                \"tertiary\": \"Terciário\"\n            },\n            \"types\": {\n                \"keyboard\": \"Teclado\",\n                \"oldJoystick\": \"Joystick Antigo\",\n                \"gamepad\": \"Controle\",\n                \"touchscreen\": \"Ecrã de Toque\"\n            },\n            \"wordProfiles\": \"Perfis\",\n            \"caseInvalid\": \"(Invalido)\",\n            \"caseMouse\": \"(Rato)\",\n            \"caseTouch\": \"(Toque)\",\n            \"controlsConnected\": \"Conectado:\",\n            \"controlsDeleteKey\": \"(Pulo Alt para Apagar)\",\n            \"controlsDeleteKey2\": \"(Correr Alt para Apagar)\",\n            \"controlsDeviceTypes\": \"Tipos de Dispositivos\",\n            \"controlsInUse\": \"(Em Uso)\",\n            \"controlsNewProfile\": \"<Novo Perfil>\",\n            \"controlsNotInUse\": \"(Não Usado)\",\n            \"controlsReallyDeleteProfile\": \"Apagar perfil?\",\n            \"controlsTitle\": \"Controles\",\n            \"wordButtons\": \"Botões\",\n            \"joystickSimpleEditor\": \"\"\n        },\n        \"editor\": {\n            \"promptNewWorldName\": \"Nome do novo mundo\",\n            \"errorMissingResources\": \"Desculpe! {0} falta, é necessário para o editor no jogo.\",\n            \"battles\": \"<Níveis de Batalha>\",\n            \"newWorld\": \"<Novo Mundo>\",\n            \"makeFor\": \"\"\n        },\n        \"game\": {\n            \"gameEraseSlot\": \"Selecione um Jogo para apagar\",\n            \"gameNoBattleLevels\": \"<Sem níveis de batalha>\",\n            \"gameNoEpisodesToPlay\": \"<Sem episódios para jogar>\",\n            \"gameSlotContinue\": \"JOGO {0} ... {1}%\",\n            \"gameSlotNew\": \"JOGO {0} ... NOVO JOGO\",\n            \"gameTargetSlot\": \"Agora, o Jogo de destino\",\n            \"phraseScore\": \"Pontos: {0}\",\n            \"phraseTime\": \"Tempo: {0}\",\n            \"gameBattleRandom\": \"Nível Aleatório\",\n            \"gameCopySave\": \"Copiar Jogo\",\n            \"gameEraseSave\": \"Apagar Jogo\",\n            \"gameSourceSlot\": \"Selecione o Jogo de origem\",\n            \"warnEpCompat\": \"\"\n        },\n        \"loading\": \"A carregar...\",\n        \"main\": {\n            \"main1PlayerGame\": \"Um Jogador\",\n            \"mainBattleGame\": \"Modo Batalha\",\n            \"mainEditor\": \"Editor\",\n            \"mainExit\": \"Sair\",\n            \"mainMultiplayerGame\": \"Dois Jogadores\",\n            \"mainOptions\": \"Opções\",\n            \"mainPlayEpisode\": \"Jogar Episódio\"\n        },\n        \"options\": {\n            \"restartEngine\": \"Reinicie a Engine para aplicar as mudanças.\",\n            \"advanced\": {\n                \"render\": {\n                    \"opengl-tip\": \"\",\n                    \"opengl11-tip\": \"\",\n                    \"opengles\": \"OpenGL ES 2+\",\n                    \"opengl11\": \"OpenGL 1.1\",\n                    \"opengles11-tip\": \"\",\n                    \"hw\": \"Automático\",\n                    \"_name\": \"Modo de renderização\",\n                    \"opengl\": \"OpenGL 3+\",\n                    \"opengles-tip\": \"\",\n                    \"sdl\": \"SDL2\",\n                    \"opengles11\": \"OpenGL ES 1.1+\",\n                    \"sdl-tip\": \"\",\n                    \"sw\": \"Software\"\n                },\n                \"audio-channels\": {\n                    \"mono\": \"Mono\",\n                    \"_name\": \"Canais\",\n                    \"stereo\": \"Estéreo\"\n                },\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"Aumente para menos engasgos a custo de desempenho\",\n                    \"_name\": \"Tamanho do buffer\"\n                },\n                \"audio-sample-rate\": {\n                    \"16000\": \"16000 Hz\",\n                    \"22050\": \"22050 Hz\",\n                    \"_name\": \"Taxa de amostragem\",\n                    \"11025\": \"11025 Hz\",\n                    \"32000\": \"32000 Hz\",\n                    \"44100\": \"44100 Hz\",\n                    \"48000\": \"48000 Hz\"\n                },\n                \"choose-assets-on-launch\": \"Escolher assets ao iniciar\",\n                \"audio-format\": {\n                    \"_tooltip\": \"Formato para driver de som\",\n                    \"_name\": \"Formato de áudio\"\n                },\n                \"log-level\": {\n                    \"critical\": \"Crítico\",\n                    \"_name\": \"Nível de depuração (log)\",\n                    \"debug\": \"Depuração\",\n                    \"info\": \"Informação\",\n                    \"none\": \"Nada\",\n                    \"fatal\": \"Fatal\",\n                    \"warning\": \"Aviso\"\n                },\n                \"webm-recording\": \"\",\n                \"internal-res-4p\": {\n                    \"qsmbx\": \"1600x1200 (QSMBX)\",\n                    \"default\": \"Padrão\",\n                    \"qhd\": \"2560x1440 (QHD)\",\n                    \"_name\": \"\",\n                    \"fhd\": \"1920x1080 (FHD)\"\n                },\n                \"advanced-audio\": \"Áudio\",\n                \"_tooltip\": \"Opções técnicas para operações internas\",\n                \"advanced-video\": \"Vídeo\",\n                \"_header\": \"Avançado\",\n                \"record-gameplay-data\": \"Gravar jogatina\",\n                \"scale-down-textures\": {\n                    \"_tooltip\": \"Armazena imagens como 1x para economizar memória\",\n                    \"_name\": \"Reduzir escala das imagens\",\n                    \"all-tip\": \"Menos travamentos no carregamento do que 'Seguro'\",\n                    \"all\": \"Todas\",\n                    \"none\": \"Nenhum\",\n                    \"none-tip\": \"Menos travamentos\",\n                    \"safe-tip\": \"Verifica se as imagens estão no formato 2x\",\n                    \"safe\": \"Seguro\"\n                },\n                \"audio-enable\": \"Ativar\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"Tipo de ecrã inteiro\",\n                    \"exclusive\": \"Exclusivo\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"Área de trabalho\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"Auto\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"GIFs imprecisos\",\n                \"fullscreen-depth\": {\n                    \"16\": \"16-bit\",\n                    \"auto\": \"Auto\",\n                    \"_name\": \"\",\n                    \"32\": \"32-bit\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"main\": {\n                \"two-screen-mode\": {\n                    \"topbottom\": \"\",\n                    \"shared\": \"Partilhado\",\n                    \"split\": \"Esquerda/direita\",\n                    \"smbx\": \"\",\n                    \"_name\": \"\"\n                },\n                \"language\": \"Idioma\",\n                \"discord-rpc\": \"Integração com Discord\",\n                \"timing\": \"\",\n                \"multiplayer\": \"Multijogador\",\n                \"unlimited-framerate\": \"\",\n                \"background-work-tip\": \"\",\n                \"background-work\": \"\",\n                \"_header\": \"Principal\",\n                \"frame-skip\": \"\",\n                \"four-screen-mode\": {\n                    \"split\": \"\",\n                    \"shared\": \"Partilhado\",\n                    \"_name\": \"\"\n                },\n                \"vsync\": \"V-Sync\"\n            },\n            \"audio\": {\n                \"sfx-pet-beat-tip\": \"\",\n                \"sfx-pet-beat\": \"\",\n                \"sfx-modern-tip\": \"\",\n                \"_header\": \"Áudio\",\n                \"sfx-modern\": \"Efeitos modernos\",\n                \"audio-music-volume\": \"Volume da música\",\n                \"sfx-audio-fx\": \"Efeitos de eco\",\n                \"audio-sfx-volume\": \"Volume dos efeitos\",\n                \"audio-prefs\": \"Referências\",\n                \"sfx-spatial-audio\": \"Som espacial\",\n                \"sfx-spatial-audio-tip\": \"Reduz o volume de sons fora do ecrã\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"0\": \"0\",\n                    \"2\": \"2\",\n                    \"1\": \"1\",\n                    \"3\": \"\",\n                    \"_name\": \"\"\n                }\n            },\n            \"view-credits\": {\n                \"_header\": \"Ver créditos\"\n            },\n            \"video\": {\n                \"world-map-stars-show-tip\": \"\",\n                \"battery-status\": {\n                    \"off\": \"Desligado\",\n                    \"on\": \"Sempre\",\n                    \"_name\": \"\",\n                    \"low-tip\": \"\",\n                    \"low\": \"Baixo\",\n                    \"fullscreen-tip\": \"\",\n                    \"fullscreen\": \"Ecrã inteiro\"\n                },\n                \"internal-res\": {\n                    \"hd\": \"1280x720 (HD)\",\n                    \"hello\": \"768x432 (HELLO)\",\n                    \"gba\": \"480x320 (GBA)\",\n                    \"dynamic\": \"Dinâmico\",\n                    \"_tooltip\": \"\",\n                    \"_name\": \"Tamanho do ecrã\",\n                    \"3ds\": \"800x480 (3DS)\",\n                    \"vga\": \"640x480 (VGA)\",\n                    \"nds\": \"512x384 (NDS)\",\n                    \"smbx\": \"800x600 (SMBX)\",\n                    \"snes\": \"512x448 (SNES)\",\n                    \"smbx-wide\": \"\"\n                },\n                \"_header\": \"Vídeo\",\n                \"fullscreen\": \"Ecrã inteiro\",\n                \"enable-inter-level-fade-effect\": \"\",\n                \"info-game\": \"\",\n                \"scale-mode\": {\n                    \"1x\": \"1x\",\n                    \"2x\": \"2x\",\n                    \"integer\": \"Inteiros\",\n                    \"_name\": \"Modo de escala\",\n                    \"0_5x\": \"0,5x\",\n                    \"center\": \"Centro\",\n                    \"linear\": \"Suave\",\n                    \"nearest\": \"\",\n                    \"full\": \"\",\n                    \"3x\": \"\"\n                },\n                \"show-episode-title\": {\n                    \"bottom-tip\": \"\",\n                    \"_name\": \"\",\n                    \"off\": \"Desligado\",\n                    \"bottom\": \"\",\n                    \"top-tip\": \"\",\n                    \"top\": \"\"\n                },\n                \"3d-compat-mode\": \"\",\n                \"show-playtime-counter\": {\n                    \"_name\": \"\",\n                    \"transparent\": \"Transparente\",\n                    \"animated\": \"\",\n                    \"_tooltip\": \"\",\n                    \"opaque\": \"Opaco\",\n                    \"animated-tip\": \"\",\n                    \"off\": \"Desl.\",\n                    \"off-tip\": \"\"\n                },\n                \"show-medals-counter-tip\": \"\",\n                \"show-screen-shake\": \"\",\n                \"video-system\": \"Sistema\",\n                \"world-map-stars-show\": \"\",\n                \"info-run\": \"\",\n                \"info-meta\": \"\",\n                \"display-controllers\": \"\",\n                \"effects\": \"\",\n                \"3d-compat-mode-tip\": \"\",\n                \"show-fails-counter\": \"\",\n                \"show-medals-counter\": \"\",\n                \"show-fails-counter-tip\": \"\",\n                \"show-fps\": \"\"\n            },\n            \"controls\": {\n                \"_header\": \"Controlos\"\n            },\n            \"episode-options\": {\n                \"_header\": \"\",\n                \"creator-compat\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"disable\": \"Desativar\",\n                    \"enable\": \"Ativar\",\n                    \"file-only\": \"Somente ficheiro\"\n                },\n                \"playstyle\": {\n                    \"_name\": \"\",\n                    \"classic-tip\": \"Atualizações minimais\",\n                    \"classic\": \"Classic\",\n                    \"modern\": \"Moderno (PGE-X)\",\n                    \"modern-tip\": \"Todas atualizações\",\n                    \"vanilla-tip\": \"Nenhumas atualizações\",\n                    \"vanilla\": \"\"\n                },\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"compatibility\": {\n                \"_header\": \"\",\n                \"features\": \"Funcionalidades\",\n                \"bugfixes\": \"\",\n                \"reset-all\": \"\",\n                \"autocode\": \"\",\n                \"_tooltip\": \"\"\n            }\n        },\n        \"wordBack\": \"Voltar\",\n        \"wordHide\": \"Esconder\",\n        \"wordOff\": \"Desl.\",\n        \"wordWaiting\": \"À espera\",\n        \"wordOn\": \"Ligado\",\n        \"wordPlayer\": \"Jogador\",\n        \"wordProfile\": \"Perfil\",\n        \"wordResume\": \"Continuar\",\n        \"wordShow\": \"Mostrar\",\n        \"wordYes\": \"Sim\",\n        \"wordNo\": \"Não\"\n    },\n    \"outro\": {\n        \"cppPortDevelopers\": \"Programadores da versão C++:\",\n        \"customSprites\": \"Sprite Personalizado:\",\n        \"engineCredits\": \"Créditos da Engine:\",\n        \"gameCredits\": \"Créditos do jogo:\",\n        \"levelDesign\": \"Design de níveis:\",\n        \"nameAndrewSpinks\": \"Andrew Spinks\",\n        \"nameVitalyNovichkov\": \"Novichkov Vitaliy\",\n        \"originalBy\": \"Código original VB6 Por:\",\n        \"psVitaPortBy\": \"Port para PS Vita Por:\",\n        \"qualityControl\": \"Controle de Qualidade:\",\n        \"specialThanks\": \"Agradecimentos Especiais:\"\n    },\n    \"pluralRules\": \"one-is-singular\",\n    \"languageName\": \"Português\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_ru.json",
    "content": "{\n    \"editor\": {\n        \"block\": {\n            \"canBreak\": \"Можн. слом.\",\n            \"canBreakTooltip\": \"Устар.: ломается при ударе\",\n            \"inside\": \"Содерж.:\",\n            \"invis\": \"Невид.\",\n            \"letterHeight\": \"В \",\n            \"letterWidth\": \"Ш \",\n            \"pickContents\": \"Выберите содержимое блока\",\n            \"slick\": \"Лёд\"\n        },\n        \"browser\": {\n            \"askOverwriteFile\": \"Заменить {0}?\",\n            \"itemNewFile\": \"<Нов. файл>\",\n            \"itemNewFolder\": \"<Нов. папка>\",\n            \"newFile\": \"Новый файл\",\n            \"openFile\": \"Открыть файл\",\n            \"saveFile\": \"Сохранить файл\"\n        },\n        \"events\": {\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"изменить все границы секции к текущим?\",\n                \"changeSectionBoundsToCurrent\": \"изменить границы секции {0} к текущим?\",\n                \"shouldEvent\": \"Должно ли событие {0}\"\n            },\n            \"controlsForEventN\": \"Авто-управление персонажем по событию \\\"{0}\\\"\",\n            \"deletion\": {\n                \"cancel\": \"Нет: не удалять событие\",\n                \"confirm\": \"Да: удалить событие\",\n                \"deletingEvent\": \"Удалить событие {0}\"\n            },\n            \"desc\": {\n                \"activate\": \"НИП появится на экране\",\n                \"death\": \"НИП умрёт\",\n                \"destroy\": \"блок будет разрушен\",\n                \"enter\": \"игрок войдёт в проход\",\n                \"hit\": \"по блоку ударят\",\n                \"layerClear\": \"слой будет очищен от объектов\",\n                \"phraseTriggersAfterTemplate\": \"Событие \\\"{0}\\\", которое сработает через {1} мс. после события \\\\\\\"{2}\\\\\\\"\",\n                \"phraseTriggersWhenTemplate\": \"{0}: Сработает, когда {1}\",\n                \"talk\": \"игрок поговорит с НИП\"\n            },\n            \"header\": \"Событ.:\",\n            \"headerTriggerEvent\": \"Триггер:\",\n            \"itemNewEvent\": \"<Новое событие>\",\n            \"label\": {\n                \"activate\": \"Активация\",\n                \"death\": \"Смерть\",\n                \"destroy\": \"Разруш.\",\n                \"enter\": \"Вход\",\n                \"hit\": \"Удар\",\n                \"layerClear\": \"Слой чист\",\n                \"next\": \"След.\",\n                \"talk\": \"Поговорить\"\n            },\n            \"layers\": {\n                \"headerHide\": \"Скрыть:\",\n                \"headerMove\": \"Движение:\",\n                \"headerShow\": \"Показать:\",\n                \"headerToggle\": \"Переключ.:\"\n            },\n            \"letter\": {\n                \"activate\": \"Ак:\",\n                \"death\": \"См:\",\n                \"destroy\": \"Ун:\",\n                \"enter\": \"Вх:\",\n                \"hit\": \"Ур:\",\n                \"layerClear\": \"Сл:\",\n                \"talk\": \"Гв:\"\n            },\n            \"promptEventName\": \"Имя события:\",\n            \"promptEventText\": \"Текст события:\",\n            \"props\": {\n                \"autostart\": \"Автозап.\",\n                \"controls\": \"Авто-уп.\",\n                \"endGame\": \"Зав.игр.\",\n                \"sound\": \"Звук\",\n                \"layerSmoke\": \"Дым\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"Ост.\",\n                \"actionReset\": \"Сброс\",\n                \"actionSet\": \"Назн.\",\n                \"phraseAllSections\": \"Все секции\",\n                \"propBackground\": \"Фон\",\n                \"propBounds\": \"Границы\",\n                \"propMusic\": \"Музыка\"\n            },\n            \"settingsForEvent\": \"Настройки события \\\"{0}\\\"\"\n        },\n        \"file\": {\n            \"actionClearLevel\": \"Стереть уровень\",\n            \"actionClearWorld\": \"Стереть мир\",\n            \"actionExit\": \"Выйти\",\n            \"actionOpen\": \"Открыть\",\n            \"actionRevert\": \"Откатить\",\n            \"commandNew\": \"Новый\",\n            \"commandOpen\": \"Открыть...\",\n            \"commandSave\": \"Сохранить\",\n            \"commandSaveAs\": \"Сохранить как...\",\n            \"confirmConfirmAction\": \"Вы уверены, что хотите {0}?\",\n            \"confirmConvertFormatTo\": \"Преобразовать в {0} формат?\",\n            \"confirmSaveBeforeAction\": \"Сохранить, прежде чем {0}?\",\n            \"convert\": {\n                \"descNew\": \"Расширение имени файла будет изменено, однако старый файл СОХРАНИТСЯ.\\n\\nНе забудьте проверить файл на предмет потерянной функциональности после сохранения.\"\n            },\n            \"formatLegacy\": \"Устаревший\",\n            \"formatModern\": \"Современный\",\n            \"labelCurrentFile\": \"Текущий файл:\",\n            \"labelFormat\": \"Формат:\",\n            \"optionActionWithoutSave\": \"{0} без сохранения?\",\n            \"optionCancelAction\": \"Отмена действия: {0}\",\n            \"optionCancelConversion\": \"Отмена преобразования\",\n            \"optionProceedWithConversion\": \"Выполнить с преобразованием\",\n            \"optionYesSaveThenAction\": \"Да: сохран., прежде чем {0}\",\n            \"sectionLevel\": \"Уровень\",\n            \"sectionWorld\": \"Мир\"\n        },\n        \"layers\": {\n            \"abbrevAttLayer\": \"Прис.: \",\n            \"default\": \"По-умолч.\",\n            \"deletion\": {\n                \"cancel\": \"Отмена: не удалять слой\",\n                \"confirmDelete\": \"Нет: *УДАЛИТЬ ВСЁ СОДЕРЖИМОЕ*\",\n                \"confirmPreserve\": \"Да: перемест.. в основной слой\",\n                \"header\": \"Удаление слоя {0}\",\n                \"preserveLayerContents\": \"Сохранить содержимое слоя?\"\n            },\n            \"desc\": {\n                \"att\": \"Пока НИП движется, присоединён- ный слой следует за ним\"\n            },\n            \"header\": \"Слои:\",\n            \"itemNewLayer\": \"<Нов. слой>\",\n            \"label\": \"Слой:\",\n            \"labelAttached\": \"Присоед.:\",\n            \"labelAttachedLayer\": \"Присоед. слой:\",\n            \"labelMoveLayer\": \"Движение слоя:\",\n            \"promptLayerName\": \"Имя слоя\"\n        },\n        \"letterCoordX\": \"X\",\n        \"letterCoordY\": \"Y\",\n        \"letterDown\": \"Н\",\n        \"letterLeft\": \"Л\",\n        \"letterRight\": \"П\",\n        \"letterUp\": \"В\",\n        \"level\": {\n            \"alwaysVis\": \"Всегда вид.\",\n            \"bigBG\": \"Большой фон\",\n            \"gameStart\": \"Начало игры\",\n            \"levelName\": \"Имя уровня\",\n            \"pathBG\": \"Фон-путь\",\n            \"pathUnlocks\": \"Разблок.путей\",\n            \"startPos\": \"Нач. позиция\"\n        },\n        \"mapPos\": \"Позиция:\",\n        \"npc\": {\n            \"abbrevGen\": \"Ген.\",\n            \"ai\": {\n                \"LR\": \"ЛП\",\n                \"UD\": \"ВН\",\n                \"aiIs\": \"ИИ: {0}\",\n                \"headerCustomAi\": \"Свой ИИ:\",\n                \"jump\": \"Прыг\",\n                \"leap\": \"Скок\",\n                \"swim\": \"Плав.\",\n                \"target\": \"Цель\",\n                \"use1_0Ai\": \"Исп. ИИ 1.0?\"\n            },\n            \"gen\": {\n                \"direction\": \"Направление\",\n                \"effectIs\": \"Эффект: {0}\",\n                \"effectShoot\": \"Выстрел\",\n                \"effectWarp\": \"Выползание\",\n                \"header\": \"Настройки генератора\"\n            },\n            \"inContainer\": \"В\",\n            \"inertNice\": \"Друг\",\n            \"props\": {\n                \"active\": \"Актив.\",\n                \"attachSurface\": \"Прис.\",\n                \"facing\": \"Напр.\"\n            },\n            \"stuckStop\": \"Стоп\",\n            \"tooltipExpandSection\": \"Расширить секцию\"\n        },\n        \"pageBlankOfBlank\": \"Стр. {0} из {1}\",\n        \"phraseAreYouSure\": \"Вы уверены?\",\n        \"phraseCountMore\": \"Ещё {0}\",\n        \"phraseDelayIsMs\": \"Период: {0} мс\",\n        \"phraseGenericIndex\": \"Номер {0}\",\n        \"phraseRadiusIndex\": \"Радиус {0}\",\n        \"phraseSectionIndex\": \"Секция {0}\",\n        \"phraseTextOf\": \"Текст \\\"{0}\\\"\",\n        \"phraseWarpIndex\": \"Проход {0}\",\n        \"section\": {\n            \"horizWrap\": \"Гор. перенос\",\n            \"noTurnBack\": \"Только вправо\",\n            \"offscreenExit\": \"Выход за экран\",\n            \"scroll\": \"Прокрутка\",\n            \"setBounds\": \"Уст. границы\",\n            \"underwater\": \"Под водой\",\n            \"vertWrap\": \"Верт.Прох.\"\n        },\n        \"select\": {\n            \"allSectPropBlankForEventN\": \"{0} всех секций для \\\"{1}\\\"\",\n            \"pathBlankUnlock\": \"Разблок. путь {0}:\",\n            \"sectBlankPropBlankForEventN\": \"{1} секции {0} для \\\"{2}\\\"\",\n            \"sectionBlankPropBlank\": \"{1} секции {0}\",\n            \"soundForEventN\": \"Звук для события \\\"{0}\\\"\",\n            \"warpTransitEffect\": \"Эффект перехода прохода\",\n            \"worldMusic\": \"Музыка мира\"\n        },\n        \"testPlay\": {\n            \"boot\": \"Сапог\",\n            \"char\": \"Перс.\",\n            \"magicHand\": \"Хватайка\",\n            \"pet\": \"Конь\",\n            \"power\": \"Сила\"\n        },\n        \"toggleMagicBlock\": \"Волшебный блок\",\n        \"tooltip\": {\n            \"BGOs\": \"Фоновые объекты\",\n            \"NPCs\": \"Неигровые персонажи\",\n            \"blocks\": \"Блоки\",\n            \"erase\": \"Ластик\",\n            \"eraseAll\": \"Стереть всё\",\n            \"events\": \"События\",\n            \"file\": \"Файл\",\n            \"layers\": \"Слои\",\n            \"levels\": \"Уровни\",\n            \"music\": \"Музыка\",\n            \"paths\": \"Пути\",\n            \"scenes\": \"Декорации\",\n            \"select\": \"Выбор\",\n            \"settings\": \"Настройки\",\n            \"show\": \"Показать\",\n            \"tiles\": \"Ландшафт\",\n            \"warps\": \"Проходы\",\n            \"water\": \"Вода\",\n            \"area\": \"Район\"\n        },\n        \"warp\": {\n            \"allow\": \"Разр.\",\n            \"cannonExit\": \"Выход-пушка\",\n            \"dir\": \"Напр.\",\n            \"effect\": \"Эффект:\",\n            \"in\": \"Вх.\",\n            \"item\": \"Вещь\",\n            \"lvlWarp\": \"Ц.проход\",\n            \"needFloor\": \"Нужна земля\",\n            \"needKey\": \"Запереть\",\n            \"needStarCount\": \"Нужно {0} {1}\",\n            \"out\": \"Вых.\",\n            \"placing\": \"Размещение:\",\n            \"ride\": \"Езд.\",\n            \"showStarCount\": \"Показ. звёзды\",\n            \"showStartScene\": \"Показ. н.сцену\",\n            \"speed\": \"Сила \",\n            \"starLockMessage\": \"Сообщ. о звзд.\",\n            \"style\": {\n                \"blipInstant\": \"Мгнв.\",\n                \"door\": \"Дверь\",\n                \"pipe\": \"Труба\",\n                \"portal\": \"Портал\",\n                \"style\": \"Стиль:\"\n            },\n            \"target\": \"Цель: \",\n            \"title\": \"Настр. прохода\",\n            \"to\": \"К: {0}\",\n            \"toMap\": \"В мир\",\n            \"transit\": {\n                \"name0\": \"НЕТ\",\n                \"name1\": \"ПРОКР.\",\n                \"name2\": \"ПЕРЕЛ.\",\n                \"name3\": \"КРУГ\",\n                \"name4\": \"ОТР. (Г)\",\n                \"name5\": \"ОТР. (В)\"\n            },\n            \"twoWay\": \"Дву-напр.\"\n        },\n        \"water\": {\n            \"title\": \"Настройки воды\"\n        },\n        \"wordCoins\": \"Монетки\",\n        \"wordEnabled\": \"Включено\",\n        \"wordEvent\": {\n            \"genitive\": \"События\",\n            \"nominative\": \"Событие\",\n            \"typeLabel\": \"Событие \\\"{0}\\\":\"\n        },\n        \"wordHeight\": \"Высота\",\n        \"wordInstant\": \"Мгновенно\",\n        \"wordMode\": \"Режим\",\n        \"wordNPC\": {\n            \"genitive\": \"НИПа\",\n            \"nominative\": \"НИП\"\n        },\n        \"wordText\": \"Текст\",\n        \"wordWidth\": \"Ширина\",\n        \"world\": {\n            \"allowChars\": \"Разр. перс.\",\n            \"hubWorld\": \"Коридорный мир\",\n            \"introLevel\": \"Урвоень-вступление\",\n            \"name\": \"Название мира\",\n            \"phraseCreditIndex\": \"Титры, стрка {0}:\",\n            \"retryOnFail\": \"Переигр. сразу\",\n            \"totalStars\": \"Всего звёзд:\"\n        },\n        \"labelSortLayer\": \"Слой сорт.:\",\n        \"labelSortOffset\": \"Смещ.Сорт:\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"dropAddTitle\": \"Доб./Убрать игроков\",\n            \"phraseChangeChar\": \"Сменить персонажа\",\n            \"phraseDropMe\": \"Убрать себя\",\n            \"phraseDropPX\": \"Убрать игрока {0}\",\n            \"phraseForceResume\": \"Принуд. возобновить\",\n            \"phraseHoldStart\": \"Удерж. Старт\",\n            \"phrasePressAButton\": \"Нажмите кнопку A\",\n            \"phraseSetControls\": \"Назначить управление\",\n            \"phraseStartToForceRes\": \"Нажмите Start, чтобы принуд. возобн.\",\n            \"phraseStartToResume\": \"Нажмите Start, чтобы возобновить\",\n            \"phraseTestControls\": \"Проверить управление\",\n            \"phraseWaitingForInput\": \"Ожидание устройств ввода..\",\n            \"reconnectTitle\": \"Переподключить\",\n            \"splitPressSelect_1\": \"Нажмите Select, чтобы\",\n            \"splitPressSelect_2\": \"Настройки управления\",\n            \"wordDisconnect\": \"Отключить\",\n            \"phraseTestProfile\": \"Тест профиля\"\n        },\n        \"error\": {\n            \"errorInvalidEnterWarp\": \"Невозможно запустить уровень, потому что указан некорректный входной проход {1}.\\nВсего проходов: {2}\\n\\nФайл: {0}\",\n            \"errorNoStartPoint\": \"Невозможно запустить уровень из-за отсутствия начальных точек, либо не указан входной проход.\\n\\nФайл: {0}\",\n            \"openFileFailed\": \"Не удаётся открыть \\\"{0}\\\": файл не существует или повреждён.\",\n            \"warpNeedStarCount\": \"Чтобы войти, нужно найти\\n{0} {1}.\",\n            \"IPCTimeOut\": \"Нет ответа от подключённого редактора. Игра будет закрыта.\",\n            \"openIPCDataFailed\": \"Невозможно обработать принятые данные файла из-за повреждений либо других ошибок.\",\n            \"errorTooOldEngine\": \"Контент требует более новую версию функционала ({0}), чем текущая версия ({1}). Пожалуйста, обновите TheXTech.\",\n            \"errorTooOldGameAssets\": \"Контент требует более новый уровень функционала ({0}), чем заявлено в текущем пакете игровых ресурсов ({1}). Пожалуйста, обновите Ваш набор игровых ресурсов.\"\n        },\n        \"pause\": {\n            \"continue\": \"Продолжить\",\n            \"enterCode\": \"Ввести код\",\n            \"quit\": \"Выйти\",\n            \"quitTesting\": \"Закрыть тест\",\n            \"resetCheckpoints\": \"Сбросить контр. точки\",\n            \"restartLevel\": \"Начать сначала\",\n            \"returnToEditor\": \"Назад в редактор\",\n            \"saveAndContinue\": \"Сохранить и продолжить\",\n            \"saveAndQuit\": \"Сохранить и выйти\",\n            \"playerSetup\": \"Настройки игрока\"\n        },\n        \"msgbox\": {\n            \"sysInfoError\": \"Ошибка!\",\n            \"sysInfoTitle\": \"Информация\",\n            \"sysInfoWarning\": \"Предупреждение!\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"Чтение уровней...\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0}м{1}с\"\n        },\n        \"hint\": {\n            \"no-lives-new\": \"Не осталось монет? Возьмите в кредит! Следите за своим счётом...\",\n            \"no-lives-old\": \"Будьте осторожны: конец игры неизбежен!\",\n            \"rainbow-surf\": \"Захвати, беги, зажми \\\"вниз\\\", и выпусти: поедешь с ветерком.\",\n            \"char5-bombs\": \"Нажмите БЕГ, чтобы подобрать, и Альт-БЕГ, чтобы швырнуть.\",\n            \"gray-bricks\": \"Некоторые блоки можно сломать с помощью особых усилителей.\",\n            \"heavy-duck\": \"Пригнитесь, чтобы защититься от огня (но не от любого)!\",\n            \"shoe-block\": \"Одев это, можно защититься от огня (но не от любого)!\",\n            \"pound-down\": \"Нажмите Вниз, чтобы сотрясти землю!\",\n            \"pound-altrun\": \"Нажмите Альт-Бег, чтобы сотрясти землю!\"\n        },\n        \"screenPaused\": \"Игра приостановлена\",\n        \"ipcStatus\": {\n            \"dataAccepted\": \"Данные приняты, началась обработка...\",\n            \"dataTransferStarted\": \"Началась передача данных...\",\n            \"dataValid\": \"Принятые данные корректны\",\n            \"errorTimeout\": \"ОШИБКА: Время ожидания истекло.\",\n            \"loadingdone\": \"Готово. Теперь запускаем игру...\",\n            \"waitingInput\": \"Ожидание входящих данных...\"\n        },\n        \"loader\": {\n            \"statusAssetPacks\": \"Наборы ресурсов\",\n            \"statusFinishing\": \"Завершение...\",\n            \"statusGameInfo\": \"Информация об игре\",\n            \"statusLoadData\": \"Загрузка данных...\",\n            \"statusLoadFile\": \"Загрузка: {0}\",\n            \"statusTranslations\": \"Переводы\",\n            \"loading\": \"Загрузка...\"\n        }\n    },\n    \"languageName\": \"Русский\",\n    \"menu\": {\n        \"abbrevMilliseconds\": \"мс\",\n        \"battle\": {\n            \"errorNoLevels\": \"Невозможно начать битву потому что нету доступных уровней\"\n        },\n        \"caseNone\": \"<Нет>\",\n        \"character\": {\n            \"selectCharacter\": \"Играть, как {0}\"\n        },\n        \"controls\": {\n            \"buttons\": {\n                \"altJump\": \"Альт-Прыг\",\n                \"altRun\": \"Альт-Бег\",\n                \"down\": \"Вниз\",\n                \"drop\": \"Выброс\",\n                \"jump\": \"Прыжок\",\n                \"left\": \"Влево\",\n                \"right\": \"Впаво\",\n                \"run\": \"Бег\",\n                \"start\": \"Старт\",\n                \"up\": \"Вверх\"\n            },\n            \"caseInvalid\": \"(Некор.)\",\n            \"caseMouse\": \"(Мышь)\",\n            \"caseTouch\": \"(Сенсор)\",\n            \"controlsConnected\": \"Подключено:\",\n            \"controlsDeleteKey\": \"(Альт-Прыжок чтобы сбросить)\",\n            \"controlsDeviceTypes\": \"Типы устройств\",\n            \"controlsInUse\": \"(Используется)\",\n            \"controlsNewProfile\": \"<Новый профиль>\",\n            \"controlsNotInUse\": \"(Не используется)\",\n            \"controlsReallyDeleteProfile\": \"Действительно удалить профиль?\",\n            \"controlsTitle\": \"Управление\",\n            \"cursor\": {\n                \"down\": \"Мышь вниз\",\n                \"left\": \"Мышь влево\",\n                \"primary\": \"Первичн.\",\n                \"right\": \"Мышь вправо\",\n                \"secondary\": \"Вторичн.\",\n                \"tertiary\": \"Третичн.\",\n                \"up\": \"Мышь вверх\"\n            },\n            \"editor\": {\n                \"fastScroll\": \"Быс.прокр.\",\n                \"modeErase\": \"Ластик\",\n                \"modeSelect\": \"Выбор\",\n                \"nextSection\": \"След.сект.\",\n                \"prevSection\": \"Пред.сект.\",\n                \"scrollDown\": \"Пр.вниз\",\n                \"scrollLeft\": \"Пр.влево\",\n                \"scrollRight\": \"Пр.вправо\",\n                \"scrollUp\": \"Пр.вверх\",\n                \"switchScreens\": \"Панель\",\n                \"testPlay\": \"Тест-игра\"\n            },\n            \"hotkeys\": {\n                \"debugInfo\": \"Отл.инф.\",\n                \"enterCheats\": \"Ввести код\",\n                \"fullscreen\": \"Полн.экран\",\n                \"legacyPause\": \"Ст.пауза\",\n                \"recordGif\": \"Зап.GIF\",\n                \"screenshot\": \"Снимок\",\n                \"toggleHUD\": \"Пер.пан.\",\n                \"vanillaCam\": \"Классич. камера\"\n            },\n            \"options\": {\n                \"batteryStatus\": \"Состояние батареи\",\n                \"maxPlayers\": \"Макс. игроков\",\n                \"rumble\": \"Вибрация\",\n                \"textEntryStyle\": \"Тип ввода текста\",\n                \"altMenuControls\": \"Альт. управление меню\"\n            },\n            \"phraseNewProfOldJoy\": \"Новый профиль для ст.джойстика\",\n            \"profile\": {\n                \"cursorControls\": \"Указатель\",\n                \"deleteProfile\": \"Удалить профиль\",\n                \"editorControls\": \"Редактор\",\n                \"hotkeys\": \"Горчие клавиши\",\n                \"playerControls\": \"Игровое управление\",\n                \"renameProfile\": \"Переименовать профиль\"\n            },\n            \"tDS\": {\n                \"buttonA\": \"A\",\n                \"buttonB\": \"B\",\n                \"buttonL\": \"L\",\n                \"buttonR\": \"R\",\n                \"buttonSelect\": \"SELECT\",\n                \"buttonStart\": \"Старт\",\n                \"buttonX\": \"X\",\n                \"buttonY\": \"Y\",\n                \"buttonZL\": \"ZL\",\n                \"buttonZR\": \"ZR\",\n                \"cStick\": \"C-Stick\",\n                \"casePen\": \"(Перо)\",\n                \"dPad\": \"Крест.\",\n                \"tStick\": \"Джойстик\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"longOld\": \"Длинная (ст.)\",\n                    \"phabletOld\": \"Фаблет (ст.)\",\n                    \"phoneOld\": \"Телефон (ст.)\",\n                    \"standard\": \"Обычная\",\n                    \"tabletOld\": \"Планшет (ст.)\",\n                    \"tight\": \"Плотная\",\n                    \"tinyOld\": \"Кроха (ст.)\"\n                },\n                \"option\": {\n                    \"feedbackLength\": \"Длительность отдачи\",\n                    \"feedbackStrength\": \"Сила отдачи\",\n                    \"holdRun\": \"Удержание БЕГа при запуске\",\n                    \"interfaceStyle\": \"Стиль кнопок\",\n                    \"layoutStyle\": \"Вид раскладки\",\n                    \"resetLayout\": \"Сбросить раскладку\",\n                    \"sStartSpacing\": \"Расстояние Выбор-Старт\",\n                    \"scaleButtons\": \"Масштаб кнопок\",\n                    \"scaleDPad\": \"Масштаб крестовины\",\n                    \"scaleFactor\": \"Общий масштаб\",\n                    \"showCodeButton\": \"Показать кнопку кода\"\n                },\n                \"style\": {\n                    \"ABXY\": \"ABXY\",\n                    \"XODA\": \"XODA\",\n                    \"actions\": \"Дейсвтия\"\n                }\n            },\n            \"types\": {\n                \"gamepad\": \"Геймпад\",\n                \"keyboard\": \"Клавиатура\",\n                \"oldJoystick\": \"Ст.джойстик\",\n                \"touchscreen\": \"Сенсор\"\n            },\n            \"wii\": {\n                \"classic\": {\n                    \"buttonLT\": \"LT\",\n                    \"buttonRT\": \"RT\",\n                    \"buttonX\": \"X\",\n                    \"buttonY\": \"Y\",\n                    \"buttonZL\": \"ZL\",\n                    \"buttonZR\": \"ZR\",\n                    \"lStick\": \"Л.Джой.\",\n                    \"rStick\": \"П.Джой.\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"C\",\n                    \"buttonZ\": \"Z\",\n                    \"prefixN\": \"N\"\n                },\n                \"phraseNewClassic\": \"Нов. профиль Classic\",\n                \"phraseNewNunchuck\": \"Нов. профиль Nunchack\",\n                \"typeClassic\": \"Classic\",\n                \"typeNunchuck\": \"Nunchack\",\n                \"typeWiimote\": \"Wiimote\",\n                \"wiimote\": {\n                    \"button1\": \"1\",\n                    \"button2\": \"2\",\n                    \"buttonA\": \"A\",\n                    \"buttonB\": \"B\",\n                    \"buttonHome\": \"Дом\",\n                    \"buttonMinus\": \"-\",\n                    \"buttonPlus\": \"+\",\n                    \"caseIR\": \"(ИК)\",\n                    \"dPad\": \"Крест.\",\n                    \"shake\": \"Встряска\"\n                },\n                \"typeGamecube\": \"GameCube\"\n            },\n            \"wordButtons\": \"Кнопки\",\n            \"wordProfiles\": \"Профили\",\n            \"joystickSimpleEditor\": \"Простое управление редактором\",\n            \"controlsDeleteKey2\": \"(Альт-Бег, чтобы удалить)\"\n        },\n        \"editor\": {\n            \"errorMissingResources\": \"Извините, но для встроенного редактора не хватает файла {0}.\",\n            \"newWorld\": \"<Новый мир>\",\n            \"promptNewWorldName\": \"Имя нового мира\",\n            \"battles\": \"<Уровни для битв>\",\n            \"makeFor\": \"Создать для:\"\n        },\n        \"game\": {\n            \"gameBattleRandom\": \"Случайный уровень\",\n            \"gameCopySave\": \"Скопир. сохранение\",\n            \"gameEraseSave\": \"Стереть сохранение\",\n            \"gameEraseSlot\": \"Какое сохранение стереть?\",\n            \"gameNoBattleLevels\": \"<Нету уровней битвы>\",\n            \"gameNoEpisodesToPlay\": \"<Эпизоды отсутствуют>\",\n            \"gameSlotContinue\": \"Слот {0} ... {1}%\",\n            \"gameSlotNew\": \"Слот {0} ... Новая игра\",\n            \"gameSourceSlot\": \"Что скопировать?\",\n            \"gameTargetSlot\": \"Куда скопировать?\",\n            \"phraseScore\": \"Счёт: {0}\",\n            \"phraseTime\": \"Время: {0}\",\n            \"warnEpCompat\": \"Данный эпизод создан для другой ветки SMBX и может работать неправильно.\"\n        },\n        \"loading\": \"Загрузка...\",\n        \"main\": {\n            \"main1PlayerGame\": \"Одиночная игра\",\n            \"mainBattleGame\": \"Битва\",\n            \"mainEditor\": \"Редактор\",\n            \"mainExit\": \"Выйти\",\n            \"mainMultiplayerGame\": \"Игра вдвоём\",\n            \"mainOptions\": \"Настройки\",\n            \"mainPlayEpisode\": \"Сыграть в эпизод\"\n        },\n        \"options\": {\n            \"restartEngine\": \"Перезагрузите игру, чтобы применить изменения.\",\n            \"advanced\": {\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"Увеличьте, чтобы уменьшить обрывы, но при этом звук будет сильнее отставать\",\n                    \"_name\": \"Размер буфера\"\n                },\n                \"audio-sample-rate\": {\n                    \"11025\": \"11025 Гц\",\n                    \"32000\": \"32000 Гц\",\n                    \"44100\": \"44100 Гц\",\n                    \"22050\": \"22050 Гц\",\n                    \"48000\": \"48100 Гц\",\n                    \"16000\": \"16000 Гц\",\n                    \"_name\": \"Частота дискретизации\"\n                },\n                \"audio-channels\": {\n                    \"_name\": \"Каналы\",\n                    \"mono\": \"Моно\",\n                    \"stereo\": \"Стерео\"\n                },\n                \"log-level\": {\n                    \"_name\": \"Уровень отчётов\",\n                    \"none\": \"Откл.\",\n                    \"critical\": \"Критические\",\n                    \"debug\": \"Отладка\",\n                    \"fatal\": \"Фатальные\",\n                    \"info\": \"Инфо\",\n                    \"warning\": \"Предупреждения\"\n                },\n                \"render\": {\n                    \"hw\": \"Авто\",\n                    \"opengles-tip\": \"Мобильный интерфейс, поддерживающий все новые эффекты и высокую точность SMBX64\",\n                    \"sw\": \"Программный\",\n                    \"_name\": \"Режим отрисовки\",\n                    \"opengl-tip\": \"Настольный интерфейс с поддержкой всех графических эфектов SMBX64 с максимальной точностью\",\n                    \"opengl11-tip\": \"Устаревший настольный интерфейс с точной поддержкой SMBX64\",\n                    \"opengl\": \"OpenGL 3+\",\n                    \"opengles\": \"OpenGL ES 2+\",\n                    \"opengles11-tip\": \"Устаревший мобильный интерфейс с полной поддержкой SMBX64\",\n                    \"sdl\": \"SDL2\",\n                    \"sdl-tip\": \"Базовый кросс-платформенный интерфейс отрисовки\",\n                    \"opengles11\": \"OpenGL ES 1.1+\",\n                    \"opengl11\": \"OpenGL 1.1\"\n                },\n                \"scale-down-textures\": {\n                    \"all\": \"Всё\",\n                    \"all-tip\": \"Меньше тормозов при загрузке, чем \\\"Безопасно\\\"\",\n                    \"_name\": \"Уменьшать картинки\",\n                    \"none\": \"Не уменьшать\",\n                    \"none-tip\": \"Меньше всего тормозов при загрузке\",\n                    \"safe\": \"Безопасно\",\n                    \"_tooltip\": \"Для экономии оперативной памяти, картинки будут уменьшаться в два раза при загрузке\",\n                    \"safe-tip\": \"Проверяет, является ли картинка увеличенного в 2 раза формата\"\n                },\n                \"_header\": \"Продвинутое\",\n                \"_tooltip\": \"Технические настройки для внутренней работы\",\n                \"advanced-audio\": \"Аудио\",\n                \"advanced-video\": \"Видео\",\n                \"audio-enable\": \"Включить\",\n                \"audio-format\": {\n                    \"_name\": \"Формат аудио\",\n                    \"_tooltip\": \"Формат для звукового драйвера\"\n                },\n                \"choose-assets-on-launch\": \"Выбирать игру при запуске\",\n                \"record-gameplay-data\": \"Запись игрового процесса\",\n                \"webm-recording\": \"Запись WebM\",\n                \"internal-res-4p\": {\n                    \"_name\": \"Размер разделения экрана на 4И\",\n                    \"default\": \"По-умолчанию\",\n                    \"fhd\": \"1920x1080 (FHD)\",\n                    \"qhd\": \"2560x1440 (QHD)\",\n                    \"qsmbx\": \"1600x1200 (QSMBX)\"\n                },\n                \"use-native-osk\": \"Системная экранная клавиатура\",\n                \"inaccurate-gifs-tip\": \"Разрешает 3D-эффект в присутствии GIF-текстур\",\n                \"inaccurate-gifs\": \"Неточные GIFы\",\n                \"fullscreen-type\": {\n                    \"desktop\": \"Растяжение\",\n                    \"desktop-tip\": \"Окно будет растянуто без изменения разрешения экрана\",\n                    \"exclusive\": \"Весь экран\",\n                    \"auto\": \"Авто\",\n                    \"exclusive-tip\": \"Захват всего экрана со сменой видео-режима\",\n                    \"_name\": \"Тип полного экрана\"\n                },\n                \"fullscreen-depth\": {\n                    \"16\": \"16-бит\",\n                    \"32\": \"32-бит\",\n                    \"_name\": \"Глубина цвета в п/э режиме\",\n                    \"auto\": \"Авто\"\n                },\n                \"fullscreen-res\": \"Разрешение п/э\"\n            },\n            \"audio\": {\n                \"_header\": \"Аудио\",\n                \"audio-music-volume\": \"Громкость музыки\",\n                \"audio-prefs\": \"Свойства\",\n                \"audio-sfx-volume\": \"Громкость звука\",\n                \"sfx-audio-fx\": \"Эхо-эфекты\",\n                \"sfx-modern\": \"Новые звуки\",\n                \"sfx-spatial-audio\": \"Пространственный звук\",\n                \"sfx-modern-tip\": \"Использовать новые звуки, добавленные в TheXTech\",\n                \"sfx-pet-beat\": \"Тема питомца\",\n                \"sfx-spatial-audio-tip\": \"Глушить громкость заэкранных источников\",\n                \"sfx-pet-beat-tip\": \"Проигрывать особую музыкальную тему во время верховой езды на питомце\"\n            },\n            \"compatibility\": {\n                \"_header\": \"Настройки сеанса\",\n                \"reset-all\": \"Сбросить всё\",\n                \"bugfixes\": \"Исправления ошибок\",\n                \"_tooltip\": \"Настройки совместимости или отладки\",\n                \"autocode\": \"Автокод\",\n                \"features\": \"Возможности\"\n            },\n            \"controls\": {\n                \"_header\": \"Управление\"\n            },\n            \"episode-options\": {\n                \"_header\": \"Настройки эпизода\",\n                \"creator-compat\": {\n                    \"_tooltip\": \"Калибровка логики от авторов контента\",\n                    \"enable\": \"Включить\",\n                    \"_name\": \"Калибровка от авторов\",\n                    \"disable\": \"Выключить\",\n                    \"file-only\": \"Только файл\"\n                },\n                \"playstyle\": {\n                    \"_name\": \"Стиль игры\",\n                    \"classic\": \"Классический\",\n                    \"classic-tip\": \"Минимум обновлений\",\n                    \"modern\": \"Современный\",\n                    \"modern-tip\": \"Все обновления\",\n                    \"vanilla\": \"Ванильный\",\n                    \"vanilla-tip\": \"Без обновлений\"\n                },\n                \"hub-resume\": \"Запоминать проход в корридорах\",\n                \"hub-resume-tip\": \"Позволяет возобновить игру у недавнего прохода, куда вы входили\"\n            },\n            \"main\": {\n                \"four-screen-mode\": {\n                    \"split\": \"Разбивка\",\n                    \"_name\": \"Режим экрана для 4И\",\n                    \"shared\": \"Общий\"\n                },\n                \"frame-skip\": \"Пропуск кадров\",\n                \"discord-rpc\": \"Интеграция с Discord\",\n                \"language\": \"Язык\",\n                \"unlimited-framerate\": \"Неограниченная частота кадров\",\n                \"vsync\": \"Верт. синхронизация\",\n                \"_header\": \"Основные\",\n                \"background-work\": \"Фоновая работа\",\n                \"background-work-tip\": \"В игру можно будет играть джойстиком даже при отсутствии фокуса\",\n                \"multiplayer\": \"Несколько игроков\",\n                \"timing\": \"Синхронизация кадров\",\n                \"two-screen-mode\": {\n                    \"_name\": \"Режим экрана для 2И\",\n                    \"shared\": \"Общий\",\n                    \"smbx\": \"Как в SMBX\",\n                    \"split\": \"Лево/Право\",\n                    \"topbottom\": \"Верх/Низ\"\n                }\n            },\n            \"video\": {\n                \"_header\": \"Видео\",\n                \"battery-status\": {\n                    \"_name\": \"Состояние батареи устройства\",\n                    \"fullscreen\": \"Полный экран\",\n                    \"low\": \"Низкий заряд\",\n                    \"off\": \"Скрыт\",\n                    \"fullscreen-tip\": \"Показать только в полноэкранном режиме\",\n                    \"low-tip\": \"Показать, когда заряд будет низкий\",\n                    \"on\": \"Всегда\"\n                },\n                \"info-run\": \"Информация об игровом процессе\",\n                \"internal-res\": {\n                    \"3ds\": \"800x480 (3DS)\",\n                    \"_name\": \"Размер экрана\",\n                    \"smbx-wide\": \"SMBX (широкий)\",\n                    \"gba\": \"480x320 (GBA)\",\n                    \"_tooltip\": \"Разрешение игрового поля\",\n                    \"dynamic\": \"Динамическое\",\n                    \"hd\": \"1280x720 (HD)\",\n                    \"snes\": \"512x448 (SNES)\",\n                    \"vga\": \"640x480 (VGA)\",\n                    \"hello\": \"768x432 (HELLO)\",\n                    \"nds\": \"512x384 (NDS)\",\n                    \"smbx\": \"800x600 (SMBX)\"\n                },\n                \"scale-mode\": {\n                    \"integer\": \"Целочисленный\",\n                    \"linear\": \"Сглаженный\",\n                    \"nearest\": \"Чёткий\",\n                    \"full\": \"Полный\",\n                    \"center\": \"Центр\",\n                    \"_name\": \"Режим масштабирования\",\n                    \"2x\": \"2x\",\n                    \"0_5x\": \"0.5x\",\n                    \"1x\": \"1x\",\n                    \"3x\": \"3x\"\n                },\n                \"show-episode-title\": {\n                    \"bottom\": \"Снизу\",\n                    \"bottom-tip\": \"Показать над секундомером\",\n                    \"top\": \"Сверху\",\n                    \"off\": \"Скрыто\",\n                    \"top-tip\": \"Отобразить над индикаторной панелью\",\n                    \"_name\": \"Название эпизода\"\n                },\n                \"3d-compat-mode\": \"Режим совместимости 3D\",\n                \"show-medals-counter\": \"Показать счётчик медалей\",\n                \"show-medals-counter-tip\": \"Отобразить медали на индикаторной панели и над входом на уровень\",\n                \"show-playtime-counter\": {\n                    \"_name\": \"Счётчик игрового времени\",\n                    \"animated\": \"Анимированный\",\n                    \"off\": \"Выключен\",\n                    \"off-tip\": \"Отображать только в режиме прохождения на скорость\",\n                    \"opaque\": \"Непрозрачный\",\n                    \"transparent\": \"Прозрачный\",\n                    \"animated-tip\": \"Добавляет радужный эффект в конце уровня\",\n                    \"_tooltip\": \"Отображает время, потраченное на прохождение эпизода и текущую попытку\"\n                },\n                \"show-screen-shake\": \"Встряски экрана\",\n                \"info-meta\": \"Мета-информация\",\n                \"show-fails-counter-tip\": \"Отображает счётчик неудач за весь эпизод и за текущий уровень\",\n                \"effects\": \"Экранные эфекты\",\n                \"display-controllers\": \"Состояние контроллера\",\n                \"fullscreen\": \"Полный экран\",\n                \"info-game\": \"Состояние игры\",\n                \"enable-inter-level-fade-effect\": \"Плавные переходы\",\n                \"3d-compat-mode-tip\": \"Отрисовать все объекты на одном 3D-плане\",\n                \"show-fails-counter\": \"Показать счётчик неудач\",\n                \"show-fps\": \"Частота кадров\",\n                \"video-system\": \"Система\",\n                \"world-map-stars-show-tip\": \"Отображает собранные звёзды над уровнями\",\n                \"world-map-stars-show\": \"Показывать звёзды на карте мира\"\n            },\n            \"view-credits\": {\n                \"_header\": \"Показать титры\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"0\": \"0 (Выкл.)\",\n                    \"1\": \"1 (Соврем.)\",\n                    \"2\": \"2 (Классич.)\",\n                    \"3\": \"3 (Ориг.)\",\n                    \"_name\": \"Режим прохождения на скорость\"\n                }\n            }\n        },\n        \"wordBack\": \"Назад\",\n        \"wordHide\": \"Скрыть\",\n        \"wordNo\": \"Нет\",\n        \"wordOff\": \"Выкл.\",\n        \"wordOn\": \"Вкл.\",\n        \"wordPlayer\": \"Игрок\",\n        \"wordProfile\": \"Профиль\",\n        \"wordResume\": \"Возобновить\",\n        \"wordShow\": \"Показать\",\n        \"wordWaiting\": \"Ждём\",\n        \"wordYes\": \"Да\"\n    },\n    \"outro\": {\n        \"cppPortDevelopers\": \"Разработчики C++-порта:\",\n        \"customSprites\": \"Доп-спрайты:\",\n        \"engineCredits\": \"Создатели движка:\",\n        \"gameCredits\": \"Создатели игры:\",\n        \"levelDesign\": \"Дизайн уровней:\",\n        \"nameAndrewSpinks\": \"Эндрю Спинкс\",\n        \"nameVitalyNovichkov\": \"Новичков Виталий\",\n        \"originalBy\": \"Автор оригинала на VB6:\",\n        \"psVitaPortBy\": \"Автор порта на PS Vita:\",\n        \"qualityControl\": \"Контроль качества:\",\n        \"specialThanks\": \"Особые благодарности:\"\n    },\n    \"pluralRules\": \"slavic\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_ta.json",
    "content": "{\n    \"editor\": {\n        \"block\": {\n            \"canBreak\": \"உடையும்\",\n            \"letterHeight\": \"ம\",\n            \"letterWidth\": \"W\",\n            \"canBreakTooltip\": \"மரபு: அடிக்கும்போது உடைகிறது\",\n            \"inside\": \"உள்ளே:\",\n            \"invis\": \"இன்விச்\",\n            \"pickContents\": \"தொகுதி உள்ளடக்கங்களைத் தேர்ந்தெடுங்கள்\",\n            \"slick\": \"மென்மை\"\n        },\n        \"events\": {\n            \"bounds\": {\n                \"shouldEvent\": \"நிகழ்வு {0}\",\n                \"changeSectionBoundsToCurrent\": \"பிரிவு {0} வரம்புகளை மின்னோட்டத்திற்கு மாற்றவா?\",\n                \"changeAllSectionBoundsToCurrent\": \"அனைத்து பிரிவு வரம்புகளையும் மின்னோட்டத்திற்கு மாற்றவா?\"\n            },\n            \"headerTriggerEvent\": \"தூண்டுதல்:\",\n            \"itemNewEvent\": \"<புதிய நிகழ்வு>\",\n            \"label\": {\n                \"destroy\": \"அழிக்கவும்\",\n                \"enter\": \"உள்ளிடவும்\",\n                \"hit\": \"செய்\",\n                \"layerClear\": \"அடுக்கு தெளிவாக\",\n                \"next\": \"அடுத்தது\",\n                \"talk\": \"பேச்சு\",\n                \"activate\": \"செயல்படுத்து\",\n                \"death\": \"இறப்பு\"\n            },\n            \"layers\": {\n                \"headerHide\": \"மறை:\",\n                \"headerMove\": \"நகர்த்து:\",\n                \"headerShow\": \"காட்டு:\",\n                \"headerToggle\": \"தடுமாறுங்கள்:\"\n            },\n            \"letter\": {\n                \"enter\": \"இ:\",\n                \"hit\": \"எச்:\",\n                \"activate\": \"அ:\",\n                \"death\": \"ம:\",\n                \"destroy\": \"ஒ:\",\n                \"layerClear\": \"எல்:\",\n                \"talk\": \"பே:\"\n            },\n            \"promptEventName\": \"நிகழ்வு பெயர்\",\n            \"promptEventText\": \"நிகழ்வு உரை\",\n            \"props\": {\n                \"controls\": \"கட்டுப்பாடுகள்\",\n                \"endGame\": \"இறுதி விளையாட்டு\",\n                \"layerSmoke\": \"புகை\",\n                \"autostart\": \"ஆட்டோச்டார்ட்\",\n                \"sound\": \"ஒலி\"\n            },\n            \"sections\": {\n                \"propBackground\": \"பி.சி.\",\n                \"actionKeep\": \"வைத்திருங்கள்\",\n                \"actionReset\": \"மீட்டமை\",\n                \"actionSet\": \"கணம்\",\n                \"phraseAllSections\": \"அனைத்து பிரிவுகளும்\",\n                \"propBounds\": \"எல்லைகள்\",\n                \"propMusic\": \"இசை\"\n            },\n            \"controlsForEventN\": \"நிகழ்வுக்கான கட்டுப்பாடுகள் {0}\",\n            \"deletion\": {\n                \"cancel\": \"இல்லை: நிகழ்வை நீக்க வேண்டாம்\",\n                \"confirm\": \"ஆம்: நிகழ்வை நீக்கு\",\n                \"deletingEvent\": \"நிகழ்வை நீக்குதல் {0}\"\n            },\n            \"desc\": {\n                \"activate\": \"NPC திரையில் நுழைகிறது\",\n                \"death\": \"NPC இறந்துவிடுகிறது\",\n                \"destroy\": \"தொகுதி அழிக்கப்படுகிறது\",\n                \"enter\": \"வார்ப் நுழைந்தது\",\n                \"hit\": \"தொகுதி செய் பெறுகிறது\",\n                \"phraseTriggersAfterTemplate\": \"{0} தூண்டுதல்கள் {1} எம்.எச். நிகழ்வுக்குப் பிறகு {2} நிகழ்கிறது\",\n                \"phraseTriggersWhenTemplate\": \"{0} {1} போது தூண்டுகிறது\",\n                \"layerClear\": \"பொருள் அடுக்கில் உள்ள அனைத்தும் போய்விட்டன\",\n                \"talk\": \"வீரர் NPC உடன் பேசுகிறார்\"\n            },\n            \"header\": \"நிகழ்வுகள்:\",\n            \"settingsForEvent\": \"நிகழ்வுக்கான அமைப்புகள் {0}\"\n        },\n        \"file\": {\n            \"actionClearLevel\": \"தெளிவான நிலை\",\n            \"commandSave\": \"சேமி\",\n            \"actionExit\": \"வெளியேறு\",\n            \"actionOpen\": \"திற\",\n            \"actionRevert\": \"மாற்றியமைக்கவும்\",\n            \"commandNew\": \"புதிய\",\n            \"commandOpen\": \"திறந்த ...\",\n            \"commandSaveAs\": \"சேமி ...\",\n            \"confirmConfirmAction\": \"நீங்கள் நிச்சயமாக {0} செய்ய விரும்புகிறீர்களா?\",\n            \"confirmConvertFormatTo\": \"வடிவத்தை {0} ஆக மாற்றவா?\",\n            \"formatLegacy\": \"மரபு\",\n            \"optionActionWithoutSave\": \"{0} சேமிக்காமல்\",\n            \"optionYesSaveThenAction\": \"ஆம்: சேமி {0}\",\n            \"sectionLevel\": \"நிலை\",\n            \"sectionWorld\": \"உலகம்\",\n            \"actionClearWorld\": \"உலகத்தை அழிக்கவும்\",\n            \"confirmSaveBeforeAction\": \"உங்களுக்கு முன் சேமிக்கவும் {0}?\",\n            \"convert\": {\n                \"descNew\": \"கோப்பு நீட்டிப்பு மாறும், ஆனால் பழைய கோப்பு நீக்கப்படாது.\\n\\n சேமித்த பிறகு இழந்த அம்சங்களை சரிபார்க்கவும்.\"\n            },\n            \"formatModern\": \"நவீன\",\n            \"labelCurrentFile\": \"தற்போதைய கோப்பு:\",\n            \"labelFormat\": \"வடிவம்:\",\n            \"optionCancelAction\": \"ரத்துசெய்: {0} வேண்டாம்\",\n            \"optionCancelConversion\": \"மாற்றத்தை ரத்துசெய்\",\n            \"optionProceedWithConversion\": \"மாற்றத்துடன் தொடரவும்\"\n        },\n        \"labelSortLayer\": \"வரிசை அடுக்கு:\",\n        \"labelSortOffset\": \"வரிசைப்படுத்தல் ஆஃப்செட்:\",\n        \"layers\": {\n            \"deletion\": {\n                \"cancel\": \"ரத்துசெய்: அடுக்கை நீக்க வேண்டாம்\",\n                \"confirmDelete\": \"இல்லை: *எல்லா உள்ளடக்கங்களையும் நீக்கு *\",\n                \"confirmPreserve\": \"ஆம்: இயல்புநிலை அடுக்குக்கு நகர்த்தவும்\",\n                \"header\": \"அடுக்கு நீக்குதல் {0}\",\n                \"preserveLayerContents\": \"அடுக்கு உள்ளடக்கங்களைப் பாதுகாக்கவா?\"\n            },\n            \"labelAttachedLayer\": \"இணைக்கப்பட்ட அடுக்கு:\",\n            \"labelMoveLayer\": \"அடுக்கை நகர்த்தவும்:\",\n            \"promptLayerName\": \"அடுக்கு பெயர்\",\n            \"abbrevAttLayer\": \"க்கு:\",\n            \"default\": \"இயல்புநிலை\",\n            \"header\": \"அடுக்குகள்:\",\n            \"itemNewLayer\": \"<புதிய அடுக்கு>\",\n            \"label\": \"அடுக்கு:\",\n            \"desc\": {\n                \"att\": \"NPC நகரும் போதெல்லாம், இணைக்கப்பட்ட அடுக்கு அதைப் பின்பற்றுகிறது\"\n            },\n            \"labelAttached\": \"இணைக்கப்பட்டுள்ளது:\"\n        },\n        \"letterCoordX\": \"ஃச்\",\n        \"npc\": {\n            \"ai\": {\n                \"target\": \"இலக்கு\",\n                \"use1_0Ai\": \"1.0 பயன்படுத்தவா?\",\n                \"LR\": \"எல்.ஆர்\",\n                \"UD\": \"Ud\",\n                \"aiIs\": \"AI: {0}\",\n                \"headerCustomAi\": \"தனிப்பயன் AI:\",\n                \"jump\": \"தாவு\",\n                \"leap\": \"பாய்ச்சல்\",\n                \"swim\": \"நீச்சல்\"\n            },\n            \"gen\": {\n                \"direction\": \"திசை\",\n                \"effectWarp\": \"வார்ப்\",\n                \"header\": \"செனரேட்டர் அமைப்புகள்\",\n                \"effectIs\": \"விளைவு: {0}\",\n                \"effectShoot\": \"தண்டுக் கிளை\"\n            },\n            \"inContainer\": \"இல்\",\n            \"abbrevGen\": \"கென்\",\n            \"inertNice\": \"நல்லது\",\n            \"props\": {\n                \"active\": \"செயலில்\",\n                \"attachSurface\": \"இணைக்கவும்\",\n                \"facing\": \"எதிர்கொள்ளும்\"\n            },\n            \"stuckStop\": \"நிறுத்து\",\n            \"tooltipExpandSection\": \"பிரிவு விரிவாக்க\"\n        },\n        \"phraseGenericIndex\": \"எண் {0}\",\n        \"phraseRadiusIndex\": \"ஆரம் {0}\",\n        \"phraseSectionIndex\": \"பிரிவு {0}\",\n        \"phraseTextOf\": \"{0} உரை\",\n        \"section\": {\n            \"scroll\": \"சுருள்\",\n            \"setBounds\": \"எல்லைகளை அமைக்கவும்\",\n            \"underwater\": \"நீருக்கடியில்\",\n            \"horizWrap\": \"மஞ்சள். மடக்கு\",\n            \"vertWrap\": \"வெர்ட். மடக்கு\",\n            \"noTurnBack\": \"திரும்பவில்லை\",\n            \"offscreenExit\": \"ஆஃப்ச்கிரீன் வெளியேறுதல்\"\n        },\n        \"select\": {\n            \"pathBlankUnlock\": \"பாதை {0} திறக்கவும்\",\n            \"worldMusic\": \"உலக இசை\",\n            \"allSectPropBlankForEventN\": \"{1} க்கு அனைத்து தேர்வு {0}\",\n            \"sectBlankPropBlankForEventN\": \"{2} க்கு தேர்வு {0} {1}\",\n            \"sectionBlankPropBlank\": \"பிரிவு {0} {1}\",\n            \"soundForEventN\": \"நிகழ்வுக்கான ஒலி {0}\",\n            \"warpTransitEffect\": \"வார்ப் மாற்றம் விளைவு\"\n        },\n        \"testPlay\": {\n            \"boot\": \"துவக்க\",\n            \"char\": \"சார்\",\n            \"magicHand\": \"மேசிக் கை\",\n            \"power\": \"விசை\",\n            \"pet\": \"செல்லப்பிள்ளை\"\n        },\n        \"tooltip\": {\n            \"BGOs\": \"BGOS\",\n            \"NPCs\": \"NPCS\",\n            \"scenes\": \"காட்சிகள்\",\n            \"area\": \"பகுதி\",\n            \"blocks\": \"தொகுதிகள்\",\n            \"erase\": \"அழிக்கவும்\",\n            \"eraseAll\": \"அனைத்தையும் அழிக்கவும்\",\n            \"file\": \"கோப்பு\",\n            \"events\": \"நிகழ்வுகள்\",\n            \"layers\": \"அடுக்குகள்\",\n            \"levels\": \"நிலைகள்\",\n            \"music\": \"இசை\",\n            \"paths\": \"பாதைகள்\",\n            \"select\": \"தேர்ந்தெடு\",\n            \"settings\": \"அமைப்புகள்\",\n            \"show\": \"காட்டு\",\n            \"tiles\": \"ஓடுகள்\",\n            \"warps\": \"வார்ப்ச்\",\n            \"water\": \"நீர்\"\n        },\n        \"warp\": {\n            \"cannonExit\": \"பீரங்கி வெளியேறுதல்\",\n            \"dir\": \"இயக்குனர்\",\n            \"item\": \"உருப்படி\",\n            \"lvlWarp\": \"எல்விஎல் வார்ப்\",\n            \"showStartScene\": \"தொடக்க காட்சியைக் காட்டு\",\n            \"speed\": \"விரைவு\",\n            \"style\": {\n                \"blipInstant\": \"தட்டச்சு\",\n                \"door\": \"கதவு\",\n                \"pipe\": \"புழம்பு\",\n                \"portal\": \"துறைமுகம்\",\n                \"style\": \"ச்டைல்:\"\n            },\n            \"transit\": {\n                \"name2\": \"மங்கல்\",\n                \"name0\": \"எதுவுமில்லை\",\n                \"name1\": \"சுருள்\",\n                \"name3\": \"வட்டம்\",\n                \"name4\": \"(ம) புரட்டல்\",\n                \"name5\": \"புரட்டுதல் (வி)\"\n            },\n            \"allow\": \"அனுமதிக்கவும்:\",\n            \"effect\": \"விளைவு:\",\n            \"in\": \"இல்\",\n            \"needFloor\": \"தளம் தேவை\",\n            \"needKey\": \"திறவுகோல் தேவை\",\n            \"needStarCount\": \"{0} {1} தேவை\",\n            \"out\": \"வெளியே\",\n            \"placing\": \"வைப்பது:\",\n            \"ride\": \"சவாரி\",\n            \"showStarCount\": \"நட்சத்திர எண்ணிக்கையைக் காட்டு\",\n            \"starLockMessage\": \"ச்டார் லாக் எம்.எச்.சி.\",\n            \"target\": \"இலக்கு:\",\n            \"title\": \"வார்ப் அமைப்புகள்\",\n            \"to\": \"க்கு: {0}\",\n            \"toMap\": \"வரைபடத்திற்கு\",\n            \"twoWay\": \"இரு வழி\"\n        },\n        \"wordEvent\": {\n            \"genitive\": \"நிகழ்வு\",\n            \"nominative\": \"நிகழ்வு\",\n            \"typeLabel\": \"{0} நிகழ்வு:\"\n        },\n        \"browser\": {\n            \"itemNewFolder\": \"<புதிய கோப்புறை>\",\n            \"newFile\": \"புதிய கோப்பு\",\n            \"askOverwriteFile\": \"மேலெழுதும் {0}?\",\n            \"itemNewFile\": \"<புதிய கோப்பு>\",\n            \"openFile\": \"கோப்பை திற\",\n            \"saveFile\": \"கோப்பை சேமி\"\n        },\n        \"letterCoordY\": \"ஒய்\",\n        \"letterDown\": \"டி\",\n        \"level\": {\n            \"startPos\": \"POS ஐத் தொடங்குங்கள்\",\n            \"alwaysVis\": \"எப்போதும் காண்\",\n            \"bigBG\": \"பெரிய பின்னணி\",\n            \"gameStart\": \"விளையாடத் தொடக்கு\",\n            \"levelName\": \"நிலை பெயர்\",\n            \"pathBG\": \"பாதை பின்னணி\",\n            \"pathUnlocks\": \"பாதை திறத்தல்\"\n        },\n        \"mapPos\": \"வரைபடம் போச்:\",\n        \"letterLeft\": \"எல்\",\n        \"letterRight\": \"ஆர்\",\n        \"letterUp\": \"உ\",\n        \"pageBlankOfBlank\": \"{1} இன் பக்கம் {0}\",\n        \"phraseAreYouSure\": \"நீங்கள் உறுதியாக இருக்கிறீர்களா?\",\n        \"phraseCountMore\": \"{0} மேலும்\",\n        \"phraseDelayIsMs\": \"தாமதம்: {0} எம்.எச்\",\n        \"phraseWarpIndex\": \"வார்ப் {0}\",\n        \"toggleMagicBlock\": \"மேசிக் தொகுதி\",\n        \"water\": {\n            \"title\": \"நீர் அமைப்புகள்\"\n        },\n        \"wordCoins\": \"நாணயங்கள்\",\n        \"wordEnabled\": \"இயக்கப்பட்டது\",\n        \"wordHeight\": \"உயரம்\",\n        \"wordInstant\": \"உடனடி\",\n        \"wordMode\": \"பயன்முறை\",\n        \"wordNPC\": {\n            \"genitive\": \"NPC\",\n            \"nominative\": \"NPC\"\n        },\n        \"wordText\": \"உரை\",\n        \"wordWidth\": \"அகலம்\",\n        \"world\": {\n            \"allowChars\": \"எழுத்துக்களை அனுமதிக்கவும்\",\n            \"hubWorld\": \"மைய உலகம்\",\n            \"introLevel\": \"அறிமுக நிலை\",\n            \"name\": \"உலக பெயர்\",\n            \"phraseCreditIndex\": \"உலக கடன் வரி {0}:\",\n            \"retryOnFail\": \"தோல்வியுற்றதை மீண்டும் முயற்சிக்கவும்\",\n            \"totalStars\": \"மொத்த நட்சத்திரங்கள்:\"\n        }\n    },\n    \"game\": {\n        \"connect\": {\n            \"phraseDropPX\": \"P ஐ விடுங்கள் p {0}\",\n            \"phraseStartToForceRes\": \"விண்ணப்பத்தை கட்டாயப்படுத்தத் தொடங்குங்கள்\",\n            \"dropAddTitle\": \"வீரர்களை கைவிடவும்/சேர்க்கவும்\",\n            \"phraseChangeChar\": \"கரியை மாற்றவும்\",\n            \"phraseDropMe\": \"என்னை விடுங்கள்\",\n            \"phraseForceResume\": \"படை மீண்டும் தொடங்குகிறது\",\n            \"phraseHoldStart\": \"தொடக்கத்தை வைத்திருங்கள்\",\n            \"phrasePressAButton\": \"ஒரு பொத்தானை அழுத்தவும்\",\n            \"phraseSetControls\": \"கட்டுப்பாடுகளை அமைக்கவும்\",\n            \"phraseTestControls\": \"சோதனை கட்டுப்பாடுகள்\",\n            \"phraseStartToResume\": \"மீண்டும் தொடங்கத் தொடங்குங்கள்\",\n            \"phraseTestProfile\": \"சோதனை சுயவிவரம்\",\n            \"phraseWaitingForInput\": \"உள்ளீட்டு சாதனத்திற்காக காத்திருக்கிறது ...\",\n            \"reconnectTitle\": \"மீண்டும் இணைக்கவும்\",\n            \"splitPressSelect_1\": \"தேர்வு என்பதை அழுத்தவும்\",\n            \"splitPressSelect_2\": \"விருப்பங்களை கட்டுப்படுத்துகிறது\",\n            \"wordDisconnect\": \"துண்டிக்கவும்\"\n        },\n        \"error\": {\n            \"errorNoStartPoint\": \"கிடைக்கக்கூடிய தொடக்க புள்ளிகள் அல்லது நுழைவு வார்ப் குறிப்பிடப்படாததால் மட்டத்தைத் தொடங்க முடியாது.\\n\\n கோப்பு: {0}\",\n            \"errorInvalidEnterWarp\": \"தவறான நுழைவு வார்ப் {1} குறிப்பிடப்பட்டதால் மட்டத்தைத் தொடங்க முடியாது.\\n மொத்த வார்ப் உள்ளீடுகள்: {2}\\n\\n கோப்பு: {0}\",\n            \"openFileFailed\": \"\\\"{0}\\\" ஐ திறக்க முடியாது: கோப்பு இல்லை அல்லது சிதைக்கப்படவில்லை.\",\n            \"warpNeedStarCount\": \"நுழைய உங்களுக்கு {0} {1} தேவை.\",\n            \"errorTooOldGameAssets\": \"உள்ளடக்கத்திற்கு தற்போதைய சொத்து தொகுப்பை ({1}) விட புதிய சொத்து தொகுப்பு அம்ச நிலை ({0}) தேவை. உங்கள் கேம் அசெட் பேக்கை மேம்படுத்தவும்.\",\n            \"openIPCDataFailed\": \"ஊழல் அல்லது பிற பிழைகள் காரணமாக பெறப்பட்ட கோப்புத் தரவைத் தொடர முடியாது.\",\n            \"IPCTimeOut\": \"இணைக்கப்பட்ட எடிட்டரிடமிருந்து பதில் இல்லை. விளையாட்டு மூடப்படும்.\",\n            \"errorTooOldEngine\": \"உள்ளடக்கத்திற்கு தற்போதைய பதிப்பை ({1}) விட புதிய எஞ்சின் அம்ச நிலை ({0}) தேவை. தயவுசெய்து TheXTech ஐப் புதுப்பிக்கவும்.\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"அளவை ச்கேனிங் ...\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0} m {1} s\"\n        },\n        \"msgbox\": {\n            \"sysInfoError\": \"பிழை!\",\n            \"sysInfoTitle\": \"தகவல்\",\n            \"sysInfoWarning\": \"எச்சரிக்கை!\"\n        },\n        \"hint\": {\n            \"pound-altrun\": \"கீழ்நோக்கி பவுண்டுக்கு ஆல்ட் ரன் அழுத்தவும்!\",\n            \"pound-down\": \"கீழ்நோக்கி பவுண்டுக்கு கீழே அழுத்தவும்!\",\n            \"no-lives-new\": \"நாணயங்கள் எதுவும் இல்லை? கடன் எடுத்துக் கொள்ளுங்கள்! உங்கள் மதிப்பெண்ணைப் பாருங்கள் ...\",\n            \"no-lives-old\": \"கவனமாக இருங்கள் - கேம் ஓவர் உடனடி!\",\n            \"rainbow-surf\": \"பிடிக்கவும், ஓடுங்கள், பிடிக்கவும், சர்ஃப் செல்லவும்.\",\n            \"char5-bombs\": \"சேகரிக்க ரன் அழுத்தவும், வீசவும் ஆல்ட் ரன்.\",\n            \"heavy-duck\": \"பெரும்பாலானவர்களைத் தடுக்க வாத்து - ஆனால் அனைத்தும் இல்லை - தீப்பிழம்புகள்!\",\n            \"shoe-block\": \"இந்த தொகுதிகளை மிகவும் அணிந்துகொள்வது - ஆனால் அனைத்தும் இல்லை - தீப்பிழம்புகள்!\",\n            \"gray-bricks\": \"சில தொகுதிகள் சிறப்பு சக்திகளுக்கு பாதிக்கப்படக்கூடியவை.\"\n        },\n        \"pause\": {\n            \"continue\": \"தொடரவும்\",\n            \"playerSetup\": \"பிளேயர் அமைப்பு\",\n            \"enterCode\": \"குறியீட்டை உள்ளிடவும்\",\n            \"quit\": \"வெளியேறு\",\n            \"quitTesting\": \"பரிசோதனையை விட்டுவிடுங்கள்\",\n            \"resetCheckpoints\": \"சோதனைச் சாவடிகளை மீட்டமைக்கவும்\",\n            \"restartLevel\": \"அளவை மறுதொடக்கம் செய்யுங்கள்\",\n            \"returnToEditor\": \"எடிட்டருக்குத் திரும்பு\",\n            \"saveAndContinue\": \"சேமித்து தொடரவும்\",\n            \"saveAndQuit\": \"சேமித்து வெளியேறவும்\"\n        },\n        \"screenPaused\": \"இடைநிறுத்தப்பட்டது\",\n        \"loader\": {\n            \"statusLoadData\": \"தரவை ஏற்றுகிறது...\",\n            \"statusGameInfo\": \"விளையாட்டு செய்தி\",\n            \"statusLoadFile\": \"ஏற்ற: {0}\",\n            \"statusFinishing\": \"முடிக்கிறது...\",\n            \"statusAssetPacks\": \"சொத்து தொகுப்புகள்\",\n            \"statusTranslations\": \"மொழிபெயர்ப்புகள்\",\n            \"loading\": \"ஏற்றுகிறது...\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"முடிந்தது. ஆட்டம் தொடங்குகிறது...\",\n            \"dataAccepted\": \"தரவு ஏற்றுக்கொள்ளப்பட்டது, பாகுபடுத்தல் தொடங்கியது...\",\n            \"dataTransferStarted\": \"தரவு பரிமாற்றத்தைத் தொடங்கு...\",\n            \"errorTimeout\": \"பிழை: நேரம் முடிந்தது.\",\n            \"dataValid\": \"ஏற்றுக்கொள்ளப்பட்ட தரவு செல்லுபடியாகும்\",\n            \"waitingInput\": \"உள்ளீட்டு தரவுக்காக காத்திருக்கிறது...\"\n        }\n    },\n    \"menu\": {\n        \"controls\": {\n            \"buttons\": {\n                \"down\": \"கீழே\",\n                \"altJump\": \"ஆல்ட் சம்ப்\",\n                \"altRun\": \"அனைத்து ரன்\",\n                \"drop\": \"உருப்படியை கைவிடுங்கள்\",\n                \"jump\": \"தாவு\",\n                \"left\": \"இடது\",\n                \"right\": \"வலது\",\n                \"run\": \"ஓடு\",\n                \"start\": \"தொடங்கு\",\n                \"up\": \"மேலே\"\n            },\n            \"controlsReallyDeleteProfile\": \"உண்மையில் சுயவிவரத்தை நீக்கவா?\",\n            \"controlsTitle\": \"கட்டுப்பாடுகள்\",\n            \"hotkeys\": {\n                \"legacyPause\": \"பழைய இடைநிறுத்தம்\",\n                \"vanillaCam\": \"வெண்ணிலா கேம்\",\n                \"debugInfo\": \"பிழைத்திருத்த செய்தி\",\n                \"enterCheats\": \"குறியீட்டை உள்ளிடவும்\",\n                \"fullscreen\": \"முழு திரை\",\n                \"recordGif\": \"பதிவு gif\",\n                \"screenshot\": \"திரைக்காட்சி\",\n                \"toggleHUD\": \"HUD ஐ மாற்றவும்\"\n            },\n            \"options\": {\n                \"batteryStatus\": \"பேட்டர் நிலை\",\n                \"maxPlayers\": \"எம்எக்ச் பிளேயர்\",\n                \"rumble\": \"ரம்பிள்\",\n                \"textEntryStyle\": \"உரை நுழைவு நடை\",\n                \"altMenuControls\": \"மாற்று பட்டியல் கட்டுப்பாடுகள்\"\n            },\n            \"tDS\": {\n                \"buttonR\": \"ஆர்\",\n                \"buttonSelect\": \"தேர்ந்தெடு\",\n                \"buttonStart\": \"தொடங்கு\",\n                \"buttonZR\": \"Zr\",\n                \"cStick\": \"சி-ச்டிக்\",\n                \"buttonA\": \"ஏ\",\n                \"buttonB\": \"பி\",\n                \"buttonL\": \"எல்\",\n                \"buttonX\": \"ஃச்\",\n                \"buttonY\": \"ஒய்\",\n                \"buttonZL\": \"Zl\",\n                \"casePen\": \"(பேனா)\",\n                \"dPad\": \"டி-பேட்\",\n                \"tStick\": \"கட்டைவிரல்\"\n            },\n            \"touchscreen\": {\n                \"option\": {\n                    \"interfaceStyle\": \"இடைமுக நடை\",\n                    \"layoutStyle\": \"தளவமைப்பு நடை\",\n                    \"resetLayout\": \"தளவமைப்பை மீட்டமைக்கவும்\",\n                    \"feedbackLength\": \"கருத்து நீளம்\",\n                    \"holdRun\": \"தொடக்கத்தில் ரன் வைத்திருங்கள்\",\n                    \"feedbackStrength\": \"கருத்து வலிமை\",\n                    \"sStartSpacing\": \"எச்-ச்டார்ட் இடைவெளி\",\n                    \"scaleButtons\": \"அளவிலான பொத்தான்கள்\",\n                    \"scaleFactor\": \"அளவிலான காரணி\",\n                    \"scaleDPad\": \"அளவிலான டி-பேட்\",\n                    \"showCodeButton\": \"குறியீடு பொத்தானைக் காட்டு\"\n                },\n                \"layout\": {\n                    \"phoneOld\": \"தொலைபேசி (பழையது)\",\n                    \"standard\": \"தரநிலை\",\n                    \"longOld\": \"நீண்ட (பழைய)\",\n                    \"phabletOld\": \"பேப்லெட் (பழைய)\",\n                    \"tabletOld\": \"டேப்லெட் (பழையது)\",\n                    \"tight\": \"இறுக்கமான\",\n                    \"tinyOld\": \"சிறிய (பழைய)\"\n                },\n                \"style\": {\n                    \"ABXY\": \"Abxy\",\n                    \"XODA\": \"காட்\",\n                    \"actions\": \"செயல்கள்\"\n                }\n            },\n            \"wii\": {\n                \"classic\": {\n                    \"buttonLT\": \"Lt\",\n                    \"lStick\": \"எல்-பேட்\",\n                    \"buttonRT\": \"Rt\",\n                    \"buttonX\": \"ஃச்\",\n                    \"buttonY\": \"ஒய்\",\n                    \"buttonZL\": \"Zl\",\n                    \"buttonZR\": \"Zr\",\n                    \"rStick\": \"ஆர்-பேட்\"\n                },\n                \"phraseNewClassic\": \"புதிய கிளாசிக் சுயவிவரம்\",\n                \"phraseNewNunchuck\": \"புதிய நன்சக் சுயவிவரம்\",\n                \"typeClassic\": \"கிளாசிக்\",\n                \"wiimote\": {\n                    \"buttonB\": \"பி\",\n                    \"buttonHome\": \"வீடு\",\n                    \"buttonMinus\": \"-\",\n                    \"buttonPlus\": \"+\",\n                    \"button1\": \"1\",\n                    \"button2\": \"2\",\n                    \"buttonA\": \"ஏ\",\n                    \"caseIR\": \"(மற்றும்)\",\n                    \"dPad\": \"டி-பேட்\",\n                    \"shake\": \"குலுக்கல்\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"சி\",\n                    \"buttonZ\": \"சட்\",\n                    \"prefixN\": \"என்\"\n                },\n                \"typeNunchuck\": \"சங்கிலித் தண்டுகள்\",\n                \"typeWiimote\": \"வைமோட்\",\n                \"typeGamecube\": \"கேம்க்யூப்\"\n            },\n            \"caseInvalid\": \"(தவறானது)\",\n            \"caseMouse\": \"(சுட்டி)\",\n            \"caseTouch\": \"(தொடு)\",\n            \"controlsConnected\": \"இணைக்கப்பட்டுள்ளது:\",\n            \"controlsDeleteKey\": \"(நீக்க ஆல்ட் சம்ப்)\",\n            \"controlsDeleteKey2\": \"(நீக்க அனைத்து ரன்)\",\n            \"controlsDeviceTypes\": \"சாதன வகைகள்\",\n            \"controlsInUse\": \"(பயன்பாட்டில்)\",\n            \"controlsNewProfile\": \"<புதிய சுயவிவரம்>\",\n            \"controlsNotInUse\": \"(பயன்பாட்டில் இல்லை)\",\n            \"cursor\": {\n                \"down\": \"சுட்டி கீழே\",\n                \"right\": \"சுட்டி சரியானது\",\n                \"secondary\": \"இரண்டாம் நிலை\",\n                \"left\": \"சுட்டி இடது\",\n                \"primary\": \"முதன்மை\",\n                \"tertiary\": \"மூன்றாம் நிலை\",\n                \"up\": \"சுட்டி அப்\"\n            },\n            \"editor\": {\n                \"fastScroll\": \"வேகமான scrl\",\n                \"modeSelect\": \"பயன்முறை தேர்ந்தெடுக்கவும்\",\n                \"nextSection\": \"அடுத்த பிரிவு\",\n                \"prevSection\": \"முந்தைய பிரிவு\",\n                \"scrollDown\": \"Scrl கீழே\",\n                \"scrollLeft\": \"Scrl இடது\",\n                \"scrollRight\": \"Scrl சரி\",\n                \"modeErase\": \"பயன்முறை அழித்தல்\",\n                \"scrollUp\": \"Scrl மேலே\",\n                \"switchScreens\": \"பலகம் காட்டு\",\n                \"testPlay\": \"சோதனை நாடகம்\"\n            },\n            \"phraseNewProfOldJoy\": \"பழைய சாய்ச்டிக் புதிய சுயவிவரம்\",\n            \"profile\": {\n                \"cursorControls\": \"கர்சர் கட்டுப்பாடுகள்\",\n                \"deleteProfile\": \"சுயவிவரத்தை நீக்கு\",\n                \"editorControls\": \"ஆசிரியர் கட்டுப்பாடுகள்\",\n                \"hotkeys\": \"ஆட்கீச்\",\n                \"playerControls\": \"பிளேயர் கட்டுப்பாடுகள்\",\n                \"renameProfile\": \"சுயவிவரத்தை மறுபெயரிடுங்கள்\"\n            },\n            \"joystickSimpleEditor\": \"எளிய எடிட்டர் கட்டுப்பாடுகள்\",\n            \"types\": {\n                \"oldJoystick\": \"பழைய மகிழ்ச்சி\",\n                \"gamepad\": \"கேம்பேட்\",\n                \"keyboard\": \"விசைப்பலகை\",\n                \"touchscreen\": \"தொடுதிரை\"\n            },\n            \"wordProfiles\": \"சுயவிவரங்கள்\",\n            \"wordButtons\": \"பொத்தான்கள்\"\n        },\n        \"game\": {\n            \"gameBattleRandom\": \"சீரற்ற நிலை\",\n            \"gameCopySave\": \"சேமி நகல்\",\n            \"gameEraseSave\": \"சேமி\",\n            \"phraseTime\": \"நேரம்: {0}\",\n            \"gameEraseSlot\": \"அழிக்க ச்லாட்டைத் தேர்ந்தெடுக்கவும்\",\n            \"gameNoBattleLevels\": \"<போர் நிலைகள் இல்லை>\",\n            \"gameNoEpisodesToPlay\": \"<விளையாடுவதற்கு அத்தியாயங்கள் இல்லை>\",\n            \"warnEpCompat\": \"இந்த அத்தியாயம் SMBX இன் வேறு கிளைக்காக உருவாக்கப்பட்டது மற்றும் சரியாக வேலை செய்யாமல் போகலாம்.\",\n            \"gameSlotContinue\": \"ச்லாட் {0} ... {1}%\",\n            \"gameSlotNew\": \"ச்லாட் {0} ... புதிய விளையாட்டு\",\n            \"gameSourceSlot\": \"மூல ச்லாட்டைத் தேர்ந்தெடுக்கவும்\",\n            \"gameTargetSlot\": \"இப்போது இலக்கைத் தேர்ந்தெடுக்கவும்\",\n            \"phraseScore\": \"ச்கோர்: {0}\"\n        },\n        \"loading\": \"ஏற்றுகிறது ...\",\n        \"options\": {\n            \"advanced\": {\n                \"_header\": \"மேம்பட்ட\",\n                \"_tooltip\": \"உள் செயல்பாடுகளுக்கான தொழில்நுட்ப விருப்பங்கள்\",\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"குறைவான பாப்சுக்கு அதிகரிப்பு ஆனால் அதிக பின்னடைவு\",\n                    \"_name\": \"இடையக அளவு\"\n                },\n                \"audio-channels\": {\n                    \"_name\": \"சேனல்கள்\",\n                    \"mono\": \"மோனோ\",\n                    \"stereo\": \"ச்டீரியோ\"\n                },\n                \"audio-enable\": \"இயக்கு\",\n                \"audio-sample-rate\": {\n                    \"16000\": \"16000 எர்ட்ச்\",\n                    \"22050\": \"22050 எர்ட்ச்\",\n                    \"_name\": \"மாதிரி வீதம்\",\n                    \"11025\": \"11025 எர்ட்ச்\",\n                    \"32000\": \"32000 எர்ட்ச்\",\n                    \"44100\": \"44100 எர்ட்ச்\",\n                    \"48000\": \"48000 எர்ட்ச்\"\n                },\n                \"log-level\": {\n                    \"warning\": \"எச்சரிக்கை\",\n                    \"_name\": \"பதிவு நிலை\",\n                    \"critical\": \"விமர்சன\",\n                    \"debug\": \"பிழைத்திருத்தம்\",\n                    \"fatal\": \"அபாயகரமான\",\n                    \"info\": \"தகவல்\",\n                    \"none\": \"எதுவுமில்லை\"\n                },\n                \"record-gameplay-data\": \"விளையாட்டுப் பதிவு\",\n                \"render\": {\n                    \"hw\": \"தானி\",\n                    \"opengl\": \"Opengl 3+\",\n                    \"opengl11\": \"Opengl 1.1\",\n                    \"_name\": \"பயன்முறையை வழங்கவும்\",\n                    \"opengl-tip\": \"அனைத்து புதிய காட்சி விளைவுகளுக்கும் ஆதரவுடன் டெச்க்டாப் பநிஇ மற்றும் SMBX64 க்கு முழு துல்லியம்\",\n                    \"opengl11-tip\": \"SMBX64 க்கு முழு துல்லியத்துடன் மரபு டெச்க்டாப் பநிஇ\",\n                    \"opengles\": \"ஓபன்சிஎல் 2+ ஆகும்\",\n                    \"opengles-tip\": \"அனைத்து புதிய காட்சி விளைவுகளுக்கும் ஆதரவுடன் மொபைல் பநிஇ மற்றும் SMBX64 க்கு அதிக துல்லியம்\",\n                    \"opengles11\": \"ஓபன்சிஎல் 1.1+ ஆகும்\",\n                    \"opengles11-tip\": \"SMBX64 க்கு முழு துல்லியத்துடன் மரபு மொபைல் பநிஇ\",\n                    \"sdl\": \"SDL2\",\n                    \"sdl-tip\": \"அடிப்படை குறுக்கு-தளம் வழங்கல் பநிஇ\",\n                    \"sw\": \"மென்பொருள்\"\n                },\n                \"scale-down-textures\": {\n                    \"none\": \"எதுவுமில்லை\",\n                    \"_tooltip\": \"நினைவகத்தை சேமிக்க படங்களை 1x ஆக சேமிக்கவும்\",\n                    \"all\": \"அனைத்தும்\",\n                    \"all-tip\": \"'பாதுகாப்பானது' என்பதை விட குறைவான ஏற்றுதல் திணறல்\",\n                    \"_name\": \"படங்களை அளவிடவும்\",\n                    \"none-tip\": \"குறைந்தது ஏற்றுதல் திணறல்\",\n                    \"safe\": \"பாதுகாப்பானது\",\n                    \"safe-tip\": \"படங்கள் 2x வடிவத்தில் இருந்தால் சரிபார்க்கிறது\"\n                },\n                \"inaccurate-gifs-tip\": \"GIF கள் இருக்கும்போது 3D விளைவை அனுமதிக்கிறது\",\n                \"choose-assets-on-launch\": \"துவக்கத்தில் சொத்துக்களைத் தேர்வுசெய்க\",\n                \"advanced-audio\": \"ஆடியோ\",\n                \"advanced-video\": \"ஒளிதோற்றம்\",\n                \"audio-format\": {\n                    \"_name\": \"ஆடியோ வடிவம்\",\n                    \"_tooltip\": \"ஒலி இயக்கி வடிவம்\"\n                },\n                \"use-native-osk\": \"சொந்த ஓச்க்\",\n                \"inaccurate-gifs\": \"தவறான GIF கள்\",\n                \"webm-recording\": \"வெப்எம் பதிவு\",\n                \"internal-res-4p\": {\n                    \"_name\": \"4p பிளவு திரை அளவு\",\n                    \"default\": \"இயல்புநிலை\",\n                    \"fhd\": \"1920x1080 (FHD)\",\n                    \"qhd\": \"2560x1440 (QHD)\",\n                    \"qsmbx\": \"1600x1200 (QSMBX)\"\n                },\n                \"fullscreen-type\": {\n                    \"_name\": \"முழுத்திரை வகை\",\n                    \"auto\": \"தானி\",\n                    \"desktop\": \"டெச்க்டாப்\",\n                    \"desktop-tip\": \"தற்போதைய திரை தெளிவுத்திறனை வைத்திருங்கள்\",\n                    \"exclusive\": \"பிரத்தியேக\",\n                    \"exclusive-tip\": \"உடல் திரை தெளிவுத்திறனை அமைக்கவும்\"\n                },\n                \"fullscreen-depth\": {\n                    \"16\": \"16-பிட்\",\n                    \"32\": \"32-பிட்\",\n                    \"_name\": \"பிரத்யேக பிட் ஆழம்\",\n                    \"auto\": \"தானி\"\n                },\n                \"fullscreen-res\": \"பிரத்யேக ரெச்\"\n            },\n            \"audio\": {\n                \"sfx-spatial-audio-tip\": \"ஆஃப்ச்கிரீன் ஒலிகளின் அளவைக் குறைக்கவும்\",\n                \"_header\": \"ஆடியோ\",\n                \"audio-music-volume\": \"இசை தொகுதி\",\n                \"audio-prefs\": \"விருப்பத்தேர்வுகள்\",\n                \"audio-sfx-volume\": \"SFX தொகுதி\",\n                \"sfx-audio-fx\": \"எதிரொலி விளைவுகள்\",\n                \"sfx-modern\": \"நவீன எச்.எஃப்.எக்ச்\",\n                \"sfx-modern-tip\": \"Textech இல் சேர்க்கப்பட்ட ஒலிகளைப் பயன்படுத்தவும்\",\n                \"sfx-pet-beat\": \"செல்லப்பிராணி பள்ளங்கள்\",\n                \"sfx-spatial-audio\": \"இடஞ்சார்ந்த ஆடியோ\",\n                \"sfx-pet-beat-tip\": \"செல்லப்பிராணியை சவாரி செய்யும் போது சிறப்பு பள்ளம் விளையாடுங்கள்\"\n            },\n            \"episode-options\": {\n                \"creator-compat\": {\n                    \"disable\": \"முடக்கு\",\n                    \"_name\": \"படைப்பாளி இணைத்தல்\",\n                    \"_tooltip\": \"உள்ளடக்க படைப்பாளரின் வழக்கு மாற்றங்கள்\",\n                    \"enable\": \"இயக்கு\",\n                    \"file-only\": \"கோப்பு மட்டுமே\"\n                },\n                \"playstyle\": {\n                    \"_name\": \"பிளேச்டைல்\",\n                    \"classic-tip\": \"குறைந்தபட்ச புதுப்பிப்புகள்\",\n                    \"classic\": \"கிளாசிக்\",\n                    \"modern\": \"நவீன\",\n                    \"modern-tip\": \"அனைத்து புதுப்பிப்புகளும்\",\n                    \"vanilla\": \"வெண்ணிலா\",\n                    \"vanilla-tip\": \"புதுப்பிப்புகள் இல்லை\"\n                },\n                \"_header\": \"அத்தியாயம் விருப்பங்கள்\",\n                \"hub-resume\": \"வார்ப் அப்பில் சேமிக்கவும்\",\n                \"hub-resume-tip\": \"நீங்கள் நுழைந்த கடைசி போரில் மீண்டும் விளையாடுங்கள்\"\n            },\n            \"main\": {\n                \"background-work\": \"பின்னணியில் இயக்கவும்\",\n                \"four-screen-mode\": {\n                    \"split\": \"பிளவு\",\n                    \"_name\": \"4p திரை பயன்முறை\",\n                    \"shared\": \"பகிரப்பட்டது\"\n                },\n                \"multiplayer\": \"மல்டிபிளேயர்\",\n                \"timing\": \"சட்ட நேரம்\",\n                \"_header\": \"முக்கிய\",\n                \"background-work-tip\": \"விளையாட்டு கவனம் செலுத்தாமல் இருக்கும்போது சாய்ச்டிக் உடன் விளையாடுங்கள்\",\n                \"frame-skip\": \"பிரேம்கிப்\",\n                \"discord-rpc\": \"முரண்பாடு ஒருங்கிணைப்பு\",\n                \"language\": \"மொழி\",\n                \"two-screen-mode\": {\n                    \"_name\": \"2p திரை பயன்முறை\",\n                    \"shared\": \"பகிரப்பட்டது\",\n                    \"smbx\": \"SMBX\",\n                    \"split\": \"இடது/வலது\",\n                    \"topbottom\": \"மேல்/கீழ்\"\n                },\n                \"unlimited-framerate\": \"வரம்பற்ற ஃப்ரேம்ரேட்\",\n                \"vsync\": \"வி-ஒத்திசைவு\"\n            },\n            \"video\": {\n                \"battery-status\": {\n                    \"off\": \"அணை\",\n                    \"_name\": \"சாதன பேட்டரி நிலை\",\n                    \"fullscreen\": \"முழு திரை\",\n                    \"fullscreen-tip\": \"விளையாட்டு முழுத்திரையாக இருக்கும்போது காண்பி\",\n                    \"low\": \"குறைந்த\",\n                    \"low-tip\": \"பேட்டரி குறைவாக இருக்கும்போது காண்பி\",\n                    \"on\": \"எப்போதும்\"\n                },\n                \"internal-res\": {\n                    \"3ds\": \"800x480 (3DS)\",\n                    \"_name\": \"திரை அளவு\",\n                    \"_tooltip\": \"விளையாட்டு புலத்தின் தீர்மானம்\",\n                    \"dynamic\": \"மாறும்\",\n                    \"gba\": \"480x320 (பெறுங்கள்)\",\n                    \"hd\": \"1280x720 (எச்டி)\",\n                    \"hello\": \"768x432 (அலோ)\",\n                    \"nds\": \"512x384 (என்.டி.எச்)\",\n                    \"smbx\": \"800x600 (SMBX)\",\n                    \"snes\": \"512x448 (SNES)\",\n                    \"smbx-wide\": \"SMBX (அகலம்)\",\n                    \"vga\": \"640x480 (விசிஏ)\"\n                },\n                \"scale-mode\": {\n                    \"0_5x\": \"0.5x\",\n                    \"1x\": \"1x\",\n                    \"_name\": \"அளவிலான பயன்முறை\",\n                    \"integer\": \"முழு எண்\",\n                    \"linear\": \"மென்மையான\",\n                    \"nearest\": \"அருகில்\",\n                    \"2x\": \"ஃச்\",\n                    \"full\": \"முழு\",\n                    \"center\": \"நடுவண்\",\n                    \"3x\": \"Zks\"\n                },\n                \"show-episode-title\": {\n                    \"off\": \"அணை\",\n                    \"top\": \"மேலே\",\n                    \"top-tip\": \"உயர் தெளிவுத்திறனில் HUD க்கு மேலே காண்பி\",\n                    \"_name\": \"அத்தியாயம் பெயர்\",\n                    \"bottom\": \"கீழே\",\n                    \"bottom-tip\": \"ச்பீட்ரன் டைமருக்கு மேலே காட்டவும்\"\n                },\n                \"3d-compat-mode\": \"3D இணைத்தல் பயன்முறை\",\n                \"show-medals-counter-tip\": \"HUD மற்றும் நிலை நுழைவாயிலில் பதக்கங்களைக் காட்டு\",\n                \"show-playtime-counter\": {\n                    \"animated-tip\": \"நிலை முடிவில் வானவில் விளைவைச் சேர்க்கிறது\",\n                    \"_name\": \"பிளே டைம் கவுண்டர்\",\n                    \"_tooltip\": \"அத்தியாயம் மற்றும் தற்போதைய முயற்சியில் செலவழித்த நேரத்தைக் காட்டு\",\n                    \"animated\": \"அனிமேசன்\",\n                    \"off\": \"அணை\",\n                    \"off-tip\": \"ச்பீட்ரன்களின் போது மட்டுமே காட்டு\",\n                    \"opaque\": \"ஒளிபுகா\",\n                    \"transparent\": \"வெளிப்படையானது\"\n                },\n                \"_header\": \"ஒளிதோற்றம்\",\n                \"effects\": \"திரை விளைவுகள்\",\n                \"display-controllers\": \"செயல்பாட்டைக் கட்டுப்படுத்துகிறது\",\n                \"enable-inter-level-fade-effect\": \"மங்கலான மாற்றங்கள்\",\n                \"fullscreen\": \"முழு திரை\",\n                \"info-game\": \"திரை விளையாட்டு செய்தி\",\n                \"info-meta\": \"திரை மேவு செய்தி\",\n                \"info-run\": \"திரை ரன் செய்தி\",\n                \"3d-compat-mode-tip\": \"அனைத்து பொருட்களையும் ஒரு 3D விமானத்தில் வரையவும்\",\n                \"show-fails-counter\": \"எதிர் தோல்வி\",\n                \"show-fails-counter-tip\": \"அத்தியாயம் மற்றும் தற்போதைய மட்டத்தில் சோ தோல்வியடைகிறது\",\n                \"show-fps\": \"பிரேம்ரேட்\",\n                \"show-medals-counter\": \"பதக்கங்கள் கவுண்டர்\",\n                \"show-screen-shake\": \"திரை குலுக்கல்\",\n                \"video-system\": \"மண்டலம்\",\n                \"world-map-stars-show\": \"உலக வரைபட நட்சத்திரங்கள்\",\n                \"world-map-stars-show-tip\": \"சேகரிக்கப்பட்ட நட்சத்திரங்களை மட்டங்களுக்கு மேலே காட்டு\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"0\": \"0\",\n                    \"1\": \"1\",\n                    \"2\": \"2\",\n                    \"3\": \"3\",\n                    \"_name\": \"ச்பீட்ரன் பயன்முறை\"\n                }\n            },\n            \"restartEngine\": \"மாற்றங்கள் நடைமுறைக்கு வர இயந்திரத்தை மறுதொடக்கம் செய்யுங்கள்.\",\n            \"compatibility\": {\n                \"_header\": \"அமர்வு மாற்றங்கள்\",\n                \"_tooltip\": \"இணைத்தல் அல்லது பிழைத்திருத்தத்திற்கான விருப்பங்கள்\",\n                \"autocode\": \"ஆட்டோகோட்\",\n                \"bugfixes\": \"பிழைத்திருத்தங்கள்\",\n                \"features\": \"நற்பொருத்தங்கள்\",\n                \"reset-all\": \"அனைத்தையும் மீட்டமைக்கவும்\"\n            },\n            \"controls\": {\n                \"_header\": \"கட்டுப்பாடுகள்\"\n            },\n            \"view-credits\": {\n                \"_header\": \"வரவுகளைக் காண்க\"\n            }\n        },\n        \"wordYes\": \"ஆம்\",\n        \"abbrevMilliseconds\": \"எம்.எச்\",\n        \"battle\": {\n            \"errorNoLevels\": \"எந்த நிலைகளும் கிடைக்காததால் போரைத் தொடங்க முடியாது\"\n        },\n        \"caseNone\": \"<இல்லை>\",\n        \"character\": {\n            \"selectCharacter\": \"{0} விளையாட்டு\"\n        },\n        \"editor\": {\n            \"errorMissingResources\": \"மன்னிக்கவும்! விளையாட்டு எடிட்டருக்கு தேவைப்படும் {0} ஐ நீங்கள் காணவில்லை.\",\n            \"battles\": \"<போர் நிலைகள்>\",\n            \"makeFor\": \"செய்யுங்கள்:\",\n            \"newWorld\": \"<புதிய உலகம்>\",\n            \"promptNewWorldName\": \"புதிய உலக பெயர்\"\n        },\n        \"main\": {\n            \"main1PlayerGame\": \"1 பிளேயர் விளையாட்டு\",\n            \"mainBattleGame\": \"போர் விளையாட்டு\",\n            \"mainEditor\": \"திருத்தி\",\n            \"mainExit\": \"வெளியேறு\",\n            \"mainMultiplayerGame\": \"2 பிளேயர் விளையாட்டு\",\n            \"mainOptions\": \"விருப்பங்கள்\",\n            \"mainPlayEpisode\": \"அத்தியாயம் விளையாடுங்கள்\"\n        },\n        \"wordBack\": \"பின்\",\n        \"wordHide\": \"மறை\",\n        \"wordNo\": \"இல்லை\",\n        \"wordOff\": \"அணை\",\n        \"wordOn\": \"ஆன்\",\n        \"wordPlayer\": \"வீரர்\",\n        \"wordProfile\": \"சுயவிவரம்\",\n        \"wordResume\": \"மீண்டும் தொடங்குங்கள்\",\n        \"wordShow\": \"காட்டு\",\n        \"wordWaiting\": \"காத்திருக்கிறது\"\n    },\n    \"outro\": {\n        \"engineCredits\": \"இயந்திர வரவு:\",\n        \"nameVitalyNovichkov\": \"விட்டலி நோவாச்ச்கோவ்\",\n        \"cppPortDevelopers\": \"சி ++ துறைமுகம் டெவலப்பர்கள்:\",\n        \"customSprites\": \"தனிப்பயன் உருவங்கள்:\",\n        \"gameCredits\": \"விளையாட்டு வரவு:\",\n        \"levelDesign\": \"நிலை வடிவமைப்பு:\",\n        \"nameAndrewSpinks\": \"ஆண்ட்ரூ சூரரிமாச்சிலை\",\n        \"originalBy\": \"அசல் விபி 6 குறியீடு:\",\n        \"psVitaPortBy\": \"பி.எச். வீடா போர்ட்:\",\n        \"qualityControl\": \"தரக் கட்டுப்பாடு:\",\n        \"specialThanks\": \"சிறப்பு நன்றி:\"\n    },\n    \"pluralRules\": \"ஒன்று-ஒற்றை\",\n    \"languageName\": \"தமிழ்\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_tr.json",
    "content": "{\n    \"editor\": {\n        \"block\": {\n            \"canBreak\": \"Kırılabilir\",\n            \"canBreakTooltip\": \"Legacy: vurunca kırılır\",\n            \"inside\": \"İçinde:\",\n            \"invis\": \"Görmez\",\n            \"letterHeight\": \"Y\",\n            \"letterWidth\": \"G\",\n            \"pickContents\": \"Blok içeriği seç\",\n            \"slick\": \"Kaygan\"\n        },\n        \"browser\": {\n            \"askOverwriteFile\": \"Üzerine Yaz {0}?\",\n            \"itemNewFile\": \"<Yeni Dosya>\",\n            \"itemNewFolder\": \"<Yeni Klâsör>\",\n            \"newFile\": \"Yeni dosya\",\n            \"openFile\": \"Dosyayı aç\",\n            \"saveFile\": \"Dosyayı kaydet\"\n        },\n        \"events\": {\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"Bütün kısımları buna dönüştür?\",\n                \"changeSectionBoundsToCurrent\": \"{0} kısımının ayarlarını buna bağla?\",\n                \"shouldEvent\": \"{0} Olayı Şunu Yapmalı Mı\"\n            },\n            \"controlsForEventN\": \"'{0}' olayı için kontroller\",\n            \"deletion\": {\n                \"cancel\": \"Hayır: olayı silme\",\n                \"confirm\": \"Evet: olayı sil\",\n                \"deletingEvent\": \"{0} olayı siliniyor\"\n            },\n            \"desc\": {\n                \"activate\": \"NPC ekrana girer\",\n                \"death\": \"NPC ölür\",\n                \"destroy\": \"blok yok edildi\",\n                \"enter\": \"ışınlanma girildi\",\n                \"hit\": \"bloğa vurulduğunda\",\n                \"layerClear\": \"obje katmanındaki her şey temizlendiğinde\",\n                \"phraseTriggersAfterTemplate\": \"{2} yaşandıktan ms sonra {0} etkinleştirir {1}'i\",\n                \"phraseTriggersWhenTemplate\": \"{1} {0}'i etkinleştir\",\n                \"talk\": \"NPC ile konuşulduğunda\"\n            },\n            \"header\": \"Olaylar:\",\n            \"headerTriggerEvent\": \"Etkinleştir:\",\n            \"itemNewEvent\": \"<Yeni Olay>\",\n            \"label\": {\n                \"activate\": \"Aktifleştir\",\n                \"death\": \"Ölüm\",\n                \"destroy\": \"Yok Et\",\n                \"enter\": \"Giriş\",\n                \"hit\": \"Vuruldu\",\n                \"layerClear\": \"Katmanı Temizle\",\n                \"next\": \"Sonraki\",\n                \"talk\": \"Konuş\"\n            },\n            \"layers\": {\n                \"headerHide\": \"Gizle:\",\n                \"headerMove\": \"Hareket Ettir:\",\n                \"headerShow\": \"Göster:\",\n                \"headerToggle\": \"Geçiş Yap:\"\n            },\n            \"letter\": {\n                \"activate\": \"A:\",\n                \"death\": \"Ö:\",\n                \"destroy\": \"Y:\",\n                \"enter\": \"G:\",\n                \"hit\": \"V:\",\n                \"layerClear\": \"Ka:\",\n                \"talk\": \"Ko:\"\n            },\n            \"promptEventName\": \"Olay adı\",\n            \"promptEventText\": \"Olay metni\",\n            \"props\": {\n                \"autostart\": \"OtoBaşla\",\n                \"controls\": \"Kontroller\",\n                \"endGame\": \"Oyun sonu\",\n                \"sound\": \"Ses\",\n                \"layerSmoke\": \"Duman\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"Tut\",\n                \"actionReset\": \"Sıfırla\",\n                \"actionSet\": \"Ayarla\",\n                \"phraseAllSections\": \"Bütün Kısımlar\",\n                \"propBackground\": \"AP\",\n                \"propBounds\": \"Sınırlar\",\n                \"propMusic\": \"Müzik\"\n            },\n            \"settingsForEvent\": \"{0} olayının ayarları\"\n        },\n        \"file\": {\n            \"actionClearLevel\": \"Seviyeyi Temizle\",\n            \"actionClearWorld\": \"Dünyayı Temizle\",\n            \"actionExit\": \"Çıkış\",\n            \"actionOpen\": \"Aç\",\n            \"actionRevert\": \"Geri Al\",\n            \"commandNew\": \"Yeni\",\n            \"commandOpen\": \"Aç...\",\n            \"commandSave\": \"Kaydet\",\n            \"commandSaveAs\": \"Farklı Kaydet...\",\n            \"confirmConfirmAction\": \"{0} yapmak istediğine emin misin?\",\n            \"confirmConvertFormatTo\": \"{0} formatına dönüştürülsün mü?\",\n            \"confirmSaveBeforeAction\": \"{0} yapmadan kaydet?\",\n            \"formatLegacy\": \"Legacy\",\n            \"formatModern\": \"Modern\",\n            \"labelCurrentFile\": \"Şu anki dosya:\",\n            \"labelFormat\": \"Biçim:\",\n            \"optionActionWithoutSave\": \"Kaydetmeden {0}\",\n            \"optionCancelAction\": \"İptal: {0} yapma\",\n            \"optionCancelConversion\": \"Dönüştürmeyi iptal et\",\n            \"optionProceedWithConversion\": \"Dönüştürmeye devam et\",\n            \"optionYesSaveThenAction\": \"Evet: kaydet ve {0}\",\n            \"sectionLevel\": \"Seviye\",\n            \"sectionWorld\": \"Dünya\",\n            \"convert\": {\n                \"descNew\": \"Dosya uzantısı değişecek ama eski dosya SİLİNMEYECEK.\\n\\nLütfen kaydettikten sonra kaybolmuş özelliklere bakın.\"\n            }\n        },\n        \"layers\": {\n            \"abbrevAttLayer\": \"Takılı: \",\n            \"default\": \"Varsayılan\",\n            \"deletion\": {\n                \"cancel\": \"İptal: katmanı silme\",\n                \"confirmDelete\": \"Hayır: *BÜTÜN İÇERİĞİ SİL*\",\n                \"confirmPreserve\": \"Evet: varsayılan katmana taşı\",\n                \"header\": \"{0} katmanı siliniyor\",\n                \"preserveLayerContents\": \"Katman içeriği korunsun mu?\"\n            },\n            \"desc\": {\n                \"att\": \"NPC hareket ettiği zaman bağlı olduğu katman onu takip eder\"\n            },\n            \"header\": \"Katmanlar:\",\n            \"itemNewLayer\": \"<Yeni Katman>\",\n            \"label\": \"Katman:\",\n            \"labelAttached\": \"Bağlı Olduğu:\",\n            \"labelAttachedLayer\": \"Bağlı Olduğu Katman:\",\n            \"labelMoveLayer\": \"Katmanı Hareket Ettir:\",\n            \"promptLayerName\": \"Katman adı\"\n        },\n        \"letterCoordX\": \"X\",\n        \"letterCoordY\": \"Y\",\n        \"letterDown\": \"A\",\n        \"letterLeft\": \"So\",\n        \"letterRight\": \"Sa\",\n        \"letterUp\": \"Y\",\n        \"level\": {\n            \"alwaysVis\": \"Hep Görün\",\n            \"bigBG\": \"Büyük AP\",\n            \"gameStart\": \"Oyun Başlar\",\n            \"levelName\": \"Seviye İsmi\",\n            \"pathBG\": \"Yol AP\",\n            \"pathUnlocks\": \"Yol Anahtarları\",\n            \"startPos\": \"Başlangıç Pozisyonu\"\n        },\n        \"mapPos\": \"Harita Pozisyonu\",\n        \"npc\": {\n            \"abbrevGen\": \"Üret\",\n            \"ai\": {\n                \"LR\": \"SağSol\",\n                \"UD\": \"YukAş\",\n                \"aiIs\": \"AI: {0}\",\n                \"headerCustomAi\": \"Custom AI:\",\n                \"jump\": \"Atla\",\n                \"leap\": \"Sıçra\",\n                \"swim\": \"Yüz\",\n                \"target\": \"Hedef\",\n                \"use1_0Ai\": \"1.0 AI Kullan\"\n            },\n            \"gen\": {\n                \"direction\": \"Yönü\",\n                \"effectIs\": \"Efekt: {0}\",\n                \"effectShoot\": \"Ateşle\",\n                \"effectWarp\": \"Işınla\",\n                \"header\": \"Jeneratör Ayarları\"\n            },\n            \"inContainer\": \"İçi\",\n            \"inertNice\": \"Dost\",\n            \"props\": {\n                \"active\": \"\",\n                \"attachSurface\": \"\",\n                \"facing\": \"Yönü\"\n            },\n            \"stuckStop\": \"Dur\",\n            \"tooltipExpandSection\": \"Kısmı Genişlet\"\n        },\n        \"pageBlankOfBlank\": \"Sayfa {0} de {1}\",\n        \"phraseAreYouSure\": \"Emin misin?\",\n        \"phraseCountMore\": \"{0} Daha\",\n        \"phraseDelayIsMs\": \"{0} ms Ertele\",\n        \"phraseRadiusIndex\": \"\",\n        \"phraseSectionIndex\": \"Kısım {0}\",\n        \"phraseTextOf\": \"\",\n        \"phraseWarpIndex\": \"\",\n        \"section\": {\n            \"horizWrap\": \"\",\n            \"noTurnBack\": \"\",\n            \"offscreenExit\": \"\",\n            \"scroll\": \"\",\n            \"setBounds\": \"\",\n            \"underwater\": \"\",\n            \"vertWrap\": \"\"\n        },\n        \"select\": {\n            \"allSectPropBlankForEventN\": \"\",\n            \"pathBlankUnlock\": \"\",\n            \"sectBlankPropBlankForEventN\": \"\",\n            \"sectionBlankPropBlank\": \"\",\n            \"soundForEventN\": \"\",\n            \"warpTransitEffect\": \"\",\n            \"worldMusic\": \"\"\n        },\n        \"testPlay\": {\n            \"boot\": \"\",\n            \"char\": \"\",\n            \"magicHand\": \"\",\n            \"pet\": \"\",\n            \"power\": \"\"\n        },\n        \"toggleMagicBlock\": \"Büyülü Blok\",\n        \"tooltip\": {\n            \"BGOs\": \"\",\n            \"NPCs\": \"\",\n            \"blocks\": \"\",\n            \"erase\": \"\",\n            \"eraseAll\": \"\",\n            \"events\": \"\",\n            \"file\": \"Dosya\",\n            \"layers\": \"\",\n            \"levels\": \"\",\n            \"music\": \"\",\n            \"paths\": \"\",\n            \"scenes\": \"\",\n            \"select\": \"\",\n            \"settings\": \"\",\n            \"show\": \"\",\n            \"tiles\": \"Tilelar\",\n            \"warps\": \"\",\n            \"water\": \"\",\n            \"area\": \"\"\n        },\n        \"warp\": {\n            \"allow\": \"\",\n            \"cannonExit\": \"\",\n            \"dir\": \"\",\n            \"effect\": \"\",\n            \"in\": \"\",\n            \"item\": \"\",\n            \"lvlWarp\": \"\",\n            \"needFloor\": \"\",\n            \"needKey\": \"\",\n            \"needStarCount\": \"\",\n            \"out\": \"\",\n            \"placing\": \"\",\n            \"ride\": \"\",\n            \"showStarCount\": \"\",\n            \"showStartScene\": \"\",\n            \"speed\": \"\",\n            \"starLockMessage\": \"\",\n            \"style\": {\n                \"blipInstant\": \"\",\n                \"door\": \"\",\n                \"pipe\": \"\",\n                \"portal\": \"\",\n                \"style\": \"\"\n            },\n            \"target\": \"\",\n            \"title\": \"\",\n            \"to\": \"\",\n            \"toMap\": \"\",\n            \"transit\": {\n                \"name0\": \"\",\n                \"name1\": \"\",\n                \"name2\": \"\",\n                \"name3\": \"\",\n                \"name4\": \"\",\n                \"name5\": \"\"\n            },\n            \"twoWay\": \"\"\n        },\n        \"water\": {\n            \"title\": \"\"\n        },\n        \"wordCoins\": \"\",\n        \"wordEnabled\": \"\",\n        \"wordEvent\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\",\n            \"typeLabel\": \"\"\n        },\n        \"wordHeight\": \"\",\n        \"wordInstant\": \"\",\n        \"wordMode\": \"\",\n        \"wordNPC\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\"\n        },\n        \"wordText\": \"\",\n        \"wordWidth\": \"\",\n        \"world\": {\n            \"allowChars\": \"\",\n            \"hubWorld\": \"\",\n            \"introLevel\": \"\",\n            \"name\": \"\",\n            \"phraseCreditIndex\": \"\",\n            \"retryOnFail\": \"\",\n            \"totalStars\": \"\"\n        },\n        \"labelSortLayer\": \"Katmanları Sırala:\",\n        \"labelSortOffset\": \"\",\n        \"phraseGenericIndex\": \"\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"dropAddTitle\": \"\",\n            \"phraseChangeChar\": \"\",\n            \"phraseDropMe\": \"\",\n            \"phraseDropPX\": \"\",\n            \"phraseForceResume\": \"\",\n            \"phraseHoldStart\": \"\",\n            \"phrasePressAButton\": \"\",\n            \"phraseSetControls\": \"\",\n            \"phraseStartToForceRes\": \"\",\n            \"phraseStartToResume\": \"\",\n            \"phraseTestControls\": \"\",\n            \"phraseWaitingForInput\": \"\",\n            \"reconnectTitle\": \"\",\n            \"splitPressSelect_1\": \"\",\n            \"splitPressSelect_2\": \"\",\n            \"wordDisconnect\": \"\",\n            \"phraseTestProfile\": \"Test Profili\"\n        },\n        \"error\": {\n            \"openFileFailed\": \"\",\n            \"warpNeedStarCount\": \"\",\n            \"errorInvalidEnterWarp\": \"\",\n            \"errorNoStartPoint\": \"\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"pause\": {\n            \"continue\": \"\",\n            \"enterCode\": \"\",\n            \"quit\": \"\",\n            \"quitTesting\": \"\",\n            \"resetCheckpoints\": \"\",\n            \"restartLevel\": \"\",\n            \"returnToEditor\": \"\",\n            \"saveAndContinue\": \"\",\n            \"saveAndQuit\": \"\",\n            \"playerSetup\": \"\"\n        },\n        \"hint\": {\n            \"char5-bombs\": \"\",\n            \"gray-bricks\": \"\",\n            \"rainbow-surf\": \"\",\n            \"shoe-block\": \"\",\n            \"heavy-duck\": \"\",\n            \"no-lives-old\": \"\",\n            \"no-lives-new\": \"\",\n            \"pound-altrun\": \"\",\n            \"pound-down\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"\"\n        },\n        \"msgbox\": {\n            \"sysInfoWarning\": \"\",\n            \"sysInfoError\": \"\",\n            \"sysInfoTitle\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"languageName\": \"Türkçe\",\n    \"menu\": {\n        \"abbrevMilliseconds\": \"\",\n        \"battle\": {\n            \"errorNoLevels\": \"\"\n        },\n        \"caseNone\": \"\",\n        \"character\": {\n            \"selectCharacter\": \"\"\n        },\n        \"controls\": {\n            \"buttons\": {\n                \"altJump\": \"\",\n                \"altRun\": \"\",\n                \"down\": \"\",\n                \"drop\": \"\",\n                \"jump\": \"\",\n                \"left\": \"\",\n                \"right\": \"\",\n                \"run\": \"\",\n                \"start\": \"\",\n                \"up\": \"\"\n            },\n            \"caseInvalid\": \"\",\n            \"caseMouse\": \"\",\n            \"caseTouch\": \"\",\n            \"controlsConnected\": \"\",\n            \"controlsDeleteKey\": \"\",\n            \"controlsDeviceTypes\": \"\",\n            \"controlsInUse\": \"\",\n            \"controlsNewProfile\": \"\",\n            \"controlsNotInUse\": \"\",\n            \"controlsReallyDeleteProfile\": \"\",\n            \"controlsTitle\": \"\",\n            \"cursor\": {\n                \"down\": \"\",\n                \"left\": \"\",\n                \"primary\": \"\",\n                \"right\": \"\",\n                \"secondary\": \"\",\n                \"tertiary\": \"\",\n                \"up\": \"\"\n            },\n            \"editor\": {\n                \"fastScroll\": \"\",\n                \"modeErase\": \"\",\n                \"modeSelect\": \"\",\n                \"nextSection\": \"\",\n                \"prevSection\": \"\",\n                \"scrollDown\": \"\",\n                \"scrollLeft\": \"\",\n                \"scrollRight\": \"\",\n                \"scrollUp\": \"\",\n                \"switchScreens\": \"\",\n                \"testPlay\": \"\"\n            },\n            \"hotkeys\": {\n                \"debugInfo\": \"\",\n                \"enterCheats\": \"\",\n                \"fullscreen\": \"\",\n                \"legacyPause\": \"\",\n                \"recordGif\": \"\",\n                \"screenshot\": \"\",\n                \"toggleHUD\": \"\",\n                \"vanillaCam\": \"\"\n            },\n            \"options\": {\n                \"batteryStatus\": \"\",\n                \"maxPlayers\": \"\",\n                \"rumble\": \"\",\n                \"textEntryStyle\": \"\"\n            },\n            \"phraseNewProfOldJoy\": \"\",\n            \"profile\": {\n                \"cursorControls\": \"\",\n                \"deleteProfile\": \"\",\n                \"editorControls\": \"\",\n                \"hotkeys\": \"\",\n                \"playerControls\": \"\",\n                \"renameProfile\": \"\"\n            },\n            \"tDS\": {\n                \"buttonA\": \"\",\n                \"buttonB\": \"\",\n                \"buttonL\": \"\",\n                \"buttonR\": \"\",\n                \"buttonSelect\": \"\",\n                \"buttonStart\": \"\",\n                \"buttonX\": \"\",\n                \"buttonY\": \"\",\n                \"buttonZL\": \"\",\n                \"buttonZR\": \"\",\n                \"cStick\": \"\",\n                \"casePen\": \"\",\n                \"dPad\": \"\",\n                \"tStick\": \"\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"longOld\": \"\",\n                    \"phabletOld\": \"\",\n                    \"phoneOld\": \"\",\n                    \"standard\": \"\",\n                    \"tabletOld\": \"\",\n                    \"tight\": \"\",\n                    \"tinyOld\": \"\"\n                },\n                \"option\": {\n                    \"feedbackLength\": \"\",\n                    \"feedbackStrength\": \"\",\n                    \"holdRun\": \"\",\n                    \"interfaceStyle\": \"\",\n                    \"layoutStyle\": \"\",\n                    \"resetLayout\": \"\",\n                    \"sStartSpacing\": \"\",\n                    \"scaleButtons\": \"\",\n                    \"scaleDPad\": \"\",\n                    \"scaleFactor\": \"\",\n                    \"showCodeButton\": \"\"\n                },\n                \"style\": {\n                    \"ABXY\": \"\",\n                    \"XODA\": \"\",\n                    \"actions\": \"\"\n                }\n            },\n            \"types\": {\n                \"gamepad\": \"\",\n                \"keyboard\": \"\",\n                \"oldJoystick\": \"\",\n                \"touchscreen\": \"\"\n            },\n            \"wii\": {\n                \"classic\": {\n                    \"buttonLT\": \"\",\n                    \"buttonRT\": \"\",\n                    \"buttonX\": \"\",\n                    \"buttonY\": \"\",\n                    \"buttonZL\": \"\",\n                    \"buttonZR\": \"\",\n                    \"lStick\": \"\",\n                    \"rStick\": \"\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"\",\n                    \"buttonZ\": \"\",\n                    \"prefixN\": \"\"\n                },\n                \"phraseNewClassic\": \"\",\n                \"phraseNewNunchuck\": \"\",\n                \"typeClassic\": \"\",\n                \"typeNunchuck\": \"\",\n                \"typeWiimote\": \"\",\n                \"wiimote\": {\n                    \"button1\": \"\",\n                    \"button2\": \"\",\n                    \"buttonA\": \"\",\n                    \"buttonB\": \"\",\n                    \"buttonHome\": \"\",\n                    \"buttonMinus\": \"-\",\n                    \"buttonPlus\": \"\",\n                    \"caseIR\": \"\",\n                    \"dPad\": \"\",\n                    \"shake\": \"Salla\"\n                },\n                \"typeGamecube\": \"\"\n            },\n            \"wordButtons\": \"\",\n            \"wordProfiles\": \"\",\n            \"joystickSimpleEditor\": \"\"\n        },\n        \"editor\": {\n            \"errorMissingResources\": \"\",\n            \"newWorld\": \"\",\n            \"promptNewWorldName\": \"\",\n            \"battles\": \"\",\n            \"makeFor\": \"\"\n        },\n        \"game\": {\n            \"gameBattleRandom\": \"\",\n            \"gameCopySave\": \"\",\n            \"gameEraseSave\": \"\",\n            \"gameEraseSlot\": \"\",\n            \"gameNoBattleLevels\": \"\",\n            \"gameNoEpisodesToPlay\": \"\",\n            \"gameSlotContinue\": \"\",\n            \"gameSlotNew\": \"\",\n            \"gameSourceSlot\": \"\",\n            \"gameTargetSlot\": \"\",\n            \"phraseScore\": \"\",\n            \"phraseTime\": \"\",\n            \"warnEpCompat\": \"\"\n        },\n        \"loading\": \"\",\n        \"main\": {\n            \"main1PlayerGame\": \"\",\n            \"mainBattleGame\": \"\",\n            \"mainEditor\": \"\",\n            \"mainExit\": \"\",\n            \"mainMultiplayerGame\": \"\",\n            \"mainOptions\": \"\",\n            \"mainPlayEpisode\": \"\"\n        },\n        \"options\": {\n            \"advanced\": {\n                \"advanced-video\": \"Video\",\n                \"advanced-audio\": \"Ses\",\n                \"audio-format\": {\n                    \"_name\": \"Ses formatı\",\n                    \"_tooltip\": \"\"\n                },\n                \"render\": {\n                    \"opengl\": \"\",\n                    \"opengl-tip\": \"\",\n                    \"opengl11-tip\": \"\",\n                    \"opengl11\": \"\",\n                    \"opengles11\": \"\",\n                    \"sw\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"opengles11-tip\": \"\",\n                    \"sdl\": \"\",\n                    \"opengles-tip\": \"\",\n                    \"hw\": \"\",\n                    \"_name\": \"\",\n                    \"opengles\": \"\"\n                },\n                \"log-level\": {\n                    \"debug\": \"\",\n                    \"fatal\": \"\",\n                    \"_name\": \"\",\n                    \"critical\": \"\",\n                    \"warning\": \"\",\n                    \"info\": \"\",\n                    \"none\": \"\"\n                },\n                \"audio-sample-rate\": {\n                    \"_name\": \"\",\n                    \"48000\": \"\",\n                    \"11025\": \"\",\n                    \"22050\": \"\",\n                    \"16000\": \"\",\n                    \"32000\": \"\",\n                    \"44100\": \"\"\n                },\n                \"scale-down-textures\": {\n                    \"_name\": \"\",\n                    \"all\": \"\",\n                    \"_tooltip\": \"\",\n                    \"none-tip\": \"\",\n                    \"safe-tip\": \"\",\n                    \"safe\": \"\",\n                    \"all-tip\": \"\",\n                    \"none\": \"\"\n                },\n                \"webm-recording\": \"\",\n                \"internal-res-4p\": {\n                    \"qhd\": \"\",\n                    \"_name\": \"\",\n                    \"fhd\": \"\",\n                    \"default\": \"\",\n                    \"qsmbx\": \"\"\n                },\n                \"_header\": \"\",\n                \"_tooltip\": \"\",\n                \"audio-buffer-size\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"audio-channels\": {\n                    \"mono\": \"\",\n                    \"_name\": \"\",\n                    \"stereo\": \"\"\n                },\n                \"choose-assets-on-launch\": \"\",\n                \"audio-enable\": \"\",\n                \"record-gameplay-data\": \"\",\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"episode-options\": {\n                \"creator-compat\": {\n                    \"_tooltip\": \"\",\n                    \"disable\": \"\",\n                    \"_name\": \"\",\n                    \"enable\": \"\",\n                    \"file-only\": \"\"\n                },\n                \"_header\": \"Bölüm Ayarları\",\n                \"playstyle\": {\n                    \"modern-tip\": \"\",\n                    \"classic\": \"\",\n                    \"classic-tip\": \"\",\n                    \"vanilla\": \"\",\n                    \"modern\": \"\",\n                    \"vanilla-tip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"main\": {\n                \"background-work\": \"\",\n                \"four-screen-mode\": {\n                    \"shared\": \"\",\n                    \"_name\": \"\",\n                    \"split\": \"\"\n                },\n                \"frame-skip\": \"\",\n                \"vsync\": \"\",\n                \"two-screen-mode\": {\n                    \"topbottom\": \"\",\n                    \"split\": \"\",\n                    \"shared\": \"\",\n                    \"_name\": \"\",\n                    \"smbx\": \"\"\n                },\n                \"background-work-tip\": \"\",\n                \"timing\": \"\",\n                \"multiplayer\": \"\",\n                \"language\": \"\",\n                \"_header\": \"\",\n                \"unlimited-framerate\": \"\",\n                \"discord-rpc\": \"\"\n            },\n            \"video\": {\n                \"internal-res\": {\n                    \"hd\": \"\",\n                    \"hello\": \"\",\n                    \"smbx\": \"\",\n                    \"dynamic\": \"\",\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"gba\": \"\",\n                    \"snes\": \"\",\n                    \"vga\": \"\",\n                    \"smbx-wide\": \"\",\n                    \"3ds\": \"\",\n                    \"nds\": \"\"\n                },\n                \"effects\": \"\",\n                \"scale-mode\": {\n                    \"_name\": \"\",\n                    \"center\": \"\",\n                    \"2x\": \"\",\n                    \"1x\": \"\",\n                    \"0_5x\": \"\",\n                    \"nearest\": \"\",\n                    \"full\": \"\",\n                    \"linear\": \"\",\n                    \"integer\": \"\",\n                    \"3x\": \"3x\"\n                },\n                \"info-meta\": \"\",\n                \"show-episode-title\": {\n                    \"_name\": \"Bölüm adı\",\n                    \"bottom\": \"Alt\",\n                    \"top\": \"Üst\",\n                    \"off\": \"Kapalı\",\n                    \"top-tip\": \"\",\n                    \"bottom-tip\": \"Speedrun sayacının üzerinde göster\"\n                },\n                \"show-playtime-counter\": {\n                    \"opaque\": \"Opak\",\n                    \"animated-tip\": \"Seviye sonuna gökkuşağı efekti ekle\",\n                    \"off\": \"Kapalı\",\n                    \"off-tip\": \"Sadece speedrun esnasında göster\",\n                    \"_tooltip\": \"\",\n                    \"_name\": \"Oynama sayısı sayacı\",\n                    \"transparent\": \"Yarı şeffaf\",\n                    \"animated\": \"\"\n                },\n                \"_header\": \"\",\n                \"world-map-stars-show-tip\": \"Toplanmış yıldızları seviye üzerinde göster\",\n                \"3d-compat-mode-tip\": \"Bütün objeleri bir 3D düzlemde çiz\",\n                \"battery-status\": {\n                    \"_name\": \"\",\n                    \"low-tip\": \"\",\n                    \"low\": \"\",\n                    \"fullscreen-tip\": \"\",\n                    \"fullscreen\": \"\",\n                    \"off\": \"\",\n                    \"on\": \"\"\n                },\n                \"info-run\": \"\",\n                \"display-controllers\": \"\",\n                \"enable-inter-level-fade-effect\": \"\",\n                \"fullscreen\": \"\",\n                \"info-game\": \"\",\n                \"3d-compat-mode\": \"\",\n                \"show-screen-shake\": \"Ekran sallanması\",\n                \"world-map-stars-show\": \"Dünya haritası yıldızları\",\n                \"video-system\": \"Sistem\",\n                \"show-medals-counter-tip\": \"\",\n                \"show-fps\": \"Framerate\",\n                \"show-fails-counter-tip\": \"\",\n                \"show-medals-counter\": \"Madalya sayacı\",\n                \"show-fails-counter\": \"Hata sayacı\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"3\": \"3\",\n                    \"_name\": \"Speedrun modu\",\n                    \"0\": \"0\",\n                    \"2\": \"2\",\n                    \"1\": \"1\"\n                }\n            },\n            \"audio\": {\n                \"sfx-modern-tip\": \"\",\n                \"audio-sfx-volume\": \"\",\n                \"sfx-modern\": \"\",\n                \"audio-prefs\": \"\",\n                \"sfx-spatial-audio-tip\": \"\",\n                \"sfx-audio-fx\": \"\",\n                \"sfx-spatial-audio\": \"\",\n                \"sfx-pet-beat\": \"\",\n                \"sfx-pet-beat-tip\": \"\",\n                \"_header\": \"\",\n                \"audio-music-volume\": \"\"\n            },\n            \"controls\": {\n                \"_header\": \"\"\n            },\n            \"compatibility\": {\n                \"bugfixes\": \"\",\n                \"_header\": \"\",\n                \"reset-all\": \"\",\n                \"features\": \"\",\n                \"autocode\": \"\",\n                \"_tooltip\": \"\"\n            },\n            \"view-credits\": {\n                \"_header\": \"\"\n            },\n            \"restartEngine\": \"\"\n        },\n        \"wordBack\": \"Geri\",\n        \"wordHide\": \"Gizle\",\n        \"wordNo\": \"Hayır\",\n        \"wordOff\": \"Kapalı\",\n        \"wordOn\": \"Açık\",\n        \"wordPlayer\": \"Oyuncu\",\n        \"wordProfile\": \"Profil\",\n        \"wordResume\": \"Devam Et\",\n        \"wordShow\": \"Göster\",\n        \"wordWaiting\": \"Bekleniyor\",\n        \"wordYes\": \"Evet\"\n    },\n    \"outro\": {\n        \"cppPortDevelopers\": \"C++ portunun geliştiricileri:\",\n        \"customSprites\": \"\",\n        \"engineCredits\": \"\",\n        \"gameCredits\": \"\",\n        \"levelDesign\": \"Seviye Tasarımı:\",\n        \"nameAndrewSpinks\": \"Andrew Spinks\",\n        \"nameVitalyNovichkov\": \"Vitaly Novichkov\",\n        \"originalBy\": \"Orijinal VB6 kodu:\",\n        \"psVitaPortBy\": \"PS Vita Portu:\",\n        \"specialThanks\": \"Özel Teşekkürler:\",\n        \"qualityControl\": \"Kalite Kontrol:\"\n    },\n    \"pluralRules\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_uk.json",
    "content": "{\n    \"outro\": {\n        \"cppPortDevelopers\": \"\",\n        \"qualityControl\": \"\",\n        \"nameVitalyNovichkov\": \"\",\n        \"psVitaPortBy\": \"\",\n        \"customSprites\": \"\",\n        \"levelDesign\": \"\",\n        \"gameCredits\": \"\",\n        \"nameAndrewSpinks\": \"\",\n        \"engineCredits\": \"\",\n        \"originalBy\": \"\",\n        \"specialThanks\": \"\"\n    },\n    \"menu\": {\n        \"options\": {\n            \"advanced\": {\n                \"_header\": \"\",\n                \"audio-enable\": \"\",\n                \"render\": {\n                    \"opengles11\": \"\",\n                    \"opengles\": \"\",\n                    \"opengles11-tip\": \"\",\n                    \"opengles-tip\": \"\",\n                    \"opengl\": \"\",\n                    \"hw\": \"\",\n                    \"opengl-tip\": \"\",\n                    \"opengl11\": \"\",\n                    \"sdl-tip\": \"\",\n                    \"sdl\": \"\",\n                    \"sw\": \"\",\n                    \"_name\": \"\",\n                    \"opengl11-tip\": \"\"\n                },\n                \"scale-down-textures\": {\n                    \"_tooltip\": \"\",\n                    \"safe\": \"\",\n                    \"all\": \"\",\n                    \"safe-tip\": \"\",\n                    \"none-tip\": \"\",\n                    \"_name\": \"\",\n                    \"all-tip\": \"\",\n                    \"none\": \"\"\n                },\n                \"audio-sample-rate\": {\n                    \"11025\": \"\",\n                    \"32000\": \"\",\n                    \"22050\": \"\",\n                    \"44100\": \"\",\n                    \"48000\": \"\",\n                    \"_name\": \"\",\n                    \"16000\": \"\"\n                },\n                \"audio-format\": {\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\"\n                },\n                \"internal-res-4p\": {\n                    \"fhd\": \"\",\n                    \"default\": \"\",\n                    \"_name\": \"\",\n                    \"qsmbx\": \"\",\n                    \"qhd\": \"\"\n                },\n                \"webm-recording\": \"\",\n                \"advanced-video\": \"\",\n                \"advanced-audio\": \"\",\n                \"audio-channels\": {\n                    \"_name\": \"\",\n                    \"mono\": \"\",\n                    \"stereo\": \"\"\n                },\n                \"log-level\": {\n                    \"debug\": \"\",\n                    \"none\": \"\",\n                    \"warning\": \"\",\n                    \"info\": \"\",\n                    \"critical\": \"\",\n                    \"_name\": \"\",\n                    \"fatal\": \"\"\n                },\n                \"record-gameplay-data\": \"\",\n                \"choose-assets-on-launch\": \"\",\n                \"_tooltip\": \"\",\n                \"audio-buffer-size\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\"\n                },\n                \"use-native-osk\": \"\",\n                \"fullscreen-type\": {\n                    \"_name\": \"\",\n                    \"exclusive\": \"\",\n                    \"desktop-tip\": \"\",\n                    \"desktop\": \"\",\n                    \"exclusive-tip\": \"\",\n                    \"auto\": \"\"\n                },\n                \"inaccurate-gifs-tip\": \"\",\n                \"inaccurate-gifs\": \"\",\n                \"fullscreen-depth\": {\n                    \"16\": \"\",\n                    \"auto\": \"\",\n                    \"_name\": \"\",\n                    \"32\": \"\"\n                },\n                \"fullscreen-res\": \"\"\n            },\n            \"main\": {\n                \"multiplayer\": \"\",\n                \"two-screen-mode\": {\n                    \"topbottom\": \"\",\n                    \"split\": \"\",\n                    \"shared\": \"\",\n                    \"_name\": \"\",\n                    \"smbx\": \"\"\n                },\n                \"unlimited-framerate\": \"\",\n                \"timing\": \"\",\n                \"four-screen-mode\": {\n                    \"shared\": \"\",\n                    \"_name\": \"\",\n                    \"split\": \"\"\n                },\n                \"background-work-tip\": \"\",\n                \"background-work\": \"\",\n                \"_header\": \"\",\n                \"frame-skip\": \"\",\n                \"language\": \"\",\n                \"discord-rpc\": \"\",\n                \"vsync\": \"\"\n            },\n            \"audio\": {\n                \"_header\": \"\",\n                \"sfx-modern-tip\": \"\",\n                \"sfx-modern\": \"\",\n                \"audio-prefs\": \"\",\n                \"sfx-audio-fx\": \"\",\n                \"audio-sfx-volume\": \"\",\n                \"audio-music-volume\": \"\",\n                \"sfx-spatial-audio-tip\": \"\",\n                \"sfx-spatial-audio\": \"\",\n                \"sfx-pet-beat-tip\": \"\",\n                \"sfx-pet-beat\": \"\"\n            },\n            \"compatibility\": {\n                \"_tooltip\": \"\",\n                \"_header\": \"\",\n                \"autocode\": \"\",\n                \"bugfixes\": \"\",\n                \"features\": \"\",\n                \"reset-all\": \"\"\n            },\n            \"video\": {\n                \"_header\": \"\",\n                \"scale-mode\": {\n                    \"0_5x\": \"\",\n                    \"1x\": \"\",\n                    \"full\": \"\",\n                    \"center\": \"\",\n                    \"2x\": \"\",\n                    \"_name\": \"\",\n                    \"nearest\": \"\",\n                    \"integer\": \"\",\n                    \"linear\": \"\",\n                    \"3x\": \"\"\n                },\n                \"internal-res\": {\n                    \"nds\": \"\",\n                    \"hello\": \"\",\n                    \"vga\": \"\",\n                    \"smbx-wide\": \"\",\n                    \"3ds\": \"\",\n                    \"smbx\": \"\",\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\",\n                    \"dynamic\": \"\",\n                    \"gba\": \"\",\n                    \"snes\": \"\",\n                    \"hd\": \"\"\n                },\n                \"show-episode-title\": {\n                    \"_name\": \"\",\n                    \"off\": \"\",\n                    \"bottom\": \"\",\n                    \"bottom-tip\": \"\",\n                    \"top-tip\": \"\",\n                    \"top\": \"\"\n                },\n                \"enable-inter-level-fade-effect\": \"\",\n                \"battery-status\": {\n                    \"_name\": \"\",\n                    \"fullscreen-tip\": \"\",\n                    \"low-tip\": \"\",\n                    \"on\": \"\",\n                    \"low\": \"\",\n                    \"off\": \"\",\n                    \"fullscreen\": \"\"\n                },\n                \"fullscreen\": \"\",\n                \"show-playtime-counter\": {\n                    \"off-tip\": \"\",\n                    \"off\": \"\",\n                    \"animated-tip\": \"\",\n                    \"transparent\": \"\",\n                    \"opaque\": \"\",\n                    \"_tooltip\": \"\",\n                    \"_name\": \"\",\n                    \"animated\": \"\"\n                },\n                \"world-map-stars-show\": \"\",\n                \"world-map-stars-show-tip\": \"\",\n                \"show-fails-counter-tip\": \"\",\n                \"show-fails-counter\": \"\",\n                \"3d-compat-mode-tip\": \"\",\n                \"show-fps\": \"\",\n                \"show-medals-counter\": \"\",\n                \"effects\": \"\",\n                \"display-controllers\": \"\",\n                \"3d-compat-mode\": \"\",\n                \"show-medals-counter-tip\": \"\",\n                \"video-system\": \"\",\n                \"info-game\": \"\",\n                \"info-run\": \"\",\n                \"show-screen-shake\": \"\",\n                \"info-meta\": \"\"\n            },\n            \"controls\": {\n                \"_header\": \"\"\n            },\n            \"restartEngine\": \"\",\n            \"episode-options\": {\n                \"creator-compat\": {\n                    \"_name\": \"\",\n                    \"_tooltip\": \"\",\n                    \"disable\": \"\",\n                    \"enable\": \"\",\n                    \"file-only\": \"\"\n                },\n                \"playstyle\": {\n                    \"vanilla\": \"\",\n                    \"modern\": \"\",\n                    \"modern-tip\": \"\",\n                    \"vanilla-tip\": \"\",\n                    \"_name\": \"\",\n                    \"classic\": \"\",\n                    \"classic-tip\": \"\"\n                },\n                \"_header\": \"\",\n                \"hub-resume-tip\": \"\",\n                \"hub-resume\": \"\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"0\": \"\",\n                    \"1\": \"\",\n                    \"_name\": \"\",\n                    \"2\": \"\",\n                    \"3\": \"\"\n                }\n            },\n            \"view-credits\": {\n                \"_header\": \"\"\n            }\n        },\n        \"wordBack\": \"\",\n        \"wordNo\": \"\",\n        \"controls\": {\n            \"controlsTitle\": \"\",\n            \"editor\": {\n                \"modeSelect\": \"\",\n                \"modeErase\": \"\",\n                \"scrollDown\": \"\",\n                \"fastScroll\": \"\",\n                \"switchScreens\": \"\",\n                \"scrollUp\": \"\",\n                \"prevSection\": \"\",\n                \"nextSection\": \"\",\n                \"scrollRight\": \"\",\n                \"testPlay\": \"\",\n                \"scrollLeft\": \"\"\n            },\n            \"options\": {\n                \"rumble\": \"\",\n                \"maxPlayers\": \"\",\n                \"batteryStatus\": \"\",\n                \"textEntryStyle\": \"\"\n            },\n            \"tDS\": {\n                \"buttonL\": \"\",\n                \"buttonStart\": \"\",\n                \"tStick\": \"\",\n                \"casePen\": \"\",\n                \"buttonR\": \"\",\n                \"buttonY\": \"\",\n                \"buttonZR\": \"\",\n                \"buttonZL\": \"\",\n                \"buttonSelect\": \"\",\n                \"cStick\": \"\",\n                \"buttonX\": \"\",\n                \"buttonB\": \"\",\n                \"buttonA\": \"\",\n                \"dPad\": \"\"\n            },\n            \"touchscreen\": {\n                \"option\": {\n                    \"scaleFactor\": \"\",\n                    \"layoutStyle\": \"\",\n                    \"scaleButtons\": \"\",\n                    \"interfaceStyle\": \"\",\n                    \"sStartSpacing\": \"\",\n                    \"resetLayout\": \"\",\n                    \"showCodeButton\": \"\",\n                    \"holdRun\": \"\",\n                    \"feedbackLength\": \"\",\n                    \"scaleDPad\": \"\",\n                    \"feedbackStrength\": \"\"\n                },\n                \"layout\": {\n                    \"phabletOld\": \"\",\n                    \"longOld\": \"\",\n                    \"tabletOld\": \"\",\n                    \"tight\": \"\",\n                    \"phoneOld\": \"\",\n                    \"standard\": \"\",\n                    \"tinyOld\": \"\"\n                },\n                \"style\": {\n                    \"XODA\": \"\",\n                    \"ABXY\": \"\",\n                    \"actions\": \"\"\n                }\n            },\n            \"types\": {\n                \"touchscreen\": \"\",\n                \"gamepad\": \"\",\n                \"keyboard\": \"\",\n                \"oldJoystick\": \"\"\n            },\n            \"wii\": {\n                \"typeWiimote\": \"\",\n                \"classic\": {\n                    \"buttonX\": \"\",\n                    \"buttonZL\": \"\",\n                    \"buttonY\": \"\",\n                    \"buttonZR\": \"\",\n                    \"lStick\": \"\",\n                    \"rStick\": \"\",\n                    \"buttonRT\": \"\",\n                    \"buttonLT\": \"\"\n                },\n                \"phraseNewNunchuck\": \"\",\n                \"wiimote\": {\n                    \"buttonPlus\": \"\",\n                    \"buttonMinus\": \"\",\n                    \"buttonHome\": \"\",\n                    \"caseIR\": \"\",\n                    \"buttonB\": \"\",\n                    \"shake\": \"\",\n                    \"button1\": \"\",\n                    \"buttonA\": \"\",\n                    \"dPad\": \"\",\n                    \"button2\": \"\"\n                },\n                \"typeNunchuck\": \"\",\n                \"phraseNewClassic\": \"\",\n                \"typeClassic\": \"\",\n                \"nunchuck\": {\n                    \"buttonC\": \"\",\n                    \"prefixN\": \"\",\n                    \"buttonZ\": \"\"\n                },\n                \"typeGamecube\": \"\"\n            },\n            \"wordButtons\": \"\",\n            \"hotkeys\": {\n                \"screenshot\": \"\",\n                \"toggleHUD\": \"\",\n                \"recordGif\": \"\",\n                \"fullscreen\": \"\",\n                \"enterCheats\": \"\",\n                \"debugInfo\": \"\",\n                \"legacyPause\": \"\",\n                \"vanillaCam\": \"\"\n            },\n            \"profile\": {\n                \"deleteProfile\": \"\",\n                \"cursorControls\": \"\",\n                \"hotkeys\": \"\",\n                \"editorControls\": \"\",\n                \"playerControls\": \"\",\n                \"renameProfile\": \"\"\n            },\n            \"joystickSimpleEditor\": \"\",\n            \"buttons\": {\n                \"down\": \"\",\n                \"up\": \"\",\n                \"run\": \"\",\n                \"drop\": \"\",\n                \"altJump\": \"\",\n                \"altRun\": \"\",\n                \"left\": \"\",\n                \"jump\": \"\",\n                \"start\": \"\",\n                \"right\": \"\"\n            },\n            \"cursor\": {\n                \"left\": \"\",\n                \"down\": \"\",\n                \"primary\": \"\",\n                \"tertiary\": \"\",\n                \"right\": \"\",\n                \"secondary\": \"\",\n                \"up\": \"\"\n            },\n            \"controlsDeleteKey\": \"\",\n            \"caseMouse\": \"\",\n            \"caseInvalid\": \"\",\n            \"phraseNewProfOldJoy\": \"\",\n            \"wordProfiles\": \"\",\n            \"controlsReallyDeleteProfile\": \"\",\n            \"controlsNotInUse\": \"\",\n            \"controlsNewProfile\": \"\",\n            \"controlsInUse\": \"\",\n            \"controlsConnected\": \"\",\n            \"caseTouch\": \"\",\n            \"controlsDeviceTypes\": \"\"\n        },\n        \"editor\": {\n            \"battles\": \"\",\n            \"promptNewWorldName\": \"\",\n            \"makeFor\": \"\",\n            \"newWorld\": \"\",\n            \"errorMissingResources\": \"\"\n        },\n        \"game\": {\n            \"gameSlotNew\": \"\",\n            \"gameSourceSlot\": \"\",\n            \"gameEraseSlot\": \"\",\n            \"gameNoBattleLevels\": \"\",\n            \"gameNoEpisodesToPlay\": \"\",\n            \"gameSlotContinue\": \"\",\n            \"gameBattleRandom\": \"\",\n            \"phraseTime\": \"\",\n            \"gameCopySave\": \"\",\n            \"gameEraseSave\": \"\",\n            \"phraseScore\": \"\",\n            \"gameTargetSlot\": \"\",\n            \"warnEpCompat\": \"\"\n        },\n        \"main\": {\n            \"mainEditor\": \"\",\n            \"mainExit\": \"\",\n            \"mainBattleGame\": \"\",\n            \"main1PlayerGame\": \"\",\n            \"mainMultiplayerGame\": \"\",\n            \"mainOptions\": \"\",\n            \"mainPlayEpisode\": \"\"\n        },\n        \"character\": {\n            \"selectCharacter\": \"\"\n        },\n        \"battle\": {\n            \"errorNoLevels\": \"\"\n        },\n        \"wordResume\": \"\",\n        \"wordShow\": \"\",\n        \"wordWaiting\": \"\",\n        \"wordYes\": \"\",\n        \"loading\": \"\",\n        \"wordProfile\": \"\",\n        \"wordOn\": \"\",\n        \"wordOff\": \"\",\n        \"wordPlayer\": \"\",\n        \"wordHide\": \"\",\n        \"abbrevMilliseconds\": \"\",\n        \"caseNone\": \"\"\n    },\n    \"editor\": {\n        \"file\": {\n            \"sectionWorld\": \"\",\n            \"convert\": {\n                \"descNew\": \"\"\n            },\n            \"confirmConfirmAction\": \"\",\n            \"commandSave\": \"\",\n            \"commandSaveAs\": \"\",\n            \"confirmConvertFormatTo\": \"\",\n            \"formatLegacy\": \"\",\n            \"optionActionWithoutSave\": \"\",\n            \"labelCurrentFile\": \"\",\n            \"optionYesSaveThenAction\": \"\",\n            \"optionCancelAction\": \"\",\n            \"optionCancelConversion\": \"\",\n            \"optionProceedWithConversion\": \"\",\n            \"actionClearLevel\": \"\",\n            \"actionExit\": \"\",\n            \"commandNew\": \"\",\n            \"confirmSaveBeforeAction\": \"\",\n            \"commandOpen\": \"\",\n            \"labelFormat\": \"\",\n            \"sectionLevel\": \"\",\n            \"actionClearWorld\": \"\",\n            \"actionOpen\": \"\",\n            \"formatModern\": \"\",\n            \"actionRevert\": \"\"\n        },\n        \"layers\": {\n            \"deletion\": {\n                \"confirmDelete\": \"\",\n                \"confirmPreserve\": \"\",\n                \"preserveLayerContents\": \"\",\n                \"header\": \"\",\n                \"cancel\": \"\"\n            },\n            \"desc\": {\n                \"att\": \"\"\n            },\n            \"labelAttachedLayer\": \"\",\n            \"labelMoveLayer\": \"\",\n            \"labelAttached\": \"\",\n            \"promptLayerName\": \"\",\n            \"abbrevAttLayer\": \"\",\n            \"default\": \"\",\n            \"label\": \"\",\n            \"header\": \"\",\n            \"itemNewLayer\": \"\"\n        },\n        \"letterLeft\": \"\",\n        \"letterRight\": \"\",\n        \"level\": {\n            \"bigBG\": \"\",\n            \"pathBG\": \"\",\n            \"levelName\": \"\",\n            \"startPos\": \"\",\n            \"gameStart\": \"\",\n            \"pathUnlocks\": \"\",\n            \"alwaysVis\": \"\"\n        },\n        \"npc\": {\n            \"ai\": {\n                \"LR\": \"\",\n                \"UD\": \"\",\n                \"aiIs\": \"\",\n                \"swim\": \"\",\n                \"target\": \"\",\n                \"jump\": \"\",\n                \"leap\": \"\",\n                \"headerCustomAi\": \"\",\n                \"use1_0Ai\": \"\"\n            },\n            \"abbrevGen\": \"\",\n            \"inContainer\": \"\",\n            \"props\": {\n                \"active\": \"\",\n                \"attachSurface\": \"\",\n                \"facing\": \"\"\n            },\n            \"inertNice\": \"\",\n            \"stuckStop\": \"\",\n            \"gen\": {\n                \"effectIs\": \"\",\n                \"effectWarp\": \"\",\n                \"effectShoot\": \"\",\n                \"header\": \"\",\n                \"direction\": \"\"\n            },\n            \"tooltipExpandSection\": \"\"\n        },\n        \"mapPos\": \"\",\n        \"pageBlankOfBlank\": \"\",\n        \"phraseAreYouSure\": \"\",\n        \"phraseRadiusIndex\": \"\",\n        \"phraseDelayIsMs\": \"\",\n        \"phraseCountMore\": \"\",\n        \"phraseWarpIndex\": \"\",\n        \"section\": {\n            \"horizWrap\": \"\",\n            \"offscreenExit\": \"\",\n            \"noTurnBack\": \"\",\n            \"vertWrap\": \"\",\n            \"underwater\": \"\",\n            \"setBounds\": \"\",\n            \"scroll\": \"\"\n        },\n        \"select\": {\n            \"allSectPropBlankForEventN\": \"\",\n            \"pathBlankUnlock\": \"\",\n            \"sectBlankPropBlankForEventN\": \"\",\n            \"sectionBlankPropBlank\": \"\",\n            \"soundForEventN\": \"\",\n            \"worldMusic\": \"\",\n            \"warpTransitEffect\": \"\"\n        },\n        \"warp\": {\n            \"style\": {\n                \"door\": \"\",\n                \"pipe\": \"\",\n                \"blipInstant\": \"\",\n                \"portal\": \"\",\n                \"style\": \"\"\n            },\n            \"dir\": \"\",\n            \"out\": \"\",\n            \"needStarCount\": \"\",\n            \"ride\": \"\",\n            \"transit\": {\n                \"name3\": \"\",\n                \"name5\": \"\",\n                \"name4\": \"\",\n                \"name2\": \"\",\n                \"name1\": \"\",\n                \"name0\": \"\"\n            },\n            \"allow\": \"\",\n            \"speed\": \"\",\n            \"target\": \"\",\n            \"starLockMessage\": \"\",\n            \"title\": \"\",\n            \"effect\": \"\",\n            \"item\": \"\",\n            \"in\": \"\",\n            \"showStartScene\": \"\",\n            \"showStarCount\": \"\",\n            \"toMap\": \"\",\n            \"to\": \"\",\n            \"needFloor\": \"\",\n            \"needKey\": \"\",\n            \"lvlWarp\": \"\",\n            \"twoWay\": \"\",\n            \"cannonExit\": \"\",\n            \"placing\": \"\"\n        },\n        \"water\": {\n            \"title\": \"\"\n        },\n        \"wordEvent\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\",\n            \"typeLabel\": \"\"\n        },\n        \"wordNPC\": {\n            \"genitive\": \"\",\n            \"nominative\": \"\"\n        },\n        \"wordHeight\": \"\",\n        \"wordInstant\": \"\",\n        \"wordWidth\": \"\",\n        \"tooltip\": {\n            \"BGOs\": \"\",\n            \"NPCs\": \"\",\n            \"erase\": \"\",\n            \"area\": \"\",\n            \"blocks\": \"\",\n            \"paths\": \"\",\n            \"water\": \"\",\n            \"eraseAll\": \"\",\n            \"file\": \"\",\n            \"layers\": \"\",\n            \"select\": \"\",\n            \"settings\": \"\",\n            \"show\": \"\",\n            \"warps\": \"\",\n            \"tiles\": \"\",\n            \"scenes\": \"\",\n            \"levels\": \"\",\n            \"events\": \"\",\n            \"music\": \"\"\n        },\n        \"toggleMagicBlock\": \"\",\n        \"testPlay\": {\n            \"magicHand\": \"\",\n            \"char\": \"\",\n            \"boot\": \"\",\n            \"power\": \"\",\n            \"pet\": \"\"\n        },\n        \"wordText\": \"\",\n        \"events\": {\n            \"controlsForEventN\": \"\",\n            \"bounds\": {\n                \"changeSectionBoundsToCurrent\": \"\",\n                \"shouldEvent\": \"\",\n                \"changeAllSectionBoundsToCurrent\": \"\"\n            },\n            \"deletion\": {\n                \"cancel\": \"\",\n                \"confirm\": \"\",\n                \"deletingEvent\": \"\"\n            },\n            \"label\": {\n                \"destroy\": \"\",\n                \"death\": \"\",\n                \"hit\": \"\",\n                \"enter\": \"\",\n                \"talk\": \"\",\n                \"next\": \"\",\n                \"activate\": \"\",\n                \"layerClear\": \"\"\n            },\n            \"layers\": {\n                \"headerToggle\": \"\",\n                \"headerShow\": \"\",\n                \"headerMove\": \"\",\n                \"headerHide\": \"\"\n            },\n            \"promptEventName\": \"\",\n            \"letter\": {\n                \"talk\": \"\",\n                \"layerClear\": \"\",\n                \"enter\": \"\",\n                \"hit\": \"\",\n                \"activate\": \"\",\n                \"destroy\": \"\",\n                \"death\": \"\"\n            },\n            \"props\": {\n                \"autostart\": \"\",\n                \"endGame\": \"\",\n                \"sound\": \"\",\n                \"layerSmoke\": \"\",\n                \"controls\": \"\"\n            },\n            \"sections\": {\n                \"phraseAllSections\": \"\",\n                \"actionSet\": \"\",\n                \"propBackground\": \"\",\n                \"actionKeep\": \"\",\n                \"actionReset\": \"\",\n                \"propMusic\": \"\",\n                \"propBounds\": \"\"\n            },\n            \"desc\": {\n                \"phraseTriggersAfterTemplate\": \"\",\n                \"layerClear\": \"\",\n                \"enter\": \"\",\n                \"hit\": \"\",\n                \"death\": \"\",\n                \"activate\": \"\",\n                \"phraseTriggersWhenTemplate\": \"\",\n                \"destroy\": \"\",\n                \"talk\": \"\"\n            },\n            \"itemNewEvent\": \"\",\n            \"settingsForEvent\": \"\",\n            \"header\": \"\",\n            \"promptEventText\": \"\",\n            \"headerTriggerEvent\": \"\"\n        },\n        \"phraseGenericIndex\": \"\",\n        \"block\": {\n            \"canBreak\": \"\",\n            \"canBreakTooltip\": \"\",\n            \"inside\": \"\",\n            \"invis\": \"\",\n            \"letterHeight\": \"\",\n            \"pickContents\": \"\",\n            \"slick\": \"\",\n            \"letterWidth\": \"\"\n        },\n        \"browser\": {\n            \"askOverwriteFile\": \"\",\n            \"itemNewFolder\": \"\",\n            \"itemNewFile\": \"\",\n            \"openFile\": \"\",\n            \"newFile\": \"\",\n            \"saveFile\": \"\"\n        },\n        \"letterDown\": \"\",\n        \"letterCoordY\": \"\",\n        \"letterCoordX\": \"\",\n        \"wordMode\": \"\",\n        \"phraseSectionIndex\": \"\",\n        \"phraseTextOf\": \"\",\n        \"world\": {\n            \"allowChars\": \"\",\n            \"name\": \"\",\n            \"totalStars\": \"\",\n            \"hubWorld\": \"\",\n            \"introLevel\": \"\",\n            \"retryOnFail\": \"\",\n            \"phraseCreditIndex\": \"\"\n        },\n        \"wordCoins\": \"\",\n        \"wordEnabled\": \"\",\n        \"letterUp\": \"\",\n        \"labelSortOffset\": \"\",\n        \"labelSortLayer\": \"\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"wordDisconnect\": \"\",\n            \"phraseHoldStart\": \"\",\n            \"dropAddTitle\": \"\",\n            \"phraseTestControls\": \"\",\n            \"splitPressSelect_1\": \"\",\n            \"phraseChangeChar\": \"\",\n            \"phraseDropMe\": \"\",\n            \"splitPressSelect_2\": \"\",\n            \"phraseTestProfile\": \"\",\n            \"phraseStartToResume\": \"\",\n            \"phraseStartToForceRes\": \"\",\n            \"phraseSetControls\": \"\",\n            \"phraseWaitingForInput\": \"\",\n            \"reconnectTitle\": \"\",\n            \"phrasePressAButton\": \"\",\n            \"phraseDropPX\": \"\",\n            \"phraseForceResume\": \"\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"\"\n        },\n        \"error\": {\n            \"openFileFailed\": \"\",\n            \"errorInvalidEnterWarp\": \"\",\n            \"warpNeedStarCount\": \"\",\n            \"errorNoStartPoint\": \"\",\n            \"errorTooOldGameAssets\": \"\",\n            \"openIPCDataFailed\": \"\",\n            \"IPCTimeOut\": \"\",\n            \"errorTooOldEngine\": \"\"\n        },\n        \"hint\": {\n            \"no-lives-old\": \"\",\n            \"gray-bricks\": \"\",\n            \"char5-bombs\": \"\",\n            \"shoe-block\": \"\",\n            \"heavy-duck\": \"\",\n            \"rainbow-surf\": \"\",\n            \"no-lives-new\": \"\",\n            \"pound-altrun\": \"\",\n            \"pound-down\": \"\"\n        },\n        \"pause\": {\n            \"enterCode\": \"\",\n            \"saveAndContinue\": \"\",\n            \"saveAndQuit\": \"\",\n            \"quit\": \"\",\n            \"resetCheckpoints\": \"\",\n            \"restartLevel\": \"\",\n            \"quitTesting\": \"\",\n            \"returnToEditor\": \"\",\n            \"playerSetup\": \"\",\n            \"continue\": \"\"\n        },\n        \"msgbox\": {\n            \"sysInfoError\": \"\",\n            \"sysInfoTitle\": \"\",\n            \"sysInfoWarning\": \"\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"\"\n        },\n        \"loader\": {\n            \"statusLoadData\": \"\",\n            \"statusGameInfo\": \"\",\n            \"statusLoadFile\": \"\",\n            \"statusFinishing\": \"\",\n            \"statusAssetPacks\": \"\",\n            \"statusTranslations\": \"\"\n        },\n        \"ipcStatus\": {\n            \"loadingdone\": \"\",\n            \"dataAccepted\": \"\",\n            \"dataTransferStarted\": \"\",\n            \"errorTimeout\": \"\",\n            \"dataValid\": \"\",\n            \"waitingInput\": \"\"\n        },\n        \"screenPaused\": \"\"\n    },\n    \"languageName\": \"\",\n    \"pluralRules\": \"\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_zh-cn.json",
    "content": "{\n    \"editor\": {\n        \"block\": {\n            \"canBreak\": \"可顶碎\",\n            \"canBreakTooltip\": \"旧版本：顶砖块后将会破碎\",\n            \"inside\": \"包含：\",\n            \"invis\": \"隐形\",\n            \"letterHeight\": \"高 \",\n            \"letterWidth\": \"宽 \",\n            \"pickContents\": \"选择砖块包含道具\",\n            \"slick\": \"打滑\"\n        },\n        \"browser\": {\n            \"askOverwriteFile\": \"是否覆盖文件{0}？\",\n            \"itemNewFile\": \"<新文件>\",\n            \"itemNewFolder\": \"<新建文件夹>\",\n            \"newFile\": \"新建文件\",\n            \"openFile\": \"打开文件\",\n            \"saveFile\": \"删除文件\"\n        },\n        \"events\": {\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"改变所有场景范围到当前设置吗？\",\n                \"changeSectionBoundsToCurrent\": \"改变场景{0}的范围到当前设置吗？\",\n                \"shouldEvent\": \"要使事件{0}\"\n            },\n            \"controlsForEventN\": \"强制按下玩家按键：{0}\",\n            \"deletion\": {\n                \"cancel\": \"否：不删除事件\",\n                \"confirm\": \"是：删除事件\",\n                \"deletingEvent\": \"删除{0}事件\"\n            },\n            \"desc\": {\n                \"activate\": \"NPC在当前画面出现时\",\n                \"death\": \"NPC死亡时\",\n                \"destroy\": \"砖块破碎时\",\n                \"enter\": \"进入传送点时\",\n                \"hit\": \"砖块被撞击时\",\n                \"layerClear\": \"当前图层中的对象为空时\",\n                \"phraseTriggersAfterTemplate\": \"将于事件：{2}经过{1}毫秒之后，触发{0}事件\",\n                \"phraseTriggersWhenTemplate\": \"{0}：{1}触发该事件\",\n                \"talk\": \"和NPC对话后\"\n            },\n            \"header\": \"事件：\",\n            \"headerTriggerEvent\": \"触发\",\n            \"itemNewEvent\": \"<新事件>\",\n            \"label\": {\n                \"activate\": \"出现\",\n                \"death\": \"死亡\",\n                \"destroy\": \"破碎\",\n                \"enter\": \"进入传送点\",\n                \"hit\": \"撞击\",\n                \"layerClear\": \"空白图层\",\n                \"next\": \"下一个\",\n                \"talk\": \"对话\"\n            },\n            \"layers\": {\n                \"headerHide\": \"隐藏：\",\n                \"headerMove\": \"移动：\",\n                \"headerShow\": \"显示：\",\n                \"headerToggle\": \"切换：\"\n            },\n            \"letter\": {\n                \"activate\": \"出：\",\n                \"death\": \"死：\",\n                \"destroy\": \"碎：\",\n                \"enter\": \"进：\",\n                \"hit\": \"撞：\",\n                \"layerClear\": \"空：\",\n                \"talk\": \"话：\"\n            },\n            \"promptEventName\": \"事件名称：\",\n            \"promptEventText\": \"文字信息：\",\n            \"props\": {\n                \"autostart\": \"进入关卡时执行事件\",\n                \"controls\": \"按键\",\n                \"endGame\": \"游戏通关\",\n                \"sound\": \"声音\",\n                \"layerSmoke\": \"图层烟雾效果\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"当前\",\n                \"actionReset\": \"默认\",\n                \"actionSet\": \"指定\",\n                \"phraseAllSections\": \"所有场景\",\n                \"propBackground\": \"背景图片\",\n                \"propBounds\": \"场景范围\",\n                \"propMusic\": \"音乐\"\n            },\n            \"settingsForEvent\": \"{0}事件选项\"\n        },\n        \"file\": {\n            \"actionClearLevel\": \"清除关卡\",\n            \"actionClearWorld\": \"清除大地图\",\n            \"actionExit\": \"退出\",\n            \"actionOpen\": \"打开\",\n            \"actionRevert\": \"恢复\",\n            \"commandNew\": \"新建\",\n            \"commandOpen\": \"打开...\",\n            \"commandSave\": \"保存\",\n            \"commandSaveAs\": \"另存为...\",\n            \"confirmConfirmAction\": \"您确定要{0}吗？\",\n            \"confirmConvertFormatTo\": \"是否转换格式到{0}？\",\n            \"confirmSaveBeforeAction\": \"{0}前是否保存文件？\",\n            \"convert\": {\n                \"descNew\": \"文件格式将会转换，但不会删除源文件。\\n\\n请在保存文件之后检查不支持的功能。\"\n            },\n            \"formatLegacy\": \"旧版本\",\n            \"formatModern\": \"新版本\",\n            \"labelCurrentFile\": \"当前文件：\",\n            \"labelFormat\": \"格式：\",\n            \"optionActionWithoutSave\": \"否：{0}而不保存文件\",\n            \"optionCancelAction\": \"取消：返回上一级菜单\",\n            \"optionCancelConversion\": \"取消转换\",\n            \"optionProceedWithConversion\": \"确认转换\",\n            \"optionYesSaveThenAction\": \"是：{0}并保存文件\",\n            \"sectionLevel\": \"关卡\",\n            \"sectionWorld\": \"大地图\"\n        },\n        \"layers\": {\n            \"abbrevAttLayer\": \"跟随：\",\n            \"default\": \"默认\",\n            \"deletion\": {\n                \"cancel\": \"取消：返回上一级菜单\",\n                \"confirmDelete\": \"否：*删除全部对象*\",\n                \"confirmPreserve\": \"是：移动到默认图层\",\n                \"header\": \"删除{0}图层\",\n                \"preserveLayerContents\": \"是否保留图层对象？\"\n            },\n            \"desc\": {\n                \"att\": \"当NPC移动时，图层也将随之移动\"\n            },\n            \"header\": \"图层：\",\n            \"itemNewLayer\": \"<新图层>\",\n            \"label\": \"图层：\",\n            \"labelAttached\": \"跟随：\",\n            \"labelAttachedLayer\": \"跟随图层：\",\n            \"labelMoveLayer\": \"移动图层：\",\n            \"promptLayerName\": \"图层名称\"\n        },\n        \"letterCoordX\": \"X\",\n        \"letterCoordY\": \"Y\",\n        \"letterDown\": \"下\",\n        \"letterLeft\": \"左\",\n        \"letterRight\": \"右\",\n        \"letterUp\": \"上\",\n        \"level\": {\n            \"alwaysVis\": \"总是可见\",\n            \"bigBG\": \"大背景\",\n            \"gameStart\": \"游戏起点\",\n            \"levelName\": \"关卡名称\",\n            \"pathBG\": \"路线背景\",\n            \"pathUnlocks\": \"路线开启\",\n            \"startPos\": \"游戏起点\"\n        },\n        \"mapPos\": \"地图位置：\",\n        \"npc\": {\n            \"abbrevGen\": \"生成器\",\n            \"ai\": {\n                \"LR\": \"左右\",\n                \"UD\": \"上下\",\n                \"aiIs\": \"动作：{0}\",\n                \"headerCustomAi\": \"自定义动作：\",\n                \"jump\": \"跳跃\",\n                \"leap\": \"飞跃\",\n                \"swim\": \"游泳\",\n                \"target\": \"跟踪\",\n                \"use1_0Ai\": \"使用1.0 AI？\"\n            },\n            \"gen\": {\n                \"direction\": \"方向\",\n                \"effectIs\": \"特效：{0}\",\n                \"effectShoot\": \"喷射\",\n                \"effectWarp\": \"水管\",\n                \"header\": \"生成器选项\"\n            },\n            \"inContainer\": \"包含\",\n            \"inertNice\": \"友好\",\n            \"props\": {\n                \"active\": \"启动\",\n                \"attachSurface\": \"位置\",\n                \"facing\": \"方向\"\n            },\n            \"stuckStop\": \"静止\",\n            \"tooltipExpandSection\": \"扩展场景\"\n        },\n        \"pageBlankOfBlank\": \"第{0}/{1}页\",\n        \"phraseAreYouSure\": \"您确定吗？\",\n        \"phraseCountMore\": \"更多{0}\",\n        \"phraseDelayIsMs\": \"延迟：{0}毫秒\",\n        \"phraseGenericIndex\": \"编号：{0}\",\n        \"phraseRadiusIndex\": \"半径：{0}\",\n        \"phraseSectionIndex\": \"场景{0}\",\n        \"phraseTextOf\": \"{0}的文字信息\",\n        \"phraseWarpIndex\": \"传送点{0}号\",\n        \"section\": {\n            \"horizWrap\": \"横向循环\",\n            \"noTurnBack\": \"单向卷轴\",\n            \"offscreenExit\": \"场景边缘出口\",\n            \"scroll\": \"强制卷轴\",\n            \"setBounds\": \"设置范围\",\n            \"underwater\": \"水下\",\n            \"vertWrap\": \"纵向循环\"\n        },\n        \"select\": {\n            \"allSectPropBlankForEventN\": \"更改所有场景{0}于{1}事件\",\n            \"pathBlankUnlock\": \"路线向{0}开启条件\",\n            \"sectBlankPropBlankForEventN\": \"更改场景{0}的{1}于{2}事件\",\n            \"sectionBlankPropBlank\": \"场景{0}{1}\",\n            \"soundForEventN\": \"执行{0}事件时播放声音\",\n            \"warpTransitEffect\": \"过渡效果\",\n            \"worldMusic\": \"大地图音乐\"\n        },\n        \"testPlay\": {\n            \"boot\": \"靴子\",\n            \"char\": \"角色\",\n            \"magicHand\": \"鼠标\",\n            \"pet\": \"坐骑\",\n            \"power\": \"状态\"\n        },\n        \"toggleMagicBlock\": \"智能编辑模式\",\n        \"tooltip\": {\n            \"BGOs\": \"背景对象\",\n            \"NPCs\": \"NPC\",\n            \"blocks\": \"砖块\",\n            \"erase\": \"删除\",\n            \"eraseAll\": \"删除全部\",\n            \"events\": \"事件\",\n            \"file\": \"文件\",\n            \"layers\": \"图层\",\n            \"levels\": \"关卡\",\n            \"music\": \"音乐\",\n            \"paths\": \"路线\",\n            \"scenes\": \"风景\",\n            \"select\": \"选择\",\n            \"settings\": \"设置\",\n            \"show\": \"显示\",\n            \"tiles\": \"地形\",\n            \"warps\": \"传送点\",\n            \"water\": \"水流\",\n            \"area\": \"地图区域\"\n        },\n        \"warp\": {\n            \"allow\": \"允许：\",\n            \"cannonExit\": \"水管炮出口\",\n            \"dir\": \"方向\",\n            \"effect\": \"过渡效果：\",\n            \"in\": \"入口\",\n            \"item\": \"携带道具\",\n            \"lvlWarp\": \"传送到另一关卡\",\n            \"needFloor\": \"需要站立\",\n            \"needKey\": \"需要钥匙\",\n            \"needStarCount\": \"需要{0}{2}{1}\",\n            \"out\": \"出口\",\n            \"placing\": \"放置：\",\n            \"ride\": \"坐骑\",\n            \"showStarCount\": \"显示星星数量\",\n            \"showStartScene\": \"显示开场界面\",\n            \"speed\": \"速度：\",\n            \"starLockMessage\": \"需要星星时的信息\",\n            \"style\": {\n                \"blipInstant\": \"瞬移\",\n                \"door\": \"门\",\n                \"pipe\": \"水管\",\n                \"portal\": \"传送门\",\n                \"style\": \"传送类别：\"\n            },\n            \"target\": \"目标：\",\n            \"title\": \"传送点\",\n            \"to\": \"传送到：{0}\",\n            \"toMap\": \"传送到大地图\",\n            \"transit\": {\n                \"name0\": \"无\",\n                \"name1\": \"滚动\",\n                \"name2\": \"淡入淡出\",\n                \"name3\": \"圆形\",\n                \"name4\": \"水平翻转\",\n                \"name5\": \"垂直翻转\"\n            },\n            \"twoWay\": \"双向传送\"\n        },\n        \"water\": {\n            \"title\": \"流体\"\n        },\n        \"wordCoins\": \"金币\",\n        \"wordEnabled\": \"已启用\",\n        \"wordEvent\": {\n            \"genitive\": \"事件\",\n            \"nominative\": \"事件\",\n            \"typeLabel\": \"触发{0}事件：\"\n        },\n        \"wordHeight\": \"高\",\n        \"wordInstant\": \"立即\",\n        \"wordMode\": \"流体类型\",\n        \"wordNPC\": {\n            \"genitive\": \"NPC\",\n            \"nominative\": \"NPC\"\n        },\n        \"wordText\": \"信息\",\n        \"wordWidth\": \"宽\",\n        \"world\": {\n            \"allowChars\": \"可用角色\",\n            \"hubWorld\": \"禁用大地图\",\n            \"introLevel\": \"开场关卡\",\n            \"name\": \"地图名称\",\n            \"phraseCreditIndex\": \"地图制作团队 第{0}行：\",\n            \"retryOnFail\": \"失败之后重新开始\",\n            \"totalStars\": \"总星星数：\"\n        },\n        \"labelSortLayer\": \"叠放图层：\",\n        \"labelSortOffset\": \"叠放次序：\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"dropAddTitle\": \"加入/离开游戏\",\n            \"phraseChangeChar\": \"更换角色\",\n            \"phraseDropMe\": \"离开游戏\",\n            \"phraseDropPX\": \"移除玩家{0}的游戏\",\n            \"phraseForceResume\": \"强制恢复游戏\",\n            \"phraseHoldStart\": \"按住开始键\",\n            \"phrasePressAButton\": \"按下一个按键\",\n            \"phraseSetControls\": \"设置按键\",\n            \"phraseStartToForceRes\": \"按开始键强制恢复游戏\",\n            \"phraseStartToResume\": \"按开始键恢复游戏\",\n            \"phraseTestControls\": \"测试按键\",\n            \"phraseWaitingForInput\": \"等待玩家加入……\",\n            \"reconnectTitle\": \"重新连接\",\n            \"splitPressSelect_1\": \"按选择键\",\n            \"splitPressSelect_2\": \"进入按键选项设置\",\n            \"wordDisconnect\": \"断开\",\n            \"phraseTestProfile\": \"测试配置\"\n        },\n        \"error\": {\n            \"errorInvalidEnterWarp\": \"作为关卡起点的传送点{1}号不存在，因此无法进入关卡。\\n传送点总数：{2}\\n\\n文件：{0}\",\n            \"errorNoStartPoint\": \"未放置玩家起点或传送点入口，因此无法进入关卡。\\n\\n文件：{0}\",\n            \"openFileFailed\": \"无法打开文件\\\"{0}\\\"：文件不存在或已损坏。\",\n            \"warpNeedStarCount\": \"你需要{0}{2}{1}才能进入此传送点。\",\n            \"IPCTimeOut\": \"加载编辑器时无响应，即将退出游戏。\",\n            \"openIPCDataFailed\": \"无法接收文件数据：文件可能已损坏，或者发生其他错误。\",\n            \"errorTooOldEngine\": \"该文件需要版本{0}或更高版本的TheXTech引擎才能正常运作，当前TheXTech引擎版本为{1}。请更新引擎。\",\n            \"errorTooOldGameAssets\": \"该文件需要版本{0}或更高版本的游戏组件才能正常运作，当前游戏组件版本为{1}。请更新游戏组件。\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0}分{1}秒\"\n        },\n        \"pause\": {\n            \"continue\": \"继续游戏\",\n            \"enterCode\": \"输入指令\",\n            \"quit\": \"退出\",\n            \"quitTesting\": \"结束测试\",\n            \"resetCheckpoints\": \"重置中间点\",\n            \"restartLevel\": \"重新开始\",\n            \"returnToEditor\": \"返回编辑器\",\n            \"saveAndContinue\": \"保存并继续\",\n            \"saveAndQuit\": \"保存并退出\",\n            \"playerSetup\": \"玩家选项\"\n        },\n        \"msgbox\": {\n            \"sysInfoError\": \"错误！\",\n            \"sysInfoTitle\": \"信息\",\n            \"sysInfoWarning\": \"警告！\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"扫描关卡中……\"\n        },\n        \"hint\": {\n            \"no-lives-old\": \"注意：如果本次通关失败，游戏将会结束！\",\n            \"no-lives-new\": \"即使本次通关失败，也不会发生什么事情的；但请留意您的得分。\",\n            \"rainbow-surf\": \"在拿起彩虹龟壳的时候，按下“下”键，开始冲浪吧！\",\n            \"char5-bombs\": \"按下“奔跑”键可收集炸弹，按下“第二奔跑”键可投掷炸弹。\",\n            \"gray-bricks\": \"神秘力量可以破坏部分砖块。\",\n            \"heavy-duck\": \"玩家下蹲时，可以抵挡大多数火焰！\",\n            \"shoe-block\": \"穿上鞋子时，可以抵挡大多数火焰！\",\n            \"pound-altrun\": \"按下“第二奔跑”键，进行震地！\",\n            \"pound-down\": \"按下“下”键，进行震地！\"\n        },\n        \"screenPaused\": \"游戏暂停\",\n        \"ipcStatus\": {\n            \"dataAccepted\": \"已接收到数据，开始解析……\",\n            \"dataTransferStarted\": \"开始传输数据……\",\n            \"dataValid\": \"接收的数据是有效数据\",\n            \"errorTimeout\": \"错误：等待超时。\",\n            \"loadingdone\": \"加载完成，即将开始游戏……\",\n            \"waitingInput\": \"等待玩家按键输入……\"\n        },\n        \"loader\": {\n            \"statusAssetPacks\": \"游戏组件\",\n            \"statusFinishing\": \"完成加载……\",\n            \"statusGameInfo\": \"游戏信息\",\n            \"statusLoadData\": \"正在加载数据……\",\n            \"statusLoadFile\": \"正在加载：{0}\",\n            \"statusTranslations\": \"翻译\",\n            \"loading\": \"加载中……\"\n        }\n    },\n    \"languageName\": \"简体中文\",\n    \"menu\": {\n        \"abbrevMilliseconds\": \"毫秒\",\n        \"battle\": {\n            \"errorNoLevels\": \"对战关卡列表为空，无法进入对战模式。\"\n        },\n        \"caseNone\": \"<无>\",\n        \"character\": {\n            \"selectCharacter\": \"{0}游戏\"\n        },\n        \"controls\": {\n            \"buttons\": {\n                \"altJump\": \"第二跳跃\",\n                \"altRun\": \"第二奔跑\",\n                \"down\": \"下\",\n                \"drop\": \"取出道具\",\n                \"jump\": \"跳跃\",\n                \"left\": \"左\",\n                \"right\": \"右\",\n                \"run\": \"奔跑\",\n                \"start\": \"开始\",\n                \"up\": \"上\"\n            },\n            \"caseInvalid\": \"（无效）\",\n            \"caseMouse\": \"（鼠标）\",\n            \"caseTouch\": \"（触屏）\",\n            \"controlsConnected\": \"已连接：\",\n            \"controlsDeleteKey\": \"（按第二跳跃键清除）\",\n            \"controlsDeleteKey2\": \"（按第二奔跑键清除）\",\n            \"controlsDeviceTypes\": \"设备类型\",\n            \"controlsInUse\": \"（已使用）\",\n            \"controlsNewProfile\": \"<新建配置>\",\n            \"controlsNotInUse\": \"（未使用）\",\n            \"controlsReallyDeleteProfile\": \"是否删除配置？\",\n            \"controlsTitle\": \"按键选项\",\n            \"cursor\": {\n                \"down\": \"鼠标向下移动\",\n                \"left\": \"鼠标向左移动\",\n                \"primary\": \"鼠标左键\",\n                \"right\": \"鼠标向右移动\",\n                \"secondary\": \"鼠标右键\",\n                \"tertiary\": \"鼠标滚轮\",\n                \"up\": \"鼠标向上移动\"\n            },\n            \"editor\": {\n                \"fastScroll\": \"快速滚动\",\n                \"modeErase\": \"橡皮擦\",\n                \"modeSelect\": \"选择模式\",\n                \"nextSection\": \"下一场景\",\n                \"prevSection\": \"上一场景\",\n                \"scrollDown\": \"向下滚动\",\n                \"scrollLeft\": \"向左滚动\",\n                \"scrollRight\": \"向右滚动\",\n                \"scrollUp\": \"向上滚动\",\n                \"switchScreens\": \"显示面板\",\n                \"testPlay\": \"测试关卡\"\n            },\n            \"hotkeys\": {\n                \"debugInfo\": \"调试信息\",\n                \"enterCheats\": \"输入作弊指令\",\n                \"fullscreen\": \"全屏\",\n                \"legacyPause\": \"旧版暂停菜单\",\n                \"recordGif\": \"GIF 动画录制\",\n                \"screenshot\": \"截图\",\n                \"toggleHUD\": \"状态栏切换\",\n                \"vanillaCam\": \"原始画面大小\"\n            },\n            \"options\": {\n                \"batteryStatus\": \"电池状态\",\n                \"maxPlayers\": \"玩家上限\",\n                \"rumble\": \"Rumble控制器\",\n                \"textEntryStyle\": \"文字输入使用设备\",\n                \"altMenuControls\": \"切换确定/返回按键\"\n            },\n            \"phraseNewProfOldJoy\": \"新建老式手柄配置\",\n            \"profile\": {\n                \"cursorControls\": \"鼠标控制\",\n                \"deleteProfile\": \"删除配置\",\n                \"editorControls\": \"编辑器控制\",\n                \"hotkeys\": \"热键\",\n                \"playerControls\": \"玩家控制\",\n                \"renameProfile\": \"重命名配置\"\n            },\n            \"tDS\": {\n                \"buttonA\": \"A\",\n                \"buttonB\": \"B\",\n                \"buttonL\": \"L\",\n                \"buttonR\": \"R\",\n                \"buttonSelect\": \"选择\",\n                \"buttonStart\": \"开始\",\n                \"buttonX\": \"X\",\n                \"buttonY\": \"Y\",\n                \"buttonZL\": \"ZL\",\n                \"buttonZR\": \"ZR\",\n                \"cStick\": \"右摇杆\",\n                \"casePen\": \"(触控笔)\",\n                \"dPad\": \"方向键\",\n                \"tStick\": \"左摇杆\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"longOld\": \"宽屏（旧）\",\n                    \"phabletOld\": \"大平板（旧）\",\n                    \"phoneOld\": \"手机（旧）\",\n                    \"standard\": \"普通\",\n                    \"tabletOld\": \"平板（旧）\",\n                    \"tight\": \"大号\",\n                    \"tinyOld\": \"小号（旧）\"\n                },\n                \"option\": {\n                    \"feedbackLength\": \"震动时长\",\n                    \"feedbackStrength\": \"震动强度\",\n                    \"holdRun\": \"启动时自动按住奔跑键\",\n                    \"interfaceStyle\": \"按钮样式\",\n                    \"layoutStyle\": \"按键样式\",\n                    \"resetLayout\": \"重置布局\",\n                    \"sStartSpacing\": \"选择/开始键间距\",\n                    \"scaleButtons\": \"按钮缩放\",\n                    \"scaleDPad\": \"方向键缩放\",\n                    \"scaleFactor\": \"按键全局缩放\",\n                    \"showCodeButton\": \"显示作弊指令按钮\"\n                },\n                \"style\": {\n                    \"ABXY\": \"ABXY\",\n                    \"XODA\": \"XODA\",\n                    \"actions\": \"动作\"\n                }\n            },\n            \"types\": {\n                \"gamepad\": \"手柄\",\n                \"keyboard\": \"键盘\",\n                \"oldJoystick\": \"老式手柄\",\n                \"touchscreen\": \"触屏\"\n            },\n            \"wii\": {\n                \"classic\": {\n                    \"buttonLT\": \"LT\",\n                    \"buttonRT\": \"RT\",\n                    \"buttonX\": \"X\",\n                    \"buttonY\": \"Y\",\n                    \"buttonZL\": \"ZL\",\n                    \"buttonZR\": \"ZR\",\n                    \"lStick\": \"左摇杆\",\n                    \"rStick\": \"右摇杆\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"C\",\n                    \"buttonZ\": \"Z\",\n                    \"prefixN\": \"N\"\n                },\n                \"phraseNewClassic\": \"新建经典手柄配置\",\n                \"phraseNewNunchuck\": \"新建双节棍控制器配置\",\n                \"typeClassic\": \"经典手柄\",\n                \"typeNunchuck\": \"双节棍控制器\",\n                \"typeWiimote\": \"Wii遥控器\",\n                \"wiimote\": {\n                    \"button1\": \"1\",\n                    \"button2\": \"2\",\n                    \"buttonA\": \"A\",\n                    \"buttonB\": \"B\",\n                    \"buttonHome\": \"Home\",\n                    \"buttonMinus\": \"-\",\n                    \"buttonPlus\": \"+\",\n                    \"caseIR\": \"（IR）\",\n                    \"dPad\": \"十字键\",\n                    \"shake\": \"摇晃\"\n                },\n                \"typeGamecube\": \"GC控制器\"\n            },\n            \"wordButtons\": \"按键\",\n            \"wordProfiles\": \"配置\",\n            \"joystickSimpleEditor\": \"简易编辑器控制模式\"\n        },\n        \"editor\": {\n            \"errorMissingResources\": \"抱歉，需要文件：{0}，才能进入编辑器。\",\n            \"newWorld\": \"<创建新地图>\",\n            \"promptNewWorldName\": \"新地图名称\",\n            \"battles\": \"<创建对战模式关卡>\",\n            \"makeFor\": \"目标引擎：\"\n        },\n        \"game\": {\n            \"gameBattleRandom\": \"随机关卡\",\n            \"gameCopySave\": \"复制存档\",\n            \"gameEraseSave\": \"删除存档\",\n            \"gameEraseSlot\": \"选择要删除的存档\",\n            \"gameNoBattleLevels\": \"<对战关卡列表为空>\",\n            \"gameNoEpisodesToPlay\": \"<地图列表为空>\",\n            \"gameSlotContinue\": \"存档 {0} ... {1}%\",\n            \"gameSlotNew\": \"存档 {0} ... 新游戏\",\n            \"gameSourceSlot\": \"选择源存档\",\n            \"gameTargetSlot\": \"选择目标存档\",\n            \"phraseScore\": \"得分：{0}\",\n            \"phraseTime\": \"时间：{0}\",\n            \"warnEpCompat\": \"警告：该地图使用其他分支的SMBX引擎制作，在该引擎下可能无法正常运作。\"\n        },\n        \"loading\": \"加载中...\",\n        \"main\": {\n            \"main1PlayerGame\": \"单人游戏\",\n            \"mainBattleGame\": \"对战模式\",\n            \"mainEditor\": \"编辑器\",\n            \"mainExit\": \"退出\",\n            \"mainMultiplayerGame\": \"双人游戏\",\n            \"mainOptions\": \"游戏选项\",\n            \"mainPlayEpisode\": \"选择地图进行游戏\"\n        },\n        \"options\": {\n            \"restartEngine\": \"重启引擎后生效\",\n            \"advanced\": {\n                \"audio-channels\": {\n                    \"stereo\": \"立体声\",\n                    \"_name\": \"输出声道\",\n                    \"mono\": \"单声道\"\n                },\n                \"audio-buffer-size\": {\n                    \"_tooltip\": \"降低未预期的噪音，增加延迟\",\n                    \"_name\": \"缓冲大小\"\n                },\n                \"_tooltip\": \"有关内部操作的技术性设置选项\",\n                \"advanced-video\": \"视频选项\",\n                \"audio-enable\": \"启用声音\",\n                \"audio-format\": {\n                    \"_name\": \"音频格式\",\n                    \"_tooltip\": \"声卡驱动音频格式\"\n                },\n                \"audio-sample-rate\": {\n                    \"44100\": \"44100赫兹\",\n                    \"48000\": \"48000赫兹\",\n                    \"_name\": \"采样率\",\n                    \"16000\": \"16000赫兹\",\n                    \"32000\": \"32000赫兹\",\n                    \"11025\": \"11025赫兹\",\n                    \"22050\": \"22050赫兹\"\n                },\n                \"choose-assets-on-launch\": \"启动程序时，弹出选择组件界面\",\n                \"log-level\": {\n                    \"critical\": \"紧急状态\",\n                    \"fatal\": \"异常中止\",\n                    \"info\": \"信息\",\n                    \"warning\": \"警告\",\n                    \"debug\": \"调试\",\n                    \"none\": \"关闭\",\n                    \"_name\": \"日志记录等级\"\n                },\n                \"record-gameplay-data\": \"生成录像\",\n                \"render\": {\n                    \"_name\": \"图形渲染类型\",\n                    \"hw\": \"自动选择\",\n                    \"opengl\": \"OpenGL 3+\",\n                    \"opengl-tip\": \"使用新版电脑渲染器，用于最精确的SMBX64模拟，并支持增强视觉效果。\",\n                    \"opengl11-tip\": \"使用旧版电脑渲染器，仅用于最精确的SMBX64模拟，不支持增强视觉效果。\",\n                    \"opengles-tip\": \"使用新版手机渲染器，用于高精确度的SMBX64模拟，并支持增强视觉效果。\",\n                    \"opengles11-tip\": \"使用旧版手机渲染器，仅用于最精确的SMBX64模拟，不支持增强视觉效果。\",\n                    \"sdl\": \"SDL2\",\n                    \"sw\": \"软件渲染器\",\n                    \"opengl11\": \"OpenGL 1.1\",\n                    \"opengles\": \"OpenGL ES 2+\",\n                    \"opengles11\": \"OpenGL ES 1.1+\",\n                    \"sdl-tip\": \"使用SDL2，基础跨平台渲染器。\"\n                },\n                \"scale-down-textures\": {\n                    \"_name\": \"缩小图片\",\n                    \"all\": \"全部\",\n                    \"none\": \"无\",\n                    \"safe\": \"仅点阵2x\",\n                    \"all-tip\": \"缩小全部图片：加载卡顿的概率比“仅点阵2x”低\",\n                    \"none-tip\": \"不缩小任何图片：加载卡顿的概率更低\",\n                    \"safe-tip\": \"仅点阵2x：如果图片为点阵2x，将缩小一半\",\n                    \"_tooltip\": \"将图片缩小一半以释放内存\"\n                },\n                \"_header\": \"高级选项\",\n                \"advanced-audio\": \"音频选项\",\n                \"webm-recording\": \"录制WEBM视频\",\n                \"internal-res-4p\": {\n                    \"_name\": \"四人分屏模式画面大小\",\n                    \"qhd\": \"2560x1440（QHD）\",\n                    \"qsmbx\": \"1600x1200（QSMBX）\",\n                    \"default\": \"默认\",\n                    \"fhd\": \"1920x1080（FHD）\"\n                },\n                \"use-native-osk\": \"启用内置虚拟键盘\",\n                \"inaccurate-gifs-tip\": \"当GIF图片文件存在时，启用3D效果\",\n                \"inaccurate-gifs\": \"错误GIF图片渲染\",\n                \"fullscreen-type\": {\n                    \"desktop-tip\": \"以当前分辨率显示\",\n                    \"desktop\": \"电脑\",\n                    \"auto\": \"自动选择\",\n                    \"_name\": \"全屏模式显示类型\",\n                    \"exclusive-tip\": \"设置实际画面大小\",\n                    \"exclusive\": \"独立设置\"\n                },\n                \"fullscreen-depth\": {\n                    \"auto\": \"自动\",\n                    \"_name\": \"位深\",\n                    \"16\": \"16位\",\n                    \"32\": \"32位\"\n                },\n                \"fullscreen-res\": \"分辨率\"\n            },\n            \"audio\": {\n                \"_header\": \"音频选项\",\n                \"sfx-audio-fx\": \"回音效果\",\n                \"sfx-modern\": \"播放新增的声音\",\n                \"sfx-modern-tip\": \"播放TheXTech引擎新增的声音\",\n                \"sfx-pet-beat\": \"坐骑节奏\",\n                \"sfx-pet-beat-tip\": \"骑上坐骑时，播放特殊节奏\",\n                \"sfx-spatial-audio-tip\": \"减少画面外环境的音量\",\n                \"audio-prefs\": \"偏好设置\",\n                \"audio-music-volume\": \"音乐音量\",\n                \"audio-sfx-volume\": \"声音音量\",\n                \"sfx-spatial-audio\": \"三维音频\"\n            },\n            \"compatibility\": {\n                \"_header\": \"游戏微调选项\",\n                \"autocode\": \"LunaScript代码\",\n                \"reset-all\": \"重置全部\",\n                \"features\": \"新特性\",\n                \"_tooltip\": \"兼容模式或调试模式选项，仅对当前游戏生效\",\n                \"bugfixes\": \"BUG修复\"\n            },\n            \"controls\": {\n                \"_header\": \"按键选项\"\n            },\n            \"episode-options\": {\n                \"_header\": \"地图选项\",\n                \"creator-compat\": {\n                    \"_name\": \"创作者模式\",\n                    \"_tooltip\": \"创作者相关的逻辑微调\",\n                    \"disable\": \"关闭\",\n                    \"enable\": \"开启\",\n                    \"file-only\": \"仅文件\"\n                },\n                \"playstyle\": {\n                    \"_name\": \"游戏模式\",\n                    \"classic\": \"经典模式\",\n                    \"modern\": \"现代模式\",\n                    \"vanilla\": \"怀旧模式\",\n                    \"classic-tip\": \"开启部分游戏更新\",\n                    \"modern-tip\": \"开启全部游戏更新\",\n                    \"vanilla-tip\": \"关闭全部游戏更新\"\n                },\n                \"hub-resume\": \"中心关卡：传送点记忆\",\n                \"hub-resume-tip\": \"从上次进入的传送点继续游戏\"\n            },\n            \"main\": {\n                \"_header\": \"常规选项\",\n                \"background-work\": \"后台运行\",\n                \"background-work-tip\": \"窗口未激活时，可通过手柄游玩\",\n                \"four-screen-mode\": {\n                    \"shared\": \"同屏\",\n                    \"split\": \"分屏\",\n                    \"_name\": \"四人游戏画面模式\"\n                },\n                \"frame-skip\": \"跳帧\",\n                \"discord-rpc\": \"在Discord中显示\",\n                \"multiplayer\": \"多人游戏\",\n                \"timing\": \"帧数控制\",\n                \"two-screen-mode\": {\n                    \"_name\": \"双人游戏画面模式\",\n                    \"shared\": \"同屏\",\n                    \"smbx\": \"SMBX\",\n                    \"topbottom\": \"上下分屏\",\n                    \"split\": \"左右分屏\"\n                },\n                \"unlimited-framerate\": \"禁用锁帧\",\n                \"vsync\": \"垂直同步\",\n                \"language\": \"语言\"\n            },\n            \"video\": {\n                \"_header\": \"视频选项\",\n                \"battery-status\": {\n                    \"fullscreen\": \"全屏时显示\",\n                    \"fullscreen-tip\": \"全屏游戏时，显示电池电量\",\n                    \"_name\": \"电池状态\",\n                    \"low\": \"仅在低电量时显示\",\n                    \"low-tip\": \"在电池电量低时，显示电池电量\",\n                    \"off\": \"隐藏\",\n                    \"on\": \"显示\"\n                },\n                \"display-controllers\": \"显示按键\",\n                \"effects\": \"画面效果\",\n                \"fullscreen\": \"全屏模式\",\n                \"info-game\": \"画面中游戏信息\",\n                \"info-meta\": \"画面中元数据信息\",\n                \"info-run\": \"画面中运行时信息\",\n                \"internal-res\": {\n                    \"3ds\": \"800x480 (3DS)\",\n                    \"_name\": \"画面大小\",\n                    \"_tooltip\": \"设置游戏画面大小\",\n                    \"dynamic\": \"自适应\",\n                    \"vga\": \"640x480（VGA）\",\n                    \"hd\": \"1280x720（高清）\",\n                    \"gba\": \"480x320（GBA）\",\n                    \"nds\": \"512x384（DS）\",\n                    \"snes\": \"512x448（SFC）\",\n                    \"hello\": \"768x432（HELLO引擎）\",\n                    \"smbx-wide\": \"SMBX（宽屏）\",\n                    \"smbx\": \"800x600（SMBX）\"\n                },\n                \"enable-inter-level-fade-effect\": \"过渡效果\",\n                \"show-episode-title\": {\n                    \"bottom\": \"底部\",\n                    \"off\": \"隐藏\",\n                    \"_name\": \"显示当前地图\",\n                    \"bottom-tip\": \"在秒表计时器的上方显示当前地图\",\n                    \"top-tip\": \"在游戏画面大小较大时，在状态栏上方显示当前地图\",\n                    \"top\": \"顶部\"\n                },\n                \"show-playtime-counter\": {\n                    \"_tooltip\": \"显示当前地图和当前关卡的游玩时间\",\n                    \"animated-tip\": \"过关时，为秒表计时器添加彩虹特效\",\n                    \"off\": \"关闭\",\n                    \"off-tip\": \"仅在速通时显示秒表计时器\",\n                    \"opaque\": \"开启（不透明）\",\n                    \"transparent\": \"开启（透明）\",\n                    \"_name\": \"秒表计时器\",\n                    \"animated\": \"动态特效\"\n                },\n                \"video-system\": \"系统选项\",\n                \"show-screen-shake\": \"画面震动\",\n                \"world-map-stars-show\": \"大地图上显示星星数量\",\n                \"world-map-stars-show-tip\": \"在关卡上方显示已收集星星数量\",\n                \"show-fps\": \"显示帧数\",\n                \"scale-mode\": {\n                    \"integer\": \"整数\",\n                    \"center\": \"中心\",\n                    \"0_5x\": \"0.5倍\",\n                    \"2x\": \"2倍\",\n                    \"_name\": \"图像缩放\",\n                    \"1x\": \"1倍\",\n                    \"nearest\": \"最近邻居\",\n                    \"full\": \"完全缩放\",\n                    \"linear\": \"双线性\",\n                    \"3x\": \"3倍\"\n                },\n                \"3d-compat-mode\": \"三维兼容模式\",\n                \"3d-compat-mode-tip\": \"在同一个三维平面下绘制所有对象\",\n                \"show-fails-counter\": \"显示失败次数\",\n                \"show-fails-counter-tip\": \"显示当前关卡和当前地图的失败次数\",\n                \"show-medals-counter\": \"显示奖牌数量\",\n                \"show-medals-counter-tip\": \"当玩家在关卡入口处时，在状态栏上显示奖牌数量\"\n            },\n            \"view-credits\": {\n                \"_header\": \"查看制作团队\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"_name\": \"秒表计时模式\",\n                    \"0\": \"0（默认）\",\n                    \"1\": \"1（现代）\",\n                    \"3\": \"3（怀旧）\",\n                    \"2\": \"2（经典）\"\n                }\n            }\n        },\n        \"wordBack\": \"返回\",\n        \"wordHide\": \"隐藏\",\n        \"wordNo\": \"否\",\n        \"wordOff\": \"关闭\",\n        \"wordOn\": \"开启\",\n        \"wordPlayer\": \"玩家\",\n        \"wordProfile\": \"配置\",\n        \"wordResume\": \"恢复游戏\",\n        \"wordShow\": \"显示\",\n        \"wordWaiting\": \"等待\",\n        \"wordYes\": \"是\"\n    },\n    \"outro\": {\n        \"cppPortDevelopers\": \"C++移植开发人员：\",\n        \"customSprites\": \"自定义素材：\",\n        \"engineCredits\": \"引擎制作人员名单：\",\n        \"gameCredits\": \"游戏制作人员名单:\",\n        \"levelDesign\": \"关卡设计：\",\n        \"nameAndrewSpinks\": \"安德鲁·斯宾克斯\",\n        \"nameVitalyNovichkov\": \"维塔利·诺维奇科夫\",\n        \"originalBy\": \"原始VB6代码：\",\n        \"psVitaPortBy\": \"PS Vita移植：\",\n        \"specialThanks\": \"特别鸣谢：\",\n        \"qualityControl\": \"品质管理：\"\n    },\n    \"pluralRules\": \"singular-only\"\n}\n"
  },
  {
    "path": "resources/languages/thextech_zh-tw.json",
    "content": "{\n    \"editor\": {\n        \"block\": {\n            \"canBreak\": \"可頂碎\",\n            \"canBreakTooltip\": \"舊版本：頂磚塊後將會破碎\",\n            \"inside\": \"包含：\",\n            \"invis\": \"隱形\",\n            \"letterHeight\": \"高 \",\n            \"letterWidth\": \"寬 \",\n            \"pickContents\": \"選擇磚塊包含道具\",\n            \"slick\": \"打滑\"\n        },\n        \"browser\": {\n            \"askOverwriteFile\": \"是否覆蓋檔案{0}？\",\n            \"itemNewFile\": \"<新建檔案>\",\n            \"itemNewFolder\": \"<新建資料夾>\",\n            \"newFile\": \"新建檔案\",\n            \"openFile\": \"打開檔案\",\n            \"saveFile\": \"刪除檔案\"\n        },\n        \"events\": {\n            \"bounds\": {\n                \"changeAllSectionBoundsToCurrent\": \"改變所有場景範圍到當前設定嗎？\",\n                \"changeSectionBoundsToCurrent\": \"改變場景{0}的範圍到當前設定嗎？\",\n                \"shouldEvent\": \"要使事件{0}\"\n            },\n            \"controlsForEventN\": \"強制按下玩家按鍵：{0}\",\n            \"deletion\": {\n                \"cancel\": \"否：不刪除事件\",\n                \"confirm\": \"是：刪除事件\",\n                \"deletingEvent\": \"刪除{0}事件\"\n            },\n            \"desc\": {\n                \"activate\": \"NPC在當前畫面出現時\",\n                \"death\": \"NPC死亡時\",\n                \"destroy\": \"磚塊破碎時\",\n                \"enter\": \"進入傳送點時\",\n                \"hit\": \"磚塊被撞擊時\",\n                \"layerClear\": \"當前圖層中的對象爲空時\",\n                \"phraseTriggersAfterTemplate\": \"將於事件：{2}經過{1}毫秒之後，觸發{0}事件\",\n                \"phraseTriggersWhenTemplate\": \"{0}：{1}觸發該事件\",\n                \"talk\": \"和NPC對話後\"\n            },\n            \"header\": \"事件：\",\n            \"headerTriggerEvent\": \"觸發\",\n            \"itemNewEvent\": \"<新事件>\",\n            \"label\": {\n                \"activate\": \"出現\",\n                \"death\": \"死亡\",\n                \"destroy\": \"破碎\",\n                \"enter\": \"進入傳送點\",\n                \"hit\": \"撞擊\",\n                \"layerClear\": \"空白圖層\",\n                \"next\": \"下一個\",\n                \"talk\": \"對話\"\n            },\n            \"layers\": {\n                \"headerHide\": \"隱藏：\",\n                \"headerMove\": \"移動：\",\n                \"headerShow\": \"顯示：\",\n                \"headerToggle\": \"切換：\"\n            },\n            \"letter\": {\n                \"activate\": \"出：\",\n                \"death\": \"死：\",\n                \"destroy\": \"碎：\",\n                \"enter\": \"進：\",\n                \"hit\": \"撞：\",\n                \"layerClear\": \"空：\",\n                \"talk\": \"話：\"\n            },\n            \"promptEventName\": \"事件名稱：\",\n            \"promptEventText\": \"文字信息：\",\n            \"props\": {\n                \"autostart\": \"進入關卡時執行事件\",\n                \"controls\": \"按鍵\",\n                \"endGame\": \"遊戲通關\",\n                \"sound\": \"音效\",\n                \"layerSmoke\": \"圖層煙霧效果\"\n            },\n            \"sections\": {\n                \"actionKeep\": \"當前\",\n                \"actionReset\": \"默認\",\n                \"actionSet\": \"指定\",\n                \"phraseAllSections\": \"所有場景\",\n                \"propBackground\": \"背景圖片\",\n                \"propBounds\": \"場景範圍\",\n                \"propMusic\": \"音樂\"\n            },\n            \"settingsForEvent\": \"{0}事件選項\"\n        },\n        \"file\": {\n            \"actionClearLevel\": \"清除關卡\",\n            \"actionClearWorld\": \"清除大地圖\",\n            \"actionExit\": \"退出\",\n            \"actionOpen\": \"打開\",\n            \"actionRevert\": \"恢復\",\n            \"commandNew\": \"新建\",\n            \"commandOpen\": \"打開...\",\n            \"commandSave\": \"保存\",\n            \"commandSaveAs\": \"另存爲...\",\n            \"confirmConfirmAction\": \"您確定要{0}嗎？\",\n            \"confirmConvertFormatTo\": \"是否轉換格式到{0}？\",\n            \"confirmSaveBeforeAction\": \"{0}前是否保存檔案？\",\n            \"convert\": {\n                \"descNew\": \"檔案格式將會轉換，但不會刪除原始檔。\\n\\n請在保存檔案之後檢查不支持的功能。\"\n            },\n            \"formatLegacy\": \"舊版本\",\n            \"formatModern\": \"新版本\",\n            \"labelCurrentFile\": \"當前檔案：\",\n            \"labelFormat\": \"格式：\",\n            \"optionActionWithoutSave\": \"否：{0}而不保存檔案\",\n            \"optionCancelAction\": \"取消：返回上一級菜單\",\n            \"optionCancelConversion\": \"取消轉換\",\n            \"optionProceedWithConversion\": \"確認轉換\",\n            \"optionYesSaveThenAction\": \"是：{0}並保存檔案\",\n            \"sectionLevel\": \"關卡\",\n            \"sectionWorld\": \"大地圖\"\n        },\n        \"layers\": {\n            \"abbrevAttLayer\": \"跟隨：\",\n            \"default\": \"缺省\",\n            \"deletion\": {\n                \"cancel\": \"取消：返回上一級菜單\",\n                \"confirmDelete\": \"否：*刪除全部對象*\",\n                \"confirmPreserve\": \"是：移動到缺省圖層\",\n                \"header\": \"刪除{0}圖層\",\n                \"preserveLayerContents\": \"是否保留圖層對象？\"\n            },\n            \"desc\": {\n                \"att\": \"當 NPC 移動時，圖層也將隨之移動\"\n            },\n            \"header\": \"圖層：\",\n            \"itemNewLayer\": \"<新圖層>\",\n            \"label\": \"圖層：\",\n            \"labelAttached\": \"跟隨：\",\n            \"labelAttachedLayer\": \"跟隨圖層：\",\n            \"labelMoveLayer\": \"移動圖層：\",\n            \"promptLayerName\": \"圖層名稱\"\n        },\n        \"letterCoordX\": \"X\",\n        \"letterCoordY\": \"Y\",\n        \"letterDown\": \"下\",\n        \"letterLeft\": \"左\",\n        \"letterRight\": \"右\",\n        \"letterUp\": \"上\",\n        \"level\": {\n            \"alwaysVis\": \"總是可見\",\n            \"bigBG\": \"大背景\",\n            \"gameStart\": \"遊戲起點\",\n            \"levelName\": \"關卡名稱\",\n            \"pathBG\": \"路線背景\",\n            \"pathUnlocks\": \"路線開啓\",\n            \"startPos\": \"遊戲起點\"\n        },\n        \"mapPos\": \"地圖位置：\",\n        \"npc\": {\n            \"abbrevGen\": \"生成器\",\n            \"ai\": {\n                \"LR\": \"左右\",\n                \"UD\": \"上下\",\n                \"aiIs\": \"動作：{0}\",\n                \"headerCustomAi\": \"自定義動作：\",\n                \"jump\": \"跳躍\",\n                \"leap\": \"飛躍\",\n                \"swim\": \"游泳\",\n                \"target\": \"目標\",\n                \"use1_0Ai\": \"使用1.0 AI？\"\n            },\n            \"gen\": {\n                \"direction\": \"方向\",\n                \"effectIs\": \"特效：{0}\",\n                \"effectShoot\": \"噴射\",\n                \"effectWarp\": \"水管\",\n                \"header\": \"生成器選項\"\n            },\n            \"inContainer\": \"包含\",\n            \"inertNice\": \"友好\",\n            \"props\": {\n                \"active\": \"啓動\",\n                \"attachSurface\": \"位置\",\n                \"facing\": \"方向\"\n            },\n            \"stuckStop\": \"靜止\",\n            \"tooltipExpandSection\": \"擴展場景\"\n        },\n        \"pageBlankOfBlank\": \"第{0}/{1}頁\",\n        \"phraseAreYouSure\": \"您確定嗎？\",\n        \"phraseCountMore\": \"更多{0}\",\n        \"phraseDelayIsMs\": \"延遲：{0}毫秒\",\n        \"phraseGenericIndex\": \"編號：{0}\",\n        \"phraseRadiusIndex\": \"半徑：{0}\",\n        \"phraseSectionIndex\": \"場景{0}\",\n        \"phraseTextOf\": \"{0}的文字信息\",\n        \"phraseWarpIndex\": \"傳送點{0}號\",\n        \"section\": {\n            \"horizWrap\": \"橫向循環\",\n            \"noTurnBack\": \"單向卷軸\",\n            \"offscreenExit\": \"場景邊緣出口\",\n            \"scroll\": \"強制卷軸\",\n            \"setBounds\": \"設定範圍\",\n            \"underwater\": \"水下\",\n            \"vertWrap\": \"縱向循環\"\n        },\n        \"select\": {\n            \"allSectPropBlankForEventN\": \"更改所有場景{0}於{1}事件\",\n            \"pathBlankUnlock\": \"路線向{0}開啓條件\",\n            \"sectBlankPropBlankForEventN\": \"更改場景{0}的{1}於{2}事件\",\n            \"sectionBlankPropBlank\": \"場景{0}{1}\",\n            \"soundForEventN\": \"執行{0}事件時播放聲音\",\n            \"warpTransitEffect\": \"過渡效果\",\n            \"worldMusic\": \"大地圖音樂\"\n        },\n        \"testPlay\": {\n            \"boot\": \"靴子\",\n            \"char\": \"角色\",\n            \"magicHand\": \"鼠標\",\n            \"pet\": \"坐騎\",\n            \"power\": \"狀態\"\n        },\n        \"toggleMagicBlock\": \"智能編輯模式\",\n        \"tooltip\": {\n            \"BGOs\": \"背景對象\",\n            \"NPCs\": \"NPC\",\n            \"blocks\": \"磚塊\",\n            \"erase\": \"刪除\",\n            \"eraseAll\": \"刪除全部\",\n            \"events\": \"事件\",\n            \"file\": \"檔案\",\n            \"layers\": \"圖層\",\n            \"levels\": \"關卡\",\n            \"music\": \"音樂\",\n            \"paths\": \"路線\",\n            \"scenes\": \"風景\",\n            \"select\": \"選擇\",\n            \"settings\": \"設定\",\n            \"show\": \"顯示\",\n            \"tiles\": \"地形\",\n            \"warps\": \"傳送點\",\n            \"water\": \"水流\",\n            \"area\": \"地圖區域\"\n        },\n        \"warp\": {\n            \"allow\": \"允許：\",\n            \"cannonExit\": \"水管炮出口\",\n            \"dir\": \"方向\",\n            \"effect\": \"過渡效果：\",\n            \"in\": \"入口\",\n            \"item\": \"攜帶道具\",\n            \"lvlWarp\": \"傳送到另一關卡\",\n            \"needFloor\": \"需要站立\",\n            \"needKey\": \"需要鑰匙\",\n            \"needStarCount\": \"需要{0}{2}{1}\",\n            \"out\": \"出口\",\n            \"placing\": \"放置：\",\n            \"ride\": \"坐騎\",\n            \"showStarCount\": \"顯示星星數量\",\n            \"showStartScene\": \"顯示開場界面\",\n            \"speed\": \"速度：\",\n            \"starLockMessage\": \"需要星星時的信息\",\n            \"style\": {\n                \"blipInstant\": \"瞬移\",\n                \"door\": \"門\",\n                \"pipe\": \"水管\",\n                \"portal\": \"傳送門\",\n                \"style\": \"傳送類別：\"\n            },\n            \"target\": \"目標：\",\n            \"title\": \"傳送點\",\n            \"to\": \"傳送到：{0}\",\n            \"toMap\": \"傳送到大地圖\",\n            \"transit\": {\n                \"name0\": \"無\",\n                \"name1\": \"滾動\",\n                \"name2\": \"淡入淡出\",\n                \"name3\": \"圓形\",\n                \"name4\": \"水平翻轉\",\n                \"name5\": \"垂直翻轉\"\n            },\n            \"twoWay\": \"雙向傳送\"\n        },\n        \"water\": {\n            \"title\": \"流體\"\n        },\n        \"wordCoins\": \"金幣\",\n        \"wordEnabled\": \"已啓用\",\n        \"wordEvent\": {\n            \"genitive\": \"事件\",\n            \"nominative\": \"事件\",\n            \"typeLabel\": \"觸發{0}事件：\"\n        },\n        \"wordHeight\": \"高\",\n        \"wordInstant\": \"立即\",\n        \"wordMode\": \"流體類型\",\n        \"wordNPC\": {\n            \"genitive\": \"NPC\",\n            \"nominative\": \"NPC\"\n        },\n        \"wordText\": \"信息\",\n        \"wordWidth\": \"寬\",\n        \"world\": {\n            \"allowChars\": \"可用角色\",\n            \"hubWorld\": \"禁用大地圖\",\n            \"introLevel\": \"開場關卡\",\n            \"name\": \"地圖名稱\",\n            \"phraseCreditIndex\": \"地圖製作團隊 第{0}行：\",\n            \"retryOnFail\": \"失敗之後重新開始\",\n            \"totalStars\": \"總星星數：\"\n        },\n        \"labelSortOffset\": \"疊放次序：\",\n        \"labelSortLayer\": \"疊放圖層：\"\n    },\n    \"game\": {\n        \"connect\": {\n            \"dropAddTitle\": \"加入/離開遊戲\",\n            \"phraseChangeChar\": \"更換角色\",\n            \"phraseDropMe\": \"離開遊戲\",\n            \"phraseDropPX\": \"移除玩家{0}的遊戲\",\n            \"phraseForceResume\": \"強制恢復遊戲\",\n            \"phraseHoldStart\": \"按住開始鍵\",\n            \"phrasePressAButton\": \"按下一個按鍵\",\n            \"phraseSetControls\": \"設定按鍵\",\n            \"phraseStartToForceRes\": \"按開始鍵強制恢復遊戲\",\n            \"phraseStartToResume\": \"按開始鍵恢復遊戲\",\n            \"phraseTestControls\": \"測試按鍵\",\n            \"phraseWaitingForInput\": \"等待玩家加入……\",\n            \"reconnectTitle\": \"重新連接\",\n            \"splitPressSelect_1\": \"按選擇鍵\",\n            \"splitPressSelect_2\": \"進入按鍵選項設定\",\n            \"wordDisconnect\": \"斷開\",\n            \"phraseTestProfile\": \"測試配置\"\n        },\n        \"error\": {\n            \"errorInvalidEnterWarp\": \"作爲關卡起點的傳送點{1}號不存在，因此無法進入關卡。\\n傳送點總數：{2}\\n\\n檔案：{0}\",\n            \"errorNoStartPoint\": \"未放置玩家起點或傳送點入口，因此無法進入關卡。\\n\\n檔案：{0}\",\n            \"openFileFailed\": \"無法打開檔案\\\"{0}\\\"：檔案不存在或已損壞。\",\n            \"warpNeedStarCount\": \"你需要{0}{2}{1}才能進入此傳送點。\",\n            \"openIPCDataFailed\": \"無法接收檔案數據：檔案可能已損壞，或者發生其他錯誤。\",\n            \"IPCTimeOut\": \"載入編輯器時無響應，即將退出遊戲。\",\n            \"errorTooOldEngine\": \"該檔案需要版本{0}或更高版本的TheXTech引擎才能正常運作，當前TheXTech引擎版本為{1}。請更新引擎。\",\n            \"errorTooOldGameAssets\": \"該檔案需要版本{0}或更高版本的遊戲組件才能正常運作，當前遊戲組件版本為{1}。請更新遊戲組件。\"\n        },\n        \"pause\": {\n            \"continue\": \"繼續遊戲\",\n            \"enterCode\": \"輸入指令\",\n            \"quit\": \"退出\",\n            \"quitTesting\": \"結束測試\",\n            \"resetCheckpoints\": \"重置中間點\",\n            \"restartLevel\": \"重新開始\",\n            \"returnToEditor\": \"返回編輯器\",\n            \"saveAndContinue\": \"保存並繼續\",\n            \"saveAndQuit\": \"保存並退出\",\n            \"playerSetup\": \"玩家設定\"\n        },\n        \"msgbox\": {\n            \"sysInfoError\": \"錯誤！\",\n            \"sysInfoWarning\": \"警告！\",\n            \"sysInfoTitle\": \"信息\"\n        },\n        \"format\": {\n            \"minutesSeconds\": \"{0}分{1}秒\"\n        },\n        \"message\": {\n            \"scanningLevels\": \"掃描關卡中……\"\n        },\n        \"hint\": {\n            \"no-lives-old\": \"注意：如果本次通關失敗，遊戲將會結束！\",\n            \"no-lives-new\": \"即使本次通關失敗，也沒什麼事情發生；請注意查看您的積分……\",\n            \"char5-bombs\": \"按下“奔跑”鍵可收集炸彈，按下“第二奔跑”鍵可投擲炸彈。\",\n            \"gray-bricks\": \"神秘力量可以破壞部分磚塊。\",\n            \"rainbow-surf\": \"在拿起彩虹龜殼的時候，按下“下”鍵，開始衝浪吧！\",\n            \"shoe-block\": \"穿上鞋子時，可以抵擋大多數火焰！\",\n            \"heavy-duck\": \"玩家下蹲時，可以抵擋大多數火焰！\",\n            \"pound-altrun\": \"按下“第二奔跑”鍵，進行震地！\",\n            \"pound-down\": \"按下“下”鍵，進行震地！\"\n        },\n        \"screenPaused\": \"遊戲暫停\",\n        \"ipcStatus\": {\n            \"waitingInput\": \"等待玩家按鍵輸入……\",\n            \"loadingdone\": \"載入完成，即將開始遊戲……\",\n            \"errorTimeout\": \"錯誤：等待超時。\",\n            \"dataValid\": \"接收的數據是有效數據\",\n            \"dataTransferStarted\": \"開始傳輸數據……\",\n            \"dataAccepted\": \"已接收到數據，開始解析……\"\n        },\n        \"loader\": {\n            \"statusAssetPacks\": \"遊戲組件\",\n            \"statusFinishing\": \"完成加載……\",\n            \"statusGameInfo\": \"遊戲信息\",\n            \"statusLoadData\": \"正在加載數據……\",\n            \"statusLoadFile\": \"正在加載：{0}\",\n            \"statusTranslations\": \"翻譯\",\n            \"loading\": \"加載中……\"\n        }\n    },\n    \"languageName\": \"繁體中文\",\n    \"menu\": {\n        \"abbrevMilliseconds\": \"毫秒\",\n        \"battle\": {\n            \"errorNoLevels\": \"對戰關卡列表爲空，無法進入對戰模式。\"\n        },\n        \"caseNone\": \"<無>\",\n        \"character\": {\n            \"selectCharacter\": \"{0}遊戲\"\n        },\n        \"controls\": {\n            \"buttons\": {\n                \"altJump\": \"第二跳躍\",\n                \"altRun\": \"第二奔跑\",\n                \"down\": \"下\",\n                \"drop\": \"取出道具\",\n                \"jump\": \"跳躍\",\n                \"left\": \"左\",\n                \"right\": \"右\",\n                \"run\": \"奔跑\",\n                \"start\": \"開始\",\n                \"up\": \"上\"\n            },\n            \"caseInvalid\": \"（無效）\",\n            \"caseMouse\": \"（鼠標）\",\n            \"caseTouch\": \"（觸屏）\",\n            \"controlsConnected\": \"已連接：\",\n            \"controlsDeleteKey\": \"（按第二跳躍鍵清除）\",\n            \"controlsDeleteKey2\": \"（按第二奔跑鍵清除）\",\n            \"controlsDeviceTypes\": \"設備類型\",\n            \"controlsInUse\": \"（已使用）\",\n            \"controlsNewProfile\": \"<新建配置>\",\n            \"controlsNotInUse\": \"（未使用）\",\n            \"controlsReallyDeleteProfile\": \"是否刪除配置？\",\n            \"controlsTitle\": \"按鍵配置\",\n            \"cursor\": {\n                \"down\": \"鼠標向下移動\",\n                \"left\": \"鼠標向左移動\",\n                \"primary\": \"鼠標左鍵\",\n                \"right\": \"鼠標向右移動\",\n                \"secondary\": \"鼠標右鍵\",\n                \"tertiary\": \"鼠標滾輪\",\n                \"up\": \"鼠標向上移動\"\n            },\n            \"editor\": {\n                \"fastScroll\": \"快速滾動\",\n                \"modeErase\": \"橡皮擦\",\n                \"modeSelect\": \"選擇模式\",\n                \"nextSection\": \"下一場景\",\n                \"prevSection\": \"上一場景\",\n                \"scrollDown\": \"向下滾動\",\n                \"scrollLeft\": \"向左滾動\",\n                \"scrollRight\": \"向右滾動\",\n                \"scrollUp\": \"向上滾動\",\n                \"switchScreens\": \"顯示面板\",\n                \"testPlay\": \"測試關卡\"\n            },\n            \"hotkeys\": {\n                \"debugInfo\": \"調試信息\",\n                \"enterCheats\": \"輸入作弊指令\",\n                \"fullscreen\": \"全屏\",\n                \"legacyPause\": \"舊版暫停菜單\",\n                \"recordGif\": \"GIF 動畫錄製\",\n                \"screenshot\": \"截圖\",\n                \"toggleHUD\": \"狀態欄切換\",\n                \"vanillaCam\": \"原始畫面大小\"\n            },\n            \"options\": {\n                \"batteryStatus\": \"電池狀態\",\n                \"maxPlayers\": \"玩家上限\",\n                \"rumble\": \"Rumble控制器\",\n                \"textEntryStyle\": \"文字輸入使用設備\",\n                \"altMenuControls\": \"切換確定/返回按鍵\"\n            },\n            \"phraseNewProfOldJoy\": \"新建老式手柄配置\",\n            \"profile\": {\n                \"cursorControls\": \"鼠標控制\",\n                \"deleteProfile\": \"刪除配置\",\n                \"editorControls\": \"編輯器控制\",\n                \"hotkeys\": \"熱鍵\",\n                \"playerControls\": \"玩家控制\",\n                \"renameProfile\": \"重命名配置\"\n            },\n            \"tDS\": {\n                \"buttonA\": \"A\",\n                \"buttonB\": \"B\",\n                \"buttonL\": \"L\",\n                \"buttonR\": \"R\",\n                \"buttonSelect\": \"選擇\",\n                \"buttonStart\": \"開始\",\n                \"buttonX\": \"X\",\n                \"buttonY\": \"Y\",\n                \"buttonZL\": \"ZL\",\n                \"buttonZR\": \"ZR\",\n                \"cStick\": \"右搖桿\",\n                \"casePen\": \"(觸控筆)\",\n                \"dPad\": \"方向鍵\",\n                \"tStick\": \"左搖桿\"\n            },\n            \"touchscreen\": {\n                \"layout\": {\n                    \"longOld\": \"寬屏（舊）\",\n                    \"phabletOld\": \"大平板（舊）\",\n                    \"phoneOld\": \"手機（舊）\",\n                    \"standard\": \"普通\",\n                    \"tabletOld\": \"平板（舊）\",\n                    \"tight\": \"大號\",\n                    \"tinyOld\": \"小號（舊）\"\n                },\n                \"option\": {\n                    \"feedbackLength\": \"震動時長\",\n                    \"feedbackStrength\": \"震動強度\",\n                    \"holdRun\": \"啓動時自動按住奔跑鍵\",\n                    \"interfaceStyle\": \"按鈕樣式\",\n                    \"layoutStyle\": \"按鍵樣式\",\n                    \"resetLayout\": \"重置佈局\",\n                    \"sStartSpacing\": \"選擇/開始鍵間距\",\n                    \"scaleButtons\": \"按鈕縮放\",\n                    \"scaleDPad\": \"方向鍵縮放\",\n                    \"scaleFactor\": \"按鍵全局縮放\",\n                    \"showCodeButton\": \"顯示作弊指令按鈕\"\n                },\n                \"style\": {\n                    \"ABXY\": \"ABXY\",\n                    \"XODA\": \"XODA\",\n                    \"actions\": \"動作\"\n                }\n            },\n            \"types\": {\n                \"gamepad\": \"手柄\",\n                \"keyboard\": \"鍵盤\",\n                \"oldJoystick\": \"老式手柄\",\n                \"touchscreen\": \"觸屏\"\n            },\n            \"wii\": {\n                \"classic\": {\n                    \"buttonLT\": \"LT\",\n                    \"buttonRT\": \"RT\",\n                    \"buttonX\": \"X\",\n                    \"buttonY\": \"Y\",\n                    \"buttonZL\": \"ZL\",\n                    \"buttonZR\": \"ZR\",\n                    \"lStick\": \"左搖桿\",\n                    \"rStick\": \"右搖桿\"\n                },\n                \"nunchuck\": {\n                    \"buttonC\": \"C\",\n                    \"buttonZ\": \"Z\",\n                    \"prefixN\": \"N\"\n                },\n                \"phraseNewClassic\": \"新建經典手柄配置\",\n                \"phraseNewNunchuck\": \"新建雙節棍控制器配置\",\n                \"typeClassic\": \"經典手柄\",\n                \"typeNunchuck\": \"雙節棍控制器\",\n                \"typeWiimote\": \"Wii遙控器\",\n                \"wiimote\": {\n                    \"button1\": \"1\",\n                    \"button2\": \"2\",\n                    \"buttonA\": \"A\",\n                    \"buttonB\": \"B\",\n                    \"buttonHome\": \"Home\",\n                    \"buttonMinus\": \"-\",\n                    \"buttonPlus\": \"+\",\n                    \"caseIR\": \"（IR）\",\n                    \"dPad\": \"十字鍵\",\n                    \"shake\": \"搖晃\"\n                },\n                \"typeGamecube\": \"GC控制器\"\n            },\n            \"wordButtons\": \"按鍵\",\n            \"wordProfiles\": \"配置\",\n            \"joystickSimpleEditor\": \"簡單編輯器控制模式\"\n        },\n        \"editor\": {\n            \"errorMissingResources\": \"抱歉，需要檔案：{0}，才能進入編輯器。\",\n            \"newWorld\": \"<新地圖>\",\n            \"promptNewWorldName\": \"新地圖名稱\",\n            \"battles\": \"<對戰關卡>\",\n            \"makeFor\": \"目標引擎：\"\n        },\n        \"game\": {\n            \"gameBattleRandom\": \"隨機關卡\",\n            \"gameCopySave\": \"複製存檔\",\n            \"gameEraseSave\": \"刪除存檔\",\n            \"gameEraseSlot\": \"選擇要刪除的存檔\",\n            \"gameNoBattleLevels\": \"<對戰關卡列表爲空>\",\n            \"gameNoEpisodesToPlay\": \"<地圖列表爲空>\",\n            \"gameSlotContinue\": \"存檔 {0} ... {1}%\",\n            \"gameSlotNew\": \"存檔 {0} ... 新遊戲\",\n            \"gameSourceSlot\": \"選擇源存檔\",\n            \"gameTargetSlot\": \"選擇目標存檔\",\n            \"phraseScore\": \"積分：{0}\",\n            \"phraseTime\": \"時間：{0}\",\n            \"warnEpCompat\": \"警告：該地圖使用其他分支的SMBX引擎製作，在該引擎下可能無法正常運作。\"\n        },\n        \"loading\": \"加載中...\",\n        \"main\": {\n            \"main1PlayerGame\": \"單人遊戲\",\n            \"mainBattleGame\": \"對戰模式\",\n            \"mainEditor\": \"編輯器\",\n            \"mainExit\": \"退出\",\n            \"mainMultiplayerGame\": \"雙人遊戲\",\n            \"mainOptions\": \"遊戲選項\",\n            \"mainPlayEpisode\": \"開始遊戲\"\n        },\n        \"options\": {\n            \"restartEngine\": \"重啓引擎后生效\",\n            \"advanced\": {\n                \"scale-down-textures\": {\n                    \"all-tip\": \"縮小全部圖片：載入卡頓的機率比“僅點陣2x”低\",\n                    \"none-tip\": \"不縮小任何圖片：載入卡頓的機率更低\",\n                    \"_tooltip\": \"將圖片縮小一半以釋放內存\",\n                    \"none\": \"無\",\n                    \"_name\": \"縮小圖片\",\n                    \"safe\": \"僅點陣2x\",\n                    \"safe-tip\": \"僅點陣2x：如果圖片為點陣2x，將縮小一半\",\n                    \"all\": \"全部\"\n                },\n                \"audio-enable\": \"啟用聲音\",\n                \"render\": {\n                    \"sdl-tip\": \"使用SDL2，基礎跨平臺渲染器。\",\n                    \"_name\": \"圖形渲染類型\",\n                    \"hw\": \"自動選擇\",\n                    \"sw\": \"軟件渲染器\",\n                    \"opengl\": \"OpenGL 3+\",\n                    \"opengl11\": \"OpenGL 1.1\",\n                    \"opengl-tip\": \"使用新版電腦渲染器，用於最精確的SMBX64模擬，並支持增強視覺效果。\",\n                    \"opengl11-tip\": \"使用舊版電腦渲染器，僅用於最精確的SMBX64模擬，不支持增強視覺效果。\",\n                    \"opengles\": \"OpenGL ES 2+\",\n                    \"opengles11\": \"OpenGL ES 1.1+\",\n                    \"opengles-tip\": \"使用新版手機渲染器，用於高精確度的SMBX64模擬，並支持增強視覺效果。\",\n                    \"opengles11-tip\": \"使用舊版手機渲染器，僅用於最精確的SMBX64模擬，不支援增強視覺效果。\",\n                    \"sdl\": \"SDL2\"\n                },\n                \"_header\": \"高階設定\",\n                \"_tooltip\": \"有關內部操作的技術性設定選項\",\n                \"advanced-audio\": \"音訊選項\",\n                \"advanced-video\": \"視訊選項\",\n                \"log-level\": {\n                    \"debug\": \"調試\",\n                    \"fatal\": \"異常中止\",\n                    \"none\": \"關閉\",\n                    \"_name\": \"日誌記錄等級\",\n                    \"critical\": \"緊急狀態\",\n                    \"info\": \"信息\",\n                    \"warning\": \"警告\"\n                },\n                \"record-gameplay-data\": \"生成録像\",\n                \"audio-channels\": {\n                    \"mono\": \"單聲道\",\n                    \"stereo\": \"立體聲\",\n                    \"_name\": \"輸出聲道\"\n                },\n                \"audio-buffer-size\": {\n                    \"_name\": \"緩衝大小\",\n                    \"_tooltip\": \"降低未預期的噪音，增加延遲\"\n                },\n                \"audio-format\": {\n                    \"_name\": \"音訊格式\",\n                    \"_tooltip\": \"音效卡驅動音訊格式\"\n                },\n                \"audio-sample-rate\": {\n                    \"11025\": \"11025赫茲\",\n                    \"16000\": \"16000赫茲\",\n                    \"22050\": \"22050赫茲\",\n                    \"32000\": \"32000赫茲\",\n                    \"44100\": \"44100赫茲\",\n                    \"48000\": \"48000赫茲\",\n                    \"_name\": \"取樣率\"\n                },\n                \"choose-assets-on-launch\": \"啟動程序時，彈出選擇組件介面\",\n                \"webm-recording\": \"録製WEBM視訊\",\n                \"internal-res-4p\": {\n                    \"fhd\": \"1920x1080（FHD）\",\n                    \"qhd\": \"2560x1440（QHD）\",\n                    \"qsmbx\": \"1600x1200（QSMBX）\",\n                    \"_name\": \"四人分屏模式畫面大小\",\n                    \"default\": \"缺省\"\n                },\n                \"use-native-osk\": \"啓用內置虛擬鍵盤\",\n                \"inaccurate-gifs\": \"錯誤GIF圖片渲染\",\n                \"inaccurate-gifs-tip\": \"當GIF圖片檔案存在時，啟用3D效果\",\n                \"fullscreen-type\": {\n                    \"_name\": \"全屏模式顯示類型\",\n                    \"auto\": \"自動選擇\",\n                    \"desktop\": \"電腦\",\n                    \"desktop-tip\": \"以當前分辨率顯示\",\n                    \"exclusive-tip\": \"設置實際畫面大小\",\n                    \"exclusive\": \"獨立設置\"\n                },\n                \"fullscreen-depth\": {\n                    \"32\": \"32位\",\n                    \"_name\": \"位深\",\n                    \"16\": \"16位\",\n                    \"auto\": \"自動\"\n                },\n                \"fullscreen-res\": \"分辨率\"\n            },\n            \"audio\": {\n                \"audio-sfx-volume\": \"聲音音量\",\n                \"sfx-modern\": \"播放新增的聲音\",\n                \"sfx-audio-fx\": \"迴音效果\",\n                \"sfx-modern-tip\": \"播放TheXTech引擎新增的聲音\",\n                \"sfx-pet-beat\": \"坐騎節奏\",\n                \"sfx-spatial-audio\": \"三維音訊\",\n                \"sfx-spatial-audio-tip\": \"減少畫面外環境的音量\",\n                \"audio-prefs\": \"偏好設定\",\n                \"audio-music-volume\": \"音樂音量\",\n                \"sfx-pet-beat-tip\": \"騎上坐騎時，播放特殊節奏\",\n                \"_header\": \"音訊選項\"\n            },\n            \"compatibility\": {\n                \"features\": \"新特性\",\n                \"_header\": \"遊戲微調選項\",\n                \"_tooltip\": \"兼容模式或調試模式選項，僅對當前遊戲生效\",\n                \"bugfixes\": \"BUG修復\",\n                \"autocode\": \"LunaScript代碼\",\n                \"reset-all\": \"重置全部\"\n            },\n            \"episode-options\": {\n                \"creator-compat\": {\n                    \"enable\": \"開啓\",\n                    \"disable\": \"關閉\",\n                    \"_tooltip\": \"創作者相關的邏輯微調\",\n                    \"_name\": \"創作者模式\",\n                    \"file-only\": \"僅檔案\"\n                },\n                \"_header\": \"地圖選項\",\n                \"playstyle\": {\n                    \"_name\": \"遊戲模式\",\n                    \"classic\": \"經典模式\",\n                    \"classic-tip\": \"開啟部分遊戲更新\",\n                    \"modern\": \"現代模式\",\n                    \"modern-tip\": \"開啟全部遊戲更新\",\n                    \"vanilla-tip\": \"關閉全部遊戲更新\",\n                    \"vanilla\": \"懷舊模式\"\n                },\n                \"hub-resume\": \"中心關卡：傳送點記憶\",\n                \"hub-resume-tip\": \"從上次進入的傳送點繼續遊戲\"\n            },\n            \"main\": {\n                \"two-screen-mode\": {\n                    \"topbottom\": \"上下分屏\",\n                    \"_name\": \"雙人遊戲畫面模式\",\n                    \"shared\": \"同屏\",\n                    \"smbx\": \"SMBX\",\n                    \"split\": \"左右分屏\"\n                },\n                \"unlimited-framerate\": \"禁用鎖幀\",\n                \"_header\": \"常規選項\",\n                \"background-work\": \"後臺執行\",\n                \"background-work-tip\": \"窗口未激活時，可通過手柄遊玩\",\n                \"four-screen-mode\": {\n                    \"_name\": \"四人遊戲畫面模式\",\n                    \"shared\": \"同屏\",\n                    \"split\": \"分屏\"\n                },\n                \"frame-skip\": \"跳幀\",\n                \"multiplayer\": \"多人遊戲\",\n                \"timing\": \"幀數控制\",\n                \"discord-rpc\": \"在Discord中顯示\",\n                \"language\": \"語言\",\n                \"vsync\": \"垂直同步\"\n            },\n            \"video\": {\n                \"battery-status\": {\n                    \"fullscreen-tip\": \"全屏遊戲時，顯示電池電量\",\n                    \"_name\": \"電池狀態\",\n                    \"on\": \"顯示\",\n                    \"fullscreen\": \"全屏時顯示\",\n                    \"low\": \"僅在低電量時顯示\",\n                    \"low-tip\": \"在電池電量低時，顯示電池電量\",\n                    \"off\": \"隱藏\"\n                },\n                \"info-meta\": \"畫面中元數據信息\",\n                \"show-episode-title\": {\n                    \"bottom\": \"底部\",\n                    \"off\": \"隱藏\",\n                    \"top\": \"頂部\",\n                    \"bottom-tip\": \"在秒錶計時器的上方顯示當前地圖\",\n                    \"top-tip\": \"在遊戲畫面大小較大時，在狀態列上方顯示當前地圖\",\n                    \"_name\": \"顯示當前地圖\"\n                },\n                \"show-fails-counter\": \"顯示失敗次數\",\n                \"show-playtime-counter\": {\n                    \"_name\": \"秒錶計時器\",\n                    \"off-tip\": \"僅在速通時顯示秒錶計時器\",\n                    \"animated\": \"動態特效\",\n                    \"_tooltip\": \"顯示當前地圖和當前關卡的遊玩時間\",\n                    \"transparent\": \"開啟（透明）\",\n                    \"opaque\": \"開啟（不透明）\",\n                    \"off\": \"關閉\",\n                    \"animated-tip\": \"過關時，為秒錶計時器新增彩虹特效\"\n                },\n                \"world-map-stars-show\": \"大地圖上顯示星星數量\",\n                \"video-system\": \"系統選項\",\n                \"show-screen-shake\": \"畫面震動\",\n                \"show-medals-counter\": \"顯示獎牌數量\",\n                \"3d-compat-mode\": \"三維兼容模式\",\n                \"3d-compat-mode-tip\": \"在同一個三維平面下繪製所有物件\",\n                \"world-map-stars-show-tip\": \"在關卡上方顯示已收集星星數量\",\n                \"show-fps\": \"顯示幀數\",\n                \"show-medals-counter-tip\": \"當玩家在關卡入口處時，在狀態列上顯示獎牌數量\",\n                \"show-fails-counter-tip\": \"顯示當前關卡和當前地圖的失敗次數\",\n                \"effects\": \"畫面效果\",\n                \"enable-inter-level-fade-effect\": \"過渡效果\",\n                \"fullscreen\": \"全屏模式\",\n                \"display-controllers\": \"顯示按鍵\",\n                \"internal-res\": {\n                    \"nds\": \"512x384（DS）\",\n                    \"hd\": \"1280x720（高清）\",\n                    \"gba\": \"480x320（GBA）\",\n                    \"dynamic\": \"自適應\",\n                    \"hello\": \"768x432（HELLO引擎）\",\n                    \"3ds\": \"800x480 (3DS)\",\n                    \"_name\": \"畫面大小\",\n                    \"_tooltip\": \"設定遊戲畫面大小\",\n                    \"smbx-wide\": \"SMBX（寬屏）\",\n                    \"snes\": \"512x448（SFC）\",\n                    \"vga\": \"640x480（VGA）\",\n                    \"smbx\": \"800x600（SMBX）\"\n                },\n                \"_header\": \"視訊選項\",\n                \"info-game\": \"畫面中游戲信息\",\n                \"info-run\": \"畫面中執行時信息\",\n                \"scale-mode\": {\n                    \"0_5x\": \"0.5倍\",\n                    \"1x\": \"1倍\",\n                    \"2x\": \"2倍\",\n                    \"_name\": \"圖像縮放\",\n                    \"integer\": \"整數\",\n                    \"linear\": \"雙綫性\",\n                    \"nearest\": \"最近鄰居\",\n                    \"full\": \"完全縮放\",\n                    \"center\": \"中心\",\n                    \"3x\": \"3倍\"\n                }\n            },\n            \"view-credits\": {\n                \"_header\": \"查看製作團隊\"\n            },\n            \"controls\": {\n                \"_header\": \"按鍵選項\"\n            },\n            \"speedrun\": {\n                \"mode\": {\n                    \"0\": \"0（默認）\",\n                    \"1\": \"1（現代）\",\n                    \"2\": \"2（經典）\",\n                    \"3\": \"3（懷舊）\",\n                    \"_name\": \"秒錶計時模式\"\n                }\n            }\n        },\n        \"wordBack\": \"返回\",\n        \"wordHide\": \"隱藏\",\n        \"wordNo\": \"否\",\n        \"wordOff\": \"關閉\",\n        \"wordOn\": \"開啓\",\n        \"wordPlayer\": \"玩家\",\n        \"wordProfile\": \"配置\",\n        \"wordResume\": \"恢復遊戲\",\n        \"wordShow\": \"顯示\",\n        \"wordWaiting\": \"等待\",\n        \"wordYes\": \"是\"\n    },\n    \"outro\": {\n        \"cppPortDevelopers\": \"C++移植開發人員：\",\n        \"customSprites\": \"自定義素材：\",\n        \"engineCredits\": \"引擎製作人員名單：\",\n        \"gameCredits\": \"遊戲製作人員名單:\",\n        \"levelDesign\": \"關卡設計：\",\n        \"nameAndrewSpinks\": \"安德魯·斯賓克斯\",\n        \"nameVitalyNovichkov\": \"維塔利·諾維奇科夫\",\n        \"originalBy\": \"原始VB6代碼：\",\n        \"psVitaPortBy\": \"PS Vita移植：\",\n        \"specialThanks\": \"特別鳴謝：\",\n        \"qualityControl\": \"品質管理：\"\n    },\n    \"pluralRules\": \"singular-only\"\n}\n"
  },
  {
    "path": "resources/switch/Important.txt",
    "content": "The valid icon must be:\n- Should be a JPEG format\n- No EXIF included, absolutely\n- 256x256 size\n"
  },
  {
    "path": "resources/thextech.plist.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDocumentTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>lvl</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>file_lvl.icns</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>SMBX64/38A Level File</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Owner</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>lvlx</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>file_lvlx.icns</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Moondust Level File</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Owner</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>wld</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>file_wld.icns</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>SMBX64/38A World map file</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Owner</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>wldx</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>file_wldx.icns</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Moondust World map file</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Owner</string>\n\t\t</dict>\n\t</array>\n\t<key>CFBundleExecutable</key>\n\t<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>\n\t<key>CFBundleIconFile</key>\n\t<string>${MACOSX_BUNDLE_ICON_FILE}</string>\n\t<key>CFBundleName</key>\n\t<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>\n\t<key>CFBundleVersion</key>\n\t<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>\n\t<key>CFBundleSignature</key>\n\t<string>TXTH</string>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.arcade-games</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>10.7</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>NSPrincipalClass</key>\n\t<string>TheXTechApplication</string>\n\t<key>NSHighResolutionCapable</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "resources/thextech.plist.tiger.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDocumentTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>lvl</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>file_lvl.icns</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>SMBX64/38A Level File</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Owner</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>lvlx</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>file_lvlx.icns</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Moondust Level File</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Owner</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>wld</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>file_wld.icns</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>SMBX64/38A World map file</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Owner</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>wldx</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>file_wldx.icns</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Moondust World map file</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t\t<key>LSHandlerRank</key>\n\t\t\t<string>Owner</string>\n\t\t</dict>\n\t</array>\n\t<key>CFBundleExecutable</key>\n\t<string>@THEXTECH_EXECUTABLE_NAME_OUT@</string>\n\t<key>CFBundleIconFile</key>\n\t<string>${MACOSX_BUNDLE_ICON_FILE}</string>\n\t<key>CFBundleName</key>\n\t<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>\n\t<key>CFBundleVersion</key>\n\t<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>\n\t<key>CFBundleSignature</key>\n\t<string>TXTH</string>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.arcade-games</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>@THEXTECH_MACOS_MIN_VERSION@</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>NSPrincipalClass</key>\n\t<string>TheXTechApplication</string>\n\t<key>NSHighResolutionCapable</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "resources/thextech.rc",
    "content": "IDI_ICON1               ICON    DISCARDABLE     \"thextech.ico\"\n\n#include <windows.h>\n#include \"../version.h\"\n\nVS_VERSION_INFO     VERSIONINFO\nFILEVERSION         V_VF1, V_VF2, V_VF3, V_VF4\nPRODUCTVERSION      V_VP1, V_VP2, V_VP3, V_VP4\nFILEFLAGSMASK       0x3fL\nFILEFLAGS           0\nFILEOS              VOS_NT_WINDOWS32\nFILETYPE            VFT_APP\nFILESUBTYPE         VFT2_UNKNOWN\nBEGIN\nBLOCK   \"VarFileInfo\"\nBEGIN\n    VALUE   \"Translation\",  0x409,  1200\nEND\nBLOCK   \"StringFileInfo\"\nBEGIN\n    BLOCK   \"040904b0\"\n    BEGIN\n        VALUE   \"CompanyName\",      V_COMPANY\n        VALUE   \"FileDescription\",  V_FILE_DESC\n        VALUE   \"FileVersion\",      V_FILE_VERSION V_FILE_RELEASE\n        VALUE   \"InternalName\",     V_INTERNAL_NAME\n        VALUE   \"LegalCopyright\",   V_COPYRIGHT\n        VALUE   \"OriginalFilename\", V_ORIGINAL_NAME\n        VALUE   \"ProductName\",      V_PRODUCT_NAME\n        VALUE   \"ProductVersion\",   V_VERSION V_RELEASE\n    END\nEND\nEND\n"
  },
  {
    "path": "resources/tiger/TheXTechRun.in",
    "content": "#!/bin/sh\n\nnumber=$(ps aux | grep -v grep | grep -ci X11)\n\nif [ $number -le 0 ]; then\n    hadHomeInit=false\n\n    if [ -f ~/.xinitrc ]; then\n        F=`mktemp -t ~/.xinitrc-XXXX`\n        cp -f ~/.xinitrc $F\n        hadHomeInit=true\n    else\n        F=/etc/X11/xinit/xinitrc\n        cp -f $F ~/.xinitrc\n    fi\n\n    sed -ie 's/^ *xterm/#xterm/' ~/.xinitrc\n\n    open /Applications/Utilities/X11.app\n\n    sleep 4\n\n    if $hadHomeInit ; then\n        mv -f $F ~/.xinitrc\n    else\n        rm -f ~/.xinitrc\n    fi\nfi\n\nSCRIPTPATH=`dirname \"$0\"`\ncd $SCRIPTPATH\n\n./@THEXTECH_BUNDLE_NAME@ &\n\nexit 0\n"
  },
  {
    "path": "resources/vita/frag.cgf",
    "content": "\nfloat4 main(\n    float4 fragColor : COLOR,\n    float2 texCoord : TEXCOORD0,\n    uniform int useTexture,\n    uniform sampler2D ourTexture\n) : COLOR\n{\n    if(useTexture == 1)\n    {\n        float4 texSamp = tex2D(ourTexture, texCoord) * fragColor;\n        return texSamp;\n    }\n    else\n        return fragColor;\n}"
  },
  {
    "path": "resources/vita/sce_sys/livearea/contents/template.xml.in",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<livearea style=\"a1\" format-ver=\"01.00\" content-rev=\"1\">\n\t<!-- <livearea-background> -->\n\t\t<!-- <image>bg.png</image> -->\n\t<!-- </livearea-background> -->\n\t\n\t<gate>\n\t\t<startup-image>startup.png</startup-image>\n\t</gate>\n\t\n\t<frame id=\"frame1\">\n\t\t<liveitem>\n\t\t\t<text>\n\t\t\t\t<str size=\"20\" color=\"#ffffff\" shadow=\"on\">@XTECH_VITA_AUTHORS@</str>\n\t\t\t</text>\n\t\t</liveitem>\n\t</frame>\n\n\t<frame id=\"frame2\">\n\t\t<liveitem>\n\t\t\t<text>\n\t\t\t\t<str size=\"20\" color=\"#ffffff\" shadow=\"on\">@XTECH_VITA_SHORT_DESC@</str>\n\t\t\t</text>\n\t\t</liveitem>\n\t</frame>\n\n</livearea>\n"
  },
  {
    "path": "resources/vita/vert.cgv",
    "content": "void main(\n    float2 aPosition,\n    float2 vTexCoord,\n    float4 vColor,\n    uniform float4x4 mvp,\n    uniform float4x4 _rot,\n    uniform float4x4 _scale,\n    float4 out vertPos : POSITION,\n    float4 out fragColor : COLOR,\n    float2 out texCoord : TEXCOORD0\n)\n{\n    vertPos = mul(mul(mul(float4(aPosition, 0.f, 1.f), _scale), _rot), mvp);\n    fragColor = vColor;\n    texCoord = vTexCoord;\n}"
  },
  {
    "path": "resources/wii/meta.xml.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<app version=\"1\">\n\t<name>TheXTech</name>\n\t<coder>Wohlstand and ds-sloth</coder>\n\t<version>@THEXTECH_VERSION_STRING@</version>\n\t<release_date>@XTECH_WIIMETA_RELEASE_DATE@</release_date>\n\t<short_description>@XTECH_WIIMETA_SHORT_DESC@</short_description>\n\t<long_description>A free and open-source game engine for platforming games. This is a full source code port of the Super Mario Bros. X (SMBX) 1.3 game engine originally developed by Andrew Spinks from 2009 to 2011, and its unofficial continuation after the VB6 source code was released in 2020.\n\nhttps://wohlsoft.ru/projects/TheXTech/\nhttps://github.com/TheXTech/TheXTech\n</long_description>\n\t<no_ios_reload/>\n</app>\n"
  },
  {
    "path": "resources/wiiu/meta.xml.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<app version=\"1\">\n    <name>TheXTech</name>\n    <coder>Wohlstand and ds-sloth</coder>\n    <version>@THEXTECH_VERSION_STRING@</version>\n    <release_date>@XTECH_WIIMETA_RELEASE_DATE@</release_date>\n    <short_description>The platforming game engine.</short_description>\n    <long_description><![CDATA[This is the free and open-source game engine for Mario-like platforming games. This is a complete and extended source code port of the Super Mario Bros. X 1.3 game engine (later just \"SMBX\") (originally developed by Andrew Spinks in 2009), and its direct unofficial continuation after development halted in the 2011th year.\n\nhttps://wohlsoft.ru/projects/TheXTech/\nhttps://github.com/Wohlstand/TheXTech]]></long_description>\n</app>\n"
  },
  {
    "path": "script/CMakeLists.txt",
    "content": "cmake_minimum_required (VERSION 3.5)\nproject(PGEFileLibrary C CXX)\n\nif(NOT CMAKE_BUILD_TYPE)\n    set(CMAKE_BUILD_TYPE \"Release\" CACHE STRING \"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel.\" FORCE)\n    message(\"== Using default build configuration which is a Release!\")\nendif()\n\nif(POLICY CMP0077) # Allow external configuring when building as sub-directory\n    cmake_policy(SET CMP0077 NEW)\nendif()\n\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)\nset(CMAKE_DEBUG_POSTFIX \"d\")\n\ninclude(../cmake/build_props.cmake)\n\npge_cxx_standard(11)\n\nset(LIB_XTECHLUA_SRCS\n    src/xtech_lua_main.cpp\n)\n\nadd_library(XTechLua STATIC\n    ${LIB_XTECHLUA_SRCS}\n)\n\ntarget_include_directories(XTechLua PRIVATE\n    include src lib\n    ${TheXTech_SOURCE_DIR}/src\n    ${TheXTech_SOURCE_DIR}/lib\n    \"${DEPENDENCIES_INSTALL_DIR}/include\"\n    ${A2XT_INCS}\n)\n\nadd_dependencies(XTechLua LuaBind_Local ${A2XT_DEPS})\n"
  },
  {
    "path": "script/include/xtech_lua_main.h",
    "content": "#ifndef LUA_MAIN_HHH\n#define LUA_MAIN_HHH\n\nextern bool xtech_lua_init();\nextern bool xtech_lua_quit();\n\n#endif // LUA_MAIN_HHH\n"
  },
  {
    "path": "script/src/xtech_lua_main.cpp",
    "content": "#include \"xtech_lua_main.h\"\n\n\nbool xtech_lua_init()\n{\n    return true;\n}\n\nbool xtech_lua_quit()\n{\n    return true;\n}\n"
  },
  {
    "path": "src/blk_id.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef ENUMBLKID_HHH\n#define ENUMBLKID_HHH\n\nenum BLKID\n{\n    BLKID_SPIN_BLOCK = 90,\n    BLKID_RED_BRICK = 186,\n    BLKID_GRY_BRICK = 457,\n\n    // these can be expanded into user-modifiable ranges in the future.\n    // currently the \"CONV\" blocks can't be placed or modified in any way by the user\n    BLKID_CONVEYOR_L_START = 701,\n    BLKID_CONVEYOR_L_CONV = 701,\n    BLKID_CONVEYOR_L_END = 701,\n\n    BLKID_CONVEYOR_R_START = 702,\n    BLKID_CONVEYOR_R_CONV = 702,\n    BLKID_CONVEYOR_R_END = 702,\n};\n\n\n#endif // ENUMBLKID_HHH\n"
  },
  {
    "path": "src/blocks.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include <algorithm>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"globals.h\"\n#include \"frame_timer.h\"\n#include \"blocks.h\"\n#include \"sound.h\"\n#include \"graphics.h\"\n#include \"effect.h\"\n#include \"collision.h\"\n#include \"npc.h\"\n#include \"npc_id.h\"\n#include \"eff_id.h\"\n#include \"blk_id.h\"\n#include \"npc_traits.h\"\n#include \"player.h\"\n#include \"sorting.h\"\n#include \"layers.h\"\n#include \"config.h\"\n#include \"editor.h\"\n#include \"game_main.h\"\n\n#include \"graphics/gfx_update.h\"\n#include \"npc/npc_queues.h\"\n#include \"main/trees.h\"\n#include \"main/game_loop_interrupt.h\"\n\nvoid s_makeCoin(Block_t& b)\n{\n    Coins += 1;\n    if(Coins >= 100)\n        Got100Coins();\n\n    PlaySoundSpatial(SFX_Coin, b.Location);\n    NewEffect(EFFID_COIN_BLOCK_S3, b.Location);\n}\n\nstatic int s_findNearbyPlayer(const Location_t& loc)\n{\n    if(numPlayers == 1)\n        return 1;\n\n    int target_plr = 1;\n    num_t min_dist = 0;\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        const Player_t& player = Player[B];\n        if(!player.Dead)\n        {\n            num_t dx = loc.minus_center_x(player.Location);\n            num_t dy = loc.minus_center_y(player.Location);\n            num_t dist = num_t::dist2(dx, dy);\n            if(min_dist == 0 || dist < min_dist)\n            {\n                min_dist = dist;\n                target_plr = B;\n            }\n        }\n    }\n\n    return target_plr;\n}\n\n// initial value for ScreensLeft -- sets bit for each screen that has players\nstatic uint16_t s_InitScreensLeft()\n{\n    uint16_t ScreensLeft = 0;\n\n    uint16_t cur_bit = 1;\n    for(int i = 0; i < maxNetplayClients; i++, cur_bit <<= 1)\n    {\n        if(Screens[i].player_count)\n            ScreensLeft |= cur_bit;\n    }\n\n    return ScreensLeft;\n}\n\nvoid BlockHit(int A, bool HitDown, int whatPlayer)\n{\n    // int tempPlayer = 0;\n    // bool makeShroom = false; // if true make a mushroom\n    int newBlock = 0; // what the block should turn into if anything\n    // int C = 0;\n//    int B = 0;\n    // Block_t blankBlock;\n    int oldSpecial = 0; // previous .Special\n    // bool tempBool = false;\n    // Location_t tempLocation;\n\n    auto &b = Block[A];\n\n    if(BattleMode && b.RespawnDelay_ScreensLeft == 0)\n        b.RespawnDelay_ScreensLeft = 1;\n\n    // if(whatPlayer != 0)\n    //     Controls::Rumble(whatPlayer, 10, .1);\n\n    // character switch blocks\n    if((b.Type >= 622 && b.Type <= 625) || b.Type == 631)\n    {\n        if(whatPlayer == 0)\n        {\n            return;\n        }\n        else\n        {\n            b.Special = 0;\n\n            // note: this line was previously inside the below for loop\n            SavedChar[Player[whatPlayer].Character] = Player[whatPlayer];\n\n            // mark characters in current use as unavailable\n            for(auto B = 1; B <= numPlayers; B++)\n            {\n                switch(Player[B].Character)\n                {\n                case 1: BlockFrame[622] = 4; break;\n                case 2: BlockFrame[623] = 4; break;\n                case 3: BlockFrame[624] = 4; break;\n                case 4: BlockFrame[625] = 4; break;\n                case 5: BlockFrame[631] = 4; break;\n                default: break;\n                }\n            }\n\n            if(BlockFrame[b.Type] < 4)\n            {\n                // UnDuck whatPlayer // (commented since SMBX64)\n\n                int transform_to = 0;\n\n                switch(b.Type)\n                {\n                case 622: transform_to = 1; break;\n                case 623: transform_to = 2; break;\n                case 624: transform_to = 3; break;\n                case 625: transform_to = 4; break;\n                case 631: transform_to = 5; break;\n                default: break;\n                }\n\n                // moved SwapCharacter logic into player.cpp\n\n                if(transform_to)\n                    SwapCharacter(whatPlayer, transform_to, true);\n\n                PlaySoundSpatial(SFX_Transform, b.Location);\n            }\n            else\n            {\n                return;\n            }\n        }\n    }\n\n    oldSpecial = b.Special;\n\n    // if(b.ShakeY != 0 || b.ShakeY2 != 0 || b.ShakeY3 != 0) // if the block has just been hit, ignore\n    if(b.ShakeCounter != 0) // if the block has just been hit, ignore\n    {\n        if(b.RapidHit > 0 && IF_INRANGE(whatPlayer, 1, maxPlayers) && Player[whatPlayer].Character == 4)\n            b.RapidHit = iRand(3) + 1;\n        return;\n    }\n\n    b.Invis = false;\n\n    // check if block with contents is blocked from below, if so cancel hit from above\n    if(HitDown && b.Special > 0)\n    {\n        // tempBool = false; // unused since SMBX64, must have been replaced with the simple modification of HitDown\n\n        // this is symmetric to the original transformation on the *other* block's location, but much cheaper\n        const auto query_loc = newLoc(b.Location.X + 4, b.Location.Y + 16, b.Location.Width - 8, b.Location.Height);\n\n        for(int B : treeBlockQuery(query_loc, SORTMODE_NONE))\n        {\n            if(B != A)\n            {\n                const auto &bLoc = Block[B].Location;\n                // if(CheckCollision(b.Location, newLoc(bLoc.X + 4, bLoc.Y - 16, bLoc.Width - 8, bLoc.Height)))\n                if(CheckCollision(query_loc, bLoc))\n                {\n                    HitDown = false;\n                    break;\n                }\n            }\n        }\n    }\n\n    if(b.Special == 1000 + NPCID_RED_VINE_TOP_S3 || b.Special == 1000 + NPCID_GRN_VINE_TOP_S3 || b.Special == 1000 + NPCID_GRN_VINE_TOP_S4)\n        HitDown = false;\n\n\n    // Shake the block\n    if(b.Type == 4 || b.Type == 615 || b.Type == 55 ||\n       b.Type == 60 || b.Type == 90 || b.Type == 159 ||\n       b.Type == 169 || b.Type == 170 || b.Type == 173 ||\n       b.Type == 176 || b.Type == 179 || b.Type == 188 ||\n       b.Type == 226 || b.Type == 281 || b.Type == 282 ||\n       b.Type == 283 || (b.Type >= 622 && b.Type <= 625))\n    {\n        if(!HitDown)\n            BlockShakeUp(A);\n        else\n            BlockShakeDown(A);\n    }\n\n\n    if(b.Type == 169)\n    {\n        PlaySoundSpatial(SFX_PSwitch, b.Location);\n        BeltDirection = -BeltDirection; // for the blet direction changing block\n\n        for(auto B = 1; B <= numBlock; B++)\n        {\n            if(Block[B].Type >= BLKID_CONVEYOR_L_START && Block[B].Type <= BLKID_CONVEYOR_L_END)\n            {\n                Block[B].Type += BLKID_CONVEYOR_R_START - BLKID_CONVEYOR_L_START;\n                Block[B].Location.SpeedX = (num_t)Layer[Block[B].Layer].ApplySpeedX + 0.8_n;\n            }\n            else if(Block[B].Type >= BLKID_CONVEYOR_R_START && Block[B].Type <= BLKID_CONVEYOR_R_END)\n            {\n                Block[B].Type -= BLKID_CONVEYOR_R_START - BLKID_CONVEYOR_L_START;\n                Block[B].Location.SpeedX = (num_t)Layer[Block[B].Layer].ApplySpeedX - 0.8_n;\n            }\n        }\n    }\n\n\n    // note: these four cases were previously handled using separate code\n    NPCID switch_npc = NPCID_NULL;\n\n    if(b.Type == 170)\n        switch_npc = NPCID_YEL_PLATFORM;\n    else if(b.Type == 173)\n        switch_npc = NPCID_BLU_PLATFORM;\n    else if(b.Type == 176)\n        switch_npc = NPCID_GRN_PLATFORM;\n    else if(b.Type == 179)\n        switch_npc = NPCID_RED_PLATFORM;\n\n    if(switch_npc != NPCID_NULL) // switch blocks\n    {\n        PlaySoundSpatial(SFX_PSwitch, b.Location);\n        for(auto B = 1; B <= numBlock; B++)\n        {\n            if(Block[B].Type == b.Type + 1)\n                Block[B].Type = b.Type + 2;\n            else if(Block[B].Type == b.Type + 2)\n                Block[B].Type = b.Type + 1;\n        }\n\n        for(auto B = 1; B <= numNPCs; B++)\n        {\n            if(NPC[B].Type == switch_npc)\n                NPC[B].Direction = -NPC[B].Direction;\n        }\n    }\n\n    // Find out what the block should turn into\n    if(b.Type == 88 || b.Type == 90 || b.Type == 89 || b.Type == 171 || b.Type == 174 || b.Type == 177 || b.Type == 180) // SMW\n        newBlock = 89;\n    else if(b.Type == 188 || b.Type == 192 || b.Type == 193 || b.Type == 60 || b.Type == 369) // SMB1\n        newBlock = 192;\n    else if(b.Type == 224 || b.Type == 225 || b.Type == 226) // Large SMB3 blocks\n        newBlock = 225;\n    else if(b.Type == 159) // SMB3 Battle Block\n        newBlock = 159;\n    else // Everything else defaults to SMB3\n        newBlock = 2;\n\n    if(b.Special > 0 && b.Special < 100) // Block has coins\n    {\n        if(whatPlayer > 0 && Player[whatPlayer].Character == 4)\n            b.RapidHit = iRand(3) + 1;\n\n        if(!HitDown)\n            BlockShakeUp(A);\n        else\n            BlockShakeDown(A);\n\n\n        // chars 2 and 5 make all coins come out at once\n        if(whatPlayer > 0 && (Player[whatPlayer].Character == 2 || Player[whatPlayer].Character == 5))\n        {\n            // check if blocked from above\n            bool blocked_above = false;\n            const auto query_loc = newLoc(b.Location.X + 1, b.Location.Y - 31, 30, 30);\n\n            for(int B : treeBlockQuery(query_loc, SORTMODE_NONE))\n            {\n                if(B != A && !Block[B].Hidden && !BlockOnlyHitspot1[Block[B].Type] && !BlockIsSizable[Block[B].Type])\n                {\n                    // if(CheckCollision(Block[B].Location, newLoc(b.Location.X + 1, b.Location.Y - 31, 30, 30)))\n                    if(CheckCollision(Block[B].Location, query_loc))\n                    {\n                        blocked_above = true;\n                        break;\n                    }\n                }\n            }\n\n            if(!blocked_above)\n            {\n                for(auto B = 1; B <= b.Special; B++)\n                {\n                    numNPCs++;\n                    auto &nn = NPC[numNPCs];\n\n                    nn.Active = true;\n                    nn.TimeLeft = 100;\n\n                    if(newBlock == 89)\n                        nn.Type = NPCID_COIN_S4;\n                    else if(newBlock == 192)\n                        nn.Type = NPCID_COIN_S1;\n                    else\n                        nn.Type = NPCID_COIN_S3;\n\n                    if(Player[whatPlayer].Character == 5)\n                    {\n                        nn.Type = NPCID_GEM_1;\n                        if(iRand(20) < 3)\n                            nn.Type = NPCID_GEM_5;\n                        if(iRand(60) < 3)\n                            nn.Type = NPCID_GEM_20;\n                        PlaySoundSpatial(SFX_HeroRupee, b.Location);\n                    }\n                    else\n                    {\n                        PlaySoundSpatial(SFX_Coin, b.Location);\n                    }\n\n                    auto &nLoc = nn.Location;\n                    nLoc.Width = nn->TWidth;\n                    nLoc.Height = nn->THeight;\n                    nLoc.X = b.Location.X + (b.Location.Width - nLoc.Width) / 2;\n                    nLoc.Y = b.Location.Y - nLoc.Height - 0.01_n;\n                    nLoc.SpeedX = dRand() * 3 - 1.5_n;\n                    nLoc.SpeedY = -(dRand() * 4) - 3;\n                    if(HitDown)\n                    {\n                        nLoc.SpeedY = -nLoc.SpeedY / 2;\n                        nLoc.Y = b.Location.Y + b.Location.Height;\n                    }\n\n                    nn.Special = 1;\n                    nn.Immune = 20;\n                    syncLayers_NPC(numNPCs);\n                    CheckSectionNPC(numNPCs);\n\n                    if(B > 20 || (Player[whatPlayer].Character == 5 && B > 5))\n                        break;\n                }\n                b.Special = 0;\n            }\n            else\n            {\n                s_makeCoin(b);\n                b.Special -= 1;\n            }\n        }\n        else if(b.RapidHit > 0) // (whatPlayer > 0 And Player(whatPlayer).Character = 3)\n        {\n            // check if anything is blocking from above\n            bool blocked_above = false;\n            const auto query_loc = newLoc(b.Location.X + 1, b.Location.Y - 31, 30, 30);\n\n            for(int B : treeBlockQuery(query_loc, SORTMODE_NONE))\n            {\n                if(B != A && !Block[B].Hidden && !(BlockOnlyHitspot1[Block[B].Type] && !BlockIsSizable[Block[B].Type]))\n                {\n                    if(CheckCollision(Block[B].Location, query_loc))\n                    {\n                        blocked_above = true;\n                        break;\n                    }\n                }\n            }\n\n            if(!blocked_above)\n            {\n                numNPCs++;\n                auto &nn = NPC[numNPCs];\n                nn.Active = true;\n                nn.TimeLeft = 100;\n\n                // NOTE: This code was already broken by Redigit. It should spawn a coin depending on thematic block\n#if 0 // Useless code, needs a fix. Right now its broken by overriding by constant value\n                if(newBlock == 89)\n                {\n                    nn.Type = NPCID_COIN_S4;\n                }\n                else if(newBlock == 192)\n                {\n                    nn.Type = NPCID_COIN_S1;\n                }\n                else\n                {\n                    nn.Type = NPCID_COIN_S3;\n                }\n#endif\n                nn.Type = NPCID_COIN_S2;\n\n                nn.Location.Width = nn->TWidth;\n                nn.Location.Height = nn->THeight;\n                nn.Location.X = b.Location.X + (b.Location.Width - nn.Location.Width) / 2;\n                nn.Location.Y = b.Location.Y - nn.Location.Height - 0.01_n;\n                nn.Location.SpeedX = dRand() * 3 - 1.5_n;\n                nn.Location.SpeedY = -(dRand() * 4) - 3;\n                nn.Special = 1;\n                nn.Immune = 20;\n                PlaySoundSpatial(SFX_Coin, b.Location);\n                syncLayers_NPC(numNPCs);\n                CheckSectionNPC(numNPCs);\n                b.Special -= 1;\n            }\n            else\n            {\n                s_makeCoin(b);\n                b.Special -= 1;\n            }\n        }\n        else\n        {\n            s_makeCoin(b);\n            b.Special -= 1;\n        }\n\n        if(b.Special == 0 && b.Type != 55)\n        {\n            b.Type = newBlock;\n            b.Location.Height = BlockHeight[newBlock];\n            b.Location.Width = BlockWidth[newBlock];\n        }\n    }\n    else if(b.Special >= 100) // New spawn code\n    {\n        // represents behavior for ancient 101 / 104 / 201 Special values (other ancient values replaced in OpenLevel)\n        bool is_ancient = false;\n\n        NPCID C = NPCID_NULL;\n\n        if(b.Special >= 1000)\n        {\n            C = NPCID(b.Special - 1000); // this finds the NPC type and puts in the variable C\n        }\n        else if(b.Special == 201)\n        {\n            C = NPCID_LIFE_S3;\n            is_ancient = true;\n        }\n        else if(b.Special == 104)\n        {\n            C = NPCID_GRN_BOOT;\n            is_ancient = true;\n        }\n        else if(b.Special == 110)\n        {\n            if(InvincibilityTime)\n                C = NPCID_INVINCIBILITY_POWER;\n            else\n                C = NPCID_NULL;\n        }\n        else // b.Special == 101\n        {\n            C = NPCID_FODDER_S3;\n            is_ancient = true;\n        }\n\n        // NEW: logic to allow \"important\" NPCs to spawn multiple times, once per screen, in NetPlay\n        // Intentionally excludes keys and coin/timer switches because those could be used to defeat puzzles\n        if(!BattleMode && (NPCTraits[C].IsABonus || NPCLongLife(C) || C == NPCID_RANDOM_POWER || C == NPCID_NULL))\n        {\n            if(!b.RespawnDelay_ScreensLeft)\n                b.RespawnDelay_ScreensLeft = s_InitScreensLeft();\n\n            int use_player = whatPlayer;\n            if(use_player <= 0)\n            {\n                // find nearest player\n                use_player = s_findNearbyPlayer(b.Location);\n            }\n\n            // find player's screen index\n            uint16_t screen_index = ScreenIdxByPlayer(use_player);\n            uint16_t screen_index_bit = (1U << screen_index);\n\n            if(screen_index >= 16)\n            {\n                // invalid screen index, cancel the block for everyone\n                b.RespawnDelay_ScreensLeft = 0;\n            }\n            else if(b.RespawnDelay_ScreensLeft & screen_index_bit)\n            {\n                // cancel the block just for this player's screen\n                b.RespawnDelay_ScreensLeft &= ~screen_index_bit;\n            }\n            else\n            {\n                // screen has already hit block, don't make anything!\n                return;\n            }\n        }\n\n        if(!HitDown)\n            BlockShakeUp(A);\n        else\n            BlockShakeDown(A);\n\n        // cancel block, unless some other screen will get to hit it later\n        if(BattleMode || b.RespawnDelay_ScreensLeft == 0)\n        {\n            b.Special = 0;\n\n            if(b.Type != 55) // 55 is the bouncy note block\n            {\n                b.Type = newBlock;\n                b.Location.Height = BlockHeight[newBlock];\n\n                // Was always set in SMBX64. Doing this check here keeps the easy bonus pickup and prevents movement. -- ds-sloth\n                if(!g_config.fix_restored_block_move || !b.getShrinkResized())\n                    b.Location.Width = BlockWidth[newBlock];\n            }\n        }\n\n        // Shake code was duplicated for some reason in ancient code, but that had no effect\n\n#if 0 // Completely disable the DEAD the code that spawns the player\n        if(NPCIsABonus(C) && C != 169 && C != 170) // check to see if it should spawn a dead player\n        {\n            tempPlayer = CheckDead();\n            if(g_ClonedPlayerMode)\n            {\n                tempPlayer = 0;\n            }\n        }\n\n        // don't spawn players from blocks anymore\n        tempPlayer = 0;\n\n        if(tempPlayer == 0) // Spawn the npc\n#endif\n        // Spawn the npc\n        if(C < 1 || C > maxNPCType)\n            s_makeCoin(b);\n        else\n        {\n            numNPCs++; // create a new NPC\n            auto &nn = NPC[numNPCs];\n            nn.Active = true;\n            nn.TimeLeft = 1000;\n\n            if(is_ancient)\n                nn.TimeLeft = 100;\n\n            // always give char 5 or big player full power\n            bool player_gets_full = (whatPlayer > 0 && (Player[whatPlayer].State > 1 || Player[whatPlayer].Character == 5));\n\n            // replacement index if full powerup should not be received\n            NPCID replacement = C;\n\n            if(g_ClonedPlayerMode || BattleMode || player_gets_full)\n            {\n                // never replace if cloned player, in battle mode, or the player that hit the block should get the full powerup\n            }\n            else if(C == NPCID_FIRE_POWER_S3 || C == NPCID_LEAF_POWER || C == NPCID_ICE_POWER_S3 || C == NPCID_ICE_POWER_S4 || C == NPCID_CYCLONE_POWER || C == NPCID_AQUATIC_POWER)\n                replacement = NPCID_POWER_S3;\n            else if(C == NPCID_FIRE_POWER_S4)\n                replacement = NPCID_POWER_S4;\n            else if(C == NPCID_FIRE_POWER_S1)\n                replacement = NPCID_POWER_S1;\n\n            if(NPCIsYoshi(C))\n            {\n                nn.Type = NPCID_ITEM_POD;\n                nn.Special = C;\n            }\n            else if(replacement != C)\n            {\n                // if any player is small, then replace the NPC\n                bool do_replace = false;\n\n                for(auto B = 1; B <= numPlayers; B++)\n                {\n                    if(Player[B].State == 1 && Player[B].Character != 5)\n                    {\n                        do_replace = true;\n                        break;\n                    }\n                }\n\n                if(!do_replace)\n                    nn.Type = C;\n                else\n                    nn.Type = replacement;\n            }\n            else\n            {\n                nn.Type = C;\n            }\n\n            // replace random power immediately\n            if(nn.Type == NPCID_RANDOM_POWER)\n                nn.Type = RandomBonus();\n\n            CharStuff(numNPCs);\n\n            // note: minor SMBX64 bugfix, should be nn->TWidth in case nn's type has changed\n            nn.Location.Width = (g_config.fix_npc_emerge_size) ? nn->TWidth : NPCWidth(C);\n\n            // bug from ancient 101 case\n            if(is_ancient && C == NPCID_FODDER_S3)\n                nn.Location.Width = NPCWidth(NPCID_POWER_S3);\n\n            // Make block a bit smaller to allow player take a bonus easier (Redigit's idea)\n            if(!is_ancient && num_t::fEqual_d(b.Location.Width, 32.0_n))\n            {\n                // set value directly to make sure Location.Width == 31.9 heuristic works on low-mem builds\n                // b.Location.Width -= 0.1;\n                b.Location.Width = 31.9_n;\n\n                b.Location.X += 0.05_n;\n                b.setShrinkResized();\n            }\n\n            nn.Location.Height = 0;\n            nn.Location.X = (b.Location.X + (b.Location.Width - nn.Location.Width) / 2);\n            nn.Location.SpeedX = 0;\n            nn.Location.SpeedY = 0;\n\n            // direction set to -1 if ancient boot block (104 case)\n            if(is_ancient && C == NPCID_GRN_BOOT)\n                nn.Direction = -1;\n\n            if(NPCIsYoshi(C)) // if the npc is pet then set the color of the pod\n            {\n                if(C == NPCID_PET_BLUE)\n                    nn.Frame = 1;\n                else if(C == NPCID_PET_YELLOW)\n                    nn.Frame = 2;\n                else if(C == NPCID_PET_RED)\n                    nn.Frame = 3;\n                else if(C == NPCID_PET_BLACK)\n                    nn.Frame = 4;\n                else if(C == NPCID_PET_PURPLE)\n                    nn.Frame = 5;\n                else if(C == NPCID_PET_PINK)\n                    nn.Frame = 6;\n            }\n\n            if(!HitDown)\n            {\n                nn.Location.Y = b.Location.Y; // - 0.1\n                nn.Location.Height = 0;\n\n                // old behavior was never changed for ancient 101 / 104 / 201 cases\n                if(is_ancient)\n                    nn.Location.Y = b.Location.Y - 0.1_n;\n\n                if(NPCIsYoshi(C))\n                {\n                    nn.Effect = NPCEFF_NORMAL;\n                    nn.Location.Height = 32;\n                    nn.Location.Y = b.Location.Y - 32;\n                }\n                else if(nn.Type == NPCID_LEAF_POWER || nn.Type == NPCID_CYCLONE_POWER)\n                {\n                    nn.Effect = NPCEFF_NORMAL;\n                    nn.Location.Y = b.Location.Y - 32;\n                    nn.Location.SpeedY = -6;\n                    nn.Location.Height = NPCHeight(C);\n                    // PlaySound(SFX_ItemEmerge); // Don't play mushroom sound on leaf, like in original SMB3 (Redigit's comment)\n                }\n                else\n                {\n                    nn.Effect = NPCEFF_EMERGE_UP;\n                    switch(C)\n                    {\n                    case NPCID_GRN_VINE_TOP_S3:\n                    case NPCID_RED_VINE_TOP_S3:\n                    case NPCID_GRN_VINE_TOP_S4:\n                        PlaySoundSpatial(SFX_SproutVine, b.Location);\n                        break;\n                    default:\n                        PlaySoundSpatial(SFX_ItemEmerge, b.Location);\n                        break;\n                    }\n                }\n            }\n            else\n            {\n                nn.Location.Y = b.Location.Y + 4;\n                nn.Location.Height = NPCHeight(C);\n\n                // hardcoded to 32 in ancient 101 / 104 / 201 cases\n                if(is_ancient)\n                    nn.Location.Height = 32;\n\n                nn.Effect = NPCEFF_EMERGE_DOWN;\n                PlaySoundSpatial(SFX_ItemEmerge, b.Location);\n            }\n\n            nn.Effect2 = 0;\n            syncLayers_NPC(numNPCs);\n            CheckSectionNPC(numNPCs);\n\n            if(is_ancient)\n            {\n                // the logic is the next clause didn't exist in ancient cases\n            }\n            else if(NPCLongLife(nn.Type))\n            {\n                nn.TimeLeft = Physics.NPCTimeOffScreen * 20;\n            }\n        }\n#if 0 // don't spawn players from blocks anymore\n        else // Spawn the player\n        {\n            PlaySound(SFX_ItemEmerge);\n            Player[tempPlayer].State = 1;\n            Player[tempPlayer].Location.Width = Physics.PlayerWidth[Player[tempPlayer].Character][Player[tempPlayer].State];\n            Player[tempPlayer].Location.Height = Physics.PlayerHeight[Player[tempPlayer].Character][Player[tempPlayer].State];\n            Player[tempPlayer].Frame = 1;\n            Player[tempPlayer].Dead = false;\n            Player[tempPlayer].Location.X = b.Location.X + (b.Location.Width - Player[tempPlayer].Location.Width) / 2;\n            if(!HitDown)\n            {\n                Player[tempPlayer].Location.Y = b.Location.Y - 0.1 - Player[tempPlayer].Location.Height;\n            }\n            else\n            {\n                Player[tempPlayer].Location.Y = b.Location.Y + 0.1 + b.Location.Height;\n            }\n            Player[tempPlayer].Location.SpeedX = 0;\n            Player[tempPlayer].Location.SpeedY = 0;\n            Player[tempPlayer].Immune = 150;\n        }\n#endif\n\n    }\n\n    // There was a long passage of dead code that handled the (b.Special >= 100 && b.Special < 1000) cases here\n    // No longer needed -- most of these numbers are replaced with normal NPC ID at OpenLevel side in SMBX 1.3, and the others have been added to the above code.\n\n    if(PSwitchTime > 0 && newBlock == 89 && b.Special == 0 && oldSpecial > 0)\n    {\n        numNPCs++;\n        auto &nn = NPC[numNPCs];\n        nn.Active = true;\n        nn.TimeLeft = 1;\n        nn.Type = NPCID_COIN_S4;\n        // nn.coinSwitchBlockType = 89;\n        // this value has been moved to Damage for coins\n        nn.Damage = 89;\n        nn.Location = b.Location;\n        nn.Location.Width = nn->TWidth;\n        nn.Location.Height = nn->THeight;\n        nn.Location.X += (b.Location.Width - nn.Location.Width) / 2;\n        nn.Location.Y -= 0.01_n;\n        nn.DefaultLocationX = nn.Location.X;\n        nn.DefaultLocationY = nn.Location.Y;\n        nn.DefaultType = nn.Type;\n        syncLayers_NPC(numNPCs);\n        CheckSectionNPC(numNPCs);\n        b = Block_t();\n        // Prevent possible crash on render (#1136)\n        invalidateDrawBlocks();\n\n        // After additional research: consider syncing the erased block to keep logic working as expected.\n        // Right now some normal expectations (quadtree, layer list) may be violated, but the block's position is not immediately synced in SMBX 1.3.\n        // syncLayersTrees_Block(A);\n    }\n\n    if(b.Type == 90)\n        BlockHitHard(A);\n}\n\nenum BlockShakeProgram : uint8_t\n{\n    SHAKE_UPDOWN12_BEG = 16 + 0,\n    SHAKE_UPDOWN12_MID = 16 + 6,\n    SHAKE_UPDOWN12_END = 16 + 12,\n\n    SHAKE_DOWNUP12_BEG = 32 + 0,\n    SHAKE_DOWNUP12_MID = 32 + 6,\n    SHAKE_DOWNUP12_END = 32 + 12,\n\n    SHAKE_UPDOWN06_BEG = 48 + 0,\n    SHAKE_UPDOWN06_MID = 48 + 3,\n    SHAKE_UPDOWN06_END = 48 + 6,\n};\n\nvoid BlockShakeUp(int A)\n{\n    if(Block[A].Hidden)\n        return;\n\n    if(iBlocks >= maxBlocks)\n        return; // iBlock array overflown\n\n    // Block[A].ShakeY = -12; // Go up\n    // Block[A].ShakeY2 = 12; // Come back down\n    // Block[A].ShakeY3 = 0;\n\n    Block[A].ShakeCounter = SHAKE_UPDOWN12_BEG;\n    Block[A].ShakeOffset = 0;\n\n    if(A != iBlock[iBlocks])\n    {\n        iBlocks++;\n        iBlock[iBlocks] = A;\n    }\n}\n\nvoid BlockShakeUpPow(int A)\n{\n    if(Block[A].Hidden)\n        return;\n\n    if(iBlocks >= maxBlocks)\n        return; // iBlock array overflown\n\n    // Block[A].ShakeY = -6; // Go up\n    // Block[A].ShakeY2 = 6; // Come back down\n    // Block[A].ShakeY3 = 0;\n\n    Block[A].ShakeCounter = SHAKE_UPDOWN06_BEG;\n    Block[A].ShakeOffset = 0;\n\n    if(A != iBlock[iBlocks])\n    {\n        iBlocks++;\n        iBlock[iBlocks] = A;\n    }\n}\n\nvoid BlockShakeDown(int A)\n{\n    if(Block[A].Hidden)\n        return;\n\n    if(iBlocks >= maxBlocks)\n        return; // iBlock array overflown\n\n    // Block[A].ShakeY = 12; // Go down\n    // Block[A].ShakeY2 = -12; // Come back up\n    // Block[A].ShakeY3 = 0;\n\n    Block[A].ShakeCounter = SHAKE_DOWNUP12_BEG;\n    Block[A].ShakeOffset = 0;\n\n    if(A != iBlock[iBlocks])\n    {\n        iBlocks++;\n        iBlock[iBlocks] = A;\n    }\n}\n\nvoid BlockHitHard(int A)\n{\n    if(Block[A].Hidden)\n        return;\n\n    if(Block[A].Type == 90 && !Block[A].forceSmashable)\n    {\n        // Block(A).Hidden = True\n        // NewEffect 82, Block(A).Location, , A\n        // PlaySound 3\n    }\n    else\n    {\n        Block[A].Kill = true;\n\n        if(iBlocks < maxBlocks)\n        {\n            iBlocks++;\n            iBlock[iBlocks] = A;\n        }\n    }\n}\n\nvoid SafelyKillBlock(int A)\n{\n    auto& block = Block[A];\n\n    if(block.Hidden)\n        return;\n\n    // emulate old immediate kill behavior if it is safe to do so\n    if(g_config.playstyle == Config_t::MODE_VANILLA && block.TriggerDeath == EVENT_NONE && block.TriggerLast == EVENT_NONE)\n    {\n        KillBlock(A, true);\n        return;\n    }\n\n    block.Kill = 9;\n    if(iBlocks < maxBlocks)\n    {\n        iBlocks++;\n        iBlock[iBlocks] = A;\n    }\n}\n\nbool KillBlock(int A, bool Splode)\n{\n    Block_t blankBlock;\n    bool tempBool = false;\n\n    Block_t& b = Block[A];\n\n    switch(g_gameLoopInterrupt.site)\n    {\n    case GameLoopInterrupt::UpdateBlocks_KillBlock:\n    case GameLoopInterrupt::UpdateBlocks_SwitchOff_KillBlock:\n        if(g_gameLoopInterrupt.bool1)\n            goto resume_TriggerLast;\n        else\n            goto resume_TriggerDeath;\n    default:\n        break;\n    }\n\n    if(Block[A].Hidden)\n        return false;\n\n    if(BattleMode && Block[A].RespawnDelay_ScreensLeft == 0)\n        Block[A].RespawnDelay_ScreensLeft = 1;\n\n    if(Splode)\n    {\n        if(Block[A].Type == 526)\n            PlaySoundSpatial(SFX_SMBlockHit, b.Location);\n        else if(Block[A].Type == 186)\n            PlaySoundSpatial(SFX_Fireworks, b.Location);\n        else\n            PlaySoundSpatial(SFX_BlockSmashed, b.Location); // Block smashed\n\n        // Create the break effect\n        if(Block[A].Type == 60)\n            NewEffect(EFFID_BLU_BLOCK_SMASH, Block[A].Location);\n        else if(Block[A].Type == 188)\n            NewEffect(EFFID_BLOCK_S1_SMASH, Block[A].Location);\n        else if(Block[A].Type == 457)\n            NewEffect(EFFID_GRY_BLOCK_SMASH, Block[A].Location);\n        else if(Block[A].Type == 526)\n            NewEffect(EFFID_SPACE_BLOCK_SMASH, Block[A].Location);\n        else if(Block[A].Type == 293)\n            NewEffect(EFFID_DIRT_BLOCK_SMASH, Block[A].Location);\n        else\n            NewEffect(EFFID_BLOCK_SMASH, Block[A].Location);\n    }\n\n    if(LevelEditor)\n    {\n        if(numBlock > 0)\n        {\n            Block[A] = Block[numBlock];\n            Block[numBlock] = blankBlock;\n            numBlock--;\n            syncLayersTrees_Block(A);\n            syncLayersTrees_Block(numBlock + 1);\n        }\n\n        return false;\n    }\n\n    Score += 50;\n\n    if(Block[A].TriggerDeath != EVENT_NONE)\n    {\n        eventindex_t resume_index;\n        resume_index = ProcEvent_Safe(false, Block[A].TriggerDeath, 0);\n        while(resume_index != EVENT_NONE)\n        {\n            g_gameLoopInterrupt.C = resume_index;\n            g_gameLoopInterrupt.bool1 = false; // marks as TriggerDeath\n            return true;\n\nresume_TriggerDeath:\n            resume_index = g_gameLoopInterrupt.C;\n            g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n            resume_index = ProcEvent_Safe(true, resume_index, 0);\n        }\n    }\n\n    if(Block[A].TriggerLast != EVENT_NONE)\n    {\n        tempBool = false;\n\n        if(Block[A].Layer != LAYER_NONE)\n        {\n            int C = Block[A].Layer;\n\n            for(int npc : Layer[C].NPCs)\n            {\n                if(!NPC[npc].Generator)\n                {\n                    tempBool = true;\n                    break;\n                }\n            }\n\n            if(!tempBool)\n            {\n                for(int other_block : Layer[C].blocks)\n                {\n                    if(other_block != A)\n                    {\n                        tempBool = true;\n                        break;\n                    }\n                }\n            }\n        }\n\n        if(!tempBool)\n        {\n            eventindex_t resume_index;\n            resume_index = ProcEvent_Safe(false, Block[A].TriggerLast, 0);\n            while(resume_index != EVENT_NONE)\n            {\n                g_gameLoopInterrupt.C = resume_index;\n                g_gameLoopInterrupt.bool1 = true; // marks as TriggerLast\n                return true;\n\nresume_TriggerLast:\n                resume_index = g_gameLoopInterrupt.C;\n                g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n                resume_index = ProcEvent_Safe(true, resume_index, 0);\n            }\n        }\n    }\n\n    Block[A].Hidden = true;\n    Block[A].Layer = LAYER_DESTROYED_BLOCKS;\n    Block[A].Kill = false;\n    syncLayersTrees_Block(A);\n\n    return false;\n}\n\nvoid BlockFrames()\n{\n    int A = 0;\n\n    bool pChar[maxPlayers] = {false};\n    bool tempBool = false;\n    SDL_memset(pChar, 0, sizeof(pChar));\n\n    if(FreezeNPCs)\n        return;\n\n    // Update block frame counter\n    BlockFrame2[4] += 1;\n    if(BlockFrame2[4] == 8)\n        BlockFrame2[4] = 0;\n\n    BlockFrame2[5] += 1;\n    if(BlockFrame2[5] == 8)\n        BlockFrame2[5] = 0;\n\n    BlockFrame2[30] += 1;\n    if(BlockFrame2[30] == 8)\n        BlockFrame2[30] = 0;\n\n    BlockFrame2[55] += 1;\n    if(BlockFrame2[55] == 8)\n        BlockFrame2[55] = 0;\n\n    BlockFrame2[88] += 1;\n    if(BlockFrame2[88] == 8)\n        BlockFrame2[88] = 0;\n\n    BlockFrame2[109] += 1;\n    if(BlockFrame2[109] == 4)\n        BlockFrame2[109] = 0;\n\n    BlockFrame2[371] += 1;\n    if(BlockFrame2[371] == 8)\n        BlockFrame2[371] = 0;\n\n    BlockFrame2[379] += 1;\n    if(BlockFrame2[379] >= 12)\n        BlockFrame2[379] = 0;\n\n    // Check if the block type is ready for the next frame\n    if(BlockFrame2[4] == 0)\n    {\n        BlockFrame[4] += 1;\n        if(BlockFrame[4] == 4)\n            BlockFrame[4] = 0;\n    }\n\n    if(BlockFrame2[5] == 0)\n    {\n        BlockFrame[5] += 1;\n        if(BlockFrame[5] == 4)\n            BlockFrame[5] = 0;\n    }\n\n    BlockFrame[598] = BlockFrame[5];\n    BlockFrame[511] = BlockFrame[5];\n    BlockFrame[169] = BlockFrame[5];\n    BlockFrame[173] = BlockFrame[5];\n    BlockFrame[176] = BlockFrame[5];\n    BlockFrame[179] = BlockFrame[5];\n    BlockFrame[193] = BlockFrame[5];\n    BlockFrame[389] = BlockFrame[5];\n    BlockFrame[391] = BlockFrame[5];\n    BlockFrame[392] = BlockFrame[5];\n    BlockFrame[404] = BlockFrame[5];\n    BlockFrame[459] = BlockFrame[5];\n    BlockFrame[460] = BlockFrame[5];\n    BlockFrame[461] = BlockFrame[5];\n    BlockFrame[462] = BlockFrame[5];\n    BlockFrame[463] = BlockFrame[5];\n    BlockFrame[464] = BlockFrame[5];\n    BlockFrame[465] = BlockFrame[5];\n    BlockFrame[466] = BlockFrame[5];\n    BlockFrame[468] = BlockFrame[5];\n    BlockFrame[469] = BlockFrame[5];\n    BlockFrame[470] = BlockFrame[5];\n    BlockFrame[471] = BlockFrame[5];\n    BlockFrame[472] = BlockFrame[5];\n    BlockFrame[473] = BlockFrame[5];\n    BlockFrame[474] = BlockFrame[5];\n    BlockFrame[475] = BlockFrame[5];\n    BlockFrame[476] = BlockFrame[5];\n    BlockFrame[477] = BlockFrame[5];\n    BlockFrame[478] = BlockFrame[5];\n    BlockFrame[479] = BlockFrame[5];\n    BlockFrame[480] = BlockFrame[5];\n    BlockFrame[481] = BlockFrame[5];\n    BlockFrame[482] = BlockFrame[5];\n    BlockFrame[483] = BlockFrame[5];\n    BlockFrame[484] = BlockFrame[5];\n    BlockFrame[485] = BlockFrame[5];\n    BlockFrame[486] = BlockFrame[5];\n    BlockFrame[487] = BlockFrame[5];\n    BlockFrame[622] = BlockFrame[5];\n    BlockFrame[623] = BlockFrame[5];\n    BlockFrame[624] = BlockFrame[5];\n    BlockFrame[625] = BlockFrame[5];\n    BlockFrame[631] = BlockFrame[5];\n\n    for(A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Character == 1)\n            BlockFrame[622] = 4;\n\n        if(Player[A].Character == 2)\n            BlockFrame[623] = 4;\n\n        if(Player[A].Character == 3)\n            BlockFrame[624] = 4;\n\n        if(Player[A].Character == 4)\n            BlockFrame[625] = 4;\n\n        if(Player[A].Character == 5)\n            BlockFrame[631] = 4;\n    }\n\n    BlockFrame2[626] += 1;\n    if(BlockFrame2[626] < 8)\n        BlockFrame[626] = 3;\n    else if(BlockFrame2[626] < 16)\n        BlockFrame[626] = 2;\n    else if(BlockFrame2[626] < 23)\n        BlockFrame[626] = 1;\n    else\n    {\n        BlockFrame2[626] = 0;\n        BlockFrame[626] = 1;\n    }\n\n    BlockFrame[627] = BlockFrame[626];\n    BlockFrame[628] = BlockFrame[626];\n    BlockFrame[629] = BlockFrame[626];\n    BlockFrame[632] = BlockFrame[626];\n\n    for(A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Character <= 5)\n            pChar[Player[A].Character] = true;\n    }\n\n    if(!pChar[1])\n        BlockFrame[626] = 0;\n\n    if(!pChar[2])\n        BlockFrame[627] = 0;\n\n    if(!pChar[3])\n        BlockFrame[628] = 0;\n\n    if(!pChar[4])\n        BlockFrame[629] = 0;\n\n    if(!pChar[5])\n        BlockFrame[632] = 0;\n\n    if(BlockFrame2[30] == 0)\n    {\n        BlockFrame[30] += 1;\n        if(BlockFrame[30] == 4)\n            BlockFrame[30] = 0;\n    }\n\n    if(BlockFrame2[55] == 0)\n    {\n        BlockFrame[55] += 1;\n        if(BlockFrame[55] == 4)\n            BlockFrame[55] = 0;\n    }\n\n    if(BlockFrame2[88] == 0)\n    {\n        BlockFrame[88] += 1;\n        if(BlockFrame[88] == 4)\n            BlockFrame[88] = 0;\n    }\n\n    BlockFrame[170] = BlockFrame[88];\n\n    if(BlockFrame2[109] == 0)\n    {\n        BlockFrame[109] += 1;\n        if(BlockFrame[109] == 8)\n            BlockFrame[109] = 0;\n    }\n\n    if(BlockFrame2[371] == 0)\n    {\n        BlockFrame[371] += 1;\n        if(BlockFrame[371] == 8)\n            BlockFrame[371] = 0;\n    }\n\n    if(BlockFrame2[379] == 0)\n    {\n        BlockFrame[379] += 1;\n        if(BlockFrame[379] == 4)\n            BlockFrame[379] = 0;\n    }\n\n    BlockFrame[380] = BlockFrame[379];\n    BlockFrame[381] = BlockFrame[379];\n    BlockFrame[382] = BlockFrame[379];\n\n    BlockFrame2[530] += 1;\n\n    if(BlockFrame2[530] <= 8)\n        BlockFrame[530] = 0;\n    else if(BlockFrame2[530] <= 16)\n        BlockFrame[530] = 1;\n    else if(BlockFrame2[530] <= 24)\n        BlockFrame[530] = 2;\n    else if(BlockFrame2[530] <= 32)\n        BlockFrame[530] = 3;\n    else if(BlockFrame2[530] <= 40)\n        BlockFrame[530] = 2;\n    else if(BlockFrame2[530] <= 48)\n        BlockFrame[530] = 1;\n    else\n    {\n        BlockFrame[530] = 0;\n        BlockFrame2[530] = 0;\n    }\n\n    if(LevelEditor && !TestLevel)\n        BlockFrame[458] = 5;\n    else\n    {\n        tempBool = false;\n        for(A = 1; A <= numPlayers; A++)\n        {\n            if(Player[A].Stoned)\n            {\n                tempBool = true;\n                break;\n            }\n        }\n\n        if(BlockFrame[458] < 5 && tempBool)\n        {\n            BlockFrame2[458] += 1;\n            if(BlockFrame2[458] >= 4)\n            {\n                BlockFrame2[458] = 0;\n                BlockFrame[458] += 1;\n            }\n        }\n        else if(BlockFrame[458] > 0 && !tempBool)\n        {\n            BlockFrame2[458] += 1;\n            if(BlockFrame2[458] >= 4)\n            {\n                BlockFrame2[458] = 0;\n                BlockFrame[458] -= 1;\n            }\n        }\n        else\n            BlockFrame2[458] = 0;\n    }\n}\n\nbool PSwitch(bool enabled);\n\nbool UpdateBlocks()\n{\n    int B = 0;\n    if(FreezeNPCs)\n        return false;\n\n    switch(g_gameLoopInterrupt.site)\n    {\n    case GameLoopInterrupt::UpdateBlocks_KillBlock:\n    case GameLoopInterrupt::UpdateBlocks_TriggerHit:\n        goto resume_iBlocks;\n    case GameLoopInterrupt::UpdateBlocks_SwitchOn:\n        goto resume_SwitchOn;\n    case GameLoopInterrupt::UpdateBlocks_SwitchOff:\n    case GameLoopInterrupt::UpdateBlocks_SwitchOff_KillBlock:\n        goto resume_SwitchOff;\n    default:\n        break;\n    }\n\n    BlockFrames();\n\n    if(BattleMode)\n    {\n        for(int A = 1; A <= numBlock; A++)\n        {\n            auto &b = Block[A];\n            // respawn\n            if(b.RespawnDelay_ScreensLeft > 0)\n            {\n                b.RespawnDelay_ScreensLeft += 1;\n                if(b.RespawnDelay_ScreensLeft >= 65 * 60)\n                {\n                    if(b.DefaultType > 0 || b.DefaultSpecial > 0 || b.Layer == LAYER_DESTROYED_BLOCKS)\n                    {\n                        for(B = 1; B <= numPlayers; B++)\n                        {\n                            if(CheckCollision(b.Location, newLoc(Player[B].Location.X - 64, Player[B].Location.Y - 64, 128, 128)))\n                            {\n                                B = 0;\n                                break;\n                            }\n                        }\n\n                        if(B > 0)\n                        {\n                            if(b.Layer == LAYER_DESTROYED_BLOCKS)\n                                b.Layer = LAYER_DEFAULT;\n\n                            if(b.Hidden)\n                            {\n                                syncLayersTrees_Block_SetHidden(A);\n\n                                if(!b.Hidden) // -V547 // False positive: the b.Hidden gets changed inside syncLayersTrees_Block_SetHidden() call\n                                    NewEffect(EFFID_SMOKE_S3_CENTER, b.Location);\n                            }\n                            else\n                            {\n                                syncLayersTrees_Block(A);\n                            }\n\n                            if(b.Type != b.DefaultType || b.Special != b.DefaultSpecial)\n                            {\n                                if(b.Type != b.DefaultType)\n                                    NewEffect(EFFID_SMOKE_S3_CENTER, b.Location);\n                                b.Special = b.DefaultSpecial;\n                                b.Type = b.DefaultType;\n                            }\n\n                            b.RespawnDelay_ScreensLeft = 0;\n                        }\n                        else\n                            b.RespawnDelay_ScreensLeft = 65 * 30;\n                    }\n                    else\n                        b.RespawnDelay_ScreensLeft = 0;\n                }\n            }\n        }\n    }\n\n    int A;\n    for(A = 1; A <= iBlocks; A++)\n    {\n        bool is_resume;\n        if(false)\n        {\nresume_iBlocks:\n            A = g_gameLoopInterrupt.A;\n            is_resume = true;\n        }\n        else\n            is_resume = false;\n\n        auto &ib = Block[iBlock[A]];\n\n        if(is_resume)\n        {\n            switch(g_gameLoopInterrupt.site)\n            {\n            case GameLoopInterrupt::UpdateBlocks_KillBlock:\n                goto resume_KillBlock;\n            case GameLoopInterrupt::UpdateBlocks_TriggerHit:\n                goto resume_TriggerHit;\n            default:\n                break;\n            }\n        }\n\n        // Update the shake effect\n        if(ib.Hidden)\n        {\n            // ib.ShakeY = 0;\n            // ib.ShakeY2 = 0;\n            // ib.ShakeY3 = 0;\n\n            ib.ShakeCounter = 0;\n            ib.ShakeOffset = 0;\n        }\n\n        // check modBlocks.bas for the old shake logic\n#if 0\n        if(ib.ShakeY < 0) // Block Shake Up\n        else if(ib.ShakeY > 0) // Block Shake Down\n        else if(ib.ShakeY2 > 0) // Come back down\n        else if(ib.ShakeY2 < 0) // Go back up\n#endif\n\n        if(ib.ShakeCounter != 0)\n        {\n            if(    (ib.ShakeCounter >= SHAKE_UPDOWN06_BEG && ib.ShakeCounter < SHAKE_UPDOWN06_MID)\n                || (ib.ShakeCounter >= SHAKE_UPDOWN12_BEG && ib.ShakeCounter < SHAKE_UPDOWN12_MID)\n                || (ib.ShakeCounter >= SHAKE_DOWNUP12_MID && ib.ShakeCounter < SHAKE_DOWNUP12_END))\n            {\n                ib.ShakeOffset -= 2;\n            }\n            else\n                ib.ShakeOffset += 2;\n\n            ib.ShakeCounter++;\n\n            // do hit events at the middle of the shake program\n            if(    ib.ShakeCounter == SHAKE_UPDOWN06_MID\n                || ib.ShakeCounter == SHAKE_UPDOWN12_MID\n                || ib.ShakeCounter == SHAKE_DOWNUP12_MID)\n            {\n                if(ib.TriggerHit != EVENT_NONE)\n                {\n                    eventindex_t resume_index;\n                    resume_index = ProcEvent_Safe(false, ib.TriggerHit, 0);\n                    while(resume_index != EVENT_NONE)\n                    {\n                        g_gameLoopInterrupt.C = resume_index;\n                        g_gameLoopInterrupt.A = A;\n                        g_gameLoopInterrupt.site = GameLoopInterrupt::UpdateBlocks_TriggerHit;\n                        return true;\n\nresume_TriggerHit:\n                        resume_index = g_gameLoopInterrupt.C;\n                        g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n                        resume_index = ProcEvent_Safe(true, resume_index, 0);\n                    }\n                }\n\n                // on/off block\n                if(ib.Type == 282)\n                    ib.Type = 283;\n                else if(ib.Type == 283)\n                    ib.Type = 282;\n\n                // spin block\n                if(ib.Type == 90 && (ib.ShakeCounter == SHAKE_DOWNUP12_MID || ib.Special == 0) && !ib.forceSmashable)\n                {\n                    ib.Hidden = true;\n                    invalidateDrawBlocks();\n                    if(NewEffect(EFFID_SPINBLOCK, ib.Location, 1))\n                        Effect[numEffects].NewNpc = iBlock[A];\n                    ib.ShakeCounter = 0;\n                    ib.ShakeOffset = 0;\n                }\n            }\n\n            // finish the shake program\n            if(    ib.ShakeCounter == SHAKE_UPDOWN06_END\n                || ib.ShakeCounter == SHAKE_UPDOWN12_END\n                || ib.ShakeCounter == SHAKE_DOWNUP12_END)\n            {\n                SDL_assert(ib.ShakeOffset == 0);\n\n                if(ib.RapidHit > 0 && ib.Special > 0 && ib.ShakeCounter != SHAKE_DOWNUP12_END)\n                {\n                    ib.ShakeCounter = 0;\n\n                    BlockHit(iBlock[A]);\n                    ib.RapidHit -= 1;\n                }\n                else\n                    ib.ShakeCounter = 0;\n            }\n        }\n\n        if(ib.ShakeOffset != 0)\n        {\n            Location_t query_loc = ib.Location;\n            query_loc.Y += ib.ShakeOffset;\n\n            for(int B : treeNPCQuery(query_loc, SORTMODE_ID))\n            {\n                if(NPC[B].Active && NPC[B].Killed == 0 && NPC[B].Effect == NPCEFF_NORMAL && NPC[B].HoldingPlayer == 0 && (!NPC[B]->NoClipping || NPC[B]->IsACoin))\n                {\n                    if(ib.ShakeOffset <= 0 || NPC[B]->IsACoin)\n                    {\n                        if(ShakeCollision(NPC[B].Location, ib))\n                        {\n                            if(iBlock[A] != NPC[B].tempBlock && ib.tempBlockNpcIdx != B)\n                            {\n                                if(!BlockIsSizable[ib.Type] && !BlockOnlyHitspot1[ib.Type])\n                                    NPCHit(B, 2, iBlock[A]);\n                                else if(ib.Location.Y + 1 >= NPC[B].Location.Y + NPC[B].Location.Height - 1)\n                                    NPCHit(B, 2, iBlock[A]);\n                            }\n                        }\n                    }\n                }\n            }\n\n            for(auto B = 1; B <= numPlayers; B++)\n            {\n                if(!Player[B].Dead)\n                {\n                    if(Player[B].Effect == PLREFF_NORMAL && ib.Type != 55)\n                    {\n                        if(ShakeCollision(Player[B].Location, ib))\n                        {\n                            if((!BlockIsSizable[ib.Type] && !BlockOnlyHitspot1[ib.Type]) || (ib.Location.Y + 1 >= Player[B].Location.Y + Player[B].Location.Height - 1))\n                            {\n                                Player[B].Location.SpeedY = Physics.PlayerJumpVelocity;\n                                Player[B].StandUp = true;\n                                PlaySoundSpatial(SFX_Stomp, Player[B].Location);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        if(ib.Kill) // See if block should be broke\n        {\n            bool just_kill_it, is_breakable;\n            {\n                just_kill_it = (ib.Kill == 9);\n                is_breakable = BlockIsBreakable(ib);\n            }\n\n            ib.Kill = false;\n\n            if(is_breakable || just_kill_it)\n            {\n                if(false)\n                {\nresume_KillBlock:\n                    (void)nullptr;\n                }\n\n                if(KillBlock(iBlock[A])) // Destroy the block\n                {\n                    g_gameLoopInterrupt.A = A;\n                    g_gameLoopInterrupt.site = GameLoopInterrupt::UpdateBlocks_KillBlock;\n                    return true;\n                }\n            }\n        }\n    }\n\n    for(auto A = iBlocks; A >= 1; A--)\n    {\n        auto &ib = Block[iBlock[A]];\n\n        // if(ib.ShakeY1 == 0 && ib.ShakeY2 == 0 && ib.ShakeY3 == 0)\n        if(ib.ShakeCounter == 0)\n        {\n            iBlock[A] = iBlock[iBlocks];\n            iBlocks -= 1;\n        }\n    }\n\n    if(PSwitchTime > 0)\n    {\n        if(PSwitchTime == Physics.NPCPSwitch)\n        {\n            StopMusic();\n            StartMusic(-1);\n\n            if(PSwitchPlayer >= 1 && PSwitchPlayer <= numPlayers)\n                PlaySoundSpatial(SFX_PSwitch, Player[PSwitchPlayer].Location);\n            else\n                PlaySound(SFX_PSwitch);\n\nresume_SwitchOn:\n            if(PSwitch(true))\n                return true;\n        }\n\n        PSwitchTime--;\n\n        if(PSwitchTime == 195)\n            PlaySound(SFX_CoinSwitchTimeout);\n\n        if(PSwitchTime <= 1)\n        {\nresume_SwitchOff:\n            if(PSwitch(false))\n                return true;\n\n            SwitchEndResumeMusic();\n        }\n    }\n\n    return false;\n}\n\nvoid SwitchEndResumeMusic()\n{\n    StopMusic();\n\n    if(InvincibilityTime)\n    {\n        StartMusic(-1);\n        return;\n    }\n\n    int switch_player_section = Player[PSwitchPlayer].Section;\n\n#ifndef THEXTECH_ENABLE_SDL_NET\n    // originally, just used section of the player that triggered the switch\n    StartMusic(switch_player_section);\n#else\n    // now, prefer that section if it's onscreen, but fallback to section of first onscreen player\n    for(int i = l_screen->player_count - 1; i >= 0; i--)\n    {\n        int pi_section = Player[l_screen->players[i]].Section;\n        if(i == 0 || pi_section == switch_player_section)\n        {\n            StartMusic(pi_section);\n            return;\n        }\n    }\n#endif\n}\n\nbool PSwitch(bool enabled)\n{\n    int A = 0;\n    // int B = 0;\n    Block_t blankBlock;\n    eventindex_t resume_index = EVENT_NONE;\n\n    switch(g_gameLoopInterrupt.site)\n    {\n    case GameLoopInterrupt::UpdateBlocks_SwitchOn:\n    case GameLoopInterrupt::UpdateBlocks_SwitchOff:\n        goto resume_ProcEvent;\n    case GameLoopInterrupt::UpdateBlocks_SwitchOff_KillBlock:\n        goto resume_KillBlock;\n    default:\n        break;\n    }\n\n    if(enabled)\n    {\n        for(A = 1; A <= numNPCs; A++)\n        {\n            bool transform = NPC[A]->IsACoin && NPC[A].coinSwitchBlockType() == 0 && !NPC[A].Hidden && NPC[A].Special == 0 && !NPC[A].Wings;\n\n            if(NPC[A].Type == NPCID_MEDAL && g_config.fix_special_coin_switch)\n                transform = false;\n\n            if(transform)\n            {\n                if(numBlock < maxBlocks)\n                {\n                    numBlock++;\n                    auto &nb = Block[numBlock];\n\n                    if((NPC[A].Type == NPCID_GEM_1 || NPC[A].Type == NPCID_GEM_5 || NPC[A].Type == NPCID_GEM_20) && NPC[A].DefaultType != 0)\n                        NPC[A].Type = NPC[A].DefaultType;\n\n                    if(NPC[A].Type == NPCID_COIN_S4 || NPC[A].Type == NPCID_COIN_5)\n                        nb.Type = 89;\n                    else if(NPC[A].Type == NPCID_COIN_S1)\n                        nb.Type = 188;\n                    else if(NPC[A].Type == NPCID_RED_COIN)\n                        nb.Type = 280;\n                    else if(NPC[A].Type == NPCID_COIN_S2)\n                        nb.Type = 293;\n                    else\n                        nb.Type = 4;\n\n                    nb.TriggerDeath = NPC[A].TriggerDeath;\n                    nb.TriggerLast = NPC[A].TriggerLast;\n                    nb.Layer = NPC[A].Layer;\n                    nb.Invis = false;\n                    nb.Hidden = false;\n                    nb.Location = NPC[A].Location;\n                    nb.Location.Width = BlockWidth[nb.Type];\n                    nb.Location.Height = BlockHeight[nb.Type];\n                    nb.Location.X += (NPC[A].Location.Width - nb.Location.Width) / 2;\n                    nb.Location.SpeedX = 0;\n                    nb.Location.SpeedY = 0;\n                    nb.Special = -NPC[A].Variant; // NEW: store Variant to preserve medals tracking\n                    nb.Kill = false;\n                    nb.coinSwitchNpcType = NPC[A].Type;\n\n                    if(g_config.fix_switched_block_clipping)\n                    {\n                        nb.tempBlockNpcIdx = 0;\n                        nb.tempBlockNpcType = NPCID_NULL;\n                        nb.tempBlockVehiclePlr = 0;\n                        nb.tempBlockVehicleYOffset = 0;\n                    }\n\n                    syncLayersTrees_Block(numBlock);\n                }\n\n                NPC[A].Killed = 9;\n                NPCQueues::Killed.push_back(A);\n            }\n        }\n\n        // From release v1.3.6.1 to release v1.3.7, this section had a much more complex routine\n        // because I was trying to emulate sorting here without the blocks actually being sorted.\n        // However, the blocks ARE sorted in compat mode since v1.3.6.1, so the code was actually\n        // overcomplicated. -- ds-sloth\n\n        // make the NPCs and kill the blocks\n        for(A = numBlock; A >= 1; A--)\n        {\n            if(BlockPSwitch[Block[A].Type] && Block[A].Special == 0 && Block[A].coinSwitchNpcType == 0 && !Block[A].Hidden)\n            {\n                if(numNPCs < maxNPCs)\n                {\n                    numNPCs++;\n                    auto &nn = NPC[numNPCs];\n                    nn.Active = true;\n                    nn.TimeLeft = 1;\n\n                    if(Block[A].Type == 89)\n                        nn.Type = NPCID_COIN_S4;\n                    else if(Block[A].Type == 188 || Block[A].Type == 60)\n                        nn.Type = NPCID_COIN_S1;\n                    else if(Block[A].Type == 280)\n                        nn.Type = NPCID_RED_COIN;\n                    else if(Block[A].Type == 293)\n                        nn.Type = NPCID_COIN_S2;\n                    else\n                        nn.Type = NPCID_COIN_S3;\n\n                    nn.Layer = Block[A].Layer;\n                    nn.TriggerDeath = Block[A].TriggerDeath;\n                    nn.TriggerLast = Block[A].TriggerLast;\n                    // nn.coinSwitchBlockType = Block[A].Type;\n                    // this value has been moved to Damage for coins\n                    nn.Damage = Block[A].Type;\n                    nn.Hidden = false;\n                    nn.Location = Block[A].Location;\n                    nn.Location.SpeedX = 0;\n                    nn.Location.SpeedY = 0;\n                    nn.Location.Width = nn->TWidth;\n                    nn.Location.Height = nn->THeight;\n                    nn.Location.X += (Block[A].Location.Width - nn.Location.Width) / 2;\n                    nn.DefaultLocationX = nn.Location.X;\n                    nn.DefaultLocationY = nn.Location.Y;\n                    nn.DefaultType = nn.Type;\n\n                    // WARNING: this is new logic from #167. Check in case of any inconsistencies after Coin Switch is activated.\n                    if(nn->TFrames > 0)\n                    {\n                        nn.Direction = 1;\n                        nn.Frame = EditorNPCFrame(nn.Type, nn.Direction);\n                        nn.DefaultDirection = nn.Direction;\n                    }\n\n                    syncLayers_NPC(numNPCs);\n                    CheckSectionNPC(numNPCs);\n\n                    Block[A] = Block[numBlock];\n                    Block[numBlock] = blankBlock;\n                    numBlock--;\n\n                    // make sure that iBlock isn't forgotten (this is done for all blocks below if the compat flag is set)\n                    if(!g_config.emulate_classic_block_order && iBlocks <= maxBlocks)\n                    {\n                        iBlocks++;\n                        iBlock[iBlocks] = A;\n                    }\n\n                    syncLayersTrees_Block(A);\n                    syncLayersTrees_Block(numBlock + 1);\n                }\n                else\n                    break;\n            }\n        }\n\n        resume_index = ProcEvent_Safe(false, EVENT_PSWITCH_START, 0, EventContext::CoinSwitch);\n    }\n    else\n    {\n        for(A = 1; A <= numNPCs; A++)\n        {\n            if(NPC[A].coinSwitchBlockType() > 0)\n            {\n                if(numBlock < maxBlocks)\n                {\n                    numBlock++;\n                    auto &nb = Block[numBlock];\n                    nb.Layer = NPC[A].Layer;\n                    nb.TriggerDeath = NPC[A].TriggerDeath;\n                    nb.TriggerLast = NPC[A].TriggerLast;\n                    nb.Hidden = NPC[A].Hidden;\n                    nb.Invis = false;\n                    nb.Type = NPC[A].coinSwitchBlockType();\n                    nb.Location = NPC[A].Location;\n                    nb.Location.SpeedX = 0;\n                    nb.Location.SpeedY = 0;\n                    nb.Location.Width = BlockWidth[nb.Type];\n                    nb.Location.Height = BlockHeight[nb.Type];\n                    nb.Location.X += (NPC[A].Location.Width - nb.Location.Width) / 2;\n                    nb.Special = 0;\n                    nb.Kill = false;\n\n                    if(g_config.fix_switched_block_clipping)\n                    {\n                        nb.coinSwitchNpcType = NPCID_NULL;\n                        nb.tempBlockNpcIdx = 0;\n                        nb.tempBlockNpcType = NPCID_NULL;\n                        nb.tempBlockVehiclePlr = 0;\n                        nb.tempBlockVehicleYOffset = 0;\n                    }\n\n                    syncLayersTrees_Block(numBlock);\n                }\n                NPC[A].Killed = 9;\n                NPCQueues::Killed.push_back(A);\n            }\n        }\n\n\n        // See comment in the previous section. There was a much more complicated routine here for some versions. -- ds-sloth\n\n        // restore the NPCs\n        for(A = numBlock; A >= 1; A--)\n        {\n            if(Block[A].coinSwitchNpcType > 0 && !Block[A].Hidden)\n            {\n                if(numNPCs >= maxNPCs)\n                    break;\n\n                // scoping nn\n                {\n                    numNPCs++;\n                    auto &nn = NPC[numNPCs];\n                    nn.Layer = Block[A].Layer;\n                    nn.TriggerDeath = Block[A].TriggerDeath;\n                    nn.TriggerLast = Block[A].TriggerLast;\n                    nn.Active = true;\n                    nn.TimeLeft = 1;\n                    nn.Hidden = Block[A].Hidden;\n                    nn.Type = Block[A].coinSwitchNpcType;\n                    nn.Location = Block[A].Location;\n                    nn.Location.SpeedX = 0;\n                    nn.Location.SpeedY = 0;\n                    nn.Location.Width = nn->TWidth;\n                    nn.Location.Height = nn->THeight;\n                    nn.Location.X += (Block[A].Location.Width - nn.Location.Width) / 2;\n                    nn.DefaultLocationX = nn.Location.X;\n                    nn.DefaultLocationY = nn.Location.Y;\n                    nn.DefaultType = nn.Type;\n                    nn.Variant = -Block[A].Special;\n\n                    // WARNING: this is new logic from #167. Check in case of any inconsistencies after Coin Switch is activated.\n                    if(nn->TFrames > 0)\n                    {\n                        nn.Direction = 1;\n                        nn.Frame = EditorNPCFrame(nn.Type, nn.Direction);\n                        nn.DefaultDirection = nn.Direction;\n                    }\n\n                    syncLayers_NPC(numNPCs);\n                    CheckSectionNPC(numNPCs);\n                    nn.Killed = 0;\n                }\n\n                if(false)\n                {\nresume_KillBlock:\n                    A = g_gameLoopInterrupt.A;\n                }\n\n                if(KillBlock(A, false))\n                {\n                    g_gameLoopInterrupt.A = A;\n                    g_gameLoopInterrupt.site = GameLoopInterrupt::UpdateBlocks_SwitchOff_KillBlock;\n                    return true;\n                }\n\n                Block[A].Layer = LAYER_USED_P_SWITCH;\n                syncLayersTrees_Block(A);\n                // this is as close to a permanent death as blocks get in the game,\n                // because this layer usually doesn't exist\n            }\n        }\n\n        resume_index = ProcEvent_Safe(false, EVENT_PSWITCH_END, 0, EventContext::CoinSwitch);\n    }\n\n    while(resume_index != EVENT_NONE)\n    {\n        g_gameLoopInterrupt.C = resume_index;\n        g_gameLoopInterrupt.site = (enabled) ? GameLoopInterrupt::UpdateBlocks_SwitchOn : GameLoopInterrupt::UpdateBlocks_SwitchOff;\n        return true;\n\nresume_ProcEvent:\n        resume_index = g_gameLoopInterrupt.C;\n        g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n        resume_index = ProcEvent_Safe(true, resume_index, 0, EventContext::CoinSwitch);\n    }\n\n    // qSortBlocksX(1, numBlock);\n    // B = 1;\n\n    // for(A = 2; A <= numBlock; A++)\n    // {\n    //     if(Block[A].Location.X > Block[B].Location.X)\n    //     {\n    //         qSortBlocksY(B, A - 1);\n    //         B = A;\n    //     }\n    // }\n\n    // qSortBlocksY(B, A - 1);\n    // FindSBlocks();\n    // FindBlocks();\n    // SO expensive, can't wait to get rid of this.\n    // syncLayersTrees_AllBlocks();\n\n    if(g_config.emulate_classic_block_order)\n    {\n        // Doing this just to replicate some unusual, unpredictable glitches\n        // that sometimes occur when blocks' relative order is changing during the level\n\n        qSortBlocksX(1, numBlock);\n        int B = 1;\n\n        for(A = 2; A <= numBlock; A++)\n        {\n            if(Block[A].Location.X > Block[B].Location.X)\n            {\n                qSortBlocksY(B, A - 1);\n                B = A;\n            }\n        }\n\n        qSortBlocksY(B, A - 1);\n\n        syncLayersTrees_AllBlocks();\n\n        BlocksSorted = true;\n\n        // make sure that iBlock references weren't broken if blocks got sorted\n        iBlocks = numBlock;\n        for(A = 1; A <= numBlock; A++)\n            iBlock[A] = A;\n    }\n\n    resetFrameTimer();\n\n    return false;\n}\n\nvoid PowBlock()\n{\n    // For NetPlay: make this only affect screens where the earthquake block NPC is near at least one vScreen\n\n    int numScreens = 0;\n    int Z = 0;\n\n    PlaySound(SFX_Stone);\n\n    // Shake blocks. This isn't just cosmetic, it damages NPCs, so we should check the canonical screens here.\n\n    for(int screen_i = 0; screen_i < c_screenCount; screen_i++)\n    {\n        Screen_t& screen = Screens[screen_i];\n\n        if(!screen.is_active())\n            continue;\n\n        // use modern screen iteration bounds\n        if(g_config.allow_multires)\n        {\n            Z = screen.active_begin() + 1;\n            numScreens = screen.active_end();\n        }\n        // buggy original code, doesn't correctly handle SingleCoop\n        else\n        {\n            Z = 1;\n            numScreens = 1;\n\n            if(!LevelEditor)\n            {\n                if(screen.Type == 1 || screen.Type == 4)\n                    numScreens = 2;\n\n                if(screen.Type == 5)\n                {\n                    DynamicScreen(screen);\n                    if(screen.vScreen(2).Visible)\n                        numScreens = 2;\n                }\n            }\n        }\n\n        for(; Z <= numScreens; Z++)\n        {\n            uint8_t vscreen_Z = screen.vScreen_refs[Z - 1];\n            const vScreen_t& vscreen = vScreen[vscreen_Z];\n            const Location_t query_loc = newLoc(-vscreen.X, -vscreen.Y, vscreen.Width, vscreen.Height);\n\n            for(int A : treeBlockQuery(query_loc, SORTMODE_COMPAT))\n            {\n                if(vScreenCollision(vscreen_Z, Block[A].Location))\n                {\n                    if(!Block[A].Hidden)\n                        BlockShakeUpPow(A);\n                }\n            }\n\n            // force-activate coins on the canonical screens\n            if(g_config.allow_multires && screen.is_canonical())\n            {\n                for(int A : treeNPCQuery(query_loc, SORTMODE_NONE))\n                {\n                    if(!NPC[A].Active && NPC[A]->IsACoin)\n                    {\n                        NPC[A].JustActivated = vscreen_Z;\n\n                        NPCQueues::Active.insert(A);\n                        NPC[A].Active = true;\n\n                        NPC[A].TimeLeft = Physics.NPCTimeOffScreen;\n                    }\n                }\n            }\n        }\n\n        if(!g_config.allow_multires)\n            break;\n    }\n\n    for(int A : NPCQueues::Active.no_change)\n    {\n        if(NPC[A].Active)\n        {\n            if(NPC[A]->IsACoin)\n            {\n                NPC[A].Wings = WING_NONE;\n                NPC[A].Special = 1;\n                NPC[A].Location.SpeedX = dRand() - 0.5_n;\n            }\n        }\n    }\n\n    doShakeScreen(20, SHAKE_RANDOM);\n}\n\nbool BlockCheckPlayerFilter(int blockIdx, int playerIdx)\n{\n    auto block = Block[blockIdx].Type;\n    auto player = Player[playerIdx].Character;\n\n    switch(block)\n    {\n    case 626:\n        return (player == 1);\n    case 627:\n        return (player == 2);\n    case 628:\n        return (player == 3);\n    case 629:\n        return (player == 4);\n    case 632:\n        return (player == 5);\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/blocks.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef BLOCKS_H\n#define BLOCKS_H\n\n#include \"globals.h\"\n\ninline bool BlockTypeBreakable(int Type)\n{\n    return (Type == 4 || Type == 60 ||\n       Type == 90 || Type == 188 ||\n       Type == 226 || Type == 293 ||\n       Type == 526);\n}\n\ninline bool BlockIsBreakable(const Block_t& block)\n{\n    return BlockTypeBreakable(block.Type) && block.Special <= 0;\n}\n\n// Check is this a player filter block that matches a character and must be ignored in checks\nbool BlockCheckPlayerFilter(int blockIdx, int playerIdx);\n\n//! The block was hit by a player\nvoid BlockHit(int A, bool HitDown = false, int whatPlayer = 0);\n//! Shake the block up\nvoid BlockShakeUp(int A);\n//! Shake the block up\nvoid BlockShakeUpPow(int A);\n//! Shake the block down\nvoid BlockShakeDown(int A);\n\nvoid BlockHitHard(int A);\n//! Safely destroy a block\nvoid SafelyKillBlock(int A);\n//! Unsafely destroy a block; returns true if this procedure should be resumed. (this procedure will become private)\nbool KillBlock(int A, bool Splode = true);\n// update the frames for animated blocks\nvoid BlockFrames();\n//! Update the blocks; returns true if this procedure should be resumed.\nbool UpdateBlocks();\n\n//! turns all the blocks to coins and vice versa (now private, because it can interrupt the gameplay logic)\n// bool PSwitch(bool enabled);\n//! drops coins and shakes all blocks on screen when player throws a POW block\nvoid PowBlock();\n\n// Extra: restore the normal music after a switch ends\nvoid SwitchEndResumeMusic();\n\n#endif // BLOCKS_H\n"
  },
  {
    "path": "src/capabilities.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../version.h\"\n#include \"capabilities.h\"\n\n\nconst char *const g_capabilities =\n    \"{\"\n        \"\\\"version\\\" : \\\"\" V_LATEST_STABLE \"\\\",\"\n        \"\\\"ipc-protocols\\\" : [\"\n            \"\\\"moondust-stdinout\\\",\"\n            \"\\\"--END--\\\"\"\n        \"],\"\n        \"\\\"arguments\\\" : [\"\n            \"\\\"c\\\",\"\n            \"\\\"u\\\",\"\n            \"\\\"game-dirname\\\",\"\n            \"\\\"frameskip\\\",\"\n            \"\\\"no-frameskip\\\",\"\n            \"\\\"no-sound\\\",\"\n            \"\\\"never-pause\\\",\"\n            \"\\\"bg-input\\\",\"\n            \"\\\"vsync\\\",\"\n            \"\\\"render\\\",\"\n            \"\\\"leveltest\\\",\"\n            \"\\\"num-players\\\",\"\n            \"\\\"start-warp\\\",\"\n            \"\\\"battle\\\",\"\n            \"\\\"player1\\\",\"\n            \"\\\"player2\\\",\"\n            \"\\\"god-mode\\\",\"\n            \"\\\"grab-all\\\",\"\n            \"\\\"show-fps\\\",\"\n            \"\\\"max-fps\\\",\"\n            \"\\\"magic-hand\\\",\"\n            \"\\\"editor\\\",\"\n            \"\\\"interprocessing\\\",\"\n            \"\\\"compat-level\\\",\"\n            \"\\\"speed-run-mode\\\",\"\n            \"\\\"speed-run-semitransparent\\\",\"\n            \"\\\"speed-run-blink-mode\\\",\"\n            \"\\\"show-controls\\\",\"\n            \"\\\"show-battery-status\\\",\"\n            \"\\\"save-slot\\\",\"\n            \"\\\"export-lang\\\",\"\n            \"\\\"lang\\\",\"\n            \"\\\"verbose\\\",\"\n            \"\\\"levelpath\\\",\"\n            \"\\\"--END--\\\"\"\n        \"],\"\n        \"\\\"renders\\\" : [\"\n            \"\\\"sw\\\",\"\n            \"\\\"hw\\\",\"\n            \"\\\"vsync\\\",\"\n            \"\\\"sdl\\\",\"\n#ifdef THEXTECH_BUILD_GL_DESKTOP_MODERN\n            \"\\\"opengl\\\",\"\n#endif\n#ifdef THEXTECH_BUILD_GL_ES_MODERN\n            \"\\\"opengles\\\",\"\n#endif\n#ifdef THEXTECH_BUILD_GL_DESKTOP_LEGACY\n            \"\\\"opengl11\\\",\"\n#endif\n#ifdef THEXTECH_BUILD_GL_ES_LEGACY\n            \"\\\"opengles11\\\",\"\n#endif\n            \"\\\"--END--\\\"\"\n        \"],\"\n        \"\\\"features\\\" : [\"\n            \"\\\"stars-number\\\",\"\n            \"\\\"editor\\\",\"\n            \"\\\"test-level-file\\\",\"\n            \"\\\"test-level-ipc\\\",\"\n            \"\\\"test-world-file\\\",\"\n            \"\\\"vsync-flag\\\",\"\n            \"\\\"ipc-lvlx-multipart\\\",\"\n            \"\\\"ipc-player-health\\\",\"\n            \"\\\"ipc-player-item\\\",\"\n            \"\\\"--END--\\\"\"\n        \"]\"\n    \"}\";\n"
  },
  {
    "path": "src/capabilities.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef CAPABILITIES_H\n#define CAPABILITIES_H\n\nextern const char *const g_capabilities;\n\n#endif // CAPABILITIES_H\n"
  },
  {
    "path": "src/change_res.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"config.h\"\n#include \"change_res.h\"\n#include \"load_gfx.h\"\n#include \"graphics.h\"\n#include \"sound.h\"\n#include \"game_main.h\"\n#include \"message.h\"\n#include \"main/screen_asset_pack.h\"\n#include \"core/render.h\"\n#include \"core/window.h\"\n#include \"core/render.h\"\n#ifdef __EMSCRIPTEN__\n#include \"core/events.h\"\n#endif\n\nvoid SyncSysCursorDisplay()\n{\n    bool hide_cursor = false;\n\n    // hide cursor in fullscreen\n    if(XWindow::isFullScreen())\n        hide_cursor = true;\n\n    // hide cursor in game states that have own cursor\n    if(GameMenu || MagicHand || LevelEditor || ScreenAssetPack::g_LoopActive)\n        hide_cursor = true;\n\n    // hide cursor in pause states that have own cursor\n    if(GamePaused == PauseCode::Options || GamePaused == PauseCode::DropAdd || GamePaused == PauseCode::TextEntry)\n        hide_cursor = true;\n\n    if(hide_cursor)\n    {\n        XWindow::setCursor(CURSOR_NONE);\n        XWindow::showCursor(0);\n    }\n    else\n    {\n        XWindow::setCursor(CURSOR_DEFAULT);\n        XWindow::showCursor(1);\n    }\n}\n\nvoid SetOrigRes()\n{\n    XWindow::setFullScreen(false);\n\n#ifndef __EMSCRIPTEN__\n    if(g_config.scale_mode == Config_t::SCALE_FIXED_05X)\n        XWindow::setWindowSize(XRender::TargetW / 2, XRender::TargetH / 2);\n    else if(g_config.scale_mode == Config_t::SCALE_FIXED_2X)\n        XWindow::setWindowSize(XRender::TargetW * 2, XRender::TargetH * 2);\n    else if(g_config.scale_mode == Config_t::SCALE_FIXED_3X)\n        XWindow::setWindowSize(XRender::TargetW * 3, XRender::TargetH * 3);\n    else\n        XWindow::setWindowSize(XRender::TargetW, XRender::TargetH);\n#endif\n\n#ifdef __EMSCRIPTEN__\n    XEvents::eventResize();\n#endif\n\n    if(LoadingInProcess)\n        UpdateLoad();\n\n    SyncSysCursorDisplay();\n}\n\nvoid ChangeRes(int, int, int, int)\n{\n    XWindow::setFullScreen(true);\n\n    if(LoadingInProcess)\n        UpdateLoad();\n\n    SyncSysCursorDisplay();\n}\n\n//void SaveIt(int ScX, int ScY, int ScC, int ScF, std::string ScreenChanged)\n//{\n\n//}\n\nvoid UpdateInternalRes()\n{\n    // bool ignore_compat = LevelEditor || (GameMenu && g_config.speedrun_mode.m_set < ConfigSetLevel::cmdline);\n    bool ignore_compat = LevelEditor;\n\n    int req_w = g_config.internal_res.m_value.first;\n    int req_h = g_config.internal_res.m_value.second;\n\n#ifndef PGE_MIN_PORT\n    if(l_screen->Type == ScreenTypes::Quad && g_config.internal_res_4p.m_value.first != 0)\n    {\n        req_w = g_config.internal_res_4p.m_value.first;\n        req_h = g_config.internal_res_4p.m_value.second;\n        ignore_compat = true;\n    }\n#endif\n\n    // use the correct canonical screen's resolution here\n    int canon_w = l_screen->canonical_screen().W;\n    int canon_h = l_screen->canonical_screen().H;\n\n    if(!g_config.allow_multires && !ignore_compat)\n    {\n        if((req_w != 0 && req_w < canon_w) || (req_h != 0 && req_h < canon_h))\n        {\n            req_w = canon_w;\n            req_h = canon_h;\n        }\n    }\n\n    if(!XRender::is_nullptr() && (req_w == 0 || req_h == 0))\n    {\n        int int_w, int_h, orig_int_h;\n\n        XRender::getRenderSize(&int_w, &int_h);\n        orig_int_h = int_h;\n\n        // set internal height first\n        if(req_h != 0)\n        {\n            int_h = req_h;\n        }\n        else if(g_config.scale_mode == Config_t::SCALE_FIXED_05X)\n        {\n            int_h *= 2;\n        }\n        else if(g_config.scale_mode == Config_t::SCALE_FIXED_2X)\n        {\n            int_h /= 2;\n        }\n        else if(g_config.scale_mode == Config_t::SCALE_FIXED_3X)\n        {\n            int_h /= 3;\n        }\n        else if(g_config.scale_mode == Config_t::SCALE_DYNAMIC_INTEGER)\n        {\n            if(int_h >= 600)\n            {\n                // constrains height to be in the 600-720p range\n                int scale_factor = int_h / 600;\n                int_h /= scale_factor;\n            }\n        }\n\n        // minimum height constraint\n        if(int_h < 320)\n            int_h = 320;\n\n        // maximum height constraint\n        if(int_h > 720 && req_h <= 720)\n            int_h = 720;\n\n        // now, set width based on height and scaling mode\n        if(g_config.scale_mode == Config_t::SCALE_FIXED_05X)\n        {\n            int_w *= 2;\n        }\n        else if(g_config.scale_mode == Config_t::SCALE_FIXED_1X)\n        {\n            // keep as-is\n            // int_w = int_w;\n        }\n        else if(g_config.scale_mode == Config_t::SCALE_FIXED_2X)\n        {\n            int_w /= 2;\n        }\n        else if(g_config.scale_mode == Config_t::SCALE_FIXED_3X)\n        {\n            int_w /= 3;\n        }\n        else if(g_config.scale_mode == Config_t::SCALE_DYNAMIC_INTEGER)\n        {\n            int scale_factor = orig_int_h / int_h;\n            if(scale_factor == 0)\n            {\n                // keep as-is\n                // int_w = int_w;\n            }\n            else if(int_w / scale_factor >= 800)\n            {\n                int_w /= scale_factor;\n            }\n            else\n            {\n                // scale based on width\n                int scale_factor = int_w / 800;\n                if(scale_factor != 0)\n                    int_w /= scale_factor;\n\n                // rescale the height if possible\n                if(scale_factor != 0 && req_h == 0)\n                {\n                    int_h = orig_int_h / scale_factor;\n                    if(int_h < 600)\n                        int_h = 600;\n                    if(int_h > 720)\n                        int_h = 720;\n                }\n            }\n        }\n        else\n        {\n            int_w = (int_w * int_h) / orig_int_h;\n        }\n\n        // force >800x600 resolution if required\n        if(!g_config.allow_multires && !ignore_compat)\n        {\n            if(int_w < canon_w)\n            {\n                int_h = (int_h * canon_w) / int_w;\n                int_w = canon_w;\n                if(int_h > 720)\n                    int_h = 720;\n            }\n\n            if(int_h < canon_h)\n            {\n                int_w = (int_w * canon_h) / int_h;\n                int_h = canon_h;\n            }\n        }\n\n        // minimum width constraint\n        if(int_w < 480)\n            int_w = 480;\n\n        // maximum 2.4 (cinematic) aspect ratio\n        if(int_w > int_h * 24 / 10)\n            int_w = int_h * 24 / 10;\n\n        // force even dimensions\n        int_w -= int_w & 1;\n        int_h -= int_h & 1;\n\n        XRender::TargetW = int_w;\n        XRender::TargetH = int_h;\n    }\n    else\n    {\n        XRender::TargetW = req_w;\n        XRender::TargetH = req_h;\n\n        if(XRender::TargetW == 0)\n            XRender::TargetW = (XRender::TargetH * 16 / 9) & ~1;\n\n        if(XRender::TargetH == 0)\n        {\n            XRender::TargetW = 1280;\n            XRender::TargetH = 720;\n        }\n    }\n\n    if(LevelEditor || MagicHand)\n    {\n        XRender::TargetW = SDL_max(XRender::TargetW, 640);\n        XRender::TargetH = SDL_max(XRender::TargetH, 480);\n    }\n\n    int new_CameraOverscanX = (g_config.allow_multires || ignore_compat) ? XRender::TargetCameraOverscanX : 0;\n    XRender::TargetW += new_CameraOverscanX * 2;\n\n    // DONE: above should tweak render target resolution. This should tweak game's screen resolution.\n    int new_ScreenW, new_ScreenH;\n    if(!GameMenu && !GameOutro && !LevelEditor && !BattleMode && g_VanillaCam)\n    {\n        XRender::TargetW = SDL_max(XRender::TargetW, canon_w);\n        XRender::TargetH = SDL_max(XRender::TargetH, canon_h);\n        new_ScreenW = canon_w;\n        new_ScreenH = canon_h;\n    }\n    else if(ignore_compat || (g_config.allow_multires && g_config.dynamic_camera_logic))\n    {\n        new_ScreenW = XRender::TargetW;\n        new_ScreenH = XRender::TargetH;\n    }\n    else if(g_config.allow_multires)\n    {\n        new_ScreenW = SDL_min(XRender::TargetW, canon_w);\n        new_ScreenH = SDL_min(XRender::TargetH, canon_h);\n    }\n    else\n    {\n        new_ScreenW = canon_w;\n        new_ScreenH = canon_h;\n    }\n\n    if(l_screen->W != new_ScreenW)\n        XMessage::PushMessage({XMessage::Type::screen_w, (uint8_t)(new_ScreenW / 256), (uint8_t)(new_ScreenW % 256)});\n    if(l_screen->H != new_ScreenH)\n        XMessage::PushMessage({XMessage::Type::screen_h, (uint8_t)(new_ScreenH / 256), (uint8_t)(new_ScreenH % 256)});\n    if(l_screen->CameraOverscanX != new_CameraOverscanX)\n        XMessage::PushMessage({XMessage::Type::camera_overscan_x, 0, (uint8_t)new_CameraOverscanX});\n    if(XRender::is_nullptr() || !GameIsActive)\n        return;\n\n    XRender::updateViewport();\n\n    // recenter the game menu graphics\n    if(GameMenu && (l_screen->W != new_ScreenW || l_screen->H != new_ScreenH))\n    {\n        l_screen->W = new_ScreenW;\n        l_screen->H = new_ScreenH;\n\n        SetupScreens();\n        CenterScreens();\n        GameMenu = false;\n        GetvScreenAverage(Screens[0].vScreen(1));\n        if(!Screens[0].is_canonical())\n            GetvScreenAverage(Screens[0].canonical_screen().vScreen(1));\n        GameMenu = true;\n    }\n\n    // disable world map qScreen if active\n    if(LevelSelect && qScreen)\n        qScreen = false;\n}\n\nvoid UpdateWindowRes()\n{\n    if(XWindow::is_nullptr() || XWindow::isFullScreen() || XWindow::isMaximized())\n        return;\n\n    int h = g_config.internal_res.m_value.second;\n    if(h == 0)\n        return;\n\n    int w = g_config.internal_res.m_value.first;\n\n    if(w == 0 && h == XRender::TargetH)\n        w = XRender::TargetW;\n    else if(w == 0)\n        return;\n\n    if(g_config.scale_mode == Config_t::SCALE_FIXED_05X)\n        XWindow::setWindowSize(w / 2, h / 2);\n    else if(g_config.scale_mode == Config_t::SCALE_FIXED_2X)\n        XWindow::setWindowSize(w * 2, h * 2);\n    else if(g_config.scale_mode == Config_t::SCALE_FIXED_3X)\n        XWindow::setWindowSize(w * 3, h * 3);\n    else\n        XWindow::setWindowSize(w, h);\n}\n\n"
  },
  {
    "path": "src/change_res.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CHANGE_RES_H\n#define CHANGE_RES_H\n\n// New: sync whether the window's cursor should be shown\nvoid SyncSysCursorDisplay();\n\n//void GetCurrentRes(); // Useless\nvoid SetOrigRes();\nvoid ChangeRes(int ScreenX, int ScreenY, int ScreenColor, int ScreenFreq);\n\n// New: update the internal game resolution and scaling based on game window size and preferences\n// Calls XRender::updateViewport on completion\nvoid UpdateInternalRes();\n\n// New: update the window size based on internal resolution and scaling factor\n// Only active for windowed mode with 0.5x, 1x, or 2x scaling\nvoid UpdateWindowRes();\n\n#endif // CHANGE_RES_H\n"
  },
  {
    "path": "src/cmd_line_setup.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CMD_LINE_SETUP_H\n#define CMD_LINE_SETUP_H\n\n#include <string>\n\nstruct CmdLineSetup_t\n{\n    //! Is a level testing mode\n    bool testLevelMode = false;\n    //! Level file to test\n    std::string testLevel;\n    //! Replay file to run\n    std::string testReplay;\n    //! Number of players for level test\n    int testNumPlayers = 1;\n    //! Save slot to use for world test\n    int testSave = 0;\n    //! Run a test in battle mode\n    bool testBattleMode = false;\n\n    //! Custom-specified asset pack name or path\n    std::string assetPack;\n\n    //! Enable interprocessing communication with the Moondust Editor\n    bool interprocess = false;\n\n    //! Allow playable character grab everything\n    bool testGrabAll = false;\n    //! Make playable character be inmortal\n    bool testGodMode = false;\n    //! Enable magic-hand functionality\n    bool testMagicHand = false;\n    //! Open in editor\n    bool testEditor = false;\n\n    //! Force log output into console\n    bool verboseLogging = false;\n};\n\n#endif // CMD_LINE_SETUP_H\n"
  },
  {
    "path": "src/collision.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"collision.h\"\n\n// Intersect collisions\nbool CheckCollisionIntersect(const Location_t &Loc1, const Location_t &Loc2)\n{\n    if(Loc1.Y < Loc2.Y)\n        return false;\n\n    if(Loc1.Y + Loc1.Height > Loc2.Y + Loc2.Height)\n        return false;\n\n    if(Loc1.X < Loc2.X)\n        return false;\n\n    if(Loc1.X + Loc1.Width > Loc2.X + Loc2.Width)\n        return false;\n\n    return true;\n}\n\n\n// Make the game easier for the people who whine about the detection being 'off'\nbool n00bCollision(const Location_t &Loc1, const Location_t &Loc2)\n{\n    bool tempn00bCollision = false;\n    num_t EZ = 2;\n\n    if(Loc2.Width >= 32 - EZ * 2 && Loc2.Height >= 32 - EZ * 2)\n    {\n        if(Loc1.Y + Loc1.Height - EZ >= Loc2.Y)\n        {\n            if(Loc1.Y + EZ <= Loc2.Y + Loc2.Height)\n            {\n                if(Loc1.X + EZ <= Loc2.X + Loc2.Width)\n                {\n                    if(Loc1.X + Loc1.Width - EZ >= Loc2.X)\n                    {\n                        tempn00bCollision = true;\n                    }\n                }\n            }\n        }\n    }\n    else\n    {\n        if(Loc1.Y + Loc1.Height >= Loc2.Y)\n        {\n            if(Loc1.Y <= Loc2.Y + Loc2.Height)\n            {\n                if(Loc1.X <= Loc2.X + Loc2.Width)\n                {\n                    if(Loc1.X + Loc1.Width >= Loc2.X)\n                    {\n                        tempn00bCollision = true;\n                    }\n                }\n            }\n        }\n    }\n\n    return tempn00bCollision;\n}\n\n#if 0\n// Used when a NPC is activated to see if it should spawn\nbool NPCStartCollision(const Location_t &Loc1, const Location_t &Loc2)\n{\n    bool tempNPCStartCollision = false;\n    if(Loc1.X < Loc2.X + Loc2.Width)\n    {\n        if(Loc1.X + Loc1.Width > Loc2.X)\n        {\n            if(Loc1.Y < Loc2.Y + Loc2.Height)\n            {\n                if(Loc1.Y + Loc1.Height > Loc2.Y)\n                {\n                    tempNPCStartCollision = true;\n                }\n            }\n        }\n    }\n    return tempNPCStartCollision;\n}\n#endif\n\n// Warp point collisions\nbool WarpCollision(const Location_t &Loc1, const SpeedlessLocation_t &entrance, int direction)\n{\n    bool hasCollision = false;\n\n    num_t X2 = 0;\n    num_t Y2 = 0;\n\n    if(direction == 3)\n    {\n        X2 = 0;\n        Y2 = 32;\n    }\n    else if(direction == 1)\n    {\n        X2 = 0;\n        Y2 = -30;\n    }\n    else if(direction == 2)\n    {\n        X2 = -31;\n        Y2 = 32;\n    }\n    else if(direction == 4)\n    {\n        X2 = 31;\n        Y2 = 32;\n    }\n\n    if(Loc1.X <= entrance.X + entrance.Width + X2)\n    {\n        if(Loc1.X + Loc1.Width >= entrance.X + X2)\n        {\n            if(Loc1.Y <= entrance.Y + entrance.Height + Y2)\n            {\n                if(Loc1.Y + Loc1.Height >= entrance.Y + Y2)\n                {\n                    hasCollision = true;\n                }\n            }\n        }\n    }\n\n    return hasCollision;\n}\n\n// Whats side the collision happened\nint FindCollision(const Location_t &Loc1, const Location_t &Loc2)\n{\n    int tempFindCollision = COLLISION_NONE;\n\n    if(Loc1.Y + Loc1.Height - Loc1.SpeedY <= Loc2.Y - Loc2.SpeedY)\n    {\n        tempFindCollision = COLLISION_TOP;\n    }\n    else if(Loc1.X - Loc1.SpeedX >= Loc2.X + Loc2.Width - Loc2.SpeedX)\n    {\n        tempFindCollision = COLLISION_RIGHT;\n    }\n    else if(Loc1.X + Loc1.Width - Loc1.SpeedX <= Loc2.X - Loc2.SpeedX)\n    {\n        tempFindCollision = COLLISION_LEFT;\n    }\n    else if(Loc1.Y - Loc1.SpeedY > Loc2.Y + Loc2.Height - Loc2.SpeedY - 0.1_n)\n    {\n        tempFindCollision = COLLISION_BOTTOM;\n    }\n    else\n    {\n        tempFindCollision = COLLISION_CENTER;\n    }\n\n    return tempFindCollision;\n}\n\n// Whats side the collision happened for belts\nint FindCollisionBelt(const Location_t &Loc1, const Location_t &Loc2, tempf_t BeltSpeed)\n{\n    int tempFindCollisionBelt = COLLISION_NONE;\n\n    if(Loc1.Y + Loc1.Height - Loc1.SpeedY <= Loc2.Y - Loc2.SpeedY)\n    {\n        tempFindCollisionBelt = COLLISION_TOP;\n    }\n    else if(Loc1.X - Loc1.SpeedX - (num_t)BeltSpeed >= Loc2.X + Loc2.Width - Loc2.SpeedX)\n    {\n        tempFindCollisionBelt = COLLISION_RIGHT;\n    }\n    else if(Loc1.X + Loc1.Width - Loc1.SpeedX <= Loc2.X - Loc2.SpeedX)\n    {\n        tempFindCollisionBelt = COLLISION_LEFT;\n    }\n    else if(Loc1.Y - Loc1.SpeedY - (num_t)BeltSpeed > Loc2.Y + Loc2.Height - Loc2.SpeedY - 0.1_n)\n    {\n        tempFindCollisionBelt = COLLISION_BOTTOM;\n    }\n    else\n    {\n        tempFindCollisionBelt = COLLISION_CENTER;\n    }\n\n    return tempFindCollisionBelt;\n}\n\n// Whats side the collision happened for NPCs\nint NPCFindCollision(const Location_t &Loc1, const Location_t &Loc2)\n{\n    int tempNPCFindCollision = COLLISION_NONE;\n\n    if(Loc1.Y + Loc1.Height - Loc1.SpeedY <= Loc2.Y - Loc2.SpeedY + 4)\n    {\n        tempNPCFindCollision = COLLISION_TOP;\n    }\n    else if(Loc1.X - Loc1.SpeedX >= Loc2.X + Loc2.Width - Loc2.SpeedX)\n    {\n        tempNPCFindCollision = COLLISION_RIGHT;\n    }\n    else if(Loc1.X + Loc1.Width - Loc1.SpeedX <= Loc2.X - Loc2.SpeedX)\n    {\n        tempNPCFindCollision = COLLISION_LEFT;\n    }\n    else if(Loc1.Y - Loc1.SpeedY > Loc2.Y + Loc2.Height - Loc2.SpeedY - 0.1_n)\n    {\n        tempNPCFindCollision = COLLISION_BOTTOM;\n    }\n    else\n    {\n        tempNPCFindCollision = COLLISION_CENTER;\n    }\n\n    return tempNPCFindCollision;\n}\n\n// Easy mode collision for jumping on NPCs\nint EasyModeCollision(const Location_t &Loc1, const Location_t &Loc2, bool StandOn)\n{\n    int tempEasyModeCollision = COLLISION_NONE;\n\n    if(!FreezeNPCs)\n    {\n        if(Loc1.Y + Loc1.Height - Loc1.SpeedY <= Loc2.Y - Loc2.SpeedY + 10)\n        {\n            if(Loc1.SpeedY > Loc2.SpeedY || StandOn)\n            {\n                tempEasyModeCollision = COLLISION_TOP;\n            }\n            else\n            {\n                tempEasyModeCollision = COLLISION_NONE;\n            }\n        }\n        else if(Loc1.X - Loc1.SpeedX >= Loc2.X + Loc2.Width - Loc2.SpeedX)\n        {\n            tempEasyModeCollision = COLLISION_RIGHT;\n        }\n        else if(Loc1.X + Loc1.Width - Loc1.SpeedX <= Loc2.X - Loc2.SpeedX)\n        {\n            tempEasyModeCollision = COLLISION_LEFT;\n        }\n        else if(Loc1.Y - Loc1.SpeedY >= Loc2.Y + Loc2.Height - Loc2.SpeedY)\n        {\n            tempEasyModeCollision = COLLISION_BOTTOM;\n        }\n        else\n        {\n            tempEasyModeCollision = COLLISION_CENTER;\n        }\n    }\n    else\n    {\n        if(Loc1.Y + Loc1.Height - Loc1.SpeedY <= Loc2.Y + 10)\n        {\n            tempEasyModeCollision = COLLISION_TOP;\n        }\n        else if(Loc1.X - Loc1.SpeedX >= Loc2.X + Loc2.Width)\n        {\n            tempEasyModeCollision = COLLISION_RIGHT;\n        }\n        else if(Loc1.X + Loc1.Width - Loc1.SpeedX <= Loc2.X)\n        {\n            tempEasyModeCollision = COLLISION_LEFT;\n        }\n        else if(Loc1.Y - Loc1.SpeedY >= Loc2.Y + Loc2.Height)\n        {\n            tempEasyModeCollision = COLLISION_BOTTOM;\n        }\n        else\n        {\n            tempEasyModeCollision = COLLISION_CENTER;\n        }\n    }\n\n    return tempEasyModeCollision;\n}\n\n// Easy mode collision for jumping on NPCs while on yoshi/boot\nint BootCollision(const Location_t &Loc1, const Location_t &Loc2, bool StandOn)\n{\n    int tempBootCollision = COLLISION_NONE;\n\n    if(!FreezeNPCs)\n    {\n        if(Loc1.Y + Loc1.Height - Loc1.SpeedY <= Loc2.Y - Loc2.SpeedY + 16)\n        {\n            if(Loc1.SpeedY > Loc2.SpeedY || StandOn)\n            {\n                tempBootCollision = COLLISION_TOP;\n            }\n            else\n            {\n                tempBootCollision = COLLISION_NONE;\n            }\n        }\n        else if(Loc1.X - Loc1.SpeedX >= Loc2.X + Loc2.Width - Loc2.SpeedX)\n        {\n            tempBootCollision = COLLISION_RIGHT;\n        }\n        else if(Loc1.X + Loc1.Width - Loc1.SpeedX <= Loc2.X - Loc2.SpeedX)\n        {\n            tempBootCollision = COLLISION_LEFT;\n        }\n        else if(Loc1.Y - Loc1.SpeedY >= Loc2.Y + Loc2.Height - Loc2.SpeedY)\n        {\n            tempBootCollision = COLLISION_BOTTOM;\n        }\n        else\n        {\n            tempBootCollision = COLLISION_CENTER;\n        }\n    }\n    else\n    {\n        if(Loc1.Y + Loc1.Height - Loc1.SpeedY <= Loc2.Y + 16)\n        {\n            tempBootCollision = COLLISION_TOP;\n        }\n        else if(Loc1.X - Loc1.SpeedX >= Loc2.X + Loc2.Width)\n        {\n            tempBootCollision = COLLISION_RIGHT;\n        }\n        else if(Loc1.X + Loc1.Width - Loc1.SpeedX <= Loc2.X)\n        {\n            tempBootCollision = COLLISION_LEFT;\n        }\n        else if(Loc1.Y - Loc1.SpeedY >= Loc2.Y + Loc2.Height)\n        {\n            tempBootCollision = COLLISION_BOTTOM;\n        }\n        else\n        {\n            tempBootCollision = COLLISION_CENTER;\n        }\n    }\n\n    return tempBootCollision;\n\n}\n\n// Cursor collision\nbool CursorCollision(const Location_t &Loc1, const Location_t &Loc2)\n{\n    return (Loc1.X <= Loc2.X + Loc2.Width - 1) &&\n           (Loc1.X + Loc1.Width >= Loc2.X + 1) &&\n           (Loc1.Y <= Loc2.Y + Loc2.Height - 1) &&\n           (Loc1.Y + Loc1.Height >= Loc2.Y + 1);\n}\nbool CursorCollision(const Location_t &Loc1, const SpeedlessLocation_t &Loc2)\n{\n    return (Loc1.X <= Loc2.X + Loc2.Width - 1) &&\n           (Loc1.X + Loc1.Width >= Loc2.X + 1) &&\n           (Loc1.Y <= Loc2.Y + Loc2.Height - 1) &&\n           (Loc1.Y + Loc1.Height >= Loc2.Y + 1);\n}\nbool CursorCollision(const Location_t &Loc1, const TinyLocation_t &Loc2)\n{\n    return (Loc1.X <= Loc2.X + Loc2.Width - 1) &&\n           (Loc1.X + Loc1.Width >= Loc2.X + 1) &&\n           (Loc1.Y <= Loc2.Y + Loc2.Height - 1) &&\n           (Loc1.Y + Loc1.Height >= Loc2.Y + 1);\n}\n\n// Shakey block collision\nbool ShakeCollision(const Location_t &Loc1, const Block_t &b)\n{\n    const Location_t &Loc2 = b.Location;\n\n    bool tempShakeCollision = false;\n\n    if(Loc1.X + 1 <= Loc2.X + Loc2.Width)\n    {\n        if(Loc1.X + Loc1.Width - 1 >= Loc2.X)\n        {\n            if(Loc1.Y <= Loc2.Y + Loc2.Height + b.ShakeOffset)\n            {\n                if(Loc1.Y + Loc1.Height >= Loc2.Y + b.ShakeOffset)\n                {\n                    tempShakeCollision = true;\n                }\n            }\n        }\n    }\n\n    return tempShakeCollision;\n}\n\n// vScreen collisions\nbool vScreenCollision(int A, const Location_t &Loc2)\n{\n    if(A == 0)\n        return true;\n\n    return (-vScreen[A].X <= Loc2.X + Loc2.Width) &&\n           (-vScreen[A].X + vScreen[A].Width >= Loc2.X) &&\n           (-vScreen[A].Y <= Loc2.Y + Loc2.Height) &&\n           (-vScreen[A].Y + vScreen[A].Height >= Loc2.Y);\n}\n\nbool vScreenCollision(int A, const SpeedlessLocation_t &Loc2)\n{\n    if(A == 0)\n        return true;\n\n    return (-vScreen[A].X <= Loc2.X + Loc2.Width) &&\n           (-vScreen[A].X + vScreen[A].Width >= Loc2.X) &&\n           (-vScreen[A].Y <= Loc2.Y + Loc2.Height) &&\n           (-vScreen[A].Y + vScreen[A].Height >= Loc2.Y);\n}\n\nbool vScreenCollision(int A, const TinyLocation_t &Loc2)\n{\n    if(A == 0)\n        return true;\n\n    return (-vScreen[A].X <= Loc2.X + Loc2.Width) &&\n           (-vScreen[A].X + vScreen[A].Width >= Loc2.X) &&\n           (-vScreen[A].Y <= Loc2.Y + Loc2.Height) &&\n           (-vScreen[A].Y + vScreen[A].Height >= Loc2.Y);\n}\n\n#if 0\n// vScreen collisions 2\nbool vScreenCollision2(int A, const Location_t &Loc2)\n{\n    return (-vScreen[A].X + 64 <= Loc2.X + Loc2.Width) &&\n           (-vScreen[A].X + vScreen[A].Width - 64 >= Loc2.X) &&\n           (-vScreen[A].Y + 96 <= Loc2.Y + Loc2.Height) &&\n           (-vScreen[A].Y + vScreen[A].Height - 64 >= Loc2.Y);\n}\n\nbool vScreenCollision2(int A, const SpeedlessLocation_t &Loc2)\n{\n    return (-vScreen[A].X + 64 <= Loc2.X + Loc2.Width) &&\n           (-vScreen[A].X + vScreen[A].Width - 64 >= Loc2.X) &&\n           (-vScreen[A].Y + 96 <= Loc2.Y + Loc2.Height) &&\n           (-vScreen[A].Y + vScreen[A].Height - 64 >= Loc2.Y);\n}\n\nbool vScreenCollision2(int A, const TinyLocation_t &Loc2)\n{\n    return (-vScreen[A].X + 64 <= Loc2.X + Loc2.Width) &&\n           (-vScreen[A].X + vScreen[A].Width - 64 >= Loc2.X) &&\n           (-vScreen[A].Y + 96 <= Loc2.Y + Loc2.Height) &&\n           (-vScreen[A].Y + vScreen[A].Height - 64 >= Loc2.Y);\n}\n#endif\n\n// Collision detection for blocks. Prevents walking on walls.\nbool WalkingCollision(const Location_t &Loc1, const Location_t &Loc2)\n{\n    bool tempWalkingCollision = false;\n\n    if(Loc1.X <= Loc2.X + Loc2.Width + Loc1.SpeedX)\n    {\n        if(Loc1.X + Loc1.Width >= Loc2.X + Loc1.SpeedX)\n        {\n            tempWalkingCollision = true;\n        }\n    }\n\n    return tempWalkingCollision;\n}\n\n// Collision detection for blocks. Lets NPCs fall through cracks.\n#if 0\nbool WalkingCollision2(const Location_t &Loc1, const Location_t &Loc2)\n{\n    bool tempWalkingCollision2 = false;\n\n    if(Loc1.X <= Loc2.X + Loc2.Width - Loc1.SpeedX - 1)\n    {\n        if(Loc1.X + Loc1.Width >= Loc2.X - Loc1.SpeedX + 1)\n        {\n            tempWalkingCollision2 = true;\n        }\n    }\n\n    return tempWalkingCollision2;\n}\n#endif\n\n// Factors in beltspeed\nbool WalkingCollision3(const Location_t &Loc1, const Location_t &Loc2, tempf_t BeltSpeed)\n{\n    bool tempWalkingCollision3 = false;\n\n    if(Loc1.X <= Loc2.X + Loc2.Width - (Loc1.SpeedX + (num_t)BeltSpeed) - 1)\n    {\n        if(Loc1.X + Loc1.Width >= Loc2.X - (Loc1.SpeedX + (num_t)BeltSpeed) + 1)\n        {\n            tempWalkingCollision3 = true;\n        }\n    }\n\n    return tempWalkingCollision3;\n}\n\n// Helps the player to walk over 1 unit cracks\nint FindRunningCollision(const Location_t &Loc1, const Location_t &Loc2, num_t BeltSpeedX)\n{\n    int tempFindRunningCollision = COLLISION_NONE;\n\n    if(Loc1.Y + Loc1.Height - Loc1.SpeedY - 2.5_n <= Loc2.Y - Loc2.SpeedY)\n    {\n        tempFindRunningCollision = COLLISION_TOP;\n    }\n    else if(Loc1.X - Loc1.SpeedX >= Loc2.X + Loc2.Width - (Loc2.SpeedX - BeltSpeedX))\n    {\n        tempFindRunningCollision = COLLISION_RIGHT;\n    }\n    else if(Loc1.X + Loc1.Width - Loc1.SpeedX <= Loc2.X - (Loc2.SpeedX - BeltSpeedX))\n    {\n        tempFindRunningCollision = COLLISION_LEFT;\n    }\n    else if(Loc1.Y - Loc1.SpeedY >= Loc2.Y + Loc2.Height - Loc2.SpeedY)\n    {\n        tempFindRunningCollision = COLLISION_BOTTOM;\n    }\n    else\n    {\n        tempFindRunningCollision = COLLISION_CENTER;\n    }\n\n    return tempFindRunningCollision;\n}\n\n// Determines if an NPC should turnaround\n#if 0\nbool ShouldTurnAround(const Location_t &Loc1, const Location_t &Loc2, float Direction)\n{\n    bool tempShouldTurnAround = false;\n    tempShouldTurnAround = true;\n\n    if(Loc1.Y + Loc1.Height + 8 <= Loc2.Y + Loc2.Height)\n    {\n        if(Loc1.Y + Loc1.Height + 8 >= Loc2.Y)\n        {\n            if(Loc1.X + Loc1.Width * 0.5 + (8 * Direction) <= Loc2.X + Loc2.Width)\n            {\n                if(Loc1.X + Loc1.Width * 0.5 + (8 * Direction) >= Loc2.X)\n                {\n                    if(Loc2.Y > Loc1.Y + Loc1.Height - 8)\n                    {\n                        tempShouldTurnAround = false;\n                    }\n                }\n            }\n        }\n    }\n\n    return tempShouldTurnAround;\n}\n#endif\n\n// Determines if an NPC can come out of a pipe\nbool CanComeOut(const Location_t &Loc1, const Location_t &Loc2)\n{\n    bool tempCanComeOut = false;\n    tempCanComeOut = true;\n\n    if(Loc1.X <= Loc2.X + Loc2.Width + 32)\n    {\n        if(Loc1.X + Loc1.Width >= Loc2.X - 32)\n        {\n            if(Loc1.Y <= Loc2.Y + Loc2.Height + 300)\n            {\n                if(Loc1.Y + Loc1.Height >= Loc2.Y - 300)\n                {\n                    tempCanComeOut = false;\n                }\n            }\n        }\n    }\n\n    return tempCanComeOut;\n}\n\n// Fixes NPCs sinking through the ground\nbool CheckHitSpot1(const Location_t &Loc1, const Location_t &Loc2)\n{\n    bool tempCheckHitSpot1 = false;\n\n    if(Loc1.Y + Loc1.Height - Loc1.SpeedY - Physics.NPCGravity <= Loc2.Y - Loc2.SpeedY)\n    {\n        tempCheckHitSpot1 = true;\n    }\n\n    return tempCheckHitSpot1;\n}\n\nnum_t blockGetTopYTouching(const Block_t &block, const Location_t& loc)\n{\n    // Get slope type\n    int blockType = block.Type;\n    int slopeDirection;\n\n    if((blockType >= 1) && (blockType <= maxBlockType))\n    {\n        slopeDirection = BlockSlope[blockType];\n    }\n    else\n    {\n        slopeDirection = 0;\n    }\n\n    // The simple case, no slope\n    if(slopeDirection == 0)\n    {\n        return block.Location.Y;\n    }\n\n    // The degenerate case, no width\n    if(block.Location.Width <= 0)\n    {\n        return block.Location.Y;\n    }\n\n    // The following uses a slope calculation like 1.3 does\n\n    // Get right or left x coordinate as relevant for the slope direction\n    num_t refX = loc.X;\n    if(slopeDirection < 0)\n        refX += loc.Width;\n\n    // Get how far along the slope we are in the x direction\n    num_t slope = (refX - block.Location.X) / (int_ok)block.Location.Width;\n    if(slopeDirection < 0) slope = 1 - slope;\n    if(slope < 0) slope = 0;\n    if(slope > 1) slope = 1;\n\n    // Determine the y coordinate\n    return block.Location.Y + (int_ok)block.Location.Height * slope;\n}\n\nbool CompareWalkBlock(int oldBlockIdx, int newBlockIdx, const Location_t &referenceLoc)\n{\n    if(oldBlockIdx > numBlock)\n        return false;\n    if(newBlockIdx > numBlock)\n        return false;\n\n    const Block_t& oldBlock = Block[oldBlockIdx];\n    const Block_t& newBlock = Block[newBlockIdx];\n\n    num_t newBlockY = blockGetTopYTouching(newBlock, referenceLoc);\n    num_t oldBlockY = blockGetTopYTouching(oldBlock, referenceLoc);\n\n    if(newBlockY < oldBlockY)\n    {\n        // New block is higher, replace\n        return true;\n    }\n    else if(newBlockY > oldBlockY)\n    {\n        // New block is lower, don't replace\n        return false;\n    }\n\n    // Break tie based on if one is moving upward faster\n    num_t newBlockSpeedY = newBlock.Location.SpeedY;\n    num_t oldBlockSpeedY = oldBlock.Location.SpeedY;\n\n    if(newBlockSpeedY < oldBlockSpeedY)\n    {\n        // New block is moving more upward, replace\n        return true;\n    }\n    else if(newBlockSpeedY > oldBlockSpeedY)\n    {\n        // New block is moving more downward, don't replace\n        return false;\n    }\n\n    num_t refX = referenceLoc.X + referenceLoc.Width / 2;\n\n    // Break tie based on which of blocks intersects the center point\n    bool oldIntersects = referenceLoc.X >= oldBlock.Location.X && referenceLoc.X + referenceLoc.Width <= oldBlock.Location.X + oldBlock.Location.Width;\n    bool newIntersects = referenceLoc.X >= newBlock.Location.X && referenceLoc.X + referenceLoc.Width <= newBlock.Location.X + newBlock.Location.Width;\n\n    if(newIntersects && !oldIntersects)\n    {\n        // New block intersects\n        return true;\n    }\n    else if(!newIntersects && oldIntersects)\n    {\n        // Old block intersects\n        return false;\n    }\n\n    // Break tie based on x-proximity\n    num_t newBlockDist = num_t::abs((newBlock.Location.X + newBlock.Location.Width / 2) - refX);\n    num_t oldBlockDist = num_t::abs((oldBlock.Location.X + oldBlock.Location.Width / 2) - refX);\n\n    if(newBlockDist < oldBlockDist)\n    {\n        // New block is closer, replace\n        return true;\n    }\n\n    if(newBlockDist > oldBlockDist)\n    {\n        // New block further, don't replace\n        return false;\n    }\n\n    // Break tie based on narrower width (more specific match)\n    num_t newBlockWidth = newBlock.Location.Width;\n    num_t oldBlockWidth = oldBlock.Location.Width;\n\n    if(newBlockWidth < oldBlockWidth)\n    {\n        // New block is narrower, replace\n        return true;\n    }\n\n    if(newBlockWidth > oldBlockWidth)\n    {\n        // New block wider, don't replace\n        return false;\n    }\n\n    // Still tied? Let's just not replace\n    return false;\n}\n\nvoid CompareNpcWalkBlock(int &tempHitBlock, int &tempHitOldBlock,\n                         num_t &tempHit,   num_t &tempHitOld,\n                         int &tempHitIsSlope, NPC_t *npc)\n{\n    int oldBlockIdx  = tempHitOldBlock;\n    int newBlockIdx  = tempHitBlock;\n    Location_t &loc = npc->Location;\n\n    // If no temp block was already set, just exit\n    if(tempHitOldBlock == 0)\n    {\n        // tempHitBlock is set, don't revert\n        tempHitIsSlope = 0;\n        if(npc->Slope && (npc->Slope != newBlockIdx))\n        {\n            if(CompareWalkBlock(npc->Slope, newBlockIdx, loc) != 0)\n            {\n                npc->Slope = 0;\n            }\n        }\n        return;\n    }\n\n    // Compare blocks\n    int compareResult = CompareWalkBlock(oldBlockIdx, newBlockIdx, loc);\n\n    // Revert to the old block if the comparison says we shouldn't replace\n    if(compareResult == 0)\n    {\n        // We shouldn't replace, so revert variables\n        tempHitBlock = tempHitOldBlock;\n        tempHit = tempHitOld;\n    }\n    else\n    {\n        // tempHitBlock is set, don't revert\n        tempHitIsSlope = 0;\n        if(npc->Slope && (npc->Slope != newBlockIdx))\n        {\n            if(CompareWalkBlock(npc->Slope, newBlockIdx, loc) != 0)\n            {\n                npc->Slope = 0;\n            }\n        }\n    }\n}\n\nbool SectionCollision(const int section, const Location_t &loc)\n{\n    const auto &sec = level[section];\n    const num_t gap = 64;\n\n    if(loc.X + loc.Width < sec.X - gap)\n        return false;\n\n    if(loc.X > sec.Width + gap)\n        return false;\n\n    if(loc.Y + loc.Height < sec.Y - gap)\n        return false;\n\n    if(loc.Y > sec.Height)\n        return false;\n\n    return true;\n}\n"
  },
  {
    "path": "src/collision.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef COLLISION_H\n#define COLLISION_H\n\n#include \"location.h\"\n\nstruct NPC_t;\nstruct Block_t;\n\nenum CollisionSpot\n{\n    COLLISION_NONE = 0,\n    COLLISION_TOP = 1,\n    COLLISION_RIGHT = 2,\n    COLLISION_BOTTOM = 3,\n    COLLISION_LEFT = 4,\n    COLLISION_CENTER = 5\n};\n\n// Public Function CheckCollision(Loc1 As Location, Loc2 As Location) As Boolean 'Normal collisions\n// Normal collisions\ntemplate<class T1, class T2>\nbool CheckCollision(const T1 &Loc1, const T2 &Loc2)\n{\n    return (Loc1.Y + Loc1.Height >= Loc2.Y) &&\n           (Loc1.Y <= Loc2.Y + Loc2.Height) &&\n           (Loc1.X <= Loc2.X + Loc2.Width) &&\n           (Loc1.X + Loc1.Width >= Loc2.X);\n}\n// Intersect collision\nbool CheckCollisionIntersect(const Location_t &Loc1, const Location_t &Loc2);\n// Public Function n00bCollision(Loc1 As Location, Loc2 As Location) As Boolean 'Make the game easier for the people who whine about the detection being 'off'\n// Make the game easier for the people who whine about the detection being 'off'\nbool n00bCollision(const Location_t &Loc1, const Location_t &Loc2);\n// Public Function NPCStartCollision(Loc1 As Location, Loc2 As Location) As Boolean 'Used when a NPC is activated to see if it should spawn\n// Used when a NPC is activated to see if it should spawn\n// bool NPCStartCollision(const Location_t &Loc1, const Location_t &Loc2);\n// Public Function WarpCollision(Loc1 As Location, A As Integer) As Boolean  'Warp point collisions\n// Warp point collisions\nbool WarpCollision(const Location_t &Loc1, const SpeedlessLocation_t &entrance, int direction);\n// Public Function FindCollision(Loc1 As Location, Loc2 As Location) As Integer 'Whats side the collision happened\n// Whats side the collision happened\nint FindCollision(const Location_t &Loc1, const Location_t &Loc2);\n// Public Function FindCollisionBelt(Loc1 As Location, Loc2 As Location, BeltSpeed As Single) As Integer  'Whats side the collision happened for belts\n// Whats side the collision happened for belts\nint FindCollisionBelt(const Location_t &Loc1, const Location_t &Loc2, tempf_t BeltSpeed);\n// Public Function NPCFindCollision(Loc1 As Location, Loc2 As Location) As Integer 'Whats side the collision happened for NPCs\n// Whats side the collision happened for NPCs\nint NPCFindCollision(const Location_t &Loc1, const Location_t &Loc2);\n// Public Function EasyModeCollision(Loc1 As Location, Loc2 As Location, Optional StandOn As Boolean = False) As Integer  'Easy mode collision for jumping on NPCs\n// Easy mode collision for jumping on NPCs\nint EasyModeCollision(const Location_t &Loc1, const Location_t &Loc2, bool StandOn);\n// Public Function BootCollision(Loc1 As Location, Loc2 As Location, Optional StandOn As Boolean = False) As Integer 'Easy mode collision for jumping on NPCs while on yoshi/boot\n// Easy mode collision for jumping on NPCs while on yoshi/boot\nint BootCollision(const Location_t &Loc1, const Location_t &Loc2, bool StandOn);\n// Public Function CursorCollision(Loc1 As Location, Loc2 As Location) As Boolean 'Cursor collision\n// Cursor collision\nbool CursorCollision(const Location_t &Loc1, const Location_t &Loc2);\nbool CursorCollision(const Location_t &Loc1, const SpeedlessLocation_t &Loc2);\nbool CursorCollision(const Location_t &Loc1, const TinyLocation_t &Loc2);\n// Public Function ShakeCollision(Loc1 As Location, Loc2 As Location, ShakeY3 As Integer) As Boolean 'Shakey block collision\n// Shakey block collision\nbool ShakeCollision(const Location_t &Loc1, const Block_t &b);\n// Public Function vScreenCollision(A As Integer, Loc2 As Location) As Boolean  'vScreen collisions\n// vScreen collisions\nbool vScreenCollision(int A, const Location_t &Loc2);\nbool vScreenCollision(int A, const SpeedlessLocation_t &Loc2);\nbool vScreenCollision(int A, const TinyLocation_t &Loc2);\n// Public Function vScreenCollision2(A As Integer, Loc2 As Location) As Boolean  'vScreen collisions 2\n// vScreen collisions 2\n// bool vScreenCollision2(int A, const Location_t &Loc2);\n// bool vScreenCollision2(int A, const SpeedlessLocation_t &Loc2);\n// bool vScreenCollision2(int A, const TinyLocation_t &Loc2);\n// Public Function WalkingCollision(Loc1 As Location, Loc2 As Location) As Boolean 'Collision detection for blocks. Prevents walking on walls.\n// Collision detection for blocks. Prevents walking on walls.\nbool WalkingCollision(const Location_t &Loc1, const Location_t &Loc2);\n// Public Function WalkingCollision2(Loc1 As Location, Loc2 As Location) As Boolean 'Collision detection for blocks. Lets NPCs fall through cracks.\n// Collision detection for blocks. Lets NPCs fall through cracks.\n// bool WalkingCollision2(const Location_t &Loc1, const Location_t &Loc2);\n// Public Function WalkingCollision3(Loc1 As Location, Loc2 As Location, BeltSpeed As Single) As Boolean 'Factors in beltspeed\n// Factors in beltspeed\nbool WalkingCollision3(const Location_t &Loc1, const Location_t &Loc2, tempf_t BeltSpeed);\n// Public Function FindRunningCollision(Loc1 As Location, Loc2 As Location) As Integer 'Helps the player to walk over 1 unit cracks\n// Helps the player to walk over 1 unit cracks\n// NEW: BeltSpeedX accounts for the speed of a conveyor belt when testing side collisions\nint FindRunningCollision(const Location_t &Loc1, const Location_t &Loc2, num_t BeltSpeedX);\n// Public Function ShouldTurnAround(Loc1 As Location, Loc2 As Location, Direction As Single) As Boolean  'Determines if an NPC should turnaround\n// Determines if an NPC should turnaround\n// bool ShouldTurnAround(const Location_t &Loc1, const Location_t &Loc2, float Direction);\n// Public Function CanComeOut(Loc1 As Location, Loc2 As Location) As Boolean  'Determines if an NPC can come out of a pipe\n// Determines if an NPC can come out of a pipe\nbool CanComeOut(const Location_t &Loc1, const Location_t &Loc2);\n// Public Function CheckHitSpot1(Loc1 As Location, Loc2 As Location) As Boolean  'Fixes NPCs sinking through the ground\n// Fixes NPCs sinking through the ground\nbool CheckHitSpot1(const Location_t &Loc1, const Location_t &Loc2);\n\nnum_t blockGetTopYTouching(const Block_t &block, const Location_t& loc);\n\nbool CompareWalkBlock(int oldBlockIdx, int newBlockIdx, const Location_t &referenceLoc);\n\nvoid CompareNpcWalkBlock(int &tempHitBlock, int &tempHitOldBlock,\n                         num_t &tempHit,   num_t &tempHitOld,\n                         int &tempHitIsSlope, NPC_t *npc);\n\nbool SectionCollision(const int section, const Location_t &loc);\n\n#endif // COLLISION_H\n"
  },
  {
    "path": "src/compat.cpp",
    "content": ""
  },
  {
    "path": "src/compat.h",
    "content": ""
  },
  {
    "path": "src/config/config_base.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <set>\n\n#include <fmt_format_ne.h>\n\n#include \"config/config_base.hpp\"\n#include \"config.h\"\n\n#include \"main/translate.h\"\n\n// can't include config_impl.hpp to avoid multiple declarations\n\n// config_base.cpp\nextern ConfigSetLevel g_configWriteLevel;\n\n// config_main.cpp\nextern bool g_configInTransaction;\nextern std::set<BaseConfigOption_t<true>*> g_configModified;\n\n\nConfigSetLevel g_configWriteLevel = ConfigSetLevel::debug;\n\nConfigChangeSentinel::ConfigChangeSentinel(ConfigSetLevel level)\n{\n    m_prev_level = g_configWriteLevel;\n    g_configWriteLevel = level;\n}\n\nConfigChangeSentinel::~ConfigChangeSentinel()\n{\n    g_configWriteLevel = m_prev_level;\n}\n\n\n// base config\nBaseConfigOption_t<false>::BaseConfigOption_t(_Config_t<false>* parent, uint8_t scope,\n    const char* internal_name, const char* display_name, const char* display_tooltip,\n    void (*onupdate)(), bool (*active)(), bool (*validate)(void*))\n : m_index(parent->m_options.size()),\n   m_scope(scope),\n   m_internal_name(internal_name),\n   m_display_name(display_name ? display_name : \"\"),\n   m_display_tooltip(display_tooltip ? display_tooltip : \"\"),\n   m_onupdate(onupdate),\n   m_active(active),\n   m_validate(validate)\n{\n    parent->m_options.push_back(this);\n}\n\nvoid BaseConfigOption_t<false>::make_translation(XTechTranslate& translate, const char* cur_section_id)\n{\n    if((m_scope & Options_t::Scope::MakeTranslation) == 0)\n        return;\n\n    XTechTranslate::insert(translate.m_engineMap, fmt::sprintf_ne(\"menu.options.%s.%s\", cur_section_id, m_internal_name), &m_display_name);\n\n    if(!m_display_tooltip.empty())\n        XTechTranslate::insert(translate.m_engineMap, fmt::sprintf_ne(\"menu.options.%s.%s-tip\", cur_section_id, m_internal_name), &m_display_tooltip);\n}\n\nBaseConfigOption_t<true>::BaseConfigOption_t(_Config_t<true>* parent) : m_index(parent->m_options.size())\n{\n    parent->m_options.push_back(this);\n\n    if(!parent->m_base)\n        return;\n    if((size_t)m_index >= parent->m_base->m_options.size())\n        return;\n    m_base = parent->m_base->m_options[m_index];\n}\n\nbool BaseConfigOption_t<true>::is_main() const\n{\n    return (size_t)this >= (size_t)(&g_config) && (size_t)this < (size_t)(&g_config) + sizeof(_Config_t<true>);\n}\n\nvoid BaseConfigOption_t<true>::_on_change()\n{\n    if(m_base && m_base->m_onupdate && is_main())\n    {\n        if(g_configInTransaction)\n            g_configModified.insert(this);\n        else\n            m_base->m_onupdate();\n    }\n}\n\n\nconst std::string& BaseConfigOption_t<true>::get_display_name(std::string& out) const\n{\n    if(!m_base || m_base->m_display_name.empty())\n        return out = \"(INVALID)\";\n\n    return out = m_base->m_display_name;\n}\n\nconst std::string& BaseConfigOption_t<true>::get_tooltip(std::string& out) const\n{\n    if(!m_base || m_base->m_display_tooltip.empty())\n        return out = \"\";\n\n    return out = m_base->m_display_tooltip;\n}\n\nvoid ConfigSection_t<false>::make_translation(XTechTranslate& translate, const char* cur_section_id)\n{\n    if(this != &g_options.compat && (m_scope & Options_t::Scope::MakeTranslation) == 0)\n        return;\n\n    XTechTranslate::insert(translate.m_engineMap, fmt::sprintf_ne(\"menu.options.%s._header\", cur_section_id), &m_display_name);\n\n    if(!m_display_tooltip.empty())\n        XTechTranslate::insert(translate.m_engineMap, fmt::sprintf_ne(\"menu.options.%s._tooltip\", cur_section_id), &m_display_tooltip);\n}\n"
  },
  {
    "path": "src/config/config_base.hpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n// again, want all Config objects to share:\n//   internal names, display names, tooltips, default values, compat values, value internal names, display names, tooltips\n\n// want the specific Config objects to have:\n//   current values, whether they are set\n\n#pragma once\n#ifndef CONFIG_BASE_HPP\n#define CONFIG_BASE_HPP\n\n#include <cstdint>\n#include <string>\n#include <vector>\n#include <array>\n#include <unordered_map>\n\nclass IniProcessing;\nclass XTechTranslate;\n\n// There are two versions of each class, one to represent the read-only information about an option,\n//   and another to represent a particular setting of the option.\n\ntemplate<bool writable>\nclass BaseConfigOption_t;\n\ntemplate<bool writable>\nclass ConfigSection_t;\n\ntemplate<bool writable>\nclass ConfigSubSection_t;\n\ntemplate<bool writable, class value_t>\nclass ConfigOption_t;\n\ntemplate<bool writable, class value_t>\nclass ConfigEnumOption_t;\n\ntemplate<bool writable, class value_t>\nclass ConfigRangeOption_t;\n\ntemplate<bool writable>\nclass ConfigLanguage_t;\n\ntemplate<bool writable>\nclass ConfigFullscreenRes_t;\n\ntemplate<bool writable>\nclass ConfigSetupEnum_t;\n\ntemplate<bool writable>\nclass _Config_t;\n\n// helper classes\n\nenum class ConfigSetLevel : uint8_t\n{\n    unset = 0,\n    set, // used by sub-configs\n    game_defaults,\n    game_info,\n    user_config,\n    bugfix_defaults,\n    ep_compat,\n    file_compat,\n    ep_config, // per-save configuration of an episode (very limited)\n    cmdline,\n    cheat,\n    speedrun,\n    compat,\n    script,\n    debug,\n};\n\nclass ConfigChangeSentinel\n{\n    ConfigSetLevel m_prev_level;\n\npublic:\n    ConfigChangeSentinel(ConfigSetLevel set_level);\n    ~ConfigChangeSentinel();\n};\n\n\n// the compat modes are identical to the NetPlay and speedrun presets\nenum class CompatMode\n{\n    off,       // use the user's config and the creator compat\n    modern,    // force modern defaults for all user settings, do use creator compats\n    classic,   // disable all standard bugfixes, force critical updates and gameplay enhancements on, do use creator compats\n    smbx64,    // disable all bugfixes and gameplay enhancements, do not use creator compats\n};\n\nenum class CompatClass\n{\n    pure_preference,      // user-side preference (or gameinfo value) that does not impact game logic at all\n    critical_update,      // performance- and flexibility-critical enhancements and bugfixes that prevent predictable unfair deaths, disabled in SMBX64 compat mode only\n    standard_update,      // ordinary fixes of vanilla bugs that get disabled during non-modern gameplay; these are determined by a user's Modern / Classic setting for a world even when compat mode is off\n};\n\n\ntemplate<class value_t>\nstruct ConfigCompatInfo_t\n{\n    const CompatClass mode = CompatClass::pure_preference;\n    const value_t prev_value = value_t();\n\n    ConfigCompatInfo_t() {}\n\n    ConfigCompatInfo_t(CompatClass mode, value_t prev_value)\n        : mode(mode), prev_value(prev_value) {}\n};\n\ntemplate<class value_t>\nstruct ConfigEnumValueInfo_t\n{\n    const value_t m_value;\n    bool m_is_translatable = true;\n    const char* m_internal_name;\n    std::string m_display_name;\n    std::string m_display_tooltip;\n\n    ConfigEnumValueInfo_t(const value_t value, const char* internal_name, const char* display_name = \"\", const char* display_tooltip = \"\")\n        : m_value(value), m_internal_name(internal_name), m_display_name(display_name ? display_name : \"\"), m_display_tooltip(display_tooltip ? display_tooltip : \"\") {}\n\n    ConfigEnumValueInfo_t(const value_t value, bool is_translatable, const char* internal_name, const char* display_name = \"\")\n        : m_value(value), m_is_translatable(is_translatable), m_internal_name(internal_name), m_display_name(display_name ? display_name : \"\"), m_display_tooltip(\"\") {}\n};\n\ntemplate<class value_t>\nstruct ConfigRangeInfo_t\n{\n    const value_t min;\n    const value_t max;\n    const value_t step;\n\n    ConfigRangeInfo_t(const value_t min, const value_t max, const value_t step)\n        : min(min), max(max), step(step) {}\n};\n\n// read-only classes\n\ntemplate<>\nclass BaseConfigOption_t<false>\n{\npublic:\n    const int16_t m_index;\n    const uint8_t m_scope;\n\n    const char* m_internal_name;\n    std::string m_display_name;\n    std::string m_display_tooltip;\n\n    void (*m_onupdate)();\n    bool (*m_active)();\n    bool (*m_validate)(void*);\n\n    // adds self to the parent's list\n    BaseConfigOption_t(_Config_t<false>* parent, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)(), bool (*active)(), bool (*validate)(void*));\n\n    virtual ~BaseConfigOption_t() = default;\n\n    virtual void make_translation(XTechTranslate& translate, const char* cur_section_id);\n};\n\ntemplate<class value_t>\nclass ConfigOption_t<false, value_t> : public BaseConfigOption_t<false>\n{\npublic:\n    value_t m_default_value;\n    ConfigCompatInfo_t<value_t> m_compat_info;\n\n    ConfigOption_t(_Config_t<false>* parent, const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            BaseConfigOption_t(parent, scope,\n                internal_name, display_name, display_tooltip,\n                onupdate, active, validate),\n            m_default_value(default_value),\n            m_compat_info(compat_info) {}\n};\n\ntemplate<class value_t>\nclass ConfigEnumOption_t<false, value_t> : public ConfigOption_t<false, value_t>\n{\npublic:\n    using BaseConfigOption_t<false>::m_internal_name;\n    using BaseConfigOption_t<false>::m_display_name;\n    using BaseConfigOption_t<false>::m_display_tooltip;\n\n    std::vector<ConfigEnumValueInfo_t<value_t>> m_enum_values;\n\n    ConfigEnumOption_t(_Config_t<false>* parent,\n        const std::vector<ConfigEnumValueInfo_t<value_t>>& enum_values,\n        const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            ConfigOption_t<false, value_t>(parent, default_value, compat_info, scope,\n                internal_name, display_name, display_tooltip,\n                onupdate, active, validate),\n            m_enum_values(enum_values) {}\n\n    virtual void make_translation(XTechTranslate& translate, const char* cur_section_id) override;\n};\n\ntemplate<class value_t>\nclass ConfigRangeOption_t<false, value_t> : public ConfigOption_t<false, value_t>\n{\npublic:\n    using BaseConfigOption_t<false>::m_internal_name;\n    using BaseConfigOption_t<false>::m_display_name;\n    using BaseConfigOption_t<false>::m_display_tooltip;\n\n    ConfigRangeInfo_t<value_t> m_range_info;\n\n    ConfigRangeOption_t(_Config_t<false>* parent,\n        const ConfigRangeInfo_t<value_t>& range_info,\n        const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            ConfigOption_t<false, value_t>(parent, default_value, compat_info, scope,\n                internal_name, display_name, display_tooltip,\n                onupdate, active, validate),\n            m_range_info(range_info) {}\n};\n\ntemplate<>\nclass ConfigLanguage_t<false> : public ConfigOption_t<false, std::string>\n{\n    using value_t = std::string;\n\npublic:\n    ConfigLanguage_t(_Config_t<false>* parent,\n        const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            ConfigOption_t<false, value_t>(parent, default_value, compat_info, scope,\n                internal_name, display_name, display_tooltip,\n                onupdate, active, validate)  {}\n};\n\ntemplate<>\nclass ConfigFullscreenRes_t<false> : public ConfigOption_t<false, std::pair<int, int>>\n{\n    using value_t = std::pair<int, int>;\n\npublic:\n    ConfigFullscreenRes_t(_Config_t<false>* parent,\n        const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            ConfigOption_t<false, value_t>(parent, default_value, compat_info, scope,\n                internal_name, display_name, display_tooltip,\n                onupdate, active, validate)  {}\n};\n\ntemplate<>\nclass ConfigSetupEnum_t<false> : public ConfigEnumOption_t<false, int>\n{\n    using ConfigEnumOption_t<false, int>::ConfigEnumOption_t;\n};\n\ntemplate<>\nclass ConfigSection_t<false> : public BaseConfigOption_t<false>\n{\npublic:\n    ConfigSection_t(_Config_t<false>* parent, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip = nullptr) :\n        BaseConfigOption_t<false>(parent, scope,\n            internal_name, display_name, display_tooltip,\n            nullptr, nullptr, nullptr) {}\n\n    virtual void make_translation(XTechTranslate& translate, const char* cur_section_id) override;\n};\n\ntemplate<>\nclass ConfigSubSection_t<false> : public BaseConfigOption_t<false>\n{\npublic:\n    ConfigSubSection_t(_Config_t<false>* parent,\n        const char* internal_name, const char* display_name, const char* display_tooltip = nullptr) :\n        BaseConfigOption_t<false>(parent, 0xff,\n            internal_name, display_name, display_tooltip,\n            nullptr, nullptr, nullptr) {}\n};\n\n// writable classes\n\ntemplate<>\nclass BaseConfigOption_t<true>\n{\npublic:\n    const BaseConfigOption_t<false>* m_base = nullptr;\n    const int16_t m_index;\n    ConfigSetLevel m_set = ConfigSetLevel::unset;\n\n    // adds self to the parent's list\n    BaseConfigOption_t(_Config_t<true>* parent);\n\n    BaseConfigOption_t(_Config_t<true>* parent, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)(), bool (*active)(), bool (*validate)(void*)) :\n            BaseConfigOption_t(parent)\n    { (void)scope, (void)internal_name; (void)display_name; (void)display_tooltip; (void)onupdate; (void)active; (void)validate; }\n\n    // returns whether this is a member of g_config or not\n    bool is_main() const;\n\n    // executes hooks if appropriate\n    void _on_change();\n\n    inline bool is_set() const\n    {\n        return m_set != ConfigSetLevel::unset;\n    }\n\n    virtual bool operator==(const BaseConfigOption_t<true>& o) const { (void)o; return false; }\n\n    inline void unset() { m_set = ConfigSetLevel::unset; }\n\n    virtual bool rotate_left() { return false; }\n    virtual bool rotate_right() { return false; }\n    virtual bool change() { return rotate_right(); }\n\n    virtual void set_from_default(ConfigSetLevel set_level) { (void)set_level; }\n    virtual void update_from_ini(IniProcessing* ini, ConfigSetLevel set_level) { (void)ini; (void)set_level; }\n\n    virtual void disable_bugfixes(ConfigSetLevel set_level) { (void)set_level; }\n\n    virtual void update_from_compat(CompatMode compat_mode, ConfigSetLevel set_level) { (void)compat_mode; (void)set_level; }\n    virtual void update_from(const BaseConfigOption_t<true>& o, ConfigSetLevel set_level) { (void)o; (void)set_level; }\n\n    virtual void save_to_ini(IniProcessing* ini) { (void)ini; }\n\n    const std::string& get_display_name(std::string& out) const;\n    virtual const std::string& get_display_value(std::string& out) const { out.clear(); return out; }\n\n    const std::string& get_tooltip(std::string& out) const;\n    virtual const std::string& get_value_tooltip(std::string& out) const { out.clear(); return out; }\n};\n\ntemplate<class value_t>\nclass ConfigOption_t<true, value_t> : public BaseConfigOption_t<true>\n{\npublic:\n    value_t m_value;\n\n    ConfigOption_t(_Config_t<true>* parent, const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            BaseConfigOption_t<true>(parent) { (void)default_value; (void)compat_info; (void)scope; (void)internal_name; (void)display_name; (void)display_tooltip; (void)onupdate; (void)active; (void)validate; }\n\n    virtual bool operator==(const BaseConfigOption_t<true>& o) const override;\n\n    inline operator value_t() const\n    {\n        return m_value;\n    }\n\n    virtual bool rotate_left() override;\n    virtual bool rotate_right() override;\n    virtual bool change() override;\n\n    const ConfigOption_t<true, value_t>& operator=(value_t value);\n\n    virtual void set_from_default(ConfigSetLevel set_level) override;\n    virtual void update_from_ini(IniProcessing* ini, ConfigSetLevel set_level) override;\n\n    virtual void disable_bugfixes(ConfigSetLevel set_level) override;\n\n    virtual void update_from_compat(CompatMode compat_mode, ConfigSetLevel set_level) override;\n    virtual void update_from(const BaseConfigOption_t<true>& o, ConfigSetLevel set_level) override;\n\n    virtual void save_to_ini(IniProcessing* ini) override;\n\n    virtual const std::string& get_display_value(std::string& out) const override;\n};\n\ntemplate<class value_t>\nclass ConfigEnumOption_t<true, value_t> : public ConfigOption_t<true, value_t>\n{\npublic:\n    using BaseConfigOption_t<true>::is_set;\n    using BaseConfigOption_t<true>::m_set;\n    using ConfigOption_t<true, value_t>::m_value;\n    using BaseConfigOption_t<true>::m_base;\n    using ConfigOption_t<true, value_t>::operator=;\n    using BaseConfigOption_t<true>::_on_change;\n\n    ConfigEnumOption_t(_Config_t<true>* parent,\n        const std::vector<ConfigEnumValueInfo_t<value_t>>& enum_values,\n        const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            ConfigOption_t<true, value_t>(parent, default_value, compat_info, scope,\n                internal_name, display_name, display_tooltip,\n                onupdate, active, validate) { (void)enum_values; }\n\n    virtual bool rotate_left() override;\n    virtual bool rotate_right() override;\n\n    virtual void update_from_ini(IniProcessing* ini, ConfigSetLevel set_level) override;\n\n    virtual void save_to_ini(IniProcessing* ini) override;\n\n    virtual const std::string& get_display_value(std::string& out) const override;\n    virtual const std::string& get_value_tooltip(std::string& out) const override;\n};\n\ntemplate<class value_t>\nclass ConfigRangeOption_t<true, value_t> : public ConfigOption_t<true, value_t>\n{\npublic:\n    using BaseConfigOption_t<true>::is_set;\n    using BaseConfigOption_t<true>::m_set;\n    using ConfigOption_t<true, value_t>::m_value;\n    using BaseConfigOption_t<true>::m_base;\n    using ConfigOption_t<true, value_t>::operator=;\n    using BaseConfigOption_t<true>::_on_change;\n\n    ConfigRangeOption_t(_Config_t<true>* parent,\n        const ConfigRangeInfo_t<value_t>& range_info,\n        const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            ConfigOption_t<true, value_t>(parent, default_value, compat_info, scope,\n                internal_name, display_name, display_tooltip,\n                onupdate, active, validate) { (void)range_info; }\n\n    virtual bool rotate_left() override;\n    virtual bool rotate_right() override;\n};\n\ntemplate<>\nclass ConfigLanguage_t<true> : public ConfigOption_t<true, std::string>\n{\n    using value_t = std::string;\n\npublic:\n    using BaseConfigOption_t<true>::m_set;\n    using BaseConfigOption_t<true>::m_base;\n    using ConfigOption_t<true, value_t>::operator=;\n\n    ConfigLanguage_t(_Config_t<true>* parent,\n        const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            ConfigOption_t<true, value_t>(parent, default_value, compat_info, scope,\n                internal_name, display_name, display_tooltip,\n                onupdate, active, validate) {}\n\n    virtual bool rotate_left() override;\n    virtual bool rotate_right() override;\n    virtual bool change() override;\n\n    virtual const std::string& get_display_value(std::string& out) const override;\n};\n\n#ifdef RENDER_FULLSCREEN_TYPES_SUPPORTED\ntemplate<>\nclass ConfigFullscreenRes_t<true> : public ConfigOption_t<true, std::pair<int, int>>\n{\n    using value_t = std::pair<int, int>;\n\npublic:\n    using BaseConfigOption_t<true>::m_set;\n    using BaseConfigOption_t<true>::m_base;\n    using ConfigOption_t<true, value_t>::operator=;\n\n    ConfigFullscreenRes_t(_Config_t<true>* parent,\n        const value_t default_value,\n        const ConfigCompatInfo_t<value_t>& compat_info, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip,\n        void (*onupdate)() = nullptr, bool (*active)() = nullptr, bool (*validate)(void*) = nullptr) :\n            ConfigOption_t<true, value_t>(parent, default_value, compat_info, scope,\n                internal_name, display_name, display_tooltip,\n                onupdate, active, validate) {}\n\n    size_t find_cur_index();\n\n    virtual bool rotate_left() override;\n    virtual bool rotate_right() override;\n    virtual bool change() override;\n};\n#endif // RENDER_FULLSCREEN_TYPES_SUPPORTED\n\ntemplate<>\nclass ConfigSetupEnum_t<true> : public ConfigEnumOption_t<true, int>\n{\npublic:\n    using ConfigEnumOption_t<true, int>::ConfigEnumOption_t;\n    using ConfigEnumOption_t<true, int>::operator=;\n\n    virtual const std::string& get_display_value(std::string& out) const override;\n\n    int obtained = -1;\n};\n\ntemplate<>\nclass ConfigSection_t<true> : public BaseConfigOption_t<true>\n{\npublic:\n    const ConfigSection_t<false>* m_base = nullptr;\n\n    ConfigSection_t(_Config_t<true>* parent, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip = nullptr);\n\n    virtual void update_from_ini(IniProcessing* ini, ConfigSetLevel set_level) override;\n    virtual void save_to_ini(IniProcessing* ini) override { update_from_ini(ini, ConfigSetLevel::unset); };\n};\n\ntemplate<>\nclass ConfigSubSection_t<true> : public BaseConfigOption_t<true>\n{\npublic:\n    const ConfigSubSection_t<false>* m_base = nullptr;\n\n    ConfigSubSection_t(_Config_t<true>* parent,\n        const char* internal_name, const char* display_name, const char* display_tooltip = nullptr);\n};\n\n#endif // CONFIG_BASE_HPP\n"
  },
  {
    "path": "src/config/config_hooks.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n#include <Logger/private/logger_sets.h>\n\n#include \"core/msgbox.h\"\n#include \"core/events.h\"\n#ifdef RENDER_FULLSCREEN_TYPES_SUPPORTED\n#   include \"core/window.h\"\n#endif\n\n#include \"config.h\"\n#include \"config/config_hooks.h\"\n\n#include \"main/translate.h\"\n#include \"main/menu_main.h\"\n#include \"main/screen_options.h\"\n\n#include \"message.h\"\n#include \"frm_main.h\"\n#include \"change_res.h\"\n#include \"globals.h\"\n#include \"game_main.h\"\n#include \"sound.h\"\n#include \"graphics.h\"\n#include \"player.h\"\n\n#include \"Integrator/integrator.h\"\n\n#ifndef THEXTECH_NO_SDL_BUILD\n#include <Graphics/graphics_funcs.h>\n#endif\n\n#ifdef __3DS__\n#include \"core/render.h\"\n#include \"main/cheat_code.h\"\n#endif\n\n\n// config_main.cpp\nextern bool g_configInTransaction;\n\nvoid config_res_set()\n{\n    g_VanillaCam = false;\n    if(GameIsActive && (GameMenu || GamePaused == PauseCode::Options || g_config.internal_res.m_set == ConfigSetLevel::cheat))\n        UpdateWindowRes();\n    UpdateInternalRes();\n}\n\nvoid config_rendermode_set()\n{\n    if(!GameIsActive)\n        return;\n\n    bool res = g_frmMain.restartRenderer();\n\n    if(res)\n        PlaySoundMenu(SFX_PSwitch);\n}\n\nvoid config_fullscreen_set()\n{\n    if(!GameIsActive)\n        return;\n\n    if(g_config.fullscreen)\n        ChangeRes(0, 0, 0, 0);\n    else\n        SetOrigRes();\n\n    XEvents::doEvents();\n}\n\n#ifdef RENDER_FULLSCREEN_TYPES_SUPPORTED\nvoid config_video_mode_set()\n{\n    if(!GameIsActive)\n        return;\n\n    AbstractWindow_t::VideoModeRes res;\n    uint8_t depth;\n    res.w = g_config.fullscreen_res.m_value.first;\n    res.h = g_config.fullscreen_res.m_value.second;\n\n    XWindow::setVideoMode(res, g_config.fullscreen_depth);\n\n    XWindow::getCurrentVideoMode(res, depth);\n    g_config.fullscreen_depth.obtained = depth;\n}\n\nvoid config_fullscreen_type_set()\n{\n    if(!GameIsActive)\n        return;\n\n    XWindow::setFullScreenType(g_config.fullscreen_type);\n}\n#endif // RENDER_FULLSCREEN_TYPES_SUPPORTED\n\nvoid config_mountdrums_set()\n{\n    if(!GameIsActive)\n        return;\n\n    UpdateYoshiMusic();\n}\n\nvoid config_screenmode_set()\n{\n    XMessage::PushMessage({XMessage::Type::multiplayer_prefs, (uint8_t)g_config.two_screen_mode.m_value, (uint8_t)g_config.four_screen_mode.m_value});\n}\n\nvoid config_audiofx_set()\n{\n    if(!GameIsActive)\n        return;\n\n    if(!LevelSelect && !LevelEditor)\n        UpdateSoundFX(Player[1].Section);\n}\n\nvoid config_audio_set()\n{\n    if(!GameIsActive)\n        return;\n\n    RestartMixerX();\n\n    if(GameMenu || !LevelSelect)\n        UpdateSoundFX(Player[1].Section);\n}\n\nvoid config_music_volume_set()\n{\n    if(!GameIsActive)\n        return;\n\n    UpdateMusicVolume();\n}\n\nvoid config_language_set()\n{\n    if(!GameIsActive)\n        return;\n\n    ReloadTranslations();\n\n    OptionsScreen::ResetStrings();\n}\n\nvoid config_log_level_set()\n{\n    g_pLogGlobalSetup.level = (PGE_LogLevel::Level)(int)g_config.log_level;\n    LogWriter::m_logLevel = g_pLogGlobalSetup.level;\n    pLogInfo(\"Updated log level to %d\", (int)g_config.log_level);\n}\n\nvoid config_integrations_set()\n{\n    Integrator::quitIntegrations();\n    Integrator::initIntegrations();\n}\n\n#ifdef __3DS__\nvoid config_3ds_inaccurate_gifs_set()\n{\n    if(g_ForceBitmaskMerge == g_config.inaccurate_gifs)\n        return;\n\n    g_ForceBitmaskMerge = g_config.inaccurate_gifs;\n    XRender::unloadGifTextures();\n}\n#endif\n\nvoid config_compat_changed()\n{\n    if(!g_configInTransaction)\n        UpdateConfig();\n}\n\n"
  },
  {
    "path": "src/config/config_hooks.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CONFIG_HOOKS_H\n#define CONFIG_HOOKS_H\n\nvoid config_res_set();\nvoid config_asset_pack_set();\nvoid config_language_set();\nvoid config_rendermode_set();\nvoid config_fullscreen_set();\n#ifdef RENDER_FULLSCREEN_TYPES_SUPPORTED\nvoid config_video_mode_set();\nvoid config_fullscreen_type_set();\n#endif\nvoid config_mountdrums_set();\nvoid config_screenmode_set();\nvoid config_audiofx_set();\nvoid config_audio_set();\nvoid config_music_volume_set();\nvoid config_log_level_set();\nvoid config_integrations_set();\nvoid config_3ds_inaccurate_gifs_set();\nvoid config_compat_changed();\n\n#endif\n"
  },
  {
    "path": "src/config/config_impl.hpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n// included in config_main.cpp\n\n#pragma once\n#ifndef CONFIG_IMPL_HPP\n#define CONFIG_IMPL_HPP\n\n#include <set>\n\n#include <fmt_format_ne.h>\n#include <IniProcessor/ini_processing.h>\n\n#include \"config/config_base.hpp\"\n#include \"main/screen_textentry.h\"\n#include \"game_main.h\"\n\n#include \"core/language.h\"\n#include \"core/window.h\"\n#include \"fontman/font_manager.h\"\n\n#include \"main/translate.h\"\n#include \"main/menu_main.h\"\n\n// config_base.cpp\nextern ConfigSetLevel g_configWriteLevel;\n\n// config_main.cpp\nextern bool g_configInTransaction;\nextern std::set<BaseConfigOption_t<true>*> g_configModified;\n\n// implementation for ConfigOption_t<true, value_t>\n\ntemplate<class value_t>\nbool ConfigOption_t<true, value_t>::operator==(const BaseConfigOption_t<true>& o) const\n{\n    const ConfigOption_t<true, value_t>* other = dynamic_cast<const ConfigOption_t<true, value_t>*>(&o);\n\n    if(!other)\n        return false;\n\n    if(!is_set() || !other->is_set())\n        return false;\n\n    return this->m_value == other->m_value;\n}\n\ntemplate<class value_t>\nbool ConfigOption_t<true, value_t>::rotate_left()\n{\n    return BaseConfigOption_t<true>::rotate_left();\n}\n\ntemplate<class value_t>\nbool ConfigOption_t<true, value_t>::rotate_right()\n{\n    return BaseConfigOption_t<true>::rotate_right();\n}\n\ntemplate<class value_t>\nbool ConfigOption_t<true, value_t>::change()\n{\n    return BaseConfigOption_t<true>::change();\n}\n\ntemplate<class value_t>\nconst ConfigOption_t<true, value_t>& ConfigOption_t<true, value_t>::operator=(value_t value)\n{\n    if(g_configWriteLevel < m_set)\n        return *this;\n\n    m_set = g_configWriteLevel;\n\n    if(m_value != value)\n    {\n        m_value = value;\n        _on_change();\n    }\n\n    return *this;\n}\n\n// force the compiler to instantiate these\ntemplate const ConfigOption_t<true, bool>& ConfigOption_t<true, bool>::operator=(bool value);\ntemplate const ConfigOption_t<true, int>& ConfigOption_t<true, int>::operator=(int value);\ntemplate const ConfigOption_t<true, std::pair<int, int>>& ConfigOption_t<true, std::pair<int, int>>::operator=(std::pair<int, int> value);\ntemplate const ConfigOption_t<true, std::string>& ConfigOption_t<true, std::string>::operator=(std::string value);\n\ntemplate<class value_t>\nvoid ConfigOption_t<true, value_t>::set_from_default(ConfigSetLevel level)\n{\n    if(level < m_set)\n        return;\n\n    if(!m_base)\n        return;\n\n    const ConfigOption_t<false, value_t>* base = dynamic_cast<const ConfigOption_t<false, value_t>*>(m_base);\n\n    if(!base)\n        return;\n\n    *this = base->m_default_value;\n\n    m_set = level;\n}\n\ntemplate<class value_t>\nvoid ConfigOption_t<true, value_t>::disable_bugfixes(ConfigSetLevel level)\n{\n    if(level < m_set)\n        return;\n\n    if(!m_base)\n        return;\n\n    const ConfigOption_t<false, value_t>* base = dynamic_cast<const ConfigOption_t<false, value_t>*>(m_base);\n\n    if(!base)\n        return;\n\n    if(g_config.playstyle == Config_t::MODE_MODERN)\n        return;\n\n    if(base->m_compat_info.mode == CompatClass::pure_preference)\n        return;\n\n    if(g_config.playstyle != Config_t::MODE_VANILLA && base->m_compat_info.mode == CompatClass::critical_update)\n        return;\n\n    *this = base->m_compat_info.prev_value;\n\n    m_set = level;\n}\n\ntemplate<class value_t>\nvoid ConfigOption_t<true, value_t>::update_from_ini(IniProcessing* ini, ConfigSetLevel level)\n{\n    if(level < m_set)\n        return;\n\n    if(!ini || !m_base || !m_base->m_internal_name)\n        return;\n\n    if(!ini->hasKey(m_base->m_internal_name))\n        return;\n\n    value_t read_value{};\n    ini->read(m_base->m_internal_name, read_value, read_value);\n\n    *this = read_value;\n    m_set = level;\n}\n\ntemplate<class value_t>\nvoid ConfigOption_t<true, value_t>::update_from_compat(CompatMode compat_mode, ConfigSetLevel level)\n{\n    if(level < m_set)\n        return;\n\n    if(!m_base)\n        return;\n\n    const ConfigOption_t<false, value_t>* base = dynamic_cast<const ConfigOption_t<false, value_t>*>(m_base);\n\n    if(!base)\n        return;\n\n    if(base->m_compat_info.mode == CompatClass::pure_preference)\n        return;\n\n    bool use_modern = (base->m_compat_info.mode == CompatClass::critical_update && compat_mode < CompatMode::smbx64)\n        || (base->m_compat_info.mode == CompatClass::standard_update && compat_mode < CompatMode::classic);\n\n    if(use_modern)\n        *this = base->m_default_value;\n    else\n        *this = base->m_compat_info.prev_value;\n\n    m_set = level;\n}\n\ntemplate<class value_t>\nvoid ConfigOption_t<true, value_t>::update_from(const BaseConfigOption_t<true>& o, ConfigSetLevel level)\n{\n    if(level < m_set)\n        return;\n\n    const ConfigOption_t<true, value_t>* o_conv = dynamic_cast<const ConfigOption_t<true, value_t>*>(&o);\n\n    if(!o_conv)\n        return;\n\n    if(!o_conv->is_set())\n        return;\n\n    *this = (value_t)*o_conv;\n    m_set = level;\n}\n\ntemplate<class value_t>\nvoid ConfigOption_t<true, value_t>::save_to_ini(IniProcessing* ini)\n{\n    if(!ini || !m_base || !m_base->m_internal_name)\n        return;\n\n    if(is_set())\n        ini->setValue(m_base->m_internal_name, m_value);\n    else\n        ini->clearValue(m_base->m_internal_name);\n}\n\ntemplate<class value_t>\nconst std::string& ConfigOption_t<true, value_t>::get_display_value(std::string& out) const\n{\n    out = std::to_string(m_value);\n    return out;\n}\n\n// specializer for strings\ntemplate<>\nbool ConfigOption_t<true, std::string>::change()\n{\n    if(!m_base)\n        return false;\n\n    m_value = TextEntryScreen::Run(m_base->m_display_name, m_value);\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\n\ntemplate<>\nconst std::string& ConfigOption_t<true, std::string>::get_display_value(std::string& out) const\n{\n    if(m_value.empty())\n        return (out = \"<NONE>\");\n\n    out = m_value;\n    return out;\n}\n\n// specializer for resolution\ntemplate<>\nconst std::string& ConfigOption_t<true, std::pair<int, int>>::get_display_value(std::string& out) const\n{\n    out = fmt::sprintf_ne(\"%dx%d\", m_value.first, m_value.second);\n    return out;\n}\n\ntemplate<>\nvoid ConfigOption_t<true, std::pair<int, int>>::update_from_ini(IniProcessing* ini, ConfigSetLevel level)\n{\n    if(level < m_set)\n        return;\n\n    if(!ini || !m_base || !m_base->m_internal_name)\n        return;\n\n    if(!ini->hasKey(m_base->m_internal_name))\n        return;\n\n    std::string read_value;\n    ini->read(m_base->m_internal_name, read_value, read_value);\n\n    int arg1, h;\n    int argcount = sscanf(read_value.c_str(), \"%dx%d\", &arg1, &h);\n\n    if(argcount < 1)\n        return;\n\n    if(argcount == 2)\n        *this = {arg1, h};\n    else\n        *this = {0, arg1};\n\n    m_set = level;\n}\n\ntemplate<>\nvoid ConfigOption_t<true, std::pair<int, int>>::save_to_ini(IniProcessing* ini)\n{\n    if(!ini || !m_base || !m_base->m_internal_name)\n        return;\n\n    if(is_set())\n    {\n        std::string out = fmt::sprintf_ne(\"%dx%d\", m_value.first, m_value.second);\n        ini->setValue(m_base->m_internal_name, out);\n    }\n    else\n        ini->clearValue(m_base->m_internal_name);\n}\n\n// specializer for the bitblit triple\ntemplate<>\nconst std::string& ConfigOption_t<true, std::array<uint8_t, 3>>::get_display_value(std::string& out) const\n{\n    out = fmt::sprintf_ne(\"%u,%u,%u\", (unsigned)m_value[0], (unsigned)m_value[1], (unsigned)m_value[2]);\n    return out;\n}\n\ntemplate<>\nvoid ConfigOption_t<true, std::array<uint8_t, 3>>::update_from_ini(IniProcessing* ini, ConfigSetLevel level)\n{\n    if(!ini || !m_base || !m_base->m_internal_name)\n        return;\n\n    if(!ini->hasKey(m_base->m_internal_name))\n        return;\n\n    *this = {0, 0, 0};\n\n    m_set = level;\n}\n\ntemplate<>\nvoid ConfigOption_t<true, std::array<uint8_t, 3>>::save_to_ini(IniProcessing* ini)\n{\n    if(!ini || !m_base || !m_base->m_internal_name)\n        return;\n\n    if(is_set())\n    {\n        ini->setValue(m_base->m_internal_name, \"unimplemented\");\n    }\n    else\n        ini->clearValue(m_base->m_internal_name);\n}\n\n// specialized implementation for ConfigOption_t<true, bool>\n\ntemplate<>\nbool ConfigOption_t<true, bool>::rotate_left()\n{\n    m_value = !m_value;\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\ntemplate<>\nbool ConfigOption_t<true, bool>::rotate_right()\n{\n    return rotate_left();\n}\n\ntemplate<>\nconst std::string& ConfigOption_t<true, bool>::get_display_value(std::string& out) const\n{\n    if(FontManager::hasTtfSupport())\n        out = m_value ? \"✓\" : \"×\";\n    else\n        out = m_value ? \"+\" : \"-\";\n\n    return out;\n}\n\n\n// implementation for ConfigEnumOption_t<false, value_t>\n\ntemplate<class value_t>\nvoid ConfigEnumOption_t<false, value_t>::make_translation(XTechTranslate& translate, const char* cur_section_id)\n{\n    if((ConfigOption_t<false, value_t>::m_scope & Options_t::Scope::MakeTranslation) == 0)\n        return;\n\n    XTechTranslate::insert(translate.m_engineMap, fmt::sprintf_ne(\"menu.options.%s.%s._name\", cur_section_id, m_internal_name), &m_display_name);\n\n    // some obscure behavior of the GCC version in Ubuntu 16.04 necessitates qualifying m_display_tooltip with the base class scope\n    if(!BaseConfigOption_t<false>::m_display_tooltip.empty())\n        XTechTranslate::insert(translate.m_engineMap, fmt::sprintf_ne(\"menu.options.%s.%s._tooltip\", cur_section_id, m_internal_name), &m_display_tooltip);\n\n    for(auto& value : m_enum_values)\n    {\n        if(!value.m_is_translatable)\n            continue;\n\n        if(!value.m_display_name.empty())\n            XTechTranslate::insert(translate.m_engineMap, fmt::sprintf_ne(\"menu.options.%s.%s.%s\", cur_section_id, m_internal_name, value.m_internal_name), &value.m_display_name);\n\n        if(!value.m_display_tooltip.empty())\n            XTechTranslate::insert(translate.m_engineMap, fmt::sprintf_ne(\"menu.options.%s.%s.%s-tip\", cur_section_id, m_internal_name, value.m_internal_name), &value.m_display_tooltip);\n    }\n}\n\n\n// implementation for ConfigEnumOption_t<true, value_t>\n\ntemplate<class value_t>\nvoid ConfigEnumOption_t<true, value_t>::update_from_ini(IniProcessing* ini, ConfigSetLevel level)\n{\n    if(level < m_set)\n        return;\n\n    if(!ini || !m_base || !m_base->m_internal_name)\n        return;\n\n    const ConfigEnumOption_t<false, value_t>* base = dynamic_cast<const ConfigEnumOption_t<false, value_t>*>(m_base);\n\n    if(!base)\n        return;\n\n    if(!ini->hasKey(base->m_internal_name))\n        return;\n\n    std::string key_name;\n    ini->read(base->m_internal_name, key_name, key_name);\n\n    for(const ConfigEnumValueInfo_t<value_t>& val : base->m_enum_values)\n    {\n        if(!strcmp(key_name.c_str(), val.m_internal_name))\n        {\n            ConfigOption_t<true, value_t>::operator=(val.m_value);\n            m_set = level;\n            return;\n        }\n    }\n\n    ConfigOption_t<true, value_t>::operator=(base->m_default_value);\n    m_set = level;\n}\n\ntemplate<class value_t>\nvoid ConfigEnumOption_t<true, value_t>::save_to_ini(IniProcessing* ini)\n{\n    if(!ini || !m_base || !m_base->m_internal_name)\n        return;\n\n    if(!is_set())\n    {\n        ini->clearValue(m_base->m_internal_name);\n        return;\n    }\n\n    const ConfigEnumOption_t<false, value_t>* base = dynamic_cast<const ConfigEnumOption_t<false, value_t>*>(m_base);\n\n    if(!base)\n        return;\n\n    for(const ConfigEnumValueInfo_t<value_t>& val : base->m_enum_values)\n    {\n        if((value_t)*this == val.m_value)\n        {\n            ini->setValue(base->m_internal_name, val.m_internal_name);\n            return;\n        }\n    }\n\n    ConfigOption_t<true, value_t>::save_to_ini(ini);\n}\n\ntemplate<class value_t>\nbool ConfigEnumOption_t<true, value_t>::rotate_left()\n{\n    const ConfigEnumOption_t<false, value_t>* base = dynamic_cast<const ConfigEnumOption_t<false, value_t>*>(m_base);\n\n    if(!base || base->m_enum_values.size() > 255)\n        return false;\n\n    uint8_t found_i = 0;\n\n    for(uint8_t i = 0; i < base->m_enum_values.size(); i++)\n    {\n        if((value_t)*this == base->m_enum_values[i].m_value)\n        {\n            found_i = i;\n            break;\n        }\n    }\n\n    uint8_t current_i = found_i;\n\n    // loop until we find one with a display name -> not an alias\n    do\n    {\n        if(current_i == 0)\n            current_i = base->m_enum_values.size();\n\n        current_i--;\n    }\n    while(base->m_enum_values[current_i].m_display_name.empty() && current_i != found_i);\n\n    // possibly misconfigured\n    if(current_i == found_i)\n    {\n        pLogDebug(\"Cycled through enum option [%s] without finding new value\", base->m_display_name.c_str());\n        return false;\n    }\n\n    m_value = base->m_enum_values[current_i].m_value;\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\ntemplate<class value_t>\nbool ConfigEnumOption_t<true, value_t>::rotate_right()\n{\n    const ConfigEnumOption_t<false, value_t>* base = dynamic_cast<const ConfigEnumOption_t<false, value_t>*>(m_base);\n\n    if(!base || base->m_enum_values.size() > 255)\n        return false;\n\n    uint8_t found_i = 0;\n\n    for(uint8_t i = 0; i < base->m_enum_values.size(); i++)\n    {\n        if((value_t)*this == base->m_enum_values[i].m_value)\n        {\n            found_i = i;\n            break;\n        }\n    }\n\n    uint8_t current_i = found_i;\n\n    // loop until we find one with a display name -> not an alias\n    do\n    {\n        current_i++;\n\n        if(current_i == base->m_enum_values.size())\n            current_i = 0;\n    }\n    while(base->m_enum_values[current_i].m_display_name.empty() && current_i != found_i);\n\n    // possibly misconfigured\n    if(current_i == found_i)\n    {\n        pLogDebug(\"Cycled through enum option [%s] without finding new value\", base->m_display_name.c_str());\n        return false;\n    }\n\n    m_value = base->m_enum_values[current_i].m_value;\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\ntemplate<class value_t>\nconst std::string& ConfigEnumOption_t<true, value_t>::get_display_value(std::string& out) const\n{\n    const ConfigEnumOption_t<false, value_t>* base = dynamic_cast<const ConfigEnumOption_t<false, value_t>*>(m_base);\n\n    if(!base)\n        return out = \"INVALID\";\n\n    bool found = false;\n\n    for(const ConfigEnumValueInfo_t<value_t>& val : base->m_enum_values)\n    {\n        if((value_t)*this == val.m_value)\n        {\n            if(!val.m_display_name.empty())\n                out = val.m_display_name;\n            else if(val.m_internal_name)\n                out = val.m_internal_name;\n            else\n                continue;\n\n            found = true;\n            break;\n        }\n    }\n\n    if(!found)\n        ConfigOption_t<true, value_t>::get_display_value(out);\n\n    return out;\n}\n\ntemplate<class value_t>\nconst std::string& ConfigEnumOption_t<true, value_t>::get_value_tooltip(std::string& out) const\n{\n    const ConfigEnumOption_t<false, value_t>* base = dynamic_cast<const ConfigEnumOption_t<false, value_t>*>(m_base);\n\n    if(!base)\n        return out = \"\";\n\n    for(const ConfigEnumValueInfo_t<value_t>& val : base->m_enum_values)\n    {\n        if((value_t)*this == val.m_value)\n        {\n            if(!val.m_display_tooltip.empty())\n                return out = val.m_display_tooltip;\n            else\n                return out = \"\";\n        }\n    }\n\n    return out = \"\";\n}\n\n\n// implementation for ConfigRangeOption_t<true, value_t>\n\ntemplate<class value_t>\nbool ConfigRangeOption_t<true, value_t>::rotate_left()\n{\n    const ConfigRangeOption_t<false, value_t>* base = dynamic_cast<const ConfigRangeOption_t<false, value_t>*>(m_base);\n\n    if(!base)\n        return false;\n\n    m_value -= base->m_range_info.step;\n    if(m_value < base->m_range_info.min)\n        m_value = base->m_range_info.max;\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\ntemplate<class value_t>\nbool ConfigRangeOption_t<true, value_t>::rotate_right()\n{\n    const ConfigRangeOption_t<false, value_t>* base = dynamic_cast<const ConfigRangeOption_t<false, value_t>*>(m_base);\n\n    if(!base)\n        return false;\n\n    m_value += base->m_range_info.step;\n    if(m_value > base->m_range_info.max)\n        m_value = base->m_range_info.min;\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\n\n// implementation for ConfigLanguage_t<true>\n\nbool ConfigLanguage_t<true>::rotate_left()\n{\n    std::string old_value = m_value;\n    XLanguage::rotateLanguage(m_value, -1);\n\n    if(m_value == old_value)\n        return false;\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\nbool ConfigLanguage_t<true>::rotate_right()\n{\n    std::string old_value = m_value;\n    XLanguage::rotateLanguage(m_value, 1);\n\n    if(m_value == old_value)\n        return false;\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\nbool ConfigLanguage_t<true>::change()\n{\n    return rotate_right();\n}\n\nconst std::string& ConfigLanguage_t<true>::get_display_value(std::string& out) const\n{\n    out = g_mainMenu.languageName;\n    out += \" (\";\n    out += m_value;\n    out += \")\";\n\n    return out;\n}\n\n\n// implementation for ConfigFullscreenRes_t<true>\n#ifdef RENDER_FULLSCREEN_TYPES_SUPPORTED\n\nsize_t ConfigFullscreenRes_t<true>::find_cur_index()\n{\n    const std::vector<AbstractWindow_t::VideoModeRes>& res = g_window->getAvailableVideoResolutions();\n\n    for(size_t i = 0; i < res.size(); i++)\n    {\n        if(res[i].w == m_value.first && res[i].h == m_value.second)\n            return i;\n    }\n\n    return res.size();\n}\n\nbool ConfigFullscreenRes_t<true>::rotate_left()\n{\n    const std::vector<AbstractWindow_t::VideoModeRes>& res = g_window->getAvailableVideoResolutions();\n\n    value_t old_value = m_value;\n\n    if(res.size() == 0)\n        return false;\n\n    size_t new_idx = find_cur_index() - 1;\n    if(new_idx >= res.size())\n        new_idx = res.size() - 1;\n\n    m_value.first = res[new_idx].w;\n    m_value.second = res[new_idx].h;\n\n    if(m_value == old_value)\n        return false;\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\nbool ConfigFullscreenRes_t<true>::rotate_right()\n{\n    const std::vector<AbstractWindow_t::VideoModeRes>& res = g_window->getAvailableVideoResolutions();\n\n    value_t old_value = m_value;\n\n    if(res.size() == 0)\n        return false;\n\n    size_t new_idx = find_cur_index() + 1;\n    if(new_idx >= res.size())\n        new_idx = 0;\n\n    m_value.first = res[new_idx].w;\n    m_value.second = res[new_idx].h;\n\n    if(m_value == old_value)\n        return false;\n\n    if(!is_set())\n        m_set = ConfigSetLevel::set;\n\n    _on_change();\n\n    return true;\n}\n\nbool ConfigFullscreenRes_t<true>::change()\n{\n    return rotate_right();\n}\n\n#endif // #ifdef RENDER_FULLSCREEN_TYPES_SUPPORTED\n\n\n// implementation for ConfigSetupEnum_t<true>\nconst std::string& ConfigSetupEnum_t<true>::get_display_value(std::string& out) const\n{\n    ConfigEnumOption_t<true, int>::get_display_value(out);\n\n#ifndef RENDER_CUSTOM\n    const ConfigEnumOption_t<false, int>* base = dynamic_cast<const ConfigEnumOption_t<false, int>*>(m_base);\n\n    // special case for \"auto\" values (always 0)\n    if(base && m_value == 0 && obtained > 0)\n    {\n        out += \" (\";\n\n        for(const ConfigEnumValueInfo_t<int>& val : base->m_enum_values)\n        {\n            if(obtained == val.m_value)\n            {\n                if(!val.m_display_name.empty())\n                    out += val.m_display_name;\n                else if(val.m_internal_name)\n                    out += val.m_internal_name;\n                else\n                    continue;\n\n                break;\n            }\n        }\n\n        out += \")\";\n    }\n#endif\n\n    return out;\n}\n\n\n// implementation for ConfigEnumOption_t<true, value_t>\n\n// implementation for ConfigSection_t<true>\n\nConfigSection_t<true>::ConfigSection_t(_Config_t<true>* parent, uint8_t scope,\n        const char* internal_name, const char* display_name, const char* display_tooltip) :\n        BaseConfigOption_t<true>(parent)\n{\n    (void)internal_name; (void)display_name; (void)display_tooltip; (void)scope;\n\n    if(!parent->m_base)\n        return;\n    if((size_t)m_index >= parent->m_base->m_options.size())\n        return;\n    m_base = dynamic_cast<ConfigSection_t<false>*>(parent->m_base->m_options[m_index]);\n}\n\nvoid ConfigSection_t<true>::update_from_ini(IniProcessing* ini, ConfigSetLevel level)\n{\n    (void)level;\n\n    if(!ini || !m_base)\n        return;\n\n    ini->endGroup();\n    ini->beginGroup(m_base->m_internal_name);\n}\n\n// implementation for ConfigSubSection_t<true>\n\nConfigSubSection_t<true>::ConfigSubSection_t(_Config_t<true>* parent,\n        const char* internal_name, const char* display_name, const char* display_tooltip) :\n        BaseConfigOption_t(parent)\n{\n    (void)internal_name; (void)display_name; (void)display_tooltip;\n\n    if(!parent->m_base)\n        return;\n    if((size_t)m_index >= parent->m_base->m_options.size())\n        return;\n    m_base = dynamic_cast<ConfigSubSection_t<false>*>(parent->m_base->m_options[m_index]);\n}\n\n#endif // #CONFIG_IMPL_HPP\n"
  },
  {
    "path": "src/config/config_legacy_compat.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <IniProcessor/ini_processing.h>\n#include <Logger/logger.h>\n#include <fmt_format_ne.h>\n\n#include \"globals.h\"\n#include \"config.h\"\n\n#include \"main/menu_main.h\"\n#include \"main/screen_prompt.h\"\n\ntemplate<>\nvoid Config_t::LoadLegacyCompat(IniProcessing* ini, ConfigSetLevel level)\n{\n    ini->beginGroup(\"compatibility\");\n    {\n        std::string buffer;\n        if(ini->hasKey(\"enable-last-warp-hub-resume\"))\n        {\n            ini->read(\"enable-last-warp-hub-resume\", enable_last_warp_hub_resume.m_value, true);\n            enable_last_warp_hub_resume.m_set = level;\n        }\n    }\n    ini->endGroup();\n\n    if(!ini->beginGroup(\"fails-counter\"))\n    {\n        ini->beginGroup(\"death-counter\"); // Backup fallback\n    }\n    {\n        std::string buffer;\n        if(ini->hasKey(\"enabled\"))\n        {\n            ini->read(\"enabled\", enable_fails_tracking.m_value, true);\n            enable_fails_tracking.m_set = level;\n        }\n\n        // note: title attribute has been removed, now belongs to gameinfo.ini only.\n    }\n    ini->endGroup();\n\n    ini->beginGroup(\"luna-script\");\n    {\n        std::string enable_luna_value;\n        ini->read(\"enable-engine\", enable_luna_value, \"unspecified\");\n        if(enable_luna_value != \"unspecified\")\n        {\n            luna_enable_engine = (enable_luna_value == \"enable\" || enable_luna_value == \"true\");\n            luna_enable_engine.m_set = level;\n        }\n\n        if(ini->hasKey(\"allow-level-codes\"))\n        {\n            ini->read(\"allow-level-codes\", luna_allow_level_codes.m_value, false);\n            luna_allow_level_codes.m_set = level;\n        }\n    }\n    ini->endGroup();\n}\n"
  },
  {
    "path": "src/config/config_main.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <PGE_File_Formats/file_formats.h>\n#include <IniProcessor/ini_processing.h>\n#include <Utils/files_ini.h>\n#include <fmt_format_ne.h>\n\n#include \"config.h\"\n#include \"message.h\"\n#include \"config/config_impl.hpp\"\n#include \"main/game_info.h\"\n#include \"globals.h\"\n#include \"global_dirs.h\"\n#include \"main/menu_main.h\"\n#include \"sound.h\"\n\nstatic int s_backup_bugfixes = Config_t::MODE_MODERN;\nstatic int s_backup_creator_compat = Config_t::CREATORCOMPAT_ENABLE;\n\ntemplate<>\nvoid Config_t::Clear()\n{\n    // only update backup variables for main config class\n    if(this == &g_config)\n    {\n        if(playstyle.m_set == ConfigSetLevel::ep_config)\n            s_backup_bugfixes = playstyle;\n\n        if(GameMenu)\n            s_backup_creator_compat = Config_t::CREATORCOMPAT_ENABLE;\n        else\n        {\n            if(creator_compat.m_set == ConfigSetLevel::ep_config)\n                s_backup_creator_compat = creator_compat;\n        }\n    }\n\n    for(BaseConfigOption_t<true>* option : m_options)\n    {\n        // allow speedrun mode to be kept throughout a menu quit\n        if(option == &speedrun_mode)\n            continue;\n\n        switch(option->m_set)\n        {\n        case ConfigSetLevel::cmdline:\n        case ConfigSetLevel::debug:\n            // allow these levels to always be kept\n            break;\n        case ConfigSetLevel::ep_config:\n        case ConfigSetLevel::cheat:\n            // allow these levels to be kept for the duration of a run\n            if(!GameMenu)\n                break;\n            // fallthrough\n        default:\n            option->unset();\n        }\n    }\n\n    // restore episode config\n    playstyle.m_value = s_backup_bugfixes;\n    playstyle.m_set = ConfigSetLevel::ep_config;\n\n    creator_compat.m_value = s_backup_creator_compat;\n    creator_compat.m_set = ConfigSetLevel::ep_config;\n}\n\ntemplate<>\nvoid Config_t::UpdateFromIni(IniProcessing* ini, ConfigSetLevel level)\n{\n    for(BaseConfigOption_t<true>* option : m_options)\n        option->update_from_ini(ini, level);\n}\n\ntemplate<>\nvoid Config_t::SaveToIni(IniProcessing* ini)\n{\n    for(BaseConfigOption_t<true>* option : m_options)\n        option->save_to_ini(ini);\n}\n\ntemplate<>\nvoid Config_t::SaveEpisodeConfig(saveUserData& userdata)\n{\n    saveUserData::DataSection ep_config;\n    ep_config.name = \"_xt_ep_conf\";\n    ep_config.location = saveUserData::DATA_GLOBAL;\n\n    if(speedrun_mode.m_set >= ConfigSetLevel::ep_config)\n        ep_config.data.emplace_back(saveUserData::DataEntry{\"speedrun-mode\", std::to_string(speedrun_mode.m_value)});\n\n    int playstyle_value = (playstyle.m_set == ConfigSetLevel::ep_config) ? playstyle : s_backup_bugfixes;\n    ep_config.data.emplace_back(saveUserData::DataEntry{\"playstyle\", (playstyle_value == MODE_VANILLA) ? \"vanilla\" : ((playstyle_value == MODE_CLASSIC) ? \"classic\" : \"modern\")});\n\n    int creator_compat_value = (creator_compat.m_set == ConfigSetLevel::ep_config) ? creator_compat : s_backup_creator_compat;\n    ep_config.data.emplace_back(saveUserData::DataEntry{\"creator-compat\", (creator_compat_value == CREATORCOMPAT_DISABLE) ? \"disable\" : ((creator_compat_value == CREATORCOMPAT_FILEONLY) ? \"file-only\" : \"enable\")});\n\n    if(enable_last_warp_hub_resume.m_set == ConfigSetLevel::ep_config)\n        ep_config.data.emplace_back(saveUserData::DataEntry{\"hub-resume\", (enable_last_warp_hub_resume.m_value) ? \"on\" : \"off\"});\n\n    userdata.store.push_back(ep_config);\n}\n\ntemplate<>\nvoid Config_t::LoadEpisodeConfig(const saveUserData& userdata)\n{\n    for(const auto& section : userdata.store)\n    {\n        if(section.name != \"_xt_ep_conf\" || section.location != saveUserData::DATA_GLOBAL)\n            continue;\n\n        for(const auto& entry : section.data)\n        {\n            if(entry.key == \"speedrun-mode\")\n            {\n                if(speedrun_mode.m_set <= ConfigSetLevel::ep_config)\n                {\n                    speedrun_mode.m_value = std::atoi(entry.value.c_str());\n                    if(speedrun_mode.m_value < 0 || speedrun_mode.m_value > 4)\n                        speedrun_mode.m_value = 0;\n\n                    speedrun_mode.m_set = ConfigSetLevel::ep_config;\n                }\n            }\n            else if(entry.key == \"playstyle\")\n            {\n                if(entry.value == \"vanilla\")\n                    playstyle.m_value = MODE_VANILLA;\n                else if(entry.value == \"classic\")\n                    playstyle.m_value = MODE_CLASSIC;\n                else\n                    playstyle.m_value = MODE_MODERN;\n\n                playstyle.m_set = ConfigSetLevel::ep_config;\n            }\n            else if(entry.key == \"creator-compat\")\n            {\n                if(entry.value == \"disable\")\n                    creator_compat.m_value = CREATORCOMPAT_DISABLE;\n                else if(entry.value == \"file-only\")\n                    creator_compat.m_value = CREATORCOMPAT_FILEONLY;\n                else\n                    creator_compat.m_value = CREATORCOMPAT_ENABLE;\n\n                creator_compat.m_set = ConfigSetLevel::ep_config;\n            }\n            else if(entry.key == \"hub-resume\")\n            {\n                if(entry.value == \"off\")\n                    enable_last_warp_hub_resume.m_value = false;\n                else\n                    enable_last_warp_hub_resume.m_value = true;\n\n                enable_last_warp_hub_resume.m_set = ConfigSetLevel::ep_config;\n            }\n        }\n\n        break;\n    }\n}\n\ntemplate<>\nvoid Config_t::SetFromDefaults(ConfigSetLevel level)\n{\n    for(BaseConfigOption_t<true>* option : m_options)\n        option->set_from_default(level);\n}\n\ntemplate<>\nvoid Config_t::DisableBugfixes(ConfigSetLevel level)\n{\n    for(BaseConfigOption_t<true>* option : m_options)\n        option->disable_bugfixes(level);\n}\n\ntemplate<>\nvoid Config_t::UpdateFrom(const Config_t& o, ConfigSetLevel level)\n{\n    size_t i = 0;\n    for(BaseConfigOption_t<true>* option : m_options)\n    {\n        if(i >= o.m_options.size())\n            break;\n\n        if(m_base->m_options[i] && (m_base->m_options[i]->m_scope & o.m_scope) && o.m_options[i])\n            option->update_from(*o.m_options[i], level);\n\n        i++;\n    }\n}\n\ntemplate<>\nvoid Config_t::UpdateFromCompat(CompatMode compat_mode, ConfigSetLevel level)\n{\n    for(BaseConfigOption_t<true>* option : m_options)\n        option->update_from_compat(compat_mode, level);\n}\n\ntemplate<>\nvoid Options_t::reset_options()\n{\n    // invoke destructor\n    this->~Options_t();\n\n    // invoke constructor\n    new(this) Options_t();\n}\n\ntemplate<>\nvoid Options_t::make_translation(XTechTranslate& translate)\n{\n    const char* cur_section_id = \"\";\n\n    for(BaseConfigOption_t<false>* option : m_options)\n    {\n        bool is_section = dynamic_cast<ConfigSection_t<false>*>(option);\n\n        // switch to the section's header\n        if(is_section)\n            cur_section_id = option->m_internal_name;\n\n        option->make_translation(translate, cur_section_id);\n    }\n}\n\nstatic void s_GameInfoActivitySetup(IniProcessing& ini)\n{\n    if(ini.contains(\"activity-setup\") && (GameOutro || GameMenu))\n    {\n        if(!g_gameInfo.activity_settings_in_compat)\n        {\n            g_gameInfo.ResetIntroActivitySettings();\n            g_gameInfo.ResetOutroActivitySettings();\n            g_gameInfo.activity_settings_in_compat = true;\n        }\n\n        ini.beginGroup(\"activity-setup\");\n\n        if(GameOutro)\n            g_gameInfo.LoadOutroActivitySettings(ini);\n        else if(GameMenu)\n            g_gameInfo.LoadIntroActivitySettings(ini);\n\n        ini.endGroup();\n    }\n}\n\nvoid LoadCustomConfig()\n{\n    std::string episodeCompat, customCompat, episodeConfig, customConfig, userEpConfig;\n\n    g_dirEpisode.setCurDir(FileNamePath);\n    g_dirCustom.setCurDir(FileNamePath + FileName);\n\n    g_config_episode_creator.Clear();\n    g_config_file_creator.Clear();\n\n    // Episode-wide custom creator setup\n    episodeCompat = g_dirEpisode.resolveFileCaseExistsAbs(\"compat.ini\");\n    // Level-wide custom creator setup\n    customCompat = g_dirCustom.resolveFileCaseExistsAbs(\"compat.ini\");\n\n    if(g_gameInfo.activity_settings_in_compat)\n    {\n        g_gameInfo.ResetIntroActivitySettings();\n        g_gameInfo.ResetOutroActivitySettings();\n    }\n\n    if(!episodeCompat.empty())\n    {\n        IniProcessing ini = Files::load_ini(episodeCompat);\n        g_config_episode_creator.LoadLegacyCompat(&ini, ConfigSetLevel::ep_compat);\n        g_config_episode_creator.UpdateFromIni(&ini, ConfigSetLevel::ep_compat);\n\n        // pass activity-setup keys to GameInfo\n        s_GameInfoActivitySetup(ini);\n    }\n\n    if(!customCompat.empty())\n    {\n        IniProcessing ini = Files::load_ini(customCompat);\n        g_config_file_creator.LoadLegacyCompat(&ini, ConfigSetLevel::file_compat);\n        g_config_file_creator.UpdateFromIni(&ini, ConfigSetLevel::file_compat);\n\n        // pass activity-setup keys to GameInfo\n        s_GameInfoActivitySetup(ini);\n    }\n\n    UpdateConfig();\n}\n\nvoid ResetCustomConfig()\n{\n    g_config_episode_creator.Clear();\n    g_config_file_creator.Clear();\n\n    UpdateConfig();\n}\n\nvoid UpdateConfig()\n{\n    // do not allow UpdateConfig to call itself recursively\n    static Config_t g_config_backup(g_options, Config_t::Scope::All);\n    static bool extra_request = false;\n\n    if(g_configInTransaction)\n    {\n        extra_request = true;\n        return;\n    }\n\n    // initialize transaction\n    g_configInTransaction = true;\n\n    g_config_backup.Clear();\n    g_config_backup.UpdateFrom(g_config);\n\n    // start filling in the config\n    g_config.Clear();\n    g_config.SetFromDefaults(ConfigSetLevel::game_defaults);\n\n    // scoping for a sentinel for audio defaults\n    {\n        ConfigChangeSentinel sent(ConfigSetLevel::game_defaults);\n\n#ifndef THEXTECH_NO_SDL_BUILD\n        g_config.audio_channels = g_audioDefaults.channels;\n        g_config.audio_sample_rate = g_audioDefaults.sampleRate;\n        g_config.audio_buffer_size = g_audioDefaults.bufferSize;\n        g_config.audio_format = g_audioDefaults.format;\n#endif\n    }\n\n    // load from all levels of config\n    g_config.UpdateFrom(g_config_game_user, ConfigSetLevel::user_config);\n\n    g_config.DisableBugfixes(ConfigSetLevel::bugfix_defaults);\n\n    if(g_config.creator_compat == Config_t::CREATORCOMPAT_ENABLE)\n        g_config.UpdateFrom(g_config_episode_creator, ConfigSetLevel::ep_compat);\n\n    if(g_config.creator_compat != Config_t::CREATORCOMPAT_DISABLE)\n        g_config.UpdateFrom(g_config_file_creator, ConfigSetLevel::file_compat);\n\n    // also do the speedrun, including compat...\n    if(g_config.speedrun_mode >= 1) // Always show FPS and don't pause the game work when focusing other windows\n    {\n        ConfigChangeSentinel sent(ConfigSetLevel::speedrun);\n\n#ifndef NO_WINDOW_FOCUS_TRACKING\n        g_config.background_work = true;\n#endif\n        g_config.show_fps = true;\n        g_config.enable_frameskip = false;\n        if(g_config.speedrun_mode == 1)\n            g_config.compatibility_mode = Config_t::COMPAT_MODERN;\n        else\n            g_config.compatibility_mode = g_config.speedrun_mode - 1;\n        g_config.enable_playtime_tracking = true;\n    }\n\n    if(XMessage::GetStatus() != XMessage::Status::local)\n    {\n        ConfigChangeSentinel sent(ConfigSetLevel::speedrun);\n\n#ifndef NO_WINDOW_FOCUS_TRACKING\n        g_config.background_work = true;\n#endif\n\n        g_config.enable_frameskip = true;\n        g_config.unlimited_framerate = true;\n        // g_config.render_vsync = false;\n    }\n\n    if(g_config.compatibility_mode)\n    {\n        ConfigChangeSentinel sent(ConfigSetLevel::compat);\n\n        CompatMode compat_mode = CompatMode::off;\n\n        if(g_config.compatibility_mode == Config_t::COMPAT_SMBX13)\n        {\n            compat_mode = CompatMode::smbx64;\n            g_config.playstyle = Config_t::MODE_VANILLA;\n        }\n        else if(g_config.compatibility_mode == Config_t::COMPAT_CLASSIC)\n        {\n            compat_mode = CompatMode::classic;\n            g_config.playstyle = Config_t::MODE_CLASSIC;\n        }\n        else if(g_config.compatibility_mode == Config_t::COMPAT_MODERN)\n        {\n            compat_mode = CompatMode::modern;\n            g_config.playstyle = Config_t::MODE_MODERN;\n        }\n\n        g_config.UpdateFromCompat(compat_mode, ConfigSetLevel::compat);\n    }\n\n    for(const BaseConfigOption_t<true>* changed_opt : g_configModified)\n    {\n        size_t index = std::find(g_config.m_options.begin(), g_config.m_options.end(), changed_opt) - g_config.m_options.begin();\n        if(index >= g_config_backup.m_options.size())\n            continue;\n\n        if(!(*changed_opt == *g_config_backup.m_options[index]))\n            changed_opt->m_base->m_onupdate();\n    }\n\n    g_configModified.clear();\n    g_configInTransaction = false;\n\n    if(extra_request)\n    {\n        extra_request = 0;\n        UpdateConfig();\n    }\n}\n\nbool g_configInTransaction = false;\nstd::set<BaseConfigOption_t<true>*> g_configModified;\n\nOptions_t g_options;\n\n// <defaults>                                                                     1\nConfig_t g_config_game_user(g_options, Config_t::Scope::Config);               // 3\nConfig_t g_config_episode_creator(g_options, Config_t::Scope::CreatorEpisode); // 4\nConfig_t g_config_file_creator(g_options, Config_t::Scope::CreatorFile);       // 6\n// <compat>                                                                       9\n\n// const std::vector<const Config_t*> g_config_levels =\n// {\n//     &g_gameInfo, &g_config_game_user,\n//     &g_config_episode_creator, &g_config_episode_user,\n//     &g_config_file_creator, &g_config_file_user,\n//     &g_config_cmdline\n// };\n\nConfig_t g_config(g_options, Config_t::Scope::All);\n"
  },
  {
    "path": "src/config.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CONFIG_MAIN_H\n#define CONFIG_MAIN_H\n\n#include <PGE_File_Formats/file_formats.h>\n\n#include \"sdl_proxy/sdl_audio.h\"\n#include \"sound.h\"\n#include \"screen.h\"\n\n#include \"Logger/logger_level.h\"\n\n#include \"config/config_base.hpp\"\n#include \"config/config_hooks.h\"\n\nstruct saveUserData;\n\ntemplate<bool writable>\nclass _Config_t\n{\npublic:\n    struct Scope\n    {\n        enum\n        {\n            None = 0,\n            // UserGlobal = 1 << 0,\n            // UserEpisode = 1 << 1,\n            // UserFile = 1 << 2,\n\n            // UserLocal = UserEpisode | UserFile,\n            Config = 1 << 0,\n            EpisodeOptions = 1 << 1,\n\n            UserVisible = Config | EpisodeOptions,\n\n            // Assets = 1 << 2,\n\n            CreatorEpisode = 1 << 3,\n            CreatorFile = 1 << 4,\n\n            Creator = CreatorEpisode | CreatorFile,\n            // Creator = CreatorGlobal | CreatorLocal,\n\n            TranslateOnly = 1 << 5,\n\n            MakeTranslation = UserVisible | TranslateOnly,\n\n            // Episode = UserEpisode | CreatorEpisode,\n            // File = UserFile | CreatorFile,\n            // Local = Episode | File,\n            // Global = UserGlobal | CreatorGlobal,\n            All = 0xff,\n        };\n    };\n\nprotected:\n    template<class value_t> using opt = ConfigOption_t<writable, value_t>;\n    template<class value_t> using opt_enum = ConfigEnumOption_t<writable, value_t>;\n    template<class value_t> using opt_range = ConfigRangeOption_t<writable, value_t>;\n    using section = ConfigSection_t<writable>;\n    using subsection = ConfigSubSection_t<writable>;\n    using language_t = ConfigLanguage_t<writable>;\n    using setup_enum_t = ConfigSetupEnum_t<writable>;\n\n    template<class value_t>\n    static constexpr value_t defaults(value_t value)\n    {\n        return value;\n    }\n\npublic:\n    const _Config_t<false>* m_base = nullptr;\n    std::vector<BaseConfigOption_t<writable>*> m_options;\n    uint8_t m_scope = Scope::All;\n\n    _Config_t() {}\n    _Config_t(const _Config_t<false>& base, uint8_t scope) : m_base(&base), m_scope(scope) {}\n\n    void Clear();\n\n    void LoadLegacyCompat(IniProcessing* ini, ConfigSetLevel level = ConfigSetLevel::set);\n\n    void SetFromDefaults(ConfigSetLevel level = ConfigSetLevel::set);\n\n    void DisableBugfixes(ConfigSetLevel level = ConfigSetLevel::set);\n\n    void UpdateFromIni(IniProcessing* ini, ConfigSetLevel level = ConfigSetLevel::set);\n    void UpdateFrom(const _Config_t<true>& other, ConfigSetLevel level = ConfigSetLevel::set);\n    void UpdateFromCompat(CompatMode compat_mode, ConfigSetLevel level = ConfigSetLevel::set);\n\n    void LoadEpisodeConfig(const saveUserData& userdata);\n\n    void SaveToIni(IniProcessing* ini);\n    void SaveEpisodeConfig(saveUserData& userdata);\n\n    // for translations (note: you'll get a linker error if you invoke these on a writable Config_t.)\n    void reset_options();\n    void make_translation(XTechTranslate& translate);\n\n\n    /* ---- Main ----*/\n    section main{this, Scope::All, \"main\", \"Main\", nullptr};\n\n    language_t language{this, defaults(std::string(\"auto\")), {}, Scope::Config,\n        \"language\", \"Language\", nullptr,\n        config_language_set};\n\n#ifdef ENABLE_XTECH_DISCORD_RPC\n    opt<bool> discord_rpc{this, defaults(false), {}, Scope::Config,\n        \"discord-rpc\", \"Discord Integration\", nullptr,\n        config_integrations_set};\n#endif\n\n#ifndef NO_WINDOW_FOCUS_TRACKING\n    opt<bool> background_work{this, defaults(false), {}, Scope::Config,\n        \"background-work\", \"Run in background\", \"Play with joystick while game is unfocused\"};\n#else\n    static constexpr bool background_work = true;\n#endif\n\n    static constexpr bool enable_editor = true;\n\n    /* ---- Main - Frame Timing ----*/\n    subsection main_frame_timing{this, \"timing\", \"Frame Timing\"};\n\n    opt<bool> enable_frameskip{this, defaults(false), {}, Scope::Config,\n        \"frame-skip\", \"Frameskip\", nullptr};\n\n    opt<bool> unlimited_framerate{this, defaults(false), {}, Scope::Config,\n        \"unlimited-framerate\", \"Unlimited framerate\", nullptr};\n\n    opt<bool> render_vsync{this, defaults(false), {}, Scope::Config,\n        \"vsync\", \"V-Sync\", nullptr,\n        config_res_set};\n\n    /* ---- Main - Multiplayer ----*/\n    subsection main_multiplayer{this, \"multiplayer\", \"Multiplayer\"};\n\n    opt_enum<int> two_screen_mode{this,\n        {\n            {MultiplayerPrefs::Dynamic, \"smbx\", \"SMBX\"},\n            {MultiplayerPrefs::Shared, \"shared\", \"Shared\"},\n            {MultiplayerPrefs::Split, \"split\", \"Left/Right\"},\n            {MultiplayerPrefs::TopBottom, \"topbottom\", \"Top/Bottom\"},\n        },\n        defaults(MultiplayerPrefs::Dynamic), {CompatClass::critical_update, MultiplayerPrefs::Dynamic}, Scope::Config,\n        \"two-screen-mode\", \"2P screen mode\", nullptr,\n        config_screenmode_set\n    };\n\n    opt_enum<int> four_screen_mode{this,\n        {\n            {MultiplayerPrefs::Shared, \"shared\", \"Shared\"},\n            {MultiplayerPrefs::Split, \"split\", \"Split\"},\n        },\n        defaults(MultiplayerPrefs::Shared), {CompatClass::critical_update, MultiplayerPrefs::Shared}, Scope::Config,\n        \"four-screen-mode\", \"4P screen mode\", nullptr,\n        config_screenmode_set\n    };\n\n\n    /* ---- Video ----*/\n    section video{this, Scope::Config, \"video\", \"Video\", nullptr};\n\n    /* ---- Video - System ----*/\n    subsection video_system{this, \"video-system\", \"System\"};\n\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    opt<bool> fullscreen{this, defaults(false), {}, Scope::Config,\n        \"fullscreen\", \"Fullscreen\", nullptr,\n        config_fullscreen_set};\n#else\n    static constexpr bool fullscreen = true;\n#endif\n\n    opt_enum<std::pair<int, int>> internal_res{this,\n        {\n            {{480, 320}, \"gba\", \"480x320 (GBA)\"},\n            {{512, 384}, \"nds\", \"512x384 (NDS)\"},\n            {{512, 448}, \"snes\", \"512x448 (SNES)\"},\n            {{640, 480}, \"vga\", \"640x480 (VGA)\"},\n            {{768, 432}, \"hello\", \"768x432 (HELLO)\"},\n            {{800, 480}, \"3ds\", \"800x480 (3DS)\"},\n            {{800, 600}, \"smbx\", \"800x600 (SMBX)\"},\n#ifndef __16M__\n            {{0, 600}, \"smbx-wide\", \"SMBX (wide)\"},\n#endif\n            {{1280, 720}, \"hd\", \"1280x720 (HD)\"},\n            {{0, 0}, \"dynamic\", \"Dynamic\"},\n        },\n        defaults(std::pair<int, int>({0, 0})), {}, Scope::Config,\n        \"internal-res\", \"Screen size\", \"Resolution of gameplay field\",\n        config_res_set\n    };\n\n    enum ScaleModes\n    {\n        SCALE_DYNAMIC_INTEGER = -3,\n        SCALE_DYNAMIC_NEAREST = -2,\n        SCALE_DYNAMIC_LINEAR = -1,\n        SCALE_FIXED_05X = 0,\n        SCALE_FIXED_1X = 1,\n        SCALE_FIXED_2X = 2,\n        SCALE_FIXED_3X = 3,\n    };\n\n#if defined(__WII__) || !defined(PGE_MIN_PORT)\n    opt_enum<int> scale_mode{this,\n        {\n#   ifdef __WII__\n            {SCALE_DYNAMIC_NEAREST, \"full\", \"Full\"},\n            {SCALE_FIXED_1X, \"center\", \"Center\"},\n#   else\n            {SCALE_DYNAMIC_INTEGER, \"integer\", \"Integer\"},\n            {SCALE_DYNAMIC_NEAREST, \"nearest\", \"Nearest\"},\n            {SCALE_DYNAMIC_LINEAR, \"linear\", \"Smooth\"},\n            {SCALE_FIXED_05X, \"0_5x\", \"0.5x\"},\n            {SCALE_FIXED_1X, \"1x\", \"1x\"},\n            {SCALE_FIXED_2X, \"2x\", \"2x\"},\n            {SCALE_FIXED_3X, \"3x\", \"3x\"},\n#   endif\n        },\n        defaults(SCALE_DYNAMIC_NEAREST), {}, Scope::Config,\n        \"scale-mode\", \"Scale mode\", nullptr,\n        config_res_set\n    };\n#else\n    static constexpr int scale_mode = SCALE_DYNAMIC_NEAREST;\n#endif\n\n#ifdef __3DS__\n    opt<bool> td_compat_mode{this, defaults(false), {}, Scope::Config,\n        \"3d-compat-mode\", \"3D compat mode\", \"Draw all objects in one 3D plane\"};\n#endif\n\n    /* ---- Video - Meta Info ----*/\n    subsection info_meta{this, \"info-meta\", \"Onscreen meta info\"};\n\n    static constexpr bool show_backdrop = true;\n\n    opt<bool> show_controllers{this, defaults(false), {}, Scope::Config,\n        \"display-controllers\", \"Controls activity\", nullptr};\n\n    opt<bool> show_fps{this, defaults(false), {}, Scope::Config,\n        \"show-fps\", \"Framerate\", nullptr};\n\n    enum\n    {\n        BATTERY_STATUS_OFF = 0,\n        BATTERY_STATUS_LOW,\n        BATTERY_STATUS_FULLSCREEN,\n        BATTERY_STATUS_ALWAYS_ON,\n    };\n\n#if !defined(__WII__) && !defined(__WIIU__)\n    opt_enum<int> show_battery_status{this,\n        {\n            {BATTERY_STATUS_OFF, \"off\", \"Off\", nullptr},\n            {BATTERY_STATUS_LOW, \"low\", \"Low\", \"Show when the battery is low\"},\n#ifndef RENDER_FULLSCREEN_ALWAYS\n            {BATTERY_STATUS_FULLSCREEN, \"fullscreen\", \"Fullscreen\", \"Show when the game is fullscreen\"},\n#endif\n            {BATTERY_STATUS_ALWAYS_ON, \"on\", \"Always\", nullptr},\n        },\n        defaults<int>(BATTERY_STATUS_OFF), {}, Scope::Config,\n        \"battery-status\", \"Device battery status\", nullptr};\n#else\n    static constexpr bool show_battery_status = BATTERY_STATUS_OFF;\n#endif\n\n    /* ---- Video - Game Info ----*/\n    subsection info_game{this, \"info-game\", \"Onscreen game info\"};\n\n    opt<bool> show_fails_counter{this, defaults(false), {}, Scope::Config,\n        \"show-fails-counter\", \"Fails counter\", \"Show fails on the episode and current level\"};\n\n    enum\n    {\n        MEDALS_SHOW_OFF = 0,\n        MEDALS_SHOW_GOT,\n        MEDALS_SHOW_COUNTS,\n        MEDALS_SHOW_FULL,\n    };\n#if 0\n    opt_enum<int> medals_show_policy{this,\n        {\n            {MEDALS_SHOW_OFF, \"hide\", \"Off\", \"Don't show medals\"},\n            {MEDALS_SHOW_GOT, \"collected-only\", \"Collected\", \"Show counts of gotten medals\"},\n            {MEDALS_SHOW_COUNTS, \"counts-only\", \"Counts\", \"Show counts of gotten and available medals\"},\n            {MEDALS_SHOW_FULL, \"show-full\", \"Full\", \"Show order of gotten and available medals\"},\n        },\n        defaults<int>(MEDALS_SHOW_FULL), {}, Scope::Config,\n        \"show-medals-policy\", \"Medals policy\"};\n#else\n    static constexpr int medals_show_policy = MEDALS_SHOW_FULL;\n#endif\n\n    opt<bool> show_medals_counter{this,\n        defaults<bool>(true), {CompatClass::pure_preference, false}, Scope::Config,\n        \"show-medals-counter\", \"Medals counter\", \"Show medals in HUD and at level entrance\"};\n\n    // used elsewhere to track creator-specified options\n    enum\n    {\n        MAP_STARS_UNSPECIFIED = 0,\n        MAP_STARS_HIDE,\n        MAP_STARS_COLLECTED,\n        MAP_STARS_SHOW,\n    };\n\n    opt<bool> world_map_stars_show{this, defaults(true), {CompatClass::pure_preference, false}, Scope::Config,\n        \"world-map-stars-show\", \"World map stars\", \"Show collected stars above levels\"};\n\n    /* ---- Video - Run Info ----*/\n    subsection info_run{this, \"info-run\", \"Onscreen run info\"};\n\n    enum\n    {\n        EPISODE_TITLE_OFF = 0,\n        EPISODE_TITLE_TOP,\n        EPISODE_TITLE_BOTTOM,\n    };\n    opt_enum<int> show_episode_title{this,\n        {\n            {EPISODE_TITLE_OFF, \"off\", \"Off\"},\n            {EPISODE_TITLE_TOP, \"top\", \"Top\", \"Show above HUD at high resolution\"},\n            {EPISODE_TITLE_BOTTOM, \"bottom\", \"Bottom\", \"Show above speedrun timer\"},\n            {EPISODE_TITLE_BOTTOM, \"transparent\"},\n            {EPISODE_TITLE_BOTTOM, \"on\"},\n        },\n        defaults(EPISODE_TITLE_OFF), {}, Scope::Config,\n        \"show-episode-title\", \"Episode name\", nullptr};\n\n    enum\n    {\n        PLAYTIME_COUNTER_OFF = 0,\n        PLAYTIME_COUNTER_SUBTLE = 1,\n        PLAYTIME_COUNTER_OPAQUE,\n        PLAYTIME_COUNTER_ANIMATED,\n    };\n    opt_enum<int> show_playtime_counter{this,\n        {\n            {PLAYTIME_COUNTER_OFF, \"off\", \"Off\", \"Show only during speedruns\"},\n            {PLAYTIME_COUNTER_SUBTLE, \"transparent\", \"Transparent\", nullptr},\n            {PLAYTIME_COUNTER_SUBTLE, \"subtle\"},\n            {PLAYTIME_COUNTER_OPAQUE, \"opaque\", \"Opaque\", nullptr},\n            {PLAYTIME_COUNTER_ANIMATED, \"animated\", \"Animated\", \"Adds a rainbow effect on level end\"},\n        },\n        defaults(PLAYTIME_COUNTER_OFF), {}, Scope::Config,\n        \"show-playtime-counter\", \"Playtime counter\", \"Show time spent on the episode and current attempt\"};\n\n    /* ---- Video - Effects ----*/\n    subsection effects{this, \"effects\", \"Screen Effects\"};\n\n    opt<bool> show_screen_shake{this,\n        defaults(true), {CompatClass::pure_preference, true}, Scope::Config,\n        \"show-screen-shake\", \"Screen shake\", nullptr};\n\n    opt<bool> EnableInterLevelFade{this, defaults(true), {CompatClass::pure_preference, false}, Scope::Config,\n        \"enable-inter-level-fade-effect\", \"Fade transitions\", nullptr};\n\n\n    /* ---- Audio ----*/\n    section audio{this, Scope::Config, \"audio\", \"Audio\", nullptr};\n\n#ifndef __16M__\n    opt_range<int> audio_mus_volume{this, {0, 100, 5}, defaults(100), {}, Scope::Config,\n        \"audio-music-volume\", \"Music volume\", nullptr,\n        config_music_volume_set};\n\n    opt_range<int> audio_sfx_volume{this, {0, 100, 5}, defaults(100), {}, Scope::Config,\n        \"audio-sfx-volume\", \"SFX volume\", nullptr};\n#endif\n\n    /* ---- Audio - Preferences ----*/\n    subsection audio_preferences{this, \"audio-prefs\", \"Preferences\"};\n\n    opt<bool> sfx_modern{this, defaults(true), {CompatClass::pure_preference, false}, Scope::Config,\n        \"sfx-modern\", \"Modern SFX\", \"Use sounds added in TheXTech\"};\n\n#if 0\n    enum\n    {\n        MOUNTDRUMS_NEVER = 0,\n        MOUNTDRUMS_NORMAL,\n        MOUNTDRUMS_ALWAYS\n    };\n    opt_enum<int> sfx_mount_drums{this,\n        {\n            {MOUNTDRUMS_NEVER, \"never\", \"Never\", \"Never plays the special drums\"},\n            {MOUNTDRUMS_NORMAL, \"normal\", \"Normal\", \"Plays the special drums when you are riding a pet mount\"},\n            {MOUNTDRUMS_ALWAYS, \"always\", \"Always\", \"Always plays the special drums\"},\n        },\n        defaults<int>(MOUNTDRUMS_NORMAL), {CompatClass::pure_preference, MOUNTDRUMS_NEVER}, Scope::Config,\n        \"sfx-mount-drums\", \"Mount drums\", \"Should special drums play while the player is riding a pet?\",\n        config_mountdrums_set};\n#endif\n\n#ifndef __16M__\n    opt<bool> sfx_pet_beat{this, defaults(true), {CompatClass::pure_preference, false}, Scope::Config,\n        \"sfx-pet-beat\", \"Pet grooves\", \"Play special groove when riding a pet\",\n        config_mountdrums_set};\n#endif\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\n\n#   ifdef __3DS__\n    static constexpr bool sfx_audio_fx_default = false;\n#   else\n    static constexpr bool sfx_audio_fx_default = true;\n#   endif\n\n    opt<bool> sfx_audio_fx{this, defaults(sfx_audio_fx_default), {CompatClass::pure_preference, false}, Scope::Config,\n        \"sfx-audio-fx\", \"Echo effects\", nullptr,\n        config_audiofx_set};\n#endif\n\n    opt<bool> sfx_spatial_audio{this, defaults(true), {CompatClass::pure_preference, false}, Scope::Config,\n        \"sfx-spatial-audio\", \"Spatial audio\", \"Reduce volume of offscreen sounds\"};\n\n\n    /* ---- Controls ----*/\n    section controls{this, Scope::Config, \"controls\", \"Controls\"};\n\n\n    /* ---- Advanced ----*/\n    section advanced{this, Scope::Config, \"advanced\", \"Advanced\", \"Technical options for internal operations\"};\n\n    // static constexpr bool record_gameplay_data = false;\n\n    opt<bool> pick_assets_on_start{this, defaults(true), {}, Scope::Config,\n        \"choose-assets-on-launch\", \"Choose assets on launch\", nullptr};\n\n    opt<bool> record_gameplay_data{this, defaults(false), {}, Scope::Config,\n        \"record-gameplay-data\", \"Record gameplay\", nullptr};\n\n    opt_enum<int> log_level{this,\n        {\n            {PGE_LogLevel::NoLog, \"none\", \"None\", nullptr},\n            {PGE_LogLevel::Fatal, \"fatal\", \"Fatal\", nullptr},\n            {PGE_LogLevel::Critical, \"critical\", \"Critical\", nullptr},\n            {PGE_LogLevel::Warning, \"warning\", \"Warning\", nullptr},\n            {PGE_LogLevel::Info, \"info\", \"Info\", nullptr},\n            {PGE_LogLevel::Debug, \"debug\", \"Debug\", nullptr},\n            {PGE_LogLevel::NoLog, \"0\", nullptr, nullptr},\n            {PGE_LogLevel::Fatal, \"1\", nullptr, nullptr},\n            {PGE_LogLevel::Critical, \"2\", nullptr, nullptr},\n            {PGE_LogLevel::Warning, \"3\", nullptr, nullptr},\n            {PGE_LogLevel::Info, \"4\", nullptr, nullptr},\n            {PGE_LogLevel::Debug, \"5\", nullptr, nullptr},\n        },\n#if defined(DEBUG_BUILD)\n        defaults(PGE_LogLevel::Debug),\n#else\n        defaults(PGE_LogLevel::Info),\n#endif\n        {}, Scope::Config,\n        \"log-level\", \"Log level\", nullptr,\n        config_log_level_set};\n\n#if defined(__ANDROID__) || defined(__3DS__)\n    opt<bool> use_native_osk{this, defaults(false), {}, Scope::Config,\n        \"use-native-osk\", \"Native OSK\", nullptr};\n#endif\n\n#ifdef PGE_VIDEO_REC_WEBM_SUPPORTED\n    opt<bool> webm_recording{this, defaults(true), {}, Scope::Config,\n        \"webm-recording\", \"WEBM recording\", nullptr};\n#endif\n\n    /* ---- Advanced - Video ----*/\n    subsection advanced_video{this, \"advanced-video\", \"Video\"};\n\n#ifdef __3DS__\n    opt<bool> inaccurate_gifs{this, defaults(false), {}, Scope::Config,\n        \"inaccurate-gifs\", \"Inaccurate GIFs\", \"Allows 3D effect when GIFs are present\",\n        config_3ds_inaccurate_gifs_set};\n#endif\n\n#ifndef RENDER_CUSTOM\n    enum RenderMode_t\n    {\n        RENDER_ACCELERATED_AUTO = 0,\n        RENDER_ACCELERATED_SDL,\n        RENDER_ACCELERATED_OPENGL,\n        RENDER_ACCELERATED_OPENGL_ES,\n        RENDER_ACCELERATED_OPENGL_LEGACY,\n        RENDER_ACCELERATED_OPENGL_ES_LEGACY,\n        RENDER_SOFTWARE,\n        RENDER_END\n    };\n    setup_enum_t render_mode{this,\n        {\n            {RENDER_SOFTWARE, \"sw\", \"Software\", nullptr},\n            {RENDER_ACCELERATED_AUTO, \"hw\", \"Auto\", \"\"},\n            {RENDER_ACCELERATED_SDL, \"sdl\", \"SDL2\", \"Basic cross-platform render API\"},\n#ifdef THEXTECH_BUILD_GL_DESKTOP_MODERN\n            {RENDER_ACCELERATED_OPENGL, \"opengl\", \"OpenGL 3+\", \"Desktop API with support for all new visual effects and full accuracy to SMBX64\"},\n#endif\n#ifdef THEXTECH_BUILD_GL_ES_MODERN\n            {RENDER_ACCELERATED_OPENGL_ES, \"opengles\", \"OpenGL ES 2+\", \"Mobile API with support for all new visual effects and high accuracy to SMBX64\"},\n#endif\n#ifdef THEXTECH_BUILD_GL_DESKTOP_LEGACY\n            {RENDER_ACCELERATED_OPENGL_LEGACY, \"opengl11\", \"OpenGL 1.1\", \"Legacy desktop API with full accuracy to SMBX64\"},\n#endif\n#ifdef THEXTECH_BUILD_GL_ES_LEGACY\n            {RENDER_ACCELERATED_OPENGL_ES_LEGACY, \"opengles11\", \"OpenGL ES 1.1\", \"Legacy mobile API with full accuracy to SMBX64\"},\n#endif\n            {RENDER_SOFTWARE, \"0\"},\n            {RENDER_ACCELERATED_SDL, \"1\"},\n        },\n        defaults(RENDER_ACCELERATED_AUTO), {}, Scope::Config,\n        \"render\", \"Render mode\", nullptr,\n        config_rendermode_set};\n#endif\n\n#ifndef PGE_MIN_PORT\n    enum ScaleDownTextures\n    {\n        SCALE_DOWN_NONE = 0,\n        SCALE_DOWN_SAFE = 1,\n        SCALE_DOWN_ALL = 2,\n    };\n\n    opt_enum<int> scale_down_textures{this,\n        {\n            {SCALE_DOWN_NONE, \"none\", \"None\", \"Least loading stutter\"},\n            {SCALE_DOWN_SAFE, \"safe\", \"Safe\", \"Checks if images are in 2x format\"},\n            {SCALE_DOWN_ALL, \"all\", \"All\", \"Less loading stutter than 'Safe'\"},\n        },\n        defaults(SCALE_DOWN_SAFE), {}, Scope::Config,\n        \"scale-down-textures\", \"Scale down images\", \"Store images as 1x to save memory\"};\n#endif\n\n#ifndef PGE_MIN_PORT\n    opt_enum<std::pair<int, int>> internal_res_4p{this,\n        {\n            {{0, 0}, \"default\", \"Default\"},\n            {{1920, 1080}, \"fhd\", \"1920x1080 (FHD)\"},\n            {{1600, 1200}, \"qsmbx\", \"1600x1200 (QSMBX)\"},\n            {{2560, 1440}, \"qhd\", \"2560x1440 (QHD)\"},\n        },\n        defaults(std::pair<int, int>({0, 0})), {}, Scope::Config,\n        \"internal-res-4p\", \"4P split screen size\", nullptr,\n        config_res_set\n    };\n#endif\n\n#ifdef RENDER_FULLSCREEN_TYPES_SUPPORTED\n    enum FullScreenType\n    {\n        FULLSCREEN_TYPE_AUTO = 0,\n        FULLSCREEN_TYPE_DESKTOP = 1,\n        FULLSCREEN_TYPE_EXCLUSIVE = 2\n    };\n    setup_enum_t fullscreen_type{this,\n        {\n            {FULLSCREEN_TYPE_AUTO, \"auto\", \"Auto\", nullptr},\n            {FULLSCREEN_TYPE_DESKTOP, \"desktop\", \"Desktop\", \"Keep the current screen resolution\"},\n            {FULLSCREEN_TYPE_EXCLUSIVE, \"exclusive\", \"Exclusive\", \"Set physical screen resolution\"},\n        },\n        defaults(FULLSCREEN_TYPE_AUTO), {},\n        Scope::Config,\n        \"fullscreen-type\", \"Fullscreen type\", nullptr,\n        config_fullscreen_type_set\n    };\n\n    using fullscreen_res_t = ConfigFullscreenRes_t<writable>;\n\n    fullscreen_res_t fullscreen_res{this, defaults(std::pair<int, int>{800, 600}), {}, Scope::Config,\n        \"fullscreen-res\", \"Exclusive res\", nullptr,\n        config_video_mode_set\n    };\n\n    setup_enum_t fullscreen_depth{this,\n        {\n            {0, \"auto\", \"Auto\", nullptr},\n            {16, \"16\", \"16-bit\", nullptr},\n            {32, \"32\", \"32-bit\", nullptr},\n        },\n        defaults(0), {}, Scope::Config,\n        \"fullscreen-depth\", \"Exclusive bit depth\", nullptr,\n        config_video_mode_set\n    };\n#endif // RENDER_FULLSCREEN_TYPES_SUPPORTED\n\n#ifndef THEXTECH_NO_SDL_BUILD\n    /* ---- Advanced - Audio ----*/\n    subsection advanced_audio{this, \"advanced-audio\", \"Audio\"};\n\n    opt<bool> audio_enable{this, defaults(true), {}, Scope::Config,\n        \"audio-enable\", \"Enable\", nullptr,\n        config_audio_set};\n\n    setup_enum_t audio_channels{this,\n        {\n            {1, \"mono\", \"Mono\", nullptr},\n            {2, \"stereo\", \"Stereo\", nullptr},\n        },\n        defaults(g_audioDefaults.channels), {}, Scope::Config,\n        \"audio-channels\", \"Channels\", nullptr,\n        config_audio_set};\n\n    setup_enum_t audio_sample_rate{this,\n        {\n            {11025, \"11025\", \"11025 Hz\", nullptr},\n            {16000, \"16000\", \"16000 Hz\", nullptr},\n            {22050, \"22050\", \"22050 Hz\", nullptr},\n            {32000, \"32000\", \"32000 Hz\", nullptr},\n            {44100, \"44100\", \"44100 Hz\", nullptr},\n            {48000, \"48000\", \"48000 Hz\", nullptr},\n        },\n        defaults(g_audioDefaults.sampleRate), {}, Scope::Config,\n        \"audio-sample-rate\", \"Sample rate\", nullptr,\n        config_audio_set};\n\n    setup_enum_t audio_format{this,\n        {\n            {AUDIO_S8, false, \"s8\", \"s8\"},\n            {AUDIO_S8, false, \"pcm_s8\"},\n\n            {AUDIO_U8, false, \"u8\", \"u8\"},\n            {AUDIO_U8, false, \"pcm_u8\"},\n\n            {AUDIO_S16SYS, false, \"s16\", \"s16\"},\n            {AUDIO_S16SYS, false, \"pcm_s16\"},\n#       if AUDIO_S16LSB != AUDIO_S16SYS\n            {AUDIO_S16LSB, false, \"s16le\", \"s16le\"},\n            {AUDIO_S16LSB, false, \"pcm_s16le\"},\n#       else\n            {AUDIO_S16MSB, false, \"s16be\", \"s16be\"},\n            {AUDIO_S16MSB, false, \"pcm_s16be\"},\n#       endif\n\n            {AUDIO_U16SYS, false, \"u16\", \"u16\"},\n            {AUDIO_U16SYS, false, \"pcm_u16\"},\n#       if AUDIO_U16LSB != AUDIO_U16SYS\n            {AUDIO_U16LSB, false, \"u16le\", \"u16le\"},\n            {AUDIO_U16LSB, false, \"pcm_u16le\"},\n#       else\n            {AUDIO_U16MSB, false, \"u16be\", \"u16be\"},\n            {AUDIO_U16MSB, false, \"pcm_u16be\"},\n#       endif\n\n            {AUDIO_S32SYS, false, \"s32\", \"s32\"},\n            {AUDIO_S32SYS, false, \"pcm_s32\"},\n#       if AUDIO_S32LSB != AUDIO_S32SYS\n            {AUDIO_S32LSB, false, \"s32le\", \"s32le\"},\n            {AUDIO_S32LSB, false, \"pcm_s32le\"},\n#       else\n            {AUDIO_S32MSB, false, \"s32be\", \"s32be\"},\n            {AUDIO_S32MSB, false, \"pcm_s32be\"},\n#       endif\n\n            {AUDIO_F32SYS, false, \"float32\", \"float32\"},\n            {AUDIO_F32SYS, false, \"pcm_f32\"},\n#       if AUDIO_F32LSB != AUDIO_F32SYS\n            {AUDIO_F32LSB, false, \"float32le\", \"float32le\"},\n            {AUDIO_F32LSB, false, \"pcm_f32le\"},\n#       else\n            {AUDIO_F32MSB, false, \"float32be\", \"float32be\"},\n            {AUDIO_F32MSB, false, \"pcm_f32be\"},\n#       endif\n        },\n        defaults(g_audioDefaults.format), {}, Scope::Config,\n        \"audio-format\", \"Audio format\", \"Format for sound driver\",\n        config_audio_set};\n\n    setup_enum_t audio_buffer_size{this,\n        {\n            {512, false, \"512\", \"512\"},\n            {768, false, \"768\", \"768\"},\n            {1024, false, \"1024\", \"1024\"},\n            {1536, false, \"1536\", \"1536\"},\n            {2048, false, \"2048\", \"2048\"},\n            {4096, false, \"4096\", \"4096\"},\n        },\n        defaults(g_audioDefaults.bufferSize), {}, Scope::Config,\n        \"audio-buffer-size\", \"Buffer size\", \"Increase for fewer pops but more lag\",\n        config_audio_set};\n#else\n    static constexpr int audio_sample_rate = 44100;\n    static constexpr int audio_format = AUDIO_F32SYS;\n    static constexpr int audio_buffer_size = 1024;\n#endif\n\n\n    /* ---- Episode options ----*/\n    section episode_options{this, Scope::EpisodeOptions, \"episode-options\", \"Episode Options\"};\n\n    enum\n    {\n        MODE_MODERN = 0,\n        MODE_CLASSIC,\n        MODE_VANILLA,\n    };\n    opt_enum<int> playstyle{this,\n        {\n            {MODE_MODERN, \"modern\", \"Modern\", \"All updates\"},\n            {MODE_CLASSIC, \"classic\", \"Classic\", \"Minimal updates\"},\n            {MODE_VANILLA, \"vanilla\", \"Vanilla\", \"No updates\"},\n        },\n        defaults(MODE_MODERN), {CompatClass::pure_preference, MODE_VANILLA}, Scope::EpisodeOptions,\n        \"playstyle\", \"Playstyle\", nullptr,\n        config_compat_changed};\n\n    enum\n    {\n        CREATORCOMPAT_ENABLE = 0,\n        CREATORCOMPAT_FILEONLY,\n        CREATORCOMPAT_DISABLE,\n    };\n    opt_enum<int> creator_compat{this,\n        {\n            {CREATORCOMPAT_ENABLE, \"enable\", \"Enable\", nullptr},\n            {CREATORCOMPAT_FILEONLY, \"file-only\", \"File only\", nullptr},\n            {CREATORCOMPAT_DISABLE, \"disable\", \"Disable\", nullptr},\n        },\n        defaults(CREATORCOMPAT_ENABLE), {CompatClass::critical_update, CREATORCOMPAT_DISABLE}, Scope::EpisodeOptions,\n        \"creator-compat\", \"Creator compat\", \"Logic tweaks by content creator\"};\n\n    opt<bool> enable_last_warp_hub_resume{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorEpisode | Scope::EpisodeOptions,\n        \"hub-resume\", \"Save warp in hub\", \"Resume play at the last warp you entered\"};\n\n    // /* ---- Prefs - Effects ----*/\n    // subsection effects{this, \"effects\", \"Visual Effects\"};\n\n\n\n    /* ---- Compatibility ----*/\n    section compat{this, Scope::All, \"compatibility\", \"Session Tweaks\", \"Options for compat or debugging\"};\n\n    subsection reset_all{this, \"reset-all\", \"Reset All\"};\n\n    // TODO: make sure that Modern / Classic do not override compat.ini settings\n    enum\n    {\n        COMPAT_OFF = 0,\n        COMPAT_CLASSIC,\n        COMPAT_SMBX13,\n        COMPAT_MODERN,\n    };\n    opt_enum<int> compatibility_mode{this,\n        {\n            {COMPAT_OFF, \"off\", \"Off\", nullptr},\n            {COMPAT_OFF, \"native\"},\n            {COMPAT_MODERN, \"modern\", \"Modern\", nullptr},\n            {COMPAT_CLASSIC, \"classic\", \"Classic\", nullptr},\n            {COMPAT_SMBX13, \"smbx13\", \"Vanilla\", nullptr},\n            // {COMPAT_CONTENT, \"content\", \"Content\", \"Preserves the gameplay of the version for which the content was released\"},\n        },\n        defaults(COMPAT_OFF), {}, Scope::None,\n        \"compatibility-mode\", \"Compat mode\", \"Overrides settings (even custom ones in a world or level) to guarantee behavior compatible with an older fan game\"};\n\n    /* ---- Compatibility - Features ----*/\n    subsection compat_features{this, \"features\", \"Features\"};\n\n    opt<bool> allow_drop_add{this, defaults(true), {CompatClass::critical_update, false}, Scope::None,\n        \"allow-drop-add\", \"Allow drop/add\", \"Allow players to be dropped/added from the pause menu item during gameplay\"};\n    opt<bool> multiplayer_pause_controls{this, defaults(true), {CompatClass::critical_update, false}, Scope::None,\n        \"multiplayer-pause-controls\", \"Multiplayer pause controls\", \"Allows players other than P1 to pause the game and control the pause menu\"};\n\n    opt<bool> world_map_lvlname_marquee{this, defaults(false), {CompatClass::pure_preference, false}, Scope::CreatorEpisode,\n    \"world-map-lvlname-marquee\", \"Level name marquee\", \"In the world map, use a marquee effect for the level name instead of splitting it over two lines\"};\n\n    opt<bool> fix_vanilla_checkpoints{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-vanilla-checkpoints\", \"Fix vanilla checkpoints\", \"Resume from the most recently touched checkpoint after death\"};\n    opt<bool> disable_background2_tiling{this, defaults(false), {CompatClass::standard_update, true}, Scope::Creator,\n        \"disable-background2-tiling\", \"Disable BG tiling\", \"Disable tiling of the level background texture if it causes visual disturbances\"};\n    opt<bool> modern_section_change{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"modern-section-change\", \"Modern section change\", \"Animate sections resizing when they shrink or during 2-player mode\"};\n\n    opt<bool> extra_screen_shake{this, defaults(true), {CompatClass::critical_update, false}, Scope::Creator,\n        \"extra-screen-shake\", \"Extra screen shake\", \"Shake screen for large enemy and player ground pounds\"};\n\n    opt<bool> enable_playtime_tracking{this, defaults(true), {CompatClass::pure_preference, false}, Scope::CreatorFile,\n        \"enable-playtime-tracking\", \"Playtime tracking\", \"Record the amount of time spent playing an episode\"};\n    opt<bool> enable_fails_tracking{this, defaults(true), {CompatClass::pure_preference, false}, Scope::CreatorFile,\n        \"enable-fails-tracking\", \"Fails tracking\", \"Record the number of fails on the current level and world\"};\n\n    opt<bool> world_map_fast_move{this, defaults(true), {CompatClass::standard_update, false}, Scope::Creator,\n        \"world-map-fast-move\", \"Automatic movement\", \"Moves automatically between forks on the path\"};\n\n    // opt<bool> modern_item_drop{this, defaults(true), {CompatClass::standard_update, false}, Scope::Creator,\n    //     \"modern-item-drop\", \"Modern item drop\", \"Experimental implementation of modern item drop\"};\n    opt<bool> small_screen_cam{this, defaults(true), {CompatClass::standard_update, false}, Scope::None,\n        \"small-screen-cam\", \"Small screen camera\", \"Camera panning and control\"};\n\n    opt<bool> disable_spin_jump{this, defaults(false), {CompatClass::critical_update, false}, Scope::Creator,\n        \"disable-spin-jump\", \"Disable spin jump\", \"The alt jump key should trigger an ordinary jump instead of a spin jump\"};\n\n    opt<bool> no_shell_grab_top{this, defaults(false), {CompatClass::critical_update, false}, Scope::CreatorEpisode,\n        \"no-shell-grab-top\", \"No shell grab from top\", nullptr};\n\n    opt<bool> alt_powerdown{this, defaults(false), {CompatClass::critical_update, false}, Scope::CreatorEpisode,\n        \"alt-powerdown\", \"Alt powerdown (chars 1+2)\", nullptr};\n\n    /* ---- Compatibility - Autocode ----*/\n    subsection compat_autocode{this, \"autocode\", \"Autocode\"};\n\n    // Luna\n    opt<bool> luna_enable_engine{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"luna-script-enable-engine\", \"Enable engine\", nullptr};\n    opt<bool> luna_allow_level_codes{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"luna-script-allow-level-codes\", \"Allow level codes\", nullptr};\n    static constexpr bool autocode_translate_coords = true;\n    // opt<bool> autocode_translate_coords{this, defaults(true), {}, Scope::CreatorFile,\n    //     \"autocode-translate-coords\", \"Translate coords\", \"Translate the coordinates of autocode screen-space objects based on the HUD location\"};\n\n    /* ---- Compatibility - Bugfixes ----*/\n    subsection bugfixes{this, \"bugfixes\", \"Bugfixes\"};\n\n    // <1.3.4\n    opt<bool> fix_restored_block_move{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-restored-block-move\", \"Fix restored block move\", \"Don't move powerup blocks to the right when they are hit after restoring\"};\n    opt<bool> fix_player_slope_speed{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-player-slope-speed\", \"Player slope speed\", nullptr};\n    // 1.3.4\n    opt<bool> fix_player_filter_bounce{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-player-filter-bounce\", \"Player filter bounce\", \"Fix a glitch where player could clip downwards\"};\n    opt<bool> fix_player_clip_wall_at_npc{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-player-clip-wall-at-npc\", \"Player clip wall at NPC\", nullptr};\n    opt<bool> fix_player_downward_clip{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-player-downward-clip\", \"Player downward clip\", nullptr};\n    opt<bool> fix_npc_downward_clip{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-npc-downward-clip\", \"NPC downward clip\", nullptr};\n    opt<bool> fix_npc247_collapse{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-npc247-collapse\", \"NPC 247 collapse\", \"Prevents the stacked spiky plants from collapsing incorrectly\"};\n    opt<bool> fix_platforms_acceleration{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-platform-acceleration\", \"Platform acceleration\", nullptr};\n    opt<bool> fix_npc55_kick_ice_blocks{this, defaults(false), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-npc55-kick-ice-blocks\", \"NPC55 kick ice blocks\", \"NPC55 should kick ice blocks like NPC119\"};\n    opt<bool> fix_climb_invisible_fences{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-climb-invisible-fences\", \"Don't climb hidden fences\", nullptr};\n    opt<bool> fix_climb_bgo_speed_adding{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-climb-bgo-speed-adding\", \"Climb BGO speed adding\", nullptr};\n    opt<bool> enable_climb_bgo_layer_move{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"enable-climb-bgo-layer-move\", \"Move with climbable BGOs\", \"Player will move together with the climbable BGO on the moving layer as on vines\"};\n    opt<bool> fix_skull_raft{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-skull-raft\", \"Skull raft\", nullptr};\n    opt<bool> fix_char3_escape_shell_surf{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-char3-escape-shell-surf\", \"Char 3 escape shell surf\", nullptr};\n    opt<bool> fix_plant_wobble{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-plant-wobble\", \"Fix plant wobble\", \"Plants should correctly resize instead of appearing to change size / position\"};\n    opt<bool> fix_powerup_lava_bug{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-powerup-lava-bug\", \"Fix powerup lava bug\", \"Powerups should be destroyed when hitting lava instead of surviving depending on the current NPC array\"};\n    // opt<bool> fix_keyhole_framerate{this, defaults(true), {VER_THEXTECH134, CompatClass::critical_update, false}, Scope::CreatorFile,\n    //     \"fix-keyhole-framerate\", \"Fix keyhole framerate\", nullptr};\n    // 1.3.5\n    opt<bool> fix_char5_vehicle_climb{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-char5-vehicle-climb\", \"Fix char 5 car fairy\", nullptr};\n    opt<bool> fix_vehicle_char_switch{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-vehicle-char-switch\", \"Don't switch character by car\", nullptr};\n    opt<bool> fix_autoscroll_speed{this, defaults(false), {CompatClass::pure_preference, false}, Scope::CreatorFile,\n        \"fix-autoscroll-speed\", \"[Deprecated] autoscroll speed\", nullptr};\n    opt<bool> fix_submerged_splash_effect{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-submerged-splash-effect\", \"Fix submerged splash effect\", \"Don't make a splash effect for items already underwater\"};\n    // 1.3.5.1\n    opt<bool> fix_squid_stomp_effect{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-squid-stomp-effect\", \"Fix calamari stomp effect\", nullptr};\n    // deprecated, fixed non-vanilla bug\n    // opt<bool> keep_bullet_bill_dir{this, defaults(true), {VER_THEXTECH1351, CompatClass::critical_update, false}, Scope::CreatorFile,\n    //     \"keep-bullet-bill-direction\", \"Keep bullet dir\", nullptr};\n    opt<bool> fix_special_coin_switch{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-special-coin-switch\", \"Preserve special coins\", \"Don't turn special coins into blocks when pressing switch\"};\n    // 1.3.5.2\n    opt<bool> fix_bat_start_while_inactive{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-bat-start-while-inactive\", \"Bat start\", \"Don't activate the bats until they are onscreen\"};\n    // 1.3.5.3\n    opt<bool> fix_npc_activation_event_loop_bug{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-npc-activation-event-loop-bug\", \"NPC activation loop\", \"Fixes softlock bug that results when NPCs activate each other in a loop\"};\n    // 1.3.6\n    opt<bool> fix_fairy_stuck_in_pipe{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-fairy-stuck-in-pipe\", \"Fairy stuck in pipe\", nullptr};\n    opt<bool> fix_flamethrower_gravity{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-flamethrower-gravity\", \"Flamethrower gravity\", \"Should this have even been made?\"};\n    // 1.3.6.1\n    opt<bool> fix_npc_ceiling_speed{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-npc-ceiling-speed\", \"Fix NPC ceiling speed\", \"Don't stick a thrown NPC to ceiling when a layer is moving\"};\n    opt<bool> emulate_classic_block_order{this, defaults(false), {CompatClass::critical_update, true}, Scope::CreatorFile,\n        \"emulate-classic-block-order\", \"Emulate classic block order\", \"Use the classic game's block sorting methods internally for frame-perfect compatibility\"};\n    opt<bool> custom_powerup_collect_score{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"custom-powerup-collect-score\", \"Custom powerup collect score\", \"Collected powerups give score from npc-X.txt\"};\n    opt<bool> fix_player_crush_death{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-player-crush-death\", \"Fix player crush death\", \"Player should not be crushed by corners of slopes or by hitting a horizontally moving ceiling\"};\n    opt<bool> fix_pound_skip_warp{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-pound-skip-warp\", \"Fix pound skip warp\", \"Pound move should not skip instant / portal warps\"};\n    opt<bool> fix_held_item_cancel{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-held-item-cancel\", \"Fix held item cancel\", \"Do not cancel held item hitting hostile NPC that intersects with an immune NPC\"};\n    opt<bool> fix_frame_perfect_despawn{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-frame-perfect-despawn\", \"Fix frame perfect despawn\", \"If NPC comes onscreen later in the same frame it timed out, should still be able to spawn\"};\n\n    // TODO: implement proper read/write operations\n    // opt<std::array<uint8_t, 3>> bitblit_background_colour{this, defaults<std::array<uint8_t, 3>>({0, 0, 0}), {VER_THEXTECH1361}, Scope::CreatorFile,\n    //     \"bitblit-background-colour\", \"Bitblit background colour\", \"Background colour for legacy transparency effects\", config_bitblit_background_set};\n\n    // 1.3.6.3\n    opt<bool> pound_by_alt_run{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"pound-by-alt-run\", \"Pound by alt run\", \"Use Alt Run for Pound action when player is in a purple pet mount\"};\n\n    // 1.3.6.5\n    opt<bool> fix_visual_bugs{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-visual-bugs\", \"Fix visual bugs\", \"Fix misc visual bugs from SMBX 1.3\"};\n    opt<bool> fix_npc_emerge_size{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-npc-emerge-size\", \"Fix NPC emerge size\", \"Fix size of NPC emerging from a block\"};\n    opt<bool> fix_switched_block_clipping{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-switched-block-clipping\", \"Fix switched block clipping\", \"Don't let blocks become intangible to NPCs after coin switch\"};\n\n    // 1.3.6.6\n    opt<bool> fix_vehicle_altjump_bug{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-vehicle-altjump-bug\", \"Fix vehicle AltJump bug\", \"Don't become vulnerable after AltJump into car\"};\n    opt<bool> fix_vehicle_altjump_lock{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-vehicle-altjump-lock\", \"Fix vehicle AltJump lock\", \"Don't get stuck in car entered while holding AltJump\"};\n    // player can be stuck when hit by an NPC while digging dirt (#125)\n    opt<bool> fix_player_stuck_on_dirt{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-player-stuck-on-dirt\", \"Fix player stuck on dirt\", \"Don't get stuck when digging dirt\"};\n\n    // 1.3.7\n    opt<bool> fix_npc_camera_logic{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-npc-camera-logic\", \"Fix NPC camera logic\", \"NPCs should support more than two cameras\"};\n    opt<bool> fix_multiplayer_targeting{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-multiplayer-targeting\", \"Fix NPC targeting\", \"Updated NPC targeting logic in multiplayer\"};\n    opt<bool> allow_multires{this, defaults(true), {CompatClass::critical_update, false}, Scope::Creator,\n        \"allow-multires\", \"Allow multires\", \"Allows the gameplay field to be <800x600\", config_res_set};\n    opt<bool> dynamic_camera_logic{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"dynamic-camera-logic\", \"Dynamic camera logic\", \"Allow a non-800x600 camera for most logic, and only use the event logic camera in rare cases\",\n        config_res_set};\n    opt<bool> fix_attlayer_reset{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-attlayer-reset\", \"Fix AttLayer reset\", \"Resets an att. layer when its NPC dies\"};\n    opt<bool> fix_medal_kill{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-medal-kill\", \"Fix medal kill\", \"Count a medal as collected when player kills it\"};\n    opt<bool> fix_event_swap_bug{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-event-swap-bug\", \"Fix event swap bug\", \"Don't overwrite pending events when multiple events triggered in a frame\"};\n\n    // if we make the scope below be Scope::CreatorEpisode, episodes can force this in both modern / classic mode\n    opt<bool> modern_lives_system{this, defaults(true), {CompatClass::critical_update, false}, Scope::None,\n        \"modern-lives-system\", \"Modern lives system\", \"Allow negative lives instead of returning to the main menu\"};\n\n    // 1.3.7.1\n    opt<bool> fix_player_grab_clip{this, defaults(true), {CompatClass::critical_update, false}, Scope::CreatorFile,\n        \"fix-player-grab-clip\", \"Fix player grab clip\", \"Don't clip downwards on powerup while digging\"};\n\n    // 1.3.7.3\n    opt<bool> fix_char_pass_balls{this, defaults(true), {CompatClass::standard_update, false}, Scope::CreatorFile,\n        \"fix-char-pass-balls\", \"Fix char-pass balls\", \"Fix char 2-4's iceballs and char 5's fire/iceballs on char-pass blocks\"};\n\n    // 1.3.8\n    opt<bool> new_conveyor_belts{this, defaults(true), {CompatClass::standard_update, false}, Scope::Creator,\n        \"new-conveyor-belts\", \"New conveyor belts\", \"Update belts to faster and more stable logic\"};\n    opt<bool> optimize_coins{this, defaults(true), {CompatClass::critical_update, false}, Scope::Creator,\n        \"optimize-coins\", \"Optimize coins\", \"Speed up the game when many coins are onscreen\"};\n    opt<bool> fix_timestop_respawn{this, defaults(true), {CompatClass::standard_update, false}, Scope::Creator,\n        \"fix-timestop-respawn\", \"Fix timestop respawn\", \"Allow NPCs to respawn during timestop\"};\n\n    /* ---- Compatibility - Speedrun ----*/\n\n    section speedrun{this, Scope::CreatorEpisode, \"speedrun\", \"Speedrun\"};\n\n    opt_enum<int> speedrun_mode{this,\n        {\n            {0, \"0\", \"0\", nullptr},\n            {1, \"1\", \"1\", nullptr},\n            {2, \"2\", \"2\", nullptr},\n            {3, \"3\", \"3\", nullptr},\n        },\n        defaults<int>(0), {}, Scope::EpisodeOptions,\n        \"mode\", \"Speedrun mode\", nullptr,\n        config_compat_changed};\n\n    enum\n    {\n        SPEEDRUN_STOP_NONE = 0,\n        SPEEDRUN_STOP_EVENT,\n        SPEEDRUN_STOP_LEAVE_LEVEL,\n        SPEEDRUN_STOP_ENTER_LEVEL\n    };\n    opt_enum<int> speedrun_stop_timer_by{this,\n        {\n            {SPEEDRUN_STOP_NONE, \"none\", \"Off\", nullptr},\n            {SPEEDRUN_STOP_EVENT, \"event\", \"Event (specify)\", nullptr},\n            {SPEEDRUN_STOP_LEAVE_LEVEL, \"leave\", \"Leave level (specify)\", nullptr},\n            {SPEEDRUN_STOP_ENTER_LEVEL, \"enter\", \"Enter level (specify)\", nullptr},\n        },\n        defaults<int>(SPEEDRUN_STOP_NONE), {}, Scope::CreatorEpisode,\n        \"stop-timer-by\", \"Stop speedrun timer on\", \"This triggers the speedrun timer to stop\"};\n    opt<std::string> speedrun_stop_timer_at{this, defaults<std::string>(\"Boss Dead\"), {}, Scope::CreatorEpisode,\n        \"stop-timer-at\", \"Stop speedrun timer event/level\", \"If above is 'Event' or 'Level', this specifies which\"};\n\n    /* ---- View Credits ----*/\n    section view_credits{this, Scope::All, \"view-credits\", \"View Credits\"};\n};\n\nvoid OpenConfig();\nvoid LoadCustomConfig();\nvoid ResetCustomConfig();\nvoid UpdateConfig();\nvoid SaveConfig();\n\nusing Options_t = _Config_t<false>;\nusing Config_t = _Config_t<true>;\n\n// extern const std::vector<const Config_t*> g_config_levels;\n\nextern _Config_t<false> g_options;\n\n// <defaults>                                1\nextern Config_t g_config_game_user;       // 3\nextern Config_t g_config_episode_creator; // 4\n// extern Config_t g_config_episode_user;    // 5\nextern Config_t g_config_file_creator;    // 6\n// extern Config_t g_config_cmdline;         // 7 // temporary\n// <cmdline>                                10\n// <usertest>                               11\n// <cheats>                                 12\n// <compat>                                 13\n\nextern Config_t g_config;\n\n#endif // CONFIG_MAIN_H\n"
  },
  {
    "path": "src/control/con_control.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CON_CONTROL_H\n#define CON_CONTROL_H\n\n#include <cstdint>\n#include <string>\n\n//Public Type conKeyboard  'Input settings for the keyboard\nstruct ConKeyboard_t\n{\n//    Up As Integer\n    int Up = 0;\n//    Down As Integer\n    int Down = 0;\n//    Left As Integer\n    int Left = 0;\n//    Right As Integer\n    int Right = 0;\n//    Jump As Integer\n    int Jump = 0;\n//    AltJump As Integer\n    int AltJump = 0;\n//    Run As Integer\n    int Run = 0;\n//    AltRun As Integer\n    int AltRun = 0;\n//    Drop As Integer\n    int Drop = 0;\n//    Start As Integer\n    int Start = 0;\n//End Type\n};\n\nstruct KM_Key\n{\n    // SDL_Joystick control\n    int val = -1;\n    int id = -1;\n    int type = -1;\n\n    // SDL_GameController control\n    int ctrl_val = -1;\n    int ctrl_id = -1;\n    int ctrl_type = -1;\n};\n\n//Public Type conJoystick   'Input settings for the joystick\nstruct ConJoystick_t\n{\n// EXTRA\n    bool isValid = false;\n    bool isGameController = false;\n    bool isHaptic = false;\n\n    enum CtrlTypes\n    {\n        NoControl=-1,\n        JoyAxis=0,\n        JoyBallX,\n        JoyBallY,\n        JoyHat,\n        JoyButton,\n        CtrlButton,\n        CtrlAxis\n    };\n\n    KM_Key Up;\n    KM_Key Down;\n    KM_Key Left;\n    KM_Key Right;\n\n//    Jump As Integer\n    KM_Key Jump;\n//    Run As Integer\n    KM_Key Run;\n//    Drop As Integer\n    KM_Key Drop;\n//    Start As Integer\n    KM_Key Start;\n//    AltJump As Integer\n    KM_Key AltJump;\n//    AltRun As Integer\n    KM_Key AltRun;\n//End Type\n};\n\n#endif // CON_CONTROL_H\n"
  },
  {
    "path": "src/control/controls.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n\n#include <AppPath/app_path.h>\n#include <Utils/files.h>\n#include <IniProcessor/ini_processing.h>\n\n#include \"../config.h\"\n#include \"../controls.h\"\n#include \"../main/record.h\"\n#include \"../main/speedrunner.h\"\n#include \"message.h\"\n#include \"change_res.h\"\n\n#include \"core/render.h\"\n#include \"core/window.h\"\n\n// for hotkeys screen\n#include \"main/menu_controls.h\"\n#include \"../game_main.h\"\n#include \"main/screen_textentry.h\"\n#include \"main/screen_quickreconnect.h\"\n#include \"main/screen_pause.h\"\n#include \"main/cheat_code.h\"\n#include \"main/menu_main.h\"\n#include \"main/translate.h\"\n#include \"main/game_strings.h\"\n#include \"../graphics.h\"\n#include \"../frame_timer.h\"\n\n#include \"control/controls_methods.h\"\n#include \"control/controls_strings.h\"\n\n#include <Logger/logger.h>\n\n\nControls_t& operator|=(Controls_t& o1, const Controls_t& o2)\n{\n    o1.Up |= o2.Up;\n    o1.Down |= o2.Down;\n    o1.Left |= o2.Left;\n    o1.Right |= o2.Right;\n\n    o1.Jump |= o2.Jump;\n    o1.AltJump |= o2.AltJump;\n\n    o1.Run |= o2.Run;\n    o1.AltRun |= o2.AltRun;\n\n    o1.Drop |= o2.Drop;\n    o1.Start |= o2.Start;\n\n    return o1;\n}\n\nControlsStrings_t g_controlsStrings;\n\n\nnamespace Controls\n{\n\nstd::vector<InputMethod*> g_InputMethods;\nstd::vector<InputMethodType*> g_InputMethodTypes;\nbool g_renderTouchscreen = false;\nHotkeysPressed_t g_hotkeysPressed;\nstatic HotkeysPressed_t s_hotkeysPressedOld;\nbool g_disallowHotkeys = false;\n\nstd::array<Controls_t, maxLocalPlayers> g_RawControls;\n\nstd::array<std::string, PlayerControls::Buttons::MAX> PlayerControls::g_button_name_UI;\nstd::array<std::string, CursorControls::Buttons::MAX> CursorControls::g_button_name_UI;\nstd::array<std::string, EditorControls::Buttons::MAX> EditorControls::g_button_name_UI;\nstd::array<std::string, Hotkeys::Buttons::MAX> Hotkeys::g_button_name_UI;\n\nvoid Hotkeys::Activate(size_t i, int player)\n{\n    if(g_disallowHotkeys || g_pollingInput)\n        return;\n\n    g_disallowHotkeys = true;\n\n    switch(i)\n    {\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    case Buttons::Fullscreen:\n        ChangeScreen();\n        return;\n#endif\n\n#ifdef USE_SCREENSHOTS_AND_RECS\n    case Buttons::Screenshot:\n        TakeScreen = true;\n        return;\n#endif\n\n#ifdef PGE_ENABLE_VIDEO_REC\n    case Buttons::RecordGif:\n        XRender::toggleGifRecorder();\n        return;\n#endif\n\n#ifdef DEBUG_BUILD\n    case Buttons::ReloadLanguage:\n    {\n        ReloadTranslations();\n        return;\n    }\n#endif\n\n    case Buttons::VanillaCam:\n        if(!GameMenu && !GameOutro && !LevelEditor && !BattleMode && !qScreen && (g_VanillaCam || l_screen->W != l_screen->canonical_screen().W || l_screen->H != l_screen->canonical_screen().H))\n        {\n            g_VanillaCam = !g_VanillaCam;\n            SoundPause[SFX_Camera] = 0;\n            PlaySoundMenu(SFX_Camera);\n            UpdateInternalRes();\n        }\n        else\n        {\n            PlaySoundMenu(SFX_BlockHit);\n        }\n\n        return;\n\n    case Buttons::DebugInfo:\n        g_stats.next_page();\n        return;\n\n    case Buttons::EnterCheats:\n        l_SharedControls.Pause = true;\n        l_SharedControls.ForcePause = true;\n        PauseScreen::UnlockCheats();\n        return;\n\n    case Buttons::ToggleHUD:\n        ShowOnScreenHUD = !ShowOnScreenHUD;\n        return;\n\n    case Buttons::LegacyPause:\n        if(player == 0)\n            l_SharedControls.Pause = true;\n\n        l_SharedControls.LegacyPause = true;\n        return;\n\n    default:\n        return;\n    }\n}\n\n/*====================================================*\\\n|| implementation for InputMethod                     ||\n\\*====================================================*/\n\nInputMethod::~InputMethod() {}\n\n// Used for battery, latency, etc\n// If the char* member is dynamically generated, you MUST use an instance-owned buffer\n// This will NOT be freed\n// returning a null pointer is allowed\nStatusInfo InputMethod::GetStatus()\n{\n    return StatusInfo();\n}\n\n// Optional function allowing developer to consume an SDL event (on SDL clients)\n//     usually used for hotkeys or for connect/disconnect events.\n// Called (1) in order of InputMethodTypes, then (2) in order of InputMethods\n// Returns true if event is consumed, false if other InputMethodTypes and InputMethods\n//     should receive it.\nbool InputMethod::ConsumeEvent(const SDL_Event* ev)\n{\n    UNUSED(ev);\n    return false;\n}\n\n\n/*====================================================*\\\n|| implementation for InputMethodProfile              ||\n\\*====================================================*/\n\nInputMethodProfile::~InputMethodProfile() {}\n\nvoid InputMethodProfile::SaveConfig_All(IniProcessing* ctl)\n{\n    if(this->Type->RumbleSupported())\n        ctl->setValue(\"enable-rumble\", this->m_rumbleEnabled);\n\n    if(this->Type->PowerStatusSupported())\n        ctl->setValue(\"show-power-status\", this->m_showPowerStatus);\n\n    ctl->setValue(\"alt-menu-controls\", this->m_altMenuControls);\n\n    this->SaveConfig(ctl);\n}\n\nvoid InputMethodProfile::LoadConfig_All(IniProcessing* ctl)\n{\n    if(this->Type->RumbleSupported())\n        ctl->read(\"enable-rumble\", this->m_rumbleEnabled, this->m_rumbleEnabled);\n\n    if(this->Type->PowerStatusSupported())\n        ctl->read(\"show-power-status\", this->m_showPowerStatus, this->m_showPowerStatus);\n\n    // defaults to false to avoid changes when users upgrade from earlier versions of TheXTech\n    ctl->read(\"alt-menu-controls\", this->m_altMenuControls, false);\n\n    this->LoadConfig(ctl);\n}\n\n// How many per-type special options are there?\nsize_t InputMethodProfile::GetOptionCount()\n{\n    size_t shared_options_count = CommonOptions::COUNT;\n\n    if(!this->Type->RumbleSupported())\n        shared_options_count -= 1;\n\n    if(!this->Type->PowerStatusSupported())\n        shared_options_count -= 1;\n\n    return shared_options_count + this->GetOptionCount_Custom();\n}\n\n// Methods to manage per-profile options\n// It is guaranteed that none of these will be called if\n// GetOptionCount() returns 0.\n// get a char* describing the option\nconst char* InputMethodProfile::GetOptionName(size_t i)\n{\n    if(!this->Type->RumbleSupported() && (int)i >= CommonOptions::rumble)\n        i += 1;\n\n    if(!this->Type->PowerStatusSupported() && (int)i >= CommonOptions::show_power_status)\n        i += 1;\n\n    if(i >= CommonOptions::COUNT)\n        return this->GetOptionName_Custom(i - CommonOptions::COUNT);\n\n    if(i == CommonOptions::rumble)\n        return g_mainMenu.controlsOptionRumble.c_str();\n    else if(i == CommonOptions::show_power_status)\n        return g_mainMenu.controlsOptionBatteryStatus.c_str();\n    else if(i == CommonOptions::alt_menu_controls) // -V547 Should be here to fail when adding new enum fields\n        return g_mainMenu.controlsOptionAltMenuControls.c_str();\n    else\n        return nullptr;\n}\n// get a char* describing the current option value\n// must be allocated in static or instance memory\n// WILL NOT be freed\nconst char* InputMethodProfile::GetOptionValue(size_t i)\n{\n    if(!this->Type->RumbleSupported() && (int)i >= CommonOptions::rumble)\n        i += 1;\n\n    if(!this->Type->PowerStatusSupported() && (int)i >= CommonOptions::show_power_status)\n        i += 1;\n\n    if(i >= CommonOptions::COUNT)\n        return this->GetOptionValue_Custom(i - CommonOptions::COUNT);\n\n    if(i == CommonOptions::rumble)\n    {\n        if(this->m_rumbleEnabled)\n            return g_mainMenu.wordOn.c_str();\n        else\n            return g_mainMenu.wordOff.c_str();\n    }\n    else if(i == CommonOptions::show_power_status)\n    {\n        if(this->m_showPowerStatus)\n            return g_mainMenu.wordShow.c_str();\n        else\n            return g_mainMenu.wordHide.c_str();\n    }\n    else if(i == CommonOptions::alt_menu_controls) // -V547 Should be here to fail when adding new enum fields\n    {\n        if(this->m_altMenuControls)\n            return g_mainMenu.wordOn.c_str();\n        else\n            return g_mainMenu.wordOff.c_str();\n    }\n    else\n        return nullptr;\n}\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodProfile::OptionChange(size_t i)\n{\n    if(!this->Type->RumbleSupported() && (int)i >= CommonOptions::rumble)\n        i += 1;\n\n    if(!this->Type->PowerStatusSupported() && (int)i >= CommonOptions::show_power_status)\n        i += 1;\n\n    if(i >= CommonOptions::COUNT)\n        return this->OptionChange_Custom(i - CommonOptions::COUNT);\n\n    if(i == CommonOptions::rumble)\n    {\n        this->m_rumbleEnabled = !this->m_rumbleEnabled;\n\n        for(InputMethod* m : g_InputMethods)\n        {\n            if(!m || m->Profile != this)\n                continue;\n\n            if(this->m_rumbleEnabled)\n                m->Rumble(200, .5);\n        }\n\n        return true;\n    }\n    else if(i == CommonOptions::show_power_status)\n    {\n        this->m_showPowerStatus = !this->m_showPowerStatus;\n        return true;\n    }\n    else if(i == CommonOptions::alt_menu_controls) // -V547 Should be here to fail when adding new enum fields\n    {\n        this->m_altMenuControls = !this->m_altMenuControls;\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n// called when left is pressed\nbool InputMethodProfile::OptionRotateLeft(size_t i)\n{\n    int i_proc = (int)i;\n\n    if(!this->Type->RumbleSupported() && (int)i_proc >= CommonOptions::rumble)\n        i_proc += 1;\n\n    if(!this->Type->PowerStatusSupported() && (int)i_proc >= CommonOptions::show_power_status)\n        i_proc += 1;\n\n    if(i_proc >= CommonOptions::COUNT)\n        return this->OptionRotateLeft_Custom(i_proc - CommonOptions::COUNT);\n\n    return this->OptionChange(i);\n}\n// called when right is pressed\nbool InputMethodProfile::OptionRotateRight(size_t i)\n{\n    int i_proc = (int)i;\n\n    if(!this->Type->RumbleSupported() && (int)i_proc >= CommonOptions::rumble)\n        i_proc += 1;\n\n    if(!this->Type->PowerStatusSupported() && (int)i_proc >= CommonOptions::show_power_status)\n        i_proc += 1;\n\n    if(i_proc >= CommonOptions::COUNT)\n        return this->OptionRotateRight_Custom(i_proc - CommonOptions::COUNT);\n\n    return this->OptionChange(i);\n}\n\n// How many per-type special options are there?\nsize_t InputMethodProfile::GetOptionCount_Custom()\n{\n    return 0;\n}\n\n// get a nullable char* describing the option\nconst char* InputMethodProfile::GetOptionName_Custom(size_t i)\n{\n    UNUSED(i);\n    return nullptr;\n}\n// get a nullable char* describing the current option value\n// must be allocated in static or instance memory\nconst char* InputMethodProfile::GetOptionValue_Custom(size_t i)\n{\n    UNUSED(i);\n    return nullptr;\n}\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodProfile::OptionChange_Custom(size_t i)\n{\n    UNUSED(i);\n    return false;\n}\n// called when left is pressed\nbool InputMethodProfile::OptionRotateLeft_Custom(size_t i)\n{\n    UNUSED(i);\n    return false;\n}\n// called when right is pressed\nbool InputMethodProfile::OptionRotateRight_Custom(size_t i)\n{\n    UNUSED(i);\n    return false;\n}\n\n/*====================================================*\\\n|| implementation for InputMethodType                 ||\n\\*====================================================*/\n\nInputMethodType::~InputMethodType()\n{\n    for(InputMethodProfile* p : this->m_profiles)\n        delete p;\n\n    this->m_profiles.clear();\n}\n\nconst std::string& InputMethodType::LocalName() const\n{\n    return this->Name;\n}\n\nstd::vector<InputMethodProfile*> InputMethodType::GetProfiles()\n{\n    return this->m_profiles;\n}\n\nInputMethodProfile* InputMethodType::AddProfile()\n{\n    InputMethodProfile* profile = this->AllocateProfile();\n\n    if(!profile)\n        return nullptr;\n\n    profile->Type = this;\n    this->m_profiles.push_back((InputMethodProfile*) profile);\n    profile->Name = this->LocalName() + \" \" + std::to_string(this->m_profiles.size());\n\n    return profile;\n}\n\n// be extremely careful never to delete a profile that is in use\nbool InputMethodType::DeleteProfile(InputMethodProfile* profile, const std::vector<InputMethod*>& active_methods)\n{\n    if(!profile)\n        return false;\n\n    // delete from m_profiles\n    std::vector<InputMethodProfile*>::iterator loc = std::find(this->m_profiles.begin(), this->m_profiles.end(), profile);\n\n    if(loc == this->m_profiles.end())\n        return false;\n\n    for(InputMethod* method : active_methods)\n    {\n        if(!method)\n            continue;\n\n        if(method->Profile == profile)\n            DeleteInputMethod(method);\n    }\n\n    for(int i = 0; i < maxLocalPlayers; i++)\n    {\n        if(this->m_defaultProfiles[i] == profile)\n        {\n            // m_defaultProfiles is nullable, so this is okay\n            this->m_defaultProfiles[i] = nullptr;\n        }\n    }\n\n    if(!this->DeleteProfile_Custom(profile, active_methods))\n        return false;\n\n    this->m_profiles.erase(loc);\n\n    // deallocate\n    delete profile;\n\n    return true;\n}\n\nbool InputMethodType::ClearProfiles(const std::vector<InputMethod*>& active_methods)\n{\n    // clear existing profiles safely\n    size_t i = 0;\n\n    while(i < this->m_profiles.size())\n    {\n        // only increment the index when deletion fails\n        if(!this->DeleteProfile(this->m_profiles[i], active_methods))\n            i++;\n    }\n\n    // succeeded if all were cleared successfully\n    return (i == 0);\n}\n\nbool InputMethodType::SetProfile(InputMethod* method, int player_no, InputMethodProfile* profile, const std::vector<InputMethod*>& active_methods)\n{\n    if(player_no < 0 || player_no >= maxLocalPlayers || !profile)\n        return false;\n\n    if(!this->TestProfileType(profile))\n        return false;\n\n    if(!this->SetProfile_Custom(method, player_no, profile, active_methods))\n        return false;\n\n    this->SetDefaultProfile(player_no, profile);\n    method->Profile = profile;\n\n    return true;\n}\n\nvoid InputMethodType::SetDefaultProfile(int player_no, InputMethodProfile* profile)\n{\n    if(player_no < 0 || player_no >= maxLocalPlayers || !profile)\n        return;\n\n    this->m_defaultProfiles[player_no] = profile;\n}\n\nInputMethodProfile* InputMethodType::GetDefaultProfile(int player_no)\n{\n    if(player_no < 0 || player_no >= maxLocalPlayers)\n        return nullptr;\n\n    // still possibly a null pointer\n    return this->m_defaultProfiles[player_no];\n}\n\nvoid InputMethodType::SaveConfig(IniProcessing* ctl)\n{\n    ctl->beginGroup(this->Name);\n    this->SaveConfig_Custom(ctl);\n\n    // save the number of profiles\n    ctl->setValue(\"n-profiles\", this->m_profiles.size());\n\n    // save the default (most recent) profile for each player\n    for(int i = 0; i < maxLocalPlayers; i++)\n    {\n        InputMethodProfile* default_profile = this->m_defaultProfiles[i];\n        std::string default_profile_key = \"default-profile-\" + std::to_string(i + 1);\n\n        if(default_profile == nullptr)\n        {\n            ctl->setValue(default_profile_key.c_str(), -1);\n            continue;\n        }\n\n        std::vector<InputMethodProfile*>::iterator loc = std::find(this->m_profiles.begin(), this->m_profiles.end(), default_profile);\n        size_t index = loc - this->m_profiles.begin();\n\n        if(index == this->m_profiles.size())\n            ctl->setValue(default_profile_key.c_str(), -1);\n        else\n            ctl->setValue(default_profile_key.c_str(), index);\n    }\n\n    ctl->endGroup();\n\n    // save each profile\n    for(size_t i = 0; i < this->m_profiles.size(); i++)\n    {\n        ctl->beginGroup(this->Name + \"-\" + std::to_string(i + 1));\n        ctl->setValue(\"name\", this->m_profiles[i]->Name);\n        this->m_profiles[i]->SaveConfig_All(ctl);\n        ctl->endGroup();\n    }\n}\n\nvoid InputMethodType::LoadConfig(IniProcessing* ctl)\n{\n    int n_profiles;\n    int n_existing = (int)this->m_profiles.size(); // should usually be zero\n\n    ctl->beginGroup(this->Name);\n    ctl->read(\"n-profiles\", n_profiles, 2);\n    ctl->endGroup();\n\n    for(int i = 0; i < n_profiles; i++)\n    {\n        std::string group_name = this->Name + \"-\" + std::to_string(i + n_existing + 1);\n\n        // look for legacy profile if there is no modern profile\n        if(!ctl->contains(group_name))\n        {\n            if(!this->LegacyName.empty())\n            {\n                group_name = \"player-\" + std::to_string(i + 1) + \"-\" + this->LegacyName;\n\n                if(!ctl->contains(group_name))\n                    continue;\n            }\n            else\n                continue;\n        }\n\n        // found a profile in the INI, now allocate and load it\n        InputMethodProfile* new_profile = this->AddProfile();\n\n        if(new_profile)\n        {\n            ctl->beginGroup(group_name);\n            new_profile->LoadConfig_All(ctl);\n            ctl->read(\"name\", new_profile->Name, this->LocalName() + \" \" + std::to_string(i + n_existing + 1));\n            ctl->endGroup();\n        }\n    }\n\n    // load the default recent profile for each player\n    ctl->beginGroup(this->Name);\n\n    for(int i = 0; i < maxLocalPlayers; i++)\n    {\n        std::string default_profile_key = \"default-profile-\" + std::to_string(i + 1);\n        int index;\n        ctl->read(default_profile_key.c_str(), index, -1);\n\n        if(index == -1)\n        {\n            this->m_defaultProfiles[i] = nullptr;\n        }\n        else\n        {\n            index += n_existing;\n            if(index < (int)this->m_profiles.size())\n                this->m_defaultProfiles[i] = this->m_profiles[index];\n            else\n            {\n                pLogWarning(\"Attempt to get the profile index %d out of range (m_profiles size: %d)\", index, (int)this->m_profiles.size());\n                this->m_defaultProfiles[i] = nullptr;\n            }\n        }\n    }\n\n    this->LoadConfig_Custom(ctl);\n    ctl->endGroup();\n}\n\n// optionally overriden methods\n\nbool InputMethodType::ConsumeEvent(const SDL_Event* ev)\n{\n    UNUSED(ev);\n    return false;\n}\n\nbool InputMethodType::SetProfile_Custom(InputMethod* method, int player_no, InputMethodProfile* profile, const std::vector<InputMethod*>& active_methods)\n{\n    (void)active_methods;\n\n    if(!method || !profile || player_no < 0 || player_no >= maxLocalPlayers)\n        return false;\n\n    return true;\n}\n\nbool InputMethodType::DeleteProfile_Custom(InputMethodProfile* profile, const std::vector<InputMethod*>& active_methods)\n{\n    UNUSED(profile);\n    UNUSED(active_methods);\n    return true;\n}\n\n// How many per-type special options are there?\nsize_t InputMethodType::GetOptionCount()\n{\n    return 0;\n}\n\n// get a nullable char* describing the option\nconst char* InputMethodType::GetOptionName(size_t i)\n{\n    UNUSED(i);\n    return nullptr;\n}\n// get a nullable char* describing the current option value\n// must be allocated in static or instance memory\nconst char* InputMethodType::GetOptionValue(size_t i)\n{\n    UNUSED(i);\n    return nullptr;\n}\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodType::OptionChange(size_t i)\n{\n    UNUSED(i);\n    return false;\n}\n// called when left is pressed\nbool InputMethodType::OptionRotateLeft(size_t i)\n{\n    UNUSED(i);\n    return false;\n}\n// called when right is pressed\nbool InputMethodType::OptionRotateRight(size_t i)\n{\n    UNUSED(i);\n    return false;\n}\n\nvoid InputMethodType::SaveConfig_Custom(IniProcessing* ctl)\n{\n    UNUSED(ctl);\n    // must be implemented if user has created special options\n    SDL_assert_release(this->GetOptionCount() == 0);\n}\nvoid InputMethodType::LoadConfig_Custom(IniProcessing* ctl)\n{\n    UNUSED(ctl);\n    // must be implemented if user has created special options\n    SDL_assert_release(this->GetOptionCount() == 0);\n}\n\n/*====================================================*\\\n|| implementation for global functions                ||\n\\*====================================================*/\n\nvoid InitStrings()\n{\n    // initialize the strings for localization\n    for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n        PlayerControls::g_button_name_UI[i] = PlayerControls::GetButtonName_UI_Init(i);\n\n    for(size_t i = 0; i < CursorControls::n_buttons; i++)\n        CursorControls::g_button_name_UI[i] = CursorControls::GetButtonName_UI_Init(i);\n\n    for(size_t i = 0; i < EditorControls::n_buttons; i++)\n        EditorControls::g_button_name_UI[i] = EditorControls::GetButtonName_UI_Init(i);\n\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n        Hotkeys::g_button_name_UI[i] = Hotkeys::GetButtonName_UI_Init(i);\n}\n\n/*====================================================*\\\n||                                                    ||\n||         ADD EVERY NEW INPUT METHOD HERE,           ||\n||           IF SUPPORTED AT COMPILE TIME             ||\n||                                                    ||\n\\*====================================================*/\n\n// allocate InputMethodTypes according to system configuration\nvoid Init()\n{\n#ifdef INPUT_3DS_H\n    g_InputMethodTypes.push_back(new InputMethodType_3DS);\n#endif\n#ifdef INPUT_WII_H\n    g_InputMethodTypes.push_back(new InputMethodType_Wii);\n#endif\n#ifdef INPUT_WII_GC_H\n    g_InputMethodTypes.push_back(new InputMethodType_GameCube);\n#endif\n#ifdef INPUT_16M_H\n    g_InputMethodTypes.push_back(new InputMethodType_16M);\n#endif\n#ifdef KEYBOARD_H\n    g_InputMethodTypes.push_back(new InputMethodType_Keyboard);\n#endif\n#ifdef JOYSTICK_H\n    g_InputMethodTypes.push_back(new InputMethodType_Joystick);\n#endif\n#ifdef TOUCHSCREEN_H\n    g_InputMethodTypes.push_back(new InputMethodType_TouchScreen);\n#endif\n\n    InitStrings();\n\n    // not yet ready for prime time\n    // g_InputMethodTypes.push_back(new InputMethodType_Duplicate);\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n    {\n        g_hotkeysPressed[i] = -1;\n        s_hotkeysPressedOld[i] = -1;\n    }\n}\n\nvoid Quit()\n{\n    ClearInputMethods();\n\n    for(InputMethodType* type : g_InputMethodTypes)\n        delete type;\n\n    g_InputMethodTypes.clear();\n}\n\n// (for SDL clients) process SDL_Event using active InputMethodTypes\n// return true if successfully processed, false if unrecognized\nbool ProcessEvent(const SDL_Event* ev)\n{\n    for(InputMethodType* type : g_InputMethodTypes)\n    {\n        if(type && type->ConsumeEvent(ev))\n            return true;\n    }\n\n    for(InputMethod* method : g_InputMethods)\n    {\n        if(method && method->ConsumeEvent(ev))\n            return true;\n    }\n\n    return false;\n}\n\n// 1. Calls the UpdateControlsPre hooks of loaded InputMethodTypes\n//    a. Syncs hardware state as needed\n// 2. Updates Player and Editor controls by calling currently bound InputMethods\n//    a. May call or set changeScreen, frmMain.toggleGifRecorder, TakeScreen, g_stats.enabled, etc\n// 3. Calls the UpdateControlsPost hooks of loaded InputMethodTypes\n//    a. May call or set changeScreen, frmMain.toggleGifRecorder, TakeScreen, g_stats.enabled, etc\n//    b. May update l_SharedControls or SharedCursor using hardcoded keys\n// 4. Updates speedrun and recording modules\n// 5. Resolves inconsistent control states (Left+Right, etc)\nbool Update(bool check_lost_devices)\n{\n    bool okay = true;\n    bool prev_okay = true;\n\n    // track whether shared controls buttons were pressed last frame, to modify the global versions\n    bool old_shared_pause = l_SharedControls.Pause;\n    bool old_shared_legacy = l_SharedControls.LegacyPause;\n    bool old_shared_force = l_SharedControls.ForcePause;\n\n    // reset per-frame state for SharedCursor\n    SharedCursor.Move = false;\n    SharedCursor.Primary = false;\n    SharedCursor.Secondary = false;\n    SharedCursor.Tertiary = false;\n    SharedCursor.ScrollUp = false;\n    SharedCursor.ScrollDown = false;\n    // reset l_SharedControls\n    l_SharedControls = SharedControls_t();\n\n    // reset EditorControls\n    ::EditorControls = EditorControls_t();\n\n    for(InputMethodType* type : g_InputMethodTypes)\n        type->UpdateControlsPre();\n\n    Controls_t blankControls = Controls_t();\n\n    for(int i = 0; i < l_screen->player_count; i++)\n    {\n        Controls_t newControls = blankControls;\n\n        CursorControls_t& cursor = SharedCursor;\n        EditorControls_t& editor = ::EditorControls;\n\n        if(i < (int)g_InputMethods.size() && g_InputMethods[i])\n        {\n            InputMethod* method = g_InputMethods[i];\n\n            if(!method->Update(i + 1, newControls, cursor, editor, g_hotkeysPressed) && check_lost_devices)\n            {\n                okay = false;\n                DeleteInputMethod(method);\n                // the method pointer is no longer valid\n            }\n        }\n        else\n            prev_okay = false;\n\n        if(g_hotkeysPressed[Hotkeys::Buttons::LegacyPause] == i + 1)\n            newControls.Start = true;\n\n        // push messages corresponding to controls press / release\n        XMessage::PushControls(i, newControls);\n    }\n\n    for(InputMethodType* type : g_InputMethodTypes)\n        type->UpdateControlsPost();\n\n    // call pause screen logic if needed (this is needed for pause screen to use local controls state to trigger events)\n    if(GamePaused == PauseCode::PauseScreen)\n        PauseScreen::ControlsLogic();\n\n    // trigger hotkeys\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n    {\n        if(s_hotkeysPressedOld[i] != g_hotkeysPressed[i] && g_hotkeysPressed[i] != -1)\n            Hotkeys::Activate(i, g_hotkeysPressed[i]);\n\n        s_hotkeysPressedOld[i] = g_hotkeysPressed[i];\n        g_hotkeysPressed[i] = -1;\n    }\n\n    // push shared controls\n    if(l_SharedControls.Pause && !old_shared_pause)\n        XMessage::PushMessage({XMessage::Type::shared_controls, 0, 0});\n\n    if(l_SharedControls.LegacyPause && !old_shared_legacy)\n        XMessage::PushMessage({XMessage::Type::shared_controls, 0, 1});\n\n    if(l_SharedControls.ForcePause && !old_shared_force)\n        XMessage::PushMessage({XMessage::Type::shared_controls, 0, 2});\n\n    SharedPause = false;\n    SharedPauseLegacy = false;\n    SharedPauseForce = false;\n\n    // sync any messages, and reset player controls to raw controls\n    XMessage::Tick();\n\n    if(SharedCursor.Move)\n    {\n        if(SharedCursor.X >= 0 && SharedCursor.Y >= 0)\n        {\n            int window_x, window_y;\n            XRender::mapFromScreen((int)SharedCursor.X, (int)SharedCursor.Y, &window_x, &window_y);\n            XWindow::placeCursor(window_x, window_y);\n        }\n    }\n\n    // sync controls\n    Record::Sync();\n\n    for(int i = 0; i < l_screen->player_count; i++)\n        speedRun_syncControlKeys(i, Player[l_screen->players[i]].Controls);\n\n    // resolve invalid states and override players without controls\n    For(B, 1, numPlayers)\n    {\n        // erase for non-existant players during main menu and outro\n        if((GameMenu || GameOutro) && B > (int)g_InputMethods.size())\n            Player[B].Controls = blankControls;\n\n        // override in ClonedPlayerMode and SingleCoop mode (slightly different from original case which checked if numPlayers != 2)\n        if((g_ClonedPlayerMode || SingleCoop) && B > 1)\n        {\n            // new location for cloned player code (\"superbdemo128\", was misplaced in ClownCar previously)\n            Player[B].Controls = Player[1].Controls;\n\n            // allow pausing in SingleCoop\n            if(SingleCoop)\n                Player[B].UnStart |= Player[1].UnStart;\n\n            continue;\n        }\n\n        auto& p = Player[B];\n        Controls_t& c = p.Controls;\n\n        if(!c.Start && !c.Jump)\n            p.UnStart = true;\n\n        if(c.Up && c.Down)\n        {\n            c.Up = false;\n            c.Down = false;\n        }\n\n        if(c.Left && c.Right)\n        {\n            c.Left = false;\n            c.Right = false;\n        }\n\n        // don't remap AltRun when player has Statue power\n        if(!(p.State == 5 && p.Mount == 0) && c.AltRun)\n            c.Run = true;\n\n        if(ForcedControls && GamePaused == PauseCode::None)\n            c = ForcedControl;\n    }\n\n    // single coop code -- may want to revise\n    if(SingleCoop > 0)\n    {\n        if(numPlayers == 1 || g_ClonedPlayerMode)\n            SingleCoop = 0;\n\n        if(SingleCoop == 1)\n            Player[2].Controls = blankControls;\n        else\n            Player[1].Controls = blankControls;\n    }\n\n    For(A, 1, numPlayers)\n    {\n        {\n            Player_t& p = Player[A];\n\n            if(p.SpinJump)\n                p.Direction = (p.SpinFrame < 4 || p.SpinFrame > 9) ? -1 : 1;\n        }\n    }\n\n    // indicate if some control slots are missing\n    if(!prev_okay && !SingleCoop && !GameMenu && !LevelEditor && !Record::replay_file && check_lost_devices)\n        okay = false;\n\n    g_disallowHotkeys = false;\n\n    return okay;\n}\n\nvoid SaveConfig(IniProcessing* ctl)\n{\n    for(InputMethodType* type : g_InputMethodTypes)\n        type->SaveConfig(ctl);\n}\n\nvoid LoadConfig(IniProcessing* ctl)\n{\n    for(InputMethodType* type : g_InputMethodTypes)\n    {\n        type->ClearProfiles(g_InputMethods);\n        type->LoadConfig(ctl);\n    }\n}\n\nvoid SaveConfig()\n{\n    std::string controlsPath = AppPathManager::settingsControlsFileSTD();\n    IniProcessing controls(controlsPath);\n    SaveConfig(&controls);\n    controls.writeIniFile();\n    AppPathManager::syncFs();\n\n    pLogDebug(\"Saved control mappings: %s\", controlsPath.c_str());\n}\n\nvoid LoadConfig()\n{\n    std::string configPath = AppPathManager::settingsFileSTD();\n    std::string controlsPath = AppPathManager::settingsControlsFileSTD();\n\n    // Keep backward compatibility and restore old mappings from the \"thextech.ini\"\n    const std::string *load_path = Files::fileExists(controlsPath) ? &controlsPath :\n        Files::fileExists(configPath) ? &configPath : nullptr;\n\n    if(!load_path)\n    {\n        // make a new empty config file\n        SaveConfig();\n    }\n    else\n    {\n        IniProcessing controls(*load_path);\n        LoadConfig(&controls);\n    }\n}\n\nInputMethod* PollInputMethod() noexcept\n{\n    // don't poll unless there is a free slot\n    size_t player_no = 0;\n\n    while(player_no < g_InputMethods.size() && g_InputMethods[player_no] != nullptr)\n        player_no ++;\n\n    if(player_no >= maxLocalPlayers)\n        return nullptr;\n\n    // try all input method types\n    InputMethod* new_method = nullptr;\n\n    for(InputMethodType* type : g_InputMethodTypes)\n    {\n        new_method = type->Poll(g_InputMethods);\n\n        if(new_method)\n            break;\n    }\n\n    if(!new_method)\n        return nullptr;\n\n    // check that the InputMethodType properly assigned itself as the new InputMethod's Type\n    SDL_assert_release(new_method->Type != nullptr); // InputMethodType did not assign itself as Type for new InputMethod\n\n    if(!new_method->Type)\n        return nullptr;\n\n    // tentatively add to the input method list so we can call SetInputMethodProfile\n    if(player_no < g_InputMethods.size())\n        g_InputMethods[player_no] = new_method;\n    else\n        g_InputMethods.push_back(new_method);\n\n    // if a profile has already been assigned, activate any hooks possible\n    if(new_method->Profile)\n        SetInputMethodProfile((int)player_no, new_method->Profile);\n\n    // try a number of ways of assigning a profile if one has not already been assigned\n    std::vector<InputMethodProfile*> profiles = new_method->Type->GetProfiles();\n\n    // fallback 1: last profile used by this player index\n    if(!new_method->Profile)\n    {\n        InputMethodProfile* default_profile = new_method->Type->GetDefaultProfile((int)player_no);\n\n        if(default_profile)\n            SetInputMethodProfile((int)player_no, default_profile);\n    }\n\n    // fallback 2: find first unused profile\n    if(!new_method->Profile)\n    {\n        size_t i;\n\n        for(i = 0; i < profiles.size(); i++)\n        {\n            bool unused = true;\n\n            for(InputMethod* other_method : g_InputMethods)\n            {\n                if(other_method && other_method->Profile == profiles[i])\n                {\n                    unused = false;\n                    break;\n                }\n            }\n\n            if(unused)\n                break;\n        }\n\n        if(i != profiles.size())\n            SetInputMethodProfile((int)player_no, profiles[i]);\n    }\n\n    // fallback 3: use first profile\n    if(!new_method->Profile && !profiles.empty())\n        SetInputMethodProfile((int)player_no, profiles[0]);\n\n    // fallback 4: new default profile\n    if(!new_method->Profile)\n        SetInputMethodProfile((int)player_no, new_method->Type->AddProfile());\n\n    // should only STILL be null if something is very wrong (alloc failed, etc)\n    if(!new_method->Profile)\n    {\n        pLogWarning(\"Failed to find/alloc/set profile for new %s.\",\n                    new_method->Type->Name.c_str());\n        delete new_method;\n        return nullptr;\n    }\n\n    pLogDebug(\"Just activated new %s '%s' with profile '%s'.\",\n              new_method->Type->Name.c_str(),\n              new_method->Name.c_str(),\n              new_method->Profile->Name.c_str());\n\n    return new_method;\n}\n\nvoid DeleteInputMethod(InputMethod* method)\n{\n    if(!method)\n        return;\n\n    std::vector<InputMethod*>::iterator loc = std::find(g_InputMethods.begin(), g_InputMethods.end(), method);\n\n    while(loc != g_InputMethods.end())\n    {\n        *loc = nullptr;\n        loc = std::find(g_InputMethods.begin(), g_InputMethods.end(), method);\n    }\n\n    pLogDebug(\"Just disconnected %s '%s' with profile '%s'.\",\n              method->Type->Name.c_str(),  method->Name.c_str(), method->Profile->Name.c_str());\n    delete method;\n}\n\nvoid DeleteInputMethodSlot(int slot)\n{\n    if(slot < 0 || (size_t)slot > g_InputMethods.size())\n        return;\n\n    if(g_InputMethods[slot] != nullptr)\n        DeleteInputMethod(g_InputMethods[slot]);\n\n    g_InputMethods.erase(g_InputMethods.begin() + slot);\n}\n\nbool SetInputMethodProfile(int player_no, InputMethodProfile* profile)\n{\n    if(player_no >= (int)g_InputMethods.size() || !g_InputMethods[player_no] || !profile)\n        return false;\n\n    InputMethod* method = g_InputMethods[player_no];\n\n    if(!method->Type)\n        return false;\n\n    return method->Type->SetProfile(method, player_no, profile, g_InputMethods);\n}\n\nbool SetInputMethodProfile(InputMethod* method, InputMethodProfile* profile)\n{\n    if(!method || !profile || !method->Type)\n        return false;\n\n    std::vector<InputMethod*>::iterator loc = std::find(g_InputMethods.begin(), g_InputMethods.end(), method);\n    size_t player_no = loc - g_InputMethods.begin();\n\n    if(player_no == g_InputMethods.size())\n        return false;\n\n    return method->Type->SetProfile(method, (int)player_no, profile, g_InputMethods);\n}\n\nvoid ClearInputMethods()\n{\n    for(size_t i = 0; i < g_InputMethods.size(); i++)\n        DeleteInputMethod(g_InputMethods[i]);\n\n    g_InputMethods.clear();\n}\n\nvoid RemoveNullInputMethods()\n{\n    // remove any null input methods\n    for(size_t i = 0; i < Controls::g_InputMethods.size(); )\n    {\n        if(Controls::g_InputMethods[i])\n            i++;\n        else\n            Controls::DeleteInputMethodSlot(i);\n    }\n}\n\nMenuControls_t GetMenuControls(int limit_player)\n{\n    MenuControls_t ret;\n    ret.Up = l_SharedControls.MenuUp;\n    ret.Down = l_SharedControls.MenuDown;\n    ret.Left = l_SharedControls.MenuLeft;\n    ret.Right = l_SharedControls.MenuRight;\n    ret.Home = SharedCursor.Tertiary;\n\n    ret.Do = l_SharedControls.MenuDo || l_SharedControls.Pause;\n    ret.Back = l_SharedControls.MenuBack;\n\n    ret.Erase = false;\n\n    for(size_t i = 0; l_screen && i < (size_t)l_screen->player_count; i++)\n    {\n        if(limit_player != 0 && l_screen->players[i] != limit_player)\n            continue;\n\n        const Controls_t &c = g_RawControls[i];\n\n        // alt menu controls: Jump is back, AltJump is Do\n        if(i < g_InputMethods.size() && g_InputMethods[i] && g_InputMethods[i]->Profile && g_InputMethods[i]->Profile->m_altMenuControls)\n        {\n            ret.Back |= c.Jump;\n            ret.Do |= c.AltJump;\n        }\n        // normal menu controls: Jump is Do, AltJump is Back\n        else\n        {\n            ret.Do |= c.Jump;\n            ret.Back |= c.AltJump;\n        }\n\n        ret.Do |= c.Start;\n        ret.Back |= c.Run;\n\n        ret.Up |= c.Up;\n        ret.Down |= c.Down;\n        ret.Left |= c.Left;\n        ret.Right |= c.Right;\n\n        ret.Home |= c.Drop;\n        ret.Erase |= c.AltRun;\n    }\n\n    return ret;\n}\n\n\n// player is 1-indexed :(\nvoid Rumble(int player, int ms, float strength)\n{\n    if(GameMenu || GameOutro)\n        return;\n\n    const Screen_t& screen = ScreenByPlayer(player);\n\n    if(&screen != l_screen)\n        return;\n\n    int l_player_i = 0;\n    while(l_player_i < screen.player_count && player != screen.players[l_player_i])\n        l_player_i++;\n\n    if(l_player_i == screen.player_count)\n        return;\n\n    if(l_player_i >= (int)g_InputMethods.size())\n        return;\n\n    if(!g_InputMethods[l_player_i])\n        return;\n\n    if(!g_InputMethods[l_player_i]->Profile)\n        return;\n\n    if(!g_InputMethods[l_player_i]->Profile->m_rumbleEnabled)\n        return;\n\n    g_InputMethods[l_player_i]->Rumble(ms, strength);\n}\n\nvoid RumbleAllPlayers(int ms, float strength)\n{\n    if(GameMenu || GameOutro)\n        return;\n\n    for(InputMethod* method : g_InputMethods)\n    {\n        if(!method)\n            continue;\n\n        if(!method->Profile)\n            continue;\n\n        if(!method->Profile->m_rumbleEnabled)\n            continue;\n\n        method->Rumble(ms, strength);\n    }\n}\n\n\nStatusInfo GetStatus(int l_player_i)\n{\n    // return something blank if player doesn't exist\n    if(l_player_i < 0 || l_player_i >= (int)g_InputMethods.size() || g_InputMethods[l_player_i] == nullptr)\n        return StatusInfo();\n\n    // return something blank if l_player_i doesn't want to show status\n    if(!g_InputMethods[l_player_i]->Profile || !g_InputMethods[l_player_i]->Profile->m_showPowerStatus)\n        return StatusInfo();\n\n    return g_InputMethods[l_player_i]->GetStatus();\n}\n\n\nvoid RenderTouchControls()\n{\n#ifdef TOUCHSCREEN_H\n    // only want to render when the touchscreen is in use\n    InputMethod_TouchScreen* active_touchscreen = nullptr;\n    int player_no = 1;\n\n    for(size_t i = 0; i < g_InputMethods.size(); i++)\n    {\n        InputMethod* method = g_InputMethods[i];\n\n        if(!method)\n            continue;\n\n        auto* m = dynamic_cast<InputMethod_TouchScreen*>(method);\n        if(m)\n        {\n            active_touchscreen = m;\n            player_no = (int)i + 1;\n            break;\n        }\n    }\n\n    if(!g_renderTouchscreen)\n        return;\n\n    InputMethodType_TouchScreen* touchscreen = nullptr;\n\n    if(active_touchscreen)\n        touchscreen = dynamic_cast<InputMethodType_TouchScreen*>(active_touchscreen->Type);\n    else\n    {\n        for(InputMethodType* type : g_InputMethodTypes)\n        {\n            if(!type)\n                continue;\n\n            auto* t = dynamic_cast<InputMethodType_TouchScreen*>(type);\n            if(t)\n            {\n                touchscreen = t;\n                break;\n            }\n        }\n    }\n\n    if(!touchscreen)\n        return;\n\n    touchscreen->m_controller.render(player_no);\n#endif // #ifdef TOUCHSCREEN_H\n}\n\nvoid UpdateTouchScreenSize()\n{\n#ifdef TOUCHSCREEN_H\n    InputMethodType_TouchScreen* touchscreen = nullptr;\n\n    for(InputMethodType* type : g_InputMethodTypes)\n    {\n        if(!type)\n            continue;\n\n        auto* t = dynamic_cast<InputMethodType_TouchScreen*>(type);\n\n        if(t)\n        {\n            touchscreen = t;\n            break;\n        }\n    }\n\n    if(!touchscreen)\n        return;\n\n    touchscreen->m_controller.updateScreenSize();\n#endif // #ifdef TOUCHSCREEN_H\n}\n\nvoid LoadTouchScreenGFX()\n{\n#ifdef TOUCHSCREEN_H\n    InputMethodType_TouchScreen* touchscreen = nullptr;\n\n    for(InputMethodType* type : g_InputMethodTypes)\n    {\n        if(!type)\n            continue;\n\n        auto* t = dynamic_cast<InputMethodType_TouchScreen*>(type);\n\n        if(t)\n        {\n            touchscreen = t;\n            break;\n        }\n    }\n\n    if(!touchscreen)\n        return;\n\n    touchscreen->m_controller.loadGFX();\n#endif // #ifdef TOUCHSCREEN_H\n}\n\n} // namespace Controls\n\n"
  },
  {
    "path": "src/control/controls_methods.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef CONTROLS_METHODS_H\n#define CONTROLS_METHODS_H\n\n#if   defined(__3DS__)\n\n#include \"control/input_3ds.h\"\n\n#elif defined(__WII__)\n\n#include \"control/input_wii.h\"\n#include \"control/input_wii_gc.h\"\n\n#elif defined(__16M__)\n\n#include \"control/input_16m.h\"\n\n#elif !defined(THEXTECH_NO_SDL_BUILD) && !defined(THEXTECH_CLI_BUILD)\n\n#include \"control/keyboard.h\"\n#include \"control/joystick.h\"\n#include \"control/touchscreen.h\"\n\n#endif\n\n#include \"control/duplicate.h\"\n\n#endif // #ifndef CONTROLS_METHODS_H\n"
  },
  {
    "path": "src/control/controls_strings.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef CONTROLS_STRINGS_H\n#define CONTROLS_STRINGS_H\n\n#include <string>\n\n#include \"control/controls_methods.h\"\n\n// configuration: build all strings if lang tools enabled, otherwise just the needed ones\n#if !defined(THEXTECH_DISABLE_LANG_TOOLS) || defined(INPUT_16M_H) || defined(INPUT_3DS_H)\n#define CONTROLS_16M_STRINGS\n#endif\n\n#if !defined(THEXTECH_DISABLE_LANG_TOOLS) || defined(INPUT_3DS_H)\n#define CONTROLS_3DS_STRINGS\n#endif\n\n#if !defined(THEXTECH_DISABLE_LANG_TOOLS) || defined(INPUT_WII_H)\n#define CONTROLS_WII_STRINGS\n#endif\n\n#if !defined(THEXTECH_DISABLE_LANG_TOOLS) || defined(KEYBOARD_H)\n#define CONTROLS_KEYBOARD_STRINGS\n#endif\n\n#if !defined(THEXTECH_DISABLE_LANG_TOOLS) || defined(TOUCHSCREEN_H)\n#define CONTROLS_TOUCHSCREEN_STRINGS\n#endif\n\n#if !defined(THEXTECH_DISABLE_LANG_TOOLS) || defined(JOYSTICK_H)\n#define CONTROLS_JOYSTICK_STRINGS\n#endif\n\n\nstruct ControlsStrings_t\n{\n\n    std::string sharedCaseInvalid = \"(Invalid)\";\n    std::string sharedOptionMaxPlayers = \"Max Players\";\n\n#ifdef CONTROLS_KEYBOARD_STRINGS\n    std::string nameKeyboard = \"Keyboard\";\n    std::string keyboardOptionTextEntryStyle = \"Text Entry Style\";\n    std::string caseMouse = \"(Mouse)\";\n#endif\n\n#if defined(CONTROLS_KEYBOARD_STRINGS) || defined(CONTROLS_JOYSTICK_STRINGS)\n    std::string nameGamepad = \"Gamepad\";\n#endif\n\n#if defined(CONTROLS_JOYSTICK_STRINGS)\n    std::string nameOldJoy = \"Old Joy\";\n    std::string phraseNewProfOldJoy = \"New Profile for Old Joystick\";\n#endif\n\n#if defined(CONTROLS_JOYSTICK_STRINGS) || defined(CONTROLS_WII_STRINGS)\n    std::string joystickSimpleEditor = \"Simple Editor Controls\";\n#endif\n\n#ifdef CONTROLS_TOUCHSCREEN_STRINGS\n    std::string nameTouchscreen = \"Touchscreen\";\n    std::string caseTouch = \"(Touch)\";\n\n\n    std::string touchscreenOptionLayoutStyle = \"Layout Style\";\n    std::string touchscreenOptionScaleFactor = \"Scale Factor\";\n    std::string touchscreenOptionScaleDPad = \"Scale D-Pad\";\n    std::string touchscreenOptionScaleButtons = \"Scale Buttons\";\n    std::string touchscreenOptionSStartSpacing = \"S-Start Spacing\";\n    std::string touchscreenOptionResetLayout = \"Reset Layout\";\n    std::string touchscreenOptionInterfaceStyle = \"Interface Style\";\n    std::string touchscreenOptionFeedbackStrength = \"Feedback Strength\";\n    std::string touchscreenOptionFeedbackLength = \"Feedback Length\";\n    std::string touchscreenOptionHoldRun = \"Hold Run on Start\";\n    std::string touchscreenOptionShowCodeButton = \"Show Code Button\";\n\n\n    std::string touchscreenLayoutTight = \"Tight\";\n    std::string touchscreenLayoutTinyOld = \"Tiny (Old)\";\n    std::string touchscreenLayoutPhoneOld = \"Phone (Old)\";\n    std::string touchscreenLayoutLongOld = \"Long (Old)\";\n    std::string touchscreenLayoutPhabletOld = \"Phablet (Old)\";\n    std::string touchscreenLayoutTabletOld = \"Tablet (Old)\";\n    std::string touchscreenLayoutStandard = \"Standard\";\n\n    std::string touchscreenStyleActions = \"Actions\";\n    std::string touchscreenStyleABXY = \"ABXY\";\n    std::string touchscreenStyleXODA = \"XODA\";\n#endif\n\n#ifdef CONTROLS_16M_STRINGS\n    std::string tdsButtonA = \"A\";\n    std::string tdsButtonB = \"B\";\n    std::string tdsButtonX = \"X\";\n    std::string tdsButtonY = \"Y\";\n    std::string tdsButtonL = \"L\";\n    std::string tdsButtonR = \"R\";\n    std::string tdsButtonSelect = \"Select\";\n    std::string tdsButtonStart = \"Start\";\n\n    std::string tdsCasePen = \"(Pen)\";\n#endif // #ifdef CONTROLS_16M_STRINGS\n\n#if defined(CONTROLS_3DS_STRINGS)\n    std::string tdsDpad = \"D-Pad\";\n    std::string tdsTstick = \"Thumb\";\n    std::string tdsCstick = \"C-Stick\";\n    std::string tdsButtonZL = \"ZL\";\n    std::string tdsButtonZR = \"ZR\";\n#endif // #ifdef CONTROLS_3DS_STRINGS\n\n#ifdef CONTROLS_WII_STRINGS\n    std::string wiiTypeWiimote = \"Wiimote\";\n\n    std::string wiiDpad = \"D-Pad\";\n\n    std::string wiiButtonA = \"A\";\n    std::string wiiButtonB = \"B\";\n    std::string wiiButtonMinus = \"-\";\n    std::string wiiButtonPlus = \"+\";\n    std::string wiiButtonHome = \"Home\";\n    std::string wiiButton2 = \"2\";\n    std::string wiiButton1 = \"1\";\n    std::string wiiShake = \"Shake\";\n\n    std::string wiiTypeNunchuck = \"Nunchuck\";\n    std::string wiiPhraseNewNunchuck = \"New Nunchuck Profile\";\n\n    std::string wiiPrefixNunchuck = \"N\";\n\n    std::string wiiButtonZ = \"Z\";\n    std::string wiiButtonC = \"C\";\n\n    std::string wiiTypeClassic = \"Classic\";\n    std::string wiiPhraseNewClassic = \"New Classic Profile\";\n\n    std::string wiiLStick = \"L-Pad\";\n    std::string wiiRStick = \"R-Pad\";\n\n    std::string wiiButtonZL = \"ZL\";\n    std::string wiiButtonZR = \"ZR\";\n    std::string wiiButtonLT = \"LT\";\n    std::string wiiButtonRT = \"RT\";\n    std::string wiiButtonX = \"X\";\n    std::string wiiButtonY = \"Y\";\n\n    std::string wiiCaseIR = \"(IR)\";\n\n    std::string wiiTypeGamecube = \"GameCube\";\n\n#endif // #ifdef CONTROLS_WII_STRINGS\n\n\n};\n\nextern ControlsStrings_t g_controlsStrings;\n\n#endif // #ifndef CONTROLS_STRINGS_H\n"
  },
  {
    "path": "src/control/duplicate.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n\n#include \"duplicate.h\"\n#include \"../globals.h\"\n\nnamespace Controls\n{\n\n/*====================================================*\\\n|| implementation for InputMethod_Duplicate         ||\n\\*====================================================*/\n\n// Update functions that set player controls (and editor controls)\n// based on current device input. Return false if device lost.\nbool InputMethod_Duplicate::Update(int player, Controls_t& c, CursorControls_t& m, EditorControls_t& e, HotkeysPressed_t& h)\n{\n    if(this->player_no < 1 || this->player_no > maxLocalPlayers)\n        return false;\n\n    if(this->player_no - 1 >= (int)g_InputMethods.size())\n        return false;\n\n    if(!g_InputMethods[this->player_no - 1])\n        return false;\n\n    c = Player[this->player_no].Controls;\n\n    UNUSED(player);\n    UNUSED(m);\n    UNUSED(e);\n    UNUSED(h);\n\n    return true;\n}\n\nvoid InputMethod_Duplicate::Rumble(int ms, float strength)\n{\n    Controls::Rumble(this->player_no, ms, strength);\n}\n\n/*====================================================*\\\n|| implementation for InputMethodProfile_Duplicate  ||\n\\*====================================================*/\n\n// the job of this function is to initialize the class in a consistent state\nInputMethodProfile_Duplicate::InputMethodProfile_Duplicate()\n{}\n\nbool InputMethodProfile_Duplicate::PollPrimaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return true;\n}\n\nbool InputMethodProfile_Duplicate::PollSecondaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return true;\n}\n\nbool InputMethodProfile_Duplicate::DeletePrimaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return true;\n}\n\nbool InputMethodProfile_Duplicate::DeleteSecondaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return true;\n}\n\nconst char* InputMethodProfile_Duplicate::NamePrimaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return \"(DUP)\";\n}\n\nconst char* InputMethodProfile_Duplicate::NameSecondaryButton(ControlsClass c, size_t i)\n{\n    (void)c;\n    (void)i;\n    return \"\";\n}\n\nvoid InputMethodProfile_Duplicate::SaveConfig(IniProcessing* ctl)\n{\n    UNUSED(ctl);\n}\n\nvoid InputMethodProfile_Duplicate::LoadConfig(IniProcessing* ctl)\n{\n    UNUSED(ctl);\n}\n\n/*====================================================*\\\n|| implementation for InputMethodType_Duplicate     ||\n\\*====================================================*/\n\nInputMethodProfile* InputMethodType_Duplicate::AllocateProfile() noexcept\n{\n    return (InputMethodProfile*) new(std::nothrow) InputMethodProfile_Duplicate;\n}\n\nInputMethodType_Duplicate::InputMethodType_Duplicate()\n{\n    this->Name = \"Duplicate\";\n}\n\nbool InputMethodType_Duplicate::TestProfileType(InputMethodProfile* profile)\n{\n    return (bool)dynamic_cast<InputMethodProfile_Duplicate*>(profile);\n}\n\nbool InputMethodType_Duplicate::RumbleSupported()\n{\n    return true;\n}\n\nvoid InputMethodType_Duplicate::UpdateControlsPre()\n{}\n\nvoid InputMethodType_Duplicate::UpdateControlsPost()\n{}\n\nInputMethod* InputMethodType_Duplicate::Poll(const std::vector<InputMethod*>& active_methods) noexcept\n{\n    int found = 0;\n\n    for(int p = 1; p <= maxLocalPlayers; p++)\n    {\n        if(p > numPlayers)\n            continue;\n\n        if(!Player[p].Controls.Drop || !Player[p].Controls.Up)\n            continue;\n\n        if(p - 1 >= (int)active_methods.size())\n            continue;\n\n        if(!active_methods[p - 1])\n            continue;\n\n        bool allowed = true;\n\n        for(InputMethod* method : active_methods)\n        {\n            if(!method)\n                continue;\n\n            InputMethod_Duplicate* m = dynamic_cast<InputMethod_Duplicate*>(method);\n\n            if(m && m->player_no == p)\n            {\n                allowed = false;\n                break;\n            }\n        }\n\n        if(!allowed)\n            continue;\n\n        found = p;\n        break;\n    }\n\n    InputMethod_Duplicate* method = new(std::nothrow) InputMethod_Duplicate;\n\n    if(!method)\n        return nullptr;\n\n    method->player_no = found;\n\n    method->Name = \"Duplicate P\" + std::to_string(found);\n    method->Type = this;\n\n    return (InputMethod*)method;\n}\n\n/*-----------------------*\\\n|| OPTIONAL METHODS      ||\n\\*-----------------------*/\n\n// How many per-type special options are there?\nsize_t InputMethodType_Duplicate::GetOptionCount()\n{\n    return 1;\n}\n\n// Methods to manage per-profile options\n// It is guaranteed that none of these will be called if\n// GetOptionCount() returns 0.\n// get a char* describing the option\nconst char* InputMethodType_Duplicate::GetOptionName(size_t i)\n{\n    if(i == 0)\n        return \"PRESS DROP & UP\";\n\n    return nullptr;\n}\n\n// get a char* describing the current option value\n// must be allocated in static or instance memory\n// WILL NOT be freed\nconst char* InputMethodType_Duplicate::GetOptionValue(size_t i)\n{\n    UNUSED(i);\n    return nullptr;\n}\n\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodType_Duplicate::OptionChange(size_t i)\n{\n    UNUSED(i);\n    return false;\n}\n\n// called when left is pressed\nbool InputMethodType_Duplicate::OptionRotateLeft(size_t i)\n{\n    UNUSED(i);\n    return false;\n}\n\n// called when right is pressed\nbool InputMethodType_Duplicate::OptionRotateRight(size_t i)\n{\n    UNUSED(i);\n    return false;\n}\n\nvoid InputMethodType_Duplicate::SaveConfig_Custom(IniProcessing* ctl)\n{\n    UNUSED(ctl);\n}\n\nvoid InputMethodType_Duplicate::LoadConfig_Custom(IniProcessing* ctl)\n{\n    UNUSED(ctl);\n}\n\n} // namespace Controls\n"
  },
  {
    "path": "src/control/duplicate.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef DUPLICATE_H\n#define DUPLICATE_H\n\n#include \"../controls.h\"\n\nnamespace Controls\n{\n\nclass InputMethod_Duplicate : public InputMethod\n{\npublic:\n    using InputMethod::Type;\n    using InputMethod::Profile;\n\n    int player_no = 1;\n\n    // Update functions that set player controls (and editor controls)\n    // based on current device input. Return false if device lost.\n    bool Update(int player, Controls_t &c, CursorControls_t &m, EditorControls_t &e, HotkeysPressed_t &h);\n\n    void Rumble(int ms, float strength);\n};\n\nclass InputMethodProfile_Duplicate : public InputMethodProfile\n{\npublic:\n    using InputMethodProfile::Name;\n    using InputMethodProfile::Type;\n\n    InputMethodProfile_Duplicate();\n\n    // Polls a new (secondary) device button for the i'th player button\n    // Returns true on success and false if no button pressed\n    // Never allows two player buttons to bind to the same device button\n    bool PollPrimaryButton(ControlsClass c, size_t i);\n    bool PollSecondaryButton(ControlsClass c, size_t i);\n\n    // Deletes a primary button for the i'th button of class c (only called for non-Player buttons)\n    bool DeletePrimaryButton(ControlsClass c, size_t i);\n\n    // Deletes a secondary device button for the i'th button of class c\n    bool DeleteSecondaryButton(ControlsClass c, size_t i);\n\n    // Gets strings for the device buttons currently used for the i'th button of class c\n    const char *NamePrimaryButton(ControlsClass c, size_t i);\n    const char *NameSecondaryButton(ControlsClass c, size_t i);\n\n    // one can assume that the IniProcessing* is already in the correct group\n    void SaveConfig(IniProcessing *ctl);\n    void LoadConfig(IniProcessing *ctl);\n};\n\nclass InputMethodType_Duplicate : public InputMethodType\n{\nprivate:\n    InputMethodProfile *AllocateProfile() noexcept;\n\npublic:\n    using InputMethodType::Name;\n    using InputMethodType::m_profiles;\n\n    InputMethodType_Duplicate();\n\n    bool TestProfileType(InputMethodProfile *profile);\n    bool RumbleSupported();\n\n    void UpdateControlsPre();\n    void UpdateControlsPost();\n\n    // null if no input method is ready\n    // allocates the new InputMethod on the heap\n    InputMethod *Poll(const std::vector<InputMethod *> &active_methods) noexcept;\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\npublic:\n    // How many per-type special options are there?\n    size_t GetOptionCount();\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount() returns 0.\n    // get a char* describing the option\n    const char *GetOptionName(size_t i);\n    // get a char* describing the current option value\n    // must be allocated in static or instance memory\n    // WILL NOT be freed\n    const char *GetOptionValue(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange(size_t i);\n    // called when left is pressed\n    bool OptionRotateLeft(size_t i);\n    // called when right is pressed\n    bool OptionRotateRight(size_t i);\n\nprotected:\n    void SaveConfig_Custom(IniProcessing *ctl);\n    void LoadConfig_Custom(IniProcessing *ctl);\n};\n\n} // namespace Controls\n\n#endif // DUPLICATE_H\n"
  },
  {
    "path": "src/control/input_16m.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n#include <nds.h>\n\n#include \"globals.h\"\n#include \"game_main.h\"\n\n#include \"core/render.h\"\n#include \"editor/new_editor.h\"\n\n#include \"controls.h\"\n#include \"control/input_16m.h\"\n#include \"control/controls_strings.h\"\n\n#include \"main/menu_main.h\"\n\n#define buttonRight Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Right]\n#define buttonLeft  Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Left]\n#define buttonUp    Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Up]\n#define buttonDown  Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Down]\n\nconst std::string* KEYNAMES[32] = {\n &g_controlsStrings.tdsButtonA,   &g_controlsStrings.tdsButtonB, &g_controlsStrings.tdsButtonSelect, &g_controlsStrings.tdsButtonStart,\n &buttonRight,                    &buttonLeft,                   &buttonUp,                     &buttonDown,\n &g_controlsStrings.tdsButtonR,   &g_controlsStrings.tdsButtonL, &g_controlsStrings.tdsButtonX, &g_controlsStrings.tdsButtonY,\n &g_mainMenu.caseNone,            &g_mainMenu.caseNone,          &g_mainMenu.caseNone,          &g_mainMenu.caseNone,\n &g_mainMenu.caseNone,            &g_mainMenu.caseNone,          &g_mainMenu.caseNone,          &g_mainMenu.caseNone,\n &g_controlsStrings.tdsCasePen,   &g_mainMenu.caseNone,          &g_mainMenu.caseNone,          &g_mainMenu.caseNone,\n &g_mainMenu.caseNone,            &g_mainMenu.caseNone,          &g_mainMenu.caseNone,          &g_mainMenu.caseNone,\n &g_mainMenu.caseNone,            &g_mainMenu.caseNone,          &g_mainMenu.caseNone,          &g_mainMenu.caseNone\n};\n\nenum KEYID {\n KEYID_A=0,\n KEYID_B=1,\n KEYID_SELECT=2,\n KEYID_START=3,\n KEYID_RIGHT=4,\n KEYID_LEFT=5,\n KEYID_UP=6,\n KEYID_DOWN=7,\n KEYID_R=8,\n KEYID_L=9,\n KEYID_X=10,\n KEYID_Y=11,\n\n KEYID_TOUCH=20,\n};\n\nnamespace Controls\n{\n\n// helper functions\n\n\n/*===============================================*\\\n|| implementation for InputMethod_16M            ||\n\\*===============================================*/\n\nInputMethod_16M::~InputMethod_16M()\n{\n    InputMethodType_16M* t = dynamic_cast<InputMethodType_16M*>(this->Type);\n\n    if(!t)\n        return;\n\n    t->m_numPlayers --;\n}\n\n// Update functions that set player controls (and editor controls)\n// based on current device input. Return false if device lost.\nbool InputMethod_16M::Update(int player, Controls_t& c, CursorControls_t& m, EditorControls_t& e, HotkeysPressed_t& h)\n{\n    InputMethodType_16M* k = dynamic_cast<InputMethodType_16M*>(this->Type);\n    InputMethodProfile_16M* p = dynamic_cast<InputMethodProfile_16M*>(this->Profile);\n\n    if(!k || !p)\n        return false;\n\n    uint32_t keys_held = keysCurrent();\n\n    for(int a = 0; a < 4; a++)\n    {\n        int* keys;\n        int* keys2;\n        size_t key_start;\n        size_t key_max;\n        bool activate = false;\n\n        if(a == 0)\n        {\n            keys = p->m_keys;\n            keys2 = p->m_keys2;\n            key_start = 0;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = p->m_cursor_keys2;\n            key_start = 4;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = p->m_editor_keys;\n            keys2 = p->m_editor_keys2;\n            key_start = 4;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = p->m_hotkeys;\n            keys2 = p->m_hotkeys2;\n            key_start = 0;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = key_start; i < key_max; i++)\n        {\n            int key;\n\n            if(keys)\n                key = keys[i];\n            else\n                key = null_key;\n\n            int key2 = keys2[i];\n\n            bool* b;\n\n            if(a == 0)\n            {\n                b = &PlayerControls::GetButton(c, i);\n                *b = false;\n            }\n            else if(a == 1)\n            {\n                b = &CursorControls::GetButton(m, i);\n            }\n            else if(a == 2)\n            {\n                b = &EditorControls::GetButton(e, i);\n            }\n            else\n            {\n                b = &activate;\n                *b = false;\n            }\n\n            if(key != null_key && BIT(key) & keys_held)\n                *b = true;\n            else if(key2 != null_key && BIT(key2) & keys_held)\n                *b = true;\n\n            if(a == 3 && *b)\n                h[i] = player;\n        }\n    }\n\n    num_t* const scroll[4] = {&e.ScrollUp, &e.ScrollDown, &e.ScrollLeft, &e.ScrollRight};\n\n    for(int i = 0; i < 4; i++)\n    {\n        int key = p->m_editor_keys[i];\n        int key2 = p->m_editor_keys2[i];\n\n        if(key != null_key && BIT(key) & keys_held)\n            *scroll[i] += 10;\n        else if(key2 != null_key && BIT(key2) & keys_held)\n            *scroll[i] += 10;\n    }\n\n    bool cursor[4];\n\n    for(int i = 0; i < 4; i++)\n    {\n        int key = p->m_cursor_keys2[i];\n        cursor[i] = (key != null_key && BIT(key) & keys_held);\n    }\n\n    // Cursor control (UDLR)\n    if(cursor[0] || cursor[1] || cursor[2] || cursor[3])\n    {\n        if(m.X < 0)\n            m.X = XRender::TargetW / 2;\n\n        if(m.Y < 0)\n            m.Y = XRender::TargetH / 2;\n\n        if(cursor[3])\n            m.X += 16;\n\n        if(cursor[2])\n            m.X -= 16;\n\n        if(cursor[1])\n            m.Y += 16;\n\n        if(cursor[0])\n            m.Y -= 16;\n\n        if(m.X < 0)\n            m.X = 0;\n        else if(m.X >= XRender::TargetW)\n            m.X = XRender::TargetW - 1;\n\n        if(m.Y < 0)\n            m.Y = 0;\n        else if(m.Y >= XRender::TargetH)\n            m.Y = XRender::TargetH - 1;\n\n        m.Move = true;\n    }\n\n    // one may uncomment this to quickly simulate controller disconnection for debugging purposes\n    // if(c.Up && c.Down)\n    //     return false;\n\n    return true;\n}\n\nvoid InputMethod_16M::Rumble(int ms, float strength)\n{\n    UNUSED(ms);\n    UNUSED(strength);\n}\n\nStatusInfo InputMethod_16M::GetStatus()\n{\n    return XPower::devicePowerStatus();\n}\n\n/*===============================================*\\\n|| implementation for InputMethodProfile_16M     ||\n\\*===============================================*/\n\n// the job of this function is to initialize the class in a consistent state\nInputMethodProfile_16M::InputMethodProfile_16M()\n{\n    for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n    {\n        this->m_keys[i] = null_key;\n        this->m_keys2[i] = null_key;\n    }\n\n    for(size_t i = 0; i < CursorControls::n_buttons; i++)\n        this->m_cursor_keys2[i] = null_key;\n\n    for(size_t i = 0; i < EditorControls::n_buttons; i++)\n    {\n        this->m_editor_keys[i] = null_key;\n        this->m_editor_keys2[i] = null_key;\n    }\n\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n    {\n        this->m_hotkeys[i] = null_key;\n        this->m_hotkeys2[i] = null_key;\n    }\n\n    this->m_keys[PlayerControls::Buttons::Up] = KEYID_UP;\n    this->m_keys[PlayerControls::Buttons::Down] = KEYID_DOWN;\n    this->m_keys[PlayerControls::Buttons::Left] = KEYID_LEFT;\n    this->m_keys[PlayerControls::Buttons::Right] = KEYID_RIGHT;\n    this->m_keys[PlayerControls::Buttons::Jump] = KEYID_B;\n    this->m_keys[PlayerControls::Buttons::AltJump] = KEYID_A;\n    this->m_keys[PlayerControls::Buttons::Run] = KEYID_Y;\n    this->m_keys[PlayerControls::Buttons::AltRun] = KEYID_X;\n    this->m_keys[PlayerControls::Buttons::Drop] = KEYID_SELECT;\n    this->m_keys[PlayerControls::Buttons::Start] = KEYID_START;\n\n    this->m_editor_keys[EditorControls::Buttons::ScrollUp] = KEYID_UP;\n    this->m_editor_keys[EditorControls::Buttons::ScrollDown] = KEYID_DOWN;\n    this->m_editor_keys[EditorControls::Buttons::ScrollLeft] = KEYID_LEFT;\n    this->m_editor_keys[EditorControls::Buttons::ScrollRight] = KEYID_RIGHT;\n    this->m_editor_keys[EditorControls::Buttons::FastScroll] = KEYID_A;\n    this->m_editor_keys[EditorControls::Buttons::ModeSelect] = KEYID_B;\n    this->m_editor_keys[EditorControls::Buttons::ModeErase] = KEYID_Y;\n    this->m_editor_keys[EditorControls::Buttons::PrevSection] = KEYID_L;\n    this->m_editor_keys[EditorControls::Buttons::NextSection] = KEYID_R;\n    this->m_editor_keys[EditorControls::Buttons::SwitchScreens] = KEYID_SELECT;\n    this->m_editor_keys[EditorControls::Buttons::TestPlay] = KEYID_START;\n\n    this->m_hotkeys[Hotkeys::Buttons::VanillaCam] = KEYID_L;\n\n    this->m_altMenuControls = true;\n}\n\nbool InputMethodProfile_16M::PollPrimaryButton(ControlsClass c, size_t i)\n{\n    if(c == ControlsClass::Cursor)\n        return true;\n\n    // note: m_canPoll is initialized to false\n    InputMethodType_16M* k = dynamic_cast<InputMethodType_16M*>(this->Type);\n\n    if(!k)\n        return false;\n\n    uint32_t keys_held = keysCurrent();\n\n    int key;\n\n    for(key = 0; key < 32; key++)\n    {\n        if(BIT(key) & keys_held)\n            break;\n    }\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == 32)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    int* keys;\n    int* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        // BAD!\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n    // if a button's secondary key (including the current one) is the new key, delete it.\n    // if a button's primary key (excluding the current one) is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, replace it with the button the player is replacing.\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(keys2[j] == key)\n            keys2[j] = null_key;\n        else if(i != j && keys[j] == key)\n        {\n            if(keys2[j] != null_key)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_key;\n            }\n            else\n                keys[j] = keys[i];\n        }\n    }\n\n    keys[i] = key;\n    return true;\n}\n\nbool InputMethodProfile_16M::PollSecondaryButton(ControlsClass c, size_t i)\n{\n    // note: m_canPoll is initialized to false\n    InputMethodType_16M* k = dynamic_cast<InputMethodType_16M*>(this->Type);\n\n    if(!k)\n        return false;\n\n    uint32_t keys_held = keysCurrent();\n\n    int key;\n\n    for(key = 0; key < 32; key++)\n    {\n        if(BIT(key) & keys_held)\n            break;\n    }\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == 32)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    int* keys;\n    int* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = nullptr;\n        keys2 = this->m_cursor_keys2;\n        key_max = CursorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        // BAD!\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n\n    // if the current button's primary key is the new key,\n    //     delete its secondary key instead of setting it.\n    if(keys && keys[i] == key)\n    {\n        keys2[i] = null_key;\n        return true;\n    }\n\n    // if another button's secondary key is the new key, delete it.\n    // if another button's primary key is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, if this button's secondary key is defined, overwrite the other's with this.\n    //     if all else fails, overwrite the other button's with this button's PRIMARY key and assign\n    //         this button's PRIMARY key instead\n\n    bool can_do_secondary = true;\n\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(i != j && keys2[j] == key)\n        {\n            keys2[j] = null_key;\n        }\n        else if(keys && i != j && keys[j] == key)\n        {\n            if(keys2[j] != null_key)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_key;\n            }\n            else if(keys2[i] != null_key)\n            {\n                keys[j] = keys2[i];\n            }\n            else\n            {\n                keys[j] = keys[i];\n                can_do_secondary = false;\n            }\n        }\n    }\n\n    if(can_do_secondary)\n        keys2[i] = key;\n    else if(keys)\n        keys[i] = key;\n\n    return true;\n}\n\nbool InputMethodProfile_16M::DeletePrimaryButton(ControlsClass c, size_t i)\n{\n    // resolve the particular primary and secondary key arrays\n    int* keys;\n    int* keys2;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        return false;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n    }\n    else\n    {\n        // BAD!\n        return false;\n    }\n\n    if(keys2[i] != null_key)\n    {\n        keys[i] = keys2[i];\n        keys2[i] = null_key;\n        return true;\n    }\n\n    if(c == ControlsClass::Player)\n        return false;\n\n    if(keys[i] != null_key)\n    {\n        keys[i] = null_key;\n        return true;\n    }\n\n    return false;\n}\n\nbool InputMethodProfile_16M::DeleteSecondaryButton(ControlsClass c, size_t i)\n{\n    int* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return false;\n\n    if(keys2[i] != null_key)\n    {\n        keys2[i] = null_key;\n        return true;\n    }\n\n    return false;\n}\n\nconst char* InputMethodProfile_16M::NamePrimaryButton(ControlsClass c, size_t i)\n{\n    int* keys;\n\n    if(c == ControlsClass::Player)\n        keys = this->m_keys;\n    else if(c == ControlsClass::Cursor)\n        return g_controlsStrings.tdsCasePen.c_str();\n    else if(c == ControlsClass::Editor)\n        keys = this->m_editor_keys;\n    else if(c == ControlsClass::Hotkey)\n        keys = this->m_hotkeys;\n    else\n        return \"\";\n\n    if(keys[i] == null_key)\n        return g_mainMenu.caseNone.c_str();\n\n    if(keys[i] < 0 || keys[i] >= 32)\n        return g_controlsStrings.sharedCaseInvalid.c_str();\n\n    return KEYNAMES[keys[i]]->c_str();\n}\n\nconst char* InputMethodProfile_16M::NameSecondaryButton(ControlsClass c, size_t i)\n{\n    int* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return \"\";\n\n    if(keys2[i] == null_key)\n        return \"\";\n\n    if(keys2[i] < 0 || keys2[i] >= 32)\n        return g_controlsStrings.sharedCaseInvalid.c_str();\n\n    return KEYNAMES[keys2[i]]->c_str();\n}\n\nvoid InputMethodProfile_16M::SaveConfig(IniProcessing* ctl)\n{\n    char name2[20];\n\n    for(int a = 0; a < 4; a++)\n    {\n        int* keys;\n        int* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            const char* name;\n\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            if(keys)\n                ctl->setValue(name, keys[i]);\n\n            for(size_t c = 0; c < 20; c++)\n            {\n                if(c + 2 == 20 || name[c] == '\\0')\n                {\n                    name2[c] = '2';\n                    name2[c + 1] = '\\0';\n                    break;\n                }\n\n                name2[c] = name[c];\n            }\n\n            ctl->setValue(name2, keys2[i]);\n        }\n    }\n}\n\nvoid InputMethodProfile_16M::LoadConfig(IniProcessing* ctl)\n{\n    char name2[20];\n\n    for(int a = 0; a < 4; a++)\n    {\n        int* keys;\n        int* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            const char* name;\n\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            if(keys)\n                ctl->read(name, keys[i], keys[i]);\n\n            for(size_t c = 0; c < 20; c++)\n            {\n                if(c + 2 == 20 || name[c] == '\\0')\n                {\n                    name2[c] = '2';\n                    name2[c + 1] = '\\0';\n                    break;\n                }\n\n                name2[c] = name[c];\n            }\n\n            ctl->read(name2, keys2[i], keys2[i]);\n        }\n    }\n}\n\n/*===============================================*\\\n|| implementation for InputMethodType_16M        ||\n\\*===============================================*/\n\nInputMethodProfile* InputMethodType_16M::AllocateProfile() noexcept\n{\n    return (InputMethodProfile*) new(std::nothrow) InputMethodProfile_16M;\n}\n\nInputMethodType_16M::InputMethodType_16M()\n{\n    this->Name = \"NDS\";\n}\n\nInputMethodType_16M::~InputMethodType_16M()\n{\n}\n\nbool InputMethodType_16M::TestProfileType(InputMethodProfile* profile)\n{\n    return (bool)dynamic_cast<InputMethodProfile_16M*>(profile);\n}\n\nbool InputMethodType_16M::RumbleSupported()\n{\n    return false;\n}\n\nvoid InputMethodType_16M::UpdateControlsPre()\n{\n    scanKeys();\n}\n\nvoid InputMethodType_16M::UpdateControlsPost()\n{\n    uint32_t keys_pressed = keysDown();\n    uint32_t keys_held = keysCurrent();\n\n    // handle the mouse\n    int tx, ty;\n    touchPosition pos;\n    touchRead(&pos);\n    XRender::mapToScreen(pos.px, pos.py, &tx, &ty);\n\n    if(keys_pressed & KEY_TOUCH)\n    {\n        if((LevelEditor || MagicHand) && ty > 40 && !editorScreen.active)\n        {\n            int distance2 = (m_lastTouchX - tx)*(m_lastTouchX - tx)\n                + (m_lastTouchY - ty)*(m_lastTouchY - ty);\n\n            if(distance2 < 64)\n                m_click_accepted = true;\n            else\n                m_click_accepted = false;\n        }\n        else\n            m_click_accepted = true;\n    }\n\n    if(keys_held & KEY_TOUCH)\n    {\n        if(tx - m_lastTouchX < -1 || tx - m_lastTouchX > 1\n            || ty - m_lastTouchY < -1 || ty - m_lastTouchY > 1)\n        {\n            SharedCursor.Move = true;\n            SharedCursor.X = tx;\n            SharedCursor.Y = ty;\n\n            m_lastTouchX = tx;\n            m_lastTouchY = ty;\n        }\n\n        if(m_click_accepted)\n        {\n            SharedCursor.Primary = true;\n        }\n    }\n}\n\nInputMethod* InputMethodType_16M::Poll(const std::vector<InputMethod*>& active_methods) noexcept\n{\n    if(this->m_numPlayers != this->m_lastNumPlayers)\n    {\n        // this ensures that keys that were held when a keyboard method was removed cannot be polled to add that method back\n        if(this->m_numPlayers < this->m_lastNumPlayers)\n            this->m_canPoll = false;\n\n        this->m_lastNumPlayers = this->m_numPlayers;\n    }\n\n    if(this->m_numPlayers >= m_maxPlayers)\n    {\n        // ban polling in case things change\n        this->m_canPoll = false;\n        return nullptr;\n    }\n\n    uint32_t keys_held = keysCurrent();\n\n    // ban attachment from active profile, must find new profile\n    int key;\n    InputMethodProfile* target_profile = nullptr;\n\n    for(key = 0; key < 32; key++)\n    {\n        if((BIT(key) & keys_held) == 0)\n            continue;\n\n        bool allowed = true;\n\n        // ban attachment from active profile\n        for(InputMethod* method : active_methods)\n        {\n            if(!allowed)\n                break;\n\n            if(!method)\n                continue;\n\n            InputMethodProfile_16M* p = dynamic_cast<InputMethodProfile_16M*>(method->Profile);\n\n            if(!p)\n                continue;\n\n            for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n            {\n                if(p->m_keys[i] == key || p->m_keys2[i] == key)\n                {\n                    allowed = false;\n                    break;\n                }\n            }\n\n            if(LevelEditor)\n            {\n                for(size_t i = 0; i < EditorControls::n_buttons; i++)\n                {\n                    if(p->m_editor_keys[i] == key || p->m_editor_keys2[i] == key)\n                    {\n                        allowed = false;\n                        break;\n                    }\n                }\n            }\n\n            for(size_t i = 0; i < CursorControls::n_buttons; i++)\n            {\n                if(p->m_cursor_keys2[i] == key)\n                {\n                    allowed = false;\n                    break;\n                }\n            }\n\n            for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n            {\n                if(p->m_hotkeys[i] == key || p->m_hotkeys2[i] == key)\n                {\n                    allowed = false;\n                    break;\n                }\n            }\n        }\n\n        if(!allowed)\n            continue;\n\n        // which player index is connecting?\n        int my_index = 0;\n\n        for(const InputMethod* method : active_methods)\n        {\n            if(!method)\n                break;\n\n            my_index ++;\n        }\n\n        // try to find profile matching the keypress\n        for(int i = -1; i < (int)this->m_profiles.size(); i++)\n        {\n            // start with the most recent profile for this player index\n            InputMethodProfile* profile;\n\n            if(i == -1)\n                profile = this->GetDefaultProfile(my_index);\n            else\n                profile = this->m_profiles[i];\n\n            if(!profile)\n                continue;\n\n            InputMethodProfile_16M* p = dynamic_cast<InputMethodProfile_16M*>(profile);\n\n            if(!p)\n                continue;\n\n            for(size_t j = 0; j < PlayerControls::n_buttons; j++)\n            {\n                if(p->m_keys[j] == key || p->m_keys2[j] == key)\n                {\n                    target_profile = profile;\n                    break;\n                }\n            }\n\n            if(target_profile)\n                break;\n        }\n\n        if(target_profile || this->m_profiles.empty())\n            break;\n    }\n\n    // if didn't find any key allow poll in future but return false\n    if(key == 32)\n    {\n        this->m_canPoll = true;\n        return nullptr;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return nullptr;\n\n    // we're going to create a new keyboard!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    InputMethod_16M* method = new(std::nothrow) InputMethod_16M;\n\n    if(!method)\n        return nullptr;\n\n    method->Name = this->Name;\n    method->Type = this;\n    method->Profile = target_profile;\n\n    this->m_numPlayers ++;\n\n    return (InputMethod*)method;\n}\n\n/*-----------------------*\\\n|| OPTIONAL METHODS      ||\n\\*-----------------------*/\nbool InputMethodType_16M::ConsumeEvent(const SDL_Event* ev)\n{\n    UNUSED(ev);\n    return false;\n}\n\n// optional function allowing developer to associate device information with profile, etc\nbool InputMethodType_16M::SetProfile_Custom(InputMethod* method, int player_no, InputMethodProfile* profile,\n        const std::vector<InputMethod*>& active_methods)\n{\n    if(!method || !profile || player_no < 0 || player_no >= maxLocalPlayers)\n        return false;\n\n    // prevent duplicates of a profile from ever being set\n    for(InputMethod* o_method : active_methods)\n    {\n        if(!o_method)\n            continue;\n\n        if(o_method != method && o_method->Profile == profile)\n            return false;\n    }\n\n    m_canPoll = false;\n    return true;\n}\n\n// How many per-type special options are there?\nsize_t InputMethodType_16M::GetOptionCount()\n{\n    return 1;\n}\n\n// Methods to manage per-profile options\n// It is guaranteed that none of these will be called if\n// GetOptionCount() returns 0.\n// get a char* describing the option\nconst char* InputMethodType_16M::GetOptionName(size_t i)\n{\n    if(i == 0)\n        return g_controlsStrings.sharedOptionMaxPlayers.c_str();\n\n    return nullptr;\n}\n\n// get a char* describing the current option value\n// must be allocated in static or instance memory\n// WILL NOT be freed\nconst char* InputMethodType_16M::GetOptionValue(size_t i)\n{\n    if(i == 0)\n    {\n        static char buf[3];\n        snprintf(buf, 3, \"%d\", this->m_maxPlayers);\n        return buf;\n    }\n\n    return nullptr;\n}\n\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodType_16M::OptionChange(size_t i)\n{\n    if(i == 0)\n    {\n        this->m_maxPlayers ++;\n\n        if(this->m_maxPlayers > 2)\n            this->m_maxPlayers = 1;\n\n        return true;\n    }\n\n    return false;\n}\n\n// called when left is pressed\nbool InputMethodType_16M::OptionRotateLeft(size_t i)\n{\n    if(i == 0)\n    {\n        if(this->m_maxPlayers > 1)\n        {\n            this->m_maxPlayers --;\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// called when right is pressed\nbool InputMethodType_16M::OptionRotateRight(size_t i)\n{\n    if(i == 0)\n    {\n        if(this->m_maxPlayers < 2)\n        {\n            this->m_maxPlayers ++;\n            return true;\n        }\n    }\n\n    return false;\n}\n\nvoid InputMethodType_16M::SaveConfig_Custom(IniProcessing* ctl)\n{\n    ctl->setValue(\"max-players\", this->m_maxPlayers);\n}\n\nvoid InputMethodType_16M::LoadConfig_Custom(IniProcessing* ctl)\n{\n    ctl->read(\"max-players\", this->m_maxPlayers, 2);\n}\n\n} // namespace Controls\n"
  },
  {
    "path": "src/control/input_16m.h",
    "content": "#ifndef INPUT_16M_H\n#define INPUT_16M_H\n\n#include \"../controls.h\"\n\nnamespace Controls\n{\n\nconstexpr int null_key = -1;\n\nclass InputMethod_16M : public InputMethod\n{\npublic:\n    using InputMethod::Type;\n    using InputMethod::Profile;\n\n    ~InputMethod_16M();\n\n    // Update functions that set player controls (and editor controls)\n    // based on current device input. Return false if device lost.\n    bool Update(int player, Controls_t &c, CursorControls_t &m, EditorControls_t &e, HotkeysPressed_t &h);\n\n    void Rumble(int ms, float strength);\n\n    StatusInfo GetStatus();\n};\n\nclass InputMethodProfile_16M : public InputMethodProfile\n{\nprivate:\n    bool m_canPoll = false;\n\npublic:\n    using InputMethodProfile::Name;\n    using InputMethodProfile::Type;\n\n    int m_keys[PlayerControls::n_buttons] = {null_key};\n    int m_keys2[PlayerControls::n_buttons] = {null_key};\n\n    int m_editor_keys[EditorControls::n_buttons] = {null_key};\n    int m_editor_keys2[EditorControls::n_buttons] = {null_key};\n\n    int m_cursor_keys2[CursorControls::n_buttons] = {null_key};\n\n    int m_hotkeys[Hotkeys::n_buttons] = {null_key};\n    int m_hotkeys2[Hotkeys::n_buttons] = {null_key};\n\n    InputMethodProfile_16M();\n\n    // Polls a new (secondary) device button for the i'th player button\n    // Returns true on success and false if no button pressed\n    // Never allows two player buttons to bind to the same device button\n    bool PollPrimaryButton(ControlsClass c, size_t i);\n    bool PollSecondaryButton(ControlsClass c, size_t i);\n\n    // Deletes a primary button for the i'th button of class c (only called for non-Player buttons)\n    bool DeletePrimaryButton(ControlsClass c, size_t i);\n\n    // Deletes a secondary device button for the i'th button of class c\n    bool DeleteSecondaryButton(ControlsClass c, size_t i);\n\n    // Gets strings for the device buttons currently used for the i'th button of class c\n    const char *NamePrimaryButton(ControlsClass c, size_t i);\n    const char *NameSecondaryButton(ControlsClass c, size_t i);\n\n    // one can assume that the IniProcessing* is already in the correct group\n    void SaveConfig(IniProcessing *ctl);\n    void LoadConfig(IniProcessing *ctl);\n};\n\nclass InputMethodType_16M : public InputMethodType\n{\nprivate:\n    bool m_canPoll = false;\n\n    int m_lastNumPlayers = 0;\n    uint16_t m_lastTouchX = 0;\n    uint16_t m_lastTouchY = 0;\n    bool m_click_accepted = false;\n\n    InputMethodProfile *AllocateProfile() noexcept;\n\npublic:\n    int m_numPlayers = 0;\n\n    // options\n    int m_maxPlayers = 1;\n\n    using InputMethodType::Name;\n    using InputMethodType::m_profiles;\n\n    InputMethodType_16M();\n    ~InputMethodType_16M();\n\n    bool TestProfileType(InputMethodProfile *profile);\n    bool RumbleSupported();\n\n    void UpdateControlsPre();\n    void UpdateControlsPost();\n\n    // null if no input method is ready\n    // allocates the new InputMethod on the heap\n    InputMethod *Poll(const std::vector<InputMethod *> &active_methods) noexcept;\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\nprotected:\n    // optional function allowing developer to associate device information with profile, etc\n    // if developer wants to forbid assignment, return false\n    bool SetProfile_Custom(InputMethod *method, int player_no, InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods);\n\npublic:\n    bool ConsumeEvent(const SDL_Event *ev);\n\n    // How many per-type special options are there?\n    size_t GetOptionCount();\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount() returns 0.\n    // get a char* describing the option\n    const char *GetOptionName(size_t i);\n    // get a char* describing the current option value\n    // must be allocated in static or instance memory\n    // WILL NOT be freed\n    const char *GetOptionValue(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange(size_t i);\n    // called when left is pressed\n    bool OptionRotateLeft(size_t i);\n    // called when right is pressed\n    bool OptionRotateRight(size_t i);\n\nprotected:\n    void SaveConfig_Custom(IniProcessing *ctl);\n    void LoadConfig_Custom(IniProcessing *ctl);\n};\n\n} // namespace Controls\n\n#endif // #ifndef INPUT_16M_H\n"
  },
  {
    "path": "src/control/input_3ds.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <3ds.h>\n#include <algorithm>\n\n#include \"globals.h\"\n#include \"game_main.h\"\n\n#include \"core/render.h\"\n#include \"editor/new_editor.h\"\n\n#include \"controls.h\"\n#include \"control/input_3ds.h\"\n\n#include \"control/controls_strings.h\"\n\n#include \"main/menu_main.h\"\n\n#include <Logger/logger.h>\n\nstatic std::string s_buffer;\n\n#define buttonRight Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Right]\n#define buttonLeft  Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Left]\n#define buttonUp    Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Up]\n#define buttonDown  Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Down]\n\nconst std::string* KEYNAMES[32] = {\n &g_controlsStrings.tdsButtonA,   &g_controlsStrings.tdsButtonB, &g_controlsStrings.tdsButtonSelect, &g_controlsStrings.tdsButtonStart,\n &buttonRight,                    &buttonLeft,                   &buttonUp,                     &buttonDown,\n &g_controlsStrings.tdsButtonR,   &g_controlsStrings.tdsButtonL, &g_controlsStrings.tdsButtonX, &g_controlsStrings.tdsButtonY,\n &g_mainMenu.caseNone,            &g_mainMenu.caseNone,          &g_controlsStrings.tdsButtonZL, &g_controlsStrings.tdsButtonZR,\n &g_mainMenu.caseNone,            &g_mainMenu.caseNone,          &g_mainMenu.caseNone,          &g_mainMenu.caseNone,\n &g_controlsStrings.tdsCasePen,   &g_mainMenu.caseNone,          &g_mainMenu.caseNone,          &g_mainMenu.caseNone,\n &buttonRight,                    &buttonLeft,                   &buttonUp,                     &buttonDown,\n &buttonRight,                    &buttonLeft,                   &buttonUp,                     &buttonDown,\n};\n\nenum KEYID {\n    KEYID_A=0,\n    KEYID_B=1,\n    KEYID_SELECT=2,\n    KEYID_START=3,\n    KEYID_DRIGHT=4,\n    KEYID_DLEFT=5,\n    KEYID_DUP=6,\n    KEYID_DDOWN=7,\n    KEYID_R=8,\n    KEYID_L=9,\n    KEYID_X=10,\n    KEYID_Y=11,\n    KEYID_ZL=14,\n    KEYID_ZR=15,\n\n    KEYID_TOUCH=20,\n    KEYID_CSTICK_RIGHT=24,\n    KEYID_CSTICK_LEFT=25,\n    KEYID_CSTICK_UP=26,\n    KEYID_CSTICK_DOWN=27,\n    KEYID_PAD_RIGHT=28,\n    KEYID_PAD_LEFT=29,\n    KEYID_PAD_UP=30,\n    KEYID_PAD_DOWN=31\n};\n\nnamespace Controls\n{\n\n// helper functions\n\n\n/*===============================================*\\\n|| implementation for InputMethod_3DS            ||\n\\*===============================================*/\n\nInputMethod_3DS::~InputMethod_3DS()\n{\n    InputMethodType_3DS* t = dynamic_cast<InputMethodType_3DS*>(this->Type);\n\n    if(!t)\n        return;\n\n    t->m_numPlayers --;\n}\n\n// Update functions that set player controls (and editor controls)\n// based on current device input. Return false if device lost.\nbool InputMethod_3DS::Update(int player, Controls_t& c, CursorControls_t& m, EditorControls_t& e, HotkeysPressed_t& h)\n{\n    InputMethodType_3DS* k = dynamic_cast<InputMethodType_3DS*>(this->Type);\n    InputMethodProfile_3DS* p = dynamic_cast<InputMethodProfile_3DS*>(this->Profile);\n\n    if(!k || !p)\n        return false;\n\n    uint32_t keys_held = hidKeysHeld();\n\n    for(int a = 0; a < 4; a++)\n    {\n        int* keys;\n        int* keys2;\n        size_t key_start;\n        size_t key_max;\n        bool activate = false;\n\n        if(a == 0)\n        {\n            keys = p->m_keys;\n            keys2 = p->m_keys2;\n            key_start = 0;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = p->m_cursor_keys2;\n            key_start = 4;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = p->m_editor_keys;\n            keys2 = p->m_editor_keys2;\n            key_start = 4;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = p->m_hotkeys;\n            keys2 = p->m_hotkeys2;\n            key_start = 0;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = key_start; i < key_max; i++)\n        {\n            int key;\n\n            if(keys)\n                key = keys[i];\n            else\n                key = null_key;\n\n            int key2 = keys2[i];\n\n            bool* b;\n\n            if(a == 0)\n            {\n                b = &PlayerControls::GetButton(c, i);\n                *b = false;\n            }\n            else if(a == 1)\n            {\n                b = &CursorControls::GetButton(m, i);\n            }\n            else if(a == 2)\n            {\n                b = &EditorControls::GetButton(e, i);\n            }\n            else\n            {\n                b = &activate;\n                *b = false;\n            }\n\n            if(key != null_key && BIT(key) & keys_held)\n                *b = true;\n            else if(key2 != null_key && BIT(key2) & keys_held)\n                *b = true;\n\n            if(a == 3 && *b)\n                h[i] = player;\n        }\n    }\n\n    num_t* const scroll[4] = {&e.ScrollUp, &e.ScrollDown, &e.ScrollLeft, &e.ScrollRight};\n\n    for(int i = 0; i < 4; i++)\n    {\n        int key = p->m_editor_keys[i];\n        int key2 = p->m_editor_keys2[i];\n\n        if(key != null_key && BIT(key) & keys_held)\n            *scroll[i] += 10;\n        else if(key2 != null_key && BIT(key2) & keys_held)\n            *scroll[i] += 10;\n    }\n\n    bool cursor[4];\n\n    for(int i = 0; i < 4; i++)\n    {\n        int key = p->m_cursor_keys2[i];\n        cursor[i] = (key != null_key && BIT(key) & keys_held);\n    }\n\n    // Cursor control (UDLR)\n    if(cursor[0] || cursor[1] || cursor[2] || cursor[3])\n    {\n        if(m.X < 0)\n            m.X = XRender::TargetW / 2;\n\n        if(m.Y < 0)\n            m.Y = XRender::TargetH / 2;\n\n        if(cursor[3])\n            m.X += 16;\n\n        if(cursor[2])\n            m.X -= 16;\n\n        if(cursor[1])\n            m.Y += 16;\n\n        if(cursor[0])\n            m.Y -= 16;\n\n        if(m.X < 0)\n            m.X = 0;\n        else if(m.X >= XRender::TargetW)\n            m.X = XRender::TargetW - 1;\n\n        if(m.Y < 0)\n            m.Y = 0;\n        else if(m.Y >= XRender::TargetH)\n            m.Y = XRender::TargetH - 1;\n\n        m.Move = true;\n    }\n\n    // one may uncomment this to quickly simulate controller disconnection for debugging purposes\n    // if(c.Up && c.Down)\n    //     return false;\n\n    return true;\n}\n\nvoid InputMethod_3DS::Rumble(int ms, float strength)\n{\n    UNUSED(ms);\n    UNUSED(strength);\n}\n\nStatusInfo InputMethod_3DS::GetStatus()\n{\n    return XPower::devicePowerStatus();\n}\n\n/*===============================================*\\\n|| implementation for InputMethodProfile_3DS     ||\n\\*===============================================*/\n\n// the job of this function is to initialize the class in a consistent state\nInputMethodProfile_3DS::InputMethodProfile_3DS()\n{\n    for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n    {\n        this->m_keys[i] = null_key;\n        this->m_keys2[i] = null_key;\n    }\n\n    for(size_t i = 0; i < CursorControls::n_buttons; i++)\n        this->m_cursor_keys2[i] = null_key;\n\n    for(size_t i = 0; i < EditorControls::n_buttons; i++)\n    {\n        this->m_editor_keys[i] = null_key;\n        this->m_editor_keys2[i] = null_key;\n    }\n\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n    {\n        this->m_hotkeys[i] = null_key;\n        this->m_hotkeys2[i] = null_key;\n    }\n\n    this->m_keys[PlayerControls::Buttons::Up] = KEYID_DUP;\n    this->m_keys[PlayerControls::Buttons::Down] = KEYID_DDOWN;\n    this->m_keys[PlayerControls::Buttons::Left] = KEYID_DLEFT;\n    this->m_keys[PlayerControls::Buttons::Right] = KEYID_DRIGHT;\n    this->m_keys[PlayerControls::Buttons::Jump] = KEYID_B;\n    this->m_keys[PlayerControls::Buttons::AltJump] = KEYID_A;\n    this->m_keys[PlayerControls::Buttons::Run] = KEYID_Y;\n    this->m_keys[PlayerControls::Buttons::AltRun] = KEYID_X;\n    this->m_keys[PlayerControls::Buttons::Drop] = KEYID_SELECT;\n    this->m_keys[PlayerControls::Buttons::Start] = KEYID_START;\n\n    this->m_keys2[PlayerControls::Buttons::Up] = KEYID_PAD_UP;\n    this->m_keys2[PlayerControls::Buttons::Down] = KEYID_PAD_DOWN;\n    this->m_keys2[PlayerControls::Buttons::Left] = KEYID_PAD_LEFT;\n    this->m_keys2[PlayerControls::Buttons::Right] = KEYID_PAD_RIGHT;\n\n    this->m_editor_keys[EditorControls::Buttons::ScrollUp] = KEYID_PAD_UP;\n    this->m_editor_keys[EditorControls::Buttons::ScrollDown] = KEYID_PAD_DOWN;\n    this->m_editor_keys[EditorControls::Buttons::ScrollLeft] = KEYID_PAD_LEFT;\n    this->m_editor_keys[EditorControls::Buttons::ScrollRight] = KEYID_PAD_RIGHT;\n    this->m_editor_keys[EditorControls::Buttons::FastScroll] = KEYID_L;\n    this->m_editor_keys[EditorControls::Buttons::ModeSelect] = KEYID_B;\n    this->m_editor_keys[EditorControls::Buttons::ModeErase] = KEYID_Y;\n    this->m_editor_keys[EditorControls::Buttons::PrevSection] = KEYID_DLEFT;\n    this->m_editor_keys[EditorControls::Buttons::NextSection] = KEYID_DRIGHT;\n    this->m_editor_keys[EditorControls::Buttons::SwitchScreens] = KEYID_SELECT;\n    this->m_editor_keys[EditorControls::Buttons::TestPlay] = KEYID_START;\n\n    this->m_hotkeys[Hotkeys::Buttons::VanillaCam] = KEYID_L;\n\n    this->m_altMenuControls = true;\n}\n\nbool InputMethodProfile_3DS::PollPrimaryButton(ControlsClass c, size_t i)\n{\n    if(c == ControlsClass::Cursor)\n        return true;\n\n    // note: m_canPoll is initialized to false\n    InputMethodType_3DS* k = dynamic_cast<InputMethodType_3DS*>(this->Type);\n\n    if(!k)\n        return false;\n\n    uint32_t keys_held = hidKeysHeld();\n\n    int key;\n\n    for(key = 0; key < 32; key++)\n    {\n        if(BIT(key) & keys_held)\n            break;\n    }\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == 32)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    int* keys;\n    int* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling 3DS primary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n    // if a button's secondary key (including the current one) is the new key, delete it.\n    // if a button's primary key (excluding the current one) is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, replace it with the button the player is replacing.\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(keys2[j] == key)\n            keys2[j] = null_key;\n        else if(i != j && keys[j] == key)\n        {\n            if(keys2[j] != null_key)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_key;\n            }\n            else\n                keys[j] = keys[i];\n        }\n    }\n\n    keys[i] = key;\n    return true;\n}\n\nbool InputMethodProfile_3DS::PollSecondaryButton(ControlsClass c, size_t i)\n{\n    // note: m_canPoll is initialized to false\n    InputMethodType_3DS* k = dynamic_cast<InputMethodType_3DS*>(this->Type);\n\n    if(!k)\n        return false;\n\n    uint32_t keys_held = hidKeysHeld();\n\n    int key;\n\n    for(key = 0; key < 32; key++)\n    {\n        if(BIT(key) & keys_held)\n            break;\n    }\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == 32)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    int* keys;\n    int* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = nullptr;\n        keys2 = this->m_cursor_keys2;\n        key_max = CursorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling 3DS secondary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n\n    // if the current button's primary key is the new key,\n    //     delete its secondary key instead of setting it.\n    if(keys && keys[i] == key)\n    {\n        keys2[i] = null_key;\n        return true;\n    }\n\n    // if another button's secondary key is the new key, delete it.\n    // if another button's primary key is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, if this button's secondary key is defined, overwrite the other's with this.\n    //     if all else fails, overwrite the other button's with this button's PRIMARY key and assign\n    //         this button's PRIMARY key instead\n\n    bool can_do_secondary = true;\n\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(i != j && keys2[j] == key)\n        {\n            keys2[j] = null_key;\n        }\n        else if(keys && i != j && keys[j] == key)\n        {\n            if(keys2[j] != null_key)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_key;\n            }\n            else if(keys2[i] != null_key)\n            {\n                keys[j] = keys2[i];\n            }\n            else\n            {\n                keys[j] = keys[i];\n                can_do_secondary = false;\n            }\n        }\n    }\n\n    if(can_do_secondary)\n        keys2[i] = key;\n    else if(keys)\n        keys[i] = key;\n\n    return true;\n}\n\nbool InputMethodProfile_3DS::DeletePrimaryButton(ControlsClass c, size_t i)\n{\n    // resolve the particular primary and secondary key arrays\n    int* keys;\n    int* keys2;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        return false;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n    }\n    else\n    {\n        D_pLogWarning(\"Attempted to delete 3DS input binding of disallowed controls class %d\\n\", (int)c);\n        return false;\n    }\n\n    if(keys2[i] != null_key)\n    {\n        keys[i] = keys2[i];\n        keys2[i] = null_key;\n        return true;\n    }\n\n    if(c == ControlsClass::Player)\n        return false;\n\n    if(keys[i] != null_key)\n    {\n        keys[i] = null_key;\n        return true;\n    }\n\n    return false;\n}\n\nbool InputMethodProfile_3DS::DeleteSecondaryButton(ControlsClass c, size_t i)\n{\n    int* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return false;\n\n    if(keys2[i] != null_key)\n    {\n        keys2[i] = null_key;\n        return true;\n    }\n\n    return false;\n}\n\nconst char* InputMethodProfile_3DS::NamePrimaryButton(ControlsClass c, size_t i)\n{\n    int* keys;\n\n    if(c == ControlsClass::Player)\n        keys = this->m_keys;\n    else if(c == ControlsClass::Cursor)\n        return g_controlsStrings.tdsCasePen.c_str();\n    else if(c == ControlsClass::Editor)\n        keys = this->m_editor_keys;\n    else if(c == ControlsClass::Hotkey)\n        keys = this->m_hotkeys;\n    else\n        return \"\";\n\n    if(keys[i] == null_key)\n        return g_mainMenu.caseNone.c_str();\n\n    if(keys[i] < 0 || keys[i] >= 32)\n        return g_controlsStrings.sharedCaseInvalid.c_str();\n\n    if(keys[i] >= KEYID_DRIGHT && keys[i] <= KEYID_DDOWN)\n    {\n        s_buffer = g_controlsStrings.tdsDpad + \" \" + *KEYNAMES[keys[i]];\n        return s_buffer.c_str();\n    }\n\n    if(keys[i] >= KEYID_CSTICK_RIGHT && keys[i] <= KEYID_CSTICK_DOWN)\n    {\n        s_buffer = g_controlsStrings.tdsCstick + \" \" + *KEYNAMES[keys[i]];\n        return s_buffer.c_str();\n    }\n\n    if(keys[i] >= KEYID_PAD_RIGHT && keys[i] <= KEYID_PAD_DOWN)\n    {\n        s_buffer = g_controlsStrings.tdsTstick + \" \" + *KEYNAMES[keys[i]];\n        return s_buffer.c_str();\n    }\n\n    return KEYNAMES[keys[i]]->c_str();\n}\n\nconst char* InputMethodProfile_3DS::NameSecondaryButton(ControlsClass c, size_t i)\n{\n    int* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return \"\";\n\n    if(keys2[i] == null_key)\n        return \"\";\n\n    if(keys2[i] < 0 || keys2[i] >= 32)\n        return g_controlsStrings.sharedCaseInvalid.c_str();\n\n    if(keys2[i] >= KEYID_DRIGHT && keys2[i] <= KEYID_DDOWN)\n    {\n        s_buffer = g_controlsStrings.tdsDpad + \" \" + *KEYNAMES[keys2[i]];\n        return s_buffer.c_str();\n    }\n\n    if(keys2[i] >= KEYID_CSTICK_RIGHT && keys2[i] <= KEYID_CSTICK_DOWN)\n    {\n        s_buffer = g_controlsStrings.tdsCstick + \" \" + *KEYNAMES[keys2[i]];\n        return s_buffer.c_str();\n    }\n\n    if(keys2[i] >= KEYID_PAD_RIGHT && keys2[i] <= KEYID_PAD_DOWN)\n    {\n        s_buffer = g_controlsStrings.tdsTstick + \" \" + *KEYNAMES[keys2[i]];\n        return s_buffer.c_str();\n    }\n\n    return KEYNAMES[keys2[i]]->c_str();\n}\n\nvoid InputMethodProfile_3DS::SaveConfig(IniProcessing* ctl)\n{\n    char name2[20];\n\n    for(int a = 0; a < 4; a++)\n    {\n        int* keys;\n        int* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            const char* name;\n\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            if(keys)\n                ctl->setValue(name, keys[i]);\n\n            for(size_t c = 0; c < 20; c++)\n            {\n                if(c + 2 == 20 || name[c] == '\\0')\n                {\n                    name2[c] = '2';\n                    name2[c + 1] = '\\0';\n                    break;\n                }\n\n                name2[c] = name[c];\n            }\n\n            ctl->setValue(name2, keys2[i]);\n        }\n    }\n}\n\nvoid InputMethodProfile_3DS::LoadConfig(IniProcessing* ctl)\n{\n    char name2[20];\n\n    for(int a = 0; a < 4; a++)\n    {\n        int* keys;\n        int* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            const char* name;\n\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            if(keys)\n                ctl->read(name, keys[i], keys[i]);\n\n            for(size_t c = 0; c < 20; c++)\n            {\n                if(c + 2 == 20 || name[c] == '\\0')\n                {\n                    name2[c] = '2';\n                    name2[c + 1] = '\\0';\n                    break;\n                }\n\n                name2[c] = name[c];\n            }\n\n            ctl->read(name2, keys2[i], keys2[i]);\n        }\n    }\n}\n\n/*===============================================*\\\n|| implementation for InputMethodType_3DS        ||\n\\*===============================================*/\n\nInputMethodProfile* InputMethodType_3DS::AllocateProfile() noexcept\n{\n    return (InputMethodProfile*) new(std::nothrow) InputMethodProfile_3DS;\n}\n\nInputMethodType_3DS::InputMethodType_3DS()\n{\n    this->Name = \"3DS\";\n    hidInit();\n}\n\nInputMethodType_3DS::~InputMethodType_3DS()\n{\n    hidExit();\n}\n\nbool InputMethodType_3DS::TestProfileType(InputMethodProfile* profile)\n{\n    return (bool)dynamic_cast<InputMethodProfile_3DS*>(profile);\n}\n\nbool InputMethodType_3DS::RumbleSupported()\n{\n    return false;\n}\n\nvoid InputMethodType_3DS::UpdateControlsPre()\n{\n    hidScanInput();\n}\n\nvoid InputMethodType_3DS::UpdateControlsPost()\n{\n    uint32_t keys_pressed = hidKeysDown();\n    uint32_t keys_held = hidKeysHeld();\n\n    // handle the mouse\n    int tx, ty;\n    touchPosition pos;\n    hidTouchRead(&pos);\n    XRender::mapToScreen(pos.px, pos.py, &tx, &ty);\n\n    if(keys_pressed & KEY_TOUCH)\n    {\n        if((LevelEditor || MagicHand) && ty > 40 && !editorScreen.active)\n        {\n            int distance2 = (m_lastTouchX - tx)*(m_lastTouchX - tx)\n                + (m_lastTouchY - ty)*(m_lastTouchY - ty);\n\n            if(distance2 < 64)\n                m_click_accepted = true;\n            else\n                m_click_accepted = false;\n        }\n        else\n            m_click_accepted = true;\n    }\n\n    if(keys_held & KEY_TOUCH)\n    {\n        if(tx - m_lastTouchX < -1 || tx - m_lastTouchX > 1\n            || ty - m_lastTouchY < -1 || ty - m_lastTouchY > 1)\n        {\n            SharedCursor.Move = true;\n            SharedCursor.X = tx;\n            SharedCursor.Y = ty;\n\n            m_lastTouchX = tx;\n            m_lastTouchY = ty;\n        }\n\n        if(m_click_accepted)\n        {\n            SharedCursor.Primary = true;\n        }\n    }\n}\n\nInputMethod* InputMethodType_3DS::Poll(const std::vector<InputMethod*>& active_methods) noexcept\n{\n    if(this->m_numPlayers != this->m_lastNumPlayers)\n    {\n        // this ensures that keys that were held when a keyboard method was removed cannot be polled to add that method back\n        if(this->m_numPlayers < this->m_lastNumPlayers)\n            this->m_canPoll = false;\n\n        this->m_lastNumPlayers = this->m_numPlayers;\n    }\n\n    if(this->m_numPlayers >= m_maxPlayers)\n    {\n        // ban polling in case things change\n        this->m_canPoll = false;\n        return nullptr;\n    }\n\n    uint32_t keys_held = hidKeysHeld();\n\n    // ban attachment from active profile, must find new profile\n    int key;\n    InputMethodProfile* target_profile = nullptr;\n\n    for(key = 0; key < 32; key++)\n    {\n        if((BIT(key) & keys_held) == 0)\n            continue;\n\n        bool allowed = true;\n\n        // ban attachment from active profile\n        for(InputMethod* method : active_methods)\n        {\n            if(!allowed)\n                break;\n\n            if(!method)\n                continue;\n\n            InputMethodProfile_3DS* p = dynamic_cast<InputMethodProfile_3DS*>(method->Profile);\n\n            if(!p)\n                continue;\n\n            for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n            {\n                if(p->m_keys[i] == key || p->m_keys2[i] == key)\n                {\n                    allowed = false;\n                    break;\n                }\n            }\n\n            if(LevelEditor)\n            {\n                for(size_t i = 0; i < EditorControls::n_buttons; i++)\n                {\n                    if(p->m_editor_keys[i] == key || p->m_editor_keys2[i] == key)\n                    {\n                        allowed = false;\n                        break;\n                    }\n                }\n            }\n\n            for(size_t i = 0; i < CursorControls::n_buttons; i++)\n            {\n                if(p->m_cursor_keys2[i] == key)\n                {\n                    allowed = false;\n                    break;\n                }\n            }\n\n            for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n            {\n                if(p->m_hotkeys[i] == key || p->m_hotkeys2[i] == key)\n                {\n                    allowed = false;\n                    break;\n                }\n            }\n        }\n\n        if(!allowed)\n            continue;\n\n        // which player index is connecting?\n        int my_index = 0;\n\n        for(const InputMethod* method : active_methods)\n        {\n            if(!method)\n                break;\n\n            my_index ++;\n        }\n\n        // try to find profile matching the keypress\n        for(int i = -1; i < (int)this->m_profiles.size(); i++)\n        {\n            // start with the most recent profile for this player index\n            InputMethodProfile* profile;\n\n            if(i == -1)\n                profile = this->GetDefaultProfile(my_index);\n            else\n                profile = this->m_profiles[i];\n\n            if(!profile)\n                continue;\n\n            InputMethodProfile_3DS* p = dynamic_cast<InputMethodProfile_3DS*>(profile);\n\n            if(!p)\n                continue;\n\n            for(size_t j = 0; j < PlayerControls::n_buttons; j++)\n            {\n                if(p->m_keys[j] == key || p->m_keys2[j] == key)\n                {\n                    target_profile = profile;\n                    break;\n                }\n            }\n\n            if(target_profile)\n                break;\n        }\n\n        if(target_profile || this->m_profiles.empty())\n            break;\n    }\n\n    // if didn't find any key allow poll in future but return false\n    if(key == 32)\n    {\n        this->m_canPoll = true;\n        return nullptr;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return nullptr;\n\n    // we're going to create a new keyboard!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    InputMethod_3DS* method = new(std::nothrow) InputMethod_3DS;\n\n    if(!method)\n        return nullptr;\n\n    method->Name = this->Name;\n    method->Type = this;\n    method->Profile = target_profile;\n\n    this->m_numPlayers ++;\n\n    return (InputMethod*)method;\n}\n\n/*-----------------------*\\\n|| OPTIONAL METHODS      ||\n\\*-----------------------*/\nbool InputMethodType_3DS::ConsumeEvent(const SDL_Event* ev)\n{\n    UNUSED(ev);\n    return false;\n}\n\n// optional function allowing developer to associate device information with profile, etc\nbool InputMethodType_3DS::SetProfile_Custom(InputMethod* method, int player_no, InputMethodProfile* profile,\n        const std::vector<InputMethod*>& active_methods)\n{\n    if(!method || !profile || player_no < 0 || player_no >= maxLocalPlayers)\n        return false;\n\n    // prevent duplicates of a profile from ever being set\n    for(InputMethod* o_method : active_methods)\n    {\n        if(!o_method)\n            continue;\n\n        if(o_method != method && o_method->Profile == profile)\n            return false;\n    }\n\n    m_canPoll = false;\n    return true;\n}\n\n// How many per-type special options are there?\nsize_t InputMethodType_3DS::GetOptionCount()\n{\n    return 1;\n}\n\n// Methods to manage per-profile options\n// It is guaranteed that none of these will be called if\n// GetOptionCount() returns 0.\n// get a char* describing the option\nconst char* InputMethodType_3DS::GetOptionName(size_t i)\n{\n    if(i == 0)\n        return g_controlsStrings.sharedOptionMaxPlayers.c_str();\n\n    return nullptr;\n}\n\n// get a char* describing the current option value\n// must be allocated in static or instance memory\n// WILL NOT be freed\nconst char* InputMethodType_3DS::GetOptionValue(size_t i)\n{\n    if(i == 0)\n    {\n        static char buf[3];\n        snprintf(buf, 3, \"%d\", this->m_maxPlayers);\n        return buf;\n    }\n\n    return nullptr;\n}\n\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodType_3DS::OptionChange(size_t i)\n{\n    if(i == 0)\n    {\n        this->m_maxPlayers ++;\n\n        if(this->m_maxPlayers > 2)\n            this->m_maxPlayers = 1;\n\n        return true;\n    }\n\n    return false;\n}\n\n// called when left is pressed\nbool InputMethodType_3DS::OptionRotateLeft(size_t i)\n{\n    if(i == 0)\n    {\n        if(this->m_maxPlayers > 1)\n        {\n            this->m_maxPlayers --;\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// called when right is pressed\nbool InputMethodType_3DS::OptionRotateRight(size_t i)\n{\n    if(i == 0)\n    {\n        if(this->m_maxPlayers < 2)\n        {\n            this->m_maxPlayers ++;\n            return true;\n        }\n    }\n\n    return false;\n}\n\nvoid InputMethodType_3DS::SaveConfig_Custom(IniProcessing* ctl)\n{\n    ctl->setValue(\"max-players\", this->m_maxPlayers);\n}\n\nvoid InputMethodType_3DS::LoadConfig_Custom(IniProcessing* ctl)\n{\n    ctl->read(\"max-players\", this->m_maxPlayers, 2);\n}\n\n} // namespace Controls\n"
  },
  {
    "path": "src/control/input_3ds.h",
    "content": "#ifndef INPUT_3DS_H\n#define INPUT_3DS_H\n\n#include \"../controls.h\"\n\nnamespace Controls\n{\n\nconstexpr int null_key = -1;\n\nclass InputMethod_3DS : public InputMethod\n{\npublic:\n    using InputMethod::Type;\n    using InputMethod::Profile;\n\n    ~InputMethod_3DS();\n\n    // Update functions that set player controls (and editor controls)\n    // based on current device input. Return false if device lost.\n    bool Update(int player, Controls_t &c, CursorControls_t &m, EditorControls_t &e, HotkeysPressed_t &h);\n\n    void Rumble(int ms, float strength);\n\n    StatusInfo GetStatus();\n};\n\nclass InputMethodProfile_3DS : public InputMethodProfile\n{\nprivate:\n    bool m_canPoll = false;\n\npublic:\n    using InputMethodProfile::Name;\n    using InputMethodProfile::Type;\n\n    int m_keys[PlayerControls::n_buttons] = {null_key};\n    int m_keys2[PlayerControls::n_buttons] = {null_key};\n\n    int m_editor_keys[EditorControls::n_buttons] = {null_key};\n    int m_editor_keys2[EditorControls::n_buttons] = {null_key};\n\n    int m_cursor_keys2[CursorControls::n_buttons] = {null_key};\n\n    int m_hotkeys[Hotkeys::n_buttons] = {null_key};\n    int m_hotkeys2[Hotkeys::n_buttons] = {null_key};\n\n    InputMethodProfile_3DS();\n\n    // Polls a new (secondary) device button for the i'th player button\n    // Returns true on success and false if no button pressed\n    // Never allows two player buttons to bind to the same device button\n    bool PollPrimaryButton(ControlsClass c, size_t i);\n    bool PollSecondaryButton(ControlsClass c, size_t i);\n\n    // Deletes a primary button for the i'th button of class c (only called for non-Player buttons)\n    bool DeletePrimaryButton(ControlsClass c, size_t i);\n\n    // Deletes a secondary device button for the i'th button of class c\n    bool DeleteSecondaryButton(ControlsClass c, size_t i);\n\n    // Gets strings for the device buttons currently used for the i'th button of class c\n    const char *NamePrimaryButton(ControlsClass c, size_t i);\n    const char *NameSecondaryButton(ControlsClass c, size_t i);\n\n    // one can assume that the IniProcessing* is already in the correct group\n    void SaveConfig(IniProcessing *ctl);\n    void LoadConfig(IniProcessing *ctl);\n};\n\nclass InputMethodType_3DS : public InputMethodType\n{\nprivate:\n    bool m_canPoll = false;\n\n    int m_lastNumPlayers = 0;\n    uint16_t m_lastTouchX = 0;\n    uint16_t m_lastTouchY = 0;\n    bool m_click_accepted = false;\n\n    InputMethodProfile *AllocateProfile() noexcept;\n\npublic:\n    int m_numPlayers = 0;\n\n    // options\n    int m_maxPlayers = 2;\n\n    using InputMethodType::Name;\n    using InputMethodType::m_profiles;\n\n    InputMethodType_3DS();\n    ~InputMethodType_3DS();\n\n    bool TestProfileType(InputMethodProfile *profile);\n    bool RumbleSupported();\n\n    void UpdateControlsPre();\n    void UpdateControlsPost();\n\n    // null if no input method is ready\n    // allocates the new InputMethod on the heap\n    InputMethod *Poll(const std::vector<InputMethod *> &active_methods) noexcept;\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\nprotected:\n    // optional function allowing developer to associate device information with profile, etc\n    // if developer wants to forbid assignment, return false\n    bool SetProfile_Custom(InputMethod *method, int player_no, InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods);\n\npublic:\n    bool ConsumeEvent(const SDL_Event *ev);\n\n    // How many per-type special options are there?\n    size_t GetOptionCount();\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount() returns 0.\n    // get a char* describing the option\n    const char *GetOptionName(size_t i);\n    // get a char* describing the current option value\n    // must be allocated in static or instance memory\n    // WILL NOT be freed\n    const char *GetOptionValue(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange(size_t i);\n    // called when left is pressed\n    bool OptionRotateLeft(size_t i);\n    // called when right is pressed\n    bool OptionRotateRight(size_t i);\n\nprotected:\n    void SaveConfig_Custom(IniProcessing *ctl);\n    void LoadConfig_Custom(IniProcessing *ctl);\n};\n\n} // namespace Controls\n\n#endif // #ifndef INPUT_3DS_H\n"
  },
  {
    "path": "src/control/input_wii.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n\n#include \"globals.h\"\n#include \"game_main.h\"\n\n#include \"core/render.h\"\n\n#include \"controls.h\"\n#include \"control/input_wii.h\"\n\n#include \"control/controls_strings.h\"\n#include \"main/menu_main.h\"\n\n#include <Logger/logger.h>\n\n#include <wiiuse/wpad.h>\n\n\n#define WPAD_STICK_LL  0x2000\n#define WPAD_STICK_LR  0x4000\n#define WPAD_STICK_LU  0x6000\n// not technically legal.\n#define WPAD_STICK_LD  0x7000\n#define WPAD_STICK_RL  0xA000\n#define WPAD_STICK_RR  0xC000\n#define WPAD_STICK_RU  0xE000\n// not technically legal.\n#define WPAD_STICK_RD  0xF000\n\n#define WPAD_SHAKE     0x1FFF\n\n#define buttonRight Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Right]\n#define buttonLeft  Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Left]\n#define buttonUp    Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Up]\n#define buttonDown  Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Down]\n\nbool is_direction(const std::string* s)\n{\n    return s == &buttonLeft || s == &buttonDown || s == &buttonUp || s == &buttonRight;\n}\n\nstatic constexpr std::array<const uint32_t, 12> wiimote_buttons =      {WPAD_BUTTON_2,                 WPAD_BUTTON_1,                 WPAD_BUTTON_B,                 WPAD_BUTTON_A,                 WPAD_BUTTON_MINUS,                 WPAD_BUTTON_HOME,                 WPAD_BUTTON_LEFT, WPAD_BUTTON_RIGHT, WPAD_BUTTON_DOWN, WPAD_BUTTON_UP, WPAD_BUTTON_PLUS,                 WPAD_SHAKE};\nstatic const std::array<const std::string*, 12> wiimote_button_names = {&g_controlsStrings.wiiButton2, &g_controlsStrings.wiiButton1, &g_controlsStrings.wiiButtonB, &g_controlsStrings.wiiButtonA, &g_controlsStrings.wiiButtonMinus, &g_controlsStrings.wiiButtonHome, &buttonLeft,      &buttonRight,      &buttonDown,      &buttonUp,      &g_controlsStrings.wiiButtonPlus, &g_controlsStrings.wiiShake};\n\nstatic constexpr std::array<const uint32_t, 6> nunchuck_buttons =      {WPAD_STICK_LL, WPAD_STICK_LR, WPAD_STICK_LU, WPAD_STICK_LD, WPAD_NUNCHUK_BUTTON_Z,         WPAD_NUNCHUK_BUTTON_C};\nstatic const std::array<const std::string*, 6> nunchuck_button_names = {&buttonLeft,   &buttonRight,  &buttonUp,     &buttonDown,   &g_controlsStrings.wiiButtonZ, &g_controlsStrings.wiiButtonC};\n\nstatic constexpr std::array<const uint32_t, 23> classic_buttons = {WPAD_CLASSIC_BUTTON_UP, WPAD_CLASSIC_BUTTON_LEFT, WPAD_CLASSIC_BUTTON_ZR,         WPAD_CLASSIC_BUTTON_X,         WPAD_CLASSIC_BUTTON_A,         WPAD_CLASSIC_BUTTON_Y,\n    WPAD_CLASSIC_BUTTON_B,         WPAD_CLASSIC_BUTTON_ZL,         WPAD_CLASSIC_BUTTON_FULL_R,     WPAD_CLASSIC_BUTTON_PLUS,         WPAD_CLASSIC_BUTTON_HOME,         WPAD_CLASSIC_BUTTON_MINUS,         WPAD_CLASSIC_BUTTON_FULL_L,     WPAD_CLASSIC_BUTTON_DOWN,\n    WPAD_CLASSIC_BUTTON_RIGHT, WPAD_STICK_LL, WPAD_STICK_LR, WPAD_STICK_LU, WPAD_STICK_LD, WPAD_STICK_RL, WPAD_STICK_RR, WPAD_STICK_RU, WPAD_STICK_RD};\nstatic const std::array<const std::string*, 23> classic_button_names = {&buttonUp,         &buttonLeft,              &g_controlsStrings.wiiButtonZR, &g_controlsStrings.wiiButtonX, &g_controlsStrings.wiiButtonA, &g_controlsStrings.wiiButtonY,\n    &g_controlsStrings.wiiButtonB, &g_controlsStrings.wiiButtonZL, &g_controlsStrings.wiiButtonRT, &g_controlsStrings.wiiButtonPlus, &g_controlsStrings.wiiButtonHome, &g_controlsStrings.wiiButtonMinus, &g_controlsStrings.wiiButtonLT, &buttonDown,\n    &buttonRight,              &buttonLeft,   &buttonRight,  &buttonUp,     &buttonDown,   &buttonLeft,   &buttonRight,  &buttonUp,     &buttonDown};\n\nstatic std::string s_buffer;\n\nstatic const char* s_get_name(uint32_t button, uint8_t expansion)\n{\n    if(button == Controls::null_but)\n        return nullptr;\n\n    // main Wiimote buttons\n    if(button < WPAD_STICK_LL)\n    {\n        auto i = std::find(wiimote_buttons.begin(), wiimote_buttons.end(), button);\n        if(i == wiimote_buttons.end())\n            return g_controlsStrings.sharedCaseInvalid.c_str();\n\n        int index = i - wiimote_buttons.begin();\n        if(is_direction(wiimote_button_names[index]))\n        {\n            s_buffer = g_controlsStrings.wiiDpad + \" \" + *wiimote_button_names[index];\n            return s_buffer.c_str();\n        }\n\n        return wiimote_button_names[index]->c_str();\n    }\n\n    // nunchuck buttons\n    if(expansion == WPAD_EXP_NUNCHUK)\n    {\n        auto i = std::find(nunchuck_buttons.begin(), nunchuck_buttons.end(), button);\n        if(i == nunchuck_buttons.end())\n            return g_controlsStrings.sharedCaseInvalid.c_str();\n\n        int index = i - nunchuck_buttons.begin();\n        if(is_direction(nunchuck_button_names[index]))\n        {\n            s_buffer = g_controlsStrings.wiiPrefixNunchuck + \" \" + *nunchuck_button_names[index];\n            return s_buffer.c_str();\n        }\n\n        return nunchuck_button_names[index]->c_str();\n    }\n\n    // classic buttons\n    if(expansion == WPAD_EXP_CLASSIC)\n    {\n        auto i = std::find(classic_buttons.begin(), classic_buttons.end(), button);\n        if(i == classic_buttons.end())\n            return g_controlsStrings.sharedCaseInvalid.c_str();\n\n        int index = i - classic_buttons.begin();\n        if(is_direction(classic_button_names[index]))\n        {\n            const std::string& prefix = (button >= WPAD_STICK_RL && button <= WPAD_STICK_RD) ? g_controlsStrings.wiiRStick\n                : (button >= WPAD_STICK_LL && button <= WPAD_STICK_LD) ? g_controlsStrings.wiiLStick\n                : g_controlsStrings.wiiDpad;\n            s_buffer = prefix + \" \" + *classic_button_names[index];\n            return s_buffer.c_str();\n        }\n\n        return classic_button_names[index]->c_str();\n    }\n\n    return g_controlsStrings.sharedCaseInvalid.c_str();\n}\n\nstatic inline num_t i_get_thumb_dbl(WPADData* data, uint32_t button, uint8_t expansion)\n{\n    const joystick_t& js = (expansion == WPAD_EXP_NUNCHUK)\n        ? data->exp.nunchuk.js\n        : ((button < WPAD_STICK_RL)\n            ? data->exp.classic.ljs\n            : data->exp.classic.rjs);\n\n    if(button >= 0x8000)\n        button -= 0x8000;\n\n    int max, center, val;\n    if(button == WPAD_STICK_LL)\n    {\n        center = js.center.x;\n        max = js.min.x;\n        val = js.pos.x;\n    }\n    else if(button == WPAD_STICK_LR)\n    {\n        center = js.center.x;\n        max = js.max.x;\n        val = js.pos.x;\n    }\n    else if(button == WPAD_STICK_LU)\n    {\n        center = js.center.y;\n        max = js.max.y;\n        val = js.pos.y;\n    }\n    else // (button == WPAD_STICK_LD)\n    {\n        center = js.center.y;\n        max = js.min.y;\n        val = js.pos.y;\n    }\n\n    num_t decode = (num_t)(val - center) / (max - center);\n    decode -= 0.1_n;\n    decode /= 0.9_ri;\n    if(decode < 0.1_n)\n        decode = 0;\n\n    return decode;\n}\n\nstatic bool s_get_button(WPADData* data, uint32_t button, uint8_t expansion)\n{\n    (void)expansion;\n\n    if(button == Controls::null_but)\n        return false;\n\n    // intelligent sideways controls\n    if(button >= WPAD_BUTTON_LEFT && button <= WPAD_BUTTON_UP && expansion == WPAD_EXP_NONE)\n    {\n        bool probably_sideways = (!GameMenu && !LevelEditor && !MagicHand && (GamePaused == PauseCode::None || GamePaused == PauseCode::PauseScreen || GamePaused == PauseCode::Message));\n        probably_sideways |= !data->ir.valid;\n\n        if(probably_sideways)\n        {\n            switch(button)\n            {\n            case WPAD_BUTTON_LEFT:\n                button = WPAD_BUTTON_UP;\n                break;\n            case WPAD_BUTTON_DOWN:\n                button = WPAD_BUTTON_LEFT;\n                break;\n            case WPAD_BUTTON_RIGHT:\n                button = WPAD_BUTTON_DOWN;\n                break;\n            case WPAD_BUTTON_UP:\n                button = WPAD_BUTTON_RIGHT;\n                break;\n            }\n        }\n    }\n\n    if(button < WPAD_SHAKE)\n        return button & data->btns_h;\n\n    if(button == WPAD_SHAKE)\n        return data->gforce.z < 0.0f || data->gforce.z > 2.0f;\n\n    // thumbstick button\n    if(button <= WPAD_STICK_RU)\n        return i_get_thumb_dbl(data, button, expansion) > 0.25_n;\n\n    // expansion button, WPAD makes this easy :)\n    return button & data->btns_h;\n\n    return false;\n}\n\nstatic num_t s_get_button_dbl(WPADData* data, uint32_t button, uint8_t expansion)\n{\n    if(button >= WPAD_STICK_LL && button <= WPAD_STICK_RU)\n    {\n        return i_get_thumb_dbl(data, button, expansion);\n    }\n\n    if(s_get_button(data, button, expansion))\n        return 0.5_n;\n    else\n        return 0.0_n;\n}\n\nnamespace XRender\n{\n    extern int g_rmode_w, g_rmode_h;\n};\n\nnamespace Controls\n{\n\n/*===============================================*\\\n|| implementation for InputMethod_Wii            ||\n\\*===============================================*/\n\nInputMethod_Wii::InputMethod_Wii(int chn) : m_chn(chn)\n{\n}\n\nInputMethod_Wii::~InputMethod_Wii()\n{\n    InputMethodType_Wii* t = dynamic_cast<InputMethodType_Wii*>(this->Type);\n\n    if(!t)\n        return;\n\n    t->m_canPoll = false;\n}\n\n// Update functions that set player controls (and editor controls)\n// based on current device input. Return false if device lost.\nbool InputMethod_Wii::Update(int player, Controls_t& c, CursorControls_t& m, EditorControls_t& e, HotkeysPressed_t& h)\n{\n    InputMethodProfile_Wii* p = dynamic_cast<InputMethodProfile_Wii*>(this->Profile);\n\n    if(!p)\n        return false;\n\n    WPADData* data = WPAD_Data(m_chn);\n    if(!data)\n        return false;\n\n    if(data->err || !data->data_present)\n        return false;\n\n    if(data->exp.type != p->m_expansion)\n    {\n        pLogDebug(\"Disconnected input profile %s from Wiimote index %d because of extension mismatch: (Wiimote %d, profile %d). This usually happens because an extension has been connected or disconnected.\", p->Name.c_str(), m_chn, (int)data->exp.type, (int)p->m_expansion);\n        return false;\n    }\n\n    if(m_rumble_ticks)\n    {\n        m_rumble_ticks--;\n        if(!m_rumble_ticks)\n            WPAD_Rumble(m_chn, 0);\n    }\n\n    float shake_accum_target = fabs(data->gforce.z - 1.0f);\n\n    if(shake_accum_target > m_shake_accum)\n        m_shake_accum = shake_accum_target;\n    else if(m_shake_accum > 0.05)\n        m_shake_accum -= 0.05;\n\n    m_battery_status = data->battery_level;\n\n    bool probably_sideways = (!GameMenu && !LevelEditor && !MagicHand && (GamePaused == PauseCode::None || GamePaused == PauseCode::PauseScreen || GamePaused == PauseCode::Message));\n    probably_sideways |= !data->ir.valid;\n\n    for(int a = 0; a < 4; a++)\n    {\n        uint32_t* keys;\n        uint32_t* keys2;\n        size_t key_start;\n        size_t key_max;\n        bool activate = false;\n\n        if(a == 0)\n        {\n            keys = p->m_keys;\n            keys2 = p->m_keys2;\n            key_start = 0;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = p->m_cursor_keys2;\n            key_start = 4;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = p->m_editor_keys;\n            keys2 = p->m_editor_keys2;\n            key_start = 4;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = p->m_hotkeys;\n            keys2 = p->m_hotkeys2;\n            key_start = 0;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = key_start; i < key_max; i++)\n        {\n            uint32_t key;\n\n            if(keys)\n                key = keys[i];\n            else\n                key = null_but;\n\n            uint32_t key2 = keys2[i];\n\n            if(p->m_expansion == WPAD_EXP_NONE)\n            {\n                // don't use A/B as cursor controls if in sideways mode\n                if(probably_sideways && a == 1)\n                {\n                    continue;\n                }\n\n                // don't use A/B as AltJump / AltRun if in IR mode\n                if(!probably_sideways && a != 1)\n                {\n                    if(key == p->m_cursor_keys2[CursorControls::Primary] || key == p->m_cursor_keys2[CursorControls::Secondary])\n                        key = null_but;\n                    if(key2 == p->m_cursor_keys2[CursorControls::Primary] || key == p->m_cursor_keys2[CursorControls::Secondary])\n                        key2 = null_but;\n                }\n            }\n\n            bool* b;\n\n            if(a == 0)\n            {\n                b = &PlayerControls::GetButton(c, i);\n                *b = false;\n            }\n            else if(a == 1)\n            {\n                b = &CursorControls::GetButton(m, i);\n            }\n            else if(a == 2)\n            {\n                b = &EditorControls::GetButton(e, i);\n            }\n            else\n            {\n                b = &activate;\n                *b = false;\n            }\n\n            if((key == WPAD_SHAKE && m_shake_accum > 1.0) || s_get_button(data, key, p->m_expansion))\n                *b = true;\n            else if((key2 == WPAD_SHAKE && m_shake_accum > 1.0) || s_get_button(data, key2, p->m_expansion))\n                *b = true;\n\n            if(a == 3 && *b)\n                h[i] = player;\n        }\n    }\n\n    num_t* const scroll[4] = {&e.ScrollUp, &e.ScrollDown, &e.ScrollLeft, &e.ScrollRight};\n\n    for(int i = 0; i < 4; i++)\n    {\n        int key = p->m_editor_keys[i];\n        int key2 = p->m_editor_keys2[i];\n\n        *scroll[i] += s_get_button_dbl(data, key, p->m_expansion) * 32;\n        *scroll[i] += s_get_button_dbl(data, key2, p->m_expansion) * 32;\n    }\n\n    num_t cursor[4];\n\n    for(int i = 0; i < 4; i++)\n    {\n        int key = p->m_cursor_keys2[i];\n        cursor[i] = s_get_button_dbl(data, key, p->m_expansion) * 32;\n    }\n\n    // Cursor control (UDLR)\n    if(cursor[0] || cursor[1] || cursor[2] || cursor[3])\n    {\n        if(m.X < 0)\n            m.X = XRender::TargetW / 2;\n\n        if(m.Y < 0)\n            m.Y = XRender::TargetH / 2;\n\n        m.X += cursor[3];\n        m.X -= cursor[2];\n        m.Y += cursor[1];\n        m.Y -= cursor[0];\n\n        if(m.X < 0)\n            m.X = 0;\n        else if(m.X >= XRender::TargetW)\n            m.X = XRender::TargetW - 1;\n\n        if(m.Y < 0)\n            m.Y = 0;\n        else if(m.Y >= XRender::TargetH)\n            m.Y = XRender::TargetH - 1;\n\n        m.Move = true;\n    }\n\n    return true;\n}\n\nvoid InputMethod_Wii::Rumble(int ms, float strength)\n{\n    UNUSED(strength);\n\n    WPAD_Rumble(m_chn, 1);\n\n    m_rumble_ticks = ms / 15 + 1;\n}\n\nStatusInfo InputMethod_Wii::GetStatus()\n{\n    StatusInfo res;\n\n    res.power_status = StatusInfo::POWER_DISCHARGING;\n\n    res.power_level = numf_t(m_battery_status) / 105;\n\n    return res;\n}\n\n/*===============================================*\\\n|| implementation for InputMethodProfile_Wii     ||\n\\*===============================================*/\n\n// the job of this function is to initialize the class in a consistent state\nInputMethodProfile_Wii::InputMethodProfile_Wii(uint8_t expansion)\n{\n    this->InitAs(expansion);\n}\n\nvoid InputMethodProfile_Wii::InitAs(uint8_t expansion)\n{\n    this->m_expansion = expansion;\n\n    for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n    {\n        this->m_keys[i] = null_but;\n        this->m_keys2[i] = null_but;\n    }\n\n    for(size_t i = 0; i < CursorControls::n_buttons; i++)\n        this->m_cursor_keys2[i] = null_but;\n\n    for(size_t i = 0; i < EditorControls::n_buttons; i++)\n    {\n        this->m_editor_keys[i] = null_but;\n        this->m_editor_keys2[i] = null_but;\n    }\n\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n    {\n        this->m_hotkeys[i] = null_but;\n        this->m_hotkeys2[i] = null_but;\n    }\n\n    if(m_expansion == WPAD_EXP_NUNCHUK)\n    {\n        this->m_keys[PlayerControls::Buttons::Up] = WPAD_STICK_LU;\n        this->m_keys[PlayerControls::Buttons::Down] = WPAD_STICK_LD;\n        this->m_keys[PlayerControls::Buttons::Left] = WPAD_STICK_LL;\n        this->m_keys[PlayerControls::Buttons::Right] = WPAD_STICK_LR;\n        this->m_keys[PlayerControls::Buttons::Jump] = WPAD_BUTTON_A;\n        this->m_keys[PlayerControls::Buttons::AltJump] = WPAD_NUNCHUK_BUTTON_C;\n        this->m_keys[PlayerControls::Buttons::Run] = WPAD_BUTTON_B;\n        this->m_keys[PlayerControls::Buttons::AltRun] = WPAD_NUNCHUK_BUTTON_Z;\n        this->m_keys[PlayerControls::Buttons::Drop] = WPAD_BUTTON_MINUS;\n        this->m_keys[PlayerControls::Buttons::Start] = WPAD_BUTTON_PLUS;\n\n        this->m_keys2[PlayerControls::Buttons::Drop] = WPAD_BUTTON_DOWN;\n\n        this->m_editor_keys[EditorControls::Buttons::ScrollUp] = WPAD_STICK_LU;\n        this->m_editor_keys[EditorControls::Buttons::ScrollDown] = WPAD_STICK_LD;\n        this->m_editor_keys[EditorControls::Buttons::ScrollLeft] = WPAD_STICK_LL;\n        this->m_editor_keys[EditorControls::Buttons::ScrollRight] = WPAD_STICK_LR;\n        this->m_editor_keys[EditorControls::Buttons::FastScroll] = WPAD_NUNCHUK_BUTTON_Z;\n        this->m_editor_keys[EditorControls::Buttons::ModeErase] = WPAD_NUNCHUK_BUTTON_C;\n        this->m_editor_keys[EditorControls::Buttons::PrevSection] = WPAD_BUTTON_MINUS;\n        this->m_editor_keys[EditorControls::Buttons::NextSection] = WPAD_BUTTON_PLUS;\n        this->m_editor_keys[EditorControls::Buttons::SwitchScreens] = WPAD_BUTTON_1;\n        this->m_editor_keys[EditorControls::Buttons::TestPlay] = WPAD_BUTTON_2;\n\n        this->m_cursor_keys2[CursorControls::Buttons::Primary] = WPAD_BUTTON_A;\n        this->m_cursor_keys2[CursorControls::Buttons::Secondary] = WPAD_BUTTON_B;\n    }\n    else if(m_expansion == WPAD_EXP_CLASSIC)\n    {\n        this->m_keys[PlayerControls::Buttons::Up] = WPAD_STICK_LU;\n        this->m_keys[PlayerControls::Buttons::Down] = WPAD_STICK_LD;\n        this->m_keys[PlayerControls::Buttons::Left] = WPAD_STICK_LL;\n        this->m_keys[PlayerControls::Buttons::Right] = WPAD_STICK_LR;\n        this->m_keys[PlayerControls::Buttons::Jump] = WPAD_CLASSIC_BUTTON_B;\n        this->m_keys[PlayerControls::Buttons::AltJump] = WPAD_CLASSIC_BUTTON_A;\n        this->m_keys[PlayerControls::Buttons::Run] = WPAD_CLASSIC_BUTTON_Y;\n        this->m_keys[PlayerControls::Buttons::AltRun] = WPAD_CLASSIC_BUTTON_X;\n        this->m_keys[PlayerControls::Buttons::Drop] = WPAD_CLASSIC_BUTTON_MINUS;\n        this->m_keys[PlayerControls::Buttons::Start] = WPAD_CLASSIC_BUTTON_PLUS;\n\n        this->m_keys2[PlayerControls::Buttons::Up] = WPAD_CLASSIC_BUTTON_UP;\n        this->m_keys2[PlayerControls::Buttons::Down] = WPAD_CLASSIC_BUTTON_DOWN;\n        this->m_keys2[PlayerControls::Buttons::Left] = WPAD_CLASSIC_BUTTON_LEFT;\n        this->m_keys2[PlayerControls::Buttons::Right] = WPAD_CLASSIC_BUTTON_RIGHT;\n\n        this->m_editor_keys[EditorControls::Buttons::ScrollUp] = WPAD_STICK_LU;\n        this->m_editor_keys[EditorControls::Buttons::ScrollDown] = WPAD_STICK_LD;\n        this->m_editor_keys[EditorControls::Buttons::ScrollLeft] = WPAD_STICK_LL;\n        this->m_editor_keys[EditorControls::Buttons::ScrollRight] = WPAD_STICK_LR;\n        this->m_editor_keys[EditorControls::Buttons::FastScroll] = WPAD_CLASSIC_BUTTON_ZL;\n        this->m_editor_keys[EditorControls::Buttons::ModeErase] = WPAD_CLASSIC_BUTTON_ZR;\n        this->m_editor_keys[EditorControls::Buttons::PrevSection] = WPAD_CLASSIC_BUTTON_B;\n        this->m_editor_keys[EditorControls::Buttons::NextSection] = WPAD_CLASSIC_BUTTON_A;\n        this->m_editor_keys[EditorControls::Buttons::SwitchScreens] = WPAD_CLASSIC_BUTTON_MINUS;\n        this->m_editor_keys[EditorControls::Buttons::TestPlay] = WPAD_CLASSIC_BUTTON_PLUS;\n\n        this->m_cursor_keys2[CursorControls::Buttons::CursorUp] = WPAD_STICK_RU;\n        this->m_cursor_keys2[CursorControls::Buttons::CursorDown] = WPAD_STICK_RD;\n        this->m_cursor_keys2[CursorControls::Buttons::CursorLeft] = WPAD_STICK_RL;\n        this->m_cursor_keys2[CursorControls::Buttons::CursorRight] = WPAD_STICK_RR;\n        this->m_cursor_keys2[CursorControls::Buttons::Primary] = WPAD_CLASSIC_BUTTON_FULL_R;\n        this->m_cursor_keys2[CursorControls::Buttons::Secondary] = WPAD_CLASSIC_BUTTON_FULL_L;\n\n        this->m_altMenuControls = true;\n    }\n    else\n    {\n        this->m_keys[PlayerControls::Buttons::Up] = WPAD_BUTTON_UP;\n        this->m_keys[PlayerControls::Buttons::Down] = WPAD_BUTTON_DOWN;\n        this->m_keys[PlayerControls::Buttons::Left] = WPAD_BUTTON_LEFT;\n        this->m_keys[PlayerControls::Buttons::Right] = WPAD_BUTTON_RIGHT;\n        this->m_keys[PlayerControls::Buttons::Jump] = WPAD_BUTTON_2;\n        this->m_keys[PlayerControls::Buttons::AltJump] = WPAD_BUTTON_A;\n        this->m_keys[PlayerControls::Buttons::Run] = WPAD_BUTTON_1;\n        this->m_keys[PlayerControls::Buttons::AltRun] = WPAD_BUTTON_B;\n        this->m_keys[PlayerControls::Buttons::Drop] = WPAD_BUTTON_MINUS;\n        this->m_keys[PlayerControls::Buttons::Start] = WPAD_BUTTON_PLUS;\n\n        this->m_keys2[PlayerControls::Buttons::AltJump] = WPAD_SHAKE;\n\n        this->m_editor_keys[EditorControls::Buttons::ScrollUp] = WPAD_BUTTON_UP;\n        this->m_editor_keys[EditorControls::Buttons::ScrollDown] = WPAD_BUTTON_DOWN;\n        this->m_editor_keys[EditorControls::Buttons::ScrollLeft] = WPAD_BUTTON_LEFT;\n        this->m_editor_keys[EditorControls::Buttons::ScrollRight] = WPAD_BUTTON_RIGHT;\n        this->m_editor_keys[EditorControls::Buttons::FastScroll] = WPAD_BUTTON_1;\n        this->m_editor_keys[EditorControls::Buttons::PrevSection] = WPAD_BUTTON_MINUS;\n        this->m_editor_keys[EditorControls::Buttons::NextSection] = WPAD_BUTTON_PLUS;\n        this->m_editor_keys[EditorControls::Buttons::SwitchScreens] = WPAD_BUTTON_HOME;\n        this->m_editor_keys[EditorControls::Buttons::TestPlay] = WPAD_BUTTON_2;\n\n        this->m_cursor_keys2[CursorControls::Buttons::Primary] = WPAD_BUTTON_A;\n        this->m_cursor_keys2[CursorControls::Buttons::Secondary] = WPAD_BUTTON_B;\n    }\n}\n\nbool InputMethodProfile_Wii::PollPrimaryButton(ControlsClass c, size_t i)\n{\n    if(c == ControlsClass::Cursor)\n        return true;\n\n    // note: m_canPoll is initialized to false\n    uint32_t key = null_but;\n\n    for(int chn = 0; chn < 4; chn++)\n    {\n        WPADData* data = WPAD_Data(chn);\n\n        if(!data)\n            continue;\n\n        if(data->err)\n            continue;\n\n        if(data->exp.type != m_expansion)\n            continue;\n\n        for(uint32_t but : wiimote_buttons)\n        {\n            if(s_get_button(data, but, m_expansion))\n            {\n                key = but;\n                break;\n            }\n        }\n\n        if(data->exp.type == WPAD_EXP_NUNCHUK)\n        {\n            for(uint32_t but : nunchuck_buttons)\n            {\n                if(s_get_button(data, but, data->exp.type))\n                {\n                    key = but;\n                    break;\n                }\n            }\n        }\n\n        if(data->exp.type == WPAD_EXP_CLASSIC)\n        {\n            for(uint32_t but : classic_buttons)\n            {\n                if(s_get_button(data, but, data->exp.type))\n                {\n                    key = but;\n                    break;\n                }\n            }\n        }\n\n        if(key != null_but)\n            break;\n    }\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == null_but)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    uint32_t* keys;\n    uint32_t* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling Wii primary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n    // if a button's secondary key (including the current one) is the new key, delete it.\n    // if a button's primary key (excluding the current one) is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, replace it with the button the player is replacing.\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(keys2[j] == key)\n            keys2[j] = null_but;\n        else if(i != j && keys[j] == key)\n        {\n            if(keys2[j] != null_but)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_but;\n            }\n            else\n                keys[j] = keys[i];\n        }\n    }\n\n    keys[i] = key;\n    return true;\n}\n\nbool InputMethodProfile_Wii::PollSecondaryButton(ControlsClass c, size_t i)\n{\n    // note: m_canPoll is initialized to false\n    uint32_t key = null_but;\n\n    for(int chn = 0; chn < 4; chn++)\n    {\n        WPADData* data = WPAD_Data(chn);\n\n        if(!data)\n            continue;\n\n        if(data->err)\n            continue;\n\n        if(data->exp.type != m_expansion)\n            continue;\n\n        for(uint32_t but : wiimote_buttons)\n        {\n            if(s_get_button(data, but, m_expansion))\n            {\n                key = but;\n                break;\n            }\n        }\n\n        if(data->exp.type == WPAD_EXP_NUNCHUK)\n        {\n            for(uint32_t but : nunchuck_buttons)\n            {\n                if(s_get_button(data, but, data->exp.type))\n                {\n                    key = but;\n                    break;\n                }\n            }\n        }\n\n        if(data->exp.type == WPAD_EXP_CLASSIC)\n        {\n            for(uint32_t but : classic_buttons)\n            {\n                if(s_get_button(data, but, data->exp.type))\n                {\n                    key = but;\n                    break;\n                }\n            }\n        }\n\n        if(key != null_but)\n            break;\n    }\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == null_but)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    uint32_t* keys;\n    uint32_t* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = nullptr;\n        keys2 = this->m_cursor_keys2;\n        key_max = CursorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling Wii secondary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n\n    // if the current button's primary key is the new key,\n    //     delete its secondary key instead of setting it.\n    if(keys && keys[i] == key)\n    {\n        keys2[i] = null_but;\n        return true;\n    }\n\n    // if another button's secondary key is the new key, delete it.\n    // if another button's primary key is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, if this button's secondary key is defined, overwrite the other's with this.\n    //     if all else fails, overwrite the other button's with this button's PRIMARY key and assign\n    //         this button's PRIMARY key instead\n\n    bool can_do_secondary = true;\n\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(i != j && keys2[j] == key)\n        {\n            keys2[j] = null_but;\n        }\n        else if(keys && i != j && keys[j] == key)\n        {\n            if(keys2[j] != null_but)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_but;\n            }\n            else if(keys2[i] != null_but)\n            {\n                keys[j] = keys2[i];\n            }\n            else\n            {\n                keys[j] = keys[i];\n                can_do_secondary = false;\n            }\n        }\n    }\n\n    if(can_do_secondary)\n        keys2[i] = key;\n    else if(keys)\n        keys[i] = key;\n\n    return true;\n}\n\nbool InputMethodProfile_Wii::DeletePrimaryButton(ControlsClass c, size_t i)\n{\n    // resolve the particular primary and secondary key arrays\n    uint32_t* keys;\n    uint32_t* keys2;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        return false;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n    }\n    else\n    {\n        D_pLogWarning(\"Attempted to delete Wii primary button with disallowed controls class %d\\n\", (int)c);\n        return false;\n    }\n\n    if(keys2[i] != null_but)\n    {\n        keys[i] = keys2[i];\n        keys2[i] = null_but;\n        return true;\n    }\n\n    if(c == ControlsClass::Player)\n        return false;\n\n    if(keys[i] != null_but)\n    {\n        keys[i] = null_but;\n        return true;\n    }\n\n    return false;\n}\n\nbool InputMethodProfile_Wii::DeleteSecondaryButton(ControlsClass c, size_t i)\n{\n    uint32_t* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return false;\n\n    if(keys2[i] != null_but)\n    {\n        keys2[i] = null_but;\n        return true;\n    }\n\n    return false;\n}\n\nconst char* InputMethodProfile_Wii::NamePrimaryButton(ControlsClass c, size_t i)\n{\n    uint32_t* keys;\n\n    if(c == ControlsClass::Player)\n        keys = this->m_keys;\n    else if(c == ControlsClass::Cursor)\n    {\n        if(i < CursorControls::Buttons::Primary)\n            return g_controlsStrings.wiiCaseIR.c_str();\n        else\n            return g_mainMenu.caseNone.c_str();\n    }\n    else if(c == ControlsClass::Editor)\n        keys = this->m_editor_keys;\n    else if(c == ControlsClass::Hotkey)\n        keys = this->m_hotkeys;\n    else\n        return \"\";\n\n    const char* ret = s_get_name(keys[i], m_expansion);\n\n    if(!ret)\n        return g_mainMenu.caseNone.c_str();\n\n    return ret;\n}\n\nconst char* InputMethodProfile_Wii::NameSecondaryButton(ControlsClass c, size_t i)\n{\n    uint32_t* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return \"\";\n\n    const char* ret = s_get_name(keys2[i], m_expansion);\n\n    if(!ret)\n        return \"\";\n\n    return ret;\n}\n\nvoid InputMethodProfile_Wii::SaveConfig(IniProcessing* ctl)\n{\n    ctl->setValue(\"expansion\", (int)this->m_expansion);\n\n    char name2[20];\n\n    for(int a = 0; a < 4; a++)\n    {\n        uint32_t* keys;\n        uint32_t* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            const char* name;\n\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            if(keys)\n                ctl->setValue(name, keys[i]);\n\n            for(size_t c = 0; c < 20; c++)\n            {\n                if(c + 2 == 20 || name[c] == '\\0')\n                {\n                    name2[c] = '2';\n                    name2[c + 1] = '\\0';\n                    break;\n                }\n\n                name2[c] = name[c];\n            }\n\n            ctl->setValue(name2, keys2[i]);\n        }\n    }\n}\n\nvoid InputMethodProfile_Wii::LoadConfig(IniProcessing* ctl)\n{\n    int expansion;\n    ctl->read(\"expansion\", expansion, WPAD_EXP_NONE);\n\n    this->m_expansion = expansion;\n\n    char name2[20];\n\n    for(int a = 0; a < 4; a++)\n    {\n        uint32_t* keys;\n        uint32_t* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            const char* name;\n\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            if(keys)\n                ctl->read(name, keys[i], keys[i]);\n\n            for(size_t c = 0; c < 20; c++)\n            {\n                if(c + 2 == 20 || name[c] == '\\0')\n                {\n                    name2[c] = '2';\n                    name2[c + 1] = '\\0';\n                    break;\n                }\n\n                name2[c] = name[c];\n            }\n\n            ctl->read(name2, keys2[i], keys2[i]);\n        }\n    }\n}\n\n/*===============================================*\\\n|| implementation for InputMethodType_Wii        ||\n\\*===============================================*/\n\nInputMethodProfile* InputMethodType_Wii::AllocateProfile() noexcept\n{\n    return (InputMethodProfile*) new(std::nothrow) InputMethodProfile_Wii(WPAD_EXP_NONE);\n}\n\nInputMethodType_Wii::InputMethodType_Wii()\n{\n    this->Name = \"Wii\";\n    WPAD_Init();\n    for(int chn = 0; chn < 4; chn++)\n    {\n        WPAD_SetDataFormat(chn, WPAD_FMT_BTNS_ACC_IR);\n        WPAD_SetVRes(chn, XRender::g_rmode_w, XRender::g_rmode_h);\n    }\n}\n\nInputMethodType_Wii::~InputMethodType_Wii()\n{\n    WPAD_Shutdown();\n}\n\nbool InputMethodType_Wii::TestProfileType(InputMethodProfile* profile)\n{\n    return (bool)dynamic_cast<InputMethodProfile_Wii*>(profile);\n}\n\nbool InputMethodType_Wii::RumbleSupported()\n{\n    return true;\n}\n\nbool InputMethodType_Wii::PowerStatusSupported()\n{\n    return true;\n}\n\nvoid InputMethodType_Wii::UpdateControlsPre()\n{\n    WPAD_ScanPads();\n}\n\nvoid InputMethodType_Wii::UpdateControlsPost()\n{\n    int active_chn = -1;\n    int scr_x = -10;\n    int scr_y = -10;\n\n    for(int i = 0; i < 4; i++)\n    {\n        WPADData* data = WPAD_Data(i);\n\n        if(!data)\n            continue;\n\n        if(data->err || !data->data_present)\n            m_chnConnected[i] = 0;\n        else if(m_chnConnected[i] < 8)\n        {\n            m_chnConnected[i]++;\n\n            // fixes bug where Wiimote could be stuck in Rumble state if it had previously died while Rumble was active\n            WPAD_Rumble(i, 0);\n        }\n\n        if(data->ir.valid)\n        {\n            int phys_x = data->ir.x;\n            int phys_y = data->ir.y;\n\n            XRender::mapToScreen(phys_x, phys_y, &scr_x, &scr_y);\n            active_chn = i;\n\n            break;\n        }\n    }\n\n    if(active_chn == -1 && m_irActiveChn != -1)\n    {\n        SharedCursor.GoOffscreen();\n        return;\n    }\n\n    if(scr_x - m_irLastX <= -1 || scr_x - m_irLastX >= 1 ||\n       scr_y - m_irLastY <= -1 || scr_y - m_irLastY >= 1 ||\n       active_chn != m_irActiveChn)\n    {\n        m_irLastX = scr_x;\n        m_irLastY = scr_y;\n        m_irActiveChn = active_chn;\n        SharedCursor.Move = true;\n        SharedCursor.X = scr_x;\n        SharedCursor.Y = scr_y;\n    }\n\n    // editor edge scrolling logic, held back for now.\n    // I'll try to rework the IR logic above, manually converting to screen coordinates using smooth_valid and sx/sy.\n    // See ir_correct_for_bounds and ir_convert_to_vres at https://github.com/devkitPro/libogc/blob/master/wiiuse/ir.c\n    // Just need to make very slightly less stringent valid conditions\n    // Try to allow up to 128px offset from screen in any direction.\n    if(LevelEditor && false)\n    {\n        if(scr_x < 64)\n            ::EditorControls.ScrollLeft += SDL_min((64 - scr_x) / 2, 32);\n        else if(scr_x > XRender::TargetW - 64)\n            ::EditorControls.ScrollRight += SDL_min((scr_x - (XRender::TargetW - 64)) / 2, 32);\n\n        if(scr_y < 0)\n            ::EditorControls.ScrollUp += SDL_min((0 - scr_y) / 2, 32);\n        else if(scr_y > XRender::TargetH - 64)\n            ::EditorControls.ScrollDown += SDL_min((scr_y - (XRender::TargetH - 64)) / 2, 32);\n    }\n}\n\nInputMethod* InputMethodType_Wii::Poll(const std::vector<InputMethod*>& active_methods) noexcept\n{\n    std::array<bool, 4> in_use = {false};\n\n    for(InputMethod* method : active_methods)\n    {\n        InputMethod_Wii* m = dynamic_cast<InputMethod_Wii*>(method);\n        if(!m)\n            continue;\n\n        if(m->m_chn >= 0 || m->m_chn < 4)\n            in_use[m->m_chn] = true;\n    }\n\n    int chn = 0;\n    uint8_t expansion = 0;\n\n    for(chn = 0; chn < 4; chn++)\n    {\n        if(in_use[chn])\n            continue;\n\n        WPADData* data = WPAD_Data(chn);\n\n        if(!data)\n            continue;\n\n        if(data->err)\n            continue;\n\n        if(!data->data_present)\n            continue;\n\n        // newly connected wiimote\n        bool newly_connected = (m_chnConnected[chn] < 8);\n\n        uint32_t key = null_but;\n\n        for(uint32_t but : wiimote_buttons)\n        {\n            if(s_get_button(data, but, data->exp.type))\n            {\n                key = but;\n                break;\n            }\n        }\n\n        if(data->exp.type == WPAD_EXP_NUNCHUK)\n        {\n            for(uint32_t but : nunchuck_buttons)\n            {\n                if(s_get_button(data, but, data->exp.type))\n                {\n                    key = but;\n                    break;\n                }\n            }\n        }\n\n        if(data->exp.type == WPAD_EXP_CLASSIC)\n        {\n            for(uint32_t but : classic_buttons)\n            {\n                if(s_get_button(data, but, data->exp.type))\n                {\n                    key = but;\n                    break;\n                }\n            }\n        }\n\n        if(key != null_but || newly_connected)\n        {\n            expansion = data->exp.type;\n            break;\n        }\n    }\n\n    // if didn't find any wiimote allow poll in future but return false\n    if(chn == 4)\n    {\n        this->m_canPoll = true;\n        return nullptr;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return nullptr;\n\n    // we're going to create a new wiimote!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    InputMethod_Wii* method = new(std::nothrow) InputMethod_Wii(chn);\n\n    if(!method)\n        return nullptr;\n\n    if(expansion == WPAD_EXP_NUNCHUK)\n        method->Name = g_controlsStrings.wiiTypeNunchuck;\n    else if(expansion == WPAD_EXP_CLASSIC)\n        method->Name = g_controlsStrings.wiiTypeClassic;\n    else\n        method->Name = g_controlsStrings.wiiTypeWiimote;\n\n    method->Name += \" \";\n    method->Name += std::to_string(chn + 1);\n    method->Type = this;\n\n    // now, cleverly find a profile!\n\n    // 1. cleverly look for profile by GUID and player...\n    uint16_t new_index = expansion * 256 + 1;\n\n    for(const InputMethod* m : active_methods)\n    {\n        if(!m)\n            break;\n\n        new_index += 1;\n    }\n\n    std::unordered_map<uint16_t, InputMethodProfile*>::iterator found\n        = this->m_lastProfileByPlayerAndExp.find(new_index);\n\n    // ...then by just expansion index.\n    if(found == this->m_lastProfileByPlayerAndExp.end())\n        found = this->m_lastProfileByPlayerAndExp.find(expansion * 256);\n\n    if(found != this->m_lastProfileByPlayerAndExp.end())\n        method->Profile = found->second;\n\n    // 2. find first profile appropriate to the expansion of the controller\n    if(!method->Profile)\n    {\n        for(InputMethodProfile* p_ : this->m_profiles)\n        {\n            InputMethodProfile_Wii* p = dynamic_cast<InputMethodProfile_Wii*>(p_);\n\n            if(!p)\n                continue;\n\n            if(p->m_expansion == expansion)\n            {\n                method->Profile = p_;\n                break;\n            }\n        }\n\n        // 3. make appropriate new profile (note that allocs could fail, that will be cleaned up later)\n        if(!method->Profile)\n        {\n            method->Profile = new(std::nothrow) InputMethodProfile_Wii(expansion);\n            if(method->Profile)\n            {\n                method->Profile->Name = method->Name;\n                method->Profile->Type = this;\n                this->m_profiles.push_back(method->Profile);\n            }\n        }\n    }\n\n    if(method->Profile)\n    {\n        this->m_lastProfileByPlayerAndExp[new_index] = method->Profile;\n        this->m_lastProfileByPlayerAndExp[expansion * 256] = method->Profile;\n    }\n\n    return (InputMethod*)method;\n}\n\nInputMethodProfile* InputMethodType_Wii::AddNunchuckProfile()\n{\n    InputMethodProfile* p_ = this->AddProfile();\n    auto* p = dynamic_cast<InputMethodProfile_Wii*>(p_);\n\n    if(!p)\n        return nullptr;\n\n    p->InitAs(WPAD_EXP_NUNCHUK);\n    p->Name = g_controlsStrings.wiiTypeNunchuck + \" \" + std::to_string(this->m_profiles.size());\n\n    return p_;\n}\n\nInputMethodProfile* InputMethodType_Wii::AddClassicProfile()\n{\n    InputMethodProfile* p_ = this->AddProfile();\n    auto* p = dynamic_cast<InputMethodProfile_Wii*>(p_);\n\n    if(!p)\n        return nullptr;\n\n    p->InitAs(WPAD_EXP_CLASSIC);\n    p->Name = g_controlsStrings.wiiTypeClassic + \" \" + std::to_string(this->m_profiles.size());\n\n    return p_;\n}\n\n/*-----------------------*\\\n|| OPTIONAL METHODS      ||\n\\*-----------------------*/\nbool InputMethodType_Wii::ConsumeEvent(const SDL_Event* ev)\n{\n    UNUSED(ev);\n    return false;\n}\n\n// optional function allowing developer to associate device information with profile, etc\nbool InputMethodType_Wii::SetProfile_Custom(InputMethod* method, int player_no, InputMethodProfile* profile,\n        const std::vector<InputMethod*>& active_methods)\n{\n    UNUSED(active_methods);\n\n    if(!method || !profile || player_no < 0 || player_no >= maxLocalPlayers)\n        return false;\n\n    InputMethod_Wii* m = dynamic_cast<InputMethod_Wii*>(method);\n    InputMethodProfile_Wii* p = dynamic_cast<InputMethodProfile_Wii*>(profile);\n\n    if(!m || !p || m->m_chn < 0 || m->m_chn >= 4)\n        return false;\n\n    WPADData* data = WPAD_Data(m->m_chn);\n\n    if(!data || data->err || data->exp.type != p->m_expansion)\n        return false;\n\n    this->m_lastProfileByPlayerAndExp[p->m_expansion * 256 + player_no + 1] = profile;\n    this->m_lastProfileByPlayerAndExp[p->m_expansion * 256] = profile;\n\n    m_canPoll = false;\n    return true;\n}\n\n// unregisters any references to the profile before final deallocation\n// returns false to prevent deletion if this is impossible\nbool InputMethodType_Wii::DeleteProfile_Custom(InputMethodProfile* profile, const std::vector<InputMethod*>& active_methods)\n{\n    UNUSED(active_methods);\n\n    for(auto it = m_lastProfileByPlayerAndExp.begin(); it != m_lastProfileByPlayerAndExp.end();)\n    {\n        if(it->second == profile)\n        {\n            pLogDebug(\"Unsetting default profile for index '%x' on deletion of '%s'.\", (unsigned int)it->first, profile->Name.c_str());\n            it = m_lastProfileByPlayerAndExp.erase(it);\n        }\n        else\n            ++it;\n    }\n\n    return true;\n}\n\n// How many per-type special options are there?\nsize_t InputMethodType_Wii::GetOptionCount()\n{\n    return 2;\n}\n\n// Methods to manage per-profile options\n// It is guaranteed that none of these will be called if\n// GetOptionCount() returns 0.\n// get a char* describing the option\nconst char* InputMethodType_Wii::GetOptionName(size_t i)\n{\n    if(i == 0)\n        return g_controlsStrings.wiiPhraseNewNunchuck.c_str();\n    else if(i == 1)\n        return g_controlsStrings.wiiPhraseNewClassic.c_str();\n\n    return nullptr;\n}\n\n// get a char* describing the current option value\n// must be allocated in static or instance memory\n// WILL NOT be freed\nconst char* InputMethodType_Wii::GetOptionValue(size_t i)\n{\n    (void)i;\n    return nullptr;\n}\n\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodType_Wii::OptionChange(size_t i)\n{\n    if(i == 0 && this->AddNunchuckProfile())\n        return true;\n    else if(i == 1 && this->AddClassicProfile())\n        return true;\n\n    return false;\n}\n\nvoid InputMethodType_Wii::SaveConfig_Custom(IniProcessing* ctl)\n{\n    std::string name = \"last-profile-\";\n    int uuid_begin = name.size();\n\n    // set all default controller profiles\n    for(auto it = m_lastProfileByPlayerAndExp.begin(); it != m_lastProfileByPlayerAndExp.end(); ++it)\n    {\n        std::vector<InputMethodProfile*>::iterator loc = std::find(this->m_profiles.begin(), this->m_profiles.end(), it->second);\n        size_t index = loc - this->m_profiles.begin();\n\n        ctl->setValue(name.replace(uuid_begin, std::string::npos, std::to_string(it->first)).c_str(), index);\n    }\n}\n\nvoid InputMethodType_Wii::LoadConfig_Custom(IniProcessing* ctl)\n{\n    // load all default controller profiles\n    std::vector<std::string> keys = ctl->allKeys();\n    std::string keyNeed = \"last-profile-\";\n\n    for(std::string& k : keys)\n    {\n        std::string::size_type r = k.find(keyNeed);\n\n        if(r != std::string::npos && r == 0)\n        {\n            std::string type_index_str = k.substr(13); // length of \"last-profile-\"\n            uint32_t type_index = strtoul(type_index_str.c_str(), nullptr, 10);\n\n            int prof_index;\n            ctl->read(k.c_str(), prof_index, -1);\n\n            if(prof_index >= 0 && prof_index < (int)this->m_profiles.size() && this->m_profiles[prof_index])\n            {\n                this->m_lastProfileByPlayerAndExp[type_index] = this->m_profiles[prof_index];\n                pLogDebug(\"Set default profile for '%u' to '%s'.\", type_index, this->m_profiles[prof_index]->Name.c_str());\n            }\n        }\n    }\n}\n\n} // namespace Controls\n"
  },
  {
    "path": "src/control/input_wii.h",
    "content": "#ifndef INPUT_WII_H\n#define INPUT_WII_H\n\n#include <unordered_map>\n#include <utility>\n#include <array>\n\n#include \"../controls.h\"\n\nnamespace Controls\n{\n\nconstexpr uint32_t null_but = -1;\n\nclass InputMethod_Wii : public InputMethod\n{\npublic:\n    int m_chn = 0;\n    int m_rumble_ticks = 0;\n    uint8_t m_battery_status = 0;\n    double m_shake_accum = 0.0;\n\n    using InputMethod::Type;\n    using InputMethod::Profile;\n\n    InputMethod_Wii(int chn);\n    ~InputMethod_Wii();\n\n    // Update functions that set player controls (and editor controls)\n    // based on current device input. Return false if device lost.\n    bool Update(int player, Controls_t &c, CursorControls_t &m, EditorControls_t &e, HotkeysPressed_t &h);\n\n    void Rumble(int ms, float strength);\n\n    StatusInfo GetStatus();\n};\n\nclass InputMethodProfile_Wii : public InputMethodProfile\n{\nprivate:\n    std::unordered_map<uint16_t, InputMethodProfile *> m_lastProfileByPlayerAndExp;\n    bool m_canPoll = false;\n\npublic:\n    using InputMethodProfile::Name;\n    using InputMethodProfile::Type;\n\n    uint8_t m_expansion = 0;\n\n    uint32_t m_keys[PlayerControls::n_buttons] = {null_but};\n    uint32_t m_keys2[PlayerControls::n_buttons] = {null_but};\n\n    uint32_t m_editor_keys[EditorControls::n_buttons] = {null_but};\n    uint32_t m_editor_keys2[EditorControls::n_buttons] = {null_but};\n\n    uint32_t m_cursor_keys2[CursorControls::n_buttons] = {null_but};\n\n    uint32_t m_hotkeys[Hotkeys::n_buttons] = {null_but};\n    uint32_t m_hotkeys2[Hotkeys::n_buttons] = {null_but};\n\n    InputMethodProfile_Wii(uint8_t expansion);\n\n    void InitAs(uint8_t expansion);\n\n    // Polls a new (secondary) device button for the i'th player button\n    // Returns true on success and false if no button pressed\n    // Never allows two player buttons to bind to the same device button\n    bool PollPrimaryButton(ControlsClass c, size_t i);\n    bool PollSecondaryButton(ControlsClass c, size_t i);\n\n    // Deletes a primary button for the i'th button of class c (only called for non-Player buttons)\n    bool DeletePrimaryButton(ControlsClass c, size_t i);\n\n    // Deletes a secondary device button for the i'th button of class c\n    bool DeleteSecondaryButton(ControlsClass c, size_t i);\n\n    // Gets strings for the device buttons currently used for the i'th button of class c\n    const char *NamePrimaryButton(ControlsClass c, size_t i);\n    const char *NameSecondaryButton(ControlsClass c, size_t i);\n\n    // one can assume that the IniProcessing* is already in the correct group\n    void SaveConfig(IniProcessing *ctl);\n    void LoadConfig(IniProcessing *ctl);\n};\n\nclass InputMethodType_Wii : public InputMethodType\n{\nprivate:\n    std::unordered_map<uint16_t, InputMethodProfile *> m_lastProfileByPlayerAndExp;\n\n    InputMethodProfile *AllocateProfile() noexcept;\n\npublic:\n    bool m_canPoll = false;\n    int m_irActiveChn = -1;\n    int m_irLastX = -10;\n    int m_irLastY = -10;\n\n    // how many frames has the wiimote been connected?\n    std::array<uint8_t, 4> m_chnConnected{0};\n\n    using InputMethodType::Name;\n    using InputMethodType::m_profiles;\n\n    InputMethodType_Wii();\n    ~InputMethodType_Wii();\n\n    bool TestProfileType(InputMethodProfile *profile);\n    bool RumbleSupported();\n    bool PowerStatusSupported() override;\n\n    void UpdateControlsPre();\n    void UpdateControlsPost();\n\n    // null if no input method is ready\n    // allocates the new InputMethod on the heap\n    InputMethod *Poll(const std::vector<InputMethod *> &active_methods) noexcept;\n\n    /*-----------------------*\\\n    || CUSTOM METHODS        ||\n    \\*-----------------------*/\n    InputMethodProfile *AddNunchuckProfile();\n    InputMethodProfile *AddClassicProfile();\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\nprotected:\n    // optional function allowing developer to associate device information with profile, etc\n    // if developer wants to forbid assignment, return false\n    bool SetProfile_Custom(InputMethod *method, int player_no, InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods) override;\n    // unregisters any references to the profile before final deallocation\n    // returns false to prevent deletion if this is impossible\n    bool DeleteProfile_Custom(InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods) override;\n\npublic:\n    bool ConsumeEvent(const SDL_Event *ev);\n\n    // How many per-type special options are there?\n    size_t GetOptionCount();\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount() returns 0.\n    // get a char* describing the option\n    const char *GetOptionName(size_t i);\n    // get a char* describing the current option value\n    // must be allocated in static or instance memory\n    // WILL NOT be freed\n    const char *GetOptionValue(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange(size_t i);\n\nprotected:\n    void SaveConfig_Custom(IniProcessing *ctl);\n    void LoadConfig_Custom(IniProcessing *ctl);\n};\n\n} // namespace Controls\n\n#endif // #ifndef INPUT_WII_H\n"
  },
  {
    "path": "src/control/input_wii_gc.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n\n#include \"globals.h\"\n#include \"game_main.h\"\n\n#include \"core/render.h\"\n\n#include \"controls.h\"\n#include \"control/input_wii_gc.h\"\n\n#include \"control/controls_strings.h\"\n#include \"main/menu_main.h\"\n#include \"game_main.h\"\n\n#include <Logger/logger.h>\n\n#include <ogc/pad.h>\n\n#define PAD_STICK_LL  0x2000\n#define PAD_STICK_LR  0x4000\n#define PAD_STICK_LU  0x6000\n// not technically legal.\n#define PAD_STICK_LD  0x7000\n#define PAD_STICK_RL  0xA000\n#define PAD_STICK_RR  0xC000\n#define PAD_STICK_RU  0xE000\n// not technically legal.\n#define PAD_STICK_RD  0xF000\n\n#define buttonRight Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Right]\n#define buttonLeft  Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Left]\n#define buttonUp    Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Up]\n#define buttonDown  Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Down]\n\nstatic constexpr uint32_t null_key = -1;\n\nstatic constexpr std::array<const uint32_t, 21> gamecube_buttons = {\n    PAD_BUTTON_LEFT,\n    PAD_BUTTON_RIGHT,\n    PAD_BUTTON_DOWN,\n    PAD_BUTTON_UP,\n    PAD_TRIGGER_Z,\n    PAD_TRIGGER_R,\n    PAD_TRIGGER_L,\n    PAD_BUTTON_A,\n    PAD_BUTTON_B,\n    PAD_BUTTON_X,\n    PAD_BUTTON_Y,\n    PAD_BUTTON_MENU,\n    PAD_BUTTON_START,\n    PAD_STICK_LL,\n    PAD_STICK_LR,\n    PAD_STICK_LU,\n    PAD_STICK_LD,\n    PAD_STICK_RL,\n    PAD_STICK_RR,\n    PAD_STICK_RU,\n    PAD_STICK_RD,\n};\n\nstatic const std::array<const std::string*, 21> gamecube_button_names = {\n    &buttonLeft,\n    &buttonRight,\n    &buttonDown,\n    &buttonUp,\n    &g_controlsStrings.wiiButtonZ,\n    &g_controlsStrings.wiiButtonRT,\n    &g_controlsStrings.wiiButtonLT,\n    &g_controlsStrings.wiiButtonA,\n    &g_controlsStrings.wiiButtonB,\n    &g_controlsStrings.wiiButtonX,\n    &g_controlsStrings.wiiButtonY,\n    &g_controlsStrings.wiiButtonHome,\n    &g_controlsStrings.wiiButtonPlus,\n    &buttonLeft,\n    &buttonRight,\n    &buttonUp,\n    &buttonDown,\n    &buttonLeft,\n    &buttonRight,\n    &buttonUp,\n    &buttonDown,\n};\n\nstatic std::string s_buffer;\n\nstatic const char* s_get_name(uint32_t key)\n{\n    if(key == null_key)\n        return g_mainMenu.caseNone.c_str();\n\n    auto it = std::find(gamecube_buttons.begin(), gamecube_buttons.end(), key);\n    if(it == gamecube_buttons.end())\n        return g_controlsStrings.sharedCaseInvalid.c_str();\n\n    int i = it - gamecube_buttons.begin();\n\n    if(key <= PAD_BUTTON_UP)\n        s_buffer = g_controlsStrings.wiiDpad;\n    else if(key >= PAD_STICK_LL && key < PAD_STICK_RL)\n        s_buffer = g_controlsStrings.wiiLStick;\n    else if(key >= PAD_STICK_RL)\n        s_buffer = g_controlsStrings.wiiRStick;\n    else\n        s_buffer.clear();\n\n    if(!s_buffer.empty())\n        s_buffer += ' ';\n\n    s_buffer += *gamecube_button_names[i];\n    return s_buffer.c_str();\n}\n\nnamespace Controls\n{\n\n/*====================================================*\\\n|| implementation for InputMethod_GameCube            ||\n\\*====================================================*/\n\nstatic inline num_t s_s8_to_dbl(int8_t in)\n{\n    if(in <= 32)\n        return 0;\n\n    return SDL_min(num_t(in - 32) / 64, 1.0_n);\n}\n\nstatic inline num_t s_get_thumb_dbl(const PADStatus* data, uint32_t button)\n{\n    switch(button)\n    {\n    case(PAD_STICK_LL):\n        return s_s8_to_dbl(-data->stickX);\n    case(PAD_STICK_LR):\n        return s_s8_to_dbl( data->stickX);\n    case(PAD_STICK_LU):\n        return s_s8_to_dbl( data->stickY);\n    case(PAD_STICK_LD):\n        return s_s8_to_dbl(-data->stickY);\n    case(PAD_STICK_RL):\n        return s_s8_to_dbl(-data->substickX);\n    case(PAD_STICK_RR):\n        return s_s8_to_dbl( data->substickX);\n    case(PAD_STICK_RU):\n        return s_s8_to_dbl( data->substickY);\n    case(PAD_STICK_RD):\n        return s_s8_to_dbl(-data->substickY);\n    default:\n        return 0;\n    }\n}\n\nstatic bool s_get_button(const PADStatus* data, uint32_t button)\n{\n    if(button == null_key)\n        return false;\n\n    if(button < PAD_STICK_LL)\n        return (button & data->button);\n\n    // thumbstick button\n    if(button <= PAD_STICK_RD)\n        return s_get_thumb_dbl(data, button) > 0.25_n;\n\n    return false;\n}\n\nstatic num_t s_get_button_dbl(const PADStatus* data, uint32_t button)\n{\n    if(button >= PAD_STICK_LL && button <= PAD_STICK_RD)\n        return s_get_thumb_dbl(data, button);\n\n    if(s_get_button(data, button))\n        return 0.5_n;\n    else\n        return 0.0_n;\n}\n\n/*====================================================*\\\n|| implementation for InputMethod_GameCube            ||\n\\*====================================================*/\n\nInputMethod_GameCube::~InputMethod_GameCube()\n{\n    InputMethodType_GameCube* t = dynamic_cast<InputMethodType_GameCube*>(this->Type);\n\n    if(!t)\n        return;\n\n    t->m_canPoll = false;\n}\n\n// Update functions that set player controls (and editor controls)\n// based on current device input. Return false if device lost.\nbool InputMethod_GameCube::Update(int player, Controls_t& c, CursorControls_t& m, EditorControls_t& e, HotkeysPressed_t& h)\n{\n    InputMethodProfile_GameCube* p = dynamic_cast<InputMethodProfile_GameCube*>(this->Profile);\n    InputMethodType_GameCube* t = dynamic_cast<InputMethodType_GameCube*>(this->Type);\n\n    if(!p || !t || m_chn < 0 || m_chn >= 4)\n        return false;\n\n    const PADStatus* data = &t->m_status[m_chn];\n    if(!data)\n        return false;\n\n    if(data->err != PAD_ERR_NONE)\n        return false;\n\n    if(m_rumble_ticks)\n    {\n        m_rumble_ticks--;\n        if(!m_rumble_ticks)\n            PAD_ControlMotor(m_chn, PAD_MOTOR_STOP);\n    }\n\n    for(int a = 0; a < 4; a++)\n    {\n        uint32_t* keys;\n        uint32_t* keys2;\n        size_t key_start;\n        size_t key_max;\n        bool activate = false;\n\n        if(a == 0)\n        {\n            keys = p->m_keys;\n            keys2 = p->m_keys2;\n            key_start = 0;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = p->m_cursor_keys;\n            keys2 = p->m_cursor_keys2;\n            key_start = 4;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = p->m_editor_keys;\n            keys2 = p->m_editor_keys2;\n            key_start = 4;\n            key_max = EditorControls::n_buttons;\n\n            if(p->m_simple_editor && LevelEditor && GamePaused == PauseCode::None)\n                continue;\n        }\n        else\n        {\n            keys = p->m_hotkeys;\n            keys2 = p->m_hotkeys2;\n            key_start = 0;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = key_start; i < key_max; i++)\n        {\n            uint32_t key;\n\n            if(keys)\n                key = keys[i];\n            else\n                key = null_key;\n\n            uint32_t key2 = keys2[i];\n\n            bool* b;\n\n            if(a == 0)\n            {\n                b = &PlayerControls::GetButton(c, i);\n                *b = false;\n            }\n            else if(a == 1)\n            {\n                b = &CursorControls::GetButton(m, i);\n            }\n            else if(a == 2)\n            {\n                b = &EditorControls::GetButton(e, i);\n            }\n            else\n            {\n                b = &activate;\n                *b = false;\n            }\n\n            if(s_get_button(data, key))\n                *b = true;\n            else if(s_get_button(data, key2))\n                *b = true;\n\n            if(a == 3 && *b)\n                h[i] = player;\n        }\n    }\n\n    // simple editor controls\n    if(p->m_simple_editor && LevelEditor && GamePaused == PauseCode::None)\n    {\n        CursorControls::GetButton(m, CursorControls::Buttons::Primary) = PlayerControls::GetButton(c, PlayerControls::Buttons::Jump);\n        CursorControls::GetButton(m, CursorControls::Buttons::Secondary) = false;\n        CursorControls::GetButton(m, CursorControls::Buttons::Tertiary) = false;\n\n        EditorControls::GetButton(e, EditorControls::FastScroll) = false;\n        EditorControls::GetButton(e, EditorControls::PrevSection) = false;\n        EditorControls::GetButton(e, EditorControls::NextSection) = false;\n\n        EditorControls::GetButton(e, EditorControls::ModeSelect) |= PlayerControls::GetButton(c, PlayerControls::Buttons::Run);\n        EditorControls::GetButton(e, EditorControls::ModeErase) |= PlayerControls::GetButton(c, PlayerControls::Buttons::AltJump);\n        EditorControls::GetButton(e, EditorControls::SwitchScreens) |= PlayerControls::GetButton(c, PlayerControls::Buttons::Drop);\n        EditorControls::GetButton(e, EditorControls::TestPlay) |= PlayerControls::GetButton(c, PlayerControls::Buttons::Start);\n    }\n\n    // analogue controls\n    num_t cursor[4] = {0, 0, 0, 0};\n    num_t scroll[4] = {0, 0, 0, 0};\n\n    for(int i = 0; i < 4; i++)\n    {\n        if(p->m_simple_editor && LevelEditor && GamePaused == PauseCode::None)\n        {\n            cursor[i] += s_get_button_dbl(data, p->m_keys[i]);\n            cursor[i] += s_get_button_dbl(data, p->m_keys2[i]);\n        }\n        else\n        {\n            scroll[i] += s_get_button_dbl(data, p->m_editor_keys[i]);\n            scroll[i] += s_get_button_dbl(data, p->m_editor_keys2[i]);\n            cursor[i] += s_get_button_dbl(data, p->m_cursor_keys[i]);\n            cursor[i] += s_get_button_dbl(data, p->m_cursor_keys2[i]);\n        }\n    }\n\n    // Scroll control (UDLR)\n    num_t* const scroll_dest[4] = {&e.ScrollUp, &e.ScrollDown, &e.ScrollLeft, &e.ScrollRight};\n    for(int i = 0; i < 4; i++)\n        *scroll_dest[i] += scroll[i] * 10;\n\n    // Cursor control (UDLR)\n    if(cursor[0] || cursor[1] || cursor[2] || cursor[3])\n    {\n        bool edge_scroll = LevelEditor && !MagicHand;\n\n        if(m.X < 0)\n            m.X = XRender::TargetW / 2;\n\n        if(m.Y < 0)\n            m.Y = XRender::TargetH / 2;\n\n        m.X += (cursor[3] - cursor[2]) * 16;\n        m.Y += (cursor[1] - cursor[0]) * 16;\n\n        if(m.X < 0)\n        {\n            if(edge_scroll)\n                e.ScrollLeft += -m.X;\n\n            m.X = 0;\n        }\n        else if(m.X >= XRender::TargetW)\n        {\n            if(edge_scroll)\n                e.ScrollRight += m.X - (XRender::TargetW - 1);\n\n            m.X = XRender::TargetW - 1;\n        }\n\n        if(m.Y < 0)\n        {\n            if(edge_scroll)\n                e.ScrollUp += -m.Y;\n\n            m.Y = 0;\n        }\n        else if(m.Y >= XRender::TargetH)\n        {\n            if(edge_scroll)\n                e.ScrollDown += m.Y - (XRender::TargetH - 1);\n\n            m.Y = XRender::TargetH - 1;\n        }\n\n        m.Move = true;\n    }\n\n    return true;\n}\n\nvoid InputMethod_GameCube::Rumble(int ms, float strength)\n{\n    UNUSED(strength);\n\n    PAD_ControlMotor(m_chn, PAD_MOTOR_RUMBLE);\n\n    m_rumble_ticks = ms / 15 + 1;\n}\n\nStatusInfo InputMethod_GameCube::GetStatus()\n{\n    StatusInfo res;\n    res.power_status = StatusInfo::POWER_WIRED;\n\n    return res;\n}\n\n/*====================================================*\\\n|| implementation for InputMethodProfile_GameCube     ||\n\\*====================================================*/\n\n// the job of this function is to initialize the class in a consistent state\nInputMethodProfile_GameCube::InputMethodProfile_GameCube()\n{\n    this->m_simple_editor = true;\n\n    this->m_keys[PlayerControls::Buttons::Up] = PAD_BUTTON_UP;\n    this->m_keys[PlayerControls::Buttons::Down] = PAD_BUTTON_DOWN;\n    this->m_keys[PlayerControls::Buttons::Left] = PAD_BUTTON_LEFT;\n    this->m_keys[PlayerControls::Buttons::Right] = PAD_BUTTON_RIGHT;\n\n    this->m_keys[PlayerControls::Buttons::Jump] = PAD_BUTTON_A;\n    this->m_keys[PlayerControls::Buttons::AltJump] = PAD_BUTTON_X;\n    this->m_keys[PlayerControls::Buttons::Run] = PAD_BUTTON_B;\n    this->m_keys[PlayerControls::Buttons::AltRun] = PAD_BUTTON_Y;\n\n    this->m_keys[PlayerControls::Buttons::Drop] = PAD_TRIGGER_Z;\n    this->m_keys[PlayerControls::Buttons::Start] = PAD_BUTTON_START;\n\n    // clear all of them, then fill in some of them\n    for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n        this->m_keys2[i] = null_key;\n\n    this->m_keys2[PlayerControls::Buttons::Up] = PAD_STICK_LU;\n    this->m_keys2[PlayerControls::Buttons::Down] = PAD_STICK_LD;\n    this->m_keys2[PlayerControls::Buttons::Left] = PAD_STICK_LL;\n    this->m_keys2[PlayerControls::Buttons::Right] = PAD_STICK_LR;\n\n    this->m_keys2[PlayerControls::Buttons::AltJump] = PAD_TRIGGER_R;\n    this->m_keys2[PlayerControls::Buttons::AltRun] = PAD_TRIGGER_L;\n\n    // clear all of the non-standard controls, then fill in some of them\n    for(size_t i = 0; i < CursorControls::n_buttons; i++)\n    {\n        this->m_cursor_keys[i] = null_key;\n        this->m_cursor_keys2[i] = null_key;\n    }\n\n    for(size_t i = 0; i < EditorControls::n_buttons; i++)\n    {\n        this->m_editor_keys[i] = null_key;\n        this->m_editor_keys2[i] = null_key;\n    }\n\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n    {\n        this->m_hotkeys[i] = null_key;\n        this->m_hotkeys2[i] = null_key;\n    }\n\n    this->m_cursor_keys[CursorControls::Buttons::CursorUp] = PAD_STICK_RU;\n    this->m_cursor_keys[CursorControls::Buttons::CursorDown] = PAD_STICK_RD;\n    this->m_cursor_keys[CursorControls::Buttons::CursorLeft] = PAD_STICK_RL;\n    this->m_cursor_keys[CursorControls::Buttons::CursorRight] = PAD_STICK_RR;\n}\n\nbool InputMethodProfile_GameCube::PollPrimaryButton(ControlsClass c, size_t i)\n{\n    // note: m_canPoll is initialized to false\n    InputMethodType_GameCube* t = dynamic_cast<InputMethodType_GameCube*>(this->Type);\n\n    if(!t)\n        return true;\n\n    if(this->m_simple_editor && c == ControlsClass::Editor)\n        return true;\n\n    uint32_t key = t->PollKeyAll();\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == null_key)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    uint32_t* keys;\n    uint32_t* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = this->m_cursor_keys;\n        keys2 = this->m_cursor_keys2;\n        key_max = CursorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling Joystick primary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n    // if a button's secondary key (including the current one) is the new key, delete it.\n    // if a button's primary key (excluding the current one) is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, replace it with the button the player is replacing.\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(keys2[j] == key)\n        {\n            keys2[j] = null_key;\n        }\n        else if(i != j && keys[j] == key)\n        {\n            if(keys2[j] == null_key)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_key;\n            }\n            else\n            {\n                keys[j] = keys[i];\n            }\n        }\n    }\n\n    keys[i] = key;\n    return true;\n}\n\nbool InputMethodProfile_GameCube::PollSecondaryButton(ControlsClass c, size_t i)\n{\n    // note: m_canPoll is initialized to false\n    InputMethodType_GameCube* t = dynamic_cast<InputMethodType_GameCube*>(this->Type);\n\n    if(!t)\n        return true;\n\n    if(this->m_simple_editor && c == ControlsClass::Editor)\n        return true;\n\n    uint32_t key = t->PollKeyAll();\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == null_key)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    uint32_t* keys;\n    uint32_t* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = this->m_cursor_keys;\n        keys2 = this->m_cursor_keys2;\n        key_max = CursorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling Joystick secondary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n\n    // if the current button's primary key is the new key,\n    //     delete its secondary key instead of setting it.\n    if(keys[i] == key)\n    {\n        keys2[i] = null_key;\n        return true;\n    }\n\n    // if another button's secondary key is the new key, delete it.\n    // if another button's primary key is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, if this button's secondary key is defined, overwrite the other's with this.\n    //     if all else fails, overwrite the other button's with this button's PRIMARY key and assign\n    //         this button's PRIMARY key instead\n\n    bool can_do_secondary = true;\n\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(i != j && keys2[j] == key)\n        {\n            keys2[j] = null_key;\n        }\n        else if(i != j && keys[j] == key)\n        {\n            if(keys2[j] != null_key)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_key;\n            }\n            else if(keys2[i] != null_key)\n            {\n                keys[j] = keys2[i];\n            }\n            else\n            {\n                keys[j] = keys[i];\n                can_do_secondary = false;\n            }\n        }\n    }\n\n    if(can_do_secondary)\n        keys2[i] = key;\n    else\n        keys[i] = key;\n\n    return true;\n}\n\nbool InputMethodProfile_GameCube::DeletePrimaryButton(ControlsClass c, size_t i)\n{\n    // resolve the particular primary and secondary key arrays\n    uint32_t* keys;\n    uint32_t* keys2;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = this->m_cursor_keys;\n        keys2 = this->m_cursor_keys2;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        if(this->m_simple_editor)\n            return false;\n\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n    }\n    else\n    {\n        D_pLogWarning(\"Attempted to delete Joystick primary button with disallowed controls class %d\\n\", (int)c);\n        return false;\n    }\n\n    if(keys2[i] != null_key)\n    {\n        keys[i] = keys2[i];\n        keys2[i] = null_key;\n        return true;\n    }\n\n    if(c == ControlsClass::Player)\n        return false;\n\n    if(keys[i] != null_key)\n    {\n        keys[i] = null_key;\n        return true;\n    }\n\n    return false;\n}\n\nbool InputMethodProfile_GameCube::DeleteSecondaryButton(ControlsClass c, size_t i)\n{\n    // resolve the particular primary and secondary key arrays\n    uint32_t* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n    {\n        if(this->m_simple_editor)\n            return false;\n\n        keys2 = this->m_editor_keys2;\n    }\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return false; // BAD!\n\n    if(keys2[i] != null_key)\n    {\n        keys2[i] = null_key;\n        return true;\n    }\n\n    return false;\n}\n\nconst char* InputMethodProfile_GameCube::NamePrimaryButton(ControlsClass c, size_t i)\n{\n    uint32_t* keys;\n\n    if(c == ControlsClass::Player)\n        keys = this->m_keys;\n    else if(c == ControlsClass::Cursor)\n        keys = this->m_cursor_keys;\n    else if(c == ControlsClass::Editor && this->m_simple_editor)\n    {\n        keys = this->m_keys;\n\n        if(i == EditorControls::Buttons::ModeSelect)\n            i = PlayerControls::Buttons::Run;\n        else if(i == EditorControls::Buttons::ModeErase)\n            i = PlayerControls::Buttons::AltJump;\n        else if(i == EditorControls::Buttons::SwitchScreens)\n            i = PlayerControls::Buttons::Drop;\n        else if(i == EditorControls::Buttons::TestPlay)\n            i = PlayerControls::Buttons::Start;\n        else\n            return g_mainMenu.caseNone.c_str();\n    }\n    else if(c == ControlsClass::Editor)\n        keys = this->m_editor_keys;\n    else if(c == ControlsClass::Hotkey)\n        keys = this->m_hotkeys;\n    else\n        return \"\";\n\n    return s_get_name(keys[i]);\n}\n\nconst char* InputMethodProfile_GameCube::NameSecondaryButton(ControlsClass c, size_t i)\n{\n    uint32_t* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor && this->m_simple_editor)\n    {\n        keys2 = this->m_keys2;\n\n        if(i == EditorControls::Buttons::ModeSelect)\n            i = PlayerControls::Buttons::Run;\n        else if(i == EditorControls::Buttons::ModeErase)\n            i = PlayerControls::Buttons::AltJump;\n        else if(i == EditorControls::Buttons::SwitchScreens)\n            i = PlayerControls::Buttons::Drop;\n        else if(i == EditorControls::Buttons::TestPlay)\n            i = PlayerControls::Buttons::Start;\n        else\n            return \"\";\n    }\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return \"\";\n\n    return s_get_name(keys2[i]);\n}\n\nvoid InputMethodProfile_GameCube::SaveConfig(IniProcessing* ctl)\n{\n    std::string name;\n\n    for(int a = 0; a < 4; a++)\n    {\n        uint32_t* keys;\n        uint32_t* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = this->m_cursor_keys;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            ctl->setValue(name.c_str(), keys[i]);\n\n            name += '2';\n\n            ctl->setValue(name.c_str(), keys2[i]);\n        }\n    }\n\n    ctl->setValue(\"simple-editor\", this->m_simple_editor);\n}\n\nvoid InputMethodProfile_GameCube::LoadConfig(IniProcessing* ctl)\n{\n    std::string name;\n\n    for(int a = 0; a < 4; a++)\n    {\n        uint32_t* keys;\n        uint32_t* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = this->m_cursor_keys;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            ctl->read(name.c_str(), keys[i], keys[i]);\n\n            name += '2';\n\n            ctl->read(name.c_str(), keys2[i], keys2[i]);\n        }\n    }\n\n    ctl->read(\"simple-editor\", this->m_simple_editor, true);\n}\n\nsize_t InputMethodProfile_GameCube::GetOptionCount_Custom()\n{\n    return 1;\n}\n\nconst char *InputMethodProfile_GameCube::GetOptionName_Custom(size_t i)\n{\n    UNUSED(i);\n\n    return g_controlsStrings.joystickSimpleEditor.c_str();\n}\n\nconst char *InputMethodProfile_GameCube::GetOptionValue_Custom(size_t i)\n{\n    UNUSED(i);\n\n    if(this->m_simple_editor)\n        return g_mainMenu.wordOn.c_str();\n    else\n        return g_mainMenu.wordOff.c_str();\n}\n\nbool InputMethodProfile_GameCube::OptionChange_Custom(size_t i)\n{\n    return this->OptionRotateRight_Custom(i);\n}\n\nbool InputMethodProfile_GameCube::OptionRotateLeft_Custom(size_t i)\n{\n    return this->OptionRotateRight_Custom(i);\n}\n\nbool InputMethodProfile_GameCube::OptionRotateRight_Custom(size_t i)\n{\n    UNUSED(i);\n\n    this->m_simple_editor = !this->m_simple_editor;\n\n    return true;\n}\n\n/*====================================================*\\\n|| implementation for InputMethodType_GameCube        ||\n\\*====================================================*/\n\nInputMethodProfile* InputMethodType_GameCube::AllocateProfile() noexcept\n{\n    return (InputMethodProfile*) new(std::nothrow) InputMethodProfile_GameCube;\n}\n\nInputMethodType_GameCube::InputMethodType_GameCube()\n{\n    this->Name = \"GameCube\";\n    PAD_Init();\n}\n\nInputMethodType_GameCube::~InputMethodType_GameCube()\n{\n}\n\nconst std::string& InputMethodType_GameCube::LocalName() const\n{\n    return g_controlsStrings.wiiTypeGamecube;\n}\n\nbool InputMethodType_GameCube::TestProfileType(InputMethodProfile* profile)\n{\n    return (bool)dynamic_cast<InputMethodProfile_GameCube*>(profile);\n}\n\nbool InputMethodType_GameCube::RumbleSupported()\n{\n    return true;\n}\n\nbool InputMethodType_GameCube::PowerStatusSupported()\n{\n    return false;\n}\n\nvoid InputMethodType_GameCube::UpdateControlsPre()\n{\n    PAD_Read(m_status_raw);\n\n    for(int chn = 0; chn < 4; chn++)\n    {\n        if(m_status_raw[chn].err == PAD_ERR_NONE || m_status_raw[chn].err == PAD_ERR_NO_CONTROLLER)\n            m_status[chn] = m_status_raw[chn];\n    }\n\n    for(int chn = 0; chn < 4; chn++)\n    {\n        if(m_status[chn].err != PAD_ERR_NONE)\n            m_chnConnected[chn] = 0;\n        else if(m_chnConnected[chn] < 8)\n            m_chnConnected[chn]++;\n    }\n}\n\nvoid InputMethodType_GameCube::UpdateControlsPost() {}\n\nInputMethod* InputMethodType_GameCube::Poll(const std::vector<InputMethod*>& active_methods) noexcept\n{\n    std::array<bool, 4> in_use = {false};\n\n    for(InputMethod* method : active_methods)\n    {\n        InputMethod_GameCube* m = dynamic_cast<InputMethod_GameCube*>(method);\n        if(!m)\n            continue;\n\n        if(m->m_chn >= 0 || m->m_chn < 4)\n            in_use[m->m_chn] = true;\n    }\n\n    int chn = 0;\n\n    for(chn = 0; chn < 4; chn++)\n    {\n        if(in_use[chn])\n            continue;\n\n        const PADStatus* data = &m_status[chn];\n\n        if(data->err != PAD_ERR_NONE)\n            continue;\n\n        // newly connected controller\n        bool newly_connected = (m_chnConnected[chn] < 8);\n\n        uint32_t key = null_key;\n\n        for(uint32_t but : gamecube_buttons)\n        {\n            if(s_get_button(data, but))\n            {\n                key = but;\n                break;\n            }\n        }\n\n        if(key != null_key || newly_connected)\n            break;\n    }\n\n    // if didn't find any controller allow poll in future but return false\n    if(chn == 4)\n    {\n        this->m_canPoll = true;\n        return nullptr;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return nullptr;\n\n    // we're going to create a new gamecube controller!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    InputMethod_GameCube* method = new(std::nothrow) InputMethod_GameCube(chn);\n\n    if(!method)\n        return nullptr;\n\n    method->Name = g_controlsStrings.wiiTypeGamecube;\n\n    method->Name += \" \";\n    method->Name += std::to_string(chn + 1);\n    method->Type = this;\n\n    return (InputMethod*)method;\n}\n\nuint32_t InputMethodType_GameCube::PollKeyAll() const\n{\n    for(int chn = 0; chn < 4; chn++)\n    {\n        const PADStatus* data = &m_status[chn];\n\n        if(data->err != PAD_ERR_NONE)\n            continue;\n\n        for(uint32_t but : gamecube_buttons)\n        {\n            if(s_get_button(data, but))\n                return but;\n        }\n    }\n\n    return null_key;\n}\n\n} // namespace Controls\n"
  },
  {
    "path": "src/control/input_wii_gc.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef INPUT_WII_GC_H\n#define INPUT_WII_GC_H\n\n#include \"../controls.h\"\n#include \"control/input_wii.h\"\n\n#include <string>\n\nnamespace Controls\n{\n\nclass InputMethod_GameCube : public InputMethod\n{\npublic:\n    using InputMethod::Type;\n    using InputMethod::Profile;\n\n    int m_chn = 0;\n    int m_rumble_ticks = 0;\n\n    InputMethod_GameCube(int chn) : m_chn(chn) {}\n    ~InputMethod_GameCube();\n\n    // Update functions that set player controls (and editor controls)\n    // based on current device input. Return false if device lost.\n    bool Update(int player, Controls_t &c, CursorControls_t &m, EditorControls_t &e, HotkeysPressed_t &h);\n\n    void Rumble(int ms, float strength);\n\n    StatusInfo GetStatus();\n};\n\nclass InputMethodProfile_GameCube : public InputMethodProfile\n{\nprivate:\n    bool m_canPoll = false;\n\npublic:\n    using InputMethodProfile::Name;\n    using InputMethodProfile::Type;\n\n    // current settings\n    bool m_simple_editor = true;\n\n    // primary keys (also controller keys in legacy mode)\n    uint32_t m_keys[PlayerControls::n_buttons];\n    // secondary keys (also joystick keys in legacy mode)\n    uint32_t m_keys2[PlayerControls::n_buttons];\n\n    // cursor keys\n    uint32_t m_cursor_keys[CursorControls::n_buttons];\n    uint32_t m_cursor_keys2[CursorControls::n_buttons];\n\n    // editor keys\n    uint32_t m_editor_keys[EditorControls::n_buttons];\n    uint32_t m_editor_keys2[EditorControls::n_buttons];\n\n    // hotkeys\n    uint32_t m_hotkeys[Hotkeys::n_buttons];\n    uint32_t m_hotkeys2[Hotkeys::n_buttons];\n\n    InputMethodProfile_GameCube();\n\n    // Polls a new (secondary) device button for the i'th player button\n    // Returns true on success and false if no button pressed\n    // Never allows two player buttons to bind to the same device button\n    bool PollPrimaryButton(ControlsClass c, size_t i);\n    bool PollSecondaryButton(ControlsClass c, size_t i);\n\n    // Deletes a primary button for the i'th button of class c (only called for non-Player buttons)\n    bool DeletePrimaryButton(ControlsClass c, size_t i);\n\n    // Deletes a secondary device button for the i'th button of class c\n    bool DeleteSecondaryButton(ControlsClass c, size_t i);\n\n    // Gets strings for the device buttons currently used for the i'th button of class c\n    const char *NamePrimaryButton(ControlsClass c, size_t i);\n    const char *NameSecondaryButton(ControlsClass c, size_t i);\n\n    // one can assume that the IniProcessing* is already in the correct group\n    void SaveConfig(IniProcessing *ctl);\n    void LoadConfig(IniProcessing *ctl);\n\npublic:\n    // How many per-type special options are there?\n    size_t GetOptionCount_Custom();\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount_Custom() returns 0.\n    // get a char* describing the option\n    const char *GetOptionName_Custom(size_t i);\n    // get a char* describing the current option value\n    // must be allocated in static or instance memory\n    // WILL NOT be freed\n    const char *GetOptionValue_Custom(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange_Custom(size_t i);\n    // called when left is pressed\n    bool OptionRotateLeft_Custom(size_t i);\n    // called when right is pressed\n    bool OptionRotateRight_Custom(size_t i);\n};\n\nclass InputMethodType_GameCube : public InputMethodType\n{\nprivate:\n    InputMethodProfile *AllocateProfile() noexcept override;\n\npublic:\n    using InputMethodType::Name;\n    using InputMethodType::m_profiles;\n\n    bool m_canPoll = false;\n\n    PADStatus m_status_raw[4];\n    PADStatus m_status[4];\n\n    // how many frames has the controller been connected?\n    std::array<uint8_t, 4> m_chnConnected{0};\n\n    InputMethodType_GameCube();\n    ~InputMethodType_GameCube();\n\n    const std::string& LocalName() const override;\n\n    bool TestProfileType(InputMethodProfile *profile) override;\n    bool RumbleSupported() override;\n    bool PowerStatusSupported() override;\n\n    void UpdateControlsPre() override;\n    void UpdateControlsPost() override;\n\n    // null if no input method is ready\n    // allocates the new InputMethod on the heap\n    InputMethod *Poll(const std::vector<InputMethod *> &active_methods) noexcept override;\n\n    uint32_t PollKeyAll() const;\n};\n\n} // namespace Controls\n\n#endif // #ifndef INPUT_WII_GC_H\n"
  },
  {
    "path": "src/control/joystick.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n\n#include <SDL2/SDL_version.h>\n#include <SDL2/SDL_joystick.h>\n#include <SDL2/SDL_gamecontroller.h>\n#include <SDL2/SDL_haptic.h>\n#include <SDL2/SDL_events.h>\n\n#ifdef VITA\n#    include <vitasdk.h>\n#endif\n\n#include <Utils/strings.h>\n#include <Logger/logger.h>\n\n#include \"core/render.h\"\n\n#include \"config.h\"\n#include \"../controls.h\"\n#include \"joystick.h\"\n\n#include \"main/menu_main.h\"\n#include \"control/controls_strings.h\"\n#include \"game_main.h\"\n\nnamespace Controls\n{\n\n// hardcoded list of GUIDs that prefer alt (Japanese) menu control layout -- middle 16 bytes only\nstatic const char* s_alt_guids_16[] =\n{\n    \"7e05000006030000\", // Wii Classic Controller\n    \"7e05000030030000\", // Wii U Pro Controller\n    \"7e05000006200000\", // Switch 1 Joy-Con (L)\n    \"7e05000007200000\", // Switch 1 Joy-Con (R)\n    \"7e05000008200000\", // Switch 1 Joy-Cons\n    \"7e05000009200000\", // Switch 1 Pro Controller\n    \"7e05000017200000\", // Switch 1 SNES Controller\n    \"7e05000069200000\", // Switch 2 Pro Controller\n    \"d620000013a70000\", // PowerA Switch 1 Controller\n    \"d620000011a70000\", // PowerA Core Plus Switch 1 Controller\n    \"4c69632050726f20\", // Switch 1 Pro Controller (alt GUID)\n#ifdef __WIIU__ // Special GUIDs on Wii U\n    \"5769692055204761\", // Wii U GamePad (on Wii U)\n    \"5769692055205072\", // Wii U Pro Controller (on Wii U)\n    \"57696920436c6173\", // Wii Classic Controller (on Wii U)\n#endif\n#ifdef __SWITCH__ // Special GUIDs on Switch 1\n    \"5377697463682043\", // Switch 1 virtual controller\n#endif\n};\n\nstatic const char* s_alt_guids_32[] =\n{\n    \"050000005769696d6f74652028313800\", // Wii U Pro Controller (alt GUID)\n};\n\n// hardcoded list of Wii Remote GUIDs for special layout -- middle 16 bytes only\nstatic const char *s_wii_remote_guids_32[] =\n{\n    \"050000005769696d6f74652028303000\",\n    \"050000007e0500000603000000060000\",\n#ifdef __WIIU__\n    \"000000005769692052656d6f74650000\", // Wii Remote (on Wii U)\n#endif\n};\n\nstatic const char *s_wii_nunchack_guids_32[] =\n{\n#ifdef __WIIU__\n    \"00000000576969204e756e6368756b00\", // Wii Remote + Nunchack (on Wii U)\n    \"0000bce1576969204e756e6368756b00\", // Wii Remote + Nunchack (on Wii U)\n#endif\n    \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", // Dummy, remove if any non-Wii-U entries will be added\n};\n\nstatic bool s_AltControlsDefault(const std::string& guid)\n{\n    if(guid.size() != 32)\n        return false;\n\n    for(size_t i = 0; i < sizeof(s_alt_guids_32) / sizeof(const char*); i++)\n    {\n        if(SDL_memcmp(guid.c_str(), s_alt_guids_32[i], 32) == 0)\n            return true;\n    }\n\n    for(size_t i = 0; i < sizeof(s_alt_guids_16) / sizeof(const char*); i++)\n    {\n        if(SDL_memcmp(guid.c_str() + 8, s_alt_guids_16[i], 16) == 0)\n            return true;\n    }\n\n    return false;\n}\n\nstatic bool s_WiiRemoteControlsDefault(const std::string& guid)\n{\n    if(guid.size() != 32)\n        return false;\n\n    for(size_t i = 0; i < sizeof(s_wii_remote_guids_32) / sizeof(const char*); i++)\n    {\n        if(SDL_memcmp(guid.c_str(), s_wii_remote_guids_32[i], 32) == 0)\n            return true;\n    }\n\n    return false;\n}\n\nstatic bool s_WiiRemoteNunchackControlsDefault(const std::string& guid)\n{\n    if(guid.size() != 32)\n        return false;\n\n    for(size_t i = 0; i < sizeof(s_wii_nunchack_guids_32) / sizeof(const char*); i++)\n    {\n        if(SDL_memcmp(guid.c_str(), s_wii_nunchack_guids_32[i], 32) == 0)\n            return true;\n    }\n\n    return false;\n}\n\n/*====================================================*\\\n|| implementation for InputMethod_Joystick            ||\n\\*====================================================*/\n\nstatic void s_updateJoystickKey(SDL_Joystick* j, bool& key, const KM_Key& mkey)\n{\n    Sint32 val = 0, dx = 0, dy = 0;\n    Sint16 val_initial = 0;\n    bool key_new = false;\n\n    switch(mkey.type)\n    {\n    case KM_Key::JoyAxis:\n        //Note: SDL_JoystickGetAxisInitialState is a new API function added into dev version\n        //      and doesn't available in already released assemblies\n        if(SDL_JoystickGetAxisInitialState(j, mkey.id, &val_initial) == SDL_FALSE)\n        {\n            key_new = false;\n            break;\n        }\n\n        val = SDL_JoystickGetAxis(j, mkey.id);\n\n        if(SDL_abs(val) <= 15000)\n            key_new = false;\n        else if(mkey.val > val_initial)\n            key_new = (val > val_initial);\n        else if(mkey.val < val_initial)\n            key_new = (val < val_initial);\n        else\n            key_new = false;\n\n        break;\n\n    case KM_Key::JoyBallX:\n        SDL_JoystickGetBall(j, mkey.id, &dx, &dy);\n\n        if(mkey.id > 0)\n            key_new = (dx > 0);\n        else if(mkey.id < 0)\n            key_new = (dx < 0);\n        else key_new = false;\n\n        break;\n\n    case KM_Key::JoyBallY:\n        SDL_JoystickGetBall(j, mkey.id, &dx, &dy);\n\n        if(mkey.id > 0)\n            key_new = (dy > 0);\n        else if(mkey.id < 0)\n            key_new = (dy < 0);\n        else key_new = false;\n\n        break;\n\n    case KM_Key::JoyHat:\n        val = (Sint32)SDL_JoystickGetHat(j, mkey.id);\n        key_new = ((val & mkey.val)) != 0;\n        break;\n\n    case KM_Key::JoyButton:\n        key_new = (0 != (Sint32)SDL_JoystickGetButton(j, mkey.id));\n        break;\n\n    default:\n        key_new = false;\n        break;\n    }\n\n    key |= key_new;\n}\n\nstatic void s_updateCtrlKey(SDL_GameController* c, bool& key, const KM_Key& mkey)\n{\n    bool key_new = false;\n\n    switch(mkey.type)\n    {\n    // using brackets to scope val\n    case KM_Key::CtrlAxis:\n    {\n        Sint32 val = SDL_GameControllerGetAxis(c, (SDL_GameControllerAxis)mkey.id);\n\n        if(SDL_abs(val) <= 15000)\n            key_new = false;\n        else if(mkey.val > 0)\n            key_new = (val > 0);\n        else if(mkey.val < 0)\n            key_new = (val < 0);\n        else\n            key_new = false;\n\n        break;\n    }\n\n    case KM_Key::CtrlButton:\n        key_new = (SDL_GameControllerGetButton(c, (SDL_GameControllerButton)mkey.id) != 0);\n        break;\n\n    default:\n        key_new = false;\n        break;\n    }\n\n    key |= key_new;\n}\n\nstatic void s_updateJoystickAnalogue(SDL_Joystick* j, num_t& amount, const KM_Key& mkey, num_t digital_amount = 1)\n{\n    Sint32 val = 0, dx = 0, dy = 0;\n    Sint16 val_initial = 0;\n    num_t amount_new = 0;\n\n    switch(mkey.type)\n    {\n    case KM_Key::JoyAxis:\n        //Note: SDL_JoystickGetAxisInitialState is a new API function added into dev version\n        //      and doesn't available in already released assemblies\n        if(SDL_JoystickGetAxisInitialState(j, mkey.id, &val_initial) == SDL_FALSE)\n        {\n            amount_new = 0;\n            break;\n        }\n\n        val = SDL_JoystickGetAxis(j, mkey.id);\n\n        if(SDL_abs(val) <= 15000)\n        {\n            amount_new = 0;\n        }\n        else if(mkey.val > val_initial)\n        {\n            amount_new = num_t(val - val_initial) / (32768 - val_initial);\n            num_t other_amount_new = num_t(val - 15000) / (32768 - 15000);\n\n            if(other_amount_new > 0 && other_amount_new < amount_new)\n                amount_new = other_amount_new;\n        }\n        else if(mkey.val < val_initial)\n        {\n            amount_new = num_t(val_initial - val) / (val_initial + 32768);\n            num_t other_amount_new = num_t(-val - 15000) / (32768 - 15000);\n\n            if(other_amount_new > 0 && other_amount_new < amount_new)\n                amount_new = other_amount_new;\n        }\n        else\n        {\n            amount_new = 0;\n        }\n\n        break;\n\n    case KM_Key::JoyBallX:\n        SDL_JoystickGetBall(j, mkey.id, &dx, &dy);\n\n        if(mkey.id > 0)\n            amount_new = num_t(dx) / 32;\n        else if(mkey.id < 0)\n            amount_new = num_t(-dx) / 32;\n        else\n            amount_new = 0;\n\n        break;\n\n    case KM_Key::JoyBallY:\n        SDL_JoystickGetBall(j, mkey.id, &dx, &dy);\n\n        if(mkey.id > 0)\n            amount_new = num_t(dy) / 32;\n        else if(mkey.id < 0)\n            amount_new = num_t(dy) / 32;\n        else\n            amount_new = 0;\n\n        break;\n\n    case KM_Key::JoyHat:\n        val = (Sint32)SDL_JoystickGetHat(j, mkey.id);\n\n        if((val & mkey.val))\n            amount_new = digital_amount;\n\n        break;\n\n    case KM_Key::JoyButton:\n        if((Sint32)SDL_JoystickGetButton(j, mkey.id))\n            amount_new = digital_amount;\n\n        break;\n\n    default:\n        amount_new = 0;\n        break;\n    }\n\n    if(amount_new > 0)\n        amount = amount_new;\n}\n\nstatic void s_updateCtrlAnalogue(SDL_GameController* c, num_t& amount, const KM_Key& mkey, num_t digital_amount = 1)\n{\n    num_t amount_new = 0;\n\n    switch(mkey.type)\n    {\n    // using brackets to scope val\n    case KM_Key::CtrlAxis:\n    {\n        Sint32 val = SDL_GameControllerGetAxis(c, (SDL_GameControllerAxis)mkey.id);\n\n        if(SDL_abs(val) <= 15000)\n            amount_new = 0;\n        else if(mkey.val > 0)\n            amount_new = num_t(val - 15000) / (32768 - 15000);\n        else if(mkey.val < 0)\n            amount_new = num_t(-val - 15000) / (32768 - 15000);\n        else\n            amount_new = 0;\n\n        break;\n    }\n\n    case KM_Key::CtrlButton:\n        if(SDL_GameControllerGetButton(c, (SDL_GameControllerButton)mkey.id) != 0)\n            amount_new = digital_amount;\n\n        break;\n\n    default:\n        amount_new = 0;\n        break;\n    }\n\n    if(amount_new > 0)\n        amount = amount_new;\n}\n\nstatic bool s_bindJoystickKey(SDL_Joystick* joy, KM_Key& k)\n{\n    Sint32 val = 0;\n    Sint16 val_initial = 0;\n    int dx = 0, dy = 0;\n    int balls = SDL_JoystickNumBalls(joy);\n    int hats = SDL_JoystickNumHats(joy);\n    int buttons = SDL_JoystickNumButtons(joy);\n    int axes = SDL_JoystickNumAxes(joy);\n\n    for(int i = 0; i < buttons; i++)\n    {\n        val = SDL_JoystickGetButton(joy, i);\n\n        if(val != 0)\n        {\n            k.val = val;\n            k.id = i;\n            k.type = (int)KM_Key::JoyButton;\n            return true;\n        }\n    }\n\n    for(int i = 0; i < hats; i++)\n    {\n        val = SDL_JoystickGetHat(joy, i);\n\n        if(val != 0)\n        {\n            k.val = val;\n            k.id = i;\n            k.type = (int)KM_Key::JoyHat;\n            return true;\n        }\n    }\n\n    for(int i = 0; i < axes; i++)\n    {\n        //Note: The SDL 2.0.6 and higher is requires to support this function\n        if(SDL_JoystickGetAxisInitialState(joy, i, &val_initial) == SDL_FALSE)\n            break;\n\n        val = SDL_JoystickGetAxis(joy, i);\n\n        if(SDL_abs(val) > 15000 && val != (Sint32)val_initial)\n        {\n            k.val = val;\n            k.id = i;\n            k.type = (int)KM_Key::JoyAxis;\n            return true;\n        }\n    }\n\n    for(int i = 0; i < balls; i++)\n    {\n        dx = 0;\n        dy = 0;\n        SDL_JoystickGetBall(joy, i, &dx, &dy);\n\n        if(dx != 0)\n        {\n            k.val = dx;\n            k.id = i;\n            k.type = (int)KM_Key::JoyBallX;\n            return true;\n        }\n        else if(dy != 0)\n        {\n            k.val = dy;\n            k.id = i;\n            k.type = (int)KM_Key::JoyBallY;\n            return true;\n        }\n    }\n\n    k.val = 0;\n    k.id = 0;\n    k.type = (int)KM_Key::NoControl;\n    return false;\n}\n\nstatic bool s_bindControllerKey(SDL_GameController* ctrl, KM_Key& k)\n{\n    Sint32 val = 0;\n\n    for(int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++)\n    {\n        val = SDL_GameControllerGetButton(ctrl, (SDL_GameControllerButton)i);\n\n        if(val != 0)\n        {\n            k.val = val;\n            k.id = i;\n            k.type = (int)KM_Key::CtrlButton;\n            return true;\n        }\n    }\n\n    for(int i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++)\n    {\n        val = SDL_GameControllerGetAxis(ctrl, (SDL_GameControllerAxis)i);\n\n        if(SDL_abs(val) > 15000)\n        {\n            k.val = val;\n            k.id = i;\n            k.type = (int)KM_Key::CtrlAxis;\n            return true;\n        }\n    }\n\n    k.val = 0;\n    k.id = 0;\n    k.type = (int)KM_Key::NoControl;\n    return false;\n}\n\n/*====================================================*\\\n|| implementation for InputMethod_Joystick            ||\n\\*====================================================*/\n\nInputMethod_Joystick::~InputMethod_Joystick()\n{\n    if(this->m_devices)\n        this->m_devices->can_poll = false;\n}\n\n// Update functions that set player controls (and editor controls)\n// based on current device input. Return false if device lost.\nbool InputMethod_Joystick::Update(int player, Controls_t& c, CursorControls_t& m, EditorControls_t& e, HotkeysPressed_t& h)\n{\n    InputMethodProfile_Joystick* p = dynamic_cast<InputMethodProfile_Joystick*>(this->Profile);\n\n    if(!p || !this->m_devices)\n        return false;\n\n    if(!SDL_JoystickGetAttached(this->m_devices->joy))\n    {\n        // note: the joystick is owned by the InputMethodType, not the InputMethod.\n        this->m_devices = nullptr;\n        return false;\n    }\n\n    for(int a = 0; a < 4; a++)\n    {\n        KM_Key* keys;\n        KM_Key* keys2;\n        size_t key_start;\n        size_t key_max;\n        bool activate = false;\n\n        if(a == 0)\n        {\n            keys = p->m_keys;\n            keys2 = p->m_keys2;\n            key_start = 0;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = p->m_cursor_keys;\n            keys2 = p->m_cursor_keys2;\n            key_start = 4;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = p->m_editor_keys;\n            keys2 = p->m_editor_keys2;\n            key_start = 4;\n            key_max = EditorControls::n_buttons;\n\n            if(p->m_simple_editor && LevelEditor && GamePaused == PauseCode::None)\n                continue;\n        }\n        else\n        {\n            keys = p->m_hotkeys;\n            keys2 = p->m_hotkeys2;\n            key_start = 0;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = key_start; i < key_max; i++)\n        {\n            const KM_Key& key = keys[i];\n            const KM_Key& key2 = keys2[i];\n            bool* b;\n\n            if(a == 0)\n            {\n                b = &PlayerControls::GetButton(c, i);\n                *b = false;\n            }\n            else if(a == 1)\n            {\n                b = &CursorControls::GetButton(m, i);\n            }\n            else if(a == 2)\n            {\n                b = &EditorControls::GetButton(e, i);\n            }\n            else\n            {\n                b = &activate;\n                *b = false;\n            }\n\n            if(p->m_controllerProfile && this->m_devices->ctrl)\n            {\n                s_updateCtrlKey(this->m_devices->ctrl, *b, key);\n                s_updateCtrlKey(this->m_devices->ctrl, *b, key2);\n            }\n            else\n            {\n                s_updateJoystickKey(this->m_devices->joy, *b, key);\n                s_updateJoystickKey(this->m_devices->joy, *b, key2);\n            }\n\n            if(a == 3 && *b)\n                h[i] = player;\n        }\n    }\n\n    // simple editor controls\n    if(p->m_simple_editor && LevelEditor && GamePaused == PauseCode::None)\n    {\n        CursorControls::GetButton(m, CursorControls::Buttons::Primary) = PlayerControls::GetButton(c, PlayerControls::Buttons::Jump);\n        CursorControls::GetButton(m, CursorControls::Buttons::Secondary) = false;\n        CursorControls::GetButton(m, CursorControls::Buttons::Tertiary) = false;\n\n        EditorControls::GetButton(e, EditorControls::FastScroll) = false;\n        EditorControls::GetButton(e, EditorControls::PrevSection) = false;\n        EditorControls::GetButton(e, EditorControls::NextSection) = false;\n\n        EditorControls::GetButton(e, EditorControls::ModeSelect) |= PlayerControls::GetButton(c, PlayerControls::Buttons::Run);\n        EditorControls::GetButton(e, EditorControls::ModeErase) |= PlayerControls::GetButton(c, PlayerControls::Buttons::AltJump);\n        EditorControls::GetButton(e, EditorControls::SwitchScreens) |= PlayerControls::GetButton(c, PlayerControls::Buttons::Drop);\n        EditorControls::GetButton(e, EditorControls::TestPlay) |= PlayerControls::GetButton(c, PlayerControls::Buttons::Start);\n    }\n\n    // analogue controls\n    num_t cursor[4] = {0, 0, 0, 0};\n    num_t scroll[4] = {0, 0, 0, 0};\n\n    if(p->m_controllerProfile && this->m_devices->ctrl)\n    {\n        for(int i = 0; i < 4; i++)\n        {\n            if(p->m_simple_editor && LevelEditor && GamePaused == PauseCode::None)\n            {\n                s_updateCtrlAnalogue(this->m_devices->ctrl, cursor[i], p->m_keys[i], 0.5_n);\n                s_updateCtrlAnalogue(this->m_devices->ctrl, cursor[i], p->m_keys2[i], 0.5_n);\n            }\n            else\n            {\n                s_updateCtrlAnalogue(this->m_devices->ctrl, cursor[i], p->m_cursor_keys[i]);\n                s_updateCtrlAnalogue(this->m_devices->ctrl, cursor[i], p->m_cursor_keys2[i]);\n                s_updateCtrlAnalogue(this->m_devices->ctrl, scroll[i], p->m_editor_keys[i]);\n                s_updateCtrlAnalogue(this->m_devices->ctrl, scroll[i], p->m_editor_keys2[i]);\n            }\n        }\n    }\n    else\n    {\n        for(int i = 0; i < 4; i++)\n        {\n            if(p->m_simple_editor && LevelEditor && GamePaused == PauseCode::None)\n            {\n                s_updateJoystickAnalogue(this->m_devices->joy, cursor[i], p->m_keys[i], 0.5_n);\n                s_updateJoystickAnalogue(this->m_devices->joy, cursor[i], p->m_keys2[i], 0.5_n);\n            }\n            else\n            {\n                s_updateJoystickAnalogue(this->m_devices->joy, cursor[i], p->m_cursor_keys[i]);\n                s_updateJoystickAnalogue(this->m_devices->joy, cursor[i], p->m_cursor_keys2[i]);\n                s_updateJoystickAnalogue(this->m_devices->joy, scroll[i], p->m_editor_keys[i]);\n                s_updateJoystickAnalogue(this->m_devices->joy, scroll[i], p->m_editor_keys2[i]);\n            }\n        }\n    }\n\n    // Scroll control (UDLR)\n    num_t* const scroll_dest[4] = {&e.ScrollUp, &e.ScrollDown, &e.ScrollLeft, &e.ScrollRight};\n    for(int i = 0; i < 4; i++)\n        *scroll_dest[i] += scroll[i] * 10;\n\n    // Cursor control (UDLR)\n    if(cursor[0] || cursor[1] || cursor[2] || cursor[3])\n    {\n        bool edge_scroll = LevelEditor && !MagicHand;\n\n        if(m.X < 0)\n            m.X = XRender::TargetW / 2;\n\n        if(m.Y < 0)\n            m.Y = XRender::TargetH / 2;\n\n        m.X += (cursor[3] - cursor[2]) * 16;\n        m.Y += (cursor[1] - cursor[0]) * 16;\n\n        if(m.X < 0)\n        {\n            if(edge_scroll)\n                e.ScrollLeft += -m.X;\n\n            m.X = 0;\n        }\n        else if(m.X >= XRender::TargetW)\n        {\n            if(edge_scroll)\n                e.ScrollRight += m.X - (XRender::TargetW - 1);\n\n            m.X = XRender::TargetW - 1;\n        }\n\n        if(m.Y < 0)\n        {\n            if(edge_scroll)\n                e.ScrollUp += -m.Y;\n\n            m.Y = 0;\n        }\n        else if(m.Y >= XRender::TargetH)\n        {\n            if(edge_scroll)\n                e.ScrollDown += m.Y - (XRender::TargetH - 1);\n\n            m.Y = XRender::TargetH - 1;\n        }\n\n        m.Move = true;\n    }\n\n    return true;\n}\n\nvoid InputMethod_Joystick::Rumble(int ms, float strength)\n{\n    if(!this->m_devices)\n        return;\n\n    if(this->m_devices->haptic)\n    {\n        pLogDebug(\"Trying to use SDL haptic rumble: %dms %f\", ms, strength);\n\n        if(SDL_HapticRumblePlay(this->m_devices->haptic, strength, ms) == 0)\n            return;\n    }\n\n    int intStrength = (int)(0xFFFF * strength + 0.5f);\n\n    if(intStrength > 0xFFFF)\n        intStrength = 0xFFFF;\n\n    if(intStrength < 0)\n        intStrength = 0;\n\n#if SDL_VERSION_ATLEAST(2, 0, 12)\n    if(this->m_devices->joy)\n    {\n        pLogDebug(\"Trying to use SDL joystick rumble: %dms %f\", ms, strength);\n        SDL_JoystickRumble(this->m_devices->joy, intStrength, intStrength, ms);\n    }\n#endif\n}\n\nStatusInfo InputMethod_Joystick::GetStatus()\n{\n    StatusInfo res;\n\n    if(!this->m_devices)\n        return res;\n\n    uint32_t ticks = SDL_GetTicks();\n\n    if(this->m_last_power_check > 10000)\n    {\n        SDL_JoystickPowerLevel level = SDL_JoystickCurrentPowerLevel(this->m_devices->joy);\n\n        if(level == SDL_JOYSTICK_POWER_UNKNOWN)\n        {\n            res.power_status = StatusInfo::POWER_UNKNOWN;\n        }\n        else if(level == SDL_JOYSTICK_POWER_WIRED)\n        {\n            res.power_status = StatusInfo::POWER_WIRED;\n        }\n        else if(level == SDL_JOYSTICK_POWER_MAX)\n        {\n            res.power_status = StatusInfo::POWER_CHARGED;\n            res.power_level = 1.0_nf;\n        }\n        else\n        {\n            res.power_status = StatusInfo::POWER_DISCHARGING;\n\n            if(level == SDL_JOYSTICK_POWER_EMPTY)\n                res.power_level = 0.15_nf;\n\n            if(level == SDL_JOYSTICK_POWER_LOW)\n                res.power_level = 0.3_nf;\n\n            if(level == SDL_JOYSTICK_POWER_MEDIUM)\n                res.power_level = 0.6_nf;\n\n            if(level == SDL_JOYSTICK_POWER_FULL)\n                res.power_level = 0.9_nf;\n        }\n\n        m_last_power_check = ticks;\n        m_recent_status = res;\n    }\n\n    return m_recent_status;\n}\n\n/*====================================================*\\\n|| implementation for InputMethodProfile_Joystick     ||\n\\*====================================================*/\n\n// the job of this function is to initialize the class in a consistent state\nInputMethodProfile_Joystick::InputMethodProfile_Joystick()\n{\n    this->InitAsController(INIT_AS_DEFAULT);\n    this->m_showPowerStatus = false;\n}\n\nvoid InputMethodProfile_Joystick::InitAsJoystick()\n{\n    this->m_controllerProfile = false;\n    this->m_keys[PlayerControls::Buttons::Up].assign(KM_Key::JoyHat, 0, SDL_HAT_UP);\n    this->m_keys[PlayerControls::Buttons::Down].assign(KM_Key::JoyHat, 0, SDL_HAT_DOWN);\n    this->m_keys[PlayerControls::Buttons::Left].assign(KM_Key::JoyHat, 0, SDL_HAT_LEFT);\n    this->m_keys[PlayerControls::Buttons::Right].assign(KM_Key::JoyHat, 0, SDL_HAT_RIGHT);\n    this->m_keys[PlayerControls::Buttons::Jump].assign(KM_Key::JoyButton, 0, 1);\n    this->m_keys[PlayerControls::Buttons::AltJump].assign(KM_Key::JoyButton, 1, 1);\n    this->m_keys[PlayerControls::Buttons::Run].assign(KM_Key::JoyButton, 2, 1);\n    this->m_keys[PlayerControls::Buttons::AltRun].assign(KM_Key::JoyButton, 3, 1);\n    this->m_keys[PlayerControls::Buttons::Drop].assign(KM_Key::JoyButton, 6, 1);\n    this->m_keys[PlayerControls::Buttons::Start].assign(KM_Key::JoyButton, 7, 1);\n\n    // clear all of them, then fill in some of them\n    for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n        this->m_keys2[i].assign(KM_Key::NoControl, -1, -1);\n\n    this->m_keys2[PlayerControls::Buttons::Up].assign(KM_Key::JoyAxis, 1, -1);\n    this->m_keys2[PlayerControls::Buttons::Down].assign(KM_Key::JoyAxis, 1, 1);\n    this->m_keys2[PlayerControls::Buttons::Left].assign(KM_Key::JoyAxis, 0, -1);\n    this->m_keys2[PlayerControls::Buttons::Right].assign(KM_Key::JoyAxis, 0, 1);\n    this->m_keys2[PlayerControls::Buttons::AltRun].assign(KM_Key::JoyButton, 4, 1);\n    this->m_keys2[PlayerControls::Buttons::AltJump].assign(KM_Key::JoyButton, 5, 1);\n\n    // clear all of the non-standard controls, then fill in some of them\n    for(size_t i = 0; i < CursorControls::n_buttons; i++)\n    {\n        this->m_cursor_keys[i].assign(KM_Key::NoControl, -1, -1);\n        this->m_cursor_keys2[i].assign(KM_Key::NoControl, -1, -1);\n    }\n\n    for(size_t i = 0; i < EditorControls::n_buttons; i++)\n    {\n        this->m_editor_keys[i].assign(KM_Key::NoControl, -1, -1);\n        this->m_editor_keys2[i].assign(KM_Key::NoControl, -1, -1);\n    }\n\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n    {\n        this->m_hotkeys[i].assign(KM_Key::NoControl, -1, -1);\n        this->m_hotkeys2[i].assign(KM_Key::NoControl, -1, -1);\n    }\n\n    this->m_cursor_keys[CursorControls::Buttons::CursorUp].assign(KM_Key::JoyAxis, 3, -1);\n    this->m_cursor_keys[CursorControls::Buttons::CursorDown].assign(KM_Key::JoyAxis, 3, 1);\n    this->m_cursor_keys[CursorControls::Buttons::CursorLeft].assign(KM_Key::JoyAxis, 2, -1);\n    this->m_cursor_keys[CursorControls::Buttons::CursorRight].assign(KM_Key::JoyAxis, 2, 1);\n    this->m_cursor_keys[CursorControls::Buttons::Primary].assign(KM_Key::JoyAxis, 4, 1);\n    this->m_cursor_keys[CursorControls::Buttons::Secondary].assign(KM_Key::JoyAxis, 5, 1);\n}\n\nvoid InputMethodProfile_Joystick::InitAsController(InitAs init_as)\n{\n    this->m_controllerProfile = true;\n\n    this->m_keys[PlayerControls::Buttons::Up].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_DPAD_UP, 1);\n    this->m_keys[PlayerControls::Buttons::Down].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_DPAD_DOWN, 1);\n    this->m_keys[PlayerControls::Buttons::Left].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_DPAD_LEFT, 1);\n    this->m_keys[PlayerControls::Buttons::Right].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, 1);\n\n    if(init_as == INIT_AS_ALT)\n    {\n        this->m_keys[PlayerControls::Buttons::Jump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_B, 1);\n        this->m_keys[PlayerControls::Buttons::AltJump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_A, 1);\n        this->m_keys[PlayerControls::Buttons::Run].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_Y, 1);\n        this->m_keys[PlayerControls::Buttons::AltRun].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_X, 1);\n        this->m_altMenuControls = true;\n    }\n    else if(init_as == INIT_AS_WII_REMOTE)\n    {\n        this->m_keys[PlayerControls::Buttons::Jump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_X, 1);\n        this->m_keys[PlayerControls::Buttons::AltJump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_A, 1);\n        this->m_keys[PlayerControls::Buttons::Run].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_Y, 1);\n        this->m_keys[PlayerControls::Buttons::AltRun].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_B, 1);\n    }\n    else if(init_as == INIT_AS_WII_REMOTE_WITH_NUNCHACK)\n    {\n        this->m_keys[PlayerControls::Buttons::Jump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_A, 1);\n        this->m_keys[PlayerControls::Buttons::AltJump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_X, 1);\n        this->m_keys[PlayerControls::Buttons::Run].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_B, 1);\n        this->m_keys[PlayerControls::Buttons::AltRun].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_Y, 1);\n    }\n    else\n    {\n        this->m_keys[PlayerControls::Buttons::Jump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_A, 1);\n        this->m_keys[PlayerControls::Buttons::AltJump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_B, 1);\n        this->m_keys[PlayerControls::Buttons::Run].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_X, 1);\n        this->m_keys[PlayerControls::Buttons::AltRun].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_Y, 1);\n\n#ifdef VITA\n        // Vita's layout under SDL is normal (Cross/A button is South), but Japanese Vitas do use the alt menu controls (Circle button advances in menus)\n        int enter_button = SCE_SYSTEM_PARAM_ENTER_BUTTON_CROSS;\n        sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_ENTER_BUTTON, &enter_button);\n        if(enter_button == SCE_SYSTEM_PARAM_ENTER_BUTTON_CIRCLE)\n            this->m_altMenuControls = true;\n#endif\n    }\n\n    const char *init_as_str;\n    switch(init_as)\n    {\n    default:\n        init_as_str = \"Standard\";\n        break;\n\n    case INIT_AS_ALT:\n        init_as_str = \"Alt\";\n        break;\n\n    case INIT_AS_WII_REMOTE:\n        init_as_str = \"Wii Remote\";\n        break;\n\n    case INIT_AS_WII_REMOTE_WITH_NUNCHACK:\n        init_as_str = \"Wii Remote + Nunchuk\";\n        break;\n    }\n\n    pLogDebug(\"Initializing controller with mode: [%s] controls and [%s] menus.\",\n        init_as_str,\n        this->m_altMenuControls ? \"Alt\" : \"Standard\");\n\n    this->m_keys[PlayerControls::Buttons::Drop].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_BACK, 1);\n    this->m_keys[PlayerControls::Buttons::Start].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_START, 1);\n\n    // clear all of them, then fill in some of them\n    for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n        this->m_keys2[i].assign(KM_Key::NoControl, -1, -1);\n\n    this->m_keys2[PlayerControls::Buttons::Up].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_LEFTY, -1);\n    this->m_keys2[PlayerControls::Buttons::Down].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_LEFTY, 1);\n    this->m_keys2[PlayerControls::Buttons::Left].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_LEFTX, -1);\n    this->m_keys2[PlayerControls::Buttons::Right].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_LEFTX, 1);\n\n    if(init_as == INIT_AS_WII_REMOTE_WITH_NUNCHACK)\n    {\n        this->m_keys2[PlayerControls::Buttons::AltRun].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, 1);\n        this->m_keys2[PlayerControls::Buttons::AltJump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, 1);\n    }\n    else\n    {\n        this->m_keys2[PlayerControls::Buttons::AltRun].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, 1);\n        this->m_keys2[PlayerControls::Buttons::AltJump].assign(KM_Key::CtrlButton, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, 1);\n    }\n\n    // clear all of the non-standard controls, then fill in some of them\n    for(size_t i = 0; i < CursorControls::n_buttons; i++)\n    {\n        this->m_cursor_keys[i].assign(KM_Key::NoControl, -1, -1);\n        this->m_cursor_keys2[i].assign(KM_Key::NoControl, -1, -1);\n    }\n\n    for(size_t i = 0; i < EditorControls::n_buttons; i++)\n    {\n        this->m_editor_keys[i].assign(KM_Key::NoControl, -1, -1);\n        this->m_editor_keys2[i].assign(KM_Key::NoControl, -1, -1);\n    }\n\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n    {\n        this->m_hotkeys[i].assign(KM_Key::NoControl, -1, -1);\n        this->m_hotkeys2[i].assign(KM_Key::NoControl, -1, -1);\n    }\n\n    this->m_cursor_keys[CursorControls::Buttons::CursorUp].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_RIGHTY, -1);\n    this->m_cursor_keys[CursorControls::Buttons::CursorDown].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_RIGHTY, 1);\n    this->m_cursor_keys[CursorControls::Buttons::CursorLeft].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_RIGHTX, -1);\n    this->m_cursor_keys[CursorControls::Buttons::CursorRight].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_RIGHTX, 1);\n    this->m_cursor_keys[CursorControls::Buttons::Primary].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 1);\n    this->m_cursor_keys[CursorControls::Buttons::Secondary].assign(KM_Key::CtrlAxis, SDL_CONTROLLER_AXIS_TRIGGERLEFT, 1);\n}\n\nbool InputMethodProfile_Joystick::PollPrimaryButton(ControlsClass c, size_t i)\n{\n    // note: m_canPoll is initialized to false\n    InputMethodType_Joystick* t = dynamic_cast<InputMethodType_Joystick*>(this->Type);\n\n    if(!t)\n        return true;\n\n    if(this->m_simple_editor && c == ControlsClass::Editor)\n        return true;\n\n    KM_Key key = this->m_controllerProfile ?\n                 t->PollControllerKeyAll() :\n                 t->PollJoystickKeyAll();\n\n    // if didn't find any key, allow poll in future but return false\n    if(key.type == KM_Key::NoControl)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    KM_Key* keys;\n    KM_Key* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = this->m_cursor_keys;\n        keys2 = this->m_cursor_keys2;\n        key_max = CursorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling Joystick primary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n    // if a button's secondary key (including the current one) is the new key, delete it.\n    // if a button's primary key (excluding the current one) is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, replace it with the button the player is replacing.\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(keys2[j] == key)\n        {\n            keys2[j] = KM_Key();\n        }\n        else if(i != j && keys[j] == key)\n        {\n            if(keys2[j].type != KM_Key::NoControl)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = KM_Key();\n            }\n            else\n            {\n                keys[j] = keys[i];\n            }\n        }\n    }\n\n    keys[i] = key;\n    return true;\n}\n\nbool InputMethodProfile_Joystick::PollSecondaryButton(ControlsClass c, size_t i)\n{\n    // note: m_canPoll is initialized to false\n    InputMethodType_Joystick* t = dynamic_cast<InputMethodType_Joystick*>(this->Type);\n\n    if(!t)\n        return true;\n\n    if(this->m_simple_editor && c == ControlsClass::Editor)\n        return true;\n\n    KM_Key key;\n\n    if(this->m_controllerProfile)\n        key = t->PollControllerKeyAll();\n    else\n        key = t->PollJoystickKeyAll();\n\n    // if didn't find any key, allow poll in future but return false\n    if(key.type == KM_Key::NoControl)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    KM_Key* keys;\n    KM_Key* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = this->m_cursor_keys;\n        keys2 = this->m_cursor_keys2;\n        key_max = CursorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling Joystick secondary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n\n    // if the current button's primary key is the new key,\n    //     delete its secondary key instead of setting it.\n    if(keys[i] == key)\n    {\n        keys2[i] = KM_Key();\n        return true;\n    }\n\n    // if another button's secondary key is the new key, delete it.\n    // if another button's primary key is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, if this button's secondary key is defined, overwrite the other's with this.\n    //     if all else fails, overwrite the other button's with this button's PRIMARY key and assign\n    //         this button's PRIMARY key instead\n\n    bool can_do_secondary = true;\n\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(i != j && keys2[j] == key)\n        {\n            keys2[j] = KM_Key();\n        }\n        else if(i != j && keys[j] == key)\n        {\n            if(keys2[j].type != KM_Key::NoControl)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = KM_Key();\n            }\n            else if(keys2[i].type != KM_Key::NoControl)\n            {\n                keys[j] = keys2[i];\n            }\n            else\n            {\n                keys[j] = keys[i];\n                can_do_secondary = false;\n            }\n        }\n    }\n\n    if(can_do_secondary)\n        keys2[i] = key;\n    else\n        keys[i] = key;\n\n    return true;\n}\n\nbool InputMethodProfile_Joystick::DeletePrimaryButton(ControlsClass c, size_t i)\n{\n    // resolve the particular primary and secondary key arrays\n    KM_Key* keys;\n    KM_Key* keys2;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = this->m_cursor_keys;\n        keys2 = this->m_cursor_keys2;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        if(this->m_simple_editor)\n            return false;\n\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n    }\n    else\n    {\n        D_pLogWarning(\"Attempted to delete Joystick primary button with disallowed controls class %d\\n\", (int)c);\n        return false;\n    }\n\n    if(keys2[i].type != KM_Key::NoControl)\n    {\n        keys[i] = keys2[i];\n        keys2[i] = KM_Key();\n        return true;\n    }\n\n    if(c == ControlsClass::Player)\n        return false;\n\n    if(keys[i].type != KM_Key::NoControl)\n    {\n        keys[i] = KM_Key();\n        return true;\n    }\n\n    return false;\n}\n\nbool InputMethodProfile_Joystick::DeleteSecondaryButton(ControlsClass c, size_t i)\n{\n    // resolve the particular primary and secondary key arrays\n    KM_Key* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n    {\n        if(this->m_simple_editor)\n            return false;\n\n        keys2 = this->m_editor_keys2;\n    }\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return false; // BAD!\n\n    if(keys2[i].type != KM_Key::NoControl)\n    {\n        keys2[i] = KM_Key();\n        return true;\n    }\n\n    return false;\n}\n\nstatic char s_buttonNameBuffer[16] = \"XXXXXXXXXXXXXXX\";\n\nstatic const char* s_nameButton(const KM_Key& k)\n{\n    switch(k.type)\n    {\n    case KM_Key::NoControl:\n        return \"NONE\";\n        break;\n\n    case KM_Key::CtrlButton:\n        return SDL_GameControllerGetStringForButton((SDL_GameControllerButton)k.id);\n        break;\n\n    case KM_Key::CtrlAxis:\n        if(k.val < 0)\n            snprintf(s_buttonNameBuffer, 15, \"%s-\", SDL_GameControllerGetStringForAxis((SDL_GameControllerAxis)k.id));\n        else\n            snprintf(s_buttonNameBuffer, 15, \"%s+\", SDL_GameControllerGetStringForAxis((SDL_GameControllerAxis)k.id));\n\n        break;\n\n    case KM_Key::JoyButton:\n        snprintf(s_buttonNameBuffer, 15, \"BUT %d\", k.id);\n        break;\n\n    case KM_Key::JoyAxis:\n        if(k.val < 0)\n            snprintf(s_buttonNameBuffer, 15, \"AX %d-\", k.id);\n        else\n            snprintf(s_buttonNameBuffer, 15, \"AX %d+\", k.id);\n\n        break;\n\n    case KM_Key::JoyBallX:\n        if(k.val < 0)\n            snprintf(s_buttonNameBuffer, 15, \"BALL %dX-\", k.id);\n        else\n            snprintf(s_buttonNameBuffer, 15, \"BALL %dX+\", k.id);\n\n        break;\n\n    case KM_Key::JoyBallY:\n        if(k.val < 0)\n            snprintf(s_buttonNameBuffer, 15, \"BALL %dY-\", k.id);\n        else\n            snprintf(s_buttonNameBuffer, 15, \"BALL %dY+\", k.id);\n\n        break;\n\n    case KM_Key::JoyHat:\n        if(k.val == SDL_HAT_UP)\n            snprintf(s_buttonNameBuffer, 15, \"HAT %dU\", k.id);\n        else if(k.val == SDL_HAT_DOWN)\n            snprintf(s_buttonNameBuffer, 15, \"HAT %dD\", k.id);\n        else if(k.val == SDL_HAT_LEFT)\n            snprintf(s_buttonNameBuffer, 15, \"HAT %dL\", k.id);\n        else if(k.val == SDL_HAT_RIGHT)\n            snprintf(s_buttonNameBuffer, 15, \"HAT %dR\", k.id);\n        else\n            snprintf(s_buttonNameBuffer, 15, \"HAT %d %d\", k.id, k.val);\n\n        break;\n\n    default:\n        return \"INVALID\";\n        break;\n    }\n\n    return s_buttonNameBuffer;\n}\n\nconst char* InputMethodProfile_Joystick::NamePrimaryButton(ControlsClass c, size_t i)\n{\n    KM_Key* keys;\n\n    if(c == ControlsClass::Player)\n        keys = this->m_keys;\n    else if(c == ControlsClass::Cursor)\n        keys = this->m_cursor_keys;\n    else if(c == ControlsClass::Editor && this->m_simple_editor)\n    {\n        keys = this->m_keys;\n\n        if(i == EditorControls::Buttons::ModeSelect)\n            i = PlayerControls::Buttons::Run;\n        else if(i == EditorControls::Buttons::ModeErase)\n            i = PlayerControls::Buttons::AltJump;\n        else if(i == EditorControls::Buttons::SwitchScreens)\n            i = PlayerControls::Buttons::Drop;\n        else if(i == EditorControls::Buttons::TestPlay)\n            i = PlayerControls::Buttons::Start;\n        else\n            return g_mainMenu.caseNone.c_str();\n    }\n    else if(c == ControlsClass::Editor)\n        keys = this->m_editor_keys;\n    else if(c == ControlsClass::Hotkey)\n        keys = this->m_hotkeys;\n    else\n        return \"\";\n\n    return s_nameButton(keys[i]);\n}\n\nconst char* InputMethodProfile_Joystick::NameSecondaryButton(ControlsClass c, size_t i)\n{\n    KM_Key* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor && this->m_simple_editor)\n    {\n        keys2 = this->m_keys2;\n\n        if(i == EditorControls::Buttons::ModeSelect)\n            i = PlayerControls::Buttons::Run;\n        else if(i == EditorControls::Buttons::ModeErase)\n            i = PlayerControls::Buttons::AltJump;\n        else if(i == EditorControls::Buttons::SwitchScreens)\n            i = PlayerControls::Buttons::Drop;\n        else if(i == EditorControls::Buttons::TestPlay)\n            i = PlayerControls::Buttons::Start;\n        else\n            return \"\";\n    }\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return \"\";\n\n    return s_nameButton(keys2[i]);\n}\n\nvoid InputMethodProfile_Joystick::SaveConfig(IniProcessing* ctl)\n{\n    std::string name;\n\n    for(int a = 0; a < 4; a++)\n    {\n        KM_Key* keys;\n        KM_Key* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = this->m_cursor_keys;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            size_t orig_l = name.size();\n\n            ctl->setValue(name.replace(orig_l, std::string::npos, \"-type\").c_str(),\n                          keys[i].type);\n            ctl->setValue(name.replace(orig_l, std::string::npos, \"-id\").c_str(),\n                          keys[i].id);\n            ctl->setValue(name.replace(orig_l, std::string::npos, \"-val\").c_str(),\n                          keys[i].val);\n\n            ctl->setValue(name.replace(orig_l, std::string::npos, \"2-type\").c_str(),\n                          keys2[i].type);\n            ctl->setValue(name.replace(orig_l, std::string::npos, \"2-id\").c_str(),\n                          keys2[i].id);\n            ctl->setValue(name.replace(orig_l, std::string::npos, \"2-val\").c_str(),\n                          keys2[i].val);\n        }\n    }\n\n    ctl->setValue(\"old-joystick\", !this->m_controllerProfile);\n    ctl->setValue(\"simple-editor\", this->m_simple_editor);\n}\n\nvoid InputMethodProfile_Joystick::LoadConfig(IniProcessing* ctl)\n{\n    std::string name;\n\n    for(int a = 0; a < 4; a++)\n    {\n        KM_Key* keys;\n        KM_Key* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = this->m_cursor_keys;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            size_t orig_l = name.size();\n\n            ctl->read(name.replace(orig_l, std::string::npos, \"-type\").c_str(),\n                      keys[i].type, keys[i].type);\n            ctl->read(name.replace(orig_l, std::string::npos, \"-id\").c_str(),\n                      keys[i].id, keys[i].id);\n            ctl->read(name.replace(orig_l, std::string::npos, \"-val\").c_str(),\n                      keys[i].val, keys[i].val);\n\n            ctl->read(name.replace(orig_l, std::string::npos, \"2-type\").c_str(),\n                      keys2[i].type, keys2[i].type);\n            ctl->read(name.replace(orig_l, std::string::npos, \"2-id\").c_str(),\n                      keys2[i].id, keys2[i].id);\n            ctl->read(name.replace(orig_l, std::string::npos, \"2-val\").c_str(),\n                      keys2[i].val, keys2[i].val);\n        }\n    }\n\n    bool legacyProfile;\n    ctl->read(\"old-joystick\", legacyProfile, false);\n    this->m_controllerProfile = !legacyProfile;\n\n    ctl->read(\"simple-editor\", this->m_simple_editor, true);\n}\n\nvoid InputMethodProfile_Joystick::SaveConfig_Legacy(IniProcessing* ctl)\n{\n    std::string name;\n\n    for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n    {\n        name = PlayerControls::GetButtonName_INI(i);\n        size_t orig_l = name.size();\n\n        ctl->setValue(name.replace(orig_l, std::string::npos, \"-ctrl-type\").c_str(),\n                      this->m_keys[i].type);\n        ctl->setValue(name.replace(orig_l, std::string::npos, \"-ctrl-id\").c_str(),\n                      this->m_keys[i].id);\n        ctl->setValue(name.replace(orig_l, std::string::npos, \"-ctrl-val\").c_str(),\n                      this->m_keys[i].val);\n\n        ctl->setValue(name.replace(orig_l, std::string::npos, \"-type\").c_str(),\n                      this->m_keys2[i].type);\n        ctl->setValue(name.replace(orig_l, std::string::npos, \"-id\").c_str(),\n                      this->m_keys2[i].id);\n        ctl->setValue(name.replace(orig_l, std::string::npos, \"-val\").c_str(),\n                      this->m_keys2[i].val);\n    }\n}\n\nsize_t InputMethodProfile_Joystick::GetOptionCount_Custom()\n{\n    return 1;\n}\n\nconst char *InputMethodProfile_Joystick::GetOptionName_Custom(size_t i)\n{\n    UNUSED(i);\n\n    return g_controlsStrings.joystickSimpleEditor.c_str();\n}\n\nconst char *InputMethodProfile_Joystick::GetOptionValue_Custom(size_t i)\n{\n    UNUSED(i);\n\n    if(this->m_simple_editor)\n        return g_mainMenu.wordOn.c_str();\n    else\n        return g_mainMenu.wordOff.c_str();\n}\n\nbool InputMethodProfile_Joystick::OptionChange_Custom(size_t i)\n{\n    return this->OptionRotateRight_Custom(i);\n}\n\nbool InputMethodProfile_Joystick::OptionRotateLeft_Custom(size_t i)\n{\n    return this->OptionRotateRight_Custom(i);\n}\n\nbool InputMethodProfile_Joystick::OptionRotateRight_Custom(size_t i)\n{\n    UNUSED(i);\n\n    this->m_simple_editor = !this->m_simple_editor;\n\n    return true;\n}\n\n/*====================================================*\\\n|| implementation for InputMethodType_Joystick        ||\n\\*====================================================*/\n\nInputMethodProfile* InputMethodType_Joystick::AllocateProfile() noexcept\n{\n    return (InputMethodProfile*) new(std::nothrow) InputMethodProfile_Joystick;\n}\n\nInputMethodType_Joystick::InputMethodType_Joystick()\n{\n    this->Name = \"Joystick\";\n\n    SDL_JoystickEventState(SDL_ENABLE);\n\n#ifdef __WIIU__\n    // Wii Remote\n    SDL_GameControllerAddMapping(\"000000005769692052656d6f74650000,\"\n                                 \"Wii Remote,\"\n                                 \"crc:1d69,\"\n                                 \"x:b7,y:b6,\"\n                                 \"a:b0,b:b1,\"\n                                 \"back:b11,start:b10,\"\n                                 \"dpdown:b12,dpleft:b13,dpright:b15,dpup:b14,\");\n\n    // Wii Remote + Nunchack\n    SDL_GameControllerAddMapping(\"00000000576969204e756e6368756b00,\"\n                                 \"Wii Remote + Nunchuk,\"\n                                 \"crc:bce1,\"\n                                 \"a:b0,b:b1,\"\n                                 \"x:b6,y:b7,\"\n                                 \"leftshoulder:b2,rightshoulder:b3,\"\n                                 \"lefttrigger:b8,righttrigger:b9,\"\n                                 \"back:b11,start:b10,\"\n                                 \"dpdown:b15,dpleft:b12,dpright:b14,dpup:b13,\"\n                                 \"leftstick:b4,rightstick:b5,\"\n                                 \"leftx:a0,lefty:a1,\"\n                                 \"rightx:a2,righty:a3,\");\n\n    SDL_GameControllerAddMapping(\"0000bce1576969204e756e6368756b00,\"\n                                 \"Wii Remote + Nunchuk,\"\n                                 \"crc:bce1,\"\n                                 \"a:b0,b:b1,\"\n                                 \"x:b6,y:b7,\"\n                                 \"leftshoulder:b2,rightshoulder:b3,\"\n                                 \"lefttrigger:b8,righttrigger:b9,\"\n                                 \"back:b11,start:b10,\"\n                                 \"dpdown:b15,dpleft:b12,dpright:b14,dpup:b13,\"\n                                 \"leftstick:b4,rightstick:b5,\"\n                                 \"leftx:a0,lefty:a1,\"\n                                 \"rightx:a2,righty:a3,\");\n#endif\n\n#ifdef __SWITCH__\n    // Sets the correct mapping for Switch controllers\n    SDL_GameControllerAddMapping(\"000038f853776974636820436f6e7400,\"\n                                 \"Switch Controller,\"\n                                 \"a:b0,b:b1,x:b2,y:b3,\"\n                                 \"back:b11,start:b10,\"\n                                 \"dpdown:b15,dpleft:b12,dpright:b14,dpup:b13,\"\n                                 \"leftshoulder:b6,rightshoulder:b7,\"\n                                 \"lefttrigger:b8,righttrigger:b9,\"\n                                 \"leftstick:b4,rightstick:b5,\"\n                                 \"leftx:a0,lefty:a1,\"\n                                 \"rightx:a2,righty:a3,\");\n#endif\n\n    int num = SDL_NumJoysticks();\n\n    for(int i = 0; i < num; ++i)\n        this->OpenJoystick(i);\n}\n\nInputMethodType_Joystick::~InputMethodType_Joystick()\n{\n    this->m_lastProfileByGUID.clear();\n\n    // A single pass would be much more efficient,\n    // but I want to avoid duplicating code\n    // and this only happens on program exit.\n    std::vector<int> open_joystick_ids;\n\n    for(const std::pair<const int, JoystickDevices*>& p : this->m_availableJoysticks)\n        open_joystick_ids.push_back(p.first);\n\n    for(int joystick_id : open_joystick_ids)\n        this->CloseJoystick(joystick_id);\n}\n\nconst std::string& InputMethodType_Joystick::LocalName() const\n{\n    return g_controlsStrings.nameGamepad;\n}\n\nbool InputMethodType_Joystick::TestProfileType(InputMethodProfile* profile)\n{\n    return (bool)dynamic_cast<InputMethodProfile_Joystick*>(profile);\n}\n\nbool InputMethodType_Joystick::RumbleSupported()\n{\n    return true;\n}\n\nbool InputMethodType_Joystick::PowerStatusSupported()\n{\n    return true;\n}\n\nvoid InputMethodType_Joystick::UpdateControlsPre() {}\nvoid InputMethodType_Joystick::UpdateControlsPost() {}\n\nstatic std::string s_GetJoystickName(SDL_Joystick* joy)\n{\n    // initialize with SDL name\n    const char* sdl_name = SDL_JoystickName(joy);\n    std::string raw_name = sdl_name ? sdl_name : \"\";\n\n    // split into words\n    Strings::List words = Strings::split(raw_name, ' ');\n\n    // reconstruct, while abbreviating certain words\n    std::string name = \"\";\n\n    size_t index = 0;\n    for(std::string& s : words)\n    {\n        if(s.empty())\n            continue;\n\n        // abbreviated words\n        if(SDL_strcasecmp(s.c_str(), \"generic\") == 0)\n            s.resize(3);\n        else if(SDL_strcasecmp(s.c_str(), \"nintendo\") == 0)\n            s.resize(1);\n        else if(SDL_strcasecmp(s.c_str(), \"microsoft\") == 0)\n            s.resize(1);\n        else if(SDL_strcasecmp(s.c_str(), \"sony\") == 0)\n            s.resize(1);\n        else if(SDL_strcasecmp(s.c_str(), \"playstation\") == 0)\n            s = \"PS\";\n        else if(SDL_strcasecmp(s.c_str(), \"controller\") == 0)\n            s.resize(1);\n        else if(SDL_strcasecmp(s.c_str(), \"joystick\") == 0)\n            s.resize(3);\n        else if(SDL_strcasecmp(s.c_str(), \"standard\") == 0)\n            s = \"Std\";\n        // skip connection methods\n        else if(SDL_strcasecmp(s.c_str(), \"usb\") == 0)\n            continue;\n        else if(SDL_strcasecmp(s.c_str(), \"bluetooth\") == 0)\n            continue;\n        else if(SDL_strcasecmp(s.c_str(), \"hid\") == 0)\n            continue;\n\n        // skip duplicate words post conversion\n        for(size_t i = 0; i < index; ++i)\n        {\n            if(SDL_strcasecmp(s.c_str(), words[i].c_str()) == 0)\n                continue;\n        }\n        index++;\n\n        // stop early if reconstructed string would become too long\n        if(!name.empty() && name.size() + s.size() > 10)\n            break;\n\n        // add to reconstructed string\n        name += s;\n        name += \" \";\n    }\n\n    // strip trailing spaces\n    while(!name.empty() && name[name.size() - 1] == ' ')\n        name.resize(name.size() - 1);\n\n    // cutoff word if needed\n    if(name.size() > 10)\n        name.resize(10);\n\n    // fix empty names\n    if(name.empty())\n        name = \"Joystick\";\n\n    pLogDebug(\"Shortened joystick name: raw name was [%s], short name is [%s]\", raw_name.c_str(), name.c_str());\n\n    return name;\n}\n\nInputMethod* InputMethodType_Joystick::Poll(const std::vector<InputMethod*>& active_methods) noexcept\n{\n    JoystickDevices* active_joystick = nullptr;\n\n    for(std::pair<const int, JoystickDevices*>& p : this->m_availableJoysticks)\n    {\n        SDL_Joystick* joy = p.second->joy;\n        SDL_GameController* ctrl = p.second->ctrl;\n        bool duplicate = false;\n\n        for(InputMethod* method : active_methods)\n        {\n            InputMethod_Joystick* m = dynamic_cast<InputMethod_Joystick*>(method);\n\n            if(!m)\n                continue;\n\n            if(m->m_devices == p.second)\n            {\n                duplicate = true;\n                break;\n            }\n        }\n\n        // don't want any duplicates!\n        if(duplicate)\n            continue;\n\n        KM_Key k;\n        if(ctrl)\n            s_bindControllerKey(ctrl, k);\n        else\n            s_bindJoystickKey(joy, k);\n\n        // can_poll is set as false on joystick initialization and unbinding\n        if(k.type == KM_Key::NoControl)\n            p.second->can_poll = true;\n        else if(p.second->can_poll)\n        {\n            active_joystick = p.second;\n            break;\n        }\n    }\n\n    // if didn't find any joystick return false right now\n    if(!active_joystick)\n        return nullptr;\n\n    // we're going to create a new joystick!\n    InputMethod_Joystick* method = new(std::nothrow) InputMethod_Joystick;\n\n    // alloc failed :(\n    if(!method)\n        return nullptr;\n\n    method->Type = this;\n    method->Name = s_GetJoystickName(active_joystick->joy);\n    method->m_devices = active_joystick;\n\n    // In this section, we find the default profile for this controller/player combination...!\n\n    // first, figure out what the player index will be\n    int my_index = 0;\n\n    for(const InputMethod* m : active_methods)\n    {\n        if(!m)\n            break;\n\n        my_index ++;\n    }\n\n    // 1. cleverly look for profile by GUID and player...\n    std::unordered_map<std::string, InputMethodProfile*>::iterator found\n        = this->m_lastProfileByGUID.find(std::to_string(my_index + 1) + \"-\" + active_joystick->guid);\n\n    // ...then by just GUID.\n    if(found == this->m_lastProfileByGUID.end())\n        found = this->m_lastProfileByGUID.find(active_joystick->guid);\n\n    // and finally by middle 16 chars of GUID\n    if(found == this->m_lastProfileByGUID.end() && active_joystick->guid.size() == 32)\n    {\n        for(found = this->m_lastProfileByGUID.begin(); found != this->m_lastProfileByGUID.end(); ++found)\n        {\n            if(found->first.size() == 32 && memcmp(found->first.c_str() + 8, active_joystick->guid.c_str() + 8, 16) == 0)\n                break;\n        }\n    }\n\n    if(found != this->m_lastProfileByGUID.end())\n        method->Profile = found->second;\n\n#if 0\n    // Inappropriate method that causes different types of controllers to share a profile\n\n    // 2. find first profile appropriate to the controller-ness (controller vs joystick) of the controller\n    if(!method->Profile)\n    {\n        for(InputMethodProfile* p_ : this->m_profiles)\n        {\n            InputMethodProfile_Joystick* p = dynamic_cast<InputMethodProfile_Joystick*>(p_);\n\n            if(!p)\n                continue;\n\n            // if((active_joystick->ctrl && p->m_controllerProfile) || (!active_joystick->ctrl && !p->m_controllerProfile))\n            if((active_joystick->ctrl != nullptr) == p->m_controllerProfile)\n            {\n                method->Profile = p_;\n                break;\n            }\n        }\n\n        // the following block (2) was previously a backup for this block\n\n    }\n#endif\n\n    // 2. make appropriate new profile (note that allocs could fail, that will be cleaned up later)\n    if(!method->Profile)\n    {\n        if(active_joystick->ctrl)\n        {\n            method->Profile = this->AddProfile();\n\n            // Detect whether alt controls are appropriate here given a hardcoded list of GUIDs.\n            if(s_AltControlsDefault(active_joystick->guid))\n            {\n                pLogInfo(\"New controller profile will use alt menu controls layout [Controller GUID: %s]\", active_joystick->guid.c_str());\n\n                auto* p = dynamic_cast<InputMethodProfile_Joystick*>(method->Profile);\n                if(p)\n                    p->InitAsController(InputMethodProfile_Joystick::INIT_AS_ALT);\n            }\n            else if(s_WiiRemoteControlsDefault(active_joystick->guid))\n            {\n                pLogInfo(\"New controller profile will use Wii Remote controls layout [Controller GUID: %s]\", active_joystick->guid.c_str());\n\n                auto* p = dynamic_cast<InputMethodProfile_Joystick*>(method->Profile);\n                if(p)\n                    p->InitAsController(InputMethodProfile_Joystick::INIT_AS_WII_REMOTE);\n            }\n            else if(s_WiiRemoteNunchackControlsDefault(active_joystick->guid))\n            {\n                pLogInfo(\"New controller profile will use Wii Remote with Nunchuk controls layout [Controller GUID: %s]\", active_joystick->guid.c_str());\n\n                auto* p = dynamic_cast<InputMethodProfile_Joystick*>(method->Profile);\n                if(p)\n                    p->InitAsController(InputMethodProfile_Joystick::INIT_AS_WII_REMOTE_WITH_NUNCHACK);\n            }\n            else\n                pLogDebug(\"New controller profile will use standard menu controls layout [Controller GUID: %s]\", active_joystick->guid.c_str());\n        }\n        else\n            method->Profile = this->AddOldJoystickProfile();\n\n        if(method->Profile)\n            method->Profile->Name = method->Name;\n    }\n\n    return (InputMethod*)method;\n}\n\n\n/*-----------------------*\\\n|| CUSTOM METHODS        ||\n\\*-----------------------*/\n\nKM_Key InputMethodType_Joystick::PollJoystickKeyAll()\n{\n    KM_Key k;\n\n    for(std::pair<const int, JoystickDevices*>& p : this->m_availableJoysticks)\n    {\n        if(p.second->joy)\n            s_bindJoystickKey(p.second->joy, k);\n\n        if(k.type != KM_Key::NoControl)\n            break;\n    }\n\n    return k;\n}\n\nKM_Key InputMethodType_Joystick::PollControllerKeyAll()\n{\n    KM_Key k;\n\n    for(std::pair<const int, JoystickDevices*>& p : this->m_availableJoysticks)\n    {\n        if(p.second->ctrl)\n            s_bindControllerKey(p.second->ctrl, k);\n\n        if(k.type != KM_Key::NoControl)\n            break;\n    }\n\n    return k;\n}\n\n\n/*-----------------------*\\\n|| CUSTOM METHODS        ||\n\\*-----------------------*/\n\nbool InputMethodType_Joystick::OpenJoystick(int joystick_index)\n{\n    pLogDebug(\"Opening joystick id %d...\", joystick_index);\n    SDL_Joystick* joy = SDL_JoystickOpen(joystick_index);\n\n    if(!joy)\n    {\n        pLogDebug(\"  could not open.\");\n        return false;\n    }\n\n    pLogDebug(\"  successfully opened!\");\n    pLogDebug(\"  Name: %s\", SDL_JoystickName(joy));\n\n    std::string guid;\n    // explicitly scoping this buffer\n    {\n        SDL_JoystickGUID guid_bytes = SDL_JoystickGetGUID(joy);\n        char guid_chars[35];\n        SDL_JoystickGetGUIDString(guid_bytes, guid_chars, 35);\n        pLogDebug(\"  GUID: %s\", guid_chars);\n        guid = guid_chars;\n    }\n    pLogDebug(\"  Number of Axes: %d\", SDL_JoystickNumAxes(joy));\n    pLogDebug(\"  Number of Buttons: %d\", SDL_JoystickNumButtons(joy));\n    pLogDebug(\"  Number of Balls: %d\", SDL_JoystickNumBalls(joy));\n\n    KM_Key key;\n    s_bindJoystickKey(joy, key);\n\n    if(key.type == KM_Key::NoControl)\n        pLogDebug(\"  No buttons held\");\n    else if(key.type < 5)\n    {\n        const char* helper[] = {\"Ax\", \"Bx\", \"By\", \"Hat\", \"But\"};\n        pLogDebug(\"  Button held: %s %d %d\", helper[key.type], key.id, key.val);\n    }\n\n    JoystickDevices* devices = new(std::nothrow) JoystickDevices;\n\n    if(!devices)\n    {\n        SDL_JoystickClose(joy);\n        pLogDebug(\"  could not allocate devices struct (out of memory).\");\n        return false;\n    }\n\n    devices->joy = joy;\n    devices->guid = std::move(guid);\n\n    if(SDL_IsGameController(joystick_index))\n    {\n        pLogDebug(\"  claims to be a game controller...\");\n        devices->ctrl = SDL_GameControllerOpen(joystick_index);\n\n        if(devices->ctrl)\n        {\n            pLogDebug(\"    successfully opened game controller!\");\n\n            s_bindControllerKey(devices->ctrl, key);\n\n            if(key.type == KM_Key::NoControl)\n                pLogDebug(\"    No buttons held\");\n            else\n                pLogDebug(\"    Button held: %s\", s_nameButton(key));\n        }\n        else\n            pLogDebug(\"    could not open.\");\n    }\n    else\n        pLogDebug(\"  is not a game controller.\");\n\n    if(SDL_JoystickIsHaptic(joy))\n    {\n        pLogDebug(\"  claims to support haptic...\");\n        devices->haptic = SDL_HapticOpenFromJoystick(joy);\n\n        if(!devices->haptic)\n        {\n            pLogDebug(\"    could not open haptic!\");\n        }\n        else if(!SDL_HapticRumbleSupported(devices->haptic) || SDL_HapticRumbleInit(devices->haptic) != 0)\n        {\n            pLogDebug(\"    could not init haptic rumble!\");\n            SDL_HapticClose(devices->haptic);\n            devices->haptic = nullptr;\n        }\n        else\n        {\n            pLogDebug(\"    successfully initialized haptic rumble!\");\n        }\n    }\n    else\n        pLogDebug(\"  does not support haptic.\");\n\n    int id = SDL_JoystickInstanceID(joy);\n    pLogDebug(\"  received ID %d. adding to poll list!\", id);\n\n    this->m_availableJoysticks.emplace(id, devices);\n\n    return true;\n}\n\nbool InputMethodType_Joystick::CloseJoystick(int instance_id)\n{\n    pLogDebug(\"joystick with ID %d removed.\", instance_id);\n    auto found = this->m_availableJoysticks.find(instance_id);\n\n    if(found == this->m_availableJoysticks.end())\n    {\n        pLogDebug(\"  could not find!\");\n        return false;\n    }\n\n    JoystickDevices* devices = found->second;\n\n    for(InputMethod* method : g_InputMethods)\n    {\n        auto* m = dynamic_cast<InputMethod_Joystick*>(method);\n\n        if(!m)\n            continue;\n\n        if(m->m_devices == devices)\n        {\n            pLogDebug(\"  unset m_devices of InputMethod '%s' using Profile '%s'.\", m->Name.c_str(), m->Profile->Name.c_str());\n            m->m_devices = nullptr;\n        }\n    }\n\n    if(devices->haptic)\n        SDL_HapticClose(devices->haptic);\n\n    if(devices->ctrl)\n        SDL_GameControllerClose(devices->ctrl);\n\n    SDL_JoystickClose(devices->joy);\n    delete devices;\n\n    this->m_availableJoysticks.erase(found);\n\n    return true;\n}\n\nInputMethodProfile* InputMethodType_Joystick::AddOldJoystickProfile()\n{\n    InputMethodProfile* p_ = this->AddProfile();\n    auto* p = dynamic_cast<InputMethodProfile_Joystick*>(p_);\n\n    if(!p)\n        return nullptr;\n\n    p->InitAsJoystick();\n    p->Name = g_controlsStrings.nameOldJoy + \" \" + std::to_string(this->m_profiles.size());\n\n    return p_;\n}\n\n/*-----------------------*\\\n|| OPTIONAL METHODS      ||\n\\*-----------------------*/\n\n// optional function allowing developer to associate device information with profile, etc\n// if developer wants to forbid assignment, return false\nbool InputMethodType_Joystick::SetProfile_Custom(InputMethod* method, int player_no, InputMethodProfile* profile, const std::vector<InputMethod*>& active_methods)\n{\n    (void)active_methods;\n\n    auto* m = dynamic_cast<InputMethod_Joystick*>(method);\n    auto* p = dynamic_cast<InputMethodProfile_Joystick*>(profile);\n\n    if(!m || !p || !m->m_devices)\n        return false;\n\n    // only allow controllerProfiles for controllers, joystick profiles for old joysticks\n    // C++ has no proper logical XOR operator, so two lines\n    if(p->m_controllerProfile && !m->m_devices->ctrl)\n        return false;\n\n    if(!p->m_controllerProfile && m->m_devices->ctrl)\n        return false;\n\n    // set in map!\n    this->m_lastProfileByGUID[std::to_string(player_no + 1) + \"-\" + m->m_devices->guid] = profile;\n    this->m_lastProfileByGUID[m->m_devices->guid] = profile;\n\n    pLogDebug(\"Setting default controller for GUID '%s' and player %d to %s.\",\n              m->m_devices->guid.c_str(), player_no, profile->Name.c_str());\n\n    return true;\n}\n\n// unregisters any references to the profile before final deallocation\n// returns false to prevent deletion if this is impossible\nbool InputMethodType_Joystick::DeleteProfile_Custom(InputMethodProfile* profile, const std::vector<InputMethod*>& active_methods)\n{\n    UNUSED(active_methods);\n\n    for(auto it = m_lastProfileByGUID.begin(); it != m_lastProfileByGUID.end();)\n    {\n        if(it->second == profile)\n        {\n            pLogDebug(\"Unsetting default controller for GUID '%s'.\", it->first.c_str());\n            it = m_lastProfileByGUID.erase(it);\n        }\n        else\n            ++it;\n    }\n\n    return true;\n}\n\n// Optional function allowing developer to consume an SDL event (on SDL clients)\n//     usually used for hotkeys or for connect/disconnect events.\n// Called (1) in order of InputMethodTypes, then (2) in order of InputMethods\n// Returns true if event is consumed, false if other InputMethodTypes and InputMethods\n//     should receive it.\nbool InputMethodType_Joystick::ConsumeEvent(const SDL_Event* ev)\n{\n    if(ev->type == SDL_JOYDEVICEADDED)\n    {\n        this->OpenJoystick(ev->jdevice.which);\n        return true;\n    }\n    else if(ev->type == SDL_JOYDEVICEREMOVED)\n    {\n        this->CloseJoystick(ev->jdevice.which);\n        return true;\n    }\n\n    return false;\n}\n\n// How many per-type special options are there?\nsize_t InputMethodType_Joystick::GetOptionCount()\n{\n    return 1;\n}\n\n// Methods to manage per-profile options\n// It is guaranteed that none of these will be called if\n// GetOptionCount() returns 0.\n// get a char* describing the option\nconst char* InputMethodType_Joystick::GetOptionName(size_t i)\n{\n    if(i == 0)\n        return g_controlsStrings.phraseNewProfOldJoy.c_str();\n    else\n        return nullptr;\n}\n\n// get a char* describing the current option value\n// must be allocated in static or instance memory\n// WILL NOT be freed\nconst char* InputMethodType_Joystick::GetOptionValue(size_t i)\n{\n    (void)i;\n    return nullptr;\n}\n\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodType_Joystick::OptionChange(size_t i)\n{\n    if(i == 0 && this->AddOldJoystickProfile())\n        return true;\n\n    return false;\n}\n\nvoid InputMethodType_Joystick::SaveConfig_Custom(IniProcessing* ctl)\n{\n    std::string name = \"last-profile-\";\n    int uuid_begin = (int)name.size();\n\n    // clear any old default controller profiles (keeping a copy of the set of keys in a stack-allocated vector)\n    std::vector<std::string> keys_to_clear = ctl->allKeys();\n    for(const std::string& key : keys_to_clear)\n    {\n        // delete any key with the prefix \"last-profile-\"\n        if(SDL_strncmp(key.c_str(), name.c_str(), name.size()) == 0)\n            ctl->clearValue(key.c_str());\n    }\n\n    // set all default controller profiles\n    for(auto it = m_lastProfileByGUID.begin(); it != m_lastProfileByGUID.end(); ++it)\n    {\n        auto loc = std::find(this->m_profiles.begin(), this->m_profiles.end(), it->second);\n        size_t index = loc - this->m_profiles.begin();\n\n        if(index != this->m_profiles.size())\n            ctl->setValue(name.replace(uuid_begin, std::string::npos, it->first).c_str(), index);\n    }\n}\n\nvoid InputMethodType_Joystick::LoadConfig_Custom(IniProcessing* ctl)\n{\n    // load all default controller profiles\n    std::vector<std::string> keys = ctl->allKeys();\n    std::string keyNeed = \"last-profile-\";\n\n    for(std::string& k : keys)\n    {\n        std::string::size_type r = k.find(keyNeed);\n\n        if(/*r != std::string::npos &&*/ r == 0)\n        {\n            std::string guid = k.substr(13); // length of \"last-profile-\"\n            int index;\n            ctl->read(k.c_str(), index, -1);\n\n            if(index >= 0 && index < (int)this->m_profiles.size() && this->m_profiles[index])\n            {\n                this->m_lastProfileByGUID[guid] = this->m_profiles[index];\n                pLogDebug(\"Set default profile for '%s' to '%s'.\", guid.c_str(), this->m_profiles[index]->Name.c_str());\n            }\n        }\n    }\n\n    ctl->endGroup();\n\n    ctl->beginGroup(this->Name);\n}\n\n} // namespace Controls\n"
  },
  {
    "path": "src/control/joystick.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef JOYSTICK_H\n#define JOYSTICK_H\n\n#include \"../controls.h\"\n\n#include <string>\n#include <set>\n#include <unordered_map>\n#include <SDL2/SDL_timer.h>\n\ntypedef struct _SDL_Joystick SDL_Joystick;\ntypedef struct _SDL_GameController SDL_GameController;\ntypedef struct _SDL_Haptic SDL_Haptic;\n\nnamespace Controls\n{\n\nstruct JoystickDevices\n{\n    SDL_Joystick *joy = nullptr;\n    SDL_GameController *ctrl = nullptr;\n    SDL_Haptic *haptic = nullptr;\n\n    bool can_poll = false;\n    std::string guid;\n};\n\nstruct KM_Key\n{\n    enum CtrlTypes\n    {\n        NoControl = -1,\n        JoyAxis = 0,\n        JoyBallX,\n        JoyBallY,\n        JoyHat,\n        JoyButton,\n        CtrlButton,\n        CtrlAxis\n    };\n\n    // SDL_Joystick control or SDL_GameController control, depending on context\n    int val = -1;\n    int id = -1;\n    int type = -1;\n\n    inline bool operator==(const KM_Key &o)\n    {\n        return o.id == id && o.val == val && o.type == type;\n    }\n\n    inline void assign(int i_type, int i_id, int i_val)\n    {\n        this->type = i_type;\n        this->id = i_id;\n        this->val = i_val;\n    }\n};\n\nclass InputMethod_Joystick : public InputMethod\n{\nprivate:\n    uint32_t m_last_power_check = -10000;\n    StatusInfo m_recent_status;\n\npublic:\n    JoystickDevices *m_devices;\n\n    using InputMethod::Type;\n    using InputMethod::Profile;\n\n    ~InputMethod_Joystick();\n\n    // Update functions that set player controls (and editor controls)\n    // based on current device input. Return false if device lost.\n    bool Update(int player, Controls_t &c, CursorControls_t &m, EditorControls_t &e, HotkeysPressed_t &h);\n\n    void Rumble(int ms, float strength);\n\n    StatusInfo GetStatus();\n};\n\nclass InputMethodProfile_Joystick : public InputMethodProfile\n{\nprivate:\n    bool m_canPoll = false;\n\npublic:\n    using InputMethodProfile::Name;\n    using InputMethodProfile::Type;\n\n    bool m_controllerProfile = false;\n\n    // current settings\n    bool m_simple_editor = true;\n\n    // primary keys (also controller keys in legacy mode)\n    KM_Key m_keys[PlayerControls::n_buttons];\n    // secondary keys (also joystick keys in legacy mode)\n    KM_Key m_keys2[PlayerControls::n_buttons];\n\n    // cursor keys\n    KM_Key m_cursor_keys[CursorControls::n_buttons];\n    KM_Key m_cursor_keys2[CursorControls::n_buttons];\n\n    // editor keys\n    KM_Key m_editor_keys[EditorControls::n_buttons];\n    KM_Key m_editor_keys2[EditorControls::n_buttons];\n\n    // hotkeys\n    KM_Key m_hotkeys[Hotkeys::n_buttons];\n    KM_Key m_hotkeys2[Hotkeys::n_buttons];\n\n    InputMethodProfile_Joystick();\n\n    enum InitAs\n    {\n        INIT_AS_DEFAULT = 0,\n        INIT_AS_ALT,\n        INIT_AS_WII_REMOTE,\n        INIT_AS_WII_REMOTE_WITH_NUNCHACK,\n    };\n\n    void InitAsJoystick();\n    void InitAsController(InitAs init_as);\n    void SaveConfig_Legacy(IniProcessing *ctl);\n    void LoadConfig_Legacy(IniProcessing *ctl);\n\n    // Polls a new (secondary) device button for the i'th player button\n    // Returns true on success and false if no button pressed\n    // Never allows two player buttons to bind to the same device button\n    bool PollPrimaryButton(ControlsClass c, size_t i);\n    bool PollSecondaryButton(ControlsClass c, size_t i);\n\n    // Deletes a primary button for the i'th button of class c (only called for non-Player buttons)\n    bool DeletePrimaryButton(ControlsClass c, size_t i);\n\n    // Deletes a secondary device button for the i'th button of class c\n    bool DeleteSecondaryButton(ControlsClass c, size_t i);\n\n    // Gets strings for the device buttons currently used for the i'th button of class c\n    const char *NamePrimaryButton(ControlsClass c, size_t i);\n    const char *NameSecondaryButton(ControlsClass c, size_t i);\n\n    // one can assume that the IniProcessing* is already in the correct group\n    void SaveConfig(IniProcessing *ctl);\n    void LoadConfig(IniProcessing *ctl);\n\npublic:\n    // How many per-type special options are there?\n    size_t GetOptionCount_Custom();\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount_Custom() returns 0.\n    // get a char* describing the option\n    const char *GetOptionName_Custom(size_t i);\n    // get a char* describing the current option value\n    // must be allocated in static or instance memory\n    // WILL NOT be freed\n    const char *GetOptionValue_Custom(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange_Custom(size_t i);\n    // called when left is pressed\n    bool OptionRotateLeft_Custom(size_t i);\n    // called when right is pressed\n    bool OptionRotateRight_Custom(size_t i);\n};\n\nclass InputMethodType_Joystick : public InputMethodType\n{\nprivate:\n    std::unordered_map<int, JoystickDevices *> m_availableJoysticks;\n    std::unordered_map<std::string, InputMethodProfile *> m_lastProfileByGUID;\n\n    InputMethodProfile *AllocateProfile() noexcept override;\n\n    /*-----------------------*\\\n    || CUSTOM METHODS        ||\n    \\*-----------------------*/\n    bool OpenJoystick(int joystick_index);\n    bool CloseJoystick(int instance_id);\n\npublic:\n    using InputMethodType::Name;\n    using InputMethodType::m_profiles;\n\n    InputMethodType_Joystick();\n    ~InputMethodType_Joystick();\n\n    const std::string& LocalName() const override;\n\n    bool TestProfileType(InputMethodProfile *profile) override;\n    bool RumbleSupported() override;\n    bool PowerStatusSupported() override;\n\n    void UpdateControlsPre() override;\n    void UpdateControlsPost() override;\n\n    // null if no input method is ready\n    // allocates the new InputMethod on the heap\n    InputMethod *Poll(const std::vector<InputMethod *> &active_methods) noexcept override;\n\n    /*-----------------------*\\\n    || CUSTOM METHODS        ||\n    \\*-----------------------*/\n    KM_Key PollJoystickKeyAll();\n    KM_Key PollControllerKeyAll();\n    InputMethodProfile *AddOldJoystickProfile();\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\nprotected:\n    // optional function allowing developer to associate device information with profile, etc\n    // if developer wants to forbid assignment, return false\n    bool SetProfile_Custom(InputMethod *method, int player_no, InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods) override;\n    // unregisters any references to the profile before final deallocation\n    // returns false to prevent deletion if this is impossible\n    bool DeleteProfile_Custom(InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods) override;\npublic:\n    bool ConsumeEvent(const SDL_Event *ev) override;\n\npublic:\n    // How many per-type special options are there?\n    size_t GetOptionCount() override;\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount() returns 0.\n    // get a char* describing the option\n    const char *GetOptionName(size_t i) override;\n    // get a char* describing the current option value\n    // must be allocated in static or instance memory\n    // WILL NOT be freed\n    const char *GetOptionValue(size_t i) override;\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange(size_t i) override;\n\nprotected:\n    void SaveConfig_Custom(IniProcessing *ctl) override;\n    void LoadConfig_Custom(IniProcessing *ctl) override;\n\n};\n\n} // namespace Controls\n\n#endif // #ifndef JOYSTICK_H\n"
  },
  {
    "path": "src/control/keyboard.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <SDL2/SDL_scancode.h>\n#include <SDL2/SDL_power.h>\n#include <SDL2/SDL_keyboard.h>\n#include <SDL2/SDL_mouse.h>\n#include <SDL2/SDL_events.h>\n#include \"sdl_proxy/sdl_timer.h\"\n\n#include \"../controls.h\"\n#include \"../globals.h\"\n#include \"keyboard.h\"\n#include \"../main/screen_textentry.h\"\n#include \"../game_main.h\"\n\n#include \"core/render.h\"\n#include \"core/window.h\"\n#include \"core/power.h\"\n\n#include \"main/cheat_code.h\"\n#include \"main/menu_main.h\"\n\n#include \"control/controls_strings.h\"\n\n#include <Logger/logger.h>\n\nnamespace Controls\n{\n\nbool g_cancelDoubleClick = false;\n\n// helper functions\n\n\n/*====================================================*\\\n|| implementation for InputMethod_Keyboard            ||\n\\*====================================================*/\n\nInputMethod_Keyboard::~InputMethod_Keyboard()\n{\n    InputMethodType_Keyboard* t = dynamic_cast<InputMethodType_Keyboard*>(this->Type);\n\n    if(!t)\n        return;\n\n    t->m_numKeyboards --;\n}\n\n// Update functions that set player controls (and editor controls)\n// based on current device input. Return false if device lost.\nbool InputMethod_Keyboard::Update(int player, Controls_t& c, CursorControls_t& m, EditorControls_t& e, HotkeysPressed_t& h)\n{\n    InputMethodType_Keyboard* k = dynamic_cast<InputMethodType_Keyboard*>(this->Type);\n    InputMethodProfile_Keyboard* p = dynamic_cast<InputMethodProfile_Keyboard*>(this->Profile);\n\n    if(!k || !p)\n        return false;\n\n    if(k->m_directText && GamePaused == PauseCode::TextEntry)\n        return true;\n\n    // consume parent's precise scrolling\n    if(k->m_scroll_x_precise < 0)\n        e.ScrollLeft -= num_t::from_double(k->m_scroll_x_precise) * 32;\n    else if(k->m_scroll_x_precise > 0)\n        e.ScrollRight += num_t::from_double(k->m_scroll_x_precise) * 32;\n\n    if(k->m_scroll_y_precise < 0)\n        e.ScrollUp -= num_t::from_double(k->m_scroll_y_precise) * 32;\n    else if(k->m_scroll_y_precise > 0)\n        e.ScrollDown += num_t::from_double(k->m_scroll_y_precise) * 32;\n\n    k->m_scroll_x_precise = 0;\n    k->m_scroll_y_precise = 0;\n\n    // check for alt press (disables some keys)\n    bool altPressed = (k->m_keyboardState[SDL_SCANCODE_LALT] ||\n                       k->m_keyboardState[SDL_SCANCODE_RALT] ||\n                       k->m_keyboardState[SDL_SCANCODE_LCTRL] ||\n                       k->m_keyboardState[SDL_SCANCODE_RCTRL]);\n\n    for(int a = 0; a < 4; a++)\n    {\n        int* keys;\n        int* keys2;\n        size_t key_start;\n        size_t key_max;\n        bool activate = false;\n\n        if(a == 0)\n        {\n            keys = p->m_keys;\n            keys2 = p->m_keys2;\n            key_start = 0;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = p->m_cursor_keys2;\n            key_start = 4;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = p->m_editor_keys;\n            keys2 = p->m_editor_keys2;\n            key_start = 4;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = p->m_hotkeys;\n            keys2 = p->m_hotkeys2;\n            key_start = 0;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = key_start; i < key_max; i++)\n        {\n            int key;\n\n            if(keys)\n                key = keys[i];\n            else\n                key = null_key;\n\n            int key2 = keys2[i];\n\n            if(altPressed && (key == SDL_SCANCODE_F || key == SDL_SCANCODE_RETURN))\n                key = null_key;\n\n            if(altPressed && (key2 == SDL_SCANCODE_F || key2 == SDL_SCANCODE_RETURN))\n                key2 = null_key;\n\n            bool* b;\n\n            if(a == 0)\n            {\n                b = &PlayerControls::GetButton(c, i);\n                *b = false;\n            }\n            else if(a == 1)\n            {\n                b = &CursorControls::GetButton(m, i);\n            }\n            else if(a == 2)\n            {\n                b = &EditorControls::GetButton(e, i);\n            }\n            else\n            {\n                b = &activate;\n                *b = false;\n            }\n\n            if(key != null_key && k->m_keyboardState[key] != 0)\n                *b = true;\n            else if(key2 != null_key && k->m_keyboardState[key2] != 0)\n                *b = true;\n\n            if(a == 3 && *b)\n                h[i] = player;\n        }\n    }\n\n    num_t* const scroll[4] = {&e.ScrollUp, &e.ScrollDown, &e.ScrollLeft, &e.ScrollRight};\n\n    for(int i = 0; i < 4; i++)\n    {\n        int key = p->m_editor_keys[i];\n        int key2 = p->m_editor_keys2[i];\n\n        if(key != null_key && k->m_keyboardState[key] != 0)\n            *scroll[i] += 10;\n        else if(key2 != null_key && k->m_keyboardState[key2] != 0)\n            *scroll[i] += 10;\n    }\n\n    bool cursor[4];\n\n    for(int i = 0; i < 4; i++)\n    {\n        int key = p->m_cursor_keys2[i];\n        cursor[i] = (key != null_key && k->m_keyboardState[key]);\n    }\n\n    // Cursor control (UDLR)\n    if(cursor[0] || cursor[1] || cursor[2] || cursor[3])\n    {\n        bool edge_scroll = LevelEditor && !MagicHand;\n\n        if(m.X < 0)\n            m.X = XRender::TargetW / 2;\n\n        if(m.Y < 0)\n            m.Y = XRender::TargetH / 2;\n\n        if(cursor[3])\n            m.X += 16;\n\n        if(cursor[2])\n            m.X -= 16;\n\n        if(cursor[1])\n            m.Y += 16;\n\n        if(cursor[0])\n            m.Y -= 16;\n\n        if(m.X < 0)\n        {\n            if(edge_scroll)\n                e.ScrollLeft += -m.X;\n\n            m.X = 0;\n        }\n        else if(m.X >= XRender::TargetW)\n        {\n            if(edge_scroll)\n                e.ScrollRight += m.X - (XRender::TargetW - 1);\n\n            m.X = XRender::TargetW - 1;\n        }\n\n        if(m.Y < 0)\n        {\n            if(edge_scroll)\n                e.ScrollUp += -m.Y;\n\n            m.Y = 0;\n        }\n        else if(m.Y >= XRender::TargetH)\n        {\n            if(edge_scroll)\n                e.ScrollDown += m.Y - (XRender::TargetH - 1);\n\n            m.Y = XRender::TargetH - 1;\n        }\n\n        m.Move = true;\n    }\n\n    // one may uncomment this to quickly simulate controller disconnection for debugging purposes\n    // if(c.Up && c.Down)\n    //     return false;\n\n    return true;\n}\n\nvoid InputMethod_Keyboard::Rumble(int ms, float strength)\n{\n    UNUSED(ms);\n    UNUSED(strength);\n}\n\nStatusInfo InputMethod_Keyboard::GetStatus()\n{\n    return XPower::devicePowerStatus();\n}\n\n/*====================================================*\\\n|| implementation for InputMethodProfile_Keyboard     ||\n\\*====================================================*/\n\n// the job of this function is to initialize the class in a consistent state\nInputMethodProfile_Keyboard::InputMethodProfile_Keyboard()\n{\n    for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n    {\n        this->m_keys[i] = null_key;\n        this->m_keys2[i] = null_key;\n    }\n\n    for(size_t i = 0; i < CursorControls::n_buttons; i++)\n        this->m_cursor_keys2[i] = null_key;\n\n    for(size_t i = 0; i < EditorControls::n_buttons; i++)\n    {\n        this->m_editor_keys[i] = null_key;\n        this->m_editor_keys2[i] = null_key;\n    }\n\n    for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n    {\n        this->m_hotkeys[i] = null_key;\n        this->m_hotkeys2[i] = null_key;\n    }\n\n    this->m_keys[PlayerControls::Buttons::Up] = SDL_SCANCODE_UP;\n    this->m_keys[PlayerControls::Buttons::Down] = SDL_SCANCODE_DOWN;\n    this->m_keys[PlayerControls::Buttons::Left] = SDL_SCANCODE_LEFT;\n    this->m_keys[PlayerControls::Buttons::Right] = SDL_SCANCODE_RIGHT;\n    this->m_keys[PlayerControls::Buttons::Jump] = SDL_SCANCODE_Z;\n    this->m_keys[PlayerControls::Buttons::AltJump] = SDL_SCANCODE_A;\n    this->m_keys[PlayerControls::Buttons::Run] = SDL_SCANCODE_X;\n    this->m_keys[PlayerControls::Buttons::AltRun] = SDL_SCANCODE_S;\n    this->m_keys[PlayerControls::Buttons::Drop] = SDL_SCANCODE_LSHIFT;\n    this->m_keys2[PlayerControls::Buttons::Drop] = SDL_SCANCODE_RSHIFT;\n    this->m_keys[PlayerControls::Buttons::Start] = SDL_SCANCODE_RETURN;\n\n    this->m_editor_keys[EditorControls::Buttons::ScrollUp] = SDL_SCANCODE_UP;\n    this->m_editor_keys[EditorControls::Buttons::ScrollDown] = SDL_SCANCODE_DOWN;\n    this->m_editor_keys[EditorControls::Buttons::ScrollLeft] = SDL_SCANCODE_LEFT;\n    this->m_editor_keys[EditorControls::Buttons::ScrollRight] = SDL_SCANCODE_RIGHT;\n    this->m_editor_keys[EditorControls::Buttons::FastScroll] = SDL_SCANCODE_LSHIFT;\n    this->m_editor_keys[EditorControls::Buttons::ModeSelect] = SDL_SCANCODE_Z;\n    this->m_editor_keys[EditorControls::Buttons::ModeErase] = SDL_SCANCODE_X;\n    this->m_editor_keys[EditorControls::Buttons::PrevSection] = SDL_SCANCODE_A;\n    this->m_editor_keys[EditorControls::Buttons::NextSection] = SDL_SCANCODE_S;\n    this->m_editor_keys[EditorControls::Buttons::SwitchScreens] = SDL_SCANCODE_RSHIFT;\n    this->m_editor_keys[EditorControls::Buttons::TestPlay] = SDL_SCANCODE_RETURN;\n\n    // ALSO UPDATE InputMethodType_Keyboard::DefaultHotkey\n    this->m_hotkeys[Hotkeys::Buttons::ToggleHUD] = SDL_SCANCODE_F1;\n    this->m_hotkeys[Hotkeys::Buttons::DebugInfo] = SDL_SCANCODE_F3;\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    this->m_hotkeys[Hotkeys::Buttons::Fullscreen] = SDL_SCANCODE_F7;\n#endif\n\n#ifndef PGE_ENABLE_VIDEO_REC\n    // nothing to define\n#elif defined(__APPLE__) // on macOS the F11 key is reserved by the \"Show Desktop\" global action\n    this->m_hotkeys[Hotkeys::Buttons::RecordGif] = SDL_SCANCODE_F10;\n#else\n    this->m_hotkeys[Hotkeys::Buttons::RecordGif] = SDL_SCANCODE_F11;\n#endif\n\n#ifdef USE_SCREENSHOTS_AND_RECS\n    this->m_hotkeys[Hotkeys::Buttons::Screenshot] = SDL_SCANCODE_F12;\n    this->m_hotkeys2[Hotkeys::Buttons::Screenshot] = SDL_SCANCODE_F2;\n#endif\n\n#ifdef DEBUG_BUILD\n    this->m_hotkeys[Hotkeys::Buttons::ReloadLanguage] = SDL_SCANCODE_F5;\n#endif\n}\n\nbool InputMethodProfile_Keyboard::PollPrimaryButton(ControlsClass c, size_t i)\n{\n    if(c == ControlsClass::Cursor)\n        return true;\n\n    // note: m_canPoll is initialized to false\n    InputMethodType_Keyboard* k = dynamic_cast<InputMethodType_Keyboard*>(this->Type);\n\n    if(!k)\n        return false;\n\n    int key;\n\n    for(key = 0; key < k->m_keyboardStateSize; key++)\n    {\n        if(k->m_keyboardState[key] != 0)\n            break;\n    }\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == k->m_keyboardStateSize)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    int* keys;\n    int* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling Keyboard primary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n    // if a button's secondary key (including the current one) is the new key, delete it.\n    // if a button's primary key (excluding the current one) is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, replace it with the button the player is replacing.\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(keys2[j] == key)\n            keys2[j] = null_key;\n        else if(i != j && keys[j] == key)\n        {\n            if(keys2[j] != null_key)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_key;\n            }\n            else\n                keys[j] = keys[i];\n        }\n    }\n\n    keys[i] = key;\n    return true;\n}\n\nbool InputMethodProfile_Keyboard::PollSecondaryButton(ControlsClass c, size_t i)\n{\n    // note: m_canPoll is initialized to false\n    InputMethodType_Keyboard* k = dynamic_cast<InputMethodType_Keyboard*>(this->Type);\n\n    if(!k)\n        return false;\n\n    int key;\n\n    for(key = 0; key < k->m_keyboardStateSize; key++)\n    {\n        if(k->m_keyboardState[key] != 0)\n            break;\n    }\n\n    // if didn't find any key, allow poll in future but return false\n    if(key == k->m_keyboardStateSize)\n    {\n        this->m_canPoll = true;\n        return false;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return false;\n\n    // we will assign the key!\n    // reset canPoll for next time\n    m_canPoll = false;\n\n    // resolve the particular primary and secondary key arrays\n    int* keys;\n    int* keys2;\n    size_t key_max;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n        key_max = PlayerControls::n_buttons;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        keys = nullptr;\n        keys2 = this->m_cursor_keys2;\n        key_max = CursorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n        key_max = EditorControls::n_buttons;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n        key_max = Hotkeys::n_buttons;\n    }\n    else\n    {\n        D_pLogWarning(\"Polling Keyboard secondary button with disallowed controls class %d\\n\", (int)c);\n        return true;\n    }\n\n    // minor switching algorithm to ensure that every button always has at least one key\n    // and no button ever has a non-unique key\n\n    // if the current button's primary key is the new key,\n    //     delete its secondary key instead of setting it.\n    if(keys && keys[i] == key)\n    {\n        keys2[i] = null_key;\n        return true;\n    }\n\n    // if another button's secondary key is the new key, delete it.\n    // if another button's primary key is the new key,\n    //     and it has a secondary key, overwrite it with the secondary key.\n    //     otherwise, if this button's secondary key is defined, overwrite the other's with this.\n    //     if all else fails, overwrite the other button's with this button's PRIMARY key and assign\n    //         this button's PRIMARY key instead\n\n    bool can_do_secondary = true;\n\n    for(size_t j = 0; j < key_max; j++)\n    {\n        if(i != j && keys2[j] == key)\n        {\n            keys2[j] = null_key;\n        }\n        else if(keys && i != j && keys[j] == key)\n        {\n            if(keys2[j] != null_key)\n            {\n                keys[j] = keys2[j];\n                keys2[j] = null_key;\n            }\n            else if(keys2[i] != null_key)\n            {\n                keys[j] = keys2[i];\n            }\n            else\n            {\n                keys[j] = keys[i];\n                can_do_secondary = false;\n            }\n        }\n    }\n\n    if(can_do_secondary)\n        keys2[i] = key;\n    else if(keys)\n        keys[i] = key;\n\n    return true;\n}\n\nbool InputMethodProfile_Keyboard::DeletePrimaryButton(ControlsClass c, size_t i)\n{\n    // resolve the particular primary and secondary key arrays\n    int* keys;\n    int* keys2;\n\n    if(c == ControlsClass::Player)\n    {\n        keys = this->m_keys;\n        keys2 = this->m_keys2;\n    }\n    else if(c == ControlsClass::Cursor)\n    {\n        return false;\n    }\n    else if(c == ControlsClass::Editor)\n    {\n        keys = this->m_editor_keys;\n        keys2 = this->m_editor_keys2;\n    }\n    else if(c == ControlsClass::Hotkey)\n    {\n        keys = this->m_hotkeys;\n        keys2 = this->m_hotkeys2;\n    }\n    else\n    {\n        D_pLogWarning(\"Attempted to delete Keyboard primary button with disallowed controls class %d\\n\", (int)c);\n        return false;\n    }\n\n    if(keys2[i] != null_key)\n    {\n        keys[i] = keys2[i];\n        keys2[i] = null_key;\n        return true;\n    }\n\n    if(c == ControlsClass::Player)\n        return false;\n\n    if(keys[i] != null_key)\n    {\n        keys[i] = null_key;\n        return true;\n    }\n\n    return false;\n}\n\nbool InputMethodProfile_Keyboard::DeleteSecondaryButton(ControlsClass c, size_t i)\n{\n    int* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return false;\n\n    if(keys2[i] != null_key)\n    {\n        keys2[i] = null_key;\n        return true;\n    }\n\n    return false;\n}\n\nconst char* InputMethodProfile_Keyboard::NamePrimaryButton(ControlsClass c, size_t i)\n{\n    int* keys;\n\n    if(c == ControlsClass::Player)\n        keys = this->m_keys;\n    else if(c == ControlsClass::Cursor)\n        return g_controlsStrings.caseMouse.c_str();\n    else if(c == ControlsClass::Editor)\n        keys = this->m_editor_keys;\n    else if(c == ControlsClass::Hotkey)\n        keys = this->m_hotkeys;\n    else\n        return \"\";\n\n    if(keys[i] == null_key)\n        return g_mainMenu.caseNone.c_str();\n\n    return SDL_GetScancodeName((SDL_Scancode)keys[i]);\n}\n\nconst char* InputMethodProfile_Keyboard::NameSecondaryButton(ControlsClass c, size_t i)\n{\n    int* keys2;\n\n    if(c == ControlsClass::Player)\n        keys2 = this->m_keys2;\n    else if(c == ControlsClass::Cursor)\n        keys2 = this->m_cursor_keys2;\n    else if(c == ControlsClass::Editor)\n        keys2 = this->m_editor_keys2;\n    else if(c == ControlsClass::Hotkey)\n        keys2 = this->m_hotkeys2;\n    else\n        return \"\";\n\n    if(keys2[i] == null_key)\n        return \"\";\n\n    return SDL_GetScancodeName((SDL_Scancode)keys2[i]);\n}\n\nvoid InputMethodProfile_Keyboard::SaveConfig(IniProcessing* ctl)\n{\n    char name2[20];\n\n    for(int a = 0; a < 4; a++)\n    {\n        int* keys;\n        int* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            const char* name;\n\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            if(keys)\n                ctl->setValue(name, keys[i]);\n\n            for(size_t c = 0; c < 20; c++)\n            {\n                if(c + 2 == 20 || name[c] == '\\0')\n                {\n                    name2[c] = '2';\n                    name2[c + 1] = '\\0';\n                    break;\n                }\n\n                name2[c] = name[c];\n            }\n\n            ctl->setValue(name2, keys2[i]);\n        }\n    }\n}\n\nvoid InputMethodProfile_Keyboard::LoadConfig(IniProcessing* ctl)\n{\n    char name2[20];\n\n    for(int a = 0; a < 4; a++)\n    {\n        int* keys;\n        int* keys2;\n        size_t key_max;\n\n        if(a == 0)\n        {\n            keys = this->m_keys;\n            keys2 = this->m_keys2;\n            key_max = PlayerControls::n_buttons;\n        }\n        else if(a == 1)\n        {\n            keys = nullptr;\n            keys2 = this->m_cursor_keys2;\n            key_max = CursorControls::n_buttons;\n        }\n        else if(a == 2)\n        {\n            keys = this->m_editor_keys;\n            keys2 = this->m_editor_keys2;\n            key_max = EditorControls::n_buttons;\n        }\n        else\n        {\n            keys = this->m_hotkeys;\n            keys2 = this->m_hotkeys2;\n            key_max = Hotkeys::n_buttons;\n        }\n\n        for(size_t i = 0; i < key_max; i++)\n        {\n            const char* name;\n\n            if(a == 0)\n                name = PlayerControls::GetButtonName_INI(i);\n            else if(a == 1)\n                name = CursorControls::GetButtonName_INI(i);\n            else if(a == 2)\n                name = EditorControls::GetButtonName_INI(i);\n            else\n                name = Hotkeys::GetButtonName_INI(i);\n\n            if(keys)\n                ctl->read(name, keys[i], keys[i]);\n\n            for(size_t c = 0; c < 20; c++)\n            {\n                if(c + 2 == 20 || name[c] == '\\0')\n                {\n                    name2[c] = '2';\n                    name2[c + 1] = '\\0';\n                    break;\n                }\n\n                name2[c] = name[c];\n            }\n\n            // only add RSHIFT secondary key during conversion if current primary key is LSHIFT\n            int def = keys2[i];\n\n            if(a == 0 && i == PlayerControls::Buttons::Drop && keys[i] != SDL_SCANCODE_LSHIFT)\n                def = null_key;\n\n            ctl->read(name2, keys2[i], def);\n        }\n    }\n}\n\n/*====================================================*\\\n|| implementation for InputMethodType_Keyboard        ||\n\\*====================================================*/\n\nInputMethodProfile* InputMethodType_Keyboard::AllocateProfile() noexcept\n{\n    return (InputMethodProfile*) new(std::nothrow) InputMethodProfile_Keyboard;\n}\n\nInputMethodType_Keyboard::InputMethodType_Keyboard()\n{\n    this->m_keyboardState = SDL_GetKeyboardState(&this->m_keyboardStateSize);\n    this->Name = \"Keyboard\";\n    this->LegacyName = \"keyboard\";\n}\n\nconst std::string& InputMethodType_Keyboard::LocalName() const\n{\n    return g_controlsStrings.nameKeyboard;\n}\n\nbool InputMethodType_Keyboard::TestProfileType(InputMethodProfile* profile)\n{\n    return (bool)dynamic_cast<InputMethodProfile_Keyboard*>(profile);\n}\n\nbool InputMethodType_Keyboard::RumbleSupported()\n{\n    return false;\n}\n\nvoid InputMethodType_Keyboard::UpdateControlsPre() {}\nvoid InputMethodType_Keyboard::UpdateControlsPost()\n{\n    if(this->m_touchscreenActive && g_renderTouchscreen)\n    {\n        // let the touchscreen input method handle the mouse\n    }\n    // handle the mouse\n    else if(SDL_GetMouseFocus())\n    {\n        int window_x, window_y;\n        Uint32 buttons = SDL_GetMouseState(&window_x, &window_y);\n\n        static int last_window_x = -1, last_window_y = -1;\n        bool allow_move = false;\n\n        // only allow move if both the window coordinates and the gamespace coordinates changed (prevents fake move when resolution changes)\n        if(last_window_x != window_x || last_window_y != window_y)\n        {\n            last_window_x = window_x;\n            last_window_y = window_y;\n            allow_move = true;\n        }\n\n        SDL_Point p;\n        XRender::mapToScreen(window_x, window_y, &p.x, &p.y);\n        static SDL_Point last_p;\n\n        if(p.x - last_p.x <= -1 || p.x - last_p.x >= 1 ||\n           p.y - last_p.y <= -1 || p.y - last_p.y >= 1)\n        {\n            last_p = p;\n\n            if(allow_move)\n                SharedCursor.Move = true;\n\n            SharedCursor.X = p.x;\n            SharedCursor.Y = p.y;\n        }\n\n        if(buttons & SDL_BUTTON_LMASK)\n            SharedCursor.Primary = true;\n\n        if(buttons & SDL_BUTTON_RMASK)\n            SharedCursor.Secondary = true;\n\n        if(buttons & SDL_BUTTON_MMASK)\n            SharedCursor.Tertiary = true;\n    }\n    else if(XWindow::hasWindowInputFocus() && !SharedCursor.Move && (SharedCursor.X >= 0 || SharedCursor.Y >= 0))\n    {\n        SharedCursor.GoOffscreen();\n    }\n\n    if(this->m_scroll >= 1)\n    {\n        this->m_scroll -= 1;\n        SharedCursor.ScrollDown |= true;\n    }\n    else if(this->m_scroll <= -1)\n    {\n        this->m_scroll += 1;\n        SharedCursor.ScrollUp |= true;\n    }\n\n    if(this->m_directText && GamePaused == PauseCode::TextEntry)\n    {\n        this->m_canPoll = false;\n        return;\n    }\n\n    bool altPressed = this->m_keyboardState[SDL_SCANCODE_LALT] ||\n                      this->m_keyboardState[SDL_SCANCODE_RALT];\n    bool escBackPressed = this->m_keyboardState[SDL_SCANCODE_ESCAPE];\n    bool escPausePressed = this->m_keyboardState[SDL_SCANCODE_ESCAPE];\n    bool returnPressed = this->m_keyboardState[SDL_SCANCODE_RETURN];\n    bool spacePressed = this->m_keyboardState[SDL_SCANCODE_SPACE];\n    bool upPressed = this->m_keyboardState[SDL_SCANCODE_UP];\n    bool downPressed = this->m_keyboardState[SDL_SCANCODE_DOWN];\n    bool leftPressed = this->m_keyboardState[SDL_SCANCODE_LEFT];\n    bool rightPressed = this->m_keyboardState[SDL_SCANCODE_RIGHT];\n\n    // disable the shared keys if they are currently in use\n    for(InputMethod* method : g_InputMethods)\n    {\n        if(!method)\n            continue;\n\n        InputMethodProfile* p = method->Profile;\n        InputMethodProfile_Keyboard* profile = dynamic_cast<InputMethodProfile_Keyboard*>(p);\n\n        if(!profile)\n            continue;\n\n        for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n        {\n            if(profile->m_keys[i] == SDL_SCANCODE_ESCAPE || profile->m_keys2[i] == SDL_SCANCODE_ESCAPE)\n            {\n                // allow escape to count even when it is a player's start key, important for correct menu behavior\n                if(i != PlayerControls::Buttons::Start)\n                    escBackPressed = false;\n\n                escPausePressed = false;\n            }\n\n            if(profile->m_keys[i] == SDL_SCANCODE_RETURN || profile->m_keys2[i] == SDL_SCANCODE_RETURN)\n                returnPressed = false;\n\n            if(profile->m_keys[i] == SDL_SCANCODE_SPACE || profile->m_keys2[i] == SDL_SCANCODE_SPACE)\n                spacePressed = false;\n\n            if(profile->m_keys[i] == SDL_SCANCODE_UP || profile->m_keys2[i] == SDL_SCANCODE_UP)\n                upPressed = false;\n\n            if(profile->m_keys[i] == SDL_SCANCODE_DOWN || profile->m_keys2[i] == SDL_SCANCODE_DOWN)\n                downPressed = false;\n\n            if(profile->m_keys[i] == SDL_SCANCODE_LEFT || profile->m_keys2[i] == SDL_SCANCODE_LEFT)\n                leftPressed = false;\n\n            if(profile->m_keys[i] == SDL_SCANCODE_RIGHT || profile->m_keys2[i] == SDL_SCANCODE_RIGHT)\n                rightPressed = false;\n        }\n    }\n\n#ifdef __ANDROID__ // Quit credits on BACK key press\n    bool backPressed = this->m_keyboardState[SDL_SCANCODE_AC_BACK];\n#else\n    bool backPressed = false;\n#endif\n\n    l_SharedControls.QuitCredits |= (spacePressed || backPressed);\n    l_SharedControls.QuitCredits |= (returnPressed && !altPressed);\n    l_SharedControls.QuitCredits |= (escBackPressed && !altPressed);\n\n    l_SharedControls.Pause |= backPressed;\n    l_SharedControls.Pause |= (escPausePressed && !altPressed);\n\n    l_SharedControls.MenuUp |= upPressed;\n    l_SharedControls.MenuDown |= downPressed;\n    l_SharedControls.MenuLeft |= leftPressed;\n    l_SharedControls.MenuRight |= rightPressed;\n    l_SharedControls.MenuDo |= (returnPressed && !altPressed) || spacePressed;\n    l_SharedControls.MenuBack |= backPressed;\n    l_SharedControls.MenuBack |= (escBackPressed && !altPressed);\n}\n\n// this is challenging for the keyboard because we don't want to allocate 20 copies of it\nInputMethod* InputMethodType_Keyboard::Poll(const std::vector<InputMethod*>& active_methods) noexcept\n{\n    if(this->m_numKeyboards != this->m_lastNumKeyboards)\n    {\n        // this ensures that keys that were held when a keyboard method was removed cannot be polled to add that method back\n        if(this->m_numKeyboards < this->m_lastNumKeyboards)\n            this->m_canPoll = false;\n\n        this->m_lastNumKeyboards = this->m_numKeyboards;\n    }\n\n    if(this->m_numKeyboards >= m_maxKeyboards)\n    {\n        // ban polling in case things change\n        this->m_canPoll = false;\n        return nullptr;\n    }\n\n\n    // ban attachment from active profile, must find new profile\n    int key;\n    InputMethodProfile* target_profile = nullptr;\n\n    for(key = 0; key < this->m_keyboardStateSize; key++)\n    {\n        if(!this->m_keyboardState[key])\n            continue;\n\n        bool allowed = true;\n\n        if(key == SDL_SCANCODE_LALT || key == SDL_SCANCODE_RALT || key == SDL_SCANCODE_LCTRL || key == SDL_SCANCODE_RCTRL || key == SDL_SCANCODE_ESCAPE)\n            allowed = false;\n\n        // ban attachment from active profile\n        for(InputMethod* method : active_methods)\n        {\n            if(!allowed)\n                break;\n\n            if(!method)\n                continue;\n\n            InputMethodProfile_Keyboard* p = dynamic_cast<InputMethodProfile_Keyboard*>(method->Profile);\n\n            if(!p)\n                continue;\n\n            for(size_t i = 0; i < PlayerControls::n_buttons; i++)\n            {\n                if(p->m_keys[i] == key || p->m_keys2[i] == key)\n                {\n                    allowed = false;\n                    break;\n                }\n            }\n\n            if(LevelEditor)\n            {\n                for(size_t i = 0; i < EditorControls::n_buttons; i++)\n                {\n                    if(p->m_editor_keys[i] == key || p->m_editor_keys2[i] == key)\n                    {\n                        allowed = false;\n                        break;\n                    }\n                }\n            }\n\n            for(size_t i = 0; i < CursorControls::n_buttons; i++)\n            {\n                if(p->m_cursor_keys2[i] == key)\n                {\n                    allowed = false;\n                    break;\n                }\n            }\n\n            for(size_t i = 0; i < Hotkeys::n_buttons; i++)\n            {\n                if(p->m_hotkeys[i] == key || p->m_hotkeys2[i] == key)\n                {\n                    allowed = false;\n                    break;\n                }\n            }\n        }\n\n        if(!allowed)\n            continue;\n\n        // which player index is connecting?\n        int my_index = 0;\n\n        for(const InputMethod* method : active_methods)\n        {\n            if(!method)\n                break;\n\n            my_index ++;\n        }\n\n        // try to find profile matching the keypress\n        for(int i = -1; i < (int)this->m_profiles.size(); i++)\n        {\n            // start with the most recent profile for this player index\n            InputMethodProfile* profile;\n\n            if(i == -1)\n                profile = this->GetDefaultProfile(my_index);\n            else\n                profile = this->m_profiles[i];\n\n            if(!profile)\n                continue;\n\n            InputMethodProfile_Keyboard* p = dynamic_cast<InputMethodProfile_Keyboard*>(profile);\n\n            if(!p)\n                continue;\n\n            for(size_t j = 0; j < PlayerControls::n_buttons; j++)\n            {\n                if(p->m_keys[j] == key || p->m_keys2[j] == key)\n                {\n                    target_profile = profile;\n                    break;\n                }\n            }\n\n            if(target_profile)\n                break;\n        }\n\n        if(target_profile || this->m_profiles.empty())\n            break;\n    }\n\n    // if didn't find any key allow poll in future but return false\n    if(key == this->m_keyboardStateSize)\n    {\n        this->m_canPoll = true;\n        return nullptr;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return nullptr;\n\n    // we're going to create a new keyboard!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    InputMethod_Keyboard* method = new(std::nothrow) InputMethod_Keyboard;\n\n    if(!method)\n        return nullptr;\n\n    method->Name = this->LocalName();\n    method->Type = this;\n    method->Profile = target_profile;\n\n    this->m_numKeyboards ++;\n\n    return (InputMethod*)method;\n}\n\n/*-----------------------*\\\n|| OPTIONAL METHODS      ||\n\\*-----------------------*/\nbool InputMethodType_Keyboard::DefaultHotkey(const SDL_Event* ev)\n{\n    const SDL_KeyboardEvent& evt = ev->key;\n\n    int KeyCode = evt.keysym.scancode;\n\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    bool ctrlF = ((evt.keysym.mod & KMOD_CTRL) != 0 && evt.keysym.scancode == SDL_SCANCODE_F);\n    bool altEnter = ((evt.keysym.mod & KMOD_ALT) != 0 && (evt.keysym.scancode == SDL_SCANCODE_RETURN || evt.keysym.scancode == SDL_SCANCODE_KP_ENTER));\n\n    if(ctrlF || altEnter)\n        g_hotkeysPressed[Hotkeys::Buttons::Fullscreen] = 0;\n#endif\n\n    bool use_defaults = false;\n\n    if(this->m_numKeyboards == 0)\n        use_defaults = true;\n\n    if(this->m_directText && GamePaused == PauseCode::TextEntry)\n        use_defaults = true;\n\n    if(use_defaults && evt.repeat == 0)\n    {\n        // ALSO UPDATE InputMethodProfile_Keyboard::InputMethodProfile_Keyboard\n        if(KeyCode == SDL_SCANCODE_F3)\n            g_hotkeysPressed[Hotkeys::Buttons::DebugInfo] = 0;\n        else if(KeyCode == SDL_SCANCODE_F1)\n            g_hotkeysPressed[Hotkeys::Buttons::ToggleHUD] = 0;\n\n#ifdef USE_SCREENSHOTS_AND_RECS\n        else if(KeyCode == SDL_SCANCODE_F12 || KeyCode == SDL_SCANCODE_F2)\n            g_hotkeysPressed[Hotkeys::Buttons::Screenshot] = 0;\n#endif\n\n#ifdef PGE_ENABLE_VIDEO_REC\n#   ifdef __APPLE__\n        else if(KeyCode == SDL_SCANCODE_F10) // Reserved by macOS as \"show desktop\"\n#   else\n        else if(KeyCode == SDL_SCANCODE_F11)\n#   endif\n            g_hotkeysPressed[Hotkeys::Buttons::RecordGif] = 0;\n#endif\n    }\n\n    if(this->m_directText && GamePaused == PauseCode::TextEntry)\n        return false;\n\n    SDL_Scancode KeyASCII = evt.keysym.scancode;\n\n    // classic cheat codes\n    switch(KeyASCII)\n    {\n    case SDL_SCANCODE_A:\n        CheatCode('a');\n        break;\n\n    case SDL_SCANCODE_B:\n        CheatCode('b');\n        break;\n\n    case SDL_SCANCODE_C:\n        CheatCode('c');\n        break;\n\n    case SDL_SCANCODE_D:\n        CheatCode('d');\n        break;\n\n    case SDL_SCANCODE_E:\n        CheatCode('e');\n        break;\n\n    case SDL_SCANCODE_F:\n        CheatCode('f');\n        break;\n\n    case SDL_SCANCODE_G:\n        CheatCode('g');\n        break;\n\n    case SDL_SCANCODE_H:\n        CheatCode('h');\n        break;\n\n    case SDL_SCANCODE_I:\n        CheatCode('i');\n        break;\n\n    case SDL_SCANCODE_J:\n        CheatCode('j');\n        break;\n\n    case SDL_SCANCODE_K:\n        CheatCode('k');\n        break;\n\n    case SDL_SCANCODE_L:\n        CheatCode('l');\n        break;\n\n    case SDL_SCANCODE_M:\n        CheatCode('m');\n        break;\n\n    case SDL_SCANCODE_N:\n        CheatCode('n');\n        break;\n\n    case SDL_SCANCODE_O:\n        CheatCode('o');\n        break;\n\n    case SDL_SCANCODE_P:\n        CheatCode('p');\n        break;\n\n    case SDL_SCANCODE_Q:\n        CheatCode('q');\n        break;\n\n    case SDL_SCANCODE_R:\n        CheatCode('r');\n        break;\n\n    case SDL_SCANCODE_S:\n        CheatCode('s');\n        break;\n\n    case SDL_SCANCODE_T:\n        CheatCode('t');\n        break;\n\n    case SDL_SCANCODE_U:\n        CheatCode('u');\n        break;\n\n    case SDL_SCANCODE_V:\n        CheatCode('v');\n        break;\n\n    case SDL_SCANCODE_W:\n        CheatCode('w');\n        break;\n\n    case SDL_SCANCODE_X:\n        CheatCode('x');\n        break;\n\n    case SDL_SCANCODE_Y:\n        CheatCode('y');\n        break;\n\n    case SDL_SCANCODE_Z:\n        CheatCode('z');\n        break;\n\n    case SDL_SCANCODE_1:\n        CheatCode('1');\n        break;\n\n    case SDL_SCANCODE_2:\n        CheatCode('2');\n        break;\n\n    case SDL_SCANCODE_3:\n        CheatCode('3');\n        break;\n\n    case SDL_SCANCODE_4:\n        CheatCode('4');\n        break;\n\n    case SDL_SCANCODE_5:\n        CheatCode('5');\n        break;\n\n    case SDL_SCANCODE_6:\n        CheatCode('6');\n        break;\n\n    case SDL_SCANCODE_7:\n        CheatCode('7');\n        break;\n\n    case SDL_SCANCODE_8:\n        CheatCode('8');\n        break;\n\n    case SDL_SCANCODE_9:\n        CheatCode('9');\n        break;\n\n    case SDL_SCANCODE_0:\n        CheatCode('0');\n        break;\n\n    case SDL_SCANCODE_SEMICOLON:\n        CheatCode(';');\n        break; // for AZERTY support\n\n    default:\n        CheatCode(' ');\n        break;\n    }\n\n    return true;\n}\n\nbool InputMethodType_Keyboard::ConsumeEvent(const SDL_Event* ev)\n{\n    switch(ev->type)\n    {\n    case SDL_MOUSEWHEEL:\n        if(ev->wheel.which != SDL_TOUCH_MOUSEID)\n        {\n            // scrolling up results in traversing items backwards\n            this->m_scroll -= ev->wheel.y;\n\n#if SDL_VERSION_ATLEAST(2,0,18)\n            SDL_version ver;\n            SDL_GetVersion(&ver);\n\n            if(int(ver.major) * 0x10000 + int(ver.minor) * 0x100 + int(ver.patch) >= 2 * 0x10000 + 0 * 0x100 + 18)\n            {\n                this->m_scroll_x_precise += ev->wheel.preciseX;\n                this->m_scroll_y_precise -= ev->wheel.preciseY;\n            }\n            else\n#endif\n            {\n                this->m_scroll_x_precise += ev->wheel.x;\n                this->m_scroll_y_precise -= ev->wheel.y;\n            }\n\n            return true;\n        }\n        else\n            return false;\n\n    case SDL_MOUSEBUTTONUP:\n#ifndef RENDER_FULLSCREEN_ALWAYS\n        if(ev->button.button == SDL_BUTTON_LEFT && ev->button.which != SDL_TOUCH_MOUSEID)\n        {\n            bool doubleClick = (this->m_lastMousePress + 300) >= SDL_GetTicks();\n            this->m_lastMousePress = SDL_GetTicks();\n\n            if(doubleClick && !MagicHand && !LevelEditor && !g_cancelDoubleClick)\n            {\n                this->m_lastMousePress = 0;\n                g_hotkeysPressed[Hotkeys::Buttons::Fullscreen] = 0;\n                return true;\n            }\n\n            g_cancelDoubleClick = false;\n        }\n#endif\n\n    // intentional fallthrough\n    case SDL_MOUSEBUTTONDOWN:\n        if(ev->button.which == SDL_TOUCH_MOUSEID)\n            this->m_touchscreenActive = true;\n        else\n            this->m_touchscreenActive = false;\n\n        break;\n\n    case SDL_MOUSEMOTION:\n        if(ev->motion.which == SDL_TOUCH_MOUSEID)\n            this->m_touchscreenActive = true;\n        else\n            this->m_touchscreenActive = false;\n\n        break;\n\n    case SDL_TEXTINPUT:\n        if(this->m_directText && GamePaused == PauseCode::TextEntry)\n        {\n            TextEntryScreen::Insert(ev->text.text);\n            return true;\n        }\n\n        return false;\n\n    case SDL_KEYDOWN:\n        if(this->DefaultHotkey(ev))\n            return true;\n\n        if(this->m_directText && GamePaused == PauseCode::TextEntry)\n        {\n            if(ev->key.keysym.scancode == SDL_SCANCODE_RETURN || ev->key.keysym.scancode == SDL_SCANCODE_KP_ENTER)\n                TextEntryScreen::Commit();\n            else if(ev->key.keysym.scancode == SDL_SCANCODE_LEFT)\n                TextEntryScreen::CursorLeft();\n            else if(ev->key.keysym.scancode == SDL_SCANCODE_RIGHT)\n                TextEntryScreen::CursorRight();\n            else if(ev->key.keysym.scancode == SDL_SCANCODE_BACKSPACE)\n                TextEntryScreen::Backspace();\n\n            return true;\n        }\n\n        return false;\n\n    default:\n        return false;\n    }\n\n    return false;\n}\n\n// optional function allowing developer to associate device information with profile, etc\nbool InputMethodType_Keyboard::SetProfile_Custom(InputMethod* method, int player_no, InputMethodProfile* profile,\n        const std::vector<InputMethod*>& active_methods)\n{\n    if(!method || !profile || player_no < 0 || player_no >= maxLocalPlayers)\n        return false;\n\n    // prevent duplicates of a profile from ever being set\n    for(InputMethod* o_method : active_methods)\n    {\n        if(!o_method)\n            continue;\n\n        if(o_method != method && o_method->Profile == profile)\n            return false;\n    }\n\n    m_canPoll = false;\n    return true;\n}\n\n// How many per-type special options are there?\nsize_t InputMethodType_Keyboard::GetOptionCount()\n{\n    return 2;\n}\n\n// Methods to manage per-profile options\n// It is guaranteed that none of these will be called if\n// GetOptionCount() returns 0.\n// get a char* describing the option\nconst char* InputMethodType_Keyboard::GetOptionName(size_t i)\n{\n    if(i == 0)\n        return g_controlsStrings.sharedOptionMaxPlayers.c_str();\n\n    if(i == 1)\n        return g_controlsStrings.keyboardOptionTextEntryStyle.c_str();\n\n    return nullptr;\n}\n\n// get a char* describing the current option value\n// must be allocated in static or instance memory\n// WILL NOT be freed\nconst char* InputMethodType_Keyboard::GetOptionValue(size_t i)\n{\n    if(i == 0)\n    {\n        static char buf[3];\n        snprintf(buf, 3, \"%d\", this->m_maxKeyboards);\n        return buf;\n    }\n\n    if(i == 1)\n    {\n        if(this->m_directText)\n            return g_controlsStrings.nameKeyboard.c_str();\n        else\n            return g_controlsStrings.nameGamepad.c_str();\n    }\n\n    return nullptr;\n}\n\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodType_Keyboard::OptionChange(size_t i)\n{\n    if(i == 0)\n    {\n        this->m_maxKeyboards ++;\n\n        if(this->m_maxKeyboards > maxLocalPlayers)\n            this->m_maxKeyboards = 0;\n\n        return true;\n    }\n\n    if(i == 1)\n    {\n        this->m_directText = !this->m_directText;\n        return true;\n    }\n\n    return false;\n}\n\n// called when left is pressed\nbool InputMethodType_Keyboard::OptionRotateLeft(size_t i)\n{\n    if(i == 0)\n    {\n        if(this->m_maxKeyboards > 0)\n        {\n            this->m_maxKeyboards --;\n            return true;\n        }\n    }\n\n    if(i == 1)\n    {\n        this->m_directText = !this->m_directText;\n        return true;\n    }\n\n    return false;\n}\n\n// called when right is pressed\nbool InputMethodType_Keyboard::OptionRotateRight(size_t i)\n{\n    if(i == 0)\n    {\n        if(this->m_maxKeyboards < maxLocalPlayers)\n        {\n            this->m_maxKeyboards ++;\n            return true;\n        }\n    }\n\n    if(i == 1)\n    {\n        this->m_directText = !this->m_directText;\n        return true;\n    }\n\n    return false;\n}\n\nvoid InputMethodType_Keyboard::SaveConfig_Custom(IniProcessing* ctl)\n{\n    ctl->setValue(\"max-keyboards\", this->m_maxKeyboards);\n    ctl->setValue(\"direct-text-entry\", this->m_directText);\n}\n\nvoid InputMethodType_Keyboard::LoadConfig_Custom(IniProcessing* ctl)\n{\n    ctl->read(\"max-keyboards\", this->m_maxKeyboards, maxLocalPlayers);\n    ctl->read(\"direct-text-entry\", this->m_directText, true);\n}\n\n} // namespace Controls\n"
  },
  {
    "path": "src/control/keyboard.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef KEYBOARD_H\n#define KEYBOARD_H\n\n#include \"../controls.h\"\n\nnamespace Controls\n{\n\nextern bool g_cancelDoubleClick;\n\nconstexpr int null_key = -1;\n\nclass InputMethod_Keyboard : public InputMethod\n{\npublic:\n    using InputMethod::Type;\n    using InputMethod::Profile;\n\n    ~InputMethod_Keyboard();\n\n    // Update functions that set player controls (and editor controls)\n    // based on current device input. Return false if device lost.\n    bool Update(int player, Controls_t &c, CursorControls_t &m, EditorControls_t &e, HotkeysPressed_t &h);\n\n    void Rumble(int ms, float strength);\n\n    StatusInfo GetStatus();\n};\n\nclass InputMethodProfile_Keyboard : public InputMethodProfile\n{\nprivate:\n    bool m_canPoll = false;\n\npublic:\n    using InputMethodProfile::Name;\n    using InputMethodProfile::Type;\n\n    int m_keys[PlayerControls::n_buttons] = {null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key};\n    int m_keys2[PlayerControls::n_buttons] = {null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key};\n\n    int m_editor_keys[EditorControls::n_buttons] = {null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key};\n    int m_editor_keys2[EditorControls::n_buttons] = {null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key, null_key};\n\n    int m_cursor_keys2[CursorControls::n_buttons] = {null_key, null_key, null_key, null_key, null_key, null_key, null_key};\n\n    int m_hotkeys[Hotkeys::n_buttons];\n    int m_hotkeys2[Hotkeys::n_buttons];\n\n    InputMethodProfile_Keyboard();\n\n    // Polls a new (secondary) device button for the i'th player button\n    // Returns true on success and false if no button pressed\n    // Never allows two player buttons to bind to the same device button\n    bool PollPrimaryButton(ControlsClass c, size_t i);\n    bool PollSecondaryButton(ControlsClass c, size_t i);\n\n    // Deletes a primary button for the i'th button of class c (only called for non-Player buttons)\n    bool DeletePrimaryButton(ControlsClass c, size_t i);\n\n    // Deletes a secondary device button for the i'th button of class c\n    bool DeleteSecondaryButton(ControlsClass c, size_t i);\n\n    // Gets strings for the device buttons currently used for the i'th button of class c\n    const char *NamePrimaryButton(ControlsClass c, size_t i);\n    const char *NameSecondaryButton(ControlsClass c, size_t i);\n\n    // one can assume that the IniProcessing* is already in the correct group\n    void SaveConfig(IniProcessing *ctl);\n    void LoadConfig(IniProcessing *ctl);\n};\n\nclass InputMethodType_Keyboard : public InputMethodType\n{\nprivate:\n    bool m_canPoll = false;\n\n    int m_lastNumKeyboards = 0;\n    bool m_touchscreenActive = false;\n\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    Uint32 m_lastMousePress = 0;\n#endif\n\n    int m_scroll = 0;\n\n    InputMethodProfile *AllocateProfile() noexcept override;\n\npublic:\n    int m_numKeyboards = 0;\n\n    // options\n    int m_maxKeyboards = 2;\n    bool m_directText = true;\n\n    using InputMethodType::m_profiles;\n\n    const uint8_t *m_keyboardState;\n    int m_keyboardStateSize = 0;\n\n\n    // used by input methods for scrolling in editor\n    float m_scroll_x_precise = 0.0;\n    float m_scroll_y_precise = 0.0;\n\n\n    InputMethodType_Keyboard();\n\n    const std::string& LocalName() const override;\n\n    bool TestProfileType(InputMethodProfile *profile) override;\n    bool RumbleSupported() override;\n\n    void UpdateControlsPre() override;\n    void UpdateControlsPost() override;\n\n    // null if no input method is ready\n    // allocates the new InputMethod on the heap\n    InputMethod *Poll(const std::vector<InputMethod *> &active_methods) noexcept override;\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\nprotected:\n    // optional function allowing developer to associate device information with profile, etc\n    // if developer wants to forbid assignment, return false\n    bool SetProfile_Custom(InputMethod *method, int player_no, InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods) override;\n\npublic:\n    bool DefaultHotkey(const SDL_Event *ev);\n    bool ConsumeEvent(const SDL_Event *ev) override;\n\n    // How many per-type special options are there?\n    size_t GetOptionCount() override;\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount() returns 0.\n    // get a char* describing the option\n    const char *GetOptionName(size_t i) override;\n    // get a char* describing the current option value\n    // must be allocated in static or instance memory\n    // WILL NOT be freed\n    const char *GetOptionValue(size_t i) override;\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange(size_t i) override;\n    // called when left is pressed\n    bool OptionRotateLeft(size_t i) override;\n    // called when right is pressed\n    bool OptionRotateRight(size_t i) override;\n\nprotected:\n    void SaveConfig_Custom(IniProcessing *ctl) override;\n    void LoadConfig_Custom(IniProcessing *ctl) override;\n};\n\n} // namespace Controls\n\n#endif // #ifndef KEYBOARD_H\n"
  },
  {
    "path": "src/control/touchscreen.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <SDL2/SDL.h>\n\n#include <Logger/logger.h>\n#ifdef __ANDROID__\n#   include <Utils/files.h>\n#endif\n\n#include \"AppPath/app_path.h\"\n\n#include \"touchscreen.h\"\n#include \"../globals.h\"\n// #include \"config.h\" // Not used\n#include \"../game_main.h\"\n#include \"../player.h\"\n#include \"../core/render.h\"\n#include \"../core/window.h\"\n\n#include \"main/menu_main.h\"\n#include \"main/screen_asset_pack.h\"\n#include \"control/controls_strings.h\"\n\n#include \"core/power.h\"\n\n#include \"editor/new_editor.h\"\n\n#include <SDL2/SDL_haptic.h>\n\n\n#ifdef __ANDROID__\n#   include <jni.h>\n\nstatic double s_screenSize = -1;\nstatic double s_screenWidth = -1;\nstatic double s_screenHeight = -1;\n\nextern \"C\" JNIEXPORT void JNICALL\nJava_ru_wohlsoft_thextech_thextechActivity_setScreenSize(\n    JNIEnv* env,\n    jclass type,\n    jdouble screenSize,\n    jdouble screenWidth,\n    jdouble screenHeight\n)\n{\n    (void)env;\n    (void)type;\n    s_screenSize = screenSize;\n    s_screenWidth = screenWidth;\n    s_screenHeight = screenHeight;\n}\n\nextern \"C\" JNIEXPORT void JNICALL\nJava_org_libsdl_app_SDLActivity_thextechDebugLog(\n    JNIEnv* env,\n    jclass clazz,\n    jstring line_j)\n{\n    const char *line;\n    (void)clazz;\n    line = env->GetStringUTFChars(line_j, nullptr);\n    pLogDebug(\"Java-Side: %s\", line);\n    env->ReleaseStringUTFChars(line_j, line);\n}\n\n#endif\n\nnamespace Controls\n{\n\n/*------------------------------------------*\\\n|| implementation for TouchScreenGFX_t      ||\n\\*------------------------------------------*/\n\nvoid TouchScreenGFX_t::loadImage(StdPicture& img, const std::string& fileName)\n{\n    std::string imgPath = m_gfxPath + fileName;\n\n#ifdef __ANDROID__\n    if(!Files::fileExists(imgPath)) // If not exists at assets, do load bundled\n        imgPath = \"buttons/\" + fileName;\n#endif\n\n    // pLogDebug(\"Loading texture %s...\", imgPath.c_str());\n    img.reset();\n    XRender::lazyLoadPicture(img, imgPath);\n\n    if(!img.inited)\n    {\n        if(m_loadErrors == 0)\n            pLogWarning(\"Failed to load texture: %s...\", imgPath.c_str());\n\n        m_loadErrors++;\n    }\n}\n\nTouchScreenGFX_t::TouchScreenGFX_t()\n{\n    m_success = false;\n}\n\nvoid TouchScreenGFX_t::loadAll()\n{\n    m_loadErrors = 0;\n    m_success = true;\n\n    // Loading a touch-screen buttons from assets\n    loadImage(touch[BUTTON_START], \"Start.png\");\n    loadImage(touch[BUTTON_LEFT], \"Left.png\");\n    loadImage(touch[BUTTON_LEFT_CHAR], \"Left_char.png\");\n    loadImage(touch[BUTTON_RIGHT], \"Right.png\");\n    loadImage(touch[BUTTON_RIGHT_CHAR], \"Right_char.png\");\n    loadImage(touch[BUTTON_UP], \"Up.png\");\n    loadImage(touch[BUTTON_DOWN], \"Down.png\");\n    loadImage(touch[BUTTON_UPLEFT], \"UpLeft.png\");\n    loadImage(touch[BUTTON_UPRIGHT], \"UpRight.png\");\n    loadImage(touch[BUTTON_DOWNLEFT], \"DownLeft.png\");\n    loadImage(touch[BUTTON_DOWNRIGHT], \"DownRight.png\");\n    loadImage(touch[BUTTON_A], \"A.png\");\n    loadImage(touch[BUTTON_A_PS], \"A_ps.png\");\n    loadImage(touch[BUTTON_A_BLANK], \"A_blank.png\");\n    loadImage(touch[BUTTON_A_DO], \"A_do.png\");\n    loadImage(touch[BUTTON_A_ENTER], \"A_enter.png\");\n    loadImage(touch[BUTTON_A_JUMP], \"A_jump.png\");\n    loadImage(touch[BUTTON_B], \"V.png\");\n    loadImage(touch[BUTTON_B_PS], \"V_ps.png\");\n    loadImage(touch[BUTTON_B_BLANK], \"V_blank.png\");\n    loadImage(touch[BUTTON_B_JUMP], \"V_jump.png\");\n    loadImage(touch[BUTTON_B_SPINJUMP], \"V_spinjump.png\");\n    loadImage(touch[BUTTON_X], \"X.png\");\n    loadImage(touch[BUTTON_X_PS], \"X_ps.png\");\n    loadImage(touch[BUTTON_X_BACK], \"X_back.png\");\n    loadImage(touch[BUTTON_X_BLANK], \"X_blank.png\");\n    loadImage(touch[BUTTON_X_BOMB], \"X_bomb.png\");\n    loadImage(touch[BUTTON_X_BUMERANG], \"X_bumerang.png\");\n    loadImage(touch[BUTTON_X_FIRE], \"X_fire.png\");\n    loadImage(touch[BUTTON_X_HAMMER], \"X_hammer.png\");\n    loadImage(touch[BUTTON_X_RUN], \"X_run.png\");\n    loadImage(touch[BUTTON_X_SWORD], \"X_sword.png\");\n    loadImage(touch[BUTTON_Y], \"Y.png\");\n    loadImage(touch[BUTTON_Y_PS], \"Y_ps.png\");\n    loadImage(touch[BUTTON_Y_BLANK], \"Y_blank.png\");\n    loadImage(touch[BUTTON_Y_BOMB], \"Y_bomb.png\");\n    loadImage(touch[BUTTON_Y_BUMERANG], \"Y_bumerang.png\");\n    loadImage(touch[BUTTON_Y_FIRE], \"Y_fire.png\");\n    loadImage(touch[BUTTON_Y_HAMMER], \"Y_hammer.png\");\n    loadImage(touch[BUTTON_Y_RUN], \"Y_run.png\");\n    loadImage(touch[BUTTON_Y_STATUE], \"Y_statue.png\");\n    loadImage(touch[BUTTON_Y_SWORD], \"Y_sword.png\");\n    loadImage(touch[BUTTON_DROP], \"Select.png\");\n    loadImage(touch[BUTTON_HOLD_RUN_OFF], \"RunOff.png\");\n    loadImage(touch[BUTTON_HOLD_RUN_ON], \"RunOn.png\");\n    loadImage(touch[BUTTON_VIEW_TOGGLE_OFF], \"KeysShowOff.png\");\n    loadImage(touch[BUTTON_VIEW_TOGGLE_ON], \"KeysShow.png\");\n    loadImage(touch[BUTTON_ANALOG_BORDER], \"SBorder.png\");\n    loadImage(touch[BUTTON_ANALOG_STICK], \"AStick.png\");\n    loadImage(touch[BUTTON_ENTER_CHEATS], \"EnterCheats.png\");\n\n    if(m_loadErrors > 0)\n        m_success = false;\n\n    if(m_loadErrors > 1)\n        pLogWarning(\"and %d other file(s)\", m_loadErrors - 1);\n}\n\nvoid TouchScreenGFX_t::load()\n{\n    m_gfxPath = AppPath + \"graphics/touchscreen/\";\n    loadAll();\n\n    // try loading from other paths if unsuccessful\n    for(const auto& root_ : AppPathManager::assetsSearchPath())\n    {\n        if(m_success)\n            break;\n\n        m_gfxPath = root_.first + \"graphics/touchscreen/\";\n        loadAll();\n    }\n\n    if(m_success)\n        pLogDebug(\"Loaded touchscreen GFX from %s\", m_gfxPath.c_str());\n    else\n        pLogWarning(\"Touch-screen controller cannot be used due to missing assets.\");\n}\n\n/*------------------------------------------*\\\n|| implementation for TouchScreenController ||\n\\*------------------------------------------*/\n\nbool TouchScreenController::touchSupported()\n{\n    if(m_touchDevicesCount <= 0)\n        return false;\n\n    if(!m_GFX.m_success)\n        return false;\n\n    return true;\n}\n\nint TouchScreenController::numDevices() const\n{\n    return m_touchDevicesCount;\n}\n\nbool TouchScreenController::touchOn()\n{\n    for(auto it = m_devices.begin(); it != m_devices.end(); ++it)\n    {\n        int fingers = SDL_GetNumTouchFingers(it->id);\n        if(fingers > 0)\n            return true;\n    }\n\n    return false;\n}\n\nTouchScreenController::FingerState::FingerState()\n{\n    for(int i = key_BEGIN; i < key_END; i++)\n    {\n        heldKey[i] = false;\n        heldKeyPrev[i] = false;\n    }\n}\n\nTouchScreenController::FingerState::FingerState(const FingerState& fs)\n{\n    alive = fs.alive;\n\n    for(int i = key_BEGIN; i < key_END; i++)\n    {\n        heldKey[i] = fs.heldKey[i];\n        heldKeyPrev[i] = fs.heldKeyPrev[i];\n    }\n}\n\nTouchScreenController::FingerState& TouchScreenController::FingerState::operator=(const FingerState& fs)\n{\n    alive = fs.alive;\n\n    for(int i = key_BEGIN; i < key_END; i++)\n    {\n        heldKey[i] = fs.heldKey[i];\n        heldKeyPrev[i] = fs.heldKeyPrev[i];\n    }\n\n    return *this;\n}\n\nstatic int buttonLeft(int player_no, int style)\n{\n    (void)player_no;\n    (void)style;\n\n    if(SwapCharAllowed() && GamePaused == PauseCode::PauseScreen)\n        return TouchScreenGFX_t::BUTTON_LEFT_CHAR;\n    else\n        return TouchScreenGFX_t::BUTTON_LEFT;\n}\n\nstatic int buttonRight(int player_no, int style)\n{\n    (void)player_no;\n    (void)style;\n\n    if(SwapCharAllowed() && GamePaused == PauseCode::PauseScreen)\n        return TouchScreenGFX_t::BUTTON_RIGHT_CHAR;\n    else\n        return TouchScreenGFX_t::BUTTON_RIGHT;\n}\n\nstatic int buttonA(int player_no, int style)\n{\n    (void)player_no;\n\n    switch(style)\n    {\n    case TouchScreenController::style_abxy:\n        return TouchScreenGFX_t::BUTTON_A;\n\n    case TouchScreenController::style_xoda:\n        return TouchScreenGFX_t::BUTTON_A_PS;\n\n    default:\n    case TouchScreenController::style_actions:\n        if(GamePaused != PauseCode::None || GameMenu)\n            return TouchScreenGFX_t::BUTTON_A_DO;\n        else if(GameOutro)\n            return TouchScreenGFX_t::BUTTON_A_BLANK;\n        else if(LevelSelect)\n            return TouchScreenGFX_t::BUTTON_A_ENTER;\n        else\n            return TouchScreenGFX_t::BUTTON_A_JUMP;\n    }\n}\n\nstatic int buttonX(int player_no, int style)\n{\n    switch(style)\n    {\n    case TouchScreenController::style_abxy:\n        return TouchScreenGFX_t::BUTTON_X;\n\n    case TouchScreenController::style_xoda:\n        return TouchScreenGFX_t::BUTTON_X_PS;\n\n    default:\n    case TouchScreenController::style_actions:\n        if(GamePaused != PauseCode::None || GameMenu)\n            return TouchScreenGFX_t::BUTTON_X_BACK;\n        else if(LevelSelect || GameOutro)\n            return TouchScreenGFX_t::BUTTON_X_BLANK;\n        else\n        {\n            if(numPlayers >= player_no)\n            {\n                auto& p = Player[player_no];\n\n                if(p.Character == 5 || p.State == 4 || p.State == 5)\n                    return TouchScreenGFX_t::BUTTON_X_SWORD;\n                else if(p.State < 3)\n                    return TouchScreenGFX_t::BUTTON_X_RUN;\n                else if(p.State == 3 || p.State == 7)\n                    return TouchScreenGFX_t::BUTTON_X_FIRE;\n                else if(p.State == 6)\n                {\n                    switch(p.Character)\n                    {\n                    default:\n                    case 1:\n                    case 2:\n                        return TouchScreenGFX_t::BUTTON_X_HAMMER;\n\n                    case 3:\n                        return TouchScreenGFX_t::BUTTON_X_BOMB;\n\n                    case 4:\n                        return TouchScreenGFX_t::BUTTON_X_BUMERANG;\n\n                    case 5:\n                        return TouchScreenGFX_t::BUTTON_X_SWORD;\n                    }\n                }\n            }\n\n            return TouchScreenGFX_t::BUTTON_X_BLANK;\n        }\n    }\n}\n\nstatic int buttonB(int player_no, int style)\n{\n    switch(style)\n    {\n    case TouchScreenController::style_abxy:\n        return TouchScreenGFX_t::BUTTON_B;\n\n    case TouchScreenController::style_xoda:\n        return TouchScreenGFX_t::BUTTON_B_PS;\n\n    default:\n    case TouchScreenController::style_actions:\n        if(LevelSelect || GamePaused != PauseCode::None || GameMenu || GameOutro)\n            return TouchScreenGFX_t::BUTTON_B_BLANK;\n        else\n        {\n            if(numPlayers >= player_no)\n            {\n                auto& p = Player[player_no];\n\n                if(p.Character <= 2 || p.Character == 4)\n                    return TouchScreenGFX_t::BUTTON_B_SPINJUMP;\n                else\n                    return TouchScreenGFX_t::BUTTON_B_JUMP;\n            }\n\n            return TouchScreenGFX_t::BUTTON_B_BLANK;\n        }\n    }\n}\n\nstatic int buttonY(int player_no, int style)\n{\n    switch(style)\n    {\n    case TouchScreenController::style_abxy:\n        return TouchScreenGFX_t::BUTTON_Y;\n\n    case TouchScreenController::style_xoda:\n        return TouchScreenGFX_t::BUTTON_Y_PS;\n\n    default:\n    case TouchScreenController::style_actions:\n        if(LevelSelect || GamePaused != PauseCode::None || GameMenu || GameOutro)\n            return TouchScreenGFX_t::BUTTON_Y_BLANK;\n        else\n        {\n            if(numPlayers >= player_no)\n            {\n                auto& p = Player[player_no];\n\n                if(p.State == 5)\n                    return TouchScreenGFX_t::BUTTON_Y_STATUE;\n\n                if(p.Character == 5 || p.State == 4)\n                    return TouchScreenGFX_t::BUTTON_Y_SWORD;\n                else if(p.State < 3)\n                    return TouchScreenGFX_t::BUTTON_Y_RUN;\n                else if(p.State == 3 || p.State == 7)\n                    return TouchScreenGFX_t::BUTTON_Y_FIRE;\n                else if(p.State == 6)\n                {\n                    switch(p.Character)\n                    {\n                    default:\n                    case 1:\n                    case 2:\n                        return TouchScreenGFX_t::BUTTON_Y_HAMMER;\n\n                    case 3:\n                        return TouchScreenGFX_t::BUTTON_Y_BOMB;\n\n                    case 4:\n                        return TouchScreenGFX_t::BUTTON_Y_BUMERANG;\n\n                    case 5:\n                        return TouchScreenGFX_t::BUTTON_Y_SWORD;\n                    }\n                }\n            }\n\n            return TouchScreenGFX_t::BUTTON_Y_BLANK;\n        }\n    }\n}\n\n\nstatic struct TouchKeyMap\n{\n    enum anchor\n    {\n        T = 0,\n        B,\n        L = 0,\n        R,\n        C\n    };\n\n    struct KeyPos\n    {\n        anchor xa;\n        anchor ya;\n        float x1;\n        float y1;\n        float x2;\n        float y2;\n        TouchScreenController::commands cmd;\n    };\n\n    //! Width of canvas area\n    float touchCanvasWidth = 1024.0f;\n    //! Height of canvas area\n    float touchCanvasHeight = 600.0f;\n\n    //! List of key hit boxes\n    KeyPos touchKeysMap[TouchScreenController::key_END];\n\n    TouchKeyMap()\n    {\n        static_assert(sizeof(touchKeysMap) == TouchScreenController::key_END * sizeof(KeyPos),\n                      \"The size of touchscreen keys pam must have the size of key_END multiplied to size of KeyPos\");\n\n        for(int it = TouchScreenController::key_BEGIN; it < TouchScreenController::key_END; it++)\n        {\n            auto& p = touchKeysMap[it];\n            p.cmd = static_cast<TouchScreenController::commands>(it);\n            SDL_assert(p.cmd >= TouchScreenController::key_BEGIN && p.cmd < TouchScreenController::key_END);\n        }\n    }\n\n    /**\n     * \\brief Change size of virtual canvas\n     * @param width Width of touchable canvas\n     * @param height Height of touchable canvas\n     */\n    void setCanvasSize(float width, float height)\n    {\n        touchCanvasWidth = width;\n        touchCanvasHeight = height;\n    }\n\n    /**\n     * \\brief Change hitbox of key\n     * @param cmd Command\n     * @param left Left side position\n     * @param top Top side position\n     * @param right Right side position\n     * @param bottom Bottom side position\n     */\n    void setKeyPos(TouchScreenController::commands cmd, float left, float top, float right, float bottom)\n    {\n        if(cmd < TouchScreenController::key_BEGIN || cmd >= TouchScreenController::key_END)\n            return;\n\n        SDL_assert_release(touchKeysMap[cmd].cmd == cmd);\n\n        auto& key = touchKeysMap[cmd];\n        key.x1 = left;\n        key.y1 = top;\n        key.x2 = right;\n        key.y2 = bottom;\n    }\n\n    /**\n     * \\brief Find keys are touched by one finger\n     * @param x X position of finger\n     * @param y Y position of finger\n     * @param keys Destination array to write captured keys\n     * @return Count of keys are got touched\n     */\n    int findTouchKeys(float x, float y, TouchScreenController::FingerState& fs)\n    {\n        // const size_t touchKeyMapSize = sizeof(touchKeysMap) / sizeof(KeyPos);\n        int count = 0;\n        x *= touchCanvasWidth;\n        y *= touchCanvasHeight;\n\n        for(const auto& p : touchKeysMap)\n        {\n            SDL_assert(p.cmd >= TouchScreenController::key_BEGIN && p.cmd < TouchScreenController::key_END);\n            fs.heldKey[p.cmd] = false;\n\n            bool coll_normal = (x >= p.x1 && x <= p.x2 && y >= p.y1 && y <= p.y2);\n\n            // special overlap for face buttons\n            bool is_button = (p.cmd == TouchScreenController::key_jump || p.cmd == TouchScreenController::key_altjump\n                            || p.cmd == TouchScreenController::key_run || p.cmd == TouchScreenController::key_altrun);\n\n            float w_extra = (p.x2 - p.x1) * 0.25;\n            float h_extra = (p.y2 - p.y1) * 0.1;\n            bool coll_button = (x >= p.x1 - w_extra && x <= p.x2 + w_extra && y >= p.y1 - h_extra && y <= p.y2 + h_extra);\n\n            if(coll_normal || (is_button && coll_button))\n            {\n                fs.heldKey[p.cmd] = true;\n                count++;\n            }\n        }\n\n        return count;\n    }\n\n} g_touchKeyMap;\n\n/*-----------------------Screen size depending layouts ----------------------------------*/\n/************************************************\n * Please use this tool to edit these layouts:  *\n * https://github.com/Wohlstand/TouchpadTuner   *\n ************************************************/\n\nstatic const TouchKeyMap::KeyPos c_4_tinyPhoneMap[TouchScreenController::key_END] =\n{\n    /* Note that order of keys must match the TouchScreenController::commands enum!!! */\n    {TouchKeyMap::L, TouchKeyMap::T, 335.0f, 12.0f, 451.0f, 52.0f, TouchScreenController::key_start},\n    {TouchKeyMap::L, TouchKeyMap::T, 11.0f, 262.0f, 91.0f, 342.0f, TouchScreenController::key_left},\n    {TouchKeyMap::L, TouchKeyMap::T, 171.0f, 262.0f, 251.0f, 342.0f, TouchScreenController::key_right},\n    {TouchKeyMap::L, TouchKeyMap::T, 91.0f, 182.0f, 171.0f, 262.0f, TouchScreenController::key_up},\n    {TouchKeyMap::L, TouchKeyMap::T, 91.0f, 342.0f, 171.0f, 422.0f, TouchScreenController::key_down},\n    {TouchKeyMap::L, TouchKeyMap::T, 11.0f, 182.0f, 91.0f, 262.0f, TouchScreenController::key_upleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 171.0f, 182.0f, 251.0f, 262.0f, TouchScreenController::key_upright},\n    {TouchKeyMap::L, TouchKeyMap::T, 11.0f, 342.0f, 91.0f, 422.0f, TouchScreenController::key_downleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 171.0f, 342.0f, 251.0f, 422.0f, TouchScreenController::key_downright},\n    {TouchKeyMap::L, TouchKeyMap::T, 396.0f, 276.0f, 493.0f, 373.0f, TouchScreenController::key_run},\n    {TouchKeyMap::L, TouchKeyMap::T, 512.0f, 307.0f, 609.0f, 404.0f, TouchScreenController::key_jump},\n    {TouchKeyMap::L, TouchKeyMap::T, 416.0f, 163.0f, 513.0f, 260.0f, TouchScreenController::key_altrun},\n    {TouchKeyMap::L, TouchKeyMap::T, 531.0f, 191.0f, 628.0f, 288.0f, TouchScreenController::key_altjump},\n    {TouchKeyMap::L, TouchKeyMap::T, 196.0f, 12.0f, 312.0f, 52.0f, TouchScreenController::key_drop},\n    {TouchKeyMap::L, TouchKeyMap::T, 494.0f, 50.0f, 618.0f, 97.0f, TouchScreenController::key_holdRun},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 10.0f, 64.0f, 64.0f, TouchScreenController::key_toggleKeysView},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 66.0f, 64.0f, 110.0f, TouchScreenController::key_enterCheats},\n};\n\nstatic const TouchKeyMap::KeyPos c_averagePhoneMap[TouchScreenController::key_END] =\n{\n    /* Note that order of keys must match the TouchScreenController::commands enum!!! */\n    {TouchKeyMap::L, TouchKeyMap::T, 542.0f, 537.0f, 693.0f, 587.0f, TouchScreenController::key_start},\n    {TouchKeyMap::L, TouchKeyMap::T, 1.0f, 410.0f, 83.0f, 492.0f, TouchScreenController::key_left},\n    {TouchKeyMap::L, TouchKeyMap::T, 165.0f, 410.0f, 247.0f, 492.0f, TouchScreenController::key_right},\n    {TouchKeyMap::L, TouchKeyMap::T, 83.0f, 328.0f, 165.0f, 410.0f, TouchScreenController::key_up},\n    {TouchKeyMap::L, TouchKeyMap::T, 83.0f, 492.0f, 165.0f, 574.0f, TouchScreenController::key_down},\n    {TouchKeyMap::L, TouchKeyMap::T, 1.0f, 328.0f, 83.0f, 410.0f, TouchScreenController::key_upleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 165.0f, 328.0f, 247.0f, 410.0f, TouchScreenController::key_upright},\n    {TouchKeyMap::L, TouchKeyMap::T, 1.0f, 492.0f, 83.0f, 574.0f, TouchScreenController::key_downleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 165.0f, 492.0f, 247.0f, 574.0f, TouchScreenController::key_downright},\n    {TouchKeyMap::L, TouchKeyMap::T, 764.0f, 403.0f, 868.0f, 507.0f, TouchScreenController::key_run},\n    {TouchKeyMap::L, TouchKeyMap::T, 885.0f, 436.0f, 989.0f, 540.0f, TouchScreenController::key_jump},\n    {TouchKeyMap::L, TouchKeyMap::T, 786.0f, 287.0f, 890.0f, 391.0f, TouchScreenController::key_altrun},\n    {TouchKeyMap::L, TouchKeyMap::T, 904.0f, 317.0f, 1008.0f, 421.0f, TouchScreenController::key_altjump},\n    {TouchKeyMap::L, TouchKeyMap::T, 331.0f, 537.0f, 482.0f, 587.0f, TouchScreenController::key_drop},\n    {TouchKeyMap::L, TouchKeyMap::T, 827.0f, 129.0f, 943.0f, 169.0f, TouchScreenController::key_holdRun},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 10.0f, 70.0f, 70.0f, TouchScreenController::key_toggleKeysView},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 74.0f, 70.0f, 134.0f, TouchScreenController::key_enterCheats},\n};\n\nstatic const TouchKeyMap::KeyPos c_averagePhoneLongMap[TouchScreenController::key_END] =\n{\n    /* Note that order of keys must match the TouchScreenController::commands enum!!! */\n    {TouchKeyMap::L, TouchKeyMap::T, 727.0f, 632.0f, 911.0f, 691.0f, TouchScreenController::key_start},\n    {TouchKeyMap::L, TouchKeyMap::T, 5.0f, 444.0f, 106.0f, 545.0f, TouchScreenController::key_left},\n    {TouchKeyMap::L, TouchKeyMap::T, 207.0f, 444.0f, 308.0f, 545.0f, TouchScreenController::key_right},\n    {TouchKeyMap::L, TouchKeyMap::T, 106.0f, 343.0f, 207.0f, 444.0f, TouchScreenController::key_up},\n    {TouchKeyMap::L, TouchKeyMap::T, 106.0f, 545.0f, 207.0f, 646.0f, TouchScreenController::key_down},\n    {TouchKeyMap::L, TouchKeyMap::T, 5.0f, 343.0f, 106.0f, 444.0f, TouchScreenController::key_upleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 207.0f, 343.0f, 308.0f, 444.0f, TouchScreenController::key_upright},\n    {TouchKeyMap::L, TouchKeyMap::T, 5.0f, 545.0f, 106.0f, 646.0f, TouchScreenController::key_downleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 207.0f, 545.0f, 308.0f, 646.0f, TouchScreenController::key_downright},\n    {TouchKeyMap::L, TouchKeyMap::T, 1055.0f, 500.0f, 1183.0f, 628.0f, TouchScreenController::key_run},\n    {TouchKeyMap::L, TouchKeyMap::T, 1218.0f, 537.0f, 1346.0f, 665.0f, TouchScreenController::key_jump},\n    {TouchKeyMap::L, TouchKeyMap::T, 1085.0f, 351.0f, 1213.0f, 479.0f, TouchScreenController::key_altrun},\n    {TouchKeyMap::L, TouchKeyMap::T, 1242.0f, 389.0f, 1370.0f, 517.0f, TouchScreenController::key_altjump},\n    {TouchKeyMap::L, TouchKeyMap::T, 474.0f, 632.0f, 658.0f, 691.0f, TouchScreenController::key_drop},\n    {TouchKeyMap::L, TouchKeyMap::T, 1178.0f, 214.0f, 1315.0f, 275.0f, TouchScreenController::key_holdRun},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 10.0f, 85.0f, 85.0f, TouchScreenController::key_toggleKeysView},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 90.0f, 85.0f, 165.0f, TouchScreenController::key_enterCheats},\n};\n\nstatic const TouchKeyMap::KeyPos c_7_tablet[TouchScreenController::key_END] =\n{\n    /* Note that order of keys must match the TouchScreenController::commands enum!!! */\n    {TouchKeyMap::L, TouchKeyMap::T, 636.0f, 544.0f, 775.0f, 582.0f, TouchScreenController::key_start},\n    {TouchKeyMap::L, TouchKeyMap::T, 3.0f, 430.0f, 76.0f, 503.0f, TouchScreenController::key_left},\n    {TouchKeyMap::L, TouchKeyMap::T, 149.0f, 430.0f, 222.0f, 503.0f, TouchScreenController::key_right},\n    {TouchKeyMap::L, TouchKeyMap::T, 76.0f, 357.0f, 149.0f, 430.0f, TouchScreenController::key_up},\n    {TouchKeyMap::L, TouchKeyMap::T, 76.0f, 503.0f, 149.0f, 576.0f, TouchScreenController::key_down},\n    {TouchKeyMap::L, TouchKeyMap::T, 3.0f, 357.0f, 76.0f, 430.0f, TouchScreenController::key_upleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 149.0f, 357.0f, 222.0f, 430.0f, TouchScreenController::key_upright},\n    {TouchKeyMap::L, TouchKeyMap::T, 3.0f, 503.0f, 76.0f, 576.0f, TouchScreenController::key_downleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 149.0f, 503.0f, 222.0f, 576.0f, TouchScreenController::key_downright},\n    {TouchKeyMap::L, TouchKeyMap::T, 797.0f, 439.0f, 887.0f, 529.0f, TouchScreenController::key_run},\n    {TouchKeyMap::L, TouchKeyMap::T, 897.0f, 463.0f, 987.0f, 553.0f, TouchScreenController::key_jump},\n    {TouchKeyMap::L, TouchKeyMap::T, 819.0f, 341.0f, 909.0f, 431.0f, TouchScreenController::key_altrun},\n    {TouchKeyMap::L, TouchKeyMap::T, 919.0f, 363.0f, 1009.0f, 453.0f, TouchScreenController::key_altjump},\n    {TouchKeyMap::L, TouchKeyMap::T, 257.0f, 544.0f, 396.0f, 582.0f, TouchScreenController::key_drop},\n    {TouchKeyMap::L, TouchKeyMap::T, 873.0f, 226.0f, 968.0f, 258.0f, TouchScreenController::key_holdRun},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 10.0f, 58.0f, 58.0f, TouchScreenController::key_toggleKeysView},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 64.0f, 58.0f, 112.0f, TouchScreenController::key_enterCheats},\n};\n\nstatic const TouchKeyMap::KeyPos c_10_6_tablet[TouchScreenController::key_END] =\n{\n    /* Note that order of keys must match the TouchScreenController::commands enum!!! */\n    {TouchKeyMap::L, TouchKeyMap::T, 869.0f, 753.0f, 1002.0f, 785.0f, TouchScreenController::key_start},\n    {TouchKeyMap::L, TouchKeyMap::T, 19.0f, 591.0f, 89.0f, 661.0f, TouchScreenController::key_left},\n    {TouchKeyMap::L, TouchKeyMap::T, 159.0f, 591.0f, 229.0f, 661.0f, TouchScreenController::key_right},\n    {TouchKeyMap::L, TouchKeyMap::T, 89.0f, 521.0f, 159.0f, 591.0f, TouchScreenController::key_up},\n    {TouchKeyMap::L, TouchKeyMap::T, 89.0f, 661.0f, 159.0f, 731.0f, TouchScreenController::key_down},\n    {TouchKeyMap::L, TouchKeyMap::T, 19.0f, 521.0f, 89.0f, 591.0f, TouchScreenController::key_upleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 159.0f, 521.0f, 229.0f, 591.0f, TouchScreenController::key_upright},\n    {TouchKeyMap::L, TouchKeyMap::T, 19.0f, 661.0f, 89.0f, 731.0f, TouchScreenController::key_downleft},\n    {TouchKeyMap::L, TouchKeyMap::T, 159.0f, 661.0f, 229.0f, 731.0f, TouchScreenController::key_downright},\n    {TouchKeyMap::L, TouchKeyMap::T, 1047.0f, 588.0f, 1137.0f, 678.0f, TouchScreenController::key_run},\n    {TouchKeyMap::L, TouchKeyMap::T, 1159.0f, 624.0f, 1249.0f, 714.0f, TouchScreenController::key_jump},\n    {TouchKeyMap::L, TouchKeyMap::T, 1067.0f, 477.0f, 1157.0f, 567.0f, TouchScreenController::key_altrun},\n    {TouchKeyMap::L, TouchKeyMap::T, 1179.0f, 505.0f, 1269.0f, 595.0f, TouchScreenController::key_altjump},\n    {TouchKeyMap::L, TouchKeyMap::T, 256.0f, 753.0f, 389.0f, 785.0f, TouchScreenController::key_drop},\n    {TouchKeyMap::L, TouchKeyMap::T, 1131.0f, 387.0f, 1225.0f, 426.0f, TouchScreenController::key_holdRun},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 10.0f, 57.0f, 57.0f, TouchScreenController::key_toggleKeysView},\n    {TouchKeyMap::L, TouchKeyMap::T, 10.0f, 60.0f, 57.0f, 107.0f, TouchScreenController::key_enterCheats},\n};\n\n// watch your aspect ratios. they are preserved now!\n\n// based on c_10_6_tablet; no longer used.\n#if 0\nstatic const TouchKeyMap::KeyPos c_smallAutoMap[TouchScreenController::key_END] =\n{\n    /* Note that order of keys must match the TouchScreenController::commands enum!!! */\n    {TouchKeyMap::C, TouchKeyMap::B, 0.04, -0.08, 0.15, -0.04, TouchScreenController::key_start},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.015, -0.245, 0.105, -0.155, TouchScreenController::key_left},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.195, -0.245, 0.285, -0.155, TouchScreenController::key_right},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.105, -0.335, 0.195, -0.245, TouchScreenController::key_up},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.105, -0.155, 0.195, -0.065, TouchScreenController::key_down},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.015, -0.335, 0.105, -0.245, TouchScreenController::key_upleft},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.195, -0.335, 0.285, -0.245, TouchScreenController::key_upright},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.015, -0.155, 0.105, -0.065, TouchScreenController::key_downleft},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.195, -0.155, 0.285, -0.065, TouchScreenController::key_downright},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.265, -0.215, -0.165, -0.115, TouchScreenController::key_run},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.15, -0.20, -0.05, -0.10, TouchScreenController::key_jump},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.25, -0.33, -0.15, -0.23, TouchScreenController::key_altrun},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.135, -0.315, -0.035, -0.215, TouchScreenController::key_altjump},\n    {TouchKeyMap::C, TouchKeyMap::B, -0.1525, -0.08, -0.0375, -0.04, TouchScreenController::key_drop},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.12, -0.40, -0.045, -0.36, TouchScreenController::key_holdRun},\n    {TouchKeyMap::L, TouchKeyMap::T, 0.01, 0.01, 0.07, 0.07, TouchScreenController::key_toggleKeysView},\n    {TouchKeyMap::L, TouchKeyMap::T, 0.01, 0.08, 0.07, 0.14, TouchScreenController::key_enterCheats},\n};\n#endif\n\n// based on c_averagePhoneMap\nstatic const TouchKeyMap::KeyPos c_standardAutoMap[TouchScreenController::key_END] =\n{\n    /* Note that order of keys must match the TouchScreenController::commands enum!!! */\n    {TouchKeyMap::R, TouchKeyMap::B, -0.555f, -0.07f, -0.39f, -0.01f, TouchScreenController::key_start},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.01f, -0.34f, 0.15f, -0.20f, TouchScreenController::key_left},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.29f, -0.34f, 0.43f, -0.20f, TouchScreenController::key_right},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.15f, -0.48f, 0.29f, -0.34f, TouchScreenController::key_up},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.15f, -0.20f, 0.29f, -0.06f, TouchScreenController::key_down},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.01f, -0.48f, 0.15f, -0.34f, TouchScreenController::key_upleft},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.29f, -0.48f, 0.43f, -0.34f, TouchScreenController::key_upright},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.01f, -0.20f, 0.15f, -0.06f, TouchScreenController::key_downleft},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.29f, -0.20f, 0.43f, -0.06f, TouchScreenController::key_downright},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.375f, -0.275f, -0.225f, -0.125f, TouchScreenController::key_run},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.20f, -0.25f, -0.05f, -0.10f, TouchScreenController::key_jump},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.35f, -0.45f, -0.20f, -0.30f, TouchScreenController::key_altrun},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.175f, -0.425f, -0.025f, -0.275f, TouchScreenController::key_altjump},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.44f, -0.07f, 0.6125f, -0.01f, TouchScreenController::key_drop},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.20f, -0.56f, -0.05f, -0.48f, TouchScreenController::key_holdRun},\n    {TouchKeyMap::L, TouchKeyMap::T, 0.02f, 0.02f, 0.12f, 0.12f, TouchScreenController::key_toggleKeysView},\n    {TouchKeyMap::L, TouchKeyMap::T, 0.02f, 0.14f, 0.12f, 0.24f, TouchScreenController::key_enterCheats},\n};\n\n// based on c_4_tinyPhoneMap\nstatic const TouchKeyMap::KeyPos c_tightAutoMap[TouchScreenController::key_END] =\n{\n    /* Note that order of keys must match the TouchScreenController::commands enum!!! */\n    {TouchKeyMap::R, TouchKeyMap::T, -0.50f, 0.02f, -0.17f, 0.14f, TouchScreenController::key_start},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.01f, -0.37f, 0.18f, -0.20f, TouchScreenController::key_left},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.35f, -0.37f, 0.52f, -0.20f, TouchScreenController::key_right},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.18f, -0.54f, 0.35f, -0.37f, TouchScreenController::key_up},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.18f, -0.20f, 0.35f, -0.03f, TouchScreenController::key_down},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.01f, -0.54f, 0.18f, -0.37f, TouchScreenController::key_upleft},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.35f, -0.54f, 0.52f, -0.37f, TouchScreenController::key_upright},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.01f, -0.20f, 0.18f, -0.03f, TouchScreenController::key_downleft},\n    {TouchKeyMap::L, TouchKeyMap::B, 0.35f, -0.20f, 0.52f, -0.03f, TouchScreenController::key_downright},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.48f, -0.25f, -0.28f, -0.05f, TouchScreenController::key_run},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.25f, -0.22f, -0.05f, -0.02f, TouchScreenController::key_jump},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.45f, -0.48f, -0.25f, -0.28f, TouchScreenController::key_altrun},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.22f, -0.45f, -0.02f, -0.25f, TouchScreenController::key_altjump},\n    {TouchKeyMap::L, TouchKeyMap::T, 0.165f, 0.02f, 0.50f, 0.14f, TouchScreenController::key_drop},\n    {TouchKeyMap::R, TouchKeyMap::B, -0.245f, -0.62f, -0.02f, -0.50f, TouchScreenController::key_holdRun},\n    {TouchKeyMap::L, TouchKeyMap::T, 0.02f, 0.02f, 0.14f, 0.14f, TouchScreenController::key_toggleKeysView},\n    {TouchKeyMap::L, TouchKeyMap::T, 0.02f, 0.16f, 0.14f, 0.28f, TouchScreenController::key_enterCheats},\n};\n/*---------------------------------------------------------------------------------------*/\n\n// figures out the ratio between sides (such that smaller, usually y, axis size is 1)\n//   then handles all of the bottom/right anchored items\nstatic void updateTouchMap(int preferredLayout,\n                           float screenWidth, float screenHeight,\n                           int scaleFactor,\n                           int scaleFactorDPad,\n                           int scaleFactorButtons,\n                           int ssSpacing)\n{\n    switch(preferredLayout)\n    {\n    case(TouchScreenController::layout_old_tiny):\n        SDL_memcpy(g_touchKeyMap.touchKeysMap, c_4_tinyPhoneMap, sizeof(g_touchKeyMap.touchKeysMap));\n        g_touchKeyMap.touchCanvasWidth = 640.f;\n        g_touchKeyMap.touchCanvasHeight = 480.f;\n        break;\n\n    case(TouchScreenController::layout_old_average):\n        SDL_memcpy(g_touchKeyMap.touchKeysMap, c_averagePhoneMap, sizeof(g_touchKeyMap.touchKeysMap));\n        g_touchKeyMap.touchCanvasWidth = 1024.f;\n        g_touchKeyMap.touchCanvasHeight = 600.f;\n        break;\n\n    case(TouchScreenController::layout_old_long):\n        SDL_memcpy(g_touchKeyMap.touchKeysMap, c_averagePhoneLongMap, sizeof(g_touchKeyMap.touchKeysMap));\n        g_touchKeyMap.touchCanvasWidth = 1396.0f;\n        g_touchKeyMap.touchCanvasHeight = 720.0f;\n        break;\n\n    case(TouchScreenController::layout_old_phablet):\n        SDL_memcpy(g_touchKeyMap.touchKeysMap, c_7_tablet, sizeof(g_touchKeyMap.touchKeysMap));\n        g_touchKeyMap.touchCanvasWidth = 1024.f;\n        g_touchKeyMap.touchCanvasHeight = 600.f;\n        break;\n\n    case(TouchScreenController::layout_old_tablet):\n        SDL_memcpy(g_touchKeyMap.touchKeysMap, c_10_6_tablet, sizeof(g_touchKeyMap.touchKeysMap));\n        g_touchKeyMap.touchCanvasWidth = 1300.f;\n        g_touchKeyMap.touchCanvasHeight = 812.f;\n        break;\n\n    case(TouchScreenController::layout_tight):\n        SDL_memcpy(g_touchKeyMap.touchKeysMap, c_tightAutoMap, sizeof(g_touchKeyMap.touchKeysMap));\n        break;\n\n    case(TouchScreenController::layout_standard):\n    default:\n        SDL_memcpy(g_touchKeyMap.touchKeysMap, c_standardAutoMap, sizeof(g_touchKeyMap.touchKeysMap));\n        break;\n    }\n\n    // legacy layout\n    if(g_touchKeyMap.touchKeysMap[TouchScreenController::key_start].x1 > 2.f)\n        return;\n\n    if(screenWidth > screenHeight)\n    {\n        g_touchKeyMap.touchCanvasWidth = screenWidth / screenHeight;\n        g_touchKeyMap.touchCanvasHeight = 1.f;\n    }\n    else\n    {\n        g_touchKeyMap.touchCanvasWidth = 1.f;\n        g_touchKeyMap.touchCanvasHeight = screenHeight / screenWidth;\n    }\n\n    for(int i = 0; i < TouchScreenController::key_END; i++)\n    {\n        g_touchKeyMap.touchKeysMap[i].x1 *= scaleFactor / 100.f;\n        g_touchKeyMap.touchKeysMap[i].x2 *= scaleFactor / 100.f;\n        g_touchKeyMap.touchKeysMap[i].y1 *= scaleFactor / 100.f;\n        g_touchKeyMap.touchKeysMap[i].y2 *= scaleFactor / 100.f;\n\n        if(i >= TouchScreenController::key_left && i <= TouchScreenController::key_downright)\n        {\n            g_touchKeyMap.touchKeysMap[i].x1 *= scaleFactorDPad / 100.f;\n            g_touchKeyMap.touchKeysMap[i].x2 *= scaleFactorDPad / 100.f;\n            g_touchKeyMap.touchKeysMap[i].y1 *= scaleFactorDPad / 100.f;\n            g_touchKeyMap.touchKeysMap[i].y2 *= scaleFactorDPad / 100.f;\n        }\n        else if(i >= TouchScreenController::key_run && i <= TouchScreenController::key_altjump)\n        {\n            g_touchKeyMap.touchKeysMap[i].x1 *= scaleFactorButtons / 100.f;\n            g_touchKeyMap.touchKeysMap[i].x2 *= scaleFactorButtons / 100.f;\n            g_touchKeyMap.touchKeysMap[i].y1 *= scaleFactorButtons / 100.f;\n            g_touchKeyMap.touchKeysMap[i].y2 *= scaleFactorButtons / 100.f;\n        }\n\n        if(g_touchKeyMap.touchKeysMap[i].xa == TouchKeyMap::R)\n        {\n            g_touchKeyMap.touchKeysMap[i].x1 += g_touchKeyMap.touchCanvasWidth;\n            g_touchKeyMap.touchKeysMap[i].x2 += g_touchKeyMap.touchCanvasWidth;\n        }\n        else if(g_touchKeyMap.touchKeysMap[i].xa == TouchKeyMap::C)\n        {\n            g_touchKeyMap.touchKeysMap[i].x1 += g_touchKeyMap.touchCanvasWidth / 2;\n            g_touchKeyMap.touchKeysMap[i].x2 += g_touchKeyMap.touchCanvasWidth / 2;\n        }\n\n        if(g_touchKeyMap.touchKeysMap[i].ya == TouchKeyMap::B)\n        {\n            g_touchKeyMap.touchKeysMap[i].y1 += g_touchKeyMap.touchCanvasHeight;\n            g_touchKeyMap.touchKeysMap[i].y2 += g_touchKeyMap.touchCanvasHeight;\n        }\n        else if(g_touchKeyMap.touchKeysMap[i].ya == TouchKeyMap::C)\n        {\n            g_touchKeyMap.touchKeysMap[i].y1 += g_touchKeyMap.touchCanvasHeight / 2;\n            g_touchKeyMap.touchKeysMap[i].y2 += g_touchKeyMap.touchCanvasHeight / 2;\n        }\n\n        // Don't let hold-run button go lower than buttons\n        if(i == TouchScreenController::key_holdRun)\n        {\n            if(g_touchKeyMap.touchKeysMap[i].ya == TouchKeyMap::B)\n            {\n                float yb1 = g_touchKeyMap.touchKeysMap[TouchScreenController::key_altrun].y1;\n                float yb2 = g_touchKeyMap.touchKeysMap[TouchScreenController::key_altjump].y1;\n                float yb = SDL_min(yb1, yb2) - 0.05f;\n\n                // is aligning needed?\n                if(yb < g_touchKeyMap.touchKeysMap[i].y2)\n                {\n                    float o = g_touchKeyMap.touchKeysMap[i].y2;\n                    g_touchKeyMap.touchKeysMap[i].y2 = yb;\n                    g_touchKeyMap.touchKeysMap[i].y1 += (g_touchKeyMap.touchKeysMap[i].y2 - o);\n                }\n            }\n        }\n        // Compute spacing between start and select\n        else if(ssSpacing != 100 && (i == TouchScreenController::key_start || i == TouchScreenController::key_drop))\n        {\n            if(g_touchKeyMap.touchKeysMap[i].xa == TouchKeyMap::L)\n            {\n                float o = g_touchKeyMap.touchKeysMap[i].x1;\n                g_touchKeyMap.touchKeysMap[i].x1 *= 1.0f / (ssSpacing / 100.f);\n                g_touchKeyMap.touchKeysMap[i].x2 += (g_touchKeyMap.touchKeysMap[i].x1 - o);\n            }\n\n            if(g_touchKeyMap.touchKeysMap[i].xa == TouchKeyMap::R)\n            {\n                float o = g_touchKeyMap.touchKeysMap[i].x2;\n                float off = g_touchKeyMap.touchCanvasWidth - g_touchKeyMap.touchKeysMap[i].x2;\n                off *= 1.0f / (ssSpacing / 100.f);\n                g_touchKeyMap.touchKeysMap[i].x2 = g_touchKeyMap.touchCanvasWidth - off;\n                g_touchKeyMap.touchKeysMap[i].x1 += (g_touchKeyMap.touchKeysMap[i].x2 - o);\n            }\n        }\n    }\n}\n\n\nvoid TouchScreenController::doVibration()\n{\n    if(!m_vibrator)\n        return;\n\n    if(m_feedback_strength == 0.f)\n        return;\n\n    SDL_HapticRumblePlay(m_vibrator, m_feedback_strength, m_feedback_length);\n    D_pLogDebug(\"TouchScreen: Vibration %g, %d ms\", m_feedback_strength, m_feedback_length);\n}\n\nTouchScreenController::~TouchScreenController()\n{\n    if(m_vibrator)\n        SDL_HapticClose(m_vibrator);\n\n    m_vibrator = nullptr;\n}\n\nTouchScreenController::TouchScreenController() noexcept\n{\n    updateScreenSize();\n\n    pLogDebug(\"Initialization of touch-screen controller...\");\n\n    scanTouchDevices();\n\n    for(auto &d : m_devices)\n    {\n        for(int key = key_BEGIN; key < key_END; ++key)\n            d.keysHeld[key] = false;\n    }\n\n    m_vibrator = nullptr;\n    int numHaptics = SDL_NumHaptics();\n\n    const std::array<const char*, 3> allowlist = {\n        \"VIBRATOR_SERVICE\", // Android haptics device\n        \"spmi_haptics\",     // device name on sdm845 linux\n        \"gpio-vibrator\",    // device name on PinePhone and some Raspberry Pi-based devices\n    };\n\n    for(int i = 0; i < numHaptics; ++i)\n    {\n        bool is_allowed = false;\n        for(const char* allowed_device : allowlist)\n        {\n            if(SDL_strcmp(SDL_HapticName(i), allowed_device) == 0)\n            {\n                is_allowed = true;\n                break;\n            }\n        }\n\n        if(is_allowed)\n        {\n            m_vibrator = SDL_HapticOpen(i);\n\n            if(m_vibrator)\n            {\n                pLogDebug(\"TouchScreen: Opened haptics device %d [%s]\", i, SDL_HapticName(i));\n                SDL_HapticRumbleInit(m_vibrator);\n                break;\n            }\n            else\n                pLogWarning(\"TouchScreen: Can't open haptics device %d [%s]\", i, SDL_HapticName(i));\n        }\n        else\n            pLogInfo(\"TouchScreen: ignoring haptics device [%s]\", SDL_HapticName(i));\n    }\n}\n\nvoid TouchScreenController::scanTouchDevices()\n{\n    m_touchDevicesCount = SDL_GetNumTouchDevices();\n\n    if(m_touchDevicesCount > 0)\n    {\n        pLogDebug(\"Found %d touch devices, screen size: %d x %d\",\n                  m_touchDevicesCount,\n                  m_screenWidth, m_screenHeight);\n\n        m_devices.clear();\n        m_devices.resize(m_touchDevicesCount);\n\n        for(int i = 0; i < m_touchDevicesCount; ++i)\n        {\n            auto &d = m_devices[i];\n            d.id = SDL_GetTouchDevice(i);\n\n// At the SDL2 older than 2.30.8 is a bug that valid touch device was treated as invalid on Vita. This problem had been resolved since 2.30.8.\n// A similar problem appears at the 3DS on SDL2 before the 2.30.11\n#if (!defined(VITA) || SDL_VERSION_ATLEAST(2, 30, 8)) && (!defined(__3DS__) || SDL_VERSION_ATLEAST(2, 30, 11))\n            if(!d.id) // Invalid touch device, will be dropped from the list\n            {\n                d.id = -1;\n                pLogDebug(\"Touch device %d: <Invalid device>\", i);\n                continue;\n            }\n#endif\n\n#if SDL_VERSION_ATLEAST(2, 0, 10)\n            const char *typeText = \"Invalid type\";\n            SDL_TouchDeviceType ty = SDL_GetTouchDeviceType(d.id);\n\n            switch(ty)\n            {\n            default:\n            case SDL_TOUCH_DEVICE_INVALID: // invalid\n                break;\n            case SDL_TOUCH_DEVICE_DIRECT:\n                typeText = \"Direct touch type\";\n                break;\n            case SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE:\n                typeText = \"Indiriect absolute type\";\n                break;\n            case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE:\n                typeText = \"Indiriect relative type\";\n                break;\n            }\n\n            pLogDebug(\"Touch device %d (id=%d): %s\", i, (int)d.id, typeText);\n\n            if(ty != SDL_TOUCH_DEVICE_DIRECT)\n            {\n                d.id = -1; // Drop any indirect devices\n                continue;\n            }\n#endif\n\n            SDL_memset(d.keysHeld, 0, sizeof(d.keysHeld));\n        }\n\n        // Remove unnecessary devices from the list\n        for(auto it = m_devices.begin(); it != m_devices.end(); )\n        {\n            if(it->id == -1)\n                it = m_devices.erase(it);\n            else\n                ++it;\n        }\n\n        // Set number of touch devices to be equal to the `m_devices`\n        m_touchDevicesCount = (int)m_devices.size();\n    }\n\n    pLogDebug(\"Totally loaded valid touch devices: %d\", (int)m_devices.size());\n}\n\nvoid TouchScreenController::updateScreenSize()\n{\n    XWindow::getWindowSize(&m_screenWidth, &m_screenHeight);\n    XRender::getRenderSize(&m_renderWidth, &m_renderHeight);\n\n    updateTouchMap(m_layout,\n                   m_renderWidth, m_renderHeight,\n                   m_scale_factor,\n                   m_scale_factor_dpad,\n                   m_scale_factor_buttons,\n                   m_scale_factor_ss_spacing);\n}\n\nstatic void updateKeyValue(bool& key, bool state)\n{\n    key = state;\n    D_pLogDebug(\"= Touch key: State=%d\", (int)key);\n}\n\nstatic void updateFingerKeyState(TouchScreenController::FingerState& st,\n                                 Controls_t& keys, int keyCommand, bool setState, TouchScreenController::ExtraKeys_t& extraSt)\n{\n    st.alive = (setState != 0);\n\n    if(keyCommand >= static_cast<int>(TouchScreenController::key_BEGIN) && keyCommand < static_cast<int>(TouchScreenController::key_END))\n    {\n        switch(keyCommand)\n        {\n        case TouchScreenController::key_left:\n            updateKeyValue(keys.Left, setState);\n            break;\n\n        case TouchScreenController::key_right:\n            updateKeyValue(keys.Right, setState);\n            break;\n\n        case TouchScreenController::key_up:\n            updateKeyValue(keys.Up, setState);\n            break;\n\n        case TouchScreenController::key_down:\n            updateKeyValue(keys.Down, setState);\n            break;\n\n        case TouchScreenController::key_upleft:\n            updateKeyValue(keys.Up, setState);\n            updateKeyValue(keys.Left, setState);\n            break;\n\n        case TouchScreenController::key_upright:\n            updateKeyValue(keys.Up, setState);\n            updateKeyValue(keys.Right, setState);\n            break;\n\n        case TouchScreenController::key_downleft:\n            updateKeyValue(keys.Down, setState);\n            updateKeyValue(keys.Left, setState);\n            break;\n\n        case TouchScreenController::key_downright:\n            updateKeyValue(keys.Down, setState);\n            updateKeyValue(keys.Right, setState);\n            break;\n\n        case TouchScreenController::key_jump:\n            updateKeyValue(keys.Jump, setState);\n            break;\n\n        case TouchScreenController::key_altjump:\n            updateKeyValue(keys.AltJump, setState);\n            break;\n\n        case TouchScreenController::key_run:\n            extraSt.keyRunOnce = (setState & !keys.Run);\n            updateKeyValue(keys.Run, setState);\n            break;\n\n        case TouchScreenController::key_altrun:\n            extraSt.keyAltRunOnce = (setState & !keys.AltRun);\n            updateKeyValue(keys.AltRun, setState);\n            break;\n\n        case TouchScreenController::key_drop:\n            updateKeyValue(keys.Drop, setState);\n            break;\n\n        case TouchScreenController::key_start:\n            updateKeyValue(keys.Start, setState);\n            break;\n\n        case TouchScreenController::key_toggleKeysView:\n            extraSt.keyToggleViewOnce = (setState & !extraSt.keyToggleView);\n            extraSt.keyToggleView = setState;\n            break;\n\n        case TouchScreenController::key_holdRun:\n            extraSt.keyHoldRunOnce = (setState & !extraSt.keyHoldRun);\n            extraSt.keyHoldRun = setState;\n            break;\n\n        case TouchScreenController::key_enterCheats:\n            extraSt.keyCheats = setState;\n            break;\n\n        default:\n            break;\n        }\n    }\n    else\n        st.alive = false;\n}\n\nvoid TouchScreenController::processTouchDevice(TouchDevice_t& dev)\n{\n    auto& m_fingers = dev.fingers;\n    auto& keysHeld = dev.keysHeld;\n    auto& current_extra_keys = dev.extra_keys;\n    auto& current_keys = dev.current_keys;\n    int  fingers = SDL_GetNumTouchFingers(dev.id);\n\n    for(auto& m_finger : m_fingers)\n    {\n        // Mark as \"dead\"\n        m_finger.second.alive = false;\n    }\n\n    int n_real_fingers = 0;\n    double total_finger_x = 0;\n    double total_finger_y = 0;\n\n    for(int i = 0; i < fingers; i++)\n    {\n        SDL_Finger* f = SDL_GetTouchFinger(dev.id, i);\n\n        if(!f) //Skip a wrong finger\n            continue;\n\n        SDL_FingerID finger_id = f->id;\n        float finger_x = f->x, finger_y = f->y, finger_pressure = f->pressure;\n        (void)finger_pressure;\n\n        n_real_fingers++;\n        total_finger_x += finger_x;\n        total_finger_y += finger_y;\n\n        // track which finger is controlling the cursor\n        if(!m_wasScrolling && (m_cursorFinger == -1 || m_cursorFinger == finger_id))\n        {\n            m_cursorActive = true;\n            m_cursorFinger = finger_id;\n            int cX, cY;\n            XRender::mapToScreen(finger_x * m_screenWidth, finger_y * m_screenHeight, &cX, &cY);\n            m_cursorX = cX;\n            m_cursorY = cY;\n        }\n\n        auto found = m_fingers.find(finger_id);\n\n        if(found != m_fingers.end())\n        {\n            FingerState& fs = found->second;\n            int keysCount = g_touchKeyMap.findTouchKeys(finger_x, finger_y, fs);\n\n            for(int key = key_BEGIN; key < key_END; ++key)\n            {\n                if(fs.ignore)\n                    break;\n\n                if(m_touchHidden && key != key_toggleKeysView)\n                    continue;\n\n                if(fs.heldKeyPrev[key] && !fs.heldKey[key]) // set key off\n                {\n                    updateFingerKeyState(fs, current_keys, key, false, current_extra_keys);\n                    D_pLogDebug(\"= Finger Key ID=%d released (move)\", static_cast<int>(key));\n                    keysHeld[key] = false;\n                    fs.heldKeyPrev[key] = fs.heldKey[key];\n                }\n                else if(fs.heldKey[key]) // set key on and keep alive\n                {\n                    if(!fs.heldKeyPrev[key] /*&& fs.heldKey[key]*/) // Already true because of condition from above\n                        doVibration(); // Vibrate when key gets on\n\n                    updateFingerKeyState(fs, current_keys, key, true, current_extra_keys);\n                    D_pLogDebug(\"= Finger Key ID=%d pressed (move)\", static_cast<int>(key));\n                    keysHeld[key] = true;\n                    fs.heldKeyPrev[key] = fs.heldKey[key];\n                }\n            }\n\n            fs.alive = (keysCount > 0);\n        }\n        else\n        {\n            // Detect which key is pressed, and press it\n            FingerState st;\n            int keysCount = g_touchKeyMap.findTouchKeys(finger_x, finger_y, st);\n\n            for(int key = key_BEGIN; key < key_END; key++)\n            {\n                if(m_touchHidden && key != key_toggleKeysView)\n                    continue;\n\n                if(st.heldKey[key]) // set key on\n                {\n                    updateFingerKeyState(st, current_keys, key, true, current_extra_keys);\n                    D_pLogDebug(\"= Finger Key ID=%d pressed (put)\", static_cast<int>(key));\n                    doVibration();\n                    keysHeld[key] = true;\n                    st.heldKeyPrev[key] = st.heldKey[key];\n                }\n            }\n\n            st.alive = (keysCount > 0);\n\n            if(st.alive)\n            {\n                D_pLogDebug(\"= Finger ID=%d came\", static_cast<int>(finger_id));\n                m_fingers.insert({finger_id, st});\n            }\n        }\n\n        D_pLogDebug(\"= Finger press: ID=%d, X=%.04f, Y=%.04f, P=%.04f\",\n                    static_cast<int>(finger_id), finger_x, finger_y, finger_pressure);\n    }\n\n    for(auto it = m_fingers.begin(); it != m_fingers.end();)\n    {\n        if(!it->second.alive)\n        {\n            for(int key = key_BEGIN; key < key_END; key++)\n            {\n                if(it->second.heldKey[key]) // Key was previously held\n                {\n                    updateFingerKeyState(it->second, current_keys, key, false, current_extra_keys);\n                    D_pLogDebug(\"= Finger Key ID=%d released (take)\", static_cast<int>(key));\n                    keysHeld[key] = false;\n                }\n            }\n\n            D_pLogDebug(\"= Finger ID=%d has gone\", static_cast<int>(it->first));\n\n            it = m_fingers.erase(it);\n            continue;\n        }\n\n        ++it;\n    }\n\n    if(n_real_fingers > 1 && LevelEditor && !editorScreen.active)\n    {\n        int MeanX, MeanY;\n        XRender::mapToScreen(total_finger_x / n_real_fingers * m_screenWidth, total_finger_y / n_real_fingers * m_screenHeight, &MeanX, &MeanY);\n\n        if(m_wasScrolling)\n        {\n            m_scrollX += m_lastMeanX - MeanX;\n            m_scrollY += m_lastMeanY - MeanY;\n        }\n\n        m_lastMeanX = MeanX;\n        m_lastMeanY = MeanY;\n        m_scrollActive = true;\n    }\n\n    // Merge per-device states into common states:\n    for(int key = key_BEGIN; key < key_END; ++key)\n        m_keysHeld[key] |= keysHeld[key];\n\n    m_current_keys |= current_keys;\n    m_current_extra_keys |= current_extra_keys;\n}\n\nvoid TouchScreenController::update()\n{\n    if(!touchSupported())\n        return;\n\n    if(this->m_active_method)\n    {\n        auto* p = dynamic_cast<InputMethodProfile_TouchScreen*>(this->m_active_method->Profile);\n\n        if(p)\n        {\n            this->m_touchpad_style = p->m_touchpad_style;\n            this->m_enable_enter_cheats = p->m_enable_enter_cheats;\n\n            if(p->m_device_count <= 0)\n                p->m_device_count = this->m_touchDevicesCount;\n\n            if(this->m_feedback_strength != p->m_feedback_strength\n               || this->m_feedback_length != p->m_feedback_length)\n            {\n                this->m_feedback_strength = p->m_feedback_strength;\n                this->m_feedback_length = p->m_feedback_length;\n                this->doVibration();\n            }\n\n            if(this->m_hold_run != p->m_hold_run)\n            {\n                this->m_hold_run = p->m_hold_run;\n                this->m_runHeld = this->m_hold_run;\n            }\n\n            if(this->m_layout != p->m_layout)\n            {\n                this->m_layout = p->m_layout;\n                this->updateScreenSize();\n            }\n\n            if(this->m_scale_factor != p->m_scale_factor)\n            {\n                this->m_scale_factor = p->m_scale_factor;\n                this->updateScreenSize();\n            }\n\n            if(this->m_scale_factor_dpad != p->m_scale_factor_dpad)\n            {\n                this->m_scale_factor_dpad = p->m_scale_factor_dpad;\n                this->updateScreenSize();\n            }\n\n            if(this->m_scale_factor_buttons != p->m_scale_factor_buttons)\n            {\n                this->m_scale_factor_buttons = p->m_scale_factor_buttons;\n                this->updateScreenSize();\n            }\n\n            if(this->m_scale_factor_ss_spacing != p->m_scale_factor_ss_spacing)\n            {\n                this->m_scale_factor_ss_spacing = p->m_scale_factor_ss_spacing;\n                this->updateScreenSize();\n            }\n        }\n    }\n\n\n    bool cursor_was_held = m_cursorActive;\n    m_cursorActive = false;\n    m_scrollActive = false;\n\n    m_scrollX = 0;\n    m_scrollY = 0;\n\n    for(int key = key_BEGIN; key < key_END; ++key)\n        m_keysHeld[key] = false;\n\n    m_current_extra_keys = ExtraKeys_t();\n    m_current_keys = Controls_t();\n\n    // Process all working touch devices\n    for(auto& d : m_devices)\n        processTouchDevice(d);\n\n    // handle cursor for double-tap logic\n    if(!m_cursorActive && cursor_was_held)\n    {\n        m_lastCursorX = m_cursorX;\n        m_lastCursorY = m_cursorY;\n        m_cursorFinger = -1;\n    }\n\n\n    // handle scrolling and scroll momentum\n\n    if(m_scrollActive && m_wasScrolling)\n    {\n        m_scrollMomentumX = m_lastScrollX;\n        m_scrollMomentumY = m_lastScrollY;\n    }\n\n    if(m_scrollActive)\n    {\n        // need two counters because of an SDL bug where the frame\n        // a finger it is lifted its finger still exists at its old location\n        m_lastScrollX = m_scrollX;\n        m_lastScrollY = m_scrollY;\n    }\n\n    m_wasScrolling = m_scrollActive;\n\n    if(!m_scrollActive && m_scrollMomentumX != 0)\n    {\n        m_scrollMomentumX *= 0.97_r;\n        m_scrollX += m_scrollMomentumX;\n        if(m_scrollMomentumX > -0.1_n && m_scrollMomentumX < 0.1_n)\n            m_scrollMomentumX = 0;\n    }\n\n    if(!m_scrollActive && m_scrollMomentumY != 0)\n    {\n        m_scrollMomentumY *= 0.97_r;\n        m_scrollY += m_scrollMomentumY;\n        if(m_scrollMomentumY > -0.1_n && m_scrollMomentumY < 0.1_n)\n            m_scrollMomentumY = 0;\n    }\n\n    // handle special keys\n    if(m_current_extra_keys.keyToggleViewOnce)\n    {\n        m_touchHidden = !m_touchHidden;\n\n        if(!m_touchHidden)\n            SharedCursor.GoOffscreen();\n    }\n\n    if(m_current_extra_keys.keyHoldRunOnce)\n        m_runHeld = !m_runHeld;\n}\n\nvoid TouchScreenController::render(int player_no)\n{\n    if(!touchSupported())\n        return;\n\n    int style = m_touchpad_style;\n\n    for(int key = key_BEGIN; key < key_END; key++)\n    {\n        if((m_touchHidden && key != TouchScreenController::key_toggleKeysView) || (LoadingInProcess && !ScreenAssetPack::g_LoopActive) || LevelEditor)\n            continue;\n\n        const auto& k = g_touchKeyMap.touchKeysMap[key];\n        int x1 = round((k.x1 / g_touchKeyMap.touchCanvasWidth) * float(m_renderWidth));\n        int y1 = round((k.y1 / g_touchKeyMap.touchCanvasHeight) * float(m_renderHeight));\n        int x2 = round((k.x2 / g_touchKeyMap.touchCanvasWidth) * float(m_renderWidth));\n        int y2 = round((k.y2 / g_touchKeyMap.touchCanvasHeight) * float(m_renderHeight));\n        int w = x2 - x1;\n        int h = y2 - y1;\n\n        const uint8_t a = XTColor::from_num(m_keysHeld[key] ? 0.9_n : 0.3_n);\n\n        switch(key)\n        {\n        case TouchScreenController::key_toggleKeysView:\n            XRender::renderTextureScale(x1, y1, w, h,\n                                        m_GFX.touch[m_touchHidden ? TouchScreenGFX_t::BUTTON_VIEW_TOGGLE_OFF : TouchScreenGFX_t::BUTTON_VIEW_TOGGLE_ON],\n                                        XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_start:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[TouchScreenGFX_t::BUTTON_START], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_drop:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[TouchScreenGFX_t::BUTTON_DROP], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_up:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[TouchScreenGFX_t::BUTTON_UP], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_left:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[buttonLeft(player_no, style)], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_right:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[buttonRight(player_no, style)], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_down:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[TouchScreenGFX_t::BUTTON_DOWN], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_upleft:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[TouchScreenGFX_t::BUTTON_UPLEFT], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_upright:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[TouchScreenGFX_t::BUTTON_UPRIGHT], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_downleft:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[TouchScreenGFX_t::BUTTON_DOWNLEFT], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_downright:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[TouchScreenGFX_t::BUTTON_DOWNRIGHT], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_holdRun:\n            XRender::renderTextureScale(x1, y1, w, h,\n                                        m_GFX.touch[m_runHeld ? TouchScreenGFX_t::BUTTON_HOLD_RUN_ON : TouchScreenGFX_t::BUTTON_HOLD_RUN_OFF],\n                                        XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_jump:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[buttonA(player_no, style)], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_run:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[buttonX(player_no, style)], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_altjump:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[buttonB(player_no, style)], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_altrun:\n            XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[buttonY(player_no, style)], XTAlpha(a));\n            break;\n\n        case TouchScreenController::key_enterCheats:\n            if(m_enable_enter_cheats)\n                XRender::renderTextureScale(x1, y1, w, h, m_GFX.touch[TouchScreenGFX_t::BUTTON_ENTER_CHEATS], XTAlpha(a));\n\n            break;\n\n        default:\n            XRender::renderRect(x1, y1, w, h, {255, 0, 0, 80});\n            break;\n        }\n    }\n}\n\nvoid TouchScreenController::resetState()\n{\n    this->update();\n\n    for(auto &d : m_devices)\n    {\n        for(auto& state : d.fingers)\n            state.second.ignore = true;\n        d.current_keys = Controls_t();\n        d.extra_keys = ExtraKeys_t();\n        SDL_memset(d.keysHeld, 0, sizeof(d.keysHeld));\n    }\n\n    m_current_keys = Controls_t();\n    m_current_extra_keys = ExtraKeys_t();\n    SDL_memset(m_keysHeld, 0, sizeof(m_keysHeld));\n}\n\n/*====================================================*\\\n|| implementation for InputMethod_TouchScreen         ||\n\\*====================================================*/\n\nInputMethod_TouchScreen::~InputMethod_TouchScreen()\n{\n    InputMethodType_TouchScreen* t = dynamic_cast<InputMethodType_TouchScreen*>(this->Type);\n\n    if(!t)\n        return;\n\n    if(t->m_controller.m_active_method == this)\n        t->m_controller.m_active_method = nullptr;\n}\n\n// Update functions that set player controls (and editor controls)\n// based on current device input. Return false if device lost.\nbool InputMethod_TouchScreen::Update(int player, Controls_t& c, CursorControls_t& m, EditorControls_t& e, HotkeysPressed_t& h)\n{\n    auto* t = dynamic_cast<InputMethodType_TouchScreen*>(this->Type);\n\n    if(!t)\n        return false;\n\n    if(!t->m_controller.touchSupported())\n        return false;\n\n    c = t->m_controller.m_current_keys;\n\n    const TouchScreenController::ExtraKeys_t& te = t->m_controller.m_current_extra_keys;\n\n    if(GamePaused == PauseCode::None && !GameMenu && !GameOutro && !LevelSelect && t->m_controller.m_runHeld)\n    {\n        // Alt Run functions as normal\n        if(c.AltRun)\n        {\n            // make sure Alt Run activates properly\n            if(te.keyAltRunOnce)\n                c.AltRun = false;\n            c.Run = false;\n        }\n        else if(te.keyRunOnce)\n            c.Run = false;\n        else\n            c.Run |= true;\n    }\n\n    // use the touchscreen as a mouse if the buttons are currently hidden, or we are in LevelEditor mode\n    bool allowed = t->m_controller.m_touchHidden || LevelEditor;\n\n    if(allowed && t->m_controller.m_scrollActive)\n    {\n        m.GoOffscreen();\n        m.Move = true;\n    }\n    else if(allowed && t->m_controller.m_cursorActive)\n    {\n        if(LevelEditor || MagicHand)\n        {\n            // hold to preview, double-tap to click in main editor screen\n            if(editorScreen.active || t->m_controller.m_cursorY < 40)\n            {\n                m.Primary = true;\n            }\n            else if(t->m_controller.m_lastCursorX == -42\n                || (t->m_controller.m_cursorX - t->m_controller.m_lastCursorX > -10\n                && t->m_controller.m_cursorX - t->m_controller.m_lastCursorX < 10\n                && t->m_controller.m_cursorY - t->m_controller.m_lastCursorY > -10\n                && t->m_controller.m_cursorY - t->m_controller.m_lastCursorY < 10))\n            {\n                m.Primary = true;\n                // special value to allow drags\n                t->m_controller.m_lastCursorX = -42;\n            }\n        }\n        else\n        {\n            m.Primary = true;\n        }\n\n        if(t->m_controller.m_cursorX - m.X <= -1 || t->m_controller.m_cursorX - m.X >= 1\n           || t->m_controller.m_cursorY - m.Y <= -1 || t->m_controller.m_cursorY - m.Y >= 1)\n        {\n            m.Move = true;\n            m.X = t->m_controller.m_cursorX;\n            m.Y = t->m_controller.m_cursorY;\n        }\n\n        m.Touch = true;\n    }\n\n    // update editor scroll\n    if(t->m_controller.m_scrollX < 0)\n        e.ScrollLeft += -t->m_controller.m_scrollX;\n    else if(t->m_controller.m_scrollX > 0)\n        e.ScrollRight += t->m_controller.m_scrollX;\n    if(t->m_controller.m_scrollY < 0)\n        e.ScrollUp += -t->m_controller.m_scrollY;\n    else if(t->m_controller.m_scrollY > 0)\n        e.ScrollDown += t->m_controller.m_scrollY;\n\n    if(t->m_controller.m_current_extra_keys.keyCheats && t->m_controller.m_enable_enter_cheats)\n        h[Hotkeys::Buttons::EnterCheats] = player;\n\n    // auto show/hide depending on context\n    if(m_wasTextEntry && GamePaused != PauseCode::TextEntry)\n    {\n        t->m_controller.m_touchHidden = false;\n        m_wasTextEntry = false;\n    }\n    else if(!m_wasTextEntry && GamePaused == PauseCode::TextEntry)\n    {\n        t->m_controller.m_touchHidden = true;\n        m_wasTextEntry = true;\n    }\n\n    return true;\n}\n\nvoid InputMethod_TouchScreen::Rumble(int ms, float strength)\n{\n    InputMethodType_TouchScreen* t = dynamic_cast<InputMethodType_TouchScreen*>(this->Type);\n\n    if(!t)\n        return;\n\n    if(!t->m_controller.touchSupported())\n        return;\n\n    if(!t->m_controller.m_vibrator)\n        return;\n\n    pLogDebug(\"Trying to use SDL haptic rumble: %dms %f\", ms, strength);\n\n    if(SDL_HapticRumblePlay(t->m_controller.m_vibrator, strength, ms) == 0)\n        return;\n}\n\nStatusInfo InputMethod_TouchScreen::GetStatus()\n{\n    return XPower::devicePowerStatus();\n}\n\n/*====================================================*\\\n|| implementation for InputMethodProfile_TouchScreen  ||\n\\*====================================================*/\n\n// the job of this function is to initialize the class in a consistent state\nInputMethodProfile_TouchScreen::InputMethodProfile_TouchScreen()\n{\n    this->m_showPowerStatus = false;\n\n#ifdef __ANDROID__\n    if(s_screenSize >= 9.0) // Big tablets\n    {\n        m_default_layout = TouchScreenController::layout_standard;\n        m_default_scale_factor = 65;\n        m_default_scale_factor_dpad = 80;\n        m_default_scale_factor_buttons = 105;\n        m_default_scale_factor_ss_spacing = 110;\n    }\n    else if(s_screenSize >= 7.0) // Middle tablets\n    {\n        m_default_layout = TouchScreenController::layout_standard;\n        m_default_scale_factor = 95;\n        m_default_scale_factor_dpad = 80;\n        m_default_scale_factor_buttons = 105;\n        m_default_scale_factor_ss_spacing = 95;\n    }\n    else if(s_screenSize < 4.0) // Very small phones\n    {\n        m_default_layout = TouchScreenController::layout_tight;\n        m_default_scale_factor = 100;\n        m_default_scale_factor_dpad = 110;\n        m_default_scale_factor_buttons = 135;\n        m_default_scale_factor_ss_spacing = 75;\n    }\n    else // All other devices\n    {\n        // Longer screens (big ration between sides, more like a stick)\n        if((s_screenWidth / s_screenHeight) > 1.6f)\n        {\n            m_default_layout = TouchScreenController::layout_standard;\n            m_default_scale_factor = 135;\n            m_default_scale_factor_dpad = 80;\n            m_default_scale_factor_buttons = 100;\n            m_default_scale_factor_ss_spacing = 100;\n        }\n        else // Shorter screens (smaller ratio between sides, more like a square)\n        {\n            m_default_layout = TouchScreenController::layout_standard;\n            m_default_scale_factor = 135;\n            m_default_scale_factor_dpad = 80;\n            m_default_scale_factor_buttons = 100;\n            m_default_scale_factor_ss_spacing = 90;\n        }\n    }\n#endif\n}\n\nbool InputMethodProfile_TouchScreen::PollPrimaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return true;\n}\n\nbool InputMethodProfile_TouchScreen::PollSecondaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return true;\n}\n\nbool InputMethodProfile_TouchScreen::DeletePrimaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return true;\n}\n\nbool InputMethodProfile_TouchScreen::DeleteSecondaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return true;\n}\n\nconst char* InputMethodProfile_TouchScreen::NamePrimaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return g_controlsStrings.caseTouch.c_str();\n}\n\nconst char* InputMethodProfile_TouchScreen::NameSecondaryButton(ControlsClass c, size_t i)\n{\n    UNUSED(c);\n    UNUSED(i);\n    return \"\";\n}\n\nvoid InputMethodProfile_TouchScreen::SaveConfig(IniProcessing* ctl)\n{\n    ctl->setValue(\"ui-layout\", this->m_layout);\n    ctl->setValue(\"scale-factor\", this->m_scale_factor);\n    ctl->setValue(\"scale-factor-dpad\", this->m_scale_factor_dpad);\n    ctl->setValue(\"scale-factor-buttons\", this->m_scale_factor_buttons);\n    ctl->setValue(\"scale-factor-ss-spacing\", this->m_scale_factor_ss_spacing);\n    ctl->setValue(\"ui-style\", this->m_touchpad_style);\n    ctl->setValue(\"vibration-strength\", this->m_feedback_strength);\n    ctl->setValue(\"vibration-length\", this->m_feedback_length);\n    ctl->setValue(\"hold-run\", this->m_hold_run);\n    ctl->setValue(\"enable-enter-cheats\", this->m_enable_enter_cheats);\n}\n\nvoid InputMethodProfile_TouchScreen::LoadConfig(IniProcessing* ctl)\n{\n    ctl->read(\"ui-layout\", this->m_layout, this->m_default_layout);\n\n    if(this->m_layout >= TouchScreenController::layout_END)\n        this->m_layout = this->m_default_layout;\n\n    ctl->read(\"scale-factor\", this->m_scale_factor, this->m_default_scale_factor);\n    ctl->read(\"scale-factor-dpad\", this->m_scale_factor_dpad, this->m_default_scale_factor_dpad);\n    ctl->read(\"scale-factor-buttons\", this->m_scale_factor_buttons, this->m_default_scale_factor_buttons);\n    ctl->read(\"scale-factor-ss-spacing\", this->m_scale_factor_ss_spacing, this->m_default_scale_factor_ss_spacing);\n    ctl->read(\"ui-style\", this->m_touchpad_style, TouchScreenController::style_actions);\n    ctl->read(\"vibration-strength\", this->m_feedback_strength, 0.f);\n    ctl->read(\"vibration-length\", this->m_feedback_length, 12);\n    ctl->read(\"hold-run\", this->m_hold_run, false);\n    ctl->read(\"enable-enter-cheats\", this->m_enable_enter_cheats, false);\n}\n\n// How many per-type special options are there?\nsize_t InputMethodProfile_TouchScreen::GetOptionCount_Custom()\n{\n    return Options::COUNT;\n}\n\n// Methods to manage per-profile options\n// It is guaranteed that none of these will be called if\n// GetOptionCount_Custom() returns 0.\n// get a char* describing the option\nconst char* InputMethodProfile_TouchScreen::GetOptionName_Custom(size_t i)\n{\n    switch(i)\n    {\n    case Options::layout:\n        return g_controlsStrings.touchscreenOptionLayoutStyle.c_str();\n\n    case Options::scale_factor:\n        return g_controlsStrings.touchscreenOptionScaleFactor.c_str();\n\n    case Options::scale_factor_dpad:\n        return g_controlsStrings.touchscreenOptionScaleDPad.c_str();\n\n    case Options::scale_factor_buttons:\n        return g_controlsStrings.touchscreenOptionScaleButtons.c_str();\n\n    case Options::scale_factor_ss_spacing:\n        return g_controlsStrings.touchscreenOptionSStartSpacing.c_str();\n\n    case Options::reset_layout:\n        return g_controlsStrings.touchscreenOptionResetLayout.c_str();\n\n    case Options::style:\n        return g_controlsStrings.touchscreenOptionInterfaceStyle.c_str();\n\n    case Options::fb_strength:\n        return g_controlsStrings.touchscreenOptionFeedbackStrength.c_str();\n\n    case Options::fb_length:\n        return g_controlsStrings.touchscreenOptionFeedbackLength.c_str();\n\n    case Options::hold_run:\n        return g_controlsStrings.touchscreenOptionHoldRun.c_str();\n\n    case Options::enable_enter_cheats:\n        return g_controlsStrings.touchscreenOptionShowCodeButton.c_str();\n    }\n\n    return nullptr;\n}\n\n// get a char* describing the current option value\n// must be allocated in static or instance memory\n// WILL NOT be freed\nconst char* InputMethodProfile_TouchScreen::GetOptionValue_Custom(size_t i)\n{\n    static char length_buf[8];\n    static std::string delay_buf;\n\n    switch(i)\n    {\n    case Options::layout:\n        if(this->m_layout == TouchScreenController::layout_tight)\n            return g_controlsStrings.touchscreenLayoutTight.c_str();\n        else if(this->m_layout == TouchScreenController::layout_old_tiny)\n            return g_controlsStrings.touchscreenLayoutTinyOld.c_str();\n        else if(this->m_layout == TouchScreenController::layout_old_average)\n            return g_controlsStrings.touchscreenLayoutPhoneOld.c_str();\n        else if(this->m_layout == TouchScreenController::layout_old_long)\n            return g_controlsStrings.touchscreenLayoutLongOld.c_str();\n        else if(this->m_layout == TouchScreenController::layout_old_phablet)\n            return g_controlsStrings.touchscreenLayoutPhabletOld.c_str();\n        else if(this->m_layout == TouchScreenController::layout_old_tablet)\n            return g_controlsStrings.touchscreenLayoutTabletOld.c_str();\n        else\n            return g_controlsStrings.touchscreenLayoutStandard.c_str();\n\n    case Options::scale_factor:\n        SDL_snprintf(length_buf, 8, \"%d%%\", this->m_scale_factor);\n        return length_buf;\n\n    case Options::scale_factor_dpad:\n        SDL_snprintf(length_buf, 8, \"%d%%\", this->m_scale_factor_dpad);\n        return length_buf;\n\n    case Options::scale_factor_buttons:\n        SDL_snprintf(length_buf, 8, \"%d%%\", this->m_scale_factor_buttons);\n        return length_buf;\n\n    case Options::scale_factor_ss_spacing:\n        SDL_snprintf(length_buf, 8, \"%d%%\", this->m_scale_factor_ss_spacing);\n        return length_buf;\n\n    case Options::style:\n        if(this->m_touchpad_style == TouchScreenController::style_actions)\n            return g_controlsStrings.touchscreenStyleActions.c_str();\n        else if(this->m_touchpad_style == TouchScreenController::style_abxy)\n            return g_controlsStrings.touchscreenStyleABXY.c_str();\n        else\n            return g_controlsStrings.touchscreenStyleXODA.c_str();\n\n    case Options::fb_strength:\n        if(this->m_feedback_strength == 0.f)\n            return g_mainMenu.wordOff.c_str();\n        else if(this->m_feedback_strength == 0.25f)\n            return \"25%\";\n        else if(this->m_feedback_strength == 0.50f)\n            return \"50%\";\n        else if(this->m_feedback_strength == 0.75f)\n            return \"75%\";\n        else\n            return \"100%\";\n\n    case Options::fb_length:\n        delay_buf = std::to_string(this->m_feedback_length);\n        delay_buf += \" \";\n        delay_buf += g_mainMenu.abbrevMilliseconds;\n        return delay_buf.c_str();\n\n    case Options::hold_run:\n        if(this->m_hold_run)\n            return g_mainMenu.wordOn.c_str();\n        else\n            return g_mainMenu.wordOff.c_str();\n\n    case Options::enable_enter_cheats:\n        if(this->m_enable_enter_cheats)\n            return g_mainMenu.wordOn.c_str();\n        else\n            return g_mainMenu.wordOff.c_str();\n    }\n\n    return nullptr;\n}\n\n// called when A is pressed; allowed to interrupt main game loop\nbool InputMethodProfile_TouchScreen::OptionChange_Custom(size_t i)\n{\n    switch(i)\n    {\n    case Options::reset_layout:\n        this->m_layout = this->m_default_layout;\n        this->m_scale_factor = this->m_default_scale_factor;\n        this->m_scale_factor_dpad = this->m_default_scale_factor_dpad;\n        this->m_scale_factor_buttons = this->m_default_scale_factor_buttons;\n        this->m_scale_factor_ss_spacing = this->m_default_scale_factor_ss_spacing;\n        return true;\n\n    default:\n        return this->OptionRotateRight_Custom(i);\n    }\n}\n\n// called when left is pressed\nbool InputMethodProfile_TouchScreen::OptionRotateLeft_Custom(size_t i)\n{\n    switch(i)\n    {\n    case Options::layout:\n        if(this->m_layout > 0)\n            this->m_layout--;\n        else\n            this->m_layout = TouchScreenController::layout_END - 1;\n\n        return true;\n\n    case Options::scale_factor:\n        if(this->m_scale_factor > 50)\n        {\n            this->m_scale_factor -= 5;\n            return true;\n        }\n        else\n            return false;\n\n    case Options::scale_factor_dpad:\n        if(this->m_scale_factor_dpad > 50)\n        {\n            this->m_scale_factor_dpad -= 5;\n            return true;\n        }\n        else\n            return false;\n\n    case Options::scale_factor_buttons:\n        if(this->m_scale_factor_buttons > 50)\n        {\n            this->m_scale_factor_buttons -= 5;\n            return true;\n        }\n        else\n            return false;\n\n    case Options::scale_factor_ss_spacing:\n        if(this->m_scale_factor_ss_spacing > 20)\n        {\n            this->m_scale_factor_ss_spacing -= 5;\n            return true;\n        }\n        else\n            return false;\n\n    case Options::style:\n        if(this->m_touchpad_style > 0)\n            this->m_touchpad_style--;\n        else\n            this->m_touchpad_style = TouchScreenController::style_END - 1;\n\n        return true;\n\n    case Options::fb_strength:\n        if(this->m_feedback_strength > 0.f)\n        {\n            this->m_feedback_strength -= 0.25f;\n            return true;\n        }\n\n        return false;\n\n    case Options::fb_length:\n        if(this->m_feedback_length > 2)\n        {\n            this->m_feedback_length -= 2;\n            return true;\n        }\n\n        return false;\n\n    case Options::hold_run:\n        this->m_hold_run = !this->m_hold_run;\n        return true;\n\n    case Options::enable_enter_cheats:\n        this->m_enable_enter_cheats = !this->m_enable_enter_cheats;\n        return true;\n    }\n\n    return false;\n}\n\n// called when right is pressed\nbool InputMethodProfile_TouchScreen::OptionRotateRight_Custom(size_t i)\n{\n    switch(i)\n    {\n    case Options::layout:\n        this->m_layout++;\n\n        if(this->m_layout >= TouchScreenController::layout_END)\n            this->m_layout = 0;\n\n        return true;\n\n    case Options::scale_factor:\n        if(this->m_scale_factor < 150)\n        {\n            this->m_scale_factor += 5;\n            return true;\n        }\n        else\n            return false;\n\n    case Options::scale_factor_dpad:\n        if(this->m_scale_factor_dpad < 150)\n        {\n            this->m_scale_factor_dpad += 5;\n            return true;\n        }\n        else\n            return false;\n\n    case Options::scale_factor_buttons:\n        if(this->m_scale_factor_buttons < 150)\n        {\n            this->m_scale_factor_buttons += 5;\n            return true;\n        }\n        else\n            return false;\n\n    case Options::scale_factor_ss_spacing:\n        if(this->m_scale_factor_ss_spacing < 110)\n        {\n            this->m_scale_factor_ss_spacing += 5;\n            return true;\n        }\n        else\n            return false;\n\n    case Options::style:\n        this->m_touchpad_style ++;\n\n        if(this->m_touchpad_style >= TouchScreenController::style_END)\n            this->m_touchpad_style = 0;\n\n        return true;\n\n    case Options::fb_strength:\n        if(this->m_feedback_strength < 1.f)\n        {\n            this->m_feedback_strength += 0.25f;\n            return true;\n        }\n\n        return false;\n\n    case Options::fb_length:\n        this->m_feedback_length += 2;\n        return true;\n\n    case Options::hold_run:\n        this->m_hold_run = !this->m_hold_run;\n        return true;\n\n    case Options::enable_enter_cheats:\n        this->m_enable_enter_cheats = !this->m_enable_enter_cheats;\n        return true;\n    }\n\n    return false;\n}\n\n/*====================================================*\\\n|| implementation for InputMethodType_TouchScreen     ||\n\\*====================================================*/\n\nInputMethodProfile* InputMethodType_TouchScreen::AllocateProfile() noexcept\n{\n    return (InputMethodProfile*) new(std::nothrow) InputMethodProfile_TouchScreen;\n}\n\nInputMethodType_TouchScreen::InputMethodType_TouchScreen()\n{\n    this->Name = \"Touchscreen\";\n}\n\nconst std::string& InputMethodType_TouchScreen::LocalName() const\n{\n    return g_controlsStrings.nameTouchscreen;\n}\n\nbool InputMethodType_TouchScreen::TestProfileType(InputMethodProfile* profile)\n{\n    return (bool)dynamic_cast<InputMethodProfile_TouchScreen*>(profile);\n}\n\nbool InputMethodType_TouchScreen::RumbleSupported()\n{\n    return true;\n}\n\nbool InputMethodType_TouchScreen::ConsumeEvent(const SDL_Event* ev)\n{\n    // update the touch devices count as needed\n    if(ev->type == SDL_FINGERDOWN && !this->m_controller.touchSupported())\n        this->m_controller.scanTouchDevices();\n\n    return false;\n}\n\nvoid InputMethodType_TouchScreen::UpdateControlsPre()\n{\n    this->m_controller.update();\n}\n\nvoid InputMethodType_TouchScreen::UpdateControlsPost()\n{\n    bool method_exists = false;\n\n    for(InputMethod* m : g_InputMethods)\n    {\n        if(m)\n        {\n            method_exists = true;\n            break;\n        }\n    }\n\n    // deactivate the touchscreen rendering when there is no longer an active touchscreen,\n    //   but some other input method exists (ie, not the transition between menu and player add)\n    if(method_exists && this->m_controller.m_active_method == nullptr && g_renderTouchscreen)\n        g_renderTouchscreen = false;\n}\n\nInputMethod* InputMethodType_TouchScreen::Poll(const std::vector<InputMethod*>& active_methods) noexcept\n{\n    int n_touchscreens = 0;\n\n    for(InputMethod* method : active_methods)\n    {\n        if(!method)\n            continue;\n\n        InputMethod_TouchScreen* m = dynamic_cast<InputMethod_TouchScreen*>(method);\n\n        if(m)\n            n_touchscreens ++;\n    }\n\n    if(n_touchscreens > 0)\n    {\n        this->m_canPoll = false;\n        return nullptr;\n    }\n\n    if(!this->m_controller.touchSupported())\n    {\n        this->m_canPoll = false;\n        return nullptr;\n    }\n\n    // if didn't find any touch, allow poll in future but return nullptr\n    if(!this->m_controller.touchOn())\n    {\n        this->m_canPoll = true;\n        return nullptr;\n    }\n\n    // if poll not allowed, return false\n    if(!this->m_canPoll)\n        return nullptr;\n\n    // we're going to create a touchscreen method!\n    // reset canPoll for next time\n    this->m_canPoll = false;\n\n    InputMethod_TouchScreen* method = new(std::nothrow) InputMethod_TouchScreen;\n\n    if(!method)\n        return nullptr;\n\n    if(!g_renderTouchscreen)\n    {\n        this->m_controller.resetState();\n        SharedCursor.GoOffscreen();\n    }\n\n    method->Name = this->LocalName();\n    method->Type = this;\n\n    this->m_controller.m_active_method = method;\n    g_renderTouchscreen = true;\n\n    return (InputMethod*)method;\n}\n\nTouchScreenController::ExtraKeys_t& TouchScreenController::ExtraKeys_t::operator|=(const ExtraKeys_t& o)\n{\n    keyToggleView |= o.keyToggleView;\n    keyToggleViewOnce |= o.keyToggleViewOnce;\n\n    keyHoldRun |= o.keyHoldRun;\n    keyHoldRunOnce |= o.keyHoldRunOnce;\n\n    keyRunOnce |= o.keyRunOnce;\n    keyAltRunOnce |= o.keyAltRunOnce;\n\n    keyCheats |= o.keyCheats;\n\n    return *this;\n}\n\n} // namespace Controls\n"
  },
  {
    "path": "src/control/touchscreen.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef TOUCHSCREEN_H\n#define TOUCHSCREEN_H\n\n#include <map>\n#include <string>\n#include <vector>\n\n#include <SDL2/SDL_touch.h>\n\n#include \"../controls.h\"\n#include \"../std_picture.h\"\n\ntypedef struct _SDL_Haptic SDL_Haptic;\n\nnamespace Controls\n{\n\n\n// forward declaration for the TouchScreenController\nclass InputMethod_TouchScreen;\n\n/*!\n * \\brief A basic class including graphics for touchscreen controller\n */\n\nclass TouchScreenGFX_t\n{\n    void loadImage(StdPicture &img, const std::string &fileName);\n    std::string m_gfxPath;\n    int m_loadErrors = 0;\n\n    void loadAll();\n\npublic:\n    bool m_success = false;\n\n    TouchScreenGFX_t();\n\n    //! (re)load the touchscreen graphics from the current asset pack\n    void load();\n\n    enum\n    {\n        BUTTON_START = 0,\n        BUTTON_LEFT,\n        BUTTON_LEFT_CHAR,\n        BUTTON_RIGHT,\n        BUTTON_RIGHT_CHAR,\n        BUTTON_UP,\n        BUTTON_DOWN,\n        BUTTON_UPLEFT,\n        BUTTON_UPRIGHT,\n        BUTTON_DOWNLEFT,\n        BUTTON_DOWNRIGHT,\n        BUTTON_A,\n        BUTTON_A_PS,\n        BUTTON_A_BLANK,\n        BUTTON_A_DO,\n        BUTTON_A_ENTER,\n        BUTTON_A_JUMP,\n        BUTTON_B,\n        BUTTON_B_PS,\n        BUTTON_B_BLANK,\n        BUTTON_B_JUMP,\n        BUTTON_B_SPINJUMP,\n        BUTTON_X,\n        BUTTON_X_PS,\n        BUTTON_X_BACK,\n        BUTTON_X_BLANK,\n        BUTTON_X_BOMB,\n        BUTTON_X_BUMERANG,\n        BUTTON_X_FIRE,\n        BUTTON_X_HAMMER,\n        BUTTON_X_RUN,\n        BUTTON_X_SWORD,\n        BUTTON_Y,\n        BUTTON_Y_PS,\n        BUTTON_Y_BLANK,\n        BUTTON_Y_BOMB,\n        BUTTON_Y_BUMERANG,\n        BUTTON_Y_FIRE,\n        BUTTON_Y_HAMMER,\n        BUTTON_Y_RUN,\n        BUTTON_Y_STATUE,\n        BUTTON_Y_SWORD,\n        BUTTON_DROP,\n        BUTTON_HOLD_RUN_OFF,\n        BUTTON_HOLD_RUN_ON,\n        BUTTON_VIEW_TOGGLE_OFF,\n        BUTTON_VIEW_TOGGLE_ON,\n        BUTTON_ANALOG_BORDER,\n        BUTTON_ANALOG_STICK,\n        BUTTON_ENTER_CHEATS,\n        BUTTONS_END\n    };\n    StdPicture touch[BUTTONS_END];\n};\n\n/*!\n * \\brief A mobile touch-screen controller which reads state of the keyboard device\n */\nclass TouchScreenController\n{\n    //! Count of touch devices\n    int m_touchDevicesCount = 0;\n    //! Physical screen width (touchscreen coords)\n    int m_screenWidth = 0;\n    //! Physical screen height (touchscreen coords)\n    int m_screenHeight = 0;\n    //! Physical screen width (render coords)\n    int m_renderWidth = 0;\n    //! Physical screen height (render coords)\n    int m_renderHeight = 0;\n    //! Graphics for controller\n    TouchScreenGFX_t m_GFX;\n\npublic:\n    //! Vibrator used with the touch device as rumble and/or feedback\n    SDL_Haptic *m_vibrator = nullptr;\n\n    /*!\n     * \\brief Is touch-screen supported?\n     */\n    bool touchSupported();\n\n    /*!\n     * \\brief Number of detected touchscreen devices\n     * \\return Count of devices detected\n     */\n    int numDevices() const;\n\n    /*!\n     * \\brief Is touch-screen being touched?\n     */\n    bool touchOn();\n\n    enum commands\n    {\n        key_BEGIN = 0,\n        key_start = 0,\n        key_left,\n        key_right,\n        key_up,\n        key_down,\n        key_upleft,\n        key_upright,\n        key_downleft,\n        key_downright,\n        key_run,\n        key_jump,\n        key_altrun,\n        key_altjump,\n        key_drop,\n        key_holdRun,\n        key_toggleKeysView,\n        key_enterCheats,\n        key_END\n    };\n\n    enum styles\n    {\n        style_actions = 0,\n        style_abxy,\n        style_xoda,\n        style_END\n    };\n\n    enum layouts\n    {\n        layout_standard = 0,\n        layout_tight,\n        layout_old_tiny,\n        layout_old_average,\n        layout_old_long,\n        layout_old_phablet,\n        layout_old_tablet,\n        layout_END\n    };\n\n    // touchscreen settings (duplicated from InputMethodProfile_TouchScreen)\n    //! Current layout type\n    int m_layout = TouchScreenController::layout_standard;\n    //! General scale factor\n    int m_scale_factor = 100;\n    //! D-Pad exclusive scale factor\n    int m_scale_factor_dpad = 100;\n    //! Buttons scale factor\n    int m_scale_factor_buttons = 100;\n    //! Spacing between Select and Start\n    int m_scale_factor_ss_spacing = 100;\n    //! Current touchpad style\n    int m_touchpad_style = TouchScreenController::style_actions;\n    //! Feedback strength\n    float m_feedback_strength = 0.f;\n    //! Feedback length in milliseconds\n    int m_feedback_length = 12;\n    //! Hold run enabled\n    bool m_hold_run = false;\n    //! Show the enter cheats button\n    bool m_enable_enter_cheats = false;\n\n    // active InputMethod (nullable, used for configuration)\n    InputMethod *m_active_method = nullptr;\n\n    //! In-game controls pressed\n    Controls_t m_current_keys;\n    bool m_keysHeld[key_END] = {false};\n\n    bool m_cursorActive = false;\n    SDL_FingerID m_cursorFinger = -1;\n    int m_cursorX = 0, m_cursorY = 0;\n    int m_lastCursorX = -32;\n    int m_lastCursorY = -32;\n\n    bool m_scrollActive = false;\n    bool m_wasScrolling = false;\n    int m_lastMeanX = 0, m_lastMeanY = 0;\n    num_t m_scrollX = 0, m_scrollY = 0;\n    // needed because of an SDL bug where the frame a finger it is lifted its finger still exists at its old location\n    num_t m_lastScrollX = 0, m_lastScrollY = 0;\n    num_t m_scrollMomentumX = 0, m_scrollMomentumY = 0;\n\n    struct ExtraKeys_t\n    {\n        bool keyToggleView = false;\n        bool keyToggleViewOnce = false;\n\n        bool keyHoldRun = false;\n        bool keyHoldRunOnce = false;\n\n        bool keyRunOnce = false;\n        bool keyAltRunOnce = false;\n\n        bool keyCheats = false;\n\n        ExtraKeys_t &operator|=(const ExtraKeys_t& o);\n    } m_current_extra_keys;\n\n    //! Touch can be hidden by left-top corner to use virtual mouse\n    bool       m_touchHidden = false;\n    bool       m_runHeld = false;\n\n    struct FingerState\n    {\n        bool alive = false;\n        bool ignore = false;\n        bool heldKey[key_END] = {};\n        bool heldKeyPrev[key_END] = {};\n\n        FingerState();\n        FingerState(const FingerState &fs);\n        FingerState &operator=(const FingerState &fs);\n    };\n\nprivate:\n    typedef std::map<SDL_FingerID, FingerState> FingersMap;\n\n    struct TouchDevice_t\n    {\n        //! Touch device ID\n        SDL_TouchID id = -1;\n        //! Registered finger states\n        FingersMap  fingers;\n        //! States for control keys\n        Controls_t current_keys;\n        //! States for extra keys\n        ExtraKeys_t extra_keys;\n        //! Held finger states\n        bool keysHeld[key_END] = {false};\n    };\n\n    std::vector<TouchDevice_t> m_devices;\n\n    void doVibration();\n\n    /*!\n     * \\brief Read current state of touch controller\n     */\n    void processTouchDevice(TouchDevice_t &dev);\n\npublic:\n    /*!\n     * \\brief Constructor\n     */\n    TouchScreenController() noexcept;\n\n    /*!\n     * \\brief Destructor\n     */\n    ~TouchScreenController();\n\n    void scanTouchDevices();\n\n    void updateScreenSize();\n\n    void update();\n\n    void render(int player_no);\n\n    void resetState();\n\n    inline void loadGFX()\n    {\n        m_GFX.load();\n    }\n};\n\nclass InputMethod_TouchScreen : public InputMethod\n{\n    bool m_wasTextEntry = false;\n\npublic:\n    using InputMethod::Type;\n    using InputMethod::Profile;\n\n    ~InputMethod_TouchScreen();\n\n    // Update functions that set player controls (and editor controls)\n    // based on current device input. Return false if device lost.\n    bool Update(int player, Controls_t &c, CursorControls_t &m, EditorControls_t &e, HotkeysPressed_t &h);\n\n    void Rumble(int ms, float strength);\n\n    StatusInfo GetStatus();\n};\n\nclass InputMethodProfile_TouchScreen : public InputMethodProfile\n{\npublic:\n    using InputMethodProfile::Name;\n    using InputMethodProfile::Type;\n\n    // touchscreen settings\n    /* Screen-size depend defaults (computed on load) */\n    int m_default_layout = TouchScreenController::layout_standard;\n    int m_default_scale_factor = 100;\n    int m_default_scale_factor_dpad = 100;\n    int m_default_scale_factor_buttons = 100;\n    int m_default_scale_factor_ss_spacing = 100;\n\n    /* Current settings */\n    //! Current layout type\n    int m_layout = TouchScreenController::layout_standard;\n    //! General scale factor\n    int m_scale_factor = 100;\n    //! D-Pad exclusive scale factor\n    int m_scale_factor_dpad = 100;\n    //! Buttons scale factor\n    int m_scale_factor_buttons = 100;\n    //! Spacing between Select and Start\n    int m_scale_factor_ss_spacing = 100;\n    //! Current touchpad style\n    int m_touchpad_style = TouchScreenController::style_actions;\n    float m_feedback_strength = 0.f;\n    int m_feedback_length = 12;\n    bool m_hold_run = false;\n    bool m_enable_enter_cheats = false;\n    //! Count of touch devices, gets filled on initialization\n    int m_device_count = 0;\n\n    InputMethodProfile_TouchScreen();\n\n    // Polls a new (secondary) device button for the i'th player button\n    // Returns true on success and false if no button pressed\n    // Never allows two player buttons to bind to the same device button\n    bool PollPrimaryButton(ControlsClass c, size_t i);\n    bool PollSecondaryButton(ControlsClass c, size_t i);\n\n    // Deletes a primary button for the i'th button of class c (only called for non-Player buttons)\n    bool DeletePrimaryButton(ControlsClass c, size_t i);\n\n    // Deletes a secondary device button for the i'th button of class c\n    bool DeleteSecondaryButton(ControlsClass c, size_t i);\n\n    // Gets strings for the device buttons currently used for the i'th button of class c\n    const char *NamePrimaryButton(ControlsClass c, size_t i);\n    const char *NameSecondaryButton(ControlsClass c, size_t i);\n\n    // one can assume that the IniProcessing* is already in the correct group\n    void SaveConfig(IniProcessing *ctl);\n    void LoadConfig(IniProcessing *ctl);\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\n    struct Options\n    {\n        enum o\n        {\n            layout,\n            scale_factor,\n            scale_factor_dpad,\n            scale_factor_buttons,\n            scale_factor_ss_spacing,\n            reset_layout,\n            style,\n            fb_strength,\n            fb_length,\n            hold_run,\n            enable_enter_cheats,\n            COUNT\n        };\n    };\n\npublic:\n    // How many per-type special options are there?\n    size_t GetOptionCount_Custom();\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount_Custom() returns 0.\n    // get a char* describing the option\n    const char *GetOptionName_Custom(size_t i);\n    // get a char* describing the current option value\n    // must be allocated in static or instance memory\n    // WILL NOT be freed\n    const char *GetOptionValue_Custom(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange_Custom(size_t i);\n    // called when left is pressed\n    bool OptionRotateLeft_Custom(size_t i);\n    // called when right is pressed\n    bool OptionRotateRight_Custom(size_t i);\n};\n\nclass InputMethodType_TouchScreen : public InputMethodType\n{\nprivate:\n    bool m_canPoll = false;\n\n    InputMethodProfile *AllocateProfile() noexcept override;\n\npublic:\n    using InputMethodType::Name;\n    using InputMethodType::m_profiles;\n\n    TouchScreenController m_controller;\n\n    InputMethodType_TouchScreen();\n\n    const std::string& LocalName() const override;\n\n    bool TestProfileType(InputMethodProfile *profile) override;\n    bool RumbleSupported() override;\n    bool ConsumeEvent(const SDL_Event *ev) override;\n\n    void UpdateControlsPre() override;\n    void UpdateControlsPost() override;\n\n    // null if no input method is ready\n    // allocates the new InputMethod on the heap\n    InputMethod *Poll(const std::vector<InputMethod *> &active_methods) noexcept override;\n};\n\n} // namespace Controls\n\n#endif // TOUCHSCREEN_H\n"
  },
  {
    "path": "src/control/touchscreen.txt",
    "content": "{542.0f, 537.0f, 693.0f,  587.0f, TouchScreenController::key_start},\n{1.0f, 410.0f, 83.0f,  492.0f, TouchScreenController::key_left},\n{165.0f, 410.0f, 247.0f,  492.0f, TouchScreenController::key_right},\n{83.0f, 328.0f, 165.0f,  410.0f, TouchScreenController::key_up},\n{83.0f, 492.0f, 165.0f,  574.0f, TouchScreenController::key_down},\n{1.0f, 328.0f, 83.0f,  410.0f, TouchScreenController::key_upleft},\n{165.0f, 328.0f, 247.0f,  410.0f, TouchScreenController::key_upright},\n{1.0f, 492.0f, 83.0f,  574.0f, TouchScreenController::key_downleft},\n{165.0f, 492.0f, 247.0f,  574.0f, TouchScreenController::key_downright},\n{770.0f, 396.0f, 877.0f,  487.0f, TouchScreenController::key_run},\n{888.0f, 431.0f, 995.0f,  522.0f, TouchScreenController::key_jump},\n{780.0f, 290.0f, 887.0f,  381.0f, TouchScreenController::key_altrun},\n{898.0f, 325.0f, 1005.0f,  416.0f, TouchScreenController::key_altjump},\n{331.0f, 537.0f, 482.0f,  587.0f, TouchScreenController::key_drop},\n{807.0f, 150.0f, 914.0f,  180.0f, TouchScreenController::key_holdRun},\n{10.0f, 10.0f, 70.0f,  70.0f, TouchScreenController::key_toggleKeysView},\n"
  },
  {
    "path": "src/control_types.h",
    "content": "#ifndef CONTROL_TYPES_H\n\n#define CONTROL_TYPES_H\n\n#include \"numeric_types.h\"\n\n// Controls for the player\nstruct Controls_t\n{\n    bool Up = false;\n    bool Down = false;\n    bool Left = false;\n    bool Right = false;\n    bool Jump = false;\n    bool AltJump = false;\n    bool Run = false;\n    bool AltRun = false;\n    bool Drop = false;\n    bool Start = false;\n};\n\n// For controls hard-coded into devices,\n// like escape key on keyboard and back key on Android\nstruct SharedControls_t\n{\n    bool Pause = false;\n    bool LegacyPause = false; // modifier, only set if Pause is also set\n    bool ForcePause = false; // modifier, only set by the enter cheats hotkey to allow pausing at certain times\n    bool QuitCredits = false;\n    bool MenuUp = false;\n    bool MenuDown = false;\n    bool MenuLeft = false;\n    bool MenuRight = false;\n    bool MenuDo = false;\n    bool MenuBack = false;\n};\n\n// Control struct combined from shared controls and individual player controls, based on player controls' requested layout\nstruct MenuControls_t\n{\n    bool Up = false;\n    bool Down = false;\n    bool Left = false;\n    bool Right = false;\n    bool Do = false;\n    bool Back = false;\n    bool Erase = false;\n    bool Home = false;\n};\n\n// Each player has one; there is also a shared fallback mouse\nstruct CursorControls_t\n{\n    // using direct touch device; affects rendering\n    bool Touch = false;\n    // moved this frame\n    bool Move = false;\n    // position of cursor in screen coordinates\n    num_t X = -4000.0_n;\n    num_t Y = -4000.0_n;\n    // \"left button\" down\n    bool Primary = false;\n    // \"right button\" down\n    bool Secondary = false;\n    // \"middle button\" down\n    bool Tertiary = false;\n    // scroll up / down\n    bool ScrollUp = false;\n    bool ScrollDown = false;\n\n    // call to easily send cursor offscreen (on disconnect, etc)\n    inline void GoOffscreen()\n    {\n        X = -4000.0_n;\n        Y = -4000.0_n;\n        Move = true;\n    }\n};\n\n// Controls for the editor\nstruct EditorControls_t\n{\n    num_t ScrollUp = 0.0_n;\n    num_t ScrollDown = 0.0_n;\n    num_t ScrollLeft = 0.0_n;\n    num_t ScrollRight = 0.0_n;\n\n    bool FastScroll = false;\n\n    bool ModeSelect = false;\n    bool ModeErase = false;\n\n    bool NextSection = false;\n    bool PrevSection = false;\n\n    bool SwitchScreens = false;\n    bool TestPlay = false;\n};\n\n#endif // #ifndef CONTROL_TYPES_H\n"
  },
  {
    "path": "src/controls.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CONTROLS_H\n#define CONTROLS_H\n\n// forward declaration since some clients do not have SDL\ntypedef union SDL_Event SDL_Event;\n\n#include <string>\n#include <array>\n#include <vector>\n\n#include <IniProcessor/ini_processing.h>\n\n#include \"sdl_proxy/sdl_types.h\"\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"globals.h\"\n#include \"core/power.h\"\n\nextern Controls_t &operator|=(Controls_t &o1, const Controls_t &o2);\n\nnamespace Controls\n{\n\nusing XPower::StatusInfo;\n\nclass InputMethod;\nclass InputMethodProfile;\nclass InputMethodType;\n\nenum class ControlsClass\n{\n    None, Player, Cursor, Editor, Hotkey\n};\n\n// utility functions to access information from the Controls_t struct\nnamespace PlayerControls\n{\n// These attributes should be used by Input Methods and the game when possible\n// to allow them to function even if the controls structures change.\n\n// enumerate of the Player key indices (which are almost never used)\nenum Buttons : size_t\n{\n    Up = 0, Down, Left, Right, Jump, AltJump, Run, AltRun, Drop, Start, MAX\n};\n\nstatic constexpr size_t n_buttons = Buttons::MAX;\n\ninline const char *GetButtonName_INI(size_t i)\n{\n    switch(i)\n    {\n    case Buttons::Up:\n        return \"up\";\n    case Buttons::Down:\n        return \"down\";\n    case Buttons::Left:\n        return \"left\";\n    case Buttons::Right:\n        return \"right\";\n    case Buttons::Jump:\n        return \"jump\";\n    case Buttons::AltJump:\n        return \"altjump\";\n    case Buttons::Run:\n        return \"run\";\n    case Buttons::AltRun:\n        return \"altrun\";\n    case Buttons::Drop:\n        return \"drop\";\n    case Buttons::Start:\n        return \"start\";\n    default:\n        return \"NULL\";\n    }\n}\n\ninline const char *GetButtonName_UI_Init(size_t i)\n{\n    switch(i)\n    {\n    case Buttons::Up:\n        return \"Up\";\n    case Buttons::Down:\n        return \"Down\";\n    case Buttons::Left:\n        return \"Left\";\n    case Buttons::Right:\n        return \"Right\";\n    case Buttons::Jump:\n        return \"Jump\";\n    case Buttons::AltJump:\n        return \"Alt Jump\";\n    case Buttons::Run:\n        return \"Run\";\n    case Buttons::AltRun:\n        return \"Alt Run\";\n    case Buttons::Drop:\n        return \"Drop Item\";\n    case Buttons::Start:\n        return \"Start\";\n    default:\n        return \"NULL\";\n    }\n}\n\nextern std::array<std::string, Buttons::MAX> g_button_name_UI;\n\ninline const char *GetButtonName_UI(size_t i)\n{\n    if(i < Buttons::MAX)\n        return g_button_name_UI[i].c_str();\n\n    return \"NULL\";\n}\n\n// convenience function for accessing a button index\ninline bool &GetButton(Controls_t &c, size_t i)\n{\n    switch(i)\n    {\n    case Buttons::Up:\n        return c.Up;\n    case Buttons::Down:\n        return c.Down;\n    case Buttons::Left:\n        return c.Left;\n    case Buttons::Right:\n        return c.Right;\n    case Buttons::Jump:\n        return c.Jump;\n    case Buttons::AltJump:\n        return c.AltJump;\n    case Buttons::Run:\n        return c.Run;\n    case Buttons::AltRun:\n        return c.AltRun;\n    case Buttons::Drop:\n        return c.Drop;\n    case Buttons::Start:\n        return c.Start;\n    default:\n        SDL_assert(false); // -V654 // Made especially to fail on abnormal value\n        return c.Start;\n    }\n}\n\ninline bool GetButton(const Controls_t &c, size_t i)\n{\n    return GetButton(const_cast<Controls_t&>(c), i);\n}\n\n} // namespace (Controls::)PlayerControls\n\n// utility functions to access information from the CursorControls_t struct\n// note that CursorX and CursorY are analogue and do not directly correspond with\n// the virtual buttons CursorUp, CursorDown, CursorLeft, and CursorRight\nnamespace CursorControls\n{\n// These attributes should be used by Input Methods and the game when possible\n// to allow them to function even if the controls structures change.\n\n// enumerate of the Cursor key indices (which are almost never used)\nenum Buttons : size_t\n{\n    CursorUp = 0, CursorDown, CursorLeft, CursorRight, Primary,\n    Secondary, Tertiary, MAX\n};\n\nstatic constexpr size_t n_buttons = Buttons::MAX;\n\n\ninline const char *GetButtonName_INI(size_t i)\n{\n    switch(i)\n    {\n    case Buttons::CursorUp:\n        return \"cursor-up\";\n    case Buttons::CursorDown:\n        return \"cursor-down\";\n    case Buttons::CursorLeft:\n        return \"cursor-left\";\n    case Buttons::CursorRight:\n        return \"cursor-right\";\n    case Buttons::Primary:\n        return \"primary\";\n    case Buttons::Secondary:\n        return \"secondary\";\n    case Buttons::Tertiary:\n        return \"tertiary\";\n    default:\n        return \"NULL\";\n    }\n}\n\ninline const char *GetButtonName_UI_Init(size_t i)\n{\n    switch(i)\n    {\n    case Buttons::CursorUp:\n        return \"Mouse Up\";\n    case Buttons::CursorDown:\n        return \"Mouse Down\";\n    case Buttons::CursorLeft:\n        return \"Mouse Left\";\n    case Buttons::CursorRight:\n        return \"Mouse Right\";\n    case Buttons::Primary:\n        return \"Primary\";\n    case Buttons::Secondary:\n        return \"Secondary\";\n    case Buttons::Tertiary:\n        return \"Tertiary\";\n    default:\n        return \"NULL\";\n    }\n}\n\nextern std::array<std::string, Buttons::MAX> g_button_name_UI;\n\ninline const char *GetButtonName_UI(size_t i)\n{\n    if(i < Buttons::MAX)\n        return g_button_name_UI[i].c_str();\n\n    return \"NULL\";\n}\n\n// convenience function for accessing a button index\ninline bool &GetButton(CursorControls_t &c, size_t i)\n{\n    switch(i)\n    {\n    case Buttons::Primary:\n        return c.Primary;\n    case Buttons::Secondary:\n        return c.Secondary;\n    case Buttons::Tertiary:\n        return c.Tertiary;\n    case Buttons::CursorUp:\n    case Buttons::CursorDown:\n    case Buttons::CursorLeft:\n    case Buttons::CursorRight:\n    default:\n        SDL_assert(false); // -V654 // Made especially to fail on abnormal value\n        return c.Primary;\n    }\n}\n} // namespace (Controls::)CursorControls\n\n// utility functions to access information from the EditorControls_t struct\n// note that ScrollUp, ScrollDown, ScrollLeft, and ScrollRight are analogue and are set separately\nnamespace EditorControls\n{\n// These attributes should be used by Input Methods and the game when possible\n// to allow them to function even if the controls structures change.\n\n// enumerate of the Editor key indices (which are almost never used)\nenum Buttons : size_t\n{\n    ScrollUp = 0, ScrollDown, ScrollLeft, ScrollRight, FastScroll,\n    ModeSelect, ModeErase, PrevSection, NextSection, SwitchScreens, TestPlay, MAX\n};\n\nstatic constexpr size_t n_buttons = Buttons::MAX;\n\ninline const char *GetButtonName_INI(size_t i)\n{\n    switch(i)\n    {\n    case Buttons::ScrollUp:\n        return \"scroll-up\";\n    case Buttons::ScrollDown:\n        return \"scroll-down\";\n    case Buttons::ScrollLeft:\n        return \"scroll-left\";\n    case Buttons::ScrollRight:\n        return \"scroll-right\";\n    case Buttons::FastScroll:\n        return \"fast-scroll\";\n    case Buttons::ModeSelect:\n        return \"mode-select\";\n    case Buttons::ModeErase:\n        return \"mode-erase\";\n    case Buttons::PrevSection:\n        return \"prev-section\";\n    case Buttons::NextSection:\n        return \"next-section\";\n    case Buttons::SwitchScreens:\n        return \"switch-screens\";\n    case Buttons::TestPlay:\n        return \"test-play\";\n    default:\n        return \"NULL\";\n    }\n}\n\ninline const char *GetButtonName_UI_Init(size_t i)\n{\n    switch(i)\n    {\n    case Buttons::ScrollUp:\n        return \"Scrl Up\";\n    case Buttons::ScrollDown:\n        return \"Scrl Down\";\n    case Buttons::ScrollLeft:\n        return \"Scrl Left\";\n    case Buttons::ScrollRight:\n        return \"Scrl Right\";\n    case Buttons::FastScroll:\n        return \"Fast Scrl\";\n    case Buttons::ModeSelect:\n        return \"Mode Select\";\n    case Buttons::ModeErase:\n        return \"Mode Erase\";\n    case Buttons::PrevSection:\n        return \"Prev Sect\";\n    case Buttons::NextSection:\n        return \"Next Sect\";\n    case Buttons::SwitchScreens:\n        return \"Show Pane\";\n    case Buttons::TestPlay:\n        return \"Test Play\";\n    default:\n        return \"NULL\";\n    }\n}\n\nextern std::array<std::string, Buttons::MAX> g_button_name_UI;\n\ninline const char *GetButtonName_UI(size_t i)\n{\n    if(i < Buttons::MAX)\n        return g_button_name_UI[i].c_str();\n\n    return \"NULL\";\n}\n\n// convenience function for accessing a button index\ninline bool &GetButton(EditorControls_t &c, size_t i)\n{\n    switch(i)\n    {\n    case Buttons::FastScroll:\n        return c.FastScroll;\n    case Buttons::ModeSelect:\n        return c.ModeSelect;\n    case Buttons::ModeErase:\n        return c.ModeErase;\n    case Buttons::PrevSection:\n        return c.PrevSection;\n    case Buttons::NextSection:\n        return c.NextSection;\n    case Buttons::SwitchScreens:\n        return c.SwitchScreens;\n    case Buttons::TestPlay:\n        return c.TestPlay;\n    case Buttons::ScrollUp:\n    case Buttons::ScrollDown:\n    case Buttons::ScrollLeft:\n    case Buttons::ScrollRight:\n    default:\n        SDL_assert(false); // -V654 // Made especially to fail on abnormal value\n        return c.FastScroll;\n    }\n}\n} // namespace (Controls::)EditorControls\n\n// utility functions to handle hotkeys. can accommodate new hotkeys.\nnamespace Hotkeys\n{\n// These attributes should be used by Input Methods and the game when possible\n// to allow them to function even when hotkeys change / are added.\n\n// enumerate of the Hotkey indices (which are almost never used)\nenum Buttons : size_t\n{\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    Fullscreen,\n#endif\n#ifdef USE_SCREENSHOTS_AND_RECS\n    Screenshot,\n#endif\n#ifdef PGE_ENABLE_VIDEO_REC\n    RecordGif,\n#endif\n    VanillaCam,\n    DebugInfo, EnterCheats,\n    ToggleHUD, LegacyPause,\n#ifdef DEBUG_BUILD\n    ReloadLanguage,\n#endif\n    MAX\n};\n\nconstexpr size_t n_buttons = Buttons::MAX;\n\ninline const char *GetButtonName_INI(size_t i)\n{\n    switch(i)\n    {\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    case Buttons::Fullscreen:\n        return \"fullscreen\";\n#endif\n#ifdef USE_SCREENSHOTS_AND_RECS\n    case Buttons::Screenshot:\n        return \"screenshot\";\n#endif\n#ifdef PGE_ENABLE_VIDEO_REC\n    case Buttons::RecordGif:\n        return \"record-gif\";\n#endif\n    case Buttons::VanillaCam:\n        return \"vanilla-cam\";\n    case Buttons::DebugInfo:\n        return \"debug-info\";\n    case Buttons::EnterCheats:\n        return \"enter-cheats\";\n    case Buttons::ToggleHUD:\n        return \"toggle-hud\";\n    case Buttons::LegacyPause:\n        return \"legacy-pause\";\n#ifdef DEBUG_BUILD\n    case Buttons::ReloadLanguage:\n        return \"reload-language\";\n#endif\n    default:\n        return \"NULL\";\n    }\n}\n\ninline const char *GetButtonName_UI_Init(size_t i)\n{\n    switch(i)\n    {\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    case Buttons::Fullscreen:\n        return \"Fullscreen\";\n#endif\n#ifdef USE_SCREENSHOTS_AND_RECS\n    case Buttons::Screenshot:\n        return \"Screenshot\";\n#endif\n#ifdef PGE_ENABLE_VIDEO_REC\n    case Buttons::RecordGif:\n        return \"Record GIF\";\n#endif\n    case Buttons::DebugInfo:\n        return \"Debug Info\";\n    case Buttons::VanillaCam:\n        return \"Vanilla Cam\";\n    case Buttons::EnterCheats:\n        return \"Enter Code\";\n    case Buttons::ToggleHUD:\n        return \"Toggle HUD\";\n    case Buttons::LegacyPause:\n        return \"Old Pause\";\n#ifdef DEBUG_BUILD\n    case Buttons::ReloadLanguage:\n        return \"Reload lang\";\n#endif\n    default:\n        return \"NULL\";\n    }\n}\n\nextern std::array<std::string, Buttons::MAX> g_button_name_UI;\n\ninline const char *GetButtonName_UI(size_t i)\n{\n    if(i < Buttons::MAX)\n        return g_button_name_UI[i].c_str();\n\n    return \"NULL\";\n}\n\n// function for activating a hotkey\nvoid Activate(size_t i, int player = 0);\n} // namespace (Controls::)Hotkeys\n\nusing HotkeysPressed_t = std::array<int, Hotkeys::n_buttons>;\n\n// represents a particular bound input method\nclass InputMethod\n{\npublic:\n    // not required to be unique\n    std::string Name;\n\n    // Once an InputMethod has been returned by PollInputMethod,\n    //     the developer may trust that neither of these are null.\n    // The InputMethodType is responsible for setting Type to itself.\n    InputMethodType *Type = nullptr;\n    // PollInputMethod is responsible for setting Profile;\n    //     but the game may later change it depending on user interaction.\n    InputMethodProfile *Profile = nullptr;\n\n    // true if it's been explicitly used by any player since being added\n    bool used_for_player = false;\n\n    virtual ~InputMethod();\n\n    // Update function that sets player controls, cursor controls, and editor controls,\n    // as well as optionally processing hotkeys, based on current device input.\n    // Remember that hotkeys should only process once per hotkey press, so it may be easier\n    // to put them in ConsumeEvent.\n    // Return false if device lost.\n    virtual bool Update(int player, Controls_t &c, CursorControls_t &m, EditorControls_t &e, HotkeysPressed_t &h) = 0;\n\n    virtual void Rumble(int ms, float strength) = 0;\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\n\n    // Used for battery, latency, etc\n    // If the char* member is dynamically generated, you MUST use an instance-owned buffer\n    // This will NOT be freed\n    // returning a null pointer is allowed\n    virtual StatusInfo GetStatus();\n\n    // Optional function allowing developer to consume an SDL event (on SDL clients)\n    //     usually used for hotkeys or for connect/disconnect events.\n    // Called (1) in order of InputMethodTypes, then (2) in order of InputMethods\n    // Returns true if event is consumed, false if other InputMethodTypes and InputMethods\n    //     should receive it.\n    virtual bool ConsumeEvent(const SDL_Event *ev);\n};\n\n// represents an input method profile for a particular input class\n// some input method classes (keyboard) might disallow multiple\n//   players using the same input profile, others (gamepad)\n//   might disallow them using the same hardware device\nclass InputMethodProfile\n{\npublic:\n    // strongly encouraged to be unique, but nothing will break if it isn't\n    std::string Name;\n\n    // The InputMethodType that creates an InputMethodProfile\n    // is responsible for setting Type to itself.\n    InputMethodType *Type = nullptr;\n\n    virtual ~InputMethodProfile();\n\n    /*----------------------*\\\n    || SHARED CONFIGURATION ||\n    \\*----------------------*/\n\n    struct CommonOptions\n    {\n        enum co\n        {\n            rumble = 0,\n            show_power_status,\n            alt_menu_controls,\n            COUNT\n        };\n    };\n\n    bool m_rumbleEnabled = false;\n    bool m_showPowerStatus = false;\n    bool m_altMenuControls = false;\n\n    // assume that the IniProcessing* is already in the correct group\n    // saves/loads the shared options and calls the device-specific Save/LoadConfig\n    void SaveConfig_All(IniProcessing *ctl);\n    void LoadConfig_All(IniProcessing *ctl);\n\n    // each of these delegate to the _Custom methods.\n    // How many per-profile special options are there?\n    size_t GetOptionCount();\n\n    // Methods to manage per-profile options\n    // get a nullable char* describing the option\n    const char *GetOptionName(size_t i);\n    // get a nullable char* describing the current option value\n    // must be allocated in static or instance memory\n    const char *GetOptionValue(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    bool OptionChange(size_t i);\n    // called when left is pressed\n    bool OptionRotateLeft(size_t i);\n    // called when right is pressed\n    bool OptionRotateRight(size_t i);\n\n    /*-------------------------*\\\n    || PURE VIRTUAL FUNCTIONS  ||\n    \\*-------------------------*/\n    // Polls a new primary/secondary device button for the i'th button of class c\n    // Returns true on success and false if no button currently pressed\n    // Never allows two player buttons to bind to the same device button\n    // Expected to use ALL of the devices available to the InputMethodType\n    virtual bool PollPrimaryButton(ControlsClass c, size_t i) = 0;\n    virtual bool PollSecondaryButton(ControlsClass c, size_t i) = 0;\n\n    // Deletes a primary button for the i'th button of class c (only called for non-Player buttons)\n    virtual bool DeletePrimaryButton(ControlsClass c, size_t i) = 0;\n\n    // Deletes a secondary device button for the i'th button of class c\n    virtual bool DeleteSecondaryButton(ControlsClass c, size_t i) = 0;\n\n    // Gets strings for the device buttons currently used for the i'th button of class c\n    virtual const char *NamePrimaryButton(ControlsClass c, size_t i) = 0;\n    virtual const char *NameSecondaryButton(ControlsClass c, size_t i) = 0;\n\n    // assume that the IniProcessing* is already in the correct group\n    // saves/loads the controls and the device-specific options\n    virtual void SaveConfig(IniProcessing *ctl) = 0;\n    virtual void LoadConfig(IniProcessing *ctl) = 0;\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\n    // How many device-specific per-profile special options are there?\n    virtual size_t GetOptionCount_Custom();\n\n    // Methods to manage device-specific per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount_Custom() returns 0.\n    // get a nullable char* describing the option\n    virtual const char *GetOptionName_Custom(size_t i);\n    // get a nullable char* describing the current option value\n    // must be allocated in static or instance memory\n    virtual const char *GetOptionValue_Custom(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    virtual bool OptionChange_Custom(size_t i);\n    // called when left is pressed\n    virtual bool OptionRotateLeft_Custom(size_t i);\n    // called when right is pressed\n    virtual bool OptionRotateRight_Custom(size_t i);\n};\n\n// represents a class of input devices, such as keyboard, SDL gamepad,\n//   or 3DS input. one of each class is instantiated in Controls::Init()\nclass InputMethodType\n{\nprotected:\n    // OWNED by InputMethodType\n    std::vector<InputMethodProfile *> m_profiles;\n    // nullable\n    InputMethodProfile *m_defaultProfiles[maxLocalPlayers] = {nullptr};\n\n    /*-------------------------*\\\n    || PURE VIRTUAL FUNCTION   ||\n    \\*-------------------------*/\n    // function that must be declared per-type because each\n    //     InputMethodType has its own InputMethodProfile class.\n    virtual InputMethodProfile *AllocateProfile() noexcept = 0;\n\npublic:\n    // absolutely required to be unique, because it is used to identify configuration. DO NOT LOCALIZE.\n    std::string Name;\n    // not required to be defined, refers to original \"player-X-keyboard\", etc, configurations.\n    std::string LegacyName;\n\n    // InputMethodType frees its InputMethodProfiles in its destructor\n    virtual ~InputMethodType();\n\n    // returns localized name based on translations, possibly to name individual methods and profiles\n    virtual const std::string& LocalName() const;\n\n    std::vector<InputMethodProfile *> GetProfiles();\n    // returns a pointer to the new profile on success\n    InputMethodProfile *AddProfile();\n    // ensures that the profile is not in use by any active methods, and that it is not a default profile.\n    // returns false if this would delete the last remaining profile, and any are in use.\n    bool DeleteProfile(InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods);\n    // succeeds and returns true if no profiles are in use.\n    // otherwise, leaves one profile allocated.\n    bool ClearProfiles(const std::vector<InputMethod *> &active_methods);\n\nprotected:\n    void SetDefaultProfile(int player_no, InputMethodProfile *profile);\npublic:\n    InputMethodProfile *GetDefaultProfile(int player_no);\n    bool SetProfile(InputMethod *method, int player_no, InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods);\n\n    void SaveConfig(IniProcessing *ctl);\n    void LoadConfig(IniProcessing *ctl);\n\n    /*-------------------------*\\\n    || PURE VIRTUAL FUNCTIONS  ||\n    \\*-------------------------*/\n    // Tests whether a profile is of the corresponding type using RTTI\n    virtual bool TestProfileType(InputMethodProfile *profile) = 0;\n\n    // Returns true if Rumble is ever supported\n    virtual bool RumbleSupported() = 0;\n\n    // Returns true if Power Status is ever supported\n    virtual bool PowerStatusSupported()\n    {\n        return false;\n    }\n\n    // Hooks that are called before and after Player controls are updated\n    // Update any information that the InputMethods will reference\n    virtual void UpdateControlsPre() = 0;\n    // Do post-update calls to global methods,\n    //     emergency sets of the menu controls, etc.\n    // This is where shared controls should be modified.\n    virtual void UpdateControlsPost() = 0;\n\n    // null if no new input method is ready\n    // allocates the new InputMethod on the heap\n    // do not activate any methods using controllers or keysets that are already in active_methods\n    // must set the InputMethod's Name, and set its Type to the InputMethod Type\n    // may optionally set the InputMethod's Profile\n    virtual InputMethod *Poll(const std::vector<InputMethod *> &active_methods) noexcept = 0;\n\n    /*-----------------------*\\\n    || OPTIONAL METHODS      ||\n    \\*-----------------------*/\npublic:\n    // Optional function allowing developer to consume an SDL event (on SDL clients)\n    //     usually used for hotkeys or for connect/disconnect events.\n    // Called (1) in order of InputMethodTypes, then (2) in order of InputMethods\n    // Returns true if event is consumed, false if other InputMethodTypes and InputMethods\n    //     should receive it.\n    virtual bool ConsumeEvent(const SDL_Event *ev);\n\nprotected:\n    // optional function allowing developer to associate device information with profile, etc\n    // if developer wants to forbid assignment, return false\n    virtual bool SetProfile_Custom(InputMethod *method, int player_no, InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods);\n    // unregisters any references to the profile before final deallocation\n    // returns false to prevent deletion if this is impossible\n    virtual bool DeleteProfile_Custom(InputMethodProfile *profile, const std::vector<InputMethod *> &active_methods);\n\npublic:\n    // How many per-type special options are there?\n    virtual size_t GetOptionCount();\n\n    // Methods to manage per-profile options\n    // It is guaranteed that none of these will be called if\n    // GetOptionCount() returns 0.\n    // get a (nullable) char* describing the option\n    virtual const char *GetOptionName(size_t i);\n    // get a (nullable) char* describing the current option value\n    // if not null, must be allocated in static or instance memory\n    // WILL NOT be freed\n    virtual const char *GetOptionValue(size_t i);\n    // called when A is pressed; allowed to interrupt main game loop\n    virtual bool OptionChange(size_t i);\n    // called when left is pressed\n    virtual bool OptionRotateLeft(size_t i);\n    // called when right is pressed\n    virtual bool OptionRotateRight(size_t i);\n\nprotected:\n    virtual void SaveConfig_Custom(IniProcessing *ctl);\n    virtual void LoadConfig_Custom(IniProcessing *ctl);\n};\n\n// initialize the strings for localization (normally called by Init())\nvoid InitStrings();\n\n// allocate InputMethodTypes according to system configuration\nvoid Init();\n\n// free all InputMethodTypes, InputMethodProfiles (implicitly), and InputMethods\nvoid Quit();\n\n// (for SDL clients) process SDL_Event using active InputMethodTypes\n// return true if successfully processed, false if unrecognized\nbool ProcessEvent(const SDL_Event *ev);\n\n// 1. Calls the UpdateControlsPre hooks of currently active InputMethodTypes\n//    a. Syncs hardware state as needed\n// 2. Updates Player and Editor controls by calling currently bound InputMethods\n//    a. May call or set changeScreen, frmMain.toggleGifRecorder, TakeScreen, g_stats.enabled, etc\n// 3. Calls the UpdateControlsPost hooks of currently active InputMethodTypes\n//    a. May call or set changeScreen, frmMain.toggleGifRecorder, TakeScreen, g_stats.enabled, etc\n//    b. If GameMenu or GameOutro is set, may update controls or Menu variables using hardcoded keys\n// 4. Updates speedrun and recording modules\n// 5. Resolves inconsistent control states (Left+Right, etc)\n//\n// If `check_lost_devices` is set and any of the devices were lost, removes the corresponding input method and returns false.\nbool Update(bool check_lost_devices = true);\n\nvoid SaveConfig(IniProcessing *ctl);\nvoid LoadConfig(IniProcessing *ctl);\nvoid SaveConfig();\nvoid LoadConfig();\n\n// null if no input method is ready and being pressed\n// allocates the new InputMethod on the heap\nInputMethod *PollInputMethod() noexcept;\nvoid DeleteInputMethod(InputMethod *method);\nvoid DeleteInputMethodSlot(int slot);\nbool SetInputMethodProfile(int slot, InputMethodProfile *profile);\nbool SetInputMethodProfile(InputMethod *method, InputMethodProfile *profile);\nvoid ClearInputMethods();\nvoid RemoveNullInputMethods();\n\n// Resolve current menu controls\nMenuControls_t GetMenuControls(int limit_player = 0);\n\n// player is 1-indexed as an actual player here\nvoid Rumble(int player, int ms, float strength);\nvoid RumbleAllPlayers(int ms, float strength);\n\n// player is 0-indexed as an input method here\nStatusInfo GetStatus(int l_player_i);\n\nvoid RenderTouchControls();\nvoid UpdateTouchScreenSize();\nvoid LoadTouchScreenGFX();\n\n// global variables at bottom\n\n// a nulled pointer in here signifies a player whose controller has disconnected\nextern std::vector<InputMethod *> g_InputMethods;\n// no nulled pointers here\nextern std::vector<InputMethodType *> g_InputMethodTypes;\nextern bool g_renderTouchscreen;\nextern HotkeysPressed_t g_hotkeysPressed;\nextern bool g_disallowHotkeys;\n\n// FIXME: this should become read-only and be renamed LocalControls, and there should be a separate NetControls (indexed by player) that occurs post XMessage, used by the speedrun controls display.\n// raw controls values for the current locally-connected players, with no processing. may be publicly accessed only for menu logic\nextern std::array<Controls_t, maxLocalPlayers> g_RawControls;\n\n} // namespace Controls\n\n#endif // CONTROLS_H\n"
  },
  {
    "path": "src/custom.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"global_dirs.h\"\n#include \"load_gfx.h\"\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"custom.h\"\n#include \"config.h\"\n#include \"npc_traits.h\"\n#include \"npc_id.h\"\n#include \"blk_id.h\"\n\n#include <utility>\n\n#include <IniProcessor/ini_processing.h>\n#include <DirManager/dirman.h>\n#include <Utils/files.h>\n#include <Utils/files_ini.h>\n#include <Utils/dir_list_ci.h>\n#include <PGE_File_Formats/file_formats.h>\n#include <fmt_format_ne.h>\n\n\nstatic struct PlayerBackup\n{\n    struct FramePos\n    {\n        int8_t x;\n        int8_t y;\n    } p[maxPlayerFrames + 1];\n\n    struct Calibration\n    {\n        vbint_t w;\n        vbint_t h;\n        vbint_t h_duck;\n        vbint_t grubX;\n        vbint_t grubY;\n    } c[numStates];\n} s_playerFramesBackup[numCharacters];\n\ntypedef RangeArrI<int8_t, 0, maxPlayerFrames, 0> PlayerOffsetArray;\n\nstatic PlayerOffsetArray *s_playerFrameX[numCharacters + 1] = {\n    nullptr, &MarioFrameX, &LuigiFrameX, &PeachFrameX, &ToadFrameX, &LinkFrameX\n};\nstatic PlayerOffsetArray *s_playerFrameY[numCharacters + 1] = {\n    nullptr, &MarioFrameY, &LuigiFrameY, &PeachFrameY, &ToadFrameY, &LinkFrameY\n};\n\nconst char *s_playerFileName[] = {nullptr, \"mario\", \"luigi\", \"peach\", \"toad\", \"link\"};\n\n\nstatic struct NPCDefaults_t\n{\n    RangeArr<NPCTraits_t, 0, maxNPCType> NPCTraits;\n//End Type\n} s_NPCDefaults;\n\n\nvoid LoadCustomNPC(int A, std::string cFileName);\nvoid LoadCustomPlayer(int character, int state, std::string cFileName);\n\n\nvoid SavePlayerDefaults()\n{\n    pLogDebug(\"Saving Player defaults...\");\n\n    DirListCI PlayerDir;\n    std::string PlayerPathRes;\n\n    // Load global customization configs first\n    for(int C = 1; C <= numCharacters; ++C)\n    {\n        PlayerDir.setCurDir(AppPath + \"graphics/\" + s_playerFileName[C]);\n\n        for(int S = 1; S <= numStates; ++S)\n        {\n            // Global override of player setup\n            PlayerPathRes = PlayerDir.resolveFileCaseExistsAbs(fmt::sprintf_ne(\"%s-%d.ini\", s_playerFileName[C], S));\n            if(!PlayerPathRes.empty())\n                LoadCustomPlayer(C, S, PlayerPathRes);\n        }\n    }\n\n    // Then, backup all parameters\n    for(int p = 1; p <= numCharacters; ++p)\n    {\n        auto &pb = s_playerFramesBackup[p - 1];\n        for(int j = 0; j <= maxPlayerFrames; ++j)\n        {\n            pb.p[j].x = (*s_playerFrameX[p])[j];\n            pb.p[j].y = (*s_playerFrameY[p])[j];\n        }\n\n        for(int j = 1; j <= numStates; ++j)\n        {\n            pb.c[j - 1].w = Physics.PlayerWidth[p][j];\n            pb.c[j - 1].h = Physics.PlayerHeight[p][j];\n            pb.c[j - 1].h_duck = Physics.PlayerDuckHeight[p][j];\n            pb.c[j - 1].grubX = Physics.PlayerGrabSpotX[p][j];\n            pb.c[j - 1].grubY = Physics.PlayerGrabSpotY[p][j];\n        }\n    }\n}\n\nvoid LoadPlayerDefaults()\n{\n    pLogDebug(\"Restoring Player defaults...\");\n\n    for(int p = 1; p <= numCharacters; ++p)\n    {\n        auto &pb = s_playerFramesBackup[p - 1];\n        for(int j = 0; j <= maxPlayerFrames; ++j)\n        {\n            (*s_playerFrameX[p])[j] = pb.p[j].x;\n            (*s_playerFrameY[p])[j] = pb.p[j].y;\n        }\n\n        for(int j = 1; j <= numStates; ++j)\n        {\n            Physics.PlayerWidth[p][j] = pb.c[j - 1].w;\n            Physics.PlayerHeight[p][j] = pb.c[j - 1].h;\n            Physics.PlayerDuckHeight[p][j] = pb.c[j - 1].h_duck;\n            Physics.PlayerGrabSpotX[p][j] = pb.c[j - 1].grubX;\n            Physics.PlayerGrabSpotY[p][j] = pb.c[j - 1].grubY;\n        }\n    }\n}\n\n/**\n * @brief Applies bug-fixes to default settings of NPC objects according to compatibility settings\n */\nSDL_FORCE_INLINE void loadNpcSetupFixes()\n{\n    // TODO: Implement settings fixing logic here!\n    if(g_config.custom_powerup_collect_score)\n    {\n        NPCTraits[9].Score = 6; // Set Default Scores for PowerUps (6=1000)\n        NPCTraits[184].Score = 6; // Mushrooms\n        NPCTraits[185].Score = 6;\n        NPCTraits[249].Score = 6;\n        NPCTraits[250].Score = 6;\n        NPCTraits[14].Score = 6; // Fire Flowers\n        NPCTraits[182].Score = 6;\n        NPCTraits[183].Score = 6;\n        NPCTraits[34].Score = 6; // Leaf\n        NPCTraits[169].Score = 6; // Tanooki\n        NPCTraits[170].Score = 6; // Hammer Suit\n        NPCTraits[264].Score = 6; // Ice Flowers\n        NPCTraits[277].Score = 6;\n    }\n}\n\nvoid SaveNPCDefaults()\n{\n    DirListCI NPCDir = DirListCI(AppPath + \"graphics/npc/\");\n    std::string npcPathRes;\n\n    for(int A = 1; A <= maxNPCType; A++)\n    {\n        // Global override of NPC setup\n        npcPathRes = NPCDir.resolveFileCaseExistsAbs(fmt::sprintf_ne(\"npc-%d.txt\", A));\n\n        if(!npcPathRes.empty())\n            LoadCustomNPC(A, npcPathRes);\n\n        s_NPCDefaults.NPCTraits[A] = NPCTraits[A];\n    }\n}\n\nvoid LoadNPCDefaults()\n{\n    int A = 0;\n    for(A = 1; A <= maxNPCType; A++)\n        NPCTraits[A] = s_NPCDefaults.NPCTraits[A];\n\n    BlockWidth[BLKID_CONVEYOR_L_CONV] = 32;\n    BlockWidth[BLKID_CONVEYOR_R_CONV] = 32;\n    BlockHeight[BLKID_CONVEYOR_L_CONV] = 32;\n    BlockHeight[BLKID_CONVEYOR_R_CONV] = 32;\n\n    loadNpcSetupFixes();\n}\n\nvoid FindCustomPlayers(const char* preview_players_from)\n{\n    pLogDebug(\"Trying to load custom Player configs...\");\n\n    std::string playerPath, playerPathC;\n\n    if(preview_players_from)\n        g_dirEpisode.setCurDir(preview_players_from);\n    else\n    {\n        g_dirEpisode.setCurDir(FileNamePath);\n        g_dirCustom.setCurDir(FileNamePath + FileName);\n    }\n\n    for(int C = 1; C <= numCharacters; ++C)\n    {\n        for(int S = 1; S <= numStates; ++S)\n        {\n            const auto pFile = fmt::sprintf_ne(\"%s-%d.ini\", s_playerFileName[C], S);\n            // Episode-wide custom player setup\n            playerPath = g_dirEpisode.resolveFileCaseExistsAbs(pFile);\n\n            // Level-wide custom player setup\n            if(!preview_players_from)\n                playerPathC = g_dirCustom.resolveFileCaseExistsAbs(pFile);\n\n            if(!playerPath.empty())\n                LoadCustomPlayer(C, S, playerPath);\n            if(!playerPathC.empty())\n                LoadCustomPlayer(C, S, playerPathC);\n        }\n    }\n}\n\nstatic inline int convIndexCoorToSpriteIndex(short x, short y)\n{\n    return (y + 10 * x) - 49;\n}\n\nvoid LoadCustomPlayer(int character, int state, std::string cFileName)\n{\n    pLogDebug(\"Loading %s...\", cFileName.c_str());\n\n    IniProcessing hitBoxFile = Files::load_ini(cFileName);\n    if(!hitBoxFile.isOpened())\n    {\n        pLogWarning(\"Can't open the player calibration file: %s\", cFileName.c_str());\n        return;\n    }\n\n    const short UNDEFINED = 0x7FFF;\n    short width = UNDEFINED;\n    short height = UNDEFINED;\n    short height_duck = UNDEFINED;\n    short grab_offset_x = UNDEFINED;\n    short grab_offset_y = UNDEFINED;\n    bool isUsed = true;\n    short offsetX = UNDEFINED;\n    short offsetY = UNDEFINED;\n\n    hitBoxFile.beginGroup(\"common\");\n    //normal\n    hitBoxFile.read(\"width\", width, UNDEFINED);\n    hitBoxFile.read(\"height\", height, UNDEFINED);\n    //duck\n    hitBoxFile.read(\"height-duck\", height_duck, UNDEFINED);\n\n    //grab offsets\n    hitBoxFile.read(\"grab-offset-x\", grab_offset_x, UNDEFINED);\n    hitBoxFile.read(\"grab-offset-y\", grab_offset_y, UNDEFINED);\n\n    hitBoxFile.endGroup();\n\n    for(int x = 0; x < 10; x++)\n    {\n        for(int y = 0; y < 10; y++)\n        {\n            isUsed = true;\n            offsetX = UNDEFINED;\n            offsetY = UNDEFINED;\n\n            std::string tFrame = fmt::format(\"frame-{0}-{1}\", x, y);\n\n            if(!hitBoxFile.contains(tFrame))\n                continue; // Skip not existing frame\n\n            hitBoxFile.beginGroup(tFrame);\n            hitBoxFile.read(\"used\", isUsed, true);\n            if(isUsed) //--> skip this frame\n            {\n                //Offset relative to\n                hitBoxFile.read(\"offsetX\", offsetX, UNDEFINED);\n                hitBoxFile.read(\"offsetY\", offsetY, UNDEFINED);\n                if(offsetX != UNDEFINED && offsetY != UNDEFINED)\n                {\n                    (*s_playerFrameX[character])[convIndexCoorToSpriteIndex(x, y) + state * 100] = -offsetX;\n                    (*s_playerFrameY[character])[convIndexCoorToSpriteIndex(x, y) + state * 100] = -offsetY;\n                }\n            }\n            hitBoxFile.endGroup();\n        }\n    }\n\n    if(width != UNDEFINED)\n        Physics.PlayerWidth[character][state] = width;\n    if(height != UNDEFINED)\n        Physics.PlayerHeight[character][state] = height;\n    if(height_duck != UNDEFINED)\n        Physics.PlayerDuckHeight[character][state] = height_duck;\n    if(grab_offset_x != UNDEFINED)\n        Physics.PlayerGrabSpotX[character][state] = grab_offset_x;\n    if(grab_offset_y != UNDEFINED)\n        Physics.PlayerGrabSpotY[character][state] = grab_offset_y;\n}\n\nvoid FindCustomNPCs(/*std::string cFilePath*/)\n{\n    pLogDebug(\"Trying to load custom NPC configs...\");\n\n    //const std::string GfxRoot = AppPath + \"graphics/\";\n    std::string /*npcPathG,*/ npcPath, npcPathC;\n    // DirMan searchDir(FileNamePath);\n//    std::set<std::string> existingFiles;\n//    std::vector<std::string> files;\n//    searchDir.getListOfFiles(files, {\".txt\"});\n//    for(auto &p : files)\n//        existingFiles.insert(FileNamePath + p);\n\n    g_dirEpisode.setCurDir(FileNamePath);\n    g_dirCustom.setCurDir(FileNamePath + FileName);\n\n//    if(DirMan::exists(FileNamePath + FileName))\n//    {\n//        DirMan searchDataDir(FileNamePath + FileName);\n//        searchDataDir.getListOfFiles(files, {\".png\", \".gif\"});\n//        for(auto &p : files)\n//            existingFiles.insert(FileNamePath + FileName  + \"/\"+ p);\n//    }\n\n    for(int A = 1; A <= maxNPCType; ++A)\n    {\n        const auto nFile = fmt::sprintf_ne(\"npc-%d.txt\", A);\n        // Episode-wide custom NPC setup\n        npcPath = g_dirEpisode.resolveFileCaseExistsAbs(nFile);\n        // Level-wide custom NPC setup\n        npcPathC = g_dirCustom.resolveFileCaseExistsAbs(nFile);\n\n        if(!npcPath.empty())\n            LoadCustomNPC(A, npcPath);\n        if(!npcPathC.empty())\n            LoadCustomNPC(A, npcPathC);\n    }\n}\n\nvoid LoadCustomNPC(int A, std::string cFileName)\n{\n    NPCConfigFile npc;\n    SDL_RWops* rwops = Files::open_file(cFileName, \"r\");\n    PGE_FileFormats_misc::RWopsTextInput in(rwops, std::move(cFileName));\n    FileFormats::ReadNpcTXTFile(in, npc, true);\n\n    auto& traits = NPCTraits[A];\n\n    if(npc.en_gfxoffsetx)\n        traits.FrameOffsetX = npc.gfxoffsetx;\n    if(npc.en_gfxoffsety)\n        traits.FrameOffsetY = npc.gfxoffsety;\n    if(npc.en_width)\n        traits.TWidth = int(npc.width);\n    if(npc.en_height)\n        traits.THeight = int(npc.height);\n    if(npc.en_gfxwidth)\n        traits.WidthGFX = int(npc.gfxwidth);\n    if(npc.en_gfxheight)\n        traits.HeightGFX = int(npc.gfxheight);\n    if(npc.en_score)\n        traits.Score = int(npc.score);\n    if(npc.en_playerblock)\n        traits.MovesPlayer = npc.playerblock;\n    if(npc.en_playerblocktop)\n        traits.CanWalkOn = npc.playerblocktop;\n    if(npc.en_npcblock)\n        traits.IsABlock = npc.npcblock;\n    if(npc.en_npcblocktop)\n        traits.IsAHit1Block = npc.npcblocktop;\n    if(npc.en_grabside)\n        traits.IsGrabbable = npc.grabside;\n    if(npc.en_grabtop)\n        traits.GrabFromTop = npc.grabtop;\n    if(npc.en_jumphurt)\n        traits.JumpHurt = npc.jumphurt;\n    if(npc.en_nohurt)\n        traits.WontHurt = npc.nohurt;\n    if(npc.en_noblockcollision)\n        traits.NoClipping = npc.noblockcollision;\n    if(npc.en_cliffturn)\n        traits.TurnsAtCliffs = npc.cliffturn;\n    if(npc.en_noyoshi)\n        traits.NoYoshi = npc.noyoshi;\n    if(npc.en_foreground)\n        traits.Foreground = npc.foreground;\n    if(npc.en_speed)\n        traits.Speedvar = (numf_t)num_t::from_double((float)npc.speed);\n    if(npc.en_nofireball)\n        traits.NoFireBall = npc.nofireball;\n    if(npc.en_noiceball)\n        traits.NoIceBall = npc.noiceball;\n    if(npc.en_nogravity)\n        traits.NoGravity = npc.nogravity;\n    if(npc.en_frames)\n        traits.TFrames = int(npc.frames);\n    if(npc.en_framespeed)\n        traits.FrameSpeed = int(npc.framespeed);\n    if(npc.en_framestyle)\n        traits.FrameStyle = int(npc.framestyle);\n    if(npc.en_usedefaultcam)\n        traits.UseDefaultCam = npc.usedefaultcam;\n\n    if(A == NPCID_CONVEYOR)\n    {\n        BlockWidth[BLKID_CONVEYOR_L_CONV] = traits.TWidth;\n        BlockWidth[BLKID_CONVEYOR_R_CONV] = traits.TWidth;\n\n        BlockHeight[BLKID_CONVEYOR_L_CONV] = traits.THeight;\n        BlockHeight[BLKID_CONVEYOR_R_CONV] = traits.THeight;\n    }\n}\n\nvoid LoadCustomPlayerPreviews(const char* preview_players_from)\n{\n    FindCustomPlayers(preview_players_from);\n    LoadCustomGFX(false, preview_players_from);\n}\n\nvoid UnloadCustomPlayerPreviews()\n{\n    LoadPlayerDefaults();\n    UnloadPlayerPreviewGFX();\n}\n"
  },
  {
    "path": "src/custom.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CUSTOM_H\n#define CUSTOM_H\n\n#include <string>\n\n\nvoid SavePlayerDefaults();\nvoid LoadPlayerDefaults();\n\n// Public Sub SaveNPCDefaults()\nvoid SaveNPCDefaults();\n\n// Public Sub LoadNPCDefaults()\nvoid LoadNPCDefaults();\n\n\nvoid FindCustomPlayers(const char* preview_players_from = nullptr);\n// Public Sub FindCustomNPCs(Optional cFilePath As String = \"\")\n//void FindCustomNPCs(std::string cFilePath = \"\");\nvoid FindCustomNPCs();\n\n// Private Sub LoadCustomNPC(A As Integer, cFileName As String)\n\n// public-facing functions used by the main menu to load and unload previews of the custom players\nvoid LoadCustomPlayerPreviews(const char* preview_players_from);\nvoid UnloadCustomPlayerPreviews();\n\n#endif // CUSTOM_H\n"
  },
  {
    "path": "src/draw_planes.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef DRAW_PLANES_H\n#define DRAW_PLANES_H\n\nenum PLANE\n{\n\n    PLANE_INTERNAL_BG    = 0x00, // reserved for engine-internal purposes\n\n    PLANE_GAME_BACKDROP  = 0x08, // backdrop, drawn behind level / world map screens\n\n    PLANE_LVL_3D_NEG2    = 0x00, // start of -2 plane on 3DS (level)\n    PLANE_LVL_BG         = 0x10, // level background (background2)\n    PLANE_LVL_3D_NEG1    = 0x18, // start of -1 plane on 3DS (level)\n    PLANE_LVL_BGO_LOW    = 0x18, // special BGOs drawn below sizables\n    PLANE_LVL_SBLOCK     = 0x20, // sizable blocks only\n    PLANE_LVL_BGO_NORM   = 0x28, // includes locks at +1\n    PLANE_LVL_3D_MAIN    = 0x30, // start of +0 plane on 3DS (level)\n    PLANE_LVL_NPC_BG     = 0x30, // NPCs drawn below blocks (includes warping NPCs)\n    PLANE_LVL_PLR_WARP   = 0x38, // players warping and their held items\n    PLANE_LVL_BLK_NORM   = 0x40, // normal blocks\n    PLANE_LVL_EFF_LOW    = 0x48, // low effects\n    PLANE_LVL_NPC_LOW    = 0x50, // low NPCs (includes frozen NPCs at +1)\n    PLANE_LVL_NPC_NORM   = 0x58, // includes NPC chat indicators\n    PLANE_LVL_PLR_NORM   = 0x60, // player graphics (including mounts and held NPCs)\n    PLANE_LVL_BGO_FG     = 0x68, // foreground BGOs\n    PLANE_LVL_NPC_FG     = 0x70, // foreground NPCs\n    PLANE_LVL_BLK_HURTS  = 0x78, // lava and spike blocks\n    PLANE_LVL_BGO_TOP    = 0x80, // special BGOs drawn above hurt blocks and FG NPCs\n    PLANE_LVL_EFF_NORM   = 0x88, // normal effects\n    PLANE_LVL_INFO       = 0x90, // level info, currently includes warp info (star count), editor water rects, and editor info\n    PLANE_LVL_SECTION_FG = 0x98, // highest game-space draws, includes Luna draws, Section FX\n    PLANE_LVL_3D_POS1    = 0xA0, // start of +1 plane on 3DS (level)\n    PLANE_LVL_HUD        = 0xA0, // includes dropped NPCs (at +1), and level vScreen fader (at -1)\n    PLANE_LVL_META       = 0xA8, // information drawn above the HUD but still in the vScreen\n\n    PLANE_WLD_3D_NEG2    = 0x00, // start of -2 plane on 3DS (world)\n    PLANE_WLD_BG         = 0x10, // reserved for future background draws on the world map\n    PLANE_WLD_3D_NEG1    = 0x18, // start of -1 plane on 3DS (world)\n    PLANE_WLD_TIL        = 0x18, // world map tiles\n    PLANE_WLD_SCN        = 0x20, // world map scenes\n    PLANE_WLD_PTH        = 0x28, // world map paths\n    PLANE_WLD_LVL        = 0x30, // world map levels\n    PLANE_WLD_PLR        = 0x38, // world map players\n    PLANE_WLD_3D_MAIN    = 0x88, // start of +0 plane on 3DS (world)\n    PLANE_WLD_EFF        = 0x88, // world map effects (editor-exclusive)\n    PLANE_WLD_INFO       = 0x90, // world map info, currently includes level info (star count), editor music boxes, and editor info\n    PLANE_WLD_FRAME      = 0x98, // world map frame, drawn below HUD\n    PLANE_WLD_3D_POS1    = 0xA0, // start of +1 plane on 3DS (world)\n    PLANE_WLD_HUD        = 0xA0, // world map HUD (player preview, level title, etc)\n    PLANE_WLD_META       = 0xA8, // information drawn above the HUD but still in the screen (currently unused)\n\n    PLANE_GAME_META      = 0xC8, // information drawn above the game but below menus\n    PLANE_GAME_MENUS     = 0xD0, // game menus drawn above everything (including main menu, pause menu, message box, etc)\n    PLANE_GAME_FADER     = 0xD8, // game screen fader\n    PLANE_INTERNAL_FG    = 0xF0, // reserved for engine-internal purposes\n};\n\n\n#endif\n"
  },
  {
    "path": "src/editor/editor.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n#   include <InterProcess/intproc.h>\n#endif\n#include <Logger/logger.h>\n#include <Utils/elapsed_timer.h>\n#include <pge_delay.h>\n#include <fmt_format_ne.h>\n\n#include \"globals.h\"\n#include \"config.h\"\n#include \"editor.h\"\n#include \"graphics.h\"\n#include \"sound.h\"\n#include \"collision.h\"\n#include \"npc.h\"\n#include \"blocks.h\"\n#include \"sorting.h\"\n#include \"player.h\"\n#include \"effect.h\"\n#include \"layers.h\"\n#include \"game_main.h\"\n#include \"change_res.h\"\n#include \"main/level_file.h\"\n#include \"main/cheat_code.h\"\n#include \"main/trees.h\"\n#include \"main/game_globals.h\"\n#include \"main/screen_connect.h\"\n#include \"main/screen_quickreconnect.h\"\n#include \"main/game_strings.h\"\n#include \"main/speedrunner.h\"\n#include \"load_gfx.h\"\n#include \"core/render.h\"\n#include \"core/window.h\"\n#include \"core/events.h\"\n#include \"fontman/font_manager.h\"\n#include \"npc/section_overlap.h\"\n\n#include \"controls.h\"\n\n#include \"pseudo_vb.h\"\n#include \"npc_traits.h\"\n#include \"npc_id.h\"\n#include \"eff_id.h\"\n\n#include \"write_level.h\"\n#include \"write_world.h\"\n#include \"editor/new_editor.h\"\n\n#include \"editor/magic_block.h\"\n#include \"editor/editor_custom.h\"\n\n#include <PGE_File_Formats/file_formats.h>\n\n// static int ScrollDelay = 0; // slows down the camera movement when scrolling through a level\n//Public Declare Function GetCursorPos Lib \"user32\" (lpPoint As POINTAPI) As long long;\n\nstd::string Backup_FullFileName;\n\nbool MouseCancel = false;\nbool HasCursor = false;\n// bool NoReallyKillIt = false; //Unused\nint curSection = 0;\n\nbool enableAutoAlign = true;\n\nint last_vScreenX[maxSections+1];\nint last_vScreenY[maxSections+1];\n\n// backup values\nint curSection_b = 0;\nint last_vScreenX_b[maxSections+1];\nint last_vScreenY_b[maxSections+1];\n\n// number of frames to show section\nint editor_section_toast = 0;\n\n// buffer for scrolling since we can only scroll in 32-pixel increments\nnum_t scroll_buffer_x = 0;\nnum_t scroll_buffer_y = 0;\n\n// to prevent constant replacement of tiled items during \"replace_existing\" mode\nLocation_t last_EC_loc;\n\n// the first f stands for \"fixed\"\nconstexpr bool ffEqual(num_t i, num_t j)\n{\n    return (i - j > -0.1_n) && (i - j < 0.1_n);\n}\n\nvoid ResetSectionScrolls()\n{\n    int p1_section = 0;\n\n    // use screen 0 for section scrolls\n    const Screen_t& screen = Screens[0];\n\n    for(int i = 0; i <= maxSections; i++)\n    {\n        // initialize the section\n        if(LevelREAL[i].Height == LevelREAL[i].Y)\n        {\n            LevelREAL[i].Height = (-200000 + 20000 * i);\n            LevelREAL[i].Y = LevelREAL[i].Height - 600;\n            LevelREAL[i].X = (-200000 + 20000 * i);\n            LevelREAL[i].Width = LevelREAL[i].X + 800;\n\n            // initialize player positions\n            if(i == 0)\n            {\n                for(int p = 1; p <= 2; p++)\n                {\n                    PlayerStart[p].Width = Physics.PlayerWidth[p][2];\n                    PlayerStart[p].Height = Physics.PlayerHeight[p][2];\n                    PlayerStart[p].X = LevelREAL[i].X + 128 - 64 * (p - 1);\n                    PlayerStart[p].Y = LevelREAL[i].Height - 32 - PlayerStart[p].Height;\n                }\n            }\n\n            level[i] = static_cast<SpeedlessLocation_t>(LevelREAL[i]);\n        }\n\n        // normally start at bottom-left\n        last_vScreenY[i] = -(LevelREAL[i].Height - screen.H);\n        last_vScreenX[i] = -(LevelREAL[i].X);\n\n        // if player start is in section, start there instead\n        for(int p = 1; p <= 2; p++)\n        {\n            if(PlayerStart[p].X >= LevelREAL[i].X && PlayerStart[p].X + PlayerStart[p].Width <= LevelREAL[i].Width\n                && PlayerStart[p].Y >= LevelREAL[i].Y && PlayerStart[p].Y + PlayerStart[p].Height <= LevelREAL[i].Height)\n            {\n                // center on player\n                last_vScreenX[i] = -(PlayerStart[p].X + PlayerStart[p].Width / 2 - screen.W / 2);\n                last_vScreenY[i] = -(PlayerStart[p].Y + PlayerStart[p].Height / 2 - screen.H / 2);\n\n                // check section bounds\n                if(-last_vScreenX[i] < LevelREAL[i].X)\n                    last_vScreenX[i] = -LevelREAL[i].X;\n                else if(-last_vScreenX[i] + screen.W > LevelREAL[i].Width)\n                    last_vScreenX[i] = -(LevelREAL[i].Width - screen.W);\n\n                if(-last_vScreenY[i] < LevelREAL[i].Y)\n                    last_vScreenY[i] = -LevelREAL[i].Y;\n                else if(-last_vScreenY[i] + screen.H > LevelREAL[i].Height)\n                    last_vScreenY[i] = -(LevelREAL[i].Height - screen.H);\n\n                // save P1 section\n                if(p == 1)\n                    p1_section = i;\n\n                // don't check other player\n                break;\n            }\n        }\n\n        // center on section if screen bigger\n        if(LevelREAL[i].Width - LevelREAL[i].X < screen.W)\n            last_vScreenX[i] += screen.W / 2 - (LevelREAL[i].Width - LevelREAL[i].X) / 2;\n        if(LevelREAL[i].Height - LevelREAL[i].Y < screen.H)\n            last_vScreenY[i] = -LevelREAL[i].Y + screen.H / 2 - (LevelREAL[i].Height - LevelREAL[i].Y) / 2;\n\n        // align to grid\n        last_vScreenY[i] = ((last_vScreenY[i] + 8 + 16) / 32) * 32 - 8;\n        last_vScreenX[i] = ((last_vScreenX[i] + 16) / 32) * 32;\n    }\n\n    curSection = p1_section;\n    vScreen[1].Y = last_vScreenY[curSection];\n    vScreen[1].X = last_vScreenX[curSection];\n}\n\nvoid SetSection(int i)\n{\n    if(curMusic != bgMusic[i])\n        StartMusic(i);\n    else if(bgMusic[i] == 24)\n    {\n        if(curSection >= 0)\n            if(CustomMusic[curSection] != CustomMusic[i])\n                StartMusic(i);\n    }\n\n    if(curSection != i)\n    {\n        PlaySound(SFX_Slide);\n        editor_section_toast = 66;\n    }\n\n    last_vScreenY[curSection] = (int)vScreen[1].Y;\n    last_vScreenX[curSection] = (int)vScreen[1].X;\n    curSection = i;\n    vScreen[1].Y = last_vScreenY[curSection];\n    vScreen[1].X = last_vScreenX[curSection];\n}\n\nvoid EditorBackup()\n{\n    last_vScreenY[curSection] = (int)vScreen[1].Y;\n    last_vScreenX[curSection] = (int)vScreen[1].X;\n    for(int i = 0; i <= maxSections; i++)\n    {\n        last_vScreenX_b[i] = last_vScreenX[i];\n        last_vScreenY_b[i] = last_vScreenY[i];\n    }\n    curSection_b = curSection;\n}\n\nvoid EditorRestore()\n{\n    for(int i = 0; i <= maxSections; i++)\n    {\n        last_vScreenX[i] = last_vScreenX_b[i];\n        last_vScreenY[i] = last_vScreenY_b[i];\n    }\n    curSection = curSection_b;\n    vScreen[1].X = last_vScreenX_b[curSection];\n    vScreen[1].Y = last_vScreenY_b[curSection];\n    SetSection(curSection);\n    vScreen[1].X = last_vScreenX_b[curSection];\n    vScreen[1].Y = last_vScreenY_b[curSection];\n}\n\nvoid EditorCursor_t::ClearStrings()\n{\n    FreeS(this->NPC.Text);\n    FreeS(this->Warp.StarsMsg);\n    FreeS(this->Warp.level);\n    FreeS(this->WorldMusic.MusicFile);\n}\n\nenum InteractFlags\n{\n    IF_AltMode =  1,\n    IF_ResizeT =  2,\n    IF_ResizeB =  4,\n    IF_ResizeL =  8,\n    IF_ResizeR = 16,\n};\n\nvoid UpdateInteract();\ntemplate<class LocType>\nvoid InteractResize(LocType& loc, int min, int snap);\n\nvoid InteractResizeSection(IntegerLocation_t& section);\n\n// this sub handles the level editor\n// it is still called when the player is testing a level in the editor in windowed mode\nvoid UpdateEditor()\n{\n    if(!MagicHand)\n        Controls::PollInputMethod();\n\n    int A = 0;\n    // int B = 0;\n//    int C = 0;\n    int qLevel = 0;\n    bool CanPlace = false; // Determines if something is in the way\n//    bool grabBool = false;\n    Location_t tempLocation;\n//    if(Debugger == true)\n//        frmLevelDebugger::UpdateDisplay;\n    GameMenu = false;\n\n    GetEditorControls();\n\n    if(!SharedCursor.Primary && !EditorControls.SwitchScreens && !EditorControls.TestPlay)\n    {\n        MouseRelease = true;\n        MouseCancel = false;\n        if(EditorCursor.SubMode > 0 && EditorCursor.Mode == OptCursor_t::LVL_ERASER)\n            EditorCursor.SubMode = 0;\n    }\n\n    if(EditorCursor.Y < 40)\n    {\n        MouseCancel = true;\n        EditorCursor.InteractMode = 0;\n        EditorCursor.InteractFlags = 0;\n        EditorCursor.InteractIndex = 0;\n    }\n\n    bool MouseClick_Current = SharedCursor.Primary && !MouseCancel;\n\n    if(LevelEditor)\n        numPlayers = 0;\n\n    if(MagicHand)\n    {\n        // keep the cursor's position correct when the player moves!\n        MouseMove(EditorCursor.X, EditorCursor.Y, true);\n    }\n    else\n    {\n        FreezeNPCs = false;\n        LevelMacro = LEVELMACRO_OFF;\n        LevelMacroCounter = 0;\n    }\n\n    if(!MagicHand)\n    {\n        if(EditorControls.PrevSection && !WorldEditor)\n        {\n            if(ScrollRelease)\n            {\n                ScrollRelease = false;\n                if(curSection != 0)\n                    SetSection(curSection - 1);\n            }\n        }\n        else if(EditorControls.NextSection && !WorldEditor)\n        {\n            if(ScrollRelease)\n            {\n                ScrollRelease = false;\n                if(curSection != maxSections)\n                    SetSection(curSection + 1);\n            }\n        }\n        else\n            ScrollRelease = true;\n\n        vScreen[1].Y = num_t::floor((vScreen[1].Y + 8) / 32) * 32 - 8;\n        vScreen[1].X = num_t::floor(vScreen[1].X / 32) * 32;\n    }\n    else\n    {\n        curSection = Player[1].Section;\n    }\n\n\n    // if(!XWindow::hasWindowMouseFocus() || SharedCursor.X < 0 || SharedCursor.Y > XRender::TargetW || SharedCursor.Y < 0 || SharedCursor.Y > XRender::TargetH)\n    //     HideCursor();\n\n    if(LevelEditor || MagicHand)\n    {\n        int scroll_required = EditorControls.FastScroll ? 16 : 32;\n\n        if(MagicHand)\n        {\n            scroll_buffer_x = 0;\n            scroll_buffer_y = 0;\n        }\n\n        // if(ScrollDelay <= 0)\n        {\n            int to_scroll_x = (int)(scroll_buffer_x / scroll_required);\n            int to_scroll_y = (int)(scroll_buffer_y / scroll_required);\n\n            if(to_scroll_x)\n            {\n                vScreen[1].X -= 32 * to_scroll_x;\n                EditorCursor.Location.X += 32 * to_scroll_x;\n                scroll_buffer_x -= to_scroll_x * scroll_required;\n            }\n\n            if(to_scroll_y)\n            {\n                vScreen[1].Y -= 32 * to_scroll_y;\n                EditorCursor.Location.Y += 32 * to_scroll_y;\n                scroll_buffer_y -= to_scroll_y * scroll_required;\n            }\n        }\n        // else\n        //     ScrollDelay -= 1;\n        SetCursor();\n\n        // this is where objects are placed/grabbed/deleted\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n        if(IntProc::isEnabled())\n            UpdateInterprocess();\n#endif\n\n        UpdateInteract();\n\n        if(MouseClick_Current && !editorScreen.active && EditorCursor.Y > 40)\n        {\n            CanPlace = true;\n            if(EditorCursor.Mode == OptCursor_t::LVL_SELECT)\n            {\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_PLAYERSTART) // Player start points\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n\n                    EditorCursor.Mode = OptCursor_t::LVL_PLAYERSTART;\n                    EditorCursor.SubMode = 3 + A;\n\n                    EditorCursor.Location = PlayerStart[A];\n                    PlayerStart[A] = PlayerStart_t();\n                    MouseMove(EditorCursor.X, EditorCursor.Y);\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n                }\n                else if(EditorCursor.InteractMode == OptCursor_t::LVL_SECTION)\n                {\n                    MouseRelease = false;\n                    InteractResizeSection(LevelREAL[curSection]);\n                    level[curSection] = static_cast<SpeedlessLocation_t>(LevelREAL[curSection]);\n                    UpdateSectionOverlaps(curSection);\n                }\n\n                // event section resize\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_EVENTS)\n                {\n                    int A = EditorCursor.InteractIndex;\n                    InteractResizeSection(Events[A].section[curSection].position);\n                    MouseRelease = false;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_NPCS) // NPCs\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n\n                    EditorCursor.Mode = OptCursor_t::LVL_NPCS;\n                    EditorCursor.NPC = NPC[A];\n                    EditorCursor.NPC.Hidden = false;\n                    EditorCursor.Layer = NPC[A].Layer;\n                    EditorCursor.Location = NPC[A].Location;\n                    EditorCursor.Location.X = NPC[A].Location.X;\n                    EditorCursor.Location.Y = NPC[A].Location.Y;\n                    SetCursor();\n                    ResetNPC(EditorCursor.NPC.Type);\n\n                    NPC[A].DefaultType = NPCID_NULL;\n                    KillNPC(A, 9);\n\n                    editorScreen.FocusNPC();\n                    MouseRelease = false;\n                    MouseCancel = true;\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n                    if(IntProc::isEnabled()) // Report the taken block into the Editor\n                    {\n                        LevelNPC n;\n                        n.id = EditorCursor.NPC.Type;\n                        n.direct = EditorCursor.NPC.Direction;\n\n                        n.generator = EditorCursor.NPC.Generator;\n                        if(n.generator)\n                        {\n                            n.generator_direct = EditorCursor.NPC.GeneratorDirection();\n                            n.generator_type = EditorCursor.NPC.GeneratorEffect();\n                            n.generator_period = EditorCursor.NPC.GeneratorTimeMax();\n                        }\n\n                        if(n.id == NPCID_ITEM_BURIED || n.id == NPCID_ITEM_POD || n.id == NPCID_ITEM_BUBBLE || n.id == NPCID_ITEM_THROWER)\n                            n.contents = EditorCursor.NPC.Special;\n\n                        if(n.id == NPCID_DOOR_MAKER || n.id == NPCID_MAGIC_DOOR || (n.id == NPCID_ITEM_BURIED && EditorCursor.NPC.Special == NPCID_DOOR_MAKER))\n                            n.special_data = EditorCursor.NPC.Variant;\n\n                        if(NPCIsAParaTroopa((NPCID)n.id) || NPCTraits[n.id].IsFish || n.id == NPCID_FIRE_CHAIN)\n                            n.special_data = EditorCursor.NPC.Special;\n\n                        if(n.id == NPCID_VILLAIN_S3)\n                            n.special_data = EditorCursor.NPC.Variant;\n\n                        if(EditorCursor.NPC.Wings)\n                            n.wings_type = (int)EditorCursor.NPC.DefaultWings;\n\n                        n.msg = GetS(EditorCursor.NPC.Text);\n                        n.friendly = EditorCursor.NPC.Inert;\n                        n.nomove = EditorCursor.NPC.Stuck;\n                        n.is_boss = EditorCursor.NPC.Legacy;\n\n                        n.layer = GetL(EditorCursor.NPC.Layer);\n                        n.event_activate = GetE(EditorCursor.NPC.TriggerActivate);\n                        n.event_die = GetE(EditorCursor.NPC.TriggerDeath);\n                        n.event_emptylayer = GetE(EditorCursor.NPC.TriggerLast);\n                        n.event_talk = GetE(EditorCursor.NPC.TriggerTalk);\n                        n.attach_layer = GetL(EditorCursor.NPC.AttLayer);\n                        IntProc::sendTakenNPC(n);\n                    }\n#endif\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_BLOCKS && EditorCursor.InteractFlags > 1) // resizing block\n                {\n                    MouseRelease = false;\n\n                    Location_t& iLoc = Block[EditorCursor.InteractIndex].Location;\n                    InteractResize(iLoc, 64, 32);\n\n                    syncLayersTrees_Block(EditorCursor.InteractIndex);\n                }\n                else if(EditorCursor.InteractMode == OptCursor_t::LVL_BLOCKS) // Blocks\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n\n                    EditorCursor.Mode = OptCursor_t::LVL_BLOCKS;\n                    EditorCursor.Block = Block[A];\n                    EditorCursor.Layer = Block[A].Layer;\n                    EditorCursor.Location.X = Block[A].Location.X;\n                    EditorCursor.Location.Y = Block[A].Location.Y;\n                    EditorCursor.Location.Width = Block[A].Location.Width;\n                    EditorCursor.Location.Height = Block[A].Location.Height;\n                    SetCursor();\n\n                    Location_t loc = Block[A].Location;\n                    int type = Block[A].Type;\n                    KillBlock(A, false);\n\n                    MagicBlock::MagicBlock(type, loc);\n\n                    editorScreen.FocusBlock();\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n                    if(IntProc::isEnabled()) // Report the taken block into the Editor\n                    {\n                        LevelBlock block;\n                        block.id = EditorCursor.Block.Type;\n                        block.w = (int)EditorCursor.Location.Width;\n                        block.h = (int)EditorCursor.Location.Height;\n                        block.invisible = EditorCursor.Block.Invis;\n                        block.slippery = EditorCursor.Block.Slippy;\n                        block.layer = GetL(EditorCursor.Block.Layer);\n                        if(EditorCursor.Block.Special >= 1000)\n                            block.npc_id = EditorCursor.Block.Special - 1000;\n                        else if(EditorCursor.Block.Special <= 0)\n                            block.npc_id = 0;\n                        else if(EditorCursor.Block.Special < 1000)\n                            block.npc_id = -EditorCursor.Block.Special;\n                        block.event_hit = GetE(EditorCursor.Block.TriggerHit);\n                        block.event_emptylayer = GetE(EditorCursor.Block.TriggerLast);\n                        block.event_destroy = GetE(EditorCursor.Block.TriggerDeath);\n                        IntProc::sendTakenBlock(block);\n                    }\n#endif // THEXTECH_INTERPROC_SUPPORTED\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_WARPS && EditorCursor.InteractFlags > 1) // resizing warps\n                {\n                    MouseRelease = false;\n\n                    SpeedlessLocation_t& iLoc = (EditorCursor.InteractFlags & IF_AltMode) ? Warp[EditorCursor.InteractIndex].Exit : Warp[EditorCursor.InteractIndex].Entrance;\n                    InteractResize(iLoc, 32, 32);\n                }\n                else if(EditorCursor.InteractMode == OptCursor_t::LVL_WARPS) // Warps\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n                    MouseRelease = false;\n                    EditorCursor.Mode = OptCursor_t::LVL_WARPS;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n\n                    if(EditorCursor.InteractFlags == 0)\n                    {\n                        Warp[A].PlacedEnt = false;\n                        EditorCursor.SubMode = 1;\n                        EditorCursor.Location.Width = Warp[A].Entrance.Width;\n                        EditorCursor.Location.Height = Warp[A].Entrance.Height;\n\n                        if(Warp[A].LevelEnt || EditorCursor.Warp.MapWarp || EditorCursor.Warp.level != STRINGINDEX_NONE)\n                            Warp[A].PlacedExit = false;\n                    }\n                    else\n                    {\n                        Warp[A].PlacedExit = false;\n                        EditorCursor.SubMode = 2;\n                        EditorCursor.Location.Width = Warp[A].Exit.Width;\n                        EditorCursor.Location.Height = Warp[A].Exit.Height;\n\n                        // TODO: additional testing of these situations\n                        if(Warp[A].LevelEnt || EditorCursor.Warp.MapWarp || EditorCursor.Warp.level != STRINGINDEX_NONE)\n                            Warp[A].PlacedEnt = false;\n                    }\n\n                    EditorCursor.Warp = Warp[A];\n                    EditorCursor.Layer = EditorCursor.Warp.Layer;\n\n                    if(!Warp[A].PlacedEnt && !Warp[A].PlacedExit)\n                        KillWarp(A);\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_BGOS) // BGOs\n                {\n                    int A = EditorCursor.InteractIndex;\n\n                    PlaySound(SFX_Grab);\n\n                    EditorCursor.Mode = OptCursor_t::LVL_BGOS;\n                    EditorCursor.Background = Background[A];\n                    EditorCursor.Layer = Background[A].Layer;\n                    EditorCursor.Location.X = Background[A].Location.X;\n                    EditorCursor.Location.Y = Background[A].Location.Y;\n                    SetCursor();\n\n                    Location_t loc = static_cast<Location_t>(Background[A].Location);\n                    int type = Background[A].Type;\n\n                    Background[A] = Background[numBackground];\n                    numBackground--;\n\n                    editorScreen.FocusBGO();\n                    if(MagicHand)\n                    {\n                        qSortBackgrounds(1, numBackground);\n                        UpdateBackgrounds();\n                        syncLayers_AllBGOs();\n                    }\n                    else\n                    {\n                        syncLayers_BGO(A);\n                        syncLayers_BGO(numBackground+1);\n                    }\n\n                    MagicBlock::MagicBackground(type, loc);\n\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n                    if(IntProc::isEnabled()) // Report the taken block into the Editor\n                    {\n                        LevelBGO b;\n                        b.id = EditorCursor.Background.Type;\n                        b.layer = GetL(EditorCursor.Background.Layer);\n                        b.z_mode = EditorCursor.Background.GetCustomLayer();\n                        b.z_offset = EditorCursor.Background.GetCustomOffset();\n                        IntProc::sendTakenBGO(b);\n                    }\n#endif // THEXTECH_INTERPROC_SUPPORTED\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_WATER && EditorCursor.InteractFlags > 1) // resizing water\n                {\n                    MouseRelease = false;\n\n                    SpeedlessLocation_t& iLoc = Water[EditorCursor.InteractIndex].Location;\n                    InteractResize(iLoc, 32, 32);\n\n                    syncLayers_Water(EditorCursor.InteractIndex);\n                }\n                else if(EditorCursor.InteractMode == OptCursor_t::LVL_WATER) // water\n                {\n                    int A = EditorCursor.InteractIndex;\n\n                    PlaySound(SFX_Grab);\n                    EditorCursor.Mode = OptCursor_t::LVL_WATER;\n                    EditorCursor.Location = static_cast<Location_t>(Water[A].Location);\n                    EditorCursor.Layer = Water[A].Layer;\n                    EditorCursor.Water = Water[A];\n                    Water[A] = Water[numWater];\n                    numWater--;\n                    syncLayers_Water(A);\n                    syncLayers_Water(numWater+1);\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_AREA && EditorCursor.InteractFlags > 1) // resizing world map area\n                {\n                    MouseRelease = false;\n\n                    TinyLocation_t& iLoc = WorldArea[EditorCursor.InteractIndex].Location;\n                    InteractResize(iLoc, 32, 32);\n                }\n                else if(EditorCursor.InteractMode == OptCursor_t::WLD_AREA) // World map areas\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n                    EditorCursor.Mode = OptCursor_t::WLD_AREA;\n                    EditorCursor.Location = static_cast<Location_t>(WorldArea[A].Location);\n                    SetCursor();\n                    EditorCursor.WorldArea = WorldArea[A];\n                    if(A != numWorldAreas)\n                        WorldArea[A] = WorldArea[numWorldAreas];\n                    numWorldAreas--;\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_MUSIC) // World map music\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n                    EditorCursor.Mode = OptCursor_t::WLD_MUSIC;\n                    EditorCursor.Location = static_cast<Location_t>(WorldMusic[A].Location);\n                    SetCursor();\n                    EditorCursor.WorldMusic = WorldMusic[A];\n                    if(A != numWorldMusic)\n                    {\n                        WorldMusic[A] = WorldMusic[numWorldMusic];\n                        treeWorldMusicUpdate(&WorldMusic[A]);\n                    }\n                    treeWorldMusicRemove(&WorldMusic[numWorldMusic]);\n                    numWorldMusic--;\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_PATHS) // World paths\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n                    EditorCursor.Mode = OptCursor_t::WLD_PATHS;\n                    EditorCursor.Location = static_cast<Location_t>(WorldPath[A].Location);\n                    EditorCursor.WorldPath = WorldPath[A];\n                    SetCursor();\n                    if(A != numWorldPaths)\n                    {\n                        WorldPath[A] = WorldPath[numWorldPaths];\n                        treeWorldPathUpdate(&WorldPath[A]);\n                    }\n                    treeWorldPathRemove(&WorldPath[numWorldPaths]);\n                    numWorldPaths--;\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_SCENES) // World scenes\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n                    EditorCursor.Mode = OptCursor_t::WLD_SCENES;\n                    EditorCursor.Location = static_cast<Location_t>(Scene[A].Location);\n                    EditorCursor.Scene = Scene[A];\n                    SetCursor();\n                    MouseMove(EditorCursor.X, EditorCursor.Y);\n                    // this maintains the order of the scenes\n                    // but makes for a hellish quadtree update\n                    for(int B = A; B < numScenes; B++)\n                    {\n                        Scene[B] = Scene[B + 1];\n                        treeWorldSceneUpdate(&Scene[B]);\n                    }\n                    treeWorldSceneRemove(&Scene[numScenes]);\n                    numScenes--;\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_LEVELS) // World map level points\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n                    EditorCursor.Mode = OptCursor_t::WLD_LEVELS;\n                    EditorCursor.Location = static_cast<Location_t>(WorldLevel[A].Location);\n                    EditorCursor.WorldLevel = WorldLevel[A];\n                    SetCursor();\n                    if(A != numWorldLevels)\n                    {\n                        WorldLevel[A] = WorldLevel[numWorldLevels];\n                        treeWorldLevelUpdate(&WorldLevel[A]);\n                    }\n                    treeWorldLevelRemove(&WorldLevel[numWorldLevels]);\n                    numWorldLevels--;\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_TILES) // World map tiles\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Grab);\n                    EditorCursor.Mode = OptCursor_t::WLD_TILES;\n                    EditorCursor.Location = static_cast<Location_t>(Tile[A].Location);\n                    EditorCursor.Tile = Tile[A];\n                    SetCursor();\n\n                    Location_t loc = static_cast<Location_t>(Tile[A].Location);\n                    int type = Tile[A].Type;\n\n                    if(A != numTiles)\n                    {\n                        Tile[A] = Tile[numTiles];\n                        treeWorldTileUpdate(&Tile[A]);\n                    }\n                    treeWorldTileRemove(&Tile[numTiles]);\n\n                    numTiles--;\n\n                    MagicBlock::MagicTile(type, loc);\n\n                    editorScreen.FocusTile();\n                    MouseRelease = false;\n                    MouseCancel = true; /* Simulate \"Focus out\" inside of SMBX Editor */\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::LVL_ERASER && (SharedCursor.Move || MouseRelease)) // Eraser\n            {\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_NPCS)\n                {\n                    int A = EditorCursor.InteractIndex;\n\n                    if(iRand(2) == 0)\n                        NPC[A].Location.SpeedX = Physics.NPCShellSpeed / 2;\n                    else\n                        NPC[A].Location.SpeedX = -Physics.NPCShellSpeed / 2;\n\n                    NPC[A].DefaultType = NPCID_NULL;\n                    if(NPC[A]->IsABonus || NPC[A]->IsACoin)\n                        KillNPC(A, 4); // Kill the bonus/coin\n                    else\n                        KillNPC(A, 2); // Kill the NPC\n\n                    MouseRelease = false;\n\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::LVL_NPCS;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_BLOCKS)\n                {\n                    int A = EditorCursor.InteractIndex;\n\n                    Location_t loc = Block[A].Location;\n                    int type = Block[A].Type;\n                    KillBlock(A);\n\n                    MagicBlock::MagicBlock(type, loc);\n\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::LVL_BLOCKS;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_WARPS)\n                {\n                    int A = EditorCursor.InteractIndex;\n                    KillWarp(A);\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::LVL_WARPS;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_BGOS)\n                {\n                    int A = EditorCursor.InteractIndex;\n\n                    Location_t loc = static_cast<Location_t>(Background[A].Location);\n                    int type = Background[A].Type;\n\n                    NewEffect(EFFID_SMOKE_S3_CENTER, loc);\n                    PlaySound(SFX_Smash);\n\n                    Background[A] = Background[numBackground];\n                    numBackground--;\n\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::LVL_BGOS;\n\n                    if(MagicHand)\n                    {\n                        qSortBackgrounds(1, numBackground);\n                        UpdateBackgrounds();\n                        syncLayers_AllBGOs();\n                        syncLayers_BGO(numBackground + 1);\n                    }\n                    else\n                    {\n                        syncLayers_BGO(A);\n                        syncLayers_BGO(numBackground + 1);\n                    }\n\n                    MagicBlock::MagicBackground(type, loc);\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::LVL_WATER)\n                {\n                    int A = EditorCursor.InteractIndex;\n                    PlaySound(SFX_Smash);\n                    Water[A] = Water[numWater];\n                    numWater--;\n                    syncLayers_Water(A);\n                    syncLayers_Water(numWater + 1);\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::LVL_WATER;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_AREA)\n                {\n                    int A = EditorCursor.InteractIndex;\n                    tempLocation = static_cast<Location_t>(WorldArea[A].Location);\n                    for(int X = 16; X < WorldArea[A].Location.Width; X += 32)\n                    {\n                        for(int Y = 16; Y < WorldArea[A].Location.Height; Y += 32)\n                        {\n                            tempLocation.X += X - EffectWidth[EFFID_SMOKE_S3] / 2;\n                            tempLocation.Y += Y - EffectHeight[EFFID_SMOKE_S3] / 2;\n                            NewEffect(EFFID_SMOKE_S3, tempLocation);\n                        }\n                    }\n\n                    PlaySound(SFX_ShellHit);\n                    if(A != numWorldAreas)\n                        WorldArea[A] = WorldArea[numWorldAreas];\n                    numWorldAreas--;\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::WLD_AREA;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_MUSIC)\n                {\n                    int A = EditorCursor.InteractIndex;\n                    tempLocation = static_cast<Location_t>(WorldMusic[A].Location);\n                    NewEffect(EFFID_SMOKE_S3_CENTER, tempLocation);\n                    PlaySound(SFX_ShellHit);\n                    if(A != numWorldMusic)\n                    {\n                        WorldMusic[A] = WorldMusic[numWorldMusic];\n                        treeWorldMusicUpdate(&WorldMusic[A]);\n                    }\n                    treeWorldMusicRemove(&WorldMusic[numWorldMusic]);\n                    numWorldMusic--;\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::WLD_MUSIC;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_PATHS)\n                {\n                    int A = EditorCursor.InteractIndex;\n                    tempLocation = static_cast<Location_t>(WorldPath[A].Location);\n                    NewEffect(EFFID_SMOKE_S3_CENTER, tempLocation);\n                    PlaySound(SFX_ShellHit);\n                    if(A != numWorldPaths)\n                    {\n                        WorldPath[A] = WorldPath[numWorldPaths];\n                        treeWorldPathUpdate(&WorldPath[A]);\n                    }\n                    treeWorldPathRemove(&WorldPath[numWorldPaths]);\n                    numWorldPaths--;\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::WLD_PATHS;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_SCENES)\n                {\n                    int A = EditorCursor.InteractIndex;\n                    tempLocation = static_cast<Location_t>(Scene[A].Location);\n                    NewEffect(EFFID_SMOKE_S3_CENTER, tempLocation);\n                    PlaySound(SFX_ShellHit);\n                    for(int B = A; B < numScenes; B++)\n                    {\n                        Scene[B] = Scene[B + 1];\n                        treeWorldSceneUpdate(&Scene[B]);\n                    }\n                    treeWorldSceneRemove(&Scene[numScenes]);\n                    numScenes--;\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::WLD_SCENES;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_LEVELS)\n                {\n                    int A = EditorCursor.InteractIndex;\n                    tempLocation = static_cast<Location_t>(WorldLevel[A].Location);\n                    NewEffect(EFFID_SMOKE_S3_CENTER, tempLocation);\n                    PlaySound(SFX_ShellHit);\n                    if(A != numWorldLevels)\n                    {\n                        WorldLevel[A] = WorldLevel[numWorldLevels];\n                        treeWorldLevelUpdate(&WorldLevel[A]);\n                    }\n                    treeWorldLevelRemove(&WorldLevel[numWorldLevels]);\n                    numWorldLevels--;\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::WLD_LEVELS;\n                }\n\n                if(EditorCursor.InteractMode == OptCursor_t::WLD_TILES)\n                {\n                    int A = EditorCursor.InteractIndex;\n                    Location_t loc = static_cast<Location_t>(Tile[A].Location);\n                    NewEffect(EFFID_SMOKE_S3_CENTER, loc);\n                    PlaySound(SFX_ShellHit);\n\n                    int type = Tile[A].Type;\n\n                    if(A != numTiles)\n                    {\n                        Tile[A] = Tile[numTiles];\n                        treeWorldTileUpdate(&Tile[A]);\n                    }\n                    treeWorldTileRemove(&Tile[numTiles]);\n\n                    numTiles--;\n\n                    MagicBlock::MagicTile(type, loc);\n\n                    MouseRelease = false;\n                    if(EditorCursor.SubMode == 0)\n                        EditorCursor.SubMode = OptCursor_t::WLD_TILES;\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::LVL_WATER) // Water\n            {\n                if(MouseRelease)\n                {\n                    MouseRelease = false;\n                    CanPlace = true;\n                    for(A = 1; A <= numWater; A++)\n                    {\n                        if(ffEqual(Water[A].Location.X, EditorCursor.Location.X) &&\n                           ffEqual(Water[A].Location.Y, EditorCursor.Location.Y) &&\n                           ffEqual(Water[A].Location.Height, EditorCursor.Location.Height) &&\n                           ffEqual(Water[A].Location.Width, EditorCursor.Location.Width))\n                        {\n                            CanPlace = false;\n                            break;\n                        }\n                    }\n\n                    if(CanPlace)\n                    {\n                        numWater++;\n                        Water[numWater] = EditorCursor.Water;\n                        syncLayers_Water(numWater);\n                    }\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::LVL_BLOCKS) // Blocks\n            {\n                for(A = numBlock; A >= 1; A--)\n                {\n                    if(!MouseRelease || (!BlockIsSizable[Block[A].Type] && !BlockIsSizable[EditorCursor.Block.Type]))\n                    {\n                        // ignore sizable background blocks in Magic Block mode\n                        if(MagicBlock::enabled && BlockIsSizable[Block[A].Type] && !BlockIsSizable[EditorCursor.Block.Type])\n                            continue;\n\n                        if(CursorCollision(EditorCursor.Location, Block[A].Location) && !Block[A].Hidden)\n                        {\n                            if(!(MagicBlock::enabled && MagicBlock::replace_existing) || (!MouseRelease && CheckCollision(Block[A].Location, last_EC_loc)))\n                            {\n                                CanPlace = false;\n                                break;\n                            }\n                            else\n                            {\n                                Location_t loc = Block[A].Location;\n                                int type = Block[A].Type;\n                                KillBlock(A, false);\n                                MagicBlock::MagicBlock(type, loc);\n                            }\n                        }\n                    }\n                    else\n                    {\n                        if(Block[A].Type == EditorCursor.Block.Type)\n                        {\n                            if(ffEqual(EditorCursor.Location.X, Block[A].Location.X) &&\n                               ffEqual(EditorCursor.Location.Y, Block[A].Location.Y))\n                            {\n                                if(MouseRelease)\n                                    pLogDebug(\"Sizable block was rejected at block at EC Loc (%d, %d), other block loc (%d, %d)\", (int)EditorCursor.Location.X, (int)EditorCursor.Location.Y, (int)Block[A].Location.X, (int)Block[A].Location.Y);\n\n                                CanPlace = false;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                if(CanPlace)\n                {\n                    last_EC_loc = EditorCursor.Location;\n                    last_EC_loc.X += 1;\n                    last_EC_loc.Y += 1;\n                    last_EC_loc.Width -= 2;\n                    last_EC_loc.Height -= 2;\n                }\n\n                if(!BlockIsSizable[EditorCursor.Block.Type] && EditorCursor.Block.Type != 370 && CanPlace)\n                {\n                    for(A = 1; A <= numNPCs; A++)\n                    {\n                        if(NPC[A].Type != NPCID_ITEM_BURIED && NPC[A].Type != NPCID_FIRE_DISK && NPC[A].Type != NPCID_FIRE_CHAIN)\n                        {\n                            if(CursorCollision(EditorCursor.Location, NPC[A].Location) && !NPC[A].Hidden && NPC[A].Active)\n                            {\n                                CanPlace = false;\n                                break;\n                            }\n                        }\n                    }\n                    for(A = 1; A <= 2; A++)\n                    {\n                        if(CursorCollision(EditorCursor.Location, PlayerStart[A]) && !MagicHand)\n                        {\n                            CanPlace = false;\n                            break;\n                        }\n                    }\n                }\n\n                if(CanPlace) // Nothing is in the way\n                {\n//                    if(frmBlocks::chkFill.Value == 1)\n//                    {\n//                        BlockFill(EditorCursor.Block.Location); // and nuke an application with memory overflow error >:D\n//                        if(MagicHand == true)\n//                        {\n//                            for(A = -FLBlocks; A <= FLBlocks; A++)\n//                            {\n//                                FirstBlock[A] = 1;\n//                                LastBlock[A] = numBlock;\n//                            }\n//                            BlocksSorted = false;\n//                        }\n//                        FindSBlocks();\n//                    }\n//                    else\n                    {\n                        if(numBlock < maxBlocks) // Not out of blocks\n                        {\n                            MouseRelease = false;\n                            numBlock++;\n                            Block[numBlock] = EditorCursor.Block;\n                            Block[numBlock].DefaultType = Block[numBlock].Type;\n                            Block[numBlock].DefaultSpecial = Block[numBlock].Special;\n                            syncLayersTrees_Block(numBlock);\n\n                            MagicBlock::MagicBlock(numBlock);\n#if 0\n                            if(MagicHand)\n                            {\n                                for(A = -FLBlocks; A <= FLBlocks; A++)\n                                {\n                                    FirstBlock[A] = 1;\n                                    LastBlock[A] = numBlock;\n                                }\n                                BlocksSorted = false;\n                            }\n#endif\n                        }\n                        // FindSBlocks();\n//                        if(nPlay.Online == true)\n//                            Netplay::sendData Netplay::AddBlock(numBlock);\n                    }\n\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::LVL_PLAYERSTART && !MagicHand) // player start points\n            {\n                if(EditorCursor.SubMode >= 4)\n                {\n                    // printf(\"Trying to place player at %f, %f...\\n\", EditorCursor.Location.X, EditorCursor.Location.Y);\n                    int B = EditorCursor.SubMode - 3;\n\n                    for(A = 1; A <= 2; A++)\n                    {\n                        if(CursorCollision(EditorCursor.Location, PlayerStart[A]) && A != B)\n                            CanPlace = false;\n                    }\n                    for(A = 1; A <= numBlock; A++)\n                    {\n                        if(CursorCollision(EditorCursor.Location, Block[A].Location) && !Block[A].Hidden && !Block[A].Invis && !BlockIsSizable[Block[A].Type] && !BlockNoClipping[Block[A].Type] && BlockOnlyHitspot1[Block[A].Type] == false && BlockSlope[Block[A].Type] == 0 && BlockSlope2[Block[A].Type] == 0)\n                        {\n                            CanPlace = false;\n                            break;\n                        }\n                    }\n                    if(CanPlace)\n                    {\n                        if(EditorCursor.SubMode == 4)\n                            PlayerStart[1] = EditorCursor.Location;\n                        else\n                            PlayerStart[2] = EditorCursor.Location;\n                    }\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::LVL_BGOS) // Backgrounds\n            {\n                for(A = 1; A <= numBackground; A++)\n                {\n                    const int ctype = EditorCursor.Background.Type;\n                    const int btype = Background[A].Type;\n                    bool same_type = (ctype == btype);\n\n                    if(MagicBlock::enabled && ctype > 0 && ctype <= maxBackgroundType && btype > 0 && btype <= maxBackgroundType\n                        && EditorCustom::bgo_family_by_type[ctype - 1] != EditorCustom::FAMILY_NONE\n                        && EditorCustom::bgo_family_by_type[ctype - 1] == EditorCustom::bgo_family_by_type[btype - 1])\n                    {\n                        same_type = true;\n                    }\n\n                    if(same_type)\n                    {\n                        if(CursorCollision(EditorCursor.Location, Background[A].Location) && !Background[A].Hidden)\n                        {\n                            CanPlace = false;\n                            break;\n                        }\n                    }\n                }\n\n                if(CanPlace) // Nothing is in the way\n                {\n                    if(numBackground < maxBackgrounds) // Not out of backgrounds\n                    {\n                        numBackground++;\n                        Background[numBackground] = EditorCursor.Background;\n                        syncLayers_BGO(numBackground);\n\n                        MagicBlock::MagicBackground(numBackground);\n\n                        if(MagicHand)\n                        {\n                            qSortBackgrounds(1, numBackground);\n                            UpdateBackgrounds();\n                            // ugh\n                            syncLayers_AllBGOs();\n                        }\n                    }\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::LVL_NPCS) // NPCs\n            {\n                if(EditorCursor.NPC.Type != NPCID_ITEM_BURIED && EditorCursor.NPC.Type != NPCID_FIRE_DISK && EditorCursor.NPC.Type != NPCID_FIRE_CHAIN)\n                {\n                    for(A = 1; A <= numBlock; A++)\n                    {\n                        if(!BlockIsSizable[Block[A].Type] && !Block[A].Hidden && BlockSlope[Block[A].Type] == 0 && BlockSlope2[Block[A].Type] == 0)\n                        {\n                            if(CursorCollision(EditorCursor.Location, Block[A].Location))\n                            {\n                                CanPlace = false;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                for(A = 1; A <= numNPCs; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, NPC[A].Location) && !NPC[A].Hidden && NPC[A].Active && (NPC[A].Type != NPCID_LIFT_SAND || EditorCursor.NPC.Type == NPCID_LIFT_SAND))\n                    {\n                        if(!NPC[A].Generator || NPC[A].Type == EditorCursor.NPC.Type)\n                        {\n                            if((EditorCursor.NPC.Type != NPCID_BOSS_CASE && NPC[A].Type != NPCID_BOSS_CASE) || (EditorCursor.NPC.Type == NPCID_BOSS_CASE && NPC[A].Type == NPCID_BOSS_CASE))\n                            {\n                                if(!NPC[A]->IsAVine)\n                                {\n                                    CanPlace = false;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n                }\n\n                for(A = 1; A <= 2; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, PlayerStart[A]) && !MagicHand)\n                        CanPlace = false;\n                }\n\n                if(!MouseRelease)\n                    CanPlace = false;\n\n                if(CanPlace) // Nothing is in the way\n                {\n                    if(numNPCs < maxNPCs - 20) // Not out of npcs\n                    {\n                        MouseRelease = false;\n                        numNPCs++;\n//                        if(frmNPCs::Bubble.Caption == \"Yes\" && frmNPCs::optNPCDirection(1).Value == true)\n//                        {\n//                            EditorCursor.NPC.Direction = 0;\n//                            EditorCursor.NPC.DefaultDirection = 0;\n//                        }\n\n                        NPC[numNPCs] = EditorCursor.NPC;\n                        if(NPC[numNPCs].Text != STRINGINDEX_NONE)\n                        {\n                            NPC[numNPCs].Text = STRINGINDEX_NONE;\n                            SetS(NPC[numNPCs].Text, GetS(EditorCursor.NPC.Text));\n                        }\n//                        Netplay::sendData Netplay::AddNPC(numNPCs);\n                        if(!MagicHand)\n                        {\n                            // ugh\n                            NPCSort();\n                            syncLayers_AllNPCs();\n                        }\n\n                        if(MagicHand)\n                        {\n                            auto &n = NPC[numNPCs];\n                            n.FrameCount = 0;\n                            n.Active = true;\n                            n.TimeLeft = 10;\n                            n.DefaultDirection = n.Direction;\n                            n.DefaultLocationX = n.Location.X;\n                            n.DefaultLocationY = n.Location.Y;\n                            n.DefaultSpecial = n.Special;\n                            CheckSectionNPC(numNPCs);\n                        }\n                        syncLayers_NPC(numNPCs);\n                    }\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::LVL_WARPS) // Warps\n            {\n                // find an incomplete warp slot\n                int numWarpsMax = numWarps + 1;\n                for(A = 1; A <= numWarpsMax; A++)\n                {\n                    if(!Warp[A].PlacedEnt || !Warp[A].PlacedExit)\n                        break;\n                }\n\n                if(A > numWarps)\n                    numWarps = A;\n\n                if(EditorCursor.SubMode == 1 || EditorCursor.Warp.level != STRINGINDEX_NONE || EditorCursor.Warp.LevelEnt || EditorCursor.Warp.MapWarp)\n                {\n                    EditorCursor.Warp.Entrance = static_cast<SpeedlessLocation_t>(EditorCursor.Location);\n                    MouseCancel = true;\n                    MouseRelease = false;\n                    EditorCursor.Warp.PlacedEnt = true;\n                }\n                if(EditorCursor.SubMode == 2 || EditorCursor.Warp.level != STRINGINDEX_NONE || EditorCursor.Warp.LevelEnt || EditorCursor.Warp.MapWarp)\n                {\n                    EditorCursor.Warp.Exit = static_cast<SpeedlessLocation_t>(EditorCursor.Location);\n                    MouseCancel = true;\n                    MouseRelease = false;\n                    EditorCursor.Warp.PlacedExit = true;\n                }\n\n                Warp[A] = EditorCursor.Warp;\n                Warp[A].Layer = EditorCursor.Layer;\n\n                // de-duplicate strings\n                if(Warp[A].level != STRINGINDEX_NONE)\n                {\n                    Warp[A].level = STRINGINDEX_NONE;\n                    SetS(Warp[A].level, GetS(EditorCursor.Warp.level));\n                }\n                if(Warp[A].StarsMsg != STRINGINDEX_NONE)\n                {\n                    Warp[A].StarsMsg = STRINGINDEX_NONE;\n                    SetS(Warp[A].StarsMsg, GetS(EditorCursor.Warp.StarsMsg));\n                }\n\n                if(Warp[A].PlacedEnt && Warp[A].PlacedExit)\n                {\n                    EditorCursor.Warp.PlacedEnt = false;\n                    EditorCursor.Warp.PlacedExit = false;\n                    EditorCursor.SubMode = 1;\n                }\n                else if(Warp[A].PlacedEnt)\n                    EditorCursor.SubMode = 2;\n                else\n                    EditorCursor.SubMode = 1;\n\n                syncLayers_Warp(A);\n//                if(nPlay.Online == true)\n//                    Netplay::sendData Netplay::AddWarp[A];\n            }\n            else if(EditorCursor.Mode == OptCursor_t::WLD_TILES) // Tiles\n            {\n                for(A = numTiles; A >= 1; A--)\n                {\n                    if(CursorCollision(EditorCursor.Location, Tile[A].Location))\n                    {\n                        if(!(MagicBlock::enabled && MagicBlock::replace_existing) || (!MouseRelease && CheckCollision(Tile[A].Location, last_EC_loc)))\n                        {\n                            CanPlace = false;\n                            break;\n                        }\n                        else\n                        {\n                            Location_t loc = static_cast<Location_t>(Tile[A].Location);\n                            int type = Tile[A].Type;\n\n                            if(A != numTiles)\n                            {\n                                Tile[A] = Tile[numTiles];\n                                treeWorldTileUpdate(&Tile[A]);\n                            }\n                            treeWorldTileRemove(&Tile[numTiles]);\n                            numTiles--;\n\n                            MagicBlock::MagicTile(type, loc);\n                        }\n                    }\n                }\n\n                MouseRelease = false;\n\n                if(CanPlace) // Nothing is in the way\n                {\n                    last_EC_loc = EditorCursor.Location;\n                    last_EC_loc.X += 1;\n                    last_EC_loc.Y += 1;\n                    last_EC_loc.Width -= 2;\n                    last_EC_loc.Height -= 2;\n\n                    if(numTiles < maxTiles) // Not out of blocks\n                    {\n                        numTiles++;\n                        Tile[numTiles] = EditorCursor.Tile;\n                        treeWorldTileAdd(&Tile[numTiles]);\n\n                        MagicBlock::MagicTile(numTiles);\n                    }\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::WLD_SCENES) // Scenery\n            {\n                for(A = 1; A <= numScenes; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, Scene[A].Location))\n                    {\n                        if(EditorCursor.Scene.Type == Scene[A].Type)\n                        {\n                            if(ffEqual(EditorCursor.Scene.Location.X, Scene[A].Location.X) &&\n                               ffEqual(EditorCursor.Scene.Location.Y, Scene[A].Location.Y))\n                            {\n                                CanPlace = false;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                for(A = 1; A <= numWorldLevels; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, WorldLevel[A].Location))\n                    {\n                        CanPlace = false;\n                        break;\n                    }\n                }\n\n                if(CanPlace)\n                {\n                    if(numScenes < maxScenes)\n                    {\n                        numScenes++;\n                        Scene[numScenes] = EditorCursor.Scene;\n                        treeWorldSceneAdd(&Scene[numScenes]);\n                    }\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::WLD_LEVELS) // Level\n            {\n                // can do something fancy with the quadtrees here\n\n                for(A = 1; A <= numWorldPaths; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, WorldPath[A].Location))\n                    {\n                        CanPlace = false;\n                        break;\n                    }\n                }\n\n                for(A = 1; A <= numScenes; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, Scene[A].Location))\n                    {\n                        CanPlace = false;\n                        break;\n                    }\n                }\n\n                for(A = 1; A <= numWorldLevels; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, WorldLevel[A].Location))\n                    {\n                        CanPlace = false;\n                        qLevel = A;\n                        UNUSED(qLevel);\n                        break;\n                    }\n                }\n\n                if(CanPlace)\n                {\n                    if(numWorldLevels < maxWorldLevels)\n                    {\n                        numWorldLevels++;\n                        WorldLevel[numWorldLevels] = EditorCursor.WorldLevel;\n                        treeWorldLevelAdd(&WorldLevel[numWorldLevels]);\n                    }\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::WLD_PATHS) // Paths\n            {\n                // can do something fancy with the quadtrees here\n\n                for(A = 1; A <= numWorldPaths; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, WorldPath[A].Location))\n                    {\n                        CanPlace = false;\n                        break;\n                    }\n                }\n\n                for(A = 1; A <= numWorldLevels; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, WorldLevel[A].Location))\n                    {\n                        CanPlace = false;\n                        break;\n                    }\n                }\n\n                if(CanPlace)\n                {\n                    if(numWorldPaths < maxWorldPaths)\n                    {\n                        numWorldPaths++;\n                        WorldPath[numWorldPaths] = EditorCursor.WorldPath;\n                        treeWorldPathAdd(&WorldPath[numWorldPaths]);\n                    }\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::WLD_MUSIC) // Music\n            {\n                for(A = 1; A <= numWorldMusic; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location, WorldMusic[A].Location))\n                    {\n                        CanPlace = false;\n                        break;\n                    }\n                }\n\n                if(CanPlace)\n                {\n                    EditorCursor.WorldMusic.Location = static_cast<TinyLocation_t>(EditorCursor.Location);\n                    numWorldMusic++;\n                    WorldMusic[numWorldMusic] = EditorCursor.WorldMusic;\n                    // de-duplicate music file\n                    if(EditorCursor.WorldMusic.MusicFile != STRINGINDEX_NONE)\n                    {\n                        WorldMusic[numWorldMusic].MusicFile = STRINGINDEX_NONE;\n                        SetS(WorldMusic[numWorldMusic].MusicFile, GetS(EditorCursor.WorldMusic.MusicFile));\n                    }\n                    treeWorldMusicAdd(&WorldMusic[numWorldMusic]);\n                }\n            }\n            else if(EditorCursor.Mode == OptCursor_t::WLD_AREA && MouseRelease) // Areas\n            {\n                for(A = 1; A <= numWorldAreas; A++)\n                {\n                    if(CursorCollision(EditorCursor.Location,\n                        newLoc(WorldArea[A].Location.X, WorldArea[A].Location.Y, 32, 32)))\n                    {\n                        CanPlace = false;\n                        break;\n                    }\n                }\n\n                if(CanPlace)\n                {\n                    EditorCursor.WorldArea.Location.X = (int)EditorCursor.Location.X;\n                    EditorCursor.WorldArea.Location.Y = (int)EditorCursor.Location.Y;\n                    numWorldAreas++;\n                    WorldArea[numWorldAreas] = EditorCursor.WorldArea;\n                    MouseRelease = false;\n                }\n            }\n        }\n\n        if(!MagicHand)\n        {\n            for(A = 1; A <= numNPCs; A++)\n            {\n                // .Frame = EditorNPCFrame(.Type, .Direction)\n                NPCFrames(A);\n            }\n        }\n    }\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n    if(!MagicHand || !IntProc::isEnabled())\n#endif\n    {\n        editorScreen.UpdateEditorScreen(EditorScreen::CallMode::Logic);\n    }\n}\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\nvoid UpdateInterprocess()\n{\n    if(!IntProc::hasCommand())\n        return;\n\n    IntProc::cmdLock();\n\n    //Recive external commands!\n    switch(IntProc::commandType())\n    {\n    case IntProc::MsgBox:\n    {\n        g_MessageType = MESSAGE_TYPE_SYS_INFO;\n        MessageText = IntProc::getCMD();\n        PauseGame(PauseCode::Message);\n        break;\n    }\n\n    case IntProc::Cheat:\n    {\n        cheats_setBuffer(IntProc::getCMD(), false);\n        break;\n    }\n\n    case IntProc::SetLayer:\n    {\n        EditorCursor.Layer = FindLayer(IntProc::getCMD());\n\n        EditorCursor.Block.Layer = EditorCursor.Layer;\n        EditorCursor.Background.Layer = EditorCursor.Layer;\n        EditorCursor.NPC.Layer = EditorCursor.Layer;\n        break;\n    }\n\n    case IntProc::SetNumStars:\n    {\n        auto s = IntProc::getCMD();\n        int starsNew = SDL_atoi(s.c_str());\n        if(numStars < starsNew) // Can't decrease stars number\n        {\n            numStars = starsNew;\n            CheckAfterStarTake(true);\n        }\n        break;\n    }\n\n    case IntProc::PlaceItem:\n    {\n        std::string raw = IntProc::getCMD();\n        pLogDebug(raw);\n        LevelData got;\n        PGE_FileFormats_misc::RawTextInput raw_file(&raw);\n        FileFormats::ReadExtendedLvlFile(raw_file, got);\n\n        if(!got.meta.ReadFileValid)\n        {\n            pLogDebug(got.meta.ERROR_info);\n            break;\n        }\n\n        if(raw.compare(0, 11, \"BLOCK_PLACE\") == 0)\n        {\n            if(got.blocks.empty())\n                break;\n\n            const LevelBlock &b = got.blocks[0];\n\n            if(EditorCursor.Mode != OptCursor_t::LVL_BLOCKS ||\n               EditorCursor.Block.Type != int(b.id))\n                PlaySound(SFX_Grab);\n\n            EditorCursor.Layer = FindLayer(b.layer);\n\n            EditorCursor.Mode = OptCursor_t::LVL_BLOCKS;\n            EditorCursor.Block = Block_t();\n            EditorCursor.Block.Type = int(b.id);\n            EditorCursor.Location.X = b.x;\n            EditorCursor.Location.Y = b.y;\n            EditorCursor.Location.Width = b.w;\n            EditorCursor.Location.Height = b.h;\n            if(EditorCursor.Block.Type <= maxBlockType && BlockIsSizable[EditorCursor.Block.Type])\n            {\n                EditorCursor.Block.Location.Width = 128;\n                EditorCursor.Block.Location.Height = 128;\n                EditorCursor.Location.Width = 128;\n                EditorCursor.Location.Height = 128;\n            }\n            EditorCursor.Block.Invis = b.invisible;\n            EditorCursor.Block.Slippy = b.slippery;\n            EditorCursor.Block.Special = b.npc_id > 0 ? int(b.npc_id + 1000) : int(-b.npc_id);\n            EditorCursor.Block.Layer = FindLayer(b.layer);\n            EditorCursor.Block.TriggerHit = FindEvent(b.event_hit);\n            EditorCursor.Block.TriggerLast = FindEvent(b.event_emptylayer);\n            EditorCursor.Block.TriggerDeath = FindEvent(b.event_destroy);\n\n            if(EditorCursor.Block.Type > maxBlockType) // Avoid out of range crash\n                EditorCursor.Block.Type = 1;\n\n            SetCursor();\n        }\n        else if(raw.compare(0, 9, \"BGO_PLACE\") == 0)\n        {\n            if(got.bgo.empty())\n                break;\n\n            const LevelBGO &b = got.bgo[0];\n\n            if(EditorCursor.Mode != OptCursor_t::LVL_BGOS ||\n               EditorCursor.Background.Type != int(b.id))\n                PlaySound(SFX_Grab);\n\n            EditorCursor.Layer = FindLayer(b.layer);\n\n            EditorCursor.Mode = OptCursor_t::LVL_BGOS;\n            EditorCursor.Background = Background_t();\n            EditorCursor.Background.Type = int(b.id);\n            EditorCursor.Location.X = b.x;\n            EditorCursor.Location.Y = b.y;\n            EditorCursor.Background.Layer = FindLayer(b.layer);\n            EditorCursor.Background.SetSortPriority(b.z_mode, std::round(b.z_offset));\n\n            if(EditorCursor.Background.Type > maxBackgroundType) // Avoid out of range crash\n                EditorCursor.Background.Type = 1;\n\n            SetCursor();\n        }\n        else if(raw.compare(0, 9, \"NPC_PLACE\") == 0)\n        {\n            if(got.npc.empty())\n                break;\n\n            const LevelNPC &n = got.npc[0];\n\n            if(EditorCursor.Mode != OptCursor_t::LVL_NPCS ||\n               EditorCursor.NPC.Type != int(n.id))\n                PlaySound(SFX_Grab);\n\n            EditorCursor.Layer = FindLayer(n.layer);\n\n            EditorCursor.Mode = OptCursor_t::LVL_NPCS;\n            EditorCursor.NPC = NPC_t();\n            EditorCursor.NPC.Type = NPCID(n.id);\n            EditorCursor.NPC.Hidden = false;\n            EditorCursor.Location.X = n.x;\n            EditorCursor.Location.Y = n.y;\n            EditorCursor.NPC.Direction = n.direct;\n\n            if(n.id > maxNPCType) // Avoid out of range crash\n                EditorCursor.NPC.Type = NPCID(1);\n\n            if(NPCIsContainer(EditorCursor.NPC))\n            {\n                EditorCursor.NPC.Special = (vbint_t)n.contents;\n                EditorCursor.NPC.DefaultSpecial = EditorCursor.NPC.Special;\n            }\n\n            if(EditorCursor.NPC.Type == NPCID_DOOR_MAKER || EditorCursor.NPC.Type == NPCID_MAGIC_DOOR || (EditorCursor.NPC.Type == NPCID_ITEM_BURIED && EditorCursor.NPC.Special == NPCID_DOOR_MAKER))\n                EditorCursor.NPC.Variant = (vbint_t)n.special_data;\n\n            if(NPCIsAParaTroopa(EditorCursor.NPC))\n            {\n                EditorCursor.NPC.Special = (vbint_t)n.special_data;\n                EditorCursor.NPC.DefaultSpecial = EditorCursor.NPC.Special;\n            }\n\n            if(EditorCursor.NPC->IsFish)\n            {\n                EditorCursor.NPC.Special = (vbint_t)n.special_data;\n                EditorCursor.NPC.DefaultSpecial = EditorCursor.NPC.Special;\n            }\n\n            if(EditorCursor.NPC.Type == NPCID_FIRE_CHAIN)\n            {\n                EditorCursor.NPC.Special = (vbint_t)n.special_data;\n                EditorCursor.NPC.DefaultSpecial = EditorCursor.NPC.Special;\n            }\n\n            if(EditorCursor.NPC.Type == NPCID_VILLAIN_S3)\n            {\n                if(n.special_data >= 0 && n.special_data < 256)\n                    EditorCursor.NPC.Variant = (uint8_t)n.special_data;\n                else\n                    pLogWarning(\"Out of range Variant value %ld from IntProc\", (long)n.special_data);\n            }\n\n            EditorCursor.NPC.Generator = n.generator;\n            if(EditorCursor.NPC.Generator)\n            {\n                // EditorCursor.NPC.GeneratorDirection = n.generator_direct;\n                // EditorCursor.NPC.GeneratorEffect = n.generator_type;\n                EditorCursor.NPC.Special3 = ((uint16_t)(uint8_t)n.generator_direct << 8) + (uint8_t)n.generator_type;\n                EditorCursor.NPC.GeneratorTimeMax() = n.generator_period;\n            }\n\n            if(!n.msg.empty())\n                SetS(EditorCursor.NPC.Text, n.msg);\n\n            EditorCursor.NPC.Inert = n.friendly;\n            if(EditorCursor.NPC.Type == NPCID_SIGN)\n                EditorCursor.NPC.Inert = true;\n            EditorCursor.NPC.Stuck = n.nomove;\n            EditorCursor.NPC.DefaultStuck = EditorCursor.NPC.Stuck;\n\n            EditorCursor.NPC.DefaultWings = (WingBehaviors)n.wings_type;\n            EditorCursor.NPC.Wings = EditorCursor.NPC.DefaultWings;\n\n            EditorCursor.NPC.Legacy = n.is_boss;\n\n            EditorCursor.NPC.Layer = FindLayer(n.layer);\n            EditorCursor.NPC.TriggerActivate = FindEvent(n.event_activate);\n            EditorCursor.NPC.TriggerDeath = FindEvent(n.event_die);\n            EditorCursor.NPC.TriggerTalk = FindEvent(n.event_talk);\n            EditorCursor.NPC.TriggerLast = FindEvent(n.event_emptylayer);\n            EditorCursor.NPC.AttLayer = FindLayer(n.attach_layer);\n\n            EditorCursor.NPC.DefaultType = EditorCursor.NPC.Type;\n            EditorCursor.NPC.Location.Width = EditorCursor.NPC->TWidth;\n            EditorCursor.NPC.Location.Height = EditorCursor.NPC->THeight;\n            EditorCursor.NPC.DefaultLocationX = EditorCursor.NPC.Location.X;\n            EditorCursor.NPC.DefaultLocationY = EditorCursor.NPC.Location.Y;\n            EditorCursor.NPC.DefaultDirection = EditorCursor.NPC.Direction;\n            EditorCursor.NPC.TimeLeft = 1;\n            EditorCursor.NPC.Active = true;\n            EditorCursor.NPC.JustActivated = 1;\n            SetCursor();\n        }\n        else\n            PlaySound(SFX_Fireworks);\n\n        break;\n    }\n    }\n\n    IntProc::cmdUnLock();\n}\n#endif // THEXTECH_INTERPROC_SUPPORTED\n\n// int EditorNPCFrame(const NPCID A, vbint_t C, int N)\n// {\n//     return EditorNPCFrame(A, C, N);\n// }\n\nint EditorNPCFrame(const NPCID A, vbint_t& C, int N)\n{\n    int ret = 0;\n// find the default left/right frames for NPCs\n\n    if(A > maxNPCType)\n        return ret;\n    int B = 0;\n    int D = 0;\n    int E = 0;\n    B = C;\n    while(B == 0)\n        B = iRand(3) - 1;\n\n    if(!LevelEditor)\n        C = B;\n    if(A == 241)\n        ret = 4;\n    if(A == 195)\n        ret = 3;\n\n    // suits\n    if(N > 0)\n    {\n        if(A == 169 || A == 170)\n        {\n            E = 0;\n            for(D = 1; D <= numPlayers; D++)\n            {\n                if(!Player[D].Dead && Player[D].Section == NPC[N].Section && Player[D].Character != 3 &&\n                    Player[D].Character != 4 && Player[D].TimeToLive == 0)\n                {\n                    // this logic is expensive and broken but must be kept because this NPC's frame affects the RNG state (via effect spawning)\n                    int dist = (int)(num_t::abs(NPC[N].Location.minus_center_x(Player[D].Location))\n                        + num_t::abs(NPC[N].Location.minus_center_y(Player[D].Location)));\n\n                    // dist should get compared to E, but instead it's compared to D\n                    if(E == 0 || dist < D)\n                    {\n                        E = dist;\n                        if(Player[D].Character == 5)\n                            ret = 1;\n                        else\n                            ret = 0;\n                    }\n                }\n            }\n        }\n    }\n\n\n    if(A == 135 || A == 4 || A == 6 || A == 19 || A == 20 || A == 23 || A == 25 || A == 28 || A == 36 || A == 38 ||\n       A == 42 || A == 43 || A == 44 || A == 193 || A == 35 || A == 191 || A == 52 || A == 72 || A == 77 || A == 108 ||\n       (A >= 109 && A <= 112) || (A >= 121 && A <= 124) || A == 125 || (A >= 129 && A <= 132) || A == 136 || A == 158 ||\n        A == 164 || A == 163 || A == 162 || A == 165 || A == 166 || A == 189 || A == 199 || A == 209 || A == 207 ||\n        A == 229 || A == 230 || A == 232 || A == 236 || A == 233 || A == 173 || A == 175 || A == 177 ||\n        A == 178 || A == 176) // Koopa troopas / Shy guy\n    {\n        if(int(B) == -1)\n            ret = 0;\n        else\n            ret = 2;\n    }\n\n    // Bullet Bills\n    if(A == 17 || A == 18 || A == 29 || A == 31 || A == 84 || A == 94 || A == 198 ||\n       NPCIsYoshi(A) || A == 101 || A == 102 || A == 181 || A == 81)\n    {\n        if(int(B) == -1)\n            ret = 0;\n        else\n            ret = 1;\n    }\n\n    // Hammer Bros.\n    if(A == 29 || A == 55 || A == 75 || A == 78 || A == 168 || A == 234)\n    {\n        if(int(B) == -1)\n            ret = 0;\n        else\n            ret = 3;\n    }\n\n    if(A == 34)\n    {\n        if(int(B) == -1)\n            ret = 1;\n        else\n            ret = 0;\n    }\n\n    if(A == 201)\n    {\n        if(int(B) == -1)\n            ret = 0;\n        else\n            ret = 8;\n    }\n\n    if(A == 137)\n    {\n        if(int(B) == -1)\n            ret = 0;\n        else\n            ret = 6;\n    }\n\n    if(A == 86 || (A >= 117 && A <= 120) || A == 200)\n    {\n        if(int(B) == -1)\n            ret = 0;\n        else\n            ret = 5;\n    }\n\n    // winged koopa / bob-omb buddy\n    if(A == 76 || A == 107 || A == 160 || A == 161 || A == 167 || A == 203 || A == 204)\n    {\n        if(int(B) == -1)\n            ret = 0;\n        else\n            ret = 4;\n    }\n\n    // Birdo\n    if(A == 39 || A == 208)\n    {\n        if(int(B) == -1)\n            ret = 0;\n        else\n            ret = 5;\n    }\n\n    if(A == 45)\n        ret = BlockFrame[4];\n\n    if(A == 56)\n    {\n        ret = SpecialFrame[2];\n        if(int(B) == 1)\n            ret += 4;\n    }\n\n    if(A == 57) // smb3 belt\n    {\n        if(int(B) == -1)\n            ret = SpecialFrame[4];\n        else\n            ret = 3 - SpecialFrame[4];\n    }\n\n    if(A == 60 || A == 62 || A == 64 || A == 66)\n    {\n        if(int(B) == -1)\n            ret = 1;\n    }\n\n    return ret;\n}\n\ninline void s_CloseProps()\n{\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n    // Tell the IPC Editor to close the properties dialog\n    if(IntProc::isWorking())\n        IntProc::sendCloseProperties();\n#endif\n}\n\nvoid GetEditorControls()\n{\n    if(GamePaused != PauseCode::None)\n    {\n        if(HasCursor)\n            HideCursor();\n        return;\n    }\n\n    if(SharedCursor.Move)\n        MouseMove((int)SharedCursor.X, (int)SharedCursor.Y);\n\n    if(SharedCursor.Secondary || (EditorControls.ModeSelect && !MagicHand))\n    {\n        if(EditorCursor.Mode != OptCursor_t::LVL_SELECT && EditorCursor.Mode != OptCursor_t::LVL_ERASER)\n            s_CloseProps();\n\n        EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n        SetCursor();\n    }\n\n    if(SharedCursor.Tertiary || (EditorControls.ModeErase && !MagicHand))\n    {\n        if(EditorCursor.Mode != OptCursor_t::LVL_SELECT && EditorCursor.Mode != OptCursor_t::LVL_ERASER)\n            s_CloseProps();\n\n        EditorCursor.Mode = OptCursor_t::LVL_ERASER;\n        SetCursor();\n    }\n\n    if(MagicHand)\n        return;\n\n    if(!WorldEditor && EditorControls.TestPlay && MouseRelease)\n    {\n        EditorBackup();\n        Backup_FullFileName = FullFileName;\n        FullFileName = FullFileName + \"tst\";\n        SaveLevel(FullFileName, FileFormat);\n\n        if(g_config.EnableInterLevelFade)\n            g_levelScreenFader.setupFader(4, 0, 65, ScreenFader::S_FADE);\n        else\n            g_levelScreenFader.setupFader(65, 0, 65, ScreenFader::S_FADE);\n        editorWaitForFade();\n\n        // force reconnect on leveltest start\n        Controls::ClearInputMethods();\n\n        HasCursor = false;\n        zTestLevel(editorScreen.test_magic_hand);\n    }\n    if(EditorControls.SwitchScreens && MouseRelease)\n    {\n#ifdef __3DS__\n        int win_x, win_y;\n        XRender::mapFromScreen((int)SharedCursor.X, (int)SharedCursor.Y, &win_x, &win_y);\n#endif\n\n        editorScreen.active = !editorScreen.active;\n\n#ifdef __3DS__\n        int m_x, m_y;\n        XRender::mapToScreen(win_x, win_y, &m_x, &m_y);\n        SharedCursor.X = m_x;\n        SharedCursor.Y = m_y;\n        MouseMove(m_x, m_y);\n#endif\n\n        HasCursor = false;\n        MouseRelease = false;\n        MenuMouseRelease = false;\n        PlaySound(SFX_Pause);\n    }\n\n    if(EditorControls.ScrollDown || EditorControls.ScrollUp || EditorControls.ScrollLeft || EditorControls.ScrollRight)\n    {\n        scroll_buffer_x += EditorControls.ScrollRight;\n        scroll_buffer_x -= EditorControls.ScrollLeft;\n        scroll_buffer_y += EditorControls.ScrollDown;\n        scroll_buffer_y -= EditorControls.ScrollUp;\n    }\n    else\n    {\n        scroll_buffer_x = 0;\n        scroll_buffer_y = 0;\n    }\n}\n\nvoid SetCursor()\n{\n//    EditorCursor.Layer = frmLayers::lstLayer::List(frmLayers::lstLayer::ListIndex);\n    if(EditorCursor.Mode == OptCursor_t::LVL_ERASER) // Eraser\n    {\n        EditorCursor.Location.Width = 18;\n        EditorCursor.Location.Height = 8;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_SELECT || EditorCursor.Mode == 14) // Selection\n    {\n        EditorCursor.Location.Width = 4;\n        EditorCursor.Location.Height = 4;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_WATER) // Water\n    {\n//        EditorCursor.Location.Height = frmWater::WaterH * 32;\n//        EditorCursor.Location.Width = frmWater::WaterW * 32;\n        if(EditorCursor.Water.Location.Width < 32)\n            EditorCursor.Water.Location.Width = 32;\n        if(EditorCursor.Water.Location.Height < 32)\n            EditorCursor.Water.Location.Height = 32;\n        EditorCursor.Location.Height = EditorCursor.Water.Location.Height;\n        EditorCursor.Location.Width = EditorCursor.Water.Location.Width;\n        EditorCursor.Water.Location.X = EditorCursor.Location.X;\n        EditorCursor.Water.Location.Y = EditorCursor.Location.Y;\n        // EditorCursor.Water.Buoy = 0; // frmWater.scrBuoy / 100\n        EditorCursor.Water.Layer = EditorCursor.Layer;\n//        if(frmWater::Quicksand.Caption == \"Yes\")\n            // EditorCursor.Water.Quicksand = false;\n//        else\n//            EditorCursor.Water.Quicksand = false;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_BLOCKS) // Blocks\n    {\n        if(EditorCursor.Block.Type <= 0)\n            EditorCursor.Block.Type = 1;\n        if(EditorCursor.Block.Type > maxBlockType)\n            EditorCursor.Block.Type = 1;\n\n        EditorCursor.Block.Location.X = EditorCursor.Location.X;\n        EditorCursor.Block.Location.Y = EditorCursor.Location.Y;\n        EditorCursor.Block.Layer = EditorCursor.Layer;\n//        EditorCursor.Block.TriggerHit = frmAdvancedBlock::TriggerHit.Text;\n//        EditorCursor.Block.TriggerDeath = frmAdvancedBlock::TriggerDeath.Text;\n//        EditorCursor.Block.TriggerLast = frmAdvancedBlock::TriggerLast.Text;\n//        for(A = 1; A <= frmBlocks::Block.Count; A++)\n//        {\n//            if(frmBlocks::Block(A).Value == true && frmBlocks::Block(A).Visible == true)\n//            {\n//                EditorCursor.Block.Type = A;\n//                break;\n//            }\n//        }\n        if(!BlockIsSizable[EditorCursor.Block.Type])\n        {\n            if(EditorCursor.Block.Location.Width <= 0)\n            {\n                if(BlockWidth[EditorCursor.Block.Type] > 0)\n                    EditorCursor.Block.Location.Width = BlockWidth[EditorCursor.Block.Type];\n                else\n                    EditorCursor.Block.Location.Width = 32;\n            }\n\n            if(EditorCursor.Block.Location.Height <= 0)\n            {\n                if(BlockHeight[EditorCursor.Block.Type] > 0)\n                    EditorCursor.Block.Location.Height = BlockHeight[EditorCursor.Block.Type];\n                else\n                    EditorCursor.Block.Location.Height = 32;\n            }\n        }\n\n        EditorCursor.Location.Width = EditorCursor.Block.Location.Width;\n        EditorCursor.Location.Height = EditorCursor.Block.Location.Height;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_PLAYERSTART) // Level\n    {\n        if(EditorCursor.SubMode == 4)\n        {\n            EditorCursor.Location.Width = Physics.PlayerWidth[1][2]; // Mario\n            EditorCursor.Location.Height = Physics.PlayerHeight[1][2];\n        }\n        else if(EditorCursor.SubMode == 5)\n        {\n            EditorCursor.Location.Width = Physics.PlayerWidth[2][2]; // Luigi\n            EditorCursor.Location.Height = Physics.PlayerHeight[2][2];\n        }\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_BGOS) // Background\n    {\n        if(EditorCursor.Background.Type <= 0)\n            EditorCursor.Background.Type = 1;\n        if(EditorCursor.Background.Type > maxBackgroundType)\n            EditorCursor.Background.Type = 1;\n\n        EditorCursor.Background.Layer = EditorCursor.Layer;\n        EditorCursor.Background.Location.X = EditorCursor.Location.X;\n        EditorCursor.Background.Location.Y = EditorCursor.Location.Y;\n        EditorCursor.Background.Location.Width = BackgroundWidth[EditorCursor.Background.Type];\n        EditorCursor.Background.Location.Height = BackgroundHeight[EditorCursor.Background.Type];\n        EditorCursor.Location.Width = EditorCursor.Background.Location.Width;\n        EditorCursor.Location.Height = EditorCursor.Background.Location.Height;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_NPCS) // NPCs\n    {\n        NPCID t = EditorCursor.NPC.Type;\n\n        // Container NPCs are handled elsewhere in new editor\n        if(MagicHand)\n        {\n            if(!NPCIsContainer(EditorCursor.NPC) && !NPCTraits[t].IsFish && !NPCIsAParaTroopa(t) && t != NPCID_FIRE_CHAIN)\n                EditorCursor.NPC.Special = 0;\n        }\n\n        EditorCursor.NPC.Special2 = 0;\n        EditorCursor.NPC.Special5 = 0;\n\n        if(!EditorCursor.NPC.Generator)\n        {\n            EditorCursor.NPC.Special3 = 0;\n            EditorCursor.NPC.Special4 = 0;\n        }\n\n        // EditorCursor.NPC.Special6 = 0;\n        EditorCursor.NPC.SpecialX = 0;\n        EditorCursor.NPC.SpecialY = 0;\n        EditorCursor.NPC.Layer = EditorCursor.Layer;\n        EditorCursor.NPC.Location = EditorCursor.Location;\n\n        if(EditorCursor.NPC->TWidth > 0)\n            EditorCursor.NPC.Location.Width = EditorCursor.NPC->TWidth;\n        else\n            EditorCursor.NPC.Location.Width = 32;\n        if(EditorCursor.NPC->THeight > 0)\n            EditorCursor.NPC.Location.Height = EditorCursor.NPC->THeight;\n        else\n            EditorCursor.NPC.Location.Height = 32;\n        EditorCursor.Location.Width = EditorCursor.NPC.Location.Width;\n        EditorCursor.Location.Height = EditorCursor.NPC.Location.Height;\n\n        EditorCursor.Location.SpeedX = 0;\n        EditorCursor.Location.SpeedY = 0;\n\n        EditorCursor.NPC.Frame = EditorNPCFrame(EditorCursor.NPC.Type, EditorCursor.NPC.Direction);\n        EditorCursor.NPC.Active = true;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_WARPS) // Warps\n    {\n        EditorCursor.Warp.Layer = EditorCursor.Layer;\n        if(EditorCursor.Location.Width < 32)\n            EditorCursor.Location.Width = 32;\n        if(EditorCursor.Location.Height < 32)\n            EditorCursor.Location.Height = 32;\n        // EditorCursor.Warp is now the canonical Warp object.\n        // It stores the warp's entrance and exit until the warp is placed,\n        // instead of finding and modifying an existing warp.\n        // EditorCursor.Warp.Entrance = EditorCursor.Location;\n        // EditorCursor.Warp.Exit = EditorCursor.Location;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_TILES) // Tiles\n    {\n        if(EditorCursor.Tile.Type == 0)\n            EditorCursor.Tile.Type = 1;\n        EditorCursor.Location.Width = TileWidth[EditorCursor.Tile.Type];\n        EditorCursor.Location.Height = TileHeight[EditorCursor.Tile.Type];\n        EditorCursor.Tile.Location = static_cast<TinyLocation_t>(EditorCursor.Location);\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_SCENES) // Scene\n    {\n        if(EditorCursor.Scene.Type == 0)\n            EditorCursor.Scene.Type = 1;\n        EditorCursor.Location.Width = SceneWidth[EditorCursor.Scene.Type];\n        EditorCursor.Location.Height = SceneHeight[EditorCursor.Scene.Type];\n        EditorCursor.Scene.Location = static_cast<TinyLocation_t>(EditorCursor.Location);\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_LEVELS) // Levels\n    {\n        if(EditorCursor.WorldLevel.Type == 0)\n            EditorCursor.WorldLevel.Type = 1;\n        EditorCursor.Location.Width = 32;\n        EditorCursor.Location.Height = 32;\n        EditorCursor.WorldLevel.Location = static_cast<TinyLocation_t>(EditorCursor.Location);\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_PATHS) // Paths\n    {\n        if(EditorCursor.WorldPath.Type == 0)\n            EditorCursor.WorldPath.Type = 1;\n        EditorCursor.Location.Width = 32;\n        EditorCursor.Location.Height = 32;\n        EditorCursor.WorldPath.Location = static_cast<TinyLocation_t>(EditorCursor.Location);\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_MUSIC) // World Music\n    {\n        EditorCursor.Location.Height = 32;\n        EditorCursor.Location.Width = 32;\n        EditorCursor.WorldMusic.Location = static_cast<TinyLocation_t>(EditorCursor.Location);\n        // make it play the music\n        if(g_isWorldMusicNotSame(EditorCursor.WorldMusic))\n            g_playWorldMusic(EditorCursor.WorldMusic);\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_AREA) // World Area\n    {\n        EditorCursor.Location.Height = 32;\n        EditorCursor.Location.Width = 32;\n        EditorCursor.WorldArea.Location.X = (int)EditorCursor.Location.X;\n        EditorCursor.WorldArea.Location.Y = (int)EditorCursor.Location.Y;\n\n        if(EditorCursor.WorldArea.Location.Width < 32)\n            EditorCursor.WorldArea.Location.Width = 32;\n\n        if(EditorCursor.WorldArea.Location.Height < 32)\n            EditorCursor.WorldArea.Location.Height = 32;\n    }\n}\n\nvoid PositionCursor()\n{\n//    if(EditorCursor.Mode == 4 && frmNPCs::Buried.Caption == \"Yes\")\n//        EditorCursor.Location.Y += 16;\n\n    if(EditorCursor.Mode == OptCursor_t::LVL_SELECT)\n        return;\n\n    if(!enableAutoAlign)\n    {\n        EditorCursor.Location.X += -EditorCursor.Location.Width + 4;\n        EditorCursor.Location.Y += -EditorCursor.Location.Height + 12;\n        return;\n    }\n\n    if(EditorCursor.Mode == OptCursor_t::LVL_PLAYERSTART)\n        EditorCursor.Location.X -= 14;\n\n    if(EditorCursor.Mode == OptCursor_t::LVL_PLAYERSTART || EditorCursor.Mode == OptCursor_t::LVL_NPCS)\n    {\n        if(!(EditorCursor.Mode == OptCursor_t::LVL_NPCS && EditorCursor.NPC.Type == 52))\n        {\n            num_t odd_width = EditorCursor.Location.Width - num_t::floor(EditorCursor.Location.Width / 32) * 32;\n            num_t odd_height = EditorCursor.Location.Height - num_t::floor(EditorCursor.Location.Height / 32) * 32;\n\n            if(odd_width != 0)\n            {\n                if(EditorCursor.Location.Width > 32)\n                    EditorCursor.Location.X += -odd_width / 2;\n                else\n                    EditorCursor.Location.X += 16 - odd_width / 2;\n            }\n\n            if(odd_height != 0)\n                EditorCursor.Location.Y += -odd_height;\n        }\n    }\n\n    if(EditorCursor.Mode == OptCursor_t::LVL_BGOS)\n    {\n        if(EditorCursor.Background.Type == 13) // End level container\n        {\n            EditorCursor.Location.X -= 12;\n            EditorCursor.Location.Y -= 12;\n        }\n        if(EditorCursor.Background.Type == 156 || EditorCursor.Background.Type == 157)\n            EditorCursor.Location.Y += 16;\n    }\n\n    else if(EditorCursor.Mode == OptCursor_t::LVL_NPCS)\n    {\n        if(EditorCursor.NPC.Type == 245 || EditorCursor.NPC.Type == 8 ||\n           EditorCursor.NPC.Type == 270 || EditorCursor.NPC.Type == 93 ||\n           EditorCursor.NPC.Type == 180 || EditorCursor.NPC.Type == 179 ||\n           EditorCursor.NPC.Type == 37 || EditorCursor.NPC.Type == 51) // Piranha Plants\n            EditorCursor.Location.X += 16;\n        else if(EditorCursor.NPC.Type == 197)\n        {\n            EditorCursor.Location.X -= 8;\n            EditorCursor.Location.Y += 16;\n        }\n\n//        if(frmNPCs::Buried.Caption == \"Yes\")\n        if(EditorCursor.NPC.Type == NPCID_ITEM_BURIED)\n        {\n            if(!enableAutoAlign)\n                EditorCursor.Location.Y += 0/*16*/;\n            else\n                EditorCursor.Location.Y += 16/*32*/;\n        }\n        else if(EditorCursor.NPC.Type == 105)\n            EditorCursor.Location.Y += 22;\n        else if(EditorCursor.NPC.Type == 106)\n            EditorCursor.Location.Y += 16;\n        else if(EditorCursor.NPC.Type == 260)\n            EditorCursor.Location.Y -= 8;\n    }\n\n    if(EditorCursor.Mode == OptCursor_t::LVL_NPCS)\n    {\n        if(EditorCursor.NPC->THeight < 32)\n            EditorCursor.Location.Y += 32;\n    }\n}\n\nvoid HideCursor()\n{\n    // printf(\"Hiding cursor...\\n\");\n    EditorCursor.Location.X = vScreen[1].X - 800;\n    EditorCursor.X = (int)EditorCursor.Location.X;\n    EditorCursor.Location.Y = vScreen[1].Y - 600;\n    EditorCursor.Y = (int)EditorCursor.Location.Y;\n    HasCursor = false;\n    EditorControls.ScrollDown = false;\n    EditorControls.ScrollRight = false;\n    SharedCursor.Primary = false;\n    MouseCancel = true;\n    EditorControls.ScrollLeft = false;\n    EditorControls.ScrollUp = false;\n    SharedCursor.GoOffscreen();\n}\n\nvoid KillWarp(int A)\n{\n    Warp_t blankWarp;\n    Warp[A] = Warp[numWarps];\n    Warp[numWarps] = blankWarp;\n    numWarps--;\n    syncLayers_Warp(A);\n    syncLayers_Warp(numWarps+1);\n}\n\nvoid zTestLevel(bool magicHand, bool interProcess)\n{\n    int A = 0;\n    Player_t blankPlayer;\n    qScreen = false;\n    qScreen_canonical = false;\n\n#ifndef THEXTECH_INTERPROC_SUPPORTED\n    UNUSED(interProcess);\n#endif\n\n    for(A = 1; A <= numCharacters; A++)\n    {\n        SavedChar[A] = blankPlayer;\n        SavedChar[A].State = 1;\n        SavedChar[A].Character = A;\n    }\n\n    if(numPlayers == 0)\n        numPlayers = editorScreen.num_test_players;\n\n    if(BattleMode && numPlayers < 2)\n        numPlayers = 2;\n\n    for(A = 1; A <= numNPCs; A++)\n    {\n        auto &n = NPC[A];\n        // If .Generator = True Then\n        n.TimeLeft = 0;\n        n.Active = false;\n        // End If\n    }\n\n    GamePaused = PauseCode::None;\n    MessageText.clear();\n//  frmNPCs::chkMessage.Value = 0;\n    for(int i = 1; i <= numPlayers; i++)\n    {\n        BattleLives[i] = 3;\n        Player[i].Hearts = 1;\n    }\n    BattleIntro = 150;\n    BattleWinner = 0;\n    BattleOutro = 0;\n//  frmLevelEditor::mnuOnline.Enabled = false;\n    StopMusic();\n    Score = 0;\n    Coins = 0;\n    Lives = 3;\n    g_100s = 3;\n\n    if(Checkpoint.empty()) // Don't reset players when resume at the checkpoint\n    {\n        if(g_ClonedPlayerMode)\n        {\n            for(A = 1; A <= numPlayers; A++)\n            {\n                Player[A] = Player_t();\n                Player[A].Hearts = testPlayer[1].Hearts;\n                Player[A].State = testPlayer[1].State;\n                Player[A].HeldBonus = testPlayer[1].HeldBonus;\n                Player[A].Dead = false;\n                Player[A].Mount = testPlayer[1].Mount;\n                Player[A].MountType = testPlayer[1].MountType;\n                Player[A].Character = testPlayer[1].Character;\n                Player[A].UnStart = false;\n                if(Player[A].Character == 0)\n                    Player[A].Character = 1;\n            }\n        }\n        else\n        {\n            for(A = numPlayers; A >= 1; A--)\n            {\n                Player[A] = Player_t();\n                Player[A].Hearts = testPlayer[A].Hearts;\n                Player[A].State = testPlayer[A].State;\n                Player[A].HeldBonus = testPlayer[A].HeldBonus;\n                Player[A].Dead = false;\n                Player[A].Mount = testPlayer[A].Mount;\n                Player[A].MountType = testPlayer[A].MountType;\n                Player[A].Character = testPlayer[A].Character;\n                if(Player[A].Character == 0)\n                    Player[A].Character = A;\n                SavedChar[Player[A].Character] = Player[A];\n                Player[A].UnStart = false;\n            }\n        }\n\n        StartWarp = testStartWarp;\n    }\n\n    LevelEditor = false;\n    TestLevel = true;\n\n    // assign players to screens\n    InitScreens();\n    for(A = 1; A <= numPlayers; A++)\n    {\n        Screens_AssignPlayer(A, *l_screen);\n        if(g_ClonedPlayerMode)\n            break;\n    }\n\n    UpdateInternalRes();\n    XMessage::PushMessage({XMessage::Type::multiplayer_prefs, (uint8_t)g_config.two_screen_mode.m_value, (uint8_t)g_config.four_screen_mode.m_value});\n\n    SetupPlayers();\n    MagicHand = magicHand;\n    FontManager::clearAllCustomFonts();\n\n    // this clears the cached medals and stars data from the level\n    LevelWarpSaveEntries.clear();\n\n    // dead code, TestFullscreen never set\n#if 0\n    if(TestFullscreen)\n    {\n#ifndef RENDER_FULLSCREEN_ALWAYS\n        ChangeScreen();\n#endif\n        XEvents::doEvents();\n        MagicHand = false;\n    }\n#endif\n\n    // in speedrun mode, confirm that controls are set up before game starts\n    if((int)g_config.speedrun_mode != 0 && !Controls::Update())\n    {\n        ClearLevel();\n        // force players offscreen\n        for(int i = 1; i <= maxLocalPlayers; i++)\n            Player[i].Location.X = -20000;\n\n        LevelBeatCode = BEATCODE_SETUP;\n        QuickReconnectScreen::g_active = true;\n        PauseGame(PauseCode::PauseScreen);\n        LevelBeatCode = BEATCODE_NONE;\n    }\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n    if(interProcess)\n    {\n        pLogDebug(\"ICP: Requesting editor for a file....\");\n        IntProc::sendMessage(\"CMD:CONNECT_TO_ENGINE\");\n        ElapsedTimer time;\n        time.start();\n        //wait for accepting of level data\n        // bool timeOut = false;\n        int attempts = 0;\n\n        pLogDebug(\"ICP: Waiting reply....\");\n        IntProc::setState(g_gameStrings.ipcStatusWaitingInput);\n        while(!IntProc::hasLevelData())\n        {\n            UpdateLoadREAL();\n\n            //Abort loading process and exit from game if window was closed\n            if(!GameIsActive)\n                return;\n\n            if(!IntProc::levelReceivingInProcess() && time.elapsed() > 1500)\n            {\n                pLogDebug(\"ICP: Waiting #%d....\", attempts);\n                IntProc::sendMessage(\"CMD:CONNECT_TO_ENGINE\"); // Re-ask again\n                time.restart();\n                attempts += 1;\n            }\n\n            if(attempts > 4)\n            {\n                pLogWarning(\"ICP: Wait timeout\");\n                // timeOut = true;\n                IntProc::setState(g_gameStrings.ipcStatusErrorTimeout);\n                MessageText = g_gameStrings.errorIPCTimeOut;\n                UpdateLoadREAL();\n                g_MessageType = MESSAGE_TYPE_SYS_WARNING;\n                PauseGame(PauseCode::Message);\n                // PGE_Delay(1000);\n                GameIsActive = false;\n                return;\n            }\n\n            PGE_Delay(2);\n        }\n\n        UpdateLoadREAL();\n\n        if(!OpenLevelData(IntProc::editor->m_acceptedLevel, IntProc::editor->m_accepted_lvl_path)) //-V560\n        {\n            pLogWarning(\"Bad file format!\");\n            pLogDebug(\"ERROR: Bad data format\");\n            UpdateLoadREAL();\n            ReportLoadFailure(\"<Interprocess>\", true);\n            // PGE_Delay(1000);\n            GameIsActive = false;\n            return;\n        }\n\n        pLogDebug(\"ICP: Done, starting a game....\");\n        IntProc::setState(g_gameStrings.ipcStatusLoadingDone);\n        UpdateLoadREAL();\n\n        OpenLevelDataPost();\n    }\n    else\n#endif // THEXTECH_INTERPROC_SUPPORTED\n    {\n        if(!OpenLevel(FullFileName))\n        {\n            ReportLoadFailure(FullFileName);\n            ErrorQuit = true;\n        }\n    }\n\n    // reset Drop/Add allowed characters\n    ConnectScreen::SaveChars();\n\n    int waitms = ((int)g_config.speedrun_mode != 0) ? 750 : 0;\n    GameThing(waitms, 0);\n\n    SetupScreens();\n    LevelSelect = false;\n    EndLevel = false;\n    editorScreen.active = false;\n    ReturnWarp = 0;\n\n    EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n}\n\nstatic constexpr int s_resize_border = 8;\n\ntemplate<class LocType>\nstatic inline int s_find_flags(const LocType& loc)\n{\n    // require cursor to be at least nearby\n    if(    EditorCursor.Location.X < loc.X - s_resize_border\n        || EditorCursor.Location.X > loc.X + loc.Width + s_resize_border\n        || EditorCursor.Location.Y < loc.Y - s_resize_border\n        || EditorCursor.Location.Y > loc.Y + loc.Height + s_resize_border)\n    {\n        return 0;\n    }\n\n    int found_flags = 0;\n\n    if(EditorCursor.Location.X < loc.X + s_resize_border)\n        found_flags |= IF_ResizeL;\n    else if(EditorCursor.Location.X > loc.X + loc.Width - s_resize_border)\n        found_flags |= IF_ResizeR;\n\n    if(EditorCursor.Location.Y < loc.Y + s_resize_border)\n        found_flags |= IF_ResizeT;\n    else if(EditorCursor.Location.Y > loc.Y + loc.Height - s_resize_border)\n        found_flags |= IF_ResizeB;\n\n    return found_flags;\n}\n\nstatic inline int s_find_flags_section(const IntegerLocation_t& loc)\n{\n    // require cursor to be at least nearby\n    if(    EditorCursor.Location.X < loc.X - s_resize_border\n        || EditorCursor.Location.X > loc.Width + s_resize_border\n        || EditorCursor.Location.Y < loc.Y - s_resize_border\n        || EditorCursor.Location.Y > loc.Height + s_resize_border)\n    {\n        return 0;\n    }\n\n    int found_flags = 0;\n\n    if(EditorCursor.Location.X < loc.X + s_resize_border)\n        found_flags |= IF_ResizeL;\n    else if(EditorCursor.Location.X > loc.Width - s_resize_border)\n        found_flags |= IF_ResizeR;\n\n    if(EditorCursor.Location.Y < loc.Y + s_resize_border)\n        found_flags |= IF_ResizeT;\n    else if(EditorCursor.Location.Y > loc.Height - s_resize_border)\n        found_flags |= IF_ResizeB;\n\n    return found_flags;\n}\n\nvoid UpdateInteract()\n{\n    // only update items in select mode (mouse up or just pressed) or erase mode\n    bool mouse_held = (SharedCursor.Primary && !MouseRelease);\n    bool select_mode = EditorCursor.Mode == OptCursor_t::LVL_SELECT;\n    bool erase_mode = EditorCursor.Mode == OptCursor_t::LVL_ERASER;\n\n    if(select_mode && mouse_held)\n        return;\n\n    EditorCursor.InteractMode = 0;\n    EditorCursor.InteractFlags = 0;\n    EditorCursor.InteractIndex = 0;\n    EditorCursor.InteractX = (int)EditorCursor.Location.X;\n    EditorCursor.InteractY = (int)EditorCursor.Location.Y;\n\n     if(!select_mode && !erase_mode)\n        return;\n\n    if(MouseCancel)\n        return;\n\n   // class filter for eraser\n    int need_class = 0;\n    if(erase_mode && EditorCursor.SubMode > 0)\n        need_class = EditorCursor.SubMode;\n\n    if(!WorldEditor)\n    {\n        // player start points\n        if(!MagicHand && select_mode)\n        {\n            for(int A = 1; A <= 2; A++)\n            {\n                if(CursorCollision(EditorCursor.Location, PlayerStart[A]))\n                {\n                    EditorCursor.InteractMode = OptCursor_t::LVL_PLAYERSTART;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // NPCs\n        // never sizable, so only do this if nothing found yet\n        if(EditorCursor.InteractMode == 0 && (!need_class || need_class == OptCursor_t::LVL_NPCS))\n        {\n            for(int A : treeNPCQuery(EditorCursor.Location, SORTMODE_ID))\n            {\n                Location_t tempLocation = NPC[A].Location;\n\n                if(NPC[A].Type == NPCID_ITEM_BURIED) // Herb's container offset\n                    tempLocation.Y -= 16;\n\n                if(CursorCollision(EditorCursor.Location, tempLocation) && !NPC[A].Hidden)\n                {\n                    EditorCursor.InteractMode = OptCursor_t::LVL_NPCS;\n                    EditorCursor.InteractFlags = 0;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // non-sizable blocks (same condition)\n        if(EditorCursor.InteractMode == 0 && (!need_class || need_class == OptCursor_t::LVL_BLOCKS))\n        {\n            for(int A : treeBlockQuery(EditorCursor.Location, SORTMODE_ID))\n            {\n                if(BlockIsSizable[Block[A].Type])\n                    continue;\n\n                if(CursorCollision(EditorCursor.Location, Block[A].Location) && !Block[A].Hidden)\n                {\n                    EditorCursor.InteractMode = OptCursor_t::LVL_BLOCKS;\n                    EditorCursor.InteractFlags = 0;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // warps (now sizable)\n        if(!MagicHand && ((select_mode && EditorCursor.InteractFlags < 2) || EditorCursor.InteractMode == 0) && (!need_class || need_class == OptCursor_t::LVL_WARPS))\n        {\n            for(int A = 1; A <= numWarps; A++)\n            {\n                if(Warp[A].Hidden)\n                    continue;\n\n                int entr_flags = (select_mode) ? s_find_flags(Warp[A].Entrance) : 0;\n                int exit_flags = (select_mode) ? s_find_flags(Warp[A].Exit) : 0;\n                if(entr_flags || (EditorCursor.InteractMode == 0 && CursorCollision(EditorCursor.Location, Warp[A].Entrance)))\n                {\n                    EditorCursor.InteractMode = OptCursor_t::LVL_WARPS;\n                    EditorCursor.InteractFlags = entr_flags;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n                else if(exit_flags || (EditorCursor.InteractMode == 0 && CursorCollision(EditorCursor.Location, Warp[A].Exit)))\n                {\n                    EditorCursor.InteractMode = OptCursor_t::LVL_WARPS;\n                    EditorCursor.InteractFlags = IF_AltMode | exit_flags;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // BGOs\n        // never sizable, so only do this if nothing found yet\n        if(EditorCursor.InteractMode == 0 && (!need_class || need_class == OptCursor_t::LVL_BGOS))\n        {\n            // more difficult to iterate backwards, but that's what we need to do here\n            auto sentinel = treeBackgroundQuery(EditorCursor.Location, SORTMODE_Z);\n            for(auto i = sentinel.end(); i > sentinel.begin();)\n            {\n                int A = *(--i);\n\n                if(CursorCollision(EditorCursor.Location, Background[A].Location) && !Background[A].Hidden)\n                {\n                    EditorCursor.InteractMode = OptCursor_t::LVL_BGOS;\n                    EditorCursor.InteractFlags = 0;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // Sizable blocks\n        // now things get exciting\n        if(((select_mode && EditorCursor.InteractFlags < 2) || EditorCursor.InteractMode == 0)\n            && (!need_class || need_class == OptCursor_t::LVL_BLOCKS)) // Sizable blocks\n        {\n            auto sentinel = treeBlockQuery(EditorCursor.Location, SORTMODE_Z);\n            for(auto i = sentinel.end(); i > sentinel.begin();)\n            {\n                int A = *(--i);\n\n                if(!BlockIsSizable[Block[A].Type] || Block[A].Hidden)\n                    continue;\n\n                int found_flags = (select_mode) ? s_find_flags(Block[A].Location) : 0;\n\n                if(found_flags || CursorCollision(EditorCursor.Location, Block[A].Location))\n                {\n                    if(found_flags || EditorCursor.InteractMode == 0)\n                    {\n                        EditorCursor.InteractMode = OptCursor_t::LVL_BLOCKS;\n                        EditorCursor.InteractFlags = found_flags;\n                        EditorCursor.InteractIndex = A;\n                        break;\n                    }\n                }\n            }\n        }\n\n        // Water boxes\n        // can resize\n        if(!MagicHand\n            && ((select_mode && EditorCursor.InteractFlags < 2) || EditorCursor.InteractMode == 0)\n            && (!need_class || need_class == OptCursor_t::LVL_WATER))\n        {\n            for(int A : treeWaterQuery(EditorCursor.Location, SORTMODE_ID))\n            {\n                if(Water[A].Hidden)\n                    continue;\n\n                int found_flags = (select_mode) ? s_find_flags(Water[A].Location) : 0;\n\n                if(found_flags || CursorCollision(EditorCursor.Location, Water[A].Location))\n                {\n                    if(found_flags || EditorCursor.InteractMode == 0)\n                    {\n                        EditorCursor.InteractMode = OptCursor_t::LVL_WATER;\n                        EditorCursor.InteractFlags = found_flags;\n                        EditorCursor.InteractIndex = A;\n                        break;\n                    }\n                }\n            }\n        }\n\n        // event section borders\n        if(!MagicHand && select_mode && EditorCursor.InteractFlags < 2)\n        {\n            for(int A = numEvents - 1; A >= 0; A--)\n            {\n                const auto& sectPos = Events[A].section[curSection].position;\n                if(sectPos.X == EventSection_t::LESet_Nothing || sectPos.X == EventSection_t::LESet_ResetDefault)\n                    continue;\n\n                int found_flags = s_find_flags_section(sectPos);\n\n                if(found_flags)\n                {\n                    EditorCursor.InteractMode = OptCursor_t::LVL_EVENTS;\n                    EditorCursor.InteractFlags = found_flags;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // section borders\n        if(!MagicHand && select_mode && EditorCursor.InteractFlags < 2)\n        {\n            int found_flags = s_find_flags_section(LevelREAL[curSection]);\n\n            if(found_flags)\n            {\n                EditorCursor.InteractMode = OptCursor_t::LVL_SECTION;\n                EditorCursor.InteractFlags = found_flags;\n                EditorCursor.InteractIndex = 0;\n            }\n        }\n    }\n    else\n    {\n        // world map areas\n        // can resize\n        if(((select_mode && EditorCursor.InteractFlags < 2) || EditorCursor.InteractMode == 0)\n             && (!need_class || need_class == OptCursor_t::WLD_AREA))\n        {\n            for(int A = numWorldAreas; A >= 1; A--)\n            {\n                int found_flags = (select_mode) ? s_find_flags(WorldArea[A].Location) : 0;\n\n                // count as collision if on corner, or if in top-left\n                if(found_flags || (EditorCursor.InteractMode == 0 && CursorCollision(EditorCursor.Location, newLoc(WorldArea[A].Location.X, WorldArea[A].Location.Y, 32, 32))))\n                {\n                    EditorCursor.InteractMode = OptCursor_t::WLD_AREA;\n                    EditorCursor.InteractFlags = found_flags;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // world map music\n        if(EditorCursor.InteractMode == 0 && (!need_class || need_class == OptCursor_t::WLD_MUSIC))\n        {\n            for(int A : treeWorldMusicQuery(static_cast<TinyLocation_t>(EditorCursor.Location), SORTMODE_NONE))\n            {\n                if(CursorCollision(EditorCursor.Location, WorldMusic[A].Location))\n                {\n                    EditorCursor.InteractMode = OptCursor_t::WLD_MUSIC;\n                    EditorCursor.InteractFlags = 0;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // world paths\n        if(EditorCursor.InteractMode == 0 && (!need_class || need_class == OptCursor_t::WLD_PATHS))\n        {\n            for(int A : treeWorldPathQuery(static_cast<TinyLocation_t>(EditorCursor.Location), SORTMODE_NONE))\n            {\n                if(CursorCollision(EditorCursor.Location, WorldPath[A].Location))\n                {\n                    EditorCursor.InteractMode = OptCursor_t::WLD_PATHS;\n                    EditorCursor.InteractFlags = 0;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // world sceneries\n        if(EditorCursor.InteractMode == 0 && (!need_class || need_class == OptCursor_t::WLD_SCENES))\n        {\n            // harder to go backwards, but that's all we're doing here.\n            // it's a good thing that the sentinel's scope ends quickly,\n            // otherwise it would take a long time for the result vector\n            // to rejoin the pool. -- ds-sloth\n            auto sentinel = treeWorldSceneQuery(static_cast<TinyLocation_t>(EditorCursor.Location), SORTMODE_ID);\n            for(auto i = sentinel.end(); i > sentinel.begin();)\n            {\n                int A = *(--i);\n\n                if(CursorCollision(EditorCursor.Location, Scene[A].Location))\n                {\n                    EditorCursor.InteractMode = OptCursor_t::WLD_SCENES;\n                    EditorCursor.InteractFlags = 0;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // world levels\n        if(EditorCursor.InteractMode == 0 && (!need_class || need_class == OptCursor_t::WLD_LEVELS))\n        {\n            for(int A : treeWorldLevelQuery(static_cast<TinyLocation_t>(EditorCursor.Location), SORTMODE_NONE))\n            {\n                if(CursorCollision(EditorCursor.Location, WorldLevel[A].Location))\n                {\n                    EditorCursor.InteractMode = OptCursor_t::WLD_LEVELS;\n                    EditorCursor.InteractFlags = 0;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n\n        // world tiles\n        if(EditorCursor.InteractMode == 0 && (!need_class || need_class == OptCursor_t::WLD_TILES))\n        {\n            for(int A : treeWorldTileQuery(static_cast<TinyLocation_t>(EditorCursor.Location), SORTMODE_NONE))\n            {\n                if(CursorCollision(EditorCursor.Location, Tile[A].Location))\n                {\n                    EditorCursor.InteractMode = OptCursor_t::WLD_TILES;\n                    EditorCursor.InteractFlags = 0;\n                    EditorCursor.InteractIndex = A;\n                    break;\n                }\n            }\n        }\n    }\n}\n\ntemplate<class LocType>\nvoid InteractResize(LocType& loc, int min, int snap)\n{\n    if(EditorCursor.InteractFlags & IF_ResizeL)\n    {\n        num_t dl = EditorCursor.Location.X - loc.X;\n        if(dl > snap && loc.Width > min)\n        {\n            loc.X += snap;\n            loc.Width -= snap;\n            PlaySound(SFX_Saw);\n        }\n        else if(dl < -snap)\n        {\n            loc.X -= snap;\n            loc.Width += snap;\n            PlaySound(SFX_Saw);\n        }\n    }\n\n    if(EditorCursor.InteractFlags & IF_ResizeR)\n    {\n        num_t dr = EditorCursor.Location.X - (loc.X + loc.Width);\n        if(dr < -snap && loc.Width > min)\n        {\n            loc.Width -= snap;\n            PlaySound(SFX_Saw);\n        }\n        else if(dr > snap)\n        {\n            loc.Width += snap;\n            PlaySound(SFX_Saw);\n        }\n    }\n\n    if(EditorCursor.InteractFlags & IF_ResizeT)\n    {\n        num_t dt = EditorCursor.Location.Y - loc.Y;\n        if(dt > snap && loc.Height > min)\n        {\n            loc.Y += snap;\n            loc.Height -= snap;\n            PlaySound(SFX_Saw);\n        }\n        else if(dt < -snap)\n        {\n            loc.Y -= snap;\n            loc.Height += snap;\n            PlaySound(SFX_Saw);\n        }\n    }\n\n    if(EditorCursor.InteractFlags & IF_ResizeB)\n    {\n        num_t db = EditorCursor.Location.Y - (loc.Y + loc.Height);\n        if(db < -snap && loc.Height > min)\n        {\n            loc.Height -= snap;\n            PlaySound(SFX_Saw);\n        }\n        else if(db > snap)\n        {\n            loc.Height += snap;\n            PlaySound(SFX_Saw);\n        }\n    }\n}\n\nvoid InteractResizeSection(IntegerLocation_t& section)\n{\n    bool resized = false;\n\n    if(EditorCursor.InteractFlags & IF_ResizeT)\n    {\n        int new_Y = num_t::round(EditorCursor.Location.Y / 32) * 32;\n        if(section.Height - new_Y < 600)\n            new_Y = section.Height - 600;\n\n        if(new_Y != section.Y)\n        {\n            section.Y = new_Y;\n            resized = true;\n        }\n    }\n\n    if(EditorCursor.InteractFlags & IF_ResizeL)\n    {\n        int new_X = num_t::round(EditorCursor.Location.X / 32) * 32;\n        if(section.Width - new_X < 800)\n            new_X = section.Width - 800;\n\n        if(new_X != section.X)\n        {\n            section.X = new_X;\n            resized = true;\n        }\n    }\n\n    if(EditorCursor.InteractFlags & IF_ResizeR)\n    {\n        int new_Width = num_t::round(EditorCursor.Location.X / 32) * 32;\n        if(new_Width - section.X < 800)\n            new_Width = section.X + 800;\n\n        if(new_Width != section.Width)\n        {\n            section.Width = new_Width;\n            resized = true;\n        }\n    }\n\n    if(EditorCursor.InteractFlags & IF_ResizeB)\n    {\n        int new_Height = num_t::round(EditorCursor.Location.Y / 32) * 32;\n        if(new_Height - section.Y < 600)\n            new_Height = section.Y + 600;\n\n        if(new_Height != section.Height)\n        {\n            section.Height = new_Height;\n            resized = true;\n        }\n    }\n\n    if(resized)\n        PlaySound(SFX_Saw);\n}\n\nvoid MouseMove(int X, int Y, bool /*nCur*/)\n{\n    EditorCursor.X = X;\n    EditorCursor.Y = Y;\n\n    HasCursor = true;\n\n    // figure out which VScreen to use\n    int A = 1;\n    if(SingleCoop > 0)\n        A = SingleCoop;\n    else if(l_screen->Type == 5 && vScreen[2].Visible)\n    {\n        if(X < vScreen[2].TargetX() + vScreen[2].Width)\n        {\n            if(X > vScreen[2].TargetX())\n            {\n                if(Y < vScreen[2].TargetY() + vScreen[2].Height)\n                {\n                    if(Y > vScreen[2].TargetY())\n                        A = 2;\n                }\n            }\n        }\n    }\n    else\n        A = 1;\n\n    X -= vScreen[A].TargetX();\n    Y -= vScreen[A].TargetY();\n\n    if(XRender::TargetOverscanX && WorldEditor)\n        X -= XRender::TargetOverscanX;\n\n    num_t lX = 0, lY = 0;\n\n    // translate into layer coordinates to snap to layer's grid\n    if(MagicHand && EditorCursor.Layer != LAYER_NONE)\n    {\n        lX = 32 - (Layer[EditorCursor.Layer].OffsetX - num_t::floor(Layer[EditorCursor.Layer].OffsetX / 32) * 32);\n        lY = 32 - (Layer[EditorCursor.Layer].OffsetY - num_t::floor(Layer[EditorCursor.Layer].OffsetY / 32) * 32);\n        X += (int)lX;\n        Y += (int)lY;\n    }\n\n    if(EditorCursor.Mode == OptCursor_t::LVL_ERASER || EditorCursor.Mode == OptCursor_t::LVL_SELECT /*|| frmLevelEditor::chkAlign.Value == 0*/)\n    {\n        EditorCursor.Location.X = X - vScreen[A].X;\n        EditorCursor.Location.Y = Y - vScreen[A].Y;\n        PositionCursor();\n    }\n    else\n    {\n        int vScreenX = num_t::floor(vScreen[A].X / 32) * 32;\n        int vScreenY = num_t::floor((vScreen[A].Y + 8) / 32) * 32 - 8;\n\n        X += (int)(vScreenX - vScreen[A].X);\n        Y += (int)(vScreenY - vScreen[A].Y);\n\n        // 16x16 alignment\n        if(\n            (EditorCursor.Mode == OptCursor_t::LVL_BLOCKS &&\n            (EditorCursor.Block.Type == 534 || EditorCursor.Block.Type == 535 ||\n             EditorCursor.Block.Type == 536 || EditorCursor.Block.Type == 537)) ||\n\n             EditorCursor.Mode == OptCursor_t::LVL_WARPS ||\n\n            (EditorCursor.Mode == OptCursor_t::LVL_NPCS &&\n             EditorCursor.NPC.Type == 52) ||\n\n            (EditorCursor.Mode == OptCursor_t::LVL_BGOS &&\n             (EditorCursor.Background.Type == 71 ||\n              EditorCursor.Background.Type == 72 ||\n              EditorCursor.Background.Type == 73 ||\n              EditorCursor.Background.Type == 141 ||\n              EditorCursor.Background.Type == 74 ||\n              EditorCursor.Background.Type == 70 ||\n              EditorCursor.Background.Type == 100)) ||\n\n              (EditorCursor.Mode == OptCursor_t::LVL_NPCS &&\n               (EditorCursor.NPC.Generator ||\n                EditorCursor.NPC.Type == 209 || EditorCursor.NPC.Type == 256 ||\n                EditorCursor.NPC.Type == 257 || EditorCursor.NPC.Type == 260))\n        )\n        {\n            if(!(ffEqual(EditorCursor.Location.X, (X / 16) * 16 - vScreenX) &&\n                 ffEqual(EditorCursor.Location.Y, ((Y + 8) / 16) * 16 - vScreenY - 8)) )\n            {\n                EditorCursor.Location.X = (X / 16) * 16 - vScreenX;\n                EditorCursor.Location.Y = ((Y + 8) / 16) * 16 - vScreenY - 8;\n                PositionCursor();\n            }\n        }\n        else if(EditorCursor.Mode == OptCursor_t::LVL_PLAYERSTART)\n        {\n            if(!(EditorCursor.Location.X == (X / 8) * 8 - vScreenX && EditorCursor.Location.Y == (Y / 8) * 8 - vScreenY))\n            {\n                EditorCursor.Location.X = (X / 8) * 8 - vScreenX;\n                EditorCursor.Location.Y = (Y / 8) * 8 - vScreenY;\n                PositionCursor();\n            }\n        }\n        else if(EditorCursor.Mode == OptCursor_t::WLD_SCENES)\n        {\n            EditorCursor.Location.X = (X / 16) * 16 - vScreenX;\n            EditorCursor.Location.Y = ((Y + 8) / 16) * 16 - vScreenY - 8;\n            PositionCursor();\n        }\n        else if(EditorCursor.Mode == OptCursor_t::LVL_WATER)\n        {\n            EditorCursor.Location.X = (X / 16) * 16 - vScreenX;\n            EditorCursor.Location.Y = ((Y + 8) / 16) * 16 - vScreenY - 8;\n            PositionCursor();\n        }\n        else // Everything also align as 32x32\n        {\n            EditorCursor.Location.X = (X / 32) * 32 - vScreenX;\n            EditorCursor.Location.Y = ((Y + 8) / 32) * 32 - vScreenY - 8;\n            PositionCursor();\n        }\n    }\n//    if(nPlay.Online == true && nCur == true)\n//    {\n//        if(nPlay.Mode == 0)\n//            Netplay::sendData \"f\" + std::to_string(X) - vScreenX(A) + \"|\" + std::to_string(Y) - vScreenY(A); // Netplay\n//        else\n//        {\n//            SetCursor();\n//            Netplay::sendData std::string(\"f\") + \"0|\" + std::to_string(X) - vScreenX(A) + \"|\" + std::to_string(Y) - vScreenY(A); // Netplay\n//        }\n//    }\n\n    // translate from layer coordinates to screen coordinates\n    if(MagicHand && EditorCursor.Layer != LAYER_NONE)\n    {\n        EditorCursor.Location.X -= lX;\n        EditorCursor.Location.Y -= lY;\n    }\n}\n\nvoid ResetNPC(NPCID A)\n{\n    NPC_t blankNPC;\n    NPC[0] = blankNPC;\n    NPC[0].Frame = EditorNPCFrame(A, EditorCursor.NPC.Direction);\n    EditorCursor.NPC.Frame = NPC[0].Frame;\n    EditorCursor.NPC.FrameCount = NPC[0].FrameCount;\n    NPC[0].Frame = 0;\n}\n"
  },
  {
    "path": "src/editor/editor_custom.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n#include <IniProcessor/ini_processing.h>\n#include <Utils/files_ini.h>\n\n#include <fmt_format_ne.h>\n\n#include <sorting/tinysort.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"main/translate.h\"\n\n#include \"editor/editor_custom.h\"\n\n#include \"sound.h\"\n\nnamespace EditorCustom\n{\n\nstatic std::vector<ItemFamily*> s_ordered_block_families;\nstd::vector<ItemFamily> block_families;\nstd::array<uint8_t, maxBlockType> block_family_by_type;\nstd::vector<ItemPage_t> block_pages;\n\n\nstatic std::vector<ItemFamily*> s_ordered_bgo_families;\nstd::vector<ItemFamily> bgo_families;\nstd::array<uint8_t, maxBackgroundType> bgo_family_by_type;\nstd::vector<ItemPage_t> bgo_pages;\n\n\nstatic std::vector<ItemFamily*> s_ordered_npc_families;\nstd::vector<ItemFamily> npc_families;\nstd::array<uint8_t, maxNPCType> npc_family_by_type;\nstd::vector<ItemPage_t> npc_pages;\n\n\nstatic std::vector<ItemFamily*> s_ordered_tile_families;\nstd::vector<ItemFamily> tile_families;\nstd::array<uint8_t, maxTileType> tile_family_by_type;\nstd::vector<ItemPage_t> tile_pages;\n\n\nItemList_t sound_list;\nItemList_t music_list;\nItemList_t wmusic_list;\nItemList_t bg2_list;\n\nstd::vector<std::string> list_level_exit_names(11);\n\nstatic constexpr uint8_t LOADED_ALL = 7;\nuint8_t loaded = 0;\n\n\n// implementation for ItemType_t\nItemType_t::ItemType_t(int type, const char* sides, slope_t slope, int width, int height, int group)\n    : type(type), group(group), temp_flag(false), slope(slope), width(width), height(height)\n{\n    set_adj(sides);\n}\n\nvoid ItemType_t::set_adj(const char* sides)\n{\n    has_1 = false;\n    has_2 = false;\n    has_3 = false;\n    has_4 = false;\n    has_6 = false;\n    has_7 = false;\n    has_8 = false;\n    has_9 = false;\n\n    if(!sides)\n        return;\n\n    for(const char* c = sides; *c != '\\0'; c++)\n    {\n        switch(*c)\n        {\n        case '1':\n            has_1 = true;\n            break;\n        case '2':\n            has_2 = true;\n            break;\n        case '3':\n            has_3 = true;\n            break;\n        case '4':\n            has_4 = true;\n            break;\n        case '6':\n            has_6 = true;\n            break;\n        case '7':\n            has_7 = true;\n            break;\n        case '8':\n            has_8 = true;\n            break;\n        case '9':\n            has_9 = true;\n            break;\n        default:\n            break;\n        }\n    }\n}\n\n\n// implementation for LayoutPod_t\nLayoutPod_t::LayoutPod_t()\n{\n    x = 0;\n    y = 0;\n    rows = 0;\n    cols = 0;\n}\n\nLayoutPod_t::LayoutPod_t(int rows, int cols)\n{\n    x = 0;\n    y = 0;\n    resize(rows, cols);\n}\n\nvoid LayoutPod_t::resize(int _rows, int _cols)\n{\n    types.clear();\n    types.resize(_rows * _cols);\n    rows = _rows;\n    cols = _cols;\n}\n\nvoid LayoutPod_t::compact()\n{\n    // the cheap one first\n    for(int row = rows - 1; row >= 0; row--)\n    {\n        bool empty = true;\n        for(int col = 0; col < cols; col++)\n        {\n            if(types[row * cols + col])\n            {\n                empty = false;\n                break;\n            }\n        }\n\n        if(empty)\n        {\n            types.erase(types.begin() + row * cols, types.begin() + (row + 1) * cols);\n            rows--;\n        }\n    }\n\n    // the expensive one second\n    for(int col = cols - 1; col >= 0; col--)\n    {\n        bool empty = true;\n        for(int row = 0; row < rows; row++)\n        {\n            if(types[row * cols + col])\n            {\n                empty = false;\n                break;\n            }\n        }\n\n        if(empty)\n        {\n            for(int row = 0; row < rows; row++)\n            {\n                for(int col2 = 0; col2 < cols; col2++)\n                {\n                    // before col-erase, shift everything back [row] elements\n                    if(col2 < col)\n                        types[row * (cols - 1) + col2] = types[row * cols + col2];\n                    // after, shift everything back [row + 1] elements\n                    else if(col2 > col)\n                        types[row * (cols - 1) + col2 - 1] = types[row * cols + col2];\n                }\n            }\n\n            cols--;\n            types.resize(rows * cols);\n        }\n    }\n}\n\n// implementation for ItemFamily\nvoid ItemFamily::make_layout_pods()\n{\n    if(is_misc && req_layout_pods.empty())\n        return;\n\n    temp_layout_pods.clear();\n\n    for(const LayoutPod_t& pod : req_layout_pods)\n    {\n        for(int type : pod.types)\n        {\n            for(ItemType_t& t2 : types)\n            {\n                if(t2.type == type)\n                    t2.temp_flag = true;\n            }\n        }\n\n        temp_layout_pods.push_back(pod);\n    }\n\n    LayoutPod_t current_pod_external;\n    LayoutPod_t current_pod_internal;\n    LayoutPod_t current_pod_hstrip;\n    LayoutPod_t current_pod_vstrip;\n\n    LayoutPod_t trash_pod;\n\n    bool types_left = true;\n\n    // default-initializes to the standard (no group, no slope, width/height 1)\n    ItemType_t current_exemplar;\n\n    while(types_left)\n    {\n        types_left = false;\n\n        if(current_exemplar.slope == ItemType_t::no_slope)\n            current_pod_external.resize(3, 3);\n        else\n            current_pod_external.resize(2, 2);\n\n        current_pod_internal.resize(2, 2);\n        current_pod_hstrip.resize(1, 3);\n        current_pod_vstrip.resize(3, 1);\n\n        for(ItemType_t& t : types)\n        {\n            if(t.group != current_exemplar.group || t.slope != current_exemplar.slope\n                || t.width != current_exemplar.width || t.height != current_exemplar.height)\n            {\n                continue;\n            }\n\n            if(t.temp_flag)\n                continue;\n\n            t.temp_flag = true;\n            for(ItemType_t& t2 : types)\n            {\n                if(t2.type == t.type)\n                    t2.temp_flag = true;\n            }\n\n            int* pos = nullptr;\n\n            // 4 tiles in the internal pod\n            // bottomright\n            if(t.has_1 && t.has_2 && !t.has_3 && t.has_4 && t.has_6 && t.has_7 && t.has_8 && t.has_9)\n                pos = &current_pod_internal.types[3];\n            // bottomleft\n            else if(!t.has_1 && t.has_2 && t.has_3 && t.has_4 && t.has_6 && t.has_7 && t.has_8 && t.has_9)\n                pos = &current_pod_internal.types[2];\n            // topright\n            else if(t.has_1 && t.has_2 && t.has_3 && t.has_4 && t.has_6 && t.has_7 && t.has_8 && !t.has_9)\n                pos = &current_pod_internal.types[1];\n            // topleft\n            else if(t.has_1 && t.has_2 && t.has_3 && t.has_4 && t.has_6 && !t.has_7 && t.has_8 && t.has_9)\n                pos = &current_pod_internal.types[0];\n\n            if(!pos && current_exemplar.slope == ItemType_t::no_slope)\n            {\n                // 9 tiles in the external pod\n\n                // topleft\n                if(!t.has_1 && t.has_2 && t.has_3 && !t.has_4 && t.has_6 && !t.has_7 && !t.has_8 && !t.has_9)\n                    pos = &current_pod_external.types[0];\n                // top\n                else if(t.has_1 && t.has_2 && t.has_3 && t.has_4 && t.has_6 && !t.has_7 && !t.has_8 && !t.has_9)\n                    pos = &current_pod_external.types[1];\n                // topright\n                else if(t.has_1 && t.has_2 && !t.has_3 && t.has_4 && !t.has_6 && !t.has_7 && !t.has_8 && !t.has_9)\n                    pos = &current_pod_external.types[2];\n                // midleft\n                else if(!t.has_1 && t.has_2 && t.has_3 && !t.has_4 && t.has_6 && !t.has_7 && t.has_8 && t.has_9)\n                    pos = &current_pod_external.types[3];\n                // mid\n                else if(t.has_1 && t.has_2 && t.has_3 && t.has_4 && t.has_6 && t.has_7 && t.has_8 && t.has_9)\n                    pos = &current_pod_external.types[4];\n                // midright\n                else if(t.has_1 && t.has_2 && !t.has_3 && t.has_4 && !t.has_6 && t.has_7 && t.has_8 && !t.has_9)\n                    pos = &current_pod_external.types[5];\n                // bottomleft\n                else if(!t.has_1 && !t.has_2 && !t.has_3 && !t.has_4 && t.has_6 && !t.has_7 && t.has_8 && t.has_9)\n                    pos = &current_pod_external.types[6];\n                // bottom\n                else if(!t.has_1 && !t.has_2 && !t.has_3 && t.has_4 && t.has_6 && t.has_7 && t.has_8 && t.has_9)\n                    pos = &current_pod_external.types[7];\n                // bottomright\n                else if(!t.has_1 && !t.has_2 && !t.has_3 && t.has_4 && !t.has_6 && t.has_7 && t.has_8 && !t.has_9)\n                    pos = &current_pod_external.types[8];\n\n                // 3 tiles in the horizontal strip pod\n                // left\n                else if(!t.has_1 && !t.has_2 && !t.has_3 && !t.has_4 && t.has_6 && !t.has_7 && !t.has_8 && !t.has_9)\n                    pos = &current_pod_hstrip.types[0];\n                // center\n                else if(!t.has_1 && !t.has_2 && !t.has_3 && t.has_4 && t.has_6 && !t.has_7 && !t.has_8 && !t.has_9)\n                    pos = &current_pod_hstrip.types[1];\n                // right\n                else if(!t.has_1 && !t.has_2 && !t.has_3 && t.has_4 && !t.has_6 && !t.has_7 && !t.has_8 && !t.has_9)\n                    pos = &current_pod_hstrip.types[2];\n\n                // 3 tiles in the vertical strip pod\n                // top\n                else if(!t.has_1 && t.has_2 && !t.has_3 && !t.has_4 && !t.has_6 && !t.has_7 && !t.has_8 && !t.has_9)\n                    pos = &current_pod_vstrip.types[0];\n                // center\n                else if(!t.has_1 && t.has_2 && !t.has_3 && !t.has_4 && !t.has_6 && !t.has_7 && t.has_8 && !t.has_9)\n                    pos = &current_pod_vstrip.types[1];\n                // bottom\n                else if(!t.has_1 && !t.has_2 && !t.has_3 && !t.has_4 && !t.has_6 && !t.has_7 && t.has_8 && !t.has_9)\n                    pos = &current_pod_vstrip.types[2];\n            }\n            else if(!pos)\n            {\n                // topleft\n                if(t.has_2 && t.has_3 && !t.has_4 && t.has_6 && !t.has_7 && !t.has_8)\n                    pos = &current_pod_external.types[0];\n                // topright\n                else if(t.has_1 && t.has_2 && t.has_4 && !t.has_6 && !t.has_8 && !t.has_9)\n                    pos = &current_pod_external.types[1];\n                // bottomleft\n                else if(!t.has_1 && !t.has_2 && !t.has_4 && t.has_6 && t.has_8 && t.has_9)\n                    pos = &current_pod_external.types[2];\n                // bottomright\n                else if(!t.has_2 && !t.has_3 && t.has_4 && !t.has_6 && t.has_7 && t.has_8)\n                    pos = &current_pod_external.types[3];\n            }\n\n            if(pos && *pos == 0)\n                *pos = t.type;\n            else\n                trash_pod.types.push_back(t.type);\n        }\n\n        // find a new exemplar (can't do this during, since temp_flag is set for some during)\n        for(ItemType_t& t : types)\n        {\n            if(t.group != current_exemplar.group || t.slope != current_exemplar.slope\n                || t.width != current_exemplar.width || t.height != current_exemplar.height)\n            {\n                if(!t.temp_flag)\n                {\n                    current_exemplar = t;\n                    types_left = true;\n                    break;\n                }\n            }\n        }\n\n        current_pod_external.compact();\n        if(!current_pod_external.types.empty())\n            temp_layout_pods.push_back(current_pod_external);\n\n        current_pod_internal.compact();\n        if(!current_pod_internal.types.empty())\n            temp_layout_pods.push_back(current_pod_internal);\n\n        current_pod_hstrip.compact();\n        if(!current_pod_hstrip.types.empty())\n            temp_layout_pods.push_back(current_pod_hstrip);\n\n        current_pod_vstrip.compact();\n        if(!current_pod_vstrip.types.empty())\n            temp_layout_pods.push_back(current_pod_vstrip);\n    }\n\n    trash_pod.rows = 1;\n    trash_pod.cols = (int)trash_pod.types.size();\n\n    // order the pods by size\n    tinysort(temp_layout_pods.begin(), temp_layout_pods.end(),\n    [](const LayoutPod_t& a, const LayoutPod_t& b)\n    {\n        return a.rows > b.rows || (a.rows == b.rows && a.cols > b.cols);\n    });\n\n    if(!trash_pod.types.empty())\n    {\n        for(int t : trash_pod.types)\n        {\n            temp_layout_pods.push_back(LayoutPod_t(1, 1));\n            temp_layout_pods[temp_layout_pods.size() - 1].types[0] = t;\n        }\n    }\n\n    for(ItemType_t& t : types)\n        t.temp_flag = false;\n}\n\nbool ItemFamily::make_layout(int layout_width)\n{\n    if(temp_layout_pods.empty())\n    {\n        layout_pod.resize((int)types.size() / layout_width + 1, layout_width);\n\n        layout_pod.types.resize(0);\n        for(ItemType_t t : types)\n            layout_pod.types.push_back(t.type);\n        layout_pod.types.resize((types.size() / layout_width + 1) * layout_width);\n\n        layout_pod.compact();\n\n        return true;\n    }\n\n    // make a layout using these pods\n    // place them where they fit\n    layout_pod.resize(3, layout_width);\n\n    for(LayoutPod_t& pod : temp_layout_pods)\n    {\n        int x, y;\n\n        bool okay = false;\n\n        int prows = pod.rows;\n        int pcols = pod.cols;\n\n        // rotate if needed\n        if(layout_width < pcols && prows == 1)\n        {\n            prows = pcols;\n            pcols = 1;\n        }\n\n\n        for(y = 0; y < 10; y++)\n        {\n            for(x = 0; x + pcols <= layout_width; x++)\n            {\n                if(y + prows > layout_pod.rows)\n                {\n                    layout_pod.rows = y + prows;\n                    layout_pod.types.resize(layout_pod.rows * layout_pod.cols);\n                }\n\n                okay = true;\n                for(int y2 = 0; y2 < prows; y2++)\n                {\n                    for(int x2 = 0; x2 < pcols; x2++)\n                    {\n                        if(layout_pod.types[(y + y2) * layout_pod.cols + (x + x2)])\n                        {\n                            okay = false;\n                            break;\n                        }\n                    }\n                    if(!okay)\n                        break;\n                }\n\n                if(okay)\n                    break;\n            }\n            if(okay)\n                break;\n        }\n\n        if(!okay)\n        {\n            layout_pod.resize(types.size() / layout_width + 1, layout_width);\n            return false;\n        }\n\n        for(int y2 = 0; y2 < prows; y2++)\n        {\n            for(int x2 = 0; x2 < pcols; x2++)\n                layout_pod.types[(y + y2) * layout_pod.cols + (x + x2)] = pod.types[y2 * pcols + x2];\n        }\n    }\n\n    layout_pod.compact();\n\n    return true;\n}\n\n\n// implementation for ItemList_t\n\nvoid ItemList_t::make_layout(int rows)\n{\n    // first, remove any existing padding\n    for(int i = (int)indices.size() - 1; i >= 0; i--)\n    {\n        if(indices[i] == -1 && names[i].empty())\n        {\n            indices.erase(indices.begin() + i);\n            names.erase(names.begin() + i);\n        }\n    }\n\n    // now, add any padding needed\n    for(size_t i = 0; i < indices.size(); i++)\n    {\n        if(indices[i] == -1 && !names[i].empty() && (i + 1) % rows == 0)\n        {\n            indices.insert(indices.begin() + i, -1);\n            names.insert(names.begin() + i, std::string());\n        }\n    }\n}\n\nvoid ItemList_t::make_translation(XTechTranslate& translate, const char* group, const char* prefix, const std::vector<std::pair<ListItemFamily_t, std::string>>& families)\n{\n    int header = -1;\n\n    for(size_t i = 0; i < indices.size(); i++)\n    {\n        if(indices[i] != -1)\n        {\n            if(header < 0)\n                XTechTranslate::insert(translate.m_assetsMap, fmt::sprintf_ne(\"editor.%s.auto.%s%d\", group, prefix, (int)indices[i]), &names[i]);\n            else if(header < (int)families.size())\n                XTechTranslate::insert(translate.m_assetsMap, fmt::sprintf_ne(\"editor.%s.%s.%s%d\", group, families[header].second.c_str(), prefix, (int)indices[i]), &names[i]);\n            else\n                XTechTranslate::insert(translate.m_assetsMap, fmt::sprintf_ne(\"editor.%s.fam%d.%s%d\", group, header, prefix, (int)indices[i]), &names[i]);\n        }\n        else if(!names[i].empty())\n        {\n            header++;\n\n            if(header < (int)families.size())\n                XTechTranslate::insert(translate.m_assetsMap, fmt::sprintf_ne(\"editor.%s.%s.header\", group, families[header].second.c_str()), &names[i]);\n            else\n                XTechTranslate::insert(translate.m_assetsMap, fmt::sprintf_ne(\"editor.%s.fam%d.header\", group, header), &names[i]);\n        }\n    }\n}\n\n\n// general-purpose layout functions\n\nstd::vector<ItemFamily*>::iterator make_page(std::vector<ItemFamily*>::iterator begin, std::vector<ItemFamily*>::iterator end,\n    int width, int height)\n{\n    tinysort(begin, end,\n    [](const ItemFamily* a, const ItemFamily* b)\n    {\n        return a->category < b->category\n            || (a->category == b->category &&\n                a->types.size() > b->types.size());\n    });\n\n    int page_category = -1;\n\n    // row - half row, because of titles\n    int start_section_row = 0;\n    int current_row = 0;\n    int main_width = width;\n\n    std::vector<std::array<int, 4>> lost_margins;\n\n    auto unplaced = begin;\n    auto orig_end = end;\n\n    while(unplaced != end)\n    {\n        ItemFamily& family = **unplaced;\n\n        if(page_category == -1)\n            page_category = family.category;\n        else if(family.category != page_category)\n            break;\n\n        bool okay = false;\n\n        if(current_row != start_section_row)\n        {\n            // try to fill a margin, if it exists\n            okay = family.make_layout(width - main_width - 1);\n            int r_height = family.layout_pod.rows * 2 + 1;\n\n            if(okay && r_height <= current_row - start_section_row)\n            {\n                int min_width = family.layout_pod.cols;\n                int min_height = family.layout_pod.rows;\n                while(min_width > 1)\n                {\n                    min_width--;\n                    okay = family.make_layout(min_width);\n                    if(!okay || family.layout_pod.rows > min_height)\n                    {\n                        min_width++;\n                        okay = family.make_layout(min_width);\n                        break;\n                    }\n                }\n                r_height = family.layout_pod.rows * 2 + 1;\n\n                family.X = main_width + 1;\n                family.Y = start_section_row;\n\n                int margin_size_tall = (width - main_width - 1) * (current_row - start_section_row - r_height);\n                int margin_size_wide = (width - main_width - family.layout_pod.cols - 2) * (current_row - start_section_row);\n\n                // pick the bigger continued margin\n                if(margin_size_tall >= margin_size_wide)\n                {\n                    lost_margins.push_back({start_section_row, current_row, main_width + family.layout_pod.cols + 2, width});\n                    start_section_row += r_height;\n                }\n                else\n                {\n                    lost_margins.push_back({start_section_row + r_height, current_row, main_width + 1, main_width + 1 + family.layout_pod.cols});\n                    main_width += family.layout_pod.cols + 1;\n                }\n\n                ++unplaced;\n                continue;\n            }\n\n            // we'll be below the margin\n\n            // pick whether to fill the row (use width) or keep the margin (use main_width)\n            family.make_layout(width);\n            int height_full = family.layout_pod.rows * 2 + 1;\n\n            okay = family.make_layout(main_width);\n            int height_main = family.layout_pod.rows * 2 + 1;\n\n            int margin_size = (width - main_width - 1) * (current_row - start_section_row);\n\n            if(!okay || height_main * main_width > height_full * width + margin_size)\n            {\n                // discard the margin\n                // (added to lost margins later)\n                okay = family.make_layout(width);\n            }\n        }\n        else\n        {\n            okay = family.make_layout(width);\n        }\n\n        // figure out the minimum height and width at which it gets this height\n        int min_height = family.layout_pod.rows;\n\n        // skip if we can't fit it\n        if((!okay || current_row + (min_height * 2 + 1) > height * 2) && current_row != 0)\n        {\n            std::rotate(unplaced, unplaced + 1, end);\n            end--;\n            continue;\n        }\n        // if there isn't enough room for another one below, allow it to be taller\n        else if(current_row + (min_height * 2 + 1) + 4 == height * 2)\n        {\n            min_height += 2;\n        }\n        // if there isn't enough room for another one below, allow it to be taller\n        else if(current_row + (min_height * 2 + 1) + 3 == height * 2)\n        {\n            min_height++;\n        }\n        // if there isn't enough room for another one below, allow it to be taller\n        else if(current_row + (min_height * 2 + 1) + 2 == height * 2)\n        {\n            min_height++;\n        }\n\n        int min_width = family.layout_pod.cols;\n        while(min_width > 1)\n        {\n            min_width--;\n            okay = family.make_layout(min_width);\n            if(!okay || family.layout_pod.rows > min_height)\n            {\n                min_width++;\n                okay = family.make_layout(min_width);\n                break;\n            }\n        }\n\n        min_height = family.layout_pod.rows;\n\n        // convert to half-rows, add title\n        min_height = min_height * 2 + 1;\n\n        family.X = 0;\n        family.Y = current_row;\n\n        // consume the entire rows\n        if(min_width >= width - 1)\n        {\n            if(current_row != start_section_row)\n                lost_margins.push_back({start_section_row, current_row, main_width + 1, width});\n\n            current_row += min_height;\n            start_section_row = current_row;\n            main_width = width;\n        }\n        // start a margin\n        else if(current_row == start_section_row)\n        {\n            current_row += min_height;\n            main_width = min_width;\n        }\n        // decide what happens to the margin\n        else\n        {\n            // pick the bigger continued margin, or just cancel\n            int margin_size_tall = (width - main_width - 1) * (min_height + current_row - start_section_row);\n            int margin_size_wide = (width - min_width - 1) * (min_height);\n\n            // just cancel\n            // if(margin_size_tall < 4 && margin_size_wide < 4)\n            // {\n            //     current_row += min_height;\n            //     start_section_row = current_row;\n            //     main_width = width;\n            // }\n            // make a wide margin, starting on the current row\n            if(margin_size_wide >= margin_size_tall)\n            {\n                lost_margins.push_back({start_section_row, current_row, main_width + 1, width});\n\n                start_section_row = current_row;\n                current_row += min_height;\n                main_width = min_width;\n            }\n            // make a tall margin, keeping start row and width the same\n            else\n            {\n                if(min_width + 1 != main_width)\n                    lost_margins.push_back({current_row, current_row + min_height, min_width + 1, main_width});\n\n                current_row += min_height;\n            }\n        }\n\n        ++unplaced;\n    }\n\n    // try to fill lost margins if possible\n    if(current_row != start_section_row)\n    {\n        lost_margins.push_back({start_section_row, height * 2, main_width + 1, width});\n    }\n\n    // grow lost margins to screen height\n    for(auto& i: lost_margins)\n    {\n        if(i[1] == current_row)\n            i[1] = height * 2;\n    }\n\n    // grow any combined margins\n    tinysort(lost_margins.begin(), lost_margins.end(),\n    [](const std::array<int, 4>& a, const std::array<int, 4>& b)\n    {\n        return a[2] < b[2];\n    });\n\n    for(auto& i : lost_margins)\n    {\n        for(auto& j : lost_margins)\n        {\n            if((i[3] == j[2] || i[3] + 1 == j[2]) && i[0] == j[0] && i[1] == j[1])\n            {\n                i[3] = j[3];\n                j[2] = j[3] + 1;\n            }\n        }\n    }\n\n    tinysort(lost_margins.begin(), lost_margins.end(),\n    [](const std::array<int, 4>& a, const std::array<int, 4>& b)\n    {\n        return a[0] < b[0];\n    });\n\n    for(auto& i : lost_margins)\n    {\n        for(auto& j : lost_margins)\n        {\n            if(i[1] == j[0] && i[2] == j[2] && i[3] == j[3])\n            {\n                i[1] = j[1];\n                j[0] = j[1];\n            }\n        }\n    }\n\n    // remove bad lost margins\n    for(size_t x = 0; x < lost_margins.size(); x++)\n    {\n        const auto& i = lost_margins[x];\n        if((i[1] - i[0]) < 3 || (i[3] - i[2]) < 1)\n        {\n            lost_margins[x] = lost_margins[lost_margins.size() - 1];\n            lost_margins.resize(lost_margins.size() - 1);\n        }\n    }\n\n    // do it!\n    end = orig_end;\n\n    tinysort(unplaced, end,\n    [](const ItemFamily* a, const ItemFamily* b)\n    {\n        return a->category < b->category\n            || (a->category == b->category &&\n                (a->is_misc < b->is_misc\n                    || (a->is_misc == b->is_misc && a->types.size() > b->types.size())));\n    });\n\n    while(unplaced != end)\n    {\n        ItemFamily& family = **unplaced;\n\n        if(family.category != page_category)\n            break;\n\n        bool placed = false;\n\n        tinysort(lost_margins.begin(), lost_margins.end(),\n        [](const std::array<int, 4>& a, const std::array<int, 4>& b)\n        {\n            return ((a[1] - a[0]) * (a[3] - a[2])) < ((b[1] - b[0]) * (b[3] - b[2]));\n        });\n\n        for(size_t x = 0; x < lost_margins.size(); x++)\n        {\n            auto i = lost_margins[x];\n            if((i[1] - i[0]) * (i[3] - i[2]) < (int)family.types.size() * 2)\n                continue;\n\n            bool okay = family.make_layout(i[3] - i[2]);\n\n            if(!okay || family.layout_pod.rows * 2 + 1 > i[1] - i[0])\n                continue;\n\n            int min_height = family.layout_pod.rows;\n            int min_width = family.layout_pod.cols;\n            while(min_width > 1)\n            {\n                min_width--;\n                okay = family.make_layout(min_width);\n                if(!okay || family.layout_pod.rows > min_height)\n                {\n                    min_width++;\n                    okay = family.make_layout(min_width);\n                    break;\n                }\n            }\n\n            family.X = i[2];\n            family.Y = i[0];\n\n            if(family.layout_pod.cols + 1 < i[3] - i[2])\n                lost_margins.push_back({i[0], i[1], i[2] + family.layout_pod.cols + 1, i[3]});\n\n            if(family.layout_pod.rows * 2 + 1 < i[1] - i[0])\n                lost_margins.push_back({i[0] + family.layout_pod.rows * 2 + 1, i[1], i[2], i[3]});\n\n            lost_margins[x] = lost_margins[lost_margins.size() - 1];\n            lost_margins.resize(lost_margins.size() - 1);\n\n            placed = true;\n            break;\n        }\n\n        if(!placed)\n        {\n            std::rotate(unplaced, unplaced + 1, end);\n            end--;\n            continue;\n        }\n\n        ++unplaced;\n    }\n\n    return unplaced;\n}\n\nvoid make_pages(std::vector<ItemFamily*>& families, std::vector<ItemPage_t>& pages, int width, int height)\n{\n    pages.clear();\n\n    auto unplaced = families.begin();\n\n    while(unplaced != families.end())\n    {\n        auto unplaced_new = make_page(unplaced, families.end(), width, height);\n\n        if(unplaced_new == unplaced)\n        {\n            pLogCritical(\"Block page layout algorithm failed.\");\n            break;\n        }\n\n        pages.emplace_back();\n        ItemPage_t& new_page = pages[pages.size() - 1];\n        new_page.begin = unplaced;\n        new_page.end = unplaced_new;\n        new_page.category = (**unplaced).category;\n        new_page.icon = 0;\n\n        // find icon if one of the families has a custom icon\n        for(auto f = new_page.begin; f != new_page.end; f++)\n        {\n            if((**f).icon)\n            {\n                new_page.icon = (**f).icon;\n                break;\n            }\n        }\n\n        for(auto f = new_page.begin; f != new_page.end; ++f)\n        {\n            // find icon if none of the families has a custom icon\n            if(!new_page.icon && !(**f).types.empty())\n                new_page.icon = (**f).types[0].type;\n\n            // add reference to page\n            (**f).page = (int8_t)pages.size();\n        }\n\n        // don't generate fully empty page\n        if(!new_page.icon)\n            pages.pop_back();\n\n        unplaced = unplaced_new;\n    }\n}\n\n\nvoid Load(XTechTranslate* translate)\n{\n    block_families.clear();\n    bgo_families.clear();\n    npc_families.clear();\n    tile_families.clear();\n\n    sound_list.clear();\n    music_list.clear();\n    wmusic_list.clear();\n    bg2_list.clear();\n\n    loaded = 0;\n\n\n    pLogDebug(\"Loading editor.ini...\");\n\n    IniProcessing editor = Files::load_ini(AppPath + \"editor.ini\");\n\n    std::vector<int> temp_ints;\n    std::vector<int> temp_layout_pod_indices;\n    std::string temp_str;\n\n    // will be added to the global list and then destroyed\n    std::vector<std::pair<ListItemFamily_t, std::string>> sound_families;\n    std::vector<std::pair<ListItemFamily_t, std::string>> music_families;\n    std::vector<std::pair<ListItemFamily_t, std::string>> wmusic_families;\n    std::vector<std::pair<ListItemFamily_t, std::string>> bg2_families;\n\n    // needed to form the translations correctly\n    std::vector<std::string> block_family_keys;\n    std::vector<std::string> bgo_family_keys;\n    std::vector<std::string> npc_family_keys;\n    std::vector<std::string> tile_family_keys;\n\n    for(const std::string& group : editor.childGroups())\n    {\n        ItemFamily* f_ptr = nullptr;\n        ListItemFamily_t* l_ptr = nullptr;\n        const char* prefix = \"\";\n        int prefix_length = 3;\n        int max_item_type = 0;\n        bool has_slope = false;\n\n        if(SDL_strncasecmp(group.c_str(), \"blkfam\", 6) == 0)\n        {\n            block_families.emplace_back();\n            f_ptr = &block_families[block_families.size() - 1];\n            prefix = \"blk\";\n            max_item_type = maxBlockType;\n            has_slope = true;\n            block_family_keys.push_back(group.c_str() + prefix_length);\n        }\n        else if(SDL_strncasecmp(group.c_str(), \"bgofam\", 6) == 0)\n        {\n            bgo_families.emplace_back();\n            f_ptr = &bgo_families[bgo_families.size() - 1];\n            prefix = \"bgo\";\n            max_item_type = maxBackgroundType;\n            bgo_family_keys.push_back(group.c_str() + prefix_length);\n        }\n        else if(SDL_strncasecmp(group.c_str(), \"npcfam\", 6) == 0)\n        {\n            npc_families.emplace_back();\n            f_ptr = &npc_families[npc_families.size() - 1];\n            prefix = \"npc\";\n            max_item_type = maxNPCType;\n            npc_family_keys.push_back(group.c_str() + prefix_length);\n        }\n        else if(SDL_strncasecmp(group.c_str(), \"tilefam\", 7) == 0)\n        {\n            tile_families.emplace_back();\n            f_ptr = &tile_families[tile_families.size() - 1];\n            prefix = \"tile\";\n            prefix_length = 4;\n            max_item_type = maxTileType;\n            tile_family_keys.push_back(group.c_str() + prefix_length);\n        }\n        else if(SDL_strncasecmp(group.c_str(), \"sndfam\", 6) == 0)\n        {\n            sound_families.emplace_back();\n            l_ptr = &sound_families[sound_families.size() - 1].first;\n            prefix = \"snd\";\n            sound_families[sound_families.size() - 1].second = group.c_str() + prefix_length;\n        }\n        else if(SDL_strncasecmp(group.c_str(), \"musfam\", 6) == 0)\n        {\n            music_families.emplace_back();\n            l_ptr = &music_families[music_families.size() - 1].first;\n            prefix = \"mus\";\n            music_families[music_families.size() - 1].second = group.c_str() + prefix_length;\n        }\n        else if(SDL_strncasecmp(group.c_str(), \"wmusfam\", 7) == 0)\n        {\n            wmusic_families.emplace_back();\n            l_ptr = &wmusic_families[wmusic_families.size() - 1].first;\n            prefix = \"wmus\";\n            prefix_length = 4;\n            wmusic_families[wmusic_families.size() - 1].second = group.c_str() + prefix_length;\n        }\n        else if(SDL_strncasecmp(group.c_str(), \"bg2fam\", 6) == 0)\n        {\n            bg2_families.emplace_back();\n            l_ptr = &bg2_families[bg2_families.size() - 1].first;\n            prefix = \"bg2\";\n            bg2_families[bg2_families.size() - 1].second = group.c_str() + prefix_length;\n        }\n        else if(SDL_strcasecmp(group.c_str(), \"exit-codes\") == 0)\n        {\n            // handled above to ensure we get failsafes\n        }\n        else\n        {\n            pLogWarning(\"Ignoring invalid group %s.\", group.c_str());\n        }\n\n        // handling for Lists\n        if(l_ptr)\n        {\n            editor.beginGroup(group);\n\n            ListItemFamily_t& f = *l_ptr;\n\n            editor.read(\"name\", f.name, \"...\");\n            editor.read(\"sort-index\", f.sort_index, 0);\n\n            temp_ints.clear();\n            editor.read(\"members\", temp_ints, temp_ints);\n            for(int i : temp_ints)\n            {\n                if(i > 0 && i <= 127)\n                {\n                    f.indices.push_back(i);\n                    f.names.push_back(fmt::sprintf_ne(\"%s %d\", prefix, i));\n                }\n            }\n\n            for(const std::string& key : editor.allKeys())\n            {\n                if(SDL_strncasecmp(key.c_str(), \"name\", 4) == 0)\n                    continue;\n\n                if(SDL_strncasecmp(key.c_str(), \"sort-index\", 10) == 0)\n                    continue;\n\n                if(SDL_strncasecmp(key.c_str(), \"members\", 7) == 0)\n                    continue;\n\n                if(SDL_strncasecmp(key.c_str(), prefix, prefix_length) != 0)\n                {\n                    pLogWarning(\"Ignoring invalid key %s in family %s\", key.c_str(), group.c_str());\n                    continue;\n                }\n\n                const char* startptr = key.c_str() + prefix_length;\n\n                const char* endptr;\n\n                int i = strtol(startptr, const_cast<char**>(&endptr), 0);\n                if(endptr == startptr)\n                {\n                    pLogWarning(\"Ignoring invalid key %s in family %s\", key.c_str(), group.c_str());\n                    continue;\n                }\n\n                if(i < 0)\n                    i = -i;\n\n                if(i < 1 || i > 127)\n                {\n                    pLogWarning(\"Ignoring invalid key %s in family %s\", key.c_str(), group.c_str());\n                    continue;\n                }\n\n                if(*endptr == '\\0')\n                {\n                    pLogWarning(\"Ignoring invalid key %s in family %s\", key.c_str(), group.c_str());\n                    continue;\n                }\n                if(*endptr == '-')\n                    endptr++;\n\n                if(SDL_strncasecmp(endptr, \"name\", 4) != 0)\n                {\n                    pLogWarning(\"Ignoring invalid key %s in family %s\", key.c_str(), group.c_str());\n                    continue;\n                }\n\n                // find the item\n                std::string* str = nullptr;\n                for(size_t ind = 0; ind < f.indices.size(); ind++)\n                {\n                    if(f.indices[ind] == i)\n                    {\n                        str = &f.names[ind];\n                        break;\n                    }\n                }\n\n                if(!str)\n                {\n                    f.indices.push_back(i);\n                    f.names.push_back(fmt::sprintf_ne(\"%s %d\", prefix, i));\n                    str = &f.names[f.names.size() - 1];\n                }\n\n                editor.read(key.c_str(), *str, *str);\n            }\n        }\n\n        // handling for ordinary Families\n        if(f_ptr)\n        {\n            temp_layout_pod_indices.clear();\n\n            editor.beginGroup(group);\n\n            ItemFamily& f = *f_ptr;\n\n            editor.read(\"name\", f.name, \"\");\n\n            int temp_int;\n            editor.read(\"category\", temp_int, 0);\n            if(temp_int > -128 && temp_int < 128)\n            {\n                f.category = temp_int;\n            }\n            else\n            {\n                f.category = 0;\n                pLogWarning(\"Ignoring invalid category index %d for family %s (max 127)\", temp_int, f.name.c_str());\n            }\n\n            editor.read(\"icon\", temp_int, 0);\n            if(temp_int >= 0 && temp_int <= max_item_type)\n            {\n                f.icon = temp_int;\n            }\n            else\n            {\n                f.icon = 0;\n                pLogWarning(\"Ignoring invalid icon %d for family %s (max %d)\", temp_int, f.name.c_str(), max_item_type);\n            }\n\n            f.is_misc = true;\n\n            temp_ints.clear();\n            editor.read(\"members\", temp_ints, temp_ints);\n            for(int i : temp_ints)\n            {\n                if(i > 0 && i <= max_item_type)\n                    f.types.push_back(ItemType_t(i));\n            }\n\n            // first pass for the blocks, second pass to add alternate group and adj values\n            for(int alt = 0; alt < 2; alt++)\n            {\n                for(const std::string& key : editor.allKeys())\n                {\n                    if(SDL_strncasecmp(key.c_str(), \"name\", 4) == 0)\n                        continue;\n\n                    if(SDL_strncasecmp(key.c_str(), \"cate\", 4) == 0)\n                        continue;\n\n                    if(SDL_strncasecmp(key.c_str(), \"memb\", 4) == 0)\n                        continue;\n\n                    if(SDL_strncasecmp(key.c_str(), \"misc\", 4) == 0)\n                        continue;\n\n                    if(SDL_strncasecmp(key.c_str(), \"icon\", 4) == 0)\n                        continue;\n\n                    if(SDL_strncasecmp(key.c_str(), \"behi\", 4) == 0)\n                        continue;\n\n                    bool pod_mode = false;\n\n                    if(SDL_strncasecmp(key.c_str(), \"pod\", 3) == 0)\n                    {\n                        if(alt)\n                            continue;\n                        else\n                            pod_mode = true;\n                    }\n                    else if(SDL_strncasecmp(key.c_str(), prefix, prefix_length) != 0)\n                    {\n                        pLogWarning(\"Ignoring invalid key %s in family %s\", key.c_str(), group.c_str());\n                        continue;\n                    }\n\n                    const char* startptr = key.c_str() + prefix_length;\n                    if(pod_mode)\n                        startptr = key.c_str() + 3;\n\n                    const char* endptr;\n\n                    int i = strtol(startptr, const_cast<char**>(&endptr), 0);\n                    if(endptr == startptr)\n                    {\n                        pLogWarning(\"Ignoring invalid key %s in family %s\", key.c_str(), group.c_str());\n                        continue;\n                    }\n\n                    if(i < 0)\n                        i = -i;\n\n                    if(!pod_mode && (i < 1 || i > max_item_type))\n                        continue;\n\n                    if(*endptr == '\\0')\n                    {\n                        pLogWarning(\"Ignoring invalid key %s in family %s\", key.c_str(), group.c_str());\n                        continue;\n                    }\n                    if(*endptr == '-')\n                        endptr++;\n\n                    // handle pods\n                    if(pod_mode)\n                    {\n                        size_t ind;\n                        for(ind = 0; ind < temp_layout_pod_indices.size(); ind++)\n                        {\n                            if(temp_layout_pod_indices[ind] == i)\n                                break;\n                        }\n\n                        if(ind == temp_layout_pod_indices.size())\n                        {\n                            temp_layout_pod_indices.push_back(i);\n                            f.req_layout_pods.emplace_back();\n                        }\n\n                        LayoutPod_t& pod = f.req_layout_pods[ind];\n\n                        if(SDL_strncasecmp(endptr, \"mem\", 3) == 0)\n                        {\n                            pod.types.clear();\n                            editor.read(key.c_str(), pod.types, pod.types);\n                            for(int j : pod.types)\n                            {\n                                // add to family types if needed\n                                if(j > 0 && j <= max_item_type)\n                                {\n                                    bool found = false;\n                                    for(ItemType_t& t_i : f.types)\n                                    {\n                                        if(t_i.type == j)\n                                        {\n                                            found = true;\n                                            break;\n                                        }\n                                    }\n                                    if(!found)\n                                        f.types.push_back(ItemType_t(j));\n                                }\n                            }\n\n                            if(pod.cols <= 0)\n                                pod.cols = 4;\n\n                            pod.rows = (pod.types.size() - 1) / pod.cols + 1;\n                            pod.types.resize(pod.rows * pod.cols);\n                        }\n                        else if(SDL_strncasecmp(endptr, \"col\", 3) == 0 || SDL_strncasecmp(endptr, \"wid\", 3) == 0)\n                        {\n                            int old_cols = pod.cols;\n\n                            editor.read(key.c_str(), pod.cols, pod.cols);\n\n                            if(pod.cols <= 0)\n                                pod.cols = old_cols;\n\n                            if(!pod.types.empty() && pod.cols > 0)\n                            {\n                                pod.rows = pod.types.size() / pod.cols + 1;\n                                pod.types.resize(pod.rows * pod.cols);\n                            }\n                        }\n                        else\n                        {\n                            pLogWarning(\"Ignoring group %s key %s\", group.c_str(), key.c_str());\n                            continue;\n                        }\n\n                        continue;\n                    }\n\n                    // deal with these in the second pass\n                    if(SDL_strncasecmp(endptr, \"alt\", 3) == 0)\n                    {\n                        if(alt)\n                        {\n                            endptr += 3;\n\n                            if(*endptr == '\\0')\n                            {\n                                pLogWarning(\"Ignoring invalid key %s\", key.c_str());\n                                continue;\n                            }\n                            if(*endptr == '-')\n                                endptr++;\n                        }\n                        else\n                            continue;\n                    }\n                    else if(alt)\n                    {\n                        continue;\n                    }\n\n                    // find the item\n                    ItemType_t* t = nullptr;\n                    for(ItemType_t& t_i : f.types)\n                    {\n                        if(t_i.type == i)\n                        {\n                            t = &t_i;\n                            break;\n                        }\n                    }\n\n                    if(alt)\n                    {\n                        if(!t)\n                        {\n                            pLogWarning(\"Ignoring alt key %s for non-existent item %d\", key.c_str(), i);\n                            continue;\n                        }\n                        else\n                        {\n                            // add a copy to the end of the array\n                            f.types.push_back(*t);\n                            t = &f.types[f.types.size() - 1];\n                        }\n                    }\n                    else if(!t)\n                    {\n                        f.types.push_back(ItemType_t(i));\n                        t = &f.types[f.types.size() - 1];\n                    }\n\n                    if(SDL_strncasecmp(endptr, \"adj\", 3) == 0)\n                    {\n                        f.is_misc = false;\n\n                        editor.read(key.c_str(), temp_str, \"\");\n                        t->set_adj(temp_str.c_str());\n                    }\n                    else if(SDL_strncasecmp(endptr, \"gr\", 2) == 0)\n                    {\n                        int temp;\n                        editor.read(key.c_str(), temp, 1);\n                        if(temp < 0 || temp >= 16)\n                            pLogWarning(\"Ignoring invalid group %d for item %d (max 15)\", temp, i);\n                        else\n                            t->group = temp;\n                    }\n                    else if(has_slope && SDL_strncasecmp(endptr, \"sl\", 2) == 0)\n                    {\n                        int temp;\n                        editor.read(key.c_str(), temp, 0);\n\n                        switch(temp)\n                        {\n                        case 0:\n                            t->slope = ItemType_t::no_slope;\n                            break;\n                        case 1:\n                            t->slope = ItemType_t::slope_1;\n                            break;\n                        case 2:\n                            t->slope = ItemType_t::slope_2;\n                            break;\n                        case 4:\n                            t->slope = ItemType_t::slope_4;\n                            break;\n                        default:\n                            pLogWarning(\"Ignoring invalid slope %d for item %d\", temp, i);\n                        }\n                    }\n                    else if(alt)\n                    {\n                        pLogWarning(\"Ignoring invalid alt key %s\", key.c_str());\n                        continue;\n                    }\n                    else if(SDL_strncasecmp(endptr, \"w\", 1) == 0)\n                    {\n                        int temp;\n                        editor.read(key.c_str(), temp, 1);\n                        if(temp < 0 || temp >= 8)\n                            pLogWarning(\"Ignoring invalid width %d for item %d (max 7)\", temp, i);\n                        else\n                            t->width = temp;\n                    }\n                    else if(SDL_strncasecmp(endptr, \"h\", 1) == 0)\n                    {\n                        int temp;\n                        editor.read(key.c_str(), temp, 1);\n                        if(temp < 0 || temp >= 8)\n                            pLogWarning(\"Ignoring invalid height %d for item %d (max 7)\", temp, i);\n                        else\n                            t->height = temp;\n                    }\n                    else\n                    {\n                        pLogWarning(\"Ignoring invalid key %s for item %d in family %s\", key.c_str(), i, group.c_str());\n                    }\n                }\n            }\n\n#if 0\n            for(ItemType_t& t : f.types)\n            {\n                printf(\"Itemtype %d (gp %d, slope %d, wh %d%d)\", t.type, t.group, (int)t.slope, t.width, t.height);\n                printf(\" adj \");\n                if(t.has_1) printf(\"1\");\n                if(t.has_2) printf(\"2\");\n                if(t.has_3) printf(\"3\");\n                if(t.has_4) printf(\"4\");\n                if(t.has_6) printf(\"6\");\n                if(t.has_7) printf(\"7\");\n                if(t.has_8) printf(\"8\");\n                if(t.has_9) printf(\"9\");\n                printf(\"\\n\");\n            }\n#endif\n\n            editor.read(\"misc\", f.is_misc, f.is_misc);\n            editor.read(\"behind\", f.behind_mode, false);\n            f.make_layout_pods();\n        }\n    }\n\n    // Process exit codes\n    editor.beginGroup(\"exit-codes\");\n\n    editor.read(\"any\", list_level_exit_names[0], \"Any\");\n    editor.read(\"none\", list_level_exit_names[1], \"None\");\n\n    XTechTranslate::insert(translate->m_assetsMap, fmt::sprintf_ne(\"editor.exit-codes.%s\", \"any\"), &(list_level_exit_names[0]));\n    XTechTranslate::insert(translate->m_assetsMap, fmt::sprintf_ne(\"editor.exit-codes.%s\", \"none\"), &(list_level_exit_names[1]));\n\n    for(size_t i = 1; i + 1 < list_level_exit_names.size(); i++)\n    {\n        const std::string s = fmt::sprintf_ne(\"code%zu\", i);\n        editor.read(s.c_str(), list_level_exit_names[i + 1], s);\n        XTechTranslate::insert(translate->m_assetsMap, fmt::sprintf_ne(\"editor.exit-codes.%zu\", i), &(list_level_exit_names[i + 1]));\n    }\n\n    editor.endGroup();\n\n\n    // process Blocks\n    if(!block_families.empty())\n        loaded++;\n\n    s_ordered_block_families.clear();\n    for(ItemFamily& family : block_families)\n    {\n        s_ordered_block_families.push_back(&family);\n    }\n\n    make_pages(s_ordered_block_families, block_pages, 11, 11);\n\n    for(int i = 0; i < maxBlockType; i++)\n    {\n        block_family_by_type[i] = FAMILY_NONE;\n    }\n\n    SDL_assert_release(block_families.size() < 255); // Max of uint8_t\n\n    for(uint8_t family = (uint8_t)block_families.size() - 1; family != FAMILY_NONE; family--)\n    {\n        for(ItemType_t t : block_families[family].types)\n            block_family_by_type[t.type - 1] = family;\n    }\n\n    bool blocks_begun = false;\n    for(int i = maxBlockType - 1; i >= 0; i--)\n    {\n        if(block_family_by_type[i] != FAMILY_NONE)\n            blocks_begun = true;\n        else if(blocks_begun)\n            pLogWarning(\"Can't find family for block type %d\", i + 1);\n    }\n\n    // add Blocks to translations\n    if(translate)\n    {\n        for(uint8_t i = 0; i < block_families.size(); i++)\n            XTechTranslate::insert(translate->m_assetsMap, fmt::sprintf_ne(\"editor.block.%s\", block_family_keys[i].c_str()), &(block_families[i].name));\n    }\n\n\n    // process BGOs\n    if(!bgo_families.empty())\n        loaded++;\n\n    s_ordered_bgo_families.clear();\n    for(ItemFamily& family : bgo_families)\n    {\n        s_ordered_bgo_families.push_back(&family);\n    }\n\n    make_pages(s_ordered_bgo_families, bgo_pages, 11, 11);\n\n    for(int i = 0; i < maxBackgroundType; i++)\n    {\n        bgo_family_by_type[i] = FAMILY_NONE;\n    }\n\n    SDL_assert_release(bgo_families.size() < 255); // Max of uint8_t\n\n    for(uint8_t family = (uint8_t)bgo_families.size() - 1; family != FAMILY_NONE; family--)\n    {\n        for(ItemType_t t : bgo_families[family].types)\n            bgo_family_by_type[t.type - 1] = family;\n    }\n\n    bool bgos_begun = false;\n    for(int i = maxBackgroundType - 1; i >= 0; i--)\n    {\n        if(bgo_family_by_type[i] != FAMILY_NONE)\n            bgos_begun = true;\n        // don't worry about locked types\n        else if(bgos_begun && i + 1 != 160 && i + 1 != 98)\n            pLogWarning(\"Can't find family for BGO type %d\", i + 1);\n    }\n\n    // add BGOs to translations\n    if(translate)\n    {\n        for(uint8_t i = 0; i < bgo_families.size(); i++)\n            XTechTranslate::insert(translate->m_assetsMap, fmt::sprintf_ne(\"editor.bgo.%s\", bgo_family_keys[i].c_str()), &(bgo_families[i].name));\n    }\n\n\n    // process NPCs\n    if(!npc_families.empty())\n        loaded++;\n\n    s_ordered_npc_families.clear();\n    for(ItemFamily& family : npc_families)\n    {\n        s_ordered_npc_families.push_back(&family);\n    }\n\n    make_pages(s_ordered_npc_families, npc_pages, 10, 11);\n\n    // Not an off-by-one error, this is zero-indexed\n    for(int i = 0; i < maxNPCType; i++)\n    {\n        npc_family_by_type[i] = FAMILY_NONE;\n    }\n\n    SDL_assert_release(npc_families.size() < 255); // Max of uint8_t\n\n    for(uint8_t family = (uint8_t)npc_families.size() - 1; family != FAMILY_NONE; family--)\n    {\n        for(ItemType_t t : npc_families[family].types)\n            npc_family_by_type[t.type - 1] = family;\n    }\n\n#if 0\n    // no warnings for NPCs because many are not included\n\n    bool npcs_begun = false;\n    for(int i = maxNPCType - 1; i >= 0; i--)\n    {\n        if(npc_family_by_type[i] != FAMILY_NONE)\n            npcs_begun = true;\n        else if(npcs_begun)\n            pLogWarning(\"Can't find family for NPC type %d\", i + 1);\n    }\n#endif\n\n    // add NPCs to translations\n    if(translate)\n    {\n        for(uint8_t i = 0; i < npc_families.size(); i++)\n            XTechTranslate::insert(translate->m_assetsMap, fmt::sprintf_ne(\"editor.npc.%s\", npc_family_keys[i].c_str()), &(npc_families[i].name));\n    }\n\n\n    // process tiles\n    if(!tile_families.empty())\n        loaded++;\n\n    s_ordered_tile_families.clear();\n    for(ItemFamily& family : tile_families)\n    {\n        s_ordered_tile_families.push_back(&family);\n    }\n\n    make_pages(s_ordered_tile_families, tile_pages, 15, 11);\n\n    for(int i = 0; i < maxTileType; i++)\n    {\n        tile_family_by_type[i] = FAMILY_NONE;\n    }\n\n    SDL_assert_release(tile_families.size() < 255); // Max of uint8_t\n\n    for(uint8_t family = (uint8_t)tile_families.size() - 1; family != FAMILY_NONE; family--)\n    {\n        for(ItemType_t t : tile_families[family].types)\n            tile_family_by_type[t.type - 1] = family;\n    }\n\n    bool tiles_begun = false;\n    for(int i = maxTileType - 1; i >= 0; i--)\n    {\n        if(tile_family_by_type[i] != FAMILY_NONE)\n            tiles_begun = true;\n        else if(tiles_begun)\n            pLogWarning(\"Can't find family for tile type %d\", i + 1);\n    }\n\n    // add tiles to translations\n    if(translate)\n    {\n        for(uint8_t i = 0; i < tile_families.size(); i++)\n            XTechTranslate::insert(translate->m_assetsMap, fmt::sprintf_ne(\"editor.tile.%s\", tile_family_keys[i].c_str()), &(tile_families[i].name));\n    }\n\n\n    // process bg2s\n    if(!bg2_families.empty())\n    {\n        loaded++;\n\n        tinysort(bg2_families.begin(), bg2_families.end(),\n        [](const std::pair<ListItemFamily_t, std::string>& a, const std::pair<ListItemFamily_t, std::string>& b)\n        {\n            return a.first.sort_index < b.first.sort_index;\n        });\n\n        size_t length = 1;\n        for(const std::pair<ListItemFamily_t, std::string>& p : bg2_families)\n        {\n            const ListItemFamily_t& f = p.first;\n            length += 1 + f.indices.size();\n        }\n\n        bg2_list.indices.reserve(length);\n        bg2_list.names.reserve(length);\n\n        bg2_list.indices.push_back(0);\n        bg2_list.names.push_back(\"None\");\n\n        for(std::pair<ListItemFamily_t, std::string>& p : bg2_families)\n        {\n            ListItemFamily_t& f = p.first;\n\n            bg2_list.indices.push_back(-1);\n            bg2_list.names.push_back(std::move(f.name));\n\n            for(const int16_t i : f.indices)\n                bg2_list.indices.push_back(i);\n\n            for(std::string& s : f.names)\n                bg2_list.names.push_back(std::move(s));\n        }\n\n        bg2_list.make_layout(10);\n\n        // add to translation engine\n        if(translate)\n            bg2_list.make_translation(*translate, \"background2\", \"bg2-\", bg2_families);\n    }\n\n\n    // process music\n    if(!music_families.empty())\n    {\n        loaded++;\n\n        tinysort(music_families.begin(), music_families.end(),\n        [](const std::pair<ListItemFamily_t, std::string>& a, const std::pair<ListItemFamily_t, std::string>& b)\n        {\n            return a.first.sort_index < b.first.sort_index;\n        });\n\n        size_t length = 2;\n        for(const std::pair<ListItemFamily_t, std::string>& p : music_families)\n        {\n            const ListItemFamily_t& f = p.first;\n            length += 1 + f.indices.size();\n        }\n\n        music_list.indices.reserve(length);\n        music_list.names.reserve(length);\n\n        music_list.indices.push_back(0);\n        music_list.names.push_back(\"None\");\n\n        music_list.indices.push_back(24);\n        music_list.names.push_back(\"Custom\");\n\n        for(std::pair<ListItemFamily_t, std::string>& p : music_families)\n        {\n            ListItemFamily_t& f = p.first;\n\n            music_list.indices.push_back(-1);\n            music_list.names.push_back(std::move(f.name));\n\n            for(const int16_t i : f.indices)\n                music_list.indices.push_back(i);\n\n            for(std::string& s : f.names)\n                music_list.names.push_back(std::move(s));\n        }\n\n        music_list.make_layout(10);\n\n        // add to translation engine\n        if(translate)\n            music_list.make_translation(*translate, \"music\", \"music\", music_families);\n    }\n\n    // process world music\n    if(!wmusic_families.empty())\n    {\n        tinysort(wmusic_families.begin(), wmusic_families.end(),\n        [](const std::pair<ListItemFamily_t, std::string>& a, const std::pair<ListItemFamily_t, std::string>& b)\n        {\n            return a.first.sort_index < b.first.sort_index;\n        });\n\n        size_t length = 1;\n        for(const std::pair<ListItemFamily_t, std::string>& p : wmusic_families)\n        {\n            const ListItemFamily_t& f = p.first;\n            length += 1 + f.indices.size();\n        }\n\n        wmusic_list.indices.reserve(length);\n        wmusic_list.names.reserve(length);\n\n        wmusic_list.indices.push_back(0);\n        wmusic_list.names.push_back(\"None\");\n\n        for(std::pair<ListItemFamily_t, std::string>& p : wmusic_families)\n        {\n            ListItemFamily_t& f = p.first;\n\n            wmusic_list.indices.push_back(-1);\n            wmusic_list.names.push_back(std::move(f.name));\n\n            for(const int16_t i : f.indices)\n                wmusic_list.indices.push_back(i);\n\n            for(std::string& s : f.names)\n                wmusic_list.names.push_back(std::move(s));\n        }\n\n        wmusic_list.make_layout(10);\n\n        // add to translation engine\n        if(translate)\n            wmusic_list.make_translation(*translate, \"worldMusic\", \"wmusic\", wmusic_families);\n    }\n\n    // process sounds\n    if(!sound_families.empty())\n    {\n        loaded++;\n\n        tinysort(sound_families.begin(), sound_families.end(),\n        [](const std::pair<ListItemFamily_t, std::string>& a, const std::pair<ListItemFamily_t, std::string>& b)\n        {\n            return a.first.sort_index < b.first.sort_index;\n        });\n\n        size_t length = 1;\n        for(const std::pair<ListItemFamily_t, std::string>& p : sound_families)\n        {\n            const ListItemFamily_t& f = p.first;\n            length += 1 + f.indices.size();\n        }\n\n        sound_list.indices.reserve(length);\n        sound_list.names.reserve(length);\n\n        sound_list.indices.push_back(0);\n        sound_list.names.push_back(\"None\");\n\n        for(std::pair<ListItemFamily_t, std::string>& p : sound_families)\n        {\n            ListItemFamily_t& f = p.first;\n\n            sound_list.indices.push_back(-1);\n            sound_list.names.push_back(std::move(f.name));\n\n            for(const int16_t i : f.indices)\n                sound_list.indices.push_back(i);\n\n            for(std::string& s : f.names)\n                sound_list.names.push_back(std::move(s));\n        }\n\n        sound_list.make_layout(10);\n\n        // add to translation engine\n        if(translate)\n            sound_list.make_translation(*translate, \"sound\", \"sound\", sound_families);\n    }\n\n    // report loading status\n    if(loaded == 0)\n        pLogWarning(\"Could not load editor.ini, editor disabled\");\n    else if(loaded < LOADED_ALL)\n        pLogWarning(\"editor.ini seems incomplete\");\n}\n\n} // namespace EditorCustom\n"
  },
  {
    "path": "src/editor/editor_custom.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef EDITOR_CUSTOM_H\n#define EDITOR_CUSTOM_H\n\n#include <vector>\n#include <array>\n\n#include \"globals.h\"\n\n\nclass XTechTranslate;\n\n\nnamespace EditorCustom\n{\n\nstruct ItemType_t\n{\n    enum slope_t\n    {\n        no_slope = 0,\n        slope_1 = 1,\n        slope_2 = 2,\n        slope_4 = 3\n    };\n\n    int type : 11;\n    int group : 4;\n    bool temp_flag : 1;\n\n    // uses numpad coordinates\n    bool has_1 : 1;\n    bool has_2 : 1;\n    bool has_3 : 1;\n    bool has_4 : 1;\n    bool has_6 : 1;\n    bool has_7 : 1;\n    bool has_8 : 1;\n    bool has_9 : 1;\n\n    slope_t slope : 2;\n    unsigned width : 3;\n    unsigned height : 3;\n\n    ItemType_t(int type = 0, const char* sides = nullptr, slope_t slope = no_slope, int width = 1, int height = 1, int special = 0);\n\n    void set_adj(const char* sides);\n};\n\nstruct LayoutPod_t\n{\n    std::vector<int> types;\n    int x;\n    int y;\n    int rows;\n    int cols;\n\n    LayoutPod_t();\n    LayoutPod_t(int rows, int cols);\n    void resize(int rows, int cols);\n    void compact();\n};\n\nclass ItemFamily\n{\npublic:\n    // user-supplied\n    std::string name;\n\n    std::vector<ItemType_t> types;\n    std::vector<LayoutPod_t> req_layout_pods;\n\n    // automatically generated\n    std::vector<LayoutPod_t> temp_layout_pods;\n    LayoutPod_t layout_pod;\n\n    int8_t page = 0;\n    int8_t X = 0;\n    int8_t Y = 0;\n\n    // user-supplied\n    int16_t icon = 0;\n    int8_t category = 0;\n    bool is_misc = true;\n    bool behind_mode = false;\n\n\n    void make_layout_pods();\n    bool make_layout(int layout_width);\n};\n\nstruct ItemPage_t\n{\n    std::vector<ItemFamily*>::iterator begin;\n    std::vector<ItemFamily*>::iterator end;\n\n    int16_t icon;\n    int8_t category;\n};\n\nstruct ListItemFamily_t\n{\n    std::string name;\n    int sort_index = 0;\n\n    std::vector<int16_t> indices;\n    std::vector<std::string> names;\n};\n\nstruct ItemList_t\n{\n    std::vector<int16_t> indices;\n    std::vector<std::string> names;\n\n    inline void clear()\n    {\n        indices.clear();\n        names.clear();\n    }\n\n    void make_layout(int rows);\n\n    void make_translation(XTechTranslate& translate, const char* group, const char* prefix, const std::vector<std::pair<ListItemFamily_t, std::string>>& families);\n};\n\nvoid Load(XTechTranslate* translate = nullptr);\n\nconstexpr uint8_t PAGE_NONE = 255;\nconstexpr uint8_t FAMILY_NONE = 255;\n\nextern std::vector<ItemFamily> block_families;\nextern std::vector<ItemPage_t> block_pages;\nextern std::array<uint8_t, maxBlockType> block_family_by_type;\n\n\nextern std::vector<ItemFamily> bgo_families;\nextern std::vector<ItemPage_t> bgo_pages;\nextern std::array<uint8_t, maxBackgroundType> bgo_family_by_type;\n\nextern std::vector<ItemFamily> npc_families;\nextern std::vector<ItemPage_t> npc_pages;\nextern std::array<uint8_t, maxNPCType> npc_family_by_type;\n\nextern std::vector<ItemFamily> tile_families;\nextern std::vector<ItemPage_t> tile_pages;\nextern std::array<uint8_t, maxTileType> tile_family_by_type;\n\n\nextern ItemList_t sound_list;\nextern ItemList_t music_list;\nextern ItemList_t wmusic_list;\nextern ItemList_t bg2_list;\n\n\n// has fixed size of 10\nextern std::vector<std::string> list_level_exit_names;\n\n\n// tracks loading state\nextern uint8_t loaded;\n\n} // namespace EditorCustom\n\n#endif // EDITOR_CUSTOM_H\n"
  },
  {
    "path": "src/editor/editor_strings.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"editor/editor_strings.h\"\n\nEditorContent g_editorStrings;\n\nvoid initEditorStrings()\n{\n    g_editorStrings.phraseAreYouSure = \"Are you sure?\";\n    g_editorStrings.pageBlankOfBlank = \"Page {0} of {1}\";\n\n    g_editorStrings.pickBlockContents = \"Pick block contents\";\n\n    g_editorStrings.blockLetterWidth  = \"W \";\n    g_editorStrings.blockLetterHeight = \"H \";\n    g_editorStrings.blockCanBreak = \"Can Break\";\n    g_editorStrings.blockTooltipCanBreak = \"Legacy: breaks when hit\";\n    g_editorStrings.blockSlick = \"Slick\";\n    g_editorStrings.blockInvis = \"Invis\";\n    g_editorStrings.blockInside = \"Inside:\";\n\n    g_editorStrings.warpTitle = \"Warp Settings\";\n    g_editorStrings.warpPlacing = \"Placing:\";\n    g_editorStrings.warpIn = \"In\";\n    g_editorStrings.warpOut = \"Out\";\n    g_editorStrings.warpDir = \"Dir.\";\n    g_editorStrings.warpTwoWay = \"Two-Way\";\n    g_editorStrings.warpStyle = \"Style: \";\n    g_editorStrings.warpStylePipe = \"Pipe\";\n    g_editorStrings.warpStyleDoor = \"Door\";\n    g_editorStrings.warpStyleBlipInstant = \"Blip\";\n    g_editorStrings.warpStylePortal = \"Port\";\n    g_editorStrings.warpEffect = \"Effect:\";\n    g_editorStrings.warpAllow = \"Allow:\";\n    g_editorStrings.warpItem = \"Item\";\n    g_editorStrings.warpRide = \"Ride\";\n    g_editorStrings.warpCannonExit = \"Cannon Exit\";\n    g_editorStrings.warpSpeed = \"Speed \";\n    g_editorStrings.warpNeedStarCount = \"Need {0} {1}\";\n    g_editorStrings.warpNeedKey = \"Need Key\";\n    g_editorStrings.warpNeedFloor = \"Need Floor\";\n    g_editorStrings.warpStarLockMessage = \"Star lock msg\";\n    g_editorStrings.warpToMap = \"To Map\";\n    g_editorStrings.warpLvlWarp = \"Lvl Warp\";\n    g_editorStrings.warpTarget = \"Target: \";\n    g_editorStrings.warpTo = \"To: {0}\";\n    g_editorStrings.warpShowStartScene = \"Show Start Scene\";\n    g_editorStrings.warpShowStarCount = \"Show Star Count\";\n\n    g_editorStrings.waterTitle = \"Water Settings\";\n\n    g_editorStrings.npcInContainer = \"In\";\n    g_editorStrings.npcInertNice = \"Nice\";\n    g_editorStrings.npcStuckStop = \"Stop\";\n    g_editorStrings.npcAbbrevGen = \"Gen\";\n    g_editorStrings.npcPropertyActive = \"Active\";\n    g_editorStrings.npcPropertyAttachSurface = \"Attach\";\n    g_editorStrings.npcPropertyFacing = \"Facing\";\n\n    g_editorStrings.npcAiIs = \"AI: {0}\";\n    g_editorStrings.npcAiTarget = \"Target\";\n    g_editorStrings.npcAiJump = \"Jump\";\n    g_editorStrings.npcAiLeap = \"Leap\";\n    g_editorStrings.npcAiSwim = \"Swim\";\n    g_editorStrings.npcAiLR = \"LR\";\n    g_editorStrings.npcAiUD = \"UD\";\n\n    g_editorStrings.npcCustomAi = \"Custom AI:\";\n\n    g_editorStrings.npcUse1_0Ai = \"Use 1.0 AI?\";\n    g_editorStrings.npcTooltipExpandSection = \"Expand section\";\n\n    g_editorStrings.npcGenHeader = \"Generator Settings\";\n    g_editorStrings.npcGenDirection = \"Direction\";\n    g_editorStrings.npcGenEffectIs = \"Effect: {0}\";\n    g_editorStrings.npcGenEffectWarp = \"Warp\";\n    g_editorStrings.npcGenEffectShoot = \"Shoot\";\n\n    g_editorStrings.wordNPC = \"NPC\";\n    g_editorStrings.wordNPCGenitive = \"NPC\";\n\n    g_editorStrings.wordEvent = \"Event\";\n    g_editorStrings.wordEventGenitive = \"Event\";\n    g_editorStrings.phraseTypeLabelEvent = \"{0} Event:\";\n\n    g_editorStrings.wordCoins = \"Coins\";\n\n    g_editorStrings.wordEnabled = \"Enabled\";\n    g_editorStrings.wordText = \"Text\";\n    g_editorStrings.wordInstant = \"Instant\";\n    g_editorStrings.wordMode = \"Mode\";\n    g_editorStrings.wordHeight = \"Height\";\n    g_editorStrings.wordWidth = \"Width\";\n\n    g_editorStrings.labelSortLayer = \"Sort Layer:\";\n    g_editorStrings.labelSortOffset = \"Sort Offset:\";\n\n    g_editorStrings.phraseTextOf = \"{0} Text\";\n    g_editorStrings.phraseSectionIndex = \"Section {0}\";\n    g_editorStrings.phraseRadiusIndex = \"Radius {0}\";\n    g_editorStrings.phraseWarpIndex = \"Warp {0}\";\n    g_editorStrings.phraseGenericIndex = \"Number {0}\";\n    g_editorStrings.phraseCountMore = \"{0} More\";\n    g_editorStrings.phraseDelayIsMs = \"Delay: {0} ms\";\n    g_editorStrings.mapPos = \"Map Pos:\";\n\n    g_editorStrings.letterUp = \"U\";\n    g_editorStrings.letterDown = \"D\";\n    g_editorStrings.letterLeft = \"L\";\n    g_editorStrings.letterRight = \"R\";\n    g_editorStrings.letterCoordX = \"X\";\n    g_editorStrings.letterCoordY = \"Y\";\n\n    g_editorStrings.toggleMagicBlock = \"Magic Block\";\n\n    g_editorStrings.testMagicHand = \"Magic Hand\";\n    g_editorStrings.testChar = \"Char\";\n    g_editorStrings.testPower = \"Power\";\n    g_editorStrings.testBoot = \"Boot\";\n    g_editorStrings.testPet = \"Pet\";\n\n    g_editorStrings.eventsHeader = \"Events:\";\n\n    g_editorStrings.eventsLetterActivate = \"A:\";\n    g_editorStrings.eventsLetterDeath = \"D:\";\n    g_editorStrings.eventsLetterTalk = \"T:\";\n    g_editorStrings.eventsLetterLayerClear = \"L:\";\n    g_editorStrings.eventsLetterHit = \"H:\";\n    g_editorStrings.eventsLetterDestroy = \"D:\";\n    g_editorStrings.eventsLetterEnter = \"E:\";\n\n    g_editorStrings.eventsLabelNext = \"Next\";\n    g_editorStrings.eventsLabelActivate = \"Activate\";\n    g_editorStrings.eventsLabelDeath = \"Death\";\n    g_editorStrings.eventsLabelTalk = \"Talk\";\n    g_editorStrings.eventsLabelLayerClear = \"Layer Clear\";\n    g_editorStrings.eventsLabelHit = \"Hit\";\n    g_editorStrings.eventsLabelDestroy = \"Destroy\";\n    g_editorStrings.eventsLabelEnter = \"Enter\";\n\n    g_editorStrings.eventsDescActivate = \"NPC enters the screen\";\n    g_editorStrings.eventsDescDeath = \"NPC dies\";\n    g_editorStrings.eventsDescTalk = \"player talks to NPC\";\n    g_editorStrings.eventsDescLayerClear = \"everything in object layer is gone\";\n    g_editorStrings.eventsDescHit = \"block is hit\";\n    g_editorStrings.eventsDescDestroy = \"block is destroyed\";\n    g_editorStrings.eventsDescEnter = \"warp is entered\";\n\n    g_editorStrings.eventsDescPhraseTriggersWhenTemplate = \"{0} triggers when {1}\";\n    g_editorStrings.eventsDescPhraseTriggersAfterTemplate = \"{0} triggers {1} ms after event {2} occurs\";\n\n    g_editorStrings.eventsDeletingEvent = \"Deleting event {0}\";\n    g_editorStrings.eventsDeletionConfirm = \"Yes: delete event\";\n    g_editorStrings.eventsDeletionCancel = \"No: do not delete event\";\n\n    g_editorStrings.eventsPromptEventText = \"Event text\";\n    g_editorStrings.eventsPromptEventName = \"Event name\";\n    g_editorStrings.eventsItemNewEvent = \"<New Event>\";\n\n    g_editorStrings.eventsControlsForEvent = \"Controls for event {0}\";\n    g_editorStrings.eventsSettingsForEvent = \"Settings for event\";\n    g_editorStrings.eventsHeaderShow = \"Show:\";\n    g_editorStrings.eventsHeaderHide = \"Hide:\";\n    g_editorStrings.eventsHeaderToggle = \"Toggle:\";\n    g_editorStrings.eventsHeaderMove = \"Move:\";\n\n    g_editorStrings.eventsActionKeep = \"Keep\";\n    g_editorStrings.eventsActionReset = \"Reset\";\n    g_editorStrings.eventsActionSet = \"Set\";\n\n    g_editorStrings.eventsCaseMusic = \"Music\";\n    g_editorStrings.eventsCaseBackground = \"BG\";\n    g_editorStrings.eventsCaseBounds = \"Bounds\";\n\n    g_editorStrings.eventsPhraseAllSections = \"All Sections\";\n\n    g_editorStrings.eventsPropAutostart = \"Autostart\";\n    g_editorStrings.eventsPropSound = \"Sound\";\n    g_editorStrings.eventsPropEndGame = \"End Game\";\n    g_editorStrings.eventsPropControls = \"Controls\";\n    g_editorStrings.eventsPropLayerSmoke = \"Smoke\";\n\n    g_editorStrings.eventsHeaderTriggerEvent = \"Trigger:\";\n\n    g_editorStrings.levelName = \"Level Name\";\n    g_editorStrings.levelStartPos = \"Start Pos\";\n    g_editorStrings.levelPathBG = \"Path BG\";\n    g_editorStrings.levelBigBG = \"Big BG\";\n    g_editorStrings.levelGameStart = \"Game Start\";\n    g_editorStrings.levelAlwaysVis = \"Always Vis\";\n    g_editorStrings.levelPathUnlocks = \"Path Unlocks\";\n\n    g_editorStrings.sectionScroll = \"Scroll\";\n    g_editorStrings.sectionHorizWrap = \"Horiz. Wrap\";\n    g_editorStrings.sectionVertWrap = \"Vert. Wrap\";\n    g_editorStrings.sectionUnderwater = \"Underwater\";\n    g_editorStrings.sectionNoTurnBack = \"No Turn Back\";\n    g_editorStrings.sectionOffscreenExit = \"Offscreen Exit\";\n\n    g_editorStrings.worldName = \"World Name\";\n    g_editorStrings.worldIntroLevel = \"Intro Level\";\n    g_editorStrings.worldHubWorld = \"Hub World\";\n    g_editorStrings.worldRetryOnFail = \"Retry On Fail\";\n    g_editorStrings.worldTotalStars = \"Total Stars: \";\n    g_editorStrings.worldAllowChars = \"Allow Chars\";\n    g_editorStrings.worldCreditIndex = \"World Credit Line {0}:\";\n\n    g_editorStrings.selectSoundForEvent = \"Sound for Event {0}\";\n    g_editorStrings.selectSectBlankPropBlankForEvent = \"Sect {0} {1} for {2}\";\n    g_editorStrings.selectAllSectPropBlankForEvent = \"All Sect {0} for {1}\";\n    g_editorStrings.selectSectionBlankPropBlank = \"Section {0} {1}\";\n    g_editorStrings.selectPathBlankUnlock = \"Path {0} Unlock By\";\n    g_editorStrings.selectWarpTransitionEffect = \"Warp Transition Effect\";\n    g_editorStrings.selectWorldMusic = \"World Music\";\n\n    g_editorStrings.layersHeader = \"Layers:\";\n\n    g_editorStrings.labelLayer = \"Layer:\";\n    g_editorStrings.layersLabelAttached = \"Attached:\";\n    g_editorStrings.layersAbbrevAttLayer = \"Att: \";\n    g_editorStrings.layersLayerDefault = \"Default\";\n\n    g_editorStrings.layersLabelAttachedLayer = \"Attached Layer:\";\n    g_editorStrings.layersLabelMoveLayer = \"Move Layer:\";\n\n    g_editorStrings.layersDeletionHeader = \"Deleting layer {0}\";\n    g_editorStrings.layersDeletionPreserveLayerContents = \"Preserve layer contents?\";\n    g_editorStrings.layersDeletionConfirmPreserve = \"Yes: move to default layer\";\n    g_editorStrings.layersDeletionConfirmDelete = \"No: *DELETE ALL CONTENTS*\";\n    g_editorStrings.layersDeletionCancel = \"Cancel: do not delete layer\";\n\n    g_editorStrings.layersDescAtt = \"Whenever the NPC moves, the attached layer moves following it\";\n\n    g_editorStrings.layersPromptLayerName = \"Layer name\";\n    g_editorStrings.layersItemNewLayer = \"<New Layer>\";\n\n    g_editorStrings.fileActionClearLevel = \"Clear Level\";\n    g_editorStrings.fileActionClearWorld = \"Clear World\";\n    g_editorStrings.fileActionOpen = \"Open\";\n    g_editorStrings.fileActionRevert = \"Revert\";\n    g_editorStrings.fileActionExit = \"Exit\";\n    g_editorStrings.fileConfirmationSaveBeforeAction = \"Save before you {0}?\";\n    g_editorStrings.fileConfirmationConfirmAction = \"Are you sure you want to {0}?\";\n    g_editorStrings.fileConfirmationConvertFormatTo = \"Convert format to {0}?\";\n    g_editorStrings.fileOptionYesSaveThenAction = \"Yes: save then {0}\";\n    g_editorStrings.fileOptionActionWithoutSave = \"{0} without saving\";\n    g_editorStrings.fileOptionCancelAction = \"Cancel: do not {0}\";\n    g_editorStrings.fileOptionProceedWithConversion = \"Proceed with conversion\";\n    g_editorStrings.fileOptionCancelConversion = \"Cancel conversion\";\n\n    g_editorStrings.fileLabelCurrentFile = \"Current file: \";\n    g_editorStrings.fileLabelFormat = \"Format:\";\n\n    g_editorStrings.fileFormatModern = \"Modern\";\n    g_editorStrings.fileFormatLegacy = \"Legacy\";\n\n    g_editorStrings.fileSectionLevel = \"Level\";\n    g_editorStrings.fileSectionWorld = \"World\";\n    g_editorStrings.fileCommandNew = \"New\";\n    g_editorStrings.fileCommandOpen = \"Open...\";\n    g_editorStrings.fileCommandSave = \"Save\";\n    g_editorStrings.fileCommandSaveAs = \"Save as...\";\n\n    g_editorStrings.fileConvertDesc = \"The file extension will change but the old file will NOT be deleted.\\n\\nPlease check for lost features after saving.\";\n\n#if 0\n    g_editorStrings.fileConvert38aUnsupported = \"The SMBX38-A format is not supported for conversion.\";\n    g_editorStrings.fileConvertFormatUnknown = \"Requested format is unknown.\";\n\n    g_editorStrings.fileConvertFeatureWarpTransit = \"A warp uses new transition effect.\";\n    g_editorStrings.fileConvertFeatureWarpNeedsStand = \"A warp requires player to stand.\";\n    g_editorStrings.fileConvertFeatureWarpCannonExit = \"A warp has the cannon exit effect.\";\n    g_editorStrings.fileConvertFeatureWarpEnterEvent = \"A warp triggers an event on entry.\";\n    g_editorStrings.fileConvertFeatureWarpCustomStarsMsg = \"A warp has a custom stars message.\";\n    g_editorStrings.fileConvertFeatureWarpNoPrintStars = \"A warp hides target level stars.\";\n    g_editorStrings.fileConvertFeatureWarpNoStartScene = \"A level warp skips the start scene.\";\n    g_editorStrings.fileConvertFeatureWarpPortal = \"A warp uses the portal effect.\";\n\n    g_editorStrings.fileConvertFeatureEventCustomMusic = \"An event sets music to a file.\";\n    g_editorStrings.fileConvertFeatureEventAutoscroll = \"An event uses modern autoscroll.\";\n\n    g_editorStrings.fileConvertFeatureNPCVariant = \"An NPC uses a behavior variant.\";\n    g_editorStrings.fileConvertFeatureBlockForceSmashable = \"A spin block uses legacy smash.\";\n    g_editorStrings.fileConvertFeatureBgoOrder = \"A BGO uses custom draw order.\";\n\n    g_editorStrings.fileConvertFeatureCustomWorldMusic = \"Uses custom world music file.\";\n    g_editorStrings.fileConvertFeatureWorldStarDisplay = \"Uses world setting for star display.\";\n    g_editorStrings.fileConvertFeatureLevelStarDisplay = \"A level uses setting for star display.\";\n    g_editorStrings.fileConvertFeatureWorldMapSections = \"The world includes world map sections.\";\n#endif\n\n    g_editorStrings.browserNewFile = \"New file\";\n    g_editorStrings.browserSaveFile = \"Save file\";\n    g_editorStrings.browserOpenFile = \"Open file\";\n\n    g_editorStrings.browserItemNewFile = \"<New File>\";\n    g_editorStrings.browserItemNewFolder = \"<New Folder>\";\n\n    g_editorStrings.browserAskOverwriteFile = \"Overwrite {0}?\";\n\n    g_editorStrings.tooltipSelect = \"Select\";\n    g_editorStrings.tooltipErase = \"Erase\";\n    g_editorStrings.tooltipEraseAll = \"Erase All\";\n    g_editorStrings.tooltipBlocks = \"Blocks\";\n    g_editorStrings.tooltipBGOs = \"BGOs\";\n    g_editorStrings.tooltipNPCs = \"NPCs\";\n    g_editorStrings.tooltipWarps = \"Warps\";\n    g_editorStrings.tooltipWater = \"Water\";\n    g_editorStrings.tooltipSettings = \"Settings\";\n    g_editorStrings.tooltipLayers = \"Layers\";\n    g_editorStrings.tooltipEvents = \"Events\";\n    g_editorStrings.tooltipTiles = \"Tiles\";\n    g_editorStrings.tooltipScenes = \"Scenes\";\n    g_editorStrings.tooltipLevels = \"Levels\";\n    g_editorStrings.tooltipPaths = \"Paths\";\n    g_editorStrings.tooltipMusic = \"Music\";\n    g_editorStrings.tooltipArea = \"Area\";\n    g_editorStrings.tooltipFile = \"File\";\n    g_editorStrings.tooltipShow = \"Show\";\n\n    g_editorStrings.listWarpTransitNames = {\"NONE\", \"SCROLL\", \"FADE\", \"CIRCLE\", \"FLIP (H)\", \"FLIP (V)\"};\n}\n"
  },
  {
    "path": "src/editor/editor_strings.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef EDITOR_STRINGS_H\n#define EDITOR_STRINGS_H\n\n#include <string>\n#include <vector>\n\nstruct EditorContent\n{\n    std::string phraseAreYouSure;\n    std::string pageBlankOfBlank;\n\n    std::string pickBlockContents;\n\n    std::string blockLetterWidth;\n    std::string blockLetterHeight;\n    std::string blockCanBreak;\n    std::string blockTooltipCanBreak;\n    std::string blockSlick;\n    std::string blockInvis;\n    std::string blockInside;\n\n    std::string warpTitle;\n    std::string warpPlacing;\n    std::string warpIn;\n    std::string warpOut;\n    std::string warpDir;\n    std::string warpTwoWay;\n    std::string warpStyle;\n    std::string warpStylePipe;\n    std::string warpStyleDoor;\n    std::string warpStyleBlipInstant;\n    std::string warpStylePortal;\n    std::string warpEffect;\n    std::string warpAllow;\n    std::string warpItem;\n    std::string warpRide;\n    std::string warpCannonExit;\n    std::string warpSpeed;\n    std::string warpNeedStarCount;\n    std::string warpNeedKey;\n    std::string warpNeedFloor;\n    std::string warpStarLockMessage;\n    std::string warpToMap;\n    std::string warpLvlWarp;\n    std::string warpTarget;\n    std::string warpTo;\n    std::string warpShowStartScene;\n    std::string warpShowStarCount;\n\n    std::string waterTitle;\n\n    std::string npcInContainer;\n    std::string npcInertNice;\n    std::string npcStuckStop;\n    std::string npcAbbrevGen;\n    std::string npcPropertyActive;\n    std::string npcPropertyAttachSurface;\n    std::string npcPropertyFacing;\n\n    std::string npcAiIs;\n    std::string npcAiTarget;\n    std::string npcAiJump;\n    std::string npcAiLeap;\n    std::string npcAiSwim;\n    std::string npcAiLR;\n    std::string npcAiUD;\n\n    std::string npcCustomAi;\n\n    std::string npcUse1_0Ai;\n    std::string npcTooltipExpandSection;\n\n    std::string npcGenHeader;\n    std::string npcGenDirection;\n    std::string npcGenEffectIs;\n    std::string npcGenEffectWarp;\n    std::string npcGenEffectShoot;\n\n    std::string wordNPC;\n    std::string wordNPCGenitive;\n\n    std::string wordEvent;\n    std::string wordEventGenitive;\n    std::string phraseTypeLabelEvent;\n\n    std::string wordCoins;\n\n    std::string wordEnabled;\n    std::string wordText;\n    std::string wordInstant;\n    std::string wordMode;\n    std::string wordHeight;\n    std::string wordWidth;\n\n    std::string labelSortLayer;\n    std::string labelSortOffset;\n\n    std::string phraseTextOf;\n    std::string phraseSectionIndex;\n    std::string phraseRadiusIndex;\n    std::string phraseWarpIndex;\n    std::string phraseGenericIndex;\n    std::string phraseCountMore;\n    std::string phraseDelayIsMs;\n    std::string mapPos;\n\n    std::string letterUp;\n    std::string letterDown;\n    std::string letterLeft;\n    std::string letterRight;\n    std::string letterCoordX;\n    std::string letterCoordY;\n\n    std::string toggleMagicBlock;\n\n    std::string testMagicHand;\n    std::string testChar;\n    std::string testPower;\n    std::string testBoot;\n    std::string testPet;\n\n    std::string eventsHeader;\n\n    std::string eventsLetterActivate;\n    std::string eventsLetterDeath;\n    std::string eventsLetterTalk;\n    std::string eventsLetterLayerClear;\n    std::string eventsLetterHit;\n    std::string eventsLetterDestroy;\n    std::string eventsLetterEnter;\n\n    std::string eventsLabelNext;\n    std::string eventsLabelActivate;\n    std::string eventsLabelDeath;\n    std::string eventsLabelTalk;\n    std::string eventsLabelLayerClear;\n    std::string eventsLabelHit;\n    std::string eventsLabelDestroy;\n    std::string eventsLabelEnter;\n\n    std::string eventsDescActivate;\n    std::string eventsDescDeath;\n    std::string eventsDescTalk;\n    std::string eventsDescLayerClear;\n    std::string eventsDescHit;\n    std::string eventsDescDestroy;\n    std::string eventsDescEnter;\n\n    std::string eventsDescPhraseTriggersWhenTemplate;\n    std::string eventsDescPhraseTriggersAfterTemplate;\n\n    std::string eventsDeletingEvent;\n    std::string eventsDeletionConfirm;\n    std::string eventsDeletionCancel;\n\n    std::string eventsPromptEventText;\n    std::string eventsPromptEventName;\n    std::string eventsItemNewEvent;\n\n    std::string eventsControlsForEvent;\n    std::string eventsSettingsForEvent;\n    std::string eventsHeaderShow;\n    std::string eventsHeaderHide;\n    std::string eventsHeaderToggle;\n    std::string eventsHeaderMove;\n\n    std::string eventsActionKeep;\n    std::string eventsActionReset;\n    std::string eventsActionSet;\n\n    std::string eventsCaseMusic;\n    std::string eventsCaseBackground;\n    std::string eventsCaseBounds;\n\n    std::string eventsPhraseAllSections;\n\n    std::string eventsPropAutostart;\n    std::string eventsPropSound;\n    std::string eventsPropEndGame;\n    std::string eventsPropControls;\n    std::string eventsPropLayerSmoke;\n\n    std::string eventsHeaderTriggerEvent;\n\n    std::string levelName;\n    std::string levelStartPos;\n    std::string levelPathBG;\n    std::string levelBigBG;\n    std::string levelGameStart;\n    std::string levelAlwaysVis;\n    std::string levelPathUnlocks;\n\n    std::string sectionScroll;\n    std::string sectionHorizWrap;\n    std::string sectionVertWrap;\n    std::string sectionUnderwater;\n    std::string sectionNoTurnBack;\n    std::string sectionOffscreenExit;\n\n    std::string worldName;\n    std::string worldIntroLevel;\n    std::string worldHubWorld;\n    std::string worldRetryOnFail;\n    std::string worldTotalStars;\n    std::string worldAllowChars;\n    std::string worldCreditIndex;\n\n    std::string selectSoundForEvent;\n    std::string selectSectBlankPropBlankForEvent;\n    std::string selectAllSectPropBlankForEvent;\n    std::string selectSectionBlankPropBlank;\n    std::string selectPathBlankUnlock;\n    std::string selectWarpTransitionEffect;\n    std::string selectWorldMusic;\n\n    std::string layersHeader;\n\n    std::string labelLayer;\n    std::string layersLabelAttached;\n    std::string layersAbbrevAttLayer;\n    std::string layersLayerDefault;\n\n    std::string layersLabelAttachedLayer;\n    std::string layersLabelMoveLayer;\n\n    std::string layersDeletionHeader;\n    std::string layersDeletionPreserveLayerContents;\n    std::string layersDeletionConfirmPreserve;\n    std::string layersDeletionConfirmDelete;\n    std::string layersDeletionCancel;\n\n    std::string layersDescAtt;\n\n    std::string layersPromptLayerName;\n    std::string layersItemNewLayer;\n\n    std::string fileActionClearLevel;\n    std::string fileActionClearWorld;\n    std::string fileActionOpen;\n    std::string fileActionRevert;\n    std::string fileActionExit;\n    std::string fileConfirmationSaveBeforeAction;\n    std::string fileConfirmationConfirmAction;\n    std::string fileConfirmationConvertFormatTo;\n    std::string fileOptionYesSaveThenAction;\n    std::string fileOptionActionWithoutSave;\n    std::string fileOptionCancelAction;\n    std::string fileOptionProceedWithConversion;\n    std::string fileOptionCancelConversion;\n\n    std::string fileLabelCurrentFile;\n    std::string fileLabelFormat;\n\n    std::string fileFormatModern;\n    std::string fileFormatLegacy;\n\n    std::string fileSectionLevel;\n    std::string fileSectionWorld;\n    std::string fileCommandNew;\n    std::string fileCommandOpen;\n    std::string fileCommandSave;\n    std::string fileCommandSaveAs;\n\n    std::string fileConvertDesc;\n\n#if 0\n    std::string fileConvertNoIssues;\n    std::string fileConvertFeaturesWillBeLost;\n\n    std::string fileConvert38aUnsupported;\n    std::string fileConvertFormatUnknown;\n\n    std::string fileConvertFeatureWarpTransit;\n    std::string fileConvertFeatureWarpNeedsStand;\n    std::string fileConvertFeatureWarpCannonExit;\n    std::string fileConvertFeatureWarpEnterEvent;\n    std::string fileConvertFeatureWarpCustomStarsMsg;\n    std::string fileConvertFeatureWarpNoPrintStars;\n    std::string fileConvertFeatureWarpNoStartScene;\n    std::string fileConvertFeatureWarpPortal;\n\n    std::string fileConvertFeatureEventCustomMusic;\n    std::string fileConvertFeatureEventAutoscroll;\n\n    std::string fileConvertFeatureNPCVariant;\n    std::string fileConvertFeatureBlockForceSmashable;\n    std::string fileConvertFeatureBgoOrder;\n\n    std::string fileConvertFeatureCustomWorldMusic;\n    std::string fileConvertFeatureWorldStarDisplay;\n    std::string fileConvertFeatureLevelStarDisplay;\n    std::string fileConvertFeatureWorldMapSections;\n#endif\n\n    std::string browserNewFile;\n    std::string browserSaveFile;\n    std::string browserOpenFile;\n\n    std::string browserItemNewFile;\n    std::string browserItemNewFolder;\n\n    std::string browserAskOverwriteFile;\n\n    std::string tooltipSelect;\n    std::string tooltipErase;\n    std::string tooltipEraseAll;\n    std::string tooltipBlocks;\n    std::string tooltipBGOs;\n    std::string tooltipNPCs;\n    std::string tooltipWarps;\n    std::string tooltipWater;\n    std::string tooltipSettings;\n    std::string tooltipLayers;\n    std::string tooltipEvents;\n    std::string tooltipTiles;\n    std::string tooltipScenes;\n    std::string tooltipLevels;\n    std::string tooltipPaths;\n    std::string tooltipMusic;\n    std::string tooltipArea;\n    std::string tooltipFile;\n    std::string tooltipShow;\n\n    std::vector<std::string> listWarpTransitNames{6};\n};\n\nextern EditorContent g_editorStrings;\n\nvoid initEditorStrings();\n\n#endif // EDITOR_STRINGS_H\n"
  },
  {
    "path": "src/editor/magic_block.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <vector>\n#include <array>\n#include <algorithm>\n\n#include <Logger/logger.h>\n\n#include \"globals.h\"\n#include \"collision.h\"\n#include \"main/trees.h\"\n\n#include \"editor/magic_block.h\"\n#include \"editor/editor_custom.h\"\n#include \"editor.h\"\n\n#include \"rand.h\"\n\nnamespace MagicBlock\n{\n\nusing namespace EditorCustom;\n\n\nbool enabled = false;\nbool replace_existing = true;\n// bool advanced_mode = false;\nbool count_level_edges = true;\n\n// CrossEffectLevel check_level = LEVEL_FAMILY;\n// CrossEffectLevel change_level = LEVEL_FAMILY;\n\n\n\nstatic int s_score_match(const ItemType_t candidate, const ItemType_t target)\n{\n    // don't consider it if width or height mismatched\n    if(candidate.width != target.width)\n        return -1;\n    if(candidate.height != target.height)\n        return -1;\n\n    // don't consider it if group subcategory mismatched\n    if(candidate.group != target.group)\n        return -1;\n\n    int score = 0;\n\n    // penalize 6 for unmatched UDLR, 2 for unmatched diagonal\n    if(candidate.has_4 != target.has_4)\n        score += 8;\n    if(candidate.has_8 != target.has_8)\n        score += 8;\n    if(candidate.has_6 != target.has_6)\n        score += 8;\n    if(candidate.has_2 != target.has_2)\n        score += 8;\n\n    if(candidate.has_1 != target.has_1)\n        score += 2;\n    if(candidate.has_3 != target.has_3)\n        score += 2;\n    if(candidate.has_7 != target.has_7)\n        score += 2;\n    if(candidate.has_9 != target.has_9)\n        score += 2;\n\n    // care just a tad more about top\n    if(candidate.has_8 != target.has_8)\n        score += 1;\n\n    // especially penalize missing connections\n    if(target.has_4 && !candidate.has_4)\n        score += 4;\n    if(target.has_8 && !candidate.has_8)\n        score += 4;\n    if(target.has_6 && !candidate.has_6)\n        score += 4;\n    if(target.has_2 && !candidate.has_2)\n        score += 4;\n\n    if(target.has_1 && !candidate.has_1)\n        score += 1;\n    if(target.has_3 && !candidate.has_3)\n        score += 1;\n    if(target.has_7 && !candidate.has_7)\n        score += 1;\n    if(target.has_9 && !candidate.has_9)\n        score += 1;\n\n    // penalize 3 for unmatched slope existence, additional 1 for unmatched slope number\n    if((target.slope == ItemType_t::no_slope) && (candidate.slope != ItemType_t::no_slope))\n        score += 3;\n    if(candidate.slope != target.slope)\n        score += 1;\n\n    return score;\n}\n\nItemType_t s_match_type(const ItemFamily& family, const ItemType_t inferred_type)\n{\n    ItemType_t best_type;\n    int best_score = -1;\n\n#if 0\n    printf(\"Has inferred adjacency: \");\n    if(inferred_type.has_1) printf(\"1\");\n    if(inferred_type.has_2) printf(\"2\");\n    if(inferred_type.has_3) printf(\"3\");\n    if(inferred_type.has_4) printf(\"4\");\n    if(inferred_type.has_6) printf(\"6\");\n    if(inferred_type.has_7) printf(\"7\");\n    if(inferred_type.has_8) printf(\"8\");\n    if(inferred_type.has_9) printf(\"9\");\n    printf(\" (slope %d)\\n\", inferred_type.slope);\n    printf(\"Self is %d\\n\", inferred_type.type);\n#endif\n\n    for(const ItemType_t t : family.types)\n    {\n        if(t.type == inferred_type.type)\n        {\n            best_type = t;\n            best_score = s_score_match(t, inferred_type);\n            // printf(\"Initial self best is %d\\n\", best_score);\n            break;\n        }\n    }\n\n    int matches = 1;\n\n    for(const ItemType_t t : family.types)\n    {\n        int score = s_score_match(t, inferred_type);\n\n        if(score == -1)\n            continue;\n\n        // printf(\"%d score is %d\\n\", t.type, score);\n\n        if(best_score == -1 || score < best_score)\n        {\n            best_type = t;\n            best_score = score;\n        }\n        else if(score == best_score && best_type.type != inferred_type.type)\n        {\n            matches++;\n\n            if(t.type == inferred_type.type || iRand(matches) == 0)\n                best_type = t;\n        }\n    }\n\n    return best_type;\n}\n\ntemplate<class ItemRef_t>\nTreeResult_Sentinel<ItemRef_t> treeQuery(const Location_t& loc, int sort_mode);\n\ntemplate<>\nTreeResult_Sentinel<BlockRef_t> treeQuery<BlockRef_t>(const Location_t& loc, int sort_mode)\n{\n    return treeBlockQuery(loc, sort_mode);\n}\n\ntemplate<>\nTreeResult_Sentinel<TileRef_t> treeQuery<TileRef_t>(const Location_t& loc, int sort_mode)\n{\n    return treeWorldTileQuery(static_cast<TinyLocation_t>(loc), sort_mode);\n}\n\ntemplate<>\nTreeResult_Sentinel<BackgroundRef_t> treeQuery<BackgroundRef_t>(const Location_t& loc, int sort_mode)\n{\n    return treeBackgroundQuery(loc, sort_mode);\n}\n\ntemplate<class ItemRef_t>\nbool s_check_hidden(ItemRef_t B)\n{\n    return B->Hidden;\n}\n\ntemplate<>\nbool s_check_hidden(TileRef_t B)\n{\n    UNUSED(B);\n    return false;\n}\n\ntemplate<class ItemRef_t>\nbool s_check_sizable(ItemRef_t B)\n{\n    UNUSED(B);\n    return false;\n}\n\ntemplate<>\nbool s_check_sizable(BlockRef_t B)\n{\n    return BlockIsSizable[B->Type];\n}\n\ntemplate<class ItemRef_t>\nstatic inline bool s_ban_slope(ItemRef_t B, int dir)\n{\n    UNUSED(B);\n    UNUSED(dir);\n    return false;\n}\n\ntemplate<>\ninline bool s_ban_slope(BlockRef_t B, int dir)\n{\n    int t = B->Type;\n\n    // for cases on left, ban .\\ and */ slopes\n    if(dir == 1 || dir == 4 || dir == 7)\n    {\n        if(BlockSlope[t] > 0 || BlockSlope2[t] < 0)\n            return true;\n    }\n\n    // for cases on right, ban /. and \\* slopes\n    if(dir == 3 || dir == 6 || dir == 9)\n    {\n        if(BlockSlope[t] < 0 || BlockSlope2[t] > 0)\n            return true;\n    }\n\n    // for cases above, ban \\* and */ slopes\n    if(dir == 7 || dir == 8 || dir == 9)\n    {\n        if(BlockSlope2[t])\n            return true;\n    }\n\n    // for cases below, ban /. and .\\ slopes\n    if(dir == 1 || dir == 2 || dir == 3)\n    {\n        if(BlockSlope[t])\n            return true;\n    }\n\n    return false;\n}\n\ntemplate<class ItemRef_t>\nstruct MagicInfo {};\n\ntemplate<>\nstruct MagicInfo<BlockRef_t>\n{\n    static const int maxType = maxBlockType;\n    static constexpr const int& numItem = numBlock;\n    static constexpr std::vector<ItemFamily>& families = block_families;\n    static constexpr const std::array<uint8_t, maxType>& family_by_type = block_family_by_type;\n};\n\ntemplate<>\nstruct MagicInfo<TileRef_t>\n{\n    static const int maxType = maxTileType;\n    static constexpr const int& numItem = numTiles;\n    static constexpr std::vector<ItemFamily>& families = tile_families;\n    static constexpr const std::array<uint8_t, maxType>& family_by_type = tile_family_by_type;\n};\n\ntemplate<>\nstruct MagicInfo<BackgroundRef_t>\n{\n    static const int maxType = maxBackgroundType;\n    static constexpr const int& numItem = numBackground;\n    static constexpr std::vector<ItemFamily>& families = bgo_families;\n    static constexpr const std::array<uint8_t, maxType>& family_by_type = bgo_family_by_type;\n};\n\ntemplate<class ItemRef_t>\nint s_pick_type(ItemFamily& family, ItemRef_t A)\n{\n    const CrossEffectLevel check_level = family.behind_mode ? LEVEL_ALL : LEVEL_FAMILY;\n    constexpr bool advanced_mode = false;\n\n    ItemType_t inferred_type;\n\n    inferred_type.type = A->Type;\n    inferred_type.width = num_t::round(A->Location.Width / 32);\n    inferred_type.height = num_t::round(A->Location.Height / 32);\n\n    for(ItemType_t t : family.types)\n    {\n        if(t.type == A->Type)\n        {\n            inferred_type.group = t.group;\n            inferred_type.slope = t.slope;\n            break;\n        }\n    }\n\n    Location_t tempLoc = static_cast<Location_t>(A->Location);\n\n    // 7 (top-left)\n    tempLoc.X -= 31;\n    tempLoc.Width = 30;\n    tempLoc.Y -= 31;\n    tempLoc.Height = 30;\n\n    if(count_level_edges && !WorldEditor && (tempLoc.X + tempLoc.Width < level[curSection].X || tempLoc.Y + tempLoc.Height < level[curSection].Y))\n        inferred_type.has_7 = true;\n    else for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n    {\n        if(A != B && !s_check_hidden(B) && CheckCollision(tempLoc, B->Location))\n        {\n            if(s_check_sizable(B))\n                continue;\n\n            bool hit = false;\n            for(ItemType_t t : family.types)\n            {\n                if(t.type == B->Type && (check_level != LEVEL_GROUP || t.group == inferred_type.group))\n                {\n                    if(t.has_3 || !advanced_mode)\n                    {\n                        hit = true;\n                        break;\n                    }\n                }\n            }\n\n            if(hit || (check_level == LEVEL_ALL && !s_ban_slope(B, 7)))\n            {\n                inferred_type.has_7 = true;\n                break;\n            }\n        }\n    }\n\n    // 8 (top)\n    tempLoc.X = A->Location.X + 1;\n    tempLoc.Width = A->Location.Width - 2;\n\n    if(count_level_edges && !WorldEditor && tempLoc.Y + tempLoc.Height < level[curSection].Y)\n        inferred_type.has_8 = true;\n    else for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n    {\n        if(A != B && !s_check_hidden(B) && CheckCollision(tempLoc, B->Location))\n        {\n            if(s_check_sizable(B))\n                continue;\n\n            bool hit = false;\n            for(ItemType_t t : family.types)\n            {\n                if(t.type == B->Type && (check_level != LEVEL_GROUP || t.group == inferred_type.group))\n                {\n                    if(t.has_2 || !advanced_mode)\n                    {\n                        if((t.has_1 || t.has_3) && !t.has_8 && t.slope > inferred_type.slope)\n                            inferred_type.slope = t.slope;\n\n                        hit = true;\n                        break;\n                    }\n                }\n            }\n\n            if(hit || (check_level == LEVEL_ALL && !s_ban_slope(B, 8)))\n            {\n                inferred_type.has_8 = true;\n                break;\n            }\n        }\n    }\n\n    // 9 (top-right)\n    tempLoc.X = A->Location.X + A->Location.Width + 1;\n    tempLoc.Width = 30;\n\n    if(count_level_edges && !WorldEditor && (tempLoc.X > level[curSection].Width || tempLoc.Y + tempLoc.Height < level[curSection].Y))\n        inferred_type.has_9 = true;\n    else for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n    {\n        if(A != B && !s_check_hidden(B) && CheckCollision(tempLoc, B->Location))\n        {\n            if(s_check_sizable(B))\n                continue;\n\n            bool hit = false;\n            for(ItemType_t t : family.types)\n            {\n                if(t.type == B->Type && (check_level != LEVEL_GROUP || t.group == inferred_type.group))\n                {\n                    if(t.has_1 || !advanced_mode)\n                    {\n                        hit = true;\n                        break;\n                    }\n                }\n            }\n\n            if(hit || (check_level == LEVEL_ALL && !s_ban_slope(B, 9)))\n            {\n                inferred_type.has_9 = true;\n                break;\n            }\n        }\n    }\n\n    // 6 (right)\n    tempLoc.Y = A->Location.Y + 1;\n    tempLoc.Height = A->Location.Height - 2;\n\n    if(count_level_edges && !WorldEditor && tempLoc.X > level[curSection].Width)\n        inferred_type.has_6 = true;\n    else for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n    {\n        if(A != B && !s_check_hidden(B) && CheckCollision(tempLoc, B->Location))\n        {\n            if(s_check_sizable(B))\n                continue;\n\n            bool hit = false;\n            for(ItemType_t t : family.types)\n            {\n                if(t.type == B->Type && (check_level != LEVEL_GROUP || t.group == inferred_type.group))\n                {\n                    if(t.has_4 || !advanced_mode)\n                    {\n                        if((t.has_1 || t.has_7) && !t.has_6 && t.slope > inferred_type.slope)\n                            inferred_type.slope = t.slope;\n\n                        hit = true;\n                        break;\n                    }\n                }\n            }\n\n            if(hit || (check_level == LEVEL_ALL && !s_ban_slope(B, 6)))\n            {\n                inferred_type.has_6 = true;\n                break;\n            }\n        }\n    }\n\n    // 3 (bottom-right)\n    tempLoc.Y = A->Location.Y + A->Location.Height + 1;\n    tempLoc.Height = 30;\n\n    if(count_level_edges && !WorldEditor && (tempLoc.X > level[curSection].Width || tempLoc.Y > level[curSection].Height))\n        inferred_type.has_3 = true;\n    else for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n    {\n        if(A != B && !s_check_hidden(B) && CheckCollision(tempLoc, B->Location))\n        {\n            // count only the top of a sizable as a collision\n            if(s_check_sizable(B) && A->Location.Y >= B->Location.Y)\n                continue;\n\n            bool hit = false;\n            for(ItemType_t t : family.types)\n            {\n                if(t.type == B->Type && (check_level != LEVEL_GROUP || t.group == inferred_type.group))\n                {\n                    if(t.has_7 || !advanced_mode)\n                    {\n                        hit = true;\n                        break;\n                    }\n                }\n            }\n\n            if(hit || (check_level == LEVEL_ALL && !s_ban_slope(B, 3)))\n            {\n                inferred_type.has_3 = true;\n                break;\n            }\n        }\n    }\n\n    // 2 (bottom)\n    tempLoc.X = A->Location.X + 1;\n    tempLoc.Width = A->Location.Width - 2;\n\n    if(count_level_edges && !WorldEditor && tempLoc.Y > level[curSection].Height)\n        inferred_type.has_2 = true;\n    else for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n    {\n        if(A != B && !s_check_hidden(B) && CheckCollision(tempLoc, B->Location))\n        {\n            // count only the top of a sizable as a collision\n            if(s_check_sizable(B) && A->Location.Y >= B->Location.Y)\n                continue;\n\n            bool hit = false;\n            for(ItemType_t t : family.types)\n            {\n                if(t.type == B->Type && (check_level != LEVEL_GROUP || t.group == inferred_type.group))\n                {\n                    if(t.has_8 || !advanced_mode)\n                    {\n                        if((t.has_7 || t.has_9) && !t.has_2 && t.slope > inferred_type.slope)\n                            inferred_type.slope = t.slope;\n\n                        hit = true;\n                        break;\n                    }\n                }\n            }\n\n            if(hit || (check_level == LEVEL_ALL && !s_ban_slope(B, 2)))\n            {\n                inferred_type.has_2 = true;\n                break;\n            }\n        }\n    }\n\n    // 1 (bottom-left)\n    tempLoc.X = A->Location.X - 31;\n    tempLoc.Width = 30;\n\n    if(count_level_edges && !WorldEditor && (tempLoc.X + tempLoc.Width < level[curSection].X || tempLoc.Y > level[curSection].Height))\n        inferred_type.has_1 = true;\n    else for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n    {\n        if(A != B && !s_check_hidden(B) && CheckCollision(tempLoc, B->Location))\n        {\n            // count only the top of a sizable as a collision\n            if(s_check_sizable(B) && A->Location.Y >= B->Location.Y)\n                continue;\n\n            bool hit = false;\n            for(ItemType_t t : family.types)\n            {\n                if(t.type == B->Type && (check_level != LEVEL_GROUP || t.group == inferred_type.group))\n                {\n                    if(t.has_9 || !advanced_mode)\n                    {\n                        hit = true;\n                        break;\n                    }\n                }\n            }\n\n            if(hit || (check_level == LEVEL_ALL && !s_ban_slope(B, 1)))\n            {\n                inferred_type.has_1 = true;\n                break;\n            }\n        }\n    }\n\n    // 4 (left)\n    tempLoc.Y = A->Location.Y + 1;\n    tempLoc.Height = A->Location.Height - 2;\n\n    if(count_level_edges && !WorldEditor && tempLoc.X + tempLoc.Width < level[curSection].X)\n        inferred_type.has_4 = true;\n    else for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n    {\n        if(A != B && !s_check_hidden(B) && CheckCollision(tempLoc, B->Location))\n        {\n            if(s_check_sizable(B))\n                continue;\n\n            bool hit = false;\n            for(ItemType_t t : family.types)\n            {\n                if(t.type == B->Type && (check_level != LEVEL_GROUP || t.group == inferred_type.group))\n                {\n                    if(t.has_6 || !advanced_mode)\n                    {\n                        if((t.has_3 || t.has_9) && !t.has_4 && t.slope > inferred_type.slope)\n                            inferred_type.slope = t.slope;\n\n                        hit = true;\n                        break;\n                    }\n                }\n            }\n\n            if(hit || (check_level == LEVEL_ALL && !s_ban_slope(B, 4)))\n            {\n                inferred_type.has_4 = true;\n                break;\n            }\n        }\n    }\n\n    return s_match_type(family, inferred_type).type;\n}\n\n\ntemplate<class ItemRef_t>\nvoid s_apply_type(ItemRef_t B, int type)\n{\n    B->Type = type;\n}\n\ntemplate<>\nvoid s_apply_type(BlockRef_t B, int type)\n{\n    if(B->Slippy ==\n        (B->Type == 189 || B->Type == 190 || B->Type == 191\n            || B->Type == 270 || B->Type == 271 || B->Type == 272\n            || B->Type == 620 || B->Type == 621 || B->Type == 633\n            || B->Type == 634 || B->Type == 241 || B->Type == 242))\n    {\n        B->Slippy = (type == 189 || type == 190 || type == 191 || type == 270 || type == 271 || type == 272 || type == 620 || type == 621 || type == 633 || type == 634 || type == 241 || type == 242);\n    }\n\n    B->Type = type;\n}\n\ntemplate<class ItemRef_t>\nvoid MagicItem(int Type, Location_t loc)\n{\n    constexpr CrossEffectLevel change_level = LEVEL_FAMILY;\n\n    using ItemInfo = MagicInfo<ItemRef_t>;\n\n    if(Type < 1 || Type > ItemInfo::maxType)\n        return;\n\n    uint8_t family = ItemInfo::family_by_type[Type - 1];\n\n    if(family == FAMILY_NONE)\n        return;\n\n    if(change_level != LEVEL_ALL && ItemInfo::families[family].is_misc)\n        return;\n\n    int group = 0;\n\n    for(const ItemType_t& t : ItemInfo::families[family].types)\n    {\n        if(t.type == Type)\n        {\n            group = t.group;\n            break;\n        }\n    }\n\n    // first, transform all nearby blocks, then transform the block itself\n    Location_t tempLoc = loc;\n    tempLoc.X -= 31;\n    tempLoc.Y -= 31;\n    tempLoc.Width += 62;\n    tempLoc.Height += 62;\n\n    for(int i = 0; i < 2; i++)\n    {\n        for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n        {\n            if(B->Type < 1 || B->Type > ItemInfo::maxType)\n                continue;\n\n            uint8_t family_b = ItemInfo::family_by_type[B->Type - 1];\n\n            if(family_b == FAMILY_NONE)\n                continue;\n\n            if(change_level != LEVEL_ALL && family_b != family && !ItemInfo::families[family_b].behind_mode)\n                continue;\n\n            if(!CheckCollision(tempLoc, B->Location))\n                continue;\n\n            if(change_level >= LEVEL_GROUP)\n            {\n                bool group_match = false;\n                for(const ItemType_t& t : ItemInfo::families[family_b].types)\n                {\n                    if(t.type == B->Type && t.group == group)\n                    {\n                        group_match = true;\n                        break;\n                    }\n                }\n\n                if(!group_match)\n                    continue;\n            }\n\n            int new_type = s_pick_type(ItemInfo::families[family_b], B);\n            if(new_type != 0)\n                s_apply_type(B, new_type);\n        }\n    }\n}\n\ntemplate<class ItemRef_t>\nvoid MagicItem(ItemRef_t A)\n{\n    constexpr CrossEffectLevel change_level = LEVEL_FAMILY;\n\n    using ItemInfo = MagicInfo<ItemRef_t>;\n\n    if((int)A < 1 || (int)A > ItemInfo::numItem)\n        return;\n    if(A->Type < 1 || A->Type > ItemInfo::maxType)\n        return;\n\n    uint8_t family = ItemInfo::family_by_type[A->Type - 1];\n\n    if(family == FAMILY_NONE)\n        return;\n\n    if(change_level != LEVEL_ALL && ItemInfo::families[family].is_misc)\n        return;\n\n    int group = 0;\n\n    for(const ItemType_t& t : ItemInfo::families[family].types)\n    {\n        if(t.type == A->Type)\n        {\n            group = t.group;\n            break;\n        }\n    }\n\n    // first, transform all nearby blocks, then transform the block itself\n    Location_t tempLoc = static_cast<Location_t>(A->Location);\n    tempLoc.X -= 31;\n    tempLoc.Y -= 31;\n    tempLoc.Width += 62;\n    tempLoc.Height += 62;\n\n    for(int i = 0; i < 2; i++)\n    {\n        for(ItemRef_t B : treeQuery<ItemRef_t>(tempLoc, SORTMODE_NONE))\n        {\n            if(B == A)\n                continue;\n\n            if(B->Type < 1 || B->Type > ItemInfo::maxType)\n                continue;\n\n            uint8_t family_b = ItemInfo::family_by_type[B->Type - 1];\n\n            if(family_b == FAMILY_NONE)\n                continue;\n\n            if(change_level != LEVEL_ALL && family_b != family && !ItemInfo::families[family_b].behind_mode)\n                continue;\n\n            if(!CheckCollision(tempLoc, B->Location))\n                continue;\n\n            if(change_level == LEVEL_GROUP)\n            {\n                bool group_match = false;\n                for(const ItemType_t& t : ItemInfo::families[family_b].types)\n                {\n                    if(t.type == B->Type && t.group == group)\n                    {\n                        group_match = true;\n                        break;\n                    }\n                }\n\n                if(!group_match)\n                    continue;\n            }\n\n            int new_type = s_pick_type(ItemInfo::families[family_b], B);\n            if(new_type != 0)\n                s_apply_type(B, new_type);\n        }\n\n        int new_type = s_pick_type(ItemInfo::families[family], A);\n        if(new_type != 0)\n            s_apply_type(A, new_type);\n    }\n}\n\nvoid MagicBlock(int Type, Location_t loc)\n{\n    if(!enabled)\n        return;\n\n    MagicItem<BlockRef_t>(Type, loc);\n}\n\nvoid MagicBlock(BlockRef_t A)\n{\n    if(!enabled)\n        return;\n\n    MagicItem(A);\n}\n\nvoid MagicBackground(int Type, Location_t loc)\n{\n    if(!enabled)\n        return;\n\n    MagicItem<BackgroundRef_t>(Type, loc);\n}\n\nvoid MagicBackground(BackgroundRef_t A)\n{\n    if(!enabled)\n        return;\n\n    MagicItem(A);\n}\n\nvoid MagicTile(int Type, Location_t loc)\n{\n    if(!enabled)\n        return;\n\n    MagicItem<TileRef_t>(Type, loc);\n}\n\nvoid MagicTile(TileRef_t A)\n{\n    if(!enabled)\n        return;\n\n    MagicItem(A);\n}\n\n} // namespace MagicBlock\n"
  },
  {
    "path": "src/editor/magic_block.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef MAGIC_BLOCK_H\n#define MAGIC_BLOCK_H\n\n#include <vector>\n#include <array>\n\n#include \"globals.h\"\n\n\nnamespace MagicBlock\n{\n\nenum CrossEffectLevel\n{\n    LEVEL_ALL,\n    LEVEL_FAMILY,\n    LEVEL_GROUP\n};\n\nextern bool enabled;\nextern bool replace_existing;\n// extern bool advanced_mode;\nextern bool count_level_edges;\n// extern CrossEffectLevel check_level;\n// extern CrossEffectLevel change_level;\n\nvoid MagicBlock(BlockRef_t A);\nvoid MagicBlock(int Type, Location_t loc);\n\nvoid MagicBackground(BackgroundRef_t A);\nvoid MagicBackground(int Type, Location_t loc);\n\nvoid MagicTile(TileRef_t A);\nvoid MagicTile(int Type, Location_t loc);\n\n} // namespace MagicBlock\n\n#endif // MAGIC_BLOCK_H\n"
  },
  {
    "path": "src/editor/new_editor.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <sorting/tinysort.h>\n\n#include <PGE_File_Formats/file_formats.h>\n#include <Logger/logger.h>\n#include <fmt_format_ne.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include <Integrator/integrator.h>\n#include <fontman/font_manager.h>\n#include \"Utils/files.h\"\n\n#include \"core/render.h\"\n\n#include \"../globals.h\"\n#include \"../gfx.h\"\n#include \"../layers.h\"\n#include \"../graphics.h\"\n#include \"../sound.h\"\n#include \"../main/level_file.h\"\n#include \"../main/world_file.h\"\n#include \"../game_main.h\"\n#include \"main/game_globals.h\"\n#include \"main/world_globals.h\"\n\n#include \"config.h\"\n#include \"npc.h\"\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n#include \"npc_special_data.h\"\n#include \"controls.h\"\n\n#include \"editor.h\"\n#include \"editor/new_editor.h\"\n#include \"editor/write_level.h\"\n#include \"editor/write_world.h\"\n\n#include \"editor/magic_block.h\"\n#include \"editor/editor_custom.h\"\n#include \"editor/editor_strings.h\"\n\n#include \"main/menu_main.h\"\n#include \"main/screen_textentry.h\"\n#include \"main/game_info.h\"\n#include \"main/translate.h\"\n\n\ntemplate<typename... Args>\ninline void SuperPrintR(EditorScreen::CallMode mode, Args... args)\n{\n    if(mode == EditorScreen::CallMode::Render)\n        SuperPrint(args...);\n}\n\ntemplate<typename... Args>\ninline void SuperPrintRightR(EditorScreen::CallMode mode, Args... args)\n{\n    if(mode == EditorScreen::CallMode::Render)\n        SuperPrintRightAlign(args...);\n}\n\ntemplate<typename... Args>\ninline void SuperPrintCenterR(EditorScreen::CallMode mode, Args... args)\n{\n    if(mode == EditorScreen::CallMode::Render)\n        SuperPrintCenter(args...);\n}\n\ninline PGE_Size FontManager_printTextOptiPxR(EditorScreen::CallMode mode,\n                                             std::string text,\n                                             int x, int y,\n                                             size_t max_pixels_lenght,\n                                             int font = FontManager::DefaultRaster,\n                                             XTColor color = XTColor(),\n                                             uint32_t ttf_FontSize = 14)\n{\n    if(mode == EditorScreen::CallMode::Render)\n        return FontManager::printTextOptiPx(text, x, y,\n                                            max_pixels_lenght,\n                                            font, color, ttf_FontSize);\n    else\n        return FontManager::optimizeTextPx(text, max_pixels_lenght, font, ttf_FontSize);\n}\n\nconstexpr auto LESet_Nothing = EventSection_t::LESet_Nothing;\nconstexpr auto LESet_ResetDefault = EventSection_t::LESet_ResetDefault;\n\nconstexpr int e_ScreenW = 640;\nconstexpr int e_ScreenH = 480;\n\nstatic const std::vector<std::string> c_musicFormats =\n{\n    // MPEG 1 Layer III (LibMAD)\n    \".mp3\",\n\n    // OGG Vorbis, FLAC, amd Opus (LibOGG, LibVorbis, LibFLAC, libOpus)\n    \".ogg\", \".flac\", \".opus\",\n\n    // Uncompressed audio data\n    \".wav\", \".aiff\",\n\n    // MIDI\n    \".mid\", \".midi\", \".rmi\", \".mus\", \".kar\", \".xmi\", \".cmf\",\n\n    // Id Music File (OPL2 raw) / Imago Orpheus (Tracker music)\n    \".imf\", \".wlf\",\n\n    // Modules (Tracker music)\n    \".mod\", \".it\", \".s3m\", \".669\", \".med\", \".xm\", \".amf\",\n    \".apun\", \".dsm\", \".far\", \".gdm\", \".mtm\",\n    \".okt\", \".stm\", \".stx\", \".ult\", \".uni\", \".mptm\",\n\n    // GAME EMU (Chiptunes)\n    \".ay\", \".gbs\", \".gym\", \".hes\", \".kss\", \".nsf\",\n    \".nsfe\", \".sap\", \".spc\", \".vgm\", \".vgz\",\n\n    // PXTONE\n    \".pttune\", \".ptcop\"\n};\n\nint e_CursorX, e_CursorY;\n\nvoid DisableCursorNew()\n{\n    EditorCursor.Location.X = vScreen[1].X - 800;\n    EditorCursor.X = (int)EditorCursor.Location.X;\n    EditorCursor.Location.Y = vScreen[1].Y - 600;\n    EditorCursor.Y = (int)EditorCursor.Location.Y;\n    HasCursor = false;\n\n    e_CursorX = -50;\n    e_CursorY = -50;\n}\n\nstatic numf_t s_divide_by_ten(int n)\n{\n    return numf_t(n) / 10;\n}\n\nstatic void s_fix_mouse_pos()\n{\n    MouseMove((int)SharedCursor.X, (int)SharedCursor.Y);\n}\n\n// static const std::vector<std::string> list_backgrounds_names = {\"None\", \"Set 1\", \"Underground\", \"Night\", \"Night 2\", \"Overworld\", \"Castle\", \"Mushrooms\", \"Desert\", \"\", \"Set 2\", \"Trees\", \"Underground\", \"Castle\", \"Clouds\", \"Night - Hills\", \"Night - Desert\", \"Cliff\", \"Warehouse\", \"Dungeon\", \"Set 3\", \"Blocks\", \"Hills\", \"Dungeon\", \"Pipes\", \"Bonus\", \"Clouds\", \"Desert\", \"Dungeon 2\", \"Ship\", \"Forest\", \"Battle\", \"Waterfall\", \"Tanks\", \"Final Boss\", \"Shroom Dealer\", \"Castle\", \"Snow Trees\", \"Clouds 2\", \"Snow Hills\", \"Cave\", \"Cave 2\", \"Underwater\", \"World\", \"Trees\", \"Mansion\", \"Forest\", \"Bonus\", \"Night\", \"Cave\", \"Clouds\", \"Hills\", \"Hills 2\", \"Hills 4\", \"Hills 3\", \"Castle\", \"Castle 2\", \"Underwater\", \"Desert Night\", \"\", \"Misc.\", \"Space Base\", \"Space Ship\", \"Space Swamp\", \"Space Crater\", \"Secret Mine\"};\n// static const std::vector<int16_t> list_backgrounds_indices = {0, -1, 7, 8, 9, 10, 41, 50, 51, -1, -1, 5, 25, 44, 48, 49, 52, 53, 54, 57, -1, 1, 2, 3, 4, 6, 13, 14, 15, 17, 20, 21, 22, 23, 24, 26, 27, 35, 36, 37, 38, 39, 56, -1, 12, 18, 19, 28, 29, 30, 31, 11, 32, 33, 34, 42, 43, 55, 58, -1, -1, 47, 46, 45, 16, 40};\n\n// static const std::vector<std::string> list_music_names = {\"None\", \"Custom\", \"Set 1\", \"Overworld\", \"Underground\", \"Dungeon\", \"Water\", \"Set 2\", \"Overworld\", \"Underground\", \"Boss\", \"Final Boss\", \"Set 3\", \"Overworld\", \"Sky\", \"Underground\", \"Dungeon\", \"Water\", \"Roaming Enemy\", \"Boss\", \"World\", \"Overworld\", \"Mansion\", \"Sky\", \"Cave\", \"Dungeon\", \"Water\", \"Boss\", \"RPG\", \"Bachelor Pad\", \"Town\", \"Forest\", \"Seaside\", \"Pond\", \"Clouds\", \"Battle\", \"64\", \"Main Theme\", \"Cave\", \"Snow\", \"Desert\", \"Water\", \"Castle\", \"Boss\", \"Fight\", \"Knight\", \"Underground\", \"Temple\", \"Steampunk\", \"Pinball\", \"Space\", \"Red Swamp\", \"Crater\", \"Item Room\", \"Final Boss\", \"Remake\", \"Misc.\", \"Jungle Village\", \"Ice Mountain\", \"Title Theme\", \"Beach\", \"Fusion Reactor\", \"Bouncy Race\", \"Remake\", \"Heroic Woods\", \"Cornered!\"};\n// static const std::vector<int16_t> list_music_indices = {0, 24, -1, 9, 7, 42, 46, -1, 5, 25, 15, 43, -1, 1, 2, 4, 3, 47, 54, 6, -1, 10, 17, 28, 29, 41, 48, 51, -1, 30, 34, 16, 31, 32, 33, 21, -1, 27, 50, 35, 14, 49, 26, 36, -1, 40, 52, 39, 19, 53, -1, 11, 12, 44, 45, 22, -1, 38, 37, 55, 18, 20, 56, 13, 23, 8};\n\n// static const std::vector<std::string> list_world_music_names = {\"None\", \"SET 3\", \"World 1\", \"World 2\", \"World 3\", \"World 4\", \"World 5\", \"World 6\", \"World 7\", \"World 8\", \"WORLD\", \"Theme\", \"Cave\", \"Island\", \"Forest\", \"Dungeon\", \"Sky\", \"Special\", \"NEW\", \"Theme\"};\n// static const std::vector<int16_t> list_world_music_indices = {0, -1, 1, 6, 8, 2, 11, 10, 3, 9, -1, 4, 16, 15, 7, 13, 14, 12, -1, 5};\n\n// static const std::array<std::string, 10> list_level_exit_names = {\"ANY\", \"NONE\", \"ROULETTE\", \"? ORB\", \"LEAVE\", \"KEYHOLE\", \"CLEAR ORB\", \"WARP\", \"STAR\", \"BAR END\"};\n\nconst char* e_tooltip = nullptr;\n\nvoid EditorScreen::EnsureWorld()\n{\n    if(WorldEditor) return;\n    ClearLevel();\n    WorldEditor = true;\n}\n\nvoid EditorScreen::EnsureLevel()\n{\n    if(!WorldEditor) return;\n    ClearWorld();\n    WorldEditor = false;\n}\n\nvoid EditorScreen::ResetCursor()\n{\n    EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n    EditorCursor.InteractMode = 0;\n    EditorCursor.InteractFlags = 0;\n    EditorCursor.InteractIndex = 0;\n\n    EditorCursor.Block = Block_t();\n    EditorCursor.Block.Type = 1;\n    EditorCursor.Background = Background_t();\n    EditorCursor.Background.Type = 1;\n    EditorCursor.Background.SetSortPriority(0, 0);\n    EditorCursor.NPC = NPC_t();\n    EditorCursor.NPC.Type = NPCID(1);\n    EditorCursor.NPC.Direction = -1;\n    EditorCursor.Water = Water_t();\n    EditorCursor.Warp = Warp_t();\n    EditorCursor.Warp.WarpNPC = true;\n    EditorCursor.Warp.Direction = 3;\n    EditorCursor.Warp.Direction2 = 3;\n    EditorCursor.Warp.MapX = -1;\n    EditorCursor.Warp.MapY = -1;\n    EditorCursor.Warp.Effect = 1;\n\n    EditorCursor.Tile = Tile_t();\n    EditorCursor.Tile.Type = 1;\n    EditorCursor.Scene = Scene_t();\n    EditorCursor.Scene.Type = 1;\n    EditorCursor.WorldLevel = WorldLevel_t();\n    EditorCursor.WorldLevel.Type = 1;\n    EditorCursor.WorldLevel.WarpX = -1;\n    EditorCursor.WorldLevel.WarpY = -1;\n    EditorCursor.WorldPath = WorldPath_t();\n    EditorCursor.WorldPath.Type = 1;\n    EditorCursor.WorldMusic = WorldMusic_t();\n\n    m_special_page = SPECIAL_PAGE_NONE;\n    m_special_subpage = 0;\n    m_NPC_page = 0;\n    m_Block_page = 0;\n    m_BGO_page = 0;\n    m_Warp_page = WARP_PAGE_MAIN;\n    m_layers_page = 0;\n    m_events_page = 0;\n    m_sounds_page = 0;\n    m_tile_page = 0;\n    m_music_page = 0;\n    m_background_page = 0;\n    m_current_event = 0;\n\n    MagicBlock::enabled = false;\n\n    if(testStartWarp > numWarps && LevelEditor)\n        testStartWarp = numWarps;\n\n    FocusNPC();\n    FocusBlock();\n    FocusBGO();\n    FocusTile();\n}\n\nbool AllowBubble()\n{\n    int type;\n\n    if(NPCIsContainer(EditorCursor.NPC))\n        type = EditorCursor.NPC.Special;\n    else\n        type = EditorCursor.NPC.Type;\n\n    if(type == NPCID_BOMB)\n        return true;\n\n    if(NPCHeight(type) > 36 || NPCWidth(type) > 36\n        || NPCWidthGFX(type) > 36 || NPCHeightGFX(type) > 36)\n    {\n        int W = NPCWidth(type);\n        int H = NPCHeight(type);\n\n        if(NPCWidthGFX(type) > W)\n            W = NPCWidthGFX(type);\n\n        if(NPCHeightGFX(type) > H)\n            H = NPCHeightGFX(type);\n\n        if((W <= 32 && H <= 54) || (H <= 32 && W <= 54))\n            return true;\n        else\n            return false;\n    }\n    else\n        return true;\n}\n\nvoid SetEditorNPCType(NPCID type)\n{\n    NPCID prev_type;\n\n    if(NPCIsContainer(EditorCursor.NPC))\n    {\n        prev_type = NPCID(EditorCursor.NPC.Special);\n        EditorCursor.NPC.Special = type;\n    }\n    else\n    {\n        prev_type = EditorCursor.NPC.Type;\n        EditorCursor.NPC.Type = type;\n\n        // can't have a murderous default, reset to 1 for ParaTroopas\n        if(NPCIsAParaTroopa(type) && !NPCIsAParaTroopa(prev_type))\n            EditorCursor.NPC.Special = 1;\n\n        // reset special for NPCs that don't allow it\n        if(!(NPCTraits[type].IsFish || NPCIsAParaTroopa(type) || type == NPCID_FIRE_CHAIN))\n            EditorCursor.NPC.Special = 0;\n\n        // reset special if it's out of range\n        if(!(type == NPCID_FIRE_CHAIN) && EditorCursor.NPC.Special > 5)\n            EditorCursor.NPC.Special = 0;\n    }\n\n    if(NPCBansWings(EditorCursor.NPC))\n    {\n        EditorCursor.NPC.Wings = WING_NONE;\n        EditorCursor.NPC.DefaultWings = WING_NONE;\n    }\n\n    // reset legacy for the NPCs that don't allow it\n    if(!(type == NPCID_MINIBOSS || type == NPCID_SPIT_BOSS || type == NPCID_VILLAIN_S3))\n        EditorCursor.NPC.Legacy = false;\n\n    // reset Variant data across different NPCs\n    if(find_Variant_Data(prev_type) != find_Variant_Data(type))\n    {\n        if(FileFormat == FileFormats::LVL_PGEX)\n            EditorCursor.NPC.Variant = find_modern_Variant(type);\n        else\n            EditorCursor.NPC.Variant = 0;\n    }\n\n    // reset Variant data for NPCs that don't support it\n    if(find_Variant_Data(type) == nullptr && !(type == NPCID_STAR_COLLECT || type == NPCID_STAR_EXIT || type == NPCID_MEDAL || type == NPCID_MAGIC_DOOR || type == NPCID_DOOR_MAKER))\n        EditorCursor.NPC.Variant = 0;\n\n    // turn into new type if can't be in bubble anymore\n    if(EditorCursor.NPC.Type == NPCID_ITEM_BUBBLE && !AllowBubble())\n    {\n        EditorCursor.NPC.Type = type;\n        EditorCursor.NPC.Special = 0;\n    }\n\n    // force a direction if they don't allow neutral direction (conveyers, moving platform blocks)\n    if(EditorCursor.NPC.Direction == 0 && (type == 57 || type == 60 || type == 62 || type == 64 || type == 66))\n        EditorCursor.NPC.Direction = -1;\n\n    ResetNPC(type);\n}\n\nvoid SetEditorBlockType(int type)\n{\n    if(BlockIsSizable[type])\n    {\n        if(EditorCursor.Block.Location.Width < 64)\n            EditorCursor.Block.Location.Width = 64;\n        if(EditorCursor.Block.Location.Height < 64)\n            EditorCursor.Block.Location.Height = 64;\n    }\n    else\n    {\n        EditorCursor.Block.Location.Width = 0;\n        EditorCursor.Block.Location.Height = 0;\n    }\n\n    if(type == 5 || type == 88 || type == 193 || type == 224)\n    {\n        if(EditorCursor.Block.Special == 0)\n            EditorCursor.Block.Special = 1;\n    }\n    else if(type != 60 && type != 188 && type != 4 && type != 226 && type != 55 && type != 159 && type != 226\n        && type != 55 && type != 90 && type != 170 && type != 171 && type != 172 && type != 173\n        && type != 174 && type != 175 && type != 176 && type != 177 && type != 178 && type != 179 && type != 180\n        && type != 181 && type != 622 && type != 623 && type != 624 && type != 625 && type != 626 && type != 627\n        && type != 628 && type != 629 && type != 631 && type != 632)\n    {\n        EditorCursor.Block.Special = 0;\n    }\n\n    // only update slipperiness if it is currently what you would expect.\n    if(EditorCursor.Block.Slippy ==\n        (EditorCursor.Block.Type == 189 || EditorCursor.Block.Type == 190 || EditorCursor.Block.Type == 191\n            || EditorCursor.Block.Type == 270 || EditorCursor.Block.Type == 271 || EditorCursor.Block.Type == 272\n            || EditorCursor.Block.Type == 620 || EditorCursor.Block.Type == 621 || EditorCursor.Block.Type == 633\n            || EditorCursor.Block.Type == 634 || EditorCursor.Block.Type == 241 || EditorCursor.Block.Type == 242))\n    {\n        EditorCursor.Block.Slippy = (type == 189 || type == 190 || type == 191 || type == 270 || type == 271 || type == 272 || type == 620 || type == 621 || type == 633 || type == 634 || type == 241 || type == 242);\n    }\n\n    EditorCursor.Block.Type = type;\n}\n\nvoid EditorScreen::FocusNPC()\n{\n    int type;\n    if(m_special_page == SPECIAL_PAGE_BLOCK_CONTENTS)\n        type = EditorCursor.Block.Special - 1000;\n    else if(NPCIsContainer(EditorCursor.NPC))\n        type = EditorCursor.NPC.Special;\n    else\n        type = EditorCursor.NPC.Type;\n\n    if(m_special_page == SPECIAL_PAGE_BLOCK_CONTENTS && (type == 10 || type == 9 || type == 90 || type == 14 || type == 264 || type == 34 || type == 169 || type == 170 || type == 226 || type == 287 || type == 33 || type == 185 || type == 187 || type == 183 || type == 188 || type == 277 || type == 95 || type == 31 || type == 227 || type == 88 || type == 184 || type == 186 || type == 182 || type == 153 || type == 138 || type == 249 || type == 134 || type == 241 || type == 240 || type == 152 || type == 250 || type == 254 || type == 251 || type == 252 || type == 253))\n        m_NPC_page = -1;\n    else if(!EditorCustom::npc_families.empty() && type >= 1 && type <= maxNPCType && EditorCustom::npc_family_by_type[type - 1] != EditorCustom::FAMILY_NONE)\n        m_NPC_page = EditorCustom::npc_families[EditorCustom::npc_family_by_type[type - 1]].page;\n    else if(m_special_page == SPECIAL_PAGE_BLOCK_CONTENTS)\n        m_NPC_page = -1;\n    else\n        m_NPC_page = 0;\n}\n\nvoid EditorScreen::FocusBlock()\n{\n    int type = EditorCursor.Block.Type;\n    if(!EditorCustom::block_families.empty() && type >= 1 && type <= maxBlockType && EditorCustom::block_family_by_type[type - 1] != EditorCustom::FAMILY_NONE)\n        m_Block_page = EditorCustom::block_families[EditorCustom::block_family_by_type[type - 1]].page;\n    else\n        m_Block_page = 0;\n}\n\nvoid EditorScreen::FocusBGO()\n{\n    int type = EditorCursor.Background.Type;\n    if(!EditorCustom::bgo_families.empty() && type >= 1 && type <= maxBackgroundType && EditorCustom::bgo_family_by_type[type - 1] != EditorCustom::FAMILY_NONE)\n        m_BGO_page = EditorCustom::bgo_families[EditorCustom::bgo_family_by_type[type - 1]].page;\n    else\n        m_BGO_page = 0;\n}\n\nvoid EditorScreen::FocusTile()\n{\n    int type = EditorCursor.Tile.Type;\n    if(!EditorCustom::tile_families.empty() && type >= 1 && type <= maxTileType && EditorCustom::tile_family_by_type[type - 1] != EditorCustom::FAMILY_NONE)\n        m_tile_page = EditorCustom::tile_families[EditorCustom::tile_family_by_type[type - 1]].page;\n    else\n        m_tile_page = 0;\n}\n\nbool EditorScreen::UpdateButton(CallMode mode, int x, int y, StdPicture &im, bool sel,\n    int src_x, int src_y, int src_w, int src_h, const char* tooltip)\n{\n    // the button is 32x32 and outlined by a 36x36 box\n    bool coll = false;\n    if(e_CursorX >= x && e_CursorX < x + 32\n        && e_CursorY >= y && e_CursorY < y + 32)\n    {\n        coll = true;\n    }\n\n    // just do the simple logic if in logic mode\n    if(mode == CallMode::Logic)\n    {\n        bool ret = (MenuMouseRelease && coll);\n        if(ret)\n            PlaySound(SFX_Saw);\n        return ret;\n    }\n\n    // otherwise, fully render!\n    if(coll && tooltip)\n        e_tooltip = tooltip;\n\n    // outline:\n    if(sel)\n    {\n        if(coll && SharedCursor.Primary)\n            XRender::renderRect(x - 2, y - 2, 36, 36, XTColorF(0.0_n, 0.5_n, 0.0_n, 1.0_n), true);\n        else\n            XRender::renderRect(x - 2, y - 2, 36, 36, XTColorF(0.0_n, 1.0_n, 0.0_n, 1.0_n), true);\n    }\n    else if(coll && SharedCursor.Primary)\n        XRender::renderRect(x - 2, y - 2, 36, 36, XTColorF(0.0_n, 0.0_n, 0.0_n, 1.0_n), true);\n    else\n        XRender::renderRect(x - 2, y - 2, 36, 36, XTColorF(1.0_n, 1.0_n, 1.0_n, 0.5_n), true);\n\n    // background:\n    if(SharedCursor.Primary && coll)\n        XRender::renderRect(x, y, 32, 32, XTColorF(0.2_n, 0.2_n, 0.2_n), true);\n    else\n        XRender::renderRect(x, y, 32, 32, XTColorF(0.5_n, 0.5_n, 0.5_n), true);\n\n    // scale and center image\n    int dst_x, dst_y, dst_h, dst_w;\n    if(src_w > 32 && src_w >= src_h)\n    {\n        dst_h = (src_h * 32) / src_w;\n        dst_w = 32;\n    }\n    else if(src_h > 32 && src_h > src_w)\n    {\n        dst_w = (src_w * 32) / src_h;\n        dst_h = 32;\n    }\n    else\n    {\n        dst_w = src_w;\n        dst_h = src_h;\n    }\n    dst_x = x + 16 - dst_w / 2;\n    dst_y = y + 16 - dst_h / 2;\n\n    if(dst_w > src_w)\n        D_pLogCriticalNA(\"Editor upscaling image (should never happen)\");\n\n\n    XRender::renderTextureScaleEx(dst_x, dst_y, dst_w, dst_h, im, src_x, src_y, src_w, src_h);\n    return false;\n}\n\nbool EditorScreen::UpdateCheckBox(CallMode mode, int x, int y, bool sel, const char* tooltip)\n{\n    if(sel)\n        return this->UpdateButton(mode, x, y, GFX.EIcons, sel, 0, 32*Icon::check, 32, 32, tooltip);\n    else\n        return this->UpdateButton(mode, x, y, GFX.EIcons, sel, 0, 0, 1, 1, tooltip);\n}\n\nbool EditorScreen::UpdateNPCButton(CallMode mode, int x, int y, NPCID type, bool sel)\n{\n    int draw_width, draw_height;\n    if(NPCWidthGFX(type) == 0)\n    {\n        draw_width = NPCWidth(type);\n        draw_height = NPCHeight(type);\n    }\n    else\n    {\n        draw_width = NPCWidthGFX(type);\n        draw_height = NPCHeightGFX(type);\n    }\n\n    return UpdateButton(mode, x, y, GFXNPC[type], sel, 0, 0, draw_width, draw_height);\n}\n\nvoid EditorScreen::UpdateNPC(CallMode mode, int x, int y, NPCID type)\n{\n    if((type < 1) || (type > maxNPCType))\n    {\n        pLogWarning(\"Attempted to render NPC type %d, but the max NPC type is %d!\", type, maxNPCType);\n        return;\n    }\n    if(m_special_page != SPECIAL_PAGE_BLOCK_CONTENTS)\n    {\n        bool sel = (EditorCursor.NPC.Type == type ||\n            (EditorCursor.NPC.Special == type && NPCIsContainer(EditorCursor.NPC)));\n\n        if(UpdateNPCButton(mode, x, y, type, sel) && !sel)\n            SetEditorNPCType(type);\n    }\n    else\n    {\n        bool sel = (EditorCursor.Block.Special == type + 1000);\n        if(UpdateNPCButton(mode, x, y, type, sel) && !sel)\n            EditorCursor.Block.Special = type + 1000;\n    }\n}\n\nvoid EditorScreen::UpdateNPCGrid(CallMode mode, int x, int y, const int* types, int n_npcs, int n_cols)\n{\n    for(int i = 0; i < n_npcs; i ++)\n    {\n        int type = types[i];\n        if(!type)\n            continue;\n\n        int row = i / n_cols;\n        int col = i % n_cols;\n        UpdateNPC(mode, x + col * 40 + 4, y + row * 40 + 4, NPCID(type));\n    }\n}\n\nvoid EditorScreen::UpdateNPCScreen(CallMode mode)\n{\n    // NPC GUI\n    if(mode == CallMode::Render)\n    {\n        XRender::renderRect(e_ScreenW - 200, 40, 200, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n        XRender::renderRect(0, 40, 40, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n        XRender::renderRect(38, 40, 2, e_ScreenH - 40, XTColorF(0.25_n, 0.0_n, 0.5_n), true);\n    }\n\n    if(m_special_page == SPECIAL_PAGE_BLOCK_CONTENTS && UpdateButton(mode, e_ScreenW - 40 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n    {\n        m_special_page = SPECIAL_PAGE_NONE;\n    }\n\n    // Page selector\n    int last_category = -1;\n    int index = 0;\n\n    for(const EditorCustom::ItemPage_t& page : EditorCustom::npc_pages)\n    {\n        if(page.category != last_category)\n        {\n            last_category = page.category;\n\n            if(mode == CallMode::Render && index != 0)\n                XRender::renderRect(0, 40 + -2 + (40 * index), 40, 4, XTColorF(0.25_n, 0.0_n, 0.5_n), true);\n        }\n\n        index++;\n\n        if(UpdateNPCButton(mode, 4, 4 + (40 * index), NPCID(page.icon), m_NPC_page == index))\n            m_NPC_page = index;\n    }\n\n    if(m_special_page == SPECIAL_PAGE_BLOCK_CONTENTS && mode == CallMode::Render)\n        XRender::renderRect(0, (40 * index) + 40 + -2, 40, 4, XTColorF(0.25_n, 0.0_n, 0.5_n), true);\n\n    if(m_special_page == SPECIAL_PAGE_BLOCK_CONTENTS && UpdateNPCButton(mode, 4, 4 + (40 * index) + 40, NPCID_COIN_S3, m_NPC_page == -1))\n        m_NPC_page = -1;\n\n\n    if(m_special_page == SPECIAL_PAGE_BLOCK_CONTENTS && mode == CallMode::Render)\n    {\n        FontManager::printTextOptiPx(g_editorStrings.pickBlockContents,\n                                     e_ScreenW - 200, 90,\n                                     230,\n                                     FontManager::fontIdFromSmbxFont(3));\n//        SuperPrint(g_editorStrings.pickBlockContents1, 3, e_ScreenW - 200, 90);\n//        SuperPrint(g_editorStrings.pickBlockContents2, 3, e_ScreenW - 200, 110);\n    }\n\n    if(m_special_page != SPECIAL_PAGE_BLOCK_CONTENTS)\n    {\n        // Containers\n        SuperPrintCenterR(mode, g_editorStrings.npcInContainer, 3, e_ScreenW - 20, 40);\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 60 + 4, GFXNPC[NPCID_ITEM_BURIED], EditorCursor.NPC.Type == NPCID_ITEM_BURIED, 0, 0, NPCWidth(NPCID_ITEM_BURIED), NPCHeight(NPCID_ITEM_BURIED)))\n        {\n            if(EditorCursor.NPC.Type == NPCID_ITEM_BURIED)\n            {\n                EditorCursor.NPC.Type = NPCID(EditorCursor.NPC.Special);\n                EditorCursor.NPC.Special = 0;\n            }\n            else if(!NPCIsContainer(EditorCursor.NPC))\n            {\n                EditorCursor.NPC.Special = EditorCursor.NPC.Type;\n                EditorCursor.NPC.Type = NPCID_ITEM_BURIED;\n            }\n            else\n            {\n                EditorCursor.NPC.Type = NPCID_ITEM_BURIED;\n            }\n        }\n\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 100 + 4, GFXNPC[NPCID_ITEM_POD], EditorCursor.NPC.Type == NPCID_ITEM_POD))\n        {\n            if(EditorCursor.NPC.Type == NPCID_ITEM_POD)\n            {\n                EditorCursor.NPC.Type = NPCID(EditorCursor.NPC.Special);\n                EditorCursor.NPC.Special = 0;\n            }\n            else if(!NPCIsContainer(EditorCursor.NPC))\n            {\n                EditorCursor.NPC.Special = EditorCursor.NPC.Type;\n                EditorCursor.NPC.Type = NPCID_ITEM_POD;\n            }\n            else\n            {\n                EditorCursor.NPC.Type = NPCID_ITEM_POD;\n            }\n        }\n\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 140 + 4, GFXNPC[NPCID_ITEM_THROWER], EditorCursor.NPC.Type == NPCID_ITEM_THROWER, 0, 0, NPCWidthGFX(NPCID_ITEM_THROWER), NPCHeightGFX(NPCID_ITEM_THROWER)))\n        {\n            if(EditorCursor.NPC.Type == NPCID_ITEM_THROWER)\n            {\n                EditorCursor.NPC.Type = NPCID(EditorCursor.NPC.Special);\n                EditorCursor.NPC.Special = 0;\n            }\n            else if(!NPCIsContainer(EditorCursor.NPC))\n            {\n                EditorCursor.NPC.Special = EditorCursor.NPC.Type;\n                EditorCursor.NPC.Type = NPCID_ITEM_THROWER;\n            }\n            else\n            {\n                EditorCursor.NPC.Type = NPCID_ITEM_THROWER;\n            }\n        }\n\n        if(FileFormat == FileFormats::LVL_PGEX && UpdateButton(mode, e_ScreenW - 40 + 4, 180 + 4, GFXNPC[NPCID_SPIT_BOSS], (EditorCursor.NPC.Type == NPCID_SPIT_BOSS && EditorCursor.NPC.Special), 0, 0, NPCWidthGFX(NPCID_SPIT_BOSS), NPCHeightGFX(NPCID_SPIT_BOSS)))\n        {\n            if(EditorCursor.NPC.Type == NPCID_SPIT_BOSS && EditorCursor.NPC.Special)\n            {\n                EditorCursor.NPC.Type = NPCID(EditorCursor.NPC.Special);\n                EditorCursor.NPC.Special = 0;\n            }\n            else if(!NPCIsContainer(EditorCursor.NPC))\n            {\n                EditorCursor.NPC.Special = EditorCursor.NPC.Type;\n                EditorCursor.NPC.Type = NPCID_SPIT_BOSS;\n            }\n            else\n            {\n                EditorCursor.NPC.Type = NPCID_SPIT_BOSS;\n            }\n        }\n\n        if(AllowBubble())\n        {\n            if(UpdateButton(mode, e_ScreenW - 40 + 4, ((FileFormat == FileFormats::LVL_PGEX) ? 220 : 180) + 4, GFXNPC[NPCID_ITEM_BUBBLE], EditorCursor.NPC.Type == NPCID_ITEM_BUBBLE, 0, 0, NPCWidthGFX(NPCID_ITEM_BUBBLE), NPCHeightGFX(NPCID_ITEM_BUBBLE)))\n            {\n                if(EditorCursor.NPC.Type == NPCID_ITEM_BUBBLE)\n                {\n                    EditorCursor.NPC.Type = NPCID(EditorCursor.NPC.Special);\n                    EditorCursor.NPC.Special = 0;\n                }\n                else if(!NPCIsContainer(EditorCursor.NPC))\n                {\n                    EditorCursor.NPC.Special = EditorCursor.NPC.Type;\n                    EditorCursor.NPC.Type = NPCID_ITEM_BUBBLE;\n                }\n                else\n                {\n                    EditorCursor.NPC.Type = NPCID_ITEM_BUBBLE;\n                }\n            }\n        }\n\n        // Various properties that depend on type, get the real one here:\n        NPCID type;\n        if(NPCIsContainer(EditorCursor.NPC))\n        {\n            type = (NPCID)EditorCursor.NPC.Special;\n        }\n        else\n        {\n            type = EditorCursor.NPC.Type;\n        }\n\n        // Direction\n        Icon::Icons dir_neg_icon = Icon::left;\n        Icon::Icons dir_pos_icon = Icon::right;\n\n        bool show_random = true;\n        if(type == NPCID_CONVEYOR || type == NPCID_YEL_PLATFORM || type == NPCID_BLU_PLATFORM || type == NPCID_GRN_PLATFORM || type == NPCID_RED_PLATFORM)\n            show_random = false;\n\n        // \"direction\" means different things for these types\n        if(mode == CallMode::Render)\n        {\n            if(type == NPCID_YEL_PLATFORM || type == NPCID_BLU_PLATFORM || type == NPCID_GRN_PLATFORM || type == NPCID_RED_PLATFORM || type == NPCID_PLATFORM_S3)\n            {\n                SuperPrintCenter(g_editorStrings.npcPropertyActive, 3, e_ScreenW - 120, 40);\n                dir_neg_icon = Icon::x;\n                dir_pos_icon = Icon::check;\n            }\n            else if(type == NPCID_FIRE_DISK || type == NPCID_FIRE_CHAIN)\n            {\n                SuperPrintCenter(g_editorStrings.npcPropertyAttachSurface, 3, e_ScreenW - 120, 40);\n                dir_neg_icon = Icon::bottom;\n                dir_pos_icon = Icon::top;\n            }\n            else\n            {\n                SuperPrintCenter(g_editorStrings.npcPropertyFacing, 3, e_ScreenW - 120, 40);\n                if(type == NPCID_PLATFORM_S1 || (NPCIsAParaTroopa(type) && EditorCursor.NPC.Special == 3))\n                {\n                    dir_neg_icon = Icon::up;\n                    dir_pos_icon = Icon::down;\n                }\n            }\n        }\n\n        // show and apply direction\n        if(UpdateButton(mode, e_ScreenW - 180 + 4, 60 + 4, GFX.EIcons, EditorCursor.NPC.Direction == -1, 0, 32 * dir_neg_icon, 32, 32))\n            EditorCursor.NPC.Direction = -1;\n\n        if(show_random && UpdateButton(mode, e_ScreenW - 140 + 4, 60 + 4, GFX.EIcons, EditorCursor.NPC.Direction == 0, 0, 32 * Icon::unk, 32, 32))\n            EditorCursor.NPC.Direction = 0;\n\n        if(UpdateButton(mode, e_ScreenW - 100 + 4, 60 + 4, GFX.EIcons, EditorCursor.NPC.Direction == 1, 0, 32 * dir_pos_icon, 32, 32))\n            EditorCursor.NPC.Direction = 1;\n\n        // Inert (\"nice\") and Stuck (\"stop\")\n        // The sign (NPC ID 151) is always nice.\n        if(type == NPCID_SIGN)\n            EditorCursor.NPC.Inert = true;\n        else\n        {\n            SuperPrintRightR(mode, g_editorStrings.npcInertNice, 3, e_ScreenW - 130, 100);\n            if(UpdateCheckBox(mode, e_ScreenW - 160 + 4, 120 + 4, EditorCursor.NPC.Inert))\n                EditorCursor.NPC.Inert = !EditorCursor.NPC.Inert;\n        }\n\n        SuperPrintR(mode, g_editorStrings.npcStuckStop, 3, e_ScreenW - 110, 100);\n        if(UpdateCheckBox(mode, e_ScreenW - 120 + 4, 120 + 4, EditorCursor.NPC.Stuck))\n            EditorCursor.NPC.Stuck = !EditorCursor.NPC.Stuck;\n\n        // Text\n        if(EditorCursor.NPC.Inert)\n        {\n            MessageText = GetS(EditorCursor.NPC.Text);\n            PrepareMessageDims();\n            SuperPrintRightR(mode, g_editorStrings.wordText, 3, e_ScreenW - 130, 160);\n            if(UpdateButton(mode, e_ScreenW - 160 + 4, 180 + 4, GFX.EIcons, EditorCursor.NPC.Text != STRINGINDEX_NONE, 0, 32*Icon::pencil, 32, 32))\n            {\n                DisableCursorNew();\n\n                std::string&& prompt = fmt::format_ne(g_editorStrings.phraseTextOf, g_editorStrings.wordNPCGenitive);\n                SetS(EditorCursor.NPC.Text, TextEntryScreen::Run(prompt, GetS(EditorCursor.NPC.Text)));\n\n                s_fix_mouse_pos();\n            }\n        }\n\n        // Wings\n        if(FileFormat == FileFormats::LVL_PGEX && !NPCBansWings(EditorCursor.NPC))\n        {\n            int icon = 32;\n\n            if(EditorCursor.NPC.Wings == WING_JUMP)\n                icon = Icon::hop;\n            else if(EditorCursor.NPC.Wings == WING_LEFTRIGHT)\n                icon = Icon::lr;\n            else if(EditorCursor.NPC.Wings == WING_UPDOWN)\n                icon = Icon::ud;\n            else if(EditorCursor.NPC.Wings == WING_CHASE)\n                icon = Icon::target;\n            else if(EditorCursor.NPC.Wings == WING_FLEE)\n                icon = Icon::hide;\n\n            if(UpdateButton(mode, e_ScreenW - 200 + 4, 180 + 4, GFX.EIcons, EditorCursor.NPC.DefaultWings != WING_NONE, 0, 32 * icon, 32, 32))\n            {\n                if(EditorCursor.NPC.DefaultWings >= WING_FLEE)\n                    EditorCursor.NPC.DefaultWings = WING_NONE;\n                else if(EditorCursor.NPC.DefaultWings >= WING_CHASE)\n                    EditorCursor.NPC.DefaultWings = WING_FLEE;\n                else\n                    EditorCursor.NPC.DefaultWings = (WingBehaviors)((int)EditorCursor.NPC.DefaultWings + 1);\n\n                EditorCursor.NPC.Wings = EditorCursor.NPC.DefaultWings;\n            }\n\n            if(mode == CallMode::Render)\n            {\n                int sz = (icon == 32) ? 32 : 16;\n                XRender::renderTextureScaleEx(e_ScreenW - 200 + 4, 180 + 4, sz, sz, GFX.YoshiWings, 0, 0, 32, 32);\n            }\n        }\n\n        // Generator\n        SuperPrintR(mode, g_editorStrings.npcAbbrevGen, 3, e_ScreenW - 110, 160);\n        if(UpdateButton(mode, e_ScreenW - 120 + 4, 180 + 4, GFX.EIcons, EditorCursor.NPC.Generator, 0, 32*Icon::subscreen, 32, 32))\n            m_NPC_page = -2;\n\n        // GFX slot\n        if(FileFormat == FileFormats::LVL_PGEX)\n        {\n            int GFX_type = (EditorCursor.NPC.Type == NPCID_ITEM_BUBBLE) ? EditorCursor.NPC.Special : (int)EditorCursor.NPC.Type;\n            int gw = NPCTraits[GFX_type].WidthGFX;\n            int gh = NPCTraits[GFX_type].HeightGFX;\n            if(gw == 0)\n            {\n                gw = NPCTraits[GFX_type].TWidth;\n                gh = NPCTraits[GFX_type].THeight;\n            }\n\n            int avail_slots = GFXNPC[GFX_type].w / gw;\n\n            if(avail_slots > 1 && UpdateButton(mode, e_ScreenW - 80 + 4, 180 + 4, GFXNPC[GFX_type], false, gw * EditorCursor.NPC.GFXSlot, 0, gw, gh))\n                EditorCursor.NPC.GFXSlot++;\n\n            if(EditorCursor.NPC.GFXSlot >= avail_slots)\n                EditorCursor.NPC.GFXSlot = 0;\n        }\n\n        // Behavior\n        if(NPCIsAParaTroopa(EditorCursor.NPC))\n        {\n            // Describe current AI if valid\n            if(mode == CallMode::Render)\n            {\n                std::string ai_invalid;\n                const std::string* ai_name = &ai_invalid;\n\n                int index = EditorCursor.NPC.Special;\n\n                if(index >= 0 && index < 4)\n                {\n                    const std::string* map[] = {\n                        &g_editorStrings.npcAiTarget,\n                        &g_editorStrings.npcAiJump,\n                        &g_editorStrings.npcAiLR,\n                        &g_editorStrings.npcAiUD,\n                    };\n                    ai_name = map[index];\n                }\n\n                std::string&& ai_is = fmt::format_ne(g_editorStrings.npcAiIs, *ai_name);\n\n                SuperPrint(ai_is, 3, e_ScreenW - 200, 220);\n            }\n\n            if(UpdateButton(mode, e_ScreenW - 200 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Special == 1, 0, 32*Icon::hop, 32, 32))\n                EditorCursor.NPC.Special = 1;\n\n            if(UpdateButton(mode, e_ScreenW - 160 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Special == 0, 0, 32*Icon::target, 32, 32))\n                EditorCursor.NPC.Special = 0;\n\n            if(UpdateButton(mode, e_ScreenW - 120 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Special == 2, 0, 32*Icon::lr, 32, 32))\n                EditorCursor.NPC.Special = 2;\n\n            if(UpdateButton(mode, e_ScreenW - 80 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Special == 3, 0, 32*Icon::ud, 32, 32))\n                EditorCursor.NPC.Special = 3;\n        }\n\n        if(EditorCursor.NPC->IsFish)\n        {\n            // Describe current AI if valid\n            if(mode == CallMode::Render)\n            {\n                std::string ai_invalid;\n                const std::string* ai_name = &ai_invalid;\n\n                int index = EditorCursor.NPC.Special;\n\n                if(index >= 0 && index < 5)\n                {\n                    const std::string* map[] =\n                    {\n                        &g_editorStrings.npcAiSwim,\n                        &g_editorStrings.npcAiJump,\n                        &g_editorStrings.npcAiLeap,\n                        &g_editorStrings.npcAiLR,\n                        &g_editorStrings.npcAiUD,\n                    };\n                    ai_name = map[index];\n                }\n\n                std::string&& ai_is = fmt::format_ne(g_editorStrings.npcAiIs, *ai_name);\n\n                SuperPrint(ai_is, 3, e_ScreenW - 200, 220);\n            }\n\n            if(UpdateButton(mode, e_ScreenW - 200 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Special == 0, 0, 32*Icon::wave, 32, 32))\n                EditorCursor.NPC.Special = 0;\n\n            if(UpdateButton(mode, e_ScreenW - 160 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Special == 1, 0, 32*Icon::hop, 32, 32))\n                EditorCursor.NPC.Special = 1;\n\n            if(UpdateButton(mode, e_ScreenW - 120 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Special == 2, 0, 32*Icon::leap, 32, 32))\n                EditorCursor.NPC.Special = 2;\n\n            if(UpdateButton(mode, e_ScreenW - 80 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Special == 3, 0, 32*Icon::lr, 32, 32))\n                EditorCursor.NPC.Special = 3;\n\n            if(UpdateButton(mode, e_ScreenW - 40 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Special == 4, 0, 32*Icon::ud, 32, 32))\n                EditorCursor.NPC.Special = 4;\n        }\n\n        if(type == NPCID_MINIBOSS || type == NPCID_SPIT_BOSS || type == NPCID_VILLAIN_S3)\n        {\n            SuperPrintR(mode, g_editorStrings.npcUse1_0Ai, 3, e_ScreenW - 200, 220);\n            if(UpdateButton(mode, e_ScreenW - 200 + 4, 240 + 4, GFX.EIcons, EditorCursor.NPC.Legacy, 0, 32*Icon::_10, 32, 32))\n                EditorCursor.NPC.Legacy = true;\n\n            if(UpdateButton(mode, e_ScreenW - 160 + 4, 240 + 4, GFX.EIcons, !EditorCursor.NPC.Legacy, 0, 32*Icon::_1x, 32, 32))\n                EditorCursor.NPC.Legacy = false;\n        }\n\n        if(EditorCursor.NPC.Type == NPCID_FIRE_CHAIN)\n        {\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.phraseRadiusIndex, EditorCursor.NPC.Special), 3, e_ScreenW - 200, 220);\n\n            if(EditorCursor.NPC.Special > 0 && UpdateButton(mode, e_ScreenW - 160 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n                EditorCursor.NPC.Special --;\n\n            if(UpdateButton(mode, e_ScreenW - 120 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n                EditorCursor.NPC.Special ++;\n        }\n\n        bool is_magic_door = (EditorCursor.NPC.Type == NPCID_MAGIC_DOOR || EditorCursor.NPC.Type == NPCID_DOOR_MAKER\n            || (EditorCursor.NPC.Type == NPCID_ITEM_BURIED && EditorCursor.NPC.Special == NPCID_DOOR_MAKER));\n\n        bool is_multistar = ((type == NPCID_STAR_COLLECT || type == NPCID_STAR_EXIT || type == NPCID_MEDAL) && FileFormat == FileFormats::LVL_PGEX);\n\n        // multistars and magic door destination\n        if(is_magic_door || is_multistar)\n        {\n            if(is_magic_door)\n            {\n                std::string&& dest_section = fmt::format_ne(g_editorStrings.phraseSectionIndex, EditorCursor.NPC.Variant + 1);\n                SuperPrintR(mode, dest_section, 3, e_ScreenW - 200, 220);\n            }\n            else\n            {\n                std::string&& star_index = EditorCursor.NPC.Variant ? fmt::format_ne(g_editorStrings.phraseGenericIndex, (int)(EditorCursor.NPC.Variant)) : g_editorStrings.fileFormatLegacy;\n                SuperPrintCenterR(mode, star_index, 3, e_ScreenW - 120, 220);\n            }\n\n            uint8_t max_index = (type == NPCID_MEDAL) ? 8 : 20;\n\n            if(EditorCursor.NPC.Variant > 0 && UpdateButton(mode, e_ScreenW - 160 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n                EditorCursor.NPC.Variant --;\n\n            if(EditorCursor.NPC.Variant < max_index && UpdateButton(mode, e_ScreenW - 120 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n                EditorCursor.NPC.Variant ++;\n        }\n\n        const NPC_Variant_Data_t* data = find_Variant_Data(EditorCursor.NPC.Type);\n\n        // special case for NPCID_VILLAIN_S3 since it also has the Legacy button\n        if(EditorCursor.NPC.Type == NPCID_VILLAIN_S3 && FileFormat == FileFormats::LVL_PGEX)\n        {\n            if(UpdateButton(mode, e_ScreenW - 40 + 4, 240 + 4, GFXBlock[4], EditorCursor.NPC.Variant == 1,\n                0, 32 * BlockFrame[4], 32, 32, g_editorStrings.npcTooltipExpandSection.c_str()))\n            {\n                if(EditorCursor.NPC.Variant == 0)\n                    EditorCursor.NPC.Variant = 1;\n                else\n                    EditorCursor.NPC.Variant = 0;\n            }\n        }\n        else if(data && FileFormat == FileFormats::LVL_PGEX)\n        {\n            int i;\n            bool valid;\n\n            i = data->find_current(EditorCursor.NPC.Variant);\n            valid = data->strings[i] != nullptr;\n\n            if(mode == CallMode::Render)\n            {\n                SuperPrint(g_editorStrings.npcCustomAi, 3, e_ScreenW - 180, 220);\n                if(valid)\n                    SuperPrint(data->strings[i], 3, e_ScreenW - 160, 242);\n                else\n                    SuperPrint(std::to_string(EditorCursor.NPC.Variant), 3, e_ScreenW - 160, 242);\n            }\n\n            // only show it if it will (i) reset, or (ii) have something to go to.\n            // short-circuit evaluation keeps this from accessing outside of the valid range\n            bool show_prev_button = (!valid || i != 0);\n            bool show_next_button = (!valid || data->strings[i + 1]);\n            if(show_prev_button && UpdateButton(mode, e_ScreenW - 200 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            {\n                if(!valid)\n                    EditorCursor.NPC.Variant = data->values[0];\n                else\n                    EditorCursor.NPC.Variant = data->values[i-1];\n            }\n\n            if(show_next_button && UpdateButton(mode, e_ScreenW - 40 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            {\n                if(!valid)\n                    EditorCursor.NPC.Variant = data->values[0];\n                else\n                    EditorCursor.NPC.Variant = data->values[i + 1];\n            }\n        }\n\n        // Events\n        if(mode == CallMode::Render)\n        {\n            SuperPrintRightAlign(g_editorStrings.eventsHeader, 3, e_ScreenW - 40, 294);\n            SuperPrint(g_editorStrings.eventsLetterActivate   + GetE(EditorCursor.NPC.TriggerActivate), 3, e_ScreenW - 200, 320);\n            SuperPrint(g_editorStrings.eventsLetterDeath      + GetE(EditorCursor.NPC.TriggerDeath), 3, e_ScreenW - 200, 340);\n            SuperPrint(g_editorStrings.eventsLetterTalk       + GetE(EditorCursor.NPC.TriggerTalk), 3, e_ScreenW - 200, 360);\n            SuperPrint(g_editorStrings.eventsLetterLayerClear + GetE(EditorCursor.NPC.TriggerLast), 3, e_ScreenW - 200, 380);\n        }\n\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 280 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n            m_special_page = SPECIAL_PAGE_OBJ_TRIGGERS;\n\n        // Layers\n        if(mode == CallMode::Render)\n        {\n            SuperPrintRightAlign(g_editorStrings.labelLayer, 3, e_ScreenW - 40, 414);\n            if(EditorCursor.NPC.Layer == LAYER_NONE)\n                SuperPrint(g_editorStrings.layersLayerDefault, 3, e_ScreenW - 200, 440);\n            else\n                SuperPrint(GetL(EditorCursor.NPC.Layer), 3, e_ScreenW - 200, 440);\n            if(EditorCursor.NPC.AttLayer != LAYER_NONE && EditorCursor.NPC.AttLayer != LAYER_DEFAULT)\n                SuperPrint(g_editorStrings.layersAbbrevAttLayer + GetL(EditorCursor.NPC.AttLayer), 3, e_ScreenW - 200, 460);\n        }\n\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 400 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n            m_special_page = SPECIAL_PAGE_OBJ_LAYER;\n    }\n\n\n    // NPC selector\n    if(!EditorCustom::npc_pages.empty() && m_NPC_page > 0 && m_NPC_page <= (int)EditorCustom::npc_pages.size())\n    {\n        const EditorCustom::ItemPage_t& page = EditorCustom::npc_pages[m_NPC_page - 1];\n\n        for(auto it = page.begin; it != page.end; ++it)\n        {\n            const EditorCustom::ItemFamily& family = **it;\n\n            if(mode == CallMode::Render)\n            {\n                int pix_len = SuperTextPixLen(family.name, 3);\n\n                if(40 + family.X * 40 + pix_len > e_ScreenW - 200)\n                    SuperPrint(family.name, 3, e_ScreenW - 200 - 4 - pix_len, 40 + family.Y * 20);\n                else\n                    SuperPrint(family.name, 3, 40 + family.X * 40 + 4, 40 + family.Y * 20);\n            }\n\n            UpdateNPCGrid(mode, 40 + family.X * 40, 60 + family.Y * 20, family.layout_pod.types.data(), family.layout_pod.types.size(), family.layout_pod.cols);\n        }\n    }\n\n\n    // GENERATOR SETTINGS SCREEN\n    if(m_NPC_page == -2 && m_special_page != SPECIAL_PAGE_BLOCK_CONTENTS)\n    {\n        SuperPrintR(mode, g_editorStrings.npcGenHeader, 3, 50, 44);\n\n        if(UpdateButton(mode, e_ScreenW - 200 - 40 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n            FocusNPC();\n\n        SuperPrintR(mode, g_editorStrings.wordEnabled, 3, 50, 110);\n\n        if(UpdateCheckBox(mode, 280 + 4, 100 + 4, EditorCursor.NPC.Generator))\n        {\n            EditorCursor.NPC.Generator = true;\n\n            // Set GeneratorDirection and GeneratorEffect both to 1\n            if(!EditorCursor.NPC.Special3)\n                EditorCursor.NPC.Special3 = 0x0101;\n\n            // GeneratorTimeMax\n            if(EditorCursor.NPC.GeneratorTimeMax() < 1)\n                EditorCursor.NPC.GeneratorTimeMax() = 1;\n        }\n\n        if(UpdateButton(mode, 320 + 4, 100 + 4, GFX.EIcons, !EditorCursor.NPC.Generator, 0, 32*Icon::x, 32, 32))\n            EditorCursor.NPC.Generator = false;\n\n        if(EditorCursor.NPC.Generator)\n        {\n            // direction change\n            uint8_t GeneratorDirection = EditorCursor.NPC.GeneratorDirection();\n            SuperPrintR(mode, g_editorStrings.npcGenDirection, 3, 50, 150);\n\n            if(UpdateButton(mode, 280 + 4, 140 + 4, GFX.EIcons, GeneratorDirection == 1, 0, 32*Icon::up, 32, 32))\n                GeneratorDirection = 1;\n\n            if(UpdateButton(mode, 320 + 4, 140 + 4, GFX.EIcons, GeneratorDirection == 3, 0, 32*Icon::down, 32, 32))\n                GeneratorDirection = 3;\n\n            if(UpdateButton(mode, 360 + 4, 140 + 4, GFX.EIcons, GeneratorDirection == 2, 0, 32*Icon::left, 32, 32))\n                GeneratorDirection = 2;\n\n            if(UpdateButton(mode, 400 + 4, 140 + 4, GFX.EIcons, GeneratorDirection == 4, 0, 32*Icon::right, 32, 32))\n                GeneratorDirection = 4;\n\n            // effect change\n            uint8_t GeneratorEffect = EditorCursor.NPC.GeneratorEffect();\n            const std::string& effect =\n                   (GeneratorEffect == 1) ? g_editorStrings.npcGenEffectWarp\n                : ((GeneratorEffect == 2) ? g_editorStrings.npcGenEffectShoot\n                : g_mainMenu.caseNone);\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.npcGenEffectIs, effect), 3, 50, 190);\n\n            if(UpdateButton(mode, 280 + 4, 180 + 4, GFX.EIcons, GeneratorEffect == 1, 0, 32*Icon::bottom, 32, 32))\n                GeneratorEffect = 1;\n\n            if(UpdateButton(mode, 320 + 4, 180 + 4, GFX.EIcons, GeneratorEffect == 2, 0, 32*Icon::up, 32, 32))\n                GeneratorEffect = 2;\n\n            // assign back to updated internal representation in Special3\n            EditorCursor.NPC.Special3 = ((uint16_t)GeneratorDirection << 8) + (uint8_t)GeneratorEffect;\n\n            // delay change\n            auto& GeneratorTimeMax = EditorCursor.NPC.GeneratorTimeMax();\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.phraseDelayIsMs, GeneratorTimeMax * 100), 3, 50, 230);\n\n            if(GeneratorTimeMax > 1 && UpdateButton(mode, 280 + 4, 220 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n                GeneratorTimeMax --;\n\n            if(UpdateButton(mode, 320 + 4, 220 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n                GeneratorTimeMax ++;\n        }\n    }\n\n    // COMMON CONTENTS\n    if(m_NPC_page == -1 && m_special_page == SPECIAL_PAGE_BLOCK_CONTENTS)\n    {\n        SuperPrintR(mode, g_editorStrings.wordCoins, 3, 110, 40);\n\n        bool currently_coins = EditorCursor.Block.Special > 0 && EditorCursor.Block.Special < 100;\n        if(UpdateButton(mode, 100 + 4, 60 + 4, GFXNPC[NPCID_COIN_S3], currently_coins, 0, 0, 32, 32) && !currently_coins)\n        {\n            EditorCursor.Block.Special = 1;\n            currently_coins = true;\n        }\n\n        if(currently_coins)\n        {\n            SuperPrint(\"x\" + std::to_string(EditorCursor.Block.Special), 3, 140, 74);\n            if(EditorCursor.Block.Special > 0 && UpdateButton(mode, 220 + 4, 60 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n                EditorCursor.Block.Special --;\n            if(EditorCursor.Block.Special < 99 && UpdateButton(mode, 260 + 4, 60 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n                EditorCursor.Block.Special ++;\n        }\n\n        if(FileFormat == FileFormats::LVL_PGEX)\n        {\n            if(UpdateButton(mode, 60 + 4, 60 + 4, (CommonFrame & 32) ? GFXNPC[NPCID_INVINCIBILITY_POWER] : GFXNPC[NPCID_COIN_S3], EditorCursor.Block.Special == 110, 0, 0, 32, 32))\n                EditorCursor.Block.Special = 110;\n        }\n\n        SuperPrintR(mode, g_mainMenu.caseNone, 3, 40 + 260, 40);\n        if(UpdateButton(mode, 360 + 4, 60 + 4, GFXBlock[2], EditorCursor.Block.Special == 0, 0, 0, 32, 32))\n            EditorCursor.Block.Special = 0;\n\n        SuperPrintR(mode, \"3\", 3, 40 + 10, 140);\n        static const int p7_common[] = {NPCID_COIN_S3, NPCID_POWER_S3, NPCID_LIFE_S3, NPCID_FIRE_POWER_S3, NPCID_ICE_POWER_S3, NPCID_LEAF_POWER, NPCID_STATUE_POWER, NPCID_HEAVY_POWER, NPCID_GRN_VINE_TOP_S3, NPCID_RANDOM_POWER};\n        UpdateNPCGrid(mode, 40, 160, p7_common, sizeof(p7_common)/sizeof(int), 10);\n\n        SuperPrintR(mode, \"4\", 3, 40 + 10, 200);\n        static const int p7_smw[] = {NPCID_COIN_S4, NPCID_POWER_S4, NPCID_LIFE_S4, NPCID_FIRE_POWER_S4, NPCID_3_LIFE, NPCID_ICE_POWER_S4, NPCID_PET_GREEN, NPCID_KEY, NPCID_GRN_VINE_TOP_S4};\n        UpdateNPCGrid(mode, 40, 220, p7_smw, sizeof(p7_smw)/sizeof(int), 10);\n\n        SuperPrintR(mode, \"1\", 3, 40 + 10, 260);\n        static const int p7_smb1[] = {NPCID_COIN_S1, NPCID_POWER_S1, NPCID_LIFE_S1, NPCID_FIRE_POWER_S1, NPCID_POISON};\n        UpdateNPCGrid(mode, 40, 280, p7_smb1, sizeof(p7_smb1)/sizeof(int), 5);\n\n        SuperPrintR(mode, \"2\", 3, 40 + 10, 320);\n        static const int p7_smb2[] = {NPCID_COIN_S2, NPCID_POWER_S2, NPCID_BOMB, NPCID_EARTHQUAKE_BLOCK, NPCID_TIMER_S2};\n        UpdateNPCGrid(mode, 40, 340, p7_smb2, sizeof(p7_smb2)/sizeof(int), 5);\n\n        SuperPrintR(mode, \"X\", 3, 40 + 10, 380);\n        static const int p7_misc[] = {NPCID_RING, NPCID_POWER_S5, NPCID_FLY_POWER, NPCID_GEM_1, NPCID_GEM_5, NPCID_GEM_20, NPCID_INVINCIBILITY_POWER, NPCID_AQUATIC_POWER, NPCID_POLAR_POWER, NPCID_CYCLONE_POWER, NPCID_SHELL_POWER};\n        UpdateNPCGrid(mode, 40, 400, p7_misc, sizeof(p7_misc)/sizeof(int) - (FileFormat != FileFormats::LVL_PGEX) * 5, 10);\n    }\n}\n\n#if 0\nvoid EditorScreen::UpdateMagicBlockScreen(CallMode mode)\n{\n    SuperPrintR(mode, \"MAGIC BLOCK (DEPRECATED EXTRA SETTINGS)\", 3, 160, 50);\n\n    if(UpdateButton(mode, e_ScreenW - 40 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n        m_special_page = SPECIAL_PAGE_EDITOR_SETTINGS;\n\n    SuperPrintR(mode, \"Enable\", 3, 90, 94);\n    if(UpdateCheckBox(mode, 40 + 4, 80 + 4, MagicBlock::enabled))\n        MagicBlock::enabled = !MagicBlock::enabled;\n\n    SuperPrintR(mode, \"Overwrite existing items\", 3, 90, 134);\n    if(UpdateCheckBox(mode, 40 + 4, 120 + 4, MagicBlock::replace_existing))\n        MagicBlock::replace_existing = !MagicBlock::replace_existing;\n\n\n    if(MagicBlock::enabled)\n    {\n        SuperPrintR(mode, \"Check level edges\", 3, 90, 174);\n        if(UpdateCheckBox(mode, 40 + 4, 160 + 4, MagicBlock::count_level_edges))\n            MagicBlock::count_level_edges = !MagicBlock::count_level_edges;\n\n\n        SuperPrintR(mode, \"ADVANCED\", 3, 280, 240);\n\n\n        SuperPrintR(mode, \"Allow inner edges\", 3, 90, 274);\n        if(UpdateCheckBox(mode, 40 + 4, 260 + 4, MagicBlock::advanced_mode))\n            MagicBlock::advanced_mode = !MagicBlock::advanced_mode;\n\n\n        SuperPrintR(mode, \"Check\", 3, 160, 320);\n\n        SuperPrintR(mode, \"All\", 3, 90, 354);\n        if(UpdateCheckBox(mode, 40 + 4, 340 + 4, MagicBlock::check_level == MagicBlock::LEVEL_ALL))\n            MagicBlock::check_level = MagicBlock::LEVEL_ALL;\n\n        SuperPrintR(mode, \"Family\", 3, 90, 394);\n        if(UpdateCheckBox(mode, 40 + 4, 380 + 4, MagicBlock::check_level == MagicBlock::LEVEL_FAMILY))\n            MagicBlock::check_level = MagicBlock::LEVEL_FAMILY;\n\n        SuperPrintR(mode, \"Subgroup\", 3, 90, 434);\n        if(UpdateCheckBox(mode, 40 + 4, 420 + 4, MagicBlock::check_level == MagicBlock::LEVEL_GROUP))\n            MagicBlock::check_level = MagicBlock::LEVEL_GROUP;\n\n        SuperPrintR(mode, \"Change\", 3, 460, 320);\n\n        SuperPrintR(mode, \"All\", 3, 320 + 90, 354);\n        if(UpdateCheckBox(mode, 320 + 40 + 4, 340 + 4, MagicBlock::change_level == MagicBlock::LEVEL_ALL))\n            MagicBlock::change_level = MagicBlock::LEVEL_ALL;\n\n        SuperPrintR(mode, \"Family\", 3, 320 + 90, 394);\n        if(UpdateCheckBox(mode, 320 + 40 + 4, 380 + 4, MagicBlock::change_level == MagicBlock::LEVEL_FAMILY))\n            MagicBlock::change_level = MagicBlock::LEVEL_FAMILY;\n\n        SuperPrintR(mode, \"Subgroup\", 3, 320 + 90, 434);\n        if(UpdateCheckBox(mode, 320 + 40 + 4, 420 + 4, MagicBlock::change_level == MagicBlock::LEVEL_GROUP))\n            MagicBlock::change_level = MagicBlock::LEVEL_GROUP;\n    }\n\n    return;\n}\n#endif\n\nvoid EditorScreen::UpdateEventsScreen(CallMode mode)\n{\n    if(m_special_page == SPECIAL_PAGE_EVENT_DELETION)\n    {\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.eventsDeletingEvent, Events[m_current_event].Name), 3, 60, 40);\n        SuperPrintR(mode, g_editorStrings.phraseAreYouSure, 3, 10, 60);\n        SuperPrintR(mode, g_editorStrings.eventsDeletionConfirm, 3, 60, 110);\n\n        if(UpdateButton(mode, 20 + 4, 100 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            DeleteEvent((eventindex_t)m_current_event);\n            m_special_page = SPECIAL_PAGE_EVENTS;\n            m_current_event = 0;\n        }\n\n        SuperPrintR(mode, g_editorStrings.eventsDeletionCancel, 3, 60, 150);\n        if(UpdateButton(mode, 20 + 4, 140 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_EVENTS;\n            m_current_event = 0;\n        }\n        return;\n    }\n\n    // render general GUI\n    SuperPrintR(mode, g_editorStrings.eventsHeader, 3, 60, 40);\n    int page_max = numEvents / 10;\n    SuperPrintR(mode, fmt::format_ne(g_editorStrings.pageBlankOfBlank, m_events_page + 1, page_max + 1), 3, e_ScreenW - 330, 40);\n\n    if(m_events_page > 0 && UpdateButton(mode, e_ScreenW - 120 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        m_events_page --;\n\n    if(m_events_page < page_max && UpdateButton(mode, e_ScreenW - 80 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        m_events_page ++;\n\n    // render event selector\n    for(int i = 0; i < 10; i++)\n    {\n        int e = m_events_page*10 + i;\n        auto &eName = Events[e].Name;\n        if(!eName.empty())\n        {\n            if(eName.length() < 20)\n                SuperPrintR(mode, eName, 3, 10, 80 + 40*i + 10);\n            else\n            {\n                SuperPrintR(mode, eName.substr(0,19), 3, 10, 80 + 40*i + 2);\n                SuperPrintR(mode, eName.substr(19), 3, 10, 80 + 40*i + 20);\n            }\n\n            if(UpdateButton(mode, 360 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::page, 32, 32))\n            {\n                m_special_page = SPECIAL_PAGE_EVENT_SETTINGS;\n                m_current_event = e;\n            }\n\n            // rename, shift up, shift down, delete\n\n            if(e <= 2)\n                continue;\n\n            // rename\n            if(UpdateButton(mode, 400 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n            {\n                DisableCursorNew();\n                std::string new_name = TextEntryScreen::Run(g_editorStrings.eventsPromptEventName, Events[e].Name);\n                if(!new_name.empty())\n                    RenameEvent((eventindex_t)e, new_name);\n                s_fix_mouse_pos();\n            }\n\n            // shift up\n            if(e > 3 && UpdateButton(mode, 440 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::up, 32, 32))\n                SwapEvents(e-1, e);\n\n            // shift down\n            if(e < numEvents - 1 && UpdateButton(mode, 480 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::down, 32, 32))\n                SwapEvents(e, e+1);\n\n            // delete\n            if(e < numEvents && UpdateButton(mode, 520 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n            {\n                m_special_page = SPECIAL_PAGE_EVENT_DELETION;\n                m_current_event = e;\n                return;\n            }\n        }\n        // create a new event!\n        else if(e != 0 && !Events[e - 1].Name.empty())\n        {\n            SuperPrintR(mode, g_editorStrings.eventsItemNewEvent, 3, 54, 80 + 40*i + 10);\n            // rename only\n            if(UpdateButton(mode, 400 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n            {\n                DisableCursorNew();\n                std::string new_name = TextEntryScreen::Run(g_editorStrings.eventsPromptEventName, \"\");\n                s_fix_mouse_pos();\n                if(!new_name.empty() && FindEvent(new_name) == EVENT_NONE)\n                {\n                    InitializeEvent(Events[e]);\n                    Events[e].Name = new_name;\n                    numEvents ++;\n                }\n            }\n        }\n    }\n}\n\nvoid EditorScreen::UpdateEventSettingsScreen(CallMode mode)\n{\n    if(m_special_page == SPECIAL_PAGE_EVENT_CONTROLS)\n    {\n        const int titlePosX = 10;\n        const int titlePosY = 40;\n        PGE_Size titleSize = FontManager_printTextOptiPxR(mode,\n                                                          fmt::format_ne(g_editorStrings.eventsControlsForEvent,\n                                                                         Events[m_current_event].Name),\n                                                          titlePosX, titlePosY,\n                                                          590,\n                                                          FontManager::fontIdFromSmbxFont(3));\n\n        int elementsListBaseY = 80;\n        const int elementsLabelOffset = 10;\n        const int elementsRowHeight = 40;\n        while(titlePosY + titleSize.h() >= elementsListBaseY)\n            elementsListBaseY += elementsRowHeight;\n\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 40 + 4, GFX.EIcons, false, 0, 8 * 32, 32, 32))\n            m_special_page = SPECIAL_PAGE_EVENT_SETTINGS;\n\n        using Controls::PlayerControls::Buttons;\n\n        int yOffset = elementsListBaseY;\n\n        if(UpdateCheckBox(mode, 10 + 4, yOffset + 4, Events[m_current_event].Controls.Up))\n            Events[m_current_event].Controls.Up = !Events[m_current_event].Controls.Up;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Up), 3, 54, yOffset + elementsLabelOffset);\n\n        if(UpdateCheckBox(mode, e_ScreenW / 2 + 10 + 4, yOffset + 4, Events[m_current_event].Controls.Down))\n            Events[m_current_event].Controls.Down = !Events[m_current_event].Controls.Down;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Down), 3, e_ScreenW / 2 + 54, yOffset + elementsLabelOffset);\n\n        yOffset += elementsRowHeight;\n\n        if(UpdateCheckBox(mode, 10 + 4, yOffset + 4, Events[m_current_event].Controls.Left))\n            Events[m_current_event].Controls.Left = !Events[m_current_event].Controls.Left;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Left), 3, 54, yOffset + elementsLabelOffset);\n\n        if(UpdateCheckBox(mode, e_ScreenW / 2 + 10 + 4, yOffset + 4, Events[m_current_event].Controls.Right))\n            Events[m_current_event].Controls.Right = !Events[m_current_event].Controls.Right;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Right), 3, e_ScreenW / 2 + 54, yOffset + elementsLabelOffset);\n\n        yOffset += elementsRowHeight;\n\n        if(UpdateCheckBox(mode, 10 + 4, yOffset + 4, Events[m_current_event].Controls.Jump))\n            Events[m_current_event].Controls.Jump = !Events[m_current_event].Controls.Jump;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Jump), 3, 54, yOffset + elementsLabelOffset);\n\n        if(UpdateCheckBox(mode, e_ScreenW / 2 + 10 + 4, yOffset + 4, Events[m_current_event].Controls.Run))\n            Events[m_current_event].Controls.Run = !Events[m_current_event].Controls.Run;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Run), 3, e_ScreenW / 2 + 54, yOffset + elementsLabelOffset);\n\n        yOffset += elementsRowHeight;\n\n        if(UpdateCheckBox(mode, 10 + 4, yOffset + 4, Events[m_current_event].Controls.AltJump))\n            Events[m_current_event].Controls.AltJump = !Events[m_current_event].Controls.AltJump;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::AltJump), 3, 54, yOffset + elementsLabelOffset);\n\n        if(UpdateCheckBox(mode, e_ScreenW / 2 + 10 + 4, yOffset + 4, Events[m_current_event].Controls.AltRun))\n            Events[m_current_event].Controls.AltRun = !Events[m_current_event].Controls.AltRun;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::AltRun), 3, e_ScreenW / 2 + 54, yOffset + elementsLabelOffset);\n\n        yOffset += elementsRowHeight;\n\n        if(UpdateCheckBox(mode, 10 + 4, yOffset + 4, Events[m_current_event].Controls.Start))\n            Events[m_current_event].Controls.Start = !Events[m_current_event].Controls.Start;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Start), 3, 54, yOffset + elementsLabelOffset);\n\n        if(UpdateCheckBox(mode, e_ScreenW / 2 + 10 + 4, yOffset + 4, Events[m_current_event].Controls.Drop))\n            Events[m_current_event].Controls.Drop = !Events[m_current_event].Controls.Drop;\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Drop), 3, e_ScreenW / 2 + 54, yOffset + elementsLabelOffset);\n\n        return;\n    }\n\n    // SuperPrintR(mode, g_editorStrings.eventsSettingsForEvent, 3, 60, 40);\n    // SuperPrintR(mode, Events[m_current_event].Name, 3, 10, 60);\n    FontManager_printTextOptiPxR(mode,\n                                 fmt::format_ne(g_editorStrings.eventsSettingsForEvent,\n                                                Events[m_current_event].Name),\n                                 60, 40,\n                                 300,\n                                 FontManager::fontIdFromSmbxFont(3));\n\n\n    // RIGHT PANE: layers\n    if(mode == CallMode::Render)\n        XRender::renderRect(e_ScreenW - 240, 40, 240, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n\n    if(UpdateButton(mode, e_ScreenW - 40 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n    {\n        m_special_page = SPECIAL_PAGE_EVENTS;\n        m_current_event = 0;\n    }\n\n    // layers\n    int layer_line = 1;\n    if(!Events[m_current_event].ShowLayer.empty())\n    {\n        layer_line ++;\n        SuperPrintR(mode, g_editorStrings.eventsHeaderShow, 3, e_ScreenW - 200, 40 + (20 * layer_line));\n        layer_line ++;\n        SuperPrintR(mode, GetL(Events[m_current_event].ShowLayer[0]), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n        layer_line ++;\n\n        if(Events[m_current_event].ShowLayer.size() >= 2)\n        {\n            SuperPrintR(mode, GetL(Events[m_current_event].ShowLayer[1]), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n            layer_line ++;\n        }\n\n        if(Events[m_current_event].ShowLayer.size() == 3)\n        {\n            SuperPrintR(mode, GetL(Events[m_current_event].ShowLayer[2]), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n            layer_line ++;\n        }\n        else if(Events[m_current_event].ShowLayer.size() > 3)\n        {\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.phraseCountMore, Events[m_current_event].ShowLayer.size() - 2), 3, e_ScreenW - 220, 40 + (20 * layer_line));\n            layer_line ++;\n        }\n    }\n\n    if(!Events[m_current_event].HideLayer.empty())\n    {\n        layer_line ++;\n        SuperPrintR(mode, g_editorStrings.eventsHeaderHide, 3, e_ScreenW - 200, 40 + (20 * layer_line));\n        layer_line ++;\n        SuperPrintR(mode, GetL(Events[m_current_event].HideLayer[0]), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n        layer_line ++;\n\n        if(Events[m_current_event].HideLayer.size() >= 2)\n        {\n            SuperPrintR(mode, GetL(Events[m_current_event].HideLayer[1]), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n            layer_line ++;\n        }\n\n        if(Events[m_current_event].HideLayer.size() == 3)\n        {\n            SuperPrintR(mode, GetL(Events[m_current_event].HideLayer[2]), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n            layer_line ++;\n        }\n        else if(Events[m_current_event].HideLayer.size() > 3)\n        {\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.phraseCountMore, Events[m_current_event].HideLayer.size() - 2), 3, e_ScreenW - 220, 40 + (20 * layer_line));\n            layer_line ++;\n        }\n    }\n\n    if(!Events[m_current_event].ToggleLayer.empty())\n    {\n        layer_line ++;\n        SuperPrintR(mode, g_editorStrings.eventsHeaderToggle, 3, e_ScreenW - 200, 40 + (20 * layer_line));\n        layer_line ++;\n        SuperPrintR(mode, GetL(Events[m_current_event].ToggleLayer[0]), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n        layer_line ++;\n\n        if(Events[m_current_event].ToggleLayer.size() >= 2)\n        {\n            SuperPrintR(mode, GetL(Events[m_current_event].ToggleLayer[1]), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n            layer_line ++;\n        }\n\n        if(Events[m_current_event].ToggleLayer.size() == 3)\n        {\n            SuperPrintR(mode, GetL(Events[m_current_event].ToggleLayer[2]), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n            layer_line ++;\n        }\n        else if(Events[m_current_event].ToggleLayer.size() > 3)\n        {\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.phraseCountMore, Events[m_current_event].ToggleLayer.size() - 2), 3, e_ScreenW - 220, 40 + (20 * layer_line));\n            layer_line ++;\n        }\n    }\n\n    // MoveLayer is a layerindex_t, not a vector\n    if(Events[m_current_event].MoveLayer != LAYER_NONE)\n    {\n        layer_line ++;\n        SuperPrintR(mode, g_editorStrings.eventsHeaderMove, 3, e_ScreenW - 200, 40 + (20 * layer_line));\n        layer_line ++;\n        SuperPrintR(mode, GetL(Events[m_current_event].MoveLayer), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n        layer_line ++;\n\n        // settings for this...\n        int sy = num_t::round((num_t)Events[m_current_event].SpeedY * 10);\n        int sx = num_t::round((num_t)Events[m_current_event].SpeedX * 10);\n\n        if(sy < 0)\n            SuperPrintR(mode, g_editorStrings.letterUp + std::to_string(-sy), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n        else if(sy > 0)\n            SuperPrintR(mode, g_editorStrings.letterDown + std::to_string(sy), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n        else\n            SuperPrintR(mode, \"-\", 3, e_ScreenW - 240, 40 + (20 * layer_line));\n\n        layer_line ++;\n\n        if(sx < 0)\n            SuperPrintR(mode, g_editorStrings.letterLeft + std::to_string(-sx), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n        else if(sx > 0)\n            SuperPrintR(mode, g_editorStrings.letterRight + std::to_string(sx), 3, e_ScreenW - 240, 40 + (20 * layer_line));\n        else\n            SuperPrintR(mode, \"-\", 3, e_ScreenW - 240, 40 + (20 * layer_line));\n\n        layer_line --;\n\n        if(UpdateButton(mode, e_ScreenW - 160 + 4, 40 + (20 * layer_line) + 4, GFX.EIcons, false, 0, 32*Icon::down, 32, 32))\n            Events[m_current_event].SpeedY = s_divide_by_ten(sy + 1);\n\n        if(UpdateButton(mode, e_ScreenW - 120 + 4, 40 + (20 * layer_line) + 4, GFX.EIcons, false, 0, 32*Icon::up, 32, 32))\n            Events[m_current_event].SpeedY = s_divide_by_ten(sy - 1);\n\n        if(UpdateButton(mode, e_ScreenW - 80 + 4, 40 + (20 * layer_line) + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            Events[m_current_event].SpeedX = s_divide_by_ten(sx - 1);\n\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 40 + (20 * layer_line) + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            Events[m_current_event].SpeedX = s_divide_by_ten(sx + 1);\n\n        layer_line ++;\n    }\n\n    if(layer_line == 1)\n    {\n        SuperPrintR(mode, g_editorStrings.layersHeader, 3, e_ScreenW - 192, 40);\n        SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 40 + (20 * layer_line));\n\n        Events[m_current_event].LayerSmoke = false;\n    }\n    else\n    {\n        SuperPrintR(mode, g_editorStrings.layersHeader, 3, e_ScreenW - 192, 50);\n\n        // layer smoke (bottom of layers pane)\n        SuperPrintR(mode, g_editorStrings.eventsPropLayerSmoke, 3, e_ScreenW - 192, 40 + (20 * layer_line) + 30);\n        if(UpdateCheckBox(mode, e_ScreenW - 240 + 4, 40 + (20 * layer_line) + 24, !Events[m_current_event].LayerSmoke))\n            Events[m_current_event].LayerSmoke = !Events[m_current_event].LayerSmoke;\n    }\n\n    if(UpdateButton(mode, e_ScreenW - 240 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_EVENT_LAYERS;\n\n    // BOTTOM PANE: sections - background, music, autoscroll\n    if(mode == CallMode::Render)\n        XRender::renderRect(0, e_ScreenH - 180, e_ScreenW - 240, 180, XTColorF(0.6_n, 0.6_n, 0.8_n), true);\n\n    if(m_special_subpage > 0 && UpdateButton(mode, 40 + 4, e_ScreenH - 180 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        m_special_subpage --;\n\n    if(m_special_subpage < maxSections + 1 && UpdateButton(mode, 320 + 4, e_ScreenH - 180 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        m_special_subpage ++;\n\n    // description of possibilities\n    SuperPrintCenterR(mode, g_editorStrings.eventsActionKeep, 3, 170, e_ScreenH - 140);\n    SuperPrintCenterR(mode, g_editorStrings.eventsActionReset, 3, 270, e_ScreenH - 140);\n    SuperPrintCenterR(mode, g_editorStrings.eventsActionSet, 3, 370, e_ScreenH - 140);\n\n    SuperPrintR(mode, g_editorStrings.eventsCaseMusic, 3, 4, e_ScreenH - 110);\n    SuperPrintR(mode, g_editorStrings.eventsCaseBackground, 3, 4, e_ScreenH - 70);\n    SuperPrintR(mode, g_editorStrings.eventsCaseBounds, 3, 4, e_ScreenH - 30);\n\n    // subpage - 1 is the internal section ID; subpage 0 is all sections.\n    if(m_special_subpage == 0)\n    {\n        SuperPrintCenterR(mode, g_editorStrings.eventsPhraseAllSections, 3, 190, e_ScreenH - 174);\n\n        // music\n        bool all_keep = true;\n        bool all_reset = true;\n        bool all_set = true;\n        int set_same = -1;\n\n        for(int s = 0; s <= maxSections; s++)\n        {\n            if(Events[m_current_event].section[s].music_id != LESet_Nothing)\n                all_keep = false;\n\n            if(Events[m_current_event].section[s].music_id != LESet_ResetDefault)\n                all_reset = false;\n\n            if(Events[m_current_event].section[s].music_id < 0)\n                all_set = false;\n            else if(set_same == -1)\n                set_same = Events[m_current_event].section[s].music_id;\n            else if(set_same != Events[m_current_event].section[s].music_id)\n                all_set = false;\n        }\n\n        if(UpdateButton(mode, 150 + 4, e_ScreenH - 120 + 4, GFX.EIcons, all_keep, 0, 0, 1, 1) && !all_keep)\n        {\n            for(int s = 0; s <= maxSections; s++)\n                Events[m_current_event].section[s].music_id = LESet_Nothing;\n        }\n\n        if(UpdateButton(mode, 250 + 4, e_ScreenH - 120 + 4, GFX.EIcons, all_reset, 0, 32*Icon::x, 32, 32) && !all_reset)\n        {\n            for(int s = 0; s <= maxSections; s++)\n                Events[m_current_event].section[s].music_id = LESet_ResetDefault;\n        }\n\n        if(UpdateButton(mode, 350 + 4, e_ScreenH - 120 + 4, GFX.EIcons, all_set, 0, 32*Icon::subscreen, 32, 32))\n            m_special_page = SPECIAL_PAGE_EVENT_MUSIC;\n\n        // background\n        all_keep = true;\n        all_reset = true;\n        all_set = true;\n        set_same = -1;\n\n        for(int s = 0; s <= maxSections; s++)\n        {\n            if(Events[m_current_event].section[s].background_id != LESet_Nothing)\n                all_keep = false;\n            if(Events[m_current_event].section[s].background_id != LESet_ResetDefault)\n                all_reset = false;\n            if(Events[m_current_event].section[s].background_id < 0)\n                all_set = false;\n            else if(set_same == -1)\n                set_same = Events[m_current_event].section[s].background_id;\n            else if(set_same != Events[m_current_event].section[s].background_id)\n                all_set = false;\n        }\n\n        if(UpdateButton(mode, 150 + 4, e_ScreenH - 80 + 4, GFX.EIcons, all_keep, 0, 0, 1, 1) && !all_keep)\n        {\n            for(int s = 0; s <= maxSections; s++)\n                Events[m_current_event].section[s].background_id = LESet_Nothing;\n        }\n\n        if(UpdateButton(mode, 250 + 4, e_ScreenH - 80 + 4, GFX.EIcons, all_reset, 0, 32*Icon::x, 32, 32) && !all_reset)\n        {\n            for(int s = 0; s <= maxSections; s++)\n                Events[m_current_event].section[s].background_id = LESet_ResetDefault;\n        }\n\n        if(UpdateButton(mode, 350 + 4, e_ScreenH - 80 + 4, GFX.EIcons, all_set, 0, 32*Icon::subscreen, 32, 32))\n            m_special_page = SPECIAL_PAGE_EVENT_BACKGROUND;\n\n        // bounds\n        all_keep = true;\n        all_reset = true;\n        all_set = true;\n        for(int s = 0; s <= maxSections; s++)\n        {\n            if(Events[m_current_event].section[s].position.X == LESet_Nothing)\n            {\n                all_reset = false;\n                all_set = false;\n            }\n            else if(Events[m_current_event].section[s].position.X == LESet_ResetDefault)\n            {\n                all_set = false;\n                all_keep = false;\n            }\n            else\n            {\n                all_keep = false;\n                all_reset = false;\n            }\n        }\n\n        if(UpdateButton(mode, 150 + 4, e_ScreenH - 40 + 4, GFX.EIcons, all_keep, 0, 0, 1, 1) && !all_keep)\n        {\n            for(int s = 0; s <= maxSections; s++)\n                Events[m_current_event].section[s].position.X = -1;\n        }\n\n        if(UpdateButton(mode, 250 + 4, e_ScreenH - 40 + 4, GFX.EIcons, all_reset, 0, 32*Icon::x, 32, 32) && !all_reset)\n        {\n            for(int s = 0; s <= maxSections; s++)\n                Events[m_current_event].section[s].position.X = -2;\n        }\n    }\n    else\n    {\n        SuperPrintCenterR(mode, fmt::format_ne(g_editorStrings.phraseSectionIndex, m_special_subpage), 3, 190, e_ScreenH - 174);\n\n        auto& es = Events[m_current_event].section[m_special_subpage-1];\n\n        // music\n        if(UpdateButton(mode, 150 + 4, e_ScreenH - 120 + 4, GFX.EIcons, es.music_id == LESet_Nothing, 0, 0, 1, 1))\n            es.music_id = LESet_Nothing;\n\n        if(UpdateButton(mode, 250 + 4, e_ScreenH - 120 + 4, GFX.EIcons, es.music_id == LESet_ResetDefault, 0, 32*Icon::x, 32, 32))\n            es.music_id = LESet_ResetDefault;\n\n        if(UpdateButton(mode, 350 + 4, e_ScreenH - 120 + 4, GFX.EIcons, es.music_id >= 0, 0, 32*Icon::subscreen, 32, 32))\n            m_special_page = SPECIAL_PAGE_EVENT_MUSIC;\n\n        // background\n        if(UpdateButton(mode, 150 + 4, e_ScreenH - 80 + 4, GFX.EIcons, es.background_id == LESet_Nothing, 0, 0, 1, 1))\n            es.background_id = LESet_Nothing;\n\n        if(UpdateButton(mode, 250 + 4, e_ScreenH - 80 + 4, GFX.EIcons, es.background_id == LESet_ResetDefault, 0, 32*Icon::x, 32, 32))\n            es.background_id = LESet_ResetDefault;\n\n        if(UpdateButton(mode, 350 + 4, e_ScreenH - 80 + 4, GFX.EIcons, es.background_id >= 0, 0, 32*Icon::subscreen, 32, 32))\n            m_special_page = SPECIAL_PAGE_EVENT_BACKGROUND;\n\n        // bounds\n        if(UpdateButton(mode, 150 + 4, e_ScreenH - 40 + 4, GFX.EIcons, es.position.X == LESet_Nothing, 0, 0, 1, 1))\n            es.position.X = LESet_Nothing;\n\n        if(UpdateButton(mode, 250 + 4, e_ScreenH - 40 + 4, GFX.EIcons, es.position.X == LESet_ResetDefault, 0, 32*Icon::x, 32, 32))\n            es.position.X = LESet_ResetDefault;\n\n        if(UpdateButton(mode, 350 + 4, e_ScreenH - 40 + 4, GFX.EIcons, es.position.X != LESet_Nothing && es.position.X != LESet_ResetDefault, 0, 32*Icon::subscreen, 32, 32))\n            es.position = LevelREAL[m_special_subpage-1];\n    }\n\n    // autostart (top left)\n    SuperPrintR(mode, g_editorStrings.eventsPropAutostart, 3, 54, 90);\n    if(UpdateCheckBox(mode, 10 + 4, 80 + 4, Events[m_current_event].AutoStart))\n        Events[m_current_event].AutoStart = !Events[m_current_event].AutoStart;\n\n    // sound (top right)\n    SuperPrintR(mode, g_editorStrings.eventsPropSound, 3, 254, 90);\n    if(UpdateButton(mode, 210 + 4, 80 + 4, GFX.EIcons, Events[m_current_event].Sound != 0, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_EVENT_SOUND;\n\n    // end game (mid left)\n    SuperPrintR(mode, g_editorStrings.eventsPropEndGame, 3, 54, 130);\n    if(UpdateCheckBox(mode, 10 + 4, 120 + 4, Events[m_current_event].EndGame == 1))\n        Events[m_current_event].EndGame ^= 1;\n\n    // control lock (mid right)\n    bool controls_set = (Events[m_current_event].Controls.AltJump ||\n        Events[m_current_event].Controls.AltRun ||\n        Events[m_current_event].Controls.Down ||\n        Events[m_current_event].Controls.Drop ||\n        Events[m_current_event].Controls.Jump ||\n        Events[m_current_event].Controls.Left ||\n        Events[m_current_event].Controls.Right ||\n        Events[m_current_event].Controls.Run ||\n        Events[m_current_event].Controls.Start ||\n        Events[m_current_event].Controls.Up);\n    SuperPrintR(mode, g_editorStrings.eventsPropControls, 3, 254, 130);\n    if(UpdateButton(mode, 210 + 4, 120 + 4, GFX.EIcons, controls_set, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_EVENT_CONTROLS;\n\n    // message (bottom left)\n    if(!GetS(Events[m_current_event].Text).empty())\n    {\n        MessageText = GetS(Events[m_current_event].Text);\n        PrepareMessageDims();\n    }\n\n    SuperPrintR(mode, g_editorStrings.wordText, 3, 54, 170);\n    if(UpdateButton(mode, 10 + 4, 160 + 4, GFX.EIcons, !GetS(Events[m_current_event].Text).empty(), 0, 32*Icon::pencil, 32, 32))\n    {\n        DisableCursorNew();\n        SetS(Events[m_current_event].Text, TextEntryScreen::Run(g_editorStrings.eventsPromptEventText, GetS(Events[m_current_event].Text)));\n        s_fix_mouse_pos();\n    }\n\n    // trigger event (full width, below all)\n    SuperPrintR(mode, g_editorStrings.eventsHeaderTriggerEvent, 3, 54, 220);\n    if(UpdateButton(mode, 10 + 4, 220 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_EVENT_TRIGGER;\n\n    if(Events[m_current_event].TriggerEvent != EVENT_NONE)\n    {\n        SuperPrintR(mode, GetE(Events[m_current_event].TriggerEvent).substr(0,19), 3, 54, 240);\n\n        if(Events[m_current_event].TriggerDelay > 0)\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.phraseDelayIsMs, 100 * Events[m_current_event].TriggerDelay), 3, 54, 272);\n        else\n            SuperPrintR(mode, g_editorStrings.wordInstant, 3, 54, 272);\n\n        if(Events[m_current_event].TriggerDelay > 0 && UpdateButton(mode, 10 + 4, 260 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            Events[m_current_event].TriggerDelay --;\n\n        if(UpdateButton(mode, 290 + 4, 260 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            Events[m_current_event].TriggerDelay ++;\n    }\n    else\n        SuperPrintR(mode, g_mainMenu.caseNone, 3, 54, 240);\n}\n\n// updates the bounds for Section 0 in level start event according to its autoscroll\nvoid UpdateStartLevelEventBounds()\n{\n    Events[0].AutoSection = 0;\n    Events[0].section[0].position = LevelREAL[0];\n\n    // not sure why 800 is also used for height in the default code, but I will stick with it.\n    if(Events[0].AutoX < 0)\n        Events[0].section[0].position.X = Events[0].section[0].position.Width - 800;\n    else if(Events[0].AutoX > 0)\n        Events[0].section[0].position.Width = Events[0].section[0].position.X + 800;\n\n    if(Events[0].AutoY < 0)\n        Events[0].section[0].position.Y = Events[0].section[0].position.Height - 800;\n    else if(Events[0].AutoY > 0)\n        Events[0].section[0].position.Height = Events[0].section[0].position.Y + 800;\n}\n\nvoid EditorScreen::UpdateSectionsScreen(CallMode mode)\n{\n    // level settings\n    if(UpdateButton(mode, 10 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n    {\n        DisableCursorNew();\n        LevelName = TextEntryScreen::Run(g_editorStrings.levelName, LevelName);\n        s_fix_mouse_pos();\n    }\n\n    SuperPrintR(mode, g_editorStrings.levelName, 3, 54, 42);\n    if(!LevelName.empty())\n        SuperPrintR(mode, LevelName, 3, 54, 60);\n    else\n        SuperPrintR(mode, g_mainMenu.caseNone, 3, 54, 60);\n\n    SuperPrintR(mode, g_editorStrings.levelStartPos, 3, 10, 90);\n    if(UpdateButton(mode, 240, 80 + 4, GFXBlock[622], (EditorCursor.Mode == OptCursor_t::LVL_PLAYERSTART && EditorCursor.SubMode == 4), 0, 0, 32, 32))\n    {\n        EditorCursor.Mode = OptCursor_t::LVL_PLAYERSTART;\n        EditorCursor.SubMode = 4;\n    }\n\n    if(UpdateButton(mode, 280, 80 + 4, GFXBlock[623], (EditorCursor.Mode == OptCursor_t::LVL_PLAYERSTART && EditorCursor.SubMode == 5), 0, 0, 32, 32))\n    {\n        EditorCursor.Mode = OptCursor_t::LVL_PLAYERSTART;\n        EditorCursor.SubMode = 5;\n    }\n\n    // section settings\n    SuperPrintCenterR(mode, fmt::format_ne(g_editorStrings.phraseSectionIndex, curSection + 1), 3, 320, 166);\n    if(curSection > 0 && UpdateButton(mode, 160 + 4, 160 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        SetSection(curSection - 1);\n\n    if(curSection < maxSections && UpdateButton(mode, 440 + 4, 160 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        SetSection(curSection + 1);\n\n    // background\n    if(UpdateButton(mode, 10 + 4, 200 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_SECTION_BACKGROUND;\n\n    if(mode == CallMode::Render)\n    {\n        const auto& indices = EditorCustom::bg2_list.indices;\n        const auto& names = EditorCustom::bg2_list.names;\n\n        size_t i;\n        for(i = 0; i < indices.size(); i++)\n        {\n            if(indices[i] == Background2[curSection])\n                break;\n        }\n\n        if(i == indices.size())\n            SuperPrintR(mode, g_editorStrings.eventsCaseBackground + \": \" + std::to_string(Background2[curSection]), 3, 54, 206);\n        else\n            SuperPrintR(mode, g_editorStrings.eventsCaseBackground + \": \" + names[i], 3, 54, 206);\n    }\n\n    // music\n    if(UpdateButton(mode, 10 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_SECTION_MUSIC;\n\n    if(mode == CallMode::Render)\n    {\n        const auto& indices = EditorCustom::music_list.indices;\n        const auto& names = EditorCustom::music_list.names;\n\n        size_t i;\n        for(i = 0; i < indices.size(); i++)\n        {\n            if(indices[i] == bgMusic[curSection])\n                break;\n        }\n\n        if(i == indices.size())\n            SuperPrint(g_editorStrings.eventsCaseMusic + \": \" + std::to_string(bgMusic[curSection]), 3, 54, 246);\n        else\n            SuperPrint(g_editorStrings.eventsCaseMusic + \": \" + names[i], 3, 54, 246);\n    }\n\n    if(bgMusic[curSection] == 24)\n    {\n        if(CustomMusic[curSection].length() < 15)\n        {\n            SuperPrintR(mode, CustomMusic[curSection], 3, 374, 252);\n        }\n        else\n        {\n            SuperPrintR(mode, CustomMusic[curSection].substr(0,14), 3, 374, 242);\n            SuperPrintR(mode, CustomMusic[curSection].substr(14,14), 3, 374, 260);\n        }\n\n        if(UpdateButton(mode, 330 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::open, 32, 32))\n            StartFileBrowser(&CustomMusic[curSection], FileNamePath, \"\", c_musicFormats, BROWSER_MODE_OPEN, BROWSER_CALLBACK_CUSTOM_MUSIC);\n    }\n\n    // will put section special effects below music\n\n    // vwrap - LevelVWrap\n    if(FileFormat == FileFormats::LVL_PGEX)\n    {\n        if(UpdateCheckBox(mode, e_ScreenW/2 + 10 + 4, 280 + 4, LevelVWrap[curSection]))\n            LevelVWrap[curSection] = !LevelVWrap[curSection];\n        SuperPrintR(mode, g_editorStrings.sectionVertWrap, 3, e_ScreenW/2 + 54, 286);\n    }\n\n    // underwater - UnderWater\n    if(UpdateCheckBox(mode, 10 + 4, 320 + 4, UnderWater[curSection]))\n        UnderWater[curSection] = !UnderWater[curSection];\n    SuperPrintR(mode, g_editorStrings.sectionUnderwater, 3, 54, 326);\n\n    // hwrap - LevelWrap\n    if(UpdateCheckBox(mode, e_ScreenW/2 + 10 + 4, 320 + 4, LevelWrap[curSection]))\n        LevelWrap[curSection] = !LevelWrap[curSection];\n    SuperPrintR(mode, g_editorStrings.sectionHorizWrap, 3, e_ScreenW/2 + 54, 326);\n\n    // no turn back - NoTurnBack\n    if(UpdateCheckBox(mode, 10 + 4, 360 + 4, NoTurnBack[curSection]))\n        NoTurnBack[curSection] = !NoTurnBack[curSection];\n    SuperPrintR(mode, g_editorStrings.sectionNoTurnBack, 3, 54, 366);\n\n    // leave to exit - OffScreenExit\n    if(UpdateCheckBox(mode, e_ScreenW/2 + 10 + 4, 360 + 4, OffScreenExit[curSection]))\n        OffScreenExit[curSection] = !OffScreenExit[curSection];\n    SuperPrintR(mode, g_editorStrings.sectionOffscreenExit, 3, e_ScreenW/2 + 54, 366);\n\n    // moved autoscroll into level settings, and only allow section 0 / event 0 (level start).\n    // this is due to an awful bug which couldn't be fixed if people had taken\n    // advantage of the ridiculously incorrect original behavior.\n    if(curSection == 0)\n    {\n        SuperPrintR(mode, g_editorStrings.sectionScroll, 3, 10, 430);\n\n        int sy = num_t::round((num_t)Events[0].AutoY * 10);\n        if(sy < 0)\n            SuperPrintR(mode, g_editorStrings.letterUp    + std::to_string(-sy), 3, 180, 422);\n        else if(sy > 0)\n            SuperPrintR(mode, g_editorStrings.letterDown  + std::to_string(sy), 3, 180, 422);\n        else\n            SuperPrintR(mode, \"-\", 3, 180, 422);\n\n        int sx = num_t::round((num_t)Events[0].AutoX * 10);\n        if(sx < 0)\n            SuperPrintR(mode, g_editorStrings.letterLeft  + std::to_string(-sx), 3, 180, 440);\n        else if(sx > 0)\n            SuperPrintR(mode, g_editorStrings.letterRight + std::to_string(sx), 3, 180, 440);\n        else\n            SuperPrintR(mode, \"-\", 3, 180, 440);\n\n        if(UpdateButton(mode, 240 + 4, 420 + 4, GFX.EIcons, false, 0, 32*Icon::up, 32, 32))\n        {\n            Events[0].AutoY = s_divide_by_ten(sy - 1);\n            UpdateStartLevelEventBounds();\n        }\n\n        if(UpdateButton(mode, 280 + 4, 420 + 4, GFX.EIcons, false, 0, 32*Icon::down, 32, 32))\n        {\n            Events[0].AutoY = s_divide_by_ten(sy + 1);\n            UpdateStartLevelEventBounds();\n        }\n\n        if(UpdateButton(mode, 320 + 4, 420 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        {\n            Events[0].AutoX = s_divide_by_ten(sx - 1);\n            UpdateStartLevelEventBounds();\n        }\n\n        if(UpdateButton(mode, 360 + 4, 420 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        {\n            Events[0].AutoX = s_divide_by_ten(sx + 1);\n            UpdateStartLevelEventBounds();\n        }\n    }\n}\n\nvoid EditorScreen::UpdateEditorSettingsScreen(CallMode mode)\n{\n    // settings screen, now.\n\n#if 0\n    // previous magic block settings\n\n    SuperPrintR(mode, g_editorStrings.toggleMagicBlock, 3, 10, 50);\n\n\n    if(EditorCustom::block_pages.empty() && EditorCustom::bgo_pages.empty() && EditorCustom::tile_pages.empty())\n    {\n        if(mode == CallMode::Render)\n        {\n            SuperPrint(\"Magic Block\", 3, 10, 140);\n            SuperPrint(\"needs editor.ini\", 3, 10, 160);\n            SuperPrint(\"to work.\", 3, 10, 180);\n        }\n    }\n    else\n    {\n        SuperPrintR(mode, \"Normal\", 3, 50, 94);\n        if(UpdateButton(mode, 4, 80 + 4, GFX.ECursor[2], MagicBlock::enabled && MagicBlock::count_level_edges && !MagicBlock::advanced_mode && MagicBlock::check_level == MagicBlock::LEVEL_FAMILY && MagicBlock::change_level == MagicBlock::LEVEL_FAMILY))\n        {\n            MagicBlock::enabled = true;\n            MagicBlock::count_level_edges = true;\n            MagicBlock::advanced_mode = false;\n            MagicBlock::check_level = MagicBlock::LEVEL_FAMILY;\n            MagicBlock::change_level = MagicBlock::LEVEL_FAMILY;\n        }\n\n        SuperPrintR(mode, \"Behind\", 3, 50, 134);\n        if(UpdateButton(mode, 4, 120 + 4, GFX.ECursor[2], MagicBlock::enabled && MagicBlock::count_level_edges && !MagicBlock::advanced_mode && MagicBlock::check_level == MagicBlock::LEVEL_ALL && MagicBlock::change_level == MagicBlock::LEVEL_FAMILY))\n        {\n            MagicBlock::enabled = true;\n            MagicBlock::count_level_edges = true;\n            MagicBlock::advanced_mode = false;\n            MagicBlock::check_level = MagicBlock::LEVEL_ALL;\n            MagicBlock::change_level = MagicBlock::LEVEL_FAMILY;\n        }\n\n        SuperPrintR(mode, \"Advanced\", 3, 50, 174);\n        if(UpdateButton(mode, 4, 160 + 4, GFX.EIcons, MagicBlock::enabled && (!MagicBlock::count_level_edges || MagicBlock::advanced_mode || MagicBlock::check_level == MagicBlock::LEVEL_GROUP || MagicBlock::change_level != MagicBlock::LEVEL_FAMILY), 0, 32 * Icon::subscreen, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_MAGICBLOCK;\n            return;\n        }\n\n        SuperPrintR(mode, \"Off\", 3, 50, 214);\n        if(UpdateButton(mode, 4, 200 + 4, GFX.ECursor[2], !MagicBlock::enabled))\n        {\n            MagicBlock::enabled = false;\n        }\n\n        SuperPrintR(mode, \"Overwrite\", 3, 50, 294);\n        if(UpdateCheckBox(mode, 4, 280 + 4, MagicBlock::replace_existing))\n            MagicBlock::replace_existing = !MagicBlock::replace_existing;\n    }\n#endif\n\n    if(WorldEditor || MagicHand)\n        return;\n\n    // level test settings\n\n    if(m_special_subpage < 1)\n        m_special_subpage = 1;\n\n    if(m_special_subpage > maxLocalPlayers)\n        m_special_subpage = maxLocalPlayers;\n\n    if(this->num_test_players < 1)\n        this->num_test_players = 1;\n\n    if(BattleMode /*&& this->num_test_players < 2*/) // currently >2P battle is not supported\n        this->num_test_players = 2;\n\n    if(m_special_subpage > this->num_test_players + 1)\n        m_special_subpage = this->num_test_players;\n\n    SuperPrintRightR(mode, g_editorStrings.testMagicHand, 3, e_ScreenW - 60, 94);\n    if(UpdateCheckBox(mode, e_ScreenW - 50 + 4, 80 + 4, this->test_magic_hand))\n        this->test_magic_hand = !this->test_magic_hand;\n\n    if(testStartWarp == 0)\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.warpTo, g_editorStrings.levelStartPos), 3, 46, 94);\n    else\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.warpTo, fmt::format_ne(g_editorStrings.phraseWarpIndex, testStartWarp)), 3, 46, 94);\n\n    if(testStartWarp > 0 && UpdateButton(mode, 4, 80 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        testStartWarp--;\n\n    if(testStartWarp < numWarps && UpdateButton(mode, 280 + 4, 80 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        testStartWarp++;\n\n    SuperPrintRightR(mode, Controls::EditorControls::g_button_name_UI[Controls::EditorControls::TestPlay], 3, e_ScreenW - 60, 54);\n    if(UpdateButton(mode, e_ScreenW-50 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::play, 32, 32))\n    {\n        // turn this into a routine...?! (cross-reference editor.cpp handler for EditorControls.TestPlay)\n        EditorBackup();\n        Backup_FullFileName = FullFileName;\n        // how does this interact with cross-level warps?\n        FullFileName = FullFileName + \"tst\";\n        SaveLevel(FullFileName, FileFormat);\n\n        if(g_config.EnableInterLevelFade)\n            g_levelScreenFader.setupFader(4, 0, 65, ScreenFader::S_FADE);\n        else\n            g_levelScreenFader.setupFader(65, 0, 65, ScreenFader::S_FADE);\n\n        editorWaitForFade();\n\n        // force reconnect on leveltest start\n        Controls::ClearInputMethods();\n\n        HasCursor = false;\n        zTestLevel(this->test_magic_hand);\n    }\n\n    SuperPrintCenterR(mode, g_mainMenu.wordPlayer + \" \" + std::to_string(m_special_subpage), 3, 510, 154);\n    if(m_special_subpage > 1 && UpdateButton(mode, 400 + 4, 140 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        m_special_subpage --;\n\n    if(m_special_subpage < maxLocalPlayers && m_special_subpage <= this->num_test_players && UpdateButton(mode, 580 + 4, 140 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        m_special_subpage ++;\n\n    if(m_special_subpage >= this->num_test_players && m_special_subpage != 1 && !(BattleMode && m_special_subpage == 2))\n    {\n        SuperPrintR(mode, g_editorStrings.wordEnabled, 3, e_ScreenW / 2 + 54, 194);\n\n        if(UpdateCheckBox(mode, e_ScreenW / 2 + 10 + 4, 180 + 4, m_special_subpage == this->num_test_players))\n        {\n            if(m_special_subpage == this->num_test_players)\n                this->num_test_players -= 1;\n            else\n                this->num_test_players += 1;\n        }\n    }\n\n    if(m_special_subpage > this->num_test_players)\n        return;\n\n    SuperPrintR(mode, g_editorStrings.testChar, 3, e_ScreenW / 2 - 30, 234);\n\n    constexpr int block_for_char[] = {622, 623, 624, 625, 631};\n    for(int ch = 1; ch <= 5; ch++)\n    {\n        bool pPctive = testPlayer[m_special_subpage].Character == ch;\n        int block = block_for_char[ch - 1];\n\n        if(UpdateButton(mode, e_ScreenW / 2 + 40 + 4 + 40*ch, 220 + 4, GFXBlock[block], pPctive, 0, 0, 32, 32))\n            testPlayer[m_special_subpage].Character = ch;\n    }\n\n    SuperPrintR(mode, g_editorStrings.testPower, 3, e_ScreenW / 2 - 30, 274);\n\n    if(testPlayer[m_special_subpage].State == 0)\n        testPlayer[m_special_subpage].State = 2;\n\n    constexpr int NPC_for_state[] = {0, NPCID_POWER_S3, NPCID_FIRE_POWER_S3, NPCID_LEAF_POWER, NPCID_STATUE_POWER, NPCID_HEAVY_POWER, NPCID_ICE_POWER_S3, NPCID_AQUATIC_POWER, NPCID_POLAR_POWER, NPCID_CYCLONE_POWER, NPCID_SHELL_POWER};\n    int max_state = (g_gameInfo.contentFeatureLevel >= 1030790) ? 11 : 7;\n    for(int state = 1; state <= max_state; state++)\n    {\n        bool pActive = testPlayer[m_special_subpage].State == state;\n        int sNPC = NPC_for_state[state - 1];\n\n        bool selected;\n        if(!sNPC)\n            selected = UpdateButton(mode, e_ScreenW / 2 + 80 + 4 + 40 * ((state - 1) % 6), 260 + 4 + ((state - 1) / 6) * 40, GFX.EIcons, pActive, 0, 0, 1, 1);\n        else\n            selected = UpdateButton(mode, e_ScreenW / 2 + 80 + 4 + 40 * ((state - 1) % 6), 260 + 4 + ((state - 1) / 6) * 40, GFXNPC[sNPC], pActive, 0, 0, 32, 32);\n\n        if(selected)\n            testPlayer[m_special_subpage].State = state;\n    }\n\n    SuperPrintR(mode, g_editorStrings.testBoot, 3, e_ScreenW / 2 - 30, 354);\n\n    constexpr int NPC_for_boot[] = {NPCID_GRN_BOOT, NPCID_RED_BOOT, NPCID_BLU_BOOT};\n    for(int boot = 1; boot <= 3; boot++)\n    {\n        bool pActive = testPlayer[m_special_subpage].Mount == 1 && testPlayer[m_special_subpage].MountType == boot;\n        int pNPC = NPC_for_boot[boot - 1];\n\n        if(UpdateButton(mode, e_ScreenW / 2 + 40 + 4 + 40*boot, 340 + 4, GFXNPC[pNPC], pActive, 0, 0, 32, 32))\n        {\n            if(pActive)\n            {\n                testPlayer[m_special_subpage].Mount = 0;\n            }\n            else\n            {\n                testPlayer[m_special_subpage].Mount = 1;\n                testPlayer[m_special_subpage].MountType = boot;\n            }\n        }\n    }\n\n    SuperPrintR(mode, g_editorStrings.testPet, 3, e_ScreenW / 2 - 30, 394);\n\n    constexpr int NPC_for_yoshi[] = {NPCID_PET_GREEN, NPCID_PET_BLUE, NPCID_PET_YELLOW, NPCID_PET_RED, NPCID_PET_BLACK, NPCID_PET_PURPLE, NPCID_PET_PINK, NPCID_PET_CYAN};\n    for(int yoshi = 1; yoshi <= 8; yoshi++)\n    {\n        bool pActive = testPlayer[m_special_subpage].Mount == 3 && testPlayer[m_special_subpage].MountType == yoshi;\n        int pNPC = NPC_for_yoshi[yoshi - 1];\n\n        if(UpdateButton(mode, e_ScreenW / 2 + 80 + 4 + 40 * ((yoshi - 1) % 6), 380 + 4 + ((yoshi - 1) / 6) * 40, GFXNPC[pNPC], pActive, 0, 0, 72, 56))\n        {\n            if(pActive)\n            {\n                testPlayer[m_special_subpage].Mount = 0;\n            }\n            else\n            {\n                testPlayer[m_special_subpage].Mount = 3;\n                testPlayer[m_special_subpage].MountType = yoshi;\n            }\n        }\n    }\n}\n\nvoid EditorScreen::UpdateWorldSettingsScreen(CallMode mode)\n{\n    // world name\n    if(UpdateButton(mode, 10 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n    {\n        DisableCursorNew();\n        WorldName = TextEntryScreen::Run(g_editorStrings.worldName, WorldName);\n        s_fix_mouse_pos();\n    }\n\n    SuperPrintR(mode, g_editorStrings.worldName, 3, 54, 42);\n    if(!WorldName.empty())\n        SuperPrintR(mode, WorldName, 3, 54, 60);\n    else\n        SuperPrintR(mode, g_mainMenu.caseNone, 3, 54, 60);\n\n    // auto start level\n    if(UpdateButton(mode, 10 + 4, 100 + 4, GFX.EIcons, false, 0, 32*Icon::open, 32, 32))\n        StartFileBrowser(&StartLevel, FileNamePath, \"\", {\".lvl\", \".lvlx\"}, BROWSER_MODE_OPEN);\n\n    SuperPrintR(mode, g_editorStrings.worldIntroLevel, 3, 54, 102);\n\n    if(!StartLevel.empty())\n        SuperPrintR(mode, StartLevel, 3, 54, 120);\n    else\n        SuperPrintR(mode, g_mainMenu.caseNone, 3, 54, 120);\n\n    // no world map - NoMap\n    if(UpdateCheckBox(mode, 10 + 4, 160 + 4, NoMap))\n        NoMap = !NoMap;\n\n    SuperPrintR(mode, g_editorStrings.worldHubWorld, 3, 54, 170);\n\n    // restart after death - RestartLevel\n    if(UpdateCheckBox(mode, e_ScreenW/2 + 10 + 4, 160 + 4, RestartLevel))\n        RestartLevel = !RestartLevel;\n\n    SuperPrintR(mode, g_editorStrings.worldRetryOnFail, 3, e_ScreenW/2 + 54, 170);\n\n    // world star count\n    if(MaxWorldStars > 0 && UpdateButton(mode, 120 + 4, 220 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        MaxWorldStars --;\n\n    SuperPrintCenterR(mode, g_editorStrings.worldTotalStars + std::to_string(MaxWorldStars), 3, 300, 230);\n\n    if(UpdateButton(mode, 440 + 4, 220 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        MaxWorldStars ++;\n\n    // allow chars\n    SuperPrintR(mode, g_editorStrings.worldAllowChars, 3, 10, 290);\n\n    if(UpdateButton(mode, 240 + 4, 280 + 4, GFXBlock[622], !blockCharacter[1], 0, 0, 32, 32))\n        blockCharacter[1] = !blockCharacter[1];\n\n    if(UpdateButton(mode, 280 + 4, 280 + 4, GFXBlock[623], !blockCharacter[2], 0, 0, 32, 32))\n        blockCharacter[2] = !blockCharacter[2];\n\n    if(UpdateButton(mode, 320 + 4, 280 + 4, GFXBlock[624], !blockCharacter[3], 0, 0, 32, 32))\n        blockCharacter[3] = !blockCharacter[3];\n\n    if(UpdateButton(mode, 360 + 4, 280 + 4, GFXBlock[625], !blockCharacter[4], 0, 0, 32, 32))\n        blockCharacter[4] = !blockCharacter[4];\n\n    if(UpdateButton(mode, 400 + 4, 280 + 4, GFXBlock[631], !blockCharacter[5], 0, 0, 32, 32))\n        blockCharacter[5] = !blockCharacter[5];\n\n    // don't allow all characters to be blocked.\n    if(blockCharacter[1] && blockCharacter[2] && blockCharacter[3] && blockCharacter[4] && blockCharacter[5])\n        blockCharacter[1] = false;\n\n\n    // World credits...\n    if(m_special_subpage > 0 && UpdateButton(mode, 10 + 4, 340 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        m_special_subpage --;\n\n    if(UpdateButton(mode, 50 + 4, 340 + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n    {\n        DisableCursorNew();\n        WorldCredits[m_special_subpage + 1] = TextEntryScreen::Run(fmt::format_ne(g_editorStrings.worldCreditIndex, m_special_subpage + 1), WorldCredits[m_special_subpage + 1]);\n        s_fix_mouse_pos();\n        for(int i = SDL_max(numWorldCredits, m_special_subpage + 1); i > 0; --i) // Find the last non-empty line\n        {\n            if(!WorldCredits[m_special_subpage + 1].empty())\n            {\n                numWorldCredits = i;\n                break;\n            }\n        }\n    }\n\n    if(m_special_subpage < 4 && UpdateButton(mode, 90 + 4, 340 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        m_special_subpage ++;\n\n    SuperPrintR(mode, fmt::format_ne(g_editorStrings.worldCreditIndex, m_special_subpage + 1), 3, 144, 342);\n    SuperPrintR(mode, WorldCredits[m_special_subpage + 1], 3, 144, 360);\n}\n\nvoid EditorScreen::UpdateSelectListScreen(CallMode mode)\n{\n    if(m_special_page != SPECIAL_PAGE_NONE && UpdateButton(mode, e_ScreenW - 40 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n    {\n        if(m_special_page == SPECIAL_PAGE_EVENT_SOUND || m_special_page == SPECIAL_PAGE_EVENT_MUSIC || m_special_page == SPECIAL_PAGE_EVENT_BACKGROUND)\n            m_special_page = SPECIAL_PAGE_EVENT_SETTINGS;\n        else if(m_special_page == SPECIAL_PAGE_SECTION_MUSIC || m_special_page == SPECIAL_PAGE_SECTION_BACKGROUND)\n            m_special_page = SPECIAL_PAGE_SECTION_SETTINGS;\n        else\n            m_special_page = SPECIAL_PAGE_NONE;\n    }\n\n    vbint_t* target;\n    int* current_page;\n    PGE_Size titleSize;\n    const int titlePosX = 10;\n    const int titlePosY = 40;\n    const std::vector<std::string>* source;\n    const std::vector<int16_t>* source_indices;\n\n    if(m_special_page == SPECIAL_PAGE_EVENT_SOUND)\n    {\n        titleSize = FontManager_printTextOptiPxR(mode,\n                                                 fmt::format_ne(g_editorStrings.selectSoundForEvent,\n                                                                Events[m_current_event].Name),\n                                                 titlePosX, titlePosY,\n                                                 300,\n                                                 FontManager::fontIdFromSmbxFont(3));\n        target = &Events[m_current_event].Sound;\n        current_page = &m_sounds_page;\n\n        source = &EditorCustom::sound_list.names;\n        source_indices = &EditorCustom::sound_list.indices;\n    }\n    else if(m_special_page == SPECIAL_PAGE_EVENT_BACKGROUND)\n    {\n        if(m_special_subpage > 0)\n        {\n            titleSize = FontManager_printTextOptiPxR(mode,\n                                                     fmt::format_ne(g_editorStrings.selectSectBlankPropBlankForEvent,\n                                                                    m_special_subpage,\n                                                                    g_editorStrings.eventsCaseBackground,\n                                                                    Events[m_current_event].Name),\n                                                     titlePosX, titlePosY,\n                                                     300,\n                                                     FontManager::fontIdFromSmbxFont(3));\n            target = &Events[m_current_event].section[m_special_subpage-1].background_id;\n        }\n        else\n        {\n            titleSize = FontManager_printTextOptiPxR(mode,\n                                                     fmt::format_ne(g_editorStrings.selectAllSectPropBlankForEvent,\n                                                                    g_editorStrings.eventsCaseBackground,\n                                                                    Events[m_current_event].Name),\n                                                     titlePosX, titlePosY,\n                                                     300,\n                                                     FontManager::fontIdFromSmbxFont(3));\n            target = nullptr;\n        }\n\n        current_page = &m_background_page;\n\n        source = &EditorCustom::bg2_list.names;\n        source_indices = &EditorCustom::bg2_list.indices;\n    }\n    else if(m_special_page == SPECIAL_PAGE_EVENT_MUSIC)\n    {\n        if(m_special_subpage > 0)\n        {\n            titleSize = FontManager_printTextOptiPxR(mode,\n                                                     fmt::format_ne(g_editorStrings.selectSectBlankPropBlankForEvent,\n                                                                    m_special_subpage,\n                                                                    g_editorStrings.eventsCaseMusic,\n                                                                    Events[m_current_event].Name),\n                                                     titlePosX, titlePosY,\n                                                     300,\n                                                     FontManager::fontIdFromSmbxFont(3));\n            target = &Events[m_current_event].section[m_special_subpage-1].music_id;\n        }\n        else\n        {\n            titleSize = FontManager_printTextOptiPxR(mode,\n                                                     fmt::format_ne(g_editorStrings.selectAllSectPropBlankForEvent,\n                                                                    g_editorStrings.eventsCaseMusic,\n                                                                    Events[m_current_event].Name),\n                                                     titlePosX, titlePosY,\n                                                     300,\n                                                     FontManager::fontIdFromSmbxFont(3));\n            target = nullptr;\n        }\n\n        current_page = &m_music_page;\n\n        source = &EditorCustom::music_list.names;\n        source_indices = &EditorCustom::music_list.indices;\n    }\n    else if(m_special_page == SPECIAL_PAGE_SECTION_BACKGROUND)\n    {\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.selectSectionBlankPropBlank, curSection + 1, g_editorStrings.eventsCaseBackground), 3, 10, 50);\n        target = &Background2[curSection];\n        current_page = &m_background_page;\n\n        source = &EditorCustom::bg2_list.names;\n        source_indices = &EditorCustom::bg2_list.indices;\n    }\n    else if(m_special_page == SPECIAL_PAGE_SECTION_MUSIC)\n    {\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.selectSectionBlankPropBlank, curSection + 1, g_editorStrings.eventsCaseMusic), 3, 10, 50);\n        target = &bgMusic[curSection];\n        current_page = &m_music_page;\n\n        source = &EditorCustom::music_list.names;\n        source_indices = &EditorCustom::music_list.indices;\n    }\n    else if(m_special_page == SPECIAL_PAGE_LEVEL_EXIT)\n    {\n        using Controls::PlayerControls::Buttons;\n\n        if(m_special_subpage == 1)\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.selectPathBlankUnlock, GetButtonName_UI(Buttons::Up)), 3, 10, 50);\n        else if(m_special_subpage == 2)\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.selectPathBlankUnlock, GetButtonName_UI(Buttons::Left)), 3, 10, 50);\n        else if(m_special_subpage == 3)\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.selectPathBlankUnlock, GetButtonName_UI(Buttons::Down)), 3, 10, 50);\n        else if(m_special_subpage == 4)\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.selectPathBlankUnlock, GetButtonName_UI(Buttons::Right)), 3, 10, 50);\n        else\n            return;\n\n        target = &EditorCursor.WorldLevel.LevelExit[m_special_subpage];\n        current_page = nullptr;\n        source = &EditorCustom::list_level_exit_names;\n        source_indices = nullptr;\n    }\n    else if(m_special_page == SPECIAL_PAGE_WARP_TRANSITION)\n    {\n        SuperPrintR(mode, g_editorStrings.selectWarpTransitionEffect, 3, 10, 50);\n\n        target = &EditorCursor.Warp.transitEffect;\n        current_page = nullptr;\n        source = &g_editorStrings.listWarpTransitNames;\n        source_indices = nullptr;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_MUSIC)\n    {\n        SuperPrintR(mode, g_editorStrings.selectWorldMusic, 3, 10, 50);\n        target = &EditorCursor.WorldMusic.Type;\n        current_page = &m_music_page;\n\n        source = &EditorCustom::wmusic_list.names;\n        source_indices = &EditorCustom::wmusic_list.indices;\n    }\n    else\n    {\n        return;\n    }\n\n\n    int elementsOnPage = 20;\n    int elementsOnColumn = elementsOnPage / 2;\n    int elementsListBaseY = 80;\n    const int elementsRowHeight = 40;\n\n    // Reduce the size of the elements list if title has more lines than expected\n    while(titlePosY + titleSize.h() >= elementsListBaseY)\n    {\n        elementsOnPage -= 2;\n        elementsOnColumn -= 1;\n        elementsListBaseY += elementsRowHeight;\n    }\n\n    SDL_assert(elementsOnPage > 0);\n    SDL_assert(elementsOnColumn > 0);\n\n    if(current_page != nullptr)\n    {\n        int page_max = !source->empty() ? ((static_cast<int>(source->size()) - 1) / elementsOnPage) : 0;\n\n        if(!(page_max == 0 && *current_page == 0))\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.pageBlankOfBlank, *current_page + 1, page_max + 1), 3, e_ScreenW - 320, 50);\n\n        if(*current_page > 0 && UpdateButton(mode, e_ScreenW - 120 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            *current_page = *current_page - 1;\n\n        if(*current_page < page_max && UpdateButton(mode, e_ScreenW - 80 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            *current_page = *current_page + 1;\n\n        if(*current_page < 0)\n            *current_page = 0;\n\n        if(*current_page > page_max)\n            *current_page = page_max;\n    }\n\n    for(int i = 0; i < elementsOnPage; i++)\n    {\n        int x = 10 + (e_ScreenW / 2) * (i / elementsOnColumn);\n        int y = elementsListBaseY + elementsRowHeight * (i % elementsOnColumn);\n\n        int j;\n\n        if(current_page != nullptr)\n            j = (*current_page * elementsOnPage) + i;\n        else\n            j = i;\n\n        if(j >= (int)source->size())\n            break;\n\n        int index;\n        if(source_indices != nullptr)\n        {\n            if(source_indices->empty())\n                return; // Note: this is an invalid: array must not being empty!\n            index = (*source_indices)[j];\n        }\n        else if(m_special_page == SPECIAL_PAGE_LEVEL_EXIT)\n            index = j - 1;\n        else\n            index = j;\n\n        if(index == -1 && m_special_page != SPECIAL_PAGE_LEVEL_EXIT)\n            x += 40;\n\n        SuperPrintR(mode, (*source)[j], 3, x + 44, y + 10);\n        if(index != -1 || m_special_page == SPECIAL_PAGE_LEVEL_EXIT)\n        {\n            bool sel;\n            if(target != nullptr)\n                sel = *target == index;\n            else if(m_special_page == SPECIAL_PAGE_EVENT_BACKGROUND)\n            {\n                sel = true;\n                for(int s = 0; s <= maxSections; s++)\n                {\n                    if(Events[m_current_event].section[s].background_id != index)\n                        sel = false;\n                }\n            }\n            else if(m_special_page == SPECIAL_PAGE_EVENT_MUSIC)\n            {\n                sel = true;\n                for(int s = 0; s <= maxSections; s++)\n                {\n                    if(Events[m_current_event].section[s].music_id != index)\n                        sel = false;\n                }\n            }\n            else // should never happen\n            {\n                return;\n            }\n\n            if(UpdateButton(mode, x + 4, y + 4, GFX.ECursor[2], sel, 0, 0, 32, 32))\n            {\n                if(!sel)\n                {\n                    if(target != nullptr)\n                        *target = index;\n                    else if(m_special_page == SPECIAL_PAGE_EVENT_BACKGROUND)\n                    {\n                        for(int s = 0; s <= maxSections; s++)\n                            Events[m_current_event].section[s].background_id = index;\n                    }\n                    else if(m_special_page == SPECIAL_PAGE_EVENT_MUSIC)\n                    {\n                        for(int s = 0; s <= maxSections; s++)\n                            Events[m_current_event].section[s].music_id = index;\n                    }\n\n                    // and do whatever necessary to preview it.\n                    if(m_special_page == SPECIAL_PAGE_EVENT_SOUND && index != 0)\n                        PlaySound(index);\n                    else if(m_special_page == SPECIAL_PAGE_SECTION_MUSIC)\n                        StartMusic(curSection);\n                }\n                else\n                {\n                    if(m_special_page == SPECIAL_PAGE_EVENT_SOUND || m_special_page == SPECIAL_PAGE_EVENT_MUSIC || m_special_page == SPECIAL_PAGE_EVENT_BACKGROUND)\n                        m_special_page = SPECIAL_PAGE_EVENT_SETTINGS;\n                    else if(m_special_page == SPECIAL_PAGE_SECTION_MUSIC || m_special_page == SPECIAL_PAGE_SECTION_BACKGROUND)\n                        m_special_page = SPECIAL_PAGE_SECTION_SETTINGS;\n                    else\n                        m_special_page = SPECIAL_PAGE_NONE;\n                }\n            }\n        }\n    }\n}\n\nvoid EditorScreen::UpdateEventsSubScreen(CallMode mode)\n{\n    // render shared GUI elements on right\n    if(mode == CallMode::Render)\n        XRender::renderRect(e_ScreenW - 240, 40, 240, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n\n    // back button\n    if(UpdateButton(mode, e_ScreenW - 40 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n    {\n        if(m_special_page == SPECIAL_PAGE_EVENT_TRIGGER)\n            m_special_page = SPECIAL_PAGE_EVENT_SETTINGS;\n        else\n            m_special_page = SPECIAL_PAGE_NONE;\n\n        m_special_subpage = 0;\n        return;\n    }\n\n    if(EditorCursor.Mode == OptCursor_t::LVL_NPCS)\n    {\n        // activate event\n        SuperPrintR(mode, g_editorStrings.eventsLabelActivate, 3, e_ScreenW - 200, 200 + 2);\n\n        if(EditorCursor.NPC.TriggerActivate != EVENT_NONE)\n            SuperPrintR(mode, GetE(EditorCursor.NPC.TriggerActivate), 3, e_ScreenW - 200, 220 + 2);\n        else\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 220 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 200 + 4, GFX.EIcons, m_special_subpage == 1, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 1;\n\n        // death event\n        SuperPrintR(mode, g_editorStrings.eventsLabelDeath, 3, e_ScreenW - 200, 240 + 2);\n\n        if(EditorCursor.NPC.TriggerDeath != EVENT_NONE)\n            SuperPrintR(mode, GetE(EditorCursor.NPC.TriggerDeath), 3, e_ScreenW - 200, 260 + 2);\n        else\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 260 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 240 + 4, GFX.EIcons, m_special_subpage == 2, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 2;\n\n        // talk event\n        SuperPrintR(mode, g_editorStrings.eventsLabelTalk, 3, e_ScreenW - 200, 280 + 2);\n\n        if(EditorCursor.NPC.TriggerTalk != EVENT_NONE)\n            SuperPrintR(mode, GetE(EditorCursor.NPC.TriggerTalk), 3, e_ScreenW - 200, 300 + 2);\n        else\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 300 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 280 + 4, GFX.EIcons, m_special_subpage == 3, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 3;\n\n        // layer clear event\n        SuperPrintR(mode, g_editorStrings.eventsLabelLayerClear, 3, e_ScreenW - 200, 320 + 2);\n\n        if(EditorCursor.NPC.TriggerLast != EVENT_NONE)\n            SuperPrintR(mode, GetE(EditorCursor.NPC.TriggerLast), 3, e_ScreenW - 200, 340 + 2);\n        else\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 340 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 320 + 4, GFX.EIcons, m_special_subpage == 4, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 4;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_BLOCKS)\n    {\n        // block hit event\n        SuperPrintR(mode, g_editorStrings.eventsLabelHit, 3, e_ScreenW - 200, 200 + 2);\n\n        if(EditorCursor.Block.TriggerHit != EVENT_NONE)\n            SuperPrintR(mode, GetE(EditorCursor.Block.TriggerHit), 3, e_ScreenW - 200, 220 + 2);\n        else\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 220 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 200 + 4, GFX.EIcons, m_special_subpage == 1, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 1;\n\n        // block destroy event\n        SuperPrintR(mode, g_editorStrings.eventsLabelDestroy, 3, e_ScreenW - 200, 240 + 2);\n\n        if(EditorCursor.Block.TriggerDeath != EVENT_NONE)\n            SuperPrintR(mode, GetE(EditorCursor.Block.TriggerDeath), 3, e_ScreenW - 200, 260 + 2);\n        else\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 260 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 240 + 4, GFX.EIcons, m_special_subpage == 2, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 2;\n\n        // layer clear event\n        SuperPrintR(mode, g_editorStrings.eventsLabelLayerClear, 3, e_ScreenW - 200, 280 + 2);\n\n        if(EditorCursor.Block.TriggerLast != EVENT_NONE)\n            SuperPrintR(mode, GetE(EditorCursor.Block.TriggerLast), 3, e_ScreenW - 200, 300 + 2);\n        else\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 300 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 280 + 4, GFX.EIcons, m_special_subpage == 3, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 3;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_WARPS)\n    {\n        m_special_subpage = 1;\n\n        SuperPrintR(mode, g_editorStrings.eventsLabelEnter, 3, e_ScreenW - 200, 200 + 2);\n        if(EditorCursor.Warp.eventEnter != EVENT_NONE)\n            SuperPrintR(mode, GetE(EditorCursor.Warp.eventEnter), 3, e_ScreenW - 200, 220 + 2);\n        else\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 220 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 200 + 4, GFX.EIcons, m_special_subpage == 1, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 1;\n    }\n    else if(m_special_page == SPECIAL_PAGE_EVENT_TRIGGER)\n    {\n        // delay selector, instead of page selector\n        if(Events[m_current_event].TriggerDelay > 0)\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.phraseDelayIsMs, 100 * Events[m_current_event].TriggerDelay), 3, e_ScreenW - 240 + 4, 272);\n        else\n            SuperPrintR(mode, g_editorStrings.wordInstant, 3, e_ScreenW - 240 + 4, 272);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 230 + 4, GFX.EIcons, false, 0, 32*Icon::up, 32, 32))\n            Events[m_current_event].TriggerDelay ++;\n\n        if(Events[m_current_event].TriggerDelay > 0 && UpdateButton(mode, e_ScreenW - 240 + 4, 290 + 4, GFX.EIcons, false, 0, 32*Icon::down, 32, 32))\n            Events[m_current_event].TriggerDelay --;\n    }\n    else\n        return;\n\n    int page_max = numEvents / 10;\n    SuperPrintR(mode, fmt::format_ne(g_editorStrings.pageBlankOfBlank, m_events_page + 1, page_max + 1), 3, e_ScreenW - 228, e_ScreenH - 60);\n\n    if(m_events_page > 0 && UpdateButton(mode, e_ScreenW - 160 + 4, e_ScreenH - 40 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        m_events_page --;\n\n    if(m_events_page < page_max && UpdateButton(mode, e_ScreenW - 120 + 4, e_ScreenH - 40 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        m_events_page ++;\n\n    // prepare selector\n    std::string event_name;\n    std::string event_desc;\n    eventindex_t* event_to_set;\n\n    if(m_special_page == SPECIAL_PAGE_EVENT_TRIGGER)\n    {\n        event_name = g_editorStrings.eventsLabelNext;\n        event_to_set = &Events[m_current_event].TriggerEvent;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_NPCS)\n    {\n        if(m_special_subpage == 1)\n        {\n            event_name = g_editorStrings.eventsLabelActivate;\n            event_desc = g_editorStrings.eventsDescActivate;\n            event_to_set = &EditorCursor.NPC.TriggerActivate;\n        }\n        else if(m_special_subpage == 2)\n        {\n            event_name = g_editorStrings.eventsLabelDeath;\n            event_desc = g_editorStrings.eventsDescDeath;\n            event_to_set = &EditorCursor.NPC.TriggerDeath;\n        }\n        else if(m_special_subpage == 3)\n        {\n            event_name = g_editorStrings.eventsLabelTalk;\n            event_desc = g_editorStrings.eventsDescTalk;\n            event_to_set = &EditorCursor.NPC.TriggerTalk;\n        }\n        else if(m_special_subpage == 4)\n        {\n            event_name = g_editorStrings.eventsLabelLayerClear;\n            event_desc = g_editorStrings.eventsDescLayerClear;\n            event_to_set = &EditorCursor.NPC.TriggerLast;\n        }\n        else\n            return;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_BLOCKS)\n    {\n        if(m_special_subpage == 1)\n        {\n            event_name = g_editorStrings.eventsLabelHit;\n            event_desc = g_editorStrings.eventsDescHit;\n            event_to_set = &EditorCursor.Block.TriggerHit;\n        }\n        else if(m_special_subpage == 2)\n        {\n            event_name = g_editorStrings.eventsLabelDestroy;\n            event_desc = g_editorStrings.eventsDescDestroy;\n            event_to_set = &EditorCursor.Block.TriggerDeath;\n        }\n        else if(m_special_subpage == 3)\n        {\n            event_name = g_editorStrings.eventsLabelLayerClear;\n            event_desc = g_editorStrings.eventsDescLayerClear;\n            event_to_set = &EditorCursor.Block.TriggerLast;\n        }\n        else\n            return;\n    }\n    else if(EditorCursor.Mode == OptCursor_t::LVL_WARPS)\n    {\n        if(m_special_subpage == 1)\n        {\n            event_name = g_editorStrings.eventsLabelEnter;\n            event_desc = g_editorStrings.eventsDescEnter;\n            event_to_set = &EditorCursor.Warp.eventEnter;\n        }\n        else\n            return;\n    }\n    else // this should not happen\n    {\n        return;\n    }\n\n    // render description\n    if(mode == CallMode::Render)\n    {\n        // trigger after current event is a special case\n        std::string descFormatted = (m_special_page == SPECIAL_PAGE_EVENT_TRIGGER)\n            ? fmt::format(g_editorStrings.eventsDescPhraseTriggersAfterTemplate, event_name, Events[m_current_event].TriggerDelay * 100, Events[m_current_event].Name)\n            : fmt::format(g_editorStrings.eventsDescPhraseTriggersWhenTemplate, event_name, event_desc);\n\n        FontManager::printTextOptiPx(descFormatted,\n                                     e_ScreenW - 236, 80,\n                                     230,\n                                     FontManager::fontIdFromSmbxFont(3));\n    }\n\n    // render current event\n    SuperPrintR(mode, fmt::format_ne(g_editorStrings.phraseTypeLabelEvent, event_name), 3, 10, 40);\n    if(*event_to_set == EVENT_NONE)\n        SuperPrintR(mode, g_mainMenu.caseNone, 3, 10, 56);\n    else\n        SuperPrintR(mode, Events[*event_to_set].Name, 3, 10, 56);\n\n    // render event selector\n    for(int i = 0; i < 10; i++)\n    {\n        eventindex_t e = m_events_page*10 + i - 1;\n        if(e == EVENT_NONE)\n        {\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, 54, 80 + 40*i + 12);\n            if(UpdateButton(mode, 10 + 4, 80 + 40*i + 4, GFX.ECursor[2], *event_to_set == EVENT_NONE, 0, 0, 32, 32))\n                *event_to_set = EVENT_NONE;\n        }\n        else if(!Events[e].Name.empty())\n        {\n            if(Events[e].Name.length() < 20)\n                SuperPrintR(mode, Events[e].Name, 3, 54, 80 + 40*i + 10);\n            else\n            {\n                SuperPrintR(mode, Events[e].Name.substr(0,19), 3, 54, 80 + 40*i + 2);\n                SuperPrintR(mode, Events[e].Name.substr(19), 3, 54, 80 + 40*i + 20);\n            }\n\n            if(UpdateButton(mode, 10 + 4, 80 + 40*i + 4, GFX.ECursor[2], *event_to_set == e, 0, 0, 32, 32))\n                *event_to_set = e;\n        }\n    }\n}\n\nvoid EditorScreen::UpdateLayersScreen(CallMode mode)\n{\n    if(m_special_page == SPECIAL_PAGE_LAYER_DELETION)\n    {\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.layersDeletionHeader, Layer[m_special_subpage].Name), 3, 60, 40);\n        SuperPrintR(mode, g_editorStrings.layersDeletionPreserveLayerContents, 3, 10, 60);\n\n        SuperPrintR(mode, g_editorStrings.layersDeletionConfirmPreserve, 3, 60, 110);\n\n        if(UpdateButton(mode, 20 + 4, 100 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            DeleteLayer((layerindex_t)m_special_subpage, false);\n            m_special_subpage = 0;\n            m_special_page = SPECIAL_PAGE_LAYERS;\n            return;\n        }\n\n\n        SuperPrintR(mode, g_editorStrings.layersDeletionConfirmDelete, 3, 60, 150);\n\n        if(UpdateButton(mode, 20 + 4, 140 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            DeleteLayer((layerindex_t)m_special_subpage, true);\n            m_special_subpage = 0;\n            m_special_page = SPECIAL_PAGE_LAYERS;\n            return;\n        }\n\n\n        SuperPrintR(mode, g_editorStrings.layersDeletionCancel, 3, 60, 190);\n\n        if(UpdateButton(mode, 20 + 4, 180 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            m_special_subpage = 0;\n            m_special_page = SPECIAL_PAGE_LAYERS;\n        }\n\n        return;\n    }\n\n    // fix just in case wrong subpage is set for obj layer selection\n    if(m_special_page == SPECIAL_PAGE_OBJ_LAYER && EditorCursor.Mode != OptCursor_t::LVL_NPCS && m_special_subpage != 0)\n        m_special_subpage = 0;\n\n    // render shared GUI elements on right\n    if(mode == CallMode::Render && m_special_page != SPECIAL_PAGE_LAYERS && m_special_page != SPECIAL_PAGE_EVENT_LAYERS)\n        XRender::renderRect(e_ScreenW - 240, 40, 240, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n\n    if(m_special_page != SPECIAL_PAGE_LAYERS && UpdateButton(mode, e_ScreenW - 40 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n    {\n        if(m_special_page == SPECIAL_PAGE_EVENT_LAYERS)\n            m_special_page = SPECIAL_PAGE_EVENT_SETTINGS;\n        else\n            m_special_page = SPECIAL_PAGE_NONE;\n        m_special_subpage = 0;\n    }\n\n    if(m_special_page == SPECIAL_PAGE_OBJ_LAYER && EditorCursor.Mode == OptCursor_t::LVL_NPCS)\n    {\n        SuperPrintR(mode, g_editorStrings.labelLayer, 3, e_ScreenW - 200, 200 + 2);\n        if(EditorCursor.Layer == LAYER_NONE)\n            SuperPrintR(mode, g_editorStrings.layersLayerDefault, 3, e_ScreenW - 200, 220 + 2);\n        else\n            SuperPrintR(mode, GetL(EditorCursor.Layer), 3, e_ScreenW - 200, 220 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 200 + 4, GFX.EIcons, m_special_subpage == 0, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 0;\n\n        SuperPrintR(mode, g_editorStrings.layersLabelAttached, 3, e_ScreenW - 200, 260 + 2);\n        if(EditorCursor.NPC.AttLayer == LAYER_NONE || EditorCursor.NPC.AttLayer == LAYER_DEFAULT)\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 200, 280 + 2);\n        else\n            SuperPrintR(mode, GetL(EditorCursor.NPC.AttLayer), 3, e_ScreenW - 200, 280 + 2);\n\n        if(UpdateButton(mode, e_ScreenW - 240 + 4, 260 + 4, GFX.EIcons, m_special_subpage == 1, 0, 32*Icon::action, 32, 32))\n            m_special_subpage = 1;\n    }\n\n    int page_max;\n    // extra slot for layer creation when not in the object layer selection\n    if(m_special_page == SPECIAL_PAGE_OBJ_LAYER)\n        page_max = (numLayers - 1) / 10;\n    else\n        page_max = numLayers / 10;\n\n    // different location for layers and event layers page because they has so many options\n    if(m_special_page == SPECIAL_PAGE_LAYERS || m_special_page == SPECIAL_PAGE_EVENT_LAYERS)\n    {\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.pageBlankOfBlank, m_layers_page + 1, page_max + 1), 3, e_ScreenW - 330, 40);\n        if(m_layers_page > 0 && UpdateButton(mode, e_ScreenW - 120 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            m_layers_page --;\n\n        if(m_layers_page < page_max && UpdateButton(mode, e_ScreenW - 80 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            m_layers_page ++;\n    }\n    else\n    {\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.pageBlankOfBlank, m_layers_page + 1, page_max + 1), 3, e_ScreenW - 228, e_ScreenH - 60);\n        if(m_layers_page > 0 && UpdateButton(mode, e_ScreenW - 160 + 4, e_ScreenH - 40 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            m_layers_page --;\n\n        if(m_layers_page < page_max && UpdateButton(mode, e_ScreenW - 120 + 4, e_ScreenH - 40 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            m_layers_page ++;\n    }\n\n    // prepare selector\n    const std::string* layer_name;\n    layerindex_t* layer_to_set;\n    if(m_special_page == SPECIAL_PAGE_OBJ_LAYER && m_special_subpage == 1)\n    {\n        layer_name = &g_editorStrings.layersLabelAttachedLayer;\n        layer_to_set = &EditorCursor.NPC.AttLayer;\n    }\n    else if(m_special_page == SPECIAL_PAGE_EVENT_LAYERS)\n    {\n        layer_name = &g_editorStrings.layersLabelMoveLayer;\n        layer_to_set = &Events[m_current_event].MoveLayer;\n    }\n    else\n    {\n        layer_name = &g_editorStrings.labelLayer;\n        layer_to_set = &EditorCursor.Layer;\n    }\n\n    // render description\n    if(mode == CallMode::Render && m_special_page == SPECIAL_PAGE_OBJ_LAYER && m_special_subpage == 1)\n    {\n        FontManager::printTextOptiPx(g_editorStrings.layersDescAtt,\n                                     e_ScreenW - 236, 80,\n                                     230,\n                                     FontManager::fontIdFromSmbxFont(3));\n//        SuperPrintR(mode, g_editorStrings.layersDescAtt, 3, e_ScreenW - 236, 80);\n//        SuperPrintR(mode, g_editorStrings.layersDescAtt2, 3, e_ScreenW - 236, 100);\n//        SuperPrintR(mode, g_editorStrings.layersDescAtt3, 3, e_ScreenW - 236, 120);\n//        SuperPrintR(mode, g_editorStrings.layersDescAtt4, 3, e_ScreenW - 236, 140);\n//        SuperPrintR(mode, g_editorStrings.layersDescAtt5, 3, e_ScreenW - 236, 160);\n    }\n\n    // render current layer\n    SuperPrintR(mode, *layer_name, 3, 10, 40);\n    if(*layer_to_set == LAYER_NONE)\n    {\n        if(m_special_subpage == 1 || m_special_page == SPECIAL_PAGE_EVENT_LAYERS)\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, 10, 56);\n        else\n            SuperPrintR(mode, g_editorStrings.layersLayerDefault, 3, 10, 56);\n    }\n    else\n        SuperPrintR(mode, GetL(*layer_to_set), 3, 10, 56);\n\n    // render layer selector\n    for(int i = 0; i < 10; i++)\n    {\n        int l;\n        // separate Default and None for Event layers\n        if(m_special_page == SPECIAL_PAGE_EVENT_LAYERS)\n            l = m_layers_page*10 + i - 1;\n        else\n            l = m_layers_page*10 + i;\n\n        if(l > maxLayers)\n            continue;\n\n        // separate None for Event layers\n        if(l == -1)\n        {\n            SuperPrintR(mode, g_mainMenu.caseNone, 3, 54, 80 + 40*i + 12);\n\n            if(m_special_page != SPECIAL_PAGE_EVENT_LAYERS && UpdateButton(mode, 10 + 4, 80 + 40*i + 4, GFX.ECursor[2], *layer_to_set == LAYER_NONE, 0, 0, 32, 32))\n                *layer_to_set = LAYER_NONE;\n\n            if(m_special_page == SPECIAL_PAGE_EVENT_LAYERS && UpdateButton(mode, 10 + 4, 80 + 40*i + 4, GFX.EIcons, *layer_to_set == LAYER_NONE, 0, 32*Icon::move, 32, 32))\n                *layer_to_set = LAYER_NONE;\n        }\n        // Default and None are the same for objects layers and for the editor cursor -- set to None if \"Default\".\n        else if(l == 0 && (m_special_page == SPECIAL_PAGE_OBJ_LAYER || m_special_page == SPECIAL_PAGE_LAYERS))\n        {\n            // AttLayer\n            if(m_special_page == SPECIAL_PAGE_OBJ_LAYER && m_special_subpage == 1)\n                SuperPrintR(mode, g_mainMenu.caseNone, 3, 54, 80 + 40*i + 12);\n            else\n                SuperPrintR(mode, g_editorStrings.layersLayerDefault, 3, 54, 80 + 40*i + 12);\n\n            if(UpdateButton(mode, 10 + 4, 80 + 40*i + 4, GFX.ECursor[2], *layer_to_set == LAYER_NONE || (*layer_to_set) == l, 0, 0, 32, 32))\n                *layer_to_set = LAYER_NONE;\n        }\n        else if(!Layer[l].Name.empty())\n        {\n            if(Layer[l].Name.length() < 20)\n                SuperPrintR(mode, Layer[l].Name, 3, 54, 80 + 40*i + 10);\n            else\n            {\n                SuperPrintR(mode, Layer[l].Name.substr(0,19), 3, 54, 80 + 40*i + 2);\n                SuperPrintR(mode, Layer[l].Name.substr(19), 3, 54, 80 + 40*i + 20);\n            }\n\n            if(m_special_page != SPECIAL_PAGE_EVENT_LAYERS && UpdateButton(mode, 10 + 4, 80 + 40*i + 4, GFX.ECursor[2], (*layer_to_set) == l, 0, 0, 32, 32))\n                *layer_to_set = l;\n\n            // extra buttons for layers page\n            if(m_special_page == SPECIAL_PAGE_LAYERS)\n            {\n                // rename, hide/show, shift up, shift down, delete\n                // hide/show\n                if(UpdateButton(mode, 440 + 4, 80 + 40*i + 4, GFX.EIcons, !Layer[l].Hidden, 0, 32*Icon::show, 32, 32))\n                {\n                    if(Layer[l].Hidden)\n                        ShowLayer(l);\n                    else\n                        HideLayer(l);\n                }\n\n                if(l <= 2)\n                    continue;\n\n                // rename\n                if(UpdateButton(mode, 400 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n                {\n                    DisableCursorNew();\n                    std::string new_name = TextEntryScreen::Run(g_editorStrings.layersPromptLayerName, Layer[l].Name);\n                    if(!new_name.empty())\n                        RenameLayer(l, new_name);\n                    s_fix_mouse_pos();\n                }\n\n                // shift up\n                if(l > 3 && UpdateButton(mode, 480 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::up, 32, 32))\n                    SwapLayers(l-1, l);\n\n                // shift down\n                if(l < numLayers - 1 && UpdateButton(mode, 520 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::down, 32, 32))\n                    SwapLayers(l, l+1);\n\n                // delete\n                if(l < numLayers && UpdateButton(mode, 560 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n                {\n                    m_special_page = SPECIAL_PAGE_LAYER_DELETION;\n                    m_special_subpage = l;\n                    return;\n                }\n            }\n            // extra buttons for event layers\n            else if(m_special_page == SPECIAL_PAGE_EVENT_LAYERS)\n            {\n                // nothing, hide, show, toggle\n                auto hide_it = std::find(Events[m_current_event].HideLayer.begin(), Events[m_current_event].HideLayer.end(), l);\n                auto show_it = std::find(Events[m_current_event].ShowLayer.begin(), Events[m_current_event].ShowLayer.end(), l);\n                auto togg_it = std::find(Events[m_current_event].ToggleLayer.begin(), Events[m_current_event].ToggleLayer.end(), l);\n                bool cur_hide = hide_it != Events[m_current_event].HideLayer.end();\n                bool cur_show = show_it != Events[m_current_event].ShowLayer.end();\n                bool cur_togg = togg_it != Events[m_current_event].ToggleLayer.end();\n\n                // no change\n                if(UpdateButton(mode, 400 + 4, 80 + 40*i + 4, GFX.EIcons, !cur_hide && !cur_show && !cur_togg, 0, 0, 1, 1))\n                {\n                    if(cur_hide) Events[m_current_event].HideLayer.erase(hide_it);\n                    if(cur_show) Events[m_current_event].ShowLayer.erase(show_it);\n                    if(cur_togg) Events[m_current_event].ToggleLayer.erase(togg_it);\n                }\n\n                // show layer\n                if(UpdateButton(mode, 440 + 4, 80 + 40*i + 4, GFX.EIcons, cur_show, 0, 32*Icon::show, 32, 32))\n                {\n                    if(cur_hide) Events[m_current_event].HideLayer.erase(hide_it);\n                    if(!cur_show) Events[m_current_event].ShowLayer.push_back(l);\n                    if(cur_togg) Events[m_current_event].ToggleLayer.erase(togg_it);\n                }\n\n                // hide layer\n                if(UpdateButton(mode, 480 + 4, 80 + 40*i + 4, GFX.EIcons, cur_hide, 0, 32*Icon::hide, 32, 32))\n                {\n                    if(!cur_hide) Events[m_current_event].HideLayer.push_back(l);\n                    if(cur_show) Events[m_current_event].ShowLayer.erase(show_it);\n                    if(cur_togg) Events[m_current_event].ToggleLayer.erase(togg_it);\n                }\n\n                // togg layer\n                if(UpdateButton(mode, 520 + 4, 80 + 40*i + 4, GFX.EIcons, cur_togg, 0, 32*Icon::toggle, 32, 32))\n                {\n                    if(cur_hide) Events[m_current_event].HideLayer.erase(hide_it);\n                    if(cur_show) Events[m_current_event].ShowLayer.erase(show_it);\n                    if(!cur_togg) Events[m_current_event].ToggleLayer.push_back(l);\n                }\n\n                if(UpdateButton(mode, 10 + 4, 80 + 40*i + 4, GFX.EIcons, (*layer_to_set) == l, 0, 32*Icon::move, 32, 32))\n                    *layer_to_set = l;\n            }\n        }\n        // create a new layer!\n        else if(m_special_page == SPECIAL_PAGE_LAYERS && l != 0 && !Layer[l - 1].Name.empty())\n        {\n            SuperPrintR(mode, g_editorStrings.layersItemNewLayer, 3, 54, 80 + 40*i + 10);\n            // rename only\n            if(UpdateButton(mode, 400 + 4, 80 + 40*i + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n            {\n                DisableCursorNew();\n                std::string new_name = TextEntryScreen::Run(g_editorStrings.layersPromptLayerName, \"\");                \n                if(!new_name.empty() && FindLayer(new_name) == LAYER_NONE)\n                {\n                    Layer[l] = Layer_t();\n                    Layer[l].Name = new_name;\n                    numLayers ++;\n                }\n\n                s_fix_mouse_pos();\n            }\n        }\n    }\n}\n\nbool EditorScreen::UpdateBlockButton(CallMode mode, int x, int y, int type, bool sel)\n{\n    int draw_width = 32, draw_height = 32;\n\n    if(!BlockIsSizable[type])\n    {\n        if(BlockWidth[type] > 0)\n            draw_width = BlockWidth[type];\n\n        if(BlockHeight[type] > 0)\n            draw_height = BlockHeight[type];\n    }\n\n    return UpdateButton(mode, x, y, GFXBlock[type], sel, 0, BlockFrame[type] * 32, draw_width, draw_height) && !sel;\n}\n\nvoid EditorScreen::UpdateBlock(CallMode mode, int x, int y, int type)\n{\n    if((type < 1) || (type >= maxBlockType))\n        return;\n\n    bool sel = EditorCursor.Block.Type == type;\n\n    if(UpdateBlockButton(mode, x, y, type, sel) && !sel)\n    {\n        // printf(\"Block %d\\n\", type);\n        SetEditorBlockType(type);\n    }\n}\n\nvoid EditorScreen::UpdateBlockGrid(CallMode mode, int x, int y, const int* types, int n_blocks, int n_cols)\n{\n    for(int i = 0; i < n_blocks; i ++)\n    {\n        int type = types[i];\n        int row = i / n_cols;\n        int col = i % n_cols;\n        UpdateBlock(mode, x + col * 40 + 4, y + row * 40 + 4, type);\n    }\n}\n\nvoid EditorScreen::UpdateBlockScreen(CallMode mode)\n{\n    // Block GUI\n    if(mode == CallMode::Render)\n    {\n        XRender::renderRect(e_ScreenW - 160, 40, 160, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n        XRender::renderRect(0, 40, 40, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n        XRender::renderRect(38, 40, 2, e_ScreenH - 40, XTColorF(0.25_n, 0.0_n, 0.5_n), true);\n    }\n\n    // Page selector\n    int last_category = -1;\n    int index = 0;\n\n    for(const EditorCustom::ItemPage_t& page : EditorCustom::block_pages)\n    {\n        if(page.category != last_category)\n        {\n            last_category = page.category;\n\n            if(mode == CallMode::Render && index != 0)\n                XRender::renderRect(0, 40 + -2 + (40 * index), 40, 4, XTColorF(0.25_n, 0.0_n, 0.5_n), true);\n        }\n\n        index++;\n\n        if(UpdateBlockButton(mode, 4, 4 + (40 * index), page.icon, m_Block_page == index))\n            m_Block_page = index;\n    }\n\n    // Magic Block toggle\n    if(mode == CallMode::Render)\n    {\n        FontManager::printTextOptiPx(g_editorStrings.toggleMagicBlock,\n                                     e_ScreenW - 120, 42,\n                                     120,\n                                     FontManager::fontIdFromSmbxFont(3));\n    }\n\n    if(UpdateButton(mode, e_ScreenW - 160 + 4, 40 + 4, GFXNPC[NPCID_COIN_SWITCH], MagicBlock::enabled, 0, 0, 32, 32))\n        MagicBlock::enabled = !MagicBlock::enabled;\n\n    // Resizing\n    if(BlockIsSizable[EditorCursor.Block.Type])\n    {\n        int H = ((int)EditorCursor.Block.Location.Height) / 32;\n        int W = ((int)EditorCursor.Block.Location.Width) / 32;\n\n        SuperPrintR(mode, g_editorStrings.blockLetterWidth  + std::to_string(W), 3, e_ScreenW - 160, 112);\n\n        if(W > 2 && UpdateButton(mode, e_ScreenW - 80 + 4, 100 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            EditorCursor.Block.Location.Width = 32 * (W - 1);\n\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 100 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            EditorCursor.Block.Location.Width = 32 * (W + 1);\n\n        SuperPrintR(mode, g_editorStrings.blockLetterHeight + std::to_string(H), 3, e_ScreenW - 160, 152);\n\n        if(H > 2 && UpdateButton(mode, e_ScreenW - 80 + 4, 140 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            EditorCursor.Block.Location.Height = 32 * (H - 1);\n\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 140 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            EditorCursor.Block.Location.Height = 32 * (H + 1);\n    }\n\n    // Legacy for Spin Block\n    if(FileFormat == FileFormats::LVL_PGEX && EditorCursor.Block.Type == 90)\n    {\n        SuperPrintR(mode, g_editorStrings.blockCanBreak, 3, e_ScreenW - 160, 106);\n        if(UpdateButton(mode, e_ScreenW - 40 + 4, 120 + 4, GFXBlock[188], EditorCursor.Block.forceSmashable, 0, 0, 32, 32, g_editorStrings.blockTooltipCanBreak.c_str()))\n            EditorCursor.Block.forceSmashable = !EditorCursor.Block.forceSmashable;\n    }\n    else\n    {\n        EditorCursor.Block.forceSmashable = 0;\n    }\n\n    // Slippy (\"SLICK\") and Invis\n    SuperPrintRightR(mode, g_editorStrings.blockSlick, 3, e_ScreenW - 40, 214);\n    if(UpdateCheckBox(mode, e_ScreenW - 40 + 4, 200 + 4, EditorCursor.Block.Slippy))\n        EditorCursor.Block.Slippy = !EditorCursor.Block.Slippy;\n\n    SuperPrintRightR(mode, g_editorStrings.blockInvis, 3, e_ScreenW - 40, 254);\n    if(UpdateCheckBox(mode, e_ScreenW - 40 + 4, 240 + 4, EditorCursor.Block.Invis))\n        EditorCursor.Block.Invis = !EditorCursor.Block.Invis;\n\n    // Contents\n    SuperPrintRightR(mode, g_editorStrings.blockInside, 3, e_ScreenW - 40, 294);\n    NPCID n_type = NPCID_NULL;\n    if(EditorCursor.Block.Special > 0 && EditorCursor.Block.Special <= 100)\n    {\n        n_type = NPCID_COIN_S3;\n        SuperPrintR(mode, \"x\" + std::to_string(EditorCursor.Block.Special), 3, e_ScreenW-80, 314);\n    }\n    else if(EditorCursor.Block.Special == 110)\n    {\n        n_type = (CommonFrame & 32) ? NPCID_INVINCIBILITY_POWER : NPCID_COIN_S3;\n    }\n    else if(EditorCursor.Block.Special > 1000)\n    {\n        n_type = NPCID(EditorCursor.Block.Special - 1000);\n    }\n\n    if(((n_type >= 1 && n_type <= maxNPCType) && UpdateNPCButton(mode, e_ScreenW - 40 + 4, 280 + 4, n_type, EditorCursor.Block.Special != 0))\n        || ((n_type < 1 || n_type > maxNPCType) && UpdateButton(mode, e_ScreenW - 40 + 4, 280 + 4, GFX.EIcons, EditorCursor.Block.Special != 0, 0, 32 * Icon::subscreen, 32, 32)))\n    {\n        m_special_page = SPECIAL_PAGE_BLOCK_CONTENTS;\n        FocusNPC();\n    }\n\n    // Events\n    SuperPrintRightR(mode, g_editorStrings.eventsHeader, 3, e_ScreenW - 40, 334);\n    SuperPrintR(mode, g_editorStrings.eventsLetterHit + GetE(EditorCursor.Block.TriggerHit), 3, e_ScreenW - 160, 360);\n\n    if(EditorCursor.Block.Type != 186 && EditorCursor.Block.Type != 457)\n    {\n        SuperPrintR(mode, g_editorStrings.eventsLetterDestroy + GetE(EditorCursor.Block.TriggerDeath), 3, e_ScreenW - 160, 380);\n        SuperPrintR(mode, g_editorStrings.eventsLetterLayerClear + GetE(EditorCursor.Block.TriggerLast), 3, e_ScreenW - 160, 400);\n    }\n    else\n    {\n        EditorCursor.Block.TriggerDeath = EVENT_NONE;\n        EditorCursor.Block.TriggerLast = EVENT_NONE;\n    }\n\n    if(UpdateButton(mode, e_ScreenW - 40 + 4, 320 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_OBJ_TRIGGERS;\n\n    // Layers\n    SuperPrintRightR(mode, g_editorStrings.labelLayer, 3, e_ScreenW - 40, 434);\n    if(EditorCursor.Block.Layer == LAYER_NONE)\n        SuperPrintR(mode, g_editorStrings.layersLayerDefault, 3, e_ScreenW - 160, 460);\n    else\n        SuperPrintR(mode, GetL(EditorCursor.Block.Layer), 3, e_ScreenW - 160, 460);\n\n    if(UpdateButton(mode, e_ScreenW - 40 + 4, 420 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_OBJ_LAYER;\n\n    // block selector\n    if(m_Block_page > 0 && m_Block_page <= (int)EditorCustom::block_pages.size())\n    {\n        const EditorCustom::ItemPage_t& page = EditorCustom::block_pages[m_Block_page - 1];\n\n        for(auto it = page.begin; it != page.end; ++it)\n        {\n            const EditorCustom::ItemFamily& family = **it;\n\n            if(mode == CallMode::Render)\n            {\n                int pix_len = SuperTextPixLen(family.name, 3);\n\n                if(40 + family.X * 40 + pix_len > e_ScreenW - 160)\n                    SuperPrint(family.name, 3, e_ScreenW - 160 - 4 - pix_len, 40 + family.Y * 20);\n                else\n                    SuperPrint(family.name, 3, 40 + family.X * 40 + 4, 40 + family.Y * 20);\n            }\n\n            UpdateBlockGrid(mode, 40 + family.X * 40, 60 + family.Y * 20, family.layout_pod.types.data(), family.layout_pod.types.size(), family.layout_pod.cols);\n        }\n    }\n}\n\nbool EditorScreen::UpdateBGOButton(CallMode mode, int x, int y, int type, bool sel)\n{\n    return UpdateButton(mode, x, y, GFXBackgroundBMP[type], sel, 0, BackgroundFrame[type] * BackgroundHeight[type], GFXBackground[type].w, BackgroundHeight[type]);\n}\n\nvoid EditorScreen::UpdateBGO(CallMode mode, int x, int y, int type)\n{\n    if((type < 1) || (type >= maxBackgroundType))\n        return;\n\n    bool sel = EditorCursor.Background.Type == type;\n    if(UpdateBGOButton(mode, x, y, type, sel) && !sel)\n    {\n        EditorCursor.Background.Type = type;\n        EditorCursor.Background.UpdateSortPriority();\n    }\n}\n\nvoid EditorScreen::UpdateBGOGrid(CallMode mode, int x, int y, const int* types, int n_bgos, int n_cols)\n{\n    for(int i = 0; i < n_bgos; i ++)\n    {\n        int type = types[i];\n        int row = i / n_cols;\n        int col = i % n_cols;\n        UpdateBGO(mode, x + col * 40 + 4, y + row * 40 + 4, type);\n    }\n}\n\nvoid EditorScreen::UpdateBGOScreen(CallMode mode)\n{\n    // BGO GUI\n    if(mode == CallMode::Render)\n    {\n        XRender::renderRect(e_ScreenW - 160, 40, 160, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n        XRender::renderRect(0, 40, 40, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n        XRender::renderRect(38, 40, 2, e_ScreenH - 40, XTColorF(0.25_n, 0.0_n, 0.5_n), true);\n    }\n\n\n    // Page selector\n    int last_category = -1;\n    int index = 0;\n\n    for(const EditorCustom::ItemPage_t& page : EditorCustom::bgo_pages)\n    {\n        if(page.category != last_category)\n        {\n            last_category = page.category;\n\n            if(mode == CallMode::Render && index != 0)\n                XRender::renderRect(0, 40 + -2 + (40 * index), 40, 4, XTColorF(0.25_n, 0.0_n, 0.5_n), true);\n        }\n\n        index++;\n\n        if(UpdateBGOButton(mode, 4, 4 + (40 * index), page.icon, m_BGO_page == index))\n            m_BGO_page = index;\n    }\n\n    // Magic Block toggle\n    if(mode == CallMode::Render)\n    {\n        FontManager::printTextOptiPx(g_editorStrings.toggleMagicBlock,\n                                     e_ScreenW - 120, 42,\n                                     120,\n                                     FontManager::fontIdFromSmbxFont(3));\n    }\n\n    if(UpdateButton(mode, e_ScreenW - 160 + 4, 40 + 4, GFXNPC[NPCID_COIN_SWITCH], MagicBlock::enabled, 0, 0, 32, 32))\n        MagicBlock::enabled = !MagicBlock::enabled;\n\n    // Z-Layer and Z-Offset\n    if(FileFormat == FileFormats::LVL_PGEX)\n    {\n        int layer = EditorCursor.Background.GetCustomLayer();\n        int offset = EditorCursor.Background.GetCustomOffset();\n\n        if(mode == CallMode::Render)\n        {\n            FontManager::printTextOptiPx(g_editorStrings.labelSortLayer,\n                                         e_ScreenW - 120, 102,\n                                         120,\n                                         FontManager::fontIdFromSmbxFont(3));\n\n            SuperPrint((layer == 0) ? g_editorStrings.layersLayerDefault : std::to_string(layer),\n                       3,\n                       e_ScreenW - 160, 142);\n        }\n\n        if(layer < 2 && UpdateButton(mode, e_ScreenW - 160 + 4, 100 + 4, GFX.EIcons, false, 0, 32*Icon::up, 32, 32))\n            EditorCursor.Background.SetSortPriority(layer + 1, offset);\n\n        if(layer > -2 && UpdateButton(mode, e_ScreenW - 160 + 4, 160 + 4, GFX.EIcons, false, 0, 32*Icon::down, 32, 32))\n            EditorCursor.Background.SetSortPriority(layer - 1, offset);\n\n        if(mode == CallMode::Render)\n        {\n            FontManager::printTextOptiPx(g_editorStrings.labelSortOffset,\n                                         e_ScreenW - 120, 222,\n                                         120,\n                                         FontManager::fontIdFromSmbxFont(3));\n\n            SuperPrint(std::to_string(offset),\n                       3,\n                       e_ScreenW - 160, 262);\n\n            // debug: total sort priority\n            SuperPrint(\"=\" + std::to_string(EditorCursor.Background.SortPriority),\n                       3,\n                       e_ScreenW - 140, 322);\n        }\n\n        if(offset < 3 && UpdateButton(mode, e_ScreenW - 160 + 4, 220 + 4, GFX.EIcons, false, 0, 32*Icon::up, 32, 32))\n            EditorCursor.Background.SetSortPriority(layer, offset + 1);\n\n        if(offset > -3 && UpdateButton(mode, e_ScreenW - 160 + 4, 280 + 4, GFX.EIcons, false, 0, 32*Icon::down, 32, 32))\n            EditorCursor.Background.SetSortPriority(layer, offset - 1);\n    }\n\n    // Layers\n    SuperPrintRightR(mode, g_editorStrings.labelLayer, 3, e_ScreenW - 40, 434);\n    if(EditorCursor.Background.Layer == LAYER_NONE)\n        SuperPrintR(mode, g_editorStrings.layersLayerDefault, 3, e_ScreenW - 160, 460);\n    else\n        SuperPrintR(mode, GetL(EditorCursor.Background.Layer), 3, e_ScreenW - 160, 460);\n\n    if(UpdateButton(mode, e_ScreenW - 40 + 4, 420 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_OBJ_LAYER;\n\n    // BGO selector\n    if(m_BGO_page > 0 && m_BGO_page <= (int)EditorCustom::bgo_pages.size())\n    {\n        const EditorCustom::ItemPage_t& page = EditorCustom::bgo_pages[m_BGO_page - 1];\n\n        for(auto it = page.begin; it != page.end; ++it)\n        {\n            const EditorCustom::ItemFamily& family = **it;\n\n            if(mode == CallMode::Render)\n            {\n                int pix_len = SuperTextPixLen(family.name, 3);\n\n                if(40 + family.X * 40 + pix_len > e_ScreenW - 160)\n                    SuperPrint(family.name, 3, e_ScreenW - 160 - 4 - pix_len, 40 + family.Y * 20);\n                else\n                    SuperPrint(family.name, 3, 40 + family.X * 40 + 4, 40 + family.Y * 20);\n            }\n\n            UpdateBGOGrid(mode, 40 + family.X * 40, 60 + family.Y * 20, family.layout_pod.types.data(), family.layout_pod.types.size(), family.layout_pod.cols);\n        }\n    }\n}\n\nvoid EditorScreen::UpdateAreaScreen(CallMode mode)\n{\n    SuperPrintR(mode, g_editorStrings.tooltipArea, 3, 200, 50);\n\n    int H = EditorCursor.WorldArea.Location.Height / 32;\n    int W = EditorCursor.WorldArea.Location.Width / 32;\n\n    // validation\n    if(H == 0)\n    {\n        H = 1;\n        EditorCursor.WorldArea.Location.Height = 32;\n    }\n\n    if(W == 0)\n    {\n        W = 1;\n        EditorCursor.WorldArea.Location.Width = 32;\n    }\n\n    // Width\n    SuperPrintRightR(mode, g_editorStrings.wordWidth + \" \" + std::to_string(W), 3, 330, 90);\n\n    if(W > 1 && UpdateButton(mode, 340 + 4, 80 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        EditorCursor.WorldArea.Location.Width = 32 * (W - 1);\n\n    if(UpdateButton(mode, 380 + 4, 80 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        EditorCursor.WorldArea.Location.Width = 32 * (W + 1);\n\n    // Height\n    SuperPrintRightR(mode, g_editorStrings.wordHeight + \" \" + std::to_string(H), 3, 330, 130);\n\n    if(H > 1 && UpdateButton(mode, 340 + 4, 120 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        EditorCursor.WorldArea.Location.Height = 32 * (H - 1);\n\n    if(UpdateButton(mode, 380 + 4, 120 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        EditorCursor.WorldArea.Location.Height = 32 * (H + 1);\n}\n\nvoid EditorScreen::UpdateWaterScreen(CallMode mode)\n{\n    SuperPrintR(mode, g_editorStrings.waterTitle, 3, 200, 50);\n\n    int H = ((int)EditorCursor.Water.Location.Height)/32;\n    int W = ((int)EditorCursor.Water.Location.Width)/32;\n\n    // validation\n    if(H == 0)\n    {\n        H = 1;\n        EditorCursor.Water.Location.Height = 32;\n    }\n\n    if(W == 0)\n    {\n        W = 1;\n        EditorCursor.Water.Location.Width = 32;\n    }\n\n    // Width\n    if(W >= 10)\n        SuperPrintRightR(mode, g_editorStrings.wordWidth + \" \" + std::to_string(W), 3, 330, 90);\n    else\n        SuperPrintRightR(mode, g_editorStrings.wordWidth + \" \" + std::to_string(W), 3, 330, 90);\n\n    if(W > 1 && UpdateButton(mode, 340 + 4, 80 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        EditorCursor.Water.Location.Width = 32 * (W - 1);\n\n    if(UpdateButton(mode, 380 + 4, 80 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        EditorCursor.Water.Location.Width = 32 * (W + 1);\n\n    // Height\n    if(H >= 10)\n        SuperPrintRightR(mode, g_editorStrings.wordHeight + \" \" + std::to_string(H), 3, 330, 130);\n    else\n        SuperPrintRightR(mode, g_editorStrings.wordHeight + \" \" + std::to_string(H), 3, 330, 130);\n\n    if(H > 1 && UpdateButton(mode, 340 + 4, 120 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        EditorCursor.Water.Location.Height = 32 * (H - 1);\n\n    if(UpdateButton(mode, 380 + 4, 120 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        EditorCursor.Water.Location.Height = 32 * (H + 1);\n\n    // mode\n    SuperPrintRightR(mode, g_editorStrings.wordMode, 3, 330, 170);\n\n    if(UpdateButton(mode, 340 + 4, 160 + 4, GFXBackgroundBMP[26], EditorCursor.Water.Type == PHYSID_WATER, 0, 0, 32, 32))\n        EditorCursor.Water.Type = PHYSID_WATER;\n\n    if(UpdateButton(mode, 380 + 4, 160 + 4, GFXBackgroundBMP[188], EditorCursor.Water.Type == PHYSID_QUICKSAND, 0, 0, 32, 32))\n        EditorCursor.Water.Type = PHYSID_QUICKSAND;\n\n    if(FileFormat == FileFormats::LVL_PGEX && UpdateButton(mode, 420 + 4, 160 + 4, GFXBlock[294], EditorCursor.Water.Type == PHYSID_MAZE, 0, 0, 32, 32))\n        EditorCursor.Water.Type = PHYSID_MAZE;\n\n    // layers\n    SuperPrintRightR(mode, g_editorStrings.labelLayer, 3, 330, 234);\n    if(EditorCursor.Layer == LAYER_NONE)\n        SuperPrintR(mode, g_editorStrings.layersLayerDefault, 3, 206, 260);\n    else\n        SuperPrintR(mode, GetL(EditorCursor.Layer), 3, 206, 260);\n\n    if(UpdateButton(mode, 340 + 4, 220 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_OBJ_LAYER;\n}\n\nvoid EditorScreen::UpdateWarpScreen(CallMode mode)\n{\n    // Warp GUI\n    if(mode == CallMode::Render)\n        XRender::renderRect(0, 40, e_ScreenW - 240, 140, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n    if(mode == CallMode::Render)\n        XRender::renderRect(e_ScreenW - 240, 40, 240, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n\n    SuperPrintR(mode, g_editorStrings.warpTitle, 3, 200, 50);\n\n    // prep for placement selection\n    if(EditorCursor.Warp.level == STRINGINDEX_NONE && !EditorCursor.Warp.LevelEnt && !EditorCursor.Warp.MapWarp)\n    {\n        if(EditorCursor.SubMode != 1 && EditorCursor.SubMode != 2)\n            EditorCursor.SubMode = 1;\n    }\n    else if(!EditorCursor.Warp.LevelEnt)\n    {\n        EditorCursor.Warp.twoWay = false;\n        EditorCursor.SubMode = 1;\n    }\n    else\n    {\n        EditorCursor.Warp.twoWay = false;\n        EditorCursor.SubMode = 2;\n    }\n\n    // placement selection and directions\n    if(!EditorCursor.Warp.LevelEnt)\n    {\n        SuperPrintR(mode, g_editorStrings.warpPlacing, 3, 40, 82);\n\n        SuperPrintRightR(mode, g_editorStrings.warpIn, 3, 80, 114);\n        if(UpdateButton(mode, 80 + 4, 100 + 4, GFX.EIcons, EditorCursor.SubMode == 1, 0, 32*Icon::action, 32, 32))\n            EditorCursor.SubMode = 1;\n\n        SuperPrintRightR(mode, g_editorStrings.warpDir, 3, 220, 114);\n\n        if(UpdateButton(mode, 220 + 4, 100 + 4, GFX.EIcons, EditorCursor.Warp.Direction == 1, 0, 32*Icon::up, 32, 32))\n            EditorCursor.Warp.Direction = 1;\n\n        if(UpdateButton(mode, 260 + 4, 100 + 4, GFX.EIcons, EditorCursor.Warp.Direction == 3, 0, 32*Icon::down, 32, 32))\n            EditorCursor.Warp.Direction = 3;\n\n        if(UpdateButton(mode, 300 + 4, 100 + 4, GFX.EIcons, EditorCursor.Warp.Direction == 2, 0, 32*Icon::left, 32, 32))\n            EditorCursor.Warp.Direction = 2;\n\n        if(UpdateButton(mode, 340 + 4, 100 + 4, GFX.EIcons, EditorCursor.Warp.Direction == 4, 0, 32*Icon::right, 32, 32))\n            EditorCursor.Warp.Direction = 4;\n    }\n    else\n    {\n        SuperPrintR(mode, g_editorStrings.warpPlacing, 3, 40, 122);\n    }\n\n    if(EditorCursor.Warp.level == STRINGINDEX_NONE && !EditorCursor.Warp.MapWarp)\n    {\n        SuperPrintRightR(mode, g_editorStrings.warpOut, 3, 80, 154);\n        if(UpdateButton(mode, 80 + 4, 140 + 4, GFX.EIcons, EditorCursor.SubMode == 2, 0, 32*Icon::action, 32, 32))\n            EditorCursor.SubMode = 2;\n\n        SuperPrintRightR(mode, g_editorStrings.warpDir, 3, 220, 154);\n\n        if(UpdateButton(mode, 220 + 4, 140 + 4, GFX.EIcons, EditorCursor.Warp.Direction2 == 1, 0, 32*Icon::down, 32, 32))\n            EditorCursor.Warp.Direction2 = 1;\n\n        if(UpdateButton(mode, 260 + 4, 140 + 4, GFX.EIcons, EditorCursor.Warp.Direction2 == 3, 0, 32*Icon::up, 32, 32))\n            EditorCursor.Warp.Direction2 = 3;\n\n        if(UpdateButton(mode, 300 + 4, 140 + 4, GFX.EIcons, EditorCursor.Warp.Direction2 == 2, 0, 32*Icon::right, 32, 32))\n            EditorCursor.Warp.Direction2 = 2;\n\n        if(UpdateButton(mode, 340 + 4, 140 + 4, GFX.EIcons, EditorCursor.Warp.Direction2 == 4, 0, 32*Icon::left, 32, 32))\n            EditorCursor.Warp.Direction2 = 4;\n    }\n\n    if(EditorCursor.Warp.level == STRINGINDEX_NONE && !EditorCursor.Warp.LevelEnt && !EditorCursor.Warp.MapWarp)\n    {\n        SuperPrintR(mode, g_editorStrings.warpTwoWay, 3, e_ScreenW - 240 + 40 + 4, 134);\n        if(UpdateCheckBox(mode, e_ScreenW - 240 + 4, 120 + 4, EditorCursor.Warp.twoWay))\n            EditorCursor.Warp.twoWay = !EditorCursor.Warp.twoWay;\n\n    }\n\n    // Events\n    if(FileFormat == FileFormats::LVL_PGEX)\n    {\n        if(mode == CallMode::Render)\n        {\n            SuperPrintRightAlign(g_editorStrings.eventsHeader, 3, e_ScreenW - 80, 334);\n            SuperPrint(g_editorStrings.eventsLetterEnter + GetE(EditorCursor.Warp.eventEnter), 3, e_ScreenW - 240, 360);\n        }\n\n        if(UpdateButton(mode, e_ScreenW - 80 + 4, 320 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n            m_special_page = SPECIAL_PAGE_OBJ_TRIGGERS;\n    }\n\n    // Layers\n    if(mode == CallMode::Render)\n    {\n        SuperPrintRightAlign(g_editorStrings.labelLayer, 3, e_ScreenW - 80, 414);\n        if(EditorCursor.Warp.Layer == LAYER_NONE)\n            SuperPrint(g_editorStrings.layersLayerDefault, 3, e_ScreenW - 240, 440);\n        else\n            SuperPrint(GetL(EditorCursor.Warp.Layer), 3, e_ScreenW - 240, 440);\n    }\n\n    if(UpdateButton(mode, e_ScreenW - 80 + 4, 400 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        m_special_page = SPECIAL_PAGE_OBJ_LAYER;\n\n    // page selector\n    if(UpdateButton(mode, e_ScreenW - 240 + 4, 180 + 4, GFXBlock[294], m_Warp_page == WARP_PAGE_MAIN, 0, 0, 32, 32))\n        m_Warp_page = WARP_PAGE_MAIN;\n\n    if(UpdateButton(mode, e_ScreenW - 240 + 4, 220 + 4, GFXNPC[31], m_Warp_page == WARP_PAGE_REQS, 0, 0, 32, 32))\n        m_Warp_page = WARP_PAGE_REQS;\n\n    if(UpdateButton(mode, e_ScreenW - 240 + 4, 260 + 4, GFXLevelBMP[2], m_Warp_page == WARP_PAGE_LEVEL, 0, 0, 32, 32))\n        m_Warp_page = WARP_PAGE_LEVEL;\n\n\n    // MAIN WARP PAGE\n\n    if(m_Warp_page == WARP_PAGE_MAIN)\n    {\n        std::string* style_name;\n\n        // warp effect\n        switch(EditorCursor.Warp.Effect)\n        {\n        default:\n            EditorCursor.Warp.Effect = 1;\n        //fallthrough\n        case 1:\n            style_name = &g_editorStrings.warpStylePipe;\n            break;\n        case 2:\n            style_name = &g_editorStrings.warpStyleDoor;\n            break;\n        case 0:\n            style_name = &g_editorStrings.warpStyleBlipInstant;\n            break;\n        case 3:\n            style_name = &g_editorStrings.warpStylePortal;\n            break;\n        }\n\n        SuperPrintR(mode, g_editorStrings.warpStyle + *style_name, 3, 6, 194);\n\n        if(UpdateButton(mode, 220 + 4, 180 + 4, GFXBlock[294], EditorCursor.Warp.Effect == 1, 0, 0, 32, 32))\n            EditorCursor.Warp.Effect = 1;\n\n        if(UpdateButton(mode, 260 + 4, 180 + 4, GFXBackgroundBMP[88], EditorCursor.Warp.Effect == 2, 0, 0, 32, 32))\n        {\n            EditorCursor.Warp.Effect = 2;\n            EditorCursor.Warp.Direction = 1;\n            EditorCursor.Warp.Direction2 = 1;\n        }\n\n        if(UpdateButton(mode, 300 + 4, 180 + 4, GFXBackgroundBMP[61], EditorCursor.Warp.Effect == 0, 0, 0, 32, 32))\n            EditorCursor.Warp.Effect = 0;\n\n        if(FileFormat == FileFormats::LVL_PGEX && UpdateButton(mode, 340 + 4, 180 + 4, GFXNPC[NPCID_HOMING_BALL], EditorCursor.Warp.Effect == 3, 0, 0, 32, 32))\n            EditorCursor.Warp.Effect = 3;\n\n        // fade effect\n        if(FileFormat == FileFormats::LVL_PGEX)\n        {\n            if(EditorCursor.Warp.transitEffect < 0 || EditorCursor.Warp.transitEffect >= (int)g_editorStrings.listWarpTransitNames.size())\n                EditorCursor.Warp.transitEffect = 0;\n\n            SuperPrintR(mode, g_editorStrings.warpEffect, 3, 6, 234);\n            if(mode == CallMode::Render)\n                SuperPrintRightAlign(g_editorStrings.listWarpTransitNames[EditorCursor.Warp.transitEffect], 3, 256, 234);\n\n            if(UpdateButton(mode, 260 + 4, 220 + 4, GFX.EIcons, EditorCursor.Warp.transitEffect, 0, 32*Icon::subscreen, 32, 32))\n                m_special_page = SPECIAL_PAGE_WARP_TRANSITION;\n        }\n\n        // allow / forbid\n        SuperPrintR(mode, g_editorStrings.warpAllow, 3, 6, 294);\n\n        SuperPrintRightR(mode, g_editorStrings.warpItem, 3, 220, 294);\n        if(UpdateCheckBox(mode, 220 + 4, 280 + 4, EditorCursor.Warp.WarpNPC))\n            EditorCursor.Warp.WarpNPC = !EditorCursor.Warp.WarpNPC;\n\n        SuperPrintRightR(mode, g_editorStrings.warpRide, 3, 360, 294);\n        if(UpdateCheckBox(mode, 360 + 4, 280 + 4, !EditorCursor.Warp.NoYoshi))\n            EditorCursor.Warp.NoYoshi = !EditorCursor.Warp.NoYoshi;\n\n        // cannon exit\n        if(FileFormat == FileFormats::LVL_PGEX)\n        {\n            SuperPrintR(mode, g_editorStrings.warpCannonExit, 3, 6, 354);\n\n            if(UpdateCheckBox(mode, 220 + 4, 340 + 4, EditorCursor.Warp.cannonExit))\n            {\n                EditorCursor.Warp.cannonExit = !EditorCursor.Warp.cannonExit;\n                if(!EditorCursor.Warp.cannonExit)\n                    EditorCursor.Warp.cannonExitSpeed = 0;\n            }\n\n            if(EditorCursor.Warp.cannonExit)\n            {\n                SuperPrintR(mode, g_editorStrings.warpSpeed + std::to_string(EditorCursor.Warp.cannonExitSpeed), 3, 26, 394);\n                if(EditorCursor.Warp.cannonExitSpeed > 1 && UpdateButton(mode, 180 + 4, 380 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n                    EditorCursor.Warp.cannonExitSpeed --;\n                if(EditorCursor.Warp.cannonExitSpeed <= 31 && UpdateButton(mode, 220 + 4, 380 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n                    EditorCursor.Warp.cannonExitSpeed ++;\n            }\n        }\n    }\n\n    if(m_Warp_page == WARP_PAGE_REQS)\n    {\n        // stars required\n        if(mode == CallMode::Render)\n        {\n            std::string need_star_count = fmt::format_ne(g_editorStrings.warpNeedStarCount, EditorCursor.Warp.Stars,\n                LanguageFormatNumber(EditorCursor.Warp.Stars, g_gameInfo.wordStarAccusativeSingular, g_gameInfo.wordStarAccusativeDual_Cnt, g_gameInfo.wordStarAccusativePlural),\n                g_gameInfo.wordStarAccusativeDual_Cnt);\n\n            SuperPrintR(mode, need_star_count, 3, 6, 194);\n        }\n\n        if(EditorCursor.Warp.Stars > 0 && UpdateButton(mode, 260 + 4, 180 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            EditorCursor.Warp.Stars --;\n        if(UpdateButton(mode, 300 + 4, 180 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            EditorCursor.Warp.Stars ++;\n\n        // new: StarsMsg\n        if(FileFormat == FileFormats::LVL_PGEX)\n        {\n            SuperPrintR(mode, g_editorStrings.warpStarLockMessage, 3, 44, 234);\n            if(UpdateButton(mode, 300 + 4, 220 + 4, GFX.EIcons, EditorCursor.Warp.StarsMsg != STRINGINDEX_NONE, 0, 32*Icon::pencil, 32, 32))\n            {\n                DisableCursorNew();\n                SetS(EditorCursor.Warp.StarsMsg, TextEntryScreen::Run(g_editorStrings.warpStarLockMessage, GetS(EditorCursor.Warp.StarsMsg)));\n                s_fix_mouse_pos();\n            }\n        }\n\n        SuperPrintR(mode, g_editorStrings.warpNeedKey, 3, 6, 274);\n        if(UpdateCheckBox(mode, 220 + 4, 260 + 4, EditorCursor.Warp.Locked))\n            EditorCursor.Warp.Locked = !EditorCursor.Warp.Locked;\n\n        // new: must stand to enter\n        if(FileFormat == FileFormats::LVL_PGEX)\n        {\n            SuperPrintR(mode, g_editorStrings.warpNeedFloor, 3, 6, 314);\n            if(UpdateCheckBox(mode, 220 + 4, 300 + 4, EditorCursor.Warp.stoodRequired))\n                EditorCursor.Warp.stoodRequired = !EditorCursor.Warp.stoodRequired;\n        }\n    }\n\n    if(m_Warp_page == WARP_PAGE_LEVEL)\n    {\n        // map/level warps\n        SuperPrintR(mode, g_editorStrings.warpToMap, 3, 6, 194);\n        if(UpdateCheckBox(mode, 120 + 4, 180 + 4, EditorCursor.Warp.MapWarp))\n        {\n            EditorCursor.Warp.MapWarp = !EditorCursor.Warp.MapWarp;\n            if(EditorCursor.Warp.MapWarp)\n            {\n                FreeS(EditorCursor.Warp.level);\n                EditorCursor.Warp.LevelEnt = false;\n            }\n        }\n\n        SuperPrintR(mode, g_editorStrings.warpLvlWarp, 3, 6, 234);\n\n        SuperPrintRightR(mode, g_editorStrings.warpIn, 3, 240, 234);\n        if(UpdateCheckBox(mode, 240 + 4, 220 + 4, EditorCursor.Warp.level != STRINGINDEX_NONE))\n        {\n            if(EditorCursor.Warp.level == STRINGINDEX_NONE)\n            {\n                SetS(EditorCursor.Warp.level, \"...\");\n                EditorCursor.Warp.MapWarp = false;\n                EditorCursor.Warp.LevelEnt = false;\n            }\n            else\n                FreeS(EditorCursor.Warp.level);\n        }\n\n        SuperPrintRightR(mode, g_editorStrings.warpOut, 3, 360, 234);\n        if(UpdateCheckBox(mode, 360 + 4, 220 + 4, EditorCursor.Warp.LevelEnt))\n        {\n            EditorCursor.Warp.LevelEnt = !EditorCursor.Warp.LevelEnt;\n            if(EditorCursor.Warp.LevelEnt)\n            {\n                FreeS(EditorCursor.Warp.level);\n                EditorCursor.Warp.MapWarp = false;\n            }\n        }\n\n        // special options for lvl warp entrance\n        if(EditorCursor.Warp.level != STRINGINDEX_NONE)\n        {\n            if(GetS(EditorCursor.Warp.level).length() <= 12)\n            {\n                SuperPrintR(mode, g_editorStrings.warpTarget + GetS(EditorCursor.Warp.level), 3, 6, 314);\n            }\n            else\n            {\n                SuperPrintR(mode, g_editorStrings.warpTarget + GetS(EditorCursor.Warp.level).substr(0,12), 3, 6, 302);\n                SuperPrintR(mode, GetS(EditorCursor.Warp.level).substr(12), 3, 24, 322);\n            }\n\n            if(UpdateButton(mode, 360 + 4, 300 + 4, GFX.EIcons, false, 0, 32*Icon::open, 32, 32))\n                StartFileBrowser(PtrS(EditorCursor.Warp.level), FileNamePath, \"\", {\".lvl\", \".lvlx\"}, BROWSER_MODE_OPEN);\n\n            // display this in a different way if modern options are enabled\n            if(EditorCursor.Warp.LevelWarp == 0)\n                SuperPrintR(mode, fmt::format_ne(g_editorStrings.warpTo, g_editorStrings.levelStartPos), 3, 46, 354);\n            else\n                SuperPrintR(mode, fmt::format_ne(g_editorStrings.warpTo, fmt::format_ne(g_editorStrings.phraseWarpIndex, EditorCursor.Warp.LevelWarp)), 3, 46, 354);\n\n            if(EditorCursor.Warp.LevelWarp > 0 && UpdateButton(mode, 4, 340 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n                EditorCursor.Warp.LevelWarp --;\n\n            if(EditorCursor.Warp.LevelWarp < maxWarps && UpdateButton(mode, 280 + 4, 340 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n                EditorCursor.Warp.LevelWarp ++;\n\n            if(FileFormat == FileFormats::LVL_PGEX)\n            {\n                // display the option to show/hide the level start scene\n                SuperPrintRightR(mode, g_editorStrings.warpShowStartScene, 3, 320, 414);\n                if(UpdateCheckBox(mode, 320 + 4, 400 + 4, !EditorCursor.Warp.noEntranceScene))\n                    EditorCursor.Warp.noEntranceScene = !EditorCursor.Warp.noEntranceScene;\n                // display the option to show/hide the level star count\n                SuperPrintRightR(mode, g_editorStrings.warpShowStarCount, 3, 320, 454);\n                if(UpdateButton(mode, 320 + 4, 440 + 4, GFXNPC[NPCID_STAR_EXIT], !EditorCursor.Warp.noPrintStars, 0, 0, 32, 32))\n                    EditorCursor.Warp.noPrintStars = !EditorCursor.Warp.noPrintStars;\n            }\n        }\n\n        // special options for map warp\n        if(EditorCursor.Warp.MapWarp)\n        {\n            // map warp!\n            SuperPrintR(mode, g_editorStrings.mapPos, 3, 44, 302);\n\n            if(EditorCursor.Warp.MapX != -1 || EditorCursor.Warp.MapY != -1)\n            {\n                SuperPrintR(mode, g_editorStrings.letterCoordX + \": \" + std::to_string(EditorCursor.Warp.MapX), 3, 4, 320);\n                SuperPrintR(mode, g_editorStrings.letterCoordY + \": \" + std::to_string(EditorCursor.Warp.MapY), 3, 4, 340);\n            }\n            else\n            {\n                SuperPrintR(mode, g_mainMenu.caseNone, 3, 4, 330);\n            }\n\n            if(UpdateButton(mode, 4, 360 + 4, GFX.EIcons, false, 0, 32*Icon::up, 32, 32))\n            {\n                EditorCursor.Warp.MapY = 32*(EditorCursor.Warp.MapY/32 - 1);\n                if(EditorCursor.Warp.MapX == -1)\n                    EditorCursor.Warp.MapX = 0.;\n            }\n\n            if(UpdateButton(mode, 40 + 4, 360 + 4, GFX.EIcons, false, 0, 32*Icon::down, 32, 32))\n            {\n                EditorCursor.Warp.MapY = 32*(EditorCursor.Warp.MapY/32 + 1);\n                if(EditorCursor.Warp.MapX == -1)\n                    EditorCursor.Warp.MapX = 0.;\n            }\n\n            if(UpdateButton(mode, 80 + 4, 360 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            {\n                EditorCursor.Warp.MapX = 32*(EditorCursor.Warp.MapX/32 - 1);\n                if(EditorCursor.Warp.MapY == -1)\n                    EditorCursor.Warp.MapY = 0.;\n            }\n\n            if(UpdateButton(mode, 120 + 4, 360 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            {\n                EditorCursor.Warp.MapX = 32*(EditorCursor.Warp.MapX/32 + 1);\n                if(EditorCursor.Warp.MapY == -1)\n                    EditorCursor.Warp.MapY = 0.;\n            }\n        }\n    }\n}\n\nbool EditorScreen::UpdateTileButton(CallMode mode, int x, int y, int type, bool sel)\n{\n    return UpdateButton(mode, x, y, GFXTileBMP[type], sel, 0, TileHeight[type] * TileFrame[type], TileWidth[type], TileHeight[type]);\n}\n\nvoid EditorScreen::UpdateTile(CallMode mode, int x, int y, int type)\n{\n    if((type < 1) || (type >= maxTileType))\n        return;\n\n    bool sel = EditorCursor.Tile.Type == type;\n    if(UpdateTileButton(mode, x, y, type, sel) && !sel)\n    {\n        // printf(\"Tile %d\\n\", type);\n        EditorCursor.Tile.Type = type;\n    }\n}\n\nvoid EditorScreen::UpdateTileGrid(CallMode mode, int x, int y, const int* types, int n_tiles, int n_cols)\n{\n    for(int i = 0; i < n_tiles; i ++)\n    {\n        int type = types[i];\n        int row = i / n_cols;\n        int col = i % n_cols;\n        UpdateTile(mode, x + col * 40 + 4, y + row * 40 + 4, type);\n    }\n}\n\nvoid EditorScreen::UpdateTileScreen(CallMode mode)\n{\n    // Block GUI\n    if(mode == CallMode::Render)\n    {\n        XRender::renderRect(0, 40, 40, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n        XRender::renderRect(38, 40, 2, e_ScreenH - 40, XTColorF(0.25_n, 0.0_n, 0.5_n), true);\n    }\n\n\n    // Page selector\n    int last_category = -1;\n    int index = 0;\n\n    for(const EditorCustom::ItemPage_t& page : EditorCustom::tile_pages)\n    {\n        if(page.category != last_category)\n        {\n            last_category = page.category;\n\n            if(mode == CallMode::Render && index != 0)\n                XRender::renderRect(0, 40 + -2 + (40 * index), 40, 4, XTColorF(0.25_n, 0.0_n, 0.5_n), true);\n        }\n\n        index++;\n\n        if(UpdateTileButton(mode, 4, 4 + (40 * index), page.icon, m_tile_page == index))\n            m_tile_page = index;\n    }\n\n\n    // Tile selector\n    if(m_tile_page > 0 && m_tile_page <= (int)EditorCustom::tile_pages.size())\n    {\n        const EditorCustom::ItemPage_t& page = EditorCustom::tile_pages[m_tile_page - 1];\n\n        for(auto it = page.begin; it != page.end; ++it)\n        {\n            const EditorCustom::ItemFamily& family = **it;\n\n            if(mode == CallMode::Render)\n            {\n                int pix_len = SuperTextPixLen(family.name, 3);\n\n                if(40 + family.X * 40 + pix_len > e_ScreenW)\n                    SuperPrint(family.name, 3, e_ScreenW - 4 - pix_len, 40 + family.Y * 20);\n                else\n                    SuperPrint(family.name, 3, 40 + family.X * 40 + 4, 40 + family.Y * 20);\n            }\n\n            UpdateTileGrid(mode, 40 + family.X * 40, 60 + family.Y * 20, family.layout_pod.types.data(), family.layout_pod.types.size(), family.layout_pod.cols);\n        }\n    }\n}\n\nvoid EditorScreen::UpdateScene(CallMode mode, int x, int y, int type)\n{\n    if((type < 1) || (type >= maxSceneType))\n        return;\n    bool sel = EditorCursor.Scene.Type == type;\n    if(UpdateButton(mode, x, y, GFXSceneBMP[type], sel, 0, SceneHeight[type] * SceneFrame[type], SceneWidth[type], SceneHeight[type]) && !sel)\n    {\n        // printf(\"%d\\n\", type);\n        EditorCursor.Scene.Type = type;\n    }\n}\n\nvoid EditorScreen::UpdateSceneGrid(CallMode mode, int x, int y, const int* types, int n_scenes, int n_cols)\n{\n    for(int i = 0; i < n_scenes; i ++)\n    {\n        int type = types[i];\n        int row = i / n_cols;\n        int col = i % n_cols;\n        UpdateScene(mode, x + col * 40 + 4, y + row * 40 + 4, type);\n    }\n}\n\nvoid EditorScreen::UpdateSceneScreen(CallMode mode)\n{\n    // Scene GUI (none)\n\n    // SuperPrintR(mode, \"SCENERY\", 3, 10, 40);\n    static const int scenes[] =\n    {\n        1,  2, 3, 4, 5, 6, 7, 8, 9, 10,\n        11, 12, 13, 14, 15, 16, 17, 18,\n        19, 20, 21, 22, 23, 24, 25, 26,\n        27, 28, 29, 30, 31, 32, 33, 34,\n        35, 36, 37, 38, 39, 40, 41, 48,\n        49, 42, 47, 46, 43, 45, 64, 65,\n        44, 50, 51, 52, 53, 54, 55, 56,\n        57, 58, 59, 60, 61, 62, 63\n    };\n    UpdateSceneGrid(mode, 0, 60, scenes, sizeof(scenes)/sizeof(int), 10);\n}\n\nvoid EditorScreen::UpdateLevel(CallMode mode, int x, int y, int type)\n{\n    if((type < 1) || (type >= maxLevelType))\n        return;\n\n    bool sel = EditorCursor.WorldLevel.Type == type;\n    int draw_height;\n\n    if(GFXLevelBig[type])\n        draw_height = GFXLevel[type].h;\n    else\n        draw_height = 32;\n\n    if(UpdateButton(mode, x, y, GFXLevelBMP[type], sel, 0, 32 * LevelFrame[type], GFXLevel[type].w, draw_height) && !sel)\n    {\n        // printf(\"%d\\n\", type);\n        EditorCursor.WorldLevel.Type = type;\n    }\n}\n\nvoid EditorScreen::UpdateLevelGrid(CallMode mode, int x, int y, const int* types, int n_levels, int n_cols)\n{\n    for(int i = 0; i < n_levels; i ++)\n    {\n        int type = types[i];\n        int row = i / n_cols;\n        int col = i % n_cols;\n        UpdateLevel(mode, x + col * 40 + 4, y + row * 40 + 4, type);\n    }\n}\n\nvoid EditorScreen::UpdateLevelScreen(CallMode mode)\n{\n    // World Level GUI\n    if(mode == CallMode::Render)\n        XRender::renderRect(e_ScreenW - 240, 40, 240, e_ScreenH - 40, XTColorF(0.7_n, 0.7_n, 0.9_n, 0.75_n), true);\n\n    // SuperPrintR(mode, \"LEVEL GRAPHIC\", 3, 10, 40);\n    static const int levels[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};\n    UpdateLevelGrid(mode, 0, 60, levels, sizeof(levels)/sizeof(int), 8);\n\n    // path bg - Path\n    if(UpdateCheckBox(mode, e_ScreenW - 240 + 4, 80+4, EditorCursor.WorldLevel.Path))\n    {\n        EditorCursor.WorldLevel.Path = !EditorCursor.WorldLevel.Path;\n        if(EditorCursor.WorldLevel.Path)\n            EditorCursor.WorldLevel.Path2 = false;\n    }\n    SuperPrintR(mode, g_editorStrings.levelPathBG, 3, e_ScreenW - 240 + 44, 90);\n\n    // big bg - Path2\n    if(UpdateCheckBox(mode, e_ScreenW - 240 + 4, 120+4, EditorCursor.WorldLevel.Path2))\n    {\n        EditorCursor.WorldLevel.Path2 = !EditorCursor.WorldLevel.Path2;\n        if(EditorCursor.WorldLevel.Path2)\n            EditorCursor.WorldLevel.Path = false;\n    }\n    SuperPrintR(mode, g_editorStrings.levelBigBG, 3, e_ScreenW - 240 + 44, 130);\n\n    // game start - Start\n    if(UpdateCheckBox(mode, e_ScreenW - 240 + 4, 160 + 4, EditorCursor.WorldLevel.Start))\n        EditorCursor.WorldLevel.Start = !EditorCursor.WorldLevel.Start;\n    SuperPrintR(mode, g_editorStrings.levelGameStart, 3, e_ScreenW - 240 + 44, 170);\n\n    // always visible - Visible\n    if(UpdateCheckBox(mode, e_ScreenW - 240 + 4, 200 + 4, EditorCursor.WorldLevel.Visible))\n        EditorCursor.WorldLevel.Visible = !EditorCursor.WorldLevel.Visible;\n    SuperPrintR(mode, g_editorStrings.levelAlwaysVis, 3, e_ScreenW - 240 + 44, 210);\n\n    // map warp!\n    SuperPrintR(mode, g_editorStrings.mapPos, 3, e_ScreenW - 240 + 44, 302);\n\n    if(EditorCursor.WorldLevel.WarpX != -1 || EditorCursor.WorldLevel.WarpY != -1)\n    {\n        SuperPrintR(mode, g_editorStrings.letterCoordX + \": \" + std::to_string(EditorCursor.WorldLevel.WarpX), 3, e_ScreenW - 240 + 4, 320);\n        SuperPrintR(mode, g_editorStrings.letterCoordY + \": \" + std::to_string(EditorCursor.WorldLevel.WarpY), 3, e_ScreenW - 240 + 4, 340);\n        if(UpdateButton(mode, e_ScreenW - 240 + 160 + 4, 320 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n        {\n            EditorCursor.WorldLevel.WarpX = -1;\n            EditorCursor.WorldLevel.WarpY = -1;\n        }\n    }\n    else\n    {\n        SuperPrintR(mode, g_mainMenu.caseNone, 3, e_ScreenW - 240 + 4, 330);\n    }\n\n    if(UpdateButton(mode, e_ScreenW - 240 + 4, 360 + 4, GFX.EIcons, false, 0, 32*Icon::up, 32, 32))\n    {\n        EditorCursor.WorldLevel.WarpY = 32*(EditorCursor.WorldLevel.WarpY/32 - 1);\n        if(EditorCursor.WorldLevel.WarpX == -1)\n            EditorCursor.WorldLevel.WarpX = 0.;\n    }\n\n    if(UpdateButton(mode, e_ScreenW - 240 + 40 + 4, 360 + 4, GFX.EIcons, false, 0, 32*Icon::down, 32, 32))\n    {\n        EditorCursor.WorldLevel.WarpY = 32*(EditorCursor.WorldLevel.WarpY/32 + 1);\n        if(EditorCursor.WorldLevel.WarpX == -1)\n            EditorCursor.WorldLevel.WarpX = 0.;\n    }\n\n    if(UpdateButton(mode, e_ScreenW - 240 + 80 + 4, 360 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n    {\n        EditorCursor.WorldLevel.WarpX = 32*(EditorCursor.WorldLevel.WarpX/32 - 1);\n        if(EditorCursor.WorldLevel.WarpY == -1)\n            EditorCursor.WorldLevel.WarpY = 0.;\n    }\n\n    if(UpdateButton(mode, e_ScreenW - 240 + 120 + 4, 360 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n    {\n        EditorCursor.WorldLevel.WarpX = 32*(EditorCursor.WorldLevel.WarpX/32 + 1);\n        if(EditorCursor.WorldLevel.WarpY == -1)\n            EditorCursor.WorldLevel.WarpY = 0.;\n    }\n\n    // bottom pane: level filename, entrance and exits\n    if(mode == CallMode::Render)\n        XRender::renderRect(0, e_ScreenH - 240, e_ScreenW - 240, 240, XTColorF(0.6_n, 0.6_n, 0.8_n), true);\n\n    // level name - LevelName\n    SuperPrintR(mode, g_editorStrings.levelName, 3, 10 + 44, e_ScreenH - 240 + 2);\n    SuperPrintR(mode, EditorCursor.WorldLevel.LevelName.substr(0, 19), 3, 10 + 44, e_ScreenH - 240 + 20);\n    if(EditorCursor.WorldLevel.LevelName.length() > 19)\n        SuperPrintR(mode, EditorCursor.WorldLevel.LevelName.substr(19), 3, 10 + 44 + 18, e_ScreenH - 240 + 38);\n\n    if(UpdateButton(mode, 10 + 4, e_ScreenH - 240 + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n    {\n        DisableCursorNew();\n        EditorCursor.WorldLevel.LevelName = TextEntryScreen::Run(g_editorStrings.levelName, EditorCursor.WorldLevel.LevelName);\n        s_fix_mouse_pos();\n    }\n\n    // level filename - FileName\n    SuperPrintR(mode, g_editorStrings.warpTarget, 3, 10 + 44, e_ScreenH - 180 + 2);\n    if(!EditorCursor.WorldLevel.FileName.empty())\n        SuperPrintR(mode, EditorCursor.WorldLevel.FileName, 3, 10 + 44, e_ScreenH - 180 + 20);\n    else\n        SuperPrintR(mode, g_mainMenu.caseNone, 3, 10 + 44, e_ScreenH - 180 + 20);\n\n    if(UpdateButton(mode, 10 + 4, e_ScreenH - 180 + 4, GFX.EIcons, false, 0, 32*Icon::open, 32, 32))\n        StartFileBrowser(&EditorCursor.WorldLevel.FileName, FileNamePath, \"\", {\".lvl\", \".lvlx\"}, BROWSER_MODE_OPEN);\n\n    if(!EditorCursor.WorldLevel.FileName.empty())\n    {\n        // entrance warp - StartWarp\n        if(EditorCursor.WorldLevel.StartWarp > 0 && UpdateButton(mode, 10 + 4, e_ScreenH - 180 + 40 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n            EditorCursor.WorldLevel.StartWarp -= 1;\n        if(EditorCursor.WorldLevel.StartWarp < maxWarps && UpdateButton(mode, 280 + 4, e_ScreenH - 180 + 40 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n            EditorCursor.WorldLevel.StartWarp += 1;\n\n        if(EditorCursor.WorldLevel.StartWarp == 0)\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.warpTo, g_editorStrings.levelStartPos), 3, 10 + 44, e_ScreenH - 180 + 40 + 12);\n        else\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.warpTo, fmt::format_ne(g_editorStrings.phraseWarpIndex, EditorCursor.WorldLevel.StartWarp)), 3, 10 + 44, e_ScreenH - 180 + 40 + 12);\n\n        // exits\n        SuperPrintR(mode, g_editorStrings.levelPathUnlocks, 3, 10 + 44, e_ScreenH - 180 + 80 + 2);\n\n        using Controls::PlayerControls::Buttons;\n\n        if(UpdateButton(mode, 10 + 4, e_ScreenH - 180 + 100 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_LEVEL_EXIT;\n            m_special_subpage = 1;\n        }\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Up), 3, 10 + 44, e_ScreenH - 180 + 102);\n        SuperPrintR(mode, EditorCustom::list_level_exit_names[EditorCursor.WorldLevel.LevelExit[1]+1], 3, 10 + 44, e_ScreenH - 180 + 120);\n\n        if(UpdateButton(mode, (e_ScreenW - 240)/2 + 10 + 4, e_ScreenH - 180 + 100 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_LEVEL_EXIT;\n            m_special_subpage = 3;\n        }\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Down), 3, (e_ScreenW - 240)/2 + 10 + 44, e_ScreenH - 180 + 102);\n        SuperPrintR(mode, EditorCustom::list_level_exit_names[EditorCursor.WorldLevel.LevelExit[3]+1], 3, (e_ScreenW - 240)/2 + 10 + 44, e_ScreenH - 180 + 120);\n\n        if(UpdateButton(mode, 10 + 4, e_ScreenH - 180 + 140 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_LEVEL_EXIT;\n            m_special_subpage = 2;\n        }\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Left), 3, 10 + 44, e_ScreenH - 180 + 142);\n        SuperPrintR(mode, EditorCustom::list_level_exit_names[EditorCursor.WorldLevel.LevelExit[2]+1], 3, 10 + 44, e_ScreenH - 180 + 160);\n\n        if(UpdateButton(mode, (e_ScreenW - 240)/2 + 10 + 4, e_ScreenH - 180 + 140 + 4, GFX.EIcons, false, 0, 32*Icon::subscreen, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_LEVEL_EXIT;\n            m_special_subpage = 4;\n        }\n        SuperPrintR(mode, GetButtonName_UI(Buttons::Right), 3, (e_ScreenW - 240)/2 + 10 + 44, e_ScreenH - 180 + 142);\n        SuperPrintR(mode, EditorCustom::list_level_exit_names[EditorCursor.WorldLevel.LevelExit[4]+1], 3, (e_ScreenW - 240)/2 + 10 + 44, e_ScreenH - 180 + 160);\n    }\n\n}\n\nvoid EditorScreen::UpdatePath(CallMode mode, int x, int y, int type)\n{\n    if((type < 1) || (type >= maxPathType))\n        return;\n\n    bool sel = EditorCursor.WorldPath.Type == type;\n    if(UpdateButton(mode, x, y, GFXPathBMP[type], sel, 0, 0, 32, 32) && !sel)\n    {\n        // printf(\"%d\\n\", type);\n        EditorCursor.WorldPath.Type = type;\n    }\n}\n\nvoid EditorScreen::UpdatePathGrid(CallMode mode, int x, int y, const int* types, int n_paths, int n_cols)\n{\n    for(int i = 0; i < n_paths; i ++)\n    {\n        int type = types[i];\n        int row = i / n_cols;\n        int col = i % n_cols;\n        UpdatePath(mode, x + col * 40 + 4, y + row * 40 + 4, type);\n    }\n}\n\nvoid EditorScreen::UpdatePathScreen(CallMode mode)\n{\n    // Path GUI (none)\n\n    // SuperPrintR(mode, \"PATH GRAPHIC\", 3, 10, 40);\n    static const int paths[] =\n    {\n        2,  1, 18, 19, 31, 22, 32,\n        11, 6, 10, 30, 25, 27, 23,\n         9, 5,  7, 21,  3, 20, 26,\n        13, 8, 12, 29, 24, 28,  4\n    };\n    UpdatePathGrid(mode, 0, 60, paths, sizeof(paths)/sizeof(int), 7);\n}\n\nvoid EditorScreen::UpdateFileScreen(CallMode mode)\n{\n    if(m_special_page == SPECIAL_PAGE_FILE_CONFIRM)\n    {\n        if(m_special_subpage == 0)\n        {\n            m_special_page = SPECIAL_PAGE_FILE;\n            m_special_subpage = 0;\n            return;\n        }\n\n        bool can_save;\n        std::string* action;\n        if(m_special_subpage == 1)\n        {\n            can_save = true;\n            action = &g_editorStrings.fileActionClearLevel;\n        }\n        else if(m_special_subpage == 11)\n        {\n            can_save = true;\n            action = &g_editorStrings.fileActionClearWorld;\n        }\n        else if(m_special_subpage == 2 || m_special_subpage == 12)\n        {\n            can_save = true;\n            action = &g_editorStrings.fileActionOpen;\n        }\n        else if(m_special_subpage == 3 || m_special_subpage == 13)\n        {\n            can_save = false;\n            action = &g_editorStrings.fileActionRevert;\n        }\n        else if(m_special_subpage == 4)\n        {\n            can_save = true;\n            action = &g_editorStrings.fileActionExit;\n        }\n        else // should never happen\n        {\n            return;\n        }\n\n        if(can_save)\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.fileConfirmationSaveBeforeAction, *action), 3, 10, 50);\n        else\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.fileConfirmationConfirmAction, *action), 3, 10, 50);\n\n        bool confirmed = false;\n        if(can_save)\n        {\n            SuperPrintR(mode, fmt::format_ne(g_editorStrings.fileOptionYesSaveThenAction, *action), 3, 60, 110);\n            if(UpdateButton(mode, 20 + 4, 100 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n            {\n                if(!WorldEditor)\n                    SaveLevel(FullFileName, FileFormat);\n                else\n                    SaveWorld(FullFileName, FileFormat);\n                confirmed = true;\n            }\n        }\n\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.fileOptionActionWithoutSave, *action), 3, 60, 150);\n        if(UpdateButton(mode, 20 + 4, 140 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            confirmed = true;\n        }\n\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.fileOptionCancelAction, *action), 3, 60, 190);\n        if(UpdateButton(mode, 20 + 4, 180 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_FILE;\n            m_special_subpage = 0;\n            return;\n        }\n\n        if(confirmed)\n        {\n            if(m_special_subpage == 1) // new level\n            {\n                int cur_FileFormat = FileFormat;\n                if((cur_FileFormat < 0) || (cur_FileFormat > 2))\n                    cur_FileFormat = FileFormats::LVL_PGEX;\n\n                if(cur_FileFormat == FileFormats::LVL_SMBX64 || cur_FileFormat == FileFormats::LVL_SMBX38A)\n                    StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".lvl\"}, BROWSER_MODE_SAVE_NEW, BROWSER_CALLBACK_NEW_LEVEL);\n                else\n                    StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".lvlx\"}, BROWSER_MODE_SAVE_NEW, BROWSER_CALLBACK_NEW_LEVEL);\n            }\n            else if(m_special_subpage == 2) // open level\n            {\n                StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".lvlx\", \".lvl\"}, BROWSER_MODE_OPEN, BROWSER_CALLBACK_OPEN_LEVEL);\n            }\n            else if(m_special_subpage == 3) // revert level\n            {\n                OpenLevel(FullFileName);\n                m_special_page = SPECIAL_PAGE_FILE;\n                m_special_subpage = 0;\n            }\n            else if(m_special_subpage == 11) // new world\n            {\n                int cur_FileFormat = FileFormat;\n                if((cur_FileFormat < 0) || (cur_FileFormat > 2))\n                    cur_FileFormat = FileFormats::LVL_PGEX;\n\n                if(cur_FileFormat == FileFormats::LVL_SMBX64 || cur_FileFormat == FileFormats::LVL_SMBX38A)\n                    StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".wld\"}, BROWSER_MODE_SAVE_NEW, BROWSER_CALLBACK_NEW_WORLD);\n                else\n                    StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".wldx\"}, BROWSER_MODE_SAVE_NEW, BROWSER_CALLBACK_NEW_WORLD);\n            }\n            else if(m_special_subpage == 12) // open world\n            {\n                StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".wldx\", \".wld\"}, BROWSER_MODE_OPEN, BROWSER_CALLBACK_OPEN_WORLD);\n            }\n            else if(m_special_subpage == 13) // revert world\n            {\n                OpenWorld(FullFileName);\n                m_special_page = SPECIAL_PAGE_FILE;\n                m_special_subpage = 0;\n            }\n            else if(m_special_subpage == 4) // exit\n            {\n                if(g_config.EnableInterLevelFade)\n                {\n                    g_levelScreenFader.setupFader(4, 0, 65, ScreenFader::S_FADE);\n                    g_worldScreenFader.setupFader(4, 0, 65, ScreenFader::S_FADE);\n                }\n                else\n                {\n                    g_levelScreenFader.setupFader(65, 0, 65, ScreenFader::S_FADE);\n                    g_worldScreenFader.setupFader(65, 0, 65, ScreenFader::S_FADE);\n                }\n                editorWaitForFade();\n\n                ClearLevel();\n                ClearWorld();\n                GameMenu = true;\n                MenuMode = 0;\n                MenuCursor = 0;\n                LevelEditor = false;\n                WorldEditor = false;\n                TestLevel = false;\n                m_special_page = SPECIAL_PAGE_NONE;\n                m_special_subpage = 0;\n                testStartWarp = 0;\n            }\n        }\n        return;\n    }\n\n    if(m_special_page == SPECIAL_PAGE_FILE_CONVERT)\n    {\n        // TODO: find a way to warn them if this would overwrite something\n\n        const std::string* format = (m_special_subpage == FileFormats::LVL_PGEX) ? &g_editorStrings.fileFormatModern : &g_editorStrings.fileFormatLegacy;\n\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.fileConfirmationConvertFormatTo, *format), 3, 10, 50);\n\n        if(mode == CallMode::Render)\n        {\n            FontManager::printTextOptiPx(g_editorStrings.fileConvertDesc,\n                                         20, 90,\n                                         606,\n                                         FontManager::fontIdFromSmbxFont(4));\n        }\n\n        SuperPrintR(mode, g_editorStrings.fileOptionProceedWithConversion, 3, 60, 390);\n        if(UpdateButton(mode, 20 + 4, 380 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            FileFormat = m_special_subpage;\n            if(FileFormat == FileFormats::LVL_SMBX64 || FileFormat == FileFormats::LVL_SMBX38A)\n            {\n                if(!FileNameFull.empty() && FileNameFull.back() == 'x')\n                    FileNameFull.resize(FileNameFull.size() - 1);\n                if(!FullFileName.empty() && FullFileName.back() == 'x')\n                    FullFileName.resize(FullFileName.size() - 1);\n            }\n            else\n            {\n                if(!FileNameFull.empty() && FileNameFull.back() != 'x')\n                    FileNameFull += \"x\";\n                if(!FullFileName.empty() && FullFileName.back() != 'x')\n                    FullFileName += \"x\";\n            }\n\n            if(WorldEditor)\n            {\n                // ConvertWorld(m_special_subpage);\n                SaveWorld(FullFileName, FileFormat);\n                OpenWorld(FullFileName);\n            }\n            else\n            {\n                // ConvertLevel(m_special_subpage);\n                SaveLevel(FullFileName, FileFormat);\n                OpenLevel(FullFileName);\n            }\n\n            Integrator::setEditorFile(FileName);\n\n            m_special_page = SPECIAL_PAGE_FILE;\n            m_special_subpage = 0;\n        }\n\n        SuperPrintR(mode, g_editorStrings.fileOptionCancelConversion, 3, 60, 430);\n        if(UpdateButton(mode, 20 + 4, 420 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_FILE;\n            m_special_subpage = 0;\n        }\n\n        return;\n    }\n\n    SuperPrintR(mode, g_editorStrings.fileLabelCurrentFile + FileNameFull, 3, 10, 50);\n\n    if(selWorld == 0)\n    {\n        SuperPrintR(mode, g_editorStrings.fileLabelFormat, 3, 150, 90);\n\n        if(UpdateButton(mode, 320 + 4, 80 + 4, GFXNPC[NPCID_POWER_S3], FileFormat == FileFormats::LVL_SMBX64, 0, 0, 32, 32, g_editorStrings.fileFormatLegacy.c_str()) && FileFormat != FileFormats::LVL_SMBX64)\n        {\n            m_special_page = SPECIAL_PAGE_FILE_CONVERT;\n            m_special_subpage = FileFormats::LVL_SMBX64;\n        }\n\n        if(UpdateButton(mode, 360 + 4, 80 + 4, GFX.EIcons, FileFormat == FileFormats::LVL_PGEX, 0, 32*Icon::thextech, 32, 32, g_editorStrings.fileFormatModern.c_str()) && FileFormat != FileFormats::LVL_PGEX)\n        {\n            m_special_page = SPECIAL_PAGE_FILE_CONVERT;\n            m_special_subpage = FileFormats::LVL_PGEX;\n        }\n    }\n\n    SuperPrintR(mode, g_editorStrings.fileSectionLevel, 3, 110, 140);\n\n    SuperPrintR(mode, g_editorStrings.fileCommandNew, 3, 54, 170);\n    if(UpdateButton(mode, 10 + 4, 160 + 4, GFX.EIcons, false, 0, 32*Icon::newf, 32, 32))\n    {\n        m_special_page = SPECIAL_PAGE_FILE_CONFIRM;\n        m_special_subpage = 1;\n    }\n\n    SuperPrintR(mode, g_editorStrings.fileCommandOpen, 3, 54, 210);\n    if(UpdateButton(mode, 10 + 4, 200 + 4, GFX.EIcons, false, 0, 32*Icon::open, 32, 32))\n    {\n        m_special_page = SPECIAL_PAGE_FILE_CONFIRM;\n        m_special_subpage = 2;\n    }\n\n    if(!WorldEditor)\n    {\n        SuperPrintR(mode, g_editorStrings.fileCommandSave, 3, 54, 250);\n        if(UpdateButton(mode, 10 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::save, 32, 32))\n        {\n            SaveLevel(FullFileName, FileFormat);\n        }\n\n        SuperPrintR(mode, g_editorStrings.fileCommandSaveAs, 3, 54, 290);\n        if(UpdateButton(mode, 10 + 4, 280 + 4, GFX.EIcons, false, 0, 32*Icon::save, 32, 32))\n        {\n            if(FileFormat == 1 || FileFormat == 2)\n                StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".lvl\"}, BROWSER_MODE_SAVE, BROWSER_CALLBACK_SAVE_LEVEL);\n            else\n                StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".lvlx\"}, BROWSER_MODE_SAVE, BROWSER_CALLBACK_SAVE_LEVEL);\n        }\n\n        SuperPrintR(mode, g_editorStrings.fileActionRevert, 3, 54, 330);\n        if(UpdateButton(mode, 10 + 4, 320 + 4, GFX.EIcons, false, 0, 32*Icon::hop, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_FILE_CONFIRM;\n            m_special_subpage = 3;\n        }\n    }\n\n    SuperPrintR(mode, g_editorStrings.fileSectionWorld, 3, e_ScreenW/2 + 110, 140);\n\n    SuperPrintR(mode, g_editorStrings.fileCommandNew, 3, e_ScreenW/2 + 54, 170);\n    if(UpdateButton(mode, e_ScreenW/2 + 10 + 4, 160 + 4, GFX.EIcons, false, 0, 32*Icon::newf, 32, 32))\n    {\n        m_special_page = SPECIAL_PAGE_FILE_CONFIRM;\n        m_special_subpage = 11;\n    }\n\n    SuperPrintR(mode, g_editorStrings.fileCommandOpen, 3, e_ScreenW/2 + 54, 210);\n    if(UpdateButton(mode, e_ScreenW/2 + 10 + 4, 200 + 4, GFX.EIcons, false, 0, 32*Icon::open, 32, 32))\n    {\n        m_special_page = SPECIAL_PAGE_FILE_CONFIRM;\n        m_special_subpage = 12;\n    }\n\n    if(WorldEditor)\n    {\n        SuperPrintR(mode, g_editorStrings.fileCommandSave, 3, e_ScreenW/2 + 54, 250);\n        if(UpdateButton(mode, e_ScreenW/2 + 10 + 4, 240 + 4, GFX.EIcons, false, 0, 32*Icon::save, 32, 32))\n        {\n            SaveWorld(FullFileName, FileFormat);\n        }\n\n        SuperPrintR(mode, g_editorStrings.fileCommandSaveAs, 3, e_ScreenW/2 + 54, 290);\n        if(UpdateButton(mode, e_ScreenW/2 + 10 + 4, 280 + 4, GFX.EIcons, false, 0, 32*Icon::save, 32, 32))\n        {\n            if(FileFormat == 1 || FileFormat == 2)\n                StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".wld\"}, BROWSER_MODE_SAVE, BROWSER_CALLBACK_SAVE_WORLD);\n            else\n                StartFileBrowser(&FullFileName, \"\", FileNamePath, {\".wldx\"}, BROWSER_MODE_SAVE, BROWSER_CALLBACK_SAVE_WORLD);\n        }\n\n        SuperPrintR(mode, g_editorStrings.fileActionRevert, 3, e_ScreenW/2 + 54, 330);\n        if(UpdateButton(mode, e_ScreenW/2 + 10 + 4, 320 + 4, GFX.EIcons, false, 0, 32*Icon::hop, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_FILE_CONFIRM;\n            m_special_subpage = 13;\n        }\n    }\n\n    SuperPrintR(mode, g_editorStrings.fileActionExit, 3, 54, 410);\n    if(UpdateButton(mode, 10 + 4, 400 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n    {\n        m_special_page = SPECIAL_PAGE_FILE_CONFIRM;\n        m_special_subpage = 4;\n    }\n}\n\nvoid EditorScreen::StartFileBrowser(std::string* file_target,\n                                    const std::string &root_path,\n                                    const std::string &current_path,\n                                    const std::vector<std::string> &target_exts,\n                                    BrowserMode_t browser_mode,\n                                    BrowserCallback_t browser_callback)\n{\n    m_file_target = file_target;\n    m_root_path = root_path;\n    m_target_exts = target_exts;\n    m_browser_mode = browser_mode;\n    m_browser_callback = browser_callback;\n    m_cur_path = current_path;\n    m_path_synced = false;\n    m_last_special_page = m_special_page;\n    m_special_page = SPECIAL_PAGE_BROWSER;\n    m_special_subpage = 0;\n}\n\nvoid EditorScreen::FileBrowserSuccess()\n{\n    if(m_file_target)\n        *m_file_target = m_cur_file;\n\n    if(m_browser_callback == BROWSER_CALLBACK_OPEN_LEVEL)\n    {\n        EnsureLevel();\n        OpenLevel(FullFileName);\n        ResetCursor();\n        Integrator::setEditorFile(FileName);\n    }\n    else if(m_browser_callback == BROWSER_CALLBACK_SAVE_LEVEL)\n    {\n        SaveLevel(FullFileName, FileFormat);\n        // this will resync custom assets\n        OpenLevel(FullFileName);\n        Integrator::setEditorFile(FileName);\n    }\n    else if(m_browser_callback == BROWSER_CALLBACK_NEW_LEVEL)\n    {\n        int cur_FileFormat = FileFormat;\n        if((cur_FileFormat < 0) || (cur_FileFormat > 2))\n            cur_FileFormat = FileFormats::LVL_PGEX;\n        EnsureLevel();\n        ClearLevel();\n        SaveLevel(FullFileName, cur_FileFormat);\n        // this will resync custom assets\n        OpenLevel(FullFileName);\n        ResetCursor();\n        Integrator::setEditorFile(FileName);\n    }\n    else if(m_browser_callback == BROWSER_CALLBACK_OPEN_WORLD)\n    {\n        EnsureWorld();\n        OpenWorld(FullFileName);\n        ResetCursor();\n        Integrator::setEditorFile(FileName);\n    }\n    else if(m_browser_callback == BROWSER_CALLBACK_SAVE_WORLD)\n    {\n        SaveWorld(FullFileName, FileFormat);\n        // resync custom assets\n        OpenWorld(FullFileName);\n        Integrator::setEditorFile(FileName);\n    }\n    else if(m_browser_callback == BROWSER_CALLBACK_NEW_WORLD)\n    {\n        int cur_FileFormat = FileFormat;\n        if((cur_FileFormat < 0) || (cur_FileFormat > 2))\n            cur_FileFormat = FileFormats::LVL_PGEX;\n        EnsureWorld();\n        ClearWorld();\n        SaveWorld(FullFileName, cur_FileFormat);\n        // resync custom assets\n        OpenWorld(FullFileName);\n        ResetCursor();\n        Integrator::setEditorFile(FileName);\n    }\n    else if(m_browser_callback == BROWSER_CALLBACK_CUSTOM_MUSIC)\n    {\n        StartMusic(curSection);\n    }\n\n    FileBrowserCleanup();\n}\n\nvoid EditorScreen::FileBrowserFailure()\n{\n    // if(m_file_target && m_file_target != &FullFileName) m_file_target->clear();\n    FileBrowserCleanup();\n}\n\nvoid EditorScreen::FileBrowserCleanup()\n{\n    m_cur_file.clear();\n    m_file_target = nullptr;\n    m_special_page = m_last_special_page;\n    m_last_special_page = SPECIAL_PAGE_NONE;\n    m_special_subpage = 0;\n    m_browser_mode = BROWSER_MODE_NONE;\n    m_browser_callback = BROWSER_CALLBACK_NONE;\n}\n\nvoid EditorScreen::SyncPath()\n{\n    if(m_path_synced) return;\n    m_dirman.setPath(m_root_path+m_cur_path);\n    m_dirman.getListOfFiles(m_cur_path_files, m_target_exts);\n    m_dirman.getListOfFolders(m_cur_path_dirs);\n    tinysort(m_cur_path_files.begin(), m_cur_path_files.end());\n    tinysort(m_cur_path_dirs.begin(), m_cur_path_dirs.end());\n    m_path_synced = true;\n}\n\nvoid EditorScreen::GoToSuper()\n{\n    if(m_cur_path.empty())\n    {\n        m_cur_path += \"../\";\n    }\n    else\n    {\n        size_t last_slash = m_cur_path.rfind('/', m_cur_path.length()-2);\n        size_t last_chunk_start;\n        if(last_slash == std::string::npos)\n            last_chunk_start = 0;\n        else\n            last_chunk_start = last_slash + 1;\n        if(m_cur_path.substr(last_chunk_start) == \"../\")\n            m_cur_path += \"../\";\n        else\n            m_cur_path = m_cur_path.substr(0, last_chunk_start);\n    }\n    m_path_synced = false;\n}\n\nvoid EditorScreen::ValidateExt(std::string& cur_file)\n{\n    if(m_target_exts.empty()) return;\n\n    for(const std::string& ext : m_target_exts)\n    {\n        if(Files::hasSuffix(cur_file, ext))\n            return;\n    }\n\n    cur_file += m_target_exts[0];\n}\n\nbool EditorScreen::FileExists(const std::string& cur_file)\n{\n    return (std::find(m_cur_path_files.begin(), m_cur_path_files.end(), cur_file) != m_cur_path_files.end());\n}\n\nvoid EditorScreen::UpdateBrowserScreen(CallMode mode)\n{\n    constexpr bool IGNORE_DIRS = true;\n    // render shared GUI elements on right\n    if(UpdateButton(mode, e_ScreenW - 40 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::x, 32, 32))\n    {\n        FileBrowserFailure();\n        return;\n    }\n\n    if(m_special_page == SPECIAL_PAGE_BROWSER_CONFIRM)\n    {\n        SuperPrintR(mode, g_editorStrings.phraseAreYouSure, 3, 60, 40);\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.browserAskOverwriteFile, m_cur_file), 3, 10, 60);\n\n        SuperPrintR(mode, g_mainMenu.wordYes, 3, 60, 110);\n        if(UpdateButton(mode, 10 + 4, 100 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            FileBrowserSuccess();\n            return;\n        }\n\n        SuperPrintR(mode, g_mainMenu.wordNo, 3, 60, 150);\n        if(UpdateButton(mode, 10 + 4, 140 + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n        {\n            m_special_page = SPECIAL_PAGE_BROWSER;\n            m_cur_file.clear();\n        }\n\n        return;\n    }\n\n    if(!m_path_synced)\n        SyncPath();\n\n    int dir_length = m_cur_path_dirs.size() + 1; // \"..\"\n    int file_length = m_cur_path_files.size();\n\n    if(m_browser_mode == BROWSER_MODE_SAVE || m_browser_mode == BROWSER_MODE_SAVE_NEW)\n    {\n        if(m_browser_mode == BROWSER_MODE_SAVE_NEW)\n            SuperPrintR(mode, g_editorStrings.browserNewFile, 3, 60, 40);\n        else\n            SuperPrintR(mode, g_editorStrings.browserSaveFile, 3, 60, 40);\n\n        dir_length ++; // \"new\", last folder\n        file_length ++; // \"new\", first file\n    }\n    else\n    {\n        SuperPrintR(mode, g_editorStrings.browserOpenFile, 3, 60, 40);\n    }\n\n    // ignore directories\n    if(IGNORE_DIRS)\n        dir_length = 0;\n\n    int page_max = (dir_length + file_length - 1) / 20;\n    if(!(page_max == 0 && m_special_subpage == 0))\n        SuperPrintR(mode, fmt::format_ne(g_editorStrings.pageBlankOfBlank, m_special_subpage + 1, page_max + 1), 3, e_ScreenW - 320, 40);\n\n    if(m_special_subpage > 0 && UpdateButton(mode, e_ScreenW - 120 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::left, 32, 32))\n        m_special_subpage --;\n\n    if(m_special_subpage < page_max && UpdateButton(mode, e_ScreenW - 80 + 4, 40 + 4, GFX.EIcons, false, 0, 32*Icon::right, 32, 32))\n        m_special_subpage ++;\n\n    if(!m_cur_path.empty())\n    {\n        const int avail_chars = (e_ScreenW-140)/18;\n        if(m_cur_path.size() > avail_chars)\n            SuperPrintR(mode, g_editorStrings.npcInContainer + \" ...\" + m_cur_path.substr(m_cur_path.size() - avail_chars), 3, 10, 60);\n        else\n            SuperPrintR(mode, g_editorStrings.npcInContainer + \" \" + m_cur_path, 3, 10, 60);\n    }\n\n    // render file selector\n    for(int i = 0; i < 20; i++)\n    {\n        int x = 10 + (e_ScreenW/2)*(i/10);\n        int y = 80 + 40*(i%10);\n\n        int l = m_special_subpage*20 + i;\n        if(!IGNORE_DIRS && l == 0)\n        {\n            SuperPrintR(mode, \"..\", 3, x + 44, y + 12);\n            if(UpdateButton(mode, x + 4, y + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n            {\n                GoToSuper();\n                m_special_subpage = 0;\n            }\n        }\n        else if((m_browser_mode == BROWSER_MODE_SAVE || m_browser_mode == BROWSER_MODE_SAVE_NEW) && l == dir_length - 1)\n        {\n            SuperPrintR(mode, g_editorStrings.browserItemNewFolder, 3, x + 44, y + 12);\n            if(UpdateButton(mode, x + 4, y + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n            {\n                DisableCursorNew();\n                std::string folder_name = TextEntryScreen::Run(g_editorStrings.browserItemNewFolder, \"\");\n                s_fix_mouse_pos();\n\n                if(!folder_name.empty() && !m_dirman.exists(folder_name))\n                {\n                    m_dirman.mkdir(folder_name);\n                    m_path_synced = false;\n                }\n            }\n        }\n        else if(l < dir_length)\n        {\n            l -= 1;\n            if(m_cur_path_dirs[l].length() < 15)\n                SuperPrintR(mode, m_cur_path_dirs[l], 3, x + 44, y + 10);\n            else\n            {\n                SuperPrintR(mode, m_cur_path_dirs[l].substr(0, 14), 3, x + 44, y + 2);\n                SuperPrintR(mode, m_cur_path_dirs[l].substr(14, 14), 3, x + 44, y + 20);\n            }\n\n            if(UpdateButton(mode, x + 4, y + 4, GFX.EIcons, false, 0, 32*Icon::action, 32, 32))\n            {\n                m_cur_path += m_cur_path_dirs[l] + \"/\";\n                m_path_synced = false;\n                m_special_subpage = 0;\n            }\n        }\n        else if((m_browser_mode == BROWSER_MODE_SAVE || m_browser_mode == BROWSER_MODE_SAVE_NEW) && l == dir_length)\n        {\n            SuperPrintR(mode, g_editorStrings.browserItemNewFile, 3, x + 44, y + 12);\n            if(UpdateButton(mode, x + 4, y + 4, GFX.EIcons, false, 0, 32*Icon::pencil, 32, 32))\n            {\n                DisableCursorNew();\n                std::string file_name = TextEntryScreen::Run(g_editorStrings.fileCommandSaveAs, \"\");\n                s_fix_mouse_pos();\n\n                if(!file_name.empty())\n                {\n                    // validate: append the file extension if it doesn't already appear.\n                    ValidateExt(file_name);\n                    m_cur_file = m_cur_path + file_name;\n\n                    if(FileExists(file_name))\n                        m_special_page = SPECIAL_PAGE_BROWSER_CONFIRM;\n                    else\n                    {\n                        FileBrowserSuccess();\n                        return;\n                    }\n                }\n            }\n        }\n        else if(l < dir_length + file_length)\n        {\n            if(m_browser_mode == BROWSER_MODE_SAVE || m_browser_mode == BROWSER_MODE_SAVE_NEW)\n                l -= dir_length + 1;\n            else\n                l -= dir_length;\n\n            if(m_cur_path_files[l].length() < 15)\n                SuperPrintR(mode, m_cur_path_files[l], 3, x + 44, y + 10);\n            else\n            {\n                SuperPrintR(mode, m_cur_path_files[l].substr(0, 14), 3, x + 44, y + 2);\n                SuperPrintR(mode, m_cur_path_files[l].substr(14, 14), 3, x + 44, y + 20);\n            }\n\n            if(UpdateButton(mode, x + 4, y + 4, GFX.ECursor[2], false, 0, 0, 32, 32))\n            {\n                m_cur_file = m_cur_path + m_cur_path_files[l];\n                if(m_browser_mode == BROWSER_MODE_OPEN)\n                {\n                    FileBrowserSuccess();\n                    return;\n                }\n                else\n                {\n                    // overwrite confirmation\n                    m_special_page = SPECIAL_PAGE_BROWSER_CONFIRM;\n                }\n            }\n        }\n    }\n}\n\ninline void swap_screens()\n{\n#ifdef __3DS__\n    int win_x, win_y;\n    XRender::mapFromScreen((int)SharedCursor.X, (int)SharedCursor.Y, &win_x, &win_y);\n#endif\n\n    editorScreen.active = !editorScreen.active;\n\n#ifdef __3DS__\n    int m_x, m_y;\n    XRender::mapToScreen(win_x, win_y, &m_x, &m_y);\n    SharedCursor.X = m_x;\n    SharedCursor.Y = m_y;\n    MouseMove(m_x, m_y);\n#endif\n\n    HasCursor = false;\n    MouseRelease = false;\n    MenuMouseRelease = false;\n    PlaySound(SFX_Pause);\n}\n\nvoid EditorScreen::UpdateSelectorBar(CallMode mode, bool select_bar_only)\n{\n    // can be drawn either over level screen or at top of editor screen\n\n    // find the selector bar location\n    int sx;\n    if(select_bar_only)\n    {\n        e_CursorX = EditorCursor.X;\n        e_CursorY = EditorCursor.Y;\n        // if(WorldEditor)\n        //     e_CursorY += 8;\n        sx = (XRender::TargetW - e_ScreenW)/2;\n    }\n    else\n        sx = 0;\n\n    if(mode == CallMode::Render)\n    {\n        uint8_t alpha = 255;\n        if(select_bar_only && MagicHand)\n            alpha = XTColor::from_num(0.7_n);\n\n        for(int i = 0; i < 5; i++)\n        {\n            uint8_t r = 153 + 13 * i;\n            uint8_t g = 127 + 19 * i;\n            XRender::renderRect(sx+0, 38-2*i, e_ScreenW, 2, {r, g, 204, alpha}, true);\n        }\n\n        XRender::renderRect(sx+0, 0, e_ScreenW, 30, {204, 204, 204, alpha}, true);\n    }\n\n    bool in_layers = (m_special_page == SPECIAL_PAGE_LAYERS || m_special_page == SPECIAL_PAGE_LAYER_DELETION);\n    bool in_events = (m_special_page == SPECIAL_PAGE_EVENTS || m_special_page == SPECIAL_PAGE_EVENT_LAYERS\n        || m_special_page == SPECIAL_PAGE_EVENT_TRIGGER || m_special_page == SPECIAL_PAGE_EVENT_SETTINGS\n        || m_special_page == SPECIAL_PAGE_EVENT_DELETION\n        || m_special_page == SPECIAL_PAGE_EVENT_MUSIC || m_special_page == SPECIAL_PAGE_EVENT_BACKGROUND\n        || m_special_page == SPECIAL_PAGE_EVENT_CONTROLS || m_special_page == SPECIAL_PAGE_EVENT_SOUND);\n    bool in_file = (m_special_page == SPECIAL_PAGE_FILE || m_special_page == SPECIAL_PAGE_FILE_CONFIRM\n        || m_special_page == SPECIAL_PAGE_FILE_CONVERT);\n    bool in_section_settings = (m_special_page == SPECIAL_PAGE_SECTION_SETTINGS\n        || m_special_page == SPECIAL_PAGE_SECTION_MUSIC\n        || m_special_page == SPECIAL_PAGE_SECTION_BACKGROUND);\n    bool in_world_settings = (m_special_page == SPECIAL_PAGE_WORLD_SETTINGS);\n    bool in_leveltest_settings = (m_special_page == SPECIAL_PAGE_EDITOR_SETTINGS || m_special_page == SPECIAL_PAGE_LEVELTEST_HELDNPC || m_special_page == SPECIAL_PAGE_MAGICBLOCK);\n    bool in_excl_special = in_layers || in_events || in_section_settings || in_world_settings || in_leveltest_settings || in_file;\n    bool exit_special = false;\n\n    bool currently_in;\n\n    // select\n    currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::LVL_SELECT;\n    if(UpdateButton(mode, sx+0*40+4, 4, GFX.ECursor[2], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipSelect.c_str()))\n    {\n        if(editorScreen.active)\n            swap_screens();\n        EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n        if(in_excl_special)\n            exit_special = true;\n    }\n\n    // erase\n    currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::LVL_ERASER;\n    if(UpdateButton(mode, sx+1*40+4, 4, GFX.ECursor[3], currently_in && EditorCursor.SubMode != -1, 0, 0, 22, 30, g_editorStrings.tooltipErase.c_str()))\n    {\n        if(editorScreen.active)\n            swap_screens();\n        EditorCursor.Mode = OptCursor_t::LVL_ERASER;\n        EditorCursor.SubMode = 0;\n        if(in_excl_special)\n            exit_special = true;\n    }\n\n    if(currently_in && UpdateButton(mode, sx+2*40+4, 4, GFXNPC[NPCID_AXE], currently_in && EditorCursor.SubMode == -1, 0, 0, 32, 32, g_editorStrings.tooltipEraseAll.c_str()))\n    {\n        if(editorScreen.active)\n            swap_screens();\n        EditorCursor.Mode = OptCursor_t::LVL_ERASER;\n        EditorCursor.SubMode = -1;\n        if(in_excl_special)\n            exit_special = true;\n    }\n\n    // level editor tabs\n    if(!WorldEditor)\n    {\n        // blocks\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::LVL_BLOCKS;\n        if(UpdateButton(mode, sx+3*40+4, 4, GFXBlock[1], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipBlocks.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::LVL_BLOCKS;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // BGOs\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::LVL_BGOS;\n        if(UpdateButton(mode, sx+4*40+4, 4, GFXBackgroundBMP[1], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipBGOs.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::LVL_BGOS;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // NPCs\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::LVL_NPCS;\n        if(UpdateButton(mode, sx+5*40+4, 4, GFXNPC[1], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipNPCs.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::LVL_NPCS;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // water\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::LVL_WATER;\n        if(!MagicHand && UpdateButton(mode, sx+6*40+4, 4, GFXBackgroundBMP[26], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipWater.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::LVL_WATER;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // warps\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::LVL_WARPS;\n        if(!MagicHand && UpdateButton(mode, sx+7*40+4, 4, GFXBlock[294], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipWarps.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::LVL_WARPS;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // level settings\n        currently_in = (in_section_settings || EditorCursor.Mode == OptCursor_t::LVL_PLAYERSTART);\n        if(!MagicHand && UpdateButton(mode, sx+9*40+4, 4, GFXBlock[60], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipSettings.c_str()))\n        {\n            if(currently_in || !editorScreen.active)\n                swap_screens();\n\n            if(!currently_in)\n            {\n                EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n                m_last_mode = OptCursor_t::LVL_SELECT;\n            }\n\n            if(!in_section_settings)\n                m_special_page = SPECIAL_PAGE_SECTION_SETTINGS;\n        }\n\n        // layers\n        if(!MagicHand && UpdateButton(mode, sx+10*40+4, 4, GFXBlock[447], in_layers, 0, 0, 32, 32, g_editorStrings.tooltipLayers.c_str()))\n        {\n            if(in_layers || !editorScreen.active)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n            m_last_mode = OptCursor_t::LVL_SELECT;\n            if(!in_layers)\n                m_special_page = SPECIAL_PAGE_LAYERS;\n        }\n\n        // events\n        if(!MagicHand && UpdateButton(mode, sx+11*40+4, 4, GFXBlock[169], in_events, 0, 0, 32, 32, g_editorStrings.tooltipEvents.c_str()))\n        {\n            if(in_events || !editorScreen.active)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n            m_last_mode = OptCursor_t::LVL_SELECT;\n            if(!in_events)\n                m_special_page = SPECIAL_PAGE_EVENTS;\n        }\n    }\n\n    // world editor tabs\n    if(WorldEditor)\n    {\n        // tiles\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::WLD_TILES;\n        if(UpdateButton(mode, sx+3*40+4, 4, GFXTileBMP[1], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipTiles.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::WLD_TILES;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // scenes\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::WLD_SCENES;\n        if(UpdateButton(mode, sx+4*40+4, 4, GFXSceneBMP[1], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipScenes.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::WLD_SCENES;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // levels\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::WLD_LEVELS;\n        if(UpdateButton(mode, sx+5*40+4, 4, GFXLevelBMP[2], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipLevels.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::WLD_LEVELS;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // paths\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::WLD_PATHS;\n        if(UpdateButton(mode, sx+6*40+4, 4, GFXPathBMP[4], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipPaths.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::WLD_PATHS;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // world music\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::WLD_MUSIC;\n        if(UpdateButton(mode, sx+7*40+4, 4, GFX.EIcons, currently_in, 0, 32*Icon::music, 32, 32, g_editorStrings.tooltipMusic.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::WLD_MUSIC;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // world area\n        currently_in = !in_excl_special && EditorCursor.Mode == OptCursor_t::WLD_AREA;\n        if(FileFormat == FileFormats::LVL_PGEX && UpdateButton(mode, sx+8*40+4, 4, GFXBlock[60], currently_in, 0, 0, 32, 32, g_editorStrings.tooltipArea.c_str()))\n        {\n            if(currently_in)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::WLD_AREA;\n            if(in_excl_special)\n                exit_special = true;\n        }\n\n        // world settings\n        if(UpdateButton(mode, sx+9*40+4, 4, GFXLevelBMP[15], in_world_settings, 0, 0, 32, 32, g_editorStrings.tooltipSettings.c_str()))\n        {\n            if(in_world_settings || !editorScreen.active)\n                swap_screens();\n            EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n            m_last_mode = OptCursor_t::LVL_SELECT;\n            if(!in_world_settings)\n                m_special_page = SPECIAL_PAGE_WORLD_SETTINGS;\n        }\n    }\n\n    // file\n    if(!MagicHand && UpdateButton(mode, sx+13*40+4, 4, GFX.EIcons, in_file, 0, 32*Icon::page, 32, 32, g_editorStrings.tooltipFile.c_str()))\n    {\n        if(in_file || !editorScreen.active)\n            swap_screens();\n        EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n        m_last_mode = OptCursor_t::LVL_SELECT;\n        if(!in_file)\n            m_special_page = SPECIAL_PAGE_FILE;\n    }\n\n    // test play settings\n    if(!WorldEditor && UpdateButton(mode, sx+14*40 + 4, 4, GFX.EIcons, in_leveltest_settings, 0, 32*Icon::play, 32, 32, g_editorStrings.tooltipSettings.c_str()))\n    {\n        if(in_leveltest_settings || !editorScreen.active)\n            swap_screens();\n        EditorCursor.Mode = OptCursor_t::LVL_SELECT;\n        m_last_mode = OptCursor_t::LVL_SELECT;\n        if(!in_leveltest_settings)\n            m_special_page = SPECIAL_PAGE_EDITOR_SETTINGS;\n    }\n\n    // swap screens\n    int switch_screens_icon = 0;\n    const char* switch_screens_tooltip;\n    if(select_bar_only)\n    {\n        switch_screens_icon = Icon::down;\n        switch_screens_tooltip = g_editorStrings.tooltipShow.c_str();\n    }\n    else\n    {\n        switch_screens_icon = Icon::up;\n        switch_screens_tooltip = nullptr;\n    }\n\n    if(UpdateButton(mode, sx+15*40 + 4, 4, GFX.EIcons, false, 0, 32*switch_screens_icon, 32, 32, switch_screens_tooltip))\n        swap_screens();\n\n    // if mode has been updated for any reason, close any special dialogue boxes\n    // and sync everything up\n    if(exit_special || EditorCursor.Mode != m_last_mode)\n    {\n        if(m_browser_mode != BROWSER_MODE_NONE)\n            FileBrowserCleanup();\n        m_special_page = SPECIAL_PAGE_NONE;\n        m_special_subpage = 0;\n        m_last_mode = EditorCursor.Mode;\n    }\n\n    if(select_bar_only && mode == CallMode::Render && e_CursorY < 40 && e_CursorX >= sx && e_CursorX < sx+e_ScreenW)\n    {\n        int X = e_CursorX;\n        int Y = e_CursorY;\n#if 0\n        if(g_config.editor_edge_scroll && !MagicHand)\n        {\n            if(X < 36)\n                X = 36;\n            if(Y < 36)\n                Y = 36;\n            if(X >= XRender::TargetW - 36)\n                X = XRender::TargetW - 36;\n            if(Y >= XRender::TargetH - 36)\n                Y = XRender::TargetH - 36;\n        }\n#endif\n\n#ifdef __3DS__\n        if(select_bar_only)\n            XRender::renderTextureBasic(X, Y, GFX.ECursor[2]);\n#else\n        XRender::renderTextureBasic(X, Y, GFX.ECursor[2]);\n#endif\n\n        if(e_tooltip)\n        {\n            if(e_CursorX + 28 < sx + e_ScreenW / 2)\n                SuperPrint(e_tooltip, 3, e_CursorX + 28, e_CursorY + 34, XTColorF(1.0_n, 0.7_n, 0.7_n));\n            else\n                SuperPrintRightAlign(e_tooltip, 3, e_CursorX + 10, e_CursorY + 34, XTColorF(1.0_n, 0.7_n, 0.7_n));\n        }\n    }\n}\n\nvoid EditorScreen::UpdateEditorScreen(CallMode mode, bool second_screen)\n{\n    // second screen is like the upper screen of the 3DS or the TV of the Wii U -- any screen without a direct pointing device\n    if(second_screen && mode == CallMode::Logic)\n        return;\n\n    if(MagicHand && !LevelEditor)\n        m_special_page = SPECIAL_PAGE_NONE;\n\n    if(mode == CallMode::Logic)\n    {\n        if(GamePaused != PauseCode::None)\n            return;\n\n        MenuMouseRelease = MouseRelease && !MenuMouseRelease && !SharedCursor.Primary;\n    }\n\n    e_tooltip = nullptr;\n\n    bool select_bar_only = ((active && second_screen) || (!active && !second_screen));\n\n    if(select_bar_only)\n    {\n        XRender::resetViewport();\n        UpdateSelectorBar(mode, true);\n\n        if(mode == CallMode::Logic)\n            MenuMouseRelease = !SharedCursor.Primary;\n\n        return;\n    }\n\n    MessageText.clear();\n\n#ifdef __3DS__\n    if(mode == CallMode::Render && active)\n        XRender::setTargetSubScreen();\n    else if(mode == CallMode::Render)\n    {\n        XRender::setTargetMainScreen();\n        XRender::setViewport(800/2 - e_ScreenW/2, 0, e_ScreenW, e_ScreenH);\n    }\n#else\n    if(mode == CallMode::Render)\n        XRender::setViewport(XRender::TargetW/2-e_ScreenW/2, 0, e_ScreenW, e_ScreenH);\n#endif\n\n    e_CursorX = EditorCursor.X;\n    e_CursorY = EditorCursor.Y;\n\n#ifdef __3DS__\n    if(!editorScreen.active)\n        e_CursorX -= XRender::TargetW/2-e_ScreenW/2;\n#else\n    e_CursorX -= XRender::TargetW/2-e_ScreenW/2;\n#endif\n\n    // if(WorldEditor)\n    //     e_CursorY += 8;\n\n    if(mode == CallMode::Render)\n        XRender::renderRect(0, 0, e_ScreenW, e_ScreenH, XTColorF(0.4_n, 0.4_n, 0.8_n, 0.75_n), true);\n\n    UpdateSelectorBar(mode, false);\n\n    if(m_special_page == SPECIAL_PAGE_BROWSER || m_special_page == SPECIAL_PAGE_BROWSER_CONFIRM)\n        UpdateBrowserScreen(mode);\n    else if(m_special_page == SPECIAL_PAGE_FILE || m_special_page == SPECIAL_PAGE_FILE_CONFIRM\n        || m_special_page == SPECIAL_PAGE_FILE_CONVERT)\n        UpdateFileScreen(mode);\n    else if(m_special_page == SPECIAL_PAGE_LAYERS || m_special_page == SPECIAL_PAGE_LAYER_DELETION\n        || m_special_page == SPECIAL_PAGE_OBJ_LAYER || m_special_page == SPECIAL_PAGE_EVENT_LAYERS)\n        UpdateLayersScreen(mode);\n    else if(m_special_page == SPECIAL_PAGE_EVENTS || m_special_page == SPECIAL_PAGE_EVENT_DELETION)\n        UpdateEventsScreen(mode);\n    else if(m_special_page == SPECIAL_PAGE_EVENT_SETTINGS || m_special_page == SPECIAL_PAGE_EVENT_CONTROLS)\n        UpdateEventSettingsScreen(mode);\n    else if(m_special_page == SPECIAL_PAGE_OBJ_TRIGGERS || m_special_page == SPECIAL_PAGE_EVENT_TRIGGER)\n        UpdateEventsSubScreen(mode);\n    else if(m_special_page == SPECIAL_PAGE_BLOCK_CONTENTS || m_special_page == SPECIAL_PAGE_LEVELTEST_HELDNPC)\n        UpdateNPCScreen(mode);\n    else if(m_special_page == SPECIAL_PAGE_EDITOR_SETTINGS)\n        UpdateEditorSettingsScreen(mode);\n    else if(m_special_page == SPECIAL_PAGE_WORLD_SETTINGS)\n        UpdateWorldSettingsScreen(mode);\n    // else if(m_special_page == SPECIAL_PAGE_MAGICBLOCK)\n    //     UpdateMagicBlockScreen(mode);\n    else if(m_special_page == SPECIAL_PAGE_EVENT_MUSIC || m_special_page == SPECIAL_PAGE_EVENT_BACKGROUND\n        || m_special_page == SPECIAL_PAGE_EVENT_SOUND || m_special_page == SPECIAL_PAGE_SECTION_BACKGROUND\n        || m_special_page == SPECIAL_PAGE_SECTION_MUSIC || m_special_page == SPECIAL_PAGE_LEVEL_EXIT\n        || EditorCursor.Mode == OptCursor_t::WLD_MUSIC || m_special_page == SPECIAL_PAGE_WARP_TRANSITION)\n        UpdateSelectListScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::LVL_BLOCKS)\n        UpdateBlockScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::LVL_BGOS)\n        UpdateBGOScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::LVL_NPCS)\n        UpdateNPCScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::LVL_WATER)\n        UpdateWaterScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::LVL_WARPS)\n        UpdateWarpScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::LVL_PLAYERSTART || m_special_page == SPECIAL_PAGE_SECTION_SETTINGS)\n        UpdateSectionsScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::WLD_TILES)\n        UpdateTileScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::WLD_SCENES)\n        UpdateSceneScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::WLD_LEVELS)\n        UpdateLevelScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::WLD_PATHS)\n        UpdatePathScreen(mode);\n    else if(EditorCursor.Mode == OptCursor_t::WLD_AREA)\n        UpdateAreaScreen(mode);\n\n    if(mode == CallMode::Render && e_CursorX >= 0 && GamePaused == PauseCode::None)\n    {\n#ifndef __3DS__\n        XRender::renderTextureBasic(e_CursorX, e_CursorY, GFX.ECursor[2]);\n#endif\n        if(e_tooltip)\n        {\n            if(e_CursorX + 28 < e_ScreenW / 2)\n                SuperPrint(e_tooltip, 3, e_CursorX + 28, e_CursorY + 34, XTColorF(1.0_n, 0.7_n, 0.7_n));\n            else\n                SuperPrintRightAlign(e_tooltip, 3, e_CursorX + 10, e_CursorY + 34, XTColorF(1.0_n, 0.7_n, 0.7_n));\n        }\n    }\n\n    if(mode == CallMode::Logic)\n        MenuMouseRelease = !SharedCursor.Primary;\n}\n\nEditorScreen editorScreen;\n"
  },
  {
    "path": "src/editor/new_editor.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef NEW_EDITOR_H\n#define NEW_EDITOR_H\n\n#include <string>\n#include <vector>\n\n#include <DirManager/dirman.h>\n\n#include \"std_picture.h\"\n#include \"global_constants.h\"\n\nenum NPCID : vbint_t;\n\nnamespace Icon\n{\n    enum Icons : int\n    {\n        unk = 0,\n        left,\n        right,\n        up,\n        down,\n        bottom,\n        top,\n        check,\n        x,\n        lr,\n        ud,\n        hop,\n        target,\n        wave,\n        leap,\n        _10,\n        _1x,\n        pencil,\n        show,\n        hide,\n        toggle,\n        play,\n        save,\n        open,\n        newf,\n        page,\n        music,\n        move,\n        thextech,\n        subscreen,\n        select,\n        action\n    };\n}\n\nclass EditorScreen\n{\npublic:\n    enum class CallMode\n    {\n        Logic,\n        Render\n    };\n\nprivate:\n    typedef enum\n    {\n        SPECIAL_PAGE_NONE,\n        SPECIAL_PAGE_MAGICBLOCK,\n        SPECIAL_PAGE_BROWSER,\n        SPECIAL_PAGE_BROWSER_CONFIRM,\n        SPECIAL_PAGE_EVENTS,\n        SPECIAL_PAGE_EVENT_SETTINGS,\n        SPECIAL_PAGE_EVENT_DELETION,\n        SPECIAL_PAGE_EVENT_LAYERS,\n        SPECIAL_PAGE_EVENT_TRIGGER,\n        SPECIAL_PAGE_EVENT_MUSIC,\n        SPECIAL_PAGE_EVENT_BACKGROUND,\n        SPECIAL_PAGE_EVENT_CONTROLS,\n        SPECIAL_PAGE_EVENT_SOUND,\n        SPECIAL_PAGE_SECTION_SETTINGS,\n        SPECIAL_PAGE_SECTION_MUSIC,\n        SPECIAL_PAGE_SECTION_BACKGROUND,\n        SPECIAL_PAGE_LAYERS,\n        SPECIAL_PAGE_LAYER_DELETION,\n        SPECIAL_PAGE_OBJ_LAYER,\n        SPECIAL_PAGE_OBJ_TRIGGERS,\n        SPECIAL_PAGE_BLOCK_CONTENTS,\n        SPECIAL_PAGE_WARP_TRANSITION,\n        SPECIAL_PAGE_LEVEL_EXIT,\n        SPECIAL_PAGE_WORLD_SETTINGS,\n        SPECIAL_PAGE_EDITOR_SETTINGS,\n        SPECIAL_PAGE_LEVELTEST_HELDNPC,\n        SPECIAL_PAGE_FILE,\n        SPECIAL_PAGE_FILE_CONFIRM,\n        SPECIAL_PAGE_FILE_CONVERT,\n    } SpecialPage_t;\n    typedef enum\n    {\n        WARP_PAGE_MAIN,\n        WARP_PAGE_REQS,\n        WARP_PAGE_LEVEL,\n    } WarpPage_t;\n    typedef enum\n    {\n        BROWSER_MODE_NONE,\n        BROWSER_MODE_SAVE_NEW,\n        BROWSER_MODE_SAVE,\n        BROWSER_MODE_OPEN,\n    } BrowserMode_t;\n    typedef enum\n    {\n        BROWSER_CALLBACK_NONE,\n        BROWSER_CALLBACK_NEW_LEVEL,\n        BROWSER_CALLBACK_SAVE_LEVEL,\n        BROWSER_CALLBACK_OPEN_LEVEL,\n        BROWSER_CALLBACK_NEW_WORLD,\n        BROWSER_CALLBACK_SAVE_WORLD,\n        BROWSER_CALLBACK_OPEN_WORLD,\n        BROWSER_CALLBACK_CUSTOM_MUSIC,\n    } BrowserCallback_t;\n\n    // current pages\n    SpecialPage_t m_special_page = SPECIAL_PAGE_NONE;\n    int m_special_subpage = 0;\n    int m_last_mode = 0;\n    int m_NPC_page = 0;\n    int m_Block_page = 0;\n    int m_BGO_page = 0;\n    WarpPage_t m_Warp_page = WARP_PAGE_MAIN;\n    int m_layers_page = 0;\n    int m_events_page = 0;\n    int m_sounds_page = 0;\n    int m_tile_page = 0;\n    int m_music_page = 0;\n    int m_background_page = 0;\n    int m_current_event = 0;\n\n    // saved message string for user (maybe expensive to recalculate each frame)\n    std::string m_saved_message;\n\n    // file browser info\n    std::string* m_file_target = nullptr;\n    std::string m_root_path;\n    std::vector<std::string> m_target_exts;\n    std::string m_cur_path;\n    std::string m_cur_file;\n    std::vector<std::string> m_cur_path_dirs;\n    std::vector<std::string> m_cur_path_files;\n    DirMan m_dirman;\n    bool m_path_synced = false;\n    BrowserMode_t m_browser_mode = BROWSER_MODE_NONE;\n    BrowserCallback_t m_browser_callback = BROWSER_CALLBACK_NONE;\n    SpecialPage_t m_last_special_page = SPECIAL_PAGE_NONE;\n\n    void EnsureWorld();\n    void EnsureLevel();\n\n    void SyncPath();\n    void GoToSuper();\n    void ValidateExt(std::string& cur_file);\n    bool FileExists(const std::string& cur_file);\n    void StartFileBrowser(std::string* file_target,\n                          const std::string &root_path,\n                          const std::string &current_path,\n                          const std::vector<std::string> &target_exts,\n                          BrowserMode_t browser_mode = BROWSER_MODE_OPEN,\n                          BrowserCallback_t browser_callback = BROWSER_CALLBACK_NONE);\n    void FileBrowserSuccess();\n    void FileBrowserFailure();\n    void FileBrowserCleanup();\n\n    bool UpdateButton(CallMode mode, int x, int y, StdPicture &im, bool sel,\n        int src_x = 0, int src_y = 0, int src_w = 32, int src_h = 32, const char* tooltip = nullptr);\n\n    bool UpdateCheckBox(CallMode mode, int x, int y, bool sel, const char* tooltip = nullptr);\n\n    bool UpdateNPCButton(CallMode mode, int x, int y, NPCID type, bool sel);\n    void UpdateNPC(CallMode mode, int x, int y, NPCID type);\n    void UpdateNPCGrid(CallMode mode, int x, int y, const int* types, int n_npcs, int n_cols);\n    void UpdateNPCScreen(CallMode mode);\n\n    bool UpdateBlockButton(CallMode mode, int x, int y, int type, bool sel);\n    void UpdateBlock(CallMode mode, int x, int y, int type);\n    void UpdateBlockGrid(CallMode mode, int x, int y, const int* types, int n_blocks, int n_cols);\n    void UpdateBlockScreen(CallMode mode);\n\n    bool UpdateBGOButton(CallMode mode, int x, int y, int type, bool sel);\n    void UpdateBGO(CallMode mode, int x, int y, int type);\n    void UpdateBGOGrid(CallMode mode, int x, int y, const int* types, int n_bgos, int n_cols);\n    void UpdateBGOScreen(CallMode mode);\n\n    void UpdateAreaScreen(CallMode mode);\n    void UpdateWaterScreen(CallMode mode);\n    void UpdateWarpScreen(CallMode mode);\n\n    void UpdateSectionsScreen(CallMode mode);\n\n    void UpdateEditorSettingsScreen(CallMode mode);\n\n    void UpdateWorldSettingsScreen(CallMode mode);\n\n    void UpdateLayersScreen(CallMode mode);\n\n    void UpdateEventsScreen(CallMode mode);\n    void UpdateEventSettingsScreen(CallMode mode);\n    void UpdateEventsSubScreen(CallMode mode);\n\n    bool UpdateTileButton(CallMode mode, int x, int y, int type, bool sel);\n    void UpdateTile(CallMode mode, int x, int y, int type);\n    void UpdateTileGrid(CallMode mode, int x, int y, const int* types, int n_tiles, int n_cols);\n    void UpdateTileScreen(CallMode mode);\n\n    void UpdateScene(CallMode mode, int x, int y, int type);\n    void UpdateSceneGrid(CallMode mode, int x, int y, const int* types, int n_scenes, int n_cols);\n    void UpdateSceneScreen(CallMode mode);\n\n    void UpdateLevel(CallMode mode, int x, int y, int type);\n    void UpdateLevelGrid(CallMode mode, int x, int y, const int* types, int n_levels, int n_cols);\n    void UpdateLevelScreen(CallMode mode);\n\n    void UpdatePath(CallMode mode, int x, int y, int type);\n    void UpdatePathGrid(CallMode mode, int x, int y, const int* types, int n_paths, int n_cols);\n    void UpdatePathScreen(CallMode mode);\n\n    void UpdateSelectListScreen(CallMode mode);\n\n    void UpdateFileScreen(CallMode mode);\n    void UpdateBrowserScreen(CallMode mode);\n\n    // void UpdateMagicBlockScreen(CallMode mode);\n\npublic:\n    bool active = true;\n    int num_test_players = 1;\n    bool test_magic_hand = false;\n\n    void UpdateEditorScreen(CallMode mode, bool second_screen = false);\n    void UpdateSelectorBar(CallMode mode, bool select_bar_only = false);\n    void ResetCursor();\n    // set m_NPC_page based on the editor cursor\n    void FocusNPC();\n    // set m_Block_page based on the editor cursor\n    void FocusBlock();\n    // set m_BGO_page based on the editor cursor\n    void FocusBGO();\n    // set m_tile_page based on the editor cursor\n    void FocusTile();\n};\n\nextern EditorScreen editorScreen;\n\n#endif // #ifndef NEW_EDITOR_H\n"
  },
  {
    "path": "src/editor/write_common.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"write_common.h\"\n\n\nvoid fwritestr(FILE* f, const std::string& s)\n{\n    char buf[512];\n    int dest = 0;\n    for (const char& c : s)\n    {\n        if(dest == 512)\n            break;\n        if(c == '\\t' || c == '\\r' || c == '\\n' || c == '\"')\n            continue;\n        buf[dest] = c;\n        dest ++;\n    }\n    buf[dest] = '\\0';\n    fprintf(f, \"\\\"%s\\\"\\r\\n\", buf);\n}\n\nvoid fwritestr_multiline(FILE* f, const std::string& s)\n{\n    char buf[512];\n    int dest = 0;\n    for (const char& c : s)\n    {\n        if(dest == 512)\n            break;\n        if(c == '\\t' || c == '\\r')\n            continue;\n        if(c == '\"')\n            buf[dest] = '\\'';\n        else if(c == '\\n')\n        {\n            if(dest == 511) break;\n            buf[dest] = '\\r';\n            dest ++;\n            buf[dest] = '\\n';\n        }\n        else\n            buf[dest] = c;\n        dest ++;\n    }\n    buf[dest] = '\\0';\n    fprintf(f, \"\\\"%s\\\"\\r\\n\", buf);\n}\n\nvoid fwritenum(FILE* f, int n)\n{\n    fprintf(f, \"%d\\r\\n\", n);\n}\n\nvoid fwritebool(FILE* f, bool b)\n{\n    if (b)\n        fprintf(f, \"#TRUE#\\r\\n\");\n    else\n        fprintf(f, \"#FALSE#\\r\\n\");\n}\n\nvoid fwritefloat(FILE* f, float n)\n{\n    fprintf(f, \"%f\\r\\n\", n);\n}\n"
  },
  {
    "path": "src/editor/write_common.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef WRITE_COMMON_HHHH\n#define WRITE_COMMON_HHHH\n\n#include <cstdio>\n#include <string>\n\n\nvoid fwritestr(FILE* f, const std::string& s);\n\nvoid fwritestr_multiline(FILE* f, const std::string& s);\n\nvoid fwritenum(FILE* f, int n);\n\nvoid fwritebool(FILE* f, bool b);\n\nvoid fwritefloat(FILE* f, float n);\n\n#endif // WRITE_COMMON_HHHH\n"
  },
  {
    "path": "src/editor/write_level.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"sorting.h\"\n#include \"layers.h\"\n#include \"write_common.h\"\n#include \"main/game_info.h\"\n#include \"sound.h\"\n#include \"npc.h\"\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n#include \"npc_special_data.h\"\n#include <PGE_File_Formats/file_formats.h>\n#include <AppPath/app_path.h>\n#include \"Logger/logger.h\"\n\nvoid SaveLevel(const std::string& FilePath, int format, int version)   // saves the level\n{\n    LevelData out;\n    LevelBlock block;\n    LevelBGO bgo;\n    LevelSection section;\n    LevelNPC npc;\n    LevelDoor warp;\n    LevelPhysEnv pez;\n    LevelLayer layer;\n    LevelSMBX64Event evt;\n    PlayerPoint player;\n\n    FileFormats::CreateLevelData(out);\n\n    int A = 0;\n    int B = 0;\n    int C = 0;\n\n    // put NPC types 60, 62, 64, 66, and 78-83 first. (why?)\n    for(A = 1; A <= numNPCs; A++)\n    {\n        if(NPC[A].Type == NPCID_YEL_PLATFORM || NPC[A].Type == NPCID_BLU_PLATFORM || NPC[A].Type == NPCID_GRN_PLATFORM || NPC[A].Type == NPCID_RED_PLATFORM || (NPC[A].Type >= NPCID_TANK_TREADS && NPC[A].Type <= NPCID_SLANT_WOOD_M))\n        {\n            // swap it with the first NPC that isn't one of the special ones\n            // this started as B = 1 but C + 1 will work now.\n            for(B = C + 1; B < A; B++)\n            {\n                if (!(NPC[B].Type == NPCID_YEL_PLATFORM || NPC[B].Type == NPCID_BLU_PLATFORM || NPC[B].Type == NPCID_GRN_PLATFORM || NPC[B].Type == NPCID_RED_PLATFORM || (NPC[B].Type >= NPCID_TANK_TREADS && NPC[B].Type <= NPCID_SLANT_WOOD_M)))\n                {\n                    std::swap(NPC[A], NPC[B]);\n                    break;\n                }\n            }\n            C++;\n            // we know that the first C slots are all these types\n        }\n        // C++ was here but that is a logical flaw.\n    }\n\n    qSortNPCsY(1, C);\n    qSortNPCsY(C + 1, numNPCs);\n    qSortBlocks(1, numBlock);\n    qSortBackgrounds(1, numBackground);\n    // FindSBlocks();\n\n    syncLayersTrees_AllBlocks();\n    syncLayers_AllBGOs();\n    syncLayers_AllNPCs();\n\n    // NPCyFix\n    // Split filepath\n    // For A = Len(FilePath) To 1 Step -1\n    //     If Mid(FilePath, A, 1) = \"/\" Or Mid(FilePath, A, 1) = \"\\\\\" Then Exit For\n    // Next A\n    // FileNamePath = Left(FilePath, (A))\n    // FileName = Right(FilePath, (Len(FilePath) - A))\n    // FullFileName = FilePath\n    // If Right(FileNamePath, 2) = \"\\\\\" Then\n    //     FileNamePath = Left(FileNamePath, Len(FileNamePath) - 1)\n    // End If\n\n    // Level-wide settings\n    out.LevelName = LevelName;\n    out.meta.configPackId = \"TheXTech\";\n    out.meta.engineFeatureLevel = g_gameInfo.contentFeatureLevel;\n\n    // sections\n    for(int i = 0; i < numSections; ++i)\n    {\n        const auto &s = LevelREAL[i];\n\n        section.id = i;\n        section.size_left = s.X;\n        section.size_top  = s.Y;\n        section.size_right = s.Width;\n        section.size_bottom  = s.Height;\n        section.music_id = bgMusic[i];\n        // section.bgcolor = bgColor[i];    // unused since SMBX64, removed\n        section.wrap_h = LevelWrap[i];\n        section.wrap_v = LevelVWrap[i];\n        section.OffScreenEn = OffScreenExit[i];\n        section.background = Background2[i];\n        section.lock_left_scroll = NoTurnBack[i];\n        section.underwater = UnderWater[i];\n        section.music_file = CustomMusic[i];\n\n        if(SectionJSONInfo[i] != STRINGINDEX_NONE)\n            section.custom_params = GetS(SectionJSONInfo[i]);\n\n        // swapped order of operands because\n        // previous code did not make sense\n        if(size_t(i) >= out.sections.size())\n            out.sections.push_back(section);\n        else\n            out.sections[i] = section;\n    }\n\n    for(int i = 1; i <= 2; ++i)\n    {\n        const auto &p = PlayerStart[i];\n\n        if(p.isNull())\n            continue;\n\n        player.id = i;\n        player.x = p.X;\n        player.y = p.Y;\n\n        // force to Moondust compatible size\n        player.w = 24;\n        player.h = (i == 1) ? 54 : 60;\n\n        player.x += PlayerStart[i].Width / 2;\n        player.x -= player.w / 2;\n\n        player.y += PlayerStart[i].Height;\n        player.y -= player.h;\n\n        player.direction = p.Direction;\n        out.players.push_back(player);\n    }\n\n    for(int i = 1; i <= numBlock; ++i)\n    {\n        const auto &b = Block[i];\n\n        block.id = b.Type;\n        block.x = (int32_t)b.Location.X;\n        block.y = (int32_t)b.Location.Y;\n        block.w = (int32_t)b.Location.Width;\n        block.h = (int32_t)b.Location.Height;\n\n        if(b.Special >= 1000)\n            block.npc_id = b.Special - 1000;\n        else if(b.Special > 0)\n            block.npc_id = -b.Special;\n        else\n            block.npc_id = 0;\n\n        block.invisible = b.Invis;\n        block.slippery = b.Slippy;\n        block.layer = GetL(b.Layer);\n\n        // fix this to update as needed\n        if(block.layer.empty())\n            block.layer = \"Default\";\n\n        block.event_destroy = GetE(b.TriggerDeath);\n        block.event_hit = GetE(b.TriggerHit);\n        block.event_emptylayer = GetE(b.TriggerLast);\n\n        // NEW: legacy behavior for spin block\n        if(b.Type == 90)\n            block.special_data = (int)b.forceSmashable;\n\n        block.meta.array_id = (out.blocks_array_id++);\n\n        out.blocks.push_back(block);\n    }\n\n    for(int i = 1; i <= numBackground; ++i)\n    {\n        const auto &b = Background[i];\n\n        bgo.id = b.Type;\n        bgo.x = (int32_t)b.Location.X;\n        bgo.y = (int32_t)b.Location.Y;\n        bgo.layer = GetL(b.Layer);\n\n        bgo.z_mode = b.GetCustomLayer();\n        bgo.z_offset = b.GetCustomOffset();\n        // bgo.smbx64_sp = bgo.z_mode == LevelBGO::ZDefault ? b.SortPriority : -1;\n\n        // fix this to update as needed\n        if(bgo.layer.empty())\n            bgo.layer = \"Default\";\n\n        bgo.meta.array_id = out.bgo_array_id++;\n        out.bgo.push_back(bgo);\n    }\n\n    const auto generator_direct_default = npc.generator_direct;\n    const auto generator_period_default = npc.generator_period;\n    const auto generator_type_default = npc.generator_type;\n\n    for(int i = 1; i <= numNPCs; ++i)\n    {\n        const auto &n = NPC[i];\n\n        npc.id = n.Type;\n        npc.x = (int32_t)n.Location.X;\n        npc.y = (int32_t)n.Location.Y;\n        npc.direct = n.Direction;\n\n        npc.contents = 0;\n        npc.special_data = 0;\n\n        if(NPCIsContainer(n))\n        {\n            npc.contents = n.Special;\n            npc.special_data = n.Variant;\n        }\n\n        // Warp Section pointer\n        if(n.Type == NPCID_DOOR_MAKER || n.Type == NPCID_MAGIC_DOOR ||\n          (n.Type == NPCID_ITEM_BURIED && n.Special == NPCID_DOOR_MAKER))\n        {\n            npc.special_data = n.Variant;\n        }\n        // AI / firebar length\n        else if(n.Type == NPCID_FIRE_CHAIN || NPCIsAParaTroopa(n) || n->IsFish)\n        {\n            npc.special_data = n.Special;\n        }\n        // Star ID if >0\n        else if(n.Type == NPCID_STAR_EXIT || n.Type == NPCID_STAR_COLLECT || n.Type == NPCID_MEDAL)\n        {\n            npc.special_data = int(n.Variant);\n        }\n        // Legacy and custom behaviors\n        else if(find_Variant_Data(n.Type) != nullptr)\n        {\n            npc.special_data = n.Variant;\n        }\n\n        npc.generator = n.Generator;\n        if(npc.generator)\n        {\n            npc.generator_direct = n.GeneratorDirection();\n            npc.generator_period = n.GeneratorTimeMax();\n            npc.generator_type = n.GeneratorEffect();\n        }\n        else\n        {\n            npc.generator_direct = generator_direct_default;\n            npc.generator_period = generator_period_default;\n            npc.generator_type = generator_type_default;\n        }\n        npc.attach_layer = GetL(n.AttLayer);\n\n        npc.msg = GetS(n.Text);\n        npc.friendly = n.Inert;\n        npc.nomove = n.Stuck;\n        npc.is_boss = n.Legacy;\n        npc.wings_type = (int)n.DefaultWings;\n        npc.gfx_dx = n.GFXSlot;\n\n        npc.layer = GetL(n.Layer);\n\n        npc.event_activate = GetE(n.TriggerActivate);\n        npc.event_die = GetE(n.TriggerDeath);\n        npc.event_talk = GetE(n.TriggerTalk);\n        npc.event_emptylayer = GetE(n.TriggerLast);\n\n        // fix this to update as needed\n        if(npc.layer.empty())\n            npc.layer = \"Default\";\n\n        npc.meta.array_id = out.npc_array_id++;\n        out.npc.push_back(npc);\n    }\n\n    for(int i = 1; i <= numWarps; ++i)\n    {\n        const auto &w = Warp[i];\n\n        // no case where user would want to save incomplete warp in classic editor\n        if(!w.PlacedEnt || !w.PlacedExit)\n            continue;\n\n        warp.ix = (int32_t)w.Entrance.X;\n        warp.iy = (int32_t)w.Entrance.Y;\n        warp.length_i = (int32_t)w.Entrance.Width;\n        warp.height_i = (int32_t)w.Entrance.Height;\n        warp.ox = (int32_t)w.Exit.X;\n        warp.oy = (int32_t)w.Exit.Y;\n        warp.length_o = (int32_t)w.Exit.Width;\n        warp.height_o = (int32_t)w.Exit.Height;\n        warp.isSetIn = w.PlacedEnt;\n        warp.isSetOut = w.PlacedExit;\n        warp.idirect = w.Direction;\n        warp.odirect = w.Direction2;\n\n        warp.type = w.Effect;\n        warp.lname = GetS(w.level);\n\n        warp.warpto = w.LevelWarp;\n        warp.lvl_i = w.LevelEnt;\n\n        warp.lvl_o = w.MapWarp;\n        warp.world_x = w.MapX;\n        warp.world_y = w.MapY;\n\n        warp.stars = w.Stars;\n        warp.layer = GetL(w.Layer);\n        warp.unknown = w.Hidden;\n\n        warp.novehicles = w.NoYoshi;\n        warp.allownpc = w.WarpNPC;\n        warp.locked = w.Locked;\n\n        // custom fields:\n        warp.two_way = w.twoWay;\n\n        warp.cannon_exit = w.cannonExit;\n        warp.cannon_exit_speed = w.cannonExitSpeed;\n        warp.event_enter = GetE(w.eventEnter);\n        warp.event_exit = GetE(w.eventExit);\n        warp.stars_msg = GetS(w.StarsMsg);\n        warp.star_num_hide = w.noPrintStars;\n        warp.hide_entering_scene = w.noEntranceScene;\n\n        warp.stood_state_required = w.stoodRequired;\n        warp.transition_effect = w.transitEffect;\n\n        // fix this to update as needed\n        if(warp.layer.empty())\n            warp.layer = \"Default\";\n\n        warp.meta.array_id = out.doors_array_id++;\n        out.doors.push_back(warp);\n    }\n\n    for(int i = 1; i <= numWater; ++i)\n    {\n        const auto &p = Water[i];\n\n        pez.x = (int32_t)p.Location.X;\n        pez.y = (int32_t)p.Location.Y;\n        pez.w = (int32_t)p.Location.Width;\n        pez.h = (int32_t)p.Location.Height;\n        // pez.buoy = p.Buoy;\n        pez.env_type = p.Type; // p.Quicksand ? LevelPhysEnv::ENV_QUICKSAND : LevelPhysEnv::ENV_WATER;\n        pez.layer = GetL(p.Layer);\n\n        // fix this to update as needed\n        if(pez.layer.empty())\n            pez.layer = \"Default\";\n\n        pez.meta.array_id = out.physenv_array_id++;\n        out.physez.push_back(pez);\n    }\n\n    // clear the layers to prevent duplicates of the built-in layers\n    out.layers_array_id = 1;\n    out.layers.clear();\n\n    for(int i = 0; i < numLayers; ++i)\n    {\n        const auto &l = Layer[i];\n\n        layer.name = l.Name;\n        layer.hidden = l.Hidden;\n\n        layer.meta.array_id = out.layers_array_id++;\n        out.layers.push_back(layer);\n    }\n\n    // clear the events to prevent duplicates of the built-in events\n    out.events_array_id = 1;\n    out.events.clear();\n\n    for(int i = 0; i < numEvents; ++i)\n    {\n        const auto &e = Events[i];\n\n        evt.name = e.Name;\n        evt.msg = GetS(e.Text);\n        evt.sound_id = e.Sound;\n        evt.end_game = e.EndGame;\n        evt.layers_hide.clear();\n        for(layerindex_t i : e.HideLayer)\n        {\n            evt.layers_hide.push_back(GetL(i));\n        }\n        evt.layers_show.clear();\n        for(layerindex_t i : e.ShowLayer)\n        {\n            evt.layers_show.push_back(GetL(i));\n        }\n        evt.layers_toggle.clear();\n        for(layerindex_t i : e.ToggleLayer)\n        {\n            evt.layers_toggle.push_back(GetL(i));\n        }\n\n        LevelEvent_Sets s;\n\n        for(int j = 0; j < numSections; ++j)\n        {\n            const auto &ss = e.section[j];\n\n            s.id = j;\n\n            s.music_id = ss.music_id;\n            s.background_id = ss.background_id;\n            s.music_file = GetS(ss.music_file);\n\n            s.position_left = ss.position.X;\n            s.position_top = ss.position.Y;\n            s.position_right = ss.position.Width;\n            s.position_bottom = ss.position.Height;\n\n            s.autoscrol = ss.autoscroll;\n            s.autoscrol_x = (double)ss.autoscroll_x;\n            s.autoscrol_y = (double)ss.autoscroll_y;\n            s.autoscroll_style = LevelEvent_Sets::AUTOSCROLL_SIMPLE;\n\n            if(j >= (int)evt.sets.size())\n                evt.sets.push_back(s);\n            else\n                evt.sets[j] = s;\n        }\n\n        evt.trigger = GetE(e.TriggerEvent);\n        evt.trigger_timer = e.TriggerDelay;\n\n        evt.nosmoke = e.LayerSmoke;\n\n        evt.ctrl_altjump = e.Controls.AltJump;\n        evt.ctrl_altrun = e.Controls.AltRun;\n        evt.ctrl_down   = e.Controls.Down;\n        evt.ctrl_drop   = e.Controls.Drop;\n        evt.ctrl_jump   = e.Controls.Jump;\n        evt.ctrl_left   = e.Controls.Left;\n        evt.ctrl_right  = e.Controls.Right;\n        evt.ctrl_run    = e.Controls.Run;\n        evt.ctrl_start  = e.Controls.Start;\n        evt.ctrl_up     = e.Controls.Up;\n\n        evt.autostart = e.AutoStart;\n        evt.movelayer = GetL(e.MoveLayer);\n        evt.layer_speed_x = (double)e.SpeedX;\n        evt.layer_speed_y = (double)e.SpeedY;\n\n        evt.move_camera_x = (double)e.AutoX;\n        evt.move_camera_y = (double)e.AutoY;\n        evt.scroll_section = e.AutoSection;\n\n        evt.meta.array_id = out.events_array_id++;\n        out.events.push_back(evt);\n    }\n\n    if(!FileFormats::SaveLevelFile(out, FilePath, (FileFormats::LevelFileFormat)format, version))\n    {\n        pLogWarning(\"Error while saving the level file: %s\", out.meta.ERROR_info.c_str());\n        PlaySound(SFX_Smash);\n        return;\n    }\n\n    AppPathManager::syncFs();\n\n    // the rest of this stuff is all meant to be appropriately loading data\n    // from the chosen folder\n    // LoadNPCDefaults\n    // If Dir(FileNamePath & Left(FileName, Len(FileName) - 4) & \"\\*.txt\") <> \"\" Then\n    //     FindCustomNPCs FileNamePath & Left(FileName, Len(FileName) - 4)\n    // Else\n    //     FindCustomNPCs\n    // End If\n\n    // UnloadCustomGFX\n    // LoadCustomGFX\n\n    // LoadCustomGFX2 FileNamePath & Left(FileName, Len(FileName) - 4)\n    PlaySound(SFX_GotItem);\n}\n"
  },
  {
    "path": "src/editor/write_level.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef WRITE_LEVEL_HHHH\n#define WRITE_LEVEL_HHHH\n\n#include <string>\n\nvoid SaveLevel(const std::string &FilePath, int format, int version = 64);\n\n#endif // WRITE_LEVEL_HHHH\n"
  },
  {
    "path": "src/editor/write_world.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"sound.h\"\n#include \"write_common.h\"\n#include \"main/game_info.h\"\n#include <PGE_File_Formats/file_formats.h>\n#include <AppPath/app_path.h>\n#include \"Logger/logger.h\"\n\nvoid SaveWorld(const std::string& FilePath, int format, int version)   // Saves the world!\n{\n    WorldData out;\n    WorldTerrainTile terra;\n    WorldScenery scene;\n    WorldPathTile path;\n    WorldLevelTile level;\n    WorldMusicBox musicbox;\n    WorldAreaRect area;\n\n    FileFormats::CreateWorldData(out);\n\n    out.meta.configPackId = \"TheXTech\";\n    out.meta.engineFeatureLevel = g_gameInfo.contentFeatureLevel;\n\n    out.EpisodeTitle = WorldName;\n    out.IntroLevel_file = StartLevel;\n    out.HubStyledWorld = NoMap;\n    out.restartlevel = RestartLevel;\n    out.stars = MaxWorldStars;\n    out.custom_params = WldxCustomParams;\n\n    out.nocharacter.clear();\n    for(int i = 1; i <= 5; ++i)\n        out.nocharacter.push_back(blockCharacter[i]);\n    out.charactersToS64();\n\n    for(int A = 1; A <= numWorldCredits; ++A)\n    {\n        if(!out.authors.empty())\n            out.authors.push_back('\\n');\n        out.authors.append(WorldCredits[A]);\n    }\n\n    // remove all trailing new-lines and spacings\n    while(!out.authors.empty() && (out.authors.back() == '\\n' || out.authors.back() == ' '))\n        out.authors.pop_back();\n\n    for(int i = 1; i <= numTiles; ++i)\n    {\n        auto &t = Tile[i];\n        terra = FileFormats::CreateWldTile();\n        terra.x = t.Location.X;\n        terra.y = t.Location.Y;\n        terra.id = t.Type;\n        terra.meta.array_id = out.tile_array_id++;\n        out.tiles.push_back(terra);\n    }\n\n    for(int i = 1; i <= numScenes; ++i)\n    {\n        auto &s = Scene[i];\n        scene = FileFormats::CreateWldScenery();\n        scene.x = s.Location.X;\n        scene.y = s.Location.Y;\n        scene.id = s.Type;\n        scene.meta.array_id = out.scene_array_id++;\n        out.scenery.push_back(scene);\n    }\n\n    for(int i = 1; i <= numWorldPaths; ++i)\n    {\n        auto &p = WorldPath[i];\n        path = FileFormats::CreateWldPath();\n        path.x = p.Location.X;\n        path.y = p.Location.Y;\n        path.id = p.Type;\n        path.meta.array_id = out.path_array_id++;\n        out.paths.push_back(path);\n    }\n\n    for(int i = 1; i <= numWorldLevels; ++i)\n    {\n        auto &s = WorldLevel[i];\n        level = FileFormats::CreateWldLevel();\n        level.x = s.Location.X;\n        level.y = s.Location.Y;\n        level.id = s.Type;\n        level.lvlfile = s.FileName;\n        level.title= s.LevelName;\n        level.top_exit = s.LevelExit[1];\n        level.left_exit = s.LevelExit[2];\n        level.bottom_exit = s.LevelExit[3];\n        level.right_exit = s.LevelExit[4];\n        level.meta.array_id = out.path_array_id++;\n        level.entertowarp = s.StartWarp;\n        level.alwaysVisible = s.Visible;\n        level.gamestart = s.Start;\n        level.gotox = s.WarpX;\n        level.gotoy = s.WarpY;\n        level.pathbg = s.Path;\n        level.bigpathbg = s.Path2;\n\n        // new:\n        level.starsShowPolicy = s.starsShowPolicy;\n\n        out.levels.push_back(level);\n    }\n\n    for(int i = 1; i <= numWorldMusic; i++)\n    {\n        auto &s = WorldMusic[i];\n        musicbox = FileFormats::CreateWldMusicbox();\n        musicbox.x = s.Location.X;\n        musicbox.y = s.Location.Y;\n        musicbox.id = s.Type;\n\n        // new:\n        musicbox.music_file = GetS(s.MusicFile);\n\n        musicbox.meta.array_id = out.musicbox_array_id++;\n        out.music.push_back(musicbox);\n    }\n\n    for(int i = 1; i <= numWorldAreas; i++)\n    {\n        auto &s = WorldArea[i];\n\n        area = WorldAreaRect();\n\n        area.x = s.Location.X;\n        area.y = s.Location.Y;\n        area.w = s.Location.Width;\n        area.h = s.Location.Height;\n\n        area.flags |= WorldAreaRect::SETUP_SET_VIEWPORT;\n\n        area.meta.array_id = out.arearect_array_id++;\n        out.arearects.push_back(area);\n    }\n\n    if(!FileFormats::SaveWorldFile(out, FilePath, (FileFormats::WorldFileFormat)format, version))\n    {\n        pLogWarning(\"Error while saving the level file: %s\", out.meta.ERROR_info.c_str());\n        PlaySound(SFX_Smash);\n        return;\n    }\n\n    AppPathManager::syncFs();\n\n    PlaySound(SFX_GotItem);\n}\n"
  },
  {
    "path": "src/editor/write_world.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef WRITE_WORLD_HHHH\n#define WRITE_WORLD_HHHH\n\n#include <string>\n\nvoid SaveWorld(const std::string &FilePath, int format, int version = 64);\n\n#endif // WRITE_WORLD_HHHH\n"
  },
  {
    "path": "src/editor.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef EDITOR_H\n#define EDITOR_H\n\n#include <string>\n\n#include \"location.h\"\n#include \"global_constants.h\"\n\nenum NPCID : vbint_t;\n\nextern std::string Backup_FullFileName;\nextern int editor_section_toast;\n\nextern bool HasCursor;\nextern bool NoReallyKillIt;\nextern int curSection;\n\nextern bool enableAutoAlign;\n\nnamespace OptCursor_t\n{\n    enum Modes {\n        LVL_SELECT = 13,\n        LVL_ERASER = 6,\n        LVL_BLOCKS = 1,\n        LVL_BGOS = 3,\n        LVL_NPCS = 4,\n        LVL_SECTION = 2,\n        LVL_PLAYERSTART = 18,\n        LVL_WARPS = 5,\n        LVL_WATER = 15,\n        LVL_EVENTS = 17,\n        WLD_PATHS = 10,\n        WLD_MUSIC = 11,\n        WLD_SCENES = 8,\n        WLD_LEVELS = 9,\n        WLD_TILES = 7,\n        WLD_AREA = 16,\n    };\n}\n\n// NEW: set / reset the scrolls when changing sections\nvoid ResetSectionScrolls();\nvoid SetSection(int i);\n\nvoid EditorBackup();\nvoid EditorRestore();\n\n// this sub handles the level editor\n// it is still called when the player is testing a level in the editor in windowed mode\nextern void UpdateEditor();\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\nextern void UpdateInterprocess();\n#endif\n\n// extern int EditorNPCFrame(const NPCID A, vbint_t C, int N = 0);\nextern int EditorNPCFrame(const NPCID A, vbint_t& C, int N = 0);\n\nextern void GetEditorControls();\n\nextern void SetCursor();\n\nextern void PositionCursor();\n\nextern void HideCursor();\n\nextern void KillWarp(int A);\n\nextern void zTestLevel(bool magicHand = false, bool interProcess = false);\n\nextern void MouseMove(int X, int Y, bool nCur = false);\n\nextern void ResetNPC(NPCID A);\n\n#endif // EDITOR_H\n"
  },
  {
    "path": "src/eff_id.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef ENUMEFFID_HHH\n#define ENUMEFFID_HHH\n\n#include \"global_constants.h\"\n\nenum EFFID : vbint_t\n{\n    EFFID_SMOKE_S3_CENTER = -10,\n    EFFID_BLOCK_SMASH = 1,\n    EFFID_FODDER_S3_SQUISH = 2,\n    EFFID_CHAR1_DIE = 3,\n    EFFID_FODDER_S3_DIE = 4,\n    EFFID_CHAR2_DIE = 5,\n    EFFID_RED_FODDER_SQUISH = 6,\n    EFFID_RED_FODDER_DIE = 7,\n    EFFID_GRN_SHELL_S3_DIE = 8,\n    EFFID_RED_SHELL_S3_DIE = 9,\n    EFFID_SMOKE_S3 = 10,\n    EFFID_COIN_BLOCK_S3 = 11,\n    EFFID_BIG_FIREBALL_TAIL = 12,\n    EFFID_LAVA_SPLASH = 13,\n    EFFID_MINIBOSS_DIE = 14,\n    EFFID_BULLET_DIE = 15,\n    EFFID_BIG_BULLET_DIE = 16,\n    EFFID_RED_GUY_DIE = 17,\n    EFFID_BLU_GUY_DIE = 18,\n    EFFID_GLASS_SHELL_DIE = 19,\n    EFFID_JUMPER_S3_DIE = 20,\n    EFFID_BLU_BLOCK_SMASH = 21,\n    EFFID_UNDER_FODDER_DIE = 22,\n    EFFID_UNDER_FODDER_SQUISH = 23,\n    EFFID_RED_FISH_S1_DIE = 24,\n    EFFID_HEAVY_THROWER_DIE = 25,\n    EFFID_GRN_BOOT_DIE = 26,\n    EFFID_SPIKY_S3_DIE = 27,\n    EFFID_SPIT_BOSS_BALL_DIE = 28,\n    EFFID_SPIT_BOSS_DIE = 29,\n    EFFID_SLIDE_BLOCK_SMASH = 30,\n    EFFID_SPIKY_BALL_S3_DIE = 31,\n    EFFID_SPIKY_THROWER_DIE = 32,\n    EFFID_CRAB_DIE = 33,\n    EFFID_FLY_DIE = 34,\n    EFFID_EXT_TURTLE_SQUISH = 35,\n    EFFID_EXT_TURTLE_DIE = 36,\n    EFFID_YELSWITCH_FODDER_SQUISH = 37,\n    EFFID_YELSWITCH_FODDER_DIE = 38,\n    EFFID_BLUSWITCH_FODDER_SQUISH = 39,\n    EFFID_BLUSWITCH_FODDER_DIE = 40,\n    EFFID_GRNSWITCH_FODDER_SQUISH = 41,\n    EFFID_GRNSWITCH_FODDER_DIE = 42,\n    EFFID_REDSWITCH_FODDER_SQUISH = 43,\n    EFFID_REDSWITCH_FODDER_DIE = 44,\n    EFFID_BIG_FODDER_SQUISH = 45,\n    EFFID_BIG_FODDER_DIE = 46,\n    EFFID_BIG_SHELL_DIE = 47,\n    EFFID_POWER_S3_DIE = 48,\n    EFFID_JUMPER_S4_DIE = 49,\n    EFFID_VILLAIN_S3_DIE = 50,\n    EFFID_BLOCK_S1_SMASH = 51,\n    EFFID_FODDER_S1_SQUISH = 52,\n    EFFID_FODDER_S1_DIE = 53,\n    EFFID_DOOR_S2_OPEN = 54,\n    EFFID_DOOR_DOUBLE_S3_OPEN = 55,\n    EFFID_ITEM_POD_OPEN = 56,\n    EFFID_ITEM_POD_BREAK = 57,\n    EFFID_PET_BIRTH = 58,\n    EFFID_DOOR_SIDE_S3_OPEN = 59,\n    EFFID_SHELL_S4_DIE = 60,\n    EFFID_HIT_TURTLE_S4_DIE = 61,\n    EFFID_HIT_TURTLE_S4_SQUISH = 62,\n    EFFID_SMOKE_S5 = 63,\n    EFFID_BIRD_DIE = 64,\n    EFFID_RED_SPIT_GUY_DIE = 65,\n    EFFID_BLU_SPIT_GUY_DIE = 66,\n    EFFID_GRY_SPIT_GUY_DIE = 67,\n    EFFID_SPIT_GUY_BALL_DIE = 68,\n    EFFID_BOMB_S2_EXPLODE = 69,\n    EFFID_BOMB_S3_EXPLODE_SEED = 70,\n    EFFID_BOMB_S3_EXPLODE = 71,\n    EFFID_WALK_BOMB_S3_DIE = 72,\n    EFFID_WHIP = 73,\n    EFFID_SKID_DUST = 74,\n    EFFID_WHACK = 75,\n    EFFID_BOOT_STOMP = 76,\n    EFFID_PLR_FIREBALL_TRAIL = 77,\n    EFFID_COIN_COLLECT = 78,\n    EFFID_SCORE = 79,\n    EFFID_SPARKLE = 80,\n    EFFID_COIN_SWITCH_PRESS = 81,\n    EFFID_SPINBLOCK = 82,\n    EFFID_CARRY_BUDDY_DIE = 83,\n    EFFID_BRUTE_SQUISH = 84,\n    EFFID_BRUTE_SQUISHED_DIE = 85,\n    EFFID_BRUTE_DIE = 86,\n    EFFID_BIG_GUY_DIE = 87,\n    EFFID_CARRY_FODDER_DIE = 88,\n    EFFID_CHASER_DIE = 89,\n    EFFID_STONE_S3_DIE = 90,\n    EFFID_BIG_GHOST_DIE = 91,\n    EFFID_GHOST_S4_DIE = 92,\n    EFFID_GHOST_FAST_DIE = 93,\n    EFFID_GHOST_S3_DIE = 94,\n    EFFID_GRN_SHELL_S1_DIE = 95,\n    EFFID_RED_SHELL_S1_DIE = 96,\n    EFFID_SKELETON_DIE = 97,\n    EFFID_STONE_S4_DIE = 98,\n    EFFID_SAW_DIE = 99,\n    EFFID_GRY_BLOCK_SMASH = 100,\n    EFFID_RED_BOOT_DIE = 101,\n    EFFID_BLU_BOOT_DIE = 102,\n    EFFID_BIG_DOOR_OPEN = 103,\n    EFFID_LAVA_MONSTER_LOOK = 104,\n    EFFID_VILLAIN_S1_DIE = 105,\n    EFFID_SICK_BOSS_DIE = 106,\n    EFFID_SPACE_BLOCK_SMASH = 107,\n    EFFID_BOSS_FRAGILE_EXPLODE = 108,\n    EFFID_WALL_TURTLE_DIE = 109,\n    EFFID_WALL_SPARK_DIE = 110,\n    EFFID_BOSS_CASE_BREAK = 111,\n    EFFID_BOSS_FRAGILE_DIE = 112,\n    EFFID_AIR_BUBBLE = 113,\n    EFFID_WATER_SPLASH = 114,\n    EFFID_GRN_FISH_S3_DIE = 115,\n    EFFID_RED_FISH_S3_DIE = 116,\n    EFFID_SQUID_S3_DIE = 117,\n    EFFID_GRN_FISH_S4_DIE = 118,\n    EFFID_GRN_FISH_S1_DIE = 119,\n    EFFID_BONE_FISH_DIE = 120,\n    EFFID_SQUID_S1_DIE = 121,\n    EFFID_YEL_FISH_S4_DIE = 122,\n    EFFID_TIME_SWITCH_PRESS = 123,\n    EFFID_TNT_PRESS = 124,\n    EFFID_EARTHQUAKE_BLOCK_HIT = 125,\n    EFFID_FODDER_S5_SQUISH = 126,\n    EFFID_FODDER_S5_DIE = 127,\n    EFFID_STACKER_DIE = 128,\n    EFFID_CHAR3_DIE = 129,\n    EFFID_CHAR4_DIE = 130,\n    EFFID_SMOKE_S4 = 131,\n    EFFID_STOMP_INIT = 132,\n    EFFID_STOMP_STAR = 133,\n    EFFID_CHAR5_DIE = 134,\n    EFFID_DIRT_BLOCK_SMASH = 135,\n    EFFID_FIRE_DISK_DIE = 136,\n    EFFID_WALK_PLANT_DIE = 137,\n    EFFID_BOMBER_BOSS_DIE = 138,\n    EFFID_PLR_ICEBALL_TRAIL = 139,\n    EFFID_MAGIC_BOSS_DIE = 140,\n    EFFID_BAT_DIE = 141,\n    EFFID_VINE_BUG_DIE = 142,\n    EFFID_FIRE_BOSS_DIE = 143,\n    EFFID_BUBBLE_POP = 144,\n    EFFID_ITEM_THROWER_DIE = 145,\n    EFFID_SPIKY_S4_DIE = 146,\n    EFFID_SMOKE_S2 = 147,\n    EFFID_CHAR3_HEAVY_EXPLODE = 148,\n    EFFID_GENERIC_NPC_DIE = 149,\n    EFFID_GENERIC_NPC_SQUISH = 150,\n};\n\n\n#endif // ENUMEFFID_HHH\n"
  },
  {
    "path": "src/effect.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"config.h\"\n\n#include \"effect.h\"\n#include \"npc.h\"\n#include \"npc_id.h\"\n#include \"eff_id.h\"\n#include \"npc_traits.h\"\n#include \"sound.h\"\n#include \"game_main.h\"\n#include \"collision.h\"\n#include \"layers.h\"\n#include \"graphics/gfx_update.h\"\n\n#include \"main/trees.h\"\n\n// should be tuned based on profiling of Effect-heavy cases (consider Col.'s Cathedral in SRW2)\nstatic constexpr int s_kill_stack_size = 16;\n\n// Updates the effects\nvoid UpdateEffects()\n{\n// please reference the /graphics/effect folder to see what the effects are\n\n//    int A = 0;\n    int B = 0;\n//    bool DontSpawnExit = false;\n//    bool DontResetMusic = false;\n    int CoinCount = 0;\n\n    if(FreezeNPCs)\n        return;\n\n    vbint_t killed_effects[s_kill_stack_size];\n    int num_killed = 0;\n\n    For(A, 1, numEffects)\n    {\n        auto &e = Effect[A];\n        e.Life -= 1;\n\n        // moved code for Life == 0 for EFFID_MINIBOSS_DIE below\n\n        e.Location.X += e.Location.SpeedX;\n        e.Location.Y += e.Location.SpeedY;\n\n        if(e.Type == EFFID_FODDER_S3_SQUISH || e.Type == EFFID_FODDER_S5_SQUISH || e.Type == EFFID_RED_FODDER_SQUISH\n            || e.Type == EFFID_UNDER_FODDER_SQUISH || e.Type == EFFID_EXT_TURTLE_SQUISH || e.Type == EFFID_YELSWITCH_FODDER_SQUISH\n            || e.Type == EFFID_BLUSWITCH_FODDER_SQUISH || e.Type == EFFID_GRNSWITCH_FODDER_SQUISH || e.Type == EFFID_REDSWITCH_FODDER_SQUISH\n            || e.Type == EFFID_BIG_FODDER_SQUISH || e.Type == EFFID_FODDER_S1_SQUISH || e.Type == EFFID_HIT_TURTLE_S4_SQUISH\n            || e.Type == EFFID_GENERIC_NPC_SQUISH) // Stomped Goombas\n        {\n            e.Location.SpeedY = 0;\n            e.Location.SpeedX = 0;\n        }\n        else if(e.Type == EFFID_BOSS_FRAGILE_DIE)\n        {\n            if(e.Life % 5 == 0)\n            {\n                num_t dx = dRand();\n                num_t dy = dRand();\n                NewEffect(EFFID_BOSS_FRAGILE_EXPLODE, newLoc(e.Location.X + (dx * (int_ok)e.Location.Width),\n                                      e.Location.Y + (dy * (int_ok)e.Location.Height)));\n            }\n        }\n        else if(e.Type == EFFID_BOSS_CASE_BREAK)\n        {\n            e.Location.SpeedY += 0.5_n;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 7)\n                    e.Frame = 0;\n                if(e.Frame >= 14)\n                    e.Frame = 7;\n            }\n        }\n        else if(e.Type == EFFID_BOSS_FRAGILE_EXPLODE)\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n            }\n\n            if(e.Frame >= 7)\n                e.Life = 0;\n        }\n        else if(e.Type == EFFID_FIRE_DISK_DIE) // RotoDisk\n        {\n            if(e.Location.SpeedX != 0 || e.Location.SpeedY != 0)\n                e.Location.SpeedY += 0.5_n;\n            e.Frame += 1;\n            if(e.Frame >= 5)\n                e.Frame = 0;\n        }\n        else if(e.Type == EFFID_BOMB_S2_EXPLODE) // bomb\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 2)\n            {\n                e.FrameCount = 0;\n                if(e.Frame == 0)\n                    e.Frame = 1;\n                else\n                    e.Frame = 0;\n            }\n        }\n        else if(e.Type == EFFID_BLOCK_SMASH || e.Type == EFFID_BLU_BLOCK_SMASH || e.Type == EFFID_SLIDE_BLOCK_SMASH\n            || e.Type == EFFID_BLOCK_S1_SMASH || e.Type == EFFID_GRY_BLOCK_SMASH || e.Type == EFFID_DIRT_BLOCK_SMASH\n            || e.Type == EFFID_ITEM_POD_BREAK) // Block break\n        {\n            e.Location.SpeedY += 0.6_n;\n            e.Location.SpeedX = e.Location.SpeedX * 0.99_r;\n            if(e.Location.SpeedY >= 10)\n                e.Location.SpeedY = 10;\n\n            if(e.Type != EFFID_ITEM_POD_BREAK)\n            {\n                e.FrameCount += 1;\n                if(e.FrameCount >= 3)\n                {\n                    e.FrameCount = 0;\n                    e.Frame += 1;\n                    if(e.Frame == 4)\n                        e.Frame = 0;\n                }\n            }\n        }\n        else if(e.Type == EFFID_MAGIC_BOSS_DIE) // larry shell\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.Frame += 1;\n                e.FrameCount = 0;\n            }\n\n            if(e.Frame > 7)\n                e.Frame = 0;\n\n            if(HasSound(SFX_MagicBossShell) && g_config.sfx_modern) // When new sound is presented, do animation a bit differently\n            {\n                if(e.Life == 75)\n                    e.Location.SpeedY = -5;\n                else if(e.Life == 65)\n                    e.Location.SpeedY = -8;\n                else if(e.Life == 60)\n                    e.Location.SpeedY = -11;\n                else if(e.Life == 52)\n                    e.Location.SpeedY = -14;\n            }\n            else if(e.Life == 100) // Old sound\n            {\n                e.Location.SpeedY = -8;\n                PlaySoundSpatial(SFX_SickBossKilled, e.Location);\n            }\n        }\n        else if(e.Type == EFFID_WATER_SPLASH) // Splash\n        {\n            e.FrameCount += 1;\n\n            // simplified logic\n            if(e.FrameCount < 40)\n                e.Frame = e.FrameCount / 8;\n            else\n                e.Life = 0;\n\n            if(e.FrameCount % 3 == 0)\n                e.Frame = 5;\n        }\n        else if(e.Type == EFFID_AIR_BUBBLE) // Water Bubbles\n        {\n            if(e.NewNpc == 0)\n            {\n                bool tempBool = false;\n                for(B = 1; B <= numWater; B++)\n                {\n                    if(CheckCollision(e.Location, Water[B].Location) && !Water[B].Hidden)\n                    {\n                        tempBool = true;\n                        break;\n                    }\n                }\n\n                if(!tempBool)\n                    e.Life = 0;\n            }\n            e.FrameCount += 1;\n            if(e.FrameCount < 4)\n                e.Frame = 0;\n            else if(e.FrameCount < 6)\n                e.Frame = 1;\n            else\n            {\n                e.FrameCount = 0;\n                e.Frame = 0;\n            }\n            e.Location.Y -= 2;\n            e.Location.X += dRand() * 2 - 1;\n        }\n        else if(e.Type == EFFID_CHAR1_DIE || e.Type == EFFID_CHAR2_DIE || e.Type == EFFID_CHAR3_DIE || e.Type == EFFID_CHAR4_DIE || e.Type == EFFID_CHAR5_DIE) // Mario & Luigi death\n            e.Location.SpeedY += 0.25_n;\n        else if(e.Type == EFFID_ITEM_THROWER_DIE || e.Type == EFFID_WALL_SPARK_DIE || e.Type == EFFID_FODDER_S5_DIE || e.Type == EFFID_FODDER_S3_DIE || e.Type == EFFID_FIRE_BOSS_DIE || e.Type == EFFID_VINE_BUG_DIE || e.Type == EFFID_RED_FODDER_DIE || e.Type == EFFID_UNDER_FODDER_DIE || e.Type == EFFID_SPIKY_BALL_S3_DIE || e.Type == EFFID_CRAB_DIE || e.Type == EFFID_FLY_DIE || e.Type == EFFID_YELSWITCH_FODDER_DIE || e.Type == EFFID_BLUSWITCH_FODDER_DIE || e.Type == EFFID_GRNSWITCH_FODDER_DIE || e.Type == EFFID_REDSWITCH_FODDER_DIE || e.Type == EFFID_BIG_FODDER_DIE || e.Type == EFFID_FODDER_S1_DIE || e.Type == EFFID_SQUID_S3_DIE) // Goomba air ride of dooom\n        {\n            e.Location.SpeedY += 0.5_n;\n            if(e.Location.SpeedY >= 10)\n                e.Location.SpeedY = 10;\n            e.FrameCount += 1;\n            if(e.Type == EFFID_WALL_SPARK_DIE || e.Type == EFFID_FIRE_BOSS_DIE)\n                e.FrameCount += 1;\n            if(e.FrameCount >= 8)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 2)\n                    e.Frame = 0;\n            }\n        }\n        else if(e.Type == EFFID_LAVA_MONSTER_LOOK) // Blaarg eyes\n        {\n            e.Life += 2;\n            if(e.Life <= 30)\n                e.Location.SpeedY = -2.8_n;\n            else if(e.Life <= 40)\n                e.Location.SpeedY = 0.5_n;\n            else if(e.Life <= 80)\n                e.Location.SpeedY = 0;\n            else if(e.Life <= 100)\n                e.Location.SpeedY = 2;\n            else\n                e.Life = 0;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 16)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 2)\n                    e.Frame = 0;\n            }\n        }\n        else if(e.Type == EFFID_HIT_TURTLE_S4_DIE) // Beack Koopa\n        {\n            e.Location.SpeedY += 0.5_n;\n            if(e.Location.SpeedY >= 10)\n                e.Location.SpeedY = 10;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 15)\n            {\n                e.FrameCount = 0;\n                e.Frame -= 1;\n            }\n            else if(e.FrameCount == 8)\n                e.Frame += 1;\n        }\n        // these are non-animated standard death effects\n        else if(e.Type == EFFID_GRN_SHELL_S3_DIE || e.Type == EFFID_RED_SHELL_S3_DIE || e.Type == EFFID_BULLET_DIE\n            || e.Type == EFFID_BIG_BULLET_DIE || e.Type == EFFID_GLASS_SHELL_DIE || e.Type == EFFID_SPIKY_S3_DIE\n            || e.Type == EFFID_SPIKY_S4_DIE || e.Type == EFFID_SPIT_BOSS_BALL_DIE || e.Type == EFFID_SPIT_BOSS_DIE\n            || e.Type == EFFID_SPIKY_THROWER_DIE || e.Type == EFFID_EXT_TURTLE_DIE || e.Type == EFFID_BIG_SHELL_DIE\n            || e.Type == EFFID_SHELL_S4_DIE || e.Type == EFFID_GRN_SHELL_S1_DIE || e.Type == EFFID_RED_SHELL_S1_DIE\n            || e.Type == EFFID_WALL_TURTLE_DIE || e.Type == EFFID_HEAVY_THROWER_DIE || e.Type == EFFID_POWER_S3_DIE\n            || e.Type == EFFID_JUMPER_S4_DIE || e.Type == EFFID_VILLAIN_S3_DIE || e.Type == EFFID_SPIT_GUY_BALL_DIE\n            || e.Type == EFFID_WALK_BOMB_S3_DIE || e.Type == EFFID_CHASER_DIE || e.Type == EFFID_STONE_S3_DIE\n            || e.Type == EFFID_BIG_GHOST_DIE || e.Type == EFFID_GHOST_S4_DIE || e.Type == EFFID_GHOST_FAST_DIE\n            || e.Type == EFFID_GHOST_S3_DIE || e.Type == EFFID_STONE_S4_DIE || e.Type == EFFID_SAW_DIE\n            || e.Type == EFFID_VILLAIN_S1_DIE || e.Type == EFFID_BOMBER_BOSS_DIE || e.Type == EFFID_SICK_BOSS_DIE\n            || e.Type == EFFID_BAT_DIE || e.Type == EFFID_GENERIC_NPC_DIE) // Flying turtle shell / Bullet bill /hard thing, combined with section with comment \"Bullet Bill / Hammer Bro\"\n        {\n            e.Location.SpeedY += 0.5_n;\n            if(e.Location.SpeedY >= 10)\n                e.Location.SpeedY = 10;\n        }\n        else if(e.Type == EFFID_GRN_BOOT_DIE || e.Type == EFFID_RED_BOOT_DIE || e.Type == EFFID_BLU_BOOT_DIE) // Goombas shoes\n        {\n            e.Location.SpeedY += 0.5_n;\n\n            if(e.Location.SpeedY >= 10)\n                e.Location.SpeedY = 10;\n\n            if(e.Location.SpeedX > 0)\n                e.Frame = 0 + SpecialFrame[1];\n            else\n                e.Frame = 2 + SpecialFrame[1];\n        }\n        else if(e.Type == EFFID_SMOKE_S3 || e.Type == EFFID_SMOKE_S4) // SMW / SMB3 Puff of smoke\n        {\n            // no longer double SpeedX for SMOKE_S3 or SMOKE_S4\n            // multiplied SpeedX constants by 2 elsewhere.\n\n            // e.Location.X += e.Location.SpeedX;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 3)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 4)\n                    e.Life = 0;\n            }\n        }\n        else if(e.Type == EFFID_SMOKE_S2) // SMB2 Puff of smoke\n        {\n            // this did nothing in SMBX 1.3 because SpeedX was always 0\n            // e.Location.X += e.Location.SpeedX;\n\n            e.FrameCount += 1;\n            if(e.FrameCount >= 6)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 4)\n                    e.Life = 0;\n            }\n        }\n        else if(e.Type == EFFID_STOMP_INIT) // stomp stars\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 3)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 2)\n                {\n                    e.Life = 0;\n                    NewEffect(EFFID_STOMP_STAR, e.Location);\n                }\n            }\n        }\n        else if(e.Type == EFFID_STOMP_STAR) // stomp stars\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 1)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 4)\n                    e.Frame = 0;\n            }\n        }\n        else if(e.Type == EFFID_WHIP) // Tail whack\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 2)\n            {\n                // If .Frame = 0 Then\n                    // .Frame = 2\n                // ElseIf .Frame = 2 Then\n                    // .Frame = 1\n                // ElseIf .Frame = 1 Then\n                    // .Frame = 3\n                // Else\n                e.Frame += 1;\n                if(e.Frame > 3)\n                {\n                    e.Frame = 0;\n                    e.Life = 0;\n                }\n                e.FrameCount = 0;\n            }\n        }\n        else if(e.Type == EFFID_WHACK) // Whack\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.Frame += 1;\n                if(e.Frame > 1)\n                    e.Life = 0;\n                e.FrameCount = 0;\n            }\n        }\n        else if(e.Type == EFFID_BOOT_STOMP)\n        {\n            // doubled speed in NewEffect\n            // e.Location.X += e.Location.SpeedX;\n            // e.Location.Y += e.Location.SpeedY;\n        }\n        else if(e.Type == EFFID_COIN_SWITCH_PRESS || e.Type == EFFID_TIME_SWITCH_PRESS || e.Type == EFFID_TNT_PRESS) // P Switch\n        {\n            if(e.Life == 1)\n                NewEffect(EFFID_SMOKE_S3_CENTER, e.Location);\n        }\n        else if(e.Type == EFFID_SKID_DUST) // Slide Smoke\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.Frame += 1;\n                e.FrameCount = 0;\n                if(e.Frame > 2)\n                    e.Life = 0;\n            }\n        }\n        else if(e.Type == EFFID_SMOKE_S5) // Zelda Smoke\n        {\n            // this had no effect because SpeedX/SpeedY was always zero for EFFID_SMOKE_S5 in SMBX 1.3\n            // e.Location.X += e.Location.SpeedX;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 4)\n                    e.Life = 0;\n            }\n        }\n        else if(e.Type == EFFID_COIN_BLOCK_S3) // Coin out of block effect\n        {\n            if(e.Life == 1)\n            {\n                CoinCount += 1;\n                if(CoinCount > 13)\n                    CoinCount = 10;\n                MoreScore(CoinCount, e.Location);\n            }\n\n            // simplified logic\n            if(e.Life <= 6)\n            {\n                // NOTE: 4, 5, 6 are valid frames.\n                // This sets Frame to 7 on the frame when Life is 0.\n                e.Frame = 4 + (6 - e.Life) / 2;\n                // NOTE: The same behavior at VB6 code: just checks the boolean expression and writes 0 or -1\n                e.Location.SpeedY = (e.Location.SpeedY == 0) ? 0 : -1;\n            }\n            else\n            {\n                e.Location.SpeedY += 0.4_n;\n                e.FrameCount += 1;\n                if(e.FrameCount >= 3)\n                {\n                    e.FrameCount = 0;\n                    e.Frame += 1;\n                    if(e.Frame >= 4)\n                        e.Frame = 0;\n                }\n            }\n        }\n        else if(e.Type == EFFID_BIG_FIREBALL_TAIL) // Big Fireball Tail\n        {\n            // .Location.SpeedX = 0\n            // .Location.SpeedY = 0\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n            }\n        }\n        else if(e.Type == EFFID_COIN_COLLECT) // Coin\n        {\n            e.Location.SpeedX = 0;\n            e.Location.SpeedY = 0;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 5)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 3)\n                    e.Life = 0;\n            }\n        }\n        else if(e.Type == EFFID_SPINBLOCK) // Spinning block\n        {\n            e.Frame = SpecialFrame[3];\n            if(e.Life < 10)\n            {\n                bool tempBool = false;\n                for(B = 1; B <= numPlayers; B++)\n                {\n                    if(!Player[B].Dead && Player[B].TimeToLive == 0)\n                    {\n                        if(CheckCollision(e.Location, Player[B].Location))\n                        {\n                            tempBool = true;\n                            break;\n                        }\n                    }\n                }\n                // tempBool = True\n                if(!tempBool)\n                {\n                    e.Life = 0;\n                    e.Frame = 3;\n                    Block[e.NewNpc].Hidden = false;\n                    invalidateDrawBlocks();\n                }\n                else\n                    e.Life = 10;\n            }\n        }\n        else if(e.Type == EFFID_SPARKLE) // Twinkle\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 8)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 3)\n                    e.Life = 0;\n            }\n        }\n        else if(e.Type == EFFID_PLR_FIREBALL_TRAIL || e.Type == EFFID_PLR_ICEBALL_TRAIL) // Small Fireball Tail\n        {\n            e.Location.X += dRand() * 2 - 1;\n            e.Location.Y += dRand() * 2 - 1;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame == 3 || e.Frame == 6 || e.Frame == 9 || e.Frame == 12 || e.Frame == 15)\n                    e.Life = 0;\n            }\n        }\n        else if(e.Type == EFFID_LAVA_SPLASH) // Big Fireball Tail\n        {\n            e.Location.SpeedX = 0;\n            e.Location.SpeedY = 0;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 6)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                // e.FrameCount = 0;\n            }\n        }\n        else if(e.Type == EFFID_MINIBOSS_DIE) // Dead Big Koopa\n        {\n            // moved from top\n            if(e.Life == 0)\n            {\n                if(e.NewNpc > 0)\n                {\n                    numNPCs++;\n                    auto &nn = NPC[numNPCs];\n                    nn.Type = NPCID(e.NewNpc);\n\n                    nn.Location.Height = nn->THeight;\n                    nn.Location.Width = nn->TWidth;\n                    nn.Location.X = e.Location.X + (e.Location.Width - NPC[numNPCs].Location.Width) / 2;\n                    nn.Location.Y = e.Location.Y - 1;\n                    nn.Location.SpeedY = -6;\n\n                    // this would fix the fact that the code was moved from before the effect's speed was applied, but SpeedX / SpeedY is always 0 for EFFID_MINIBOSS_DIE\n                    // nn.Location.X -= e.Location.SpeedX;\n                    // nn.Location.Y -= e.Location.SpeedY;\n\n                    nn.Active = true;\n                    nn.TimeLeft = 100;\n                    nn.Frame = 0;\n                    syncLayers_NPC(numNPCs);\n                    CheckSectionNPC(numNPCs);\n                    PlaySoundSpatial(SFX_BossBeat, e.Location);\n                }\n            }\n\n            e.Location.SpeedX = 0;\n            e.Location.SpeedY = 0;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 2)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                // e.FrameCount = 0;\n                if(e.Frame >= 4)\n                    e.Frame = 0;\n            }\n        }\n        else if(e.Type == EFFID_BOMB_S3_EXPLODE_SEED) // SMB3 Bomb Part 1\n        {\n            if(e.FrameCount == 0)\n            {\n                NewEffect(EFFID_BOMB_S3_EXPLODE, e.Location, e.Frame);\n                e.Frame += 1;\n                if(e.Frame >= 4)\n                    e.Frame = 0;\n            }\n            else if(e.FrameCount >= 6)\n                e.FrameCount = -1;\n            e.FrameCount += 1;\n        }\n        else if(e.Type == EFFID_BOMB_S3_EXPLODE || e.Type == EFFID_CHAR3_HEAVY_EXPLODE) // SMB3 Bomb Part 2\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame >= 4)\n                    e.Frame = 0;\n            }\n\n            if(e.Type == EFFID_CHAR3_HEAVY_EXPLODE && iRand(10) >= 8)\n            {\n                NewEffect(EFFID_PLR_FIREBALL_TRAIL, e.Location, 3);\n                Effect[numEffects].Location.SpeedX = dRand() * 3 - 1.5_n;\n                Effect[numEffects].Location.SpeedY = dRand() * 3 - 1.5_n;\n            }\n        }\n        else if(e.Type == EFFID_EARTHQUAKE_BLOCK_HIT) // POW Block\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.FrameCount = 0;\n                e.Frame += 1;\n                if(e.Frame >= 4)\n                {\n                    e.Life = 0;\n                    e.Frame = 3;\n                }\n            }\n        }\n        else if(e.Type == EFFID_DOOR_S2_OPEN || e.Type == EFFID_DOOR_DOUBLE_S3_OPEN || e.Type == EFFID_DOOR_SIDE_S3_OPEN || e.Type == EFFID_BIG_DOOR_OPEN) // door\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount > 60)\n                e.Life = 0;\n            else if(e.FrameCount > 55)\n                e.Frame = 0;\n            else if(e.FrameCount > 50)\n                e.Frame = 1;\n            else if(e.FrameCount > 45)\n                e.Frame = 2;\n            else if(e.FrameCount > 40)\n                e.Frame = 3;\n            else if(e.FrameCount > 20)\n                e.Frame = 4;\n            else if(e.FrameCount > 15)\n                e.Frame = 3;\n            else if(e.FrameCount > 10)\n                e.Frame = 2;\n            else if(e.FrameCount > 5)\n                e.Frame = 1;\n        }\n        else if(e.Type == EFFID_STACKER_DIE)\n        {\n            e.Location.SpeedY += 0.5_n;\n            if(e.Location.SpeedY >= 10)\n                e.Location.SpeedY = 10;\n            e.FrameCount += 1;\n            e.Frame = 5;\n            if(e.FrameCount >= 16)\n            {\n                e.FrameCount = 0;\n                //e.Frame = 5; // Already 5!\n            }\n            else if(e.FrameCount > 8)\n                e.Frame = 4;\n\n            // don't combine with below, because it's lopsided: 9 final values (0-8) use Frame 5, and 7 final values (9-15) use Frame 4\n        }\n        else if(e.Type == EFFID_RED_GUY_DIE || e.Type == EFFID_BLU_GUY_DIE || e.Type == EFFID_JUMPER_S3_DIE\n            || e.Type == EFFID_RED_FISH_S1_DIE || (e.Type >= EFFID_BIRD_DIE && e.Type <= EFFID_GRY_SPIT_GUY_DIE)\n            || e.Type == EFFID_CARRY_BUDDY_DIE // Shy guy free falling (uses frames 4-7)\n            || e.Type == EFFID_BRUTE_SQUISHED_DIE || e.Type == EFFID_BRUTE_DIE || e.Type == EFFID_BIG_GUY_DIE\n            || e.Type == EFFID_CARRY_FODDER_DIE || e.Type == EFFID_SKELETON_DIE || e.Type == EFFID_GRN_FISH_S3_DIE\n            || e.Type == EFFID_YEL_FISH_S4_DIE || e.Type == EFFID_RED_FISH_S3_DIE || e.Type == EFFID_GRN_FISH_S4_DIE\n            || e.Type == EFFID_GRN_FISH_S1_DIE || e.Type == EFFID_BONE_FISH_DIE || e.Type == EFFID_SQUID_S1_DIE\n            || e.Type == EFFID_WALK_PLANT_DIE // Rex / mega mole / smw goomba free falling\n            )\n        {\n            e.Location.SpeedY += 0.5_n;\n            if(e.Location.SpeedY >= 10)\n                e.Location.SpeedY = 10;\n            e.FrameCount += 1;\n            if(e.FrameCount >= 8)\n            {\n                e.FrameCount = 0;\n                // flip least significant bit to switch frame while keeping same frame set\n                e.Frame ^= 1;\n            }\n        }\n        else if(e.Type == EFFID_ITEM_POD_OPEN) // Egg\n        {\n            if(e.NewNpc == 0 && e.FrameCount < 19)\n                e.FrameCount = 19;\n            e.FrameCount += 1;\n            if(e.FrameCount == 10)\n                e.Frame += 1;\n            else if(e.FrameCount == 20)\n            {\n                e.Frame = 2;\n                NewEffect(EFFID_ITEM_POD_BREAK, e.Location);\n            }\n            else if(e.FrameCount == 30)\n            {\n                e.Life = 0;\n                if(!LevelEditor && e.NewNpc != NPCID_ITEM_POD)\n                {\n                    if(NPCIsYoshi((NPCID)e.NewNpc))\n                    {\n                        if(NewEffect(EFFID_PET_BIRTH, e.Location, 1))\n                            Effect[numEffects].NewNpc = e.NewNpc;\n                    }\n                    else if(e.NewNpc > 0)\n                    {\n                        numNPCs++;\n                        auto &nn = NPC[numNPCs];\n                        nn.Location = e.Location;\n                        nn.Active = true;\n                        nn.TimeLeft = 100;\n                        nn.Direction = 0;\n                        nn.Type = NPCID(e.NewNpc);\n                        nn.Location.Height = nn->THeight;\n                        nn.Location.Width = nn->TWidth;\n                        nn.Location.Y += 32 - nn.Location.Height;\n                        nn.Location.X += -nn.Location.Width / 2 + 16;\n\n                        if(nn.Type == NPCID_LEAF_POWER)\n                            nn.Location.SpeedY = -6;\n\n                        // this is new TheXTech logic\n                        if(NPCTraits[e.NewNpc].IsFish || NPCIsAParaTroopa((NPCID)e.NewNpc) || e.NewNpc == NPCID_FIRE_CHAIN)\n                        {\n                            nn.Special = e.NewNpcSpecial;\n                            nn.DefaultSpecial = e.NewNpcSpecial;\n                        }\n\n                        // this is new TheXTech logic\n                        if(e.NewNpc == NPCID_STAR_EXIT || e.NewNpc == NPCID_STAR_COLLECT || e.NewNpc == NPCID_MEDAL)\n                            nn.Variant = e.NewNpcSpecial;\n\n                        syncLayers_NPC(numNPCs);\n                        CheckSectionNPC(numNPCs);\n                    }\n                }\n            }\n        }\n        else if(e.Type == EFFID_SPACE_BLOCK_SMASH)\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount >= 4)\n            {\n                e.Frame += 1;\n                e.FrameCount = 0;\n            }\n\n            if(e.Frame >= 3)\n                e.Life = 0;\n        }\n        else if(e.Type == EFFID_PET_BIRTH) // yoshi grow\n        {\n            e.FrameCount += 1;\n            if(e.FrameCount < 10)\n                e.Frame = 0;\n            else if(e.FrameCount < 20)\n                e.Frame = 1;\n            else if(e.FrameCount < 30)\n                e.Frame = 0;\n            else if(e.FrameCount < 40)\n                e.Frame = 1;\n            else if(e.FrameCount < 50)\n                e.Frame = 0;\n            else if(e.FrameCount < 60)\n                e.Frame = 1;\n            else\n            {\n                e.Frame = 1;\n                e.Life = 0;\n                numNPCs++;\n                auto &nn = NPC[numNPCs];\n                nn.Location = e.Location;\n                nn.Active = true;\n                nn.TimeLeft = 100;\n                nn.Direction = 1;\n                nn.Type = NPCID(e.NewNpc);\n                nn.Location.Height = nn->THeight;\n                nn.Location.Width = nn->TWidth;\n                syncLayers_NPC(numNPCs);\n                CheckSectionNPC(numNPCs);\n            }\n\n            if(e.NewNpc == NPCID_PET_BLUE)\n                e.Frame += 2;\n            else if(e.NewNpc == NPCID_PET_YELLOW)\n                e.Frame += 4;\n            else if(e.NewNpc == NPCID_PET_RED)\n                e.Frame += 6;\n            else if(e.NewNpc == NPCID_PET_BLACK)\n                e.Frame += 8;\n            else if(e.NewNpc == NPCID_PET_PURPLE)\n                e.Frame += 10;\n            else if(e.NewNpc == NPCID_PET_PINK)\n                e.Frame += 12;\n            else if(e.NewNpc == NPCID_PET_CYAN)\n                e.Frame += 14;\n        }\n        else if(e.Type == EFFID_SCORE)\n            e.Location.SpeedY = e.Location.SpeedY * 0.97_r;\n\n        // check for killed (lets us only do a single loop over effects)\n        if(e.Life <= 0)\n        {\n            if(num_killed < s_kill_stack_size)\n                killed_effects[num_killed] = A;\n\n            num_killed++;\n        }\n    } //for\n\n    if(num_killed > s_kill_stack_size)\n    {\n        for(int A = numEffects; A >= 1; --A)\n        {\n            if(Effect[A].Life <= 0)\n                KillEffect(A);\n        }\n    }\n    else\n    {\n        for(int A = num_killed - 1; A >= 0; --A)\n            KillEffect(killed_effects[A]);\n    }\n}\n\nenum NewEffect_PosRoutine\n{\n    NE_KEEP_POS = 0,\n    NE_CENTER_X = 1,\n    NE_CENTER_Y = 2,\n    NE_FLOOR_Y = 4,\n};\n\nbool NewEffect(EFFID A, const Location_t &Location, int Direction, bool Shadow)\n{\n// this sub creates effects\n// please reference the /graphics/effect folder to see what the effects are\n// A is the effect type\n\n    int B = 0;\n\n    if(numEffects >= maxEffects - 4)\n        return false;\n\n    // cases where more than one Effect is spawned\n    if(A == EFFID_BLOCK_SMASH || A == EFFID_BLU_BLOCK_SMASH || A == EFFID_SLIDE_BLOCK_SMASH || A == EFFID_BLOCK_S1_SMASH || A == EFFID_GRY_BLOCK_SMASH || A == EFFID_DIRT_BLOCK_SMASH || A == EFFID_ITEM_POD_BREAK) // Block break effect\n    {\n        for(B = 1; B <= 4; B++)\n        {\n            numEffects++;\n            auto &ne = Effect[numEffects];\n            ne.Shadow = Shadow;\n            ne.Location.Width = EffectWidth[A];\n            ne.Location.Height = EffectHeight[A];\n            ne.Type = A;\n            ne.Life = 200;\n\n            ne.Location.X = Location.X;\n            ne.Location.Y = Location.Y;\n            ne.Location.SpeedX = 3;\n\n            if(B == 1 || B == 2)\n                ne.Location.SpeedY = -11;\n            else\n            {\n                ne.Location.SpeedY = -7;\n                ne.Location.Y += Location.Height / 2;\n            }\n\n            if(A == EFFID_ITEM_POD_BREAK)\n            {\n                if(B == 1 || B == 2)\n                    ne.Location.SpeedX = 2;\n                else\n                    ne.Location.SpeedX = 1.5_n;\n\n                if(B == 1)\n                    ne.Frame = 0;\n                else if(B == 2)\n                    ne.Frame = 1;\n                else if(B == 3)\n                    ne.Frame = 3;\n                else\n                    ne.Frame = 2;\n            }\n\n            if(B == 1 || B == 3)\n                ne.Location.SpeedX = -ne.Location.SpeedX;\n            else\n                ne.Location.X += Location.Width / 2;\n\n            auto rand_mul = (A == EFFID_ITEM_POD_BREAK) ? 0.25_rb : 1.0_rb;\n            ne.Location.SpeedX += (dRand() * 2 - 1) * rand_mul;\n            ne.Location.SpeedY += (dRand() * 4 - 2) * rand_mul;\n        }\n\n        return true;\n    }\n    else if(A == EFFID_BOOT_STOMP || A == EFFID_STOMP_STAR) // \"SMW Smashed\", combined with \"stomp star part 2\"\n    {\n        for(B = 1; B <= 4; B++)\n        {\n            if(numEffects < maxEffects)\n            {\n                numEffects++;\n                auto &ne = Effect[numEffects];\n                ne.Shadow = Shadow;\n                ne.Type = A;\n                ne.Location.Width = EffectWidth[A];\n                ne.Location.Height = EffectHeight[A];\n                ne.Location.X = Location.X + (Location.Width - ne.Location.Width) / 2;\n                ne.Location.Y = Location.Y + (Location.Height - ne.Location.Height) / 2;\n\n                if(A == EFFID_BOOT_STOMP)\n                {\n                    ne.Life = 15;\n                    ne.Location.SpeedX = 4.8_n; // 3 * 0.8; * 2 because SpeedX got applied twice for EFFID_BOOT_STOMP in SMBX 1.3\n                    ne.Location.SpeedY = 2.4_n; // 1.5 * 0.8; * 2 because SpeedY got applied twice for EFFID_BOOT_STOMP in SMBX 1.3\n                }\n                else\n                {\n                    ne.Life = 8;\n                    ne.Location.SpeedX = 2;\n                    ne.Location.SpeedY = 2;\n                }\n\n                if(B == 1 || B == 2)\n                    ne.Location.SpeedY = -ne.Location.SpeedY;\n                if(B == 1 || B == 3)\n                    ne.Location.SpeedX = -ne.Location.SpeedX;\n\n                if(A == EFFID_STOMP_STAR)\n                {\n                    ne.Location.Y += ne.Location.SpeedY * 6;\n                    ne.Location.X += ne.Location.SpeedX * 6;\n                }\n\n                ne.Frame = 0;\n            }\n        }\n\n        return true;\n    }\n    else if(A == EFFID_CHAR3_HEAVY_EXPLODE) // Heart Bomb\n    {\n        for(B = 1; B <= 6; B++)\n        {\n            if(numEffects < maxEffects)\n            {\n                numEffects++;\n                auto &ne = Effect[numEffects];\n                ne.Shadow = Shadow;\n\n                ne.Location.Width = EffectWidth[A];\n                ne.Location.Height = EffectHeight[A];\n                ne.Location.X = Location.X + (Location.Width - ne.Location.Width) / 2;\n                ne.Location.Y = Location.Y + (Location.Height - ne.Location.Height) / 2;\n\n                if(B == 1 || B == 3 || B == 4 || B == 6)\n                {\n                    ne.Life = 10;\n                    ne.Location.SpeedY = 1.75_n; // 3.5 * 0.5\n                    ne.Location.SpeedX = 1; // 2 * 0.5\n                }\n                else\n                {\n                    ne.Life = 11;\n                    ne.Location.SpeedY = 0;\n                    ne.Location.SpeedX = 2; // 4 * 0.5\n                }\n\n                if(B <= 3)\n                    ne.Location.SpeedX = -ne.Location.SpeedX;\n                if(B == 1 || B == 6)\n                    ne.Location.SpeedY = -ne.Location.SpeedY;\n\n                // Note: Direction was always the default value of 1 in SMBX 1.3\n                // if(int(Direction) % 2 == 0)\n                //     std::swap(ne.Location.SpeedX, ne.Location.SpeedY);\n\n                ne.Location.X += ne.Location.SpeedX * 3;\n                ne.Location.Y += ne.Location.SpeedY * 3;\n\n                ne.Frame = iRand(4);\n                ne.Type = A;\n            }\n        }\n\n        return true;\n    }\n    else if(A == EFFID_BOMB_S3_EXPLODE) // SMB3 Bomb Part 2\n    {\n        for(B = 1; B <= 6; B++)\n        {\n            if(numEffects < maxEffects)\n            {\n                numEffects++;\n                auto &ne = Effect[numEffects];\n                ne.Shadow = Shadow;\n\n                ne.Location.Width = 16;\n                ne.Location.Height = 16;\n                ne.Location.X = Location.X;\n                ne.Location.Y = Location.Y;\n\n                if(B == 1 || B == 3 || B == 4 || B == 6)\n                {\n                    ne.Location.SpeedY = 4.5_n; // 3 * 1.5\n                    ne.Location.SpeedX = 3; // 2 * 1.5\n                    ne.Life = 14;\n                }\n                else\n                {\n                    ne.Location.SpeedY = 0;\n                    ne.Location.SpeedX = 6; // 4 * 1.5\n                    ne.Life = 13;\n                }\n\n                if(B <= 3)\n                    ne.Location.SpeedX = -ne.Location.SpeedX;\n                if(B == 1 || B == 6)\n                    ne.Location.SpeedY = -ne.Location.SpeedY;\n\n                // Note: Direction was always the default value of 1 in SMBX 1.3\n                // if(int(Direction) % 2 == 0)\n                //     std::swap(ne.Location.SpeedX, ne.Location.SpeedY);\n\n                // ne.Location.SpeedX = ne.Location.SpeedX * 1.5;\n                // ne.Location.SpeedY = ne.Location.SpeedY * 1.5;\n\n                ne.Frame = Direction;\n                ne.Type = A;\n            }\n        }\n\n        return true;\n    }\n    else if(A == EFFID_COIN_COLLECT) // Coins\n    {\n        for(B = 1; B <= 4; B++)\n        {\n            numEffects++;\n            auto &ne = Effect[numEffects];\n            ne.Shadow = Shadow;\n            ne.Location.Width = EffectWidth[A];\n            ne.Location.Height = EffectHeight[A];\n            ne.Location.X = Location.X + (Location.Width - ne.Location.Width) / 2;\n            ne.Location.Y = Location.Y + (Location.Height - ne.Location.Height) / 2;\n            ne.Location.SpeedY = 0;\n            ne.Location.SpeedX = 0;\n            if(B == 1)\n                ne.Location.X -= 10;\n            if(B == 3)\n                ne.Location.X += 10;\n            if(B == 2)\n                ne.Location.Y += 16;\n            if(B == 4)\n                ne.Location.Y -= 16;\n            ne.Frame = 0 - B;\n            ne.Life = 20 * B;\n            ne.Type = A;\n        }\n\n        return true;\n    }\n\n    // how should the position get adjusted based on the size?\n    int pos_routine = NE_KEEP_POS;\n\n    // very special case for SMOKE_S3_CENTER\n    if(A == EFFID_SMOKE_S3_CENTER)\n    {\n        A = EFFID_SMOKE_S3;\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n    }\n\n    // this is the most common logic across all effects\n    numEffects++;\n    auto &ne = Effect[numEffects];\n    num_t old_speedY = ne.Location.SpeedY; // for EFFID_BIG_FIREBALL_TAIL (#1101)\n\n    // default initialization\n    ne.Shadow = Shadow;\n    ne.Type = A;\n    ne.NewNpc = 0; // it was never left uninitialized for Effects that use it\n    ne.NewNpcSpecial = 0; // it was never left uninitialized for Effects that use it\n\n    ne.Location.X = Location.X;\n    ne.Location.Y = Location.Y;\n    ne.Location.Width = EffectWidth[A];\n    ne.Location.Height = EffectHeight[A];\n    ne.Location.SpeedX = 0;\n    ne.Location.SpeedY = 0;\n\n    // they were actually reset to these values in the destructors, so we can add debug asserts here\n    SDL_assert(ne.Frame == 0);\n    SDL_assert(ne.FrameCount == 0);\n    ne.Frame = 0;\n    ne.FrameCount = 0;\n\n    // begin switch on type\n    if(A == EFFID_MAGIC_BOSS_DIE) // larry shell\n    {\n        ne.Life = 160;\n        pos_routine = NE_CENTER_X | NE_FLOOR_Y;\n        PlaySoundSpatial(SFX_MagicBossKilled, Location);\n    }\n    else if(A == EFFID_LAVA_MONSTER_LOOK) // Blaarg eyes\n    {\n        if(Direction == -1)\n            ne.Location.X -= 48;\n\n        pos_routine = NE_CENTER_X;\n\n        ne.Location.Width = 32;\n        ne.Location.Height = 32;\n        ne.Life = 10;\n    }\n    else if(A == EFFID_ITEM_POD_OPEN || A == EFFID_PET_BIRTH) // Egg break / Yoshi grow\n    {\n        // ne.NewNpc = 0; // NewNpc -- now gets set at callsite\n        // ne.NewNpcSpecial = 0; // NewNpc -- now gets set at callsite\n        // if(ne.NewNpc == NPCID_ITEM_POD) (logic moved to callsite)\n        //     ne.NewNpc = 0;\n\n        ne.Location.Width = 32;\n        ne.Location.Height = 32;\n        ne.Life = 100;\n\n        if(A == EFFID_ITEM_POD_OPEN)\n        {\n            // moved to callsite\n            // if(ne.NewNpc != 0 /*&& ne.NewNpc != NPCID_ITEM_POD*/) // never 96, because of condition above that replaces 96 with zero\n            //     PlaySoundSpatial(SFX_PetBirth, Location);\n            // else\n            //     PlaySoundSpatial(SFX_Smash, Location);\n        }\n        else if(A == EFFID_PET_BIRTH)\n            PlaySoundSpatial(SFX_Pet, Location);\n    }\n    else if(A == EFFID_FIRE_DISK_DIE) // Roto Disk\n    {\n        ne.Location.SpeedX = Location.SpeedX;\n        ne.Location.SpeedY = Location.SpeedY;\n        ne.Location.Width = Location.Width;\n        ne.Location.Height = Location.Width; // this is a bug\n\n        ne.Life = 10;\n    }\n    else if(A == EFFID_EARTHQUAKE_BLOCK_HIT) // pow\n    {\n        pos_routine = NE_CENTER_X | NE_FLOOR_Y;\n        ne.Life = 100;\n    }\n    else if(A == EFFID_SPACE_BLOCK_SMASH) // Metroid Block\n    {\n        ne.Location.Width = 32;\n        ne.Location.Height = 32;\n\n        ne.Life = 100;\n    }\n    else if(A == EFFID_FODDER_S3_SQUISH || A == EFFID_RED_FODDER_SQUISH || A == EFFID_UNDER_FODDER_SQUISH\n        || A == EFFID_EXT_TURTLE_SQUISH || A == EFFID_YELSWITCH_FODDER_SQUISH || A == EFFID_BLUSWITCH_FODDER_SQUISH\n        || A == EFFID_GRNSWITCH_FODDER_SQUISH || A == EFFID_REDSWITCH_FODDER_SQUISH || A == EFFID_BIG_FODDER_SQUISH\n        || A == EFFID_FODDER_S1_SQUISH || A == EFFID_HIT_TURTLE_S4_SQUISH || A == EFFID_BRUTE_SQUISH\n        || A == EFFID_FODDER_S5_SQUISH || A == EFFID_GENERIC_NPC_SQUISH) // Goomba smash effect\n    {\n        PlaySoundSpatial(SFX_Stomp, Location); // Stomp sound\n\n        ne.Location.Width = 32;\n        ne.Location.Height = 34;\n\n        ne.Life = 20;\n\n        if(A == EFFID_BIG_FODDER_SQUISH)\n        {\n            ne.Location.Height = 46;\n            ne.Location.Width = 48;\n        }\n\n        if(A == EFFID_BRUTE_SQUISH)\n        {\n            if(Direction == 1)\n                ne.Frame = 1;\n        }\n    }\n    else if(A == EFFID_COIN_SWITCH_PRESS || A == EFFID_TIME_SWITCH_PRESS || A == EFFID_TNT_PRESS) // P Switch\n    {\n        pos_routine = NE_CENTER_X | NE_FLOOR_Y;\n        ne.Life = 120;\n    }\n    else if(A == EFFID_AIR_BUBBLE || A == EFFID_WATER_SPLASH) // Water Bubble / Splash\n    {\n        ne.Location.Height = EffectWidth[A]; // this is a bug\n\n        ne.Life = 300;\n        // ne.NewNpc = 0; // NewNpc -- now gets set at callsite\n\n        if(A == EFFID_WATER_SPLASH) // Change height for the background\n        {\n            bool tempBool = false;\n            for(const Background_t& b : treeBackgroundQuery(ne.Location, SORTMODE_ID))\n            {\n                if(b.Type == 82 || b.Type == 26 || b.Type == 65 || b.Type == 159 || b.Type == 166 || b.Type == 168)\n                {\n                    // note: NOT a reference\n                    auto t = b.Location;\n                    if(t.Height > 8 && g_config.fix_submerged_splash_effect)\n                        t.Height = 8; // Limit the height\n                    if(CheckCollision(ne.Location, t))\n                    {\n                        if(b.Type == 82 || b.Type == 159)\n                            ne.Location.Y = b.Location.Y - ne.Location.Height + 12;\n                        if(b.Type == 26)\n                            ne.Location.Y = b.Location.Y - ne.Location.Height + 6;\n                        if(b.Type == 168)\n                            ne.Location.Y = b.Location.Y - ne.Location.Height + 8;\n                        if(b.Type == 166)\n                            ne.Location.Y = b.Location.Y - ne.Location.Height + 10;\n                        if(b.Type == 65)\n                            ne.Location.Y = b.Location.Y - ne.Location.Height + 16;\n                        tempBool = true;\n                        break;\n                    }\n                }\n            }\n\n            if(!tempBool)\n                numEffects -= 1;\n        }\n    }\n    else if(A == EFFID_WALL_TURTLE_DIE) // Spike Top\n    {\n        ne.Location.Width = 32;\n        ne.Location.Height = 32;\n\n        ne.Location.SpeedY = -12;\n        ne.Location.SpeedX = Location.SpeedX;\n        ne.Frame = Direction;\n        ne.Life = 120;\n    }\n    else if(A == EFFID_BOSS_FRAGILE_EXPLODE) // Metroid\n    {\n        ne.Location.Width = 64;\n        ne.Location.Height = 64;\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n        ne.Life = 200;\n    }\n    else if(A == EFFID_SPINBLOCK) // Block Spin\n    {\n        // ne.NewNpc = 0; // NewNpc -- now gets set at callsite\n        ne.Location.Width = 32;\n        ne.Location.Height = 32;\n        ne.Life = 300;\n    }\n    else if(A == EFFID_CHAR1_DIE || A == EFFID_CHAR2_DIE || A == EFFID_CHAR3_DIE || A == EFFID_CHAR4_DIE || A == EFFID_CHAR5_DIE) // Mario & Luigi died effect\n    {\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n        ne.Location.SpeedY = -11;\n\n        if(A == EFFID_CHAR5_DIE)\n        {\n            if(Direction == 1)\n                ne.Frame = 1;\n        }\n\n        ne.Life = 150;\n    }\n    else if(A == EFFID_SCORE) // Score\n    {\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n        ne.Location.X += dRand() * 32 - 16;\n        ne.Location.Y += dRand() * 32 - 16;\n        ne.Location.SpeedY = -2;\n\n        ne.Life = 60;\n    }\n    else if(A == EFFID_BOMB_S3_EXPLODE_SEED) // SMB3 Bomb Part 1\n    {\n        ne.Location.Width = 16;\n        ne.Location.Height = 16;\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n        ne.Location.SpeedY = -0;\n\n        ne.Life = 46;\n    }\n    else if(A == EFFID_DOOR_S2_OPEN || A == EFFID_DOOR_DOUBLE_S3_OPEN\n        || A == EFFID_DOOR_SIDE_S3_OPEN || A == EFFID_BIG_DOOR_OPEN) // Door Effect\n    {\n        ne.Location.Width = Location.Width;\n        ne.Location.Height = Location.Height;\n\n        ne.Life = 150;\n    }\n    else if(A == EFFID_FODDER_S3_DIE || A == EFFID_RED_FODDER_DIE || A == EFFID_GRN_SHELL_S3_DIE || A == EFFID_RED_SHELL_S3_DIE || A == EFFID_GLASS_SHELL_DIE\n        || A == EFFID_UNDER_FODDER_DIE || A == EFFID_GRN_BOOT_DIE || A == EFFID_RED_BOOT_DIE || A == EFFID_BLU_BOOT_DIE\n        || A == EFFID_SPIKY_S3_DIE || A == EFFID_SPIKY_S4_DIE || A == EFFID_SPIT_BOSS_BALL_DIE || A == EFFID_SPIT_BOSS_DIE\n        || A == EFFID_SPIKY_BALL_S3_DIE || A == EFFID_SPIKY_THROWER_DIE || A == EFFID_ITEM_THROWER_DIE || A == EFFID_CRAB_DIE\n        || A == EFFID_FLY_DIE || A == EFFID_EXT_TURTLE_DIE || A == EFFID_YELSWITCH_FODDER_DIE || A == EFFID_BLUSWITCH_FODDER_DIE\n        || A == EFFID_GRNSWITCH_FODDER_DIE || A == EFFID_REDSWITCH_FODDER_DIE || A == EFFID_BIG_FODDER_DIE || A == EFFID_BIG_SHELL_DIE\n        || A == EFFID_FODDER_S1_DIE || A == EFFID_SHELL_S4_DIE  /* || A == EFFID_RED_FODDER_SQUISH -- mistake, repeated case from above */\n        || A == EFFID_GRN_SHELL_S1_DIE || A == EFFID_RED_SHELL_S1_DIE || A == EFFID_WALL_SPARK_DIE || A == EFFID_SQUID_S3_DIE\n        || A == EFFID_SQUID_S1_DIE || A == EFFID_FODDER_S5_DIE || A == EFFID_VINE_BUG_DIE || A == EFFID_GENERIC_NPC_DIE) // Flying goomba / turtle shell / hard thing shell /*A == 9 || - duplicated*/\n    {\n        pos_routine = NE_CENTER_X | NE_FLOOR_Y;\n\n        if(num_t::fEqual_d(Location.SpeedY, 0.123_n))\n        {\n            ne.Location.SpeedY = 1;\n            ne.Location.SpeedX = 0;\n        }\n        else if(Location.SpeedY != -5.1_n)\n        {\n            ne.Location.SpeedY = -11;\n            ne.Location.SpeedX = Location.SpeedX;\n        }\n        else\n        {\n            ne.Location.SpeedY = -5.1_n;\n            ne.Location.SpeedX = Location.SpeedX * 0.6_r;\n        }\n\n        ne.Life = 150;\n\n        ne.Frame = 0;\n        if(ne.Type == EFFID_SPIT_BOSS_DIE && Direction == -1)\n            ne.Frame = 1;\n        if((ne.Type == EFFID_SPIKY_S3_DIE || ne.Type == EFFID_SPIKY_S4_DIE) && Direction == 1)\n            ne.Frame = 2;\n        if(ne.Type == EFFID_EXT_TURTLE_DIE && Direction == 1)\n            ne.Frame = 1;\n    }\n    else if(A == EFFID_SMOKE_S3_CENTER || A == EFFID_SMOKE_S3 || A == EFFID_WHIP || A == EFFID_SKID_DUST\n        || A == EFFID_WHACK || A == EFFID_SMOKE_S4 || A == EFFID_STOMP_INIT || A == EFFID_SMOKE_S2) // Puff of smoke\n    {\n        if(A == EFFID_SMOKE_S4)\n            pos_routine = NE_CENTER_X | NE_CENTER_Y;\n\n        ne.Life = 12;\n\n        if(ne.Type == EFFID_SMOKE_S2)\n            ne.Life = 24;\n\n        if(A == EFFID_STOMP_INIT || A == EFFID_WHIP || A == EFFID_WHACK)\n        {\n            ne.Location.X += dRand() * 16 - 8;\n            ne.Location.Y += dRand() * 16 - 8;\n        }\n        else if(A == EFFID_SKID_DUST)\n        {\n            ne.Location.X += dRand() * 4 - 2;\n            ne.Location.Y += dRand() * 4 - 2;\n\n            // moved from UpdateEffects\n            ne.Location.SpeedY = -0.1_n;\n        }\n    }\n    else if(A == EFFID_BUBBLE_POP) // bubble pop\n    {\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n        ne.Life = 6;\n    }\n    else if(A == EFFID_SMOKE_S5) // Zelda Style Smoke\n    {\n        ne.Location.Width = 48;\n        ne.Location.Height = 48;\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n        ne.Life = 100;\n    }\n    else if(A == EFFID_COIN_BLOCK_S3) // Coin hit out of block\n    {\n        ne.Location.Width = 32;\n        ne.Location.Height = 32;\n\n        pos_routine = NE_CENTER_X;\n        ne.Location.Y = Location.Y - ne.Location.Height; // not floor Y, this effect is above the block\n\n        ne.Location.SpeedY = -8;\n        ne.Life = 46;\n    }\n    else if(A == EFFID_BIG_FIREBALL_TAIL) // Big Fireball Tail\n    {\n        ne.Location.Width = 8;\n        ne.Location.Height = 8;\n        ne.Location.X = Location.X + 4 + (dRand() * 12);\n        ne.Location.Y = Location.Y + 40;\n        // DANGER: this is fully uninitialized (see #1101 on GitHub TheXTech/TheXTech)\n        // ' .Location.SpeedY = -8\n        ne.Location.SpeedY = old_speedY;\n\n        ne.Life = 12;\n    }\n    else if(A == EFFID_BOSS_CASE_BREAK) // Glass Shatter\n    {\n        ne.Location.Width = 16;\n        ne.Location.Height = 16;\n\n        ne.Location.SpeedY = -2 - dRand() * 10;\n        ne.Location.SpeedX = dRand() * 8 - 4;\n\n        ne.Frame = 0;\n        if(iRand(2) == 0)\n            ne.Frame = 7;\n        ne.Frame += iRand(7);\n\n        ne.Life = 300;\n    }\n    else if(A == EFFID_BOSS_FRAGILE_DIE) // Mother Brain\n    {\n        ne.Location.Width = Location.Width;\n        ne.Location.Height = Location.Height;\n\n        ne.Frame = 0;\n        if(int(Direction) == 1)\n            ne.Frame = 1;\n\n        ne.Life = 360;\n    }\n    else if(A == EFFID_PLR_FIREBALL_TRAIL || A == EFFID_PLR_ICEBALL_TRAIL) // Small Fireball Tail\n    {\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n\n        ne.Location.X += dRand() * 4 - 2;\n        ne.Location.Y += dRand() * 4 - 2;\n\n        ne.Frame = 0;\n\n        if(int(Direction) == 2)\n            ne.Frame = 3;\n        else if(int(Direction) == 3)\n            ne.Frame = 6;\n        else if(int(Direction) == 4)\n            ne.Frame = 9;\n        else if(int(Direction) == 5)\n            ne.Frame = 12;\n\n        ne.Life = 60;\n    }\n    else if(A == EFFID_SPARKLE) // Twinkle\n    {\n        ne.Location.Width = 16;\n        ne.Location.Height = 16;\n        ne.Location.X = Location.X + Location.Width / 2 - 4 + dRand() * 4 - 2;\n        ne.Location.Y = Location.Y + Location.Height / 2 - 4 + dRand() * 4 - 2;\n\n        ne.Life = 60;\n    }\n    else if(A == EFFID_LAVA_SPLASH) // Lava Splash\n    {\n        pos_routine = NE_CENTER_X;\n        ne.Location.Y = Location.Y + 24;\n        ne.Location.SpeedY = -8;\n\n        ne.Life = 100;\n    }\n    else if(A == EFFID_MINIBOSS_DIE) // Dead Big Koopa\n    {\n        pos_routine = NE_CENTER_X;\n        ne.Location.Y = Location.Y + 22;\n\n        // ne.NewNpc = 0; // NewNpc -- now gets set at callsite\n\n        ne.Life = 120;\n    }\n    else if(A == EFFID_BULLET_DIE || A == EFFID_SPIT_GUY_BALL_DIE || A == EFFID_BIG_BULLET_DIE) // Dead Bullet Bill\n    {\n        ne.Location.Width = Location.Width;\n        ne.Location.Height = Location.Height;\n\n        ne.Location.SpeedY = Location.SpeedY;\n        ne.Location.SpeedX = -Location.SpeedX;\n\n        if(int(Direction) == -1)\n            ne.Frame = 0;\n        else\n            ne.Frame = 1;\n\n        if(A == EFFID_SPIT_GUY_BALL_DIE)\n            ne.Frame = 0;\n        else if(A == EFFID_BIG_BULLET_DIE)\n            ne.Location.SpeedX = Location.SpeedX;\n\n        ne.Life = 120;\n    }\n    else if(A == EFFID_HIT_TURTLE_S4_DIE) // Flying Beach Koopa\n    {\n        ne.Location.Width = Location.Width;\n        ne.Location.Height = Location.Height;\n\n        ne.Location.SpeedY = -11;\n        ne.Location.SpeedX = -Location.SpeedX;\n        ne.Life = 120;\n\n        // NOTE: Frame set at callsite\n    }\n    else if(A == EFFID_POWER_S3_DIE) // Dead toad\n    {\n        ne.Location.Width = 32;\n        ne.Location.Height = 32;\n        // ne.Location.X += ne.Location.Width / 2 - 16;\n        // ne.Location.Y += ne.Location.Height / 2 - 16;\n\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n\n        ne.Location.SpeedX = Location.SpeedX;\n        ne.Location.SpeedY = -8;\n\n        ne.Life = 120;\n    }\n    else if(A == EFFID_BOMB_S2_EXPLODE) // Bomb\n    {\n        ne.Location.Width = 64;\n        ne.Location.Height = 64;\n        // ne.Location.X = Location.X + Location.Width / 2 - 32;\n        // ne.Location.Y = Location.Y + Location.Height / 2 - 32;\n\n        pos_routine = NE_CENTER_X | NE_CENTER_Y;\n\n        ne.Life = 60;\n    }\n    else if(A == EFFID_STACKER_DIE) // pokey\n    {\n        ne.Location.Width = Location.Width;\n        ne.Location.Height = Location.Height;\n\n        ne.Location.SpeedY = -11;\n        ne.Location.SpeedX = Location.SpeedX;\n        ne.Life = 120;\n\n        ne.Frame = 5;\n    }\n    else if(A == EFFID_RED_GUY_DIE || A == EFFID_BLU_GUY_DIE || A == EFFID_JUMPER_S3_DIE || A == EFFID_RED_FISH_S1_DIE\n            || (A >= EFFID_BIRD_DIE && A <= EFFID_GRY_SPIT_GUY_DIE) || A == EFFID_CARRY_BUDDY_DIE || A == EFFID_BRUTE_SQUISHED_DIE\n            || A == EFFID_BRUTE_DIE || A == EFFID_BIG_GUY_DIE || A == EFFID_CARRY_FODDER_DIE || A == EFFID_SKELETON_DIE\n            || A == EFFID_GRN_FISH_S3_DIE || A == EFFID_YEL_FISH_S4_DIE || A == EFFID_RED_FISH_S3_DIE || A == EFFID_GRN_FISH_S4_DIE\n            || A == EFFID_GRN_FISH_S1_DIE || A == EFFID_BONE_FISH_DIE || A == EFFID_WALK_PLANT_DIE) // Shy guy / Star Thing /Red Jumping Fish\n    {\n        pos_routine = NE_CENTER_X | NE_FLOOR_Y;\n\n        if(A != EFFID_RED_FISH_S1_DIE && A != EFFID_GRN_FISH_S3_DIE && A != EFFID_RED_FISH_S3_DIE)\n            ne.Location.SpeedY = -11;\n        else\n            ne.Location.SpeedY = Location.SpeedY;\n\n        ne.Location.SpeedX = Location.SpeedX;\n        if(Location.SpeedY == 0.123_n)\n        {\n            ne.Location.SpeedY = 1;\n            ne.Location.SpeedX = 0;\n        }\n\n        if(A == EFFID_BRUTE_SQUISHED_DIE || A == EFFID_BRUTE_DIE || A == EFFID_BIG_GUY_DIE || A == EFFID_CARRY_FODDER_DIE\n            || A == EFFID_SKELETON_DIE || A == EFFID_GRN_FISH_S3_DIE || A == EFFID_RED_FISH_S3_DIE || A == EFFID_GRN_FISH_S4_DIE\n            || A == EFFID_GRN_FISH_S1_DIE || A == EFFID_BONE_FISH_DIE || A == EFFID_YEL_FISH_S4_DIE || A == EFFID_WALK_PLANT_DIE)\n        {\n            ne.Frame = 0;\n        }\n        else\n            ne.Frame = 4;\n\n        if(int(Direction) != -1)\n            ne.Frame += 2;\n\n        ne.Life = 120;\n    }\n    else if(A == EFFID_STONE_S3_DIE || A == EFFID_BIG_GHOST_DIE || A == EFFID_GHOST_S4_DIE || A == EFFID_GHOST_FAST_DIE\n        || A == EFFID_GHOST_S3_DIE || A == EFFID_STONE_S4_DIE || A == EFFID_SAW_DIE // Case commented \"Boo / thwomps\"\n        || A == EFFID_HEAVY_THROWER_DIE || A == EFFID_JUMPER_S4_DIE || A == EFFID_VILLAIN_S3_DIE || A == EFFID_WALK_BOMB_S3_DIE\n        || A == EFFID_CHASER_DIE || A == EFFID_VILLAIN_S1_DIE || A == EFFID_SICK_BOSS_DIE || A == EFFID_BOMBER_BOSS_DIE\n        || A == EFFID_BAT_DIE) // Case commented \"Hammer Bro\"\n    {\n        ne.Location.Width = Location.Width;\n        ne.Location.Height = Location.Height;\n\n        ne.Location.SpeedY = Location.SpeedY;\n        ne.Location.SpeedX = -Location.SpeedX;\n\n        if(A == EFFID_BIG_GHOST_DIE)\n        {\n            ne.Location.set_width_center(EffectWidth[A]);\n            ne.Location.set_height_center(EffectHeight[A]);\n        }\n\n        if(ne.Location.SpeedX != 0 && ne.Location.SpeedX > -2 && ne.Location.SpeedX < 2)\n            ne.Location.SpeedX = 2 * -Direction;\n\n        if(Direction == -1)\n            ne.Frame = 0;\n        else\n            ne.Frame = 2;\n\n        if(A == EFFID_STONE_S3_DIE || A == EFFID_STONE_S4_DIE || A == EFFID_SAW_DIE)\n            ne.Frame = 0;\n        // combined case previously commented \"Hammer Bro\"\n        else if(A != EFFID_BIG_GHOST_DIE && A != EFFID_GHOST_S4_DIE && A != EFFID_GHOST_FAST_DIE && A != EFFID_GHOST_S3_DIE)\n        {\n            ne.Location.Width = EffectWidth[A];\n            ne.Location.Height = EffectHeight[A];\n\n            if(ne.Frame == 2)\n                ne.Frame = 1;\n        }\n\n        ne.Life = 120;\n    }\n    else if(A == EFFID_FIRE_BOSS_DIE) // ludwig dead\n    {\n        ne.Location.SpeedY = -14;\n        ne.Location.SpeedX = 3 * -Direction;\n        ne.Life = 200;\n\n        PlaySoundSpatial(SFX_FireBossKilled, Location);\n    }\n\n    // apply pos routine if needed\n    if(pos_routine & NE_CENTER_X)\n        ne.Location.X += (Location.Width - ne.Location.Width) / 2;\n\n    if(pos_routine & NE_CENTER_Y)\n        ne.Location.Y += (Location.Height - ne.Location.Height) / 2;\n\n    if(pos_routine & NE_FLOOR_Y)\n        ne.Location.Y += (Location.Height - ne.Location.Height);\n\n    return true;\n}\n\nvoid NewEffect_IceSparkle(const NPC_t& n, Location_t& tempLocation)\n{\n    tempLocation.Height = EffectHeight[EFFID_SPARKLE];\n    tempLocation.Width = EffectWidth[EFFID_SPARKLE];\n    tempLocation.SpeedX = 0;\n    tempLocation.SpeedY = 0;\n    tempLocation.X = n.Location.X - tempLocation.Width / 2 + dRand().times(n.Location.Width) - 4;\n    tempLocation.Y = n.Location.Y - tempLocation.Height / 2 + dRand().times(n.Location.Height) - 4;\n    NewEffect(EFFID_SPARKLE, tempLocation, 1, n.Shadow);\n}\n\nbool NewEffect_NpcDeath(EFFID legacy_effect, const NPC_t& n, EFFID generic_effect)\n{\n    if(n.GFXSlot != 0 && NewEffect(generic_effect, n.Location, n.Direction, n.Shadow))\n    {\n        auto& ne = Effect[numEffects];\n        if(n->WidthGFX)\n        {\n            ne.Location.Width = n->WidthGFX;\n            ne.Location.Height = n->HeightGFX;\n            ne.Location.X = n.Location.X + (n->FrameOffsetX * -n.Direction) - n->WidthGFX / 2 + n.Location.Width / 2;\n            ne.Location.Y = n.Location.Y + n->FrameOffsetY - n->HeightGFX + n.Location.Height;\n        }\n        else\n        {\n            ne.Location.Width = n->TWidth;\n            ne.Location.Height = n->THeight;\n            ne.Location.X = n.Location.X + n->FrameOffsetX;\n            ne.Location.Y = n.Location.Y + n->FrameOffsetY;\n        }\n\n        ne.NewNpc = n.Type;\n        ne.NewNpcSpecial = n.GFXSlot;\n        ne.Frame = n.Frame;\n        ne.FrameCount = 0;\n\n        return true;\n    }\n\n    return NewEffect(legacy_effect, n.Location, n.Direction, n.Shadow);\n}\n\nbool NewEffect_NpcDie(EFFID legacy_effect, const NPC_t& n)\n{\n    return NewEffect_NpcDeath(legacy_effect, n, EFFID_GENERIC_NPC_DIE);\n}\n\nbool NewEffect_NpcSquish(EFFID legacy_effect, const NPC_t& n)\n{\n    return NewEffect_NpcDeath(legacy_effect, n, EFFID_GENERIC_NPC_SQUISH);\n}\n\n// Remove the effect\nvoid KillEffect(int A)\n{\n    if(numEffects == 0 || A > maxEffects)\n        return;\n\n    Effect_t &e = Effect[numEffects];\n    Effect[A] = e;\n    e.Frame = 0;\n    e.FrameCount = 0;\n    e.Life = 0;\n    e.Type = EFFID(0);\n    numEffects -= 1;\n}\n"
  },
  {
    "path": "src/effect.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef EFFECT_H\n#define EFFECT_H\n\n#include \"global_constants.h\"\n#include \"location.h\"\n\n// Public Sub UpdateEffects() 'Updates the effects\n// Updates the effects\nvoid UpdateEffects();\n// Public Sub NewEffect(A As Integer, Location As Location, Optional Direction As Single = 1, Optional NewNpc As Integer = 0, Optional Shadow As Boolean = False)  'Create an effect\n// Create an effect\nbool NewEffect(EFFID A, const Location_t &Location_t, int Direction, int NewNpc, bool Shadow = false, uint8_t newNpcSpecial = 0) = delete;\nbool NewEffect(EFFID A, const Location_t &Location_t, int Direction = 1, bool Shadow = false);\n// Public Sub KillEffect(A As Integer) 'Remove the effect\n// Remove the effect\nvoid KillEffect(int A);\n\n// new sub deduplicating old logic\nvoid NewEffect_IceSparkle(const NPC_t& n, Location_t& tempLocation);\n\n// new sub deduplicating old logic\nbool NewEffect_NpcDeath(EFFID legacy_effect, const NPC_t& n, EFFID generic_effect);\n\n// new sub deduplicating old logic\nbool NewEffect_NpcDie(EFFID legacy_effect, const NPC_t& n);\n// new sub deduplicating old logic\nbool NewEffect_NpcSquish(EFFID legacy_effect, const NPC_t& n);\n\n#endif // EFFECT_H\n"
  },
  {
    "path": "src/fontman/crop_info.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CROP_INFO_H\n#define CROP_INFO_H\n\n#include <cstdint>\n\nstruct CropInfo\n{\n    //! width of current text in pixels\n    // uint16_t text_width = 0;\n\n    //! width to draw the marquee effect in pixels\n    uint16_t draw_width = 0;\n\n    //! draw offset from left in pixels\n    uint16_t offset = 0;\n\n    //! fade amount in pixels (left)\n    uint8_t fade_left = 0;\n\n    //! fade amount in pixels (right)\n    uint8_t fade_right = 0;\n\n    //! use tweaked alpha curve for text outline\n    bool for_outline = false;\n\n    //! OUTPUT variable: did the text overrun the crop window?\n    bool did_terminate = false;\n\n    /**\n     * \\brief: calculates alpha multiplier given left and right bounds of letter\n     *\n     * \\param letter_alpha output to store the letter's alpha value\n     * \\param text_alpha the text's maximum alpha value between zero and one\n     * \\param left left bound of letter in pixels from the left of the cropped region\n     * \\param right right bound of letter in pixels from the left of the cropped region\n     *\n     * \\returns false if the right bound has been passed and drawing should be interrupted\n     *\n     * Side effect: marks the did_terminate flag if returning false\n     */\n    inline bool letter_alpha(uint8_t& letter_alpha, uint16_t text_alpha, int left, int right)\n    {\n        if(left < 0)\n        {\n            letter_alpha = 0;\n            return true;\n        }\n\n        if((uint16_t)right > draw_width)\n        {\n            did_terminate = true;\n            return false;\n        }\n\n        uint16_t fade_zone_right = draw_width - fade_right;\n\n        if((uint16_t)left < fade_left)\n        {\n            letter_alpha = (text_alpha * left) / fade_left;\n\n            // blend based on proportion in fade zone\n            if((uint16_t)right > fade_left)\n                letter_alpha = (text_alpha * (right - fade_left) + letter_alpha * (fade_left - left)) / (right - left);\n        }\n        else if((uint16_t)right > fade_zone_right)\n        {\n            letter_alpha = (text_alpha * (draw_width - (uint16_t)right)) / fade_right;\n\n            // blend based on proportion in fade zone\n            if((uint16_t)left < fade_zone_right)\n                letter_alpha = (text_alpha * (fade_zone_right - left) + letter_alpha * (right - fade_zone_right)) / (right - left);\n        }\n        else\n            letter_alpha = text_alpha;\n\n        if(for_outline)\n            letter_alpha = letter_alpha * letter_alpha / text_alpha;\n\n        return true;\n    }\n};\n\n#endif // CROP_INFO_H\n"
  },
  {
    "path": "src/fontman/font_engine_base.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef FONT_ENGINE_BASE_H\n#define FONT_ENGINE_BASE_H\n\n#include <Graphics/size.h>\n#include <string>\n\n#include \"xt_color.h\"\n\n#include \"fontman/crop_info.h\"\n\nclass BaseFontEngine\n{\npublic:\n    virtual ~BaseFontEngine();\n\n    enum FontType\n    {\n        FONT_RASTER = 0,\n        FONT_TTF,\n        FONT_LEGACY\n    };\n\n    /*!\n     * \\brief Get a size of one glyph\n     * \\param utf8char Pointer to one UTF-8 character\n     * \\param charNum Character number in the line (required to compute the size of the taboluation)\n     * \\param fontSize Size of TTF font glyph\n     * \\return Width and height of one glyph in pixels\n     */\n    virtual PGE_Size glyphSize(const char *utf8char, uint32_t charNum = 0, uint32_t fontSize = 14) = 0;\n\n    /*!\n     * \\brief Print the multiline text block on the screen\n     * \\param text Multi-line text string\n     * \\param text_size The byte size of the text string to print\n     * \\param x Hotizontal screen position (at left-top corner of the block)\n     * \\param y Vertical screen position (at left-top corner of the block)\n     * \\param Red Red channel colour\n     * \\param Green Green channel colour\n     * \\param Blue Blue channel colour\n     * \\param Alpha Alpha channel level (does not issue render calls if Alpha is 0)\n     * \\param fontSize The size of the TTF font glyph\n     * \\param crop_info nullable pointer to info for crop (marquee) logic.\n     * \\return Width and height of the text block in pixels\n     */\n    virtual PGE_Size printText(const char *text, size_t text_size,\n                               int32_t x, int32_t y,\n                               XTColor color = XTColor(),\n                               uint32_t fontSize = 14,\n                               CropInfo* crop_info = nullptr) = 0;\n\n    /*!\n     * \\brief Measure the size of the multiline text block in pixels\n     * \\param text Multi-line text string\n     * \\param text_size The byte size of the text string to print\n     * \\param fontSize The size of the TTF font glyph\n     * \\return Width and height of the text block in pixels\n     */\n    inline PGE_Size textSize(const char *text, size_t text_size,\n                             uint32_t fontSize = 14)\n    {\n        return printText(text, text_size,\n            0, 0,\n            XTAlpha(0),\n            fontSize,\n            nullptr);\n    }\n\n    virtual bool isLoaded() const = 0;\n\n    virtual std::string getFontName() const = 0;\n    virtual FontType getFontType() const = 0;\n};\n\n#endif // FONT_ENGINE_BASE_H\n"
  },
  {
    "path": "src/fontman/font_manager.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_assert.h\"\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"font_manager.h\"\n#include \"font_manager_private.h\"\n#include \"raster_font.h\"\n#include \"legacy_font.h\"\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n#include \"ttf_font.h\"\n#endif\n\n#include <AppPath/app_path.h>\n#include <Graphics/graphics_funcs.h>\n#include <IniProcessor/ini_processing.h>\n#include <Logger/logger.h>\n#include <Utils/files.h>\n#include <Utils/files_ini.h>\n#include <DirManager/dirman.h>\n#include <IniProcessor/ini_processing.h>\n#include <sorting/tinysort.h>\n#include <fmt_format_ne.h>\n\n#include \"global_constants.h\"\n#include \"core/render.h\"\n#include \"global_dirs.h\"\n\n\n#include <vector>\n#ifdef LOW_MEM\n#   include <map>\n#else\n#   include <unordered_map>\n#endif\n\nBaseFontEngine::~BaseFontEngine()\n{}\n\ntypedef VPtrList<RasterFont> RasterFontsList;\ntypedef VPtrList<TtfFont> TtfFontsList;\ntypedef VPtrList<LegacyFont> LegacyFontsList;\n\n//! Complete array of available raster fonts\nstatic RasterFontsList g_rasterFonts;\nstatic RasterFontsList g_rasterFontsCustomLevel;\nstatic RasterFontsList g_rasterFontsCustom;\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\nstatic TtfFontsList    g_ttfFonts;\nstatic TtfFontsList    g_ttfFontsCustom;\nstatic TtfFontsList    g_ttfFontsCustomLevel;\n#define LOADFONTARG(x) x\n#else\n#   define LOADFONTARG(x) false\n#endif\nstatic LegacyFontsList g_legacyFonts;\n\ntypedef VPtrList<BaseFontEngine*> PtrFontsList;\nstatic PtrFontsList    g_anyFonts;\n//! Backup for the case when any custom fonts are been loaded\nstatic PtrFontsList    g_anyFontsBackup;\nstatic PtrFontsList    g_anyFontsBackupWorld;\n\n//! Default raster font to render text\nstatic BaseFontEngine  *g_defaultRasterFont = nullptr;\n\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n//! Default TTF font to render text\nstatic BaseFontEngine  *g_defaultTtfFont = nullptr;\n#endif\n\n//! IS font manager initialized?\nstatic bool             g_fontManagerIsInit = false;\n//! Does font manager uses legacy fonts fallback?\nstatic bool             g_fontManagerIsLegacy = false;\n\n#ifdef LOW_MEM\ntypedef std::map<std::string, vbint_t> FontsHash;\n#else\ntypedef std::unordered_map<std::string, vbint_t> FontsHash;\n#endif\n//! Database of available fonts\nstatic FontsHash        g_fontNameToId;\n//! Backup of the database of available fonts\nstatic FontsHash        g_fontNameToIdBackup;\nstatic FontsHash        g_fontNameToIdBackupWorld;\n\nstatic bool             g_double_pixled = false;\n\n#ifdef LOW_MEM\nstatic const vbint_t    c_smbxFontsMapMax = 10;\n#else\nstatic const vbint_t    c_smbxFontsMapMax = 20;\n#endif\n\n// Current state\nstatic vbint_t s_smbxFontsMap[c_smbxFontsMapMax];\nstatic vbint_t s_smbxFontsSizesMap[c_smbxFontsMapMax];\n\n// The initial default state\nstatic vbint_t s_smbxFontsMapDefault[c_smbxFontsMapMax];\nstatic vbint_t s_smbxFontsSizesMapDefault[c_smbxFontsMapMax];\n\n// The episode-wide preserved state without content of data directory\nstatic vbint_t s_smbxFontsMapWorld[c_smbxFontsMapMax];\nstatic vbint_t s_smbxFontsSizesMapWorld[c_smbxFontsMapMax];\n\nstatic const uint32_t c_defaultFontSize = 14;\n\nstatic bool s_fontMapsDefault = true;\nstatic bool s_fontMapsWorld = true;\nstatic std::string s_lastWorldFontsPath;\nstatic std::string s_lastCustomFontsPath;\n\n\nstatic void registerFont(BaseFontEngine* font)\n{\n    g_anyFonts.push_back(font);\n\n    vbint_t newIdx = static_cast<vbint_t>(g_anyFonts.size()) - 1;\n    auto ef = g_fontNameToId.find(font->getFontName());\n\n    // If overriding an existing font by name, also replace the SMBX code overrides\n    if(ef != g_fontNameToId.end())\n    {\n        vbint_t oldIdx = ef->second;\n        for(int i = 0; i < c_smbxFontsMapMax; ++i)\n        {\n            if(s_smbxFontsMap[i] == oldIdx)\n                s_smbxFontsMap[i] = newIdx;\n        }\n\n        ef->second = newIdx;\n    }\n    else\n        g_fontNameToId.insert({font->getFontName(), newIdx});\n}\n\nstatic void backupDefaultFontMaps()\n{\n    if(!s_fontMapsDefault)\n        return; // Don't preserve customized font maps\n\n    SDL_memcpy(s_smbxFontsMapDefault, s_smbxFontsMap, sizeof(s_smbxFontsMap));\n    SDL_memcpy(s_smbxFontsSizesMapDefault, s_smbxFontsSizesMap, sizeof(s_smbxFontsSizesMap));\n    g_anyFontsBackup = g_anyFonts;\n    g_fontNameToIdBackup = g_fontNameToId;\n}\n\nstatic void restoreDefaultFontMaps()\n{\n    if(s_fontMapsDefault)\n        return; // Don't restore if already default\n\n    SDL_memcpy(s_smbxFontsMap, s_smbxFontsMapDefault, sizeof(s_smbxFontsMap));\n    SDL_memcpy(s_smbxFontsSizesMap, s_smbxFontsSizesMapDefault, sizeof(s_smbxFontsSizesMap));\n    g_anyFonts = g_anyFontsBackup;\n    g_fontNameToId = g_fontNameToIdBackup;\n    s_fontMapsDefault = true;\n    s_fontMapsWorld = true;\n}\n\nstatic void backupWorldFontMaps()\n{\n    if(!s_fontMapsWorld)\n        return; // Don't preserve customized font maps\n\n    SDL_memcpy(s_smbxFontsMapWorld, s_smbxFontsMap, sizeof(s_smbxFontsMap));\n    SDL_memcpy(s_smbxFontsSizesMapWorld, s_smbxFontsSizesMap, sizeof(s_smbxFontsSizesMap));\n    g_anyFontsBackupWorld = g_anyFonts;\n    g_fontNameToIdBackupWorld = g_fontNameToId;\n}\n\nstatic void restoreWorldFontMaps()\n{\n    if(s_fontMapsWorld)\n        return; // Don't restore if already default\n\n    SDL_memcpy(s_smbxFontsMap, s_smbxFontsMapWorld, sizeof(s_smbxFontsMap));\n    SDL_memcpy(s_smbxFontsSizesMap, s_smbxFontsSizesMapWorld, sizeof(s_smbxFontsSizesMap));\n    g_anyFonts = g_anyFontsBackupWorld;\n    g_fontNameToId = g_fontNameToIdBackupWorld;\n    s_fontMapsWorld = true;\n}\n\nint FontManager::fontIdFromSmbxFont(int font)\n{\n    if(font >= c_smbxFontsMapMax || font < 0)\n        return -1;\n    return s_smbxFontsMap[font];\n}\n\nuint32_t FontManager::fontSizeFromSmbxFont(int font)\n{\n    if(font >= c_smbxFontsMapMax || font < 0)\n        return c_defaultFontSize;\n\n    int ret = s_smbxFontsSizesMap[font];\n    return (ret <= 0) ? c_defaultFontSize : static_cast<uint32_t>(ret);\n}\n\n\nTtfFont* FontManager::getDefaultTtfFont()\n{\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    return dynamic_cast<TtfFont*>(g_defaultTtfFont);\n#else\n    return nullptr;\n#endif\n}\n\nTtfFont* FontManager::getTtfFontByName(const std::string& fontName)\n{\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    if(fontName.empty())\n        return FontManager::getDefaultTtfFont();\n\n    FontsHash::iterator i = g_fontNameToId.find(fontName);\n    if(i == g_fontNameToId.end())\n        return FontManager::getDefaultTtfFont();\n\n    auto *ret = dynamic_cast<TtfFont*>(g_anyFonts[i->second]);\n\n    return ret ? ret : FontManager::getDefaultTtfFont();\n#else\n    (void)fontName;\n    return nullptr;\n#endif\n}\n\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\nstatic bool s_loadFontsFromDir(DirListCI &fonts_root,\n                               const std::string &subdir,\n                               RasterFontsList &outRasterFonts,\n                               TtfFontsList &outTtfFonts)\n#else\nstatic bool s_loadFontsFromDir(DirListCI &fonts_root,\n                               const std::string &subdir,\n                               RasterFontsList &outRasterFonts,\n                               bool)\n#endif\n{\n    using namespace FontManager;\n\n    /***************Load raster font support****************/\n    if(!subdir.empty() && !fonts_root.dirExistsCI(subdir))\n        return false; // Fonts manager is unavailable when directory is not exists\n\n    std::vector<std::string> files = fonts_root.getFilesList(subdir, {\".font.ini\"});\n    tinysort(files.begin(), files.end());\n\n    std::string sSubDir = subdir + (subdir.empty() ? \"\" : \"/\");\n\n    for(std::string &fonFile : files)\n    {\n        outRasterFonts.emplace_back();\n        RasterFont& rf = outRasterFonts.back();\n        std::string fontPath = fonts_root.getCurDir() + sSubDir + fonFile;\n        pLogDebug(\"Loading raster font %s...\", fontPath.c_str());\n        rf.loadFont(fontPath);\n\n        if(!rf.isLoaded())   //Pop broken font from array\n        {\n            pLogWarning(\"Failed to load the font %s\", fontPath.c_str());\n            outRasterFonts.pop_back();\n        }\n        else   //Register font name in a table\n            registerFont(&rf);\n    }\n\n    if(!outRasterFonts.empty())\n        g_defaultRasterFont = &outRasterFonts.front();\n\n    /***************Load TTF font support****************/\n    IniProcessing overrider = Files::load_ini(fonts_root.getCurDir() + sSubDir + \"overrides.ini\");\n\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    if(overrider.beginGroup(\"ttf-fonts\"))\n    {\n        for(int i = 1; ;++i)\n        {\n            std::string key = fmt::sprintf_ne(\"file%d\", i);\n            std::string keyAntiAlias = fmt::sprintf_ne(\"file%d-antialias\", i);\n            std::string keyBitmapSize = fmt::sprintf_ne(\"file%d-bitmap-size\", i);\n            std::string keyFontName = fmt::sprintf_ne(\"file%d-font-name\", i);\n            std::string keyDoublePixel = fmt::sprintf_ne(\"file%d-double-pixel\", i);\n            std::string keyLangsPriority = fmt::sprintf_ne(\"file%d-lang-priority\", i);\n\n            if(!overrider.hasKey(key))\n                break; // Stop look up on a first missing key\n\n            std::string fontFile;\n            overrider.read(key.c_str(), fontFile, \"\");\n\n            if(fontFile.empty())\n                continue;\n\n            bool antiAlias;\n            overrider.read(keyAntiAlias.c_str(), antiAlias, true);\n\n            int bitmapSize;\n            overrider.read(keyBitmapSize.c_str(), bitmapSize, 0);\n\n            std::string fontName;\n            overrider.read(keyFontName.c_str(), fontName, \"\");\n\n            bool doublePixel;\n            // FIXME: add XRender::RENDER_1X flag, use that instead\n#if defined(__WII__) || defined(__3DS__)\n            const bool doublePixelDefault = true;\n#else\n            const bool doublePixelDefault = false;\n#endif\n            overrider.read(keyDoublePixel.c_str(), doublePixel, doublePixelDefault);\n\n            std::string langsPriority;\n            overrider.read(keyLangsPriority.c_str(), langsPriority, std::string());\n\n            std::string fontPath = fonts_root.getCurDir() + sSubDir + fontFile;\n\n            outTtfFonts.emplace_back();\n            TtfFont& tf = outTtfFonts.back();\n\n            tf.setAntiAlias(antiAlias);\n            tf.setBitmapSize(bitmapSize);\n            tf.setDoublePixel(doublePixel);\n            tf.setLanguages(langsPriority);\n\n            pLogDebug(\"Loading TTF font %s...\", fontPath.c_str());\n            tf.loadFont(fontPath);\n            if(!tf.isLoaded())   //Pop broken font from array\n            {\n                pLogWarning(\"Failed to load the font %s\", fontPath.c_str());\n                outTtfFonts.pop_back();\n            }\n            else   //Register font name in a table\n            {\n                if(!fontName.empty()) // Set the custom font name\n                    tf.setFontName(fontName);\n\n                registerFont(&tf);\n                // Set the default TTF font (as a fallback)\n                if(!g_defaultTtfFont)\n                    g_defaultTtfFont = &tf;\n            }\n        }\n    }\n\n    overrider.endGroup();\n#endif\n\n    if(overrider.beginGroup(\"font-overrides\"))\n    {\n        auto k = overrider.allKeys();\n        for(auto &key : k)\n        {\n            std::string name;\n\n            if(key.find(\"-size\") != std::string::npos)\n                continue; // Skip the size key\n\n            overrider.read(key.c_str(), name, \"\");\n            if(name.empty())\n                continue;\n\n            int iKey = std::atoi(key.c_str());\n            if(iKey >= 0 && iKey < c_smbxFontsMapMax)\n                s_smbxFontsMap[iKey] = getFontID(name);\n\n            std::string size_key = fmt::format_ne(\"{0}-size\", iKey);\n            if(overrider.hasKey(size_key))\n                overrider.read(size_key.c_str(), s_smbxFontsSizesMap[iKey], -1);\n        }\n    }\n\n    overrider.endGroup();\n\n    return true;\n}\n\nstatic void s_initFontsFallback()\n{\n    for(int i = 1; i <= 4; ++i)\n    {\n        g_legacyFonts.emplace_back(i);\n        LegacyFont& lf = g_legacyFonts.back();\n\n        if(!lf.isLoaded())   //Pop broken font from array\n        {\n            pLogWarning(\"Failed to load the legacy font %d\", i);\n            g_legacyFonts.pop_back();\n        }\n        else   //Register font name in a table\n        {\n            registerFont(&lf);\n            s_smbxFontsMap[i] = FontManager::getFontID(lf.getFontName());\n        }\n    }\n\n    if(!g_legacyFonts.empty())\n        g_defaultRasterFont = &g_legacyFonts.front();\n\n    s_fontMapsDefault = true;\n    s_fontMapsWorld = true;\n    g_fontManagerIsInit = true;\n    g_fontManagerIsLegacy = true;\n}\n\n\nvoid FontManager::initFallback()\n{\n    if(g_fontManagerIsInit)\n        return;\n\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    g_defaultTtfFont = nullptr;\n#endif\n\n    g_double_pixled = false; //ConfigManager::setup_fonts.double_pixled;\n\n    for(int i = 0; i < c_smbxFontsMapMax; ++i)\n    {\n        s_smbxFontsMap[i] = -1;\n        s_smbxFontsSizesMap[i] = -1;\n    }\n\n    s_initFontsFallback();\n\n    // use fallback font for everything\n    for(int i = 0; i < c_smbxFontsMapMax; ++i)\n    {\n        s_smbxFontsMap[i] = s_smbxFontsMap[4];\n        s_smbxFontsSizesMap[i] = s_smbxFontsSizesMap[4];\n    }\n}\n\nvoid FontManager::initFull()\n{\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    if(!g_ft)\n        initializeFreeType();\n    g_defaultTtfFont = nullptr;\n#endif\n\n    g_double_pixled = false; //ConfigManager::setup_fonts.double_pixled;\n\n    SDL_memset(s_smbxFontsMap, 0, sizeof(s_smbxFontsMap));\n    SDL_memset(s_smbxFontsSizesMap, 0, sizeof(s_smbxFontsSizesMap));\n\n    for(int i = 0; i < c_smbxFontsMapMax; ++i)\n    {\n        s_smbxFontsMap[i] = -1;\n        s_smbxFontsSizesMap[i] = -1;\n    }\n\n    const std::string fonts_root = AppPathManager::assetsRoot() + \"fonts\";\n    if(!DirMan::exists(fonts_root))\n    {\n        pLogWarning(\"Can't load fonts as directory %s does not exists. Built-in fallback fonts will be loaded.\", fonts_root.c_str());\n        s_initFontsFallback();\n        return;\n    }\n\n    DirListCI fontsRootCI(fonts_root);\n\n    if(!s_loadFontsFromDir(fontsRootCI, std::string(), g_rasterFonts, LOADFONTARG(g_ttfFonts)))\n    {\n        pLogWarning(\"Can't load any font from the directory %s. Built-in fallback fonts will be loaded.\", fonts_root.c_str());\n        s_initFontsFallback();\n        return;\n    }\n\n    if(g_anyFonts.empty())\n    {\n        pLogWarning(\"There is no available fonts at the directory %s. Fallback fonts will be loaded.\", fonts_root.c_str());\n        s_initFontsFallback();\n        return;\n    }\n\n    s_fontMapsDefault = true;\n    s_fontMapsWorld = true;\n    g_fontManagerIsInit = true;\n    g_fontManagerIsLegacy = false;\n}\n\nvoid FontManager::quit()\n{\n    clearAllCustomFonts();\n\n    g_fontNameToId.clear();\n    g_anyFonts.clear();\n\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    g_ttfFonts.clear();\n    g_defaultTtfFont = nullptr;\n#endif\n\n    g_rasterFonts.clear();\n    g_defaultRasterFont = nullptr;\n\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    closeFreeType();\n#endif\n}\n\nvoid FontManager::updateDefaultFontByLang(const std::string &lang, const std::string &country)\n{\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    g_defaultTtfFont = nullptr;\n\n    if(g_ttfFonts.empty())\n        return; // Nothing to choose\n\n    // Just use a first font in the list by default\n    g_defaultTtfFont = &g_ttfFonts.front();\n\n    for(auto &f : g_ttfFonts)\n    {\n        if(!country.empty() && f.hasLanguage(fmt::sprintf_ne(\"%s-%s\", lang.c_str(), country.c_str())))\n        {\n            g_defaultTtfFont = &f;\n            break;\n        }\n\n        if(f.hasLanguage(lang))\n        {\n            g_defaultTtfFont = &f;\n            break;\n        }\n    }\n#else\n    (void)lang;\n    (void)country;\n#endif\n}\n\nint FontManager::getMetricsValue(UIMetricsCategory category, const std::string &lang)\n{\n#ifndef THEXTECH_ENABLE_TTF_SUPPORT\n    UNUSED(lang);\n#endif\n    // FIXME: Implement the INI-side table of rules instead of hardcoding that\n    // That might work differently if different fonts are used.\n    // Hardcoded rules are fine for content that lacks them\n\n    switch(category)\n    {\n    case Metrics_MenuMinLineHeight:\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n        if(lang == \"zh\")\n            return 26;\n        else if(lang == \"ko\" || lang == \"ja\")\n            return 22;\n#endif\n        return 18;\n\n    default:\n            break;\n    }\n\n    return 18;\n}\n\nvoid FontManager::loadCustomFonts()\n{\n    backupDefaultFontMaps();\n    bool doLoadWorld = false;\n    bool doLoadCustom = false;\n\n    const std::string& episodeRoot = g_dirEpisode.getCurDir();\n    const std::string& dataSubDir = g_dirCustom.getCurDir();\n\n    // Loading fonts from the episode directory\n    if(s_lastWorldFontsPath != episodeRoot && episodeRoot != AppPathManager::assetsRoot())\n    {\n        if(!s_lastWorldFontsPath.empty())\n            clearAllCustomFonts();\n        doLoadWorld = true;\n        pLogDebug(\"Attempt to load custom fonts at %sfonts\", episodeRoot.c_str());\n    }\n    else\n        pLogDebug(\"Custom fonts at %sfonts already loaded\", episodeRoot.c_str());\n\n    if(doLoadWorld && s_loadFontsFromDir(g_dirEpisode,\n                                         \"fonts\",\n                                         g_rasterFontsCustom,\n                                         LOADFONTARG(g_ttfFontsCustom)))\n    {\n        s_lastWorldFontsPath = episodeRoot;\n        pLogDebug(\"Loaded custom fonts from the %sfonts\", episodeRoot.c_str());\n        s_fontMapsDefault = false;\n        s_fontMapsWorld = true;\n        backupWorldFontMaps();\n    }\n\n    // Loading fonts from the data directory\n    if(s_lastCustomFontsPath != dataSubDir)\n    {\n        if(!s_lastCustomFontsPath.empty())\n            clearLevelFonts();\n        doLoadCustom = true;\n        pLogDebug(\"Attempt to load custom fonts at %sfonts\", dataSubDir.c_str());\n    }\n    else\n        pLogDebug(\"Custom fonts at %sfonts already loaded\", dataSubDir.c_str());\n\n    if(doLoadCustom && s_loadFontsFromDir(g_dirCustom,\n                                         \"fonts\",\n                                          g_rasterFontsCustomLevel,\n                                          LOADFONTARG(g_ttfFontsCustomLevel)))\n    {\n        s_lastCustomFontsPath = dataSubDir;\n        pLogDebug(\"Loaded custom fonts from the %sfonts\", dataSubDir.c_str());\n        s_fontMapsDefault = false;\n        s_fontMapsWorld = false;\n    }\n}\n\nvoid FontManager::clearAllCustomFonts()\n{\n    g_rasterFontsCustomLevel.clear();\n    g_rasterFontsCustom.clear();\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    g_ttfFontsCustomLevel.clear();\n    g_ttfFontsCustom.clear();\n#endif\n    s_lastCustomFontsPath.clear();\n    s_lastWorldFontsPath.clear();\n    restoreDefaultFontMaps();\n}\n\nvoid FontManager::clearLevelFonts()\n{\n    g_rasterFontsCustomLevel.clear();\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    g_ttfFontsCustomLevel.clear();\n#endif\n\n    s_lastCustomFontsPath.clear();\n    restoreWorldFontMaps();\n}\n\nbool FontManager::isInitied()\n{\n    return g_fontManagerIsInit;\n}\n\nbool FontManager::isLegacy()\n{\n    return g_fontManagerIsLegacy;\n}\n\n\nPGE_Size FontManager::textSize(const char* text, size_t text_size, int fontID,\n                               uint32_t ttfFontSize)\n{\n    SDL_assert_release(g_fontManagerIsInit);// Font manager is not initialized!\n#ifndef THEXTECH_ENABLE_TTF_SUPPORT\n    (void)ttfFontSize;\n#endif\n\n    if(!g_fontManagerIsInit)\n        return PGE_Size(27 * 20, static_cast<int>(std::count(text, text + text_size, '\\n') + 1) * 20);\n\n    if(!text || text_size == 0)\n        return PGE_Size(0, 0);\n\n    //Use one of loaded fonts\n    if((fontID >= 0) && (static_cast<size_t>(fontID) < g_anyFonts.size()) && g_anyFonts[fontID])\n    {\n        if(g_anyFonts[fontID]->isLoaded())\n            return g_anyFonts[fontID]->textSize(text, text_size, ttfFontSize);\n    }\n\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    if(g_defaultTtfFont && g_defaultTtfFont->isLoaded())\n        return g_defaultTtfFont->textSize(text, text_size, ttfFontSize);\n#endif\n\n    return PGE_Size(27 * 20, static_cast<int>(std::count(text, text + text_size, '\\n') + 1) * 20);\n}\n\nPGE_Size FontManager::glyphSize(const char* utf8char, uint32_t charNum, int fontId, uint32_t ttf_fontSize)\n{\n    SDL_assert_release(g_fontManagerIsInit);// Font manager is not initialized!\n#ifndef THEXTECH_ENABLE_TTF_SUPPORT\n    (void)ttf_fontSize;\n#endif\n\n    //Use one of loaded fonts\n    if((fontId >= 0) && (static_cast<size_t>(fontId) < g_anyFonts.size()) && g_anyFonts[fontId])\n    {\n        if(g_anyFonts[fontId]->isLoaded())\n            return g_anyFonts[fontId]->glyphSize(utf8char, charNum, ttf_fontSize);\n    }\n\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n    if(g_defaultTtfFont && g_defaultTtfFont->isLoaded())\n        return g_defaultTtfFont->glyphSize(utf8char, charNum, ttf_fontSize);\n#endif\n\n    return PGE_Size(27 * 20, 1 * 20);\n}\n\nint FontManager::getFontID(std::string fontName)\n{\n    FontsHash::iterator i = g_fontNameToId.find(fontName);\n    if(i == g_fontNameToId.end())\n        return DefaultRaster;\n    else\n        return i->second;\n}\n\nPGE_Size FontManager::printText(const char* text, size_t text_size,\n                                int x, int y,\n                                int font,\n                                XTColor color,\n                                uint32_t ttf_FontSize, bool outline,\n                                XTColor outline_color,\n                                CropInfo* crop_info)\n{\n    if(!g_fontManagerIsInit)\n        return PGE_Size(0, 0);\n\n    if(!text || text_size == 0)\n        return PGE_Size(0, 0);\n\n    BaseFontEngine* font_engine = nullptr;\n\n    if((font >= 0) && (static_cast<size_t>(font) < g_anyFonts.size()) && g_anyFonts[font])\n    {\n        if(g_anyFonts[font]->isLoaded())\n            font_engine = g_anyFonts[font];\n    }\n\n    if(!font_engine)\n    {\n        switch(font)\n        {\n        case DefaultRaster:\n            if(g_defaultRasterFont && g_defaultRasterFont->isLoaded())\n            {\n                font_engine = g_defaultRasterFont;\n                break;\n            } /*fallthrough*/\n        case DefaultTTF_Font:\n        default:\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n            if(g_defaultTtfFont && g_defaultTtfFont->isLoaded())\n            {\n                font_engine = g_defaultTtfFont;\n\n                if(ttf_FontSize == 0)\n                    ttf_FontSize = 14;\n            }\n#endif\n            break;\n        }\n\n        if(!font_engine)\n        {\n            pLogWarning(\"Attempt to print text [%s] without any font being loaded\", text);\n            return PGE_Size(0, 0);\n        }\n    }\n\n    if(outline)\n    {\n        // take square of Alpha to match blend of normal text\n        uint8_t scaled_a = XTColor::mul(color.a, color.a);\n        if(crop_info)\n            crop_info->for_outline = true;\n\n        font_engine->printText(text, text_size, x - 2, y, color.with_alpha(scaled_a) * outline_color, ttf_FontSize, crop_info);\n        font_engine->printText(text, text_size, x + 2, y, color.with_alpha(scaled_a) * outline_color, ttf_FontSize, crop_info);\n        font_engine->printText(text, text_size, x, y - 2, color.with_alpha(scaled_a) * outline_color, ttf_FontSize, crop_info);\n        font_engine->printText(text, text_size, x, y + 2, color.with_alpha(scaled_a) * outline_color, ttf_FontSize, crop_info);\n\n        if(crop_info)\n            crop_info->for_outline = false;\n    }\n\n    return font_engine->printText(text, text_size, x, y, color, ttf_FontSize, crop_info);\n}\n\nPGE_Size FontManager::optimizeText(std::string &text, size_t max_columns)\n{\n    /****************Word wrap*********************/\n    size_t  lastspace = 0;\n    int     count = 1;\n    size_t  maxWidth = 0;\n\n    for(size_t x = 0, i = 0; i < text.size(); i++, x++)\n    {\n        switch(text[i])\n        {\n        case '\\t':\n        case ' ':\n            lastspace = i;\n            break;\n\n        case '\\n':\n            lastspace = 0;\n            if((maxWidth < x) && (maxWidth < max_columns))\n                maxWidth = x;\n            x = 0;\n            count++;\n            break;\n        }\n\n        if(x >= max_columns) //If lenght more than allowed\n        {\n            maxWidth = x;\n\n            if(lastspace > 0)\n            {\n                text[lastspace] = '\\n';\n                i = lastspace - 1;\n                lastspace = 0;\n            }\n            else\n            {\n                text.insert(i, 1, '\\n');\n                x = 0;\n                count++;\n            }\n        }\n\n        UTF8 uch = static_cast<unsigned char>(text[i]);\n        i += static_cast<size_t>(trailingBytesForUTF8[uch]);\n    }\n\n    if(count == 1)\n        maxWidth = text.length();\n\n    /****************Word wrap*end*****************/\n\n    return PGE_Size(count, static_cast<int>(maxWidth));\n}\n\n\nPGE_Size FontManager::printTextOptiCol(std::string text,\n                                   int x, int y,\n                                   size_t max_columns,\n                                   int font,\n                                   XTColor color,\n                                   uint32_t ttf_FontSize)\n{\n    PGE_Size ret = FontManager::optimizeText(text, max_columns);\n    FontManager::printText(text.c_str(), text.size(),\n                           x, y,\n                           font,\n                           color,\n                           ttf_FontSize);\n    return ret;\n}\n\nPGE_Size FontManager::printTextOptiPx(std::string text,\n                                  int x, int y,\n                                  size_t max_pixels_lenght,\n                                  int font,\n                                  XTColor color,\n                                  uint32_t ttf_FontSize)\n{\n    PGE_Size ret = FontManager::optimizeTextPx(text, max_pixels_lenght, font, ttf_FontSize);\n    FontManager::printText(text.c_str(), text.size(),\n                           x, y,\n                           font,\n                           color,\n                           ttf_FontSize);\n    return ret;\n}\n\nPGE_Size FontManager::optimizeTextPx(std::string& text,\n                                     size_t max_pixels_lenght,\n                                     int fontId,\n                                     uint32_t ttf_FontSize)\n{\n    /****************Word wrap*********************/\n    size_t  lastspace = 0;\n    // int     count = 1;\n    size_t  height = 0;\n    size_t  maxWidth = 0;\n    size_t  maxWidthAtSpace = 0;\n    size_t  pixelWidth = 0;\n    size_t  pixelWidthPrev = 0;\n    BaseFontEngine *font = nullptr;\n    PGE_Size gs;\n\n    int line_h = 0;\n    int line_h_at_space = 0;\n\n    if((fontId < 0) || (static_cast<size_t>(fontId) >= g_anyFonts.size()) || !g_anyFonts[fontId])\n        return PGE_Size(0, 0); // Invalid font\n\n    font = g_anyFonts[fontId];\n\n    for(size_t x = 0, i = 0; i < text.size(); i++, x++)\n    {\n        switch(text[i])\n        {\n        case '\\t':\n        case ' ':\n            lastspace = i;\n            maxWidthAtSpace = pixelWidth;\n            gs = font->glyphSize(&text[i], x, ttf_FontSize);\n            pixelWidthPrev = pixelWidth;\n            pixelWidth += gs.w();\n\n            if(line_h < gs.h())\n                line_h = gs.h();\n\n            line_h_at_space = line_h;\n\n            break;\n\n        case '\\n':\n            lastspace = 0;\n            maxWidthAtSpace = 0;\n\n            if((maxWidth < pixelWidth) && (maxWidth < max_pixels_lenght))\n                maxWidth = pixelWidth;\n\n            x = 0;\n            pixelWidth = 0;\n            pixelWidthPrev = 0;\n\n            if(line_h < gs.h())\n                line_h = gs.h();\n\n            height += line_h;\n            line_h = 0;\n\n            // count++;\n            break;\n\n        default:\n            pixelWidthPrev = pixelWidth;\n            gs = font->glyphSize(&text[i], x, ttf_FontSize);\n            pixelWidth += gs.w();\n\n            if(line_h < gs.h())\n                line_h = gs.h();\n\n            break;\n        }\n\n        if(pixelWidth >= max_pixels_lenght) //If lenght more than allowed\n        {\n            // don't line break if the last character would exactly fill the last line (this is the SMBX 1.3 logic)\n            if(pixelWidth == max_pixels_lenght && i + trailingBytesForUTF8[(UTF8)text[i]] + 1 == text.size())\n            {\n            }\n            else if(lastspace > 0)\n            {\n                if(maxWidth < maxWidthAtSpace)\n                    maxWidth = maxWidthAtSpace;\n                text[lastspace] = '\\n';\n                i = lastspace - 1;\n                lastspace = 0;\n                line_h = line_h_at_space;\n            }\n            else\n            {\n                if(maxWidth < pixelWidthPrev)\n                    maxWidth = pixelWidthPrev;\n                text.insert(i, 1, '\\n');\n                x = 0;\n                pixelWidth = 0;\n\n                height += line_h;\n                line_h = 0;\n                // count++;\n            }\n        }\n\n        UTF8 uch = static_cast<unsigned char>(text[i]);\n        i += static_cast<size_t>(trailingBytesForUTF8[uch]);\n    }\n\n    height += line_h;\n\n    if(maxWidth < pixelWidth)\n        maxWidth = pixelWidth;\n\n    /****************Word wrap*end*****************/\n\n    return PGE_Size(maxWidth, height);\n}\n\nstd::string FontManager::cropText(std::string text, size_t max_symbols)\n{\n    if(max_symbols == 0)\n        return text;\n\n    size_t utf8len = utf8_substrlen(text, max_symbols);\n    if(text.size() > utf8len)\n    {\n        text.erase(utf8len, text.size() - utf8len);\n        text.append(\"...\");\n    }\n\n    return text;\n}\n\nbool FontManager::hasTtfSupport()\n{\n    return (bool)getDefaultTtfFont();\n}\n"
  },
  {
    "path": "src/fontman/font_manager.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef FONT_MANAGER_H\n#define FONT_MANAGER_H\n\n#include \"std_picture.h\"\n#include <Graphics/size.h>\n#include <Utils/vptrlist.h>\n\n#include <string>\n\n#include \"fontman/font_engine_base.h\"\n#include \"fontman/crop_info.h\"\n\nnamespace FontManager\n{\n/*\n * UTF-8 helpers\n */\nsize_t       utf8_strlen(const char *str);\nsize_t       utf8_strlen(const char *str, size_t len);\nsize_t       utf8_strlen(const std::string &str);\n/**\n * @brief Get byte lenght from character lenght from begin to utf8len\n * @param str input UTF-8 string\n * @param utf8len Desireed character lenght\n * @return Byte-lenght of desired character lenght\n */\nsize_t       utf8_substrlen(const std::string &str, size_t utf8len);\nstd::string  utf8_substr(const std::string &str, size_t utf8_begin, size_t utf8_len);\nconst char*  utf8_skip_begin(const char *str, size_t utf8_begin);\nvoid         utf8_pop_back(std::string &str);\nvoid         utf8_erase_at(std::string &str, size_t begin);\nvoid         utf8_erase_before(std::string &str, size_t end);\n\n//! Fallback initialization of font rendering including only the fallback font\nvoid initFallback();\n//! Full initialization of font renderith include external fonts\nvoid initFull();\n//! De-Initialize font manager and clear memory\nvoid quit();\n\n/**\n * @brief Once language had been toggled, a different default TTF font may be chosen\n * \\param lang Language code\n * \\param country Country code (may be empty)\n */\nvoid updateDefaultFontByLang(const std::string &lang, const std::string &country);\n\n/*!\n * \\brief List of UI metrics categories\n */\nenum UIMetricsCategory\n{\n    Metrics_MenuMinLineHeight = 0,\n    // FIXME: Implement other categories\n};\n\n/*!\n * \\brief Gets the per language UI metrics value\n * \\param category Metrics category index\n * \\param lang Current language\n * \\return Height in pixels\n */\nint getMetricsValue(UIMetricsCategory category, const std::string &lang);\n\n/**\n * @brief Attempts to load custom fonts for the episode and for the level\n */\nvoid loadCustomFonts();\n\n/**\n * @brief Removes all custom fonts completely\n */\nvoid clearAllCustomFonts();\n\n/**\n * @brief Removes all level-wide fonts, but keeps episode-wide fonts being loaded\n */\nvoid clearLevelFonts();\n\n/*!\n * \\brief Checks if font manager works\n * \\return true when manager actually works\n */\nbool isInitied();\n\n/*!\n * \\brief Checks if font manager uses legacy fonts fallback\n * \\return true when manager uses legacy fonts fallback\n */\nbool isLegacy();\n\nint fontIdFromSmbxFont(int font);\nuint32_t fontSizeFromSmbxFont(int font);\n\n/**\n * @brief Automatical font choice\n */\nenum DefaultFont\n{\n    //! Using default TTF font\n    DefaultTTF_Font = -2,\n    //! Using default raster font\n    DefaultRaster   = -1\n};\n\n/**\n * @brief Get pixel metrics of the text block\n * @param text Text fragment to measure\n * @param text_size The byte size of the text fragment to print\n * @param fontID Font-ID\n * @param ttfFontPixelSize Point size for the TTF fonts\n * @return\n */\nPGE_Size textSize(const char *text, size_t text_size,\n                  int       fontID,\n                  uint32_t  ttfFontSize = 14);\n\n\n/**\n * @brief Get the size of one glyph\n * @param utf8char The UTF-8 character to measure\n * @param charNum The row number of the character in the line (affects the size of tabulation)\n * @param fontId Font ID\n * @param ttf_fontSize Point size for the TTF fonts\n * @return\n */\nPGE_Size glyphSize(const char *utf8char, uint32_t charNum, int fontId, uint32_t ttf_fontSize = 14);\n\n/**\n * @brief Retreive Font-ID from the font name\n * @param fontName Name of the font\n * @return FontID key\n */\nint      getFontID(std::string fontName);\n\n/**\n * @brief Print the text fragment to the screen\n * @param text Text fragment to print\n * @param text_size The byte size of the text fragment to print\n * @param x X position on the screen\n * @param y Y position on the screen\n * @param font Font-ID\n * @param Red Red color level from 0.0 to 1.0\n * @param Green Green color level from 0.0 to 1.0\n * @param Blue Blue color level from 0.0 to 1.0\n * @param Alpha Alpha-channel level from 0.0 to 1.0\n * @param ttf_FontSize Point size for the TTF fonts, expected height of line (if non-zero) for raster fonts\n * @param outline Render using added outline\n * @param crop_info nullable pointer to info for crop (marquee) logic.\n * @return The size of text block in pixels\n */\nPGE_Size printText(const char* text, size_t text_size,\n                   int x, int y,\n                   int font = DefaultRaster,\n                   XTColor color = XTColor(),\n                   uint32_t ttf_FontSize = 0, bool outline = false,\n                   XTColor outline_color = {0, 0, 0},\n                   CropInfo* crop_info = nullptr);\n\n/**\n * @brief Optimize and print the text fragment to the screen\n * @param text Text fragment to print\n * @param x X position on the screen\n * @param y Y position on the screen\n * @param max_columns The maximum line length in characters (columns)\n * @param font The Font-ID\n * @param Red Red color level from 0.0 to 1.0\n * @param Green Green color level from 0.0 to 1.0\n * @param Blue Blue color level from 0.0 to 1.0\n * @param Alpha Alpha-channel level from 0.0 to 1.0\n * @param ttf_FontSize Point size for the TTF fonts\n * @return The size of text block in columns/rows\n */\nPGE_Size printTextOptiCol(std::string text,\n                          int x, int y,\n                          size_t max_columns,\n                          int font = DefaultRaster,\n                          XTColor color = XTColor(),\n                          uint32_t ttf_FontSize = 14);\n\n/**\n * @brief Optimize and print the text fragment to the screen\n * @param text Text fragment to print\n * @param x X position on the screen\n * @param y Y position on the screen\n * @param max_pixels_lenght The maximum line length in pixels\n * @param font The Font-ID\n * @param Red Red color level from 0.0 to 1.0\n * @param Green Green color level from 0.0 to 1.0\n * @param Blue Blue color level from 0.0 to 1.0\n * @param Alpha Alpha-channel level from 0.0 to 1.0\n * @param ttf_FontSize Point size for the TTF fonts\n * @return The size of text block in pixels\n */\nPGE_Size printTextOptiPx(std::string text,\n                         int x, int y,\n                         size_t max_pixels_lenght,\n                         int font = DefaultRaster,\n                         XTColor color = XTColor(),\n                         uint32_t ttf_FontSize = 14);\n\n/**\n * @brief Optimise the given text fragment to fit it into the given columns count.\n * @param [_inout] text Text to optimize\n * @param [_in]  max_columns The maximum line length in characters (columns)\n * @return Computed size of the printed text in columns and lines\n *\n * Suitable with monospace fonts.\n */\nPGE_Size       optimizeText(std::string &text, size_t max_columns);\n\n/**\n * @brief Optimise the given text fragment to fit it into the given pixels width\n * @param [_inout] text Text to optimize (the result will be writtein back to here)\n * @param [_in]  max_pixels_lenght The maximum line length in pixels\n * @param [_in]  fontId font ID\n * @return Computed pixels size of the printed text fragment\n *\n * Can be used with any kind of the font\n */\nPGE_Size    optimizeTextPx(std::string &text,\n                           size_t max_pixels_lenght,\n                           int fontId,\n                           uint32_t ttf_FontSize = 14);\n\n/**\n * @brief Strip text outing of max count of symbols\n * @param text input text\n * @param max_symbols Maximal character count limit\n * @return Begin of string which is fit into desired lenght limit\n */\nstd::string     cropText(std::string text, size_t max_symbols);\n\n/**\n * @brief Checks for TTF font support\n * @return True if any TTF font is loaded\n */\nbool            hasTtfSupport();\n\n} //namespace FontManager\n\n#endif // FONT_MANAGER_H\n"
  },
  {
    "path": "src/fontman/font_manager_private.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include \"font_manager_private.h\"\n\n/*\n * Index into the table below with the first byte of a UTF-8 sequence to\n * get the number of trailing bytes that are supposed to follow it.\n * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is\n * left as-is for anyone who may want to do such conversion, which was\n * allowed in earlier algorithms.\n */\nconst char trailingBytesForUTF8[256] =\n{\n    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\n    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\n    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\n    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\n    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\n    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,\n    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,\n    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5\n};\n\n/*\n * Magic values subtracted from a buffer value during UTF8 conversion.\n * This table contains as many values as there might be trailing bytes\n * in a UTF-8 sequence.\n */\nconst UTF32 offsetsFromUTF8[6] =\n{\n    0x00000000UL, 0x00003080UL, 0x000E2080UL,\n    0x03C82080UL, 0xFA082080UL, 0x82082080UL\n};\n\nchar32_t get_utf8_char(const char *str)\n{\n    const UTF8 *source = reinterpret_cast<const UTF8*>(str);\n    UTF32 ch = 0;\n    uint16_t extraBytesToRead = static_cast<uint16_t>(trailingBytesForUTF8[*source]);\n\n    switch (extraBytesToRead)\n    {\n    case 5: ch += *source++; ch <<= 6; /*fallthrough*/ /* remember, illegal UTF-8 */\n    case 4: ch += *source++; ch <<= 6; /*fallthrough*/ /* remember, illegal UTF-8 */\n    case 3: ch += *source++; ch <<= 6; /*fallthrough*/\n    case 2: ch += *source++; ch <<= 6; /*fallthrough*/\n    case 1: ch += *source++; ch <<= 6; /*fallthrough*/\n    case 0: ch += *source++; /*fallthrough*/\n    }\n    ch -= offsetsFromUTF8[extraBytesToRead];\n    return static_cast<char32_t>(ch);\n}\n\nstd::u32string std_to_utf32(const std::string &src)\n{\n    std::u32string dst_tmp;\n    dst_tmp.resize(src.size());\n    const UTF8* src_begin = reinterpret_cast<const UTF8*>(src.c_str());\n    const UTF8* src_end   = src_begin + src.size();\n    UTF32*      dst_begin = reinterpret_cast<UTF32*>(&dst_tmp[0]);\n    UTF32*      dst_end   = dst_begin + dst_tmp.size();\n    PGEFF_ConvertUTF8toUTF32(&src_begin, src_end, &dst_begin, dst_end, strictConversion);\n    return std::u32string(reinterpret_cast<char32_t*>(&dst_tmp[0]));\n}\n"
  },
  {
    "path": "src/fontman/font_manager_private.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef FONT_MANAGER_PRIVATE_H\n#define FONT_MANAGER_PRIVATE_H\n\n#include <PGE_File_Formats/ConvertUTF.h>\n#include <stdint.h>\n#include <string>\n\ninline uint32_t char2int(const char32_t &ch)\n{\n    return static_cast<uint32_t>(ch - U'0');\n}\n\n/* //Currently unused, uncommend when it needed\nstatic int char2int(const char& ch)\n{\n    return ch - '0';\n}\n*/\n\n/*\n * Index into the table below with the first byte of a UTF-8 sequence to\n * get the number of trailing bytes that are supposed to follow it.\n * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is\n * left as-is for anyone who may want to do such conversion, which was\n * allowed in earlier algorithms.\n */\nextern const char trailingBytesForUTF8[256];\n\n/*\n * Magic values subtracted from a buffer value during UTF8 conversion.\n * This table contains as many values as there might be trailing bytes\n * in a UTF-8 sequence.\n */\nextern const UTF32 offsetsFromUTF8[6];\n\n/**\n * @brief Get single UTF32 character from firs character of UTF8 string\n * @param str pointer to position on UTF8 string where need to find a character\n * @return UTF32 character\n */\nextern char32_t get_utf8_char(const char *str);\n\n/**\n * @brief Converts UTF8 string into UTF32 string\n * @param src Source string in UTF8\n * @return UTF32 string\n */\nextern std::u32string std_to_utf32(const std::string &src);\n\n\nclass BaseFontEngine;\nclass TtfFont;\n\nnamespace FontManager\n{\n    TtfFont* getDefaultTtfFont();\n    TtfFont* getTtfFontByName(const std::string &fontName);\n}\n\n#endif // FONT_MANAGER_PRIVATE_H\n"
  },
  {
    "path": "src/fontman/hardcoded_font.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <vector>\n\n#include \"fontman/hardcoded_font.h\"\n\n#include \"core/render.h\"\n\n// The monochromic 1-bit texture of the 8x749 size\nconst std::array<uint8_t, 749> c_hardcoded_font_bytes =\n{\n    0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00, 0x00, 0x24, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x7E, 0x24,\n    0x7E, 0x24, 0x00, 0x00, 0x00, 0x38, 0x6C, 0x3B, 0x6E, 0xC6, 0x7B, 0x00, 0x62, 0x66, 0x0C, 0x18, 0x30, 0x66, 0xC6, 0x00,\n    0x00, 0x38, 0x6C, 0x3B, 0x6E, 0xC6, 0x7B, 0x00, 0x00, 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x18,\n    0x18, 0x18, 0x0C, 0x00, 0x00, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x6C, 0x7C, 0x38, 0x7C, 0x6C, 0x00, 0x00,\n    0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x7E,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00, 0x00,\n    0x00, 0x3C, 0x66, 0x6E, 0x76, 0x66, 0x3C, 0x00, 0x00, 0x18, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x3C, 0x66, 0x06,\n    0x3C, 0x60, 0x7E, 0x00, 0x00, 0x3C, 0x66, 0x0C, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x60, 0x6C, 0x6C, 0x7E, 0x0C, 0x0C, 0x00,\n    0x00, 0x7E, 0x40, 0x7C, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x3C, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x7E, 0x0C, 0x0C,\n    0x18, 0x18, 0x18, 0x00, 0x00, 0x3C, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0x00,\n    0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x10, 0x00, 0x00, 0x0C, 0x18, 0x30,\n    0x18, 0x0C, 0x00, 0x00, 0x00, 0x3E, 0x3E, 0x00, 0x3E, 0x3E, 0x00, 0x00, 0x00, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x00, 0x00,\n    0x00, 0x3C, 0x66, 0x06, 0x1C, 0x00, 0x18, 0x00, 0x00, 0x3C, 0x66, 0x6E, 0x6C, 0x60, 0x3E, 0x00, 0x00, 0x3C, 0x66, 0x66,\n    0x7E, 0x66, 0x66, 0x00, 0x00, 0x7C, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x3C, 0x66, 0x60, 0x60, 0x66, 0x3C, 0x00,\n    0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x7E, 0x60, 0x78, 0x60, 0x60, 0x7E, 0x00, 0x00, 0x7E, 0x60, 0x78,\n    0x60, 0x60, 0x60, 0x00, 0x00, 0x3C, 0x66, 0x60, 0x6E, 0x66, 0x3C, 0x00, 0x00, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00,\n    0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x3E, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x00, 0x00, 0x66, 0x6C, 0x78,\n    0x78, 0x6C, 0x66, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7C, 0x00, 0x00, 0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x00,\n    0x00, 0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x7C, 0x66, 0x66,\n    0x7C, 0x60, 0x60, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x6C, 0x3A, 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x00,\n    0x00, 0x3C, 0x62, 0x3C, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66,\n    0x66, 0x66, 0x3C, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00,\n    0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x42, 0x00, 0x00, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00, 0x00, 0x7E, 0x0C, 0x18,\n    0x30, 0x60, 0x7E, 0x00, 0x00, 0x1C, 0x18, 0x18, 0x18, 0x18, 0x1C, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x04, 0x00, 0x00,\n    0x00, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1C, 0x00, 0x1C, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x7E, 0x00, 0x00, 0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x66, 0x66, 0x66, 0x3B, 0x00,\n    0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C, 0x00, 0x06, 0x06, 0x3E, 0x66,\n    0x66, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x7C, 0x60, 0x3E, 0x00, 0x00, 0x0E, 0x18, 0x3C, 0x18, 0x18, 0x18, 0x00,\n    0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00, 0x18, 0x00, 0x18, 0x18,\n    0x18, 0x18, 0x18, 0x00, 0x0C, 0x00, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x60, 0x60, 0x66, 0x7C, 0x78, 0x6C, 0x66, 0x00,\n    0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1C, 0x00, 0x00, 0x00, 0xEC, 0xD6, 0xD6, 0xD6, 0xD6, 0x00, 0x00, 0x00, 0x7C, 0x66,\n    0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60,\n    0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x07, 0x06, 0x00, 0x00, 0x6C, 0x76, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x3C, 0x60,\n    0x3C, 0x06, 0x7C, 0x00, 0x00, 0x30, 0x7C, 0x30, 0x30, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3B, 0x00,\n    0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x6B, 0x6B, 0x6B, 0x6B, 0x37, 0x00, 0x00, 0x00, 0x66, 0x7E,\n    0x3C, 0x7E, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x7E, 0x00,\n    0x00, 0x0C, 0x18, 0x70, 0x18, 0x18, 0x0C, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x30, 0x18, 0x0E,\n    0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x30, 0x5A, 0x0C\n};\n\n#ifdef __16M__\n\nvoid LoadHardcodedFont(StdPicture& tx)\n{\n    tx.reset();\n\n    tx.inited = true;\n    tx.l.path = \"!F\";\n    tx.l.lazyLoaded = true;\n\n    tx.w = c_hardcoded_font_w * 2;\n    tx.h = c_hardcoded_font_h * 2;\n    tx.l.flags = 0;\n}\n\n#else // __16M__\n\nvoid LoadHardcodedFont(StdPicture& tx)\n{\n    tx.reset();\n\n    tx.w = c_hardcoded_font_w * 2;\n    tx.h = c_hardcoded_font_h * 2;\n\n    std::vector<uint32_t> image_data(c_hardcoded_font_bytes.size() * 8);\n\n    auto i = c_hardcoded_font_bytes.cbegin();\n    uint32_t* o = &image_data[0];\n\n    while(i != c_hardcoded_font_bytes.cend())\n    {\n        uint8_t row = *(i++);\n        *(o++) = (row & 0x80) ? 0xFFFFFFFF : 0;\n        *(o++) = (row & 0x40) ? 0xFFFFFFFF : 0;\n        *(o++) = (row & 0x20) ? 0xFFFFFFFF : 0;\n        *(o++) = (row & 0x10) ? 0xFFFFFFFF : 0;\n        *(o++) = (row & 0x08) ? 0xFFFFFFFF : 0;\n        *(o++) = (row & 0x04) ? 0xFFFFFFFF : 0;\n        *(o++) = (row & 0x02) ? 0xFFFFFFFF : 0;\n        *(o++) = (row & 0x01) ? 0xFFFFFFFF : 0;\n    }\n\n    XRender::loadTexture(tx, c_hardcoded_font_w, c_hardcoded_font_h, (uint8_t*)image_data.data(), c_hardcoded_font_w * 4);\n}\n\n#endif // __16M__\n"
  },
  {
    "path": "src/fontman/hardcoded_font.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef HARDCODED_FONT_H\n#define HARDCODED_FONT_H\n\n#include <array>\n#include <cstdint>\n\nstruct StdPicture;\n\n\n// each byte is a row of the font, most significant bit is left\nextern const std::array<uint8_t, 749> c_hardcoded_font_bytes;\n\nstatic constexpr int c_hardcoded_font_w = 8;\nstatic constexpr int c_hardcoded_font_h = 749; // 752 including padding\nstatic constexpr int c_hardcoded_font_h_pow2 = 1024;\n\nvoid LoadHardcodedFont(StdPicture& tx);\n\n#endif // #ifndef HARDCODED_FONT_H\n"
  },
  {
    "path": "src/fontman/legacy_font.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"hardcoded_font.h\"\n\n#include \"legacy_font.h\"\n#include \"gfx.h\"\n#include \"std_picture.h\"\n#include \"core/render.h\"\n#include \"font_manager_private.h\"\n\n#if defined(_MSC_VER) && _MSC_VER <= 1900 // Workaround for MSVC 2015\nnamespace std\n{\n    using ::toupper;\n}\n#endif\n\n\nLegacyFont::LegacyFont(int legacyFontId)\n{\n    loadFont(legacyFontId);\n}\n\nLegacyFont::~LegacyFont()\n{\n    m_charMap.clear();\n    SDL_memset(m_textures, 0, sizeof(m_textures));\n}\n\nvoid LegacyFont::loadFont(int fontId)\n{\n    m_charMap.clear();\n    SDL_memset(m_textures, 0, sizeof(m_textures));\n\n    switch(fontId)\n    {\n    default:\n    case 1: // Numbers font\n        m_glyphWidth = 18;\n        m_glyphHeight = 16;\n\n        for(int i = 0; i <= 9; ++i)\n        {\n            char uchar[5] = {static_cast<char>('0' + i), 0, 0, 0, 0};\n            m_textures[i] = &GFX.Font1[i];\n\n            RasChar c;\n            c.texId = i;\n            c.tX = 0;\n            c.tY = 0;\n            c.tW = 16;\n            c.tH = 14;\n            c.width = 18;\n            c.valid = true;\n\n            m_charMap.insert({get_utf8_char(uchar), std::move(c)});\n        }\n\n        m_isReady = true;\n        m_fontName = \"numeric\";\n        break;\n\n    case 2: // World map level title font\n        m_glyphWidth = 16;\n        m_glyphHeight = 18;\n\n        m_textures[0] = &GFX.Font2[1];\n        m_textures[1] = &GFX.Font2S;\n\n        for(char c = 33; c <= 125; ++c)\n        {\n            char uchar[5] = {c, 0, 0, 0, 0};\n            RasChar cc;\n            cc.texId = 0;\n            cc.tY = 0;\n            cc.tW = 15;\n            cc.tH = 17;\n            cc.width = 16;\n\n            if(c >= 48 && c <= 57)\n            {\n                cc.tX = (c - 48) * 16;\n                cc.texId = 0;\n            }\n            else if(c >= 65 && c <= 90)\n            {\n                cc.tX = (c - 55) * 16;\n                cc.texId = 0;\n            }\n            else if(c >= 97 && c <= 122)\n            {\n                cc.tX = (c - 61) * 16;\n                cc.texId = 0;\n            }\n            else if(c >= 33 && c <= 47)\n            {\n                cc.tX = (c - 33) * 16;\n                cc.texId = 1;\n            }\n            else if(c >= 58 && c <= 64)\n            {\n                cc.tX = (c - 58 + 15) * 16;\n                cc.texId = 1;\n            }\n            else if(c >= 91 && c <= 96)\n            {\n                cc.tX = (c - 91 + 22) * 16;\n                cc.texId = 1;\n            }\n            else if(c >= 123 && c <= 125)\n            {\n                cc.tX = (c - 123 + 28) * 16;\n                cc.texId = 1;\n            }\n\n            cc.valid = true;\n\n            m_charMap.insert({get_utf8_char(uchar), std::move(cc)});\n        }\n\n        m_isReady = true;\n        m_fontName = \"font3\";\n        break;\n\n    case 3: // Menu font\n        m_glyphWidth = 16;\n        m_glyphHeight = 18;\n\n        m_textures[0] = &GFX.Font2[2];\n\n        for(char c = 33; c <= 126; ++c)\n        {\n            char ucharH[5] = {c, 0, 0, 0, 0};\n            char ucharL[5] = {static_cast<char>(std::tolower(c)), 0, 0, 0, 0};\n\n            RasChar cc;\n            cc.texId = 0;\n            cc.tX = 2;\n            cc.tY = (c - 33) * 32;\n            cc.tW = 18;\n            cc.tH = 16;\n            cc.width = 18;\n\n            if(c == 'M')\n                cc.width += 2;\n\n            cc.valid = true;\n\n            m_charMap.insert({get_utf8_char(ucharH), cc});\n            m_charMap.insert({get_utf8_char(ucharL), std::move(cc)});\n        }\n\n        m_isReady = true;\n        m_fontName = \"font1\";\n        break;\n\n    case 4: // Message box font\n        m_glyphWidth = 18;\n        m_glyphHeight = 18;\n\n        m_textures[0] = &GFX.Font2[3];\n\n        for(char c = 33; c <= 126; ++c)\n        {\n            char ucharL[5] = {c, 0, 0, 0, 0};\n\n            RasChar cc;\n            cc.texId = 0;\n            cc.tX = 0;\n            cc.tY = (c - 33) * 16;\n            cc.tW = 18;\n            cc.tH = 16;\n            cc.width = 18;\n\n            if(c == 'M')\n                cc.width += 2;\n\n            cc.valid = true;\n\n            m_charMap.insert({get_utf8_char(ucharL), cc});\n        }\n\n        m_isReady = true;\n        m_fontName = \"font2\";\n        break;\n    }\n}\n\nPGE_Size LegacyFont::glyphSize(const char* utf8char, uint32_t charNum, uint32_t /*fontSize*/)\n{\n    PGE_Size ret(0, m_glyphHeight);\n    const char &cx = utf8char[0];\n\n    switch(cx)\n    {\n    case '\\n':\n    case '\\r':\n        break; // Has no lenght\n\n    case '\\t':\n    {\n        size_t spaceSize = m_glyphWidth;\n        if(spaceSize == 0)\n            spaceSize = 1; // Don't divide by zero\n        size_t tabMult = 4 - ((charNum / spaceSize) % 4);\n        ret.setWidth(static_cast<size_t>(m_glyphWidth) * tabMult);\n        break;\n    }\n\n    case ' ':\n    {\n        ret.setWidth(m_glyphWidth);\n        break;\n    }\n\n    default:\n    {\n        CharMap::iterator rc = m_charMap.find(get_utf8_char(&cx));\n        if(rc != m_charMap.end())\n        {\n            RasChar &rch = rc->second;\n            ret.setWidth(rch.valid ? rch.width : m_glyphWidth);\n        }\n        else\n            ret.setWidth(m_glyphWidth);\n\n        break;\n    }\n\n    }//Switch\n\n    return ret;\n}\n\nPGE_Size LegacyFont::printText(const char* text, size_t text_size, int32_t x, int32_t y,\n                               XTColor color,\n                               uint32_t /*fontSize*/,\n                               CropInfo* crop_info)\n{\n    if(m_charMap.empty() || !text || text_size == 0)\n        return PGE_Size(0, 0);\n\n    // load fallback texture if needed\n    if(m_textures[0] == &GFX.Font2[3] && !m_textures[0]->d.hasTexture())\n        LoadHardcodedFont(*m_textures[0]);\n\n    int32_t  offsetX = (crop_info) ? -crop_info->offset : 0;\n    uint32_t offsetY = 0;\n\n    int32_t  offsetX_max = 0;\n\n    uint8_t letter_alpha = color.a;\n\n    const char *strIt  = text;\n    const char *strEnd = strIt + text_size;\n    for(; strIt < strEnd; strIt++)\n    {\n        const char &cx = *strIt;\n        const char *fch = \"?\";\n        UTF8 ucx = static_cast<unsigned char>(cx);\n\n        switch(cx)\n        {\n        case '\\n':\n            if(offsetX > offsetX_max)\n                offsetX_max = offsetX;\n\n            offsetX = (crop_info) ? -crop_info->offset : 0;\n            offsetY += m_glyphHeight;\n            continue;\n\n        case '\\t':\n            // Fake tabulation\n            offsetX += offsetX + offsetX % m_glyphWidth;\n            continue;\n\n        case ' ':\n            offsetX += m_glyphWidth;\n            continue;\n        }\n\n        auto rch_f = m_charMap.find(get_utf8_char(strIt));\n        if(rch_f == m_charMap.end())\n            rch_f = m_charMap.find(get_utf8_char(fch));\n\n        if(rch_f != m_charMap.end() && rch_f->second.valid)\n        {\n            const RasChar &rch = rch_f->second;\n\n            if(crop_info)\n            {\n                if(!crop_info->letter_alpha(letter_alpha, color.a, offsetX, offsetX + rch.width))\n                    break;\n            }\n\n            if(letter_alpha != 0)\n            {\n                XRender::renderTextureBasic(x + static_cast<int32_t>(offsetX),\n                                       y + static_cast<int32_t>(offsetY),\n                                       rch.tW, rch.tH,\n                                       *m_textures[rch.texId],\n                                       rch.tX, rch.tY,\n                                       color.with_alpha(letter_alpha));\n            }\n\n            offsetX += rch.width;\n        }\n        else\n            offsetX += m_glyphWidth;\n\n        strIt += static_cast<size_t>(trailingBytesForUTF8[ucx]);\n    }\n\n    if(offsetX > offsetX_max)\n        offsetX_max = offsetX;\n\n    if(offsetX > 0)\n        offsetY += m_glyphHeight;\n\n    return PGE_Size(offsetX_max, offsetY);\n}\n\nbool LegacyFont::isLoaded() const\n{\n    return m_isReady;\n}\n\nstd::string LegacyFont::getFontName() const\n{\n    return m_fontName;\n}\n\nBaseFontEngine::FontType LegacyFont::getFontType() const\n{\n    return FONT_LEGACY;\n}\n"
  },
  {
    "path": "src/fontman/legacy_font.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LEGACYFONT_H\n#define LEGACYFONT_H\n\n#ifdef LOW_MEM\n#   include <map>\n#else\n#   include <unordered_map>\n#endif\n#include \"font_engine_base.h\"\n\nstruct StdPicture;\n\n/**\n * @brief Implementation of the legacy SMBX font engine as a part of the modern font engine\n */\nclass LegacyFont final : public BaseFontEngine\n{\npublic:\n    explicit LegacyFont(int legacyFontId);\n    LegacyFont(const LegacyFont &rf) = delete;\n    LegacyFont &operator=(const LegacyFont &tf) = delete;\n    LegacyFont(LegacyFont &&tf) = default;\n\n    virtual ~LegacyFont() override;\n\n    void loadFont(int fontId);\n\n    /*!\n     * \\brief Get a size of one glyph\n     * \\param utf8char Pointer to one UTF-8 character\n     * \\param charNum Character number in the line (required to compute the size of the taboluation)\n     * \\param fontSize Size of TTF font glyph\n     * \\return Width and height of one glyph in pixels\n     */\n    PGE_Size glyphSize(const char *utf8char, uint32_t charNum = 0, uint32_t fontSize = 14) override;\n\n    /*!\n     * \\brief Print the multiline text block on the screen\n     * \\param text Multi-line text string\n     * \\param text_size The byte size of the text string to print\n     * \\param x Hotizontal screen position (at left-top corner of the block)\n     * \\param y Vertical screen position (at left-top corner of the block)\n     * \\param Red Red channel colour\n     * \\param Green Green channel colour\n     * \\param Blue Blue channel colour\n     * \\param Alpha Alpha channel level (does not issue render calls if Alpha is 0)\n     * \\param fontSize The size of the TTF font glyph (unused for raster fonts)\n     * \\param crop_info nullable pointer to info for crop (marquee) logic\n     * \\return Width and height of the text block in pixels\n     */\n    PGE_Size printText(const char* text, size_t text_size,\n                       int32_t x, int32_t y,\n                       XTColor color = XTColor(),\n                       uint32_t fontSize = 0,\n                       CropInfo* crop_info = nullptr) override;\n\n    bool isLoaded() const override;\n\n    std::string getFontName() const override;\n    FontType getFontType() const override;\n\nprivate:\n    //! font is fine\n    bool m_isReady = false;\n\n    //! Handalable name of the font\n    std::string m_fontName;\n\n    //! Bank of legacy font textures\n    StdPicture *m_textures[10];\n\n    /**\n     * @brief Raster font glyph\n     */\n    struct RasChar\n    {\n        uint8_t  texId = 0; //!< Texture index in m_textures array\n        bool     valid = false; //!< Is a valid glyph\n        uint16_t tX = 0; //!< Horizontal pixel offset on texture\n        uint16_t tY = 0; //!< Vertical pixel offset on texture\n        uint8_t  tW = 0; //!< Pixels width of the texture fragment\n        uint8_t  tH = 0; //!< Pixels height of the texture fragment\n        uint8_t  width = 0; //!< Width of glyph\n    };\n\n    //! Fallback width of glyph\n    uint32_t m_glyphWidth = 0;\n\n    //! Height of glyph\n    uint32_t m_glyphHeight = 0;\n\n#ifdef LOW_MEM\n    typedef std::map<char32_t, RasChar > CharMap;\n#else\n    typedef std::unordered_map<char32_t, RasChar > CharMap;\n#endif\n\n    //! Table of available characters\n    CharMap m_charMap;\n\n};\n\n#endif // LEGACYFONT_H\n"
  },
  {
    "path": "src/fontman/raster_font.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <inttypes.h>\n#include \"raster_font.h\"\n\n#include \"../core/render.h\"\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include <fmt_format_ne.h>\n#include <Logger/logger.h>\n#include <IniProcessor/ini_processing.h>\n#include <Utils/files.h>\n#include <Utils/files_ini.h>\n#include <DirManager/dirman.h>\n\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n#include \"ttf_font.h\"\n#endif\n#include \"font_manager_private.h\"\n\n\nRasterFont::RasterFont() : BaseFontEngine()\n{\n    static uint64_t fontNumberCount = 0;//This number will be used as default font name\n    m_letterWidth    = 0;\n    m_letterHeight   = 0;\n    m_spaceWidth     = 0;\n    m_interLetterSpace = 0;\n    m_newlineOffset  = 0;\n    m_glyphOffsetX   = 0;\n    m_glyphOffsetY   = 0;\n    m_matrixWidth    = 0;\n    m_matrixHeight   = 0;\n    m_isReady        = false;\n    m_ttfOutlines    = false;\n    m_fontName       = fmt::format_ne(\"font{0}\", fontNumberCount++);\n}\n\nRasterFont::~RasterFont()\n{\n    m_texturesBank.clear();\n}\n\nvoid RasterFont::loadFont(const std::string &font_ini)\n{\n    if(!Files::fileExists(font_ini))\n    {\n        pLogWarning(\"Can't load font %s: file not exist\", font_ini.c_str());\n        return;\n    }\n\n    std::string root = DirMan(Files::dirname(font_ini)).absolutePath() + \"/\";\n    IniProcessing font = Files::load_ini(font_ini);\n\n// FIXME: Define it at CMake rather than here\n#if defined(__3DS__) || defined(__WII__) || defined(__16M__)\n#   define THEXTECH_USE_1X_FONT_MODE\n#endif\n\n    uint32_t ttfColourPacked = 0x000000FF;\n    uint32_t ttfOutlinesColourPacked = 0x000000FF;\n\n    size_t tables = 0;\n    font.beginGroup(\"font\");\n    font.read(\"tables\", tables, 0);\n    font.read(\"name\", m_fontName, m_fontName);\n    font.read(\"ttf-outlines\", m_ttfOutlines, false);\n    font.read(\"ttf-colour\", ttfColourPacked, 0xFFFFFFFF);\n    font.read(\"ttf-outlines-colour\", ttfOutlinesColourPacked, 0x000000FF);\n    font.read(\"ttf-fallback\", m_ttfFallback, std::string());\n    font.read(\"ttf-size\", m_ttfSize, -1);\n#if defined(THEXTECH_USE_1X_FONT_MODE) // Use special fonts targeted to smaller screen resolutions\n    font.read(\"ttf-fallback-1x\", m_ttfFallback, m_ttfFallback);\n    font.read(\"ttf-size-1x\", m_ttfSize, m_ttfSize);\n#endif\n    font.read(\"space-width\", m_spaceWidth, 0);\n    font.read(\"interletter-space\", m_interLetterSpace, 0);\n    font.read(\"newline-offset\", m_newlineOffset, 0);\n    font.read(\"glyph-offset-x\", m_glyphOffsetX, 0);\n    font.read(\"glyph-offset-y\", m_glyphOffsetY, 0);\n    font.endGroup();\n\n    std::vector<std::string> tables_list;\n    tables_list.reserve(tables);\n\n    m_ttfColour.r = (ttfColourPacked >> 24) & 0xFF;\n    m_ttfColour.g = (ttfColourPacked >> 16) & 0xFF;\n    m_ttfColour.b = (ttfColourPacked >> 8) & 0xFF;\n    m_ttfColour.a = (ttfColourPacked >> 0) & 0xFF;\n\n    m_ttfOutlinesColour.r = (ttfOutlinesColourPacked >> 24) & 0xFF;\n    m_ttfOutlinesColour.g = (ttfOutlinesColourPacked >> 16) & 0xFF;\n    m_ttfOutlinesColour.b = (ttfOutlinesColourPacked >> 8) & 0xFF;\n    m_ttfOutlinesColour.a = (ttfOutlinesColourPacked >> 0) & 0xFF;\n\n    font.beginGroup(\"tables\");\n\n    for(size_t i = 1; i <= tables; i++)\n    {\n        std::string table;\n        font.read(fmt::format_ne(\"table{0}\", i).c_str(), table, \"\");\n        if(!table.empty())\n            tables_list.push_back(table);\n    }\n\n    font.endGroup();\n\n    for(std::string &tbl : tables_list)\n        loadFontMap(root + tbl);\n\n    pLogDebug(\"Loaded raster font with name: [%s]\", m_fontName.c_str());\n}\n\nvoid RasterFont::loadFontMap(const std::string& fontmap_ini)\n{\n    std::string root = DirMan(Files::dirname(fontmap_ini)).absolutePath() + \"/\";\n\n    if(!Files::fileExists(fontmap_ini))\n    {\n        pLogWarning(\"Can't load font map %s: file not exist\", fontmap_ini.c_str());\n        return;\n    }\n\n    IniProcessing font = Files::load_ini(fontmap_ini);\n    std::string texFile;\n    uint32_t w = m_letterWidth, h = m_letterHeight;\n    int texture_scale_factor;\n    font.beginGroup(\"font-map\");\n    font.read(\"texture\", texFile, \"\");\n    font.read(\"texture-scale\", texture_scale_factor, 1);\n    font.read(\"width\", w, 0);\n    font.read(\"height\", h, 0);\n    m_matrixWidth = w;\n    m_matrixHeight = h;\n\n    if((w <= 0) || (h <= 0))\n    {\n        pLogWarning(\"Wrong width and height values! %\" PRId32 \" x %\" PRId32 \"\",  w, h);\n        return;\n    }\n\n    if(!Files::fileExists(root + texFile))\n    {\n        pLogWarning(\"Failed to load font texture! file not exists: %s\",\n                    (root + texFile).c_str());\n        return;\n    }\n\n    m_texturesBank.emplace_back();\n    StdPicture &fontTexture = m_texturesBank.back();\n\n    XRender::LoadPicture(fontTexture, root + texFile, texture_scale_factor);\n\n    if(!fontTexture.inited)\n        pLogWarning(\"Failed to load font texture! Invalid image!\");\n\n    if((m_letterWidth == 0) || (m_letterHeight == 0))\n    {\n        m_letterWidth    = static_cast<uint32_t>(fontTexture.w) / w;\n        m_letterHeight   = static_cast<uint32_t>(fontTexture.h) / h;\n\n        if(m_spaceWidth == 0)\n            m_spaceWidth = m_letterWidth;\n\n        if(m_newlineOffset == 0)\n            m_newlineOffset = m_letterHeight;\n    }\n\n    font.endGroup();\n    font.beginGroup(\"entries\");\n    std::vector<std::string> entries = font.allKeys();\n\n    for(std::string &x : entries)\n    {\n        std::string charPosX = \"0\", charPosY = \"0\";\n\n        std::string::size_type begPos = 0;\n        std::string::size_type endPos = x.find('-', begPos);\n\n        //QStringList tmp = x.split('-');\n        if(endPos == std::string::npos)\n            endPos = x.size();//Use entire string\n\n        charPosX = x.substr(begPos, endPos);\n\n        if(charPosX.empty())\n        {\n            pLogDebug(\"=invalid-X=%s=\", x.c_str());\n            continue;\n        }\n\n        if(m_matrixWidth > 1)\n        {\n            if(endPos == x.size())\n            {\n                pLogWarning(\"=invalid-Y=%s= in the raster font map %s\", x.c_str(), fontmap_ini.c_str());\n                continue;\n            }\n            begPos = endPos + 1;//+1 to skip '-' divider\n            endPos = x.find('-', begPos);\n            if(endPos == std::string::npos)\n                endPos = x.size();//Use entire string\n            charPosY = x.substr(begPos, endPos);\n            if(charPosY.empty())\n            {\n                pLogWarning(\"=invalid-Y=%s= in the raster font map %s\", x.c_str(), fontmap_ini.c_str());\n                continue;\n            }\n        }\n\n        std::string charX;\n        font.read(x.c_str(), charX, \"\");\n\n        /*Format of entry: X23\n         * X - UTF-8 Symbol\n         * 2 - padding left [for non-mono fonts]\n         * 3 - padding right [for non-mono fonts]\n        */\n        if(charX.empty())\n            continue;\n\n        std::u32string ucharX = std_to_utf32(charX);\n        char32_t ch = ucharX[0];\n        //qDebug()<<\"=char=\" << ch << \"=id=\"<<charPosX.toInt()<<charPosY.toInt()<<\"=\";\n        RasChar rch;\n        try\n        {\n            rch.tx              =  &fontTexture;\n            rch.x               =  static_cast<int16_t>(std::stoi(charPosY.c_str()) * fontTexture.w / m_matrixWidth);\n            rch.padding_left    = (ucharX.size() > 1) ? char2int(ucharX[1]) : 0;\n            rch.padding_right   = (ucharX.size() > 2) ? char2int(ucharX[2]) : 0;\n            rch.y               =  static_cast<int16_t>(std::stoi(charPosX.c_str()) * fontTexture.h / m_matrixHeight);\n            rch.valid = true;\n        }\n        catch(std::exception &e)\n        {\n            pLogWarning(\"Invalid entry of font map: entry: %s, reason: %s, file: %s\", x.c_str(), e.what(), fontmap_ini.c_str());\n        }\n\n        m_charMap[ch] = rch;\n    }\n\n    font.endGroup();\n\n    if(!m_charMap.empty())\n        m_isReady = true;\n}\n\n\nPGE_Size RasterFont::glyphSize(const char* utf8char, uint32_t charNum, uint32_t /*fontSize*/)\n{\n    PGE_Size ret(0, m_newlineOffset);\n    const char &cx = utf8char[0];\n\n    switch(cx)\n    {\n    case '\\n':\n    case '\\r':\n        break; // Has no lenght\n\n    case '\\t':\n    {\n        size_t spaceSize = m_spaceWidth;\n        if(spaceSize == 0)\n            spaceSize = 1; // Don't divide by zero\n        size_t tabMult = 4 - ((charNum / spaceSize) % 4);\n        ret.setWidth(static_cast<size_t>(spaceSize) * tabMult);\n        ret.setHeight(m_newlineOffset);\n        break;\n    }\n\n    case ' ':\n    {\n        ret.setWidth(m_spaceWidth);\n        ret.setHeight(m_newlineOffset);\n        break;\n    }\n\n    default:\n    {\n        CharMap::iterator rc = m_charMap.find(get_utf8_char(&cx));\n        bool need_a_ttf = false;\n        if(rc != m_charMap.end())\n        {\n            RasChar &rch = rc->second;\n            need_a_ttf = !rch.valid;\n            if(rch.valid)\n                ret.setWidth(m_letterWidth - rch.padding_left - rch.padding_right + m_interLetterSpace);\n        }\n        else\n            need_a_ttf = true;\n\n        if(need_a_ttf)\n        {\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n            TtfFont *font = FontManager::getTtfFontByName(m_ttfFallback);\n            if(font)\n            {\n                uint32_t font_size_use = m_ttfSize > 0 ? m_ttfSize : m_letterWidth;\n                // int y_offset = 0; // UNUSED\n                bool doublePixel = font->doublePixel();\n\n                if(font->bitmapSize())\n                {\n                    if(font_size_use > font->bitmapSize() * 1.5)\n                        doublePixel = true;\n\n                    font_size_use = font->bitmapSize();\n                }\n                else if(doublePixel)\n                    font_size_use /= 2;\n\n                TtfFont::TheGlyphInfo glyph = font->getGlyphInfo(&cx, font_size_use);\n                uint32_t glyph_width = glyph.width > 0 ? uint32_t(glyph.advance_x >> 6) : (font_size_use >> 2);\n                uint32_t glyph_height = glyph.height;\n                if(doublePixel)\n                {\n                    glyph_width *= 2;\n                    glyph_height *= 2;\n                }\n\n                // Raster fonts are monospace fonts. TTF glyphs shoudn't break mono-width until they are wider than a cell\n                auto lw = SDL_max(glyph_width, m_letterWidth);\n                ret.setWidth(lw + m_interLetterSpace);\n                ret.setHeight(glyph_height + 2);\n            }\n            else\n#endif\n            {\n                ret.setWidth(m_letterWidth + m_interLetterSpace);\n                ret.setHeight(m_newlineOffset);\n            }\n        }\n\n        break;\n    }\n\n    }//Switch\n\n    return ret;\n}\n\nPGE_Size RasterFont::printText(const char* text, size_t text_size,\n                               int32_t x, int32_t y,\n                               XTColor color,\n                               uint32_t fontSize,\n                               CropInfo* crop_info)\n{\n    if(m_charMap.empty() || !text || text_size == 0)\n        return PGE_Size(0, 0);\n\n    int32_t  offsetX = (crop_info) ? -crop_info->offset : 0;\n    uint32_t offsetY = 0;\n    uint32_t w = m_letterWidth;\n    uint32_t h = m_letterHeight;\n\n    uint32_t line_offset = m_newlineOffset;\n\n    int32_t  offsetX_max = 0;\n\n    uint8_t letter_alpha = color.a;\n\n    const char *strIt  = text;\n    const char *strEnd = strIt + text_size;\n    for(; strIt < strEnd; strIt++)\n    {\n        const char &cx = *strIt;\n        UTF8 ucx = static_cast<unsigned char>(cx);\n\n        switch(cx)\n        {\n        case '\\n':\n            if(offsetX > offsetX_max)\n                offsetX_max = offsetX;\n\n            offsetX = (crop_info) ? -crop_info->offset : 0;\n            offsetY += line_offset;\n            line_offset = m_newlineOffset;\n            continue;\n\n        case '\\t':\n            //Fake tabulation\n            offsetX += offsetX + offsetX % w;\n            continue;\n\n        case ' ':\n            offsetX += m_spaceWidth;\n            continue;\n        }\n\n        const auto rch_f = m_charMap.find(get_utf8_char(strIt));\n        if(rch_f != m_charMap.end() && rch_f->second.valid)\n        {\n            const auto &rch = rch_f->second;\n\n            int drawn_width = w - rch.padding_left - rch.padding_right;\n\n            if(crop_info)\n            {\n                if(!crop_info->letter_alpha(letter_alpha, color.a, offsetX, offsetX + drawn_width))\n                    break;\n            }\n\n            if(letter_alpha != 0)\n            {\n                XRender::renderTextureBasic(x + static_cast<int32_t>(offsetX - rch.padding_left + m_glyphOffsetX),\n                                       y + static_cast<int32_t>(offsetY + m_glyphOffsetY),\n                                       w, h,\n                                       *rch.tx,\n                                       rch.x, rch.y,\n                                       color.with_alpha(letter_alpha));\n            }\n\n            offsetX += drawn_width + m_interLetterSpace;\n        }\n        else\n#ifdef THEXTECH_ENABLE_TTF_SUPPORT\n        {\n            TtfFont *font = FontManager::getTtfFontByName(m_ttfFallback);\n            if(font)\n            {\n                uint32_t font_size_use = (m_ttfSize > 0) ? m_ttfSize : m_letterWidth;\n\n                int y_offset = 0;\n                int baseline = h;\n                bool doublePixel = font->doublePixel();\n\n                if(font->bitmapSize())\n                {\n                    if(font->doublePixel() || font_size_use > font->bitmapSize() * 1.5)\n                        doublePixel = true;\n\n                    if(fontSize == 0)\n                        y_offset = 0;\n                    else if(doublePixel)\n                    {\n                        y_offset = ((int)fontSize - (font->bitmapSize() * 2)) / 2;\n                        baseline += - (h / 2) + font->bitmapSize();\n                    }\n                    else\n                    {\n                        y_offset = ((int)fontSize - font->bitmapSize()) / 2;\n                        baseline += - (h / 2) + (font->bitmapSize() / 2);\n                    }\n\n                    font_size_use = font->bitmapSize();\n                }\n                else if(doublePixel)\n                    font_size_use /= 2;\n\n                TtfFont::TheGlyphInfo glyph = font->getGlyphInfo(&cx, font_size_use);\n                uint32_t glyph_width = glyph.width > 0 ? uint32_t(glyph.advance_x >> 6) : (font_size_use >> 2);\n                uint32_t glyph_height = glyph.height;\n\n                if(doublePixel)\n                {\n                    glyph_width *= 2;\n                    glyph_height *= 2;\n                }\n\n                if(glyph_height + 2 > line_offset)\n                    line_offset = glyph_height + 2;\n\n                if(m_ttfOutlines)\n                    offsetX += (doublePixel ? 2 : 1);\n\n                int drawn_width = SDL_max(glyph_width, m_letterWidth);\n\n                if(crop_info)\n                {\n                    if(!crop_info->letter_alpha(letter_alpha, color.a, offsetX, offsetX + drawn_width))\n                        break;\n                }\n\n                if(letter_alpha != 0)\n                {\n                    font->drawGlyphB(&cx,\n                                    x + static_cast<int32_t>(offsetX + m_glyphOffsetX),\n                                    y + baseline + y_offset + static_cast<int32_t>(offsetY),\n                                    font_size_use,\n                                    (doublePixel ? 2 : 1),\n                                    m_ttfOutlines,\n                                    (color * m_ttfColour).with_alpha(letter_alpha),\n                                    m_ttfOutlinesColour);\n                }\n\n                offsetX += drawn_width + m_interLetterSpace;\n            }\n            else\n            {\n                offsetX += m_letterWidth + m_interLetterSpace;\n            }\n        }\n#else\n        {\n            UNUSED(fontSize);\n            offsetX += m_interLetterSpace;\n        }\n#endif\n        strIt += static_cast<size_t>(trailingBytesForUTF8[ucx]);\n    }\n\n    if(offsetX > offsetX_max)\n        offsetX_max = offsetX;\n\n    // remove trailing space\n    if(offsetX_max > (int32_t)m_interLetterSpace)\n        offsetX_max -= m_interLetterSpace;\n\n    // add current line\n    if(offsetX > 0)\n        offsetY += m_newlineOffset;\n\n    return PGE_Size(offsetX_max, offsetY);\n}\n\nbool RasterFont::isLoaded() const\n{\n    return m_isReady;\n}\n\nstd::string RasterFont::getFontName() const\n{\n    return m_fontName;\n}\n\nBaseFontEngine::FontType RasterFont::getFontType() const\n{\n    return FONT_RASTER;\n}\n"
  },
  {
    "path": "src/fontman/raster_font.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef RASTER_FONT_H\n#define RASTER_FONT_H\n\n#include <string>\n#ifdef LOW_MEM\n#   include <map>\n#else\n#   include <unordered_map>\n#endif\n#include <Utils/vptrlist.h>\n#include \"std_picture.h\"\n#include <Graphics/size.h>\n\n#include \"font_engine_base.h\"\n\n/**\n * @brief The raster fonts engine\n */\nclass RasterFont final : public BaseFontEngine\n{\npublic:\n    RasterFont();\n    RasterFont(const RasterFont &rf) = delete;\n    RasterFont &operator=(const RasterFont &tf) = delete;\n    RasterFont(RasterFont &&tf) = default;\n\n    virtual ~RasterFont() override;\n\n    void  loadFont(const std::string& font_ini);\n    void  loadFontMap(const std::string &fontmap_ini);\n\n    /*!\n     * \\brief Get a size of one glyph\n     * \\param utf8char Pointer to one UTF-8 character\n     * \\param charNum Character number in the line (required to compute the size of the taboluation)\n     * \\param fontSize Size of TTF font glyph\n     * \\return Width and height of one glyph in pixels\n     */\n    PGE_Size glyphSize(const char *utf8char, uint32_t charNum = 0, uint32_t fontSize = 14) override;\n\n    /*!\n     * \\brief Print the multiline text block on the screen\n     * \\param text Multi-line text string\n     * \\param text_size The byte size of the text string to print\n     * \\param x Hotizontal screen position (at left-top corner of the block)\n     * \\param y Vertical screen position (at left-top corner of the block)\n     * \\param Red Red channel colour\n     * \\param Green Green channel colour\n     * \\param Blue Blue channel colour\n     * \\param Alpha Alpha channel level (does not issue render calls if Alpha is 0)\n     * \\param fontSize The size of the TTF font glyph (used for line height if non-zero for raster fonts)\n     * \\param crop_info nullable pointer to info for crop (marquee) logic\n     * \\return Width and height of the text block in pixels\n     */\n    PGE_Size printText(const char* text, size_t text_size,\n                       int32_t x, int32_t y,\n                       XTColor color = XTColor(),\n                       uint32_t fontSize = 0,\n                       CropInfo* crop_info = nullptr) override;\n\n    bool isLoaded() const override;\n\n    std::string getFontName() const override;\n    FontType getFontType() const override;\n\nprivate:\n    //! font is fine\n    bool m_isReady = false;\n    //! The fallback TTF name to prefer to render a missing character [all unknown characters will be rendered as TTF]\n    std::string m_ttfFallback;\n    //! Colour for backup ttf font render\n    XTColor m_ttfColour = {255, 255, 255, 255};\n    //! Enable outline borders on backup ttf font render\n    bool m_ttfOutlines = false;\n    XTColor m_ttfOutlinesColour = {0, 0, 0, 255};\n    //! The fallback TTF size of the glyph to request\n    int  m_ttfSize = -1;\n    //! Width of one letter\n    uint32_t m_letterWidth = 0;\n    //! Height of one letter\n    uint32_t m_letterHeight = 0;\n    //! Space between printing letters\n    int32_t m_interLetterSpace = 0;\n    //! Width of space symbol\n    uint32_t m_spaceWidth = 0;\n    //! Distance between top of one line and top of next\n    uint32_t m_newlineOffset = 0;\n\n    //! Offset all characters by X\n    int32_t m_glyphOffsetX = 0;\n    //! Offset all characters by Y\n    int32_t m_glyphOffsetY = 0;\n\n    //! Width of font matrix\n    uint32_t m_matrixWidth = 0;\n    //! Width of font matrix\n    uint32_t m_matrixHeight = 0;\n\n    //! Handalable name of the font\n    std::string m_fontName;\n\n    /**\n     * @brief Raster font glyph\n     */\n    struct RasChar\n    {\n        bool        valid = false; //!< Is a valid glyph\n        StdPicture* tx     = nullptr; //!< Pointer to the texture that contains this glyph\n        uint8_t     padding_left    = 0; //!< Crop left\n        uint8_t     padding_right   = 0; //!< Crop right\n        int16_t     x = 0; //!< X pixel offset\n        int16_t     y = 0; //!< Y pixel offset\n    };\n\n#ifdef LOW_MEM\n    typedef std::map<char32_t, RasChar > CharMap;\n#else\n    typedef std::unordered_map<char32_t, RasChar > CharMap;\n#endif\n\n    //! Table of available characters\n    CharMap m_charMap;\n\n    //! Bank of loaded textures\n    VPtrList<StdPicture> m_texturesBank;\n};\n\n#endif // RASTER_FONT_H\n"
  },
  {
    "path": "src/fontman/ttf_font.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <inttypes.h>\n#include <SDL2/SDL_rwops.h>\n#include <Utils/files.h>\n#include <Utils/strings.h>\n\n#include \"ttf_font.h\"\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_assert.h\"\n#if !defined(PGE_NO_THREADING)\n#   ifdef PGE_SDL_MUTEX\n#       include <SDL2/SDL_mutex.h>\n#   else\n#       include <mutex>\n#   endif\n#endif\n#include \"core/render.h\"\n#include <Logger/logger.h>\n#include <unordered_set>\n\n#include \"font_manager_private.h\"\n\n//! FreeType library descriptor\nFT_Library  g_ft = nullptr;\n\n//! Loaded TTF fonts set used as fallback for missing glyphs\nstatic std::unordered_set<TtfFont*> g_loadedFaces;\n\n#ifndef PGE_NO_THREADING\n#   ifdef PGE_SDL_MUTEX\nstatic SDL_mutex                   *g_loadedFaces_mutex = nullptr;\n#   define TTF_MUTEX_LOCK()         SDL_LockMutex(g_loadedFaces_mutex)\n#   define TTF_MUTEX_UNLOCK()       SDL_UnlockMutex(g_loadedFaces_mutex)\n#   else\nstatic std::mutex                   g_loadedFaces_mutex;\n#   define TTF_MUTEX_LOCK()         g_loadedFaces_mutex.lock()\n#   define TTF_MUTEX_UNLOCK()       g_loadedFaces_mutex.unlock()\n#   endif\n#else\n#   define TTF_MUTEX_LOCK()         (void)0\n#   define TTF_MUTEX_UNLOCK()       (void)0\n#endif\n\nstatic unsigned long s_FT_rwops_io_func(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count)\n{\n    if(!stream)\n        return !count;\n\n    SDL_RWops* rwops = (SDL_RWops*)stream->descriptor.pointer;\n\n    if(!rwops || SDL_RWseek(rwops, offset, RW_SEEK_SET) < 0)\n        return !count;\n\n    if(!count)\n        return 0;\n\n    if(!buffer)\n        return 0;\n\n    return SDL_RWread(rwops, buffer, 1, count);\n}\n\nstatic void s_FT_rwops_close_func(FT_Stream stream)\n{\n    if(!stream)\n        return;\n\n    SDL_RWops* rwops = (SDL_RWops*)stream->descriptor.pointer;\n\n    if(!rwops)\n        return;\n\n    SDL_RWclose(rwops);\n\n    delete stream;\n}\n\nstatic FT_Stream s_FT_make_stream(const std::string& path)\n{\n    SDL_RWops* rwops = Files::open_file(path, \"rb\");\n\n    if(!rwops)\n        return nullptr;\n\n    FT_Stream stream = new(std::nothrow) FT_StreamRec;\n    if(!stream)\n    {\n        SDL_RWclose(rwops);\n        return nullptr;\n    }\n\n    stream->base = nullptr;\n    stream->descriptor.pointer = (void*)rwops;\n    stream->size = SDL_RWsize(rwops);\n    stream->read = s_FT_rwops_io_func;\n    stream->close = s_FT_rwops_close_func;\n\n    return stream;\n}\n\n\nbool initializeFreeType()\n{\n    FT_Error error = FT_Init_FreeType(&g_ft);\n    SDL_assert_release(error == 0);\n#if !defined(PGE_NO_THREADING) && defined(PGE_SDL_MUTEX)\n    if(!g_loadedFaces_mutex)\n        g_loadedFaces_mutex = SDL_CreateMutex();\n#endif\n    return true;\n}\n\nvoid closeFreeType()\n{\n    if(g_ft)\n    {\n        FT_Done_FreeType(g_ft);\n        g_ft = nullptr;\n    }\n\n#if !defined(PGE_NO_THREADING) && defined(PGE_SDL_MUTEX)\n    if(g_loadedFaces_mutex)\n    {\n        SDL_DestroyMutex(g_loadedFaces_mutex);\n        g_loadedFaces_mutex = nullptr;\n    }\n#endif\n}\n\n// Default dummy glyph\nconst TtfFont::TheGlyph TtfFont::dummyGlyph = TtfFont::TheGlyph();\n\nTtfFont::TtfFont() : BaseFontEngine()\n{\n    SDL_assert_release(g_ft);\n}\n\nTtfFont::~TtfFont()\n{\n    TTF_MUTEX_LOCK();\n    if(m_face)\n    {\n        g_loadedFaces.erase(this);\n        FT_Done_Face(m_face);\n    }\n    TTF_MUTEX_UNLOCK();\n\n    m_face = nullptr;\n}\n\nbool TtfFont::loadFont(const std::string &path)\n{\n    SDL_assert_release(g_ft);\n\n    FT_Open_Args args;\n    args.flags = FT_OPEN_STREAM;\n    args.stream = s_FT_make_stream(path);\n\n    FT_Error error = FT_Err_Cannot_Open_Resource;\n    if(args.stream)\n        error = FT_Open_Face(g_ft, &args, 0, &m_face);\n\n    if(!args.stream || error)\n    {\n        pLogWarning(\"Failed to load the font: %s\", FT_Error_String(error));\n        return false;\n    }\n\n    error = FT_Set_Pixel_Sizes(m_face, 0, m_recentPixelSize);\n    if(error)\n        pLogWarning(\"Failed to set the pixel sizes %\" PRIu32 \" for the font %s: %s\", m_recentPixelSize, path.c_str(), FT_Error_String(error));\n\n    error = FT_Select_Charmap(m_face, FT_ENCODING_UNICODE);\n    if(error)\n    {\n        pLogWarning(\"Failed to select the charmap for the font %s: %s\", path.c_str(), FT_Error_String(error));\n        FT_Done_Face(m_face);\n        m_face = nullptr;\n        return false;\n    }\n\n    TTF_MUTEX_LOCK();\n    g_loadedFaces.insert(this);\n    TTF_MUTEX_UNLOCK();\n\n    m_fontName.append(m_face->family_name);\n    m_fontName.push_back(' ');\n    m_fontName.append(m_face->style_name);\n    m_isReady = true;\n\n    pLogDebug(\"Loaded TTF font with name: [%s]\", m_fontName.c_str());\n\n    return true;\n}\n\nbool TtfFont::loadFont(const char *mem, size_t size)\n{\n    SDL_assert_release(g_ft);\n    FT_Error error = FT_New_Memory_Face(g_ft, reinterpret_cast<const FT_Byte *>(mem),\n                                        static_cast<FT_Long>(size), 0, &m_face);\n    SDL_assert(error == 0);\n    if(error)\n        return false;\n    error = FT_Set_Pixel_Sizes(m_face, 0, m_recentPixelSize);\n    SDL_assert(error == 0);\n    if(error)\n        return false;\n    error = FT_Select_Charmap(m_face, FT_ENCODING_UNICODE);\n    SDL_assert(error == 0);\n    if(error)\n        return false;\n\n    TTF_MUTEX_LOCK();\n    g_loadedFaces.insert(this);\n    TTF_MUTEX_UNLOCK();\n\n    m_isReady = true;\n    return true;\n}\n\nvoid TtfFont::setAntiAlias(bool enable)\n{\n    m_enableAntialias = enable;\n}\n\nbool TtfFont::antiAlias() const\n{\n    return m_enableAntialias;\n}\n\nvoid TtfFont::setBitmapSize(int size)\n{\n    m_bitmapSize = size;\n}\n\nint TtfFont::bitmapSize() const\n{\n    return m_bitmapSize;\n}\n\nvoid TtfFont::setDoublePixel(bool enable)\n{\n    m_doublePixel = enable;\n}\n\nbool TtfFont::doublePixel() const\n{\n    return m_doublePixel;\n}\n\nvoid TtfFont::setLanguages(const std::string &langs)\n{\n    m_languages = langs;\n}\n\nbool TtfFont::hasLanguage(const std::string &lang) const\n{\n    std::string::size_type beg = 0;\n    std::string::size_type end = 0;\n\n    if(m_languages.empty())\n        return false;\n\n    do\n    {\n        end = m_languages.find(',', beg);\n\n        if(end == std::string::npos)\n            end = m_languages.size();\n\n        // trim word\n        auto trim_beg = beg;\n        while(m_languages[trim_beg] == ' ' && trim_beg < end)\n            trim_beg++;\n\n        auto trim_end = end;\n        while(trim_end > trim_beg && m_languages[trim_end - 1] == ' ')\n            trim_end--;\n\n        if(SDL_strncmp(m_languages.c_str() + trim_beg, lang.c_str(), trim_end - trim_beg) == 0)\n            return true;\n\n        beg = end + 1;\n    }\n    while(end < m_languages.size() - 1);\n\n    return false;\n}\n\nPGE_Size TtfFont::glyphSize(const char* utf8char, uint32_t charNum, uint32_t fontSize)\n{\n    PGE_Size ret(0, static_cast<int32_t>(fontSize * 1.5));\n\n    const char &cx = utf8char[0];\n\n    switch(cx)\n    {\n    case '\\n':\n    case '\\r':\n        break;\n\n    case '\\t':\n    {\n        //Fake tabulation\n        size_t space = (4 * fontSize);\n        if(space == 0)\n            space = 1; // Don't divide by zero\n        ret.setWidth(space - ((charNum / space) % 4));\n        break;\n    }\n\n    default:\n    {\n        const TheGlyph &glyph = getGlyph(fontSize, get_utf8_char(&cx));\n        ret.setWidth(glyph.info.width > 0 ? uint32_t(glyph.info.advance_x >> 6) : (fontSize >> 2));\n        break;\n    }\n\n    }//Switch\n\n    return ret;\n}\n\nPGE_Size TtfFont::printText(const char *text, size_t text_size,\n                            int32_t x, int32_t y,\n                            XTColor color,\n                            uint32_t fontSize,\n                            CropInfo* crop_info)\n{\n    SDL_assert_release(g_ft);\n    if(!text || text_size == 0)\n        return PGE_Size(0, 0);\n\n    int32_t  offsetX = (crop_info) ? -crop_info->offset : 0;\n    uint32_t offsetY = 0;\n\n    int32_t  offsetX_max = 0;\n\n    uint8_t letter_alpha = color.a;\n\n    const char *strIt  = text;\n    const char *strEnd = strIt + text_size;\n    for(; strIt < strEnd; strIt++)\n    {\n        const char &cx = *strIt;\n        UTF8 ucx = static_cast<unsigned char>(cx);\n\n        switch(cx)\n        {\n        case '\\n':\n            if(offsetX > offsetX_max)\n                offsetX_max = offsetX;\n\n            offsetX = (crop_info) ? -crop_info->offset : 0;\n            offsetY += static_cast<uint32_t>(fontSize * 1.5);\n            continue;\n\n        case '\\t':\n            //Fake tabulation\n            offsetX += offsetX + offsetX % uint32_t(fontSize * 1.5);\n            continue;\n//        case ' ':\n//            offsetX += m_spaceWidth + m_interLetterSpace / 2;\n//            continue;\n        default:\n            break;\n        }\n\n        const TheGlyph &glyph = getGlyph(m_doublePixel ? (fontSize / 2) : fontSize, get_utf8_char(&cx));\n        if(glyph.tx)\n        {\n            if(crop_info)\n            {\n                if(!crop_info->letter_alpha(letter_alpha, color.a, offsetX, offsetX + (glyph.info.advance_x >> 6)))\n                    break;\n            }\n\n            if(letter_alpha != 0)\n            {\n                int32_t glyph_x = x + static_cast<int32_t>(offsetX);\n                int32_t glyph_y = y + static_cast<int32_t>(offsetY + fontSize);\n                XRender::renderTextureScale(\n                    glyph_x + glyph.info.left,\n                    glyph_y - glyph.info.top,\n                    (m_doublePixel ? (glyph.info.width * 2) : glyph.info.width),\n                    (m_doublePixel ? (glyph.info.height * 2) : glyph.info.height),\n                    *glyph.tx,\n                    color.with_alpha(letter_alpha)\n                );\n            }\n        }\n        offsetX += glyph.tx ? uint32_t(glyph.info.advance_x >> 6) : (fontSize >> 2);\n\n        strIt += static_cast<size_t>(trailingBytesForUTF8[ucx]);\n    }\n\n    if(offsetX > offsetX_max)\n        offsetX_max = offsetX;\n\n    if(offsetX > 0)\n        offsetY += static_cast<uint32_t>(fontSize);\n\n    return PGE_Size(offsetX_max, offsetY);\n}\n\nbool TtfFont::isLoaded() const\n{\n    return m_isReady;\n}\n\nvoid TtfFont::setFontName(const std::string& name)\n{\n    m_fontName = name;\n}\n\nstd::string TtfFont::getFontName() const\n{\n    return m_fontName;\n}\n\nBaseFontEngine::FontType TtfFont::getFontType() const\n{\n    return FONT_TTF;\n}\n\n// #define TTF_RENDER_DEBUG\n\nuint32_t TtfFont::drawGlyph(const char *u8char,\n                            int32_t x, int32_t y, uint32_t fontSize, int scaleSize,\n                            bool drawOutlines,\n                            XTColor color,\n                            XTColor OL_color)\n{\n#ifdef TTF_RENDER_DEBUG\n    const TheGlyph &glyph = getGlyph(fontSize, get_utf8_char(u8char));\n\n    if(glyph.tx)\n        XRender::renderRect(x, y, glyph.info.width * scaleSize, glyph.info.height * scaleSize, XTColor(0, 0, 255, 128), false);\n#endif\n\n    return drawGlyphB(u8char, x, y + static_cast<int32_t>(fontSize * scaleSize), fontSize, scaleSize, drawOutlines, color, OL_color);\n}\n\nuint32_t TtfFont::drawGlyphB(const char *u8char, int32_t x, int32_t baseline_y, uint32_t fontSize, int scaleSize, bool drawOutlines, XTColor color, XTColor OL_color)\n{\n    const TheGlyph &glyph = getGlyph(fontSize, get_utf8_char(u8char));\n\n    if(glyph.tx)\n    {\n        int32_t glyph_x = x + (glyph.info.left * scaleSize);\n        int32_t glyph_y = baseline_y - (glyph.info.top * scaleSize);\n        int32_t glyph_w = glyph.info.width * scaleSize;\n        int32_t glyph_h = glyph.info.height * scaleSize;\n\n        if(drawOutlines)\n        {\n            const int offsets[4][2] =\n            {\n                {-scaleSize, 0},\n                { scaleSize, 0},\n                {0, -scaleSize},\n                {0,  scaleSize}\n            };\n\n            // take square of Alpha to match blend of normal text\n            uint8_t scaled_a = XTColor::mul(color.a, color.a);\n\n            for(size_t i = 0; i < 4; ++i)\n            {\n                XRender::renderTextureScale(\n                    glyph_x + offsets[i][0], glyph_y + offsets[i][1],\n                    glyph_w, glyph_h, *glyph.tx,\n                    color.with_alpha(scaled_a) * OL_color\n                );\n            }\n        }\n\n        XRender::renderTextureScale(glyph_x, glyph_y, glyph_w, glyph_h, *glyph.tx, color);\n\n#ifdef TTF_RENDER_DEBUG\n        XRender::renderRect(x, baseline_y - glyph_h, glyph_w, glyph_h, XTColor(0, 0, 255, 128), false);\n        XRender::renderRect(glyph_x, glyph_y, glyph_w, glyph_h, XTColor(255, 0, 0, 128), false);\n#endif\n\n        return glyph.info.width;\n    }\n\n    return fontSize;\n}\n\nTtfFont::TheGlyphInfo TtfFont::getGlyphInfo(const char *u8char, uint32_t fontSize)\n{\n    TheGlyph glyph = getGlyph(fontSize, get_utf8_char(u8char));\n    return glyph.info;\n}\n\nconst TtfFont::TheGlyph &TtfFont::getGlyph(uint32_t fontSize, char32_t character)\n{\n    auto fSize = m_charMap.find(fontSize);\n    if(fSize != m_charMap.end())\n    {\n        auto rc = fSize->second.find(character);\n        if(rc != fSize->second.end())\n        {\n            // reload if the renderer has unloaded the glyph\n            if(rc->second.tx && !rc->second.tx->d.hasTexture())\n                return loadGlyph(*(rc->second.tx), fontSize, character);\n\n            return rc->second;\n        }\n        return loadGlyph(fontSize, character);\n    }\n\n    return loadGlyph(fontSize, character);\n}\n\nconst TtfFont::TheGlyph &TtfFont::loadGlyph(uint32_t fontSize, char32_t character)\n{\n    m_texturesBank.emplace_back();\n    StdPicture &texture = m_texturesBank.back();\n\n    const TtfFont::TheGlyph &ret = loadGlyph(texture, fontSize, character);\n\n    if(ret.tx != &texture)\n        m_texturesBank.pop_back();\n\n    return ret;\n}\n\nconst TtfFont::TheGlyph &TtfFont::loadGlyph(StdPicture &texture, uint32_t fontSize, char32_t character)\n{\n    FT_Error     error = 0;\n    FT_UInt      t_glyphIndex = 0;\n    TheGlyph     t_glyph;\n    FT_Face      cur_font = m_face;\n\n    if(m_recentPixelSize != fontSize)\n    {\n        TTF_MUTEX_LOCK();\n        error = FT_Set_Pixel_Sizes(cur_font, 0, fontSize);\n        if(error)\n            pLogWarning(\"TheGlyph::loadGlyph (1) Failed to set the pixel sizes %\" PRIu32 \" for the font %s: %s\", fontSize, cur_font->family_name, FT_Error_String(error));\n        m_recentPixelSize = fontSize;\n        TTF_MUTEX_UNLOCK();\n    }\n\n    t_glyphIndex = FT_Get_Char_Index(cur_font, character);\n\n    if(t_glyphIndex == 0)//Attempt to find a fallback\n    {\n        TTF_MUTEX_LOCK();\n        for(TtfFont *fb_font : g_loadedFaces)\n        {\n            if(fb_font->m_recentPixelSize != fontSize)\n            {\n                error = FT_Set_Pixel_Sizes(fb_font->m_face, 0, fontSize);\n                if(error)\n                    pLogWarning(\"TheGlyph::loadGlyph (2) Failed to set the pixel sizes %\" PRIu32 \" for the font %s: %s\", fontSize, cur_font->family_name, FT_Error_String(error));\n                fb_font->m_recentPixelSize = fontSize;\n            }\n\n            t_glyphIndex = FT_Get_Char_Index(fb_font->m_face, character);\n            if(t_glyphIndex != 0)\n            {\n                // Fallback has been found!\n                cur_font = fb_font->m_face;\n                break;\n            }\n        }\n        TTF_MUTEX_UNLOCK();\n    }\n\n    FT_Int32 ftLoadFlags = FT_LOAD_RENDER;\n\n    if(FT_HAS_COLOR(cur_font))\n        ftLoadFlags |= FT_LOAD_COLOR;\n    else if(!m_enableAntialias)\n        ftLoadFlags |= FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME;\n\n    error = FT_Load_Glyph(cur_font, t_glyphIndex, ftLoadFlags);\n    if(error != 0)\n        return dummyGlyph;\n\n\n    FT_GlyphSlot glyph  = cur_font->glyph;\n    FT_Bitmap &bitmap   = glyph->bitmap;\n    uint32_t width      = bitmap.width;\n    uint32_t height     = bitmap.rows;\n    uint32_t pitch      = width * 4;\n\n    if((width == 0) || (height == 0))\n        return dummyGlyph;\n\n    SDL_assert_release(bitmap.buffer); // Buffer must NOT be null\n\n    uint8_t *image = nullptr;\n\n    try\n    {\n        size_t image_size = 4 * width * height;\n        image = new uint8_t[image_size];\n        SDL_memset(image, 0, image_size);\n    }\n    catch(const std::bad_alloc &e)\n    {\n        pLogCritical(\"TtfFont::TheGlyph: Out of memory: %s\", e.what());\n        return dummyGlyph;\n    }\n\n    switch(bitmap.pixel_mode)\n    {\n    case FT_PIXEL_MODE_NONE:\n        break;\n\n    case FT_PIXEL_MODE_MONO:\n    {\n        uint32_t bsize = (width + 7) / 8;\n\n        if(bitmap.pitch >= 0)\n        {\n            for(uint32_t h = 0; h < height; ++h)\n            {\n                for(uint32_t b = 0; b < bsize; ++b)\n                {\n                    uint8_t block = bitmap.buffer[(static_cast<uint32_t>(bitmap.pitch) * ((height - 1) - h)) + b];\n                    uint32_t* dest = reinterpret_cast<uint32_t*>(&(image[((4 * width) * (height - 1 - h)) + (32 * b)]));\n                    for(int x = 0; x < 8 && (b * 8 + x) < width; x++)\n                    {\n                        if(block & (1 << (7 - x)))\n                            dest[x] = 0xFFFFFFFF;\n                        else\n                            dest[x] = 0;\n                    }\n                }\n            }\n        }\n        else if(bitmap.pitch < 0)\n        {\n            for(uint32_t h = 0; h < height; ++h)\n            {\n                for(uint32_t b = 0; b < bsize; ++b)\n                {\n                    uint8_t block = *(bitmap.buffer - (static_cast<uint32_t>(bitmap.pitch) * h) + b);\n                    uint32_t* dest = reinterpret_cast<uint32_t*>(&(image[((4 * width) * h) + (32 * b)]));\n                    for(int x = 0; x < 8 && (b * 8 + x) < width; x++)\n                    {\n                        if(block & (1 << (7 - x)))\n                            dest[x] = 0xFFFFFFFF;\n                        else\n                            dest[x] = 0;\n                    }\n                }\n            }\n        }\n        break;\n    }\n    case FT_PIXEL_MODE_GRAY:\n    {\n        if(bitmap.pitch >= 0)\n        {\n            for(uint32_t h = 0; h < height; ++h)\n            {\n                for(uint32_t w = 0; w < width; ++w)\n                {\n                    uint8_t color = bitmap.buffer[static_cast<uint32_t>(bitmap.pitch) * ((height - 1) - h) + w];\n                    size_t hp = (4 * width) * (height - 1 - h);\n                    uint32_t *dst = reinterpret_cast<uint32_t*>(image + hp + (4 * w));\n                    *dst = (uint32_t(color) << 0) |\n                           (uint32_t(color) << 8) |\n                           (uint32_t(color) << 16) |\n                           (uint32_t(color) << 24);\n                }\n            }\n        }\n        else if(bitmap.pitch < 0)\n        {\n            for(uint32_t h = 0; h < height; ++h)\n            {\n                for(uint32_t w = 0; w < width; ++w)\n                {\n                    uint8_t color = *(bitmap.buffer - (static_cast<uint32_t>(bitmap.pitch) * h) + w);\n                    size_t hp = (4 * width) * h;\n                    uint32_t *dst = reinterpret_cast<uint32_t*>(image + hp + (4 * w));\n                    *dst = (uint32_t(color) << 0) |\n                           (uint32_t(color) << 8) |\n                           (uint32_t(color) << 16) |\n                           (uint32_t(color) << 24);\n                }\n            }\n        }\n        break;\n    }\n\n    case FT_PIXEL_MODE_BGRA:\n    {\n        if(bitmap.pitch >= 0)\n        {\n            for(uint32_t h = 0; h < height; ++h)\n            {\n                for(uint32_t w = 0; w < width; ++w)\n                {\n                    uint8_t colour[4];\n                    SDL_memcpy(colour, bitmap.buffer + static_cast<uint32_t>(bitmap.pitch) * ((height - 1) - h) + w * 4, 4);\n                    size_t hp = (4 * width) * (height - 1 - h);\n                    uint32_t *dst = reinterpret_cast<uint32_t*>(image + hp + (4 * w));\n                    // Convert pre-multiplied to normal format\n                    *dst++ = colour[0] > 0 ? colour[2] / colour[0]: 0;\n                    *dst++ = colour[0] > 0 ? colour[1] / colour[0]: 0;\n                    *dst++ = colour[0] > 0 ? colour[0] / colour[0]: 0;\n                    *dst++ = colour[3];\n                }\n            }\n        }\n        else if(bitmap.pitch < 0)\n        {\n            for(uint32_t h = 0; h < height; ++h)\n            {\n                for(uint32_t w = 0; w < width; ++w)\n                {\n                    uint8_t colour[4];\n                    SDL_memcpy(colour, (bitmap.buffer - (static_cast<uint32_t>(bitmap.pitch) * h) + w * 4), 4);\n                    size_t hp = (4 * width) * h;\n                    uint32_t *dst = reinterpret_cast<uint32_t*>(image + hp + (4 * w));\n                    // Convert pre-multiplied to normal format\n                    *dst++ = colour[0] > 0 ? colour[2] / colour[0]: 0;\n                    *dst++ = colour[0] > 0 ? colour[1] / colour[0]: 0;\n                    *dst++ = colour[0] > 0 ? colour[0] / colour[0]: 0;\n                    *dst++ = colour[3];\n                }\n            }\n        }\n        break;\n    }\n\n    default:\n        pLogWarning(\"TtfFont::TheGlyph: FIXME: The pixel mode %d is not supported yet!\", bitmap.pixel_mode);\n        break;\n    }\n\n    if(m_doublePixel)\n    {\n        texture.w = width * 2;\n        texture.h = height * 2;\n    }\n    else\n    {\n        texture.w = width;\n        texture.h = height;\n    }\n\n    XRender::loadTexture(texture, width, height, image, pitch);\n\n    t_glyph.tx      = &texture;\n    t_glyph.info.width   = width;\n    t_glyph.info.height  = height;\n    t_glyph.info.left    = glyph->bitmap_left;\n    t_glyph.info.top     = glyph->bitmap_top;\n    t_glyph.info.advance_x = int32_t(glyph->advance.x);\n    t_glyph.info.advance_y = int32_t(glyph->advance.y);\n    t_glyph.info.metric_h_bearing_x = int32_t(glyph->metrics.horiBearingX);\n    t_glyph.info.metric_h_bearing_y = int32_t(glyph->metrics.horiBearingY);\n    t_glyph.info.metric_h_advance = int32_t(glyph->metrics.horiAdvance);\n    t_glyph.info.metric_v_bearing_x = int32_t(glyph->metrics.vertBearingX);\n    t_glyph.info.metric_v_bearing_y = int32_t(glyph->metrics.vertBearingY);\n    t_glyph.info.metric_v_advance = int32_t(glyph->metrics.vertAdvance);\n    t_glyph.info.metric_w = int32_t(glyph->metrics.width);\n    t_glyph.info.metric_h = int32_t(glyph->metrics.height);\n    t_glyph.info.glyph_width = glyph->advance.x;\n    t_glyph.info.glyph_height = glyph->advance.y;\n\n\n#ifdef DEBUG_BUILD\n    UTF8 glyph8[6] = {0, 0, 0, 0, 0, 0};\n    UTF8 *glyph8_ptr = static_cast<UTF8*>(glyph8);\n    const UTF32 glyph32[2] = {character, 0};\n    const UTF32 *glyph32_ptr1 = static_cast<const UTF32*>(glyph32);\n    PGEFF_ConvertUTF32toUTF8(&glyph32_ptr1, glyph32 + 1, &glyph8_ptr, glyph8 + 5, strictConversion);\n\n    D_pLogDebug(\"TTF Glyph info %s 0x%08\" PRIX32 \": \"\n        \"w=%\" PRIu32 \", h=%\" PRIu32 \", l=%\" PRId32 \" t=%\" PRId32 \", \"\n        \"ax=%\" PRId32 \", ay=%\" PRId32 \", \"\n        \"mhbx=%\" PRId32 \", mhby=%\" PRId32 \", mha=%\" PRId32 \", \"\n        \"mvbx=%\" PRId32 \", mvby=%\" PRId32 \", mva=%\" PRId32 \", \"\n        \"mw=%\" PRId32 \", mh=%\" PRId32 \", \"\n        \"gw=%ld, gh=%ld\",\n        glyph8,\n        static_cast<uint32_t>(character),\n        t_glyph.info.width,\n        t_glyph.info.height,\n        t_glyph.info.left,\n        t_glyph.info.top,\n        (t_glyph.info.advance_x >> 6),\n        (t_glyph.info.advance_y >> 6),\n        (t_glyph.info.metric_h_bearing_x >> 6),\n        (t_glyph.info.metric_h_bearing_y >> 6),\n        (t_glyph.info.metric_h_advance >> 6),\n        (t_glyph.info.metric_v_bearing_x >> 6),\n        (t_glyph.info.metric_v_bearing_y >> 6),\n        (t_glyph.info.metric_v_advance >> 6),\n        (t_glyph.info.metric_w >> 6),\n        (t_glyph.info.metric_h >> 6),\n        (t_glyph.info.glyph_width >> 6),\n        (t_glyph.info.glyph_height >> 6)\n    );\n#endif\n\n    delete [] image;\n\n    auto fSize = m_charMap.find(fontSize);\n    if(fSize == m_charMap.end())\n    {\n        m_charMap.insert({fontSize, CharMap()});\n        fSize = m_charMap.find(fontSize);\n        SDL_assert_release(fSize != m_charMap.end());\n    }\n\n    fSize->second.insert({character, t_glyph});\n    auto rc = fSize->second.find(character);\n    SDL_assert_release(rc != fSize->second.end());\n\n    return rc->second;\n}\n"
  },
  {
    "path": "src/fontman/ttf_font.h",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef TTF_FONT_H\n#define TTF_FONT_H\n\n\n#ifdef LOW_MEM\n#   include <map>\n#else\n#   include <unordered_map>\n#endif\n#include <Utils/vptrlist.h>\n#include \"std_picture.h\"\n\n#ifdef HAVE_STDINT_H\n#undef HAVE_STDINT_H //Avoid a warning about HAVE_STDINT_H's redefinition\n#endif\n#include <freetype2/ft2build.h>\n#include <freetype2/freetype/freetype.h>\n#include <freetype2/freetype/ftglyph.h>\n#include <freetype2/freetype/ftoutln.h>\n#include <freetype2/freetype/fttrigon.h>\n\n#include \"font_engine_base.h\"\n\n//! FreeType library descriptor\nextern FT_Library  g_ft;\n\nextern bool initializeFreeType();\nextern void closeFreeType();\n\n/**\n * @brief The FreeType-based font engine\n */\nclass TtfFont final : public BaseFontEngine\n{\npublic:\n    TtfFont();\n    TtfFont(const TtfFont &tf) = delete;\n    TtfFont &operator=(const TtfFont &tf) = delete;\n    TtfFont(TtfFont &&tf) = default;\n    virtual ~TtfFont() override;\n\n    bool loadFont(const std::string &path);\n    bool loadFont(const char* mem, size_t size);\n\n    void setAntiAlias(bool enable);\n    bool antiAlias() const;\n\n    void setBitmapSize(int size);\n    int bitmapSize() const;\n\n    void setDoublePixel(bool enable);\n    bool doublePixel() const;\n\n    void setLanguages(const std::string &langs);\n    bool hasLanguage(const std::string &lang) const;\n\n    /*!\n     * \\brief Get a size of one glyph\n     * \\param utf8char Pointer to one UTF-8 character\n     * \\param charNum Character number in the line (required to compute the size of the taboluation)\n     * \\param fontSize Size of TTF font glyph\n     * \\return Width and height of one glyph in pixels\n     */\n    PGE_Size glyphSize(const char *utf8char, uint32_t charNum = 0, uint32_t fontSize = 14) override;\n\n    /*!\n     * \\brief Print the multiline text block on the screen\n     * \\param text Multi-line text string\n     * \\param text_size The byte size of the text string to print\n     * \\param x Hotizontal screen position (at left-top corner of the block)\n     * \\param y Vertical screen position (at left-top corner of the block)\n     * \\param Red Red channel colour\n     * \\param Green Green channel colour\n     * \\param Blue Blue channel colour\n     * \\param Alpha Alpha channel level (does not issue render calls if Alpha is 0)\n     * \\param fontSize The size of the TTF font glyph\n     * \\param crop_info nullable pointer to info for crop (marquee) logic\n     * \\return Width and height of the text block in pixels\n     */\n    PGE_Size printText(const char *text, size_t text_size,\n                       int32_t x, int32_t y,\n                       XTColor color = XTColor(),\n                       uint32_t fontSize = 14,\n                       CropInfo* crop_info = nullptr) override;\n\n    bool isLoaded() const override;\n\n    void setFontName(const std::string &name);\n\n    std::string getFontName() const override;\n    FontType getFontType() const override;\n\n    /**\n     * @brief Draw a single glyph by x-y coordinates\n     * @param u8char Pointer to UTF8 multi-byte character\n     * @param x X position to draw\n     * @param y Y position do draw\n     * @param fontSize Size of font\n     * @param scaleSize Scale rendered texture\n     * @param drawOutlines Draw the outline at every glyph\n     * @param Red Red color level from 0.0 to 1.0\n     * @param Green Green color level from 0.0 to 1.0\n     * @param Blue Blue color level from 0.0 to 1.0\n     * @param Alpha Transparency level from 0.0 to 1.0\n     * @return Width of the glypth\n     */\n    uint32_t drawGlyph(const char* u8char,\n                       int32_t x, int32_t y, uint32_t fontSize, int scaleSize = 1,\n                       bool drawOutlines = false,\n                       XTColor color = XTColor(),\n                       XTColor OL_color = XTColor());\n\n    /**\n     * @brief Draw a single glyph relative to baseline offset\n     * @param u8char Pointer to UTF8 multi-byte character\n     * @param x X position to draw\n     * @param baseline_y Y position of the baseline to draw\n     * @param fontSize Size of font\n     * @param scaleSize Scale rendered texture\n     * @param drawOutlines Draw the outline at every glyph\n     * @param Red Red color level from 0.0 to 1.0\n     * @param Green Green color level from 0.0 to 1.0\n     * @param Blue Blue color level from 0.0 to 1.0\n     * @param Alpha Transparency level from 0.0 to 1.0\n     * @return Width of the glypth\n     */\n    uint32_t drawGlyphB(const char* u8char,\n                       int32_t x, int32_t baseline_y, uint32_t fontSize, int scaleSize = 1,\n                       bool drawOutlines = false,\n                       XTColor color = XTColor(),\n                       XTColor OL_color = XTColor());\n\n    struct TheGlyphInfo\n    {\n        TheGlyphInfo() = default;\n        TheGlyphInfo(const TheGlyphInfo &) = default;\n        TheGlyphInfo &operator=(const TheGlyphInfo &) = default;\n\n        uint32_t width  = 0;\n        uint32_t height = 0;\n        int32_t  left   = 0;\n        int32_t  top    = 0;\n        int32_t  advance_x = 0;\n        int32_t  advance_y = 0;\n        int32_t  metric_h_bearing_x = 0;\n        int32_t  metric_h_bearing_y = 0;\n        int32_t  metric_h_advance = 0;\n        int32_t  metric_v_bearing_x = 0;\n        int32_t  metric_v_bearing_y = 0;\n        int32_t  metric_v_advance = 0;\n        int32_t  metric_w = 0;\n        int32_t  metric_h = 0;\n        FT_Pos   glyph_width = 0;\n        FT_Pos   glyph_height = 0;\n    };\n\n    TheGlyphInfo getGlyphInfo(const char *u8char, uint32_t fontSize);\n\nprivate:\n    //! font is fine\n    bool m_isReady = false;\n    //! Handalable name of the font\n    std::string m_fontName;\n\n    //! Comma-separated list of languages that should bring this font being loaded first\n    std::string m_languages;\n\n    //! Recently used pixel size of the font\n    uint32_t     m_recentPixelSize = 10;\n\n    //! Loaded font face\n    FT_Face      m_face = nullptr;\n\n    //! Load anti-aliased glyphs\n    bool         m_enableAntialias = true;\n\n    //! Make pixelized glyphs: load them with a smaller size, and then, upscale them during a render\n    bool         m_doublePixel     = false;\n\n    //! Font face preferred bitmap size, 0 to disable\n    int          m_bitmapSize = 0;\n\n    struct TheGlyph\n    {\n        TheGlyph() = default;\n        StdPicture *tx     = nullptr;\n        TheGlyphInfo info;\n    };\n\n    //! Default dummy glyph\n    static const TheGlyph dummyGlyph;\n\n    const TheGlyph &getGlyph(uint32_t fontSize, char32_t character);\n\n    // version allocating a new texture\n    const TheGlyph &loadGlyph(uint32_t fontSize, char32_t character);\n\n    // version using an existing texture\n    const TheGlyph &loadGlyph(StdPicture &texture, uint32_t fontSize, char32_t character);\n\n#ifdef LOW_MEM\n    typedef std::map<char32_t, TheGlyph> CharMap;\n    typedef std::map<uint32_t, CharMap>  SizeCharMap;\n#else\n    typedef std::unordered_map<char32_t, TheGlyph> CharMap;\n    typedef std::unordered_map<uint32_t, CharMap>  SizeCharMap;\n#endif\n\n    SizeCharMap m_charMap;\n    VPtrList<StdPicture > m_texturesBank;\n};\n\n#endif // TTF_FONT_H\n"
  },
  {
    "path": "src/fontman/utf8_helpers.cpp",
    "content": "/*\n * Moondust, a free game engine for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include \"font_manager.h\"\n#include \"font_manager_private.h\"\n\nsize_t FontManager::utf8_strlen(const char *str)\n{\n    size_t size = 0;\n\n    while(str && *str != 0)\n    {\n        size_t  charLen = 1 + static_cast<size_t>(trailingBytesForUTF8[static_cast<UTF8>(*str)]);\n        size += charLen;\n        str  += charLen;\n    }\n\n    return size;\n}\n\nsize_t FontManager::utf8_strlen(const char *str, size_t len)\n{\n    size_t utf8_pos = 0;\n\n    while(str && len > 0)\n    {\n        size_t  charLen = 1 + static_cast<size_t>(trailingBytesForUTF8[static_cast<UTF8>(*str)]);\n        str  += charLen;\n        len  -= charLen;\n        utf8_pos++;\n    }\n\n    return utf8_pos;\n}\n\nsize_t FontManager::utf8_strlen(const std::string &str)\n{\n    return utf8_strlen(str.c_str(), str.size());\n}\n\nsize_t FontManager::utf8_substrlen(const std::string &str, size_t utf8len)\n{\n    size_t pos = 0;\n    size_t utf8_pos = 0;\n    const char *cstr = str.c_str();\n    size_t len = str.length();\n\n    while(cstr && len > 0)\n    {\n        size_t  charLen = 1 + static_cast<size_t>(trailingBytesForUTF8[static_cast<UTF8>(*cstr)]);\n        if(utf8_pos == utf8len)\n            return pos;\n        cstr += charLen;\n        pos  += charLen;\n        len  -= charLen;\n        utf8_pos++;\n    }\n\n    return pos;\n}\n\nstd::string FontManager::utf8_substr(const std::string &str, size_t utf8_begin, size_t utf8_len)\n{\n    size_t utf8_pos = 0;\n    const char *cstr = str.c_str();\n    size_t len = str.length();\n    std::string out;\n\n    while(cstr && (len > 0) && (utf8_len > 0))\n    {\n        size_t  charLen = 1 + static_cast<size_t>(trailingBytesForUTF8[static_cast<UTF8>(*cstr)]);\n        if(utf8_pos >= utf8_begin)\n        {\n            out.append(cstr, charLen);\n            utf8_len--;\n        }\n\n        cstr += charLen;\n        len  -= charLen;\n        utf8_pos++;\n    }\n\n    return out;\n}\n\nconst char* FontManager::utf8_skip_begin(const char *str, size_t utf8_begin)\n{\n    size_t utf8_pos = 0;\n    const char *cstr = str;\n    const char *out = cstr;\n\n    while(cstr && *cstr != 0)\n    {\n        size_t  charLen = 1 + static_cast<size_t>(trailingBytesForUTF8[static_cast<UTF8>(*cstr)]);\n        if(utf8_pos >= utf8_begin)\n            out = cstr;\n        cstr += charLen;\n        utf8_pos++;\n    }\n\n    return out;\n}\n\nvoid FontManager::utf8_pop_back(std::string &str)\n{\n    size_t pos = 0;\n    const char*cstr = str.c_str();\n    size_t len = str.length();\n\n    while(cstr && len > 0)\n    {\n        size_t  charLen = 1 + static_cast<size_t>(trailingBytesForUTF8[static_cast<UTF8>(*cstr)]);\n        len  -= charLen;\n        if(len == 0)\n        {\n            str.erase(str.begin() + static_cast<std::string::difference_type>(pos), str.end());\n            return;\n        }\n\n        cstr += charLen;\n        pos  += charLen;\n    }\n}\n\nvoid FontManager::utf8_erase_at(std::string &str, size_t begin)\n{\n    size_t pos = 0;\n    size_t utf8_pos = 0;\n    const char*cstr = str.c_str();\n    size_t len = str.length();\n\n    while(cstr && len > 0)\n    {\n        size_t  charLen = 1 + static_cast<size_t>(trailingBytesForUTF8[static_cast<UTF8>(*cstr)]);\n        if(utf8_pos == begin)\n        {\n            str.erase(str.begin() + static_cast<std::string::difference_type>(pos), str.end());\n            return;\n        }\n\n        cstr += charLen;\n        pos  += charLen;\n        len  -= charLen;\n        utf8_pos++;\n    }\n}\n\nvoid FontManager::utf8_erase_before(std::string &str, size_t end)\n{\n    size_t pos = 0;\n    size_t utf8_pos = 0;\n    const char*cstr = str.c_str();\n    size_t len = str.length();\n\n    while(cstr && len > 0)\n    {\n        size_t  charLen = 1 + static_cast<size_t>(trailingBytesForUTF8[static_cast<UTF8>(*cstr)]);\n        if(utf8_pos == end)\n        {\n            str.erase(str.begin(), str.begin() + static_cast<std::string::difference_type>(pos) + 1);\n            return;\n        }\n\n        cstr += charLen;\n        pos  += charLen;\n        len  -= charLen;\n        utf8_pos++;\n    }\n}\n"
  },
  {
    "path": "src/frame_timer.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <inttypes.h>\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_timer.h\"\n\n#include <fmt_format_ne.h>\n#include <Logger/logger.h>\n#include \"pge_delay.h\"\n\n#include \"frame_timer.h\"\n#include \"globals.h\"\n#include \"config.h\"\n#include \"graphics.h\"\n#include \"message.h\"\n#include \"core/render.h\"\n#include \"core/events.h\"\n\n#include \"npc/npc_queues.h\"\n\n// heap info for min console ports\n#if defined(__16M__) || defined(__3DS__) // || defined(__WII__)\n    #define STATS_SHOW_RAM\n    #include <malloc.h>\n#endif // #if defined(__16M__) || defined(__3DS__) || defined(__WII__)\n\n// VRAM info for 16M\n#ifdef __16M__\n    namespace XRender\n    {\n        extern uint32_t s_loadedVRAM;\n    }\n#endif\n\n// VRAM info for 3DS\n#ifdef __3DS__\n    #include <3ds.h>\n    extern u32 __ctru_linear_heap_size;\n#endif\n\n// heap size for Wii\n#if defined(STATS_SHOW_RAM) && defined(__WII__)\n    extern u8 __Arena1Lo[], __Arena1Hi[];\n    extern u8 __Arena2Lo[], __Arena2Hi[];\n    static const uint32_t s_heap_size = (__Arena2Hi - __Arena2Lo) + (__Arena1Hi - __Arena1Lo);\n// heap size for other homebrew\n#elif defined(STATS_SHOW_RAM)\n    extern u8 *fake_heap_start;\n    extern u8 *fake_heap_end;\n    static const uint32_t s_heap_size = (fake_heap_end - fake_heap_start);\n#endif\n\nMicroStats g_microStats;\nPerformanceStats_t g_stats;\n\nvoid MicroStats::reset()\n{\n    for(uint8_t i = 0; i < TASK_END; i++)\n    {\n        level_timer[i] = 0;\n        view_timer[i] = 0;\n        m_cur_timer[i] = 0;\n    }\n\n    view_total = 0;\n\n    m_slow_frame_time = 0;\n    m_frame_time = 0;\n\n    m_cur_task = TASK_END;\n    m_cur_frame = 0;\n}\n\nvoid MicroStats::start_task(Task task)\n{\n    uint64_t next_time = SDL_GetMicroTicks();\n\n    if(m_cur_task < TASK_END)\n    {\n        m_cur_timer[m_cur_task] += next_time - m_cur_time;\n        level_timer[m_cur_task] += next_time - m_cur_time;\n        m_frame_time += next_time - m_cur_time;\n    }\n\n    m_cur_time = next_time;\n    m_cur_task = task;\n}\n\nvoid MicroStats::start_sleep()\n{\n    start_task(TASK_END);\n}\n\nvoid MicroStats::end_frame()\n{\n    start_task(TASK_END);\n\n    m_cur_frame++;\n    m_level_frame++;\n\n    if(m_frame_time > m_slow_frame_time)\n        m_slow_frame_time = m_frame_time;\n\n    m_frame_time = 0;\n\n    if(m_cur_frame == 66)\n    {\n        m_cur_frame = 0;\n        view_total = 0;\n\n        for(uint8_t i = 0; i < TASK_END; i++)\n        {\n            view_timer[i] = (m_cur_timer[i] + 500) / 1000;\n            view_total += (m_cur_timer[i] + 500) / 1000;\n            m_cur_timer[i] = 0;\n        }\n\n        view_slow_frame_time = (m_slow_frame_time * 66 + 500) / 1000;\n        m_slow_frame_time = 0;\n    }\n}\n\nvoid PerformanceStats_t::next_page()\n{\n    if((XRender::TargetW >= 720 && XRender::TargetH >= 360) || (LevelSelect && !GameMenu))\n        page = !page;\n    else\n        page = (page + 1) % (GameMenu ? 4 : 5);\n\n#ifndef STATS_SHOW_RAM\n    if(page == 3)\n        page++;\n#endif\n}\n\nvoid PerformanceStats_t::reset()\n{\n   renderedBlocks = 0;\n   renderedBGOs = 0;\n   renderedNPCs = 0;\n   renderedEffects = 0;\n\n   checkedBlocks = 0;\n   checkedBGOs = 0;\n\n   renderedTiles = 0;\n   renderedScenes = 0;\n   renderedPaths = 0;\n   renderedLevels = 0;\n\n   checkedTiles = 0;\n   checkedScenes = 0;\n   checkedPaths = 0;\n   checkedLevels = 0;\n}\n\n#define YLINE (y + 2 + (row++ * 18))\n\n#ifdef STATS_SHOW_RAM\nstatic void s_print_ram(int x, int y)\n{\n    int row = 0;\n\n    int items = 1; // RAM\n\n#   ifndef __WII__\n    items++; // VRAM\n#   endif\n\n    XRender::renderRect(x, y, 340, 12 + (18 * items), XTColorF(0.0_n, 0.0_n, 0.0_n, 0.3_n), true);\n\n    if(x < 200)\n        y += 6;\n\n    auto m = mallinfo();\n\n#   ifdef __16M__\n    SuperPrint(fmt::sprintf_ne(\" RAM: %4zu/%4\" PRIu32 \" kb\", m.uordblks / 1024, s_heap_size / 1024),\n               3, x + 4, YLINE);\n    SuperPrint(fmt::sprintf_ne(\"VRAM: %4\" PRIu32 \"/%4d kb\", XRender::s_loadedVRAM / 1024, 512),\n               3, x + 4, YLINE, XTColorF(0.5_n, 1.0_n, 0.5_n));\n#   else\n    SuperPrint(fmt::sprintf_ne(\" RAM: %5zu / %5\" PRIu32 \" kb\", m.uordblks / 1024, s_heap_size / 1024),\n               3, x + 4, YLINE);\n#   endif\n\n#   ifdef __3DS__\n    SuperPrint(fmt::sprintf_ne(\"VRAM: %5\" PRIu32 \"/%5\" PRIu32 \" kb\", (__ctru_linear_heap_size - linearSpaceFree()) / 1024, __ctru_linear_heap_size / 1024),\n               3, x + 4, YLINE, XTColorF(0.5_n, 1.0_n, 0.5_n));\n#   endif\n}\n#else\nstatic void s_print_ram(int, int) {}\n#endif // #ifdef STATS_SHOW_RAM\n\nvoid PerformanceStats_t::print_filenames(int x, int y)\n{\n    int items = (GameMenu) ? 4 : 3;\n    int row = 0;\n\n    XRender::renderRect(x, y, 745, 6 + (18 * items), XTColorF(0.0_n, 0.0_n, 0.0_n, 0.3_n), true);\n\n    SuperPrint(fmt::sprintf_ne(\"FILE: %s\", FileNameFull.empty() ? \"<none>\" : FileNameFull.c_str()),\n               3, x + 4, YLINE, XTColorF(1.0_n, 0.5_n, 1.0_n));\n    SuperPrint(fmt::sprintf_ne(\"MUSK: %s\", currentMusic.empty() ? \"<none>\" : currentMusic.c_str()),\n               3, x + 4, YLINE, XTColorF(1.0_n, 0.5_n, 1.0_n));\n    SuperPrint(fmt::sprintf_ne(\"MUSF: %s\", currentMusicFile.empty() ? \"<none>\" : currentMusicFile.c_str()),\n               3, x + 4, YLINE, XTColorF(1.0_n, 0.5_n, 1.0_n));\n\n    if(GameMenu)\n    {\n        SuperPrint(fmt::sprintf_ne(\"MENU-MODE: %d\", MenuMode),\n                   3, x + 4, YLINE, XTColorF(0.5_n, 1.0_n, 1.0_n));\n    }\n}\n\nvoid PerformanceStats_t::print_obj_stats(int x, int y)\n{\n    int items = 5;\n    int row = 0;\n\n    XRender::renderRect(x, y, 340, 6 + (18 * items), XTColorF(0.0_n, 0.0_n, 0.0_n, 0.3_n), true);\n\n    SuperPrint(fmt::sprintf_ne(\"   DRAW/ACTV/TOTAL\"),\n        3, x + 4, YLINE, XTColorF(1.0_n, 1.0_n, 1.0_n));\n\n    SuperPrint(fmt::sprintf_ne(\"B: %04d/%04d/%05d\", renderedBlocks, checkedBlocks, numBlock),\n        3, x + 4, YLINE, XTColorF(0.5_n, 1.0_n, 1.0_n));\n    SuperPrint(fmt::sprintf_ne(\"G: %04d/%04d/%05d\", renderedBGOs, checkedBGOs, numBackground),\n        3, x + 4, YLINE, XTColorF(0.5_n, 1.0_n, 1.0_n));\n    SuperPrint(fmt::sprintf_ne(\"N: %04d/%04d/%05d\", renderedNPCs, (int)NPCQueues::Active.no_change.size(), numNPCs),\n        3, x + 4, YLINE, XTColorF(0.5_n, 1.0_n, 1.0_n));\n    SuperPrint(fmt::sprintf_ne(\"E: %04d/%04d\", renderedEffects, numEffects),\n        3, x + 4, YLINE, XTColorF(0.5_n, 1.0_n, 1.0_n));\n}\n\nvoid PerformanceStats_t::print_cpu_stats(int x, int y)\n{\n    int items = 6;\n\n    XRender::renderRect(x, y, 340, 6 + (18 * items), XTColorF(0.0_n, 0.0_n, 0.0_n, 0.3_n), true);\n\n    SuperPrint(fmt::sprintf_ne(\"CPU: %05dms/s\",\n                               g_microStats.view_total),\n               3, x + 24, y + 2);\n\n    SuperPrint(fmt::sprintf_ne(\"%s %04d\\n%s %04d\\n%s %04d\\n%s %04d\\n%s %04d\",\n                               g_microStats.task_names[0], g_microStats.view_timer[0],\n                               g_microStats.task_names[1], g_microStats.view_timer[1],\n                               g_microStats.task_names[2], g_microStats.view_timer[2],\n                               g_microStats.task_names[3], g_microStats.view_timer[3],\n                               g_microStats.task_names[4], g_microStats.view_timer[4]),\n               3, x + 4, y + 2 + 18, XTColorF(1.0_n, 1.0_n, 0.5_n));\n\n    SuperPrint(fmt::sprintf_ne(\"%s %04d\\n%s %04d\\n%s %04d\\n%s %04d\\n%s %04d\",\n                               g_microStats.task_names[5], g_microStats.view_timer[5],\n                               g_microStats.task_names[6], g_microStats.view_timer[6],\n                               g_microStats.task_names[7], g_microStats.view_timer[7],\n                               g_microStats.task_names[8], g_microStats.view_timer[8],\n                               g_microStats.task_names[9], g_microStats.view_timer[9]),\n               3, x + 164, y + 2 + 18, XTColorF(1.0_n, 1.0_n, 0.5_n));\n}\n\nvoid PerformanceStats_t::print()\n{\n    if(page == 0)\n        return;\n\n    if(LevelSelect && !GameMenu)\n    {\n        int items = 5;\n        XRender::renderRect(6 + XRender::TargetOverscanX, 6, 745, 6 + (18 * items), XTColorF(0.0_n, 0.0_n, 0.0_n, 0.3_n), true);\n\n        int y = 6;\n        int row = 0;\n\n        SuperPrint(fmt::sprintf_ne(\"FILE: %s\", FileNameFull.empty() ? \"<none>\" : FileNameFull.c_str()),\n                   3, 10 + XRender::TargetOverscanX, YLINE, XTColorF(0.5_n, 1.0_n, 1.0_n));\n        SuperPrint(fmt::sprintf_ne(\"MUSK: %s\", currentMusic.empty() ? \"<none>\" : currentMusic.c_str()),\n                   3, 10 + XRender::TargetOverscanX, YLINE, XTColorF(0.5_n, 1.0_n, 1.0_n));\n        SuperPrint(fmt::sprintf_ne(\"MUSF: %s\", currentMusicFile.empty() ? \"<none>\" : currentMusicFile.c_str()),\n                   3, 10 + XRender::TargetOverscanX, YLINE, XTColorF(0.5_n, 1.0_n, 1.0_n));\n\n        SuperPrint(fmt::sprintf_ne(\"DRAW: T=%03d S=%03d P=%03d L=%03d, SUM=%03d\",\n                                   renderedTiles, renderedScenes, renderedPaths, renderedLevels,\n                                   (renderedTiles + renderedScenes + renderedPaths + renderedLevels)),\n                   3, 10 + XRender::TargetOverscanX, YLINE);\n        SuperPrint(fmt::sprintf_ne(\"CHEK: T=%03d S=%03d P=%03d L=%03d, SUM=%03d\",\n                                   checkedTiles, checkedScenes, checkedPaths, checkedLevels,\n                                   (checkedTiles + checkedScenes + checkedPaths + checkedLevels)),\n                   3, 10 + XRender::TargetOverscanX, YLINE);\n\n        s_print_ram(10 + XRender::TargetOverscanX, YLINE);\n    }\n    else if(XRender::TargetW >= 720 && XRender::TargetH >= 360)\n    {\n        // threshold of 720\n        int next_y = 6;\n        print_filenames(6 + XRender::TargetOverscanX, next_y);\n\n        next_y += (GameMenu) ? 6 + 18 * 4 : 6 + 18 * 3;\n\n        if(!GameMenu)\n        {\n            print_cpu_stats(6 + XRender::TargetOverscanX, next_y);\n            print_obj_stats(6 + 340 + XRender::TargetOverscanX, next_y);\n            s_print_ram(6 + XRender::TargetOverscanX, next_y + 6 + 18 * 6);\n        }\n        else\n        {\n            print_obj_stats(6 + XRender::TargetOverscanX, next_y);\n            s_print_ram(6 + XRender::TargetOverscanX, next_y + 6 + 18 * 5);\n        }\n    }\n    else if(page == 1)\n        print_filenames(6 + XRender::TargetOverscanX, 6);\n    else if(page == 3)\n        s_print_ram(6 + XRender::TargetOverscanX, 6);\n    else if(page == 4 && !GameMenu)\n        print_cpu_stats(6 + XRender::TargetOverscanX, 6);\n    else\n        print_obj_stats(6 + XRender::TargetOverscanX, 6);\n}\n\n#undef YLINE\n\n//#if !defined(__EMSCRIPTEN__)\n#define USE_NEW_TIMER\n#define USE_NEW_FRAMESKIP\n//#endif\n\n#ifdef USE_NEW_TIMER\n#define COMPUTE_FRAME_TIME_1_REAL computeFrameTime1Real_2\n#define COMPUTE_FRAME_TIME_2_REAL computeFrameTime2Real_2\n#else\n#define COMPUTE_FRAME_TIME_1_REAL computeFrameTime1Real\n#define COMPUTE_FRAME_TIME_2_REAL computeFrameTime2Real\n#endif\n\n\n#ifdef USE_NEW_TIMER\n\n#define ONE_MILLIARD 1000000000\n\ntypedef int64_t nanotime_t;\n\nstatic inline nanotime_t getNanoTime()\n{\n    return static_cast<nanotime_t>(SDL_GetTicks()) * 1000000;\n}\n\nstatic inline nanotime_t getElapsedTime(nanotime_t oldTime)\n{\n    return getNanoTime() - oldTime;\n}\n\nstatic inline nanotime_t getSleepTime(nanotime_t oldTime, nanotime_t target)\n{\n    return target - getElapsedTime(oldTime);\n}\n\nstatic inline void xtech_nanosleep(nanotime_t sleepTime)\n{\n    if(sleepTime <= 0)\n        return;\n    PGE_Delay((uint32_t)((sleepTime + 999999) / 1000000));\n}\n\n\nstruct TimeStore\n{\n    const size_t size = 4;\n    size_t       pos = 0;\n    nanotime_t   sum = 0;\n    nanotime_t   items[4] = {0, 0, 0, 0};\n\n    void add(nanotime_t item)\n    {\n        sum -= items[pos];\n        sum += item;\n        items[pos] = item;\n        pos = (pos + 1) % size;\n    }\n\n    nanotime_t average()\n    {\n        return sum / size;\n    }\n};\n\nstatic TimeStore         s_overheadTimes;\nstatic const  nanotime_t c_frameRateNano = 1000000000.0 / 64.1025;\nstatic nanotime_t        s_oldTime = 0,\n                         s_overhead = 0;\n#ifdef USE_NEW_FRAMESKIP\nstatic nanotime_t        s_startProcessing = 0;\nstatic nanotime_t        s_stopProcessing = 0;\nstatic nanotime_t        s_doUpdate = 0;\n#endif\n\n#endif\n// ----------------------------------------------------\n\n//Public Const frameRate As Double = 15 'for controlling game speed\n//const int frameRate = 15;\nstatic const  int64_t c_frameRate = 15;\n\nstatic int64_t s_overTime = 0;\nstatic int64_t s_goalTime = 0;\nstatic int64_t s_fpsCount = 0;\nstatic int64_t s_fpsTime = 0;\nstatic int    s_cycleCount = 0;\nstatic int64_t s_gameTime = 0;\nstatic int64_t s_currentTicks = 0;\n\n\nvoid resetFrameTimer()\n{\n    s_overTime = 0;\n    s_fpsCount = 0;\n    s_fpsTime = 0;\n    s_cycleCount = 0;\n    s_gameTime = 0;\n#ifdef USE_NEW_FRAMESKIP\n    s_doUpdate = 0;\n    s_goalTime = 0;\n#else\n    s_goalTime = SDL_GetTicks() + 1000;\n#endif\n    // D_pLogDebugNA(\"Time counter reset was called\");\n}\n\nvoid resetTimeBuffer()\n{\n    s_currentTicks = 0;\n}\n\n#ifdef USE_NEW_FRAMESKIP\n\nbool frameSkipNeeded()\n{\n    return s_doUpdate > 0;\n}\n\n#else\n\nbool frameSkipNeeded() // Old and buggy Redigit's\n{\n    return SDL_GetTicks() + SDL_floor(1000 * (1 - (s_cycleCount / 63.0))) > s_goalTime;\n}\n\n#endif\n\nvoid frameNextInc()\n{\n    s_fpsCount += 1;\n}\n\nvoid cycleNextInc()\n{\n    s_cycleCount++;\n}\n\nextern void CheckActive(); // game_main.cpp\n\nstatic inline bool canProcessFrameCond()\n{\n    bool ret = s_currentTicks >= s_gameTime + c_frameRate || s_currentTicks < s_gameTime || g_config.unlimited_framerate;\n#ifdef USE_NEW_FRAMESKIP\n    if(ret && s_doUpdate <= 0)\n        s_startProcessing = getNanoTime();\n#endif\n    return ret;\n}\n\nbool canProceedFrame()\n{\n    s_currentTicks = SDL_GetTicks();\n    return canProcessFrameCond();\n}\n\n#ifndef USE_NEW_TIMER\nstatic inline void computeFrameTime1Real()\n{\n    if(s_fpsCount >= 32000)\n        s_fpsCount = 0; // Fixes Overflow bug\n\n    if(s_cycleCount >= 32000)\n        s_cycleCount = 0; // Fixes Overflow bug\n\n    s_overTime += (s_currentTicks - (s_gameTime + c_frameRate));\n\n    if(s_gameTime == 0.0)\n        s_overTime = 0;\n\n    if(s_overTime <= 1)\n        s_overTime = 0;\n    else if(s_overTime > 1000)\n        s_overTime = 1000;\n\n    s_gameTime = s_currentTicks - s_overTime;\n    s_overTime -= (s_currentTicks - s_gameTime);\n}\n\nstatic inline void computeFrameTime2Real()\n{\n    if(SDL_GetTicks() > s_fpsTime)\n    {\n        if(s_cycleCount >= 65)\n        {\n            s_overTime = 0;\n            s_gameTime = s_currentTicks;\n        }\n        s_cycleCount = 0;\n        s_fpsTime = SDL_GetTicks() + 1000;\n        s_goalTime = s_fpsTime;\n        //      if(Debugger == true)\n        //          frmLevelDebugger.lblFPS = fpsCount;\n\n        // if(g_config.show_fps)\n        PrintFPS = s_fpsCount;\n\n        s_fpsCount = 0;\n    }\n}\n#endif\n\n\n#ifdef USE_NEW_TIMER\nstatic inline void computeFrameTime1Real_2()\n{\n    if(s_fpsCount >= 32000)\n        s_fpsCount = 0; // Fixes Overflow bug\n\n    if(s_cycleCount >= 32000)\n        s_cycleCount = 0; // Fixes Overflow bug\n}\n\nstatic inline void computeFrameTime2Real_2()\n{\n#ifdef USE_NEW_FRAMESKIP\n    if(s_doUpdate > 0)\n        s_doUpdate -= c_frameRateNano;\n\n    s_startProcessing = 0;\n    s_stopProcessing = 0;\n#endif\n\n    if(SDL_GetTicks() > s_fpsTime)\n    {\n        if(s_cycleCount >= 65)\n        {\n            s_overTime = 0;\n            s_gameTime = s_currentTicks;\n        }\n\n        s_cycleCount = 0;\n        s_fpsTime = SDL_GetTicks() + 1000;\n        s_goalTime = s_fpsTime;\n\n        // if(g_config.show_fps)\n        PrintFPS = s_fpsCount;\n\n        s_fpsCount = 0;\n    }\n\n    if(!g_config.unlimited_framerate)\n    {\n        nanotime_t start = getNanoTime();\n        nanotime_t sleepTime = getSleepTime(s_oldTime, c_frameRateNano);\n        s_overhead = s_overheadTimes.average();\n\n        // D_pLogDebug(\"ST=%lld, OH=%lld\", sleepTime, s_overhead);\n        if(sleepTime > s_overhead)\n        {\n            nanotime_t adjustedSleepTime = sleepTime - s_overhead;\n            if(adjustedSleepTime > 500000000)\n            {\n                pLogWarning(\"frame_timer: Adjusted sleep time got a too big value: %ld\", (long)adjustedSleepTime);\n                adjustedSleepTime = 500000000;\n            }\n\n            xtech_nanosleep(adjustedSleepTime);\n            auto e = getElapsedTime(start);\n            nanotime_t overslept = e - adjustedSleepTime;\n            // SDL_assert(overslept >= 0);\n            if(overslept < 0)\n                s_overheadTimes.add(0);\n            else if(overslept < c_frameRateNano)\n                s_overheadTimes.add(overslept);\n        }\n    }\n\n    s_oldTime = getNanoTime();\n}\n#endif\n\n\nvoid computeFrameTime1()\n{\n    COMPUTE_FRAME_TIME_1_REAL();\n}\n\nvoid computeFrameTime2()\n{\n    COMPUTE_FRAME_TIME_2_REAL();\n}\n\nvoid runFrameLoop(LoopCall_t doLoopCallbackPre,\n                  LoopCall_t doLoopCallbackPost,\n                  std::function<bool(void)> condition,\n                  std::function<bool ()> subCondition,\n                  std::function<void ()> preTimerExtraPre,\n                  std::function<void ()> preTimerExtraPost)\n{\n    do\n    {\n        if(preTimerExtraPre)\n            preTimerExtraPre();\n\n        if(XMessage::GetStatus() != XMessage::Status::replay)\n            XEvents::doEvents();\n\n        s_currentTicks = SDL_GetTicks();\n\n        if(preTimerExtraPost)\n            preTimerExtraPost();\n\n        if(canProcessFrameCond())\n        {\n            CheckActive();\n            if(doLoopCallbackPre)\n                doLoopCallbackPre();\n\n            COMPUTE_FRAME_TIME_1_REAL();\n\n            if(doLoopCallbackPost)\n                doLoopCallbackPost(); // Run the loop callback\n\n            if(XMessage::GetStatus() != XMessage::Status::replay)\n                XEvents::doEvents();\n\n            COMPUTE_FRAME_TIME_2_REAL();\n\n            if(subCondition && subCondition())\n                break;\n        }\n\n        if(!g_config.unlimited_framerate)\n            PGE_Delay(1);\n\n        if(!GameIsActive)\n            break;// Break on quit\n    }\n    while(condition());\n}\n\nvoid frameRenderStart()\n{\n#ifdef USE_NEW_FRAMESKIP\n    if(s_doUpdate <= 0)\n        s_startProcessing = getNanoTime();\n#endif\n}\n\nvoid frameRenderEnd()\n{\n#ifdef USE_NEW_FRAMESKIP\n    if(s_doUpdate <= 0)\n    {\n        s_stopProcessing = getNanoTime();\n        nanotime_t newTime = g_config.enable_frameskip ? (s_stopProcessing - s_startProcessing): 0;\n        // D_pLogDebug(\"newTime/nano=%lld (%lld)\", newTime/c_frameRateNano, newTime / 1000000);\n        if(newTime > c_frameRateNano * 25) // Limit 25 frames being skipped maximum\n        {\n            D_pLogDebug(\"frame_timer: Overloading detected: %lld frames to skip (%lld milliseconds delay)\", (long long)newTime / c_frameRateNano, (long long)newTime / 1000000);\n            newTime = c_frameRateNano * 25;\n        }\n\n        s_doUpdate += newTime * 300 / 166;\n        s_goalTime = double(SDL_GetTicks() + (newTime / 1000000));\n    }\n#endif\n}\n"
  },
  {
    "path": "src/frame_timer.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef FRAME_TIMER_H\n#define FRAME_TIMER_H\n\n#include <cstdint>\n#include <functional>\n\nstruct MicroStats\n{\npublic:\n    enum Task\n    {\n        Script,\n        Controls,\n        Layers,\n        NPCs,\n        Blocks,\n        Effects,\n        Player,\n        Camera,\n        Graphics,\n        Events,\n        Sound,\n        TASK_END\n    };\n\n    const char* const task_names[TASK_END] =\n    {\n        \"Sct\",\n        \"Ctl\",\n        \"Lay\",\n        \"NPC\",\n        \"Blk\",\n        \"Eff\",\n        \"Plr\",\n        \"Cam\",\n        \"Gfx\",\n        \"Evt\",\n        \"Snd\",\n    };\n\nprivate:\n    uint8_t m_cur_task = TASK_END;\n    uint8_t m_cur_frame = 0;\n    uint64_t m_level_frame = 0;\n    uint64_t m_cur_time = 0;\n    uint64_t m_cur_timer[TASK_END] = {0};\n\n    uint64_t m_frame_time = 0;\n    uint64_t m_slow_frame_time = 0;\n\npublic:\n    uint64_t level_timer[TASK_END] = {0};\n    int view_timer[TASK_END] = {0};\n    int view_total = 0;\n    int view_slow_frame_time = 0;\n\n    void reset();\n    void start_task(Task task);\n    void start_sleep();\n    void end_frame();\n\n};\n\nstruct PerformanceStats_t\n{\n    // How many objects got drawn in one frame\n    int renderedBlocks = 0;\n    // int renderedSzBlocks = 0; // combined with normal blocks\n    int renderedBGOs = 0;\n    int renderedNPCs = 0;\n    int renderedEffects = 0;\n\n    // How many objects got scanned to find what to render\n    int checkedBlocks = 0;\n    // int checkedSzBlocks = 0;\n    int checkedBGOs = 0;\n    // int checkedNPCs = 0; // use active NPCs\n    // int checkedEffects = 0; // use total effects\n\n    int renderedTiles = 0;\n    int renderedScenes = 0;\n    int renderedPaths = 0;\n    int renderedLevels = 0;\n\n    int checkedTiles = 0;\n    int checkedScenes = 0;\n    int checkedPaths = 0;\n    int checkedLevels = 0;\n\n    int page = 0;\n\n    // Displays title of the music OR filename\n    std::string currentMusic;\n    std::string currentMusicFile;\n\n    void next_page();\n\n    void reset();\n    void print_filenames(int x, int y);\n    void print_obj_stats(int x, int y);\n    void print_cpu_stats(int x, int y);\n    // void print_ram_stats();\n    void print();\n};\n\nextern MicroStats g_microStats;\nextern PerformanceStats_t g_stats;\n\nvoid resetFrameTimer();\nvoid resetTimeBuffer();\nbool frameSkipNeeded();\n\ntypedef void (*LoopCall_t)(void);\n\nbool canProceedFrame();\nvoid computeFrameTime1();\nvoid computeFrameTime2();\nvoid frameNextInc();\nvoid cycleNextInc();\n\nvoid frameRenderStart();\nvoid frameRenderEnd();\n\nvoid runFrameLoop(LoopCall_t doLoopCallbackPre,\n                  LoopCall_t doLoopCallbackPost,\n                  std::function<bool ()> condition,\n                  std::function<bool ()> subCondition = nullptr,\n                  std::function<void ()> preTimerExtraPre = nullptr,\n                  std::function<void ()> preTimerExtraPost = nullptr);\n\n#endif // FRAME_TIMER_H\n"
  },
  {
    "path": "src/frm_main.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n\n#if defined(__WII__) || defined(__3DS__) || !defined(RENDER_CUSTOM)\n#   include <Graphics/graphics_funcs.h>\n#endif\n\n#include \"../version.h\"\n\n#include \"gfx.h\"\n#include \"config.h\"\n#ifdef RENDER_FULLSCREEN_TYPES_SUPPORTED\n#   include \"change_res.h\"\n#endif\n\n#include \"core/render.h\"\n#include \"core/window.h\"\n#include \"core/msgbox.h\"\n#include \"core/events.h\"\n\n#ifndef THEXTECH_NO_SDL_CORE\n#   include \"core/sdl/sdl_core.h\"\n#endif\n\n#ifdef CORE_EVERYTHING_SDL\n\n#   include \"core/sdl/render_sdl.h\"\n#   include \"core/opengl/render_gl.h\"\n\n#   define USE_CORE_RENDER_SDL\n\n#   include \"core/sdl/window_sdl.h\"\ntypedef WindowSDL WindowUsed;\n#   define USE_CORE_WINDOW_SDL\n\n#   ifdef __WIIU__\n#       include \"core/wiiu/msgbox_wiiu.h\"\ntypedef MsgBoxWiiU MsgBoxUsed;\n#       define SDL_CORE_MSGBOX_WIIU\n#   else\n#       include \"core/sdl/msgbox_sdl.h\"\ntypedef MsgBoxSDL MsgBoxUsed;\n#       define SDL_CORE_MSGBOX_SDL\n#   endif\n\n#   include \"core/sdl/events_sdl.h\"\ntypedef EventsSDL EventsUsed;\n#   define USE_CORE_EVENTS_SDL\n#endif\n\n#include \"fontman/font_manager.h\"\n\n#include \"frm_main.h\"\n\nFrmMain g_frmMain;\n\nbool FrmMain::initSystem(const CmdLineSetup_t &setup)\n{\n    bool res = false;\n\n    LoadLogSettings(setup.interprocess, setup.verboseLogging);\n    //Write into log the application start event\n    pLogInfo(\"<Application started>\");\n    pLogInfo(\"TheXTech %s (branch %s, commit %s)\", V_LATEST_STABLE, V_BUILD_BRANCH, V_BUILD_VER);\n\n#ifndef THEXTECH_NO_SDL_CORE\n    res = CoreSDL::init(setup);\n    if(!res)\n        return false;\n#endif\n\n#if defined(__WII__) || defined(__3DS__) || !defined(RENDER_CUSTOM)\n    //Initialize FreeImage\n    D_pLogDebugNA(\"FrmMain: Loading FreeImage...\");\n    GraphicsHelps::initFreeImage();\n#endif\n\n    // Build interfaces\n#ifndef WINDOW_CUSTOM\n    WindowUsed *window = new WindowUsed();\n    m_window.reset(window);\n    g_window = m_window.get();\n#endif\n\n#ifdef USE_CORE_RENDER_SDL\n\n\n#   ifdef RENDERGL_SUPPORTED\n    bool try_gl = false;\n\n    if(    g_config.render_mode == Config_t::RENDER_ACCELERATED_AUTO\n        || g_config.render_mode == Config_t::RENDER_ACCELERATED_OPENGL\n        || g_config.render_mode == Config_t::RENDER_ACCELERATED_OPENGL_ES\n        || g_config.render_mode == Config_t::RENDER_ACCELERATED_OPENGL_LEGACY\n        || g_config.render_mode == Config_t::RENDER_ACCELERATED_OPENGL_ES_LEGACY)\n    {\n        RenderGL *render = new RenderGL();\n        m_render.reset(render);\n        g_render = m_render.get();\n        try_gl = true;\n    }\n    else\n#   endif // #ifdef RENDERGL_SUPPORTED\n\n    {\n        RenderSDL *render = new RenderSDL();\n        m_render.reset(render);\n        g_render = m_render.get();\n    }\n\n#endif\n\n#ifndef MSGBOX_CUSTOM\n    MsgBoxUsed *msgbox = new MsgBoxUsed();\n    m_msgbox.reset(msgbox);\n    g_msgBox = m_msgbox.get();\n#endif\n\n#ifndef EVENTS_CUSTOM\n    EventsUsed *events = new EventsUsed();\n    m_events.reset(events);\n    g_events = m_events.get();\n#endif\n\n    // Initializing window\n\n#ifdef WINDOW_CUSTOM\n    D_pLogDebugNA(\"FrmMain: Loading XWindow...\");\n    res = XWindow::init();\n#elif defined(USE_CORE_WINDOW_SDL)\n    res = window->initSDL(g_render->SDL_InitFlags());\n#else\n#   error \"FIXME: Implement supported window initialization here\"\n#endif\n\n    if(!res)\n    {\n#ifndef THEXTECH_NO_SDL_CORE\n        CoreSDL::quit();\n#endif\n        return true;\n    }\n\n\n    // Initializing message box\n\n#ifdef MSGBOX_CUSTOM\n    D_pLogDebugNA(\"FrmMain: Loading XMsgBox...\");\n    res &= XMsgBox::init();\n#elif defined(USE_CORE_WINDOW_SDL) && defined(SDL_CORE_MSGBOX_SDL)\n    msgbox->init(window->getWindow());\n#elif defined(SDL_CORE_MSGBOX_WIIU)\n    // Nothing to do for Wii U\n#else\n#   error \"FIXME: Implement supported message boxes initialization here\"\n#endif\n\n\n\n    // Initializing events\n\n#ifdef EVENTS_CUSTOM\n    D_pLogDebugNA(\"FrmMain: Loading XEvents...\");\n    res &= XEvents::init();\n#else\n    events->init(this);\n#endif\n\n\n#if defined(_WIN32) && !defined(RENDER_FULLSCREEN_ALWAYS) && defined(RENDER_FULLSCREEN_TYPES_SUPPORTED)\n    if(g_config.fullscreen_type == Config_t::FULLSCREEN_TYPE_EXCLUSIVE)\n    {\n        XWindow::setFullScreenType(g_config.fullscreen_type);\n        if(g_config.fullscreen)\n        {\n            UpdateInternalRes();\n            XWindow::setWindowSize(XRender::TargetW, XRender::TargetH);\n            XWindow::setFullScreen(g_config.fullscreen);\n        }\n    }\n#endif\n\n    // Initializing render\n    pLogDebug(\"Init renderer settings...\");\n\n#ifdef RENDER_CUSTOM\n    D_pLogDebugNA(\"FrmMain: Loading XRender...\");\n    res &= XRender::init();\n#elif defined(USE_CORE_WINDOW_SDL) && defined(USE_CORE_RENDER_SDL)\n    res = g_render->initRender(window->getWindow());\n\n#   ifdef RENDERGL_SUPPORTED\n    if(try_gl && !res)\n    {\n        pLogDebug(\"FrmMain: closing Render GL\");\n        m_render->clearAllTextures();\n        m_render->close();\n\n        msgbox->close();\n        window->close();\n\n        m_render.reset();\n        g_render = nullptr;\n\n        pLogDebug(\"FrmMain: retrying with Render SDL layer...\");\n\n        RenderSDL *render = new RenderSDL();\n        m_render.reset(render);\n        g_render = m_render.get();\n\n        // make new window\n        res = window->initSDL(g_render->SDL_InitFlags());\n\n        if(res)\n        {\n            msgbox->init(window->getWindow());\n            res = g_render->initRender(window->getWindow());\n        }\n    }\n#   endif // #ifdef RENDERGL_SUPPORTED\n\n#else\n#   error \"FIXME: Implement supported render initialization here\"\n#endif\n\n    if(!res)\n    {\n        D_pLogCriticalNA(\"FrmMain: Error has occured, exiting...\");\n        freeSystem();\n        return true;\n    }\n\n#ifdef RENDER_FULLSCREEN_ALWAYS // Use a full-screen on Android & PS Vita mode by default\n    XWindow::setFullScreen(true);\n    XWindow::show();\n#else\n#   ifdef RENDER_FULLSCREEN_TYPES_SUPPORTED\n    WindowSDL::setHasFrameBuffer(m_render->hasFrameBuffer());\n    XWindow::setFullScreenType(g_config.fullscreen_type);\n    XWindow::setFullScreen(g_config.fullscreen);\n#   endif\n#   ifdef _WIN32\n    XWindow::show();\n#   endif\n#endif\n\n    XEvents::doEvents();\n\n    return !res;\n}\n\nvoid FrmMain::freeSystem()\n{\n    FontManager::quit();\n\n    GFX.unLoad();\n\n#ifdef RENDER_CUSTOM\n    XRender::quit();\n#else\n    if(m_render)\n    {\n        m_render->clearAllTextures();\n        m_render->close();\n    }\n\n    m_render.reset();\n    g_render = nullptr;\n#endif\n\n#ifdef MSGBOX_CUSTOM\n    XMsgBox::quit();\n#else\n    if(m_msgbox)\n        m_msgbox->close();\n\n    m_msgbox.reset();\n    g_msgBox = nullptr;\n#endif\n\n#ifdef EVENTS_CUSTOM\n    XEvents::quit();\n#else\n    m_events.reset();\n    g_events = nullptr;\n#endif\n\n#ifdef WINDOW_CUSTOM\n    XWindow::quit();\n#else\n    if(m_window)\n        m_window->close();\n\n    m_window.reset();\n    g_window = nullptr;\n#endif\n\n#if defined(__WII__) || defined(__3DS__) || !defined(RENDER_CUSTOM)\n    GraphicsHelps::closeFreeImage();\n#endif\n\n    pLogInfo(\"<Application closed>\");\n    CloseLog();\n\n#ifndef THEXTECH_NO_SDL_CORE\n    CoreSDL::quit();\n#endif\n}\n\nbool FrmMain::restartRenderer()\n{\n#ifndef THEXTECH_RESTART_RENDERER_SUPPORTED\n    return false;\n#else // #ifndef THEXTECH_RESTART_RENDERER_SUPPORTED\n    pLogDebug(\"FrmMain: attempting to restart XRender...\");\n\n    bool res;\n\n#    ifdef RENDER_CUSTOM\n    // custom renderer\n    XRender::quit();\n\n    res = XRender::init();\n\n#    elif defined(RENDERGL_SUPPORTED)\n\n    if(m_render)\n    {\n        m_render->clearAllTextures();\n        m_render->close();\n    }\n\n    g_msgBox->close();\n    g_window->close();\n\n    m_render.reset();\n    g_render = nullptr;\n\n    bool try_gl = false;\n\n    CmdLineSetup_t setup;\n\n    if(g_config.render_mode == Config_t::RENDER_ACCELERATED_AUTO || g_config.render_mode == Config_t::RENDER_ACCELERATED_OPENGL || g_config.render_mode == Config_t::RENDER_ACCELERATED_OPENGL_ES || g_config.render_mode == Config_t::RENDER_ACCELERATED_OPENGL_LEGACY || g_config.render_mode == Config_t::RENDER_ACCELERATED_OPENGL_ES_LEGACY)\n    {\n        m_render.reset(new RenderGL());\n        try_gl = true;\n    }\n    else\n    {\n        m_render.reset(new RenderSDL());\n    }\n\n    g_render = m_render.get();\n\n    res = reinterpret_cast<WindowUsed*>(g_window)->initSDL(g_render->SDL_InitFlags());\n\n    if(res)\n    {\n        reinterpret_cast<MsgBoxUsed*>(g_msgBox)->init(reinterpret_cast<WindowUsed*>(g_window)->getWindow());\n        res = m_render->initRender(reinterpret_cast<WindowUsed*>(g_window)->getWindow());\n    }\n\n    if(try_gl && !res)\n    {\n        pLogDebug(\"FrmMain: closing Render GL\");\n        m_render->clearAllTextures();\n        m_render->close();\n\n        g_msgBox->close();\n        g_window->close();\n\n        m_render.reset();\n        g_render = nullptr;\n\n        pLogDebug(\"FrmMain: retrying with Render SDL layer...\");\n\n        RenderSDL *render = new RenderSDL();\n        m_render.reset(render);\n        g_render = m_render.get();\n\n        res = reinterpret_cast<WindowUsed*>(g_window)->initSDL(g_render->SDL_InitFlags());\n\n        if(res)\n        {\n            reinterpret_cast<MsgBoxUsed*>(g_msgBox)->init(reinterpret_cast<WindowUsed*>(g_window)->getWindow());\n            res = g_render->initRender(reinterpret_cast<WindowUsed*>(g_window)->getWindow());\n        }\n\n        res = false;\n    }\n\n#    else\n    // SDL, no OpenGL\n\n    if(m_render)\n    {\n        m_render->clearAllTextures();\n        m_render->close();\n    }\n\n    m_render.reset();\n    g_render = nullptr;\n\n    m_render.reset(new RenderSDL());\n\n    g_render = m_render.get();\n\n    CmdLineSetup_t setup;\n\n    res = m_render->initRender(setup, reinterpret_cast<WindowUsed*>(g_window)->getWindow());\n#    endif\n\n    XWindow::show();\n\n    return res;\n#endif // #ifndef THEXTECH_RESTART_RENDERER_SUPPORTED\n}\n"
  },
  {
    "path": "src/frm_main.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef FRMMAIN_H\n#define FRMMAIN_H\n\n#include <memory>\n#include <string>\n\n#include \"cmd_line_setup.h\"\n\n#ifndef RENDER_CUSTOM\n#include \"core/base/render_base.h\"\n#endif\n\n#ifndef RENDER_CUSTOM\n#include \"core/base/window_base.h\"\n#endif\n\n#ifndef MSGBOX_CUSTOM\n#include \"core/base/msgbox_base.h\"\n#endif\n\n#ifndef EVENTS_CUSTOM\n#include \"core/base/events_base.h\"\n#endif\n\nclass FrmMain\n{\n#ifndef WINDOW_CUSTOM\n    std::unique_ptr<AbstractWindow_t> m_window;\n#endif\n\n#ifndef RENDER_CUSTOM\n    std::unique_ptr<AbstractRender_t> m_render;\n#endif\n\n#ifndef MSGBOX_CUSTOM\n    std::unique_ptr<AbstractMsgBox_t> m_msgbox;\n#endif\n\n#ifndef EVENTS_CUSTOM\n    std::unique_ptr<AbstractEvents_t> m_events;\n#endif\n\npublic:\n    FrmMain() noexcept = default;\n    ~FrmMain() = default;\n\n    bool initSystem(const CmdLineSetup_t &setup);\n    void freeSystem();\n\n    bool restartRenderer();\n};\n\nextern FrmMain g_frmMain;\n\n#endif // FRMMAIN_H\n"
  },
  {
    "path": "src/game_main.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef PGE_NO_THREADING\n#include <SDL2/SDL_atomic.h>\n#include <SDL2/SDL_thread.h>\n#endif\n\n#ifdef __WIIU__\n#include <sysapp/launch.h>\n#endif\n\n#if defined(THEXTECH_ASSERTS_INGAME_MESSAGE) && !defined(THEXTECH_NO_SDL_BUILD)\n#   include <CrashHandler/crash_handler.h>\n#   include <signal.h>\n#endif\n\n#include <Logger/logger.h>\n#include <Utils/files.h>\n#include <Utils/strings.h>\n#include <Archives/archives.h>\n#include <AppPath/app_path.h>\n#include <Integrator/integrator.h>\n#include <PGE_File_Formats/file_formats.h>\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n#   include <InterProcess/intproc.h>\n#endif\n#include <pge_delay.h>\n#include <fmt_format_ne.h>\n#include <sorting/tinysort.h>\n\n#ifdef ENABLE_XTECH_LUA\n#include \"xtech_lua_main.h\"\n#endif\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n#include \"main/client_methods.h\"\n#endif\n\n#include \"sdl_proxy/sdl_timer.h\"\n\n#include \"globals.h\"\n#include \"game_main.h\"\n#include \"gfx.h\"\n\n#include \"config.h\"\n#include \"message.h\"\n#include \"frame_timer.h\"\n#include \"blocks.h\"\n#include \"change_res.h\"\n#include \"collision.h\"\n#include \"effect.h\"\n#include \"eff_id.h\"\n#include \"graphics.h\"\n#include \"layers.h\"\n#include \"load_gfx.h\"\n#include \"player.h\"\n#include \"sound.h\"\n#include \"editor.h\"\n#include \"editor/new_editor.h\"\n#include \"custom.h\"\n#include \"main/world_globals.h\"\n#include \"main/cheat_code.h\"\n#include \"main/game_globals.h\"\n#include \"main/level_file.h\"\n#include \"main/world_file.h\"\n#include \"main/speedrunner.h\"\n#include \"main/menu_main.h\"\n#include \"main/game_info.h\"\n#include \"main/outro_loop.h\"\n#include \"editor/editor_strings.h\"\n#include \"main/game_strings.h\"\n#include \"main/translate.h\"\n#include \"main/record.h\"\n#include \"main/asset_pack.h\"\n#include \"core/render.h\"\n#include \"core/window.h\"\n#include \"core/events.h\"\n#include \"core/msgbox.h\"\n#include \"core/language.h\"\n#include \"script/luna/luna.h\"\n#include \"fontman/font_manager.h\"\n\n#include \"pseudo_vb.h\"\n\n#include \"controls.h\"\n\n#include \"main/screen_connect.h\"\n#include \"main/screen_quickreconnect.h\"\n#include \"main/screen_asset_pack.h\"\n#include \"main/game_loop_interrupt.h\"\n\n#include \"main/level_medals.h\"\n\n#include \"main/trees.h\"\n\nbool g_ShortDelay = false;\n\nvoid CheckActive();\n// set up sizable blocks\nvoid SizableBlocks();\n\n// game_main_setupphysics.cpp\n\nstatic int loadingThread(void *waiter_ptr)\n{\n#ifndef PGE_NO_THREADING\n    auto *waiter = (SDL_atomic_t *)waiter_ptr;\n#else\n    UNUSED(waiter_ptr);\n#endif\n\n    LoaderUpdateDebugString(g_gameStrings.loaderStatusGameInfo);\n    initGameInfo();\n    cheats_reset();\n    ConfigReloadRecentEpisodes();\n\n    LoaderUpdateDebugString(g_gameStrings.loaderStatusTranslations);\n    XLanguage::findLanguages(); // find present translations\n    ReloadTranslations(); // load translations\n\n    LoaderUpdateDebugString(g_gameStrings.loaderStatusAssetPacks);\n    GetAssetPacks();\n\n    SetupPhysics(); // Setup Physics\n    SetupGraphics(); // setup graphics\n//    Load GFX 'load the graphics form\n//    GFX.load(); // load the graphics form // Moved to before sound load\n    SizableBlocks();\n    LoadGFX(); // load the graphics from file\n\n    Controls::LoadTouchScreenGFX();\n\n    SetupVars(); //Setup Variables\n\n#ifdef THEXTECH_PRELOAD_LEVELS\n    LoaderUpdateDebugString(\"Worlds preload\");\n    FindWorlds();\n    LoaderUpdateDebugString(\"Levels preload\");\n    FindLevels();\n#endif\n\n    InitSound(); // Setup sound effects\n\n    LoaderUpdateDebugString(g_gameStrings.loaderStatusFinishing, true);\n    UpdateLoad();\n\n#ifndef PGE_NO_THREADING\n    if(waiter)\n        SDL_AtomicSet(waiter, 0);\n#endif\n\n    return 0;\n}\n\nstatic std::string getIntrFile()\n{\n    auto introPath = AppPath + \"intro.lvlx\";\n\n    if(!Files::fileExists(introPath))\n        introPath = AppPath + \"intro.lvl\";\n\n    if(!Files::fileExists(introPath))\n        return std::string();\n\n    pLogDebug(\"Found root intro level file: %s\", introPath.c_str());\n\n    return introPath;\n}\n\n/**\n * @brief Choice randomly one of level from the directory\n * @param out The selection path (absolute path)\n * @param dir Directory with levels, must end with /\n * @param rootIntro The fallback absolute path to level if introset not exists or empty\n * @return true if choice was done, otherwise, nothing\n */\nstatic bool choiceFromIntroset(std::string &out, const std::string &dir, const std::string &rootIntro = std::string())\n{\n    DirMan introSet(dir);\n    std::vector<std::string> intros;\n\n    if(!introSet.exists() || !introSet.getListOfFiles(intros, {\".lvl\", \"lvlx\"}) || intros.empty())\n    {\n        if(rootIntro.empty())\n            return false;\n        else\n        {\n            out = rootIntro;\n            return true;\n        }\n    }\n\n    tinysort(intros.begin(), intros.end());\n\n    for(auto &i : intros)\n    {\n        i.insert(0, dir);\n        pLogDebug(\"Found introset intro level: %s\", i.c_str());\n    }\n\n    // The final choice\n    out = intros[iRand2T(intros.size())];\n\n    return true;\n}\n\nstatic std::string findIntroLevel()\n{\n    std::string choice;\n\n    // Try to find custom intros\n    if(!g_recentWorldIntro.empty())\n    {\n        if(DirMan::exists(g_recentWorldIntro)) // If path is a directory\n        {\n            if(g_recentWorldIntro.back() != '/')\n                g_recentWorldIntro.push_back('/');\n\n            if(choiceFromIntroset(choice, g_recentWorldIntro))\n                return choice;\n        }\n        else if(Files::fileExists(g_recentWorldIntro))\n            return g_recentWorldIntro;\n\n        // When found nothing\n        Strings::dealloc(g_recentWorldIntro);\n    }\n\n    // Find default set\n    choiceFromIntroset(choice, AppPath + \"introset/\", getIntrFile());\n\n    return choice;\n}\n\n// mount an archive used in an episode (or level)'s path\nstatic std::string s_prepare_episode_path(const std::string& path)\n{\n    std::string target_path = path;\n\n    // parse and mount assets if possible\n    if(target_path[0] == '@')\n    {\n        // mount target, then replace path\n        auto archive_end = target_path.begin();\n        // don't check for path end until after the first slash\n        while(archive_end != target_path.end() && *archive_end != '/' && *archive_end != '\\\\')\n            ++archive_end;\n        while(archive_end != target_path.end() && *archive_end != ':')\n            ++archive_end;\n\n        if(archive_end != target_path.end())\n        {\n            *archive_end = '\\0';\n\n            if(Archives::mount_episode(target_path.c_str() + 1))\n            {\n                target_path.erase(target_path.begin() + 2, archive_end + 1);\n                target_path[0] = ':';\n                target_path[1] = 'e';\n            }\n            else\n                *archive_end = ':';\n        }\n    }\n\n    return target_path;\n}\n\n// expand the section vertically if the top 8px of the level are empty\nstatic void s_ExpandSectionForMenu()\n{\n    SpeedlessLocation_t& menu_section = level[0];\n\n    // check current section top for expandability\n    Location_t tempLocation = newLoc(menu_section.X, menu_section.Y, menu_section.Width - menu_section.X, 8);\n\n    for(int A : treeBlockQuery(tempLocation, SORTMODE_NONE))\n    {\n        if(CheckCollision(Block[A].Location, tempLocation))\n            return;\n    }\n\n    for(int A : treeBackgroundQuery(tempLocation, SORTMODE_NONE))\n    {\n        if(CheckCollision(Background[A].Location, tempLocation))\n            return;\n    }\n\n    // expand level height to a maximum of 2160px\n    if(menu_section.Y > menu_section.Height - 2160)\n        menu_section.Y = menu_section.Height - 2160;\n}\n\nvoid ReportLoadFailure(const std::string& filename, bool isIPC)\n{\n#ifdef THEXTECH_ENABLE_SDL_NET\n    XMessage::Disconnect();\n#endif\n#if !defined(THEXTECH_INTERPROC_SUPPORTED)\n    UNUSED(isIPC);\n#endif\n\n    g_MessageType = MESSAGE_TYPE_SYS_WARNING;\n\n    // temporarily store error code from load process in MessageTitle string\n    std::swap(MessageText, MessageTitle);\n#if defined(THEXTECH_INTERPROC_SUPPORTED)\n    MessageText = isIPC ? g_gameStrings.errorOpenIPCDataFailed : fmt::format_ne(g_gameStrings.errorOpenFileFailed, filename);\n#else\n    MessageText = fmt::format_ne(g_gameStrings.errorOpenFileFailed, filename);\n#endif\n\n    // add error code from load process\n    if(!MessageTitle.empty())\n    {\n        MessageText += '\\n';\n        MessageText += '\\n';\n        MessageText += MessageTitle;\n        MessageTitle.clear();\n    }\n\n    PauseGame(PauseCode::Message);\n}\n\nvoid MainLoadAll()\n{\n    LoadingInProcess = true;\n\n    if(g_AssetsLoaded)\n    {\n        StopAllSounds();\n        StopMusic();\n\n        UnloadSound();\n        UnloadGFX(true);\n    }\n\n    if(ScreenAssetPack::g_LoopActive)\n        PlayInitSound();\n\n    if(FontManager::isInitied())\n        FontManager::quit();\n\n    LoaderInit();\n\n    LoaderUpdateDebugString(\"Fonts\");\n\n    FontManager::initFull();\n\n#ifndef PGE_NO_THREADING\n    {\n        gfxLoaderThreadingMode = true;\n\n        SDL_Thread*     loadThread;\n        SDL_atomic_t    loadWaiter;\n        int             loadWaiterState = 1;\n        int             threadReturnValue;\n\n        SDL_AtomicSet(&loadWaiter, loadWaiterState);\n        loadThread = SDL_CreateThread(loadingThread, \"Loader\", &loadWaiter);\n\n        if(!loadThread)\n        {\n            gfxLoaderThreadingMode = false;\n            pLogCritical(\"Failed to create the loading thread! Do running the load directly\");\n            loadingThread(nullptr);\n        }\n        else\n        {\n            do\n            {\n                UpdateLoadREAL();\n                PGE_Delay(15);\n                loadWaiterState = SDL_AtomicGet(&loadWaiter);\n            } while(loadWaiterState);\n\n            SDL_WaitThread(loadThread, &threadReturnValue);\n            pLogDebug(\"Loading thread was exited with %d code.\", threadReturnValue);\n\n            // Draw last frame\n            UpdateLoadREAL();\n        }\n    }\n#else\n    loadingThread(nullptr);\n    UpdateLoad();\n#endif\n\n    Integrator::setGameName(g_gameInfo.title, g_gameInfo.statusIconName);\n    XWindow::setTitle(g_gameInfo.titleWindow().c_str());\n    XWindow::updateWindowIcon();\n\n    LoaderFinish();\n    LoadingInProcess = false;\n\n    g_AssetsLoaded = true;\n}\n\n\n#if defined(THEXTECH_ASSERTS_INGAME_MESSAGE) && !defined(THEXTECH_NO_SDL_BUILD)\nstatic SDL_AssertState ingame_assert_sdl_handler(const SDL_AssertData *data, void *)\n{\n    CrashHandler::logAssertInfo(data);\n    g_MessageType = MESSAGE_TYPE_SYS_FATAL_ASSERT;\n    MessageTitle = \"Fatal error!\";\n    MessageText =  fmt::sprintf_ne(\"Assertion condition has failed:\\n\"\n                                   \"\\n\"\n                                   \"File: %s(%d)\\n\"\n                                   \"Function: %s\\n\"\n                                   \"Condition: %s\\n\"\n                                   \"\\n\"\n                                   \"Game will be closed.\\n\\n\"\n                                   \"See log for details:\\n\"\n                                   \"%s\",\n                                   data->filename, data->linenum,\n                                   data->function,\n                                   data->condition,\n                                   getLogFilePath().c_str());\n    PauseGame(PauseCode::Message);\n\n    return SDL_ASSERTION_ABORT;\n}\n\nstatic void ingame_crash_msg_handler(const std::string &title, const std::string &message)\n{\n    CloseLog(); // Before this, last log message was written, so, no more logs will be printed\n    g_MessageType = MESSAGE_TYPE_SYS_FATAL_ASSERT;\n    MessageTitle = \"Fatal error!\";\n    signal(SIGABRT, SIG_DFL);\n    MessageText =  fmt::sprintf_ne(\"%s\\n\"\n                                  \"\\n\"\n                                  \"%s\\n\\n\"\n                                  \"Game will be closed.\\n\\n\"\n                                  \"See log for details:\\n\"\n                                  \"%s\",\n                                  title.c_str(),\n                                  message.c_str(),\n                                  getLogFilePath().c_str());\n    PauseGame(PauseCode::Message);\n}\n#endif\n\n\nint GameMain(const CmdLineSetup_t &setup)\n{\n    Player_t blankPlayer;\n//    int A = 0;\n//    int B = 0;\n//    int C = 0;\n    bool tempBool = false;\n    int lastWarpEntered = 0;\n\n//    LB = \"\\n\";\n//    EoT = \"\";\n\n    // moved into MainLoadAll\n    // cheats_reset();\n\n    // [ !Here was a starting dialog! ]\n\n    //    frmLoader.Show 'show the Splash screen\n    //    Do\n    //        DoEvents\n    //    Loop While StartMenu = False 'wait until the player clicks a button\n\n    // Set global SMBX64 behaviour at PGE-FL\n    FileFormats::SetSMBX64LvlFlags(FileFormats::F_SMBX64_KEEP_LEGACY_NPC_IN_BLOCK_CODES);\n\n    // StartMenu = true;\n    MenuMode = MENU_INTRO;\n\n    // strings and translation initialization moved into MainLoadAll\n#if 0\n    initOutroContent();\n    initMainMenu();\n    initEditorStrings();\n    initGameStrings();\n\n    if(!CurrentLanguage.empty())\n    {\n        XTechTranslate translator;\n        if(translator.translate())\n        {\n            pLogDebug(\"Loaded translation for language %s-%s\",\n                      CurrentLanguage.c_str(),\n                      CurrentLangDialect.empty() ? \"??\" : CurrentLangDialect.c_str());\n        }\n    }\n#endif\n\n    initAll();\n\n    UpdateInternalRes();\n\n//    Unload frmLoader\n    gfxLoaderTestMode = setup.testLevelMode;\n\n    // find asset pack and load required UI graphics\n    bool init_failure = !InitUIAssetsFrom(setup.assetPack);\n\n    if(init_failure)\n        FontManager::initFallback();\n\n//    If LevelEditor = False Then\n//        frmMain.Show // Show window a bit later\n//    XWindow::show();\n//        GameMenu = True\n    GameMenu = true;\n//    Else\n//        frmSplash.Show\n//        BlocksSorted = True\n//    End If\n\n    LoadingInProcess = true;\n\n    XEvents::doEvents();\n\n#ifdef __EMSCRIPTEN__ // Workaround for a recent Chrome's policy to avoid sudden sound without user's interaction\n    if(!init_failure)\n        FontManager::initFull();\n\n    XWindow::show(); // Don't show window until playing an initial sound\n\n    while(!SharedCursor.Primary)\n    {\n        XRender::setTargetTexture();\n        XRender::clearBuffer();\n        SuperPrintScreenCenter(\"Click to start a game\", 5, XRender::TargetH / 2 - 10);\n        XRender::repaint();\n        XRender::setTargetScreen();\n        XEvents::doEvents();\n        Controls::Update(false);\n        PGE_Delay(10);\n    }\n#endif\n\n    InitMixerX();\n\n#ifndef PGE_NO_THREADING\n    gfxLoaderThreadingMode = true;\n#endif\n    XWindow::show(); // Don't show window until playing an initial sound\n\n#if defined(THEXTECH_ASSERTS_INGAME_MESSAGE) && !defined(THEXTECH_NO_SDL_BUILD)\n    SDL_SetAssertionHandler(&ingame_assert_sdl_handler, NULL);\n    CrashHandler::setCrashMsgBoxHook(&ingame_crash_msg_handler);\n#endif\n\n    if(!setup.testLevelMode && !init_failure)\n        PlayInitSound();\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n    if(setup.interprocess)\n        IntProc::init();\n#endif\n\n    Integrator::initIntegrations();\n\n    // want to go directly to game content\n    bool cmdline_content = (!setup.testLevel.empty() || !setup.testReplay.empty() || setup.interprocess);\n\n    // special case: go straight to asset pack menu\n    if(g_config.pick_assets_on_start && !cmdline_content && setup.assetPack.empty() && GetAssetPacks().size() > 1)\n    {\n        FontManager::initFull();\n        Controls::LoadTouchScreenGFX();\n        GameMenu = false;\n        ScreenAssetPack::g_LoopActive = true;\n    }\n    // normal case: load everything and go to menu\n    else if(!init_failure)\n        MainLoadAll();\n\n    LevelSelect = true; // world map is to be shown\n\n    LoadingInProcess = false;\n\n    // Clear the screen\n    XRender::setTargetTexture();\n    XRender::clearBuffer();\n    XRender::repaint();\n    XEvents::doEvents();\n\n    if(!g_config.background_work && !XWindow::hasWindowInputFocus())\n        SoundPauseEngine(1);\n\n    if(init_failure)\n    {\n        GameMenu = false;\n        LevelSelect = false;\n        gSMBXHUDSettings.skip = true;\n        numPlayers = 1;\n        InitScreens();\n        Screens_AssignPlayer(1, *l_screen);\n        QuickReconnectScreen::g_active = true;\n\n        g_MessageType = MESSAGE_TYPE_SYS_ERROR;\n        MessageTitle = \"Fatal: assets not found\";\n        MessageText = \"Please extract a game/asset pack to:\\n\";\n\n        for(auto& i : AppPathManager::assetsSearchPath())\n        {\n            MessageText += \"\\n\";\n            MessageText += i.first;\n\n            if(i.second == AssetsPathType::Legacy)\n                MessageText += \"assets/<pack-id>/\";\n            else if(i.second == AssetsPathType::Multiple)\n                MessageText += \"<pack-id>/\";\n        }\n\n        if(!setup.assetPack.empty())\n        {\n            MessageText = \"Could not load cmdline-requested assets: \";\n            MessageText += setup.assetPack;\n        }\n\n        PauseGame(PauseCode::Message);\n        GracefulQuit();\n        return 0;\n    }\n    else if(cmdline_content) // Start level testing immediately!\n    {\n        bool is_world = (Files::hasSuffix(setup.testLevel, \".wld\") || Files::hasSuffix(setup.testLevel, \".wldx\"));\n\n        GameMenu = false;\n        LevelSelect = is_world;\n\n        if(!setup.testReplay.empty())\n            Record::LoadReplay(setup.testReplay, setup.testLevel);\n        else\n            FullFileName = setup.testLevel;\n\n        if(setup.testBattleMode && !is_world)\n        {\n            numPlayers = 2;\n            BattleMode = true;\n            BattleIntro = 150;\n        }\n        else\n        {\n            numPlayers = setup.testNumPlayers;\n            BattleMode = false;\n            BattleIntro = 0;\n        }\n\n        if(!is_world)\n        {\n            GodMode = setup.testGodMode;\n            GrabAll = setup.testGrabAll;\n\n            if(GodMode || GrabAll)\n                Cheater = true;\n        }\n\n        editorScreen.ResetCursor();\n\n        if(setup.testReplay.empty() && setup.testEditor)\n        {\n            editorScreen.active = false;\n            MouseRelease = false;\n            LevelEditor = true;\n            WorldEditor = is_world;\n            OpenLevel(FullFileName);\n            editorScreen.ResetCursor();\n            EditorBackup();\n        }\n        else if(is_world)\n        {\n            LoadSingleWorld(setup.testLevel);\n\n            selWorld = 1;\n\n            if(SelectWorld[selWorld].WorldFilePath.empty())\n            {\n                LevelSelect = false;\n                TestLevel = true;\n                EndLevel = false;\n\n                ReportLoadFailure(setup.testLevel);\n                ErrorQuit = true;\n            }\n            else\n            {\n                if(setup.testSave >= 0 && setup.testSave <= maxSaveSlots)\n                {\n                    selSave = setup.testSave;\n                    FindSaves();\n                }\n\n                if(numPlayers < 1)\n                    numPlayers = 1;\n\n                for(int A = 1; A <= numCharacters; A++)\n                    blockCharacter[A] = (g_forceCharacter) ? false : SelectWorld[selWorld].blockChar[A];\n\n                // prepare for StartEpisode(): set player characters\n                for(int i = 0; i < numPlayers; i++)\n                {\n                    if(testPlayer[i + 1].Character != 0)\n                        l_screen->charSelect[i] = testPlayer[i + 1].Character;\n                    else\n                        l_screen->charSelect[i] = i + 1;\n\n                    // replace blocked characters\n                    if(blockCharacter[l_screen->charSelect[i]])\n                    {\n                        for(int new_char = 1; new_char <= numCharacters; new_char++)\n                        {\n                            // check it's unblocked\n                            if(blockCharacter[new_char])\n                                continue;\n\n                            // check no other player has the character first\n                            int j = 0;\n                            for(; j < i; j++)\n                            {\n                                if(l_screen->charSelect[j] == new_char)\n                                    break;\n                            }\n\n                            // if loop ended naturally, character is unused\n                            if(j == i)\n                            {\n                                l_screen->charSelect[i] = new_char;\n                                break;\n                            }\n                        }\n                    }\n\n                    if(i <= (int)Controls::g_InputMethods.size())\n                        Controls::g_InputMethods.push_back(nullptr);\n                }\n\n                QuickReconnectScreen::g_active = true;\n\n                StartEpisode();\n            }\n        }\n        else\n        {\n            zTestLevel(setup.testMagicHand, setup.interprocess);\n\n            if(!LevelName.empty())\n                Integrator::setLevelName(LevelName);\n            else\n                Integrator::setLevelName(FileName);\n        }\n    }\n\n    while(GameIsActive)\n    {\n        SyncSysCursorDisplay();\n\n        if(ScreenAssetPack::g_LoopActive)\n        {\n            // make sure that controllers can connect properly\n            numPlayers = maxLocalPlayers;\n\n            // Run the frame-loop\n            runFrameLoop(&ScreenAssetPack::Loop,\n                         nullptr,\n                        []()->bool{ return ScreenAssetPack::g_LoopActive;}, nullptr,\n                        nullptr,\n                        nullptr);\n        }\n        else if(LevelEditor) // Load the level editor\n        {\n            // if(resChanged)\n            //     ChangeScreen();\n            // BattleMode = false;\n            SingleCoop = 0;\n            numPlayers = 0;\n            // ScreenType = 0; // set in SetupScreens()\n            XEvents::doEvents();\n            SetupEditorGraphics(); //Set up the editor graphics\n\n            g_VanillaCam = false;\n\n            InitScreens();\n            UpdateInternalRes();\n\n            for(int i = 0; i < maxLocalPlayers; i++)\n                Screens_AssignPlayer(i + 1, *l_screen);\n\n            SetupScreens();\n            MagicHand = false;\n            MouseRelease = false;\n            ScrollRelease = false;\n\n            // coming back from a level test\n            if(!WorldEditor)\n                EditorRestore();\n\n            // Run the frame-loop\n            runFrameLoop(&EditorLoop,\n                         nullptr,\n                        []()->bool{ return LevelEditor || WorldEditor;}, nullptr,\n                        nullptr,\n                        nullptr);\n\n            MenuMode = MENU_INTRO;\n            LevelEditor = false;\n            WorldEditor = false;\n\n            if(GameIsActive)\n            {\n                XRender::clearBuffer();\n                XRender::repaint();\n#ifdef THEXTECH_PRELOAD_LEVELS\n                FindWorlds();\n#endif\n            }\n        }\n\n        // TheXTech Credits\n        else if(GameOutro)\n        {\n            ShadowMode = false;\n            GodMode = false;\n            GrabAll= false;\n            CaptainN = false;\n            FlameThrower = false;\n            FreezeNPCs = false;\n            WalkAnywhere = false;\n            MultiHop = false;\n            SuperSpeed = false;\n            FlyForever = false;\n            GoToLevelNoGameThing = false;\n\n            g_VanillaCam = false;\n            UpdateInternalRes();\n\n            for(int A = 1; A <= maxPlayers; A++)\n                Player[A] = blankPlayer;\n\n            GameMenu = false;\n            StopMusic();\n\n            std::string outroPath;\n\n            if(!g_recentWorldOutro.empty() && Files::fileExists(g_recentWorldOutro))\n                outroPath = g_recentWorldOutro;\n            else\n            {\n                outroPath = AppPath + \"outro.lvlx\";\n\n                if(!Files::fileExists(outroPath))\n                    outroPath = AppPath + \"outro.lvl\";\n            }\n\n            OpenLevel(outroPath);\n\n            numPlayers = g_gameInfo.outroMaxPlayersCount;\n#ifdef __16M__\n            if(numPlayers > 3)\n                numPlayers = 3;\n#endif\n            if(g_gameInfo.outroDeadMode)\n                numPlayers = 1; // Deadman mode\n\n            SetupScreens();\n            ClearBuffer = true;\n\n            for(int A = 1; A <= numPlayers; ++A)\n            {\n                Player_t &p = Player[A];\n\n                if(A <= (int)g_gameInfo.outroStates.size())\n                    p.State = g_gameInfo.outroStates[A - 1];\n                else if(A == 1)\n                    p.State = 4;\n                else if(A == 2)\n                    p.State = 7;\n                else if(A == 3)\n                    p.State = 5;\n                else if(A == 4)\n                    p.State = 3;\n                else\n                    p.State = 6;\n\n                p.Character = g_gameInfo.outroCharacterNext();\n\n                if(A <= (int)g_gameInfo.outroMounts.size())\n                {\n                    p.Mount = g_gameInfo.outroMounts[A - 1];\n                    switch(p.Mount)\n                    {\n                    case 1:\n                        p.MountType = iRand(3) + 1;\n                        break;\n                    case 3:\n                        p.MountType = iRand(8) + 1;\n                        break;\n                    default:\n                        p.MountType = 0;\n                    }\n                }\n                else if(A == 4)\n                {\n                    p.Mount = 1;\n                    p.MountType = iRand(3) + 1;\n                }\n                else if(A == 2)\n                {\n                    p.Mount = 3;\n                    p.MountType = iRand(8) + 1;\n                }\n\n                p.HeldBonus = NPCID(0);\n                p.Section = 0;\n                p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n            }\n\n            SetupPlayers();\n            GameOutroDoQuit = false;\n            SetupCredits();\n\n            // Update graphics before loop begin (to process an initial lazy-unpacking of used sprites)\n            GraphicsLazyPreLoad();\n            resetFrameTimer();\n\n            for(int A = 1; A <= numPlayers; ++A)\n            {\n                if(g_gameInfo.outroWalkDirection == 0 && A <= (int)g_gameInfo.outroInitialDirections.size())\n                {\n                    if(g_gameInfo.outroInitialDirections[A - 1] < 0)\n                        Player[A].Direction = -1;\n                    else if(g_gameInfo.outroInitialDirections[A - 1] > 0)\n                        Player[A].Direction = 1;\n                }\n            }\n\n            if(g_gameInfo.outroDeadMode)\n            {\n                CheckSection(1);\n                for(int A = 1; A <= numPlayers; ++A)\n                    Player[A].Dead = true;\n            }\n\n            lunaLoad();\n\n            clearScreenFaders();\n\n            // Run the frame-loop\n            runFrameLoop(&OutroLoop,\n                         nullptr,\n                        []()->bool{ return GameOutro;}, nullptr,\n                        nullptr,\n                        []()->void\n                        {\n                            SetupScreens();\n                        });\n\n            // Ensure everything is clear\n            GraphicsClearScreen();\n            XEvents::doEvents();\n        }\n\n        // quickly exit if returned to menu from world test\n        else if(setup.testLevelMode && GameMenu)\n        {\n            GracefulQuit();\n            return 0;\n        }\n\n        // The Game Menu\n        else if(GameMenu)\n        {\n            {\n                g_config.playstyle.m_value = Config_t::MODE_MODERN;\n                g_config.playstyle.m_set = ConfigSetLevel::ep_config;\n\n                if(g_config.speedrun_mode.m_set != ConfigSetLevel::cmdline)\n                {\n                    g_config.speedrun_mode.m_value = 0;\n                    g_config.playstyle.m_set = ConfigSetLevel::ep_config;\n                }\n            }\n\n            Integrator::clearEpisodeName();\n            Integrator::clearLevelName();\n            Integrator::clearEditorFile();\n            FontManager::clearAllCustomFonts();\n\n            BattleIntro = 0;\n            BattleOutro = 0;\n            // AllCharBlock = 0;\n            Cheater = false;\n\n            // in a main menu, reset this into initial state\n            GoToLevelNoGameThing = false;\n\n            for(int A = 1; A <= maxPlayers; ++A)\n            {\n                OwedMount[A] = 0;\n                OwedMountType[A] = 0;\n            }\n\n            MenuMouseRelease = false;\n            MenuMouseClick = false;\n            MenuCursorCanMove = false;\n            BattleMode = false;\n\n            // if(MenuMode != MENU_BATTLE_MODE)\n            // {\n            //     PlayerCharacter = 0;\n            //     PlayerCharacter2 = 0;\n            // }\n\n            pLogDebug(\"Clear check-points at Game Menu start\");\n            Checkpoint.clear();\n            CheckpointsList.clear();\n            g_curLevelMedals.reset_checkpoint();\n            WorldPlayer[1].Frame = 0;\n            cheats_clearBuffer();\n            LevelBeatCode = BEATCODE_NONE;\n            curWorldLevel = 0;\n\n            lunaReset();\n            ResetSoundFX();\n            ClearWorld();\n\n            ReturnWarp = 0;\n            ReturnWarpSaved = 0;\n            ShadowMode = false;\n            GodMode = false;\n            GrabAll = false;\n            CaptainN = false;\n            FlameThrower = false;\n            FreezeNPCs = false;\n            WalkAnywhere = false;\n            MultiHop = false;\n            SuperSpeed = false;\n            FlyForever = false;\n            BeatTheGame = false;\n            g_ForceBitmaskMerge = false;\n#ifdef __3DS__\n            g_ForceBitmaskMerge = g_config.inaccurate_gifs;\n#endif\n            g_ClonedPlayerMode = false;\n            g_CheatLogicScreen = false;\n            g_CheatEditYourFriends = false;\n            CanWallJump = false;\n            g_VanillaCam = false;\n            SharedPause = false;\n            SharedPauseLegacy = false;\n            XRender::unloadGifTextures();\n\n            Controls::RemoveNullInputMethods();\n            QuickReconnectScreen::Deactivate();\n\n            // reset l_screen to index 0\n            Screens[0] = *l_screen;\n            l_screen = &Screens[0];\n\n            // reset non-local screens' charSelect\n            for(int s = 1; s < maxNetplayClients; s++)\n                Screens[s].charSelect = {};\n\n            // reinitialize the screens (resets multiplayer preferences and restores state disrupted by reassigning Screens[0])\n            InitScreens();\n            UpdateInternalRes();\n\n            for(int i = 0; i < maxLocalPlayers; i++)\n                Screens_AssignPlayer(i + 1, *l_screen);\n\n            SetupScreens();\n\n            BattleOutro = 0;\n            BattleIntro = 0;\n\n            for(int A = 1; A <= maxPlayers; ++A)\n                Player[A] = blankPlayer;\n\n            auto introPath = findIntroLevel();\n            if(introPath.empty())\n            {\n                XMsgBox::errorMsgBox(\"Fatal error\",\n                                     \"Can't find any intro level file to start the main menu.\\n\"\n                                     \"The game will be closed.\\n\"\n                                     \"\\n\"\n                                     \"Please make sure the intro.lvlx or intro.lvl file is exist\\n\"\n                                     \"in the game assets directory, or make sure the \\\"introset\\\" directory\\n\"\n                                     \"contains any valid level files.\");\n                return 1;// Fatal error happen\n            }\n\n            OpenLevel(introPath);\n            Screens[0].vScreen(1).X = -level[0].X;\n            Screens[0].canonical_screen().vScreen(1).X = -level[0].X;\n            s_ExpandSectionForMenu();\n\n            numPlayers = g_gameInfo.introMaxPlayersCount;\n#ifdef __16M__\n            if(numPlayers > 3)\n                numPlayers = 3;\n#endif\n            if(g_gameInfo.introDeadMode)\n                numPlayers = 1;// one deadman should be\n\n            if(g_config.EnableInterLevelFade)\n                g_levelScreenFader.setupFader(3, 65, 0, ScreenFader::S_FADE);\n            else\n                clearScreenFaders();\n\n            setMusicStartDelay(); // Don't start music until all gfx will be loaded\n\n            StartMusic(0);\n            SetupPlayers();\n\n            For(A, 1, numPlayers)\n            {\n                Player_t &p = Player[A];\n                p.State = iRand(6) + 2;\n                // p.Character = (iRand() % 5) + 1;\n\n                // if(A >= 1 && A <= 5)\n                p.Character = g_gameInfo.introCharacterNext();\n\n                p.HeldBonus = NPCID(0);\n                p.Section = 0;\n                p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                p.Location.X = level[p.Section].X + ((128 + dRand() * 64) * A);\n                p.Location.Y = level[p.Section].Height - p.Location.Height - 65;\n\n                do\n                {\n                    tempBool = true;\n                    for(int B : treeBlockQuery(p.Location, SORTMODE_NONE))\n                    {\n                        if(CheckCollision(p.Location, Block[B].Location))\n                        {\n                            p.Location.Y = Block[B].Location.Y - p.Location.Height - 0.1_n;\n                            tempBool = false;\n                        }\n                    }\n                } while(!tempBool);\n                p.Dead = true;\n            }\n\n            // Update graphics before loop begin (to process inital lazy-unpacking of used sprites)\n            GraphicsLazyPreLoad();\n            resetFrameTimer();\n            // Clear the speed-runner timer\n            speedRun_resetTotal();\n\n            lunaLoad();\n\n            delayedMusicStart(); // Allow music being started\n\n            ProcEvent(EVENT_LEVEL_START, 0, EventContext::InitSetup);\n            For(A, 2, maxEvents)\n            {\n                if(Events[A].AutoStart)\n                    ProcEvent(A, 0, EventContext::InitSetup);\n            }\n\n            // Main menu loop\n            runFrameLoop(&MenuLoop, nullptr, []()->bool{ return GameMenu;});\n            if(!GameIsActive)\n            {\n                speedRun_saveStats();\n                return 0;// Break on quit\n            }\n\n            // Ensure everything is clear if the main menu is no longer active\n            if(!ScreenAssetPack::g_LoopActive)\n            {\n                GraphicsClearScreen();\n                XEvents::doEvents();\n            }\n        }\n\n        // World Map\n        else if(LevelSelect)\n        {\n            // only set if loading the WORLD fails\n            if(ErrorQuit)\n            {\n                ErrorQuit = false;\n                GameMenu = true;\n                MenuMode = MENU_INTRO;\n                MenuCursor = 0;\n                continue;\n            }\n\n            cheats_clearBuffer();\n\n            For(A, 1, numPlayers)\n            {\n                if(Player[A].Mount == 0 || Player[A].Mount == 2)\n                {\n                    if(OwedMount[A] > 0)\n                    {\n                        Player[A].Mount = OwedMount[A];\n                        if(OwedMountType[A] > 0)\n                            Player[A].MountType = OwedMountType[A];\n                        else\n                            Player[A].MountType = 1;\n                    }\n                }\n\n                OwedMount[A] = 0;\n                OwedMountType[A] = 0;\n            }\n\n            if(!NoMap)\n            {\n                // Restore the previously preserved world map paths\n                FileNameFull = FileNameFullWorld;\n                FileName = FileNameWorld;\n                FileNamePath = FileNamePathWorld;\n                FileFormat = FileFormatWorld;\n\n                // Recalculate the FullFileName\n                FullFileName = FileNamePath + FileNameFull;\n            }\n\n            LoadCustomConfig();\n            FindCustomPlayers();\n            LoadCustomGFX();\n            LoadCustomSound();\n            SetupPlayers();\n            FontManager::loadCustomFonts();\n\n            if(!NoMap)\n                FindWldStars();\n\n            if((!StartLevel.empty() && NoMap) || !GoToLevel.empty() || !FileRecentSubHubLevel.empty())\n            {\n                if(NoMap)\n                    SaveGame();\n\n                Player[1].Vine = 0;\n                Player[2].Vine = 0;\n\n//                if(!GoToLevelNoGameThing)\n//                    PlaySound(SFX_LevelSelect);\n                SoundPause[SFX_Slide] = 2000;\n\n                LevelSelect = false;\n\n                if(XMessage::GetStatus() != XMessage::Status::replay)\n                {\n                    XRender::setTargetTexture();\n                    XRender::clearBuffer();\n                    XRender::repaint();\n                }\n\n                lunaReset();\n                ResetSoundFX();\n                ClearLevel();\n\n                std::string levelPath;\n                if(GoToLevel.empty())\n                {\n                    if(FileRecentSubHubLevel.empty())\n                        levelPath = FileNamePathWorld + StartLevel;\n                    else\n                        levelPath = FileNamePathWorld + FileRecentSubHubLevel;\n                }\n                else\n                {\n                    levelPath = FileNamePathWorld + GoToLevel;\n                    GoToLevel.clear();\n                }\n\n                levelPath = s_prepare_episode_path(levelPath);\n\n                if(!OpenLevel(levelPath))\n                {\n                    ReportLoadFailure(levelPath);\n                    ErrorQuit = true;\n                }\n\n                if(!GoToLevelNoGameThing)\n                {\n                    GameThing(1000 - (g_ShortDelay * 250), 3);\n                    g_ShortDelay = false;\n                }\n                else if(XMessage::GetStatus() != XMessage::Status::replay)\n                {\n                    XRender::setTargetTexture();\n                    XRender::clearBuffer();\n                    XRender::repaint();\n                }\n\n                GoToLevelNoGameThing = false;\n            }\n            else\n            {\n                ResetSoundFX();\n                setMusicStartDelay(); // Don't start music until all gfx will be loaded\n\n                worldResetSection();\n\n                if(curWorldMusic > 0)\n                    StartMusic(curWorldMusic);\n\n                resetFrameTimer();\n                speedRun_resetCurrent();\n\n                // On a world map, reset this into default state\n                GoToLevelNoGameThing = false;\n\n                // Clear the recent hub level when entering a world map\n                FileRecentSubHubLevel.clear();\n\n                // Update graphics before loop begin (to process inital lazy-unpacking of used sprites)\n                UpdateGraphics2(true);\n                resetFrameTimer();\n\n                if(g_config.EnableInterLevelFade)\n                    g_worldScreenFader.setupFader(4, 65, 0, ScreenFader::S_FADE);\n                else\n                    g_worldScreenFader.clearFader();\n\n                // WorldLoop will automatically resume the music as needed\n                // delayedMusicStart(); // Allow music being started\n                worldResetSection();\n\n                // 'level select loop\n                runFrameLoop(nullptr, &WorldLoop,\n                             []()->bool{return LevelSelect;},\n                             nullptr,\n                             []()->void{FreezeNPCs = false;});\n\n                if(!GameIsActive)\n                {\n                    speedRun_saveStats();\n                    return 0;// Break on quit\n                }\n            }\n        }\n\n        // MAIN GAME\n        else\n        {\n            cheats_clearBuffer();\n            EndLevel = false;\n\n            Record::InitRecording(); // initializes level data recording\n\n            for(int A = 1; A <= numPlayers; ++A)\n            {\n                if(Player[A].Mount == 2)\n                    Player[A].Mount = 0; // take players off the clown car\n            }\n\n            setMusicStartDelay(); // Don't start music until all gfx will be loaded\n\n            SetupPlayers(); // Setup Players for the level\n\n            if(LevelRestartRequested && Checkpoint.empty())\n                StartWarp = lastWarpEntered; // When restarting a level (after death), don't restore an entered warp on checkpoints\n\n            if(IsHubLevel && StartWarp > 0) // Save the warp where player entered the hub\n                ReturnWarpSaved = StartWarp;\n            else if(IsHubLevel && ReturnWarp == 0)\n                ReturnWarpSaved = 0;\n\n            qScreen = false;\n            qScreen_canonical = false;\n            LevelRestartRequested = false;\n\n            if(lastWarpEntered != StartWarp)\n                lastWarpEntered = StartWarp; // Re-use it when player re-enters a level after death (when option is toggled on)\n\n// for warp entrances\n            if((ReturnWarp > 0 && (IsEpisodeIntro || IsHubLevel)/*FileName == StartLevel*/) || (StartWarp > 0))\n            {\n                for(int numPlayersMax = numPlayers, A = 1; A <= numPlayersMax; ++A)\n                {\n                    Player_t &p = Player[A];\n\n                    if(StartWarp > 0)\n                        p.Warp = StartWarp;\n                    else\n                        p.Warp = ReturnWarp;\n\n                    if(p.Warp > maxWarps)\n                        break;\n\n                    p.WarpBackward = false;\n                    auto &warp = Warp[p.Warp];\n\n                    if(warp.Effect == 1)\n                    {\n                        if(warp.Direction2 == 1) // DOWN\n                        {\n//                                .Location.X = Warp(.Warp).Exit.X + Warp(.Warp).Exit.Width / 2 - .Location.Width / 2\n                            p.Location.X = warp.Exit.X + (warp.Exit.Width - p.Location.Width) / 2;\n//                                .Location.Y = Warp(.Warp).Exit.Y - .Location.Height - 8\n                            p.Location.Y = warp.Exit.Y - p.Location.Height - 8;\n                        }\n//                            ElseIf Warp(.Warp).Direction2 = 3 Then\n                        if(warp.Direction2 == 3) // UP\n                        {\n//                                .Location.X = Warp(.Warp).Exit.X + Warp(.Warp).Exit.Width / 2 - .Location.Width / 2\n                            p.Location.X = warp.Exit.X + (warp.Exit.Width - p.Location.Width) / 2;\n//                                .Location.Y = Warp(.Warp).Exit.Y + Warp(.Warp).Exit.Height + 8\n                            p.Location.Y = warp.Exit.Y + warp.Exit.Height + 8;\n                        }\n//                            ElseIf Warp(.Warp).Direction2 = 2 Then\n                        if(warp.Direction2 == 2) // RIGHT\n                        {\n//                                If .Mount = 3 Then .Duck = True\n                            if(p.Mount == 3) p.Duck = true;\n//                                .Location.X = Warp(.Warp).Exit.X - .Location.Width - 8\n                            p.Location.X = warp.Exit.X - p.Location.Width - 8;\n//                                .Location.Y = Warp(.Warp).Exit.Y + Warp(.Warp).Exit.Height - .Location.Height - 2\n                            p.Location.Y = warp.Exit.Y + warp.Exit.Height - p.Location.Height - 2;\n                        }\n//                            ElseIf Warp(.Warp).Direction2 = 4 Then\n                        if(warp.Direction2 == 4) // LEFT\n                        {\n//                                If .Mount = 3 Then .Duck = True\n                            if(p.Mount == 3) p.Duck = true;\n//                                .Location.X = Warp(.Warp).Exit.X + Warp(.Warp).Exit.Width + 8\n                            p.Location.X = warp.Exit.X + warp.Exit.Width + 8;\n//                                .Location.Y = Warp(.Warp).Exit.Y + Warp(.Warp).Exit.Height - .Location.Height - 2\n                            p.Location.Y = warp.Exit.Y + warp.Exit.Height - p.Location.Height - 2;\n//                            End If\n                        }\n\n                        PlayerFrame(p);\n                        CheckSection_Init(A);\n                        SoundPause[SFX_Warp] = 0;\n                        p.Effect = PLREFF_WAITING;\n                        p.Effect2 = 950;\n                    }\n                    else if(warp.Effect == 2)\n                    {\n//                            .Location.X = Warp(.Warp).Exit.X + Warp(.Warp).Exit.Width / 2 - .Location.Width / 2\n                        p.Location.X = warp.Exit.X + (warp.Exit.Width - p.Location.Width) / 2;\n//                            .Location.Y = Warp(.Warp).Exit.Y + Warp(.Warp).Exit.Height - .Location.Height\n                        p.Location.Y = warp.Exit.Y + warp.Exit.Height - p.Location.Height;\n\n                        CheckSection_Init(A);\n                        p.Effect = PLREFF_WAITING;\n                        p.Effect2 = 2000;\n                    }\n                    else if(warp.Effect == 3) // Portal warp\n                    {\n                        p.Location.X = warp.Exit.X + (warp.Exit.Width - p.Location.Width) / 2;\n                        p.Location.Y = warp.Exit.Y + warp.Exit.Height - p.Location.Height;\n                        CheckSection_Init(A);\n                        p.WarpCD = 50;\n\n                        if(warp.eventExit != EVENT_NONE)\n                            TriggerEvent(warp.eventExit, A);\n                    }\n                }\n\n                if(StartWarp > 0)\n                {\n                    StartWarp = 0;\n\n                    // fix a bug where ReturnWarp (from different level) would be used at hub after death if StartWarp was set for hub\n                    if(IsHubLevel && g_config.enable_last_warp_hub_resume)\n                        ReturnWarp = 0;\n                }\n                else\n                    ReturnWarp = 0;\n            }\n\n            // ---------------------------------------\n            //    Verify if level can run or not\n            // ---------------------------------------\n            bool hasPlayerPoint = false;\n            bool hasStartWarp = (Player[1].Warp > 0);\n            bool hasCrashStartWarp = (Player[1].Warp > maxWarps);\n            bool hasValidStartWarp = (Player[1].Warp > 0 && Player[1].Warp <= numWarps);\n            bool startError = false;\n\n            for(int i = 1; i <= numPlayers && i <= 2; ++i)\n                hasPlayerPoint |= !PlayerStart[i].isNull();\n\n            if(hasCrashStartWarp || (TestLevel && hasStartWarp && !hasValidStartWarp))\n            {\n                g_MessageType = MESSAGE_TYPE_SYS_ERROR;\n                // this case would have crashed SMBX 1.3.\n                MessageText = fmt::format_ne(g_gameStrings.errorInvalidEnterWarp,\n                                             FullFileName,\n                                             Player[1].Warp,\n                                             numWarps);\n                startError = true;\n                Player[1].Warp = 0;\n            }\n            else if(!hasPlayerPoint && !hasValidStartWarp)\n            {\n                g_MessageType = MESSAGE_TYPE_SYS_ERROR;\n                MessageText = fmt::format_ne(g_gameStrings.errorNoStartPoint, FullFileName);\n                startError = true;\n            }\n            else if(hasStartWarp && !hasValidStartWarp)\n            {\n                pLogWarning(\"Level start: warp %d requested, but only %d warps present.\", Player[1].Warp, numWarps);\n            }\n\n            if(startError) // Quit the level because of error\n            {\n                // Mark all players as dead\n                for(int A = 1; A <= numPlayers; A++)\n                    Player[A].Dead = true;\n\n                PauseGame(PauseCode::Message);\n\n                if(g_config.modern_lives_system)\n                    ++g_100s;\n                else\n                    ++Lives;\n\n                EveryonesDead();\n                clearScreenFaders();\n\n                if(BattleMode && !LevelEditor && !setup.testLevelMode)\n                {\n                    BattleMode = false;\n                    GameMenu = true;\n                    MenuMode = MENU_BATTLE_MODE;\n                    MenuCursor = selWorld - 1;\n                }\n            }\n            else // Run the level normally\n            {\n                speedRun_resetCurrent();\n//'--------------------------------------------\n\n                // Update graphics before loop begin (to process inital lazy-unpacking of used sprites)\n                GraphicsLazyPreLoad();\n                resetFrameTimer();\n\n                speedRun_triggerEnter();\n\n                clearScreenFaders(); // Reset all faders\n                if(g_config.EnableInterLevelFade)\n                    g_levelScreenFader.setupFader(2, 65, 0, ScreenFader::S_FADE);\n\n                lunaLoad();\n\n                delayedMusicStart(); // Allow music being started\n\n                // intro events previously processed directly here\n                g_gameLoopInterrupt.process_intro_events = true;\n\n                // MAIN GAME LOOP\n                runFrameLoop(nullptr, &GameLoop,\n                []()->bool{return !LevelSelect && !GameMenu;},\n                []()->bool\n                {\n                    if(!LivingPlayers())\n                    {\n                        EveryonesDead();\n                        return true;\n                    }\n                    return false;\n                });\n\n                // Ensure everything is clear\n                GraphicsClearScreen();\n                XEvents::doEvents();\n            }\n\n            // store to level save info if level won\n            if((LevelBeatCode > 0 && LevelBeatCode != BEATCODE_GAME_COMPLETE) || !GoToLevel.empty())\n            {\n                CommitBeatCode(LevelBeatCode);\n                g_curLevelMedals.commit();\n            }\n            // otherwise, reset the medal count\n            else if(LevelBeatCode != BEATCODE_GAME_COMPLETE)\n                g_curLevelMedals.on_all_dead();\n\n            Record::EndRecording();\n\n            StopAllSounds();\n            UnloadExtSounds();\n\n            if(!GameIsActive)\n            {\n                speedRun_saveStats();\n                return 0;// Break on quit\n            }\n\n            // TODO: Utilize this and any TestLevel/MagicHand related code to allow PGE Editor integration\n            // (do any code without interaction of no more existnig Editor VB forms, keep IPS with PGE Editor instead)\n\n//            If TestLevel = True Then\n            if(TestLevel)\n            {\n                // provide option to restart (was previously restricted to fails and command-line runs)\n                // if(LevelBeatCode == 0 || (setup.testLevelMode && LevelBeatCode >= 0))\n                if(LevelBeatCode >= 0)\n                {\n                    LevelSelect = false;\n                    LevelBeatCode = BEATCODE_RESTART; // checked in PauseScreen::Init()\n                    PauseGame(PauseCode::PauseScreen);\n                }\n\n                // check that we are still restarting (it could have been canceled above)\n                if(LevelBeatCode == BEATCODE_NONE || LevelBeatCode == BEATCODE_RESTART)\n                {\n                    GameThing();\n                    zTestLevel(setup.testMagicHand || editorScreen.test_magic_hand, setup.interprocess); // Restart level\n                }\n                // from editor, return to editor\n                else if(!Backup_FullFileName.empty())\n                {\n                    // printf(\"returning to editor...\\n\");\n\n                    TestLevel = false;\n                    LevelEditor = true;\n                    GameMenu = false;\n                    SetupPlayers();\n\n                    // reopen the temporary level (FullFileName)\n                    OpenLevel(FullFileName);\n\n                    // reset FullFileName to point to the real level (Backup_FullFileName)\n                    Files::deleteFile(FullFileName);\n                    FullFileName = Backup_FullFileName;\n                    // this is needed because the temporary levels are currently saved as \".lvl(x)tst\"\n                    if(FileNameFull.size() > 3 && FileNameFull.substr(FileNameFull.size() - 3) == \"tst\")\n                        FileNameFull.resize(FileNameFull.size() - 3);\n\n                    Backup_FullFileName = \"\";\n\n                    Integrator::setEditorFile(FileName);\n\n                    editorScreen.active = false;\n                    MouseRelease = false;\n\n                    if(g_config.EnableInterLevelFade)\n                        g_levelScreenFader.setupFader(3, 65, 0, ScreenFader::S_FADE);\n                }\n                // from command line, close (if player has requested to stop testing)\n                else if(setup.testLevelMode)\n                {\n                    GracefulQuit();\n                    return 0;\n                }\n\n                LevelBeatCode = BEATCODE_NONE;\n\n//                If nPlay.Online = False Then\n//                    OpenLevel FullFileName\n//                OpenLevel(FullFileName);\n//                Else\n//                    If nPlay.Mode = 1 Then\n//                        Netplay.sendData \"H0\" & LB\n//                        If Len(FullFileName) > 4 Then\n//                            If LCase(Right(FullFileName, 4)) = \".lvl\" Then\n//                                OpenLevel FullFileName\n//                            Else\n//                                For A = 1 To 15\n//                                    If nPlay.ClientCon(A) = True Then Netplay.InitSync A\n//                                Next A\n//                            End If\n//                        Else\n//                            For A = 1 To 15\n//                                If nPlay.ClientCon(A) = True Then Netplay.InitSync A\n//                            Next A\n//                        End If\n//                    End If\n//                End If\n\n//                LevelSelect = False\n                LevelSelect = false;\n            }\n//            Else\n            else if(!LevelRestartRequested)\n            {\n                lunaReset();\n                ResetSoundFX();\n                ClearLevel();\n//            End If\n            } // TestLevel\n        }\n\n        Archives::unmount_temp();\n    }\n\n    return 0;\n}\n\n// game_main_setupvars.cpp\n\n// game_loop.cpp\n\n// menu_loop.cpp\n\nvoid EditorLoop()\n{\n    Controls::Update();\n    Integrator::sync();\n    UpdateEditor();\n    UpdateBlocks();\n    UpdateEffects();\n    if(WorldEditor)\n        UpdateGraphics2();\n    else if(LevelEditor)\n        UpdateGraphics();\n\n    updateScreenFaders();\n\n    // TODO: if there's a second screen, draw editor screen there too\n\n    UpdateSound();\n}\n\nvoid KillIt()\n{\n#ifdef __WIIU__\n    if(!GameIsActive)\n        return; // Don't call this twice\n#endif\n\n    GameIsActive = false;\n    Integrator::quitIntegrations();\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    XWindow::hide();\n    if(g_config.fullscreen)\n        SetOrigRes();\n#else\n    XRender::clearBuffer();\n    XRender::repaint();\n#endif\n    lunaReset();\n    QuitMixerX();\n    UnloadGFX();\n    XWindow::showCursor(1);\n}\n\nvoid GracefulQuit(bool wait)\n{\n#ifdef __WIIU__\n    if(!GameIsActive)\n        return; // Don't call this twice\n#endif\n\n    StopMusic();\n\n    if(wait)\n    {\n        const Uint64 waitDst = SDL_GetTicks64() + 500;\n\n        do\n        {\n            XRender::setTargetTexture();\n            XRender::clearBuffer();\n            XRender::repaint();\n            XEvents::doEvents();\n            PGE_Delay(16);\n        } while(SDL_GetTicks64() < waitDst);\n    }\n\n    StopAllSounds();\n\n    XRender::setTargetTexture();\n    XRender::clearBuffer();\n    XRender::repaint();\n    XEvents::doEvents();\n\n#ifdef __WIIU__\n    if(GameIsActive && !g_isHBLauncher)\n    {\n        SYSLaunchMenu(); // Trigger the SDL_QUIT and the leading quit into Wii U main menu\n\n        while(GameIsActive) // Wait until quit event will happen\n        {\n            XEvents::doEvents();\n            PGE_Delay(10);\n        }\n\n        PGE_Delay(100);\n    }\n#endif\n\n    KillIt();\n}\n\nvoid NextLevel()\n{\n    int A = 0;\n\n    for(A = 1; A <= numPlayers; A++)\n        Player[A].HoldingNPC = 0;\n\n    LevelMacro = LEVELMACRO_OFF;\n    LevelMacroCounter = 0;\n    LevelMacroWhich = 0;\n    StopMusic();\n    lunaReset();\n    ResetSoundFX();\n    ClearLevel();\n\n    if(XMessage::GetStatus() != XMessage::Status::replay)\n    {\n        XRender::setTargetTexture();\n        XRender::clearBuffer();\n        XRender::repaint();\n        XEvents::doEvents();\n    }\n\n    // do an inter-level delay here if there won't be a GameThing later\n    if(!TestLevel && GoToLevel.empty() && !NoMap && FileRecentSubHubLevel.empty())\n    {\n        if(XMessage::GetStatus() != XMessage::Status::local)\n        {\n            for(int i = 0; i < ((g_ShortDelay) ? 16 : 32); i++)\n                Controls::Update(false);\n        }\n        else if(!g_config.unlimited_framerate)\n            PGE_Delay(500 - (g_ShortDelay * 250));\n\n        g_ShortDelay = false;\n    }\n\n    if(BattleMode && !LevelEditor && !TestLevel)\n    {\n        g_ShortDelay = false;\n        EndLevel = false;\n        GameMenu = true;\n        MenuMode = MENU_BATTLE_MODE;\n        MenuCursor = selWorld - 1;\n        // PlayerCharacter = Player[1].Character;\n        // PlayerCharacter2 = Player[2].Character;\n    }\n    else\n    {\n        LevelSelect = true;\n        EndLevel = false;\n        if(TestLevel && BattleMode)\n        {\n            BattleIntro = 150;\n\n            // if(!LevelEditor && Backup_FullFileName.empty())\n            //     GameIsActive = false; // Quit game\n        }\n    }\n}\n\n// macros mainly used for end of level stuffs. takes over the players controls\nvoid UpdateMacro()\n{\n    int A = 0;\n    bool OnScreen = false;\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n    if(LevelMacro != LEVELMACRO_OFF && LevelMacroCounter == 0 && IntProc::isEnabled())\n    {\n        for(int i = 0; i < numPlayers; ++i)\n        {\n            auto &p = Player[i + 1];\n            IntProc::sendPlayerSettings(i, p.Character, p.State, p.Mount, p.MountType);\n            IntProc::sendPlayerSettings2(i, p.Hearts, p.HeldBonus);\n        }\n    }\n#endif\n\n    if(LevelMacro == LEVELMACRO_CARD_ROULETTE_EXIT) // SMB3 Exit\n    {\n        for(A = 1; A <= numPlayers; A++)\n        {\n            auto &p = Player[A];\n            auto &c = p.Controls;\n            if(p.Location.X < level[p.Section].Width && !p.Dead)\n            {\n                OnScreen = true;\n                c.Down = false;\n                c.Drop = false;\n                c.Jump = false;\n                c.Left = false;\n                c.Right = true;\n                c.Run = false;\n                c.Up = false;\n                c.Start = false;\n                c.AltJump = false;\n                c.AltRun = false;\n\n                if(p.State == PLR_STATE_AQUATIC && !p.Mount && !p.HoldingNPC)\n                    c.Run = true;\n\n                if(p.Wet > 0 && p.CanJump)\n                {\n                    if(p.Location.SpeedY > 1)\n                        c.Jump = true;\n                }\n            }\n            else\n            {\n                p.Location.SpeedY = -Physics.PlayerGravity;\n                c.Down = false;\n                c.Drop = false;\n                c.Jump = false;\n                c.Left = false;\n                c.Right = true;\n                c.Run = false;\n                c.Up = false;\n                c.Start = false;\n                c.AltJump = false;\n                c.AltRun = false;\n            }\n        }\n\n        // !OnScreen requires Player to leave Screen during normal play.\n        // is_cheat ensures the ItsVegas Cheat doesn't Softlock the game.\n        bool is_cheat = (LevelMacroWhich == -1);\n\n        if(!OnScreen || is_cheat)\n        {\n            LevelMacroCounter++;\n\n            if(g_config.EnableInterLevelFade &&\n                ((LevelMacroCounter == 34 && !is_cheat)\n                || (LevelMacroCounter == 250 && is_cheat)))\n            {\n                g_levelScreenFader.setupFader(1, 0, 65, ScreenFader::S_FADE);\n            }\n\n            if((!is_cheat && LevelMacroCounter >= 100) || (is_cheat && LevelMacroCounter >= 316))\n            {\n                LevelBeatCode = BEATCODE_CARD_ROULETTE;\n                LevelMacro = LEVELMACRO_OFF;\n                LevelMacroCounter = 0;\n                EndLevel = true;\n            }\n        }\n    }\n    else if(LevelMacro == LEVELMACRO_QUESTION_SPHERE_EXIT)\n    {\n        for(A = 1; A <= numPlayers; A++)\n        {\n            auto &c = Player[A].Controls;\n            c.Down = false;\n            c.Drop = false;\n            c.Jump = false;\n            c.Left = false;\n            c.Right = false;\n            c.Run = false;\n            c.Up = false;\n            c.Start = false;\n            c.AltJump = false;\n            c.AltRun = false;\n        }\n\n        LevelMacroCounter++;\n\n        if(g_config.EnableInterLevelFade && LevelMacroCounter == 395)\n            g_levelScreenFader.setupFader(1, 0, 65, ScreenFader::S_FADE);\n\n        if(LevelMacroCounter >= 460)\n        {\n            LevelBeatCode = BEATCODE_QUESTION_SPHERE;\n            EndLevel = true;\n            LevelMacro = LEVELMACRO_OFF;\n            LevelMacroCounter = 0;\n\n            if(XMessage::GetStatus() != XMessage::Status::replay)\n                XRender::clearBuffer();\n        }\n    }\n    else if(LevelMacro == LEVELMACRO_KEYHOLE_EXIT)\n    {\n        const int keyholeMax = 192; // Was 300\n\n        // this was previously its own frameloop.\n        // items done earlier in the frameloop have been commented out.\n\n        speedRun_tick();\n        // Controls::Update(false);\n        UpdateGraphics();\n        UpdateSound();\n        BlockFrames();\n\n        // XEvents::doEvents();\n        // computeFrameTime2();\n\n        updateScreenFaders();\n\n        LevelMacroCounter++;\n\n        if(g_config.EnableInterLevelFade && LevelMacroCounter == (keyholeMax - 65))\n            g_levelScreenFader.setupFader(1, 0, 65, ScreenFader::S_FADE);\n\n        if(LevelMacroCounter >= keyholeMax) /*300*/\n        {\n            LevelBeatCode = BEATCODE_KEYHOLE;\n            EndLevel = true;\n            LevelMacro = LEVELMACRO_OFF;\n            LevelMacroWhich = 0;\n            LevelMacroCounter = 0;\n\n            if(XMessage::GetStatus() != XMessage::Status::replay)\n                XRender::clearBuffer();\n        }\n    }\n    else if(LevelMacro == LEVELMACRO_CRYSTAL_BALL_EXIT)\n    {\n        for(A = 1; A <= numPlayers; A++)\n        {\n            auto &c = Player[A].Controls;\n            c.Down = false;\n            c.Drop = false;\n            c.Jump = false;\n            c.Left = false;\n            c.Right = false;\n            c.Run = false;\n            c.Up = false;\n            c.Start = false;\n            c.AltJump = false;\n            c.AltRun = false;\n        }\n\n        LevelMacroCounter++;\n\n        if(g_config.EnableInterLevelFade && LevelMacroCounter == 235)\n            g_levelScreenFader.setupFader(1, 0, 65, ScreenFader::S_FADE);\n\n        if(LevelMacroCounter >= 300)\n        {\n            LevelBeatCode = BEATCODE_CRYSTAL_BALL;\n            EndLevel = true;\n            LevelMacro = LEVELMACRO_OFF;\n            LevelMacroCounter = 0;\n\n            if(XMessage::GetStatus() != XMessage::Status::replay)\n                XRender::clearBuffer();\n        }\n    }\n    else if(LevelMacro == LEVELMACRO_GAME_COMPLETE_EXIT)\n    {\n        // numNPCs = 0\n        for(A = 1; A <= numPlayers; A++)\n        {\n            auto &c = Player[A].Controls;\n            c.Down = false;\n            c.Drop = false;\n            c.Jump = false;\n            c.Left = false;\n            c.Right = false;\n            c.Run = false;\n            c.Up = false;\n            c.Start = false;\n            c.AltJump = false;\n            c.AltRun = false;\n        }\n\n        LevelMacroCounter++;\n\n        if(LevelMacroCounter == 250)\n            PlaySound(SFX_GameBeat);\n\n        if(g_config.EnableInterLevelFade && LevelMacroCounter == 735)\n            g_levelScreenFader.setupFader(1, 0, 65, ScreenFader::S_FADE);\n\n        if(LevelMacroCounter >= 800)\n        {\n            EndLevel = true;\n            LevelMacro = LEVELMACRO_OFF;\n            LevelMacroCounter = 0;\n            if(!TestLevel)\n            {\n                BeatTheGame = true;\n                // new level beat code 15: beat the game (exclusive to new level save info -- does not open any exits)\n                CommitBeatCode(BEATCODE_GAME_COMPLETE);\n                g_curLevelMedals.commit();\n                SaveGame();\n                GameOutro = true;\n                MenuMode = MENU_INTRO;\n                MenuCursor = 0;\n            }\n\n            if(XMessage::GetStatus() != XMessage::Status::replay)\n                XRender::clearBuffer();\n        }\n    }\n    else if(LevelMacro == LEVELMACRO_STAR_EXIT) // Star Exit\n    {\n        for(A = 1; A <= numPlayers; A++)\n        {\n            auto &c = Player[A].Controls;\n            c.Down = false;\n            c.Drop = false;\n            c.Jump = false;\n            c.Left = false;\n            c.Right = false;\n            c.Run = false;\n            c.Up = false;\n            c.Start = false;\n            c.AltJump = false;\n            c.AltRun = false;\n        }\n\n        LevelMacroCounter++;\n\n        if(g_config.EnableInterLevelFade && LevelMacroCounter == 235)\n            g_levelScreenFader.setupFader(1, 0, 65, ScreenFader::S_FADE);\n\n        if(LevelMacroCounter >= 300)\n        {\n            LevelBeatCode = BEATCODE_STAR;\n            LevelMacro = LEVELMACRO_OFF;\n            LevelMacroCounter = 0;\n            EndLevel = true;\n        }\n    }\n    else if(LevelMacro == LEVELMACRO_GOAL_TAPE_EXIT || LevelMacro == LEVELMACRO_FLAG_EXIT) // SMW Exit\n    {\n        for(A = 1; A <= numPlayers; A++)\n        {\n            auto &p = Player[A];\n            auto &c = p.Controls;\n\n            if(p.Location.X < level[p.Section].Width && !p.Dead)\n            {\n                c.Down = false;\n                c.Drop = false;\n                c.Jump = false;\n                c.Left = false;\n                c.Right = true;\n                c.Run = false;\n                c.Up = (LevelMacro == LEVELMACRO_FLAG_EXIT);\n                c.Start = false;\n                c.AltJump = false;\n                c.AltRun = false;\n\n                if(p.State == PLR_STATE_AQUATIC && !p.Mount && !p.HoldingNPC)\n                    c.Run = true;\n            }\n            else\n            {\n                p.Location.SpeedY = -Physics.PlayerGravity;\n                c.Down = false;\n                c.Drop = false;\n                c.Jump = false;\n                c.Left = false;\n                c.Right = true;\n                c.Run = false;\n                c.Up = false;\n                c.Start = false;\n                c.AltJump = false;\n                c.AltRun = false;\n            }\n        }\n\n        LevelMacroCounter++;\n\n        if(g_config.EnableInterLevelFade && LevelMacroCounter == 598)\n        {\n            bool canTrack = (Player[1].Location.X < level[Player[1].Section].Width);\n            num_t focusX = (canTrack) ?\n                            Player[1].Location.X + Player[1].Location.Width / 2 :\n                            level[Player[1].Section].Width;\n            num_t focusY = Player[1].Location.Y + Player[1].Location.Height / 2;\n\n            g_levelScreenFader.setupFader(2, 0, 65, ScreenFader::S_CIRCLE, true, (int)focusX, (int)focusY, 1);\n\n            if(canTrack)\n                g_levelScreenFader.setTrackedFocus(&Player[1].Location.X,\n                                                   &Player[1].Location.Y,\n                                                   Player[1].Location.Width / 2,\n                                                   Player[1].Location.Height / 2);\n        }\n\n        if(LevelMacroCounter >= 630)\n        {\n            // LEVELMACRO_GOAL_TAPE_EXIT = 7 -> 8\n            // LEVELMACRO_FLAG_EXIT = 8 -> 9\n            // LEVELMACRO_ALT_FLAG_EXIT = 9 -> 10 (reserved)\n            LevelBeatCode = (LevelBeatCode_t)(LevelMacro + 1);\n            LevelMacro = LEVELMACRO_OFF;\n            LevelMacroCounter = 0;\n            EndLevel = true;\n        }\n    }\n}\n\n// main_config.cpp\n\n\nvoid CheckActive()\n{\n    // It's useless on Emscripten as no way to check activity (or just differently)\n    // and on Android as it has built-in application pauser\n#if !defined(NO_WINDOW_FOCUS_TRACKING)\n//    bool MusicPaused = false;\n    bool focusLost = false;\n\n    if(g_config.background_work)\n        return;\n\n    if(!GameIsActive)\n        return;\n\n//    If nPlay.Online = True Then Exit Sub\n    // If LevelEditor = False And TestLevel = False Then Exit Sub\n    // If LevelEditor = False Then Exit Sub\n    while(!XWindow::hasWindowInputFocus())\n    {\n        if(!focusLost)\n        {\n            XRender::setTargetTexture();\n            XRender::renderRect(0, 0, XRender::TargetW, XRender::TargetH, {0, 0, 0, 127}, true);\n            SuperPrintScreenCenter(g_gameStrings.screenPaused.empty() ? \"Paused\" : g_gameStrings.screenPaused, 3, XRender::TargetH / 2);\n            pLogDebug(\"Window Focus lost\");\n            focusLost = true;\n        }\n\n        XRender::repaint();\n\n        XEvents::waitEvents();\n//        If LevelEditor = True Or MagicHand = True Then frmLevelWindow.vScreen(1).MousePointer = 0\n        SyncSysCursorDisplay();\n\n        resetFrameTimer();\n\n        resetTimeBuffer();\n        //keyDownEnter = false;\n        //keyDownAlt = false;\n\n//        if(musicPlaying && !MusicPaused)\n//        {\n//            MusicPaused = true;\n//            SoundPauseEngine(1);\n//        }\n\n        if(!GameIsActive)\n        {\n            speedRun_saveStats();\n            break;\n        }\n    }\n\n    if(focusLost)\n        pLogDebug(\"Window Focus got back\");\n\n//    if(MusicPaused)\n//        SoundPauseEngine(0);\n\n/* // Useless condition\n    if(MusicPaused)\n    {\n        if(MusicPaused)\n        {\n            SoundResumeAll();\n            if(GameOutro == true)\n                SoundResumeAll();\n            else if(LevelSelect == true && GameMenu == false && LevelEditor == false)\n                SoundResumeAll();\n            else if(curMusic > 0)\n                SoundResumeAll();\n            else if(curMusic < 0)\n            {\n                SoundResumeAll();\n                if(PSwitchStop > 0)\n                {\n                    // mciSendString \"resume stmusic\", 0, 0, 0\n                    SoundResumeAll();\n                }\n                else\n                {\n                    // mciSendString \"resume smusic\", 0, 0, 0\n                    SoundResumeAll();\n                }\n            }\n        }\n    }\n    */\n//    If LevelEditor = True Or MagicHand = True Then frmLevelWindow.vScreen(1).MousePointer = 99\n#endif // not def __EMSCRIPTEN__\n}\n\n\nLocation_t newLoc(num_t X, num_t Y, num_t Width, num_t Height)\n{\n    Location_t ret;\n    ret.X = X;\n    ret.Y = Y;\n    ret.Width = Width;\n    ret.Height = Height;\n    return ret;\n}\n\n// Location_t roundLoc(const Location_t &inLoc, double grid)\n// {\n//     Location_t ret = inLoc;\n//     ret.X = Maths::roundTo(ret.X, grid);\n//     ret.Y = Maths::roundTo(ret.Y, grid);\n//     return ret;\n// }\n\nvoid MoreScore(int addScore, const Location_t &Loc)\n{\n    uint8_t mult = 0; // dummy\n    MoreScore(addScore, Loc, mult);\n}\n\nvoid MoreScore(int addScore, const Location_t &Loc, uint8_t &Multiplier)\n{\n    //int oldM = 0;\n    int A = 0;\n\n    if(GameMenu || GameOutro || BattleMode)\n        return;\n\n    A = addScore + Multiplier;\n\n    if(A == 0)\n        return;\n\n    Multiplier++;\n\n    if(Multiplier > 9)\n        Multiplier = 8;\n\n    if(A < addScore)\n        A = addScore;\n\n    if(A > 13)\n        A = 13;\n\n    if(Points[A] <= 5)\n    {\n        if(g_config.modern_lives_system)\n            g_100s += Points[A];\n        else\n            Lives += Points[A];\n\n        PlaySound(SFX_1up, Points[A] - 1);\n    }\n    else\n        Score += Points[A];\n\n    NewEffect(EFFID_SCORE, Loc);\n    Effect[numEffects].Frame = A - 1;\n}\n\nvoid Got100Coins()\n{\n    if(g_config.modern_lives_system)\n    {\n        if(g_100s < 9999)\n        {\n            g_100s++;\n            PlaySound(SFX_1up);\n            Coins -= 100;\n        }\n        else\n            Coins = 99;\n    }\n    else\n    {\n        if(Lives < 99)\n        {\n            Lives += 1;\n            PlaySound(SFX_1up);\n            Coins -= 100;\n        }\n        else\n            Coins = 99;\n    }\n}\n\nvoid SizableBlocks()\n{\n    BlockIsSizable[568] = true;\n    BlockIsSizable[579] = true;\n    BlockIsSizable[575] = true;\n    BlockIsSizable[25] = true;\n    BlockIsSizable[26] = true;\n    BlockIsSizable[27] = true;\n    BlockIsSizable[28] = true;\n    BlockIsSizable[38] = true;\n    BlockIsSizable[79] = true;\n    BlockIsSizable[108] = true;\n    BlockIsSizable[130] = true;\n    BlockIsSizable[161] = true;\n    BlockIsSizable[240] = true;\n    BlockIsSizable[241] = true;\n    BlockIsSizable[242] = true;\n    BlockIsSizable[243] = true;\n    BlockIsSizable[244] = true;\n    BlockIsSizable[245] = true;\n    BlockIsSizable[259] = true;\n    BlockIsSizable[260] = true;\n    BlockIsSizable[261] = true;\n    BlockIsSizable[287] = true;\n    BlockIsSizable[288] = true;\n    BlockIsSizable[437] = true;\n    BlockIsSizable[441] = true;\n    BlockIsSizable[442] = true;\n    BlockIsSizable[443] = true;\n    BlockIsSizable[444] = true;\n    BlockIsSizable[438] = true;\n    BlockIsSizable[439] = true;\n    BlockIsSizable[440] = true;\n    BlockIsSizable[445] = true;\n}\n\nstatic void s_InitPlayersFromCharSelect()\n{\n    for(int A = 0; A <= maxPlayers; A++)\n        Player[A] = Player_t();\n\n    // load players from the screens\n    numPlayers = 0;\n    InitScreens();\n\n    for(int s = 0; s < maxNetplayClients; s++)\n    {\n        Screen_t& screen = Screens[s];\n\n        for(int i = 0; i < maxLocalPlayers; i++)\n        {\n            if(screen.charSelect[i] != 0)\n            {\n                numPlayers++;\n                Screens_AssignPlayer(numPlayers, screen);\n\n                Player_t& p = Player[numPlayers];\n                p.State = 1;\n                p.Mount = 0;\n                p.Character = screen.charSelect[i];\n                p.HeldBonus = NPCID(0);\n                p.CanFly = false;\n                p.CanFly2 = false;\n                p.TailCount = 0;\n                p.YoshiBlue = false;\n                p.YoshiRed = false;\n                p.YoshiYellow = false;\n                p.Hearts = 0;\n            }\n        }\n    }\n\n    for(int i = (int)Controls::g_InputMethods.size() - 1; i >= l_screen->player_count; i--)\n        Controls::DeleteInputMethodSlot(i);\n}\n\nvoid StartEpisode()\n{\n    XMessage::InitSession();\n\n    For(A, 1, numCharacters)\n    {\n        SavedChar[A] = Player_t();\n        SavedChar[A].Character = A;\n        SavedChar[A].State = 1;\n    }\n\n    // load players from the screens\n    s_InitPlayersFromCharSelect();\n\n    ConnectScreen::SaveChars();\n\n    numStars = 0;\n    Coins = 0;\n    Score = 0;\n    g_100s = 3;\n    Lives = 3;\n    LevelSelect = true;\n    GameMenu = false;\n    g_ShortDelay = false;\n\n    // Update local screen size and multiplayer prefs\n    UpdateInternalRes();\n    XMessage::PushMessage({XMessage::Type::multiplayer_prefs, (uint8_t)g_config.two_screen_mode.m_value, (uint8_t)g_config.four_screen_mode.m_value});\n\n    XRender::setTargetTexture();\n    XRender::clearBuffer();\n    XRender::repaint();\n    StopMusic();\n    XEvents::doEvents();\n\n    // Note: this causes the rendered touchscreen controller to freeze with button pressed.\n    if(!g_config.unlimited_framerate)\n        PGE_Delay(500);\n\n    ClearGame();\n    FontManager::clearAllCustomFonts();\n    UnloadCustomSound();\n    Archives::unmount_episode();\n\n    // New: prevents any buggy processing of leftover effects during a pause on the world map\n    ClearLevel();\n\n    std::string wPath = SelectWorld[selWorld].WorldFilePath;\n    std::string recentWorldIntroPrev = g_recentWorldIntro;\n    bool doSaveConfig = false;\n\n    if((numPlayers == 1 || g_config.compatibility_mode != Config_t::COMPAT_SMBX13) && g_recentWorld1p != wPath)\n    {\n        g_recentWorld1p = wPath;\n        doSaveConfig = true;\n    }\n    else if((numPlayers >= 2 && g_config.compatibility_mode == Config_t::COMPAT_SMBX13) && g_recentWorld2p != wPath)\n    {\n        g_recentWorld2p = wPath;\n        doSaveConfig = true;\n    }\n\n    wPath = s_prepare_episode_path(wPath);\n\n    if(!OpenWorld(wPath))\n    {\n        if(doSaveConfig)\n            SaveConfig();\n        ClearLevel();\n        LevelSelect = false;\n        ReportLoadFailure(wPath);\n        LevelSelect = true;\n        ErrorQuit = true;\n        return;\n    }\n\n    if(recentWorldIntroPrev != g_recentWorldIntro)\n        doSaveConfig = true;\n\n    if(doSaveConfig)\n        SaveConfig();\n\n    if(selSave && SaveSlotInfo[selSave].Progress >= 0)\n    {\n        if(!NoMap)\n            StartLevel.clear();\n        LoadGame();\n        speedRun_loadStats();\n    }\n\n    Integrator::setEpisodeName(WorldName);\n\n#if 0 // unused cheat code from SMBX64\n    if(WorldUnlock)\n    {\n        For(A, 1, numWorldPaths)\n        {\n            TinyLocation_t tempLocation = WorldPath[A].Location;\n            {\n                TinyLocation_t &l = tempLocation;\n                l.X += 4;\n                l.Y += 4;\n                l.Width -= 8;\n                l.Height -= 8;\n            }\n\n            WorldPath[A].Active = true;\n\n            For(B, 1, numScenes)\n            {\n                if(CheckCollision(tempLocation, Scene[B].Location))\n                    Scene[B].Active = false;\n            }\n        }\n\n        For(A, 1, numWorldLevels)\n            WorldLevel[A].Active = true;\n    }\n#endif\n\n    SetupPlayers();\n\n    if(!StartLevel.empty() || !FileRecentSubHubLevel.empty())\n    {\n        // TODO: why did Wohlstand disable this?\n        PlaySoundMenu(SFX_LevelSelect);\n        SoundPause[SFX_Slide] = 200;\n        LevelSelect = false;\n\n        ResetSoundFX();\n        // todo: update this!\n        ClearLevel();\n\n        std::string levelName = (FileRecentSubHubLevel.empty() ? StartLevel : FileRecentSubHubLevel);\n        std::string levelPath = FileNamePathWorld + levelName;\n\n        levelPath = s_prepare_episode_path(levelPath);\n\n        if(!OpenLevel(levelPath))\n        {\n            ReportLoadFailure(levelName);\n            LevelSelect = true;\n\n            // return to menu if the hub of a hub world is broken\n            if(NoMap)\n                ErrorQuit = true;\n        }\n        else\n            GameThing(1000, 3);\n    }\n}\n\nvoid StartBattleMode()\n{\n    XMessage::InitSession();\n\n    int A = 0;\n    Player_t blankPlayer;\n\n    for(A = 1; A <= numCharacters; A++)\n    {\n        SavedChar[A] = blankPlayer;\n        SavedChar[A].Character = A;\n        SavedChar[A].State = 1;\n    }\n\n    s_InitPlayersFromCharSelect();\n\n    for(int i = 1; i <= numPlayers; i++)\n    {\n        Player[i].State = 2;\n        Player[i].Hearts = 2;\n    }\n\n    numStars = 0;\n    Coins = 0;\n    Score = 0;\n    g_100s = 0;\n    Lives = 99;\n    for(int i = 1; i <= numPlayers; i++)\n        BattleLives[i] = 3;\n    LevelSelect = false;\n    GameMenu = false;\n    BattleMode = true;\n    XRender::setTargetTexture();\n    XRender::clearBuffer();\n    XRender::repaint();\n    StopMusic();\n    XEvents::doEvents();\n\n    if(!g_config.unlimited_framerate)\n        PGE_Delay(500);\n\n    lunaReset();\n    ResetSoundFX();\n    ClearLevel();\n\n    if(NumSelectBattle <= 1)\n    {\n        g_MessageType = MESSAGE_TYPE_SYS_WARNING;\n        MessageText = g_mainMenu.errorBattleNoLevels;\n        PauseGame(PauseCode::Message);\n        ErrorQuit = true;\n    }\n    else\n    {\n        if(selWorld == 1)\n            selWorld = (iRand(NumSelectBattle - 1)) + 2;\n    }\n\n    const std::string& levelPath = SelectBattle[selWorld].WorldFilePath;\n    if(!OpenLevel(levelPath))\n    {\n        ReportLoadFailure(levelPath);\n        ErrorQuit = true;\n    }\n    SetupPlayers();\n\n    BattleIntro = 150;\n    BattleWinner = 0;\n    BattleOutro = 0;\n}\n"
  },
  {
    "path": "src/game_main.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef MAIN_H\n#define MAIN_H\n\n#include \"globals.h\"\n#include \"cmd_line_setup.h\"\n\nenum class PauseCode\n{\n    None,\n    PauseScreen,\n    Message,\n    DropAdd,\n    TextEntry,\n    Prompt,\n    Options,\n};\n\n//Public GamePaused As Boolean 'true if the game is paused\nextern PauseCode GamePaused;\n\nvoid SetupPhysics();\n\nvoid initAll();\n\n//! Report the failure to load a level, pausing the game with a message box\nvoid ReportLoadFailure(const std::string& filename, bool isIPC = false);\n\n//! NEW: (re)load all file-based assets from the current asset pack directory\nvoid MainLoadAll();\n\nint GameMain(const CmdLineSetup_t &setup);\n//! Set up object sizes and frame offsets for blocks/npcs/effects\nvoid SetupVars();\n//! The loop for the game\nvoid GameLoop();\n//! The loop for the menu\nvoid MenuLoop();\n//! The loop for the level editor [DUMMY]\nvoid EditorLoop();\n//! Cleans up the buffer before ending the program\nvoid KillIt();\nvoid GracefulQuit(bool wait = false);\n\n// OpenLevel() and ClearLevel() moved into main/level_file.h\n\nvoid NextLevel();\n//! macros mainly used for end of level stuffs. takes over the players controls\nvoid UpdateMacro();\n\n// OpenWorld, FindWldStars, and ClearWorld moved into main/world_file.h\n\n//! Loop for world select\nvoid WorldLoop();\n\nvoid LevelPath(const WorldLevel_t &Lvl, int Direction, bool Skp = false);\n\nvoid PlayerPath(WorldPlayer_t &p);\n\nvoid PathPath(WorldPath_t &Pth, bool Skp = false);\n\nvoid PathWait();\n\nvoid FindWorlds();\n\nvoid LoadSingleWorld(const std::string wPath);\n\nvoid FindLevels();\n\nvoid FindSaves();\n\nextern std::string makeGameSavePath(std::string episode, std::string saveFile);\n\nvoid SaveGame();\n\nvoid LoadGame();\n//! Removes gamesave file and restores initial state of all level objects\nvoid ClearGame(bool punnish = false);\nvoid DeleteSave(int world, int save);\nvoid CopySave(int world, int src, int dst);\n\nvoid PauseInit(PauseCode code, int plr = 0, void (*callback)() = nullptr);\nvoid PauseLoop();\nvoid PauseGame(PauseCode code, int plr = 0);\n\n// reload recent episodes from config (used when asset pack changes)\nvoid ConfigReloadRecentEpisodes();\n\nvoid OpenConfig();\n\nvoid SaveConfig();\n\n// dead since 1.3\n// void NPCyFix();\n\n// void CheatCode(char NewKey);// Moved into \"main/cheat_code.h\"\n\n//! credit loop\nvoid OutroLoop();\n\nvoid SetupCredits();\n\n// FindStars() moved into main/level_file.h\n\n// for settings up the game's credits\nvoid AddCredit(const std::string& newCredit);\n// calcualtes scores based on the multiplyer and how much the NPC is worth\nvoid MoreScore(int addScore, const Location_t &Loc);\nvoid MoreScore(int addScore, const Location_t &Loc, uint8_t &Multiplier);\n\n// NEW: convenience hook for when a player has collected 100 coins\nvoid Got100Coins();\n\n// sets up player frame offsets so they are displayed correctly on the screen\nvoid SetupPlayerFrames();\n\nvoid StartEpisode();\nvoid StartBattleMode();\n\n// std::string FixComma(std::string newStr); // USELESS\n\n#endif // MAIN_H\n"
  },
  {
    "path": "src/gfx.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"globals.h\"\n#include \"gfx.h\"\n#include \"core/msgbox.h\"\n#include \"core/render.h\"\n#include \"graphics/gfx_frame.h\"\n\n#include <IniProcessor/ini_processing.h>\n#include <Utils/files_ini.h>\n#include <DirManager/dirman.h>\n#include <fmt_format_ne.h>\n#include <Logger/logger.h>\n\n#if defined(__3DS__) || defined(__SWITCH__) || defined(__WII__) || defined(__WIIU__)\n#   include <Utils/files.h>\n#   if defined(__SWITCH__)\n#       define UI_PLATFORM_EXT \"-switch\"\n#   elif defined(__3DS__)\n#       define UI_PLATFORM_EXT \"-3ds\"\n#   elif defined(__WII__)\n#       define UI_PLATFORM_EXT \"-wii\"\n#   elif defined(__WIIU__)\n#       define UI_PLATFORM_EXT \"-wiiu\"\n#   endif\n#endif\n\n#ifdef X_IMG_EXT\n#   define  UI_IMG_EXT X_IMG_EXT\n#else\n#   define  UI_IMG_EXT \".png\"\n#endif\n\nGFX_t GFX;\n\n\nvoid GFX_t::loadImage(StdPicture &img, const std::string &fileName)\n{\n    std::string path_ext = fileName + UI_IMG_EXT;\n\n    if(!m_uiPathTr.empty() && Files::fileExists(m_uiPathTr + path_ext))\n        path_ext = m_uiPathTr + path_ext; // Load localized variant\n    else\n        path_ext = m_uiPath + path_ext; // Load original\n\n    pLogDebug(\"Loading texture %s...\", path_ext.c_str());\n    XRender::LoadPicture(img, path_ext);\n\n#if defined(X_IMG_EXT) && !defined(X_NO_PNG_GIF)\n    if(!img.inited)\n    {\n        path_ext = fileName + \".png\";\n        if(!m_uiPathTr.empty() && Files::fileExists(m_uiPathTr + path_ext))\n            path_ext = m_uiPathTr + path_ext; // Load localized variant\n        else\n            path_ext = m_uiPath + path_ext; // Load original\n\n        pLogDebug(\"Could not load, trying %s...\", path_ext.c_str());\n        XRender::LoadPicture(img, path_ext);\n    }\n#endif\n\n    if(!img.inited)\n    {\n        pLogWarning(\"Failed to load texture: %s...\", path_ext.c_str());\n        m_loadErrors++;\n    }\n\n    m_loadedImages.push_back(&img);\n}\n\nvoid GFX_t::loadBorder(FrameBorder& border, const std::string& fileName)\n{\n    std::string path_dst;\n\n    if(!m_uiPathTr.empty() && Files::fileExists(m_uiPathTr + fileName + \".ini\"))\n        path_dst = m_uiPathTr + fileName; // Load localized variant\n    else\n        path_dst = m_uiPath + fileName; // Load original\n\n    loadImage(border.tex, fileName);\n\n    if(!border.tex.inited)\n        return;\n\n    IniProcessing ini = Files::load_ini(path_dst + \".ini\");\n    loadFrameInfo(ini, border);\n\n    // warn if invalid\n    const FrameBorderInfo& i = border;\n    if(i.le + i.li + i.ri + i.re > border.tex.w)\n        pLogWarning(\"Invalid border: total internal/external width is %d but [%s] is only %dpx wide.\", i.le + i.li + i.ri + i.re, path_dst.c_str(), border.tex.w);\n    if(i.te + i.ti + i.bi + i.be > border.tex.h)\n        pLogWarning(\"Invalid border: total internal/external height is %d but [%s] is only %dpx tall.\", i.te + i.ti + i.bi + i.be, path_dst.c_str(), border.tex.h);\n}\n\nGFX_t::GFX_t() noexcept\n{}\n\nbool GFX_t::load()\n{\n    m_uiPath = AppPath + \"graphics/ui/\";\n    m_uiPathTr.clear();\n\n    if(!CurrentLanguage.empty())\n    {\n        std::string langDir = CurrentLanguage + \"-\" + CurrentLangDialect;\n        if(!CurrentLangDialect.empty() && DirMan::exists(m_uiPath + \"i18n/\" + langDir))\n            m_uiPathTr = m_uiPath + \"i18n/\" + langDir + \"/\";\n        else if(DirMan::exists(m_uiPath + \"i18n/\" + CurrentLanguage))\n            m_uiPathTr = m_uiPath + \"i18n/\" + CurrentLanguage + \"/\";\n    }\n\n    loadImage(BMVs, \"BMVs\");\n    loadImage(BMWin, \"BMWin\");\n    For(i, 1, 3)\n        loadImage(Boot[i], fmt::sprintf_ne(\"Boot%d\", i));\n\n    For(i, 1, 5)\n        loadImage(CharacterName[i], fmt::sprintf_ne(\"CharacterName%d\", i));\n\n    loadImage(Chat, \"Chat\");\n\n    For(i, 0, 2)\n        loadImage(Container[i], fmt::sprintf_ne(\"Container%d\", i));\n\n    For(i, 1, 3)\n        loadImage(ECursor[i], fmt::sprintf_ne(\"ECursor%d\", i));\n\n    For(i, 0, 9)\n        loadImage(Font1[i], fmt::sprintf_ne(\"Font1_%d\", i));\n\n    For(i, 1, 3)\n        loadImage(Font2[i], fmt::sprintf_ne(\"Font2_%d\", i));\n\n    loadImage(Font2S, \"Font2S\");\n\n    For(i, 1, 2)\n        loadImage(Heart[i], fmt::sprintf_ne(\"Heart%d\", i));\n\n    For(i, 0, 8)\n        loadImage(Interface[i], fmt::sprintf_ne(\"Interface%d\", i));\n\n    loadImage(LoadCoin, \"LoadCoin\");\n    loadImage(Loader, \"Loader\");\n\n    For(i, 0, 3)\n        loadImage(MCursor[i], fmt::sprintf_ne(\"MCursor%d\", i));\n\n    For(i, 1, 4)\n    {\n        if(i == 4 && (XRender::TargetW < 800 || XRender::TargetH < 480))\n        {\n            const char *n = \"MenuGFX4-320p\";\n#   ifdef X_IMG_EXT\n            if(Files::fileExists(m_uiPath + n + UI_IMG_EXT) || Files::fileExists(m_uiPath + n + \".png\"))\n#   else\n            if(Files::fileExists(m_uiPath + n + UI_IMG_EXT))\n#   endif\n            {\n                loadImage(MenuGFX[i], n);\n                continue;\n            }\n        }\n\n#if defined(UI_PLATFORM_EXT)\n        auto n = fmt::format_ne(\"MenuGFX{0}\" UI_PLATFORM_EXT, i);\n#   ifdef X_IMG_EXT\n        if(Files::fileExists(m_uiPath + n + UI_IMG_EXT) || Files::fileExists(m_uiPath + n + \".png\"))\n#   else\n        if(Files::fileExists(m_uiPath + n + UI_IMG_EXT))\n#   endif\n        {\n            loadImage(MenuGFX[i], n);\n            continue;\n        }\n\n        pLogWarning(\"File %s%s doesn't exist, trying to load generic one...\", n.c_str(), UI_IMG_EXT);\n#endif\n        loadImage(MenuGFX[i], fmt::sprintf_ne(\"MenuGFX%d\", i));\n    }\n\n    loadImage(Mount[2], \"Mount\");\n\n    For(i, 0, 7)\n        loadImage(nCursor[i], fmt::sprintf_ne(\"nCursor%d\", i));\n\n    loadImage(TextBox, \"TextBox\");\n\n    For(i, 1, 2)\n        loadImage(Tongue[i], fmt::sprintf_ne(\"Tongue%d\", i));\n\n    // loadImage(Warp, uiPath + \"Warp\");\n\n    loadImage(YoshiWings, \"YoshiWings\");\n\n    // Add new required assets here. Also update load_gfx.cpp:loadCustomUIAssets()\n\n    if(m_loadErrors > 0)\n    {\n        // std::string msg = fmt::format_ne(\"Failed to load UI image assets from {0}. Look a log file to get more details:\\n{1}\"\n        //                                  \"\\n\\n\"\n        //                                  \"It's possible that you didn't install the game assets package, or you had installed it at the incorrect directory.\",\n        //                                  AppPath, getLogFilePath());\n        // XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_ERROR, \"UI image assets loading error\", msg);\n        m_loadErrors = 0;\n        return false;\n    }\n\n    loadImage(CycloneAcc, \"CycloneAcc\");\n\n    loadImage(EIcons, \"EditorIcons\");\n\n    if(m_loadErrors > 0)\n        m_loadErrors = 0;\n\n    loadImage(PCursor, \"PCursor\");\n\n    if(m_loadErrors > 0)\n        m_loadErrors = 0;\n\n    loadImage(Medals, \"Medals\");\n\n    if(m_loadErrors > 0)\n        m_loadErrors = 0;\n\n    loadImage(CharSelIcons, \"CharSelIcons\");\n\n    loadBorder(CharSelFrame, \"CharSelFrame\");\n\n    if(m_loadErrors > 0)\n        m_loadErrors = 0;\n\n    loadImage(Backdrop, \"Backdrop\");\n    loadBorder(Backdrop_Border, \"Backdrop_Border\");\n\n    if(m_loadErrors > 0)\n    {\n        pLogDebug(\"Missing new backdrop textures.\");\n        m_loadErrors = 0;\n    }\n\n    loadImage(WorldMapFrame_Tile, \"WorldMapFrame_Tile\");\n    loadBorder(WorldMapFrame_Border, \"WorldMapFrame_Border\");\n\n    if(m_loadErrors > 0)\n    {\n        pLogDebug(\"Missing new world map frame tile/border textures.\");\n        m_loadErrors = 0;\n    }\n\n    loadImage(Camera, \"Camera\");\n\n    if(m_loadErrors > 0)\n        m_loadErrors = 0;\n\n    loadImage(Balance, \"Balance\");\n\n    loadImage(Placeholder, \"Placeholder\");\n\n    loadImage(SaveIcons, \"SaveIcons\");\n\n    if(m_loadErrors > 0)\n        m_loadErrors = 0;\n\n    // Add new optional assets above this line. Also update load_gfx.cpp: loadCustomUIAssets(), and gfx.h: GFX_t::m_isCustomVolume.\n\n    SDL_assert_release(m_loadedImages.size() <= m_isCustomVolume);\n    SDL_memset(m_isCustom, 0, sizeof(m_loadedImages.size() * sizeof(bool)));\n\n    return true;\n}\n\nvoid GFX_t::unLoad()\n{\n    for(StdPicture *p : m_loadedImages)\n        p->reset();\n\n    m_loadedImages.clear();\n    SDL_memset(m_isCustom, 0, sizeof(m_loadedImages.size() * sizeof(bool)));\n}\n\nbool& GFX_t::isCustom(size_t i)\n{\n    SDL_assert_release(i < m_isCustomVolume);\n    return m_isCustom[i];\n}\n"
  },
  {
    "path": "src/gfx.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GFX_H\n#define GFX_H\n\n#include <vector>\n#include <string>\n\n#include \"range_arr.hpp\"\n#include \"std_picture.h\"\n#include \"graphics/gfx_frame.h\"\n\n/*!\n * \\brief Holder of commonly-used textures such as interface, font, etc.\n */\nclass GFX_t\n{\n    //! Holder of loaded textures for easier clean-up\n    std::vector<StdPicture*> m_loadedImages;\n    //! Capacity of the m_isCustom array (update when new assets are added)\n    static constexpr size_t m_isCustomVolume = 79;\n    //! Holder of \"is custom\" flag\n    bool m_isCustom[m_isCustomVolume];\n\n    std::string m_uiPathTr;\n    std::string m_uiPath;\n\n    /*!\n     * \\brief Internal function of the texture loading\n     * \\param img Target texture\n     * \\param fileName File name of the texture file (excluding extension)\n     */\n    void loadImage(StdPicture &img, const std::string &fileName);\n\n    /*!\n     * \\brief Internal function to load a frame border including its texture\n     * \\param border Target border to load\n     * \\param fileName File name of the texture file (excluding extension); border info will not include extension either\n     */\n    void loadBorder(FrameBorder& border, const std::string& fileName);\n\n    //! Counter of loading errors\n    int m_loadErrors = 0;\npublic:\n    GFX_t() noexcept;\n    bool load();\n    void unLoad();\n\n    StdPicture BMVs;\n    StdPicture BMWin;\n    RangeArr<StdPicture, 1, 3> Boot;\n    RangeArr<StdPicture, 1, 5> CharacterName;\n    StdPicture Chat;\n    RangeArr<StdPicture, 0, 2> Container;\n    RangeArr<StdPicture, 1, 3> ECursor;\n    RangeArr<StdPicture, 0, 9> Font1;\n    RangeArr<StdPicture, 1, 3> Font2;\n    StdPicture Font2S;\n    RangeArr<StdPicture, 1, 2> Heart;\n    RangeArr<StdPicture, 0, 8> Interface; // Interface[4] is 37\n    StdPicture LoadCoin;\n    StdPicture Loader;\n    RangeArr<StdPicture, 0, 3> MCursor;\n    RangeArr<StdPicture, 1, 4> MenuGFX;\n    RangeArr<StdPicture, 2, 2> Mount;\n    RangeArr<StdPicture, 0, 7> nCursor;\n    StdPicture TextBox;\n    RangeArr<StdPicture, 1, 2> Tongue;\n    // StdPicture Warp;\n    StdPicture YoshiWings;\n\n    // new graphics for TheXTech\n    StdPicture CycloneAcc;\n    StdPicture EIcons;\n    StdPicture PCursor;\n    StdPicture Medals;\n    StdPicture CharSelIcons;\n    FrameBorder CharSelFrame;\n    StdPicture Backdrop; // Backdrop is 71\n    FrameBorder Backdrop_Border;\n    StdPicture WorldMapFrame_Tile; // WorldMapFrame_Tile is 73\n    FrameBorder WorldMapFrame_Border;\n    StdPicture Camera;\n    StdPicture Balance;\n    StdPicture SaveIcons;\n\n    // menu-exclusive graphics, aren't checked in loadCustomUIAssets\n    StdPicture Placeholder;\n\n    bool &isCustom(size_t i);\n};\n\n//! Container of \"hardcoded\" (no more) graphics\nextern GFX_t GFX;\n\n#endif // GFX_H\n"
  },
  {
    "path": "src/global_constants.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GLOBALCONSTANTS_H\n#define GLOBALCONSTANTS_H\n\n#include <stdint.h>\n\n// VB6 Integer type had 16-bit precision, but this is not ideal on modern platforms. Decide based on memory constraint.\n#ifdef LOW_MEM\n    using vbint_t = int16_t;\n#else\n    using vbint_t = int;\n#endif\n\n// OBSERVE: layer and event references are no longer\n//   represented by strings but rather indices in the\n//   Layers / Events array.\n// Every object that had a Layer or Event reference\n//   now has a corresponding *String() inline function\n//   which looks up and returns the appropriate string.\ntypedef uint8_t layerindex_t;\ntypedef uint8_t eventindex_t;\ntypedef uint16_t stringindex_t;\n\n// note that the NONE indices rely on the fact that\n//   there are at most 254 layers/events,\n//   leaving the -1 (255) index unused.\n// see layers.h for the respective maximums.\nconstexpr layerindex_t LAYER_NONE = static_cast<layerindex_t>(-1);\nconstexpr layerindex_t LAYER_DEFAULT = static_cast<layerindex_t>(0);\nconstexpr layerindex_t LAYER_DESTROYED_BLOCKS = static_cast<layerindex_t>(1);\nconstexpr layerindex_t LAYER_SPAWNED_NPCS = static_cast<layerindex_t>(2);\n\nconstexpr eventindex_t EVENT_NONE = static_cast<eventindex_t>(-1);\nconstexpr eventindex_t EVENT_LEVEL_START = static_cast<eventindex_t>(0);\nconstexpr eventindex_t EVENT_PSWITCH_START = static_cast<eventindex_t>(1);\nconstexpr eventindex_t EVENT_PSWITCH_END = static_cast<eventindex_t>(2);\n\nconstexpr stringindex_t STRINGINDEX_NONE = static_cast<stringindex_t>(-1);\n\nconst int MaxLevelStrings = 65535;\n\n//Public Const MaxSavedEvents As Integer = 200\n// const int MaxSavedEvents = 200;\n\n//const int maxSelectWorlds = 100;\n\nconst int maxCreditsLines = 200;\n\n#ifdef LOW_MEM\nconst int maxSaveSlots = 3;\n#else\n// Temporarily limited to 3 while working on UI design...\nconst int maxSaveSlots = 3;\n#endif\n\nconst int maxWorldCredits = 100;\n\nconst int maxYoshiGfx = 10;\n\n// unused since multistars update\n// const int maxStarsNum = 1000;\n\n// maximum number of tracked medals and stars per level\n// WARNING: c_max_track_medals must not exceed 8 (number of bits per byte) and c_max_track_stars must not exceed 255 (largest uint8_t)\nconst int c_max_track_medals = 8;\nconst int c_max_track_stars = 255;\n\nconst int maxLocalPlayers = 4;\n\n// was previously changed to 10000; now only used for certain NPC bounds checking.\n// returned to 8000.\nconst int FLBlocks = 8000;\n\n//Public Const vScreenYOffset As Integer = 0     'Players Y on the screen\nconst int vScreenYOffset = 0;\n//Public Const maxBlocks As Integer = 20000  'Max # of blocks\nconst int maxBlocks = 20000;\n//Public Const maxPlayers As Integer = 200  'Holds the max number of players\n// Must not exceed 255 (largest uint8_t)\n#ifdef __16M__\nconst int maxPlayers = 16;\n#else\nconst int maxPlayers = 200;\n#endif\n\n//Public Const maxEffects As Integer = 1000    'Max # of effects\nconst int maxEffects = 1000;\n//Public Const maxNPCs As Integer = 5000    'Max # of NPCs\nconst int maxNPCs = 5000;\n//Public Const maxBackgrounds As Integer = 8000    'Max # of background objects\nconst int maxBackgrounds = 8000;\n//Public Const maxBlockType As Integer = 700 'Maximum number of block types\nconst int maxBlockType = 702;\n//Public Const maxBackgroundType As Integer = 200 'Maximum number of background types\nconst int maxBackgroundType = 200;\n//Public Const maxSceneType As Integer = 100 'Maximum number of scenetypes\nconst int maxSceneType = 100;\n//Public Const maxNPCType As Integer = 300 'Maximum number of NPC types\nconst int maxNPCType = 306;\n//Public Const maxEffectType As Integer = 200 'Maximum number of effect types\nconst int maxEffectType = 200;\n\n//Public Const maxWarps As Integer = 200 'Maximum number of warps\n#ifdef LOW_MEM\nconst int maxWarps = 200; // 200\n#else\nconst int maxWarps = 2000; // 200\n#endif\n\n//Public Const numBackground2 As Integer = 100  'Total # of backgrounds\nconst int numBackground2 = 100;\n//Public Const numCharacters As Integer = 5 'Maximum number of player characters\nconst int numCharacters = 5;\n//Public Const numStates As Integer = 7   'Maximum number of player states\nconst int numStates = 11;\n//Public Const maxPlayerFrames As Integer = 750 'Maximum number of player frames\nconst int maxPlayerFrames = 100 * numStates + 50;\n//Public Const maxWater As Integer = 1000\nconst int maxWater = 1000;\n//Public Const maxWorldLevels As Integer = 400   'Maximum number of levels\nconst int maxWorldLevels = 400;\n//Public Const maxWorldPaths As Integer = 2000   'Maximum number of paths\nconst int maxWorldPaths = 2000;\n//Public Const maxWorldMusic As Integer = 1000   'Maximum number of musics\nconst int maxWorldMusic = 1000;\n//! NEW: limit on world map area count\nconst int maxWorldAreas = 20;\n//Public Const numSounds As Integer = 100\nconst int numSounds = 200;\n//Public Const maxSections As Integer = 20\n#ifdef LOW_MEM\nconst int maxSections = 20;\n#else\nconst int maxSections = 200;\n#endif\n//Public Const maxTileType As Integer = 400\nconst int maxTileType = 400;\n//Public Const maxLevelType As Integer = 100\nconst int maxLevelType = 100;\n//Public Const maxPathType As Integer = 100\nconst int maxPathType = 100;\n//Public Const maxTiles As Integer = 20000\nconst int maxTiles = 20000;\n//Public Const maxScenes As Integer = 5000\nconst int maxScenes = 5000;\n\n// moved to \"screen.h\"\n//Public Const ScreenW As Integer = 800  'Game Screen Width\n// const int ScreenW = 800;\n//Public Const ScreenH As Integer = 600  'Game Screen Height\n// const int ScreenH = 600;\n\n#endif // GLOBALCONSTANTS_H\n"
  },
  {
    "path": "src/global_dirs.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"global_dirs.h\"\n\nDirListCI g_dirEpisode;\nDirListCI g_dirCustom;\n"
  },
  {
    "path": "src/global_dirs.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GLOBALDIRS_H\n#define GLOBALDIRS_H\n\n#include <Utils/dir_list_ci.h>\n\n// extern DirListCI g_dirAssets;\nextern DirListCI g_dirEpisode;\nextern DirListCI g_dirCustom;\n\n#endif // #ifndef GLOBALDIRS_H\n"
  },
  {
    "path": "src/global_strings.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"global_strings.h\"\n\n#include \"sdl_proxy/sdl_assert.h\"\n\n#define STRS_UNIQUENESS_TRACKING\n\n\n// utilities for stringindex_t\nconst std::string g_emptyString = \"\";\n//! Strings storage\nstatic std::vector<std::string> g_LevelString;\n//! Number of world strings\nstatic size_t g_numWorldString = 0;\n//! List of free indexes appearing at the moddle of array\nstatic std::vector<int> g_FreeIndexes;\n\n#ifdef STRS_UNIQUENESS_TRACKING\n//! Counter of usages per every string\nstatic std::vector<int> g_LevelStringUsages;\n//! String-To-Index map\nstatic std::unordered_map<std::string, int> g_uniqueStringsIds;\n#endif\n\n#ifdef STRS_UNIQUENESS_TRACKING\nstatic std::vector<std::string> g_LevelString_backup;\nstatic std::vector<int> g_LevelStringUsages_backup;\nstatic std::vector<int> g_FreeIndexes_backup;\nstatic std::unordered_map<std::string, int> g_uniqueStringsIds_backup;\n#endif\n\n\n\nsize_t StringsBankSize()\n{\n    return g_LevelString.size();\n}\n\nsize_t StringsUnusedEntries()\n{\n    return g_FreeIndexes.size();\n}\n\nvoid SaveWorldStrings()\n{\n#ifdef STRS_UNIQUENESS_TRACKING\n    g_LevelString_backup = g_LevelString;\n    g_LevelStringUsages_backup = g_LevelStringUsages;\n    g_FreeIndexes_backup = g_FreeIndexes;\n    g_uniqueStringsIds_backup = g_uniqueStringsIds;\n#endif\n\n    g_numWorldString = g_LevelString.size();\n}\n\nvoid RestoreWorldStrings()\n{\n    EditorCursor.ClearStrings();\n\n#ifdef STRS_UNIQUENESS_TRACKING\n    g_LevelString = g_LevelString_backup;\n    g_LevelStringUsages = g_LevelStringUsages_backup;\n    g_FreeIndexes = g_FreeIndexes_backup;\n    g_uniqueStringsIds = g_uniqueStringsIds_backup;\n#else\n    for(auto i = g_FreeIndexes.begin(); i != g_FreeIndexes.end(); )\n    {\n        if(*i >= (int)g_numWorldString)\n            i = g_FreeIndexes.erase(i);\n        else\n            ++i;\n    }\n    g_LevelString.resize(g_numWorldString);\n#endif\n}\n\nvoid ClearStringsBank()\n{\n    EditorCursor.ClearStrings();\n\n    g_LevelString.clear();\n    g_numWorldString = 0;\n\n#ifdef STRS_UNIQUENESS_TRACKING\n    g_LevelStringUsages.clear();\n    g_uniqueStringsIds.clear();\n    g_FreeIndexes.clear();\n#endif\n}\n\nconst std::string& GetS(stringindex_t index)\n{\n    if(index == STRINGINDEX_NONE)\n        return g_emptyString;\n\n    SDL_assert_release(index < g_LevelString.size());\n\n    return g_LevelString[index];\n}\n\nvoid SetS(stringindex_t& index, const std::string target)\n{\n    if(index == STRINGINDEX_NONE && target.empty())\n        return;\n\n    SDL_assert_release(index <= STRINGINDEX_NONE);\n\n    bool stringEmpty = index == STRINGINDEX_NONE && g_LevelString.size() < MaxLevelStrings;\n\n    if(!stringEmpty)\n    {\n#ifdef STRS_UNIQUENESS_TRACKING\n        SDL_assert_release(index < g_LevelString.size());\n        if(g_LevelString[index] == target)\n            return; // Do nothing, there is an attempt to set the same string\n\n        g_LevelStringUsages[index]--;\n        if(g_LevelStringUsages[index] == 0)\n        {\n            g_uniqueStringsIds.erase(g_LevelString[index]);\n            g_LevelString[index].clear();\n            g_FreeIndexes.push_back(index);\n        }\n        stringEmpty = true;\n#else\n        SDL_assert_release(index < g_LevelString.size());\n        g_LevelString[index] = target;\n#endif\n    }\n\n    if(stringEmpty)\n    {\n#ifdef STRS_UNIQUENESS_TRACKING\n        auto f = g_uniqueStringsIds.find(target);\n        if(f == g_uniqueStringsIds.end())\n        {\n            if(g_FreeIndexes.empty())\n            {\n                index = (stringindex_t)g_LevelString.size();\n                g_LevelString.push_back(target);\n                g_LevelStringUsages.push_back(1);\n            }\n            else\n            {\n                index = g_FreeIndexes.back();\n                g_FreeIndexes.pop_back();\n                g_LevelString[index] = target;\n                g_LevelStringUsages[index] = 1;\n            }\n            g_uniqueStringsIds[target] = index;\n        }\n        else\n        {\n            index = f->second;\n            g_LevelStringUsages[index]++;\n        }\n#else\n        if(g_FreeIndexes.empty())\n        {\n            index = (stringindex_t)g_LevelString.size();\n            g_LevelString.push_back(target);\n        }\n        else\n        {\n            index = g_FreeIndexes.back();\n            g_FreeIndexes.pop_back();\n            g_LevelString[index] = target;\n        }\n#endif\n    }\n}\n\nstringindex_t AllocS(const std::string& target)\n{\n    stringindex_t out = STRINGINDEX_NONE;\n    SetS(out, target);\n    return out;\n}\n\nvoid FreeS(stringindex_t& index)\n{\n    if(index == STRINGINDEX_NONE)\n        return;\n\n    SDL_assert_release(index <= STRINGINDEX_NONE);\n    SDL_assert_release(index < g_LevelString.size());\n\n#ifdef STRS_UNIQUENESS_TRACKING\n    g_LevelStringUsages[index]--;\n    if(g_LevelStringUsages[index] == 0)\n    {\n        g_uniqueStringsIds.erase(g_LevelString[index]);\n        g_LevelString[index].clear();\n        g_FreeIndexes.push_back(index);\n    }\n#else\n    g_FreeIndexes.push_back(index);\n#endif\n\n    index = STRINGINDEX_NONE;\n}\n\nstd::string* PtrS(stringindex_t& index)\n{\n#ifdef STRS_UNIQUENESS_TRACKING\n    // deduplicate the string\n    std::string target;\n    if(index != STRINGINDEX_NONE && g_LevelStringUsages[index] > 1)\n    {\n        target = GetS(index);\n        FreeS(index);\n    }\n#endif\n\n    if(index == STRINGINDEX_NONE)\n    {\n        if(g_LevelString.size() >= MaxLevelStrings)\n            return nullptr;\n\n        index = (stringindex_t)g_LevelString.size();\n        g_LevelString.push_back(std::string());\n\n#ifdef STRS_UNIQUENESS_TRACKING\n        g_LevelStringUsages.push_back(1);\n        g_LevelString[index] = target;\n#endif\n    }\n\n    SDL_assert_release(index < g_LevelString.size());\n\n    return &g_LevelString[index];\n}\n"
  },
  {
    "path": "src/global_strings.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n// utilities for stringindex_t\n// everything here is defined in `globals.cpp`\n\n#pragma once\n#ifndef GLOBAL_STRINGS_H\n#define GLOBAL_STRINGS_H\n\n#include \"global_constants.h\"\n#include <vector>\n#include <string>\n#include <unordered_map>\n\nextern const std::string g_emptyString;\n\nextern size_t StringsBankSize();\nextern size_t StringsUnusedEntries();\n\nextern void SaveWorldStrings();\nextern void RestoreWorldStrings();\nextern void ClearStringsBank();\n\n/*!\n * \\brief Get string from the bank by index\n * \\param index Index of string\n * \\return Const referrence to the actual string\n */\nextern const std::string& GetS(stringindex_t index);\n\n/*!\n * \\brief Set the string to the index\n * \\param index destinition string field\n * \\param target Target string data to assign\n */\nextern void SetS(stringindex_t& index, const std::string target);\n\n/*!\n * \\brief Create new string index entry or return exist matching\n * \\param target Targete string data to assing\n * \\return destinition string field\n */\nextern stringindex_t AllocS(const std::string& target);\n\n/*!\n * \\brief Clear the string index entry\n * \\param index Target index to clear\n */\nextern void FreeS(stringindex_t& index);\n\n/*!\n * \\brief Get string as pointer from the bank by index\n * \\param index Index of string\n * \\return Pointer to the actual string\n */\nextern std::string* PtrS(stringindex_t& index);\n\n#endif // #ifndef GLOBAL_STRINGS_H\n"
  },
  {
    "path": "src/globals.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"core/events.h\"\n#include \"globals.h\"\n#include \"npc_traits.h\"\n#include <fmt_format_ne.h>\n#include <cmath>\n\nbool GameIsActive = false;\nstd::string AppPath;\n\n// int numSavedEvents = 0;\n// RangeArr<std::string, 1, MaxSavedEvents> SavedEvents;\n// RangeArrI<bool, 1, 4, false> BlockSwitch;\n// RangeArrI<bool, 2, 7, false> PowerUpUnlock;\n// long myBackBuffer = 0;\n// long myBufferBMP = 0;\n// int AllCharBlock = 0;\n// bool StartMenu = false;\nuint32_t CommonFrame = 0;\nuint32_t CommonFrame_NotFrozen = 0;\nbool ScrollRelease = false;\nbool TakeScreen = false;\nbool ShowOnScreenHUD = true;\nstd::string LB;\nstd::string EoT;\n\nstd::string Checkpoint;\nstd::vector<Checkpoint_t> CheckpointsList;\nstd::vector<LevelWarpSaveEntry_t> LevelWarpSaveEntries;\nbool MagicHand = false;\nRangeArr<Player_t, 1, maxLocalPlayers> testPlayer;\nbool ClearBuffer = false;\nint numLocked = 0;\n// replaced with g_config.fullscreen\n// bool resChanged = false;\n\nint16_t testStartWarp = 0;\n\n// moved into game_loop.cpp\n// PauseCode GamePaused = PauseCode::None;\n\nstd::string MessageTitle;\nMessageType g_MessageType = MESSAGE_TYPE_NORMAL;\n\nstd::string MessageText;\n\n// int NumSelectWorld  = 0;\n// std::vector<SelectWorld_t> SelectWorld;\n\nstd::string g_recentAssetPack;\nstd::string g_recentWorld1p;\nstd::string g_recentWorld2p;\nstd::string g_recentWorldEditor;\nstd::string g_recentWorldIntro;\nstd::string g_recentWorldOutro;\n//bool ShowFPS = false;\nint PrintFPS = 0;\nbool g_VanillaCam = false;\n// moved to \"screen.cpp\"\n// RangeArr<vScreen_t, 0, 2> vScreen;\n// int ScreenType = 0;\n// int DScreenType = 0;\nbool LevelEditor = false;\nbool WorldEditor = false;\nbool g_forceCharacter = false;\nRangeArr<PlayerStart_t, 1, 2> PlayerStart;\nRangeArrI<bool, 0, 20, false> blockCharacter;\nRangeArrI<vbint_t, 0, maxPlayers, 0> OwedMount;\nRangeArrI<vbint_t, 0, maxPlayers, 0> OwedMountType;\nbool AutoUseModern = false;\nRangeArr<numf_t, 0, maxSections> AutoX;\nRangeArr<numf_t, 0, maxSections> AutoY;\nint numStars = 0;\nRangeArr<Water_t, 0, maxWater> Water;\nint numWater = 0;\nstd::vector<Star_t> Star;\nstd::string GoToLevel;\nbool GoToLevelNoGameThing = false;\nstd::string StartLevel;\nbool NoMap = false;\nbool RestartLevel = false;\nint WorldStarsShowPolicy = -1;\n// float LevelChop[maxSections + 1];\n// RangeArr<int, -FLBlocks, FLBlocks> FirstBlock;\n// RangeArr<int, -FLBlocks, FLBlocks> LastBlock;\nint MidBackground = 1;\nint LastBackground = 1;\nint iBlocks = 0;\nRangeArrI<vbint_t, 0, maxBlocks, 0> iBlock;\nint numTiles = 0;\nint numScenes = 0;\nRangeArr<std::string, 0, maxSections> CustomMusic;\nint numSections = 0;\nRangeArr<SpeedlessLocation_t, 0, maxSections> level;\nRangeArrI<bool, 0, maxSections, false> LevelWrap;\nRangeArrI<bool, 0, maxSections, false> LevelVWrap;\nRangeArrI<bool, 0, maxSections, false> OffScreenExit;\nRangeArrI<vbint_t, 0, maxSections, 0> bgMusic;\nRangeArrI<vbint_t, 0, maxSections, 0> bgMusicREAL;\nRangeArrI<vbint_t, 0, maxSections, 0> Background2REAL;\nRangeArr<IntegerLocation_t, 0, maxSections> LevelREAL;\nint curMusic = 0;\n// RangeArrI<long, 0, maxSections, 0> bgColor;    // unused since SMBX64, removed\nRangeArrI<vbint_t, 0, maxSections, 0> Background2;\nRangeArr<WorldPath_t, 1, maxWorldPaths> WorldPath;\nint numWorldPaths = 0;\nint numWarps = 0;\nRangeArr<Warp_t, 1, maxWarps> Warp;\nRangeArr<Tile_t, 1, maxTiles> Tile;\nRangeArr<Scene_t, 1, maxScenes> Scene;\nRangeArr<CreditLine_t, 1, maxCreditsLines> Credit;\nint numWorldCredits = 0;\nint CreditOffsetY = 0;\nint CreditTotalHeight = 0;\nint numCredits = 0;\nint numBlock = 0;\nint numBackground = 0;\nint numNPCs = 0;\nint numEffects = 0;\nint numPlayers = 0;\nint numWorldLevels = 0;\nRangeArr<WorldMusic_t, 1, maxWorldMusic> WorldMusic;\nint numWorldMusic = 0;\nRangeArr<WorldArea_t, 1, maxWorldAreas> WorldArea;\nint numWorldAreas = 0;\nRangeArr<WorldLevel_t, 1, maxWorldLevels> WorldLevel;\nRangeArr<Background_t, 1, (maxBackgrounds + maxWarps)> Background;\nRangeArr<Effect_t, 1, maxEffects> Effect;\n\nRangeArr<NPC_t, -128, maxNPCs> NPC;\nRangeArr<Block_t, 0, maxBlocks> Block;\n\nRangeArr<Player_t, 0, maxPlayers> Player;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> MarioFrameX;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> MarioFrameY;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> LuigiFrameX;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> LuigiFrameY;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> PeachFrameX;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> PeachFrameY;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> ToadFrameX;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> ToadFrameY;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> LinkFrameX;\nRangeArrI<int8_t, 0, maxPlayerFrames, 0> LinkFrameY;\nRangeArrI<bool, 0, maxBackgroundType, false> BackgroundFence;\n\nRangeArr<NPCTraits_t, 0, maxNPCType> NPCTraits;\n\n#if 0\nRangeArrI<int, 0, maxNPCType, 0> NPCFrameOffsetX;\nRangeArrI<int, 0, maxNPCType, 0> NPCFrameOffsetY;\nRangeArrI<int, 0, maxNPCType, 0> NPCWidth;\nRangeArrI<int, 0, maxNPCType, 0> NPCHeight;\nRangeArrI<int, 0, maxNPCType, 0> NPCWidthGFX;\nRangeArrI<int, 0, maxNPCType, 0> NPCHeightGFX;\nRangeArr<float, 0, maxNPCType> NPCSpeedvar;\n\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsAShell;\nRangeArrI<bool, 0, maxNPCType, false> NPCIsABlock;\nRangeArrI<bool, 0, maxNPCType, false> NPCIsAHit1Block;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsABonus;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsACoin;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsAVine;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsAnExit;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsAParaTroopa;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsCheep;\nRangeArrI<bool, 0, maxNPCType, false> NPCJumpHurt;\nRangeArrI<bool, 0, maxNPCType, false> NPCNoClipping;\nRangeArrI<int, 0, maxNPCType, 0> NPCScore;\nRangeArrI<bool, 0, maxNPCType, false> NPCCanWalkOn;\nRangeArrI<bool, 0, maxNPCType, false> NPCGrabFromTop;\nRangeArrI<bool, 0, maxNPCType, false> NPCTurnsAtCliffs;\nRangeArrI<bool, 0, maxNPCType, false> NPCWontHurt;\nRangeArrI<bool, 0, maxNPCType, false> NPCMovesPlayer;\nRangeArrI<bool, 0, maxNPCType, false> NPCStandsOnPlayer;\nRangeArrI<bool, 0, maxNPCType, false> NPCIsGrabbable;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsBoot;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsYoshi;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsToad;\nRangeArrI<bool, 0, maxNPCType, false> NPCNoYoshi;\nRangeArrI<bool, 0, maxNPCType, false> NPCForeground;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsABot;\n// RangeArrI<bool, 0, maxNPCType, false> NPCDefaultMovement;\n// RangeArrI<bool, 0, maxNPCType, false> NPCIsVeggie;\nRangeArrI<bool, 0, maxNPCType, false> NPCNoFireBall;\nRangeArrI<bool, 0, maxNPCType, false> NPCNoIceBall;\nRangeArrI<bool, 0, maxNPCType, false> NPCNoGravity;\n\nRangeArrI<int, 0, maxNPCType, 0> NPCFrame;\nRangeArrI<int, 0, maxNPCType, 0> NPCFrameSpeed;\nRangeArrI<int, 0, maxNPCType, 0> NPCFrameStyle;\n#endif\n\nRangeArrI<bool, 0, maxBlockType, false> BlockIsSizable;\nRangeArrI<vbint_t, 0, maxBlockType, 0> BlockSlope;\nRangeArrI<vbint_t, 0, maxBlockType, 0> BlockSlope2;\n\n// moved into vScreen\n// RangeArr<double, 0, maxPlayers> vScreenX;\n// RangeArr<double, 0, maxPlayers> vScreenY;\n\n// moved into qScreenLoc\n// RangeArr<double, 0, maxPlayers> qScreenX;\n// RangeArr<double, 0, maxPlayers> qScreenY;\n\nbool qScreen = false;\nbool qScreen_canonical = false;\n// RangeArr<vScreen_t, 0, 2> qScreenLoc;\n\nRangeArrI<vbint_t, 0, maxBlockType, 0> BlockWidth;\nRangeArrI<vbint_t, 0, maxBlockType, 0> BlockHeight;\n// RangeArrI<int, 0, maxBlockType, 0> BonusWidth;\n// RangeArrI<int, 0, maxBlockType, 0> BonusHeight;\nRangeArrI<vbint_t, 0, maxEffectType, 0> EffectWidth;\nRangeArrI<vbint_t, 0, maxEffectType, 0> EffectHeight;\n\nEffectDefaults_t EffectDefaults;\nRangeArrI<vbint_t, 1, maxSceneType, 0> SceneWidth;\nRangeArrI<vbint_t, 1, maxSceneType, 0> SceneHeight;\nRangeArrI<vbint_t, 0, 9, 0> SpecialFrame;\nRangeArr<vbint_t, 0, 9> SpecialFrameCount;\nRangeArrI<bool, 1, maxBackgroundType, false> BackgroundHasNoMask;\nRangeArrI<bool, 0, maxBackgroundType, false> Foreground;\nRangeArrI<vbint_t, 1, maxBackgroundType, 0> BackgroundWidth;\nRangeArrI<vbint_t, 1, maxBackgroundType, 0> BackgroundHeight;\nRangeArrI<vbint_t, 1, maxBackgroundType, 0> BackgroundFrame;\nRangeArrI<vbint_t, 1, maxBackgroundType, 0> BackgroundFrameCount;\nRangeArrI<vbint_t, 1, maxBlockType, 0> BlockFrame;\nRangeArrI<vbint_t, 1, maxBlockType, 0> BlockFrame2;\n\n// deprecated\n// RangeArrI<int, 1, 1000, 0> sBlockArray;\n// int sBlockNum = 0;\n\nRangeArrI<vbint_t, 1, maxSceneType, 0> SceneFrame;\nRangeArrI<vbint_t, 1, maxSceneType, 0> SceneFrame2;\nRangeArrI<vbint_t, 1, maxTileType, 0> TileWidth;\nRangeArrI<vbint_t, 1, maxTileType, 0> TileHeight;\nRangeArrI<vbint_t, 1, maxTileType, 0> TileFrame;\nRangeArrI<vbint_t, 1, maxTileType, 0> TileFrame2;\nRangeArrI<vbint_t, 1, 100, 0> LevelFrame;\nRangeArrI<vbint_t, 1, 100, 0> LevelFrame2;\nRangeArrI<bool, 1, maxBlockType, false> BlockHasNoMask;\n// RangeArrI<bool, 1, 100, false> LevelHasNoMask;\nRangeArrI<bool, 0, maxBlockType, false> BlockOnlyHitspot1;\nRangeArrI<bool, 0, maxBlockType, false> BlockKills;\nRangeArrI<bool, 0, maxBlockType, false> BlockKills2;\nRangeArrI<bool, 0, maxBlockType, false> BlockHurts;\nRangeArrI<bool, 0, maxBlockType, false> BlockPSwitch;\nRangeArrI<bool, 0, maxBlockType, false> BlockNoClipping;\nRangeArrI<vbint_t, 1, 10, 0> CoinFrame;\nRangeArrI<vbint_t, 1, 10, 0> CoinFrame2;\nEditorCursor_t EditorCursor;\nEditorControls_t EditorControls;\nSharedControls_t l_SharedControls;\n\nCursorControls_t SharedCursor;\n// RangeArr<CursorControls_t, 1, maxLocalPlayers> PlayerCursor;\n\nbool SharedPause;\nbool SharedPauseLegacy;\nbool SharedPauseForce;\n\n// RangeArrI<int, 1, numSounds, 0> Sound;\nRangeArrI<vbint_t, 1, numSounds, 0> SoundPause;\nbool ErrorQuit = false;\nbool EndLevel = false;\nLevelMacro_t LevelMacro = LEVELMACRO_OFF;\nint LevelMacroWhich = 0;\nint LevelMacroCounter = 0;\nbool CanWallJump = false;\n\n// information about the currently loaded file\nstd::string FileName;\nstd::string FileNameFull;\nstd::string FullFileName;\nstd::string FileNamePath;\nstd::string FileRecentSubHubLevel;\nint FileFormat = 0;\n\n// backup information to restore when returning to world map\nstd::string FileNameWorld;\nstd::string FileNameFullWorld;\nstd::string FileNamePathWorld;\nint FileFormatWorld = 0;\n\nbool IsEpisodeIntro = false;\nbool IsHubLevel = false;\nint Coins = 0;\nint Lives = 0;\nint g_100s = 0;\n// bool EndIntro = false;\n// bool ExitMenu = false;\nbool LevelSelect = false;\nbool LevelRestartRequested = false;\nRangeArr<WorldPlayer_t, 0, 1> WorldPlayer;\nLevelBeatCode_t LevelBeatCode = BEATCODE_NONE;\nint curWorldLevel = 0;\nint curWorldMusic = 0;\nstd::string curWorldMusicFile;\nRangeArrI<bool, 0, maxSections, false> NoTurnBack;\nRangeArrI<bool, 0, maxSections, false> UnderWater;\nRangeArrI<stringindex_t, 0, maxSections, STRINGINDEX_NONE> SectionJSONInfo;\nstd::string WldxCustomParams;\nstd::vector<std::string> SubHubLevels;\nbool TestLevel = false;\nbool GameMenu = false;\nstd::string WorldName;\nint selWorld = 0;\nint selSave = 0;\nint PSwitchTime = 0;\nint PSwitchStop = 0;\nint InvincibilityTime = 0;\nint PSwitchPlayer = 0;\n\nRangeArr<SaveSlotInfo_t, 1, maxSaveSlots> SaveSlotInfo;\n\nint BeltDirection = 0;\nbool BeatTheGame = false;\n//int cycleCount = 0;\n//double fpsTime = 0.0;\n//double fpsCount = 0.0;\n//bool FrameSkip = false;\n//double GoalTime = 0.0;\n//double overTime = 0.0;\n// int worldCurs = 0;\n// int minShow = 0;\n// int maxShow = 0;\n\nint ReturnWarp = 0;\nint ReturnWarpSaved = 0;\nint StartWarp = 0;\nPhysics_t Physics;\nint MenuCursor = 0;\nint MenuMode = 0;\nbool MenuCursorCanMove = false;\nbool MenuCursorCanMove_Back = false;\n// bool NextFrame = false;\nint StopHit = 0;\nbool MouseRelease = false;\n// bool TestFullscreen = false;\n//bool keyDownAlt = false;\n//bool keyDownEnter = false;\nbool BlocksSorted = false;\nint SingleCoop = 0;\nbool g_ClonedPlayerMode = false;\n//std::string CheatString;\nbool GameOutro = false;\nbool GameOutroDoQuit = false;\nint CreditChop = 0;\nint EndCredits = 0;\nint curStars = 0;\n// int maxStars = 0;\nbool ShadowMode = false;\nbool MultiHop = false;\nbool SuperSpeed = false;\nbool WalkAnywhere = false;\nbool FlyForever = false;\nbool FreezeNPCs = false;\nbool CaptainN = false;\nbool FlameThrower = false;\nbool CoinMode = false;\n// bool WorldUnlock = false;\n//bool MaxFPS = false;\nbool GodMode = false;\nbool GrabAll = false;\nbool Cheater = false;\n#ifdef ENABLE_ANTICHEAT_TRAP\nbool CheaterMustDie = false;\n#endif\n\nRangeArr<std::string, 1, maxWorldCredits> WorldCredits;\nint Score = 0;\nRangeArrI<int, 1, 13, 0> Points;\nint MaxWorldStars = 0;\n// bool Debugger = false;\nRangeArr<SavedChar_t, 0, 10> SavedChar;\n\nbool LoadingInProcess = false;\nint LoadCoins = 0;\nunsigned int LoadCoinsT = 0;\n\n// RangeArrI<bool, 1, maxBlockType, false> GFXBlockCustom;\n//RangeArrI<long, 1, maxBlockType, 0> GFXBlock;\n//RangeArrI<long, 1, maxBlockType, 0> GFXBlockMask;\nRangeArr<StdPicture, 1, maxBlockType> GFXBlockBMP;\n//RangeArr<StdPicture, 1, maxBlockType> GFXBlockMaskBMP;\n// RangeArrI<bool, 1, numBackground2, false> GFXBackground2Custom;\n//RangeArrI<long, 1, numBackground2, 0> GFXBackground2;\nRangeArr<StdPicture, 1, numBackground2> GFXBackground2BMP;\n// RangeArrI<vbint_t, 1, numBackground2, 0> GFXBackground2Height;\n// RangeArrI<vbint_t, 1, numBackground2, 0> GFXBackground2Width;\n// RangeArrI<bool, 1, maxNPCType, false> GFXNPCCustom;\n//RangeArrI<long, 1, maxNPCType, 0> GFXNPC;\n//RangeArrI<long, 1, maxNPCType, 0> GFXNPCMask;\nRangeArr<StdPicture, 0, maxNPCType> GFXNPCBMP;\n//RangeArr<StdPicture, 0, maxNPCType> GFXNPCMaskBMP;\n// RangeArrI<int, 1, maxNPCType, 0> GFXNPCHeight;\n// RangeArrI<int, 1, maxNPCType, 0> GFXNPCWidth;\nRangeArrI<bool, 1, maxEffectType, false> GFXEffectCustom;\n//RangeArrI<long, 1, maxEffectType, 0> GFXEffect;\n//RangeArrI<long, 1, maxEffectType, 0> GFXEffectMask;\nRangeArr<StdPicture, 1, maxEffectType> GFXEffectBMP;\n//RangeArr<StdPicture, 1, maxEffectType> GFXEffectMaskBMP;\n// RangeArrI<vbint_t, 1, maxEffectType, 0> GFXEffectHeight;\n// RangeArrI<vbint_t, 1, maxEffectType, 0> GFXEffectWidth;\n// RangeArrI<bool, 1, maxBackgroundType, false> GFXBackgroundCustom;\n//RangeArrI<long, 1, maxBackgroundType, 0> GFXBackground;\n//RangeArrI<long, 1, maxBackgroundType, 0> GFXBackgroundMask;\nRangeArr<StdPicture, 1, maxBackgroundType> GFXBackgroundBMP;\n//RangeArr<StdPicture, 1, maxBackgroundType> GFXBackgroundMaskBMP;\n// RangeArrI<vbint_t, 1, maxBackgroundType, 0> GFXBackgroundHeight;\n// RangeArrI<vbint_t, 1, maxBackgroundType, 0> GFXBackgroundWidth;\n\nconst char *GFXPlayerNames[numCharacters] =\n{\n    \"mario\", \"luigi\", \"peach\", \"toad\", \"link\"\n};\nRangeArr<StdPicture, 1, numStates> *GFXCharacterBMP[numCharacters] =\n{\n    &GFXMarioBMP, &GFXLuigiBMP, &GFXPeachBMP, &GFXToadBMP, &GFXLinkBMP\n};\n// RangeArrI<vbint_t, 1, 10, 0> *GFXCharacterWidth[numCharacters] =\n// {\n//     &GFXMarioWidth, &GFXLuigiWidth, &GFXPeachHeight, &GFXToadWidth, &GFXLinkWidth\n// };\n// RangeArrI<vbint_t, 1, 10, 0> *GFXCharacterHeight[numCharacters] =\n// {\n//     &GFXMarioHeight, &GFXLuigiHeight, &GFXPeachHeight, &GFXToadHeight, &GFXLinkHeight\n// };\n// RangeArrI<bool, 1, 10, false> *GFXCharacterCustom[numCharacters] =\n// {\n//     &GFXMarioCustom, &GFXLuigiCustom, &GFXPeachCustom, &GFXToadCustom, &GFXLinkCustom\n// };\n\n// RangeArrI<bool, 1, 10, false> GFXMarioCustom;\n//RangeArrI<long, 1, 10, 0> GFXMario;\n//RangeArrI<long, 1, 10, 0> GFXMarioMask;\nRangeArr<StdPicture, 1, numStates> GFXMarioBMP;\n//RangeArr<StdPicture, 1, 10> GFXMarioMaskBMP;\n// RangeArrI<vbint_t, 1, 10, 0> GFXMarioHeight;\n// RangeArrI<vbint_t, 1, 10, 0> GFXMarioWidth;\n// RangeArrI<bool, 1, 10, false> GFXLuigiCustom;\n//RangeArrI<long, 1, 10, 0> GFXLuigi;\n//RangeArrI<long, 1, 10, 0> GFXLuigiMask;\nRangeArr<StdPicture, 1, numStates> GFXLuigiBMP;\n//RangeArr<StdPicture, 1, 10> GFXLuigiMaskBMP;\n// RangeArrI<vbint_t, 1, 10, 0> GFXLuigiHeight;\n// RangeArrI<vbint_t, 1, 10, 0> GFXLuigiWidth;\n// RangeArrI<bool, 1, 10, false> GFXPeachCustom;\n//RangeArrI<long, 1, 10, 0> GFXPeach;\n//RangeArrI<long, 1, 10, 0> GFXPeachMask;\nRangeArr<StdPicture, 1, numStates> GFXPeachBMP;\n//RangeArr<StdPicture, 1, 10> GFXPeachMaskBMP;\n// RangeArrI<vbint_t, 1, 10, 0> GFXPeachHeight;\n// RangeArrI<vbint_t, 1, 10, 0> GFXPeachWidth;\n// RangeArrI<bool, 1, 10, false> GFXToadCustom;\n//RangeArrI<long, 1, 10, 0> GFXToad;\n//RangeArrI<long, 1, 10, 0> GFXToadMask;\nRangeArr<StdPicture, 1, numStates> GFXToadBMP;\n//RangeArr<StdPicture, 1, 10> GFXToadMaskBMP;\n// RangeArrI<vbint_t, 1, 10, 0> GFXToadHeight;\n// RangeArrI<vbint_t, 1, 10, 0> GFXToadWidth;\n\n// RangeArrI<bool, 1, 10, false> GFXLinkCustom;\n//RangeArrI<long, 1, 10, 0> GFXLink;\n//RangeArrI<long, 1, 10, 0> GFXLinkMask;\nRangeArr<StdPicture, 1, numStates> GFXLinkBMP;\n//RangeArr<StdPicture, 1, 10> GFXLinkMaskBMP;\n// RangeArrI<vbint_t, 1, 10, 0> GFXLinkHeight;\n// RangeArrI<vbint_t, 1, 10, 0> GFXLinkWidth;\n\n// RangeArrI<bool, 1, maxYoshiGfx, false> GFXYoshiBCustom;\n//RangeArrI<long, 1, 10, 0> GFXYoshiB;\n//RangeArrI<long, 1, 10, 0> GFXYoshiBMask;\nRangeArr<StdPicture, 1, 10> GFXYoshiBBMP;\n//RangeArr<StdPicture, 1, 10> GFXYoshiBMaskBMP;\n// RangeArrI<bool, 1, 10, false> GFXYoshiTCustom;\n//RangeArrI<long, 1, 10, 0> GFXYoshiT;\n//RangeArrI<long, 1, 10, 0> GFXYoshiTMask;\nRangeArr<StdPicture, 1, 10> GFXYoshiTBMP;\n//RangeArr<StdPicture, 1, 10> GFXYoshiTMaskBMP;\n// RangeArrI<bool, 1, maxTileType, false> GFXTileCustom;\n//RangeArrI<long, 1, maxTileType, 0> GFXTile;\nRangeArr<StdPicture, 1, maxTileType> GFXTileBMP;\n// RangeArrI<vbint_t, 1, maxTileType, 0> GFXTileHeight;\n// RangeArrI<vbint_t, 1, maxTileType, 0> GFXTileWidth;\n// RangeArrI<bool, 0, maxLevelType, false> GFXLevelCustom;\n//RangeArrI<long, 0, maxLevelType, 0> GFXLevel;\n//RangeArrI<long, 0, maxLevelType, 0> GFXLevelMask;\nRangeArr<StdPicture, 0, maxLevelType> GFXLevelBMP;\n//RangeArr<StdPicture, 0, maxLevelType> GFXLevelMaskBMP;\n// RangeArrI<vbint_t, 0, maxLevelType, 0> GFXLevelHeight;\n// RangeArrI<vbint_t, 0, maxLevelType, 0> GFXLevelWidth;\nRangeArrI<bool, 0, maxLevelType, false> GFXLevelBig;\n// RangeArrI<bool, 1, maxSceneType, false> GFXSceneCustom;\n//RangeArrI<long, 1, maxSceneType, 0> GFXScene;\n//RangeArrI<long, 1, maxSceneType, 0> GFXSceneMask;\nRangeArr<StdPicture, 1, maxSceneType> GFXSceneBMP;\n//RangeArr<StdPicture, 1, maxSceneType> GFXSceneMaskBMP;\n// RangeArrI<vbint_t, 1, maxSceneType, 0> GFXSceneHeight;\n// RangeArrI<vbint_t, 1, maxSceneType, 0> GFXSceneWidth;\n// RangeArrI<bool, 1, maxPathType, false> GFXPathCustom;\n//RangeArrI<long, 1, maxPathType, 0> GFXPath;\n//RangeArrI<long, 1, maxPathType, 0> GFXPathMask;\nRangeArr<StdPicture, 1, maxPathType> GFXPathBMP;\n//RangeArr<StdPicture, 1, maxPathType> GFXPathMaskBMP;\n// RangeArrI<vbint_t, 1, maxPathType, 0> GFXPathHeight;\n// RangeArrI<vbint_t, 1, maxPathType, 0> GFXPathWidth;\n\n// RangeArrI<bool, 1, numCharacters, false> GFXPlayerCustom;\n//RangeArrI<long, 1, numCharacters, 0> GFXPlayer;\n//RangeArrI<long, 1, numCharacters, 0> GFXPlayerMask;\nRangeArr<StdPicture, 1, numCharacters> GFXPlayerBMP;\n//RangeArr<StdPicture, 1, numCharacters> GFXPlayerMaskBMP;\n// RangeArrI<vbint_t, 1, numCharacters, 0> GFXPlayerHeight;\n// RangeArrI<vbint_t, 1, numCharacters, 0> GFXPlayerWidth;\n\n// int PlayerCharacter = 0;\n// int PlayerCharacter2 = 0;\n// double MenuMouseX = 0.0;\n// double MenuMouseY = 0.0;\n// Sint32 MenuWheelDelta = 0;\n// bool MenuWheelMoved = false;\n// bool MenuMouseDown = false;\n// bool MenuMouseBack = false;\nbool MenuMouseRelease = false;\n// bool MenuMouseMove = false;\nbool MenuMouseClick = false;\n\nbool ForcedControls = false;\nControls_t ForcedControl;\n// int SyncCount = 0;\n// bool noUpdate = false;\n//double gameTime = 0.0;\n\n// deprecated by g_config.audio_enable\n// bool noSound = false;\n\n//bool neverPause = false;\n//double tempTime = 0.0;\nbool BattleMode = false;\nint BattleWinner = 0;\nRangeArrI<int, 1, maxPlayers, 0> BattleLives;\nint BattleIntro = 0;\nint BattleOutro = 0;\nstd::string LevelName;\nstd::string CurrentLanguage;\nstd::string CurrentLangDialect;\n\n#ifdef __WIIU__\nbool g_isHBLauncher = false;\n#endif\n\n//void DoEvents()\n//{\n//    g_events->doEvents();\n//}\n\n//Uint8 getKeyState(int key)\n//{\n//    return XEvents::getKeyState(key);\n//}\n\n//Uint8 getKeyStateI(int key)\n//{\n//    if(key < 0)\n//        return 0;\n//    return XEvents::getKeyState(key);\n//}\n\n// const char *getKeyName(int key)\n// {\n//     if(key < 0)\n//         return \" ... \";\n//     return g_events->getScanCodeName(key);\n// }\n\nvoid initAll()\n{\n    // SavedEvents.fill(std::string());\n    // BlockSwitch.fill(false);\n    // PowerUpUnlock.fill(false);\n    vScreen.fill(vScreen_t());\n    qScreenLoc.fill(vScreen_t());\n    PlayerStart.fill(PlayerStart_t());\n    blockCharacter.fill(false);\n    OwedMount.fill(0);\n    OwedMountType.fill(0);\n    AutoX.fill(0);\n    AutoY.fill(0);\n    Water.fill(Water_t());\n    // FirstBlock.fill(0);\n    // LastBlock.fill(0);\n    iBlock.fill(0);\n    CustomMusic.fill(std::string());\n    level.fill(SpeedlessLocation_t());\n    LevelWrap.fill(false);\n    LevelVWrap.fill(false);\n    OffScreenExit.fill(false);\n    bgMusic.fill(0);\n    bgMusicREAL.fill(0);\n    Background2REAL.fill(0);\n    Background2.fill(0);\n    SpecialFrameCount.fill(0);\n    // NPCSpeedvar.fill(0.f);\n\n    Block.fill(Block_t());\n    Background.fill(Background_t());\n    NPC.fill(NPC_t());\n\n    InitScreens();\n    for(int i = 0; i < maxLocalPlayers; i++)\n        Screens_AssignPlayer(i + 1, *l_screen);\n}\n\nXTColor XTColorString(const std::string& s)\n{\n    const char* s_ = s.c_str();\n\n    if(s_[0] == '0' && s_[1] == 'x')\n        s_ += 2;\n\n    if(*s_ == '#')\n        s_++;\n\n    uint8_t color[4] = {255, 255, 255, 255};\n\n    // read each byte separately\n    for(int i = 0; i < 4; i++)\n    {\n        if(s_[0] == '\\0' || s_[1] == '\\0')\n            break;\n\n        uint8_t nybble[2] = {0, 0};\n\n        for(int j = 0; j < 2; j++)\n        {\n            if(*s_ >= '0' && *s_ <= '9')\n                nybble[j] = *s_ - '0';\n            else if(*s_ >= 'a' && *s_ <= 'f')\n                nybble[j] = *s_ - 'a' + 10;\n            else if(*s_ >= 'A' && *s_ <= 'F')\n                nybble[j] = *s_ - 'A' + 10;\n\n            s_++;\n        }\n\n        color[i] = nybble[0] * 16 + nybble[1];\n    }\n\n    return XTColor(color[0], color[1], color[2], color[3]);\n}\n"
  },
  {
    "path": "src/globals.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GLOBALS_H\n#define GLOBALS_H\n\n#include <string>\n#include <vector>\n#include <cstdlib>\n\n#include \"std_picture.h\"\n\n#include \"numeric_types.h\"\n#include \"location.h\"\n#include \"pinched_info.h\"\n#include \"range_arr.hpp\"\n#include \"ref_type.h\"\n#include \"rand.h\"\n#include \"npc_id.h\"\n#include \"phys_id.h\"\n#include \"eff_id.h\"\n#include \"npc_effect.h\"\n#include \"player/player_effect.h\"\n\n#include \"global_constants.h\"\n#include \"global_strings.h\"\n\n//Option Explicit\n//Public Declare Sub Sleep Lib \"kernel32\" (ByVal dwMilliseconds As Long)\n//Public Declare Function BitBlt Lib \"gdi32\" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long\n//Public Declare Function StretchBlt Lib \"gdi32\" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, ByVal dwRop As Long) As Long\n//Public Declare Function CreateCompatibleBitmap Lib \"gdi32\" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long\n//Public Declare Function CreateCompatibleDC Lib \"gdi32\" (ByVal hdc As Long) As Long\n//Public Declare Function GetDC Lib \"user32\" (ByVal hWnd As Long) As Long\n//Public Declare Function SelectObject Lib \"gdi32\" (ByVal hdc As Long, ByVal hObject As Long) As Long\n//Public Declare Function DeleteObject Lib \"gdi32\" (ByVal hObject As Long) As Long\n//Public Declare Function DeleteDC Lib \"gdi32\" (ByVal hdc As Long) As Long\n//Public Declare Function GetKeyState Lib \"user32\" (ByVal nVirtKey As Long) As Integer\n//'Public Declare Function mciSendString Lib \"winmm.dll\" Alias \"mciSendStringA\" (ByVal lpstrCommand As String, ByVal lpstrReturnString As String, ByVal uReturnLength As Integer, ByVal hwndCallback As Integer) As Integer\n//Public Declare Function SetCursorPos Lib \"user32\" (ByVal X As Long, ByVal Y As Long) As Long\n//Public Declare Function SetWindowPos Lib \"user32\" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long\n//Private Declare Function GetSystemDirectory Lib \"kernel32\" Alias \"GetSystemDirectoryA\" (ByVal lpBuffer As String, ByVal nSize As Long) As Long\n//Public Declare Function GetDesktopWindow Lib \"user32.dll\" () As Long\n//Public Declare Function GetWindowDC Lib \"user32.dll\" (ByVal hWnd As Long) As Long\n//Declare Function GetActiveWindow Lib \"user32\" () As Integer\n//Public Declare Function GetTickCount& Lib \"kernel32\" ()\n//Public OnlineDisc As Boolean\n\n#define UNUSED(x) (void)x\n\n#if defined(__GNUC__)\n#   define ATTRIB_UNUSED    __attribute__((unused))\n#else\n#   define ATTRIB_UNUSED\n#endif\n\n#define IF_OUTRANGE(x, l, r)  ((x) < (l) || (x) > (r))\n#define IF_INRANGE(x, l, r)  ((x) >= (l) && (x) <= (r))\n\n\n//! Showing that game is works. It gets false when closing a window or exiting a game by menu. To mean that application must be closed.\nextern bool GameIsActive;\n//! Path to game resources assets (by default it's ~/.PGE_Project/thextech/)\nextern std::string AppPath;\n\n\n// Process internal events (mouse, keyboard, joysticks, window's update, OS communications, etc.)\n//extern void DoEvents(); /* Replaced with \"XEvents::doEvents()\" from `core/events.h` */\n\n//extern Uint8 getKeyState(int key);\n//extern Uint8 getKeyStateI(int key);\n\n//Public Const KEY_PRESSED As Integer = &H1000    'For control information\n//const int KEY_PRESSED = 1;\n\n/**\n * @brief Get name of key from a keycode\n * @param key Key code\n * @return Human-readable key name\n */\n// const char *getKeyName(int key); // no longer used\n\n/**\n * @brief Rounding function that works same as in VB6\n * @param x Floating point value to round\n * @return rounded result\n */\n// moved to num_t::vb6round\n// extern int vb6Round(double x);\n\n\n//'Saved Events\n//Public numSavedEvents As Integer\n// extern int numSavedEvents;\n//Public SavedEvents(1 To MaxSavedEvents) As String\n// extern RangeArr<std::string, 1, MaxSavedEvents> SavedEvents;\n//Public BlockSwitch(1 To 4) As Boolean\n// extern RangeArrI<bool, 1, 4, false> BlockSwitch;\n//'Public PowerUpUnlock(2 To 7) As Boolean\n// extern RangeArrI<bool, 2, 7, false> PowerUpUnlock;\n\n//Public Const SWP_SHOWWINDOW = &H40\n//const int SWP_SHOWWINDOW = 0x40;\n//Public Const SWP_NOMOVE As Long = 2\n//const long SWP_NOMOVE = 2;\n//Public Const SWP_NOSIZE As Long = 1\n//const long SWP_NOSIZE = 1;\n//Public Const FLAGS = SWP_NOMOVE Or SWP_NOSIZE\n//const long FLAGS = SWP_NOMOVE | SWP_NOSIZE;\n//Public Const HWND_TOPMOST As Long = -1\n//const long HWND_TOPMOST = -1;\n//Public Const HWND_NOTOPMOST As Long = -2\n//const long HWND_NOTOPMOST  = -2;\n//Public myBackBuffer As Long 'Backbuffer\n// extern long myBackBuffer;\n//Public myBufferBMP As Long 'Backbuffer\n// extern long myBufferBMP;\n//Public AllCharBlock As Integer\n// extern int AllCharBlock;\n//Public Const KEY_TOGGLED As Integer = &H1   'For control information\n//const int KEY_TOGGLED = 0x01;\n//Public LocalNick As String  'Online Nickname\n//Public LocalCursor As Integer  'Online Cursor color\n//Public ClientPassword As String  'Password client is connecting with\n//Public ServerPassword As String  'Password game server wants the client to use\n//Public ServerClear As Boolean\n//Public StartMenu As Boolean\n// extern bool StartMenu;\n\n//Public BlockFlash As Integer\n// Note: was previously BlockFlash, manually looped by local code to 0-90. Now incremented every frame, should be used with modulus operator.\nextern uint32_t CommonFrame;\n// This one only increments when FreezeNPCs is not true. Used by shaders.\nextern uint32_t CommonFrame_NotFrozen;\n\n//Public ScrollRelease As Boolean\nextern bool ScrollRelease;\n//Public TakeScreen As Boolean\nextern bool TakeScreen;\n// EXTRA: Show any on-screen meta (HUD, debug prints, etc.)\nextern bool ShowOnScreenHUD;\n//Public LB As String  ' Line Break\n//extern std::string LB;\n//Public EoT As String  ' End of Transmission for WINSOCK\n//extern std::string EoT;\n\n// Moved back into \"control_types.h\"\n//     since \"controls.h\" is changing more rapidly\n#include \"control_types.h\"\n\n//Public Type nPlayer  'online player type\n//    Controls As Controls  'online players controls\n//    Cursor As Integer\n//    IsMe As Boolean  'True if this player is the local player\n//    Nick As String\n//    Active As Boolean  'True if a player is using this variable\n//    ECurserX As Double  'Cursor X position\n//    ECurserY As Double   'Cursor Y position\n//End Type\n\n\n//Public Type nPlay  'Netplay data type\n//    Allow As Boolean\n//    Mode As Integer  'Server or client\n//    ServerIP As String 'Server's IP\n//    ServerCon As Boolean 'Server is connected\n//    ServerStr As String\n//    ServerLocked As Boolean\n//    ServerLoad1 As Double\n//    ServerLoad As Boolean\n//    ClientLocked(0 To 15) As Boolean\n//    ClientIP(0 To 15) As String\n//    ClientCon(0 To 15) As Boolean\n//    ClientName(0 To 15) As String\n//    ClientStr(0 To 15) As String\n//    ClientRelease(0 To 15) As Integer\n//    ClientPassword(0 To 15) As Boolean\n//    ClientLoad1(0 To 15) As Double\n//    Online As Boolean 'online or local\n//    MySlot As Integer\n//    MyControls As Controls\n//    Player(0 To 15) As nPlayer\n//    PlayerWaitCount As Integer\n//    NPCWaitCount As Single\n//End Type\n\n//Public Type Location    'Holds location information for objects\n//    X As Double\n//    Y As Double\n//    Height As Double\n//    Width As Double\n//    SpeedX As Double\n//    SpeedY As Double\n//End Type\n\n//Public Type EditorControls      'Controls for the editor\n// struct OldEditorControls_t\n// {\n//    Up As Boolean\n//    Down As Boolean\n//    Left As Boolean\n//    Right As Boolean\n//    Mouse1 As Boolean\n//End Type\n// };\n\n\n// Structures moved into con_control.h\n\n// Functionality moved into the Controls namespace\n\n// The information of conKeyboard and conJoystick is now found in the InputMethodType Profiles.\n// To access it you will need to access the internals of the Controls namespace.\n// Avoid doing this.\n\n//Public conKeyboard(1 To 2) As conKeyboard  'player 1 and 2's controls\n// extern RangeArr<ConKeyboard_t, 1, maxLocalPlayers> conKeyboard;\n//Public conJoystick(1 To 2) As conJoystick\n// extern RangeArr<ConJoystick_t, 1, maxLocalPlayers> conJoystick;\n\n// The information of useJoystick and wantedKeyboard is now dynamic you can\n// determine by observing the RTTI of the members of Controls::g_InputMethods.\n// Avoid doing this.\n\n//Public useJoystick(1 To 2) As Integer\n// extern RangeArrI<int, 1, maxLocalPlayers, 0> useJoystick; // no longer\n// extern RangeArrI<bool, 1, maxLocalPlayers, false> wantedKeyboard;\n\nstruct NPCTraits_t;\n\nenum WingBehaviors : uint8_t\n{\n    WING_NONE = 0,\n    WING_PARA_CHASE = 0,\n    WING_JUMP = 1,\n    WING_LEFTRIGHT = 2,\n    WING_UPDOWN = 3,\n    WING_CHASE = 4,\n    // reserved 38A values\n    WING_HOVER_FORWARD = 5,\n    WING_BUTTERFLY_AI = 6,\n    // reserved 38A values -- won't happen\n    WING_RSV_REMOTE_CONTROL = 7,\n    WING_RSV_RAILS = 8,\n    // reserved TheXTech values\n    WING_FLEE = 9,\n    // use normal NPC control for now\n    WING_OVERRIDE = 255,\n};\n\n//Public Type NPC 'The NPC Type\nstruct NPC_t\n{\n    // most important and frequently accessed fields at the top of the struct\n//    Type As Integer 'Defines what NPC this is.  1 for goomba, 2 for red goomba, etc.\n    NPCID Type = NPCID(0);\n//    Killed As Integer 'Flags the NPC to die a specific way.\n    vbint_t Killed = 0;\n//    Frame As Integer 'The graphic to be shown\n    vbint_t Frame = 0;\n//    tempBlock As Integer\n    // temp block index in the block array\n    vbint_t tempBlock = 0;\n\n//    Active As Boolean 'If on screen\n    bool Active = false;\n//    Hidden As Boolean 'if the layer is hidden or not\n    bool Hidden = false;\n//    Inert As Boolean 'the friendly toggle. makes the NPC not do anything\n    bool Inert = false;\n\n//    Reset(1 To 2) As Boolean 'If it can display the NPC\n    // IMPORTANT: in SMBX64 and compat mode, Reset[1] is whether the NPC was NOT on vScreen 1 during the last draw\n    //            and Reset[2] is whether it was NOT on vScreen 2 during the last draw.\n    //     Because TheXTech plans to support more than 2 vScreens, it uses Reset[1] to mark whether the NPC was on\n    //         NO screens during the last draw. Reset[1] is the only externally usable flag, and Reset[2] is used\n    //         internally during the NPC screen logic to mark whether Reset[1] was set prior to the screen logic.\n    RangeArrI<bool, 1, 2, false> Reset;\n\n//    JustActivated As Integer 'The player that activated the NPC\n    uint8_t JustActivated = 0;\n//    TimeLeft As Integer 'Time left before reset when not on screen\n    vbint_t TimeLeft = 0;\n\n//    Location As Location 'collsion detection information\n    Location_t Location;\n\n// NEW: position variables (or double counters) used by AI\n    num_t SpecialX = 0_n;\n    num_t SpecialY = 0_n;\n\n//'Secial - misc variables used for NPC AI\n//    Special As Double\n    vbint_t Special = 0;\n//    Special2 As Double\n    vbint_t Special2 = 0;\n//    Special3 As Double\n    vbint_t Special3 = 0;\n//    Special4 As Double\n    vbint_t Special4 = 0;\n//    Special5 As Double\n    vbint_t Special5 = 0;\n//    Special6 As Double\n    // vbint_t Special6 = 0;\n\n    // Information about the NPC's current unusual state (reordered for alignment purposes)\n//    Effect2 As Double\n    // double Effect2 = 0.0; // When Effect 4, Used to store a destination position, must be in double!\n\n    // when Effect == NPCEFF_WARP, this was previously used to hold a destination position.\n    // now the destination position is stored in SpecialX/Y, and this is an integer.\n    vbint_t Effect2 = 0;\n//    Effect As Integer 'For starting / stopping effects\n    NPCEffect Effect = NPCEFF_NORMAL;\n//    Effect3 As Integer\n    // rarely used for warping NPCs (direction) and for NPCs being eaten (countdown timer initialized to 5)\n    uint8_t Effect3 = 0;\n\n    // Moderately important counter variables\n//    Section As Integer 'what section of the level the NPC is in\n    // never set to any non-section values\n    uint8_t Section = 0;\n//    Wet As Integer ' greater then 0 of the NPC is in water\n    // counter for whether NPC is in water, set to 2 when detected, decremented otherwise\n    uint8_t Wet = 0;\n//    TailCD As Integer 'if greater then 0 the player can't hit with it's tail\n    // set to values up to 12 when whipped / kicked, decremented otherwise\n    uint8_t TailCD = 0;\n\n//    Direction As Single 'The direction the NPC is walking\n    // we have confirmed that this is never assigned a value other than 0, -1, or 1\n    vbint_t Direction = 0;\n\n//    Pinched1 As Integer  'getting smashed by a block\n    // int Pinched1 = 0;\n//    Pinched2 As Integer\n    // int Pinched2 = 0;\n//    Pinched3 As Integer\n    // int Pinched3 = 0;\n//    Pinched4 As Integer\n    // int Pinched4 = 0;\n//    MovingPinched As Integer 'required to be smashed\n    // int MovingPinched = 0;\n\n    // NEW: replaces above with bitfield\n    PinchedInfo_t Pinched = PinchedInfo_t();\n\n    // indexes to layers / events / text\n//    TriggerActivate As String 'for events - triggers when NPC gets activated\n    eventindex_t TriggerActivate = EVENT_NONE;\n//    TriggerDeath As String 'triggers when NPC dies\n    eventindex_t TriggerDeath = EVENT_NONE;\n//    TriggerTalk As String 'triggers when you talk to the NPC\n    eventindex_t TriggerTalk = EVENT_NONE;\n//    TriggerLast As String 'trigger when this is the last NPC in a layer to die\n    eventindex_t TriggerLast = EVENT_NONE;\n//    Layer As String 'the layer name that the NPC is in\n    layerindex_t Layer = LAYER_NONE;\n//    AttLayer As String\n    layerindex_t AttLayer = LAYER_NONE;\n//    Text As String 'the text that is displayed when you talk to the NPC\n    stringindex_t Text = STRINGINDEX_NONE;\n\n//    Projectile As Boolean 'If the NPC is a projectile\n    bool Projectile = false;\n// EXTRA: Variant (previously Special7)\n    uint8_t Variant = 0;\n\n    // some misc variables\n//    Slope As Integer 'the block that the NPC is on a slope with\n    vbint_t Slope = 0;\n//    Multiplier As Integer 'for upping the points the player recieves\n    uint8_t Multiplier = 0;\n//    standingOnPlayer As Integer 'If this NPC is standing on a player in the clown car\n    uint8_t vehiclePlr = 0;\n//    standingOnPlayerY As Integer\n    vbint_t vehicleYOffset = 0;\n\n    // Information about Generator state (Generator and GeneratorActive in bitfield at the bottom of the struct)\n//    GeneratorDirection As Integer\n    // valid values: 0, 1, 2, 3, 4\n    // MOVED: now stored in the upper byte of Special3 for Generators\n    // uint8_t GeneratorDirection = 0;\n    inline uint8_t GeneratorDirection() const { return (uint8_t)(Special3 >> 8); }\n//    GeneratorEffect As Integer\n    // valid values: 0, 1, 2\n    // MOVED: now stored in the lower byte of Special3 for Generators\n    // uint8_t GeneratorEffect = 0;\n    inline uint8_t GeneratorEffect() const { return (uint8_t)Special3; }\n//    GeneratorTimeMax As Single\n    // surprisingly, never stores any floating point variables. expressed in deci-seconds.\n    // MOVED: now stored in Special4 for Generators\n    // vbint_t GeneratorTimeMax = 0;\n    inline vbint_t& GeneratorTimeMax() { return Special4; }\n    inline const vbint_t& GeneratorTimeMax() const { return Special4; }\n//    GeneratorTime As Single\n    // surprisingly, only stores its own limit as a floating point variables. expressed in ticks.\n    // MOVED: now stored in Special5 for Generators\n    // vbint_t GeneratorTime = 0;\n    inline vbint_t& GeneratorTime() { return Special5; }\n\n//    FrameCount As Single 'The counter for incrementing the frames\n    // was previously a float but this didn't accomplish anything\n    vbint_t FrameCount = 0;\n\n    // Misc floating-point variables\n//    RealSpeedX As Single 'the real speed of the NPC\n    numf_t RealSpeedX = 0;\n//    BeltSpeed As Single 'The speed of the object this NPC is standing on\n    numf_t BeltSpeed = 0;\n//    oldAddBelt As Single\n    numf_t oldAddBelt = 0;\n//    Damage As Single\n    // never set to a non-integer value, likely used a float for saturation arithemtic\n    // (note: for NPCs whose DefaultType is a coin, this now stores the block type of a coin created by a coin switch)\n    vbint_t Damage = 0;\n\n    // Player reference variables\n//    CantHurtPlayer As Integer\n    // almost always set to a valid player index; very rarely set to an invalid index for NPCID_FLIPPED_RAINBOW_SHELL, only used to index the player array by NPCID_SWORDBEAM\n    uint8_t CantHurtPlayer = 0;\n//    BattleOwner As Integer 'Owner of the projectile\n    // not only used in battle mode; almost always set to a valid player index, (see above)\n    uint8_t BattleOwner = 0;\n//    HoldingPlayer As Integer 'Who is holding it\n    // only ever set to 0 or a player index\n    uint8_t HoldingPlayer = 0;\n\n    // some more misc counter variables\n//    Immune As Integer 'time that the NPC is immune\n    // set to values up to 100, decremented each frame\n    uint8_t Immune = 0;\n//    CantHurt As Integer 'Won't hurt the player\n    // timer for how long the NPC will be harmless to a certain player, set to values up to 10000\n    vbint_t CantHurt = 0;\n//    WallDeath As Integer\n    // tracks whether the NPC was activated in a wall (or is not in water, for fish). set to values between 0 and 10, used as a counter bounded at these values, sometimes very briefly 11.\n    uint8_t WallDeath = 0;\n//  NEW: which GFX expansion slot should the NPC use? Reserved for now\n    uint8_t GFXSlot = 0;\n//  NEW: what type of wings does the NPC currently have?\n    WingBehaviors Wings = WING_NONE;\n\n    // rarely used bools turned into bitfields\n//    TurnAround As Boolean 'if the NPC needs to turn around\n    bool TurnAround : 1;\n//    TurnBackWipe As Boolean\n    bool TurnBackWipe : 1;\n//    Generator As Boolean 'for spawning new NPCs\n    bool Generator : 1;\n//    GeneratorActive As Boolean\n    bool GeneratorActive : 1;\n//    playerTemp As Boolean\n    bool playerTemp : 1;\n//    Legacy As Boolean 'Legacy Boss\n    bool Legacy : 1;\n//    Chat As Boolean 'for talking to the NPC\n    bool Chat : 1;\n    // (only set by NPCHit and used by KillNPC. Killed == 6 is never checked. Could certainly migrate to a KillCode modifier if a bit is needed.)\n//    NoLavaSplash As Boolean 'true for no lava splash\n    bool NoLavaSplash : 1;\n//    Bouce As Boolean\n    bool Bouce : 1;\n//    DefaultStuck As Boolean\n    bool DefaultStuck : 1;\n//    RespawnDelay As Integeri\n    // Used to respawn an NPC in Battle Mode but no longer a counter.\n    // Effect2 is set to 65 * 30 (30s) on deactivation and decremented each frame while this flag is set.\n    bool RespawnDelay : 1;\n    // EXTRA (private to npc_activation.cpp): stores whether the NPC needs to use an event logic screen for activation\n    bool _priv_force_canonical : 1;\n    // EXTRA (private to npc_activation.cpp): stores whether the NPC has an activation event to hide self\n    bool _priv_self_hide_event : 1;\n    // EXTRA: used only for vines spawned by vine head\n    bool DefaultLocationHeight_Force32 : 1;\n//    EXTRA: does the tempBlock have its own tree entry?\n    // To explain further: when an NPC is at the same location as its tempBlock,\n    //   its temp block is *not* added to the temp block quadtree\n    //   (saves time by only keeping one tree).\n    // Whenever the NPC is moved and temp block isn't, they split and the temp block needs to be added to the tree if it has not already been added. (treeNPCSplitTempBlock)\n    // Whenever the temp block is moved and the NPC isn't, they split and the temp block needs to be updated even if it is already added. (treeNPCUpdateTempBlock)\n    // Whenever the temp block is moved *to* the NPC's position, they re-join, and the temp block is removed if it was added.\n    bool tempBlockInTree : 1;\n//    Stuck As Boolean 'the 'don't move' toggle. forces the NPC not to move\n    bool Stuck : 1;\n//    Shadow As Boolean 'if true turn the NPC black and allow it to pass through walls.  only used for a cheat code\n    bool Shadow : 1;\n//    Quicksand As Integer\n    // counter for whether NPC is in quicksand, set to 2 when detected, decremented otherwise\n    uint8_t Quicksand : 2;\n\n//  NEW: what type of wing behavior should the NPC normally have?\n    WingBehaviors DefaultWings = WING_NONE;\n//'the default values are used when De-Activating an NPC when it goes on screen\n//    DefaultDirection As Single\n    // changed to int8_t, only ever holds values -1, 0, and 1\n    int8_t DefaultDirection = 0;\n//    DefaultType As Integer\n    NPCID DefaultType = NPCID(0);\n//    DefaultSpecial As Integer\n    vbint_t DefaultSpecial = 0;\n//    DefaultLocation As Location\n    num_t DefaultLocationX = 0_n;\n    num_t DefaultLocationY = 0_n;\n\n    // obsolete and removed fields\n//    PinchCount As Integer 'obsolete\n    // int PinchCount = 0;    // unused since SMBX64, removed\n//    Pinched As Boolean 'obsolete\n    // bool Pinched = false;    // unused since SMBX64, removed\n//    PinchedDirection As Integer 'obsolete\n    // int PinchedDirection = 0;    // unused since SMBX64, removed\n//    NetTimeout As Integer 'for online\n    // int NetTimeout = 0;    // unused since SMBX64, removed\n//    Settings As Integer\n    // int Settings = 0;    // unused since SMBX64, removed\n    // was previously a persistent variable, but was set to a constant value at the start of UpdateNPCs and never read outside of that routine\n//    onWall As Boolean\n    // bool onWall : 1;\n//    DefaultSpecial2 As Integer\n    // was only used by NPCID_MAGIC_DOOR and friends, which now use Variant\n    // vbint_t DefaultSpecial2 = 0;\n\n//    Block As Integer 'Used when a P-Switch turns a block into a coint\n    // Now stored in Damage which is unused for coins\n    inline vbint_t coinSwitchBlockType() const;\n\n//End Type\n\n    // reset location to default\n    void ResetLocation();\n    const NPCTraits_t* operator->() const;\n\n    NPC_t() : TurnAround(false), TurnBackWipe(false),\n        Generator(false), GeneratorActive(false),\n        playerTemp(false), Legacy(false), Chat(false), NoLavaSplash(false),\n        Bouce(false), DefaultStuck(false), RespawnDelay(false),\n        _priv_force_canonical(false),\n        _priv_self_hide_event(false),\n        DefaultLocationHeight_Force32(false),\n        tempBlockInTree(false),\n        Stuck(false),\n        Shadow(false),\n        Quicksand(0) {}\n\n};\n\n//Public Type Player              'The player data type.\nstruct Player_t\n{\n//    DoubleJump As Boolean\n    bool DoubleJump = false;\n//    FlySparks As Boolean\n    bool FlySparks = false;\n//    Driving As Boolean\n    bool Driving = false;\n//    Quicksand As Integer\n    vbint_t Quicksand = 0;\n//    Bombs As Integer\n    vbint_t Bombs = 0;\n//    Slippy As Boolean\n    bool Slippy = false;\n//    Fairy As Boolean\n    bool Fairy = false;\n//    FairyCD As Integer\n    vbint_t FairyCD = 0;\n//    FairyTime As Integer\n    vbint_t FairyTime = 0;\n//    HasKey As Boolean\n    bool HasKey = false;\n//    SwordPoke As Integer\n    vbint_t SwordPoke = 0;\n//    Hearts As Integer\n    vbint_t Hearts = 0;\n//    CanFloat As Boolean\n    bool CanFloat = false;\n//    FloatRelease As Boolean\n    bool FloatRelease = false;\n//    FloatTime As Integer\n    vbint_t FloatTime = 0;\n//    FloatSpeed As Single\n    numf_t FloatSpeed = 0;\n//    FloatDir As Integer\n    vbint_t FloatDir = 0;\n//    GrabTime As Integer 'how long the player has been trying to grab an npc from above\n    vbint_t GrabTime = 0;\n//    GrabSpeed As Single\n    numf_t GrabSpeed = 0;\n//    VineNPC As Double 'the NPC that the player is climbing\n    vbint_t VineNPC = 0;\n//  EXTRA:  Fence BGO\n    vbint_t VineBGO = 0;\n//    Wet As Integer 'weather or not the player is under water\n    vbint_t Wet = 0;\n//    WetFrame As Boolean 'true if the play should be swimming\n    bool WetFrame = false;\n//    SwimCount As Integer 'cool down between swim strokes\n    vbint_t SwimCount = 0;\n//    NoGravity As Integer\n    vbint_t NoGravity = 0;\n//    Slide As Boolean 'true if the player is sliding\n    bool Slide = false;\n//    SlideKill As Boolean 'true if the player is sliding fast enough to kill an NPC\n    bool SlideKill = false;\n//    Vine As Integer 'greater then 0 if the player is climbing\n    vbint_t Vine = 0;\n//    NoShellKick As Integer 'dont kick a shell\n    // never set in SMBX 1.3, was originally used to improve shell-surf\n    // int NoShellKick = 0;\n//    ShellSurf As Boolean 'true if surfing a shell\n    bool ShellSurf = false;\n    // is currently a rolling shell/ball\n    bool Rolling = false;\n//    StateNPC As Integer\n    NPCID StateNPC = NPCID(0);\n//    Slope As Integer 'the block that the player is standing on when on a slope\n    vbint_t Slope = 0;\n//    Stoned As Boolean 'true of a statue form (tanooki suit)\n    bool Stoned = false;\n    // is currently swimming with aquatic/polar power\n    bool AquaticSwim = false;\n//    StonedCD As Integer 'delay before going back in to stone form\n    vbint_t StonedCD = 0;\n//    StonedTime As Integer 'how long the player can remain as a statue\n    vbint_t StonedTime = 0;\n//    SpinJump As Boolean 'true if spin jumping\n    bool SpinJump = false;\n//    SpinFrame As Integer 'frame for spinning\n    vbint_t SpinFrame = 0;\n//    SpinFireDir As Integer 'for shooting fireballs while spin jumping\n    vbint_t SpinFireDir = 0;\n//    Multiplier As Integer 'for score increase for multiple hops\n    uint8_t Multiplier = 0;\n//    SlideCounter As Integer 'for creating the dust effect when sliding\n    vbint_t SlideCounter = 0;\n//    ShowWarp As Integer\n    vbint_t ShowWarp = 0;\n//    ForceHold As Integer  'force the player to hold an item for a specific amount of time\n    vbint_t ForceHold = 0;\n\n    // pound state converted to bitfield\n//    GroundPound As Boolean 'for purple yoshi pound\n    bool GroundPound : 1;\n//    GroundPound2 As Boolean 'for purple yoshi pound\n    bool GroundPound2 : 1;\n//    CanPound As Boolean 'for purple yoshi pound\n    bool CanPound : 1;\n//    NEW: AltRunRelease As Boolean 'has the player not been holding Alt Run?\n    bool AltRunRelease : 1;\n//    DuckRelease As Boolean\n    bool DuckRelease : 1;\n\n    // Can the player NOT currently wall-jump because the wall is slippery?\n    bool SlippyWall : 1;\n    // Is the player's current jump a wall-jump?\n    bool JumpOffWall : 1;\n\n//'yoshi powers\n//    YoshiYellow As Boolean\n    bool YoshiYellow = false;\n//    YoshiBlue As Boolean\n    bool YoshiBlue = false;\n//    YoshiRed As Boolean\n    bool YoshiRed = false;\n//    YoshiWingsFrame As Integer\n    vbint_t YoshiWingsFrame = 0;\n//    YoshiWingsFrameCount As Integer\n    vbint_t YoshiWingsFrameCount = 0;\n//'yoshi graphic display\n//    YoshiTX As Integer\n    vbint_t YoshiTX = 0;\n//    YoshiTY As Integer\n    vbint_t YoshiTY = 0;\n//    YoshiTFrame As Integer\n    vbint_t YoshiTFrame = 0;\n//    YoshiTFrameCount As Integer\n    vbint_t YoshiTFrameCount = 0;\n//    YoshiBX As Integer\n    vbint_t YoshiBX = 0;\n//    YoshiBY As Integer\n    vbint_t YoshiBY = 0;\n//    YoshiBFrame As Integer\n    vbint_t YoshiBFrame = 0;\n//    YoshiBFrameCount As Integer\n    vbint_t YoshiBFrameCount = 0;\n//    YoshiTongue As Location\n    SpeedlessLocation_t YoshiTongue;\n//    YoshiTongueX As Single\n    // float YoshiTongueX = 0.0f;\n//    YoshiTongueLength As Integer 'length of yoshi's tongue\n    vbint_t YoshiTongueLength = 0;\n//    YoshiTonugeBool As Boolean\n    bool YoshiTonugeBool = false;\n//    YoshiNPC As Integer 'the NPC that is in yoshi's mouth\n    vbint_t YoshiNPC = 0;\n//    YoshiPlayer As Integer 'the player that is in yoshi's mouth\n    vbint_t YoshiPlayer = 0;\n//    Dismount As Integer 'delay before you can remount\n    vbint_t Dismount = 0;\n//    NoPlayerCol As Integer\n    // vbint_t NoPlayerCol = 0;\n//    Location As Location 'collision detection info\n    Location_t Location;\n//    Character As Integer 'luigi or mario\n    vbint_t Character = 0;\n//    Controls As Controls 'players controls\n    Controls_t Controls;\n//    Direction As Integer 'the way the player is facing\n    vbint_t Direction = 0;\n//    Mount As Integer '1 for boot, 2 for clown car, 3 for yoshi\n    vbint_t Mount = 0;\n//    MountType As Integer 'for different types of mounts. blue yoshi, red yoshi, etc\n    vbint_t MountType = 0;\n//    MountSpecial As Integer\n    vbint_t MountSpecial = 0;\n//    MountOffsetY As Integer\n    vbint_t MountOffsetY = 0;\n//    MountFrame As Integer 'GFX frame for the player's mount\n    vbint_t MountFrame = 0;\n//    State As Integer '1 for small mario, 2 for super, 3 for fire, 4 for racoon, 5 for tanooki, 6 for hammer\n    vbint_t State = 0;\n//    Frame As Integer\n    vbint_t Frame = 0;\n//    FrameCount As Single\n    vbint_t FrameCount = 0;\n//    Jump As Integer 'how long the player can jump for\n    vbint_t Jump = 0;\n//    CanJump As Boolean 'true if the player can jump\n    bool CanJump = false;\n//    CanAltJump As Boolean 'true if the player can alt jump\n    bool CanAltJump = false;\n//    Effect As Integer 'for various effects like shrinking/growing/warping\n    PlayerEffect Effect = PLREFF_NORMAL;\n//    Effect2 As Double 'counter for the effects (was double)\n    vbint_t Effect2 = 0;\n//    NEW: this previously used the same storage as Effect2\n    num_t RespawnY = 0_n;\n//    Duck As Boolean 'true if ducking\n    bool Duck = false;\n//    DropRelease As Boolean\n    bool DropRelease = false;\n//    StandUp As Boolean 'aid with collision detection after ducking\n    bool StandUp = false;\n//    StandUp2 As Boolean\n    bool StandUp2 = false;\n//    Bumped As Boolean 'true if hit by another player\n    bool Bumped = false;\n//    Bumped2 As Single\n    vbint_t Bumped2 = 0;\n//    Dead As Boolean 'true if dead\n    bool Dead = false;\n//    TimeToLive As Integer 'for returning to the other play after dying\n    vbint_t TimeToLive = 0;\n//    Immune As Integer 'greater then 0 if immune, this is a counter\n    vbint_t Immune = 0;\n//    Immune2 As Boolean 'makes the player blink\n    bool Immune2 = false;\n//    ForceHitSpot3 As Boolean 'force hitspot 3 for collision detection\n    bool ForceHitSpot3 = false;\n\n//'for getting smashed by a block\n//    Pinched1 As Integer\n    // int Pinched1 = 0;\n//    Pinched2 As Integer\n    // int Pinched2 = 0;\n//    Pinched3 As Integer\n    // int Pinched3 = 0;\n//    Pinched4 As Integer\n    // int Pinched4 = 0;\n//    NPCPinched As Integer 'must be > 0 for the player to get crushed\n    // int NPCPinched = 0;\n\n    PinchedInfo_t Pinched = PinchedInfo_t();\n\n//    m2Speed As Single\n    // unused since SMBX 1.3\n    // float m2Speed = 0.0f;\n//    HoldingNPC As Integer 'What NPC is being held\n    vbint_t HoldingNPC = 0;\n//    CanGrabNPCs As Boolean 'If the player can grab NPCs\n    bool CanGrabNPCs = false;\n//    HeldBonus As Integer 'the NPC that is in the player's container\n    NPCID HeldBonus = NPCID(0);\n//    Section As Integer 'What section of the level the player is in\n    vbint_t Section = 0;\n//    WarpCD As Integer 'delay before allowing the player to warp again\n    vbint_t WarpCD = 0;\n//    Warp As Integer 'the warp the player is using\n    vbint_t Warp = 0;\n// EXTRA: Is the backward warp mode\n    bool WarpBackward = false;\n// EXTRA: True if shooted from the cannon\n    bool WarpShooted = false;\n//    FireBallCD As Integer 'How long the player has to wait before he can shoot again\n    vbint_t FireBallCD = 0;\n//    FireBallCD2 As Integer 'How long the player has to wait before he can shoot again\n    vbint_t FireBallCD2 = 0;\n//    TailCount As Integer 'Used for the tail swipe\n    vbint_t TailCount = 0;\n//    RunCount As Single 'To find how long the player has ran for\n    // was previously incremented 1.0 per running frame and decremented 0.3 per slow frame\n    // now is incremented 10 per running frame and decremented 3 per slow frame\n    vbint_t RunCount = 0;\n//    CanFly As Boolean 'If the player can fly\n    bool CanFly = false;\n//    CanFly2 As Boolean\n    bool CanFly2 = false;\n//    FlyCount As Integer 'length of time the player can fly\n    vbint_t FlyCount = 0;\n//    RunRelease As Boolean 'The player let go of run and pressed again\n    bool RunRelease = false;\n//    JumpRelease As Boolean 'The player let go of run and pressed again\n    bool JumpRelease = false;\n//    StandingOnNPC As Integer 'The NPC the player is standing on\n    vbint_t StandingOnNPC = 0;\n//    StandingOnTempNPC As Integer 'The NPC the player is standing on\n    vbint_t StandingOnVehiclePlr = 0;\n//    UnStart As Boolean 'Player let go of the start button\n    bool UnStart = false;\n//    mountBump As Single 'Player hit something while in a mount\n    numf_t mountBump = 0.0_nf;\n//    SpeedFixY As Single\n    // unused since SMBX 1.3\n    // float SpeedFixY = 0.0f;\n//End Type\n\n    // NEW: currently in a maze zone PhysEnv? (stores physenv index)\n    vbint_t CurMazeZone = 0;\n    // NEW: status field for maze zone (stores current direction and clearance to leave)\n    uint8_t MazeZoneStatus = 0;\n\n    Player_t() : GroundPound(false), GroundPound2(false), CanPound(false), AltRunRelease(false), DuckRelease(false), SlippyWall(false), JumpOffWall(false) {}\n};\n\n//Public Type Background  'Background objects\nstruct Background_t\n{\n//    Type As Integer\n    vbint_t Type = 0;\n//    Hidden As Boolean\n    bool Hidden = false;\n//    Layer As String\n    layerindex_t Layer = LAYER_NONE;\n//    EXTRA: sort priority for BGO (NOT a draw plane itself, a BGO-only format used for sorting and determining draw plane)\n    uint8_t SortPriority = 0;\n//    Location As Location\n    SpeedlessLocation_t Location;\n\n    //! SortPriority at which PLANE_LVL_BGO_NORM, PLANE_LVL_3D_MAIN, PLANE_LVL_BGO_FG, and PLANE_LVL_BGO_TOP start\n    static constexpr uint8_t PRI_NORM_START = 0x30;\n    static constexpr uint8_t PRI_BLK_START = 0xA0;\n    static constexpr uint8_t PRI_FG_START = 0xC0;\n    static constexpr uint8_t PRI_TOP_START = 0xF8;\n\n    // all defined in sorting.cpp:\n\n    //! checks if a custom sorting layer is set; returns -2, -1, +1, or +2 if so, 0 if not\n    int GetCustomLayer() const;\n    //! returns custom sorting offset (a number between -3 and +3)\n    int GetCustomOffset() const;\n    //! sets custom sorting layer and offset bits and updates sort priority\n    void SetSortPriority(int layer, int offset);\n    //! updates SortPriority based on current type, custom layer, and custom offset\n    void UpdateSortPriority();\n\n//End Type\n};\n\n//Public Type Water\nstruct Water_t\n{\n//    Location As Location\n    SpeedlessLocation_t Location;\n//    Buoy As Single 'not used\n    // float Buoy = 0.0f;\n//    Quicksand As Boolean\n    PHYSID Type = PHYSID_WATER;\n//    Layer As String\n    layerindex_t Layer = LAYER_NONE;\n//    Hidden As Boolean\n    bool Hidden = false;\n//End Type\n};\n\n//Public Type Block   'Blocks\nstruct Block_t\n{\n//    Location As Location\n    Location_t Location;\n//! EXTRA: temporary workaround: is it a smashable block of type 90 (normally not smashable)? (previously Special2)\n    bool forceSmashable = false;\n//    Slippy As Boolean\n    bool Slippy = false;\n//    RespawnDelay As Integer\n    // Newly re-used to represent which screens can still hit the block (outside of Battle Mode)\n    uint16_t RespawnDelay_ScreensLeft = 0;\n//    RapidHit As Integer\n    vbint_t RapidHit = 0;\n//    DefaultType As Integer\n    vbint_t DefaultType = 0;\n//    DefaultSpecial As Integer\n    vbint_t DefaultSpecial = 0;\n//'for event triggers\n//    TriggerHit As String\n    eventindex_t TriggerHit = EVENT_NONE;\n//    TriggerDeath As String\n    eventindex_t TriggerDeath = EVENT_NONE;\n//    TriggerLast As String\n    eventindex_t TriggerLast = EVENT_NONE;\n//    Layer As String\n    layerindex_t Layer = LAYER_NONE;\n//    NPC As Integer 'when a coin is turned into a block after the p switch is hit\n    NPCID coinSwitchNpcType = NPCID(0);\n//    Type As Integer 'the block's type\n    vbint_t Type = 0;\n//    Special As Integer 'what is in the block?\n    vbint_t Special = 0;\n//'for the shake effect after hitting ablock\n//    ShakeY As Integer\n//    ShakeY2 As Integer\n//    ShakeY3 As Integer\n    uint8_t ShakeCounter = 0;\n    int8_t ShakeOffset = 0;\n//    Kill As Boolean 'if true the game will destroy the block\n    // NEW: can be set to 9 in which case the game will ACTUALLY destroy it\n    uint8_t Kill = false;\n//    Invis As Boolean 'for invisible blocks\n    bool Invis = false;\n//    Hidden As Boolean\n    bool Hidden = false;\n//    IsPlayer As Integer 'for the clown car\n    uint8_t tempBlockVehiclePlr = 0;\n//    IsNPC As Integer 'the type of NPC the block is\n    NPCID tempBlockNpcType = NPCID(0);\n//    standingOnPlayerY As Integer 'when standing on a player in the clown car\n    vbint_t tempBlockVehicleYOffset = 0;\n//    noProjClipping As Boolean\n    // bool noProjClipping = false;\n//    IsReally As Integer 'the NPC that is this block\n    vbint_t tempBlockNpcIdx = 0;\n\n    inline bool tempBlockNoProjClipping() const;\n\npublic:\n\n// EXTRA: Indicate the fact that block was resized by a hit\n#ifdef LOW_MEM\n\n    inline void setShrinkResized() {}\n\n    inline bool getShrinkResized() const\n    {\n        // Because the initial block width is stored as an integer, the only way the width could be 31.9 is if it was shrink-resized from 32.\n        // The block location width isn't set to a non-integer anywhere else in the game, so this is safe.\n        // This is a heuristic and has a small CPU tradeoff, but it saves memory.\n        // If it fails in some case, we can switch to the below implementation, or we could use this implementation only when LOW_MEM is set.\n        return Location.Width == 31.9_n;\n    }\n\n#else\n\nprivate:\n    bool m_wasShrinkResized = false;\n\npublic:\n    inline void setShrinkResized()\n    {\n        m_wasShrinkResized = true;\n    }\n\n    inline bool getShrinkResized()\n    {\n        return m_wasShrinkResized;\n    }\n\n#endif\n\n//End Type\n};\n\n//Public Type Effect  'Special effects\nstruct Effect_t\n{\n//    Location As Location\n    Location_t Location;\n//    Type As Integer\n    EFFID Type = EFFID(0);\n//    Frame As Integer\n    vbint_t Frame = 0;\n//    FrameCount As Single\n    vbint_t FrameCount = 0;\n//    Life As Integer 'timer before the effect disappears\n    vbint_t Life = 0;\n//    NewNpc As Integer 'when an effect should create and NPC, such as Yoshi (NOTE: occasionally abused, so not turning into an NPCID)\n    vbint_t NewNpc = 0;\n// EXTRA: New NPC's special value\n    uint8_t NewNpcSpecial = 0;\n//    Shadow As Boolean 'for a black effect set to true\n    bool Shadow = false;\n//End Type\n};\n\n//Public Type vScreen 'Screen controls\n#include \"screen.h\"\n\n//! NEW: information about available Stars and obtained / available Medals for a level\nstruct LevelSaveInfo_t\n{\n    // INTEGERS\n    uint8_t  max_stars = 0;\n    uint8_t  max_medals = 255; // invalid, maximum is 8\n\n    // BITMASKS, max medals is 8\n    uint8_t medals_got = 0;\n    uint8_t medals_best = 0;\n\n    // BITMASK\n    uint16_t exits_got = 0;\n\n    inline bool inited() const\n    {\n        return max_medals != 255;\n    }\n};\n\n//! NEW: stores LevelSaveInfo for levels without WorldLevels\nstruct LevelWarpSaveEntry_t\n{\n    std::string levelPath;\n    LevelSaveInfo_t save_info;\n};\n\n//Public Type WorldLevel 'the type for levels on the world map\nstruct WorldLevel_t\n{\n//    Location As Location\n    TinyLocation_t Location;\n\n//    FileName As String 'level's file\n    std::string FileName;\n//    LevelName As String 'The name of the level\n    std::string LevelName;\n\n//    LevelExit(1 To 4) As Integer ' For the direction each type of exit opens the path\n    RangeArrI<vbint_t, 1, 4, 0> LevelExit;\n\n//    Type As Integer\n    vbint_t Type = 0;\n//    StartWarp As Integer 'If the level should start with a player exiting a warp\n    vbint_t StartWarp = 0;\n\n//    Active As Boolean\n    bool Active = false;\n//    Path As Boolean 'for drawing a small path background\n    bool Path = false;\n//    Path2 As Boolean 'big path background\n    bool Path2 = false;\n//    Start As Boolean 'true if the game starts here\n    bool Start = false;\n\n//    Visible As Boolean 'true if it should be shown on the map\n    bool Visible = false;\n\n//    WarpX As Double 'for warping to another location on the world map\n    int32_t WarpX = 0;\n//    WarpY As Double\n    int32_t WarpY = 0;\n\n//End Type\n\n    // int64_t Z = 0;\n\n// Display number of stars (if available)\n    int8_t starsShowPolicy = -1;\n    uint8_t curStars = 0;\n\n    //! NEW: info about collected / available medals / stars (replaces maxStars)\n    LevelSaveInfo_t save_info;\n\n    // NEW: returns graphical location extent (based on whether GFXLevelBig is set)\n    //   defined in graphics.cpp\n    Location_t LocationGFX();\n\n    // NEW: returns location extent, including big background paths, for onscreen checks\n    //   defined in graphics.cpp\n    Location_t LocationOnscreen();\n};\n\n//Public Type Warp 'warps such as pipes and doors\nstruct Warp_t\n{\n//    Entrance As Location 'location of warp entrance\n    SpeedlessLocation_t Entrance;\n//    Exit As Location 'location of warp exit\n    SpeedlessLocation_t Exit;\n//    Locked As Boolean 'requires a key NPC\n    bool Locked = false;\n//    WarpNPC As Boolean 'allows NPC through the warp\n    bool WarpNPC = false;\n//    NoYoshi As Boolean 'don't allow yoshi\n    bool NoYoshi = false;\n//    Layer As String 'the name of the layer\n    layerindex_t Layer = LAYER_NONE;\n//    Hidden As Boolean 'if the layer is hidden\n    bool Hidden = false;\n//    PlacedEnt As Boolean 'for the editor, flags the entranced as placed\n    bool PlacedEnt = false;\n//    PlacedExit As Boolean\n    bool PlacedExit = false;\n//    Stars As Integer 'number of stars required to enter\n    vbint_t Stars = 0;\n//    Effect As Integer 'style of warp. door/\n    vbint_t Effect = 0;\n//    level As String 'filename of the level it should warp to\n    stringindex_t level = STRINGINDEX_NONE;\n//    LevelWarp As Integer\n    vbint_t LevelWarp = 0;\n//    LevelEnt As Boolean 'this warp can't be used if set to true (this is for level entrances)\n    bool LevelEnt = false;\n//    Direction As Integer 'direction of the entrance for pipe style warps\n    int8_t Direction = 0;\n//    Direction2 As Integer 'direction of the exit\n    int8_t Direction2 = 0;\n//    MapWarp As Boolean\n    bool MapWarp = false;\n//    MapX As Integer\n    vbint_t MapX = 0;\n//    MapY As Integer\n    vbint_t MapY = 0;\n//    curStars As Integer\n    uint8_t curStars = 0;\n//    maxStars As Integer\n    //! NEW: index into either LevelWarpSaveEntries (<0x7fff) or WorldLevel (>0x8000)\n    uint16_t save_info_idx = 0x8000;\n//EXTRA:\n    bool twoWay = false;\n    bool noPrintStars = false;\n    bool noEntranceScene = false;\n    bool cannonExit = false;\n    vbint_t cannonExitSpeed = 10;\n    bool stoodRequired = false; // Require player stood on the ground to enter this warp\n    eventindex_t eventEnter = EVENT_NONE;\n    eventindex_t eventExit = EVENT_NONE;\n    stringindex_t StarsMsg = STRINGINDEX_NONE;\n    vbint_t transitEffect = 0;\n//End Type\n\n    //! NEW: get the warp's LevelSaveInfo_t (based on save_info_idx)\n    inline const LevelSaveInfo_t save_info() const;\n};\n\n//Public Type Tile 'Tiles for the World\nstruct Tile_t\n{\n//    Location As Location\n    TinyLocation_t Location;\n//    Type As Integer\n    vbint_t Type = 0;\n//End Type\n\n    bool Active = true;\n\n    // int64_t Z = 0;\n};\n\n//Public Type Scene 'World Scenery\nstruct Scene_t\n{\n//    Location As Location\n    TinyLocation_t Location;\n//    Type As Integer\n    vbint_t Type = 0;\n//    Active As Boolean 'if false this won't be shown. used for paths that become available on a scene\n    bool Active = false;\n//End Type\n\n    // int64_t Z = 0;\n};\n\n//Public Type WorldPath 'World Paths\nstruct WorldPath_t\n{\n//    Location As Location\n    TinyLocation_t Location;\n//    Active As Boolean\n    bool Active = false;\n//    Type As Integer\n    vbint_t Type = 0;\n//End Type\n\n    // int64_t Z = 0;\n};\n\n//Public Type WorldMusic 'World Music\nstruct WorldMusic_t\n{\n//    Location As Location\n    TinyLocation_t Location;\n//    Type As Integer\n    vbint_t Type = 0;\n//    EXTRA: Custom Music\n    stringindex_t MusicFile = STRINGINDEX_NONE;\n//End Type\n\n    bool Active = true;\n\n    // int64_t Z = 0;\n};\n\n//! NEW: a camera zone for the world map\nstruct WorldArea_t\n{\n    TinyLocation_t Location;\n};\n\n//Public Type EditorCursor 'The editor's cursor\nstruct EditorCursor_t\n{\n//    X As Single\n    int X = -50;\n//    Y As Single\n    int Y = -50;\n//    SelectedMode As Integer 'cursor mode. eraser/npc/block/background\n\n    // class of object (uses same enum as Mode)\n    int InteractMode = 0;\n    // NEW: modifiers for type of interaction\n    int InteractFlags = 0;\n    // index of currently interacted / highlighted object\n    int InteractIndex = 0;\n    // coordinates where interaction began\n    int InteractX = 0;\n    int InteractY = 0;\n\n//    Location As Location\n    Location_t Location;\n//    Layer As String 'current layer\n    layerindex_t Layer = LAYER_NONE;\n//    Mode As Integer\n    int Mode = 0;\n//  New, used to represent warp entrance/exit, level settings submodes, and erase mode\n//  (erase mode: 0 for unset, positive number for each type of item, negative number for *everything*)\n    int SubMode = 0;\n//    Block As Block\n    Block_t Block;\n//    Water As Water\n    Water_t Water;\n//    Background As Background\n    Background_t Background;\n//    NPC As NPC\n    NPC_t NPC;\n//    Warp As Warp\n    Warp_t Warp;\n//    Tile As Tile\n    Tile_t Tile;\n//    Scene As Scene\n    Scene_t Scene;\n//    WorldLevel As WorldLevel\n    WorldLevel_t WorldLevel;\n//    WorldPath As WorldPath\n    WorldPath_t WorldPath;\n//    WorldMusic As WorldMusic\n    WorldMusic_t WorldMusic;\n//End Type\n\n//! NEW\n    WorldArea_t WorldArea;\n\n    // clears any strings of objects stored by the world cursor\n    void ClearStrings();\n};\n\n//Public Type WorldPlayer 'the players variables on the world map\nstruct WorldPlayer_t\n{\n//    Location As Location\n    TinyLocation_t Location;\n//    Type As Integer\n    int Type = 0;\n//    Frame As Integer\n    int Frame = 0;\n//    Frame2 As Integer\n    int Frame2 = 0;\n//    Move As Integer\n    int Move = 0;\n//    Move2 As Integer\n    int Move2 = 0;\n//    Move3 As Boolean\n    int Move3 = 0;\n// EXTRA: last move direction\n    int LastMove = 0;\n// EXTRA: current world map section\n    int Section = 0;\n//    LevelName As String\n    // std::string LevelName;\n    //! NEW: index to player's current WorldLevel, 0 if none. (Replaces LevelName and stars.)\n    vbint_t LevelIndex;\n//End Type\n};\n\n//Public Type Layer\n//moved into layers.h\n\n//Public Type CreditLine\nstruct CreditLine_t\n{\n//    Location As Location\n    IntegerLocation_t Location;\n//    Text As String\n    stringindex_t Text = STRINGINDEX_NONE;\n//End Type\n};\n\n//Public ScreenShake As Integer\n//extern int ScreenShake; // REPLACED with static variables at the update_gfx.cpp\n// TODO: Make it have multiple checkpoints and assign each one with different NPCs,\n// last one should resume player at given position\n//Public Checkpoint As String 'the filename of the level the player has a checkpoint in\nextern std::string Checkpoint;\n\nstruct Checkpoint_t\n{\n    int id = 0;\n};\n// List of taken checkpoints, spawn player at last of them\nextern std::vector<Checkpoint_t> CheckpointsList;\n\n//! List of stars / medal info entries for the levels NOT on the world map\nextern std::vector<LevelWarpSaveEntry_t> LevelWarpSaveEntries;\n\n//Public MagicHand As Boolean 'true if playing a level in the editor while not in fullscreen mode\nextern bool MagicHand;\n//Public testPlayer(1 To 2) As Player 'test level player settings\nextern RangeArr<Player_t, 1, maxLocalPlayers> testPlayer;\n//Public ClearBuffer As Boolean 'true to black the backbuffer\nextern bool ClearBuffer;\n//Public numLocked As Integer\nextern int numLocked;\n\n\n// replaced with g_config.fullscreen\n//Public resChanged As Boolean 'true if in fullscreen mode\n// extern bool resChanged;\n\n\n// NEW: start warp for a test level\nextern int16_t testStartWarp;\n\n// These have been partially moved into the Controls namespace\n// and partially moved to g_pollingInput (declared and defined in main/menu_controls.*)\n\n//Public inputKey As Integer 'for setting the players controls\n// extern int inputKey;\n//Public getNewKeyboard As Boolean 'true if setting keyboard controls\n// extern bool getNewKeyboard;\n//Public getNewJoystick As Boolean\n// extern bool getNewJoystick;\n//Public lastJoyButton As Integer\n// extern KM_Key lastJoyButton;\n\n// moved into game_main.h / game_loop.cpp\n//Public GamePaused As Boolean 'true if the game is paused\n// extern PauseCode GamePaused;\n\nextern std::string MessageTitle;\nenum MessageType\n{\n    // Has no title\n    MESSAGE_TYPE_NORMAL = 0,\n    // Has title, normal colour\n    MESSAGE_TYPE_SYS_INFO,\n    // Has title, yellow colour\n    MESSAGE_TYPE_SYS_WARNING,\n    // Has title, red colour\n    MESSAGE_TYPE_SYS_ERROR,\n    // Has title, red colour, game will quit after closing this\n    MESSAGE_TYPE_SYS_FATAL_ASSERT,\n};\nextern MessageType g_MessageType;\n\n//Public MessageText As String 'when talking to an npc\nextern std::string MessageText;\n\n// moved to menu_main.h\n\n//Public NumSelectWorld As Integer\n// extern int NumSelectWorld;\n//Public SelectWorld(1 To 100) As SelectWorld\n// struct SelectWorld_t;\n//extern RangeArr<SelectWorld_t, 1, maxSelectWorlds> SelectWorld;\n// extern std::vector<SelectWorld_t> SelectWorld;\n\nextern std::string g_recentAssetPack;\nextern std::string g_recentWorld1p;\nextern std::string g_recentWorld2p;\nextern std::string g_recentWorldEditor;\n\n// Absolute path to the level file or directory that contains them. Will be saved at gamesave\nextern std::string g_recentWorldIntro;\n// Absolute path to the outro level file. Will NOT be saved at gamesave, and will reset on world clear.\nextern std::string g_recentWorldOutro;\n// replaced with g_config.show_fps\n//Public ShowFPS As Boolean\n// extern bool ShowFPS;\n//Public PrintFPS As Double\nextern int PrintFPS;\nextern bool g_VanillaCam;\n\n// moved to \"screen.h\"\n//Public vScreen(0 To 2) As vScreen 'Sets up the players screens\n// extern RangeArr<vScreen_t, 0, 2> vScreen;\n//Public ScreenType As Integer 'The screen/view type\n// extern int ScreenType;\n//Public DScreenType As Integer 'The dynamic screen setup\n// extern int DScreenType;\n\n//Public LevelEditor As Boolean 'if true, load the editor\nextern bool LevelEditor;\n//Public WorldEditor As Boolean\nextern bool WorldEditor;\n//Public PlayerStart(1 To 2) As Location\nextern RangeArr<PlayerStart_t, 1, 2> PlayerStart;\n\n// NEW: force selected characters to be used (ignore the blockCharacter array)\nextern bool g_forceCharacter;\n\n//Public blockCharacter(0 To 20) As Boolean\nextern RangeArrI<bool, 0, 20, false> blockCharacter;\n\nstruct SelectWorld_t; // moved to main_menu.h\n\n//Public OwedMount(0 To maxPlayers) As Integer 'when a yoshi/boot is taken from the player this returns after going back to the world map\nextern RangeArrI<vbint_t, 0, maxPlayers, 0> OwedMount;\n//Public OwedMountType(0 To maxPlayers) As Integer\nextern RangeArrI<vbint_t, 0, maxPlayers, 0> OwedMountType;\n//EXTRA: set this flag once modern autoscroll used, otherwise, legacy will be used\nextern bool AutoUseModern;\n//Public AutoX(0 To maxSections) As Single 'for autoscroll\nextern RangeArr<numf_t, 0, maxSections> AutoX;\n//Public AutoY(0 To maxSections) As Single 'for autoscroll\nextern RangeArr<numf_t, 0, maxSections> AutoY;\n//Public numStars As Integer 'the number of stars the player has\nextern int numStars;\n\n//Public Type Star 'keeps track of where there player got the stars from\nstruct Star_t\n{\n//    level As String\n    std::string level;\n//    Section As Integer\n    int Section = 0;\n//End Type\n};\n\n//Public nPlay As nPlay ' for online stuff\n//Public Water(0 To maxWater) As Water\nextern RangeArr<Water_t, 0, maxWater> Water;\nDECLREF_T(Water);\n//Public numWater As Integer 'number of water\nextern int numWater;\n//Public Star(1 To 1000) As Star\nextern std::vector<Star_t> Star;\n//Public GoToLevel As String\nextern std::string GoToLevel;\n//! EXTRA: Hide entrance screen\nextern bool GoToLevelNoGameThing;\n//Public StartLevel As String 'start level for an episode\nextern std::string StartLevel;\n//Public NoMap As Boolean 'episode has no world map\nextern bool NoMap;\n//Public RestartLevel As Boolean 'restart the level on death\nextern bool RestartLevel;\n//! Per-level stars showing policy\nextern int WorldStarsShowPolicy;\n//Public LevelChop(0 To maxSections) As Single 'for drawing backgrounds when the level has been shrunk\n// extern float LevelChop[maxSections + 1];\n\n//'collision detection optimization. creates a table of contents for blocks\n// removed in favor of new block quadtree\n\n//Public Const FLBlocks As Long = 8000\n// const int64_t FLBlocks = 10000; // moved to global_constants.h and changed back to 8000\n//Public FirstBlock(-FLBlocks To FLBlocks) As Integer\n// extern RangeArr<int, -FLBlocks, FLBlocks> FirstBlock;\n//Public LastBlock(-FLBlocks To FLBlocks) As Integer\n// extern RangeArr<int, -FLBlocks, FLBlocks> LastBlock;\n\n//Public MidBackground As Integer 'for drawing backgrounds\nextern int MidBackground;\n//Public LastBackground As Integer 'last backgrounds to be drawn\nextern int LastBackground;\n//Public iBlocks As Integer 'blocks that are doing something. this keeps the number of interesting blocks\nextern int iBlocks;\n//Public iBlock(0 To maxBlocks) As Integer 'references a block #\nextern RangeArrI<vbint_t, 0, maxBlocks, 0> iBlock;\n//Public numTiles As Integer 'number of map tiles\nextern int numTiles;\n//Public numScenes As Integer 'number of scense\nextern int numScenes;\n//Public CustomMusic(0 To maxSections) As String 'section's custom music\nextern RangeArr<std::string, 0, maxSections> CustomMusic;\n//EXTRA: Max count of used sections\nextern int numSections;\n//Public level(0 To maxSections) As Location 'sections\nextern RangeArr<SpeedlessLocation_t, 0, maxSections> level;\n//Public LevelWrap(0 To maxSections) As Boolean 'Wrap around the level\nextern RangeArrI<bool, 0, maxSections, false> LevelWrap;\n//EXTRA: Wrap vertically around the level\nextern RangeArrI<bool, 0, maxSections, false> LevelVWrap;\n//Public OffScreenExit(0 To maxSections) As Boolean 'walk offscreen to end the level\nextern RangeArrI<bool, 0, maxSections, false> OffScreenExit;\n//Public bgMusic(0 To maxSections) As Integer 'music\nextern RangeArrI<vbint_t, 0, maxSections, 0> bgMusic;\n//Public bgMusicREAL(0 To maxSections) As Integer 'default music\nextern RangeArrI<vbint_t, 0, maxSections, 0> bgMusicREAL;\n//Public Background2REAL(0 To maxSections) As Integer 'background\nextern RangeArrI<vbint_t, 0, maxSections, 0> Background2REAL;\n//Public LevelREAL(0 To maxSections) As Location 'default background\nextern RangeArr<IntegerLocation_t, 0, maxSections> LevelREAL;\n//Public curMusic As Integer 'current music playing\nextern int curMusic;\n//Public bgColor(0 To maxSections) As Long 'obsolete\n// extern RangeArrI<long, 0, maxSections, 0> bgColor;    // unused since SMBX64, removed\n//Public Background2(0 To maxSections) As Integer 'level background\nextern RangeArrI<vbint_t, 0, maxSections, 0> Background2;\n//Public WorldPath(1 To maxWorldPaths) As WorldPath\nextern RangeArr<WorldPath_t, 1, maxWorldPaths> WorldPath;\nDECLREF_T(WorldPath);\n//Public numWorldPaths As Integer\nextern int numWorldPaths;\n//Public numWarps As Integer 'number of warps in a level\nextern int numWarps;\n//Public Warp(1 To maxWarps) As Warp 'define the warps\nextern RangeArr<Warp_t, 1, maxWarps> Warp;\nDECLREF_T(Warp);\n//Public Tile(1 To maxTiles) As Tile\nextern RangeArr<Tile_t, 1, maxTiles> Tile;\nDECLREF_T(Tile);\n//Public Scene(1 To maxScenes) As Scene\nextern RangeArr<Scene_t, 1, maxScenes> Scene;\nDECLREF_T(Scene);\n//Public Credit(1 To 200) As CreditLine 'for end game credits\nextern RangeArr<CreditLine_t, 1, maxCreditsLines> Credit;\nextern int numWorldCredits;\nextern int CreditOffsetY;\nextern int CreditTotalHeight;\n//Public numCredits As Integer 'number of credits\nextern int numCredits;\n//Public numBlock As Integer 'number of blocks\nextern int numBlock;\n//Public numBackground As Integer 'number of background objects\nextern int numBackground;\n//Public numNPCs As Integer\nextern int numNPCs;\n//Public numEffects As Integer\nextern int numEffects;\n//Public numPlayers As Integer\nextern int numPlayers;\n//Public numWorldLevels As Integer\nextern int numWorldLevels;\n//Public WorldMusic(1 To maxWorldMusic) As WorldMusic\nextern RangeArr<WorldMusic_t, 1, maxWorldMusic> WorldMusic;\nDECLREF_T(WorldMusic);\n//Public numWorldMusic As Integer\nextern int numWorldMusic;\n//NEW\nextern RangeArr<WorldArea_t, 1, maxWorldAreas> WorldArea;\nDECLREF_T(WorldArea);\nextern int numWorldAreas;\n//Public WorldLevel(1 To maxWorldLevels) As WorldLevel\nextern RangeArr<WorldLevel_t, 1, maxWorldLevels> WorldLevel;\nDECLREF_T(WorldLevel);\n\ninline const LevelSaveInfo_t Warp_t::save_info() const\n{\n    if(save_info_idx < 0x7FFF && save_info_idx < LevelWarpSaveEntries.size())\n        return LevelWarpSaveEntries[save_info_idx].save_info;\n\n    if(save_info_idx > 0x8000 && save_info_idx - 0x8000 <= numWorldLevels)\n        return WorldLevel[save_info_idx - 0x8000].save_info;\n\n    return LevelSaveInfo_t();\n}\n\n//Public Background(1 To maxBackgrounds) As Background\nextern RangeArr<Background_t, 1, (maxBackgrounds + maxWarps)> Background;\nDECLREF_T(Background);\n//Public Effect(1 To maxEffects) As Effect\nextern RangeArr<Effect_t, 1, maxEffects> Effect;\n\n//Public NPC(-128 To maxNPCs) As NPC\nextern RangeArr<NPC_t, -128, maxNPCs> NPC;\nDECLREF_T(NPC);\n//Public Block(0 To maxBlocks) As Block\nextern RangeArr<Block_t, 0, maxBlocks> Block;\nDECLREF_T(Block);\n\n//Public Player(0 To maxPlayers) As Player\nextern RangeArr<Player_t, 0, maxPlayers> Player;\n//Public MarioFrameX(0 To maxPlayerFrames) As Integer 'Player frame offset X\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> MarioFrameX;\n//Public MarioFrameY(0 To maxPlayerFrames) As Integer 'Player frame offset Y\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> MarioFrameY;\n//Public LuigiFrameX(0 To maxPlayerFrames) As Integer 'Player frame offset X\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> LuigiFrameX;\n//Public LuigiFrameY(0 To maxPlayerFrames) As Integer 'Player frame offset Y\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> LuigiFrameY;\n//Public PeachFrameX(0 To maxPlayerFrames) As Integer 'Player frame offset X\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> PeachFrameX;\n//Public PeachFrameY(0 To maxPlayerFrames) As Integer 'Player frame offset Y\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> PeachFrameY;\n//Public ToadFrameX(0 To maxPlayerFrames) As Integer 'Player frame offset X\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> ToadFrameX;\n//Public ToadFrameY(0 To maxPlayerFrames) As Integer 'Player frame offset Y\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> ToadFrameY;\n//Public LinkFrameX(0 To maxPlayerFrames) As Integer 'Player frame offset X\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> LinkFrameX;\n//Public LinkFrameY(0 To maxPlayerFrames) As Integer 'Player frame offset Y\nextern RangeArrI<int8_t, 0, maxPlayerFrames, 0> LinkFrameY;\n//Public BackgroundFence(0 To maxBackgroundType) As Boolean\nextern RangeArrI<bool, 0, maxBackgroundType, false> BackgroundFence;\n\n#if 0\n// moved to npc_traits.h\n\n//Public NPCFrameOffsetX(0 To maxNPCType) As Integer 'NPC frame offset X\nextern RangeArrI<int, 0, maxNPCType, 0> NPCFrameOffsetX;\n//Public NPCFrameOffsetY(0 To maxNPCType) As Integer 'NPC frame offset Y\nextern RangeArrI<int, 0, maxNPCType, 0> NPCFrameOffsetY;\n//Public NPCWidth(0 To maxNPCType) As Integer 'NPC width\nextern RangeArrI<int, 0, maxNPCType, 0> NPCWidth;\n//Public NPCHeight(0 To maxNPCType) As Integer 'NPC height\nextern RangeArrI<int, 0, maxNPCType, 0> NPCHeight;\n//Public NPCWidthGFX(0 To maxNPCType) As Integer 'NPC gfx width\nextern RangeArrI<int, 0, maxNPCType, 0> NPCWidthGFX;\n//Public NPCHeightGFX(0 To maxNPCType) As Integer 'NPC gfx height\nextern RangeArrI<int, 0, maxNPCType, 0> NPCHeightGFX;\n//Public NPCSpeedvar(0 To maxNPCType) As Single 'NPC Speed Change\nextern RangeArr<float, 0, maxNPCType> NPCSpeedvar;\n\n//Public NPCIsAShell(0 To maxNPCType) As Boolean 'Flags the NPC type if it is a shell\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsAShell;\n//Public NPCIsABlock(0 To maxNPCType) As Boolean 'Flag NPC as a block\nextern RangeArrI<bool, 0, maxNPCType, false> NPCIsABlock;\n//Public NPCIsAHit1Block(0 To maxNPCType) As Boolean 'Flag NPC as a hit1 block\nextern RangeArrI<bool, 0, maxNPCType, false> NPCIsAHit1Block;\n//Public NPCIsABonus(0 To maxNPCType) As Boolean 'Flags the NPC type if it is a bonus\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsABonus;\n//Public NPCIsACoin(0 To maxNPCType) As Boolean 'Flags the NPC type if it is a coin\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsACoin;\n//Public NPCIsAVine(0 To maxNPCType) As Boolean 'Flags the NPC type if it is a vine\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsAVine;\n//Public NPCIsAnExit(0 To maxNPCType) As Boolean 'Flags the NPC type if it is a level exit\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsAnExit;\n//Public NPCIsAParaTroopa(0 To maxNPCType) As Boolean 'Flags the NPC type as a para-troopa\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsAParaTroopa;\n//Public NPCIsCheep(0 To maxNPCType) As Boolean 'Flags the NPC type as a cheep cheep\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsCheep;\n//Public NPCJumpHurt(0 To maxNPCType) As Boolean 'Hurts the player even if it jumps on the NPC\nextern RangeArrI<bool, 0, maxNPCType, false> NPCJumpHurt;\n//Public NPCNoClipping(0 To maxNPCType) As Boolean 'NPC can go through blocks\nextern RangeArrI<bool, 0, maxNPCType, false> NPCNoClipping;\n//Public NPCScore(0 To maxNPCType) As Integer 'NPC score value\nextern RangeArrI<int, 0, maxNPCType, 0> NPCScore;\n//Public NPCCanWalkOn(0 To maxNPCType) As Boolean  'NPC can be walked on\nextern RangeArrI<bool, 0, maxNPCType, false> NPCCanWalkOn;\n//Public NPCGrabFromTop(0 To maxNPCType) As Boolean  'NPC can be grabbed from the top\nextern RangeArrI<bool, 0, maxNPCType, false> NPCGrabFromTop;\n//Public NPCTurnsAtCliffs(0 To maxNPCType) As Boolean  'NPC turns around at cliffs\nextern RangeArrI<bool, 0, maxNPCType, false> NPCTurnsAtCliffs;\n//Public NPCWontHurt(0 To maxNPCType) As Boolean  'NPC wont hurt the player\nextern RangeArrI<bool, 0, maxNPCType, false> NPCWontHurt;\n//Public NPCMovesPlayer(0 To maxNPCType) As Boolean 'Player can not walk through the NPC\nextern RangeArrI<bool, 0, maxNPCType, false> NPCMovesPlayer;\n//Public NPCStandsOnPlayer(0 To maxNPCType) As Boolean 'for the clown car\nextern RangeArrI<bool, 0, maxNPCType, false> NPCStandsOnPlayer;\n//Public NPCIsGrabbable(0 To maxNPCType) As Boolean 'Player can grab the NPC\nextern RangeArrI<bool, 0, maxNPCType, false> NPCIsGrabbable;\n//Public NPCIsBoot(0 To maxNPCType) As Boolean 'npc is a kurbo's shoe\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsBoot;\n//Public NPCIsYoshi(0 To maxNPCType) As Boolean 'npc is a yoshi\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsYoshi;\n//Public NPCIsToad(0 To maxNPCType) As Boolean 'npc is a toad\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsToad;\n//Public NPCNoYoshi(0 To maxNPCType) As Boolean 'Player can't eat the NPC\nextern RangeArrI<bool, 0, maxNPCType, false> NPCNoYoshi;\n//Public NPCForeground(0 To maxNPCType) As Boolean 'draw the npc in front\nextern RangeArrI<bool, 0, maxNPCType, false> NPCForeground;\n//Public NPCIsABot(0 To maxNPCType) As Boolean 'Zelda 2 Bot monster\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsABot;\n//Public NPCDefaultMovement(0 To maxNPCType) As Boolean 'default NPC movement\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCDefaultMovement;\n//Public NPCIsVeggie(0 To maxNPCType) As Boolean 'turnips\n// extern RangeArrI<bool, 0, maxNPCType, false> NPCIsVeggie;\n//Public NPCNoFireBall(0 To maxNPCType) As Boolean 'not hurt by fireball\nextern RangeArrI<bool, 0, maxNPCType, false> NPCNoFireBall;\n//Public NPCNoIceBall(0 To maxNPCType) As Boolean 'not hurt by fireball\nextern RangeArrI<bool, 0, maxNPCType, false> NPCNoIceBall;\n//Public NPCNoGravity(0 To maxNPCType) As Boolean 'not affected by gravity\nextern RangeArrI<bool, 0, maxNPCType, false> NPCNoGravity;\n\n//Public NPCFrame(0 To maxNPCType) As Integer\nextern RangeArrI<int, 0, maxNPCType, 0> NPCFrame;\n//Public NPCFrameSpeed(0 To maxNPCType) As Integer\nextern RangeArrI<int, 0, maxNPCType, 0> NPCFrameSpeed;\n//Public NPCFrameStyle(0 To maxNPCType) As Integer\nextern RangeArrI<int, 0, maxNPCType, 0> NPCFrameStyle;\n#endif\n\n//Public Type NPCDefaults 'Default NPC Settings\n// Moved into custom.cpp as local-private\n\n//Public BlockIsSizable(0 To maxBlockType) As Boolean 'Flags block if it is sizable\nextern RangeArrI<bool, 0, maxBlockType, false> BlockIsSizable;\n\nenum\n{\n    SLOPE_FLOOR = 0,\n    SLOPE_FLOOR_LEFT = -1,\n    SLOPE_FLOOR_RIGHT = +1,\n    SLOPE_CEILING = 0,\n    SLOPE_CEILING_LEFT = -1,\n    SLOPE_CEILING_RIGHT = +1,\n};\n//Public BlockSlope(0 To maxBlockType) As Integer 'block is sloped on top. -1 of block has an upward slope, 1 for downward\nextern RangeArrI<vbint_t, 0, maxBlockType, 0> BlockSlope;\n//Public BlockSlope2(0 To maxBlockType) As Integer 'block is sloped on the bottom.\nextern RangeArrI<vbint_t, 0, maxBlockType, 0> BlockSlope2;\n\n// moved into vScreen\n\n//Public vScreenX(0 To maxPlayers) As Double  'vScreen offset\n// extern RangeArr<double, 0, maxPlayers> vScreenX;\n//Public vScreenY(0 To maxPlayers) As Double 'vScreen offset\n// extern RangeArr<double, 0, maxPlayers> vScreenY;\n\n// moved into qScreenLoc\n\n//Public qScreenX(1 To maxPlayers) As Double  'vScreen offset adjust\n// extern RangeArr<double, 0, maxPlayers> qScreenX;\n//Public qScreenY(1 To maxPlayers) As Double 'vScreen offset adjust\n// extern RangeArr<double, 0, maxPlayers> qScreenY;\n\n//Public qScreen As Boolean 'Weather or not the screen needs adjusting\nextern bool qScreen;\n//! New: whether any canonical screens are currently in qScreen mode\nextern bool qScreen_canonical;\n\n// moved to \"screen.h\"\n// NEW: allows screen position to change during qScreen\n// extern RangeArr<vScreen_t, 0, 2> qScreenLoc;\n\n//Public BlockWidth(0 To maxBlockType) As Integer 'Block type width\nextern RangeArrI<vbint_t, 0, maxBlockType, 0> BlockWidth;\n//Public BlockHeight(0 To maxBlockType) As Integer 'Block type height\nextern RangeArrI<vbint_t, 0, maxBlockType, 0> BlockHeight;\n//Public BonusWidth(1 To 100) As Integer 'Bonus type width\n// extern RangeArrI<vbint_t, 0, maxBlockType, 0> BonusWidth;\n//Public BonusHeight(1 To 100) As Integer 'Bonus type height\n// extern RangeArrI<vbint_t, 0, maxBlockType, 0> BonusHeight;\n//Public EffectWidth(1 To maxEffectType) As Integer 'Effect width\nextern RangeArrI<vbint_t, 0, maxEffectType, 0> EffectWidth;\n//Public EffectHeight(1 To maxEffectType) As Integer 'Effect height\nextern RangeArrI<vbint_t, 0, maxEffectType, 0> EffectHeight;\n\n//Public Type EffectDefaults\nstruct EffectDefaults_t\n{\n#ifndef LOW_MEM\n//    EffectWidth(1 To maxEffectType) As Integer\n    RangeArrI<vbint_t, 1, maxEffectType, 0> EffectWidth;\n//    EffectHeight(1 To maxEffectType) As Integer\n    RangeArrI<vbint_t, 1, maxEffectType, 0> EffectHeight;\n#endif\n//EXTRA: count of frames (compute from the GFX height)\n    RangeArrI<vbint_t, 1, maxEffectType, 0> EffectFrames;\n//End Type\n};\n\n//Public EffectDefaults As EffectDefaults\nextern EffectDefaults_t EffectDefaults;\n//Public SceneWidth(1 To 100) As Integer 'Scene width\nextern RangeArrI<vbint_t, 1, maxSceneType, 0> SceneWidth;\n//Public SceneHeight(1 To 100) As Integer 'Scene height\nextern RangeArrI<vbint_t, 1, maxSceneType, 0> SceneHeight;\n//Public BackgroundHasNoMask(1 To maxBackgroundType) As Boolean\nextern RangeArrI<bool, 1, maxBackgroundType, false> BackgroundHasNoMask;\n//Public Foreground(0 To maxBackgroundType) As Boolean 'flags the background object to be drawn in front of everything else\nextern RangeArrI<bool, 0, maxBackgroundType, false> Foreground;\n//Public BackgroundWidth(1 To maxBackgroundType) As Integer\nextern RangeArrI<vbint_t, 1, maxBackgroundType, 0> BackgroundWidth;\n//Public BackgroundHeight(1 To maxBackgroundType) As Integer\nextern RangeArrI<vbint_t, 1, maxBackgroundType, 0> BackgroundHeight;\n//Public BackgroundFrame(1 To maxBackgroundType) As Integer\nextern RangeArrI<vbint_t, 1, maxBackgroundType, 0> BackgroundFrame;\n//Public BackgroundFrameCount(1 To maxBackgroundType) As Integer\nextern RangeArrI<vbint_t, 1, maxBackgroundType, 0> BackgroundFrameCount;\n//Public BlockFrame(1 To maxBlockType) As Integer 'What frame the block is on\nextern RangeArrI<vbint_t, 1, maxBlockType, 0> BlockFrame;\n//Public BlockFrame2(1 To maxBlockType) As Integer 'Counter to update the blocks frame\nextern RangeArrI<vbint_t, 1, maxBlockType, 0> BlockFrame2;\n\n// deprecated\n//Public sBlockArray(1 To 1000) As Integer 'sizable block array\n// extern RangeArrI<int, 1, 1000, 0> sBlockArray;\n//Public sBlockNum As Integer\n// extern int sBlockNum;\n\n//Public SceneFrame(1 To maxSceneType) As Integer 'What frame the scene is on\nextern RangeArrI<vbint_t, 1, maxSceneType, 0> SceneFrame;\n//Public SceneFrame2(1 To maxSceneType) As Integer 'Counter to update the scene frames\nextern RangeArrI<vbint_t, 1, maxSceneType, 0> SceneFrame2;\n//Public SpecialFrame(100) As Integer 'misc frames for things like coins and the kurbi shoe\nextern RangeArrI<vbint_t, 0, 9, 0> SpecialFrame;\n//Public SpecialFrameCount(100) As Single\nextern RangeArr<vbint_t, 0, 9> SpecialFrameCount;\n//Public TileWidth(1 To maxTileType) As Integer\nextern RangeArrI<vbint_t, 1, maxTileType, 0> TileWidth;\n//Public TileHeight(1 To maxTileType) As Integer\nextern RangeArrI<vbint_t, 1, maxTileType, 0> TileHeight;\n//Public TileFrame(1 To maxTileType) As Integer\nextern RangeArrI<vbint_t, 1, maxTileType, 0> TileFrame;\n//Public TileFrame2(1 To maxTileType) As Integer\nextern RangeArrI<vbint_t, 1, maxTileType, 0> TileFrame2;\n//Public LevelFrame(1 To 100) As Integer 'What frame the scene is on\nextern RangeArrI<vbint_t, 1, 100, 0> LevelFrame;\n//Public LevelFrame2(1 To 100) As Integer 'Counter to update the scene frames\nextern RangeArrI<vbint_t, 1, 100, 0> LevelFrame2;\n//Public BlockHasNoMask(1 To maxBlockType) As Boolean\nextern RangeArrI<bool, 1, maxBlockType, false> BlockHasNoMask;\n//Public LevelHasNoMask(1 To 100) As Boolean\n// extern RangeArrI<bool, 1, 100, false> LevelHasNoMask;\n//Public BlockOnlyHitspot1(0 To maxBlockType) As Boolean\nextern RangeArrI<bool, 0, maxBlockType, false> BlockOnlyHitspot1;\n//Public BlockKills(0 To maxBlockType) As Boolean 'block is lava\nextern RangeArrI<bool, 0, maxBlockType, false> BlockKills;\n//Public BlockKills2(0 To maxBlockType) As Boolean\nextern RangeArrI<bool, 0, maxBlockType, false> BlockKills2;\n//Public BlockHurts(0 To maxBlockType) As Boolean 'block hurts the player\nextern RangeArrI<bool, 0, maxBlockType, false> BlockHurts;\n//Public BlockPSwitch(0 To maxBlockType) As Boolean 'block is affected by the p switch\nextern RangeArrI<bool, 0, maxBlockType, false> BlockPSwitch;\n//Public BlockNoClipping(0 To maxBlockType) As Boolean 'player/npcs can walk throught the block\nextern RangeArrI<bool, 0, maxBlockType, false> BlockNoClipping;\n//Public CoinFrame(1 To 10) As Integer 'What frame the coin is on\nextern RangeArrI<vbint_t, 1, 10, 0> CoinFrame;\n//Public CoinFrame2(1 To 10) As Integer 'Counter to update the coin frames\nextern RangeArrI<vbint_t, 1, 10, 0> CoinFrame2;\n//Public EditorCursor As EditorCursor\nextern EditorCursor_t EditorCursor;\n//Public EditorControls As EditorControls\n\nextern SharedControls_t l_SharedControls;\n\nextern CursorControls_t SharedCursor;\n\nextern EditorControls_t EditorControls;\n\nextern bool SharedPause;\nextern bool SharedPauseLegacy;\nextern bool SharedPauseForce;\n\n// extern RangeArr<CursorControls_t, 1, maxLocalPlayers> PlayerCursor;\n\n//Public Sound(1 To numSounds) As Integer\n// extern RangeArrI<int, 1, numSounds, 0> Sound;\n//Public SoundPause(1 To numSounds) As Integer\nextern RangeArrI<vbint_t, 1, numSounds, 0> SoundPause;\n//EXTRA: Immediately quit level because of a fatal error\nextern bool ErrorQuit;\n//Public EndLevel As Boolean 'End the level and move to the next\nextern bool EndLevel;\n\nenum LevelMacro_t\n{\n    LEVELMACRO_OFF = 0,\n    LEVELMACRO_CARD_ROULETTE_EXIT = 1,\n    LEVELMACRO_QUESTION_SPHERE_EXIT = 2,\n    LEVELMACRO_KEYHOLE_EXIT = 3,\n    LEVELMACRO_CRYSTAL_BALL_EXIT = 4,\n    LEVELMACRO_GAME_COMPLETE_EXIT = 5,\n    LEVELMACRO_STAR_EXIT = 6,\n    LEVELMACRO_GOAL_TAPE_EXIT = 7,\n    LEVELMACRO_FLAG_EXIT = 8,\n    LEVELMACRO_ALT_FLAG_EXIT = 9, // reserved\n};\n//Public LevelMacro As Integer 'Shows a level outro when beat\nextern LevelMacro_t LevelMacro;\n\n//Public LevelMacroCounter As Integer\nextern int LevelMacroCounter;\n\n//EXTRA: variant for level macro.\n// Keyhole exit: which BGO caused the key exit?\n// Card roulette exit: negative value indicates triggered by cheat\nextern int LevelMacroWhich;\n\n//EXTRA: can the player wall-jump?\nextern bool CanWallJump;\n\n//Public numJoysticks As Integer\n// extern int numJoysticks;\n\n//Public FileName As String\nextern std::string FileName;\n//! EXTRA: A full filename (the \"FileName\" is now has the \"base name\" sense)\nextern std::string FileNameFull;\n//Public FullFileName As String\nextern std::string FullFileName;\n//Public FileNamePath As String\nextern std::string FileNamePath;\n//! EXTRA: The recent sub-hub level file\nextern std::string FileRecentSubHubLevel;\n//! EXTRA: The format of the current file\nextern int FileFormat;\n\n//! EXTRA: World map preserved filename\nextern std::string FileNameWorld;\n//! EXTRA: World map preserved full path\nextern std::string FileNameFullWorld;\n//! EXTRA: World map preserved parent directory\nextern std::string FileNamePathWorld;\n//! EXTRA: The format of the world file\nextern int FileFormatWorld;\n\n//! EXTRA: Identify that episode is an intro level\nextern bool IsEpisodeIntro;\n//! EXTRA: Identify that level is a hub or sub-hub where player can save the game\nextern bool IsHubLevel;\n//Public Coins As Integer 'number of coins\nextern int Coins;\n//Public Lives As Single 'number of lives\nextern int Lives;\n//NEW: tracker of number of hundreds of coins that have been obtained\nextern int g_100s;\n//Public EndIntro As Boolean\n// extern bool EndIntro;\n//Public ExitMenu As Boolean\n// extern bool ExitMenu;\n//Public LevelSelect As Boolean 'true if game should load the world map\nextern bool LevelSelect;\n\n\nextern bool LevelRestartRequested;\n//Public WorldPlayer(1) As WorldPlayer\nextern RangeArr<WorldPlayer_t, 0, 1> WorldPlayer;\n\n// unsafe to reorder these -- they are used in the WLD format and also included in the save file\nenum LevelBeatCode_t\n{\n    BEATCODE_SETUP = -3,\n    BEATCODE_RESTART = -2,\n    BEATCODE_QUIT = -1,\n    BEATCODE_NONE = 0,\n    BEATCODE_CARD_ROULETTE = 1,\n    BEATCODE_QUESTION_SPHERE = 2,\n    BEATCODE_OFFSCREEN = 3,\n    BEATCODE_KEYHOLE = 4,\n    BEATCODE_CRYSTAL_BALL = 5,\n    BEATCODE_WARP = 6,\n    BEATCODE_STAR = 7,\n    BEATCODE_GOAL_TAPE = 8,\n    BEATCODE_FLAG = 9,      // new\n    BEATCODE_ALT_FLAG = 10, // new, not yet implemented\n    BEATCODE_RESERVED_1 = 11,    // new, reserved for scripting\n    BEATCODE_RESERVED_2 = 12,    // new, reserved for scripting\n    BEATCODE_RESERVED_3 = 13,    // new, reserved for scripting\n    BEATCODE_RESERVED_4 = 14,    // new, reserved for scripting\n    BEATCODE_GAME_COMPLETE = 15, // new, used to mark the game as completed from this level\n};\n//Public LevelBeatCode As Integer ' code for the way the plauer beat the level\nextern LevelBeatCode_t LevelBeatCode;\n//Public curWorldLevel As Integer\nextern int curWorldLevel;\n//Public curWorldMusic As Integer\nextern int curWorldMusic;\n//EXTRA: Custom world music\nextern std::string curWorldMusicFile;\n\n// EXTRA: convenience functions to check if world music is new or not, and play\n// defined in main/world_loop.cpp\nbool g_isWorldMusicNotSame(WorldMusic_t &mus);\nvoid g_playWorldMusic(WorldMusic_t &mus);\n\n//Public NoTurnBack(0 To maxSections) As Boolean\nextern RangeArrI<bool, 0, maxSections, false> NoTurnBack;\n//Public UnderWater(0 To maxSections) As Boolean\nextern RangeArrI<bool, 0, maxSections, false> UnderWater;\n\n// EXTRA: track extra JSON info from a loaded level\nextern RangeArrI<stringindex_t, 0, maxSections, STRINGINDEX_NONE> SectionJSONInfo;\n\n// world custom data\nextern std::string WldxCustomParams;\nextern std::vector<std::string> SubHubLevels;\n\n//Public TestLevel As Boolean\nextern bool TestLevel;\n//Public GameMenu As Boolean\nextern bool GameMenu;\n//Public WorldName As String\nextern std::string WorldName;\n//Public selWorld As Integer\nextern int selWorld;\n//Public selSave As Integer\nextern int selSave;\n//Public PSwitchTime As Integer\nextern int PSwitchTime;\n//Public PSwitchStop As Integer\nextern int PSwitchStop;\n// NEW: tracks period of player's invincibility to all damage except lava, offscreen, and crushing\nextern int InvincibilityTime;\n//Public PSwitchPlayer As Integer\nextern int PSwitchPlayer;\n\n// newly extended\n\nstruct SavedChar_t\n{\n    uint16_t HeldBonus = NPCID(0);\n    uint8_t State = 1;\n    uint8_t Mount = 0;\n    uint8_t MountType = 0;\n    uint8_t Hearts = 1;\n    uint8_t Character = 1;\n\n    inline SavedChar_t& operator=(const SavedChar_t& ch) = default;\n    inline SavedChar_t& operator=(const Player_t& p)\n    {\n        HeldBonus = p.HeldBonus;\n        State = p.State;\n        Mount = p.Mount;\n        MountType = p.MountType;\n        Hearts = p.Hearts;\n        Character = p.Character;\n\n        return *this;\n    }\n    inline operator Player_t() const\n    {\n        Player_t p;\n\n        p.HeldBonus = NPCID(HeldBonus);\n        p.State = State;\n        p.Mount = Mount;\n        p.MountType = MountType;\n        p.Hearts = Hearts;\n        p.Character = Character;\n\n        return p;\n    }\n};\n\nstruct SaveSlotInfo_t\n{\n    int64_t Time = 0;\n    bool    FailsEnabled = false;\n    int32_t Fails = 0;\n    int32_t Score = 0;\n    int ConfigDefaults = 0;\n\n    RangeArr<SavedChar_t, 1, 5> SavedChar;\n\n    // Save progress percent, displayed at title. <0 value denotes uninitialized saves\n    int Progress = -1;\n    int Stars = 0;\n    int Lives = 3;\n    int Hundreds = 0;\n    int Coins = 0;\n};\n\n// new: all save info\nextern RangeArr<SaveSlotInfo_t, 1, maxSaveSlots> SaveSlotInfo;\n\n// deprecated\n//Public SaveSlot(1 To 3) As Integer\n// extern RangeArrI<int, 1, maxSaveSlots, 0> SaveSlot;\n//Public SaveStars(1 To 3) As Integer\n// extern RangeArrI<int, 1, maxSaveSlots, 0> SaveStars;\n\n\n//Public BeltDirection As Integer 'direction of the converyer belt blocks\nextern int BeltDirection;\n//Public BeatTheGame As Boolean 'true if the game has been beaten\nextern bool BeatTheGame;\n// 'for frameskip\n//Public cycleCount As Integer\n//extern int cycleCount;\n//Public fpsTime As Double\n//extern double fpsTime;\n//Public fpsCount As Double\n//extern double fpsCount;\n// replaced with g_config.enable_frameskip\n//Public FrameSkip As Boolean\n// extern bool FrameSkip;\n//Public GoalTime As Double\n//extern double GoalTime;\n//Public overTime As Double\n//extern double overTime;\n//'------------------\n//Public worldCurs As Integer\n// extern int worldCurs;\n//Public minShow As Integer\n// extern int minShow;\n//Public maxShow As Integer\n// extern int maxShow;\n\n//Public Type Physics\nstruct Physics_t\n{\n//    PlayerJumpHeight As Integer\n    int PlayerJumpHeight = 0;\n//    PlayerBlockJumpHeight As Integer\n    int PlayerBlockJumpHeight = 0;\n//    PlayerHeadJumpHeight As Integer\n    int PlayerHeadJumpHeight = 0;\n//    PlayerNPCJumpHeight As Integer\n    int PlayerNPCJumpHeight = 0;\n//    PlayerSpringJumpHeight As Integer\n    int PlayerSpringJumpHeight = 0;\n//    PlayerJumpVelocity As Single\n    num_t PlayerJumpVelocity = 0_n;\n//    PlayerRunSpeed As Single\n    int PlayerRunSpeed = 0;\n//    PlayerWalkSpeed As Single\n    int PlayerWalkSpeed = 0;\n//    PlayerTerminalVelocity As Integer\n    int PlayerTerminalVelocity = 0;\n//    PlayerGravity As Single\n    num_t PlayerGravity = 0_n;\n//    PlayerHeight(1 To numCharacters, 1 To numStates) As Integer\n    RangeArr<RangeArrI<int, 1, numStates, 0>, 1, numCharacters> PlayerHeight;\n//    PlayerDuckHeight(1 To numCharacters, 1 To numStates) As Integer\n    RangeArr<RangeArrI<int, 1, numStates, 0>, 1, numCharacters> PlayerDuckHeight;\n//    PlayerWidth(1 To numCharacters, 1 To numStates) As Integer\n    RangeArr<RangeArrI<int, 1, numStates, 0>, 1, numCharacters> PlayerWidth;\n//    PlayerGrabSpotX(1 To numCharacters, 1 To numStates) As Integer\n    RangeArr<RangeArrI<int, 1, numStates, 0>, 1, numCharacters> PlayerGrabSpotX;\n//    PlayerGrabSpotY(1 To numCharacters, 1 To numStates) As Integer\n    RangeArr<RangeArrI<int, 1, numStates, 0>, 1, numCharacters> PlayerGrabSpotY;\n//    NPCTimeOffScreen As Integer\n    int NPCTimeOffScreen = 0;\n//    NPCCanHurtWait As Integer\n    int NPCCanHurtWait = 0;\n//    NPCShellSpeed As Single\n    num_t NPCShellSpeed = 0_n;\n//    NPCShellSpeedY As Single\n    num_t NPCShellSpeedY = 0_n;\n//    NPCWalkingSpeed As Single\n    num_t NPCWalkingSpeed = 0_n;\n//    NPCWalkingOnSpeed As Single\n    num_t NPCWalkingOnSpeed = 0_n;\n//    NPCMushroomSpeed As Single\n    num_t NPCMushroomSpeed = 0_n;\n//    NPCGravity As Single\n    // Was previously a float, changed to a double for efficient comparisons. All stores must factor through float.\n    num_t NPCGravity = 0_n;\n//    NPCGravityReal As Single\n    numf_t NPCGravityReal = 0_nf;\n//    NPCPSwitch As Integer\n    int NPCPSwitch = 0;\n//End Type\n};\n\n//Public Type Events\n//moved into \"layers.h\"\n\n//Public ReturnWarp As Integer 'for when the player returns from a warp\nextern int ReturnWarp;\n//! EXTRA: Used to be captured into game save\nextern int ReturnWarpSaved;\n//Public StartWarp As Integer\nextern int StartWarp;\n//Public Physics As Physics\nextern Physics_t Physics;\n//Public MenuCursor As Integer\nextern int MenuCursor;\n//Public MenuMode As Integer\nextern int MenuMode;\n//Public MenuCursorCanMove As Boolean\nextern bool MenuCursorCanMove;\n//Public MenuCursorCanMove2 As Boolean 'Joystick\n// Now used to check if it's okay to go back (separately from the other actions)\nextern bool MenuCursorCanMove_Back;\n//Public NextFrame As Boolean\n// extern bool NextFrame;\n//Public StopHit As Integer\nextern int StopHit;\n//Public MouseRelease As Boolean\nextern bool MouseRelease;\n//Public TestFullscreen As Boolean\n// extern bool TestFullscreen;\n////Public keyDownAlt As Boolean 'for alt/enter fullscreen\n//extern bool keyDownAlt;\n////Public keyDownEnter As Boolean\n//extern bool keyDownEnter;\n\n// no longer needed thanks to block quadtree, BUT used to recreate one buggy behavior\n//Public BlocksSorted As Boolean 'if using block optimization it requires the locks to be sorted\nextern bool BlocksSorted;\n\n//Public SingleCoop As Integer 'cheat code\nextern int SingleCoop;\n//NEW: checks whether a superbdemo* cheat code is active\nextern bool g_ClonedPlayerMode;\n//Public CheatString As String 'logs keys for cheats\n//extern std::string CheatString; // Made static at cheat_code.cpp\n//Public GameOutro As Boolean 'true if showing credits\nextern bool GameOutro;\nextern bool GameOutroDoQuit;\n//Public CreditChop As Single\nextern int CreditChop;\n//Public EndCredits As Integer\nextern int EndCredits;\n//Public curStars As Integer 'number of stars\nextern int curStars;\n//Public maxStars As Integer 'max number of stars in the game\n// extern int maxStars;\n//'cheat codes --------------\n//Public ShadowMode As Boolean 'cheat code\nextern bool ShadowMode;\n//Public MultiHop As Boolean\nextern bool MultiHop;\n//Public SuperSpeed As Boolean\nextern bool SuperSpeed;\n//Public WalkAnywhere As Boolean\nextern bool WalkAnywhere;\n//Public FlyForever As Boolean\nextern bool FlyForever;\n//Public FreezeNPCs As Boolean\nextern bool FreezeNPCs;\n//Public CaptainN As Boolean\nextern bool CaptainN;\n//Public FlameThrower As Boolean\nextern bool FlameThrower;\n//Public CoinMode As Boolean 'cheat code\nextern bool CoinMode;\n//Public WorldUnlock As Boolean\n// extern bool WorldUnlock;\n// replaced with g_config.unlimited_framerate\n//Public MaxFPS As Boolean\n// extern bool MaxFPS;\n//Public GodMode As Boolean\nextern bool GodMode;\n//Public GrabAll As Boolean\nextern bool GrabAll;\n//Public Cheater As Boolean 'if the player is a cheater\nextern bool Cheater;\n\n#ifdef ENABLE_ANTICHEAT_TRAP\n//EXTRA: Quit the game like \"game over\" even with enough lifes\nextern bool CheaterMustDie;\n#endif\n\n//'--------------------------------\n//Public WorldCredits(1 To 5) As String\nextern RangeArr<std::string, 1, maxWorldCredits> WorldCredits;\n//Public Score As Long 'player's score\nextern int Score;\n//Public Points(1 To 13) As Integer\nextern RangeArrI<int, 1, 13, 0> Points;\n\n// moved into the implementation details of InputMethodProfile_Joystick\n//Public oldJumpJoy As Integer\n// extern KM_Key oldJumpJoy;\n\n//Public MaxWorldStars As Integer 'maximum number of world stars\nextern int MaxWorldStars;\n//Public Debugger As Boolean 'if the debugger window is open\n// extern bool Debugger;\n//Public SavedChar(0 To 10) As Player 'Saves the Player's Status\nextern RangeArr<SavedChar_t, 0, 10> SavedChar;\n\nextern bool LoadingInProcess;\n//Public LoadCoins As Integer\nextern int LoadCoins;\n//Public LoadCoinsT As Single\nextern unsigned int LoadCoinsT;\n\n//'Game Graphics\n//Public GFXBlockCustom(1 To maxBlockType) As Boolean\n// extern RangeArrI<bool, 1, maxBlockType, false> GFXBlockCustom;\n//Public GFXBlock(1 To maxBlockType) As Long\n//extern RangeArrI<long, 1, maxBlockType, 0> GFXBlock;\n#define GFXBlock GFXBlockBMP\n//Public GFXBlockMask(1 To maxBlockType) As Long\n//extern RangeArrI<long, 1, maxBlockType, 0> GFXBlockMask;\n//Public GFXBlockBMP(1 To maxBlockType) As StdPicture\nextern RangeArr<StdPicture, 1, maxBlockType> GFXBlockBMP;\n//Public GFXBlockMaskBMP(1 To maxBlockType) As StdPicture\n//extern RangeArr<StdPicture, 1, maxBlockType> GFXBlockMaskBMP;\n//Public GFXBackground2Custom(1 To numBackground2) As Boolean\n// extern RangeArrI<bool, 1, numBackground2, false> GFXBackground2Custom;\n//Public GFXBackground2(1 To numBackground2) As Long\n//extern RangeArrI<long, 1, numBackground2, 0> GFXBackground2;\n#define GFXBackground2 GFXBackground2BMP\n//Public GFXBackground2BMP(1 To numBackground2) As StdPicture\nextern RangeArr<StdPicture, 1, numBackground2> GFXBackground2BMP;\n//Public GFXBackground2Height(1 To numBackground2) As Integer\n// extern RangeArrI<vbint_t, 1, numBackground2, 0> GFXBackground2Height;\n//Public GFXBackground2Width(1 To numBackground2) As Integer\n// extern RangeArrI<vbint_t, 1, numBackground2, 0> GFXBackground2Width;\n//Public GFXNPCCustom(1 To maxNPCType) As Boolean\n// extern RangeArrI<bool, 1, maxNPCType, false> GFXNPCCustom;\n//Public GFXNPC(1 To maxNPCType) As Long\n//extern RangeArrI<long, 1, maxNPCType, 0> GFXNPC;\n#define GFXNPC GFXNPCBMP\n//Public GFXNPCMask(1 To maxNPCType) As Long\n//extern RangeArrI<long, 1, maxNPCType, 0> GFXNPCMask;\n//Public GFXNPCBMP(1 To maxNPCType) As StdPicture\nextern RangeArr<StdPicture, 0, maxNPCType> GFXNPCBMP;\n//Public GFXNPCMaskBMP(1 To maxNPCType) As StdPicture\n//extern RangeArr<StdPicture, 0, maxNPCType> GFXNPCMaskBMP;\n\n// removed (GFXNPC[Type].w/w used instead, only used by veggies originally)\n//Public GFXNPCHeight(1 To maxNPCType) As Integer\n// extern RangeArrI<int, 1, maxNPCType, 0> GFXNPCHeight;\n//Public GFXNPCWidth(1 To maxNPCType) As Integer\n// extern RangeArrI<int, 1, maxNPCType, 0> GFXNPCWidth;\n\n//Public GFXEffectCustom(1 To maxEffectType) As Boolean\nextern RangeArrI<bool, 1, maxEffectType, false> GFXEffectCustom;\n//Public GFXEffect(1 To maxEffectType) As Long\n//extern RangeArrI<long, 1, maxEffectType, 0> GFXEffect;\n#define GFXEffect GFXEffectBMP\n//Public GFXEffectMask(1 To maxEffectType) As Long\n//extern RangeArrI<long, 1, maxEffectType, 0> GFXEffectMask;\n//Public GFXEffectBMP(1 To maxEffectType) As StdPicture\nextern RangeArr<StdPicture, 1, maxEffectType> GFXEffectBMP;\n//Public GFXEffectMaskBMP(1 To maxEffectType) As StdPicture\n//extern RangeArr<StdPicture, 1, maxEffectType> GFXEffectMaskBMP;\n//Public GFXEffectHeight(1 To maxEffectType) As Integer\n// extern RangeArrI<vbint_t, 1, maxEffectType, 0> GFXEffectHeight;\n//Public GFXEffectWidth(1 To maxEffectType) As Integer\n// extern RangeArrI<vbint_t, 1, maxEffectType, 0> GFXEffectWidth;\n//Public GFXBackgroundCustom(1 To maxBackgroundType) As Boolean\n// extern RangeArrI<bool, 1, maxBackgroundType, false> GFXBackgroundCustom;\n//Public GFXBackground(1 To maxBackgroundType) As Long\n//extern RangeArrI<long, 1, maxBackgroundType, 0> GFXBackground;\n#define GFXBackground GFXBackgroundBMP\n//Public GFXBackgroundMask(1 To maxBackgroundType) As Long\n//extern RangeArrI<long, 1, maxBackgroundType, 0> GFXBackgroundMask;\n//Public GFXBackgroundBMP(1 To maxBackgroundType) As StdPicture\nextern RangeArr<StdPicture, 1, maxBackgroundType> GFXBackgroundBMP;\n//Public GFXBackgroundMaskBMP(1 To maxBackgroundType) As StdPicture\n//extern RangeArr<StdPicture, 1, maxBackgroundType> GFXBackgroundMaskBMP;\n//Public GFXBackgroundHeight(1 To maxBackgroundType) As Integer\n// extern RangeArrI<vbint_t, 1, maxBackgroundType, 0> GFXBackgroundHeight;\n//Public GFXBackgroundWidth(1 To maxBackgroundType) As Integer\n// extern RangeArrI<vbint_t, 1, maxBackgroundType, 0> GFXBackgroundWidth;\n\nextern const char *GFXPlayerNames[numCharacters];\nextern RangeArr<StdPicture, 1, numStates> *GFXCharacterBMP[numCharacters];\nextern RangeArrI<vbint_t, 1, numStates, 0> *GFXCharacterWidth[numCharacters];\nextern RangeArrI<vbint_t, 1, numStates, 0> *GFXCharacterHeight[numCharacters];\n// extern RangeArrI<bool, 1, 10, false> *GFXCharacterCustom[numCharacters];\n\n//Public GFXMarioCustom(1 To 10) As Boolean\n// extern RangeArrI<bool, 1, 10, false> GFXMarioCustom;\n//Public GFXMario(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXMario;\n#define GFXMario GFXMarioBMP\n//Public GFXMarioMask(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXMarioMask;\n//Public GFXMarioBMP(1 To 10) As StdPicture\nextern RangeArr<StdPicture, 1, numStates> GFXMarioBMP;\n//Public GFXMarioMaskBMP(1 To 10) As StdPicture\n//extern RangeArr<StdPicture, 1, 10> GFXMarioMaskBMP;\n//Public GFXMarioHeight(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXMarioHeight;\n//Public GFXMarioWidth(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXMarioWidth;\n//Public GFXLuigiCustom(1 To 10) As Boolean\n// extern RangeArrI<bool, 1, 10, false> GFXLuigiCustom;\n//Public GFXLuigi(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXLuigi;\n#define GFXLuigi GFXLuigiBMP\n//Public GFXLuigiMask(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXLuigiMask;\n//Public GFXLuigiBMP(1 To 10) As StdPicture\nextern RangeArr<StdPicture, 1, numStates> GFXLuigiBMP;\n//Public GFXLuigiMaskBMP(1 To 10) As StdPicture\n//extern RangeArr<StdPicture, 1, 10> GFXLuigiMaskBMP;\n//Public GFXLuigiHeight(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXLuigiHeight;\n//Public GFXLuigiWidth(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXLuigiWidth;\n//Public GFXPeachCustom(1 To 10) As Boolean\n// extern RangeArrI<bool, 1, 10, false> GFXPeachCustom;\n//Public GFXPeach(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXPeach;\n#define GFXPeach GFXPeachBMP\n//Public GFXPeachMask(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXPeachMask;\n//Public GFXPeachBMP(1 To 10) As StdPicture\nextern RangeArr<StdPicture, 1, numStates> GFXPeachBMP;\n//Public GFXPeachMaskBMP(1 To 10) As StdPicture\n//extern RangeArr<StdPicture, 1, 10> GFXPeachMaskBMP;\n//Public GFXPeachHeight(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXPeachHeight;\n//Public GFXPeachWidth(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXPeachWidth;\n//Public GFXToadCustom(1 To 10) As Boolean\n// extern RangeArrI<bool, 1, 10, false> GFXToadCustom;\n//Public GFXToad(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXToad;\n#define GFXToad GFXToadBMP\n//Public GFXToadMask(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXToadMask;\n//Public GFXToadBMP(1 To 10) As StdPicture\nextern RangeArr<StdPicture, 1, numStates> GFXToadBMP;\n//Public GFXToadMaskBMP(1 To 10) As StdPicture\n//extern RangeArr<StdPicture, 1, 10> GFXToadMaskBMP;\n//Public GFXToadHeight(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXToadHeight;\n//Public GFXToadWidth(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXToadWidth;\n\n//Public GFXLinkCustom(1 To 10) As Boolean\n// extern RangeArrI<bool, 1, 10, false> GFXLinkCustom;\n//Public GFXLink(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXLink;\n#define GFXLink GFXLinkBMP\n//Public GFXLinkMask(1 To 10) As Long\n//extern RangeArrI<long, 1, 10, 0> GFXLinkMask;\n//Public GFXLinkBMP(1 To 10) As StdPicture\nextern RangeArr<StdPicture, 1, numStates> GFXLinkBMP;\n//Public GFXLinkMaskBMP(1 To 10) As StdPicture\n//extern RangeArr<StdPicture, 1, 10> GFXLinkMaskBMP;\n//Public GFXLinkHeight(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXLinkHeight;\n//Public GFXLinkWidth(1 To 10) As Integer\n// extern RangeArrI<vbint_t, 1, 10, 0> GFXLinkWidth;\n//Public GFXYoshiBCustom(1 To 10) As Boolean\n// extern RangeArrI<bool, 1, maxYoshiGfx, false> GFXYoshiBCustom;\n//Public GFXYoshiB(1 To 10) As Long\n//extern RangeArrI<long, 1, maxYoshiGfx, 0> GFXYoshiB;\n#define GFXYoshiB GFXYoshiBBMP\n//Public GFXYoshiBMask(1 To 10) As Long\n//extern RangeArrI<long, 1, maxYoshiGfx, 0> GFXYoshiBMask;\n//Public GFXYoshiBBMP(1 To 10) As StdPicture\nextern RangeArr<StdPicture, 1, maxYoshiGfx> GFXYoshiBBMP;\n//Public GFXYoshiBMaskBMP(1 To 10) As StdPicture\n//extern RangeArr<StdPicture, 1, maxYoshiGfx> GFXYoshiBMaskBMP;\n//Public GFXYoshiTCustom(1 To 10) As Boolean\n// extern RangeArrI<bool, 1, maxYoshiGfx, false> GFXYoshiTCustom;\n//Public GFXYoshiT(1 To 10) As Long\n//extern RangeArrI<long, 1, maxYoshiGfx, 0> GFXYoshiT;\n#define GFXYoshiT GFXYoshiTBMP\n//Public GFXYoshiTMask(1 To 10) As Long\n//extern RangeArrI<long, 1, maxYoshiGfx, 0> GFXYoshiTMask;\n//Public GFXYoshiTBMP(1 To 10) As StdPicture\nextern RangeArr<StdPicture, 1, maxYoshiGfx> GFXYoshiTBMP;\n//Public GFXYoshiTMaskBMP(1 To 10) As StdPicture\n//extern RangeArr<StdPicture, 1, maxYoshiGfx> GFXYoshiTMaskBMP;\n//'World Map Graphics\n//Public GFXTileCustom(1 To maxTileType) As Long\n// extern RangeArrI<bool, 1, maxTileType, false> GFXTileCustom;\n//Public GFXTile(1 To maxTileType) As Long\n//extern RangeArrI<long, 1, maxTileType, 0> GFXTile;\n#define GFXTile GFXTileBMP\n//Public GFXTileBMP(1 To maxTileType) As StdPicture\nextern RangeArr<StdPicture, 1, maxTileType> GFXTileBMP;\n//Public GFXTileHeight(1 To maxTileType) As Integer\n// extern RangeArrI<vbint_t, 1, maxTileType, 0> GFXTileHeight;\n//Public GFXTileWidth(1 To maxTileType) As Integer\n// extern RangeArrI<vbint_t, 1, maxTileType, 0> GFXTileWidth;\n//Public GFXLevelCustom(0 To maxLevelType) As Long\n// extern RangeArrI<bool, 0, maxLevelType, false> GFXLevelCustom;\n//Public GFXLevel(0 To maxLevelType) As Long\n//extern RangeArrI<long, 0, maxLevelType, 0> GFXLevel;\n#define GFXLevel GFXLevelBMP\n//Public GFXLevelMask(0 To maxLevelType) As Long\n//extern RangeArrI<long, 0, maxLevelType, 0> GFXLevelMask;\n//Public GFXLevelBMP(0 To maxLevelType) As StdPicture\nextern RangeArr<StdPicture, 0, maxLevelType> GFXLevelBMP;\n//Public GFXLevelMaskBMP(0 To maxLevelType) As StdPicture\n//extern RangeArr<StdPicture, 0, maxLevelType> GFXLevelMaskBMP;\n//Public GFXLevelHeight(0 To maxLevelType) As Integer\n// extern RangeArrI<vbint_t, 0, maxLevelType, 0> GFXLevelHeight;\n//Public GFXLevelWidth(0 To maxLevelType) As Integer\n// extern RangeArrI<vbint_t, 0, maxLevelType, 0> GFXLevelWidth;\n//Public GFXLevelBig(0 To maxLevelType) As Boolean\nextern RangeArrI<bool, 0, maxLevelType, false> GFXLevelBig;\n//Public GFXSceneCustom(1 To maxSceneType) As Long\n// extern RangeArrI<bool, 1, maxSceneType, false> GFXSceneCustom;\n//Public GFXScene(1 To maxSceneType) As Long\n//extern RangeArrI<long, 1, maxSceneType, 0> GFXScene;\n#define GFXScene GFXSceneBMP\n//Public GFXSceneMask(1 To maxSceneType) As Long\n//extern RangeArrI<long, 1, maxSceneType, 0> GFXSceneMask;\n//Public GFXSceneBMP(1 To maxSceneType) As StdPicture\nextern RangeArr<StdPicture, 1, maxSceneType> GFXSceneBMP;\n//Public GFXSceneMaskBMP(1 To maxSceneType) As StdPicture\n//extern RangeArr<StdPicture, 1, maxSceneType> GFXSceneMaskBMP;\n//Public GFXSceneHeight(1 To maxSceneType) As Integer\n// extern RangeArrI<vbint_t, 1, maxSceneType, 0> GFXSceneHeight;\n//Public GFXSceneWidth(1 To maxSceneType) As Integer\n// extern RangeArrI<vbint_t, 1, maxSceneType, 0> GFXSceneWidth;\n//Public GFXPathCustom(1 To maxPathType) As Long\n// extern RangeArrI<bool, 1, maxPathType, false> GFXPathCustom;\n//Public GFXPath(1 To maxPathType) As Long\n//extern RangeArrI<long, 1, maxPathType, 0> GFXPath;\n#define GFXPath GFXPathBMP\n//Public GFXPathMask(1 To maxPathType) As Long\n//extern RangeArrI<long, 1, maxPathType, 0> GFXPathMask;\n//Public GFXPathBMP(1 To maxPathType) As StdPicture\nextern RangeArr<StdPicture, 1, maxPathType> GFXPathBMP;\n//Public GFXPathMaskBMP(1 To maxPathType) As StdPicture\n//extern RangeArr<StdPicture, 1, maxPathType> GFXPathMaskBMP;\n//Public GFXPathHeight(1 To maxPathType) As Integer\n// extern RangeArrI<vbint_t, 1, maxPathType, 0> GFXPathHeight;\n//Public GFXPathWidth(1 To maxPathType) As Integer\n// extern RangeArrI<vbint_t, 1, maxPathType, 0> GFXPathWidth;\n\n//Public GFXPlayerCustom(1 To numCharacters) As Long\n// extern RangeArrI<bool, 1, numCharacters, false> GFXPlayerCustom;\n//Public GFXPlayer(1 To numCharacters) As Long\n//extern RangeArrI<long, 1, numCharacters, 0> GFXPlayer;\n#define GFXPlayer GFXPlayerBMP\n//Public GFXPlayerMask(1 To numCharacters) As Long\n//extern RangeArrI<long, 1, numCharacters, 0> GFXPlayerMask;\n//Public GFXPlayerBMP(1 To numCharacters) As StdPicture\nextern RangeArr<StdPicture, 1, numCharacters> GFXPlayerBMP;\n//Public GFXPlayerMaskBMP(1 To numCharacters) As StdPicture\n//extern RangeArr<StdPicture, 1, numCharacters> GFXPlayerMaskBMP;\n//Public GFXPlayerHeight(1 To numCharacters) As Integer\n// extern RangeArrI<vbint_t, 1, numCharacters, 0> GFXPlayerHeight;\n//Public GFXPlayerWidth(1 To numCharacters) As Integer\n// extern RangeArrI<vbint_t, 1, numCharacters, 0> GFXPlayerWidth;\n\n//Public PlayerCharacter As Integer\n// extern int PlayerCharacter;\n//Public PlayerCharacter2 As Integer\n// extern int PlayerCharacter2;\n\n// replaced with SharedCursor.*\n\n//Public MenuMouseX As Double\n// extern double MenuMouseX;\n//Public MenuMouseY As Double\n// extern double MenuMouseY;\n//! mouse wheel delta\n// extern Sint32 MenuWheelDelta;\n//! mouse wheel event\n// extern bool MenuWheelMoved;\n//Public MenuMouseDown As Boolean\n// extern bool MenuMouseDown;\n//Public MenuMouseBack As Boolean\n// extern bool MenuMouseBack;\n//Public MenuMouseMove As Boolean\n// extern bool MenuMouseMove;\n\n// these are preserved because they keep track of the specific frame\n//   that the mouse is clicked / released\n\n//Public MenuMouseRelease As Boolean\nextern bool MenuMouseRelease;\n//Public MenuMouseClick As Boolean\nextern bool MenuMouseClick;\n\n//' event stuff\n// Moved into \"layers.h\"\n\n//Public ForcedControls As Boolean\nextern bool ForcedControls;\n//Public ForcedControl As Controls\nextern Controls_t ForcedControl;\n//Public SyncCount As Integer\n// extern int SyncCount;\n//Public noUpdate As Boolean\n// extern bool noUpdate;\n//Public gameTime As Double\n//extern double gameTime;\n\n// deprecated by g_config.audio_enable\n//Public noSound As Boolean\n//extern bool noSound;\n\n//extern bool neverPause;\n//Public tempTime As Double\n//extern double tempTime;\n//Dim ScrollDelay As Integer [in main.cpp]\n//'battlemode stuff\n//Public BattleMode As Boolean\nextern bool BattleMode;\n//Public BattleWinner As Integer\nextern int BattleWinner;\n//Public BattleLives(1 To maxPlayers) As Integer\nextern RangeArrI<int, 1, maxPlayers, 0> BattleLives;\n//Public BattleIntro As Integer\nextern int BattleIntro;\n//Public BattleOutro As Integer\nextern int BattleOutro;\n//Public LevelName As String\nextern std::string LevelName;\n//Public Const curRelease As Integer = 64\nconst int curRelease = 64;\n\n//EXTRA: Language\nextern std::string CurrentLanguage;\nextern std::string CurrentLangDialect;\n\n#ifdef __WIIU__\nextern bool g_isHBLauncher;\n#endif\n\n#endif // GLOBALS_H\n"
  },
  {
    "path": "src/graphics/gfx_background.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"../globals.h\"\n#include \"../graphics.h\"\n#include \"../collision.h\"\n#include \"../core/render.h\"\n#include \"config.h\"\n\nvoid DrawBackgroundColor(int A, int Z, bool lower = false)\n{\n    XRender::lazyPreLoad(GFXBackground2[A]);\n    if(lower)\n    {\n        XRender::renderRect(0, 0, vScreen[Z].Width, vScreen[Z].Height,\n            GFXBackground2[A].ColorLower);\n    }\n    else\n    {\n        XRender::renderRect(0, 0, vScreen[Z].Width, vScreen[Z].Height,\n            GFXBackground2[A].ColorUpper);\n    }\n}\n\n// draws backgrounds _, _, _, _, _\nvoid DrawTopAnchoredBackground(int S, int Z, int A, int offset = 32, int expected_height = 0, int tile_bottom = 0, int h_num = 2)\n{\n    DrawBackgroundColor(A, Z, true);\n    int camX = vScreen[Z].CameraAddX_i();\n    int camY = vScreen[Z].CameraAddY_i();\n\n    const auto& sect = LevelREAL[S];\n    int levelX = sect.X;\n\n    int camX_levelX = camX + levelX;\n    int Left = vScreen[Z].Left;\n    int offsetX = (camX_levelX * (4 - h_num) - Left * h_num) / 4;\n\n    int offsetY = sect.Y - offset + camY;\n    if(GameMenu && offsetY > 0)\n        offsetY = 0;\n\n    for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n    {\n        if(offsetX + GFXBackground2[A].w <= 0)\n            continue;\n\n        int drawH = GFXBackground2[A].h;\n        int offsetY_i = offsetY;\n\n        while(offsetY_i < vScreen[Z].Height)\n        {\n            XRender::renderTextureBasic(offsetX, offsetY_i,\n                GFXBackground2[A].w, drawH, GFXBackground2[A], 0, GFXBackground2[A].h - drawH);\n\n            if(g_config.disable_background2_tiling)\n                break;\n\n            if(expected_height != 0 && GFXBackground2[A].h != expected_height)\n                break;\n\n            offsetY_i += drawH;\n\n            if(tile_bottom != 0)\n            {\n                if(GFXBackground2[A].h != expected_height)\n                    break;\n                drawH = tile_bottom;\n            }\n        }\n    }\n}\n\nvoid DrawCenterAnchoredBackground(int S, int Z, int A, int expected_height = 0, int tile_bottom = 0, int tile_top = 0, bool flip_tile = false, int h_num = 2, bool anim = false)\n{\n    const auto& sect = LevelREAL[S];\n    const Screen_t& screen = Screens[vScreen[Z].screen_ref];\n    int camX = vScreen[Z].CameraAddX_i();\n    int camY = vScreen[Z].CameraAddY_i();\n\n    int Eff_ScreenH = vScreen[Z].Height;\n    int Eff_Top = 0;\n    if(screen.Type == ScreenTypes::Dynamic)\n    {\n        Eff_Top = vScreen[Z].Top;\n        Eff_ScreenH = 0;\n        for(int i = screen.active_begin(); i < screen.active_end(); i++)\n        {\n            const auto& s = screen.vScreen(i + 1);\n            if(s.Left == vScreen[Z].Left)\n                Eff_ScreenH += s.Height;\n        }\n    }\n\n    bool no_tiling = g_config.disable_background2_tiling;\n    if((tile_bottom != 0 || tile_top != 0 || expected_height != 0) && GFXBackground2[A].h != expected_height)\n    {\n        // HACK: don't cancel the tiling if we have the slightly incorrect original asset\n        if(!(A == 42 && GFXBackground2[A].h == expected_height - 1))\n            no_tiling = true;\n    }\n\n    int frameH = GFXBackground2[A].h;\n    // HACK: align non-rounded pictures (there was Redigit's original with the 3455 pixels height,\n    // but it must be 3456. There are lot of custom resources that using the 3455 height by mistake)\n    // in the original image, the fourth frame is missing its top line.\n    if(A == 42 && GFXBackground2[A].h == expected_height - 1 && anim)\n    {\n        frameH = expected_height / 4;\n    }\n    else if(anim)\n    {\n        frameH = GFXBackground2[A].h / 4;\n    }\n\n    int CanvasH = frameH;\n    int CanvasOffset = 0;\n\n    // ensure that the canvas covers above and below the screen\n    if(Eff_ScreenH > CanvasH)\n    {\n        CanvasOffset = Eff_ScreenH - CanvasH;\n        CanvasH += CanvasOffset * 2;\n    }\n\n    TinyLocation_t tempLocation;\n\n    int camX_levelX = camX + sect.X;\n    int Left = vScreen[Z].Left;\n\n    int offsetX = (camX_levelX * (4 - h_num) - Left * h_num) / 4;\n\n    for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n    {\n        if(offsetX + GFXBackground2[A].w <= 0)\n            continue;\n\n        tempLocation.Width = GFXBackground2[A].w;\n        tempLocation.Height = frameH;\n\n        tempLocation.X = offsetX;\n        if(sect.Height - sect.Y > CanvasH)\n        {\n            // .Y = (-vScreenY(Z) - level(S).Y) / (level(S).Height - level(S).Y - (600 - vScreen(Z).Top)) * (GFXBackground2Height(A) / 4 - (600 - vScreen(Z).Top))\n            // .Y = -vScreenY(Z) - .Y\n            tempLocation.Y = (-camY - Eff_Top - sect.Y) * (CanvasH - Eff_ScreenH) / (sect.Height - sect.Y - Eff_ScreenH) + Eff_Top;\n            tempLocation.Y = -camY - tempLocation.Y;\n            tempLocation.Y += CanvasOffset;\n        }\n        else if(CanvasH > frameH)\n        {\n            tempLocation.Y = sect.Y + (sect.Height - sect.Y - frameH) / 2;\n        }\n        else\n            tempLocation.Y = sect.Height - frameH;\n\n        int bottom_Y = tempLocation.Y + frameH;\n        unsigned int flip = X_FLIP_NONE;\n        while(tempLocation.Y + tempLocation.Height > -camY)\n        {\n            // HACK: place the fourth frame in the correct location if we are missing a single line\n            if(A == 42 && GFXBackground2[A].h == expected_height - 1 && anim && SpecialFrame[3] == 3)\n            {\n                // duplicate the line\n                XRender::renderTextureScaleEx(tempLocation.X, camY + tempLocation.Y,\n                    GFXBackground2[A].w, 1,\n                    GFXBackground2[A],\n                    0, frameH * SpecialFrame[3],\n                    GFXBackground2[A].w, 1,\n                    0, nullptr, flip);\n                // draw the frame\n                XRender::renderTextureScaleEx(tempLocation.X, camY + tempLocation.Y + 1,\n                    GFXBackground2[A].w, tempLocation.Height - 1,\n                    GFXBackground2[A],\n                    0, frameH * SpecialFrame[3],\n                    GFXBackground2[A].w, tempLocation.Height - 1,\n                    0, nullptr, flip);\n            }\n            else if(anim)\n            {\n                XRender::renderTextureScaleEx(tempLocation.X, camY + tempLocation.Y,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    GFXBackground2[A],\n                    0, frameH * SpecialFrame[3],\n                    GFXBackground2[A].w, tempLocation.Height,\n                    0, nullptr, flip);\n            }\n            else\n            {\n                XRender::renderTextureScaleEx(tempLocation.X, camY + tempLocation.Y,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    GFXBackground2[A],\n                    0, 0,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    0, nullptr, flip);\n            }\n\n            if(no_tiling)\n                break;\n\n            if(tile_top != 0)\n                tempLocation.Height = tile_top;\n\n            tempLocation.Y -= tempLocation.Height;\n            if(flip_tile)\n                flip ^= X_FLIP_VERTICAL;\n        }\n\n        if(no_tiling)\n            continue;\n\n        tempLocation.Y = bottom_Y;\n        if(tile_bottom != 0)\n            tempLocation.Height = tile_bottom;\n        else\n            tempLocation.Height = frameH;\n\n        if(flip_tile)\n            flip = X_FLIP_VERTICAL;\n\n        while(tempLocation.Y < -camY + vScreen[Z].Height)\n        {\n            // HACK: use the smaller frame size if we are missing a single line\n            if(A == 42 && GFXBackground2[A].h == expected_height - 1 && anim && SpecialFrame[3] == 3)\n            {\n                XRender::renderTextureScaleEx(tempLocation.X, camY + tempLocation.Y,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    GFXBackground2[A],\n                    0, frameH * SpecialFrame[3] + (frameH - 1) - tempLocation.Height,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    0, nullptr, flip);\n            }\n            else if(anim)\n            {\n                XRender::renderTextureScaleEx(tempLocation.X, camY + tempLocation.Y,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    GFXBackground2[A],\n                    0, frameH * SpecialFrame[3] + frameH - tempLocation.Height,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    0, nullptr, flip);\n            }\n            else\n            {\n                XRender::renderTextureScaleEx(tempLocation.X, camY + tempLocation.Y,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    GFXBackground2[A],\n                    0, frameH - tempLocation.Height,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    0, nullptr, flip);\n            }\n\n            tempLocation.Y += tempLocation.Height;\n            if(flip_tile)\n                flip ^= X_FLIP_VERTICAL;\n        }\n    }\n\n    // use the remaining information from the tempLocation (Y and Height, never tiled)\n    if(no_tiling)\n    {\n        int undrawn_above = tempLocation.Y + camY;\n        int undrawn_below = tempLocation.Y + camY + tempLocation.Height;\n        XRender::renderRect(0, 0, vScreen[Z].Width, undrawn_above,\n            GFXBackground2[A].ColorUpper);\n        XRender::renderRect(0, undrawn_below, vScreen[Z].Width, vScreen[Z].Height - undrawn_below + 1,\n            GFXBackground2[A].ColorLower);\n    }\n}\n\nvoid DrawBottomAnchoredBackground(int S, int Z, int A, int offset = 0, int expected_height = 0, int tile_top = 0, int h_parallax_num = 2, bool no_bg = false, bool anim = false)\n{\n    const auto& sect = LevelREAL[S];\n    const Screen_t& screen = Screens[vScreen[Z].screen_ref];\n    int camX = vScreen[Z].CameraAddX_i();\n    int camY = vScreen[Z].CameraAddY_i();\n\n    if(!no_bg)\n        DrawBackgroundColor(A, Z, false);\n\n    int frameH = GFXBackground2[A].h;\n    if(anim)\n        frameH = GFXBackground2[A].h / 4;\n\n    TinyLocation_t tempLocation;\n\n    int horiz_reps = ((sect.Width - sect.X) * h_parallax_num / 4 + screen.W) / GFXBackground2[A].w + 1;\n    for(int B = 0; B <= horiz_reps; B++)\n    {\n        tempLocation.X = sect.X + ((B * GFXBackground2[A].w) - (camX + vScreen[Z].Left + sect.X) * h_parallax_num / 4);\n        tempLocation.Y = sect.Height - frameH - offset;\n\n        tempLocation.Height = frameH;\n        tempLocation.Width = GFXBackground2[A].w;\n\n        if(vScreenCollision(Z, tempLocation))\n        {\n            if(anim)\n            {\n                XRender::renderTextureBasic(camX + tempLocation.X, camY + tempLocation.Y,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    GFXBackground2[A], 0, frameH * SpecialFrame[3]);\n            }\n            else\n            {\n                XRender::renderTextureBasic(camX + tempLocation.X, camY + tempLocation.Y,\n                    GFXBackground2[A].w, tempLocation.Height,\n                    GFXBackground2[A], 0, 0);\n            }\n        }\n\n        if(g_config.disable_background2_tiling)\n            continue;\n\n        if(expected_height != 0 && GFXBackground2[A].h != expected_height)\n            continue;\n\n        if(tile_top != 0 && GFXBackground2[A].h != expected_height)\n            continue;\n\n        if(tile_top != 0)\n            tempLocation.Height = tile_top;\n\n        while(tempLocation.Y > sect.Y || tempLocation.Y > -camY)\n        {\n            tempLocation.Y -= tempLocation.Height;\n            if(vScreenCollision(Z, tempLocation))\n            {\n                if(anim)\n                {\n                    XRender::renderTextureBasic(camX + tempLocation.X, camY + tempLocation.Y,\n                        GFXBackground2[A].w, tempLocation.Height,\n                        GFXBackground2[A], 0, frameH * SpecialFrame[3]);\n                }\n                else\n                {\n                    XRender::renderTextureBasic(camX + tempLocation.X, camY + tempLocation.Y,\n                        GFXBackground2[A].w, tempLocation.Height,\n                        GFXBackground2[A], 0, 0);\n                }\n            }\n        }\n    }\n}\n\nstatic void DrawYTiledBackground(int off_x, int off_y, int vscreen_w, int vscreen_h, StdPicture& tx)\n{\n    off_x %= tx.w;\n    off_y %= tx.h;\n\n    if(off_x > 0)\n        off_x -= tx.w;\n    if(off_y > 0)\n        off_y -= tx.h;\n\n    // Fixed an SMBX 1.3 peculiarity -- this was previously -1 rather than force-to-even\n    int stride_y = tx.h & ~1;\n\n    for(; off_y < vscreen_h; off_y += stride_y)\n    {\n        for(int off_x_i = off_x; off_x_i < vscreen_w; off_x_i += tx.w)\n            XRender::renderTextureBasic(off_x_i, off_y, tx);\n    }\n}\n\nvoid DrawBackground(int S, int Z)\n{\n    const Screen_t& screen = Screens[vScreen[Z].screen_ref];\n    int camX = vScreen[Z].CameraAddX_i();\n    int camY = vScreen[Z].CameraAddY_i();\n\n    int Left = vScreen[Z].Left;\n\n    int A = 0;\n\n    const auto& sect = LevelREAL[S];\n\n    int camX_levelX = camX + sect.X;\n\n    int Eff_ScreenH = vScreen[Z].Height;\n    int Eff_Top = 0;\n\n    if(screen.Type == ScreenTypes::Dynamic)\n    {\n        Eff_Top = vScreen[Z].Top;\n        Eff_ScreenH = 0;\n\n        for(int i = screen.active_begin(); i < screen.active_end(); i++)\n        {\n            const auto& s = screen.vScreen(i + 1);\n            if(s.Left == vScreen[Z].Left)\n                Eff_ScreenH += s.Height;\n        }\n    }\n\n    if(Background2[S] == 0)\n        XRender::renderRect(0, 0, vScreen[Z].Width, vScreen[Z].Height, {0, 0, 0});\n\n    A = 2; // Clouds\n    if(Background2[S] == 1 || Background2[S] == 2 || Background2[S] == 22)\n    {\n        DrawBottomAnchoredBackground(S, Z, A, 500, 0, 0, 3);\n    }\n\n    if(Background2[S] == 13)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, 0, 0, 0, false, 3);\n    }\n\n    A = 1; // Blocks\n    if(Background2[S] == 1)\n    {\n        DrawBottomAnchoredBackground(S, Z, A, 0, -1, 0, 2, true);\n    }\n\n    A = 3; // Hills\n    if(Background2[S] == 2)\n    {\n        DrawBottomAnchoredBackground(S, Z, A, 0, -1, 0, 2, true);\n    }\n\n    A = 4; // Castle\n    if(Background2[S] == 3)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 5; // Pipes\n    if(Background2[S] == 4)\n    {\n        int Eff_Top = 0;\n        if(screen.Type == ScreenTypes::Dynamic)\n            Eff_Top = vScreen[Z].Top;\n\n        int off_y = camY + sect.Y - (camY + Eff_Top + sect.Y) / 2 - 32;\n        int off_x = sect.Width - GFXBackground2[A].w;\n\n        if(sect.Width - sect.X > GFXBackground2[A].w)\n        {\n            off_x = (-camX - sect.X - vScreen[Z].Left) * (GFXBackground2[A].w - 800) / (sect.Width - sect.X - 800) + vScreen[Z].Left;\n            off_x = -off_x;\n        }\n\n        DrawYTiledBackground(off_x, off_y, vScreen[Z].Width, vScreen[Z].Height, GFXBackground2[A]);\n    }\n\n    A = 6; // Trees\n    if(Background2[S] == 5)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 7; // Bonus\n    if(Background2[S] == 6)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 8; // SMB Underground\n    if(Background2[S] == 7)\n    {\n        DrawTopAnchoredBackground(S, Z, A);\n    }\n\n    A = 9; // Night\n    if(Background2[S] == 8 || Background2[S] == 9)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1, 0, 0, false, 3);\n    }\n\n    A = 10; // Night 2\n    if(Background2[S] == 9)\n    {\n        DrawBottomAnchoredBackground(S, Z, A, 0, -1, 0, 2, true);\n    }\n\n    A = 11; // Overworld\n    if(Background2[S] == 10)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 12; // SMW Hills\n    if(Background2[S] == 11)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 13; // SMW Trees\n    if(Background2[S] == 12)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, 800, 0, 0, true);\n    }\n\n    A = 14; // SMB3 Desert\n    if(Background2[S] == 14)\n    {\n        // could possibly tile\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 15; // SMB3 Dungeon\n    if(Background2[S] == 15)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 16; // Crateria\n    if(Background2[S] == 16)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 17; // smb3 ship\n    if(Background2[S] == 17)\n    {\n        // top segment is 254px tall\n        DrawTopAnchoredBackground(S, Z, A, 32, 858, 858-254);\n    }\n\n    A = 18; // SMW ghost house\n    if(Background2[S] == 18)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, 0, 0, 0, false, 2, true);\n    }\n\n    A = 19; // smw forest\n    if(Background2[S] == 19)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, 1200, 0, 0, true);\n    }\n\n    A = 20; // smb3 forest\n    if(Background2[S] == 20)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 21; // smb3 battle game\n    if(Background2[S] == 21)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 22; // SMB3 Waterfall\n    if(Background2[S] == 22)\n    {\n        DrawBottomAnchoredBackground(S, Z, A, 0, -1, 0, 2, true, true);\n    }\n\n    A = 23; // SMB3 Tank\n    if(Background2[S] == 23)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 24; // smb3 bowsers castle\n    if(Background2[S] == 24)\n    {\n        DrawTopAnchoredBackground(S, Z, A, 32, 1504, 384);\n    }\n\n    A = 25; // SMB2 Underground\n    if(Background2[S] == 25)\n    {\n        int Eff_Top = 0;\n        if(screen.Type == ScreenTypes::Dynamic)\n            Eff_Top = vScreen[Z].Top;\n\n        int off_y = camY + sect.Y - (camY + Eff_Top + sect.Y) / 2 - 32;\n        int off_x = sect.Width - GFXBackground2[A].w;\n\n        if(sect.Width - sect.X > GFXBackground2[A].w)\n        {\n            int32_t sect_off_x = sect.Width - sect.X - 800;\n            off_x = (sect_off_x == 0 ? 0 : (-camX - sect.X - vScreen[Z].Left) * (GFXBackground2[A].w - 800) / sect_off_x) + vScreen[Z].Left;\n            off_x = -off_x;\n        }\n\n        DrawYTiledBackground(off_x, off_y, vScreen[Z].Width, vScreen[Z].Height, GFXBackground2[A]);\n    }\n\n    A = 26; // Toad's House\n    if(Background2[S] == 26)\n    {\n        // top segment is 244px tall\n        DrawTopAnchoredBackground(S, Z, A, 32, 1396, 1396-244);\n    }\n\n    A = 27; // SMB3 Castle\n    if(Background2[S] == 27)\n    {\n        // can be tiled; top section is 1000px\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 28; // SMW Bonus\n    if(Background2[S] == 28)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 29; // SMW Night\n    if(Background2[S] == 29)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1, 0, 0, false, 2, true);\n    }\n\n    A = 30; // SMW Cave\n    if(Background2[S] == 30)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, 3456, 0, 0, true, 2, true);\n    }\n\n    A = 31; // SMW Hills 2\n    if(Background2[S] == 31)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 32; // SMW Clouds\n    if(Background2[S] == 32)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 33; // SMW Snow\n    if(Background2[S] == 33)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 34; // SMW Hills 3\n    if(Background2[S] == 34)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 36; // Snow Clouds\n    if(Background2[S] == 35 || Background2[S] == 37)\n    {\n        DrawBottomAnchoredBackground(S, Z, A, 500, 0, 0, 3);\n    }\n\n    if(Background2[S] == 36)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, 0, 0, 0, false, 3);\n    }\n\n    A = 35; // SMB 3 Snow Trees\n    if(Background2[S] == 35)\n    {\n        DrawBottomAnchoredBackground(S, Z, A, 0, -1, 0, 2, true);\n    }\n\n    A = 37; // SMB 3 Snow Hills\n    if(Background2[S] == 37)\n    {\n        DrawBottomAnchoredBackground(S, Z, A, 0, -1, 0, 2, true);\n    }\n\n    A = 38; // SMB3 Cave with Sky\n    if(Background2[S] == 38)\n    {\n        // each of the lower cave segments are 428px\n        DrawTopAnchoredBackground(S, Z, A, 20, 1500, 428*2);\n    }\n\n    A = 39; // SMB3 Cave no Sky\n    if(Background2[S] == 39)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 40; // Mystic Cave Zone\n    if(Background2[S] == 40)\n    {\n        if(g_config.disable_background2_tiling)\n        {\n            XRender::lazyPreLoad(GFXBackground2[A]);\n            XRender::renderRect(0, 0, vScreen[Z].Width, vScreen[Z].Height,\n                GFXBackground2[A].ColorUpper);\n        }\n\n        int offsetY = 0;\n\n        if(Eff_ScreenH > GFXBackground2[A].h)\n        {\n            offsetY = Eff_ScreenH - GFXBackground2[A].h + (sect.Height + camY + Eff_Top - Eff_ScreenH) / 2 - Eff_Top;\n        }\n        else if(sect.Height - sect.Y > GFXBackground2[A].h)\n        {\n            // .Y = (-vScreenY(Z) - level(S).Y) / (level(S).Height - level(S).Y - (screen.H - vScreen(Z).Top)) * (GFXBackground2Height(A) - (screen.H - vScreen(Z).Top))\n            // .Y = (-vScreenY(Z) - level(S).Y) / (level(S).Height - level(S).Y - 600) * (GFXBackground2Height(A) - 600)\n            offsetY = (camY + Eff_Top + sect.Y) * (GFXBackground2[A].h - Eff_ScreenH) / (sect.Height - sect.Y - Eff_ScreenH) - Eff_Top;\n        }\n        else\n        {\n            offsetY = sect.Height + camY - GFXBackground2[A].h;\n        }\n\n        do\n        {\n            int offsetX = (camX_levelX * 1 - Left * 1) / 2;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 953, GFXBackground2[A].w, 47, GFXBackground2[A], 0, 953);\n            }\n\n            offsetX = (camX_levelX * 4 - Left * 6) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 916, GFXBackground2[A].w, 37, GFXBackground2[A], 0, 916);\n            }\n\n            offsetX = (camX_levelX * 3 - Left * 7) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 849, GFXBackground2[A].w, 67, GFXBackground2[A], 0, 849);\n            }\n\n            offsetX = (camX_levelX * 2 - Left * 8) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 815, GFXBackground2[A].w, 34, GFXBackground2[A], 0, 815);\n            }\n\n            offsetX = (camX_levelX * 1 - Left * 9) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 709, GFXBackground2[A].w, 106, GFXBackground2[A], 0, 709);\n            }\n\n            offsetX = (camX_levelX * 15 - Left * 85) / 100;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 664, GFXBackground2[A].w, 45, GFXBackground2[A], 0, 664);\n            }\n\n            offsetX = (camX_levelX * 2 - Left * 8) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 614, GFXBackground2[A].w, 50, GFXBackground2[A], 0, 614);\n            }\n\n            offsetX = (camX_levelX * 25 - Left * 75) / 100;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 540, GFXBackground2[A].w, 74, GFXBackground2[A], 0, 540);\n            }\n\n            offsetX = (camX_levelX * 3 - Left * 7) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 408, GFXBackground2[A].w, 132, GFXBackground2[A], 0, 408);\n            }\n\n            offsetX = (camX_levelX * 25 - Left * 75) / 100;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 333, GFXBackground2[A].w, 75, GFXBackground2[A], 0, 333);\n            }\n\n            offsetX = (camX_levelX * 2 - Left * 8) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 278, GFXBackground2[A].w, 55, GFXBackground2[A], 0, 278);\n            }\n\n            offsetX = (camX_levelX * 15 - Left * 85) / 100;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 235, GFXBackground2[A].w, 43, GFXBackground2[A], 0, 235);\n            }\n\n            offsetX = (camX_levelX * 1 - Left * 9) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 123, GFXBackground2[A].w, 112, GFXBackground2[A], 0, 123);\n            }\n\n            offsetX = (camX_levelX * 2 - Left * 8) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 85, GFXBackground2[A].w, 38, GFXBackground2[A], 0, 85);\n            }\n\n            offsetX = (camX_levelX * 3 - Left * 7) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 48, GFXBackground2[A].w, 37, GFXBackground2[A], 0, 48);\n            }\n\n            offsetX = (camX_levelX * 4 - Left * 6) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 0, GFXBackground2[A].w, 48, GFXBackground2[A], 0, 0);\n            }\n\n            offsetY -= GFXBackground2[A].h;\n\n            if(g_config.disable_background2_tiling)\n                break;\n\n        } while(offsetY + GFXBackground2[A].h > 0);\n    }\n\n    A = 41; // SMB 1 Castle\n    if(Background2[S] == 41)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 42; // SMW Castle\n    if(Background2[S] == 42)\n    {\n        // safe to tile top 546px and bottom 160px of vanilla asset\n        DrawCenterAnchoredBackground(S, Z, A, 3456, 160, 546, false, 2, true);\n    }\n\n    A = 43; // SMW Castle 2\n    if(Background2[S] == 43)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 44; // SMB2 Castle\n    if(Background2[S] == 44)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 45; // Brinstar\n    if(Background2[S] == 45)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 46; // Transport\n    if(Background2[S] == 46)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 47; // Transport\n    if(Background2[S] == 47)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 48; // SMB2 Blouds\n    if(Background2[S] == 48)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 49; // Desert Night\n    if(Background2[S] == 49)\n    {\n        int CanvasH = GFXBackground2[A].h;\n        int CanvasOffset = 0;\n\n        // ensure that the canvas covers above and below the screen\n        if(Eff_ScreenH > CanvasH)\n        {\n            CanvasOffset = Eff_ScreenH - CanvasH;\n            CanvasH += CanvasOffset * 2;\n        }\n\n        int offsetY = 0;\n\n        if(sect.Height - sect.Y > CanvasH)\n        {\n            // .Y = (-vScreenY(Z) - level(S).Y) / (level(S).Height - level(S).Y - 600) * (GFXBackground2Height(A) - 600)\n            offsetY = (-camY - Eff_Top - sect.Y) * (CanvasH - Eff_ScreenH) / (sect.Height - sect.Y - Eff_ScreenH) + Eff_Top;\n            offsetY = -offsetY;\n            offsetY += CanvasOffset;\n        }\n        else if(CanvasH > GFXBackground2[A].h)\n        {\n            offsetY = camY + sect.Y + (sect.Height - sect.Y - GFXBackground2[A].h) / 2;\n        }\n        else\n            offsetY = camY + sect.Height - GFXBackground2[A].h;\n\n        // use a simple color fill for the sky above and sand below the texture\n        XRender::lazyPreLoad(GFXBackground2[A]);\n        int undrawn_above = offsetY;\n        int undrawn_below = offsetY + GFXBackground2[A].h;\n        XRender::renderRect(0, 0, vScreen[Z].Width, std::ceil(undrawn_above),\n            GFXBackground2[A].ColorUpper);\n        XRender::renderRect(0, undrawn_below, vScreen[Z].Width, vScreen[Z].Height - undrawn_below + 1,\n            GFXBackground2[A].ColorLower);\n\n        int offsetX = (camX_levelX * 1 - Left * 1) / 2;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 280, GFXBackground2[A].w, 450, GFXBackground2[A], 0, 280);\n        }\n\n        offsetX = (camX_levelX * 1 - Left * 9) / 10;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 268, GFXBackground2[A].w, 12, GFXBackground2[A], 0, 268);\n        }\n\n        offsetX = (camX_levelX * 11 - Left * 89) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 244, GFXBackground2[A].w, 24, GFXBackground2[A], 0, 244);\n        }\n\n        offsetX = (camX_levelX * 12 - Left * 88) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 228, GFXBackground2[A].w, 16, GFXBackground2[A], 0, 228);\n        }\n\n        offsetX = (camX_levelX * 13 - Left * 87) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 196, GFXBackground2[A].w, 32, GFXBackground2[A], 0, 196);\n        }\n\n        offsetX = (camX_levelX * 14 - Left * 86) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 164, GFXBackground2[A].w, 32, GFXBackground2[A], 0, 164);\n        }\n\n        offsetX = (camX_levelX * 15 - Left * 85) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 116, GFXBackground2[A].w, 48, GFXBackground2[A], 0, 116);\n        }\n\n        offsetX = (camX_levelX * 16 - Left * 84) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 58, GFXBackground2[A].w, 58, GFXBackground2[A], 0, 58);\n        }\n\n        offsetX = (camX_levelX * 17 - Left * 83) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 0, GFXBackground2[A].w, 58, GFXBackground2[A], 0, 0);\n        }\n    }\n\n    A = 50; // Shrooms\n    if(Background2[S] == 50)\n    {\n        XRender::lazyPreLoad(GFXBackground2[A]);\n        XRender::renderRect(0, 0, vScreen[Z].Width, vScreen[Z].Height,\n            GFXBackground2[A].ColorLower);\n\n        int CanvasH = GFXBackground2[A].h;\n        int CanvasOffset = 0;\n\n        // ensure that the canvas covers above and below the screen\n        if(Eff_ScreenH > CanvasH)\n        {\n            CanvasOffset = (Eff_ScreenH - CanvasH) * 3 / 2;\n            CanvasH += CanvasOffset;\n        }\n\n        int offsetY = 0;\n\n        if(sect.Height - sect.Y > CanvasH)\n        {\n            // .Y = (-vScreenY(Z) - level(S).Y) / (level(S).Height - level(S).Y - 600) * (GFXBackground2Height(A) - 600)\n            offsetY = (-camY - Eff_Top - sect.Y) * (CanvasH - Eff_ScreenH) / (sect.Height - sect.Y - Eff_ScreenH) + Eff_Top;\n            offsetY = -offsetY;\n            offsetY += CanvasOffset;\n        }\n        else\n            offsetY = camY + sect.Height - GFXBackground2[A].h;\n\n        int offsetX = (camX_levelX - Left) / 2;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 378, GFXBackground2[A].w, 378, GFXBackground2[A], 0, 378);\n        }\n\n        while(offsetY > -378)\n        {\n            offsetX = (camX_levelX * 35 - Left * 65) / 100;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY, GFXBackground2[A].w, 220, GFXBackground2[A], 0, 0);\n            }\n\n            offsetX = (camX_levelX * 4 - Left * 6) / 10;\n            for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n            {\n                if(offsetX + GFXBackground2[A].w <= 0)\n                    continue;\n\n                XRender::renderTextureBasic(offsetX, offsetY + 220, GFXBackground2[A].w, 158, GFXBackground2[A], 0, 220);\n            }\n\n            offsetY -= 378;\n\n            if(g_config.disable_background2_tiling)\n                break;\n        }\n    }\n\n    A = 51; // SMB1 Desert\n    if(Background2[S] == 51)\n    {\n        int CanvasH = GFXBackground2[A].h;\n        int CanvasOffset = 0;\n\n        // ensure that the canvas covers above and below the screen\n        if(Eff_ScreenH > CanvasH)\n        {\n            CanvasOffset = 0;\n            CanvasH += (Eff_ScreenH - CanvasH);\n        }\n\n        int offsetY = 0;\n\n        if(sect.Height - sect.Y > CanvasH)\n        {\n            // .Y = (-vScreenY(Z) - level(S).Y) / (level(S).Height - level(S).Y - 600) * (GFXBackground2Height(A) - 600)\n            offsetY = (-camY - Eff_Top - sect.Y) * (CanvasH - Eff_ScreenH) / (sect.Height - sect.Y - Eff_ScreenH) + Eff_Top;\n            offsetY = -offsetY;\n            offsetY += CanvasOffset;\n        }\n        else if(CanvasH > GFXBackground2[A].h)\n        {\n            offsetY = camY + sect.Y + (sect.Height - sect.Y - GFXBackground2[A].h) / 2;\n        }\n        else\n            offsetY = camY + sect.Height - GFXBackground2[A].h;\n\n        // use a simple color fill for the sky above and sand below the texture\n        XRender::lazyPreLoad(GFXBackground2[A]);\n        int undrawn_above = offsetY;\n        int undrawn_below = offsetY + GFXBackground2[A].h;\n        XRender::renderRect(0, 0, vScreen[Z].Width, undrawn_above,\n            GFXBackground2[A].ColorUpper);\n        XRender::renderRect(0, undrawn_below, vScreen[Z].Width, vScreen[Z].Height - undrawn_below + 1,\n            GFXBackground2[A].ColorLower);\n\n        int offsetX = (camX_levelX - Left * 3) / 4;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY, GFXBackground2[A].w, 350, GFXBackground2[A], 0, 0);\n        }\n\n        offsetX = (camX_levelX - Left) / 2;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 350, GFXBackground2[A].w, GFXBackground2[A].h - 350, GFXBackground2[A], 0, 350);\n        }\n    }\n\n    A = 52; // SMB2 Desert Night\n    if(Background2[S] == 52)\n    {\n        int CanvasH = GFXBackground2[A].h;\n        int CanvasOffset = 0;\n\n        // ensure that the canvas covers above and below the screen\n        if(Eff_ScreenH > CanvasH)\n        {\n            CanvasOffset = (Eff_ScreenH - CanvasH) * 3 / 2;\n            CanvasH += CanvasOffset;\n        }\n\n        int offsetY = 0;\n\n        if(sect.Height - sect.Y > CanvasH)\n        {\n            // .Y = (-vScreenY(Z) - level(S).Y) / (level(S).Height - level(S).Y - 600) * (GFXBackground2Height(A) - 600)\n            offsetY = (-camY - Eff_Top - sect.Y) * (CanvasH - Eff_ScreenH) / (sect.Height - sect.Y - Eff_ScreenH) + Eff_Top;\n            offsetY = -offsetY;\n            offsetY += CanvasOffset;\n        }\n        else if(CanvasH > GFXBackground2[A].h)\n        {\n            offsetY = camY + sect.Y + (sect.Height - sect.Y - GFXBackground2[A].h) / 2;\n        }\n        else\n            offsetY = camY + sect.Height - GFXBackground2[A].h;\n\n\n        // use a simple color fill for the sky above and sand below the texture\n        XRender::lazyPreLoad(GFXBackground2[A]);\n        int undrawn_above = offsetY;\n        int undrawn_below = offsetY + GFXBackground2[A].h;\n        XRender::renderRect(0, 0, vScreen[Z].Width, undrawn_above,\n            GFXBackground2[A].ColorUpper);\n        XRender::renderRect(0, undrawn_below, vScreen[Z].Width, vScreen[Z].Height - undrawn_below + 1,\n            GFXBackground2[A].ColorLower);\n\n        int offsetX = (camX_levelX * 1 - Left * 1) / 2;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 280, GFXBackground2[A].w, GFXBackground2[A].h - 280, GFXBackground2[A], 0, 280);\n        }\n\n        offsetX = (camX_levelX * 1 - Left * 9) / 10;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 268, GFXBackground2[A].w, 12, GFXBackground2[A], 0, 268);\n        }\n\n        offsetX = (camX_levelX * 11 - Left * 89) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 244, GFXBackground2[A].w, 24, GFXBackground2[A], 0, 244);\n        }\n\n        offsetX = (camX_levelX * 12 - Left * 88) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 228, GFXBackground2[A].w, 16, GFXBackground2[A], 0, 228);\n        }\n\n        offsetX = (camX_levelX * 13 - Left * 87) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 196, GFXBackground2[A].w, 32, GFXBackground2[A], 0, 196);\n        }\n\n        offsetX = (camX_levelX * 14 - Left * 86) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 164, GFXBackground2[A].w, 32, GFXBackground2[A], 0, 164);\n        }\n\n        offsetX = (camX_levelX * 15 - Left * 85) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 116, GFXBackground2[A].w, 48, GFXBackground2[A], 0, 116);\n        }\n\n        offsetX = (camX_levelX * 16 - Left * 84) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 58, GFXBackground2[A].w, 58, GFXBackground2[A], 0, 58);\n        }\n\n        offsetX = (camX_levelX * 17 - Left * 83) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 0, GFXBackground2[A].w, 58, GFXBackground2[A], 0, 0);\n        }\n    }\n\n    A = 53; // Cliffs\n    if(Background2[S] == 53)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, -1);\n    }\n\n    A = 54; // Warehouse\n    if(Background2[S] == 54)\n    {\n        DrawCenterAnchoredBackground(S, Z, A);\n    }\n\n    A = 55; // SMW Water\n    if(Background2[S] == 55)\n    {\n        DrawCenterAnchoredBackground(S, Z, A, 0, 0, 0, false, 2, true);\n    }\n\n    A = 56; // SMB3 Water\n    if(Background2[S] == 56)\n    {\n        int CanvasH = GFXBackground2[A].h;\n        int CanvasOffset = 0;\n\n        // ensure that the canvas covers above and below the screen\n        if(Eff_ScreenH > CanvasH)\n        {\n            CanvasOffset = (Eff_ScreenH - CanvasH) * 3 / 2;\n            CanvasH += CanvasOffset;\n        }\n\n        int offsetY = 0;\n\n        if(sect.Height - sect.Y > CanvasH)\n        {\n            // .Y = (-vScreenY(Z) - level(S).Y) / (level(S).Height - level(S).Y - 600) * (GFXBackground2Height(A) - 600)\n            offsetY = (-camY - Eff_Top - sect.Y) * (CanvasH - Eff_ScreenH) / (sect.Height - sect.Y - Eff_ScreenH) + Eff_Top;\n            offsetY = -offsetY;\n            offsetY += CanvasOffset;\n        }\n        else if(CanvasH > GFXBackground2[A].h)\n        {\n            offsetY = camY + sect.Y + (sect.Height - sect.Y - GFXBackground2[A].h) / 2;\n        }\n        else\n            offsetY = camY + sect.Height - GFXBackground2[A].h;\n\n        // use a simple color fill for the water above and below the texture\n        XRender::lazyPreLoad(GFXBackground2[A]);\n        int undrawn_above = offsetY;\n        int undrawn_below = offsetY + GFXBackground2[A].h;\n        XRender::renderRect(0, 0, vScreen[Z].Width, undrawn_above,\n            GFXBackground2[A].ColorUpper);\n        XRender::renderRect(0, undrawn_below, vScreen[Z].Width, vScreen[Z].Height - undrawn_below + 1,\n            GFXBackground2[A].ColorLower);\n\n        int offsetX = (camX_levelX * 35 - Left * 65) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY, GFXBackground2[A].w, 100, GFXBackground2[A], 0, 0);\n        }\n\n        offsetX = (camX_levelX * 4 - Left * 6) / 10;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 100, GFXBackground2[A].w, 245, GFXBackground2[A], 0, 100);\n        }\n\n        offsetX = (camX_levelX * 45 - Left * 55) / 100;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 345, GFXBackground2[A].w, 110, GFXBackground2[A], 0, 345);\n        }\n\n        offsetX = (camX_levelX * 5 - Left * 5) / 10;\n        for(; offsetX < vScreen[Z].Width; offsetX += GFXBackground2[A].w)\n        {\n            if(offsetX + GFXBackground2[A].w <= 0)\n                continue;\n\n            XRender::renderTextureBasic(offsetX, offsetY + 455, GFXBackground2[A].w, GFXBackground2[A].h - 455, GFXBackground2[A], 0, 455);\n        }\n    }\n\n    A = 57; // Warehouse\n    if(Background2[S] == 57)\n    {\n        // looks more like a temple to me.\n        // in vanilla asset:\n        // top: can loop top 672px\n        // bottom: can loop bottom 64px\n        DrawCenterAnchoredBackground(S, Z, A, 800, 64, 672);\n    }\n\n    A = 58; // SMW Night\n    if(Background2[S] == 58)\n    {\n        // can loop top 600px and bottom 128px of vanilla asset\n        DrawCenterAnchoredBackground(S, Z, A, 3584, 128, 600, false, 2, true);\n    }\n}\n"
  },
  {
    "path": "src/graphics/gfx_camera.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"core/render.h\"\n\n#include \"globals.h\"\n#include \"game_main.h\"\n#include \"graphics.h\"\n#include \"gfx.h\"\n#include \"screen.h\"\n#include \"config.h\"\n#include \"sound.h\"\n\nvoid ResetCameraPanning()\n{\n    for(int i = 0; i < 2; ++i)\n        vScreen[i].small_screen_features = vScreen_t::SmallScreenFeatures_t();\n}\n\nstatic inline num_t s_small_screen_rate(const Screen_t& screen, const Screen_t& c_screen)\n{\n    // slowly disable small screen camera features as player cameras approach each other\n    num_t rate = 1;\n    if(screen.Type == ScreenTypes::Dynamic && (screen.W < c_screen.W || screen.H < c_screen.H))\n    {\n        num_t dx = num_t::abs(screen.vScreen(1).X - screen.vScreen(2).X);\n        num_t dy = num_t::abs(screen.vScreen(1).Y - screen.vScreen(2).Y);\n        num_t d = dx + dy;\n        const int screen_join = (screen.W + screen.H) / 2;\n\n        rate = (d - screen_join) / screen_join;\n        if(rate < 0)\n            rate = 0;\n        if(rate > 1)\n            rate = 1;\n    }\n\n    return rate;\n}\n\nvoid ProcessSmallScreenCam(vScreen_t& vscreen)\n{\n    const Screen_t& screen = Screens[vscreen.screen_ref];\n    const Screen_t& c_screen = screen.canonical_screen();\n    const Player_t& p = Player[vscreen.player];\n\n    // slowly disable small screen camera features as player cameras approach each other\n    num_t rate = s_small_screen_rate(screen, c_screen);\n\n    if(g_config.small_screen_cam && screen.W < c_screen.W && !NoTurnBack[p.Section])\n    {\n        int16_t max_offsetX = 360;\n        if(max_offsetX > vscreen.Width - (int)p.Location.Width * 4)\n            max_offsetX = vscreen.Width - (int)p.Location.Width * 4;\n\n        if(max_offsetX > c_screen.W - screen.W)\n            max_offsetX = c_screen.W - screen.W;\n\n        int16_t lookX_target = (int)(max_offsetX * p.Location.SpeedX) / 4;\n        if(lookX_target > max_offsetX)\n            lookX_target = max_offsetX;\n        if(lookX_target < -max_offsetX)\n            lookX_target = -max_offsetX;\n        lookX_target &= ~1;\n\n        if(LevelMacro != LEVELMACRO_OFF || ForcedControls)\n            lookX_target = 0;\n\n        int16_t rateX = 1;\n        // switching directions\n        if((vscreen.small_screen_features.offset_x < 0 && lookX_target > 0)\n            || (vscreen.small_screen_features.offset_x > 0 && lookX_target < 0))\n        {\n            rateX = 3;\n        }\n        // accelerating\n        else if((vscreen.small_screen_features.offset_x > 0) == (lookX_target > vscreen.small_screen_features.offset_x))\n        {\n            rateX = 2;\n        }\n\n        if(GamePaused == PauseCode::None && !qScreen && p.Effect == PLREFF_NORMAL)\n        {\n            if(vscreen.small_screen_features.offset_x < lookX_target)\n                vscreen.small_screen_features.offset_x += rateX;\n            else if(vscreen.small_screen_features.offset_x > lookX_target)\n                vscreen.small_screen_features.offset_x -= rateX;\n        }\n\n        vscreen.X -= (int)(rate * vscreen.small_screen_features.offset_x / 2);\n    }\n\n    if(g_config.small_screen_cam && screen.H < c_screen.H)\n    {\n        int16_t max_offsetY = 200;\n        if(max_offsetY > (c_screen.H - screen.H) + 50)\n            max_offsetY = (c_screen.H - screen.H) + 50;\n\n        int16_t lookY_target = max_offsetY;\n\n        bool on_ground = p.Pinched.Bottom1 || p.Slope || p.StandingOnNPC || p.Wet || p.Quicksand;\n        // bool duck_jump = !on_ground && p.Duck;\n        bool prevent_unlock = vscreen.small_screen_features.offset_y_hold != 0 && (p.Vine || !on_ground || p.GrabTime);\n\n        if(p.Controls.Up == p.Controls.Down || prevent_unlock)\n            lookY_target = vscreen.small_screen_features.offset_y_hold;\n        else if(p.Controls.Down)\n            lookY_target *= -1;\n\n        int16_t rateY = 4;\n        if((vscreen.small_screen_features.offset_y < 0 && lookY_target > 0)\n            || (vscreen.small_screen_features.offset_y > 0 && lookY_target < 0))\n        {\n            if(vscreen.small_screen_features.offset_y < 50 && vscreen.small_screen_features.offset_y > -50)\n                vscreen.small_screen_features.offset_y *= -1;\n        }\n\n        if(GamePaused == PauseCode::None && !qScreen && !ForcedControls && LevelMacro == LEVELMACRO_OFF && p.Effect == PLREFF_NORMAL)\n        {\n            if(vscreen.small_screen_features.offset_y < lookY_target)\n            {\n                vscreen.small_screen_features.offset_y += rateY;\n\n                if(vscreen.small_screen_features.offset_y > lookY_target)\n                    vscreen.small_screen_features.offset_y = lookY_target;\n            }\n            else if(vscreen.small_screen_features.offset_y > lookY_target)\n            {\n                vscreen.small_screen_features.offset_y -= rateY;\n\n                if(vscreen.small_screen_features.offset_y < lookY_target)\n                    vscreen.small_screen_features.offset_y = lookY_target;\n            }\n\n            if(vscreen.small_screen_features.offset_y_hold == 0 && vscreen.small_screen_features.offset_y < -max_offsetY + 40 && (vscreen.small_screen_features.last_buttons_held & 1) == 0 && p.Controls.Down)\n            {\n                vscreen.small_screen_features.offset_y_hold = -max_offsetY;\n                PlaySoundSpatial(SFX_Camera, p.Location);\n            }\n            else if(vscreen.small_screen_features.offset_y_hold == 0 && vscreen.small_screen_features.offset_y > max_offsetY - 40 && (vscreen.small_screen_features.last_buttons_held & 2) == 0 && p.Controls.Up)\n            {\n                vscreen.small_screen_features.offset_y_hold = max_offsetY;\n                PlaySoundSpatial(SFX_Camera, p.Location);\n            }\n            else if(vscreen.small_screen_features.offset_y_hold != 0 && vscreen.small_screen_features.offset_y > -60 && vscreen.small_screen_features.offset_y < 60)\n            {\n                vscreen.small_screen_features.offset_y_hold = 0;\n                PlaySoundSpatial(SFX_Camera, p.Location);\n            }\n\n            vscreen.small_screen_features.last_buttons_held = (int8_t)p.Controls.Down | (int8_t)p.Controls.Up << 1;\n        }\n\n        int16_t lookY = vscreen.small_screen_features.offset_y;\n\n        if(lookY > -50 && lookY < 50)\n            lookY = 0;\n        else\n        {\n            if(lookY > 0)\n                lookY -= 50;\n            if(lookY < 0)\n                lookY += 50;\n            lookY /= 2;\n        }\n\n        // lower the default offset by 32px\n        int fix_default_offset = 32;\n\n        // weaken this effect when very close to the original canonical resolution\n        if(fix_default_offset > (c_screen.H - screen.H) / 2)\n            fix_default_offset = (c_screen.H - screen.H) / 2;\n\n        // fade this out if approaching the canonical screen ounbds\n        fix_default_offset *= (c_screen.H - screen.H) - std::abs(lookY) * 2;\n        fix_default_offset /= (c_screen.H - screen.H);\n\n        vscreen.Y += (int)(rate * (lookY + fix_default_offset));\n    }\n}\n\nvoid DrawSmallScreenCam(vScreen_t& vscreen)\n{\n    const Screen_t& screen = Screens[vscreen.screen_ref];\n    const Screen_t& c_screen = screen.canonical_screen();\n\n    int CamX = vscreen.Width - 54;\n    int CamY = vscreen.Height - 42;\n\n    // don't overlap the controls display\n    if(screen.Type == ScreenTypes::Dynamic && screen.vScreen(2).Visible && vscreen.Left > 0)\n        CamY -= 24;\n\n    // stay in the safe zone\n    if(vscreen.ScreenLeft + vscreen.Width >= XRender::TargetW)\n        CamX -= XRender::TargetOverscanX;\n\n    // scale the opacity by the current effectiveness of the camera features\n    num_t rate = s_small_screen_rate(screen, c_screen);\n\n    int16_t max_offsetY = 200;\n    if(max_offsetY > (c_screen.H - screen.H) + 50)\n        max_offsetY = (c_screen.H - screen.H) + 50;\n\n    // color for camera\n    XTColor color;\n\n    if(vscreen.small_screen_features.offset_y_hold != 0)\n    {\n        color = XTColorF(1.0_n, 0.2_n, 0.2_n, 1.0_n);\n\n        if(vscreen.small_screen_features.offset_y_hold > 0)\n            XRender::renderTextureBasic(CamX + 4, CamY - 18, GFX.MCursor[1], XTAlphaF(rate));\n        else\n            XRender::renderTextureBasic(CamX + 4, CamY + 18, GFX.MCursor[2], XTAlphaF(rate));\n    }\n    else if(vscreen.small_screen_features.offset_y < -max_offsetY * 4 / 5 || vscreen.small_screen_features.offset_y > max_offsetY * 4 / 5)\n    {\n        color = XTColorF(0.5_n, 1.0_n, 0.5_n, 0.7_n);\n\n        if(vscreen.small_screen_features.offset_y > 0)\n            XRender::renderTextureBasic(CamX + 4, CamY - 18, GFX.MCursor[1], XTAlphaF(0.7_r * rate));\n        else\n            XRender::renderTextureBasic(CamX + 4, CamY + 18, GFX.MCursor[2], XTAlphaF(0.7_r * rate));\n    }\n    else if(vscreen.small_screen_features.offset_y <= -48 || vscreen.small_screen_features.offset_y >= 48)\n    {\n        color = XTAlphaF(0.5_n);\n    }\n    else\n    {\n        color = XTColorF(0.0_n, 0.0_n, 0.0_n, 0.3_n);\n    }\n\n    color.a = int(rate * color.a);\n\n    if(GFX.Camera.inited)\n        XRender::renderTextureBasic(CamX, CamY, GFX.Camera, color);\n    else\n        XRender::renderRect(CamX, CamY, 24, 16, color);\n}\n"
  },
  {
    "path": "src/graphics/gfx_camera.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GFX_CAMERA_H\n#define GFX_CAMERA_H\n\n#include <cstdint>\n\nstruct vScreen_t;\n\nvoid ResetCameraPanning();\nvoid ProcessSmallScreenCam(vScreen_t& vscreen);\nvoid DrawSmallScreenCam(vScreen_t& vscreen);\n\n#endif\n"
  },
  {
    "path": "src/graphics/gfx_credits.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../globals.h\"\n#include \"../graphics.h\"\n#include \"main/game_info.h\"\n#include \"core/render.h\"\n\n\nstatic XTColor s_alphaFromY(int y)\n{\n    const int h = 32; // An approximate height of one text line\n    const int tb = 16;\n    int b = static_cast<int>(y + h);\n    int t = static_cast<int>(y);\n\n    if(t >= XRender::TargetH) // The line at bottom\n    {\n        return XTAlpha(0);\n    }\n\n    if(b > XRender::TargetH) // The line enters the screen\n    {\n        return XTAlpha(255 -  255 * (b - XRender::TargetH) / h);\n    }\n\n    if(b <= tb) // The line at the top\n    {\n        return XTAlpha(0);\n    }\n\n    if(t < tb) // The line quits the screen\n    {\n        return XTAlpha(255 * (b - tb) / h);\n    }\n\n    return XTAlpha(255);\n}\n\nvoid DrawCredits()\n{\n    // CreditChop and CreditOffsetY were previously floats, now they are ints measured in tenths\n    XRender::renderRect(0, 0, XRender::TargetW, (CreditChop + 5) / 10, {0, 0, 0});\n    XRender::renderRect(0, XRender::TargetH - (CreditChop + 5) / 10, XRender::TargetW, (CreditChop + 5) / 10, {0, 0, 0});\n\n    // previously hardcoded to 100\n    int shrink = vScreen[1].Top;\n\n    if(CreditChop > shrink * 10)\n    {\n        int chop = (CreditChop + 5) / 10 - shrink;\n        int chop_max = (XRender::TargetH / 2) - shrink;\n        uint8_t alpha = 255 * chop / chop_max;\n        XRender::renderRect(0, 0, XRender::TargetW, XRender::TargetH, {0, 0, 0, alpha});\n    }\n\n    int A;\n\n    // Find the highest\n    for(A = 1; A <= numCredits; A++)\n    {\n        auto &c = Credit[A];\n        auto &l = c.Location;\n        auto bottom = l.Y + l.Height + CreditOffsetY / 10;\n        if(bottom >= 0)\n            break; // found!\n    }\n\n    // Draw that actually visible\n    for(; A <= numCredits; A++)\n    {\n        auto &c = Credit[A];\n        auto &l = c.Location;\n        auto y = l.Y + CreditOffsetY / 10;\n\n        if(y > XRender::TargetH)\n            break; // Nothing also to draw\n\n        // Printing lines of credits\n        SuperPrint(GetS(c.Text),\n                   g_gameInfo.creditsFont,\n                   l.X,\n                   y,\n                   s_alphaFromY(y));\n    }\n}\n"
  },
  {
    "path": "src/graphics/gfx_draw_player.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <array>\n\n#include \"globals.h\"\n#include \"graphics.h\"\n#include \"collision.h\"\n#include \"core/render.h\"\n#include \"gfx.h\"\n#include \"frame_timer.h\"\n\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n\n#include \"graphics/gfx_keyhole.h\"\n\nstatic inline int s_round2int(num_t d)\n{\n    return num_t::floor(d + 0.5_n);\n}\n\n//! Get left pixel at the player sprite\nint pfrX(const StdPicture& tx, const Player_t& p)\n{\n    // FIXME: Replace this heuristic logic with a proper texture flags mechanism\n\n    // will use internal flags (tx.flags & PLAYER_MODERN and tx.flags & PLAYER_CUSTOM) in future\n    // if tx.flags & PLAYER_CUSTOM, then will use heap-allocated polymorphic CustomData_t* tx.custom_data,\n    // which will include all frame bounding boxes and offsets\n    if(tx.h != 512)\n        return ((p.Frame * p.Direction + 49) / 10) * 100;\n    else\n    {\n        int col_w = (p.Character == 5) ? 64 : 48;\n        int n_rows = 4;\n        int n_cols = (p.Character == 5) ? 4 : 10;\n\n        int fr = p.Frame;\n        int dir = p.Direction;\n\n        if(fr < 0)\n        {\n            fr = -fr;\n            dir = -dir;\n        }\n\n        fr = (fr <= 32) ? fr : fr - 7;\n\n        bool is_center_frame = (fr == 0);\n\n        if(p.Character == 5)\n            fr -= 1;\n\n        int col = fr / n_rows;\n\n        // load left-facing frame (bottom) - right to left (in order to mirror top half)\n        if(!is_center_frame && dir < 0)\n            return col_w * (n_cols - col - 1);\n        // load right-facing frame (top) - left to right\n        else\n            return col_w * col;\n    }\n}\n\n//! Get top pixel at the player sprite\nint pfrY(const StdPicture& tx, const Player_t& p)\n{\n    // FIXME: Replace this heuristic logic with a proper texture flags mechanism\n    if(tx.h != 512)\n        return ((p.Frame * p.Direction + 49) % 10) * 100;\n    else\n    {\n        int row_h = 64;\n        int n_rows = 4;\n\n        int fr = p.Frame;\n        int dir = p.Direction;\n\n        if(fr < 0)\n        {\n            fr = -fr;\n            dir = -dir;\n        }\n\n        fr = (fr <= 32) ? fr : fr - 7;\n\n        bool is_center_frame = (fr == 0);\n\n        if(p.Character == 5)\n            fr -= 1;\n\n        int row = fr % n_rows;\n\n        // load left-facing frame (bottom)\n        if(!is_center_frame && dir < 0)\n            return row_h * (n_rows + row);\n        // load right-facing frame (top)\n        else\n            return row_h * row;\n    }\n}\n\n//! Get width at the player sprite\nint pfrW(const StdPicture& tx, const Player_t& p)\n{\n    if(tx.h != 512)\n        return 100;\n    else if(p.Character == 5)\n        return 64;\n    else\n        return 48;\n}\n\n//! Get height at the player sprite\nint pfrH(const StdPicture& tx, const Player_t& p)\n{\n    UNUSED(p);\n\n    if(tx.h != 512)\n        return 100;\n    else\n        return 64;\n}\n\n//! Get x offset that should be ADDED to the player position to draw the sprite\nint pfrOffX(const StdPicture& tx, const Player_t& p)\n{\n    UNUSED(tx);\n\n    if((p.Character < 1) || (p.Character > 5))\n        return 0;\n\n    using plr_frame_off_arr = RangeArrI<int8_t, 0, maxPlayerFrames, 0>;\n    constexpr std::array<plr_frame_off_arr*, 5> char_offsetX = {&MarioFrameX, &LuigiFrameX, &PeachFrameX, &ToadFrameX, &LinkFrameX};\n    int offX = (*char_offsetX[p.Character - 1])[(p.State * 100) + (p.Frame * p.Direction)];\n\n    return offX;\n}\n\n//! Get y offset that should be ADDED to the player position to draw the sprite\nint pfrOffY(const StdPicture& tx, const Player_t& p)\n{\n    UNUSED(tx);\n\n    if((p.Character < 1) || (p.Character > 5))\n        return 0;\n\n    using plr_frame_off_arr = RangeArrI<int8_t, 0, maxPlayerFrames, 0>;\n    constexpr std::array<plr_frame_off_arr*, 5> char_offsetY = {&MarioFrameY, &LuigiFrameY, &PeachFrameY, &ToadFrameY, &LinkFrameY};\n    int offY = (*char_offsetY[p.Character - 1])[(p.State * 100) + (p.Frame * p.Direction)];\n\n    return offY;\n}\n\nvoid DrawCycloneAccessory(int Z, const Player_t& p, int cX, int tY, XTColor c)\n{\n    int acc_frame = (CommonFrame / 4) % 4;\n    if(acc_frame == 3)\n        acc_frame = 1;\n\n    // calculate offset for accessory\n    int offsetX = -16;\n    int offsetY = -32;\n\n    int extra_offX = 0;\n    int extra_offY = 0;\n\n    if(p.Character == 5)\n    {\n        offsetX += 1;\n\n        if(p.Frame == 1 || p.Frame == 2 || p.Frame == 3)\n        {\n            extra_offX = -4;\n            extra_offY = -6;\n        }\n        else if(p.Frame == 4)\n        {\n            extra_offX = -2;\n            extra_offY = -4;\n        }\n        else if(p.Frame == 5)\n        {\n            extra_offY = -6;\n        }\n        else if(p.Frame == 6)\n        {\n            extra_offX = -12;\n            extra_offY = -4;\n        }\n        else if(p.Frame == 7)\n        {\n            extra_offX = 10;\n            extra_offY = -2;\n        }\n        else if(p.Frame == 8)\n        {\n            extra_offX = 12;\n            extra_offY = -4;\n        }\n        else if(p.Frame == 9)\n        {\n            extra_offX = 0;\n            extra_offY = 0;\n        }\n        else if(p.Frame == 10)\n        {\n            extra_offX = -8;\n            extra_offY = -4;\n        }\n        else if(p.Frame == 11)\n        {\n            extra_offX = 2;\n            extra_offY = 0;\n        }\n    }\n    else if(p.Frame == 30)\n    {\n        extra_offX = (p.Character == 2) ? -10 : -8;\n        int offY = (p.Character == 2) ? -4 : 0;\n\n        extra_offY = p.MountOffsetY + offY;\n    }\n    else if(p.Frame == 31)\n    {\n        extra_offX = -4;\n        extra_offY = (p.Character == 2) ? -28 : -26;\n    }\n    else if(p.Frame == 7 || p.Frame == 27)\n    {\n        if(p.Character == 4)\n            extra_offY = 2;\n        else if(p.Character == 1)\n            extra_offY = -4;\n        else if(p.Character == 2)\n            extra_offY = -6;\n        else\n            extra_offY = -2;\n    }\n    else if(p.Frame == 13)\n    {\n        if(p.Character == 3)\n            extra_offY = 4;\n    }\n    else if(p.Frame == 15)\n    {\n        if(p.Character == 3)\n            extra_offY = -2;\n    }\n    else if(p.Frame >= 40)\n    {\n        if(p.Character == 2)\n            extra_offX = -2;\n    }\n    else if(p.Frame == 24)\n    {\n        extra_offX = -2;\n        extra_offY = 2;\n    }\n    else if(p.Frame == 25 || p.Frame == 26)\n    {\n        acc_frame = 3 + (CommonFrame / 8) % 4;\n        offsetY = -2;\n\n        if(p.Character == 4)\n            offsetY = -6;\n        else if(p.Character == 3)\n        {\n            offsetY = -4;\n            offsetX += 4;\n        }\n    }\n    else if(p.Frame == 22 || p.Frame == 23)\n    {\n        if(p.Frame == 23)\n            offsetY += 2;\n\n        extra_offX = -2;\n\n        if(p.Character == 4)\n            extra_offY = -12;\n        else if(p.Character == 3)\n        {\n            extra_offY = -22;\n            extra_offX = 0;\n\n            if(p.Direction == 1)\n                offsetX += 8;\n        }\n        else\n            extra_offY = -18;\n    }\n    else if(p.Frame == 16 || p.Frame == 17 || p.Frame == 18)\n    {\n        if(p.Character == 3)\n            extra_offY = 2;\n        else if(p.Character != 4)\n            extra_offY = -2;\n    }\n    else if(p.Frame <= 10)\n    {\n        if(p.Character == 1 || p.Character == 2)\n        {\n            if(p.Frame == 4 || p.Frame == 5)\n                extra_offY = -2;\n\n            if(p.Frame == 6)\n                extra_offX = 2;\n            else if(!p.SpinJump)\n                extra_offX = -2;\n        }\n    }\n\n    if(p.Character == 2 && !(p.Mount == 1 && p.Duck))\n        offsetY -= 2;\n\n    offsetX += extra_offX * p.Direction;\n    offsetY += extra_offY;\n\n    RenderTexturePlayer(Z, p,\n        cX + offsetX,\n        tY + offsetY,\n        32,\n        32,\n        GFX.CycloneAcc,\n        0,\n        32 * acc_frame,\n        c);\n}\n\nusing plr_pic_arr = RangeArr<StdPicture, 1, numStates>;\nstatic constexpr std::array<plr_pic_arr*, 5> s_char_tex = {&GFXMario, &GFXLuigi, &GFXPeach, &GFXToad, &GFXLink};\n\nvoid DrawPlayerRaw(int X, int Y, int Character, int State, int Frame, int Direction)\n{\n    Player_t p;\n    p.Character = Character;\n    p.State = State;\n    p.Frame = Frame;\n    p.Direction = Direction;\n\n    StdPicture& tx = (*s_char_tex[p.Character - 1])[p.State];\n\n    int offX = pfrOffX(tx, p);\n    int offY = pfrOffY(tx, p);\n\n    XRender::renderTextureBasic(X + offX,\n                Y + offY,\n                pfrW(tx, p),\n                pfrH(tx, p),\n                tx,\n                pfrX(tx, p),\n                pfrY(tx, p));\n}\n\nvoid DrawPlayer(const int A, const int Z, XTColor color)\n{\n    DrawPlayer(Player[A], Z, color);\n}\n\nvoid DrawPlayer(Player_t &p, const int Z, XTColor color)\n{\n    num_t camX = vScreen[Z].CameraAddX();\n    num_t camY = vScreen[Z].CameraAddY();\n\n    int B = 0;\n    // double C = 0;\n    XTColor s = (ShadowMode ? XTColor(64, 64, 64, color.a) : color);\n\n    // color player during invincibility\n    if(InvincibilityTime)\n    {\n        switch(((unsigned)CommonFrame / 4) % 4)\n        {\n        case 0:\n            s.r = 128;\n            break;\n        case 1:\n            s.g = 128;\n            break;\n        case 2:\n            s.b = 128;\n            break;\n        case 3:\n            break;\n        }\n    }\n\n    //auto &p = Player[A];\n\n    int sX = num_t::floor(camX + p.Location.X);\n    int sY = num_t::floor(camY + p.Location.Y);\n    int w = s_round2int(p.Location.Width);\n    int h = s_round2int(p.Location.Height);\n\n    if(!p.Immune2) // other draw conditions moved to calling site in UpdateGraphics\n    {\n        if(vScreenCollision(Z, p.Location))\n        {\n            // was previously in UpdateGraphicsDraw for pipe-warping players -- think about whether to move these to UpdateGraphicsLogic\n            if(p.Character == 5 && p.Effect == PLREFF_WARP_PIPE && p.Frame > 5)\n                p.Frame = 1;\n\n            if(p.Mount == 3 && !p.Fairy)\n            {\n                B = p.MountType;\n                // Yoshi's Tongue\n                if(p.MountSpecial > 0)\n                {\n                    int C = 0;\n                    if(p.Direction == 1)\n                        C = p.YoshiTongueLength;\n                    else\n                        C = -16;\n\n                    RenderTexturePlayer(Z, p, num_t::floor(camX + p.YoshiTongue.X) - C - 1,\n                                          num_t::floor(camY + p.YoshiTongue.Y),\n                                          p.YoshiTongueLength + 2,\n                                          16,\n                                          GFX.Tongue[2],\n                                          0, 0,\n                                          s);\n\n                    C = 1;\n                    if(p.Direction == 1)\n                        C = 0;\n\n                    RenderTexturePlayer(Z, p, num_t::floor(camX + p.YoshiTongue.X),\n                                          num_t::floor(camY + p.YoshiTongue.Y),\n                                          16, 16,\n                                          GFX.Tongue[1],\n                                          0,\n                                          16 * C,\n                                          s);\n                }\n\n                // Yoshi's Body\n                RenderTexturePlayer(Z, p, sX + p.YoshiBX,\n                                      sY + p.YoshiBY,\n                                      32, 32,\n                                      GFXYoshiB[B], 0, 32 * p.YoshiBFrame, s);\n\n                // Yoshi's Head\n                RenderTexturePlayer(Z, p, sX + p.YoshiTX,\n                                      sY + p.YoshiTY,\n                                      32, 32,\n                                      GFXYoshiT[B], 0, 32 * p.YoshiTFrame, s);\n            }\n\n            if(p.Fairy) // draw a fairy\n            {\n                p.Frame = 1;\n\n                //if(!p.Immune2) // Always true because of covered condition above\n                {\n                    RenderTexturePlayer(Z, p, sX - 5,\n                                          sY - 2,\n                                          32, 32,\n                                          GFXNPC[NPCID_FLY_POWER],\n                                          0,\n                                          (SpecialFrame[9] + (p.Direction > 0 ? 1 : 3)) * 32,\n                                          s);\n//                    if(p.Direction == 1)\n//                    {\n//                        RenderTexturePlayer(Z, camX + p.Location.X - 5, camY + p.Location.Y - 2, 32, 32, GFXNPC[254], 0, (SpecialFrame[9] + 1) * 32, s);\n//                    }\n//                    else\n//                    {\n//                        RenderTexturePlayer(Z, camX + p.Location.X - 5, camY + p.Location.Y - 2, 32, 32, GFXNPC[254], 0, (SpecialFrame[9] + 3) * 32, s);\n//                    }\n                }\n            }\n            else if(p.Character >= 1 && p.Character <= 5) // draw player\n            {\n                StdPicture& tx = (*s_char_tex[p.Character - 1])[p.State];\n                int offX = pfrOffX(tx, p);\n                int offY = pfrOffY(tx, p);\n\n                if(p.Mount == 0)\n                {\n                    RenderTexturePlayer(Z, p,\n                                sX + offX,\n                                sY + offY,\n                                pfrW(tx, p),\n                                pfrH(tx, p),\n                                tx,\n                                pfrX(tx, p),\n                                pfrY(tx, p),\n                                s);\n                }\n                else if(p.Mount == 1)\n                {\n                    if(!p.Duck)\n                    {\n                        int small_toad_oy_corr\n                            = (p.Character == 4 && p.State == 1)\n                            ? 6\n                            : 0;\n\n                        int peach_h_corr\n                            = (p.Character == 3)\n                            ? -2\n                            : 0;\n\n                        RenderTexturePlayer(Z, p,\n                                    sX + offX,\n                                    small_toad_oy_corr + sY + offY,\n                                    pfrW(tx, p),\n                                    h - 26 /*- p.MountOffsetY*/ - offY + peach_h_corr,\n                                    tx,\n                                    pfrX(tx, p),\n                                    pfrY(tx, p),\n                                    s);\n                    }\n\n                    RenderTexturePlayer(Z, p, sX + w / 2 - 16,\n                                          sY + h - 30,\n                                          32, 32,\n                                          GFX.Boot[p.MountType],\n                                          0,\n                                          32 * p.MountFrame,\n                                          s);\n                }\n                else if(p.Mount == 3)\n                {\n                    RenderTexturePlayer(Z, p, sX + offX,\n                                          sY + offY + p.MountOffsetY,\n                                          pfrW(tx, p),\n                                          pfrH(tx, p),\n                                          tx,\n                                          pfrX(tx, p),\n                                          pfrY(tx, p),\n                                          s);\n                }\n\n                if(p.State == PLR_STATE_CYCLONE)\n                    DrawCycloneAccessory(Z, p, sX + w / 2, sY, s);\n            }\n\n            // peach/toad held npcs\n            // NEW: also, all players' held NPCs during warp\n            if((p.Character == 3 || p.Character == 4 || (p.Effect == PLREFF_WARP_PIPE && p.Frame == 15)) && p.HoldingNPC > 0 && p.Effect != PLREFF_WARP_DOOR)\n            {\n                if(\n                    (\n                        (\n                             (\n                                    NPC[p.HoldingNPC].HoldingPlayer > 0\n                                    // && Player[NPC[p.HoldingNPC].HoldingPlayer].Effect != PLREFF_WARP_PIPE\n                              ) ||\n                             (NPC[p.HoldingNPC].Type == NPCID_TOOTHY && NPC[p.HoldingNPC].vehiclePlr == 0) ||\n                             (NPC[p.HoldingNPC].Type == NPCID_BULLET && NPC[p.HoldingNPC].CantHurt > 0)\n                         ) ||\n                      NPC[p.HoldingNPC].Effect == NPCEFF_PET_TONGUE\n                    ) &&\n                    NPC[p.HoldingNPC].Type != NPCID_ITEM_BURIED &&\n                 !Player[NPC[p.HoldingNPC].HoldingPlayer].Dead\n                )\n                {\n                    DrawNPCHeld(Z, camX, camY, p.HoldingNPC);\n                }\n            }\n\n            if(!p.Fairy)\n            {\n                if(p.Mount == 3 && p.YoshiBlue)\n                {\n                    if(p.Direction == 1)\n                    {\n                        RenderTexturePlayer(Z, p, sX + p.YoshiBX - 12,\n                                              sY + p.YoshiBY - 16,\n                                              32, 32, GFX.YoshiWings, 0, 0 + 32 * p.YoshiWingsFrame, s);\n                    }\n                    else\n                    {\n                        RenderTexturePlayer(Z, p, sX + p.YoshiBX + 12,\n                                              sY + p.YoshiBY - 16,\n                                              32, 32, GFX.YoshiWings, 0, 0 + 32 * p.YoshiWingsFrame, s);\n                    }\n                }\n                if((p.Mount == 1 && p.MountType == 3) || p.Effect == PLREFF_COOP_WINGS)\n                {\n                    if(p.Direction == 1)\n                    {\n                        RenderTexturePlayer(Z, p, sX - 24,\n                                              sY + h - 40,\n                                              32, 32, GFX.YoshiWings, 0, 0 + 32 * p.YoshiWingsFrame, s);\n                    }\n                    else\n                    {\n                        RenderTexturePlayer(Z, p, sX + 16,\n                                              sY + h - 40,\n                                              32, 32, GFX.YoshiWings, 0, 0 + 32 * p.YoshiWingsFrame, s);\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid DrawNPCHeld(int Z, num_t camX, num_t camY, int A)\n{\n    g_stats.renderedNPCs++;\n\n    XTColor cn = (NPC[A].Shadow) ? XTColor(64, 64, 64) : XTColor();\n\n    if(NPC[A].Type == NPCID_ICE_CUBE)\n    {\n        DrawFrozenNPC(camX, camY, A);\n    }\n    else if(!NPCIsYoshi(NPC[A]) && NPC[A].Type > 0)\n    {\n        int sX = num_t::floor(camX + NPC[A].Location.X);\n        int sY = num_t::floor(camY + NPC[A].Location.Y);\n        int w = s_round2int(NPC[A].Location.Width);\n        int h = s_round2int(NPC[A].Location.Height);\n\n        if(NPC[A]->WidthGFX == 0)\n        {\n            int src_x = NPC[A].GFXSlot * NPC[A]->TWidth;\n            if(src_x >= GFXNPC[NPC[A].Type].w)\n                src_x = 0;\n\n            RenderTexturePlayer(Z, Player[NPC[A].HoldingPlayer],\n                sX + NPC[A]->FrameOffsetX,\n                sY + NPC[A]->FrameOffsetY,\n                w,\n                h,\n                GFXNPC[NPC[A].Type],\n                src_x, NPC[A].Frame * h,\n                cn);\n        }\n        else\n        {\n            int src_x = NPC[A].GFXSlot * NPC[A]->WidthGFX;\n            if(src_x >= GFXNPC[NPC[A].Type].w)\n                src_x = 0;\n\n            RenderTexturePlayer(Z, Player[NPC[A].HoldingPlayer],\n                sX + (NPC[A]->FrameOffsetX * -NPC[A].Direction) - NPC[A]->WidthGFX / 2 + w / 2,\n                sY + NPC[A]->FrameOffsetY - NPC[A]->HeightGFX + h,\n                NPC[A]->WidthGFX,\n                NPC[A]->HeightGFX,\n                GFXNPC[NPC[A].Type],\n                src_x, NPC[A].Frame * NPC[A]->HeightGFX,\n                cn);\n        }\n\n        if(NPC[A].Wings)\n            DrawNPCWings(NPC[A], sX, sY, cn);\n    }\n}\n"
  },
  {
    "path": "src/graphics/gfx_editor.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <fmt_format_ne.h>\n\n#include \"core/render.h\"\n#include \"globals.h\"\n#include \"graphics.h\"\n#include \"layers.h\"\n#include \"npc.h\"\n#include \"collision.h\"\n#include \"gfx.h\"\n#include \"config.h\"\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n#include \"player.h\"\n\n#include \"main/trees.h\"\n\n#include \"editor.h\"\n#include \"editor/new_editor.h\"\n#include \"editor/editor_strings.h\"\n#include \"editor/magic_block.h\"\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n#   include <InterProcess/intproc.h>\n#endif\n\nstatic inline int s_round2int(num_t d)\n{\n    return num_t::floor(d + 0.5_n);\n}\n\nvoid s_drawBlockExtra(int Z, num_t camX, num_t camY, const Block_t& b)\n{\n    if(b.Special > 0 && !b.Hidden)\n    {\n        if(vScreenCollision(Z, b.Location))\n        {\n            NPCID C = NPCID_NULL;\n            if(b.Special > 1000)\n                C = NPCID(b.Special - 1000);\n            else if(b.Special == 110 && (CommonFrame & 32))\n                C = NPCID_INVINCIBILITY_POWER;\n            else\n                C = NPCID_COIN_S3;\n\n            int dW, dH;\n\n            if(NPCWidthGFX(C) == 0)\n            {\n                dH = NPCHeight(C);\n                dW = NPCWidth(C);\n            }\n            else\n            {\n                dH = NPCHeightGFX(C);\n                dW = NPCWidthGFX(C);\n            }\n\n            int sX = num_t::floor(camX + b.Location.X + b.Location.Width / 2) - dW / 2;\n            int sY = num_t::floor(camY + b.Location.Y + b.Location.Height / 2) - dH / 2;\n\n            vbint_t tempDirection = -1;\n\n            XRender::renderTextureBasic(sX + NPCFrameOffsetX(C),\n                sY + NPCFrameOffsetY(C),\n                dW, dH,\n                GFXNPC[C], 0, EditorNPCFrame(C, tempDirection) * dH);\n        }\n    }\n\n    // new: indicate that blocks have events\n    if(b.TriggerHit != EVENT_NONE || b.TriggerDeath != EVENT_NONE || b.TriggerLast != EVENT_NONE)\n    {\n        if(vScreenCollision(Z, b.Location))\n        {\n            int sX = num_t::floor(camX + b.Location.X + b.Location.Width / 2) - GFX.Chat.w / 2;\n            int sY = num_t::floor(camY + b.Location.Y) - GFX.Chat.h - 8;\n\n            XRender::renderTextureBasic(sX, sY, GFX.Chat, XTColorF(1.0_n, 0.0_n, 0.0_n, 0.7_n));\n        }\n    }\n}\n\nvoid s_drawNpcExtra(int Z, num_t camX, num_t camY, const NPC_t& n)\n{\n    if(!n.Hidden && NPCIsContainer(n) && n.Type != NPCID_ITEM_BUBBLE && (n.Special > 0))\n    {\n        if(vScreenCollision(Z, n.Location))\n        {\n            NPCID C = NPCID(n.Special);\n\n            int dW, dH;\n            if(NPCWidthGFX(C) == 0)\n            {\n                dH = NPCHeight(C);\n                dW = NPCWidth(C);\n            }\n            else\n            {\n                dH = NPCHeightGFX(C);\n                dW = NPCWidthGFX(C);\n            }\n\n            int sY;\n            if(n.Type == NPCID_ITEM_POD)\n                sY = num_t::floor(camY + n.Location.Y + n.Location.Height) - dH;\n            else\n                sY = num_t::floor(camY + n.Location.Y);\n\n            int sX = num_t::floor(camX + n.Location.X + n.Location.Width / 2) - dW / 2;\n\n            vbint_t tempDirection = -1;\n\n            XRender::renderTextureBasic(sX + NPCFrameOffsetX(C),\n                sY + NPCFrameOffsetY(C),\n                dW, dH,\n                GFXNPC[C], 0, EditorNPCFrame(C, tempDirection) * dH);\n        }\n    }\n\n    // new: indicate that NPCs have events\n    if(n.TriggerActivate != EVENT_NONE || n.TriggerTalk != EVENT_NONE || n.TriggerDeath != EVENT_NONE || n.TriggerLast != EVENT_NONE)\n    {\n        if(vScreenCollision(Z, n.Location))\n        {\n            int sX = num_t::floor(camX + n.Location.X + n.Location.Width / 2);\n            if(n.Text == STRINGINDEX_NONE)\n                sX -= GFX.Chat.w / 2;\n            else\n                sX -= 4 + GFX.Chat.w;\n\n            int sY = num_t::floor(camY + n.Location.Y) - GFX.Chat.h - 8;\n\n            XRender::renderTextureBasic(sX, sY, GFX.Chat, XTColorF(1.0_n, 0.0_n, 0.0_n, 0.7_n));\n        }\n    }\n\n    // and that they can talk\n    if(n.Text != STRINGINDEX_NONE)\n    {\n        if(vScreenCollision(Z, n.Location))\n        {\n            int sX = num_t::floor(camX + n.Location.X + n.Location.Width / 2);\n            if(!(n.TriggerActivate != EVENT_NONE || n.TriggerTalk != EVENT_NONE || n.TriggerDeath != EVENT_NONE || n.TriggerLast != EVENT_NONE))\n                sX -= GFX.Chat.w / 2;\n            else\n                sX += 4;\n\n            int sY = num_t::floor(camY + n.Location.Y) - GFX.Chat.h - 8;\n\n            XRender::renderTextureBasic(sX, sY, GFX.Chat, XTColorF(1.0_n, 1.0_n, 1.0_n, 0.7_n));\n        }\n    }\n}\n\nvoid s_drawWaterBox(num_t camX, num_t camY, const Water_t& w)\n{\n    XRender::renderRect(num_t::floor(camX + w.Location.X), num_t::floor(camY + w.Location.Y), num_t::floor(w.Location.Width), num_t::floor(w.Location.Height),\n        (w.Type == PHYSID_QUICKSAND) ? XTColor{255, 255, 0} :\n        (w.Type == PHYSID_MAZE) ? XTColor{192, 192, 255} :\n        XTColor{0, 255, 255},\n        false);\n}\n\nvoid s_drawPlayer(int Z, int A, num_t sceneX, num_t sceneY)\n{\n    Player_t& p = Player[A];\n\n    p.Character = testPlayer[A].Character;\n    p.State = testPlayer[A].State;\n    p.Mount = testPlayer[A].Mount;\n    p.MountType = testPlayer[A].MountType;\n\n    if(p.Character < 1 || p.Character > numCharacters)\n        p.Character = A;\n    if(p.State < 1 || p.State > numStates)\n        p.State = 2;\n\n    p.Direction = 1;\n    p.Location.SpeedY = 0;\n    p.Location.SpeedX = 0;\n    p.Controls.Left = false;\n    p.Controls.Right = false;\n    p.SpinJump = false;\n    p.Dead = false;\n    p.Immune2 = false;\n    p.Fairy = false;\n    p.TimeToLive = 0;\n    p.Effect = PLREFF_NORMAL;\n    p.MountSpecial = 0;\n    p.HoldingNPC = 0;\n    if(p.Duck)\n        UnDuck(p);\n    PlayerFrame(p);\n\n    if(p.MountType == 3)\n    {\n        p.YoshiWingsFrameCount += 1;\n        p.YoshiWingsFrame = 0;\n        if(p.YoshiWingsFrameCount <= 12)\n            p.YoshiWingsFrame = 1;\n        else if(p.YoshiWingsFrameCount >= 24)\n            p.YoshiWingsFrameCount = 0;\n        if(p.Direction == 1)\n            p.YoshiWingsFrame += 2;\n    }\n\n    int C = Physics.PlayerHeight[p.Character][p.State] - Physics.PlayerHeight[A][2];\n\n    p.Location.X = sceneX;\n    p.Location.Y = sceneY - C;\n    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n    SizeCheck(p);\n\n    DrawPlayer(p, Z);\n}\n\nvoid DrawEditorLevel(int Z)\n{\n    int A = 0;\n    // int B = 0;\n    int S = curSection; // Level section to display\n\n    // camera offsets to add to all object positions before drawing\n    num_t camX = vScreen[Z].CameraAddX();\n    num_t camY = vScreen[Z].CameraAddY();\n    int camX_i = num_t::floor(camX);\n    int camY_i = num_t::floor(camY);\n\n#ifdef __3DS__\n    XRender::setTargetLayer(2);\n#endif\n    if(LevelEditor)\n    {\n        if((CommonFrame % 46) <= 30)\n        {\n            // render NPCs in and events for blocks\n            for(A = 1; A <= numBlock; A++)\n                s_drawBlockExtra(Z, camX, camY, Block[A]);\n\n            // render NPCs in containers\n            for(A = 1; A <= numNPCs; A++)\n                s_drawNpcExtra(Z, camX, camY, NPC[A]);\n        }\n\n        // render player start points\n        for(A = 1; A <= 2; A++)\n        {\n            if(!(PlayerStart[A].Width > 0)) continue;\n            if(vScreenCollision(Z, PlayerStart[A]))\n                s_drawPlayer(Z, A, PlayerStart[A].X, PlayerStart[A].Y);\n        }\n\n        // render water\n        for(int B : treeWaterQuery(-camX, -camY,\n            -camX + vScreen[Z].Width, -camY + vScreen[Z].Height,\n            SORTMODE_ID))\n        {\n            if(!Water[B].Hidden && vScreenCollision(Z, Water[B].Location))\n                s_drawWaterBox(camX, camY, Water[B]);\n        }\n\n        // render warps\n        for(A = 1; A <= numWarps; A++)\n        {\n            if(Warp[A].Direction > 0 && !Warp[A].Hidden)\n            {\n                bool complete = Warp[A].PlacedEnt && Warp[A].PlacedExit;\n                XTColor color = complete ? XTColorF(1.0_n, 0.0_n, 1.0_n) : XTColorF(0.7_n, 0.3_n, 0.0_n);\n\n                const SpeedlessLocation_t* const locs[2] = {&Warp[A].Entrance, &Warp[A].Exit};\n                const bool enabled[2] = {Warp[A].PlacedEnt, Warp[A].PlacedExit};\n                const int direction[2] = {Warp[A].Direction, Warp[A].Direction2};\n\n                for(int i = 0; i < 2; i++)\n                {\n                    if(!enabled[i])\n                        continue;\n\n                    int l = num_t::floor(camX + locs[i]->X);\n                    int t = num_t::floor(camY + locs[i]->Y);\n                    int w = num_t::floor(locs[i]->Width);\n                    int h = num_t::floor(locs[i]->Height);\n\n                    XRender::renderRect(l, t, w, h, color, false);\n\n                    if(Warp[A].Effect == 1 || Warp[A].Effect == 2)\n                    {\n                        if(direction[i] == 1 || direction[i] == 3)\n                            XRender::renderRect(l + 2, t + 2 + (h - 6) * (direction[i] == 3), w - 4, 2, XTColor(0, 255, 0), true);\n\n                        if(direction[i] == 2 || direction[i] == 4)\n                            XRender::renderRect(l + 2 + (w - 6) * (direction[i] == 4), t + 2, 2, h - 4, XTColor(0, 255, 0), true);\n                    }\n\n                    SuperPrint(std::to_string(A), 1, l + 2 + (w - 16 - 4) * i,\n                        t + 2 + (h - 14 - 4) * i);\n                }\n            }\n        }\n\n        // render event section resizes\n        for(int A = 0; A < numEvents; A++)\n        {\n            const auto& e = Events[A];\n            const IntegerLocation_t& sectPos = e.section[curSection].position;\n            if(sectPos.X == EventSection_t::LESet_Nothing || sectPos.X == EventSection_t::LESet_ResetDefault)\n                continue;\n\n            XTColor ev_color = {uint8_t(64 + 128 * (A & 1)), uint8_t(64 + 64 * (A & 2)), uint8_t(64 + 32 * (A & 4))};\n            DrawSimpleFrame(camX_i + sectPos.X, camY_i + sectPos.Y, sectPos.Width - sectPos.X, sectPos.Height - sectPos.Y, {0, 0, 0}, ev_color, {0, 0, 0, 0});\n            SuperPrint(g_editorStrings.wordEvent, 3, camX_i + sectPos.X + 8, camY_i + sectPos.Y + 8);\n            SuperPrint(e.Name, 3, camX_i + sectPos.X + 8, camY_i + sectPos.Y + 28, ev_color);\n            SuperPrint(g_editorStrings.eventsCaseBounds, 3, camX_i + sectPos.X + 8, camY_i + sectPos.Y + 48);\n        }\n    }\n\n#ifdef __3DS__\n    XRender::setTargetLayer(2);\n#endif\n\n    // render section boundary\n    if(LevelEditor)\n    {\n        if(camX_i + LevelREAL[S].X > 0)\n        {\n            XRender::renderRect(0, 0,\n                               camX_i + LevelREAL[S].X, XRender::TargetH, {63, 63, 63, 192}, true);\n        }\n\n        if(XRender::TargetW > LevelREAL[S].Width + camX_i)\n        {\n            XRender::renderRect(LevelREAL[S].Width + camX_i, 0,\n                               XRender::TargetW - (LevelREAL[S].Width + camX_i), XRender::TargetH, {63, 63, 63, 192}, true);\n        }\n\n        if(camY_i + LevelREAL[S].Y > 0)\n        {\n            XRender::renderRect(camX_i + LevelREAL[S].X, 0,\n                               LevelREAL[S].Width - LevelREAL[S].X, camY_i + LevelREAL[S].Y, {63, 63, 63, 192}, true);\n        }\n\n        if(XRender::TargetH > LevelREAL[S].Height + camY_i)\n        {\n            XRender::renderRect(camX_i + LevelREAL[S].X, LevelREAL[S].Height + camY_i,\n                               LevelREAL[S].Width - LevelREAL[S].X, XRender::TargetH - (LevelREAL[S].Height + camY_i), {63, 63, 63, 192}, true);\n        }\n    }\n\n#ifdef __3DS__\n    XRender::setTargetLayer(3);\n#endif\n\n#ifdef __3DS__\n    // In-Editor message box preview (actually only useful on 3DS)\n    if(editorScreen.active && !MessageText.empty())\n        DrawMessage();\n#endif\n\n#ifdef __3DS__\n    // disable cursor rendering on main screen when editor screen is active\n    if(!editorScreen.active)\n#endif\n    // Display the cursor\n    {\n        auto &e = EditorCursor;\n        int curX = s_round2int(e.X) - vScreen[Z].TargetX();\n        int curY = s_round2int(e.Y) - vScreen[Z].TargetY();\n\n        if((CommonFrame % 46) < 10)\n        {\n            // don't draw the currently held object\n        }\n        else if(e.Mode == OptCursor_t::LVL_BLOCKS) // Blocks\n        {\n            auto &b = e.Block;\n            if(BlockIsSizable[b.Type])\n            {\n                if(vScreenCollision(Z, b.Location))\n                    XRender::renderSizableBlock(num_t::floor(camX + b.Location.X), num_t::floor(camY + b.Location.Y), (int)(b.Location.Width), (int)(b.Location.Height), GFXBlockBMP[b.Type]);\n            }\n            else\n            {\n                if(vScreenCollision(Z, b.Location))\n                {\n                    XRender::renderTextureBasic(num_t::floor(camX + b.Location.X),\n                                          num_t::floor(camY + b.Location.Y) + b.ShakeOffset,\n                                          (int)(b.Location.Width),\n                                          (int)(b.Location.Height),\n                                          GFXBlock[b.Type], 0, BlockFrame[b.Type] * 32);\n                }\n            }\n\n            // render NPC inside block\n            if((CommonFrame % 46) <= 30)\n                s_drawBlockExtra(Z, camX, camY, b);\n        }\n\n        else if(e.Mode == OptCursor_t::LVL_PLAYERSTART) // Player start points\n        {\n            if(e.SubMode == 4 || e.SubMode == 5)\n                s_drawPlayer(Z, e.SubMode - 3, e.Location.X, e.Location.Y);\n        }\n\n        else if(e.Mode == OptCursor_t::LVL_BGOS) // BGOs\n        {\n            auto &b = e.Background;\n            if(vScreenCollision(Z, b.Location))\n            {\n                XRender::renderTextureBasic(num_t::floor(camX + b.Location.X),\n                                      num_t::floor(camY + b.Location.Y),\n                                      BackgroundWidth[b.Type],\n                                      BackgroundHeight[b.Type],\n                                      GFXBackground[b.Type], 0,\n                                      BackgroundHeight[b.Type] * BackgroundFrame[b.Type]);\n            }\n        }\n\n        else if(e.Mode == OptCursor_t::LVL_NPCS) // NPCs\n        {\n            e.NPC.Frame = NPC[0].Frame;\n            e.NPC.FrameCount = NPC[0].FrameCount;\n            NPC[0] = e.NPC;\n            NPCFrames(0);\n            e.NPC = NPC[0];\n            auto &n = e.NPC;\n\n            DrawNPC(camX, camY, 0);\n\n            s_drawNpcExtra(Z, camX, camY, n);\n        }\n        else if(EditorCursor.Mode == OptCursor_t::LVL_WATER) // Water\n            s_drawWaterBox(camX, camY, EditorCursor.Water);\n        else if(EditorCursor.Mode == OptCursor_t::LVL_WARPS)\n        {\n            XRender::renderRect(num_t::floor(camX + EditorCursor.Location.X), num_t::floor(camY + EditorCursor.Location.Y), (int)EditorCursor.Location.Width, (int)EditorCursor.Location.Height,\n                XTColorF(1.0_n, 0.0_n, 0.0_n, 1.0_n), false);\n        }\n\n        if(EditorCursor.Mode == 0 || EditorCursor.Mode == 6) // Eraser\n        {\n            if(EditorCursor.SubMode == -1)\n            {\n                int frame = (CommonFrame % 46) / 20;\n                if(frame > 2)\n                    frame = 2;\n                XRender::renderTextureBasic(curX - 8, curY, 32, 32, GFXNPC[NPCID_AXE], 0, 32 * frame);\n            }\n            else\n                XRender::renderTextureBasic(curX - 2, curY, GFX.ECursor[3]);\n        }\n\n        // left-right resize\n        else if(EditorCursor.InteractFlags > 1 && !(EditorCursor.InteractFlags & 2) && !(EditorCursor.InteractFlags & 4))\n        {\n            XRender::renderTextureBasic(curX - 16, curY - 16, 32, 32, GFX.EIcons, 0, 32 * Icon::lr);\n        }\n\n        // up-down resize\n        else if(EditorCursor.InteractFlags > 1 && !(EditorCursor.InteractFlags & 8) && !(EditorCursor.InteractFlags & 16))\n        {\n            XRender::renderTextureBasic(curX - 16, curY - 16, 32, 32, GFX.EIcons, 0, 32 * Icon::ud);\n        }\n\n        // resize\n        else if(EditorCursor.InteractFlags > 1)\n        {\n            XRender::renderTextureBasic(curX - 16, curY - 16, 32, 32, GFX.EIcons, 0, 32 * Icon::target);\n        }\n\n        else if(EditorCursor.Mode == 13 || EditorCursor.Mode == 14) // Selector\n        {\n            if(MagicBlock::enabled)\n                XRender::renderTextureBasic(curX, curY, GFX.ECursor[2], XTColorF(0.7_n, 1.0_n, 0.7_n, 1.0_n));\n            else\n                XRender::renderTextureBasic(curX, curY, GFX.ECursor[2]);\n\n        }\n\n        // Section Resize\n        else if(EditorCursor.Mode == 2 && EditorCursor.SubMode < 4)\n        {\n            XRender::renderTextureBasic(curX, curY, GFX.ECursor[1]);\n        }\n        else\n        {\n            if(MagicBlock::enabled)\n                XRender::renderTextureBasic(curX, curY, GFX.ECursor[2], XTColorF(0.7_n, 1.0_n, 0.7_n, 1.0_n));\n            else\n                XRender::renderTextureBasic(curX, curY, GFX.ECursor[2]);\n\n            if(e.Layer != LAYER_NONE && e.Layer != LAYER_DEFAULT)\n            {\n                // there might be a tooltip in this case\n                if(editorScreen.active || EditorCursor.Y < 40)\n                    SuperPrint(GetL(e.Layer), 3, curX + 28 , curY + 34, XTColorF(1.0_n, 1.0_n, 1.0_n, 0.3_n));\n                else\n                    SuperPrint(GetL(e.Layer), 3, curX + 28 , curY + 34);\n            }\n        }\n\n//            End With\n    }\n\n    if(editor_section_toast > 0)\n    {\n        SuperPrintCenter(\"SECTION \" + std::to_string(curSection+1), 3, vScreen[Z].Width / 2, vScreen[Z].Height - 100);\n        editor_section_toast--;\n    }\n}\n\nvoid DrawEditorLevel_UI()\n{\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n    if(!MagicHand || !IntProc::isEnabled())\n#endif\n    {\n        editorScreen.UpdateEditorScreen(EditorScreen::CallMode::Render);\n        XRender::resetViewport();\n    }\n\n#ifdef __3DS__\n    if(!editorScreen.active)\n    {\n        editorScreen.UpdateEditorScreen(EditorScreen::CallMode::Render, true);\n        XRender::resetViewport();\n        XRender::setTargetLayer(3);\n    }\n#endif\n}\n\nvoid DrawEditorWorld()\n{\n    int Z = 1;\n\n    int camX = vScreen[Z].CameraAddX_i() + XRender::TargetOverscanX;\n    int camY = vScreen[Z].CameraAddY_i();\n\n#ifdef __3DS__\n    // disable cursor rendering on inactive screen of 3DS\n    if(editorScreen.active) {}\n    else\n#endif\n\n    if((CommonFrame % 46) < 10)\n    {\n        // don't draw the currently held object\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_TILES)\n    {\n        XRender::renderTextureBasic(camX + EditorCursor.Tile.Location.X,\n            camY + EditorCursor.Tile.Location.Y,\n            EditorCursor.Tile.Location.Width,\n            EditorCursor.Tile.Location.Height,\n            GFXTile[EditorCursor.Tile.Type],\n            0,\n            TileHeight[EditorCursor.Tile.Type] * TileFrame[EditorCursor.Tile.Type]);\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_SCENES)\n    {\n        XRender::renderTextureBasic(camX + EditorCursor.Scene.Location.X,\n            camY + EditorCursor.Scene.Location.Y,\n            EditorCursor.Scene.Location.Width,\n            EditorCursor.Scene.Location.Height,\n            GFXScene[EditorCursor.Scene.Type],\n            0,\n            SceneHeight[EditorCursor.Scene.Type] * SceneFrame[EditorCursor.Scene.Type]);\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_LEVELS)\n    {\n        if(EditorCursor.WorldLevel.Path)\n        {\n            XRender::renderTextureBasic(camX + EditorCursor.WorldLevel.Location.X,\n                                  camY + EditorCursor.WorldLevel.Location.Y,\n                                  EditorCursor.WorldLevel.Location.Width,\n                                  EditorCursor.WorldLevel.Location.Height,\n                                  GFXLevelBMP[0], 0, 0);\n        }\n\n        if(EditorCursor.WorldLevel.Path2)\n        {\n            XRender::renderTextureBasic(camX + EditorCursor.WorldLevel.Location.X - 16,\n                                  camY + 8 + EditorCursor.WorldLevel.Location.Y,\n                                  64, 32,\n                                  GFXLevelBMP[29], 0, 0);\n        }\n\n        if(GFXLevelBig[EditorCursor.WorldLevel.Type])\n        {\n            XRender::renderTextureBasic(camX + EditorCursor.WorldLevel.Location.X - (GFXLevel[EditorCursor.WorldLevel.Type].w - 32) / 2,\n                                  camY + EditorCursor.WorldLevel.Location.Y - GFXLevel[EditorCursor.WorldLevel.Type].h + 32,\n                                  GFXLevel[EditorCursor.WorldLevel.Type].w, GFXLevel[EditorCursor.WorldLevel.Type].h,\n                                  GFXLevelBMP[EditorCursor.WorldLevel.Type], 0, 32 * LevelFrame[EditorCursor.WorldLevel.Type]);\n        }\n        else\n        {\n            XRender::renderTextureBasic(camX + EditorCursor.WorldLevel.Location.X,\n                                  camY + EditorCursor.WorldLevel.Location.Y,\n                                  EditorCursor.WorldLevel.Location.Width, EditorCursor.WorldLevel.Location.Height,\n                                  GFXLevelBMP[EditorCursor.WorldLevel.Type], 0, 32 * LevelFrame[EditorCursor.WorldLevel.Type]);\n        }\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_PATHS)\n    {\n        XRender::renderTextureBasic(camX + EditorCursor.WorldPath.Location.X,\n            camY + EditorCursor.WorldPath.Location.Y,\n            EditorCursor.WorldPath.Location.Width,\n            EditorCursor.WorldPath.Location.Height,\n            GFXPath[EditorCursor.WorldPath.Type],\n            0, 0);\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_MUSIC)\n    {\n        XRender::renderRect(camX + EditorCursor.WorldMusic.Location.X, camY + EditorCursor.WorldMusic.Location.Y, 32, 32,\n            XTColorF(1.0_n, 0.0_n, 1.0_n), false);\n        SuperPrint(std::to_string(EditorCursor.WorldMusic.Type), 1, camX + EditorCursor.WorldMusic.Location.X + 2, camY + EditorCursor.WorldMusic.Location.Y + 2);\n    }\n    else if(EditorCursor.Mode == OptCursor_t::WLD_AREA)\n    {\n        XRender::renderRect(camX + EditorCursor.WorldArea.Location.X, camY + EditorCursor.WorldArea.Location.Y,\n            EditorCursor.WorldArea.Location.Width, EditorCursor.WorldArea.Location.Height,\n            XTColorF(1.0_n, 0.8_n, 0.2_n), false);\n    }\n\n    int X = (int)EditorCursor.X - vScreen[Z].TargetX();\n    int Y = (int)EditorCursor.Y - vScreen[Z].TargetY();\n\n#if 0\n    if(g_config.editor_edge_scroll && !editorScreen.active && !MagicHand)\n    {\n        if(X >= 0 && X < 36)\n            X = 36;\n        if(Y >= 0 && Y < 36)\n            Y = 36;\n        if(X >= XRender::TargetW - 36)\n            X = XRender::TargetW - 36;\n        if(Y >= XRender::TargetH - 36)\n            Y = XRender::TargetH - 36;\n    }\n#endif\n\n#ifdef __3DS__\n    // disable cursor rendering on inactive screen of 3DS\n    if(editorScreen.active) {}\n    else\n#endif\n\n    if(EditorCursor.Mode == OptCursor_t::LVL_ERASER)\n    {\n        if(EditorCursor.SubMode == -1)\n        {\n            int frame = (CommonFrame % 46) / 20;\n            if(frame > 2)\n                frame = 2;\n            XRender::renderTextureBasic(X - 8, Y, 32, 32, GFXNPC[NPCID_AXE], 0, 32 * frame);\n        }\n        else\n            XRender::renderTextureBasic(X - 2, Y, GFX.ECursor[3]);\n    }\n    // left-right resize\n    else if(EditorCursor.InteractFlags > 1 && !(EditorCursor.InteractFlags & 2) && !(EditorCursor.InteractFlags & 4))\n    {\n        XRender::renderTextureBasic(X - 16, Y - 16, 32, 32, GFX.EIcons, 0, 32 * Icon::lr);\n    }\n\n    // up-down resize\n    else if(EditorCursor.InteractFlags > 1 && !(EditorCursor.InteractFlags & 8) && !(EditorCursor.InteractFlags & 16))\n    {\n        XRender::renderTextureBasic(X - 16, Y - 16, 32, 32, GFX.EIcons, 0, 32 * Icon::ud);\n    }\n\n    // resize\n    else if(EditorCursor.InteractFlags > 1)\n    {\n        XRender::renderTextureBasic(X - 16, Y - 16, 32, 32, GFX.EIcons, 0, 32 * Icon::target);\n    }\n    else\n    {\n        XRender::renderTextureBasic(X, Y, GFX.ECursor[2]);\n\n        // show coordinates of nearby level for help making warps\n        for(WorldLevel_t* t : treeWorldLevelQuery(TinyLocation_t{(int)EditorCursor.Location.X, (int)EditorCursor.Location.Y, 1, 1}, SORTMODE_NONE))\n        {\n            WorldLevel_t &lvl = *t;\n            if(CursorCollision(EditorCursor.Location, lvl.Location))\n            {\n                int at_X = lvl.Location.X + lvl.Location.Width / 2 + camX;\n                int at_Y = lvl.Location.Y + camY - 40;\n                XRender::renderRect(at_X - 80, at_Y - 4, 160, 44, {0, 0, 0, 127}, true);\n                SuperPrintCenter(fmt::sprintf_ne(\"%s: %d\", g_editorStrings.letterCoordX.c_str(), (int)lvl.Location.X), 3, at_X, at_Y);\n                SuperPrintCenter(fmt::sprintf_ne(\"%s: %d\", g_editorStrings.letterCoordY.c_str(), (int)lvl.Location.Y), 3, at_X, at_Y + 20);\n            }\n        }\n    }\n\n    editorScreen.UpdateEditorScreen(EditorScreen::CallMode::Render);\n    XRender::resetViewport();\n\n#ifdef __3DS__\n    if(!editorScreen.active)\n    {\n        editorScreen.UpdateEditorScreen(EditorScreen::CallMode::Render, true);\n        XRender::resetViewport();\n        XRender::setTargetLayer(3);\n    }\n#endif\n}\n"
  },
  {
    "path": "src/graphics/gfx_enter_screen.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_timer.h\"\n\n#include \"../gfx.h\"\n#include \"../globals.h\"\n#include \"../config.h\"\n#include \"../graphics.h\"\n#include \"../player.h\"\n#include \"../frame_timer.h\"\n#include \"../screen_fader.h\"\n#include \"../core/render.h\"\n#include \"../core/events.h\"\n#include \"../controls.h\"\n#include \"../main/game_strings.h\" // For g_gameStrings.loaderLoading strign\n#include \"pge_delay.h\"\n#include \"npc_id.h\"\n#include \"message.h\"\n\n#include \"main/hints.h\"\n\n\nstatic void initPlayers(Player_t tempPlayer[maxLocalPlayers])\n{\n    for(int A = 0; A < numPlayers && A < maxLocalPlayers; ++A)\n    {\n        auto &ref = Player[A + 1];\n        auto &dst = tempPlayer[A];\n\n        dst.Character = ref.Character;\n        dst.State = ref.State;\n        dst.Hearts = ref.Hearts;\n        dst.Mount = ref.Mount;\n        dst.MountType = ref.MountType;\n\n        if(dst.Character == 0)\n        {\n            dst.Character = 1; // Sets as Mario\n            if(numPlayers == 2 && A == 1 /*&& nPlay.Online == false*/) // Sets as Luigi\n                dst.Character = 2;\n        }\n\n        if(dst.State == 0) // if no state it defaults to small mario\n            dst.State = 1;\n\n        if(dst.Character == 3 || dst.Character == 4 || dst.Character == 5) // Peach and Toad\n        {\n            if(dst.Hearts <= 0)\n                dst.Hearts = 1;\n\n            if(dst.Hearts <= 1 && dst.State > 1 && dst.Character != 5)\n                dst.Hearts = 2;\n\n            if(dst.HeldBonus > 0)\n            {\n                dst.Hearts += 1;\n                dst.HeldBonus = NPCID(0);\n            }\n\n            if(dst.State == 1 && dst.Hearts > 1)\n                dst.State = 2;\n\n            if(dst.Hearts > 3)\n                dst.Hearts = 3;\n\n            if(dst.Mount == 3)\n                dst.Mount = 0;\n        }\n        else // Mario and Luigi\n        {\n            if(dst.Hearts == 3 && dst.HeldBonus == 0)\n                dst.HeldBonus = NPCID_POWER_S3;\n            dst.Hearts = 0;\n        }\n\n        if(dst.Character == 5) // Link\n            dst.Mount = 0;\n\n        dst.Location.Width = Physics.PlayerWidth[dst.Character][dst.State]; // set height\n        dst.Location.Height = Physics.PlayerHeight[dst.Character][dst.State]; // set width\n        if(dst.State == 1 && dst.Mount == 1) // if small and in a shoe then set the height to super mario\n            dst.Location.Height = Physics.PlayerHeight[1][2];\n\n        // reset all variables\n        if(dst.Mount == 2)\n            dst.Mount = 0;\n\n        if(dst.Character >= 3 && dst.Mount == 3)\n            dst.Mount = 0;\n\n        dst.Frame = 1;\n\n        if(dst.Character == 3)\n            dst.CanFloat = true;\n\n        if(dst.Character == 3 || dst.Character == 4)\n        {\n            if(dst.State == 1)\n                dst.Hearts = 1;\n\n            if(dst.State > 1 && dst.Hearts < 2)\n                dst.Hearts = 2;\n        }\n\n        dst.Section = -1;\n\n        dst.Location.SpeedY = 0;\n    }\n\n    if(numPlayers == 1 || g_ClonedPlayerMode)\n    {\n        tempPlayer[0].Direction = 1;\n    }\n    else if(numPlayers == 2)\n    {\n        tempPlayer[0].Direction = -1;\n        tempPlayer[1].Direction = 1;\n    }\n    else\n    {\n        for(int i = 0; i < numPlayers && i < maxLocalPlayers; i++)\n            tempPlayer[i].Direction = 1;\n    }\n\n    for(int i = 0; i < numPlayers && i < maxLocalPlayers; i++)\n        PlayerFrame(tempPlayer[i]);\n}\n\nstatic void placePlayers(Player_t tempPlayer[maxLocalPlayers])\n{\n    if(numPlayers == 1 || g_ClonedPlayerMode)\n    {\n        tempPlayer[0].Location.X = XRender::TargetW / 2 - tempPlayer[0].Location.Width / 2;\n        tempPlayer[0].Location.Y = XRender::TargetH / 2 - tempPlayer[0].Location.Height + 24;\n    }\n    else if(numPlayers == 2)\n    {\n        tempPlayer[0].Location.X = XRender::TargetW / 2 - tempPlayer[0].Location.Width / 2 - 30;\n        tempPlayer[0].Location.Y = XRender::TargetH / 2 - tempPlayer[0].Location.Height + 24;\n        tempPlayer[0].Direction = -1;\n\n        tempPlayer[1].Location.X = XRender::TargetW / 2 - tempPlayer[1].Location.Width / 2 + 32;\n        tempPlayer[1].Location.Y = XRender::TargetH / 2 - tempPlayer[1].Location.Height + 24;\n    }\n    else\n    {\n        int start_x = XRender::TargetW / 2 - (numPlayers - 1) * 32;\n\n        for(int i = 0; i < numPlayers && i < maxLocalPlayers; i++)\n        {\n            tempPlayer[i].Location.X = start_x + 64 * i - tempPlayer[i].Location.Width / 2;\n            tempPlayer[i].Location.Y = XRender::TargetH / 2 - tempPlayer[i].Location.Height + 24;\n        }\n    }\n}\n\n\nstatic void drawEnterScreen(Player_t tempPlayer[maxLocalPlayers])\n{\n    placePlayers(tempPlayer);\n\n    for(int A = 0; A < numPlayers && A < maxLocalPlayers; ++A)\n    {\n        DrawPlayer(tempPlayer[A], 0);\n\n        if(g_ClonedPlayerMode)\n            break;\n    }\n\n    if(TestLevel)\n        SuperPrintScreenCenter(g_gameStrings.loaderLoading, 3, XRender::TargetH / 2 + 32);\n    else\n        DrawLives(XRender::TargetW / 2 - 14, XRender::TargetH / 2 + 31, Lives, g_100s);\n\n    XHints::Draw(XRender::TargetH / 2 + 64, 0);\n}\n\n\nvoid GameThing(int waitms, int fadeSpeed)\n{\n    Player_t tempPlayer[maxLocalPlayers];\n    initPlayers(tempPlayer);\n\n    XHints::Select();\n\n    int wait_frames = (waitms * 10 + 155) / 156;\n\n    if(wait_frames <= 0)\n    {\n        XRender::setTargetTexture();\n        XRender::clearBuffer();\n        drawEnterScreen(tempPlayer);\n        XRender::repaint();\n        XEvents::doEvents();\n    }\n    else\n    {\n        ScreenFader fader;\n\n        if(g_config.EnableInterLevelFade && fadeSpeed > 0)\n            fader.setupFader(fadeSpeed, 65, 0, ScreenFader::S_FADE);\n\n        while(wait_frames > 0 && GameIsActive)\n        {\n            XEvents::doEvents();\n\n            if(canProceedFrame())\n            {\n                computeFrameTime1();\n\n                Controls::Update(false);\n\n                wait_frames--;\n\n                if(XMessage::GetStatus() == XMessage::Status::replay)\n                    continue;\n\n                XRender::setTargetTexture();\n                XRender::clearBuffer();\n                drawEnterScreen(tempPlayer);\n\n                if(fadeSpeed > 0 && fader.m_active)\n                {\n                    fader.update();\n                    fader.draw();\n                }\n\n                XRender::repaint();\n                XEvents::doEvents();\n                computeFrameTime2();\n            }\n\n            if(!g_config.unlimited_framerate)\n                PGE_Delay(1);\n        }\n    }\n}\n"
  },
  {
    "path": "src/graphics/gfx_frame.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <IniProcessor/ini_processing.h>\n#include <Logger/logger.h>\n\n#include \"core/render.h\"\n\n#include \"graphics/gfx_frame.h\"\n\nvoid loadFrameInfo(IniProcessing& ini, FrameBorderInfo& info)\n{\n\tinfo = FrameBorderInfo();\n\n\tini.beginGroup(\"border-info\");\n\tini.read(\"te\", info.te, info.te);\n\tini.read(\"ti\", info.ti, info.ti);\n\tini.read(\"be\", info.be, info.be);\n\tini.read(\"bi\", info.bi, info.bi);\n\tini.read(\"le\", info.le, info.le);\n\tini.read(\"li\", info.li, info.li);\n\tini.read(\"re\", info.re, info.re);\n\tini.read(\"ri\", info.ri, info.ri);\n\tini.endGroup();\n}\n\nvoid DrawTextureTiled(int dst_x, int dst_y, int dst_w, int dst_h, StdPicture& tx, int src_x, int src_y, int src_w, int src_h, int off_x, int off_y, XTColor color)\n{\n    if(off_x == -1)\n        off_x = dst_x;\n    if(off_y == -1)\n        off_y = dst_y;\n    if(src_w == -1)\n        src_w = tx.w;\n    if(src_h == -1)\n        src_h = tx.h;\n\n    int c_off_x = off_x % src_w;\n    // want modulus, not remainder\n    if(c_off_x < 0)\n    \tc_off_x += src_w;\n\n    for(int x = dst_x; x < dst_x + dst_w;)\n    {\n        int render_w = src_w - c_off_x;\n        if(x + render_w > dst_x + dst_w)\n            render_w = dst_x + dst_w - x;\n\n        int c_off_y = off_y % src_h;\n        // want modulus, not remainder\n        if(c_off_y < 0)\n        \tc_off_y += src_h;\n\n        for(int y = dst_y; y < dst_y + dst_h;)\n        {\n            int render_h = src_h - c_off_y;\n            if(y + render_h > dst_y + dst_h)\n                render_h = dst_y + dst_h - y;\n\n            XRender::renderTextureBasic(x, y, render_w, render_h, tx, src_x + c_off_x, src_y + c_off_y, color);\n\n            y += src_h - c_off_y;\n            c_off_y = 0;\n        }\n\n        x += src_w - c_off_x;\n        c_off_x = 0;\n    }\n}\n\n// renders a new-style frame with the included external and internal locations\nvoid RenderFrameBorder(const IntegerLocation_t& external, const IntegerLocation_t& internal,\n\tStdPicture& tile, FrameBorder* border)\n{\n\t// first, verify to what extent the external and internal parts are the same\n\tbool have_l = external.X != internal.X;\n\tbool have_t = external.Y != internal.Y;\n\tbool have_r = external.X + external.Width != internal.X + internal.Width;\n\tbool have_b = external.Y + external.Height != internal.Y + internal.Height;\n\n\n\t// render a modern background: first the tile, then the border (if it exists)\n\n\t// top\n\tif(have_t)\n\t{\n\t\tDrawTextureTiled(external.X,\n\t\t\texternal.Y,\n\t\t\texternal.Width,\n\t\t\tinternal.Y - external.Y,\n\t\t\ttile);\n\t}\n\t// left (excl top)\n\tif(have_l)\n\t{\n\t\tDrawTextureTiled(external.X,\n\t\t\tinternal.Y,\n\t\t\tinternal.X - external.X,\n\t\t\texternal.Y + external.Height - internal.Y,\n\t\t\ttile);\n\t}\n\t// right (excl top)\n\tif(have_r)\n\t{\n\t\tDrawTextureTiled(internal.X + internal.Width,\n\t\t\tinternal.Y,\n\t\t\t(external.X + external.Width) - (internal.X + internal.Width),\n\t\t\texternal.Y + external.Height - internal.Y,\n\t\t\ttile);\n\t}\n\t// bottom (excl left and right)\n\tif(have_b)\n\t{\n\t\tDrawTextureTiled(internal.X,\n\t\t\tinternal.Y + internal.Height,\n\t\t\tinternal.Width,\n\t\t\t(external.Y + external.Height) - (internal.Y + internal.Height),\n\t\t\ttile);\n\t}\n\n\tif(!border || !border->tex.inited)\n\t\treturn;\n\n\tFrameBorder& i = *border;\n\n\t// zero: check if the `ini` is invalid\n\tif(i.le + i.li + i.ri + i.re > i.tex.w)\n\t\treturn;\n\tif(i.te + i.ti + i.bi + i.be > i.tex.h)\n\t\treturn;\n\n\tint li = have_l ? i.li : 0;\n\tint ti = have_t ? i.ti : 0;\n\tint ri = have_r ? i.ri : 0;\n\tint bi = have_b ? i.bi : 0;\n\n\t// top external-left external\n\tif(have_l && have_t && i.le && i.te)\n\t{\n\t\tXRender::renderTextureBasic(internal.X - i.le, internal.Y - i.te, i.le, i.te,\n\t\t    i.tex,\n\t\t    0, 0);\n\t}\n\t// top external-left internal\n\tif(have_l && have_t && i.li && i.te)\n\t{\n\t\tXRender::renderTextureBasic(internal.X, internal.Y - i.te, i.li, i.te,\n\t\t    i.tex,\n\t\t    i.le, 0);\n\t}\n\t// top external-center\n\tif(have_t && i.te && internal.Width - li - ri > 0)\n\t{\n\t\tDrawTextureTiled(internal.X + li, internal.Y - i.te, internal.Width - li - ri, i.te,\n\t\t    i.tex,\n\t\t    i.le + i.li, 0,\n\t\t    i.tex.w - i.le - i.li - i.ri - i.re, i.te,\n\t\t    0, 0);\n\t}\n\t// top external-right internal\n\tif(have_t && have_r && i.ri && i.te)\n\t{\n\t\tXRender::renderTextureBasic(internal.X + internal.Width - i.ri, internal.Y - i.te, i.ri, i.te,\n\t\t    i.tex,\n\t\t    i.tex.w - i.ri - i.re, 0);\n\t}\n\t// top external-right external\n\tif(have_t && have_r && i.re && i.te)\n\t{\n\t\tXRender::renderTextureBasic(internal.X + internal.Width, internal.Y - i.te, i.re, i.te,\n\t\t    i.tex,\n\t\t    i.tex.w - i.re, 0);\n\t}\n\t// top internal-left external\n\tif(have_t && have_l && i.le && i.ti)\n\t{\n\t\tXRender::renderTextureBasic(internal.X - i.le, internal.Y, i.le, i.ti,\n\t\t    i.tex,\n\t\t    0, i.te);\n\t}\n\t// center-left external\n\tif(have_l && i.le && internal.Height - ti - bi > 0)\n\t{\n\t\tDrawTextureTiled(internal.X - i.le, internal.Y + ti, i.le, internal.Height - ti - bi,\n\t\t    i.tex,\n\t\t    0, i.te + i.ti,\n\t\t    i.le,\n\t\t    i.tex.h - i.te - i.ti - i.bi - i.be,\n\t\t    0, 0);\n\t}\n\t// bottom internal-left external\n\tif(have_b && have_l && i.le && i.bi)\n\t{\n\t\tXRender::renderTextureBasic(internal.X - i.le, internal.Y + internal.Height - i.bi, i.le, i.bi,\n\t\t    i.tex,\n\t\t    0, i.tex.h - i.bi - i.be);\n\t}\n\t// bottom external-left external\n\tif(have_b && have_l && i.le && i.be)\n\t{\n\t\tXRender::renderTextureBasic(internal.X - i.le, internal.Y + internal.Height, i.le, i.be,\n\t\t    i.tex,\n\t\t    0, i.tex.h - i.be);\n\t}\n\t// bottom external-left internal\n\tif(have_b && have_l && i.li && i.be)\n\t{\n\t\tXRender::renderTextureBasic(internal.X, internal.Y + internal.Height, i.li, i.be,\n\t\t    i.tex,\n\t\t    i.le, i.tex.h - i.be);\n\t}\n\t// bottom external-center\n\tif(have_b && i.be && internal.Width - li - ri > 0)\n\t{\n\t\tDrawTextureTiled(internal.X + li, internal.Y + internal.Height, internal.Width - li - ri, i.be,\n\t\t    i.tex,\n\t\t    i.le + i.li, i.tex.h - i.be, i.tex.w - i.le - i.li - i.ri - i.re, i.be,\n\t\t    0, 0);\n\t}\n\t// bottom external-right internal\n\tif(have_b && have_r && i.ri && i.be)\n\t{\n\t\tXRender::renderTextureBasic(internal.X + internal.Width - i.ri, internal.Y + internal.Height, i.ri, i.be,\n\t\t    i.tex,\n\t\t    i.tex.w - i.ri - i.re, i.tex.h - i.be);\n\t}\n\t// bottom external-right external\n\tif(have_b && have_r && i.re && i.be)\n\t{\n\t\tXRender::renderTextureBasic(internal.X + internal.Width, internal.Y + internal.Height, i.re, i.be,\n\t\t    i.tex,\n\t\t    i.tex.w - i.re, i.tex.h - i.be);\n\t}\n\t// top internal-right external\n\tif(have_t && have_r && i.re && i.ti)\n\t{\n\t\tXRender::renderTextureBasic(internal.X + internal.Width, internal.Y, i.re, i.ti,\n\t\t    i.tex,\n\t\t    i.tex.w - i.re, i.te);\n\t}\n\t// center-right external\n\tif(have_r && i.re && internal.Height - ti - bi)\n\t{\n\t\tDrawTextureTiled(internal.X + internal.Width, internal.Y + ti, i.re, internal.Height - ti - bi,\n\t\t    i.tex,\n\t\t    i.tex.w - i.re, i.te + i.ti,\n\t\t    i.re, i.tex.h - i.te - i.ti - i.bi - i.be,\n\t\t    0, 0);\n\t}\n\t// bottom internal-right external\n\tif(have_b && have_r && i.bi && i.re)\n\t{\n\t\tXRender::renderTextureBasic(internal.X + internal.Width, internal.Y + internal.Height - i.bi, i.re, i.bi,\n\t\t    i.tex,\n\t\t    i.tex.w - i.re, i.tex.h - i.bi - i.be);\n\t}\n}\n\n// renders a new-style frame as a fill at the included internal location\n// the frame fills the internal rect\nvoid RenderFrameFill(const IntegerLocation_t& internal, FrameBorder& frame, XTColor color)\n{\n\tFrameBorder& i = frame;\n\n\t// zero: check if the `ini` is invalid\n\tif(i.le + i.li + i.ri + i.re > i.tex.w)\n\t\treturn;\n\tif(i.te + i.ti + i.bi + i.be > i.tex.h)\n\t\treturn;\n\n\t// top internal-left internal\n\tif(i.li && i.ti)\n\t{\n\t\tXRender::renderTextureBasic(internal.X, internal.Y, i.li, i.ti,\n\t\t    i.tex,\n\t\t    i.le, i.te,\n\t\t    color);\n\t}\n\t// top internal-center\n\tif(i.ti && internal.Width - i.li - i.ri > 0)\n\t{\n\t\tDrawTextureTiled(internal.X + i.li, internal.Y, internal.Width - i.li - i.ri, i.ti,\n\t\t    i.tex,\n\t\t    i.le + i.li, i.te,\n\t\t    i.tex.w - i.le - i.li - i.ri - i.re,\n\t\t    i.ti,\n\t\t    0, 0,\n\t\t    color);\n\t}\n\t// top internal-right internal\n\tif(i.ri && i.ti)\n\t{\n\t\tXRender::renderTextureBasic(internal.X + internal.Width - i.ri, internal.Y, i.ri, i.ti,\n\t\t    i.tex,\n\t\t    i.tex.w - i.ri - i.re, i.te,\n\t\t    color);\n\t}\n\t// center-left internal\n\tif(i.li && internal.Height - i.ti - i.bi > 0)\n\t{\n\t\tDrawTextureTiled(internal.X, internal.Y + i.ti, i.li, internal.Height - i.ti - i.bi,\n\t\t    i.tex,\n\t\t    i.le, i.te + i.ti,\n\t\t    i.li,\n\t\t    i.tex.h - i.te - i.ti - i.bi - i.be,\n\t\t    0, 0,\n\t\t    color);\n\t}\n\t// bottom internal-left internal\n\tif(i.li && i.bi)\n\t{\n\t\tXRender::renderTextureBasic(internal.X, internal.Y + internal.Height - i.bi, i.li, i.bi,\n\t\t    i.tex,\n\t\t    i.le,\n\t\t    i.tex.h - i.bi - i.be,\n\t\t    color);\n\t}\n\t// bottom internal-center\n\tif(i.bi && internal.Width - i.li - i.ri > 0)\n\t{\n\t\tDrawTextureTiled(internal.X + i.li, internal.Y + internal.Height - i.bi, internal.Width - i.li - i.ri, i.bi,\n\t\t    i.tex,\n\t\t    i.le + i.li, i.tex.h - i.be - i.bi, i.tex.w - i.le - i.li - i.ri - i.re, i.bi,\n\t\t    0, 0,\n\t\t    color);\n\t}\n\t// bottom internal-right internal\n\tif(i.ri && i.bi)\n\t{\n\t\tXRender::renderTextureBasic(internal.X + internal.Width - i.ri, internal.Y + internal.Height - i.bi, i.ri, i.bi,\n\t\t    i.tex,\n\t\t    i.tex.w - i.ri - i.re, i.tex.h - i.bi - i.be,\n\t\t    color);\n\t}\n\t// center-right internal\n\tif(i.ri && internal.Height - i.ti - i.bi)\n\t{\n\t\tDrawTextureTiled(internal.X + internal.Width - i.ri, internal.Y + i.ti, i.ri, internal.Height - i.ti - i.bi,\n\t\t    i.tex,\n\t\t    i.tex.w - i.re - i.ri, i.te + i.ti,\n\t\t    i.ri, i.tex.h - i.te - i.ti - i.bi - i.be,\n\t\t    0, 0,\n\t\t    color);\n\t}\n\t// center\n\tif(internal.Width - i.li - i.ri > 0 && internal.Height - i.ti - i.bi > 0)\n\t{\n\t\tDrawTextureTiled(internal.X + i.li, internal.Y + i.ti, internal.Width - i.li - i.ri, internal.Height - i.ti - i.bi,\n\t\t    i.tex,\n\t\t    i.le + i.li, i.te + i.ti,\n\t\t    i.tex.w - i.le - i.li - i.ri - i.re, i.tex.h - i.te - i.ti - i.bi - i.be,\n\t\t    0, 0,\n\t\t    color);\n\t}\n}\n\n// renders a simple colored frame, optimized for an opaque frame\nvoid DrawSimpleFrameOpaque(int x, int y, int w, int h, XTColor border_1, XTColor border_2, XTColor fill)\n{\n\tint bg_l = x;\n\tint bg_r = x + w;\n\tint bg_t = y;\n\tint bg_b = y + h;\n\n\t// border 1\n\tXRender::renderRect(bg_l, bg_t, bg_r - bg_l, bg_b - bg_t, border_1);\n\n\t// border 2\n\tXRender::renderRect(bg_l + 2, bg_t + 2, bg_r - bg_l - 4, bg_b - bg_t - 4, border_2);\n\n\t// fill\n\tXRender::renderRect(bg_l + 4, bg_t + 4, bg_r - bg_l - 8, bg_b - bg_t - 8, fill);\n}\n\n// renders a simple colored frame\nvoid DrawSimpleFrame(int x, int y, int w, int h, XTColor border_1, XTColor border_2, XTColor fill)\n{\n\tint bg_l = x;\n\tint bg_r = x + w;\n\tint bg_t = y;\n\tint bg_b = y + h;\n\n\t// border 1\n\tXRender::renderRect(bg_l, bg_t, 2, bg_b - bg_t, border_1);\n\tXRender::renderRect(bg_r - 2, bg_t, 2, bg_b - bg_t, border_1);\n\tXRender::renderRect(bg_l + 2, bg_t, bg_r - bg_l - 4, 2, border_1);\n\tXRender::renderRect(bg_l + 2, bg_b - 2, bg_r - bg_l - 4, 2, border_1);\n\n\t// border 2\n\tXRender::renderRect(bg_l + 2, bg_t + 2, 2, bg_b - bg_t - 4, border_2);\n\tXRender::renderRect(bg_r - 4, bg_t + 2, 2, bg_b - bg_t - 4, border_2);\n\tXRender::renderRect(bg_l + 4, bg_t + 2, bg_r - bg_l - 8, 2, border_2);\n\tXRender::renderRect(bg_l + 4, bg_b - 4, bg_r - bg_l - 8, 2, border_2);\n\n\t// fill\n\tif(fill.a)\n\t\tXRender::renderRect(bg_l + 4, bg_t + 4, bg_r - bg_l - 8, bg_b - bg_t - 8, fill);\n}\n"
  },
  {
    "path": "src/graphics/gfx_frame.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GFX_FRAME_H\n#define GFX_FRAME_H\n\n#include \"location.h\"\n#include \"std_picture.h\"\n\nstruct FrameBorderInfo\n{\n\t// top, left, bottom, and right, external and internal\n\tint te = 0;\n\tint ti = 0;\n\tint le = 0;\n\tint li = 0;\n\tint be = 0;\n\tint bi = 0;\n\tint re = 0;\n\tint ri = 0;\n};\n\nstruct FrameBorder : public FrameBorderInfo\n{\n\tStdPicture tex;\n};\n\nclass IniProcessing;\n\nvoid loadFrameInfo(IniProcessing& ini, FrameBorderInfo& borderinfo);\n\nvoid DrawTextureTiled(int dst_x, int dst_y, int dst_w, int dst_h, StdPicture& tx, int src_x = 0, int src_y = 0, int src_w = -1, int src_h = -1, int off_x = -1, int off_y = -1, XTColor color = XTColor());\n\n// renders a new-style frame as a border with the included external and internal locations\n// the frame fills the space between external and internal\n// border is nullable\nvoid RenderFrameBorder(const IntegerLocation_t& external, const IntegerLocation_t& internal,\n\tStdPicture& tile, FrameBorder* border);\n\n// renders a new-style frame as a fill at the included internal location\n// the frame fills the internal rect\nvoid RenderFrameFill(const IntegerLocation_t& internal, FrameBorder& frame, XTColor color = XTColor());\n\n// renders a simple colored frame, optimized for an opaque frame\nvoid DrawSimpleFrameOpaque(int x, int y, int w, int h, XTColor border_1, XTColor border_2, XTColor fill);\n\n// renders a simple colored frame\nvoid DrawSimpleFrame(int x, int y, int w, int h, XTColor border_1, XTColor border_2, XTColor fill);\n\n#endif // GFX_FRAME_H\n"
  },
  {
    "path": "src/graphics/gfx_hud.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <fmt_format_ne.h>\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"../globals.h\"\n#include \"../graphics.h\"\n#include \"../core/render.h\"\n#include \"../core/power.h\"\n#include \"../gfx.h\"\n#include \"npc_traits.h\"\n#include \"eff_id.h\"\n\n#include \"main/speedrunner.h\"\n\n#include \"config.h\"\n#include \"main/level_medals.h\"\n#include \"main/screen_asset_pack.h\"\n\nvoid DrawInterface(int Z, int numScreens)\n{\n    int ScreenTop = 0;\n    if(vScreen[Z].Height > 600)\n        ScreenTop = vScreen[Z].Height / 2 - 300;\n    int CenterX = vScreen[Z].Width / 2;\n\n    const Screen_t& screen = Screens[vScreen[Z].screen_ref];\n\n    // note: to_string doesn't belong in per-frame code, replace with faster implementation soon\n    std::string scoreStr = std::to_string(Score);\n    std::string coinsStr = std::to_string(Coins);\n    std::string numStarsStr = std::to_string(numStars);\n\n    bool is_multiplayer = screen.player_count > 1;\n    bool shared_screen = (is_multiplayer && numScreens == 1 && screen.Type != 6);\n\n    // number of players to render on this vScreen\n    int plr_count = shared_screen ? screen.player_count : 1;\n\n    // do any of the players on this vScreen have a key?\n    bool someone_has_key = false;\n\n    // first onscreen player index, affects coin icon and battle mode lives\n    int first_plr_idx = 0;\n\n    // bigger player HUD sizes in 1P/2P for compatibility purposes\n    int plr_hud_width = plr_count == 1 ? 148 : (114 * plr_count);\n    if(plr_count > 2)\n        plr_hud_width = 84 * plr_count;\n\n    // left and right sides of hearts / held item box section\n    int left_margin = CenterX - plr_hud_width / 2;\n    int right_margin = CenterX + plr_hud_width / 2;\n\n    // loop over players and draw them\n    for(int i = 0; i < plr_count; i++)\n    {\n        // look up player index using either screen (shared) or vScreen (non-shared)\n        int plr_idx = shared_screen ? screen.players[i] : vScreen[Z].player;\n        Player_t& plr = Player[plr_idx];\n\n        if(i == 0)\n            first_plr_idx = plr_idx;\n\n        // find center for player HUD info; this is the center of the i'th portion of the HUD (out of plr_count portions)\n        int plr_center = left_margin + (plr_hud_width * (i * 2 + 1)) / (plr_count * 2);\n\n        // show hearts\n        if(plr.Character == 3 || plr.Character == 4 || plr.Character == 5)\n        {\n            auto& heart_1_gfx = plr.Hearts > 0 ? GFX.Heart[1] : GFX.Heart[2];\n            auto& heart_2_gfx = plr.Hearts > 1 ? GFX.Heart[1] : GFX.Heart[2];\n            auto& heart_3_gfx = plr.Hearts > 2 ? GFX.Heart[1] : GFX.Heart[2];\n\n            int heart_offset = (plr_count > 2) ? 24 : 32;\n\n            XRender::renderTextureBasic(plr_center - heart_1_gfx.w / 2 - heart_offset, ScreenTop + 16, heart_1_gfx);\n            XRender::renderTextureBasic(plr_center - heart_2_gfx.w / 2               , ScreenTop + 16, heart_2_gfx);\n            XRender::renderTextureBasic(plr_center - heart_3_gfx.w / 2 + heart_offset, ScreenTop + 16, heart_3_gfx);\n        }\n        // show nothing\n        else if(g_config.alt_powerdown) {}\n        // show held bonus (item box)\n        else\n        {\n            // held bonus offset, bring it towards the screen center in 2P\n            if(plr_count == 2)\n                plr_center += (i == 0) ? 17 : -17;\n\n            int use_container = plr.Character;\n\n            if(!is_multiplayer || use_container > 2)\n                use_container = 0;\n\n            XRender::renderTextureBasic(plr_center - GFX.Container[1].w / 2, ScreenTop + 16, GFX.Container[use_container]);\n\n            if(plr.HeldBonus > 0)\n            {\n                int x = plr_center - GFX.Container[1].w / 2 + 12;\n                int y = ScreenTop + 16 + 12;\n                int w = NPCWidth(plr.HeldBonus);\n                int h = NPCHeight(plr.HeldBonus);\n\n                if(g_config.fix_visual_bugs)\n                {\n                    if(NPCWidthGFX(plr.HeldBonus))\n                    {\n                        w = NPCWidthGFX(plr.HeldBonus);\n                        h = NPCHeightGFX(plr.HeldBonus);\n                    }\n                    x += 16 - w / 2;\n                    y += 16 - h / 2;\n                }\n\n                XRender::renderTextureBasic(x, y, w, h, GFXNPC[plr.HeldBonus], 0, 0);\n            }\n        }\n\n        // show bombs\n        if(plr.Character == 5 && plr.Bombs > 0)\n        {\n            // offset for bomb text, just for compatibility\n            int off = (plr_count == 2) ? -1 : 0;\n\n            XRender::renderTextureBasic(20 + plr_center - 28 - 34 + off, ScreenTop + 52,\n                GFX.Interface[8]);\n            XRender::renderTextureBasic(20 + plr_center - 28 - 10 + off, ScreenTop + 53,\n                GFX.Interface[1]);\n            SuperPrint(std::to_string(plr.Bombs), 1,\n                       20 + plr_center - 28 + 12 + off,\n                       ScreenTop + 53);\n        }\n\n        if(plr.HasKey)\n            someone_has_key = true;\n    }\n\n    // slightly expand margins for >2P\n    if(plr_count > 2)\n    {\n        left_margin -= 4;\n        right_margin += 12;\n    }\n\n    // draw non-battle mode info\n    if(!BattleMode)\n    {\n        int coins_x = right_margin + 14;\n\n        // Indicate key\n        if(someone_has_key)\n            XRender::renderTextureBasic(coins_x - 24, ScreenTop + 16 + 10, GFX.Interface[0]);\n\n        // Print coins on the screen\n        auto& coin_icon = (Player[first_plr_idx].Character == 5) ? GFX.Interface[6] : GFX.Interface[2];\n        XRender::renderTextureBasic(coins_x, ScreenTop + 16 + 10, coin_icon);\n\n        XRender::renderTextureBasic(coins_x + coin_icon.w + 8, ScreenTop + 16 + 11, GFX.Interface[1]);\n\n        // note: the 36px at end gives 8px of padding between \"x\" icon and text when there are 2 digits\n        int coins_score_text_right = (coins_x + coin_icon.w + 8 + GFX.Interface[1].w + 8 + 36);\n\n        int coins_length = int(coinsStr.size()) * 18;\n        SuperPrint(coinsStr, 1,\n                   coins_score_text_right - coins_length,\n                   ScreenTop + 16 + 11);\n\n        // Print score below coins (match right align)\n        int score_length = int(scoreStr.size()) * 18;\n        SuperPrint(scoreStr, 1,\n                   coins_score_text_right - score_length,\n                   ScreenTop + 16 + 31);\n\n        // Print lives on the screen\n        int lives_stars_x = left_margin - 60; // excludes life / star icon width\n        int lives_stars_text_left = lives_stars_x + 8 + GFX.Interface[1].w + 8;\n\n        DrawLives(lives_stars_x, ScreenTop + 16 + 10, Lives, g_100s);\n\n        // Print stars on the screen\n        if(numStars > 0)\n        {\n            XRender::renderTextureBasic(lives_stars_x - GFX.Interface[5].w, ScreenTop + 16 + 30, GFX.Interface[5]);\n            XRender::renderTextureBasic(lives_stars_x + 8, ScreenTop + 16 + 31, GFX.Interface[1]);\n\n            SuperPrint(numStarsStr, 1,\n                       lives_stars_text_left,\n                       ScreenTop + 16 + 31);\n        }\n\n        // draw medals at top-right side of HUD\n        int medals_x = SDL_min(static_cast<int>(vScreen[Z].Width), CenterX + 320) - 16;\n        int medals_y = ScreenTop + 32;\n\n        if(vScreen[Z].Width > 440 && vScreen[Z].Width < 640)\n            medals_x = coins_score_text_right;\n\n        if(vScreen[Z].Width < 640 || plr_count > 2)\n            medals_y = ScreenTop + 4;\n\n        DrawMedals(medals_x, medals_y, false, g_curLevelMedals.max, g_curLevelMedals.prev, 0, g_curLevelMedals.got, g_curLevelMedals.life);\n    }\n    // draw both battle mode lives\n    else if(shared_screen)\n    {\n        // plr 1 lives\n        int plr1_lives_x = left_margin - 60; // excludes life icon width\n        XRender::renderTextureBasic(plr1_lives_x - GFX.Interface[3].w, ScreenTop + 16 + 10, GFX.Interface[3]);\n        XRender::renderTextureBasic(plr1_lives_x + 8, ScreenTop + 16 + 11, GFX.Interface[1]);\n        SuperPrint(std::to_string(BattleLives[screen.players[0]]), 1,\n                   plr1_lives_x + 8 + GFX.Interface[1].w + 8,\n                   ScreenTop + 16 + 11);\n\n        // plr 2 lives\n        int plr2_lives_x = right_margin + 30; // excludes life icon width\n        XRender::renderTextureBasic(plr2_lives_x - GFX.Interface[7].w, ScreenTop + 16 + 10, GFX.Interface[7]);\n        XRender::renderTextureBasic(plr2_lives_x + 8, ScreenTop + 16 + 11, GFX.Interface[1]);\n        SuperPrint(std::to_string(BattleLives[screen.players[1]]), 1,\n                   plr2_lives_x + 8 + GFX.Interface[1].w + 8,\n                   ScreenTop + 16 + 11);\n    }\n    // draw 1P battle mode lives\n    else\n    {\n        // lives in battle mode\n        int lives_x = left_margin - 60; // excludes life icon width\n        auto& oneup_twoup = (first_plr_idx == 2 && numPlayers == 2) ? GFX.Interface[7] : GFX.Interface[3];\n\n        XRender::renderTextureBasic(lives_x - oneup_twoup.w,\n                              ScreenTop + 16 + 10,\n                              oneup_twoup);\n\n        XRender::renderTextureBasic(lives_x + 8,\n            ScreenTop + 16 + 11,\n            GFX.Interface[1]);\n\n        SuperPrint(std::to_string(BattleLives[first_plr_idx]), 1,\n                   lives_x + 8 + GFX.Interface[1].w + 8,\n                   ScreenTop + 16 + 11);\n    }\n\n    if(BattleIntro > 0)\n    {\n        if(BattleIntro > 45 || BattleIntro % 2 == 1)\n        {\n            int nextPlr = 1;\n\n            int nextY = -96 + vScreen[Z].Height / 2 - 24 * (numPlayers / 2 - 1);\n\n            while(numPlayers > nextPlr)\n            {\n                auto& P1_charname = GFX.CharacterName[Player[nextPlr].Character];\n                auto& P2_charname = GFX.CharacterName[Player[nextPlr + 1].Character];\n\n                int w = P1_charname.w + P2_charname.w + GFX.BMVs.w + 16 * 2;\n                if(nextPlr > 1)\n                    w += GFX.BMVs.w + 16;\n\n                int l = vScreen[Z].Width / 2 - w / 2;\n\n                if(nextPlr > 1)\n                {\n                    XRender::renderTextureBasic(l, nextY - GFX.BMVs.h / 2, GFX.BMVs);\n                    l += GFX.BMVs.w + 16;\n                }\n\n                XRender::renderTextureBasic(l, nextY - P1_charname.h / 2, P1_charname);\n                l += P1_charname.w + 16;\n\n                XRender::renderTextureBasic(l, nextY - GFX.BMVs.h / 2, GFX.BMVs);\n                l += GFX.BMVs.w + 16;\n\n                XRender::renderTextureBasic(l, nextY - P2_charname.h / 2, P2_charname);\n\n                nextY += 48;\n                nextPlr += 2;\n            }\n\n            // final row if odd number of players\n            if(numPlayers == nextPlr)\n            {\n                auto& last_charname = GFX.CharacterName[Player[nextPlr].Character];\n\n                int w = last_charname.w + GFX.BMVs.w + 16;\n                int l = vScreen[Z].Width / 2 - w / 2;\n\n                XRender::renderTextureBasic(l, nextY - GFX.BMVs.h / 2, GFX.BMVs);\n                XRender::renderTextureBasic(l + GFX.BMVs.w + 16, nextY - last_charname.h / 2, last_charname);\n            }\n        }\n    }\n\n    if(BattleOutro > 0)\n    {\n        auto& win_charname = GFX.CharacterName[Player[BattleWinner].Character];\n\n        XRender::renderTextureBasic(10 + (int)vScreen[Z].Width / 2, -96 + (int)vScreen[Z].Height / 2 - GFX.BMWin.h / 2, GFX.BMWin);\n        XRender::renderTextureBasic(-10 + (int)vScreen[Z].Width / 2 - win_charname.w, -96 + (int)vScreen[Z].Height / 2 - win_charname.h / 2, win_charname);\n    }\n}\n\nvoid DrawLives(int X, int Y, int lives, int hunds, bool force_lives)\n{\n    bool show_times = true;\n    int count = 0;\n    int text_X = X + 8 + GFX.Interface[1].w + 8;\n\n    if(g_config.modern_lives_system && !force_lives)\n    {\n        bool debt = (hunds < 0);\n        count = (debt) ? -hunds : hunds;\n\n        if(GFX.Balance.inited)\n            XRender::renderTextureBasic(X - GFX.Balance.w / 2, Y, GFX.Balance.w / 2, GFX.Balance.h, GFX.Balance, debt * GFX.Balance.w / 2, 0);\n        else\n        {\n            XRender::renderTextureBasic(X - GFX.Interface[2].w,      Y, GFX.Interface[2], XTColor(0x7F, 0x7F * !debt, 0x7F * !debt));\n            XRender::renderTextureBasic(X - GFX.Interface[2].w - 8,  Y, GFX.Interface[2], XTColor(0xBF, 0xBF * !debt, 0xBF * !debt));\n            XRender::renderTextureBasic(X - GFX.Interface[2].w - 16, Y, GFX.Interface[2], XTColor(0xFF, 0xFF * !debt, 0xFF * !debt));\n        }\n\n        if(count >= 100)\n        {\n            show_times = false;\n            text_X -= 18;\n        }\n\n        if(count >= 1000)\n            text_X -= 10;\n    }\n    else\n    {\n        count = lives;\n        XRender::renderTextureBasic(X - GFX.Interface[3].w, Y, GFX.Interface[3]);\n    }\n\n    if(show_times)\n        XRender::renderTextureBasic(X + 8, Y + 1, GFX.Interface[1]);\n\n    SuperPrint(std::to_string(count), 1,\n               text_X,\n               Y + 1);\n}\n\nenum class MedalDrawLevel\n{\n    Off = 0, Prev, Got, Shiny\n};\n\n//! helper function to draw a single medal at a specific top-left position and acquisition level\nstatic inline void s_DrawMedal(int i, int x, int y, int coin_width, int coin_height, MedalDrawLevel level)\n{\n    if(GFX.Medals.inited)\n    {\n        XRender::renderTextureBasic(x, y, coin_width, coin_height, GFX.Medals, coin_width * (int)level, 0);\n    }\n    else\n    {\n        if(level == MedalDrawLevel::Shiny || level == MedalDrawLevel::Got)\n            XRender::renderTextureBasic(x, y, GFX.Interface[2]);\n        else if(level == MedalDrawLevel::Prev)\n            XRender::renderTextureBasic(x, y, GFX.Interface[2], XTColorF(0.5_n, 0.5_n, 0.5_n));\n        else\n            XRender::renderTextureBasic(x, y, GFX.Interface[2], XTColorF(0.5_n, 0.5_n, 0.5_n, 0.5_n));\n    }\n\n    // render sparkles for shiny\n    if(level == MedalDrawLevel::Shiny && coin_width > 8 && coin_height > 8)\n    {\n        int sparkle_1_idx = ((CommonFrame + i * 16 * 37) % 1024) / 16; // on frame 3\n\n        for(int i = 0; i < 3; ++i)\n        {\n            int sparkle_idx = (sparkle_1_idx + i) % 64;\n\n            if(sparkle_idx % 2)\n                continue;\n\n            int sparkle_frame = 2 - i;\n\n            int sparkle_X = (9 * sparkle_idx) % (coin_width - 8) + 4;\n            int sparkle_Y = (13 * (sparkle_idx % 16) + 29 * (sparkle_idx / 16)) % (coin_height - 8) + 4;\n\n            sparkle_X -= EffectWidth[EFFID_COIN_COLLECT] / 2;\n            sparkle_Y -= EffectHeight[EFFID_COIN_COLLECT] / 2;\n\n            sparkle_X &= ~1;\n            sparkle_Y &= ~1;\n\n            XRender::renderTextureBasic(x + sparkle_X, y + sparkle_Y, EffectWidth[EFFID_COIN_COLLECT], EffectHeight[EFFID_COIN_COLLECT], GFXEffect[EFFID_COIN_COLLECT], 0, EffectHeight[EFFID_COIN_COLLECT] * sparkle_frame, XTAlphaF(0.8_n));\n        }\n    }\n}\n\nvoid DrawMedals(int X, int Y, bool warp, uint8_t max, uint8_t prev, uint8_t ckpt, uint8_t got, uint8_t best)\n{\n    if(!g_config.show_medals_counter)\n        return;\n\n    if(g_config.medals_show_policy == Config_t::MEDALS_SHOW_OFF)\n        return;\n\n    if(g_config.medals_show_policy == Config_t::MEDALS_SHOW_GOT && got == 0)\n        return;\n\n    if(max == 0)\n        return;\n\n    int coin_width = GFX.Interface[2].w;\n    int coin_height = GFX.Interface[2].h;\n\n    if(GFX.Medals.inited)\n    {\n        coin_width = GFX.Medals.w / 4;\n        coin_height = GFX.Medals.h;\n    }\n\n    if(max > static_cast<uint8_t>(c_max_track_medals))\n        max = static_cast<uint8_t>(c_max_track_medals);\n\n    // don't spoil the maximum count, make it shiny if all of the discovered medals are shiny\n    bool show_max = g_config.medals_show_policy >= Config_t::MEDALS_SHOW_COUNTS;\n\n    // whether to use the shiny effect for medals; for warps, check if best is all 1s (up to bit max); in HUD, always draw shiny medals\n    bool show_shiny = (warp && show_max) ? (best == ((1 << max) - 1)) : true;\n\n    // slot-based display\n    if(g_config.medals_show_policy == Config_t::MEDALS_SHOW_FULL)\n    {\n        // position scene\n        if(warp)\n            X -= ((coin_width * max) / 2) & ~1;\n        else\n            X -= (coin_width * max);\n\n        // draw coins\n        for(uint8_t i = 0; i < max; ++i)\n        {\n            int bit = (1 << i);\n            int X_i = X + coin_width * i;\n\n            if((best & bit) && show_shiny)\n                s_DrawMedal(i, X_i, Y, coin_width, coin_height, MedalDrawLevel::Shiny);\n            else if(got & bit)\n                s_DrawMedal(i, X_i, Y, coin_width, coin_height, MedalDrawLevel::Got);\n            else if(ckpt & bit && (CommonFrame % 64) < 32)\n                s_DrawMedal(i, X_i, Y, coin_width, coin_height, MedalDrawLevel::Got);\n            else if(prev & bit)\n                s_DrawMedal(i, X_i, Y, coin_width, coin_height, MedalDrawLevel::Prev);\n            else\n                s_DrawMedal(i, X_i, Y, coin_width, coin_height, MedalDrawLevel::Off);\n        }\n\n        return;\n    }\n\n    // text-based display\n\n    // get counts\n    int best_count = 0;\n    int got_count = 0;\n    int prev_count = 0;\n\n    UNUSED(best_count);\n    UNUSED(prev_count);\n\n    for(uint8_t i = 0; i < max; ++i)\n    {\n        int bit = (1 << i);\n\n        if(best & bit)\n            best_count++;\n        if(got & bit)\n            got_count++;\n        if(prev & bit)\n            prev_count++;\n    }\n\n    // make labels and construct / position scene\n    std::string label;\n    int total_len = 0;\n\n    if(g_config.medals_show_policy == Config_t::MEDALS_SHOW_COUNTS)\n        label = fmt::sprintf_ne(\"%d/%u\", got_count, (unsigned)max);\n    else\n        label = fmt::sprintf_ne(\"%d\", got_count);\n\n    total_len += coin_width + 8 + GFX.Interface[1].w + 4;\n    total_len += SuperTextPixLen(label, 3);\n\n    if(warp)\n        X -= (total_len / 2) & ~1;\n    else\n        X -= total_len;\n\n    // draw scene\n    s_DrawMedal(0, X, Y, coin_width, coin_height, show_shiny ? MedalDrawLevel::Shiny : MedalDrawLevel::Got);\n    X += coin_width + 8;\n    XRender::renderTextureBasic(X, Y, GFX.Interface[1]);\n    X += GFX.Interface[1].w + 4;\n    SuperPrint(label, 3, X, Y);\n}\n\nvoid DrawDeviceBattery()\n{\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    if(!g_config.fullscreen && g_config.show_battery_status == Config_t::BATTERY_STATUS_FULLSCREEN)\n        return;\n#endif\n\n    if(g_config.show_battery_status == Config_t::BATTERY_STATUS_OFF)\n        return;\n\n    XPower::StatusInfo status_info = XPower::devicePowerStatus();\n\n    if(status_info.power_status == XPower::StatusInfo::POWER_DISABLED || status_info.power_status == XPower::StatusInfo::POWER_UNKNOWN || status_info.power_status == XPower::StatusInfo::POWER_WIRED)\n        return;\n\n    bool isLow = (status_info.power_level <= 0.35_nf);\n\n    if(g_config.show_battery_status == Config_t::BATTERY_STATUS_LOW && !isLow)\n        return;\n\n    int bw = 40;\n    int bh = 22;\n    int bx = XRender::TargetW - XRender::TargetOverscanX - (bw + 8);\n    int by = (GameMenu || ScreenAssetPack::g_LoopActive) ? 24 : 8;\n\n    RenderPowerInfo(-1, bx, by, bw, bh, 255, &status_info);\n}\n"
  },
  {
    "path": "src/graphics/gfx_keyhole.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"core/render.h\"\n\n#include \"graphics/gfx_keyhole.h\"\n\nvoid RenderKeyhole(int Z)\n{\n    const unsigned keyholeMax = 192; // Was 300\n    unsigned keyholeDone = keyholeMax - 65;\n    unsigned ratio = 328 * LevelMacroCounter / keyholeDone;\n    if(ratio > 327)\n        ratio = 327;\n\n    Background_t& keyhole = Background[LevelMacroWhich];\n\n    num_t realKeyholeBottom = keyhole.Location.Y + 24;\n    num_t idealKeyholeBottom = 32 * num_t::ceil(realKeyholeBottom / 32);\n\n    // basis of 128\n    unsigned keyholeGrowthCoord = ratio;\n\n    if(keyholeGrowthCoord > 128)\n        keyholeGrowthCoord = 128;\n\n    // basis of 128\n    unsigned keyholeScale = keyholeGrowthCoord * 12;\n\n    if(ratio < 164)\n        keyholeScale += (128 - keyholeGrowthCoord);\n\n    num_t keyholeBottom = (realKeyholeBottom * (128 - keyholeGrowthCoord) + idealKeyholeBottom * keyholeGrowthCoord) / 128;\n\n    RenderTexturePlayerScale(Z, Player[0],\n        num_t::round(vScreen[Z].X + keyhole.Location.X + keyhole.Location.Width / 2 - keyhole.Location.Width * keyholeScale / 256),\n        num_t::round(vScreen[Z].Y + keyholeBottom - 24 * keyholeScale / 128),\n        (int)(keyhole.Location.Width) * keyholeScale / 128,\n        (int)(keyhole.Location.Height) * keyholeScale / 128,\n        GFXBackgroundBMP[keyhole.Type],\n        0,\n        0,\n        (int)(keyhole.Location.Width),\n        (int)(keyhole.Location.Height));\n\n    // hide the real keyhole underneath\n    if(ratio >= 164)\n        keyhole.Hidden = true;\n}\n\n// WARP LOGIC: moved from PlayerWarpGFX (graphics.cpp)\nstatic inline bool s_warp_gfx(int Z, const Player_t& p,\n                         int& dst_x, int& dst_y, int& dst_w, int& dst_h,\n                         int& src_x, int& src_y)\n{\n    // .Effect = 3      -- Warp Pipe\n    // .Effect2 = 0     -- Entering\n    // .Effect2 = 1     -- Move to next spot\n    // .Effect2 => 100  -- Delay at next spot\n    // .Effect2 = 2     -- Exiting\n    // .Effect2 = 3     -- Done\n\n    if(p.Effect2 == 1 || p.Effect2 >= 100)\n        return true;\n\n    if(p.Effect2 == 0 || p.Effect2 == 2)\n    {\n        num_t camX = vScreen[Z].CameraAddX();\n        num_t camY = vScreen[Z].CameraAddY();\n\n        bool use_exit = (p.WarpBackward) == (p.Effect2 == 0);\n        const Warp_t& warp = Warp[p.Warp];\n        const SpeedlessLocation_t& warp_loc = (use_exit) ? warp.Exit : warp.Entrance;\n        auto warp_dir = (use_exit) ? warp.Direction2 : warp.Direction;\n\n        int warp_l = num_t::floor(warp_loc.X + camX);\n        int warp_r = warp_l + num_t::floor(warp_loc.Width);\n        int warp_t = num_t::floor(warp_loc.Y + camY);\n        int warp_b = warp_t + num_t::floor(warp_loc.Height);\n\n        if(warp_dir == 3) // warp below player\n        {\n            if(dst_h > warp_b - dst_y)\n                dst_h = warp_b - dst_y;\n        }\n        else if(warp_dir == 1) // warp above player\n        {\n            if(dst_y < warp_t)\n            {\n                src_y += (warp_t - dst_y);\n                dst_h -= (warp_t - dst_y);\n                dst_y = warp_t;\n            }\n        }\n        else if(warp_dir == 4) // warp to right of player\n        {\n            if(dst_w > warp_r - dst_x)\n                dst_w = warp_r - dst_x;\n        }\n        else if(warp_dir == 2) // warp to left of player\n        {\n            if(dst_x < warp_l)\n            {\n                src_x += (warp_l - dst_x);\n                dst_w -= (warp_l - dst_x);\n                dst_x = warp_l;\n            }\n        }\n\n        if(dst_w < 0 || dst_h < 0)\n            return true;\n    }\n\n    return false;\n}\n\nvoid RenderTexturePlayerScale(int Z, const Player_t& p,\n                              int dst_x, int dst_y, int dst_w, int dst_h,\n                              StdPicture& tex,\n                              int src_x, int src_y, int src_w, int src_h,\n                              XTColor color)\n{\n    if(LevelMacro != LEVELMACRO_KEYHOLE_EXIT || LevelMacroWhich == 0)\n    {\n        if(src_w == -1 || src_h == -1)\n        {\n            if(p.Effect == PLREFF_WARP_PIPE)\n            {\n                // apply warp to bounding box and return if sprite should not be drawn\n                if(s_warp_gfx(Z, p, dst_x, dst_y, dst_w, dst_h, src_x, src_y))\n                    return;\n            }\n\n            return XRender::renderTextureBasic(dst_x, dst_y, dst_w, dst_h, tex, src_x, src_y, color);\n        }\n        else\n            return XRender::renderTextureScaleEx(dst_x, dst_y, dst_w, dst_h, tex, src_x, src_y, src_w, src_h, 0, nullptr, X_FLIP_NONE, color);\n    }\n\n    if(src_w == -1 || src_h == -1)\n    {\n        src_w = dst_w;\n        src_h = dst_h;\n    }\n\n    const unsigned keyholeMax = 192; // Was 300\n    unsigned keyholeDone = keyholeMax - 65;\n    unsigned ratio = 328 * LevelMacroCounter / keyholeDone;\n    if(ratio > 327)\n        ratio = 327;\n\n    Background_t& keyhole = Background[LevelMacroWhich];\n\n    // basis of 128\n    int scale = (ratio < 199) ? 128 : (327 - ratio);\n\n    num_t cx = vScreen[Z].X + keyhole.Location.X + keyhole.Location.Width / 2;\n    num_t cy = vScreen[Z].Y + keyhole.Location.Y + 12;\n\n    return XRender::renderTextureScaleEx(\n                (int)(dst_x * scale + cx * (128 - scale)) / 128,\n                (int)(dst_y * scale + cy * (128 - scale)) / 128,\n                dst_w * scale / 128, dst_h * scale / 128,\n                tex,\n                src_x,\n                src_y,\n                src_w, src_h, 0, nullptr, X_FLIP_NONE,\n                color);\n}\n\nvoid RenderTexturePlayer(int Z, const Player_t& p,\n                         int dst_x, int dst_y, int dst_w, int dst_h,\n                         StdPicture& tex,\n                         int src_x, int src_y, XTColor color)\n{\n    RenderTexturePlayerScale(Z, p,\n        dst_x, dst_y, dst_w, dst_h,\n        tex,\n        src_x, src_y, -1, -1,\n        color);\n}\n"
  },
  {
    "path": "src/graphics/gfx_keyhole.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GFX_KEYHOLE_H\n#define GFX_KEYHOLE_H\n\n#include \"std_picture.h\"\n\nstruct Player_t;\n\nvoid RenderKeyhole(int Z);\n\nvoid RenderTexturePlayer(int Z, const Player_t& p, int dst_x, int dst_y, int dst_w, int dst_h,\n                         StdPicture& tex,\n                         int src_x = 0, int src_y = 0,\n                         XTColor color = XTColor());\n\nvoid RenderTexturePlayer(int Z, const Player_t& p, double dst_x, double dst_y, double dst_w, double dst_h,\n                         StdPicture& tex,\n                         double src_x = 0, double src_y = 0,\n                         XTColor color = XTColor()) = delete;\n\nvoid RenderTexturePlayerScale(int Z, const Player_t& p, int dst_x, int dst_y, int dst_w, int dst_h,\n                         StdPicture& tex,\n                         int src_x, int src_y, int src_w, int src_h,\n                         XTColor color = XTColor());\n\n#endif // GFX_KEYHOLE_H\n"
  },
  {
    "path": "src/graphics/gfx_marquee.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"graphics.h\"\n#include \"graphics/gfx_marquee.h\"\n\n#include \"fontman/font_manager.h\"\n\n\n// implementation for MarqueeState\n\n\nvoid MarqueeState::reset_width(uint16_t text_width)\n{\n    if(m_text_width != text_width)\n    {\n        m_text_width = text_width;\n        m_cur_frame = 0;\n    }\n}\n\nvoid MarqueeState::advance(MarqueeSpec spec)\n{\n    if(m_text_width < spec.marquee_width)\n    {\n        m_cur_frame = 0;\n        return;\n    }\n\n    m_cur_frame++;\n\n    // figure out how many frames there are to decide whether or not to wrap\n\n    // animation requires scrolling through the text\n    uint16_t scroll_pixels = m_text_width - spec.marquee_width;\n\n    // animation speed is measured in pixels per 10 frames\n    uint16_t anim_frames = (scroll_pixels * 10) / spec.anim_speed;\n\n    // there are also hold frames at start and end\n    uint16_t hold_frames = spec.hold_frames * 2;\n\n    uint16_t total_frames = anim_frames + hold_frames;\n\n    if(m_cur_frame >= total_frames)\n        m_cur_frame = 0;\n}\n\nCropInfo MarqueeState::crop_info(MarqueeSpec spec) const\n{\n    uint16_t scroll_pixels = m_text_width - spec.marquee_width;\n\n    // need to figure out current pixel in full animation sequence (fade + scroll + fade)\n    int cur_pixel = 0;\n\n    if(m_cur_frame < spec.hold_frames)\n        cur_pixel = 0;\n    else\n    {\n        int cur_frame = m_cur_frame - spec.hold_frames;\n        cur_pixel = (cur_frame * spec.anim_speed) / 10;\n    }\n\n    if(cur_pixel > scroll_pixels)\n        cur_pixel = scroll_pixels;\n\n    // begin filling in a CropInfo object\n    CropInfo crop_info;\n    crop_info.draw_width = spec.marquee_width;\n    crop_info.offset = cur_pixel;\n\n    // set fade values\n    if(cur_pixel < spec.fade_pixels)\n        crop_info.fade_left = cur_pixel;\n    else\n        crop_info.fade_left = spec.fade_pixels;\n\n    if(scroll_pixels - cur_pixel < spec.fade_pixels)\n        crop_info.fade_right = (scroll_pixels - cur_pixel);\n    else\n        crop_info.fade_right = spec.fade_pixels;\n\n    return crop_info;\n}\n\n\nvoid SuperPrintMarquee(int SuperN, const char* SuperChars, int Font, int X, int Y,\n                    MarqueeSpec marquee_spec, MarqueeState& marquee_state,\n                    XTColor color)\n{\n    bool outline = false;\n\n    int dFont = FontManager::fontIdFromSmbxFont(Font);\n    if(dFont < 0 && Font == 5)\n    {\n        Font = 4;\n        outline = true;\n\n        dFont = FontManager::fontIdFromSmbxFont(Font);\n    }\n\n    if(dFont < 0)\n    {\n        pLogWarning(\"SuperPrint: Invalid font %d is specified\", Font);\n        return; // Invalid font specified\n    }\n\n    if(!marquee_state.check_width())\n        marquee_state.reset_width(SuperTextPixLen(SuperN, SuperChars, Font));\n\n    if(marquee_state.check_width() < marquee_spec.marquee_width)\n    {\n        if(marquee_spec.align > 0)\n            X += (marquee_spec.marquee_width - marquee_state.check_width());\n        else if(marquee_spec.align == 0)\n            X += (marquee_spec.marquee_width - marquee_state.check_width()) / 2;\n\n        marquee_spec.marquee_width = marquee_state.check_width();\n    }\n\n    CropInfo ci = marquee_state.crop_info(marquee_spec);\n\n    FontManager::printText(SuperChars, SuperN, X, Y, dFont, color, FontManager::fontSizeFromSmbxFont(Font), outline, {0, 0, 0}, &ci);\n\n    // update text width if unexpected result occurred (didn't terminate while text left, terminated when no text left)\n    bool text_left = (ci.offset < marquee_state.check_width() - marquee_spec.marquee_width);\n\n    if(ci.did_terminate ^ text_left)\n    {\n        marquee_state.reset_width(SuperTextPixLen(SuperN, SuperChars, Font));\n        pLogDebug(\"Text width changed, resetting to %d\", (int)marquee_state.check_width());\n    }\n}\n\nvoid SuperPrintMarquee(const char* SuperChars, int Font, int X, int Y,\n                    MarqueeSpec marquee_spec, MarqueeState& marquee_state,\n                    XTColor color)\n{\n    int len = (int)SDL_strlen(SuperChars);\n\n    return SuperPrintMarquee(len, SuperChars, Font, X, Y,\n        marquee_spec, marquee_state,\n        color);\n}\n\nvoid SuperPrintMarquee(const std::string& SuperWords, int Font, int X, int Y,\n                    MarqueeSpec marquee_spec, MarqueeState& marquee_state,\n                    XTColor color)\n{\n    int len = (int)SuperWords.size();\n\n    return SuperPrintMarquee(len, SuperWords.c_str(), Font, X, Y,\n        marquee_spec, marquee_state,\n        color);\n}\n"
  },
  {
    "path": "src/graphics/gfx_marquee.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GFX_MARQUEE_H\n#define GFX_MARQUEE_H\n\n#include <cstdint>\n#include <string>\n\n#include \"xt_color.h\"\n#include \"fontman/crop_info.h\"\n\nstruct MarqueeSpec\n{\n    //! width of marquee\n    uint16_t marquee_width = 0;\n\n    //! speed of animation in pixels per ten frames\n    uint8_t anim_speed = 0;\n\n    //! pixels to fade text on each side\n    uint8_t fade_pixels = 0;\n\n    //! frames to hold text on left and right\n    uint8_t hold_frames = 0;\n\n    //! how text should be aligned with an unfilled marquee (-1 for left, 0 for center, and 1 for right)\n    int8_t align = 0;\n\n    inline constexpr MarqueeSpec(uint16_t marquee_width, uint8_t anim_speed, uint8_t fade_pixels, uint8_t hold_frames, int8_t align)\n        : marquee_width(marquee_width), anim_speed(anim_speed), fade_pixels(fade_pixels), hold_frames(hold_frames), align(align) {}\n};\n\nclass MarqueeState\n{\n    //! width of currently loaded text in pixels\n    uint16_t m_text_width = 0;\n\n    //! current animation frame\n    uint16_t m_cur_frame = 0;\n\npublic:\n    /**\n     * \\brief: returns marquee's text width\n     */\n    inline uint16_t check_width() const\n    {\n        return m_text_width;\n    }\n\n    /**\n     * \\brief: resets the marquee state for a new text width\n     */\n    void reset_width(uint16_t text_width = 0);\n\n    /**\n     * \\brief: advance the marquee state by one frame\n     */\n    void advance(MarqueeSpec spec);\n\n    /**\n     * \\brief: provides the current crop info for rendering\n     */\n    CropInfo crop_info(MarqueeSpec spec) const;\n};\n\nvoid SuperPrintMarquee(int SuperN, const char* SuperChars, int Font, int X, int Y, MarqueeSpec spec, MarqueeState& marquee_state, XTColor color = XTColor());\nvoid SuperPrintMarquee(const char* SuperChars, int Font, int X, int Y, MarqueeSpec spec, MarqueeState& marquee_state, XTColor color = XTColor());\nvoid SuperPrintMarquee(const std::string &SuperWords, int Font, int X, int Y, MarqueeSpec spec, MarqueeState& marquee_state, XTColor color = XTColor());\n\n#endif // GFX_MARQUEE_H\n"
  },
  {
    "path": "src/graphics/gfx_message.cpp",
    "content": "﻿#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"globals.h\"\n#include \"../graphics.h\"\n#include \"core/render.h\"\n#include \"../gfx.h\"\n#include \"../draw_planes.h\"\n#include \"../frame_timer.h\"\n#include \"../config.h\"\n\n#include \"fontman/font_manager_private.h\"\n#include \"fontman/font_manager.h\"\n\nstatic std::string s_opt_message;\nstatic int s_last_width = 0;\nstatic PGE_Size s_title_dims;\nstatic PGE_Size s_message_dims;\n\n\nvoid PrepareMessageDims()\n{\n    int TextBoxW = GFX.TextBox.w;\n\n    if(!GFX.TextBox.inited || XRender::TargetW < GFX.TextBox.w || (g_MessageType >= MESSAGE_TYPE_SYS_INFO))\n        TextBoxW = XRender::TargetW - 50;\n\n    int font = FontManager::fontIdFromSmbxFont(4);\n\n    s_opt_message = MessageText;\n    s_message_dims = FontManager::optimizeTextPx(s_opt_message, TextBoxW - 14, font);\n\n    if(!MessageTitle.empty())\n        s_title_dims = FontManager::textSize(MessageTitle.c_str(), MessageTitle.size(), font);\n\n    s_last_width = XRender::TargetH;\n}\n\n// Now uses the modern systems\nvoid DrawMessage()\n{\n    if(s_last_width != XRender::TargetH)\n        PrepareMessageDims();\n\n    int TextBoxW = GFX.TextBox.w;\n    bool UseGFX = true;\n\n    if(!GFX.TextBox.inited || XRender::TargetW < GFX.TextBox.w || (g_MessageType >= MESSAGE_TYPE_SYS_INFO))\n    {\n        TextBoxW = XRender::TargetW - 50;\n        UseGFX = false;\n    }\n\n    int BoxY_Start = XRender::TargetH / 2 - 150;\n\n    if(BoxY_Start < 60)\n    {\n        BoxY_Start = XRender::TargetH / 2 - s_message_dims.h() / 2 - 10;\n\n        if(BoxY_Start > 60)\n            BoxY_Start = 60;\n    }\n\n    // Draw the background now we know how many lines there are. 10px of padding above and below.\n    int totalHeight = s_message_dims.h() + 20;\n\n    XTColor messageColour = {8, 96, 168};\n    XTColor titleColour = {255, 255, 255};\n\n    switch(g_MessageType)\n    {\n    case MESSAGE_TYPE_SYS_INFO:\n        messageColour = {0x1D, 0x08, 0x5E};\n        titleColour = {0xf5, 0xff, 0x1a};\n        break;\n    case MESSAGE_TYPE_SYS_WARNING:\n        messageColour = {0xBF, 0x63, 0x24};\n        titleColour = {0xf5, 0xff, 0x1a};\n        break;\n    case MESSAGE_TYPE_SYS_ERROR:\n    case MESSAGE_TYPE_SYS_FATAL_ASSERT:\n        messageColour = {0x61, 0x00, 0x0F};\n        titleColour = {0xf5, 0xff, 0x1a};\n        break;\n    default:\n        messageColour = {8, 96, 168};\n        break;\n    }\n\n    if(g_MessageType >= MESSAGE_TYPE_SYS_INFO && !MessageTitle.empty())\n    {\n        int titleBoxWidth = TextBoxW > s_title_dims.w() + 12 ? TextBoxW : s_title_dims.w() + 12;\n        int titleBoxHeight = s_title_dims.h() + 20;\n        int titleBoxX = (XRender::TargetW / 2) - (titleBoxWidth / 2);\n        int titleBoxY = BoxY_Start - 40;\n\n        XRender::renderRect(titleBoxX, titleBoxY,\n                            titleBoxWidth, titleBoxHeight, {0, 0, 0});\n        XRender::renderRect(titleBoxX + 2, titleBoxY + 2,\n                            titleBoxWidth - 4, titleBoxHeight - 4, {255, 255, 255});\n        XRender::renderRect(titleBoxX + 4, titleBoxY + 4,\n                            titleBoxWidth - 8, titleBoxHeight - 8, messageColour);\n\n        SuperPrint(MessageTitle, 4, (XRender::TargetW / 2) - (s_title_dims.w() / 2), titleBoxY + 10, titleColour);\n    }\n\n    if(!UseGFX)\n    {\n        XRender::renderRect(XRender::TargetW / 2 - TextBoxW / 2,\n                            BoxY_Start,\n                            TextBoxW, totalHeight, {0, 0, 0});\n        XRender::renderRect(XRender::TargetW / 2 - TextBoxW / 2 + 2,\n                            BoxY_Start + 2,\n                            TextBoxW - 4, totalHeight - 4, {255, 255, 255});\n        XRender::renderRect(XRender::TargetW / 2 - TextBoxW / 2 + 4,\n                            BoxY_Start + 4,\n                            TextBoxW - 8, totalHeight - 8, messageColour);\n    }\n\n#ifndef BUILT_IN_TEXTBOX\n    else\n    {\n        // amount of space to fill\n        int spaceToFill = totalHeight - 20 - 20;\n\n        // amount of middle GFX that gets looped. 20px in SMBX64.\n        int gfxMidH = GFX.TextBox.h - 20 - 20;\n\n        // number of reps needed to fill space\n        int vertReps = spaceToFill / gfxMidH + 1;\n\n        // want 20px of padding at bottom if possible\n        int bottomPaddingHeight = 20;\n\n        // special case where the entire message box is under 40px tall\n        if(spaceToFill <= 0)\n        {\n            vertReps = 0;\n            bottomPaddingHeight += spaceToFill;\n            spaceToFill = 0;\n        }\n\n        // render top 20px of graphics\n        XRender::renderTextureBasic(XRender::TargetW / 2 - TextBoxW / 2,\n                               BoxY_Start,\n                               TextBoxW, 20, GFX.TextBox, 0, 0);\n\n        // loop middle of graphics\n        for(int i = 0; i < vertReps; i++)\n        {\n            if((i + 1) * gfxMidH <= spaceToFill)\n                XRender::renderTextureBasic(XRender::TargetW / 2 - TextBoxW / 2,\n                                       BoxY_Start + 20 + i * gfxMidH,\n                                       TextBoxW, gfxMidH, GFX.TextBox, 0, 20);\n            else\n                XRender::renderTextureBasic(XRender::TargetW / 2 - TextBoxW / 2,\n                                       BoxY_Start + 20 + i * gfxMidH,\n                                       TextBoxW, spaceToFill - i * gfxMidH, GFX.TextBox, 0, 20);\n        }\n\n        // render bottom of graphics\n        XRender::renderTextureBasic(XRender::TargetW / 2 - TextBoxW / 2,\n                               BoxY_Start + 20 + spaceToFill,\n                               TextBoxW, bottomPaddingHeight, GFX.TextBox, 0, GFX.TextBox.h - bottomPaddingHeight);\n    }\n#endif\n\n    // PASS TWO: draw the lines\n    int font = FontManager::fontIdFromSmbxFont(4);\n\n    int left = XRender::TargetW / 2 - TextBoxW / 2 + 12;\n\n    // single-line centering special case\n    if(s_message_dims.h() < 30)\n        left += (TextBoxW - 14) / 2 - s_message_dims.w() / 2;\n\n    FontManager::printText(s_opt_message.c_str(), s_opt_message.size(),\n                            left, BoxY_Start + totalHeight / 2 - s_message_dims.h() / 2,\n                            font);\n}\n\nvoid UpdateGraphicsFatalAssert()\n{\n    cycleNextInc();\n\n    if(g_config.enable_frameskip && !TakeScreen && frameSkipNeeded())\n        return;\n\n    XRender::setTargetTexture();\n    XRender::resetViewport();\n    XRender::setDrawPlane(PLANE_LVL_META);\n\n    if(PrintFPS > 0 && g_config.show_fps)\n        SuperPrint(std::to_string(PrintFPS), 1, XRender::TargetOverscanX + 8, 8, {0, 255, 0});\n\n    DrawMessage();\n\n    XRender::repaint();\n}\n"
  },
  {
    "path": "src/graphics/gfx_print.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"core/render.h\"\n\n#include \"globals.h\"\n#include \"graphics.h\"\n#include \"fontman/font_manager.h\"\n#include \"fontman/font_manager_private.h\"\n\n\nint SuperTextPixLen(int SuperN, const char* SuperChars, int Font)\n{\n    int dFont = FontManager::fontIdFromSmbxFont(Font);\n    if(dFont < 0 && Font == 5)\n    {\n        Font = 4;\n        dFont = FontManager::fontIdFromSmbxFont(Font);\n    }\n\n    if(dFont < 0)\n    {\n        int len = 0;\n        pLogWarning(\"SuperTextPixLen: Invalid font %d is specified\", Font);\n\n        for(int i = 0; i < SuperN; ++i)\n        {\n            len += 18;\n            i += static_cast<size_t>(trailingBytesForUTF8[static_cast<UTF8>(SuperChars[i])]);\n        }\n\n        return len;\n    }\n\n    return FontManager::textSize(SuperChars, SuperN, dFont, FontManager::fontSizeFromSmbxFont(Font)).w();\n}\n\nvoid SuperPrintRightAlign(int SuperN, const char* SuperChars, int Font, int X, int Y, XTColor color)\n{\n    int RealFont = Font;\n    bool outline = false;\n\n    int dFont = FontManager::fontIdFromSmbxFont(Font);\n    if(dFont < 0 && Font == 5)\n    {\n        Font = 4;\n        outline = true;\n\n        dFont = FontManager::fontIdFromSmbxFont(Font);\n    }\n\n    if(dFont >= 0)\n    {\n        X -= FontManager::textSize(SuperChars, SuperN, dFont, FontManager::fontSizeFromSmbxFont(Font)).w();\n        FontManager::printText(SuperChars, SuperN, X, Y, dFont, color, FontManager::fontSizeFromSmbxFont(Font), outline);\n        return;\n    }\n\n    X -= SuperTextPixLen(SuperN, SuperChars, RealFont);\n    SuperPrint(SuperN, SuperChars, RealFont, X, Y, color);\n}\n\nvoid SuperPrintCenter(int SuperN, const char* SuperChars, int Font, int X, int Y, XTColor color)\n{\n    int RealFont = Font;\n    bool outline = false;\n\n    int dFont = FontManager::fontIdFromSmbxFont(Font);\n    if(dFont < 0 && Font == 5)\n    {\n        Font = 4;\n        outline = true;\n\n        dFont = FontManager::fontIdFromSmbxFont(Font);\n    }\n\n    if(dFont >= 0)\n    {\n        X -= FontManager::textSize(SuperChars, SuperN, dFont, FontManager::fontSizeFromSmbxFont(Font)).w() / 2;\n        FontManager::printText(SuperChars, SuperN, X, Y, dFont, color, FontManager::fontSizeFromSmbxFont(Font), outline);\n        return;\n    }\n\n    X -= SuperTextPixLen(SuperN, SuperChars, RealFont) / 2;\n    SuperPrint(SuperN, SuperChars, RealFont, X, Y, color);\n}\n\nvoid SuperPrintScreenCenter(int SuperN, const char* SuperChars, int Font, int Y, XTColor color)\n{\n    int RealFont = Font;\n    bool outline = false;\n\n    int dFont = FontManager::fontIdFromSmbxFont(Font);\n    if(dFont < 0 && Font == 5)\n    {\n        Font = 4;\n        outline = true;\n\n        dFont = FontManager::fontIdFromSmbxFont(Font);\n    }\n\n    if(dFont >= 0)\n    {\n        int X = (XRender::TargetW / 2) - (FontManager::textSize(SuperChars, SuperN, dFont, FontManager::fontSizeFromSmbxFont(Font)).w() / 2);\n        FontManager::printText(SuperChars, SuperN, X, Y, dFont, color, FontManager::fontSizeFromSmbxFont(Font), outline);\n        return;\n    }\n\n    int X = (XRender::TargetW / 2) - (SuperTextPixLen(SuperN, SuperChars, RealFont) / 2);\n    SuperPrint(SuperN, SuperChars, RealFont, X, Y, color);\n}\n\nvoid SuperPrint(int SuperN, const char* SuperChars, int Font, int X, int Y,\n                XTColor color)\n{\n    bool outline = false;\n\n    int dFont = FontManager::fontIdFromSmbxFont(Font);\n    if(dFont < 0 && Font == 5)\n    {\n        Font = 4;\n        outline = true;\n\n        dFont = FontManager::fontIdFromSmbxFont(Font);\n    }\n\n    if(dFont < 0)\n    {\n        pLogWarning(\"SuperPrint: Invalid font %d is specified\", Font);\n        return; // Invalid font specified\n    }\n\n    FontManager::printText(SuperChars, SuperN, X, Y, dFont, color, FontManager::fontSizeFromSmbxFont(Font), outline);\n}\n\n// const char* versions\n\nint SuperTextPixLen(const char* SuperChars, int Font)\n{\n    int len = (int)SDL_strlen(SuperChars);\n    return SuperTextPixLen(len, SuperChars, Font);\n}\n\nvoid SuperPrintRightAlign(const char* SuperChars, int Font, int X, int Y, XTColor color)\n{\n    int len = (int)SDL_strlen(SuperChars);\n    SuperPrintRightAlign(len, SuperChars, Font, X, Y, color);\n}\n\nvoid SuperPrintCenter(const char* SuperChars, int Font, int X, int Y, XTColor color)\n{\n    int len = (int)SDL_strlen(SuperChars);\n    SuperPrintCenter(len, SuperChars, Font, X, Y, color);\n}\n\nvoid SuperPrintScreenCenter(const char* SuperChars, int Font, int Y, XTColor color)\n{\n    int len = (int)SDL_strlen(SuperChars);\n    SuperPrintScreenCenter(len, SuperChars, Font, Y, color);\n}\n\nvoid SuperPrint(const char* SuperChars, int Font, int X, int Y, XTColor color)\n{\n    int len = (int)SDL_strlen(SuperChars);\n    SuperPrint(len, SuperChars, Font, X, Y, color);\n}\n\n\n// const std::string& versions\n\nint SuperTextPixLen(const std::string& SuperWords, int Font)\n{\n    return SuperTextPixLen((int)SuperWords.size(), SuperWords.c_str(), Font);\n}\n\nvoid SuperPrintRightAlign(const std::string& SuperWords, int Font, int X, int Y, XTColor color)\n{\n    SuperPrintRightAlign((int)SuperWords.size(), SuperWords.c_str(), Font, X, Y, color);\n}\n\nvoid SuperPrintCenter(const std::string& SuperWords, int Font, int X, int Y, XTColor color)\n{\n    SuperPrintCenter((int)SuperWords.size(), SuperWords.c_str(), Font, X, Y, color);\n}\n\nvoid SuperPrintScreenCenter(const std::string& SuperWords, int Font, int Y, XTColor color)\n{\n    SuperPrintScreenCenter((int)SuperWords.size(), SuperWords.c_str(), Font, Y, color);\n}\n\nvoid SuperPrint(const std::string& SuperWords, int Font, int X, int Y, XTColor color)\n{\n    SuperPrint((int)SuperWords.size(), SuperWords.c_str(), Font, X, Y, color);\n}\n"
  },
  {
    "path": "src/graphics/gfx_screen.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <sdl_proxy/sdl_stdinc.h>\n\n#include \"../globals.h\"\n#include \"../graphics.h\"\n#include \"../player.h\"\n#include \"../sound.h\"\n#include \"../change_res.h\"\n#include \"../load_gfx.h\"\n#include \"../core/window.h\"\n\n#include \"config.h\"\n#include \"core/render.h\"\n\nvoid SetScreenType(Screen_t& screen)\n{\n    if(!screen.is_active())\n        return;\n\n    auto old_type = screen.Type;\n\n    // moved this code from game_main.cpp, but it occured elsewhere also\n    //   it was always called before setup screens, now it is a part of setup screens.\n    //   better to have it in one place so it can be updated\n    if(screen.player_count == 1)\n        screen.Type = ScreenTypes::SinglePlayer; // Follow 1 player\n    else if(screen.player_count == 2)\n    {\n        if(BattleMode)\n            screen.Type = ScreenTypes::Dynamic;\n        else if(screen.two_screen_pref == MultiplayerPrefs::Split)\n            screen.Type = ScreenTypes::LeftRight;\n        else if(screen.two_screen_pref == MultiplayerPrefs::Shared)\n            screen.Type = ScreenTypes::SharedScreen;\n        else if(screen.two_screen_pref == MultiplayerPrefs::TopBottom)\n            screen.Type = ScreenTypes::TopBottom;\n        else\n            screen.Type = ScreenTypes::Dynamic; // Dynamic screen\n    }\n    else\n    {\n        if(g_config.fix_npc_camera_logic && (BattleMode || screen.four_screen_pref == MultiplayerPrefs::Split))\n            screen.Type = ScreenTypes::Quad;\n        else\n            screen.Type = ScreenTypes::SharedScreen; // Average, no one leaves the screen\n    }\n\n    // special cases\n    if(g_ClonedPlayerMode)\n        screen.Type = ScreenTypes::Average;\n    if(SingleCoop > 0)\n        screen.Type = ScreenTypes::SingleCoop;\n    if(GameMenu)\n        screen.Type = ScreenTypes::Average;\n    if(GameOutro)\n        screen.Type = ScreenTypes::Credits;\n    if(LevelEditor)\n        screen.Type = ScreenTypes::SinglePlayer;\n\n#ifndef PGE_MIN_PORT\n    if(&screen == l_screen && (old_type == ScreenTypes::Quad) != (screen.Type == ScreenTypes::Quad))\n        UpdateInternalRes();\n#else\n    (void)old_type;\n#endif\n}\n\n// Sets up the split lines\nvoid SetupScreens(Screen_t& screen, bool reset)\n{\n    if(!screen.is_active())\n        return;\n\n    SetScreenType(screen);\n\n    vScreen_t& vscreen1 = screen.vScreen(1);\n    vScreen_t& vscreen2 = screen.vScreen(2);\n\n    switch(screen.Type)\n    {\n    case 0: // Follows Player 1\n        vscreen1.Height = screen.H;\n        vscreen1.Width = screen.W;\n        vscreen1.Left = 0;\n        vscreen1.Top = 0;\n        vscreen2.Visible = false;\n        break;\n    case 1: // Split Screen vertical\n        vscreen1.Height = screen.H / 2;\n        vscreen1.Width = screen.W;\n        vscreen1.Left = 0;\n        vscreen1.Top = 0;\n        vscreen2.Height = screen.H / 2;\n        vscreen2.Width = screen.W;\n        vscreen2.Left = 0;\n        vscreen2.Top = screen.H / 2;\n        break;\n    case 2: // Follows all players\n        vscreen1.Height = screen.H;\n        vscreen1.Width = screen.W;\n        vscreen1.Left = 0;\n        vscreen1.Top = 0;\n        vscreen2.Visible = false;\n        break;\n    case 3: // Follows all players. Noone leaves the screen\n        vscreen1.Height = screen.H;\n        vscreen1.Width = screen.W;\n        vscreen1.Left = 0;\n        vscreen1.Top = 0;\n        vscreen2.Visible = false;\n        break;\n    case 4: // Split Screen horizontal\n        vscreen1.Height = screen.H;\n        vscreen1.Width = screen.W / 2;\n        vscreen1.Left = 0;\n        vscreen1.Top = 0;\n        vscreen2.Height = screen.H;\n        vscreen2.Width = screen.W / 2;\n        vscreen2.Left = screen.W / 2;\n        vscreen2.Top = 0;\n        break;\n    case 5: // Dynamic screen detection\n        vscreen1.Height = screen.H;\n        vscreen1.Width = screen.W;\n        vscreen1.Left = 0;\n        vscreen1.Top = 0;\n\n        if(reset)\n            vscreen2.Visible = false;\n        break;\n    case 6: // VScreen Coop\n        vscreen1.Height = screen.H;\n        vscreen1.Width = screen.W;\n        vscreen1.Left = 0;\n        vscreen1.Top = 0;\n        vscreen2.Height = screen.H;\n        vscreen2.Width = screen.W;\n        vscreen2.Left = 0;\n        vscreen2.Top = 0;\n        break;\n    case 7: // Credits (MODIFIED to include an adjusted chop feature)\n        vscreen1.Left = 0;\n        vscreen1.Top = (screen.H > 600) ? 100 : (screen.H / 6) & ~1;\n        vscreen1.Height = screen.H - 2 * vscreen1.Top;\n        vscreen1.Width = screen.W;\n        vscreen2.Visible = false;\n        break;\n    case 8: // netplay\n        vscreen1.Left = 0;\n        vscreen1.Height = screen.H;\n        vscreen1.Top = 0;\n        vscreen1.Width = screen.W;\n        vscreen2.Visible = false;\n        break;\n    case ScreenTypes::Quad: // quad\n        for(int i = 0; i < 4 && i < maxLocalPlayers; i++)\n        {\n            vScreen_t& vscreen_i = screen.vScreen(i + 1);\n\n            vscreen_i.Width = screen.W / 2;\n            vscreen_i.Height = screen.H / 2;\n            vscreen_i.Left = (i & 1) ? screen.W / 2 : 0;\n            vscreen_i.Top = (i & 2) ? screen.H / 2 : 0;\n        }\n        break;\n    }\n\n    // clear dynamic screen / section resize offsets when called with reset\n    if(reset)\n    {\n        for(int i = screen.active_begin(); i < screen.active_end(); i++)\n        {\n            vScreen_t& vscreen_i = screen.vScreen(i + 1);\n            vscreen_i.TempDelay = 0;\n            vscreen_i.tempX = 0;\n            vscreen_i.TempY = 0;\n        }\n    }\n}\n\nvoid SetupScreens(bool reset)\n{\n    for(int i = 0; i < c_screenCount; i++)\n        SetupScreens(Screens[i], reset);\n}\n\nvoid DynamicScreen(Screen_t& screen, bool mute)\n{\n    if(!screen.is_active())\n        return;\n\n    int A = 0;\n\n    vScreen_t& vscreen1 = screen.vScreen(1);\n    vScreen_t& vscreen2 = screen.vScreen(2);\n\n    GetvScreenAverage(vscreen1);\n\n    if(!g_config.multiplayer_pause_controls)\n    {\n        for(int i = 0; i < 2; i++)\n        {\n            if(Player[screen.players[i]].Effect == PLREFF_RESPAWN)\n                return;\n        }\n    }\n\n    for(A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Mount == 2)\n            Player[A].Location.Height = 0;\n    }\n\n    Player_t& p1 = Player[screen.players[0]];\n    Player_t& p2 = Player[screen.players[1]];\n\n    num_t p1LocY = (p1.Effect == PLREFF_RESPAWN) ? p1.RespawnY : p1.Location.Y;\n    num_t p2LocY = (p2.Effect == PLREFF_RESPAWN) ? p2.RespawnY : p2.Location.Y;\n\n    if(!p1.Dead && !p2.Dead)\n    {\n        if(p1.Section == p2.Section)\n        {\n            const SpeedlessLocation_t& section = level[p1.Section];\n\n            // use canonical width for checks in NoTurnBack case\n            bool shrink_screen = (NoTurnBack[p1.Section] && g_config.allow_multires);\n            const int check_W = shrink_screen ? screen.canonical_screen().W : screen.W;\n\n            // a number of clauses check whether the section is larger than the screen\n            bool section_wide = section.Width  - section.X > check_W;\n            bool section_tall = section.Height - section.Y > screen.H;\n\n            // observe that in the original code, there is a condition on whether vScreen 2 is visible, and this decides what P1 is compared with\n            // (but NOT P2, which is always compared with vScreen 1). This is actually unnecessary and the logic works fine if both players are compared with\n            // vScreen 1. It was likely a vestige from before the GetvScreenAverage call above was added. It must be removed if modern_section_change is set,\n            // because if qScreen is active for vScreen2 (impossible prior to modern section change), the checks no longer work.\n            const vScreen_t& p2_compare_vscreen = vscreen1;\n            bool use_vscreen2 = !g_config.modern_section_change && vscreen2.Visible;\n            const vScreen_t& p1_compare_vscreen = use_vscreen2 ? vscreen2 : vscreen1;\n\n            // explanation of logic (example of first case, all are similar):\n            //     if (1) the section is wider than the screen,\n            //    and (2) P2's center is >75% to the right of the screen,\n            //    and (3) the players could not both fit within the right 75% of the screen at the right of the section, then split\n\n            // the VB6 code here was extremely complicated; including it below so that simplifications can be verified\n\n            // If level(Player(1).Section).Width - level(Player(1).Section).X > ScreenW And\n            //   (((vScreen(2).Visible = False   And Player(2).Location.X + vScreenX(1) >= ScreenW * 0.75 - Player(2).Location.Width / 2) Or\n            //       (vScreen(2).Visible = True  And Player(2).Location.X + vScreenX(1) >= ScreenW * 0.75 - Player(2).Location.Width / 2))\n            //   And (Player(1).Location.X < level(Player(1).Section).Width - ScreenW * 0.75 - Player(1).Location.Width / 2)) Then\n\n            if(section_wide && (p2.Location.X + p2_compare_vscreen.X >= check_W * 0.75_n - p2.Location.Width / 2) && (p1.Location.X < section.Width - check_W * 0.75_n - p1.Location.Width / 2))\n            {\n                vscreen2.Height = screen.H;\n                vscreen2.Width = screen.W / 2;\n                vscreen2.Left = screen.W / 2;\n                vscreen2.Top = 0;\n                vscreen1.Height = screen.H;\n                vscreen1.Width = screen.W / 2;\n                vscreen1.Left = 0;\n                vscreen1.Top = 0;\n                GetvScreenAverage2(vscreen1);\n                if(screen.DType != 1 && !mute)\n                    PlaySound(SFX_Camera);\n                for(A = 1; A <= 2; A++)\n                {\n                    vScreen_t& vscreena = screen.vScreen(A);\n                    Player_t& p = Player[screen.players[A - 1]];\n                    num_t pLocY = (A == 1) ? p1LocY : p2LocY;\n\n                    vscreena.TempDelay = 200;\n                    vscreena.tempX = 0;\n                    vscreena.TempY = -vscreen1.Y + screen.H * 0.5_n - pLocY - vScreenYOffset - p.Location.Height;\n                }\n                vscreen2.Visible = true;\n                screen.DType = 1;\n            }\n\n            // ElseIf level(Player(1).Section).Width - level(Player(1).Section).X > ScreenW And\n            // (((vScreen(2).Visible = False And Player(1).Location.X + vScreenX(1) >= ScreenW * 0.75 - Player(1).Location.Width / 2) Or\n            //   (vScreen(2).Visible = True  And Player(1).Location.X + vScreenX(2) >= ScreenW * 0.75 - Player(1).Location.Width / 2))\n            // And (Player(2).Location.X < level(Player(1).Section).Width - ScreenW * 0.75 - Player(2).Location.Width / 2)) Then\n\n            else if(section_wide && (p1.Location.X + p1_compare_vscreen.X >= check_W * 0.75_n - p1.Location.Width / 2) && (p2.Location.X < section.Width - check_W * 0.75_n - p2.Location.Width / 2))\n            {\n                vscreen1.Height = screen.H;\n                vscreen1.Width = screen.W / 2;\n                vscreen1.Left = screen.W / 2;\n                vscreen1.Top = 0;\n                vscreen2.Height = screen.H;\n                vscreen2.Width = screen.W / 2;\n                vscreen2.Left = 0;\n                vscreen2.Top = 0;\n                GetvScreenAverage2(vscreen1);\n                if(screen.DType != 2 && !mute)\n                    PlaySound(SFX_Camera);\n                for(A = 1; A <= 2; A++)\n                {\n                    vScreen_t& vscreena = screen.vScreen(A);\n                    Player_t& p = Player[screen.players[A - 1]];\n                    num_t pLocY = (A == 1) ? p1LocY : p2LocY;\n\n                    vscreena.TempDelay = 200;\n                    vscreena.tempX = 0;\n                    vscreena.TempY = -vscreen1.Y + screen.H * 0.5_n - pLocY - vScreenYOffset - p.Location.Height;\n                }\n                screen.DType = 2;\n                vscreen2.Visible = true;\n            }\n\n            // ElseIf (level(Player(1).Section).Height - level(Player(1).Section).Y > ScreenH And\n            // ((vScreen(2).Visible = False And Player(1).Location.Y + vScreenY(1) >= ScreenH * 0.75 - vScreenYOffset - Player(1).Location.Height) Or\n            //  (vScreen(2).Visible = True  And Player(1).Location.Y + vScreenY(2) >= ScreenH * 0.75 - vScreenYOffset - Player(1).Location.Height))\n            // And (Player(2).Location.Y < level(Player(1).Section).Height - ScreenH * 0.75 - vScreenYOffset - Player(2).Location.Height)) Then\n\n            else if(section_tall && (p1LocY + p1_compare_vscreen.Y >= screen.H * 0.75_n - vScreenYOffset - p1.Location.Height) && (p2LocY < section.Height - screen.H * 0.75_n - vScreenYOffset - p2.Location.Height))\n            {\n                vscreen1.Height = screen.H / 2;\n                vscreen1.Width = screen.W;\n                vscreen1.Left = 0;\n                vscreen1.Top = screen.H / 2;\n                vscreen2.Height = screen.H / 2;\n                vscreen2.Width = screen.W;\n                vscreen2.Left = 0;\n                vscreen2.Top = 0;\n                GetvScreenAverage2(vscreen1);\n                if(screen.DType != 3 && !mute)\n                    PlaySound(SFX_Camera);\n                for(A = 1; A <= 2; A++)\n                {\n                    vScreen_t& vscreena = screen.vScreen(A);\n                    Player_t& p = Player[screen.players[A - 1]];\n\n                    vscreena.TempDelay = 200;\n                    vscreena.TempY = 0;\n                    vscreena.tempX = -vscreen1.X + check_W * 0.5_n - p.Location.X - p.Location.Width / 2;\n                }\n                vscreen2.Visible = true;\n                screen.DType = 3;\n            }\n\n            // ElseIf (level(Player(1).Section).Height - level(Player(1).Section).Y > ScreenH And\n            // ((vScreen(2).Visible = False And Player(2).Location.Y + vScreenY(1) >= ScreenH * 0.75 - vScreenYOffset - Player(2).Location.Height) Or\n            //  (vScreen(2).Visible = True  And Player(2).Location.Y + vScreenY(1) >= ScreenH * 0.75 - vScreenYOffset - Player(2).Location.Height))\n            // And (Player(1).Location.Y < level(Player(1).Section).Height - ScreenH * 0.75 - vScreenYOffset - Player(1).Location.Height)) Then\n\n            else if(section_tall && (p2LocY + p2_compare_vscreen.Y >= screen.H * 0.75_n - vScreenYOffset - p2.Location.Height) && (p1LocY < section.Height - screen.H * 0.75_n - vScreenYOffset - p1.Location.Height))\n            {\n                vscreen1.Height = screen.H / 2;\n                vscreen1.Width = screen.W;\n                vscreen1.Left = 0;\n                vscreen1.Top = 0;\n                vscreen2.Height = screen.H / 2;\n                vscreen2.Width = screen.W;\n                vscreen2.Left = 0;\n                vscreen2.Top = screen.H / 2;\n                GetvScreenAverage2(vscreen1);\n                if(screen.DType != 4 && !mute)\n                    PlaySound(SFX_Camera);\n                for(A = 1; A <= 2; A++)\n                {\n                    vScreen_t& vscreena = screen.vScreen(A);\n                    Player_t& p = Player[screen.players[A - 1]];\n\n                    vscreena.TempDelay = 200;\n                    vscreena.TempY = 0;\n                    vscreena.tempX = -vscreen1.X + check_W * 0.5_n - p.Location.X - p.Location.Width / 2;\n                }\n                vscreen2.Visible = true;\n                screen.DType = 4;\n            }\n            else\n            {\n                if(vscreen2.Visible)\n                {\n                    if(screen.DType != 5 && !mute)\n                        PlaySound(SFX_Camera);\n                    vscreen2.Visible = false;\n                    vscreen1.Height = screen.H;\n                    vscreen1.Width = screen.W;\n                    vscreen1.Left = 0;\n                    vscreen1.Top = 0;\n                    vscreen1.tempX = 0;\n                    vscreen1.TempY = 0;\n                    vscreen2.tempX = 0;\n                    vscreen2.TempY = 0;\n                }\n                screen.DType = 5;\n            }\n            for(A = 1; A <= 2; A++)\n            {\n                vScreen_t& vscreena = screen.vScreen(A);\n\n                if(vscreena.TempY > (vscreena.Height * 0.25_n))\n                    vscreena.TempY = (vscreena.Height * 0.25_n);\n                if(vscreena.TempY < -(vscreena.Height * 0.25_n))\n                    vscreena.TempY = -(vscreena.Height * 0.25_n);\n                if(vscreena.tempX > (vscreena.Width * 0.25_n))\n                    vscreena.tempX = (vscreena.Width * 0.25_n);\n                if(vscreena.tempX < -(vscreena.Width * 0.25_n))\n                    vscreena.tempX = -(vscreena.Width * 0.25_n);\n            }\n        }\n        else\n        {\n            vscreen1.Height = screen.H / 2;\n            vscreen1.Width = screen.W;\n            vscreen1.Left = 0;\n            vscreen1.Top = 0;\n            vscreen2.Height = screen.H / 2;\n            vscreen2.Width = screen.W;\n            vscreen2.Left = 0;\n            vscreen2.Top = screen.H / 2;\n            vscreen1.tempX = 0;\n            vscreen1.TempY = 0;\n            vscreen2.tempX = 0;\n            vscreen2.TempY = 0;\n            GetvScreenAverage2(vscreen1);\n            if(screen.DType != 6 && !mute)\n                PlaySound(SFX_Camera);\n            screen.DType = 6;\n            vscreen2.Visible = true;\n        }\n    }\n    else\n    {\n        if(vscreen2.Visible)\n        {\n            vscreen2.Visible = false;\n            // vscreen1.Visible = false; // Useless, because code below sets it as TRUE back\n            vscreen1.Height = screen.H;\n            vscreen1.Width = screen.W;\n            vscreen1.Left = 0;\n            vscreen1.Top = 0;\n            vscreen1.Visible = true;\n            vscreen1.tempX = 0;\n            vscreen1.TempY = 0;\n            vscreen2.tempX = 0;\n            vscreen2.TempY = 0;\n        }\n    }\n    for(A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Mount == 2)\n            Player[A].Location.Height = 128;\n    }\n}\n\nvoid DynamicScreens()\n{\n    for(int i = 0; i < c_screenCount; i++)\n    {\n        Screen_t& screen = Screens[i];\n        if(screen.Type == ScreenTypes::Dynamic)\n            DynamicScreen(screen, !screen.Visible);\n    }\n}\n\n// NEW: limit vScreens to playable section area and center them on the real screen\nvoid CenterScreens(Screen_t& screen)\n{\n    if(!screen.is_active())\n        return;\n\n    for(int v = 1; v <= maxLocalPlayers; v++)\n    {\n        vScreen_t& vscreen = screen.vScreen(v);\n        const Player_t& p = Player[screen.players[v - 1]];\n        const SpeedlessLocation_t& section = level[p.Section];\n\n        vscreen.ScreenLeft = vscreen.Left;\n        vscreen.ScreenTop  = vscreen.Top;\n\n        bool in_map = (LevelSelect && !GameMenu);\n\n        // skip centering in all these places (world map sections handled elsewhere)\n        if(LevelEditor || WorldEditor || in_map)\n            continue;\n\n        // restrict the vScreen to the level if the level is smaller than the screen\n        int MaxWidth = num_t::round(section.Width - section.X);\n        int MaxHeight = num_t::round(section.Height - section.Y);\n\n        // on 3DS allow a slight amount of expansion for 3D overdraw, if the vscreen covers (and will cover) the entire screen\n        int allow_X = (vscreen.Width == Screens[vscreen.screen_ref].W) ? Screens[vscreen.screen_ref].CameraOverscanX : 0;\n\n        // don't do overscan if the section will be smaller than the screen after overscan (add 1 for floating precision margin)\n        if(MaxWidth + XRender::TargetCameraOverscanX * 2 + 1 < vscreen.Width)\n            allow_X = 0;\n\n        MaxWidth += allow_X * 2;\n\n        int MinWidth = 0;\n        int MinHeight = 0;\n\n        bool no_turn_back = NoTurnBack[p.Section];\n\n        // restrict single vScreens on NoTurnBack sections\n        if(g_config.allow_multires && no_turn_back)\n        {\n            MaxWidth = SDL_min(MaxWidth, screen.canonical_screen().W);\n\n            // limit Visible screens further during DynamicScreen\n            if(screen.Visible && screen.Type == 5 && screen.vScreen(2).Visible)\n                MaxWidth = SDL_min(MaxWidth, screen.canonical_screen().W / 2);\n        }\n\n        // allow the canonical vScreens to approach normal screen size during dynamic screen\n        if(g_config.allow_multires && !screen.Visible && !no_turn_back)\n        {\n            MinWidth = screen.W;\n            MinHeight = screen.H;\n\n            // allow to grow up to half the size of the visible screen (in dynamic screen), otherwise full screen\n            if(screen.Type == ScreenTypes::Dynamic)\n            {\n                MinWidth = SDL_min(MinWidth, screen.visible_screen().W / 2);\n                MinHeight = SDL_min(MinHeight, screen.visible_screen().H / 2);\n            }\n            else\n            {\n                MinWidth = SDL_min(MinWidth, screen.visible_screen().W);\n                MinHeight = SDL_min(MinHeight, screen.visible_screen().H);\n            }\n\n            // this ensures a gradual transition between canonical screen size / 2 and visible screen size / 2\n            // (no need to use it if in different sections or in permanent splitscreen)\n            if(screen.Type == ScreenTypes::Dynamic && screen.DType != DScreenTypes::DiffSections)\n            {\n                // approximate positions of player screens\n                num_t cX1, cY1, cX2, cY2;\n                GetPlayerScreen(screen.canonical_screen().W, screen.canonical_screen().H, Player[screen.players[0]], cX1, cY1);\n                GetPlayerScreen(screen.canonical_screen().W, screen.canonical_screen().H, Player[screen.players[1]], cX2, cY2);\n\n                int screen_X_distance = (int)num_t::abs(cX1 - cX2);\n                int screen_Y_distance = (int)num_t::abs(cY1 - cY2);\n\n                MinWidth = SDL_min(MinWidth, screen_X_distance);\n                MinHeight = SDL_min(MinHeight, screen_Y_distance);\n            }\n\n            // never get larger than section\n            MinWidth = SDL_min(MinWidth, MaxWidth);\n            MinHeight = SDL_min(MinHeight, MaxHeight);\n        }\n\n        if(MinWidth > vscreen.Width || MaxWidth < vscreen.Width)\n        {\n            int SetWidth = (MaxWidth < vscreen.Width) ? MaxWidth : MinWidth;\n\n            int left_from_center = vscreen.ScreenLeft - (screen.W / 2);\n            int right_from_center = (screen.W / 2) - (vscreen.ScreenLeft + vscreen.Width);\n            int total_from_center = (left_from_center + right_from_center);\n            int size_diff = vscreen.Width - SetWidth;\n\n            // Move towards center of screen. If left is on center, don't need to move left side of screen.\n            // If right is on center, need to fully move left so right can stay. Yields following formula:\n            if(total_from_center)\n                vscreen.ScreenLeft += size_diff * left_from_center / total_from_center;\n\n            vscreen.Width = SetWidth;\n        }\n\n        if(MinHeight > vscreen.Height || MaxHeight < vscreen.Height)\n        {\n            int SetHeight = (MaxHeight < vscreen.Height) ? MaxHeight : MinHeight;\n\n            int top_from_center = vscreen.ScreenTop - (screen.H / 2);\n            int bottom_from_center = (screen.H / 2) - (vscreen.ScreenTop + vscreen.Height);\n            int total_from_center = (top_from_center + bottom_from_center);\n            int size_diff = vscreen.Height - SetHeight;\n\n            // Move towards center of screen. If top is on center, don't need to move top side of screen.\n            // If bottom is on center, need to fully move top so bottom can stay. Yields following formula:\n            if(total_from_center)\n                vscreen.ScreenTop += size_diff * top_from_center / total_from_center;\n\n            vscreen.Height = SetHeight;\n        }\n    }\n}\n\nvoid CenterScreens()\n{\n    for(int i = 0; i < c_screenCount; i++)\n    {\n        Screen_t& screen = Screens[i];\n        CenterScreens(screen);\n    }\n}\n\n// NEW: moves qScreen towards vScreen, now including the screen size\nbool Update_qScreen(int Z, num_t camRate, num_t resizeRate)\n{\n    if(Z == 2 && !g_config.modern_section_change)\n        return false;\n\n    bool continue_qScreen = true;\n\n    // take the slower option of 2px per second camera (vanilla)\n    //   or 2px per second resize, then scale the speed of the faster one to match\n    num_t camRateX = camRate;\n    num_t camRateY = camRate;\n\n    int resizeRateX = int(resizeRate);\n    int resizeRateY = int(resizeRate);\n\n    num_t camFramesX = num_t::abs(vScreen[Z].X - qScreenLoc[Z].X).divided_by(camRateX);\n    num_t camFramesY = num_t::abs(vScreen[Z].Y - qScreenLoc[Z].Y).divided_by(camRateY);\n\n    // qScreenLoc Width and Height values are only valid if modern section change is disabled\n    if(g_config.modern_section_change)\n    {\n        num_t camFramesX_r = num_t::abs(vScreen[Z].X - vScreen[Z].Width - qScreenLoc[Z].X + qScreenLoc[Z].Width).divided_by(camRateX);\n        num_t camFramesY_b = num_t::abs(vScreen[Z].Y - vScreen[Z].Height - qScreenLoc[Z].Y + qScreenLoc[Z].Height).divided_by(camRateY);\n\n        camFramesX = SDL_min(camFramesX, camFramesX_r);\n        camFramesY = SDL_min(camFramesY, camFramesY_b);\n    }\n\n    num_t resizeFramesX = num_t::abs(vScreen[Z].Width - qScreenLoc[Z].Width) / resizeRateX;\n    num_t resizeFramesY = num_t::abs(vScreen[Z].Height - qScreenLoc[Z].Height) / resizeRateY;\n\n    if(!g_config.modern_section_change)\n    {\n        resizeFramesX = 0;\n        resizeFramesY = 0;\n    }\n\n    num_t qFramesX = SDL_max(camFramesX, resizeFramesX);\n    num_t qFramesY = SDL_max(camFramesY, resizeFramesY);\n\n    // don't continue after this frame if it would arrive next frame\n    // (this is the intent of the <5 condition in the vanilla game)\n    if(qFramesX < 2.5_n && qFramesY < 2.5_n)\n        continue_qScreen = false;\n\n    // but, the original condition occurred *after* adding/subtracting 2, so actually\n    // the original game would not continue if it would arrive the frame after next, too\n    if(!g_config.modern_section_change && qFramesX < 3.5_n && qFramesY < 3.5_n)\n        continue_qScreen = false;\n\n    if(qFramesX < 1)\n        qFramesX = 1;\n    if(qFramesY < 1)\n        qFramesY = 1;\n\n    camRateX = num_t::abs(vScreen[Z].X - qScreenLoc[Z].X).divided_by(qFramesX);\n    camRateY = num_t::abs(vScreen[Z].Y - qScreenLoc[Z].Y).divided_by(qFramesY);\n\n    resizeRateX = (int)(num_t::abs(vScreen[Z].Width - qScreenLoc[Z].Width).divided_by(qFramesX));\n    resizeRateY = (int)(num_t::abs(vScreen[Z].Height - qScreenLoc[Z].Height).divided_by(qFramesY));\n\n    int screenRateX = (int)(num_t::abs(vScreen[Z].ScreenLeft - qScreenLoc[Z].ScreenLeft).divided_by(qFramesX));\n    int screenRateY = (int)(num_t::abs(vScreen[Z].ScreenTop - qScreenLoc[Z].ScreenTop).divided_by(qFramesY));\n\n    if(vScreen[Z].X < qScreenLoc[Z].X - camRateX)\n        qScreenLoc[Z].X -= camRateX;\n    else if(vScreen[Z].X > qScreenLoc[Z].X + camRateX)\n        qScreenLoc[Z].X += camRateX;\n    else\n        qScreenLoc[Z].X = vScreen[Z].X;\n\n    if(vScreen[Z].Y < qScreenLoc[Z].Y - camRateY)\n        qScreenLoc[Z].Y -= camRateY;\n    else if(vScreen[Z].Y > qScreenLoc[Z].Y + camRateY)\n        qScreenLoc[Z].Y += camRateY;\n    else\n        qScreenLoc[Z].Y = vScreen[Z].Y;\n\n    if(vScreen[Z].ScreenLeft < qScreenLoc[Z].ScreenLeft - screenRateX)\n        qScreenLoc[Z].ScreenLeft -= screenRateX;\n    else if(vScreen[Z].ScreenLeft > qScreenLoc[Z].ScreenLeft + screenRateX)\n        qScreenLoc[Z].ScreenLeft += screenRateX;\n    else\n        qScreenLoc[Z].ScreenLeft = vScreen[Z].ScreenLeft;\n\n    if(vScreen[Z].ScreenTop < qScreenLoc[Z].ScreenTop - screenRateY)\n        qScreenLoc[Z].ScreenTop -= screenRateY;\n    else if(vScreen[Z].ScreenTop > qScreenLoc[Z].ScreenTop + screenRateY)\n        qScreenLoc[Z].ScreenTop += screenRateY;\n    else\n        qScreenLoc[Z].ScreenTop = vScreen[Z].ScreenTop;\n\n    if(vScreen[Z].Width < qScreenLoc[Z].Width - resizeRateX)\n        qScreenLoc[Z].Width -= resizeRateX;\n    else if(vScreen[Z].Width > qScreenLoc[Z].Width + resizeRateX)\n        qScreenLoc[Z].Width += resizeRateX;\n    else\n        qScreenLoc[Z].Width = vScreen[Z].Width;\n\n    if(vScreen[Z].Height < qScreenLoc[Z].Height - resizeRateY)\n        qScreenLoc[Z].Height -= resizeRateY;\n    else if(vScreen[Z].Height > qScreenLoc[Z].Height + resizeRateY)\n        qScreenLoc[Z].Height += resizeRateY;\n    else\n        qScreenLoc[Z].Height = vScreen[Z].Height;\n\n    vScreen[Z].X = qScreenLoc[Z].X;\n    vScreen[Z].Y = qScreenLoc[Z].Y;\n\n    // update vScreen width / height\n    if(g_config.modern_section_change)\n    {\n        vScreen[Z].Width = (qScreenLoc[Z].Width / 2) * 2;\n        vScreen[Z].Height = (qScreenLoc[Z].Height / 2) * 2;\n        vScreen[Z].ScreenLeft = qScreenLoc[Z].ScreenLeft;\n        vScreen[Z].ScreenTop = qScreenLoc[Z].ScreenTop;\n    }\n\n    return continue_qScreen;\n}\n"
  },
  {
    "path": "src/graphics/gfx_special_frames.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../globals.h\"\n#include \"../graphics.h\"\n#include \"blk_id.h\"\n#include \"game_main.h\"\n#include \"npc_traits.h\"\n\nstatic void s_ConveyorBlockConvertedFrames()\n{\n    // check whether converted conveyors should use custom frames\n    const auto& t = NPCTraits[NPCID_CONVEYOR];\n\n    // non-preferred case, custom frames\n    for(int dir_right = 0; dir_right < 2; dir_right++)\n    {\n        int id = (dir_right) ? BLKID_CONVEYOR_R_CONV : BLKID_CONVEYOR_L_CONV;\n        auto& Frame = BlockFrame[id];\n        auto& FrameCount = BlockFrame2[id];\n\n        FrameCount += 1;\n\n        if(FrameCount > t.FrameSpeed)\n        {\n            if(t.FrameStyle == 0)\n                Frame += (dir_right) ? 1 : -1;\n            else\n                Frame += 1;\n\n            FrameCount = 0;\n        }\n\n        if(t.FrameStyle == 0)\n        {\n            if(Frame >= t.TFrames)\n                Frame = 0;\n            else if(Frame < 0)\n                Frame = t.TFrames - 1;\n        }\n        else if(t.FrameStyle == 1 || t.FrameStyle == 2)\n        {\n            if(!dir_right)\n            {\n                if(Frame < 0 || Frame >= t.TFrames)\n                    Frame = 0;\n            }\n            else\n            {\n                if(Frame < t.TFrames || Frame >= t.TFrames * 2)\n                    Frame = t.TFrames;\n            }\n        }\n    }\n}\n\n/*Private*/\nvoid SpecialFrames()\n{\n    SpecialFrameCount[1]++;\n    if(SpecialFrameCount[1] >= 6)\n    {\n        SpecialFrameCount[1] = 0;\n\n        SpecialFrame[1]++;\n        if(SpecialFrame[1] >= 2)\n            SpecialFrame[1] = 0;\n    }\n\n    SpecialFrameCount[2]++;\n    if(SpecialFrameCount[2] >= 3)\n    {\n        SpecialFrameCount[2] = 0;\n\n        SpecialFrame[2]++;\n        if(SpecialFrame[2] >= 4)\n            SpecialFrame[2] = 0;\n    }\n\n    SpecialFrameCount[3]++;\n    if(SpecialFrameCount[3] >= 8)\n    {\n        SpecialFrameCount[3] = 0;\n\n        SpecialFrame[3]++;\n        if(SpecialFrame[3] >= 4)\n            SpecialFrame[3] = 0;\n    }\n\n    // SpecialFrameCount[4]++;\n    // if(SpecialFrameCount[4] >= 2.475F)\n\n    bool conveyer_block_difficult_frames = (NPCTraits[NPCID_CONVEYOR].TFrames > 0);\n\n    SpecialFrameCount[4] += 1000;\n    if(SpecialFrameCount[4] >= 2475)\n    {\n        // SpecialFrameCount[4] = SpecialFrameCount[4] - 2.475F;\n        SpecialFrameCount[4] -= 2475;\n\n        SpecialFrame[4]++;\n        if(SpecialFrame[4] >= 4)\n            SpecialFrame[4] = 0;\n\n        // Apply NPC conveyor belt frame to block conveyor belts as well\n        if(!FreezeNPCs && !LevelSelect && GamePaused == PauseCode::None)\n        {\n            for(int i = BLKID_CONVEYOR_L_START; i <= BLKID_CONVEYOR_L_END; i++)\n            {\n                if(i == BLKID_CONVEYOR_L_CONV && conveyer_block_difficult_frames)\n                    continue;\n\n                BlockFrame[i] = SpecialFrame[4];\n            }\n\n            for(int i = BLKID_CONVEYOR_R_START; i <= BLKID_CONVEYOR_R_END; i++)\n            {\n                if(i == BLKID_CONVEYOR_R_CONV && conveyer_block_difficult_frames)\n                    continue;\n\n                BlockFrame[i] = 3 - SpecialFrame[4];\n            }\n        }\n    }\n\n    // do conveyor block converted frames if needed\n    if(conveyer_block_difficult_frames)\n        s_ConveyorBlockConvertedFrames();\n\n    SpecialFrameCount[5]++;\n    if(SpecialFrameCount[5] < 20)\n        SpecialFrame[5] = 1;\n    else if(SpecialFrameCount[5] < 25)\n        SpecialFrame[5] = 2;\n    else if(SpecialFrameCount[5] < 30)\n        SpecialFrame[5] = 3;\n    else if(SpecialFrameCount[5] < 35)\n        SpecialFrame[5] = 4;\n    else if(SpecialFrameCount[5] < 40)\n        SpecialFrame[5] = 5;\n    else if(SpecialFrameCount[5] < 45)\n        SpecialFrame[5] = 1;\n    else if(SpecialFrameCount[5] < 50)\n        SpecialFrame[5] = 0;\n    else\n        SpecialFrameCount[5] = 0;\n\n    SpecialFrameCount[6]++;\n    if(SpecialFrameCount[6] >= 12)\n    {\n        SpecialFrameCount[6] = 0;\n\n        SpecialFrame[6]++;\n        if(SpecialFrame[6] >= 4)\n            SpecialFrame[6] = 0;\n    }\n\n    SpecialFrameCount[7]++;\n    if(SpecialFrameCount[7] < 8)\n        SpecialFrame[7] = 0;\n    else if(SpecialFrameCount[7] < 16)\n        SpecialFrame[7] = 1;\n    else if(SpecialFrameCount[7] < 24)\n        SpecialFrame[7] = 2;\n    else if(SpecialFrameCount[7] < 32)\n        SpecialFrame[7] = 3;\n    else if(SpecialFrameCount[7] < 40)\n        SpecialFrame[7] = 2;\n    else if(SpecialFrameCount[7] < 48)\n        SpecialFrame[7] = 1;\n    else\n        SpecialFrameCount[7] = 0;\n\n    SpecialFrameCount[8]++;\n    if(SpecialFrameCount[8] >= 8)\n    {\n        SpecialFrameCount[8] = 0;\n\n        SpecialFrame[8]++;\n        if(SpecialFrame[8] >= 3)\n            SpecialFrame[8] = 0;\n    }\n\n    SpecialFrameCount[9]++; // Fairy frame\n    if(SpecialFrameCount[9] >= 8)\n    {\n        SpecialFrameCount[9] = 0;\n\n        SpecialFrame[9]++;\n        if(SpecialFrame[9] >= 2)\n            SpecialFrame[9] = 0;\n    }\n}\n\nvoid LevelFramesNotFrozen()\n{\n    BackgroundFrameCount[26]++;\n    if(BackgroundFrameCount[26] >= 8)\n    {\n        BackgroundFrameCount[26] = 0;\n\n        BackgroundFrame[26]++;\n        if(BackgroundFrame[26] >= 8)\n            BackgroundFrame[26] = 0;\n    }\n\n    BackgroundFrameCount[18]++;\n    if(BackgroundFrameCount[18] >= 12)\n    {\n        BackgroundFrameCount[18] = 0;\n\n        BackgroundFrame[18]++;\n        if(BackgroundFrame[18] >= 4)\n            BackgroundFrame[18] = 0;\n\n        BackgroundFrame[19] = BackgroundFrame[18];\n        BackgroundFrame[20] = BackgroundFrame[18];\n        BackgroundFrame[161] = BackgroundFrame[18];\n    }\n\n    BackgroundFrameCount[36]++;\n    if(BackgroundFrameCount[36] >= 2)\n    {\n        BackgroundFrameCount[36] = 0;\n\n        BackgroundFrame[36]++;\n        if(BackgroundFrame[36] >= 4)\n            BackgroundFrame[36] = 0;\n    }\n\n    BackgroundFrame[68] = BackgroundFrame[36];\n\n    BackgroundFrameCount[65]++;\n    if(BackgroundFrameCount[65] >= 8)\n    {\n        BackgroundFrameCount[65] = 0;\n\n        BackgroundFrame[65]++;\n        if(BackgroundFrame[65] >= 4)\n            BackgroundFrame[65] = 0;\n    }\n\n    BackgroundFrame[66] = BackgroundFrame[65];\n    BackgroundFrame[70] = BackgroundFrame[65];\n    BackgroundFrame[100] = BackgroundFrame[65];\n    BackgroundFrame[134] = BackgroundFrame[65];\n    BackgroundFrame[135] = BackgroundFrame[65];\n    BackgroundFrame[136] = BackgroundFrame[65];\n    BackgroundFrame[137] = BackgroundFrame[65];\n    BackgroundFrame[138] = BackgroundFrame[65];\n\n    BackgroundFrameCount[82]++;\n    if(BackgroundFrameCount[82] >= 10)\n    {\n        BackgroundFrameCount[82] = 0;\n\n        BackgroundFrame[82]++;\n        if(BackgroundFrame[82] >= 4)\n            BackgroundFrame[82] = 0;\n    }\n\n    BackgroundFrameCount[170]++;\n    if(BackgroundFrameCount[170] >= 8)\n    {\n        BackgroundFrameCount[170] = 0;\n\n        BackgroundFrame[170]++;\n        if(BackgroundFrame[170] >= 4)\n            BackgroundFrame[170] = 0;\n\n        BackgroundFrame[171] = BackgroundFrame[170];\n    }\n\n    BackgroundFrameCount[125]++;\n    if(BackgroundFrameCount[125] >= 4)\n    {\n        BackgroundFrameCount[125] = 0;\n\n        if(BackgroundFrame[125] == 0)\n            BackgroundFrame[125] = 1;\n        else\n            BackgroundFrame[125] = 0;\n    }\n}\n\nvoid LevelFramesAlways()\n{\n    BackgroundFrame[172] = BackgroundFrame[66];\n\n    BackgroundFrameCount[158]++;\n    if(BackgroundFrameCount[158] >= 6)\n    {\n        BackgroundFrameCount[158] = 0;\n\n        BackgroundFrame[158]++;\n        if(BackgroundFrame[158] >= 4)\n            BackgroundFrame[158] = 0;\n\n        BackgroundFrame[159]++;\n        if(BackgroundFrame[159] >= 8)\n            BackgroundFrame[159] = 0;\n    }\n\n    BackgroundFrameCount[168]++;\n    if(BackgroundFrameCount[168] >= 8)\n    {\n        BackgroundFrameCount[168] = 0;\n\n        BackgroundFrame[168]++;\n        if(BackgroundFrame[168] >= 8)\n            BackgroundFrame[168] = 0;\n    }\n\n    BackgroundFrameCount[173]++;\n    if(BackgroundFrameCount[173] >= 8)\n    {\n        BackgroundFrameCount[173] = 0;\n\n        if(BackgroundFrame[173] == 0)\n            BackgroundFrame[173] = 1;\n        else\n            BackgroundFrame[173] = 0;\n    }\n\n    BackgroundFrameCount[187]++;\n    if(BackgroundFrameCount[187] >= 6)\n    {\n        BackgroundFrameCount[187] = 0;\n\n        BackgroundFrame[187]++;\n        if(BackgroundFrame[187] >= 4)\n            BackgroundFrame[187] = 0;\n\n        BackgroundFrame[188] = BackgroundFrame[187];\n        BackgroundFrame[189] = BackgroundFrame[187];\n        BackgroundFrame[190] = BackgroundFrame[187];\n    }\n\n    // Update Coin Frames\n    CoinFrame2[1] += 1;\n    if(CoinFrame2[1] >= 6)\n    {\n        CoinFrame2[1] = 0;\n        CoinFrame[1] += 1;\n        if(CoinFrame[1] >= 4)\n            CoinFrame[1] = 0;\n    }\n\n    CoinFrame2[2] += 1;\n    if(CoinFrame2[2] >= 6)\n    {\n        CoinFrame2[2] = 0;\n        CoinFrame[2] += 1;\n        if(CoinFrame[2] >= 7)\n            CoinFrame[2] = 0;\n    }\n\n    CoinFrame2[3] += 1;\n    if(CoinFrame2[3] >= 7)\n    {\n        CoinFrame2[3] = 0;\n        CoinFrame[3] += 1;\n        if(CoinFrame[3] >= 4)\n            CoinFrame[3] = 0;\n    }\n}\n\nvoid WorldFrames()\n{\n    SceneFrame2[1] += 1;\n    if(SceneFrame2[1] >= 12)\n    {\n        SceneFrame2[1] = 0;\n        SceneFrame[1] += 1;\n        if(SceneFrame[1] >= 4)\n            SceneFrame[1] = 0;\n        SceneFrame[4] = SceneFrame[1];\n        SceneFrame[5] = SceneFrame[1];\n        SceneFrame[6] = SceneFrame[1];\n        SceneFrame[9] = SceneFrame[1];\n        SceneFrame[10] = SceneFrame[1];\n        SceneFrame[12] = SceneFrame[1];\n        SceneFrame[51] = SceneFrame[1];\n        SceneFrame[52] = SceneFrame[1];\n        SceneFrame[53] = SceneFrame[1];\n        SceneFrame[54] = SceneFrame[1];\n        SceneFrame[55] = SceneFrame[1];\n    }\n    SceneFrame2[27] += 1;\n    if(SceneFrame2[27] >= 8)\n    {\n        SceneFrame2[27] = 0;\n        SceneFrame[27] += 1;\n        if(SceneFrame[27] >= 12)\n            SceneFrame[27] = 0;\n        SceneFrame[28] = SceneFrame[27];\n        SceneFrame[29] = SceneFrame[27];\n        SceneFrame[30] = SceneFrame[27];\n    }\n    SceneFrame2[33] += 1;\n    if(SceneFrame2[33] >= 4)\n    {\n        SceneFrame2[33] = 0;\n        SceneFrame[33] = SceneFrame[27] + 1;\n        if(SceneFrame[33] >= 14)\n            SceneFrame[33] = 0;\n        SceneFrame[34] = SceneFrame[33];\n    }\n    SceneFrame2[62] += 1;\n    if(SceneFrame2[62] >= 8)\n    {\n        SceneFrame2[62] = 0;\n        SceneFrame[62] += 1;\n        if(SceneFrame[62] >= 8)\n            SceneFrame[62] = 0;\n        SceneFrame[63] = SceneFrame[62];\n    }\n    LevelFrame2[2] += 1;\n    if(LevelFrame2[2] >= 6)\n    {\n        LevelFrame2[2] = 0;\n        LevelFrame[2] += 1;\n        if(LevelFrame[2] >= 6)\n            LevelFrame[2] = 0;\n        LevelFrame[9] = LevelFrame[2];\n        LevelFrame[13] = LevelFrame[2];\n        LevelFrame[14] = LevelFrame[2];\n        LevelFrame[15] = LevelFrame[2];\n        LevelFrame[31] = LevelFrame[2];\n        LevelFrame[32] = LevelFrame[2];\n    }\n    LevelFrame2[8] += 1;\n    if(LevelFrame2[8] >= 12)\n    {\n        LevelFrame2[8] = 0;\n        LevelFrame[8] += 1;\n        if(LevelFrame[8] >= 4)\n            LevelFrame[8] = 0;\n    }\n    LevelFrame2[12] += 1;\n    if(LevelFrame2[12] >= 8)\n    {\n        LevelFrame2[12] = 0;\n        LevelFrame[12] += 1;\n        if(LevelFrame[12] >= 2)\n            LevelFrame[12] = 0;\n    }\n    LevelFrame2[25] += 1;\n    if(LevelFrame2[25] >= 8)\n    {\n        LevelFrame2[25] = 0;\n        LevelFrame[25] += 1;\n        if(LevelFrame[25] >= 4)\n            LevelFrame[25] = 0;\n        LevelFrame[26] = LevelFrame[25];\n    }\n    TileFrame2[14] += 1;\n    if(TileFrame2[14] >= 14)\n    {\n        TileFrame2[14] = 0;\n        TileFrame[14] += 1;\n        if(TileFrame[14] >= 4)\n            TileFrame[14] = 0;\n        TileFrame[27] = TileFrame[14];\n        TileFrame[241] = TileFrame[14];\n    }\n}\n"
  },
  {
    "path": "src/graphics/gfx_special_frames.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GFX_SPECIAL_FRAMES_H\n#define GFX_SPECIAL_FRAMES_H\n\n#include \"globals.h\"\n\n// Private Sub SpecialFrames() 'update frames for special things such as coins and kuribo's shoe\n// update frames for special things such as coins and kuribo's shoe (pauses during FreezeNPCs)\nextern void SpecialFrames();//PRIVATE\n\n// updates CommonFrame and CommonFrame_Unpaused\ninline void CommonFrames()\n{\n    CommonFrame++;\n\n    if(LevelSelect || !FreezeNPCs)\n        CommonFrame_NotFrozen++;\n}\n\n// NEW: update level frames that keep going during FreezeNPCs and pause\nvoid LevelFramesAlways();\n\n// NEW: update level frames that stop during FreezeNPCs (but continue during pause)\nvoid LevelFramesNotFrozen();\n\n// NEW: update world frames\nvoid WorldFrames();\n\n#endif // #ifndef GFX_SPECIAL_FRAMES_H\n"
  },
  {
    "path": "src/graphics/gfx_update.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"screen.h\"\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include <sorting/pdqsort.h>\n#include <array>\n#include <bitset>\n\n#include \"../globals.h\"\n#include \"../frame_timer.h\"\n#include \"../graphics.h\"\n#include \"../collision.h\"\n#include \"../editor.h\"\n#include \"../npc.h\"\n#include \"../player.h\"\n#include \"../gfx.h\"\n#include \"../layers.h\"\n#include \"../main/menu_main.h\"\n#include \"../main/speedrunner.h\"\n#include \"../main/trees.h\"\n#include \"../main/screen_pause.h\"\n#include \"../main/screen_connect.h\"\n#include \"../main/screen_options.h\"\n#include \"../main/screen_quickreconnect.h\"\n#include \"../main/screen_textentry.h\"\n#include \"../main/cheat_code.h\"\n#include \"../config.h\"\n#include \"message.h\"\n#include \"../game_main.h\"\n#include \"../main/game_globals.h\"\n#include \"main/world_globals.h\"\n#include \"main/level_medals.h\"\n#include \"../core/render.h\"\n#include \"../script/luna/luna.h\"\n\n#include \"npc/npc_activation.h\"\n#include \"npc/npc_queues.h\"\n#include \"npc/section_overlap.h\"\n\n#include \"effect.h\"\n#include \"npc_id.h\"\n#include \"eff_id.h\"\n#include \"npc_traits.h\"\n#include \"draw_planes.h\"\n\n#include \"graphics/gfx_special_frames.h\"\n#include \"graphics/gfx_camera.h\"\n#include \"graphics/gfx_keyhole.h\"\n\n#ifdef THEXTECH_BUILD_GL_MODERN\n#    include \"core/opengl/gl_program_bank.h\"\n#endif\n\n#include <fmt_format_ne.h>\n\nstruct ScreenShake_t\n{\n    // these are all multiplied by 0.001 pixels\n    int    forceX = 0;\n    int    forceY = 0;\n    int    forceDecay = 1000;\n\n    // these are in real pixels\n    int    offsetX = 0;\n    int    offsetY = 0;\n    int    type = SHAKE_RANDOM;\n    int    duration = 0;\n    int    sign = +1;\n\n    bool   active = false;\n\n    void update()\n    {\n        if(!active || GameMenu)\n            return;\n\n        if(duration <= 0)\n        {\n            if(forceX > 0)\n                forceX -= forceDecay;\n            if(forceY > 0)\n                forceY -= forceDecay;\n        }\n        else\n            duration--;\n\n        if(forceX <= 0 && forceY <= 0)\n        {\n            forceX = 0;\n            forceY = 0;\n            active = false;\n        }\n        // always perform this section to keep the number of random calls consistent w/legacy sources\n        {\n            switch(type)\n            {\n            default:\n            case SHAKE_RANDOM:\n                offsetX = iRand(forceX / 250) - forceX / 500;\n                offsetY = iRand(forceY / 250) - forceY / 500;\n                break;\n            case SHAKE_SEQUENTIAL:\n                offsetX = forceX > 0 ? sign * ((forceX + 500) / 1000) : 0;\n                offsetY = forceY > 0 ? sign * ((forceY + 500) / 1000) : 0;\n                sign *= -1;\n                break;\n            }\n        }\n    }\n\n    void apply()\n    {\n        if(!active || GameMenu || !g_config.show_screen_shake)\n        {\n            XRender::offsetViewport(0, 0);\n            return;\n        }\n\n        XRender::offsetViewport(offsetX, offsetY);\n        XRender::offsetViewportIgnore(false);\n    }\n\n    void setup(int i_forceX, int i_forceY, int i_type, int i_duration, int i_decay)\n    {\n        if(GameMenu)\n            return;\n\n        i_forceX *= 1000;\n        i_forceY *= 1000;\n\n        if((forceX <= 0 && forceY <= 0) || (forceDecay < i_decay))\n            forceDecay = i_decay;\n\n        // don't override random shake by sequential while random shake is active\n        if((forceX <= 0 && forceY <= 0) || (type != SHAKE_RANDOM))\n            type = i_type;\n\n        if(forceX < i_forceX)\n            forceX = i_forceX;\n        if(forceY < i_forceY)\n            forceY = i_forceY;\n        if(duration < i_duration)\n            duration = i_duration;\n\n        active = true;\n    }\n\n    void clear()\n    {\n        forceX = 0;\n        forceY = 0;\n        duration = 0;\n        active = false;\n    }\n};\n\nstatic std::array<ScreenShake_t, c_vScreenCount_visible + 1> s_shakeScreen;\nstatic std::array<bool, c_vScreenCount_visible + 1> s_forcedShakeScreen{false};\n\n//static double s_shakeScreenX = 0;\n//static double s_shakeScreenY = 0;\n//static int s_shakeScreenType = SHAKE_RANDOM;\n//static double s_shakeScreenDuration = 0;\n//static double s_shakeScreenSign = +1.0;\n\nstatic inline int s_round2int(num_t d)\n{\n    return num_t::floor(d + 0.5_n);\n}\n\nvoid doShakeScreen(int force, int type)\n{\n    for(auto& shake : s_shakeScreen)\n        shake.setup(force, force, type, 0, 1000);\n}\n\nvoid doShakeScreen(int forceX, int forceY, int type, int duration, int decay, const Location_t& source)\n{\n    for(int Z = 0; Z <= c_vScreenCount_visible; Z++)\n    {\n        if(Z == 0 || vScreenCollision(Z, source))\n            s_shakeScreen[Z].setup(forceX, forceY, type, duration, decay);\n    }\n}\n\nvoid doShakeScreenClear()\n{\n    for(auto& shake : s_shakeScreen)\n        shake.clear();\n\n    for(bool& forced : s_forcedShakeScreen)\n        forced = false;\n}\n\n// some \"special\" logic for pet mounts, used to be in the draw code\nstatic void PetFrameLogic(NPC_t& n)\n{\n    if(n.Special == 0)\n    {\n        if(!FreezeNPCs)\n            n.FrameCount += 1;\n\n        if(n.FrameCount >= 70)\n        {\n            if(!FreezeNPCs)\n                n.FrameCount = 0;\n        }\n    }\n    else\n    {\n        if(!FreezeNPCs)\n            n.FrameCount += 1;\n\n        if(n.FrameCount > 8)\n            n.FrameCount = 0;\n\n        if(!FreezeNPCs)\n            n.Special2 += 1;\n\n        if(n.Special2 > 30)\n        {\n            if(!FreezeNPCs)\n                n.Special2 = 0;\n        }\n    }\n\n}\n\n\n// this organizes all of the NPC draw conditions into one queue\nclass NPC_Draw_Queue_t\n{\n#ifndef LOW_MEM\n    static constexpr int maxDrawNPCs = maxNPCs;\n#else\n    static constexpr int maxDrawNPCs = 512;\n#endif\npublic:\n    uint16_t BG[maxDrawNPCs];\n    size_t BG_n;\n    uint16_t Low[maxDrawNPCs];\n    size_t Low_n;\n    uint16_t Iced[maxDrawNPCs];\n    size_t Iced_n;\n    uint16_t Normal[maxDrawNPCs];\n    size_t Normal_n;\n    uint16_t Chat[20];\n    size_t Chat_n;\n    uint16_t Held[20];\n    size_t Held_n;\n    uint16_t FG[maxDrawNPCs];\n    size_t FG_n;\n    uint16_t Dropped[20];\n    size_t Dropped_n;\n    uint16_t Warning[32];\n    size_t Warning_n;\n\n    // reset the draw queue for the frame\n    void reset()\n    {\n        BG_n = Low_n = Iced_n = Normal_n = Chat_n = Held_n = FG_n = Dropped_n = Warning_n = 0;\n    }\n\n    // add NPC with index A to the draw queue, according to its type properties\n    void add(uint16_t A)\n    {\n        if(NPC[A].Chat)\n        {\n            if(Chat_n != sizeof(Chat) / sizeof(uint16_t))\n            {\n                Chat[Chat_n] = A;\n                Chat_n += 1;\n            }\n        }\n\n        if(NPC[A].Effect == NPCEFF_DROP_ITEM)\n        {\n            if(NPC[A].Effect2 % 3 == 0)\n                return;\n            if(Dropped_n == sizeof(Dropped) / sizeof(uint16_t))\n                return;\n            Dropped[Dropped_n] = A;\n            Dropped_n += 1;\n        }\n        else if(\n                (\n                  (\n                    (NPC[A].HoldingPlayer > 0 && Player[NPC[A].HoldingPlayer].Effect != PLREFF_WARP_PIPE && Player[NPC[A].HoldingPlayer].CurMazeZone == 0) ||\n                    (NPC[A].Type == NPCID_TOOTHY && NPC[A].vehiclePlr == 0) ||\n                    (NPC[A].Type == NPCID_BULLET && NPC[A].CantHurt > 0)\n                  ) || NPC[A].Effect == NPCEFF_PET_TONGUE\n                ) && NPC[A].Type != NPCID_ITEM_BURIED && !Player[NPC[A].HoldingPlayer].Dead\n            )\n        {\n            if(Held_n == sizeof(Held) / sizeof(uint16_t))\n                return;\n            Held[Held_n] = A;\n            Held_n += 1;\n        }\n        else if(NPC[A].Effect == NPCEFF_NORMAL && NPC[A]->Foreground && NPC[A].HoldingPlayer == 0 && !NPC[A]->IsACoin)\n        {\n            if(FG_n == sizeof(FG) / sizeof(uint16_t))\n                return;\n            FG[FG_n] = A;\n            FG_n += 1;\n        }\n        else if(NPC[A].Type == NPCID_ICE_CUBE && NPC[A].Effect == NPCEFF_NORMAL && NPC[A].HoldingPlayer == 0)\n        {\n            if(Iced_n == sizeof(Iced) / sizeof(uint16_t))\n                return;\n            Iced[Iced_n] = A;\n            Iced_n += 1;\n        }\n        else if(NPC[A].Effect == NPCEFF_NORMAL && NPC[A].HoldingPlayer == 0 &&\n            (NPC[A].vehiclePlr > 0 || NPC[A].Type == NPCID_VEHICLE || NPC[A].Type == NPCID_CANNONITEM ||\n                NPC[A].Type == NPCID_TOOTHYPIPE || NPC[A].Type == NPCID_ITEM_BURIED || NPC[A].Type == NPCID_ROCKET_WOOD ||\n                NPC[A].Type == NPCID_FIRE_BOSS_FIRE || NPC[A]->IsACoin))\n        {\n            if(Low_n == sizeof(Low) / sizeof(uint16_t))\n                return;\n            Low[Low_n] = A;\n            Low_n += 1;\n        }\n        else if(NPC[A].Type == NPCID_SAW || NPC[A].Type == NPCID_JUMP_PLANT ||\n            NPC[A].Effect == NPCEFF_MAZE || (NPC[A].HoldingPlayer && Player[NPC[A].HoldingPlayer].CurMazeZone != 0) ||\n            ((NPC[A].Effect == NPCEFF_ENCASED || NPC[A]->IsAVine ||\n                    NPC[A].Type == NPCID_BOSS_FRAGILE || NPC[A].Type == NPCID_LIFT_SAND || NPC[A].Type == NPCID_FIRE_PLANT ||\n                    NPC[A].Type == NPCID_PLANT_S3 || NPC[A].Type == NPCID_PLANT_S1 || NPC[A].Type == NPCID_BIG_PLANT ||\n                    NPC[A].Type == NPCID_LONG_PLANT_UP || NPC[A].Type == NPCID_LONG_PLANT_DOWN || NPC[A].Type == NPCID_BOTTOM_PLANT ||\n                    NPC[A].Type == NPCID_SIDE_PLANT || NPC[A].Effect == NPCEFF_EMERGE_UP || NPC[A].Effect == NPCEFF_EMERGE_DOWN ||\n                    NPC[A].Effect == NPCEFF_WARP || (NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 0))\n                && NPC[A].vehiclePlr == 0))\n        {\n            if(BG_n == sizeof(BG) / sizeof(uint16_t))\n                return;\n            BG[BG_n] = A;\n            BG_n += 1;\n\n            // vine frames (moved from UpdateNPCs)\n            if(NPC[A]->IsAVine)\n            {\n                if(NPC[A].Type == NPCID_GRN_VINE_S3 || NPC[A].Type == NPCID_RED_VINE_S3)\n                    NPC[A].Frame = BlockFrame[5];\n                else if(NPC[A].Type >= NPCID_GRN_VINE_S2 && NPC[A].Type <= NPCID_BLU_VINE_BASE_S2)\n                    NPC[A].Frame = SpecialFrame[7];\n            }\n        }\n        else if(NPC[A].Effect == NPCEFF_NORMAL && NPC[A].HoldingPlayer == 0)\n        {\n            if(Normal_n == sizeof(Normal) / sizeof(uint16_t))\n                return;\n\n            Normal[Normal_n] = A;\n            Normal_n += 1;\n\n            // some \"special\" logic that has been moved into the logic portion\n            if(NPCIsYoshi(NPC[A].Type))\n                PetFrameLogic(NPC[A]);\n        }\n    }\n\n    void add_warning(uint16_t A)\n    {\n        if(Warning_n != sizeof(Warning) / sizeof(uint16_t))\n        {\n            Warning[Warning_n] = A;\n            Warning_n += 1;\n        }\n    }\n\n    // sort each queue properly\n    void sort()\n    {\n        pdqsort(&BG[0], &BG[BG_n]);\n        pdqsort(&Low[0], &Low[Low_n]);\n        pdqsort(&Iced[0], &Iced[Iced_n]);\n        pdqsort(&Normal[0], &Normal[Normal_n]);\n        pdqsort(&Chat[0], &Chat[Chat_n]);\n        pdqsort(&Held[0], &Held[Held_n]);\n        pdqsort(&FG[0], &FG[FG_n]);\n        pdqsort(&Dropped[0], &Dropped[Dropped_n]);\n        pdqsort(&Warning[0], &Warning[Warning_n]);\n    }\n};\n\nNPC_Draw_Queue_t NPC_Draw_Queue[maxLocalPlayers] = {NPC_Draw_Queue_t(), NPC_Draw_Queue_t()};\n\nconstexpr int NPC_intro_length = 8;\nconstexpr uint8_t NPC_shade_opacity = 100;\nconstexpr size_t NPC_intro_count_MAX = 32;\n\nuint8_t NPC_intro_count = 0;\nint16_t NPC_intro[NPC_intro_count_MAX];\n// positive values represent just-activated onscreen NPCs; negative values represent conditionally active NPCs\nint8_t NPC_intro_frame[NPC_intro_count_MAX];\n\nstatic inline void s_RemoveIntroNPC(int i)\n{\n    NPC_intro_count--;\n    NPC_intro[i] = NPC_intro[NPC_intro_count];\n    NPC_intro_frame[i] = NPC_intro_frame[NPC_intro_count];\n}\n\nstatic inline void ProcessIntroNPCFrames()\n{\n    for(uint8_t i = 0; i < NPC_intro_count; i++)\n    {\n        NPC_intro_frame[i]++;\n\n        // two termination conditions, remove the NPC from the intro list either way\n        if(NPC_intro_frame[i] == NPC_intro_length || NPC_intro_frame[i] == 0)\n        {\n            s_RemoveIntroNPC(i);\n\n            // don't advance the iteration counter, since a new NPC is here now\n            i--;\n        }\n    }\n}\n\n// tints NPCS\nstatic inline void s_get_NPC_tint(int A, XTColor& cn)\n{\n    const NPC_t& n = NPC[A];\n\n    if(!LevelEditor)\n    {\n        if(!n.Active)\n        {\n            if(!NPC_InactiveRender(n) || g_CheatLogicScreen)\n            {\n                cn = XTColorF(0.0_n, 0.0_n, 0.0_n, 0.4_n);\n                return;\n            }\n        }\n        else for(uint8_t i = 0; i < NPC_intro_count; i++)\n        {\n            if(NPC_intro[i] == A)\n            {\n                if(NPC_intro_frame[i] >= 0)\n                {\n                    uint8_t c = uint8_t(256 * NPC_intro_frame[i] / NPC_intro_length);\n                    uint8_t a_add = uint8_t((256 - NPC_shade_opacity) * NPC_intro_frame[i] / NPC_intro_length);\n                    cn = XTColor(c, c, c, NPC_shade_opacity + a_add);\n                    return;\n                }\n                break;\n            }\n        }\n    }\n\n    cn = n.Shadow ? XTColor(64, 64, 64) : XTColor();\n\n    if(n.Effect == NPCEFF_DROP_ITEM && n.Effect3 != 0)\n    {\n        cn = (NPC[A].Special5 <= 66) ? XTAlpha(128 + (66 - NPC[A].Special5) + (int)(32 * num_t::cos((num_t)NPC[A].Special5 / 4))) : XTAlpha(128);\n    }\n}\n\n// draws a warning icon for offscreen active NPC A on vScreen Z\nvoid DrawWarningNPC(int Z, num_t camX, num_t camY, int A)\n{\n    g_stats.renderedNPCs++;\n\n    XTColor cn;\n    s_get_NPC_tint(A, cn);\n\n    int scr_x = num_t::floor(camX + NPC[A].Location.X) + NPC[A]->FrameOffsetX;\n    int scr_y = num_t::floor(camY + NPC[A].Location.Y) + NPC[A]->FrameOffsetY;\n    int w = s_round2int(NPC[A].Location.Width);\n    int h = s_round2int(NPC[A].Location.Height);\n    int frame_h = NPC[A]->THeight;\n\n    int frame_x = 0, frame_y = 0;\n\n    // some special cases: plants that come from below\n    if(NPC[A].Type == NPCID_PLANT_S3 || NPC[A].Type == NPCID_BIG_PLANT || NPC[A].Type == NPCID_PLANT_S1 || NPC[A].Type == NPCID_FIRE_PLANT || NPC[A].Type == NPCID_LONG_PLANT_UP || NPC[A].Type == NPCID_JUMP_PLANT)\n    {\n        // these are actually normal but don't have WidthGFX set to 0\n    }\n    // plants from above\n    else if(NPC[A].Type == NPCID_BOTTOM_PLANT || NPC[A].Type == NPCID_LONG_PLANT_DOWN)\n    {\n        frame_y = frame_h - h;\n    }\n    // plants from side\n    else if(NPC[A].Type == NPCID_SIDE_PLANT)\n    {\n        if(NPC[A].Direction != -1)\n            frame_x = NPC[A]->TWidth - w;\n    }\n    else if(NPC[A]->WidthGFX == 0)\n    {\n        // this is the most normal case\n    }\n    else\n    {\n        if(NPC[A].Direction == 1)\n            scr_x -= NPC[A]->FrameOffsetX * 2;\n\n        scr_x += (w - NPC[A]->WidthGFX) / 2;\n        scr_y += (h - NPC[A]->HeightGFX);\n\n        w = NPC[A]->WidthGFX;\n        frame_h = h = NPC[A]->HeightGFX;\n    }\n\n    int left_x = -scr_x;\n    int right_x = scr_x + w - vScreen[Z].Width;\n\n    int add_x = (left_x > 0 ? left_x : (right_x > 0 ? -right_x : 0));\n\n    int top_y = -scr_y;\n    int bottom_y = scr_y + h - vScreen[Z].Height;\n\n    int add_y = (top_y > 0 ? top_y : (bottom_y > 0 ? -bottom_y : 0));\n\n    int total_off = (add_x > 0 ? add_x : -add_x)\n        + (add_y > 0 ? add_y : -add_y);\n\n    if(total_off >= 256)\n        return;\n\n    int a_scale = (int(cn.a) * (256 - total_off)) / 512;\n    if(a_scale > 255)\n        a_scale = 255;\n\n    cn.a = uint8_t(a_scale);\n\n    int scale = 128 + (256 - total_off);\n\n    scr_x += w * (512 - scale) / 1024;\n    scr_y += h * (512 - scale) / 512;\n\n    int exclam_x = 2;\n    int exclam_y = 2;\n\n    // push it onto the screen\n    if(scr_x < 0)\n    {\n        scr_x = 0;\n        exclam_x = 1;\n    }\n    else if(scr_x + w * scale / 512 > vScreen[Z].Width)\n    {\n        scr_x = vScreen[Z].Width - w * scale / 512;\n        exclam_x = 3;\n    }\n\n    if(scr_y < 0)\n    {\n        scr_y = 0;\n        exclam_y = 1;\n    }\n    else if(scr_y + h * scale / 512 > vScreen[Z].Height)\n    {\n        scr_y = vScreen[Z].Height - h * scale / 512;\n        exclam_y = 3;\n    }\n\n    XRender::renderTextureScaleEx(scr_x,\n        scr_y,\n        w * scale / 512, h * scale / 512,\n        GFXNPC[NPC[A].Type],\n        frame_x, NPC[A].Frame * frame_h + frame_y,\n        w, h,\n        0, nullptr, X_FLIP_NONE,\n        cn);\n\n    XRender::renderTextureBasic(scr_x + (w * scale / 512 - GFX.Chat.w) * exclam_x / 4,\n        scr_y + (h * scale / 512 - GFX.Chat.h) * exclam_y / 4,\n        GFX.Chat,\n        {255, 0, 0, cn.a});\n}\n\n// draw wings for an NPC\nvoid DrawNPCWings(const NPC_t& n, int sX, int sY, XTColor cn)\n{\n    int w = (int)(n->TWidth);\n    int h = (int)(n->THeight);\n    int h_gfx = (n->HeightGFX) ? n->HeightGFX : h;\n    int npcY = (int)(n.Location.Y);\n\n    int draw_frame = ((npcY - CommonFrame_NotFrozen) >> 5) & 1;\n\n    bool one_direction = (w <= 64);\n\n    if(n.Type == NPCID_MINIBOSS || n.Type == NPCID_QUAD_SPITTER)\n    {\n        one_direction = false;\n        sX += 8;\n        w -= 16;\n    }\n    else if(n.Type == NPCID_SPRING && !LevelEditor)\n        sY -= 16;\n\n    for(int direction = 0; direction < 2; direction++)\n    {\n        if(one_direction)\n            direction = n.Direction;\n\n        if(direction == 1)\n            draw_frame += 2;\n\n        XRender::renderTextureBasic((direction == 1) ? (sX - 20) : (sX + w + 20 - 32),\n                              sY + h - h_gfx / 4 - 32,\n                              32, 32, GFX.YoshiWings, 0, 0 + 32 * draw_frame, cn);\n\n        if(one_direction)\n            break;\n    }\n}\n\nvoid DrawNPC(num_t camX, num_t camY, int A)\n{\n    g_stats.renderedNPCs++;\n\n    XTColor cn;\n    s_get_NPC_tint(A, cn);\n\n    int sX = num_t::floor(camX + NPC[A].Location.X);\n    int sY = num_t::floor(camY + NPC[A].Location.Y);\n    int drawX = sX + NPC[A]->FrameOffsetX;\n    int drawY = sY + NPC[A]->FrameOffsetY;\n    int w = s_round2int(NPC[A].Location.Width);\n    int h = s_round2int(NPC[A].Location.Height);\n\n    int src_x = NPC[A].GFXSlot * ((NPC[A]->WidthGFX != 0) ? NPC[A]->WidthGFX : NPC[A]->TWidth);\n    if(src_x >= GFXNPCBMP[NPC[A].Type].w)\n        src_x = 0;\n\n    if(NPC[A].Type == NPCID_MEDAL && g_curLevelMedals.gotten(NPC[A].Variant - 1))\n        cn.a /= 2;\n\n    if(NPC[A].Type == NPCID_PLANT_S3 || NPC[A].Type == NPCID_BIG_PLANT || NPC[A].Type == NPCID_PLANT_S1 || NPC[A].Type == NPCID_FIRE_PLANT || NPC[A].Type == NPCID_LONG_PLANT_UP || NPC[A].Type == NPCID_JUMP_PLANT || NPC[A].Type == NPCID_BOTTOM_PLANT || NPC[A].Type == NPCID_LONG_PLANT_DOWN || NPC[A].Type == NPCID_SIDE_PLANT)\n    {\n        int x_off = 0;\n        if(NPC[A].Type == NPCID_SIDE_PLANT && NPC[A].Direction != -1)\n            x_off = NPC[A]->TWidth - w;\n\n        int y_off = NPC[A].Frame * NPC[A]->THeight;\n        if(NPC[A].Type == NPCID_BOTTOM_PLANT || NPC[A].Type == NPCID_LONG_PLANT_DOWN)\n            y_off += NPC[A]->THeight - h;\n\n        XRender::renderTextureBasic(drawX, drawY, w, h,\n            GFXNPC[NPC[A].Type],\n            src_x + x_off, y_off,\n            cn);\n    }\n    else if(NPC[A].Type == NPCID_ICE_CUBE)\n        DrawFrozenNPC(camX, camY, A);\n    else if(NPCIsYoshi(NPC[A]))\n    {\n        int B = 1;\n\n        if(NPC[A].Type == NPCID_PET_GREEN)\n            B = 1;\n        else if(NPC[A].Type == NPCID_PET_BLUE)\n            B = 2;\n        else if(NPC[A].Type == NPCID_PET_YELLOW)\n            B = 3;\n        else if(NPC[A].Type == NPCID_PET_RED)\n            B = 4;\n        else if(NPC[A].Type == NPCID_PET_BLACK)\n            B = 5;\n        else if(NPC[A].Type == NPCID_PET_PURPLE)\n            B = 6;\n        else if(NPC[A].Type == NPCID_PET_PINK)\n            B = 7;\n        else if(NPC[A].Type == NPCID_PET_CYAN)\n            B = 8;\n\n        int YoshiBX = 0;\n        int YoshiBY = 0;\n        int YoshiTX = 20;\n        int YoshiTY = -32;\n        int YoshiTFrame = 0;\n        int YoshiBFrame = 0;\n\n        // there was \"special\" logic affecting FrameCount and Special2, moved to logic side\n        if(NPC[A].Special == 0)\n        {\n            if(NPC[A].FrameCount >= 70)\n            {\n                // there was a logic assignment here\n            }\n            else if(NPC[A].FrameCount >= 50)\n                YoshiTFrame = 3;\n\n            YoshiBFrame = 6;\n            YoshiBY += 10;\n            YoshiTY += 10;\n        }\n        else\n        {\n            // FrameCount > 8 remapped to FrameCount = 0 in PetFrameLogic\n            if(NPC[A].FrameCount > 4 && NPC[A].FrameCount < 6) // previously > 4\n            {\n                YoshiBFrame = 2;\n                YoshiTX -= 2;\n                YoshiTY += 4;\n                YoshiBY += 2;\n            }\n            else if(NPC[A].FrameCount > 2) // previously > 6 or > 2\n            {\n                YoshiBFrame = 1;\n                YoshiTX -= 1;\n                YoshiTY += 2;\n                YoshiBY += 1;\n            }\n\n            if(NPC[A].Special2 > 10 && NPC[A].Special2 <= 30)\n                YoshiTFrame = 2;\n        }\n\n        if(NPC[A].Direction == 1)\n        {\n            YoshiTFrame += 5;\n            YoshiBFrame += 7;\n        }\n        else\n        {\n            YoshiBX = -YoshiBX;\n            YoshiTX = -YoshiTX;\n        }\n\n        // Yoshi's Body\n        XRender::renderTextureBasic(sX + YoshiBX, sY + YoshiBY, 32, 32, GFXYoshiB[B], 0, 32 * YoshiBFrame, cn);\n\n        // Yoshi's Head\n        XRender::renderTextureBasic(sX + YoshiTX, sY + YoshiTY, 32, 32, GFXYoshiT[B], 0, 32 * YoshiTFrame, cn);\n    }\n    // fix a graphical SMBX64 bug where the draw width and frame stride were incorrect\n    else if(NPC[A].Effect == NPCEFF_EMERGE_UP)\n    {\n        if(NPC[A]->WidthGFX != 0 && g_config.fix_visual_bugs)\n        {\n            XRender::renderTextureBasic(sX + (NPC[A]->FrameOffsetX * -NPC[A].Direction) - NPC[A]->WidthGFX / 2 + w / 2, drawY, NPC[A]->WidthGFX, h,\n                GFXNPC[NPC[A].Type],\n                src_x, NPC[A].Frame * NPC[A]->HeightGFX,\n                cn);\n        }\n        else\n        {\n            XRender::renderTextureBasic(drawX, drawY, w, h,\n                GFXNPC[NPC[A].Type],\n                src_x, NPC[A].Frame * NPC[A]->THeight,\n                cn);\n        }\n    }\n    else if(NPC[A]->WidthGFX == 0)\n    {\n        XRender::renderTextureBasic(drawX,\n            drawY,\n            w,\n            h,\n            GFXNPC[NPC[A].Type],\n            src_x, NPC[A].Frame * h,\n            cn);\n    }\n    else\n    {\n        if(NPC[A].Type == NPCID_ITEM_BUBBLE && NPC[A].Special > 0)\n        {\n            int contents_w, contents_h;\n\n            if(NPCWidthGFX(NPC[A].Special) == 0)\n            {\n                contents_w = NPCWidth(NPC[A].Special);\n                contents_h = NPCHeight(NPC[A].Special);\n            }\n            else\n            {\n                contents_w = NPCWidthGFX(NPC[A].Special);\n                contents_h = NPCHeightGFX(NPC[A].Special);\n            }\n\n            int contents_sX = sX + w / 2 - contents_w / 2;\n            int contents_sY = sY + h / 2 - contents_h / 2;\n\n            int contents_frame = EditorNPCFrame((NPCID)(NPC[A].Special), NPC[A].Direction);\n\n            // note: using NPC[A]->FrameOffsetX here doesn't really make sense, but it's the SMBX 1.3 logic\n            XRender::renderTextureBasic(contents_sX + NPC[A]->FrameOffsetX, contents_sY, contents_w, contents_h,\n                GFXNPC[NPC[A].Special],\n                NPC[A].GFXSlot * contents_w, contents_frame * contents_h,\n                cn);\n\n            if(NPC[A].DefaultWings)\n                DrawNPCWings(NPC[A], sX, sY, cn);\n\n            src_x = 0;\n        }\n\n        // WARNING: the BG case previously didn't have -NPC[A].Direction here.\n        // I've checked and only NPCID_SAW, NPCID_JUMP_PLANT, NPCID_BOSS_FRAGILE, NPCID_LIFT_SAND, and NPCID_SLIDE_BLOCK could possibly be affected.\n        // in my episode corpus, none of them set gfxoffsetx to a non-zero value (maybe because it acts differently from normal),\n        // but watch for any visual changes that result from this\n        XRender::renderTextureBasic(sX + (NPC[A]->FrameOffsetX * -NPC[A].Direction) - NPC[A]->WidthGFX / 2 + w / 2,\n            drawY - NPC[A]->HeightGFX + h,\n            NPC[A]->WidthGFX,\n            NPC[A]->HeightGFX,\n            GFXNPC[NPC[A].Type],\n            src_x,\n            NPC[A].Frame * NPC[A]->HeightGFX,\n            cn);\n    }\n\n    if(NPC[A].Wings)\n        DrawNPCWings(NPC[A], sX, sY, cn);\n\n    // countdown during modern item drop\n    if(NPC[A].Effect == NPCEFF_DROP_ITEM && NPC[A].Effect3 != 0 && NPC[A].Special5 <= 66)\n    {\n        int i = (NPC[A].Special5 - 1) / 22 + 1;\n        XRender::renderTextureBasic(sX + w / 2 - GFX.Font1[i].w / 2,\n            sY + h / 2 - GFX.Font1[i].h / 2,\n            GFX.Font1[i]);\n    }\n}\n\n// code to facilitate cached values for the onscreen blocks and BGOs\n// intentionally only initialize the vectors for the first 2 screens since >2P mode is rare\n\n// query results of last tree query\nstatic std::vector<BlockRef_t> s_drawMainBlocks[maxLocalPlayers] = {std::vector<BlockRef_t>(400), std::vector<BlockRef_t>(400)};\nstatic std::vector<BlockRef_t> s_drawLavaBlocks[maxLocalPlayers] = {std::vector<BlockRef_t>(100), std::vector<BlockRef_t>(100)};\nstatic std::vector<BlockRef_t> s_drawSBlocks[maxLocalPlayers] = {std::vector<BlockRef_t>(40), std::vector<BlockRef_t>(40)};\nstatic std::vector<BaseRef_t> s_drawBGOs[maxLocalPlayers] = {std::vector<BaseRef_t>(400), std::vector<BaseRef_t>(400)};\n\n// query location of last tree query\nstatic Location_t s_drawBlocks_bounds[maxLocalPlayers];\nstatic Location_t s_drawBGOs_bounds[maxLocalPlayers];\n\n// maximum amount of layer movement since last tree query\nstatic num_t s_drawBlocks_invalidate_timer[maxLocalPlayers] = {0, 0};\nstatic num_t s_drawBGOs_invalidate_timer[maxLocalPlayers] = {0, 0};\n\n// global: force-invalidate the cache when the blocks themselves change\nstd::array<bool, maxLocalPlayers> g_drawBlocks_valid{};\nstd::array<bool, maxLocalPlayers> g_drawBGOs_valid{};\n\n// global: based on layer movement speed, set in layers.cpp\nnum_t g_drawBlocks_invalidate_rate = 0;\nnum_t g_drawBGOs_invalidate_rate = 0;\n\n\n// Performance-tweakable code. This is the margin away from the vScreen that is filled when onscreen blocks and BGOs are calculated.\n//   Larger values result in more offscreen blocks / BGOs being checked.\n//   Smaller values result in more frequent block / BGO table queries.\nconstexpr num_t i_drawBlocks_margin = 64;\nconstexpr num_t i_drawBGOs_margin = 128;\n\n// updates the lists of blocks and BGOs to draw on i'th vScreen of screen\nvoid s_UpdateDrawItems(Screen_t& screen, int i)\n{\n    vScreen_t& vscreen = screen.vScreen(i + 1);\n\n    if(i < 0 || i >= maxLocalPlayers)\n        return;\n\n    // based on layer movement speed\n    s_drawBlocks_invalidate_timer[i] += g_drawBlocks_invalidate_rate;\n    s_drawBGOs_invalidate_timer[i] += g_drawBGOs_invalidate_rate;\n\n\n    // update draw blocks if needed\n    if(!g_drawBlocks_valid[i]\n        || -vscreen.X                  < s_drawBlocks_bounds[i].X                                 + s_drawBlocks_invalidate_timer[i]\n        || -vscreen.X + vscreen.Width  > s_drawBlocks_bounds[i].X + s_drawBlocks_bounds[i].Width  - s_drawBlocks_invalidate_timer[i]\n        || -vscreen.Y                  < s_drawBlocks_bounds[i].Y                                 + s_drawBlocks_invalidate_timer[i]\n        || -vscreen.Y + vscreen.Height > s_drawBlocks_bounds[i].Y + s_drawBlocks_bounds[i].Height - s_drawBlocks_invalidate_timer[i])\n    {\n        g_drawBlocks_valid[i] = true;\n        s_drawBlocks_invalidate_timer[i] = 0;\n\n        // form query location\n        s_drawBlocks_bounds[i] = newLoc(-vscreen.X - i_drawBlocks_margin,\n            -vscreen.Y - i_drawBlocks_margin,\n            vscreen.Width + i_drawBlocks_margin * 2,\n            vscreen.Height + i_drawBlocks_margin * 2);\n\n        // make query (sort by ID as done in vanilla)\n        TreeResult_Sentinel<BlockRef_t> areaBlocks = treeFLBlockQuery(s_drawBlocks_bounds[i], SORTMODE_ID);\n\n        // load query results into different sets of blocks\n        s_drawSBlocks[i].clear();\n        s_drawMainBlocks[i].clear();\n        s_drawLavaBlocks[i].clear();\n\n        for(BlockRef_t b : areaBlocks)\n        {\n            if(b->Hidden)\n                continue;\n            if(b->Type == 0)\n                continue;\n\n            if(BlockIsSizable[b->Type])\n                s_drawSBlocks[i].push_back(b);\n            else if(BlockKills[b->Type])\n                s_drawLavaBlocks[i].push_back(b);\n            else\n                s_drawMainBlocks[i].push_back(b);\n        }\n\n        // sort the Sizable blocks\n        // cross-ref the dead code at sorting.cpp:qSortSBlocks\n        std::stable_sort(s_drawSBlocks[i].begin(), s_drawSBlocks[i].end(),\n            [](BlockRef_t a, BlockRef_t b)\n            {\n                return a->Location.Y < b->Location.Y;\n            });\n    }\n\n    // update draw BGOs if needed\n    if(!g_drawBGOs_valid[i]\n        || -vscreen.X                  < s_drawBGOs_bounds[i].X                               + s_drawBGOs_invalidate_timer[i]\n        || -vscreen.X + vscreen.Width  > s_drawBGOs_bounds[i].X + s_drawBGOs_bounds[i].Width  - s_drawBGOs_invalidate_timer[i]\n        || -vscreen.Y                  < s_drawBGOs_bounds[i].Y                               + s_drawBGOs_invalidate_timer[i]\n        || -vscreen.Y + vscreen.Height > s_drawBGOs_bounds[i].Y + s_drawBGOs_bounds[i].Height - s_drawBGOs_invalidate_timer[i])\n    {\n        g_drawBGOs_valid[i] = true;\n        s_drawBGOs_invalidate_timer[i] = 0;\n\n        // form query location\n        s_drawBGOs_bounds[i] = newLoc(-vscreen.X - i_drawBGOs_margin,\n            -vscreen.Y - i_drawBGOs_margin,\n            vscreen.Width + i_drawBGOs_margin * 2,\n            vscreen.Height + i_drawBGOs_margin * 2);\n\n        // make query (sort by ID as done in vanilla)\n        s_drawBGOs[i].clear();\n        treeBackgroundQuery(s_drawBGOs[i], s_drawBGOs_bounds[i], (LevelEditor) ? SORTMODE_Z : SORTMODE_ID);\n    }\n}\n\nvoid GraphicsLazyPreLoad()\n{\n    // FIXME: update to work for multiple screens\n    // TODO: check if this is needed at caller\n    SetupScreens(false);\n\n    // TODO: check whether this is even safe\n    DynamicScreens();\n\n    int numScreens = Screens[0].active_end();\n\n    if(SingleCoop == 2)\n        numScreens = 1; // fine to be 1, since it would just be run for Z = 2 twice otherwise;\n\n    CenterScreens(Screens[0]);\n\n    For(Z, 1, numScreens)\n    {\n        if(SingleCoop == 2)\n            Z = 2;\n\n        // TODO: need to get vScreen?\n\n        int S = Player[Z].Section;\n        int bg = Background2[S];\n\n        switch(bg)\n        {\n        case 1: // Double-row background\n            XRender::lazyPreLoad(GFXBackground2[1]);\n            XRender::lazyPreLoad(GFXBackground2[2]);\n            break;\n\n        case 2: // Single-row clouds background\n            XRender::lazyPreLoad(GFXBackground2[2]);\n            break;\n\n        case 3: // Double-row background\n            XRender::lazyPreLoad(GFXBackground2[3]);\n            XRender::lazyPreLoad(GFXBackground2[2]);\n            break;\n        case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13:\n            // All these backgrounds do use picture with the number less with 1\n            XRender::lazyPreLoad(GFXBackground2[bg - 1]);\n            break;\n        case 22: // Double-row background\n            XRender::lazyPreLoad(GFXBackground2[22]);\n            XRender::lazyPreLoad(GFXBackground2[2]);\n            break;\n\n        default: // Any other normal backgrounds\n            if(bg < 1 || bg > maxBackgroundType)\n                break; // Don't crash it, stupid!\n            XRender::lazyPreLoad(GFXBackground2[bg]);\n            break;\n        }\n\n        For(A, 1, numPlayers)\n        {\n            Player_t &p = Player[A];\n            int c = p.Character;\n            int s = p.State;\n\n            switch(c)\n            {\n            case 1:\n                XRender::lazyPreLoad(GFXMarioBMP[s]);\n                break;\n            case 2:\n                XRender::lazyPreLoad(GFXLuigiBMP[s]);\n                break;\n            case 3:\n                XRender::lazyPreLoad(GFXPeachBMP[s]);\n                break;\n            case 4:\n                XRender::lazyPreLoad(GFXToadBMP[s]);\n                break;\n            case 5:\n                XRender::lazyPreLoad(GFXLinkBMP[s]);\n                break;\n            default: // Trap\n                abort(); // \"Please fix me up if you implemented a new playable character, see gfx_update.cpp!\"\n                return;\n            }\n        }\n\n        // int64_t fBlock = 0;\n        // int64_t lBlock = 0;\n        // blockTileGet(-vScreen[Z].X, vScreen[Z].Width, fBlock, lBlock);\n        s_UpdateDrawItems(Screens[0], Z - 1);\n\n        for(Block_t& b : s_drawSBlocks[Z - 1])\n        {\n            if(vScreenCollision(Z, b.Location) && !b.Hidden && IF_INRANGE(b.Type, 1, maxBlockType))\n                XRender::lazyPreLoad(GFXBlock[b.Type]);\n        }\n\n        for(Block_t& b : s_drawMainBlocks[Z - 1])\n        {\n            if(vScreenCollision(Z, b.Location) && !b.Hidden && IF_INRANGE(b.Type, 1, maxBlockType))\n                XRender::lazyPreLoad(GFXBlock[b.Type]);\n        }\n\n        for(Block_t& b : s_drawLavaBlocks[Z - 1])\n        {\n            if(vScreenCollision(Z, b.Location) && !b.Hidden && IF_INRANGE(b.Type, 1, maxBlockType))\n                XRender::lazyPreLoad(GFXBlock[b.Type]);\n        }\n\n        for(BackgroundRef_t bgo : s_drawBGOs[Z - 1])\n        {\n            Background_t& b = bgo;\n            if(vScreenCollision(Z, b.Location) && !b.Hidden && IF_INRANGE(b.Type, 1, maxBackgroundType))\n                XRender::lazyPreLoad(GFXBackgroundBMP[b.Type]);\n        }\n\n        for(int A = 1; A <= numNPCs; A++)\n        {\n            auto &n = NPC[A];\n            if(vScreenCollision(Z, n.Location) && IF_INRANGE(n.Type, 0, maxNPCType))\n                XRender::lazyPreLoad(GFXNPC[n.Type]);\n        }\n    }\n}\n\n// swappable buffer for previous frame's NoReset NPCs\nstatic std::vector<NPCRef_t> s_NoReset_NPCs_LastFrame;\n\n// shared between the NPC screen logic functions, always reset to 0 between frames\nstatic std::bitset<maxNPCs> s_NPC_present;\n\n// does the classic (\"onscreen\") NPC activation / reset logic for vScreen Z, directly based on the many NPC loops of the original game\nvoid ClassicNPCScreenLogic(int Z, int numScreens, bool fill_draw_queue, NPC_Draw_Queue_t& NPC_Draw_Queue_p)\n{\n    // using bitset here instead of simpler set because I benchmarked it to be faster -- ds-sloth\n    std::bitset<maxNPCs>& NPC_present = s_NPC_present;\n\n    // find the onscreen NPCs\n    TreeResult_Sentinel<NPCRef_t> _screenNPCs = treeNPCQuery(-vScreen[Z].X, -vScreen[Z].Y,\n        -vScreen[Z].X + vScreen[Z].Width, -vScreen[Z].Y + vScreen[Z].Height,\n        SORTMODE_NONE);\n\n    // combine the onscreen NPCs with the no-reset NPCs\n    std::vector<BaseRef_t>& checkNPCs = *_screenNPCs.i_vec;\n\n    // mark previous ones as checked\n    for(int16_t n : checkNPCs)\n        NPC_present[n] = true;\n\n    // add the previous frame's no-reset NPCs\n    for(int16_t n : s_NoReset_NPCs_LastFrame)\n    {\n        if(n <= numNPCs && !NPC_present[n])\n        {\n            checkNPCs.push_back(n);\n            NPC_present[n] = true;\n        }\n    }\n\n    // cleanup the checked NPCs\n    for(int16_t n : checkNPCs)\n        NPC_present[n] = false;\n\n    // allocate it outside the loop; use it only when needed\n    Location_t npcALoc;\n\n    // following logic is somewhat difficult to read but includes the precise conditions\n    // from each NPC check in the original UpdateGraphics to determine how to handle each NPC\n    for(int A : checkNPCs)\n    {\n        bool has_ALoc = false;\n        bool check_both_reset = false;\n        bool check_long_life = false;\n        bool activate_conveyer = false;\n        bool kill_zero = false;\n        bool set_justactivated = true;\n        bool can_check = false;\n        bool reset_all = true;\n\n        if(((NPC[A].Effect == NPCEFF_ENCASED || NPC[A]->IsAVine ||\n             NPC[A].Type == NPCID_BOSS_FRAGILE || NPC[A].Type == NPCID_LIFT_SAND || NPC[A].Type == NPCID_FIRE_PLANT ||\n             NPC[A].Type == NPCID_PLANT_S3 || NPC[A].Type == NPCID_PLANT_S1 || NPC[A].Type == NPCID_BIG_PLANT ||\n             NPC[A].Type == NPCID_LONG_PLANT_UP || NPC[A].Type == NPCID_LONG_PLANT_DOWN || NPC[A].Type == NPCID_BOTTOM_PLANT ||\n             NPC[A].Type == NPCID_SIDE_PLANT || NPC[A].Effect == NPCEFF_EMERGE_UP || NPC[A].Effect == NPCEFF_EMERGE_DOWN ||\n             NPC[A].Effect == NPCEFF_WARP || NPC[A].Effect == NPCEFF_MAZE || (NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 0)) &&\n             (NPC[A].vehiclePlr == 0 && (!NPC[A].Generator || LevelEditor))) ||\n             NPC[A].Type == NPCID_SAW || NPC[A].Type == NPCID_JUMP_PLANT)\n        {\n            if(NPC[A].Effect != NPCEFF_DROP_ITEM && (!NPC[A].Generator || LevelEditor))\n            {\n                can_check = true;\n            }\n        }\n\n        if(NPC[A].Effect == NPCEFF_NORMAL && ((NPC[A].HoldingPlayer == 0 && (NPC[A].vehiclePlr > 0 || NPC[A].Type == NPCID_VEHICLE ||\n                                   NPC[A].Type == NPCID_CANNONITEM || NPC[A].Type == NPCID_TOOTHYPIPE || NPC[A].Type == NPCID_ITEM_BURIED || NPC[A].Type == NPCID_ROCKET_WOOD ||\n                                   NPC[A].Type == NPCID_FIRE_BOSS_FIRE || NPC[A]->IsACoin) && (!NPC[A].Generator || LevelEditor))))\n        {\n            npcALoc = newLoc(NPC[A].Location.X - (NPC[A]->WidthGFX - NPC[A].Location.Width) / 2,\n                                  NPC[A].Location.Y,\n                                  NPC[A]->WidthGFX,\n                                  NPC[A]->THeight);\n            has_ALoc = true;\n            can_check = true;\n        }\n\n        if(NPC[A].Type == NPCID_ICE_CUBE && NPC[A].Effect == NPCEFF_NORMAL && NPC[A].HoldingPlayer == 0)\n        {\n            npcALoc = newLoc(NPC[A].Location.X - (NPC[A]->WidthGFX - NPC[A].Location.Width) / 2,\n                                  NPC[A].Location.Y,\n                                  NPC[A]->WidthGFX,\n                                  NPC[A]->THeight);\n\n            has_ALoc = true;\n            can_check = true;\n        }\n\n\n        if(NPC[A].Effect == NPCEFF_NORMAL)\n        {\n            if(!(NPC[A].HoldingPlayer > 0 || NPC[A]->IsAVine || NPC[A].Type == NPCID_BOSS_FRAGILE || NPC[A].Type == NPCID_FIRE_BOSS_FIRE ||\n                 NPC[A].Type == NPCID_JUMP_PLANT || NPC[A].Type == NPCID_ROCKET_WOOD || NPC[A].Type == NPCID_LIFT_SAND || NPC[A].Type == NPCID_PLANT_S3 || NPC[A].Type == NPCID_FIRE_PLANT ||\n                 NPC[A].Type == NPCID_PLANT_S1 || NPC[A].Type == NPCID_BOTTOM_PLANT || NPC[A].Type == NPCID_SIDE_PLANT || NPC[A].Type == NPCID_BIG_PLANT || NPC[A].Type == NPCID_LONG_PLANT_UP ||\n                 NPC[A].Type == NPCID_LONG_PLANT_DOWN || NPC[A].Type == NPCID_VEHICLE || NPC[A].Type == NPCID_CANNONITEM || NPC[A].Type == NPCID_TOOTHYPIPE || NPC[A].Type == NPCID_ITEM_BURIED) &&\n               !(NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 0) && NPC[A].vehiclePlr == 0 &&\n               !NPC[A]->Foreground && (!NPC[A].Generator || LevelEditor) &&\n               NPC[A].Type != NPCID_SAW && NPC[A].Type != NPCID_ICE_CUBE)\n            {\n                if(!NPC[A]->IsACoin)\n                {\n                    can_check = true;\n                    kill_zero = true;\n                    activate_conveyer = true;\n                    check_both_reset = true;\n                    check_long_life = true;\n                }\n            }\n        }\n\n\n        const Player_t& hp = Player[NPC[A].HoldingPlayer];\n        bool hp_door_scroll = (NPC[A].HoldingPlayer > 0 && hp.Effect == PLREFF_WARP_DOOR && hp.Effect2 >= 128);\n\n        if(\n            (\n              (\n                (NPC[A].HoldingPlayer > 0 && hp.Effect != PLREFF_WARP_PIPE && !hp_door_scroll) ||\n                (NPC[A].Type == NPCID_TOOTHY && NPC[A].vehiclePlr == 0) ||\n                (NPC[A].Type == NPCID_BULLET && NPC[A].CantHurt > 0)\n              ) || NPC[A].Effect == NPCEFF_PET_TONGUE\n            ) && NPC[A].Type != NPCID_ITEM_BURIED && !Player[NPC[A].HoldingPlayer].Dead\n        )\n        {\n            NPC_Draw_Queue_p.add(A);\n        }\n\n        if(NPC[A].Effect == NPCEFF_NORMAL)\n        {\n            if(NPC[A]->Foreground && NPC[A].HoldingPlayer == 0 && (!NPC[A].Generator || LevelEditor))\n            {\n                if(!NPC[A]->IsACoin)\n                {\n                    can_check = true;\n                    check_both_reset = true;\n                }\n            }\n        }\n\n        if(NPC[A].Generator)\n        {\n            if(vScreenCollision(Z, NPC[A].Location) && !NPC[A].Hidden)\n                NPC[A].GeneratorActive = true;\n        }\n\n        if(NPC[A].Effect == NPCEFF_DROP_ITEM)\n        {\n            if(NPC[A].Effect2 % 3 != 0)\n            {\n                can_check = true;\n                reset_all = false;\n                set_justactivated = false;\n            }\n        }\n\n        if(!can_check)\n            continue;\n\n        if((vScreenCollision(Z, NPC[A].Location) || (has_ALoc && vScreenCollision(Z, npcALoc))) && !NPC[A].Hidden)\n        {\n            if(kill_zero && NPC[A].Type == 0) // what is this? almost certainly some sort of debugging on Redigit's side\n            {\n                NPC[A].Killed = 9;\n\n                // KillNPC was called directly in SMBX 1.3. Skipping it is a logic change\n                // but I think the situation didn't come up in properly formed content anyway.\n                // KillNPC(A, 9);\n\n                NPCQueues::Killed.push_back(A);\n                continue;\n            }\n            else if(NPC[A].Active && fill_draw_queue)\n            {\n                NPC_Draw_Queue_p.add(A);\n            }\n\n            if((NPC[A].Reset[Z] && (!check_both_reset || NPC[A].Reset[3 - Z])) || NPC[A].Active || (activate_conveyer && NPC[A].Type == NPCID_CONVEYOR))\n            {\n                if(set_justactivated && !NPC[A].Active)\n                    NPC[A].JustActivated = static_cast<uint8_t>(Z);\n\n                NPC[A].TimeLeft = Physics.NPCTimeOffScreen;\n                // NPCID_PLATFORM_S3 was checked here but not in BlockHit, so it's not included in the global trait\n                if(check_long_life && (NPCLongLife(NPC[A].Type) || NPC[A].Type == NPCID_PLATFORM_S3))\n                    NPC[A].TimeLeft = Physics.NPCTimeOffScreen * 20;\n\n                if(!NPC[A].Active)\n                {\n                    NPCQueues::Active.insert(A);\n                    NPC[A].Active = true;\n                }\n            }\n            NPC[A].Reset[1] = false;\n            NPC[A].Reset[2] = false;\n\n            NPCQueues::NoReset.push_back(A);\n        }\n        else\n        {\n            NPC[A].Reset[Z] = true;\n            if(reset_all)\n            {\n                if(numScreens == 1)\n                    NPC[A].Reset[2] = true;\n                if(SingleCoop == 1)\n                    NPC[A].Reset[2] = true;\n                else if(SingleCoop == 2)\n                    NPC[A].Reset[1] = true;\n            }\n        }\n    }\n\n    if(fill_draw_queue)\n        NPC_Draw_Queue_p.sort();\n}\n\n// does the modern NPC activation / reset logic for vScreen Z\nvoid ModernNPCScreenLogic(Screen_t& screen, int vscreen_i, bool fill_draw_queue, NPC_Draw_Queue_t& NPC_Draw_Queue_p)\n{\n    int Z = screen.vScreen_refs[vscreen_i];\n\n    // canonical vScreens\n    int c_Z1 = 0;\n    int c_Z2 = 0;\n\n    if(!screen.is_canonical())\n    {\n        Screen_t& c_screen = screen.canonical_screen();\n\n        // canonical screen might have different split than visible screen\n        if(c_screen.Type == 5)\n        {\n            // visible shared, canonical split\n            if(screen.DType == 5 && c_screen.DType != 5)\n            {\n                c_Z1 = c_screen.vScreen_refs[0];\n                c_Z2 = c_screen.vScreen_refs[1];\n            }\n            // visible split, canonical shared\n            else if(screen.DType != 5 && c_screen.DType == 5)\n            {\n                c_Z1 = c_screen.vScreen_refs[0];\n            }\n            // same mode, use same vScreen index\n            else\n            {\n                c_Z1 = c_screen.vScreen_refs[vscreen_i];\n            }\n        }\n        // same mode, use same vScreen index\n        else\n        {\n            c_Z1 = c_screen.vScreen_refs[vscreen_i];\n        }\n    }\n\n    // using bitset here instead of simpler set because I benchmarked it to be faster -- ds-sloth\n    // the purpose of this logic is to avoid duplicates in the checkNPCs vector\n    std::bitset<maxNPCs>& NPC_present = s_NPC_present;\n\n    // find the onscreen NPCs; first, get query bounds that cover all 3 possible screens\n    num_t bounds_left = -vScreen[Z].X;\n    num_t bounds_top = -vScreen[Z].Y;\n    num_t bounds_right = -vScreen[Z].X + vScreen[Z].Width;\n    num_t bounds_bottom = -vScreen[Z].Y + vScreen[Z].Height;\n\n    if(c_Z1)\n    {\n        bounds_left = SDL_min(bounds_left, -vScreen[c_Z1].X);\n        bounds_top = SDL_min(bounds_top, -vScreen[c_Z1].Y);\n        bounds_right = SDL_max(bounds_right, -vScreen[c_Z1].X + vScreen[c_Z1].Width);\n        bounds_bottom = SDL_max(bounds_bottom, -vScreen[c_Z1].Y + vScreen[c_Z1].Height);\n    }\n\n    if(c_Z2)\n    {\n        bounds_left = SDL_min(bounds_left, -vScreen[c_Z2].X);\n        bounds_top = SDL_min(bounds_top, -vScreen[c_Z2].Y);\n        bounds_right = SDL_max(bounds_right, -vScreen[c_Z2].X + vScreen[c_Z2].Width);\n        bounds_bottom = SDL_max(bounds_bottom, -vScreen[c_Z2].Y + vScreen[c_Z2].Height);\n    }\n\n    TreeResult_Sentinel<NPCRef_t> _screenNPCs = treeNPCQuery(bounds_left, bounds_top,\n        bounds_right, bounds_bottom, SORTMODE_NONE);\n\n\n    // combine the onscreen NPCs with the no-reset NPCs\n    std::vector<BaseRef_t>& checkNPCs = *_screenNPCs.i_vec;\n\n    // mark previous ones as checked\n    for(int16_t n : checkNPCs)\n        NPC_present[n] = true;\n\n    // add the previous frame's no-reset NPCs (without duplicates)\n    for(int16_t n : s_NoReset_NPCs_LastFrame)\n    {\n        if(n <= numNPCs && !NPC_present[n])\n        {\n            checkNPCs.push_back(n);\n            NPC_present[n] = true;\n        }\n    }\n\n    // cleanup the checked NPCs\n    for(int16_t n : checkNPCs)\n        NPC_present[n] = false;\n\n    Location_t loc2;\n\n    for(int A : checkNPCs)\n    {\n        // there are three related things we will determine:\n        // - is the NPC onscreen for rendering?\n        // - can we reset the NPC (respawn it to its original location)?\n        // - can we activate the NPC?\n\n        // they are normally the same thing -- onscreen -- but we need to separate them to support multiple resolutions.\n\n        bool loc2_exists;\n        if(NPC[A].Effect == NPCEFF_NORMAL && NPC[A].HoldingPlayer == 0 && !NPC[A].Generator &&\n                (NPC[A].vehiclePlr > 0 || NPC[A].Type == NPCID_VEHICLE || NPC[A].Type == NPCID_CANNONITEM\n                    || NPC[A].Type == NPCID_TOOTHYPIPE || NPC[A].Type == NPCID_ITEM_BURIED || NPC[A].Type == NPCID_ROCKET_WOOD\n                    || NPC[A].Type == NPCID_FIRE_BOSS_FIRE || NPC[A]->IsACoin || NPC[A].Type == NPCID_ICE_CUBE))\n        {\n            loc2_exists = true;\n            loc2 = newLoc(NPC[A].Location.X - (NPC[A]->WidthGFX - NPC[A].Location.Width) / 2,\n                NPC[A].Location.Y,\n                NPC[A]->WidthGFX,\n                NPC[A]->THeight);\n            // not sure why loc2 does not consider NPCHeightGFX...\n        }\n        else\n            loc2_exists = false;\n\n        bool render, cannot_reset, can_activate;\n\n        if(NPC[A].Hidden)\n        {\n            render = cannot_reset = can_activate = false;\n        }\n        else\n        {\n            render = vScreenCollision(Z, NPC[A].Location) || (loc2_exists && vScreenCollision(Z, loc2));\n\n            bool onscreen_canonical = false;\n\n            // check canonical screen\n            if(c_Z1)\n            {\n                onscreen_canonical = (vScreenCollision(c_Z1, NPC[A].Location)\n                    || (loc2_exists && vScreenCollision(c_Z1, loc2)));\n            }\n            // fallback to Z itself if no canonical screen exists\n            else\n            {\n                onscreen_canonical = render;\n            }\n\n            // add second canonical screen if needed\n            if(c_Z2 && !onscreen_canonical)\n            {\n                onscreen_canonical = (vScreenCollision(c_Z2, NPC[A].Location)\n                    || (loc2_exists && vScreenCollision(c_Z2, loc2)));\n            }\n\n            // Possible situations where we need to activate as original:\n            if(\n                   ForcedControls\n                || LevelMacro != LEVELMACRO_OFF\n                || qScreen_canonical\n                || !g_config.dynamic_camera_logic\n                || NPC_MustBeCanonical(A)\n            )\n                can_activate = onscreen_canonical;\n            else\n                can_activate = render;\n\n            // normally, don't reset anything that would be capable of activating\n            cannot_reset = (can_activate || onscreen_canonical);\n\n            // if it will look different after resetting, don't reset it\n            if(!cannot_reset && render &&\n                    (!NPC_InactiveRender(NPC[A])\n                        || num_t::floor(NPC[A].Location.X) != num_t::floor(NPC[A].DefaultLocationX)\n                        || num_t::floor(NPC[A].Location.Y) != num_t::floor(NPC[A].DefaultLocationY)\n                        || (!NPC[A].Active && !NPC[A].Reset[2])\n                    )\n                )\n            {\n                cannot_reset = true;\n            }\n        }\n\n        if(NPC[A].Generator)\n        {\n            NPC[A].GeneratorActive |= can_activate;\n            continue;\n        }\n\n        if(NPC[A].Type == 0)\n            render = false;\n\n        // find the intro frame index for this NPC; will be NPC_intro_count if not found\n        uint8_t NPC_intro_index;\n        for(NPC_intro_index = 0; NPC_intro_index < NPC_intro_count; NPC_intro_index++)\n        {\n            if(NPC_intro[NPC_intro_index] == A)\n                break;\n        }\n\n        // Handle the game's \"conditional activation\" state where NPC is activated *without* its events being triggered,\n        //   with TimeLeft = 0 by the time we get to rendering. In this case, \"resetting\" means \"not activating\",\n        //   so it's essential that we set cannot_reset to follow can_activate for this frame and the next one\n        //   (when Reset[1] and Reset[2] need to become true).\n\n        // Note that this condition, NPC[A].TimeLeft == 0 or 1, is practically never encountered when the NPC is onscreen,\n        //   except in the conditional activation code.\n        if((NPC[A].TimeLeft == 0 || NPC[A].TimeLeft == 1) && !NPC[A].Reset[2])\n        {\n            if(render && !can_activate)\n            {\n                // add it to the set of NPCs in intro/conditional states if not already present\n                if(NPC_intro_index == NPC_intro_count && NPC_intro_count < NPC_intro_count_MAX)\n                {\n                    NPC_intro[NPC_intro_index] = A;\n                    NPC_intro_count++;\n                }\n\n                // if it was already in the set, or if it was successfully added,\n                // set its intro frame to -3 to give it 3 frames of allowing it to reset while onscreen\n                //   (was previously 2, but 3 fixes some rare bugs, esp. during level testing)\n                if(NPC_intro_index < NPC_intro_count)\n                    NPC_intro_frame[NPC_intro_index] = -3;\n            }\n        }\n\n        // if the NPC is in the \"conditional activation\" state but didn't activate, then allow it to reset even when onscreen\n        if(NPC_intro_index < NPC_intro_count && NPC_intro_frame[NPC_intro_index] < 0)\n            cannot_reset = can_activate;\n\n        // if in \"poof\" mode, render the smoke effect on the first frame of the intro for an active NPC, then cancel the intro\n        if(NPC_intro_index < NPC_intro_count && NPC_intro_frame[NPC_intro_index] > 0 && NPC[A].Active && NPC_InactiveSmoke(NPC[A]))\n        {\n            NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n\n            // disable the NPC intro\n            s_RemoveIntroNPC(NPC_intro_index);\n            NPC_intro_index = NPC_intro_count;\n        }\n\n        // this section of the logic handles NPCs that are onscreen but not active yet\n        if(!NPC[A].Active && render)\n        {\n            // Don't show a fish that hasn't jumped yet, a lava bubble that hasn't started coming out yet,\n            //   a plant that hasn't emerged yet, etc. Also, don't do poofs for them, since they handle their own intros.\n            if(NPC_InactiveIgnore(NPC[A]))\n                render = false;\n            else if(!NPC_InactiveRender(NPC[A]))\n            {\n                // add to queue of hidden NPCs\n                if(!can_activate)\n                {\n                    if(NPC_intro_index == NPC_intro_count && NPC_intro_count < NPC_intro_count_MAX)\n                    {\n                        NPC_intro[NPC_intro_index] = A;\n                        NPC_intro_frame[NPC_intro_index] = 0;\n                        NPC_intro_count++;\n                    }\n\n                    // keep it hidden\n                    if(NPC_intro_index < NPC_intro_count && NPC_intro_frame[NPC_intro_index] >= 0)\n                        NPC_intro_frame[NPC_intro_index] = 0;\n                }\n\n                // if in \"poof\" mode, cancel render, but still set the intro frames as above so we can spawn the Smoke effect\n                if(NPC_InactiveSmoke(NPC[A]) && !g_CheatLogicScreen)\n                    render = false;\n            }\n        }\n\n        // activate the NPC if allowed\n        if(can_activate)\n        {\n            if(NPC[A].Type == 0) // what is this? almost certainly some sort of debugging on Redigit's side\n            {\n                NPC[A].Killed = 9;\n\n                // KillNPC was called directly in SMBX 1.3. Skipping it is a logic change\n                // but I think the situation didn't come up in properly formed content anyway.\n                // KillNPC(A, 9);\n\n                NPCQueues::Killed.push_back(A);\n                continue;\n            }\n            else if(!NPC[A].Active && NPC[A].Effect != NPCEFF_DROP_ITEM\n                && (NPC[A].Reset[2] || NPC[A].Type == NPCID_CONVEYOR))\n            {\n                NPC[A].JustActivated = static_cast<uint8_t>(Z);\n\n                if(!NPC[A].Active)\n                {\n                    NPCQueues::Active.insert(A);\n                    NPC[A].Active = true;\n                }\n            }\n        }\n\n        // track the NPC's reset timer\n        if(cannot_reset)\n        {\n            // update TimeLeft (despawn timer) if active and in the no-reset zone\n            if(NPC[A].Active)\n            {\n                // NPCID_PLATFORM_S3 was checked here but not in BlockHit, so it's not included in the global trait\n                if(NPCLongLife(NPC[A].Type) || NPC[A].Type == NPCID_PLATFORM_S3)\n                    NPC[A].TimeLeft = Physics.NPCTimeOffScreen * 20;\n                else\n                    NPC[A].TimeLeft = Physics.NPCTimeOffScreen;\n            }\n\n            // mark no-reset if it is currently active, or it has never been offscreen since deactivating\n            if((NPC[A].Active || !NPC[A].Reset[2]) && NPC[A].Reset[1])\n            {\n                NPC[A].Reset[1] = false;\n                NPCQueues::NoReset.push_back(A);\n            }\n        }\n        else\n        {\n            // Reset was previously cleared here, but now it is cleared in the main UpdateGraphics function\n            // NPC[A].Reset[Z] = true;\n            // if(numScreens == 1)\n            //     NPC[A].Reset[2] = true;\n            // if(SingleCoop == 1)\n            //     NPC[A].Reset[2] = true;\n            // else if(SingleCoop == 2)\n            //     NPC[A].Reset[1] = true;\n        }\n\n        const Player_t& hp = Player[NPC[A].HoldingPlayer];\n        bool hp_door_scroll = (NPC[A].HoldingPlayer > 0 && hp.Effect == PLREFF_WARP_DOOR && hp.Effect2 >= 128);\n\n        if(hp_door_scroll)\n            render = false;\n\n        if(fill_draw_queue && render && (NPC[A].Reset[2] || NPC[A].Active || !cannot_reset))\n        {\n            NPC_Draw_Queue_p.add(A);\n\n            // just for appearance, do the NPC's frames if it hasn't been activated yet and isn't in a shade / hidden mode\n            if(!NPC[A].Active && !FreezeNPCs)\n            {\n                bool in_hidden_mode = NPC_intro_index < NPC_intro_count && NPC_intro_frame[NPC_intro_index] >= 0;\n                if(NPC[A]->InactiveRender != NPCTraits_t::SHOW_STATIC && !in_hidden_mode)\n                    NPCFrames(A);\n            }\n        }\n        else if(fill_draw_queue && g_config.small_screen_cam && NPC[A].Active && cannot_reset && NPC[A].JustActivated == 0 && !NPC[A].Inert && NPC[A].Type != NPCID_CONVEYOR)\n        {\n            if(NPC[A].Location.SpeedX != 0 || NPC[A].Location.SpeedY != 0\n                || (!NPC[A]->WontHurt && !NPC[A]->IsACoin && !NPC[A]->IsABonus))\n            {\n                NPC_Draw_Queue_p.add_warning(A);\n            }\n        }\n    }\n\n    if(fill_draw_queue)\n        NPC_Draw_Queue_p.sort();\n}\n\n//! all of the logic done by UpdateGraphics\nvoid UpdateGraphicsLogic(bool Do_FrameSkip);\n\n//! parent function for the actual drawing to screen\nvoid UpdateGraphicsDraw(bool skipRepaint);\n\n//! draw graphics for the current Screen_t\nvoid UpdateGraphicsScreen(Screen_t& screen);\n\n//! extra non-gameplay related draws (menus and information display)\nvoid UpdateGraphicsMeta();\n\nvoid GraphicsClearScreen()\n{\n    if(!GameIsActive || XMessage::GetStatus() == XMessage::Status::replay)\n        return;\n\n    XRender::setTargetTexture();\n    XRender::resetViewport();\n    XRender::setDrawPlane(PLANE_GAME_BACKDROP);\n    XRender::clearBuffer();\n    XRender::repaint();\n}\n\n// This draws the graphic to the screen when in a level/game menu/outro/level editor\nvoid UpdateGraphics(bool skipRepaint)\n{\n//    On Error Resume Next\n\n    if(!GameIsActive)\n        return;\n\n    // check that we can render, and that we should not frameskip\n    bool Do_FrameSkip = false;\n\n#ifdef USE_RENDER_BLOCKING\n    if(XRender::renderBlocked())\n        Do_FrameSkip = true;\n#endif\n\n    // frame skip code\n    cycleNextInc();\n\n    if(g_config.enable_frameskip && !TakeScreen && frameSkipNeeded())\n        Do_FrameSkip = true;\n\n#ifdef __16M__\n    if(!XRender::ready_for_frame())\n    {\n        if(g_config.unlimited_framerate)\n            frameNextInc();\n        Do_FrameSkip = true;\n    }\n#endif\n\n    if(XMessage::GetStatus() == XMessage::Status::replay)\n        Do_FrameSkip = true;\n\n    g_microStats.start_task(MicroStats::Camera);\n\n    UpdateGraphicsLogic(Do_FrameSkip);\n\n    // we've now done all the logic that UpdateGraphics can do.\n    if(Do_FrameSkip)\n        return;\n\n    g_microStats.start_task(MicroStats::Graphics);\n\n    UpdateGraphicsDraw(skipRepaint);\n}\n\nvoid UpdateGraphicsLogic(bool Do_FrameSkip)\n{\n    // ALL graphics-based logic code has been moved here, separate from rendering.\n    // (This code is a combination of the FrameSkip logic from before with the\n    //   logic components of the full rendering code.)\n    // NPC render queue formation is also here.\n\n    g_stats.reset();\n\n    SetupScreens(false);\n    DynamicScreens();\n    CenterScreens();\n\n    bool continue_qScreen = false; // will qScreen continue for any visible screen?\n    std::array<bool, maxNetplayClients> continue_qScreen_local{false}; // will qScreen continue for a visible screen on this machine? (NetPlay)\n    bool continue_qScreen_canonical = false; // will qScreen continue for any canonical screen?\n\n    // prepare to fill this frame's NoReset queue\n    std::swap(NPCQueues::NoReset, s_NoReset_NPCs_LastFrame);\n    NPCQueues::NoReset.clear();\n\n    // mark the last-frame reset state of NPCs that may have Reset[1] or Reset[2] set to false, and clear their this-frame reset state\n    //     Reset[1] could have been set by the last frame's modern NPC logic, or by external code\n    //     Reset[2] could only have been set by external code\n    if(g_config.fix_npc_camera_logic)\n    {\n        for(NPC_t& n : s_NoReset_NPCs_LastFrame)\n        {\n            n.Reset[2] = n.Reset[1] && n.Reset[2];\n            n.Reset[1] = true;\n        }\n    }\n\n    // the graphics screen logic is handled via a big loop over Screens (clients)\n    for(int screen_i = 0; screen_i < c_screenCount; screen_i++)\n    {\n        Screen_t& screen = Screens[screen_i];\n\n        if(!screen.Visible)\n            continue;\n\n        if(!screen.player_count && !LevelEditor)\n            continue;\n\n        // update screen's canonical vScreens\n        if(!screen.is_canonical())\n        {\n            Screen_t& c_screen = screen.canonical_screen();\n\n            for(int i = c_screen.active_begin() + 1; i <= c_screen.active_end(); i++)\n                GetvScreenAuto(c_screen.vScreen(i));\n\n            if(qScreen_canonical)\n            {\n                for(int i = c_screen.active_begin(); i < c_screen.active_end(); i++)\n                {\n                    int Z_i = c_screen.vScreen_refs[i];\n                    continue_qScreen_canonical |= Update_qScreen(Z_i);\n\n                    // the original code was badly written and made THIS happen (always exactly one frame of qScreen in 2P mode)\n                    if(i >= 1 && !g_config.modern_section_change)\n                        continue_qScreen_canonical = false;\n                }\n            }\n        }\n\n        int numScreens = screen.active_end();\n\n        for(int vscreen_i = screen.active_begin(); vscreen_i < screen.active_end(); vscreen_i++)\n        {\n            int Z = screen.vScreen_refs[vscreen_i];\n            int plr_Z = screen.players[vscreen_i];\n\n            int S;\n            if(LevelEditor)\n                S = curSection;\n            else\n                S = Player[plr_Z].Section;\n\n            // update vScreen location\n            if(!LevelEditor)\n                GetvScreenAuto(vScreen[Z]);\n\n            // moved to `graphics/gfx_screen.cpp`\n            // NOTE: this logic was previously only performed on non-frameskips\n            if(qScreen)\n            {\n                bool continue_this_qScreen = Update_qScreen(Z);\n                continue_qScreen |= continue_this_qScreen;\n                continue_qScreen_local[screen_i] |= continue_this_qScreen;\n            }\n\n            // the original code was badly written and made THIS happen (always exactly one frame of qScreen in 2P mode)\n            if(Z == 2 && !g_config.modern_section_change)\n                continue_qScreen = false;\n\n            // noturningback\n            if(!LevelEditor && NoTurnBack[Player[plr_Z].Section] && g_config.allow_multires)\n            {\n                // goal: find canonical vScreen currently on this section that is the furthest left\n                // only do anything with the last vScreen (of a Visible screen) in the section\n                // (ensures all other logic has happened)\n\n                int A = 0;\n                int section = Player[plr_Z].Section;\n                bool is_last = true;\n\n                for(int screen_j = 0; screen_j < c_screenCount; screen_j++)\n                {\n                    Screen_t& o_screen = Screens[screen_j];\n\n                    // active_begin() / active_end() are 0-indexed\n                    for(int vscreen_j = o_screen.active_begin(); vscreen_j < o_screen.active_end(); vscreen_j++)\n                    {\n                        int o_p = o_screen.players[vscreen_j];\n                        int o_Z = o_screen.vScreen_refs[vscreen_j];\n\n                        if(Player[o_p].Section == section)\n                        {\n                            // check if this is the last vScreen (of a Visible screen) in the section\n                            if(o_screen.Visible && (screen_j > screen_i || (screen_j == screen_i && vscreen_j > vscreen_i)))\n                                is_last = false;\n\n                            // only consider canonical screens\n                            if(!o_screen.is_canonical())\n                                break;\n\n                            // pick screen further to left\n                            if(A == 0 || -vScreen[o_Z].X < -vScreen[A].X)\n                                A = o_Z;\n                        }\n                    }\n                }\n\n                if(is_last && A != 0 && -vScreen[A].X > level[S].X)\n                {\n                    // LevelChop[S] += float(-vScreen[A].X - level[S].X); // unused since SMBX64, removed\n                    level[S].X = -vScreen[A].X;\n\n                    // mark that section has shrunk\n                    UpdateSectionOverlaps(S, true);\n                }\n            }\n            // Legacy noturningback\n            else if(!LevelEditor && NoTurnBack[Player[plr_Z].Section])\n            {\n                vScreen_t& vscreen1 = screen.vScreen(1);\n                vScreen_t& vscreen2 = screen.vScreen(2);\n                int screen_p1 = screen.players[0];\n                int screen_p2 = screen.players[1];\n\n                // goal: find screen currently on this section that is the furthest left\n                int A = vscreen_i + 1;\n                if(numScreens > 1)\n                {\n                    if(Player[screen_p1].Section == Player[screen_p2].Section)\n                    {\n                        if(A == 1)\n                            GetvScreen(vscreen2);\n\n                        if(-vscreen1.X < -vscreen2.X)\n                            A = 1;\n                        else\n                            A = 2;\n                    }\n                }\n\n                if(-screen.vScreen(A).X > level[S].X)\n                {\n                    // LevelChop[S] += float(-screen.vScreen(A).X - level[S].X); // unused since SMBX64, removed\n                    level[S].X = -screen.vScreen(A).X;\n\n                    // mark that section has shrunk\n                    UpdateSectionOverlaps(S, true);\n                }\n            }\n\n            // Keep all players onscreen in clone mode\n            if(!GameMenu && !LevelEditor)\n            {\n                if(g_ClonedPlayerMode)\n                {\n                    num_t C = 0;\n                    int D = 0;\n\n                    For(A, 1, numPlayers)\n                    {\n                        Player_t &p = Player[A];\n\n                        if(!vScreenCollision(Z, p.Location) && LevelMacro == LEVELMACRO_OFF &&\n                            p.Location.Y < level[p.Section].Height &&\n                            p.Location.Y + p.Location.Height > level[p.Section].Y &&\n                            p.TimeToLive == 0 && !p.Dead)\n                        {\n                            for(int B = 1; B <= numPlayers; B++)\n                            {\n                                if(!Player[B].Dead && Player[B].TimeToLive == 0 && Player[B].Section == Player[A].Section && vScreenCollision(Z, Player[B].Location))\n                                {\n                                    num_t dx = num_t::abs(Player[A].Location.minus_center_x(Player[B].Location));\n                                    if(C == 0 || dx < C)\n                                    {\n                                        C = dx;\n                                        D = B;\n                                    }\n                                }\n                            }\n\n                            if(C == 0)\n                            {\n                                for(int B = 1; B <= numPlayers; B++)\n                                {\n                                    if(!Player[B].Dead && Player[B].TimeToLive == 0 && Player[B].Section == Player[A].Section)\n                                    {\n                                        num_t dx = num_t::abs(Player[A].Location.minus_center_x(Player[B].Location));\n                                        if(C == 0 || dx < C)\n                                        {\n                                            C = dx;\n                                            D = B;\n                                        }\n                                    }\n                                }\n                            }\n\n                            Player[A].Location.X = Player[D].Location.X + (Player[D].Location.Width - Player[A].Location.Width) / 2;\n                            Player[A].Location.Y = Player[D].Location.Y + Player[D].Location.Height - Player[A].Location.Height;\n                            Player[A].Section = Player[D].Section;\n                            Player[A].Location.SpeedX = Player[D].Location.SpeedX;\n                            Player[A].Location.SpeedY = Player[D].Location.SpeedY;\n                            Player[A].Location.SpeedY = dRand() * 12 - 6;\n                            Player[A].CanJump = true;\n                        }\n                    }\n                }\n            }\n\n            // It's time to process NPCs. We will update their active state and fill a draw queue.\n\n            // only fill draw queue if drawing will happen and this is the local screen\n            bool fill_draw_queue = !Do_FrameSkip && (&screen == l_screen);\n\n            // Make sure we are in range.\n            SDL_assert_release(vscreen_i >= 0 && vscreen_i < (int)(sizeof(NPC_Draw_Queue) / sizeof(NPC_Draw_Queue_t)));\n            NPC_Draw_Queue_t& NPC_Draw_Queue_p = NPC_Draw_Queue[vscreen_i];\n\n            if(fill_draw_queue)\n                NPC_Draw_Queue_p.reset();\n\n            // we'll check the NPCs and do some logic for the game,\n            if(!LevelEditor)\n            {\n                if(g_config.fix_npc_camera_logic)\n                    ModernNPCScreenLogic(screen, vscreen_i, fill_draw_queue, NPC_Draw_Queue_p);\n                else if(Z <= 2)\n                    ClassicNPCScreenLogic(Z, numScreens, fill_draw_queue, NPC_Draw_Queue_p);\n            }\n            // fill the NPC render queue for the level editor\n            else if(fill_draw_queue)\n            {\n                for(int A = 1; A <= numNPCs; A++)\n                {\n                    if(NPC[A].Hidden)\n                        continue;\n\n                    const Location_t loc2 = newLoc(NPC[A].Location.X - (NPC[A]->WidthGFX - NPC[A].Location.Width) / 2,\n                        NPC[A].Location.Y,\n                        NPC[A]->WidthGFX, NPC[A]->THeight);\n\n                    if(vScreenCollision(Z, NPC[A].Location) || vScreenCollision(Z, loc2))\n                        NPC_Draw_Queue_p.add(A);\n                }\n            }\n\n            // some \"special\" logic for warping Char 5 (moved from draw code)\n            for(int A = 1; A <= numPlayers; A++)\n            {\n                Player_t& p = Player[A];\n\n                if(p.Character == 5 && p.Effect == PLREFF_WARP_PIPE && !p.Dead && !p.Immune2 && p.TimeToLive == 0 && p.Frame > 5)\n                {\n                    if(vScreenCollision(Z, p.Location))\n                        p.Frame = 1;\n                }\n            }\n\n            // moved from render code because it affects the game's random state\n            s_shakeScreen[0].update();\n\n            if(g_config.fix_npc_camera_logic)\n                s_shakeScreen[Z].update();\n\n        } // loop over vScreens\n\n    } // loop over Screens\n\n\n    // Background frames (NOTE: frames were only updated on non-frameskip in vanilla)\n    if(!FreezeNPCs)\n    {\n        LevelFramesNotFrozen();\n        SpecialFrames();\n    }\n\n    CommonFrames();\n\n    LevelFramesAlways();\n    ProcessIntroNPCFrames();\n\n    // clear the last-frame reset state of NPCs\n    if(g_config.fix_npc_camera_logic)\n    {\n        for(NPC_t& n : s_NoReset_NPCs_LastFrame)\n            n.Reset[2] = true;\n    }\n\n    // use screen-shake to indicate if invisible screen is currently causing qScreen\n    if(g_config.allow_multires)\n    {\n        for(int screen_i = 0; screen_i < maxNetplayClients; screen_i++)\n        {\n            Screen_t& screen = Screens[screen_i];\n\n            if(!screen.Visible)\n                continue;\n\n            for(int vscreen_i = screen.active_begin(); vscreen_i < screen.active_end(); vscreen_i++)\n            {\n                int Z = screen.vScreen_refs[vscreen_i];\n\n                // shake screen to tell player game is currently paused (will take effect next frame)\n                if(!continue_qScreen_local[screen_i] && (continue_qScreen || continue_qScreen_canonical) && !s_shakeScreen[Z].active)\n                {\n                    s_forcedShakeScreen[Z] = true;\n                    s_shakeScreen[Z].setup(1, 1, SHAKE_RANDOM, 0, 0);\n                }\n                // finish forced screenshake\n                else if(!(continue_qScreen || continue_qScreen_canonical) && s_forcedShakeScreen[Z])\n                {\n                    s_forcedShakeScreen[Z] = false;\n                    s_shakeScreen[Z].setup(1, 1, SHAKE_RANDOM, 0, 100);\n                }\n            }\n        }\n    }\n\n    // update score and lives to their displayable limits\n    if(Score > 9999990)\n        Score = 9999990;\n\n    if(Lives > 99)\n        Lives = 99;\n\n    if(g_100s > 9999)\n        g_100s = 9999;\n\n    if(g_100s < -9999)\n        g_100s = -9999;\n\n    // NOTE: qScreen was only updated on non-frameskip in vanilla\n    qScreen = continue_qScreen;\n    qScreen_canonical = continue_qScreen_canonical;\n}\n\nstatic bool s_should_draw_player(const Player_t& p)\n{\n    bool player_door_scroll = (p.Effect == PLREFF_WARP_DOOR && p.Effect2 >= 128);\n    return (!p.Dead && p.TimeToLive == 0 && !(p.Effect == (PLREFF_TURN_TO_STATE + PLR_STATE_LEAF) || p.Effect == PLREFF_WAITING || p.Effect == PLREFF_PET_INSIDE || player_door_scroll));\n}\n\nvoid UpdateGraphicsDraw(bool skipRepaint)\n{\n    // begin render code\n    XRender::setTargetTexture();\n\n    frameNextInc();\n    frameRenderStart();\n    lunaRenderStart();\n\n    if(ClearBuffer)\n    {\n        ClearBuffer = false;\n        XRender::clearBuffer();\n    }\n\n#ifdef __3DS__\n    XRender::setTargetLayer(0);\n#endif\n\n    XRender::resetViewport();\n\n    XRender::setDrawPlane(PLANE_GAME_BACKDROP);\n\n    UpdateGraphicsScreen(*l_screen);\n\n    UpdateGraphicsMeta();\n\n    if(!skipRepaint)\n        XRender::repaint();\n\n    lunaRenderEnd();\n    frameRenderEnd();\n\n//    if(XRender::lazyLoadedBytes() > 200000) // Reset timer while loading many pictures at the same time\n//        resetFrameTimer();\n    XRender::lazyLoadedBytesReset();\n}\n\nvoid UpdateGraphicsScreen(Screen_t& screen)\n{\n    XTColor plr_shade = ShadowMode ? XTColor(64, 64, 64) : XTColor();\n\n    int numScreens = screen.active_end();\n\n    // even if not clearing buffer, black background is good, to be safe\n    XRender::renderRect(0, 0, XRender::TargetW, XRender::TargetH, {0, 0, 0});\n    DrawBackdrop(screen);\n\n    // No logic\n    // Draw the screens!\n    for(int vscreen_i = screen.active_begin(); vscreen_i < screen.active_end(); vscreen_i++)\n    {\n        int Z = screen.vScreen_refs[vscreen_i];\n        int plr_Z = screen.players[vscreen_i];\n\n        int S;\n        if(LevelEditor)\n            S = curSection;\n        else\n            S = Player[plr_Z].Section;\n\n        // (Code to get vScreen moved into logic section above.)\n\n#ifdef __3DS__\n        if(Z != 1)\n            XRender::setTargetLayer(0);\n#endif\n\n        XRender::setDrawPlane(PLANE_LVL_BG);\n\n        // Note: this was guarded by an if(!LevelEditor) condition in the past\n        if(Background2[S] == 0)\n        {\n            XRender::renderRect(vScreen[Z].TargetX(), vScreen[Z].TargetY(),\n                                vScreen[Z].Width, vScreen[Z].Height, {0, 0, 0}, true);\n        }\n\n        // Get a reference to our NPC draw queue.\n        // Make sure we are in range.\n        SDL_assert_release((vscreen_i >= 0) && (vscreen_i < (int)(sizeof(NPC_Draw_Queue) / sizeof(NPC_Draw_Queue_t))));\n        NPC_Draw_Queue_t& NPC_Draw_Queue_p = NPC_Draw_Queue[vscreen_i];\n\n        // always needed now due to cases where vScreen is smaller than physical screen\n        XRender::setViewport(vScreen[Z].TargetX(), vScreen[Z].TargetY(), vScreen[Z].Width, vScreen[Z].Height);\n\n        // update viewport from screen shake\n        s_shakeScreen[(g_config.fix_npc_camera_logic) ? Z : 0].apply();\n\n        // camera offsets to add to all object positions before drawing\n        num_t camX = vScreen[Z].CameraAddX();\n        num_t camY = vScreen[Z].CameraAddY();\n        int camX_i = num_t::floor(camX);\n        int camY_i = num_t::floor(camY);\n\n        DrawBackground(S, Z);\n\n#ifdef THEXTECH_BUILD_GL_MODERN\n        if(SectionParticlesBG[S])\n            XRender::renderParticleSystem(**SectionParticlesBG[S], camX_i, camY_i);\n#endif\n\n        // don't show background outside of the current section!\n        if(LevelEditor)\n        {\n            if(camX_i + LevelREAL[S].X > 0)\n            {\n                XRender::renderRect(0, 0,\n                                    camX_i + LevelREAL[S].X - XRender::TargetOverscanX, screen.H, {63, 63, 63}, true);\n            }\n\n            if(screen.W > LevelREAL[S].Width + camX_i)\n            {\n                XRender::renderRect(LevelREAL[S].Width + camX_i + XRender::TargetOverscanX, 0,\n                                    screen.W - (LevelREAL[S].Width + camX_i), screen.H, {63, 63, 63}, true);\n            }\n\n            if(camY_i + LevelREAL[S].Y > 0)\n            {\n                XRender::renderRect(0, 0,\n                                    screen.W, camY_i + LevelREAL[S].Y, {63, 63, 63}, true);\n            }\n\n            if(screen.H > LevelREAL[S].Height + camY_i)\n            {\n                XRender::renderRect(0, LevelREAL[S].Height + camY_i,\n                                    screen.W, screen.H - (LevelREAL[S].Height + camY_i), {63, 63, 63}, true);\n            }\n        }\n\n\n        // moved many-player (superbdemo128) handling code to logic section above\n\n#ifdef __3DS__\n        XRender::setTargetLayer(g_config.td_compat_mode ? 2 : 1);\n#endif\n\n        // update the vectors of all the onscreen blocks and backgrounds for use at multiple places\n        s_UpdateDrawItems(screen, vscreen_i);\n        const std::vector<BlockRef_t>& screenMainBlocks = s_drawMainBlocks[vscreen_i];\n        const std::vector<BlockRef_t>& screenLavaBlocks = s_drawLavaBlocks[vscreen_i];\n        const std::vector<BlockRef_t>& screenSBlocks = s_drawSBlocks[vscreen_i];\n        const std::vector<BaseRef_t>& screenBackgrounds = s_drawBGOs[vscreen_i];\n\n        int nextBackground = 0;\n\n        XRender::setDrawPlane(PLANE_LVL_BGO_LOW);\n\n        if(LevelEditor)\n        {\n            for(; nextBackground < (int)screenBackgrounds.size(); nextBackground++) // First backgrounds\n            {\n                int A = screenBackgrounds[nextBackground];\n                const auto &bgo = Background[A];\n\n                if(A > numBackground)\n                    break;\n\n                if(bgo.SortPriority >= Background_t::PRI_NORM_START)\n                    break;\n\n                g_stats.checkedBGOs++;\n                if(vScreenCollision(Z, bgo.Location) && !bgo.Hidden)\n                {\n                    g_stats.renderedBGOs++;\n\n                    auto &bgoGfx = GFXBackgroundBMP[bgo.Type];\n                    const vbint_t bgoHeight = BackgroundHeight[bgo.Type];\n                    const vbint_t bgoFrame = BackgroundFrame[bgo.Type];\n\n                    XRender::renderTextureBasic(num_t::floor(camX + bgo.Location.X),\n                                          num_t::floor(camY + bgo.Location.Y),\n                                          bgoGfx.w,\n                                          bgoHeight,\n                                          bgoGfx, 0,\n                                          bgoHeight * bgoFrame);\n                }\n            }\n        }\n        else\n        {\n            // For A = 1 To MidBackground - 1 'First backgrounds\n            for(; nextBackground < (int)screenBackgrounds.size() && (int)screenBackgrounds[nextBackground] < MidBackground; nextBackground++)  // First backgrounds\n            {\n                int A = screenBackgrounds[nextBackground];\n                const auto &bgo = Background[A];\n\n                g_stats.checkedBGOs++;\n\n                if(bgo.Hidden)\n                    continue;\n\n                int sX = num_t::floor(camX + bgo.Location.X);\n                if(sX > vScreen[Z].Width)\n                    continue;\n\n                int sY = num_t::floor(camY + bgo.Location.Y);\n                if(sY > vScreen[Z].Height)\n                    continue;\n\n                auto &bgoGfx = GFXBackgroundBMP[bgo.Type];\n                const vbint_t bgoHeight = BackgroundHeight[bgo.Type];\n\n                if(sX + bgoGfx.w >= 0 && sY + bgoHeight >= 0 /*&& !bgo.Hidden*/)\n                {\n                    const vbint_t bgoFrame = BackgroundFrame[bgo.Type];\n                    g_stats.renderedBGOs++;\n                    XRender::renderTextureBasic(sX,\n                                          sY,\n                                          bgoGfx.w,\n                                          bgoHeight,\n                                          bgoGfx,\n                                          0,\n                                          bgoHeight * bgoFrame);\n                }\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_SBLOCK);\n\n        for(Block_t& b : screenSBlocks) // Display sizable blocks\n        {\n            g_stats.checkedBlocks++;\n            if(/*BlockIsSizable[b.Type] &&*/ (!b.Invis || LevelEditor))\n            {\n                int bLeftOnscreen = num_t::floor(camX + b.Location.X);\n                if(bLeftOnscreen > vScreen[Z].Width)\n                    continue;\n\n                int bTopOnscreen = num_t::floor(camY + b.Location.Y);\n                if(bTopOnscreen > vScreen[Z].Height)\n                    continue;\n\n                g_stats.renderedBlocks++;\n\n                XRender::renderSizableBlock(bLeftOnscreen, bTopOnscreen, s_round2int(b.Location.Width), s_round2int(b.Location.Height), GFXBlockBMP[b.Type]);\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_BGO_NORM);\n\n        if(LevelEditor)\n        {\n            for(; nextBackground < (int)screenBackgrounds.size(); nextBackground++)  // Second backgrounds\n            {\n                int A = screenBackgrounds[nextBackground];\n                const auto &bgo = Background[A];\n\n                if(A > numBackground)\n                    break;\n\n                if(bgo.SortPriority >= Background_t::PRI_FG_START)\n                    break;\n\n                g_stats.checkedBGOs++;\n                if(vScreenCollision(Z, bgo.Location) && !bgo.Hidden)\n                {\n                    g_stats.renderedBGOs++;\n\n                    auto &bgoGfx = GFXBackgroundBMP[bgo.Type];\n                    const vbint_t bgoHeight = BackgroundHeight[bgo.Type];\n                    const vbint_t bgoFrame = BackgroundFrame[bgo.Type];\n\n                    XRender::renderTextureBasic(num_t::floor(camX + bgo.Location.X),\n                                          num_t::floor(camY + bgo.Location.Y),\n                                          bgoGfx.w,\n                                          bgoHeight,\n                                          bgoGfx, 0,\n                                          bgoHeight * bgoFrame);\n                }\n            }\n        }\n        else if(numBackground > 0)\n        {\n            for(; nextBackground < (int)screenBackgrounds.size() && (int)screenBackgrounds[nextBackground] <= LastBackground; nextBackground++)  // Second backgrounds\n            {\n                int A = screenBackgrounds[nextBackground];\n                const auto &bgo = Background[A];\n\n                g_stats.checkedBGOs++;\n\n                if(bgo.Hidden)\n                    continue;\n\n                int sX = num_t::floor(camX + bgo.Location.X);\n                if(sX > vScreen[Z].Width)\n                    continue;\n\n                int sY = num_t::floor(camY + bgo.Location.Y);\n                if(sY > vScreen[Z].Height)\n                    continue;\n\n                const vbint_t bgoWidth = BackgroundWidth[bgo.Type];\n                const vbint_t bgoHeight = BackgroundHeight[bgo.Type];\n\n                if(sX + bgoWidth >= 0 && sY + bgoHeight >= 0 /*&& !bgo.Hidden*/)\n                {\n                    auto &bgoGfx = GFXBackgroundBMP[bgo.Type];\n                    const vbint_t bgoFrame = BackgroundFrame[bgo.Type];\n\n                    g_stats.renderedBGOs++;\n                    XRender::renderTextureBasic(sX,\n                                          sY,\n                                          bgoWidth,\n                                          bgoHeight,\n                                          bgoGfx,\n                                          0, bgoHeight * bgoFrame);\n                }\n            }\n        }\n\n        for(int oBackground = (int)screenBackgrounds.size() - 1; oBackground > 0 && (int)screenBackgrounds[oBackground] > numBackground; oBackground--)  // Locked doors\n        {\n            int A = screenBackgrounds[oBackground];\n            const auto &bgo = Background[A];\n\n            g_stats.checkedBGOs++;\n\n            if(bgo.Hidden || !(bgo.Type == 98 || bgo.Type == 160))\n                continue;\n\n            int sX = num_t::floor(camX + bgo.Location.X);\n            if(sX > vScreen[Z].Width)\n                continue;\n\n            int sY = num_t::floor(camY + bgo.Location.Y);\n            if(sY > vScreen[Z].Height)\n                continue;\n\n            const vbint_t bgoWidth = BackgroundWidth[bgo.Type];\n            const vbint_t bgoHeight = BackgroundHeight[bgo.Type];\n\n            if(sX + bgoWidth >= 0 && sY + bgoHeight >= 0)\n            {\n                auto &bgoGfx = GFXBackgroundBMP[bgo.Type];\n                const vbint_t bgoFrame = BackgroundFrame[bgo.Type];\n                g_stats.renderedBGOs++;\n                XRender::renderTextureBasic(sX,\n                                      sY,\n                                      bgoWidth, bgoHeight,\n                                      bgoGfx,\n                                      0, bgoHeight * bgoFrame);\n            }\n        }\n\n#ifdef __3DS__\n        XRender::setTargetLayer(2);\n#endif\n\n        XRender::setDrawPlane(PLANE_LVL_NPC_BG);\n\n        // 'Display NPCs that should be behind blocks\n        for(size_t i = 0; i < NPC_Draw_Queue_p.BG_n; i++)\n        {\n            int A = NPC_Draw_Queue_p.BG[i];\n            DrawNPC(camX, camY, A);\n        }\n\n\n        XRender::setDrawPlane(PLANE_LVL_PLR_WARP);\n\n        // Player warp effects 'Players behind blocks\n        for(int A = 1; A <= numPlayers; A++)\n        {\n            if(Player[A].Dead || Player[A].Immune2 || Player[A].TimeToLive != 0)\n                continue;\n\n            if(Player[A].CurMazeZone != 0 || Player[A].Effect == PLREFF_WARP_PIPE)\n                DrawPlayer(Player[A], Z);\n            // separated player warp draw logic has now been removed!\n        }\n\n\n        XRender::setDrawPlane(PLANE_LVL_BLK_NORM);\n\n        // 'Non-Sizable Blocks\n        for(Block_t& block : screenMainBlocks)\n        {\n            g_stats.checkedBlocks++;\n\n            if(/*!BlockIsSizable[block.Type] &&*/ (!block.Invis || (LevelEditor && (CommonFrame % 46) <= 30)) /*&& block.Type != 0 && !BlockKills[block.Type]*/)\n            {\n                // Don't show a visual difference of hit-resized block in a comparison to original state\n                int sX = (block.getShrinkResized()) ? (num_t::floor(camX + block.Location.X - 0.05_n)) : (num_t::floor(camX + block.Location.X));\n                if(sX > vScreen[Z].Width)\n                    continue;\n\n                int sY = num_t::floor(camY + block.Location.Y);\n                if(sY > vScreen[Z].Height)\n                    continue;\n\n                int bw = s_round2int(block.Location.Width);\n                int bh = s_round2int(block.Location.Height);\n\n                if(sX + bw >= 0 && sY + bh >= 0 /*&& !block.Hidden*/)\n                {\n                    g_stats.renderedBlocks++;\n                    XTColor cb;\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n                    if(block.RespawnDelay_ScreensLeft && !BattleMode)\n                    {\n                        uint16_t screen_index = (&screen - &Screens[0]);\n                        if((block.RespawnDelay_ScreensLeft & (1U << screen_index)) == 0)\n                            cb = XTColor(127, 127, 127);\n                    }\n#endif\n\n                    XRender::renderTextureBasic(sX,\n                                          sY + block.ShakeOffset,\n                                          bw,\n                                          bh,\n                                          GFXBlock[block.Type],\n                                          0,\n                                          BlockFrame[block.Type] * bh,\n                                          cb);\n                    // BlockFrame * bh was previously BlockFrame * 32\n                    // This change is needed for converted conveyor blocks\n                    // It may be reverted in the future\n                }\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_EFF_LOW);\n\n        //'effects in back\n        for(int A = 1; A <= numEffects; A++)\n        {\n            if(Effect[A].Type == EFFID_BOSS_FRAGILE_DIE || Effect[A].Type == EFFID_DOOR_S2_OPEN || Effect[A].Type == EFFID_DOOR_DOUBLE_S3_OPEN ||\n               Effect[A].Type == EFFID_DOOR_SIDE_S3_OPEN || Effect[A].Type == EFFID_PLR_FIREBALL_TRAIL || Effect[A].Type == EFFID_COIN_SWITCH_PRESS ||\n               Effect[A].Type == EFFID_SPINBLOCK || Effect[A].Type == EFFID_BIG_DOOR_OPEN || Effect[A].Type == EFFID_LAVA_MONSTER_LOOK ||\n               Effect[A].Type == EFFID_WATER_SPLASH || Effect[A].Type == EFFID_TIME_SWITCH_PRESS || Effect[A].Type == EFFID_TNT_PRESS)\n            {\n                int sX = (num_t::floor(camX + Effect[A].Location.X));\n                if(sX > vScreen[Z].Width)\n                    continue;\n\n                int sY = num_t::floor(camY + Effect[A].Location.Y);\n                if(sY > vScreen[Z].Height)\n                    continue;\n\n                int w = s_round2int(Effect[A].Location.Width);\n                int h = s_round2int(Effect[A].Location.Height);\n\n                if(sX + w >= 0 && sY + h >= 0)\n                {\n                    g_stats.renderedEffects++;\n\n                    XTColor cn = Effect[A].Shadow ? XTColor(64, 64, 64) : XTColor();\n                    XRender::renderTextureBasic(sX, sY, w, h,\n                                           GFXEffect[Effect[A].Type], 0,\n                                           Effect[A].Frame * EffectHeight[Effect[A].Type], cn);\n                }\n            }\n        }\n\n\n        XRender::setDrawPlane(PLANE_LVL_NPC_LOW);\n\n        // draw NPCs that should be behind other NPCs\n        for(size_t i = 0; i < NPC_Draw_Queue_p.Low_n; i++)\n        {\n            int A = NPC_Draw_Queue_p.Low[i];\n            DrawNPC(camX, camY, A);\n        }\n\n\n        XRender::setDrawPlane(PLANE_LVL_NPC_LOW + 1);\n\n        // ice\n        for(size_t i = 0; i < NPC_Draw_Queue_p.Iced_n; i++)\n        {\n            int A = NPC_Draw_Queue_p.Iced[i];\n            DrawFrozenNPC(camX, camY, A);\n        }\n\n\n        XRender::setDrawPlane(PLANE_LVL_NPC_NORM);\n\n        // 'Display NPCs that should be in front of blocks\n        for(size_t i = 0; i < NPC_Draw_Queue_p.Normal_n; i++)\n        {\n            int A = NPC_Draw_Queue_p.Normal[i];\n            DrawNPC(camX, camY, A);\n        }\n\n        // npc chat bubble\n        for(size_t i = 0; i < NPC_Draw_Queue_p.Chat_n; i++)\n        {\n            int A = NPC_Draw_Queue_p.Chat[i];\n\n            int B = NPC[A]->HeightGFX - s_round2int(NPC[A].Location.Height);\n            if(B < 0)\n                B = 0;\n\n            XRender::renderTextureBasic((int)num_t::floor(camX + NPC[A].Location.X) + s_round2int(NPC[A].Location.Width) / 2 - GFX.Chat.w / 2,\n                (int)num_t::floor(camY + NPC[A].Location.Y) - 30 - B,\n                GFX.Chat);\n        }\n\n\n        XRender::setDrawPlane(PLANE_LVL_PLR_NORM);\n\n        For(A, 1, numPlayers) // The clown car\n        {\n            if(!Player[A].Dead && !Player[A].Immune2 && Player[A].TimeToLive == 0 &&\n               !(Player[A].Effect == PLREFF_WARP_PIPE || Player[A].Effect == (PLREFF_TURN_TO_STATE + PLR_STATE_LEAF)) && Player[A].Mount == 2)\n            {\n                const Player_t& p = Player[A];\n\n                using plr_pic_arr = RangeArr<StdPicture, 1, numStates>;\n                constexpr std::array<plr_pic_arr*, 5> char_tex = {&GFXMario, &GFXLuigi, &GFXPeach, &GFXToad, &GFXLink};\n\n                StdPicture& tx = (*char_tex[p.Character - 1])[p.State];\n\n                int Y = 0;\n\n                switch(Player[A].Character)\n                {\n                default:\n                case 1: // Mario\n                    if(Player[A].State == 1)\n                        Y = 24;\n                    else\n                        Y = 36;\n                    break;\n\n                case 2: // Luigi\n                    if(Player[A].State == 1)\n                        Y = 24;\n                    else\n                        Y = 38;\n                    break;\n\n                case 3: // Peach\n                    if(Player[A].State == 1)\n                        Y = 24;\n                    else\n                        Y = 30;\n                    break;\n\n                case 4: // Toad\n                    if(Player[A].State == 1)\n                        Y = 24;\n                    else\n                        Y = 30;\n                    break;\n\n                case 5: // Link\n                    Y = 30;\n                    break;\n                }\n\n                int sX = num_t::floor(camX + Player[A].Location.X);\n                int sY = num_t::floor(camY + Player[A].Location.Y);\n                int pW = s_round2int(Player[A].Location.Width);\n                int pH = s_round2int(Player[A].Location.Height);\n\n                XRender::renderTextureBasic(\n                        sX + pfrOffX(tx, p) - Physics.PlayerWidth[Player[A].Character][Player[A].State] / 2 + 64,\n                        sY + pfrOffY(tx, p) + Player[A].MountOffsetY - Y,\n                        pfrW(tx, p), // was 99, not 100, but not a big deal\n                        pH - 20 - Player[A].MountOffsetY,\n                        tx,\n                        pfrX(tx, p),\n                        pfrY(tx, p),\n                        plr_shade);\n                XRender::renderTextureBasic(\n                        sX + pW / 2 - 64,\n                        sY + pH - 128,\n                        128,\n                        128,\n                        GFX.Mount[Player[A].Mount],\n                        0,\n                        128 * Player[A].MountFrame,\n                        plr_shade);\n\n                if(p.State == PLR_STATE_CYCLONE)\n                    DrawCycloneAccessory(Z, p, sX + 64, sY + Player[A].MountOffsetY - Y, plr_shade);\n            }\n        }\n\n        if(LevelMacro == LEVELMACRO_KEYHOLE_EXIT && LevelMacroWhich != 0)\n            RenderKeyhole(Z);\n\n        // Put held NPCs on top\n        for(size_t i = 0; i < NPC_Draw_Queue_p.Held_n; i++)\n        {\n            int A = NPC_Draw_Queue_p.Held[i];\n            DrawNPCHeld(Z, camX, camY, A);\n        }\n\n\n\n        //'normal player draw code\n        //'Players in front of blocks\n        for(int A = numPlayers; A >= 1; A--)\n        {\n            Player_t& p = Player[A];\n\n            if(p.CurMazeZone == 0 && p.Effect != PLREFF_WARP_PIPE && p.Mount != 2 && s_should_draw_player(p))\n                DrawPlayer(p, Z);\n        }\n        //'normal player end\n\n\n        // foreground backgrounds\n        XRender::setDrawPlane(PLANE_LVL_BGO_FG);\n\n        if(LevelEditor)\n        {\n            for(; nextBackground < (int)screenBackgrounds.size(); nextBackground++)  // Foreground objects\n            {\n                int A = screenBackgrounds[nextBackground];\n                const auto &bgo = Background[A];\n\n                if(A > numBackground)\n                    continue;\n\n                g_stats.checkedBGOs++;\n                if(vScreenCollision(Z, bgo.Location) && !bgo.Hidden)\n                {\n                    auto &bgoGfx = GFXBackgroundBMP[bgo.Type];\n                    const vbint_t bgoHeight = BackgroundHeight[bgo.Type];\n                    const vbint_t bgoFrame = BackgroundFrame[bgo.Type];\n                    g_stats.renderedBGOs++;\n                    XRender::renderTextureBasic(num_t::floor(camX + bgo.Location.X),\n                                          num_t::floor(camY + bgo.Location.Y),\n                                          bgoGfx.w,\n                                          bgoHeight,\n                                          bgoGfx, 0,\n                                          bgoHeight * bgoFrame);\n                }\n            }\n        }\n        else\n        {\n            for(; nextBackground < (int)screenBackgrounds.size() && (int)screenBackgrounds[nextBackground] <= numBackground; nextBackground++)  // Foreground objects\n            {\n                int A = screenBackgrounds[nextBackground];\n                const auto &bgo = Background[A];\n\n                g_stats.checkedBGOs++;\n\n                if(bgo.Hidden)\n                    continue;\n\n                int sX = num_t::floor(camX + bgo.Location.X);\n                if(sX > vScreen[Z].Width)\n                    continue;\n\n                int sY = num_t::floor(camY + bgo.Location.Y);\n                if(sY > vScreen[Z].Height)\n                    continue;\n\n                auto &bgoGfx = GFXBackgroundBMP[bgo.Type];\n                const vbint_t bgoHeight = BackgroundHeight[bgo.Type];\n\n                if(sX + bgoGfx.w >= 0 && sY + bgoHeight >= 0 /*&& !bgo.Hidden*/)\n                {\n                    g_stats.renderedBGOs++;\n                    const vbint_t bgoFrame = BackgroundFrame[bgo.Type];\n                    XRender::renderTextureBasic(sX, sY,\n                                                bgoGfx.w,\n                                                bgoHeight,\n                                                bgoGfx, 0,\n                                                bgoHeight * bgoFrame);\n                }\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_NPC_FG);\n\n        // foreground NPCs\n        for(size_t i = 0; i < NPC_Draw_Queue_p.FG_n; i++)\n        {\n            int A = NPC_Draw_Queue_p.FG[i];\n            DrawNPC(camX, camY, A);\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_BLK_HURTS);\n\n        // Blocks in Front\n        for(Block_t& block : screenLavaBlocks)\n        {\n            g_stats.checkedBlocks++;\n\n            // screenLavaBlocks only contains deadly blocks\n            // if(!BlockKills[block.Type]) continue;\n\n            // if(vScreenCollision(Z, block.Location) /*&& !block.Hidden*/)\n\n            // Don't show a visual difference of hit-resized block in a comparison to original state\n            int sX = (block.getShrinkResized()) ? (num_t::floor(camX + block.Location.X - 0.05_n)) : (num_t::floor(camX + block.Location.X));\n            if(sX > vScreen[Z].Width)\n                continue;\n\n            int sY = num_t::floor(camY + block.Location.Y);\n            if(sY > vScreen[Z].Height)\n                continue;\n\n            int bw = s_round2int(block.Location.Width);\n            int bh = s_round2int(block.Location.Height);\n\n            if(sX + bw >= 0 && sY + bh >= 0 /*&& !block.Hidden*/)\n            {\n                g_stats.renderedBlocks++;\n                XRender::renderTextureBasic(sX,\n                                      sY + block.ShakeOffset,\n                                      bw,\n                                      bh,\n                                      GFXBlock[block.Type],\n                                      0,\n                                      BlockFrame[block.Type] * 32);\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_EFF_NORM);\n\n        // effects on top\n        For(A, 1, numEffects)\n        {\n            const auto &e = Effect[A];\n\n            if(e.Type != EFFID_BOSS_FRAGILE_DIE && e.Type != EFFID_DOOR_S2_OPEN && e.Type != EFFID_DOOR_DOUBLE_S3_OPEN && e.Type != EFFID_DOOR_SIDE_S3_OPEN &&\n               e.Type != EFFID_PLR_FIREBALL_TRAIL && e.Type != EFFID_COIN_SWITCH_PRESS && e.Type != EFFID_SPINBLOCK && e.Type != EFFID_BIG_DOOR_OPEN &&\n               e.Type != EFFID_LAVA_MONSTER_LOOK && e.Type != EFFID_WATER_SPLASH && e.Type != EFFID_TIME_SWITCH_PRESS && e.Type != EFFID_TNT_PRESS)\n            {\n                int sX = (num_t::floor(camX + Effect[A].Location.X));\n                if(sX > vScreen[Z].Width)\n                    continue;\n\n                int sY = num_t::floor(camY + Effect[A].Location.Y);\n                if(sY > vScreen[Z].Height)\n                    continue;\n\n                int w = s_round2int(Effect[A].Location.Width);\n                int h = s_round2int(Effect[A].Location.Height);\n\n                if(sX + w >= 0 && sY + h >= 0)\n                {\n                    g_stats.renderedEffects++;\n\n                    XTColor cn = e.Shadow ? XTColor(64, 64, 64) : XTColor();\n\n                    if(e.Type == EFFID_GENERIC_NPC_DIE || e.Type == EFFID_GENERIC_NPC_SQUISH)\n                    {\n                        auto flip = X_FLIP_NONE;\n                        auto draw_h = h;\n\n                        if(e.Type == EFFID_GENERIC_NPC_SQUISH)\n                            draw_h /= 2;\n                        else\n                            flip = X_FLIP_VERTICAL;\n\n                        XRender::renderTextureScaleEx(sX, sY + h - draw_h, w, draw_h,\n                            GFXNPCBMP[e.NewNpc],\n                            e.NewNpcSpecial * w, e.Frame * h, w, h,\n                            0, nullptr, flip, cn);\n\n                        continue;\n                    }\n\n                    XRender::renderTextureBasic(sX, sY, w, h,\n                        GFXEffectBMP[e.Type], 0, e.Frame * EffectHeight[e.Type], cn);\n                }\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_INFO);\n\n        // NPCs warnings\n        for(size_t i = 0; i < NPC_Draw_Queue_p.Warning_n; i++)\n        {\n            int A = NPC_Draw_Queue_p.Warning[i];\n            DrawWarningNPC(Z, camX, camY, A);\n        }\n\n        if(!LevelEditor) // Graphics for the main game.\n        {\n            // moved vScreen divider below\n\n            // Redigit NetPlay player names were also dropped\n\n            lunaRender(Z);\n\n            // debug code to show logical screens\n            if(g_CheatLogicScreen && !screen.is_canonical())\n            {\n                Screen_t& c_screen = screen.canonical_screen();\n\n                for(int c_vscreen_Z = c_screen.active_begin() + 1; c_vscreen_Z <= c_screen.active_end(); c_vscreen_Z++)\n                {\n                    vScreen_t& c_vscreen = c_screen.vScreen(c_vscreen_Z);\n\n                    int c_vscreen_X = num_t::floor(camX - c_vscreen.X);\n                    int c_vscreen_Y = num_t::floor(camY - c_vscreen.Y);\n\n                    XRender::renderRect(c_vscreen_X, c_vscreen_Y, c_vscreen.Width, c_vscreen.Height,\n                        {  0,   0,   0}, false);\n                    XRender::renderRect(c_vscreen_X + 1, c_vscreen_Y + 1, c_vscreen.Width - 2, c_vscreen.Height - 2,\n                        {  0,   0,   0}, false);\n                    XRender::renderRect(c_vscreen_X + 2, c_vscreen_Y + 2, c_vscreen.Width - 4, c_vscreen.Height - 4,\n                        {255, 255, 255}, false);\n                    XRender::renderRect(c_vscreen_X + 3, c_vscreen_Y + 3, c_vscreen.Width - 6, c_vscreen.Height - 6,\n                        {255, 255, 255}, false);\n                    SuperPrint(std::to_string(c_vscreen_Z), 1, c_vscreen_X + 4, c_vscreen_Y + 4);\n                }\n            }\n\n            // 'Interface\n\n            // moved condition past the splitFrame() call (always draw section effects)\n            // if(!GameMenu && !GameOutro)\n\n            // figure out whether to show names over players\n            bool char_rep = false;\n            if(numPlayers > 5)\n                char_rep = true;\n            else\n            {\n                for(int A = 1; A < numPlayers; A++)\n                {\n                    for(int B = A + 1; B <= numPlayers; B++)\n                        char_rep |= (Player[A].Character == Player[B].Character);\n                }\n            }\n\n            if(g_ClonedPlayerMode || GameMenu)\n                char_rep = false;\n\n            For(A, 1, numPlayers)\n            {\n                int p_center_x = num_t::floor(camX + Player[A].Location.X) + s_round2int(Player[A].Location.Width) / 2;\n                int info_y = num_t::floor(camY + Player[A].Location.Y) + s_round2int(Player[A].Location.Height) - 96;\n\n                if(char_rep && s_should_draw_player(Player[A]))\n                {\n                    char pname[2] = {'P', '0'};\n                    pname[1] = '0' + A;\n                    SuperPrintCenter(2, pname, 3, p_center_x, info_y + 12);\n                    info_y -= 16;\n                }\n\n                if(Player[A].Effect == PLREFF_COOP_WINGS)\n                    DrawPlayer(Player[A], Z, XTColor(192, 192, 192, 192));\n                else if(Player[A].ShowWarp > 0 && Player[A].Mount != 2)\n                {\n                    const auto &w = Warp[Player[A].ShowWarp];\n\n                    if(!w.noPrintStars && w.save_info().inited() && w.save_info().max_stars > 0)\n                    {\n                        std::string tempString = fmt::format_ne(\"{0}/{1}\", w.curStars, w.save_info().max_stars);\n                        int font_half_width = (tempString.length() * 9) & ~1;\n                        XRender::renderTextureBasic(p_center_x - font_half_width - 20, info_y,     GFX.Interface[5]);\n                        XRender::renderTextureBasic(p_center_x - font_half_width,      info_y + 1, GFX.Interface[1]);\n                        SuperPrint(tempString, 3,\n                                   p_center_x - font_half_width + 18,\n                                   info_y);\n\n                        info_y -= 20;\n                    }\n\n                    if(w.save_info().inited() && w.save_info().max_medals > 0)\n                    {\n                        // IsHubLevel is functionally an optimization here, not a critical piece of logic\n                        uint8_t ckpt = (IsHubLevel && Checkpoint == FileNamePath + GetS(w.level)) ? g_curLevelMedals.checkpoint : 0;\n\n                        DrawMedals(p_center_x, info_y, true, w.save_info().max_medals, 0, ckpt, w.save_info().medals_got, w.save_info().medals_best);\n                    }\n                }\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_SECTION_FG);\n\n#ifdef THEXTECH_BUILD_GL_MODERN\n        if(SectionParticlesFG[S])\n            XRender::renderParticleSystem(**SectionParticlesFG[S], camX_i, camY_i);\n\n        XRender::setupLighting(SectionLighting[S]);\n\n        if(SectionEffect[S])\n            XRender::renderTextureScale(0, 0, vScreen[Z].Width, vScreen[Z].Height, **SectionEffect[S]);\n        else if(SectionLighting[S])\n            XRender::renderLighting();\n#endif\n\n        XRender::splitFrame();\n        XRender::offsetViewportIgnore(true);\n\n        // vScreen fader (for within-level warps). Always draw for single-player and forced shared screen\n        // And don't draw when many players at the same screen (might be leaving / joining the dynamic shared screen)\n        if(screen.player_count == 1 || numScreens != 1 || screen.Type == ScreenTypes::SharedScreen)\n        {\n            if(g_levelVScreenFader[Z].isVisible())\n            {\n                XRender::setDrawPlane(PLANE_LVL_HUD - 1);\n                g_levelVScreenFader[Z].draw(false);\n                XRender::splitFrame();\n            }\n        }\n\n#ifdef __3DS__\n        if(GamePaused != PauseCode::Options && !(GameMenu && MenuMode == MENU_NEW_OPTIONS))\n            XRender::setTargetLayer(3);\n#endif\n\n        // HUD and dropped NPCs\n        if(!GameMenu && !GameOutro && !LevelEditor)\n        {\n            XRender::setDrawPlane(PLANE_LVL_HUD);\n\n            // draw HUD only if player has not disabled it\n            if(ShowOnScreenHUD)\n            {\n#ifdef THEXTECH_ENABLE_LUNA_AUTOCODE\n                lunaRenderHud(Z);\n#endif\n\n                // this is LunaScript's way of disabling the original SMBX HUD, so it shouldn't affect the Luna HUD\n                if(!gSMBXHUDSettings.skip)\n                   DrawInterface(Z, numScreens);\n            }\n\n            XRender::setDrawPlane(PLANE_LVL_HUD + 1);\n\n            // Display NPCs that got dropped from the container\n            for(size_t i = 0; i < NPC_Draw_Queue_p.Dropped_n; i++)\n            {\n                int A = NPC_Draw_Queue_p.Dropped[i];\n                DrawNPC(camX, camY, A);\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_META);\n\n        if((LevelEditor || MagicHand))\n        {\n            // editor code now located in `gfx_editor.cpp`\n            XRender::setDrawPlane(PLANE_LVL_INFO);\n            DrawEditorLevel(Z);\n        }\n\n        XRender::setDrawPlane(PLANE_LVL_META);\n\n        // Screen shake logic was here; moved into the logic section of the file because it affects the random state of the game\n\n        // draw onscreen controls display\n\n        if(screen.Type == 5 && numScreens == 1)\n        {\n            speedRun_renderControls(0, Z, SPEEDRUN_ALIGN_LEFT);\n            speedRun_renderControls(1, Z, SPEEDRUN_ALIGN_RIGHT);\n        }\n        else if(numScreens >= 2)\n        {\n            speedRun_renderControls(vscreen_i, Z, SPEEDRUN_ALIGN_AUTO);\n        }\n\n        // indicate any small-screen camera features\n        if(g_config.small_screen_cam && screen.H < 600\n            && screen.Type != 2 && screen.Type != 3 && screen.Type != 7 && (screen.Type != 5 || screen.vScreen(2).Visible))\n        {\n            DrawSmallScreenCam(vScreen[Z]);\n        }\n    } // For(Z, 2, numScreens)\n\n\n    // graphics shared by all vScreens, but still on the Screen\n    XRender::setViewport(screen.TargetX(), screen.TargetY(), screen.W, screen.H);\n    XRender::offsetViewportIgnore(true);\n\n    // splitscreen dividers\n    if(numScreens > 1 && !SingleCoop)\n    {\n        XRender::setDrawPlane(PLANE_LVL_META);\n\n        bool horiz_split = (screen.Type == ScreenTypes::TopBottom) || (screen.Type == ScreenTypes::Quad);\n        horiz_split |= screen.Type == ScreenTypes::Dynamic && (screen.DType == 3 || screen.DType == 4 || screen.DType == 6);\n\n        if(horiz_split)\n            XRender::renderRect(0, (screen.H / 2) - 2, screen.W, 4, {0, 0, 0});\n\n        bool vert_split = (screen.Type == ScreenTypes::LeftRight) || (screen.Type == ScreenTypes::Quad);\n        vert_split |= screen.Type == ScreenTypes::Dynamic && (screen.DType == 1 || screen.DType == 2);\n\n        if(vert_split)\n            XRender::renderRect((screen.W / 2) - 2, 0, 4, screen.H, {0, 0, 0});\n    }\n\n    XRender::resetViewport();\n\n    XRender::setDrawPlane(PLANE_GAME_META);\n\n    // 1P / shared screen controls indicator\n    bool show_controls_one_vscreen = (screen.Type != 5 && numScreens == 1);\n\n    // fix missing controls info when the vScreen didn't get rendered at all\n    bool show_controls_no_vscreen = (screen.Type == 5 && numScreens == 1 && screen.vScreen(1).Width == 0);\n\n    if(show_controls_one_vscreen || show_controls_no_vscreen)\n    {\n        for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n            speedRun_renderControls(plr_i, -1, SPEEDRUN_ALIGN_AUTO);\n    }\n}\n\nvoid UpdateGraphicsMeta()\n{\n    XRender::resetViewport();\n\n    XRender::setDrawPlane(PLANE_GAME_META);\n\n    if(GameMenu && !GameOutro)\n    {\n        mainMenuDraw();\n\n#ifdef __3DS__\n        // draw everything else below new options screen on 3DS\n        if(MenuMode == MENU_NEW_OPTIONS)\n            XRender::setTargetLayer(2);\n#endif\n    }\n\n    if(GameOutro)\n        DrawCredits();\n\n    DrawDeviceBattery();\n\n    speedRun_renderTimer();\n\n    if(PrintFPS > 0 && g_config.show_fps)\n        SuperPrint(std::to_string(PrintFPS), 1, XRender::TargetOverscanX + 8, 8, {0, 255, 0});\n\n    if(g_VanillaCam && (XRender::TargetW > 800 || XRender::TargetH > 600) && GFX.Camera.inited)\n        XRender::renderTextureBasic(XRender::TargetW - XRender::TargetOverscanX - GFX.Camera.w - 4, XRender::TargetH - GFX.Camera.h - 4, GFX.Camera);\n\n    g_stats.print();\n\n    if(!BattleMode && !GameMenu && !GameOutro && !LevelEditor && g_config.show_episode_title)\n    {\n        // big screen, display at top\n        if(XRender::TargetH >= 640 && g_config.show_episode_title == Config_t::EPISODE_TITLE_TOP)\n        {\n            int y = 20;\n            SuperPrintScreenCenter(WorldName, 3, y);\n        }\n        // display at bottom\n        else if(g_config.show_episode_title == Config_t::EPISODE_TITLE_BOTTOM)\n        {\n            int y = XRender::TargetH - 60;\n            SuperPrintScreenCenter(WorldName, 3, y, XTAlphaF(0.75_n));\n        }\n    }\n\n    XRender::setDrawPlane(PLANE_GAME_MENUS);\n\n    if(LevelEditor || MagicHand)\n        DrawEditorLevel_UI();\n\n    // render special screens\n    if(GamePaused == PauseCode::PauseScreen)\n        PauseScreen::Render();\n\n    if(GamePaused == PauseCode::Message)\n        DrawMessage();\n\n    if(GamePaused == PauseCode::DropAdd)\n    {\n        ConnectScreen::Render();\n        XRender::renderTextureBasic(int(SharedCursor.X), int(SharedCursor.Y), GFX.ECursor[2]);\n    }\n\n    if(GamePaused == PauseCode::Options)\n    {\n#ifdef __3DS__\n        XRender::setTargetLayer(3);\n#endif\n\n        OptionsScreen::Render();\n        XRender::renderTextureBasic(int(SharedCursor.X), int(SharedCursor.Y), GFX.ECursor[2]);\n    }\n\n    if(GamePaused == PauseCode::TextEntry)\n        TextEntryScreen::Render();\n\n    // Draw screen fader below level menu when game is paused\n    // This makes sure that the level test menu is drawn above the screen fader during level tests\n    if(GamePaused != PauseCode::None)\n        XRender::setDrawPlane(PLANE_GAME_MENUS - 1);\n    else\n        XRender::setDrawPlane(PLANE_GAME_FADER);\n\n    if(LevelSelect && !GameMenu && !GameOutro)\n        g_worldScreenFader.draw();\n    else\n        g_levelScreenFader.draw();\n}\n"
  },
  {
    "path": "src/graphics/gfx_update.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GFX_UPDATE_H\n#define GFX_UPDATE_H\n\n#include <array>\n#include \"global_constants.h\"\n\nextern std::array<bool, maxLocalPlayers> g_drawBlocks_valid;\nextern std::array<bool, maxLocalPlayers> g_drawBGOs_valid;\n\n// should equal the largest X or Y move rate of any layer containing blocks / BGOs\nextern num_t g_drawBlocks_invalidate_rate;\nextern num_t g_drawBGOs_invalidate_rate;\n\n// call when a block is added, moved, or its Hidden attribute is changed\ninline void invalidateDrawBlocks()\n{\n    for(bool& v : g_drawBlocks_valid)\n        v = false;\n}\n\n// call when a BGO is added, moved, or its Hidden attribute is changed\ninline void invalidateDrawBGOs()\n{\n    for(bool& v : g_drawBGOs_valid)\n        v = false;\n}\n\n//! UpdateGraphics function that ONLY draws to the screen (no logic!)\nvoid UpdateGraphicsDraw(bool skipRepaint = false);\n\n#endif // #ifdef GFX_UPDATE_H\n"
  },
  {
    "path": "src/graphics/gfx_update2.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <sdl_proxy/sdl_stdinc.h>\n\n#include <fontman/font_manager.h>\n\n#include \"../globals.h\"\n#include \"../gfx.h\"\n#include \"../frame_timer.h\"\n#include \"../graphics.h\"\n#include \"../collision.h\"\n#include \"../player.h\"\n#include \"../config.h\"\n#include \"../sound.h\"\n#include \"../main/speedrunner.h\"\n#include \"../main/trees.h\"\n#include \"../main/screen_pause.h\"\n#include \"../main/screen_connect.h\"\n#include \"../main/screen_options.h\"\n#include \"../main/screen_quickreconnect.h\"\n#include \"../main/screen_textentry.h\"\n#include \"../game_main.h\"\n#include \"../main/world_globals.h\"\n#include \"main/level_medals.h\"\n#include \"../core/render.h\"\n#include \"../screen_fader.h\"\n#include \"message.h\"\n\n#include \"graphics/gfx_frame.h\"\n#include \"graphics/gfx_marquee.h\"\n#include \"graphics/gfx_world.h\"\n\n#include \"gfx_special_frames.h\"\n\n#include \"draw_planes.h\"\n\n#include <fmt_format_ne.h>\n\n\n//! extra non-gameplay related draws (menus and information display), defined in gfx_update.cpp\nvoid UpdateGraphicsMeta();\n\nstatic inline int computeStarsShowingPolicy(int ll, int cur)\n{\n    // Disable if not allowed globally\n    if(!g_config.world_map_stars_show)\n        return Config_t::MAP_STARS_HIDE;\n\n    // Level individual\n    if(ll > Config_t::MAP_STARS_UNSPECIFIED)\n    {\n        if(ll == Config_t::MAP_STARS_COLLECTED && cur <= 0)\n            return Config_t::MAP_STARS_HIDE;\n        return ll;\n    }\n\n    // World map-wide\n    // (IMPORTANT NOTE: \"hide\" is now ignored as a map-wide setting, since the user has responsibility for setting this)\n    if(WorldStarsShowPolicy > Config_t::MAP_STARS_HIDE)\n    {\n        if(WorldStarsShowPolicy == Config_t::MAP_STARS_COLLECTED && cur <= 0)\n            return Config_t::MAP_STARS_HIDE;\n        return WorldStarsShowPolicy;\n    }\n\n    // Default behavior if not disabled is collected only\n    if(cur <= 0)\n        return Config_t::MAP_STARS_HIDE;\n\n    return Config_t::MAP_STARS_COLLECTED;\n}\n\n\nstatic inline int s_round2int(num_t d)\n{\n    return num_t::floor(d + 0.5_n);\n}\n\n// draws GFX to screen when on the world map/world map editor\nvoid UpdateGraphics2(bool skipRepaint)\n{\n    if(!GameIsActive)\n        return;\n\n#ifdef USE_RENDER_BLOCKING\n    if(XRender::renderBlocked())\n        return;\n#endif\n\n    cycleNextInc();\n\n    if(g_config.enable_frameskip && frameSkipNeeded())\n        return;\n\n#ifdef __16M__\n    if(!XRender::ready_for_frame())\n    {\n        if(g_config.unlimited_framerate)\n            frameNextInc();\n        return;\n    }\n#endif\n\n    if(XMessage::GetStatus() == XMessage::Status::replay)\n        return;\n\n    XRender::setTargetTexture();\n\n    frameNextInc();\n    frameRenderStart();\n\n    g_stats.reset();\n\n    // int A = 0;\n    // int B = 0;\n    const int Z = l_screen->vScreen_refs[0];\n    int WPHeight = 0;\n//    Location_t tempLocation;\n    //Z = 1;\n\n    if(WorldEditor)\n    {\n        vScreen[Z].Left = 0;\n        vScreen[Z].ScreenLeft = 0;\n        vScreen[Z].Top = 0;\n        vScreen[Z].ScreenTop = 0;\n        vScreen[Z].Width = l_screen->W;\n        vScreen[Z].Height = l_screen->H;\n    }\n    else\n    {\n        SetupScreens(false);\n        GetvScreenWorld(vScreen[Z]);\n\n        if(qScreen)\n        {\n            qScreen = Update_qScreen(Z, g_worldCamSpeed, g_worldCamSpeed);\n\n            if(qScreen && g_worldPlayCamSound)\n                PlaySound(SFX_Camera);\n\n            // reset cam sound\n            g_worldPlayCamSound = false;\n        }\n        // reset cam speed\n        else\n        {\n            g_worldCamSpeed = 1.5_n;\n        }\n    }\n\n    SpecialFrames();\n    CommonFrames();\n    WorldFrames();\n\n//    if(WorldEditor == true)\n//    {\n//        XRender::renderTexture(0, 0, ScreenW, ScreenH, 0, 0, 0);\n//    }\n//    else\n//    {\n//        XRender::renderTexture(0, 0, ScreenW, ScreenH, 0, 0, 0);\n//    }\n\n#ifdef __3DS__\n    XRender::setTargetLayer(0);\n#endif\n\n    XRender::setDrawPlane(PLANE_GAME_BACKDROP);\n\n    XRender::clearBuffer();\n    XRender::resetViewport();\n    DrawBackdrop(*l_screen);\n\n//    if(TakeScreen == true)\n//    {\n//        if(LevelEditor == true || MagicHand == true)\n//            frmLevelWindow::vScreen[Z].AutoRedraw = true;\n//        else\n//            frmMain.AutoRedraw = true;\n//    }\n\n//    If LevelEditor = True Then\n//        For A = 1 To numTiles\n//            With Tile(A)\n//                If vScreenCollision(1, .Location) = True Then\n//                    BitBlt myBackBuffer, vScreenX(Z) + .Location.X, vScreenY(Z) + .Location.Y, .Location.Width, .Location.Height, GFXTile(.Type), 0, TileHeight(.Type) * TileFrame(.Type), vbSrcCopy\n//                End If\n//            End With\n//        Next A\n//        For A = 1 To numScenes\n//            With Scene(A)\n//                If vScreenCollision(1, .Location) = True Then\n//                    BitBlt myBackBuffer, vScreenX(Z) + .Location.X, vScreenY(Z) + .Location.Y, .Location.Width, .Location.Height, GFXSceneMask(.Type), 0, SceneHeight(.Type) * SceneFrame(.Type), vbSrcAnd\n//                    BitBlt myBackBuffer, vScreenX(Z) + .Location.X, vScreenY(Z) + .Location.Y, .Location.Width, .Location.Height, GFXScene(.Type), 0, SceneHeight(.Type) * SceneFrame(.Type), vbSrcPaint\n//                End If\n//            End With\n//        Next A\n//        For A = 1 To numWorldPaths\n//            With WorldPath(A)\n//                If vScreenCollision(1, .Location) = True Then\n//                    BitBlt myBackBuffer, vScreenX(Z) + .Location.X, vScreenY(Z) + .Location.Y, .Location.Width, .Location.Height, GFXPathMask(.Type), 0, 0, vbSrcAnd\n//                    BitBlt myBackBuffer, vScreenX(Z) + .Location.X, vScreenY(Z) + .Location.Y, .Location.Width, .Location.Height, GFXPath(.Type), 0, 0, vbSrcPaint\n//                End If\n//            End With\n//        Next A\n//        For A = 1 To numWorldLevels\n//            With WorldLevel(A)\n//                If vScreenCollision(1, .Location) = True Then\n//                    If .Path = True Then\n//                        BitBlt myBackBuffer, vScreenX(Z) + .Location.X, vScreenY(Z) + .Location.Y, .Location.Width, .Location.Height, GFXLevelMask(0), 0, 0, vbSrcAnd\n//                        BitBlt myBackBuffer, vScreenX(Z) + .Location.X, vScreenY(Z) + .Location.Y, .Location.Width, .Location.Height, GFXLevel(0), 0, 0, vbSrcPaint\n//                    End If\n//                    If .Path2 = True Then\n//                        BitBlt myBackBuffer, vScreenX(Z) + .Location.X - 16, vScreenY(Z) + 8 + .Location.Y, 64, 32, GFXLevelMask(29), 0, 0, vbSrcAnd\n//                        BitBlt myBackBuffer, vScreenX(Z) + .Location.X - 16, vScreenY(Z) + 8 + .Location.Y, 64, 32, GFXLevel(29), 0, 0, vbSrcPaint\n//                    End If\n//                    If GFXLevelBig(.Type) = True Then\n//                        BitBlt myBackBuffer, vScreenX(Z) + .Location.X - (GFXLevelWidth(.Type) - 32) / 2, vScreenY(Z) + .Location.Y - GFXLevelHeight(.Type) + 32, GFXLevelWidth(.Type), GFXLevelHeight(.Type), GFXLevelMask(.Type), 0, 32 * LevelFrame(.Type), vbSrcAnd\n//                        BitBlt myBackBuffer, vScreenX(Z) + .Location.X - (GFXLevelWidth(.Type) - 32) / 2, vScreenY(Z) + .Location.Y - GFXLevelHeight(.Type) + 32, GFXLevelWidth(.Type), GFXLevelHeight(.Type), GFXLevel(.Type), 0, 32 * LevelFrame(.Type), vbSrcPaint\n//                    Else\n//                        BitBlt myBackBuffer, vScreenX(Z) + .Location.X, vScreenY(Z) + .Location.Y, .Location.Width, .Location.Height, GFXLevelMask(.Type), 0, 32 * LevelFrame(.Type), vbSrcAnd\n//                        BitBlt myBackBuffer, vScreenX(Z) + .Location.X, vScreenY(Z) + .Location.Y, .Location.Width, .Location.Height, GFXLevel(.Type), 0, 32 * LevelFrame(.Type), vbSrcPaint\n//                    End If\n//                End If\n//            End With\n//        Next A\n//    Else\n\n#ifdef __3DS__\n    XRender::setTargetLayer(1);\n#endif\n\n    XRender::setViewport(vScreen[Z].TargetX() - XRender::TargetOverscanX, vScreen[Z].TargetY(), vScreen[Z].Width + 2 * XRender::TargetOverscanX, vScreen[Z].Height);\n\n    int camX = vScreen[Z].CameraAddX_i() + XRender::TargetOverscanX;\n    int camY = vScreen[Z].CameraAddY_i();\n\n    int sLeft = -camX - 2 * XRender::TargetOverscanX;\n    int sTop = -camY;\n    int sRight = -camX + vScreen[Z].Width + 2 * XRender::TargetOverscanX;\n    int sBottom = -camY + vScreen[Z].Height;\n\n    {\n        Location_t sView;\n        sView.X = sLeft;\n        sView.Y = sTop;\n        sView.Width = sRight - sLeft;\n        sView.Height = sBottom - sTop;\n\n        XRender::setDrawPlane(PLANE_WLD_TIL);\n\n        //for(A = 1; A <= numTiles; A++)\n        for(Tile_t* t : treeWorldTileQuery(sLeft, sTop, sRight, sBottom, true))\n        {\n            Tile_t &tile = *t;\n            SDL_assert(IF_INRANGE(tile.Type, 1, maxTileType));\n\n            g_stats.checkedTiles++;\n            if(CheckCollision(sView, tile.Location))\n            {\n                g_stats.renderedTiles++;\n//                XRender::renderTexture(camX + Tile[A].Location.X, camY + Tile[A].Location.Y, Tile[A].Location.Width, Tile[A].Location.Height, GFXTile[Tile[A].Type], 0, TileHeight[Tile[A].Type] * TileFrame[Tile[A].Type]);\n                XRender::renderTextureBasic(camX + tile.Location.X,\n                                      camY + tile.Location.Y,\n                                      tile.Location.Width,\n                                      tile.Location.Height,\n                                      GFXTileBMP[tile.Type], 0, TileHeight[tile.Type] * TileFrame[tile.Type]);\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_WLD_SCN);\n\n        //for(A = 1; A <= numScenes; A++)\n        for(Scene_t* t : treeWorldSceneQuery(sLeft, sTop, sRight, sBottom, true))\n        {\n            Scene_t &scene = *t;\n            SDL_assert(IF_INRANGE(scene.Type, 1, maxSceneType));\n\n            g_stats.checkedScenes++;\n            if(CheckCollision(sView, scene.Location) && (WorldEditor || scene.Active))\n            {\n                g_stats.renderedScenes++;\n//                XRender::renderTexture(camX + scene.Location.X, camY + scene.Location.Y, scene.Location.Width, scene.Location.Height, GFXSceneMask[scene.Type], 0, SceneHeight[scene.Type] * SceneFrame[scene.Type]);\n//                XRender::renderTexture(camX + scene.Location.X, camY + scene.Location.Y, scene.Location.Width, scene.Location.Height, GFXScene[scene.Type], 0, SceneHeight[scene.Type] * SceneFrame[scene.Type]);\n                XRender::renderTextureBasic(camX + scene.Location.X,\n                                      camY + scene.Location.Y,\n                                      scene.Location.Width, scene.Location.Height,\n                                      GFXSceneBMP[scene.Type], 0, SceneHeight[scene.Type] * SceneFrame[scene.Type]);\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_WLD_PTH);\n\n        //for(A = 1; A <= numWorldPaths; A++)\n        for(WorldPath_t* t : treeWorldPathQuery(sLeft, sTop, sRight, sBottom, true))\n        {\n            WorldPath_t &path = *t;\n            SDL_assert(IF_INRANGE(path.Type, 1, maxPathType));\n\n            g_stats.checkedPaths++;\n            if(CheckCollision(sView, path.Location) && (WorldEditor || path.Active))\n            {\n                g_stats.renderedPaths++;\n//                XRender::renderTexture(camX + path.Location.X, camY + path.Location.Y, path.Location.Width, path.Location.Height, GFXPathMask[path.Type], 0, 0);\n//                XRender::renderTexture(camX + path.Location.X, camY + path.Location.Y, path.Location.Width, path.Location.Height, GFXPath[path.Type], 0, 0);\n                XRender::renderTextureBasic(camX + path.Location.X,\n                                      camY + path.Location.Y,\n                                      path.Location.Width, path.Location.Height,\n                                      GFXPathBMP[path.Type], 0, 0);\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_WLD_LVL);\n\n        //for(A = 1; A <= numWorldLevels; A++)\n        for(WorldLevel_t* t : treeWorldLevelQuery(sLeft, sTop, sRight, sBottom, true))\n        {\n            WorldLevel_t &lvlP = *t;\n            SDL_assert(IF_INRANGE(lvlP.Type, 0, maxLevelType));\n\n            g_stats.checkedLevels++;\n\n            // using this for the onscreen collision fixes bugs where levels with large graphics or Big Path backgrounds would not be drawn when partially offscreen\n            //   could use a compat check here to choose between LocationOnscreen and Location if desired\n            Location_t locOnscreen = lvlP.LocationOnscreen();\n\n            if(CheckCollision(sView, locOnscreen) && (WorldEditor || lvlP.Active))\n            {\n                Location_t locGFX = lvlP.LocationGFX();\n\n                g_stats.renderedLevels++;\n\n                if(lvlP.Path)\n                {\n                    XRender::renderTextureBasic(camX + lvlP.Location.X,\n                                          camY + lvlP.Location.Y,\n                                          lvlP.Location.Width,\n                                          lvlP.Location.Height,\n                                          GFXLevelBMP[0], 0, 0);\n                }\n\n                if(lvlP.Path2)\n                {\n                    XRender::renderTextureBasic(camX + lvlP.Location.X - 16,\n                                          camY + 8 + lvlP.Location.Y,\n                                          64, 32,\n                                          GFXLevelBMP[29], 0, 0);\n                }\n\n                XRender::renderTextureBasic(camX + s_round2int(locGFX.X),\n                                      camY + s_round2int(locGFX.Y),\n                                      (int)locGFX.Width, (int)locGFX.Height,\n                                      GFXLevelBMP[lvlP.Type], 0, 32 * LevelFrame[lvlP.Type]);\n            }\n        }\n    }\n\n    if(WorldEditor)\n    {\n        XRender::setDrawPlane(PLANE_WLD_EFF);\n\n        for(int A = 1; A <= numEffects; A++)\n        {\n            if(vScreenCollision(Z, Effect[A].Location))\n            {\n                XRender::renderTextureBasic(camX + s_round2int(Effect[A].Location.X),\n                    camY + s_round2int(Effect[A].Location.Y),\n                    (int)Effect[A].Location.Width, (int)Effect[A].Location.Height,\n                    GFXEffect[Effect[A].Type], 0, Effect[A].Frame * EffectHeight[Effect[A].Type]);\n            }\n        }\n\n        XRender::setDrawPlane(PLANE_WLD_INFO);\n\n        for(WorldMusic_t* t : treeWorldMusicQuery(sLeft, sTop, sRight, sBottom, true))\n        {\n            WorldMusic_t &music = *t;\n            if(vScreenCollision(Z, music.Location))\n            {\n                XRender::renderRect(camX + music.Location.X, camY + music.Location.Y, 32, 32,\n                    {255, 0, 255}, false);\n                SuperPrint(std::to_string(music.Type), 1, camX + music.Location.X + 2, camY + music.Location.Y + 2);\n            }\n        }\n\n        for(int A = 1; A <= numWorldAreas; A++)\n        {\n            WorldArea_t &area = WorldArea[A];\n            if(vScreenCollision(Z, static_cast<Location_t>(area.Location)))\n            {\n                // single color for now\n                XTColor color = XTColorF(1.0_n, 0.8_n, 0.2_n);\n\n                // draw rect with outline\n                XRender::renderRect(camX + area.Location.X, camY + area.Location.Y,\n                    area.Location.Width, area.Location.Height,\n                    {0, 0, 0}, false);\n                XRender::renderRect(camX + area.Location.X + 1, camY + area.Location.Y + 1,\n                    area.Location.Width - 2, area.Location.Height - 2,\n                    {0, 0, 0}, false);\n                XRender::renderRect(camX + area.Location.X + 2, camY + area.Location.Y + 2,\n                    area.Location.Width - 4, area.Location.Height - 4,\n                    color, false);\n                XRender::renderRect(camX + area.Location.X + 3, camY + area.Location.Y + 3,\n                    area.Location.Width - 6, area.Location.Height - 6,\n                    color, false);\n\n                // highlight selectable area\n                XRender::renderRect(camX + area.Location.X + 4, camY + area.Location.Y + 4,\n                    28, 28,\n                    XTColorF(1.0_n, 1.0_n, 0.0_n, 0.5_n), true);\n\n                // label with index\n                SuperPrint(std::to_string(A), 1, camX + area.Location.X + 4, camY + area.Location.Y + 4);\n            }\n        }\n\n#ifdef __3DS__\n        XRender::setTargetLayer(3);\n#endif\n\n        DrawEditorWorld();\n    }\n    else\n    { // NOT AN EDITOR!!!\n        XRender::setDrawPlane(PLANE_WLD_PLR);\n\n        if(WorldPlayer[1].Type == 0)\n            WorldPlayer[1].Type = 1;\n\n        WorldPlayer[1].Type = Player[1].Character;\n\n        switch(WorldPlayer[1].Type)\n        {\n        case 3:\n            WPHeight = 44;\n            break;\n        case 4:\n            WPHeight = 40;\n            break;\n        default:\n            WPHeight = 32;\n            break;\n        }\n\n//        XRender::renderTexture(camX + WorldPlayer[1].Location.X, camY + WorldPlayer[1].Location.Y - 10 + WorldPlayer[1].Location.Height - WPHeight, WorldPlayer[1].Location.Width, WPHeight, GFXPlayerMask[WorldPlayer[1].Type], 0, WPHeight * WorldPlayer[1].Frame);\n//        XRender::renderTexture(camX + WorldPlayer[1].Location.X, camY + WorldPlayer[1].Location.Y - 10 + WorldPlayer[1].Location.Height - WPHeight, WorldPlayer[1].Location.Width, WPHeight, GFXPlayer[WorldPlayer[1].Type], 0, WPHeight * WorldPlayer[1].Frame);\n        XRender::renderTextureBasic(camX + s_round2int(WorldPlayer[1].Location.X),\n                              camY + s_round2int(WorldPlayer[1].Location.Y + WorldPlayer[1].Location.Height) - 10 - WPHeight,\n                              (int)WorldPlayer[1].Location.Width, WPHeight,\n                              GFXPlayerBMP[WorldPlayer[1].Type], 0, WPHeight * WorldPlayer[1].Frame);\n\n        if(WorldPlayer[1].LevelIndex)\n        {\n            XRender::setDrawPlane(PLANE_WLD_INFO);\n\n            auto &l = WorldLevel[WorldPlayer[1].LevelIndex];\n\n            auto policy = computeStarsShowingPolicy(l.starsShowPolicy, l.curStars);\n\n            int p_center_x = camX + s_round2int(WorldPlayer[1].Location.X + (WorldPlayer[1].Location.Width / 2));\n            int info_y = camY + s_round2int(WorldPlayer[1].Location.Y) - 32;\n\n            if(l.save_info.inited() && l.save_info.max_stars > 0 && policy > Config_t::MAP_STARS_HIDE)\n            {\n                std::string label;\n\n                if(policy >= Config_t::MAP_STARS_SHOW)\n                    label = fmt::format_ne(\"{0}/{1}\", l.curStars, l.save_info.max_stars);\n                else\n                    label = fmt::format_ne(\"{0}\", l.curStars);\n\n                int len = SuperTextPixLen(label, 3);\n                int totalLen = len + GFX.Interface[1].w + GFX.Interface[5].w + 8 + 4;\n                int x = p_center_x - (totalLen / 2);\n\n                XRender::renderTextureBasic(x, info_y, GFX.Interface[5]);\n                XRender::renderTextureBasic(x + GFX.Interface[5].w + 8, info_y, GFX.Interface[1]);\n                SuperPrint(label, 3, x + GFX.Interface[1].w + GFX.Interface[5].w + 8 + 4, info_y);\n                info_y -= 20;\n            }\n\n            if(l.save_info.inited() && l.save_info.max_medals > 0 && true)\n            {\n                uint8_t ckpt = (Checkpoint == FileNamePathWorld + l.FileName) ? g_curLevelMedals.checkpoint : 0;\n\n                DrawMedals(p_center_x, info_y, true, l.save_info.max_medals, 0, ckpt, l.save_info.medals_got, l.save_info.medals_best);\n            }\n        }\n\n        XRender::setViewport(l_screen->TargetX(), l_screen->TargetY(), l_screen->W, l_screen->H);\n\n#ifdef __3DS__\n        XRender::setTargetLayer(2);\n#endif\n\n        XRender::setDrawPlane(PLANE_WLD_FRAME);\n\n//        XRender::renderTexture(0, 0, 800, 130, GFX.Interface[4], 0, 0);\n        DrawWorldMapFrame(vScreen[Z]);\n\n\n        int pX = vScreen[Z].ScreenLeft + 32 - 64 + 48;\n        int pY = vScreen[Z].ScreenTop - 6;\n\n\n        XRender::setDrawPlane(PLANE_WLD_HUD);\n\n        // prepare for player draw\n        vScreen[0].X = 0;\n        vScreen[0].Y = 0;\n\n        // do this part for l_screen->players\n        for(int A = 1; A <= numPlayers; A++)\n        {\n            DrawPlayerWorld(Player[A], pX, pY);\n            pX += 48;\n        }\n\n        // Print lives on the screen\n        DrawLives(pX + 32, vScreen[Z].ScreenTop - 20, Lives, g_100s);\n\n        // Print coins on the screen\n        auto& coin_icon = (Player[1].Character == 5) ? GFX.Interface[6] : GFX.Interface[2];\n\n        XRender::renderTextureBasic(pX + 16, vScreen[Z].ScreenTop - 42, coin_icon);\n        XRender::renderTextureBasic(pX + 40, vScreen[Z].ScreenTop - 40, GFX.Interface[1]);\n        SuperPrint(std::to_string(Coins), 1, pX + 62, vScreen[Z].ScreenTop - 40);\n\n        // Print stars on the screen\n        if(numStars > 0)\n        {\n            XRender::renderTextureBasic(pX + 16, vScreen[Z].ScreenTop - 64, GFX.Interface[5]);\n            XRender::renderTextureBasic(pX + 40, vScreen[Z].ScreenTop - 62, GFX.Interface[1]);\n            SuperPrint(std::to_string(numStars), 1, pX + 62, vScreen[Z].ScreenTop - 62);\n        }\n\n        // Print the level's name\n        if(WorldPlayer[1].LevelIndex)\n        {\n            int lnlx = pX + 116;\n            int lnrx = vScreen[Z].ScreenLeft + vScreen[Z].Width;\n\n            MarqueeSpec marquee_spec(lnrx - lnlx, 10, 64, 32, -1);\n            static MarqueeState marquee_state;\n\n            // could make these arrays if multiple world players ever supported\n            static vbint_t cache_LevelIndex;\n            static int cache_vScreen_W = 0;\n\n            static std::string cache_LevelName_Split;\n            static int cache_LevelName_H = 0;\n\n            int font = FontManager::fontIdFromSmbxFont(2);\n\n            if(cache_LevelIndex != WorldPlayer[1].LevelIndex || cache_vScreen_W != vScreen[Z].Width)\n            {\n                cache_LevelIndex = WorldPlayer[1].LevelIndex;\n                cache_vScreen_W = vScreen[Z].Width;\n\n                marquee_state.reset_width();\n\n                int max_width = lnrx - lnlx;\n\n                cache_LevelName_Split = WorldLevel[WorldPlayer[1].LevelIndex].LevelName;\n                // mutates cache_LevelName_Split\n                cache_LevelName_H = FontManager::optimizeTextPx(cache_LevelName_Split, max_width, font).h();\n            }\n\n            if(g_config.world_map_lvlname_marquee || cache_LevelName_H > vScreen[Z].ScreenTop - 2 - 8)\n            {\n                SuperPrintMarquee(WorldLevel[WorldPlayer[1].LevelIndex].LevelName, 2, lnlx, vScreen[Z].ScreenTop - 21, marquee_spec, marquee_state);\n                marquee_state.advance(marquee_spec);\n            }\n            else\n            {\n                FontManager::printText(cache_LevelName_Split.c_str(), cache_LevelName_Split.size(),\n                                        lnlx, vScreen[Z].ScreenTop - 2 - cache_LevelName_H,\n                                        font);\n            }\n        }\n\n#ifdef __3DS__\n        if(GamePaused != PauseCode::Options)\n            XRender::setTargetLayer(3);\n#endif\n\n        XRender::resetViewport();\n\n        for(int plr_i = 0; plr_i < l_screen->player_count; plr_i++)\n            speedRun_renderControls(plr_i, -1, SPEEDRUN_ALIGN_AUTO);\n    }\n\n    // draw on-screen menus and meta-information\n    UpdateGraphicsMeta();\n\n    if(!skipRepaint)\n        XRender::repaint();\n\n    frameRenderEnd();\n\n//    if(XRender::lazyLoadedBytes() > 200000) // Reset timer while loading many pictures at the same time\n//        resetFrameTimer();\n    XRender::lazyLoadedBytesReset();\n}\n"
  },
  {
    "path": "src/graphics/gfx_world.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"core/render.h\"\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"gfx.h\"\n#include \"graphics.h\"\n\nbool worldHasFrameAssets()\n{\n    return GFX.WorldMapFrame_Tile.inited && (!GFX.Interface[4].inited || !GFX.isCustom(37) || GFX.isCustom(73));\n}\n\nstatic bool s_border_valid()\n{\n    return GFX.WorldMapFrame_Border.tex.inited && (!GFX.isCustom(73) || GFX.isCustom(74));\n}\n\nstatic void s_getMargins(const Screen_t& screen, int& margin, int& marginTop, int& marginBottom)\n{\n    margin = 66;\n    marginTop = 130;\n    marginBottom = 66;\n\n    if(screen.H < 400)\n    {\n        marginBottom = 24;\n        marginTop = 72;\n    }\n    else if(screen.H < 500)\n    {\n        marginBottom = 32;\n        marginTop = 96;\n    }\n\n    if(screen.W < 400)\n        margin = 24;\n    else if(screen.W < 600)\n        margin = 32;\n    else if(screen.W < 800)\n        margin = 48;\n\n    // make sure world map frame stays visible\n    if(s_border_valid())\n    {\n        if(margin < GFX.WorldMapFrame_Border.le)\n            margin = GFX.WorldMapFrame_Border.le;\n\n        if(margin < GFX.WorldMapFrame_Border.re)\n            margin = GFX.WorldMapFrame_Border.re;\n\n        if(marginTop < GFX.WorldMapFrame_Border.te)\n            marginTop = GFX.WorldMapFrame_Border.te;\n\n        if(marginBottom < GFX.WorldMapFrame_Border.be)\n            marginBottom = GFX.WorldMapFrame_Border.be;\n    }\n}\n\nvoid GetvScreenWorld(vScreen_t& vscreen)\n{\n    const Screen_t& screen = Screens[vscreen.screen_ref];\n    const WorldPlayer_t& wp = WorldPlayer[1];\n    const TinyLocation_t& wpLoc = wp.Location;\n\n    int margin, marginTop, marginBottom;\n    s_getMargins(screen, margin, marginTop, marginBottom);\n\n    vscreen.Top = marginTop;\n    vscreen.Height = screen.H - marginBottom - marginTop;\n    vscreen.Left = margin;\n    vscreen.Width = screen.W - (margin * 2);\n\n    // limit the vScreen to its 800x600 size (668x404) if the new world map frame assets are missing\n    const bool haveFrameAssets = worldHasFrameAssets();\n    if(!haveFrameAssets)\n    {\n        if(vscreen.Width > 668)\n        {\n            vscreen.Left += (vscreen.Width - 668) / 2;\n            vscreen.Width = 668;\n        }\n\n        if(vscreen.Height > 404)\n        {\n            vscreen.Top += (vscreen.Height - 404) / 2;\n            vscreen.Height = 404;\n        }\n    }\n\n    int fX = wpLoc.X + wpLoc.Width / 2;\n    int fY = wpLoc.Y + wpLoc.Height / 2;\n\n    // apply a temporary vScreen focus\n    if(vscreen.TempDelay)\n    {\n        // this is safe; tempX/Y are set to integral values\n        fX = (int)(vscreen.tempX);\n        fY = (int)(vscreen.TempY);\n    }\n\n    vscreen.X = -fX + vscreen.Width / 2;\n    vscreen.Y = -fY + vscreen.Height / 2;\n\n    // begin bounds logic\n\n    // default bounds are the canonical vScreen\n    const Screen_t& c_screen = screen.canonical_screen();\n    int c_margin, c_marginTop, c_marginBottom;\n    s_getMargins(c_screen, c_margin, c_marginTop, c_marginBottom);\n\n    int c_width = c_screen.W - c_margin * 2;\n    int c_height = c_screen.H - c_marginTop - c_marginBottom;\n\n    const TinyLocation_t defaultBounds = TinyLocation_t{fX - c_width / 2, fY - c_height / 2, (int16_t)c_width, (int16_t)c_height};\n\n    // get the bounds from the player's section if possible, otherwise the defaults\n    const TinyLocation_t& bounds = (wp.Section != 0)\n        ? WorldArea[wp.Section].Location\n        : defaultBounds;\n\n    if(bounds.Width < vscreen.Width)\n    {\n        vscreen.Left += (vscreen.Width - bounds.Width) / 2;\n        vscreen.Width = bounds.Width;\n        vscreen.X = -bounds.X;\n    }\n    else if(-vscreen.X < bounds.X)\n        vscreen.X = -bounds.X;\n    else if(-vscreen.X > bounds.X + bounds.Width - vscreen.Width)\n        vscreen.X = -(bounds.X + bounds.Width - vscreen.Width);\n\n    if(bounds.Height < vscreen.Height)\n    {\n        vscreen.Top += (vscreen.Height - bounds.Height) / 2;\n        vscreen.Height = bounds.Height;\n        vscreen.Y = -bounds.Y;\n    }\n    else if(-vscreen.Y < bounds.Y)\n        vscreen.Y = -bounds.Y;\n    else if(-vscreen.Y > bounds.Y + bounds.Height - vscreen.Height)\n        vscreen.Y = -(bounds.Y + bounds.Height - vscreen.Height);\n\n    vscreen.ScreenTop = vscreen.Top;\n    vscreen.ScreenLeft = vscreen.Left;\n}\n\nvoid DrawPlayerWorld(Player_t& p, int X, int Y)\n{\n    p.Direction = -1;\n    p.Location.SpeedY = 0;\n    p.Location.SpeedX = -1;\n    p.Controls.Left = false;\n    p.Controls.Right = false;\n    p.SpinJump = false;\n    p.Dead = false;\n    p.Immune2 = false;\n    p.Fairy = false;\n    p.TimeToLive = 0;\n    p.Effect = PLREFF_NORMAL;\n    p.MountSpecial = 0;\n    p.HoldingNPC = 0;\n    if(p.Duck)\n        UnDuck(p);\n    PlayerFrame(p);\n\n    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n    SizeCheck(p);\n    p.Location.X = X;\n    p.Location.Y = Y - p.Location.Height;\n\n    if(p.MountType == 3)\n    {\n        p.YoshiWingsFrameCount += 1;\n        p.YoshiWingsFrame = 0;\n        if(p.YoshiWingsFrameCount <= 12)\n            p.YoshiWingsFrame = 1;\n        else if(p.YoshiWingsFrameCount >= 24)\n            p.YoshiWingsFrameCount = 0;\n        if(p.Direction == 1)\n            p.YoshiWingsFrame += 2;\n    }\n\n    DrawPlayer(p, 0);\n}\n\nvoid DrawWorldMapFrame(const vScreen_t& vscreen)\n{\n    const Screen_t& screen = Screens[vscreen.screen_ref];\n\n    if(worldHasFrameAssets())\n    {\n        RenderFrameBorder({0, 0, screen.W, screen.H}, {(int)vscreen.ScreenLeft, (int)vscreen.ScreenTop, (int)vscreen.Width, (int)vscreen.Height},\n            GFX.WorldMapFrame_Tile, s_border_valid() ? &GFX.WorldMapFrame_Border : nullptr);\n    }\n    else\n    {\n        // render a legacy background, in MANY careful segments...\n        constexpr bool do_stretch = true;\n\n        int ScreenLeft = vscreen.ScreenLeft;\n        int ScreenTop = vscreen.ScreenTop;\n        int ScreenWidth = vscreen.Width;\n        int ScreenHeight = vscreen.Height;\n\n        // top-left\n        XRender::renderTextureBasic(ScreenLeft - 66, ScreenTop - 130, 66, 130, GFX.Interface[4], 0, 0);\n\n        // top\n        int orig_width = GFX.Interface[4].w - 66 - 66;\n        if(do_stretch)\n        {\n            XRender::renderTextureScaleEx(vscreen.ScreenLeft, vscreen.ScreenTop - 130,\n                vscreen.Width, 130,\n                GFX.Interface[4], 66, 0, orig_width, 130);\n        }\n        else\n        {\n            for(int offset = 0; offset < ScreenWidth; offset += orig_width)\n            {\n                XRender::renderTextureBasic(ScreenLeft + offset, ScreenTop - 130,\n                    SDL_min(ScreenWidth - offset, orig_width), 130,\n                    GFX.Interface[4], 66, 0);\n            }\n        }\n\n        // top-right\n        XRender::renderTextureBasic(ScreenLeft + ScreenWidth, ScreenTop - 130, 66, 130 + 20, GFX.Interface[4], GFX.Interface[4].w - 66, 0);\n\n        // left\n        int orig_height = GFX.Interface[4].h - 130 - 66;\n        if(do_stretch)\n        {\n            XRender::renderTextureScaleEx(vscreen.ScreenLeft - 66, vscreen.ScreenTop,\n                66, vscreen.Height,\n                GFX.Interface[4], 0, 130, 66, orig_height);\n        }\n        else\n        {\n            for(int offset = 0; offset < ScreenHeight; offset += orig_height)\n            {\n                XRender::renderTextureBasic(ScreenLeft - 66, ScreenTop + offset,\n                    66, SDL_min(ScreenHeight - offset, orig_height),\n                    GFX.Interface[4], 0, 130);\n            }\n        }\n\n        // right\n        orig_height = GFX.Interface[4].h - (130 + 20) - 66;\n        if(do_stretch)\n        {\n            XRender::renderTextureScaleEx(vscreen.ScreenLeft + vscreen.Width, vscreen.ScreenTop + 20,\n                66, vscreen.Height - 20,\n                GFX.Interface[4], GFX.Interface[4].w - 66, 130 + 20, 66, orig_height);\n        }\n        else\n        {\n            for(int offset = 20; offset < ScreenHeight; offset += orig_height)\n            {\n                XRender::renderTextureBasic(ScreenLeft + ScreenWidth, ScreenTop + offset,\n                    66, SDL_min((int)ScreenHeight - offset, orig_height),\n                    GFX.Interface[4], GFX.Interface[4].w - 66, 130 + 20);\n            }\n        }\n\n        // bottom-left\n        XRender::renderTextureBasic(ScreenLeft - 66, ScreenTop + ScreenHeight, 66 + 34, 66, GFX.Interface[4], 0, GFX.Interface[4].h - 66);\n\n        // bottom\n        orig_width = GFX.Interface[4].w - (66 + 34) - 66;\n        if(do_stretch)\n        {\n            XRender::renderTextureScaleEx(vscreen.ScreenLeft + 34, vscreen.ScreenTop + vscreen.Height,\n                vscreen.Width - 34, 66,\n                GFX.Interface[4], 100, GFX.Interface[4].h - 66, orig_width, 66);\n        }\n        else\n        {\n            for(int offset = 34; offset < ScreenWidth; offset += orig_width)\n            {\n                XRender::renderTextureBasic(ScreenLeft + offset, ScreenTop + ScreenHeight,\n                    SDL_min(ScreenWidth - offset, orig_width), 66,\n                    GFX.Interface[4], 100, GFX.Interface[4].h - 66);\n            }\n        }\n\n        // bottom-right\n        XRender::renderTextureBasic(ScreenLeft + ScreenWidth, ScreenTop + ScreenHeight, 66, 66, GFX.Interface[4], GFX.Interface[4].w - 66, GFX.Interface[4].h - 66);\n    }\n}\n"
  },
  {
    "path": "src/graphics/gfx_world.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GFX_WORLD_H\n#define GFX_WORLD_H\n\nstruct vScreen_t;\nstruct Player_t;\n\n//! Checks whether the world map frame assets exist and are valid\nbool worldHasFrameAssets();\n\n//! Get the vscreen for a world player, with world section bounds checking\nvoid GetvScreenWorld(vScreen_t& vscreen);\n\n//! Draws world map frame around vscreen in its screen\nvoid DrawWorldMapFrame(const vScreen_t& vscreen);\n\n//! Draws a player for the world map HUD, at coordinates X (left) and Y (bottom)\nvoid DrawPlayerWorld(Player_t& p, int X, int Y);\n\n#endif // GFX_WORLD_H\n"
  },
  {
    "path": "src/graphics.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"graphics.h\"\n#include \"collision.h\"\n#include \"player.h\"\n#include \"game_main.h\"\n#include \"sound.h\"\n#include \"change_res.h\"\n#include \"main/game_info.h\"\n#include \"core/render.h\"\n#include \"core/events.h\"\n\n#include \"graphics/gfx_frame.h\"\n#include \"graphics/gfx_camera.h\"\n#include \"graphics/gfx_world.h\"\n\n#include \"pseudo_vb.h\"\n#include \"gfx.h\"\n#include \"config.h\"\n#include \"npc_traits.h\"\n\n\n//  Get the screen position\nvoid GetvScreen(vScreen_t& vscreen)\n{\n    auto &p = Player[vscreen.player];\n    auto &pLoc = p.Location;\n\n    if(p.Mount == 2)\n        pLoc.Height = 0;\n\n    // this check is new because players can now respawn in 1P mode through DropAdd\n    num_t pLocY = (p.Effect == PLREFF_RESPAWN) ? p.RespawnY : pLoc.Y;\n\n    vscreen.X = -pLoc.X + (vscreen.Width - pLoc.Width) / 2;\n    vscreen.Y = -pLocY + (vscreen.Height * 0.5_n) - vScreenYOffset - pLoc.Height;\n\n    ProcessSmallScreenCam(vscreen);\n\n    vscreen.X += -vscreen.tempX;\n    vscreen.Y += -vscreen.TempY;\n\n    // allow some overscan (needed for 3DS)\n    int allow_X = (vscreen.Width == Screens[vscreen.screen_ref].W) ? Screens[vscreen.screen_ref].CameraOverscanX : 0;\n\n    // shift the level so that it is onscreen\n    if(-vscreen.X < level[p.Section].X - allow_X)\n        vscreen.X = -(level[p.Section].X - allow_X);\n    if(-vscreen.X + vscreen.Width > level[p.Section].Width + allow_X)\n        vscreen.X = -(level[p.Section].Width - vscreen.Width + allow_X);\n    if(-vscreen.Y < level[p.Section].Y)\n        vscreen.Y = -level[p.Section].Y;\n    if(-vscreen.Y + vscreen.Height > level[p.Section].Height)\n        vscreen.Y = -(level[p.Section].Height - vscreen.Height);\n\n    if(vscreen.TempDelay > 0)\n        vscreen.TempDelay -= 1;\n    else\n    {\n        if(vscreen.tempX > 0)\n            vscreen.tempX -= 1;\n        if(vscreen.tempX < 0)\n            vscreen.tempX += 1;\n        if(vscreen.TempY > 0)\n            vscreen.TempY -= 1;\n        if(vscreen.TempY < 0)\n            vscreen.TempY += 1;\n    }\n\n    if(p.Mount == 2)\n        pLoc.Height = 128;\n}\n\n// Get the average screen position for all players\nvoid GetvScreenAverage(vScreen_t& vscreen)\n{\n    // int A = 0;\n    int B = 0;\n    num_t OldX = 0;\n    num_t OldY = 0;\n\n    OldX = vscreen.X;\n    OldY = vscreen.Y;\n\n    vscreen.X = 0;\n    vscreen.Y = 0;\n\n    const Screen_t& screen = Screens[vscreen.screen_ref];\n\n    int plr_count = (GameMenu) ? numPlayers : screen.player_count;\n\n    for(int i = 0; i < plr_count; i++)\n    {\n        const Player_t& plr = Player[(GameMenu) ? i + 1 : screen.players[i]];\n\n        if(!plr.Dead && (plr.Effect != PLREFF_RESPAWN || g_config.multiplayer_pause_controls))\n        {\n            vscreen.X += -plr.Location.X - plr.Location.Width / 2;\n\n            num_t pLocY = (plr.Effect == PLREFF_RESPAWN) ? plr.RespawnY : plr.Location.Y;\n\n            if(plr.Mount == 2)\n                vscreen.Y += -pLocY;\n            else\n                vscreen.Y += -pLocY - plr.Location.Height;\n\n            B += 1;\n        }\n    }\n\n    // A = 1;\n    if(B == 0)\n    {\n        if(GameMenu)\n        {\n            vscreen.X = -level[0].X;\n            B = 1;\n        }\n        else\n        {\n            vscreen.X = OldX;\n            vscreen.Y = OldY;\n            return;\n        }\n    }\n\n    // used ScreenW / ScreenH in VB6 code\n    const SpeedlessLocation_t& section = level[Player[screen.players[0]].Section];\n\n    // remember that the screen will be limited to the section's size in all cases\n    num_t use_width  = SDL_min(static_cast<num_t>(screen.W), section.Width  - section.X);\n    num_t use_height = SDL_min(static_cast<num_t>(screen.H), section.Height - section.Y);\n\n    vscreen.X = (vscreen.X / B) + (use_width / 2);\n    vscreen.Y = (vscreen.Y / B) + (use_height / 2) - vScreenYOffset;\n\n    // allow some overscan (needed for 3DS)\n    int allow_X = (vscreen.Width == Screens[vscreen.screen_ref].W) ? Screens[vscreen.screen_ref].CameraOverscanX : 0;\n\n    if(-vscreen.X < section.X - allow_X)\n        vscreen.X = -(section.X - allow_X);\n    if(-vscreen.X + use_width > section.Width + allow_X)\n        vscreen.X = -(section.Width - use_width + allow_X);\n    if(-vscreen.Y < section.Y)\n        vscreen.Y = -section.Y;\n    if(-vscreen.Y + use_height > section.Height)\n        vscreen.Y = -(section.Height - use_height);\n\n    if(GameMenu)\n    {\n        if(vscreen.X > OldX)\n        {\n            if(-vscreen.X <= level[0].X)\n                vscreen.X = OldX + 20;\n            else\n                vscreen.X = OldX;\n        }\n        else if(vscreen.X < OldX - 10)\n            vscreen.X = OldX - 10;\n\n        // on menu, bottom of screen always tracks bottom of level\n        vscreen.Y = -(level[Player[1].Section].Height - vscreen.Height);\n    }\n}\n\n// Get the average screen position for all players with no level edge detection\nvoid GetvScreenAverage2(vScreen_t& vscreen)\n{\n    // int A = 0;\n    int B = 0;\n\n    vscreen.X = 0;\n    vscreen.Y = 0;\n\n    const Screen_t& screen = Screens[vscreen.screen_ref];\n\n    for(int i = 0; i < screen.player_count; i++)\n    {\n        const Player_t& plr = Player[screen.players[i]];\n\n        if(!plr.Dead)\n        {\n            vscreen.X += -plr.Location.X - plr.Location.Width / 2;\n            if(plr.Mount == 2)\n                vscreen.Y += -plr.Location.Y;\n            else\n                vscreen.Y += -plr.Location.Y - plr.Location.Height;\n            B += 1;\n        }\n    }\n\n    //A = 1; // Stored value gets never read\n\n    if(B == 0)\n        return;\n\n    const SpeedlessLocation_t& section = level[Player[screen.players[0]].Section];\n\n    num_t use_width  = SDL_min(static_cast<num_t>(screen.W), section.Width  - section.X);\n    num_t use_height = SDL_min(static_cast<num_t>(screen.H), section.Height - section.Y);\n\n    vscreen.X = (vscreen.X / B) + (use_width / 2);\n    vscreen.Y = (vscreen.Y / B) + (use_height / 2) - vScreenYOffset;\n}\n\n// Get the average screen position for all players for ScreenType 3\n// Uses only the furthest left and right players for the X position\n// Doubles the weight of the top player for the Y position\nvoid GetvScreenAverage3(vScreen_t& vscreen)\n{\n    int plr_count = 0;\n    num_t OldX = 0;\n    num_t OldY = 0;\n\n    OldX = vscreen.X;\n    OldY = vscreen.Y;\n\n    // calculate average Y position\n    vscreen.Y = 0;\n    num_t Y_not_warping = 0;\n    int not_warping_count = 0;\n\n    // find furthest left, right, top, and bottom players\n    num_t l, r, t, b;\n\n    const Screen_t& screen = Screens[vscreen.screen_ref];\n\n    int section_idx = Player[screen.players[0]].Section;\n\n    for(int i = 0; i < screen.player_count; i++)\n    {\n        const Player_t& plr = Player[screen.players[i]];\n\n        if(plr.Dead)\n            continue;\n\n        num_t pl = plr.Location.X;\n        num_t pr = pl + plr.Location.Width;\n\n        // DANGER: consider case where players are free-fall and a player respawns\n        num_t pLocY = (plr.Effect == PLREFF_RESPAWN) ? plr.RespawnY : plr.Location.Y;\n\n        num_t by = pLocY + plr.Location.Height;\n\n        if(plr_count == 0 || by < t)\n            t = by;\n        if(plr_count == 0 || b < by)\n            b = by;\n        if(plr_count == 0 || pl < l)\n            l = pl;\n        if(plr_count == 0 || r < pr)\n            r = pr;\n\n        vscreen.Y -= by;\n        plr_count += 1;\n\n        if(!PlayerWaitingInWarp(plr))\n        {\n            Y_not_warping -= by;\n            not_warping_count += 1;\n        }\n    }\n\n    if(plr_count == 0)\n    {\n        vscreen.X = OldX;\n        vscreen.Y = OldY;\n        return;\n    }\n\n    // 2x the contribution of the top player to the result\n    vscreen.Y -= t;\n    plr_count += 1;\n\n    // take the average, but if the players waiting to exit a warp are keeping the screen too high, don't let them control the screen\n    num_t mean_Y = vscreen.Y / plr_count;\n    if(not_warping_count != 0)\n    {\n        num_t mean_Y_not_warping = Y_not_warping / not_warping_count;\n        // if the warping players are pulling the screen up by more than 100px, ignore them\n        num_t allowed_height = 100;\n        if(mean_Y > mean_Y_not_warping + allowed_height)\n            mean_Y = mean_Y_not_warping + allowed_height;\n    }\n\n    const SpeedlessLocation_t& section = level[section_idx];\n\n    num_t use_width  = screen.W;\n    num_t use_height = screen.H;\n\n    // allow canonical screen to expand to reach size of main screen, if there are players near the side of the screen\n    if(g_config.allow_multires && !screen.Visible)\n    {\n        // vScreen boundaries that would have been present in SMBX 1.3 splitscreen\n        num_t want_l = l - screen.W / 4;\n        num_t want_r = r + screen.W / 4;\n        num_t want_t = t - screen.H / 4;\n        num_t want_b = b + screen.H / 4;\n\n        if(want_t > -mean_Y - screen.H / 2)\n            want_t = -mean_Y - screen.H / 2;\n\n        if(want_b < -mean_Y + screen.H / 2)\n            want_b = -mean_Y + screen.H / 2;\n\n        if(want_l < section.X)\n            want_l = section.X;\n        if(want_r > section.Width)\n            want_r = section.Width;\n        if(want_t < section.Y)\n            want_t = section.Y;\n        if(want_b > section.Height)\n            want_b = section.Height;\n\n        if(want_r - want_l > use_width)\n            use_width = want_r - want_l;\n\n        if(want_b - want_t > use_height)\n            use_height = want_b - want_t;\n\n        use_width  = SDL_min(use_width,  static_cast<num_t>(screen.visible_screen().W));\n        // don't limit by visible screen: needed to handle unusual shared screen cases found by Sapphire Bullet Bill\n        // use_height = SDL_min(use_height, static_cast<num_t>(screen.visible_screen().H));\n\n        vscreen.Width = (int)use_width;\n        vscreen.Height = (int)use_height;\n\n        if(use_height > screen.H)\n            mean_Y = -(want_t + want_b) / 2;\n    }\n\n    // if a NoTurnBack section, make sure that the limited width is tracked\n    if(g_config.allow_multires && NoTurnBack[section_idx])\n        use_width = SDL_min(use_width, static_cast<num_t>(screen.canonical_screen().W));\n\n    use_width  = SDL_min(use_width,  section.Width  - section.X);\n    use_height = SDL_min(use_height, section.Height - section.Y);\n\n    vscreen.X = (use_width - (l + r)) / 2;\n    vscreen.Y = mean_Y + (use_height / 2) - vScreenYOffset;\n\n    // allow some overscan (needed for 3DS)\n    int allow_X = (vscreen.Width == Screens[vscreen.screen_ref].W) ? Screens[vscreen.screen_ref].CameraOverscanX : 0;\n\n    for(int i = 0; i < 2; i++)\n    {\n        if(-vscreen.X < section.X - allow_X)\n            vscreen.X = -(section.X - allow_X);\n        if(-vscreen.X + use_width > section.Width + allow_X)\n            vscreen.X = -(section.Width - use_width + allow_X);\n        if(-vscreen.Y < section.Y)\n            vscreen.Y = -section.Y;\n        if(-vscreen.Y + use_height > section.Height)\n            vscreen.Y = -(section.Height - use_height);\n\n        if(i == 1)\n            break;\n\n        if(vscreen.tempX == 0 && vscreen.TempY == 0 && vscreen.TempDelay == 0)\n            break;\n\n        // apply vScreen temp\n        vscreen.X += -vscreen.tempX;\n        vscreen.Y += -vscreen.TempY;\n\n        if(vscreen.TempDelay > 0)\n        {\n            vscreen.TempDelay--;\n            continue;\n        }\n\n        if(vscreen.tempX >= 2)\n            vscreen.tempX -= 2;\n        else if(vscreen.tempX <= -2)\n            vscreen.tempX += 2;\n        else\n            vscreen.tempX = 0;\n\n        if(vscreen.TempY >= 2)\n            vscreen.TempY -= 2;\n        else if(vscreen.TempY <= -2)\n            vscreen.TempY += 2;\n        else\n            vscreen.TempY = 0;\n    }\n}\n\n// NEW: update a vScreen with the correct procedure based on its screen's Type and DType\nvoid GetvScreenAuto(vScreen_t& vscreen)\n{\n    const Screen_t& screen = Screens[vscreen.screen_ref];\n\n    if(screen.Type == 3)\n        GetvScreenAverage3(vscreen);\n    else if(screen.Type == 2 || (screen.Type == 5 && !screen.vScreen(2).Visible))\n        GetvScreenAverage(vscreen);\n    else if(screen.Type == 7)\n        GetvScreenCredits(vscreen);\n    else\n        GetvScreen(vscreen);\n}\n\nvoid SharedScreenAvoidJump_Pre(Screen_t& screen)\n{\n    if(screen.Type != ScreenTypes::SharedScreen)\n        return;\n\n    auto& vscreen = screen.vScreen(1);\n\n    GetvScreenAverage3(vscreen);\n\n    if(!screen.is_canonical())\n        SharedScreenAvoidJump_Pre(screen.canonical_screen());\n}\n\nvoid SharedScreenAvoidJump_Post(Screen_t& screen, int Delay)\n{\n    if(screen.Type != ScreenTypes::SharedScreen)\n        return;\n\n    auto& vscreen = screen.vScreen(1);\n\n    num_t curX = vscreen.X;\n    num_t curY = vscreen.Y;\n\n    vscreen.tempX = 0;\n    vscreen.TempY = 0;\n\n    GetvScreenAverage3(vscreen);\n\n    vscreen.tempX = vscreen.X - curX;\n    vscreen.TempY = vscreen.Y - curY;\n\n    vscreen.X = curX;\n    vscreen.Y = curY;\n\n    vscreen.TempDelay = Delay;\n\n    if(!screen.is_canonical())\n        SharedScreenAvoidJump_Post(screen.canonical_screen(), Delay);\n}\n\nvoid SharedScreenResetTemp(Screen_t& screen)\n{\n    if(screen.Type != ScreenTypes::SharedScreen)\n        return;\n\n    auto& vscreen = screen.vScreen(1);\n\n    vscreen.tempX = 0;\n    vscreen.TempY = 0;\n    vscreen.TempDelay = 0;\n\n    if(!screen.is_canonical())\n        SharedScreenResetTemp(screen.canonical_screen());\n}\n\n// NEW: get the fixed-resolution vScreen position for a player, and write the top-left coordinate to (left, top)\nvoid GetPlayerScreen(num_t W, num_t H, const Player_t& p, num_t& left, num_t& top)\n{\n    auto &pLoc = p.Location;\n\n    num_t pHeight = (p.Mount != 2) ? pLoc.Height : 0;\n\n    left = -pLoc.X + (W - pLoc.Width) / 2;\n    top = -pLoc.Y + (H / 2) - vScreenYOffset - pHeight;\n\n    // limit to level bounds\n    if(-left < level[p.Section].X)\n        left = -level[p.Section].X;\n    else if(-left + W > level[p.Section].Width)\n        left = -(level[p.Section].Width - W);\n\n    if(-top < level[p.Section].Y)\n        top = -level[p.Section].Y;\n    else if(-top + H > level[p.Section].Height)\n        top = -(level[p.Section].Height - H);\n}\n\nvoid SetupGraphics()\n{\n    //DUMMY AND USELESS\n\n    // Creates the back buffer for the main game\n    // myBackBuffer = CreateCompatibleDC(GetDC(0))\n    // myBufferBMP = CreateCompatibleBitmap(GetDC(0), screenw, screenh)\n//    myBackBuffer = CreateCompatibleDC(frmMain::hdc);\n//    myBufferBMP = CreateCompatibleBitmap(frmMain::hdc, ScreenW, ScreenH);\n//    SelectObject myBackBuffer, myBufferBMP;\n//    GFX.Split(2).Width = ScreenW;\n//    GFX.Split(2).Height = ScreenH;\n    // GFX.BackgroundColor(1).Width = Screen.Width\n    // GFX.BackgroundColor(1).Height = Screen.Height\n    // GFX.BackgroundColor(2).Width = Screen.Width\n    // GFX.BackgroundColor(2).Height = Screen.Height\n}\n\nvoid SetupEditorGraphics()\n{\n    //DUMMY AND USELESS\n\n//    GFX.Split(1).Width = frmLevelWindow.vScreen(1).Width\n//    GFX.Split(1).Height = frmLevelWindow.vScreen(1).Height\n//    GFX.Split(2).Width = frmLevelWindow.vScreen(1).Width\n//    GFX.Split(2).Height = frmLevelWindow.vScreen(1).Height\n//    vScreen(1).Height = frmLevelWindow.vScreen(1).ScaleHeight\n//    vScreen(1).Width = frmLevelWindow.vScreen(1).ScaleWidth\n//    vScreen(1).Left = 0\n//    vScreen(1).Top = 0\n//    vScreen(2).Visible = False\n//    'Creates the back buffer for the level editor\n//    'myBackBuffer = CreateCompatibleDC(GetDC(0))\n//    'myBufferBMP = CreateCompatibleBitmap(GetDC(0), screenw, screenh)\n//    'SelectObject myBackBuffer, myBufferBMP\n//    GFX.BackgroundColor(1).Width = frmLevelWindow.vScreen(1).Width\n//    GFX.BackgroundColor(1).Height = frmLevelWindow.vScreen(1).Height\n//    GFX.BackgroundColor(2).Width = frmLevelWindow.vScreen(1).Width\n//    GFX.BackgroundColor(2).Height = frmLevelWindow.vScreen(1).Height\n}\n\nstatic inline int s_round2int(num_t d)\n{\n    return num_t::floor(d + 0.5_n);\n}\n\n// change from fullscreen to windowed mode\nvoid ChangeScreen()\n{\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    // shouldn't be possible\n    if(g_config.fullscreen.m_set > ConfigSetLevel::user_config)\n    {\n        PlaySoundMenu(SFX_BlockHit);\n        return;\n    }\n\n    g_config_game_user.fullscreen = !g_config.fullscreen;\n    UpdateConfig();\n    SaveConfig();\n#endif\n}\n\nvoid GetvScreenCredits(vScreen_t& vscreen)\n{\n    int A = 0;\n    int B = 0;\n\n    vscreen.X = 0;\n    vscreen.Y = 0;\n\n    for(A = 1; A <= numPlayers; A++)\n    {\n        if((!Player[A].Dead || g_gameInfo.outroDeadMode) && Player[A].Effect != PLREFF_RESPAWN)\n        {\n            vscreen.X += -Player[A].Location.X - Player[A].Location.Width / 2;\n\n            if(Player[A].Mount == 2)\n                vscreen.Y += -Player[A].Location.Y;\n            else\n                vscreen.Y += -Player[A].Location.Y - Player[A].Location.Height;\n\n            B++;\n        }\n    }\n\n    A = 1;\n    if(B == 0)\n        return;\n\n    // used ScreenW / ScreenH in VB6 code, using vScreen.Width / Height here\n\n    const SpeedlessLocation_t& section = level[Player[1].Section];\n\n    // remember that the screen will be limited to the section's size in all cases\n    num_t use_width  = SDL_min((num_t)vscreen.Width,  section.Width  - section.X);\n    num_t use_height = SDL_min((num_t)vscreen.Height, section.Height - section.Y);\n\n    vscreen.X = (vscreen.X / B) + (use_width / 2);\n    vscreen.Y = (vscreen.Y / B) + (use_height / 2) - vScreenYOffset;\n\n    if(-vscreen.X < section.X)\n        vscreen.X = -section.X;\n    if(-vscreen.X + use_width > section.Width)\n        vscreen.X = -(section.Width - use_width);\n    if(-vscreen.Y < section.Y)\n        vscreen.Y = -section.Y;\n    if(-vscreen.Y + use_height > section.Height)\n        vscreen.Y = -(section.Height - use_height);\n}\n\n#if 0\n// old, 100x100-based functions\n// now defined at gfx_draw_player.cpp\n\nint pfrXo(int plrFrame)\n{\n#if 1\n    return pfrX(plrFrame - 100);\n#else\n    // Old Redigit's code, does the same as a small one-line formula\n    int A;\n    A = plrFrame;\n    A -= 50;\n    while(A > 100)\n        A -= 100;\n    if(A > 90)\n        A = 9;\n    else if(A > 90)\n        A = 9;\n    else if(A > 80)\n        A = 8;\n    else if(A > 70)\n        A = 7;\n    else if(A > 60)\n        A = 6;\n    else if(A > 50)\n        A = 5;\n    else if(A > 40)\n        A = 4;\n    else if(A > 30)\n        A = 3;\n    else if(A > 20)\n        A = 2;\n    else if(A > 10)\n        A = 1;\n    else\n        A = 0;\n    return A * 100;\n#endif\n}\n\nint pfrYo(int plrFrame)\n{\n#if 1\n    return pfrX(plrFrame - 100);\n#else\n    // Old Redigit's code, does the same as a small one-line formula\n    int A;\n    A = plrFrame;\n    A -= 50;\n    while(A > 100)\n        A -= 100;\n    A -= 1;\n    while(A > 9)\n        A -= 10;\n    return A * 100;\n#endif\n}\n\nint pfrX(int plrFrame)\n{\n    return ((plrFrame + 49) / 10) * 100;\n}\n\nint pfrY(int plrFrame)\n{\n    return ((plrFrame + 49) % 10) * 100;\n}\n#endif\n\nvoid ScreenShot()\n{\n#ifdef USE_SCREENSHOTS_AND_RECS\n    XRender::setTargetTexture();\n    XRender::makeShot();\n    XRender::setTargetScreen();\n    PlaySoundMenu(SFX_GotItem);\n#endif\n    TakeScreen = false;\n}\n\nvoid DrawFrozenNPC(num_t camX, num_t camY, int A)\n{\n    auto &n = NPC[A];\n\n    int sX = num_t::floor(camX + NPC[A].Location.X);\n    int sY = num_t::floor(camY + NPC[A].Location.Y);\n    int w = s_round2int(NPC[A].Location.Width);\n    int h = s_round2int(NPC[A].Location.Height);\n\n    // collision already checked elsewhere\n    // if((vScreenCollision(Z, n.Location) ||\n    //     vScreenCollision(Z, newLoc(n.Location.X - (n->WidthGFX - n.Location.Width) / 2,\n    //                         n.Location.Y, CDbl(n->WidthGFX), CDbl(n->THeight)))) && !n.Hidden)\n    if(true)\n    {\n// draw npc\n        XTColor c = n.Shadow ? XTColor(0, 0, 0) : XTColor();\n        int content = n.Special;\n        int contentFrame = n.Special2;\n\n        // SDL_assert_release(content >= 0 && content <= maxNPCType);\n\n        // Draw frozen NPC body in only condition the content value is valid\n        if(content > 0 && content <= maxNPCType)\n        {\n            int frame_h = NPCHeight(content);\n            int srcX_off = NPC[A].GFXSlot * NPCWidth(content);\n            if(srcX_off >= GFXNPCBMP[content].w)\n                srcX_off = 0;\n\n            int srcY_off = 0;\n\n            // Fix vanilla bug where these NPCs would be rendered incorrectly\n            if(NPCWidthGFX(content) != 0 && g_config.fix_visual_bugs)\n            {\n                frame_h = NPCHeightGFX(content);\n\n                // computations as though the NPC were rendered normally\n                int contents_sX = sX + w / 2 - NPCWidthGFX(content) / 2 + (NPCTraits[content].FrameOffsetX * -NPC[A].Direction);\n                int contents_sY = sY + h - NPCHeightGFX(content) + NPCTraits[content].FrameOffsetY;\n\n                // get offsets\n                srcX_off = sX - contents_sX;\n                srcY_off = sY - contents_sY;\n\n                srcX_off += NPC[A].GFXSlot * NPCWidthGFX(content);\n            }\n\n            XRender::renderTextureBasic(sX + 2,\n                                    sY + 2,\n                                    w - 4,\n                                    h - 4,\n                                    GFXNPCBMP[content],\n                                    2 + srcX_off, 2 + srcY_off + contentFrame * frame_h,\n                                    c);\n        }\n\n        // draw ice\n         XRender::renderTextureBasic(sX + n->FrameOffsetX,\n                                sY + n->FrameOffsetY,\n                                w - 6, h - 6,\n                                GFXNPCBMP[n.Type], 0, 0, c);\n         XRender::renderTextureBasic(sX + n->FrameOffsetX + w - 6,\n                                sY + n->FrameOffsetY,\n                                6, h - 6,\n                                GFXNPCBMP[n.Type], 128 - 6, 0, c);\n         XRender::renderTextureBasic(sX + n->FrameOffsetX,\n                                sY + n->FrameOffsetY + h - 6,\n                                w - 6, 6,\n                                GFXNPCBMP[n.Type], 0, 128 - 6, c);\n         XRender::renderTextureBasic(sX + n->FrameOffsetX + w - 6,\n                                sY + n->FrameOffsetY + h - 6,\n                                6, 6, GFXNPCBMP[n.Type],\n                                128 - 6, 128 - 6, c);\n    }\n}\n\nLocation_t WorldLevel_t::LocationGFX()\n{\n    Location_t ret = static_cast<Location_t>(Location);\n\n    if(Type >= 1 && Type <= maxLevelType && GFXLevelBig[Type])\n    {\n        ret.X -= (GFXLevel[Type].w - ret.Width) / 2;\n        ret.Y -= (GFXLevel[Type].h - ret.Height);\n        ret.Width = GFXLevel[Type].w;\n        ret.Height = GFXLevel[Type].h;\n    }\n\n    return ret;\n}\n\nLocation_t WorldLevel_t::LocationOnscreen()\n{\n    Location_t ret = LocationGFX();\n\n    if(Path2)\n    {\n        if(ret.Height < 40)\n            ret.Height = 40;\n\n        if(ret.Width < 64)\n        {\n            ret.X += (ret.Width - 64) / 2;\n            ret.Width = 64;\n        }\n    }\n\n    return ret;\n}\n\nvoid DrawBackdrop(const Screen_t& screen)\n{\n    if(GFX.Backdrop.inited)\n    {\n        bool border_valid = GFX.Backdrop_Border.tex.inited && (!GFX.isCustom(71) || GFX.isCustom(72));\n\n        // special case for world map\n        if(LevelSelect && !GameMenu && !GameOutro && !LevelEditor)\n        {\n            IntegerLocation_t full{0, 0, XRender::TargetW, XRender::TargetH};\n            IntegerLocation_t inner{screen.TargetX(), screen.TargetY(), screen.W, screen.H};\n\n            // if world map frame assets missing, use the 800x600 area isntead\n            if(!worldHasFrameAssets())\n            {\n                inner.X = screen.vScreen(1).TargetX() - 66;\n                inner.Y = screen.vScreen(1).TargetY() - 130;\n                inner.Width = 800;\n                inner.Height = 600;\n            }\n\n            RenderFrameBorder(full, inner, GFX.Backdrop, border_valid ? &GFX.Backdrop_Border : nullptr);\n\n            return;\n        }\n\n        for(int i = screen.active_begin(); i < screen.active_end(); i++)\n        {\n            const auto& s = screen.vScreen(i + 1);\n\n            IntegerLocation_t full{0, 0, XRender::TargetW, XRender::TargetH};\n            IntegerLocation_t inner{s.TargetX(), s.TargetY(), (int)s.Width, (int)s.Height};\n\n            // horizontal\n            if(screen.Type == 4 || (screen.Type == 5 && (screen.DType == 1 || screen.DType == 2)) || (screen.Type == 9 && (i != 2 || screen.active_end() != 3)))\n            {\n                full.Width = XRender::TargetW / 2;\n                // our screen on right\n                if(((screen.Type == 4 || (screen.Type == 5 && screen.DType == 1)) && i == 1) || (screen.DType == 2 && i == 0) || (screen.Type == 9 && (i == 1 || i == 3)))\n                    full.X = XRender::TargetW / 2;\n            }\n\n            // vertical\n            if(screen.Type == 1 || (screen.Type == 5 && (screen.DType == 3 || screen.DType == 4 || screen.DType == 6)) || screen.Type == 9)\n            {\n                full.Height = XRender::TargetH / 2;\n                // our screen on bottom\n                if(((screen.Type == 1 || (screen.Type == 5 && (screen.DType == 3 || screen.DType == 6))) && i == 1) || (screen.DType == 4 && i == 0) || (screen.Type == 9 && (i == 2 || i == 3)))\n                    full.Y = XRender::TargetH / 2;\n            }\n\n            RenderFrameBorder(full, inner, GFX.Backdrop, border_valid ? &GFX.Backdrop_Border : nullptr);\n        }\n    }\n}\n"
  },
  {
    "path": "src/graphics.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GRAPHICS_H\n#define GRAPHICS_H\n\n#include <string>\n#include \"location.h\"\n#include \"globals.h\"\n\nenum\n{\n    SHAKE_RANDOM = 0,\n    SHAKE_SEQUENTIAL\n};\n\nvoid doShakeScreen(int force, int type);\n// decay is in units of 0.001 pixels/frame (ie, 1000 is 1px decay per frame)\nvoid doShakeScreen(int forceX, int forceY, int type, int duration, int decay, const Location_t& source);\nvoid doShakeScreenClear();\n\n// Public Sub UpdateGraphics2() 'draws GFX to screen when on the world map/world map editor\n// draws GFX to screen when on the world map/world map editor\nvoid UpdateGraphics2(bool skipRepaint = false);\n// Unpack all visible lazily-loaded graphics\nvoid GraphicsLazyPreLoad();\n// Draws the black screen (A final screen clean-up before quitting, even frame-skip is enabled)\nvoid GraphicsClearScreen();\n// Public Sub UpdateGraphics() 'This draws the graphic to the screen when in a level/game menu/outro/level editor\n// This draws the graphic to the screen when in a level/game menu/outro/level editor\nvoid UpdateGraphics(bool skipRepaint = false);\n// Public Sub GetvScreen(A As Integer) ' Get the screen position\n//  Get the screen position\nvoid GetvScreen(vScreen_t& vscreen);\n\n// Public Sub GetvScreenAverage() ' Get the average screen position for all players\n//  Get the average screen position for all players\nvoid GetvScreenAverage(vScreen_t& vscreen);\n\n// Public Sub GetvScreenAverage2() ' Get the average screen position for all players with no level edge detection\n//  Get the average screen position for all players with no level edge detection\nvoid GetvScreenAverage2(vScreen_t& vscreen);\n//  EXTRA: Get the average screen position for all players in shared screen mode.\n//   The main difference is that it uses the average of the further players to each side, instead of the true average\nvoid GetvScreenAverage3(vScreen_t& vscreen);\n\n// NEW: update a vScreen with the correct procedure based on its screen's Type and DType\nvoid GetvScreenAuto(vScreen_t& vscreen);\n\n//  Prepares shared screen to avoid a jump\nvoid SharedScreenAvoidJump_Pre(Screen_t& screen);\n\n//  Sets TempX and TempY to avoid a jump for a Screen's shared vScreen, and TempDelay as requested.\nvoid SharedScreenAvoidJump_Post(Screen_t& screen, int TempDelay);\n\n//  Resets variables set by above\nvoid SharedScreenResetTemp(Screen_t& screen);\n\n// NEW: get the fixed-res vScreen position for a player, and write the top-left coordinate to (left, top)\nvoid GetPlayerScreen(num_t W, num_t H, const Player_t& p, num_t& left, num_t& top);\n\n\n// Public Sub SetupGraphics()\n//! DUMMY AND USELESS\nvoid SetupGraphics();\n// Public Sub SetupEditorGraphics()\n//! DUMMY AND USELESS\nvoid SetupEditorGraphics();\n\n// Public Sub SetupScreens()\n// Sets the screen type and vScreen locations for a single screen\nvoid SetupScreens(Screen_t& screen, bool reset = true);\n\n// Sets the screen type and vScreen locations for all screens\nvoid SetupScreens(bool reset = true);\n\n// Public Sub DynamicScreen() 'for the split screen stuff\n// Decides how to split the vScreens in Dynamic mode\nvoid DynamicScreen(Screen_t& screen, bool mute = false);\n\n// NEW: calls DynamicScreen for all screens with ScreenTypes::Dynamic. Mutes any that are not visible.\nvoid DynamicScreens();\n\n// NEW: limit vScreens to playable section area and center them on the real screen\nvoid CenterScreens(Screen_t& screen);\n\n// NEW: limits vScreens to playable section area and centers them on the real screen, for all screens.\nvoid CenterScreens();\n\n// NEW: moves qScreen towards vScreen, now including the screen size\nbool Update_qScreen(int Z, num_t camRate = 2, num_t resizeRate = 2);\n\n// Public Sub SuperPrint(SuperWords As String, Font As Integer, X As Single, Y As Single) 'prints text to the screen\n// prints text to the screen\nint SuperTextPixLen(int SuperN, const char* SuperChars, int Font);\nvoid SuperPrint(int SuperN, const char* SuperChars, int Font, int X, int Y, XTColor color = XTColor());\nvoid SuperPrintRightAlign(int SuperN, const char* SuperChars, int Font, int X, int Y, XTColor color = XTColor());\nvoid SuperPrintCenter(int SuperN, const char* SuperChars, int Font, int X, int Y, XTColor color = XTColor());\nvoid SuperPrintScreenCenter(int SuperN, const char* SuperChars, int Font, int Y, XTColor color = XTColor());\n\nint SuperTextPixLen(const char* SuperChars, int Font);\nvoid SuperPrint(const char* SuperChars, int Font, int X, int Y, XTColor color = XTColor());\nvoid SuperPrintRightAlign(const char* SuperChars, int Font, int X, int Y, XTColor color = XTColor());\nvoid SuperPrintCenter(const char* SuperChars, int Font, int X, int Y, XTColor color = XTColor());\nvoid SuperPrintScreenCenter(const char* SuperChars, int Font, int Y, XTColor color = XTColor());\n\nint SuperTextPixLen(const std::string &SuperWords, int Font);\nvoid SuperPrint(const std::string &SuperWords, int Font, int X, int Y, XTColor color = XTColor());\nvoid SuperPrintRightAlign(const std::string &SuperWords, int Font, int X, int Y, XTColor color = XTColor());\nvoid SuperPrintCenter(const std::string &SuperWords, int Font, int X, int Y, XTColor color = XTColor());\nvoid SuperPrintScreenCenter(const std::string &SuperWords, int Font, int Y, XTColor color = XTColor());\n\n/*!\n * \\brief Prepares dimensions for drawing a message with the message box\n */\nvoid PrepareMessageDims();\n\n/*!\n * \\brief Print the message box with a multi-line text inside using a plain string itself\n */\nvoid DrawMessage();\n\n/*!\n * \\brief This is a very special function that runs only last state of the texture\n */\nvoid UpdateGraphicsFatalAssert();\n\n// Public Sub SetRes()\n// void SetRes(); //deprecated\n// Public Function CheckKey(newStrizzle As String) As String\n//std::string CheckKey(std::string newStrizzle); // USELESS\n\n// moved to gfx_special_frames.h\n// Private Sub SpecialFrames() 'update frames for special things such as coins and kuribo's shoe\n// extern void SpecialFrames();//PRIVATE\n// update frames for special things such as coins and kuribo's shoe\n\n// Public Sub DrawBackground(S As Integer, Z As Integer) 'draws the background to the screen\n// draws the background to the screen\nvoid DrawBackground(int S, int Z);\n\n// these were removed, and their logic is now incorporated into RenderTexturePlayer.\n\n// Public Sub PlayerWarpGFX(A As Integer, tempLocation As Location, X2 As Single, Y2 As Single)\n// void PlayerWarpGFX(int A, IntegerLocation_t &tempLocation, int &X2, int &Y2);\n// Public Sub NPCWarpGFX(A As Integer, tempLocation As Location, X2 As Single, Y2 As Single)\n// void NPCWarpGFX(int A, IntegerLocation_t &tempLocation, int &X2, int &Y2);\n\n// Public Sub ChangeScreen() 'change from fullscreen to windowed mode\n// change from fullscreen to windowed mode\nvoid ChangeScreen();\n// Public Sub GetvScreenCredits() ' Get the average screen position for all players for the games outro\n//  Get the average screen position for all players for the games outro\nvoid GetvScreenCredits(vScreen_t& vscreen);\n// Public Sub DoCredits() 'print credits\n// print credits\nvoid DoCredits();\nvoid DrawCredits();\n// Public Sub DrawInterface(Z As Integer, numScreens) 'draws the games interface\n// draws the games interface\nvoid DrawInterface(int Z, int numScreens);\n\n/*!\n * \\brief NEW: draws the lives / 100s count\n * \\param X      the *right* side of the 1UP / 100 icon\n * \\param Y      top Y coordinate\n * \\param lives  lives count to draw\n * \\param hunds  100s count to draw\n * \\param force_lives force draw of the lives count\n */\nvoid DrawLives(int X, int Y, int lives, int hunds, bool force_lives = false);\n\n/*!\n * \\brief NEW: draws a set of medals onscreen\n * \\param X      right/center X coordinate\n * \\param Y      top Y coordinate\n * \\param warp   whether this is a medals preview for a warp/level; will be centered on X if true, otherwise right-justified\n * \\param max    integer for the maximum number of medals (limited to 8)\n * \\param prev   bitmask for previously received medals (used in level)\n * \\param ckpt   bitmask for medals gotten before checkpoint (used in world / hub)\n * \\param got    bitmask for acquired medals (acquired in current session, when inside level)\n * \\param best   bitmask for best set of medals acquired during a single life (acquired during current life, when inside level)\n */\nvoid DrawMedals(int X, int Y, bool warp, uint8_t max, uint8_t prev, uint8_t ckpt, uint8_t got, uint8_t best);\n\n// NEW: draws the level editor interface on vScreen Z\nvoid DrawEditorLevel(int Z);\n// NEW: draws the level editor UI\nvoid DrawEditorLevel_UI();\n// NEW: draws the world editor interface\nvoid DrawEditorWorld();\n\n#if 0\n// old, 100x100-based functions\n\n// Public Function pfrX(plrFrame As Integer) As Integer\n//! Get X offset at the player sprite (old call, required to add 100 into source value)\nint pfrXo(int plrFrame);\n// Public Function pfrY(plrFrame As Integer) As Integer\n//! Get Y offset at the player sprite (old call, required to add 100 into source value)\nint pfrYo(int plrFrame);\n//! Get X offset at the player sprite\nint pfrX(int plrFrame);\n//! Get Y offset at the player sprite\nint pfrY(int plrFrame);\n#endif\n\n// NEW: defined in gfx_draw_player.cpp\n\n//! Get left pixel at the player sprite\nint pfrX(const StdPicture& tx, const Player_t& p);\n//! Get top pixel at the player sprite\nint pfrY(const StdPicture& tx, const Player_t& p);\n//! Get width at the player sprite\nint pfrW(const StdPicture& tx, const Player_t& p);\n//! Get height at the player sprite\nint pfrH(const StdPicture& tx, const Player_t& p);\n//! Get x offset that should be ADDED to the player position to draw the sprite\nint pfrOffX(const StdPicture& tx, const Player_t& p);\n//! Get y offset that should be ADDED to the player position to draw the sprite\nint pfrOffY(const StdPicture& tx, const Player_t& p);\n\n// Public Sub GameThing()\n/*!\n * \\brief Draw the level enter scene\n * \\param noSetup Avoid player settings re-setup\n */\nvoid GameThing(int waitms = 0, int fadeSpeed = 0);\n// Draw a player frame onscreen\nvoid DrawPlayerRaw(int X, int Y, int Character, int State, int Frame, int Direction);\n// Public Sub DrawPlayer(A As Integer, Z As Integer)\nvoid DrawPlayer(const int A, const int Z, XTColor color = XTColor());\nvoid DrawPlayer(Player_t &p, const int Z, XTColor color = XTColor());\n\n// NEW: cX and tY are in screen coordinates\nvoid DrawCycloneAccessory(int Z, const Player_t& p, int cX, int tY, XTColor c);\n\n// Public Sub ScreenShot()\nvoid ScreenShot();\n// Public Sub DrawFrozenNPC(Z As Integer, A As Integer)\nvoid DrawFrozenNPC(num_t camX, num_t camY, int A);\n// NEW: draw wings for an NPC at a particular location\nvoid DrawNPCWings(const NPC_t& n, int sX, int sY, XTColor cn);\n// NEW: draw NPC held by player\nvoid DrawNPCHeld(int Z, num_t camX, num_t camY, int A);\n// NEW: draw NPC (generic routine)\nvoid DrawNPC(num_t camX, num_t camY, int A);\n\n// NEW: draw the backdrop texture behind the vScreens\nvoid DrawBackdrop(const Screen_t& screen);\n// NEW: draws device battery status in top-right corner of screen\nvoid DrawDeviceBattery();\n\n\n#endif // GRAPHICS_H\n"
  },
  {
    "path": "src/layers.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <set>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"globals.h\"\n#include \"layers.h\"\n#include \"saved_layers.h\"\n#include \"effect.h\"\n#include \"collision.h\"\n#include \"npc.h\"\n#include \"npc_id.h\"\n#include \"eff_id.h\"\n#include \"blk_id.h\"\n#include \"npc_traits.h\"\n#include \"sound.h\"\n#include \"graphics.h\"\n#include \"game_main.h\"\n#include \"config.h\"\n#include \"frame_timer.h\"\n#include \"main/speedrunner.h\"\n#include \"editor.h\"\n#include \"player.h\"\n#include \"blocks.h\"\n#include \"main/trees.h\"\n#include \"main/block_table.h\"\n#include \"script/msg_preprocessor.h\"\n\n#include \"npc/npc_activation.h\"\n#include \"npc/npc_queues.h\"\n#include \"npc/section_overlap.h\"\n#include \"graphics/gfx_update.h\"\n#include \"main/game_loop_interrupt.h\"\n\nint numLayers = 0;\nRangeArr<Layer_t, 0, maxLayers> Layer;\n\nint numEvents = 0;\nRangeArr<Events_t, 0, maxEvents> Events;\n\nRangeArrI<eventindex_t, 1, maxEvents, EVENT_NONE> NewEvent;\nRangeArrI<vbint_t, 1, maxEvents, 0> newEventDelay;\nRangeArrI<uint8_t, 1, maxEvents, 0> newEventPlayer;\nint newEventNum = 0;\n\nlayerindex_t LAYER_USED_P_SWITCH = LAYER_NONE;\nstatic std::set<eventindex_t> recentlyTriggeredEvents;\n\nstatic SDL_INLINE bool equalCase(const std::string &x, const std::string &y)\n{\n    return (SDL_strcasecmp(x.c_str(), y.c_str()) == 0);\n}\n\nstatic SDL_INLINE ATTRIB_UNUSED bool equalCase(const char *x, const char *y)\n{\n    return (SDL_strcasecmp(x, y) == 0);\n}\n\nstatic void sorted_insert(std::vector<vbint_t>& vec, vbint_t val)\n{\n    auto it = std::lower_bound(vec.begin(), vec.end(), val);\n    bool val_exists = (it != vec.end() && *it == val);\n\n    // don't duplicate value\n    if(val_exists)\n        return;\n\n    vec.insert(it, val);\n}\n\nstatic void sorted_erase(std::vector<vbint_t>& vec, vbint_t val)\n{\n    auto it = std::lower_bound(vec.begin(), vec.end(), val);\n    bool val_exists = (it != vec.end() && *it == val);\n\n    // don't remove nonexistent value\n    if(!val_exists)\n        return;\n\n    vec.erase(it);\n}\n\n\n\n// utilities for layerindex_t and eventindex_t\n\nlayerindex_t FindLayer(const std::string& LayerName)\n{\n    if(LayerName.empty())\n        return LAYER_NONE;\n\n    for(layerindex_t A = 0; A <= maxLayers; A++)\n    {\n        if(equalCase(Layer[A].Name, LayerName))\n            return A;\n    }\n\n    return LAYER_NONE;\n}\n\neventindex_t FindEvent(const std::string& EventName)\n{\n    if(EventName.empty())\n        return EVENT_NONE;\n\n    for(eventindex_t A = 0; A <= maxEvents; A++)\n    {\n        if(equalCase(Events[A].Name, EventName))\n            return A;\n    }\n\n    return EVENT_NONE;\n}\n\n\n// Functions for layers\n\n\n// New functions:\n\n\nbool SwapLayers(layerindex_t index_1, layerindex_t index_2)\n{\n    if(index_1 == LAYER_NONE || index_2 == LAYER_NONE)\n        return false;\n\n    std::swap(Layer[index_1], Layer[index_2]);\n\n    // repoint all of the new Layer 1's objects to index 1\n    for(int A : Layer[index_1].NPCs)\n        NPC[A].Layer = index_1;\n\n    for(int A : Layer[index_1].blocks)\n        Block[A].Layer = index_1;\n\n    for(int A : Layer[index_1].BGOs)\n        Background[A].Layer = index_1;\n\n    for(int A : Layer[index_1].warps)\n        Warp[A].Layer = index_1;\n\n    for(int A : Layer[index_1].waters)\n        Water[A].Layer = index_1;\n\n    // repoint all of the new Layer 2's objects to index 2\n    for(int A : Layer[index_2].NPCs)\n        NPC[A].Layer = index_2;\n\n    for(int A : Layer[index_2].blocks)\n        Block[A].Layer = index_2;\n\n    for(int A : Layer[index_2].BGOs)\n        Background[A].Layer = index_2;\n\n    for(int A : Layer[index_2].warps)\n        Warp[A].Layer = index_2;\n\n    for(int A : Layer[index_2].waters)\n        Water[A].Layer = index_2;\n\n    // swap AttLayers\n    for(int A = 1; A <= numNPCs; A++)\n    {\n        if(NPC[A].AttLayer == index_1)\n            NPC[A].AttLayer = index_2;\n        else if(NPC[A].AttLayer == index_2)\n            NPC[A].AttLayer = index_1;\n    }\n\n    // swap event layer refs\n    for(int A = 0; A < numEvents; A++)\n    {\n        for(layerindex_t& l : Events[A].HideLayer)\n        {\n            if(l == index_1)\n                l = index_2;\n            else if(l == index_2)\n                l = index_1;\n        }\n\n        for(layerindex_t& l : Events[A].ShowLayer)\n        {\n            if(l == index_1)\n                l = index_2;\n            else if(l == index_2)\n                l = index_1;\n        }\n\n        for(layerindex_t& l : Events[A].ToggleLayer)\n        {\n            if(l == index_1)\n                l = index_2;\n            else if(l == index_2)\n                l = index_1;\n        }\n\n        if(Events[A].MoveLayer == index_1)\n            Events[A].MoveLayer = index_2;\n        else if(Events[A].MoveLayer == index_2)\n            Events[A].MoveLayer = index_1;\n    }\n\n    // swap EditorCursor layer\n    if(EditorCursor.Layer == index_1)\n        EditorCursor.Layer = index_2;\n    else if(EditorCursor.Layer == index_2)\n        EditorCursor.Layer = index_1;\n\n    LAYER_USED_P_SWITCH = FindLayer(LAYER_USED_P_SWITCH_TITLE);\n\n    return true;\n}\n\nbool RenameLayer(layerindex_t L, const std::string& NewName)\n{\n    if(L == LAYER_NONE)\n        return false;\n    if(NewName.empty())\n        return false;\n\n    int A = 0;\n\n    for(A = 0; A <= maxLayers; A++)\n    {\n        if(equalCase(Layer[A].Name, NewName))\n            return false;\n    }\n\n    Layer[L].Name = NewName;\n\n    LAYER_USED_P_SWITCH = FindLayer(LAYER_USED_P_SWITCH_TITLE);\n\n    return true;\n}\n\nbool DeleteLayer(layerindex_t L, bool killall)\n{\n    if(L == LAYER_NONE)\n        return false;\n\n    int A = 0;\n\n    // order is important here, thus the unoptimized loops\n    for(A = numNPCs; A >= 1; A--)\n    {\n        if(NPC[A].AttLayer == L)\n            NPC[A].AttLayer = LAYER_NONE;\n        if(NPC[A].Layer == L)\n        {\n            if(killall)\n                KillNPC(A, 9);\n            else\n            {\n                NPC[A].Layer = LAYER_DEFAULT;\n                syncLayers_NPC(A);\n            }\n        }\n    }\n\n    for(A = numBlock; A >= 1; A--)\n    {\n        if(Block[A].Layer == L)\n        {\n            if(killall)\n                KillBlock(A, false);\n            else\n            {\n                Block[A].Layer = LAYER_DEFAULT;\n                syncLayersTrees_Block(A);\n            }\n        }\n    }\n\n    for(A = numWarps; A >= 1; A--)\n    {\n        if(Warp[A].Layer == L)\n        {\n            if(killall)\n                KillWarp(A);\n            else\n            {\n                Warp[A].Layer = LAYER_DEFAULT;\n                syncLayers_Warp(A);\n            }\n        }\n    }\n\n    for(A = numBackground; A >= 1; A--)\n    {\n        if(Background[A].Layer == L)\n        {\n            if(killall)\n            {\n                Background[A] = Background[numBackground];\n                numBackground --;\n                syncLayers_BGO(A);\n                syncLayers_BGO(numWater+1);\n            }\n            else\n            {\n                Background[A].Layer = LAYER_DEFAULT;\n                syncLayers_BGO(A);\n            }\n        }\n    }\n\n    for(A = numWater; A >= 1; A--)\n    {\n        if(Water[A].Layer == L)\n        {\n            if(killall)\n            {\n                Water[A] = Water[numWater];\n                numWater --;\n                syncLayers_Water(A);\n                syncLayers_Water(numWater+1);\n            }\n            else\n            {\n                Water[A].Layer = LAYER_DEFAULT;\n                syncLayers_Water(A);\n            }\n        }\n    }\n\n    for(A = 0; A < numEvents; A++)\n    {\n        for(auto it = Events[A].HideLayer.end(); it != Events[A].HideLayer.begin();)\n        {\n            it--;\n            if(*it == L)\n                it = Events[A].HideLayer.erase(it);\n        }\n\n        for(auto it = Events[A].ShowLayer.end(); it != Events[A].ShowLayer.begin();)\n        {\n            it--;\n            if(*it == L)\n                it = Events[A].ShowLayer.erase(it);\n        }\n\n        for(auto it = Events[A].ToggleLayer.end(); it != Events[A].ToggleLayer.begin();)\n        {\n            it--;\n            if(*it == L)\n                it = Events[A].ToggleLayer.erase(it);\n        }\n\n        if(Events[A].MoveLayer == L)\n            Events[A].MoveLayer = LAYER_NONE;\n    }\n\n    for(int B = L; B < numLayers - 1; B++)\n        SwapLayers(B, B+1);\n\n    numLayers --;\n    Layer[numLayers] = Layer_t();\n\n    if(EditorCursor.Layer == L)\n        EditorCursor.Layer = LAYER_DEFAULT;\n\n    LAYER_USED_P_SWITCH = FindLayer(LAYER_USED_P_SWITCH_TITLE);\n\n    return true;\n}\n\nvoid SetLayerSpeed(layerindex_t L, num_t SpeedX, num_t SpeedY, bool EffectStop, bool Defective)\n{\n    if(L == LAYER_NONE)\n        return;\n\n    if(g_config.fix_attlayer_reset)\n        Defective = false;\n\n    // relatively simple code to set the layer's speed\n    if(SpeedX != 0 || SpeedY != 0 || Defective)\n    {\n        Layer[L].SpeedX = (numf_t)SpeedX;\n        Layer[L].SpeedY = (numf_t)SpeedY;\n\n        if(!Defective)\n            Layer[L].EffectStop = EffectStop;\n\n        return;\n    }\n\n\n    // relatively more complex code to stop the layer -- first check that it's necessary (note: this check prevents an infinite recursion)\n    if(Layer[L].SpeedX == 0 && Layer[L].SpeedY == 0)\n        return;\n\n\n    // EffectStop is set arbitrarily in the SMBX 1.3 code (and not used while speed is 0), but fortunately always to the opposite of what it is actually meant to be\n    Layer[L].EffectStop = !EffectStop;\n    Layer[L].SpeedX = 0;\n    Layer[L].SpeedY = 0;\n    Layer[L].ApplySpeedX = 0;\n    Layer[L].ApplySpeedY = 0;\n\n    for(int C : Layer[L].blocks)\n    {\n        Block[C].Location.SpeedX = 0;\n        Block[C].Location.SpeedY = 0;\n\n        if(Block[C].Type >= BLKID_CONVEYOR_L_START && Block[C].Type <= BLKID_CONVEYOR_L_END)\n            Block[C].Location.SpeedX = -0.8_n;\n        else if(Block[C].Type >= BLKID_CONVEYOR_R_START && Block[C].Type <= BLKID_CONVEYOR_R_END)\n            Block[C].Location.SpeedX = 0.8_n;\n    }\n\n    for(int C : Layer[L].NPCs)\n    {\n        if(NPC[C]->IsAVine || NPC[C].Type == NPCID_ITEM_BURIED)\n        {\n            NPC[C].Location.SpeedX = 0;\n            NPC[C].Location.SpeedY = 0;\n        }\n\n        if(g_config.fix_attlayer_reset && !NPC[C].Active)\n        {\n            // this does not cause an infinite recursion due to the early return code above\n            if(NPC[C].AttLayer != LAYER_NONE && NPC[C].AttLayer != LAYER_DEFAULT)\n                SetLayerSpeed(NPC[C].AttLayer, 0, 0);\n        }\n    }\n}\n\n\n// Old functions:\n\nvoid ShowLayer(layerindex_t L, bool NoEffect)\n{\n    if(L == LAYER_NONE)\n        return;\n\n    int A = 0;\n    int B = 0;\n\n    if(Layer[L].SavedLayer)\n        SavedLayers[Layer[L].SavedLayer - 1].Visible = true;\n\n    Layer[L].Hidden = false;\n    if(L == LAYER_DESTROYED_BLOCKS)\n        Layer[L].Hidden = true;\n\n    if(L == LAYER_SPAWNED_NPCS)\n        Layer[L].Hidden = false;\n\n    for(int A : Layer[L].NPCs)\n    {\n        if(NPC[A].Hidden)\n        {\n            if(!NoEffect && !NPC[A].Generator)\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n\n            if(!LevelEditor)\n            {\n                if(!NPC[A]->WontHurt && !NPC[A]->IsABonus && NPC[A].Active)\n                {\n                    for(B = 1; B <= numPlayers; B++)\n                    {\n                        if(CheckCollision(Player[B].Location, NPC[A].Location))\n                            Player[B].Immune = 120;\n                    }\n                }\n            }\n        }\n        NPC[A].Hidden = false;\n        NPC[A].GeneratorActive = true;\n        NPC[A].Reset[1] = true;\n        NPC[A].Reset[2] = true;\n\n        if(!NPC[A].Generator)\n        {\n            bool do_activate = true;\n\n            // new logic: if an NPC must follow canonical screen logic, only activate it if on a canonical vScreen\n            // Fixes bug at star exit on SRW2:YA - Searing Skull Stepping\n            if(g_config.fix_npc_camera_logic && !NPC[A].Active && NPC_MustBeCanonical(A))\n            {\n                bool hit = false;\n\n                // see if it's close to a canonical screen (within 8px), and disallow it from activating if not\n                // (fixes mostly vanilla bug which occurs because visible NPCs move following Deactivate but hidden NPCs don't)\n                Location_t tempLocation = NPC[A].Location;\n                tempLocation.X -= 8;\n                tempLocation.Y -= 8;\n                tempLocation.Width += 16;\n                tempLocation.Height += 16;\n\n                for(int screen_i = 0; !hit && screen_i < c_screenCount; screen_i++)\n                {\n                    const Screen_t& screen = Screens[screen_i];\n\n                    if(!screen.is_active())\n                        continue;\n\n                    if(!screen.is_canonical())\n                        continue;\n\n                    for(int vscreen_i = screen.active_begin(); !hit && vscreen_i < screen.active_end(); vscreen_i++)\n                    {\n                        int vscreen_Z = screen.vScreen_refs[vscreen_i];\n\n                        if(vScreenCollision(vscreen_Z, tempLocation))\n                            hit = true;\n                    }\n                }\n\n                if(!hit)\n                    do_activate = false;\n            }\n\n            if(do_activate)\n            {\n                NPC[A].Active = true;\n                NPC[A].TimeLeft = 1;\n\n                NPCQueues::Active.insert(A);\n            }\n        }\n\n        CheckSectionNPC(A);\n    }\n\n    if(!Layer[L].blocks.empty())\n        invalidateDrawBlocks();\n\n    for(int A : Layer[L].blocks)\n    {\n        // If Not (Block(A).DefaultType = 0 And Block(A).Layer = \"Destroyed Blocks\") Then\n        if(Block[A].Hidden)\n        {\n            if(!NoEffect && !Block[A].Invis)\n                NewEffect(EFFID_SMOKE_S3_CENTER, Block[A].Location);\n        }\n        Block[A].Hidden = false;\n\n        // moved code to restore all hit blocks below\n    }\n\n    if(!Layer[L].BGOs.empty())\n        invalidateDrawBGOs();\n\n    for(int A : Layer[L].BGOs)\n    {\n        if(Background[A].Hidden)\n        {\n            if(!NoEffect)\n                NewEffect(EFFID_SMOKE_S3_CENTER, static_cast<Location_t>(Background[A].Location));\n        }\n        Background[A].Hidden = false;\n    }\n\n    for(int A : Layer[L].warps)\n        Warp[A].Hidden = false;\n\n    for(int A : Layer[L].waters)\n        Water[A].Hidden = false;\n\n    if(L == LAYER_DESTROYED_BLOCKS)\n    {\n        // restore all hit blocks, even non-destroyed\n        for(A = 1; A <= numBlock; A++)\n        {\n            if(Block[A].DefaultType > 0)\n            {\n                // could be nice to have an \"orig_layer\" variable,\n                //  would eliminate certain vanilla peculiarities,\n                //  especially if done cleverly with the block's\n                //  layer offset\n                if(Block[A].Layer == L)\n                    Block[A].Layer = LAYER_DEFAULT;\n                Block[A].Special = Block[A].DefaultSpecial;\n                Block[A].Type = Block[A].DefaultType;\n                syncLayersTrees_Block(A);\n            }\n        }\n    }\n}\n\nvoid HideLayer(layerindex_t L, bool NoEffect)\n{\n    if(L == LAYER_NONE)\n        return;\n\n    if(Layer[L].SavedLayer)\n        SavedLayers[Layer[L].SavedLayer - 1].Visible = false;\n\n    Layer[L].Hidden = true;\n\n    for(int A : Layer[L].NPCs)\n    {\n        if(!NPC[A].Hidden)\n        {\n            if(!NoEffect && !NPC[A].Generator)\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n        }\n\n        NPC[A].Hidden = true;\n\n        if(!LevelEditor && !NPC[A].Generator)\n            Deactivate(A);\n    }\n\n    if(!Layer[L].blocks.empty())\n        invalidateDrawBlocks();\n\n    for(int A : Layer[L].blocks)\n    {\n        if(!Block[A].Hidden)\n        {\n            if(!NoEffect && !Block[A].Invis)\n                NewEffect(EFFID_SMOKE_S3_CENTER, Block[A].Location);\n        }\n        Block[A].Hidden = true;\n    }\n\n    if(!Layer[L].BGOs.empty())\n        invalidateDrawBGOs();\n\n    for(int A : Layer[L].BGOs)\n    {\n        if(!Background[A].Hidden)\n        {\n            if(!NoEffect)\n                NewEffect(EFFID_SMOKE_S3_CENTER, static_cast<Location_t>(Background[A].Location));\n        }\n        Background[A].Hidden = true;\n    }\n\n    for(int A : Layer[L].warps)\n        Warp[A].Hidden = true;\n\n    for(int A : Layer[L].waters)\n        Water[A].Hidden = true;\n}\n\nvoid SetLayer(layerindex_t /*LayerName*/)\n{\n    // Unused\n}\n\n\n\n// Functions for events\n\n\n// New functions:\n\nvoid InitializeEvent(Events_t& event)\n{\n    event.reinit();\n\n    for(int i = 0; i <= maxSections; i++)\n        event.section[i].position.X = EventSection_t::LESet_Nothing;\n}\n\nbool SwapEvents(eventindex_t index_1, eventindex_t index_2)\n{\n    if(index_1 == EVENT_NONE || index_2 == EVENT_NONE)\n        return false;\n\n    std::swap(Events[index_1], Events[index_2]);\n\n    int A = 0;\n\n    // swap EVERYTHING\n    for(A = 1; A <= numNPCs; A++)\n    {\n        if(NPC[A].TriggerTalk == index_1)\n            NPC[A].TriggerTalk = index_2;\n        else if(NPC[A].TriggerTalk == index_2)\n            NPC[A].TriggerTalk = index_1;\n\n        if(NPC[A].TriggerDeath == index_1)\n            NPC[A].TriggerDeath = index_2;\n        else if(NPC[A].TriggerDeath == index_2)\n            NPC[A].TriggerDeath = index_1;\n\n        if(NPC[A].TriggerLast == index_1)\n            NPC[A].TriggerLast = index_2;\n        else if(NPC[A].TriggerLast == index_2)\n            NPC[A].TriggerLast = index_1;\n\n        if(NPC[A].TriggerActivate == index_1)\n            NPC[A].TriggerActivate = index_2;\n        else if(NPC[A].TriggerActivate == index_2)\n            NPC[A].TriggerActivate = index_1;\n    }\n\n    for(A = 1; A <= numBlock; A++)\n    {\n        if(Block[A].TriggerHit == index_1)\n            Block[A].TriggerHit = index_2;\n        else if(Block[A].TriggerHit == index_2)\n            Block[A].TriggerHit = index_1;\n\n        if(Block[A].TriggerDeath == index_1)\n            Block[A].TriggerDeath = index_2;\n        else if(Block[A].TriggerDeath == index_2)\n            Block[A].TriggerDeath = index_1;\n\n        if(Block[A].TriggerLast == index_1)\n            Block[A].TriggerLast = index_2;\n        else if(Block[A].TriggerLast == index_2)\n            Block[A].TriggerLast = index_1;\n    }\n\n    for(A = 1; A <= numWarps; A++)\n    {\n        if(Warp[A].eventEnter == index_1)\n            Warp[A].eventEnter = index_2;\n        else if(Warp[A].eventEnter == index_2)\n            Warp[A].eventEnter = index_1;\n\n        if(Warp[A].eventExit == index_1)\n            Warp[A].eventExit= index_2;\n        else if(Warp[A].eventExit == index_2)\n            Warp[A].eventExit = index_1;\n    }\n\n    for(A = 0; A < numEvents; A++)\n    {\n        if(Events[A].TriggerEvent == index_1)\n            Events[A].TriggerEvent = index_2;\n        else if(Events[A].TriggerEvent == index_2)\n            Events[A].TriggerEvent = index_1;\n    }\n\n    return true;\n}\n\nbool RenameEvent(eventindex_t index, const std::string& NewName)\n{\n    if(index == EVENT_NONE || NewName.empty())\n        return false;\n\n    Events[index].Name = NewName;\n\n    return true;\n}\n\nbool DeleteEvent(eventindex_t index)\n{\n    if(index == EVENT_NONE)\n        return false;\n\n    int A = 0;\n\n    for(A = 1; A <= numNPCs; A++)\n    {\n        if(NPC[A].TriggerTalk == index)\n            NPC[A].TriggerTalk = EVENT_NONE;\n\n        if(NPC[A].TriggerDeath == index)\n            NPC[A].TriggerDeath = EVENT_NONE;\n\n        if(NPC[A].TriggerLast == index)\n            NPC[A].TriggerLast = EVENT_NONE;\n\n        if(NPC[A].TriggerActivate == index)\n            NPC[A].TriggerActivate = EVENT_NONE;\n    }\n\n    for(A = 1; A <= numBlock; A++)\n    {\n        if(Block[A].TriggerHit == index)\n            Block[A].TriggerHit = EVENT_NONE;\n\n        if(Block[A].TriggerDeath == index)\n            Block[A].TriggerDeath = EVENT_NONE;\n\n        if(Block[A].TriggerLast == index)\n            Block[A].TriggerLast = EVENT_NONE;\n    }\n\n    for(A = 1; A <= numWarps; A++)\n    {\n        if(Warp[A].eventEnter == index)\n            Warp[A].eventEnter = EVENT_NONE;\n\n        if(Warp[A].eventExit == index)\n            Warp[A].eventExit = EVENT_NONE;\n    }\n\n    for(A = 0; A < numEvents; A++)\n    {\n        if(Events[A].TriggerEvent == index)\n            Events[A].TriggerEvent = EVENT_NONE;\n    }\n\n    for(int B = index; B < numEvents - 1; B++)\n        SwapEvents(B, B+1);\n\n    numEvents--;\n    Events[numEvents].reinit();\n\n    return true;\n}\n\n\n// Helper functions for ProcEvent\n\n// tests which players are in a resized section, and warps other onscreen players to the section if do_warp is enabled\nstatic inline void s_testPlayersInSection(const Screen_t& screen, int B, bool do_warp, int& onscreen_plr, int& warped_plr)\n{\n    // warp EVERYONE in cloned player mode, otherwise just warp players of this screen\n    int i_start = g_ClonedPlayerMode ? 1 : 0;\n    int i_end   = g_ClonedPlayerMode ? numPlayers + 1 : screen.player_count;\n    for(int i = i_start; i < i_end; i++)\n    {\n        int C = g_ClonedPlayerMode ? i : screen.players[i];\n\n        // If .Section = B Then\n        // Should set this only if the warp is successful!\n        if(do_warp && !g_config.modern_section_change)\n            Player[C].Section = B;\n\n        bool tempBool = false;\n        if(Player[C].Location.X + Player[C].Location.Width >= level[B].X)\n        {\n            if(Player[C].Location.X <= level[B].Width)\n            {\n                if(Player[C].Location.Y + Player[C].Location.Height >= level[B].Y)\n                {\n                    if(Player[C].Location.Y <= level[B].Height)\n                    {\n                        tempBool = true; // Check to see if player is still in section after resizing\n                        onscreen_plr = C;\n\n                        if(do_warp)\n                            Player[C].Section = B;\n                    }\n                }\n            }\n        }\n\n        // don't warp on reset\n        if(!do_warp)\n            continue;\n\n        if(!tempBool)\n        {\n            for(int D = 1; D <= numPlayers; D++)\n            {\n                if(D != C && Player[D].Section == B)\n                {\n                    if(Player[D].Location.X + Player[D].Location.Width >= level[B].X)\n                    {\n                        if(Player[D].Location.X <= level[B].Width)\n                        {\n                            if(Player[D].Location.Y + Player[D].Location.Height >= level[B].Y)\n                            {\n                                if(Player[D].Location.Y <= level[B].Height) // Move to another player who is still in the section\n                                {\n                                    warped_plr = C;\n\n                                    Player[C].Section = B;\n                                    Player[C].Location.X = Player[D].Location.X + (Player[D].Location.Width - Player[C].Location.Width) / 2;\n                                    Player[C].Location.Y = Player[D].Location.Y + Player[D].Location.Height - Player[C].Location.Height;\n                                    Player[C].Effect = PLREFF_NO_COLLIDE;\n                                    Player[C].Effect2 = D;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        // End If\n    }\n}\n\n// initializes modern qScreen for section B, and returns true if any of the screen's vScreens got qScreen enabled\nstatic inline bool s_initModernQScreen(Screen_t& screen, const int B, const SpeedlessLocation_t& tempLevel, const SpeedlessLocation_t& newLevel, const int onscreen_plr, const int warped_plr, const bool is_reset)\n{\n    bool set_qScreen = false;\n\n    // check that the onscreen player is here\n    if(onscreen_plr == 0 && warped_plr == 0)\n    {\n        // no qScreen because there were no onscreen players, but do an update in case another Screen called qScreen\n        for(int i = screen.active_begin(); i < screen.active_end(); i++)\n        {\n            int Z_i = screen.vScreen_refs[i];\n            GetvScreenAuto(vScreen[Z_i]);\n            qScreenLoc[Z_i] = vScreen[Z_i];\n        }\n    }\n    // 2P dynamic screen handling\n    else if(screen.Type == ScreenTypes::Dynamic)\n    {\n        int Z1 = screen.vScreen_refs[0];\n        int Z2 = screen.vScreen_refs[1];\n\n        int p1 = screen.players[0];\n        int p2 = screen.players[1];\n\n        set_qScreen = true;\n\n        bool screen2_was_visible = vScreen[Z2].Visible;\n\n        int32_t tX = 0;\n        int32_t tY = 0;\n\n        if(warped_plr == p1 || warped_plr == p2)\n        {\n            int warped_Z = (warped_plr == p1) ? Z1 : Z2;\n            int onscreen_Z = (onscreen_plr == p1) ? Z1 : Z2;\n            tX = (int)vScreen[warped_Z].X - (int)vScreen[onscreen_Z].X;\n            tY = (int)vScreen[warped_Z].Y - (int)vScreen[onscreen_Z].Y;\n        }\n\n        SoundPause[SFX_Camera] = 10;\n\n        // need two cycles to fully update the dynamic screens in the new level\n        SetupScreens(screen, false);\n        DynamicScreen(screen);\n        CenterScreens(screen);\n\n        if(vScreen[Z2].Visible)\n        {\n            for(int Z = 1; Z <= 2; Z++)\n                GetvScreen(screen.vScreen(Z));\n        }\n        else\n            GetvScreenAverage(vScreen[Z1]);\n\n        // set up the dynamic screens in the new level\n        SetupScreens(screen, false);\n        DynamicScreen(screen);\n\n        // set the positions (including screen positions) in the old level, but with the NEW dynamic splits\n        level[B] = tempLevel;\n        CenterScreens(screen);\n\n        if(vScreen[Z2].Visible)\n        {\n            for(int Z = 1; Z <= 2; Z++)\n                GetvScreen(screen.vScreen(Z));\n        }\n        else\n        {\n            GetvScreenAverage(vScreen[Z1]);\n        }\n\n        // set the qScreen!\n        qScreenLoc[Z1] = vScreen[Z1];\n\n        // set the second qScreen if possible\n        if(vScreen[Z2].Visible)\n            qScreenLoc[Z2] = vScreen[Z2];\n\n        // special code to indicate the direction the other player was warped from\n        if((tX || tY) && (screen2_was_visible && !vScreen[Z2].Visible))\n        {\n            // total distance of warp\n            int xSq = tX * tX;\n            int ySq = tY * tY;\n            int dSquare = xSq + ySq;\n\n            // project onto the circle: proportion of distance from each axis\n            if(tX < 0)\n                xSq *= -1;\n\n            if(tY < 0)\n                ySq *= -1;\n\n            // maximum total shift of 1/4 of the vScreen's size; also limit by 200x150 (SMBX64 amount)\n            int maxShiftX = vScreen[Z1].Width / 4;\n            int maxShiftY = vScreen[Z1].Height / 4;\n\n            if(maxShiftX > 200)\n                maxShiftX = 200;\n\n            if(maxShiftY > 150)\n                maxShiftY = 150;\n\n            // apply the shift\n            qScreenLoc[Z1].X += maxShiftX * xSq / dSquare;\n            qScreenLoc[Z1].Y += maxShiftY * ySq / dSquare;\n\n            // restrict to old level bounds\n            if(-qScreenLoc[Z1].X < level[B].X)\n                qScreenLoc[Z1].X = -level[B].X;\n\n            if(-qScreenLoc[Z1].X + vScreen[Z1].Width > level[B].Width)\n                qScreenLoc[Z1].X = -(level[B].Width - vScreen[Z1].Width);\n\n            if(-qScreenLoc[Z1].Y < level[B].Y)\n                qScreenLoc[Z1].Y = -level[B].Y;\n\n            if(-qScreenLoc[Z1].Y + vScreen[Z1].Height > level[B].Height)\n                qScreenLoc[Z1].Y = -(level[B].Height - vScreen[Z1].Height);\n        }\n\n        // restore the new level\n        level[B] = newLevel;\n    }\n    // single-screen or forced split-screen code\n    else\n    {\n        // first, quickly store the old vScreens into qScreen\n        for(int i = screen.active_begin(); i < screen.active_end(); i++)\n        {\n            int Z_i = screen.vScreen_refs[i];\n\n            // update qScreenLoc only if qScreen was not already set (avoids jumps)\n            if(screen.Visible && !qScreen)\n                qScreenLoc[Z_i] = vScreen[Z_i];\n\n            if(!screen.Visible && screen.is_canonical() && !qScreen_canonical)\n                qScreenLoc[Z_i] = vScreen[Z_i];\n        }\n\n        // now, update the screen positions here before proceeding\n        SetupScreens(screen, false);\n        CenterScreens(screen);\n\n        for(int i = screen.active_begin(); i < screen.active_end(); i++)\n        {\n            int Z_i = screen.vScreen_refs[i];\n            Player_t& plr = Player[vScreen[Z_i].player];\n\n            // update vScreen position\n            GetvScreenAuto(vScreen[Z_i]);\n\n            // the next code is designed to avoid needing a qScreen if it wouldn't have occurred in the original game\n            bool use_new_resize = true;\n\n            int old_w = qScreenLoc[Z_i].Width;\n            int old_h = qScreenLoc[Z_i].Height;\n            num_t old_x = qScreenLoc[Z_i].X;\n            num_t old_y = qScreenLoc[Z_i].Y;\n\n            // (0) player should not have warped\n            if(plr.Effect == PLREFF_NO_COLLIDE)\n                use_new_resize = false;\n\n            // (1) old bounds shouldn't be outside of the new level\n            if(-old_x < level[B].X\n                || -old_x + old_w > level[B].Width\n                || -old_y < level[B].Y\n                || -old_y + old_h > level[B].Height)\n            {\n                use_new_resize = false;\n            }\n\n            // (2) new screen size should equal old (get new vScreen here)\n            if(vScreen[Z_i].Width != old_w || vScreen[Z_i].Height != old_h)\n                use_new_resize = false;\n\n            // (3) qScreen should not have occurred in old game\n            if(use_new_resize && !is_reset)\n            {\n                num_t cx, cy, old_cx, old_cy;\n\n                GetPlayerScreen(800, 600, plr, cx, cy);\n\n                level[B] = tempLevel;\n                GetPlayerScreen(800, 600, plr, old_cx, old_cy);\n                level[B] = newLevel;\n\n                if(num_t::abs(cx - old_cx) > 32 || num_t::abs(cy - old_cy) > 32)\n                    use_new_resize = false;\n            }\n\n            // do it!\n            if(use_new_resize)\n            {\n                qScreenLoc[Z_i] = vScreen[Z_i];\n                vScreen[Z_i].tempX = vScreen[Z_i].X - old_x;\n                vScreen[Z_i].TempY = vScreen[Z_i].Y - old_y;\n            }\n            else\n            {\n                // limit qScreen duration if player was warped\n                if(plr.Effect == PLREFF_NO_COLLIDE)\n                {\n                    Location_t old_section_loc = newLoc(tempLevel.X, tempLevel.Y, tempLevel.Width - tempLevel.X, tempLevel.Height - tempLevel.Y);\n                    Location_t qScreen_loc = newLoc(-qScreenLoc[Z_i].X, -qScreenLoc[Z_i].Y, qScreenLoc[Z_i].Width, qScreenLoc[Z_i].Height);\n\n                    // disable qScreen if cross-section\n                    if(!CheckCollision(old_section_loc, qScreen_loc))\n                    {\n                        qScreenLoc[Z_i] = vScreen[Z_i];\n                    }\n                    // otherwise, limit distance\n                    else\n                    {\n                        num_t distance = num_t::dist(vScreen[Z_i].X - qScreenLoc[Z_i].X, vScreen[Z_i].Y - qScreenLoc[Z_i].Y);\n\n                        if(distance > 400)\n                        {\n                            qScreenLoc[Z_i].X = ((qScreenLoc[Z_i].X - vScreen[Z_i].X) * 400).divided_by(distance) + vScreen[Z_i].X;\n                            qScreenLoc[Z_i].Y = ((qScreenLoc[Z_i].Y - vScreen[Z_i].Y) * 400).divided_by(distance) + vScreen[Z_i].Y;\n                        }\n                    }\n                }\n\n                set_qScreen = true;\n            }\n        }\n    }\n\n    return set_qScreen;\n}\n\n// initializes legacy qScreen for section B, and returns true in all cases\nstatic inline bool s_initLegacyQScreen(Screen_t& screen, const int B, const SpeedlessLocation_t& tempLevel, const SpeedlessLocation_t& newLevel, const int onscreen_plr)\n{\n    int Z1 = screen.vScreen_refs[0];\n    int Z2 = screen.vScreen_refs[1];\n\n    int p1 = screen.players[0];\n    int p2 = screen.players[1];\n\n    if(screen.player_count == 2 && screen.DType != 5)\n    {\n        level[B] = tempLevel;\n\n        // faithful to original code which used vScreen[C], where C could be 0\n        vScreen_t screenLoc = vScreen[0];\n        if(p2 == onscreen_plr)\n            screenLoc = vScreen[Z2];\n        else if(p1 == onscreen_plr)\n            screenLoc = vScreen[Z1];\n\n        SoundPause[SFX_Camera] = 10;\n\n        DynamicScreen(screen);\n\n        // calculate the vScreen at non-splitscreen resolution (as the original game does)\n        GetvScreenAverage(vScreen[Z1]);\n        qScreenLoc[Z1] = vScreen[Z1];\n\n        // pan to indicate warped player, in the direction the screen was previously split\n        //   (used hardcoded 400 / 200 / 300 / 150 in VB6 code)\n        if(int(screenLoc.Width) == screen.W / 2)\n        {\n            if(qScreenLoc[Z1].X < screenLoc.X + screenLoc.Left)\n                qScreenLoc[Z1].X += screen.W / 4;\n            else\n                qScreenLoc[Z1].X -= screen.W / 4;\n        }\n\n        if(int(screenLoc.Height) == screen.H / 2)\n        {\n            if(qScreenLoc[Z1].Y < screenLoc.Y + screenLoc.Top)\n                qScreenLoc[Z1].Y += screen.H / 4;\n            else\n                qScreenLoc[Z1].Y -= screen.H / 4;\n        }\n\n        // used ScreenW / H and FrmMain.ScaleWidth / Height in VB6 code\n        num_t use_width  = SDL_min(static_cast<num_t>(screen.W), level[B].Width  - level[B].X);\n        num_t use_height = SDL_min(static_cast<num_t>(screen.H), level[B].Height - level[B].Y);\n\n        // restrict to old level bounds\n        if(-qScreenLoc[Z1].X < level[B].X)\n            qScreenLoc[Z1].X = -level[B].X;\n\n        if(-qScreenLoc[Z1].X + use_width /*FrmMain.ScaleWidth*/ > level[B].Width)\n            qScreenLoc[Z1].X = -(level[B].Width - use_width);\n\n        if(-qScreenLoc[Z1].Y < level[B].Y)\n            qScreenLoc[Z1].Y = -level[B].Y;\n\n        if(-qScreenLoc[Z1].Y + use_height /*FrmMain.ScaleHeight*/ > level[B].Height)\n            qScreenLoc[Z1].Y = -(level[B].Height - use_height);\n\n        // restore the new level\n        level[B] = newLevel;\n    }\n    else\n    {\n        qScreenLoc[Z1] = vScreen[Z1];\n    }\n\n    return true;\n}\n\n// Old functions:\n\neventindex_t ProcEvent_Safe(bool is_resume, eventindex_t index, int whichPlayer, EventContext context)\n{\n    if(index == EVENT_NONE || LevelEditor)\n        return EVENT_NONE;\n\n    // this is for events that have just been triggered\n    int B = 0;\n    // int C = 0;\n    // int D = 0;\n    // bool tempBool = false;\n    SpeedlessLocation_t tempLevel;\n    SpeedlessLocation_t newLevel;\n\n    // Ignore vanilla autoscroll if newer way has been used\n    bool autoScrollerChanged = false;\n\n    auto &evt = Events[index];\n\n    if(is_resume)\n        goto event_resume;\n\n    recentlyTriggeredEvents.insert(index);\n\n    if(g_config.speedrun_stop_timer_by == Config_t::SPEEDRUN_STOP_EVENT && equalCase(evt.Name.c_str(), g_config.speedrun_stop_timer_at))\n        speedRun_bossDeadEvent();\n\n    for(B = 0; B < numSections; B++)\n    {\n        /* Music change */\n        auto &s = evt.section[B];\n\n        bool musicChanged = false;\n        if(s.music_id == EventSection_t::LESet_ResetDefault)\n        {\n            bgMusic[B] = bgMusicREAL[B];\n            musicChanged = true;\n        }\n        else if(s.music_id != EventSection_t::LESet_Nothing)\n        {\n            bgMusic[B] = s.music_id;\n            musicChanged = true;\n        }\n\n        if(musicChanged)\n            StartMusicIfOnscreen(B);\n\n        /* Background change */\n        if(s.background_id == EventSection_t::LESet_ResetDefault)\n            Background2[B] = Background2REAL[B];\n        else if(s.background_id != EventSection_t::LESet_Nothing)\n            Background2[B] = s.background_id;\n\n        /* Per-Section autoscroll setup */\n        if(s.autoscroll)\n        {\n            if(!AutoUseModern) // First attemt to use modern autoscrolling will block futher use of the legacy autoscrolling\n                AutoUseModern = true;\n            autoScrollerChanged = true;\n            AutoX[B] = s.autoscroll_x;\n            AutoY[B] = s.autoscroll_y;\n        }\n\n        bool is_reset = (s.position.X == EventSection_t::LESet_ResetDefault);\n\n        /* Resize the section boundaries */\n        if(is_reset && !g_config.modern_section_change)\n        {\n            level[B] = static_cast<SpeedlessLocation_t>(LevelREAL[B]);\n            UpdateSectionOverlaps(B);\n        }\n        else if(s.position.X != EventSection_t::LESet_Nothing)\n        {\n            tempLevel = level[B];\n            newLevel = static_cast<SpeedlessLocation_t>((is_reset) ? LevelREAL[B] : s.position);\n            level[B] = newLevel;\n            UpdateSectionOverlaps(B);\n\n            // track these across all screens\n            bool set_qScreen = false;\n            bool set_qScreen_canonical = false;\n\n            for(int screen_i = 0; screen_i < c_screenCount; screen_i++)\n            {\n                Screen_t& screen = Screens[screen_i];\n\n                if(!screen.is_active())\n                    continue;\n\n                // which player on this screen is already in the new section? (used for 2P dynamic)\n                int onscreen_plr = 0;\n                // which player on this screen was moved to the new section? (used for 2P dynamic)\n                int warped_plr = 0;\n\n                // warp other players to resized section, if not a reset or level start\n                bool do_warp = !is_reset && (index != EVENT_LEVEL_START);\n                // modern/classic logic: don't warp if it's the auto-run event loop\n                if(g_config.modern_section_change)\n                    do_warp &= (context != EventContext::InitSetup);\n                // vanilla logic: don't warp on any Autostart event (even if it isn't the first frame of the level)\n                else\n                    do_warp &= !evt.AutoStart;\n\n                s_testPlayersInSection(screen, B, do_warp, onscreen_plr, warped_plr);\n\n                bool set_qScreen_i = false;\n\n                // modern/classic: don't start any qScreen during initial setup frame\n                if(g_config.modern_section_change && context == EventContext::InitSetup)\n                {\n                    // do nothing\n                }\n                // don't start any qScreen for \"Level - Start\" event, on any frame (vanilla logic)\n                else if(index == EVENT_LEVEL_START)\n                {\n                    // do nothing\n                }\n                // start the modern qScreen animation\n                else if(g_config.modern_section_change)\n                    set_qScreen_i = s_initModernQScreen(screen, B, tempLevel, newLevel, onscreen_plr, warped_plr, is_reset);\n                // legacy qScreen animation\n                else\n                    set_qScreen_i = s_initLegacyQScreen(screen, B, tempLevel, newLevel, onscreen_plr);\n\n                if(set_qScreen_i)\n                {\n                    set_qScreen |= screen.Visible;\n                    set_qScreen_canonical |= !screen.Visible && screen.is_canonical();\n                }\n            }\n\n            // enable qScreen (now after all logic to prevent messing up GetvScreen calls)\n            qScreen |= set_qScreen;\n            qScreen_canonical |= set_qScreen_canonical;\n\n            resetFrameTimer();\n        }\n    }\n\n    for(auto &l : evt.HideLayer)\n        HideLayer(l, (context != EventContext::Normal) ? (true) : evt.LayerSmoke);\n\n    for(auto &l : evt.ShowLayer)\n        ShowLayer(l, (context != EventContext::Normal) ? (true) : evt.LayerSmoke);\n\n    for(auto &l : evt.ToggleLayer)\n    {\n        if(Layer[l].Hidden)\n            ShowLayer(l, evt.LayerSmoke);\n        else\n            HideLayer(l, evt.LayerSmoke);\n    }\n\n    if(evt.MoveLayer != LAYER_NONE)\n    {\n        B = evt.MoveLayer;\n\n        SetLayerSpeed(B, (num_t)evt.SpeedX, (num_t)evt.SpeedY, true);\n\n        if(Layer[B].SpeedX == 0 && Layer[B].SpeedY == 0)\n        {\n            // join the layer back to the main spatial lookup tables if it doesn't start moving again for 255 frames (this can be tuned, 255 is the maximum possible for this uint8_t variable)\n            Layer[B].join_timer = 255;\n        }\n        else\n        {\n            // these thresholds can be tweaked, but they balance the expense of querying more tables with the expense of updating locations in the main table\n            if(Layer[B].blocks.size() > 2)\n                treeBlockSplitLayer(B);\n\n            if(Layer[B].BGOs.size() > 2)\n                treeBackgroundSplitLayer(B);\n\n            if(Layer[B].waters.size() > 2)\n                treeWaterSplitLayer(B);\n        }\n    }\n\n    if(!AutoUseModern) // Use legacy auto-scrolling when modern autoscrolling was never used here\n    {\n        if(g_config.fix_autoscroll_speed)\n        {\n            if(!autoScrollerChanged)\n            {\n                // Do set the autoscrool when non-zero values only, don't zero by other autoruns\n                if((evt.AutoX != 0 || evt.AutoY != 0) && IF_INRANGE(evt.AutoSection, 0, maxSections))\n                {\n                    AutoX[evt.AutoSection] = evt.AutoX;\n                    AutoY[evt.AutoSection] = evt.AutoY;\n                }\n            }\n        }\n        else if(IF_INRANGE(evt.AutoSection, 0, SDL_min(maxSections, maxEvents)))\n        {\n            // Buggy behavior, see https://github.com/Wohlstand/TheXTech/issues/44\n            AutoX[evt.AutoSection] = Events[evt.AutoSection].AutoX;\n            AutoY[evt.AutoSection] = Events[evt.AutoSection].AutoY;\n        }\n    }\n\n    if(evt.Text != STRINGINDEX_NONE)\n    {\n        MessageText = GetS(evt.Text);\n\n        bool player_valid = whichPlayer >= 1 && whichPlayer <= numPlayers;\n        int base_player = (numPlayers > 1) ? -1 : 1;\n        preProcessMessage(MessageText, player_valid ? whichPlayer : base_player);\n\n        bool use_player_pause = (player_valid && g_config.multiplayer_pause_controls);\n        PauseInit(PauseCode::Message, use_player_pause ? whichPlayer : 0);\n\n        // request resuming at the current index\n        return index;\n    }\n\nevent_resume:\n\n    if(evt.Sound > 0)\n    {\n        if(SoundPause[evt.Sound] > 4)\n            SoundPause[evt.Sound] = 0;\n        PlaySound(evt.Sound);\n    }\n\n    if(evt.EndGame == 1)\n    {\n        for(B = 0; B < numSections; B++)\n            bgMusic[B] = 0;\n        StopMusic();\n        speedRun_bossDeadEvent();\n        LevelMacroCounter = 0;\n        LevelMacro = LEVELMACRO_GAME_COMPLETE_EXIT;\n    }\n\n    ForcedControls = (evt.Controls.AltJump ||\n                      evt.Controls.AltRun ||\n                      evt.Controls.Down ||\n                      evt.Controls.Drop ||\n                      evt.Controls.Jump ||\n                      evt.Controls.Left ||\n                      evt.Controls.Right ||\n                      evt.Controls.Run ||\n                      evt.Controls.Start ||\n                      evt.Controls.Up);\n\n    ForcedControl = evt.Controls;\n\n    // tempBool = false;\n    if(evt.TriggerEvent != EVENT_NONE)\n    {\n        if(evt.TriggerDelay == 0)\n        {\n            // for(B = 0; B <= maxEvents; B++)\n            // {\n            //     if(Events[B].Name == evt.TriggerEvent)\n            //     {\n            //         if(Events[B].TriggerEvent == evt.Name)\n            //             tempBool = true;\n            //         break;\n            //     }\n            // }\n\n            // here tempBool prevented any order-2 circles from occurring\n            // if(!tempBool)\n            if(Events[evt.TriggerEvent].TriggerEvent != index)\n            {\n                // this should receive tail-call optimization\n                // possibly request resuming at the child index (or its child, etc)\n                return ProcEvent_Safe(false, evt.TriggerEvent, whichPlayer, context);\n            }\n        }\n        else if(newEventNum < maxEvents)\n        {\n            newEventNum++;\n            NewEvent[newEventNum] = evt.TriggerEvent;\n            // note: this should be rounded towards even, this is non-trivial to implement as integer logic even though all variables involved are integers\n            // FIXME: simplify this -- add a new vb6round_div2 call\n            newEventDelay[newEventNum] = num_t::vb6round(evt.TriggerDelay * 6.5_n);\n            newEventPlayer[newEventNum] = static_cast<uint8_t>(whichPlayer);\n        }\n        else\n        {\n            // SMBX64 would have crashed here... eventually we should do a message box and crash to menu in vanilla mode.\n            // probably best to make a single function handling this.\n        }\n    }\n\n    return EVENT_NONE;\n}\n\nvoid ProcEvent(eventindex_t index, int whichPlayer, EventContext context)\n{\n    eventindex_t resume_event = ProcEvent_Safe(false, index, whichPlayer, context);\n\n    while(resume_event != EVENT_NONE)\n    {\n        PauseGame(PauseCode::None, 0);\n        resume_event = ProcEvent_Safe(true, resume_event, whichPlayer, context);\n    }\n}\n\nvoid TriggerEvent(eventindex_t index, int whichPlayer)\n{\n    if(newEventNum >= maxEvents)\n        return;\n\n    newEventNum++;\n    NewEvent[newEventNum] = index;\n    newEventDelay[newEventNum] = -1;\n    newEventPlayer[newEventNum] = static_cast<uint8_t>(whichPlayer);\n}\n\nbool UpdateEvents()\n{\n    // this is for events that have a delay to call other events\n    // this sub also updates the screen position for autoscroll levels\n    bool events_active = true;\n\n    switch(g_gameLoopInterrupt.site)\n    {\n    case GameLoopInterrupt::UpdateEvents:\n        goto resume;\n    default:\n        break;\n    }\n\n    if(FreezeNPCs)\n        events_active = false;\n\n    if(!GameMenu)\n    {\n        // possibly undesirable: doesn't advance event timer at all if any players are (for example) in doors or in holding pattern\n        if(!AllPlayersNormal())\n            events_active = false;\n    }\n\n    if(newEventNum > 0)\n    {\n        int newEventNum_old;\n        newEventNum_old = newEventNum;\n\n        int A;\n        for(A = 1; A <= newEventNum_old; A++)\n        {\n            bool event_triggered;\n            event_triggered = newEventDelay[A] < 0;\n\n            // count down the event if the events are active\n            if(newEventDelay[A] > 0 && events_active)\n                newEventDelay[A]--;\n            // trigger event if it's ready, or if the event was directly Triggered\n            else if(events_active || event_triggered)\n            {\n                int prevEventNum;\n                prevEventNum = newEventNum;\n                eventindex_t resume_index;\n                resume_index = ProcEvent_Safe(false, NewEvent[A], newEventPlayer[A]);\n                while(resume_index != EVENT_NONE)\n                {\n                    g_gameLoopInterrupt.A = A;\n                    g_gameLoopInterrupt.B = newEventNum_old;\n                    g_gameLoopInterrupt.C = resume_index;\n                    g_gameLoopInterrupt.D = prevEventNum;\n                    g_gameLoopInterrupt.bool1 = events_active;\n\n                    g_gameLoopInterrupt.site = GameLoopInterrupt::UpdateEvents;\n                    return true;\n\nresume:\n                    A = g_gameLoopInterrupt.A;\n                    newEventNum_old = g_gameLoopInterrupt.B;\n                    resume_index = g_gameLoopInterrupt.C;\n                    prevEventNum = g_gameLoopInterrupt.D;\n                    events_active = g_gameLoopInterrupt.bool1;\n\n                    g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n                    resume_index = ProcEvent_Safe(true, resume_index, newEventPlayer[A]);\n                }\n\n                event_triggered = newEventDelay[A] < 0;\n                newEventDelay[A] = newEventDelay[newEventNum];\n                newEventPlayer[A] = newEventPlayer[newEventNum];\n                NewEvent[A] = NewEvent[newEventNum];\n                newEventNum--;\n\n                // if the event was triggered, countdown its successor event\n                if(event_triggered && newEventNum == prevEventNum)\n                    A--;\n                else if(g_config.fix_event_swap_bug)\n                {\n                    // if A was not replaced by a new event, then we should check the event A was replaced by (instead of duplicating it!)\n                    if(newEventNum < newEventNum_old)\n                    {\n                        if(newEventDelay[A] <= 0)\n                            A--;\n                        newEventNum_old--;\n                    }\n                }\n            }\n        }\n    }\n\n    if(!events_active)\n        return false;\n\n    for(int A = 0; A < numSections; A++)\n    {\n        if(AutoX[A] != 0 || AutoY[A] != 0)\n        {\n            level[A].X += (num_t)AutoX[A];\n            level[A].Width += (num_t)AutoX[A];\n            level[A].Y += (num_t)AutoY[A];\n            level[A].Height += (num_t)AutoY[A];\n\n            int cam_w = num_t::round(level[A].Width - level[A].X);\n            int cam_h = num_t::round(level[A].Height - level[A].Y);\n\n            // SMBX 1.3 forces 800x800 camera at end of autoscroll (vanilla bug)\n            if(!AutoUseModern)\n            {\n                cam_w = 800;\n                cam_h = 800;\n            }\n\n            if(level[A].Width > LevelREAL[A].Width)\n            {\n                level[A].Width = LevelREAL[A].Width;\n                level[A].X = LevelREAL[A].Width - cam_w;\n            }\n\n            if(level[A].X < LevelREAL[A].X)\n            {\n                level[A].Width = LevelREAL[A].X + cam_w;\n                level[A].X = LevelREAL[A].X;\n            }\n\n            if(level[A].Height > LevelREAL[A].Height)\n            {\n                level[A].Height = LevelREAL[A].Height;\n                level[A].Y = LevelREAL[A].Height - cam_h;\n            }\n\n            if(level[A].Y < LevelREAL[A].Y)\n            {\n                level[A].Height = LevelREAL[A].Y + cam_h;\n                level[A].Y = LevelREAL[A].Y;\n            }\n\n            UpdateSectionOverlaps(A);\n        }\n    }\n\n    return false;\n}\n\nvoid CancelNewEvent(eventindex_t index)\n{\n    if(newEventNum <= 0)\n        return; // Nothing to do\n\n    for(int A = 1; A <= newEventNum; ++A)\n    {\n        if(index == NewEvent[A])\n        {\n            newEventDelay[A] = newEventDelay[newEventNum];\n            NewEvent[A] = NewEvent[newEventNum];\n            newEventPlayer[A] = newEventPlayer[newEventNum];\n            newEventNum--;\n            --A;\n        }\n    }\n}\n\nbool EventWasTriggered(eventindex_t index)\n{\n    return !recentlyTriggeredEvents.empty() &&\n            recentlyTriggeredEvents.find(index) != recentlyTriggeredEvents.end();\n}\n\nvoid ClearTriggeredEvents()\n{\n    if(!recentlyTriggeredEvents.empty())\n        recentlyTriggeredEvents.clear();\n}\n\nvoid UpdateLayers()\n{\n    // this is mainly for moving layers\n    int A = 0;\n    // int C = 0;\n\n    bool FreezeLayers = false;\n\n    if(!GameMenu)\n    {\n        // possibly undesirable: doesn't advance layer movement at all if any players are (for example) in doors or in holding pattern\n        if(!AllPlayersNormal())\n        {\n            // moved this code into the loop over layers instead of repeating it per player\n            // it has also been combined with the FreezeNPCs code since they did the same thing\n            // in the original game\n\n            // for(A = 0; A <= maxLayers; A++)\n            // {\n            //     if(Layer[A].Name != \"\" && (Layer[A].SpeedX != 0.f || Layer[A].SpeedY != 0.f) && Layer[A].EffectStop)\n            //     {\n            //         for(C = 1; C <= numBlock; C++)\n            //         {\n            //             if(Block[C].Layer == Layer[A].Name)\n            //             {\n            //                 Block[C].Location.SpeedX = 0;\n            //                 Block[C].Location.SpeedY = 0;\n            //             }\n            //         }\n            //     }\n            // }\n            FreezeLayers = true;\n        }\n    }\n\n    // set invalidate rate\n    g_drawBlocks_invalidate_rate = 0;\n    g_drawBGOs_invalidate_rate = 0;\n\n    for(A = 0; A < numLayers; A++)\n    {\n        Layer[A].ApplySpeedX = 0;\n        Layer[A].ApplySpeedY = 0;\n\n        // only consider non-empty, moving layers\n        if(Layer[A].Name.empty() || (Layer[A].SpeedX == 0 && Layer[A].SpeedY == 0))\n        {\n            // join timer check for layers that were moving until recently\n            if(Layer[A].join_timer && !FreezeNPCs && !FreezeLayers)\n            {\n                Layer[A].join_timer--;\n                if(Layer[A].join_timer == 0)\n                {\n                    treeBlockJoinLayer(A);\n                    treeBackgroundJoinLayer(A);\n                    treeWaterJoinLayer(A);\n                }\n            }\n\n            continue;\n        }\n\n        // the layer does not move\n        if(FreezeNPCs || (FreezeLayers && Layer[A].EffectStop))\n        {\n            {\n                // Block-stopping code from earlier. Now together with the compat BGO layer move code.\n                for(int B : Layer[A].blocks)\n                {\n                    Block[B].Location.SpeedX = 0;\n                    Block[B].Location.SpeedY = 0;\n\n                    if(Block[B].Type >= BLKID_CONVEYOR_L_START && Block[B].Type <= BLKID_CONVEYOR_L_END)\n                        Block[B].Location.SpeedX = -0.8_n;\n                    else if(Block[B].Type >= BLKID_CONVEYOR_R_START && Block[B].Type <= BLKID_CONVEYOR_R_END)\n                        Block[B].Location.SpeedX = 0.8_n;\n                }\n\n                if(g_config.enable_climb_bgo_layer_move)\n                {\n                    for(int B : Layer[A].NPCs)\n                    {\n                        if(NPC[B].Type == NPCID_ITEM_BURIED || NPC[B].Type == NPCID_HOMING_BALL_GEN || NPC[B]->IsAVine)\n                        {\n                            NPC[B].Location.SpeedX = 0;\n                            NPC[B].Location.SpeedY = 0;\n                        }\n                    }\n                }\n            }\n        }\n        // the layer moves!\n        else\n        {\n            // if(!(FreezeLayers && Layer[A].EffectStop))\n            {\n                // widen to full precision\n                num_t SpeedX = (num_t)Layer[A].SpeedX;\n                num_t SpeedY = (num_t)Layer[A].SpeedY;\n\n                Layer[A].OffsetX += SpeedX;\n                Layer[A].OffsetY += SpeedY;\n\n                Layer[A].ApplySpeedX = Layer[A].SpeedX;\n                Layer[A].ApplySpeedY = Layer[A].SpeedY;\n\n                // no longer needed thanks to block quadtree, but used to reproduce some buggy behaviors\n                // move the sort invalidation out of the loop over blocks\n                if(!Layer[A].blocks.empty() && Layer[A].SpeedX != 0 && g_config.emulate_classic_block_order)\n                {\n                    if(BlocksSorted)\n                        BlocksSorted = false;\n                }\n\n                if(!Layer[A].blocks.empty())\n                {\n                    if(num_t::abs(SpeedX) > g_drawBlocks_invalidate_rate)\n                        g_drawBlocks_invalidate_rate = num_t::abs(SpeedX);\n                    if(num_t::abs(SpeedY) > g_drawBlocks_invalidate_rate)\n                        g_drawBlocks_invalidate_rate = num_t::abs(SpeedY);\n                }\n\n                if(!Layer[A].BGOs.empty())\n                {\n                    if(num_t::abs(SpeedX) > g_drawBGOs_invalidate_rate)\n                        g_drawBGOs_invalidate_rate = num_t::abs(SpeedX);\n                    if(num_t::abs(SpeedY) > g_drawBGOs_invalidate_rate)\n                        g_drawBGOs_invalidate_rate = num_t::abs(SpeedY);\n                }\n\n                // is the layer currently included in the main block quadtree?\n                bool inactive = !treeBlockLayerActive(A);\n\n                for(int B : Layer[A].blocks)\n                {\n                    // if(Block[B].Layer == Layer[A].Name)\n                    //{\n                    Block[B].Location.X += SpeedX;\n                    Block[B].Location.Y += SpeedY;\n                    Block[B].Location.SpeedX = SpeedX;\n                    Block[B].Location.SpeedY = SpeedY;\n\n                    if(Block[B].Type >= BLKID_CONVEYOR_L_START && Block[B].Type <= BLKID_CONVEYOR_L_END)\n                        Block[B].Location.SpeedX += -0.8_n;\n                    else if(Block[B].Type >= BLKID_CONVEYOR_R_START && Block[B].Type <= BLKID_CONVEYOR_R_END)\n                        Block[B].Location.SpeedX += 0.8_n;\n\n                    if(inactive)\n                        treeBlockUpdateLayer(A, B);\n                    //}\n                }\n\n                // is the layer currently included in the main BGO quadtree?\n                inactive = !treeBackgroundLayerActive(A);\n\n                // int allBGOs = numBackground + numLocked;\n                for(int B : Layer[A].BGOs)\n                {\n                    // if(Background[B].Layer == Layer[A].Name)\n                    //{\n                    Background[B].Location.X += SpeedX;\n                    Background[B].Location.Y += SpeedY;\n\n                    if(inactive)\n                        treeBackgroundUpdateLayer(A, B);\n                    //}\n                }\n\n                // is the layer currently included in the main water quadtree?\n                inactive = !treeWaterLayerActive(A);\n\n                for(int B : Layer[A].waters)\n                {\n                    // if(Water[B].Layer == Layer[A].Name)\n                    //{\n                    Water[B].Location.X += SpeedX;\n                    Water[B].Location.Y += SpeedY;\n\n                    if(inactive)\n                        treeWaterUpdateLayer(A, B);\n                    //}\n                }\n\n                for(int B : Layer[A].NPCs)\n                {\n                    // if(NPC[B].Layer == Layer[A].Name)\n                    {\n                        NPC[B].DefaultLocationX += SpeedX;\n                        NPC[B].DefaultLocationY += SpeedY;\n\n                        if(!NPC[B].Active || NPC[B].Generator || NPC[B].Effect != NPCEFF_NORMAL ||\n                           NPC[B]->IsACoin || NPC[B].Type == NPCID_PLANT_S3 || NPC[B].Type == NPCID_STONE_S3 ||\n                           NPC[B].Type == NPCID_BOTTOM_PLANT || NPC[B].Type == NPCID_SIDE_PLANT || NPC[B].Type == NPCID_FALL_BLOCK_RED ||\n                           NPC[B].Type == NPCID_PLANT_S1 || NPC[B].Type == NPCID_BIG_PLANT || NPC[B]->IsAVine ||\n                           NPC[B].Type == NPCID_CHECKPOINT || NPC[B].Type == NPCID_GOALTAPE || NPC[B].Type == NPCID_ITEM_BURIED ||\n                           NPC[B].Type == NPCID_HOMING_BALL_GEN || NPC[B].Type == NPCID_LONG_PLANT_UP || NPC[B].Type == NPCID_LONG_PLANT_DOWN ||\n                           NPC[B].Type == NPCID_FIRE_PLANT)\n                        {\n                            if(NPC[B].Type == NPCID_ITEM_BURIED || NPC[B].Type == NPCID_HOMING_BALL_GEN)\n                            {\n                                NPC[B].Location.SpeedX = SpeedX;\n                                NPC[B].Location.SpeedY = SpeedY;\n                            }\n                            else if(NPC[B]->IsAVine)\n                            {\n                                NPC[B].Location.SpeedX = SpeedX;\n                                NPC[B].Location.SpeedY = SpeedY;\n                            }\n\n                            if(!NPC[B].Active)\n                            {\n                                NPC[B].Location.X = NPC[B].DefaultLocationX;\n                                NPC[B].Location.Y = NPC[B].DefaultLocationY;\n\n                                // In SMBX 1.3, these assignments undone during the next call to UpdateNPCs.\n                                // Add to NPCQueues::Unchecked to ensure that will happen still.\n                                if(NPC[B].Type == NPCID_PLANT_S3 || NPC[B].Type == NPCID_BIG_PLANT || NPC[B].Type == NPCID_PLANT_S1 ||\n                                   NPC[B].Type == NPCID_LONG_PLANT_UP || NPC[B].Type == NPCID_FIRE_PLANT)\n                                {\n                                    NPC[B].Location.Y += NPC[B]->THeight;\n                                    NPCQueues::Unchecked.push_back(B);\n                                }\n                                else if(NPC[B].Type == NPCID_SIDE_PLANT && NPC[B].Direction == -1)\n                                {\n                                    NPC[B].Location.X += NPC[B]->TWidth;\n                                    NPCQueues::Unchecked.push_back(B);\n                                }\n                            }\n                            else\n                            {\n                                NPC[B].Location.X += SpeedX;\n                                NPC[B].Location.Y += SpeedY;\n                            }\n\n                            if(NPC[B].Effect == NPCEFF_WARP)\n                            {\n                                // specialY/X store the NPC's destination position\n                                // this previously changed Effect2\n                                if(NPC[B].Effect3 == 1 || NPC[B].Effect3 == 3)\n                                    NPC[B].SpecialY += SpeedY;\n                                else\n                                    NPC[B].SpecialX += SpeedX;\n                            }\n\n                            if(!NPC[B].Active)\n                            {\n                                // note: this is the \"defective\" version of SetLayerSpeed, which (in classic mode) only sets the speed, and does nothing else\n                                if(NPC[B].AttLayer != LAYER_NONE && NPC[B].AttLayer != LAYER_DEFAULT)\n                                    SetLayerSpeed(NPC[B].AttLayer, SpeedX, SpeedY, Layer[A].EffectStop, true);\n\n                                // NOTE: this was the source of at least two bugs: (1) won't get reset later; (2) an undefined value of EffectStop will be used by NPC[B].AttLayer\n                                // (1) should be fixed in SetLayerSpeed by checking for inactive NPCs with AttLayers and resetting their speeds\n                                // (2) is fixed in modern mode, where SetLayerSpeed correctly updates EffectStop to match Layer[A]'s\n                            }\n\n                            treeNPCUpdate(B);\n                        }\n                    }\n                }\n\n                for(int B : Layer[A].warps)\n                {\n                    Warp[B].Entrance.X += SpeedX;\n                    Warp[B].Entrance.Y += SpeedY;\n                    Warp[B].Exit.X += SpeedX;\n                    Warp[B].Exit.Y += SpeedY;\n                }\n            }\n        }\n    }\n}\n\n\n\n// the code for synchronizing the layers of objects\n\nvoid syncLayersTrees_AllBlocks()\n{\n    // would be nice to use a non-deallocating version here\n    treeLevelCleanBlockLayers();\n    invalidateDrawBlocks();\n\n    for(int layer = 0; layer < numLayers; layer++)\n        Layer[layer].blocks.clear();\n\n    for(int block = 1; block <= numBlock; block++)\n    {\n        int layer = Block[block].Layer;\n\n        treeBlockAddLayer(layer, block);\n        if(layer != LAYER_NONE)\n            sorted_insert(Layer[layer].blocks, block);\n    }\n}\n\nvoid syncLayersTrees_Block(int block)\n{\n    invalidateDrawBlocks();\n\n    for(int layer = 0; layer < numLayers; layer++)\n    {\n        if(layer != Block[block].Layer)\n        {\n            sorted_erase(Layer[layer].blocks, block);\n            treeBlockRemoveLayer(layer, block);\n        }\n    }\n\n    int layer = Block[block].Layer;\n    if(block <= numBlock)\n    {\n        treeBlockUpdateLayer(layer, block);\n        if(layer != LAYER_NONE)\n            sorted_insert(Layer[layer].blocks, block);\n    }\n    else\n    {\n        treeBlockRemoveLayer(layer, block);\n        if(layer != LAYER_NONE)\n        {\n            sorted_erase(Layer[layer].blocks, block);\n        }\n    }\n}\n\nvoid syncLayersTrees_Block_SetHidden(int block) // set block hidden based on layer\n{\n    syncLayersTrees_Block(block);\n    if(Block[block].Layer != LAYER_NONE)\n        Block[block].Hidden = Layer[Block[block].Layer].Hidden;\n}\n\nvoid syncLayers_AllNPCs()\n{\n    NPCQueues::clear();\n\n    for(int npc = 1; npc <= numNPCs; npc++)\n        syncLayers_NPC(npc);\n}\n\nvoid syncLayers_NPC(int npc)\n{\n    for(int layer = 0; layer < numLayers; layer++)\n    {\n        if(npc <= numNPCs && NPC[npc].Layer == layer)\n            Layer[layer].NPCs.insert(npc);\n        else\n            Layer[layer].NPCs.erase(npc);\n    }\n\n    NPCQueues::update(npc);\n\n    SDL_assert_release(npc > 0);\n\n    if(npc <= numNPCs)\n        treeNPCUpdate(npc);\n    else\n        treeNPCRemove(npc);\n}\n\nvoid syncLayers_AllBGOs()\n{\n    treeLevelCleanBackgroundLayers();\n    invalidateDrawBGOs();\n\n    for(int layer = 0; layer < numLayers; layer++)\n        Layer[layer].BGOs.clear();\n\n    for(int bgo = 1; bgo <= numBackground + numLocked; bgo++)\n    {\n        int layer = Background[bgo].Layer;\n        treeBackgroundAddLayer(layer, bgo);\n        if(layer != LAYER_NONE)\n            sorted_insert(Layer[layer].BGOs, bgo);\n    }\n}\n\nvoid syncLayers_BGO(int bgo)\n{\n    invalidateDrawBGOs();\n\n    for(int layer = 0; layer < numLayers; layer++)\n    {\n        if(layer != Background[bgo].Layer)\n        {\n            treeBackgroundRemoveLayer(layer, bgo);\n            sorted_erase(Layer[layer].BGOs, bgo);\n        }\n    }\n\n    int layer = Background[bgo].Layer;\n    if(bgo <= numBackground + numLocked)\n    {\n        treeBackgroundUpdateLayer(layer, bgo);\n        if(layer != LAYER_NONE)\n            sorted_insert(Layer[layer].BGOs, bgo);\n    }\n    else\n    {\n        treeBackgroundRemoveLayer(layer, bgo);\n        if(layer != LAYER_NONE)\n            sorted_erase(Layer[layer].BGOs, bgo);\n    }\n}\n\nvoid syncLayers_Warp(int warp)\n{\n    for(int layer = 0; layer < numLayers; layer++)\n    {\n        if(warp <= numWarps && Warp[warp].Layer == layer)\n            sorted_insert(Layer[layer].warps, warp);\n        else\n            sorted_erase(Layer[layer].warps, warp);\n    }\n}\n\nvoid syncLayers_Water(int water)\n{\n    for(int layer = 0; layer < numLayers; layer++)\n    {\n        if(layer != Water[water].Layer)\n        {\n            treeWaterRemoveLayer(layer, water);\n            sorted_erase(Layer[layer].waters, water);\n        }\n    }\n\n    int layer = Water[water].Layer;\n    if(water <= numWater)\n    {\n        treeWaterUpdateLayer(layer, water);\n        if(layer != LAYER_NONE)\n            sorted_insert(Layer[layer].waters, water);\n    }\n    else\n    {\n        treeWaterRemoveLayer(layer, water);\n        if(layer != LAYER_NONE)\n            sorted_erase(Layer[layer].waters, water);\n    }\n}\n"
  },
  {
    "path": "src/layers.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LAYERS_H\n#define LAYERS_H\n\n#include <string>\n#include <vector>\n#include <set>\n#include <cstdint>\n\n#include \"range_arr.hpp\"\n#include \"location.h\"\n#include \"global_constants.h\"\n#include \"control_types.h\"\n\n// also defined in \"globals.h\"\nextern const std::string g_emptyString;\n\nenum class EventContext\n{\n    // proceed as usual\n    Normal = 0,\n    // don't show layer smoke\n    NoEffect,\n    CoinSwitch = NoEffect,\n    // NoEffect, plus don't trigger player warp or screen pan (Modern/Classic mode)\n    InitSetup,\n};\n\n//Public Type Layer\nstruct Layer_t\n{\n//    EffectStop As Boolean\n    bool EffectStop = false;\n//    Hidden As Boolean\n    bool Hidden = false;\n    // Index into SavedLayers array, plus 1. (Normally 0, indicating a non-saved layer.)\n    uint8_t SavedLayer = 0;\n    // NEW: time until layer rejoins the main table\n    uint8_t join_timer = 0;\n//    Name As String\n    std::string Name;\n//    SpeedX As Single\n    numf_t SpeedX = 0;\n//    SpeedY As Single\n    numf_t SpeedY = 0;\n\n// NEW: track the actual applied SpeedX and SpeedY during the last frame. Used for vines.\n    numf_t ApplySpeedX = 0;\n    numf_t ApplySpeedY = 0;\n\n//End Type\n// NEW: track the objects belonging to the layer\n    std::vector<vbint_t> blocks;\n    std::vector<vbint_t> BGOs;\n    std::set<int> NPCs;\n    std::vector<vbint_t> warps;\n    std::vector<vbint_t> waters;\n// NEW: track the layer offset so we don't need to update the block/BGO trees\n    num_t OffsetX = 0;\n    num_t OffsetY = 0;\n};\n\nstruct EventSection_t\n{\n    enum SetActions\n    {\n        LESet_Nothing = -1,\n        LESet_ResetDefault = -2,\n    };\n\n    //! Change section borders if not (-1 - do nothing, -2 set default, any other values - set X position of left section boundary)\n    IntegerLocation_t position;\n\n    //! Set new Background ID in this section (-1 - do nothing, -2 - reset to defaint, >=0 - set background ID)\n    vbint_t background_id = LESet_Nothing;\n\n    //! Set new Music ID in this section (-1 - do nothing, -2 - reset to defaint, >=0 - set music ID)\n    vbint_t music_id = LESet_Nothing;\n    //! Set new Custom Music File path\n    stringindex_t music_file = STRINGINDEX_NONE;\n\n    //! Do override current autoscroll\n    bool  autoscroll = false;\n    //! X speed of autoscrool\n    numf_t autoscroll_x = 0;\n    //! Y speed of autoscrool\n    numf_t autoscroll_y = 0;\n};\n\n\n//Public Type Events\nstruct Events_t\n{\n    // never implemented by Redigit; we can add back in later\n//    addSavedEvent As String\n    // std::string addSavedEvent;\n//    RemoveSavedEvent As String\n    // std::string RemoveSavedEvent;\n//    LayerSmoke As Boolean\n    bool LayerSmoke = false;\n//    Sound As Integer\n    vbint_t Sound = 0;\n//    Name As String\n    std::string Name;\n//    Text As String\n    stringindex_t Text = STRINGINDEX_NONE;\n//    HideLayer(0 To 20) As String\n    std::vector<layerindex_t> HideLayer;\n//    ShowLayer(0 To 20) As String\n    std::vector<layerindex_t> ShowLayer;\n//    ToggleLayer(0 To 20) As String\n    std::vector<layerindex_t> ToggleLayer;\n//    Music(0 To maxSections) As Integer\n//    RangeArrI<int, 0, maxSections, 0> Music;\n//    Background(0 To maxSections) As Integer\n//    RangeArrI<int, 0, maxSections, 0> Background;\n//    level(0 To maxSections) As Location\n//    RangeArr<Location_t, 0, maxSections> level;\n// EXTRA: Override per-section settings\n    RangeArr<EventSection_t, 0, maxSections> section;\n//    EndGame As Integer\n    int EndGame = 0;\n//    TriggerEvent As String\n    eventindex_t TriggerEvent = EVENT_NONE;\n//    TriggerDelay As Double\n    vbint_t TriggerDelay = 0;\n//    Controls As Controls\n    Controls_t Controls;\n//    MoveLayer As String\n    layerindex_t MoveLayer = LAYER_NONE;\n//    SpeedX As Single\n    numf_t SpeedX = 0;\n//    SpeedY As Single\n    numf_t SpeedY = 0;\n//    AutoX As Single\n    numf_t AutoX = 0;\n//    AutoY As Single\n    numf_t AutoY = 0;\n//    AutoSection As Integer\n    int AutoSection = 0;\n//    AutoStart As Boolean\n    bool AutoStart = false;\n//End Type\n\n    // reinitialize in place\n    void reinit()\n    {\n        this->~Events_t();\n        new(this)Events_t();\n    }\n};\n\n//Public Layer(0 To 100) As Layer\n#ifdef LOW_MEM\nconst int maxLayers = 100; // 100\n#else\nconst int maxLayers = 254; // 100\n#endif\n\nextern int numLayers;\nextern RangeArr<Layer_t, 0, maxLayers> Layer;\n\n//Public Events(0 To 100) As Events\n#ifdef LOW_MEM\nconst int maxEvents = 100; // 100\n#else\nconst int maxEvents = 254; // 100\n#endif\n\nextern int numEvents;\nextern RangeArr<Events_t, 0, maxEvents> Events;\n\n//Public NewEvent(1 To 100) As String\nextern RangeArrI<eventindex_t, 1, maxEvents, EVENT_NONE> NewEvent;\n//Public newEventDelay(1 To 100) As Integer\nextern RangeArrI<vbint_t, 1, maxEvents, 0> newEventDelay;\n//! NEW: which player originally triggered each queued event\nextern RangeArrI<uint8_t, 1, maxEvents, 0> newEventPlayer;\n//Public newEventNum As Integer\nextern int newEventNum;\n\n\n// utilities for layerindex_t and eventindex_t\n\n#define LAYER_USED_P_SWITCH_TITLE \"Used P Switch\"\n\nextern layerindex_t LAYER_USED_P_SWITCH;\n\ninline const std::string& GetL(layerindex_t index)\n{\n    if(index == LAYER_NONE)\n    {\n        return g_emptyString;\n    }\n    return Layer[index].Name;\n}\ninline const std::string& GetE(eventindex_t index)\n{\n    if(index == EVENT_NONE)\n    {\n        return g_emptyString;\n    }\n    return Events[index].Name;\n}\n\n// right now these are only used by the editor,\n// so they are done using a linear search.\n// they should be updated to use a hash map if\n// they will be heavily used by autocode\nlayerindex_t FindLayer(const std::string& LayerName);\neventindex_t FindEvent(const std::string& EventName);\n\n\n// Functions for layers\n\n// New functions:\n\n// swaps two layers and updates all references to them\nbool SwapLayers(layerindex_t index_1, layerindex_t index_2);\n// renames a layer\nbool RenameLayer(layerindex_t index, const std::string& NewName);\n// deletes a layer (and, optionally, everything in it)\nbool DeleteLayer(layerindex_t index, bool killall);\n\n// sets the speed of a layer\n// EffectStop controls whether the layer will stop when a player has abnormal status\n// Defective means that SMBX 1.3 was initially missing all code other than setting the speed\nvoid SetLayerSpeed(layerindex_t index, num_t SpeedX, num_t SpeedY, bool EffectStop = true, bool Defective = false);\n\n// Old functions:\n\n// Public Sub ShowLayer(LayerName As String, Optional NoEffect As Boolean = False) 'shows a layer\n// shows a layer\nvoid ShowLayer(layerindex_t index, bool NoEffect = false);\n// Public Sub HideLayer(LayerName As String, Optional NoEffect As Boolean = False) 'hides a layer\n// hides a layer\nvoid HideLayer(layerindex_t index, bool NoEffect = false);\n// Public Sub SetLayer(LayerName As String)\n// (unused)\nvoid SetLayer(layerindex_t index);\n// Public Sub UpdateLayers()\nvoid UpdateLayers();\n\n\n// Functions for events\n\n// New functions:\n\n// initializes an event\nvoid InitializeEvent(Events_t& event);\n// swaps two events and updates all references to them\nbool SwapEvents(eventindex_t index_1, eventindex_t index_2);\n// renames an event\nbool RenameEvent(eventindex_t index, const std::string& NewName);\n// deletes an event\nbool DeleteEvent(eventindex_t index);\n\n// EXTRA: Cancel awaiting event trigger\nvoid CancelNewEvent(eventindex_t index);\n// EXTRA: Check was any event got triggered?\nbool EventWasTriggered(eventindex_t index);\n// EXTRA: Clear up the tracklist\nvoid ClearTriggeredEvents();\n\n// Old functions:\n\n// Public Sub ProcEvent(EventName As String, Optional NoEffect As Boolean = False)\nvoid ProcEvent(eventindex_t, bool) = delete; // old signature\nvoid ProcEvent(eventindex_t, EventContext) = delete; // old signature\n// NEW: added WhichPlayer, 0 by default, to indicate which player triggered the event\nvoid ProcEvent(eventindex_t index, int WhichPlayer, EventContext context = EventContext::Normal);\n\n// If this returns an eventindex other than EVENT_NONE, then a pause has been initiated for a message\n// As soon as the pause ends, the call must be re-made with the same arguments, but with resume set to true and index set to the returned eventindex\n// The caller must also support an interrupt and restore routine.\neventindex_t ProcEvent_Safe(bool resume, eventindex_t index, int WhichPlayer, EventContext context = EventContext::Normal);\n\n// NEW: safe call that adds event to the end of the events queue for the current frame\nvoid TriggerEvent(eventindex_t index, int WhichPlayer);\n\n// Public Sub UpdateEvents()\nbool UpdateEvents();\n\n\n// functions to synchronize the layers of objects\n// call these any time the layer of an object changes\nvoid syncLayersTrees_AllBlocks();\nvoid syncLayersTrees_Block(int block);\nvoid syncLayersTrees_Block_SetHidden(int block); // set block hidden based on layer\n\nvoid syncLayers_AllNPCs();\nvoid syncLayers_NPC(int npc);\n\nvoid syncLayers_AllBGOs();\nvoid syncLayers_BGO(int bgo);\n\nvoid syncLayers_Warp(int warp);\n\nvoid syncLayers_Water(int water);\n\n#endif // LAYERS_H\n"
  },
  {
    "path": "src/load_gfx.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_timer.h\"\n#include \"sdl_proxy/sdl_assert.h\"\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_types.h\"\n\n#ifndef PGE_NO_THREADING\n#include <SDL2/SDL_mutex.h>\n#endif\n\n#include \"globals.h\"\n#include \"global_dirs.h\"\n\n#include \"blk_id.h\"\n#include \"eff_id.h\"\n\n#include \"gfx.h\"\n#include \"load_gfx.h\"\n#include \"graphics.h\" // SuperPrint\n#include \"graphics/gfx_frame.h\" // FrameBorderInfo, loadFrameInfo\n#include \"core/render.h\"\n#include \"core/events.h\"\n#include \"main/asset_pack.h\"\n#include \"main/screen_asset_pack.h\"\n#include \"main/game_strings.h\"\n\n#include <IniProcessor/ini_processing.h>\n#include <Utils/files.h>\n#include <Utils/files_ini.h>\n#include <Utils/dir_list_ci.h>\n#include <Utils/strings.h>\n#include <DirManager/dirman.h>\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n#   include <InterProcess/intproc.h>\n#endif\n#include <fmt_format_ne.h>\n#ifdef __EMSCRIPTEN__\n#include <emscripten.h>\n#endif\n\n#if defined(PGE_MIN_PORT) || defined(THEXTECH_CLI_BUILD)\n#   include <PGE_File_Formats/file_formats.h>\n#endif\n\n#include <set>\n\nbool gfxLoaderTestMode = false;\nbool gfxLoaderThreadingMode = false;\n#ifndef PGE_NO_THREADING\nstatic SDL_mutex *gfxLoaderDebugMutex = nullptr;\n#endif\nstatic std::string gfxLoaderDebugString;\nstatic bool        gfxLoaderDebugStringUpdated = false;\nstatic Sint64 gfxLoaderDebugStart = -1;\nstatic const Sint64 c_gfxLoaderShowInterval = 500;\n\n//// Private Sub cBlockGFX(A As Integer)\n//void cBlockGFX(int A);\n//// Private Sub cNPCGFX(A As Integer)\n//void cNPCGFX(int A);\n//// Private Sub cBackgroundGFX(A As Integer)\n//void cBackgroundGFX(int A);\n//// Private Sub cTileGFX(A As Integer)\n//void cTileGFX(int A);\n//// Private Sub cSceneGFX(A As Integer)\n//void cSceneGFX(int A);\n//// Private Sub cLevelGFX(A As Integer)\n//void cLevelGFX(int A);\n//// Private Sub cPathGFX(A As Integer)\n//void cPathGFX(int A);\n\nstruct GFXBackup_t\n{\n    vbint_t *remote_width = nullptr;\n    vbint_t *remote_height = nullptr;\n    bool *remote_isCustom = nullptr;\n    StdPicture *remote_texture = nullptr;\n    vbint_t width = 0;\n    vbint_t height = 0;\n    bool isCustom = false;\n    StdPicture_Sub texture_backup;\n};\n\nstatic std::vector<GFXBackup_t> g_defaultLevelGfxBackup;\nstatic std::vector<GFXBackup_t> g_defaultWorldGfxBackup;\n\n// track which backups are previews of player graphics\nstatic size_t s_previewPlayersBegin = 0;\nstatic size_t s_previewPlayersEnd = 0;\n\nstruct FrameBorderInfoBackup_t\n{\n    FrameBorderInfo *remote_info = nullptr;\n    FrameBorderInfo info_backup;\n};\n\nstatic std::vector<FrameBorderInfoBackup_t> g_defaultBorderInfoBackup;\n\nstatic DirListCI s_dirFallback;\n\nstatic std::string getGfxDir()\n{\n    return AppPath + \"graphics/\";\n}\n\nstatic bool s_useLangDir = false;\nstatic std::string s_langSubDir;\nstatic bool s_useLangDirEp = false;\nstatic std::string s_langSubDirEp;\n\nstatic void cgfx_initLangDir()\n{\n    std::string langDir;\n    s_useLangDir = false;\n    s_langSubDir.clear();\n    s_useLangDirEp = false;\n    s_langSubDirEp.clear();\n\n    if(!CurrentLanguage.empty())\n    {\n        if(!CurrentLangDialect.empty())\n        {\n            langDir = CurrentLanguage + \"-\" + CurrentLangDialect;\n            // Data directory\n            if(DirMan::exists(g_dirCustom.getCurDir() + \"i18n/\" + langDir))\n                s_langSubDir = \"i18n/\" + langDir + \"/\";\n            else if(DirMan::exists(g_dirCustom.getCurDir() + \"i18n/\" + CurrentLanguage))\n                s_langSubDir = \"i18n/\" + CurrentLanguage + \"/\";\n\n            // Episode directory\n            if(DirMan::exists(g_dirEpisode.getCurDir() + \"i18n/\" + langDir))\n                s_langSubDirEp = \"i18n/\" + langDir + \"/\";\n            else if(DirMan::exists(g_dirEpisode.getCurDir() + \"i18n/\" + CurrentLanguage))\n                s_langSubDirEp = \"i18n/\" + CurrentLanguage + \"/\";\n        }\n        else\n        {\n            // Data directory\n            if(DirMan::exists(g_dirCustom.getCurDir() + \"i18n/\" + CurrentLanguage))\n                s_langSubDir = \"i18n/\" + CurrentLanguage + \"/\";\n\n            // Episode directory\n            if(DirMan::exists(g_dirEpisode.getCurDir() + \"i18n/\" + CurrentLanguage))\n                s_langSubDirEp = \"i18n/\" + CurrentLanguage + \"/\";\n        }\n\n        s_useLangDir = !s_langSubDir.empty();\n        s_useLangDirEp = !s_langSubDirEp.empty();\n    }\n}\n\nSDL_FORCE_INLINE bool s_resolveFile(const char **extList,\n                                    const char *extGif,\n                                    const std::string &fName,\n                                    std::string &imgToUse,\n                                    bool &isGif)\n{\n    const char **ext_p;\n    const char *ext;\n\n    if(s_useLangDir)\n    {\n        ext_p = extList;\n        while((ext = *ext_p++) != nullptr)\n        {\n            imgToUse = g_dirCustom.resolveFileCaseExistsAbs(s_langSubDir + fName + ext);\n            isGif = (ext == extGif);\n            if(!imgToUse.empty())\n                return true; // Found that we looked for\n        }\n    }\n\n    ext_p = extList;\n    while((ext = *ext_p++) != nullptr)\n    {\n        imgToUse = g_dirCustom.resolveFileCaseExistsAbs(fName + ext);\n        isGif = (ext == extGif);\n        if(!imgToUse.empty())\n            return true; // Found that we looked for\n    }\n\n    if(s_useLangDirEp)\n    {\n        ext_p = extList;\n        while((ext = *ext_p++) != nullptr)\n        {\n            imgToUse = g_dirEpisode.resolveFileCaseExistsAbs(s_langSubDirEp + fName + ext);\n            isGif = (ext == extGif);\n            if(!imgToUse.empty())\n                return true; // Found that we looked for\n        }\n    }\n\n    ext_p = extList;\n    while((ext = *ext_p++) != nullptr)\n    {\n        imgToUse = g_dirEpisode.resolveFileCaseExistsAbs(fName + ext);\n        isGif = (ext == extGif);\n        if(!imgToUse.empty())\n            return true; // Found that we looked for\n    }\n\n    return false; // Nothing was found\n}\n\n/*!\n * \\brief Load the custom GFX sprite\n * \\param subfolder Subfolder of graphics folder used\n * \\param stem Filename stem for graphics\n * \\param index Filename index for graphics -- if < 0, then filename won't include index\n * \\param fName file name for custommization target\n * \\param width Reference to width field (optional)\n * \\param height Reference to height field (optional)\n * \\param isCustom Reference to the \"is custom\" boolean\n * \\param texture Target texture to load\n * \\param world Is a world map\n * \\param skipMask Don't even try to load a masked GIF sprite\n */\nstatic void loadCGFX(const char* subfolder,\n                     const char* stem,\n                     int index,\n                     vbint_t *width, vbint_t *height, bool* isCustom, StdPicture &texture,\n                     bool world = false,\n                     bool skipMask = false)\n{\n    std::string fName = (index < 0) ? stem : fmt::sprintf_ne(\"%s%d\", stem, index);\n    std::string origPath = fmt::sprintf_ne(\"%sgraphics/%s/%s.png\", AppPath.c_str(), subfolder, fName.c_str());\n\n    std::string loadedPath;\n    bool success = false;\n    StdPicture_Sub newTexture;\n\n    GFXBackup_t backup;\n    backup.remote_width = width;\n    backup.remote_height = height;\n    backup.remote_isCustom = isCustom;\n    backup.remote_texture = &texture;\n    if(width)\n        backup.width = *width;\n    if(height)\n        backup.height = *height;\n    if(isCustom)\n        backup.isCustom = *isCustom;\n\n    bool isGif = false;\n\n#if defined(X_IMG_EXT) && !defined(X_NO_PNG_GIF)\n    // look for the image file: ext in custom, png in custom, gif in custom, ext in episode, png in episode, gif in episode\n    const char *extsList[] = {X_IMG_EXT, \".png\", \".gif\", nullptr};\n    const char *extGif = extsList[2];\n#elif defined(X_IMG_EXT)\n    // look for the image file: ext in custom, ext in episode\n    const char *extsList[] = {X_IMG_EXT, nullptr};\n    const char *extGif = nullptr;\n#else\n    // look for the image file: png in custom, gif in custom, png in episode, gif in episode\n    const char *extsList[] = {\".png\", \".gif\", nullptr};\n    const char *extGif = extsList[1];\n#endif\n\n    std::string imgToUse;\n\n    if(!s_resolveFile(extsList, extGif, fName, imgToUse, isGif))\n        return; // Nothing to do\n\n    if(isGif && !skipMask)\n    {\n        // look for the mask file: custom, episode, fallback\n        std::string maskToUse;\n\n        if(s_useLangDir)\n            maskToUse = g_dirCustom.resolveFileCaseExistsAbs(s_langSubDir + fName + \"m.gif\");\n\n        if(maskToUse.empty())\n            maskToUse = g_dirCustom.resolveFileCaseExistsAbs(fName + \"m.gif\");\n\n        if(s_useLangDirEp && maskToUse.empty())\n            maskToUse = g_dirEpisode.resolveFileCaseExistsAbs(s_langSubDirEp + fName + \"m.gif\");\n\n        if(maskToUse.empty())\n            maskToUse = g_dirEpisode.resolveFileCaseExistsAbs(fName + \"m.gif\");\n\n        if(maskToUse.empty())\n            maskToUse = s_dirFallback.resolveFileCaseExistsAbs(fName + \"m.gif\");\n\n#ifdef DEBUG_BUILD\n        pLogDebug(\"Trying to load custom GFX: %s with mask %s\", imgToUse.c_str(), maskToUse.c_str());\n#endif\n        XRender::lazyLoadPicture(newTexture, imgToUse, maskToUse, origPath);\n        success = newTexture.inited;\n        loadedPath = imgToUse;\n    }\n    else\n    {\n#ifdef DEBUG_BUILD\n        pLogDebug(\"Trying to load custom GFX: %s\", imgToUse.c_str());\n#endif\n        XRender::lazyLoadPicture(newTexture, imgToUse);\n        success = newTexture.inited;\n        loadedPath = imgToUse;\n    }\n\n    if(success)\n    {\n        // don't keep state from old texture\n        XRender::unloadTexture(texture);\n\n        pLogDebug(\"Loaded custom GFX: %s\", loadedPath.c_str());\n        if(isCustom)\n            *isCustom = true;\n\n        backup.texture_backup = std::move(static_cast<StdPicture_Sub&>(texture));\n        static_cast<StdPicture_Sub&>(texture) = std::move(newTexture);\n\n        if(width)\n            *width = newTexture.w;\n        if(height)\n            *height = newTexture.h;\n\n        if(world)\n            g_defaultWorldGfxBackup.push_back(std::move(backup));\n        else\n            g_defaultLevelGfxBackup.push_back(std::move(backup));\n    }\n}\n\n/* load a single custom border */\nstatic void loadCBorder(const char* stem,\n    bool* isCustom,\n    FrameBorder &border)\n{\n    loadCGFX(\"ui\", stem, -1, nullptr, nullptr, isCustom, border.tex, false, true);\n\n    std::string fName = fmt::sprintf_ne(\"%s.ini\", stem);\n\n    // find the frame border info\n    std::string res;\n\n    res = g_dirCustom.resolveFileCaseExistsAbs(fName);\n\n    if(res.empty())\n        res = g_dirEpisode.resolveFileCaseExistsAbs(fName);\n\n    // attempt to load frame border info\n    if(!res.empty())\n    {\n        // backup the frame border info\n        FrameBorderInfoBackup_t bak;\n        bak.remote_info = &border;\n        bak.info_backup =  border;\n        g_defaultBorderInfoBackup.push_back(bak);\n\n        // load the frame border info\n        IniProcessing ini = Files::load_ini(res);\n        loadFrameInfo(ini, border);\n    }\n\n    // warn if invalid\n    const FrameBorderInfo& i = border;\n    if(i.le + i.li + i.ri + i.re > border.tex.w)\n        pLogWarning(\"Invalid border: total internal/external width is %d but texture [%s.png] is only %dpx wide.\", i.le + i.li + i.ri + i.re, stem, border.tex.w);\n    if(i.te + i.ti + i.bi + i.be > border.tex.h)\n        pLogWarning(\"Invalid border: total internal/external height is %d but texture [%s.png] is only %dpx tall.\", i.te + i.ti + i.bi + i.be, stem, border.tex.h);\n}\n\n#if defined(PGE_MIN_PORT) || defined(THEXTECH_CLI_BUILD)\n/*!\n * \\brief Load the custom GFX from a load list\n * \\param f The load list\n * \\param dir The directory to which paths are relative\n * \\param texture Target texture to modify\n * \\param width Reference to width field (optional)\n * \\param height Reference to height field (optional)\n * \\param is_custom_ref Reference to the \"is custom\" boolean\n * \\param world Is a world map\n * \\param this_is_custom Is custom in the current load context\n */\nstatic void loadImageFromList(PGE_FileFormats_misc::TextInput& t, std::string& line_buf, const std::string& dir,\n                    StdPicture &texture,\n                    vbint_t *width, vbint_t *height, bool *is_custom_loc,\n                    bool world = false, bool this_is_custom = false)\n{\n    StdPicture_Sub newTexture;\n    XRender::lazyLoadPictureFromList(newTexture, t, line_buf, dir);\n\n    if(!newTexture.inited)\n        return;\n\n    if(this_is_custom)\n    {\n        GFXBackup_t backup;\n        backup.remote_width = width;\n        backup.remote_height = height;\n        backup.remote_isCustom = is_custom_loc;\n        backup.remote_texture = &texture;\n        XRender::unloadTexture(texture);\n        if(width)\n            backup.width = *width;\n        if(height)\n            backup.height = *height;\n        if(is_custom_loc)\n            backup.isCustom = *is_custom_loc;\n        backup.texture_backup = static_cast<StdPicture_Sub&>(texture);\n\n        if(world)\n            g_defaultWorldGfxBackup.push_back(backup);\n        else\n            g_defaultLevelGfxBackup.push_back(backup);\n    }\n\n    if(this_is_custom && is_custom_loc)\n    {\n        // pLogDebug(\"Loaded custom GFX: %s\", newTexture.l.path.c_str());\n        *is_custom_loc = true;\n    }\n\n    static_cast<StdPicture_Sub&>(texture) = newTexture;\n    if(width)\n        *width = newTexture.w;\n    if(height)\n        *height = newTexture.h;\n}\n\nbool LoadGFXFromList(std::string source_dir, bool custom, bool skip_world)\n{\n    std::string path = source_dir + \"graphics.list\";\n\n    SDL_RWops* f = Files::open_file(path, \"rb\");\n\n    if(!f)\n        return false;\n\n    PGE_FileFormats_misc::RWopsTextInput in(f, path);\n    std::string line_buf;\n\n    char type_buf[12];\n    int A;\n\n    bool failed = false;\n\n    while(true)\n    {\n        // advance the file to the next entry\n        if(failed)\n        {\n            do\n            {\n                in.readLine(line_buf);\n            } while(!line_buf.empty());\n\n            if(in.eof())\n            {\n                pLogWarning(\"No more entries after failure\");\n                break;\n            }\n        }\n\n        in.readLine(line_buf);\n        if(line_buf.empty() && !in.eof())\n            continue;\n\n        failed = true;\n\n        // read the entry!\n        SDL_memset(type_buf, 0, sizeof(type_buf));\n        int pos;\n        if(sscanf(line_buf.c_str(), \"%11s %d%n\", type_buf, &A, &pos) != 2 || pos != (int)line_buf.size())\n        {\n            if(in.eof())\n                break;\n            else\n            {\n                pLogDebug(\"Failed to read entry header of the load list\");\n                continue;\n            }\n        }\n\n\n        // load the entry\n        if(type_buf[10] == '2')\n        {\n            if(A > numBackground2)\n            {\n                pLogWarning(\"Received load request for invalid bg2 %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXBackground2BMP[A], nullptr, nullptr, nullptr,\n                false, custom);\n        }\n        else if(type_buf[9] == 'd')\n        {\n            if(A > maxBackgroundType)\n            {\n                pLogWarning(\"Received load request for invalid background %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXBackgroundBMP[A], nullptr, nullptr, nullptr,\n                false, custom);\n\n            if(!custom)\n            {\n                BackgroundWidth[A] = GFXBackground[A].w;\n                BackgroundHeight[A] = GFXBackground[A].h;\n            }\n        }\n        else if(type_buf[0] == 'b')\n        {\n            if(A > maxBlockType || A == BLKID_CONVEYOR_L_CONV || A == BLKID_CONVEYOR_R_CONV)\n            {\n                pLogWarning(\"Received load request for invalid block %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXBlockBMP[A], nullptr, nullptr, nullptr,\n                false, custom);\n        }\n        else if(type_buf[0] == 'n')\n        {\n            if(A > maxNPCType)\n            {\n                pLogWarning(\"Received load request for invalid NPC %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXNPCBMP[A], nullptr, nullptr, nullptr,\n                false, custom);\n\n            // load converted conveyer block graphics from conveyer NPC graphics\n            if(A == NPCID_CONVEYOR)\n            {\n                if(custom)\n                {\n                    for(int i = BLKID_CONVEYOR_L_CONV; i <= BLKID_CONVEYOR_R_CONV; i += BLKID_CONVEYOR_R_CONV - BLKID_CONVEYOR_L_CONV)\n                    {\n                        GFXBackup_t backup;\n                        backup.remote_texture = &GFXBlockBMP[i];\n                        XRender::unloadTexture(GFXBlockBMP[i]);\n                        backup.texture_backup = static_cast<StdPicture_Sub&>(GFXBlockBMP[i]);\n                        g_defaultLevelGfxBackup.push_back(backup);\n                    }\n                }\n\n                (StdPicture_Sub&)GFXBlockBMP[BLKID_CONVEYOR_L_CONV] = (const StdPicture_Sub&)GFXNPCBMP[NPCID_CONVEYOR];\n                (StdPicture_Sub&)GFXBlockBMP[BLKID_CONVEYOR_R_CONV] = (const StdPicture_Sub&)GFXNPCBMP[NPCID_CONVEYOR];\n            }\n        }\n        else if(type_buf[0] == 'e')\n        {\n            if(A > maxEffectType)\n            {\n                pLogWarning(\"Received load request for invalid effect %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXEffectBMP[A], &EffectWidth[A], &EffectHeight[A], &GFXEffectCustom[A],\n                false, custom);\n\n            // update calculation (but still rely on backup made above)\n            if(GFXEffectCustom[A])\n                EffectHeight[A] = GFXEffectBMP[A].h / EffectDefaults.EffectFrames[A];\n        }\n        else if(type_buf[0] == 'y' && type_buf[5] == 't')\n        {\n            if(A > maxYoshiGfx)\n            {\n                pLogWarning(\"Received load request for invalid yoshitop %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXYoshiTBMP[A], nullptr, nullptr, nullptr,\n                false, custom);\n        }\n        else if(type_buf[0] == 'y')\n        {\n            if(A > maxYoshiGfx)\n            {\n                pLogWarning(\"Received load request for invalid yoshib %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXYoshiBBMP[A], nullptr, nullptr, nullptr,\n                false, custom);\n        }\n        else if(type_buf[0] == 'l' && type_buf[1] == 'e')\n        {\n            if(skip_world)\n                continue;\n\n            if(A > maxLevelType)\n            {\n                pLogWarning(\"Received load request for invalid level %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXLevelBMP[A], nullptr, nullptr, nullptr,\n                true, custom);\n        }\n        else if(type_buf[0] == 't' && type_buf[1] == 'i')\n        {\n            if(skip_world)\n                continue;\n\n            if(A > maxTileType)\n            {\n                pLogWarning(\"Received load request for invalid tile %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXTileBMP[A], nullptr, nullptr, nullptr,\n                true, custom);\n        }\n        else if(type_buf[0] == 's')\n        {\n            if(skip_world)\n                continue;\n\n            if(A > maxSceneType)\n            {\n                pLogWarning(\"Received load request for invalid scene %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXSceneBMP[A], nullptr, nullptr, nullptr,\n                true, custom);\n        }\n        else if(type_buf[0] == 'p' && type_buf[1] == 'l')\n        {\n            if(skip_world)\n                continue;\n\n            if(A > numCharacters)\n            {\n                pLogWarning(\"Received load request for invalid worldplayer %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXPlayerBMP[A], nullptr, nullptr, nullptr,\n                true, custom);\n        }\n        else if(type_buf[0] == 'p' && type_buf[1] == 'a')\n        {\n            if(skip_world)\n                continue;\n\n            if(A > maxPathType)\n            {\n                pLogWarning(\"Received load request for invalid path %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                GFXPathBMP[A], nullptr, nullptr, nullptr,\n                true, custom);\n        }\n        // character graphics\n        else\n        {\n            int c;\n            if(type_buf[0] == 'l' && type_buf[4] == '\\0')\n                c = 4;\n            else if(type_buf[0] == 'l')\n                c = 1;\n            else if(type_buf[0] == 't')\n                c = 3;\n            else if(type_buf[0] == 'm')\n                c = 0;\n            else if(type_buf[0] == 'p')\n                c = 2;\n            else\n            {\n                pLogWarning(\"Received unrecognized load request category %s\", type_buf);\n                continue;\n            }\n\n            if(A > numStates)\n            {\n                pLogWarning(\"Received load request for invalid player state %s %d\", type_buf, A);\n                continue;\n            }\n\n            loadImageFromList(in, line_buf, source_dir,\n                (*GFXCharacterBMP[c])[A], nullptr, nullptr, nullptr,\n                false, custom);\n        }\n\n        failed = false;\n    }\n\n    return true;\n}\n\n#endif // #if defined(PGE_MIN_PORT) || defined(THEXTECH_CLI_BUILD)\n\nstatic void s_UnloadPreviewPlayers()\n{\n    if(s_previewPlayersEnd == 0 || s_previewPlayersEnd == s_previewPlayersBegin)\n    {\n        s_previewPlayersBegin = 0;\n        s_previewPlayersEnd = 0;\n        return;\n    }\n\n    // check that no textures have been loaded since player preview\n    SDL_assert_release(s_previewPlayersEnd == g_defaultLevelGfxBackup.size());\n\n    for(auto it = g_defaultLevelGfxBackup.begin() + s_previewPlayersEnd;\n        it > g_defaultLevelGfxBackup.begin() + s_previewPlayersBegin;\n        )\n    {\n        --it;\n\n        auto &t = *it;\n\n        if(t.remote_width)\n            *t.remote_width = t.width;\n        if(t.remote_height)\n            *t.remote_height = t.height;\n        if(t.remote_isCustom)\n            *t.remote_isCustom = t.isCustom;\n        SDL_assert_release(t.remote_texture);\n        XRender::unloadTexture(*t.remote_texture);\n        *static_cast<StdPicture_Sub*>(t.remote_texture) = std::move(t.texture_backup);\n    }\n\n    g_defaultLevelGfxBackup.resize(s_previewPlayersBegin);\n\n    s_previewPlayersBegin = 0;\n    s_previewPlayersEnd = 0;\n\n#ifndef LOW_MEM\n    // Restore default sizes of custom player effects (should no longer be required, now that backups are made directly)\n    for(int A = 1; A < maxEffectType; ++A)\n    {\n        if(GFXEffectCustom[A])\n        {\n            SDL_assert_release(EffectWidth[A] == GFXEffectBMP[A].w);\n            SDL_assert_release(EffectHeight[A] == GFXEffectBMP[A].h / EffectDefaults.EffectFrames[A]);\n        }\n        else\n        {\n            SDL_assert_release(EffectWidth[A] == EffectDefaults.EffectWidth[A]);\n            SDL_assert_release(EffectHeight[A] == EffectDefaults.EffectHeight[A]);\n        }\n    }\n#endif // #ifndef LOW_MEM\n}\n\nstatic void restoreLevelBackupTextures()\n{\n    for(auto it = g_defaultLevelGfxBackup.rbegin(); it != g_defaultLevelGfxBackup.rend(); ++it)\n    {\n        auto &t = *it;\n\n        if(t.remote_width)\n            *t.remote_width = t.width;\n        if(t.remote_height)\n            *t.remote_height = t.height;\n        if(t.remote_isCustom)\n            *t.remote_isCustom = t.isCustom;\n        SDL_assert_release(t.remote_texture);\n        XRender::unloadTexture(*t.remote_texture);\n        *static_cast<StdPicture_Sub*>(t.remote_texture) = std::move(t.texture_backup);\n    }\n    g_defaultLevelGfxBackup.clear();\n    s_previewPlayersBegin = 0;\n    s_previewPlayersEnd = 0;\n}\n\nstatic void restoreWorldBackupTextures()\n{\n    for(auto it = g_defaultWorldGfxBackup.rbegin(); it != g_defaultWorldGfxBackup.rend(); ++it)\n    {\n        auto &t = *it;\n\n        if(t.remote_width)\n            *t.remote_width = t.width;\n        if(t.remote_height)\n            *t.remote_height = t.height;\n        if(t.remote_isCustom)\n            *t.remote_isCustom = t.isCustom;\n        SDL_assert_release(t.remote_texture);\n        XRender::unloadTexture(*t.remote_texture);\n        *static_cast<StdPicture_Sub*>(t.remote_texture) = std::move(t.texture_backup);\n    }\n    g_defaultWorldGfxBackup.clear();\n}\n\n\nstatic inline void s_find_image(std::string& dest, DirListCI& CurDir, std::string baseName)\n{\n#if defined(X_IMG_EXT) && !defined(X_NO_PNG_GIF)\n    int s = baseName.size();\n    baseName += X_IMG_EXT;\n    dest = CurDir.resolveFileCaseExistsAbs(baseName);\n    if(dest.empty())\n    {\n        baseName.resize(s);\n        baseName += \".png\";\n        dest = CurDir.resolveFileCaseExistsAbs(baseName);\n    }\n#elif defined(X_IMG_EXT)\n    baseName += X_IMG_EXT;\n    dest = CurDir.resolveFileCaseExistsAbs(baseName);\n#else\n    baseName += \".png\";\n    dest = CurDir.resolveFileCaseExistsAbs(baseName);\n#endif\n}\n\nvoid LoadGFX()\n{\n#if defined(PGE_MIN_PORT) || defined(THEXTECH_CLI_BUILD)\n    if(LoadGFXFromList(getGfxDir(), false, false))\n        return;\n#endif\n\n    std::string p;\n    DirListCI CurDir;\n\n    pLogDebug(\"Loading character textures\");\n    LoaderUpdateDebugString(\"Characters\");\n    for(int c = 0; c < numCharacters; ++c)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Character %d\", c));\n        CurDir.setCurDir(getGfxDir() + GFXPlayerNames[c]);\n        For(A, 1, numStates)\n        {\n            s_find_image(p, CurDir, fmt::sprintf_ne(\"%s-%d\", GFXPlayerNames[c], A));\n            if(!p.empty())\n                XRender::lazyLoadPicture((*GFXCharacterBMP[c])[A], p);\n        }\n        UpdateLoad();\n    }\n\n    pLogDebug(\"Loading block textures\");\n    LoaderUpdateDebugString(\"Blocks\");\n    CurDir.setCurDir(getGfxDir() + \"block/\");\n    for(int A = 1; A <= maxBlockType; ++A)\n    {\n        if(A == BLKID_CONVEYOR_L_CONV || A == BLKID_CONVEYOR_R_CONV)\n            continue;\n\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Block %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"block-%d\", A));\n\n        if(!p.empty())\n            XRender::lazyLoadPicture(GFXBlockBMP[A], p);\n        else\n            break;\n\n        if(A % 20 == 0)\n            UpdateLoad();\n    }\n    UpdateLoad();\n\n    pLogDebug(\"Loading BG2 textures\");\n    LoaderUpdateDebugString(\"Backgrounds\");\n    CurDir.setCurDir(getGfxDir() + \"background2/\");\n    for(int A = 1; A <= numBackground2; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Background %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"background2-%d\", A));\n\n        if(!p.empty())\n            XRender::lazyLoadPicture(GFXBackground2BMP[A], p);\n        else\n            break;\n\n        if(A % 10 == 0)\n            UpdateLoad();\n    }\n    UpdateLoad();\n\n    pLogDebug(\"Loading NPC textures\");\n    LoaderUpdateDebugString(\"NPC\");\n    CurDir.setCurDir(getGfxDir() + \"npc/\");\n    for(int A = 1; A <= maxNPCType; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"NPC %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"npc-%d\", A));\n\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXNPCBMP[A], p);\n\n            // load converted conveyer block graphics from conveyer NPC graphics\n            if(A == NPCID_CONVEYOR)\n            {\n                XRender::lazyLoadPicture(GFXBlockBMP[BLKID_CONVEYOR_L_CONV], p);\n                XRender::lazyLoadPicture(GFXBlockBMP[BLKID_CONVEYOR_R_CONV], p);\n            }\n\n            // GFXNPCWidth(A) = GFXNPCBMP[A].w;\n            // GFXNPCHeight(A) = GFXNPCBMP[A].h;\n            if(A % 20 == 0)\n                UpdateLoad();\n        }\n        else\n        {\n            // GFXNPCWidth(A) = 0;\n            // GFXNPCHeight(A) = 0;\n            break;\n        }\n    }\n    UpdateLoad();\n\n    pLogDebug(\"Loading effect textures\");\n    LoaderUpdateDebugString(\"Effects\");\n    CurDir.setCurDir(getGfxDir() + \"effect/\");\n    for(int A = 1; A <= maxEffectType; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Effect %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"effect-%d\", A));\n\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXEffectBMP[A], p);\n            if(A % 20 == 0)\n                UpdateLoad();\n        }\n        else\n            break;\n    }\n    UpdateLoad();\n\n    pLogDebug(\"Loading mount textures\");\n    LoaderUpdateDebugString(\"Mounts\");\n    CurDir.setCurDir(getGfxDir() + \"yoshi/\");\n    for(int A = 1; A <= maxYoshiGfx; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Mount B %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"yoshib-%d\", A));\n\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXYoshiBBMP[A], p);\n            if(A % 20 == 0)\n                UpdateLoad();\n        }\n        else\n        {\n            break;\n        }\n    }\n    UpdateLoad();\n\n    for(int A = 1; A <= maxYoshiGfx; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Mount T %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"yoshit-%d\", A));\n\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXYoshiTBMP[A], p);\n            if(A % 20 == 0)\n                UpdateLoad();\n        }\n        else\n        {\n            break;\n        }\n    }\n    UpdateLoad();\n\n    pLogDebug(\"Loading background textures\");\n    LoaderUpdateDebugString(\"BGO\");\n    CurDir.setCurDir(getGfxDir() + \"background/\");\n    for(int A = 1; A <= maxBackgroundType; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"BGO %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"background-%d\", A));\n\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXBackgroundBMP[A], p);\n            BackgroundWidth[A] = GFXBackgroundBMP[A].w;\n            BackgroundHeight[A] = GFXBackgroundBMP[A].h;\n        }\n        else\n        {\n            break;\n        }\n\n        if(A % 20 == 0)\n            UpdateLoad();\n    }\n    UpdateLoad();\n\n\n// 'world map\n    pLogDebug(\"Loading tile textures\");\n    LoaderUpdateDebugString(\"Terrain\");\n    CurDir.setCurDir(getGfxDir() + \"tile/\");\n    for(int A = 1; A <= maxTileType; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Terrain %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"tile-%d\", A));\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXTileBMP[A], p);\n            if(A % 20 == 0)\n                UpdateLoad();\n        }\n        else\n        {\n            break;\n        }\n    }\n    UpdateLoad();\n\n    pLogDebug(\"Loading level textures\");\n    LoaderUpdateDebugString(\"Level entries\");\n    CurDir.setCurDir(getGfxDir() + \"level/\");\n    for(int A = 0; A <= maxLevelType; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Level %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"level-%d\", A));\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXLevelBMP[A], p);\n            if(A % 20 == 0)\n                UpdateLoad();\n        }\n        else\n        {\n            break;\n        }\n    }\n    UpdateLoad();\n\n    pLogDebug(\"Loading scene textures\");\n    LoaderUpdateDebugString(\"Scenery\");\n    CurDir.setCurDir(getGfxDir() + \"scene/\");\n    for(int A = 1; A <= maxSceneType; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Scenery %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"scene-%d\", A));\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXSceneBMP[A], p);\n            if(A % 20 == 0)\n                UpdateLoad();\n        }\n        else\n        {\n            break;\n        }\n    }\n    UpdateLoad();\n\n    pLogDebug(\"Loading world player textures\");\n    LoaderUpdateDebugString(\"World characters\");\n    CurDir.setCurDir(getGfxDir() + \"player/\");\n    for(int A = 1; A <= numCharacters; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"World character %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"player-%d\", A));\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXPlayerBMP[A], p);\n            if(A % 20 == 0)\n                UpdateLoad();\n        }\n        else\n        {\n            break;\n        }\n    }\n    UpdateLoad();\n\n    pLogDebug(\"Loading path textures\");\n    LoaderUpdateDebugString(\"Path cells\");\n    CurDir.setCurDir(getGfxDir() + \"path/\");\n    for(int A = 1; A <= maxPathType; ++A)\n    {\n        LoaderUpdateDebugString(fmt::sprintf_ne(\"Path cell %d\", A));\n        s_find_image(p, CurDir, fmt::sprintf_ne(\"path-%d\", A));\n        if(!p.empty())\n        {\n            XRender::lazyLoadPicture(GFXPathBMP[A], p);\n            if(A % 20 == 0)\n                UpdateLoad();\n        }\n        else\n        {\n            break;\n        }\n    }\n    UpdateLoad();\n}\n\nvoid UnloadGFX(bool reload)\n{\n    if(!reload)\n    {\n        // Do nothing at game exit\n        return;\n    }\n\n    UnloadCustomGFX();\n    UnloadWorldCustomGFX();\n\n    for(int c = 0; c < numCharacters; ++c)\n    {\n        For(A, 1, 10)\n        {\n            (*GFXCharacterBMP[c])[A].reset();\n        }\n    }\n\n    for(int A = 1; A <= maxBlockType; ++A)\n        GFXBlockBMP[A].reset();\n\n    for(int A = 1; A <= numBackground2; ++A)\n        GFXBackground2BMP[A].reset();\n\n    for(int A = 1; A <= maxNPCType; ++A)\n        GFXNPCBMP[A].reset();\n\n    for(int A = 1; A <= maxEffectType; ++A)\n        GFXEffectBMP[A].reset();\n\n    for(int A = 1; A <= maxYoshiGfx; ++A)\n    {\n        GFXYoshiBBMP[A].reset();\n        GFXYoshiTBMP[A].reset();\n    }\n\n    for(int A = 1; A <= maxBackgroundType; ++A)\n        GFXBackgroundBMP[A].reset();\n\n// 'world map\n    for(int A = 1; A <= maxTileType; ++A)\n        GFXTileBMP[A].reset();\n\n    for(int A = 1; A <= maxLevelType; ++A)\n        GFXLevelBMP[A].reset();\n\n    for(int A = 1; A <= maxSceneType; ++A)\n        GFXSceneBMP[A].reset();\n\n    for(int A = 1; A <= numCharacters; ++A)\n        GFXPlayerBMP[A].reset();\n\n    for(int A = 1; A <= maxPathType; ++A)\n        GFXPathBMP[A].reset();\n}\n\nstatic void loadCustomUIAssets()\n{\n    size_t ci = 0;\n\n     // these should all have been set previously, but will do no harm\n    g_dirEpisode.setCurDir(FileNamePath);\n    g_dirCustom.setCurDir(FileNamePath + FileName);\n    s_dirFallback.setCurDir(getGfxDir() + \"fallback\");\n\n    loadCGFX(\"ui\", \"BMVs\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.BMVs, false, true);\n\n    loadCGFX(\"ui\", \"BMWin\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.BMWin, false, true);\n\n    For(i, 1, 3)\n        loadCGFX(\"ui\", \"Boot\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.Boot[i], false, true);\n\n    For(i, 1, 5)\n        loadCGFX(\"ui\", \"CharacterName\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.CharacterName[i], false, true);\n\n    loadCGFX(\"ui\", \"Chat\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.Chat, false, true);\n\n    For(i, 0, 2)\n        loadCGFX(\"ui\", \"Container\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.Container[i], false, true);\n\n    For(i, 1, 3)\n        loadCGFX(\"ui\", \"ECursor\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.ECursor[i], false, true);\n\n    For(i, 0, 9)\n        loadCGFX(\"ui\", \"Font1_\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.Font1[i], false, true);\n\n    For(i, 1, 3)\n        loadCGFX(\"ui\", \"Font2_\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.Font2[i], false, true);\n\n    loadCGFX(\"ui\", \"Font2S\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.Font2S, false, true);\n\n    For(i, 1, 2)\n        loadCGFX(\"ui\", \"Heart\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.Heart[i], false, true);\n\n    For(i, 0, 8)\n        loadCGFX(\"ui\", \"Interface\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.Interface[i], false, true);\n\n    loadCGFX(\"ui\", \"LoadCoin\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.LoadCoin, false, true);\n\n    loadCGFX(\"ui\", \"Loader\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.Loader, false, true);\n\n    For(i, 0, 3)\n        loadCGFX(\"ui\", \"MCursor\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.MCursor[i], false, true);\n\n    For(i, 1, 4)\n        loadCGFX(\"ui\", \"MenuGFX\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.MenuGFX[i], false, true);\n\n    loadCGFX(\"ui\", \"Mount\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.Mount[2], false, true);\n\n    For(i, 0, 7)\n        loadCGFX(\"ui\", \"nCursor\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.nCursor[i], false, true);\n\n    loadCGFX(\"ui\", \"TextBox\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.TextBox, false, true);\n\n    For(i, 1, 2)\n        loadCGFX(\"ui\", \"Tongue\", i,\n                 nullptr, nullptr, &GFX.isCustom(ci++), GFX.Tongue[i], false, true);\n\n    // loadCGFX(\"ui\", \"Warp\", i,\n    //          nullptr, nullptr, &GFX.isCustom(ci++), GFX.Warp, false, true);\n\n    loadCGFX(\"ui\", \"YoshiWings\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.YoshiWings, false, true);\n\n    loadCGFX(\"ui\", \"CycloneAcc\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.CycloneAcc, false, true);\n\n    loadCGFX(\"ui\", \"EditorIcons\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.EIcons, false, true);\n\n    loadCGFX(\"ui\", \"PCursor\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.PCursor, false, true);\n\n    loadCGFX(\"ui\", \"Medals\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.Medals, false, true);\n\n    loadCGFX(\"ui\", \"CharSelIcons\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.CharSelIcons, false, true);\n\n    loadCBorder(\"CharSelFrame\",\n             &GFX.isCustom(ci++), GFX.CharSelFrame);\n\n    loadCGFX(\"ui\", \"Backdrop\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.Backdrop, false, true);\n\n    loadCBorder(\"Backdrop_Border\",\n             &GFX.isCustom(ci++), GFX.Backdrop_Border);\n\n    loadCGFX(\"ui\", \"WorldMapFrame_Tile\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.WorldMapFrame_Tile, false, true);\n\n    loadCBorder(\"WorldMapFrame_Border\",\n             &GFX.isCustom(ci++), GFX.WorldMapFrame_Border);\n\n    loadCGFX(\"ui\", \"Camera\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.Camera, false, true);\n\n    loadCGFX(\"ui\", \"Balance\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.Balance, false, true);\n\n    loadCGFX(\"ui\", \"SaveIcons\", -1,\n             nullptr, nullptr, &GFX.isCustom(ci++), GFX.SaveIcons, false, true);\n\n    // Add new optional assets above this line. Also update gfx.cpp: GFX_t::load(), and gfx.h: GFX_t::m_isCustomVolume.\n}\n\nvoid LoadCustomGFX(bool include_world, const char* preview_players_from)\n{\n    // this should have been set previously, but will do no harm\n    s_dirFallback.setCurDir(getGfxDir() + \"fallback\");\n\n    // unload any previously loaded player previews\n    s_UnloadPreviewPlayers();\n\n    if(!preview_players_from)\n    {\n        // these should all have been set previously, but will do no harm\n        g_dirEpisode.setCurDir(FileNamePath);\n        g_dirCustom.setCurDir(FileNamePath + FileName);\n\n        cgfx_initLangDir();\n\n        loadCustomUIAssets();\n\n#if defined(PGE_MIN_PORT) || defined(THEXTECH_CLI_BUILD)\n        bool success = LoadGFXFromList(g_dirEpisode.getCurDir(), true, !include_world);\n\n        if(s_useLangDirEp)\n            success |= LoadGFXFromList(g_dirEpisode.getCurDir() + s_langSubDirEp, true, !include_world);\n\n        success |= LoadGFXFromList(g_dirCustom.getCurDir(), true, !include_world);\n\n        if(s_useLangDir)\n            success |= LoadGFXFromList(g_dirCustom.getCurDir() + s_langSubDir, true, !include_world);\n\n        if(success)\n            return;\n#endif\n    }\n    else\n    {\n        s_previewPlayersBegin = g_defaultLevelGfxBackup.size();\n\n        g_dirEpisode.setCurDir(preview_players_from);\n        g_dirCustom.setCurDir(\"\");\n\n        loadCGFX(\"ui\", \"Interface5\", -1,\n                 nullptr, nullptr, &GFX.isCustom(38), GFX.Interface[5], false, true);\n    }\n\n\n    for(int A = 1; A <= maxYoshiGfx; ++A)\n    {\n        loadCGFX(\"yoshi\", \"yoshib-\", A,\n                 nullptr, nullptr, nullptr, GFXYoshiBBMP[A]);\n    }\n\n    for(int A = 1; A <= maxYoshiGfx; ++A)\n    {\n        loadCGFX(\"yoshi\", \"yoshit-\", A,\n                 nullptr, nullptr, nullptr, GFXYoshiTBMP[A]);\n    }\n\n    for(int c = 0; c < numCharacters; ++c)\n    {\n        std::string pstem = fmt::sprintf_ne(\"%s-\", GFXPlayerNames[c]);\n\n        for(int A = 1; A <= numStates; ++A)\n        {\n            loadCGFX(GFXPlayerNames[c], pstem.c_str(), A,\n                     nullptr, nullptr,\n                     nullptr, (*GFXCharacterBMP[c])[A]);\n        }\n    }\n\n    for(int A = 1; A <= maxEffectType; ++A)\n    {\n        if(preview_players_from && A != EFFID_CHAR1_DIE && A != EFFID_CHAR2_DIE && A != EFFID_CHAR3_DIE && A != EFFID_CHAR4_DIE && A != EFFID_CHAR5_DIE)\n            continue;\n\n        loadCGFX(\"effect\", \"effect-\", A,\n                 &EffectWidth[A], &EffectHeight[A], &GFXEffectCustom[A], GFXEffectBMP[A]);\n\n        // update calculation (but still rely on backup made above)\n        if(GFXEffectCustom[A])\n            EffectHeight[A] = GFXEffectBMP[A].h / EffectDefaults.EffectFrames[A];\n    }\n\n\n    if(preview_players_from)\n    {\n        s_previewPlayersEnd = g_defaultLevelGfxBackup.size();\n        return;\n    }\n\n\n    for(int A = 1; A <= maxBlockType; ++A)\n    {\n        if(A == BLKID_CONVEYOR_L_CONV || A == BLKID_CONVEYOR_R_CONV)\n            continue;\n\n        loadCGFX(\"block\", \"block-\", A,\n                 nullptr, nullptr, nullptr, GFXBlockBMP[A],\n                 false, BlockHasNoMask[A]);\n    }\n\n    for(int A = 1; A <= numBackground2; ++A)\n    {\n        loadCGFX(\"background2\", \"background2-\", A,\n                 nullptr, nullptr, nullptr, GFXBackground2BMP[A],\n                 false, true);\n    }\n\n    for(int A = 1; A <= maxNPCType; ++A)\n    {\n        loadCGFX(\"npc\", \"npc-\", A,\n                 nullptr, nullptr, nullptr, GFXNPCBMP[A]);\n\n        // load converted conveyer block graphics from conveyer NPC graphics\n        if(A == NPCID_CONVEYOR)\n        {\n            loadCGFX(\"npc\", \"npc-\", A,\n                     nullptr, nullptr, nullptr, GFXBlockBMP[BLKID_CONVEYOR_L_CONV]);\n\n            loadCGFX(\"npc\", \"npc-\", A,\n                     nullptr, nullptr, nullptr, GFXBlockBMP[BLKID_CONVEYOR_R_CONV]);\n        }\n    }\n\n    for(int A = 1; A <= maxBackgroundType; ++A)\n    {\n        loadCGFX(\"background\", \"background-\", A,\n                 nullptr, nullptr, nullptr, GFXBackgroundBMP[A],\n                 false, BackgroundHasNoMask[A]);\n    }\n\n\n    if(!include_world)\n        return;\n\n\n    for(int A = 1; A <= maxTileType; ++A)\n    {\n        loadCGFX(\"tile\", \"tile-\", A,\n                 nullptr, nullptr, nullptr, GFXTileBMP[A], true);\n    }\n\n    for(int A = 0; A <= maxLevelType; ++A)\n    {\n        loadCGFX(\"level\", \"level-\", A,\n                 nullptr, nullptr, nullptr, GFXLevelBMP[A], true);\n    }\n\n    for(int A = 1; A <= maxSceneType; ++A)\n    {\n        loadCGFX(\"scene\", \"scene-\", A,\n                 nullptr, nullptr, nullptr, GFXSceneBMP[A], true);\n    }\n\n    for(int A = 1; A <= numCharacters; ++A)\n    {\n        loadCGFX(\"player\", \"player-\", A,\n                 nullptr, nullptr, nullptr, GFXPlayerBMP[A], true);\n    }\n\n    for(int A = 1; A <= maxPathType; ++A)\n    {\n        loadCGFX(\"path\", \"path-\", A,\n                 nullptr, nullptr, nullptr, GFXPathBMP[A], true);\n    }\n}\n\n\nvoid UnloadCustomGFX()\n{\n#ifndef RENDER_CUSTOM\n    // reset bitmask warning flag on SDL platforms\n    XRender::g_BitmaskTexturePresent = false;\n#endif\n\n    // unload custom frame border info\n    for(auto it = g_defaultBorderInfoBackup.rbegin(); it != g_defaultBorderInfoBackup.rend(); ++it)\n    {\n        auto &t = *it;\n\n        if(t.remote_info)\n            *t.remote_info = t.info_backup;\n    }\n    g_defaultBorderInfoBackup.clear();\n\n    restoreLevelBackupTextures();\n\n#ifndef LOW_MEM\n    // Restore default sizes of custom effects (should no longer be required, now that backups are made directly)\n    for(int A = 1; A < maxEffectType; ++A)\n    {\n        SDL_assert_release(EffectWidth[A] == EffectDefaults.EffectWidth[A]);\n        SDL_assert_release(EffectHeight[A] == EffectDefaults.EffectHeight[A]);\n    }\n#endif // #ifndef LOW_MEM\n}\n\nvoid UnloadPlayerPreviewGFX()\n{\n    s_UnloadPreviewPlayers();\n}\n\n\nvoid UnloadWorldCustomGFX()\n{\n    restoreWorldBackupTextures();\n}\n\nvoid LoaderInit()\n{\n    gfxLoaderDebugStart = SDL_GetTicks();\n    Strings::dealloc(gfxLoaderDebugString);\n    gfxLoaderDebugStringUpdated = true;\n    gfxLoaderDebugString.reserve(1024);\n\n#ifndef PGE_NO_THREADING\n    if(!gfxLoaderDebugMutex)\n        gfxLoaderDebugMutex = SDL_CreateMutex();\n#endif\n}\n\nvoid LoaderFinish()\n{\n    gfxLoaderDebugStart = -1;\n    Strings::dealloc(gfxLoaderDebugString);\n    gfxLoaderDebugStringUpdated = true;\n\n#ifndef PGE_NO_THREADING\n    if(gfxLoaderDebugMutex)\n        SDL_DestroyMutex(gfxLoaderDebugMutex);\n    gfxLoaderDebugMutex = nullptr;\n#endif\n}\n\nvoid LoaderUpdateDebugString(const std::string &strig, bool forceUpdate)\n{\n    if(gfxLoaderDebugStart == -1)\n        return;\n\n#ifndef PGE_NO_THREADING\n    SDL_LockMutex(gfxLoaderDebugMutex);\n#endif\n    gfxLoaderDebugString = fmt::format_ne(g_gameStrings.loaderStatusLoadFile, strig);\n    if(forceUpdate)\n        gfxLoaderDebugStringUpdated = true;\n#ifndef PGE_NO_THREADING\n    SDL_UnlockMutex(gfxLoaderDebugMutex);\n#endif\n\n    if(!gfxLoaderThreadingMode)\n        UpdateLoadREAL();\n}\n\n\nvoid UpdateLoadREAL()\n{\n    std::string state;\n    bool draw = false;\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n    if(IntProc::isEnabled())\n    {\n        state = IntProc::getState();\n        draw = true;\n    }\n#endif // THEXTECH_INTERPROC_SUPPORTED\n\n    bool assets_reload = (ScreenAssetPack::g_LoopActive && g_AssetsLoaded);\n\n    static uint8_t alphaFader = 255;\n\n    if(assets_reload)\n        alphaFader = 0;\n\n    if(gfxLoaderDebugStringUpdated)\n    {\n        gfxLoaderDebugStringUpdated = false;\n        draw = true;\n    }\n\n    if(LoadCoinsT <= SDL_GetTicks())\n    {\n        LoadCoinsT = SDL_GetTicks() + 100;\n        LoadCoins += 1;\n        if(LoadCoins > 3)\n            LoadCoins = 0;\n        draw = true;\n    }\n\n    if(gfxLoaderThreadingMode && alphaFader >= 10)\n    {\n        alphaFader -= 10;\n        draw = true;\n    }\n    else if(gfxLoaderThreadingMode && alphaFader != 0)\n    {\n        alphaFader = 0;\n        draw = true;\n    }\n\n#ifdef USE_RENDER_BLOCKING\n    if(XRender::renderBlocked())\n        return;\n#endif\n\n    if(draw)\n    {\n        XRender::setTargetTexture();\n        XRender::clearBuffer();\n\n        int sh_w = XRender::TargetW / 2;\n        int gh_w = GFX.MenuGFX[4].w / 2;\n        int sh_h = XRender::TargetH / 2;\n        int gh_h = GFX.MenuGFX[4].h / 2;\n\n        // use default coordinates during reload\n        if(assets_reload)\n        {\n            gh_w = 400;\n            gh_h = 300;\n        }\n\n        int Left    = sh_w - gh_w;\n        int Top     = sh_h - gh_h;\n        int Right   = sh_w + gh_w;\n        int Bottom  = sh_h + gh_h;\n\n        if(Left < 0)\n            Left = 0;\n\n        if(Top < 0)\n            Top = 0;\n\n        if(Right > XRender::TargetW)\n            Right = XRender::TargetW;\n\n        if(Bottom > XRender::TargetH)\n            Bottom = XRender::TargetH;\n\n        Left += XRender::TargetOverscanX;\n        Right -= XRender::TargetOverscanX;\n\n        if(assets_reload)\n        {\n            ScreenAssetPack::DrawBackground(64);\n            DrawDeviceBattery();\n        }\n        else if(!gfxLoaderTestMode)\n        {\n            XRender::renderTextureBasic(sh_w - gh_w, sh_h - gh_h, GFX.MenuGFX[4]);\n        }\n        else\n        {\n            // Display the IPC status\n            if(!state.empty())\n                SuperPrint(state, 3, 10, 10);\n            else\n                SuperPrint(g_gameStrings.loaderStatusLoadData, 3, 10, 10);\n        }\n\n        if(gh_w < 400)\n        {\n            Left = 0;\n            Right = XRender::TargetW;\n        }\n\n        if(gh_h < 240)\n        {\n            // Top = 0;\n            Bottom = XRender::TargetH;\n        }\n\n        XRender::renderTextureBasic(Right - (GFX.Loader.w + 50), Bottom - (GFX.Loader.h + 8), GFX.Loader);\n        XRender::renderTextureBasic(Right - 40, Bottom - 40, GFX.LoadCoin.w, GFX.LoadCoin.h / 4, GFX.LoadCoin, 0, 32 * LoadCoins);\n\n        if(gfxLoaderThreadingMode && alphaFader > 0)\n            XRender::renderRect(0, 0, XRender::TargetW, XRender::TargetH, {0, 0, 0, alphaFader});\n\n#ifndef PGE_NO_THREADING\n        SDL_LockMutex(gfxLoaderDebugMutex);\n#endif\n\n        if(!gfxLoaderDebugString.empty() && gfxLoaderDebugStart + c_gfxLoaderShowInterval < SDL_GetTicks())\n        {\n            SuperPrint(gfxLoaderDebugString.c_str(), 3,\n                       Left + 10, Bottom - 24,\n                       {255, 255, 0, 127});\n        }\n\n#ifndef PGE_NO_THREADING\n        SDL_UnlockMutex(gfxLoaderDebugMutex);\n#endif\n\n        XRender::repaint();\n        XRender::setTargetScreen();\n\n        XEvents::doEvents();\n#ifdef __EMSCRIPTEN__\n        emscripten_sleep(1); // To repaint screenn, it's required to send a sleep signal\n#endif\n    }\n}\n\nvoid UpdateLoad()\n{\n    if(LoadingInProcess && !gfxLoaderThreadingMode)\n        UpdateLoadREAL();\n}\n"
  },
  {
    "path": "src/load_gfx.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LOAD_GFX_H\n#define LOAD_GFX_H\n\n#include <string>\n\nextern bool gfxLoaderTestMode;\nextern bool gfxLoaderThreadingMode;\n\n// Public Sub LoadGFX()\nvoid LoadGFX();\n// Public Sub UnloadGFX()\nvoid UnloadGFX(bool reload = false);\n// Public Sub LoadCustomGFX()\nvoid LoadCustomGFX(bool include_world = false, const char* preview_players_from = nullptr);\n// Public Sub UnloadCustomGFX()\nvoid UnloadCustomGFX();\n// Public Sub LoadCustomGFX2(GFXFilePath As String)\n//void LoadCustomGFX2(std::string GFXFilePath);\n\nvoid UnloadPlayerPreviewGFX();\n\n// now a subfunction of LoadCustomGFX\n// Public Sub LoadWorldCustomGFX()\n// void LoadWorldCustomGFX();\n// Public Sub UnloadWorldCustomGFX()\nvoid UnloadWorldCustomGFX();\n\n// Private Sub cBlockGFX(A As Integer)\n// Private Sub cNPCGFX(A As Integer)\n// Private Sub cBackgroundGFX(A As Integer)\n// Private Sub cTileGFX(A As Integer)\n// Private Sub cSceneGFX(A As Integer)\n// Private Sub cLevelGFX(A As Integer)\n// Private Sub cPathGFX(A As Integer)\n\nvoid LoaderInit();\nvoid LoaderFinish();\nvoid LoaderUpdateDebugString(const std::string &strig, bool forceUpdate = false);\n\n// Public Sub UpdateLoad()\nvoid UpdateLoadREAL();\nvoid UpdateLoad();\n\n\n#endif // LOAD_GFX_H\n"
  },
  {
    "path": "src/location.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LOCATION_H\n#define LOCATION_H\n\n#include <cstdint>\n\n#include \"numeric_types.h\"\n\n//Public Type Location    'Holds location information for objects\nstruct Location_t\n{\n//    X As Double\n    num_t X = 0.0_n;\n//    Y As Double\n    num_t Y = 0.0_n;\n//    Height As Double\n    num_t Height = 0.0_n;\n//    Width As Double\n    num_t Width = 0.0_n;\n//    SpeedX As Double\n    num_t SpeedX = 0.0_n;\n//    SpeedY As Double\n    num_t SpeedY = 0.0_n;\n//End Type\n\n    // checks if the center is strictly to the right of the other location's center\n    inline bool to_right_of(const Location_t& o) const\n    {\n        return X - o.X + (Width - o.Width) / 2 > 0;\n    }\n\n    // sets the width, maintaining location center\n    inline void set_width_center(num_t new_width)\n    {\n        X += (Width - new_width) / 2;\n        Width = new_width;\n    }\n\n    // sets the height, maintaining location center\n    inline void set_height_center(num_t new_height)\n    {\n        Y += (Height - new_height) / 2;\n        Height = new_height;\n    }\n\n    // sets the height, maintaining location floor\n    inline void set_height_floor(num_t new_height)\n    {\n        Y += Height - new_height;\n        Height = new_height;\n    }\n\n    // sees how far this location's center is to the right of the other location\n    inline num_t minus_center_x(const Location_t& o) const\n    {\n        return X - o.X + (Width - o.Width) / 2;\n    }\n\n    // sees how far this location's center is below the other location\n    inline num_t minus_center_y(const Location_t& o) const\n    {\n        return Y - o.Y + (Height - o.Height) / 2;\n    }\n};\n\n//NEW: 'Holds location information for an object without speed at integer coordinates, with width / height values below 32767\nstruct TinyLocation_t\n{\n    int32_t X = 0;\n    int32_t Y = 0;\n    int16_t Height = 0;\n    int16_t Width = 0;\n\n    inline TinyLocation_t() = default;\n    inline TinyLocation_t(int32_t X, int32_t Y, int16_t Width, int16_t Height) : X(X), Y(Y), Height(Height), Width(Width) {}\n    inline explicit TinyLocation_t(const Location_t& loc) : X(loc.X), Y(loc.Y), Height(loc.Height), Width(loc.Width) {}\n\n    inline explicit operator Location_t() const\n    {\n        Location_t ret;\n        ret.X = X;\n        ret.Y = Y;\n        ret.Height = Height;\n        ret.Width = Width;\n\n        return ret;\n    }\n};\n\n//NEW: 'Holds location information for an object without speed at integer coordinates (like most objects that have not moved since saving)\nstruct IntegerLocation_t\n{\n    int32_t X = 0;\n    int32_t Y = 0;\n    int32_t Height = 0;\n    int32_t Width = 0;\n\n    inline IntegerLocation_t() = default;\n    inline IntegerLocation_t(int32_t X, int32_t Y, int32_t Width, int32_t Height) : X(X), Y(Y), Height(Height), Width(Width) {}\n\n    inline explicit operator Location_t() const\n    {\n        Location_t ret;\n        ret.X = X;\n        ret.Y = Y;\n        ret.Height = Height;\n        ret.Width = Width;\n\n        return ret;\n    }\n};\n\n//NEW: 'Holds location information for an object without speed\nstruct SpeedlessLocation_t\n{\n//    X As Double\n    num_t X = 0_n;\n//    Y As Double\n    num_t Y = 0_n;\n//    Height As Double\n    num_t Height = 0_n;\n//    Width As Double\n    num_t Width = 0_n;\n\n    inline SpeedlessLocation_t() = default;\n    inline explicit SpeedlessLocation_t(const Location_t& loc) : X(loc.X), Y(loc.Y), Height(loc.Height), Width(loc.Width) {}\n    inline explicit SpeedlessLocation_t(const IntegerLocation_t& loc) : X(loc.X), Y(loc.Y), Height(loc.Height), Width(loc.Width) {}\n\n    inline explicit operator Location_t() const\n    {\n        Location_t ret;\n        ret.X = X;\n        ret.Y = Y;\n        ret.Height = Height;\n        ret.Width = Width;\n\n        return ret;\n    }\n};\n\n//NEW: 'Holds location information for player start location, including Direction\nstruct PlayerStart_t\n{\n//    X As Double\n    int32_t X = 0;\n//    Y As Double\n    int32_t Y = 0;\n//    Height As Double\n    int32_t Height = 0;\n//    Width As Double\n    int32_t Width = 0;\n//! Initial direction [New-added]\n    int Direction = 1;\n//End Type\n\n    inline PlayerStart_t() = default;\n    inline PlayerStart_t(const Location_t& loc) : X((int32_t)loc.X), Y((int32_t)loc.Y), Height((int32_t)loc.Height), Width((int32_t)loc.Width) {}\n\n    inline bool isNull() const\n    {\n        return X == 0 &&\n               Y == 0 &&\n               Width == 0 &&\n               Height == 0;\n    }\n\n    inline operator Location_t() const\n    {\n        Location_t ret;\n        ret.X = X;\n        ret.Y = Y;\n        ret.Height = Height;\n        ret.Width = Width;\n\n        return ret;\n    }\n};\n\n// creates a temp Location\nLocation_t newLoc(num_t X, num_t Y, num_t Width = 0, num_t Height = 0);\n// creates a copy of location with a grid rounding\n// Location_t roundLoc(const Location_t &inLoc, num_t grid);\n\n#endif // LOCATION_H\n"
  },
  {
    "path": "src/logic/object_graph.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include <Logger/logger.h>\n\n#include \"logic/object_graph.h\"\n\n#include \"npc_id.h\"\n#include \"npc.h\"\n#include \"globals.h\"\n\nnamespace ObjectGraph\n{\n\ntemplate<class LocType>\nLoc get_center(const LocType& loc)\n{\n    return {(int)(loc.X + loc.Width / 2), (int)(loc.Y + loc.Height / 2)};\n}\n\nstatic unsigned int s_get_distance(const Loc& loc_1, const Loc& loc_2)\n{\n    int dx = loc_1.x - loc_2.x;\n    int dy = loc_1.y - loc_2.y;\n    return SDL_abs(dx) + SDL_abs(dy);\n}\n\nvoid Graph::expand(SearchPriorityQueue& queue, DistMap& dist_map, const Object* node, unsigned int distance, bool warps_reverse) const\n{\n    if(dist_map.find(node) != dist_map.end())\n        return;\n\n    dist_map.emplace(node, distance);\n\n    // expand warps (only)\n    if(node->type == Object::Warp)\n    {\n        // add paths from warp to all nodes to queue\n        Loc source_loc = warps_reverse ? node->loc : node->dest;\n\n        for(const Object* next : this->all_nodes)\n        {\n            Loc dest_loc = (warps_reverse && next->type == Object::Warp) ? next->dest : next->loc;\n            queue.emplace(distance + s_get_distance(source_loc, dest_loc), next);\n        }\n    }\n}\n\nvoid Graph::search(DistMap& dist_map, const Object* origin, bool warps_reverse) const\n{\n    SearchPriorityQueue queue;\n    dist_map.clear();\n\n    // add paths from origin to all nodes to queue\n    Loc origin_loc = (!warps_reverse && origin->type == Object::Warp) ? origin->dest : origin->loc;\n\n    for(const Object* next : this->all_nodes)\n    {\n        Loc next_loc = (warps_reverse && next->type == Object::Warp) ? next->dest : next->loc;\n        queue.emplace(s_get_distance(origin_loc, next_loc), next);\n    }\n\n    while(!queue.empty())\n    {\n        QueueEntry entry = queue.top();\n        queue.pop();\n\n        unsigned int distance = entry.first;\n        const Object* node = entry.second;\n\n        expand(queue, dist_map, node, distance, warps_reverse);\n    }\n}\n\nvoid Graph::update()\n{\n    // refresh the set of nodes\n    this->all_nodes.clear();\n    for(const Object& node : this->level.warps)\n        this->all_nodes.insert(&node);\n    // don't process exits for now, because only medals are tracked for now\n    // for(const Object& node : this->level.exits)\n    //     this->all_nodes.insert(&node);\n    for(const Object& node : this->level.items)\n        this->all_nodes.insert(&node);\n\n    // create the map of distances from start\n    search(start_dist_map, &this->level.player_start, false);\n\n    // check the furthest distance\n    this->furthest_dist = 0;\n    for(const Object* node : this->all_nodes)\n    {\n        unsigned int node_dist = this->start_dist_map[node];\n        if(node_dist > this->furthest_dist)\n            this->furthest_dist = node_dist;\n    }\n}\n\nunsigned int Graph::distance_from_start(Loc loc)\n{\n    unsigned int best_distance = s_get_distance(loc, this->level.player_start.loc);\n\n    for(const Object* node : this->all_nodes)\n    {\n        unsigned int start_to_node = start_dist_map[node];\n        if(start_to_node == 0 || start_to_node >= best_distance)\n            continue;\n\n        const Loc& node_loc = (node->type == Object::Warp) ? node->dest : node->loc;\n\n        unsigned int node_to_loc = s_get_distance(loc, node_loc);\n\n        if(start_to_node + node_to_loc < best_distance)\n            best_distance = start_to_node + node_to_loc;\n    }\n\n    return best_distance;\n}\n\nvoid FillGraph(Graph& graph)\n{\n    // start by filling the level struct\n    graph.level = ObjectGraph::Level();\n\n    graph.level.player_start = o(ObjectGraph::Object::PlayerStart,\n        get_center(PlayerStart[1]));\n\n    for(int i = 1; i <= numWarps; i++)\n    {\n        if(Warp[i].LevelEnt)\n        {\n            // eventually mark it as a possible level start\n            continue;\n        }\n\n        if(Warp[i].MapWarp || Warp[i].LevelWarp)\n        {\n            graph.level.exits.push_back(o(ObjectGraph::Object::Exit,\n                get_center(Warp[i].Entrance),\n                ObjectGraph::Object::G_Warp, i));\n        }\n        else\n        {\n            graph.level.warps.push_back(o(ObjectGraph::Object::Warp,\n                get_center(Warp[i].Entrance),\n                ObjectGraph::Object::G_Warp, i,\n                get_center(Warp[i].Exit)));\n\n            if(Warp[i].twoWay)\n            {\n                graph.level.warps.push_back(o(ObjectGraph::Object::Warp,\n                    get_center(Warp[i].Exit),\n                    ObjectGraph::Object::G_Warp, i,\n                    get_center(Warp[i].Entrance)));\n            }\n        }\n    }\n\n    // add magic doors as warps\n    for(int i = 1; i <= numNPCs; ++i)\n    {\n        NPC_t& n = NPC[i];\n\n        bool is_container = NPCIsContainer(n);\n\n        bool contains_door = is_container && (n.Special == NPCID_DOOR_MAKER || n.Special == NPCID_MAGIC_DOOR);\n        bool is_door = (n.Type == NPCID_DOOR_MAKER || n.Type == NPCID_MAGIC_DOOR);\n\n        bool has_target_section = n.Variant <= maxSections;\n\n        // allow doors only\n        if(!is_door && !contains_door)\n            continue;\n\n        // only count it if it has a target section\n        if(!has_target_section)\n            continue;\n\n        // add a warp from the NPC's position to the corresponding position in the target section\n\n        // check current section (backing up whatever value was already there)\n        uint8_t old_section = n.Section;\n        CheckSectionNPC(i);\n\n        int cur_section = n.Section;\n        n.Section = old_section;\n\n        Loc npc_center = get_center(n.Location);\n\n        int targetX = npc_center.x - LevelREAL[cur_section].X + LevelREAL[n.Variant].X;\n        int targetY = npc_center.y - LevelREAL[cur_section].Y + LevelREAL[n.Variant].Y;\n\n        graph.level.warps.push_back(o(ObjectGraph::Object::Warp,\n            npc_center,\n            ObjectGraph::Object::G_NPC, i,\n            {targetX, targetY}));\n    }\n\n    for(int i = 1; i <= numNPCs; i++)\n    {\n        if(NPC[i].Type == NPCID_GOALTAPE || NPC[i].Type == NPCID_STAR_EXIT || NPC[i].Type == NPCID_FLAG_EXIT\n            || NPC[i].Type == NPCID_ITEMGOAL || NPC[i].Type == NPCID_GOALORB_S3 || NPC[i].Type == NPCID_GOALORB_S2)\n        {\n            graph.level.exits.push_back(o(ObjectGraph::Object::Exit,\n                get_center(NPC[i].Location),\n                ObjectGraph::Object::G_NPC, i));\n        }\n\n        // only track medals for now -- they're all that's used for the time being\n        if(NPC[i].Type == NPCID_MEDAL) // || NPC[i].Type == NPCID_STAR_COLLECT || NPC[i].Type == NPCID_KEY)\n        {\n            graph.level.items.push_back(o(ObjectGraph::Object::Item,\n                get_center(NPC[i].Location),\n                ObjectGraph::Object::G_NPC, i));\n        }\n    }\n\n    for(int i = 1; i <= numBackground; i++)\n    {\n        // keyhole\n        if(Background[i].Type == 35)\n        {\n            graph.level.exits.push_back(o(ObjectGraph::Object::Exit,\n                get_center(Background[i].Location),\n                ObjectGraph::Object::G_BGO, i));\n        }\n    }\n\n    // now update the graph (gets all objects' distances from start)\n    graph.update();\n}\n\n} //namespace ObjectGraph\n"
  },
  {
    "path": "src/logic/object_graph.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef OBJECT_GRAPH_H\n#define OBJECT_GRAPH_H\n\n#include <unordered_map>\n#include <vector>\n#include <set>\n#include <queue>\n\n//! a set of features designed to support warp-aware (but not block-aware) distance checks between objects\nnamespace ObjectGraph\n{\n\nstruct Loc\n{\n    int x;\n    int y;\n};\n\n//! represents a single game object (does not attempt to track a single NPC if its ID changes)\nstruct Object\n{\n    enum Object_Type\n    {\n        PlayerStart,\n        Warp,\n        Item,\n        Exit\n    };\n    enum Game_Object_Type\n    {\n        G_None,\n        G_Warp,\n        G_Block,\n        G_NPC,\n        G_BGO\n    };\n\n    Object_Type type;\n    Loc loc;\n    Game_Object_Type g_type = G_None;\n    int game_index = -1;\n    Loc dest;\n};\n\ninline Object o(Object::Object_Type type, Loc loc)\n{\n    Object o;\n    o.type = type;\n    o.loc = loc;\n    return o;\n}\n\ninline Object o(Object::Object_Type type, Loc loc, Object::Game_Object_Type g_type, int game_index)\n{\n    Object o;\n    o.type = type;\n    o.loc = loc;\n    o.g_type = g_type;\n    o.game_index = game_index;\n    return o;\n}\n\ninline Object o(Object::Object_Type type, Loc loc, Object::Game_Object_Type g_type, int game_index, Loc dest)\n{\n    Object o;\n    o.type = type;\n    o.loc = loc;\n    o.g_type = g_type;\n    o.game_index = game_index;\n    o.dest = dest;\n    return o;\n}\n\nstruct Level\n{\n    Object player_start;\n    std::vector<Object> exits;\n    std::vector<Object> warps;\n    std::vector<Object> items;\n};\n\nclass Graph\n{\npublic:\n    // map of distances to/from objects\n    using DistMap = std::unordered_map<const Object*, unsigned int>;\n\n    Level level;\n\n    // set of all objects in the graph\n    std::set<const Object*> all_nodes;\n\n    // map of distances from P1 start TO each object\n    DistMap start_dist_map;\n\n    //! furthest distance of anything from player start\n    unsigned int furthest_dist = 0;\n\n    //! update the object graph's nodes and start_dist_map based on its level substruct\n    void update();\n\n    //! get the distance (using warps if needed) from P1 start to loc\n    unsigned int distance_from_start(Loc loc);\n\nprivate:\n    // search algorithm support functions\n    using QueueEntry = std::pair<unsigned int, const Object*>;\n    using SearchPriorityQueue = std::priority_queue<QueueEntry, std::vector<QueueEntry>, std::greater<QueueEntry>>;\n\n    // Expand a single object in the queue, updating the dist_map and adding new queue entries to additional objects.\n    void expand(SearchPriorityQueue& queue, DistMap& dist_map, const Object* node, unsigned int distance, bool warps_reverse) const;\n\n    // Fill dist_map with distances from origin. If warps_reverse is true, then find paths from objects to origin.\n    void search(DistMap& dist_map, const Object* origin, bool warps_reverse) const;\n};\n\n//! create an object graph based on the currently loaded level\nvoid FillGraph(Graph& graph);\n\n} //namespace ObjectGraph\n\n#endif // #ifndef OBJECT_GRAPH_H\n"
  },
  {
    "path": "src/main/asset_pack.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n\n#include <AppPath/app_path.h>\n\n#include <Utils/files.h>\n#include <Utils/files_ini.h>\n#include <Archives/archives.h>\n#include <DirManager/dirman.h>\n#include <Logger/logger.h>\n#include <IniProcessor/ini_processing.h>\n\n#include \"global_dirs.h\"\n#include \"main/asset_pack.h\"\n#include \"main/translate.h\"\n#include \"core/render.h\"\n#include \"core/msgbox.h\"\n#include \"core/language.h\"\n#include \"fontman/font_manager.h\"\n\n#include \"sound.h\"\n#include \"gfx.h\"\n#include \"load_gfx.h\"\n#include \"game_main.h\"\n\nstatic std::vector<AssetPack_t> s_asset_packs;\nstatic bool s_found_asset_packs = false;\n\nstd::string g_AssetPackID;\nbool g_AssetsLoaded = false;\n\nstatic void appendSlash(std::string &path)\n{\n#if defined(__EMSCRIPTEN__)\n    // fix emscripten bug of duplicated worlds\n    if(path.empty() || path.back() != '/')\n        path.push_back('/');\n#else\n    if(!path.empty() && path.back() != '/')\n        path.push_back('/');\n#endif\n}\n\nstatic inline void s_load_image(StdPicture& dest, std::string nameWithPng)\n{\n    dest.reset();\n\n#if defined(X_IMG_EXT)\n    if(nameWithPng.size() > 4)\n    {\n        std::string orig_ext;\n        size_t base_size = nameWithPng.find_last_of('.');\n\n        if(base_size == std::string::npos)\n            base_size = nameWithPng.size();\n        else\n        {\n            orig_ext = nameWithPng.substr(base_size);\n            nameWithPng.resize(base_size);\n        }\n\n        nameWithPng += X_IMG_EXT;\n\n        if(Files::fileExists(nameWithPng))\n        {\n            XRender::lazyLoadPicture(dest, nameWithPng);\n\n            if(dest.inited)\n                return; // Success\n        }\n\n#   if !defined(X_NO_PNG_GIF)\n        nameWithPng.resize(base_size);\n\n        if(!orig_ext.empty())\n            nameWithPng += orig_ext;\n#   endif // !defined(X_NO_PNG_GIF)\n    }\n#endif // defined(X_IMG_EXT)\n\n#if !defined(X_NO_PNG_GIF)\n    if(!dest.inited && Files::fileExists(nameWithPng))\n        XRender::lazyLoadPicture(dest, nameWithPng);\n#endif\n}\n\nstatic AssetPack_t s_scan_asset_pack(const std::string& path, bool skip_graphics = false)\n{\n    AssetPack_t ret;\n    ret.path = path;\n    ret.gfx.reset(new AssetPack_t::Gfx());\n\n    appendSlash(ret.path);\n\n    // based on Launcher.java:updateOverlook()\n    const char* assets_icon = \"graphics/ui/icon/thextech_128.png\";\n    const char* logo_image_path_default = \"graphics/ui/MenuGFX2.png\";\n    const char* bg_image_path_default = \"graphics/background2/background2-2.png\";\n\n    std::string logo_image_path;\n    std::string bg_image_path;\n\n    ret.gfx->bg_frames = 1;\n    ret.gfx->bg_frame_ticks = 125;\n\n    std::string meta_path = ret.path + \"_meta.ini\";\n    std::string gi_path = ret.path + \"gameinfo.ini\";\n\n    IniProcessing ini;\n    int ini_exists = 0;\n\n    if(Archives::has_prefix(path) && Files::fileExists(meta_path))\n    {\n        Files::Data data = Files::load_file(meta_path);\n        ini.openMem(data.c_str(), data.size());\n        ini_exists = 1;\n\n        ini.beginGroup(\"content\");\n\n        // confirm engine support\n        std::string engine;\n        ini.read(\"engine\", engine, engine);\n        if(engine != \"TheXTech\")\n        {\n            pLogInfo(\"Asset pack [%s] not loaded; for incompatible engine [%s]\", path.c_str(), engine.c_str());\n            return ret;\n        }\n\n        // confirm platform support\n        std::string platform;\n        ini.read(\"platform\", platform, platform);\n\n        bool platform_okay = false;\n\n#ifndef __16M__\n        if(platform == \"main\")\n            platform_okay = true;\n#endif\n\n#ifdef __3DS__\n        if(platform == \"3ds\")\n            platform_okay = true;\n#endif\n\n#ifdef __WII__\n        if(platform == \"wii\")\n            platform_okay = true;\n#endif\n\n#ifdef __16M__\n        if(platform == \"dsi\")\n            platform_okay = true;\n#endif\n\n        if(!platform_okay)\n        {\n            pLogInfo(\"Asset pack [%s] not loaded; for incompatible platform [%s]\", path.c_str(), platform.c_str());\n            return ret;\n        }\n\n        ini.read(\"pack-id\", ret.id, ret.id);\n    }\n    else if(Files::fileExists(gi_path))\n    {\n        Files::Data data = Files::load_file(gi_path);\n        ini.openMem(data.c_str(), data.size());\n        ini_exists = -1;\n\n        ini.beginGroup(\"game\");\n        ini.read(\"id\", ret.id, ret.id);\n    }\n\n    if(ini_exists)\n    {\n        // finish reading main group\n        ini.read(\"version\", ret.version, ret.version);\n        ini.read(\"show-id\", ret.show_id, ret.show_id);\n        ini.endGroup();\n\n        // read graphics group (properties for _meta.ini, android for gameinfo.ini)\n        ini.beginGroup((ini_exists > 0) ? \"properties\" : \"android\");\n        ini.read(\"show-id\", ret.show_id, ret.show_id);\n        ini.read(\"logo\", logo_image_path, logo_image_path_default);\n        ini.read(\"background\", bg_image_path, bg_image_path_default);\n        ini.read(\"background-frames\", ret.gfx->bg_frames, ret.gfx->bg_frames);\n        ini.read(\"background-delay\", ret.gfx->bg_frame_ticks, ret.gfx->bg_frame_ticks);\n        ini.endGroup();\n    }\n\n    if(logo_image_path != logo_image_path_default)\n        ret.logo_override = true;\n\n    // don't allow any dirsep characters in asset pack ID\n    for(char& c : ret.id)\n    {\n        if(c == '/' || c == '\\\\')\n            c = '_';\n    }\n\n    // ms to frames (approximately)\n    ret.gfx->bg_frame_ticks /= 15;\n\n    // load graphics\n    if(!skip_graphics)\n    {\n        s_load_image(ret.gfx->icon, ret.path + assets_icon);\n        s_load_image(ret.gfx->logo, ret.path + logo_image_path);\n        s_load_image(ret.gfx->background, ret.path + bg_image_path);\n\n        if(!ret.gfx->logo.inited && logo_image_path != logo_image_path_default)\n            s_load_image(ret.gfx->logo, ret.path + logo_image_path_default);\n\n        if(!ret.gfx->background.inited && bg_image_path != bg_image_path_default)\n            s_load_image(ret.gfx->background, ret.path + bg_image_path_default);\n    }\n\n    return ret;\n}\n\nstatic void s_strip_id(AssetPack_t& pack)\n{\n    if(!pack.id.empty() && !pack.version.empty())\n        pack.version = pack.id + '-' + pack.version;\n    else if(!pack.id.empty())\n        pack.version = pack.id;\n\n    pack.id.clear();\n}\n\nstatic void s_find_asset_packs()\n{\n    pLogDebug(\"Searching for asset packs in...\");\n\n    DirMan assets;\n    std::vector<std::string> subdirList;\n    std::vector<std::string> archiveList;\n    std::string subdir;\n\n    for(const auto& root_ : AppPathManager::assetsSearchPath())\n    {\n        std::string root = root_.first;\n        AssetsPathType type = root_.second;\n\n        // check for a root passed via `-c`\n        bool is_modern_root = (type == AssetsPathType::Single);\n        bool is_multiple_root = (type == AssetsPathType::Multiple);\n\n        // Normally, root is a legacy asset pack, and <root>/assets/ contains modern asset packs.\n        // If passed via -c, root must be a modern asset pack also.\n\n        // check for legacy debug assets\n        if(!is_multiple_root)\n            pLogDebug(\"- %s%s\", root.c_str(), (is_modern_root) ? \"\" : \" (legacy)\");\n\n        if(!is_multiple_root && DirMan::exists(root + \"graphics/ui/\"))\n        {\n            AssetPack_t pack = s_scan_asset_pack(root);\n\n            // strip the ID from a legacy asset pack\n            if(!is_modern_root)\n                s_strip_id(pack);\n\n            if(!pack.gfx || !pack.gfx->logo.inited)\n                pLogWarning(\"  Could not load UI assets from %s asset pack [%s], ignoring\", (is_modern_root) ? \"user-specified\" : \"possible legacy\", pack.path.c_str());\n            else if(is_modern_root && pack.id.empty())\n                pLogWarning(\"  Could not read ID of modern asset pack [%s], ignoring\", pack.path.c_str());\n            else\n                s_asset_packs.push_back(std::move(pack));\n        }\n\n        if(type == AssetsPathType::Legacy)\n            root += \"assets/\";\n\n        if(!is_modern_root)\n            pLogDebug(\"- %s*\", root.c_str());\n\n        if(!is_modern_root && DirMan::exists(root))\n        {\n            assets.setPath(root);\n            assets.getListOfFolders(subdirList);\n            assets.getListOfFiles(archiveList);\n\n            size_t num_dirs = subdirList.size();\n\n            for(std::string& a : archiveList)\n                subdirList.push_back(std::move(a));\n\n            size_t i = 0;\n\n            for(const std::string& sub : subdirList)\n            {\n                i++;\n\n                // archive\n                if(i > num_dirs)\n                {\n                    subdir = \"@\" + root + sub + \":/\";\n\n                    if(!Files::fileExists(subdir + \"_meta.ini\"))\n                        continue;\n                }\n                // subdir\n                else\n                {\n                    subdir = root + sub;\n\n                    if(!DirMan::exists(subdir + \"/graphics/ui/\"))\n                        continue;\n                }\n\n                D_pLogDebug(\"  Checking %s\", subdir.c_str());\n\n                AssetPack_t pack = s_scan_asset_pack(subdir);\n\n                if(!pack.gfx || !pack.gfx->logo.inited)\n                    pLogWarning(\"  Could not load UI assets from possible asset pack [%s], ignoring\", pack.path.c_str());\n                else if(pack.id.empty())\n                    pLogWarning(\"  Could not read ID of possible asset pack [%s], ignoring\", pack.path.c_str());\n                else\n                    s_asset_packs.push_back(std::move(pack));\n            }\n        }\n    }\n\n    if(!s_asset_packs.empty())\n    {\n        pLogDebug(\"Found asset packs:\");\n\n        for(const AssetPack_t& pack : s_asset_packs)\n            pLogDebug(\"- %s (%s)\", pack.full_id().c_str(), pack.path.c_str());\n    }\n    else\n        pLogCritical(\"Could not find any asset packs.\");\n\n    // check for duplicates\n    for(size_t i = 0; i < s_asset_packs.size(); i++)\n    {\n        for(size_t j = i + 1; j < s_asset_packs.size();)\n        {\n            if(s_asset_packs[i].id == s_asset_packs[j].id)\n            {\n                if(s_asset_packs[i].version == s_asset_packs[j].version)\n                {\n                    // erase a case with same ID and version\n                    s_asset_packs.erase(s_asset_packs.begin() + j);\n                    continue;\n                }\n\n                // if version is the distinguishing factor, display it\n                s_asset_packs[i].show_version = true;\n                s_asset_packs[j].show_version = true;\n            }\n\n            j++;\n        }\n    }\n\n    s_found_asset_packs = true;\n}\n\nstatic std::string s_prepare_assets_path(const std::string& path)\n{\n    Archives::unmount_assets();\n\n    std::string target_path = path;\n\n    // parse and mount assets if possible\n    if(target_path[0] == '@')\n    {\n        // mount target, then replace path\n        auto archive_end = target_path.begin();\n        // don't check for path end until after the first slash\n        while(archive_end != target_path.end() && *archive_end != '/' && *archive_end != '\\\\')\n            ++archive_end;\n        while(archive_end != target_path.end() && *archive_end != ':')\n            ++archive_end;\n\n        if(archive_end != target_path.end())\n        {\n            *archive_end = '\\0';\n\n            if(Archives::mount_assets(target_path.c_str() + 1))\n            {\n                target_path.erase(target_path.begin() + 2, archive_end + 1);\n                target_path[0] = ':';\n                target_path[1] = 'a';\n            }\n            else\n                *archive_end = ':';\n        }\n    }\n\n    return target_path;\n}\n\nstatic bool s_try_load_gfx(const AssetPack_t& pack, bool skip_gfx)\n{\n    pLogDebug(\"= Trying to load asset pack \\\"%s/%s\\\" from [%s]\", pack.id.c_str(), pack.version.c_str(), pack.path.c_str());\n\n    std::string target_path = s_prepare_assets_path(pack.path);\n\n    AppPathManager::setCurrentAssetPack(pack.id, target_path);\n    AppPath = AppPathManager::assetsRoot();\n    g_AssetPackID = pack.full_id();\n\n    XLanguage::findLanguages(); // find present translations\n    ReloadTranslations(); // load translations\n\n    if(!skip_gfx && !GFX.load())\n    {\n        GFX.unLoad();\n        pLogWarning(\"Failed to load GFX from %s\", AppPath.c_str());\n        return false;\n    }\n\n    FontManager::initFull();\n\n    return true;\n}\n\n\nbool ReloadAssetsFrom(const AssetPack_t& pack)\n{\n    const AssetPack_t* fallback_pack = nullptr;\n    for(const AssetPack_t& pack : s_asset_packs)\n    {\n        if(pack.path == AppPath && pack.full_id() == g_AssetPackID)\n        {\n            fallback_pack = &pack;\n            break;\n        }\n    }\n\n    UnloadCustomGFX();\n    UnloadWorldCustomGFX();\n\n    GFX.unLoad();\n    StopAllSounds();\n    StopMusic();\n    UnloadSound();\n    FontManager::quit();\n\n    if(!s_try_load_gfx(pack, false))\n    {\n        pLogWarning(\"Failed to load UI assets\");\n\n        // restore original GFX\n        if(!fallback_pack || !s_try_load_gfx(*fallback_pack, false))\n            pLogCritical(\"Could not reload graphics from previous path.\");\n        else\n        {\n            FontManager::initFull();\n            InitSound(); // Restore sound effects\n        }\n\n        // also, remove from list of valid asset packs\n        for(auto it = s_asset_packs.begin(); it != s_asset_packs.end(); ++it)\n        {\n            // find the requested pack exactly (by identity, not contents)\n            if(&*it != &pack)\n                continue;\n\n            pLogInfo(\"Removed %s from asset pack list because it is missing UI assets\", pack.path.c_str());\n            s_asset_packs.erase(it);\n            break;\n        }\n\n        return false;\n    }\n\n    g_dirEpisode.invalidate();\n    g_dirCustom.invalidate();\n\n    pLogDebug(\"Successfully loaded UI assets; now loading all other assets from [%s]\", AppPath.c_str());\n\n    MainLoadAll();\n    return true;\n}\n\n\nbool InitUIAssetsFrom(const std::string& cmdline_id, bool skip_gfx)\n{\n    if(!s_found_asset_packs)\n        s_find_asset_packs();\n\n    // figure out exactly what is requested.\n    std::string full_id = cmdline_id;\n    std::string id_as_path = cmdline_id;\n    if(full_id.empty())\n    {\n        full_id = g_recentAssetPack;\n        pLogDebug(\"Trying to load most recent asset pack [ID: %s]\", full_id.c_str());\n    }\n    else\n    {\n        appendSlash(id_as_path);\n        pLogDebug(\"Trying to load CLI-specified asset pack [%s]\", full_id.c_str());\n    }\n\n    // split full ID into ID and version\n    std::string id = full_id;\n    std::string version;\n\n    auto slash_pos = id.find('/');\n\n    if(slash_pos != std::string::npos)\n    {\n        version = id.substr(slash_pos + 1);\n        id.resize(slash_pos);\n    }\n\n    // path-match, full-match, id-match, any\n    for(int round = 0; round < 4; round++)\n    {\n        for(auto it = s_asset_packs.begin(); it != s_asset_packs.end(); /* ++it at bottom of loop */)\n        {\n            bool matched = false;\n            if(round == 0)\n                matched = (!cmdline_id.empty() && it->path == id_as_path);\n            else if(round == 1)\n                matched = (it->id == id && it->version == version);\n            else if(round == 2 && (cmdline_id.empty() || slash_pos == std::string::npos))\n                matched = (it->id == id);\n            else\n                matched = cmdline_id.empty();\n\n            if(matched)\n            {\n                if(s_try_load_gfx(*it, skip_gfx))\n                    return true;\n\n                // failed to load, erase the failed asset pack and move on to next loop\n                it = s_asset_packs.erase(it);\n                continue;\n            }\n\n            ++it;\n        }\n    }\n\n    if(!skip_gfx && !GFX.load())\n        return false;\n\n    return true;\n}\n\n\nconst std::vector<AssetPack_t>& GetAssetPacks()\n{\n    if(!s_found_asset_packs)\n        s_find_asset_packs();\n\n    return s_asset_packs;\n}\n"
  },
  {
    "path": "src/main/asset_pack.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef ASSET_PACK_H\n#define ASSET_PACK_H\n\n#include <string>\n#include <vector>\n#include <memory>\n\n#include \"std_picture.h\"\n\n// string ID of the current asset pack\nextern std::string g_AssetPackID;\n\n// true if any asset pack has been loaded yet\nextern bool g_AssetsLoaded;\n\nstruct AssetPack_t\n{\n    struct Gfx\n    {\n        StdPicture logo;\n        StdPicture icon;\n        StdPicture background;\n\n        int bg_frames = 1;\n        int bg_frame_ticks = 8;\n    };\n\n    std::string id;\n    std::string version;\n\n    std::string path;\n\n    std::unique_ptr<Gfx> gfx;\n\n    bool show_id = false;\n    bool show_version = false;\n    bool logo_override = false;\n\n    std::string full_id() const\n    {\n        if(id.empty() && version.empty())\n            return \"\";\n        else if(version.empty())\n            return id;\n\n        return id + \"/\" + version;\n    }\n};\n\n//! reports currently discovered asset pack IDs\nconst std::vector<AssetPack_t>& GetAssetPacks();\n\n//! changes the AppPath and reloads assets from a desired asset pack\nbool ReloadAssetsFrom(const AssetPack_t& pack);\n\n//! initalizes the AppPath and loads GFX from a desired asset pack\nbool InitUIAssetsFrom(const std::string& id, bool skip_gfx = false);\n\n#endif // #ifndef ASSET_PACK_H\n"
  },
  {
    "path": "src/main/block_table.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n#include <sorting/pdqsort.h>\n\n#include \"layers.h\"\n#include \"config.h\"\n\n#include \"main/block_table.h\"\n#include \"main/block_table.hpp\"\n#include \"main/trees.h\"\n\n// all shared utility code for all item types\ntemplate<class ItemRef_t>\nstruct TableInterface\n{\n    table_t<ItemRef_t> common_table;\n    table_t<ItemRef_t> layer_table[maxLayers+1];\n\n    bool layer_table_active[maxLayers+1] = {false};\n    int16_t active_tables[maxLayers+1] = {0};\n    int num_active_tables = 0;\n\n    // clears all tables and rejoins all layers\n    void clear()\n    {\n        common_table.clear();\n\n        for(int i = 0; i < maxLayers + 1; i++)\n        {\n            layer_table[i].clear();\n            layer_table_active[i] = false;\n        }\n\n        num_active_tables = 0;\n    }\n\n    const std::vector<vbint_t>& layer_items(int layer);\n\n    // checks if a layer is currently split from the main table\n    bool active(int layer)\n    {\n        return layer_table_active[layer];\n    }\n\n    // splits a layer from the main table\n    void split(int layer)\n    {\n        if(layer < 0 || layer == LAYER_NONE || layer_table_active[layer])\n            return;\n\n        layer_table_active[layer] = true;\n        active_tables[num_active_tables] = layer;\n        num_active_tables++;\n\n        for(int i : layer_items(layer))\n        {\n            common_table.erase(i);\n            layer_table[layer].insert_layer(i);\n        }\n    }\n\n    // joins a layer to the main table\n    void join(int layer)\n    {\n        if(layer < 0 || layer == LAYER_NONE || !layer_table_active[layer])\n            return;\n\n        layer_table_active[layer] = false;\n\n        // remove from queue of active tables\n        num_active_tables--;\n        for(int i = 0; i < num_active_tables; i++)\n        {\n            if(active_tables[i] == layer)\n            {\n                active_tables[i] = active_tables[num_active_tables];\n                break;\n            }\n        }\n\n        for(int i : layer_items(layer))\n        {\n            common_table.insert(i);\n        }\n\n        layer_table[layer].clear();\n    }\n\n    void add(int layer, ItemRef_t item)\n    {\n        if(layer < 0 || layer == LAYER_NONE || !layer_table_active[layer])\n            common_table.insert(item);\n        else\n            layer_table[layer].insert_layer(item);\n    }\n\n    void update(int layer, ItemRef_t item)\n    {\n        if(layer < 0 || layer == LAYER_NONE || !layer_table_active[layer])\n            common_table.update(item);\n        else\n            layer_table[layer].update_layer(item);\n    }\n\n    void erase(int layer, ItemRef_t item)\n    {\n        if(layer < 0 || layer == LAYER_NONE || !layer_table_active[layer])\n            common_table.erase(item);\n        else\n            layer_table[layer].erase(item);\n    }\n\n    inline void query(std::vector<BaseRef_t>& out, const Location_t& _loc, int sort_mode)\n    {\n        // ignore improper rects\n        if(_loc.Width < 0 || _loc.Height < 0)\n            return;\n\n        rect_external loc;\n\n        // NOTE: there are extremely rare cases when these margins are not sufficient for full compatibility\n        //   (such as, when an item is trapped inside a wall during !BlocksSorted)\n        // This should be fixed for players now.\n        // Conversely, there may be cases where this is too much when BlocksSorted is true and an FLBlock query was made.\n        if(g_config.emulate_classic_block_order)\n            loc.query_from_loc_paranoid(_loc);\n        else\n        {\n            int32_t l_int = num_t::floor(_loc.X);\n            int32_t r_int = num_t::ceil(_loc.X + _loc.Width);\n            int32_t t_int = num_t::floor(_loc.Y);\n            int32_t b_int = num_t::ceil(_loc.Y + _loc.Height);\n\n            // workaround to handle common block push behavior of NPCs -- if NPCs eventually use updatable queries, then we can simply use query_from_loc_standard as in other callsites\n            if(r_int < l_int + 32)\n                r_int = l_int + 32;\n\n            // find which 64x64 tiles contain the query with a 2px margin in each direction\n            loc.l = s_floor_div_64(l_int - 2);\n            loc.r = s_floor_div_64(r_int + 2 + 63);\n            loc.t = s_floor_div_64(t_int - 2);\n            loc.b = s_floor_div_64(b_int + 2 + 63);\n        }\n\n        common_table.query(out, loc);\n\n        // bigger offset for r/b because of layer offset rounding issues\n        if(num_active_tables > 0)\n        {\n            loc.r += 1;\n            loc.b += 1;\n\n            for(int i = 0; i < num_active_tables; i++)\n            {\n                int layer = active_tables[i];\n\n                int16_t offX = s_floor_div_64(num_t::floor(-Layer[layer].OffsetX));\n                int16_t offY = s_floor_div_64(num_t::floor(-Layer[layer].OffsetY));\n\n                rect_external layer_loc{(int16_t)(loc.l + offX), (int16_t)(loc.r + offX), (int16_t)(loc.t + offY), (int16_t)(loc.b + offY)};\n\n                layer_table[layer].query(out, layer_loc);\n            }\n        }\n\n        if(sort_mode == SORTMODE_COMPAT)\n        {\n            if(g_config.emulate_classic_block_order)\n                sort_mode = SORTMODE_ID;\n            else\n                sort_mode = SORTMODE_LOC;\n        }\n\n        if(sort_mode == SORTMODE_LOC)\n            pdqsort(out.begin(), out.end(), Comparisons::Loc<ItemRef_t>);\n        else if(sort_mode == SORTMODE_ID)\n            trees_sort_by_index(out);\n        else if(sort_mode == SORTMODE_Z)\n            pdqsort(out.begin(), out.end(), Comparisons::Z<ItemRef_t>);\n    }\n\n    void query(std::vector<BaseRef_t>& out, num_t Left, num_t Top, num_t Right, num_t Bottom,\n               int sort_mode, num_t margin)\n    {\n        auto loc = newLoc(Left - margin,\n                          Top - margin,\n                          (Right - Left) + margin * 2,\n                          (Bottom - Top) + margin * 2);\n\n        query(out, loc, sort_mode);\n    }\n\n    TreeResult_Sentinel<ItemRef_t> query(Location_t loc,\n                             int sort_mode)\n    {\n        TreeResult_Sentinel<ItemRef_t> result;\n\n        query(*result.i_vec, loc, sort_mode);\n\n        return result;\n    }\n\n    TreeResult_Sentinel<ItemRef_t> query(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                             int sort_mode, num_t margin)\n    {\n        auto loc = newLoc(Left - margin,\n                          Top - margin,\n                          (Right - Left) + margin * 2,\n                          (Bottom - Top) + margin * 2);\n\n        return query(loc, sort_mode);\n    }\n};\n\ntable_t<BlockRef_t> s_temp_block_table;\ntable_t<NPCRef_t> s_npc_table;\nbool s_temp_blocks_enabled = false;\n\n\n/* ================= Level blocks ================= */\n\ntemplate<>\nconst std::vector<vbint_t>& TableInterface<BlockRef_t>::layer_items(int layer)\n{\n    return Layer[layer].blocks;\n}\n\nTableInterface<BlockRef_t> s_block_tables;\n\nvoid treeLevelCleanBlockLayers()\n{\n    s_block_tables.clear();\n    s_temp_block_table.clear();\n}\n\n// checks if a layer is split from the main block table\nbool treeBlockLayerActive(int layer)\n{\n    return s_block_tables.active(layer);\n}\n\n// splits a layer from the main block table\nvoid treeBlockSplitLayer(int layer)\n{\n    s_block_tables.split(layer);\n}\n\n// joins a layer to the main block table\nvoid treeBlockJoinLayer(int layer)\n{\n    s_block_tables.join(layer);\n}\n\nvoid treeBlockAddLayer(int layer, BlockRef_t block)\n{\n    s_block_tables.add(layer, block);\n}\n\nvoid treeBlockUpdateLayer(int layer, BlockRef_t block)\n{\n    s_block_tables.update(layer, block);\n}\n\nvoid treeBlockRemoveLayer(int layer, BlockRef_t block)\n{\n    s_block_tables.erase(layer, block);\n}\n\nTreeResult_Sentinel<BlockRef_t> treeFLBlockQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                         int sort_mode,\n                         num_t margin)\n{\n    return s_block_tables.query(Left, Top, Right, Bottom, sort_mode, margin);\n}\n\nvoid treeFLBlockQuery(std::vector<BaseRef_t>& out, const Location_t &loc, int sort_mode)\n{\n    s_block_tables.query(out, loc, sort_mode);\n}\n\nTreeResult_Sentinel<BlockRef_t> treeFLBlockQuery(const Location_t &loc,\n                         int sort_mode)\n{\n    return s_block_tables.query(loc, sort_mode);\n}\n\n/* ================= Temp blocks ================= */\n\nstatic void s_NPCsToTempBlocks(std::vector<BaseRef_t>& out, size_t begin)\n{\n    for(size_t i = begin; i != out.size();)\n    {\n        // some NPCs' tempBlocks are at a different place than their location,\n        // and those tempBlocks get added to the dedicated tree.\n\n        const NPC_t& n = (NPCRef_t) out[i];\n        if(!n.tempBlockInTree && n.tempBlock != 0 && n.tempBlock <= numBlock)\n        {\n            out[i] = n.tempBlock;\n            ++i;\n        }\n        else\n        {\n            out[i] = out[out.size() - 1];\n            out.resize(out.size() - 1);\n        }\n    }\n}\n\nstatic void s_NPCsToTempBlocks(std::vector<BaseRef_t>& out)\n{\n    s_NPCsToTempBlocks(out, 0);\n}\n\n\nvoid treeTempBlockEnable()\n{\n    s_temp_blocks_enabled = true;\n}\n\nvoid treeTempBlockFullClear()\n{\n    s_temp_block_table.clear();\n    s_temp_blocks_enabled = false;\n}\n\nvoid treeTempBlockClear()\n{\n    s_temp_block_table.clear_light();\n    s_temp_blocks_enabled = false;\n}\n\nvoid treeTempBlockAdd(BlockRef_t obj)\n{\n    if(!s_temp_blocks_enabled)\n        return;\n\n    s_temp_block_table.insert(obj);\n}\n\nvoid treeTempBlockUpdate(BlockRef_t obj)\n{\n    if(!s_temp_blocks_enabled)\n        return;\n\n    s_temp_block_table.update(obj);\n}\n\nvoid treeTempBlockRemove(BlockRef_t obj)\n{\n    if(!s_temp_blocks_enabled)\n        return;\n\n    s_temp_block_table.erase(obj);\n}\n\nvoid treeTempBlockQuery(std::vector<BaseRef_t>& out,\n                        const Location_t &_loc,\n                        int sort_mode)\n{\n    if(!s_temp_blocks_enabled)\n        return;\n\n    // ignore improper rects\n    if(_loc.Width < 0 || _loc.Height < 0)\n        return;\n\n    rect_external loc;\n\n    // NOTE: there are extremely rare cases when these margins are not sufficient for full compatibility\n    //   (such as, when an item is trapped inside a wall during !BlocksSorted)\n    if(g_config.emulate_classic_block_order)\n        loc.query_from_loc_paranoid(_loc);\n    else\n        loc.query_from_loc_standard(_loc);\n\n\n    s_npc_table.query(out, loc);\n\n    s_NPCsToTempBlocks(out);\n\n    // this table is empty unless an NPC crossed a boundary during this frame\n    if(s_temp_block_table.member_rects.size() != 0)\n        s_temp_block_table.query(out, loc);\n\n\n    if(sort_mode == SORTMODE_COMPAT)\n    {\n        if(g_config.emulate_classic_block_order)\n            sort_mode = SORTMODE_ID;\n        else\n            sort_mode = SORTMODE_LOC;\n    }\n\n    if(sort_mode == SORTMODE_LOC)\n        pdqsort(out.begin(), out.end(), Comparisons::Loc<BlockRef_t>);\n    else if(sort_mode == SORTMODE_ID)\n        trees_sort_by_index(out);\n    else if(sort_mode == SORTMODE_Z)\n        pdqsort(out.begin(), out.end(), Comparisons::Z<BlockRef_t>);\n}\n\nTreeResult_Sentinel<BlockRef_t> treeTempBlockQuery(const Location_t &_loc,\n                        int sort_mode)\n{\n    TreeResult_Sentinel<BlockRef_t> result;\n\n    treeTempBlockQuery(*result.i_vec, _loc, sort_mode);\n\n    return result;\n}\n\nTreeResult_Sentinel<BlockRef_t> treeTempBlockQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                         int sort_mode,\n                         num_t margin)\n{\n    auto loc = newLoc(Left - margin,\n                      Top - margin,\n                      (Right - Left) + margin * 2,\n                      (Bottom - Top) + margin * 2);\n\n    return treeTempBlockQuery(loc, sort_mode);\n}\n\n/* ================= Combined Block Query ============== */\n\nTreeResult_Sentinel<BlockRef_t> treeBlockQuery(const Location_t &_loc,\n                         int sort_mode)\n{\n    TreeResult_Sentinel<BlockRef_t> result;\n\n    // ignore improper rects\n    if(_loc.Width < 0 || _loc.Height < 0)\n        return result;\n\n    Location_t loc = _loc;\n\n    // NOTE: there are extremely rare cases when these margins are not sufficient for full compatibility\n    //   (such as, when an item is trapped inside a wall during !BlocksSorted)\n    if(g_config.emulate_classic_block_order)\n    {\n        loc.X -= 32;\n        loc.Y -= 32;\n        loc.Width += 64;\n        loc.Height += 64;\n    }\n    else\n    {\n        loc.X -= 2;\n        loc.Y -= 2;\n        loc.Width += 4;\n        loc.Height += 4;\n    }\n\n\n    s_block_tables.common_table.query(*result.i_vec, loc);\n\n    num_t oX = loc.X;\n    num_t oY = loc.Y;\n\n    for(int i = 0; i < s_block_tables.num_active_tables; i++)\n    {\n        int layer = s_block_tables.active_tables[i];\n\n        loc.X -= Layer[layer].OffsetX;\n        loc.Y -= Layer[layer].OffsetY;\n\n        s_block_tables.layer_table[layer].query(*result.i_vec, loc);\n\n        loc.X = oX;\n        loc.Y = oY;\n    }\n\n\n    // this is where this function differs from the standard TableInterface::query\n    if(s_temp_blocks_enabled)\n    {\n        auto pre_temp_size = result.i_vec->size();\n\n        s_npc_table.query(*result.i_vec, loc);\n\n        s_NPCsToTempBlocks(*result.i_vec, pre_temp_size);\n\n        s_temp_block_table.query(*result.i_vec, loc);\n    }\n\n\n    if(sort_mode == SORTMODE_COMPAT)\n    {\n        if(g_config.emulate_classic_block_order)\n            sort_mode = SORTMODE_ID;\n        else\n            sort_mode = SORTMODE_LOC;\n    }\n\n    if(sort_mode == SORTMODE_LOC)\n        pdqsort(result.i_vec->begin(), result.i_vec->end(), Comparisons::Loc<BlockRef_t>);\n    else if(sort_mode == SORTMODE_ID)\n        trees_sort_by_index(*result.i_vec);\n    else if(sort_mode == SORTMODE_Z)\n        pdqsort(result.i_vec->begin(), result.i_vec->end(), Comparisons::Z<BlockRef_t>);\n\n    return result;\n}\n\n/* ================= Level Backgrounds ================= */\n\ntemplate<>\nconst std::vector<vbint_t>& TableInterface<BackgroundRef_t>::layer_items(int layer)\n{\n    return Layer[layer].BGOs;\n}\n\nTableInterface<BackgroundRef_t> s_background_tables;\n\nvoid treeLevelCleanBackgroundLayers()\n{\n    s_background_tables.clear();\n}\n\n// checks if a layer is split from the main background table\nbool treeBackgroundLayerActive(int layer)\n{\n    return s_background_tables.active(layer);\n}\n\n// splits a layer from the main Background table\nvoid treeBackgroundSplitLayer(int layer)\n{\n    s_background_tables.split(layer);\n}\n\n// joins a layer to the main Background table\nvoid treeBackgroundJoinLayer(int layer)\n{\n    s_background_tables.join(layer);\n}\n\nvoid treeBackgroundAddLayer(int layer, BackgroundRef_t obj)\n{\n    s_background_tables.add(layer, obj);\n}\n\nvoid treeBackgroundUpdateLayer(int layer, BackgroundRef_t obj)\n{\n    s_background_tables.update(layer, obj);\n}\n\nvoid treeBackgroundRemoveLayer(int layer, BackgroundRef_t obj)\n{\n    s_background_tables.erase(layer, obj);\n}\n\nTreeResult_Sentinel<BackgroundRef_t> treeBackgroundQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                         int sort_mode,\n                         num_t margin)\n{\n    return s_background_tables.query(Left, Top, Right, Bottom, sort_mode, margin);\n}\n\nvoid treeBackgroundQuery(std::vector<BaseRef_t>& out, const Location_t &loc, int sort_mode)\n{\n    s_background_tables.query(out, loc, sort_mode);\n}\n\nTreeResult_Sentinel<BackgroundRef_t> treeBackgroundQuery(const Location_t &loc, int sort_mode)\n{\n    return s_background_tables.query(loc, sort_mode);\n}\n\n\n/* ================= Level NPCs ================= */\n\nvoid treeNPCClear()\n{\n    s_npc_table.clear();\n}\n\nvoid treeNPCAdd(NPCRef_t obj)\n{\n    s_npc_table.insert(obj);\n}\n\nbool treeNPCUpdate(NPCRef_t obj)\n{\n    SDL_assert_release((int)obj > 0);\n\n    return s_npc_table.update(obj);\n}\n\nvoid treeNPCSplitTempBlock(NPCRef_t obj)\n{\n    if(!obj->tempBlockInTree)\n    {\n        obj->tempBlockInTree = true;\n        treeTempBlockAdd(obj->tempBlock);\n    }\n}\n\nvoid treeNPCUpdateTempBlock(NPCRef_t obj)\n{\n    if(!obj->tempBlockInTree)\n    {\n        obj->tempBlockInTree = true;\n        treeTempBlockAdd(obj->tempBlock);\n    }\n    else\n    {\n        treeTempBlockUpdate(obj->tempBlock);\n    }\n}\n\nvoid treeNPCRemove(NPCRef_t obj)\n{\n    s_npc_table.erase(obj);\n}\n\nvoid treeNPCQuery(std::vector<BaseRef_t>& out, const Location_t &_loc, int sort_mode)\n{\n    // ignore improper rects\n    if(_loc.Width < 0 || _loc.Height < 0)\n        return;\n\n    rect_external loc;\n\n    // NOTE: there are extremely rare cases when these margins are not sufficient for full compatibility\n    //   (such as, when an item is trapped inside a wall during !BlocksSorted)\n    if(g_config.emulate_classic_block_order)\n        loc.query_from_loc_paranoid(_loc);\n    else\n        loc.query_from_loc_standard(_loc);\n\n    s_npc_table.query(out, loc);\n\n    if(sort_mode == SORTMODE_COMPAT)\n        sort_mode = SORTMODE_ID;\n\n    if(sort_mode == SORTMODE_LOC)\n        pdqsort(out.begin(), out.end(), Comparisons::Loc<NPCRef_t>);\n    else if(sort_mode == SORTMODE_ID)\n        trees_sort_by_index(out);\n    else if(sort_mode == SORTMODE_Z)\n        pdqsort(out.begin(), out.end(), Comparisons::Z<NPCRef_t>);\n}\n\nTreeResult_Sentinel<NPCRef_t> treeNPCQuery(const Location_t &_loc,\n                         int sort_mode)\n{\n    TreeResult_Sentinel<NPCRef_t> result;\n\n    treeNPCQuery(*result.i_vec, _loc, sort_mode);\n\n    return result;\n}\n\nTreeResult_Sentinel<NPCRef_t> treeNPCQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                         int sort_mode,\n                         num_t margin)\n{\n    auto loc = newLoc(Left - margin,\n                      Top - margin,\n                      (Right - Left) + margin * 2,\n                      (Bottom - Top) + margin * 2);\n\n    return treeNPCQuery(loc, sort_mode);\n}\n\n\n/* ================= Level PEZs ================= */\n\ntemplate<>\nconst std::vector<vbint_t>& TableInterface<WaterRef_t>::layer_items(int layer)\n{\n    return Layer[layer].waters;\n}\n\nTableInterface<WaterRef_t> s_water_tables;\n\nvoid treeLevelCleanWaterLayers()\n{\n    s_water_tables.clear();\n}\n\n// checks if a layer is split from the main Water table\nbool treeWaterLayerActive(int layer)\n{\n    return s_water_tables.active(layer);\n}\n\n// splits a layer from the main Water table\nvoid treeWaterSplitLayer(int layer)\n{\n    s_water_tables.split(layer);\n}\n\n// joins a layer to the main Water table\nvoid treeWaterJoinLayer(int layer)\n{\n    s_water_tables.join(layer);\n}\n\nvoid treeWaterAddLayer(int layer, WaterRef_t obj)\n{\n    s_water_tables.add(layer, obj);\n}\n\nvoid treeWaterUpdateLayer(int layer, WaterRef_t obj)\n{\n    s_water_tables.update(layer, obj);\n}\n\nvoid treeWaterRemoveLayer(int layer, WaterRef_t obj)\n{\n    s_water_tables.erase(layer, obj);\n}\n\nTreeResult_Sentinel<WaterRef_t> treeWaterQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                         int sort_mode,\n                         num_t margin)\n{\n    if(numWater == 0)\n    {\n        TreeResult_Sentinel<WaterRef_t> empty;\n        return empty;\n    }\n\n    return s_water_tables.query(Left, Top, Right, Bottom, sort_mode, margin);\n}\n\nTreeResult_Sentinel<WaterRef_t> treeWaterQuery(const Location_t &loc,\n                         int sort_mode)\n{\n    if(numWater == 0)\n    {\n        TreeResult_Sentinel<WaterRef_t> empty;\n        return empty;\n    }\n\n    return s_water_tables.query(loc, sort_mode);\n}\n"
  },
  {
    "path": "src/main/block_table.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef BLOCK_TABLE_H\n#define BLOCK_TABLE_H\n\n#include \"globals.h\"\n\nvoid treeBlockUpdateLayer(int layer, BlockRef_t block);\nbool treeBlockLayerActive(int layer);\nvoid treeBlockJoinLayer(int layer);\nvoid treeBlockSplitLayer(int layer);\n\nvoid treeBackgroundUpdateLayer(int layer, BackgroundRef_t block);\nbool treeBackgroundLayerActive(int layer);\nvoid treeBackgroundJoinLayer(int layer);\nvoid treeBackgroundSplitLayer(int layer);\n\nvoid treeWaterUpdateLayer(int layer, WaterRef_t block);\nbool treeWaterLayerActive(int layer);\nvoid treeWaterJoinLayer(int layer);\nvoid treeWaterSplitLayer(int layer);\n\n#endif // #ifndef BLOCK_TABLE_H\n"
  },
  {
    "path": "src/main/block_table.hpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef BLOCK_TABLE_IMPL_HPP\n#define BLOCK_TABLE_IMPL_HPP\n\n#include <iterator>\n#include <array>\n#include <set>\n#include <unordered_map>\n\n#include \"globals.h\"\n#include \"layers.h\"\n#include \"npc_traits.h\"\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\nstatic inline int32_t s_floor_div_32(int32_t x)\n{\n    static_assert(((-1) >> 5) == -1 && (31 >> 5) == 0 && (-3232 >> 5) == -101, \"block_table.hpp requires signed right shift to have floor semantics\");\n    return (int16_t)(x >> 5);\n}\n\nstatic inline int32_t s_floor_div_64(int32_t x)\n{\n    static_assert(((-1) >> 6) == -1 && (63 >> 6) == 0 && (-6464 >> 6) == -101, \"block_table.hpp requires signed right shift to have floor semantics\");\n    return (int16_t)(x >> 6);\n}\n\nenum ContinuedRect\n{\n    CONT_NONE = 0,\n    CONT_X = 1,\n    CONT_Y = 2,\n    CONT_XY = CONT_X | CONT_Y,\n};\n\nstruct AugBaseRef_t\n{\n    BaseRef_t ref;\n    uint8_t cont_axes;\n};\n\nstruct node_t\n{\n    // not safe to change this\n    static constexpr int node_size = 4;\n\n    node_t* next = nullptr;\n    int16_t refs[node_size];\n    uint8_t filled = 0;\n    uint8_t cont_axes = 0;\n\n    inline node_t()\n    {\n        next = nullptr;\n        filled = 0;\n        cont_axes = 0;\n    }\n\n    inline ~node_t()\n    {\n        if(next)\n            delete next;\n    }\n\n    struct iterator\n    {\n        node_t* parent;\n        uint16_t i;\n\n        constexpr inline iterator(std::nullptr_t parent) : parent(parent), i(0) {}\n\n        inline void check_linkage()\n        {\n            while(this->parent && this->i == this->parent->filled)\n            {\n                this->parent = this->parent->next;\n                this->i = 0;\n            }\n        }\n\n        inline iterator(node_t* parent, size_t i): parent(parent), i((uint16_t)i)\n        {\n            this->check_linkage();\n        }\n\n        inline iterator operator++()\n        {\n            if(this->parent)\n            {\n                this->i++;\n                if(this->i == this->parent->filled)\n                {\n                    this->parent = this->parent->next;\n                    this->i = 0;\n                    this->check_linkage();\n                }\n            }\n            return *this;\n        }\n\n        inline bool operator!=(const iterator& other) const\n        {\n            return this->parent != other.parent || this->i != other.i;\n        }\n\n        inline AugBaseRef_t operator*() const\n        {\n            return {this->parent->refs[this->i], (uint8_t)((this->parent->cont_axes >> (this->i * 2)) & 3)};\n        }\n    };\n\n    inline iterator begin()\n    {\n        return iterator(this, 0);\n    }\n\n    static constexpr inline iterator end()\n    {\n        return iterator(nullptr);\n    }\n\n    inline void insert(AugBaseRef_t b)\n    {\n        if(this->filled < node_size)\n        {\n            this->refs[this->filled] = b.ref;\n            this->cont_axes |= (b.cont_axes & 3) << (this->filled * 2);\n            this->filled++;\n        }\n        else\n        {\n            if(!this->next)\n                this->next = new node_t;\n            this->next->insert(b);\n        }\n    }\n\n    inline void erase(iterator& it)\n    {\n        it.parent->filled--;\n        it.parent->refs[it.i] = it.parent->refs[it.parent->filled];\n        it.parent->cont_axes &= ~(3 << (it.i * 2));\n        it.parent->cont_axes |= ((it.parent->cont_axes >> (it.parent->filled * 2)) & 3) << (it.i * 2);\n        it.parent->cont_axes &= ~(3 << (it.parent->filled * 2));\n    }\n\n    inline void erase(BaseRef_t o)\n    {\n        iterator it = this->begin();\n\n        while(it != this->end())\n        {\n            if((*it).ref == o)\n                break;\n            ++it;\n        }\n\n        if(it != this->end())\n            this->erase(it);\n    }\n};\n\nstruct AugLoc_t\n{\n    int16_t x, y;\n    uint8_t cont_axes;\n};\n\nstruct rect_internal\n{\n    int16_t t, l, b, r;\n    uint8_t cont_axes;\n\n    class iterator\n    {\n        friend struct rect_internal;\n    private:\n        const rect_internal& parent;\n        AugLoc_t cur_loc;\n\n    public:\n        inline iterator(const rect_internal& parent, AugLoc_t cur_loc): parent(parent), cur_loc(cur_loc)\n        {}\n\n        inline iterator operator++()\n        {\n            this->cur_loc.y++;\n            this->cur_loc.cont_axes |= CONT_Y;\n\n            if(this->cur_loc.y == this->parent.b)\n            {\n                this->cur_loc.x++;\n\n                if(this->cur_loc.x != this->parent.r)\n                {\n                    // CONT_X for certain, CONT_Y if it's set by the parent\n                    this->cur_loc.cont_axes = CONT_X | this->parent.cont_axes;\n                    this->cur_loc.y = this->parent.t;\n                }\n            }\n\n            return *this;\n        }\n\n        inline bool operator!=(const iterator& end) const\n        {\n            // only check for inequality on X axis; that signals iteration is over\n            return this->cur_loc.x != end.cur_loc.x;\n        }\n\n        inline const AugLoc_t& operator*() const\n        {\n            return cur_loc;\n        }\n    };\n\n    inline iterator begin() const\n    {\n        iterator ret(*this, {l, t, cont_axes});\n\n        // if range is empty, return end instead\n        if(t == b)\n            ret.cur_loc.x = r;\n\n        return ret;\n    }\n\n    inline iterator end() const\n    {\n        return iterator(*this, {r, b, CONT_XY});\n    }\n};\n\nstruct rect_external\n{\n    int16_t l, r, t, b;\n\n    rect_external() {}\n\n    rect_external(int16_t l, int16_t r, int16_t t, int16_t b) : l(l), r(r), t(t), b(b) {}\n\n    rect_external(const Location_t& loc)\n    {\n        // find which 64x64 tiles contain the object\n        l = s_floor_div_64((int32_t)num_t::floor(loc.X));\n        r = s_floor_div_64((int32_t)num_t::ceil(loc.X + loc.Width) + 63);\n        t = s_floor_div_64((int32_t)num_t::floor(loc.Y));\n        b = s_floor_div_64((int32_t)num_t::ceil(loc.Y + loc.Height) + 63);\n    }\n\n    void query_from_loc_paranoid(const Location_t& loc)\n    {\n        // find which 64x64 tiles contain the query with a 32px margin in each direction\n        l = s_floor_div_64((int32_t)num_t::floor(loc.X) - 32);\n        r = s_floor_div_64((int32_t)num_t::ceil(loc.X + loc.Width) + 32 + 63);\n        t = s_floor_div_64((int32_t)num_t::floor(loc.Y) - 32);\n        b = s_floor_div_64((int32_t)num_t::ceil(loc.Y + loc.Height) + 32 + 63);\n    }\n\n    void query_from_loc_standard(const Location_t& loc)\n    {\n        // find which 64x64 tiles contain the query with a 2px margin in each direction\n        l = s_floor_div_64((int32_t)num_t::floor(loc.X) - 2);\n        r = s_floor_div_64((int32_t)num_t::ceil(loc.X + loc.Width) + 2 + 63);\n        t = s_floor_div_64((int32_t)num_t::floor(loc.Y) - 2);\n        b = s_floor_div_64((int32_t)num_t::ceil(loc.Y + loc.Height) + 2 + 63);\n    }\n};\n\n// a screen is 2048x2048.\n// it's made up of node_t's, which are 64x64 each.\nstruct screen_t\n{\n    std::array<node_t, 1024> nodes;\n\n    screen_t()\n    {}\n\n    void query(std::vector<BaseRef_t>& out, const rect_internal& rect)\n    {\n        for(const AugLoc_t& loc : rect)\n        {\n            for(AugBaseRef_t obj : nodes[loc.x * 32 + loc.y])\n            {\n                // only want objs that are new on all continued axes\n                if((obj.cont_axes & loc.cont_axes) == CONT_NONE)\n                    out.push_back(obj.ref);\n            }\n        }\n    }\n\n    void insert(BaseRef_t obj, const rect_internal& rect)\n    {\n        for(const AugLoc_t& loc : rect)\n            nodes[loc.x * 32 + loc.y].insert({obj, loc.cont_axes});\n    }\n\n    void erase(BaseRef_t obj, const rect_internal& rect)\n    {\n        for(const AugLoc_t& loc : rect)\n            nodes[loc.x * 32 + loc.y].erase(obj);\n    }\n};\n\ntemplate<class MyRef_t>\ninline Location_t extract_loc(MyRef_t obj)\n{\n    return static_cast<Location_t>(obj->Location);\n}\n\ntemplate<>\ninline Location_t extract_loc(WorldLevelRef_t obj)\n{\n    return obj->LocationOnscreen();\n}\n\ntemplate<>\ninline Location_t extract_loc(NPCRef_t obj)\n{\n    Location_t ret = obj->Location;\n\n    if(ret.Height < NPCHeight(obj->Type))\n        ret.Height = NPCHeight(obj->Type);\n    else if(ret.Height <= 0)\n        ret.Height = 1;\n\n    if(ret.Width < NPCWidthGFX(obj->Type))\n    {\n        ret.X -= (NPCWidthGFX(obj->Type) - ret.Width) / 2;\n        ret.Width = NPCWidthGFX(obj->Type);\n    }\n    else if(ret.Width <= 0)\n        ret.Width = 1;\n\n    // for tempBlock queries\n    if(obj->Type == NPCID_SPRING)\n    {\n        ret.Y -= 16;\n        ret.Height += 16;\n    }\n\n    return ret;\n}\n\ntemplate<class MyRef_t>\ninline Location_t extract_loc_layer(MyRef_t obj)\n{\n    Location_t loc = static_cast<Location_t>(obj->Location);\n\n    if(obj->Layer != LAYER_NONE)\n    {\n        loc.X -= Layer[obj->Layer].OffsetX;\n        loc.Y -= Layer[obj->Layer].OffsetY;\n    }\n\n    return loc;\n}\n\n// it's a bunch of stacks of screens, which are 2048x2048.\n// will be reallocated as needed\nstruct base_table_t\n{\n    typedef std::vector<screen_t*> screen_ptr_arr_t;\n    std::vector<screen_ptr_arr_t> columns;\n    std::vector<int> col_first_row_index;\n    std::unordered_map<BaseRef_t, rect_external> member_rects;\n    int first_col_index;\n\n    ~base_table_t()\n    {\n        clear();\n    }\n\nprotected:\n    void insert(BaseRef_t b, const rect_external& rect)\n    {\n        int lcol = s_floor_div_32(rect.l); // each column contains 32 cells\n        int rcol = s_floor_div_32(rect.r + 31); // ceiling, this column won't get checked\n\n        int trow = s_floor_div_32(rect.t);\n        int brow = s_floor_div_32(rect.b + 31);\n\n        if(columns.size() == 0)\n        {\n            columns.emplace_back(screen_ptr_arr_t());\n            col_first_row_index.resize(1);\n            first_col_index = lcol;\n        }\n\n        if(lcol < first_col_index)\n        {\n            int to_add = first_col_index - lcol;\n            for(int i = 0; i < to_add; i++)\n                columns.emplace_back(screen_ptr_arr_t());\n\n            std::rotate(columns.rbegin(), columns.rbegin() + to_add, columns.rend());\n\n            col_first_row_index.resize(columns.size() + to_add);\n            std::rotate(col_first_row_index.rbegin(), col_first_row_index.rbegin() + to_add, col_first_row_index.rend());\n\n            first_col_index = lcol;\n        }\n\n        if(rcol > first_col_index + (int)columns.size())\n        {\n            for(int i = first_col_index + (int)columns.size(); i < rcol; i++)\n                columns.emplace_back(screen_ptr_arr_t());\n\n            col_first_row_index.resize(rcol - first_col_index);\n        }\n\n        rect_internal inner_rect;\n\n        inner_rect.l = rect.l - lcol * 32;\n        inner_rect.r = 32;\n        inner_rect.cont_axes = CONT_NONE;\n\n        for(int col = lcol; col < rcol; col++)\n        {\n            int internal_col = col - first_col_index;\n\n            if(col == rcol - 1)\n                inner_rect.r = rect.r - (rcol - 1) * 32;\n\n            if(columns[internal_col].size() == 0)\n            {\n                columns[internal_col].push_back(new screen_t);\n                col_first_row_index[internal_col] = trow;\n            }\n\n            if(trow < col_first_row_index[internal_col])\n            {\n                int to_add = col_first_row_index[internal_col] - trow;\n                for(int i = 0; i < to_add; i++)\n                    columns[internal_col].push_back(new screen_t);\n                std::rotate(columns[internal_col].rbegin(), columns[internal_col].rbegin() + to_add, columns[internal_col].rend());\n                col_first_row_index[internal_col] = trow;\n            }\n\n            if(brow > col_first_row_index[internal_col] + (int)columns[internal_col].size())\n            {\n                for(int i = col_first_row_index[internal_col] + (int)columns[internal_col].size(); i < brow; i++)\n                    columns[internal_col].push_back(new screen_t);\n            }\n\n            inner_rect.t = rect.t - trow * 32;\n            inner_rect.b = 32;\n\n            for(int row = trow; row < brow; row++)\n            {\n                int internal_row = row - col_first_row_index[internal_col];\n\n                if(row == brow - 1)\n                    inner_rect.b = rect.b - (brow - 1) * 32;\n\n                columns[internal_col][internal_row]->insert(b, inner_rect);\n\n                inner_rect.t = 0;\n                inner_rect.cont_axes |= CONT_Y;\n            }\n\n            inner_rect.l = 0;\n            inner_rect.cont_axes = CONT_X;\n        }\n    }\n\n    void erase(BaseRef_t b, const rect_external& rect)\n    {\n        int lcol = s_floor_div_32(rect.l); // each column contains 32 cells\n        int rcol = s_floor_div_32(rect.r + 31); // ceiling, this column won't get checked\n\n        int trow = s_floor_div_32(rect.t);\n        int brow = s_floor_div_32(rect.b + 31);\n\n        int lcol_check = SDL_max(lcol, first_col_index);\n        int rcol_check = SDL_min(rcol, first_col_index + (int)columns.size());\n\n        rect_internal inner_rect;\n\n        // initialize l/r bounds of inner query rect\n        inner_rect.l = rect.l - lcol * 32;\n        inner_rect.r = 32;\n        inner_rect.cont_axes = CONT_NONE;\n\n        if(lcol_check != lcol)\n        {\n            inner_rect.l = 0;\n            inner_rect.cont_axes = CONT_X;\n        }\n\n        for(int col = lcol_check; col < rcol_check; col++)\n        {\n            int internal_col = col - first_col_index;\n\n            // it's 32 until this point\n            if(col == rcol - 1)\n                inner_rect.r = rect.r - (rcol - 1) * 32;\n\n            // initialize t/b bounds of inner query rect\n            inner_rect.t = rect.t - trow * 32;\n            inner_rect.b = 32;\n\n            int trow_check = SDL_max(trow, col_first_row_index[internal_col]);\n            int brow_check = SDL_min(brow, col_first_row_index[internal_col] + (int)columns[internal_col].size());\n            if(trow_check != trow)\n            {\n                inner_rect.t = 0;\n                inner_rect.cont_axes |= CONT_Y;\n            }\n\n            for(int row = trow_check; row < brow_check; row++)\n            {\n                int internal_row = row - col_first_row_index[internal_col];\n\n                if(row == brow - 1)\n                    inner_rect.b = rect.b - (brow - 1) * 32;\n\n                columns[internal_col][internal_row]->erase(b, inner_rect);\n\n                inner_rect.t = 0;\n                inner_rect.cont_axes |= CONT_Y;\n            }\n\n            inner_rect.l = 0;\n            inner_rect.cont_axes = CONT_X;\n        }\n    }\n\npublic:\n    void query(std::vector<BaseRef_t>& out, const rect_external& rect)\n    {\n        if(columns.size() == 0 || member_rects.size() == 0)\n            return;\n\n        int lcol = s_floor_div_32(rect.l); // each column contains 32 cells\n        int rcol = s_floor_div_32(rect.r + 31); // ceiling, this column won't get checked\n\n        int trow = s_floor_div_32(rect.t);\n        int brow = s_floor_div_32(rect.b + 31);\n\n        int lcol_check = SDL_max(lcol, first_col_index);\n        int rcol_check = SDL_min(rcol, first_col_index + (int)columns.size());\n\n        rect_internal inner_rect;\n\n        // initialize l/r bounds of inner query rect\n        inner_rect.l = rect.l - lcol * 32;\n        inner_rect.r = 32;\n        inner_rect.cont_axes = CONT_NONE;\n\n        if(lcol_check != lcol)\n        {\n            inner_rect.l = 0;\n            inner_rect.cont_axes = CONT_X;\n        }\n\n        for(int col = lcol_check; col < rcol_check; col++)\n        {\n            int internal_col = col - first_col_index;\n\n            if(columns[internal_col].size() == 0)\n            {\n                inner_rect.l = 0;\n                inner_rect.cont_axes = CONT_X;\n                continue;\n            }\n\n            // it's 32 until this point\n            if(col == rcol - 1)\n                inner_rect.r = rect.r - (rcol - 1) * 32;\n\n            // initialize t/b bounds of inner query rect\n            inner_rect.t = rect.t - trow * 32;\n            inner_rect.b = 32;\n\n            int trow_check = SDL_max(trow, col_first_row_index[internal_col]);\n            int brow_check = SDL_min(brow, col_first_row_index[internal_col] + (int)columns[internal_col].size());\n            if(trow_check != trow)\n            {\n                inner_rect.t = 0;\n                inner_rect.cont_axes |= CONT_Y;\n            }\n\n            for(int row = trow_check; row < brow_check; row++)\n            {\n                int internal_row = row - col_first_row_index[internal_col];\n\n                if(row == brow - 1)\n                    inner_rect.b = rect.b - (brow - 1) * 32;\n\n                columns[internal_col][internal_row]->query(out, inner_rect);\n\n                inner_rect.t = 0;\n                inner_rect.cont_axes |= CONT_Y;\n            }\n\n            inner_rect.l = 0;\n            inner_rect.cont_axes = CONT_X;\n        }\n    }\n\n    void clear()\n    {\n        // can optimize later\n        for(auto col : columns)\n        {\n            for(screen_t* screen : col)\n                delete screen;\n        }\n\n        columns.clear();\n        col_first_row_index.clear();\n        member_rects.clear();\n    }\n\n    void clear_light()\n    {\n        for(const auto& p : member_rects)\n            base_table_t::erase(p.first, p.second);\n\n        member_rects.clear();\n    }\n};\n\ntemplate<class MyRef_t>\nstruct table_t : public base_table_t\n{\n    void insert(MyRef_t b)\n    {\n        Location_t loc = extract_loc<MyRef_t>(b);\n\n        // ignore improper rects\n        if(loc.Width < 0 || loc.Height < 0)\n            return;\n\n        rect_external rect(loc);\n        member_rects[b] = rect;\n        base_table_t::insert(b, rect);\n    }\n\n    void insert_layer(MyRef_t b)\n    {\n        Location_t loc = extract_loc_layer<MyRef_t>(b);\n\n        // ignore improper rects\n        if(loc.Width < 0 || loc.Height < 0)\n            return;\n\n        rect_external rect(loc);\n        member_rects[b] = rect;\n        base_table_t::insert(b, rect);\n    }\n\n    void erase(MyRef_t b)\n    {\n        auto it = member_rects.find(b);\n        if(it == member_rects.end())\n            return;\n\n        base_table_t::erase(b, it->second);\n        member_rects.erase(it);\n    }\n\n    bool update(MyRef_t b)\n    {\n        Location_t loc = extract_loc<MyRef_t>(b);\n\n        rect_external rect(loc);\n\n        auto it = member_rects.find(b);\n        if(it != member_rects.end())\n        {\n            // no-change optimization\n            if(it->second.l == rect.l\n                && it->second.r == rect.r\n                && it->second.t == rect.t\n                && it->second.b == rect.b)\n            {\n                return false;\n            }\n\n            base_table_t::erase(b, it->second);\n        }\n\n        // ignore improper rects\n        if(loc.Width < 0 || loc.Height < 0)\n            return true;\n\n        member_rects[b] = rect;\n        base_table_t::insert(b, rect);\n\n        return true;\n    }\n\n    void update_layer(MyRef_t b)\n    {\n        auto it = member_rects.find(b);\n        if(it != member_rects.end())\n            base_table_t::erase(b, it->second);\n\n        insert_layer(b);\n    }\n};\n\ninline void trees_sort_by_index(std::vector<BaseRef_t>& i_vec)\n{\n    pdqsort(i_vec.begin(), i_vec.end(),\n        [](BaseRef_t a, BaseRef_t b) {\n            return a < b;\n        });\n}\n\n#endif // #ifndef BLOCK_TABLE_IMPL_HPP\n"
  },
  {
    "path": "src/main/cheat_code.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <vector>\n\n#ifdef ENABLE_ANTICHEAT_TRAP\n#include \"core/msgbox.h\"\n#include \"core/render.h\"\n#include \"core/events.h\"\n#endif\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include <Logger/logger.h>\n#ifdef ENABLE_ANTICHEAT_TRAP\n#include <pge_delay.h>\n#endif\n\n#include \"globals.h\"\n#include \"sound.h\"\n#include \"graphics.h\"\n#include \"collision.h\"\n#include \"effect.h\"\n#include \"player.h\"\n#include \"npc.h\"\n#include \"layers.h\"\n#include \"controls.h\"\n#include \"game_main.h\"\n#include \"change_res.h\"\n#include \"config.h\"\n#ifdef ENABLE_ANTICHEAT_TRAP\n#include \"editor.h\" // For the `Backup_FullFileName` only\n#endif\n#include \"frm_main.h\"\n\n#include \"core/render.h\"\n\n#include \"npc/npc_queues.h\"\n\n#include \"main/game_info.h\"\n#include \"main/screen_quickreconnect.h\"\n#include \"main/screen_textentry.h\"\n\n#include \"main/trees.h\"\n\n#include \"main/cheat_code.h\"\n\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n#include \"eff_id.h\"\n\nbool g_ForceBitmaskMerge = false;\nbool g_CheatLogicScreen = false;\nint g_CheatEditYourFriends = 0;\n\nstatic void redigitIsCool()\n{\n    PlaySound(SFX_SMCry);\n    Cheater = false;\n}\n\n\n#ifdef ENABLE_ANTICHEAT_TRAP\nstatic void dieCheater()\n{\n    if(LevelEditor || WorldEditor || BattleMode || TestLevel || !Backup_FullFileName.empty())\n    {\n        // Don't perform the punish when running a level test or the editor\n        redigitIsCool();\n        return;\n    }\n\n    pLogCritical(\"redigitiscool code was been used, player got a punish!\");\n    PlaySound(SFX_SMExplosion);\n    Score = 0; // Being very evil here, mu-ha-ha-ha-ha! >:D\n    Lives = 0;\n    g_100s = 0;\n    Coins = 0;\n    GodMode = false;\n    ClearGame(true); // As a penalty, remove the saved game\n    Cheater = true;\n    CheaterMustDie = true;\n    cheats_clearBuffer();\n\n    if(!LevelSelect)\n    {\n        MessageText = \"       Die, cheater!       \"\n                      \"Now play the game all over \"\n                      \"    from the beginning!    \"\n                      \"                           \"\n                      \"     Time to be evil!      \"\n                      \"      Mu-ha-ha-ha-ha!      \";\n        PauseGame(PauseCode::Message);\n        MessageText.clear();\n    }\n    else\n    {\n        XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_ERROR,\n                              \"Die, cheater!\",\n                              \"       Die, cheater!       \\n\"\n                              \"Now play the game all over \\n\"\n                              \"    from the beginning!    \\n\"\n                              \"                           \\n\"\n                              \"     Time to be evil!      \\n\"\n                              \"      Mu-ha-ha-ha-ha!      \");\n    }\n\n    for(int A = 1; A <= numPlayers; ++A)\n    {\n        Player[A].State = 0;\n        Player[A].Hearts = 1;\n        if(!LevelSelect)\n            PlayerHurt(A);\n        else\n            KillPlayer(A);\n    }\n\n    if(LevelSelect)\n    {\n        LevelSelect = false;\n        GameMenu = true;\n        MenuMode = 0;\n        MenuCursor = 0;\n        XRender::clearBuffer();\n        XRender::repaint();\n        StopMusic();\n        XEvents::doEvents();\n\n        if(!g_config.unlimited_framerate)\n            PGE_Delay(500);\n    }\n}\n#endif\n\n\n\n/* -----------------------------------*\n *      World map only cheats         *\n *------------------------------------*/\n\n/*!\n * \\brief Open all paths\n */\nstatic void moonWalk()\n{\n    TinyLocation_t tempLocation;\n\n    for(int B = 1; B <= numWorldPaths; B++)\n    {\n        tempLocation = WorldPath[B].Location;\n        tempLocation.X += 4;\n        tempLocation.Y += 4;\n        tempLocation.Width -= 8;\n        tempLocation.Height -= 8;\n        WorldPath[B].Active = true;\n\n        for(int C = 1; C <= numScenes; C++)\n        {\n            if(CheckCollision(tempLocation, Scene[C].Location))\n                Scene[C].Active = false;\n        }\n    }\n\n    for(int B = 1; B <= numWorldLevels; B++)\n        WorldLevel[B].Active = true;\n\n    PlaySound(SFX_NewPath);\n}\n\n/*!\n * \\brief Opens all exits for current world level\n */\nstatic void openSesame()\n{\n    bool found = false;\n\n    for(WorldLevel_t &lev : treeWorldLevelQuery(WorldPlayer[1].Location, SORTMODE_ID))\n    {\n        if(CheckCollision(WorldPlayer[1].Location, lev.Location))\n        {\n            found = true;\n\n            for(int A = 1; A <= 4; A++)\n                LevelPath(lev, A);\n        }\n    }\n\n    if(!found)\n        PlaySound(SFX_BlockHit);\n}\n\n/*!\n * \\brief Allow player walk everywhere\n */\nstatic void illParkWhereIWant()\n{\n    if(WalkAnywhere)\n    {\n        WalkAnywhere = false;\n        PlaySound(SFX_PlayerShrink);\n    }\n    else\n    {\n        WalkAnywhere = true;\n        PlaySound(SFX_PlayerGrow);\n    }\n}\n\n\n/* -----------------------------------*\n *        Level only cheats           *\n *------------------------------------*/\n\n\nstatic void fairyMagic()\n{\n    if(Player[1].Fairy)\n    {\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            PlaySound(SFX_HeroFairy);\n            Player[B].Immune = 10;\n            Player[B].Effect = PLREFF_WAITING;\n            Player[B].Effect2 = 4;\n            Player[B].Fairy = false;\n            Player[B].FairyTime = 0;\n            SizeCheck(Player[B]);\n            NewEffect(EFFID_SMOKE_S5, Player[B].Location);\n        }\n    }\n    else\n    {\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            PlaySound(SFX_HeroFairy);\n            Player[B].Immune = 10;\n            Player[B].Effect = PLREFF_WAITING;\n            Player[B].Effect2 = 4;\n            Player[B].Fairy = true;\n            Player[B].FairyTime = -1;\n            SizeCheck(Player[B]);\n            NewEffect(EFFID_SMOKE_S5, Player[B].Location);\n        }\n    }\n}\n\nstatic void iceAge()\n{\n    for(int C : NPCQueues::Active.may_insert_erase)\n    {\n        if(NPC[C].Active)\n        {\n            if(!NPC[C]->NoIceBall && NPC[C].Type != NPCID_ICE_CUBE && !NPC[C]->IsABonus)\n            {\n                NPC[0].Type = NPCID_PLR_ICEBALL;\n                NPCHit(C, 3, 0);\n            }\n        }\n    }\n\n    PlaySound(SFX_Transform);\n}\n\nstatic void iStillPlayWithLegos()\n{\n    ShowLayer(LAYER_DESTROYED_BLOCKS);\n    PlaySound(SFX_Transform);\n}\n\nstatic void itsRainingMen()\n{\n    for(int C = 1; C <= numPlayers; C++)\n    {\n        for(int B = -100; B <= 900; B += 34)\n        {\n            numNPCs++;\n            NPC[numNPCs].Type = NPCID_LIFE_S3;\n            NPC[numNPCs].Location.Y = Player[C].Location.Y - 600;\n            NPC[numNPCs].Location.X = Player[C].Location.X - 400 + B;\n            NPC[numNPCs].Location.Height = 32;\n            NPC[numNPCs].Location.Width = 32;\n            NPC[numNPCs].Stuck = true;\n            NPC[numNPCs].Active = true;\n            NPC[numNPCs].TimeLeft = 200;\n            NPC[numNPCs].Section = Player[C].Section;\n            syncLayers_NPC(numNPCs);\n        }\n    }\n\n    PlaySound(SFX_Transform);\n}\n\nstatic void dontTypeThis()\n{\n    for(int C = 1; C <= numPlayers; C++)\n    {\n        for(int B = -100; B <= 900; B += 34)\n        {\n            numNPCs++;\n            NPC[numNPCs].Type = NPCID_BOMB;\n            NPC[numNPCs].Location.Y = Player[C].Location.Y - 600;\n            NPC[numNPCs].Location.X = Player[C].Location.X - 400 + B;\n            NPC[numNPCs].Location.Height = 32;\n            NPC[numNPCs].Location.Width = 32;\n            NPC[numNPCs].Stuck = true;\n            NPC[numNPCs].Active = true;\n            NPC[numNPCs].TimeLeft = 200;\n            NPC[numNPCs].Section = Player[C].Section;\n            syncLayers_NPC(numNPCs);\n        }\n    }\n\n    PlaySound(SFX_Transform);\n}\n\nstatic void wetWater()\n{\n    for(int B = 1; B <= numEffects; B++)\n    {\n        if(Effect[B].Type == EFFID_AIR_BUBBLE)\n            Effect[B].Life = 0;\n    }\n\n    for(int B = 0; B < numSections; B++)\n    {\n        if(UnderWater[B])\n        {\n            UnderWater[B] = false;\n\n            if(Background2REAL[B] == 55)\n                Background2[B] = 30;\n            else if(Background2REAL[B] == 56)\n                Background2[B] = 39;\n            else\n                Background2[B] = Background2REAL[B];\n\n            if(bgMusicREAL[B] == 46)\n            {\n                Background2[B] = 8;\n                bgMusic[B] = 7;\n            }\n            else if(bgMusicREAL[B] == 47)\n            {\n                Background2[B] = 39;\n                bgMusic[B] = 4;\n            }\n            else if(bgMusicREAL[B] == 48)\n            {\n                Background2[B] = 30;\n                bgMusic[B] = 29;\n            }\n            else if(bgMusicREAL[B] == 49)\n            {\n                Background2[B] = 30;\n                bgMusic[B] = 50;\n            }\n            else\n                bgMusic[B] = bgMusicREAL[B];\n        }\n        else\n        {\n            UnderWater[B] = true;\n\n            if(Background2REAL[B] != 55 && Background2REAL[B] != 56)\n            {\n                if(Background2REAL[B] == 12 || Background2REAL[B] == 13 || Background2REAL[B] == 19 || Background2REAL[B] == 29 ||\n                   Background2REAL[B] == 30 || Background2REAL[B] == 31 || Background2REAL[B] == 32 || Background2REAL[B] == 33 ||\n                   Background2REAL[B] == 34 || Background2REAL[B] == 42 || Background2REAL[B] == 43)\n                    Background2[B] = 55;\n                else\n                    Background2[B] = 56;\n            }\n            else\n                Background2[B] = Background2REAL[B];\n\n            if(bgMusicREAL[B] < 46 || bgMusicREAL[B] > 49)\n            {\n                if(bgMusic[B] == 7 || bgMusic[B] == 9 || bgMusic[B] == 42)\n                    bgMusic[B] = 46;\n                else if(bgMusic[B] == 1 || bgMusic[B] == 2 || bgMusic[B] == 3 || bgMusic[B] == 4 || bgMusic[B] == 6 || bgMusic[B] == 54)\n                    bgMusic[B] = 47;\n                else if(bgMusic[B] == 10 || bgMusic[B] == 17 || bgMusic[B] == 28 || bgMusic[B] == 29 || bgMusic[B] == 41 || bgMusic[B] == 51)\n                    bgMusic[B] = 48;\n                else if(bgMusic[B] == 14 || bgMusic[B] == 26 || bgMusic[B] == 27 || bgMusic[B] == 35 || bgMusic[B] == 36 || bgMusic[B] == 50)\n                    bgMusic[B] = 49;\n                else\n                    bgMusic[B] = 18;\n            }\n            else\n                bgMusic[B] = bgMusicREAL[B];\n        }\n    }\n\n    PlaySound(SFX_Transform);\n\n    for(int i = 0; i < l_screen->player_count; i++)\n    {\n        int B = l_screen->players[i];\n\n        if(!Player[B].Dead && Player[B].TimeToLive == 0)\n        {\n            StopMusic();\n            StartMusic(Player[B].Section);\n            break;\n        }\n    }\n}\n\nstatic void grantPowerup(NPCID npcid, PlayerState state)\n{\n    PlaySound(SFX_GotItem);\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        Player[B].HeldBonus = npcid;\n\n        if((Player[B].Character >= 3 || g_config.alt_powerdown) && Player[B].State != state)\n        {\n            PlaySound(SFX_Transform);\n            Player[B].Immune = 30;\n            Player[B].Effect = PLREFF_WAITING;\n            Player[B].Effect2 = 4;\n            Player[B].State = state;\n            SizeCheck(Player[B]);\n            NewEffect(EFFID_SMOKE_S3_CENTER, Player[B].Location);\n        }\n\n        if(Player[B].Character >= 3 && Player[B].Hearts < 3)\n            Player[B].Hearts += 1;\n    }\n}\n\nstatic void needATanookiSuit()\n{\n    grantPowerup(NPCID_STATUE_POWER, PLR_STATE_STATUE);\n}\n\nstatic void needAHammerSuit()\n{\n    grantPowerup(NPCID_HEAVY_POWER, PLR_STATE_HEAVY);\n}\n\nstatic void needAMushroom()\n{\n    PlaySound(SFX_GotItem);\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        Player[B].HeldBonus = NPCID_POWER_S3;\n\n        if(Player[B].Character >= 3 && Player[B].State == 1)\n        {\n            PlaySound(SFX_Transform);\n            Player[B].Immune = 30;\n            Player[B].Effect = PLREFF_WAITING;\n            Player[B].Effect2 = 4;\n            Player[B].State = 2;\n            SizeCheck(Player[B]);\n            NewEffect(EFFID_SMOKE_S3_CENTER, Player[B].Location);\n        }\n\n        if(Player[B].Character >= 3 && Player[B].Hearts < 3)\n            Player[B].Hearts += 1;\n    }\n}\n\nstatic void needAFlower()\n{\n    grantPowerup(NPCID_FIRE_POWER_S3, PLR_STATE_FIRE);\n}\n\nstatic void needAnIceFlower()\n{\n    grantPowerup(NPCID_ICE_POWER_S3, PLR_STATE_ICE);\n}\n\nstatic void needALeaf()\n{\n    grantPowerup(NPCID_LEAF_POWER, PLR_STATE_LEAF);\n}\n\nstatic void needACyclone()\n{\n    grantPowerup(NPCID_CYCLONE_POWER, PLR_STATE_CYCLONE);\n}\n\nstatic void goodTimesRoll()\n{\n    grantPowerup(NPCID_SHELL_POWER, PLR_STATE_SHELL);\n}\n\nstatic void wayPastCool()\n{\n    grantPowerup(NPCID_POLAR_POWER, PLR_STATE_POLAR);\n}\n\nstatic void sushiTime()\n{\n    grantPowerup(NPCID_AQUATIC_POWER, PLR_STATE_AQUATIC);\n}\n\nstatic void grantItemBox(NPCID npcid)\n{\n    PlaySound(SFX_GotItem);\n    for(int B = 1; B <= numPlayers; B++)\n        Player[B].HeldBonus = npcid;\n}\n\nstatic void needAShell()\n{\n    grantItemBox(NPCID_GRN_SHELL_S4);\n}\n\nstatic void needARedShell()\n{\n    grantItemBox(NPCID_RED_SHELL_S4);\n}\n\nstatic void needABlueShell()\n{\n    grantItemBox(NPCID_BLU_SHELL_S4);\n}\n\nstatic void needAYellowShell()\n{\n    grantItemBox(NPCID_YEL_SHELL_S4);\n}\n\nstatic void needATurnip()\n{\n    grantItemBox(NPCID_VEGGIE_1);\n}\n\nstatic void needA1Up()\n{\n    grantItemBox(NPCID_LIFE_S3);\n}\n\nstatic void needANegg()\n{\n    grantItemBox(NPCID_ITEM_POD);\n}\n\nstatic void needAPlant()\n{\n    grantItemBox(NPCID_TOOTHYPIPE);\n}\n\nstatic void needAGun()\n{\n    grantItemBox(NPCID_CANNONITEM);\n}\n\nstatic void needASwitch()\n{\n    grantItemBox(NPCID_COIN_SWITCH);\n}\n\nstatic void needAClock()\n{\n    grantItemBox(NPCID_TIMER_S3);\n}\n\nstatic void needABomb()\n{\n    grantItemBox(NPCID_WALK_BOMB_S2);\n}\n\nstatic void needAShoe()\n{\n    grantItemBox(NPCID_GRN_BOOT);\n}\n\nstatic void redShoe()\n{\n    grantItemBox(NPCID_RED_BOOT);\n}\n\nstatic void blueShoe()\n{\n    grantItemBox(NPCID_BLU_BOOT);\n}\n\nstatic void shadowStar()\n{\n    PlaySound(SFX_Transform);\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        Player[B].Immune = 50;\n        NewEffect(EFFID_SMOKE_S3_CENTER, Player[B].Location);\n    }\n\n    ShadowMode = !ShadowMode;\n\n    //if(ShadowMode)\n    //    ShadowMode = false;\n    //else\n    //    ShadowMode = true;\n}\n\nstatic void s_heightFix(Player_t& p)\n{\n    if(p.Mount <= 1)\n    {\n        int new_height = Physics.PlayerHeight[p.Character][p.State];\n        if(p.Mount == 1 && p.State == 1)\n            new_height = Physics.PlayerHeight[1][2];\n\n        p.Location.set_height_floor(new_height);\n        p.StandUp = true;\n    }\n}\n\nstatic void changeCharCheat(int new_char)\n{\n    PlaySound(SFX_Transform);\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        Player[B].Character = new_char;\n        Player[B].Immune = 50;\n\n        s_heightFix(Player[B]);\n\n        NewEffect(EFFID_SMOKE_S3_CENTER, Player[B].Location);\n    }\n\n    UpdateYoshiMusic();\n}\n\nstatic void becomeAsPeach()\n{\n    changeCharCheat(3);\n}\n\nstatic void becomeAsToad()\n{\n    changeCharCheat(4);\n}\n\nstatic void becomeAsLink()\n{\n    changeCharCheat(5);\n}\n\nstatic void becomeAsMario()\n{\n    changeCharCheat(1);\n}\n\nstatic void becomeAsLuigi()\n{\n    changeCharCheat(2);\n}\n\nstatic int findLivingForCheat()\n{\n    if(!g_ClonedPlayerMode && numPlayers != Screens[0].player_count)\n    {\n        PlaySound(SFX_BlockHit);\n        return false;\n    }\n\n    return CheckLiving();\n}\n\nstatic void setScreenPlayers(int count)\n{\n    Screens[0].player_count = count;\n    Screens[0].players = {1, 2, 3, 4};\n    UpdateScreenPlayers();\n}\n\nstatic void superbDemo200()\n{\n    int B = findLivingForCheat();\n    if(B > 0)\n    {\n        numPlayers = 200;\n        g_ClonedPlayerMode = true;\n        setScreenPlayers(1);\n\n        SetupScreens();\n\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n        Player[B].Immune = 1;\n\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * 24 - 12;\n            }\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void superbDemo128()\n{\n    int B = findLivingForCheat();\n    if(B > 0)\n    {\n        numPlayers = 128;\n        g_ClonedPlayerMode = true;\n        setScreenPlayers(1);\n\n        SetupScreens();\n\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n        Player[B].Immune = 1;\n\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * 24 - 12;\n            }\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void superbDemo64()\n{\n    int B = findLivingForCheat();\n    if(B > 0)\n    {\n        numPlayers = 64;\n        g_ClonedPlayerMode = true;\n        setScreenPlayers(1);\n\n        SetupScreens();\n\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n        Player[B].Immune = 1;\n\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * 24 - 12;\n            }\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void superbDemo32()\n{\n    int B = findLivingForCheat();\n    if(B > 0)\n    {\n        numPlayers = 32;\n        g_ClonedPlayerMode = true;\n        setScreenPlayers(1);\n\n        SetupScreens();\n\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n\n        Player[B].Immune = 1;\n\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * 24 - 12;\n            }\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void superbDemo16()\n{\n    int B = findLivingForCheat();\n    if(B > 0)\n    {\n        numPlayers = 16;\n        g_ClonedPlayerMode = true;\n        setScreenPlayers(1);\n\n        SetupScreens();\n\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n\n        Player[B].Immune = 1;\n\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * 24 - 12;\n            }\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void superbDemo8()\n{\n    int B = findLivingForCheat();\n    if(B > 0)\n    {\n        numPlayers = 8;\n        g_ClonedPlayerMode = true;\n        setScreenPlayers(1);\n\n        SetupScreens();\n\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n\n        Player[B].Immune = 1;\n\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * 24 - 12;\n            }\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void superbDemo4()\n{\n    int B = findLivingForCheat();\n    if(B > 0)\n    {\n        numPlayers = 4;\n        g_ClonedPlayerMode = true;\n        setScreenPlayers(1);\n\n        SetupScreens();\n\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n\n        Player[B].Immune = 1;\n\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * 24 - 12;\n            }\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void superbDemo2()\n{\n    int B = findLivingForCheat();\n\n    if(B > 0)\n    {\n        g_ClonedPlayerMode = false;\n        numPlayers = 2;\n        SingleCoop = 1;\n        setScreenPlayers(2);\n\n        SetupScreens();\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n        Player[B].Immune = 1;\n\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * 24 - 12;\n            }\n\n            if(C == 1)\n            {\n                Player[C].Character = 1;\n                s_heightFix(Player[C]);\n            }\n            else\n            {\n                Player[C].Character = 2;\n                s_heightFix(Player[C]);\n            }\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void onePlayer()\n{\n    int B = findLivingForCheat();\n\n    if(B > 0)\n    {\n        for(int C = 1; C <= numPlayers; C++)\n            Player[C].Immune = 1;\n\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Bomb(Player[C].Location, iRand(2) + 2);\n            }\n        }\n\n        // set the living player to get the controls if not P1\n        if(!g_ClonedPlayerMode && !SingleCoop && B - 1 < (int)Controls::g_InputMethods.size() && Controls::g_InputMethods[B-1])\n            std::swap(Controls::g_InputMethods[0], Controls::g_InputMethods[B-1]);\n\n        // delete other control methods\n        while(Controls::g_InputMethods.size() > 1)\n            Controls::DeleteInputMethodSlot(1);\n\n        numPlayers = 1;\n        SingleCoop = 1;\n        g_ClonedPlayerMode = false;\n\n        setScreenPlayers(1);\n        SetupScreens();\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n\n        // move the living player into slot 1\n        int C = 1;\n        Player[C] = Player[B];\n        Player[C].Character = 1;\n        s_heightFix(Player[C]);\n\n        Player[C].Immune = 1;\n        Player[C].Immune2 = true;\n    }\n}\n\nstatic void twoPlayer()\n{\n    int B = findLivingForCheat();\n    if(B > 0)\n    {\n        numPlayers = 2;\n\n        // setup so there are exactly two controller slots,\n        // activate quick-reconnect if needed\n        while(Controls::g_InputMethods.size() > 2)\n        {\n            Controls::DeleteInputMethodSlot(2);\n        }\n        if(Controls::g_InputMethods.size() == 1)\n        {\n            Controls::g_InputMethods.push_back(nullptr);\n            QuickReconnectScreen::g_active = true;\n        }\n\n        SingleCoop = 0;\n        g_ClonedPlayerMode = false;\n        setScreenPlayers(2);\n        SetupScreens();\n\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n\n        Player[B].Immune = 1;\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * -12;\n            }\n\n            Player[C].Character = C;\n            s_heightFix(Player[C]);\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void fourPlayer()\n{\n    int B = findLivingForCheat();\n    if(B > 0)\n    {\n        numPlayers = 4;\n\n        // setup so there are exactly two controller slots,\n        // activate quick-reconnect if needed\n        while(Controls::g_InputMethods.size() > 4)\n        {\n            Controls::DeleteInputMethodSlot(4);\n        }\n        while(Controls::g_InputMethods.size() < 4)\n        {\n            Controls::g_InputMethods.push_back(nullptr);\n            QuickReconnectScreen::g_active = true;\n        }\n\n        SingleCoop = 0;\n        g_ClonedPlayerMode = false;\n        setScreenPlayers(4);\n\n        if(Player[B].Effect == PLREFF_NO_COLLIDE)\n            Player[B].Effect = PLREFF_NORMAL;\n\n        Player[B].Immune = 1;\n        for(int C = 1; C <= numPlayers; C++)\n        {\n            if(C != B)\n            {\n                Player[C] = Player[B];\n                Player[C].Location.SpeedY = dRand() * -12;\n            }\n\n            Player[C].Character = C;\n\n            if(Player[C].Character > 2 && Player[C].Mount == 3)\n            {\n                Player[C].Mount = 1;\n                Player[C].MountType = 1;\n            }\n\n            s_heightFix(Player[C]);\n        }\n\n        Bomb(Player[B].Location, iRand(2) + 2);\n    }\n}\n\nstatic void fourShared()\n{\n    fourPlayer();\n\n    {\n        ConfigChangeSentinel sent(ConfigSetLevel::cheat);\n        g_config.four_screen_mode = MultiplayerPrefs::Shared;\n    }\n}\n\nstatic void fourSplit()\n{\n    fourPlayer();\n\n    {\n        ConfigChangeSentinel sent(ConfigSetLevel::cheat);\n        g_config.four_screen_mode = MultiplayerPrefs::Split;\n    }\n}\n\nstatic void warioTime()\n{\n    for(int B : NPCQueues::Active.no_change)\n    {\n        if(NPC[B].Active)\n        {\n            if(!NPC[B]->WontHurt &&\n               !NPC[B]->IsABlock &&\n               !NPC[B]->IsABonus &&\n               !NPC[B]->IsACoin &&\n               !NPCIsAnExit(NPC[B]) &&\n                NPC[B].Type != NPCID_ITEM_BURIED && !NPC[B].Generator &&\n               !NPC[B].Inert\n            )\n            {\n                PlaySound(SFX_Transform);\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[B].Location);\n                NPC[B].Type = NPCID_COIN_S3;\n                NPC[B].Location.set_width_center(NPC[B]->TWidth);\n                NPC[B].Location.set_height_center(NPC[B]->THeight);\n                NPC[B].Location.SpeedX = 0;\n                NPC[B].Location.SpeedY = 0;\n\n                treeNPCUpdate(B);\n                NPCQueues::Unchecked.push_back(B);\n            }\n        }\n    }\n}\n\nstatic void grantItemHeld(NPCID npcid, NPCEffect eff = NPCEFF_NORMAL, int special = 0)\n{\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        if(Player[B].Mount == 0 && !Player[B].Dead && Player[B].TimeToLive == 0 && Player[B].Effect == PLREFF_NORMAL)\n        {\n            numNPCs++;\n            NPC[numNPCs].Type = npcid;\n            NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n            NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n            NPC[numNPCs].Location.X = Player[B].Location.X;\n            NPC[numNPCs].Location.Y = Player[B].Location.Y;\n            NPC[numNPCs].Location.SpeedX = 0;\n            NPC[numNPCs].Location.SpeedY = 0;\n            NPC[numNPCs].Effect = eff;\n            NPC[numNPCs].Special = special;\n\n            // for the pods\n            if(special)\n                NPCFrames(numNPCs);\n\n            NPC[numNPCs].Active = true;\n            NPC[numNPCs].TimeLeft = 200;\n            NPC[numNPCs].HoldingPlayer = B;\n\n            syncLayers_NPC(numNPCs);\n            CheckSectionNPC(numNPCs);\n\n            Player[B].HoldingNPC = numNPCs;\n            Player[B].ForceHold = 60;\n            PlaySound(SFX_Grab);\n        }\n    }\n}\n\nstatic void carKeys()\n{\n    grantItemHeld(NPCID_KEY);\n}\n\nstatic void boingyBoing()\n{\n    grantItemHeld(NPCID_SPRING);\n}\n\nstatic void bombsAway()\n{\n    grantItemHeld(NPCID_BOMB);\n}\n\nstatic void fireMissiles()\n{\n    grantItemHeld(NPCID_BULLET);\n}\n\nstatic void hellFire()\n{\n    grantItemHeld(NPCID_FLY_CANNON);\n}\n\nstatic void upAndOut()\n{\n    grantItemHeld(NPCID_FLY_BLOCK);\n}\n\nstatic void powHammer()\n{\n    grantItemHeld(NPCID_EARTHQUAKE_BLOCK);\n}\n\nstatic void hammerInMyPants()\n{\n    grantItemHeld(NPCID_HEAVY_THROWER);\n}\n\nstatic void rainbowRider()\n{\n    grantItemHeld(NPCID_FLIPPED_RAINBOW_SHELL, NPCEFF_DROP_ITEM);\n}\n\nstatic void cantTouchThis()\n{\n    grantItemHeld(NPCID_INVINCIBILITY_POWER);\n}\n\nstatic void greenEgg()\n{\n    grantItemHeld(NPCID_ITEM_POD, NPCEFF_DROP_ITEM, NPCID_PET_GREEN);\n}\n\nstatic void blueEgg()\n{\n    grantItemHeld(NPCID_ITEM_POD, NPCEFF_DROP_ITEM, NPCID_PET_BLUE);\n}\n\nstatic void yellowEgg()\n{\n    grantItemHeld(NPCID_ITEM_POD, NPCEFF_DROP_ITEM, NPCID_PET_YELLOW);\n}\n\nstatic void redEgg()\n{\n    grantItemHeld(NPCID_ITEM_POD, NPCEFF_DROP_ITEM, NPCID_PET_RED);\n}\n\nstatic void blackEgg()\n{\n    grantItemHeld(NPCID_ITEM_POD, NPCEFF_DROP_ITEM, NPCID_PET_BLACK);\n}\n\nstatic void purpleEgg()\n{\n    grantItemHeld(NPCID_ITEM_POD, NPCEFF_DROP_ITEM, NPCID_PET_PURPLE);\n}\n\nstatic void pinkEgg()\n{\n    grantItemHeld(NPCID_ITEM_POD, NPCEFF_DROP_ITEM, NPCID_PET_PINK);\n}\n\nstatic void coldEgg()\n{\n    grantItemHeld(NPCID_ITEM_POD, NPCEFF_DROP_ITEM, NPCID_PET_CYAN);\n}\n\nstatic void stopHittingMe()\n{\n    bool ret = GodMode;\n    GodMode = false;\n\n    for(int B = 1; B <= numPlayers; B++)\n        PlayerHurt(B);\n\n    GodMode = ret;\n}\n\nstatic void stickyFingers()\n{\n    GrabAll = !GrabAll;\n    PlaySound(GrabAll ? SFX_PlayerGrow : SFX_PlayerShrink);\n\n    for(int B = 1; B <= maxPlayers; B++)\n        Player[B].CanGrabNPCs = GrabAll;\n}\n\nstatic void capitanN()\n{\n    CaptainN = !CaptainN;\n    PlaySound(CaptainN ? SFX_PlayerGrow : SFX_PlayerShrink);\n}\n\nstatic void flameThrower()\n{\n    FlameThrower = !FlameThrower;\n    PlaySound(FlameThrower ? SFX_PlayerGrow : SFX_PlayerShrink);\n}\n\nstatic void moneyTree()\n{\n    CoinMode = !CoinMode;\n    PlaySound(CoinMode ? SFX_PlayerGrow : SFX_PlayerShrink);\n}\n\nstatic void godMode()\n{\n    GodMode = !GodMode;\n    PlaySound(GodMode ? SFX_PlayerGrow : SFX_PlayerShrink);\n}\n\nstatic void wingMan()\n{\n    FlyForever = !FlyForever;\n    PlaySound(FlyForever ? SFX_PlayerGrow : SFX_PlayerShrink);\n}\n\nstatic void tooSlow()\n{\n    SuperSpeed = !SuperSpeed;\n    PlaySound(SuperSpeed ? SFX_PlayerGrow : SFX_PlayerShrink);\n}\n\nstatic void ahippinAndAHopping()\n{\n    MultiHop = !MultiHop;\n    PlaySound(MultiHop ? SFX_PlayerGrow : SFX_PlayerShrink);\n}\n\nstatic void frameRate()\n{\n    ConfigChangeSentinel sent(ConfigSetLevel::cheat);\n\n    g_config.show_fps = !g_config.show_fps;\n    PlaySound(g_config.show_fps ? SFX_PlayerGrow : SFX_PlayerShrink);\n}\n\nstatic void speedDemon()\n{\n    ConfigChangeSentinel sent(ConfigSetLevel::cheat);\n\n    g_config.unlimited_framerate = !g_config.unlimited_framerate;\n    PlaySound(g_config.unlimited_framerate ? SFX_PlayerGrow : SFX_PlayerShrink);\n\n    if(g_config.compatibility_mode != Config_t::COMPAT_OFF && g_config.compatibility_mode != Config_t::COMPAT_MODERN)\n    {\n        pLogDebug(\"Marking Cheater by unlimited framerate cheat code\");\n        Cheater = true;\n    }\n}\n\nstatic void gifs2png()\n{\n    PlaySound(SFX_Transform);\n\n#ifdef __3DS__\n    ConfigChangeSentinel sent(ConfigSetLevel::cheat);\n    g_config.inaccurate_gifs = !g_config.inaccurate_gifs;\n#else\n    g_ForceBitmaskMerge = !g_ForceBitmaskMerge;\n    XRender::unloadGifTextures();\n#endif\n}\n\nstatic void logicScreen()\n{\n    PlaySound(SFX_PSwitch);\n    g_CheatLogicScreen = !g_CheatLogicScreen;\n}\n\nstatic void editYourFriends()\n{\n    g_CheatEditYourFriends = 2;\n    PauseGame(PauseCode::Options);\n}\n\nstatic void bornToClimb()\n{\n    CanWallJump = !CanWallJump;\n    PlaySound(CanWallJump ? SFX_PlayerGrow : SFX_PlayerShrink);\n}\n\nstatic void newLeaf()\n{\n    if(g_config.show_fps.m_set == ConfigSetLevel::cheat || g_config.unlimited_framerate.m_set == ConfigSetLevel::cheat)\n    {\n        g_config.show_fps.unset();\n        g_config.unlimited_framerate.unset();\n        UpdateConfig();\n    }\n\n    GodMode = false;\n    MultiHop = false;\n    SuperSpeed = false;\n    FlyForever = false;\n    CoinMode = false;\n    FlameThrower = false;\n    CaptainN = false;\n    GrabAll = false;\n    ShadowMode = false;\n    CanWallJump = false;\n    PlaySound(SFX_VillainKilled);\n}\n\nstatic void getMeOuttaHere()\n{\n    // in non-cheat variant, LevelMacroCounter is stuck at 0 if player never leaves section\n    bool possible_softlock = (LevelMacro == LEVELMACRO_CARD_ROULETTE_EXIT && LevelMacroCounter == 0);\n\n    if(LevelMacro != LEVELMACRO_OFF && !possible_softlock)\n        return;\n\n    LevelBeatCode = BEATCODE_NONE;\n    LevelMacro = LEVELMACRO_OFF;\n    LevelMacroCounter = 0;\n    EndLevel = true;\n}\n\nstatic void holyTrinity()\n{\n    PlaySound(SFX_Transform);\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        Player[B].Immune = 50;\n        NewEffect(EFFID_SMOKE_S3_CENTER, Player[B].Location);\n    }\n\n    if(ShadowMode && GodMode && MultiHop)\n    {\n        ShadowMode = false;\n        GodMode = false;\n        MultiHop = false;\n    }\n    else\n    {\n        ShadowMode = true;\n        GodMode = true;\n        MultiHop = true;\n    }\n}\n\nstatic void essentials()\n{\n    PlaySound(SFX_Transform);\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        Player[B].Immune = 50;\n        NewEffect(EFFID_SMOKE_S3_CENTER, Player[B].Location);\n    }\n\n    if(ShadowMode && GodMode && MultiHop && SuperSpeed)\n    {\n        SuperSpeed = false;\n        ShadowMode = false;\n        GodMode = false;\n        MultiHop = false;\n    }\n    else\n    {\n        SuperSpeed = true;\n        ShadowMode = true;\n        GodMode = true;\n        MultiHop = true;\n    }\n}\n\nstatic void foundMyCarKey()\n{\n    if(LevelMacro != LEVELMACRO_OFF)\n        return;\n\n    PlaySound(SFX_Key);\n    StopMusic();\n    LevelMacro = LEVELMACRO_KEYHOLE_EXIT;\n\n    int player = CheckLiving();\n    if(player && numBackground + numLocked < maxBackgrounds)\n    {\n        const Location_t& pLoc = Player[player].Location;\n\n        // can't properly add a background because they need to be sorted, but can at least trigger the animation.\n        Background_t& bgo = Background[numBackground + numLocked + 1];\n        bgo = Background_t();\n\n        bgo.Type = 35;\n\n        bgo.Location.Width = BackgroundWidth[35];\n        bgo.Location.Height = BackgroundHeight[35];\n        bgo.Location.X = pLoc.X + (pLoc.Width - bgo.Location.Width) / 2;\n        bgo.Location.Y = pLoc.Y + pLoc.Height - bgo.Location.Height;\n\n        LevelMacroWhich = numBackground + numLocked + 1;\n    }\n}\n\nstatic void lifeGoals()\n{\n    if(LevelMacro != LEVELMACRO_OFF)\n        return;\n\n    LevelMacro = LEVELMACRO_GOAL_TAPE_EXIT;\n    StopMusic();\n    PlaySound(SFX_TapeExit);\n}\n\nstatic void mysteryBall()\n{\n    if(LevelMacro != LEVELMACRO_OFF)\n        return;\n\n    LevelMacro = LEVELMACRO_QUESTION_SPHERE_EXIT;\n    StopMusic();\n    PlaySound(SFX_DungeonClear);\n}\n\nstatic void itsVegas()\n{\n    // in non-cheat variant, LevelMacroCounter is stuck at 0 if player never leaves section\n    bool possible_softlock = (LevelMacro == LEVELMACRO_CARD_ROULETTE_EXIT && LevelMacroCounter == 0);\n\n    if(LevelMacro != LEVELMACRO_OFF && !possible_softlock)\n        return;\n\n    LevelMacro = LEVELMACRO_CARD_ROULETTE_EXIT;\n    // mark as cheat variant to prevent softlocks\n    LevelMacroWhich = -1;\n    StopMusic();\n    PlaySound(SFX_CardRouletteClear);\n}\n\nstatic void setRes(int w, int h)\n{\n    ConfigChangeSentinel sent(ConfigSetLevel::cheat);\n\n    g_config.internal_res = {w, h};\n}\n\nstatic void setResGb()\n{\n    setRes(320, 288);\n}\n\nstatic void setResGba()\n{\n    setRes(480, 320);\n}\n\nstatic void setResNds()\n{\n    setRes(512, 384);\n}\n\nstatic void setResSnes()\n{\n    setRes(512, 448);\n}\n\nstatic void setResVga()\n{\n    setRes(640, 480);\n}\n\nstatic void setResHello()\n{\n    setRes(768, 432);\n}\n\nstatic void setRes3ds()\n{\n    setRes(800, 480);\n}\n\nstatic void setResClassic()\n{\n    setRes(800, 600);\n}\n\nstatic void setResHD()\n{\n    setRes(1280, 720);\n}\n\nstatic void setResDyn()\n{\n    setRes(0, 0);\n}\n\nstatic void setResCustom()\n{\n    int w, h;\n\n    std::string s = TextEntryScreen::Run(\"Game width:\");\n    if(s.empty())\n        return;\n\n    w = 0; // just to suppress an unneeded warning\n\n    while(s != \"dyn\" && (w = atol(s.c_str())) <= 0)\n    {\n        s = TextEntryScreen::Run(\"Invalid input. Game width:\");\n        if(s.empty())\n            return;\n    }\n\n    if(s == \"dyn\")\n        w = 0;\n\n    s = TextEntryScreen::Run(\"Game height:\");\n    if(s.empty())\n        return;\n\n    while((h = atol(s.c_str())) <= 0)\n    {\n        s = TextEntryScreen::Run(\"Invalid input. Game height:\");\n        if(s.empty())\n            return;\n    }\n\n    setRes(w, h);\n}\n\n\n\n\nstruct CheatCodeDefault_t\n{\n    const char*key;\n    void (*call)();\n    bool isCheat;\n};\n\nstruct CheatCode_t\n{\n    char key[25];\n    size_t keyLen;\n    void (*call)();\n    bool isCheat;\n};\n\nstatic const CheatCodeDefault_t s_cheatsListGlobalDefault[] =\n{\n#ifdef ENABLE_ANTICHEAT_TRAP\n    {\"redigitiscool\", dieCheater, false},\n#else\n    {\"redigitiscool\", redigitIsCool, false},\n#endif\n    {\"\\x77\\x6f\\x68\\x6c\\x73\\x74\\x61\\x6e\\x64\\x69\\x73\\x74\\x73\\x65\\x68\\x72\\x67\\x75\\x74\", redigitIsCool, false},\n\n    {\"gifs2png\", gifs2png, false},\n\n    // cheat to show the logical screens\n    {\"logicscreen\", logicScreen, false},\n\n    // cheat to allow editing any setting\n    {\"edityourfriends\", editYourFriends, false},\n\n    // resolution cheats\n    {\"gameboyview\", setResGb, false},\n    {\"tinyview\", setResGba, false},\n    {\"gbaview\", setResGba, false},\n    {\"superbdemoadvance\", setResGba, false},\n    {\"ndsview\", setResNds, false},\n    {\"snesview\", setResSnes, false},\n    {\"vgaview\", setResVga, false},\n    {\"helloview\", setResHello, false},\n    {\"3dsview\", setRes3ds, false},\n    {\"smbxview\", setResClassic, false},\n    {\"aodview\", setResClassic, false},\n    {\"classicview\", setResClassic, false},\n    {\"hdview\", setResHD, false},\n    {\"dynview\", setResDyn, false},\n    {\"debugview\", setResCustom, false},\n    {\"customview\", setResCustom, false},\n\n    {nullptr, nullptr, false}\n};\n\n\nstatic const CheatCodeDefault_t s_cheatsListWorldDefault[] =\n{\n    {\"imtiredofallthiswalking\", moonWalk, true}, {\"moonwalk\", moonWalk, true}, {\"skywalk\", moonWalk, true},\n    {\"illparkwhereiwant\", illParkWhereIWant, true}, {\"parkinglot\", illParkWhereIWant, true},\n    {\"4shared\", fourShared, true},\n    {\"4split\", fourSplit, true},\n    {\"opensesame\", openSesame, true},\n    {nullptr, nullptr, false}\n};\n\nstatic const CheatCodeDefault_t s_cheatsListLevelDefault[] =\n{\n    {\"needashell\", needAShell, true},\n    {\"fairymagic\", fairyMagic, true},\n    {\"iceage\", iceAge, true},\n    {\"istillplaywithlegos\", iStillPlayWithLegos, true},\n    {\"itsrainingmen\", itsRainingMen, true},\n    {\"donttypethis\", dontTypeThis, true},\n    {\"wetwater\", wetWater, true},\n    {\"needaredshell\", needARedShell, true},\n    {\"needablueshell\", needABlueShell, true},\n    {\"needayellowshell\", needAYellowShell, true},\n    {\"needaturnip\", needATurnip, true},\n    {\"needa1up\", needA1Up, true},\n    {\"needatanookisuit\", needATanookiSuit, true},\n    {\"needahammersuit\", needAHammerSuit, true}, {\"hammertime\", needAHammerSuit, true},\n    {\"needamushroom\", needAMushroom, true},\n    {\"needaflower\", needAFlower, true},\n    {\"niceflower\", needAnIceFlower, true},\n    {\"needaleaf\", needALeaf, true},\n    {\"needanegg\", needANegg, true},\n    {\"needaplant\", needAPlant, true},\n    {\"needagun\", needAGun, true},\n    {\"needaswitch\", needASwitch, true},\n    {\"needaclock\", needAClock, true},\n    {\"needabomb\", needABomb, true},\n    {\"needashoe\", needAShoe, true},\n    {\"redshoe\", redShoe, true},\n    {\"blueshoe\", blueShoe, true},\n    {\"shadowstar\", shadowStar, true},\n\n    {\"ibakedacakeforyou\", becomeAsPeach, true}, {\"itsamepeach\", becomeAsPeach, true},\n    {\"anothercastle\", becomeAsToad, true}, {\"itsametoad\", becomeAsToad, true},\n    {\"iamerror\", becomeAsLink, true}, {\"itsamelink\", becomeAsLink, true},\n    {\"itsamemario\", becomeAsMario, true}, {\"plumberboy\", becomeAsMario, true}, {\"moustacheman\", becomeAsMario, true},\n    {\"itsameluigi\", becomeAsLuigi, true}, {\"greenmario\", becomeAsLuigi, true},\n\n    {\"supermario200\", superbDemo200, true},\n    {\"supermario128\", superbDemo128, true},\n    {\"supermario64\", superbDemo64, true},\n    {\"supermario32\", superbDemo32, true},\n    {\"supermario16\", superbDemo16, true},\n    {\"supermario8\", superbDemo8, true},\n    {\"supermario4\", superbDemo4, true},\n    {\"supermario2\", superbDemo2, true},\n    {\"1player\", onePlayer, true},\n    {\"2player\", twoPlayer, true},\n    {\"4shared\", fourShared, true},\n    {\"4split\", fourSplit, true},\n\n    {\"wariotime\", warioTime, true},\n    {\"carkeys\", carKeys, true},\n    {\"boingyboing\", boingyBoing, true},\n    {\"bombsaway\", bombsAway, true},\n    {\"firemissiles\", fireMissiles, true},\n    {\"burnthehousedown\", hellFire, true}, {\"hellfire\", hellFire, true},\n    {\"upandout\", upAndOut, true},\n    {\"powhammer\", powHammer, true},\n    {\"hammerinmypants\", hammerInMyPants, true},\n    {\"rainbowrider\", rainbowRider, true},\n\n    {\"greenegg\", greenEgg, true},\n    {\"blueegg\", blueEgg, true},\n    {\"yellowegg\", yellowEgg, true},\n    {\"redegg\", redEgg, true},\n    {\"blackegg\", blackEgg, true},\n    {\"purpleegg\", purpleEgg, true},\n    {\"pinkegg\", pinkEgg, true},\n    {\"coldegg\", coldEgg, true},\n    {\"stophittingme\", stopHittingMe, true}, {\"uncle\", stopHittingMe, true},\n    {\"stickyfingers\", stickyFingers, true},\n    {\"captainn\", capitanN, true},\n    {\"flamethrower\", flameThrower, true},\n    {\"moneytree\", moneyTree, true},\n    {\"donthurtme\", godMode, true}, {\"godmode\", godMode, true},\n    {\"wingman\", wingMan, true},\n    {\"tooslow\", tooSlow, true},\n    {\"ahippinandahoppin\", ahippinAndAHopping, true}, {\"jumpman\", ahippinAndAHopping, true},\n    {\"framerate\", frameRate, false},\n    {\"speeddemon\", speedDemon, false},\n\n    {\"getmeouttahere\", getMeOuttaHere, true},\n    {\"newleaf\", newLeaf, true},\n    {\"borntoclimb\", bornToClimb, true},\n    {\"needacyclone\", needACyclone, true},\n    {\"goodtimesroll\", goodTimesRoll, true},\n    {\"canttouchthis\", cantTouchThis, true},\n    {\"waypastcool\", wayPastCool, true},\n    {\"sushitime\", sushiTime, true},\n\n    {\"holytrinity\", holyTrinity, true}, {\"passerby\", holyTrinity, true},\n    {\"essentials\", essentials, true}, {\"holyfour\", essentials, true},\n\n    {\"foundmycarkey\", foundMyCarKey, true},\n    {\"lifegoals\", lifeGoals, true},\n    {\"mysteryball\", mysteryBall, true},\n    {\"itsvegas\", itsVegas, true},\n\n    {nullptr, nullptr, false}\n};\n\n\n//! Current list of global cheats\nstatic std::vector<CheatCode_t> s_cheatsListGlobal;\n//! Current list of world map specific cheats\nstatic std::vector<CheatCode_t> s_cheatsListWorld;\n//! Current list of level specific cheats\nstatic std::vector<CheatCode_t> s_cheatsListLevel;\n\n\n/*!\n * \\brief hasQWERTZ\n * \\param s Checks does string is suitable for conversion into QWERTZ\n * \\return true if string can be converted into QWERTZ\n */\nSDL_FORCE_INLINE bool hasQWERTZ(const std::string &s)\n{\n    for(const char &c : s)\n    {\n        switch(c)\n        {\n        case 'y':\n        case 'z':\n            return true;\n        default:\n            continue;\n        }\n    }\n\n    return false;\n}\n\n/*!\n * \\brief convert string into QWERTZ\n * \\param s Source string in QWERTY\n * \\return QWERTZ string\n */\nSDL_FORCE_INLINE std::string toQWERTZ(std::string s)\n{\n    for(char &c : s)\n    {\n        switch(c)\n        {\n        case 'y':\n            c = 'z';\n            break;\n        case 'z':\n            c = 'y';\n            break;\n        default:\n            continue;\n        }\n    }\n\n    return s;\n}\n\n/*!\n * \\brief hasAZERTY\n * \\param s Checks does string is suitable for conversion into AZERTY\n * \\return true if string can be converted into AZERTY\n */\nSDL_FORCE_INLINE bool hasAZERTY(const std::string &s)\n{\n    for(const char &c : s)\n    {\n        switch(c)\n        {\n        case 'q':\n        case 'w':\n        case 'a':\n        case ';':\n        case 'z':\n        case 'm':\n            return true;\n        default:\n            continue;\n        }\n    }\n\n    return false;\n}\n\n/*!\n * \\brief convert string into AZERTY\n * \\param s Source string in AZERTY\n * \\return AZERTY string\n */\nSDL_FORCE_INLINE std::string toAZERTY(std::string s)\n{\n    for(char &c : s)\n    {\n        switch(c)\n        {\n        case 'q':\n            c = 'a';\n            break;\n        case 'w':\n            c = 'z';\n            break;\n        case 'a':\n            c = 'q';\n            break;\n        case 'z':\n            c = 'w';\n            break;\n        case 'm':\n            c = ';';\n            break;\n        default:\n            continue;\n        }\n    }\n\n    return s;\n}\n\nSDL_FORCE_INLINE void convertArray(std::vector<CheatCode_t> &dst, const CheatCodeDefault_t *src)\n{\n    dst.clear();\n\n    while(src->key && src->call)\n    {\n        CheatCode_t cd = {};\n        SDL_memset(cd.key, 0, sizeof(cd.key));\n        SDL_strlcpy(cd.key, src->key, sizeof(cd.key));\n        cd.keyLen = SDL_strlen(cd.key);\n        cd.call = src->call;\n        cd.isCheat = src->isCheat;\n        dst.push_back(cd);\n\n        if(hasQWERTZ(src->key)) // Automatically add QWERTZ alias\n        {\n            std::string z = toQWERTZ(src->key);\n            SDL_memset(cd.key, 0, sizeof(cd.key));\n            SDL_strlcpy(cd.key, z.c_str(), SDL_min(sizeof(cd.key), z.size() + 1));\n            cd.keyLen = SDL_strlen(cd.key);\n            dst.push_back(cd);\n        }\n\n        if(hasAZERTY(src->key)) // Automatically add AZERTY alias\n        {\n            std::string z = toAZERTY(src->key);\n            SDL_memset(cd.key, 0, sizeof(cd.key));\n            SDL_strlcpy(cd.key, z.c_str(), SDL_min(sizeof(cd.key), z.size() + 1));\n            cd.keyLen = SDL_strlen(cd.key);\n            dst.push_back(cd);\n        }\n\n        ++src;\n    }\n}\n\nSDL_FORCE_INLINE void addAliasCheats(CheatsScope scope, std::vector<GameInfo::CheatAlias> &list)\n{\n    for(const auto &c : list)\n    {\n        cheats_addAlias(scope, c.first, c.second);\n        if(hasQWERTZ(c.second)) // Add QWERTZ version for new string\n            cheats_addAlias(scope, c.first, toQWERTZ(c.second));\n        if(hasAZERTY(c.second)) // Add AZERTY version for new string\n            cheats_addAlias(scope, c.first, toAZERTY(c.second));\n    }\n}\n\nSDL_FORCE_INLINE void addRenameCheats(CheatsScope scope, std::vector<GameInfo::CheatAlias> &list)\n{\n    for(const auto &c : list)\n    {\n        cheats_rename(scope, c.first, c.second);\n\n        if(hasQWERTZ(c.first)) // Remove no longer relevant QWERTZ version of string\n            cheats_erase(scope, toQWERTZ(c.first));\n        if(hasAZERTY(c.first)) // Remove no longer relevant AZERTY version of string\n            cheats_erase(scope, toAZERTY(c.first));\n\n        if(hasQWERTZ(c.second)) // Add QWERTZ version for new string if needed\n            cheats_addAlias(scope, c.second, toQWERTZ(c.second));\n        if(hasAZERTY(c.second)) // Add AZERTY version for new string if needed\n            cheats_addAlias(scope, c.second, toAZERTY(c.second));\n    }\n}\n\nvoid cheats_reset()\n{\n    convertArray(s_cheatsListGlobal, s_cheatsListGlobalDefault);\n    convertArray(s_cheatsListWorld, s_cheatsListWorldDefault);\n    convertArray(s_cheatsListLevel, s_cheatsListLevelDefault);\n\n    addAliasCheats(CHEAT_SCOPE_GLOBAL,  g_gameInfo.cheatsGlobalAliases);\n    addRenameCheats(CHEAT_SCOPE_GLOBAL, g_gameInfo.cheatsGlobalRenames);\n\n    addAliasCheats(CHEAT_SCOPE_WORLD,  g_gameInfo.cheatsWorldAliases);\n    addRenameCheats(CHEAT_SCOPE_WORLD, g_gameInfo.cheatsWorldRenames);\n\n    addAliasCheats(CHEAT_SCOPE_LEVEL,  g_gameInfo.cheatsLevelAliases);\n    addRenameCheats(CHEAT_SCOPE_LEVEL, g_gameInfo.cheatsLevelRenames);\n}\n\nvoid cheats_addAlias(CheatsScope scope, const std::string &source, const std::string &alias)\n{\n    std::vector<CheatCode_t> *dst = nullptr;\n\n    switch(scope)\n    {\n    case CHEAT_SCOPE_GLOBAL:\n        dst = &s_cheatsListGlobal;\n        break;\n    case CHEAT_SCOPE_WORLD:\n        dst = &s_cheatsListWorld;\n        break;\n    case CHEAT_SCOPE_LEVEL:\n        dst = &s_cheatsListLevel;\n        break;\n    }\n\n    SDL_assert(dst);\n\n    for(auto &c : *dst)\n    {\n        if(source == c.key)\n        {\n            auto cc = c;\n            SDL_memset(cc.key, 0, sizeof(cc.key));\n            SDL_strlcpy(cc.key, alias.c_str(), SDL_min(sizeof(cc.key), alias.size() + 1));\n            cc.keyLen = SDL_strlen(cc.key);\n            dst->push_back(cc);\n            break;\n        }\n    }\n}\n\nvoid cheats_rename(CheatsScope scope, const std::string &source, const std::string &alias)\n{\n    std::vector<CheatCode_t> *dst = nullptr;\n\n    switch(scope)\n    {\n    case CHEAT_SCOPE_GLOBAL:\n        dst = &s_cheatsListGlobal;\n        break;\n    case CHEAT_SCOPE_WORLD:\n        dst = &s_cheatsListWorld;\n        break;\n    case CHEAT_SCOPE_LEVEL:\n        dst = &s_cheatsListLevel;\n        break;\n    }\n\n    SDL_assert(dst);\n\n    for(auto &c : *dst)\n    {\n        if(source == c.key)\n        {\n            SDL_memset(c.key, 0, sizeof(c.key));\n            SDL_strlcpy(c.key, alias.c_str(), SDL_min(sizeof(c.key), alias.size() + 1));\n            c.keyLen = SDL_strlen(c.key);\n            break;\n        }\n    }\n\n}\n\nvoid cheats_erase(CheatsScope scope, const std::string &source)\n{\n    std::vector<CheatCode_t> *dst = nullptr;\n\n    switch(scope)\n    {\n    case CHEAT_SCOPE_GLOBAL:\n        dst = &s_cheatsListGlobal;\n        break;\n    case CHEAT_SCOPE_WORLD:\n        dst = &s_cheatsListWorld;\n        break;\n    case CHEAT_SCOPE_LEVEL:\n        dst = &s_cheatsListLevel;\n        break;\n    }\n\n    SDL_assert(dst);\n\n    for(auto it = dst->begin(); it != dst->end(); ++it)\n    {\n        if(source == it->key)\n        {\n            dst->erase(it);\n            break;\n        }\n    }\n}\n\n\nstruct CheatBuffer_t\n{\n    static const size_t maxLen = 25;\n    char buffer[2][maxLen + 1] = {};\n    size_t t = 0;\n    size_t bufLen = 0;\n\n    void clear()\n    {\n        buffer[0][0] = 0;\n        buffer[1][0] = 0;\n        bufLen = 0;\n    }\n\n    void setBuffer(const std::string &line)\n    {\n        bufLen = SDL_min(maxLen, line.size());\n        SDL_memcpy(buffer[t], line.c_str(), bufLen);\n        buffer[t][bufLen] = 0;\n    }\n\n    void addSym(char c)\n    {\n        if(bufLen < maxLen)\n        {\n            buffer[t][bufLen++] = c;\n            buffer[t][bufLen] = 0;\n        }\n        else\n        {\n            size_t ts = t,\n                   td = !t;\n            SDL_memcpy(buffer[td], buffer[ts] + 1, bufLen - 1);\n            t = td;\n            buffer[t][bufLen - 1] = c;\n            buffer[t][bufLen] = 0;\n        }\n    }\n\n    const char *getString()\n    {\n        if(bufLen == 0)\n            return \"\";\n        return buffer[t];\n    }\n\n    size_t getBufLen() const\n    {\n        return bufLen;\n    }\n};\n\nstatic CheatBuffer_t s_buffer;\n\n\nSDL_FORCE_INLINE bool cheatCompare(size_t bufLen, const char *buf,\n                                   size_t keyLen, const char *key)\n{\n    if(bufLen < keyLen)\n        return false;\n\n    return SDL_memcmp(buf + (bufLen - keyLen), key, keyLen) == 0;\n}\n\nvoid run_cheat(XMessage::Message message)\n{\n    int set = message.player;\n\n    const auto& cheat_list = (set == 0) ? s_cheatsListGlobal\n                            : (set == 1) ? s_cheatsListLevel\n                            : s_cheatsListWorld;\n\n    if(set == 1 && LevelSelect)\n        return;\n    else if(set == 2 && !LevelSelect)\n        return;\n\n    if(message.message >= cheat_list.size())\n        return;\n\n    const auto& c = cheat_list[message.message];\n\n    c.call();\n\n    if(c.isCheat)\n    {\n        pLogDebug(\"Cheating detected!!! [%s]\\n\", c.key);\n        Cheater = true;\n    }\n}\n\nstatic void processCheats(bool instant)\n{\n    const char *buf = s_buffer.getString();\n    auto bufLen = s_buffer.getBufLen();\n\n    for(uint8_t set = 0; set < 3; set++)\n    {\n        const auto& cheat_list = (set == 0) ? s_cheatsListGlobal\n                                : (set == 1) ? s_cheatsListLevel\n                                : s_cheatsListWorld;\n\n        for(size_t i = 0; i < cheat_list.size(); i++)\n        {\n            const auto& c = cheat_list[i];\n\n            if(!cheatCompare(bufLen, buf, c.keyLen, c.key))\n                continue;\n\n            XMessage::Message cheat_message{XMessage::Type::enter_code, set, (uint8_t)i};\n\n            if(instant)\n                run_cheat(cheat_message);\n            else\n                XMessage::PushMessage(cheat_message);\n\n            s_buffer.clear();\n            break;\n        }\n    }\n}\n\nvoid cheats_setBuffer(const std::string &line, bool instant)\n{\n    s_buffer.setBuffer(line);\n    processCheats(instant);\n}\n\nvoid cheats_clearBuffer()\n{\n    s_buffer.clear();\n}\n\n/*!\n * \\brief Process the cheat buffer\n * \\param sym New key symbol\n */\nvoid CheatCode(char sym)\n{\n    if(LevelEditor || GameMenu || /*nPlay.Online ||*/ BattleMode)\n    {\n        s_buffer.clear();\n        return;\n    }\n\n    s_buffer.addSym(sym);\n\n    processCheats(false);\n}\n\nbool cheats_contains(const std::string &needle)\n{\n    const char *buf = s_buffer.getString();\n    auto bufLen = s_buffer.getBufLen();\n    return cheatCompare(bufLen, buf, needle.size(), needle.c_str());\n}\n\nbool cheats_contains(const char *needle)\n{\n    const char *buf = s_buffer.getString();\n    auto bufLen = s_buffer.getBufLen();\n    return cheatCompare(bufLen, buf, SDL_strlen(needle), needle);\n}\n"
  },
  {
    "path": "src/main/cheat_code.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CHEAT_CODE_H\n#define CHEAT_CODE_H\n\n#include <string>\n#include \"message.h\"\n\n//! if true, bitmask merge is forced to occur even when logic ops are supported and merge loses information\nextern bool g_ForceBitmaskMerge;\n\n//! if true, the boundaries of logical screens are shown over the visible screen\nextern bool g_CheatLogicScreen;\n\n//! if 1, the player may edit compat settings (but they won't get saved). if 2, the player goes directly to compat on the next Options Screen start.\nextern int g_CheatEditYourFriends;\n\n/*!\n * \\brief Reset all customized cheats state into default\n */\nextern void cheats_reset();\n\n\nenum CheatsScope\n{\n    CHEAT_SCOPE_GLOBAL = 0,\n    CHEAT_SCOPE_WORLD,\n    CHEAT_SCOPE_LEVEL\n};\n\n/*!\n * \\brief Add alias to the existing cheat code\n * \\param scope Cheats scope\n * \\param source Source name of the cheat\n * \\param alias New alias name\n */\nextern void cheats_addAlias(CheatsScope scope,\n                            const std::string &source,\n                            const std::string &alias);\n\n/*!\n * \\brief Rename existing cheat into something other\n * \\param scope Cheats scope\n * \\param source Original name of cheat to rename\n * \\param alias New name for the cheat\n */\nextern void cheats_rename(CheatsScope scope,\n                          const std::string &source,\n                          const std::string &alias);\n\n/*!\n * \\brief Erase cheat code by name\n * \\param scope Cheats scope\n * \\param source Cheat to erase\n */\nextern void cheats_erase(CheatsScope scope, const std::string &source);\n\n\n/*!\n * \\brief Runs cheat based on saved message\n */\nextern void run_cheat(XMessage::Message message);\n\n/*!\n * \\brief Sets cheat buffer to string and processes cheats\n * \\param line - cheat string to set\n * \\param instant - for use by scripts: trigger cheat immediately. otherwise, push onto XMessage queue\n */\nextern void cheats_setBuffer(const std::string &line, bool instant);\n\nextern bool cheats_contains(const std::string &needle);\nextern bool cheats_contains(const char *needle);\n\nextern void cheats_clearBuffer();\n\n/*!\n * \\brief Adds one charracter into the cheat buffer and executes cheat if buffer has enough\n * \\param sym ASCII character to add into the cheat buffer\n */\nextern void CheatCode(char sym);\n\n#endif // CHEAT_CODE_H\n"
  },
  {
    "path": "src/main/client.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <array>\n#include <vector>\n\n#include <Logger/logger.h>\n#include <SDL_net.h>\n\n#include \"message.h\"\n#include \"main/client.h\"\n\nstatic constexpr bool max_debug = false;\n\nstatic XMessage::Message msg_from_frame_no(XMessage::Type type, uint32_t frame_no)\n{\n    XMessage::Message ret;\n    ret.type = type;\n    ret.screen = (uint8_t)(frame_no >> 16);\n    ret.player = (uint8_t)(frame_no >> 8);\n    ret.message = (uint8_t)(frame_no >> 0);\n\n    return ret;\n}\n\nstatic int frame_no_from_message(XMessage::Message message)\n{\n    return (int)(message.screen << 16) | (int)(message.player << 8) | (int)(message.message << 0);\n}\n\n\nnamespace XMessage\n{\n\nstatic int s_client_thread(void* _client)\n{\n    if(!_client)\n        return 1;\n\n    NetworkClient& client = *static_cast<NetworkClient*>(_client);\n\n    while(!client.shutdown)\n    {\n        client.client_loop();\n        // eventually, some way of sleeping\n    }\n\n    return 0;\n}\n\nbool TCPWrapper::FillBuffer(bool nb)\n{\n    if(!socket)\n        return false;\n\n    if(nb)\n    {\n        if(!SDLNet_SocketReady(socket))\n            return false;\n    }\n\n    if(network_client_buffer_size <= buffer_used)\n        return false;\n\n    int got = SDLNet_TCP_Recv(socket, &buffer[buffer_used], network_client_buffer_size - buffer_used);\n\n    if(got <= 0)\n    {\n        err = true;\n        return false;\n    }\n\n    buffer_used += got;\n    return true;\n}\n\nbool TCPWrapper::FillBufferTo(size_t fill)\n{\n    while(buffer_used < fill)\n    {\n        if(!socket || err)\n            return false;\n\n        FillBuffer();\n    }\n\n    return true;\n}\n\nbool TCPWrapper::FillBufferTo_NB(size_t fill)\n{\n    while(buffer_used < fill)\n    {\n        if(!socket || err)\n            return false;\n\n        if(!FillBuffer(true))\n            return false;\n    }\n\n    return true;\n}\n\nvoid TCPWrapper::ShiftBuffer(size_t shift)\n{\n    size_t to_move = buffer_used - shift;\n    SDL_memmove(&buffer[0], &buffer[shift], to_move);\n    buffer_used = to_move;\n}\n\nXMessage::Message NetworkClient::ParseMessage(const uint8_t* message)\n{\n    XMessage::Message got;\n    got.type = (XMessage::Type)message[0];\n    got.screen = message[1];\n    got.player = message[2];\n    got.message = message[3];\n\n    return got;\n}\n\nvoid NetworkClient::Shutdown()\n{\n    if(thread)\n    {\n        shutdown = true;\n        SDL_WaitThread(thread, nullptr);\n        thread = nullptr;\n    }\n\n    Disconnect(true);\n\n    if(client_wakeup)\n    {\n        SDL_DestroySemaphore(client_wakeup);\n        client_wakeup = nullptr;\n    }\n\n    if(game_wakeup)\n    {\n        SDL_DestroySemaphore(game_wakeup);\n        game_wakeup = nullptr;\n    }\n\n    if(sdlnet_inited)\n    {\n        SDLNet_FreeSocketSet(socket_set);\n        socket_set = nullptr;\n\n        SDLNet_Quit();\n        sdlnet_inited = false;\n    }\n}\n\nvoid NetworkClient::EnsureThread()\n{\n    if(!sdlnet_inited)\n    {\n        SDLNet_Init();\n        sdlnet_inited = true;\n        socket_set = SDLNet_AllocSocketSet(3);\n    }\n\n    if(!thread)\n    {\n        SDL_AtomicSet(&status_req_state, REQUEST_IDLE);\n        SDL_AtomicSet(&message_buffer_state, REQUEST_IDLE);\n        thread = SDL_CreateThread(s_client_thread, \"network thread\", this);\n        shutdown = false;\n    }\n\n    if(!client_wakeup)\n        client_wakeup = SDL_CreateSemaphore(0);\n\n    if(!game_wakeup)\n        game_wakeup = SDL_CreateSemaphore(0);\n}\n\nvoid NetworkClient::Connect(const char* host, int port)\n{\n    Disconnect();\n\n    IPaddress addr;\n    if(SDLNet_ResolveHost(&addr, host, port) != 0)\n        return;\n\n    tcp_control.socket = SDLNet_TCP_OpenClient(&addr);\n    if(!tcp_control.socket)\n        return;\n\n    SDLNet_TCP_AddSocket(socket_set, tcp_control.socket);\n\n    tcp_data.socket = SDLNet_TCP_OpenClient(&addr);\n    if(!tcp_data.socket)\n    {\n        Disconnect();\n        return;\n    }\n\n    SDLNet_TCP_AddSocket(socket_set, tcp_data.socket);\n\n    udp_socket = SDLNet_UDP_Open(0);\n    if(!udp_socket || SDLNet_UDP_Bind(udp_socket, 0, &addr) != 0)\n    {\n        Disconnect();\n        return;\n    }\n\n    // SDLNet_UDP_SetPacketLoss(udp_socket, 99);\n    SDLNet_UDP_AddSocket(socket_set, udp_socket);\n\n    udp_packet = SDLNet_AllocPacket(256);\n    if(!udp_packet)\n    {\n        Disconnect();\n        return;\n    }\n\n    udp_packet->channel = 0;\n    udp_packet->address = addr;\n\n    status = ClientStatus();\n    status.client_state = CLIENT_LOBBY;\n    status.server_address = host;\n    status.server_port = port;\n\n    tick = 0;\n}\n\nvoid NetworkClient::Disconnect(bool shutdown)\n{\n    if(tcp_control.socket)\n    {\n        SDLNet_TCP_DelSocket(socket_set, tcp_control.socket);\n        SDLNet_TCP_Close(tcp_control.socket);\n        tcp_control.socket = nullptr;\n    }\n\n    if(tcp_data.socket)\n    {\n        SDLNet_TCP_DelSocket(socket_set, tcp_data.socket);\n        SDLNet_TCP_Close(tcp_data.socket);\n        tcp_data.socket = nullptr;\n    }\n\n    if(udp_socket)\n    {\n        SDLNet_UDP_DelSocket(socket_set, udp_socket);\n        SDLNet_UDP_Close(udp_socket);\n        udp_socket = nullptr;\n    }\n\n    if(udp_packet)\n    {\n        SDLNet_FreePacket(udp_packet);\n        udp_packet = nullptr;\n    }\n\n    tcp_control.buffer_used = 0;\n    tcp_control.err = false;\n\n    tcp_data.buffer_used = 0;\n    tcp_data.err = false;\n\n    session_key = 0;\n\n    // FIXME: think carefully about how to update status field\n    // given that the primary status struct is owned by the game thread at this point\n\n    if(shutdown)\n        return;\n\n    // UpdateConfig();\n    // UpdateInternalRes();\n}\n\nvoid NetworkClient::LeaveRoom()\n{\n    if(!tcp_control.socket || status.client_state == CLIENT_LOBBY)\n        return;\n\n    uint8_t to_send[4] = {0,0,0,0};\n\n    SDLNet_TCP_Send(tcp_control.socket, to_send, 4);\n}\n\nvoid NetworkClient::SendAll()\n{\n    if(!tcp_data.socket)\n        return;\n\n    send_buffer.current_frame = tick;\n\n    if(!message_buffer.empty())\n    {\n        send_buffer.messages.push_back(msg_from_frame_no(Type::frame_begin, tick));\n\n        for(Message msg : message_buffer)\n            send_buffer.messages.push_back(msg);\n\n        message_buffer.clear();\n\n        if(!ping_send_frame)\n        {\n            if(udp_packet_recd > 0 || tick - acked_frame > 60)\n            {\n                ping_send_frame = tick;\n                ping_send_ms = SDL_GetTicks();\n            }\n        }\n    }\n\n    bool fast_forward = (receive_buffer.available_frame > tick + 10);\n    if(!send_buffer.messages.empty() || !fast_forward)\n        SendData();\n\n    SDL_AtomicSet(&message_buffer_state, REQUEST_PENDING);\n}\n\nvoid NetworkClient::SendData()\n{\n    if(send_buffer.current_frame < 0)\n        return;\n\n    // remove any ACKed messages from send buffer\n    while(!send_buffer.messages.empty())\n    {\n        Message front = send_buffer.messages.front();\n        if(front.type == Type::frame_begin && frame_no_from_message(front) > acked_frame)\n            break;\n\n        send_buffer.messages.pop_front();\n    }\n\n    // FIXME: something about having received things\n    bool has_udp = (udp_socket != nullptr && udp_packet_recd != 0);\n\n    // 1ms timer goes here\n    // if there's nothing that hasn't been ACKed, put a 15ms timer here\n    if(!has_udp && send_buffer.messages.empty())\n        return;\n\n    // serialize messages\n    std::vector<uint8_t> to_send;\n    to_send.reserve(4096);\n\n    // temporary marker to let server know how far the client has ACKed\n    send_buffer.messages.push_back(msg_from_frame_no(Type::frame_end, send_buffer.current_frame));\n\n    for(XMessage::Message m : send_buffer.messages)\n    {\n        to_send.push_back((uint8_t)m.type);\n        to_send.push_back(m.screen);\n        to_send.push_back(m.player);\n        to_send.push_back(m.message);\n    }\n\n    // remove temporary marker\n    send_buffer.messages.pop_back();\n\n    size_t MTU_size = 250;\n\n    // ideally, send with UDP\n    if(has_udp && to_send.size() <= MTU_size)\n    {\n        udp_packet->len = to_send.size();\n        SDL_memcpy(udp_packet->data, to_send.data(), to_send.size());\n        udp_packet_sent++;\n        SDLNet_UDP_Send(udp_socket, 0, udp_packet);\n    }\n    // fallback to TCP if UDP is unavailable or unfeasible\n    else\n    {\n        // send_buffer.udp_transmission.clear();\n        send_buffer.messages.clear();\n        for(uint8_t c : to_send)\n            send_buffer.tcp_transmit_in_progress.push_back(c);\n    }\n\n    // FIXME: the above should only get called once, and then the below should get called multiple times (with UDP retransmission moved below)\n\n    // update any TCP transmission in progress\n    if(!send_buffer.tcp_transmit_in_progress.empty())\n    {\n        send_buffer.tcp_transmit_pos += SDLNet_TCP_Send(tcp_data.socket,\n            send_buffer.tcp_transmit_in_progress.data() + send_buffer.tcp_transmit_pos,\n            send_buffer.tcp_transmit_in_progress.size() - send_buffer.tcp_transmit_pos);\n\n        if(send_buffer.tcp_transmit_pos >= send_buffer.tcp_transmit_in_progress.size())\n        {\n            send_buffer.tcp_transmit_in_progress.clear();\n            send_buffer.tcp_transmit_pos = 0;\n        }\n    }\n}\n\nvoid NetworkClient::ReceiveData()\n{\n    while(tcp_data.FillBufferTo_NB(4))\n    {\n        Message got = ParseMessage(&tcp_data.buffer[0]);\n        tcp_data.ShiftBuffer(4);\n\n        switch(got.type)\n        {\n        case(XMessage::Type::frame_begin):\n        case(XMessage::Type::frame_end):\n            int frame_no;\n            frame_no = frame_no_from_message(got);\n\n            // previous TCP frame is finished!\n            if(receive_buffer.tcp_frame_index > receive_buffer.available_frame)\n            {\n                receive_buffer.available_frame = receive_buffer.tcp_frame_index;\n\n                for(Message msg : receive_buffer.tcp_frame_in_progress)\n                {\n                    receive_buffer.messages.push_back(msg);\n                    if(max_debug)\n                        pLogDebug(\"TCP message get: %d %d %d %d\", (int)msg.type, msg.screen, msg.player, msg.message);\n                }\n\n                receive_buffer.tcp_frame_in_progress.clear();\n            }\n\n            if(got.type == XMessage::Type::frame_begin)\n            {\n                receive_buffer.tcp_frame_index = frame_no;\n                receive_buffer.tcp_frame_in_progress.push_back(got);\n            }\n            else if(got.type == XMessage::Type::frame_end)\n            {\n                if(frame_no > receive_buffer.available_frame)\n                    receive_buffer.available_frame = frame_no;\n\n                receive_buffer.tcp_frame_index = -1;\n            }\n\n            break;\n\n        default:\n            if(receive_buffer.tcp_frame_index > receive_buffer.available_frame)\n                receive_buffer.tcp_frame_in_progress.push_back(got);\n        }\n    }\n\n    // receive UDP data\n    while(SDLNet_UDP_Recv(udp_socket, udp_packet))\n    {\n        udp_packet_recd++;\n\n        if(max_debug)\n        {\n            std::string got;\n            for(int i = 0; i < udp_packet->len; i++)\n            {\n                got += std::to_string(udp_packet->data[i]);\n                got += ' ';\n            }\n\n            pLogDebug(\"UDP packet get: %s\", got.c_str());\n        }\n\n        int udp_frame_index = -1;\n\n        for(int i = 0; i + 4 <= udp_packet->len; i += 4)\n        {\n            Message got = ParseMessage(&udp_packet->data[i]);\n\n            switch(got.type)\n            {\n            // can recognize acks here soon\n            case(XMessage::Type::transmit_start):\n                acked_frame = frame_no_from_message(got);\n                if(acked_frame > tick)\n                {\n                    // prematurely received packet\n                    goto skip_packet;\n                }\n\n                if(acked_frame >= ping_send_frame && ping_send_frame != 0)\n                {\n                    latency_ms = SDL_GetTicks() - ping_send_ms;\n                    latency_frames = tick - ping_send_frame;\n                    ping_send_frame = 0;\n                    pLogDebug(\"UDP latency: %u ms, %d ticks\", latency_ms, latency_frames);\n                }\n                break;\n            case(XMessage::Type::frame_end):\n                udp_frame_index = frame_no_from_message(got);\n                if(udp_frame_index > receive_buffer.available_frame)\n                    receive_buffer.available_frame = udp_frame_index;\n                break;\n            case(XMessage::Type::frame_begin):\n                udp_frame_index = frame_no_from_message(got);\n            // fallthrough\n            default:\n                if(udp_frame_index > receive_buffer.available_frame)\n                {\n                    receive_buffer.messages.push_back(got);\n\n                    if(max_debug)\n                        pLogDebug(\"UDP message get: %d %d %d %d\", (int)got.type, got.screen, got.player, got.message);\n                }\n                else\n                {\n                    // pLogDebug(\"UDP stale message get: %d %d %d %d [%d %d]\", got.type, got.screen, got.player, got.message, udp_frame_index, receive_buffer.available_frame);\n                }\n                break;\n            }\n        }\nskip_packet:\n        // skip to next loop step\n        (void)0;\n    }\n}\n\nbool NetworkClient::WaitAndFill()\n{\n    // use the receive buffer to fill a frame if possible\n    if(receive_buffer.available_frame >= tick)\n    {\n        if(!receive_buffer.messages.empty())\n        {\n            Message front = receive_buffer.messages.front();\n            if(front.type == Type::frame_begin && frame_no_from_message(front) == tick)\n            {\n                receive_buffer.messages.pop_front();\n\n                while(!receive_buffer.messages.empty() && receive_buffer.messages.front().type != Type::frame_begin)\n                {\n                    if(max_debug)\n                        pLogDebug(\"Message push: %d %d %d %d %d\", tick, (int)receive_buffer.messages.front().type, receive_buffer.messages.front().screen, receive_buffer.messages.front().player, receive_buffer.messages.front().message);\n\n                    message_buffer.push_back(receive_buffer.messages.front());\n                    receive_buffer.messages.pop_front();\n                }\n            }\n        }\n\n        // pLogDebug(\"UDP packets sent %d recv %d\", udp_packet_sent, udp_packet_recd);\n\n        tick++;\n        return true;\n    }\n\n    return false;\n}\n\nvoid NetworkClient::client_loop()\n{\n    // bool fast_forward = (status.client_state > CLIENT_LOBBY) && (receive_buffer.available_frame > tick + 8);\n    if(!tcp_control.socket || !SDLNet_CheckSockets(socket_set, 0))\n    {\n        // currently, sleep 2ms here (waiting on messages from the main thread), then check again\n        // if(!fast_forward)\n        SDL_SemWaitTimeout(client_wakeup, 2);\n\n        if(tcp_control.socket)\n            SDLNet_CheckSockets(socket_set, 0);\n    }\n\n    // hang up on error\n    if(tcp_control.err || tcp_data.err)\n    {\n        Disconnect();\n        if(SDL_AtomicGet(&status_req_state) > REQUEST_IDLE)\n        {\n            status = ClientStatus();\n            SDL_AtomicSet(&status_req_state, REQUEST_COMPLETED);\n        }\n\n        if(SDL_AtomicGet(&message_buffer_state) == REQUEST_PENDING)\n            SDL_AtomicSet(&message_buffer_state, REQUEST_COMPLETED);\n    }\n\n    // section to handle connection status updates\n    if(SDL_AtomicGet(&status_req_state) == REQUEST_SUBMIT)\n    {\n        // if we shouldn't be connected, disconnect!\n        if(status_req.client_state == CLIENT_OFF)\n        {\n            Disconnect();\n        }\n        // otherwise, we should be connected.\n        else if(status_req.server_address != status.server_address || !tcp_control.socket)\n        {\n            // this will update status if it succeeds\n            Connect(status_req.server_address.c_str(), status_req.server_port);\n        }\n\n        // if not currently connected, ensure status is cleared and return early\n        if(!tcp_control.socket)\n        {\n            status = ClientStatus();\n            SDL_AtomicSet(&status_req_state, REQUEST_COMPLETED);\n            return;\n        }\n\n        if(status_req.client_state != status.client_state)\n        {\n            // always shift through lobby (in the future...)\n            if(status.client_state != CLIENT_LOBBY && status.client_state != CLIENT_SESSION_CONFIG)\n            {\n                // request leave room, mark as pending\n                return;\n            }\n\n            if(status.client_state == CLIENT_SESSION_CONFIG)\n            {\n                if(status_req.client_state == CLIENT_GUEST)\n                {\n                    std::array<uint8_t, 1> to_send =\n                    {\n                        HEADER_GET_SESSION,\n                    };\n\n                    SDLNet_TCP_Send(tcp_control.socket, to_send.data(), to_send.size());\n                }\n                else if(status_req.client_state == CLIENT_HOST)\n                {\n                    // encode session here\n                    std::array<uint8_t, 9> to_send =\n                    {\n                        HEADER_PUT_SESSION,\n                        0, 0, 0, 4,\n                        uint8_t(g_session.random_seed  >> 24), uint8_t(g_session.random_seed  >> 16), uint8_t(g_session.random_seed  >> 8), uint8_t(g_session.random_seed  >> 0),\n                    };\n\n                    SDLNet_TCP_Send(tcp_control.socket, to_send.data(), to_send.size());\n                }\n\n                SDL_AtomicSet(&status_req_state, REQUEST_PENDING);\n                return;\n            }\n            else if(status_req.client_state == CLIENT_GUEST)\n            {\n                auto room_key = status_req.room_info.room_key;\n                std::array<uint8_t, 5> to_send =\n                {\n                    HEADER_JOIN_ROOM,\n                    uint8_t(room_key >> 24), uint8_t(room_key >> 16), uint8_t(room_key >> 8), uint8_t(room_key >> 0),\n                };\n\n                SDLNet_TCP_Send(tcp_control.socket, to_send.data(), to_send.size());\n            }\n            else if(status_req.client_state == CLIENT_HOST)\n            {\n                const auto& room_info = status_req.room_info;\n\n                std::array<uint8_t, 13> to_send =\n                {\n                    HEADER_CREATE_ROOM,\n                    uint8_t(room_info.engine_hash  >> 24), uint8_t(room_info.engine_hash  >> 16), uint8_t(room_info.engine_hash  >> 8), uint8_t(room_info.engine_hash  >> 0),\n                    uint8_t(room_info.asset_hash   >> 24), uint8_t(room_info.asset_hash   >> 16), uint8_t(room_info.asset_hash   >> 8), uint8_t(room_info.asset_hash   >> 0),\n                    uint8_t(room_info.content_hash >> 24), uint8_t(room_info.content_hash >> 16), uint8_t(room_info.content_hash >> 8), uint8_t(room_info.content_hash >> 0),\n                };\n\n                SDLNet_TCP_Send(tcp_control.socket, to_send.data(), to_send.size());\n            }\n\n            SDL_AtomicSet(&status_req_state, REQUEST_PENDING);\n            return;\n        }\n\n        if(status_req.room_info.room_key != status.room_info.room_key)\n        {\n            if(status_req.client_state == CLIENT_LOBBY)\n            {\n                // send room info request\n                auto room_key = status_req.room_info.room_key;\n                std::array<uint8_t, 5> to_send =\n                {\n                    HEADER_ROOM_INFO,\n                    uint8_t(room_key >> 24), uint8_t(room_key >> 16), uint8_t(room_key >> 8), uint8_t(room_key >> 0),\n                };\n\n                SDLNet_TCP_Send(tcp_control.socket, to_send.data(), to_send.size());\n\n                SDL_AtomicSet(&status_req_state, REQUEST_PENDING);\n                return;\n            }\n        }\n\n        // all done with the request\n        SDL_AtomicSet(&status_req_state, REQUEST_COMPLETED);\n        return;\n\n        // // if currently in a room, try to leave it\n        // if(status.client_state != CLIENT_LOBBY)\n        // {\n        //     // ...\n        //     SDL_AtomicSet(&status_req_state, REQUEST_PENDING);\n        //     return;\n        // }\n\n        // // try to join a room as a guest\n        // if(status_req.client_state == CLIENT_GUEST)\n        // {\n        //     // try to leave the room\n        //     // ...\n        //     SDL_AtomicSet(&status_req_state, REQUEST_PENDING);\n        // }\n    }\n\n    // section to send pending messages\n    if(SDL_AtomicGet(&message_buffer_state) == REQUEST_SUBMIT)\n        SendAll();\n\n    // section to handle incoming TCP control messages\n    if(tcp_control.FillBufferTo_NB(1))\n    {\n        if(tcp_control.buffer[0] == HEADER_ROOM_INFO)\n        {\n            if(!tcp_control.FillBufferTo_NB(17))\n                return;\n\n            const auto& buffer = tcp_control.buffer;\n\n            uint32_t got_room_key = ((uint32_t)buffer[1] << 24) | ((uint32_t)buffer[2] << 16) | ((uint32_t)buffer[3] << 8) | ((uint32_t)buffer[4] << 0);\n            uint32_t engine_hash = ((uint32_t)buffer[5] << 24) | ((uint32_t)buffer[6] << 16) | ((uint32_t)buffer[7] << 8) | ((uint32_t)buffer[8] << 0);\n            uint32_t asset_hash = ((uint32_t)buffer[9] << 24) | ((uint32_t)buffer[10] << 16) | ((uint32_t)buffer[11] << 8) | ((uint32_t)buffer[12] << 0);\n            uint32_t content_hash = ((uint32_t)buffer[13] << 24) | ((uint32_t)buffer[14] << 16) | ((uint32_t)buffer[15] << 8) | ((uint32_t)buffer[16] << 0);\n\n            tcp_control.ShiftBuffer(17);\n\n            // ignore unexpected (stale?) rooom key responses\n            if(SDL_AtomicGet(&status_req_state) == REQUEST_PENDING && got_room_key == status_req.room_info.room_key)\n            {\n                status.room_info.room_key = got_room_key;\n                status.room_info.engine_hash = engine_hash;\n                status.room_info.asset_hash = asset_hash;\n                status.room_info.content_hash = content_hash;\n\n                SDL_AtomicSet(&status_req_state, REQUEST_COMPLETED);\n            }\n        }\n        else if(tcp_control.buffer[0] == HEADER_ROOM_KEY)\n        {\n            if(!tcp_control.FillBufferTo_NB(13))\n                return;\n\n            const auto& buffer = tcp_control.buffer;\n\n            status.room_info.room_key = ((uint32_t)buffer[1] << 24) | ((uint32_t)buffer[2] << 16) | ((uint32_t)buffer[3] << 8) | ((uint32_t)buffer[4] << 0);\n\n            status.client_index = buffer[5];\n\n            fast_forward_to = ((int)buffer[6] << 16) | ((int)buffer[7] << 8) | ((int)buffer[8] << 0);\n\n            session_key = ((uint32_t)buffer[9] << 24) | ((uint32_t)buffer[10] << 16) | ((uint32_t)buffer[11] << 8) | ((uint32_t)buffer[12] << 0);\n\n            tcp_control.ShiftBuffer(13);\n\n            if(status_req.room_info.room_key && status.room_info.room_key != status_req.room_info.room_key)\n            {\n                pLogWarning(\"Server placed client in unexpected room\");\n                Disconnect();\n            }\n\n            // register data TCP channel (would actually be ideal to CONNECT it here)\n            {\n                std::array<uint8_t, 5> to_send =\n                {\n                    HEADER_DATA_CHANNEL,\n                    uint8_t(session_key >> 24), uint8_t(session_key >> 16), uint8_t(session_key >> 8), uint8_t(session_key >> 0),\n                };\n\n                SDLNet_TCP_Send(tcp_data.socket, to_send.data(), to_send.size());\n            }\n\n            // register data UDP channel\n            {\n                udp_packet->data[0] = uint8_t(session_key >> 24);\n                udp_packet->data[1] = uint8_t(session_key >> 16);\n                udp_packet->data[2] = uint8_t(session_key >> 8);\n                udp_packet->data[3] = uint8_t(session_key >> 0);\n\n                udp_packet->len = 4;\n\n                for(int i = 0; i < 4; i++)\n                {\n                    SDLNet_UDP_Send(udp_socket, 0, udp_packet);\n                    SDL_Delay(1);\n                }\n            }\n\n            udp_packet_sent = 0;\n            udp_packet_recd = 0;\n\n            // reset send and receive buffers\n            receive_buffer = RecvBuffer();\n            send_buffer = SendBuffer();\n\n            pLogInfo(\"NetPlay: joining room %s\", DisplayRoom(status.room_info.room_key).room_name);\n\n            status.client_state = CLIENT_SESSION_CONFIG;\n\n            if(SDL_AtomicGet(&status_req_state) == REQUEST_PENDING)\n                SDL_AtomicSet(&status_req_state, REQUEST_SUBMIT);\n        }\n        else if(tcp_control.buffer[0] == HEADER_GET_SESSION)\n        {\n            if(!tcp_control.FillBufferTo_NB(1))\n                return;\n\n            tcp_control.ShiftBuffer(1);\n\n            if(SDL_AtomicGet(&status_req_state) == REQUEST_PENDING && status.client_state == CLIENT_SESSION_CONFIG)\n            {\n                status.client_state = CLIENT_HOST;\n                pLogDebug(\"The random seed is %d\", (int)g_session.random_seed);\n                SDL_AtomicSet(&status_req_state, REQUEST_COMPLETED);\n            }\n            else\n                Disconnect();\n        }\n        else if(tcp_control.buffer[0] == HEADER_PUT_SESSION)\n        {\n            if(!tcp_control.FillBufferTo_NB(5))\n                return;\n\n            uint32_t session_size = ((uint32_t)tcp_control.buffer[1] << 24) | ((uint32_t)tcp_control.buffer[2] << 16) | ((uint32_t)tcp_control.buffer[3] << 8) | ((uint32_t)tcp_control.buffer[4] << 0);\n\n            tcp_control.ShiftBuffer(5);\n\n            // decode session here\n\n            // handle this later, once we know exactly what we're filling\n            if(session_size > network_client_buffer_size)\n                Disconnect();\n\n            // note: not NB\n            if(!tcp_control.FillBufferTo(session_size))\n                Disconnect();\n\n            tcp_control.ShiftBuffer(session_size);\n\n            if(SDL_AtomicGet(&status_req_state) == REQUEST_PENDING && status.client_state == CLIENT_SESSION_CONFIG)\n            {\n                g_session.random_seed = ((uint32_t)tcp_control.buffer[0] << 24) | ((uint32_t)tcp_control.buffer[1] << 16) | ((uint32_t)tcp_control.buffer[2] << 8) | ((uint32_t)tcp_control.buffer[3] << 0);\n                status.client_state = CLIENT_GUEST;\n                pLogDebug(\"The random seed is %d\", (int)g_session.random_seed);\n                SDL_AtomicSet(&status_req_state, REQUEST_COMPLETED);\n            }\n            else\n                Disconnect();\n        }\n        else if(tcp_control.buffer[0] == HEADER_ACK)\n        {\n            if(!tcp_control.FillBufferTo_NB(4))\n                return;\n\n            acked_frame = ((int)tcp_control.buffer[1] << 16) | ((int)tcp_control.buffer[2] << 8) | ((int)tcp_control.buffer[3] << 0);\n\n            if(acked_frame >= ping_send_frame && ping_send_frame != 0)\n            {\n                latency_ms = SDL_GetTicks() - ping_send_ms;\n                latency_frames = tick - ping_send_frame;\n                ping_send_frame = 0;\n                pLogDebug(\"TCP latency: %u ms, %d ticks\", latency_ms, latency_frames);\n            }\n\n            tcp_control.ShiftBuffer(4);\n        }\n        // notice of current frame (for fast-forward)\n        else if(tcp_control.buffer[0] == HEADER_FRAME_COMPLETE)\n        {\n            if(!tcp_control.FillBufferTo_NB(4))\n                return;\n\n            // fixme: consider how to handle\n            fast_forward_to = ((int)tcp_control.buffer[1] << 16) | ((int)tcp_control.buffer[2] << 8) | ((int)tcp_control.buffer[3] << 0);\n\n            tcp_control.ShiftBuffer(4);\n        }\n    }\n\n    // handle incoming data messages from TCP data channel\n    if(status.client_state > CLIENT_LOBBY)\n    {\n        // if(!fast_forward)\n        //     SendData();\n\n        ReceiveData();\n\n        if(SDL_AtomicGet(&message_buffer_state) == REQUEST_PENDING && WaitAndFill())\n        {\n            SDL_AtomicSet(&message_buffer_state, REQUEST_COMPLETED);\n            SDL_SemPost(game_wakeup);\n        }\n    }\n}\n\n} // namespace XMessage\n"
  },
  {
    "path": "src/main/client.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef THEXTECH_ENABLE_SDL_NET\n#error \"client.h cannot be included in a build without SDL_net support\"\n#endif\n\n#ifndef XCLIENT_H\n#define XCLIENT_H\n\n#include <cstdint>\n#include <cstddef>\n#include <climits>\n#include <deque>\n\n#include <SDL_net.h>\n#include <SDL2/SDL_atomic.h>\n#include <SDL2/SDL_thread.h>\n\n#include \"message.h\"\n#include \"client_methods.h\"\n\nnamespace XMessage\n{\n\nstatic constexpr size_t network_client_buffer_size = 2048;\n\nenum NetworkHeader\n{\n    // HEADER_CLIENT_JOIN = 1,\n    // HEADER_CLIENT_LOSS = 2,\n    // HEADER_TEXT_EVENT = 3,\n    HEADER_FRAME_COMPLETE = 4,\n    // HEADER_YOU_ARE = 5,\n    // HEADER_RAND_SEED = 6,\n    // HEADER_TIME_IS = 7,\n    HEADER_LEFT_ROOM = 8,\n\n    HEADER_ROOM_KEY = 9,\n    HEADER_ROOM_INFO = 10,\n\n    HEADER_CREATE_ROOM = 11,\n    HEADER_JOIN_ROOM = 12,\n\n    HEADER_DATA_CHANNEL = 13,\n    HEADER_ACK = 14,\n\n    HEADER_PUT_SESSION = 15,\n    HEADER_GET_SESSION = 16,\n};\n\n// enum BufferState\n// {\n//     OWNER_GAME = 0,\n//     OWNER_CLIENT = 1,\n//     OWNER_CLIENT_RECV = 2,\n// };\n\nenum RequestState\n{\n    // game is owner of request fields\n    REQUEST_COMPLETED = -1,\n    REQUEST_IDLE = 0,\n    // client thread is owner of request fields\n    REQUEST_SUBMIT = 1,\n    REQUEST_PENDING = 2,\n};\n\nstruct TCPWrapper\n{\n    TCPsocket socket = nullptr;\n    uint8_t buffer[network_client_buffer_size];\n    size_t buffer_used = 0;\n    bool err = false;\n\n    // returns true if something has been read from the socket\n    bool FillBuffer(bool nb = false);\n\n    // returns true if something has been read from the socket, false if connection is broken\n    bool FillBufferTo(size_t fill);\n\n    // returns true if the TCP buffer has been filled to this point, false if not ready yet\n    bool FillBufferTo_NB(size_t fill);\n\n    // shift the buffer after reading n bytes\n    void ShiftBuffer(size_t shift);\n};\n\nstruct RecvBuffer\n{\n    std::deque<Message> messages;\n    std::vector<Message> tcp_frame_in_progress;\n    int tcp_frame_index = -1;\n\n    int available_frame = -1;\n};\n\nstruct SendBuffer\n{\n    std::deque<Message> messages;\n    int current_frame = -1;\n\n    std::vector<uint8_t> tcp_transmit_in_progress;\n    size_t tcp_transmit_pos = 0;\n};\n\nstruct NetworkClient\n{\n    SDL_Thread* thread = nullptr;\n    SDL_sem* client_wakeup = nullptr;\n    SDL_sem* game_wakeup = nullptr;\n    bool shutdown = false;\n\n    TCPWrapper tcp_control;\n    TCPWrapper tcp_data;\n    UDPsocket  udp_socket = nullptr;\n    UDPpacket* udp_packet = nullptr;\n\n    int udp_packet_sent = 0;\n    int udp_packet_recd = 0;\n\n    SDLNet_SocketSet socket_set = nullptr;\n    int tick = 0;\n    uint32_t session_key = 0;\n\n    int fast_forward_to = INT_MAX;\n\n    SDL_atomic_t status_req_state;\n    ClientStatus status_req;\n    ClientStatus status;\n\n    RecvBuffer receive_buffer;\n    SendBuffer send_buffer;\n\n    SDL_atomic_t message_buffer_state;\n    std::deque<Message> message_buffer;\n\n    // ping info\n    int ping_send_frame = -1;\n    uint32_t ping_send_ms = 0;\n    int acked_frame = 0;\n\n    uint32_t latency_ms = 0;\n    int latency_frames = 0;\n\n    bool sdlnet_inited = false;\n\n    void EnsureThread();\n\n    void Connect(const char* host, int port);\n    void Disconnect(bool shutdown = false);\n\n    void Shutdown();\n\n    XMessage::Message ParseMessage(const uint8_t* message);\n\n    // misc in-game calls\n    void LeaveRoom();\n\n    // load messages into send buffer\n    void SendAll();\n\n    // transmit messages\n    void SendData();\n\n    // receive messages\n    void ReceiveData();\n\n    bool WaitAndFill();\n\n    // misc lobby calls\n    void JoinNewRoom(const RoomInfo& room_info);\n\n    // this is the primary loop for the client thread\n    void client_loop();\n};\n\n} // namespace XMessage\n\n#endif // #ifndef XCLIENT_H\n"
  },
  {
    "path": "src/main/client_methods.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <deque>\n\n#include \"controls.h\"\n#include \"message.h\"\n#include \"globals.h\"\n#include \"sound.h\"\n#include \"change_res.h\"\n\n#include \"core/events.h\"\n\n#include \"main/client.h\"\n#include \"main/client_methods.h\"\n#include \"main/screen_progress.h\"\n\nstd::string g_netplayServer = \"thextech.link\";\nstd::string g_netplayNickname;\n\nnamespace XMessage\n{\n\nstatic NetworkClient s_network_client;\n\nbool in_fast_forward = false;\nuint32_t fast_forward_begin_ms = 0;\nuint32_t fast_forward_begin_frame = 0;\n\nvoid Connect(const char* host)\n{\n    s_network_client.EnsureThread();\n\n    if(SDL_AtomicGet(&s_network_client.status_req_state) > REQUEST_IDLE)\n        return;\n\n    s_network_client.status_req = ClientStatus();\n    s_network_client.status_req.client_state = CLIENT_LOBBY;\n    s_network_client.status_req.server_address = (host) ? host : g_netplayServer;\n\n    SDL_AtomicSet(&s_network_client.status_req_state, REQUEST_SUBMIT);\n}\n\nvoid Disconnect()\n{\n    if(SDL_AtomicGet(&s_network_client.status_req_state) > REQUEST_IDLE)\n        return;\n\n    s_network_client.status_req = ClientStatus();\n\n    SDL_AtomicSet(&s_network_client.status_req_state, REQUEST_SUBMIT);\n\n    if(!s_network_client.thread)\n        SDL_AtomicSet(&s_network_client.status_req_state, REQUEST_COMPLETED);\n}\n\nvoid Shutdown()\n{\n    s_network_client.Shutdown();\n}\n\nconst ClientStatus* GetClientStatus()\n{\n    if(SDL_AtomicGet(&s_network_client.status_req_state) > REQUEST_IDLE)\n        return nullptr;\n\n    return &s_network_client.status;\n}\n\nbool CompleteRequest()\n{\n    if(SDL_AtomicGet(&s_network_client.status_req_state) == REQUEST_COMPLETED)\n    {\n        SDL_AtomicSet(&s_network_client.status_req_state, REQUEST_IDLE);\n        return true;\n    }\n\n    return false;\n}\n\nStatus GetStatus()\n{\n    if(CurrentRoom())\n    {\n        if(in_fast_forward)\n            return Status::replay;\n        else\n            return Status::connected;\n    }\n    else\n        return Status::local;\n}\n\nvoid ClientFrameSync(std::deque<Message>& buffer)\n{\n    bool start_fast_forward = (s_network_client.tick < s_network_client.fast_forward_to - 8);\n    bool end_fast_forward = (s_network_client.tick >= s_network_client.fast_forward_to);\n    if(!in_fast_forward && start_fast_forward)\n    {\n        in_fast_forward = true;\n        fast_forward_begin_ms = SDL_GetTicks();\n        fast_forward_begin_frame = s_network_client.tick;\n    }\n    else if(in_fast_forward)\n    {\n        IndicateProgress(fast_forward_begin_ms, num_t(s_network_client.tick - fast_forward_begin_frame) / (s_network_client.fast_forward_to - fast_forward_begin_frame), \"Loading game history...\");\n\n        if(end_fast_forward)\n        {\n            in_fast_forward = false;\n            // start playing music when no longer fast forwarding\n            UpdateMusicVolume();\n            // update current resolution (may need to resync screen size)\n            UpdateInternalRes();\n        }\n    }\n\n    SDL_assert_release(SDL_AtomicGet(&s_network_client.message_buffer_state) == REQUEST_IDLE);\n\n    std::swap(s_network_client.message_buffer, buffer);\n\n    SDL_AtomicSet(&s_network_client.message_buffer_state, REQUEST_SUBMIT);\n    SDL_SemPost(s_network_client.client_wakeup);\n\n    while(SDL_AtomicGet(&s_network_client.message_buffer_state) != REQUEST_COMPLETED)\n    {\n        // wait for a wakeup call from the network thread, refreshing events every 5ms\n        if(SDL_SemWaitTimeout(s_network_client.game_wakeup, 5) < 0)\n        {\n            XEvents::doEvents();\n        }\n    }\n\n    std::swap(s_network_client.message_buffer, buffer);\n\n    SDL_AtomicSet(&s_network_client.message_buffer_state, REQUEST_IDLE);\n}\n\nbool RequestFillRoomInfo(uint32_t room_key)\n{\n    if(SDL_AtomicGet(&s_network_client.status_req_state) > REQUEST_IDLE || s_network_client.status_req.client_state != CLIENT_LOBBY)\n        return false;\n\n    s_network_client.status_req.room_info.room_key = room_key;\n\n    SDL_AtomicSet(&s_network_client.status_req_state, REQUEST_SUBMIT);\n    return true;\n}\n\nconst RoomInfo* GetRoomInfo()\n{\n    if(SDL_AtomicGet(&s_network_client.status_req_state) > REQUEST_IDLE)\n        return nullptr;\n\n    return &s_network_client.status.room_info;\n}\n\n\nvoid JoinNewRoom(const RoomInfo& room_info)\n{\n    if(SDL_AtomicGet(&s_network_client.status_req_state) > REQUEST_IDLE || s_network_client.status.client_state != CLIENT_LOBBY)\n        return;\n\n    s_network_client.status_req.client_state = CLIENT_HOST;\n    s_network_client.status_req.room_info = room_info;\n\n    XMessage::g_session.random_seed = iRand(2147483647);\n\n    SDL_AtomicSet(&s_network_client.status_req_state, REQUEST_SUBMIT);\n}\n\nvoid JoinRoom(uint32_t room_key)\n{\n    if(SDL_AtomicGet(&s_network_client.status_req_state) > REQUEST_IDLE || s_network_client.status.client_state != CLIENT_LOBBY)\n        return;\n\n    s_network_client.status_req.client_state = CLIENT_GUEST;\n    s_network_client.status_req.room_info.room_key = room_key;\n\n    SDL_AtomicSet(&s_network_client.status_req_state, REQUEST_SUBMIT);\n}\n\nuint32_t CurrentRoom()\n{\n    if(SDL_AtomicGet(&s_network_client.status_req_state) > REQUEST_IDLE || s_network_client.status.client_state == CLIENT_LOBBY)\n        return 0;\n\n    return s_network_client.status.room_info.room_key;\n}\n\nvoid LeaveRoom()\n{\n    s_network_client.LeaveRoom();\n}\n\nuint32_t RoomFromString(const std::string& room)\n{\n    if(room.size() != 6 && !(room.size() == 7 && room[3] == '-'))\n        return 0;\n\n    uint32_t sum = 0;\n    uint32_t shift = 30;\n    for(size_t i = 0; i < room.size(); i++)\n    {\n        if(i == 3 && room.size() == 7)\n            continue;\n\n        shift -= 5;\n\n        char c = room[i];\n        if(c < 48 || (c > 57 && c < 65) || (c > 90 && c < 97) || c > 122)\n            return 0;\n\n        if(c >= 97)\n            c -= 32;\n\n        const uint8_t digit_codes[10] = {14, 8, 25, 26, 27, 18, 28, 29, 30, 31};\n\n        if(c >= 65)\n            sum += (uint32_t)(c - 65) << shift;\n        else\n            sum += (uint32_t)digit_codes[c - 48] << shift;\n    }\n\n    return sum;\n}\n\nRoomName DisplayRoom(uint32_t room_key)\n{\n    RoomName ret;\n    ret.room_name[0] = '\\0';\n\n    if(room_key == 0 || room_key & (3 << 30))\n        return ret;\n\n    const char letters[33] = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ346789\";\n\n    ret.room_name[3] = '-';\n    ret.room_name[7] = '\\0';\n    uint32_t shift = 30;\n    for(int i = 0; i < 6; i++)\n    {\n        shift -= 5;\n        int index = (i >= 3) ? i + 1 : i;\n        ret.room_name[index] = letters[(room_key >> shift) & 31];\n    }\n\n    return ret;\n}\n\n} // namespace XMessage\n"
  },
  {
    "path": "src/main/client_methods.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef THEXTECH_ENABLE_SDL_NET\n#error \"client_methods.h cannot be included in a build without SDL_net support\"\n#endif\n\n#ifndef XCLIENT_METHODS_H\n#define XCLIENT_METHODS_H\n\n#include <deque>\n#include <cstdint>\n#include <string>\n\n#include \"message.h\"\n\nextern std::string g_netplayServer;\nextern std::string g_netplayNickname;\n\n\nnamespace XMessage\n{\n\nstruct RoomInfo\n{\n    uint32_t room_key = 0;\n    uint32_t engine_hash = 0;\n    uint32_t asset_hash = 0;\n    uint32_t content_hash = 0;\n};\n\n\nstruct RoomName\n{\n    char room_name[8];\n};\n\nenum ClientState\n{\n    CLIENT_OFF = 0,\n    CLIENT_SESSION_CONFIG, // joined room but haven't yet sent/received session\n    CLIENT_LOBBY,\n    CLIENT_HOST_IDLE,\n    CLIENT_HOST_SPECTATED,\n    CLIENT_HOST,\n    CLIENT_GUEST,\n    CLIENT_SPECTATOR,\n};\n\nstruct ClientStatus\n{\n    ClientState client_state = CLIENT_OFF;\n    std::string server_address;\n    int server_port = 4305;\n    RoomInfo room_info;\n    int client_index = 0;\n};\n\nvoid Connect(const char* host = nullptr);\nvoid Disconnect();\nvoid Shutdown();\nconst ClientStatus* GetClientStatus();\nbool CompleteRequest();\n\nvoid ClientFrameSync(std::deque<Message>& buffer);\n\nbool RequestFillRoomInfo(uint32_t room_key);\nconst RoomInfo* GetRoomInfo();\n\nvoid JoinNewRoom(const RoomInfo& room_info);\nvoid JoinRoom(uint32_t room_key);\nuint32_t CurrentRoom();\nvoid LeaveRoom();\n\nuint32_t RoomFromString(const std::string& room);\nRoomName DisplayRoom(uint32_t room_key);\n\n} // namespace XMessage\n\n#endif // #ifndef XCLIENT_METHODS_H\n"
  },
  {
    "path": "src/main/game_globals.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GAME_GLOBALS_H\n#define GAME_GLOBALS_H\n\n#include \"screen.h\"\n#include \"../screen_fader.h\"\n#include \"../range_arr.hpp\"\n\nextern ScreenFader g_levelScreenFader;\nextern RangeArr<ScreenFader, 0, c_vScreenCount> g_levelVScreenFader;\n\n// delay following current level should be shortened by 16 frames / 250 milliseconds,\n// because current level ended with the fade out from an offscreen exit\nextern bool g_ShortDelay;\n\nextern void clearScreenFaders();\nextern void updateScreenFaders();\nextern void levelWaitForFade(int waitTicks); // wait waitTicks frames for the fader (or no-fader) to finish\nextern void editorWaitForFade();\n\n#endif // GAME_GLOBALS_H\n"
  },
  {
    "path": "src/main/game_info.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <fmt_format_ne.h>\n#include <Utils/files.h>\n#include <Utils/files_ini.h>\n#include <Utils/strings.h>\n#include <Archives/archives.h>\n#include <IniProcessor/ini_processing.h>\n#include <PGE_File_Formats/pge_file_lib_globs.h>\n\n#include \"game_info.h\"\n#include \"script/luna/luna.h\"\n\n#include \"globals.h\"\n\n#include \"../version.h\"\n\n\nGameInfo g_gameInfo;\n\nstatic void readCheats(IniProcessing &conf, std::vector<GameInfo::CheatAlias> &dst, const std::string &group)\n{\n    dst.clear();\n    conf.beginGroup(group);\n    {\n        auto ak = conf.allKeys();\n        for(const auto &k : ak)\n        {\n            GameInfo::CheatAlias ca;\n            ca.first = k;\n            conf.read(k.c_str(), ca.second, std::string());\n            if(ca.second.empty())\n                continue; // Skip empty aliases\n            dst.push_back(ca);\n        }\n    }\n    conf.endGroup();\n}\n\nconst std::string GameInfo::titleWindow() const\n{\n#if defined(ENABLE_OLD_CREDITS) && !defined(CUSTOM_GAME_NAME_TITLE)\n    return \"Super Mario Bros. X - Version 1.3 - www.SuperMarioBrothers.org\";\n#else\n    const char* ver_string = \"TheXTech v\" V_LATEST_STABLE \", #\" V_BUILD_VER;\n    return fmt::sprintf_ne(\"%s - (%s)\", this->title.c_str(), ver_string);\n#endif\n}\n\nvoid GameInfo::InitGameInfo()\n{\n#ifdef CUSTOM_GAME_NAME_TITLE\n    title = CUSTOM_GAME_NAME_TITLE;\n#else\n#   ifdef ENABLE_OLD_CREDITS\n    title = \"Super Mario Bros. X\";\n#   else\n    title = \"TheXTech Engine\";\n#   endif\n#endif /* CUSTOM_GAME_NAME_TITLE */\n\n    statusIconName.clear();\n\n    characterName[1] = \"Mario\";\n    characterName[2] = \"Luigi\";\n    characterName[3] = \"Peach\";\n    characterName[4] = \"Toad\";\n    characterName[5] = \"Link\";\n\n    fails_counter_title = \"FAILS\";\n\n    wordStarAccusativeSingular = \"star\";\n    wordStarAccusativeDual_Cnt = \"\";\n    wordStarAccusativePlural = \"stars\";\n\n    creditsFont = 5;\n\n#ifdef CUSTOM_CREDITS_URL\n    creditsHomePage = CUSTOM_CREDITS_URL;\n#else\n#   ifdef ENABLE_OLD_CREDITS\n    creditsHomePage = \"www.SuperMarioBrothers.org\";\n#   else\n    creditsHomePage = \"wohlsoft.ru\";\n#   endif\n#endif\n\n    disableTwoPlayer = false;\n    disableBattleMode = false;\n\n    activity_settings_in_compat = false;\n    contentFeatureLevel = 0;\n\n    ResetIntroActivitySettings();\n    ResetOutroActivitySettings();\n}\n\nvoid GameInfo::ResetIntroActivitySettings()\n{\n    introEnableActivity = true;\n    introMaxPlayersCount = 6;\n    introCharacters = {1, 2, 3, 4, 5};\n    introCharacterCurrent = 0;\n    introDeadMode = false;\n}\n\nvoid GameInfo::ResetOutroActivitySettings()\n{\n    outroEnableActivity = true;\n    outroMaxPlayersCount = 5;\n    outroCharacters = {1, 2, 3, 4, 5};\n    outroStates = {4, 7, 5, 3, 6};\n    outroMounts = {0, 3, 0, 1, 0};\n    outroCharacterCurrent = 0;\n    outroInitialDirections = {0, 0, 0, 0, 0};\n    outroWalkDirection = -1;\n    outroAutoJump = true;\n    outroDeadMode = false;\n}\n\nvoid GameInfo::LoadGameInfo()\n{\n    InitGameInfo();\n\n    std::string gameInfoPath = AppPath + \"gameinfo.ini\";\n    if(Files::fileExists(gameInfoPath))\n    {\n        IniProcessing config = Files::load_ini(gameInfoPath);\n\n        config.beginGroup(\"game\");\n        {\n            if(config.hasKey(\"title\"))\n                config.read(\"title\", title, title);\n            config.read(\"disable-two-player\", disableTwoPlayer, false);\n            config.read(\"disable-battle-mode\", disableBattleMode, false);\n            config.read(\"status-icon-name\", statusIconName, std::string());\n            config.read(\"feature-level-supported\", contentFeatureLevel, 0);\n\n            constexpr unsigned int engineFeatureLevel = V_FEATURE_LEVEL;\n            if(contentFeatureLevel > engineFeatureLevel)\n            {\n                pLogDebug(\"Limiting asset pack to feature level %u (supports %u)\", engineFeatureLevel, contentFeatureLevel);\n                contentFeatureLevel = engineFeatureLevel;\n            }\n        }\n        config.endGroup();\n\n        // legacy options\n        if(!config.beginGroup(\"fails-counter\"))\n        {\n            config.beginGroup(\"death-counter\"); // Backup fallback\n        }\n        {\n            config.read(\"title\", fails_counter_title, fails_counter_title);\n        }\n        config.endGroup();\n\n        config.beginGroup(\"objects\");\n        {\n            config.read(\"star\", wordStarAccusativeSingular, \"star\");\n            config.read(\"star-singular\", wordStarAccusativeSingular, g_gameInfo.wordStarAccusativeSingular);\n            config.read(\"star-plural\", wordStarAccusativePlural, g_gameInfo.wordStarAccusativeSingular + \"s\");\n        }\n        config.endGroup();\n\n        config.beginGroup(\"characters\");\n        {\n            for(int i = 1; i <= numCharacters; ++i)\n            {\n                std::string name = fmt::sprintf_ne(\"name%d\", i);\n                config.read(name.c_str(),\n                            characterName[i],\n                            characterName[i]);\n            }\n        }\n        config.endGroup();\n\n        if(!Archives::has_prefix(AppPath))\n        {\n            config.beginGroup(\"intro\");\n            LoadIntroActivitySettings(config);\n            config.endGroup();\n\n            config.beginGroup(\"outro\");\n            LoadOutroActivitySettings(config);\n            config.endGroup();\n        }\n\n        config.beginGroup(\"credits\");\n        {\n            config.read(\"font\", creditsFont, 5);\n            config.read(\"homepage\", creditsHomePage, creditsHomePage);\n\n            std::string value;\n            Strings::dealloc(creditsGame);\n\n            std::string credits_file;\n            config.read(\"game-credits-file\", credits_file, \"\");\n\n            if(!credits_file.empty())\n            {\n                PGE_FileFormats_misc::RWopsTextInput input(Files::open_file(AppPath + credits_file, \"r\"));\n                while(!input.eof())\n                {\n                    input.readLine(value);\n                    if(!value.empty() || !input.eof())\n                        creditsGame.push_back(value);\n                }\n            }\n\n            if(creditsGame.empty())\n            {\n                // Old format\n                for(int cr = 1; ; cr++)\n                {\n                    std::string key = fmt::sprintf_ne(\"game-credit-%d\", cr);\n                    if(!config.hasKey(key))\n                        break;\n                    config.read(key.c_str(), value, value);\n                    creditsGame.push_back(value);\n                }\n            }\n        }\n        config.endGroup();\n\n        readCheats(config, cheatsGlobalAliases, \"cheats-global-aliases\");\n        readCheats(config, cheatsGlobalRenames, \"cheats-global-renames\");\n        readCheats(config, cheatsWorldAliases, \"cheats-world-aliases\");\n        readCheats(config, cheatsWorldRenames, \"cheats-world-renames\");\n        readCheats(config, cheatsLevelAliases, \"cheats-level-aliases\");\n        readCheats(config, cheatsLevelRenames, \"cheats-level-renames\");\n    }\n}\n\nvoid GameInfo::LoadIntroActivitySettings(IniProcessing& config)\n{\n    config.read(\"enable-activity\", introEnableActivity, introEnableActivity);\n    config.read(\"max-players-count\", introMaxPlayersCount, introMaxPlayersCount);\n    config.read(\"characters\", introCharacters, introCharacters);\n    introDeadMode = !introEnableActivity || introMaxPlayersCount < 1;\n}\n\nvoid GameInfo::LoadOutroActivitySettings(IniProcessing& config)\n{\n    config.read(\"enable-activity\", outroEnableActivity, outroEnableActivity);\n    config.read(\"max-players-count\", outroMaxPlayersCount, outroMaxPlayersCount);\n    config.read(\"characters\", outroCharacters, outroCharacters);\n    config.read(\"states\", outroStates, outroStates);\n    config.read(\"mounts\", outroMounts, outroMounts);\n    config.read(\"auto-jump\", outroAutoJump, outroAutoJump);\n    IniProcessing::StrEnumMap dirs\n    {\n        {\"left\", -1},\n        {\"idle\", 0},\n        {\"right\", +1}\n    };\n    config.readEnum(\"walk-direction\", outroWalkDirection, outroWalkDirection, dirs);\n    config.read(\"initial-directions\", outroInitialDirections, outroInitialDirections);\n    outroDeadMode = !outroEnableActivity || outroMaxPlayersCount < 1;\n}\n\nint GameInfo::introCharacterNext()\n{\n    if(introCharacters.empty())\n        return 1;\n    if(g_gameInfo.introCharacterCurrent >= introCharacters.size())\n        g_gameInfo.introCharacterCurrent = 0;\n    int ret = g_gameInfo.introCharacters[g_gameInfo.introCharacterCurrent++];\n\n    if(ret > 5) // anti-idiot protection\n        ret = 5;\n    else if(ret < 1)\n        ret = 1;\n\n    return ret;\n}\n\nint GameInfo::outroCharacterNext()\n{\n    if(outroCharacters.empty())\n        return 1;\n    if(g_gameInfo.outroCharacterCurrent >= outroCharacters.size())\n        g_gameInfo.outroCharacterCurrent = 0;\n    int ret = g_gameInfo.outroCharacters[g_gameInfo.outroCharacterCurrent++];\n\n    if(ret > 5) // anti-idiot protection\n        ret = 5;\n    else if(ret < 1)\n        ret = 1;\n\n    return ret;\n}\n\nvoid initGameInfo()\n{\n    g_gameInfo.InitGameInfo();\n    g_gameInfo.LoadGameInfo();\n}\n"
  },
  {
    "path": "src/main/game_info.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GAME_INFO_H\n#define GAME_INFO_H\n\n#include <string>\n#include <vector>\n#include \"global_constants.h\"\n#include \"range_arr.hpp\"\n\nclass IniProcessing;\n\nstruct GameInfo\n{\n    void InitGameInfo();\n    void LoadGameInfo();\n\n    void LoadIntroActivitySettings(IniProcessing& ini);\n    void LoadOutroActivitySettings(IniProcessing& ini);\n\n    void ResetIntroActivitySettings();\n    void ResetOutroActivitySettings();\n\n    std::string title;\n    // std::string titleCredits;\n    RangeArr<std::string, 1, numCharacters> characterName;\n\n    std::string fails_counter_title;\n\n    std::string statusIconName;\n\n    std::string wordStarAccusativeSingular;\n    std::string wordStarAccusativeDual_Cnt;\n    std::string wordStarAccusativePlural;\n\n    bool disableBattleMode = true;\n    bool disableTwoPlayer = true;\n\n    int creditsFont = 4;\n    std::string creditsHomePage;\n    std::vector<std::string> creditsGame;\n\n    bool activity_settings_in_compat = false;\n\n    bool introEnableActivity = true;\n    int  introMaxPlayersCount = 6;\n    std::vector<int> introCharacters;\n\n    bool introDeadMode = true;\n\n    bool outroEnableActivity = true;\n    int  outroMaxPlayersCount = 5;\n    bool outroAutoJump = true;\n    std::vector<int> outroCharacters;\n    std::vector<int> outroStates;\n    std::vector<int> outroMounts;\n    std::vector<int> outroInitialDirections;\n    int  outroWalkDirection = -1;\n\n    bool outroDeadMode = true;\n\n    unsigned int contentFeatureLevel = 0;\n\n    typedef std::pair<std::string, std::string> CheatAlias;\n\n    std::vector<CheatAlias> cheatsGlobalAliases;\n    std::vector<CheatAlias> cheatsGlobalRenames;\n\n    std::vector<CheatAlias> cheatsWorldAliases;\n    std::vector<CheatAlias> cheatsWorldRenames;\n\n    std::vector<CheatAlias> cheatsLevelAliases;\n    std::vector<CheatAlias> cheatsLevelRenames;\n\n    // Carousel\n    int introCharacterNext();\n    size_t introCharacterCurrent = 0;\n\n    int outroCharacterNext();\n    size_t outroCharacterCurrent = 0;\n\n    const std::string titleWindow() const;\n};\n\nextern GameInfo g_gameInfo;\n\nextern void initGameInfo();\n\n#endif // GAME_INFO_H\n"
  },
  {
    "path": "src/main/game_loop.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include <Logger/logger.h>\n#include <Integrator/integrator.h>\n#include <pge_delay.h>\n\n#if defined(THEXTECH_ASSERTS_INGAME_MESSAGE) && !defined(THEXTECH_NO_SDL_BUILD)\n#   ifdef __WIIU__\n#       include <sysapp/launch.h>\n#   endif\n#   include \"frm_main.h\"\n#endif\n\n#if defined(__16M__) && !defined(__CALICO__)\n#include \"core/16m/sound_stream_16m.h\"\n#endif\n\n#include \"../globals.h\"\n#include \"../config.h\"\n#include \"../frame_timer.h\"\n#include \"../game_main.h\"\n#include \"../sound.h\"\n#include \"../controls.h\"\n#include \"../effect.h\"\n#include \"../graphics.h\"\n#include \"../blocks.h\"\n#include \"../npc.h\"\n#include \"../layers.h\"\n#include \"../player.h\"\n#include \"../editor.h\"\n#include \"../core/render.h\"\n#include \"../core/events.h\"\n#include \"../core/window.h\"\n#include \"graphics/gfx_update.h\"\n#include \"game_globals.h\"\n#include \"world_globals.h\"\n#include \"speedrunner.h\"\n#include \"main/record.h\"\n#include \"menu_main.h\"\n#include \"change_res.h\"\n#include \"message.h\"\n#include \"screen_pause.h\"\n#include \"screen_connect.h\"\n#include \"screen_options.h\"\n#include \"screen_quickreconnect.h\"\n#include \"screen_textentry.h\"\n#include \"screen_prompt.h\"\n#include \"main/level_medals.h\"\n#include \"main/game_loop_interrupt.h\"\n#include \"script/luna/luna.h\"\n#include \"game_strings.h\"\n\n#include \"../pseudo_vb.h\"\n\nPauseCode GamePaused = PauseCode::None;\n\n//! Holds the screen overlay for the level\nScreenFader g_levelScreenFader;\nRangeArr<ScreenFader, 0, c_vScreenCount> g_levelVScreenFader;\n\nvoid clearScreenFaders()\n{\n    g_levelScreenFader.clearFader();\n    for(int s = 0; s <= c_vScreenCount; ++s)\n        g_levelVScreenFader[s].clearFader();\n}\n\nvoid updateScreenFaders()\n{\n    g_levelScreenFader.update();\n\n    for(int s = 0; s <= c_vScreenCount; ++s)\n        g_levelVScreenFader[s].update();\n}\n\nvoid levelWaitForFade(int waitTicks)\n{\n    while(waitTicks >= 0 && GameIsActive)\n    {\n        XEvents::doEvents();\n\n        if(canProceedFrame())\n        {\n            computeFrameTime1();\n            Controls::Update(false);\n            if(XMessage::GetStatus() != XMessage::Status::replay)\n                UpdateGraphicsDraw();\n            UpdateSound();\n            XEvents::doEvents();\n            computeFrameTime2();\n            updateScreenFaders();\n            waitTicks--;\n        }\n\n        if(!g_config.unlimited_framerate)\n            PGE_Delay(1);\n    }\n\n    // Ensure everything is clear\n    if(GameIsActive)\n    {\n        XEvents::doEvents();\n        GraphicsClearScreen();\n    }\n}\n\nvoid editorWaitForFade()\n{\n    while(!g_levelScreenFader.isComplete() && GameIsActive)\n    {\n        XEvents::doEvents();\n\n        if(canProceedFrame())\n        {\n            computeFrameTime1();\n            if(GamePaused == PauseCode::Prompt)\n                PromptScreen::Render();\n            else if(WorldEditor)\n                UpdateGraphics2();\n            else\n                UpdateGraphics();\n            UpdateSound();\n            XEvents::doEvents();\n            computeFrameTime2();\n            updateScreenFaders();\n        }\n\n        if(!g_config.unlimited_framerate)\n            PGE_Delay(1);\n    }\n}\n\n\nvoid CheckActive();//in game_main.cpp\n\nGameLoopInterrupt g_gameLoopInterrupt;\n\nvoid GameLoop()\n{\n    if(GamePaused != PauseCode::None)\n    {\n        PauseLoop();\n\n        // resume code (if needed)\n        if(GamePaused == PauseCode::None)\n        {\n            switch(g_gameLoopInterrupt.site)\n            {\n            case GameLoopInterrupt::UpdatePlayer_MessageNPC:\n            case GameLoopInterrupt::UpdatePlayer_TriggerTalk:\n            case GameLoopInterrupt::UpdatePlayer_SuperWarp:\n                goto resume_UpdatePlayer;\n            case GameLoopInterrupt::UpdateNPCs_Activation_Generator:\n            case GameLoopInterrupt::UpdateNPCs_Activation_Self:\n            case GameLoopInterrupt::UpdateNPCs_Activation_Chain:\n            case GameLoopInterrupt::UpdateNPCs_KillNPC:\n                goto resume_UpdateNPCs;\n            case GameLoopInterrupt::UpdateEvents:\n                goto resume_UpdateEvents;\n            case GameLoopInterrupt::UpdateBlocks_KillBlock:\n            case GameLoopInterrupt::UpdateBlocks_TriggerHit:\n            case GameLoopInterrupt::UpdateBlocks_SwitchOn:\n            case GameLoopInterrupt::UpdateBlocks_SwitchOff:\n            case GameLoopInterrupt::UpdateBlocks_SwitchOff_KillBlock:\n                goto resume_UpdateBlocks;\n            case GameLoopInterrupt::IntroEvents:\n                goto resume_IntroEvents;\n            default:\n                break;\n            }\n        }\n\n        return;\n    }\n\n    if(g_gameLoopInterrupt.process_intro_events)\n    {\n        int A;\n\n        for(A = 0; A <= maxEvents; ++A)\n        {\n            // excluded in SMBX 1.3\n            if(A == 1)\n                continue;\n\n            if(A == EVENT_LEVEL_START || Events[A].AutoStart)\n            {\n                eventindex_t resume_index;\n                resume_index = ProcEvent_Safe(false, A, 0, EventContext::InitSetup);\n                while(resume_index != EVENT_NONE)\n                {\n                    g_gameLoopInterrupt.A = A;\n                    g_gameLoopInterrupt.C = resume_index;\n                    g_gameLoopInterrupt.site = GameLoopInterrupt::IntroEvents;\n                    return;\n\nresume_IntroEvents:\n                    A = g_gameLoopInterrupt.A;\n                    resume_index = g_gameLoopInterrupt.C;\n                    g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n                    resume_index = ProcEvent_Safe(true, resume_index, 0, EventContext::InitSetup);\n                }\n            }\n        }\n\n        g_gameLoopInterrupt.process_intro_events = false;\n    }\n\n    g_microStats.start_task(MicroStats::Script);\n    lunaLoop();\n\n    g_microStats.start_task(MicroStats::Controls);\n\n    if(!Controls::Update())\n    {\n        QuickReconnectScreen::g_active = true;\n\n        if(g_config.allow_drop_add && !TestLevel && XMessage::GetStatus() == XMessage::Status::local)\n            PauseGame(PauseCode::DropAdd, 0);\n    }\n\n    if(QuickReconnectScreen::g_active)\n        QuickReconnectScreen::Logic();\n\n    Integrator::sync();\n\n    g_microStats.start_task(MicroStats::Layers);\n\n    if(LevelMacro > LEVELMACRO_OFF)\n    {\n        UpdateMacro();\n\n        // was previously a nested frameloop, now that logic is still done in UpdateMacro,\n        // but per-frame -> we need to skip the rest of the frame if it didn't finish\n        if(LevelMacro == LEVELMACRO_KEYHOLE_EXIT)\n            return;\n    }\n\n    if(BattleMode)\n    {\n        if(BattleOutro > 0)\n        {\n            BattleOutro++;\n\n            if(g_config.EnableInterLevelFade && BattleOutro == 195)\n                g_levelScreenFader.setupFader(1, 0, 65, ScreenFader::S_FADE);\n\n            if(BattleOutro == 260)\n                EndLevel = true;\n        }\n    }\n\n    if(ErrorQuit)\n    {\n        EndLevel = true;\n        ErrorQuit = false;\n        LevelBeatCode = BEATCODE_QUIT;\n        pLogWarning(\"Quit level because of an error\");\n        XRender::clearBuffer();\n    }\n\n    if(EndLevel)\n    {\n        if(LevelBeatCode > 0)\n        {\n            if(Checkpoint == FullFileName)\n            {\n                pLogDebug(\"Clear check-points at GameLoop()\");\n                Checkpoint.clear();\n                CheckpointsList.clear();\n                g_curLevelMedals.reset_checkpoint();\n            }\n\n            // Quit to world map when finishing the sub-hub\n            if(!NoMap && IsHubLevel && !FileRecentSubHubLevel.empty() && GoToLevel.empty())\n            {\n                FileRecentSubHubLevel.clear();\n                ReturnWarp = 0;\n                ReturnWarpSaved = 0;\n            }\n        }\n\n        if(SwapCharAllowed())\n        {\n            pLogDebug(\"Save drop/add characters configuration at EndLevel\");\n            ConnectScreen::SaveChars();\n        }\n\n        speedRun_triggerLeave();\n        NextLevel();\n\n        Controls::Update(false);\n    }\n    else if(qScreen || (g_config.allow_multires && qScreen_canonical))\n    {\n        g_microStats.start_task(MicroStats::Effects);\n        UpdateEffects();\n        if(!g_config.modern_section_change)\n            speedRun_tick();\n        UpdateGraphics();\n        updateScreenFaders();\n\n#if defined(__16M__) && !defined(__CALICO__)\n        // make sure that streamed audio is still updated on non-threading 16M build\n        Sound_StreamUpdate();\n#endif\n    }\n    else if(BattleIntro > 0)\n    {\n        UpdateGraphics();\n        BlockFrames();\n        g_microStats.start_task(MicroStats::Sound);\n        UpdateSound();\n        g_microStats.start_task(MicroStats::NPCs);\n        For(A, 1, numNPCs)\n            NPCFrames(A);\n        BattleIntro--;\n        if(BattleIntro == 1)\n            PlaySound(SFX_Checkpoint);\n        updateScreenFaders();\n    }\n    else\n    {\n        if(MagicHand)\n            UpdateEditor();\n\n        ClearTriggeredEvents();\n        UpdateLayers(); // layers before/after npcs\n\nresume_UpdateNPCs:\n        g_microStats.start_task(MicroStats::NPCs);\n        if(UpdateNPCs())\n            return;\n\n        if(LevelMacro == LEVELMACRO_KEYHOLE_EXIT)\n            return; // stop on key exit\n\nresume_UpdateBlocks:\n        g_microStats.start_task(MicroStats::Blocks);\n        if(UpdateBlocks())\n            return;\n\n        g_microStats.start_task(MicroStats::Effects);\n        UpdateEffects();\n\nresume_UpdatePlayer:\n        g_microStats.start_task(MicroStats::Player);\n        if(UpdatePlayer())\n            return;\n\n        speedRun_tick();\n        // UpdateGraphics() now calls start_task internally\n        if(LivingPlayers() || BattleMode)\n            UpdateGraphics();\n        g_microStats.start_task(MicroStats::Sound);\n        UpdateSound();\n\nresume_UpdateEvents:\n        g_microStats.start_task(MicroStats::Events);\n        if(UpdateEvents())\n            return;\n//        If MagicHand = True Then UpdateEditor\n\n        updateScreenFaders();\n\n        // Pause game and CaptainN logic\n        if((LevelMacro == LEVELMACRO_OFF && CheckLiving() > 0) || SharedPauseForce)\n        {\n            // this is always able to pause the game even when CaptainN is enabled.\n            if(SharedPause)\n                PauseInit(PauseCode::PauseScreen, 0);\n            // don't let double-pause or double-toggle happen\n            else\n            {\n                for(int p = 1; p <= numPlayers && p <= maxLocalPlayers; p++)\n                {\n                    // only consider new start presses\n                    if(!Player[p].UnStart || !Player[p].Controls.Start)\n                        continue;\n\n                    // use limited, buggy code for non-player 1 in compat case\n                    if(p != 1 && !g_config.multiplayer_pause_controls)\n                    {\n                        if(CaptainN || FreezeNPCs)\n                        {\n                            Player[p].UnStart = false;\n                            FreezeNPCs = !FreezeNPCs;\n                            PlaySound(SFX_Pause);\n                        }\n\n                        // don't let double-pause or double-toggle happen\n                        break;\n                    }\n\n                    // the special NPC freeze toggling functionality from CaptainN\n                    if((CaptainN || FreezeNPCs) && PSwitchStop == 0)\n                    {\n                        Player[p].UnStart = false;\n                        if(FreezeNPCs)\n                        {\n                            FreezeNPCs = false;\n                            if(PSwitchTime > 0)\n                                ResumeMusic();\n                        }\n                        else\n                        {\n                            FreezeNPCs = true;\n                            if(PSwitchTime > 0)\n                                PauseMusic();\n                        }\n                        PlaySound(SFX_Pause);\n                    }\n                    // normally pause the game\n                    else\n                        PauseInit(PauseCode::PauseScreen, 0);\n\n                    // don't let double-pause or double-toggle happen\n                    break;\n                }\n            }\n        }\n    }\n\n    g_microStats.end_frame();\n}\n\nvoid MessageScreen_Init()\n{\n    switch(g_MessageType)\n    {\n    case MESSAGE_TYPE_SYS_ERROR:\n        SoundPause[SFX_SMGlass] = 0;\n        PlaySoundMenu(SFX_SMGlass);\n        break;\n#ifdef THEXTECH_ASSERTS_INGAME_MESSAGE\n    case MESSAGE_TYPE_SYS_FATAL_ASSERT:\n        StopMusic();\n        SoundPause[SFX_SMGlass] = 0;\n        PlayErrorSound(SFX_SMGlass);\n        break;\n#endif\n    default:\n        SoundPause[SFX_Message] = 0;\n        PlaySound(SFX_Message);\n        break;\n    }\n\n    if(g_MessageType >= MESSAGE_TYPE_SYS_INFO && MessageTitle.empty())\n    {\n        switch(g_MessageType)\n        {\n        case MESSAGE_TYPE_SYS_INFO:\n            MessageTitle = g_gameStrings.msgBoxTitleInfo;\n            break;\n        case MESSAGE_TYPE_SYS_WARNING:\n            MessageTitle = g_gameStrings.msgBoxTitleWarning;\n            break;\n        case MESSAGE_TYPE_SYS_ERROR:\n        case MESSAGE_TYPE_SYS_FATAL_ASSERT:\n            MessageTitle = g_gameStrings.msgBoxTitleError;\n            break;\n        default:\n            break;\n        }\n    }\n\n    MenuCursorCanMove = false;\n    MenuCursorCanMove_Back = false;\n    PrepareMessageDims();\n}\n\nbool MessageScreen_Logic(int plr)\n{\n    // can't check other local shared controls because this is global logic\n    bool menuDoPress = SharedPause;\n    bool menuBackPress = false;\n\n    if(GameMenu)\n    {\n        bool clicked = (SharedCursor.Primary || SharedCursor.Secondary);\n        menuDoPress |= clicked;\n        MenuMouseRelease = !clicked;\n    }\n\n    // there was previously code to copy all players' controls from the main player, but this is no longer necessary (and actively harmful in the SingleCoop case)\n\n    if(!g_config.multiplayer_pause_controls && plr == 0)\n        plr = 1;\n\n    for(int i = 1; i <= numPlayers; i++)\n    {\n        // in exclusive mode, skip other players\n        if(plr != 0 && i != plr)\n            continue;\n\n        const Controls_t& c = Player[i].Controls;\n\n        menuDoPress |= (c.Start || c.Jump);\n        menuBackPress |= c.Run;\n\n        // NEW: allow AltJump (Do in alt menu layout) to advance messages\n        if(g_config.multiplayer_pause_controls && c.AltJump)\n            menuDoPress = true;\n    }\n\n    if(!MenuCursorCanMove_Back)\n    {\n        if(!menuBackPress && MenuCursorCanMove)\n            MenuCursorCanMove_Back = true;\n\n        menuBackPress = false;\n    }\n    else if(menuBackPress)\n        MenuCursorCanMove_Back = false;\n\n    if(!MenuCursorCanMove)\n    {\n        if(!menuDoPress && !menuBackPress)\n            MenuCursorCanMove = true;\n\n        return false;\n    }\n\n    if(MenuCursorCanMove && (menuDoPress || menuBackPress))\n    {\n#if defined(THEXTECH_ASSERTS_INGAME_MESSAGE) && !defined(THEXTECH_NO_SDL_BUILD)\n        // When it's a fatal error, just quit everything immediately\n        if(g_MessageType == MESSAGE_TYPE_SYS_FATAL_ASSERT)\n        {\n            GracefulQuit(false);\n\n#ifdef __WIIU__\n            if(g_isHBLauncher)\n                SYSRelaunchTitle(0, NULL);\n#endif\n\n            Controls::Quit();\n            QuitMixerX();\n            g_frmMain.freeSystem();\n#   if defined(__WII__) || defined(__WIIU__)\n            exit(0);\n#   else\n            abort();\n#   endif\n        }\n#endif\n\n        MessageTitle.clear();\n        MessageText.clear();\n        g_MessageType = MESSAGE_TYPE_NORMAL;\n        return true;\n    }\n\n    return false;\n}\n\nstatic constexpr int s_max_pause_stack_depth = 4;\n\nstruct PauseLoopState\n{\n    int pause_player = 0;\n    PauseCode pause_stack[s_max_pause_stack_depth + 1];\n    typedef void (*callback_t)();\n    callback_t pause_stack_callback[s_max_pause_stack_depth + 1];\n    int pause_stack_depth = 0;\n};\n\nstatic PauseLoopState s_pauseLoopState;\n\n// initializes a certain pause screen (but does not reset the game loop)\nvoid PauseInit(PauseCode code, int plr, void (*callback)())\n{\n    // initialize pause from main game\n    if(GamePaused == PauseCode::None)\n    {\n        s_pauseLoopState = PauseLoopState();\n        s_pauseLoopState.pause_player = plr;\n\n        if(!GameMenu && !LevelEditor)\n        {\n            for(int A = numPlayers; A >= 1; A--)\n                SavedChar[Player[A].Character] = Player[A];\n        }\n\n        if(PSwitchTime > 0)\n            PauseMusic();\n    }\n    // push pause code stack\n    else if(s_pauseLoopState.pause_stack_depth < s_max_pause_stack_depth)\n        s_pauseLoopState.pause_stack_depth++;\n\n    // set pause code\n    GamePaused = code;\n\n    // store pause code and pause callback for this pause\n    s_pauseLoopState.pause_stack[s_pauseLoopState.pause_stack_depth] = GamePaused;\n    s_pauseLoopState.pause_stack_callback[s_pauseLoopState.pause_stack_depth] = callback;\n\n    // init correct pause screen type\n    if(code == PauseCode::Message)\n        MessageScreen_Init();\n    else if(code == PauseCode::PauseScreen)\n        PauseScreen::Init(s_pauseLoopState.pause_player, SharedPauseLegacy);\n    else if(code == PauseCode::DropAdd)\n        ConnectScreen::DropAdd_Start();\n    else if(code == PauseCode::Prompt)\n        PromptScreen::Init();\n    else if(code == PauseCode::Options)\n        OptionsScreen::Init();\n    else if(code == PauseCode::TextEntry)\n    {\n        // assume TextEntryScreen has already been inited through its Run function.\n    }\n\n    // sync system cursor\n    SyncSysCursorDisplay();\n\n    // resetFrameTimer();\n}\n\n// finishes the current pause and pops it from the pause stack\nstatic void s_PauseFinish(int stack_level);\n\n// a main loop for cases where the game is paused\nvoid PauseLoop()\n{\n    int cur_stack_level = s_pauseLoopState.pause_stack_depth;\n\n    g_microStats.start_task(MicroStats::Graphics);\n\n#if defined(THEXTECH_ASSERTS_INGAME_MESSAGE) && !defined(THEXTECH_NO_SDL_BUILD)\n    const bool is_fatal_message = (g_MessageType == MESSAGE_TYPE_SYS_FATAL_ASSERT);\n#else\n    constexpr bool is_fatal_message = false;\n#endif\n\n    if(!is_fatal_message)\n        speedRun_tick();\n\n    if(is_fatal_message)\n        UpdateGraphicsFatalAssert();\n    else if(GamePaused == PauseCode::Prompt)\n        PromptScreen::Render();\n    else if((LevelSelect && !GameMenu) || WorldEditor)\n        UpdateGraphics2();\n    else\n        UpdateGraphics();\n\n    g_microStats.start_task(MicroStats::Controls);\n\n    if(!Controls::Update())\n        QuickReconnectScreen::g_active = true;\n\n    if(QuickReconnectScreen::g_active)\n        QuickReconnectScreen::Logic();\n\n    g_microStats.start_task(MicroStats::Sound);\n\n    UpdateSound();\n    BlockFrames();\n\n    g_microStats.start_task(MicroStats::Effects);\n\n    UpdateEffects();\n\n    if(LevelSelect)\n        g_worldScreenFader.update();\n    else\n        updateScreenFaders();\n\n    // reset the active player if it is no longer present\n    if(s_pauseLoopState.pause_player > numPlayers)\n        s_pauseLoopState.pause_player = 0;\n\n    g_microStats.start_task(MicroStats::Script);\n\n    bool pause_done = false;\n\n    // run the appropriate pause logic\n    if(qScreen)\n    {\n        // prevent any logic or unpause from taking place\n\n        // qScreen takes place in WorldLoop, not world graphics\n        if(LevelSelect)\n            qScreen = Update_qScreen(1);\n    }\n    else if(GamePaused == PauseCode::Message)\n    {\n        if(MessageScreen_Logic(s_pauseLoopState.pause_player))\n            pause_done = true;\n    }\n    else if(GamePaused == PauseCode::Prompt)\n    {\n        if(PromptScreen::Logic())\n            pause_done = true;\n    }\n    else\n    {\n        // Check messages from the main pause screen first, then everything else. Important for catching delayed messages.\n        if(PauseScreen::Logic())\n        {\n            s_pauseLoopState.pause_stack_depth = 0;\n            pause_done = true;\n        }\n        else if(GamePaused == PauseCode::DropAdd)\n        {\n            if(ConnectScreen::Logic())\n                pause_done = true;\n        }\n        else if(GamePaused == PauseCode::Options)\n        {\n            if(OptionsScreen::Logic())\n                pause_done = true;\n        }\n        else if(GamePaused == PauseCode::TextEntry)\n        {\n            if(TextEntryScreen::Logic())\n                pause_done = true;\n        }\n    }\n\n    g_microStats.end_frame();\n\n    if(pause_done)\n        s_PauseFinish(cur_stack_level);\n}\n\nstatic void s_PauseFinish(int stack_level)\n{\n    // perform callback\n    if(s_pauseLoopState.pause_stack_callback[stack_level])\n        s_pauseLoopState.pause_stack_callback[stack_level]();\n\n    // special case: remove dropped frame from middle of pause stack\n    if(s_pauseLoopState.pause_stack_depth > stack_level)\n    {\n        for(int i = stack_level; i < s_pauseLoopState.pause_stack_depth; i++)\n        {\n            s_pauseLoopState.pause_stack[i] = s_pauseLoopState.pause_stack[i + 1];\n            s_pauseLoopState.pause_stack_callback[i] = s_pauseLoopState.pause_stack_callback[i + 1];\n        }\n\n        s_pauseLoopState.pause_stack_depth--;\n\n        return;\n    }\n\n    // pop pause stack\n    if(s_pauseLoopState.pause_stack_depth > 0)\n    {\n        s_pauseLoopState.pause_stack_depth--;\n        GamePaused = s_pauseLoopState.pause_stack[s_pauseLoopState.pause_stack_depth];\n    }\n    // resume main game\n    else\n    {\n        GamePaused = PauseCode::None;\n\n        if(PSwitchTime > 0)\n            ResumeMusic();\n\n        // prevent unexpected button presses\n        for(int i = 1; i <= numPlayers; i++)\n        {\n            Player[i].UnStart = false;\n            Player[i].CanJump = false;\n\n            // NEW: disable AltJump (used by alt menu controls and optionally used in standard menu controls)\n            if(g_config.multiplayer_pause_controls)\n                Player[i].CanAltJump = false;\n        }\n    }\n\n    MenuCursorCanMove = false;\n\n    // sync system cursor\n    SyncSysCursorDisplay();\n\n    // resetFrameTimer();\n}\n\nvoid PauseGame(PauseCode code, int plr)\n{\n//    double fpsTime = 0;\n//    int fpsCount = 0;\n\n    // allow waiting for the current pause frame to terminate by triggering PauseGame with PauseCode::None\n    if(code != PauseCode::None)\n        PauseInit(code, plr);\n\n    int cur_pause_stack_depth = s_pauseLoopState.pause_stack_depth;\n\n    do\n    {\n        if(canProceedFrame())\n        {\n            computeFrameTime1();\n            computeFrameTime2();\n\n            g_microStats.start_task(MicroStats::Controls);\n\n            XEvents::doEvents();\n            CheckActive();\n\n            PauseLoop();\n        }\n\n        if(!g_config.unlimited_framerate)\n            PGE_Delay(1);\n\n        if(!GameIsActive)\n            break;\n    } while(GamePaused != PauseCode::None && s_pauseLoopState.pause_stack_depth >= cur_pause_stack_depth);\n}\n"
  },
  {
    "path": "src/main/game_loop_interrupt.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GAME_LOOP_INTERRUPT_H\n#define GAME_LOOP_INTERRUPT_H\n\nstruct GameLoopInterrupt\n{\n    enum Site\n    {\n        None = 0,\n        IntroEvents,\n        UpdatePlayer_MessageNPC,\n        UpdatePlayer_TriggerTalk,\n        UpdatePlayer_SuperWarp,\n        UpdateNPCs_Activation_Generator,\n        UpdateNPCs_Activation_Self,\n        UpdateNPCs_Activation_Chain,\n        UpdateNPCs_KillNPC,\n        UpdateEvents,\n        UpdateBlocks_KillBlock,\n        UpdateBlocks_TriggerHit,\n        UpdateBlocks_SwitchOn,\n        UpdateBlocks_SwitchOff,\n        UpdateBlocks_SwitchOff_KillBlock,\n    };\n\n    // variables to help with resume\n    Site site = None;\n    int A, B, C, D, E, F, G;\n    bool bool1, bool2, bool3, bool4;\n    bool process_intro_events = false;\n};\n\nextern GameLoopInterrupt g_gameLoopInterrupt;\n\n#endif // #ifndef GAME_LOOP_INTERRUPT_H\n"
  },
  {
    "path": "src/main/game_save.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../globals.h\"\n#include \"../game_main.h\"\n#include \"config.h\"\n\n#include \"speedrunner.h\"\n#ifdef THEXTECH_ENABLE_LUNA_AUTOCODE\n#include \"../script/luna/lunavarbank.h\"\n#endif\n\n#include <Utils/files.h>\n#include <DirManager/dirman.h>\n#include <AppPath/app_path.h>\n#include <PGE_File_Formats/file_formats.h>\n#include <fmt_format_ne.h>\n#include <IniProcessor/ini_processing.h>\n#include <script/luna/lunacounter.h>\n#include <Logger/logger.h>\n\n#include \"main/level_save_info.h\"\n#include \"menu_main.h\"\n#include \"saved_layers.h\"\n\nstd::string makeGameSavePath(std::string epPath, std::string saveFile)\n{\n    std::string gameSaveDir = AppPathManager::gameSaveRootDir() + Files::basename(Files::dirname(epPath));\n    if(gameSaveDir.back() == ':')\n        gameSaveDir.pop_back();\n\n    if(!DirMan::exists(gameSaveDir))\n    {\n        pLogDebug(\"Creating directory [%s]\", gameSaveDir.c_str());\n        DirMan::mkAbsPath(gameSaveDir);\n    }\n\n    std::string ret = gameSaveDir + \"/\" + Files::basename(epPath) + \"-\" + saveFile;\n\n    pLogDebug(\"Save data path for ep [%s], file [%s] -> [%s]\", epPath.c_str(), saveFile.c_str(), ret.c_str());\n\n    return ret;\n}\n\nstatic void s_LoadCharacter(SavedChar_t& dest, const saveCharState& s)\n{\n    dest = SavedChar_t();\n\n    if((s.state < 1) || (s.state > numStates))\n        dest.State = 1;\n    else\n        dest.State = int(s.state);\n\n    dest.HeldBonus = int(s.itemID);\n\n    switch(s.mountID)\n    {\n    default:\n    case 0:\n        dest.Mount = 0;\n        dest.MountType = 0;\n        break;\n    case 1: case 2: case 3:\n        dest.Mount = int(s.mountID);\n    }\n\n    dest.MountType = int(s.mountType);\n    switch(s.mountID)\n    {\n    case 1:\n        if((s.mountType < 1) || (s.mountType > 3))\n            dest.MountType = 1;\n        break;\n    default:\n        break;\n    case 3:\n        if((s.mountType < 1) || (s.mountType > 8))\n            dest.MountType = 1;\n        break;\n    }\n\n    dest.Hearts = int(s.health);\n    dest.Character = int(s.id);\n}\n\nstatic std::string s_legacy_save_path(const SelectWorld_t& w, int save_idx)\n{\n    const std::string episode = Files::dirname(w.WorldFilePath);\n    return fmt::sprintf_ne(\"%ssave%d.sav\", episode.c_str(), save_idx);\n}\n\n\nvoid FindSaves()\n{\n//    std::string newInput;\n    const auto &w = SelectWorld[selWorld];\n    GamesaveData f;\n\n    for(auto A = 1; A <= maxSaveSlots; A++)\n    {\n        auto& info = SaveSlotInfo[A];\n\n        info = SaveSlotInfo_t();\n        info.ConfigDefaults = 0;\n\n        // Modern gamesave file\n        std::string saveFile = makeGameSavePath(w.WorldFilePath,\n                                                fmt::format_ne(\"save{0}.savx\", A));\n        // Old gamesave location at episode's read-only directory\n        std::string saveFileOld = s_legacy_save_path(w, A);\n        // Gamesave locker to make an illusion of absence of the gamesave\n        std::string saveFileOldLocker = makeGameSavePath(w.WorldFilePath,\n                                                         fmt::format_ne(\"save{0}.nosave\", A));\n\n        if((Files::fileExists(saveFile) && FileFormats::ReadExtendedSaveFileF(saveFile, f)) ||\n           (!Files::fileExists(saveFileOldLocker) && Files::fileExists(saveFileOld) && FileFormats::ReadSMBX64SavFileF(saveFileOld, f)))\n        {\n            int curActive = 0;\n            int maxActive = 0;\n\n            // \"game beat flag\"\n            maxActive++;\n            if(f.gameCompleted)\n                curActive++;\n\n            // how many items\n            if(f.lvl_path_count)\n                maxActive += (int)f.lvl_path_count;\n            else\n            {\n                maxActive += (int)f.visiblePaths.size();\n                maxActive += (int)f.visibleLevels.size();\n            }\n\n            // How much paths open\n            for(auto &p : f.visiblePaths)\n            {\n                if(p.second)\n                    curActive++;\n            }\n\n            // How much levels opened\n            for(auto &p : f.visibleLevels)\n            {\n                if(p.second)\n                    curActive++;\n            }\n\n            // How many stars collected\n            maxActive += (int(f.totalStars) * 4);\n            info.Stars = int(f.gottenStars.size());\n\n            curActive += (info.Stars * 4);\n\n            // calculate progress\n            if(maxActive > 0)\n                info.Progress = curActive * 100 / maxActive;\n            else\n                info.Progress = 100;\n\n            // load normal stats\n            info.Lives = f.lives;\n            info.Hundreds = (f.hundreds == 0) ? f.lives : ((f.hundreds > 0) ? f.hundreds - 1 : f.hundreds);\n            info.Coins = f.coins;\n            info.Score = f.points;\n\n            // load saved chars\n            for(int i = 1; i <= 5; i++)\n            {\n                info.SavedChar[i] = SavedChar_t();\n                info.SavedChar[i].Character = i;\n            }\n\n            for(auto &s : f.characterStates)\n            {\n                if((s.id < 1) || (s.id > 5))\n                    continue;\n                int i = int(s.id);\n                s_LoadCharacter(info.SavedChar[i], s);\n            }\n\n            // load settings from save file here\n            for(auto &s : f.userData.store)\n            {\n                if(s.name == \"_xt_ep_conf\" && s.location == saveUserData::DATA_GLOBAL)\n                {\n                    for(const saveUserData::DataEntry& e : s.data)\n                    {\n                        if(e.key == \"speedrun-mode\" && e.value != \"0\")\n                        {\n                            info.ConfigDefaults = -atoi(e.value.c_str());\n                            break;\n                        }\n                        // TODO: remove this legacy clause\n                        else if(e.key == \"enable-bugfixes\")\n                        {\n                            info.ConfigDefaults = (e.value == \"none\") ? Config_t::MODE_VANILLA : ((e.value == \"critical\") ? Config_t::MODE_CLASSIC : Config_t::MODE_MODERN);\n                            info.ConfigDefaults += 1;\n                        }\n                        else if(e.key == \"playstyle\")\n                        {\n                            info.ConfigDefaults = (e.value == \"vanilla\") ? Config_t::MODE_VANILLA : ((e.value == \"classic\") ? Config_t::MODE_CLASSIC : Config_t::MODE_MODERN);\n                            info.ConfigDefaults += 1;\n                        }\n                    }\n                    break;\n                }\n            }\n\n            // default config if not present\n            if(info.ConfigDefaults == 0)\n            {\n                if(f.meta.smbx64strict)\n                    info.ConfigDefaults = Config_t::MODE_VANILLA + 1;\n                else if(w.bugfixes_on_by_default)\n                    info.ConfigDefaults = Config_t::MODE_MODERN + 1;\n                else\n                    info.ConfigDefaults = Config_t::MODE_CLASSIC + 1;\n            }\n\n            // load timer info for existing save\n            std::string savePath = makeGameSavePath(w.WorldFilePath,\n                                                     fmt::format_ne(\"timers{0}.ini\", A));\n\n            if(Files::fileExists(savePath))\n            {\n                IniProcessing timer(savePath);\n                timer.beginGroup(\"timers\");\n                timer.read(\"total\", info.Time, 0);\n                timer.endGroup();\n            }\n\n            // load fails for existing save\n            savePath = makeGameSavePath(w.WorldFilePath,\n                                        fmt::format_ne(\"fails-{0}.rip\", A));\n\n            if(Files::fileExists(savePath))\n            {\n                gDeathCounter.counterFile = savePath;\n                gDeathCounter.TryLoadStats();\n                gDeathCounter.Recount();\n                info.FailsEnabled = true;\n                info.Fails = gDeathCounter.mCurTotalDeaths;\n                gDeathCounter.quit();\n            }\n        }\n    }\n}\n\n\nvoid SaveGame()\n{\n    int A = 0;\n\n    if(Cheater || !selSave)\n        return;\n\n    for(A = numPlayers; A >= 1; A--)\n        SavedChar[Player[A].Character] = Player[A];\n\n    // Clean-up from invalid entries\n    for(auto it = Star.begin(); it != Star.end(); )\n    {\n        if(it->level.empty())\n            it = Star.erase(it);\n        else\n            ++it;\n    }\n\n    numStars = (int)Star.size();\n\n    const auto &w = SelectWorld[selWorld];\n\n    GamesaveData sav;\n//    std::string savePath = SelectWorld[selWorld].WorldPath + fmt::format_ne(\"save{0}.savx\", selSave);\n    std::string savePath = makeGameSavePath(w.WorldFilePath,\n                                            fmt::format_ne(\"save{0}.savx\", selSave));\n    std::string legacyGamesaveLocker = makeGameSavePath(w.WorldFilePath,\n                                                        fmt::format_ne(\"save{0}.nosave\", selSave));\n\n//    Open SelectWorld[selWorld].WorldPath + \"save\" + selSave + \".sav\" For Output As #1;\n    sav.lives = Lives;\n    sav.hundreds = (g_100s >= 0) ? g_100s + 1 : g_100s;\n    sav.coins = uint32_t(Coins);\n    sav.points = uint32_t(Score);\n    sav.worldPosX = (int)WorldPlayer[1].Location.X;\n    sav.worldPosY = (int)WorldPlayer[1].Location.Y;\n\n    for(A = 1; A <= 5; A++)\n    {\n        saveCharState c;\n        c.id = static_cast<unsigned long>(A);\n        c.state = uint32_t(SavedChar[A].State);\n        c.itemID = uint32_t(SavedChar[A].HeldBonus);\n        c.mountID = uint32_t(SavedChar[A].Mount);\n        c.mountType = uint32_t(SavedChar[A].MountType);\n        c.health = uint32_t(SavedChar[A].Hearts);\n        sav.characterStates.push_back(c);\n    }\n    sav.musicID = uint32_t(curWorldMusic);\n    sav.musicFile = curWorldMusicFile;\n    sav.last_hub_warp = static_cast<unsigned long>(ReturnWarpSaved);\n    sav.last_hub_level_file = FileRecentSubHubLevel;\n\n    // ABOVE GETS SKIPPED BY FINDSAVES\n    sav.gameCompleted = BeatTheGame; // Can only get 99% until you finish the game;\n\n    sav.lvl_path_count = numWorldLevels + numWorldPaths;\n\n    for(A = 1; A <= numWorldLevels; A++)\n    {\n        if(WorldLevel[A].Active)\n            sav.visibleLevels.emplace_back(A, WorldLevel[A].Active);\n    }\n\n    for(A = 1; A <= numWorldPaths; A++)\n    {\n        if(WorldPath[A].Active)\n            sav.visiblePaths.emplace_back(A, WorldPath[A].Active);\n    }\n\n    for(A = 1; A <= numScenes; A++)\n    {\n        if(!Scene[A].Active)\n            sav.visibleScenery.emplace_back(A, Scene[A].Active);\n    }\n\n    for(const auto& star : Star)\n        sav.gottenStars.emplace_back(star.level, star.Section);\n\n    savedLayerSaveEntry savedLayer;\n    for(A = 0; ExportSavedLayer(nullptr, savedLayer, A); A++)\n        sav.savedLayers.push_back(savedLayer);\n\n    sav.totalStars = uint32_t(MaxWorldStars);\n\n#ifdef THEXTECH_ENABLE_LUNA_AUTOCODE\n    gSavedVarBank.WriteBank();\n    if(gLunaVarBank.name == \"LunaDLL\" && !gLunaVarBank.data.empty())\n        sav.userData.store.push_back(gLunaVarBank);\n#endif\n\n    // save extra settings here\n    g_config.SaveEpisodeConfig(sav.userData);\n\n    ExportLevelSaveInfo(sav);\n\n    FileFormats::WriteExtendedSaveFileF(savePath, sav);\n\n    if(Files::fileExists(legacyGamesaveLocker))\n        Files::deleteFile(legacyGamesaveLocker); // Remove the gamesave locker of legacy file\n\n    // Also, save the speed-running states\n    speedRun_saveStats();\n\n    AppPathManager::syncFs();\n}\n\nvoid LoadGame()\n{\n    int A = 0;\n    size_t i = 0;\n    const auto &w = SelectWorld[selWorld];\n//    std::string newInput;\n\n    GamesaveData sav;\n    std::string savePath = makeGameSavePath(w.WorldFilePath,\n                                            fmt::format_ne(\"save{0}.savx\", selSave));\n    std::string savePathOld = s_legacy_save_path(w, selSave);\n    std::string legacySaveLocker = makeGameSavePath(w.WorldFilePath,\n                                                    fmt::format_ne(\"save{0}.nosave\", selSave));\n\n    if(Files::fileExists(savePath))\n        FileFormats::ReadExtendedSaveFileF(savePath, sav);\n    else if(!Files::fileExists(legacySaveLocker) && Files::fileExists(savePathOld))\n        FileFormats::ReadSMBX64SavFileF(savePathOld, sav);\n    else\n    {\n        pLogDebug(\"Game save file not found: %s\", savePath.c_str());\n        return;\n    }\n\n    if(!sav.meta.ReadFileValid)\n    {\n        pLogWarning(\"Invalid game save file: %s [%s]; \", sav.meta.filename.c_str(), sav.meta.ERROR_info.c_str());\n        return;\n    }\n\n    // load settings from save file here\n    g_config.LoadEpisodeConfig(sav.userData);\n\n    Lives = sav.lives;\n    g_100s = (sav.hundreds == 0) ? sav.lives : ((sav.hundreds > 0) ? sav.hundreds - 1 : sav.hundreds);\n    Coins = int(sav.coins);\n    Score = int(sav.points);\n    BeatTheGame = sav.gameCompleted;\n    WorldPlayer[1].Location.X = sav.worldPosX;\n    WorldPlayer[1].Location.Y = sav.worldPosY;\n\n    if(Lives > 99)\n        Lives = 99;\n    if(Coins > 99)\n        Coins = 0;\n    if(Score > 9999990)\n        Score = 9999990;\n\n    if(g_100s > 9999)\n        g_100s = 9999;\n    else if(g_100s < -9999)\n        g_100s = -9999;\n\n    // reset score on load in Vanilla Mode (like SMBX 1.3)\n    if(g_config.playstyle == Config_t::MODE_VANILLA)\n        Score = 0;\n\n    curWorldMusic = int(sav.musicID);\n    curWorldMusicFile = sav.musicFile;\n\n    if(g_config.enable_last_warp_hub_resume)\n    {\n        ReturnWarp = int(sav.last_hub_warp);\n        FileRecentSubHubLevel = sav.last_hub_level_file;\n        if(ReturnWarp > maxWarps)\n            ReturnWarp = 0; // Invalid value\n    }\n    else\n    {\n        // Keep the vanilla behavior, and let players feel the pain!, MuhahahahA! >:-D\n        ReturnWarp = 0;\n        FileRecentSubHubLevel.clear();\n    }\n\n    ReturnWarpSaved = ReturnWarp;\n\n    for(A = 1, i = 0; A <= 5; A++, i++)\n    {\n        SavedChar[A] = SavedChar_t();\n        SavedChar[A].Character = A;\n    }\n\n    for(auto &s : sav.characterStates)\n    {\n        if((s.id < 1) || (s.id > 5))\n            continue;\n        A = int(s.id);\n\n        s_LoadCharacter(SavedChar[A], s);\n    }\n\n\n    for(auto &p : sav.visiblePaths)\n    {\n        A = static_cast<int>(p.first);\n        if(A > 0 && A <= maxWorldPaths)\n            WorldPath[A].Active = p.second;\n    }\n\n    for(auto &p : sav.visibleLevels)\n    {\n        A = static_cast<int>(p.first);\n        if(A > 0 && A <= maxWorldLevels)\n            WorldLevel[A].Active = p.second;\n    }\n\n    for(auto &p : sav.visibleScenery)\n    {\n        A = static_cast<int>(p.first);\n        if(A > 0 && A <= maxScenes)\n            Scene[A].Active = p.second;\n    }\n\n    if(Star.capacity() < sav.gottenStars.size())\n        Star.reserve(sav.gottenStars.size());\n\n    for(auto &p : sav.gottenStars)\n    {\n        Star_t star;\n        star.level = p.first;\n        star.Section = p.second;\n        Star.push_back(std::move(star));\n    }\n\n    for(auto& p : sav.savedLayers)\n        LoadSavedLayer(nullptr, p);\n\n    numStars = int(sav.gottenStars.size());\n\n    for(A = 1; A <= numPlayers; A++)\n        Player[A] = SavedChar[Player[A].Character];\n\n#ifdef THEXTECH_ENABLE_LUNA_AUTOCODE\n    gLunaVarBank = saveUserData::DataSection();\n    for(auto &s : sav.userData.store)\n    {\n        if(s.name == \"LunaDLL\" && s.location == saveUserData::DATA_GLOBAL)\n        {\n            gLunaVarBank = s;\n            break;\n        }\n    }\n\n    gSavedVarBank.TryLoadWorldVars();\n#endif\n\n    ImportLevelSaveInfo(sav);\n}\n\nvoid ClearGame(bool punnish)\n{\n    curWorldMusic = 0;\n    curWorldMusicFile.clear();\n\n    ReturnWarp = 0;\n    ReturnWarpSaved = ReturnWarp;\n\n    IsHubLevel = false;\n    FileRecentSubHubLevel.clear();\n\n    WorldPlayer[1].Location.X = -1;\n    WorldPlayer[1].Location.Y = -1;\n\n    for(int A = 1, i = 0; A <= 5; A++, i++)\n    {\n        SavedChar[A].State = 1;\n        SavedChar[A].HeldBonus = NPCID(0);\n        SavedChar[A].Mount = 0;\n        SavedChar[A].MountType = 0;\n        SavedChar[A].Hearts = 1;\n        SavedChar[A].Character = A;\n    }\n\n    for(int A = 1; A <= maxWorldPaths; ++A)\n        WorldPath[A].Active = false;\n\n    for(int A = 1; A <= maxWorldLevels; ++A)\n    {\n        WorldLevel[A].Active = false;\n        WorldLevel[A].save_info = LevelSaveInfo_t();\n    }\n\n    for(int A = 1; A <= maxScenes; ++A)\n        Scene[A].Active = true;\n\n    // maxStars = 0;\n    Star.clear();\n    numStars = 0;\n\n    LevelWarpSaveEntries.clear();\n\n#ifdef THEXTECH_ENABLE_LUNA_AUTOCODE\n    gLunaVarBank = saveUserData::DataSection();\n    gSavedVarBank.ClearBank();\n#endif\n\n    if(punnish && selSave) // Remove gamesave of user who was used a trap cheat\n        DeleteSave(selWorld, selSave);\n}\n\n\nvoid DeleteSave(int world, int save)\n{\n    const auto &w = SelectWorld[world];\n    std::vector<std::string> deleteList;\n\n#define AddFile(f) \\\n    deleteList.push_back(makeGameSavePath(w.WorldFilePath,\\\n                                          fmt::format_ne(f, save)));\n\n    AddFile(\"save{0}.savx\");\n    AddFile(\"timers{0}.ini\");\n    AddFile(\"deaths-{0}.rip\");\n    AddFile(\"fails-{0}.rip\");\n    AddFile(\"demos-{0}.dmo\");\n\n    // Clear all files in list\n    for(auto &s : deleteList)\n    {\n        if(Files::fileExists(s))\n            Files::deleteFile(s);\n    }\n\n    std::string legacySave = s_legacy_save_path(w, save);\n    std::string legacySaveLocker = makeGameSavePath(w.WorldFilePath,\n                                                    fmt::format_ne(\"save{0}.nosave\", save));\n\n    // If legacy gamesave file exists, make the locker file to make illusion that old file got been removed\n    if(Files::fileExists(legacySave))\n    {\n        auto *f = Files::open_file(legacySaveLocker, \"wb\");\n        if(f)\n        {\n            const char s[] = \"If this file exists, the Vanilla \\\"save*.sav\\\" inside the episode folder is ignored.\";\n            SDL_RWwrite(f, s, 1, sizeof(s) - 1);\n            SDL_RWclose(f);\n        }\n    }\n\n#undef AddFile\n\n#ifdef __EMSCRIPTEN__\n    AppPathManager::syncFs();\n#endif\n}\n\n\nstatic void copySaveFile(const SelectWorld_t& w, const char*file_mask, int src, int dst)\n{\n    std::string filePathSrc = makeGameSavePath(w.WorldFilePath,\n                                               fmt::format_ne(file_mask, src));\n    std::string filePathDst = makeGameSavePath(w.WorldFilePath,\n                                               fmt::format_ne(file_mask, dst));\n    Files::copyFile(filePathDst, filePathSrc, true);\n}\n\nvoid CopySave(int world, int src, int dst)\n{\n    const auto &w = SelectWorld[world];\n\n    std::string savePathSrc = makeGameSavePath(w.WorldFilePath,\n                                               fmt::format_ne(\"save{0}.savx\", src));\n    std::string savePathDst = makeGameSavePath(w.WorldFilePath,\n                                               fmt::format_ne(\"save{0}.savx\", dst));\n    std::string legacySaveLocker = makeGameSavePath(w.WorldFilePath,\n                                                    fmt::format_ne(\"save{0}.nosave\", dst));\n\n    if(!Files::fileExists(savePathSrc))\n    {\n        // Attempt to import an old game-save from the episode directory\n        std::string savePathOld = s_legacy_save_path(w, src);\n\n        GamesaveData sav;\n        bool succ = false;\n\n        if(Files::fileExists(savePathOld))\n            succ = FileFormats::ReadSMBX64SavFileF(savePathOld, sav);\n\n        if(succ)\n            FileFormats::WriteExtendedSaveFileF(savePathSrc, sav);\n\n        if(Files::fileExists(legacySaveLocker))\n            Files::deleteFile(legacySaveLocker);\n    }\n\n    Files::copyFile(savePathDst, savePathSrc, true);\n\n    copySaveFile(w, \"timers{0}.ini\", src, dst);\n    copySaveFile(w, \"fails-{0}.rip\", src, dst);\n    copySaveFile(w, \"deaths-{0}.rip\", src, dst);\n    copySaveFile(w, \"demos-{0}.dmo\", src, dst);\n\n#ifdef __EMSCRIPTEN__\n    AppPathManager::syncFs();\n#endif\n}\n"
  },
  {
    "path": "src/main/game_strings.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"main/game_strings.h\"\n\nGameContent g_gameStrings;\n\nvoid initGameStrings()\n{\n    g_gameStrings.msgBoxTitleInfo = \"Info\";\n    g_gameStrings.msgBoxTitleWarning = \"Warning!\";\n    g_gameStrings.msgBoxTitleError = \"Error!\";\n\n    g_gameStrings.loaderLoading = \"Loading...\";\n    g_gameStrings.loaderStatusLoadData = \"Loading data...\";\n    g_gameStrings.loaderStatusLoadFile = \"Load: {0}\";\n    g_gameStrings.loaderStatusGameInfo = \"Game info\";\n    g_gameStrings.loaderStatusTranslations = \"Translations\";\n    g_gameStrings.loaderStatusAssetPacks = \"Asset packs\";\n    g_gameStrings.loaderStatusFinishing = \"Finishing...\";\n\n    g_gameStrings.errorOpenFileFailed = \"Can't open \\\"{0}\\\": file doesn't exist or corrupted.\";\n#if defined(THEXTECH_INTERPROC_SUPPORTED) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    g_gameStrings.errorOpenIPCDataFailed = \"Can't proceed received file data because of corruption or other errors.\";\n#endif\n    g_gameStrings.errorTooOldEngine = \"Content requires a newer engine feature level ({0}) than the current version ({1}). Please update TheXTech.\";\n    g_gameStrings.errorTooOldGameAssets = \"Content requires a newer asset pack feature level ({0}) than the current asset pack ({1}). Please upgrade your game asset pack.\";\n\n    g_gameStrings.errorInvalidEnterWarp = \"Can't start the level \"\n                                          \"because of an invalid \"\n                                          \"entrance warp {1} was \"\n                                          \"specified.\\n\"\n                                          \"Total warp entries: {2}\\n\"\n                                          \"\\n\"\n                                          \"File: {0}\";\n    g_gameStrings.errorNoStartPoint = \"Can't start the level \"\n                                      \"because of no available \"\n                                      \"start points placed or \"\n                                      \"entrance warp specified.\\n\"\n                                      \"\\n\"\n                                      \"File: {0}\";\n\n#if defined(THEXTECH_INTERPROC_SUPPORTED) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    g_gameStrings.errorIPCTimeOut = \"No responce from the connected Editor. Game will be closed.\";\n#endif\n\n    g_gameStrings.messageScanningLevels = \"Scanning levels...\";\n    g_gameStrings.formatMinutesSeconds = \"{0}m{1}s\";\n\n#if defined(THEXTECH_INTERPROC_SUPPORTED) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    g_gameStrings.ipcStatusWaitingInput = \"Waiting for input data...\";\n    g_gameStrings.ipcStatusDataTransferStarted = \"Started data tansfer...\";\n    g_gameStrings.ipcStatusDataAccepted = \"Data accepted, the parsing started...\";\n    g_gameStrings.ipcStatusDataValid = \"Accepted data is valid\";\n    g_gameStrings.ipcStatusErrorTimeout = \"ERROR: Wait time out.\";\n    g_gameStrings.ipcStatusLoadingDone = \"Done. Starting game...\";\n#endif\n\n#if !defined(NO_WINDOW_FOCUS_TRACKING) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    g_gameStrings.screenPaused = \"Paused\";\n#endif\n\n    g_gameStrings.warpNeedStarCount = \"You need {0} {1} to enter.\";\n\n    g_gameStrings.pauseItemContinue = \"Continue\";\n    g_gameStrings.pauseItemRestartLevel = \"Restart Level\";\n    g_gameStrings.pauseItemResetCheckpoints = \"Reset Checkpoints\";\n    g_gameStrings.pauseItemQuitTesting = \"Quit Testing\";\n    g_gameStrings.pauseItemReturnToEditor = \"Return to Editor\";\n    g_gameStrings.pauseItemPlayerSetup = \"Player Setup\";\n    g_gameStrings.pauseItemEnterCode = \"Enter Code\";\n    g_gameStrings.pauseItemSaveAndContinue = \"Save and Continue\";\n    g_gameStrings.pauseItemSaveAndQuit = \"Save and Quit\";\n    g_gameStrings.pauseItemQuit = \"Quit\";\n\n\n    g_gameStrings.connectReconnectTitle = \"Reconnect\";\n    g_gameStrings.connectPressAButton = \"Press A Button\";\n    g_gameStrings.connectTestProfile = \"Test Profile\";\n    g_gameStrings.connectDisconnect = \"Disconnect\";\n    g_gameStrings.connectHoldStart = \"Hold Start\";\n\n    g_gameStrings.connectDropMe = \"Drop Me\";\n\n    g_gameStrings.connectDropPX = \"Drop P{0}\";\n    g_gameStrings.connectForceResume = \"Force Resume\";\n\n    g_gameStrings.connectWaitingForInputDevice = \"Waiting for input device...\";\n    g_gameStrings.connectPressSelectForControlsOptions_P1 = \"Press Select for\";\n    g_gameStrings.connectPressSelectForControlsOptions_P2 = \"Controls Options\";\n\n}\n"
  },
  {
    "path": "src/main/game_strings.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GAME_STRINGS_H\n#define GAME_STRINGS_H\n\n#include <string>\n\nstruct GameContent\n{\n    std::string msgBoxTitleInfo;\n    std::string msgBoxTitleWarning;\n    std::string msgBoxTitleError;\n\n    std::string loaderLoading;\n    std::string loaderStatusLoadData;\n    std::string loaderStatusLoadFile;\n    std::string loaderStatusGameInfo;\n    std::string loaderStatusTranslations;\n    std::string loaderStatusAssetPacks;\n    std::string loaderStatusFinishing;\n\n    std::string errorOpenFileFailed;\n#if defined(THEXTECH_INTERPROC_SUPPORTED) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    std::string errorOpenIPCDataFailed;\n#endif\n    std::string errorTooOldEngine;\n    std::string errorTooOldGameAssets;\n    std::string errorInvalidEnterWarp;\n    std::string errorNoStartPoint;\n#if defined(THEXTECH_INTERPROC_SUPPORTED) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    std::string errorIPCTimeOut;\n#endif\n    std::string messageScanningLevels;\n    std::string formatMinutesSeconds;\n\n#if defined(THEXTECH_INTERPROC_SUPPORTED) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    std::string ipcStatusWaitingInput;\n    std::string ipcStatusDataTransferStarted;\n    std::string ipcStatusDataAccepted;\n    std::string ipcStatusDataValid;\n    std::string ipcStatusErrorTimeout;\n    std::string ipcStatusLoadingDone;\n#endif\n\n#if !defined(NO_WINDOW_FOCUS_TRACKING) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    std::string screenPaused;\n#endif\n\n    std::string warpNeedStarCount;\n\n    std::string pauseItemContinue;\n    std::string pauseItemRestartLevel;\n    std::string pauseItemResetCheckpoints;\n    std::string pauseItemQuitTesting;\n    std::string pauseItemReturnToEditor;\n    std::string pauseItemPlayerSetup;\n    std::string pauseItemEnterCode;\n    std::string pauseItemSaveAndContinue;\n    std::string pauseItemSaveAndQuit;\n    std::string pauseItemQuit;\n\n\n    // ConnectScreen\n\n    std::string connectReconnectTitle;\n\n    std::string connectPressAButton;\n\n    std::string connectTestProfile;\n    std::string connectHoldStart;\n    std::string connectDisconnect;\n\n    std::string connectDropMe;\n\n    std::string connectForceResume;\n    std::string connectDropPX;\n\n    std::string connectWaitingForInputDevice;\n    std::string connectPressSelectForControlsOptions_P1;\n    std::string connectPressSelectForControlsOptions_P2;\n};\n\nextern GameContent g_gameStrings;\n\nvoid initGameStrings();\n\n#endif // GAME_STRINGS_H\n"
  },
  {
    "path": "src/main/gameplay_timer.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include <fmt_format_ne.h>\n#include <IniProcessor/ini_processing.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"core/render.h\"\n\n#include \"gameplay_timer.h\"\n#include \"graphics.h\"\n#include \"game_main.h\"\n#include \"globals.h\"\n#include \"config.h\"\n\n#include \"menu_main.h\"\n\nstd::string GameplayTimer::formatTime(int64_t t)\n{\n    std::string displayTime;\n\n    int64_t realMiliseconds = (t * 156) / 10;\n    int miliseconds = realMiliseconds % 1000;\n    int64_t realSeconds = realMiliseconds / 1000;\n    int seconds = realSeconds % 60;\n    int64_t realMinutes = realSeconds / 60;\n    int minutes = realMinutes % 60;\n    int64_t realHours = realMinutes / 60;\n    int hours = realHours % 24;\n    int days = realHours / 24;\n\n    if(days >= 1)\n        displayTime = fmt::sprintf_ne(\"%02d:%02d:%02d:%02d.%03d\", days, hours, minutes, seconds, miliseconds);\n    else if(realHours >= 1)\n        displayTime = fmt::sprintf_ne(\"%02d:%02d:%02d.%03d\", hours, minutes, seconds, miliseconds);\n    else if(realHours < 1)\n        displayTime = fmt::sprintf_ne(\"%02d:%02d.%03d\", minutes, seconds, miliseconds);\n\n    return displayTime;\n}\n\nvoid GameplayTimer::updateColorSpin(int delta)\n{\n    const int in_s = 178;\n    const int in_v = 255;\n\n    // note: changed from 360 degrees to 384 (64 * 6) degrees\n    m_colorSpinHue += delta;\n    if(m_colorSpinHue >= 384)\n        m_colorSpinHue -= 384;\n    else if(m_colorSpinHue < 0)\n        m_colorSpinHue += 384;\n\n    if(in_s <= 0) // < is bogus, just shuts up warnings\n    {\n        m_colorSpin = XTColor(in_v, in_v, in_v);\n        return;\n    }\n\n    int ff = m_colorSpinHue % 64;\n    int p = in_v * (256 - in_s) / 256;\n    int q = in_v * (256 - (in_s * ff) / 64) / 256;\n    int t = in_v * (256 - (in_s * (64 - ff)) / 64) / 256;\n\n    int i = m_colorSpinHue / 64;\n    switch(i)\n    {\n    case 0:\n        m_colorSpin = XTColor(in_v, t, p);\n        break;\n    case 1:\n        m_colorSpin = XTColor(q, in_v, p);\n        break;\n    case 2:\n        m_colorSpin = XTColor(p, in_v, t);\n        break;\n    case 3:\n        m_colorSpin = XTColor(p, q, in_v);\n        break;\n    case 4:\n        m_colorSpin = XTColor(t, p, in_v);\n        break;\n    case 5:\n    default:\n        m_colorSpin = XTColor(in_v, p, q);\n        break;\n    }\n}\n\nGameplayTimer::GameplayTimer() = default;\n\n\nvoid GameplayTimer::reset()\n{\n    m_invalidContinue = false;\n    m_cyclesInt = false;\n    m_cyclesAtWin = 0;\n    m_cyclesAtWinDisplay = 0;\n    m_cyclesCurrent = 0;\n    m_cyclesTotal = 0;\n    m_levelBlinkActive = false;\n    m_worldBlinkActive = false;\n    m_blinkingFactor = 0;\n}\n\nvoid GameplayTimer::resetCurrent()\n{\n    m_cyclesCurrent = 0;\n    m_levelBlinkActive = false;\n    m_worldBlinkActive = false;\n    m_blinkingFactor = 0;\n}\n\nvoid GameplayTimer::load()\n{\n    if(TestLevel || selSave == 0)\n    {\n        reset();\n        return;\n    }\n\n    IniProcessing o;\n    std::string savePath = makeGameSavePath(SelectWorld[selWorld].WorldFilePath,\n                                            fmt::format_ne(\"timers{0}.ini\", selSave));\n    o.open(savePath);\n\n    o.beginGroup(\"timers\");\n    o.read(\"int\", m_cyclesInt, false);\n    o.read(\"total\", m_cyclesTotal, 0);\n\n    // no longer permanently stop the timer following credits roll, but do store the cycles at first win for future display\n    bool legacy_fin = false;\n    o.read(\"fin\", legacy_fin, false);\n    int64_t cyclesAtWin_default = (legacy_fin) ? m_cyclesTotal : 0;\n\n    o.read(\"win\", m_cyclesAtWin, cyclesAtWin_default);\n    m_cyclesCurrent = 0; // Reset the counter\n    m_cyclesAtWinDisplay = 0;\n    o.endGroup();\n\n    if(!m_cyclesInt)\n        m_invalidContinue = true;\n}\n\nvoid GameplayTimer::save()\n{\n    // should Cheater also be a condition here?\n    if(TestLevel || !selSave || m_invalidContinue)\n        return;\n\n    IniProcessing o;\n    std::string savePath = makeGameSavePath(SelectWorld[selWorld].WorldFilePath,\n                                            fmt::format_ne(\"timers{0}.ini\", selSave));\n    o.open(savePath);\n\n    o.beginGroup(\"timers\");\n    o.setValue(\"int\", m_cyclesInt);\n    o.setValue(\"total\", m_cyclesTotal);\n    o.setValue(\"win\", m_cyclesAtWin);\n    o.endGroup();\n\n    o.writeIniFile();\n}\n\nvoid GameplayTimer::tick()\n{\n    // initialize timer\n    if(!m_cyclesInt)\n    {\n        m_cyclesInt = true;\n        m_cyclesCurrent = 0;\n        m_cyclesTotal = 0;\n        m_cyclesAtWin = 0;\n        m_cyclesAtWinDisplay = 0;\n        m_levelBlinkActive = false;\n        m_worldBlinkActive = false;\n        m_blinkingFactor = 0;\n    }\n\n    bool in_leveltest_restart_screen = (GamePaused == PauseCode::PauseScreen && LevelBeatCode < 0);\n    bool in_normal_level_play = (!LevelSelect && LevelMacro == 0);\n\n    if(!in_leveltest_restart_screen && (LevelSelect || in_normal_level_play))\n        m_cyclesCurrent += 1;\n    else if(!in_leveltest_restart_screen && g_config.show_playtime_counter == Config_t::PLAYTIME_COUNTER_ANIMATED)\n        m_levelBlinkActive = true;\n\n    m_cyclesTotal += 1;\n\n    if(m_levelBlinkActive)\n        updateColorSpin(5);\n\n    if(m_worldBlinkActive)\n    {\n        m_blinkingFactor += m_blinkingDir * 5;\n        if(m_blinkingFactor >= 75 || m_blinkingFactor <= -75)\n            m_blinkingDir *= -1;\n    }\n}\n\nvoid GameplayTimer::onBossDead()\n{\n    m_cyclesAtWinDisplay = m_cyclesTotal;\n\n    if(m_cyclesAtWin == 0)\n        m_cyclesAtWin = m_cyclesTotal;\n\n    if(g_config.show_playtime_counter == Config_t::PLAYTIME_COUNTER_ANIMATED)\n        m_worldBlinkActive = true;\n}\n\nvoid GameplayTimer::render()\n{\n    uint8_t a = (g_config.show_playtime_counter == Config_t::PLAYTIME_COUNTER_SUBTLE) ? 127 : 255;\n    // int x = (XRender::TargetW / 2) - (144 / 2);\n    int y = XRender::TargetH;\n\n    XTColor lc = m_levelBlinkActive ? m_colorSpin : XTColor(154, 154, 154);\n    uint8_t wc = m_worldBlinkActive ? (128 + m_blinkingFactor) : 255;\n\n    SuperPrintScreenCenter(formatTime(m_cyclesCurrent), 3, y - 34, lc.with_alpha(a));\n\n    if(m_invalidContinue)\n        SuperPrintScreenCenter(g_mainMenu.caseNone,         3, y - 18, XTColor(127, 127, 127, a));\n    else if(!TestLevel)\n    {\n        int64_t use_total = m_cyclesTotal;\n        if(m_cyclesAtWinDisplay)\n            use_total = m_cyclesAtWinDisplay;\n\n        SuperPrintScreenCenter(formatTime(use_total),       3, y - 18, XTColor(wc, 255, wc, a));\n    }\n}\n"
  },
  {
    "path": "src/main/gameplay_timer.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef GAMEPLAYTIMER_H\n#define GAMEPLAYTIMER_H\n\n#include <string>\n\n#include \"xt_color.h\"\n\nclass GameplayTimer\n{\n    bool    m_invalidContinue = false;\n    bool    m_cyclesInt = false;\n    // bool    m_cyclesFin = false;\n    int64_t m_cyclesAtWin = 0;\n    int64_t m_cyclesAtWinDisplay = 0;\n    int64_t m_cyclesCurrent = 0;\n    int64_t m_cyclesTotal = 0;\n\n    bool    m_levelBlinkActive = false;\n    bool    m_worldBlinkActive = false;\n    int     m_blinkingFactor = 0;\n    int     m_blinkingDir = 1;\n    XTColor m_colorSpin;\n    int     m_colorSpinHue = 0;\n    void    updateColorSpin(int delta);\n\npublic:\n    static std::string formatTime(int64_t t);\n\n    GameplayTimer();\n\n    void reset();\n    void resetCurrent();\n\n    void load();\n    void save();\n\n    void tick();\n\n    void onBossDead();\n\n    void render();\n};\n\n#endif // GAMEPLAYTIMER_H\n"
  },
  {
    "path": "src/main/hints.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include <cstdint>\n#include <string>\n\n#include <fmt_format_ne.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"fontman/font_manager.h\"\n\n#include \"core/render.h\"\n\n#include \"main/hints.h\"\n#include \"main/speedrunner.h\"\n#include \"main/translate.h\"\n#include \"main/trees.h\"\n#include \"game_main.h\"\n\n#include \"std_picture.h\"\n#include \"screen.h\"\n#include \"globals.h\"\n#include \"player.h\"\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n#include \"npc/npc_queues.h\"\n#include \"config.h\"\n#include \"controls.h\"\n#include \"graphics.h\"\n#include \"gfx.h\"\n#include \"eff_id.h\"\n\n\nnamespace XHints\n{\n\nstruct Hint\n{\n    const char* text_en;\n    const char* tr_id;\n    uint8_t (&check_priority)();\n    void (&draw_icon)(int, int);\n\n    Hint(const char* text_en, const char* tr_id, uint8_t (&check_priority)(), void (&draw_icon)(int, int))\n        : text_en(text_en), tr_id(tr_id), check_priority(check_priority), draw_icon(draw_icon) {}\n};\n\n\n// default hints defined here\n\nstatic void s_draw_purple_pet(const Controls_t& c, int x, int y)\n{\n    RenderControls(c, x + 96 / 2 - 76 / 2, y + 96 - 34, 76, 30, false, 255);\n\n    StdPicture& tex = GFXNPC[NPCID_PET_PURPLE];\n    XRender::renderTextureBasic(x + 96 / 3 - tex.w / 3, y + 96 - 34 - 4 - tex.h / 2, tex.w, tex.h / 2, tex, 0, tex.h / 2);\n}\n\nstatic void s_draw_purple_pet_down(int x, int y)\n{\n    Controls_t c;\n    c.Down = true;\n\n    s_draw_purple_pet(c, x, y);\n}\n\nstatic void s_draw_purple_pet_altrun(int x, int y)\n{\n    Controls_t c;\n    c.AltRun = true;\n\n    s_draw_purple_pet(c, x, y);\n}\n\nstatic void s_draw_no_lives_new(int x, int y)\n{\n    StdPicture &tex = GFXEffect[EFFID_CHAR1_DIE];\n\n    XRender::renderTextureBasic(x + 48 - tex.w / 2, y + 54 - tex.h, tex);\n\n    if(GFX.Balance.inited)\n        XRender::renderTextureBasic(x + 48 - GFX.Balance.w / 2, y + 64, GFX.Balance);\n}\n\nstatic void s_draw_no_lives_old(int x, int y)\n{\n    StdPicture &tex = GFXNPC[NPCID_POISON];\n\n    int frame_h = NPCTraits[NPCID_POISON].HeightGFX;\n    if(frame_h == 0)\n        frame_h = NPCTraits[NPCID_POISON].THeight;\n\n    XRender::renderTextureBasic(x + 48 - tex.w / 2, y + 48 - frame_h / 2,\n        tex.w, frame_h, tex, 0, 0);\n}\n\nstatic void s_draw_rainbow_surf(int x, int y)\n{\n    Controls_t c;\n    c.Down = true;\n\n    if(Player[1].Direction < 0)\n        c.Left = true;\n    else\n        c.Right = true;\n\n    if(GamePaused == PauseCode::None || CommonFrame % 128 < 64)\n        c.Run = true;\n\n    RenderControls(c, x + 96 / 2 - 76 / 2, y + 96 - 34, 76, 30, false, 255);\n\n    StdPicture& tex = GFXNPC[NPCID_FLIPPED_RAINBOW_SHELL];\n\n    int frame_h = NPCTraits[NPCID_FLIPPED_RAINBOW_SHELL].HeightGFX;\n    if(frame_h == 0)\n        frame_h = NPCTraits[NPCID_FLIPPED_RAINBOW_SHELL].THeight;\n    int frame_idx = ((CommonFrame % 64) / 4);\n\n    XRender::renderTextureBasic(x + 96 / 2 - tex.w / 2, y + 96 - 34 - 4 - frame_h, tex.w, frame_h, tex, 0, frame_h * frame_idx);\n}\n\nstatic void s_draw_char5_bombs(int x, int y)\n{\n    DrawPlayerRaw(x + 10, y + 96 - 56, 5, 2, 8, 1);\n\n    StdPicture& tex = GFXNPC[NPCID_BOMB];\n    XRender::renderTextureBasic(x + 96 + 4 - NPCTraits[NPCID_BOMB].WidthGFX, y + 96 - 8 - NPCTraits[NPCID_BOMB].HeightGFX, NPCTraits[NPCID_BOMB].WidthGFX, NPCTraits[NPCID_BOMB].HeightGFX, tex, 0, 0);\n}\n\nstatic void s_draw_shoe_block(int x, int y)\n{\n    StdPicture& plant = GFXNPC[NPCID_FIRE_PLANT];\n    XRender::renderTextureBasic(x + 96 - 4 - 32, y + 96 - 8 - NPCTraits[NPCID_FIRE_PLANT].THeight, 32, NPCTraits[NPCID_FIRE_PLANT].THeight, plant, 0, 0);\n\n    StdPicture& fire = GFXNPC[NPCID_PLANT_FIREBALL];\n    XRender::renderTextureBasic(x + 96 - 56, y + 96 - 56, 32, NPCTraits[NPCID_PLANT_FIREBALL].THeight, fire, 0, 0);\n\n    DrawPlayerRaw(x + 8 + 4, y + 96 - 8 - 32 - 22, 3, 1, 1, 1);\n\n    StdPicture& boot = GFXNPC[NPCID_RED_BOOT];\n    XRender::renderTextureBasic(x + 8, y + 96 - 8 - 32, 32, 32, boot, 0, 64);\n}\n\nstatic void s_draw_heavy_duck(int x, int y)\n{\n    StdPicture& plant = GFXNPC[NPCID_FIRE_PLANT];\n    XRender::renderTextureBasic(x + 96 - 4 - 32, y + 96 - 8 - NPCTraits[NPCID_FIRE_PLANT].THeight, 32, NPCTraits[NPCID_FIRE_PLANT].THeight, plant, 0, 0);\n\n    StdPicture& fire = GFXNPC[NPCID_PLANT_FIREBALL];\n    XRender::renderTextureBasic(x + 96 - 56, y + 96 - 56, 32, NPCTraits[NPCID_PLANT_FIREBALL].THeight, fire, 0, 0);\n\n    DrawPlayerRaw(x + 8, y + 96 - 56, 1, 6, 7, 1);\n}\n\nstatic void s_draw_gray_bricks(int x, int y)\n{\n    StdPicture& brick = GFXBlock[457];\n    XRender::renderTextureBasic(x + 24 + 4, y + 96 - 8 - 32, 32, 32, brick, 0, 0);\n\n    StdPicture& st = GFXNPC[NPCID_STATUE_POWER];\n    XRender::renderTextureBasic(x + 4, y + 4, 32, 32, st, 0, 0);\n\n    StdPicture& heavy = GFXNPC[NPCID_HEAVY_POWER];\n    XRender::renderTextureBasic(x + 96 - 4 - 32, y + 96 - 16 - 64, 32, 32, heavy, 0, 0);\n}\n\nstatic bool s_purple_pet_present()\n{\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Mount == 3 && Player[A].MountType == 6)\n            return true;\n    }\n\n    return false;\n}\n\nstatic uint8_t s_altrun_pound_applies()\n{\n    if(!g_config.pound_by_alt_run)\n        return 0;\n\n    if(!s_purple_pet_present())\n        return 0;\n\n    return 108;\n}\n\nstatic uint8_t s_down_pound_applies()\n{\n    if(g_config.pound_by_alt_run)\n        return 0;\n\n    if(!s_purple_pet_present())\n        return 0;\n\n    return 108;\n}\n\nstatic uint8_t s_no_lives_new_applies()\n{\n    if(!g_config.modern_lives_system)\n        return 0;\n\n    if(g_100s == 0)\n        return 100;\n\n    return 0;\n}\n\nstatic uint8_t s_no_lives_old_applies()\n{\n    if(g_config.modern_lives_system)\n        return 0;\n\n    if(Lives == 0)\n        return 100;\n\n    return 0;\n}\n\nstatic uint8_t s_rainbow_surf_applies()\n{\n    if(LevelSelect)\n        return 0;\n\n    for(NPCRef_t n : NPCQueues::Active.no_change)\n    {\n        if(n->Type == NPCID_FLIPPED_RAINBOW_SHELL && n->TimeLeft > 2)\n            return 107;\n    }\n\n    return 0;\n}\n\nstatic uint8_t s_char5_bombs_applies()\n{\n    if(LevelSelect)\n        return 0;\n\n    bool has_char5 = false;\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Character == 5)\n        {\n            if(Player[A].Bombs > 0)\n                return 102;\n            else\n            {\n                has_char5 = true;\n                break;\n            }\n        }\n    }\n\n    if(!has_char5)\n        return 0;\n\n    for(NPCRef_t n : NPCQueues::Active.no_change)\n    {\n        if(n->Type == NPCID_BOMB && n->TimeLeft > 2)\n            return 102;\n    }\n\n    return 0;\n}\n\nstatic uint8_t s_shoe_block_applies()\n{\n    if(LevelSelect)\n        return 0;\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Mount == 1 && Player[A].MountType == 2)\n            return 104;\n    }\n\n    return 0;\n}\n\nstatic uint8_t s_heavy_duck_applies()\n{\n    if(LevelSelect)\n        return 0;\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].State == 6 && Player[A].Character != 5)\n            return 101;\n    }\n\n    return 0;\n}\n\nstatic uint8_t s_gray_bricks_applies()\n{\n    if(LevelSelect)\n        return 0;\n\n    int found = 0;\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].State == 5 || Player[A].State == 6)\n        {\n            found = A;\n            break;\n        }\n    }\n\n    if(!found)\n        return 0;\n\n#ifndef LOW_MEM\n    const vScreen_t& vscreen = vScreenByPlayer(found);\n    for(const Block_t& b : treeBlockQuery(newLoc(-vscreen.X, -vscreen.Y, vscreen.Width, vscreen.Height), SORTMODE_NONE))\n    {\n        if(b.Type == 457 && !b.Hidden && !b.Invis)\n            return 106;\n    }\n#endif\n\n    return 30;\n}\n\nstatic const Hint s_hints[] = {\n    {\"Press Alt Run to pound downwards!\",                       \"pound-altrun\", s_altrun_pound_applies, s_draw_purple_pet_altrun},\n    {\"Press Down to pound downwards!\",                          \"pound-down\",   s_down_pound_applies,   s_draw_purple_pet_down},\n    {\"No coins left? Take a loan! Just watch your score...\",    \"no-lives-new\", s_no_lives_new_applies, s_draw_no_lives_new},\n    {\"Be careful - Game Over is imminent!\",                     \"no-lives-old\", s_no_lives_old_applies, s_draw_no_lives_old},\n    {\"Grab, run, hold down, and let go to surf.\",               \"rainbow-surf\", s_rainbow_surf_applies, s_draw_rainbow_surf},\n    {\"Press Run to collect and Alt Run to throw.\",              \"char5-bombs\",  s_char5_bombs_applies,  s_draw_char5_bombs},\n    {\"Duck to block most - but not all - flames!\",              \"heavy-duck\",   s_heavy_duck_applies,   s_draw_heavy_duck},\n    {\"Wearing this blocks most - but not all - flames!\",        \"shoe-block\",   s_shoe_block_applies,   s_draw_shoe_block},\n    {\"Some blocks are vulnerable to special powers.\",           \"gray-bricks\",  s_gray_bricks_applies,  s_draw_gray_bricks},\n};\n\nstatic constexpr size_t s_hint_count = sizeof(s_hints) / sizeof(Hint);\n\n\n// library state defined here\n\n//! translatable strings for hints\nstatic std::vector<std::string> s_translated_hint_text;\n\n//! index into s_translated_hint_text for each hint (for case of duplicated keys)\nstatic std::array<int, s_hint_count> s_hint_text_index;\n\n//! currently loaded hint\nstatic int s_select_hint = -1;\nstatic uint8_t s_select_hint_priority = 0;\n\n\n// main library functions here\n\n/**\n * \\brief idempotent; prepares hint strings for translation and use\n **/\nstatic void Init()\n{\n    if(!s_translated_hint_text.empty())\n        return;\n\n    for(size_t i = 0; i < s_hint_count; i++)\n    {\n        // check if a duplicate\n        size_t use_idx = 0;\n        for(; use_idx < i; use_idx++)\n        {\n            if(SDL_strcmp(s_hints[i].tr_id, s_hints[use_idx].tr_id) == 0)\n                break;\n        }\n\n        // if not duplicate, add unique string\n        if(use_idx == i)\n        {\n            s_hint_text_index[i] = s_translated_hint_text.size();\n            s_translated_hint_text.push_back(s_hints[i].text_en);\n        }\n        // otherwise, refer to existing string\n        else\n            s_hint_text_index[i] = s_hint_text_index[use_idx];\n    }\n}\n\nvoid ResetStrings()\n{\n    Init();\n\n    for(size_t i = s_hint_count; i != 0;)\n    {\n        --i;\n\n        if(s_hint_text_index[i] >= (int)s_translated_hint_text.size())\n            continue;\n\n        s_translated_hint_text[s_hint_text_index[i]] = s_hints[i].text_en;\n    }\n}\n\nvoid InitTranslations(XTechTranslate& translate)\n{\n    Init();\n\n    int top_index = -1;\n\n    for(size_t i = 0; i < s_hint_count; i++)\n    {\n        if(s_hint_text_index[i] <= top_index)\n            continue;\n\n        SDL_assert(s_hint_text_index[i] == top_index + 1); // no hints should be skipped\n\n        top_index = s_hint_text_index[i];\n\n        XTechTranslate::insert(translate.m_engineMap, fmt::format_ne(\"game.hint.{0}\", s_hints[i].tr_id), &(s_translated_hint_text[top_index]));\n    }\n}\n\nvoid Select()\n{\n    Init();\n\n    s_select_hint = -1;\n    s_select_hint_priority = 0;\n\n    for(size_t hint_i = 0; hint_i < s_hint_count; hint_i++)\n    {\n        const Hint& hint = s_hints[hint_i];\n\n        uint8_t hint_priority = hint.check_priority();\n\n        if(hint_priority <= s_select_hint_priority)\n            continue;\n\n        s_select_hint = (int)hint_i;\n        s_select_hint_priority = hint_priority;\n    }\n}\n\nvoid Draw(int top, int min_priority, int width)\n{\n    if(BattleMode || s_select_hint == -1 || s_hint_text_index[s_select_hint] >= (int)s_translated_hint_text.size())\n        return;\n\n    const Hint& hint = s_hints[s_select_hint];\n    const std::string& tr_hint = s_translated_hint_text[s_hint_text_index[s_select_hint]];\n\n    if(s_select_hint_priority < min_priority)\n        return;\n\n    constexpr int hint_box_height = 96;\n    const int hint_box_width = width;\n    constexpr int text_start = 110;\n\n    int hint_box_left = XRender::TargetW / 2 - hint_box_width / 2;\n\n    XRender::renderRect(hint_box_left -  4, top    , hint_box_width + 8, hint_box_height + 8, {  0,   0,   0});\n    XRender::renderRect(hint_box_left -  2, top + 2, hint_box_width + 4, hint_box_height + 4, {255, 255, 255});\n    XRender::renderRect(hint_box_left     , top + 4, hint_box_width    , hint_box_height    , {  0,   0,   0});\n    XRender::renderRect(hint_box_left + 96, top + 4,                  2, hint_box_height    , {255, 255, 255});\n\n    // draw icon\n    hint.draw_icon(hint_box_left, top + 4);\n\n    // draw text\n    std::string text = tr_hint;\n    PGE_Size text_size = FontManager::optimizeTextPx(text, hint_box_width - 2 - text_start, FontManager::fontIdFromSmbxFont(4));\n\n    FontManager::printText(text.c_str(), text.size(),\n                           hint_box_left + text_start,\n                           top + hint_box_height / 2 - text_size.h() / 2 + 4,\n                           FontManager::fontIdFromSmbxFont(4));\n}\n\n} // namespace XHints\n"
  },
  {
    "path": "src/main/hints.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#pragma once\n#ifndef THEXTECH_HINTS_H\n#define THEXTECH_HINTS_H\n\n#include <string>\n\nclass XTechTranslate;\n\nnamespace XHints\n{\n\n/**\n * \\brief resets hint strings to their English values\n **/\nvoid ResetStrings();\n\n/**\n * \\brief registers hint strings for translation\n **/\nvoid InitTranslations(XTechTranslate& translate);\n\n/**\n * \\brief select a hint based on the currently loaded level\n **/\nvoid Select();\n\n/**\n * \\brief draws a hint chosen with SelectHint() on the screen\n *\n * \\param top top y coordinate for hint box\n * \\param min_priority required priority for the hint to get drawn\n * \\param width width of hint box\n **/\nvoid Draw(int top, int min_priority, int width = 460);\n\n} // namespace XHints\n\n#endif // #ifndef THEXTECH_HINTS_H\n"
  },
  {
    "path": "src/main/level_file.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_timer.h\"\n\n#include <json/json_rwops_input.hpp>\n#include <json/json.hpp>\n#include <fmt_format_ne.h>\n#include <algorithm>\n\n#ifdef __16M__\n// used to clear loaded textures on level/world load\n#include \"core/render.h\"\n#endif\n\n#include \"../globals.h\"\n#include \"../game_main.h\" // SetupPhysics()\n#include \"../frame_timer.h\"\n#include \"../npc.h\"\n#include \"../load_gfx.h\"\n#include \"../custom.h\"\n#include \"../sound.h\"\n#include \"../sorting.h\"\n#include \"../layers.h\"\n#include \"saved_layers.h\"\n#include \"config.h\"\n#include \"../graphics.h\"\n#include \"../editor.h\"\n#include \"../npc_id.h\"\n#include \"blk_id.h\"\n#include \"level_file.h\"\n#include \"main/level_save_info.h\"\n#include \"main/level_medals.h\"\n#include \"main/game_strings.h\"\n#include \"main/screen_progress.h\"\n#include \"main/game_strings.h\"\n#include \"main/game_info.h\"\n#include \"trees.h\"\n#include \"npc_traits.h\"\n#include \"npc_special_data.h\"\n#include \"graphics/gfx_camera.h\"\n#include \"graphics/gfx_update.h\"\n#include \"npc/npc_activation.h\"\n#include \"npc/npc_queues.h\"\n#include \"translate_episode.h\"\n#include \"fontman/font_manager.h\"\n\n#include <DirManager/dirman.h>\n#include <Utils/files.h>\n#include <Utils/strings.h>\n#include <Utils/dir_list_ci.h>\n#include <Logger/logger.h>\n#include <PGE_File_Formats/file_formats.h>\n\n#include \"global_dirs.h\"\n#include \"screen_fader.h\"\n\n#include \"editor/editor_custom.h\"\n#include \"editor/editor_strings.h\"\n\n#include \"npc/section_overlap.h\"\n\n#include \"../version.h\"\n\n#ifdef THEXTECH_BUILD_GL_MODERN\n#    include \"core/opengl/gl_program_bank.h\"\n#endif\n\n#ifdef THEXTECH_ENABLE_LUAU\n#    include \"script/luau/test.h\"\n#endif\n\n\n// warning for improper rects\nstatic const char* s_improper_rect_warning = \"Attempted to set %s %d %s to %d, setting to 0\";\n\n// used to signal a total failure in a callback\n#ifdef PGEFL_CALLBACK_API\nusing callback_error = PGE_FileFormats_misc::callback_error;\n#else\nusing callback_error = std::runtime_error;\n#endif\n\n[[ noreturn ]] void priv_FeatureLevelError(const std::string& errMsg, int reqFeatureLevel, int curFeatureLevel)\n{\n    // use MessageText as a temporary store -- the error gets processed before returning to game code control\n    MessageText = fmt::format_ne(errMsg, reqFeatureLevel, curFeatureLevel);\n    throw callback_error(MessageText.c_str());\n}\n\nvoid addMissingLvlSuffix(std::string &fileName)\n{\n    if(!fileName.empty() && !Files::hasSuffix(fileName, \".lvl\") && !Files::hasSuffix(fileName, \".lvlx\") && !Files::hasSuffix(fileName, \"tst\"))\n    {\n        bool isAbsolute = Files::isAbsolute(fileName);\n        bool lvlxExists;\n\n        if(isAbsolute)\n            lvlxExists = Files::fileExists(fileName + \".lvlx\");\n        else\n            lvlxExists = !g_dirEpisode.resolveFileCaseExists(fileName + \".lvlx\").empty();\n\n        if(lvlxExists)\n            fileName += \".lvlx\";\n        else\n            fileName += \".lvl\";\n    }\n}\n\nvoid validateLevelName(std::string &out, const std::string &raw)\n{\n    if(raw.empty())\n    {\n        out.clear();\n        return;\n    }\n\n    if(!Strings::endsWith(raw, \".lvl\") && !Strings::endsWith(raw, \".lvlx\"))\n    {\n        std::string lx = g_dirEpisode.resolveFileCaseExists(raw + \".lvlx\"),\n                    lo = g_dirEpisode.resolveFileCaseExists(raw + \".lvl\");\n\n        if(!lx.empty())\n            out = lx;\n        else if(!lo.empty())\n            out = lo;\n        else\n            out = g_dirEpisode.resolveFileCase(raw);\n    }\n    else\n        out = g_dirEpisode.resolveFileCase(raw);\n}\n\nbool OpenLevel(std::string FilePath)\n{\n    addMissingLvlSuffix(FilePath);\n\n    PGE_FileFormats_misc::RWopsTextInput in(Files::open_file(FilePath, \"r\"), FilePath);\n\n    if(in.eof())\n    {\n        pLogWarning(\"File [%s] missing!\", FilePath.c_str());\n        MessageText = \"File does not exist\";\n        return false;\n    }\n\n    if(!OpenLevelData(in, FilePath))\n        return false;\n\n    OpenLevelDataPost();\n\n    return true;\n}\n\nstruct LevelLoad\n{\n    std::vector<std::string> current_layers;\n    std::vector<std::string> current_events;\n\n    std::vector<layerindex_t> final_layer_index;\n    std::vector<eventindex_t> final_event_index;\n\n    SaveInfoInit si;\n\n    int numPlayerStart = 0;\n\n    layerindex_t layers_finalized = 0;\n    eventindex_t events_finalized = 0;\n\n    void AddCurrentLayer(const std::string& layer_name)\n    {\n        current_layers.push_back(layer_name);\n        final_layer_index.push_back(LAYER_NONE);\n    }\n\n    layerindex_t FindLayer(const std::string& layer_name)\n    {\n        if(layer_name.empty())\n            return LAYER_NONE;\n\n        const char* s = layer_name.c_str();\n        if(s[0] == 'D' && s[1] == 'e' && s[2] == 'f' && s[3] == 'a' && s[4] == 'u' && s[5] == 'l' && s[6] == 't' && s[7] == '\\0')\n            return LAYER_DEFAULT;\n\n        for(layerindex_t A = 0; A < current_layers.size(); A++)\n        {\n            if(SDL_strcasecmp(current_layers[A].c_str(), layer_name.c_str()) == 0)\n                return A;\n        }\n\n        if(current_layers.size() == LAYER_NONE)\n            return LAYER_NONE;\n\n        layerindex_t old_size = (layerindex_t)current_layers.size();\n        AddCurrentLayer(layer_name);\n\n        return old_size;\n    }\n\n    layerindex_t FinalizeLayer(layerindex_t current_index)\n    {\n        if(current_index == LAYER_NONE)\n            return LAYER_NONE;\n\n        if(final_layer_index[current_index] == LAYER_NONE)\n        {\n            // note: both 0 and maxLayers are valid layer indexes\n            if(layers_finalized > maxLayers)\n                return LAYER_NONE;\n\n            final_layer_index[current_index] = layers_finalized;\n            layers_finalized++;\n        }\n\n        return final_layer_index[current_index];\n    }\n\n    void AddCurrentEvent(const std::string& event_name)\n    {\n        current_events.push_back(event_name);\n        final_event_index.push_back(EVENT_NONE);\n    }\n\n    eventindex_t FindEvent(const std::string& event_name)\n    {\n        if(event_name.empty())\n            return EVENT_NONE;\n\n        for(eventindex_t A = 0; A < current_events.size(); A++)\n        {\n            if(SDL_strcasecmp(current_events[A].c_str(), event_name.c_str()) == 0)\n                return A;\n        }\n\n        if(current_events.size() == EVENT_NONE)\n            return EVENT_NONE;\n\n        eventindex_t old_size = (eventindex_t)current_events.size();\n        AddCurrentEvent(event_name);\n\n        return old_size;\n    }\n\n    eventindex_t FinalizeEvent(eventindex_t current_index)\n    {\n        if(current_index == EVENT_NONE)\n            return EVENT_NONE;\n\n        if(final_event_index[current_index] == EVENT_NONE)\n        {\n            // note: both 0 and maxEvents are valid event indexes\n            if(events_finalized > maxEvents)\n                return EVENT_NONE;\n\n            final_event_index[current_index] = events_finalized;\n            events_finalized++;\n        }\n\n        return final_event_index[current_index];\n    }\n};\n\n#ifdef PGEFL_CALLBACK_API\nLevelLoadCallbacks OpenLevel_SetupCallbacks(LevelLoad& load);\n#else\nbool OpenLevel_Unpack(LevelLoad& load, LevelData& lvl);\n#endif\n\nvoid OpenLevel_FixLayersEvents(const LevelLoad& load);\n\nbool OpenLevelData(PGE_FileFormats_misc::TextInput& input, const std::string FilePath)\n{\n    if(FilePath == \".lvl\" || FilePath == \".lvlx\")\n        return false;\n\n    LevelLoad load;\n\n    qScreen = false;\n    qScreen_canonical = false;\n    ClearLevel();\n    BlockSound();\n    FreezeNPCs = false;\n    CoinMode = false;\n\n    numBlock = 0;\n    numBackground = 0;\n    numLocked = 0;\n    numNPCs = 0;\n    numWarps = 0;\n    numSections = 0;\n\n    // initialize basic layers and events left by ClearLevel()\n    // they'll get updated, but this is necessary to handle PGE-X files without them included\n    load.AddCurrentLayer(\"Default\");\n\n    for(int l = 0; l < 3; l++)\n        load.FinalizeLayer(load.FindLayer(Layer[l].Name));\n\n    for(int e = 0; e < 3; e++)\n        load.FinalizeEvent(load.FindEvent(Events[e].Name));\n\n\n    // set the file path and load custom configuration\n    const std::string& path = (FilePath.empty()) ? input.getFilePath() : FilePath;\n\n    FileNamePath = Files::dirname(path) + \"/\";\n    g_dirEpisode.setCurDir(FileNamePath);\n\n    FileName = g_dirEpisode.resolveDirCase(Files::basenameNoSuffix(path));\n    g_dirCustom.setCurDir(FileNamePath + FileName);\n\n    FileNameFull = Files::basename(path);\n    FullFileName = path;\n\n\n// Load Custom Stuff\n    LoadCustomConfig();\n    FindCustomPlayers();\n    FindCustomNPCs();\n    LoadCustomGFX();\n    LoadCustomSound();\n    FontManager::loadCustomFonts();\n\n\n    load.si.begin(g_curLevelMedals.should_initialize());\n\n#ifdef PGEFL_CALLBACK_API\n    LevelLoadCallbacks callbacks = OpenLevel_SetupCallbacks(load);\n    if(!FileFormats::OpenLevelFileT(input, callbacks))\n    {\n        pLogDebug(\"Failed to load [%s]\", FilePath.c_str());\n        load.si.on_error();\n        return false;\n    }\n#else\n    LevelData lvl;\n    if(!FileFormats::OpenLevelFileT(input, lvl))\n    {\n        pLogWarning(\"Error of level \\\"%s\\\" file loading: %s (line %ld).\",\n                    FilePath.c_str(),\n                    lvl.meta.ERROR_info.c_str(),\n                    lvl.meta.ERROR_linenum);\n        load.si.on_error();\n        return false;\n    }\n\n    if(!OpenLevel_Unpack(load, lvl))\n        return false;\n#endif\n\n    OpenLevel_FixLayersEvents(load);\n\n    return true;\n}\n\n#ifdef PGEFL_CALLBACK_API\nvoid OpenLevel_Error(void*, FileFormatsError& e)\n{\n    pLogWarning(\"Error of level file loading: %s (line %ld).\",\n                e.ERROR_info.c_str(),\n                e.ERROR_linenum);\n\n    MessageText = std::move(e.ERROR_info);\n}\n#endif\n\n#ifdef PGEFL_CALLBACK_API\nbool OpenLevel_Head(void* userdata, LevelHead& head)\n#else\nbool OpenLevel_Head(void* userdata, LevelData& head)\n#endif\n{\n    LevelLoad& load = *static_cast<LevelLoad*>(userdata);\n\n    // Level-wide settings\n    // maxStars = head.stars;\n    LevelName = head.LevelName;\n\n    constexpr unsigned int engineFeatureLevel = V_FEATURE_LEVEL;\n\n#ifdef PGEFL_CALLBACK_API\n    FileFormat = head.RecentFormat;\n    unsigned int reqFeatureLevel = head.engineFeatureLevel;\n#else\n    FileFormat = head.meta.RecentFormat;\n    unsigned int reqFeatureLevel = head.meta.engineFeatureLevel;\n#endif\n\n    if(reqFeatureLevel > engineFeatureLevel)\n        priv_FeatureLevelError(g_gameStrings.errorTooOldEngine, reqFeatureLevel, engineFeatureLevel);\n    else if(reqFeatureLevel > g_gameInfo.contentFeatureLevel)\n        priv_FeatureLevelError(g_gameStrings.errorTooOldGameAssets, reqFeatureLevel, g_gameInfo.contentFeatureLevel);\n\n    // Level-wide extra settings\n    if(!head.custom_params.empty())\n    {\n        // none supported yet\n    }\n\n    load.si.check_head(head);\n\n    return true;\n}\n\nbool OpenLevel_Section(void*, LevelSection& s)\n{\n    // preserving indent\n    {\n        // load failure, invalid section ID\n        if(s.id > maxSections || s.id < 0)\n            throw callback_error(\"Invalid section id\");\n\n        if(s.id + 1 > numSections)\n            numSections = s.id + 1;\n\n        int B = s.id;\n\n        LevelREAL[B].X = s.size_left;\n        LevelREAL[B].Y = s.size_top;\n        LevelREAL[B].Height = s.size_bottom;\n        LevelREAL[B].Width = s.size_right;\n        level[B] = static_cast<SpeedlessLocation_t>(LevelREAL[B]);\n        bgMusic[B] = int(s.music_id);\n        bgMusicREAL[B] = bgMusic[B];\n        // bgColor[B] = s.bgcolor;    // unused since SMBX64, removed\n        LevelWrap[B] = s.wrap_h;\n        LevelVWrap[B] = s.wrap_v; // EXTRA\n        OffScreenExit[B] = s.OffScreenEn;\n        Background2[B] = int(s.background);\n        Background2REAL[B] = Background2[B];\n        NoTurnBack[B] = s.lock_left_scroll;\n        UnderWater[B] = s.underwater;\n        if(s.music_file.empty())\n            CustomMusic[B].clear();\n        else\n            CustomMusic[B] = g_dirEpisode.resolveFileCase(s.music_file);\n\n        // NOTE: maybe this will get stored and used as a Lua table (instead of as a string) on platforms supporting Lua\n        if(LevelEditor && !s.custom_params.empty())\n            SetS(SectionJSONInfo[B], s.custom_params);\n\n#if defined(THEXTECH_BUILD_GL_MODERN)\n        if(!s.custom_params.empty())\n        {\n            try\n            {\n                const nlohmann::json section_data = nlohmann::json::parse(s.custom_params);\n\n                if(section_data.contains(\"effects\"))\n                {\n                    std::string screen_effect = section_data[\"effects\"].value(\"screenEffect\", \"\");\n                    if(screen_effect.empty())\n                    {\n                        const char* x2_screen_effects[] = {\"\", \"wavy\", \"lava\", \"caustics\", \"underwater\", \"mist\", \"sepia\", \"grayscale\", \"inverted\", \"gameboy\", \"gameboy-dither\"};\n\n                        int x2_effect_idx = section_data[\"effects\"].value(\"screenEffects\", 0);\n                        if(x2_effect_idx > 0 && x2_effect_idx < int(sizeof(x2_screen_effects) / sizeof(const char*)))\n                        {\n                            screen_effect += \"x2-\";\n                            screen_effect += x2_screen_effects[x2_effect_idx];\n                        }\n                    }\n\n                    if(!screen_effect.empty())\n                        SectionEffect[B] = ResolveGLProgram(\"screen-\" + screen_effect);\n\n                    std::string particles_fg = section_data[\"effects\"].value(\"fgParticles\", \"\");\n                    if(particles_fg.empty())\n                    {\n                        const char* x2_weather_effects[] = {\"\", \"rain\", \"snow\", \"fog\", \"sandstorm\", \"cinders\", \"wisps\"};\n\n                        int x2_weather_idx = section_data[\"effects\"].value(\"weather\", 0);\n                        if(x2_weather_idx > 0 && x2_weather_idx < int(sizeof(x2_weather_effects) / sizeof(const char*)))\n                        {\n                            particles_fg += \"x2-\";\n                            particles_fg += x2_weather_effects[x2_weather_idx];\n                        }\n                    }\n\n                    if(!particles_fg.empty())\n                        SectionParticlesFG[B] = ResolveGLParticleSystem(\"particles-\" + particles_fg);\n\n                    std::string particles_bg = section_data[\"effects\"].value(\"bgParticles\", \"\");\n                    if(!particles_bg.empty())\n                        SectionParticlesBG[B] = ResolveGLParticleSystem(\"particles-\" + particles_bg);\n                }\n\n                SectionLighting[B] = GLLightSystem();\n                if(section_data.contains(\"darkness\") && section_data[\"darkness\"].value(\"enableDarkness\", false))\n                {\n                    double shadow_strength = section_data[\"darkness\"].value(\"shadowStrength\", 0.5);\n                    SectionLighting[B].shadow_strength = (float)shadow_strength;\n\n                    int shadow_type = section_data[\"darkness\"].value(\"shadowType\", -1);\n                    if(shadow_type == -1)\n                    {\n                        int x2_shadows = section_data[\"darkness\"].value(\"shadows\", 0);\n\n                        if(x2_shadows == 1)\n                            SectionLighting[B].system_type = GLLightSystemType::shadow_rays;\n                        else if(x2_shadows == 2)\n                        {\n                            SectionLighting[B].system_type = GLLightSystemType::shadow_rays;\n                            SectionLighting[B].shadow_strength = 1.0f;\n                        }\n                        else\n                            SectionLighting[B].system_type = GLLightSystemType::shadow_none;\n                    }\n\n                    if(shadow_type == 1)\n                        SectionLighting[B].system_type = GLLightSystemType::shadow_rays;\n                    else if(shadow_type == 2)\n                        SectionLighting[B].system_type = GLLightSystemType::shadow_drop;\n                    else\n                        SectionLighting[B].system_type = GLLightSystemType::shadow_none;\n\n                    std::string ambient = section_data[\"darkness\"].value(\"ambient\", \"#181828ff\");\n\n                    SectionLighting[B].ambient = XTColorString(ambient);\n                }\n            }\n            catch(const std::exception &e)\n            {\n                pLogWarning(\"Failed to load Section %d JSON data: %s\", B, e.what());\n\n                SectionEffect[B] = LoadedGLProgramRef_t();\n                SectionParticlesBG[B] = LoadedGLProgramRef_t();\n                SectionParticlesFG[B] = LoadedGLProgramRef_t();\n                SectionLighting[B] = GLLightSystem();\n            }\n        }\n#endif\n    }\n    return true;\n}\n\n\nbool OpenLevel_PlayerStart(void* userdata, PlayerPoint& p)\n{\n    LevelLoad& load = *static_cast<LevelLoad*>(userdata);\n\n    if(load.numPlayerStart == 2)\n        return false;\n\n    {\n        // TODO: should try to use the startpoint's ID field if possible\n        load.numPlayerStart++;\n        int A = load.numPlayerStart;\n\n        PlayerStart[A].X = p.x;\n        PlayerStart[A].Y = p.y;\n        PlayerStart[A].Width = p.w;\n        PlayerStart[A].Height = p.h;\n\n        // don't allow improper rects\n        if(PlayerStart[A].Width < 0)\n        {\n            pLogWarning(s_improper_rect_warning, \"PlayerStart\", A, \"Width\", (int)p.w);\n            PlayerStart[A].Width = 0;\n        }\n\n        if(PlayerStart[A].Height < 0)\n        {\n            pLogWarning(s_improper_rect_warning, \"PlayerStart\", A, \"Height\", (int)p.h);\n            PlayerStart[A].Height = 0;\n        }\n\n        // Width and height are zero in LVLX files\n        // This indicates SMBX-based values for height, not the values of the actual asset pack\n        if(PlayerStart[A].Width == 0)\n            PlayerStart[A].Width = 24;\n\n        if(PlayerStart[A].Height == 0)\n        {\n            if(A == 1)\n                PlayerStart[A].Height = 54;\n            else if(A == 2)\n                PlayerStart[A].Height = 60;\n            else\n                PlayerStart[A].Height = 32;\n        }\n\n        // turn into size compatible with in-game editor UI\n        if(LevelEditor)\n        {\n            PlayerStart[A].X += PlayerStart[A].Width / 2;\n            PlayerStart[A].Y += PlayerStart[A].Height;\n\n            PlayerStart[A].Width = Physics.PlayerWidth[A][2];\n            PlayerStart[A].Height = Physics.PlayerHeight[A][2];\n\n            PlayerStart[A].X -= PlayerStart[A].Width / 2;\n            PlayerStart[A].Y -= PlayerStart[A].Height;\n        }\n\n        PlayerStart[A].Direction = p.direction;\n\n        if(load.numPlayerStart == 2)\n            return false;\n    }\n\n    return true;\n}\n\nbool OpenLevel_Layer(void* userdata, LevelLayer& l)\n{\n    LevelLoad& load = *static_cast<LevelLoad*>(userdata);\n\n    {\n        int A = load.FinalizeLayer(load.FindLayer(l.name));\n\n        // too many layers\n        if(A == LAYER_NONE)\n            throw callback_error(\"Too many layers\");\n\n        // update layer count\n        if(A + 1 > numLayers)\n            numLayers = A + 1;\n\n        auto &layer = Layer[A];\n\n        layer = Layer_t();\n\n        layer.Name = l.name;\n        layer.Hidden = l.hidden;\n        // hide layers after everything is done\n    }\n\n    return true;\n}\n\nbool OpenLevel_Event(void* userdata, LevelSMBX64Event& e)\n{\n    LevelLoad& load = *static_cast<LevelLoad*>(userdata);\n\n    {\n        int A = load.FinalizeEvent(load.FindEvent(e.name));\n\n        // too many events\n        if(A == EVENT_NONE)\n            throw callback_error(\"Too many events\");\n\n        // update events count\n        if(A + 1 > numEvents)\n            numEvents = A + 1;\n\n        auto &event = Events[A];\n\n        event.reinit();\n\n        event.Name = e.name;\n        if(!e.msg.empty())\n            SetS(event.Text, e.msg);\n        event.Sound = int(e.sound_id);\n        event.EndGame = int(e.end_game);\n\n        event.HideLayer.clear();\n        for(std::string& l : e.layers_hide)\n        {\n            layerindex_t found = load.FindLayer(l);\n            if(found != LAYER_NONE)\n                event.HideLayer.push_back(found);\n        }\n        event.ShowLayer.clear();\n        for(std::string& l : e.layers_show)\n        {\n            layerindex_t found = load.FindLayer(l);\n            if(found != LAYER_NONE)\n                event.ShowLayer.push_back(found);\n        }\n        event.ToggleLayer.clear();\n        for(std::string& l : e.layers_toggle)\n        {\n            layerindex_t found = load.FindLayer(l);\n            if(found != LAYER_NONE)\n                event.ToggleLayer.push_back(found);\n        }\n\n        // this was done in ClearLevel, but may be necessary here to refresh matters\n        for(int B = 0; B <= maxSections; B++)\n        {\n            auto &s = event.section[B];\n            s.music_id = LevelEvent_Sets::LESet_Nothing;\n            s.background_id = LevelEvent_Sets::LESet_Nothing;\n            s.music_file = STRINGINDEX_NONE;\n            s.position.X = LevelEvent_Sets::LESet_Nothing;\n            s.position.Y = 0;\n            s.position.Height = 0;\n            s.position.Width = 0;\n        }\n\n        // unpack section settings\n        for(auto &s : e.sets)\n        {\n            // this has a different meaning (padding) when we are not actually using callbacks\n            if(s.id == -1)\n                continue;\n            // invalid section ID\n            else if(s.id > maxSections || s.id < 0)\n                throw callback_error(\"Invalid section id\");\n\n            // relies on the fact that this is an ARRAY at TheXTech's side\n            auto &ss = event.section[s.id];\n\n            ss.music_id = int(s.music_id);\n            ss.background_id = int(s.background_id);\n            if(!s.music_file.empty())\n            {\n                SetS(ss.music_file, s.music_file);\n            }\n\n            auto &l = ss.position;\n            l.X = s.position_left;\n            l.Y = s.position_top;\n            l.Height = s.position_bottom;\n            l.Width = s.position_right;\n\n            ss.autoscroll = s.autoscrol;\n            // Simple style is only supported yet\n            if(s.autoscroll_style == LevelEvent_Sets::AUTOSCROLL_SIMPLE)\n            {\n                ss.autoscroll_x = (numf_t)num_t::from_double((float)s.autoscrol_x);\n                ss.autoscroll_y = (numf_t)num_t::from_double((float)s.autoscrol_y);\n            }\n        }\n\n        event.TriggerEvent = load.FindEvent(e.trigger);\n\n        event.TriggerDelay = e.trigger_timer;\n\n        event.LayerSmoke = e.nosmoke;\n\n        event.Controls.AltJump = e.ctrl_altjump;\n        event.Controls.AltRun = e.ctrl_altrun;\n        event.Controls.Down = e.ctrl_down;\n        event.Controls.Drop = e.ctrl_drop;\n        event.Controls.Jump = e.ctrl_jump;\n        event.Controls.Left = e.ctrl_left;\n        event.Controls.Right = e.ctrl_right;\n        event.Controls.Run = e.ctrl_run;\n        event.Controls.Start = e.ctrl_start;\n        event.Controls.Up = e.ctrl_up;\n\n        event.AutoStart = e.autostart;\n        event.MoveLayer = load.FindLayer(e.movelayer);\n        event.SpeedX = (numf_t)num_t::from_double((float)e.layer_speed_x);\n        event.SpeedY = (numf_t)num_t::from_double((float)e.layer_speed_y);\n\n        event.AutoX = (numf_t)num_t::from_double((float)e.move_camera_x);\n        event.AutoY = (numf_t)num_t::from_double((float)e.move_camera_y);\n        event.AutoSection = int(e.scroll_section);\n    }\n\n    return true;\n}\n\nbool OpenLevel_Block(void* userdata, LevelBlock& b)\n{\n    LevelLoad& load = *static_cast<LevelLoad*>(userdata);\n\n    {\n        numBlock++;\n        if(numBlock > maxBlocks)\n        {\n            numBlock = maxBlocks;\n            return false;\n        }\n\n        auto &block = Block[numBlock];\n\n        block = Block_t();\n\n        block.Location.X = b.x;\n        block.Location.Y = b.y;\n        block.Location.Height = b.h;\n        block.Location.Width = b.w;\n\n        // don't allow improper rects\n        if(block.Location.Width < 0)\n        {\n            pLogWarning(s_improper_rect_warning, \"Block\", numBlock, \"Width\", (int)b.w);\n            block.Location.Width = 0;\n        }\n\n        if(block.Location.Height < 0)\n        {\n            pLogWarning(s_improper_rect_warning, \"Block\", numBlock, \"Height\", (int)b.h);\n            block.Location.Height = 0;\n        }\n\n        block.Type = int(b.id);\n        block.DefaultType = block.Type;\n\n        if(b.npc_id > maxNPCType)\n        {\n            pLogWarning(\"Block contents %d is out of range (max NPC type %d), reset to 1 coin\", (int)b.npc_id, maxNPCType);\n            b.npc_id = -1;\n        }\n\n        block.Special = int(b.npc_id > 0 ? b.npc_id + 1000 : -1 * b.npc_id);\n\n        switch(block.Special) // Replace some legacy NPC codes with new\n        {\n        case 100: block.Special = 1000 + NPCID_POWER_S3; break;\n        case 102: block.Special = 1000 + NPCID_FIRE_POWER_S3; break;\n        case 103: block.Special = 1000 + NPCID_LEAF_POWER; break;\n        case 105: block.Special = 1000 + NPCID_PET_GREEN; break;\n        default: break;\n        }\n\n        block.DefaultSpecial = block.Special;\n\n        block.forceSmashable = false;\n        if(b.id == 90)\n        {\n            // if(lvl.meta.RecentFormat == LevelData::SMBX64 && lvl.meta.RecentFormatVersion < 20)\n            //     block.forceSmashable = true; // Restore bricks algorithm for turn blocks for SMBX19 and lower\n            // else\n            block.forceSmashable = (bool)b.special_data; // load it if set in the modern format\n        }\n\n        block.Invis = b.invisible;\n        block.Slippy = b.slippery;\n        block.Layer = load.FindLayer(b.layer);\n        block.TriggerDeath = load.FindEvent(b.event_destroy);\n        block.TriggerHit = load.FindEvent(b.event_hit);\n        block.TriggerLast = load.FindEvent(b.event_emptylayer);\n\n        if((block.Type == 186 || block.Type == 457) && (block.TriggerDeath != EVENT_NONE || block.TriggerLast != EVENT_NONE))\n        {\n            const char* error_string = \"Block 186 or 457 has a destroy event. TheXTech does not match SMBX 1.3 logic in this case. This content cannot be played in Vanilla mode.\";\n\n            pLogWarningS(error_string);\n\n            if(g_config.playstyle == Config_t::MODE_VANILLA)\n                throw callback_error(error_string);\n        }\n\n        if(IF_OUTRANGE(block.Type, 0, maxBlockType) || block.Type == BLKID_CONVEYOR_L_CONV || block.Type == BLKID_CONVEYOR_R_CONV) // Drop ID to 1 for blocks of out of range IDs\n        {\n            pLogWarning(\"Block-%d ID is out of range (max types %d), reset to Block-1\", block.Type, maxBlockType);\n            block.Type = 1;\n        }\n    }\n\n    return true;\n}\n\nbool OpenLevel_Background(void* userdata, LevelBGO& b)\n{\n    LevelLoad& load = *static_cast<LevelLoad*>(userdata);\n\n    {\n        numBackground++;\n        if(numBackground > maxBackgrounds)\n        {\n            numBackground = maxBackgrounds;\n            return false;\n        }\n\n        auto &bgo = Background[numBackground];\n\n        bgo = Background_t();\n\n        bgo.Location.X = b.x;\n        bgo.Location.Y = b.y;\n        bgo.Type = int(b.id);\n\n        if(IF_OUTRANGE(bgo.Type, 1, maxBackgroundType)) // Drop ID to 1 for BGOs of out of range IDs\n        {\n            pLogWarning(\"BGO-%d ID is out of range (max types %d), reset to BGO-1\", bgo.Type, maxBackgroundType);\n            bgo.Type = 1;\n        }\n\n        bgo.Layer = load.FindLayer(b.layer);\n        bgo.Location.Width = GFXBackground[bgo.Type].w;\n        bgo.Location.Height = BackgroundHeight[bgo.Type];\n\n        bgo.SetSortPriority(b.z_mode, std::round(b.z_offset));\n    }\n\n    return true;\n}\n\nbool OpenLevel_NPC(void* userdata, LevelNPC& n)\n{\n    LevelLoad& load = *static_cast<LevelLoad*>(userdata);\n\n    {\n        bool variantHandled = false;\n\n        numNPCs++;\n        if(numNPCs > maxNPCs)\n        {\n            numNPCs = maxNPCs;\n            return false;\n        }\n\n        auto &npc = NPC[numNPCs];\n\n        npc.Location.X = n.x;\n        npc.Location.Y = n.y;\n        if(!LevelEditor)\n            npc.Location.Y -= 0.01_n;\n        npc.Direction = n.direct;\n\n        if(n.id > maxNPCType) // Drop ID to 1 for NPCs of out of range IDs\n        {\n            pLogWarning(\"NPC-%d ID is out of range (max types %d), reset to NPC-1\", (int)n.id, maxNPCType);\n            n.id = 1;\n        }\n\n        if(n.contents > maxNPCType) // Drop contents to 0 for NPCs of out of range contents\n        {\n            pLogWarning(\"NPC contents %d is out of range (max types %d), reset to 0\", (int)n.contents, maxNPCType);\n            n.contents = 0;\n        }\n\n        npc.Type = NPCID(n.id);\n\n        bool force_variant = false;\n\n        if(npc.Type == NPCID_ITEM_BURIED || npc.Type == NPCID_ITEM_POD ||\n           npc.Type == NPCID_ITEM_BUBBLE || npc.Type == NPCID_ITEM_THROWER ||\n           (NPCNewContainerType(npc.Type) && FileFormat == FileFormats::LVL_PGEX))\n        {\n            npc.Special = (vbint_t)n.contents;\n            npc.DefaultSpecial = npc.Special;\n            npc.Variant = n.special_data;\n            variantHandled = true;\n\n            // this use of special_data was enabled in SMBX64\n            if(npc.Type == NPCID_ITEM_BURIED && npc.Special == NPCID_DOOR_MAKER)\n                force_variant = true;\n        }\n        else if(npc.Type == NPCID_DOOR_MAKER || npc.Type == NPCID_MAGIC_DOOR)\n        {\n            if(n.special_data < 0)\n                npc.Variant = 255;\n            else\n                npc.Variant = n.special_data;\n\n            // this use of special_data was enabled in SMBX64\n            force_variant = true;\n            variantHandled = true;\n        }\n\n        if(NPCIsAParaTroopa(npc))\n        {\n            npc.Special = (vbint_t)n.special_data;\n            npc.DefaultSpecial = npc.Special;\n        }\n\n        if(npc->IsFish)\n        {\n            npc.Special = (vbint_t)n.special_data;\n            npc.DefaultSpecial = npc.Special;\n        }\n\n        if(npc.Type == NPCID_FIRE_CHAIN)\n        {\n            npc.Special = (vbint_t)n.special_data;\n            npc.DefaultSpecial = npc.Special;\n        }\n\n        if(npc.Type == NPCID_STAR_EXIT || npc.Type == NPCID_STAR_COLLECT || npc.Type == NPCID_MEDAL)\n        {\n            npc.Variant = n.special_data;\n            variantHandled = true;\n        }\n\n        // don't load anything for SMBX64 files\n        if(FileFormat == FileFormats::LVL_SMBX64 && !force_variant)\n        {\n            npc.Variant = 0;\n        }\n        // only load Variant for NPCs that support it\n        else if(find_Variant_Data(npc.Type))\n        {\n            if((n.special_data < 0) || (n.special_data >= 256))\n                pLogWarning(\"Attempted to load npc Type %d with out-of-range variant index %ld\", (int)npc.Type, n.special_data);\n            else\n                npc.Variant = (uint8_t)n.special_data;\n        }\n        else\n        {\n            if(!variantHandled)\n                npc.Variant = 0;\n        }\n\n        // NEW: GFX expansion\n        npc.GFXSlot = (uint8_t)n.gfx_dx;\n\n        npc.Generator = n.generator;\n        if(npc.Generator)\n        {\n            npc.Special3 = ((uint16_t)(uint8_t)n.generator_direct << 8) + (uint8_t)n.generator_type;\n            npc.GeneratorTime() = 0;\n            npc.GeneratorTimeMax() = n.generator_period;\n\n            // npc.GeneratorDirection = n.generator_direct;\n            // npc.GeneratorEffect = n.generator_type;\n            // npc.GeneratorTimeMax = n.generator_period;\n        }\n\n        if(!n.msg.empty())\n            SetS(npc.Text, n.msg);\n\n        npc.Inert = n.friendly;\n        if(npc.Type == NPCID_SIGN)\n            npc.Inert = true;\n        npc.Stuck = n.nomove;\n        npc.DefaultStuck = npc.Stuck;\n\n        if(n.wings_type && !NPCBansWings(npc))\n        {\n            npc.DefaultWings = (WingBehaviors)n.wings_type;\n            npc.Wings = npc.DefaultWings;\n        }\n\n        npc.Legacy = n.is_boss;\n\n        npc.Layer = load.FindLayer(n.layer);\n        npc.TriggerActivate = load.FindEvent(n.event_activate);\n        npc.TriggerDeath = load.FindEvent(n.event_die);\n        npc.TriggerTalk = load.FindEvent(n.event_talk);\n        npc.TriggerLast = load.FindEvent(n.event_emptylayer);\n        npc.AttLayer = load.FindLayer(n.attach_layer);\n\n        npc.DefaultType = npc.Type;\n        npc.Location.Width = npc->TWidth;\n        npc.Location.Height = npc->THeight;\n        npc.DefaultLocationX = npc.Location.X;\n        npc.DefaultLocationY = npc.Location.Y;\n        npc.DefaultDirection = npc.Direction;\n\n        // allow every NPC to be active for one frame to initialize its internal state\n        npc.TimeLeft = 1;\n        npc.Active = true;\n        npc.JustActivated = 1;\n    }\n\n    // update the level's save info based on the NPC\n    load.si.check_npc(n);\n\n    return true;\n}\n\nbool OpenLevel_Warp(void* userdata, LevelDoor& w)\n{\n    LevelLoad& load = *static_cast<LevelLoad*>(userdata);\n\n    w.isSetIn = (!w.lvl_i);\n    w.isSetOut = (!w.lvl_o || (w.lvl_i));\n\n    if(!w.isSetIn && w.isSetOut)\n    {\n        w.ix = w.ox;\n        w.iy = w.oy;\n        w.length_i = w.length_o;\n        w.height_i = w.height_o;\n    }\n\n    if(!w.isSetOut && w.isSetIn)\n    {\n        w.ox = w.ix;\n        w.oy = w.iy;\n        w.length_o = w.length_i;\n        w.height_o = w.height_i;\n    }\n\n    {\n        numWarps++;\n        if(numWarps > maxWarps)\n        {\n            numWarps = maxWarps;\n            return false;\n        }\n\n        auto &warp = Warp[numWarps];\n\n        warp = Warp_t();\n\n        warp.PlacedEnt = true;\n        warp.PlacedExit = true;\n        warp.Entrance.X = w.ix;\n        warp.Entrance.Y = w.iy;\n        warp.Exit.X = w.ox;\n        warp.Exit.Y = w.oy;\n        warp.Direction = w.idirect;\n        warp.Direction2 = w.odirect;\n        warp.Effect = w.type;\n\n        // Work around filenames with no extension suffix and case missmatch\n        if(!w.lname.empty())\n        {\n            std::string level_name;\n            validateLevelName(level_name, w.lname);\n            SetS(warp.level, level_name);\n        }\n\n        warp.LevelWarp = int(w.warpto);\n        warp.LevelEnt = w.lvl_i;\n\n        warp.MapWarp = w.lvl_o;\n        warp.MapX = int(w.world_x);\n        warp.MapY = int(w.world_y);\n\n        warp.Stars = w.stars;\n        warp.Layer = load.FindLayer(w.layer);\n        warp.Hidden = w.unknown;\n\n        warp.NoYoshi = w.novehicles;\n        warp.WarpNPC = w.allownpc;\n        warp.Locked = w.locked;\n\n        // custom fields:\n        warp.twoWay = w.two_way;\n\n        warp.cannonExit = w.cannon_exit;\n        warp.cannonExitSpeed = w.cannon_exit_speed;\n        warp.eventEnter = load.FindEvent(w.event_enter);\n        warp.eventExit = load.FindEvent(w.event_exit);\n        if(!w.stars_msg.empty())\n            SetS(warp.StarsMsg, w.stars_msg);\n        warp.noPrintStars = w.star_num_hide;\n        warp.noEntranceScene = w.hide_entering_scene;\n\n        warp.stoodRequired = w.stood_state_required;\n        warp.transitEffect = w.transition_effect;\n\n        warp.Entrance.Height = w.height_i;\n        warp.Entrance.Width = w.length_i;\n        warp.Exit.Height = w.height_o;\n        warp.Exit.Width = w.length_o;\n\n        // FIXME: allow warp object to specify a transit effect name as a string\n        if(warp.transitEffect >= ScreenFader::S_CUSTOM)\n            warp.transitEffect = ScreenFader::loadTransitEffect(std::to_string(warp.transitEffect));\n    }\n\n    return true;\n}\n\nbool OpenLevel_Water(void* userdata, LevelPhysEnv& w)\n{\n    LevelLoad& load = *static_cast<LevelLoad*>(userdata);\n\n    {\n        numWater++;\n        if(numWater > maxWater)\n        {\n            numWater = maxWater;\n            return false;\n        }\n\n        auto &water = Water[numWater];\n\n        water = Water_t();\n\n        water.Location.X = w.x;\n        water.Location.Y = w.y;\n        water.Location.Width = w.w;\n        water.Location.Height = w.h;\n\n        // don't allow improper rects\n        if(water.Location.Width < 0)\n        {\n            pLogWarning(s_improper_rect_warning, \"Water\", numWater, \"Width\", (int)w.w);\n            water.Location.Width = 0;\n        }\n\n        if(water.Location.Height < 0)\n        {\n            pLogWarning(s_improper_rect_warning, \"Water\", numWater, \"Height\", (int)w.h);\n            water.Location.Height = 0;\n        }\n\n        // water.Buoy = w.buoy;\n        water.Type = (PHYSID)w.env_type;\n        water.Layer = load.FindLayer(w.layer);\n    }\n\n    return true;\n}\n\nvoid OpenLevel_FixLayersEvents(const LevelLoad& load)\n{\n    // Everything is loaded.\n    // Now we fix the layers and events that got temporary indexes.\n    // We also do some NPC logic that depends on the section boundaries.\n    for(int A = 1; A <= numBlock; A++)\n    {\n        if(Block[A].Layer != LAYER_NONE)\n            Block[A].Layer = load.final_layer_index[Block[A].Layer];\n\n        if(Block[A].TriggerDeath != EVENT_NONE)\n            Block[A].TriggerDeath = load.final_event_index[Block[A].TriggerDeath];\n\n        if(Block[A].TriggerHit != EVENT_NONE)\n            Block[A].TriggerHit = load.final_event_index[Block[A].TriggerHit];\n\n        if(Block[A].TriggerLast != EVENT_NONE)\n            Block[A].TriggerLast = load.final_event_index[Block[A].TriggerLast];\n    }\n\n    for(int A = 1; A <= numBackground; A++)\n    {\n        if(Background[A].Layer != LAYER_NONE)\n            Background[A].Layer = load.final_layer_index[Background[A].Layer];\n    }\n\n    // Prepare for NPC logic\n    CalculateSectionOverlaps();\n    int checkPointId = 0;\n\n    for(int A = 1; A <= numNPCs; A++)\n    {\n        if(NPC[A].Layer != LAYER_NONE)\n            NPC[A].Layer = load.final_layer_index[NPC[A].Layer];\n\n        if(NPC[A].AttLayer != LAYER_NONE)\n            NPC[A].AttLayer = load.final_layer_index[NPC[A].AttLayer];\n\n        if(NPC[A].TriggerActivate != EVENT_NONE)\n            NPC[A].TriggerActivate = load.final_event_index[NPC[A].TriggerActivate];\n\n        if(NPC[A].TriggerDeath != EVENT_NONE)\n            NPC[A].TriggerDeath = load.final_event_index[NPC[A].TriggerDeath];\n\n        if(NPC[A].TriggerTalk != EVENT_NONE)\n            NPC[A].TriggerTalk = load.final_event_index[NPC[A].TriggerTalk];\n\n        if(NPC[A].TriggerLast != EVENT_NONE)\n            NPC[A].TriggerLast = load.final_event_index[NPC[A].TriggerLast];\n\n        // section check logic is stubbed in the game menu, so don't use a strict check\n        if(!GameMenu)\n            NPC[A].Section = maxSections + 1;\n\n        CheckSectionNPC(A);\n\n        // if not fully in any section, use canonical camera for this (fixes final boss of Valtteri's Island - Revisited)\n        if(NPC[A].Section == maxSections + 1)\n        {\n            NPC[A].Section = 0;\n            NPC[A]._priv_force_canonical = true;\n        }\n\n\n        // Extra NPC load logic\n        NPC_t& npc = NPC[A];\n\n        if(npc.Type == NPCID_CHECKPOINT) // Is a checkpoint\n        {\n            checkPointId++;\n            if(g_config.fix_vanilla_checkpoints)\n            {\n                npc.Special = checkPointId;\n                npc.DefaultSpecial = int(npc.Special);\n            }\n        }\n        else if(npc.Type == NPCID_STAR_EXIT || npc.Type == NPCID_STAR_COLLECT) // Is a star\n        {\n            bool starFound = false;\n            for(const auto& star : Star)\n            {\n                bool bySection = npc.Variant == 0 && (star.Section == npc.Section || star.Section == -1);\n                bool byId = npc.Variant > 0 && -(star.Section + 100) == (int)npc.Variant;\n                if(star.level == FileNameFull && (bySection || byId))\n                    starFound = true;\n            }\n\n            if(starFound)\n            {\n                npc.Special = 1;\n                npc.DefaultSpecial = 1;\n                if(npc.Type == NPCID_STAR_COLLECT)\n                    npc.Killed = 9;\n            }\n        }\n        else if(NPCIsContainer(npc) &&\n                (NPCID(npc.Special) == NPCID_STAR_EXIT || NPCID(npc.Special) == NPCID_STAR_COLLECT)) // Is a container that has a star inside\n        {\n            bool starFound = false;\n            for(const auto& star : Star)\n            {\n                bool byId = npc.Variant > 0 && -(star.Section + 100) == (int)npc.Variant;\n                if(star.level == FileNameFull && byId)\n                    starFound = true;\n            }\n\n            if(starFound)\n            {\n                if(NPCID(npc.Special) == NPCID_STAR_COLLECT)\n                    npc.Killed = 9;\n            }\n        }\n    }\n\n    for(int A = 1; A <= numWarps; A++)\n    {\n        if(Warp[A].Layer != LAYER_NONE)\n            Warp[A].Layer = load.final_layer_index[Warp[A].Layer];\n\n        if(Warp[A].eventEnter != EVENT_NONE)\n            Warp[A].eventEnter = load.final_event_index[Warp[A].eventEnter];\n\n        if(Warp[A].eventExit != EVENT_NONE)\n            Warp[A].eventExit = load.final_event_index[Warp[A].eventExit];\n\n        syncLayers_Warp(A);\n    }\n\n    for(int A = 1; A <= numWater; A++)\n    {\n        if(Water[A].Layer != LAYER_NONE)\n            Water[A].Layer = load.final_layer_index[Water[A].Layer];\n\n        syncLayers_Water(A);\n    }\n\n    for(int A = 0; A < numEvents; A++)\n    {\n        if(Events[A].MoveLayer != LAYER_NONE)\n            Events[A].MoveLayer = load.final_layer_index[Events[A].MoveLayer];\n\n        if(Events[A].TriggerEvent != EVENT_NONE)\n            Events[A].TriggerEvent = load.final_event_index[Events[A].TriggerEvent];\n\n        // fix all layer indices\n        for(int i = 0; i < 3; i++)\n        {\n            auto& arr = (i == 0) ? Events[A].ShowLayer\n                : (i == 1) ? Events[A].HideLayer\n                : Events[A].ToggleLayer;\n\n            auto out_it = arr.begin();\n            for(auto in_it = out_it; in_it != arr.end(); ++in_it)\n            {\n                layerindex_t l = *in_it;\n\n                if(l != LAYER_NONE)\n                    l = load.final_layer_index[l];\n\n                if(l != LAYER_NONE)\n                    *(out_it++) = l;\n            }\n\n            arr.resize(out_it - arr.begin());\n        }\n    }\n\n    LAYER_USED_P_SWITCH = FindLayer(LAYER_USED_P_SWITCH_TITLE);\n}\n\n#ifdef PGEFL_CALLBACK_API\n\nLevelLoadCallbacks OpenLevel_SetupCallbacks(LevelLoad& load)\n{\n    LevelLoadCallbacks callbacks;\n\n    callbacks.on_error = OpenLevel_Error;\n    callbacks.load_head = OpenLevel_Head;\n    callbacks.load_section = OpenLevel_Section;\n    callbacks.load_startpoint = OpenLevel_PlayerStart;\n    callbacks.load_block = OpenLevel_Block;\n    callbacks.load_bgo = OpenLevel_Background;\n    callbacks.load_npc = OpenLevel_NPC;\n    callbacks.load_warp = OpenLevel_Warp;\n    callbacks.load_phys = OpenLevel_Water;\n    callbacks.load_layer = OpenLevel_Layer;\n    callbacks.load_event = OpenLevel_Event;\n\n    callbacks.userdata = &load;\n\n    return callbacks;\n}\n\n#else // #ifdef PGEFL_CALLBACK_API\n\nbool OpenLevel_Unpack(LevelLoad& load, LevelData& lvl)\n{\n    try\n    {\n        OpenLevel_Head(&load, lvl);\n\n        for(auto &s : lvl.sections)\n        {\n            if(!OpenLevel_Section(&load, s))\n                break;\n        }\n        for(auto &p : lvl.players)\n        {\n            if(!OpenLevel_PlayerStart(&load, p))\n                break;\n        }\n        for(auto &b : lvl.blocks)\n        {\n            if(!OpenLevel_Block(&load, b))\n                break;\n        }\n        for(auto &b : lvl.bgo)\n        {\n            if(!OpenLevel_Background(&load, b))\n                break;\n        }\n        for(auto &n : lvl.npc)\n        {\n            if(!OpenLevel_NPC(&load, n))\n                break;\n        }\n        for(auto &w : lvl.doors)\n        {\n            if(!OpenLevel_Warp(&load, w))\n                break;\n        }\n        for(auto &w : lvl.physez)\n        {\n            if(!OpenLevel_Water(&load, w))\n                break;\n        }\n        for(auto &l : lvl.layers)\n        {\n            if(!OpenLevel_Layer(&load, l))\n                break;\n        }\n        for(auto &e : lvl.events)\n        {\n            if(!OpenLevel_Event(&load, e))\n                break;\n        }\n    }\n    catch(const callback_error& e)\n    {\n        pLogWarning(\"Error of level \\\"%s\\\" file loading: %s.\",\n                    lvl.meta.filename.c_str(),\n                    e.what());\n\n        MessageText = e.what();\n\n        return false;\n    }\n\n    return true;\n}\n#endif // #else // #ifdef PGEFL_CALLBACK_API\n\nvoid OpenLevelDataPost()\n{\n    TranslateEpisode tr;\n\n    if(!GameMenu && !LevelEditor)\n        tr.loadLevelTranslation(FileNameFull);\n\n#ifdef THEXTECH_ENABLE_LUAU\n    load_test_script();\n#endif\n\n    IsEpisodeIntro = (StartLevel == FileNameFull);\n\n    if(IsEpisodeIntro)\n    {\n        IsHubLevel = NoMap;\n        FileRecentSubHubLevel.clear();\n    }\n\n    if(!IsHubLevel)\n    {\n        IsHubLevel = std::find(SubHubLevels.begin(), SubHubLevels.end(), FileNameFull) != SubHubLevels.end();\n\n        if(IsHubLevel)\n            FileRecentSubHubLevel = FileNameFull;\n    }\n\n    // turn NPC conveyor belts into new conveyor belts in modern mode\n    bool used_new_belts = false;\n\n    if(g_config.new_conveyor_belts && !LevelEditor)\n    {\n        const NPCTraits_t& t = NPCTraits[NPCID_CONVEYOR];\n\n        // check all of these conditions first\n        if(t.FrameOffsetX == 0 && t.FrameOffsetY == 0\n            && (t.WidthGFX == 0 || t.WidthGFX == t.TWidth)\n            && (t.HeightGFX == 0 || t.HeightGFX == t.THeight)\n            && t.IsABlock && t.CanWalkOn && t.MovesPlayer && !t.JumpHurt\n            && t.WontHurt && t.NoYoshi && t.Speedvar == 1)\n        {\n            int numNPCs_new = 0;\n\n            for(int A = 1; A <= numNPCs; A++)\n            {\n                const NPC_t& n_in = NPC[A];\n\n                if(n_in.Type == NPCID_CONVEYOR &&\n                   !n_in.Generator &&\n                   !n_in.Inert &&\n                   numBlock < maxBlocks &&\n                   n_in.AttLayer == LAYER_NONE)\n                {\n                    used_new_belts = true;\n\n                    numBlock++;\n\n                    auto &block = Block[numBlock];\n\n                    block = Block_t();\n\n                    block.Location = n_in.Location;\n\n                    block.Type = (n_in.Direction > 0) ? BLKID_CONVEYOR_R_CONV : BLKID_CONVEYOR_L_CONV;\n                    block.Location.SpeedX = (n_in.Direction > 0) ? 0.8_n : -0.8_n;\n\n                    block.DefaultType = block.Type;\n                    block.Layer = n_in.Layer;\n\n                    continue;\n                }\n\n                numNPCs_new++;\n                if(A != numNPCs_new)\n                    NPC[numNPCs_new] = NPC[A];\n            }\n\n            // clear the deleted NPCs (prevents NPC index shenanigans)\n            for(int A = numNPCs_new + 1; A <= numNPCs; A++)\n                NPC[A] = NPC_t();\n\n            numNPCs = numNPCs_new;\n        }\n\n        // (note: it might be possible to handle the case where MovesPlayer and IsABlock are both false but CanWalkOn and IsAHit1Block are true)\n        // (note: it might be possible to handle the case where SpeedVar is not 1 -- still risky if SpeedVar is greater than 10)\n    }\n\n    // TODO: disable this if the file indicates that it is already sorted\n    if(g_config.emulate_classic_block_order && (FileFormat == FileFormats::LVL_PGEX || used_new_belts))\n    {\n        qSortBlocks(1, numBlock);\n        qSortBackgrounds(1, numBackground);\n    }\n    else\n        qSortBackgrounds(1, numBackground, false);\n\n    // FindBlocks();\n    UpdateBackgrounds();\n    // FindSBlocks();\n    syncLayersTrees_AllBlocks();\n    syncLayers_AllBGOs();\n    syncLayers_AllNPCs();\n\n    NPC_ConstructCanonicalSet();\n\n    // moved the old event/layer loading code to the top\n    // since it is needed before loading objects now\n\n    for(int A = 0; A < numLayers; A++)\n    {\n        // check if layer is a saved layer\n        for(int i = 0; i < numSavedLayers; i++)\n        {\n            if(SDL_strcasecmp(Layer[A].Name.c_str(), SavedLayers[i].Name.data()) == 0)\n            {\n                Layer[A].SavedLayer = i + 1;\n                if(!LevelEditor)\n                    Layer[A].Hidden = !SavedLayers[i].Visible;\n                break;\n            }\n        }\n\n        // hide layer if needed\n        if(Layer[A].Hidden)\n            HideLayer(A, true);\n    }\n\n\n    if(LevelEditor)\n    {\n        if(numSections < 21)\n            numSections = 21;\n\n        ResetSectionScrolls();\n        SetSection(curSection);\n    }\n    else\n    {\n        FindStars();\n        LevelMacro = LEVELMACRO_OFF;\n        for(int A = 0; A < numSections; A++) // Automatically correct 608 section height to 600\n        {\n//            if(int(level[A].Height - level[A].Y) == 608)\n//                level[A].Y += 8;\n            int height = int(level[A].Height - level[A].Y);\n            if(height > 600 && height < 610)\n                level[A].Y = level[A].Height - 600; // Better and cleaner logic\n        }\n\n        int B = numBackground;\n        for(int A = 1; A <= numWarps; A++)\n        {\n            auto &w = Warp[A];\n            if(w.Effect == 2 && w.Stars > numStars)\n            {\n                B++;\n                numLocked++;\n                auto &bgo = Background[B];\n                bgo = Background_t();\n                bgo.Layer = w.Layer;\n                bgo.Hidden = w.Hidden;\n                bgo.Location.Width = 24;\n                bgo.Location.Height = 24;\n                bgo.Location.Y = w.Entrance.Y - bgo.Location.Height;\n                bgo.Location.X = w.Entrance.X + (w.Entrance.Width - bgo.Location.Width) / 2;\n                bgo.Type = 160;\n                syncLayers_BGO(B);\n\n                if(w.twoWay)\n                {\n                    B++;\n                    numLocked++;\n                    auto &bgo2 = Background[B];\n                    bgo2 = bgo;\n                    bgo2.Location = bgo.Location;\n                    bgo2.Location.Y = w.Exit.Y - bgo2.Location.Height;\n                    bgo2.Location.X = w.Exit.X + (w.Exit.Width - bgo2.Location.Width) / 2;\n                    syncLayers_BGO(B);\n                }\n            }\n            else if(w.Effect == 2 && w.Locked) // For locks\n            {\n                B++;\n                numLocked++;\n                auto &bgo = Background[B];\n                bgo = Background_t();\n                bgo.Layer = w.Layer;\n                bgo.Hidden = w.Hidden;\n                bgo.Location = w.Entrance;\n                bgo.Type = 98;\n                bgo.Location.Width = 16;\n                syncLayers_BGO(B);\n\n                if(w.twoWay)\n                {\n                    B++;\n                    numLocked++;\n                    auto &bgo2 = Background[B];\n                    bgo2 = bgo;\n                    bgo2.Location = w.Exit;\n                    bgo2.Location.Width = 16;\n                    syncLayers_BGO(B);\n                }\n            }\n        }\n    }\n\n    if(!LevelEditor)\n    {\n        g_curLevelMedals.prepare_lvl();\n        OrderMedals();\n    }\n\n    // If too much locks\n    SDL_assert_release(numBackground + numLocked <= (maxBackgrounds + maxWarps));\n\n    SoundPause[SFX_Camera] = 100;\n    resetFrameTimer();\n}\n\nvoid ClearLevel()\n{\n    int A = 0;\n    const NPC_t blankNPC = NPC_t();\n    const Water_t blankwater = Water_t();\n    const Warp_t blankWarp = Warp_t();\n    const Block_t blankBlock = Block_t();\n    const Background_t BlankBackground = Background_t();\n    const Location_t BlankLocation = Location_t();\n    const SpeedlessLocation_t BlankSpeedless;\n    const Effect_t blankEffect = Effect_t();\n    NPCTraits[NPCID_MEDAL].Score = 6;\n    RestoreWorldStrings();\n    LevelName.clear();\n    IsHubLevel = false;\n    // default file format if level header is missing\n    FileFormat = FileFormats::LVL_PGEX;\n    // removed because the same logic is called inside of LoadCustomConfig()\n    // ResetCustomConfig();\n    SetupPhysics();\n    LoadNPCDefaults();\n    LoadPlayerDefaults();\n    // noUpdate = true;\n    BlocksSorted = true;\n    qScreen = false;\n    qScreen_canonical = false;\n\n#ifdef __16M__\n    XRender::clearAllTextures();\n#endif\n\n    SectionJSONInfo.fill(STRINGINDEX_NONE);\n\n#ifdef THEXTECH_BUILD_GL_MODERN\n    SectionEffect.fill(LoadedGLProgramRef_t());\n    SectionParticlesBG.fill(LoadedGLProgramRef_t());\n    SectionParticlesFG.fill(LoadedGLProgramRef_t());\n    SectionLighting.fill(GLLightSystem());\n#endif\n\n    UnloadCustomGFX();\n    doShakeScreenClear();\n    ResetCameraPanning();\n    treeLevelCleanAll();\n    FontManager::clearLevelFonts();\n\n    invalidateDrawBlocks();\n    invalidateDrawBGOs();\n    NPCQueues::clear();\n\n    AutoUseModern = false;\n\n    numSections = 0;\n\n    for(A = 1; A <= newEventNum; A++)\n    {\n        NewEvent[A] = EVENT_NONE;\n        newEventDelay[A] = 0;\n    }\n\n    for(A = 0; A <= maxSections; A++)\n    {\n        AutoX[A] = 0;\n        AutoY[A] = 0;\n    }\n\n    numEvents = 3;\n    newEventNum = 0;\n    for(A = 0; A <= maxEvents; A++)\n        InitializeEvent(Events[A]);\n\n    for(A = 0; A <= maxWater; A++)\n        Water[A] = blankwater;\n\n    numWater = 0;\n    Events[0].Name = \"Level - Start\";\n    Events[1].Name = \"P Switch - Start\";\n    Events[2].Name = \"P Switch - End\";\n    curMusic = 0;\n    curStars = 0;\n    // maxStars = 0;\n    g_curLevelMedals.reset_lvl();\n\n    InvincibilityTime = 0;\n    PSwitchTime = 0;\n    PSwitchStop = 0;\n    BeltDirection = 1;\n    StopMusic();\n    Layer[0] = Layer_t();\n    Layer[0].Name = \"Default\";\n    Layer[0].Hidden = false;\n    Layer[1] = Layer_t();\n    Layer[1].Name = \"Destroyed Blocks\";\n    Layer[1].Hidden = true;\n    Layer[2] = Layer_t();\n    Layer[2].Name = \"Spawned NPCs\";\n    Layer[2].Hidden = false;\n\n    LAYER_USED_P_SWITCH = LAYER_NONE;\n\n    numLayers = 3;\n    for(A = 3; A <= maxLayers; A++)\n    {\n        Layer[A] = Layer_t();\n        // all of these are cleared by reinitializing the layer\n        // if(A > 2)\n        // {\n        //     Layer[A].Name = \"\";\n        //     Layer[A].Hidden = false;\n        // }\n        // Layer[A].SpeedX = 0;\n        // Layer[A].SpeedY = 0;\n    }\n\n//    If LevelEditor = True Or MagicHand = True Then\n    if(LevelEditor || MagicHand)\n    {\n//        MessageText = \"\"\n        MessageText.clear();\n//        frmNPCs.chkMessage.Value = 0\n//        frmBlocks.chkFill.Value = 0\n//        frmEvents.txtEvent.Text = \"\"\n//        noUpdate = True\n        // noUpdate = true;\n//        frmEvents.RefreshEvents\n//        frmLayers.lstLayer.Clear\n//        frmLayers.lstLayer.AddItem \"Default\"\n//        frmLayers.lstLayer.AddItem \"Destroyed Blocks\"\n//        frmLayers.lstLayer.AddItem \"Spawned NPCs\"\n//        frmLayers.lstLayer.Selected(1) = False\n//        frmLayers.lstLayer.Selected(2) = True\n//        frmLayers.lstLayer.Selected(0) = True\n//        frmLayers.cmdDelete.Enabled = False\n//        frmLayers.txtLayer.Enabled = False\n//        frmLevelEditor.optCursor(13).Value = True\n//    End If\n    }\n\n    for(A = -128; A <= maxNPCs; A++)\n        NPC[A] = blankNPC;\n    numNPCs = 0;\n\n    for(A = 1; A <= maxBlocks; A++)\n        Block[A] = blankBlock;\n    numBlock = 0;\n\n    for(A = 1; A <= maxBackgrounds; A++)\n        Background[A] = BlankBackground;\n\n    for(A = 0; A <= maxSections; A++)\n    {\n        Background2[A] = 0;\n        // bgColor[A] = 0xF89868;    // unused since SMBX64, removed\n        bgMusic[A] = 0;\n        level[A] = BlankSpeedless;\n        LevelREAL[A] = IntegerLocation_t();\n        LevelWrap[A] = false;\n        LevelVWrap[A] = false;\n        // LevelChop[A] = 0; // unused since SMBX64, removed\n        NoTurnBack[A] = false;\n        UnderWater[A] = false;\n        OffScreenExit[A] = false;\n        CustomMusic[A] = \"\";\n    }\n\n    for(A = 1; A <= numWarps; A++)\n        Warp[A] = blankWarp;\n    numWarps = 0;\n\n    for(A = 1; A <= numEffects; A++)\n        Effect[A] = blankEffect;\n    numEffects = 0;\n    numBackground = 0;\n    numLocked = 0;\n    MidBackground = 1;\n    LastBackground = 0;\n    PlayerStart[1] = BlankLocation;\n    PlayerStart[2] = BlankLocation;\n\n    // noUpdate = false;\n}\n\nvoid FindStars()\n{\n//    int A = 0;\n//    int B = 0;\n//    std::string newInput;\n    LevelData tempData;\n\n    uint32_t start_time = SDL_GetTicks();\n\n    for(int A = 1; A <= numWarps; A++)\n    {\n        IndicateProgress(start_time, num_t(A) / numWarps, g_gameStrings.messageScanningLevels);\n\n        auto &warp = Warp[A];\n\n        if(warp.level != STRINGINDEX_NONE)\n        {\n            std::string lFile = GetS(warp.level);\n\n            warp.curStars = 0;\n\n            for(const auto& star : Star)\n            {\n                if(SDL_strcasecmp(star.level.c_str(), Files::basename(lFile).c_str()) == 0)\n                    warp.curStars++;\n            }\n\n            if(warp.save_info().inited())\n                continue;\n\n            // set the warp's save info index\n\n            // check world levels\n            for(uint16_t idx = 1; idx != 0x7FFF && idx <= numWorldLevels; ++idx)\n            {\n                const auto& l = WorldLevel[idx];\n\n                if(l.FileName == lFile)\n                {\n                    warp.save_info_idx = 0x8000 + idx;\n                    break;\n                }\n            }\n\n            if(warp.save_info().inited())\n                continue;\n\n            // check existing level warp save entries\n            for(uint16_t idx = 0; idx != 0x7FFF && idx < LevelWarpSaveEntries.size(); ++idx)\n            {\n                const auto& e = LevelWarpSaveEntries[idx];\n\n                if(e.levelPath == lFile)\n                {\n                    warp.save_info_idx = idx;\n                    break;\n                }\n            }\n\n            if(warp.save_info().inited())\n                continue;\n\n            // don't overflow the LevelWarpSaveEntries array\n            if(LevelWarpSaveEntries.size() >= 0x7FFF)\n                continue;\n\n            // add a new save entry if the file exists\n            std::string fullPath = g_dirEpisode.resolveFileCaseExistsAbs(lFile);\n\n            if(!fullPath.empty())\n            {\n                LevelSaveInfo_t info = InitLevelSaveInfo(fullPath, tempData);\n\n                if(info.inited())\n                {\n                    warp.save_info_idx = LevelWarpSaveEntries.size();\n                    LevelWarpSaveEntries.push_back({lFile, info});\n                }\n            }\n        }\n    }\n}\n\n#if 0\n// Is there any unsupported content for this format in the level?\nbool CanConvertLevel(int format, std::string* reasons)\n{\n    if(format == FileFormats::LVL_PGEX)\n        return true;\n\n    if(format == FileFormats::LVL_SMBX38A)\n    {\n        if(reasons)\n        {\n            *reasons = g_editorStrings.fileConvert38aUnsupported;\n            *reasons += '\\n';\n        }\n        return false;\n    }\n\n    if(format != FileFormats::LVL_SMBX64)\n    {\n        if(reasons)\n        {\n            *reasons = g_editorStrings.fileConvertFormatUnknown;\n            *reasons += '\\n';\n        }\n        return false;\n    }\n\n    bool can_convert = true;\n    if(reasons)\n        reasons->clear();\n\n    bool seen_transit = false;\n    bool seen_stood = false;\n    bool seen_cannon = false;\n    bool seen_warp_event = false;\n    bool seen_stars_msg = false;\n    bool seen_no_print_stars = false;\n    bool seen_no_entrance_scene = false;\n    bool seen_portal_warp = false;\n    for(int i = 1; i <= numWarps; i++)\n    {\n        Warp_t& w = Warp[i];\n\n        if(!seen_transit && w.transitEffect != LevelDoor::TRANSIT_NONE)\n        {\n            can_convert = false;\n            seen_transit = true;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureWarpTransit;\n                *reasons += '\\n';\n            }\n        }\n\n        if(!seen_stood && w.stoodRequired)\n        {\n            can_convert = false;\n            seen_stood = true;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureWarpNeedsStand;\n                *reasons += '\\n';\n            }\n        }\n\n        if(!seen_cannon && w.cannonExit)\n        {\n            can_convert = false;\n            seen_cannon = true;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureWarpCannonExit;\n                *reasons += '\\n';\n            }\n        }\n\n        if(!seen_warp_event && w.eventEnter != EVENT_NONE)\n        {\n            can_convert = false;\n            seen_warp_event = true;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureWarpEnterEvent;\n                *reasons += '\\n';\n            }\n        }\n\n        if(!seen_stars_msg && !GetS(w.StarsMsg).empty())\n        {\n            can_convert = false;\n            seen_stars_msg = true;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureWarpCustomStarsMsg;\n                *reasons += '\\n';\n            }\n        }\n\n        if(!seen_no_print_stars && w.noPrintStars)\n        {\n            can_convert = false;\n            seen_no_print_stars = true;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureWarpNoPrintStars;\n                *reasons += '\\n';\n            }\n        }\n\n        if(!seen_no_entrance_scene && w.noEntranceScene)\n        {\n            can_convert = false;\n            seen_no_entrance_scene = true;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureWarpNoStartScene;\n                *reasons += '\\n';\n            }\n        }\n\n        if(!seen_portal_warp && w.Effect == 3)\n        {\n            can_convert = false;\n            seen_portal_warp = true;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureWarpPortal;\n                *reasons += '\\n';\n            }\n         }\n    }\n\n    bool seen_event_custom_music = false;\n    bool seen_modern_autoscroll = false;\n    for(int i = 0; i < numEvents; ++i)\n    {\n        Events_t& e = Events[i];\n\n        for(int j = 0; j < numSections; ++j)\n        {\n            auto &ss = e.section[j];\n\n            if(!seen_event_custom_music && !GetS(ss.music_file).empty())\n            {\n                can_convert = false;\n                seen_event_custom_music = true;\n                if(reasons)\n                {\n                    *reasons += g_editorStrings.fileConvertFeatureEventCustomMusic;\n                    *reasons += '\\n';\n                }\n            }\n\n            if(!seen_modern_autoscroll && ss.autoscroll)\n            {\n                can_convert = false;\n                seen_modern_autoscroll = true;\n                if(reasons)\n                {\n                    *reasons += g_editorStrings.fileConvertFeatureEventAutoscroll;\n                    *reasons += '\\n';\n                }\n            }\n        }\n    }\n\n    for(int i = 1; i <= numNPCs; i++)\n    {\n        if(NPC[i].Variant != 0)\n        {\n            can_convert = false;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureNPCVariant;\n                *reasons += '\\n';\n            }\n            break;\n        }\n    }\n\n    for(int i = 1; i <= numBlock; i++)\n    {\n        if(Block[i].Type == 90 && Block[i].forceSmashable)\n        {\n            can_convert = false;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureBlockForceSmashable;\n                *reasons += '\\n';\n            }\n            break;\n        }\n    }\n\n    for(int i = 1; i <= numBackground; i++)\n    {\n        if(Background[i].GetCustomLayer() || Background[i].GetCustomOffset())\n        {\n            can_convert = false;\n            if(reasons)\n            {\n                *reasons += g_editorStrings.fileConvertFeatureBgoOrder;\n                *reasons += '\\n';\n            }\n            break;\n        }\n    }\n\n    return can_convert;\n}\n\n// Strips all unsupported content from the level.\nvoid ConvertLevel(int format)\n{\n    FileFormat = format;\n\n    if(format == FileFormats::LVL_SMBX64 || format == FileFormats::LVL_SMBX38A)\n    {\n        if(!FileNameFull.empty() && FileNameFull.back() == 'x')\n            FileNameFull.resize(FileNameFull.size() - 1);\n\n        if(!FullFileName.empty() && FullFileName.back() == 'x')\n            FullFileName.resize(FullFileName.size() - 1);\n    }\n    else\n    {\n        if(!FileNameFull.empty() && FileNameFull.back() != 'x')\n            FileNameFull += \"x\";\n\n        if(!FullFileName.empty() && FullFileName.back() != 'x')\n            FullFileName += \"x\";\n    }\n\n    if(format != FileFormats::LVL_SMBX64)\n        return;\n\n    for(int i = 1; i <= numWarps; i++)\n    {\n        Warp_t& w = Warp[i];\n\n        w.transitEffect = LevelDoor::TRANSIT_NONE;\n        w.stoodRequired = false;\n        w.cannonExit = false;\n        w.eventEnter = EVENT_NONE;\n        SetS(w.StarsMsg, \"\");\n        w.noPrintStars = false;\n        w.noEntranceScene = false;\n\n        if(w.Effect == 3)\n            w.Effect = 0;\n    }\n\n    for(int i = 0; i < numEvents; ++i)\n    {\n        Events_t& e = Events[i];\n\n        for(int j = 0; j < numSections; ++j)\n        {\n            auto &ss = e.section[j];\n            SetS(ss.music_file, \"\");\n            ss.autoscroll = false;\n        }\n    }\n\n    for(int i = 1; i <= numBackground; i++)\n        Background[i].SetSortPriority(0, 0);\n\n    for(int i = 1; i <= numNPCs; i++)\n        NPC[i].Variant = 0;\n\n\n    for(int i = 1; i <= numBlock; i++)\n        Block[i].forceSmashable = 0;\n}\n#endif\n"
  },
  {
    "path": "src/main/level_file.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#pragma once\n#ifndef LEVEL_FILE_H\n#define LEVEL_FILE_H\n\n#include <string>\n#include <PGE_File_Formats/lvl_filedata.h>\n\nextern void addMissingLvlSuffix(std::string &fileName);\nextern void validateLevelName(std::string &out, const std::string &raw);\n\n//! loads the level\nbool OpenLevel(std::string FilePath);\nbool OpenLevelData(PGE_FileFormats_misc::TextInput& input, const std::string FilePath = std::string());\nvoid OpenLevelDataPost();\n//! Reset everything to zero\nvoid ClearLevel();\n\n//! checks for stars in warps the lead to another level\nvoid FindStars();\n\n//! NEW: routines to check if it is possible to convert to legacy file formats and to remove all non-legacy content\n// Removed, let PGE-FL handle conversion on its own.\n// bool CanConvertLevel(int format, std::string* reasons);\n// void ConvertLevel(int format);\n\n#endif // LEVEL_FILE_H\n\n"
  },
  {
    "path": "src/main/level_medals.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <bitset>\n#include <array>\n#include <algorithm>\n\n#include <sorting/tinysort.h>\n#include <PGE_File_Formats/file_formats.h>\n\n#include <Logger/logger.h>\n\n#include \"npc_id.h\"\n#include \"npc.h\"\n\n#include \"main/level_medals.h\"\n#include \"main/level_save_info.h\"\n\n#include \"logic/object_graph.h\"\n\nCurLevelMedals_t g_curLevelMedals;\n\n// find the LevelSaveInfo for the current level\nstatic inline LevelSaveInfo_t* s_findSaveInfo()\n{\n    for(int i = 1; i <= numWorldLevels; ++i)\n    {\n        if(WorldLevel[i].FileName == FileNameFull)\n            return &WorldLevel[i].save_info;\n    }\n\n    for(auto& e : LevelWarpSaveEntries)\n    {\n        if(e.levelPath == FileNameFull)\n            return &e.save_info;\n    }\n\n    return nullptr;\n}\n\n// find the next matching LevelSaveInfo for the current level, used to make sure all WorldLevels pointing to the same file are updated\nstatic inline LevelSaveInfo_t* s_nextSaveInfo(LevelSaveInfo_t* prev)\n{\n    for(int i = 1; i <= numWorldLevels; ++i)\n    {\n        if(WorldLevel[i].FileName == FileNameFull)\n        {\n            LevelSaveInfo_t* info = &WorldLevel[i].save_info;\n\n            if(info > prev)\n                return info;\n        }\n    }\n\n    return nullptr;\n}\n\nvoid CurLevelMedals_t::get(uint8_t idx)\n{\n    if(idx < c_max_track_medals && idx < max)\n    {\n        got |= (1 << idx);\n        life |= (1 << idx);\n    }\n}\n\nbool CurLevelMedals_t::gotten(uint8_t idx) const\n{\n    if(idx < c_max_track_medals && idx < max)\n        return got & (1 << idx);\n\n    return false;\n}\n\nvoid CurLevelMedals_t::on_any_death()\n{\n    life = 0;\n}\n\nvoid CurLevelMedals_t::on_all_dead()\n{\n    life = 0;\n\n    if(Checkpoint == FullFileName)\n        got = checkpoint;\n    else\n        got = 0;\n}\n\nvoid CurLevelMedals_t::on_checkpoint()\n{\n    checkpoint = got;\n}\n\nvoid CurLevelMedals_t::reset_checkpoint()\n{\n    checkpoint = 0;\n}\n\nvoid CurLevelMedals_t::resume_from_checkpoint()\n{\n    got = checkpoint;\n}\n\nvoid CurLevelMedals_t::reset_lvl()\n{\n    max = 0;\n    prev = 0;\n}\n\nLevelSaveInfo_t* CurLevelMedals_t::should_initialize() const\n{\n    // find the level save info\n    LevelSaveInfo_t* info = s_findSaveInfo();\n\n    // if it can't be found in the world map / previously warped locations, initialize it and add to LevelWarpSaveEntries\n    if(!info && LevelWarpSaveEntries.size() != 0xFFFF)\n    {\n        LevelWarpSaveEntries.push_back({FileNameFull, LevelSaveInfo_t()});\n        info = &LevelWarpSaveEntries[LevelWarpSaveEntries.size() - 1].save_info;\n    }\n\n    // if allocation failed, just reset level info\n    if(!info)\n        return nullptr;\n\n    // if allocated and inited, do nothing\n    if(info->inited())\n        return nullptr;\n\n    // we are ready!\n    return info;\n}\n\nvoid CurLevelMedals_t::prepare_lvl()\n{\n    // reset got / life\n    got = 0;\n    life = 0;\n\n    // find the level save info\n    LevelSaveInfo_t* info = s_findSaveInfo();\n\n    // if save info isn't ready, just reset level info\n    if(!info || !info->inited())\n    {\n        reset_lvl();\n        return;\n    }\n\n    // load max / prev from the level info\n    max = info->max_medals;\n    prev = info->medals_got;\n}\n\nvoid CurLevelMedals_t::commit()\n{\n    LevelSaveInfo_t* info = s_findSaveInfo();\n\n    if(!info)\n        return;\n\n    info->medals_got |= got;\n\n    int old_best_count = 0;\n    int new_best_count = 0;\n\n    for(int i = 0; i < c_max_track_medals && i < info->max_medals; ++i)\n    {\n        if(info->medals_best & (1 << i))\n            old_best_count++;\n\n        if(life & (1 << i))\n            new_best_count++;\n    }\n\n    if(new_best_count > old_best_count)\n        info->medals_best = life;\n\n    // copy this to any other world levels with the same filename\n    while(LevelSaveInfo_t* next = s_nextSaveInfo(info))\n    {\n        *next = *info;\n        info = next;\n    }\n}\n\nvoid CommitBeatCode(int beat_code)\n{\n    if(beat_code <= 0 || beat_code > 16)\n        return;\n\n    LevelSaveInfo_t* info = s_findSaveInfo();\n    if(!info)\n        return;\n\n    uint16_t bit = (1 << (beat_code - 1));\n    info->exits_got |= bit;\n\n    // copy this to any other world levels with the same filename\n    while(LevelSaveInfo_t* next = s_nextSaveInfo(info))\n    {\n        *next = *info;\n        info = next;\n    }\n}\n\nvoid OrderMedals()\n{\n    using dist_and_hit_index = std::pair<uint16_t, int16_t>;\n\n    // used to track medals without creator-specified indexes; first short is distance from start, second short is NPC array index\n    std::array<dist_and_hit_index, c_max_track_medals> auto_medals;\n    int auto_medal_count = 0;\n\n    // used to track medals with creator indexes\n    std::bitset<c_max_track_medals> used_indexes;\n\n    // look for medals\n    for(int i = 1; i <= numNPCs; ++i)\n    {\n        NPC_t& n = NPC[i];\n\n        bool is_container = NPCIsContainer(n);\n\n        bool contains_medal = is_container && n.Special == NPCID_MEDAL;\n\n        // allow medals only\n        if(n.Type != NPCID_MEDAL && !contains_medal)\n            continue;\n\n        // don't count friendly medals (except, thrown medals can be collected)\n        if(n.Inert && n.Type != NPCID_ITEM_THROWER)\n            continue;\n\n        // medal won't be counted (at all) if out-of-range\n        if(n.Variant > c_max_track_medals)\n            continue;\n\n        // record that the medal index has been used\n        if(n.Variant > 0)\n        {\n            used_indexes[n.Variant - 1] = true;\n            continue;\n        }\n\n        // if not specified, then try to fill into the array to auto-assign an index\n        if(auto_medal_count < c_max_track_medals)\n        {\n            auto_medals[auto_medal_count] = {0, static_cast<int16_t>(i)};\n            auto_medal_count++;\n        }\n\n        // otherwise, Variant will be left as 0 and medal won't be counted\n    }\n\n    // find the distance of unspecified medals from level start, and sort\n    if(auto_medal_count > 1)\n    {\n        // need to order the auto_hits; get an idea of the layout of the level\n        ObjectGraph::Graph graph;\n        ObjectGraph::FillGraph(graph);\n\n        for(int auto_i = 0; auto_i < auto_medal_count; ++auto_i)\n        {\n            const NPC_t& n = NPC[auto_medals[auto_i].second];\n\n            unsigned int coord = graph.distance_from_start({(int)n.Location.X, (int)n.Location.Y});\n\n            D_pLogDebug(\"Medal with NPC ID %d at %d from level start, %d to level end\", auto_medals[auto_i].second, coord, graph.furthest_dist);\n\n            // furthest_dist is the furthest item (including medals), and it's never negative\n            uint64_t scaled_coord = (uint64_t)coord * 0x10000 / (2 * graph.furthest_dist);\n            if(scaled_coord >= 0x10000)\n                scaled_coord = 0xFFFF;\n\n            auto_medals[auto_i].first = static_cast<uint16_t>(scaled_coord);\n        }\n\n        // sort by distance\n        tinysort(auto_medals.begin(), auto_medals.begin() + auto_medal_count);\n    }\n\n    // auto-assign indexes to unspecified medals\n    for(int auto_i = 0; auto_i < auto_medal_count; ++auto_i)\n    {\n        NPC_t& n = NPC[auto_medals[auto_i].second];\n\n        // try to find an index\n        for(int i = 0; i < c_max_track_medals; ++i)\n        {\n            if(!used_indexes[i])\n            {\n                D_pLogDebug(\"Medal with NPC ID %d gets index %d\", auto_medals[auto_i].second, i + 1);\n\n                n.Variant = i + 1;\n                used_indexes[i] = true;\n                break;\n            }\n        }\n\n        // if failed, Variant will be left as 0 and medal won't be counted\n    }\n}\n"
  },
  {
    "path": "src/main/level_medals.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef LEVEL_MEDALS_H\n#define LEVEL_MEDALS_H\n\n#include <string>\n\nstruct LevelData;\nstruct LevelSaveInfo_t;\n\nnamespace PGE_FileFormats_misc\n{\n    class TextInput;\n}\n\nstruct CurLevelMedals_t\n{\nprotected:\n    friend bool OpenLevelData(PGE_FileFormats_misc::TextInput &lvl, const std::string FilePath);\n    friend void OpenLevelDataPost();\n\n    /**\n     * \\brief checks if the current level's save data must be initialized\n     * Must be called during OpenLevelData after FileNameFull is set\n     *\n     * The resulting pointer may be used by a SaveInfoInit object during level load,\n     * and remains valid until the next time LevelWarpSaveEntries is modified\n     * by this method or by FindStars()\n     *\n     * \\param loadedLevel the loaded LevelData object, used to initialize level save info if needed\n     */\n    LevelSaveInfo_t* should_initialize() const;\n\n    /**\n     * \\brief loads maximums from the current level and resets gotten counts\n     * Must be called after the level is fully loaded and the save data has been initialized\n     */\n    void prepare_lvl();\n\npublic:\n    //! INTEGER for number of medals available (can't exceed 8)\n    uint8_t max = 0;\n\n    //! bitfield of medals previously acquired in level (imported as \"got\" from save data)\n    uint8_t prev = 0;\n    //! bitfield of medals acquired during current play session (on win, combined with \"got\" in save data)\n    uint8_t got = 0;\n    //! bitfield of medals acquired since most recent lost life (on win, compared with and possibly replaces \"best\" in save data)\n    uint8_t life = 0;\n    //! bitfield of medals acquired at most recent checkpoint\n    uint8_t checkpoint = 0;\n\n    /**\n     * \\brief sets specific coin as obtained\n     * \\param idx coin index\n     */\n    void get(uint8_t idx);\n\n    /**\n     * \\brief checks whether specific coin has been obtained\n     * \\param idx coin index\n     */\n    bool gotten(uint8_t idx) const;\n\n    /**\n     * \\brief resets this-life medals\n     */\n    void on_any_death();\n\n    /**\n     * \\brief loads currently got medals from checkpoint (if appropriate), resets this-life medals\n     */\n    void on_all_dead();\n\n    /**\n     * \\brief saves currently got medals to checkpoint\n     */\n    void on_checkpoint();\n\n    /**\n     * \\brief resets saved medals in checkpoint\n     */\n    void reset_checkpoint();\n\n    /**\n     * \\brief restores saved medals from checkpoint\n     */\n    void resume_from_checkpoint();\n\n    /**\n     * \\brief resets level attributes (keeps checkpoint and gotten counts)\n     */\n    void reset_lvl();\n\n    /**\n     * \\brief commits run to current level's save info\n     */\n    void commit();\n};\n\n/**\n * \\brief sets the Variant attribute of medals in the current level\n */\nvoid OrderMedals();\n\n/**\n * \\brief Save an obtained level beat code to the current level's LevelSaveInfo entries\n */\nvoid CommitBeatCode(int beat_code);\n\nextern CurLevelMedals_t g_curLevelMedals;\n\n#endif // #ifndef LEVEL_MEDALS_H\n"
  },
  {
    "path": "src/main/level_save_info.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <cstdio>\n#include <string>\n#include <bitset>\n#include <PGE_File_Formats/file_formats.h>\n#include <Logger/logger.h>\n#include <fmt_format_ne.h>\n\n#include \"Utils/files.h\"\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"npc_id.h\"\n#include \"npc.h\"\n\n#include \"globals.h\"\n\n#include \"level_save_info.h\"\n\nstatic bool s_exportSingleSaveInfo(saveLevelInfo& s, const LevelSaveInfo_t& info)\n{\n    if(!info.inited())\n        return false;\n\n    s.max_stars = info.max_stars;\n    s.max_medals = info.max_medals;\n    s.exits_got = info.exits_got;\n\n    int size = SDL_min(static_cast<int>(info.max_medals), c_max_track_medals);\n\n    s.medals_got.resize(size);\n    s.medals_best.resize(size);\n\n    for(int i = 0; i < size; ++i)\n    {\n        s.medals_got[i]  = (info.medals_got  & (1 << i));\n        s.medals_best[i] = (info.medals_best & (1 << i));\n    }\n\n    return true;\n}\n\nstatic bool s_importSingleSaveInfo(LevelSaveInfo_t& info, const saveLevelInfo& s)\n{\n    info = LevelSaveInfo_t();\n\n    unsigned max_stars = s.max_stars;\n    unsigned max_medals = s.max_medals;\n    unsigned exits_got = s.exits_got;\n\n    // validate limits\n    static_assert(c_max_track_stars <= 255, \"c_max_track_stars used to limit values of uint8_t\");\n    if(max_stars > c_max_track_stars)\n        max_stars = c_max_track_stars;\n\n    static_assert(c_max_track_medals <= 8, \"c_max_track_medals used to limit bit indexes into uint8_t\");\n    if(max_medals > c_max_track_medals)\n        max_medals = c_max_track_medals;\n\n    exits_got &= 0xFFFF;\n\n    // load maximums\n    info.max_stars = max_stars;\n    info.max_medals = max_medals;\n    info.exits_got = exits_got;\n\n    // load medals\n    for(unsigned i = 0; i < max_medals; ++i)\n    {\n        if(i < s.medals_got.size() && s.medals_got[i])\n            info.medals_got |= (1 << i);\n\n        if(i < s.medals_got.size() && s.medals_best[i])\n            info.medals_best |= (1 << i);\n    }\n\n    return true;\n}\n\nvoid ImportLevelSaveInfo(const GamesaveData& s)\n{\n    // reset all world levels' save info\n    for(int A = 1; A <= numWorldLevels; A++)\n    {\n        if(WorldLevel[A].FileName.empty())\n            continue;\n\n        WorldLevel[A].save_info = LevelSaveInfo_t();\n    }\n\n    LevelWarpSaveEntries.clear();\n\n    for(const saveLevelInfo& e : s.levelInfo)\n    {\n        LevelSaveInfo_t info;\n\n        if(!s_importSingleSaveInfo(info, e))\n            continue;\n\n        D_pLogDebug(\"Loaded save info (stars %d, medals %d - got %x, best %x) for level [%s]\", (int)info.max_stars, (int)info.max_medals, (int)info.medals_got, (int)info.medals_best, e.level_filename.c_str());\n\n        // see if it applies to a world level\n        bool worldLevelHit = false;\n\n        for(int A = 1; A <= numWorldLevels; A++)\n        {\n            WorldLevel_t& l = WorldLevel[A];\n\n            // can skip the string comparison if the level has already been initialized and we are no longer checking whether this save info has any level\n            if(l.save_info.inited() && worldLevelHit)\n                continue;\n\n            if(l.FileName == e.level_filename)\n            {\n                // update level save info if not yet initialized\n                if(!l.save_info.inited())\n                    l.save_info = info;\n\n                // mark save info as paired with a level\n                worldLevelHit = true;\n\n                // don't break, in case another level has the same filename\n            }\n        }\n\n        // otherwise, add a new level warp save entry\n        if(!worldLevelHit)\n            LevelWarpSaveEntries.push_back(LevelWarpSaveEntry_t{e.level_filename, info});\n    }\n}\n\nvoid ExportLevelSaveInfo(GamesaveData& s)\n{\n    saveLevelInfo tempInfo;\n\n    for(int A = 1; A <= numWorldLevels; A++)\n    {\n        if(s_exportSingleSaveInfo(tempInfo, WorldLevel[A].save_info))\n        {\n            tempInfo.level_filename = WorldLevel[A].FileName;\n            s.levelInfo.push_back(tempInfo);\n        }\n    }\n\n    for(const auto& e : LevelWarpSaveEntries)\n    {\n        if(s_exportSingleSaveInfo(tempInfo, e.save_info))\n        {\n            tempInfo.level_filename = e.levelPath;\n            s.levelInfo.push_back(tempInfo);\n        }\n    }\n}\n\n// level save info initialization code\nvoid SaveInfoInit::begin(LevelSaveInfo_t* _target)\n{\n    target = _target;\n    if(!target)\n        return;\n\n    target->max_stars = 0;\n    target->max_medals = 0;\n    target->medals_got = 0;\n    target->medals_best = 0;\n\n    // keep track of the user-specified ones because two medals with the same user index are only counted once\n    used_indexes.reset();\n}\n\nvoid SaveInfoInit::on_error()\n{\n    if(target)\n        *target = LevelSaveInfo_t();\n}\n\n#ifdef PGEFL_CALLBACK_API\nvoid SaveInfoInit::check_head(const LevelHead& head)\n#else\nvoid SaveInfoInit::check_head(const LevelData& head)\n#endif\n{\n    if(!target)\n        return;\n\n    target->max_stars = head.stars;\n}\n\nvoid SaveInfoInit::check_npc(const LevelNPC& npc)\n{\n    if(!target)\n        return;\n\n    bool is_container = (npc.id == NPCID_ITEM_BURIED || npc.id == NPCID_ITEM_POD ||\n                         npc.id == NPCID_ITEM_BUBBLE || npc.id == NPCID_ITEM_THROWER ||\n                         NPCNewContainerType(npc.id));\n\n    bool contains_medal = is_container && npc.contents == NPCID_MEDAL;\n\n    // allow medals only\n    if(npc.id != NPCID_MEDAL && !contains_medal)\n        return;\n\n    // don't count friendly medals (except, thrown medals can be collected)\n    if(npc.friendly && npc.id != NPCID_ITEM_THROWER)\n        return;\n\n    uint8_t Variant = static_cast<uint8_t>(npc.special_data);\n\n    // medal won't be counted (at all) if out-of-range\n    if(Variant > c_max_track_medals)\n        return;\n\n    // if no index, add to medal count\n    if(Variant == 0)\n    {\n        if(target->max_medals < c_max_track_medals)\n            target->max_medals++;\n\n        return;\n    }\n\n    // if index specified, check that the index hasn't already been used\n    if(!used_indexes[Variant - 1])\n    {\n        if(target->max_medals < c_max_track_medals)\n            target->max_medals++;\n\n        used_indexes[Variant - 1] = true;\n    }\n}\n\n#ifdef PGEFL_CALLBACK_API\n\nstatic void InitLevelSaveInfo_on_error(void* userdata, FileFormatsError& e)\n{\n    SaveInfoInit& si = *static_cast<SaveInfoInit*>(userdata);\n    si.on_error();\n\n    pLogWarning(\"During save info init: %s (line %ld).\",\n                e.ERROR_info.c_str(),\n                e.ERROR_linenum);\n}\n\nstatic bool InitLevelSaveInfo_load_head(void* userdata, LevelHead& head)\n{\n    SaveInfoInit& si = *static_cast<SaveInfoInit*>(userdata);\n    si.check_head(head);\n    return true;\n}\n\nstatic bool InitLevelSaveInfo_load_npc(void* userdata, LevelNPC& npc)\n{\n    SaveInfoInit& si = *static_cast<SaveInfoInit*>(userdata);\n    si.check_npc(npc);\n    return true;\n}\n\nLevelSaveInfo_t InitLevelSaveInfo(const std::string& fullPath, LevelData&)\n{\n    LevelSaveInfo_t ret;\n\n    SaveInfoInit si;\n    si.begin(&ret);\n\n    LevelLoadCallbacks cb;\n    cb.on_error = InitLevelSaveInfo_on_error;\n    cb.load_head = InitLevelSaveInfo_load_head;\n    cb.load_npc = InitLevelSaveInfo_load_npc;\n    cb.userdata = &si;\n\n    PGE_FileFormats_misc::RWopsTextInput in(Files::open_file(fullPath, \"r\"), fullPath);\n\n    if(FileFormats::OpenLevelFileT(in, cb))\n        pLogDebug(\"Initing level save data at [%s] with %d stars and %d medals\", fullPath.c_str(), (int)ret.max_stars, (int)ret.max_medals);\n    else\n    {\n        si.on_error();\n        pLogWarning(\"During save info init: failed to load [%s]\", fullPath.c_str());\n    }\n\n    return ret;\n}\n\n#else // #ifdef PGEFL_CALLBACK_API\nLevelSaveInfo_t InitLevelSaveInfo(const LevelData& loadedLevel)\n{\n    LevelSaveInfo_t ret;\n\n    SaveInfoInit si;\n    si.begin(&ret);\n\n    si.check_head(loadedLevel);\n\n    for(const auto& n : loadedLevel.npc)\n        si.check_npc(n);\n\n    return ret;\n}\n\nLevelSaveInfo_t InitLevelSaveInfo(const std::string& fullPath, LevelData& tempData)\n{\n    if(!FileFormats::OpenLevelFile(fullPath, tempData))\n    {\n        pLogWarning(\"During save info init: failed to load [%s]\", fullPath.c_str());\n        return LevelSaveInfo_t();\n    }\n\n    auto ret = InitLevelSaveInfo(tempData);\n\n    pLogDebug(\"Initing level save data at [%s] with %d stars and %d medals\", fullPath.c_str(), (int)ret.max_stars, (int)ret.max_medals);\n\n    return ret;\n}\n#endif // #else // #ifdef PGEFL_CALLBACK_API\n"
  },
  {
    "path": "src/main/level_save_info.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef LEVEL_SAVE_INFO_H\n#define LEVEL_SAVE_INFO_H\n\n#include <string>\n#include <bitset>\n\n#include <PGE_File_Formats/file_formats.h>\n\n#include \"globals.h\"\n\nstruct SaveInfoInit\n{\n    LevelSaveInfo_t* target = nullptr;\n    std::bitset<c_max_track_medals> used_indexes;\n\n    // resets target's fields to begin initialization\n    void begin(LevelSaveInfo_t* target);\n\n    // marks target as invalid on load error\n    void on_error();\n\n#ifdef PGEFL_CALLBACK_API\n    // updates star count from header\n    void check_head(const LevelHead& head);\n#else\n    // updates star count from header\n    void check_head(const LevelData& head);\n#endif\n\n    // updates medal count from NPC\n    void check_npc(const LevelNPC& head);\n};\n\n/**\n * \\brief convenience function: initialize level save info from fullPath, loading the level into tempData\n *\n * \\param fullPath a full path to the level file whose save info will be initialized\n * \\param tempData an unloaded LevelData object used to prevent repeated heap allocations while loading multiple levels\n *\n * \\return loaded save info\n */\nLevelSaveInfo_t InitLevelSaveInfo(const std::string& fullPath, LevelData& tempData);\n\n/**\n * \\brief import all level save info into WorldLevel and LevelWarpSaveEntries from a GamesaveData object\n * \\param s a PGE-FL GamesaveData object from which level save info will be loaded to global arrays\n */\nvoid ImportLevelSaveInfo(const GamesaveData& s);\n\n/**\n * \\brief export all level save info from WorldLevel and LevelWarpSaveEntries to a GamesaveData object\n * \\param s a mutable PGE-FL GamesaveData object to which level save info will be stored from global arrays\n */\nvoid ExportLevelSaveInfo(GamesaveData& s);\n\n#endif // #ifndef LEVEL_SAVE_INFO_H\n"
  },
  {
    "path": "src/main/main_config.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../globals.h\"\n#include \"../game_main.h\"\n#include \"../graphics.h\"\n#include \"../sound.h\"\n#include \"../config.h\"\n#include \"../controls.h\"\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n#include \"main/client_methods.h\"\n#endif\n\n#include \"core/render.h\"\n\n#include \"speedrunner.h\"\n#include \"record.h\"\n#include \"main/asset_pack.h\"\n\n#include <Utils/files.h>\n#include <Utils/strings.h>\n#include <IniProcessor/ini_processing.h>\n#include <fmt_format_ne.h>\n#include <AppPath/app_path.h>\n#include <Logger/logger.h>\n\n\nvoid ConfigReloadRecentEpisodes()\n{\n    // reload recently used episodes for the new asset pack\n    std::string configPath = AppPathManager::settingsFileSTD();\n\n    if(Files::fileExists(configPath))\n    {\n        IniProcessing config(configPath);\n\n        std::string asset_pack_prefix = g_AssetPackID;\n        if(!asset_pack_prefix.empty())\n            asset_pack_prefix += '-';\n\n        config.beginGroup(\"recent\");\n        config.read((asset_pack_prefix + \"episode-1p\").c_str(), g_recentWorld1p, std::string());\n        config.read((asset_pack_prefix + \"episode-2p\").c_str(), g_recentWorld2p, std::string());\n        config.read((asset_pack_prefix + \"episode-editor\").c_str(), g_recentWorldEditor, std::string());\n        config.read((asset_pack_prefix + \"episode-custom-intro\").c_str(), g_recentWorldIntro, std::string());\n        config.endGroup();\n\n        pLogDebug(\"Loaded recent episodes for asset pack [id: %s] from [%s]\", g_AssetPackID.c_str(), configPath.c_str());\n    }\n}\n\n\nvoid OpenConfig()\n{\n    g_pLogGlobalSetup.logPathDefault = AppPathManager::logsDir();\n    g_pLogGlobalSetup.logPathFallBack = AppPathManager::userAppDirSTD();\n\n    int FileRelease = 0;\n\n    std::string configPath = AppPathManager::settingsFileSTD();\n\n    if(Files::fileExists(configPath))\n    {\n        IniProcessing config(configPath);\n\n        config.beginGroup(\"main\");\n        config.read(\"release\", FileRelease, curRelease);\n        config.endGroup();\n\n        std::string asset_pack_prefix = g_AssetPackID;\n        if(!asset_pack_prefix.empty())\n            asset_pack_prefix += '-';\n\n        config.beginGroup(\"recent\");\n        config.read(\"asset-pack\", g_recentAssetPack, std::string());\n#ifdef THEXTECH_ENABLE_SDL_NET\n        config.read(\"server\", g_netplayServer, g_netplayServer);\n        config.read(\"nickname\", g_netplayNickname, std::string());\n#endif\n        config.read((asset_pack_prefix + \"episode-1p\").c_str(), g_recentWorld1p, std::string());\n        config.read((asset_pack_prefix + \"episode-2p\").c_str(), g_recentWorld2p, std::string());\n        config.read((asset_pack_prefix + \"episode-editor\").c_str(), g_recentWorldEditor, std::string());\n        config.read((asset_pack_prefix + \"episode-custom-intro\").c_str(), g_recentWorldIntro, std::string());\n        config.endGroup();\n\n        config.beginGroup(\"logging\");\n        config.read(\"log-path\", g_pLogGlobalSetup.logPathCustom, std::string());\n        config.read(\"max-log-count\", g_pLogGlobalSetup.maxFilesCount, 10);\n        config.endGroup();\n\n        g_config_game_user.Clear();\n        g_config_game_user.UpdateFromIni(&config);\n\n        g_config.log_level.set_from_default(ConfigSetLevel::set);\n\n        g_pLogGlobalSetup.level = (PGE_LogLevel::Level)(int)(g_config_game_user.log_level.is_set() ? g_config_game_user.log_level : g_config.log_level);\n\n        pLogDebug(\"Loaded config: %s\", configPath.c_str());\n    }\n    else\n    {\n        pLogDebug(\"Writing new config on first run.\");\n        SaveConfig(); // Create the config file on first run\n    }\n\n}\n\nvoid SaveConfig()\n{\n    std::string configPath = AppPathManager::settingsFileSTD();\n\n    IniProcessing config(configPath);\n    config.beginGroup(\"main\");\n    config.setValue(\"release\", curRelease);\n    config.endGroup();\n\n    config.beginGroup(\"logging\");\n    {\n        if(!g_pLogGlobalSetup.logPathCustom.empty())\n            config.setValue(\"log-path\", g_pLogGlobalSetup.logPathCustom);\n        config.setValue(\"max-log-count\", g_pLogGlobalSetup.maxFilesCount);\n    }\n    config.endGroup();\n\n    std::string asset_pack_prefix = g_AssetPackID;\n    if(!asset_pack_prefix.empty())\n        asset_pack_prefix += '-';\n\n    config.beginGroup(\"recent\");\n    config.setValue(\"asset-pack\", g_AssetPackID);\n#ifdef THEXTECH_ENABLE_SDL_NET\n    config.setValue(\"server\", g_netplayServer);\n    config.setValue(\"nickname\", g_netplayNickname);\n#endif\n    config.setValue((asset_pack_prefix + \"episode-1p\").c_str(), g_recentWorld1p);\n    config.setValue((asset_pack_prefix + \"episode-2p\").c_str(), g_recentWorld2p);\n    config.setValue((asset_pack_prefix + \"episode-editor\").c_str(), g_recentWorldEditor);\n    config.setValue((asset_pack_prefix + \"episode-custom-intro\").c_str(), g_recentWorldIntro);\n    config.endGroup();\n\n    g_config_game_user.SaveToIni(&config);\n\n    config.writeIniFile();\n    AppPathManager::syncFs();\n\n    pLogDebug(\"Saved config: %s\", configPath.c_str());\n}\n"
  },
  {
    "path": "src/main/menu_controls.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <array>\n\n#include <fmt_format_ne.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"../global_constants.h\"\n#include \"../controls.h\"\n#include \"../sound.h\"\n#include \"../globals.h\"\n#include \"../graphics.h\"\n#include \"control/controls_methods.h\" // to cancel keyboard's double-click fullscreen\n\n#include \"../gfx.h\"\n#include \"../core/render.h\"\n\n#include \"fontman/font_manager.h\"\n\n#include \"graphics/gfx_frame.h\"\n#include \"graphics/gfx_marquee.h\"\n\n#include \"main/screen_textentry.h\"\n\n#include \"main/speedrunner.h\"\n#include \"main/menu_main.h\"\n#include \"main/game_strings.h\"\n\nbool g_pollingInput = false;\n\nstatic int s_curType = -1;\nstatic int s_curProfile = -1;\n\nstatic bool s_secondaryInput = false;\n\nstatic bool s_canDelete = false;\n\nstatic bool s_deleteProfileSel = false;\n\nstatic Controls::ControlsClass s_profileTab = Controls::ControlsClass::None;\n\nstatic std::array<MarqueeState, maxLocalPlayers> s_controller_type_marquee;\nstatic std::array<MarqueeState, maxLocalPlayers> s_controller_profile_marquee;\n\nstatic inline void s_cancelDoubleClick()\n{\n#ifdef KEYBOARD_H\n    Controls::g_cancelDoubleClick = true;\n#endif\n}\n\n// only partially refactored from the mouse and standard menu logic functions\n//   (which currently duplicate part of their logic)\nint menuControls_Do()\n{\n    if(s_curType == -1)\n    {\n    }\n    else if(s_curProfile == -1)\n    {\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        (void)type;\n    }\n    else if(s_profileTab == Controls::ControlsClass::None)\n    {\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        Controls::InputMethodProfile* profile = type->GetProfiles()[s_curProfile];\n\n        if(MenuCursor == 0) // Rename Profile\n        {\n            PlaySoundMenu(SFX_Do);\n            MenuCursorCanMove = false;\n            profile->Name = TextEntryScreen::Run(g_mainMenu.controlsRenameProfile, profile->Name);\n            MenuCursorCanMove = false;\n            return 0;\n        }\n        else if(MenuCursor == 1) // Delete Profile\n        {\n            PlaySoundMenu(SFX_PlayerDied2);\n            s_deleteProfileSel = true;\n            MenuCursor = 0;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n        else // submenu\n        {\n            PlaySoundMenu(SFX_Do);\n            if(MenuCursor == 2)\n                s_profileTab = Controls::ControlsClass::Player;\n            else if(MenuCursor == 3)\n                s_profileTab = Controls::ControlsClass::Cursor;\n            else if(MenuCursor == 4)\n                s_profileTab = Controls::ControlsClass::Editor;\n            else if(MenuCursor == 5)\n                s_profileTab = Controls::ControlsClass::Hotkey;\n            MenuCursor = 0;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n    }\n    return 0;\n}\n\nint menuControls_Mouse_Render(bool mouse, bool render)\n{\n    if(mouse && !SharedCursor.Move && !render && !SharedCursor.Primary)\n        return 0;\n\n    const int n_types = (int)Controls::g_InputMethodTypes.size();\n\n    // want 680px width. if not possible, use double-line mode on settable option screens\n    int width;\n    if(XRender::TargetW < 640)\n        width = XRender::TargetW - 16 - XRender::TargetOverscanX * 2;\n    else if(XRender::TargetW < 720)\n        width = XRender::TargetW - 40 - XRender::TargetOverscanX * 2;\n    else\n        width = 680;\n\n    // want up to 15 lines of text\n    int line = (XRender::TargetH - 60) / 15;\n    line -= line & 1;\n    if(line > 30)\n        line = 30;\n\n    // check for Chinese and Korean languages\n    int min_line_size = FontManager::getMetricsValue(FontManager::Metrics_MenuMinLineHeight, CurrentLanguage); // 18;\n\n    // (okay if we don't get 15 lines)\n    int max_line = 15;\n    if(line < min_line_size)\n    {\n        line = min_line_size;\n        max_line = (int)XRender::TargetH / line;\n\n        // fix some strange offscreen issues\n        if(min_line_size > 18)\n            max_line--;\n    }\n\n    // horizontal start of the menu\n    int sX = XRender::TargetW/2 - width/2;\n    sX -= sX & 1;\n    // vertical start of the menu\n    int sY = XRender::TargetH/2 - (line*max_line)/2;\n    sY -= sY & 1;\n\n    // render the background\n    if(render)\n        DrawSimpleFrame(sX, sY - (line - 18) - 4, width, line * max_line + (line - 18) + 8, {0, 0, 0, 127}, {255, 255, 255, 127}, {0, 0, 0, 127});\n\n    // rendering of profile deletion screen\n    if(s_deleteProfileSel)\n    {\n        if(render)\n        {\n            SuperPrintScreenCenter(g_mainMenu.controlsReallyDeleteProfile, 3, sY);\n        }\n\n        if(s_curType < 0 || s_curType >= n_types)\n        {\n            return 0;\n        }\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        const int n_profiles = (int)type->GetProfiles().size();\n\n        if(s_curProfile < 0 || s_curProfile >= n_profiles)\n        {\n            return 0;\n        }\n\n        Controls::InputMethodProfile* profile = type->GetProfiles()[s_curProfile];\n\n        if(profile && render)\n            SuperPrintScreenCenter(profile->Name, 3, sY+line);\n\n        if(render)\n        {\n            SuperPrint(g_mainMenu.wordNo, 3, sX+width/4, sY+line*3);\n            if(MenuCursor == 0)\n                XRender::renderTextureBasic(sX+width/4-24, sY+line*3, GFX.MCursor[0]);\n            SuperPrint(g_mainMenu.wordYes, 3, sX+width/4, sY+line*4);\n            if(MenuCursor == 1)\n                XRender::renderTextureBasic(sX+width/4-24, sY+line*4, GFX.MCursor[0]);\n        }\n\n        if(mouse)\n        {\n            // collision and click handling for \"NO\"\n            int menuLen = (int)g_mainMenu.wordNo.size() * 18;\n            if(SharedCursor.X >= sX+width/4 && SharedCursor.X <= sX+width/4 + menuLen\n                && SharedCursor.Y >= sY+line*3 && SharedCursor.Y <= sY+line*3 + 16)\n            {\n                if(MenuCursor != 0)\n                {\n                    PlaySoundMenu(SFX_Slide);\n                    MenuCursor = 0;\n                }\n\n                if(MenuMouseRelease && SharedCursor.Primary)\n                {\n                    s_cancelDoubleClick();\n                    PlaySoundMenu(SFX_Slide);\n                    s_deleteProfileSel = false;\n                    MenuCursor = 1; // Delete Profile\n                    MenuMouseRelease = false;\n                }\n            }\n\n            // collision and click handling for \"YES\"\n            menuLen = (int)g_mainMenu.wordYes.size() * 18;\n            if(SharedCursor.X >= sX+width/4 && SharedCursor.X <= sX+width/4 + menuLen\n                && SharedCursor.Y >= sY+line*4 && SharedCursor.Y <= sY+line*4 + 16)\n            {\n                if(MenuCursor != 1)\n                {\n                    PlaySoundMenu(SFX_Slide);\n                    MenuCursor = 1;\n                }\n\n                if(MenuMouseRelease && SharedCursor.Primary)\n                {\n                    s_cancelDoubleClick();\n                    if(profile && type->DeleteProfile(profile, Controls::g_InputMethods))\n                    {\n                        PlaySoundMenu(SFX_VillainKilled);\n                        s_deleteProfileSel = false;\n                        MenuCursor = s_curProfile;\n                        s_curProfile = -1;\n                    }\n                    else\n                    {\n                        PlaySoundMenu(SFX_BlockHit);\n                    }\n                    MenuMouseRelease = false;\n                }\n            }\n        }\n\n        return 0;\n    }\n\n    // rendering / mouse for the main controls screen\n    if(s_curType == -1)\n    {\n        if(render)\n            SuperPrintScreenCenter(g_mainMenu.controlsTitle, 3, sY);\n\n        // render the types at the top of the screen and the currently connected devices at the bottom\n        if(render)\n            SuperPrint(g_mainMenu.controlsDeviceTypes, 3, sX+16, sY+2*line);\n\n        int scroll_start = 0;\n        int scroll_end = n_types;\n\n        if(max_line - 9 < n_types)\n        {\n            int scroll_n = max_line - 10;\n            scroll_start = MenuCursor - scroll_n/2;\n            scroll_end = scroll_start + scroll_n;\n            if(scroll_start < 0)\n            {\n                scroll_start = 0;\n                scroll_end = scroll_start + scroll_n;\n            }\n            if(scroll_end > n_types)\n            {\n                scroll_end = n_types;\n                scroll_start = scroll_end - scroll_n;\n            }\n        }\n\n        // render the scroll indicators\n        if(render)\n        {\n            if(scroll_start > 0)\n                XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[1].w / 2, sY + 3*line - GFX.MCursor[1].h, GFX.MCursor[1]);\n\n            if(scroll_end < n_types)\n                XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[2].w / 2, sY + (3+scroll_end-scroll_start)*line - line + 18, GFX.MCursor[2]);\n        }\n\n        // render the menu items\n        for(int i = 0; i < scroll_end - scroll_start; i++)\n        {\n            if(render)\n            {\n                bool in_use = false;\n\n                for(Controls::InputMethod* method : Controls::g_InputMethods)\n                {\n                    if(!method)\n                        continue;\n                    if(method->Type == Controls::g_InputMethodTypes[scroll_start + i])\n                    {\n                        in_use = true;\n                        break;\n                    }\n                }\n\n                if(in_use)\n                    SuperPrint(Controls::g_InputMethodTypes[scroll_start + i]->LocalName() + \" \" + g_mainMenu.controlsInUse, 5, sX+48, sY+(3+i)*line);\n                else\n                    SuperPrint(Controls::g_InputMethodTypes[scroll_start + i]->LocalName(), 5, sX+48, sY+(3+i)*line);\n                if(MenuCursor == scroll_start + i)\n                    XRender::renderTextureBasic(sX + 24, sY+(3+i)*line, GFX.MCursor[0]);\n            }\n            int item_width = (int)Controls::g_InputMethodTypes[scroll_start + i]->LocalName().size() * 18;\n            if(mouse && SharedCursor.X >= sX+48 && SharedCursor.X <= sX+48 + item_width\n                && SharedCursor.Y >= sY+(3+i)*line && SharedCursor.Y <= sY+(3+i)*line + 16)\n            {\n                if(MenuCursor != scroll_start + i)\n                {\n                    PlaySoundMenu(SFX_Slide);\n                    MenuCursor = scroll_start + i;\n                }\n\n                if(MenuMouseRelease && SharedCursor.Primary)\n                {\n                    s_cancelDoubleClick();\n                    PlaySoundMenu(SFX_Do);\n                    s_curType = MenuCursor;\n                    MenuCursor = 0;\n                    MenuMouseRelease = false;\n                }\n            }\n        }\n\n        // render the players\n        if(render && s_curType == -1 && Controls::g_InputMethods.size() > 0)\n        {\n            SuperPrintScreenCenter(g_mainMenu.controlsConnected, 3, sY+(max_line-5)*line);\n\n            int p_width = (width - 8) / Controls::g_InputMethods.size();\n\n            for(size_t p = 0; p < Controls::g_InputMethods.size() && p < maxLocalPlayers; p++)\n            {\n                if(!Controls::g_InputMethods[p])\n                    continue;\n\n                int lX = sX + 4 + (p_width * (int)p);\n                int cX = lX + p_width / 2;\n\n                MarqueeSpec print_spec = MarqueeSpec(p_width - 8, 10, 16, 32, 0);\n\n                s_controller_type_marquee[p].advance(print_spec);\n                SuperPrintMarquee(Controls::g_InputMethods[p]->Name, 5, lX, sY+(max_line-4)*line, print_spec, s_controller_type_marquee[p]);\n\n                RenderControls(p, cX - 76 / 2, sY + (max_line - 3) * line - (line - 18) / 2 + 2, 76, 30, false, 255, true);\n\n                // should never be null\n                if(Controls::g_InputMethods[p]->Profile != nullptr)\n                {\n                    s_controller_profile_marquee[p].advance(print_spec);\n                    SuperPrintMarquee(Controls::g_InputMethods[p]->Profile->Name, 5, lX, sY+(max_line-3)*line + 34, print_spec, s_controller_profile_marquee[p]);\n                }\n            }\n        }\n    }\n    // rendering / mouse for the input method type screens\n    else if(s_curProfile == -1)\n    {\n        bool double_line = false;\n        if(width < 500)\n            double_line = true;\n\n        // should never happen\n        if(s_curType < 0 || s_curType >= n_types)\n        {\n            SDL_assert_release(false); // invalid state in controls settings\n            PlaySoundMenu(SFX_BlockHit);\n            s_curType = -1;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        const int n_options = (int)type->GetOptionCount();\n        std::vector<Controls::InputMethodProfile*> profiles = type->GetProfiles();\n        const int n_profiles = (int)profiles.size();\n\n        if(render)\n        {\n            SuperPrintScreenCenter(type->Name, 3, sY);\n\n            bool in_use = false;\n\n            for(Controls::InputMethod* method : Controls::g_InputMethods)\n            {\n                if(!method)\n                    continue;\n                if(method->Type == type)\n                {\n                    in_use = true;\n                    break;\n                }\n            }\n\n            if(in_use)\n                SuperPrintScreenCenter(g_mainMenu.controlsInUse, 3, sY+16);\n            else\n                SuperPrintScreenCenter(g_mainMenu.controlsNotInUse, 3, sY+16);\n        }\n\n        // first come the profiles, and then the type options.\n        // the scrolling is determined here\n\n        int avail_lines = max_line - 2;\n\n        int total_lines;\n        if(double_line)\n            total_lines = 1 + (n_profiles+1) + 2 + n_options*2;\n        else\n            total_lines = 1 + (n_profiles+1) + 2 + n_options;\n\n        int cur_line;\n        if(MenuCursor <= n_profiles)\n            cur_line = 1 + MenuCursor;\n        else if(double_line)\n            cur_line = 1 + (n_profiles+1) + 2 + (MenuCursor-(n_profiles+1))*2;\n        else\n            cur_line = 1 + (n_profiles+1) + 2 + (MenuCursor-(n_profiles+1));\n\n        // handle scrolling\n        int scroll_start = 0;\n        int scroll_end = total_lines;\n\n        if(avail_lines < total_lines)\n        {\n            avail_lines --; // for scroll indicator\n\n            scroll_start = cur_line - avail_lines/2;\n            scroll_end = scroll_start + avail_lines;\n            if(scroll_start < 0)\n            {\n                scroll_start = 0;\n                scroll_end = scroll_start + avail_lines;\n            }\n            if(scroll_end > total_lines)\n            {\n                scroll_end = total_lines;\n                scroll_start = scroll_end - avail_lines;\n            }\n        }\n\n        // overall title and \"PROFILES\" come before the profiles\n        int start_y = sY + 2*line;\n\n        // render the scroll indicators\n        if(render)\n        {\n            if(scroll_start > 0)\n                XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[1].w / 2, start_y - GFX.MCursor[1].h, GFX.MCursor[1]);\n\n            if(scroll_end < total_lines)\n                XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[2].w / 2, start_y + (avail_lines)*line - line + 18, GFX.MCursor[2]);\n        }\n\n        if(render && 0 >= scroll_start && 0 < scroll_end)\n            SuperPrint(g_mainMenu.wordProfiles, 3, sX+16, start_y + (0-scroll_start)*line);\n\n        for(int i = 0; i <= n_profiles; i++)\n        {\n            if(i + 1 >= scroll_start && i + 1 < scroll_end)\n            {\n                if(render)\n                {\n                    bool in_use = false;\n\n                    for(Controls::InputMethod* method : Controls::g_InputMethods)\n                    {\n                        if(!method || i == n_profiles)\n                            continue;\n                        if(method->Profile == profiles[i])\n                        {\n                            in_use = true;\n                            break;\n                        }\n                    }\n\n                    if(i == n_profiles)\n                        SuperPrint(g_mainMenu.controlsNewProfile, 5, sX+48, start_y + (i + 1-scroll_start)*line);\n                    else if(in_use)\n                        SuperPrint(profiles[i]->Name + \" \" + g_mainMenu.controlsInUse, 5, sX+48, start_y + (i + 1-scroll_start)*line);\n                    else\n                        SuperPrint(profiles[i]->Name, 5, sX+48, start_y + (i + 1-scroll_start)*line);\n                    if(MenuCursor == i)\n                        XRender::renderTextureBasic(sX + 24, start_y + (i + 1-scroll_start)*line, GFX.MCursor[0]);\n                }\n                if(mouse)\n                {\n                    int item_width;\n                    if(i == n_profiles)\n                        item_width = (int)g_mainMenu.controlsNewProfile.size() * 18;\n                    else\n                        item_width = (int)profiles[i]->Name.size() * 18;\n\n                    if(SharedCursor.X >= sX + 48 && SharedCursor.X <= sX + 48 + item_width\n                        && SharedCursor.Y >= start_y + (i + 1 - scroll_start)*line && SharedCursor.Y <= start_y + (i + 1 - scroll_start) * line + 16)\n                    {\n                        if(MenuCursor != i)\n                        {\n                            PlaySoundMenu(SFX_Slide);\n                            MenuCursor = i;\n                        }\n\n                        if(MenuMouseRelease && SharedCursor.Primary)\n                        {\n                            s_cancelDoubleClick();\n                            if(MenuCursor != n_profiles)\n                            {\n                                PlaySoundMenu(SFX_Do);\n                                s_curProfile = MenuCursor;\n                                MenuCursor = 0;\n                            }\n                            else\n                            {\n                                PlaySoundMenu(SFX_DropItem);\n                                type->AddProfile();\n                            }\n                            MenuMouseRelease = false;\n                        }\n                    }\n                }\n            }\n        }\n\n        // \"PROFILE\", the profiles, \"NEW PROFILE\", a blank line, and \"OPTIONS\" come before the options\n        int o_base = (1+n_profiles+1+2);\n\n        if(n_options && render && o_base - 1 >= scroll_start && o_base - 1 < scroll_end)\n        {\n            SuperPrint(g_mainMenu.mainOptions, 3, sX+16, start_y + (o_base - 1 - scroll_start)*line);\n        }\n\n        for(int i = 0; i < n_options; i++)\n        {\n            const char* name = type->GetOptionName(i);\n            const char* value = type->GetOptionValue(i);\n            if(double_line)\n            {\n                if(render)\n                {\n                    if(o_base + 2*i >= scroll_start && o_base + 2*i + 1 < scroll_end)\n                    {\n                        if(name)\n                            SuperPrint(name, 5, sX+24, start_y + (o_base + 2*i - scroll_start)*line);\n                        if(value)\n                            SuperPrint(value, 3, sX+48, start_y + (o_base + 2*i + 1 - scroll_start)*line);\n                        if(MenuCursor - n_profiles - 1 == i)\n                            XRender::renderTextureBasic(sX + 24, start_y + (o_base + 2*i + 1 - scroll_start)*line, GFX.MCursor[0]);\n                    }\n                }\n                if(mouse && value)\n                {\n                    int item_width = SuperTextPixLen(value, 3);\n\n                    if(SharedCursor.X >= sX+48 && SharedCursor.X <= sX+48 + item_width\n                        && SharedCursor.Y >= start_y + (o_base + 2*i + 1 - scroll_start)*line && SharedCursor.Y <= start_y + (o_base + 2*i + 1 - scroll_start)*line + 16)\n                    {\n                        if(MenuCursor != i + n_profiles + 1)\n                        {\n                            PlaySoundMenu(SFX_Slide);\n                            MenuCursor = i + n_profiles + 1;\n                        }\n\n                        if(MenuMouseRelease && SharedCursor.Primary)\n                        {\n                            s_cancelDoubleClick();\n                            if(type->OptionChange(i))\n                                PlaySoundMenu(SFX_Do);\n                            else\n                                PlaySoundMenu(SFX_BlockHit);\n                            MenuMouseRelease = false;\n                        }\n                    }\n                }\n            }\n            else\n            {\n                if(render)\n                {\n                    if(o_base + i >= scroll_start && o_base + i < scroll_end)\n                    {\n                        if(name)\n                            SuperPrint(name, 5, sX+48, start_y + (o_base + i - scroll_start)*line);\n                        if(value)\n                            SuperPrintRightAlign(value, 3, sX+width-32, start_y + (o_base + i - scroll_start)*line);\n                        if(MenuCursor - n_profiles - 1 == i)\n                            XRender::renderTextureBasic(sX + 24, start_y + (o_base + i - scroll_start)*line, GFX.MCursor[0]);\n                    }\n                }\n                if(mouse)\n                {\n                    if(SharedCursor.X >= sX+48 && SharedCursor.X <= sX+width-32\n                        && SharedCursor.Y >= start_y + (o_base + i - scroll_start)*line && SharedCursor.Y <= start_y + (o_base + i - scroll_start)*line + 16)\n                    {\n                        if(MenuCursor != i + n_profiles + 1)\n                        {\n                            PlaySoundMenu(SFX_Slide);\n                            MenuCursor = i + n_profiles + 1;\n                        }\n\n                        if(MenuMouseRelease && SharedCursor.Primary)\n                        {\n                            s_cancelDoubleClick();\n                            if(type->OptionChange(i))\n                                PlaySoundMenu(SFX_Do);\n                            else\n                                PlaySoundMenu(SFX_BlockHit);\n                            MenuMouseRelease = false;\n                        }\n                    }\n                }\n            }\n        }\n\n    }\n    // rendering / mouse for the input method profile main screen\n    else if(s_profileTab == Controls::ControlsClass::None)\n    {\n        bool double_line = false;\n        if(width < 680)\n        {\n            double_line = true;\n            if(line > min_line_size + 2)\n            {\n                max_line = max_line * line / (min_line_size + 2);\n                line = (min_line_size + 2);\n            }\n        }\n        // should never happen\n        if(s_curType < 0 || s_curType >= n_types)\n        {\n            return 0;\n        }\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        const int n_profiles = (int)type->GetProfiles().size();\n\n        if(s_curProfile < 0 || s_curProfile >= n_profiles)\n        {\n            return 0;\n        }\n\n        Controls::InputMethodProfile* profile = type->GetProfiles()[s_curProfile];\n        const int n_options = (int)profile->GetOptionCount();\n        const int n_stock = 6;\n\n        if(render)\n        {\n            SuperPrintScreenCenter(profile->Name, 3, sY);\n\n            bool in_use = false;\n\n            for(Controls::InputMethod* method : Controls::g_InputMethods)\n            {\n                if(!method)\n                    continue;\n                if(method->Profile == profile)\n                    in_use = true;\n            }\n\n            if(in_use)\n                SuperPrintScreenCenter(g_mainMenu.controlsInUse, 3, sY+16);\n            else\n                SuperPrintScreenCenter(g_mainMenu.controlsNotInUse, 3, sY+16);\n        }\n\n        // first come the stock options, then the profile options.\n        // the scrolling is determined here\n\n        int avail_lines = max_line - 2;\n\n        int total_lines;\n        if(double_line)\n            total_lines = n_stock + 1 + n_options*2;\n        else\n            total_lines = n_stock + 1 + n_options;\n        if(n_options)\n            total_lines += 2;\n\n        int cur_line;\n        if(double_line && MenuCursor > n_stock)\n            cur_line = n_stock + (MenuCursor-n_stock)*2;\n        else\n            cur_line = MenuCursor;\n        if(MenuCursor >= 3)\n            cur_line += 1;\n        if(MenuCursor >= n_stock)\n            cur_line += 1;\n\n        // handle scrolling\n        int scroll_start = 0;\n        int scroll_end = total_lines;\n\n        if(avail_lines < total_lines)\n        {\n            avail_lines --; // for scroll indicator\n\n            scroll_start = cur_line - avail_lines/2;\n            scroll_end = scroll_start + avail_lines;\n            if(scroll_start < 0)\n            {\n                scroll_start = 0;\n                scroll_end = scroll_start + avail_lines;\n            }\n            if(scroll_end > total_lines)\n            {\n                scroll_end = total_lines;\n                scroll_start = scroll_end - avail_lines;\n            }\n        }\n\n        // overall title comes before the stock options\n        int start_y = sY + 1*line;\n\n        // render the scroll indicators\n        if(render)\n        {\n            if(scroll_start > 0)\n                XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[1].w / 2, start_y - GFX.MCursor[1].h, GFX.MCursor[1]);\n\n            if(scroll_end < total_lines)\n                XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[2].w / 2, start_y + (avail_lines)*line - line + 18, GFX.MCursor[2]);\n        }\n\n        for(int i = 0; i < n_stock; i++)\n        {\n            const char* name;\n            if(i == 0) // Rename profile\n                name = g_mainMenu.controlsRenameProfile.c_str();\n            else if(i == 1) // Delete profile\n                name = g_mainMenu.controlsDeleteProfile.c_str();\n            else if(i == 2) // Player\n                name = g_mainMenu.controlsPlayerControls.c_str();\n            else if(i == 3) // Cursor\n                name = g_mainMenu.controlsCursorControls.c_str();\n            else if(i == 4) // Editor\n                name = g_mainMenu.controlsEditorControls.c_str();\n            else if(i == 5) // Hotkeys\n                name = g_mainMenu.controlsHotkeys.c_str();\n            else\n                name = \"\";\n\n            int s; // shift\n            if(i >= 2)\n                s = 2;\n            else\n                s = 1;\n\n            if(render && i == 2 && i+s-2 >= scroll_start && i+s-2 < scroll_end)\n                SuperPrint(g_mainMenu.controlsTitle, 3, sX+16, start_y+(i+s - 1 - scroll_start)*line);\n\n            if(i+s-1 < scroll_start || i+s-1 >= scroll_end)\n                continue;\n\n            if(render)\n            {\n                SuperPrint(name, 5, sX+48, start_y+(i+s - scroll_start)*line);\n                if(MenuCursor == i)\n                    XRender::renderTextureBasic(sX + 24, start_y + (i+s - scroll_start)*line, GFX.MCursor[0]);\n            }\n            if(mouse)\n            {\n                int item_width = SuperTextPixLen(name, 3);\n\n                if(SharedCursor.X >= sX+48 && SharedCursor.X <= sX+48 + item_width\n                    && SharedCursor.Y >= start_y + (i+s-scroll_start)*line && SharedCursor.Y <= start_y + (i+s-scroll_start)*line + 16)\n                {\n                    if(MenuCursor != i)\n                    {\n                        PlaySoundMenu(SFX_Slide);\n                        MenuCursor = i;\n                    }\n\n                    if(MenuMouseRelease && SharedCursor.Primary)\n                    {\n                        s_cancelDoubleClick();\n                        MenuMouseRelease = false;\n\n                        menuControls_Do();\n                    }\n                }\n            }\n        }\n\n        // overall title, stock options, and \"OPTIONS\" come before the options\n        int o_base = n_stock+3;\n\n        if(n_options && render && o_base - 1 >= scroll_start && o_base - 1 < scroll_end)\n        {\n            SuperPrint(g_mainMenu.mainOptions, 3, sX+16, start_y + (o_base - 1 - scroll_start)*line);\n        }\n\n        for(int i = 0; i < n_options; i++)\n        {\n            const char* name = profile->GetOptionName(i);\n            const char* value = profile->GetOptionValue(i);\n            if(double_line)\n            {\n                if(render)\n                {\n                    if(o_base + 2*i >= scroll_start && o_base + 2*i < scroll_end && name)\n                        SuperPrint(name, 5, sX+24, start_y + (o_base + 2*i - scroll_start)*line, {160, 160, 255});\n                    if(o_base + 2*i + 1 >= scroll_start && o_base + 2*i + 1 < scroll_end)\n                    {\n                        if(value)\n                            SuperPrint(value, 3, sX+48, start_y + (o_base + 2*i + 1 - scroll_start)*line);\n                        if(MenuCursor - n_stock == i)\n                            XRender::renderTextureBasic(sX + 24, start_y + (o_base + 2*i + 1 - scroll_start)*line, GFX.MCursor[0]);\n                    }\n                }\n\n                if(mouse && value)\n                {\n                    int item_width = SuperTextPixLen(value, 3);\n\n                    if(SharedCursor.X >= sX+48 && SharedCursor.X <= sX+48 + item_width\n                        && SharedCursor.Y >= start_y + (o_base + 2*i + 1 - scroll_start)*line && SharedCursor.Y <= start_y + (o_base + 2*i + 1 - scroll_start)*line + 16)\n                    {\n                        if(MenuCursor != i + n_stock)\n                        {\n                            PlaySoundMenu(SFX_Slide);\n                            MenuCursor = i + n_stock;\n                        }\n\n                        if(MenuMouseRelease && SharedCursor.Primary)\n                        {\n                            s_cancelDoubleClick();\n                            if(type->OptionChange(i))\n                                PlaySoundMenu(SFX_Do);\n                            else\n                                PlaySoundMenu(SFX_BlockHit);\n                            MenuMouseRelease = false;\n                        }\n                    }\n                }\n            }\n            else\n            {\n                if(render)\n                {\n                    if(o_base + i >= scroll_start && o_base + i < scroll_end)\n                    {\n                        if(name)\n                            SuperPrint(name, 5, sX+48, start_y + (o_base + i - scroll_start)*line);\n                        if(value)\n                            SuperPrintRightAlign(value, 3, sX+width-32, start_y + (o_base + i - scroll_start)*line);\n                        if(MenuCursor - n_stock == i)\n                            XRender::renderTextureBasic(sX + 24, start_y + (o_base + i - scroll_start)*line, GFX.MCursor[0]);\n                    }\n                }\n\n                if(mouse)\n                {\n                    if(SharedCursor.X >= sX+48 && SharedCursor.X <= sX+width-32\n                        && SharedCursor.Y >= start_y + (o_base + i - scroll_start)*line && SharedCursor.Y <= start_y + (o_base + i - scroll_start)*line + 16)\n                    {\n                        if(MenuCursor != i + n_stock)\n                        {\n                            PlaySoundMenu(SFX_Slide);\n                            MenuCursor = i + n_stock;\n                        }\n\n                        if(MenuMouseRelease && SharedCursor.Primary)\n                        {\n                            s_cancelDoubleClick();\n                            if(profile->OptionChange(i))\n                                PlaySoundMenu(SFX_Do);\n                            else\n                                PlaySoundMenu(SFX_BlockHit);\n                            MenuMouseRelease = false;\n                        }\n                    }\n                }\n            }\n        }\n    }\n    // rendering / mouse for the input method profile sub screens\n    else\n    {\n        bool double_line = false;\n        if(width < 680)\n        {\n            double_line = true;\n            if(line > min_line_size + 2)\n            {\n                max_line = max_line * line / (min_line_size + 2);\n                line = (min_line_size + 2);\n            }\n        }\n        // should never happen\n        if(s_curType < 0 || s_curType >= n_types)\n        {\n            return 0;\n        }\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        const int n_profiles = (int)type->GetProfiles().size();\n\n        if(s_curProfile < 0 || s_curProfile >= n_profiles)\n        {\n            return 0;\n        }\n\n        Controls::InputMethodProfile* profile = type->GetProfiles()[s_curProfile];\n\n        int n_buttons;\n        if(s_profileTab == Controls::ControlsClass::Player)\n            n_buttons = Controls::PlayerControls::n_buttons;\n        else if(s_profileTab == Controls::ControlsClass::Cursor)\n            n_buttons = Controls::CursorControls::n_buttons;\n        else if(s_profileTab == Controls::ControlsClass::Editor)\n            n_buttons = Controls::EditorControls::n_buttons;\n        else if(s_profileTab == Controls::ControlsClass::Hotkey)\n            n_buttons = Controls::Hotkeys::n_buttons;\n        else\n        {\n            // shouldn't happen\n            s_profileTab = Controls::ControlsClass::None;\n            return 0;\n        }\n\n        if(render)\n        {\n            SuperPrintScreenCenter(profile->Name, 3, sY);\n\n            bool in_use = false;\n\n            for(Controls::InputMethod* method : Controls::g_InputMethods)\n            {\n                if(!method)\n                    continue;\n                if(method->Profile == profile)\n                    in_use = true;\n            }\n\n            if(in_use)\n                SuperPrintScreenCenter(g_mainMenu.controlsInUse, 3, sY+16);\n            else\n                SuperPrintScreenCenter(g_mainMenu.controlsNotInUse, 3, sY+16);\n        }\n\n        // the buttons are the only thing now.\n        // work out scrolling here\n\n        int avail_lines = max_line - 3;\n\n        int total_lines;\n        if(double_line)\n            total_lines = 1 + n_buttons*2;\n        else\n            total_lines = 1 + n_buttons;\n\n        int cur_line;\n        if(double_line)\n            cur_line = MenuCursor*2;\n        else\n            cur_line = MenuCursor;\n\n        // handle scrolling\n        int scroll_start = 0;\n        int scroll_end = total_lines;\n\n        if(avail_lines < total_lines)\n        {\n            avail_lines --; // for scroll indicator\n\n            scroll_start = cur_line - avail_lines/2;\n            scroll_end = scroll_start + avail_lines;\n            if(scroll_start < 0)\n            {\n                scroll_start = 0;\n                scroll_end = scroll_start + avail_lines;\n            }\n            if(scroll_end > total_lines)\n            {\n                scroll_end = total_lines;\n                scroll_start = scroll_end - avail_lines;\n            }\n        }\n\n        // overall title and \"BUTTONS\" come before the buttons\n        int start_y = sY + 2*line;\n        int b_base = 1;\n\n        // render the scroll indicators\n        if(render)\n        {\n            if(scroll_start > 0)\n                XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[1].w / 2, start_y - GFX.MCursor[1].h, GFX.MCursor[1]);\n\n            if(scroll_end < total_lines)\n                XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[2].w / 2, start_y + (avail_lines)*line - line + 18, GFX.MCursor[2]);\n        }\n\n        // render the word \"BUTTONS\"\n        if(render && scroll_start == 0)\n        {\n            SuperPrint(g_mainMenu.wordButtons, 3, sX+16, start_y);\n            SuperPrintRightAlign(g_mainMenu.controlsDeleteKey, 3, sX+width-16, start_y);\n        }\n\n        for(int i = 0; i < n_buttons; i++)\n        {\n            int label_row, value_row, label_x, left_value_x, right_value_x;\n            XTColor label_color;\n\n            if(double_line)\n            {\n                label_color = {160, 160, 255};\n\n                label_row = b_base + (2 * i) - scroll_start;\n                value_row = label_row + 1;\n\n                label_x = sX + 24;\n                left_value_x = sX + 48;\n                right_value_x = sX + 48 + ((width - 48) / 2);\n            }\n            else\n            {\n                label_row = b_base + i - scroll_start;\n                value_row = label_row;\n\n                label_x = sX + 48;\n                left_value_x = sX + width - 420;\n                right_value_x = sX + width - 210;\n            }\n\n            if(render && label_row >= 0 && label_row < avail_lines)\n            {\n                const char* name = nullptr;\n                if(s_profileTab == Controls::ControlsClass::Player)\n                    name = Controls::PlayerControls::GetButtonName_UI(i);\n                else if(s_profileTab == Controls::ControlsClass::Cursor)\n                    name = Controls::CursorControls::GetButtonName_UI(i);\n                else if(s_profileTab == Controls::ControlsClass::Editor)\n                    name = Controls::EditorControls::GetButtonName_UI(i);\n                else if(s_profileTab == Controls::ControlsClass::Hotkey)\n                    name = Controls::Hotkeys::GetButtonName_UI(i);\n\n                SuperPrint(name, 5, label_x, start_y + label_row * line, label_color);\n\n                if(MenuCursor == i && !s_secondaryInput && g_pollingInput)\n                    SuperPrint(\"...\", 3, left_value_x, start_y + value_row * line);\n                else\n                    SuperPrint(profile->NamePrimaryButton(s_profileTab, i), 3, left_value_x, start_y + value_row * line);\n\n                if(MenuCursor == i && s_secondaryInput && g_pollingInput)\n                    SuperPrint(\"...\", 3, right_value_x, start_y + value_row * line);\n                else\n                    SuperPrint(profile->NameSecondaryButton(s_profileTab, i), 3, right_value_x, start_y + value_row * line);\n\n                if(MenuCursor == i)\n                {\n                    if(!s_secondaryInput)\n                        XRender::renderTextureBasic(left_value_x - 24, start_y + value_row * line, GFX.MCursor[0]);\n                    else\n                        XRender::renderTextureBasic(right_value_x - 24, start_y + value_row * line, GFX.MCursor[0]);\n                }\n            }\n\n            if(mouse && !g_pollingInput && SharedCursor.Y >= start_y + value_row * line && SharedCursor.Y <= start_y + value_row * line + 16)\n            {\n                int primary_width = SuperTextPixLen(profile->NamePrimaryButton(s_profileTab, i), 3);\n                int secondary_width = SuperTextPixLen(profile->NameSecondaryButton(s_profileTab, i), 3);\n\n                if(primary_width < 72)\n                    primary_width = 72;\n                if(secondary_width < 72)\n                    secondary_width = 72;\n\n                if(SharedCursor.X >= left_value_x && SharedCursor.X <= left_value_x + primary_width)\n                {\n                    if(MenuCursor != i || s_secondaryInput)\n                    {\n                        PlaySoundMenu(SFX_Slide);\n                        MenuCursor = i;\n                        s_secondaryInput = false;\n                    }\n\n                    if(MenuMouseRelease && SharedCursor.Primary)\n                    {\n                        s_cancelDoubleClick();\n                        PlaySoundMenu(SFX_PSwitch);\n                        g_pollingInput = true;\n                        MenuCursorCanMove = false;\n                        MenuMouseRelease = false;\n                        return 0;\n                    }\n                }\n\n                if(SharedCursor.X >= right_value_x && SharedCursor.X <= right_value_x + secondary_width)\n                {\n                    if(MenuCursor != i || !s_secondaryInput)\n                    {\n                        PlaySoundMenu(SFX_Slide);\n                        MenuCursor = i;\n                        s_secondaryInput = true;\n                    }\n\n                    if(MenuMouseRelease && SharedCursor.Primary)\n                    {\n                        s_cancelDoubleClick();\n                        PlaySoundMenu(SFX_PSwitch);\n                        g_pollingInput = true;\n                        MenuCursorCanMove = false;\n                        MenuMouseRelease = false;\n                        return 0;\n                    }\n                }\n            }\n        }\n    }\n\n    if(mouse && (SharedCursor.Primary || SharedCursor.Secondary))\n        MenuMouseRelease = false;\n\n    return 0;\n}\n\nint menuControls_MouseLogic()\n{\n    return menuControls_Mouse_Render(true, false);\n}\n\nint menuControls_Logic()\n{\n    // allow input methods to join so long as not currently changing a profile\n    if(!g_pollingInput && GameMenu)\n        Controls::PollInputMethod();\n\n    const int n_types = (int)Controls::g_InputMethodTypes.size();\n\n    MenuControls_t menuControls = Controls::GetMenuControls();\n\n    if(g_pollingInput)\n    {\n        // should never happen\n        if(s_curType < 0 || s_curType >= n_types)\n        {\n            g_pollingInput = false;\n            return 0;\n        }\n\n        const int n_profiles = (int)Controls::g_InputMethodTypes[s_curType]->GetProfiles().size();\n\n        if(s_curProfile < 0 || s_curProfile >= n_profiles)\n        {\n            g_pollingInput = false;\n            return 0;\n        }\n\n        Controls::InputMethodProfile* profile = Controls::g_InputMethodTypes[s_curType]->GetProfiles()[s_curProfile];\n\n        int n_buttons;\n        if(s_profileTab == Controls::ControlsClass::Player)\n            n_buttons = Controls::PlayerControls::n_buttons;\n        else if(s_profileTab == Controls::ControlsClass::Cursor)\n            n_buttons = Controls::CursorControls::n_buttons;\n        else if(s_profileTab == Controls::ControlsClass::Editor)\n            n_buttons = Controls::EditorControls::n_buttons;\n        else if(s_profileTab == Controls::ControlsClass::Hotkey)\n            n_buttons = Controls::Hotkeys::n_buttons;\n        else\n            n_buttons = 0;\n\n        if(MenuCursor < 0 || MenuCursor >= n_buttons)\n        {\n            g_pollingInput = false;\n            return 0;\n        }\n\n        if((!s_secondaryInput && profile->PollPrimaryButton(s_profileTab, MenuCursor))\n            || (s_secondaryInput && profile->PollSecondaryButton(s_profileTab, MenuCursor)))\n        {\n            g_pollingInput = false;\n            MenuCursorCanMove = false;\n            s_canDelete = false;\n            Controls::g_disallowHotkeys = true;\n            PlaySoundMenu(SFX_PSwitch);\n            return 0;\n        }\n\n        if(MenuCursorCanMove && menuControls.Back)\n        {\n            g_pollingInput = false;\n            MenuCursorCanMove = false;\n            s_canDelete = false;\n            PlaySoundMenu(SFX_Slide);\n            return 0;\n        }\n\n        return 0;\n    }\n\n    menuControls.Back |= (SharedCursor.Secondary && MenuMouseRelease);\n\n    if(menuControls.Back && menuControls.Do)\n        menuControls.Do = false;\n\n    if(menuControls.Erase && s_canDelete)\n        s_canDelete = false;\n    else if(menuControls.Erase)\n        menuControls.Erase = false;\n    else\n        s_canDelete = true;\n\n    // logic for the profile deletion screen\n    if(s_deleteProfileSel)\n    {\n        // keep things in range\n        while(MenuCursor < 0)\n            MenuCursor += 2;\n        while(MenuCursor >= 2)\n            MenuCursor -= 2;\n\n        if(!MenuCursorCanMove)\n            return 0;\n\n        if(s_curType < 0 || s_curType >= n_types)\n        {\n            s_deleteProfileSel = false;\n            return 0;\n        }\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        const int n_profiles = (int)type->GetProfiles().size();\n\n        if(s_curProfile < 0 || s_curProfile >= n_profiles)\n        {\n            s_deleteProfileSel = false;\n            return 0;\n        }\n\n        Controls::InputMethodProfile* profile = type->GetProfiles()[s_curProfile];\n\n        if(menuControls.Back || (menuControls.Do && MenuCursor == 0))\n        {\n            PlaySoundMenu(SFX_Slide);\n            s_deleteProfileSel = false;\n            MenuCursor = 1; // Delete Profile\n            MenuCursorCanMove = false;\n        }\n        else if(menuControls.Do && MenuCursor == 1)\n        {\n            if(type->DeleteProfile(profile, Controls::g_InputMethods))\n            {\n                PlaySoundMenu(SFX_VillainKilled);\n                s_deleteProfileSel = false;\n                MenuCursor = s_curProfile - 1;\n                if(MenuCursor < 0)\n                    MenuCursor = 0;\n                s_curProfile = -1;\n            }\n            else\n                PlaySoundMenu(SFX_BlockHit);\n            MenuCursorCanMove = false;\n        }\n    }\n    // logic for the main controls screen\n    else if(s_curType == -1)\n    {\n        // keep things in range\n        while(MenuCursor < 0)\n            MenuCursor += n_types;\n        while(MenuCursor >= n_types)\n            MenuCursor -= n_types;\n\n        if(!MenuCursorCanMove)\n            return 0;\n\n        // back and forward nav\n        if(menuControls.Back)\n        {\n            PlaySoundMenu(SFX_Slide);\n            MenuCursorCanMove = false;\n            return -1;\n        }\n        if(menuControls.Do && MenuCursor >= 0 && MenuCursor < n_types)\n        {\n            s_curType = MenuCursor;\n            MenuCursor = 0;\n            PlaySoundMenu(SFX_Do);\n            MenuCursorCanMove = false;\n            return 0;\n        }\n    }\n    // logic for the input method type screens\n    else if(s_curProfile == -1)\n    {\n        // should never happen\n        if(s_curType < 0 || s_curType >= n_types)\n        {\n            SDL_assert_release(false); // invalid state in controls settings\n            PlaySoundMenu(SFX_BlockHit);\n            s_curType = -1;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        const int n_options = (int)type->GetOptionCount();\n        const int n_profiles = (int)type->GetProfiles().size();\n\n        // keep things in range\n        while(MenuCursor < 0)\n            MenuCursor += n_profiles + 1 + n_options;\n        while(MenuCursor >= n_profiles + 1 + n_options)\n            MenuCursor -= n_profiles + 1 + n_options;\n\n        if(!MenuCursorCanMove)\n            return 0;\n\n        // backward navigation\n        if(menuControls.Back)\n        {\n            PlaySoundMenu(SFX_Slide);\n            MenuCursor = s_curType;\n            s_curType = -1;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n\n        // first come the profiles, then the type options.\n\n        // forward navigation\n        if(menuControls.Do && MenuCursor >= 0 && MenuCursor < n_profiles)\n        {\n            PlaySoundMenu(SFX_Do);\n            s_curProfile = MenuCursor;\n            s_profileTab = Controls::ControlsClass::None;\n            s_secondaryInput = false;\n            MenuCursor = 0;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n\n        // creation\n        if(menuControls.Do && MenuCursor == n_profiles)\n        {\n            PlaySoundMenu(SFX_DropItem);\n            type->AddProfile();\n            MenuCursorCanMove = false;\n            return 0;\n        }\n\n        // options logic\n        if(MenuCursor >= n_profiles + 1 && MenuCursor < n_profiles + 1 + n_options)\n        {\n            if(menuControls.Do)\n            {\n                if(type->OptionChange(MenuCursor - n_profiles - 1))\n                    PlaySoundMenu(SFX_Slide);\n                else\n                    PlaySoundMenu(SFX_BlockHit);\n\n                MenuCursorCanMove = false;\n            }\n            else if(menuControls.Left)\n            {\n                if(type->OptionRotateLeft(MenuCursor - n_profiles - 1))\n                    PlaySoundMenu(SFX_Slide);\n                else\n                    PlaySoundMenu(SFX_BlockHit);\n\n                MenuCursorCanMove = false;\n            }\n            else if(menuControls.Right)\n            {\n                if(type->OptionRotateRight(MenuCursor - n_profiles - 1))\n                    PlaySoundMenu(SFX_Slide);\n                else\n                    PlaySoundMenu(SFX_BlockHit);\n\n                MenuCursorCanMove = false;\n            }\n        }\n    }\n    // logic for the input method profile screens\n    else if(s_profileTab == Controls::ControlsClass::None)\n    {\n        // should never happen\n        if(s_curType < 0 || s_curType >= n_types)\n        {\n            SDL_assert_release(false); // invalid state in controls settings\n            PlaySoundMenu(SFX_BlockHit);\n            s_curType = -1;\n            s_curProfile = -1;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        const int n_profiles = (int)type->GetProfiles().size();\n\n        if(s_curProfile < 0 || s_curProfile >= n_profiles)\n        {\n            SDL_assert_release(false); // invalid state in controls settings\n            PlaySoundMenu(SFX_BlockHit);\n            s_curProfile = -1;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n\n        Controls::InputMethodProfile* profile = type->GetProfiles()[s_curProfile];\n        const int n_stock = 6;\n        const int n_options = (int)profile->GetOptionCount();\n\n        // first come the stock options, then the profile options.\n\n        // keep things in range\n        while(MenuCursor < 0)\n            MenuCursor += n_options + n_stock;\n        while(MenuCursor >= n_options + n_stock)\n            MenuCursor -= n_options + n_stock;\n\n        if(!MenuCursorCanMove)\n            return 0;\n\n        // backward navigation\n        if(menuControls.Back)\n        {\n            PlaySoundMenu(SFX_Slide);\n            MenuCursor = s_curProfile;\n            s_curProfile = -1;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n\n        // stock options\n        if(MenuCursor >= 0 && MenuCursor < n_stock && menuControls.Do)\n        {\n            return menuControls_Do();\n        }\n\n        // options logic\n        if(MenuCursor >= n_stock && MenuCursor < n_stock + n_options)\n        {\n            if(menuControls.Do)\n            {\n                if(profile->OptionChange(MenuCursor - n_stock))\n                    PlaySoundMenu(SFX_Slide);\n                else\n                    PlaySoundMenu(SFX_BlockHit);\n\n                MenuCursorCanMove = false;\n            }\n            else if(menuControls.Left)\n            {\n                if(profile->OptionRotateLeft(MenuCursor - n_stock))\n                    PlaySoundMenu(SFX_Slide);\n                else\n                    PlaySoundMenu(SFX_BlockHit);\n\n                MenuCursorCanMove = false;\n            }\n            else if(menuControls.Right)\n            {\n                if(profile->OptionRotateRight(MenuCursor - n_stock))\n                    PlaySoundMenu(SFX_Slide);\n                else\n                    PlaySoundMenu(SFX_BlockHit);\n\n                MenuCursorCanMove = false;\n            }\n        }\n    }\n    // logic for the controls submenus of the input method profile screens\n    else\n    {\n        // should never happen\n        if(s_curType < 0 || s_curType >= n_types)\n        {\n            SDL_assert_release(false); // invalid state in controls settings\n            PlaySoundMenu(SFX_BlockHit);\n            s_curType = -1;\n            s_curProfile = -1;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n\n        Controls::InputMethodType* type = Controls::g_InputMethodTypes[s_curType];\n        const int n_profiles = (int)type->GetProfiles().size();\n\n        if(s_curProfile < 0 || s_curProfile >= n_profiles)\n        {\n            SDL_assert_release(false); // invalid state in controls settings\n            PlaySoundMenu(SFX_BlockHit);\n            s_curProfile = -1;\n            MenuCursorCanMove = false;\n            return 0;\n        }\n\n        Controls::InputMethodProfile* profile = type->GetProfiles()[s_curProfile];\n\n        int n_buttons;\n        if(s_profileTab == Controls::ControlsClass::Player)\n            n_buttons = Controls::PlayerControls::n_buttons;\n        else if(s_profileTab == Controls::ControlsClass::Cursor)\n            n_buttons = Controls::CursorControls::n_buttons;\n        else if(s_profileTab == Controls::ControlsClass::Editor)\n            n_buttons = Controls::EditorControls::n_buttons;\n        else if(s_profileTab == Controls::ControlsClass::Hotkey)\n            n_buttons = Controls::Hotkeys::n_buttons;\n        else\n        {\n            // shouldn't happen\n            SDL_assert_release(false);\n            s_profileTab = Controls::ControlsClass::None;\n            return 0;\n        }\n\n        // keep things in range\n        // why didn't I write MenuCursor %= n_buttons?\n        while(MenuCursor < 0)\n            MenuCursor += n_buttons;\n\n        while(MenuCursor >= n_buttons)\n            MenuCursor -= n_buttons;\n\n        if(!MenuCursorCanMove)\n            return 0;\n\n        // backward navigation\n        if(menuControls.Back)\n        {\n            PlaySoundMenu(SFX_Slide);\n\n            if(s_profileTab == Controls::ControlsClass::Player)\n                MenuCursor = 2;\n            else if(s_profileTab == Controls::ControlsClass::Cursor)\n                MenuCursor = 3;\n            else if(s_profileTab == Controls::ControlsClass::Editor)\n                MenuCursor = 4;\n            else if(s_profileTab == Controls::ControlsClass::Hotkey)\n                MenuCursor = 5;\n\n            s_profileTab = Controls::ControlsClass::None;\n            s_secondaryInput = false;\n            MenuCursorCanMove = false;\n\n            return 0;\n        }\n\n        // key logic\n        if(MenuCursor >= 0 && MenuCursor < n_buttons)\n        {\n            if(menuControls.Do)\n            {\n                PlaySoundMenu(SFX_PSwitch);\n                g_pollingInput = true;\n                MenuCursorCanMove = false;\n                return 0;\n            }\n            else if(menuControls.Left || menuControls.Right)\n            {\n                PlaySoundMenu(SFX_Slide);\n                s_secondaryInput = !s_secondaryInput;\n                MenuCursorCanMove = false;\n            }\n            else if(menuControls.Erase && s_secondaryInput)\n            {\n                if(profile->DeleteSecondaryButton(s_profileTab, MenuCursor))\n                    PlaySoundMenu(SFX_PlayerDied2);\n                else\n                    PlaySoundMenu(SFX_BlockHit);\n\n                MenuCursorCanMove = false;\n            }\n            else if(menuControls.Erase)\n            {\n                if(profile->DeletePrimaryButton(s_profileTab, MenuCursor))\n                    PlaySoundMenu(SFX_PlayerDied2);\n                else\n                    PlaySoundMenu(SFX_BlockHit);\n\n                MenuCursorCanMove = false;\n            }\n        }\n    }\n\n    return menuControls_MouseLogic();\n}\n\nvoid menuControls_Render()\n{\n    menuControls_Mouse_Render(false, true);\n}\n"
  },
  {
    "path": "src/main/menu_controls.h",
    "content": "#ifndef MENU_CONTROLS_H\n\n#define MENU_CONTROLS_H\n\nextern bool g_pollingInput;\n\nvoid menuControls_Render();\n// return values:\n// -1 back\n// 0 continue\nint menuControls_Logic();\n\n#endif // MENU_CONTROLS_H"
  },
  {
    "path": "src/main/menu_loop.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <AppPath/app_path.h>\n#include <Utils/files.h>\n#include <Integrator/integrator.h>\n#include <PGE_File_Formats/file_formats.h>\n#include <fmt_format_ne.h>\n\n#include \"../globals.h\"\n#include \"../game_main.h\"\n#include \"../sound.h\"\n#include \"../effect.h\"\n#include \"../graphics.h\"\n#include \"../blocks.h\"\n#include \"../npc.h\"\n#include \"../eff_id.h\"\n#include \"../layers.h\"\n#include \"../player.h\"\n#include \"../collision.h\"\n#include \"../controls.h\"\n#include \"menu_main.h\"\n#include \"game_info.h\"\n#include \"game_globals.h\"\n#include \"menu_controls.h\"\n\n#include \"npc_traits.h\"\n\n#include \"control/controls_methods.h\" // to cancel keyboard's double-click fullscreen\n#include \"main/trees.h\"\n#include \"script/luna/luna.h\"\n\n#include \"npc/npc_queues.h\"\n\n\nstatic void updateIntroLevelActivity()\n{\n    Location_t tempLocation;\n    bool tempBool;\n\n    SingleCoop = 0;\n\n    // only restore the level on the first frame that all players are dead\n    static bool restore_done = false;\n\n    bool any_living = CheckLiving();\n    if(!any_living && !restore_done)\n    {\n        ShowLayer(LAYER_DESTROYED_BLOCKS);\n\n        For(A, 1, numNPCs)\n        {\n            if(NPC[A].DefaultType == 0)\n            {\n                if(NPC[A].TimeLeft > 10) NPC[A].TimeLeft = 10;\n            }\n        }\n\n        restore_done = true;\n    }\n    else if(any_living)\n    {\n        restore_done = false;\n    }\n\n    For(A, 1, numPlayers)\n    {\n        Player_t &p = Player[A];\n        if(p.TimeToLive > 0)\n        {\n            p.TimeToLive = 0;\n            p.Dead = true;\n        }\n\n        p.Controls.Down = false;\n        p.Controls.Drop = false;\n        p.Controls.Right = true;\n        p.Controls.Left = false;\n        p.Controls.Run = true;\n        p.Controls.Up = false;\n        p.Controls.AltRun = false;\n        p.Controls.AltJump = false;\n\n        if(p.Jump == 0 || p.Location.Y < level[0].Y + 200)\n            p.Controls.Jump = false;\n\n        if(p.Location.SpeedX < 0.5_n)\n        {\n            p.Controls.Jump = true;\n            if(p.Slope > 0 || p.StandingOnNPC > 0 || p.Location.SpeedY == 0)\n                p.CanJump = true;\n        }\n\n        if(p.HoldingNPC == 0)\n        {\n            if((p.State == 3 || p.State == 6 || p.State == 7) && iRand(100) >= 90)\n            {\n                if(p.FireBallCD == 0 && !p.RunRelease)\n                    p.Controls.Run = false;\n            }\n\n            if((p.State == 4 || p.State == 5) && p.TailCount == 0 && !p.RunRelease)\n            {\n                tempLocation.Width = 24;\n                tempLocation.Height = 20;\n                tempLocation.Y = p.Location.Y + p.Location.Height - 22;\n                tempLocation.X = p.Location.X + p.Location.Width;\n\n                for(int B : treeNPCQuery(tempLocation, SORTMODE_NONE))\n                {\n                    if(NPC[B].Active && !NPC[B]->IsABonus &&\n                       !NPC[B]->WontHurt && NPC[B].HoldingPlayer == 0)\n                    {\n                        if(CheckCollision(tempLocation, NPC[B].Location))\n                        {\n                            p.Controls.Run = false;\n                            break;\n                        }\n                    }\n                }\n            }\n\n            if(p.StandingOnNPC > 0)\n            {\n                if(NPC[p.StandingOnNPC]->GrabFromTop)\n                {\n                    p.Controls.Down = true;\n                    p.Controls.Run = true;\n                    p.RunRelease = true;\n                }\n            }\n        }\n\n        if(p.Character == 5)\n        {\n            if(p.FireBallCD == 0 && !p.RunRelease)\n            {\n                tempLocation.Width = 38 + p.Location.SpeedX / 2;\n                tempLocation.Height = p.Location.Height - 8;\n                tempLocation.Y = p.Location.Y + 4;\n                tempLocation.X = p.Location.X + p.Location.Width;\n\n                for(int B : treeNPCQuery(tempLocation, SORTMODE_NONE))\n                {\n                    if(NPC[B].Active && !NPC[B]->IsABonus &&\n                      !NPC[B]->WontHurt && NPC[B].HoldingPlayer == 0)\n                    {\n                        if(CheckCollision(tempLocation, NPC[B].Location))\n                        {\n                            p.RunRelease = true;\n                            if(NPC[B].Location.Y > p.Location.Y + p.Location.Height / 2)\n                                p.Controls.Down = true;\n                            break;\n                        }\n                    }\n                }\n            }\n\n            if(p.Slope == 0 && p.StandingOnNPC == 0)\n            {\n                if(p.Location.SpeedY < 0)\n                {\n                    tempLocation.Width = 200;\n                    tempLocation.Height = p.Location.Y - level[0].Y + p.Location.Height;\n                    tempLocation.Y = level[0].Y;\n                    tempLocation.X = p.Location.X;\n\n                    for(int B : treeNPCQuery(tempLocation, SORTMODE_NONE))\n                    {\n                        if(NPC[B].Active && !NPC[B]->IsABonus &&\n                           !NPC[B]->WontHurt && NPC[B].HoldingPlayer == 0)\n                        {\n                            if(CheckCollision(tempLocation, NPC[B].Location))\n                            {\n                                p.Controls.Up = true;\n                                break;\n                            }\n                        }\n                    }\n\n                }\n                else if(p.Location.SpeedY > 0)\n                {\n                    tempLocation.Width = 200;\n                    tempLocation.Height = level[0].Height - p.Location.Y;\n                    tempLocation.Y = p.Location.Y;\n                    tempLocation.X = p.Location.X;\n\n                    for(int B : treeNPCQuery(tempLocation, SORTMODE_NONE))\n                    {\n                        if(NPC[B].Active && !NPC[B]->IsABonus &&\n                           !NPC[B]->WontHurt && NPC[B].HoldingPlayer == 0)\n                        {\n                            if(CheckCollision(tempLocation, NPC[B].Location))\n                            {\n                                p.Controls.Down = true;\n                                break;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        if(p.Location.X < -vScreen[1].X - p.Location.Width && -vScreen[1].X > level[0].X)\n            p.Dead = true;\n\n        if(p.Location.X > -vScreen[1].X + vScreen[1].Width + 200)\n            p.Dead = true;\n\n        if(p.Location.X > -vScreen[1].X + vScreen[1].Width * 0.75_n && -vScreen[1].X + vScreen[1].Width + 50 < level[0].Width)\n            p.Controls.Run = false;\n\n        if(-vScreen[1].X <= level[0].X && (p.Dead || p.TimeToLive > 0) && g_gameInfo.introMaxPlayersCount > 0)\n        {\n            p.ForceHold = 65;\n            p.State = iRand(6) + 2;\n            p.CanFly = false;\n            p.CanFly2 = false;\n            p.TailCount = 0;\n            p.Dead = false;\n            p.TimeToLive = 0;\n            iRand(1); // advance the random state by one\n//            p.Character = (iRand() % 5) + 1;\n\n//            if(A >= 1 && A <= 5)\n            p.Character = g_gameInfo.introCharacterNext();\n\n            p.HeldBonus = NPCID(0);\n            p.Section = 0;\n            p.Mount = 0;\n            p.MountType = 0;\n            p.YoshiBlue = false;\n            p.YoshiRed = false;\n            p.YoshiYellow = false;\n            p.YoshiNPC = 0;\n            p.Wet = 0;\n            p.WetFrame = false;\n            p.YoshiPlayer = 0;\n            p.Bumped = false;\n            p.Bumped2 = 0;\n            p.Direction = 1;\n            p.Dismount = 0;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.FireBallCD = 0;\n            p.ForceHold = 0;\n            p.Warp = 0;\n            p.WarpBackward = false;\n            p.WarpCD = 0;\n            p.GroundPound = false;\n            p.Immune = 0;\n            p.Frame = 0;\n            p.Slope = 0;\n            p.Slide = false;\n            p.SpinJump = false;\n            p.FrameCount = 0;\n            p.TailCount = 0;\n            p.Duck = false;\n            p.GroundPound = false;\n            p.Hearts = 3;\n\n            PlayerFrame(p);\n\n            p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n            p.Location.X = level[p.Section].X - A * 48;\n            p.Location.SpeedX = Physics.PlayerRunSpeed;\n            p.Location.Y = level[p.Section].Height - p.Location.Height - 33;\n\n            do\n            {\n                tempBool = true;\n                for(int B : treeBlockQuery(p.Location, SORTMODE_NONE))\n                {\n                    if(CheckCollision(p.Location, Block[B].Location))\n                    {\n                        p.Location.Y = Block[B].Location.Y - p.Location.Height - 0.1_n;\n                        tempBool = false;\n                        break;\n                    }\n                }\n            } while(!tempBool);\n\n            if(!UnderWater[p.Section])\n            {\n                if(iRand(25) == 0)\n                {\n                    p.Mount = 1;\n                    p.MountType = iRand(3) + 1;\n                    if(p.State == 1)\n                    {\n                        p.Location.Height = Physics.PlayerHeight[1][2];\n                        p.Location.Y += -Physics.PlayerHeight[1][2] + Physics.PlayerHeight[p.Character][1];\n                    }\n                }\n            }\n\n            if(p.Mount == 0 && p.Character <= 2)\n            {\n                if(iRand(15) == 0)\n                {\n                    p.Mount = 3;\n                    p.MountType = iRand(7) + 1;\n                    p.Location.set_height_floor(Physics.PlayerHeight[2][2]);\n                    p.Location.Y -= 0.01_n;\n                }\n            }\n\n            p.CanFly = false;\n            p.CanFly2 = false;\n            p.RunCount = 0;\n\n            if(p.Mount == 0 && p.Character != 5)\n            {\n                numNPCs++;\n                p.HoldingNPC = numNPCs;\n                p.ForceHold = 120;\n\n                {\n                    NPC_t &n = NPC[numNPCs];\n                    do\n                    {\n                        do\n                        {\n                            n.Type = NPCID(iRand(286) + 1);\n                        } while(n.Type == 11 || n.Type == 16 || n.Type == 18 || n.Type == 15 ||\n                                n.Type == 21 || n.Type == 12 || n.Type == 13 || n.Type == 30 ||\n                                n.Type == 17 || n.Type == 31 || n.Type == 32 ||\n                                (n.Type >= 37 && n.Type <= 44) || n.Type == 46 || n.Type == 47 ||\n                                n.Type == 50 || (n.Type >= 56 && n.Type <= 70) || n.Type == 8 ||\n                                n.Type == 74 || n.Type == 51 || n.Type == 52 || n.Type == 75 ||\n                                n.Type == 34 || NPCIsToad(n) || NPCIsAnExit(n) ||\n                                NPCIsYoshi(n) || (n.Type >= 78 && n.Type <= 87) ||\n                                n.Type == 91 || n.Type == 93 || (n.Type >= 104 && n.Type <= 108) ||\n                                n.Type == 125 || n.Type == 133 || (n.Type >= 148 && n.Type <= 151) ||\n                                n.Type == 159 || n.Type == 160 || n.Type == 164 || n.Type == 168 ||\n                                // n.Type == 159 || n.Type == 160 || n.Type == 164 || // Duplicated segment [PVS Studio]\n                                (n.Type >= 154 && n.Type <= 157) ||\n                                n.Type == 165 || n.Type == 171 || n.Type == 178 ||\n                                n.Type == 197 || n.Type == 180 || n.Type == 181 || n.Type == 190 ||\n                                n.Type == 192 || n.Type == 196 ||\n                                // Duplicated segment, .....! [PVS Studio]\n                                // n.Type == 197 ||\n                                (UnderWater[0] && NPCIsBoot(n)) ||\n                                (n.Type >= 198 && n.Type <= 228) || n.Type == 234);\n\n                    } while(n.Type == 235 || n.Type == 231 || n.Type == 179 || n.Type == 49 ||\n                            n.Type == 237 || n.Type == 238 || n.Type == 239 || n.Type == 240 ||\n                            n.Type == 245 || n.Type == 246 || n.Type == 248 || n.Type == 254 ||\n                            n.Type == 255 || n.Type == 256 || n.Type == 257 || n.Type == 259 ||\n                            n.Type == 260 || n.Type == 262 || n.Type == 263 || n.Type == 265 ||\n                            n.Type == 266 || (n.Type >= 267 && n.Type <= 272) ||\n                            n.Type == 275 || n.Type == 276 ||\n                            (n.Type >= 280 && n.Type <= 284) || n.Type == 241);\n\n                    n.Active = true;\n                    n.HoldingPlayer = A;\n                    n.Location.Height = n->THeight;\n                    n.Location.Width = n->TWidth;\n                    n.Location.Y = Player[A].Location.Y;  // level[n.Section].Height + 1000\n                    n.Location.X = Player[A].Location.X; // level[n.Section].X + 1000\n                    n.TimeLeft = 100;\n                    n.Section = Player[A].Section;\n                    syncLayers_NPC(numNPCs);\n                }\n            }\n\n        }\n        else if(p.Location.X > level[p.Section].Width + 64)\n        {\n            p.Dead = true;\n        }\n\n        if(p.WetFrame)\n        {\n            if(p.Location.SpeedY == 0 || p.Slope > 0)\n                p.CanJump = true;\n            if(iRand(100) >= 98 || p.Location.SpeedY == 0 || p.Slope > 0)\n                p.Controls.Jump = true;\n        }\n\n        if(iRand(20) == 0 && Player[A].HoldingNPC == 0 && !Player[A].Slide && Player[A].CanAltJump && Player[A].Mount == 0)\n            Player[A].Controls.AltJump = true;\n\n        if(iRand(1000) == 0 && !Player[A].CanFly2)\n            Player[A].Controls.Run = false;\n\n        if(Player[A].Mount == 3 && iRand(50) == 0 && !Player[A].RunRelease)\n            Player[A].Controls.Run = false;\n\n        if(NPC[Player[A].HoldingNPC].Type == 22 || NPC[Player[A].HoldingNPC].Type == 49)\n            Player[A].Controls.Run = true;\n\n        if(Player[A].Slide && Player[A].CanJump)\n        {\n            if(Player[A].Location.SpeedX > -2 && Player[A].Location.SpeedX < 2)\n                Player[A].Controls.Jump = true;\n        }\n\n        if(!Player[A].CanFly && !Player[A].CanFly2 && (Player[A].State == 4 || Player[A].State == 5) && !Player[A].Slide)\n            Player[A].Controls.Jump = true;\n\n        if(Player[A].Quicksand > 0)\n        {\n            Player[A].CanJump = true;\n            Player[A].Controls.Jump = true;\n        }\n\n        if(Player[A].FloatTime > 0 || (Player[A].CanFloat && Player[A].FloatRelease && Player[A].Jump == 0 && Player[A].Location.SpeedY > 0 && iRand(20) == 0))\n            Player[A].Controls.Jump = true;\n\n        if(NPC[Player[A].HoldingNPC].Type == 13 && iRand(20) == 0)\n        {\n            Player[A].Controls.Run = false;\n            if(iRand(2) == 0)\n                Player[A].Controls.Up = true;\n            if(iRand(2) == 0)\n                Player[A].Controls.Right = false;\n        }\n\n        if(!Player[A].Slide && (Player[A].Slope > 0 || Player[A].StandingOnNPC > 0 || Player[A].Location.SpeedY == 0))\n        {\n            tempLocation = Player[A].Location;\n            tempLocation.Width = 95;\n            tempLocation.Height -= 1;\n\n            for(int B : treeBlockQuery(tempLocation, SORTMODE_NONE))\n            {\n                if(BlockSlope[Block[B].Type] == 0 && !BlockIsSizable[Block[B].Type] &&\n                   !BlockOnlyHitspot1[Block[B].Type] && !Block[B].Hidden)\n                {\n                    if(CheckCollision(Block[B].Location, tempLocation))\n                    {\n                        Player[A].CanJump = true;\n                        Player[A].SpinJump = false;\n                        Player[A].Controls.Jump = true;\n                        break;\n                    }\n                }\n            }\n        }\n\n        if(Player[A].Slope == 0 && !Player[A].Slide && Player[A].StandingOnNPC == 0 && (Player[A].Slope > 0 || Player[A].Location.SpeedY == 0))\n        {\n            tempBool = false;\n            tempLocation = Player[A].Location;\n            tempLocation.Width = 16;\n            tempLocation.Height = 16;\n            tempLocation.X = Player[A].Location.X + Player[A].Location.Width;\n            tempLocation.Y = Player[A].Location.Y + Player[A].Location.Height;\n\n            for(int B : treeBlockQuery(tempLocation, SORTMODE_NONE))\n            {\n                if((!BlockIsSizable[Block[B].Type] || Block[B].Location.Y > Player[A].Location.Y + Player[A].Location.Height - 1) &&\n                   !BlockOnlyHitspot1[Block[B].Type] && !Block[B].Hidden)\n                {\n                    if(CheckCollision(Block[B].Location, tempLocation))\n                    {\n                        tempBool = true;\n                        break;\n                    }\n                }\n            }\n\n            if(!tempBool)\n            {\n                Player[A].CanJump = true;\n                Player[A].SpinJump = false;\n                Player[A].Controls.Jump = true;\n            }\n        }\n\n        if(Player[A].Character == 5 && Player[A].Controls.Jump)\n        {\n            Player[A].Controls.AltJump = true;\n            // .Controls.Jump = False\n        }\n    }\n\n    if(LevelMacro > LEVELMACRO_OFF)\n        UpdateMacro();\n\n    ClearTriggeredEvents();\n    UpdateLayers();\n    UpdateNPCs();\n    UpdateBlocks();\n    UpdateEffects();\n    UpdatePlayer();\n    UpdateGraphics();\n    UpdateSound();\n    UpdateEvents();\n    updateScreenFaders();\n}\n\nvoid MenuLoop()\n{\n    lunaLoop();\n\n    // ConnectScreen and ControlsSettings screens handles its own input method polling\n    if(MenuMode != MENU_CHARACTER_SELECT_NEW && MenuMode != MENU_INPUT_SETTINGS && !g_pollingInput)\n        Controls::PollInputMethod();\n    Controls::Update();\n    if(!SharedCursor.Primary && !SharedCursor.Secondary)\n        MenuMouseRelease = true;\n    // replicates legacy behavior allowing clicks to be detected\n    if(SharedCursor.Primary || SharedCursor.Secondary || SharedCursor.Tertiary)\n        SharedCursor.Move = true;\n\n    if(mainMenuUpdate())\n        return;\n\n    Integrator::sync();\n\n    if(!g_gameInfo.introDeadMode)\n        updateIntroLevelActivity();\n    else\n    {\n        ClearTriggeredEvents();\n        UpdateLayers();\n        UpdateNPCs();\n        UpdateBlocks();\n        UpdateEffects();\n//        UpdatePlayer();\n        UpdateGraphics();\n        UpdateSound();\n        UpdateEvents();\n        updateScreenFaders();\n    }\n\n    if(SharedCursor.Primary)\n    {\n        const Location_t cursorLoc = newLoc(SharedCursor.X - vScreen[1].X - vScreen[1].TargetX(), SharedCursor.Y - vScreen[1].Y - vScreen[1].TargetY());\n        if(iRand(5) >= 2)\n        {\n            NewEffect(EFFID_SPARKLE, cursorLoc);\n            Effect[numEffects].Location.SpeedX = dRand() * 4 - 2;\n            Effect[numEffects].Location.SpeedY = dRand() * 4 - 2;\n        }\n\n        for(int A : treeNPCQuery(cursorLoc, SORTMODE_NONE))\n        {\n            if(NPC[A].Active)\n            {\n                if(CheckCollision(cursorLoc, NPC[A].Location))\n                {\n                    if(!NPC[A]->IsACoin)\n                    {\n                        NPC[0] = NPC[A];\n                        NPC[0].Location.X = SharedCursor.X - vScreen[1].X - vScreen[1].ScreenLeft;\n                        NPC[0].Location.Y = SharedCursor.Y - vScreen[1].Y - vScreen[1].ScreenTop;\n                        NPCHit(A, 3, 0);\n                    }\n                    else\n                    {\n                        NewEffect(EFFID_COIN_COLLECT, NPC[A].Location);\n                        NPC[A].Killed = 9;\n                        NPCQueues::Killed.push_back(A);\n                    }\n                }\n            }\n        }\n\n        for(int A : treeBlockQuery(cursorLoc, SORTMODE_COMPAT))\n        {\n            if(!Block[A].Hidden)\n            {\n                if(CheckCollision(cursorLoc, Block[A].Location))\n                {\n                    BlockHit(A);\n                    BlockHitHard(A);\n                }\n            }\n        }\n    }\n\n    if(SharedCursor.Primary || SharedCursor.Secondary)\n        MenuMouseRelease = false;\n\n#ifdef KEYBOARD_H\n    if(MenuMouseClick)\n        Controls::g_cancelDoubleClick = true;\n#endif\n\n    MenuMouseClick = false;\n}\n"
  },
  {
    "path": "src/main/menu_main.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_timer.h\"\n#include \"sdl_proxy/sdl_atomic.h\"\n\n#ifndef PGE_NO_THREADING\n#include <SDL2/SDL_thread.h>\n#endif\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n#include <md5/md5tools.hpp>\n#include \"main/client_methods.h\"\n#endif\n\n#include <fmt_format_ne.h>\n#include <array>\n#include <sorting/tinysort.h>\n\n#include <AppPath/app_path.h>\n#include <DirManager/dirman.h>\n#include <Utils/files.h>\n#include <Utils/files_ini.h>\n#include <Archives/archives.h>\n#include <PGE_File_Formats/file_formats.h>\n#include <Integrator/integrator.h>\n\n#include \"menu_main.h\"\n#include \"game_info.h\"\n#include \"../version.h\"\n#include \"../gfx.h\"\n#include \"main/screen_connect.h\"\n#include \"main/screen_options.h\"\n#include \"main/menu_controls.h\"\n#include \"main/translate_episode.h\"\n\n#include \"speedrunner.h\"\n#include \"main/gameplay_timer.h\"\n#include \"../game_main.h\"\n#include \"../sound.h\"\n#include \"../player.h\"\n#include \"../collision.h\"\n#include \"../graphics.h\"\n#include \"../core/render.h\"\n#include \"../core/window.h\"\n#include \"../core/events.h\"\n#include \"../controls.h\"\n#include \"../config.h\"\n#include \"level_file.h\"\n#include \"world_file.h\"\n#include \"pge_delay.h\"\n#include \"change_res.h\"\n#include \"game_globals.h\"\n#include \"core/language.h\"\n#include \"main/translate.h\"\n#include \"change_res.h\"\n#include \"game_globals.h\"\n#include \"npc_id.h\"\n#include \"fontman/font_manager.h\"\n#include \"custom.h\"\n\n#include \"editor.h\"\n#include \"frm_main.h\"\n#include \"load_gfx.h\"\n\n#include \"screen_textentry.h\"\n#include \"main/asset_pack.h\"\n#include \"main/screen_asset_pack.h\"\n#include \"main/screen_content.h\"\n#include \"editor/new_editor.h\"\n#include \"editor/write_level.h\"\n#include \"editor/write_world.h\"\n#include \"editor/editor_custom.h\"\n#include \"script/luna/luna.h\"\n\nMainMenuContent g_mainMenu;\n\n#ifndef PGE_NO_THREADING\nstatic bool                 s_atomicsInited = false;\nstatic SDL_atomic_t         loading = {};\nstatic SDL_atomic_t         loadingProgrss = {};\nstatic SDL_atomic_t         loadingProgrssMax = {};\n\nstatic SDL_Thread*          loadingThread = nullptr;\n#endif\n\nstatic constexpr int c_menuSavesLength = maxSaveSlots + 2;\nstatic constexpr int c_menuSavesFooterHint = (c_menuSavesLength * 30) - 30;\nstatic constexpr int c_menuItemSavesEndList = maxSaveSlots - 1;\nstatic constexpr int c_menuItemSavesCopy = maxSaveSlots;\nstatic constexpr int c_menuItemSavesDelete = maxSaveSlots + 1;\nstatic constexpr int c_menuSavesOffsetY = (maxSaveSlots - 3) * 30;\n\nstatic uint8_t s_episode_playstyle = 0;\nstatic uint8_t s_episode_speedrun_mode = 0;\n\nint NumSelectWorld = 0;\nint NumSelectBattle = 0;\nstd::vector<SelectWorld_t> SelectWorld;\nstd::vector<SelectWorld_t> SelectBattle;\n\n\nvoid initMainMenu()\n{\n#ifndef PGE_NO_THREADING\n    if(!s_atomicsInited)\n    {\n        SDL_AtomicSet(&loading, 0);\n        SDL_AtomicSet(&loadingProgrss, 0);\n        SDL_AtomicSet(&loadingProgrssMax, 0);\n        s_atomicsInited = true;\n    }\n#endif\n\n    g_mainMenu.introPressStart = \"Press Start\";\n\n    g_mainMenu.mainPlayEpisode = \"Play Episode\";\n    g_mainMenu.main1PlayerGame = \"1 Player Game\";\n    g_mainMenu.mainMultiplayerGame = \"2 Player Game\";\n    g_mainMenu.mainBattleGame = \"Battle Game\";\n    g_mainMenu.mainEditor = \"Editor\";\n    g_mainMenu.mainOptions = \"Options\";\n    g_mainMenu.mainExit = \"Exit\";\n\n    g_mainMenu.loading = \"Loading...\";\n\n    g_mainMenu.languageName = \"English\";\n    g_mainMenu.pluralRules = \"one-is-singular\";\n\n    g_mainMenu.selectCharacter = \"{0} game\";\n\n    g_mainMenu.editorBattles = \"<Battle Levels>\";\n    g_mainMenu.editorNewWorld = \"<New World>\";\n    g_mainMenu.editorMakeFor = \"Make for:\";\n    g_mainMenu.editorErrorMissingResources = \"Sorry! You are missing {0}, required for the in-game editor.\";\n    g_mainMenu.editorPromptNewWorldName = \"New world name\";\n\n    g_mainMenu.gameNoEpisodesToPlay = \"<No episodes to play>\";\n    g_mainMenu.gameNoBattleLevels = \"<No battle levels>\";\n    g_mainMenu.gameBattleRandom = \"Random Level\";\n\n    g_mainMenu.warnEpCompat = \"This episode was made for a different branch of SMBX and may not work properly.\";\n\n    g_mainMenu.gameSlotContinue = \"SLOT {0} ... {1}%\";\n    g_mainMenu.gameSlotNew = \"SLOT {0} ... NEW GAME\";\n    g_mainMenu.gameCopySave = \"Copy save\";\n    g_mainMenu.gameEraseSave = \"Erase save\";\n    g_mainMenu.gameSourceSlot = \"Select the source slot\";\n    g_mainMenu.gameTargetSlot = \"Now select the target\";\n    g_mainMenu.gameEraseSlot = \"Select the slot to erase\";\n\n    g_mainMenu.phraseScore = \"Score: {0}\";\n    g_mainMenu.phraseTime = \"Time: {0}\";\n\n    g_mainMenu.errorBattleNoLevels = \"Can't start battle because of no levels available\";\n\n    g_mainMenu.optionsRestartEngine = \"Restart engine for changes to take effect.\";\n\n    g_mainMenu.wordPlayer = \"Player\";\n    g_mainMenu.wordProfile = \"Profile\";\n    g_mainMenu.wordBack = \"Back\";\n    g_mainMenu.wordResume = \"Resume\";\n    g_mainMenu.wordWaiting = \"Waiting\";\n\n    g_mainMenu.controlsTitle = \"Controls\";\n    g_mainMenu.controlsConnected = \"Connected:\";\n    g_mainMenu.controlsDeleteKey = \"(Alt Run to Delete)\";\n    g_mainMenu.controlsDeviceTypes = \"Device Types\";\n    g_mainMenu.controlsInUse = \"(In Use)\";\n    g_mainMenu.controlsNotInUse = \"(Not In Use)\";\n\n    g_mainMenu.controlsActivateProfile = \"Activate profile\";\n    g_mainMenu.controlsRenameProfile = \"Rename profile\";\n    g_mainMenu.controlsDeleteProfile = \"Delete profile\";\n    g_mainMenu.controlsPlayerControls = \"Player controls\";\n    g_mainMenu.controlsCursorControls = \"Cursor controls\";\n    g_mainMenu.controlsEditorControls = \"Editor controls\";\n    g_mainMenu.controlsHotkeys = \"Hotkeys\";\n\n    g_mainMenu.controlsOptionRumble = \"Rumble\";\n    // g_mainMenu.controlsOptionGroundPoundButton = \"Ground Pound Button\";\n    g_mainMenu.controlsOptionBatteryStatus = \"Battery Status\";\n    g_mainMenu.controlsOptionAltMenuControls = \"Alt Menu Controls\";\n\n    g_mainMenu.wordProfiles = \"Profiles\";\n    g_mainMenu.wordButtons  = \"Buttons\";\n\n    g_mainMenu.controlsReallyDeleteProfile = \"Really delete profile?\";\n    g_mainMenu.controlsNewProfile = \"<New Profile>\";\n\n    g_mainMenu.wordNo   = \"No\";\n    g_mainMenu.wordYes  = \"Yes\";\n    g_mainMenu.wordOkay = \"Okay\";\n    g_mainMenu.caseNone = \"<None>\";\n    g_mainMenu.wordOn   = \"On\";\n    g_mainMenu.wordOff  = \"Off\";\n    g_mainMenu.wordShow = \"Show\";\n    g_mainMenu.wordHide = \"Hide\";\n    g_mainMenu.abbrevMilliseconds = \"MS\";\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // NetPlay\n    g_mainMenu.mainNetplay = \"NetPlay\";\n    g_mainMenu.netplayRoomKey = \"Room key:\";\n    g_mainMenu.netplayJoinRoom = \"Join Room\";\n    g_mainMenu.netplayCreateRoom = \"Create Room\";\n    g_mainMenu.netplayLeaveRoom = \"Leave Room\";\n    g_mainMenu.netplayServer = \"Server:\";\n    g_mainMenu.netplayNickname = \"Nickname:\";\n#endif\n\n    // g_mainMenu.promptDeprecatedSetting = \"This file uses a deprecated compatibility flag that will be removed in version 1.3.7.\\n\\nOld flag: \\\"{0}\\\"\\nNew flag: \\\"{1}\\\"\\n\\n\\nReplace it with the updated flag for version 1.3.6 and newer?\";\n    // g_mainMenu.promptDeprecatedSettingUnwritable = \"An unwritable file ({0}) uses a deprecated compatibility flag that will be removed in version 1.3.7.\\n\\nSection: [{1}]\\nOld flag: \\\"{2}\\\"\\nNew flag: \\\"{3}\\\"\\n\\n\\nPlease update it manually and copy to your device.\";\n}\n\n\nstatic int menuPlayersNum = 0;\nstatic int menuBattleMode = false;\n\nstatic int menuCopySaveSrc = 0;\nstatic int menuCopySaveDst = 0;\n\nstatic int s_startAssetPackTimer = 0;\n\nstatic const int TinyScreenH = 400;\nstatic const int SmallScreenH = 500;\nstatic const int TinyScreenW = 600;\n\nstatic bool s_prefer_modern_char_sel()\n{\n    return (g_config.compatibility_mode != Config_t::COMPAT_SMBX13) && (s_episode_speedrun_mode != 3);\n}\n\nstatic bool s_show_separate_2P()\n{\n    return g_config.compatibility_mode == Config_t::COMPAT_SMBX13 && !g_gameInfo.disableTwoPlayer;\n}\n\n#ifdef THEXTECH_ENABLE_SDL_NET\nstatic bool s_show_online()\n{\n    return !g_gameInfo.disableTwoPlayer;\n}\n\nstatic void s_StartEpisodeOnline()\n{\n    if(!XMessage::GetClientStatus())\n        return;\n\n    const auto& status = *XMessage::GetClientStatus();\n\n    l_screen = &Screens[status.client_index];\n    seedRandom(XMessage::g_session.random_seed);\n\n    selSave = 0;\n\n    if((int)Controls::g_InputMethods.size() > 1)\n        Controls::ClearInputMethods();\n\n    for(int A = 1; A <= numCharacters; A++)\n        blockCharacter[A] = (g_forceCharacter) ? false : SelectWorld[selWorld].blockChar[A];\n\n    for(int p = 0; p < maxLocalPlayers; p++)\n        l_screen->charSelect[p] = 0;\n\n    // find character for P1 (player on episode start)\n    Screens[0].charSelect[0] = 1;\n\n    for(int A = 1; A <= numCharacters; A++)\n    {\n        if(blockCharacter[A])\n            continue;\n\n        Screens[0].charSelect[0] = A;\n        break;\n    }\n\n    StartEpisode();\n}\n\nuint32_t s_assetPackHash()\n{\n    return 0;\n}\n\nuint32_t s_engineHash()\n{\n    return md5::string_to_u32(V_BUILD_VER);\n}\n#endif\n\n// export it, let's hope for some nice LTO here\nint mainMenuPlaystyle()\n{\n    return s_episode_playstyle;\n}\n\nstatic void s_change_save_item()\n{\n    if(MenuCursor < 0 || MenuCursor >= maxSaveSlots)\n        return;\n\n    int save_configs = SaveSlotInfo[MenuCursor + 1].ConfigDefaults;\n\n    if(save_configs > 0)\n    {\n        s_episode_playstyle = save_configs - 1;\n        s_episode_speedrun_mode = 0;\n    }\n    else if(save_configs < 0)\n    {\n        s_episode_speedrun_mode = -save_configs;\n        s_episode_playstyle = s_episode_speedrun_mode - 1;\n    }\n    else\n    {\n        s_episode_playstyle = SelectWorld[selWorld].bugfixes_on_by_default ? Config_t::MODE_MODERN : Config_t::MODE_CLASSIC;\n        s_episode_speedrun_mode = g_config.speedrun_mode.m_value;\n    }\n}\n\nvoid menu_draw_infobox_switch_arrows(int infobox_x, int infobox_y)\n{\n    // XRender::renderRect(infobox_x, infobox_y, 480, 68, {0, 0, 0, 192});\n\n    if(CommonFrame % 90 < 45)\n        return;\n\n    if(GFX.CharSelIcons.inited)\n    {\n        XRender::renderTextureFL(infobox_x + 8, infobox_y + 34 - 24 / 2, 24, 24, GFX.CharSelIcons, 72, 0, 0, nullptr, X_FLIP_HORIZONTAL);\n        XRender::renderTextureBasic(infobox_x + 480 - 8 - 24, infobox_y + 34 - 24 / 2, 24, 24, GFX.CharSelIcons, 72, 0);\n    }\n    else\n    {\n        XRender::renderTextureFL(infobox_x + 8, infobox_y + 34 - GFX.MCursor[1].w / 2, GFX.MCursor[1].w, GFX.MCursor[1].h, GFX.MCursor[1], 0, 0, -90);\n        XRender::renderTextureFL(infobox_x + 480 - 8 - GFX.MCursor[1].h, infobox_y + 34 - GFX.MCursor[2].w / 2, GFX.MCursor[2].w, GFX.MCursor[2].h, GFX.MCursor[2], 0, 0, -90);\n    }\n}\n\nvoid GetMenuPos(int* MenuX, int* MenuY)\n{\n    if(MenuX)\n        *MenuX = XRender::TargetW / 2 - 100;\n\n    if(MenuY)\n        *MenuY = XRender::TargetH - 250;\n\n    // tweaks for MenuX\n    if(MenuX && XRender::TargetW < TinyScreenW)\n    {\n        *MenuX = XRender::TargetW / 2 - 240;\n        if(*MenuX < 24 + XRender::TargetOverscanX)\n            *MenuX = 24 + XRender::TargetOverscanX;\n    }\n\n    // the rest is tweaks for MenuY\n    if(!MenuY)\n        return;\n\n    if(XRender::TargetH < TinyScreenH)\n        *MenuY = 100;\n    else if(XRender::TargetH < SmallScreenH)\n        *MenuY = XRender::TargetH - 220;\n\n    if(MenuMode >= MENU_SELECT_SLOT_BASE && MenuMode < MENU_SELECT_SLOT_END)\n        *MenuY -= c_menuSavesOffsetY;\n}\n\n#if !defined(PGE_NO_THREADING)\nstatic int FindWorldsThread(void *)\n{\n    FindWorlds();\n    return 0;\n}\n#endif\n\n#if (defined(__APPLE__) && defined(USE_BUNDLED_ASSETS)) || defined(FIXED_ASSETS_PATH) || defined(__EMSCRIPTEN__)\n#   define CAN_WRITE_APPPATH_WORLDS false\n#else\n#   define CAN_WRITE_APPPATH_WORLDS true\n#endif\n\nstruct WorldRoot_t\n{\n    std::string path;\n    bool editable;\n};\n\n// helper functions used by FindWorlds() and LoadSingleWorld()\nstatic void s_LoadSingleWorld(const std::string& epDir, const std::string& fName, WorldData& head, TranslateEpisode& tr, bool editable);\nstatic void s_LoadWorldArchive(const std::string& archive);\nstatic void s_FinishFindWorlds();\n\nvoid FindWorlds()\n{\n    TranslateEpisode tr;\n    WorldData head;\n    NumSelectWorld = 0;\n\n    std::vector<WorldRoot_t> worldRoots =\n    {\n        {AppPath + \"worlds/\", CAN_WRITE_APPPATH_WORLDS}\n    };\n\n    if(AppPathManager::userDirIsAvailable())\n    {\n        // assume that assets are not writable\n        worldRoots.push_back({AppPathManager::userWorldsRootDir(), true});\n        worldRoots[0].editable = false;\n    }\n\n#ifdef APP_PATH_HAS_EXTRA_WORLDS\n    // add worlds from additional romfs packages\n    for(const std::string& root : AppPathManager::worldRootDirs())\n        worldRoots.push_back({root, false});\n#endif\n\n    SelectWorld.clear();\n    SelectWorld.emplace_back(SelectWorld_t()); // Dummy entry\n\n#ifndef PGE_NO_THREADING\n    if(SDL_AtomicGet(&loading))\n    {\n        SDL_AtomicSet(&loadingProgrss, 0);\n        SDL_AtomicSet(&loadingProgrssMax, 0);\n\n        for(const auto &worldsRoot : worldRoots)\n        {\n            std::vector<std::string> dirs;\n            DirMan episodes(worldsRoot.path);\n            episodes.getListOfFolders(dirs);\n            SDL_AtomicAdd(&loadingProgrssMax, (int)dirs.size());\n            if(!Archives::has_prefix(worldsRoot.path))\n            {\n                episodes.getListOfFiles(dirs);\n                SDL_AtomicAdd(&loadingProgrssMax, (int)dirs.size());\n            }\n        }\n    }\n#endif\n\n    for(const auto &worldsRoot : worldRoots)\n    {\n        DirMan episodes(worldsRoot.path);\n\n        std::vector<std::string> dirs;\n        std::vector<std::string> files;\n        episodes.getListOfFolders(dirs);\n\n        for(const auto &dir : dirs)\n        {\n            std::string epDir = worldsRoot.path + dir + \"/\";\n            DirMan episode(epDir);\n            episode.getListOfFiles(files, {\".wld\", \".wldx\"});\n\n            for(std::string &fName : files)\n                s_LoadSingleWorld(epDir, fName, head, tr, worldsRoot.editable);\n\n#ifdef THEXTECH_PRELOAD_LEVELS\n            if(LoadingInProcess)\n                UpdateLoad();\n#endif\n\n#ifndef PGE_NO_THREADING\n            SDL_AtomicAdd(&loadingProgrss, 1);\n#endif\n        }\n\n        if(!Archives::has_prefix(worldsRoot.path))\n        {\n            episodes.getListOfFiles(dirs);\n\n            for(const auto &dir : dirs)\n            {\n                s_LoadWorldArchive(worldsRoot.path + dir);\n\n#ifdef THEXTECH_PRELOAD_LEVELS\n                if(LoadingInProcess)\n                    UpdateLoad();\n#endif\n\n#ifndef PGE_NO_THREADING\n                SDL_AtomicAdd(&loadingProgrss, 1);\n#endif\n            }\n        }\n    }\n\n    s_FinishFindWorlds();\n}\n\nstatic void s_LoadSingleWorld(const std::string& epDir, const std::string& fName, WorldData& head, TranslateEpisode& tr, bool editable)\n{\n    SelectWorld_t w;\n    w.WorldFilePath = epDir + fName;\n\n    PGE_FileFormats_misc::RWopsTextInput in(Files::open_file(w.WorldFilePath, \"r\"), w.WorldFilePath);\n\n    if(FileFormats::OpenWorldFileHeaderT(in, head))\n    {\n        w.WorldName = head.EpisodeTitle;\n        head.charactersToS64();\n        if(w.WorldName.empty())\n            w.WorldName = fName;\n\n        bool is_wldx = (head.meta.RecentFormat == WorldData::PGEX);\n        bool is_wld38a = (head.meta.RecentFormat == WorldData::SMBX38A);\n        if(is_wldx && (head.meta.configPackId == \"SMBX2\" || head.meta.engineFeatureLevel > g_gameInfo.contentFeatureLevel))\n            w.probably_incompatible = true;\n        else if(is_wld38a)\n            w.probably_incompatible = true;\n        else\n            w.bugfixes_on_by_default = is_wldx;\n\n        w.blockChar[1] = head.nocharacter1;\n        w.blockChar[2] = head.nocharacter2;\n        w.blockChar[3] = head.nocharacter3;\n        w.blockChar[4] = head.nocharacter4;\n        w.blockChar[5] = head.nocharacter5;\n\n        w.editable = editable;\n\n        if(tr.tryTranslateTitle(epDir, fName, w.WorldName))\n            pLogDebug(\"Translated world title: %s\", w.WorldName.c_str());\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n        if(w.WorldFilePath.size() > 2 && w.WorldFilePath[0] == ':' && w.WorldFilePath[1] == 'a')\n            w.lz4_content_hash = md5::string_to_u32(w.WorldFilePath);\n#endif\n\n        SelectWorld.push_back(std::move(w));\n    }\n}\n\nstatic void s_LoadWorldArchive(const std::string& archive)\n{\n    SelectWorld_t w;\n    w.WorldFilePath = \"@\";\n    w.WorldFilePath += archive;\n    w.WorldFilePath += \":/\";\n\n    IniProcessing ini = Files::load_ini(w.WorldFilePath + \"_meta.ini\");\n\n    ini.beginGroup(\"content\");\n\n    // confirm filename exists\n    ini.read(\"filename\", w.WorldName, w.WorldName);\n    if(w.WorldName.empty())\n    {\n        pLogInfo(\"Episode [%s] not loaded; could not parse _meta.ini\", archive.c_str());\n        return;\n    }\n    w.WorldFilePath += w.WorldName;\n\n    // confirm engine support\n    std::string engine;\n    ini.read(\"engine\", engine, engine);\n    if(engine != \"TheXTech\")\n    {\n        pLogInfo(\"Episode [%s] not loaded; for incompatible engine [%s]\", archive.c_str(), engine.c_str());\n        return;\n    }\n\n    // confirm platform support\n    std::string platform;\n    ini.read(\"platform\", platform, platform);\n\n    bool platform_okay = false;\n\n#ifndef __16M__\n    if(platform == \"main\")\n        platform_okay = true;\n#endif\n\n#ifdef __3DS__\n    if(platform == \"3ds\")\n        platform_okay = true;\n#endif\n\n#ifdef __WII__\n    if(platform == \"wii\")\n        platform_okay = true;\n#endif\n\n#ifdef __16M__\n    if(platform == \"dsi\")\n        platform_okay = true;\n#endif\n\n    if(!platform_okay)\n    {\n        pLogInfo(\"Episode [%s] not loaded; for incompatible platform [%s]\", archive.c_str(), platform.c_str());\n        return;\n    }\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n    ini.read(\"source-hash\", w.lz4_content_hash, 0);\n#endif\n\n    ini.endGroup();\n\n    // load properties\n    ini.beginGroup(\"properties\");\n\n    // playstyle classic\n    bool is_classic;\n    ini.read(\"is-classic\", is_classic, false);\n    w.bugfixes_on_by_default = !is_classic;\n\n    // character block\n    std::string char_block;\n    ini.read(\"char-block\", char_block, char_block);\n    char_block.resize(numCharacters, '0');\n\n    for(int i = 1; i <= numCharacters; i++)\n        w.blockChar[i] = (char_block[i - 1] == '1');\n\n    // title\n    ini.read(\"title\", w.WorldName, w.WorldName);\n\n    std::string title_key = \"title-\";\n    title_key += CurrentLanguage;\n    ini.read(title_key.c_str(), w.WorldName, w.WorldName);\n\n    title_key += '-';\n    title_key += CurrentLangDialect;\n    ini.read(title_key.c_str(), w.WorldName, w.WorldName);\n\n    ini.endGroup();\n\n    // last details\n    w.editable = false;\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n    if(!w.lz4_content_hash)\n    {\n        SDL_RWops* rw = Files::open_file(archive, \"rb\");\n        if(rw)\n        {\n            SDL_RWseek(rw, -4, RW_SEEK_END);\n            uint8_t hash[4];\n            if(SDL_RWread(rw, hash, 1, 4) == 4)\n            {\n                w.lz4_content_hash = (hash[0])\n                    | ((uint32_t)(hash[1]) <<  8)\n                    | ((uint32_t)(hash[2]) << 16)\n                    | ((uint32_t)(hash[3]) << 24);\n            }\n            SDL_RWclose(rw);\n        }\n    }\n#endif\n\n    // store it!\n    SelectWorld.push_back(std::move(w));\n}\n\nstatic void s_FinishFindWorlds()\n{\n    if(SelectWorld.size() <= 1) // No available worlds in the list\n    {\n        SelectWorld.clear();\n        SelectWorld.emplace_back(SelectWorld_t()); // Dummy entry\n        SelectWorld.emplace_back(SelectWorld_t()); // \"no episodes to play\" entry\n        SelectWorld[1].WorldName = g_mainMenu.gameNoEpisodesToPlay;\n        SelectWorld[1].disabled = true;\n    }\n\n    // Sort all worlds by alphabetical order\n    tinysort(SelectWorld.begin(), SelectWorld.end(),\n              [](const SelectWorld_t& a, const SelectWorld_t& b)\n    {\n        return a.WorldName < b.WorldName;\n    });\n\n    NumSelectWorld = (int)(SelectWorld.size() - 1);\n\n    SelectWorld_t battles = SelectWorld_t();\n    battles.WorldName = g_mainMenu.editorBattles;\n    battles.WorldFilePath = \"battle\";\n    battles.editable = true;\n    SelectWorld.push_back(std::move(battles));\n\n    SelectWorld_t createWorld = SelectWorld_t();\n    createWorld.WorldName = g_mainMenu.editorNewWorld;\n    createWorld.editable = true;\n    SelectWorld.push_back(std::move(createWorld));\n\n    ContentSelectScreen::Prepare();\n\n#ifndef PGE_NO_THREADING\n    SDL_AtomicSet(&loading, 0);\n#endif\n}\n\nvoid LoadSingleWorld(const std::string wPath)\n{\n    TranslateEpisode tr;\n    WorldData head;\n    NumSelectWorld = 0;\n\n    SelectWorld.clear();\n    SelectWorld.emplace_back(SelectWorld_t()); // Dummy entry\n\n    std::string fName = Files::basename(wPath);\n    std::string epDir = wPath.substr(0, wPath.size() - fName.size());\n\n    if(epDir.empty())\n        epDir = \"./\";\n\n    epDir = DirMan(epDir).absolutePath() + \"/\";\n\n    s_LoadSingleWorld(epDir, fName, head, tr, true);\n\n    s_FinishFindWorlds();\n}\n\n#if !defined(THEXTECH_PRELOAD_LEVELS) && !defined(PGE_NO_THREADING)\nstatic int FindLevelsThread(void *)\n{\n    FindLevels();\n    return 0;\n}\n#endif\n\nvoid FindLevels()\n{\n    std::vector<WorldRoot_t> battleRoots =\n    {\n        {AppPath + \"battle/\", CAN_WRITE_APPPATH_WORLDS}\n    };\n\n    if(AppPathManager::userDirIsAvailable())\n    {\n        // assume that assets are not writable\n        battleRoots.push_back({AppPathManager::userBattleRootDir(), true});\n        battleRoots[0].editable = false;\n    }\n\n    SelectBattle.clear();\n    SelectBattle.emplace_back(SelectWorld_t()); // Dummy entry\n\n    NumSelectBattle = 1;\n    SelectBattle.emplace_back(SelectWorld_t()); // \"random level\" entry\n    SelectBattle[1].WorldName = g_mainMenu.gameBattleRandom;\n    LevelData head;\n\n#ifndef PGE_NO_THREADING\n    if(SDL_AtomicGet(&loading))\n    {\n        SDL_AtomicSet(&loadingProgrss, 0);\n        SDL_AtomicSet(&loadingProgrssMax, 0);\n\n        for(const auto &battleRoot : battleRoots)\n        {\n            std::vector<std::string> files;\n            DirMan battleLvls(battleRoot.path);\n            battleLvls.getListOfFiles(files, {\".lvl\", \".lvlx\"});\n            SDL_AtomicAdd(&loadingProgrssMax, (int)files.size());\n        }\n    }\n#endif\n\n    for(const auto &battleRoot : battleRoots)\n    {\n        std::vector<std::string> files;\n        DirMan battleLvls(battleRoot.path);\n        battleLvls.getListOfFiles(files, {\".lvl\", \".lvlx\"});\n        for(std::string &fName : files)\n        {\n            std::string wPath = battleRoot.path + fName;\n\n            PGE_FileFormats_misc::RWopsTextInput in(Files::open_file(wPath, \"r\"), wPath);\n            if(FileFormats::OpenLevelFileHeaderT(in, head))\n            {\n                SelectWorld_t w;\n                w.WorldFilePath = battleRoot.path;\n                w.WorldFilePath += '/';\n                w.WorldFilePath += fName;\n                w.WorldName = head.LevelName;\n                if(w.WorldName.empty())\n                    w.WorldName = fName;\n                w.editable = battleRoot.editable;\n                SelectBattle.push_back(std::move(w));\n\n#ifdef THEXTECH_PRELOAD_LEVELS\n                if(LoadingInProcess)\n                    UpdateLoad();\n#endif\n            }\n#ifndef PGE_NO_THREADING\n            SDL_AtomicAdd(&loadingProgrss, 1);\n#endif\n        }\n    }\n\n    NumSelectBattle = ((int)SelectBattle.size() - 1);\n\n    if(SelectBattle.size() <= 2) // No available levels in the list\n    {\n        SelectBattle.clear();\n        SelectBattle.emplace_back(SelectWorld_t()); // Dummy entry\n\n        NumSelectBattle = 1;\n        SelectBattle.emplace_back(SelectWorld_t()); // \"no battle levels\" entry\n        SelectBattle[1].WorldName = g_mainMenu.gameNoBattleLevels;\n        SelectBattle[1].disabled = true;\n    }\n\n    ContentSelectScreen::Prepare();\n\n#ifndef PGE_NO_THREADING\n    SDL_AtomicSet(&loading, 0);\n#endif\n}\n\nstatic void s_PrepareContentSelect()\n{\n#ifdef THEXTECH_PRELOAD_LEVELS\n    ContentSelectScreen::Prepare();\n#elif defined(PGE_NO_THREADING)\n    if(MenuMode == MENU_BATTLE_MODE)\n        FindLevels();\n    else\n        FindWorlds();\n#else\n    SDL_AtomicSet(&loading, 1);\n    if(MenuMode == MENU_BATTLE_MODE)\n        loadingThread = SDL_CreateThread(FindLevelsThread, \"FindLevels\", nullptr);\n    else\n        loadingThread = SDL_CreateThread(FindWorldsThread, \"FindWorlds\", nullptr);\n    SDL_DetachThread(loadingThread);\n#endif\n}\n\n\nstatic void s_handleMouseMove(int items, int x, int y, int maxWidth, int itemHeight)\n{\n    For(A, 0, items)\n    {\n        if(SharedCursor.Y >= y + A * itemHeight && SharedCursor.Y <= y + 16 + A * itemHeight)\n        {\n            if(SharedCursor.X >= x && SharedCursor.X <= x + maxWidth)\n            {\n                if(MenuMouseRelease && SharedCursor.Primary)\n                    MenuMouseClick = true;\n                if(MenuCursor != A)\n                {\n                    PlaySoundMenu(SFX_Slide);\n                    MenuCursor = A;\n                    break;\n                }\n            }\n        }\n    }\n}\n\nstatic int s_quitKeyPos()\n{\n    int quitKeyPos = 2;\n\n    if(s_show_separate_2P())\n        quitKeyPos++;\n\n    if(!g_gameInfo.disableBattleMode)\n        quitKeyPos++;\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n    if(s_show_online())\n        quitKeyPos++;\n#endif\n\n    if(g_config.enable_editor)\n        quitKeyPos++;\n\n    return quitKeyPos;\n}\n\n\nstatic bool s_can_enter_ap_screen()\n{\n    return MenuMode == MENU_INTRO || (XRender::TargetH >= TinyScreenH && MenuMode == MENU_MAIN);\n}\n\n\n\nbool mainMenuUpdate()\n{\n    int MenuX, MenuY;\n    GetMenuPos(&MenuX, &MenuY);\n\n    // Location_t tempLocation;\n    int menuLen;\n    // Player_t blankPlayer;\n\n    MenuControls_t menuControls = Controls::GetMenuControls();\n\n    menuControls.Back |= (SharedCursor.Secondary && MenuMouseRelease);\n\n    if(menuControls.Back && menuControls.Do)\n        menuControls.Do = false;\n\n    bool should_enter_ap_screen = menuControls.Home && s_can_enter_ap_screen() && MenuCursorCanMove;\n\n    if(should_enter_ap_screen && GetAssetPacks().size() <= 1)\n    {\n        PlaySoundMenu(SFX_BlockHit);\n        MenuCursorCanMove = false;\n    }\n\n    if(should_enter_ap_screen && GetAssetPacks().size() > 1)\n    {\n        s_startAssetPackTimer++;\n        if(s_startAssetPackTimer == 64)\n        {\n            PlaySoundMenu(SFX_Do);\n            FadeOutMusic(500);\n            ScreenAssetPack::g_LoopActive = true;\n            GameMenu = false;\n            return true;\n        }\n    }\n    else if(s_can_enter_ap_screen() && s_startAssetPackTimer > 0)\n        s_startAssetPackTimer -= 2;\n    else\n        s_startAssetPackTimer = 0;\n\n    {\n        {\n            bool k = false;\n            k |= menuControls.Back;\n            k |= menuControls.Do;\n            k |= menuControls.Up;\n            k |= menuControls.Down;\n            k |= menuControls.Left;\n            k |= menuControls.Right;\n            k |= menuControls.Home;\n            k |= menuControls.Erase;\n\n            if(!k)\n                MenuCursorCanMove = true;\n        }\n\n        bool in_content_select = (MenuMode == MENU_1PLAYER_GAME || MenuMode == MENU_2PLAYER_GAME || MenuMode == MENU_BATTLE_MODE || MenuMode == MENU_EDITOR);\n#ifdef THEXTECH_ENABLE_SDL_NET\n        if(MenuMode == MENU_NETPLAY_WORLD_SELECT)\n            in_content_select = true;\n#endif\n\n        if(!g_pollingInput && (MenuMode != MENU_CHARACTER_SELECT_NEW && MenuMode != MENU_CHARACTER_SELECT_NEW_BM && MenuMode != MENU_NEW_OPTIONS && MenuMode != MENU_INTRO && !in_content_select))\n        {\n            int cursorDelta = 0;\n\n            if(menuControls.Up)\n            {\n                if(MenuCursorCanMove)\n                {\n                    MenuCursor -= 1;\n                    cursorDelta = -1;\n                }\n\n                MenuCursorCanMove = false;\n            }\n            else if(menuControls.Down)\n            {\n                if(MenuCursorCanMove)\n                {\n                    MenuCursor += 1;\n                    cursorDelta = +1;\n                }\n\n                MenuCursorCanMove = false;\n            }\n\n            if(cursorDelta != 0)\n            {\n                if(MenuMode >= MENU_SELECT_SLOT_BASE && MenuMode < MENU_SELECT_SLOT_END)\n                    s_change_save_item();\n\n                PlaySoundMenu(SFX_Slide);\n            }\n\n        } // No keyboard/Joystick grabbing active\n\n        if(MenuMode == MENU_INTRO && XRender::TargetH >= TinyScreenH)\n            MenuMode = MENU_MAIN;\n\n        if(false\n#ifndef PGE_NO_THREADING\n            || SDL_AtomicGet(&loading)\n#endif\n#ifdef THEXTECH_ENABLE_SDL_NET\n            || XMessage::GetClientStatus() == nullptr\n#endif\n        )\n        {\n            if((menuControls.Do && MenuCursorCanMove) || MenuMouseClick)\n                PlaySoundMenu(SFX_BlockHit);\n\n            if(MenuCursor != 0)\n                MenuCursor = 0;\n        }\n#ifdef THEXTECH_ENABLE_SDL_NET\n        else if(XMessage::CompleteRequest())\n        {\n            auto* status = XMessage::GetClientStatus();\n            if(status && (status->client_state == XMessage::CLIENT_GUEST || status->client_state == XMessage::CLIENT_HOST))\n            {\n                s_StartEpisodeOnline();\n                return true;\n            }\n            else if(status && status->client_state == XMessage::CLIENT_LOBBY && XMessage::GetRoomInfo() && XMessage::GetRoomInfo()->room_key)\n            {\n                const XMessage::RoomInfo& room_info = *XMessage::GetRoomInfo();\n\n                selWorld = -1;\n\n                for(size_t i = 0; i < SelectWorld.size(); i++)\n                {\n                    if(SelectWorld[i].lz4_content_hash == room_info.content_hash)\n                    {\n                        selWorld = i;\n                        break;\n                    }\n                }\n\n                if(room_info.room_key == 0 || room_info.engine_hash != s_engineHash() || room_info.asset_hash != s_assetPackHash() || room_info.content_hash == 0 || selWorld == -1)\n                    PlaySoundMenu(SFX_BlockHit);\n                else\n                {\n                    PlaySoundMenu(SFX_Do);\n                    XMessage::JoinRoom(room_info.room_key);\n                }\n            }\n            // ... some sort of switch for responses to queries\n        }\n#endif // #ifdef THEXTECH_ENABLE_SDL_NET\n        // Menu Intro\n        else if(MenuMode == MENU_INTRO)\n        {\n            if(MenuMouseRelease && SharedCursor.Primary)\n                MenuMouseClick = true;\n\n            if(menuControls.Back && MenuCursorCanMove)\n            {\n                int quitKeyPos = s_quitKeyPos();\n\n                MenuMode = MENU_MAIN;\n                MenuCursor = quitKeyPos;\n                MenuCursorCanMove = false;\n                PlaySoundMenu(SFX_Slide);\n            }\n\n            if((menuControls.Do && MenuCursorCanMove) || MenuMouseClick)\n            {\n                MenuCursorCanMove = false;\n                MenuMode = MENU_MAIN;\n                MenuCursor = 0;\n                PlaySoundMenu(SFX_Do);\n            }\n        }\n        // Main Menu\n        else if(MenuMode == MENU_MAIN)\n        {\n            if(SharedCursor.Move)\n            {\n                For(A, 0, 10)\n                {\n                    if(SharedCursor.Y >= MenuY + A * 30 && SharedCursor.Y <= MenuY + A * 30 + 16)\n                    {\n                        int i = 0;\n                        if(A == i++)\n                            menuLen = 18 * (s_show_separate_2P() ? (int)g_mainMenu.main1PlayerGame.size() : (int)g_mainMenu.mainPlayEpisode.size()) - 2;\n                        else if(s_show_separate_2P() && A == i++)\n                            menuLen = 18 * (int)g_mainMenu.mainMultiplayerGame.size() - 2;\n                        else if(!g_gameInfo.disableBattleMode && A == i++)\n                            menuLen = 18 * (int)g_mainMenu.mainBattleGame.size();\n#ifdef THEXTECH_ENABLE_SDL_NET\n                        else if(s_show_online() && A == i++)\n                            menuLen = 18 * (int)g_mainMenu.mainNetplay.size();\n#endif\n                        else if(g_config.enable_editor && A == i++)\n                            menuLen = 18 * (int)g_mainMenu.mainEditor.size();\n                        else if(A == i++)\n                            menuLen = 18 * (int)g_mainMenu.mainOptions.size();\n                        else if(A == i++)\n                            menuLen = 18 * (int)g_mainMenu.mainExit.size();\n                        else\n                            break;\n\n                        if(SharedCursor.X >= MenuX && SharedCursor.X <= MenuX + menuLen)\n                        {\n                            if(MenuMouseRelease && SharedCursor.Primary)\n                                MenuMouseClick = true;\n\n                            if(MenuCursor != A)\n                            {\n                                PlaySoundMenu(SFX_Slide);\n                                MenuCursor = A;\n                            }\n                        }\n                    }\n                }\n            }\n\n            if(menuControls.Back && MenuCursorCanMove)\n            {\n                int quitKeyPos = s_quitKeyPos();\n\n                if(XRender::TargetH < TinyScreenH)\n                {\n                    MenuCursorCanMove = false;\n                    MenuMode = MENU_INTRO;\n                    PlaySoundMenu(SFX_Slide);\n                }\n                else if(MenuCursor != quitKeyPos)\n                {\n                    MenuCursor = quitKeyPos;\n                    MenuCursorCanMove = false;\n                    PlaySoundMenu(SFX_Slide);\n                }\n            }\n            else if((menuControls.Do && MenuCursorCanMove) || MenuMouseClick)\n            {\n                MenuCursorCanMove = false;\n                // PlayerCharacter = 0;\n                // PlayerCharacter2 = 0;\n\n                int i = 0;\n                if(MenuCursor == i++)\n                {\n                    PlaySoundMenu(SFX_Do);\n                    MenuMode = MENU_1PLAYER_GAME;\n                    menuPlayersNum = 1;\n                    menuBattleMode = false;\n                    MenuCursor = 0;\n                    s_PrepareContentSelect();\n                }\n                else if(s_show_separate_2P() && MenuCursor == i++)\n                {\n                    PlaySoundMenu(SFX_Do);\n                    MenuMode = MENU_2PLAYER_GAME;\n                    menuPlayersNum = 2;\n                    menuBattleMode = false;\n                    MenuCursor = 0;\n\n                    s_PrepareContentSelect();\n                }\n                else if(!g_gameInfo.disableBattleMode && MenuCursor == i++)\n                {\n                    PlaySoundMenu(SFX_Do);\n                    MenuMode = MENU_BATTLE_MODE;\n                    menuPlayersNum = 2;\n                    menuBattleMode = true;\n                    MenuCursor = 0;\n\n                    s_PrepareContentSelect();\n                }\n#ifdef THEXTECH_ENABLE_SDL_NET\n                else if(s_show_online() && MenuCursor == i++)\n                {\n                    PlaySoundMenu(SFX_Do);\n                    MenuMode = MENU_NETPLAY;\n                    MenuCursor = 0;\n\n                    s_PrepareContentSelect();\n\n                    XMessage::Connect();\n                }\n#endif\n                else if(g_config.enable_editor && MenuCursor == i++)\n                {\n                    if(!GFX.EIcons.inited)\n                    {\n                        PlaySoundMenu(SFX_BlockHit);\n                        g_MessageType = MESSAGE_TYPE_SYS_WARNING;\n                        MessageText = fmt::format_ne(g_mainMenu.editorErrorMissingResources, \"EditorIcons.png\");\n                        PauseGame(PauseCode::Message);\n                    }\n                    else if(!EditorCustom::loaded)\n                    {\n                        PlaySoundMenu(SFX_BlockHit);\n                        g_MessageType = MESSAGE_TYPE_SYS_WARNING;\n                        MessageText = fmt::format_ne(g_mainMenu.editorErrorMissingResources, \"editor.ini\");\n                        PauseGame(PauseCode::Message);\n                    }\n                    else\n                    {\n                        PlaySoundMenu(SFX_Do);\n                        MenuMode = MENU_EDITOR;\n                        MenuCursor = 0;\n\n                        s_PrepareContentSelect();\n                    }\n                }\n                else if(MenuCursor == i++)\n                {\n                    PlaySoundMenu(SFX_Do);\n                    MenuMode = MENU_NEW_OPTIONS;\n                    OptionsScreen::Init();\n                }\n                else if(MenuCursor == i++)\n                {\n                    PlaySoundMenu(SFX_Do);\n                    GracefulQuit(true);\n                    return true;\n                }\n\n            }\n\n\n            if(MenuMode == MENU_MAIN)\n            {\n                int quitKeyPos = s_quitKeyPos();\n\n                if(MenuCursor > quitKeyPos)\n                    MenuCursor = 0;\n\n                if(MenuCursor < 0)\n                    MenuCursor = quitKeyPos;\n            }\n        } // Main Menu\n\n        // Character Select\n        else if(MenuMode == MENU_CHARACTER_SELECT_NEW ||\n                MenuMode == MENU_CHARACTER_SELECT_NEW_BM)\n        {\n            int ret = ConnectScreen::Logic();\n            if(ret == -1)\n            {\n                // disconnect input methods for convenience\n                Controls::ClearInputMethods();\n\n                if(MenuMode == MENU_CHARACTER_SELECT_NEW_BM)\n                {\n                    MenuCursor = selWorld - 1;\n                    MenuMode = MENU_BATTLE_MODE;\n                }\n                else\n                {\n                    MenuCursor = selSave - 1;\n                    if(menuPlayersNum == 1)\n                        MenuMode = MENU_SELECT_SLOT_1P;\n                    else\n                        MenuMode = MENU_SELECT_SLOT_2P;\n                }\n\n                MenuCursorCanMove = false;\n            }\n            else if(ret == 1)\n            {\n                if(MenuMode == MENU_CHARACTER_SELECT_NEW)\n                {\n                    // writing to m_value to avoid extra UpdateConfig hook\n                    g_config.playstyle.m_value = s_episode_playstyle;\n                    g_config.playstyle.m_set = ConfigSetLevel::ep_config;\n\n                    if(g_config.speedrun_mode.m_set != ConfigSetLevel::cmdline)\n                    {\n                        g_config.speedrun_mode.m_value = s_episode_speedrun_mode;\n                        g_config.speedrun_mode.m_set = ConfigSetLevel::ep_config;\n                    }\n\n                    MenuCursor = 0;\n                    StartEpisode();\n                    return true;\n                }\n                else if(MenuMode == MENU_CHARACTER_SELECT_NEW_BM)\n                {\n                    MenuCursor = 0;\n                    StartBattleMode();\n                    return true;\n                }\n            }\n        }\n#ifdef THEXTECH_ENABLE_SDL_NET\n        else if(MenuMode == MENU_NETPLAY)\n        {\n            if(SharedCursor.Move)\n            {\n                For(A, 0, 10)\n                {\n                    if(SharedCursor.Y >= MenuY + A * 30 && SharedCursor.Y <= MenuY + A * 30 + 16)\n                    {\n                        int i = 0;\n                        if(A == i++)\n                            menuLen = 18 * (int)g_mainMenu.netplayJoinRoom.size();\n                        else if(A == i++)\n                            menuLen = 18 * (int)g_mainMenu.netplayCreateRoom.size();\n                        else if(A == i++)\n                            menuLen = 18 * (int)g_mainMenu.netplayServer.size();\n                        else if(A == i++)\n                            menuLen = 18 * (int)g_mainMenu.netplayNickname.size();\n                        else\n                            break;\n\n                        if(SharedCursor.X >= MenuX && SharedCursor.X <= MenuX + menuLen)\n                        {\n                            if(MenuMouseRelease && SharedCursor.Primary)\n                                MenuMouseClick = true;\n\n                            if(MenuCursor != A)\n                            {\n                                PlaySoundMenu(SFX_Slide);\n                                MenuCursor = A;\n                            }\n                        }\n                    }\n                }\n            }\n\n            if(menuControls.Back && MenuCursorCanMove)\n            {\n                int netplayPos = 1;\n                if(s_show_separate_2P())\n                    netplayPos++;\n                if(!g_gameInfo.disableBattleMode)\n                    netplayPos++;\n\n                XMessage::Disconnect();\n\n                MenuMode = MENU_MAIN;\n                MenuCursor = netplayPos;\n                MenuCursorCanMove = false;\n                PlaySoundMenu(SFX_Slide);\n            }\n            else if((menuControls.Do && MenuCursorCanMove) || MenuMouseClick)\n            {\n                MenuCursorCanMove = false;\n\n                if((MenuCursor == 0 || MenuCursor == 1) && (!XMessage::GetClientStatus() || XMessage::GetClientStatus()->client_state != XMessage::CLIENT_LOBBY))\n                    PlaySoundMenu(SFX_BlockHit);\n                else if(MenuCursor == 0)\n                {\n                    PlaySoundMenu(SFX_Do);\n\n                    uint32_t room_key = XMessage::RoomFromString(TextEntryScreen::Run(g_mainMenu.netplayRoomKey));\n\n                    if(room_key && XMessage::RequestFillRoomInfo(room_key))\n                    {\n                    }\n                    else\n                        PlaySoundMenu(SFX_BlockHit);\n                }\n                else if(MenuCursor == 1)\n                {\n                    PlaySoundMenu(SFX_Do);\n                    MenuMode = MENU_NETPLAY_WORLD_SELECT;\n                    menuPlayersNum = 1;\n                    menuBattleMode = false;\n                    MenuCursor = 0;\n                }\n                else if(MenuCursor == 2)\n                {\n                    PlaySoundMenu(SFX_Do);\n                    g_netplayServer = TextEntryScreen::Run(g_mainMenu.netplayServer);\n                    XMessage::Connect();\n                }\n                else if(MenuCursor == 3)\n                {\n                    PlaySoundMenu(SFX_Do);\n                    g_netplayNickname = TextEntryScreen::Run(g_mainMenu.netplayNickname);\n                }\n            }\n\n            if(MenuMode == MENU_NETPLAY)\n            {\n                if(MenuCursor > 3)\n                    MenuCursor = 0;\n                else if(MenuCursor < 0)\n                    MenuCursor = 3;\n            }\n        }\n#endif\n\n        // World Select\n        else if(MenuMode == MENU_1PLAYER_GAME || MenuMode == MENU_2PLAYER_GAME\n#ifdef THEXTECH_ENABLE_SDL_NET\n            || MenuMode == MENU_NETPLAY_WORLD_SELECT\n#endif\n            || MenuMode == MENU_BATTLE_MODE || MenuMode == MENU_EDITOR)\n        {\n            int ret = ContentSelectScreen::Logic();\n\n            if(ret == -1)\n            {\n                MenuCursor = MenuMode - 1;\n\n                if(MenuMode == MENU_BATTLE_MODE)\n                {\n                    MenuCursor = !s_show_separate_2P() ? 1 : 2;\n                }\n                else if(MenuMode == MENU_EDITOR)\n                {\n                    MenuCursor = 3;\n                    if(!s_show_separate_2P())\n                        MenuCursor--;\n                    if(g_gameInfo.disableBattleMode)\n                        MenuCursor--;\n#ifdef THEXTECH_ENABLE_SDL_NET\n                    if(s_show_online())\n                        MenuCursor++;\n#endif\n                }\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n                if(MenuMode == MENU_NETPLAY_WORLD_SELECT)\n                {\n                    MenuMode = MENU_NETPLAY;\n                    MenuCursor = 1;\n                }\n                else\n#endif\n                {\n                    MenuMode = MENU_MAIN;\n                }\n//'world select back\n            }\n            else if(ret == 1)\n            {\n                if(MenuMode == MENU_EDITOR)\n                {\n                    if(selWorld == (int)SelectWorld.size() - 1)\n                    {\n                        ClearWorld(true);\n                        WorldName = TextEntryScreen::Run(g_mainMenu.editorPromptNewWorldName);\n                        if(!WorldName.empty())\n                        {\n                            std::string fn = WorldName;\n                            // eliminate bad characters\n                            std::replace(fn.begin(), fn.end(), '/', '_');\n                            std::replace(fn.begin(), fn.end(), '\\\\', '_');\n                            std::replace(fn.begin(), fn.end(), '.', '_');\n                            std::replace(fn.begin(), fn.end(), ':', '_');\n                            std::replace(fn.begin(), fn.end(), '<', '_');\n                            std::replace(fn.begin(), fn.end(), '>', '_');\n                            std::replace(fn.begin(), fn.end(), '\"', '_');\n                            std::replace(fn.begin(), fn.end(), '|', '_');\n                            std::replace(fn.begin(), fn.end(), '?', '_');\n                            std::replace(fn.begin(), fn.end(), '*', '_');\n                            // ensure uniqueness (but still case-sensitive for now)\n                            while(DirMan::exists(AppPathManager::userWorldsRootDir() + fn))\n                                fn += \"2\";\n                            DirMan::mkAbsPath(AppPathManager::userWorldsRootDir() + fn);\n\n                            std::string wPath = AppPathManager::userWorldsRootDir() + fn + \"/world.wld\";\n                            if(ContentSelectScreen::editor_target_thextech)\n                                wPath += \"x\";\n                            g_recentWorldEditor = wPath;\n\n                            SaveWorld(wPath, (ContentSelectScreen::editor_target_thextech) ? FileFormats::WLD_PGEX : FileFormats::WLD_SMBX64);\n\n#ifdef PGE_NO_THREADING\n                            FindWorlds();\n#else\n                            SDL_AtomicSet(&loading, 1);\n                            loadingThread = SDL_CreateThread(FindWorldsThread, \"FindWorlds\", NULL);\n                            SDL_DetachThread(loadingThread);\n#endif\n                        }\n                    }\n                    else if(!g_gameInfo.disableBattleMode && selWorld == (int)SelectWorld.size() - 2)\n                    {\n                        ClearWorld(true);\n                        GameMenu = false;\n                        LevelSelect = false;\n                        BattleMode = true;\n                        LevelEditor = true;\n                        WorldEditor = false;\n                        ClearLevel();\n                        ClearGame();\n                        SetupPlayers();\n\n                        std::string lPath = AppPathManager::userBattleRootDir();\n                        {\n                            // find a single battle level to open first\n                            std::vector<std::string> files;\n                            DirMan battleLvls(lPath);\n                            battleLvls.getListOfFiles(files, {(ContentSelectScreen::editor_target_thextech) ? \".lvlx\" : \".lvl\"});\n                            if(files.empty())\n                                lPath += \"newbattle.lvl\";\n                            else\n                                lPath += files[0];\n                        }\n\n                        if(!Files::fileExists(lPath))\n                        {\n                            if(ContentSelectScreen::editor_target_thextech)\n                                lPath += \"x\";\n\n                            SaveLevel(lPath, (ContentSelectScreen::editor_target_thextech) ? FileFormats::LVL_PGEX : FileFormats::LVL_SMBX64);\n                        }\n\n                        OpenLevel(lPath);\n                        EditorBackup(); // EditorRestore() gets called when not in world editor\n\n                        // todo: de-dupe with below\n                        const std::string& wPath = SelectWorld[selWorld].WorldFilePath;\n                        if(g_recentWorldEditor != wPath)\n                        {\n                            g_recentWorldEditor = wPath;\n                            SaveConfig();\n                        }\n\n                        Integrator::setEditorFile(FileName);\n                        editorScreen.ResetCursor();\n                        editorScreen.active = false;\n                        MouseRelease = false;\n                        return true;\n                    }\n                    else\n                    {\n                        GameMenu = false;\n                        LevelSelect = false;\n                        BattleMode = false;\n                        LevelEditor = true;\n                        WorldEditor = true;\n                        ClearLevel();\n                        ClearGame();\n                        SetupPlayers();\n                        const std::string& wPath = SelectWorld[selWorld].WorldFilePath;\n                        OpenWorld(wPath);\n                        if(g_recentWorldEditor != wPath)\n                        {\n                            g_recentWorldEditor = wPath;\n                            SaveConfig();\n                        }\n                        Integrator::setEditorFile(FileName);\n                        editorScreen.ResetCursor();\n                        editorScreen.active = false;\n                        MouseRelease = false;\n                        return true;\n                    }\n                }\n                // battle mode\n                else if(MenuMode == MENU_BATTLE_MODE)\n                {\n                    MenuMode = MENU_CHARACTER_SELECT_NEW_BM;\n                    ConnectScreen::MainMenu_Start(2);\n                }\n#ifdef THEXTECH_ENABLE_SDL_NET\n                // new room\n                else if(MenuMode == MENU_NETPLAY_WORLD_SELECT)\n                {\n                    XMessage::RoomInfo room_info = XMessage::RoomInfo();\n                    room_info.engine_hash = s_engineHash();\n                    room_info.asset_hash = s_assetPackHash();\n                    room_info.content_hash = SelectWorld[selWorld].lz4_content_hash;\n                    XMessage::JoinNewRoom(room_info);\n                }\n#endif\n                // enter save select\n                else\n                {\n                    LoadCustomPlayerPreviews(Files::dirname(SelectWorld[selWorld].WorldFilePath).c_str());\n\n                    FindSaves();\n                    MenuMode *= MENU_SELECT_SLOT_BASE;\n                    MenuCursor = 0;\n\n                    s_change_save_item();\n                }\n\n                MenuCursorCanMove = false;\n            }\n        } // World select\n\n        // Save Select\n        else if(MenuMode == MENU_SELECT_SLOT_1P || MenuMode == MENU_SELECT_SLOT_2P)\n        {\n            if(SharedCursor.Move)\n            {\n                int old_item = MenuCursor;\n                s_handleMouseMove(c_menuItemSavesDelete, MenuX, MenuY, 300, 30);\n\n                if(MenuCursor != old_item)\n                    s_change_save_item();\n            }\n\n            // new mode selection logic\n            if(MenuCursor >= 0 && MenuCursor < maxSaveSlots && SaveSlotInfo[MenuCursor + 1].ConfigDefaults == 0)\n            {\n                // switch mode\n                if(MenuCursorCanMove && (menuControls.Left || menuControls.Right) && s_episode_speedrun_mode == 0)\n                {\n                    if(s_episode_playstyle == Config_t::MODE_MODERN)\n                        s_episode_playstyle = Config_t::MODE_CLASSIC;\n                    else\n                        s_episode_playstyle = Config_t::MODE_MODERN;\n\n                    PlaySoundMenu(SFX_Climbing);\n                    MenuCursorCanMove = false;\n                }\n\n                // only enter the menu if allowed by the global speedrun mode\n                if(MenuCursorCanMove && menuControls.Home && g_config.speedrun_mode.m_set == ConfigSetLevel::cmdline)\n                {\n                    PlaySoundMenu(SFX_BlockHit);\n                }\n                // enter vanilla mode if an existing save slot\n                else if(MenuCursorCanMove && menuControls.Home && SaveSlotInfo[MenuCursor + 1].Progress >= 0)\n                {\n                    int target_bugfixes = (SelectWorld[selWorld].bugfixes_on_by_default) ? Config_t::MODE_MODERN : Config_t::MODE_CLASSIC;\n\n                    if(s_episode_playstyle == Config_t::MODE_VANILLA)\n                        s_episode_playstyle = target_bugfixes;\n                    else\n                        s_episode_playstyle = Config_t::MODE_VANILLA;\n\n                    PlaySoundMenu(SFX_Climbing);\n                    MenuCursorCanMove = false;\n                }\n                // go to speedrun menu otherwise\n                else if(MenuCursorCanMove && menuControls.Home)\n                {\n                    PlaySoundMenu(SFX_PlayerGrow);\n                    selSave = MenuCursor + 1;\n\n                    if(s_episode_speedrun_mode == 0)\n                        MenuCursor = s_episode_playstyle;\n                    else\n                        MenuCursor = s_episode_speedrun_mode + 2;\n\n                    MenuMode += MENU_SELECT_SLOT_ADVMODE_ADD;\n                    MenuCursorCanMove = false;\n                    MenuMouseClick = false;\n                }\n            }\n            else if(MenuCursor >= 0 && MenuCursor < maxSaveSlots)\n            {\n                int save_configs = SaveSlotInfo[MenuCursor + 1].ConfigDefaults;\n\n                if(save_configs > 0)\n                {\n                    s_episode_playstyle = save_configs - 1;\n                    s_episode_speedrun_mode = 0;\n                }\n                else if(save_configs < 0)\n                {\n                    s_episode_speedrun_mode = -save_configs;\n                    s_episode_playstyle = s_episode_speedrun_mode - 1;\n                }\n            }\n\n            if(MenuCursorCanMove || MenuMouseClick)\n            {\n                if(menuControls.Back)\n                {\n//'save select back\n                    UnloadCustomPlayerPreviews();\n\n                    MenuMode /= MENU_SELECT_SLOT_BASE;\n\n                    MenuCursorCanMove = false;\n                    PlaySoundMenu(SFX_Slide);\n                }\n                else if(menuControls.Do || MenuMouseClick)\n                {\n                    if(MenuCursor == c_menuItemSavesCopy) // Copy the gamesave\n                    {\n                        PlaySoundMenu(SFX_Do);\n                        MenuCursor = 0;\n                        MenuMode += MENU_SELECT_SLOT_COPY_S1_ADD;\n                        MenuCursorCanMove = false;\n                    }\n                    else if(MenuCursor == c_menuItemSavesDelete) // Delete the gamesave\n                    {\n                        PlaySoundMenu(SFX_Do);\n                        MenuCursor = 0;\n                        MenuMode += MENU_SELECT_SLOT_DELETE_ADD;\n                        MenuCursorCanMove = false;\n                    }\n                    // block invalid speedrun continuation\n                    else if(MenuCursor >= 0 && MenuCursor <= c_menuItemSavesEndList\n                        && (int)g_config.speedrun_mode != 0 && g_config.speedrun_mode != -SaveSlotInfo[MenuCursor + 1].ConfigDefaults\n                        && (SaveSlotInfo[MenuCursor + 1].ConfigDefaults != 0 || SaveSlotInfo[MenuCursor + 1].Progress >= 0))\n                    {\n                        PlaySoundMenu(SFX_BlockHit);\n                        MenuCursorCanMove = false;\n                    }\n                    else if(MenuCursor >= 0 && MenuCursor <= c_menuItemSavesEndList) // Select the save slot, but still need to select players\n                    {\n                        selSave = MenuCursor + 1;\n\n                        // warn the user of incompatibility if present\n                        if(SaveSlotInfo[selSave].Progress < 0 && SelectWorld[selWorld].probably_incompatible)\n                        {\n                            PlaySoundMenu(SFX_Message);\n\n                            g_MessageType = MESSAGE_TYPE_SYS_WARNING;\n                            MessageText = g_mainMenu.warnEpCompat;\n                            PauseGame(PauseCode::Message);\n                        }\n\n                        PlaySoundMenu(SFX_Do);\n\n                        if(MenuMode == MENU_SELECT_SLOT_2P)\n                            ConnectScreen::MainMenu_Start(2);\n                        else if(s_prefer_modern_char_sel())\n                            ConnectScreen::MainMenu_Start(1);\n                        else\n                            ConnectScreen::LegacyMenu_Start();\n\n                        MenuMode = MENU_CHARACTER_SELECT_NEW;\n                        MenuCursorCanMove = false;\n                    }\n                }\n            }\n\n            if(MenuMode == MENU_SELECT_SLOT_1P || MenuMode == MENU_SELECT_SLOT_2P)\n            {\n                if(MenuCursor > c_menuItemSavesDelete) MenuCursor = 0;\n                if(MenuCursor < 0) MenuCursor = c_menuItemSavesDelete;\n            }\n        } // Save Slot Select\n\n        // Save Select\n        else if(MenuMode == MENU_SELECT_SLOT_1P_COPY_S1 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S1 ||\n                MenuMode == MENU_SELECT_SLOT_1P_COPY_S2 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S2)\n        {\n            if(SharedCursor.Move)\n                s_handleMouseMove(c_menuItemSavesEndList, MenuX, MenuY, 300, 30);\n\n            if(MenuCursorCanMove || MenuMouseClick)\n            {\n                if(menuControls.Back)\n                {\n//'save select back\n                    if(MenuMode == MENU_SELECT_SLOT_1P_COPY_S2 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S2)\n                    {\n                        MenuMode -= MENU_SELECT_SLOT_COPY_S1_ADD;\n                    }\n                    else\n                    {\n                        MenuMode -= MENU_SELECT_SLOT_COPY_S1_ADD;\n                        MenuCursor = c_menuItemSavesCopy;\n                    }\n\n                    MenuCursorCanMove = false;\n                    PlaySoundMenu(SFX_Do);\n                }\n                else if(menuControls.Do || MenuMouseClick)\n                {\n                    SDL_assert_release(IF_INRANGE(MenuCursor, 0, maxSaveSlots - 1));\n                    int slot = MenuCursor + 1;\n\n                    if(MenuMode == MENU_SELECT_SLOT_1P_COPY_S1 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S1)\n                    {\n                        if(SaveSlotInfo[slot].Progress < 0)\n                            PlaySoundMenu(SFX_BlockHit);\n                        else\n                        {\n                            PlaySoundMenu(SFX_Do);\n                            menuCopySaveSrc = slot;\n                            MenuMode += MENU_SELECT_SLOT_COPY_S1_ADD;\n                        }\n\n                        MenuCursorCanMove = false;\n                    }\n                    else if(menuCopySaveSrc == slot)\n                    {\n                        PlaySoundMenu(SFX_BlockHit);\n                        MenuCursorCanMove = false;\n                    }\n                    else\n                    {\n                        PlaySoundMenu(SFX_Transform);\n                        menuCopySaveDst = slot;\n                        CopySave(selWorld, menuCopySaveSrc, menuCopySaveDst);\n                        FindSaves();\n                        MenuMode -= MENU_SELECT_SLOT_COPY_S2_ADD;\n                        MenuCursor = c_menuItemSavesCopy;\n                        MenuCursorCanMove = false;\n                    }\n                }\n            }\n\n            if(MenuMode == MENU_SELECT_SLOT_1P_COPY_S1 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S1 ||\n               MenuMode == MENU_SELECT_SLOT_1P_COPY_S2 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S2)\n            {\n                if(MenuCursor > c_menuItemSavesEndList) MenuCursor = 0;\n                if(MenuCursor < 0) MenuCursor = c_menuItemSavesEndList;\n            }\n        }\n\n        // Delete gamesave\n        else if(MenuMode == MENU_SELECT_SLOT_1P_DELETE || MenuMode == MENU_SELECT_SLOT_2P_DELETE)\n        {\n            if(SharedCursor.Move)\n                s_handleMouseMove(c_menuItemSavesEndList, MenuX, MenuY, 300, 30);\n\n            if(MenuCursorCanMove || MenuMouseClick)\n            {\n                if(menuControls.Back)\n                {\n//'save select back\n                    MenuMode -= MENU_SELECT_SLOT_DELETE_ADD;\n                    MenuCursor = c_menuItemSavesDelete;\n                    PlaySoundMenu(SFX_Do);\n                    MenuCursorCanMove = false;\n                }\n                else if(menuControls.Do || MenuMouseClick)\n                {\n                    MenuMode -= MENU_SELECT_SLOT_DELETE_ADD;\n                    DeleteSave(selWorld, (MenuCursor + 1));\n                    FindSaves();\n                    MenuCursor = c_menuItemSavesDelete;\n                    PlaySoundMenu(SFX_LavaMonster);\n                    MenuCursorCanMove = false;\n                }\n            }\n\n            if(MenuMode == MENU_SELECT_SLOT_1P_DELETE || MenuMode == MENU_SELECT_SLOT_2P_DELETE)\n            {\n                if(MenuCursor > c_menuItemSavesEndList) MenuCursor = 0;\n                if(MenuCursor < 0) MenuCursor = c_menuItemSavesEndList;\n            }\n        }\n\n        // Advanced mode selection\n        else if(MenuMode == MENU_SELECT_SLOT_1P_ADVMODE || MenuMode == MENU_SELECT_SLOT_2P_ADVMODE)\n        {\n            if(SharedCursor.Move)\n                s_handleMouseMove(5, MenuX, MenuY, MenuX, 30);\n\n            if(MenuCursorCanMove || MenuMouseClick)\n            {\n                if(menuControls.Back)\n                {\n//'save select back\n                    MenuMode -= MENU_SELECT_SLOT_ADVMODE_ADD;\n                    MenuCursor = selSave - 1;\n                    selSave = 0;\n                    PlaySoundMenu(SFX_Slide);\n                    MenuCursorCanMove = false;\n                }\n                else if(menuControls.Do || MenuMouseClick)\n                {\n                    if(MenuCursor < 3)\n                    {\n                        s_episode_playstyle = MenuCursor;\n                        s_episode_speedrun_mode = 0;\n                    }\n                    else\n                    {\n                        s_episode_speedrun_mode = MenuCursor - 2;\n                        s_episode_playstyle = s_episode_speedrun_mode - 1;\n                    }\n\n                    PlaySoundMenu(SFX_Do);\n                    MenuMode -= MENU_SELECT_SLOT_ADVMODE_ADD;\n                    MenuCursor = selSave - 1;\n                    selSave = 0;\n                    MenuCursorCanMove = false;\n                }\n            }\n\n            if(MenuMode == MENU_SELECT_SLOT_1P_ADVMODE || MenuMode == MENU_SELECT_SLOT_2P_ADVMODE)\n            {\n                if(MenuCursor > 5) MenuCursor = 0;\n                if(MenuCursor < 0) MenuCursor = 5;\n            }\n        }\n\n        // Input Settings\n        else if(MenuMode == MENU_INPUT_SETTINGS)\n        {\n            int ret = menuControls_Logic();\n            if(ret == -1)\n            {\n                Controls::SaveConfig();\n                MenuMode = MENU_NEW_OPTIONS;\n                MenuCursorCanMove = false;\n            }\n        }\n\n        // New Settings\n        else if(MenuMode == MENU_NEW_OPTIONS)\n        {\n            if(OptionsScreen::Logic())\n            {\n                int optionsIndex = 1;\n                if(s_show_separate_2P())\n                    optionsIndex++;\n                if(!g_gameInfo.disableBattleMode)\n                    optionsIndex++;\n#ifdef THEXTECH_ENABLE_SDL_NET\n                if(s_show_online())\n                    optionsIndex++;\n#endif\n                if(g_config.enable_editor)\n                    optionsIndex++;\n                MenuMode = MENU_MAIN;\n                MenuCursor = optionsIndex;\n                MenuCursorCanMove = false;\n                PlaySoundMenu(SFX_Slide);\n            }\n        }\n    }\n\n    return false;\n}\n\nstatic constexpr int find_in_string(const char* haystack, const char* haystack_start, char needle)\n{\n    return (*haystack == '\\0') ? -1 :\n        ((*haystack == needle) ? haystack - haystack_start : find_in_string(haystack + 1, haystack_start, needle));\n}\n\nstatic constexpr bool in_string(const char* haystack, char needle)\n{\n    return find_in_string(haystack, haystack, needle) != -1;\n}\n\nstatic constexpr bool str_prefix(const char* string, const char* prefix)\n{\n    return (*prefix == '\\0' || (*string == *prefix && str_prefix(string + 1, prefix + 1)));\n}\n\nstatic constexpr int find_in_string(const char* haystack, char needle)\n{\n    return find_in_string(haystack, haystack, needle);\n}\n\nvoid drawGameVersion(bool disable_git, bool git_only)\n{\n    constexpr bool is_release = !in_string(V_LATEST_STABLE, '-');\n    constexpr bool is_main = str_prefix(V_BUILD_BRANCH, \"main\");\n    constexpr bool is_stable = str_prefix(V_BUILD_BRANCH, \"stable\");\n    constexpr bool is_head = str_prefix(V_BUILD_BRANCH, \"HEAD\");\n    constexpr bool is_wip = str_prefix(V_BUILD_BRANCH, \"wip-\");\n\n    constexpr bool is_dirty = in_string(V_BUILD_VER, '-');\n\n    constexpr bool show_branch = (!is_main && !(is_release && is_stable) && !is_head);\n    constexpr bool show_commit = (!is_release || (!is_main && !is_stable && !is_head));\n\n    // show version\n    if(!git_only)\n        SuperPrintRightAlign(\"v\" V_LATEST_STABLE, 5, XRender::TargetW - XRender::TargetOverscanX - 2, 2);\n\n    // show branch\n    if(show_branch && !disable_git)\n    {\n        int y = show_commit ? XRender::TargetH - 36 : XRender::TargetH - 18;\n\n        if(is_wip)\n        {\n            // strip the \"wip-\"\n            SuperPrintRightAlign(&V_BUILD_BRANCH[find_in_string(V_BUILD_BRANCH, '-') + 1], 5, XRender::TargetW - XRender::TargetOverscanX - 2, y);\n        }\n        else\n            SuperPrintRightAlign(V_BUILD_BRANCH, 5, XRender::TargetW - XRender::TargetOverscanX - 2, y);\n    }\n\n    // show git commit\n    if(show_commit && !disable_git)\n    {\n        if(is_dirty)\n        {\n            // only show -d, not -dirty\n            SuperPrintRightAlign(find_in_string(V_BUILD_VER, '-') + 2 + 1, \"#\" V_BUILD_VER, 5, XRender::TargetW - XRender::TargetOverscanX - 2, XRender::TargetH - 18);\n        }\n        else\n            SuperPrintRightAlign(\"#\" V_BUILD_VER, 5, XRender::TargetW - XRender::TargetOverscanX - 2, XRender::TargetH - 18);\n    }\n}\n\nstatic void s_drawGameTypeTitle(int x, int y)\n{\n    if(MenuMode == MENU_EDITOR)\n        SuperPrint(g_mainMenu.mainEditor, 3, x, y, XTColorF(0.8_n, 0.8_n, 0.3_n));\n    else if(menuBattleMode)\n        SuperPrint(g_mainMenu.mainBattleGame, 3, x, y, XTColorF(0.3_n, 0.3_n, 1.0_n));\n    else\n    {\n        // show \"Play Episode\" when at the save slot selecting and adjusting settings (not when the game was started in compat / speedrun mode)\n        if(!s_show_separate_2P())\n            SuperPrint(g_mainMenu.mainPlayEpisode, 3, x, y, XTColorF(1.0_n, 0.3_n, 0.3_n));\n        else if(menuPlayersNum == 1)\n            SuperPrint(g_mainMenu.main1PlayerGame, 3, x, y, XTColorF(1.0_n, 0.3_n, 0.3_n));\n        else\n            SuperPrint(g_mainMenu.mainMultiplayerGame, 3, x, y, XTColorF(0.3_n, 1.0_n, 0.3_n));\n    }\n}\n\nstatic void s_drawGameSaves(int MenuX, int MenuY)\n{\n    int A;\n\n    for(A = 1; A <= maxSaveSlots; A++)\n    {\n        int posY = MenuY - 30 + (A * 30);\n        const auto& info = SaveSlotInfo[A];\n\n        if(info.Progress >= 0)\n        {\n            // \"SLOT {0} ... {1}%\"\n            std::string p = fmt::format_ne(g_mainMenu.gameSlotContinue, A, SaveSlotInfo[A].Progress);\n            int len = SuperTextPixLen(p, 3);\n\n            SuperPrint(p, 3, MenuX, posY);\n\n            if(info.Stars > 0)\n            {\n                len += 4;\n                XRender::renderTextureBasic(MenuX + len, posY + 1, GFX.Interface[5]);\n\n                len += GFX.Interface[5].w + 4;\n                XRender::renderTextureBasic(MenuX + len, posY + 2, GFX.Interface[1]);\n\n                len += GFX.Interface[1].w + 4;\n                SuperPrint(fmt::format_ne(\"{0}\", info.Stars), 3, MenuX + len, posY);\n            }\n        }\n        else\n        {\n            // \"SLOT {0} ... NEW GAME\"\n            SuperPrint(fmt::format_ne(g_mainMenu.gameSlotNew, A), 3, MenuX, posY);\n        }\n\n        int mode_icon_X = MenuX + 340;\n        int mode_icon_Y = MenuY - 34 + (A * 30);\n\n        int save_configs = info.ConfigDefaults;\n        if(save_configs == 0 && A == MenuCursor + 1 && (MenuMode == MENU_SELECT_SLOT_1P || MenuMode == MENU_SELECT_SLOT_2P))\n        {\n            save_configs = s_episode_playstyle + 1;\n            if((int)s_episode_speedrun_mode != 0)\n                save_configs = -s_episode_speedrun_mode;\n            if((int)g_config.speedrun_mode != 0)\n                save_configs = -g_config.speedrun_mode;\n        }\n\n        if(save_configs != 0)\n        {\n            uint16_t rot = 0;\n            uint8_t op = 255;\n            if(A == MenuCursor + 1)\n            {\n                rot = SDL_abs((int)(CommonFrame % 64) - 32);\n                rot = rot - 16;\n            }\n\n            if(info.ConfigDefaults == 0)\n                op = SDL_abs((int)(CommonFrame % 32) - 16) * 3 + 128;\n\n            if(save_configs == Config_t::MODE_CLASSIC + 1)\n            {\n                XRender::renderTextureScaleEx(mode_icon_X, mode_icon_Y, 24, 24, GFXNPC[NPCID_POWER_S3], 0, 0, 32, 32, rot, nullptr, X_FLIP_NONE, XTAlpha(op));\n            }\n            else if(save_configs == Config_t::MODE_VANILLA + 1)\n            {\n                XRender::renderTextureScaleEx(mode_icon_X, mode_icon_Y, 24, 24, GFXNPC[NPCID_FODDER_S3], 0, 0, 32, 32, rot, nullptr, X_FLIP_NONE, XTAlpha(op));\n            }\n            else if(save_configs < 0)\n            {\n                XRender::renderTextureScaleEx(mode_icon_X, mode_icon_Y, 24, 24, GFXNPC[NPCID_TIMER_S2], 0, 0, 32, 32, rot, nullptr, X_FLIP_NONE, XTAlpha(op));\n                if(save_configs > -10)\n                    XRender::renderTextureBasic(mode_icon_X + 12, mode_icon_Y + 12, GFX.Font1[-save_configs]);\n            }\n            else\n            {\n                if(GFX.EIcons.inited)\n                    XRender::renderTextureScaleEx(mode_icon_X, mode_icon_Y, 24, 24, GFX.EIcons, 0, 32*Icon::thextech, 32, 32, rot, nullptr, X_FLIP_NONE, XTAlpha(op));\n                else\n                    XRender::renderTextureScaleEx(mode_icon_X, mode_icon_Y, 24, 24, GFXNPC[NPCID_ICE_POWER_S3], 0, 0, 32, 32, rot, nullptr, X_FLIP_NONE, XTAlpha(op));\n            }\n        }\n    }\n\n    if(MenuMode == MENU_SELECT_SLOT_1P || MenuMode == MENU_SELECT_SLOT_2P)\n    {\n        SuperPrint(g_mainMenu.gameCopySave, 3, MenuX, MenuY - 30 + (A * 30));\n        A++;\n        SuperPrint(g_mainMenu.gameEraseSave, 3, MenuX, MenuY - 30 + (A * 30));\n    }\n\n\n    if(MenuCursor < 0 || MenuCursor >= maxSaveSlots || (MenuMode != MENU_SELECT_SLOT_1P && MenuMode != MENU_SELECT_SLOT_2P))\n        return;\n\n    const auto& info = SaveSlotInfo[MenuCursor + 1];\n\n    int infobox_x = XRender::TargetW / 2 - 240;\n    int infobox_y = MenuY + 145 + c_menuSavesOffsetY;\n\n    // forbid incompatible speedrun\n    if((int)g_config.speedrun_mode != 0 && g_config.speedrun_mode != -info.ConfigDefaults\n        && (info.ConfigDefaults != 0 || info.Progress >= 0))\n    {\n        XRender::renderRect(XRender::TargetW / 2 - 240, infobox_y, 480, 68, XTColorF(0, 0, 0, 1.0_n));\n        SuperPrintScreenCenter(\"SAVE INCOMPATIBLE\", 3, infobox_y + 4, XTColorF(1.0_n, 0.0_n, 0.0_n, 1.0_n));\n        SuperPrintScreenCenter(fmt::format_ne(\"WITH SPEEDRUN MODE {0}\", (int)g_config.speedrun_mode), 3, infobox_y + 24, XTColorF(1.0_n, 0.0_n, 0.0_n, 1.0_n));\n        SuperPrintScreenCenter(\"(COMMAND LINE FLAG)\", 3, infobox_y + 44, XTColorF(0.8_n, 0.8_n, 0.8_n, 1.0_n));\n    }\n    // initialize config\n    else if(info.ConfigDefaults == 0)\n    {\n        XRender::renderRect(infobox_x, infobox_y, 480, 68, {0, 0, 0, 192});\n\n        XTColor color;\n\n        if(s_episode_playstyle == Config_t::MODE_MODERN)\n            color = XTColorF(0.5_n, 0.8_n, 1.0_n);\n        else if(s_episode_playstyle == Config_t::MODE_CLASSIC)\n            color = XTColorF(1.0_n, 0.5_n, 0.5_n);\n        else\n            color = XTColorF(0.8_n, 0.5_n, 0.2_n);\n\n        if(s_episode_speedrun_mode != 0)\n        {\n            SuperPrintScreenCenter(\"SPEEDRUN MODE \" + std::to_string(s_episode_speedrun_mode), 3, infobox_y + 14, color);\n        }\n        else\n        {\n            // int target_bugfixes = (SelectWorld[selWorld].bugfixes_on_by_default) ? Config_t::MODE_MODERN : Config_t::MODE_CLASSIC;\n\n            std::string playstyle_string;\n            playstyle_string += g_options.playstyle.m_display_name;\n            playstyle_string += \": \";\n\n            if(s_episode_playstyle == Config_t::MODE_MODERN)\n                playstyle_string += g_options.playstyle.m_enum_values[0].m_display_name;\n            else if(s_episode_playstyle == Config_t::MODE_CLASSIC)\n                playstyle_string += g_options.playstyle.m_enum_values[1].m_display_name;\n            else\n                playstyle_string += g_options.playstyle.m_enum_values[2].m_display_name;\n\n            // if(target_bugfixes == g_config.playstyle)\n            //     playstyle_string += \" (Recommended)\";\n\n            SuperPrintScreenCenter(playstyle_string, 3, infobox_y + 14, color);\n\n            // switch arrows\n            menu_draw_infobox_switch_arrows(infobox_x, infobox_y);\n        }\n\n        const std::string& playstyle_description\n            = (s_episode_playstyle == Config_t::MODE_MODERN) ?\n                g_options.playstyle.m_enum_values[0].m_display_tooltip\n            : (s_episode_playstyle == Config_t::MODE_CLASSIC) ?\n                g_options.playstyle.m_enum_values[1].m_display_tooltip\n            :\n                g_options.playstyle.m_enum_values[2].m_display_tooltip;\n\n        SuperPrintScreenCenter(playstyle_description, 5, infobox_y + 34, color);\n    }\n    // display fun save slot info\n    else if(info.Progress >= 0)\n    {\n        int row_1 = infobox_y + 10;\n        int row_2 = infobox_y + 42;\n        int row_c = infobox_y + 26;\n\n        bool hasFails = (g_config.enable_fails_tracking || info.FailsEnabled);\n        bool show_timer = info.Time > 0 && (g_config.enable_playtime_tracking || info.ConfigDefaults < 0);\n        bool show_score = (s_episode_playstyle != Config_t::MODE_VANILLA);\n\n        // recenter if single row\n        if(!show_timer && !hasFails)\n            row_1 = infobox_y + 26;\n\n        XRender::renderRect(infobox_x, infobox_y, 480, 68, {0, 0, 0, 127});\n\n        // Temp string\n        std::string t;\n\n        // Score\n        if(show_score)\n        {\n            int row_score = show_timer ? row_1 : row_c;\n            SuperPrint(t = fmt::format_ne(g_mainMenu.phraseScore, info.Score), 3, infobox_x + 10, row_score);\n        }\n\n        // Gameplay Timer\n        if(show_timer)\n        {\n            std::string t = GameplayTimer::formatTime(info.Time);\n\n            if(t.size() > 9)\n                t = t.substr(0, t.size() - 4);\n\n            SuperPrint(fmt::format_ne(g_mainMenu.phraseTime, t), 3, infobox_x + 10, row_2);\n        }\n\n        // If demos off, put (l)ives and (c)oins on center\n        int row_lc = (hasFails) ? row_1 : row_c;\n\n        // Print lives on the screen (from gfx_update2.cpp)\n        DrawLives(infobox_x + 272 + 32, row_lc, info.Lives, info.Hundreds, s_episode_playstyle == Config_t::MODE_VANILLA);\n\n        // Print coins on the screen (from gfx_update2.cpp)\n        int coins_x = infobox_x + 480 - 10 - 36 - 62;\n        XRender::renderTextureBasic(coins_x + 16, row_lc, GFX.Interface[2]);\n        XRender::renderTextureBasic(coins_x + 40, row_lc + 2, GFX.Interface[1]);\n        SuperPrintRightAlign(t = std::to_string(info.Coins), 1, coins_x + 62 + 36, row_lc + 2);\n\n        // Fails Counter\n        if(hasFails)\n            SuperPrintRightAlign(fmt::format_ne(\"{0}: {1}\", g_gameInfo.fails_counter_title, info.Fails), 3, infobox_x + 480 - 10, row_2);\n    }\n}\n\nvoid mainMenuDraw()\n{\n    int MenuX, MenuY;\n    GetMenuPos(&MenuX, &MenuY);\n\n#ifdef __3DS__\n    XRender::setTargetLayer(2);\n#endif\n\n    // Render the permanent menu graphics (curtain, URL, logo)\n\n    // URL\n    if(XRender::TargetH >= SmallScreenH)\n        XRender::renderTextureBasic(XRender::TargetW / 2 - GFX.MenuGFX[3].w / 2, XRender::TargetH - 24, GFX.MenuGFX[3]);\n\n    bool at_main_title = (MenuMode == MENU_MAIN || MenuMode == MENU_INTRO);\n    bool draw_in_asset_pack = at_main_title && s_startAssetPackTimer >= 2;\n\n    if(!draw_in_asset_pack)\n    {\n        // Curtain\n        // correction to loop the original asset properly\n        int curtain_draw_w = GFX.MenuGFX[1].w;\n        if(curtain_draw_w == 800)\n            curtain_draw_w = 768;\n        int curtain_horiz_reps = XRender::TargetW / curtain_draw_w + 2;\n\n        for(int i = 0; i < curtain_horiz_reps; i++)\n            XRender::renderTextureBasic(curtain_draw_w * i, 0, curtain_draw_w, GFX.MenuGFX[1].h, GFX.MenuGFX[1], 0, 0);\n\n#ifdef __3DS__\n        if(at_main_title)\n            XRender::setTargetLayer(3);\n#endif\n\n        // game logo\n        int LogoMode = 0;\n        if(XRender::TargetH >= TinyScreenH || MenuMode == MENU_INTRO)\n            LogoMode = 1;\n        else if(MenuMode == MENU_MAIN)\n            LogoMode = 2;\n\n        if(LogoMode == 1)\n        {\n            // show at half opacity if not at main menu on a small screen\n            XTColor logo_tint = (XRender::TargetH < SmallScreenH && MenuMode != MENU_INTRO && MenuMode != MENU_MAIN) ? XTAlpha(127) : XTColor();\n\n            int logo_y = XRender::TargetH / 2 - 230;\n\n            // place manually on small screens\n            if(XRender::TargetH < TinyScreenH)\n                logo_y = 16 + (XRender::TargetH - 320) / 2;\n            else if(XRender::TargetH < SmallScreenH)\n                logo_y = 16;\n            else if(XRender::TargetH <= 600)\n                logo_y = 40;\n\n            XRender::renderTextureBasic(XRender::TargetW / 2 - GFX.MenuGFX[2].w / 2, logo_y, GFX.MenuGFX[2], logo_tint);\n        }\n        else if(LogoMode == 2)\n        {\n            SuperPrint(g_gameInfo.title, 3, XRender::TargetW / 2 - g_gameInfo.title.length()*9, 30);\n        }\n    }\n\n#ifdef __3DS__\n    if(MenuMode != MENU_NEW_OPTIONS)\n        XRender::setTargetLayer(3);\n#endif\n\n    drawGameVersion(false, draw_in_asset_pack);\n\n\n    // Menu Intro\n    if(MenuMode == MENU_INTRO)\n    {\n        if((CommonFrame % 90) < 45)\n            SuperPrintScreenCenter(g_mainMenu.introPressStart, 3, XRender::TargetH - 48 - (XRender::TargetH - 320) / 4);\n    }\n\n#ifndef PGE_NO_THREADING\n    // loading (can't safely render)\n    if(SDL_AtomicGet(&loading))\n    {\n        if(SDL_AtomicGet(&loadingProgrssMax) <= 0)\n            SuperPrint(g_mainMenu.loading, 3, MenuX, MenuY);\n        else\n        {\n            int progress = (SDL_AtomicGet(&loadingProgrss) * 100) / SDL_AtomicGet(&loadingProgrssMax);\n            SuperPrint(fmt::format_ne(\"{0} {1}%\", g_mainMenu.loading, progress), 3, MenuX, MenuY);\n        }\n    }\n    // DO NOT DETACH THE BELOW ELSE STATEMENT FROM THE FOLLOWING SERIES OF IF CLAUSES\n    else\n#endif // #ifndef PGE_NO_THREADING\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // loading (can't safely render)\n    if(XMessage::GetClientStatus() == nullptr)\n    {\n        SuperPrint(g_mainMenu.loading, 3, MenuX, MenuY);\n    }\n    // DO NOT DETACH THE BELOW ELSE STATEMENT FROM THE FOLLOWING SERIES OF IF CLAUSES\n    else\n#endif // #ifdef THEXTECH_ENABLE_SDL_NET\n    // Main menu\n        if(MenuMode == MENU_MAIN)\n    {\n        int i = 0;\n        SuperPrint(s_show_separate_2P() ? g_mainMenu.main1PlayerGame : g_mainMenu.mainPlayEpisode, 3, MenuX, MenuY+30*(i++));\n        if(s_show_separate_2P())\n            SuperPrint(g_mainMenu.mainMultiplayerGame, 3, MenuX, MenuY+30*(i++));\n        if(!g_gameInfo.disableBattleMode)\n            SuperPrint(g_mainMenu.mainBattleGame, 3, MenuX, MenuY+30*(i++));\n#ifdef THEXTECH_ENABLE_SDL_NET\n        if(s_show_online())\n            SuperPrint(g_mainMenu.mainNetplay, 3, MenuX, MenuY+30*(i++));\n#endif\n        if(g_config.enable_editor)\n            SuperPrint(g_mainMenu.mainEditor, 3, MenuX, MenuY+30*(i++));\n        SuperPrint(g_mainMenu.mainOptions, 3, MenuX, MenuY+30*(i++));\n        SuperPrint(g_mainMenu.mainExit, 3, MenuX, MenuY+30*(i++));\n        XRender::renderTextureBasic(MenuX - 20, MenuY + (MenuCursor * 30), GFX.MCursor[0]);\n    }\n    // Character select\n    else if(MenuMode == MENU_CHARACTER_SELECT_NEW ||\n            MenuMode == MENU_CHARACTER_SELECT_NEW_BM)\n    {\n        s_drawGameTypeTitle(MenuX, MenuY - 70);\n        const auto& world_list = (MenuMode == MENU_CHARACTER_SELECT_NEW) ? SelectWorld : SelectBattle;\n        SuperPrint(world_list[selWorld].WorldName, 3, MenuX, MenuY - 40, XTColorF(0.6_n, 1.0_n, 1.0_n));\n\n        ConnectScreen::Render();\n    }\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // NetPlay main menu\n    else if(MenuMode == MENU_NETPLAY)\n    {\n        int i = 0;\n        XTColor c;\n        XTColor s;\n\n        if(XMessage::GetClientStatus() && XMessage::GetClientStatus()->client_state == XMessage::CLIENT_LOBBY)\n            s = {200, 255, 200};\n        else\n        {\n            s = {255, 200, 200};\n            c = {127, 127, 127};\n        }\n\n        SuperPrint(g_mainMenu.netplayJoinRoom, 3, MenuX, MenuY+30*(i++), c);\n        SuperPrint(g_mainMenu.netplayCreateRoom, 3, MenuX, MenuY+30*(i++), c);\n        SuperPrint(g_mainMenu.netplayServer + ' ' + g_netplayServer, 3, MenuX, MenuY+30*(i++), s);\n        SuperPrint(g_mainMenu.netplayNickname + ' ' + g_netplayNickname, 3, MenuX, MenuY+30*(i++));\n        XRender::renderTextureBasic(MenuX - 20, MenuY + (MenuCursor * 30), GFX.MCursor[0]);\n    }\n#endif\n\n    // Episode / Level selection\n    else if(MenuMode == MENU_1PLAYER_GAME || MenuMode == MENU_2PLAYER_GAME\n#ifdef THEXTECH_ENABLE_SDL_NET\n        || MenuMode == MENU_NETPLAY_WORLD_SELECT\n#endif\n        || MenuMode == MENU_BATTLE_MODE || MenuMode == MENU_EDITOR)\n    {\n        s_drawGameTypeTitle(MenuX, MenuY - 70);\n        ContentSelectScreen::Render();\n    }\n\n    else if(MenuMode == MENU_SELECT_SLOT_1P || MenuMode == MENU_SELECT_SLOT_2P) // Save Select\n    {\n        s_drawGameTypeTitle(MenuX, MenuY - 70);\n        SuperPrint(SelectWorld[selWorld].WorldName, 3, MenuX, MenuY - 40, XTColorF(0.6_n, 1.0_n, 1.0_n));\n        s_drawGameSaves(MenuX, MenuY);\n        XRender::renderTextureBasic(MenuX - 20, MenuY + (MenuCursor * 30), GFX.MCursor[0]);\n    }\n\n    else if(MenuMode == MENU_SELECT_SLOT_1P_COPY_S1 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S1 ||\n            MenuMode == MENU_SELECT_SLOT_1P_COPY_S2 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S2) // Copy save\n    {\n        s_drawGameTypeTitle(MenuX, MenuY - 70);\n        SuperPrint(SelectWorld[selWorld].WorldName, 3, MenuX, MenuY - 40, XTColorF(0.6_n, 1.0_n, 1.0_n));\n        s_drawGameSaves(MenuX, MenuY);\n\n        if(MenuMode == MENU_SELECT_SLOT_1P_COPY_S1 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S1)\n            SuperPrint(g_mainMenu.gameSourceSlot, 3, MenuX, MenuY + c_menuSavesFooterHint, XTColorF(0.7_n, 0.7_n, 1.0_n));\n        else if(MenuMode == MENU_SELECT_SLOT_1P_COPY_S2 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S2)\n            SuperPrint(g_mainMenu.gameTargetSlot, 3, MenuX, MenuY + c_menuSavesFooterHint, XTColorF(0.7_n, 1.0_n, 0.7_n));\n\n        if(MenuMode == MENU_SELECT_SLOT_1P_COPY_S2 || MenuMode == MENU_SELECT_SLOT_2P_COPY_S2)\n        {\n            XRender::renderTextureBasic(MenuX - 20, MenuY + ((menuCopySaveSrc - 1) * 30), GFX.MCursor[0]);\n            XRender::renderTextureBasic(MenuX - 20, MenuY + (MenuCursor * 30), GFX.MCursor[3]);\n        }\n        else\n            XRender::renderTextureBasic(MenuX - 20, MenuY + (MenuCursor * 30), GFX.MCursor[0]);\n    }\n\n    else if(MenuMode == MENU_SELECT_SLOT_1P_DELETE || MenuMode == MENU_SELECT_SLOT_2P_DELETE) // Delete save\n    {\n        s_drawGameTypeTitle(MenuX, MenuY - 70);\n        SuperPrint(SelectWorld[selWorld].WorldName, 3, MenuX, MenuY - 40, XTColorF(0.6_n, 1.0_n, 1.0_n));\n        s_drawGameSaves(MenuX, MenuY);\n\n        SuperPrint(g_mainMenu.gameEraseSlot, 3, MenuX, MenuY + c_menuSavesFooterHint, XTColorF(1.0_n, 0.7_n, 0.7_n));\n\n        XRender::renderTextureBasic(MenuX - 20, MenuY + (MenuCursor * 30), GFX.MCursor[0]);\n    }\n\n    else if(MenuMode == MENU_SELECT_SLOT_1P_ADVMODE || MenuMode == MENU_SELECT_SLOT_2P_ADVMODE) // Advanced mode select\n    {\n        s_drawGameTypeTitle(MenuX, MenuY - 70);\n        SuperPrint(SelectWorld[selWorld].WorldName, 3, MenuX, MenuY - 40, XTColorF(0.6_n, 1.0_n, 1.0_n));\n\n        int A = 0;\n\n        SuperPrint(\"Modern Game\", 3, MenuX, MenuY + (A++ * 30), XTColorF(0.5_n, 0.7_n, 1.0_n));\n        SuperPrint(\"Classic Game\", 3, MenuX, MenuY + (A++ * 30), XTColorF(1.0_n, 0.7_n, 0.7_n));\n        SuperPrint(\"Vanilla Game\", 3, MenuX, MenuY + (A++ * 30), XTColorF(0.8_n, 0.5_n, 0.2_n));\n        SuperPrint(\"Speedrun Mode 1\", 3, MenuX, MenuY + (A++ * 30), XTColorF(0.5_n, 0.7_n, 1.0_n));\n        SuperPrint(\"Speedrun Mode 2\", 3, MenuX, MenuY + (A++ * 30), XTColorF(1.0_n, 0.7_n, 0.7_n));\n        SuperPrint(\"Speedrun Mode 3\", 3, MenuX, MenuY + (A++ * 30), XTColorF(0.8_n, 0.5_n, 0.2_n));\n\n        XRender::renderTextureBasic(MenuX - 20, MenuY + (MenuCursor * 30), GFX.MCursor[0]);\n    }\n\n    // Player controls setup\n    else if(MenuMode == MENU_INPUT_SETTINGS)\n    {\n        menuControls_Render();\n    }\n\n    // New options screen\n    else if(MenuMode == MENU_NEW_OPTIONS)\n    {\n#ifdef __3DS__\n        // draw options screen (only) in top draw plane\n        XRender::setTargetLayer(3);\n#endif\n\n        OptionsScreen::Render();\n    }\n\n    // fade to / from asset pack screen\n    if(s_can_enter_ap_screen() && s_startAssetPackTimer > 0)\n    {\n        ScreenAssetPack::DrawBackground(s_startAssetPackTimer);\n        g_levelScreenFader.clearFader();\n    }\n\n    // Mouse cursor\n    XRender::renderTextureBasic(int(SharedCursor.X), int(SharedCursor.Y), GFX.ECursor[2]);\n}\n"
  },
  {
    "path": "src/main/menu_main.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef MENU_MAIN_H\n#define MENU_MAIN_H\n\n#include <string>\n#include <memory>\n#include <stdint.h>\n\n#include \"global_constants.h\"\n#include \"std_picture.h\"\n#include \"range_arr.hpp\"\n\n//Public Type SelectWorld\nstruct SelectWorld_t\n{\n//    WorldName As String\n    std::string WorldName;\n//    WorldPath As String\n    // std::string WorldPath;\n//    WorldFile As String (NEW: Full filepath, or <URL for remote assets)\n    std::string WorldFilePath;\n\n    // NEW: repo annotations\n    std::string CreatorName;\n    std::string Description;\n    std::unique_ptr<StdPicture> screenshot;\n    uint32_t ReleaseDate = 0; // reserved for now\n\n//    blockChar(1 To numCharacters) As Boolean\n    RangeArrI<bool, 1, numCharacters, false> blockChar;\n// EXTRA:\n    bool bugfixes_on_by_default = false;\n    bool editable = false;\n    bool disabled = false;\n    bool probably_incompatible = false;\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // content hash of packed episode\n    uint32_t lz4_content_hash = 0;\n#endif\n//End Type\n};\n\nextern int NumSelectWorld;\nextern int NumSelectWorldEditable; // NEW\nextern int NumSelectBattle; // NEW\nextern std::vector<SelectWorld_t> SelectWorld;\nextern std::vector<SelectWorld_t> SelectBattle; // NEW\n\n// Menu modes\nenum\n{\n    // Main intro\n    MENU_INTRO = -1,\n    // Main menu\n    MENU_MAIN = 0,\n    // One player game episode select\n    MENU_1PLAYER_GAME = 1,\n    // Two player game episode select\n    MENU_2PLAYER_GAME = 2,\n    // Battle mode level select\n    MENU_BATTLE_MODE = 4,\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // NetPlay welcome menu\n    MENU_NETPLAY = 5,\n    // NetPlay episode select\n    MENU_NETPLAY_WORLD_SELECT = 6,\n#endif\n    // Options menu\n    // MENU_OPTIONS = 3, // FULLY UNUSED\n    // New options menu\n    MENU_NEW_OPTIONS = 8,\n    // Editor mode episode select\n    MENU_EDITOR = 9,\n\n#if 0\n    // Character select for single player game\n    MENU_CHARACTER_SELECT_1P    = 100,\n    // Character select for two player game, step 1\n    MENU_CHARACTER_SELECT_2P_S1 = 200,\n    // Character select for two player game, step 2\n    MENU_CHARACTER_SELECT_2P_S2 = 300,\n    // Character select for battle game, step 1\n    MENU_CHARACTER_SELECT_BM_S1 = 400,\n    // Character select for battle game, step 2\n    MENU_CHARACTER_SELECT_BM_S2 = 500,\n\n    MENU_CHARACTER_SELECT_BASE  = MENU_CHARACTER_SELECT_1P,\n    MENU_CHARACTER_SELECT_BASE_END = MENU_CHARACTER_SELECT_BM_S2,\n#endif\n\n    // New character select for main game\n    MENU_CHARACTER_SELECT_NEW    = 1000,\n    MENU_CHARACTER_SELECT_NEW_BM = 1001,\n\n    MENU_INPUT_SETTINGS = 30,\n\n    MENU_SELECT_SLOT_BASE = 10,\n    // Select game slot for single-player game\n    MENU_SELECT_SLOT_1P = 10,\n    // Select game slot for two player game\n    MENU_SELECT_SLOT_2P = 20,\n\n    MENU_SELECT_SLOT_1P_COPY_S1 = 11,\n    MENU_SELECT_SLOT_2P_COPY_S1 = 21,\n    MENU_SELECT_SLOT_COPY_S1_ADD = 1,\n    MENU_SELECT_SLOT_1P_COPY_S2 = 12,\n    MENU_SELECT_SLOT_2P_COPY_S2 = 22,\n    MENU_SELECT_SLOT_COPY_S2_ADD = 2,\n\n    MENU_SELECT_SLOT_1P_DELETE = 13,\n    MENU_SELECT_SLOT_2P_DELETE = 23,\n    MENU_SELECT_SLOT_DELETE_ADD = 3,\n\n    MENU_SELECT_SLOT_1P_ADVMODE = 14,\n    MENU_SELECT_SLOT_2P_ADVMODE = 24,\n    MENU_SELECT_SLOT_ADVMODE_ADD = 4,\n\n    MENU_SELECT_SLOT_END = 30,\n};\n\nstruct MainMenuContent\n{\n    std::string introPressStart;\n\n    std::string mainPlayEpisode;\n    std::string main1PlayerGame;\n    std::string mainMultiplayerGame;\n    std::string mainBattleGame;\n    std::string mainEditor;\n    std::string mainOptions;\n    std::string mainExit;\n\n    std::string loading;\n\n    std::string languageName;\n    std::string pluralRules;\n\n    std::string editorBattles;\n    std::string editorNewWorld;\n    std::string editorMakeFor;\n    std::string editorErrorMissingResources;\n    std::string editorPromptNewWorldName;\n\n    std::string gameNoEpisodesToPlay;\n    std::string gameNoBattleLevels;\n    std::string gameBattleRandom;\n\n    std::string warnEpCompat;\n\n    std::string gameSlotContinue;\n    std::string gameSlotNew;\n\n    std::string gameCopySave;\n    std::string gameEraseSave;\n\n    std::string gameSourceSlot;\n    std::string gameTargetSlot;\n    std::string gameEraseSlot;\n\n    std::string phraseScore;\n    std::string phraseTime;\n\n    // Battle\n    std::string errorBattleNoLevels;\n\n    // Options\n    std::string optionsRestartEngine;\n\n    // ConnectScreen\n    std::string selectCharacter;\n\n    // Controls menus\n    std::string controlsTitle;\n    std::string controlsConnected;\n    std::string controlsDeleteKey;\n    std::string controlsDeviceTypes;\n    std::string controlsInUse;\n    std::string controlsNotInUse;\n\n    std::string controlsActivateProfile;\n    std::string controlsRenameProfile;\n    std::string controlsDeleteProfile;\n    std::string controlsPlayerControls;\n    std::string controlsCursorControls;\n    std::string controlsEditorControls;\n    std::string controlsHotkeys;\n\n    std::string controlsOptionRumble;\n    std::string controlsOptionBatteryStatus;\n    std::string controlsOptionAltMenuControls;\n\n    std::string wordProfiles;\n    std::string wordButtons;\n    std::string controlsReallyDeleteProfile;\n    std::string controlsNewProfile;\n\n    // General purpose\n    std::string wordYes;\n    std::string wordNo;\n    std::string wordOkay;\n    std::string caseNone;\n    std::string wordOn;\n    std::string wordOff;\n    std::string wordShow;\n    std::string wordHide;\n    std::string wordPlayer;\n    std::string wordProfile;\n    std::string wordBack;\n    std::string wordResume;\n    std::string wordWaiting;\n    std::string abbrevMilliseconds;\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // NetPlay\n    std::string mainNetplay;\n    std::string netplayRoomKey;\n    std::string netplayJoinRoom;\n    std::string netplayCreateRoom;\n    std::string netplayLeaveRoom;\n    std::string netplayServer;\n    std::string netplayNickname;\n#endif\n};\n\nextern MainMenuContent g_mainMenu;\n\nextern void initMainMenu();\n\nextern bool mainMenuUpdate();\nextern void mainMenuDraw();\n\nextern int mainMenuPlaystyle();\n\nvoid GetMenuPos(int* MenuX, int* MenuY);\n\n#endif // MENU_MAIN_H\n"
  },
  {
    "path": "src/main/outro_loop.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include <Logger/logger.h>\n#include <Integrator/integrator.h>\n\n#include \"../globals.h\"\n#include \"../game_main.h\"\n#include \"../core/render.h\"\n#include \"../controls.h\"\n#include \"../npc.h\"\n#include \"../blocks.h\"\n#include \"../collision.h\"\n#include \"../effect.h\"\n#include \"../player.h\"\n#include \"../graphics.h\"\n#include \"../sound.h\"\n#include \"../main/trees.h\"\n#include \"../script/luna/luna.h\"\n#include \"layers.h\"\n#include \"game_info.h\"\n#include \"outro_loop.h\"\n\nOutroContent g_outroScreen;\n\nvoid initOutroContent()\n{\n    g_outroScreen.gameCredits = \"Game credits:\";\n    g_outroScreen.engineCredits = \"Engine credits:\";\n\n    g_outroScreen.originalBy = \"Original VB6 code By:\";\n    g_outroScreen.nameAndrewSpinks = \"Andrew Spinks\";\n\n    g_outroScreen.cppPortDevelopers = \"C++ port developers:\";\n    g_outroScreen.nameVitalyNovichkov = \"Vitaly Novichkov\";\n\n    g_outroScreen.qualityControl = \"Quality Control:\";\n\n    g_outroScreen.psVitaPortBy = \"PS Vita Port By:\";\n\n    g_outroScreen.levelDesign = \"Level Design:\";\n\n    g_outroScreen.customSprites = \"Custom Sprites:\";\n\n    g_outroScreen.specialThanks = \"Special Thanks:\";\n}\n\n\nvoid DoCredits(bool quit)\n{\n    // CreditChop and CreditOffsetY were previously floats, now they are ints measured in tenths\n\n    if(GameMenu)\n        return;\n\n    int TargetH_half = XRender::TargetH / 2;\n\n    int shrink = vScreen[1].Top;\n\n    CreditOffsetY -= 8;\n//    if(CreditOffsetY > ScreenH || CreditOffsetY + CreditTotalHeight < 0)\n//    {}\n\n    // Closing screen\n    if((CreditOffsetY + CreditTotalHeight * 10) < -shrink * 10)\n    {\n        if(musicPlaying)\n        {\n            FadeOutMusic(11000);\n            musicPlaying = false;\n        }\n\n        // CreditChop += 0.4f;\n        CreditChop += 4;\n        if(CreditChop >= TargetH_half * 10)\n        {\n            CreditChop = TargetH_half * 10;\n            EndCredits++;\n            if(EndCredits == TargetH_half)\n            {\n                SetupCredits();\n                GameOutroDoQuit = true;\n                GameMenu = true;\n            }\n        }\n        else\n            EndCredits = 0;\n    }\n\n        // Opening screen\n    else if(CreditChop > shrink * 10 && CreditOffsetY + CreditTotalHeight * 10 > 0)\n    {\n        // CreditChop -= 2.0f;\n        CreditChop -= 20;\n        if(CreditChop < shrink * 10)\n            CreditChop = shrink * 10;\n\n        if(CreditChop < TargetH_half * 10 - 500 && !musicPlaying)\n        {\n            if(bgMusic[0] <= 0) // Play default music if no music set in outro level\n            {\n                musicName = \"tmusic\";\n                PlayMusic(\"tmusic\", 2000);\n                musicPlaying = true;\n            }\n            else // Otherwise, play the music that set by level\n                StartMusic(0, 2000);\n        }\n    }\n\n    if(CreditChop <= shrink * 10 || EndCredits > 0)\n    {\n//        for(A = 1; A <= 2; A++) // Useless loop\n//        {\n        if(quit)\n        {\n            CreditChop = TargetH_half * 10;\n            EndCredits = 0;\n            XRender::clearBuffer();\n            SetupCredits();\n            GameMenu = true;\n            GameOutroDoQuit = true;\n        }\n//        }\n    }\n}\n\nvoid OutroLoop()\n{\n    lunaLoop();\n    Controls::Update(false);\n    Integrator::sync();\n    bool quit = l_SharedControls.QuitCredits;\n\n    for(int i = 0; i < l_screen->player_count; i++)\n        quit |= Controls::g_RawControls[i].Start;\n\n    if(g_gameInfo.outroDeadMode)\n    {\n        ClearTriggeredEvents();\n        UpdateLayers();\n        UpdateNPCs();\n        UpdateBlocks();\n        UpdateEffects();\n        // UpdatePlayer();\n        DoCredits(quit);\n        UpdateGraphics();\n        UpdateSound();\n        UpdateEvents();\n\n        if(GameOutroDoQuit) // Don't unset the GameOutro before GFX update, otherwise a glitch will happen\n        {\n            GameOutro = false;\n            GameOutroDoQuit = false;\n        }\n        return;\n    }\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        auto &pp = Player[A];\n        pp.Controls = Controls_t();\n\n        if(g_gameInfo.outroWalkDirection < 0)\n            pp.Controls.Left = true;\n        else if(g_gameInfo.outroWalkDirection > 0)\n            pp.Controls.Right = true;\n\n        if(pp.Controls.Left)\n            pp.Direction = -1;\n        if(pp.Controls.Right)\n            pp.Direction = 1;\n\n        Location_t tempLocation = pp.Location;\n        // tempLocation = pp.Location; // Why here was the duplicated location assignment?\n        tempLocation.SpeedX = 0;\n        tempLocation.SpeedY = 0;\n        num_t pp_bottom = pp.Location.Y + pp.Location.Height;\n        tempLocation.Y = pp_bottom - 8;\n        tempLocation.Height = pp.Mount == 1 ? 50 : 25;\n        tempLocation.Width = 16;\n\n        if(pp.Location.SpeedX > 0)\n            tempLocation.X = (pp.Location.X + pp.Location.Width) + 20;\n        else\n            tempLocation.X = pp.Location.X - (tempLocation.Width + 20);\n\n        if(g_gameInfo.outroAutoJump)\n        {\n            // int64_t fBlock = 0;\n            // int64_t lBlock = 0;\n            // fBlock = FirstBlock[long(tempLocation.X / 32) - 1];\n            // lBlock = LastBlock[long((tempLocation.X + tempLocation.Width) / 32.0) + 1];\n            // blockTileGet(tempLocation, fBlock, lBlock);\n            // if(!BlocksSorted)\n            // {\n            //     fBlock = 1;\n            //     lBlock = numBlock;\n            // }\n\n            bool doJump = true;\n\n            for(const Block_t& bb : treeFLBlockQuery(tempLocation, SORTMODE_NONE))\n            {\n//                if(tempLocation.X + tempLocation.Width >= Block[B].Location.X &&\n//                   tempLocation.X <= Block[B].Location.X + Block[B].Location.Width &&\n//                   tempLocation.Y + tempLocation.Height >= Block[B].Location.Y &&\n//                   tempLocation.Y <= Block[B].Location.Y + Block[B].Location.Height)\n                if(CheckCollision(tempLocation, bb.Location))\n                {\n                    if(!BlockNoClipping[bb.Type] &&\n                       !bb.Invis && !bb.Hidden &&\n                       !(BlockIsSizable[bb.Type] && bb.Location.Y < pp_bottom - 3))\n                    {\n                        doJump = false;\n                        break;\n                    }\n                }\n            }\n\n            // D_pLogDebug(\"Player %d jumped: doJump %d, ppJump: %d\", A, doJump ? 1 : 0, pp.Jump);\n            if(doJump || pp.Jump > 0)\n                pp.Controls.Jump = true;\n        }\n    }\n\n    ClearTriggeredEvents();\n    UpdateLayers();\n    UpdateNPCs();\n    UpdateBlocks();\n    UpdateEffects();\n    UpdatePlayer();\n    DoCredits(quit);\n    UpdateGraphics();\n    UpdateSound();\n    UpdateEvents();\n\n    if(GameOutroDoQuit) // Don't unset the GameOutro before GFX update, otherwise a glitch will happen\n    {\n        GameOutro = false;\n        GameOutroDoQuit = false;\n    }\n}\n\nvoid AddCredit(const std::string &newCredit)\n{\n    numCredits += 1;\n    if(numCredits > maxCreditsLines)\n    {\n        numCredits = maxCreditsLines;\n        pLogWarning(\"Can't add more credits lines: max limit has been excited (%d linex maximum)\", maxCreditsLines);\n        return;\n    }\n\n    auto &c = Credit[numCredits];\n    c.Text = STRINGINDEX_NONE;\n\n    SetS(c.Text, newCredit);\n}\n\nvoid SetupCredits()\n{\n    int A = 0;\n    numCredits = 0;\n\n    AddCredit(g_gameInfo.title);\n\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n#ifdef ENABLE_OLD_CREDITS\n    AddCredit(\"Created By:\");\n#else\n    if(!g_gameInfo.creditsGame.empty())\n    {\n        AddCredit(g_outroScreen.gameCredits);\n        for(auto &s : g_gameInfo.creditsGame)\n            AddCredit(s);\n        AddCredit(\"\");\n        AddCredit(\"\");\n        AddCredit(g_outroScreen.engineCredits);\n        AddCredit(\"\");\n    }\n\n    AddCredit(g_outroScreen.originalBy);\n#endif\n    AddCredit(\"\");\n    AddCredit(g_outroScreen.nameAndrewSpinks);\n    AddCredit(\"'Redigit'\"); // Author of original VB6 Engine (2009-2011)\n    AddCredit(\"\");\n    AddCredit(\"\");\n#ifndef ENABLE_OLD_CREDITS\n    AddCredit(g_outroScreen.cppPortDevelopers);\n    AddCredit(\"\");\n    AddCredit(g_outroScreen.nameVitalyNovichkov);\n    AddCredit(\"'Wohlstand'\");\n    AddCredit(\"\");\n    AddCredit(\"ds-sloth\"); // For the major contribution to the code and becoming a co-developer\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(g_outroScreen.qualityControl);\n    AddCredit(\"\");\n    AddCredit(\"0lhi\"); // Significant contribution to TheXTech development, testing, improvement process (2021-2024)\n    AddCredit(\"\");\n    AddCredit(\"\");\n#endif\n#ifdef VITA\n    AddCredit(g_outroScreen.psVitaPortBy);\n    AddCredit(\"\");\n    AddCredit(\"Axiom\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n#endif\n\n    if(numWorldCredits > 0)\n    {\n        AddCredit(g_outroScreen.levelDesign);\n        AddCredit(\"\");\n        for(A = 1; A <= numWorldCredits; A++)\n            AddCredit(WorldCredits[A]);\n        AddCredit(\"\");\n        AddCredit(\"\");\n        AddCredit(\"\");\n    }\n\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(g_outroScreen.customSprites);\n    AddCredit(\"\");\n    AddCredit(\"Blue\");\n    AddCredit(\"Iceman404\");\n    AddCredit(\"LuigiFan\");\n    AddCredit(\"NameUser\");\n    AddCredit(\"Redigit\");\n    AddCredit(\"Valtteri\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(g_outroScreen.specialThanks);\n#ifndef ENABLE_OLD_CREDITS\n    AddCredit(\"\");\n    AddCredit(\"Kevsoft\");\n    AddCredit(\"Rednaxela\");\n    AddCredit(\"Aero\");\n    AddCredit(\"Kley\");\n    AddCredit(\"ShadowYoshi (Joey)\");\n    AddCredit(\"ZeZeinzer\"); // Android testing, touchscreen controller button pictures\n    AddCredit(\"LucyZocker\"); // Android testing\n    AddCredit(\"DavFar\"); // Spanish translation of the ReadMe\n    AddCredit(\"Yingchun Soul\"); // Idea for individual iceball shooting SFX and contribution with the \"frozen NPC breaking\" SFX\n    AddCredit(\"MrDoubleA\"); // Contribution with the \"NPC got frozen\" SFX\n    AddCredit(\"Slash-18\"); // Contribution with the better iceball shooting SFX\n    AddCredit(\"sl4cer\");   // For reporting several issues while livestreaming (2021) (also known as draena)\n    AddCredit(\"Eclipsed\"); // For the help to verify and polish the speed-run mode (2021)\n    AddCredit(\"RunninPigeon\"); // For Nintendo Switch alpha testing on hardware (Formerly \"ThatRoadRunnerfan727 (Matthew)\")\n    AddCredit(\"RMN Community\"); // For Quality Episodes that allowed us to refine the Engine\n    AddCredit(\"Savby\"); // For important design work on the character select screen (ConnectScreen)\n    AddCredit(\"Otabo\"); /* For the \"Sarasaland Adventure\" projects (both 1 and 2) that was being widely used in\n                           tests and debugs: and also, they introduced the SMBX to me (Vitaliy), in 2013\n                           and inspired me to all my future projects (Moondust Project and TheXTech). */\n    AddCredit(\"Krissy Silvermoon\"); // For testing and design feedback since 2021 until at least 2025\n    AddCredit(\"Sapphire Bullet Bill\"); // For testing and gameplay feedback since 2022 until at least 2025\n    AddCredit(\"Bubble\"); // For bugtesting in 2021-2022\n    AddCredit(\"Trickii\"); // For extensive bugtesting in 2024\n    AddCredit(\"AntonioGZZ96\"); // For careful bugtesting in 2024\n    AddCredit(\"ddrsoul\"); // For sharing the engine with the PortMaster community (2024)\n    AddCredit(\"ChaikaWiFika\"); // For the help with the testing/debugging on Intel Iris Xe GPU in 2025\n    AddCredit(\"Bloxen\"); // For help testing and improving the 38A powerups (2025)\n    AddCredit(\"Liebning\"); // For careful bugtesting in 2025\n    AddCredit(\"55jedat555\"); // For long-term research support regarding SMBX 1.3 bugs, from ~2023 until at least 2025\n    // --- P.S. Put all next lines above THIS comment line. ---\n#endif\n    AddCredit(\"\");\n    AddCredit(\"4matsy\");\n    AddCredit(\"AndyDark\");\n    AddCredit(\"Bikcmp\");\n    AddCredit(\"Blue\");\n    AddCredit(\"Captain Obvious\");\n    AddCredit(\"CaptainTrek\");\n    AddCredit(\"Chase\");\n    AddCredit(\"Coldwin\");\n    AddCredit(\"CrystalMike\");\n    AddCredit(\"DarkMatt\");\n    AddCredit(\"FallingSnow\");\n    AddCredit(\"Garro\");\n    AddCredit(\"Knuckles96\");\n    AddCredit(\"Kuribo\");\n    AddCredit(\"Kyasarin\");\n    AddCredit(\"Luminous\");\n    AddCredit(\"m4sterbr0s\");\n    AddCredit(\"NameUser\");\n    AddCredit(\"Namyrr\");\n    AddCredit(\"Qig\");\n    AddCredit(\"Quill\");\n    AddCredit(\"Red_Yoshi\");\n    AddCredit(\"Spitfire\");\n    AddCredit(\"Valtteri\");\n    AddCredit(\"Vandarx\");\n    AddCredit(\"Zephyr\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n#ifndef ENABLE_OLD_CREDITS\n    AddCredit(\"'It just works!'\");\n    AddCredit(\"\");\n    AddCredit(\"Todd Howard\");\n#else\n    AddCredit(\"'He has delivered us from the power\");\n    AddCredit(\"of darkness and conveyed us into\");\n    AddCredit(\"the kingdom of the Son of His love.'\");\n    AddCredit(\"\");\n    AddCredit(\"Colossians 1:13\");\n#endif\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(\"\");\n    AddCredit(g_gameInfo.creditsHomePage);\n\n    // CreditChop and CreditOffsetY were previously floats, now they are ints measured in tenths\n    CreditChop = (XRender::TargetH / 2) * 10; // 100\n    EndCredits = 0;\n    CreditOffsetY = (XRender::TargetH + 40) * 10;\n    CreditTotalHeight = 32;\n\n    for(A = 1; A <= numCredits; A++)\n    {\n        auto &cr = Credit[A];\n        cr.Location.Width = SuperTextPixLen(GetS(cr.Text), g_gameInfo.creditsFont);\n        cr.Location.Height = 16;\n        cr.Location.X = (XRender::TargetW / 2) - (cr.Location.Width / 2);\n        cr.Location.Y = 32 * A;\n        CreditTotalHeight += 32;\n    }\n}\n"
  },
  {
    "path": "src/main/outro_loop.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef OUTRO_LOOP_H\n#define OUTRO_LOOP_H\n\n#include <string>\n\nstruct OutroContent\n{\n    std::string gameCredits;\n    std::string engineCredits;\n\n    std::string originalBy;\n    std::string nameAndrewSpinks;\n\n    std::string cppPortDevelopers;\n    std::string nameVitalyNovichkov;\n\n    std::string qualityControl;\n\n    std::string psVitaPortBy;\n\n    std::string levelDesign;\n\n    std::string customSprites;\n\n    std::string specialThanks;\n};\n\nextern OutroContent g_outroScreen;\n\nvoid initOutroContent();\n\n#endif // OUTRO_LOOP_H\n"
  },
  {
    "path": "src/main/player_frames.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../globals.h\"\n#include \"../game_main.h\"\n\n\nvoid SetupPlayerFrames()\n{\n    For(A, 1, maxPlayerFrames)\n    {\n        MarioFrameX[A] = 0;\n        MarioFrameY[A] = 0;\n        LuigiFrameX[A] = 0;\n        LuigiFrameY[A] = 0;\n        PeachFrameX[A] = 0;\n        PeachFrameY[A] = 0;\n        ToadFrameX[A] = 0;\n        ToadFrameY[A] = 0;\n    }\n\n    LinkFrameY[101] = -8;\n    LinkFrameY[102] = -8;\n    LinkFrameY[103] = -8;\n    LinkFrameY[104] = -6;\n    LinkFrameY[105] = -8;\n    LinkFrameY[106] = -8;\n    LinkFrameY[107] = -4;\n    LinkFrameY[108] = -6;\n    LinkFrameY[109] = -4;\n    LinkFrameY[110] = -16;\n    LinkFrameY[111] = -2;\n    LinkFrameY[99] = -8;\n    LinkFrameY[98] = -8;\n    LinkFrameY[97] = -8;\n    LinkFrameY[96] = -6;\n    LinkFrameY[95] = -8;\n    LinkFrameY[94] = -8;\n    LinkFrameY[93] = -4;\n    LinkFrameY[92] = -6;\n    LinkFrameY[91] = -4;\n    LinkFrameY[90] = -16;\n    LinkFrameY[89] = -2;\n    For(A, 51, 149)\n    {\n        LinkFrameX[A] = -4;\n    }\n    LinkFrameX[106] = -18;\n    LinkFrameX[93] = -36;\n    LinkFrameX[92] = -36;\n\n    For(A, 51, maxPlayerFrames - 101) // 649\n    {\n        LinkFrameX[A + 100] = LinkFrameX[A];\n        LinkFrameY[A + 100] = LinkFrameY[A];\n    }\n\n        LinkFrameX[512] = -4;\n        LinkFrameY[512] = -8;\n        LinkFrameX[488] = -4;\n        LinkFrameY[488] = -8;\n\n    For(A, 51, 149)\n    {\n        ToadFrameY[A] = -6;\n        ToadFrameX[A] = -2;\n    }\n    ToadFrameY[107] = -2;\n    ToadFrameY[127] = -2;\n\n    ToadFrameX[127] = -4;\n    ToadFrameX[73] = -4;\n\n    ToadFrameX[115] = -4;\n    ToadFrameX[85] = -4;\n    ToadFrameY[93] = -2;\n    ToadFrameY[73] = -2;\n    ToadFrameY[122] = -20;\n    ToadFrameY[123] = -12;\n\n    ToadFrameY[78] = -20;\n    ToadFrameY[77] = -12;\n\n    For(A, 151, 349)\n    {\n        ToadFrameY[A] = 2;\n        ToadFrameX[A] = -4;\n    }\n\n    For(A, 151, 349)\n    {\n        ToadFrameY[A] -= 4;\n    }\n\n    ToadFrameY[227] = 0;\n    ToadFrameY[207] = 0;\n    ToadFrameY[173] = 0;\n    ToadFrameY[193] = 0;\n    ToadFrameY[327] = 0;\n    ToadFrameY[307] = 0;\n    ToadFrameY[273] = 0;\n    ToadFrameY[293] = 0;\n    ToadFrameY[322] = -22;\n    ToadFrameY[278] = -22;\n    ToadFrameY[222] = -22;\n    ToadFrameY[178] = -22;\n    ToadFrameY[323] = -16;\n    ToadFrameY[277] = -16;\n    ToadFrameY[223] = -16;\n    ToadFrameY[177] = -16;\n\n    ToadFrameX[294] = -4;\n    ToadFrameX[194] = -4;\n    ToadFrameX[306] = -6;\n    ToadFrameX[206] = -6;\n    ToadFrameX[106] = -4;\n\n    PeachFrameY[101] = -4;\n    PeachFrameY[102] = -4;\n    PeachFrameY[103] = -4;\n    PeachFrameY[104] = -4;\n    PeachFrameY[108] = -4;\n    PeachFrameY[109] = -4;\n    PeachFrameY[110] = -4;\n    PeachFrameY[127] = -2;\n    PeachFrameY[122] = -16;\n    PeachFrameY[78] = -16;\n    PeachFrameY[99] = -4;\n    PeachFrameY[98] = -4;\n    PeachFrameY[97] = -4;\n    PeachFrameY[96] = -4;\n    PeachFrameY[92] = -4;\n    PeachFrameY[91] = -4;\n    PeachFrameY[90] = -4;\n    PeachFrameY[73] = -2;\n    PeachFrameX[101] = -2;\n    PeachFrameX[102] = -2;\n    PeachFrameX[103] = -2;\n    PeachFrameX[104] = -2;\n    PeachFrameX[108] = -2;\n    PeachFrameX[109] = -2;\n    PeachFrameX[110] = -2;\n    PeachFrameX[107] = -2;\n    PeachFrameX[93] = -2;\n    PeachFrameX[127] = -4;\n    PeachFrameX[99] = 0;\n    PeachFrameX[98] = 0;\n    PeachFrameX[97] = 0;\n    PeachFrameX[96] = 0;\n    PeachFrameX[92] = 0;\n    PeachFrameX[91] = 0;\n    PeachFrameX[90] = 0;\n    PeachFrameX[73] = -4;\n\n    PeachFrameY[201] = -2;\n    PeachFrameY[202] = -2;\n    PeachFrameY[203] = -2;\n    PeachFrameY[204] = -2;\n    PeachFrameY[208] = -2;\n    PeachFrameY[209] = -2;\n    PeachFrameY[210] = -2;\n    PeachFrameY[215] = -2;\n    PeachFrameY[185] = -2;\n    PeachFrameY[222] = -30;\n    PeachFrameY[178] = -30;\n    PeachFrameY[199] = -2;\n    PeachFrameY[198] = -2;\n    PeachFrameY[197] = -2;\n    PeachFrameY[196] = -2;\n    PeachFrameY[192] = -2;\n    PeachFrameY[191] = -2;\n    PeachFrameY[190] = -2;\n    PeachFrameX[201] = -4;\n    PeachFrameX[202] = -4;\n    PeachFrameX[203] = -4;\n    PeachFrameX[204] = -4;\n    PeachFrameX[207] = -4;\n    PeachFrameX[208] = -4;\n    PeachFrameX[209] = -4;\n    PeachFrameX[210] = -4;\n    PeachFrameX[215] = -4;\n    PeachFrameX[185] = -4;\n    PeachFrameX[227] = -4;\n    PeachFrameX[199] = -4;\n    PeachFrameX[198] = -4;\n    PeachFrameX[197] = -4;\n    PeachFrameX[196] = -4;\n    PeachFrameX[193] = -4;\n    PeachFrameX[192] = -4;\n    PeachFrameX[191] = -4;\n    PeachFrameX[190] = -4;\n    PeachFrameX[173] = -4;\n    PeachFrameY[223] = -30;\n    PeachFrameY[177] = -30;\n    PeachFrameY[206] = -2;\n    PeachFrameY[194] = -2;\n    PeachFrameX[195] = PeachFrameX[196];\n    PeachFrameY[195] = PeachFrameY[196];\n    PeachFrameX[205] = PeachFrameX[204];\n    PeachFrameY[205] = PeachFrameY[204];\n    PeachFrameX[206] = -8;\n    PeachFrameX[194] = 0;\n    PeachFrameX[213] = -4;\n    PeachFrameX[187] = -4;\n    PeachFrameY[213] = -2;\n    PeachFrameY[187] = -2;\n    PeachFrameY[123] = -16;\n    PeachFrameY[77] = -16;\n    PeachFrameY[122] = -2;\n    PeachFrameY[78] = -22;\n    PeachFrameY[106] = -4;\n    PeachFrameY[94] = -4;\n    PeachFrameX[95] = PeachFrameX[96];\n    PeachFrameY[95] = PeachFrameY[96];\n    PeachFrameX[105] = PeachFrameX[104];\n    PeachFrameY[105] = PeachFrameY[104];\n    PeachFrameX[106] = -4;\n    PeachFrameX[94] = 0;\n    PeachFrameX[113] = -2;\n    PeachFrameX[115] = -2;\n    PeachFrameX[85] = -2;\n    PeachFrameY[115] = -4;\n    PeachFrameY[85] = -4;\n    PeachFrameX[87] = -2;\n    PeachFrameY[113] = -4;\n    PeachFrameY[87] = -4;\n    PeachFrameY[75] = -2;\n    PeachFrameY[74] = -2;\n    PeachFrameY[125] = -2;\n    PeachFrameY[126] = -2;\n    PeachFrameY[123] = -16;\n    PeachFrameY[122] = -22;\n    For(A, 151, 249)\n    {\n        PeachFrameX[A + 100] = PeachFrameX[A];\n        PeachFrameY[A + 100] = PeachFrameY[A];\n    }\n    PeachFrameX[312] = PeachFrameX[310];\n    PeachFrameX[311] = PeachFrameX[310];\n    PeachFrameX[288] = PeachFrameX[290];\n    PeachFrameX[289] = PeachFrameX[290];\n    PeachFrameY[312] = PeachFrameY[310];\n    PeachFrameY[311] = PeachFrameY[310];\n    PeachFrameY[288] = PeachFrameY[290];\n    PeachFrameY[289] = PeachFrameY[290];\n\n\n\n\n    MarioFrameY[96] = 0;\n    MarioFrameX[97] = -4;\n    MarioFrameX[98] = -2;\n    MarioFrameY[98] = 0;\n    MarioFrameY[99] = 2;\n    MarioFrameY[101] = 2;\n    MarioFrameX[102] = -4;\n    MarioFrameY[102] = 0;\n    MarioFrameX[103] = -4;\n    MarioFrameY[104] = 0;\n    MarioFrameX[105] = -2;\n    MarioFrameX[106] = -4;\n    MarioFrameY[190] = 2;\n    MarioFrameY[191] = 2;\n    MarioFrameY[192] = 2;\n    MarioFrameY[193] = -4;\n    MarioFrameX[194] = -2;\n    MarioFrameY[194] = 0;\n    MarioFrameX[195] = -2;\n    MarioFrameX[196] = -2;\n    MarioFrameX[197] = -2;\n    MarioFrameY[197] = 2;\n    MarioFrameX[198] = -2;\n    MarioFrameY[198] = 2;\n    MarioFrameY[199] = 2;\n    MarioFrameY[201] = 2;\n    MarioFrameY[202] = 2;\n    MarioFrameX[202] = -2;\n    MarioFrameY[203] = 2;\n    MarioFrameX[203] = -2;\n    MarioFrameX[204] = -2;\n    MarioFrameX[205] = -2;\n    MarioFrameX[206] = -2;\n    MarioFrameY[206] = 0;\n    MarioFrameY[207] = -4;\n    MarioFrameY[208] = 2;\n    MarioFrameY[209] = 2;\n    MarioFrameY[210] = 2;\n    MarioFrameX[209] = -2;\n    MarioFrameX[210] = -4;\n    MarioFrameY[288] = 2;\n    MarioFrameY[289] = 2;\n    MarioFrameY[290] = 2;\n    MarioFrameY[291] = 2;\n    MarioFrameY[292] = 2;\n    MarioFrameY[293] = -4;\n    MarioFrameX[294] = -2;\n    MarioFrameY[294] = 0;\n    MarioFrameX[295] = -2;\n    MarioFrameX[296] = -2;\n    MarioFrameX[297] = -2;\n    MarioFrameY[297] = 2;\n    MarioFrameX[298] = -2;\n    MarioFrameY[298] = 2;\n    MarioFrameY[299] = 2;\n    MarioFrameY[301] = 2;\n    MarioFrameY[302] = 2;\n    MarioFrameX[302] = -2;\n    MarioFrameY[303] = 2;\n    MarioFrameX[303] = -2;\n    MarioFrameX[304] = -2;\n    MarioFrameX[305] = -2;\n    MarioFrameX[306] = -2;\n    MarioFrameY[306] = 0;\n    MarioFrameY[307] = -4;\n    MarioFrameY[308] = 2;\n    MarioFrameY[309] = 2;\n    MarioFrameY[310] = 2;\n    MarioFrameX[309] = -2;\n    MarioFrameX[310] = -4;\n    MarioFrameY[311] = 2;\n    MarioFrameY[312] = 2;\n    MarioFrameX[382] = -4;\n    MarioFrameX[383] = -4;\n    MarioFrameX[384] = -4;\n    MarioFrameY[385] = -2;\n    MarioFrameX[386] = -14;\n    MarioFrameX[389] = -2;\n    MarioFrameY[393] = -4;\n    MarioFrameX[394] = -2;\n    MarioFrameY[394] = -4;\n    MarioFrameX[395] = -2;\n    MarioFrameX[396] = -2;\n    MarioFrameX[397] = -2;\n    MarioFrameX[398] = -2;\n    MarioFrameX[401] = -14;\n    MarioFrameX[402] = -14;\n    MarioFrameX[403] = -16;\n    MarioFrameX[404] = -16;\n    MarioFrameX[405] = -16;\n    MarioFrameY[406] = -4;\n    MarioFrameX[407] = -14;\n    MarioFrameY[407] = -4;\n    MarioFrameX[408] = -14;\n    MarioFrameX[409] = -16;\n    MarioFrameX[410] = -18;\n    MarioFrameX[411] = -14;\n    MarioFrameX[412] = -18;\n    MarioFrameX[413] = -4;\n    MarioFrameX[414] = -4;\n    MarioFrameX[415] = -4;\n    MarioFrameY[415] = -2;\n    MarioFrameX[416] = -16;\n    MarioFrameX[417] = -16;\n    MarioFrameX[418] = -16;\n    MarioFrameX[419] = -16;\n    MarioFrameX[420] = -16;\n    MarioFrameX[421] = -16;\n    LuigiFrameY[96] = 0;\n    LuigiFrameX[97] = -4;\n    LuigiFrameX[98] = -2;\n    LuigiFrameY[98] = 0;\n    LuigiFrameY[99] = 2;\n    LuigiFrameY[101] = 2;\n    LuigiFrameX[102] = -4;\n    LuigiFrameY[102] = 0;\n    LuigiFrameX[103] = -4;\n    LuigiFrameY[104] = 0;\n    LuigiFrameX[105] = -2;\n    LuigiFrameX[106] = -4;\n    LuigiFrameY[190] = 2;\n    LuigiFrameY[191] = 2;\n    LuigiFrameY[192] = 2;\n    LuigiFrameY[193] = -6;\n    LuigiFrameX[194] = -2;\n    LuigiFrameY[194] = 0;\n    LuigiFrameX[195] = -2;\n    LuigiFrameX[196] = -2;\n    LuigiFrameX[197] = -2;\n    LuigiFrameY[197] = 2;\n    LuigiFrameX[198] = -2;\n    LuigiFrameY[198] = 2;\n    LuigiFrameY[199] = 2;\n    LuigiFrameY[201] = 2;\n    LuigiFrameY[202] = 2;\n    LuigiFrameX[202] = -2;\n    LuigiFrameY[203] = 2;\n    LuigiFrameX[203] = -2;\n    LuigiFrameX[204] = -2;\n    LuigiFrameX[205] = -2;\n    LuigiFrameX[206] = -2;\n    LuigiFrameY[206] = 0;\n    LuigiFrameY[207] = -6;\n    LuigiFrameY[208] = 2;\n    LuigiFrameY[209] = 2;\n    LuigiFrameY[210] = 2;\n    LuigiFrameX[209] = -2;\n    LuigiFrameX[210] = -4;\n    LuigiFrameY[288] = 2;\n    LuigiFrameY[289] = 2;\n    LuigiFrameY[290] = 2;\n    LuigiFrameY[291] = 2;\n    LuigiFrameY[292] = 2;\n    LuigiFrameY[293] = -6;\n    LuigiFrameX[294] = -2;\n    LuigiFrameY[294] = 0;\n    LuigiFrameX[295] = -2;\n    LuigiFrameX[296] = -2;\n    LuigiFrameX[297] = -2;\n    LuigiFrameY[297] = 2;\n    LuigiFrameX[298] = -2;\n    LuigiFrameY[298] = 2;\n    LuigiFrameY[299] = 2;\n    LuigiFrameY[301] = 2;\n    LuigiFrameY[302] = 2;\n    LuigiFrameX[302] = -2;\n    LuigiFrameY[303] = 2;\n    LuigiFrameX[303] = -2;\n    LuigiFrameX[304] = -2;\n    LuigiFrameX[305] = -2;\n    LuigiFrameX[306] = -2;\n    LuigiFrameY[306] = 0;\n    LuigiFrameY[307] = -6;\n    LuigiFrameY[308] = 2;\n    LuigiFrameY[309] = 2;\n    LuigiFrameY[310] = 2;\n    LuigiFrameX[309] = -2;\n    LuigiFrameX[310] = -4;\n    LuigiFrameY[311] = 2;\n    LuigiFrameY[312] = 2;\n    LuigiFrameX[382] = -4;\n    LuigiFrameX[383] = -4;\n    LuigiFrameX[384] = -2;\n    LuigiFrameX[386] = -14;\n    LuigiFrameX[389] = -2;\n    LuigiFrameY[393] = -4;\n    LuigiFrameX[394] = -2;\n    LuigiFrameY[394] = 0;\n    LuigiFrameX[395] = -2;\n    LuigiFrameX[396] = -2;\n    LuigiFrameX[397] = -2;\n    LuigiFrameX[398] = -2;\n    LuigiFrameX[401] = -14;\n    LuigiFrameX[402] = -14;\n    LuigiFrameX[403] = -16;\n    LuigiFrameX[404] = -16;\n    LuigiFrameX[405] = -16;\n    LuigiFrameY[406] = 0;\n    LuigiFrameX[407] = -14;\n    LuigiFrameY[407] = -4;\n    LuigiFrameX[408] = -14;\n    LuigiFrameX[409] = -16;\n    LuigiFrameX[410] = -18;\n    LuigiFrameX[411] = -14;\n    LuigiFrameX[412] = -18;\n    LuigiFrameX[413] = -4;\n    LuigiFrameX[414] = -4;\n    LuigiFrameX[415] = -4;\n    LuigiFrameX[416] = -16;\n    LuigiFrameX[417] = -16;\n    LuigiFrameX[418] = -16;\n    LuigiFrameX[419] = -16;\n    LuigiFrameX[420] = -18;\n    LuigiFrameX[421] = -16;\n    For(A, 150, maxPlayerFrames) // Adjust the players frames to their new sizes\n    {\n        MarioFrameX[A] -= 2;\n        LuigiFrameX[A] -= 2;\n        LuigiFrameY[A] -= 2;\n    }\n    LuigiFrameY[101] -= 2;\n    LuigiFrameY[102] -= 2;\n    LuigiFrameY[105] -= 2;\n    LuigiFrameY[106] -= 2;\n    LuigiFrameY[99] -= 2;\n    LuigiFrameY[98] -= 2;\n    LuigiFrameY[95] -= 2;\n    LuigiFrameY[94] -= 2;\n// Yoshi Frames\n    MarioFrameX[130] = -2 - 4;\n    MarioFrameX[70] = 6 - 4;\n    MarioFrameY[130] = 18;\n    MarioFrameY[70] = 18;\n    MarioFrameX[230] = -4 - 4;\n    MarioFrameX[170] = 4 - 4;\n    MarioFrameX[330] = -4 - 4;\n    MarioFrameX[270] = 4 - 4;\n    MarioFrameX[430] = -20 - 4;\n    MarioFrameX[370] = 4 - 4;\n    MarioFrameY[430] = -2;\n    MarioFrameY[370] = -2;\n    LuigiFrameX[130] = -2 - 4;\n    LuigiFrameX[70] = 6 - 4;\n    LuigiFrameY[130] = 16;\n    LuigiFrameY[70] = 16;\n    LuigiFrameY[230] = -4;\n    LuigiFrameY[170] = -4;\n    LuigiFrameY[330] = -4;\n    LuigiFrameY[270] = -4;\n    LuigiFrameY[430] = -6;\n    LuigiFrameY[370] = -6;\n    LuigiFrameX[230] = -6 - 4;\n    LuigiFrameX[170] = 4 - 4;\n    LuigiFrameX[330] = -6 - 4;\n    LuigiFrameX[270] = 4 - 4;\n    LuigiFrameX[430] = -20 - 4;\n    LuigiFrameX[370] = 4 - 4;\n    MarioFrameX[131] = -2 - 2;\n    MarioFrameX[69] = 6 - 4;\n    MarioFrameY[131] = 22;\n    MarioFrameY[69] = 22;\n    MarioFrameX[231] = -4 - 3;\n    MarioFrameX[169] = 4 - 6;\n    MarioFrameY[231] = 8;\n    MarioFrameY[169] = 8;\n    MarioFrameX[331] = -4 - 2;\n    MarioFrameX[269] = 4 - 6;\n    MarioFrameY[331] = 8;\n    MarioFrameY[269] = 8;\n    MarioFrameX[431] = -20;\n    MarioFrameX[369] = 4 - 6;\n    MarioFrameY[431] = 6;\n    MarioFrameY[369] = 6;\n    LuigiFrameX[131] = -2 - 2;\n    LuigiFrameX[69] = 6 - 4;\n    LuigiFrameY[131] = 22;\n    LuigiFrameY[69] = 22;\n    LuigiFrameX[231] = -6;\n    LuigiFrameX[169] = 4 - 6;\n    LuigiFrameY[231] = 4;\n    LuigiFrameY[169] = 4;\n    LuigiFrameX[331] = -6;\n    LuigiFrameX[269] = 4 - 6;\n    LuigiFrameY[331] = 4;\n    LuigiFrameY[269] = 4;\n    LuigiFrameX[431] = -20;\n    LuigiFrameX[369] = 4 - 6;\n    LuigiFrameY[431] = 2;\n    LuigiFrameY[369] = 2;\n// Facing Frames\n    MarioFrameX[113] = -2;\n    MarioFrameY[113] = 2;\n    MarioFrameX[115] = -2;\n    MarioFrameY[115] = 2;\n    MarioFrameX[87] = MarioFrameX[113];\n    MarioFrameY[87] = MarioFrameY[113];\n    MarioFrameX[85] = MarioFrameX[115];\n    MarioFrameY[85] = MarioFrameY[115];\n    MarioFrameX[213] = -4;\n    MarioFrameY[213] = 2;\n    MarioFrameX[215] = -4;\n    MarioFrameY[215] = 2;\n    MarioFrameX[187] = MarioFrameX[213];\n    MarioFrameY[187] = MarioFrameY[213];\n    MarioFrameX[185] = MarioFrameX[215];\n    MarioFrameY[185] = MarioFrameY[215];\n    MarioFrameX[313] = -4;\n    MarioFrameY[313] = 2;\n    MarioFrameX[315] = -4;\n    MarioFrameY[315] = 2;\n    MarioFrameX[287] = MarioFrameX[313];\n    MarioFrameY[287] = MarioFrameY[313];\n    MarioFrameX[285] = MarioFrameX[315];\n    MarioFrameY[285] = MarioFrameY[315];\n    MarioFrameX[413] = -4;\n    MarioFrameY[413] = 0;\n    MarioFrameX[415] = -4;\n    MarioFrameY[415] = -2;\n    MarioFrameX[387] = MarioFrameX[413];\n    MarioFrameY[387] = MarioFrameY[413];\n    MarioFrameX[385] = MarioFrameX[415];\n    MarioFrameY[385] = MarioFrameY[415];\n    LuigiFrameX[113] = -2;\n    LuigiFrameY[113] = 2;\n    LuigiFrameX[115] = -2;\n    LuigiFrameY[115] = 2;\n    LuigiFrameX[87] = LuigiFrameX[113];\n    LuigiFrameY[87] = LuigiFrameY[113];\n    LuigiFrameX[85] = LuigiFrameX[115];\n    LuigiFrameY[85] = LuigiFrameY[115];\n    LuigiFrameX[213] = -4;\n    LuigiFrameY[213] = 2;\n    LuigiFrameX[215] = -4;\n    LuigiFrameY[215] = 2;\n    LuigiFrameX[187] = LuigiFrameX[213];\n    LuigiFrameY[187] = LuigiFrameY[213];\n    LuigiFrameX[185] = LuigiFrameX[215];\n    LuigiFrameY[185] = LuigiFrameY[215];\n    LuigiFrameX[313] = -4;\n    LuigiFrameY[313] = 2;\n    LuigiFrameX[315] = -4;\n    LuigiFrameY[315] = 2;\n    LuigiFrameX[287] = LuigiFrameX[313];\n    LuigiFrameY[287] = LuigiFrameY[313];\n    LuigiFrameX[285] = LuigiFrameX[315];\n    LuigiFrameY[285] = LuigiFrameY[315];\n    LuigiFrameX[413] = -4;\n    LuigiFrameY[413] = 0;\n    LuigiFrameX[415] = -4;\n    LuigiFrameY[415] = -2;\n    LuigiFrameX[387] = LuigiFrameX[413];\n    LuigiFrameY[387] = LuigiFrameY[413];\n    LuigiFrameX[385] = LuigiFrameX[415];\n    LuigiFrameY[385] = LuigiFrameY[415];\n    LuigiFrameX[113] = 0; // -2\n    LuigiFrameY[113] = 0;\n    LuigiFrameX[115] = 0; // -2\n    LuigiFrameY[115] = 0;\n    LuigiFrameX[87] = LuigiFrameX[113];\n    LuigiFrameY[87] = LuigiFrameY[113];\n    LuigiFrameX[85] = LuigiFrameX[115];\n    LuigiFrameY[85] = LuigiFrameY[115];\n    LuigiFrameX[213] = -4;\n    LuigiFrameY[213] = 0;\n    LuigiFrameX[215] = -4;\n    LuigiFrameY[215] = 0;\n    LuigiFrameX[187] = LuigiFrameX[213];\n    LuigiFrameY[187] = LuigiFrameY[213];\n    LuigiFrameX[185] = LuigiFrameX[215];\n    LuigiFrameY[185] = LuigiFrameY[215];\n    LuigiFrameX[313] = -4;\n    LuigiFrameY[313] = 0;\n    LuigiFrameX[315] = -4;\n    LuigiFrameY[315] = 0;\n    LuigiFrameX[287] = LuigiFrameX[313];\n    LuigiFrameY[287] = LuigiFrameY[313];\n    LuigiFrameX[285] = LuigiFrameX[315];\n    LuigiFrameY[285] = LuigiFrameY[315];\n    LuigiFrameX[413] = -4;\n    LuigiFrameY[413] = -2;\n    LuigiFrameX[415] = -4;\n    LuigiFrameY[415] = -2;\n    LuigiFrameX[387] = LuigiFrameX[413];\n    LuigiFrameY[387] = LuigiFrameY[413];\n    LuigiFrameX[385] = LuigiFrameX[415];\n    LuigiFrameY[385] = LuigiFrameY[415];\n    For(A, 450, 550)\n    {\n        MarioFrameX[A] = MarioFrameX[A - 100];\n        MarioFrameY[A] = MarioFrameY[A - 100];\n        LuigiFrameX[A] = LuigiFrameX[A - 100];\n        LuigiFrameY[A] = LuigiFrameY[A - 100];\n        MarioFrameX[A + 100] = MarioFrameX[A - 200];\n        MarioFrameY[A + 100] = MarioFrameY[A - 200];\n        LuigiFrameX[A + 100] = LuigiFrameX[A - 200];\n        LuigiFrameY[A + 100] = LuigiFrameY[A - 200];\n    }\n    MarioFrameY[508] -= 2;\n    MarioFrameY[509] -= 2;\n    MarioFrameY[510] -= 2;\n    MarioFrameY[492] -= 2;\n    MarioFrameY[491] -= 2;\n    MarioFrameY[490] -= 2;\n    MarioFrameY[501] = -2;\n    MarioFrameY[499] = -2;\n    MarioFrameY[502] = -2;\n    MarioFrameY[498] = -2;\n    MarioFrameY[503] = 0;\n    MarioFrameY[497] = 0;\n    MarioFrameY[512] = -2;\n    MarioFrameY[513] = -2;\n    MarioFrameY[514] = -2;\n    MarioFrameY[515] = 0;\n    MarioFrameY[488] = -2;\n    MarioFrameY[487] = -2;\n    MarioFrameY[486] = -2;\n    MarioFrameY[485] = 0;\n    MarioFrameX[513] = -6;\n    MarioFrameX[514] = -6;\n    MarioFrameX[515] = -6;\n    MarioFrameX[485] = -2;\n    MarioFrameX[487] = -2;\n    MarioFrameX[518] = -16;\n    MarioFrameX[517] = -16;\n    MarioFrameX[516] = -16;\n    MarioFrameY[530] -= 2;\n    MarioFrameY[531] -= 2;\n    MarioFrameY[470] -= 2;\n    MarioFrameY[569] = MarioFrameY[469] - 2;\n    MarioFrameY[500] = -2;\n    MarioFrameX[500] = -4;\n    LuigiFrameY[500] = 4;\n    LuigiFrameX[500] = -4;\n    MarioFrameX[513] = -4;\n    MarioFrameX[487] = -4;\n    MarioFrameX[601] = -6;\n    MarioFrameY[601] = 0;\n    MarioFrameY[599] = 0;\n    MarioFrameY[602] = 0;\n    MarioFrameY[603] = 0;\n    MarioFrameY[598] = 0;\n    MarioFrameY[597] = 0;\n    MarioFrameY[604] = -2;\n    MarioFrameY[605] = -2;\n    MarioFrameY[596] = -2;\n    MarioFrameY[595] = -2;\n    MarioFrameY[613] = -2;\n    MarioFrameY[587] = -2;\n    MarioFrameY[615] = 0;\n    MarioFrameY[585] = 0;\n    MarioFrameX[608] = -6;\n    MarioFrameY[608] = 0;\n    MarioFrameY[592] = 0;\n    MarioFrameX[609] = -6;\n    MarioFrameY[609] = 0;\n    MarioFrameY[591] = 0;\n    MarioFrameY[610] = 0;\n    MarioFrameY[590] = 0;\n    MarioFrameY[611] = 0;\n    MarioFrameX[611] = -6;\n    MarioFrameY[589] = 0;\n    MarioFrameY[612] = 0;\n    MarioFrameX[612] = -6;\n    MarioFrameY[588] = 0;\n    MarioFrameY[613] = 0;\n    MarioFrameY[587] = 0;\n    MarioFrameY[630] = -2;\n    MarioFrameY[570] = -2;\n    MarioFrameY[631] = 6;\n    MarioFrameY[569] = 6;\n    MarioFrameY[469] = 4;\n    MarioFrameY[607] = 0;\n    MarioFrameY[593] = 0;\n    MarioFrameX[593] = -6;\n    LuigiFrameX[601] = -6;\n    LuigiFrameX[608] = -6;\n    LuigiFrameX[609] = -6;\n    LuigiFrameX[611] = -6;\n    LuigiFrameX[612] = -6;\n    LuigiFrameX[630] = -12;\n    LuigiFrameY[607] = 0;\n    LuigiFrameY[593] = 0;\n\n    MarioFrameX[225] = -4;\n    MarioFrameX[226] = -4;\n    MarioFrameX[175] = -4;\n    MarioFrameX[174] = -4;\n    MarioFrameX[325] = -4;\n    MarioFrameX[326] = -4;\n    MarioFrameX[275] = -4;\n    MarioFrameX[274] = -4;\n    MarioFrameX[425] = -4;\n    MarioFrameX[426] = -4;\n    MarioFrameX[375] = -4;\n    MarioFrameX[374] = -4;\n    MarioFrameX[525] = -4;\n    MarioFrameX[526] = -4;\n    MarioFrameX[475] = -4;\n    MarioFrameX[474] = -4;\n    MarioFrameX[625] = -4;\n    MarioFrameX[626] = -4;\n    MarioFrameX[575] = -4;\n    MarioFrameX[574] = -4;\n    MarioFrameX[125] = -2;\n    MarioFrameX[126] = -2;\n    MarioFrameX[75] = -2;\n    MarioFrameX[74] = -2;\n    LuigiFrameX[225] = -4;\n    LuigiFrameX[226] = -4;\n    LuigiFrameX[175] = -4;\n    LuigiFrameX[174] = -4;\n    LuigiFrameX[325] = -4;\n    LuigiFrameX[326] = -4;\n    LuigiFrameX[275] = -4;\n    LuigiFrameX[274] = -4;\n    LuigiFrameX[425] = -4;\n    LuigiFrameX[426] = -4;\n    LuigiFrameX[375] = -4;\n    LuigiFrameX[374] = -4;\n    LuigiFrameX[525] = -4;\n    LuigiFrameX[526] = -4;\n    LuigiFrameX[475] = -4;\n    LuigiFrameX[474] = -4;\n    LuigiFrameX[625] = -4;\n    LuigiFrameX[626] = -4;\n    LuigiFrameX[575] = -4;\n    LuigiFrameX[574] = -4;\n\n    LuigiFrameX[124] = -4;\n    LuigiFrameX[76] = -4;\n    LuigiFrameX[224] = -4;\n    LuigiFrameX[176] = -4;\n    LuigiFrameX[324] = -4;\n    LuigiFrameX[276] = -4;\n    LuigiFrameX[424] = -4;\n    LuigiFrameX[376] = -4;\n    LuigiFrameX[524] = -4;\n    LuigiFrameX[476] = -4;\n    LuigiFrameX[624] = -4;\n    LuigiFrameX[576] = -4;\n\n    LuigiFrameY[224] = 2;\n    LuigiFrameY[176] = 2;\n    LuigiFrameY[324] = 2;\n    LuigiFrameY[276] = 2;\n    LuigiFrameY[424] = 0;\n    LuigiFrameY[376] = 0;\n    LuigiFrameY[524] = 0;\n    LuigiFrameY[476] = 0;\n    LuigiFrameY[624] = 2;\n    LuigiFrameY[576] = 2;\n\n\n    MarioFrameX[124] = -4;\n    MarioFrameX[76] = -2;\n    MarioFrameX[224] = -4;\n    MarioFrameX[176] = -4;\n    MarioFrameX[324] = -4;\n    MarioFrameX[276] = -4;\n    MarioFrameX[424] = -4;\n    MarioFrameX[376] = -4;\n    MarioFrameX[524] = -4;\n    MarioFrameX[476] = -4;\n    MarioFrameX[624] = -4;\n    MarioFrameX[576] = -4;\n    MarioFrameY[224] = 4;\n    MarioFrameY[176] = 4;\n    MarioFrameY[324] = 4;\n    MarioFrameY[276] = 4;\n    MarioFrameY[424] = 2;\n    MarioFrameY[376] = 2;\n    MarioFrameY[524] = 2;\n    MarioFrameY[476] = 2;\n    MarioFrameY[624] = 2;\n    MarioFrameY[576] = 2;\n\n\n\n    MarioFrameX[140] = -4;\n    MarioFrameY[140] = 2;\n    MarioFrameX[141] = -4;\n    MarioFrameY[141] = 2;\n    MarioFrameX[142] = -4;\n    MarioFrameY[142] = 2;\n    MarioFrameX[143] = -4;\n    MarioFrameY[143] = 2;\n    MarioFrameX[60] = -4;\n    MarioFrameY[60] = 2;\n    MarioFrameX[59] = -4;\n    MarioFrameY[59] = 2;\n    MarioFrameX[58] = 0;\n    MarioFrameY[58] = 2;\n    MarioFrameX[57] = 0;\n    MarioFrameY[57] = 2;\n    MarioFrameX[240] = -8;\n    MarioFrameY[240] = 2;\n    MarioFrameX[241] = -8;\n    MarioFrameY[241] = 2;\n    MarioFrameX[242] = -8;\n    MarioFrameY[242] = 2;\n    MarioFrameX[243] = -8;\n    MarioFrameY[243] = 2;\n    MarioFrameX[244] = -8;\n    MarioFrameY[244] = 2;\n    MarioFrameX[160] = -6;\n    MarioFrameY[160] = 2;\n    MarioFrameX[159] = -6;\n    MarioFrameY[159] = 2;\n    MarioFrameX[158] = -6;\n    MarioFrameY[158] = 2;\n    MarioFrameX[157] = -4;\n    MarioFrameY[157] = 2;\n    MarioFrameX[156] = -6;\n    MarioFrameY[156] = 2;\n    MarioFrameX[340] = -8;\n    MarioFrameY[340] = 2;\n    MarioFrameX[341] = -8;\n    MarioFrameY[341] = 2;\n    MarioFrameX[342] = -8;\n    MarioFrameY[342] = 2;\n    MarioFrameX[343] = -8;\n    MarioFrameY[343] = 2;\n    MarioFrameX[344] = -8;\n    MarioFrameY[344] = 2;\n    MarioFrameX[260] = -6;\n    MarioFrameY[260] = 2;\n    MarioFrameX[259] = -6;\n    MarioFrameY[259] = 2;\n    MarioFrameX[258] = -6;\n    MarioFrameY[258] = 2;\n    MarioFrameX[257] = -4;\n    MarioFrameY[257] = 2;\n    MarioFrameX[256] = -6;\n    MarioFrameY[256] = 2;\n    MarioFrameX[540] = -18;\n    MarioFrameY[540] = 0;\n    MarioFrameX[541] = -18;\n    MarioFrameY[541] = 0;\n    MarioFrameX[542] = -18;\n    MarioFrameY[542] = 0;\n    MarioFrameX[543] = -18;\n    MarioFrameY[543] = 0;\n    MarioFrameX[544] = -18;\n    MarioFrameY[544] = 0;\n    MarioFrameX[460] = -6;\n    MarioFrameY[460] = 0;\n    MarioFrameX[459] = -6;\n    MarioFrameY[459] = 0;\n    MarioFrameX[458] = -6;\n    MarioFrameY[458] = 0;\n    MarioFrameX[457] = -4;\n    MarioFrameY[457] = 0;\n    MarioFrameX[456] = -6;\n    MarioFrameY[456] = 0;\n    MarioFrameX[440] = -18;\n    MarioFrameY[440] = 0;\n    MarioFrameX[441] = -18;\n    MarioFrameY[441] = 0;\n    MarioFrameX[442] = -18;\n    MarioFrameY[442] = 0;\n    MarioFrameX[443] = -18;\n    MarioFrameY[443] = 0;\n    MarioFrameX[444] = -18;\n    MarioFrameY[444] = 0;\n    MarioFrameX[360] = -6;\n    MarioFrameY[360] = 0;\n    MarioFrameX[359] = -6;\n    MarioFrameY[359] = 0;\n    MarioFrameX[358] = -6;\n    MarioFrameY[358] = 0;\n    MarioFrameX[357] = -4;\n    MarioFrameY[357] = 0;\n    MarioFrameX[356] = -6;\n    MarioFrameY[356] = 0;\n    MarioFrameX[640] = -8;\n    MarioFrameY[640] = 0;\n    MarioFrameX[641] = -8;\n    MarioFrameY[641] = 0;\n    MarioFrameX[642] = -8;\n    MarioFrameY[642] = 0;\n    MarioFrameX[643] = -8;\n    MarioFrameY[643] = 0;\n    MarioFrameX[644] = -8;\n    MarioFrameY[644] = 0;\n    MarioFrameX[560] = -6;\n    MarioFrameY[560] = 0;\n    MarioFrameX[559] = -6;\n    MarioFrameY[559] = 0;\n    MarioFrameX[558] = -6;\n    MarioFrameY[558] = 0;\n    MarioFrameX[557] = -4;\n    MarioFrameY[557] = 0;\n    MarioFrameX[556] = -6;\n    MarioFrameY[556] = 0;\n\n\n    MarioFrameX[515] = -4;\n    MarioFrameX[485] = -4;\n\n\n    LuigiFrameX[640] = -10;\n    LuigiFrameY[640] = 0;\n    LuigiFrameX[641] = -10;\n    LuigiFrameY[641] = 0;\n    LuigiFrameX[642] = -10;\n    LuigiFrameY[642] = 0;\n    LuigiFrameX[643] = -10;\n    LuigiFrameY[643] = 0;\n    LuigiFrameX[644] = -10;\n    LuigiFrameY[644] = 0;\n\n    LuigiFrameX[560] = -4;\n    LuigiFrameY[560] = 0;\n    LuigiFrameX[559] = -4;\n    LuigiFrameY[559] = 0;\n    LuigiFrameX[558] = -4;\n    LuigiFrameY[558] = 0;\n    LuigiFrameX[557] = -2;\n    LuigiFrameY[557] = 0;\n    LuigiFrameX[556] = -2;\n    LuigiFrameY[556] = 0;\n\n\n    LuigiFrameX[540] = -20;\n    LuigiFrameY[540] = -2;\n    LuigiFrameX[541] = -20;\n    LuigiFrameY[541] = -2;\n    LuigiFrameX[542] = -20;\n    LuigiFrameY[542] = -2;\n    LuigiFrameX[543] = -20;\n    LuigiFrameY[543] = -2;\n    LuigiFrameX[544] = -20;\n    LuigiFrameY[544] = -2;\n    LuigiFrameX[460] = -4;\n    LuigiFrameY[460] = -2;\n    LuigiFrameX[459] = -4;\n    LuigiFrameY[459] = -2;\n    LuigiFrameX[458] = -4;\n    LuigiFrameY[458] = -2;\n    LuigiFrameX[457] = -2;\n    LuigiFrameY[457] = -2;\n    LuigiFrameX[456] = -2;\n    LuigiFrameY[456] = -2;\n\n\n\n    LuigiFrameX[140] = -4;\n    LuigiFrameY[140] = 0;\n    LuigiFrameX[141] = -4;\n    LuigiFrameY[141] = 0;\n    LuigiFrameX[142] = -4;\n    LuigiFrameY[142] = 0;\n    LuigiFrameX[143] = -4;\n    LuigiFrameY[143] = 0;\n    LuigiFrameX[60] = -6;\n    LuigiFrameY[60] = 0;\n    LuigiFrameX[59] = -6;\n    LuigiFrameY[59] = 0;\n    LuigiFrameX[58] = 0;\n    LuigiFrameY[58] = 0;\n    LuigiFrameX[57] = 0;\n    LuigiFrameY[57] = 0;\n\n    LuigiFrameX[240] = -10;\n    LuigiFrameY[240] = 0;\n    LuigiFrameX[241] = -10;\n    LuigiFrameY[241] = 0;\n    LuigiFrameX[242] = -10;\n    LuigiFrameY[242] = 0;\n    LuigiFrameX[243] = -10;\n    LuigiFrameY[243] = 0;\n    LuigiFrameX[244] = -10;\n    LuigiFrameY[244] = 0;\n    LuigiFrameX[160] = -4;\n    LuigiFrameY[160] = 0;\n    LuigiFrameX[159] = -4;\n    LuigiFrameY[159] = 0;\n    LuigiFrameX[158] = -4;\n    LuigiFrameY[158] = 0;\n    LuigiFrameX[157] = -2;\n    LuigiFrameY[157] = 0;\n    LuigiFrameX[156] = -2;\n    LuigiFrameY[156] = 0;\n\n    LuigiFrameX[340] = -10;\n    LuigiFrameY[340] = 0;\n    LuigiFrameX[341] = -10;\n    LuigiFrameY[341] = 0;\n    LuigiFrameX[342] = -10;\n    LuigiFrameY[342] = 0;\n    LuigiFrameX[343] = -10;\n    LuigiFrameY[343] = 0;\n    LuigiFrameX[344] = -10;\n    LuigiFrameY[344] = 0;\n    LuigiFrameX[260] = -4;\n    LuigiFrameY[260] = 0;\n    LuigiFrameX[259] = -4;\n    LuigiFrameY[259] = 0;\n    LuigiFrameX[258] = -4;\n    LuigiFrameY[258] = 0;\n    LuigiFrameX[257] = -2;\n    LuigiFrameY[257] = 0;\n    LuigiFrameX[256] = -2;\n    LuigiFrameY[256] = 0;\n\n\n    LuigiFrameX[440] = -20;\n    LuigiFrameY[440] = -2;\n    LuigiFrameX[441] = -20;\n    LuigiFrameY[441] = -2;\n    LuigiFrameX[442] = -20;\n    LuigiFrameY[442] = -2;\n    LuigiFrameX[443] = -20;\n    LuigiFrameY[443] = -2;\n    LuigiFrameX[444] = -20;\n    LuigiFrameY[444] = -2;\n    LuigiFrameX[360] = -4;\n    LuigiFrameY[360] = -2;\n    LuigiFrameX[359] = -4;\n    LuigiFrameY[359] = -2;\n    LuigiFrameX[358] = -4;\n    LuigiFrameY[358] = -2;\n    LuigiFrameX[357] = -2;\n    LuigiFrameY[357] = -2;\n    LuigiFrameX[356] = -2;\n    LuigiFrameY[356] = -2;\n\n    MarioFrameY[122] = -8;\n    MarioFrameY[123] = -6;\n    MarioFrameY[78] = -8;\n    MarioFrameX[78] = -4;\n    MarioFrameY[77] = -6;\n\n    MarioFrameY[222] = -24;\n    MarioFrameX[222] = -4;\n    MarioFrameY[223] = -6 - 24;\n    MarioFrameX[223] = -4;\n    MarioFrameY[178] = -24;\n    MarioFrameX[178] = -4;\n    MarioFrameY[177] = -6 - 24;\n    MarioFrameX[177] = -4;\n    MarioFrameY[322] = -24;\n    MarioFrameX[322] = -4;\n    MarioFrameY[323] = -6 - 24;\n    MarioFrameX[323] = -4;\n    MarioFrameY[278] = -24;\n    MarioFrameX[278] = -4;\n    MarioFrameY[277] = -6 - 24;\n    MarioFrameX[277] = -4;\n    MarioFrameY[422] = -24 - 2;\n    MarioFrameX[422] = -4;\n    MarioFrameY[423] = -6 - 24;\n    MarioFrameX[423] = -4;\n    MarioFrameY[378] = -24 - 2;\n    MarioFrameX[378] = -4;\n    MarioFrameY[377] = -6 - 24;\n    MarioFrameX[377] = -4;\n\n    MarioFrameY[522] = -24 - 2;\n    MarioFrameX[522] = -4;\n    MarioFrameY[523] = -6 - 24;\n    MarioFrameX[523] = -4;\n    MarioFrameY[478] = -24 - 2;\n    MarioFrameX[478] = -4;\n    MarioFrameY[477] = -6 - 24;\n    MarioFrameX[477] = -4;\n\n    MarioFrameY[622] = -24 - 2;\n    MarioFrameX[622] = -4;\n    MarioFrameY[623] = -6 - 24;\n    MarioFrameX[623] = -4;\n    MarioFrameY[578] = -24 - 2;\n    MarioFrameX[578] = -4;\n    MarioFrameY[577] = -6 - 24;\n    MarioFrameX[577] = -4;\n    LuigiFrameY[122] = -8;\n    LuigiFrameY[123] = -6;\n    LuigiFrameX[123] = -2;\n    LuigiFrameY[78] = -8;\n    LuigiFrameX[78] = -4;\n    LuigiFrameY[77] = -6;\n    LuigiFrameY[222] = -24 - 4;\n    LuigiFrameX[222] = -4;\n    LuigiFrameY[223] = -8 - 24;\n    LuigiFrameX[223] = -4;\n    LuigiFrameY[178] = -24 - 4;\n    LuigiFrameX[178] = -4;\n    LuigiFrameY[177] = -8 - 24;\n    LuigiFrameX[177] = -4;\n\n    LuigiFrameY[322] = -24 - 4;\n    LuigiFrameX[322] = -4;\n    LuigiFrameY[323] = -8 - 24;\n    LuigiFrameX[323] = -4;\n    LuigiFrameY[278] = -24 - 4;\n    LuigiFrameX[278] = -4;\n    LuigiFrameY[277] = -8 - 24;\n    LuigiFrameX[277] = -4;\n\n    LuigiFrameY[422] = -24 - 4;\n    LuigiFrameX[422] = -4;\n    LuigiFrameY[423] = -8 - 24;\n    LuigiFrameX[423] = -4;\n    LuigiFrameY[378] = -24 - 4;\n    LuigiFrameX[378] = -4;\n    LuigiFrameY[377] = -8 - 24;\n    LuigiFrameX[377] = -4;\n\n    LuigiFrameY[522] = -24 - 4;\n    LuigiFrameX[522] = -4;\n    LuigiFrameY[523] = -8 - 24;\n    LuigiFrameX[523] = -4;\n    LuigiFrameY[478] = -24 - 4;\n    LuigiFrameX[478] = -4;\n    LuigiFrameY[477] = -8 - 24;\n    LuigiFrameX[477] = -4;\n\n    LuigiFrameY[622] = -24 - 4;\n    LuigiFrameX[622] = -4;\n    LuigiFrameY[623] = -8 - 24;\n    LuigiFrameX[623] = -4;\n    LuigiFrameY[578] = -24 - 4;\n    LuigiFrameX[578] = -4;\n    LuigiFrameY[577] = -8 - 24;\n    LuigiFrameX[577] = -4;\n\n    LuigiFrameY[207] = -6;\n    LuigiFrameY[193] = -6;\n    LuigiFrameY[307] = -6;\n    LuigiFrameY[293] = -6;\n\n    For(A, 650, 749)\n    {\n        MarioFrameX[A] = MarioFrameX[A - 400];\n        MarioFrameY[A] = MarioFrameY[A - 400];\n        LuigiFrameX[A] = LuigiFrameX[A - 400];\n        LuigiFrameY[A] = LuigiFrameY[A - 400];\n        PeachFrameX[A] = PeachFrameX[A - 400];\n        PeachFrameY[A] = PeachFrameY[A - 400];\n        ToadFrameX[A] = ToadFrameX[A - 400];\n        ToadFrameY[A] = ToadFrameY[A - 400];\n        LinkFrameX[A] = LinkFrameX[A - 400];\n        LinkFrameY[A] = LinkFrameY[A - 400];\n    }\n\n    PeachFrameX[401] = -16;\n    PeachFrameY[401] = -2;\n    PeachFrameX[399] = -4;\n    PeachFrameY[399] = -2;\n    PeachFrameX[402] = -16;\n    PeachFrameY[402] = -2;\n    PeachFrameX[398] = -4;\n    PeachFrameY[398] = -2;\n    PeachFrameX[403] = -16;\n    PeachFrameY[403] = -2;\n    PeachFrameX[397] = -4;\n    PeachFrameY[397] = -2;\n    PeachFrameX[404] = -14;\n    PeachFrameY[404] = -2;\n    PeachFrameX[396] = -4;\n    PeachFrameY[396] = -2;\n    PeachFrameX[405] = -14;\n    PeachFrameY[405] = -2;\n    PeachFrameX[395] = -4;\n    PeachFrameY[395] = -2;\n    PeachFrameX[406] = -8;\n    PeachFrameY[406] = -2;\n    PeachFrameX[394] = -14;\n    PeachFrameY[394] = -2;\n    PeachFrameX[407] = -4;\n    PeachFrameY[407] = -2;\n    PeachFrameX[393] = -4;\n    PeachFrameY[393] = -2;\n    PeachFrameX[408] = -14;\n    PeachFrameY[408] = -2;\n    PeachFrameX[392] = -4;\n    PeachFrameY[392] = -2;\n    PeachFrameX[409] = -14;\n    PeachFrameY[409] = -2;\n    PeachFrameX[391] = -4;\n    PeachFrameY[391] = -2;\n    PeachFrameX[410] = -16;\n    PeachFrameY[410] = -2;\n    PeachFrameX[390] = -4;\n    PeachFrameY[390] = -2;\n    PeachFrameX[412] = -16;\n    PeachFrameY[412] = -2;\n    PeachFrameX[388] = -4;\n    PeachFrameY[388] = -2;\n    PeachFrameX[413] = -4;\n    PeachFrameY[413] = -4;\n    PeachFrameX[387] = -4;\n    PeachFrameY[387] = -4;\n    PeachFrameX[414] = -4;\n    PeachFrameY[414] = -2;\n    PeachFrameX[386] = -16;\n    PeachFrameY[386] = -2;\n    PeachFrameX[415] = -4;\n    PeachFrameY[415] = -4;\n    PeachFrameX[385] = -4;\n    PeachFrameY[385] = -4;\n    PeachFrameX[419] = -13;\n    PeachFrameY[419] = 0;\n    PeachFrameX[381] = -11;\n    PeachFrameY[381] = 0;\n    PeachFrameX[420] = -13;\n    PeachFrameY[420] = 0;\n    PeachFrameX[380] = -11;\n    PeachFrameY[380] = 0;\n    PeachFrameX[421] = -13;\n    PeachFrameY[421] = 0;\n    PeachFrameX[379] = -11;\n    PeachFrameY[379] = 0;\n    PeachFrameX[422] = 0;\n    PeachFrameY[422] = -30;\n    PeachFrameX[378] = 0;\n    PeachFrameY[378] = -30;\n    PeachFrameX[423] = 0;\n    PeachFrameY[423] = -30;\n    PeachFrameX[377] = 0;\n    PeachFrameY[377] = -30;\n    PeachFrameX[425] = 0;\n    PeachFrameY[425] = -2;\n    PeachFrameX[375] = 0;\n    PeachFrameY[375] = -2;\n    PeachFrameX[426] = 0;\n    PeachFrameY[426] = -2;\n    PeachFrameX[374] = 0;\n    PeachFrameY[374] = -2;\n    PeachFrameX[427] = -4;\n    PeachFrameY[427] = -2;\n    PeachFrameX[373] = -4;\n    PeachFrameY[373] = -2;\n    PeachFrameX[501] = -16;\n    PeachFrameY[501] = -6;\n    PeachFrameX[499] = -4;\n    PeachFrameY[499] = -6;\n    PeachFrameX[502] = -18;\n    PeachFrameY[502] = -6;\n    PeachFrameX[498] = -4;\n    PeachFrameY[498] = -6;\n    PeachFrameX[503] = -16;\n    PeachFrameY[503] = -6;\n    PeachFrameX[497] = -4;\n    PeachFrameY[497] = -6;\n    PeachFrameX[504] = -14;\n    PeachFrameY[504] = -6;\n    PeachFrameX[496] = -4;\n    PeachFrameY[496] = -6;\n    PeachFrameX[505] = -14;\n    PeachFrameY[505] = -6;\n    PeachFrameX[495] = -4;\n    PeachFrameY[495] = -6;\n    PeachFrameX[506] = -8;\n    PeachFrameY[506] = -6;\n    PeachFrameX[494] = -12;\n    PeachFrameY[494] = -6;\n    PeachFrameX[507] = -4;\n    PeachFrameY[507] = -6;\n    PeachFrameX[493] = -4;\n    PeachFrameY[493] = -6;\n    PeachFrameX[508] = -16;\n    PeachFrameY[508] = -6;\n    PeachFrameX[492] = -4;\n    PeachFrameY[492] = -6;\n    PeachFrameX[509] = -18;\n    PeachFrameY[509] = -6;\n    PeachFrameX[491] = -4;\n    PeachFrameY[491] = -6;\n    PeachFrameX[510] = -18;\n    PeachFrameY[510] = -6;\n    PeachFrameX[490] = -4;\n    PeachFrameY[490] = -6;\n    PeachFrameX[512] = -16;\n    PeachFrameY[512] = -6;\n    PeachFrameX[488] = -4;\n    PeachFrameY[488] = -6;\n    PeachFrameX[513] = -4;\n    PeachFrameY[513] = -2;\n    PeachFrameX[487] = -4;\n    PeachFrameY[487] = -2;\n    PeachFrameX[514] = -4;\n    PeachFrameY[514] = -6;\n    PeachFrameX[486] = -16;\n    PeachFrameY[486] = -6;\n    PeachFrameX[515] = -4;\n    PeachFrameY[515] = -6;\n    PeachFrameX[485] = -4;\n    PeachFrameY[485] = -6;\n    PeachFrameX[519] = -13;\n    PeachFrameY[519] = -4;\n    PeachFrameX[481] = -9;\n    PeachFrameY[481] = -4;\n    PeachFrameX[520] = -13;\n    PeachFrameY[520] = -4;\n    PeachFrameX[480] = -9;\n    PeachFrameY[480] = -4;\n    PeachFrameX[521] = -13;\n    PeachFrameY[521] = -4;\n    PeachFrameX[479] = -9;\n    PeachFrameY[479] = -4;\n    PeachFrameX[522] = 0;\n    PeachFrameY[522] = -34;\n    PeachFrameX[478] = 0;\n    PeachFrameY[478] = -34;\n    PeachFrameX[523] = 0;\n    PeachFrameY[523] = -34;\n    PeachFrameX[477] = 0;\n    PeachFrameY[477] = -34;\n    PeachFrameX[525] = 0;\n    PeachFrameY[525] = 0;\n    PeachFrameX[475] = 0;\n    PeachFrameY[475] = 0;\n    PeachFrameX[526] = 0;\n    PeachFrameY[526] = 0;\n    PeachFrameX[474] = 0;\n    PeachFrameY[474] = 0;\n    PeachFrameX[527] = -6;\n    PeachFrameY[527] = -6;\n    PeachFrameX[473] = -6;\n    PeachFrameY[473] = -6;\n    PeachFrameX[500] = -5;\n    PeachFrameY[500] = -6;\n\n    PeachFrameX[601] = -10;\n    PeachFrameY[601] = -2;\n    PeachFrameX[599] = -4;\n    PeachFrameY[599] = -2;\n    PeachFrameX[602] = -10;\n    PeachFrameY[602] = -2;\n    PeachFrameX[598] = -4;\n    PeachFrameY[598] = -2;\n    PeachFrameX[603] = -8;\n    PeachFrameY[603] = -2;\n    PeachFrameX[597] = -4;\n    PeachFrameY[597] = -2;\n    PeachFrameX[604] = -8;\n    PeachFrameY[604] = -2;\n    PeachFrameX[596] = -4;\n    PeachFrameY[596] = -2;\n    PeachFrameX[605] = -8;\n    PeachFrameY[605] = -2;\n    PeachFrameX[595] = -4;\n    PeachFrameY[595] = -2;\n    PeachFrameX[606] = -6;\n    PeachFrameY[606] = -2;\n    PeachFrameX[594] = -6;\n    PeachFrameY[594] = -2;\n    PeachFrameX[607] = -4;\n    PeachFrameY[607] = -4;\n    PeachFrameX[593] = -4;\n    PeachFrameY[593] = -4;\n    PeachFrameX[608] = -6;\n    PeachFrameY[608] = -2;\n    PeachFrameX[592] = -4;\n    PeachFrameY[592] = -2;\n    PeachFrameX[609] = -6;\n    PeachFrameY[609] = -2;\n    PeachFrameX[591] = -4;\n    PeachFrameY[591] = -2;\n    PeachFrameX[610] = -8;\n    PeachFrameY[610] = -2;\n    PeachFrameX[590] = -4;\n    PeachFrameY[590] = -2;\n    PeachFrameX[611] = -6;\n    PeachFrameY[611] = -2;\n    PeachFrameX[589] = -4;\n    PeachFrameY[589] = -2;\n    PeachFrameX[612] = -6;\n    PeachFrameY[612] = -2;\n    PeachFrameX[588] = -4;\n    PeachFrameY[588] = -2;\n    PeachFrameX[613] = -4;\n    PeachFrameY[613] = 2;\n    PeachFrameX[587] = -4;\n    PeachFrameY[587] = 2;\n    PeachFrameX[615] = -4;\n    PeachFrameY[615] = -4;\n    PeachFrameX[585] = -4;\n    PeachFrameY[585] = -4;\n    PeachFrameX[622] = 0;\n    PeachFrameY[622] = -34;\n    PeachFrameX[578] = 0;\n    PeachFrameY[578] = -34;\n    PeachFrameX[623] = 0;\n    PeachFrameY[623] = -32;\n    PeachFrameX[577] = 0;\n    PeachFrameY[577] = -32;\n    PeachFrameX[625] = 0;\n    PeachFrameY[625] = 2;\n    PeachFrameX[575] = 0;\n    PeachFrameY[575] = 2;\n    PeachFrameX[626] = 0;\n    PeachFrameY[626] = 2;\n    PeachFrameX[574] = 0;\n    PeachFrameY[574] = 2;\n    PeachFrameX[627] = -4;\n    PeachFrameY[627] = -4;\n    PeachFrameX[573] = -4;\n    PeachFrameY[573] = -4;\n\n    ToadFrameX[401] = -14;\n    ToadFrameY[401] = -2;\n    ToadFrameX[399] = -4;\n    ToadFrameY[399] = -2;\n    ToadFrameX[402] = -12;\n    ToadFrameY[402] = -2;\n    ToadFrameX[398] = -4;\n    ToadFrameY[398] = -2;\n    ToadFrameX[403] = -18;\n    ToadFrameY[403] = -2;\n    ToadFrameX[397] = -4;\n    ToadFrameY[397] = -2;\n    ToadFrameX[404] = -18;\n    ToadFrameY[404] = -2;\n    ToadFrameX[396] = -4;\n    ToadFrameY[396] = -2;\n    ToadFrameX[405] = -18;\n    ToadFrameY[405] = -2;\n    ToadFrameX[395] = -4;\n    ToadFrameY[395] = -2;\n    ToadFrameX[406] = -6;\n    ToadFrameY[406] = -2;\n    ToadFrameX[394] = -4;\n    ToadFrameY[394] = -2;\n    ToadFrameX[407] = -4;\n    ToadFrameY[407] = 0;\n    ToadFrameX[393] = -4;\n    ToadFrameY[393] = 0;\n    ToadFrameX[408] = -16;\n    ToadFrameY[408] = -2;\n    ToadFrameX[392] = -4;\n    ToadFrameY[392] = -2;\n    ToadFrameX[409] = -14;\n    ToadFrameY[409] = -2;\n    ToadFrameX[391] = -4;\n    ToadFrameY[391] = -2;\n    ToadFrameX[410] = -18;\n    ToadFrameY[410] = -2;\n    ToadFrameX[390] = -4;\n    ToadFrameY[390] = -2;\n    ToadFrameX[411] = -14;\n    ToadFrameY[411] = -2;\n    ToadFrameX[389] = -4;\n    ToadFrameY[389] = -2;\n    ToadFrameX[412] = -16;\n    ToadFrameY[412] = -2;\n    ToadFrameX[388] = -4;\n    ToadFrameY[388] = -2;\n    ToadFrameX[413] = -4;\n    ToadFrameY[413] = -4;\n    ToadFrameX[387] = -4;\n    ToadFrameY[387] = -4;\n    ToadFrameX[414] = -4;\n    ToadFrameY[414] = -2;\n    ToadFrameX[386] = -14;\n    ToadFrameY[386] = -2;\n    ToadFrameX[415] = -4;\n    ToadFrameY[415] = -2;\n    ToadFrameX[385] = -4;\n    ToadFrameY[385] = -2;\n    ToadFrameX[416] = -18;\n    ToadFrameY[416] = -2;\n    ToadFrameX[384] = -8;\n    ToadFrameY[384] = -2;\n    ToadFrameX[417] = -18;\n    ToadFrameY[417] = -2;\n    ToadFrameX[383] = -8;\n    ToadFrameY[383] = -2;\n    ToadFrameX[418] = -18;\n    ToadFrameY[418] = -2;\n    ToadFrameX[382] = -8;\n    ToadFrameY[382] = -2;\n    ToadFrameX[419] = -12;\n    ToadFrameY[419] = -2;\n    ToadFrameX[381] = -8;\n    ToadFrameY[381] = -2;\n    ToadFrameX[420] = -18;\n    ToadFrameY[420] = -2;\n    ToadFrameX[380] = -8;\n    ToadFrameY[380] = -2;\n    ToadFrameX[421] = -14;\n    ToadFrameY[421] = -2;\n    ToadFrameX[379] = -8;\n    ToadFrameY[379] = -2;\n    ToadFrameX[422] = -4;\n    ToadFrameY[422] = -22;\n    ToadFrameX[378] = -4;\n    ToadFrameY[378] = -22;\n    ToadFrameX[423] = -4;\n    ToadFrameY[423] = -16;\n    ToadFrameX[377] = -4;\n    ToadFrameY[377] = -16;\n    ToadFrameX[424] = -8;\n    ToadFrameY[424] = 6;\n    ToadFrameX[376] = -4;\n    ToadFrameY[376] = 6;\n    ToadFrameX[425] = -4;\n    ToadFrameY[425] = -4;\n    ToadFrameX[375] = -4;\n    ToadFrameY[375] = -4;\n    ToadFrameX[426] = -4;\n    ToadFrameY[426] = -4;\n    ToadFrameX[374] = -4;\n    ToadFrameY[374] = -4;\n    ToadFrameX[427] = -4;\n    ToadFrameY[427] = 0;\n    ToadFrameX[373] = -4;\n    ToadFrameY[373] = 0;\n\n\n\n\n    ToadFrameX[501] = -12;\n    ToadFrameY[501] = -2;\n    ToadFrameX[499] = -4;\n    ToadFrameY[499] = -2;\n    ToadFrameX[502] = -12;\n    ToadFrameY[502] = -2;\n    ToadFrameX[498] = -4;\n    ToadFrameY[498] = -2;\n    ToadFrameX[503] = -18;\n    ToadFrameY[503] = -2;\n    ToadFrameX[497] = -4;\n    ToadFrameY[497] = -2;\n    ToadFrameX[504] = -18;\n    ToadFrameY[504] = -2;\n    ToadFrameX[496] = -4;\n    ToadFrameY[496] = -2;\n    ToadFrameX[505] = -18;\n    ToadFrameY[505] = -2;\n    ToadFrameX[495] = -4;\n    ToadFrameY[495] = -2;\n    ToadFrameX[506] = -6;\n    ToadFrameY[506] = -2;\n    ToadFrameX[494] = -4;\n    ToadFrameY[494] = -2;\n    ToadFrameX[507] = -4;\n    ToadFrameY[507] = 0;\n    ToadFrameX[493] = -4;\n    ToadFrameY[493] = 0;\n    ToadFrameX[508] = -12;\n    ToadFrameY[508] = -2;\n    ToadFrameX[492] = -4;\n    ToadFrameY[492] = -2;\n    ToadFrameX[509] = -12;\n    ToadFrameY[509] = -2;\n    ToadFrameX[491] = -4;\n    ToadFrameY[491] = -2;\n    ToadFrameX[510] = -18;\n    ToadFrameY[510] = -2;\n    ToadFrameX[490] = -4;\n    ToadFrameY[490] = -2;\n    ToadFrameX[511] = -14;\n    ToadFrameY[511] = -2;\n    ToadFrameX[489] = -4;\n    ToadFrameY[489] = -2;\n    ToadFrameX[512] = -14;\n    ToadFrameY[512] = -2;\n    ToadFrameX[488] = -4;\n    ToadFrameY[488] = -2;\n    ToadFrameX[513] = -4;\n    ToadFrameY[513] = -4;\n    ToadFrameX[487] = -4;\n    ToadFrameY[487] = -4;\n    ToadFrameX[514] = -4;\n    ToadFrameY[514] = -2;\n    ToadFrameX[486] = -12;\n    ToadFrameY[486] = -2;\n    ToadFrameX[515] = -4;\n    ToadFrameY[515] = -2;\n    ToadFrameX[485] = -4;\n    ToadFrameY[485] = -2;\n    ToadFrameX[516] = -12;\n    ToadFrameY[516] = -2;\n    ToadFrameX[484] = -8;\n    ToadFrameY[484] = -2;\n    ToadFrameX[517] = -12;\n    ToadFrameY[517] = -2;\n    ToadFrameX[483] = -8;\n    ToadFrameY[483] = -2;\n    ToadFrameX[518] = -18;\n    ToadFrameY[518] = -2;\n    ToadFrameX[482] = -8;\n    ToadFrameY[482] = -2;\n    ToadFrameX[519] = -12;\n    ToadFrameY[519] = -2;\n    ToadFrameX[481] = -8;\n    ToadFrameY[481] = -2;\n    ToadFrameX[520] = -12;\n    ToadFrameY[520] = -2;\n    ToadFrameX[480] = -8;\n    ToadFrameY[480] = -2;\n    ToadFrameX[521] = -14;\n    ToadFrameY[521] = -2;\n    ToadFrameX[479] = -8;\n    ToadFrameY[479] = -2;\n    ToadFrameX[522] = -4;\n    ToadFrameY[522] = -22;\n    ToadFrameX[478] = -4;\n    ToadFrameY[478] = -22;\n    ToadFrameX[523] = -4;\n    ToadFrameY[523] = -16;\n    ToadFrameX[477] = -4;\n    ToadFrameY[477] = -16;\n    ToadFrameX[524] = -12;\n    ToadFrameY[524] = 6;\n    ToadFrameX[476] = -4;\n    ToadFrameY[476] = 6;\n    ToadFrameX[525] = -4;\n    ToadFrameY[525] = -4;\n    ToadFrameX[475] = -4;\n    ToadFrameY[475] = -4;\n    ToadFrameX[526] = -4;\n    ToadFrameY[526] = -4;\n    ToadFrameX[474] = -4;\n    ToadFrameY[474] = -4;\n    ToadFrameX[527] = -4;\n    ToadFrameY[527] = -2;\n    ToadFrameX[473] = -4;\n    ToadFrameY[473] = -2;\n\n\n    ToadFrameX[601] = -6;\n    ToadFrameY[601] = -2;\n    ToadFrameX[599] = -4;\n    ToadFrameY[599] = -2;\n\n    ToadFrameX[602] = -6;\n    ToadFrameY[602] = -2;\n    ToadFrameX[598] = -4;\n    ToadFrameY[598] = -2;\n\n    ToadFrameX[603] = -6;\n    ToadFrameY[603] = -2;\n    ToadFrameX[597] = -4;\n    ToadFrameY[597] = -2;\n\n    ToadFrameX[604] = -6;\n    ToadFrameY[604] = -2;\n    ToadFrameX[596] = -4;\n    ToadFrameY[596] = -2;\n\n    ToadFrameX[605] = -6;\n    ToadFrameY[605] = -2;\n    ToadFrameX[595] = -4;\n    ToadFrameY[595] = -2;\n\n    ToadFrameX[606] = -6;\n    ToadFrameY[606] = -2;\n    ToadFrameX[594] = -4;\n    ToadFrameY[594] = -2;\n\n    ToadFrameX[607] = -6;\n    ToadFrameY[607] = 0;\n    ToadFrameX[593] = -6;\n    ToadFrameY[593] = 0;\n\n    ToadFrameX[608] = -6;\n    ToadFrameY[608] = -2;\n    ToadFrameX[592] = -4;\n    ToadFrameY[592] = -2;\n\n    ToadFrameX[609] = -6;\n    ToadFrameY[609] = -2;\n    ToadFrameX[591] = -4;\n    ToadFrameY[591] = -2;\n\n    ToadFrameX[610] = -6;\n    ToadFrameY[610] = -2;\n    ToadFrameX[590] = -4;\n    ToadFrameY[590] = -2;\n\n    ToadFrameX[611] = -6;\n    ToadFrameY[611] = -2;\n    ToadFrameX[589] = -4;\n    ToadFrameY[589] = -2;\n\n    ToadFrameX[612] = -6;\n    ToadFrameY[612] = -2;\n    ToadFrameX[588] = -4;\n    ToadFrameY[588] = -2;\n\n    ToadFrameX[613] = -4;\n    ToadFrameY[613] = -2;\n    ToadFrameX[587] = -4;\n    ToadFrameY[587] = -2;\n\n    ToadFrameX[615] = -6;\n    ToadFrameY[615] = -2;\n    ToadFrameX[585] = -6;\n    ToadFrameY[585] = -2;\n\n    ToadFrameX[622] = -4;\n    ToadFrameY[622] = -22;\n    ToadFrameX[578] = -4;\n    ToadFrameY[578] = -22;\n\n    ToadFrameX[623] = -4;\n    ToadFrameY[623] = -16;\n    ToadFrameX[577] = -4;\n    ToadFrameY[577] = -16;\n\n    ToadFrameX[625] = -4;\n    ToadFrameY[625] = -2;\n    ToadFrameX[575] = -4;\n    ToadFrameY[575] = -2;\n\n    ToadFrameX[626] = -4;\n    ToadFrameY[626] = -2;\n    ToadFrameX[574] = -4;\n    ToadFrameY[574] = -2;\n\n    ToadFrameX[627] = -4;\n    ToadFrameY[627] = 0;\n    ToadFrameX[573] = -4;\n    ToadFrameY[573] = 0;\n\n\n    ToadFrameX[500] = -6;\n    ToadFrameY[500] = -6;\n\n    int base = 150;\n    int offset_x = -8;\n    int offset_y = -2;\n    int char1_offset_y = -8;\n    for(int A = 750; A <= 1150; A++) // this just fills 750 and above with frames matching big power\n    {\n        // special case: cyclone power has big sprites\n        if(A == 950)\n        {\n            base = 150;\n            offset_x = -2;\n            offset_y = -2;\n            char1_offset_y = -4;\n        }\n        else if(A == 1050)\n        {\n            base = 150;\n            offset_x = -6;\n            offset_y = 0;\n            char1_offset_y = 0;\n        }\n        else if(A == 850)\n        {\n            // polar power uses fire sprites as base\n            offset_x = -4;\n            offset_y = -4;\n            char1_offset_y = -6;\n        }\n\n        MarioFrameX[A] = MarioFrameX[base] + offset_x;\n        MarioFrameY[A] = MarioFrameY[base] + char1_offset_y;\n        LuigiFrameX[A] = LuigiFrameX[base] + offset_x;\n        LuigiFrameY[A] = LuigiFrameY[base] + offset_y;\n        PeachFrameX[A] = PeachFrameX[base] + offset_x;\n        PeachFrameY[A] = PeachFrameY[base];\n        ToadFrameX[A] = ToadFrameX[base] + offset_x;\n        ToadFrameY[A] = ToadFrameY[base];\n        LinkFrameX[A] = LinkFrameX[base];\n        LinkFrameY[A] = LinkFrameY[base];\n\n        base++;\n    }\n\n    // special power corrections\n    for(int D = -1; D <= 1; D += 2)\n    {\n        // cyclone power\n        LuigiFrameY[1000 + D * 3] -= 2;\n        LuigiFrameY[1000 + D * 4] -= 2;\n        LuigiFrameY[1000 + D * 5] -= 2;\n        LuigiFrameY[1000 + D * 7] -= 2;\n        LuigiFrameY[1000 + D * 10] -= 2;\n        LuigiFrameY[1000 + D * 24] -= 2;\n        LuigiFrameY[1000 + D * 30] -= 2;\n        LuigiFrameY[1000 + D * 31] -= 2;\n        LuigiFrameY[1000 + D * 41] -= 2;\n        LuigiFrameY[1000 + D * 44] -= 2;\n\n        LuigiFrameY[1000 + D * 6] += 2;\n        LuigiFrameY[1000 + D * 23] += 2;\n\n        PeachFrameY[1000 + D * 7] -= 4;\n        PeachFrameY[1000 + D * 27] -= 4;\n\n        MarioFrameY[1000 + D * 23] += 2;\n\n        MarioFrameX[1000 + D * 16] = -12;\n        MarioFrameX[1000 + D * 17] = -12;\n        MarioFrameX[1000 + D * 18] = -12;\n\n        LuigiFrameX[1000 + D * 16] = -12;\n        LuigiFrameX[1000 + D * 17] = -12;\n        LuigiFrameX[1000 + D * 18] = -12;\n\n        PeachFrameX[1000 + D * 16] = -12;\n        PeachFrameX[1000 + D * 17] = -12;\n        PeachFrameX[1000 + D * 18] = -12;\n\n        ToadFrameX[1000 + D * 16] = -12;\n        ToadFrameX[1000 + D * 17] = -12;\n        ToadFrameX[1000 + D * 18] = -12;\n\n        // rolling power\n        LinkFrameY[1100 + D * 12] = -12;\n        LinkFrameY[1100 + D * 13] = -12;\n        LinkFrameY[1100 + D * 14] = -12;\n        LinkFrameY[1100 + D * 15] = -12;\n\n        // aquatic frame corrections\n        MarioFrameY[800 + D * 4] += 6;\n        MarioFrameY[800 + D * 5] += 6;\n        MarioFrameY[800 + D * 7] -= 2;\n        MarioFrameY[800 + D * 23] += 6;\n\n        LuigiFrameY[800 + D * 4] += 8;\n        LuigiFrameY[800 + D * 5] += 8;\n        LuigiFrameY[800 + D * 7] -= 8;\n        LuigiFrameY[800 + D * 23] += 2;\n\n        PeachFrameY[800 + D * 5] += 4;\n        PeachFrameY[800 + D * 7] -= 14;\n        PeachFrameY[800 + D * 27] -= 14;\n\n        ToadFrameY[800 + D * 7] -= 12;\n        ToadFrameY[800 + D * 8] -= 4;\n        ToadFrameY[800 + D * 9] -= 4;\n        ToadFrameY[800 + D * 10] -= 4;\n        ToadFrameY[800 + D * 27] -= 12;\n\n        // polar frame corrections\n        MarioFrameY[900 + D * 23] += 4;\n        LuigiFrameY[900 + D * 23] += 4;\n        LuigiFrameY[900 + D * 6] += 2;\n\n        // polar slide\n        PeachFrameX[900 + D * 14] = ToadFrameX[900 + D * 14] = LuigiFrameX[900 + D * 14] = MarioFrameX[900 + D * 14] = -12 - 8 * D;\n        PeachFrameY[900 + D * 14] = ToadFrameY[900 + D * 14] = LuigiFrameY[900 + D * 14] = MarioFrameY[900 + D * 14] = -10;\n\n        // aquatic swim\n        for(int C = 800; C <= 900; C += 100)\n        {\n            PeachFrameX[C + D * 16] = ToadFrameX[C + D * 16] = LuigiFrameX[C + D * 16] = MarioFrameX[C + D * 16] = -12 - 8 * D;\n            PeachFrameX[C + D * 17] = ToadFrameX[C + D * 17] = LuigiFrameX[C + D * 17] = MarioFrameX[C + D * 17] = -12 - 8 * D;\n            PeachFrameX[C + D * 18] = ToadFrameX[C + D * 18] = LuigiFrameX[C + D * 18] = MarioFrameX[C + D * 18] = -12 - 8 * D;\n            PeachFrameY[C + D * 16] = ToadFrameY[C + D * 16] = LuigiFrameY[C + D * 16] = MarioFrameY[C + D * 16] = -10;\n            PeachFrameY[C + D * 17] = ToadFrameY[C + D * 17] = LuigiFrameY[C + D * 17] = MarioFrameY[C + D * 17] = -10;\n            PeachFrameY[C + D * 18] = ToadFrameY[C + D * 18] = LuigiFrameY[C + D * 18] = MarioFrameY[C + D * 18] = -10;\n\n            PeachFrameX[C + D * 19] = ToadFrameX[C + D * 19] = LuigiFrameX[C + D * 19] = MarioFrameX[C + D * 19] = -4;\n            PeachFrameX[C + D * 20] = ToadFrameX[C + D * 20] = LuigiFrameX[C + D * 20] = MarioFrameX[C + D * 20] = -4;\n            PeachFrameX[C + D * 21] = ToadFrameX[C + D * 21] = LuigiFrameX[C + D * 21] = MarioFrameX[C + D * 21] = -4;\n            PeachFrameY[C + D * 19] = ToadFrameY[C + D * 19] = LuigiFrameY[C + D * 19] = MarioFrameY[C + D * 19] = -24;\n            PeachFrameY[C + D * 20] = ToadFrameY[C + D * 20] = LuigiFrameY[C + D * 20] = MarioFrameY[C + D * 20] = -24;\n            PeachFrameY[C + D * 21] = ToadFrameY[C + D * 21] = LuigiFrameY[C + D * 21] = MarioFrameY[C + D * 21] = -24;\n\n            PeachFrameX[C + D * 40] = ToadFrameX[C + D * 40] = LuigiFrameX[C + D * 40] = MarioFrameX[C + D * 40] = -4;\n            PeachFrameX[C + D * 41] = ToadFrameX[C + D * 41] = LuigiFrameX[C + D * 41] = MarioFrameX[C + D * 41] = -4;\n            PeachFrameX[C + D * 42] = ToadFrameX[C + D * 42] = LuigiFrameX[C + D * 42] = MarioFrameX[C + D * 42] = -4;\n            PeachFrameY[C + D * 40] = ToadFrameY[C + D * 40] = LuigiFrameY[C + D * 40] = MarioFrameY[C + D * 40] = -8;\n            PeachFrameY[C + D * 41] = ToadFrameY[C + D * 41] = LuigiFrameY[C + D * 41] = MarioFrameY[C + D * 41] = -8;\n            PeachFrameY[C + D * 42] = ToadFrameY[C + D * 42] = LuigiFrameY[C + D * 42] = MarioFrameY[C + D * 42] = -8;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/record.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n// needed because there are a lot of writes / scans whose failure is detected at the end of the function\n// updated versions of glibc don't even seem to trigger this warning in this context\n#if !defined(__clang__) && (defined(__GNUC__) || defined(__MINGW32__))\n#   pragma GCC diagnostic ignored \"-Wunused-result\"\n#   define XTECH_GCC_UNUSED_RESULT_IGNORED\n#endif\n\n\n// this module handles particular the control recording and playback functions\n// and the gameplay stats recording functions\n\n#include \"../version.h\"\n\n#define LONG_VERSION \"TheXTech branch \" V_BUILD_BRANCH \" commit \" V_BUILD_VER\n#define SHORT_VERSION V_BUILD_VER\n\n#include \"../globals.h\"\n#include \"../rand.h\"\n#include \"../frame_timer.h\"\n#include \"../config.h\"\n#include \"record.h\"\n#include \"message.h\"\n\n#include \"sdl_proxy/sdl_timer.h\"\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include <chrono>\n#include <fmt_time_ne.h>\n#include <fmt_format_ne.h>\n\n#include <cstdint>\n#include <cinttypes>\n#include <Utils/files.h>\n#include <DirManager/dirman.h>\n#include <AppPath/app_path.h>\n#include <Logger/logger.h>\n#include <md5tools.hpp>\n\n\n#ifndef PRId64 /*Workaround*/\n#   ifndef __PRI64_PREFIX\n#       if THEXTECH_WORDSIZE == 64\n#           ifdef _WIN32\n#               define __PRI64_PREFIX   \"I64\"\n#               define __PRIPTR_PREFIX  \"I64\"\n#               ifdef __MINGW32__\n#                   pragma GCC diagnostic push\n#                   pragma GCC diagnostic ignored \"-Wformat=\"\n#                   define X_GCC_NO_WARNING_FORMAT\n#               endif\n#           else\n#               define __PRI64_PREFIX   \"l\"\n#               define __PRIPTR_PREFIX  \"l\"\n#           endif\n#       else\n#           define __PRI64_PREFIX   \"ll\"\n#           define __PRIPTR_PREFIX\n#       endif\n#   endif\n#   define PRId64   __PRI64_PREFIX \"d\"\n#endif\n\n#ifdef LOW_MEM\n#   define PRIvb PRId16\n#else\n#   define PRIvb \"d\"\n#endif\n\nstatic std::string makeRecordPrefix()\n{\n    auto now = std::chrono::system_clock::now();\n    std::time_t in_time_t = std::chrono::system_clock::to_time_t(now);\n    std::tm t = fmt::localtime_ne(in_time_t);\n\n    return fmt::sprintf_ne(\"%s%s_%s_%04d-%02d-%02d_%02d-%02d-%02d.rec\",\n                           AppPathManager::gameplayRecordsRootDir().c_str(), FileName.c_str(), SHORT_VERSION,\n                           (1900 + t.tm_year), (1 + t.tm_mon), t.tm_mday,\n                           t.tm_hour, t.tm_min, t.tm_sec);\n}\n\nstatic void clipNewLine(char *buffer, size_t maxSize)\n{\n    for(size_t i = 0; i < maxSize; i++)\n    {\n        if(buffer[i] == '\\r' || buffer[i] == '\\n')\n        {\n            buffer[i] = '\\0';\n            break;\n        }\n    }\n}\n\n\nnamespace Record\n{\n\n// public\nFILE* record_file = nullptr;\nFILE* replay_file = nullptr;\n\n//! Externally providen level file path for the replay\nstatic std::string replayLevelFilePath;\n\nstatic const int c_recordVersion = 3;\n\n// private\n\nstatic bool         in_level = false;\nstatic bool         diverged_major = false;\nstatic bool         diverged_minor = false;\nstatic int64_t      frame_no = 0;\nstatic int64_t      next_record_frame = 0;\nstatic uint32_t     last_status_tick = 0;\nstatic Controls_t   last_controls[maxPlayers];\n\n\nstatic void write_header()\n{\n    // write all necessary state variables!\n    fprintf(record_file, \"Header\\r\\n\");\n    fprintf(record_file, \"RecordVersion %d\\r\\n\", c_recordVersion); // Version of record file\n    fprintf(record_file, \"Version %s\\r\\n\", LONG_VERSION); // game version / commit\n    fprintf(record_file, \"CompatLevel %d\\r\\n\", (int)g_config.compatibility_mode); // compatibility mode\n    if(FullFileName.compare(0, AppPath.size(), AppPath) == 0)\n        fprintf(record_file, \"%s\\r\\n\", FullFileName.c_str()+AppPath.size()); // level that was played\n    else\n        fprintf(record_file, \"%s\\r\\n\", FullFileName.c_str()); // level that was played\n\n    std::string md5sum = md5::file_to_hashGC(FullFileName);\n    fprintf(record_file, \"SumMD5 %s\\r\\n\", md5sum.c_str()); // level that was played\n    fprintf(record_file, \"Seed %d\\r\\n\", readSeed());\n    fprintf(record_file, \"Checkpoint %d\\r\\n\", (Checkpoint == FullFileName) ? 1 : 0);\n\n    if(g_config.fix_vanilla_checkpoints && Checkpoint == FullFileName)\n    {\n        fprintf(record_file, \"Multipoints %d: \", (int)CheckpointsList.size());\n\n        for(const Checkpoint_t& cp : CheckpointsList)\n            fprintf(record_file, \"%d,\", cp.id);\n\n        fprintf(record_file, \"\\r\\n\");\n    }\n\n    fprintf(record_file, \"StartWarp %d\\r\\n\", StartWarp);\n    fprintf(record_file, \"ReturnWarp %d\\r\\n\", ReturnWarp);\n    fprintf(record_file, \"Lives %d\\r\\n\", (int)Lives);\n    fprintf(record_file, \"Coins %d\\r\\n\", Coins);\n    fprintf(record_file, \"Score %d\\r\\n\", Score);\n    fprintf(record_file, \"Stars %d\\r\\n\", numStars);\n\n    for(const auto& star : Star)\n    {\n        fprintf(record_file, \"Star\\r\\n\");\n        fprintf(record_file, \"%s\\r\\n\", star.level.c_str());\n        fprintf(record_file, \"Section %d\\r\\n\", star.Section);\n    }\n\n    fprintf(record_file, \"Players %d\\r\\n\", numPlayers);\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        fprintf(record_file,\n                \"Player\\r\\n\"\n                \"Char %d\\r\\n\"\n                \"State %d\\r\\n\"\n                \"Mount %d\\r\\n\"\n                \"MountType %d\\r\\n\"\n                \"HeldBonus %d\\r\\n\",\n                (int)Player[A].Character, (int)Player[A].State, (int)Player[A].Mount, (int)Player[A].MountType, (int)Player[A].HeldBonus);\n    }\n}\n\n// FIXME: Implement the error returning and on-failure abortation with leading abortation of record replaying startup\n\nstatic void read_header()\n{\n    rewind(replay_file); // fseek(replay_file, 0, SEEK_SET);\n\n    // buffer is a 1024-character buffer used for reading strings, shared with the record_init() function.\n    char buffer[1024];\n    char md5hash[1024];\n    std::string thisHash;\n\n    // n is an integer for some implicit conversions\n    int n;\n    // Version of record file\n    int recordVersion = 0;\n\n    // read all necessary state variables!\n    fgets(buffer, 1024, replay_file); // \"Header\"\n    fscanf(replay_file, \"RecordVersion %d\\r\\n\", &recordVersion);\n\n    pLogDebug(\"Loading recording version %d\", recordVersion);\n\n    if(recordVersion < 2)\n        pLogCritical(\"Record file is invalid! (version below than minimally supported: %d)\", recordVersion);\n\n    fgets(buffer, 1024, replay_file); // game version / commit\n\n    pLogDebug(\"Recording made with %s\", buffer);\n\n    fscanf(replay_file, \"CompatLevel %d\\r\\n\", &n); // compatibility mode\n\n    pLogDebug(\"  at compat level %d\", n);\n\n    if(n != Config_t::COMPAT_SMBX13)\n        pLogWarning(\"compatibility mode is not a long-term support version. Do not expect identical results.\");\n\n    g_config.compatibility_mode = n;\n    UpdateConfig();\n\n    fgets(buffer, 1024, replay_file); // level that was played\n\n    clipNewLine(buffer, 1024); // clip the newline :(\n\n    // now SET the filename\n    FullFileName = replayLevelFilePath.empty() ? buffer : replayLevelFilePath;\n    // if(SDL_strcasecmp(buffer, FilefNameFull.c_str()))\n    //     pLogWarning(\"FileName does not match.\");\n\n    pLogDebug(\"Attempt to load level file %s for the replay\", FullFileName.c_str());\n\n    thisHash = md5::file_to_hashGC(FullFileName);\n\n    if(thisHash.empty())\n    {\n        FullFileName = AppPath + FullFileName;\n        pLogDebug(\"Not found; attempt to load level file %s for the replay\", FullFileName.c_str());\n        thisHash = md5::file_to_hashGC(FullFileName);\n    }\n\n    if(thisHash.empty())\n        pLogCritical(\"Failed to retrieve the MD5 hash for %s file (probably, it doesn't exist)\", FullFileName.c_str());\n\n    SDL_memset(md5hash, 0, sizeof(md5hash));\n    fscanf(replay_file, \"SumMD5 %s\\r\\n\", md5hash); // File's hash\n    pLogDebug(\"Replay file (loaded %s, expected %s)\", thisHash.c_str(), md5hash);\n    int hashCmp = thisHash.compare(md5hash);\n    if(hashCmp != 0)\n        pLogCritical(\"Loaded level file is not matched to expected (check sum missmatch %d)\", hashCmp);\n\n    fscanf(replay_file, \"Seed %d\\r\\n\", &n); // random seed\n    seedRandom(n);\n\n    fscanf(replay_file, \"Checkpoint %d\\r\\n\", &n); // is there a checkpoint?\n\n    Checkpoint = n ? FullFileName : std::string();\n\n    if(g_config.fix_vanilla_checkpoints && Checkpoint == FullFileName)\n    {\n        CheckpointsList.clear();\n        fscanf(replay_file, \"Multipoints %d: \", &n);\n        CheckpointsList.resize(n);\n\n        for(int i = 0; i < n; i++)\n            fscanf(replay_file, \"%d,\", &CheckpointsList[i].id);\n\n        fscanf(replay_file, \"\\r\\n\");\n    }\n\n    fscanf(replay_file, \"StartWarp %d\\r\\n\", &StartWarp);\n    fscanf(replay_file, \"ReturnWarp %d\\r\\n\", &ReturnWarp);\n    fscanf(replay_file, \"Lives %d\\r\\n\", &n);\n    Lives = n;\n    fscanf(replay_file, \"Coins %d\\r\\n\", &Coins);\n    fscanf(replay_file, \"Score %d\\r\\n\", &Score);\n    fscanf(replay_file, \"Stars %d\\r\\n\", &numStars);\n    \n    if(Star.capacity() < (size_t)numStars)\n        Star.reserve(numStars);\n\n    for(int A = 1; A <= numStars; A++)\n    {\n        Star_t star;\n        fgets(buffer, 1024, replay_file); // \"Star\"\n        fgets(buffer, 1024, replay_file); // level\n\n        for(int i = 0; i < 1024; i++)\n            if(buffer[i] == '\\r') buffer[i] = '\\0'; // clip the newline :(\n\n        star.level = buffer;\n        fscanf(replay_file, \"Section %d\\r\\n\", &star.Section);\n        Star.push_back(std::move(star));\n    }\n\n    fscanf(replay_file, \"Players %d\\r\\n\", &numPlayers);\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        vbint_t HeldBonus = NPCID(0);\n\n        if(recordVersion < 3)\n            fscanf(replay_file,\n                   \"Player\\r\\n\"\n                   \"Char %\" PRIvb \"\\r\\n\"\n                   \"State %\" PRIvb \"\\r\\n\"\n                   \"MountType %\" PRIvb \"\\r\\n\"\n                   \"HeldBonus %\" PRIvb \"\\r\\n\",\n                &Player[A].Character, &Player[A].State, &Player[A].MountType, &HeldBonus);\n        else\n            fscanf(replay_file,\n                   \"Player\\r\\n\"\n                   \"Char %\" PRIvb \"\\r\\n\"\n                   \"State %\" PRIvb \"\\r\\n\"\n                   \"Mount %\" PRIvb \"\\r\\n\"\n                   \"MountType %\" PRIvb \"\\r\\n\"\n                   \"HeldBonus %\" PRIvb \"\\r\\n\",\n                &Player[A].Character, &Player[A].State, &Player[A].Mount, &Player[A].MountType, &HeldBonus);\n\n        Player[A].HeldBonus = NPCID(HeldBonus);\n    }\n\n    Cheater = true; // important to avoid losing player save data in replay mode.\n    TestLevel = false;\n    g_config.unlimited_framerate = true;\n    g_config.show_fps = true;\n    g_config.enable_frameskip = false;\n}\n\nstatic void write_end()\n{\n    fprintf(record_file, \" %\" PRId64 \" \\r\\nEnd\\r\\nLevelBeatCode %d\\r\\n\", frame_no+1, (int)LevelBeatCode);\n}\n\nstatic void read_end()\n{\n    int b;\n\n    if(fscanf(replay_file, \"End\\r\\nLevelBeatCode %d\\r\\n\", &b) != 1)\n    {\n        pLogWarning(\"old gameplay file diverged (invalid end header).\");\n        diverged_major = true;\n    }\n\n    if(next_record_frame-1 != frame_no)\n    {\n        pLogWarning(\"final frame_no diverged (old: %\" PRId64 \", new: %\" PRId64 \").\", next_record_frame-1, frame_no);\n        diverged_major = true;\n    }\n\n    if(b != LevelBeatCode && !(b == -1 && LevelBeatCode == 0))\n    {\n        pLogWarning(\"LevelBeatCode diverged (old: %d, new: %d).\", b, LevelBeatCode);\n        diverged_major = true;\n    }\n}\n\nstatic void write_control()\n{\n    for(int i = 0; i < numPlayers; i++)\n    {\n        const Controls_t& keys = Player[i+1].Controls;\n\n        if(!last_controls[i].Up && keys.Up)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dU\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].Up && !keys.Up)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dU\\r\\n\", frame_no, i+1);\n\n        if(!last_controls[i].Down && keys.Down)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dD\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].Down && !keys.Down)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dD\\r\\n\", frame_no, i+1);\n\n        if(!last_controls[i].Left && keys.Left)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dL\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].Left && !keys.Left)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dL\\r\\n\", frame_no, i+1);\n\n        if(!last_controls[i].Right && keys.Right)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dR\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].Right && !keys.Right)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dR\\r\\n\", frame_no, i+1);\n\n        if(!last_controls[i].Start && keys.Start)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dS\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].Start && !keys.Start)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dS\\r\\n\", frame_no, i+1);\n\n        if(!last_controls[i].Drop && keys.Drop)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dI\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].Drop && !keys.Drop)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dI\\r\\n\", frame_no, i+1);\n\n        if(!last_controls[i].Jump && keys.Jump)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dA\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].Jump && !keys.Jump)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dA\\r\\n\", frame_no, i+1);\n\n        if(!last_controls[i].Run && keys.Run)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dB\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].Run && !keys.Run)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dB\\r\\n\", frame_no, i+1);\n\n        if(!last_controls[i].AltJump && keys.AltJump)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dX\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].AltJump && !keys.AltJump)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dX\\r\\n\", frame_no, i+1);\n\n        if(!last_controls[i].AltRun && keys.AltRun)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC+%dY\\r\\n\", frame_no, i+1);\n        else if(last_controls[i].AltRun && !keys.AltRun)\n            fprintf(record_file, \" %\" PRId64 \"\\r\\nC-%dY\\r\\n\", frame_no, i+1);\n\n        last_controls[i] = keys;\n    }\n\n    fflush(record_file);\n}\n\nstatic void read_control()\n{\n    int p;\n    char mode, key;\n\n    if(fscanf(replay_file, \"C%c%d%c\\r\\n\", &mode, &p, &key) != 3)\n        return;\n\n    bool set = (mode != '-');\n\n    if(key == 'U')\n        last_controls[p-1].Up = set;\n    else if(key == 'D')\n        last_controls[p-1].Down = set;\n    else if(key == 'L')\n        last_controls[p-1].Left = set;\n    else if(key == 'R')\n        last_controls[p-1].Right = set;\n    else if(key == 'S')\n        last_controls[p-1].Start = set;\n    else if(key == 'I')\n        last_controls[p-1].Drop = set;\n    else if(key == 'A')\n        last_controls[p-1].Jump = set;\n    else if(key == 'B')\n        last_controls[p-1].Run = set;\n    else if(key == 'X')\n        last_controls[p-1].AltJump = set;\n    else if(key == 'Y')\n        last_controls[p-1].AltRun = set;\n\n    // replicate the controls changes in the new recording\n    if(record_file)\n    {\n        fprintf(record_file, \" %\" PRId64 \"\\r\\nC%c%d%c\\r\\n\", frame_no, mode, p, key);\n    }\n}\n\nstatic void write_status()\n{\n    if(frame_no == 0)\n    {\n        g_stats.renderedNPCs = 0;\n        g_stats.renderedBlocks = 0;\n        g_stats.renderedBGOs = 0;\n    }\n\n    fprintf(record_file, \" %\" PRId64 \" \\r\\nStatus\\r\\n\", frame_no);\n    uint32_t status_tick = SDL_GetTicks();\n    fprintf(record_file, \"Ticks %lu\\r\\n\", (long unsigned)(SDL_GetTicks() - last_status_tick));\n    last_status_tick = status_tick;\n    fprintf(record_file, \"randCalls %ld\\r\\n\", random_ncalls());\n    fprintf(record_file, \"Score %d\\r\\n\", Score);\n    fprintf(record_file, \"numNPCs %d\\r\\n\", numNPCs);\n\n    int numActiveNPCs = 0;\n    if(frame_no != 0)\n    {\n        for(int i = 1; i <= numNPCs; i++)\n        {\n            if(NPC[i].Active)\n                numActiveNPCs ++;\n        }\n    }\n\n    fprintf(record_file, \"numActiveNPCs %d\\r\\n\", numActiveNPCs);\n    fprintf(record_file, \"numRenderNPCs %d\\r\\nnumRenderBlocks %d\\r\\nnumRenderBGOs %d\\r\\n\",\n        g_stats.renderedNPCs, g_stats.renderedBlocks, g_stats.renderedBGOs);\n\n    for(int i = 1; i <= numPlayers; i++)\n    {\n        fprintf(record_file, \"p%dx %lf\\r\\np%dy %lf\\r\\n\",\n            i, (double)Player[i].Location.X, i, (double)Player[i].Location.Y);\n    }\n\n    fflush(record_file);\n}\n\nstatic void read_status()\n{\n    if(frame_no == 0)\n    {\n        g_stats.renderedNPCs = 0;\n        g_stats.renderedBlocks = 0;\n        g_stats.renderedBGOs = 0;\n    }\n\n    int o_ticks, o_Score, o_numNPCs, o_numActiveNPCs, o_renderedNPCs, o_renderedBlocks, o_renderedBGOs;\n    long o_randCalls;\n\n    int success = 0;\n\n    fscanf(replay_file, \"Status\\r\\n%n\", &success);\n\n    if(!success)\n    {\n        pLogWarning(\"old gameplay file diverged (invalid status header) at frame %\" PRId64 \".\", frame_no);\n        diverged_major = true;\n        return;\n    }\n\n    if(fscanf(replay_file,\n              \"Ticks %d\\r\\n\"\n              \"randCalls %ld\\r\\n\"\n              \"Score %d\\r\\n\"\n              \"numNPCs %d\\r\\n\"\n              \"numActiveNPCs %d\\r\\n\"\n              \"numRenderNPCs %d\\r\\n\"\n              \"numRenderBlocks %d\\r\\n\"\n              \"numRenderBGOs %d\\r\\n\",\n        &o_ticks, &o_randCalls, &o_Score, &o_numNPCs, &o_numActiveNPCs, &o_renderedNPCs, &o_renderedBlocks, &o_renderedBGOs) != 8)\n    {\n        pLogWarning(\"old gameplay file diverged (invalid status info) at frame %\" PRId64 \".\", frame_no);\n        diverged_major = true;\n        return;\n    }\n\n    if(o_randCalls != random_ncalls())\n    {\n        pLogWarning(\"randCalls diverged (old: %ld, new: %ld) at frame %\" PRId64 \".\", o_randCalls, random_ncalls(), frame_no);\n        diverged_minor = true;\n#ifdef DEBUG_RANDOM_CALLS\n        for(int i = 0; i < g_random_calls.size(); i++)\n            printf(\"%p\\n\", g_random_calls[i]);\n#endif\n        pLogWarning(\"  Resetting random engine to match.\");\n        random_set_ncalls(o_randCalls);\n    }\n#ifdef DEBUG_RANDOM_CALLS\n    g_random_calls.clear();\n#endif\n\n    if(o_Score != Score)\n    {\n        pLogWarning(\"score diverged (old: %d, new: %d) at frame %\" PRId64 \".\", o_Score, Score, frame_no);\n        diverged_major = true;\n    }\n\n    if(o_numNPCs != numNPCs)\n    {\n        pLogWarning(\"numNPCs diverged (old: %d, new: %d) at frame %\" PRId64 \".\", o_numNPCs, numNPCs, frame_no);\n        diverged_major = true;\n    }\n\n    int numActiveNPCs = 0;\n    if(frame_no != 0)\n    {\n        for(int i = 1; i <= numNPCs; i++)\n        {\n            if(NPC[i].Active)\n                numActiveNPCs ++;\n        }\n    }\n\n    if(o_numActiveNPCs != numActiveNPCs)\n    {\n        pLogWarning(\"numActiveNPCs diverged (old: %d, new: %d) at frame %\" PRId64 \".\", o_numActiveNPCs, numActiveNPCs, frame_no);\n        diverged_minor = true;\n    }\n\n    if(o_renderedNPCs != g_stats.renderedNPCs)\n    {\n        pLogWarning(\"renderedNPCs diverged (old: %d, new: %d) at frame %\" PRId64 \".\", o_renderedNPCs, g_stats.renderedNPCs, frame_no);\n        diverged_minor = true;\n    }\n\n    if(o_renderedBlocks != g_stats.renderedBlocks)\n    {\n        pLogWarning(\"renderedBlocks diverged (old: %d, new: %d) at frame %\" PRId64 \".\", o_renderedBlocks, g_stats.renderedBlocks, frame_no);\n        diverged_minor = true;\n    }\n\n    if(o_renderedBGOs != g_stats.renderedBGOs)\n    {\n        pLogWarning(\"renderedBGOs diverged (old: %d, new: %d) at frame %\" PRId64 \".\", o_renderedBGOs, g_stats.renderedBGOs, frame_no);\n        diverged_minor = true;\n    }\n\n    for(int i = 1; i <= numPlayers; i++)\n    {\n        double px, py;\n\n        if(fscanf(replay_file, \"p%dx %lf\\r\\np%dy %lf\\r\\n\", &success, &px, &success, &py) != 4)\n        {\n            pLogWarning(\"old gameplay file diverged (invalid player %d info) at frame %\" PRId64 \".\", i, frame_no);\n            diverged_major = true;\n            break;\n        }\n\n        num_t dx = num_t::abs(num_t::from_double(px) - Player[i].Location.X);\n        num_t dy = num_t::abs(num_t::from_double(py) - Player[i].Location.Y);\n\n        // quite non-strict because in a true divergence situation, it will get continually worse\n        if(dx > 0.01_n || dy > 0.01_n)\n        {\n            pLogWarning(\"player %d position diverged (old x=%f new x=%f, old y=%f new y=%f) at frame %\" PRId64 \".\",\n                        i,\n                        px, (double)Player[i].Location.X,\n                        py, (double)Player[i].Location.Y, frame_no);\n            diverged_minor = true;\n            if(dx > 1 || dy > 1)\n            {\n                pLogWarning(\"  this is a major divergence.\");\n                diverged_major = true;\n            }\n        }\n    }\n}\n\nstatic void write_NPCs()\n{\n    fprintf(record_file, \" %\" PRId64 \" \\r\\nNPCs\\r\\nnumNPCs %d\\r\\n\", frame_no, numNPCs);\n    for(int i = 1; i <= numNPCs; i++)\n    {\n        const NPC_t& n = NPC[i];\n        fprintf(record_file, \"NPC %d\\r\\n\", i);\n        fprintf(record_file, \"Type %d\\r\\n\", n.Type);\n        fprintf(record_file, \"Active %d\\r\\n\", n.Active);\n        fprintf(record_file, \"Dir %lf\\r\\n\", (double)n.Direction);\n        fprintf(record_file, \"XYWH %lf %lf %lf %lf\\r\\n\", (double)n.Location.X, (double)n.Location.Y, (double)n.Location.Width, (double)n.Location.Height);\n        fprintf(record_file, \"S %lf %lf %lf %lf %lf %lf %lf\\r\\n\", (double)n.Special, (double)n.Special2, (double)n.Special3, (double)n.Special4, (double)n.Special5, (double)n.SpecialX, (double)n.SpecialY);\n    }\n}\n\nstatic void read_NPCs()\n{\n    int success = 0;\n\n    fscanf(replay_file, \"NPCs\\r\\n%n\", &success);\n\n    // will only possibly be used in the cases where it is initialized by fscanf\n    int o_numNPCs = 0;\n\n    if(!success || fscanf(replay_file, \"numNPCs %d\\r\\n\", &o_numNPCs) != 1)\n        success = 0;\n    else if(o_numNPCs != numNPCs)\n    {\n        pLogWarning(\"numNPCs diverged (old %d, new %d) at frame %\" PRId64 \".\", o_numNPCs, numNPCs, frame_no);\n        diverged_major = true;\n    }\n\n    if(success)\n    {\n        for(int i = 1; i <= o_numNPCs; i++)\n        {\n            int N, T, A;\n            double D, X, Y, W, H, S1, S2, S3, S4, S5, S6, S7;\n            bool invalid = false;\n\n            invalid |= (fscanf(replay_file,\n                               \"NPC %d\\r\\n\"\n                               \"Type %d\\r\\n\"\n                               \"Active %d\\r\\n\",\n                               &N, &T, &A) != 3);\n            invalid |= fscanf(replay_file,\n                              \"Dir %lf\\r\\n\"\n                              \"XYWH %lf %lf %lf %lf\\r\\n\"\n                              \"S %lf %lf %lf %lf %lf %lf\",\n                              &D, &X, &Y, &W, &H, &S1, &S2, &S3, &S4, &S5, &S6) != 11;\n\n            if(invalid)\n            {\n                pLogWarning(\"old gameplay file diverged (invalid NPC %d data) at frame %\" PRId64 \".\", i, frame_no);\n                diverged_major = true;\n                return;\n            }\n\n            // either '\\r' (no S7) or ' ' (S7)\n            if(fgetc(replay_file) == ' ')\n            {\n                fscanf(replay_file, \"%lf\\r\\n\", &S7);\n            }\n            else\n            {\n                S7 = 0;\n                fgetc(replay_file); // '\\n'\n            }\n\n            if(N != i)\n            {\n                pLogWarning(\"old gameplay file diverged (NPC %d index listed as %d) at frame %\" PRId64 \".\", i, N, frame_no);\n                diverged_major = true;\n                continue;\n            }\n\n            if(i > numNPCs)\n                continue;\n\n            const NPC_t& n = NPC[i];\n\n            if(T != n.Type)\n            {\n                pLogWarning(\"NPC[%d].Type diverged (old %d, new %d) at frame %\" PRId64 \".\", i, T, n.Type, frame_no);\n                diverged_major = true;\n            }\n\n            if((bool)A != n.Active)\n            {\n                pLogWarning(\"NPC[%d].Active diverged (old %d, new %d; type %d) at frame %\" PRId64 \".\", i, A, n.Active, n.Type, frame_no);\n                diverged_minor = true;\n            }\n\n            if(D != n.Direction)\n            {\n                pLogWarning(\"NPC[%d].Direction diverged (old %f, new %d; type %d) at frame %\" PRId64 \".\", i, D, n.Direction, n.Type, frame_no);\n                diverged_minor = true;\n            }\n\n            if(num_t::abs(num_t::from_double(X) - n.Location.X) > 0.01_n ||\n               num_t::abs(num_t::from_double(Y) - n.Location.Y) > 0.01_n ||\n               num_t::abs(num_t::from_double(W) - n.Location.Width) > 0.01_n ||\n               num_t::abs(num_t::from_double(H) - n.Location.Height) > 0.01_n)\n            {\n                pLogWarning(\"NPC[%d].Location diverged (old %lf %lf %lf %lf, new %lf %lf %lf %lf; type %d) at frame %\" PRId64 \".\", i,\n                    X, Y, W, H, (double)n.Location.X, (double)n.Location.Y, (double)n.Location.Width, (double)n.Location.Height, n.Type, frame_no);\n                diverged_minor = true;\n            }\n\n            num_t sOld[] = {num_t::from_double(S1), num_t::from_double(S2), num_t::from_double(S3), num_t::from_double(S4), num_t::from_double(S5), num_t::from_double(S6), num_t::from_double(S7)};\n            num_t sNew[] = {n.Special, n.Special2, n.Special3, n.Special4, n.Special5, n.SpecialX, n.SpecialY};\n\n            for(int s = 0; s < 7; ++s)\n            {\n                if(!num_t::fEqual_d(sOld[s], sNew[s]))\n                {\n                    pLogWarning(\"NPC[%d].Special%d diverged (old %f => new %f; type %d) at frame %\" PRId64 \".\",\n                                i, s + 1, (double)sOld[s], (double)sNew[s], n.Type, frame_no);\n                    diverged_minor = true;\n                }\n            }\n\n//            if(S1 != n.Special || S2 != n.Special2 || S3 != n.Special3 || S4 != n.Special4 || S5 != n.Special5 || S6 != n.Special6 || S7 != n.Special7)\n//            {\n//                pLogWarning(\"NPC[%d].Special* diverged (old %f %f %f %f %f %f %f, new %f %f %f %f %f %f %f; type %d) at frame %\" PRId64 \".\", i,\n//                    S1, S2, S3, S4, S5, S6, S7, n.Special, n.Special2, n.Special3, n.Special4, n.Special5, n.Special6, n.Special7, n.Type, frame_no);\n//                diverged_minor = true;\n//            }\n        }\n    }\n\n    if(!success)\n    {\n        pLogWarning(\"old gameplay file diverged (invalid NPC header) at frame %\" PRId64 \".\", frame_no);\n        diverged_major = true;\n        return;\n    }\n}\n\nvoid InitRecording()\n{\n    if(LevelEditor || GameMenu || GameOutro || XMessage::Status() != XMessage::Status::local)\n        return;\n\n    if(!g_config.record_gameplay_data && !replay_file)\n        return;\n\n    in_level = true;\n    diverged_major = false;\n    diverged_minor = false;\n    frame_no = 0;\n    next_record_frame = -1;\n    last_status_tick = SDL_GetTicks();\n    g_stats.renderedNPCs = 0;\n    g_stats.renderedBlocks = 0;\n    g_stats.renderedBGOs = 0;\n\n    std::string filename = makeRecordPrefix();\n\n    if(!record_file)\n        record_file = Files::utf8_fopen(filename.c_str(), \"wb\");\n\n    // start of gameplay data\n    seedRandom(iRand(32767));\n\n    if(replay_file)\n    {\n        read_header();\n        if(!fscanf(replay_file, \"%\" PRId64 \"\\r\\n\", &next_record_frame))\n        {\n            pLogWarning(\"Replayed recording file has prematurely ended.\");\n            diverged_major = true;\n            EndRecording();\n        }\n    }\n\n    if(record_file)\n        write_header();\n\n    for(int i = 0; i < numPlayers; i++)\n        last_controls[i] = Controls_t();\n}\n\n// need to preload level info from the replay to load with proper compat\nvoid LoadReplay(const std::string &recording_path, const std::string &level_path)\n{\n    if(LevelEditor || GameMenu || GameOutro)\n        return;\n\n    replayLevelFilePath = level_path;\n\n    // figure out how many runs have already happened\n    if(!replay_file)\n    {\n        replay_file = Files::utf8_fopen(recording_path.c_str(), \"rb\");\n        if(replay_file)\n            read_header();\n    }\n}\n\nvoid EndRecording()\n{\n    if(!record_file && !replay_file)\n        return;\n\n    in_level = false;\n\n    if(record_file)\n        write_end();\n\n    if(replay_file)\n    {\n        read_end();\n\n        if(!diverged_minor && !diverged_major)\n        {\n            pLogDebug(\"CONGRATULATIONS! Your build's run did not diverge from the old run.\");\n            printf(\"CONGRATULATIONS! Your build's run did not diverge from the old run.\\n\");\n\n            if(record_file)\n                fprintf(record_file, \"DID NOT diverge from old run.\\r\\n\");\n        }\n        else if(!diverged_major)\n        {\n            pLogDebug(\"Your build's run only had MINOR divergence from the old run.\");\n            printf(\"Your build's run only had MINOR divergence from the old run.\\n\");\n\n            if(record_file)\n                fprintf(record_file, \"MINOR divergence from old run.\\r\\n\");\n        }\n        else\n        {\n            pLogWarning(\"I'm sorry, but your build's run DIVERGED from the old run.\");\n            printf(\"I'm sorry, but your build's run DIVERGED from the old run.\\n\");\n            if(record_file)\n                fprintf(record_file, \"DIVERGED from old run.\\r\\n\");\n        }\n\n        fclose(replay_file);\n        replay_file = nullptr;\n\n        GameIsActive = false;\n    }\n\n    if(record_file)\n    {\n        fclose(record_file);\n        record_file = nullptr;\n    }\n}\n\nvoid Sync()\n{\n    if(!record_file && !replay_file)\n        return; // Do nothing\n\n    if(replay_file)\n    {\n        while(next_record_frame == frame_no && replay_file)\n        {\n            int type = fgetc(replay_file);\n            fseek(replay_file, -1, SEEK_CUR);\n\n            if(type == 'S')\n                read_status();\n            else if(type == 'E')\n                read_end();\n            else if(type == 'N')\n                read_NPCs();\n            else if(type == 'C')\n                read_control();\n            else if(!feof(replay_file))\n            {\n                pLogWarning(\"Invalid record type %c in replayed recording file.\", type);\n                diverged_major = true;\n                EndRecording();\n                return;\n            }\n\n            if(feof(replay_file) || fscanf(replay_file, \"%\" PRId64 \"\\r\\n\", &next_record_frame) != 1)\n            {\n                pLogWarning(\"Replayed recording file has prematurely ended.\");\n                diverged_major = true;\n                EndRecording();\n                return;\n            }\n        }\n\n        for(int i = 0; i < numPlayers; i++)\n            Player[i+1].Controls = last_controls[i];\n    }\n\n    if(record_file)\n    {\n        write_control();\n\n        if(!(frame_no % 60))\n            write_status();\n\n        if(!(frame_no % 900))\n            write_NPCs();\n    }\n\n    frame_no++;\n}\n\n} // namespace Record\n\n#if defined(X_GCC_NO_WARNING_FORMAT) || defined(XTECH_GCC_UNUSED_RESULT_IGNORED)\n#   pragma GCC diagnostic pop\n#endif\n"
  },
  {
    "path": "src/main/record.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n// this module handles the control recording and playback functions\n// and the gameplay stats recording functions\n\n#pragma once\n#ifndef RECORD_H\n#define RECORD_H\n\n#include <string>\n\nnamespace Record\n{\n\n// public to allow other systems to spy on status of the Record system\nextern FILE* record_file;\nextern FILE* replay_file;\n\nvoid LoadReplay(const std::string &recording_path, const std::string &level_path);\n\nvoid InitRecording();\n\nvoid Sync();\n\nvoid EndRecording();\n\n} // namespace Record\n\n#endif // #ifndef RECORD_H\n"
  },
  {
    "path": "src/main/screen_asset_pack.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n#include <fmt_format_ne.h>\n#include <Integrator/integrator.h>\n\n#include \"Archives/archives.h\"\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"main/screen_asset_pack.h\"\n#include \"main/asset_pack.h\"\n\n#include \"core/render.h\"\n\n#include \"sound.h\"\n#include \"controls.h\"\n#include \"draw_planes.h\"\n#include \"graphics.h\"\n#include \"gfx.h\"\n#include \"game_main.h\"\n\nvoid drawGameVersion(bool disable_git, bool git_only);\n\nnamespace ScreenAssetPack\n{\n\nbool g_LoopActive = false;\nbool s_AnimatingBack = false;\n\n// the player's target asset pack index\nstatic int s_target_idx = -1;\n\n// the current asset pack index\nstatic int s_cur_idx = -1;\n\n// how far to scroll the logos. ranges from -1 (halfway through a right rotation) to +1 (halfway through a left rotation)\nstatic num_t s_switch_coord = 0.0_n;\n\nstatic bool s_ensure_idx_valid()\n{\n    const auto& asset_packs = GetAssetPacks();\n\n    if(s_cur_idx >= 0 && s_cur_idx < (int)asset_packs.size())\n        return true;\n\n    s_cur_idx = 0;\n\n    std::string real_app_path;\n\n    // restore real path from loaded assets\n    if(AppPath[0] == ':' && AppPath[1] == 'a')\n    {\n        real_app_path.push_back('@');\n        real_app_path.append(Archives::assets_archive_path());\n        real_app_path.push_back(':');\n        real_app_path.append(AppPath.c_str() + 2, AppPath.size() - 2);\n    }\n    else\n        real_app_path = AppPath;\n\n    for(const auto& pack : asset_packs)\n    {\n        if(pack.path == real_app_path && pack.full_id() == g_AssetPackID)\n        {\n            if(s_target_idx == -1)\n                s_target_idx = s_cur_idx;\n\n            return true;\n        }\n\n        s_cur_idx++;\n    }\n\n    return false;\n}\n\nstatic void s_renderBackground(AssetPack_t::Gfx& gfx, XTColor bg_color)\n{\n    if(gfx.background.inited)\n    {\n        int background_height = gfx.background.h / gfx.bg_frames;\n        int background_frame = (CommonFrame / gfx.bg_frame_ticks) % gfx.bg_frames;\n\n        int show_width = SDL_min(background_height * XRender::TargetW / XRender::TargetH, gfx.background.w);\n        int show_height = SDL_min(gfx.background.w * XRender::TargetH / XRender::TargetW, background_height);\n\n        XRender::renderTextureScaleEx(0, 0, XRender::TargetW, XRender::TargetH,\n                                      gfx.background,\n                                      (gfx.background.w - show_width) / 2, background_height * background_frame + (background_height - show_height) / 2,\n                                      show_width, show_height,\n                                      0, nullptr, X_FLIP_NONE,\n                                      bg_color);\n    }\n    else\n    {\n        XRender::renderRect(0, 0, XRender::TargetW, XRender::TargetH, XTColor(0, 0, 0) * bg_color);\n    }\n}\n\n// this method is public so that it is possible to fade in the asset pack screen from the main menu\nvoid DrawBackground(int fade)\n{\n    if(!s_ensure_idx_valid())\n        return;\n\n    // used for things that are scaled in special ways during switch\n    XTColor color_no_switch = XTAlpha(fade * 4 - 1);\n\n    // used for things linked to the central pack\n    XTColor color = XTAlphaF(fade * (1.0_n - num_t::abs(s_switch_coord)) / 64);\n\n    const AssetPack_t& pack = GetAssetPacks()[s_cur_idx];\n\n    if(!pack.gfx)\n        return;\n\n    int menu_logo_y = XRender::TargetH / 2 - 230;\n\n    // place manually on small screens\n    if(XRender::TargetH < 400)\n        menu_logo_y = 16 + (XRender::TargetH - 320) / 2;\n    else if(XRender::TargetH < 500)\n        menu_logo_y = 16;\n    else if(XRender::TargetH <= 600)\n        menu_logo_y = 40;\n\n    // logic for the menu curtain draw\n    int curtain_draw_w = GFX.MenuGFX[1].w;\n    if(curtain_draw_w == 800)\n        curtain_draw_w = 768;\n    int curtain_horiz_reps = XRender::TargetW / curtain_draw_w + 2;\n\n    // draw background curtain fade\n    if(s_AnimatingBack)\n    {\n        for(int i = 0; i < curtain_horiz_reps; i++)\n            XRender::renderTextureBasic(curtain_draw_w * i, -(GFX.MenuGFX[1].h * fade / 64), curtain_draw_w, GFX.MenuGFX[1].h, GFX.MenuGFX[1], 0, 0);\n    }\n\n    // draw background logo\n    if(s_AnimatingBack || pack.logo_override)\n        XRender::renderTextureBasic(XRender::TargetW / 2 - GFX.MenuGFX[2].w / 2, menu_logo_y, GFX.MenuGFX[2]);\n\n    AssetPack_t::Gfx& gfx = *pack.gfx;\n\n#ifdef __3DS__\n    XRender::setTargetLayer(2);\n#endif\n\n    // draw background\n    s_renderBackground(gfx, color_no_switch);\n\n    // draw curtain during transition\n    if(!g_LoopActive && !s_AnimatingBack)\n    {\n        for(int i = 0; i < curtain_horiz_reps; i++)\n            XRender::renderTextureBasic(curtain_draw_w * i, -(GFX.MenuGFX[1].h * fade / 64), curtain_draw_w, GFX.MenuGFX[1].h, GFX.MenuGFX[1], 0, 0);\n    }\n\n    // show previous / next packs\n    int prev_index = s_cur_idx - 1;\n    int next_index = s_cur_idx + 1;\n\n    if(prev_index < 0)\n        prev_index = GetAssetPacks().size() - 1;\n    if(next_index >= (int)GetAssetPacks().size())\n        next_index = 0;\n\n    const AssetPack_t& prev_pack = GetAssetPacks()[prev_index];\n    const AssetPack_t& next_pack = GetAssetPacks()[next_index];\n\n    // cross-fade background\n    if(s_switch_coord > 0 && prev_pack.gfx)\n    {\n        XTColor bg_color = color_no_switch * XTAlphaF(s_switch_coord / 2);\n        s_renderBackground(*prev_pack.gfx, bg_color);\n    }\n    else if(s_switch_coord < 0 && next_pack.gfx)\n    {\n        XTColor bg_color = color_no_switch * XTAlphaF(-s_switch_coord / 2);\n        s_renderBackground(*next_pack.gfx, bg_color);\n    }\n\n#ifdef __3DS__\n    XRender::setTargetLayer(3);\n\n    // fade the existing display\n    if(!g_LoopActive && XRender::TargetH <= 480)\n        XRender::targetFade(256 - fade * 4);\n#endif\n\n    // calculate how much the pack logos should be shifted\n    int logo_shift = (XRender::TargetW / 2) - 100;\n\n    if(prev_pack.gfx && prev_pack.gfx->logo.inited)\n    {\n        num_t cX = 100 + logo_shift * s_switch_coord / 2;\n        XTColor pack_color = (s_switch_coord == 0) ? color * XTAlpha(127) : color_no_switch * XTAlphaF(0.25_n + (s_switch_coord + 1.0_n) / 4);\n        XRender::renderTextureBasic((int)(cX + prev_pack.gfx->logo.w * (s_switch_coord / 4 - 1)),\n            XRender::TargetH / 2 - prev_pack.gfx->logo.h / 2,\n            prev_pack.gfx->logo,\n            pack_color);\n    }\n\n    if(next_pack.gfx && next_pack.gfx->logo.inited)\n    {\n        num_t cX = (XRender::TargetW - 100) + logo_shift * s_switch_coord / 2;\n        XTColor pack_color = (s_switch_coord == 0) ? color * XTAlpha(127) : color_no_switch * XTAlphaF(0.25_n + (1.0_n - s_switch_coord) / 4);\n        XRender::renderTextureBasic((int)(cX + prev_pack.gfx->logo.w * (s_switch_coord / 4)),\n            XRender::TargetH / 2 - next_pack.gfx->logo.h / 2,\n            next_pack.gfx->logo,\n            pack_color);\n    }\n\n    // draw current logo in all cases (needed for cases where title card is not in GFX.MenuGFX[2])\n    if(!gfx.logo.inited)\n    {\n        SuperPrintScreenCenter(pack.full_id(), 3, XRender::TargetH / 2 - 20, color);\n        SuperPrintScreenCenter(pack.path, 3, XRender::TargetH / 2 + 2, color);\n    }\n    else if(g_LoopActive || s_AnimatingBack || pack.logo_override)\n    {\n        num_t cX = (XRender::TargetW / 2) + logo_shift * s_switch_coord / 2;\n        XTColor main_color = color_no_switch * XTAlphaF(0.75_n + (1.0_n - num_t::abs(s_switch_coord)) / 4);\n        XRender::renderTextureBasic((int)(cX + gfx.logo.w * (s_switch_coord / 4 - 0.5_n)), XRender::TargetH / 2 - gfx.logo.h / 2, gfx.logo, main_color);\n    }\n    else\n    {\n        num_t cX = (XRender::TargetW / 2) + logo_shift * s_switch_coord * fade / (64 * 2);\n\n        int center_Y = XRender::TargetH / 2 - gfx.logo.h / 2;\n        int place_Y = (center_Y * fade / 64) + (menu_logo_y * (64 - fade) / 64);\n\n        XRender::renderTextureBasic((int)(cX + gfx.logo.w * (s_switch_coord * fade / (64 * 4) - 0.5_n)),\n            place_Y,\n            gfx.logo);\n    }\n\n    // show version if appropriate\n    bool show_version = pack.show_version && !pack.version.empty();\n    if(pack.show_id || show_version || pack.id.empty())\n    {\n        num_t cX = (XRender::TargetW / 2) + logo_shift * s_switch_coord / 2;\n        int text_Y = XRender::TargetH / 2 + gfx.logo.h / 2 + 8;\n\n        const std::string& display = (pack.show_id) ? pack.full_id() : ((show_version) ? pack.version : \"<legacy>\");\n        SuperPrintCenter(display, 3, (int)cX, text_Y, color);\n    }\n\n    // show scroll indicators\n    if(g_LoopActive && CommonFrame % 90 >= 45 && s_cur_idx == s_target_idx && s_switch_coord == 0)\n    {\n        int offset = SDL_min(XRender::TargetW / 2 - 8, 250);\n        if(GFX.CharSelIcons.inited)\n        {\n            XRender::renderTextureFL(XRender::TargetW / 2 - offset, XRender::TargetH / 2 - 24 / 2, 24, 24, GFX.CharSelIcons, 72, 0, 0, nullptr, X_FLIP_HORIZONTAL);\n            XRender::renderTextureBasic(XRender::TargetW / 2 + offset - 24, XRender::TargetH / 2, 24, 24, GFX.CharSelIcons, 72, 0);\n        }\n        else\n        {\n            XRender::renderTextureFL(XRender::TargetW / 2 - offset, XRender::TargetH / 2 - GFX.MCursor[1].w / 2, GFX.MCursor[1].w, GFX.MCursor[1].h, GFX.MCursor[1], 0, 0, -90);\n            XRender::renderTextureFL(XRender::TargetW / 2 + offset - GFX.MCursor[1].h, XRender::TargetH / 2 - GFX.MCursor[2].w / 2, GFX.MCursor[2].w, GFX.MCursor[2].h, GFX.MCursor[2], 0, 0, -90);\n        }\n    }\n\n    drawGameVersion(true, false);\n\n    // reset all variables when alpha is low enough during exit\n    if(fade < 3)\n    {\n        s_AnimatingBack = false;\n        s_cur_idx = -1;\n        s_target_idx = -1;\n        s_switch_coord = 0;\n    }\n\n    if(g_LoopActive)\n        s_AnimatingBack = false;\n}\n\nvoid Render(bool now_loading = false)\n{\n    XRender::setTargetTexture();\n    XRender::resetViewport();\n    XRender::clearBuffer();\n\n    XRender::setDrawPlane(PLANE_GAME_MENUS);\n\n    CommonFrame++;\n\n    DrawBackground(64);\n\n    // Mouse cursor\n    XRender::renderTextureBasic(int(SharedCursor.X), int(SharedCursor.Y), GFX.ECursor[2]);\n\n    DrawDeviceBattery();\n\n    if(now_loading)\n    {\n        int R = XRender::TargetW / 2 + 400;\n        int B = XRender::TargetH / 2 + 300;\n\n        if(R > XRender::TargetW)\n            R = XRender::TargetW;\n\n        if(B > XRender::TargetH)\n            B = XRender::TargetH;\n\n        R -= (GFX.Loader.w + 50);\n        B -= (GFX.Loader.h + 8);\n\n        XRender::renderTextureBasic(R, B, GFX.Loader);\n    }\n\n    XRender::repaint();\n\n    if(TakeScreen)\n        ScreenShot();\n}\n\nbool Logic()\n{\n    // escape if the current asset pack no longer exists\n    if(!s_ensure_idx_valid())\n    {\n        g_LoopActive = false;\n        GameMenu = true;\n        return true;\n    }\n\n    if(!SharedCursor.Primary && !SharedCursor.Secondary)\n        MenuMouseRelease = true;\n    // replicates legacy behavior allowing clicks to be detected\n    if(SharedCursor.Primary || SharedCursor.Secondary || SharedCursor.Tertiary)\n        SharedCursor.Move = true;\n\n    MenuControls_t menuControls = Controls::GetMenuControls();\n\n    menuControls.Left |= SharedCursor.ScrollUp;\n    menuControls.Right |= SharedCursor.ScrollDown;\n\n    menuControls.Back |= (SharedCursor.Secondary && MenuMouseRelease);\n\n    if(menuControls.Back && menuControls.Do)\n        menuControls.Do = false;\n\n    if(!MenuCursorCanMove)\n    {\n        bool k = false;\n        k |= menuControls.Back;\n        k |= menuControls.Do;\n        k |= menuControls.Left;\n        k |= menuControls.Right;\n\n        if(!k)\n            MenuCursorCanMove = true;\n    }\n    else if(menuControls.Do)\n    {\n        MenuCursorCanMove = false;\n        MenuMouseRelease = false;\n\n        // check if we are still on the same asset pack\n        int cur_idx = s_cur_idx;\n        s_cur_idx = -1;\n        s_ensure_idx_valid();\n\n        // if not still on the same asset pack, reload assets!!\n        if(cur_idx != s_cur_idx || !g_AssetsLoaded)\n        {\n            s_cur_idx = cur_idx;\n\n            if(!g_AssetsLoaded)\n            {\n                XRender::setTargetTexture();\n                XRender::clearBuffer();\n                XRender::repaint();\n            }\n            else\n                Render(true);\n\n            CommonFrame = 0; // don't show indicators\n            if(!ReloadAssetsFrom(GetAssetPacks()[cur_idx]))\n            {\n                s_cur_idx = -1;\n                s_target_idx = -1;\n\n                // stay in the asset pack screen\n                return false;\n            }\n            else\n            {\n                // update recent asset pack\n                SaveConfig();\n            }\n        }\n        else\n            PlaySoundMenu(SFX_Do);\n\n        // proceed to game menu\n        g_LoopActive = false;\n        GameMenu = true;\n        MenuCursor = 0;\n        return true;\n    }\n    else if(menuControls.Back && g_AssetsLoaded)\n    {\n        PlaySoundMenu(SFX_Slide);\n\n        // check if we are still on the same asset pack\n        int cur_idx = s_cur_idx;\n        s_cur_idx = -1;\n        s_ensure_idx_valid();\n\n        // if not still on the same asset pack, animate the back\n        if(cur_idx != s_cur_idx)\n            s_AnimatingBack = true;\n\n        s_cur_idx = cur_idx;\n\n        // return to game menu\n        g_LoopActive = false;\n        GameMenu = true;\n        MenuCursorCanMove = false;\n        MenuMouseRelease = false;\n        return true;\n    }\n    else if(menuControls.Left)\n    {\n        MenuCursorCanMove = false;\n\n        PlaySoundMenu(SFX_Climbing);\n        s_target_idx--;\n    }\n    else if(menuControls.Right)\n    {\n        MenuCursorCanMove = false;\n\n        PlaySoundMenu(SFX_Climbing);\n        s_target_idx++;\n    }\n\n    if(SharedCursor.Primary || SharedCursor.Secondary)\n        MenuMouseRelease = false;\n\n    MenuMouseClick = false;\n\n    // 16 frames to switch\n    constexpr num_t move_rate = 0.125_n;\n\n    // make s_switch_coord approach the target\n    if(s_target_idx != s_cur_idx)\n        s_switch_coord -= move_rate * (s_target_idx - s_cur_idx);\n    else if(s_switch_coord < -move_rate)\n        s_switch_coord += move_rate;\n    else if(s_switch_coord > move_rate)\n        s_switch_coord -= move_rate;\n    else\n        s_switch_coord = 0.0_n;\n\n    // actually switch the index when switch coord grows enough\n    if(s_switch_coord >= 1.0_n)\n    {\n        s_cur_idx--;\n        if(s_cur_idx < 0)\n        {\n            if(s_target_idx < 0)\n                s_target_idx += (int)GetAssetPacks().size();\n\n            s_cur_idx = (int)GetAssetPacks().size() - 1;\n        }\n\n        s_switch_coord -= 2.0_n;\n    }\n    else if(s_switch_coord <= -1.0_n)\n    {\n        s_cur_idx++;\n        if(s_cur_idx >= (int)GetAssetPacks().size())\n        {\n            if(s_target_idx >= (int)GetAssetPacks().size())\n                s_target_idx -= (int)GetAssetPacks().size();\n\n            s_cur_idx = 0;\n        }\n\n        s_switch_coord += 2.0_n;\n    }\n\n    return false;\n}\n\nvoid Loop()\n{\n    Controls::PollInputMethod();\n    Controls::Update(false);\n\n    if(Logic())\n        return;\n\n    UpdateSound();\n\n    Integrator::sync();\n\n    Render();\n}\n\n} // namespace ScreenAssetPack\n"
  },
  {
    "path": "src/main/screen_asset_pack.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef SCREEN_ASSET_PACK_H\n#define SCREEN_ASSET_PACK_H\n\n#include \"xt_color.h\"\n\n// The asset pack screen is special, because after exiting from it with a success, EVERYTHING gets reloaded. So, it gets its own main loop.\n\nnamespace ScreenAssetPack\n{\n\nextern bool g_LoopActive;\n\n// this method is public so that it is possible to fade in the asset pack screen from the main menu\n// fade goes from 0 to 64\nvoid DrawBackground(int fade);\n\nvoid Loop();\n\n} // namespace ScreenAssetPack\n\n#endif // #ifndef SCREEN_ASSET_PACK_H\n"
  },
  {
    "path": "src/main/screen_connect.cpp",
    "content": "#include <fmt_format_ne.h>\n\n#include \"core/render.h\"\n\n#include \"global_constants.h\"\n#include \"message.h\"\n#include \"controls.h\"\n#include \"sound.h\"\n#include \"globals.h\"\n#include \"gfx.h\"\n#include \"graphics.h\"\n#include \"player.h\"\n#include \"config.h\"\n#include \"npc_id.h\"\n#include \"npc/npc_queues.h\"\n\n#include \"graphics/gfx_marquee.h\"\n\n#include \"main/game_info.h\"\n\n#include \"main/screen_connect.h\"\n#include \"main/screen_quickreconnect.h\"\n#include \"main/menu_main.h\"\n#include \"main/game_strings.h\"\n#include \"main/speedrunner.h\"\n\n#include \"control/controls_methods.h\" // to cancel keyboard's double-click fullscreen\n\nstatic inline void s_cancelDoubleClick()\n{\n#ifdef KEYBOARD_H\n    Controls::g_cancelDoubleClick = true;\n#endif\n}\n\nnamespace ConnectScreen\n{\n\nenum class PlayerState\n{\n    Disconnected,   // missing controller for player\n    SelectChar,     // left side of main screen\n    ControlsMenu,   // right side of main screen\n    Reconnecting,   // player requested to reconnect controller\n    SelectProfile,  // selecting controls profile\n    ConfirmProfile, // checking that player understands controls profile\n    ForceDrop,      // player wants to force disconnected other players to drop (to leave drop/add screen)\n};\n\nenum class Context\n{\n    MainMenu,\n    LegacyMenu,\n    DropAdd,\n};\n\nstatic Context s_context;\nstatic std::array<uint8_t, maxLocalPlayers> s_recent_char{};\n\nstruct CharInfo\n{\nprivate:\n    bool char_present[numCharacters];\n\npublic:\n    int dead_count = 0;\n    int max_players = 0;\n\n    bool accept(int c, int p, uint8_t current_add)\n    {\n        if(c < 1 || c > numCharacters)\n            return false;\n\n        // don't accept a blocked char, unless it has been present before\n        if(blockCharacter[c] && !(s_context == Context::DropAdd && this->char_present[c - 1]))\n            return false;\n\n        // allow everything if char swap is allowed\n        if(SwapCharAllowed())\n            return true;\n\n        // automatically recognize novel adds\n        if(p >= this->max_players)\n            current_add = 2;\n\n        // if not a current add, don't allow changing character\n        if(!current_add)\n            return false;\n\n        bool novel_add = (current_add >= 2);\n\n        // require the character to have been selected previously if the player is not a novel add\n        if(!novel_add && !this->char_present[c - 1])\n            return false;\n\n        return true;\n    }\n\n    void reset()\n    {\n        for(int c = 0; c < numCharacters; c++)\n            char_present[c] = false;\n\n        max_players = l_screen->player_count;\n        dead_count = 0;\n    }\n\n    void mark_present()\n    {\n        for(int p = 0; p < l_screen->player_count; p++)\n        {\n            s_recent_char[p] = Player[l_screen->players[p]].Character;\n            int c = Player[l_screen->players[p]].Character - 1;\n\n            if(0 <= c && c < numCharacters)\n                char_present[c] = true;\n        }\n\n        if(max_players < l_screen->player_count)\n            max_players = l_screen->player_count;\n    }\n\n    // checks if the active players' characters have been changed by gameplay logic (not drop/add)\n    bool chars_changed()\n    {\n        for(int p = 0; p < l_screen->player_count; p++)\n        {\n            int c = Player[l_screen->players[p]].Character - 1;\n\n            if(0 <= c && c < numCharacters)\n            {\n                if(!char_present[c])\n                    return true;\n            }\n        }\n\n        return false;\n    }\n\n    // checks if a character has been present\n    bool char_was_present(int c)\n    {\n        if(c < 1 || c > numCharacters)\n            return false;\n\n        return this->char_present[c - 1];\n    }\n\n    // marks a single character as present\n    void mark_char_present(int c)\n    {\n        if(c < 1 || c > numCharacters)\n            return;\n\n        this->char_present[c - 1] = true;\n    }\n};\n\nstruct PlayerBox\n{\n    PlayerState m_state = PlayerState::Disconnected;\n    bool m_input_ready = false;\n\n    // set if the player was added during this invocation of the Drop/Add screen; set to 2 if a novel add (the number of players is greater than it had been previously)\n    uint8_t m_current_add = 0;\n\nprivate:\n    // just added a player in multiplayer menu char select: play DropItem if appropriate, and block the start and back keys for non-P1 players\n    bool m_just_added = false;\n\n    // progress towards entering the Konami cheat to set g_forceCharacter\n    uint8_t m_konami_bits = false;\n\n    //! multiuse tracker for current menu item / action confirm progress\n    int m_menu_item = 0;\n\n    // state of hint text marquee\n    MarqueeState m_marquee_state;\n\n    Controls::InputMethodProfile* m_last_profile = nullptr;\n\n    // calculates the player index using pointer arithmetic\n    int CalcIndex() const;\n\n    // checks if a character is validly available for this player\n    bool CharAvailable(int c, bool ghost_mode = false);\n\n    // adds player if not present, and updates player's character\n    void UpdatePlayer();\n\n    // makes sure that l_screen->charSelect[p] is valid\n    void ValidateChar(bool ghost_mode = false);\n\n    // does logic for an item the mouse is currently over\n    bool MouseItem(int i);\n\n    // do all mouse logic and rendering for an item at a certain location\n    bool MenuItem_Mouse_Render(int i, const std::string& label, int X, int Y, bool mouse, bool render, int end_X = 0);\n\n    // draws the selected character at a location\n    bool DrawChar(int x, int w, int y, int h, bool show_name);\n\n    // do rendering and mouse logic for SinglePlayer char select\n    // returns -1 if player is ready to return, +1 if player is ready to proceed\n    int Mouse_Render_1P(bool render);\n\npublic:\n    // initialize the state for the current context\n    void Init();\n\n    // do rendering and mouse logic\n    // returns -1 if player is ready to return, +1 if player is ready to proceed\n    int Mouse_Render(bool render, int x, int y, int w, int h);\n\n    // returns -1 if player is ready to return, +1 if player is ready to proceed\n    int Logic();\n\n    // check if it is possible to rotate char\n    bool CanChangeChar();\n\n    // rotate character\n    void PrevChar();\n    void NextChar();\n\n    // registers the appropriate cursor input\n    void Left();\n    void Right();\n\n    void Up();\n    void Down();\n\n    // returns true if this player is ready to return to previous menu\n    bool Back();\n\n    // returns true if this player is ready to proceed\n    bool Do();\n\n    // returns true if this player is ready to proceed\n    bool Ready() const;\n};\n\nstatic bool s_controls_dirty = false;\nstatic int s_minPlayers = 1;\n\nstatic std::array<PlayerBox, maxLocalPlayers> s_players;\n\nstatic CharInfo s_char_info;\n\nstatic void s_logRecentChars()\n{\n    // reset recent chars to 0\n    s_recent_char = {};\n\n    // update recent characters\n    for(size_t i = 0; i < maxLocalPlayers; i++)\n    {\n        if(s_players[i].m_state != PlayerState::Disconnected)\n            s_recent_char[i] = l_screen->charSelect[i];\n    }\n\n    // mark input methods as used\n    for(Controls::InputMethod* method : Controls::g_InputMethods)\n    {\n        if(method)\n            method->used_for_player = true;\n    }\n\n    // if controls dirty, save them\n    if(s_controls_dirty)\n    {\n        Controls::SaveConfig();\n        s_controls_dirty = false;\n    }\n}\n\nstatic inline void Render_PCursor(int x, int y)\n{\n    if(GFX.PCursor.inited)\n        XRender::renderTextureBasic(x, y, GFX.PCursor);\n    else\n        XRender::renderTextureFL(x, y, GFX.MCursor[1].w, GFX.MCursor[1].h, GFX.MCursor[1], 0, 0, 90, nullptr, X_FLIP_NONE);\n}\n\nstatic inline void Render_PCursorDownwards(int x, int y, XTColor color = XTColor())\n{\n    if(GFX.PCursor.inited)\n        XRender::renderTextureFL(x - GFX.PCursor.h / 2, y - GFX.PCursor.w, GFX.PCursor.w, GFX.PCursor.h, GFX.PCursor, 0, 0, 90, nullptr, X_FLIP_NONE, color);\n    else\n        XRender::renderTextureBasic(x - GFX.MCursor[2].w / 2, y - GFX.MCursor[2].h, GFX.MCursor[2], color);\n}\n\nstatic inline bool Is1P()\n{\n    return s_context == Context::LegacyMenu;\n}\n\nstatic inline bool IsMenu()\n{\n    return (s_context == Context::MainMenu || s_context == Context::LegacyMenu);\n}\n\n// check if PlayerBox p (zero-indexed) can drop self\nstatic inline bool ControlsMenu_ShowDrop(int p)\n{\n    // don't allow dropping below required player count\n    if(s_context == Context::DropAdd && l_screen->player_count <= s_minPlayers)\n        return false;\n\n    // never show drop icon at main menu\n    if(s_context == Context::MainMenu)\n        return false;\n\n    int plr_A = l_screen->players[p];\n\n    // check that there is a different living player\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        if(B == plr_A)\n            continue;\n\n        if(!Player[B].Dead && Player[B].TimeToLive == 0)\n            return true;\n    }\n\n    return false;\n}\n\nstatic inline int BoxCount(bool strict = false)\n{\n    int n = (int)Controls::g_InputMethods.size();\n\n    if(!BattleMode)\n        n += 1;\n\n    if(n < s_minPlayers)\n        n = s_minPlayers;\n\n    if(s_context == Context::MainMenu && n < 2)\n        n = 2;\n\n    // disable entering >2P in vanilla mode (in-game)\n    if(n > 2 && !g_config.multiplayer_pause_controls)\n        n = 2;\n\n    // disable entering >2P in vanilla mode (main menu)\n    if(n > 2 && s_context == Context::MainMenu && mainMenuPlaystyle() == Config_t::MODE_VANILLA)\n        n = 2;\n\n    // disable entering >1P from legacy menu\n    if(s_context == Context::LegacyMenu)\n        n = 1;\n\n    // disable entering >1P when 2P is disabled\n    if(g_gameInfo.disableTwoPlayer && !g_forceCharacter)\n        n = 1;\n\n    // but do show all players once they are already present\n    if(!strict && n < (int)Controls::g_InputMethods.size())\n        n = (int)Controls::g_InputMethods.size();\n\n    // limit to maxLocalPlayers to prevent out-of-bounds access\n    if(n > maxLocalPlayers)\n        n = maxLocalPlayers;\n\n    return n;\n}\n\nstatic void s_InitBlockCharacter()\n{\n    g_forceCharacter = false;\n    For(A, 1, numCharacters)\n    {\n        if(MenuMode == MENU_CHARACTER_SELECT_NEW_BM)\n            blockCharacter[A] = false;\n        else\n            blockCharacter[A] = SelectWorld[selWorld].blockChar[A];\n    }\n}\n\nvoid MainMenu_Start(int minPlayers)\n{\n    s_minPlayers = minPlayers;\n    s_context = Context::MainMenu;\n\n    // clear input methods if invalid\n    if((int)Controls::g_InputMethods.size() > BoxCount(true))\n        Controls::ClearInputMethods();\n    // confirm that input methods are associated with players\n    else if(Controls::g_InputMethods.size() > 1)\n    {\n        for(size_t i = 0; i < Controls::g_InputMethods.size(); )\n        {\n            if(Controls::g_InputMethods[i] && Controls::g_InputMethods[i]->used_for_player)\n                i++;\n            else\n                Controls::DeleteInputMethodSlot(i);\n        }\n    }\n    else if(Controls::g_InputMethods.size() > 0 && Controls::g_InputMethods[0])\n        Controls::g_InputMethods[0]->used_for_player = true;\n\n    for(int i = 0; i < maxLocalPlayers; i++)\n        l_screen->charSelect[i] = 0;\n\n    if(!(g_forceCharacter && SelectWorld[selWorld].blockChar[s_recent_char[0]]))\n        s_InitBlockCharacter();\n\n    for(int i = 0; i < maxLocalPlayers; i++)\n    {\n        s_players[i] = PlayerBox();\n        s_players[i].Init();\n    }\n\n    MenuMouseRelease = false;\n    MenuCursorCanMove = false;\n\n    s_char_info.reset();\n\n    // prepare for first frame\n    Logic();\n}\n\nvoid LegacyMenu_Start()\n{\n    s_minPlayers = 1;\n    s_context = Context::LegacyMenu;\n\n    // clear input methods if invalid\n    if((int)Controls::g_InputMethods.size() > BoxCount(true))\n        Controls::ClearInputMethods();\n    else if(Controls::g_InputMethods.size() > 0 && Controls::g_InputMethods[0])\n        Controls::g_InputMethods[0]->used_for_player = true;\n\n    for(int i = 0; i < maxLocalPlayers; i++)\n        l_screen->charSelect[i] = 0;\n\n    s_InitBlockCharacter();\n\n    for(int i = 0; i < maxLocalPlayers; i++)\n    {\n        s_players[i] = PlayerBox();\n        s_players[i].Init();\n    }\n\n    // create an empty input slot\n    if(Controls::g_InputMethods.size() == 0)\n        Controls::g_InputMethods.push_back(nullptr);\n\n    s_players[0].m_state = PlayerState::SelectChar;\n\n    MenuMouseRelease = false;\n    MenuCursorCanMove = false;\n\n    s_char_info.reset();\n\n    // prepare for first frame\n    Logic();\n}\n\nvoid DropAdd_Start()\n{\n    if(BattleMode)\n        s_minPlayers = l_screen->player_count;\n    else\n        s_minPlayers = 1;\n\n    s_context = Context::DropAdd;\n\n    for(int i = 0; i < maxLocalPlayers; i++)\n    {\n        s_players[i] = PlayerBox();\n        s_players[i].Init();\n    }\n\n    MenuMouseRelease = false;\n    MenuCursorCanMove = false;\n\n    // update the present characters, IF they got changed by level logic\n    if(s_char_info.chars_changed())\n        SaveChars();\n\n    // prepare for first frame\n    Logic();\n}\n\nint PlayerBox::CalcIndex() const\n{\n    if(this >= &s_players[0] && this <= &s_players[maxLocalPlayers - 1])\n    {\n        return this - &s_players[0];\n    }\n\n    return 0;\n}\n\nbool PlayerBox::Ready() const\n{\n    switch(m_state)\n    {\n    case PlayerState::Disconnected:\n    case PlayerState::Reconnecting:\n    case PlayerState::SelectProfile:\n    case PlayerState::ConfirmProfile:\n        return false;\n\n    default:\n        return true;\n    }\n}\n\nvoid PlayerBox::Init()\n{\n    int p = CalcIndex();\n\n    if(p < (int)Controls::g_InputMethods.size() && Controls::g_InputMethods[p]\n            && (s_context != Context::DropAdd || p < l_screen->player_count))\n    {\n        m_state = PlayerState::SelectChar;\n    }\n    else\n        m_state = PlayerState::Disconnected;\n\n    if(s_context == Context::DropAdd && p < l_screen->player_count)\n    {\n        l_screen->charSelect[p] = Player[l_screen->players[p]].Character;\n    }\n    else\n    {\n        l_screen->charSelect[p] = s_recent_char[p];\n        if(l_screen->charSelect[p] == 0)\n            l_screen->charSelect[p] = p + 1;\n        ValidateChar(true);\n    }\n}\n\nbool PlayerBox::CharAvailable(int c, bool ghost_mode)\n{\n    // check if invalid\n    if(c < 1 || c > numCharacters)\n        return false;\n\n    int p = CalcIndex();\n\n    // check if blocked for use in the level (blocked in episode, or a different character was previously dropped in the level)\n    if(!s_char_info.accept(c, p, ghost_mode ? 1 : m_current_add))\n        return false;\n\n    int max_check = ghost_mode ? BoxCount() : (int)Controls::g_InputMethods.size();\n\n    if(max_check < s_minPlayers)\n        max_check = s_minPlayers;\n\n    // check if a different player has selected or is currently selecting the character\n    for(int i = 0; i < maxLocalPlayers && i < max_check; i++)\n    {\n        // fine if self\n        if(i == p)\n            continue;\n\n        if(l_screen->charSelect[i] == c && !(s_context == Context::MainMenu && MenuMode == MENU_CHARACTER_SELECT_NEW_BM) && !g_forceCharacter)\n            return false;\n    }\n\n    return true;\n}\n\nstatic bool CheckDone()\n{\n    // What is the first player that is not done?\n    int n = (int)Controls::g_InputMethods.size();\n    if(n < s_minPlayers)\n        n = s_minPlayers;\n    if(n > maxLocalPlayers)\n        n = maxLocalPlayers;\n\n    // What is the first player that is not done?\n    int notDone;\n    for(notDone = 0; notDone < n; notDone++)\n    {\n        if(notDone == (int)Controls::g_InputMethods.size() || !Controls::g_InputMethods[notDone])\n            break;\n\n        if(!s_players[notDone].Ready())\n            break;\n    }\n\n    // not enough players / controllers -> not done\n    if(notDone < s_minPlayers || (int)Controls::g_InputMethods.size() < s_minPlayers)\n        return false;\n\n    // need all connected players to be done\n    if(notDone == (int)Controls::g_InputMethods.size() || notDone == maxLocalPlayers)\n        return true;\n\n    return false;\n}\n\n\n// helper function when trying to force exit the drop/add screen\n// returns zero-indexed player that can be dropped (is currently disconnected), -3 if the player set is already complete, -2 if exiting the menu is blocked, and -1 if dropping a player will not make the player set complete\nstatic int CheckCanDrop()\n{\n    // What is the first player that is not done?\n    int n = (int)Controls::g_InputMethods.size();\n    if(n < s_minPlayers)\n        n = s_minPlayers;\n    if(n > maxLocalPlayers)\n        n = maxLocalPlayers;\n\n    int are_present = 0;\n    int can_drop = -3;\n\n    // What is the first player that is not done?\n    for(int notDone = 0; notDone < n; notDone++)\n    {\n        if(s_players[notDone].Ready())\n            are_present++;\n        else if(s_players[notDone].m_state == PlayerState::Disconnected || s_players[notDone].m_state == PlayerState::Reconnecting)\n            can_drop = notDone;\n        else if(s_players[notDone].m_state == PlayerState::ConfirmProfile)\n            return -2;\n    }\n\n    // not enough players / controllers -> can't drop to exit\n    if(are_present < s_minPlayers)\n        return -1;\n\n    return can_drop;\n}\n\nstatic void Player_Remove(int p)\n{\n    // if it was a novel add, mark their character as allowed\n    if(s_players[p].m_current_add == 2)\n        s_char_info.mark_char_present(l_screen->charSelect[p]);\n\n    Controls::DeleteInputMethodSlot(p);\n\n    // move all player boxes back, and reset final box\n    for(int p2 = p; p2 + 1 < maxLocalPlayers; p2++)\n    {\n        s_players[p2] = s_players[p2 + 1];\n        s_players[p2].m_input_ready = false;\n        l_screen->charSelect[p2] = l_screen->charSelect[p2 + 1];\n    }\n\n    s_players[maxLocalPlayers - 1] = PlayerBox();\n\n    // in drop-add, remove the Player officially in main game engine\n    if(p < l_screen->player_count && s_context == Context::DropAdd)\n    {\n        if((Player[l_screen->players[p]].Dead || Player[l_screen->players[p]].TimeToLive > 0) && Player[l_screen->players[p]].Effect != PLREFF_COOP_WINGS)\n            s_char_info.dead_count++;\n\n        XMessage::PushMessage({XMessage::Type::drop_player, (uint8_t)p, 0});\n    }\n}\n\nstatic void Player_Swap(int p1, int p2)\n{\n    if(p1 == p2)\n        return;\n\n    if(p1 >= (int)Controls::g_InputMethods.size() || p2 >= (int)Controls::g_InputMethods.size())\n        return;\n\n    std::swap(s_players[p1], s_players[p2]);\n\n    std::swap(l_screen->charSelect[p1], l_screen->charSelect[p2]);\n\n    s_players[p1].m_input_ready = false;\n    s_players[p2].m_input_ready = false;\n}\n\n// transform players and items based on character\nstatic void DoTransform(int p, int ch)\n{\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        if(A % (p + 1) == 0)\n        {\n            if(Player[A].Character != ch)\n            {\n                Player[A].Character = ch;\n                SizeCheck(Player[A]);\n            }\n        }\n    }\n\n    for(int A : NPCQueues::Active.no_change)\n    {\n        if(A % (p + 1) == 0 && NPC[A].Type == NPCID_PLR_FIREBALL)\n            NPC[A].Special = ch;\n    }\n}\n\nvoid PlayerBox::UpdatePlayer()\n{\n    int p = CalcIndex();\n\n    // if initialized, then update character\n    if(p < l_screen->player_count)\n    {\n        if(l_screen->charSelect[p] != Player[l_screen->players[p]].Character)\n            XMessage::PushMessage({XMessage::Type::char_swap, (uint8_t)p, (uint8_t)l_screen->charSelect[p]});\n    }\n    // otherwise, add new player!\n    else\n    {\n        // swap p with the first non-existent player slot\n        Player_Swap(l_screen->player_count, p);\n\n        // check the desired character\n        uint8_t chara = l_screen->charSelect[l_screen->player_count];\n\n        // add as dead if dead player was dropped in this level\n        if(s_char_info.dead_count > 0)\n        {\n            s_char_info.dead_count--;\n            PlaySound(SFX_ShellHit);\n            XMessage::PushMessage({XMessage::Type::add_player_dead, 0, chara});\n        }\n        else\n        {\n            PlaySound(SFX_DropItem);\n            XMessage::PushMessage({XMessage::Type::add_player, 0, chara});\n        }\n    }\n}\n\nvoid PlayerBox::ValidateChar(bool ghost_mode)\n{\n    int p = CalcIndex();\n\n    uint8_t& sel = l_screen->charSelect[p];\n\n    // ensure that character selection is still valid\n\n    // bounds check\n    if(sel < 1 || sel > numCharacters)\n        sel = 1;\n\n    // i is a dummy variable so that if invalid, end up where we started\n    for(int i = 0; i < numCharacters; i++)\n    {\n        if(CharAvailable(sel, ghost_mode))\n            return;\n\n        sel ++;\n        if(sel > numCharacters)\n            sel = 1;\n    }\n\n    // if we failed (total block), first try to find an unblocked char\n    for(int i = 1; i <= numCharacters; i++)\n    {\n        if(s_char_info.accept(i, p, m_current_add))\n        {\n            sel = i;\n            return;\n        }\n    }\n\n    // failed again? just do char 1\n    sel = 1;\n}\n\nstruct PlayDoSentinel\n{\n    bool active = true;\n    ~PlayDoSentinel()\n    {\n        if(active)\n            PlaySoundMenu(SFX_Do);\n    }\n};\n\n// returns true to signal exit from menu\nbool PlayerBox::Back()\n{\n    // int p = CalcIndex();\n\n    m_input_ready = false;\n\n    // first screen of main menu\n    if(m_state == PlayerState::Disconnected && IsMenu())\n    {\n        PlaySoundMenu(SFX_Slide);\n        return true;\n    }\n    // primary menu areas\n    else if(m_state == PlayerState::SelectChar || m_state == PlayerState::ControlsMenu)\n    {\n        // adding player at main menu\n        if(IsMenu())\n        {\n            // just go back!\n            PlaySoundMenu(SFX_Slide);\n            return true;\n        }\n        // escape the drop/add menu\n        else\n        {\n            if(CheckDone())\n            {\n                PlaySoundMenu(SFX_Slide);\n                return true;\n            }\n            else if(CheckCanDrop() >= -1)\n            {\n                m_state = PlayerState::ForceDrop;\n                m_menu_item = 0;\n                m_input_ready = true;\n                m_marquee_state.reset_width();\n                return false;\n            }\n            else\n            {\n                // m_back_requested = true;\n                PlaySoundMenu(SFX_BlockHit);\n                return false;\n            }\n        }\n    }\n\n    PlaySoundMenu(SFX_Slide);\n\n    if(Is1P() && m_state == PlayerState::SelectProfile)\n    {\n        m_state = PlayerState::SelectChar;\n        m_menu_item = 0;\n    }\n    else if(m_state == PlayerState::SelectProfile)\n    {\n        m_state = PlayerState::ControlsMenu;\n        m_menu_item = 1;\n        m_marquee_state.reset_width();\n    }\n\n    return false;\n}\n\nbool PlayerBox::Do()\n{\n    int p = CalcIndex();\n\n    m_input_ready = false;\n\n    // plays SFX_Do at end of scope unless inactivated\n    PlayDoSentinel do_sentinel;\n\n    // controls menu\n    if(m_state == PlayerState::ControlsMenu && p < (int)Controls::g_InputMethods.size())\n    {\n        // drop me\n        if(ControlsMenu_ShowDrop(p) && m_menu_item == -1)\n        {\n            do_sentinel.active = false;\n\n            Player_Remove(p);\n\n            // quit if there aren't any other players\n            if(s_context == Context::MainMenu && Controls::g_InputMethods.empty())\n            {\n                PlaySoundMenu(SFX_Slide);\n                return true;\n            }\n\n            PlaySoundMenu(SFX_PlayerDied2);\n            return false;\n        }\n        // reconnect controller\n        else if(m_menu_item == 0)\n        {\n            // does set Controls::g_InputMethods[p] to null\n            Controls::DeleteInputMethod(Controls::g_InputMethods[p]);\n\n            m_state = PlayerState::Reconnecting;\n            m_marquee_state.reset_width();\n\n            do_sentinel.active = false;\n\n            PlaySoundMenu(SFX_PetHurt);\n\n            return false;\n        }\n        // select a profile\n        else if(m_menu_item == 1)\n        {\n            // only enter the profile select screen if safe to do so.\n            if(Controls::g_InputMethods[p])\n            {\n                m_state = PlayerState::SelectProfile;\n                m_marquee_state.reset_width();\n\n                // set the current menu item to the current profile.\n                std::vector<Controls::InputMethodProfile*> profiles = Controls::g_InputMethods[p]->Type->GetProfiles();\n                std::vector<Controls::InputMethodProfile*>::iterator p_profile\n                    = std::find(profiles.begin(), profiles.end(), Controls::g_InputMethods[p]->Profile);\n\n                if(profiles.size() > 1 && p_profile != profiles.end())\n                    m_menu_item = p_profile - profiles.begin();\n                else\n                    m_menu_item = 0;\n            }\n        }\n        return false;\n    }\n\n    // select char\n    if(m_state == PlayerState::SelectChar)\n    {\n        do_sentinel.active = false;\n\n        if(IsMenu())\n        {\n            if(CheckDone())\n            {\n                // clean up charSelect fields\n                for(int p = (int)Controls::g_InputMethods.size(); p < maxLocalPlayers; p++)\n                    l_screen->charSelect[p] = 0;\n\n                do_sentinel.active = true;\n                return true;\n            }\n            else\n                PlaySoundMenu(SFX_BlockHit);\n        }\n    }\n    // select profile\n    else if(m_state == PlayerState::SelectProfile)\n    {\n        m_marquee_state.reset_width();\n\n        if(p < (int)Controls::g_InputMethods.size() && Controls::g_InputMethods[p])\n        {\n            const std::vector<Controls::InputMethodProfile*> profiles = Controls::g_InputMethods[p]->Type->GetProfiles();\n            // store profile\n            m_last_profile = Controls::g_InputMethods[p]->Profile;\n\n            int back_index = (int)profiles.size();\n            // if not possible, just go back\n            if(m_menu_item < 0 || m_menu_item >= back_index)\n            {\n                Back();\n                do_sentinel.active = false;\n            }\n            // don't do confirmation screen if just setting current profile\n            else if(m_last_profile == profiles[m_menu_item])\n            {\n                Back();\n                do_sentinel.active = false;\n            }\n            // advance to the confirmation screen if successful\n            else if(Controls::SetInputMethodProfile(p, profiles[m_menu_item]))\n            {\n                m_state = PlayerState::ConfirmProfile;\n                m_menu_item = 66 * 3;\n            }\n            // indicate that profile couldn't be bound\n            else\n            {\n                PlaySoundMenu(SFX_BlockHit);\n                do_sentinel.active = false;\n            }\n        }\n    }\n\n    return false;\n}\n\nbool PlayerBox::CanChangeChar()\n{\n    // if(!SwapCharAllowed() && !m_current_add)\n    //     return false;\n\n    int p = CalcIndex();\n\n    // see if any other character is totally free for self\n    for(int ch = 1; ch <= numCharacters; ch++)\n    {\n        if(ch == l_screen->charSelect[p])\n            continue;\n\n        if(CharAvailable(ch))\n            return true;\n    }\n\n    // if own character is okay, then it'll be kept. return false here.\n    if(CharAvailable(l_screen->charSelect[p]))\n        return false;\n\n    // if even own character not totally free, see if any character is allowed by the s_char_info state\n    for(int ch = 1; ch <= numCharacters; ch++)\n    {\n        if(ch == l_screen->charSelect[p])\n            continue;\n\n        if(s_char_info.accept(l_screen->charSelect[p], p, m_current_add))\n            return true;\n    }\n\n    return false;\n}\n\nvoid PlayerBox::PrevChar()\n{\n    // if(!SwapCharAllowed() && !m_current_add)\n    // {\n    //     PlaySoundMenu(SFX_BlockHit);\n    //     return;\n    // }\n\n    int p = CalcIndex();\n    int old_ch = l_screen->charSelect[p];\n\n    int i = 0;\n    for(i = 0; i < numCharacters; i++)\n    {\n        l_screen->charSelect[p] --;\n\n        if(l_screen->charSelect[p] < 1)\n            l_screen->charSelect[p] = numCharacters;\n\n        if(CharAvailable(l_screen->charSelect[p]))\n            break;\n    }\n\n    // if can't traverse normally, allow duplicates\n    if(i == numCharacters)\n    {\n        for(i = 0; i < numCharacters; i++)\n        {\n            l_screen->charSelect[p] --;\n\n            if(l_screen->charSelect[p] < 1)\n                l_screen->charSelect[p] = numCharacters;\n\n            if(s_char_info.accept(l_screen->charSelect[p], p, m_current_add))\n                break;\n        }\n    }\n\n    // eventually changed character, update the player\n    if(l_screen->charSelect[p] != old_ch)\n    {\n        PlaySoundMenu(SFX_Slide);\n        UpdatePlayer();\n    }\n    else\n        PlaySoundMenu(SFX_BlockHit);\n}\n\nvoid PlayerBox::NextChar()\n{\n    // if(!SwapCharAllowed() && !m_current_add)\n    // {\n    //     PlaySoundMenu(SFX_BlockHit);\n    //     return;\n    // }\n\n    int p = CalcIndex();\n    int old_ch = l_screen->charSelect[p];\n\n    int i;\n    for(i = 0; i < numCharacters; i++)\n    {\n        l_screen->charSelect[p] ++;\n\n        if(l_screen->charSelect[p] > numCharacters)\n            l_screen->charSelect[p] = 1;\n\n        if(CharAvailable(l_screen->charSelect[p]))\n            break;\n    }\n\n    // if can't traverse normally, allow duplicates\n    if(i == numCharacters)\n    {\n        for(i = 0; i < numCharacters; i++)\n        {\n            l_screen->charSelect[p] ++;\n\n            if(l_screen->charSelect[p] > numCharacters)\n                l_screen->charSelect[p] = 1;\n\n            if(s_char_info.accept(l_screen->charSelect[p], p, m_current_add))\n                break;\n        }\n    }\n\n    // eventually changed character, update the player\n    if(l_screen->charSelect[p] != old_ch)\n    {\n        PlaySoundMenu(SFX_Slide);\n        UpdatePlayer();\n    }\n    else\n        PlaySoundMenu(SFX_BlockHit);\n}\n\nvoid PlayerBox::Up()\n{\n    int p = CalcIndex();\n\n    m_input_ready = false;\n\n    if(m_state == PlayerState::SelectProfile)\n    {\n        PlaySoundMenu(SFX_Slide);\n\n        if(m_menu_item == 0)\n        {\n            if(p < (int)Controls::g_InputMethods.size() && Controls::g_InputMethods[p])\n            {\n                std::vector<Controls::InputMethodProfile*> profiles = Controls::g_InputMethods[p]->Type->GetProfiles();\n                int back_index = (int)profiles.size();\n                m_menu_item = back_index;\n            }\n        }\n        else\n            m_menu_item --;\n\n        m_marquee_state.reset_width();\n    }\n\n    if(m_state == PlayerState::ControlsMenu)\n    {\n        PlaySoundMenu(SFX_Slide);\n        m_state = PlayerState::SelectChar;\n        m_marquee_state.reset_width();\n    }\n\n    // rotate char backwards in 1P menu\n    if(m_state == PlayerState::SelectChar && Is1P())\n        PrevChar();\n}\n\nvoid PlayerBox::Down()\n{\n    int p = CalcIndex();\n\n    m_input_ready = false;\n\n    if(m_state == PlayerState::SelectProfile)\n    {\n        PlaySoundMenu(SFX_Slide);\n\n        if(p < (int)Controls::g_InputMethods.size() && Controls::g_InputMethods[p])\n        {\n            std::vector<Controls::InputMethodProfile*> profiles = Controls::g_InputMethods[p]->Type->GetProfiles();\n            int back_index = (int)profiles.size();\n            if(m_menu_item == back_index)\n                m_menu_item = -1;\n        }\n\n        m_menu_item ++;\n\n        m_marquee_state.reset_width();\n    }\n\n    // go to controls menu in non-1P\n    if(m_state == PlayerState::SelectChar && !Is1P())\n    {\n        PlaySoundMenu(SFX_Slide);\n        m_state = PlayerState::ControlsMenu;\n\n        if(ControlsMenu_ShowDrop(p))\n            m_menu_item = -1;\n        else\n            m_menu_item = 0;\n\n        m_marquee_state.reset_width();\n    }\n\n    // rotate char forwards in 1P menu\n    if(m_state == PlayerState::SelectChar && Is1P())\n        NextChar();\n}\n\nvoid PlayerBox::Left()\n{\n    m_input_ready = false;\n\n    // rotate char backwards in non-1P\n    if(m_state == PlayerState::SelectChar && !Is1P())\n        PrevChar();\n\n    if(m_state == PlayerState::ControlsMenu)\n    {\n        int p = CalcIndex();\n\n        // don't auto-rotate for clarity\n        if(m_menu_item > 0 || (ControlsMenu_ShowDrop(p) && m_menu_item == 0))\n        {\n            PlaySoundMenu(SFX_Slide);\n            m_menu_item --;\n            m_marquee_state.reset_width();\n        }\n    }\n}\n\nvoid PlayerBox::Right()\n{\n    m_input_ready = false;\n\n    // rotate char forwards in non-1P\n    if(m_state == PlayerState::SelectChar && !Is1P())\n        NextChar();\n\n    if(m_state == PlayerState::ControlsMenu)\n    {\n        if(m_menu_item < 1)\n        {\n            PlaySoundMenu(SFX_Slide);\n            m_menu_item ++;\n            m_marquee_state.reset_width();\n        }\n    }\n}\n\nbool PlayerBox::MouseItem(int i)\n{\n    if(!Is1P() && m_state == PlayerState::SelectChar)\n    {\n        m_state = PlayerState::ControlsMenu;\n        PlaySoundMenu(SFX_Slide);\n    }\n\n    int sel = (m_state == PlayerState::SelectChar) ? l_screen->charSelect[0] : m_menu_item;\n\n    if(sel != i)\n    {\n        PlaySoundMenu(SFX_Slide);\n\n        if(m_state == PlayerState::SelectChar)\n            l_screen->charSelect[0] = i;\n        else\n            m_menu_item = i;\n\n        m_marquee_state.reset_width();\n    }\n\n    if(MenuMouseRelease && SharedCursor.Primary)\n    {\n        s_cancelDoubleClick();\n        MenuMouseRelease = false;\n        return Do();\n    }\n\n    return false;\n}\n\nbool PlayerBox::MenuItem_Mouse_Render(int i, const std::string& label, int X, int Y, bool mouse, bool render, int end_X)\n{\n    if(mouse)\n    {\n        int menuLen = (int)label.size() * 18;\n        if(SharedCursor.X >= X && SharedCursor.X <= X + menuLen\n            && SharedCursor.Y >= Y && SharedCursor.Y <= Y + 16)\n        {\n            return MouseItem(i);\n        }\n    }\n\n    if(render)\n    {\n        if(m_state != PlayerState::SelectProfile)\n        {\n            if(GFX.CharSelIcons.inited && label.size() == 1)\n            {\n                if(label[0] == 'X')\n                {\n                    XRender::renderTextureBasic(X, Y, 24, 24, GFX.CharSelIcons, 0, 0);\n                    return false;\n                }\n                else if(label[0] == 'O')\n                {\n                    XRender::renderTextureBasic(X, Y, 24, 24, GFX.CharSelIcons, 24, 0);\n                    return false;\n                }\n                else if(label[0] == '?')\n                {\n                    XRender::renderTextureBasic(X, Y, 24, 24, GFX.CharSelIcons, 48, 0);\n                    return false;\n                }\n            }\n            else if(label.size() == 1)\n                X += 4;\n        }\n\n        if(end_X != 0)\n        {\n            bool cur_item = (m_menu_item == i);\n\n            MarqueeState null_marquee;\n            MarqueeSpec item_spec(end_X - X, 10, 32, 32, -1);\n\n            if(m_menu_item == i)\n                m_marquee_state.advance(item_spec);\n\n            SuperPrintMarquee(label, 5, X, Y, item_spec, (cur_item) ? m_marquee_state : null_marquee);\n        }\n        else\n            SuperPrint(label, 3, X, Y);\n    }\n\n    return false;\n}\n\n// render the selected character for player P\nbool PlayerBox::DrawChar(int x, int w, int y, int h, bool show_name)\n{\n    // don't render character box in certain states\n    if(m_state == PlayerState::SelectProfile || m_state == PlayerState::ConfirmProfile)\n        return false;\n\n    int p = CalcIndex();\n\n    // player hasn't been added yet\n    bool inactive = m_state == PlayerState::Disconnected && (IsMenu() || p >= l_screen->player_count);\n    bool show_inactive = inactive && p >= s_minPlayers;\n\n    // alpha for most uses\n    uint8_t alpha = (show_inactive) ? 127 : 255;\n\n    // verify that character is valid\n    int ch = l_screen->charSelect[p];\n    if(ch < 1 || ch > numCharacters)\n        return false;\n\n    // do the fun player transformation thing!\n    if(s_context == Context::MainMenu)\n        DoTransform(p, ch);\n\n    // draw frame\n    if(GFX.CharSelFrame.tex.inited)\n        RenderFrameFill(IntegerLocation_t{x, y, w, h}, GFX.CharSelFrame, XTAlpha(alpha));\n    else\n    {\n        uint8_t alpha_border = (show_inactive) ?  64 : 255;\n\n        XRender::renderRect(x, y, w, h, {0, 0, 0, alpha_border});\n        XRender::renderRect(x + 2, y + 2, w - 4, h - 4, {255, 255, 255, alpha});\n    }\n\n    // draw arrows\n    if(m_state == PlayerState::SelectChar || (s_context == Context::MainMenu && inactive))\n    {\n        XTColor arrow_color;\n\n        // show grayed out scroll indicator if can't change char\n        if(!CanChangeChar())\n            arrow_color.a = 127;\n        // otherwise, hide half of the time\n        else if(CommonFrame % 90 >= 45)\n            arrow_color.a = 0;\n        // never show full opacity when inactive\n        else if(show_inactive)\n            arrow_color.a = 127;\n\n        if(arrow_color.a == 0)\n        {\n            // don't render\n        }\n        else if(GFX.CharSelIcons.inited)\n        {\n            XRender::renderTextureFL(x - 24 - 4, y + h / 2 - 24 / 2, 24, 24, GFX.CharSelIcons, 72, 0, 0, nullptr, X_FLIP_HORIZONTAL, arrow_color);\n            XRender::renderTextureBasic(x + w + 4, y + h / 2 - 24 / 2, 24, 24, GFX.CharSelIcons, 72, 0, arrow_color);\n        }\n        else\n        {\n            XRender::renderTextureFL(x - GFX.MCursor[1].h - 4, y + h / 2 - GFX.MCursor[1].w / 2, GFX.MCursor[1].w, GFX.MCursor[1].h, GFX.MCursor[1], 0, 0, -90, nullptr, X_FLIP_NONE, arrow_color);\n            XRender::renderTextureFL(x + w + 4, y + h / 2 - GFX.MCursor[2].w / 2, GFX.MCursor[2].w, GFX.MCursor[2].h, GFX.MCursor[2], 0, 0, -90, nullptr, X_FLIP_NONE, arrow_color);\n        }\n    }\n\n    // flash if player is currently disconnected\n    bool player_flash = (!inactive && m_state == PlayerState::Disconnected);\n\n    if(!player_flash || (CommonFrame % 60) < 30)\n    {\n        // always walk in Drop / Add, walk only after finalizing selection at main menu\n        bool player_walk = (s_context == Context::DropAdd || !inactive);\n\n        // find a spare player to edit\n        int scratch_index = maxPlayers - maxLocalPlayers + 1 + p;\n        Player_t& scratch_player = Player[scratch_index];\n\n        // back up some info to the stack\n        int PrevCh = scratch_player.Character;\n        int Frame = scratch_player.Frame;\n        int FrameCount = scratch_player.FrameCount;\n        int YoshiTFrameCount = scratch_player.YoshiTFrameCount;\n        int YoshiBFrameCount = scratch_player.YoshiBFrameCount;\n        int YoshiWingsFrameCount = scratch_player.YoshiWingsFrameCount;\n\n        // initialize player based on saved data (unless in Battle Mode)\n        if(s_context == Context::MainMenu && MenuMode == MENU_CHARACTER_SELECT_NEW_BM)\n        {\n            scratch_player = Player_t();\n            scratch_player.Character = ch;\n            scratch_player.State = 2;\n            scratch_player.Hearts = 2;\n        }\n        // empty save\n        else if(s_context == Context::MainMenu && SaveSlotInfo[selSave].Progress < 0)\n        {\n            scratch_player = Player_t();\n            scratch_player.Character = ch;\n            scratch_player.State = 1;\n            scratch_player.Hearts = 1;\n        }\n        else if(s_context == Context::MainMenu)\n            scratch_player = SaveSlotInfo[selSave].SavedChar[ch];\n        else if(BattleMode)\n        {\n            scratch_player = Player_t();\n            scratch_player.Character = ch;\n            scratch_player.State = 2;\n            scratch_player.Hearts = 2;\n        }\n        else\n            scratch_player = SavedChar[ch];\n\n        if(scratch_player.Character < 1 || scratch_player.Character > numCharacters)\n            scratch_player.Character = ch;\n\n        if(scratch_player.State < 1 || scratch_player.State > numStates)\n            scratch_player.State = 1;\n\n        // restore data\n        if(scratch_player.Character == PrevCh)\n        {\n            scratch_player.Frame = Frame;\n            scratch_player.FrameCount = FrameCount;\n            scratch_player.YoshiTFrameCount = YoshiTFrameCount;\n            scratch_player.YoshiBFrameCount = YoshiBFrameCount;\n            scratch_player.YoshiWingsFrameCount = YoshiWingsFrameCount;\n        }\n        else\n            scratch_player.Frame = 1;\n\n        // process height and frames\n        scratch_player.Location.Height = Physics.PlayerHeight[scratch_player.Character][scratch_player.State];\n        YoshiHeight(scratch_index);\n\n        scratch_player.Direction = 1;\n        scratch_player.Location.SpeedY = 0;\n        if(player_walk)\n            scratch_player.Location.SpeedX = 1;\n\n        PlayerFrame(scratch_player);\n\n        // set up location and place player\n        scratch_player.Location.Width = Physics.PlayerWidth[scratch_player.Character][scratch_player.State];\n        scratch_player.Location.Height = Physics.PlayerHeight[scratch_player.Character][scratch_player.State];\n        SizeCheck(scratch_player);\n        scratch_player.Location.X = x + w / 2 - scratch_player.Location.Width / 2 - vScreen[0].X;\n        scratch_player.Location.Y = y + h / 2 + Physics.PlayerHeight[5][2] / 4 - scratch_player.Location.Height * 3 / 4 + 4 - vScreen[0].Y;\n\n        if(scratch_player.Mount == 3)\n            scratch_player.Location.Y += 6;\n\n        // animate winged boot\n        if(scratch_player.Mount == 1 && scratch_player.MountType == 3)\n        {\n            scratch_player.YoshiWingsFrameCount += 1;\n            scratch_player.YoshiWingsFrame = 0;\n\n            if(scratch_player.YoshiWingsFrameCount <= 12)\n                scratch_player.YoshiWingsFrame = 1;\n            else if(scratch_player.YoshiWingsFrameCount >= 24)\n                scratch_player.YoshiWingsFrameCount = 0;\n\n            if(scratch_player.Direction == 1)\n                scratch_player.YoshiWingsFrame += 2;\n        }\n\n        DrawPlayer(scratch_player, 0, XTAlpha(alpha));\n    }\n\n#if 0\n    // finish with character name\n    if(show_name)\n        SuperPrintCenter(g_gameInfo.characterName[l_screen->charSelect[p]], 3, x + w / 2, y + h + 12, XTAlpha(alpha));\n#else\n    UNUSED(show_name);\n#endif\n\n    return true;\n}\n\n// special the mouse logic and rendering 1P char select\nint PlayerBox::Mouse_Render_1P(bool render)\n{\n    int p = CalcIndex();\n    bool mouse = !render;\n\n    if(p < 0 || p >= maxLocalPlayers)\n    {\n        // `p` should not be bigger than `maxLocalPlaeyrs`, otherwise the out of range at `s_playerState`\n        SDL_assert(p >=0 && p < maxLocalPlayers);\n        return false;\n    }\n\n    if(mouse && !render && !SharedCursor.Move && !SharedCursor.Primary && !SharedCursor.Secondary && !SharedCursor.ScrollUp && !SharedCursor.ScrollDown)\n        return 0;\n\n    if(mouse && SharedCursor.Secondary && MenuMouseRelease)\n    {\n        MenuMouseRelease = false;\n\n        if(Back())\n            return -1;\n    }\n\n    int MenuX, MenuY;\n    GetMenuPos(&MenuX, &MenuY);\n\n    int y_pos = MenuY;\n\n    for(int i = 1; i <= numCharacters; i++)\n    {\n        if(!blockCharacter[i])\n        {\n            if(MenuItem_Mouse_Render(i, fmt::format_ne(g_mainMenu.selectCharacter, g_gameInfo.characterName[i]), MenuX, y_pos, mouse, render))\n                return 1;\n\n            if(render && l_screen->charSelect[p] == i)\n                XRender::renderTextureBasic(MenuX - 20, y_pos, GFX.MCursor[0]);\n\n            y_pos += 30;\n        }\n    }\n\n    // show controls info\n    if(render)\n    {\n        int infobox_y = MenuY + 145;\n\n        XRender::renderRect(XRender::TargetW / 2 - 240, infobox_y, 480, 68, XTColorF(0, 0, 0, 0.5_n));\n\n        // disconnection hint\n        if(p >= (int)Controls::g_InputMethods.size() || !Controls::g_InputMethods[p])\n        {\n            SuperPrintScreenCenter(g_gameStrings.connectWaitingForInputDevice, 3, infobox_y + 4, XTColorF(0.8_n, 0.8_n, 0.8_n, 0.8_n));\n        }\n        else\n        {\n            // global information about controller\n            // Profile should never be null\n            if(Controls::g_InputMethods[p]->Profile != nullptr)\n                SuperPrintScreenCenter(Controls::g_InputMethods[p]->Name + \" - \" + Controls::g_InputMethods[p]->Profile->Name, 3, infobox_y + 4);\n            else\n                SuperPrintScreenCenter(Controls::g_InputMethods[p]->Name, 3, infobox_y + 4);\n        }\n\n        SuperPrintScreenCenter(g_gameStrings.connectPressSelectForControlsOptions_P1, 3, infobox_y + 24, XTColorF(0.8_n, 0.8_n, 0.8_n, 0.8_n));\n        SuperPrintScreenCenter(g_gameStrings.connectPressSelectForControlsOptions_P2, 3, infobox_y + 44, XTColorF(0.8_n, 0.8_n, 0.8_n, 0.8_n));\n    }\n\n    // do char transform thing!\n    if(render && l_screen->charSelect[p] >= 1 && l_screen->charSelect[p] <= numCharacters)\n        DoTransform(p, l_screen->charSelect[p]);\n\n    return 0;\n}\n\n// do the mouse logic and rendering for each player\nint PlayerBox::Mouse_Render(bool render, int x, int y, int w, int h)\n{\n    if(Is1P() && (m_state == PlayerState::SelectChar))\n        return Mouse_Render_1P(render);\n\n    int p = CalcIndex();\n    bool mouse = !render;\n    const auto* input_method = (p < (int)Controls::g_InputMethods.size()) ? Controls::g_InputMethods[p] : nullptr;\n\n\n    // calculate positions\n    int cx = x + w / 2;\n\n    // controls / info pane\n    const int info_height = 80;\n    int info_y = y + h - info_height;\n\n    // icons column\n    const int icons_height = 22;\n    int icons_x = x + 8 + 76 + 8;\n    int icons_y = info_y + (GFX.PCursor.inited ? GFX.PCursor.w : GFX.MCursor[2].h) + 8;\n    int infotext_y = icons_y + icons_height + 8;\n\n    // main char select pane\n    int main_x = x + 4;\n    int main_y = y + 6;\n    int main_width = w - 8;\n    int main_height = h - info_height - 6;\n    int total_height = h - 12;\n\n    // not-yet-added player\n    bool inactive = m_state == PlayerState::Disconnected && (s_context == Context::MainMenu || p >= l_screen->player_count);\n    bool show_inactive = inactive && p >= s_minPlayers;\n\n    if(inactive)\n    {\n        if(show_inactive)\n            l_screen->charSelect[p] = s_recent_char[p];\n        ValidateChar(true);\n    }\n\n    // check currently selected character\n    int ch = l_screen->charSelect[p];\n\n    // verify that character is valid\n    if(ch < 1 || ch > numCharacters)\n        ch = 0;\n\n    // calculate box color\n    XTColor color = XTColorF(0.4_n, 0.4_n, 0.4_n);\n\n    switch(ch)\n    {\n    case 1:\n        color = XTColorF(1.0_n, 0.0_n, 0.0_n);\n        break;\n    case 2:\n        color = XTColorF(0.0_n, 1.0_n, 0.0_n);\n        break;\n    case 3:\n        color = XTColorF(1.0_n, 0.4_n, 0.5_n);\n        break;\n    case 4:\n        color = XTColorF(0.0_n, 0.4_n, 1.0_n);\n        break;\n    case 5:\n        color = {255, 204, 0};\n        break;\n    }\n\n    if(show_inactive)\n    {\n        color.r = color.r / 4 + 127;\n        color.g = color.g / 4 + 127;\n        color.b = color.b / 4 + 127;\n    }\n\n    // render background\n    if(render)\n    {\n        // background\n        XRender::renderRect(x + 0, y + 0, w - 0, h - 0, {0, 0, 0});\n        XRender::renderRect(x + 2, y + 2, w - 4, h - 4, color * 0.95_n);\n        XRender::renderRect(x + 4, y + 4, w - 8, h - 8, color * 0.75_n);\n\n        if(m_state != PlayerState::SelectProfile)\n        {\n            // middle dividing line (bg)\n            XRender::renderRect(x + 2, info_y - 2, w - 4, 6, color * 0.95_n);\n\n            // middle dividing line (fg)\n            XRender::renderRect(x, info_y, w, 2, {0, 0, 0});\n        }\n\n        // confirmation progress\n        if(m_state == PlayerState::ConfirmProfile || m_state == PlayerState::ForceDrop)\n        {\n            int menu_progress = m_menu_item;\n            int menu_max = 66 * 2;\n\n            // special asymmetric setup for confirming controls profile\n            if(m_state == PlayerState::ConfirmProfile)\n            {\n                menu_progress = SDL_min(m_menu_item, 66 * 3) * 2 + SDL_max(m_menu_item - 66 * 3, 0) * 3;\n                menu_max = 66 * 12;\n            }\n\n            XTColor shade = color * XTColor::from_num(0.95_n);\n            XRender::renderRect(x + 4, info_y + 4, (w - 8) * menu_progress / menu_max, info_height - 8, shade);\n\n            // move infotext y up a bit, since controls will be drawn in main box (if at all)\n            infotext_y = info_y + info_height / 2 - 8;\n        }\n    }\n\n    // render the player box (if appropriate)\n    // bool show_name = (main_height > 88 + 16 + 32);\n    constexpr bool show_name = false;\n    const int label_h = show_name ? 32 : 16;\n    const int pbox_h = SDL_max(64, SDL_min(88, main_height - 16 - label_h));\n    const int pbox_w = pbox_h;\n    const int pbox_h_full = pbox_h + 16 + label_h;\n    int pbox_x = main_x + main_width / 2 - pbox_w / 2;\n    int pbox_y = main_y + main_height / 2 - pbox_h_full / 2 + 16;\n\n    // draw character box\n    if(render)\n        DrawChar(pbox_x, pbox_w, pbox_y, pbox_h, show_name);\n\n    // allow mouse to select char\n    if(mouse && (m_state == PlayerState::ControlsMenu || m_state == PlayerState::SelectChar))\n    {\n        if(SharedCursor.X >= pbox_x && SharedCursor.X <= pbox_x + pbox_w\n            && SharedCursor.Y >= pbox_y && SharedCursor.Y <= pbox_y + pbox_h)\n        {\n            if(m_state != PlayerState::SelectChar)\n            {\n                m_state = PlayerState::SelectChar;\n                PlaySoundMenu(SFX_Slide);\n            }\n\n            if(m_input_ready && SharedCursor.ScrollUp)\n                Left();\n\n            if(m_input_ready && SharedCursor.ScrollDown)\n                Right();\n\n            if(MenuMouseRelease && SharedCursor.Primary)\n            {\n                s_cancelDoubleClick();\n                MenuMouseRelease = false;\n                return Do();\n            }\n        }\n    }\n\n    // draw cursor above char box\n    if(render)\n    {\n        if(m_state == PlayerState::SelectChar || (inactive && p < s_minPlayers))\n            Render_PCursorDownwards(pbox_x + pbox_w / 2, pbox_y - 2);\n        else if(inactive && s_context == Context::MainMenu)\n            Render_PCursorDownwards(pbox_x + pbox_w / 2, pbox_y - 2, XTAlpha(127));\n    }\n\n    // now render / process the player's menu as appropriate to its case\n\n    // render the profile selection menu\n    if(m_state == PlayerState::SelectProfile)\n    {\n        if(!input_method)\n        {\n            Back();\n            return 0;\n        }\n\n        // set the current menu item to the current profile.\n        std::vector<Controls::InputMethodProfile*> profiles = input_method->Type->GetProfiles();\n\n        if(profiles.empty())\n        {\n            Back();\n            return 0;\n        }\n\n        if(render)\n            SuperPrintCenter(g_mainMenu.wordProfiles, 3, cx, main_y);\n\n        int line = 20;\n\n        int start_y = main_y + 16 + 1 * line;\n        int end_y = main_y + total_height - 8;\n\n        // calculate scroll (top rendered index) here\n        int total_lines = (int)profiles.size() + 1;\n        int avail_lines = (end_y - start_y) / line;\n\n        int scroll_start = 0;\n        int scroll_end = total_lines;\n\n        if(avail_lines < total_lines)\n        {\n            if(m_menu_item >= 0)\n                scroll_start = m_menu_item - avail_lines/2;\n            if(scroll_start < 0)\n                scroll_start = 0;\n\n            scroll_end = scroll_start + avail_lines;\n            if(scroll_end > total_lines)\n            {\n                scroll_end = total_lines;\n                scroll_start = scroll_end - avail_lines;\n            }\n        }\n\n        // render the scroll indicators\n        if(render)\n        {\n            if(scroll_start > 0)\n                XRender::renderTextureBasic(cx - GFX.MCursor[1].w / 2, start_y - GFX.MCursor[1].h, GFX.MCursor[1]);\n\n            if(scroll_end < total_lines)\n                XRender::renderTextureBasic(cx - GFX.MCursor[2].w / 2, start_y + (avail_lines)*line - line + 18, GFX.MCursor[2]);\n        }\n\n        // show the menu cursor for the player\n        if(render && m_menu_item >= 0)\n            Render_PCursor(x + 6, start_y + (m_menu_item-scroll_start)*line);\n\n        // render the profile names and \"Back\"\n        for(int i = scroll_start; i < scroll_end; i++)\n        {\n            std::string* name = &g_mainMenu.wordBack;\n\n            if(i != (int)profiles.size())\n                name = &(profiles[i]->Name);\n\n            MenuItem_Mouse_Render(i, *name,\n                x + 6 + 16 + 2, start_y + (i-scroll_start)*line, mouse, render, x + w - 6);\n        }\n    }\n\n    // render the profile confirmation screen\n    if(render && m_state == PlayerState::ConfirmProfile)\n    {\n        SuperPrintCenter(g_gameStrings.connectTestProfile, 3, cx, main_y);\n\n        if(input_method && input_method->Profile != nullptr)\n        {\n            MarqueeSpec profile_spec(w - 12, 10, 64, 32, 0);\n            m_marquee_state.advance(profile_spec);\n            SuperPrintMarquee(input_method->Profile->Name, 5, x + 6, main_y + 20, profile_spec, m_marquee_state);\n        }\n    }\n\n    // draw the controls box in the appropriate place\n    if(render)\n    {\n        int controls_x = 0;\n        int controls_y = 0;\n\n        if(m_state == PlayerState::SelectProfile || m_state == PlayerState::ForceDrop)\n        {\n            // don't draw at all\n        }\n        else if(m_state == PlayerState::ConfirmProfile)\n        {\n            controls_x = main_x + main_width / 2 - 76 / 2;\n            controls_y = main_y + main_height / 2 - 16;\n        }\n        else if(m_state == PlayerState::Disconnected || m_state == PlayerState::Reconnecting)\n        {\n            controls_x = main_x + main_width / 2 - 76 / 2;\n            controls_y = icons_y - 6;\n        }\n        else\n        {\n            controls_x = icons_x - 84;\n            controls_y = icons_y - 6;\n        }\n\n        if(controls_x != 0)\n        {\n            if(inactive)\n                speedRun_syncControlKeys(p, Controls_t());\n\n            RenderControls(p, controls_x, controls_y, 76, 30, !input_method, 255, true);\n        }\n    }\n\n    // render the controls menu main icons\n    if(m_state == PlayerState::SelectChar || m_state == PlayerState::ControlsMenu)\n    {\n        int col_spacer = (x + w - 4 - icons_x) / 3;\n        int base_col = icons_x + col_spacer;\n\n        // show the cursor for the player\n        if(render && m_state == PlayerState::ControlsMenu)\n            Render_PCursorDownwards(base_col + 12 + m_menu_item * col_spacer, icons_y);\n\n        // drop me\n        if(ControlsMenu_ShowDrop(p))\n        {\n            // this one returns true when no players are left\n            if(MenuItem_Mouse_Render(-1, \"X\",\n                base_col - col_spacer, icons_y, mouse, render))\n            {\n                return -1;\n            }\n        }\n\n        // reconnect controller\n        MenuItem_Mouse_Render(0, \"O\",\n            base_col, icons_y, mouse, render);\n\n        // switch profile\n        MenuItem_Mouse_Render(1, \"?\",\n            base_col + col_spacer, icons_y, mouse, render);\n    }\n\n    // hint string\n    if(render)\n    {\n        const std::string* message = nullptr;\n        std::string temporary;\n        XTColor message_color;\n\n        if(m_state == PlayerState::SelectChar)\n        {\n            if(input_method && input_method->Profile != nullptr)\n            {\n                message = &input_method->Profile->Name;\n                message_color = XTAlphaF(0.75_n);\n            }\n        }\n        else if(m_state == PlayerState::ControlsMenu)\n        {\n            if(m_menu_item == -1)\n                message = &g_gameStrings.connectDropMe;\n            else if(m_menu_item == 0)\n                message = &g_gameStrings.connectReconnectTitle;\n            else if(m_menu_item == 1)\n                message = &g_mainMenu.wordProfiles;\n        }\n        else if(m_state == PlayerState::ConfirmProfile)\n        {\n            // using marquee for profile name\n            SuperPrintCenter(g_gameStrings.connectHoldStart, 5, cx, infotext_y, message_color);\n        }\n        else if(m_state == PlayerState::ForceDrop)\n        {\n            int index = CheckCanDrop() + 1;\n\n            if(index > 0)\n                message = &(temporary = fmt::format_ne(g_gameStrings.connectDropPX, index));\n            else\n                message = &g_gameStrings.connectForceResume;\n        }\n        else if(m_state == PlayerState::Reconnecting)\n            message = &g_gameStrings.connectReconnectTitle;\n        else if(m_state == PlayerState::Disconnected)\n            message = &g_gameStrings.connectPressAButton;\n\n        if(message)\n        {\n            MarqueeSpec print_spec = MarqueeSpec(main_width - 8, 10, 16, 32, 0);\n\n            m_marquee_state.advance(print_spec);\n            SuperPrintMarquee(*message, 5, main_x + 4, infotext_y, print_spec, m_marquee_state, message_color);\n        }\n    }\n\n    if(MenuMouseRelease && SharedCursor.Secondary\n        && SharedCursor.X >= x && SharedCursor.X <= x + w\n        && SharedCursor.Y >= y && SharedCursor.Y <= y + h)\n    {\n        MenuMouseRelease = false;\n        if(Back())\n            return -1;\n    }\n\n    return 0;\n}\n\nint Mouse_Render(bool mouse, bool render)\n{\n    int MenuX, MenuY;\n    GetMenuPos(&MenuX, &MenuY);\n\n    if(mouse && !SharedCursor.Move && !render && !SharedCursor.Primary && !SharedCursor.Secondary && !SharedCursor.ScrollUp && !SharedCursor.ScrollDown)\n        return 0;\n\n    int n = BoxCount();\n\n    /*--------------------*\\\n    || Arrange screen     ||\n    \\*--------------------*/\n\n    const int header_height = (s_context == Context::DropAdd) ? 32 : 0;\n    int box_width = 220;\n    int box_height = 240;\n    if(IsMenu() && box_height > XRender::TargetH - MenuY - 8)\n        box_height = XRender::TargetH - MenuY - 8;\n    int padding = 16;\n\n    // number of columns per row\n    int n_cols[2] = {n, 0};\n\n    // do drop/add in 2 rows when >2P\n    if(n > 2 && s_context != Context::MainMenu)\n    {\n        n_cols[1] = n / 2;\n        n_cols[0] = n - n_cols[1];\n    }\n\n    // shrink boxes when they would not fit on screen\n    if(n_cols[0] * box_width + (n_cols[0] - 1) * padding > XRender::TargetW)\n    {\n        box_width = 168;\n        padding = 8;\n\n        int max_width = (XRender::TargetW - (n_cols[0] - 1) * padding) / n_cols[0];\n        max_width &= ~1;\n        if(box_width > max_width)\n            box_width = max_width;\n    }\n\n    int full_height = header_height + box_height + padding;\n    int full_width = padding + (box_width + padding) * n_cols[0];\n\n    if(n_cols[1])\n        full_height += padding + box_height;\n\n    // vertical / horizontal start of the menu\n    int start_x = XRender::TargetW / 2 - full_width / 2;\n    int draw_y = XRender::TargetH / 2 - full_height / 2;\n\n    if(IsMenu())\n        GetMenuPos(nullptr, &draw_y);\n\n    // make sure they are even\n    start_x &= ~1;\n    draw_y &= ~1;\n\n    /*-----------------------*\\\n    || Background and header ||\n    \\*-----------------------*/\n\n    if(s_context == Context::DropAdd)\n    {\n        if(render)\n        {\n            if(full_width < 440)\n                XRender::renderRect((XRender::TargetW / 2 - 440 / 2) & ~1, draw_y, 440, full_height, XTColorF(0.0_n, 0.0_n, 0.0_n, 0.5_n));\n            else\n                XRender::renderRect(start_x, draw_y, full_width, full_height, XTColorF(0.0_n, 0.0_n, 0.0_n, 0.5_n));\n\n            SuperPrintScreenCenter(g_gameStrings.pauseItemPlayerSetup, 3, draw_y + 8);\n        }\n\n        draw_y += header_height;\n    }\n\n    int ret = 0;\n\n    // start drawing player boxes\n    int draw_x = start_x + padding;\n\n    for(int p = 0; p < n; p++)\n    {\n        // start new row if needed\n        if(p == n_cols[0])\n        {\n            draw_x = start_x + padding;\n            draw_y += box_height + padding;\n        }\n\n        int p_ret = s_players[p].Mouse_Render(render, draw_x, draw_y, box_width, box_height);\n\n        if(p_ret != 0)\n            ret = p_ret;\n\n        draw_x += box_width + padding;\n    }\n\n    return ret;\n}\n\nint MouseLogic()\n{\n    // check mouse release if not in main menu\n    if(s_context == Context::DropAdd)\n    {\n        if(!SharedCursor.Primary && !SharedCursor.Secondary)\n            MenuMouseRelease = true;\n    }\n\n    int ret = Mouse_Render(true, false);\n\n    // and set it to false if needed\n    if(SharedCursor.Primary || SharedCursor.Secondary)\n        MenuMouseRelease = false;\n\n    return ret;\n}\n\nint PlayerBox::Logic()\n{\n    int p = CalcIndex();\n    auto* input_method = (p < (int)Controls::g_InputMethods.size()) ? Controls::g_InputMethods[p] : nullptr;\n\n\n    // hide \"Drop Me\", if it is no longer allowed\n    if(m_state == PlayerState::ControlsMenu\n        && m_menu_item == -1 && !ControlsMenu_ShowDrop(p))\n    {\n        m_menu_item = 0;\n        m_marquee_state.reset_width();\n    }\n\n    // if player's input was lost, mark disconnected and return (unless in 1P menu)\n    if(!Is1P() && !input_method)\n    {\n        m_input_ready = false;\n\n        if(m_state != PlayerState::Reconnecting && m_state != PlayerState::Disconnected)\n        {\n            m_state = PlayerState::Disconnected;\n            m_marquee_state.reset_width();\n        }\n\n        return 0;\n    }\n\n    // if the player has a controller and was previously disconnected, mark them as connected\n    if(m_state == PlayerState::Disconnected || m_state == PlayerState::Reconnecting)\n    {\n        m_marquee_state.reset_width();\n        m_input_ready = false;\n\n        if(IsMenu())\n        {\n            if(m_state == PlayerState::Reconnecting) // requested reconnect\n            {\n                m_state = PlayerState::SelectChar;\n                m_menu_item = 0;\n                PlaySoundMenu(SFX_Pet);\n            }\n            else\n            {\n                m_input_ready = true;\n                m_just_added = true;\n\n                m_menu_item = 0;\n                if(p >= s_minPlayers)\n                    l_screen->charSelect[p] = s_recent_char[p];\n                m_state = PlayerState::SelectChar;\n                ValidateChar();\n            }\n        }\n        else if(s_context == Context::DropAdd)\n        {\n            // this is an Add situation\n            if(p >= l_screen->player_count)\n            {\n                m_menu_item = 0;\n                l_screen->charSelect[p] = s_recent_char[p];\n                m_state = PlayerState::SelectChar;\n\n                // check whether a novel add\n                if(p >= s_char_info.max_players)\n                {\n                    m_current_add = 2;\n                    s_char_info.max_players = p + 1;\n                }\n                else\n                {\n                    m_current_add = 1;\n                }\n\n                // validate character\n                ValidateChar();\n\n                // add the player immediately\n                UpdatePlayer();\n            }\n            else // was disconnected\n            {\n                m_state = PlayerState::SelectChar;\n                m_menu_item = 0;\n                PlaySoundMenu(SFX_Pet);\n            }\n        }\n\n        // wait in case controls are not yet updated\n        return 0;\n    }\n\n    const Controls_t& c = Controls::g_RawControls[p];\n\n    bool c_Do, c_Back, c_AltDo, c_AltBack;\n\n    if(p < (int)Controls::g_InputMethods.size() && Controls::g_InputMethods[p] && Controls::g_InputMethods[p]->Profile && Controls::g_InputMethods[p]->Profile->m_altMenuControls)\n    {\n        c_Do = c.AltJump;\n        c_Back = c.Jump || c.Run;\n        c_AltDo = c.AltRun;\n        c_AltBack = c.Run;\n    }\n    else\n    {\n        c_Do = c.Jump;\n        c_Back = c.Run || c.AltJump;\n        c_AltDo = c.AltJump;\n        c_AltBack = c.AltRun;\n    }\n\n    if(m_just_added)\n    {\n        // play drop item noise?\n        bool play_noise = (s_context != Context::LegacyMenu);\n\n        // block back if a new-added player\n        if((int)Controls::g_InputMethods.size() > s_minPlayers && (c_Back || l_SharedControls.MenuBack))\n            m_input_ready = false;\n        // if pressing back, don't play drop item noise\n        else if(c_Back || l_SharedControls.MenuBack)\n            play_noise = false;\n\n        // if about to move cursor, don't play drop item noise\n        if(m_input_ready && (c.Down || c.Up || c.Left || c.Right))\n            play_noise = false;\n\n        // don't allow going forwards at main menu\n        if(s_context == Context::MainMenu && m_input_ready && (c_Do || c.Start))\n            m_input_ready = false;\n\n        if(play_noise)\n            PlaySoundMenu(SFX_DropItem);\n\n        m_just_added = false;\n    }\n\n    // don't do any logic until all buttons have been released\n    if(!m_input_ready)\n    {\n        if(!l_SharedControls.MenuDo && !l_SharedControls.MenuBack && !l_SharedControls.MenuDown\n            && !l_SharedControls.MenuUp && !l_SharedControls.MenuLeft && !l_SharedControls.MenuRight\n            && !c.Jump && !c.Start && !c.Run && !c.Down && !c.Up && !c.Left && !c.Right && !c.Drop && !c.AltRun && !c.AltJump)\n        {\n            m_input_ready = true;\n        }\n\n        return 0;\n    }\n\n    if(IsMenu() && !g_forceCharacter)\n    {\n        if((m_konami_bits == 0 && c.Up)\n            || (m_konami_bits == 1 && c.Up)\n            || (m_konami_bits == 2 && c.Down)\n            || (m_konami_bits == 3 && c.Down)\n            || (m_konami_bits == 4 && c.Left)\n            || (m_konami_bits == 5 && c.Right)\n            || (m_konami_bits == 6 && c.Left)\n            || (m_konami_bits == 7 && c.Right)\n            || (m_konami_bits == 8 && c_AltBack)\n            || (m_konami_bits == 9 && c_AltDo))\n        {\n            m_konami_bits++;\n\n            if(m_konami_bits == 10)\n            {\n                for(int A = 1; A <= numCharacters; A++)\n                    blockCharacter[A] = false;\n\n                g_forceCharacter = true;\n\n                PlaySoundMenu(SFX_MedalGet);\n                m_input_ready = false;\n                return 0;\n            }\n            else if(m_konami_bits >= 9)\n            {\n                m_input_ready = false;\n                return 0;\n            }\n        }\n        else if(c.Jump || c.Start || c.Run || c.Down || c.Up || c.Left || c.Right || c.Drop || c.AltRun || c.AltJump)\n            m_konami_bits = 0;\n    }\n\n    if(m_state == PlayerState::ConfirmProfile)\n    {\n        bool no_buttons = !c.Run && !c.Jump && !c.AltRun && !c.AltJump && !c.Start && !c.Drop && !c.Down && !c.Up && !c.Left && !c.Right;\n\n        if(!no_buttons && m_menu_item < 66 * 3)\n            m_menu_item = (4 * m_menu_item + 66 * 3) / 5;\n\n        if(c.Start)\n            m_menu_item += 2;\n        else if(m_menu_item > 66 * 3 || no_buttons)\n            m_menu_item--;\n\n        if(m_menu_item < 0)\n        {\n            // Go back to profile select screen, if possible\n            if(input_method)\n            {\n                m_state = PlayerState::SelectProfile;\n\n                // set the current menu item to the profile that the player tried to activate.\n                std::vector<Controls::InputMethodProfile*> profiles = input_method->Type->GetProfiles();\n                std::vector<Controls::InputMethodProfile*>::iterator p_profile\n                    = std::find(profiles.begin(), profiles.end(), input_method->Profile);\n\n                if(profiles.size() > 1 && p_profile != profiles.end())\n                    m_menu_item = p_profile - profiles.begin();\n                else\n                    m_menu_item = 0;\n\n                Controls::SetInputMethodProfile(input_method, m_last_profile);\n            }\n            else\n            {\n                s_controls_dirty = true;\n                m_state = PlayerState::ControlsMenu;\n                m_menu_item = 1;\n            }\n\n            m_last_profile = nullptr;\n            m_input_ready = false;\n            m_marquee_state.reset_width();\n            PlaySoundMenu(SFX_Slide);\n        }\n        else if(m_menu_item > 66 * 5)\n        {\n            PlaySoundMenu(SFX_Do);\n\n            if(Is1P())\n            {\n                m_menu_item = 0;\n                m_state = PlayerState::SelectChar;\n            }\n            else\n            {\n                m_menu_item = 1;\n                m_state = PlayerState::ControlsMenu;\n            }\n\n            m_last_profile = nullptr;\n            m_input_ready = false;\n            m_marquee_state.reset_width();\n        }\n\n        return 0;\n    }\n\n    if(m_state == PlayerState::ForceDrop)\n    {\n        int to_drop = CheckCanDrop();\n\n        if(to_drop <= -2)\n        {\n            PlaySoundMenu(SFX_BlockHit);\n            m_state = PlayerState::SelectChar;\n            m_menu_item = 0;\n            m_marquee_state.reset_width();\n            m_input_ready = false;\n        }\n        else if(c_Back)\n            m_menu_item += 1;\n        else if(m_menu_item >= 0)\n            m_menu_item -= 2;\n\n        if(m_menu_item > 66 * 2)\n        {\n            if(to_drop >= 0)\n                Player_Remove(to_drop);\n            else\n                QuickReconnectScreen::g_active = true;\n\n            if(to_drop == -1 || CheckDone())\n            {\n                PlaySoundMenu(SFX_Slide);\n                return -1;\n            }\n            else\n                PlaySoundMenu(SFX_PlayerDied2);\n\n            m_menu_item = 0;\n        }\n        else if(m_menu_item < 0)\n        {\n            m_state = PlayerState::ControlsMenu;\n            m_menu_item = 0;\n            m_marquee_state.reset_width();\n            m_input_ready = false;\n        }\n\n        return 0;\n    }\n    else if(c_Back || (Is1P() && l_SharedControls.MenuBack))\n    {\n        if(Back())\n            return -1;\n    }\n    else if(c_Do || c.Start || (Is1P() && l_SharedControls.MenuDo))\n    {\n        if(Do())\n        {\n            if(Controls::g_InputMethods.empty())\n                return -1;\n\n            return 1;\n        }\n    }\n    else if(c.Down || (Is1P() && l_SharedControls.MenuDown))\n    {\n        Down();\n    }\n    else if(c.Up || (Is1P() && l_SharedControls.MenuUp))\n    {\n        Up();\n    }\n    else if(c.Left || (Is1P() && l_SharedControls.MenuLeft))\n    {\n        Left();\n    }\n    else if(c.Right || (Is1P() && l_SharedControls.MenuRight))\n    {\n        Right();\n    }\n    else if(Is1P() && m_state == PlayerState::SelectChar && c.Drop)\n    {\n        // force Select Profile mode\n        m_state = PlayerState::ControlsMenu;\n        m_menu_item = 1;\n        Do();\n    }\n\n    return 0;\n}\n\nint Logic()\n{\n    /*-----------------------*\\\n    ||    Shared back key    ||\n    \\*-----------------------*/\n    if(CheckDone() || (s_context == Context::MainMenu && Controls::g_InputMethods.size() == 0))\n    {\n        if(!l_SharedControls.MenuBack)\n            MenuCursorCanMove = true;\n\n        if((l_SharedControls.MenuBack && MenuCursorCanMove) || (SharedCursor.Secondary && MenuMouseRelease))\n        {\n            PlaySoundMenu(SFX_Slide);\n            MenuCursorCanMove = false;\n            return -1;\n        }\n    }\n\n    /*-----------------------*\\\n    ||   per-player logic    ||\n    \\*-----------------------*/\n\n    for(int p = 0; p < maxLocalPlayers; p++)\n    {\n        int p_ret = s_players[p].Logic();\n        if(p_ret)\n        {\n            s_char_info.mark_present();\n            if(p_ret == 1 || s_context == Context::DropAdd)\n                s_logRecentChars();\n            return p_ret;\n        }\n    }\n\n    int ret = MouseLogic();\n    if(ret)\n    {\n        s_char_info.mark_present();\n        if(ret == 1 || s_context == Context::DropAdd)\n            s_logRecentChars();\n        return ret;\n    }\n\n    /*-----------------------*\\\n    || polling input methods ||\n    \\*-----------------------*/\n    bool block_poll = false;\n\n    // only allow the correct number of players to connect\n    int first_not_connected = std::find(Controls::g_InputMethods.begin(), Controls::g_InputMethods.end(), nullptr) - Controls::g_InputMethods.begin();\n    if(first_not_connected >= BoxCount())\n        block_poll = true;\n\n    // block polling if a player might need to restore their previous profile\n    for(int p = 0; p < maxLocalPlayers; p++)\n    {\n        if(s_players[p].m_state == PlayerState::ConfirmProfile)\n            block_poll = true;\n    }\n\n    if(!block_poll)\n    {\n        auto found = Controls::PollInputMethod();\n        if(found)\n            found->used_for_player = true;\n    }\n\n    return 0;\n}\n\nvoid Render()\n{\n    Mouse_Render(false, true);\n}\n\n// reset allowed char tracking\nvoid SaveChars()\n{\n    s_char_info.reset();\n    s_char_info.mark_present();\n}\n\n} // namespace ConnectScreen\n"
  },
  {
    "path": "src/main/screen_connect.h",
    "content": "#ifndef SCREEN_CONNECT_H\n\n#define SCREEN_CONNECT_H\n\n// for maxLocalPlayers\n#include \"../global_constants.h\"\n\nnamespace ConnectScreen\n{\n\nvoid MainMenu_Start(int minPlayers);\nvoid DropAdd_Start();\nvoid LegacyMenu_Start();\n\nvoid Render();\n\n// return values:\n// -1 back\n// 0 continue\n// 1 next\nint Logic();\n\n// used to track which characters\n//   were present in last case with\n//   SwapCharAllowed() true.\nvoid SaveChars();\n\n} // namespace ConnectScreen\n\n#endif // SCREEN_CONNECT_H\n"
  },
  {
    "path": "src/main/screen_content.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <memory>\n#include <vector>\n\n#include \"core/render.h\"\n\n#include \"graphics.h\"\n#include \"graphics/gfx_marquee.h\"\n#include \"gfx.h\"\n\n#include \"main/menu_main.h\"\n#include \"main/game_info.h\"\n#include \"editor/new_editor.h\"\n\n#include \"globals.h\"\n#include \"controls.h\"\n#include \"sound.h\"\n#include \"config.h\"\n\nvoid menu_draw_infobox_switch_arrows(int infobox_x, int infobox_y);\n\nnamespace ContentSelectScreen\n{\n\nstruct Item\n{\n    int m_index = -1;\n    MarqueeState m_title_marquee_state;\n    MarqueeState m_author_marquee_state;\n\n    const SelectWorld_t* operator->() const;\n};\n\nstatic std::vector<Item> s_items;\nstatic int s_current_item = 0;\nstatic int s_recent_item = -1;\n\nstatic int ScrollDelay = 0;\nstatic int minShow = 0;\nstatic int maxShow = 0;\n\nstatic int worldCurs = 0;\n\nbool editor_target_thextech = true;\n\n\n// Implementation for Item\nconst SelectWorld_t* Item::operator->() const\n{\n    const std::vector<SelectWorld_t>& SelectorList\n        = (MenuMode == MENU_BATTLE_MODE) ? SelectBattle :\n            SelectWorld;\n\n    if(m_index > 0 && m_index < (int)SelectorList.size())\n        return &SelectorList[m_index];\n    else if(SelectorList.size() > 0)\n        return &SelectorList[0];\n    else\n        return nullptr;\n}\n\n\n// Implementation for global routines\n\nvoid Prepare()\n{\n    s_items.clear();\n\n    const std::vector<SelectWorld_t>& SelectorList\n        = (MenuMode == MENU_BATTLE_MODE) ? SelectBattle :\n            SelectWorld;\n\n    size_t editor_entries_start = SelectorList.size() - 2;\n    size_t editor_battle_levels = editor_entries_start;\n    // size_t editor_new_world = SelectorList.size() - 1;\n\n    for(size_t i = 1; i < SelectorList.size(); i++)\n    {\n        // filtering condition can go here\n        if(MenuMode == MENU_EDITOR)\n        {\n            if(!SelectorList[i].editable)\n                continue;\n\n            if(g_gameInfo.disableBattleMode && i == editor_battle_levels)\n                continue;\n        }\n        // skip the special editor worlds if not in editor\n        else if(MenuMode != MENU_BATTLE_MODE && i >= editor_entries_start)\n            continue;\n\n        s_items.emplace_back();\n        s_items.back().m_index = i;\n\n        // load thumbnail here\n    }\n\n    // find recent item\n    s_recent_item = -1;\n\n    for(size_t i = 0; i < s_items.size(); ++i)\n    {\n        auto &w = SelectorList[s_items[i].m_index];\n        const std::string& wPath = w.WorldFilePath;\n\n        if((MenuMode == MENU_1PLAYER_GAME && wPath == g_recentWorld1p) ||\n           (MenuMode == MENU_2PLAYER_GAME && wPath == g_recentWorld2p) ||\n           (MenuMode == MENU_EDITOR && wPath == g_recentWorldEditor))\n        {\n            s_recent_item = (int)i;\n        }\n    }\n\n    if(s_recent_item >= 0)\n    {\n        s_current_item = s_recent_item;\n        worldCurs = s_recent_item - 2;\n    }\n    else\n    {\n        s_current_item = 0;\n        worldCurs = 0;\n    }\n}\n\nint Logic()\n{\n    MenuControls_t menuControls = Controls::GetMenuControls();\n\n    menuControls.Back |= (SharedCursor.Secondary && MenuMouseRelease);\n\n    if(menuControls.Back && menuControls.Do)\n        menuControls.Do = false;\n\n    int MenuX, MenuY;\n    GetMenuPos(&MenuX, &MenuY);\n\n    if(ScrollDelay > 0)\n    {\n        SharedCursor.Move = true;\n        ScrollDelay -= 1;\n    }\n\n    if(SharedCursor.Move)\n    {\n        int B = 0;\n\n        for(int A = minShow; A < maxShow; A++)\n        {\n            if(SharedCursor.Y >= MenuY + B * 30 && SharedCursor.Y <= MenuY + B * 30 + 16)\n            {\n                int menuLen = 19 * static_cast<int>(s_items[A]->WorldName.size());\n\n                if(SharedCursor.X >= MenuX && SharedCursor.X <= MenuX + menuLen)\n                {\n                    if(MenuMouseRelease && SharedCursor.Primary)\n                        MenuMouseClick = true;\n\n                    if(s_current_item != A && ScrollDelay == 0)\n                    {\n                        ScrollDelay = 10;\n                        PlaySoundMenu(SFX_Slide);\n                        s_current_item = A;\n                    }\n                }\n            }\n\n            B += 1;\n        }\n    }\n\n    if(MenuCursorCanMove || MenuMouseClick)\n    {\n        if(menuControls.Back)\n        {\n            PlaySoundMenu(SFX_Slide);\n            MenuCursorCanMove = false;\n            return -1;\n        }\n        else if(menuControls.Do || MenuMouseClick)\n        {\n            bool disabled = false;\n            // Save menu state\n            // listMenuLastScroll = worldCurs;\n            // listMenuLastCursor = s_current_item;\n\n            selWorld = s_items[s_current_item].m_index;\n\n            MenuCursorCanMove = false;\n\n            if(s_items[s_current_item]->disabled)\n                disabled = true;\n\n            if(!disabled)\n                PlaySoundMenu(SFX_Do);\n\n            if(disabled)\n            {\n                PlaySoundMenu(SFX_BlockHit);\n                // Do nothing. stay at menu\n            }\n            // advance to next menu\n            else\n                return 1;\n        }\n    }\n\n    // New world select scroll options!\n    // Based on Wohlstand's but somewhat simpler and less keyboard-specific.\n    // Left and right are -/+ 3 (repeatable, so they also provide a quick-first/quick-last function).\n    // DropItem / Tertiary cursor button is return to last episode.\n    bool dontWrap = false;\n\n    if(SharedCursor.ScrollUp || SharedCursor.ScrollDown)\n    {\n        PlaySoundMenu(SFX_Saw);\n        dontWrap = true;\n\n        if(SharedCursor.ScrollUp)\n            s_current_item -= 1;\n        else\n            s_current_item += 1;\n    }\n    else if(menuControls.Left || menuControls.Right || menuControls.Up || menuControls.Down)\n    {\n        if(MenuCursorCanMove || ScrollDelay == 0)\n        {\n            if(menuControls.Left || menuControls.Right)\n            {\n                int first_new_content = (g_gameInfo.disableBattleMode) ? s_items.size() - 1 : s_items.size() - 2;\n\n                if(MenuMode == MENU_EDITOR && s_current_item >= first_new_content)\n                {\n                    if(MenuCursorCanMove)\n                    {\n                        editor_target_thextech = !editor_target_thextech;\n                        PlaySoundMenu(SFX_Climbing);\n                        ScrollDelay = -1;\n                    }\n                }\n                else\n                {\n                    PlaySoundMenu(SFX_Saw);\n                    ScrollDelay = 15;\n                    dontWrap = true;\n\n                    if(menuControls.Left)\n                        s_current_item -= 3;\n                    else\n                        s_current_item += 3;\n                }\n            }\n            else\n            {\n                PlaySoundMenu(SFX_Slide);\n                ScrollDelay = -1;\n\n                if(menuControls.Up)\n                    s_current_item -= 1;\n                else\n                    s_current_item += 1;\n            }\n\n            MenuCursorCanMove = false;\n        }\n    }\n\n    if(menuControls.Home && MenuCursorCanMove && s_recent_item >= 0)\n    {\n        PlaySoundMenu(SFX_Camera);\n        s_current_item = s_recent_item;\n        MenuCursorCanMove = false;\n        dontWrap = true;\n    }\n\n    if(dontWrap)\n    {\n        if(s_current_item >= (int)s_items.size())\n            s_current_item = (int)s_items.size() - 1;\n        if(s_current_item < 0)\n            s_current_item = 0;\n    }\n    else\n    {\n        if(s_current_item >= (int)s_items.size())\n            s_current_item = 0;\n        if(s_current_item < 0)\n            s_current_item = (int)s_items.size() - 1;\n    }\n\n    return 0;\n}\n\nvoid Render()\n{\n    int MenuX, MenuY;\n    GetMenuPos(&MenuX, &MenuY);\n\n    // std::string tempStr;\n\n    minShow = 0;\n    maxShow = s_items.size();\n\n    int original_maxShow = maxShow;\n    if(maxShow > 5)\n    {\n        minShow = worldCurs;\n        maxShow = minShow + 5;\n\n        if(s_current_item <= minShow)\n            worldCurs -= 1;\n\n        if(s_current_item >= maxShow - 1)\n            worldCurs += 1;\n\n        if(worldCurs < 0)\n            worldCurs = 0;\n\n        if(worldCurs > original_maxShow - 5)\n            worldCurs = original_maxShow - 5;\n\n        minShow = worldCurs;\n        maxShow = minShow + 5;\n    }\n\n    for(auto A = minShow; A < maxShow; A++)\n    {\n        const Item& w = s_items[A];\n\n        XTColor color;\n        color.r = (A == s_recent_item) ? 0 : 255;\n\n        if(w->disabled)\n            color = {127, 127, 127};\n\n        if(w->probably_incompatible)\n            color = {255, 127, 127};\n\n        int B = A - minShow + 1;\n\n        SuperPrint(w->WorldName, 3, MenuX, MenuY - 30 + (B * 30), color);\n    }\n\n    // preview type of content being created\n    int first_new_content = (g_gameInfo.disableBattleMode) ? s_items.size() - 1 : s_items.size() - 2;\n    if(MenuMode == MENU_EDITOR && s_current_item >= first_new_content)\n    {\n        int B = s_current_item - minShow + 1;\n\n        // draw mode icon\n        int mode_icon_X = MenuX + 340;\n        int mode_icon_Y = MenuY - 34 + (B * 30);\n\n        uint16_t rot = SDL_abs((int)(CommonFrame % 64) - 32);\n        rot = rot - 16;\n\n        if(!editor_target_thextech)\n            XRender::renderTextureScaleEx(mode_icon_X, mode_icon_Y, 24, 24, GFXNPC[NPCID_POWER_S3], 0, 0, 32, 32, rot, nullptr, X_FLIP_NONE);\n        else if(GFX.EIcons.inited)\n            XRender::renderTextureScaleEx(mode_icon_X, mode_icon_Y, 24, 24, GFX.EIcons, 0, 32*Icon::thextech, 32, 32, rot, nullptr, X_FLIP_NONE);\n        else\n            XRender::renderTextureScaleEx(mode_icon_X, mode_icon_Y, 24, 24, GFXNPC[NPCID_ICE_POWER_S3], 0, 0, 32, 32, rot, nullptr, X_FLIP_NONE);\n\n        // draw infobox\n        int infobox_x = XRender::TargetW / 2 - 240;\n        int infobox_y = MenuY + 145;\n\n        XRender::renderRect(infobox_x, infobox_y, 480, 68, {0, 0, 0, 192});\n\n        XTColor color;\n\n        if(editor_target_thextech)\n            color = XTColorF(0.5_n, 0.8_n, 1.0_n);\n        else\n            color = XTColorF(1.0_n, 0.5_n, 0.5_n);\n\n        SuperPrintScreenCenter(g_mainMenu.editorMakeFor, 3, infobox_y + 14, color);\n\n        if(editor_target_thextech)\n            SuperPrintScreenCenter(\"TheXTech\", 3, infobox_y + 34, color);\n        else\n            SuperPrintScreenCenter(\"SMBX 1.3\", 3, infobox_y + 34, color);\n\n        menu_draw_infobox_switch_arrows(infobox_x, infobox_y);\n    }\n\n    // render the scroll indicators\n    if(minShow > 0)\n        XRender::renderTextureBasic(XRender::TargetW/2 - 8, MenuY - 20, GFX.MCursor[1]);\n\n    if(maxShow < original_maxShow)\n        XRender::renderTextureBasic(XRender::TargetW/2 - 8, MenuY + 140, GFX.MCursor[2]);\n\n    int B = s_current_item - minShow;\n\n    if(B >= 0 && B < 5)\n        XRender::renderTextureBasic(MenuX - 20, MenuY + (B * 30), GFX.MCursor[0]);\n}\n\n\n} // namespace ContentSelectScreen\n"
  },
  {
    "path": "src/main/screen_content.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef SCREEN_CONTENT_H\n#define SCREEN_CONTENT_H\n\nnamespace ContentSelectScreen\n{\n\nextern bool editor_target_thextech;\n\nvoid Prepare();\n\nvoid Render();\n\n// return values:\n// -1 back\n// 0 continue\n// 1 next\nint Logic();\n\n} // namespace ContentSelectScreen\n\n#endif // SCREEN_CONTENT_H\n"
  },
  {
    "path": "src/main/screen_options.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n\n#include <IniProcessor/ini_processing.h>\n#include <AppPath/app_path.h>\n#include <Logger/logger.h>\n#include <fmt_format_ne.h>\n\n#include \"core/render.h\"\n\n#include \"global_constants.h\"\n#include \"global_dirs.h\"\n#include \"sound.h\"\n#include \"globals.h\"\n#include \"gfx.h\"\n#include \"graphics.h\"\n#include \"config.h\"\n\n#include \"cheat_code.h\"\n#include \"game_main.h\"\n#include \"main/game_info.h\"\n#include \"main/menu_main.h\"\n#include \"main/screen_options.h\"\n#include \"graphics/gfx_frame.h\"\n#include \"graphics/gfx_marquee.h\"\n#include \"fontman/font_manager.h\"\n#include \"main/menu_controls.h\"\n#include \"controls.h\"\n#include \"control/controls_methods.h\" // to cancel keyboard's double-click fullscreen\n\nnamespace OptionsScreen\n{\n\nconstexpr size_t SECTION_NONE = -1;\nsize_t section_index = SECTION_NONE;\nstd::vector<size_t> section_header_indexes;\nsize_t next_section_index = 0;\n\nstd::vector<size_t> visible_items;\n\n\nsize_t cur_item = 0;\nsize_t cur_scroll = 0;\n\nint8_t mouse_scroll_cooldown = -1;\nbool controls_ready = false;\nbool cur_item_changed = false;\n\nbool global_dirty = false;\n\nMarqueeState name_marquee;\nMarqueeState value_marquee;\nMarqueeState name_tooltip_marquee;\nMarqueeState value_tooltip_marquee;\n\nstatic std::string s_temp_string;\n\n// Config_t* const config_levels[] = {nullptr, nullptr, &g_gameInfo, &g_config_game_user, &g_config_episode_creator, &g_config_episode_user,\n//     &g_config_file_creator, nullptr, &g_config_cmdline, nullptr};\n\ninline bool s_deferred_changes()\n{\n#if (defined(CUSTOM_AUDIO) || defined(__16M__)) && defined(RENDER_CUSTOM)\n    return false;\n#else\n    return section_index != SECTION_NONE && g_config.m_options[section_index] == &g_config.advanced;\n#endif\n}\n\ninline void s_set_dirty()\n{\n    global_dirty = true;\n}\n\ninline void s_switch_item()\n{\n    cur_item_changed = false;\n    name_marquee.reset_width();\n    value_marquee.reset_width();\n    name_tooltip_marquee.reset_width();\n    value_tooltip_marquee.reset_width();\n}\n\ninline void s_change_item()\n{\n    cur_item_changed = true;\n    value_marquee.reset_width();\n    value_tooltip_marquee.reset_width();\n    s_set_dirty();\n\n    if(!s_deferred_changes())\n        UpdateConfig();\n}\n\ninline size_t get_num_items()\n{\n    return visible_items.size();\n}\n\ninline bool is_subsection(size_t item)\n{\n    if(section_index == SECTION_NONE || item >= get_num_items())\n        return false;\n\n    if(g_config.m_options[visible_items[item]] == &g_config.reset_all)\n        return false;\n\n    return dynamic_cast<ConfigSubSection_t<true>*>(g_config.m_options[visible_items[item]]) != nullptr;\n}\n\ninline void s_reset_friends()\n{\n    bool any_compat_set = false;\n\n    for(size_t index : visible_items)\n    {\n        if(index < g_config.m_options.size() && g_config.m_options[index] && g_config.m_options[index]->m_set == ConfigSetLevel::cheat)\n        {\n            g_config.m_options[index]->unset();\n            any_compat_set = true;\n        }\n    }\n\n    if(any_compat_set)\n    {\n        UpdateConfig();\n        PlaySoundMenu(SFX_LavaMonster);\n    }\n    else\n        PlaySoundMenu(SFX_BlockHit);\n}\n\ninline void s_check_friends_edited()\n{\n    bool any_compat_set = false;\n\n    for(size_t index : visible_items)\n    {\n        if(index < g_config.m_options.size() && g_config.m_options[index] && g_config.m_options[index]->m_set == ConfigSetLevel::cheat)\n        {\n            any_compat_set = true;\n            break;\n        }\n    }\n\n    g_CheatEditYourFriends = any_compat_set;\n}\n\nvoid RefreshVisibleItems();\n\nvoid ResetStrings()\n{\n    name_marquee.reset_width();\n    value_marquee.reset_width();\n    name_tooltip_marquee.reset_width();\n    value_tooltip_marquee.reset_width();\n}\n\nvoid Init()\n{\n    section_header_indexes.clear();\n    size_t i = 0;\n    for(BaseConfigOption_t<false>* opt : g_options.m_options)\n    {\n        if(dynamic_cast<ConfigSection_t<false>*>(opt))\n            section_header_indexes.push_back(i);\n        i++;\n    }\n\n    section_index = SECTION_NONE;\n\n    if(g_CheatEditYourFriends == 2)\n    {\n        // set to compat\n        for(size_t i : section_header_indexes)\n        {\n            if(g_options.m_options[i] == &g_options.compat)\n            {\n                section_index = i;\n                break;\n            }\n        }\n    }\n\n    RefreshVisibleItems();\n\n    cur_item = 0;\n    s_switch_item();\n    cur_scroll = 0;\n\n    controls_ready = false;\n\n    global_dirty = false;\n}\n\nvoid RefreshVisibleItems()\n{\n    visible_items.clear();\n\n    auto current_scope = Config_t::Scope::UserVisible;\n\n    if(section_index == SECTION_NONE)\n    {\n        for(size_t i : section_header_indexes)\n        {\n            if(g_options.m_options[i] == &g_options.view_credits && !GameMenu)\n                continue;\n\n            // previously banned access to advanced menu during gameplay\n            // if(g_options.m_options[i] == &g_options.advanced && !GameMenu)\n            //     continue;\n\n            if(g_options.m_options[i] == &g_options.episode_options && (GameMenu || BattleMode))\n                continue;\n\n            if(g_options.m_options[i] == &g_options.compat && !g_CheatEditYourFriends)\n                continue;\n\n            if(g_options.m_options[i]->m_scope & current_scope)\n                visible_items.push_back(i);\n        }\n    }\n    else\n    {\n        bool last_was_subsection = false;\n\n        auto it = std::find(section_header_indexes.begin(), section_header_indexes.end(), section_index);\n        if(it == section_header_indexes.end() || it + 1 == section_header_indexes.end())\n            next_section_index = g_config.m_options.size();\n        else\n            next_section_index = *(it + 1);\n\n        for(size_t i = section_index + 1; i < next_section_index; i++)\n        {\n            if(g_options.m_options[i] == &g_options.enable_last_warp_hub_resume && !NoMap)\n                continue;\n\n            if(g_CheatEditYourFriends || g_options.m_options[i]->m_scope & current_scope)\n            {\n                bool this_is_subsection = g_options.m_options[i] != &g_options.reset_all && dynamic_cast<ConfigSubSection_t<false>*>(g_options.m_options[i]);\n\n                if(last_was_subsection && this_is_subsection)\n                    visible_items.resize(visible_items.size() - 1);\n\n                visible_items.push_back(i);\n\n                last_was_subsection = this_is_subsection;\n            }\n        }\n\n        if(last_was_subsection)\n            visible_items.resize(visible_items.size() - 1);\n    }\n}\n\n// checks to see whether it is possible for a Left/Right/Do to be performed given the current cursor and resolve which exact option should be modified\ninline BaseConfigOption_t<true>* PrepareAction(bool to_delete = false)\n{\n    if(cur_item >= visible_items.size())\n        return nullptr;\n\n    size_t i = visible_items[cur_item];\n    if(i >= next_section_index)\n        return nullptr;\n\n    // ban changing speedrun mode\n    if(g_config.m_options[i] == &g_config.speedrun_mode)\n        return nullptr;\n\n    // is it one of the other unique episode config options?\n    bool is_ep_config = (g_config.m_options[i] == &g_config.creator_compat) || (g_config.m_options[i] == &g_config.playstyle) || (g_config.m_options[i] == &g_config.enable_last_warp_hub_resume);\n\n    // check some info about the option\n    ConfigSetLevel prev_level = g_config.m_options[i]->m_set;\n    bool use_cheat = g_CheatEditYourFriends && ((g_options.m_options[i]->m_scope & g_config_game_user.m_scope) == 0 || prev_level > ConfigSetLevel::user_config);\n    bool was_debug = (!use_cheat && prev_level == ConfigSetLevel::cheat) || (prev_level == ConfigSetLevel::debug);\n\n    // level that will be set\n    ConfigSetLevel target_level = (is_ep_config) ? ConfigSetLevel::ep_config\n        : ((use_cheat) ? ConfigSetLevel::cheat\n            : ConfigSetLevel::user_config);\n\n    if(prev_level > target_level && !was_debug)\n        return nullptr;\n\n    if(!(is_ep_config || use_cheat) && (g_options.m_options[i]->m_scope & g_config_game_user.m_scope) == 0)\n        return nullptr;\n\n    BaseConfigOption_t<true>* opt = (is_ep_config || use_cheat) ? g_config.m_options[i] : g_config_game_user.m_options[i];\n\n    // if not currently active, set from previous level\n    if(!opt->is_set() || (was_debug && !to_delete))\n    {\n        opt->m_set = ConfigSetLevel::unset;\n\n        if(g_config.m_options[i]->is_set() && !(s_deferred_changes() && to_delete))\n            opt->update_from(*g_config.m_options[i], ConfigSetLevel::set);\n        else\n            opt->set_from_default(ConfigSetLevel::set);\n\n        opt->m_set = ConfigSetLevel::unset;\n    }\n\n    if(was_debug)\n    {\n        pLogDebug(\"Unsetting debug / cheat option [%s]\", g_options.m_options[i]->m_internal_name);\n\n        g_config.m_options[i]->unset();\n        s_change_item();\n\n        // consider this the deleted option\n        if(to_delete)\n        {\n            PlaySoundMenu(SFX_PlayerShrink);\n            return nullptr;\n        }\n    }\n\n    if(is_ep_config || use_cheat)\n        opt->m_set = target_level;\n\n    return opt;\n}\n\nvoid Do()\n{\n    if(cur_item >= visible_items.size())\n        return;\n\n    size_t real_item = visible_items[cur_item];\n\n    if(real_item >= g_config.m_options.size())\n        return;\n\n    if(g_config.m_options[real_item] == &g_config.reset_all)\n    {\n        s_reset_friends();\n    }\n    else if(dynamic_cast<ConfigSection_t<true>*>(g_config.m_options[real_item]))\n    {\n        if(g_config.m_options[real_item] == &g_config.controls)\n        {\n            PlaySoundMenu(SFX_Do);\n\n            global_dirty = true;\n            MenuMode = MENU_INPUT_SETTINGS;\n            MenuCursor = 0;\n            MenuCursorCanMove = false;\n            controls_ready = false;\n            return;\n        }\n        else if(g_config.m_options[real_item] == &g_config.view_credits && GameMenu)\n        {\n            controls_ready = false;\n\n            PlaySoundMenu(SFX_Do);\n            GameMenu = false;\n            GameOutro = true;\n\n            // these is here only to prevent the screen from looking strange\n            SetupCredits();\n            return;\n        }\n\n        section_index = real_item;\n\n        RefreshVisibleItems();\n\n        if(visible_items.size() == 0)\n        {\n            PlaySoundMenu(SFX_BlockHit);\n            section_index = SECTION_NONE;\n            RefreshVisibleItems();\n        }\n        else\n        {\n            cur_item = 0;\n            s_switch_item();\n            while(is_subsection(cur_item) && cur_item + 1 < visible_items.size())\n                cur_item++;\n\n            cur_scroll = 0;\n\n            PlaySoundMenu(SFX_Do);\n        }\n    }\n    else\n    {\n        BaseConfigOption_t<true>* opt = PrepareAction();\n\n        if(opt && opt->change())\n        {\n            s_change_item();\n            PlaySoundMenu(SFX_Do);\n        }\n        else\n        {\n            PlaySoundMenu(SFX_BlockHit);\n        }\n    }\n}\n\nvoid RotateLeft()\n{\n    if(section_index == SECTION_NONE)\n        return;\n\n    BaseConfigOption_t<true>* opt = PrepareAction();\n\n    if(opt && opt->rotate_left())\n    {\n        s_change_item();\n        PlaySoundMenu(SFX_Do);\n    }\n    else\n    {\n        PlaySoundMenu(SFX_BlockHit);\n    }\n}\n\nvoid RotateRight()\n{\n    if(section_index == SECTION_NONE)\n        return;\n\n    BaseConfigOption_t<true>* opt = PrepareAction();\n\n    if(opt && opt->rotate_right())\n    {\n        s_change_item();\n        PlaySoundMenu(SFX_Do);\n    }\n    else\n    {\n        PlaySoundMenu(SFX_BlockHit);\n    }\n}\n\nvoid Delete()\n{\n    if(section_index == SECTION_NONE)\n        return;\n\n    BaseConfigOption_t<true>* opt = PrepareAction(true);\n\n    if(opt && opt->is_set() && opt != &g_config.playstyle && opt != &g_config.creator_compat)\n    {\n        // restore to default internally -- this helps when directly displaying g_config_game_user items\n        opt->set_from_default(ConfigSetLevel::set);\n        opt->unset();\n        s_change_item();\n        PlaySoundMenu(SFX_PlayerShrink);\n    }\n    else\n    {\n        PlaySoundMenu(SFX_BlockHit);\n    }\n}\n\nbool Back();\n\nvoid Select()\n{\n    if(s_deferred_changes())\n    {\n        UpdateConfig();\n        PlaySoundMenu(SFX_PSwitch);\n    }\n}\n\nbool Back()\n{\n    PlaySoundMenu(SFX_Slide);\n\n    if(g_CheatEditYourFriends == 2)\n    {\n        // return to game directly from first trip to edit your friends screen\n        g_CheatEditYourFriends = 1;\n        s_check_friends_edited();\n        global_dirty = false;\n        return true;\n    }\n    else if(section_index == SECTION_NONE)\n    {\n        pLogDebug(\"Saving config on options screen exit (config path [%s], dirty %d)\",\n            AppPathManager::settingsFileSTD().c_str(), (int)global_dirty);\n\n        // save any dirty configs\n        if(global_dirty)\n            SaveConfig();\n\n        return true;\n    }\n    else\n    {\n        if(s_deferred_changes())\n            UpdateConfig();\n\n        if(section_index < g_config.m_options.size() && g_config.m_options[section_index] == &g_config.compat)\n            s_check_friends_edited();\n\n        s_switch_item();\n\n        int last_section = section_index;\n\n        section_index = SECTION_NONE;\n        cur_scroll = 0;\n        RefreshVisibleItems();\n\n        auto it = std::find(visible_items.begin(), visible_items.end(), last_section);\n\n        // go to last item if couldn't find\n        if(it == visible_items.end() && it != visible_items.begin())\n            cur_item = (int)((visible_items.end() - 1) - visible_items.begin());\n        else\n            cur_item = (int)(it - visible_items.begin());\n    }\n\n    return false;\n}\n\nstatic inline void Render_Cursor(int x, int y, int p)\n{\n    XRender::renderTextureBasic(x, y, GFX.MCursor[0]);\n\n    UNUSED(p);\n}\n\nbool Mouse_Render(bool mouse, bool render)\n{\n    if(mouse && !render && !SharedCursor.Move && !SharedCursor.Primary && !SharedCursor.Secondary && !SharedCursor.ScrollUp && !SharedCursor.ScrollDown)\n    {\n        if(!GameMenu)\n            MenuMouseRelease = true;\n\n        // subtract from the mouse scroll cooldown\n        if(mouse_scroll_cooldown > 0)\n            mouse_scroll_cooldown--;\n    }\n\n    // want 680px width.\n    int width;\n    if(XRender::TargetW < 640)\n        width = XRender::TargetW - 16 - XRender::TargetOverscanX * 2;\n    else if(XRender::TargetW < 720)\n        width = XRender::TargetW - 40 - XRender::TargetOverscanX * 2;\n    else\n        width = 680;\n\n    const bool tight_mode = width < 640 || XRender::TargetH < 480;\n\n    // calculate line height: want 15 lines of text\n    int line = (XRender::TargetH - 60) / 15;\n    line -= line & 1;\n    // max line height 30\n    if(line > 30)\n        line = 30;\n\n    // check for Chinese and Korean languages\n    int min_line_size = FontManager::getMetricsValue(FontManager::Metrics_MenuMinLineHeight, CurrentLanguage);\n\n    // (it's okay if we don't get 15 lines, but we need at least 18px per line.)\n    int max_line = 15;\n    if(line < min_line_size)\n    {\n        line = min_line_size;\n        max_line = (int)XRender::TargetH / line;\n\n        // fix some strange offscreen issues\n        if(min_line_size > 18)\n            max_line--;\n    }\n\n    // two header rows, two footer rows\n    const int first_display_row = 2;\n    const int display_rows = max_line - 4;\n\n    // calculate main column and value column width\n    const int total_width = tight_mode ? width - 32 : width - 64;\n    const int main_width  = total_width * 3 / 4 - 16;\n    const int value_width = total_width * 1 / 4;\n\n    // horizontal start of the menu\n    int sX = XRender::TargetW / 2 - width / 2;\n    sX -= sX & 1;\n    // vertical start of the menu\n    int sY = XRender::TargetH / 2 - (line * max_line) / 2;\n    sY -= sY & 1;\n\n    int cursor_x = tight_mode ? sX + 8 : sX + 24;\n\n    size_t num_items = get_num_items();\n\n    // mouse logic\n    if(mouse)\n    {\n        if(SharedCursor.ScrollUp)\n        {\n            if(cur_scroll > 0)\n            {\n                PlaySoundMenu(SFX_Saw);\n                cur_scroll--;\n                if(cur_item >= display_rows + cur_scroll - 1)\n                {\n                    cur_item--;\n                    s_switch_item();\n                }\n            }\n        }\n        else if(SharedCursor.ScrollDown)\n        {\n            if(cur_scroll + display_rows < num_items)\n            {\n                PlaySoundMenu(SFX_Saw);\n                cur_scroll++;\n\n                if(cur_item < cur_scroll + 1)\n                {\n                    cur_item++;\n                    s_switch_item();\n                }\n            }\n        }\n\n        if(SharedCursor.X < sX || SharedCursor.X >= sX + width / 2)\n            return false;\n\n        int cur_mouse_line = ((int)SharedCursor.Y - sY) / line;\n        int cur_mouse_item = cur_scroll + cur_mouse_line - first_display_row;\n\n        if(SharedCursor.Primary && cur_mouse_item == (int)cur_item && MenuMouseRelease)\n        {\n#ifdef KEYBOARD_H\n            Controls::g_cancelDoubleClick = true;\n#endif\n            MenuMouseRelease = false;\n            Do();\n        }\n        else if(SharedCursor.Secondary && MenuMouseRelease)\n        {\n            MenuMouseRelease = false;\n            return Back();\n        }\n        else if(cur_mouse_item != (int)cur_item\n            && cur_mouse_line >= first_display_row\n            && cur_mouse_line < first_display_row + display_rows\n            && cur_mouse_item >= 0\n            && cur_mouse_item < (int)num_items)\n        {\n            if(cur_mouse_line == first_display_row || cur_mouse_line == first_display_row + display_rows - 1)\n            {\n                if(mouse_scroll_cooldown > 0 || (mouse_scroll_cooldown == -1 && !SharedCursor.Move))\n                    return false;\n                else\n                    mouse_scroll_cooldown = 11;\n            }\n            else if(!SharedCursor.Move)\n                return false;\n\n            if(is_subsection(cur_mouse_item))\n            {\n                if(cur_mouse_line == first_display_row && cur_scroll > 0)\n                {\n                    PlaySoundMenu(SFX_Saw);\n                    cur_scroll--;\n                    if(cur_item >= display_rows + cur_scroll - 1)\n                    {\n                        cur_item--;\n                        s_switch_item();\n                    }\n                }\n                else if(cur_mouse_line == first_display_row + display_rows - 1 && cur_scroll + display_rows < num_items)\n                {\n                    PlaySoundMenu(SFX_Saw);\n                    cur_scroll++;\n\n                    if(cur_item < cur_scroll + 1)\n                    {\n                        cur_item++;\n                        s_switch_item();\n                    }\n                }\n                else\n                {\n                    return false;\n                }\n            }\n\n            cur_item = (size_t)cur_mouse_item;\n            s_switch_item();\n            if(!SharedCursor.ScrollUp && !SharedCursor.ScrollDown)\n                PlaySoundMenu(SFX_Slide);\n        }\n\n        if(SharedCursor.Primary)\n            MenuMouseRelease = false;\n\n        return false;\n    }\n\n    // render logic\n\n    // render the background\n    DrawSimpleFrame(sX, sY - (line - 18) - 4, width, line * max_line + (line - 18) + 8, {0, 0, 0, 127}, {255, 255, 255, 127}, {0, 0, 0, 127});\n\n    // render the title\n    if(section_index != SECTION_NONE && section_index < g_config.m_options.size())\n        g_config.m_options[section_index]->get_display_name(s_temp_string);\n    else\n        s_temp_string = g_mainMenu.mainOptions;\n\n    SuperPrintScreenCenter(s_temp_string, 3, sY);\n\n    // locate the cursor\n    int cur_item_row = cur_item - cur_scroll;\n\n    // handle forced scrolling\n    if(cur_item_row < 1 && cur_scroll > 0)\n    {\n        cur_scroll--;\n        cur_item_row++;\n    }\n    else if(cur_item_row >= display_rows - 1 && cur_scroll + display_rows < num_items)\n    {\n        cur_scroll++;\n        cur_item_row--;\n    }\n\n    // render the cursor\n    if(cur_item_row >= 0 && cur_item_row < display_rows)\n        Render_Cursor(cursor_x, sY + (cur_item_row + first_display_row) * line, 0);\n\n    // render the scroll indicators\n    if(cur_scroll > 0)\n        XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[1].w / 2, sY + first_display_row * line - GFX.MCursor[1].h, GFX.MCursor[1]);\n\n    if(cur_scroll + display_rows < num_items)\n        XRender::renderTextureBasic(sX + width / 2 - GFX.MCursor[2].w / 2, sY + (first_display_row + display_rows) * line - line + 18, GFX.MCursor[2]);\n\n    MarqueeSpec name_marquee_spec = MarqueeSpec(0, 10, 64, 32, -1);\n    MarqueeSpec value_marquee_spec = MarqueeSpec(value_width, 10, 64, 32, 1);\n\n    // render the options\n    for(size_t i = cur_scroll; i < cur_scroll + display_rows && i < num_items; i++)\n    {\n        BaseConfigOption_t<true>* opt = g_config.m_options[visible_items[i]];\n\n        if(s_deferred_changes())\n        {\n            BaseConfigOption_t<true>* user_opt = g_config_game_user.m_options[visible_items[i]];\n            if(user_opt->m_set != ConfigSetLevel::unset || opt->m_set == ConfigSetLevel::user_config)\n                opt = user_opt;\n        }\n\n        bool is_header = is_subsection(i);\n\n        // for subsection headers, indent to left and allow to fill screen\n        int lX = is_header ? cursor_x : cursor_x + 24;\n        XTColor name_color = is_header ? XTColorF(0.7_n, 0.7_n, 0.7_n) : XTColor();\n        int name_font = is_header ? 3 : 5;\n\n        name_marquee_spec.marquee_width = is_header ? total_width : main_width;\n\n        if(tight_mode && i != cur_item)\n            name_color = name_color * XTColor::from_num(0.6_n);\n\n        if(opt == &g_config.compat || opt == &g_config.reset_all)\n            name_color.g = 0;\n\n        // display name\n        opt->get_display_name(s_temp_string);\n\n        MarqueeState null_marquee;\n\n        SuperPrintMarquee(s_temp_string, name_font,\n            lX, sY + (i - cur_scroll + first_display_row) * line,\n            name_marquee_spec, (i == cur_item) ? name_marquee : null_marquee,\n            name_color);\n\n        if(i == cur_item)\n            name_marquee.advance(name_marquee_spec);\n\n        if(is_header)\n            continue;\n\n        // display value\n        null_marquee = MarqueeState();\n\n        opt->get_display_value(s_temp_string);\n\n        // pick the appropriate tint based on the setting level\n        XTColor vcolor;\n        switch(opt->m_set)\n        {\n        case(ConfigSetLevel::debug):\n        case(ConfigSetLevel::script):\n        case(ConfigSetLevel::cheat):\n            vcolor = XTColorF(1.0_n, 0.0_n, 1.0_n, 1.0_n);\n            break;\n        case(ConfigSetLevel::compat):\n        case(ConfigSetLevel::speedrun):\n            vcolor = XTColorF(0.7_n, 0.5_n, 0.2_n, 1.0_n);\n            break;\n        case(ConfigSetLevel::cmdline):\n            vcolor = XTColorF(0.0_n, 0.5_n, 1.0_n, 1.0_n);\n            break;\n        case(ConfigSetLevel::file_compat):\n        case(ConfigSetLevel::ep_compat):\n            vcolor = XTColorF(1.0_n, 1.0_n, 0.0_n, 1.0_n);\n            break;\n        default:\n        case(ConfigSetLevel::user_config):\n            if(s_temp_string == \"✓\" || s_temp_string == \"+\")\n                vcolor = XTColorF(0.5_n, 1.0_n, 0.5_n, 1.0_n);\n            else if(s_temp_string == \"×\" || s_temp_string == \"-\")\n                vcolor = XTColorF(1.0_n, 0.5_n, 0.5_n, 1.0_n);\n            else\n                vcolor = XTColorF(1.0_n, 1.0_n, 1.0_n, 1.0_n);\n            break;\n        case(ConfigSetLevel::game_info):\n        case(ConfigSetLevel::game_defaults):\n        case(ConfigSetLevel::bugfix_defaults):\n            if(s_temp_string == \"✓\" || s_temp_string == \"+\")\n                vcolor = XTColorF(0.25_n, 0.5_n, 0.25_n, 1.0_n);\n            else if(s_temp_string == \"×\" || s_temp_string == \"-\")\n                vcolor = XTColorF(0.5_n, 0.25_n, 0.25_n, 1.0_n);\n            else\n                vcolor = XTColorF(0.5_n, 0.5_n, 0.5_n, 1.0_n);\n            break;\n        }\n\n        if(s_deferred_changes())\n        {\n            const BaseConfigOption_t<true>* main_opt = g_config.m_options[visible_items[i]];\n            if(!(*opt == *main_opt))\n                vcolor = XTColor(240, 255, 32);\n            else\n            {\n                auto* opt_impl = dynamic_cast<ConfigSetupEnum_t<true>*>(opt);\n                const auto* main_opt_impl = dynamic_cast<const ConfigSetupEnum_t<true>*>(main_opt);\n\n                // provide value for \"Auto\" from main option\n                if(opt_impl && main_opt_impl && opt_impl != main_opt_impl)\n                {\n                    if(main_opt_impl->m_value == 0)\n                        opt_impl->obtained = main_opt_impl->obtained;\n                    else\n                        opt_impl->obtained = 0;\n                }\n\n                if(main_opt_impl && main_opt_impl->m_value != 0 && main_opt_impl->m_value != main_opt_impl->obtained)\n                    vcolor = XTColor(192, 96, 96);\n            }\n        }\n\n        if(tight_mode && i != cur_item)\n            vcolor = vcolor * XTColor::from_num(0.6_n);\n\n        const int rX = tight_mode ? sX + width - 8 : sX + width - 16;\n        // SuperPrintRightAlign(s_temp_string, 3, rX, sY + (i - cur_scroll + first_display_row) * line, vr, vg, vb, va * name_opacity);\n\n        SuperPrintMarquee(s_temp_string, name_font,\n            rX - value_width, sY + (i - cur_scroll + first_display_row) * line,\n            value_marquee_spec, (i == cur_item) ? value_marquee : null_marquee,\n            vcolor);\n\n        if(i == cur_item)\n            value_marquee.advance(value_marquee_spec);\n    }\n\n    // render the tooltips\n    if(cur_item < visible_items.size())\n    {\n        MarqueeSpec tooltip_marquee_spec = MarqueeSpec(total_width, 10, 64, 32, 0);\n        int tooltip_left = sX + (width - total_width) / 2;\n\n        BaseConfigOption_t<true>* opt = g_config.m_options[visible_items[cur_item]];\n\n        if(cur_item_changed && !opt->get_value_tooltip(s_temp_string).empty())\n        {\n            value_tooltip_marquee.advance(tooltip_marquee_spec);\n            SuperPrintMarquee(s_temp_string, 5, tooltip_left, sY + (first_display_row + display_rows + 1) * line, tooltip_marquee_spec, value_tooltip_marquee);\n        }\n        else if(!opt->get_tooltip(s_temp_string).empty())\n        {\n            name_tooltip_marquee.advance(tooltip_marquee_spec);\n            SuperPrintMarquee(s_temp_string, 5, tooltip_left, sY + (first_display_row + display_rows + 1) * line, tooltip_marquee_spec, name_tooltip_marquee);\n        }\n    }\n\n    return false;\n}\n\nvoid Render()\n{\n    // only relevant when launched from game\n    if(MenuMode == MENU_INPUT_SETTINGS)\n        return menuControls_Render();\n\n    Mouse_Render(false, true);\n}\n\nbool Logic()\n{\n    size_t num_items = get_num_items();\n\n    MenuControls_t menuControls = Controls::GetMenuControls();\n\n    // IMPORTANT: delegate to MENU_INPUT_SETTINGS. Only relevant when launched from game.\n    if(MenuMode == MENU_INPUT_SETTINGS)\n    {\n        if(!menuControls.Up && !menuControls.Down && !menuControls.Left && !menuControls.Right && !menuControls.Do && !menuControls.Back && !menuControls.Erase && !menuControls.Home)\n            MenuCursorCanMove = true;\n\n        if(MenuCursorCanMove && menuControls.Up)\n        {\n            PlaySoundMenu(SFX_Slide);\n            MenuCursor--;\n            MenuCursorCanMove = false;\n        }\n\n        if(MenuCursorCanMove && menuControls.Down)\n        {\n            PlaySoundMenu(SFX_Slide);\n            MenuCursor++;\n            MenuCursorCanMove = false;\n        }\n\n        if(!SharedCursor.Primary && !SharedCursor.Secondary)\n            MenuMouseRelease = true;\n\n        if(menuControls_Logic() == -1)\n        {\n            Controls::SaveConfig();\n            MenuMode = MENU_NEW_OPTIONS;\n            MenuMouseRelease = false;\n            controls_ready = false;\n        }\n        return false;\n    }\n\n\n    if(!menuControls.Up && !menuControls.Down && !menuControls.Left && !menuControls.Right && !menuControls.Do && !menuControls.Back && !menuControls.Erase && !menuControls.Home)\n        controls_ready = true;\n    else\n        mouse_scroll_cooldown = -1;\n\n    if(controls_ready && menuControls.Back)\n    {\n        controls_ready = false;\n\n        if(Back())\n        {\n            MenuCursorCanMove = false;\n            MenuMouseRelease = false;\n            return true;\n        }\n    }\n\n    if(controls_ready && menuControls.Do)\n    {\n        controls_ready = false;\n        Do();\n    }\n\n    if(controls_ready && menuControls.Left)\n    {\n        controls_ready = false;\n        RotateLeft();\n    }\n\n    if(controls_ready && menuControls.Right)\n    {\n        controls_ready = false;\n        RotateRight();\n    }\n\n    if(controls_ready && menuControls.Erase)\n    {\n        controls_ready = false;\n        Delete();\n    }\n\n    if(controls_ready && menuControls.Home)\n    {\n        controls_ready = false;\n        Select();\n    }\n\n    if(controls_ready && menuControls.Up)\n    {\n        controls_ready = false;\n        if(cur_item == 0)\n            cur_item = num_items;\n        cur_item--;\n\n        if(is_subsection(cur_item))\n        {\n            if(cur_item == 0)\n                cur_item = num_items;\n            cur_item--;\n        }\n\n        s_switch_item();\n        PlaySoundMenu(SFX_Slide);\n    }\n\n    if(controls_ready && menuControls.Down)\n    {\n        controls_ready = false;\n        cur_item++;\n        if(cur_item == num_items)\n            cur_item = 0;\n\n        if(is_subsection(cur_item))\n        {\n            cur_item++;\n            if(cur_item == num_items)\n                cur_item = 0;\n        }\n\n        s_switch_item();\n        PlaySoundMenu(SFX_Slide);\n    }\n\n    return Mouse_Render(true, false);\n}\n\n} // namespace OptionsScreen\n"
  },
  {
    "path": "src/main/screen_options.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef SCREEN_OPTIONS_H\n\n#define SCREEN_OPTIONS_H\n\nnamespace OptionsScreen\n{\n\nvoid Init();\n\nvoid Render();\n\n// if returns true, user has exited the screen\nbool Logic();\n\nvoid ResetStrings();\n\n} // namespace OptionsScreen\n\n#endif // SCREEN_OPTIONS_H\n"
  },
  {
    "path": "src/main/screen_pause.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <vector>\n#include <string>\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n#include <InterProcess/intproc.h>\n#endif // #ifdef THEXTECH_INTERPROC_SUPPORTED\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n#include \"main/client_methods.h\"\n#endif\n\n#include <Logger/logger.h>\n\n#include \"../globals.h\"\n\n#include \"../player.h\"\n#include \"../sound.h\"\n#include \"../gfx.h\"\n#include \"../graphics.h\"\n#include \"../frm_main.h\"\n#include \"../config.h\"\n#include \"../core/render.h\"\n#include \"../core/events.h\"\n\n#include \"../game_main.h\"\n#include \"menu_main.h\"\n#include \"main/screen_pause.h\"\n#include \"main/screen_textentry.h\"\n#include \"main/screen_connect.h\"\n#include \"speedrunner.h\"\n#include \"cheat_code.h\"\n#include \"message.h\"\n\n#include \"main/game_strings.h\"\n#include \"main/level_medals.h\"\n#include \"main/hints.h\"\n\n#include \"controls.h\"\n\n#include \"editor.h\"\n\nnamespace PauseScreen\n{\n\nstruct MenuItem\n{\n    std::string name;\n    bool (*callback)();\n    bool is_private;\n    MenuItem(const std::string &n, bool(*cb)(), bool p = false) : name(n), callback(cb), is_private(p) {}\n};\n\nenum class PauseType\n{\n    Modern,\n    Testing,\n    Legacy,\n};\n\nstatic PauseType s_pause_type = PauseType::Modern;\nstatic int s_pause_plr = 0;\nstatic int s_longest_width = 0;\nstatic std::vector<MenuItem> s_items;\nstatic int s_cheat_menu_bits = 0;\nstatic uint32_t s_cheat_menu_frame = 0;\nstatic std::array<bool, maxLocalPlayers> s_leftright_release;\n\nuint8_t g_pending_action = 255;\nstatic bool s_force_exit = false;\n\nstatic void s_push_unpause()\n{\n    XMessage::Message unpause;\n    unpause.type = XMessage::Type::menu_action;\n    unpause.message = 0;\n    XMessage::PushMessage(unpause);\n}\n\nstatic bool s_Continue()\n{\n    PlaySound(SFX_Pause);\n    return true;\n}\n\nstatic bool s_RestartLevel()\n{\n    MenuMode = MENU_INTRO;\n    MenuCursor = 0;\n    XRender::setTargetTexture();\n    XRender::clearBuffer();\n    XRender::repaint();\n    EndLevel = true;\n    LevelBeatCode = BEATCODE_RESTART;\n    StopMusic();\n    XEvents::doEvents();\n    return true;\n}\n\nstatic bool s_ResetCheckpoints()\n{\n    pLogDebug(\"Clear check-points from a menu\");\n    Checkpoint.clear();\n    CheckpointsList.clear();\n    g_curLevelMedals.reset_checkpoint();\n    numStars = 0;\n    Star.clear();\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n    IntProc::sendStarsNumber(numStars);\n#endif\n    // numSavedEvents = 0;\n    // BlockSwitch.fill(false);\n    PlaySound(SFX_Bullet);\n    return true;\n}\n\nstatic bool s_DropAddScreen()\n{\n    // don't allow Drop / Add screen in clone mode\n    if(g_ClonedPlayerMode || SingleCoop)\n    {\n        PlaySoundMenu(SFX_BlockHit);\n        return false;\n    }\n\n    PlaySound(SFX_Do);\n\n    PauseInit(PauseCode::DropAdd, 0);\n\n    return false;\n}\n\n\nstatic void s_OptionsScreen_Resume();\n\nstatic bool s_OptionsScreen()\n{\n    PlaySound(SFX_Do);\n\n    PauseInit(PauseCode::Options, 0, s_OptionsScreen_Resume);\n\n    return false;\n}\n\nstatic void s_OptionsScreen_Resume()\n{\n    // re-initialize / re-translate pause menu\n    SoundPause[SFX_Pause] = 1;\n    Init(s_pause_plr, s_pause_type == PauseType::Legacy);\n\n    // set MenuCursor correctly\n    for(size_t i = 0; i < s_items.size(); i++)\n    {\n        if(s_items[i].callback == s_OptionsScreen)\n        {\n            MenuCursor = i;\n            break;\n        }\n    }\n}\n\nstatic void s_CheatScreen_callback()\n{\n    cheats_setBuffer(TextEntryScreen::Text, false);\n\n    // comment this if you want to return to the pause menu\n    s_push_unpause();\n}\n\nstatic bool s_CheatScreen()\n{\n    TextEntryScreen::Init(g_gameStrings.pauseItemEnterCode, s_CheatScreen_callback);\n\n    return true;\n}\n\nstatic bool s_QuitTesting()\n{\n    MenuMode = MENU_INTRO;\n    MenuCursor = 0;\n    XRender::setTargetTexture();\n    XRender::clearBuffer();\n    XRender::repaint();\n    EndLevel = true;\n    LevelBeatCode = BEATCODE_QUIT;\n    StopMusic();\n    XEvents::doEvents();\n\n    if(Backup_FullFileName.empty())\n    {\n        GameIsActive = false; // Quit the game entirely\n    }\n\n    return true;\n}\n\nstatic bool s_SaveAndContinue()\n{\n    bool CanSave = (LevelSelect || IsHubLevel) && !Cheater;\n\n    if(CanSave)\n    {\n        SaveGame();\n        PlaySound(SFX_Checkpoint);\n    }\n    else\n    {\n        // player tried to cheat, scare them\n        PlaySound(SFX_VillainKilled);\n    }\n\n    return true;\n}\n\nstatic bool s_Quit()\n{\n#ifdef THEXTECH_ENABLE_SDL_NET\n    XMessage::Disconnect();\n    s_force_exit = true;\n#endif\n\n    bool CanSave = (LevelSelect || IsHubLevel) && !Cheater;\n\n    if(CanSave)\n        SaveGame(); // \"Save & Quit\"\n    else\n        speedRun_saveStats();\n\n    ConnectScreen::SaveChars();\n\n    s_cheat_menu_bits = 0;\n    GameMenu = true;\n\n    MenuMode = MENU_INTRO;\n    MenuCursor = 0;\n\n    if(!LevelSelect)\n    {\n        LevelSelect = true;\n        EndLevel = true;\n    }\n    else\n        LevelSelect = false;\n\n    XRender::setTargetTexture();\n    XRender::clearBuffer();\n    XRender::repaint();\n    StopMusic();\n    XEvents::doEvents();\n\n    return true;\n}\n\nvoid UnlockCheats()\n{\n    s_cheat_menu_bits = 15;\n    s_cheat_menu_frame = CommonFrame;\n\n    if(GamePaused == PauseCode::PauseScreen)\n        TextEntryScreen::Init(g_gameStrings.pauseItemEnterCode, s_CheatScreen_callback);\n}\n\nvoid Init(int plr, bool LegacyPause)\n{\n    XHints::Select();\n\n    PlaySound(SFX_Pause);\n    MenuCursor = 0;\n    MenuCursorCanMove = false;\n    MenuCursorCanMove_Back = false;\n    g_pending_action = 255;\n    s_force_exit = false;\n\n    if(LegacyPause)\n        s_pause_type = PauseType::Legacy;\n    else if(TestLevel)\n        s_pause_type = PauseType::Testing;\n    else\n        s_pause_type = PauseType::Modern;\n\n    s_pause_plr = plr;\n    if(s_cheat_menu_bits < 14)\n        s_cheat_menu_bits = 0;\n\n    s_leftright_release.fill(false);\n\n    // do a context-aware initialization of s_items\n    s_items.clear();\n\n    bool CanSave = (LevelSelect || IsHubLevel) && !Cheater && !TestLevel;\n\n    // add pause menu items\n\n    // level test\n    if(s_pause_type == PauseType::Testing)\n    {\n        bool inter_screen = (LevelBeatCode <= BEATCODE_RESTART);\n        bool start_screen = (LevelBeatCode == BEATCODE_SETUP);\n        bool editor_test = !Backup_FullFileName.empty();\n\n        if(!inter_screen)\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemContinue, s_Continue});\n\n        s_items.push_back(MenuItem{start_screen ? g_gameStrings.pauseItemContinue : g_gameStrings.pauseItemRestartLevel, s_RestartLevel});\n\n        if(!start_screen && !BattleMode)\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemResetCheckpoints, s_ResetCheckpoints});\n\n        if(g_config.allow_drop_add)\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemPlayerSetup, s_DropAddScreen, true});\n\n        if(!inter_screen && s_cheat_menu_bits >= 14 && !BattleMode)\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemEnterCode, s_CheatScreen, true});\n\n        s_items.push_back(MenuItem{g_mainMenu.mainOptions, s_OptionsScreen, true});\n\n        s_items.push_back(MenuItem{editor_test ? g_gameStrings.pauseItemReturnToEditor : g_gameStrings.pauseItemQuitTesting, s_QuitTesting});\n    }\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // NetPlay pause\n    else if(XMessage::GetStatus() != XMessage::Status::local)\n    {\n        s_items.push_back(MenuItem{g_gameStrings.pauseItemContinue, s_Continue});\n\n        if(g_config.allow_drop_add && s_pause_type != PauseType::Legacy)\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemPlayerSetup, s_DropAddScreen, true});\n\n        if(s_cheat_menu_bits >= 14 && s_pause_type != PauseType::Legacy && !BattleMode)\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemEnterCode, s_CheatScreen, true});\n\n        if(s_pause_type != PauseType::Legacy)\n            s_items.push_back(MenuItem{g_mainMenu.mainOptions, s_OptionsScreen, true});\n\n        s_items.push_back(MenuItem{g_mainMenu.netplayLeaveRoom, s_Quit, true});\n    }\n#endif\n    // main game pause\n    else\n    {\n        s_items.push_back(MenuItem{g_gameStrings.pauseItemContinue, s_Continue});\n\n        if(g_config.allow_drop_add && s_pause_type != PauseType::Legacy)\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemPlayerSetup, s_DropAddScreen, true});\n\n        if(s_cheat_menu_bits >= 14 && s_pause_type != PauseType::Legacy && !BattleMode)\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemEnterCode, s_CheatScreen, true});\n\n        if(s_pause_type != PauseType::Legacy)\n            s_items.push_back(MenuItem{g_mainMenu.mainOptions, s_OptionsScreen, true});\n\n        if(CanSave)\n        {\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemSaveAndContinue, s_SaveAndContinue});\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemSaveAndQuit, s_Quit});\n        }\n        else\n        {\n            s_items.push_back(MenuItem{g_gameStrings.pauseItemQuit, TestLevel ? s_QuitTesting : s_Quit});\n        }\n    }\n\n    const int font = (s_pause_type == PauseType::Legacy) ? 3 : 4;\n\n    // set the longest width\n    s_longest_width = 0;\n\n    for(size_t i = 0; i < s_items.size(); i++)\n    {\n        int item_width = SuperTextPixLen(s_items[i].name, font);\n        if(item_width > s_longest_width)\n            s_longest_width = item_width;\n    }\n\n    int total_menu_height = (int)s_items.size() * 36 - 18;\n    int total_menu_width = s_longest_width + 40;\n\n    // GBA bounds\n    if(total_menu_height > 320 || total_menu_width > 480)\n        pLogWarning(\"Menu doesn't fit within bounds (actual size %dx%d, bounds 480x320)\", total_menu_width, total_menu_height);\n\n    // force cheat entry if needed\n    if(s_cheat_menu_bits == 15)\n    {\n        s_cheat_menu_bits = 14;\n        if(CommonFrame - s_cheat_menu_frame < 60)\n            TextEntryScreen::Init(g_gameStrings.pauseItemEnterCode, s_CheatScreen_callback);\n    }\n}\n\nvoid Render()\n{\n    // height includes intermediate padding but no top/bottom padding\n    // width includes cursor on left and 20px padding on right for symmetry\n    int total_menu_height = (int)s_items.size() * 36 - 18;\n    int total_menu_width = s_longest_width + 40;\n\n    // enforce GBA bounds (480x320)\n    if(total_menu_height > 320)\n        total_menu_height = 320;\n\n    if(total_menu_width > 480)\n        total_menu_width = 480;\n\n    int menu_box_height = 200;\n    int menu_box_width = 380;\n\n    if(menu_box_height - total_menu_height < 18)\n        menu_box_height = total_menu_height + 18;\n\n    if(menu_box_width - total_menu_width < 32)\n        menu_box_width = total_menu_width + 32;\n\n    int menu_left_X = XRender::TargetW / 2 - total_menu_width / 2 + 20;\n    int menu_top_Y = XRender::TargetH / 2 - total_menu_height / 2;\n\n    // display room info above the pause menu\n#ifdef THEXTECH_ENABLE_SDL_NET\n    if(XMessage::GetStatus() == XMessage::Status::connected)\n    {\n        XRender::renderRect(XRender::TargetW / 2 - menu_box_width / 2 - 4, XRender::TargetH / 2 - menu_box_height / 2 - 40 - 4, menu_box_width + 8, 28 + 8, {0, 0, 0});\n        XRender::renderRect(XRender::TargetW / 2 - menu_box_width / 2 - 2, XRender::TargetH / 2 - menu_box_height / 2 - 40 - 2, menu_box_width + 4, 28 + 4, {255, 255, 255});\n        XRender::renderRect(XRender::TargetW / 2 - menu_box_width / 2, XRender::TargetH / 2 - menu_box_height / 2 - 40, menu_box_width, 28, {8, 96, 168});\n\n        SuperPrintScreenCenter(g_mainMenu.netplayRoomKey + ' ' + XMessage::DisplayRoom(XMessage::CurrentRoom()).room_name, 5, XRender::TargetH / 2 - menu_box_height / 2 - 36);\n    }\n#endif // #ifdef THEXTECH_ENABLE_SDL_NET\n\n    switch(s_pause_type)\n    {\n    case(PauseType::Legacy):\n        XRender::renderRect(XRender::TargetW / 2 - menu_box_width / 2, XRender::TargetH / 2 - menu_box_height / 2, menu_box_width, menu_box_height, {0, 0, 0});\n        break;\n    case(PauseType::Modern):\n    default:\n        XRender::renderRect(XRender::TargetW / 2 - menu_box_width / 2 - 4, XRender::TargetH / 2 - menu_box_height / 2 - 4, menu_box_width + 8, menu_box_height + 8, {0, 0, 0});\n        XRender::renderRect(XRender::TargetW / 2 - menu_box_width / 2 - 2, XRender::TargetH / 2 - menu_box_height / 2 - 2, menu_box_width + 4, menu_box_height + 4, {255, 255, 255});\n        XRender::renderRect(XRender::TargetW / 2 - menu_box_width / 2, XRender::TargetH / 2 - menu_box_height / 2, menu_box_width, menu_box_height, {0, 0, 0});\n        break;\n    case(PauseType::Testing):\n        XRender::renderRect(XRender::TargetW / 2 - menu_box_width / 2, XRender::TargetH / 2 - menu_box_height / 2, menu_box_width, menu_box_height, {0, 0, 0, 127});\n        break;\n    }\n\n    if(s_pause_type == PauseType::Testing)\n    {\n        for(size_t i = 0; i < s_items.size(); i++)\n        {\n            XTColor color = ((int)i == MenuCursor) ? XTColor() : XTColor(127, 127, 127);\n\n            SuperPrintScreenCenter(s_items[i].name, 5, menu_top_Y + (i * 36), color);\n        }\n    }\n    else\n    {\n        const int font = (s_pause_type == PauseType::Legacy) ? 3 : 4;\n\n        for(size_t i = 0; i < s_items.size(); i++)\n            SuperPrint(s_items[i].name, font, menu_left_X, menu_top_Y + (i * 36));\n\n        if(GFX.PCursor.inited)\n        {\n            if(s_pause_plr == 2 && s_pause_type != PauseType::Legacy)\n                XRender::renderTextureBasic(menu_left_X - 20, menu_top_Y + (MenuCursor * 36), GFX.PCursor, {0, 255, 0});\n            else if(s_pause_plr != 1 && s_pause_type != PauseType::Legacy)\n                XRender::renderTextureBasic(menu_left_X - 20, menu_top_Y + (MenuCursor * 36), GFX.PCursor);\n            else\n                XRender::renderTextureBasic(menu_left_X - 20, menu_top_Y + (MenuCursor * 36), GFX.PCursor, {255, 0, 0});\n        }\n        else if(s_pause_plr == 2 && s_pause_type != PauseType::Legacy)\n            XRender::renderTextureBasic(menu_left_X - 20, menu_top_Y + (MenuCursor * 36), 16, 16, GFX.MCursor[3], 0, 0);\n        else\n            XRender::renderTextureBasic(menu_left_X - 20, menu_top_Y + (MenuCursor * 36), 16, 16, GFX.MCursor[0], 0, 0);\n    }\n\n    if(XRender::TargetH > XRender::TargetH / 2 + menu_box_height / 2 + 16 + 96 + 8)\n        XHints::Draw(XRender::TargetH / 2 + menu_box_height / 2 + 16, 100, menu_box_width);\n}\n\nvoid ControlsLogic()\n{\n    int plr = s_pause_plr;\n    if(plr > numPlayers)\n        plr = 0;\n\n    if(!g_config.multiplayer_pause_controls && plr == 0)\n        plr = 1;\n\n    // there was previously code to copy all players' controls from the main player, but this is no longer necessary (and actively harmful in the SingleCoop case)\n\n    MenuControls_t menuControls = Controls::GetMenuControls(plr);\n\n    if(!MenuCursorCanMove_Back)\n    {\n        if(!menuControls.Back && MenuCursorCanMove)\n            MenuCursorCanMove_Back = true;\n\n        menuControls.Back = false;\n    }\n    else if(menuControls.Back)\n        MenuCursorCanMove_Back = false;\n\n    if(menuControls.Back && menuControls.Do)\n        menuControls.Do = false;\n\n    if(!MenuCursorCanMove)\n    {\n        if(!menuControls.Do && !menuControls.Back && !menuControls.Up && !menuControls.Down && (s_pause_type == PauseType::Legacy || (!menuControls.Left && !menuControls.Right)))\n            MenuCursorCanMove = true;\n\n        return;\n    }\n\n    if((s_pause_type == PauseType::Legacy || BattleMode) && s_cheat_menu_bits < 14)\n        s_cheat_menu_bits = 0;\n\n    int max_item = (int)s_items.size() - 1;\n\n    if(menuControls.Back)\n    {\n        if(MenuCursor != max_item)\n            PlaySound(SFX_Slide);\n\n        MenuCursor = max_item;\n\n        if(s_cheat_menu_bits < 14)\n            s_cheat_menu_bits = 0;\n\n        // fixes TheXTech 1.3.7-beta bug where hitting escape (bound to both Shared Back and P1 Do) would immediately exit\n        MenuCursorCanMove = false;\n    }\n    else if(menuControls.Up)\n    {\n        PlaySound(SFX_Slide);\n        MenuCursor = MenuCursor - 1;\n        MenuCursorCanMove = false;\n\n        if(s_cheat_menu_bits < 14)\n            s_cheat_menu_bits = 0;\n    }\n    else if(menuControls.Down)\n    {\n        PlaySound(SFX_Slide);\n        MenuCursor = MenuCursor + 1;\n        MenuCursorCanMove = false;\n\n        if(s_cheat_menu_bits < 14)\n            s_cheat_menu_bits = 0;\n    }\n    else if(menuControls.Left && s_pause_type != PauseType::Legacy)\n    {\n        if(s_cheat_menu_bits == 0 || s_cheat_menu_bits == 2 || s_cheat_menu_bits == 5 || s_cheat_menu_bits == 9)\n        {\n            s_cheat_menu_bits++;\n            MenuCursorCanMove = false;\n\n            // don't swap char\n            if(s_cheat_menu_bits >= 5)\n            {\n                PlaySound(SFX_Do);\n                return;\n            }\n        }\n        else if(s_cheat_menu_bits < 14)\n            s_cheat_menu_bits = 1;\n    }\n    else if(menuControls.Right && s_pause_type != PauseType::Legacy)\n    {\n        if(s_cheat_menu_bits != 0 && s_cheat_menu_bits != 2 && s_cheat_menu_bits != 5 && s_cheat_menu_bits != 9 && s_cheat_menu_bits < 14)\n        {\n            s_cheat_menu_bits++;\n            MenuCursorCanMove = false;\n\n            // do cheat screen\n            if(s_cheat_menu_bits == 14)\n            {\n                PlaySound(SFX_MedalGet);\n                s_CheatScreen();\n                return;\n            }\n\n            // don't swap char\n            if(s_cheat_menu_bits >= 5)\n            {\n                PlaySound(SFX_Do);\n                return;\n            }\n        }\n        else if(s_cheat_menu_bits < 14)\n            s_cheat_menu_bits = 0;\n    }\n    else if(menuControls.Do && s_cheat_menu_bits < 14)\n        s_cheat_menu_bits = 0;\n\n    if(MenuCursor < 0)\n        MenuCursor = max_item;\n    else if(MenuCursor > max_item)\n        MenuCursor = 0;\n\n    // special char change code\n    if(SwapCharAllowed())\n    {\n        for(int plr_i = 0; plr_i < l_screen->player_count; plr_i++)\n        {\n            int A = l_screen->players[plr_i];\n\n            if(!s_leftright_release[plr_i])\n            {\n                if(!Controls::g_RawControls[plr_i].Left && !Controls::g_RawControls[plr_i].Right)\n                    s_leftright_release[plr_i] = true;\n            }\n            else if(Controls::g_RawControls[plr_i].Left || Controls::g_RawControls[plr_i].Right)\n            {\n                s_leftright_release[plr_i] = false;\n                auto snd = SFX_BlockHit;\n\n                if(!g_ClonedPlayerMode && Player[A].Effect == PLREFF_NORMAL)\n                {\n                    // replaced old character swap code with this new code,\n                    // supporting arbitrary multiplayer and in-level swap.\n                    int target = Player[A].Character;\n\n                    // do the full wrap-around to find an acceptable target\n                    for(int i = 0; i < 5; i++)\n                    {\n                        // move the target in the direction requested by the player\n                        if(Controls::g_RawControls[plr_i].Left)\n                        {\n                            target --;\n\n                            if(target <= 0)\n                                target = 5;\n                        }\n                        else\n                        {\n                            target ++;\n\n                            if(target > 5)\n                                target = 1;\n                        }\n\n                        // immediately skip the target if it's blocked\n                        if(blockCharacter[target])\n                            continue;\n\n                        // also skip the target if it's another player's character\n                        bool already_used = false;\n\n                        for(int B = 1; B <= numPlayers; B++)\n                        {\n                            if(B == A)\n                                continue;\n\n                            if(target == Player[B].Character)\n                            {\n                                already_used = true;\n                                break;\n                            }\n                        }\n\n                        if(already_used)\n                            continue;\n\n                        // otherwise we are good and can keep the target\n                        break;\n                    }\n\n                    // if a suitable target found, swap character\n                    if(target != Player[A].Character)\n                    {\n                        snd = SFX_Slide;\n                        XMessage::PushMessage({XMessage::Type::char_swap, (uint8_t)plr_i, (uint8_t)target});\n                    }\n                }\n\n                PlaySound(snd);\n            }\n        }\n    }\n\n    if(menuControls.Do && MenuCursor >= 0 && MenuCursor < (int)s_items.size())\n    {\n        if(s_items[MenuCursor].is_private)\n            s_items[MenuCursor].callback();\n        else\n        {\n            XMessage::Message menu_action;\n            menu_action.type = XMessage::Type::menu_action;\n            menu_action.message = MenuCursor;\n            XMessage::PushMessage(menu_action);\n        }\n    }\n}\n\nbool Logic()\n{\n    if(s_force_exit)\n    {\n        s_force_exit = false;\n        return true;\n    }\n\n    bool stopPause = false;\n\n    if(g_pending_action < s_items.size() && !s_items[g_pending_action].is_private)\n        stopPause = s_items[g_pending_action].callback();\n\n    g_pending_action = 255;\n\n    return stopPause;\n}\n\n} // namespace PauseScreen\n"
  },
  {
    "path": "src/main/screen_pause.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SCREEN_PAUSE_H\n#define SCREEN_PAUSE_H\n\nnamespace PauseScreen\n{\n\nextern uint8_t g_pending_action;\n\nvoid UnlockCheats();\n\nvoid Init(int plr, bool LegacyPause);\n\nvoid ControlsLogic();\nbool Logic();\nvoid Render();\n\n} // namespace PauseScreen\n\n#endif // #ifndef SCREEN_PAUSE_H\n"
  },
  {
    "path": "src/main/screen_progress.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifdef __EMSCRIPTEN__\n#include <emscripten.h>\n#endif\n\n#include <cstdint>\n#include <string>\n\n#include <fmt_format_ne.h>\n\n#include \"sdl_proxy/sdl_timer.h\"\n\n#include \"gfx.h\"\n#include \"graphics.h\" // SuperPrint\n\n#include \"core/render.h\"\n#include \"core/events.h\"\n\n#include \"main/game_strings.h\"\n\nstatic const uint32_t c_progress_screen_min_elapsed = 1000;\nstatic const uint32_t c_progress_screen_tick_duration = 250;\n\nvoid IndicateProgress(uint32_t start_time, num_t progress, const std::string& message)\n{\n#ifdef USE_RENDER_BLOCKING\n    if(XRender::renderBlocked())\n        return;\n#endif\n\n    bool progress_valid = (uint16_t(1024 * progress) > 0);\n\n    if(progress > 1)\n        progress = 1;\n\n    uint32_t cur_time = SDL_GetTicks();\n    uint32_t elapsed = cur_time - start_time;\n    uint32_t total = progress_valid ? uint32_t(1024 * elapsed / uint16_t(1024 * progress)) : 0;\n\n    if(elapsed < c_progress_screen_min_elapsed && total < c_progress_screen_min_elapsed)\n        return;\n\n    int load_coins_new = (elapsed / c_progress_screen_tick_duration) % 4;\n\n    if(load_coins_new == LoadCoins)\n        return;\n\n    LoadCoins = load_coins_new;\n\n    XRender::setTargetTexture();\n    XRender::clearBuffer();\n\n    SuperPrintScreenCenter(message.c_str(), 4,\n               XRender::TargetH / 2 - 40,\n               {255, 255, 0, 127});\n\n    int time_y = XRender::TargetH / 2 - 20;\n\n    // outline\n    XRender::renderRect(XRender::TargetW / 4, time_y + 4, XRender::TargetW / 2, 32, {255, 255, 255});\n    // empty progress\n    XRender::renderRect(XRender::TargetW / 4 + 2, time_y + 6, XRender::TargetW / 2 - 4, 28, {0, 0, 0});\n    // progress fill\n    if(progress > 0)\n        XRender::renderRect(XRender::TargetW / 4 + 2, time_y + 6, (int)((XRender::TargetW / 2 - 4) * progress), 28, {127, 255, 127});\n    // push text down\n    time_y += 60;\n\n    std::string time_message = fmt::format_ne(g_gameStrings.formatMinutesSeconds, elapsed / 60000, (elapsed / 1000) % 60);\n    if(progress_valid)\n    {\n        time_message += \" / \";\n        time_message += fmt::format_ne(g_gameStrings.formatMinutesSeconds, total / 60000, (total / 1000) % 60);\n    }\n\n    SuperPrintScreenCenter(time_message.c_str(), 4,\n               time_y,\n               {255, 255, 255, 127});\n\n    XRender::renderTextureBasic(XRender::TargetW / 2 - GFX.LoadCoin.w / 2, time_y + 40, GFX.LoadCoin.w, GFX.LoadCoin.h / 4, GFX.LoadCoin, 0, 32 * LoadCoins);\n\n    XRender::repaint();\n    XRender::setTargetScreen();\n    XEvents::doEvents();\n\n#ifdef __EMSCRIPTEN__\n    emscripten_sleep(1); // To repaint screenn, it's required to send a sleep signal\n#endif\n}\n"
  },
  {
    "path": "src/main/screen_progress.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef SCREEN_PROGRESS_H\n#define SCREEN_PROGRESS_H\n\n#include <cstdint>\n#include <string>\n\n#include \"numeric_types.h\"\n\n/**\n * \\brief Updates a progress screen for a long main thread operation\n *\n * \\param start_time: start time of the operation based on SDL_GetTicks() (required)\n * \\param progress: estimated progress ratio between 0 and 1 (set to negative value if no estimate possible)\n * \\param message:\n *\n * Will perform an events update and other actions required during main loop\n **/\nvoid IndicateProgress(uint32_t start_time, num_t progress, const std::string& message);\n\n#endif // SCREEN_PROGRESS_H\n"
  },
  {
    "path": "src/main/screen_prompt.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"config.h\"\n#include \"gfx.h\"\n#include \"sound.h\"\n#include \"graphics.h\"\n#include \"npc_id.h\"\n#include \"controls.h\"\n#include \"screen_fader.h\"\n#include \"game_main.h\"\n\n#include \"main/game_globals.h\"\n\n#include <Logger/logger.h>\n\n#include \"core/render.h\"\n#include \"main/screen_prompt.h\"\n#include \"main/screen_quickreconnect.h\"\n#include \"main/speedrunner.h\"\n\nnamespace PromptScreen\n{\n\nstatic int8_t s_cur_item = 0;\nstatic std::vector<std::string> s_options;\nstatic int s_longest_width = 0;\n\nint Run(const std::string& prompt, std::vector<std::string>&& options)\n{\n    // initialize text prompt\n    MessageText = prompt;\n    PrepareMessageDims();\n\n    // initialize options\n    s_options = options;\n\n    // perform the pause loop\n    PauseGame(PauseCode::Prompt, 0);\n    MenuCursorCanMove = false;\n    MenuMouseRelease = false;\n    MouseRelease = false;\n    ScrollRelease = false;\n\n    for(int i = 1; i <= numPlayers; i++)\n    {\n        Player[i].UnStart = false;\n        Player[i].CanJump = false;\n        Player[i].CanAltJump = false;\n    }\n\n    return s_cur_item;\n}\n\nvoid Init()\n{\n    PlayMusic(\"wmusic12\", 0);\n    PlaySoundMenu(SFX_Message);\n    s_cur_item = 0;\n    MenuCursorCanMove = false;\n\n    // set the longest width\n    s_longest_width = 0;\n\n    for(const std::string& label : s_options)\n    {\n        int item_width = SuperTextPixLen(label, 3);\n        if(item_width > s_longest_width)\n            s_longest_width = item_width;\n    }\n\n    int total_menu_height = (int)s_options.size() * 36 - 18;\n    int total_menu_width = s_longest_width + 40;\n\n    // GBA bounds\n    if(total_menu_height > 200 || total_menu_width > 480)\n        pLogDebug(\"Prompt options don't fit within bounds (actual size %dx%d, bounds 480x200)\", total_menu_width, total_menu_height);\n\n    // setup fader\n    if(g_config.EnableInterLevelFade)\n        g_levelScreenFader.setupFader(3, 65, 0, ScreenFader::S_CIRCLE, false, XRender::TargetW / 2, XRender::TargetH / 2, 0);\n}\n\nvoid Render()\n{\n    XRender::setTargetTexture();\n\n    // reset screen\n    XRender::clearBuffer();\n\n    // display background\n    vScreen[1].X = 0;\n    vScreen[1].Y = 0;\n    vScreen[1].Left = 0; vScreen[1].Top = 0; vScreen[1].Width = XRender::TargetW; vScreen[1].Height = XRender::TargetH;\n    LevelREAL[1] = IntegerLocation_t(0, 0, XRender::TargetW, XRender::TargetH);\n    level[1] = static_cast<SpeedlessLocation_t>(LevelREAL[1]);\n    Background2[1] = 1;\n    DrawBackground(1, 1);\n\n    // draw message box\n    DrawMessage();\n\n    // (from screen_pause.cpp)\n    // draw menu box\n\n    // height includes intermediate padding but no top/bottom padding\n    // width includes cursor on left and 20px padding on right for symmetry\n    int total_menu_height = (int)s_options.size() * 36 - 18;\n    int total_menu_width = s_longest_width + 40;\n\n    // enforce GBA bounds (480x320)\n    if(total_menu_height > 320)\n        total_menu_height = 320;\n\n    if(total_menu_width > 480)\n        total_menu_width = 480;\n\n    int menu_box_height = total_menu_height + 32;\n\n    int menu_box_width = 160;\n    if(menu_box_width - total_menu_width < 32)\n        menu_box_width = total_menu_width + 32;\n\n    int menu_box_Y = XRender::TargetH - 24 - menu_box_height;\n\n    int menu_left_X = XRender::TargetW / 2 - total_menu_width / 2 + 20;\n    int menu_top_Y = menu_box_Y + menu_box_height / 2 - total_menu_height / 2;\n\n    XRender::renderRect(XRender::TargetW / 2 - menu_box_width / 2, menu_box_Y, menu_box_width, menu_box_height, XTColorF(0.0_n, 0.0_n, 0.0_n, 0.8_n));\n\n    for(size_t i = 0; i < s_options.size(); i++)\n        SuperPrint(s_options[i], 3, menu_left_X, menu_top_Y + (i * 36));\n\n    if(GFX.PCursor.inited)\n        XRender::renderTextureBasic(menu_left_X - 20, menu_top_Y + (s_cur_item * 36), GFX.PCursor);\n    else\n        XRender::renderTextureFL(menu_left_X - 20, menu_top_Y + (s_cur_item * 36), GFX.MCursor[1].w, GFX.MCursor[1].h, GFX.MCursor[1], 0, 0, 90, nullptr, X_FLIP_NONE);\n\n    for(int plr_i = 0; plr_i < l_screen->player_count; plr_i++)\n        speedRun_renderControls(plr_i, -1);\n\n    // draw screen fader and repaint\n\n    g_levelScreenFader.draw();\n\n    DrawDeviceBattery();\n\n    XRender::repaint();\n\n    if(TakeScreen)\n        ScreenShot();\n}\n\nbool Logic()\n{\n    MenuControls_t menuControls = Controls::GetMenuControls();\n\n    if(!menuControls.Up && !menuControls.Down && !menuControls.Do)\n        MenuCursorCanMove = true;\n\n    if(!MenuCursorCanMove)\n        return false;\n\n    if(menuControls.Up)\n    {\n        PlaySoundMenu(SFX_Slide);\n        s_cur_item--;\n        if(s_cur_item < 0)\n            s_cur_item = (int8_t)s_options.size() - 1;\n        MenuCursorCanMove = false;\n        return false;\n    }\n\n    if(menuControls.Down)\n    {\n        PlaySoundMenu(SFX_Slide);\n        s_cur_item++;\n        if(s_cur_item >= (int)s_options.size())\n            s_cur_item = 0;\n        MenuCursorCanMove = false;\n        return false;\n    }\n\n    if(menuControls.Do && s_cur_item >= 0 && s_cur_item <= 3)\n    {\n        PlaySoundMenu(SFX_Do);\n\n        if(g_config.EnableInterLevelFade)\n            g_levelScreenFader.setupFader(3, 0, 65, ScreenFader::S_CIRCLE, false, XRender::TargetW / 2, XRender::TargetH / 2, 0);\n        else\n            g_levelScreenFader.setupFader(65, 0, 65, ScreenFader::S_CIRCLE, false, XRender::TargetW / 2, XRender::TargetH / 2, 0);\n\n        editorWaitForFade();\n        return true;\n    }\n\n    g_levelScreenFader.update();\n\n    return false;\n}\n\n} // namespace PromptScreen\n"
  },
  {
    "path": "src/main/screen_prompt.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef SCREEN_PROMPT_H\n\n#define SCREEN_PROMPT_H\n\n#include <string>\n#include <vector>\n\nnamespace PromptScreen\n{\n\nint Run(const std::string& prompt, std::vector<std::string>&& options);\n\nvoid Init();\n\nvoid Render();\n\n// if returns true, user has exited the screen\nbool Logic();\n\n} // namespace PromptScreen\n\n#endif // SCREEN_PROMPT_H\n"
  },
  {
    "path": "src/main/screen_quickreconnect.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <fmt_format_ne.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"controls.h\"\n#include \"globals.h\"\n#include \"graphics.h\"\n#include \"game_main.h\"\n#include \"message.h\"\n\n#include \"main/menu_main.h\"\n#include \"main/game_strings.h\"\n\n#include \"main/screen_quickreconnect.h\"\n\nnamespace QuickReconnectScreen\n{\n\nbool g_active;\nint g_toast_duration[maxLocalPlayers] = {0};\n\nvoid Deactivate()\n{\n    g_active = false;\n\n    for(int i = 0; i < maxLocalPlayers; ++i)\n        g_toast_duration[i] = 0;\n}\n\nvoid Logic()\n{\n    if(GameMenu || LevelEditor || GameOutro)\n    {\n        Deactivate();\n        return;\n    }\n\n    if(XMessage::GetStatus() == XMessage::Status::replay)\n        return;\n\n    bool has_missing = false;\n    bool has_toast = false;\n    bool was_missing[maxLocalPlayers] = {false};\n\n    for(int i = 0; i < maxLocalPlayers; i++)\n    {\n        if(i >= l_screen->player_count)\n            continue;\n\n        if(i >= (int)Controls::g_InputMethods.size() || !Controls::g_InputMethods[i])\n        {\n            has_missing = true;\n            was_missing[i] = true;\n        }\n        else if(g_toast_duration[i])\n        {\n            g_toast_duration[i] --;\n            has_toast = true;\n        }\n    }\n\n    if(has_missing && GamePaused != PauseCode::DropAdd)\n    {\n        auto found = Controls::PollInputMethod();\n        if(found)\n            found->used_for_player = true;\n\n        // add toasts for new players\n        for(int i = 0; i < maxLocalPlayers; i++)\n        {\n            if(was_missing[i] && i < (int)Controls::g_InputMethods.size() && Controls::g_InputMethods[i])\n                g_toast_duration[i] = MAX_TOAST_DURATION;\n        }\n    }\n\n    if(!has_missing && !has_toast)\n        g_active = false;\n}\n\n} // namespace QuickReconnectScreen\n"
  },
  {
    "path": "src/main/screen_quickreconnect.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SCREEN_QUICKRECONNECT_H\n#define SCREEN_QUICKRECONNECT_H\n\n#include \"global_constants.h\"\n\nnamespace QuickReconnectScreen\n{\n\nconstexpr int MAX_TOAST_DURATION = 66 * 3; // 3 seconds\n\nextern bool g_active;\nextern int g_toast_duration[maxLocalPlayers];\n\nvoid Deactivate();\nvoid Logic();\n\n} // namespace QuickReconnectScreen\n\n#endif // #ifndef SCREEN_QUICKRECONNECT_H\n"
  },
  {
    "path": "src/main/screen_textentry.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"../globals.h\"\n#include \"../graphics.h\"\n#include \"../config.h\"\n#include \"core/render.h\"\n#include \"../game_main.h\"\n#include \"controls.h\"\n#include \"gfx.h\"\n\n#include \"fontman/font_manager_private.h\"\n#include \"fontman/font_manager.h\"\n\n#ifdef __ANDROID__\n\n#   include <SDL2/SDL_system.h>\n#   include <jni.h>\n\nstatic void s_textEntry_callDialog()\n{\n    JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();\n    jobject activity = (jobject)SDL_AndroidGetActivity();\n    jclass clazz = env->GetObjectClass(activity);\n    jmethodID method = env->GetMethodID(clazz, \"requestText\", \"()V\");\n    env->CallVoidMethod(activity, method);\n    env->DeleteLocalRef(activity);\n    env->DeleteLocalRef(clazz);\n}\n\n#endif // #ifdef __ANDROID__\n\n#ifdef __3DS__\n\n#include <3ds.h>\n\n#include \"sound.h\"\n\n// to check if on second screen\n#include \"editor/new_editor.h\"\n\nnamespace XRender\n{\n    extern bool g_in_frame;\n};\n\nstatic const std::string s_GetTextInput(const std::string& prompt, const std::string& init = \"\")\n{\n    static SwkbdState keystate;\n    static char input_buffer[240];\n\n    if(XRender::g_in_frame)\n        C3D_FrameEnd(0);\n    SoundPauseAll();\n\n    swkbdInit(&keystate, SWKBD_TYPE_QWERTY, 1, 120);\n    swkbdSetHintText(&keystate, prompt.c_str());\n    swkbdSetInitialText(&keystate, init.c_str());\n    swkbdSetFeatures(&keystate, SWKBD_DARKEN_TOP_SCREEN);\n\n    swkbdInputText(&keystate, input_buffer, sizeof(input_buffer));\n\n    if(XRender::g_in_frame)\n        C3D_FrameBegin(0);\n    SoundResumeAll();\n\n    return std::string(input_buffer);\n}\n\n#endif // #ifdef __3DS__\n\nnamespace TextEntryScreen\n{\n\nstd::string Text;\nstatic std::vector<int16_t> s_Prompt_UTF_offsets;\nstatic std::vector<int16_t> s_Text_UTF_offsets;\n\nenum class MouseState : uint8_t\n{\n    down = 0,\n    just_released = 1,\n    up = 2,\n    canceled = 3,\n    canceled_2 = 4, // sometimes takes 2 frames for residual touchscreen press to become active\n};\n\nstatic std::string s_Prompt;\nstatic int s_cursor = 0;\nstatic int s_timer = 0;\nstatic bool s_committed = false;\nstatic MouseState s_mouse_state = MouseState::canceled;\n\n// KEYMAP!\n// we are assumed to be operating on a Nx5x12 (levels, rows, columns) grid.\n// special characters: \\b is backspace, \\x0e and \\x0f are shift up and shift down (a grid level)\n// \\n is a newline (actually, accept), and \\x1d and \\x1c are left and right.\n// special control character meanings: \\x11 is \"last button wider\" and \\x12 is \"empty\"\n\nstatic const char* s_keymap_EN = \"1234567890-\\b\"\n    \"qwertyuiop[]\"\n    \"asdfghjkl;'=\"\n    \"`zxcvbnm,./\\\\\"\n    \"\\x0e\\x11\\x11 \\x11\\x11\\x11\\x11\\x1d\\x1c\\n\\x11\"\n    \"!@#$%^&*()_\\b\"\n    \"QWERTYUIOP{}\"\n    \"ASDFGHJKL:\\\"+\"\n    \"~ZXCVBNM<>?|\"\n    \"\\x0f\\x11\\x11 \\x11\\x11\\x11\\x11\\x1d\\x1c\\n\\x11\";\n\n#if 0 // FIXME: Decide, what to do with these maps?\nstatic const char* s_keymap_RU = \"1234567890-\\b\"\n    \"йцукенгшщзхъ\"\n    \"фывапролджэ:\"\n    \"ячсмитьбюё,.\"\n    \"\\x0e\\x11\\x11 \\x11\\x11\\x11\\x11\\x1d\\x1c\\n\\x11\"\n    \"!\\\"№;%:?*()_\\b\"\n    \"ЙЦУКЕНГШЩЗХЪ\"\n    \"ФЫВАПРОЛДЖЭ+\"\n    \"ЯЧСМИТЬБЮЁ«»\"\n    \"\\x0f\\x11\\x11 \\x11\\x11\\x11\\x11\\x1d\\x1c\\n\\x11\";\n\nstatic const char* s_keymap_JP =\n    \"あかさたなはまやらわ\\b\"\n    \"いきしちにひみ　りゃん\"\n    \"うくすつぬふむゆるょ！\"\n    \"えけせてねへめ　れゅ？\"\n    \"おこそとのほもよろを。\"\n    \"\\x0e\\x11\\x11　\\x11\\x11\\x11\\x1d\\x1c\\n\\x11\"\n    \"\\x12がざだ\\x12ばぱ１６「\\b\"\n    \"\\x12ぎじぢ\\x12びぴ２７」～\"\n    \"\\x12ぐずづ\\x12ぶぷ３８’、\"\n    \"\\x12げぜで\\x12べぺ４９”・\"\n    \"\\x12ごぞど\\x12ぼぽ５０＋ー\"\n    \"\\x0e\\x11\\x11　\\x11\\x11\\x11\\x1d\\x1c\\n\\x11\"\n    \"アカサタナハマヤラワ\\b\"\n    \"イキシチニヒミ　リャン\"\n    \"ウクスツヌフムユルョ！\"\n    \"エケセテネヘメ　レュ？\"\n    \"オコソトノホモヨロウ。\"\n    \"\\x0e\\x11\\x11　\\x11\\x11\\x11\\x1d\\x1c\\n\\x11\"\n    \"\\x12ガザダ\\x12バパ１６「\\b\"\n    \"\\x12ギジヂ\\x12ビピ２７」～\"\n    \"\\x12グズヅ\\x12ブプ３８’、\"\n    \"\\x12ゲゼデ\\x12ベペ４９”・\"\n    \"\\x12ゴゾド\\x12ボポ５０＋ー\"\n    \"\\x0e\\x11\\x11　\\x11\\x11\\x11\\x1d\\x1c\\n\\x11\";\n#endif\n\n// used to support UTF-8 characters\nstatic std::vector<int16_t> s_current_keymap_UTF_offsets;\n\nstatic const char* s_current_keymap = s_keymap_EN;\n// static const char* s_current_keymap = s_keymap_RU;\nstatic int s_current_keymap_rows = 5;\nstatic int s_current_keymap_cols = 12;\nstatic int s_current_keymap_levels = 3;\n\n// static const char* s_current_keymap = s_keymap_JP;\n// static int s_current_keymap_rows = 6;\n// static int s_current_keymap_cols = 11;\n// static int s_current_keymap_levels = 4;\n\n// selected button location\nstatic int s_cur_level = 0;\nstatic int s_cur_row = 0;\nstatic int s_cur_col = 0;\nstatic bool s_render_sel = false;\n\ninline void find_utf_offsets(const char* str, std::vector<int16_t>& out)\n{\n    out.clear();\n\n    int16_t offset;\n\n    for(offset = 0; str[offset] != '\\0' && offset >= 0; offset++)\n    {\n        out.push_back(offset);\n        UTF8 ucx = static_cast<unsigned char>(str[offset]);\n        offset += static_cast<size_t>(trailingBytesForUTF8[ucx]);\n    }\n\n    out.push_back(offset);\n}\n\ninline const char* get_char(int cLevel, int row, int col)\n{\n    return s_current_keymap + s_current_keymap_UTF_offsets[(cLevel * s_current_keymap_rows * s_current_keymap_cols) + (row * s_current_keymap_cols) + col];\n}\n\ninline const char* get_char()\n{\n    return get_char(s_cur_level, s_cur_row, s_cur_col);\n}\n\nbool UpdateButton(int x, int y, int size, const char* c, bool sel, bool render)\n{\n    if(*c == '\\x12' || *c == '\\x11') // empty space or someone else's continuation\n        return false;\n\n    const char* next_char = c+1;\n    char print_char[6];\n\n    if(*c == '\\b')\n    {\n        print_char[0] = 'B'; print_char[1] = 'S'; print_char[2] = '\\0';\n    }\n    else if(*c == '\\x0e' || *c == '\\x0f')\n    {\n        print_char[0] = 's'; print_char[1] = 'h'; print_char[2] = 'i'; print_char[3] = 'f'; print_char[4] = 't'; print_char[5] = '\\0';\n    }\n    else if(*c == '\\n')\n    {\n        print_char[0] = 'o'; print_char[1] = 'k'; print_char[2] = 'a'; print_char[3] = 'y'; print_char[4] = '\\0';\n    }\n    else if(*c == ' ')\n    {\n        print_char[0] = 's'; print_char[1] = 'p'; print_char[2] = 'a'; print_char[3] = 'c'; print_char[4] = 'e'; print_char[5] = '\\0';\n    }\n    else if(*c == '\\x1d')\n    {\n        print_char[0] = '<'; print_char[1] = '-'; print_char[2] = '\\0';\n    }\n    else if(*c == '\\x1c')\n    {\n        print_char[0] = '-'; print_char[1] = '>'; print_char[2] = '\\0';\n    }\n    else if(c[0] & 1<<7)\n    {\n        print_char[0] = c[0];\n        print_char[1] = c[1];\n        next_char ++;\n        if(c[0] & 1<<5)\n        {\n            print_char[2] = c[2];\n            next_char ++;\n            if(c[0] & 1<<4)\n            {\n                print_char[3] = c[3];\n                print_char[4] = '\\0';\n                next_char ++;\n            }\n            else\n                print_char[3] = '\\0';\n        }\n        else\n            print_char[2] = '\\0';\n    }\n    else\n    {\n        print_char[0] = c[0];\n        print_char[1] = '\\0';\n    }\n\n    int width = size;\n\n    while(*next_char == '\\x11') // wider\n    {\n        width += size;\n        next_char ++;\n    }\n\n    // the button is 36x36 and outlined by a 2 pixel box\n    bool coll = false;\n    if(SharedCursor.X >= x + 2 && SharedCursor.X < x + width - 2\n        && SharedCursor.Y >= y + 2 && SharedCursor.Y < y + size - 2)\n        coll = true;\n\n    // outline:\n    if(render)\n    {\n        if(sel)\n        {\n            if(coll && s_mouse_state == MouseState::down)\n                XRender::renderRect(x, y, width, size, XTColorF(0.0_n, 0.5_n, 0.5_n, 1.0_n), true);\n            else\n                XRender::renderRect(x, y, width, size, XTColorF(0.0_n, 1.0_n, 1.0_n, 1.0_n), true);\n        }\n        else if(coll && s_mouse_state == MouseState::down)\n            XRender::renderRect(x, y, width, size, XTColorF(0.0_n, 0.0_n, 0.0_n, 1.0_n), true);\n\n        // background:\n        if(s_mouse_state == MouseState::down && coll)\n            XRender::renderRect(x+2, y+2, width-4, size-4, XTColorF(0.2_n, 0.2_n, 0.2_n, 1.0_n), true);\n        else\n            XRender::renderRect(x+2, y+2, width-4, size-4, XTColorF(0.5_n, 0.5_n, 0.6_n, 1.0_n), true);\n\n        SuperPrintCenter(print_char, 4, x+width/2, y+size/2-10);\n    }\n\n    return (s_mouse_state == MouseState::just_released && coll);\n}\n\nvoid GoLeft()\n{\n    int break_col = s_cur_col;\n    if(s_cur_col == 0)\n        s_cur_col = s_current_keymap_cols - 1;\n    else\n        s_cur_col --;\n\n    while(*get_char() == '\\x11' || *get_char() == '\\x12')\n    {\n        if(s_cur_col == 0)\n            s_cur_col = s_current_keymap_cols - 1;\n        else\n            s_cur_col --;\n        if(s_cur_col == break_col)\n            break;\n    }\n}\n\nvoid GoRight()\n{\n    int break_col = s_cur_col;\n\n    if(s_cur_col == s_current_keymap_cols - 1)\n        s_cur_col = 0;\n    else\n        s_cur_col ++;\n\n    while(*get_char() == '\\x11' || *get_char() == '\\x12')\n    {\n        if(s_cur_col == s_current_keymap_cols - 1)\n            s_cur_col = 0;\n        else\n            s_cur_col ++;\n        if(s_cur_col == break_col)\n            break;\n    }\n}\n\nvoid GoDown()\n{\n    int break_row = s_cur_row;\n\n    if(s_cur_row == s_current_keymap_rows - 1)\n        s_cur_row = 0;\n    else\n        s_cur_row ++;\n\n    while(*get_char() == '\\x12')\n    {\n        if(s_cur_row == s_current_keymap_rows - 1)\n            s_cur_row = 0;\n        else\n            s_cur_row ++;\n        if(s_cur_row == break_row)\n            break;\n    }\n\n    while(*get_char() == '\\x11')\n        s_cur_col --;\n}\n\nvoid GoUp()\n{\n    int break_row = s_cur_row;\n\n    if(s_cur_row == 0)\n        s_cur_row = s_current_keymap_rows - 1;\n    else\n        s_cur_row --;\n\n    while(*get_char() == '\\x12')\n    {\n        if(s_cur_row == 0)\n            s_cur_row = s_current_keymap_rows - 1;\n        else\n            s_cur_row --;\n        if(s_cur_row == break_row)\n            break;\n    }\n\n    while(*get_char() == '\\x11')\n        s_cur_col --;\n}\n\nvoid Insert(const char* c, int size)\n{\n    Text.insert(s_Text_UTF_offsets[s_cursor], c, size);\n\n    int16_t new_pos = s_Text_UTF_offsets[s_cursor] + size;\n\n    find_utf_offsets(Text.c_str(), s_Text_UTF_offsets);\n\n    while(s_cursor + 1 < (int16_t)s_Text_UTF_offsets.size() && s_Text_UTF_offsets[s_cursor] < new_pos)\n        s_cursor ++;\n}\n\nvoid Insert(const char* c)\n{\n    // don't allow keyboard insertions until event loop has properly started\n    // (this is the input repetition timer, which is initialized first time\n    // no input is pressed.)\n    if(s_timer < 0)\n        return;\n\n    Insert(c, (int)SDL_strlen(c));\n}\n\ninline void InsertUnicodeChar(const char* c)\n{\n    UTF8 ucx = static_cast<unsigned char>(*c);\n    Insert(c, 1 + trailingBytesForUTF8[ucx]);\n}\n\nvoid CursorLeft()\n{\n    if(s_cursor > 0)\n        s_cursor --;\n}\n\nvoid CursorRight()\n{\n    if(s_cursor + 1 < (int)s_Text_UTF_offsets.size())\n        s_cursor ++;\n}\n\nvoid Backspace()\n{\n    int old_cursor = s_cursor;\n\n    CursorLeft();\n\n    Text.erase(Text.begin() + s_Text_UTF_offsets[s_cursor], Text.begin() + s_Text_UTF_offsets[old_cursor]);\n\n    find_utf_offsets(Text.c_str(), s_Text_UTF_offsets);\n}\n\nvoid Commit()\n{\n    s_committed = true;\n}\n\n// returns true if the string is complete\nbool DoAction()\n{\n    const char* c = get_char(s_cur_level, s_cur_row, s_cur_col);\n\n    switch(*c)\n    {\n    // invalid chars\n    case '\\x11':\n    case '\\x12':\n        break;\n    // accept input\n    case '\\n':\n        return true;\n    // left\n    case '\\x1d':\n        CursorLeft();\n        break;\n    // right\n    case '\\x1c':\n        CursorRight();\n        break;\n    // shift up\n    case '\\x0e':\n        s_cur_level ++;\n        if(s_cur_level == s_current_keymap_levels)\n            s_cur_level = 0;\n        break;\n    // shift down\n    case '\\x0f':\n        if(s_cur_level == 0)\n            s_cur_level = s_current_keymap_levels;\n        s_cur_level --;\n        break;\n    // backspace\n    case '\\b':\n        Backspace();\n        break;\n    // proper text entry\n    default:\n        InsertUnicodeChar(c);\n    }\n    return false;\n}\n\nbool KeyboardMouseRender(bool mouse, bool render)\n{\n#ifdef __3DS__\n    int cur_ScreenW = (LevelEditor && editorScreen.active) ? 640 : XRender::TargetW;\n    int cur_ScreenH = (LevelEditor && editorScreen.active) ? 480 : XRender::TargetH;\n#else\n    const int cur_ScreenW = XRender::TargetW;\n    const int cur_ScreenH = XRender::TargetH;\n#endif\n\n#if defined(__WII__) || defined(__3DS__) || defined(__16M__)\n    constexpr bool c_always_fill_screen = true;\n#else\n    constexpr bool c_always_fill_screen = false;\n#endif\n\n    int key_size = 40;\n    // make OSK fill screen on certain platforms / when touchscreen is in use\n    if(c_always_fill_screen || Controls::g_renderTouchscreen)\n    {\n        key_size = (cur_ScreenW - 40) / s_current_keymap_cols;\n        // force even\n        key_size &= ~1;\n    }\n\n    int kb_height = s_current_keymap_rows*key_size;\n    int kb_width = s_current_keymap_cols*key_size;\n\n    int win_width = kb_width + 20;\n    int win_height = kb_height + 20;\n\n    int n_text_chars = (s_current_keymap_cols * key_size - 20) / 18 - 1;\n    int n_prompt_chars = n_text_chars + 2;\n\n    int n_prompt_lines = (((int)s_Prompt_UTF_offsets.size() - 1 + n_prompt_chars - 1) / n_prompt_chars);\n    int n_text_lines = (((int)s_Text_UTF_offsets.size() - 1 + n_text_chars - 1) / n_text_chars);\n    if(n_text_lines == 0)\n        n_text_lines = 1;\n\n    win_height += n_prompt_lines * 20;\n    win_height += n_text_lines * 20;\n\n    int win_x = cur_ScreenW / 2 - win_width / 2;\n    int kb_x = win_x + 10;\n\n    // bias towards bottom of screen\n    int win_y = (cur_ScreenH - win_height) * 4 / 5;\n    // force even\n    win_y &= ~1;\n    int kb_y = win_y + 10 + n_prompt_lines * 20 + n_text_lines * 20;\n\n    if(render)\n    {\n        XRender::renderRect(win_x, win_y, win_width, win_height, XTColorF(0.6_n, 0.6_n, 1.0_n, 0.8_n));\n        for(int i = 0; i < n_prompt_lines; i ++)\n        {\n            if(n_prompt_chars * (i + 1) < (int)s_Prompt_UTF_offsets.size())\n                SuperPrint(s_Prompt_UTF_offsets[n_prompt_chars * (i + 1)] - s_Prompt_UTF_offsets[n_prompt_chars * i], s_Prompt.c_str() + s_Prompt_UTF_offsets[n_prompt_chars * i], 4, win_x + 10, win_y + 6 + 20*i);\n            else\n                SuperPrint(s_Prompt.c_str() + s_Prompt_UTF_offsets[n_prompt_chars * i], 4, win_x + 10, win_y + 6 + 20*i);\n        }\n\n        XRender::renderRect(win_x + 20, win_y + n_prompt_lines * 20 + 4, win_width - 40, n_text_lines * 20, XTColorF(0.06_n, 0.06_n, 0.1_n, 1.0_n));\n        for(int i = 0; i < n_text_lines; i ++)\n        {\n            if(n_text_chars * (i + 1) < (int)s_Text_UTF_offsets.size())\n                SuperPrint(s_Text_UTF_offsets[n_text_chars * (i + 1)] - s_Text_UTF_offsets[n_text_chars * i], Text.c_str() + s_Text_UTF_offsets[n_text_chars * i], 4, win_x + 10 + 16, win_y + 6 + 20 * (n_prompt_lines + i));\n            else\n                SuperPrint(Text.c_str() + s_Text_UTF_offsets[n_text_chars * i], 4, win_x + 10 + 16, win_y + 6 + 20 * (n_prompt_lines + i));\n\n            // render cursor if it is on this line\n            if(CommonFrame % 64 < 32 && ((s_cursor >= i * n_text_chars && s_cursor < (i + 1) * n_text_chars) || (s_cursor == (i + 1) * n_text_chars && s_cursor == (int)s_Text_UTF_offsets.size() - 1)))\n            {\n                int cursor_offset = SuperTextPixLen(s_Text_UTF_offsets[s_cursor] - s_Text_UTF_offsets[n_text_chars * i], Text.c_str() + s_Text_UTF_offsets[n_text_chars * i], 4);\n                XRender::renderRect(win_x + 10 + 16 + cursor_offset - 2, win_y + 4 + 20 * (n_prompt_lines + i), 2, 20, XTColorF(0.7_n, 0.7_n, 0.7_n, 1.0_n));\n            }\n        }\n\n        XRender::renderRect(kb_x, kb_y, kb_width, kb_height, XTColorF(0.45_n, 0.45_n, 0.75_n, 1.0_n));\n    }\n\n    for(int row = 0; row < s_current_keymap_rows; row ++)\n    {\n        for(int col = 0; col < s_current_keymap_cols; col ++)\n        {\n            bool sel = false;\n            if(s_render_sel && s_cur_row == row && s_cur_col == col)\n                sel = true;\n\n            if(UpdateButton(key_size*col + kb_x, key_size*row + kb_y, key_size, get_char(s_cur_level, row, col), sel, render) && mouse)\n            {\n                s_render_sel = false;\n                s_cur_row = row;\n                s_cur_col = col;\n                if(DoAction())\n                    return true;\n            }\n        }\n    }\n\n    if(render && !Controls::g_renderTouchscreen)\n        XRender::renderTextureBasic((int)SharedCursor.X, (int)SharedCursor.Y, GFX.ECursor[2]);\n\n    return false;\n}\n\nbool Init(const std::string& Prompt, void (*callback)(), const std::string Value)\n{\n#ifdef __ANDROID__\n    if(g_config.use_native_osk)\n    {\n        s_textEntry_callDialog();\n\n        if(callback)\n            callback();\n\n        return false;\n    }\n#endif\n\n#ifdef __3DS__\n    if(g_config.use_native_osk)\n    {\n        Text = s_GetTextInput(Prompt, Value);\n\n        if(callback)\n            callback();\n\n        return false;\n    }\n#endif\n\n    find_utf_offsets(s_current_keymap, s_current_keymap_UTF_offsets);\n\n    s_Prompt = Prompt;\n    Text = Value;\n    find_utf_offsets(Text.c_str(), s_Text_UTF_offsets);\n    find_utf_offsets(s_Prompt.c_str(), s_Prompt_UTF_offsets);\n    s_cursor = (int)s_Text_UTF_offsets.size() - 1;\n    s_mouse_state = MouseState::canceled;\n    s_cur_level = 0;\n    s_cur_row = 0;\n    s_cur_col = 0;\n    MenuCursorCanMove = false;\n    s_render_sel = false;\n    s_timer = -1;\n    s_committed = false;\n    PauseInit(PauseCode::TextEntry, 0, callback);\n\n    return true;\n}\n\nconst std::string& Run(const std::string& Prompt, const std::string Value)\n{\n    bool using_pause_loop = Init(Prompt, nullptr, Value);\n\n    // wait for pause loop to finish\n    if(using_pause_loop)\n        PauseGame(PauseCode::None);\n\n    return Text;\n}\n\nvoid Render()\n{\n    KeyboardMouseRender(false, true);\n}\n\nstatic void s_finalize()\n{\n    MenuCursorCanMove = false;\n    MenuMouseRelease = false;\n    MouseRelease = false;\n    ScrollRelease = false;\n\n    for(int i = 1; i <= numPlayers; i++)\n    {\n        Player[i].UnStart = false;\n        Player[i].CanJump = false;\n        Player[i].CanAltJump = false;\n    }\n}\n\nbool Logic()\n{\n    if(s_mouse_state == MouseState::canceled)\n    {\n        if(!SharedCursor.Primary)\n            s_mouse_state = MouseState::canceled_2;\n    }\n    else if(s_mouse_state == MouseState::canceled_2)\n    {\n        if(!SharedCursor.Primary)\n            s_mouse_state = MouseState::up;\n    }\n    else if(SharedCursor.Primary)\n        s_mouse_state = MouseState::down;\n    else if(s_mouse_state == MouseState::down)\n        s_mouse_state = MouseState::just_released;\n    else\n        s_mouse_state = MouseState::up;\n\n    if(KeyboardMouseRender(true, false))\n    {\n        s_finalize();\n        return true;\n    }\n\n    MenuControls_t menuControls = Controls::GetMenuControls();\n\n    bool startPressed = false;\n\n    for(int i = 0; i < l_screen->player_count; i++)\n    {\n        const Controls_t &c = Controls::g_RawControls[i];\n\n        startPressed |= c.Start;\n    }\n\n    if(MenuCursorCanMove)\n    {\n        if(menuControls.Up)\n            GoUp();\n\n        if(menuControls.Down)\n            GoDown();\n\n        if(menuControls.Left)\n            GoLeft();\n\n        if(menuControls.Right)\n            GoRight();\n\n        if(menuControls.Back)\n            Backspace();\n\n        if(startPressed || (menuControls.Do && DoAction()))\n        {\n            s_finalize();\n            return true;\n        }\n    }\n\n\n    if(!menuControls.Up && !menuControls.Down && !menuControls.Left && !menuControls.Right && !menuControls.Do && !menuControls.Back && !startPressed)\n    {\n        MenuCursorCanMove = true;\n    }\n    else\n    {\n        if(MenuCursorCanMove)\n            s_render_sel = true;\n        MenuCursorCanMove = false;\n        s_timer --;\n        if(s_timer == 0)\n            MenuCursorCanMove = true;\n    }\n\n    if(MenuCursorCanMove)\n        s_timer = 10;\n\n    if(s_committed)\n        s_finalize();\n\n    return s_committed;\n}\n\n} // namespace TextEntryScreen\n\n#ifdef __ANDROID__\n\nextern \"C\" JNIEXPORT void JNICALL\nJava_ru_wohlsoft_thextech_thextechActivity_textentry_1setBuffer(JNIEnv *env, jclass clazz, jstring line_j)\n{\n    const char *line;\n    (void)clazz;\n    line = env->GetStringUTFChars(line_j, nullptr);\n    TextEntryScreen::Text = (std::string)line;\n    env->ReleaseStringUTFChars(line_j, line);\n}\n\n#endif // #ifdef __ANDROID__\n"
  },
  {
    "path": "src/main/screen_textentry.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SCREEN_TEXTENTRY_H\n#define SCREEN_TEXTENTRY_H\n\n#include <string>\n\nnamespace TextEntryScreen\n{\n\nextern std::string Text;\n\n// Initialize TextEntryScreen. [Temporarily returns whether a PauseGame(PauseCode::None) should be triggered.]\nbool Init(const std::string& Prompt, void (*callback)(), const std::string Value = std::string());\n\n// Initialize and run TextEntryScreen, waiting for the pause loop to complete, and returning the entered text. Deprecated.\nconst std::string& Run(const std::string& Prompt, const std::string Value = std::string());\n\nvoid Render();\nbool Logic();\n\nvoid CursorLeft();\nvoid CursorRight();\nvoid Insert(const char* text);\nvoid Backspace();\nvoid Commit();\n\n} // namespace TextEntryScreen\n\n#endif // #ifndef SCREEN_TEXTENTRY_H\n"
  },
  {
    "path": "src/main/setup_physics.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../globals.h\"\n#include \"../game_main.h\"\n\nvoid SetupPhysics()\n{\n    Physics.PlayerJumpVelocity = -(num_t)5.7_nf; // Jump velocity\n    Physics.PlayerJumpHeight = 20;          // Jump height\n    Physics.PlayerBlockJumpHeight = 25;     // Jump height off bouncy blocks\n    Physics.PlayerHeadJumpHeight = 22;      // Jump height off another players head\n    Physics.PlayerNPCJumpHeight = 22;       // Jump height off a NPC\n    Physics.PlayerSpringJumpHeight = 55;    // Jump height off a Spring\n    Physics.PlayerRunSpeed = 6;             // Max run speed\n    Physics.PlayerWalkSpeed = 3;            // Max walk speed\n    Physics.PlayerGravity = (num_t)0.4_nf;  // Player's gravity\n    Physics.PlayerTerminalVelocity = 12;    // Max falling speed\n    Physics.PlayerHeight[1][1] = 30;        // Little Mario\n    Physics.PlayerWidth[1][1] = 24;         // ------------\n    Physics.PlayerGrabSpotX[1][1] = 18;     // ---------\n    Physics.PlayerGrabSpotY[1][1] = -2;     // ---------\n\n    for(int i = 2; i <= numStates; i++)\n    {\n        Physics.PlayerHeight[1][i] = 54;        // Big Char1\n        Physics.PlayerWidth[1][i] = 24;         // ---------\n        Physics.PlayerDuckHeight[1][i] = 30;    // ---------\n        Physics.PlayerGrabSpotX[1][i] = 18;     // ---------\n        Physics.PlayerGrabSpotY[1][i] = 16;     // ---------\n    }\n\n    Physics.PlayerHeight[2][1] = 30;        // Little Luigi\n    Physics.PlayerWidth[2][1] = 24;         // ------------\n    Physics.PlayerGrabSpotX[2][1] = 16;     // ---------\n    Physics.PlayerGrabSpotY[2][1] = -4;     // ---------\n\n    for(int i = 2; i <= numStates; i++)\n    {\n        Physics.PlayerHeight[2][i] = 60;        // Big Char2\n        Physics.PlayerWidth[2][i] = 24;         // ---------\n        Physics.PlayerDuckHeight[2][i] = 30;    // ---------\n        Physics.PlayerGrabSpotX[2][i] = 18;     // ---------\n        Physics.PlayerGrabSpotY[2][i] = 16;     // ---------\n    }\n\n    Physics.PlayerHeight[3][1] = 38;        // Little Peach\n    Physics.PlayerDuckHeight[3][1] = 26;    // ---------\n    Physics.PlayerWidth[3][1] = 24;         // ------------\n    Physics.PlayerGrabSpotX[3][1] = 0;      // ---------\n    Physics.PlayerGrabSpotY[3][1] = 0;      // ---------\n    Physics.PlayerHeight[3][2] = 60;        // Big Peach\n    Physics.PlayerWidth[3][2] = 24;         // ---------\n    Physics.PlayerDuckHeight[3][2] = 30;    // ---------\n    Physics.PlayerGrabSpotX[3][2] = 0;     // ---------\n    Physics.PlayerGrabSpotY[3][2] = 0;     // ---------\n\n    for(int i = 3; i <= numStates; i++)\n    {\n        Physics.PlayerHeight[3][i] = 60;        // Power Char3\n        Physics.PlayerWidth[3][i] = 24;         // ---------\n        Physics.PlayerDuckHeight[3][i] = 30;    // ---------\n        Physics.PlayerGrabSpotX[3][i] = 18;\n        Physics.PlayerGrabSpotY[3][i] = 16;\n    }\n\n    Physics.PlayerHeight[4][1] = 30;        // Little Toad\n    Physics.PlayerWidth[4][1] = 24;         // ------------\n    Physics.PlayerDuckHeight[4][1] = 26;    // ---------\n    Physics.PlayerGrabSpotX[4][1] = 18;     // ---------\n    Physics.PlayerGrabSpotY[4][1] = -2;     // ---------\n\n    for(int i = 2; i <= numStates; i++)\n    {\n        Physics.PlayerHeight[4][i] = 50;        // Big Char3\n        Physics.PlayerWidth[4][i] = 24;         // ---------\n        Physics.PlayerDuckHeight[4][i] = 30;    // ---------\n        Physics.PlayerGrabSpotX[4][i] = 18;     // ---------\n        Physics.PlayerGrabSpotY[4][i] = 16;     // ---------\n    }\n\n    for(int i = 1; i <= numStates; i++)\n    {\n        Physics.PlayerHeight[5][i] = 54;        // Char5\n        Physics.PlayerWidth[5][i] = 22;         // ---------\n        Physics.PlayerDuckHeight[5][i] = 44;    // ---------\n        Physics.PlayerGrabSpotX[5][i] = 18;     // ---------\n        Physics.PlayerGrabSpotY[5][i] = 16;     // ---------\n    }\n\n    Physics.NPCTimeOffScreen = 180;                     // How long NPCs are active offscreen before being reset\n    Physics.NPCShellSpeed = (num_t)7.1_nf;              // Speed of kicked shells\n    Physics.NPCShellSpeedY = 11;                        // Vertical Speed of kicked shells\n    Physics.NPCCanHurtWait = 30;                        // How long to wait before NPCs can hurt players\n    Physics.NPCGravityReal = 0.26_nf;                   // NPC Gravity\n    Physics.NPCGravity = (num_t)Physics.NPCGravityReal; // NPC Gravity\n    Physics.NPCWalkingSpeed = (num_t)1.2_nf;            // NPC Walking Speed\n    Physics.NPCWalkingOnSpeed = 1;                      // NPC that can be walked on walking speed\n    Physics.NPCMushroomSpeed = (num_t)1.8_nf;           // Mushroom X Speed\n    Physics.NPCPSwitch = 777;                           // P Switch time\n}\n"
  },
  {
    "path": "src/main/setup_vars.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../globals.h\"\n#include \"../game_main.h\"\n#include \"../custom.h\"\n#include \"npc_traits.h\"\n#include \"npc_id.h\"\n\nvoid SetupVars()\n{\n    //int A;\n    SetupPlayerFrames();\n    for(int A = 1; A <= maxEffectType; A++)\n    {\n        EffectWidth[A] = 32;\n        EffectHeight[A] = 32;\n    }\n    // now part of the default NPCTraits_t initializer'\n    // for(int A = 1; A <= maxNPCType; ++A)\n    //     NPCTraits[A].Speedvar = 1;\n    for(int A = 174; A <= 186; ++A)\n        BackgroundFence[A] = true;\n\n    GFXLevelBig[21] = true;\n    GFXLevelBig[22] = true;\n    GFXLevelBig[23] = true;\n    GFXLevelBig[24] = true;\n    GFXLevelBig[28] = true;\n\n    EffectWidth[145] = 32;\n    EffectHeight[145] = 60;\n\n    EffectWidth[71] = 16;\n    EffectHeight[71] = 16;\n    EffectWidth[148] = 16;\n    EffectHeight[148] = 16;\n\n\n    EffectWidth[111] = 16;\n    EffectHeight[111] = 16;\n    EffectHeight[121] = 48;\n\n    EffectWidth[144] = 54;\n    EffectHeight[144] = 42;\n\n\n    EffectWidth[143] = 64;\n    EffectHeight[143] = 64;\n\n    EffectWidth[120] = 48;\n    EffectHeight[121] = 48;\n\n\n    EffectWidth[138] = 48;\n    EffectHeight[138] = 64;\n\n\n    EffectWidth[125] = 80;\n    EffectHeight[125] = 34;\n\n    EffectWidth[134] = 32;\n    EffectHeight[134] = 58;\n\n    EffectWidth[130] = 30;\n    EffectHeight[130] = 34;\n\n    EffectWidth[132] = 32;\n    EffectHeight[132] = 32;\n    EffectWidth[133] = 10;\n    EffectHeight[133] = 8;\n\n\n    EffectWidth[1] = 16;\n    EffectHeight[1] = 16;\n\n    EffectWidth[135] = 16;\n    EffectHeight[135] = 16;\n\n    EffectWidth[104] = 32;\n    EffectHeight[104] = 32;\n    EffectWidth[105] = 64;\n    EffectHeight[105] = 72;\n\n    EffectWidth[129] = 30;\n    EffectHeight[129] = 42;\n\n    EffectWidth[106] = 80;\n    EffectHeight[106] = 94;\n\n    EffectWidth[108] = 64;\n    EffectHeight[108] = 64;\n\n    EffectWidth[112] = 96;\n    EffectHeight[112] = 106;\n\n    EffectWidth[4] = 32;\n    EffectHeight[4] = 32;\n    EffectWidth[8] = 32;\n    EffectHeight[8] = 32;\n    EffectWidth[9] = 32;\n    EffectHeight[9] = 32;\n    EffectWidth[10] = 32;\n    EffectHeight[10] = 32;\n    EffectWidth[11] = 32;\n    EffectHeight[11] = 32;\n    EffectWidth[12] = 8;\n    EffectHeight[12] = 8;\n    EffectWidth[13] = 84;\n    EffectHeight[13] = 26;\n    EffectWidth[14] = 62;\n    EffectHeight[14] = 32;\n    EffectWidth[15] = 32;\n    EffectHeight[15] = 28;\n    EffectWidth[16] = 128;\n    EffectHeight[16] = 128;\n    EffectWidth[17] = 32;\n    EffectHeight[17] = 32;\n    EffectWidth[18] = 32;\n    EffectHeight[18] = 32;\n    EffectWidth[19] = 32;\n    EffectHeight[19] = 32;\n    EffectWidth[20] = 32;\n    EffectHeight[20] = 32;\n    EffectWidth[21] = 16;\n    EffectHeight[21] = 16;\n    EffectWidth[22] = 32;\n    EffectHeight[22] = 32;\n    EffectWidth[23] = 32;\n    EffectHeight[23] = 32;\n    EffectWidth[24] = 32;\n    EffectHeight[24] = 32;\n    EffectWidth[25] = 32;\n    EffectHeight[25] = 48;\n    EffectWidth[26] = 32;\n    EffectHeight[26] = 32;\n    EffectWidth[29] = 40;\n    EffectHeight[29] = 64;\n    EffectWidth[30] = 16;\n    EffectHeight[30] = 16;\n    EffectWidth[31] = 32;\n    EffectHeight[31] = 32;\n    EffectWidth[32] = 32;\n    EffectHeight[32] = 62;\n    EffectWidth[33] = 32;\n    EffectHeight[33] = 32;\n    EffectWidth[34] = 32;\n    EffectHeight[34] = 32;\n    EffectWidth[35] = 32;\n    EffectHeight[35] = 32;\n    EffectWidth[36] = 32;\n    EffectHeight[36] = 32;\n    EffectWidth[37] = 32;\n    EffectHeight[37] = 32;\n    EffectWidth[38] = 32;\n    EffectHeight[38] = 32;\n    EffectWidth[45] = 48;\n    EffectHeight[45] = 46;\n    EffectWidth[46] = 48;\n    EffectHeight[46] = 46;\n    EffectWidth[47] = 44;\n    EffectHeight[47] = 44;\n    EffectWidth[48] = 32;\n    EffectHeight[48] = 32;\n    EffectWidth[50] = 64;\n    EffectHeight[50] = 80;\n    EffectWidth[51] = 16;\n    EffectHeight[51] = 16;\n    EffectWidth[54] = 32;\n    EffectHeight[54] = 64;\n    EffectWidth[55] = 32;\n    EffectHeight[55] = 64;\n    EffectWidth[56] = 32;\n    EffectHeight[56] = 32;\n    EffectWidth[57] = 16;\n    EffectHeight[57] = 16;\n    EffectWidth[58] = 32;\n    EffectHeight[58] = 32;\n    EffectWidth[59] = 32;\n    EffectHeight[59] = 64;\n    EffectWidth[60] = 32;\n    EffectHeight[60] = 32;\n    EffectWidth[61] = 32;\n    EffectHeight[61] = 32;\n    EffectWidth[62] = 32;\n    EffectHeight[62] = 32;\n    EffectWidth[63] = 48;\n    EffectHeight[63] = 48;\n    EffectWidth[64] = 32;\n    EffectHeight[64] = 32;\n    EffectWidth[65] = 32;\n    EffectHeight[65] = 32;\n    EffectWidth[66] = 32;\n    EffectHeight[66] = 32;\n    EffectWidth[67] = 32;\n    EffectHeight[67] = 32;\n    EffectWidth[68] = 16;\n    EffectHeight[68] = 16;\n    EffectWidth[69] = 64;\n    EffectHeight[69] = 64;\n    EffectWidth[70] = 16;\n    EffectHeight[70] = 16;\n    EffectWidth[71] = 16;\n    EffectHeight[71] = 16;\n    EffectWidth[72] = 32;\n    EffectHeight[72] = 28;\n    EffectWidth[73] = 32;\n    EffectHeight[73] = 32;\n    EffectWidth[74] = 8;\n    EffectHeight[74] = 10;\n    EffectWidth[75] = 32;\n    EffectHeight[75] = 32;\n    EffectWidth[76] = 14;\n    EffectHeight[76] = 14;\n    EffectWidth[77] = 8;\n    EffectHeight[77] = 8;\n    EffectWidth[139] = 8;\n    EffectHeight[139] = 8;\n    EffectWidth[78] = 10;\n    EffectHeight[78] = 10;\n    EffectWidth[79] = 50;\n    EffectHeight[79] = 24;\n    EffectWidth[80] = 16;\n    EffectHeight[80] = 16;\n    EffectWidth[81] = 32;\n    EffectHeight[81] = 32;\n    EffectWidth[82] = 32;\n    EffectHeight[82] = 32;\n    EffectWidth[83] = 32;\n    EffectHeight[83] = 42;\n    EffectWidth[84] = 32;\n    EffectHeight[84] = 32;\n    EffectWidth[85] = 32;\n    EffectHeight[85] = 32;\n    EffectWidth[86] = 40;\n    EffectHeight[86] = 64;\n    EffectWidth[87] = 64;\n    EffectHeight[87] = 64;\n    EffectWidth[88] = 32;\n    EffectHeight[88] = 32;\n    EffectWidth[89] = 26;\n    EffectHeight[89] = 46;\n    EffectWidth[90] = 48;\n    EffectHeight[90] = 96;\n    EffectWidth[91] = 140;\n    EffectHeight[91] = 128;\n    EffectWidth[92] = 32;\n    EffectHeight[92] = 32;\n    EffectWidth[93] = 32;\n    EffectHeight[93] = 32;\n    EffectWidth[94] = 32;\n    EffectHeight[94] = 32;\n    EffectWidth[97] = 48;\n    EffectHeight[97] = 64;\n    EffectWidth[98] = 64;\n    EffectHeight[98] = 64;\n    EffectWidth[99] = 48;\n    EffectHeight[99] = 64;\n    EffectWidth[100] = 16;\n    EffectHeight[100] = 16;\n\n    EffectWidth[113] = 8;\n    EffectHeight[113] = 8;\n\n    EffectWidth[140] = 32;\n    EffectHeight[140] = 40;\n\n\n    EffectWidth[114] = 32;\n    EffectHeight[114] = 32;\n\n    EffectWidth[103] = 120;\n    EffectHeight[103] = 96;\n\n    EffectWidth[123] = 32;\n    EffectHeight[123] = 32;\n    EffectWidth[124] = 32;\n    EffectHeight[124] = 32;\n    EffectHeight[95] = 28;\n    EffectHeight[96] = 28;\n\n    EffectHeight[46] = 46;\n    EffectWidth[46] = 48;\n    EffectHeight[47] = 44;\n    EffectWidth[47] = 44;\n\n    // NEW: reset all variables to their original values (it will be nice to convert all NPC traits to a single struct)\n    NPCTraits.fill(NPCTraits_t());\n\n    // clear fields that were incorrectly initialized for NPCTraits[0] between v1.3.7 and v1.3.7.3 (caused a bug where popping an empty bubble could harm the player)\n    NPCTraits[NPCID_NULL].Speedvar = 0;\n    NPCTraits[NPCID_NULL].TWidth = 0;\n    NPCTraits[NPCID_NULL].THeight = 0;\n    NPCTraits[NPCID_NULL].Score = 0;\n\n    // SMBX64 logic:\n    // now included as part of default NPCTraits_t initializer\n    // for(int A = 1; A <= maxNPCType; ++A)\n    // {\n    //     NPCTraits[A].Score = 2;\n    //     NPCTraits[A].TWidth = 32;\n    //     NPCTraits[A].THeight = 32;\n    // }\n\n    NPCTraits[NPCID_CHAR3_HEAVY].TWidth = 22;\n    NPCTraits[NPCID_CHAR3_HEAVY].THeight = 18;\n    NPCTraits[NPCID_CHAR3_HEAVY].WidthGFX = 42;\n    NPCTraits[NPCID_CHAR3_HEAVY].HeightGFX = 34;\n    NPCTraits[NPCID_CHAR3_HEAVY].WontHurt = true;\n    NPCTraits[NPCID_CHAR3_HEAVY].JumpHurt = true;\n    NPCTraits[NPCID_CHAR3_HEAVY].NoYoshi = true;\n\n\n\n    NPCTraits[NPCID_LONG_PLANT_UP].NoYoshi = true;\n    NPCTraits[NPCID_LONG_PLANT_DOWN].NoYoshi = true;\n    NPCTraits[NPCID_TNT].NoYoshi = true;\n\n\n    NPCTraits[NPCID_BOSS_CASE].Score = 0;\n    NPCTraits[NPCID_MINIBOSS].Score = 7;\n    NPCTraits[NPCID_SPIT_BOSS].Score = 7;\n    NPCTraits[NPCID_VILLAIN_S3].Score = 9;\n    NPCTraits[NPCID_VILLAIN_S1].Score = 9;\n    NPCTraits[NPCID_SICK_BOSS].Score = 9;\n    NPCTraits[NPCID_BOSS_FRAGILE].Score = 9;\n    NPCTraits[NPCID_HEAVY_THROWER].Score = 5;\n    NPCTraits[NPCID_SPIKY_THROWER].Score = 6;\n    NPCTraits[NPCID_ITEM_THROWER].Score = 6;\n    NPCTraits[NPCID_LONG_PLANT_UP].Score = 5;\n    NPCTraits[NPCID_LONG_PLANT_DOWN].Score = 5;\n    NPCTraits[NPCID_BOMBER_BOSS].Score = 8;\n    NPCTraits[NPCID_MAGIC_BOSS].Score = 8;\n    NPCTraits[NPCID_MAGIC_BOSS_SHELL].Score = 8;\n    NPCTraits[NPCID_FIRE_BOSS].Score = 8;\n    NPCTraits[NPCID_FIRE_BOSS_SHELL].Score = 8;\n    NPCTraits[NPCID_GEM_1].FrameOffsetY = 2;\n    NPCTraits[NPCID_GEM_5].FrameOffsetY = 2;\n    NPCTraits[NPCID_GEM_20].FrameOffsetY = 2;\n    NPCTraits[NPCID_FLIPPED_RAINBOW_SHELL].FrameOffsetY = 2;\n    NPCTraits[NPCID_JUMPER_S4].FrameOffsetY = 2;\n\n    NPCTraits[NPCID_DOOR_MAKER].WontHurt = true;\n    NPCTraits[NPCID_DOOR_MAKER].JumpHurt = true;\n\n    NPCTraits[NPCID_RANDOM_POWER].WontHurt = true;\n    NPCTraits[NPCID_FLIPPED_RAINBOW_SHELL].WontHurt = true;\n    NPCTraits[NPCID_HOMING_BALL].Foreground = true;\n    NPCTraits[NPCID_RED_FISH_S3].Foreground = true;\n\n    NPCTraits[NPCID_RED_FISH_S1].IsFish = true;\n    NPCTraits[NPCID_GRN_FISH_S3].IsFish = true;\n    NPCTraits[NPCID_RED_FISH_S3].IsFish = true;\n    NPCTraits[NPCID_GRN_FISH_S4].IsFish = true;\n    NPCTraits[NPCID_GRN_FISH_S1].IsFish = true;\n    NPCTraits[NPCID_BONE_FISH].IsFish = true;\n    NPCTraits[NPCID_YEL_FISH_S4].IsFish = true;\n\n\n    NPCTraits[NPCID_MAGIC_DOOR].NoClipping = true;\n    NPCTraits[NPCID_MAGIC_DOOR].WontHurt = true;\n    NPCTraits[NPCID_MAGIC_DOOR].JumpHurt = true;\n    NPCTraits[NPCID_MAGIC_DOOR].TWidth = 32;\n    NPCTraits[NPCID_MAGIC_DOOR].THeight = 32;\n    NPCTraits[NPCID_MAGIC_DOOR].WidthGFX = 32;\n    NPCTraits[NPCID_MAGIC_DOOR].HeightGFX = 64;\n\n\n    NPCTraits[NPCID_ITEM_BUBBLE].TWidth = 48;\n    NPCTraits[NPCID_ITEM_BUBBLE].THeight = 48;\n    NPCTraits[NPCID_ITEM_BUBBLE].WidthGFX = 64;\n    NPCTraits[NPCID_ITEM_BUBBLE].HeightGFX = 64;\n    NPCTraits[NPCID_ITEM_BUBBLE].FrameOffsetY = 8;\n    NPCTraits[NPCID_ITEM_BUBBLE].WontHurt = true;\n    NPCTraits[NPCID_ITEM_BUBBLE].JumpHurt = true;\n\n    NPCTraits[NPCID_COCKPIT].WidthGFX = 32;\n    NPCTraits[NPCID_COCKPIT].HeightGFX = 64;\n    NPCTraits[NPCID_COCKPIT].Foreground = true;\n    NPCTraits[NPCID_COCKPIT].CanWalkOn = true;\n    NPCTraits[NPCID_COCKPIT].IsABlock = true;\n    NPCTraits[NPCID_COCKPIT].WontHurt = true;\n    NPCTraits[NPCID_COCKPIT].NoClipping = true;\n\n\n    NPCTraits[NPCID_ICE_CUBE].CanWalkOn = true;\n    NPCTraits[NPCID_ICE_CUBE].WontHurt = true;\n    NPCTraits[NPCID_ICE_CUBE].MovesPlayer = true;\n    NPCTraits[NPCID_ICE_CUBE].IsGrabbable = true;\n    NPCTraits[NPCID_ICE_CUBE].GrabFromTop = true;\n    NPCTraits[NPCID_ICE_CUBE].IsABlock = true;\n\n    NPCTraits[NPCID_BOMBER_BOSS].CanWalkOn = true;\n    NPCTraits[NPCID_BOMBER_BOSS].TWidth = 40;\n    NPCTraits[NPCID_BOMBER_BOSS].THeight = 56;\n    NPCTraits[NPCID_BOMBER_BOSS].WidthGFX = 48;\n    NPCTraits[NPCID_BOMBER_BOSS].HeightGFX = 64;\n\n\n\n    NPCTraits[NPCID_FLY_POWER].IsABonus = true;\n    NPCTraits[NPCID_ICE_BLOCK].IsAShell = true;\n    NPCTraits[NPCID_ICE_BLOCK].WontHurt = true;\n    NPCTraits[NPCID_ICE_BLOCK].CanWalkOn = true;\n    NPCTraits[NPCID_PLR_FIREBALL].JumpHurt = true;\n    NPCTraits[NPCID_VILLAIN_S3].JumpHurt = true;\n    NPCTraits[NPCID_FIRE_CHAIN].JumpHurt = true;\n\n\n\n    NPCTraits[NPCID_ICE_CUBE].NoYoshi = true;\n    NPCTraits[NPCID_PLR_ICEBALL].NoYoshi = true;\n    NPCTraits[NPCID_FIRE_CHAIN].NoYoshi = true;\n    NPCTraits[NPCID_LOCK_DOOR].NoYoshi = true;\n\n    NPCTraits[NPCID_JUMP_PLANT].NoClipping = true;\n\n    NPCTraits[NPCID_PLANT_FIREBALL].JumpHurt = true;\n    NPCTraits[NPCID_PLANT_FIREBALL].NoClipping = true;\n    NPCTraits[NPCID_PLANT_FIREBALL].NoYoshi = true;\n    NPCTraits[NPCID_PLANT_FIREBALL].TWidth = 16;\n    NPCTraits[NPCID_PLANT_FIREBALL].THeight = 16;\n\n    NPCTraits[NPCID_GEM_1].TWidth = 18; // TLOZ Rupee\n    NPCTraits[NPCID_GEM_1].THeight = 32;\n    NPCTraits[NPCID_GEM_1].IsABonus = true;\n    NPCTraits[NPCID_GEM_1].IsACoin = true;\n\n    NPCTraits[NPCID_GEM_5].TWidth = 18; // TLOZ Rupee\n    NPCTraits[NPCID_GEM_5].THeight = 32;\n    NPCTraits[NPCID_GEM_5].IsABonus = true;\n    NPCTraits[NPCID_GEM_5].IsACoin = true;\n\n    NPCTraits[NPCID_GEM_20].TWidth = 18; // TLOZ Rupee\n    NPCTraits[NPCID_GEM_20].THeight = 32;\n    NPCTraits[NPCID_GEM_20].IsABonus = true;\n    NPCTraits[NPCID_GEM_20].IsACoin = true;\n\n    NPCTraits[NPCID_POWER_S5].TWidth = 32; // TLOZ Heart\n    NPCTraits[NPCID_POWER_S5].THeight = 32;\n    NPCTraits[NPCID_POWER_S5].IsABonus = true;\n\n    // vines\n    for(int A = NPCID_GRN_VINE_S3; A <= NPCID_GRN_VINE_S4; ++A)\n    {\n        NPCTraits[A].IsAVine = true;\n        NPCTraits[A].WontHurt = true;\n        NPCTraits[A].NoClipping = true;\n        NPCTraits[A].JumpHurt = true;\n        NPCTraits[A].NoYoshi = true;\n        NPCTraits[A].TWidth = 16;\n        NPCTraits[A].WidthGFX = 32;\n        NPCTraits[A].HeightGFX = 32;\n    }\n    NPCTraits[NPCID_GRN_VINE_TOP_S1].THeight = 24;\n    NPCTraits[NPCID_GRN_VINE_TOP_S1].HeightGFX = 24;\n    NPCTraits[NPCID_SQUID_S3].JumpHurt = true;\n    NPCTraits[NPCID_RED_VINE_TOP_S3].WontHurt = true;\n    NPCTraits[NPCID_RED_VINE_TOP_S3].JumpHurt = true;\n    NPCTraits[NPCID_RED_VINE_TOP_S3].NoYoshi = true;\n    NPCTraits[NPCID_GRN_VINE_TOP_S3].WontHurt = true;\n    NPCTraits[NPCID_GRN_VINE_TOP_S3].JumpHurt = true;\n    NPCTraits[NPCID_GRN_VINE_TOP_S3].NoYoshi = true;\n    NPCTraits[NPCID_GRN_VINE_TOP_S4].WontHurt = true;\n    NPCTraits[NPCID_GRN_VINE_TOP_S4].JumpHurt = true;\n    NPCTraits[NPCID_GRN_VINE_TOP_S4].NoYoshi = true;\n\n    NPCTraits[NPCID_HOMING_BALL_GEN].NoYoshi = true;\n\n    NPCTraits[NPCID_FLIPPED_RAINBOW_SHELL].IsGrabbable = true;\n    NPCTraits[NPCID_FLIPPED_RAINBOW_SHELL].GrabFromTop = true;\n\n    NPCTraits[NPCID_TIMER_S2].TWidth = 32;\n    NPCTraits[NPCID_TIMER_S2].THeight = 32;\n    NPCTraits[NPCID_TIMER_S2].IsABonus = true;//32;\n\n    NPCTraits[NPCID_TIMER_S3].TWidth = 32;\n    NPCTraits[NPCID_TIMER_S3].THeight = 32;\n    NPCTraits[NPCID_TIMER_S3].IsABonus = true;//32;\n\n    NPCTraits[NPCID_VILLAIN_S1].TWidth = 64;\n    NPCTraits[NPCID_VILLAIN_S1].THeight = 72;\n    NPCTraits[NPCID_VILLAIN_S1].FrameOffsetY = 2;\n    NPCTraits[NPCID_VILLAIN_S1].JumpHurt = true;\n    NPCTraits[NPCID_HOMING_BALL].JumpHurt = true;\n    NPCTraits[NPCID_FIRE_DISK].NoClipping = true;\n    NPCTraits[NPCID_FIRE_DISK].NoYoshi = true;\n    NPCTraits[NPCID_FIRE_DISK].JumpHurt = true;\n    NPCTraits[NPCID_FIRE_CHAIN].TWidth = 16;\n    NPCTraits[NPCID_FIRE_CHAIN].THeight = 16;\n    NPCTraits[NPCID_FIRE_CHAIN].NoClipping = true;\n\n    NPCTraits[NPCID_POISON].TWidth = 32; // Poison Mushroom\n    NPCTraits[NPCID_POISON].THeight = 32;\n    NPCTraits[NPCID_POISON].FrameOffsetY = 2;\n\n    NPCTraits[NPCID_FODDER_S5].TWidth = 32; // SML2 Goomba\n    NPCTraits[NPCID_FODDER_S5].THeight = 32;\n    NPCTraits[NPCID_FODDER_S5].FrameOffsetY = 2;\n    // NPCDefaultMovement(NPCID_FODDER_S5) = true;\n\n    NPCTraits[NPCID_FLY_FODDER_S5].TWidth = 32; // SML2 Flying Goomba\n    NPCTraits[NPCID_FLY_FODDER_S5].THeight = 32;\n    NPCTraits[NPCID_FLY_FODDER_S5].FrameOffsetY = 2;\n    NPCTraits[NPCID_FLY_FODDER_S5].WidthGFX = 56;\n    NPCTraits[NPCID_FLY_FODDER_S5].HeightGFX = 36;\n\n    NPCTraits[NPCID_FLY_FODDER_S3].TWidth = 32; // SMB3 Flying Goomba\n    NPCTraits[NPCID_FLY_FODDER_S3].THeight = 32;\n    NPCTraits[NPCID_FLY_FODDER_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_FLY_FODDER_S3].WidthGFX = 40;\n    NPCTraits[NPCID_FLY_FODDER_S3].HeightGFX = 48;\n\n    NPCTraits[NPCID_LOCK_DOOR].TWidth = 20;\n    NPCTraits[NPCID_LOCK_DOOR].THeight = 96;\n    NPCTraits[NPCID_LOCK_DOOR].WontHurt = true;\n    NPCTraits[NPCID_LOCK_DOOR].IsABlock = true;\n    NPCTraits[NPCID_LOCK_DOOR].CanWalkOn = true;\n    NPCTraits[NPCID_LOCK_DOOR].MovesPlayer = true;\n\n    // NPCIsAParaTroopa(NPCID_FLY_FODDER_S3) = true;\n    // NPCIsAParaTroopa(NPCID_FLY_FODDER_S5) = true;\n\n    NPCTraits[NPCID_FODDER_S3].TWidth = 32; // Goomba\n    NPCTraits[NPCID_FODDER_S3].THeight = 32;\n    NPCTraits[NPCID_FODDER_S3].FrameOffsetY = 2;\n\n    NPCTraits[NPCID_FODDER_S3].TWidth = 32; // Goomba\n    NPCTraits[NPCID_FODDER_S3].THeight = 32;\n    NPCTraits[NPCID_FODDER_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_FODDER].TWidth = 32; // Red goomba\n    NPCTraits[NPCID_RED_FODDER].THeight = 32;\n    NPCTraits[NPCID_RED_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_FLY_FODDER].TWidth = 32; // Flying goomba\n    NPCTraits[NPCID_RED_FLY_FODDER].THeight = 32;\n    NPCTraits[NPCID_RED_FLY_FODDER].WidthGFX = 40;\n    NPCTraits[NPCID_RED_FLY_FODDER].HeightGFX = 48;\n    NPCTraits[NPCID_RED_FLY_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_GRN_TURTLE_S3].TWidth = 32; // Green koopa\n    NPCTraits[NPCID_GRN_TURTLE_S3].THeight = 32;\n    NPCTraits[NPCID_GRN_TURTLE_S3].WidthGFX = 32;\n    NPCTraits[NPCID_GRN_TURTLE_S3].HeightGFX = 54;\n    NPCTraits[NPCID_GRN_TURTLE_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_GRN_SHELL_S3].TWidth = 32; // Green shell\n    NPCTraits[NPCID_GRN_SHELL_S3].THeight = 32;\n    NPCTraits[NPCID_GRN_SHELL_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_TURTLE_S3].TWidth = 32; // Red koopa\n    NPCTraits[NPCID_RED_TURTLE_S3].THeight = 32;\n    NPCTraits[NPCID_RED_TURTLE_S3].WidthGFX = 32;\n    NPCTraits[NPCID_RED_TURTLE_S3].HeightGFX = 54;\n    NPCTraits[NPCID_RED_TURTLE_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_SHELL_S3].TWidth = 32; // Red shell\n    NPCTraits[NPCID_RED_SHELL_S3].THeight = 32;\n    NPCTraits[NPCID_RED_SHELL_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_PLANT_S3].TWidth = 32; // Plant\n    NPCTraits[NPCID_PLANT_S3].THeight = 48;\n    NPCTraits[NPCID_PLANT_S3].WidthGFX = 32;\n    NPCTraits[NPCID_PLANT_S3].HeightGFX = 48;\n    NPCTraits[NPCID_FIRE_PLANT].TWidth = 32; // SMB 3 Fire Plant\n    NPCTraits[NPCID_FIRE_PLANT].THeight = 64;\n    NPCTraits[NPCID_FIRE_PLANT].WidthGFX = 32;\n    NPCTraits[NPCID_FIRE_PLANT].HeightGFX = 64;\n    NPCTraits[NPCID_PLANT_S3].FrameOffsetY = 1;\n    NPCTraits[NPCID_POWER_S3].TWidth = 32; // Mushroom\n    NPCTraits[NPCID_POWER_S3].THeight = 32;\n    NPCTraits[NPCID_POWER_S3].FrameOffsetY =2;\n\n    // Reversed incompatible powerup score customizability implementation at v1.3.6.1.\n    // These scores are now found in loadNpcSetupFixes();\n\n    NPCTraits[NPCID_SWAP_POWER].TWidth = 32; // ? Mushroom\n    NPCTraits[NPCID_SWAP_POWER].THeight = 32;\n    NPCTraits[NPCID_SWAP_POWER].FrameOffsetY = 2;\n\n    NPCTraits[NPCID_POWER_S2].TWidth = 32; // SMB2 Mushroom\n    NPCTraits[NPCID_POWER_S2].THeight = 32;\n    NPCTraits[NPCID_POWER_S2].IsABonus = true;\n\n    NPCTraits[NPCID_MEDAL].TWidth = 32; // dragon coin\n    NPCTraits[NPCID_MEDAL].THeight = 50;\n    NPCTraits[NPCID_MEDAL].IsABonus = true;\n    NPCTraits[NPCID_MEDAL].IsACoin = true;\n    NPCTraits[NPCID_MEDAL].Score = 6;\n\n    NPCTraits[NPCID_COIN_S3].TWidth = 28; // SMB3 Coin\n    NPCTraits[NPCID_COIN_S3].THeight = 32;\n    NPCTraits[NPCID_ITEMGOAL].TWidth = 32; // SMB3 Level exit\n    NPCTraits[NPCID_ITEMGOAL].THeight = 32;\n    NPCTraits[NPCID_LAVABUBBLE].TWidth = 28; // Big Fireball\n    NPCTraits[NPCID_LAVABUBBLE].THeight = 32;\n    NPCTraits[NPCID_PLR_FIREBALL].TWidth = 16; // Small Fireball\n    NPCTraits[NPCID_PLR_FIREBALL].THeight = 16;\n    NPCTraits[NPCID_PLR_ICEBALL].TWidth = 16; // Ice Bolt\n    NPCTraits[NPCID_PLR_ICEBALL].THeight = 16;\n    NPCTraits[NPCID_PLR_ICEBALL].NoIceBall = true;\n    NPCTraits[NPCID_PLR_FIREBALL].NoYoshi = true;\n    NPCTraits[NPCID_FIRE_POWER_S3].TWidth = 32; // Fire Flower\n    NPCTraits[NPCID_FIRE_POWER_S3].THeight = 32;\n    NPCTraits[NPCID_FIRE_POWER_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_ICE_POWER_S3].TWidth = 32; // Ice Flower\n    NPCTraits[NPCID_ICE_POWER_S3].THeight = 32;\n    NPCTraits[NPCID_ICE_POWER_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_ICE_POWER_S4].TWidth = 32; // Ice Flower\n    NPCTraits[NPCID_ICE_POWER_S4].THeight = 32;\n    NPCTraits[NPCID_ICE_POWER_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_MINIBOSS].TWidth = 60; // Big Koopa\n    NPCTraits[NPCID_MINIBOSS].THeight = 54;\n    NPCTraits[NPCID_MINIBOSS].WidthGFX = 68;\n    NPCTraits[NPCID_MINIBOSS].HeightGFX = 54;\n    NPCTraits[NPCID_MINIBOSS].FrameOffsetY = 2;\n    NPCTraits[NPCID_MINIBOSS].NoYoshi = true;\n    NPCTraits[NPCID_GOALORB_S3].TWidth = 32; // Boss Exit\n    NPCTraits[NPCID_GOALORB_S3].THeight = 32;\n    NPCTraits[NPCID_BULLET].TWidth = 32; // Bullet Bill\n    NPCTraits[NPCID_BULLET].THeight = 28;\n    NPCTraits[NPCID_BIG_BULLET].TWidth = 128; // Giant Bullet Bill\n    NPCTraits[NPCID_BIG_BULLET].THeight = 128;\n    NPCTraits[NPCID_BIG_BULLET].NoYoshi = true;\n    NPCTraits[NPCID_BLU_GUY].TWidth = 32; // Red Shy guy\n    NPCTraits[NPCID_BLU_GUY].THeight = 32;\n    NPCTraits[NPCID_RED_GUY].TWidth = 32; // Blue Shy guy\n    NPCTraits[NPCID_RED_GUY].THeight = 32;\n    NPCTraits[NPCID_STACKER].TWidth = 32; // Cactus Thing\n    NPCTraits[NPCID_STACKER].THeight = 32;\n    NPCTraits[NPCID_CANNONENEMY].TWidth = 32; // Bullet Bill Shooter\n    NPCTraits[NPCID_CANNONENEMY].THeight = 32;\n    NPCTraits[NPCID_CANNONENEMY].NoYoshi = true;\n    NPCTraits[NPCID_CANNONITEM].TWidth = 32; // Bullet Bill Gun\n    NPCTraits[NPCID_CANNONITEM].THeight = 32;\n    NPCTraits[NPCID_CANNONITEM].FrameOffsetY = 2;\n    NPCTraits[NPCID_GLASS_TURTLE].TWidth = 32; // Hard thing\n    NPCTraits[NPCID_GLASS_TURTLE].THeight = 32;\n    NPCTraits[NPCID_GLASS_TURTLE].FrameOffsetY = 2;\n    NPCTraits[NPCID_GLASS_SHELL].TWidth = 32; // Hard Thing shell\n    NPCTraits[NPCID_GLASS_SHELL].THeight = 32;\n    NPCTraits[NPCID_GLASS_SHELL].FrameOffsetY = 2;\n    NPCTraits[NPCID_JUMPER_S3].TWidth = 32; // Bouncy Start Thing\n    NPCTraits[NPCID_JUMPER_S3].THeight = 32;\n    NPCTraits[NPCID_SPRING].TWidth = 32; // Spring\n    NPCTraits[NPCID_SPRING].THeight = 32;\n    NPCTraits[NPCID_SPRING].WidthGFX = 32;\n    NPCTraits[NPCID_SPRING].HeightGFX = 32;\n    NPCTraits[NPCID_SPRING].FrameOffsetY = 2;\n    NPCTraits[NPCID_UNDER_FODDER].TWidth = 32; // Grey goomba\n    NPCTraits[NPCID_UNDER_FODDER].THeight = 32;\n    NPCTraits[NPCID_RED_FISH_S1].TWidth = 32; // Red Jumping Fish\n    NPCTraits[NPCID_RED_FISH_S1].THeight = 32;\n    NPCTraits[NPCID_HEAVY_THROWER].TWidth = 32; // Hammer Bro\n    NPCTraits[NPCID_HEAVY_THROWER].THeight = 48;\n    NPCTraits[NPCID_HEAVY_THROWER].FrameOffsetY = 2;\n    NPCTraits[NPCID_HEAVY_THROWN].TWidth = 32; // Hammer\n    NPCTraits[NPCID_HEAVY_THROWN].THeight = 32;\n    NPCTraits[NPCID_HEAVY_THROWN].NoYoshi = true;\n    NPCTraits[NPCID_KEY].TWidth = 32; // Key\n    NPCTraits[NPCID_KEY].THeight = 32;\n    NPCTraits[NPCID_KEY].FrameOffsetY = 1;\n    NPCTraits[NPCID_COIN_SWITCH].TWidth = 32; // P Switch\n    NPCTraits[NPCID_COIN_SWITCH].THeight = 32;\n    NPCTraits[NPCID_COIN_SWITCH].FrameOffsetY = 2;\n    NPCTraits[NPCID_TIME_SWITCH].TWidth = 32; // P Switch Time\n    NPCTraits[NPCID_TIME_SWITCH].THeight = 32;\n    NPCTraits[NPCID_TIME_SWITCH].FrameOffsetY = 2;\n    NPCTraits[NPCID_TNT].TWidth = 32; // Push down thing\n    NPCTraits[NPCID_TNT].THeight = 32;\n    NPCTraits[NPCID_TNT].FrameOffsetY = 2;\n    NPCTraits[NPCID_COIN_S4].TWidth = 24; // SMW Coin\n    NPCTraits[NPCID_COIN_S4].THeight = 32;\n    NPCTraits[NPCID_COIN_5].TWidth = 24; // SMW Blue Coin\n    NPCTraits[NPCID_COIN_5].THeight = 32;\n    NPCTraits[NPCID_LEAF_POWER].TWidth = 32; // Leaf\n    NPCTraits[NPCID_LEAF_POWER].THeight = 32;\n    NPCTraits[NPCID_GRN_BOOT].TWidth = 32; // Goombas Shoe\n    NPCTraits[NPCID_GRN_BOOT].THeight = 32;\n    NPCTraits[NPCID_GRN_BOOT].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_BOOT].FrameOffsetY = 2;\n    NPCTraits[NPCID_BLU_BOOT].FrameOffsetY = 2;\n\n    NPCTraits[NPCID_SPIKY_S4].TWidth = 32; // Spiney\n    NPCTraits[NPCID_SPIKY_S4].THeight = 32;\n    NPCTraits[NPCID_SPIKY_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_SPIKY_BALL_S4].TWidth = 32; // Falling Spiney\n    NPCTraits[NPCID_SPIKY_BALL_S4].THeight = 32;\n    NPCTraits[NPCID_SPIKY_BALL_S4].FrameOffsetY = 2;\n\n    NPCTraits[NPCID_SPIKY_S3].TWidth = 32; // Spiney\n    NPCTraits[NPCID_SPIKY_S3].THeight = 32;\n    NPCTraits[NPCID_SPIKY_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_STONE_S3].TWidth = 48; // Thwomp\n    NPCTraits[NPCID_STONE_S3].THeight = 64;\n    NPCTraits[NPCID_STONE_S3].NoYoshi = true;\n    NPCTraits[NPCID_GHOST_S3].TWidth = 32; // Boo\n    NPCTraits[NPCID_GHOST_S3].THeight = 32;\n    NPCTraits[NPCID_GHOST_S3].NoYoshi = true;\n    NPCTraits[NPCID_SPIT_BOSS].TWidth = 32; // Birdo\n    NPCTraits[NPCID_SPIT_BOSS].THeight = 60;\n    NPCTraits[NPCID_SPIT_BOSS].WidthGFX = 40;\n    NPCTraits[NPCID_SPIT_BOSS].HeightGFX = 72;\n    NPCTraits[NPCID_SPIT_BOSS].FrameOffsetY = 2;\n    NPCTraits[NPCID_SPIT_BOSS].FrameOffsetX = 3;\n    NPCTraits[NPCID_SPIT_BOSS].NoYoshi = true;\n    NPCTraits[NPCID_SPIT_BOSS_BALL].TWidth = 32; // egg\n    NPCTraits[NPCID_SPIT_BOSS_BALL].THeight = 24;\n    NPCTraits[NPCID_GOALORB_S2].TWidth = 32; // smb2 exit(birdo)\n    NPCTraits[NPCID_GOALORB_S2].THeight = 32;\n    NPCTraits[NPCID_GHOST_FAST].TWidth = 32; // ghost 1\n    NPCTraits[NPCID_GHOST_FAST].THeight = 32;\n    NPCTraits[NPCID_GHOST_FAST].NoYoshi = true;\n    NPCTraits[NPCID_GHOST_S4].TWidth = 32; // ghost 2\n    NPCTraits[NPCID_GHOST_S4].THeight = 32;\n    NPCTraits[NPCID_GHOST_S4].NoYoshi = true;\n    NPCTraits[NPCID_BIG_GHOST].TWidth = 128; // big ghost\n    NPCTraits[NPCID_BIG_GHOST].THeight = 120;\n    NPCTraits[NPCID_BIG_GHOST].WidthGFX = 140;\n    NPCTraits[NPCID_BIG_GHOST].HeightGFX = 128;\n    NPCTraits[NPCID_BIG_GHOST].NoYoshi = true;\n    NPCTraits[NPCID_SLIDE_BLOCK].TWidth = 32; // ice block\n    NPCTraits[NPCID_SLIDE_BLOCK].THeight = 32;\n    NPCTraits[NPCID_FALL_BLOCK_RED].TWidth = 32; // falling block\n    NPCTraits[NPCID_FALL_BLOCK_RED].THeight = 32;\n    NPCTraits[NPCID_FALL_BLOCK_RED].NoYoshi = true;\n    NPCTraits[NPCID_FALL_BLOCK_BROWN].NoYoshi = true;\n    NPCTraits[NPCID_SPIKY_THROWER].TWidth = 32; // lakitu\n    NPCTraits[NPCID_SPIKY_THROWER].THeight = 48;\n    NPCTraits[NPCID_SPIKY_THROWER].WidthGFX = 32;\n    NPCTraits[NPCID_SPIKY_THROWER].HeightGFX = 64;\n    NPCTraits[NPCID_ITEM_THROWER].TWidth = 40; // smw lakitu\n    NPCTraits[NPCID_ITEM_THROWER].THeight = 48;\n    NPCTraits[NPCID_ITEM_THROWER].FrameOffsetY = 6;\n    NPCTraits[NPCID_ITEM_THROWER].WidthGFX = 56;\n    NPCTraits[NPCID_ITEM_THROWER].HeightGFX = 72;\n\n    NPCTraits[NPCID_SPIKY_BALL_S3].TWidth = 32; // unripe spiney\n    NPCTraits[NPCID_SPIKY_BALL_S3].THeight = 32;\n    NPCTraits[NPCID_TOOTHYPIPE].TWidth = 32; // killer pipe\n    NPCTraits[NPCID_TOOTHYPIPE].THeight = 32;\n    NPCTraits[NPCID_TOOTHYPIPE].FrameOffsetY = 2;\n    NPCTraits[NPCID_TOOTHY].TWidth = 48; // killer plant\n    NPCTraits[NPCID_TOOTHY].THeight = 32;\n    NPCTraits[NPCID_TOOTHY].FrameOffsetY = 2;\n    NPCTraits[NPCID_TOOTHY].NoYoshi = true;\n    NPCTraits[NPCID_BOTTOM_PLANT].TWidth = 32; // down piranha plant\n    NPCTraits[NPCID_BOTTOM_PLANT].THeight = 64;\n    NPCTraits[NPCID_SIDE_PLANT].TWidth = 48; // left.right piranha plant\n    NPCTraits[NPCID_SIDE_PLANT].THeight = 32;\n    NPCTraits[NPCID_CRAB].TWidth = 32; // mr crabs\n    NPCTraits[NPCID_CRAB].THeight = 32;\n    NPCTraits[NPCID_CRAB].FrameOffsetY = 2;\n    NPCTraits[NPCID_FLY].TWidth = 32; // bee thing\n    NPCTraits[NPCID_FLY].THeight = 32;\n    NPCTraits[NPCID_FLY].FrameOffsetY = 2;\n    NPCTraits[NPCID_EXT_TURTLE].TWidth = 32; // nekkid koopa\n    NPCTraits[NPCID_EXT_TURTLE].THeight = 32;\n    NPCTraits[NPCID_EXT_TURTLE].FrameOffsetY = 2;\n    NPCTraits[NPCID_VEHICLE].TWidth = 128; // koopa clown car\n    NPCTraits[NPCID_VEHICLE].THeight = 128;\n    NPCTraits[NPCID_VEHICLE].NoYoshi = true;\n    NPCTraits[NPCID_CONVEYOR].TWidth = 32; // smb3 conveyer belt\n    NPCTraits[NPCID_CONVEYOR].THeight = 32;\n    NPCTraits[NPCID_CONVEYOR].NoYoshi = true;\n    NPCTraits[NPCID_METALBARREL].TWidth = 32; // smb3 barrel\n    NPCTraits[NPCID_METALBARREL].THeight = 32;\n    NPCTraits[NPCID_METALBARREL].NoYoshi = true;\n    NPCTraits[NPCID_YELSWITCH_FODDER].TWidth = 32; // purple goomba\n    NPCTraits[NPCID_YELSWITCH_FODDER].THeight = 32;\n    NPCTraits[NPCID_YELSWITCH_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_YEL_PLATFORM].TWidth = 96; // purple platform\n    NPCTraits[NPCID_YEL_PLATFORM].THeight = 32;\n    NPCTraits[NPCID_YEL_PLATFORM].NoYoshi = true;\n    NPCTraits[NPCID_BLUSWITCH_FODDER].TWidth = 32; // blue goomba\n    NPCTraits[NPCID_BLUSWITCH_FODDER].THeight = 32;\n    NPCTraits[NPCID_BLUSWITCH_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_BLU_PLATFORM].TWidth = 96; // blue platform\n    NPCTraits[NPCID_BLU_PLATFORM].THeight = 32;\n    NPCTraits[NPCID_BLU_PLATFORM].NoYoshi = true;\n    NPCTraits[NPCID_GRNSWITCH_FODDER].TWidth = 32; // green goomba\n    NPCTraits[NPCID_GRNSWITCH_FODDER].THeight = 32;\n    NPCTraits[NPCID_GRNSWITCH_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_GRN_PLATFORM].TWidth = 96; // green platform\n    NPCTraits[NPCID_GRN_PLATFORM].THeight = 32;\n    NPCTraits[NPCID_GRN_PLATFORM].NoYoshi = true;\n    NPCTraits[NPCID_REDSWITCH_FODDER].TWidth = 32; // red goomba\n    NPCTraits[NPCID_REDSWITCH_FODDER].THeight = 32;\n    NPCTraits[NPCID_REDSWITCH_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_PLATFORM].TWidth = 96; // red platform\n    NPCTraits[NPCID_RED_PLATFORM].THeight = 32;\n    NPCTraits[NPCID_RED_PLATFORM].NoYoshi = true;\n    NPCTraits[NPCID_HPIPE_SHORT].TWidth = 128; // grey pipe x\n    NPCTraits[NPCID_HPIPE_SHORT].THeight = 32;\n    NPCTraits[NPCID_HPIPE_SHORT].NoYoshi = true;\n    NPCTraits[NPCID_HPIPE_LONG].TWidth = 256; // big grey pipe x\n    NPCTraits[NPCID_HPIPE_LONG].THeight = 32;\n    NPCTraits[NPCID_HPIPE_LONG].NoYoshi = true;\n    NPCTraits[NPCID_VPIPE_SHORT].TWidth = 32; // grey pipe y\n    NPCTraits[NPCID_VPIPE_SHORT].THeight = 128; // NOTE: was set to 127 until v1.3.7.2, due to different semantics for the implicit cast of 127.9 to int between VB6 and C++\n    NPCTraits[NPCID_VPIPE_SHORT].NoYoshi = true;\n    NPCTraits[NPCID_VPIPE_LONG].TWidth = 32; // big grey pipe y\n    NPCTraits[NPCID_VPIPE_LONG].THeight = 256; // NOTE: was set to 255 until v1.3.7.2, due to different semantics for the implicit cast of 127.9 to int between VB6 and C++\n    NPCTraits[NPCID_VPIPE_LONG].NoYoshi = true;\n    NPCTraits[NPCID_BIG_FODDER].TWidth = 48; // giant goomba\n    NPCTraits[NPCID_BIG_FODDER].THeight = 46;\n    NPCTraits[NPCID_BIG_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_BIG_TURTLE].TWidth = 48; // giant green koopa\n    NPCTraits[NPCID_BIG_TURTLE].THeight = 48;\n    NPCTraits[NPCID_BIG_TURTLE].FrameOffsetY = 2;\n    NPCTraits[NPCID_BIG_TURTLE].WidthGFX = 48;\n    NPCTraits[NPCID_BIG_TURTLE].HeightGFX = 62;\n    NPCTraits[NPCID_BIG_SHELL].TWidth = 44; // giant green shell\n    NPCTraits[NPCID_BIG_SHELL].THeight = 44;\n    NPCTraits[NPCID_BIG_SHELL].FrameOffsetY = 2;\n    NPCTraits[NPCID_BIG_PLANT].TWidth = 48; // giant pirhana plant\n    NPCTraits[NPCID_BIG_PLANT].THeight = 64;\n    NPCTraits[NPCID_BIG_PLANT].FrameOffsetY = 2;\n\n    NPCTraits[NPCID_LONG_PLANT_UP].TWidth = 48; // gianter pirhana plant\n    NPCTraits[NPCID_LONG_PLANT_UP].THeight = 128;\n    NPCTraits[NPCID_LONG_PLANT_UP].FrameOffsetY = 2;\n\n    NPCTraits[NPCID_LONG_PLANT_DOWN].TWidth = 48; // gianter pirhana plant\n    NPCTraits[NPCID_LONG_PLANT_DOWN].THeight = 128;\n\n    NPCTraits[NPCID_CIVILIAN_SCARED].TWidth = 38; // toad\n    NPCTraits[NPCID_CIVILIAN_SCARED].THeight = 54;\n    NPCTraits[NPCID_CIVILIAN_SCARED].FrameOffsetY = 2;\n    NPCTraits[NPCID_CIVILIAN_SCARED].WidthGFX = 38;\n    NPCTraits[NPCID_CIVILIAN_SCARED].HeightGFX = 58;\n    // NPCIsToad(NPCID_CIVILIAN_SCARED) = true;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S3].TWidth = 32; // flying green koopa\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S3].THeight = 32;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S3].WidthGFX = 32;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S3].HeightGFX = 56;\n    // NPCIsAParaTroopa(NPCID_GRN_FLY_TURTLE_S3) = true;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S3].TWidth = 32; // flying red koopa\n    NPCTraits[NPCID_RED_FLY_TURTLE_S3].THeight = 32;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S3].WidthGFX = 32;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S3].HeightGFX = 56;\n    // NPCIsAParaTroopa(NPCID_RED_FLY_TURTLE_S3) = true;\n    NPCTraits[NPCID_JUMPER_S4].TWidth = 32; // black ninja\n    NPCTraits[NPCID_JUMPER_S4].THeight = 32;\n    NPCTraits[NPCID_BIG_SHELL].FrameOffsetY = 2;\n    NPCTraits[NPCID_TANK_TREADS].TWidth = 128; // tank treads\n    NPCTraits[NPCID_TANK_TREADS].THeight = 32;\n    NPCTraits[NPCID_TANK_TREADS].NoYoshi = true;\n    NPCTraits[NPCID_SHORT_WOOD].TWidth = 64; // tank parts\n    NPCTraits[NPCID_SHORT_WOOD].THeight = 32;\n    NPCTraits[NPCID_SHORT_WOOD].NoYoshi = true;\n    NPCTraits[NPCID_LONG_WOOD].TWidth = 128; // tank parts\n    NPCTraits[NPCID_LONG_WOOD].THeight = 32;\n    NPCTraits[NPCID_LONG_WOOD].NoYoshi = true;\n    NPCTraits[NPCID_SLANT_WOOD_L].TWidth = 128; // tank parts\n    NPCTraits[NPCID_SLANT_WOOD_L].THeight = 32;\n    NPCTraits[NPCID_SLANT_WOOD_L].NoYoshi = true;\n    NPCTraits[NPCID_SLANT_WOOD_R].TWidth = 128; // tank parts\n    NPCTraits[NPCID_SLANT_WOOD_R].THeight = 32;\n    NPCTraits[NPCID_SLANT_WOOD_R].NoYoshi = true;\n    NPCTraits[NPCID_SLANT_WOOD_M].TWidth = 256; // tank parts\n    NPCTraits[NPCID_SLANT_WOOD_M].THeight = 32;\n    NPCTraits[NPCID_SLANT_WOOD_M].NoYoshi = true;\n    NPCTraits[NPCID_STATUE_S3].TWidth = 32; // bowser statue\n    NPCTraits[NPCID_STATUE_S3].THeight = 64;\n    NPCTraits[NPCID_STATUE_S3].NoYoshi = true;\n    NPCTraits[NPCID_STATUE_FIRE].TWidth = 32; // statue fireball\n    NPCTraits[NPCID_STATUE_FIRE].THeight = 16;\n    NPCTraits[NPCID_STATUE_FIRE].NoYoshi = true;\n    NPCTraits[NPCID_VILLAIN_S3].TWidth = 62; // smb3 bowser\n    NPCTraits[NPCID_VILLAIN_S3].THeight = 80;\n    NPCTraits[NPCID_VILLAIN_S3].WidthGFX = 64;\n    NPCTraits[NPCID_VILLAIN_S3].HeightGFX = 80;\n    NPCTraits[NPCID_VILLAIN_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_VILLAIN_S3].NoYoshi = true;\n    NPCTraits[NPCID_VILLAIN_FIRE].TWidth = 48; // smb3 bowser fireball\n    NPCTraits[NPCID_VILLAIN_FIRE].THeight = 32;\n    NPCTraits[NPCID_VILLAIN_FIRE].NoYoshi = true;\n    NPCTraits[NPCID_COIN_S1].TWidth = 20; // smb1 coin\n    NPCTraits[NPCID_COIN_S1].THeight = 32;\n    NPCTraits[NPCID_FODDER_S1].TWidth = 32; // smb1 brown goomba\n    NPCTraits[NPCID_FODDER_S1].THeight = 32;\n    NPCTraits[NPCID_FODDER_S1].FrameOffsetY = 2;\n    NPCTraits[NPCID_LIFE_S3].TWidth = 32; // 1 up\n    NPCTraits[NPCID_LIFE_S3].THeight = 32;\n    NPCTraits[NPCID_LIFE_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_LIFE_S3].IsAHit1Block = true;\n    NPCTraits[NPCID_ITEM_BURIED].TWidth = 32; // grab grass\n    NPCTraits[NPCID_ITEM_BURIED].THeight = 16;\n    NPCTraits[NPCID_ITEM_BURIED].FrameOffsetY = -16;\n    NPCTraits[NPCID_VEGGIE_1].TWidth = 32; // turnip\n    NPCTraits[NPCID_VEGGIE_1].THeight = 32;\n    // NPCIsVeggie(NPCID_VEGGIE_1) = true;\n    NPCTraits[NPCID_PLANT_S1].TWidth = 32; // SMB1 Plant\n    NPCTraits[NPCID_PLANT_S1].THeight = 48;\n    NPCTraits[NPCID_CIVILIAN].TWidth = 32; // Inert Toad\n    NPCTraits[NPCID_CIVILIAN].THeight = 54;\n\n    NPCTraits[NPCID_CHAR3].TWidth = 32; // Peach\n    NPCTraits[NPCID_CHAR3].THeight = 64;\n    NPCTraits[NPCID_CHAR3].FrameOffsetY = 2;\n    NPCTraits[NPCID_CIVILIAN].FrameOffsetY = 2;\n    // NPCIsToad(NPCID_CIVILIAN) = true;\n    // NPCIsToad(NPCID_CHAR3) = true;\n    NPCTraits[NPCID_PET_GREEN].TWidth = 32; // Green Yoshi\n    NPCTraits[NPCID_PET_GREEN].THeight = 32;\n    NPCTraits[NPCID_PET_GREEN].WidthGFX = 74;\n    NPCTraits[NPCID_PET_GREEN].HeightGFX = 56;\n    NPCTraits[NPCID_PET_GREEN].FrameOffsetY = 2;\n    NPCTraits[NPCID_PET_GREEN].NoYoshi = true;\n    NPCTraits[NPCID_ITEM_POD].TWidth = 32; // Yoshi Egg\n    NPCTraits[NPCID_ITEM_POD].THeight = 32;\n    NPCTraits[NPCID_STAR_EXIT].TWidth = 32; // SMB3 Star\n    NPCTraits[NPCID_STAR_EXIT].THeight = 32;\n    NPCTraits[NPCID_PET_BLUE].TWidth = 32; // Blue Yoshi\n    NPCTraits[NPCID_PET_BLUE].THeight = 32;\n    NPCTraits[NPCID_PET_BLUE].WidthGFX = 74;\n    NPCTraits[NPCID_PET_BLUE].HeightGFX = 56;\n    NPCTraits[NPCID_PET_BLUE].FrameOffsetY = 2;\n    NPCTraits[NPCID_PET_YELLOW].TWidth = 32; // Yellow Yoshi\n    NPCTraits[NPCID_PET_YELLOW].THeight = 32;\n    NPCTraits[NPCID_PET_YELLOW].WidthGFX = 74;\n    NPCTraits[NPCID_PET_YELLOW].HeightGFX = 56;\n    NPCTraits[NPCID_PET_YELLOW].FrameOffsetY = 2;\n    NPCTraits[NPCID_PET_RED].TWidth = 32; // Red Yoshi\n    NPCTraits[NPCID_PET_RED].THeight = 32;\n    NPCTraits[NPCID_PET_RED].WidthGFX = 74;\n    NPCTraits[NPCID_PET_RED].HeightGFX = 56;\n    NPCTraits[NPCID_PET_RED].FrameOffsetY = 2;\n    NPCTraits[NPCID_CHAR2].TWidth = 28; // Luigi\n    NPCTraits[NPCID_CHAR2].THeight = 62;\n    NPCTraits[NPCID_CHAR2].FrameOffsetY = 2;\n    // NPCIsToad(NPCID_CHAR2) = true;\n    NPCTraits[NPCID_CHAR5].TWidth = 32; // Link\n    NPCTraits[NPCID_CHAR5].THeight = 64;\n    NPCTraits[NPCID_CHAR5].FrameOffsetY = 2;\n    // NPCIsToad(NPCID_CHAR5) = true;\n    NPCTraits[NPCID_RED_COIN].TWidth = 28; // SMB3 Red Coin\n    NPCTraits[NPCID_RED_COIN].THeight = 32;\n    NPCTraits[NPCID_PLATFORM_S3].TWidth = 96; // SMB3 Platform\n    NPCTraits[NPCID_PLATFORM_S3].THeight = 32;\n    NPCTraits[NPCID_PLATFORM_S3].NoYoshi = true;\n    NPCTraits[NPCID_CHECKER_PLATFORM].TWidth = 128; // SMW Falling Platform\n    NPCTraits[NPCID_CHECKER_PLATFORM].THeight = 22;\n    NPCTraits[NPCID_CHECKER_PLATFORM].NoYoshi = true;\n    NPCTraits[NPCID_PLATFORM_S1].TWidth = 128; // SMB Platform\n    NPCTraits[NPCID_PLATFORM_S1].THeight = 16;\n    NPCTraits[NPCID_PLATFORM_S1].NoYoshi = true;\n    NPCTraits[NPCID_PINK_CIVILIAN].TWidth = 24; // Bob-omb buddy\n    NPCTraits[NPCID_PINK_CIVILIAN].THeight = 38;\n    NPCTraits[NPCID_PINK_CIVILIAN].WidthGFX = 48;\n    NPCTraits[NPCID_PINK_CIVILIAN].HeightGFX = 38;\n    NPCTraits[NPCID_PINK_CIVILIAN].FrameOffsetY = 2;\n    // NPCIsToad(NPCID_PINK_CIVILIAN) = true;\n    NPCTraits[NPCID_PET_FIRE].TWidth = 32; // Yoshi Fireball\n    NPCTraits[NPCID_PET_FIRE].THeight = 32;\n    NPCTraits[NPCID_PET_FIRE].NoYoshi = true;\n    NPCTraits[NPCID_GRN_TURTLE_S4].TWidth = 32; // SMW Green Koopa\n    NPCTraits[NPCID_GRN_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_GRN_TURTLE_S4].WidthGFX = 32;\n    NPCTraits[NPCID_GRN_TURTLE_S4].HeightGFX = 54;\n    NPCTraits[NPCID_GRN_TURTLE_S4].FrameOffsetY = 2;\n    // NPCDefaultMovement(NPCID_GRN_TURTLE_S4) = true;\n    NPCTraits[NPCID_RED_TURTLE_S4].TWidth = 32; // SMW Red Koopa\n    NPCTraits[NPCID_RED_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_RED_TURTLE_S4].WidthGFX = 32;\n    NPCTraits[NPCID_RED_TURTLE_S4].HeightGFX = 54;\n    NPCTraits[NPCID_RED_TURTLE_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_TURTLE_S4].TurnsAtCliffs = true;\n    // NPCDefaultMovement(NPCID_RED_TURTLE_S4) = true;\n    NPCTraits[NPCID_BLU_TURTLE_S4].TWidth = 32; // SMW Blue Koopa\n    NPCTraits[NPCID_BLU_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_BLU_TURTLE_S4].WidthGFX = 32;\n    NPCTraits[NPCID_BLU_TURTLE_S4].HeightGFX = 54;\n    NPCTraits[NPCID_BLU_TURTLE_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_BLU_TURTLE_S4].TurnsAtCliffs = true;\n    // NPCDefaultMovement(NPCID_BLU_TURTLE_S4) = true;\n    NPCTraits[NPCID_YEL_TURTLE_S4].TWidth = 32; // SMW Yellow Koopa\n    NPCTraits[NPCID_YEL_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_YEL_TURTLE_S4].WidthGFX = 32;\n    NPCTraits[NPCID_YEL_TURTLE_S4].HeightGFX = 54;\n    NPCTraits[NPCID_YEL_TURTLE_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_YEL_TURTLE_S4].TurnsAtCliffs = true;\n    // NPCDefaultMovement(NPCID_YEL_TURTLE_S4) = true;\n    NPCTraits[NPCID_GRN_SHELL_S4].TWidth = 32; // SMW Green Shell\n    NPCTraits[NPCID_GRN_SHELL_S4].THeight = 32;\n    NPCTraits[NPCID_GRN_SHELL_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_GRN_SHELL_S4].IsAShell = true;\n    NPCTraits[NPCID_RED_SHELL_S4].TWidth = 32; // SMW Red Shell\n    NPCTraits[NPCID_RED_SHELL_S4].THeight = 32;\n    NPCTraits[NPCID_RED_SHELL_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_SHELL_S4].IsAShell = true;\n    NPCTraits[NPCID_BLU_SHELL_S4].TWidth = 32; // SMW Blue Shell\n    NPCTraits[NPCID_BLU_SHELL_S4].THeight = 32;\n    NPCTraits[NPCID_BLU_SHELL_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_BLU_SHELL_S4].IsAShell = true;\n    NPCTraits[NPCID_YEL_SHELL_S4].TWidth = 32; // SMW Yellow Shell\n    NPCTraits[NPCID_YEL_SHELL_S4].THeight = 32;\n    NPCTraits[NPCID_YEL_SHELL_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_YEL_SHELL_S4].IsAShell = true;\n    NPCTraits[NPCID_GRN_HIT_TURTLE_S4].TWidth = 32; // SMW Green Beach Koopa\n    NPCTraits[NPCID_GRN_HIT_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_GRN_HIT_TURTLE_S4].FrameOffsetY = 2;\n    // NPCDefaultMovement(NPCID_GRN_HIT_TURTLE_S4) = true;\n    NPCTraits[NPCID_RED_HIT_TURTLE_S4].TWidth = 32; // SMW Red Beach Koopa\n    NPCTraits[NPCID_RED_HIT_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_RED_HIT_TURTLE_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_RED_HIT_TURTLE_S4].TurnsAtCliffs = true;\n    // NPCDefaultMovement(NPCID_RED_HIT_TURTLE_S4) = true;\n    NPCTraits[NPCID_BLU_HIT_TURTLE_S4].TWidth = 32; // SMW Blue Beach Koopa\n    NPCTraits[NPCID_BLU_HIT_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_BLU_HIT_TURTLE_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_BLU_HIT_TURTLE_S4].TurnsAtCliffs = true;\n    // NPCDefaultMovement(NPCID_BLU_HIT_TURTLE_S4) = true;\n    NPCTraits[NPCID_YEL_HIT_TURTLE_S4].TWidth = 32; // SMW Yellow Beach Koopa\n    NPCTraits[NPCID_YEL_HIT_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_YEL_HIT_TURTLE_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_YEL_HIT_TURTLE_S4].TurnsAtCliffs = true;\n    // NPCDefaultMovement(NPCID_YEL_HIT_TURTLE_S4) = true;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S4].TWidth = 32; // SMW Green Para-Koopa\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S4].WidthGFX = 56;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S4].HeightGFX = 56;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S4].FrameOffsetY = 2;\n    // NPCIsAParaTroopa(NPCID_GRN_FLY_TURTLE_S4) = true;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S4].TWidth = 32; // SMW Red Para-Koopa\n    NPCTraits[NPCID_RED_FLY_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S4].WidthGFX = 56;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S4].HeightGFX = 56;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S4].FrameOffsetY = 2;\n    // NPCIsAParaTroopa(NPCID_RED_FLY_TURTLE_S4) = true;\n    NPCTraits[NPCID_BLU_FLY_TURTLE_S4].TWidth = 32; // SMW Blue Para-Koopa\n    NPCTraits[NPCID_BLU_FLY_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_BLU_FLY_TURTLE_S4].WidthGFX = 56;\n    NPCTraits[NPCID_BLU_FLY_TURTLE_S4].HeightGFX = 56;\n    NPCTraits[NPCID_BLU_FLY_TURTLE_S4].FrameOffsetY = 2;\n    // NPCIsAParaTroopa(NPCID_BLU_FLY_TURTLE_S4) = true;\n    NPCTraits[NPCID_YEL_FLY_TURTLE_S4].TWidth = 32; // SMW Yellow Para-Koopa\n    NPCTraits[NPCID_YEL_FLY_TURTLE_S4].THeight = 32;\n    NPCTraits[NPCID_YEL_FLY_TURTLE_S4].WidthGFX = 56;\n    NPCTraits[NPCID_YEL_FLY_TURTLE_S4].HeightGFX = 56;\n    NPCTraits[NPCID_YEL_FLY_TURTLE_S4].FrameOffsetY = 2;\n    // NPCIsAParaTroopa(NPCID_YEL_FLY_TURTLE_S4) = true;\n    NPCTraits[NPCID_KNIGHT].TWidth = 36; // Rat Head\n    NPCTraits[NPCID_KNIGHT].THeight = 56;\n    NPCTraits[NPCID_KNIGHT].WidthGFX = 36;\n    NPCTraits[NPCID_KNIGHT].HeightGFX = 66;\n    NPCTraits[NPCID_KNIGHT].FrameOffsetY = 2;\n    NPCTraits[NPCID_WALK_PLANT].JumpHurt = true;\n    NPCTraits[NPCID_WALK_PLANT].NoFireBall = true;\n    NPCTraits[NPCID_BULLET].NoFireBall = true;\n    // NPCDefaultMovement(NPCID_KNIGHT) = true;\n    NPCTraits[NPCID_KNIGHT].NoYoshi = true;\n    NPCTraits[NPCID_BLU_SLIME].TWidth = 32; // Blue Bot\n    NPCTraits[NPCID_BLU_SLIME].THeight = 34;\n    NPCTraits[NPCID_BLU_SLIME].WidthGFX = 32;\n    NPCTraits[NPCID_BLU_SLIME].HeightGFX = 26;\n    // NPCIsABot(NPCID_BLU_SLIME) = true;\n    NPCTraits[NPCID_CYAN_SLIME].TWidth = 32; // Cyan Bot\n    NPCTraits[NPCID_CYAN_SLIME].THeight = 34;\n    NPCTraits[NPCID_CYAN_SLIME].WidthGFX = 32;\n    NPCTraits[NPCID_CYAN_SLIME].HeightGFX = 26;\n    // NPCIsABot(NPCID_CYAN_SLIME) = true;\n    NPCTraits[NPCID_RED_SLIME].TWidth = 32; // Red Bot\n    NPCTraits[NPCID_RED_SLIME].THeight = 34;\n    NPCTraits[NPCID_RED_SLIME].WidthGFX = 32;\n    NPCTraits[NPCID_RED_SLIME].HeightGFX = 26;\n    NPCTraits[NPCID_BIRD].TWidth = 32; // SMB2 Jumpy guy\n    NPCTraits[NPCID_BIRD].THeight = 32;\n    NPCTraits[NPCID_BIRD].CanWalkOn = true;\n    NPCTraits[NPCID_BIRD].GrabFromTop = true;\n    // NPCDefaultMovement(NPCID_BIRD) = true;\n    NPCTraits[NPCID_RED_SPIT_GUY].TWidth = 32; // Red Sniffit\n    NPCTraits[NPCID_RED_SPIT_GUY].THeight = 32;\n    NPCTraits[NPCID_RED_SPIT_GUY].CanWalkOn = true;\n    NPCTraits[NPCID_RED_SPIT_GUY].GrabFromTop = true;\n    // NPCDefaultMovement(NPCID_RED_SPIT_GUY) = true;\n    NPCTraits[NPCID_BLU_SPIT_GUY].TWidth = 32; // Blue Sniffit\n    NPCTraits[NPCID_BLU_SPIT_GUY].THeight = 32;\n    NPCTraits[NPCID_BLU_SPIT_GUY].CanWalkOn = true;\n    NPCTraits[NPCID_BLU_SPIT_GUY].GrabFromTop = true;\n    // NPCDefaultMovement(NPCID_BLU_SPIT_GUY) = true;\n    NPCTraits[NPCID_BLU_SPIT_GUY].TurnsAtCliffs = true;\n    NPCTraits[NPCID_GRY_SPIT_GUY].TWidth = 32; // Grey Sniffit\n    NPCTraits[NPCID_GRY_SPIT_GUY].THeight = 32;\n    NPCTraits[NPCID_GRY_SPIT_GUY].CanWalkOn = true;\n    NPCTraits[NPCID_GRY_SPIT_GUY].GrabFromTop = true;\n    // NPCDefaultMovement(NPCID_GRY_SPIT_GUY) = true;\n    NPCTraits[NPCID_SPIT_GUY_BALL].TWidth = 16; // Bullet\n    NPCTraits[NPCID_SPIT_GUY_BALL].THeight = 16;\n    NPCTraits[NPCID_SPIT_GUY_BALL].NoYoshi = true;\n    NPCTraits[NPCID_SPIT_GUY_BALL].JumpHurt = true;\n    NPCTraits[NPCID_BOMB].TWidth = 24; // SMB2 Bomb\n    NPCTraits[NPCID_BOMB].THeight = 24;\n    NPCTraits[NPCID_BOMB].WidthGFX = 40;\n    NPCTraits[NPCID_BOMB].HeightGFX = 40;\n    NPCTraits[NPCID_BOMB].WontHurt = true;\n    NPCTraits[NPCID_BOMB].IsGrabbable = true;\n    NPCTraits[NPCID_BOMB].GrabFromTop = true;\n    NPCTraits[NPCID_BOMB].CanWalkOn = true;\n    NPCTraits[NPCID_WALK_BOMB_S2].TWidth = 32; // SMB2 Bob-om\n    NPCTraits[NPCID_WALK_BOMB_S2].THeight = 32;\n    NPCTraits[NPCID_WALK_BOMB_S2].GrabFromTop = true;\n    NPCTraits[NPCID_WALK_BOMB_S2].CanWalkOn = true;\n    // NPCDefaultMovement(NPCID_WALK_BOMB_S2) = true;\n    NPCTraits[NPCID_WALK_BOMB_S3].TWidth = 32; // SMB3 Bob-om\n    NPCTraits[NPCID_WALK_BOMB_S3].THeight = 32;\n    NPCTraits[NPCID_WALK_BOMB_S3].FrameOffsetY = 2;\n    // NPCDefaultMovement(NPCID_WALK_BOMB_S3) = true;\n    NPCTraits[NPCID_WALK_BOMB_S3].TurnsAtCliffs = true;\n    NPCTraits[NPCID_LIT_BOMB_S3].TWidth = 32; // SMB3 Bomb\n    NPCTraits[NPCID_LIT_BOMB_S3].THeight = 28;\n    NPCTraits[NPCID_LIT_BOMB_S3].FrameOffsetY = 2;\n    NPCTraits[NPCID_LIT_BOMB_S3].IsGrabbable = true;\n    NPCTraits[NPCID_COIN_S2].TWidth = 28; // SMB 2 Coin\n    NPCTraits[NPCID_COIN_S2].THeight = 32;\n    NPCTraits[NPCID_COIN_S2].IsABonus = true;\n    NPCTraits[NPCID_COIN_S2].IsACoin = true;\n    NPCTraits[NPCID_RING].TWidth = 32; // Sonic Ring\n    NPCTraits[NPCID_RING].THeight = 32;\n    NPCTraits[NPCID_RING].IsABonus = true;\n    NPCTraits[NPCID_RING].IsACoin = true;\n    For(A, NPCID_VEGGIE_2, NPCID_VEGGIE_RANDOM) // Veggies\n    {\n        NPCTraits[A].TWidth = GFXNPC[A].w;\n        NPCTraits[A].THeight = GFXNPC[A].h;\n        NPCTraits[A].CanWalkOn = true;\n        NPCTraits[A].GrabFromTop = true;\n        NPCTraits[A].WontHurt = true;\n        NPCTraits[A].IsGrabbable = true;\n        // NPCIsVeggie(A) = true;\n    }\n    NPCTraits[NPCID_PET_BLACK].TWidth = 32; // Black Yoshi\n    NPCTraits[NPCID_PET_BLACK].THeight = 32;\n    NPCTraits[NPCID_PET_BLACK].WidthGFX = 74;\n    NPCTraits[NPCID_PET_BLACK].HeightGFX = 56;\n    NPCTraits[NPCID_PET_BLACK].FrameOffsetY = 2;\n    NPCTraits[NPCID_PET_BLACK].NoYoshi = true;\n    NPCTraits[NPCID_PET_PURPLE].TWidth = 32; // Purple Yoshi\n    NPCTraits[NPCID_PET_PURPLE].THeight = 32;\n    NPCTraits[NPCID_PET_PURPLE].WidthGFX = 74;\n    NPCTraits[NPCID_PET_PURPLE].HeightGFX = 56;\n    NPCTraits[NPCID_PET_PURPLE].FrameOffsetY = 2;\n    NPCTraits[NPCID_PET_PURPLE].NoYoshi = true;\n    NPCTraits[NPCID_PET_PINK].TWidth = 32; // Pink Yoshi\n    NPCTraits[NPCID_PET_PINK].THeight = 32;\n    NPCTraits[NPCID_PET_PINK].WidthGFX = 74;\n    NPCTraits[NPCID_PET_PINK].HeightGFX = 56;\n    NPCTraits[NPCID_PET_PINK].FrameOffsetY = 2;\n    NPCTraits[NPCID_PET_CYAN].TWidth = 32; // Ice Yoshi\n    NPCTraits[NPCID_PET_CYAN].THeight = 32;\n    NPCTraits[NPCID_PET_CYAN].WidthGFX = 74;\n    NPCTraits[NPCID_PET_CYAN].HeightGFX = 56;\n    NPCTraits[NPCID_PET_CYAN].FrameOffsetY = 2;\n    NPCTraits[NPCID_SIGN].TWidth = 48; // SMW Sign\n    NPCTraits[NPCID_SIGN].THeight = 48;\n    NPCTraits[NPCID_SIGN].NoYoshi = true;\n    NPCTraits[NPCID_SIGN].WontHurt = true;\n    NPCTraits[NPCID_SIGN].NoClipping = true;\n    NPCTraits[NPCID_CARRY_BLOCK_A].TWidth = 32; // SMB2 Mushroom Block\n    NPCTraits[NPCID_CARRY_BLOCK_A].THeight = 32;\n    NPCTraits[NPCID_CARRY_BLOCK_B].TWidth = 32; // SMB2 Mushroom Block\n    NPCTraits[NPCID_CARRY_BLOCK_B].THeight = 32;\n    NPCTraits[NPCID_CARRY_BLOCK_C].TWidth = 32; // SMB2 Mushroom Block\n    NPCTraits[NPCID_CARRY_BLOCK_C].THeight = 32;\n    NPCTraits[NPCID_CARRY_BLOCK_D].TWidth = 32; // SMB2 Mushroom Block\n    NPCTraits[NPCID_CARRY_BLOCK_D].THeight = 32;\n    NPCTraits[NPCID_CARRY_BUDDY].TWidth = 32; // Mr Saturn\n    NPCTraits[NPCID_CARRY_BUDDY].THeight = 42;\n    NPCTraits[NPCID_CARRY_BUDDY].FrameOffsetY = 2;\n    NPCTraits[NPCID_CARRY_BUDDY].JumpHurt = true;\n    NPCTraits[NPCID_LIFT_SAND].TWidth = 32; // Diggable Dirt\n    NPCTraits[NPCID_LIFT_SAND].THeight = 32;\n    NPCTraits[NPCID_ROCKET_WOOD].TWidth = 128; // Airship Rocket\n    NPCTraits[NPCID_ROCKET_WOOD].THeight = 32;\n    NPCTraits[NPCID_ROCKET_WOOD].WidthGFX = 316;\n    NPCTraits[NPCID_ROCKET_WOOD].HeightGFX = 32;\n    NPCTraits[NPCID_ROCKET_WOOD].NoYoshi = true;\n    NPCTraits[NPCID_CARRY_BLOCK_A].TWidth = 32; // SMB2 Mushroom Block\n    NPCTraits[NPCID_CARRY_BLOCK_A].THeight = 32;\n    NPCTraits[NPCID_BRUTE].TWidth = 32; // SMW Rex\n    NPCTraits[NPCID_BRUTE].THeight = 60;\n    NPCTraits[NPCID_BRUTE].WidthGFX = 40;\n    NPCTraits[NPCID_BRUTE].HeightGFX = 64;\n    NPCTraits[NPCID_BRUTE].FrameOffsetY = 2;\n    NPCTraits[NPCID_BRUTE_SQUISHED].TWidth = 32; // SMW Rex Smashed\n    NPCTraits[NPCID_BRUTE_SQUISHED].THeight = 32;\n    NPCTraits[NPCID_BRUTE_SQUISHED].WidthGFX = 32;\n    NPCTraits[NPCID_BRUTE_SQUISHED].HeightGFX = 32;\n    NPCTraits[NPCID_BRUTE_SQUISHED].FrameOffsetY = 2;\n    NPCTraits[NPCID_BIG_GUY].TWidth = 58; // SMW Mega Mole\n    NPCTraits[NPCID_BIG_GUY].THeight = 58;\n    NPCTraits[NPCID_BIG_GUY].WidthGFX = 64;\n    NPCTraits[NPCID_BIG_GUY].HeightGFX = 64;\n    NPCTraits[NPCID_BIG_GUY].FrameOffsetY = 2;\n    NPCTraits[NPCID_CARRY_FODDER].TWidth = 32; // SMW Goomba\n    NPCTraits[NPCID_CARRY_FODDER].THeight = 32;\n    NPCTraits[NPCID_CARRY_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_HIT_CARRY_FODDER].TWidth = 32; // SMW Stomped Goomba Goomba\n    NPCTraits[NPCID_HIT_CARRY_FODDER].THeight = 32;\n    NPCTraits[NPCID_HIT_CARRY_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_FLY_CARRY_FODDER].TWidth = 32; // SMW Para-Goomba\n    NPCTraits[NPCID_FLY_CARRY_FODDER].THeight = 32;\n    NPCTraits[NPCID_FLY_CARRY_FODDER].WidthGFX = 66;\n    NPCTraits[NPCID_FLY_CARRY_FODDER].HeightGFX = 50;\n    NPCTraits[NPCID_FLY_CARRY_FODDER].FrameOffsetY = 2;\n    NPCTraits[NPCID_CHASER].TWidth = 26; // Bully\n    NPCTraits[NPCID_CHASER].THeight = 30;\n    NPCTraits[NPCID_CHASER].WidthGFX = 26;\n    NPCTraits[NPCID_CHASER].HeightGFX = 46;\n    NPCTraits[NPCID_CHASER].FrameOffsetY = 2;\n    NPCTraits[NPCID_STATUE_POWER].TWidth = 32; // Tanooki Suit\n    NPCTraits[NPCID_STATUE_POWER].THeight = 32;\n    NPCTraits[NPCID_STATUE_POWER].FrameOffsetY = 2;\n    NPCTraits[NPCID_STATUE_POWER].IsABonus = true;\n    NPCTraits[NPCID_HEAVY_POWER].TWidth = 32; // Hammer Suit\n    NPCTraits[NPCID_HEAVY_POWER].THeight = 32;\n    NPCTraits[NPCID_HEAVY_POWER].FrameOffsetY = 2;\n    NPCTraits[NPCID_HEAVY_POWER].IsABonus = true;\n\n    NPCTraits[NPCID_CHAR4_HEAVY].WontHurt = true; // Boomerang\n    NPCTraits[NPCID_CHAR4_HEAVY].JumpHurt = true;\n    NPCTraits[NPCID_CHAR4_HEAVY].NoClipping = true;\n    NPCTraits[NPCID_CHAR4_HEAVY].NoYoshi = true;\n    NPCTraits[NPCID_CHAR4_HEAVY].Foreground = true;\n    NPCTraits[NPCID_CHAR4_HEAVY].NoIceBall = true;\n\n    NPCTraits[NPCID_STACKER].NoIceBall = true;\n\n    NPCTraits[NPCID_PLR_HEAVY].TWidth = 16; // Hammer Suit Hammer\n    NPCTraits[NPCID_PLR_HEAVY].THeight = 28;\n    NPCTraits[NPCID_PLR_HEAVY].WontHurt = true;\n    NPCTraits[NPCID_PLR_HEAVY].NoClipping = true;\n    NPCTraits[NPCID_PLR_HEAVY].NoYoshi = true;\n\n    NPCTraits[NPCID_GRN_SHELL_S1].THeight = 28; //  Green SMB1 Shell\n    NPCTraits[NPCID_GRN_SHELL_S1].IsAShell = true;\n    NPCTraits[NPCID_RED_SHELL_S1].THeight = 28; //  Red SMB1 Shell\n    NPCTraits[NPCID_RED_SHELL_S1].IsAShell = true;\n    NPCTraits[NPCID_FLIPPED_RAINBOW_SHELL].IsAShell = true;\n\n    NPCTraits[NPCID_SAW].JumpHurt = true; // Razor Blade\n    NPCTraits[NPCID_SAW].WidthGFX = 64;\n    NPCTraits[NPCID_SAW].HeightGFX = 64;\n    NPCTraits[NPCID_SAW].TWidth = 48;\n    NPCTraits[NPCID_SAW].THeight = 48;\n    NPCTraits[NPCID_SAW].FrameOffsetY = 8;\n    NPCTraits[NPCID_STONE_S4].JumpHurt = true; // SMW Thwomp\n    NPCTraits[NPCID_STONE_S4].TWidth = 48;\n    NPCTraits[NPCID_STONE_S4].THeight = 64;\n    NPCTraits[NPCID_EARTHQUAKE_BLOCK].TWidth = 32; // POW\n    NPCTraits[NPCID_EARTHQUAKE_BLOCK].THeight = 32; // POW\n    NPCTraits[NPCID_EARTHQUAKE_BLOCK].CanWalkOn = true;\n    NPCTraits[NPCID_EARTHQUAKE_BLOCK].MovesPlayer = true;\n    NPCTraits[NPCID_EARTHQUAKE_BLOCK].WontHurt = true;\n    NPCTraits[NPCID_EARTHQUAKE_BLOCK].IsABlock = true;\n    NPCTraits[NPCID_EARTHQUAKE_BLOCK].GrabFromTop = true;\n    NPCTraits[NPCID_EARTHQUAKE_BLOCK].IsGrabbable = true;\n\n    NPCTraits[NPCID_STATUE_S4].WontHurt = true; // SMW Bowser Statue\n    NPCTraits[NPCID_STATUE_S4].IsABlock = true;\n    NPCTraits[NPCID_STATUE_S4].CanWalkOn = true;\n    NPCTraits[NPCID_STATUE_S4].MovesPlayer = true;\n    NPCTraits[NPCID_STATUE_S4].TWidth = 48;\n    NPCTraits[NPCID_STATUE_S4].THeight = 46;\n    NPCTraits[NPCID_STATUE_S4].FrameOffsetY = 2;\n    NPCTraits[NPCID_GOALTAPE].TWidth = 48;\n    NPCTraits[NPCID_GOALTAPE].THeight = 16;\n    NPCTraits[NPCID_GOALTAPE].IsABonus = true;\n    NPCTraits[NPCID_FIRE_POWER_S1].IsABonus = true; // SMB1 Flower\n    NPCTraits[NPCID_FIRE_POWER_S4].IsABonus = true; // SMW Flower\n    NPCTraits[NPCID_POWER_S1].IsABonus = true; // SMB1 Mushroom\n    NPCTraits[NPCID_POWER_S4].IsABonus = true; // SMW Mushroom\n    NPCTraits[NPCID_LIFE_S1].IsABonus = true; // SMB1 1-up\n    NPCTraits[NPCID_LIFE_S4].IsABonus = true; // SMW 1-up\n    NPCTraits[NPCID_3_LIFE].IsABonus = true; // SMW 3 up\n    NPCTraits[NPCID_3_LIFE].TWidth = 30;\n    NPCTraits[NPCID_SKELETON].TWidth = 32;\n\n    NPCTraits[NPCID_SICK_BOSS].NoYoshi = true;\n    NPCTraits[NPCID_SICK_BOSS_BALL].NoYoshi = true;\n    NPCTraits[NPCID_SICK_BOSS_BALL].JumpHurt = true;\n    NPCTraits[NPCID_SICK_BOSS].TWidth = 80;\n    NPCTraits[NPCID_SICK_BOSS].THeight = 94;\n\n    NPCTraits[NPCID_LAVA_MONSTER].TWidth = 56;\n    NPCTraits[NPCID_LAVA_MONSTER].THeight = 60;\n    NPCTraits[NPCID_LAVA_MONSTER].WidthGFX = 130;\n    NPCTraits[NPCID_LAVA_MONSTER].HeightGFX = 64;\n    NPCTraits[NPCID_LAVA_MONSTER].JumpHurt = true;\n    NPCTraits[NPCID_LAVA_MONSTER].NoClipping = true;\n    NPCTraits[NPCID_FLIER].NoYoshi = true;\n    NPCTraits[NPCID_ROCKET_FLIER].NoYoshi = true;\n    NPCTraits[NPCID_WALL_BUG].NoYoshi = true;\n    NPCTraits[NPCID_WALL_SPARK].NoYoshi = true;\n\n    NPCTraits[NPCID_FLIER].TWidth = 46;\n    NPCTraits[NPCID_FLIER].THeight = 26;\n    NPCTraits[NPCID_FLIER].NoYoshi = true;\n    NPCTraits[NPCID_FLIER].CanWalkOn = true;\n    NPCTraits[NPCID_ROCKET_FLIER].NoYoshi = true;\n    NPCTraits[NPCID_ROCKET_FLIER].CanWalkOn = true;\n    NPCTraits[NPCID_SICK_BOSS].JumpHurt = true;\n\n    NPCTraits[NPCID_BOSS_CASE].NoYoshi = true;\n    NPCTraits[NPCID_BOSS_FRAGILE].NoYoshi = true;\n    NPCTraits[NPCID_BOSS_FRAGILE].CanWalkOn = true;\n    NPCTraits[NPCID_BOSS_FRAGILE].MovesPlayer = true;\n    NPCTraits[NPCID_BOSS_FRAGILE].IsABlock = true;\n    NPCTraits[NPCID_BOSS_FRAGILE].NoClipping = true;\n    NPCTraits[NPCID_BOSS_FRAGILE].TWidth = 96;\n    NPCTraits[NPCID_BOSS_FRAGILE].THeight = 106;\n\n    NPCTraits[NPCID_SICK_BOSS_BALL].NoYoshi = true;\n    NPCTraits[NPCID_SICK_BOSS].NoYoshi = true;\n    NPCTraits[NPCID_VILLAIN_S1].NoYoshi = true;\n\n    NPCTraits[NPCID_SKELETON].WidthGFX = 48;\n    NPCTraits[NPCID_SKELETON].HeightGFX = 64;\n    NPCTraits[NPCID_SKELETON].THeight = 64;\n    NPCTraits[NPCID_SKELETON].TurnsAtCliffs = true;\n    NPCTraits[NPCID_SKELETON].FrameOffsetY = 2;\n    // NPCDefaultMovement(189) = true;\n    NPCTraits[NPCID_RED_TURTLE_S1].WidthGFX = 32;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S1].WidthGFX = 32;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S1].WidthGFX = 32;\n    NPCTraits[NPCID_GRN_TURTLE_S1].WidthGFX = 32;\n    NPCTraits[NPCID_RED_TURTLE_S1].HeightGFX = 48;\n    NPCTraits[NPCID_GRN_FLY_TURTLE_S1].HeightGFX = 48;\n    NPCTraits[NPCID_RED_FLY_TURTLE_S1].HeightGFX = 48;\n    NPCTraits[NPCID_GRN_TURTLE_S1].HeightGFX = 48;\n\n    NPCTraits[NPCID_RAFT].WidthGFX = 32;\n    NPCTraits[NPCID_RAFT].HeightGFX = 32;\n\n    NPCTraits[NPCID_BONE_FISH].TWidth = 48;\n\n    NPCTraits[NPCID_SQUID_S1].HeightGFX = 48;\n    NPCTraits[NPCID_SQUID_S1].WidthGFX = 32;\n    NPCTraits[NPCID_SQUID_S1].FrameOffsetY = 16;\n\n    NPCTraits[NPCID_SQUID_S1].JumpHurt = true;\n    NPCTraits[NPCID_BONE_FISH].JumpHurt = true;\n\n    NPCTraits[NPCID_RAFT].THeight = 20;\n    NPCTraits[NPCID_RAFT].FrameOffsetY = 12;\n    NPCTraits[NPCID_RAINBOW_SHELL].FrameOffsetY = 2;\n\n    NPCTraits[NPCID_RAFT].MovesPlayer = true;\n\n    NPCTraits[NPCID_RED_TURTLE_S1].TurnsAtCliffs = true;\n\n    NPCTraits[NPCID_AXE].NoYoshi = true;\n    NPCTraits[NPCID_SAW].NoYoshi = true;\n    NPCTraits[NPCID_STONE_S4].NoYoshi = true;\n    NPCTraits[NPCID_STATUE_S4].NoYoshi = true;\n    NPCTraits[NPCID_SKELETON].NoYoshi = true;\n\n    NPCTraits[NPCID_RAFT].NoYoshi = true;\n    NPCTraits[NPCID_CHECKPOINT].NoYoshi = true;\n    NPCTraits[NPCID_CHECKPOINT].IsABonus = true;\n\n\n\n    // NPCIsAParaTroopa(NPCID_GRN_FLY_TURTLE_S1) = true;\n    // NPCIsAParaTroopa(NPCID_RED_FLY_TURTLE_S1) = true;\n    NPCTraits[NPCID_AXE].IsABonus = true;\n    // NPCDefaultMovement(NPCID_GRN_TURTLE_S1) = true;\n    // NPCDefaultMovement(NPCID_RED_TURTLE_S1) = true;\n    // NPCDefaultMovement(NPCID_BRUTE) = true;\n    // 'NPCDefaultMovement(NPCID_BRUTE_SQUISHED) = true;\n    // 'NPCDefaultMovement(NPCID_BIG_GUY) = true;\n    // NPCDefaultMovement(NPCID_CARRY_FODDER) = true;\n    // NPCDefaultMovement(NPCID_FLY_CARRY_FODDER) = true;\n    NPCTraits[NPCID_BIG_GUY].CanWalkOn = true;\n    NPCTraits[NPCID_BIG_GUY].IsAHit1Block = true;\n    NPCTraits[NPCID_ROCKET_WOOD].WontHurt = true;\n    NPCTraits[NPCID_HIT_CARRY_FODDER].WontHurt = true;\n    NPCTraits[NPCID_ROCKET_WOOD].CanWalkOn = true;\n    NPCTraits[NPCID_ROCKET_WOOD].MovesPlayer = true;\n    NPCTraits[NPCID_ROCKET_WOOD].IsABlock = true;\n    // NPCDefaultMovement(NPCID_ROCKET_WOOD) = true;\n    NPCTraits[NPCID_LIFT_SAND].WontHurt = true;\n    NPCTraits[NPCID_LIFT_SAND].CanWalkOn = true;\n    NPCTraits[NPCID_LIFT_SAND].IsAHit1Block = true;\n    NPCTraits[NPCID_LIFT_SAND].GrabFromTop = true;\n    NPCTraits[NPCID_CARRY_BLOCK_A].WontHurt = true;\n    NPCTraits[NPCID_CARRY_BLOCK_A].CanWalkOn = true;\n    NPCTraits[NPCID_CARRY_BLOCK_A].IsABlock = true;\n    NPCTraits[NPCID_CARRY_BLOCK_A].MovesPlayer = true;\n    NPCTraits[NPCID_CARRY_BLOCK_B].WontHurt = true;\n    NPCTraits[NPCID_CARRY_BLOCK_B].CanWalkOn = true;\n    NPCTraits[NPCID_CARRY_BLOCK_B].IsABlock = true;\n    NPCTraits[NPCID_CARRY_BLOCK_B].MovesPlayer = true;\n    NPCTraits[NPCID_CARRY_BLOCK_C].WontHurt = true;\n    NPCTraits[NPCID_CARRY_BLOCK_C].CanWalkOn = true;\n    NPCTraits[NPCID_CARRY_BLOCK_C].IsABlock = true;\n    NPCTraits[NPCID_CARRY_BLOCK_C].MovesPlayer = true;\n    NPCTraits[NPCID_CARRY_BLOCK_D].WontHurt = true;\n    NPCTraits[NPCID_CARRY_BLOCK_D].CanWalkOn = true;\n    NPCTraits[NPCID_CARRY_BLOCK_D].IsABlock = true;\n    NPCTraits[NPCID_CARRY_BLOCK_D].MovesPlayer = true;\n    NPCTraits[NPCID_LIFT_SAND].NoYoshi = true;\n\n\n    NPCTraits[NPCID_GOALTAPE].WontHurt = true;\n    NPCTraits[NPCID_GOALTAPE].NoYoshi = true;\n\n\n    NPCTraits[NPCID_CARRY_BLOCK_A].IsGrabbable = true;\n    NPCTraits[NPCID_HIT_CARRY_FODDER].IsGrabbable = true;\n    NPCTraits[NPCID_CARRY_BLOCK_A].GrabFromTop = true;\n    NPCTraits[NPCID_CARRY_BLOCK_B].IsGrabbable = true;\n    NPCTraits[NPCID_CARRY_BLOCK_B].GrabFromTop = true;\n    NPCTraits[NPCID_CARRY_BLOCK_C].IsGrabbable = true;\n    NPCTraits[NPCID_CARRY_BLOCK_C].GrabFromTop = true;\n    NPCTraits[NPCID_CARRY_BLOCK_D].IsGrabbable = true;\n    NPCTraits[NPCID_CARRY_BLOCK_D].GrabFromTop = true;\n    // NPCIsABot(NPCID_RED_SLIME) = true;\n    // NPCIsYoshi(NPCID_PET_GREEN) = true;\n    // NPCIsYoshi(NPCID_PET_BLUE) = true;\n    // NPCIsYoshi(NPCID_PET_YELLOW) = true;\n    // NPCIsYoshi(NPCID_PET_RED) = true;\n    // NPCIsYoshi(NPCID_PET_BLACK) = true;\n    // NPCIsYoshi(NPCID_PET_PURPLE) = true;\n    // NPCIsYoshi(NPCID_PET_PINK) = true;\n    // NPCIsYoshi(NPCID_PET_CYAN) = true;\n    // NPCIsBoot(NPCID_GRN_BOOT) = true;\n    // NPCIsBoot(NPCID_RED_BOOT) = true;\n    // NPCIsBoot(NPCID_BLU_BOOT) = true;\n    NPCTraits[NPCID_GRN_SHELL_S3].IsAShell = true;\n    NPCTraits[NPCID_RED_SHELL_S3].IsAShell = true;\n    NPCTraits[NPCID_GLASS_SHELL].IsAShell = true;\n    NPCTraits[NPCID_BIG_SHELL].IsAShell = true;\n    NPCTraits[NPCID_POWER_S3].IsABonus = true;\n    NPCTraits[NPCID_SWAP_POWER].IsABonus = true;\n    NPCTraits[NPCID_COIN_S3].IsABonus = true;\n    NPCTraits[NPCID_ITEMGOAL].IsABonus = true;\n    NPCTraits[NPCID_FLAG_EXIT].IsABonus = true;\n    NPCTraits[NPCID_FLAG_EXIT].TFrames = 3;\n    NPCTraits[NPCID_FLAG_EXIT].FrameSpeed = 8;\n    NPCTraits[NPCID_FIRE_POWER_S3].IsABonus = true;\n    NPCTraits[NPCID_ICE_POWER_S3].IsABonus = true;\n    NPCTraits[NPCID_ICE_POWER_S4].IsABonus = true;\n    NPCTraits[NPCID_GOALORB_S3].IsABonus = true;\n    NPCTraits[NPCID_COIN_S4].IsABonus = true;\n    NPCTraits[NPCID_COIN_5].IsABonus = true;\n    NPCTraits[NPCID_LEAF_POWER].IsABonus = true;\n    NPCTraits[NPCID_GOALORB_S2].IsABonus = true;\n    NPCTraits[NPCID_CIVILIAN_SCARED].IsABonus = true;\n    NPCTraits[NPCID_COIN_S1].IsABonus = true;\n    NPCTraits[NPCID_LIFE_S3].IsABonus = true;\n    NPCTraits[NPCID_CIVILIAN].IsABonus = true;\n    NPCTraits[NPCID_CHAR3].IsABonus = true;\n    NPCTraits[NPCID_STAR_EXIT].IsABonus = true;\n    NPCTraits[NPCID_CHAR2].IsABonus = true;\n    NPCTraits[NPCID_CHAR5].IsABonus = true;\n    NPCTraits[NPCID_RED_COIN].IsABonus = true;\n    NPCTraits[NPCID_PINK_CIVILIAN].IsABonus = true;\n    NPCTraits[NPCID_POISON].IsABonus = true;\n    NPCTraits[NPCID_STAR_COLLECT].IsABonus = true;\n    NPCTraits[NPCID_INVINCIBILITY_POWER].IsABonus = true;\n    NPCTraits[NPCID_INVINCIBILITY_POWER].TFrames = 4;\n    NPCTraits[NPCID_INVINCIBILITY_POWER].FrameSpeed = 4;\n    NPCTraits[NPCID_AQUATIC_POWER].IsABonus = true;\n    NPCTraits[NPCID_POLAR_POWER].IsABonus = true;\n    NPCTraits[NPCID_CYCLONE_POWER].IsABonus = true;\n    NPCTraits[NPCID_CYCLONE_POWER].TFrames = 4;\n    NPCTraits[NPCID_CYCLONE_POWER].FrameSpeed = 6;\n    NPCTraits[NPCID_CYCLONE_POWER].WidthGFX = 32;\n    NPCTraits[NPCID_CYCLONE_POWER].HeightGFX = 42;\n    NPCTraits[NPCID_CYCLONE_POWER].NoGravity = true;\n    NPCTraits[NPCID_CYCLONE_POWER].NoClipping = true;\n    NPCTraits[NPCID_SHELL_POWER].IsABonus = true;\n    NPCTraits[NPCID_COIN_S3].IsACoin = true;\n    NPCTraits[NPCID_COIN_S4].IsACoin = true;\n    NPCTraits[NPCID_COIN_5].IsACoin = true;\n    NPCTraits[NPCID_COIN_S1].IsACoin = true;\n    NPCTraits[NPCID_RED_COIN].IsACoin = true;\n    // NPCIsAnExit(NPCID_ITEMGOAL) = true;\n    // NPCIsAnExit(NPCID_GOALORB_S3) = true;\n    // NPCIsAnExit(NPCID_GOALORB_S2) = true;\n    // NPCIsAnExit(NPCID_STAR_EXIT) = true;\n    // NPCIsAnExit(NPCID_STAR_COLLECT) = true;\n    NPCTraits[NPCID_PLANT_S3].JumpHurt = true;\n    NPCTraits[NPCID_FIRE_PLANT].JumpHurt = true;\n    NPCTraits[NPCID_LAVABUBBLE].JumpHurt = true;\n    NPCTraits[NPCID_HEAVY_THROWN].JumpHurt = true;\n    NPCTraits[NPCID_SPIKY_S3].JumpHurt = true;\n    NPCTraits[NPCID_SPIKY_S4].JumpHurt = true;\n    NPCTraits[NPCID_SPIKY_BALL_S4].JumpHurt = true;\n    NPCTraits[NPCID_STONE_S3].JumpHurt = true;\n    NPCTraits[NPCID_GHOST_S3].JumpHurt = true;\n    NPCTraits[NPCID_GHOST_FAST].JumpHurt = true;\n    NPCTraits[NPCID_GHOST_S4].JumpHurt = true;\n    NPCTraits[NPCID_BIG_GHOST].JumpHurt = true;\n    NPCTraits[NPCID_SPIKY_BALL_S3].JumpHurt = true;\n    NPCTraits[NPCID_BOTTOM_PLANT].JumpHurt = true;\n    NPCTraits[NPCID_SIDE_PLANT].JumpHurt = true;\n    NPCTraits[NPCID_CRAB].JumpHurt = true;\n    NPCTraits[NPCID_FLY].JumpHurt = true;\n    NPCTraits[NPCID_BIG_PLANT].JumpHurt = true;\n    NPCTraits[NPCID_LONG_PLANT_UP].JumpHurt = true;\n    NPCTraits[NPCID_LONG_PLANT_DOWN].JumpHurt = true;\n    NPCTraits[NPCID_STATUE_FIRE].JumpHurt = true;\n    NPCTraits[NPCID_VILLAIN_FIRE].JumpHurt = true;\n    NPCTraits[NPCID_PLANT_S1].JumpHurt = true;\n    NPCTraits[NPCID_BLU_GUY].CanWalkOn = true;\n    NPCTraits[NPCID_RED_GUY].CanWalkOn = true;\n    NPCTraits[NPCID_STACKER].CanWalkOn = true;\n    NPCTraits[NPCID_CANNONENEMY].CanWalkOn = true;\n    NPCTraits[NPCID_JUMPER_S3].CanWalkOn = true;\n    // NPCTraits[NPCID_RED_FISH_S1].CanWalkOn = true;\n    NPCTraits[NPCID_KEY].CanWalkOn = true;\n    NPCTraits[NPCID_SPIT_BOSS].CanWalkOn = true;\n    NPCTraits[NPCID_SPIT_BOSS_BALL].CanWalkOn = true;\n    NPCTraits[NPCID_SLIDE_BLOCK].CanWalkOn = true;\n    NPCTraits[NPCID_FALL_BLOCK_RED].CanWalkOn = true;\n    NPCTraits[NPCID_FALL_BLOCK_BROWN].CanWalkOn = true;\n    NPCTraits[NPCID_VEHICLE].CanWalkOn = true;\n    NPCTraits[NPCID_CONVEYOR].CanWalkOn = true;\n    NPCTraits[NPCID_METALBARREL].CanWalkOn = true;\n    NPCTraits[NPCID_YEL_PLATFORM].CanWalkOn = true;\n    NPCTraits[NPCID_BLU_PLATFORM].CanWalkOn = true;\n    NPCTraits[NPCID_GRN_PLATFORM].CanWalkOn = true;\n    NPCTraits[NPCID_RED_PLATFORM].CanWalkOn = true;\n    NPCTraits[NPCID_HPIPE_SHORT].CanWalkOn = true;\n    NPCTraits[NPCID_HPIPE_LONG].CanWalkOn = true;\n    NPCTraits[NPCID_VPIPE_SHORT].CanWalkOn = true;\n    NPCTraits[NPCID_VPIPE_LONG].CanWalkOn = true;\n    NPCTraits[NPCID_TANK_TREADS].CanWalkOn = true;\n    NPCTraits[NPCID_SHORT_WOOD].CanWalkOn = true;\n    NPCTraits[NPCID_LONG_WOOD].CanWalkOn = true;\n    NPCTraits[NPCID_SLANT_WOOD_L].CanWalkOn = true;\n    NPCTraits[NPCID_SLANT_WOOD_R].CanWalkOn = true;\n    NPCTraits[NPCID_SLANT_WOOD_M].CanWalkOn = true;\n    NPCTraits[NPCID_STATUE_S3].CanWalkOn = true;\n    NPCTraits[NPCID_ITEM_BURIED].CanWalkOn = true;\n    NPCTraits[NPCID_VEGGIE_1].CanWalkOn = true;\n    NPCTraits[NPCID_PLATFORM_S3].CanWalkOn = true;\n    NPCTraits[NPCID_CHECKER_PLATFORM].CanWalkOn = true;\n    NPCTraits[NPCID_PLATFORM_S1].CanWalkOn = true;\n    NPCTraits[NPCID_BLU_GUY].GrabFromTop = true;\n    NPCTraits[NPCID_RED_GUY].GrabFromTop = true;\n    NPCTraits[NPCID_STACKER].GrabFromTop = true;\n    NPCTraits[NPCID_JUMPER_S3].GrabFromTop = true;\n    // NPCTraits[NPCID_RED_FISH_S1].GrabFromTop = true;\n    NPCTraits[NPCID_KEY].GrabFromTop = true;\n    NPCTraits[NPCID_SPIT_BOSS_BALL].GrabFromTop = true;\n    NPCTraits[NPCID_SLIDE_BLOCK].GrabFromTop = true;\n    NPCTraits[NPCID_ITEM_BURIED].GrabFromTop = true;\n    NPCTraits[NPCID_VEGGIE_1].GrabFromTop = true;\n    NPCTraits[NPCID_PLANT_S3].NoClipping = true;\n    NPCTraits[NPCID_FIRE_PLANT].NoClipping = true;\n    // NPCTraits[NPCID_COIN_S3].NoClipping = true;\n    NPCTraits[NPCID_LAVABUBBLE].NoClipping = true;\n    NPCTraits[NPCID_BULLET].NoClipping = true;\n    NPCTraits[NPCID_BIG_BULLET].NoClipping = true;\n    NPCTraits[NPCID_HEAVY_THROWN].NoClipping = true;\n    NPCTraits[NPCID_LEAF_POWER].NoClipping = true;\n    NPCTraits[NPCID_GHOST_S3].NoClipping = true;\n    NPCTraits[NPCID_GHOST_FAST].NoClipping = true;\n    NPCTraits[NPCID_GHOST_S4].NoClipping = true;\n    NPCTraits[NPCID_BIG_GHOST].NoClipping = true;\n    NPCTraits[NPCID_FALL_BLOCK_RED].NoClipping = true;\n    NPCTraits[NPCID_FALL_BLOCK_BROWN].NoClipping = true;\n    NPCTraits[NPCID_SPIKY_THROWER].NoClipping = true;\n    NPCTraits[NPCID_ITEM_THROWER].NoClipping = true;\n    NPCTraits[NPCID_TOOTHY].NoClipping = true;\n    NPCTraits[NPCID_BOTTOM_PLANT].NoClipping = true;\n    NPCTraits[NPCID_SIDE_PLANT].NoClipping = true;\n    // NPCTraits[NPCID_VEHICLE].NoClipping = true;\n    NPCTraits[NPCID_CONVEYOR].NoClipping = true;\n    NPCTraits[NPCID_YEL_PLATFORM].NoClipping = true;\n    NPCTraits[NPCID_BLU_PLATFORM].NoClipping = true;\n    NPCTraits[NPCID_GRN_PLATFORM].NoClipping = true;\n    NPCTraits[NPCID_RED_PLATFORM].NoClipping = true;\n    NPCTraits[NPCID_BIG_PLANT].NoClipping = true;\n    NPCTraits[NPCID_LONG_PLANT_UP].NoClipping = true;\n    NPCTraits[NPCID_LONG_PLANT_DOWN].NoClipping = true;\n    NPCTraits[NPCID_STATUE_FIRE].NoClipping = true;\n    NPCTraits[NPCID_VILLAIN_FIRE].NoClipping = true;\n    NPCTraits[NPCID_ITEM_BURIED].NoClipping = true;\n    NPCTraits[NPCID_PLANT_S1].NoClipping = true;\n    NPCTraits[NPCID_PLATFORM_S3].NoClipping = true;\n    NPCTraits[NPCID_CHECKER_PLATFORM].NoClipping = true;\n    NPCTraits[NPCID_PLATFORM_S1].NoClipping = true;\n    NPCTraits[NPCID_PET_FIRE].NoClipping = true;\n    NPCTraits[NPCID_RED_TURTLE_S3].TurnsAtCliffs = true;\n    NPCTraits[NPCID_RED_GUY].TurnsAtCliffs = true;\n    NPCTraits[NPCID_STACKER].TurnsAtCliffs = true;\n    NPCTraits[NPCID_EXT_TURTLE].TurnsAtCliffs = true;\n    NPCTraits[NPCID_YELSWITCH_FODDER].TurnsAtCliffs = true;\n    NPCTraits[NPCID_BLUSWITCH_FODDER].TurnsAtCliffs = true;\n    NPCTraits[NPCID_GRNSWITCH_FODDER].TurnsAtCliffs = true;\n    NPCTraits[NPCID_REDSWITCH_FODDER].TurnsAtCliffs = true;\n    NPCTraits[NPCID_PINK_CIVILIAN].TurnsAtCliffs = true;\n    NPCTraits[NPCID_CARRY_BUDDY].TurnsAtCliffs = true;\n    NPCTraits[NPCID_CANNONENEMY].MovesPlayer = true;\n    NPCTraits[NPCID_KEY].MovesPlayer = true;\n    NPCTraits[NPCID_COIN_SWITCH].MovesPlayer = true;\n    NPCTraits[NPCID_TIME_SWITCH].MovesPlayer = true;\n    NPCTraits[NPCID_TNT].MovesPlayer = true;\n    NPCTraits[NPCID_SLIDE_BLOCK].MovesPlayer = true;\n    NPCTraits[NPCID_FALL_BLOCK_RED].MovesPlayer = true;\n    NPCTraits[NPCID_FALL_BLOCK_BROWN].MovesPlayer = true;\n    NPCTraits[NPCID_CONVEYOR].MovesPlayer = true;\n    NPCTraits[NPCID_METALBARREL].MovesPlayer = true;\n    NPCTraits[NPCID_HPIPE_SHORT].MovesPlayer = true;\n    NPCTraits[NPCID_HPIPE_LONG].MovesPlayer = true;\n    NPCTraits[NPCID_VPIPE_SHORT].MovesPlayer = true;\n    NPCTraits[NPCID_VPIPE_LONG].MovesPlayer = true;\n    NPCTraits[NPCID_TANK_TREADS].MovesPlayer = true;\n    NPCTraits[NPCID_SHORT_WOOD].MovesPlayer = true;\n    NPCTraits[NPCID_LONG_WOOD].MovesPlayer = true;\n    NPCTraits[NPCID_SLANT_WOOD_L].MovesPlayer = true;\n    NPCTraits[NPCID_SLANT_WOOD_R].MovesPlayer = true;\n    NPCTraits[NPCID_SLANT_WOOD_M].MovesPlayer = true;\n    NPCTraits[NPCID_STATUE_S3].MovesPlayer = true;\n    NPCTraits[NPCID_CANNONENEMY].WontHurt = true;\n    NPCTraits[NPCID_CANNONITEM].WontHurt = true;\n    NPCTraits[NPCID_SPRING].WontHurt = true;\n    NPCTraits[NPCID_KEY].WontHurt = true;\n    NPCTraits[NPCID_COIN_SWITCH].WontHurt = true;\n    NPCTraits[NPCID_TIME_SWITCH].WontHurt = true;\n    NPCTraits[NPCID_TNT].WontHurt = true;\n    NPCTraits[NPCID_GRN_BOOT].WontHurt = true;\n    NPCTraits[NPCID_RED_BOOT].WontHurt = true;\n    NPCTraits[NPCID_BLU_BOOT].WontHurt = true;\n    NPCTraits[NPCID_SLIDE_BLOCK].WontHurt = true;\n    NPCTraits[NPCID_FALL_BLOCK_RED].WontHurt = true;\n    NPCTraits[NPCID_FALL_BLOCK_BROWN].WontHurt = true;\n    NPCTraits[NPCID_TOOTHYPIPE].WontHurt = true;\n    NPCTraits[NPCID_TOOTHY].WontHurt = true;\n    NPCTraits[NPCID_VEHICLE].WontHurt = true;\n    NPCTraits[NPCID_CONVEYOR].WontHurt = true;\n    NPCTraits[NPCID_METALBARREL].WontHurt = true;\n    NPCTraits[NPCID_YEL_PLATFORM].WontHurt = true;\n    NPCTraits[NPCID_BLU_PLATFORM].WontHurt = true;\n    NPCTraits[NPCID_GRN_PLATFORM].WontHurt = true;\n    NPCTraits[NPCID_RED_PLATFORM].WontHurt = true;\n    NPCTraits[NPCID_HPIPE_SHORT].WontHurt = true;\n    NPCTraits[NPCID_HPIPE_LONG].WontHurt = true;\n    NPCTraits[NPCID_VPIPE_SHORT].WontHurt = true;\n    NPCTraits[NPCID_VPIPE_LONG].WontHurt = true;\n    NPCTraits[NPCID_TANK_TREADS].WontHurt = true;\n    NPCTraits[NPCID_SHORT_WOOD].WontHurt = true;\n    NPCTraits[NPCID_LONG_WOOD].WontHurt = true;\n    NPCTraits[NPCID_SLANT_WOOD_L].WontHurt = true;\n    NPCTraits[NPCID_SLANT_WOOD_R].WontHurt = true;\n    NPCTraits[NPCID_SLANT_WOOD_M].WontHurt = true;\n    NPCTraits[NPCID_STATUE_S3].WontHurt = true;\n    NPCTraits[NPCID_ITEM_BURIED].WontHurt = true;\n    NPCTraits[NPCID_VEGGIE_1].WontHurt = true;\n    NPCTraits[NPCID_PET_GREEN].WontHurt = true;\n    NPCTraits[NPCID_ITEM_POD].WontHurt = true;\n    NPCTraits[NPCID_PET_BLUE].WontHurt = true;\n    NPCTraits[NPCID_PET_YELLOW].WontHurt = true;\n    NPCTraits[NPCID_PET_RED].WontHurt = true;\n    NPCTraits[NPCID_PLATFORM_S3].WontHurt = true;\n    NPCTraits[NPCID_CHECKER_PLATFORM].WontHurt = true;\n    NPCTraits[NPCID_PLATFORM_S1].WontHurt = true;\n    NPCTraits[NPCID_PINK_CIVILIAN].WontHurt = true;\n    NPCTraits[NPCID_PET_FIRE].WontHurt = true;\n    NPCTraits[NPCID_PET_BLACK].WontHurt = true;\n    NPCTraits[NPCID_PET_PURPLE].WontHurt = true;\n    NPCTraits[NPCID_PET_PINK].WontHurt = true;\n    NPCTraits[NPCID_PET_CYAN].WontHurt = true;\n    NPCTraits[NPCID_CARRY_BUDDY].WontHurt = true;\n    NPCTraits[NPCID_CANNONITEM].IsGrabbable = true;\n    NPCTraits[NPCID_SPRING].IsGrabbable = true;\n    NPCTraits[NPCID_KEY].IsGrabbable = true;\n    NPCTraits[NPCID_COIN_SWITCH].IsGrabbable = true;\n    NPCTraits[NPCID_TIME_SWITCH].IsGrabbable = true;\n    NPCTraits[NPCID_GRN_BOOT].IsGrabbable = true;\n    NPCTraits[NPCID_RED_BOOT].IsGrabbable = true;\n    NPCTraits[NPCID_BLU_BOOT].IsGrabbable = true;\n    NPCTraits[NPCID_SLIDE_BLOCK].IsGrabbable = true;\n    NPCTraits[NPCID_TOOTHYPIPE].IsGrabbable = true;\n    NPCTraits[NPCID_VEGGIE_1].IsGrabbable = true;\n    NPCTraits[NPCID_ITEM_POD].IsGrabbable = true;\n    NPCTraits[NPCID_CARRY_BUDDY].IsGrabbable = true;\n    NPCTraits[NPCID_CANNONENEMY].IsABlock = true;\n    NPCTraits[NPCID_KEY].IsABlock = true;\n    NPCTraits[NPCID_COIN_SWITCH].IsABlock = true;\n    NPCTraits[NPCID_TIME_SWITCH].IsABlock = true;\n    NPCTraits[NPCID_TNT].IsABlock = true;\n    NPCTraits[NPCID_SLIDE_BLOCK].IsABlock = true;\n    NPCTraits[NPCID_FALL_BLOCK_RED].IsABlock = true;\n    NPCTraits[NPCID_FALL_BLOCK_BROWN].IsABlock = true;\n    NPCTraits[NPCID_VEHICLE].IsABlock = true;\n    NPCTraits[NPCID_CONVEYOR].IsABlock = true;\n    NPCTraits[NPCID_METALBARREL].IsABlock = true;\n    NPCTraits[NPCID_HPIPE_SHORT].IsABlock = true;\n    NPCTraits[NPCID_HPIPE_LONG].IsABlock = true;\n    NPCTraits[NPCID_VPIPE_SHORT].IsABlock = true;\n    NPCTraits[NPCID_VPIPE_LONG].IsABlock = true;\n    NPCTraits[NPCID_TANK_TREADS].IsABlock = true;\n    NPCTraits[NPCID_SHORT_WOOD].IsABlock = true;\n    NPCTraits[NPCID_LONG_WOOD].IsABlock = true;\n    NPCTraits[NPCID_SLANT_WOOD_L].IsABlock = true;\n    NPCTraits[NPCID_SLANT_WOOD_R].IsABlock = true;\n    NPCTraits[NPCID_SLANT_WOOD_M].IsABlock = true;\n    NPCTraits[NPCID_STATUE_S3].IsABlock = true;\n    NPCTraits[NPCID_RAFT].IsABlock = true;\n    NPCTraits[NPCID_GRN_BOOT].IsAHit1Block = true;\n    NPCTraits[NPCID_RED_BOOT].IsAHit1Block = true;\n    NPCTraits[NPCID_BLU_BOOT].IsAHit1Block = true;\n    NPCTraits[NPCID_CANNONITEM].IsAHit1Block = true;\n    NPCTraits[NPCID_TOOTHYPIPE].IsAHit1Block = true;\n    NPCTraits[NPCID_SPRING].IsAHit1Block = true;\n    NPCTraits[NPCID_YEL_PLATFORM].IsAHit1Block = true;\n    NPCTraits[NPCID_BLU_PLATFORM].IsAHit1Block = true;\n    NPCTraits[NPCID_GRN_PLATFORM].IsAHit1Block = true;\n    NPCTraits[NPCID_RED_PLATFORM].IsAHit1Block = true;\n    NPCTraits[NPCID_PLATFORM_S3].IsAHit1Block = true;\n    NPCTraits[NPCID_CHECKER_PLATFORM].IsAHit1Block = true;\n    NPCTraits[NPCID_PLATFORM_S1].IsAHit1Block = true;\n    NPCTraits[NPCID_RAFT].WontHurt = true;\n    NPCTraits[NPCID_RAFT].CanWalkOn = true;\n    NPCTraits[NPCID_CANNONITEM].StandsOnPlayer = true;\n    NPCTraits[NPCID_SPRING].StandsOnPlayer = true;\n    NPCTraits[NPCID_KEY].StandsOnPlayer = true;\n    NPCTraits[NPCID_COIN_SWITCH].StandsOnPlayer = true;\n    NPCTraits[NPCID_TIME_SWITCH].StandsOnPlayer = true;\n    NPCTraits[NPCID_TNT].StandsOnPlayer = true;\n    NPCTraits[NPCID_GRN_BOOT].StandsOnPlayer = true;\n    NPCTraits[NPCID_RED_BOOT].StandsOnPlayer = true;\n    NPCTraits[NPCID_BLU_BOOT].StandsOnPlayer = true;\n    NPCTraits[NPCID_TOOTHYPIPE].StandsOnPlayer = true;\n    NPCTraits[NPCID_PET_GREEN].StandsOnPlayer = true;\n    NPCTraits[NPCID_ITEM_POD].StandsOnPlayer = true;\n    NPCTraits[NPCID_PET_BLUE].StandsOnPlayer = true;\n    NPCTraits[NPCID_PET_YELLOW].StandsOnPlayer = true;\n    NPCTraits[NPCID_PET_RED].StandsOnPlayer = true;\n    NPCTraits[NPCID_PET_BLACK].StandsOnPlayer = true;\n    NPCTraits[NPCID_PET_PURPLE].StandsOnPlayer = true;\n    NPCTraits[NPCID_PET_PINK].StandsOnPlayer = true;\n    NPCTraits[NPCID_PET_CYAN].StandsOnPlayer = true;\n\n\n    NPCTraits[NPCID_VINE_BUG].CanWalkOn = true;\n    NPCTraits[NPCID_VINE_BUG].GrabFromTop = true;\n    NPCTraits[NPCID_VINE_BUG].NoFireBall = true;\n    NPCTraits[NPCID_VINE_BUG].NoClipping = true;\n    NPCTraits[NPCID_VINE_BUG].TWidth = 32;\n    NPCTraits[NPCID_VINE_BUG].THeight = 32;\n\n    NPCTraits[NPCID_BOSS_CASE].CanWalkOn = true;\n    NPCTraits[NPCID_BOSS_CASE].WontHurt = true;\n    NPCTraits[NPCID_BOSS_CASE].MovesPlayer = true;\n    NPCTraits[NPCID_BOSS_CASE].TWidth = 128;\n    NPCTraits[NPCID_BOSS_CASE].THeight = 128;\n    NPCTraits[NPCID_BOSS_CASE].IsABlock = true;\n\n    NPCTraits[NPCID_BOSS_CASE].Foreground = true;\n    NPCTraits[NPCID_WALL_TURTLE].JumpHurt = true;\n    NPCTraits[NPCID_WALL_SPARK].JumpHurt = true;\n    NPCTraits[NPCID_WALL_BUG].JumpHurt = true;\n\n    NPCTraits[NPCID_ROCKET_FLIER].TWidth = 48;\n    NPCTraits[NPCID_ROCKET_FLIER].THeight = 28;\n    NPCTraits[NPCID_ROCKET_FLIER].WidthGFX = 112;\n    NPCTraits[NPCID_ROCKET_FLIER].HeightGFX = 28;\n\n    NPCTraits[NPCID_WALL_BUG].WidthGFX = 44;\n    NPCTraits[NPCID_WALL_BUG].HeightGFX = 44;\n    NPCTraits[NPCID_WALL_BUG].FrameOffsetY = 6;\n\n\n    NPCTraits[NPCID_JUMP_PLANT].TWidth = 32;\n    NPCTraits[NPCID_JUMP_PLANT].THeight = 42;\n    NPCTraits[NPCID_JUMP_PLANT].JumpHurt = true;\n    NPCTraits[NPCID_JUMP_PLANT].NoClipping = true;\n\n    NPCTraits[NPCID_BAT].TWidth = 32;\n    NPCTraits[NPCID_BAT].THeight = 32;\n    NPCTraits[NPCID_BAT].NoClipping = true;\n\n\n    NPCTraits[NPCID_HOMING_BALL].TWidth = 28;\n    NPCTraits[NPCID_HOMING_BALL].NoClipping = true;\n    NPCTraits[NPCID_HOMING_BALL_GEN].IsABlock = true;\n    NPCTraits[NPCID_HOMING_BALL_GEN].NoClipping = true;\n    NPCTraits[NPCID_HOMING_BALL_GEN].MovesPlayer = true;\n    NPCTraits[NPCID_HOMING_BALL_GEN].CanWalkOn = true;\n    NPCTraits[NPCID_HOMING_BALL_GEN].WontHurt = true;\n\n\n\n\n\n    NPCTraits[NPCID_LAVABUBBLE].Foreground = true;\n    NPCTraits[NPCID_BULLET].Foreground = true;\n    NPCTraits[NPCID_BIG_BULLET].Foreground = true;\n    NPCTraits[NPCID_RED_FISH_S1].Foreground = true;\n    NPCTraits[NPCID_HEAVY_THROWN].Foreground = true;\n    NPCTraits[NPCID_LEAF_POWER].Foreground = true;\n    NPCTraits[NPCID_GHOST_S3].Foreground = true;\n    NPCTraits[NPCID_GHOST_FAST].Foreground = true;\n    NPCTraits[NPCID_GHOST_S4].Foreground = true;\n    NPCTraits[NPCID_BIG_GHOST].Foreground = true;\n    NPCTraits[NPCID_SPIKY_THROWER].Foreground = true;\n    NPCTraits[NPCID_STATUE_FIRE].Foreground = true;\n    NPCTraits[NPCID_VILLAIN_S3].Foreground = true;\n    NPCTraits[NPCID_VILLAIN_FIRE].Foreground = true;\n    NPCTraits[NPCID_PET_FIRE].Foreground = true;\n    NPCTraits[NPCID_PLR_HEAVY].Foreground = true;\n    // NPCDefaultMovement(NPCID_FODDER_S3) = true;\n    // NPCDefaultMovement(NPCID_RED_FODDER) = true;\n    // NPCDefaultMovement(NPCID_RED_FLY_FODDER) = true;\n    // NPCDefaultMovement(NPCID_GRN_TURTLE_S3) = true;\n    // NPCDefaultMovement(NPCID_RED_TURTLE_S3) = true;\n    // NPCDefaultMovement(NPCID_BLU_GUY) = true;\n    // NPCDefaultMovement(NPCID_RED_GUY) = true;\n    // NPCDefaultMovement(NPCID_STACKER) = true;\n    // NPCDefaultMovement(NPCID_GLASS_TURTLE) = true;\n    // NPCDefaultMovement(NPCID_UNDER_FODDER) = true;\n    // NPCDefaultMovement(NPCID_SPIKY_S3) = true;\n    // NPCDefaultMovement(NPCID_SPIKY_S4) = true;\n    // NPCDefaultMovement(NPCID_TOOTHY) = true;\n    // NPCDefaultMovement(NPCID_CRAB) = true;\n    // NPCDefaultMovement(NPCID_EXT_TURTLE) = true;\n    // NPCDefaultMovement(NPCID_YELSWITCH_FODDER) = true;\n    // NPCDefaultMovement(NPCID_BLUSWITCH_FODDER) = true;\n    // NPCDefaultMovement(NPCID_GRNSWITCH_FODDER) = true;\n    // NPCDefaultMovement(NPCID_REDSWITCH_FODDER) = true;\n    // NPCDefaultMovement(NPCID_BIG_FODDER) = true;\n    // NPCDefaultMovement(NPCID_BIG_TURTLE) = true;\n    // NPCDefaultMovement(NPCID_JUMPER_S4) = true;\n    // NPCDefaultMovement(NPCID_TANK_TREADS) = true;\n    // NPCDefaultMovement(NPCID_FODDER_S1) = true;\n    // NPCDefaultMovement(NPCID_ITEM_BURIED) = true;\n    // NPCDefaultMovement(NPCID_PINK_CIVILIAN) = true;\n\n    NPCTraits[NPCID_BLU_GUY].NoFireBall = true;\n    NPCTraits[NPCID_RED_GUY].NoFireBall = true;\n    NPCTraits[NPCID_GLASS_TURTLE].NoFireBall = true;\n    NPCTraits[NPCID_GLASS_SHELL].NoFireBall = true;\n    NPCTraits[NPCID_LIT_BOMB_S3].NoFireBall = true;\n    NPCTraits[NPCID_WALK_BOMB_S3].NoFireBall = true;\n    NPCTraits[NPCID_BIRD].NoFireBall = true;\n    NPCTraits[NPCID_RED_SPIT_GUY].NoFireBall = true;\n    NPCTraits[NPCID_BLU_SPIT_GUY].NoFireBall = true;\n    NPCTraits[NPCID_GRY_SPIT_GUY].NoFireBall = true;\n    NPCTraits[NPCID_LAVABUBBLE].NoFireBall = true;\n    NPCTraits[NPCID_JUMPER_S3].NoFireBall = true;\n\n\n    // link sword beam\n    NPCTraits[NPCID_SWORDBEAM].TWidth = 16;\n    NPCTraits[NPCID_SWORDBEAM].THeight = 8;\n    NPCTraits[NPCID_SWORDBEAM].WontHurt = true;\n    NPCTraits[NPCID_SWORDBEAM].JumpHurt = true;\n    NPCTraits[NPCID_SWORDBEAM].NoGravity = true;\n\n    NPCTraits[NPCID_MAGIC_BOSS].NoYoshi = true;\n    NPCTraits[NPCID_MAGIC_BOSS_SHELL].NoYoshi = true;\n    NPCTraits[NPCID_MAGIC_BOSS_BALL].NoYoshi = true;\n    NPCTraits[NPCID_FIRE_BOSS].NoYoshi = true;\n    NPCTraits[NPCID_FIRE_BOSS_SHELL].NoYoshi = true;\n    NPCTraits[NPCID_FIRE_BOSS_FIRE].NoYoshi = true;\n\n\n    NPCTraits[NPCID_QUAD_BALL].NoClipping = true;\n    NPCTraits[NPCID_QUAD_BALL].TWidth = 16;\n    NPCTraits[NPCID_QUAD_BALL].THeight = 16;\n    NPCTraits[NPCID_QUAD_BALL].NoYoshi = true;\n    NPCTraits[NPCID_QUAD_BALL].JumpHurt = true;\n\n    NPCTraits[NPCID_QUAD_SPITTER].TWidth = 60;\n    NPCTraits[NPCID_QUAD_SPITTER].THeight = 24;\n    NPCTraits[NPCID_QUAD_SPITTER].WidthGFX = 64;\n    NPCTraits[NPCID_QUAD_SPITTER].HeightGFX = 32;\n    NPCTraits[NPCID_QUAD_SPITTER].NoFireBall = true;\n    NPCTraits[NPCID_QUAD_SPITTER].JumpHurt = true;\n\n    NPCTraits[NPCID_FLY_BLOCK].TWidth = 32;\n    NPCTraits[NPCID_FLY_BLOCK].THeight = 44;\n    NPCTraits[NPCID_FLY_BLOCK].WontHurt = true;\n    NPCTraits[NPCID_FLY_BLOCK].JumpHurt = true;\n    NPCTraits[NPCID_FLY_BLOCK].IsGrabbable = true;\n\n    NPCTraits[NPCID_FLY_CANNON].TWidth = 32;\n    NPCTraits[NPCID_FLY_CANNON].THeight = 44;\n    NPCTraits[NPCID_FLY_CANNON].WidthGFX = 96;\n    NPCTraits[NPCID_FLY_CANNON].HeightGFX = 44;\n    NPCTraits[NPCID_FLY_CANNON].WontHurt = true;\n    NPCTraits[NPCID_FLY_CANNON].JumpHurt = true;\n    NPCTraits[NPCID_FLY_CANNON].IsGrabbable = true;\n\n\n// set ice\n    NPCTraits[NPCID_GOALTAPE].NoIceBall = true;\n    NPCTraits[NPCID_CHECKPOINT].NoIceBall = true;\n\n    NPCTraits[NPCID_MAGIC_BOSS].NoIceBall = true;\n    NPCTraits[NPCID_MAGIC_BOSS_SHELL].NoIceBall = true;\n    NPCTraits[NPCID_MAGIC_BOSS_BALL].NoIceBall = true;\n    NPCTraits[NPCID_QUAD_SPITTER].NoIceBall = true;\n    NPCTraits[NPCID_FIRE_BOSS].NoIceBall = true;\n    NPCTraits[NPCID_FIRE_BOSS_SHELL].NoIceBall = true;\n    NPCTraits[NPCID_FIRE_BOSS_FIRE].NoIceBall = true;\n    NPCTraits[NPCID_MINIBOSS].NoIceBall = true;\n    NPCTraits[NPCID_BIG_BULLET].NoIceBall = true;\n    NPCTraits[NPCID_SPRING].NoIceBall = true;\n    NPCTraits[NPCID_HEAVY_THROWN].NoIceBall = true;\n    NPCTraits[NPCID_KEY].NoIceBall = true;\n    NPCTraits[NPCID_COIN_SWITCH].NoIceBall = true;\n    NPCTraits[NPCID_GRN_BOOT].NoIceBall = true;\n    NPCTraits[NPCID_STONE_S3].NoIceBall = true;\n    NPCTraits[NPCID_GHOST_S3].NoIceBall = true;\n    NPCTraits[NPCID_SPIT_BOSS].NoIceBall = true;\n    NPCTraits[NPCID_SPIT_BOSS_BALL].NoIceBall = true;\n    NPCTraits[NPCID_GHOST_FAST].NoIceBall = true;\n    NPCTraits[NPCID_GHOST_S4].NoIceBall = true;\n    NPCTraits[NPCID_BIG_GHOST].NoIceBall = true;\n    NPCTraits[NPCID_SLIDE_BLOCK].NoIceBall = true;\n    NPCTraits[NPCID_FALL_BLOCK_RED].NoIceBall = true;\n    NPCTraits[NPCID_VEHICLE].NoIceBall = true;\n    For(A, NPCID_CONVEYOR, NPCID_VPIPE_LONG)\n    {\n        NPCTraits[A].NoIceBall = true;\n    }\n    NPCTraits[NPCID_CIVILIAN_SCARED].NoIceBall = true;\n    For(A, NPCID_TANK_TREADS, NPCID_COIN_S1)\n    {\n        NPCTraits[A].NoIceBall = true;\n    }\n    NPCTraits[NPCID_ITEM_BURIED].NoIceBall = true;\n    NPCTraits[NPCID_VEGGIE_1].NoIceBall = true;\n    NPCTraits[NPCID_CIVILIAN].NoIceBall = true;\n    For(A, NPCID_PET_GREEN, NPCID_PET_FIRE)\n    {\n        NPCTraits[A].NoIceBall = true;\n    }\n    NPCTraits[NPCID_SPIT_GUY_BALL].NoIceBall = true;\n    NPCTraits[NPCID_BOMB].NoIceBall = true;\n    For(A, NPCID_COIN_S2, NPCID_ROCKET_WOOD)\n    {\n        NPCTraits[A].NoIceBall = true;\n    }\n    NPCTraits[NPCID_PLR_HEAVY].NoIceBall = true;\n    NPCTraits[NPCID_AXE].NoIceBall = true;\n    NPCTraits[NPCID_SAW].NoIceBall = true;\n    NPCTraits[NPCID_STONE_S4].NoIceBall = true;\n    NPCTraits[NPCID_STATUE_S4].NoIceBall = true;\n    NPCTraits[NPCID_RAFT].NoIceBall = true;\n    NPCTraits[NPCID_RED_BOOT].NoIceBall = true;\n    NPCTraits[NPCID_CHECKPOINT].NoIceBall = true;\n    NPCTraits[NPCID_BLU_BOOT].NoIceBall = true;\n    NPCTraits[NPCID_FLIPPED_RAINBOW_SHELL].NoIceBall = true;\n    NPCTraits[NPCID_LAVA_MONSTER].NoIceBall = true;\n    NPCTraits[NPCID_VILLAIN_S1].NoIceBall = true;\n    NPCTraits[NPCID_SICK_BOSS].NoIceBall = true;\n    NPCTraits[NPCID_BOSS_CASE].NoIceBall = true;\n    NPCTraits[NPCID_BOSS_FRAGILE].NoIceBall = true;\n    NPCTraits[NPCID_HOMING_BALL].NoIceBall = true;\n    For(A, NPCID_HOMING_BALL_GEN, NPCID_PET_CYAN)\n    {\n        NPCTraits[A].NoIceBall = true;\n    }\n    NPCTraits[NPCID_ICE_BLOCK].NoIceBall = true;\n    NPCTraits[NPCID_TIME_SWITCH].NoIceBall = true;\n    NPCTraits[NPCID_TNT].NoIceBall = true;\n    NPCTraits[NPCID_EARTHQUAKE_BLOCK].NoIceBall = true;\n    NPCTraits[NPCID_PLANT_FIREBALL].NoIceBall = true;\n    NPCTraits[NPCID_FLY_POWER].NoIceBall = true;\n    NPCTraits[NPCID_LOCK_DOOR].NoIceBall = true;\n    NPCTraits[NPCID_LONG_PLANT_UP].NoIceBall = true;\n    NPCTraits[NPCID_LONG_PLANT_DOWN].NoIceBall = true;\n    NPCTraits[NPCID_FIRE_DISK].NoIceBall = true;\n    NPCTraits[NPCID_FIRE_CHAIN].NoIceBall = true;\n    NPCTraits[NPCID_BOMBER_BOSS].NoIceBall = true;\n    NPCTraits[NPCID_GEM_1].NoIceBall = true;\n    NPCTraits[NPCID_GEM_5].NoIceBall = true;\n    NPCTraits[NPCID_GEM_20].NoIceBall = true;\n    NPCTraits[NPCID_COIN_5].NoIceBall = true;\n    NPCTraits[NPCID_COIN_S4].NoIceBall = true;\n    NPCTraits[NPCID_MEDAL].NoIceBall = true;\n    NPCTraits[NPCID_FLY_BLOCK].NoIceBall = true;\n    NPCTraits[NPCID_FLY_CANNON].NoIceBall = true;\n\n    for(int A = NPCID_FIRE_BOSS_FIRE; A <= NPCID_CHAR4_HEAVY; ++A)\n    {\n        if(A != NPCID_SPIKY_S4 && A != NPCID_SPIKY_BALL_S4)\n            NPCTraits[A].NoIceBall = true;\n    }\n\n\n    NPCTraits[NPCID_STACKER].THeight = 30;\n    NPCTraits[NPCID_STACKER].HeightGFX = 32;\n    NPCTraits[NPCID_STACKER].WidthGFX = 32;\n\n\n    // NPCDefaultMovement(NPCID_CARRY_BUDDY) = true;\n\n\n    NPCTraits[NPCID_MAGIC_BOSS].WidthGFX = 84;\n    NPCTraits[NPCID_MAGIC_BOSS].HeightGFX = 62;\n    NPCTraits[NPCID_MAGIC_BOSS].TWidth = 44;\n    NPCTraits[NPCID_MAGIC_BOSS].THeight = 50;\n\n    NPCTraits[NPCID_MAGIC_BOSS_SHELL].WidthGFX = 44;\n    NPCTraits[NPCID_MAGIC_BOSS_SHELL].HeightGFX = 32;\n    NPCTraits[NPCID_MAGIC_BOSS_SHELL].TWidth = 32;\n    NPCTraits[NPCID_MAGIC_BOSS_SHELL].THeight = 28;\n\n    NPCTraits[NPCID_MAGIC_BOSS_BALL].WidthGFX = 16;\n    NPCTraits[NPCID_MAGIC_BOSS_BALL].HeightGFX = 32;\n    NPCTraits[NPCID_MAGIC_BOSS_BALL].TWidth = 16;\n    NPCTraits[NPCID_MAGIC_BOSS_BALL].THeight = 32;\n    NPCTraits[NPCID_MAGIC_BOSS_BALL].NoClipping = true;\n    NPCTraits[NPCID_MAGIC_BOSS_BALL].JumpHurt = true;\n\n    NPCTraits[NPCID_FIRE_BOSS].WidthGFX = 64;\n    NPCTraits[NPCID_FIRE_BOSS].HeightGFX = 64;\n    NPCTraits[NPCID_FIRE_BOSS].TWidth = 48;\n    NPCTraits[NPCID_FIRE_BOSS].THeight = 48;\n\n    NPCTraits[NPCID_FIRE_BOSS_SHELL].WidthGFX = 40;\n    NPCTraits[NPCID_FIRE_BOSS_SHELL].HeightGFX = 36;\n    NPCTraits[NPCID_FIRE_BOSS_SHELL].TWidth = 36;\n    NPCTraits[NPCID_FIRE_BOSS_SHELL].THeight = 32;\n\n    NPCTraits[NPCID_FIRE_BOSS_FIRE].WidthGFX = 64;\n    NPCTraits[NPCID_FIRE_BOSS_FIRE].HeightGFX = 32;\n    NPCTraits[NPCID_FIRE_BOSS_FIRE].TWidth = 64;\n    NPCTraits[NPCID_FIRE_BOSS_FIRE].THeight = 26;\n    NPCTraits[NPCID_FIRE_BOSS_FIRE].FrameOffsetY = 4;\n    NPCTraits[NPCID_FIRE_BOSS_FIRE].NoClipping = true;\n    NPCTraits[NPCID_FIRE_BOSS_FIRE].JumpHurt = true;\n\n    // Default NPCs that must use the canonical camera\n    NPCTraits[NPCID_STONE_S3].UseDefaultCam = true;\n    NPCTraits[NPCID_STONE_S4].UseDefaultCam = true;\n    NPCTraits[NPCID_METALBARREL].UseDefaultCam = true;\n    NPCTraits[NPCID_CANNONENEMY].UseDefaultCam = true;\n    NPCTraits[NPCID_BULLET].UseDefaultCam = true;\n    NPCTraits[NPCID_BIG_BULLET].UseDefaultCam = true;\n    NPCTraits[NPCID_GHOST_FAST].UseDefaultCam = true;\n    NPCTraits[NPCID_STATUE_S3].UseDefaultCam = true;\n    NPCTraits[NPCID_STATUE_S4].UseDefaultCam = true;\n    NPCTraits[NPCID_HOMING_BALL_GEN].UseDefaultCam = true;\n    NPCTraits[NPCID_LAVA_MONSTER].UseDefaultCam = true;\n    NPCTraits[NPCID_SPIKY_THROWER].UseDefaultCam = true;\n    NPCTraits[NPCID_ITEM_THROWER].UseDefaultCam = true;\n    // NPCTraits[NPCID_SAW].UseDefaultCam = true; // held back for 1.3.7\n    // NPCTraits[NPCID_BOMB].UseDefaultCam = true; // held back for 1.3.7\n\n    // Default NPCs that render differently when inactive\n    for(int A = 1; A <= maxNPCType; A++)\n    {\n        if(NPCTraits[A].IsABlock || NPCTraits[A].IsAHit1Block || NPCTraits[A].IsACoin || NPCTraits[A].IsAVine || NPCTraits[A].IsABonus)\n            NPCTraits[A].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    }\n\n    NPCTraits[NPCID_CHECKPOINT].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_ITEM_BURIED].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_CONVEYOR].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_STONE_S3].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_STONE_S4].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_HOMING_BALL_GEN].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_ITEMGOAL].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_FLAG_EXIT].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_CANNONENEMY].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_STATUE_S3].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_STATUE_S4].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_ITEM_POD].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_SPRING].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_CANNONITEM].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_KEY].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_TIME_SWITCH].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_COIN_SWITCH].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_ICE_BLOCK].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_VEHICLE].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_AXE].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_WALK_PLANT].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_FLY_BLOCK].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_FLY_CANNON].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_MAGIC_DOOR].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    NPCTraits[NPCID_DOOR_MAKER].InactiveRender = NPCTraits_t::SHOW_ALWAYS;\n    // NPCTraits[NPCID_SAW].InactiveRender = NPCTraits_t::SHOW_STATIC;\n    // NPCTraits[NPCID_BOMB].InactiveRender = NPCTraits_t::SHOW_STATIC;\n\n    NPCTraits[NPCID_LAVABUBBLE].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_PLANT_S3].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_BOTTOM_PLANT].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_SIDE_PLANT].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_BIG_PLANT].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_PLANT_S1].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_FIRE_PLANT].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_LONG_PLANT_UP].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_LONG_PLANT_DOWN].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_JUMP_PLANT].InactiveRender = NPCTraits_t::SKIP;\n    NPCTraits[NPCID_LAVA_MONSTER].InactiveRender = NPCTraits_t::SKIP;\n\n    NPCTraits[NPCID_BULLET].InactiveRender = NPCTraits_t::SMOKE;\n    NPCTraits[NPCID_BIG_BULLET].InactiveRender = NPCTraits_t::SMOKE;\n    NPCTraits[NPCID_GHOST_FAST].InactiveRender = NPCTraits_t::SMOKE;\n\n\n    For(A, 1, maxBlockType)\n    {\n        BlockWidth[A] = 32;\n        BlockHeight[A] = 32;\n    }\n\n    BlockWidth[571] = 64;\n    BlockWidth[572] = 64;\n    BlockWidth[615] = 64;\n\n    BlockWidth[634] = 64;\n    BlockHeight[634] = 64;\n\n    BlockHeight[595] = 64;\n    BlockHeight[596] = 64;\n    BlockHeight[597] = 64;\n\n    BlockHeight[569] = 64;\n    BlockHeight[570] = 64;\n    BlockHeight[571] = 64;\n    BlockHeight[572] = 64;\n    BlockHeight[575] = 64;\n\n    BlockOnlyHitspot1[572] = true;\n    BlockWidth[21] = 64;\n    BlockWidth[22] = 64;\n    BlockHeight[23] = 64;\n    BlockHeight[24] = 64;\n    BlockWidth[34] = 64;\n    BlockWidth[35] = 64;\n    BlockWidth[36] = 64;\n    BlockWidth[37] = 64;\n    BlockWidth[61] = 128;\n    BlockHeight[61] = 128;\n    BlockWidth[78] = 64;\n    BlockWidth[91] = 64;\n    BlockHeight[91] = 64;\n    BlockWidth[92] = 128;\n    BlockHeight[92] = 128;\n    BlockWidth[93] = 128;\n    BlockHeight[93] = 128;\n    BlockWidth[103] = 64;\n    BlockWidth[104] = 64;\n    BlockWidth[113] = 64;\n    BlockWidth[114] = 64;\n    BlockWidth[125] = 64;\n    BlockHeight[125] = 64;\n    BlockWidth[182] = 96;\n    BlockHeight[182] = 96;\n    BlockWidth[184] = 64;\n    BlockHeight[184] = 64;\n    BlockWidth[185] = 128;\n    BlockHeight[187] = 128;\n    BlockWidth[187] = 128;\n    BlockWidth[194] = 64;\n    BlockWidth[195] = 64;\n    BlockWidth[196] = 64;\n    BlockWidth[197] = 64;\n    BlockWidth[206] = 64;\n    BlockHeight[206] = 64;\n    BlockHeight[211] = 64;\n    BlockHeight[212] = 64;\n    BlockWidth[224] = 64;\n    BlockHeight[224] = 64;\n    BlockWidth[225] = 64;\n    BlockHeight[225] = 64;\n    BlockWidth[226] = 64;\n    BlockHeight[226] = 64;\n    BlockWidth[262] = 128;\n    BlockHeight[262] = 128;\n\n    BlockWidth[616] = 64;\n    BlockWidth[617] = 64;\n    BlockSlope[616] = -1;\n    BlockSlope[617] = 1;\n\n    BlockSlope[635] = -1;\n    BlockSlope[636] = -1;\n    BlockWidth[636] = 64;\n    BlockSlope[637] = 1;\n    BlockSlope[638] = 1;\n    BlockWidth[638] = 64;\n\n\n    For(A, 137, 146)\n    {\n        BlockWidth[A] = 64;\n    }\n\n    For(A, 147, 158)\n    {\n        BlockHeight[A] = 64;\n    }\n    BlockWidth[301] = 128;\n    BlockWidth[302] = 128;\n    BlockWidth[303] = 128;\n    BlockWidth[304] = 128;\n    BlockWidth[306] = 64;\n    BlockWidth[308] = 64;\n    BlockWidth[312] = 64;\n    BlockWidth[314] = 64;\n    BlockWidth[319] = 128;\n    BlockWidth[320] = 128;\n    BlockWidth[321] = 128;\n    BlockWidth[322] = 128;\n    BlockWidth[325] = 64;\n    BlockWidth[324] = 64;\n    BlockWidth[325] = 64;\n    BlockWidth[324] = 64;\n    BlockWidth[336] = 64;\n    BlockWidth[338] = 64;\n    BlockWidth[340] = 64;\n    BlockWidth[342] = 64;\n    BlockWidth[357] = 64;\n    BlockWidth[360] = 64;\n    BlockWidth[361] = 64;\n    BlockWidth[364] = 64;\n    BlockWidth[365] = 64;\n    BlockWidth[366] = 64;\n    BlockWidth[367] = 64;\n    BlockWidth[368] = 64;\n    BlockHeight[376] = 64;\n    BlockHeight[377] = 64;\n    BlockWidth[378] = 64;\n    BlockHeight[378] = 64;\n    BlockWidth[472] = 64;\n    BlockHeight[472] = 32;\n    BlockWidth[474] = 64;\n    BlockHeight[474] = 32;\n    BlockWidth[476] = 64;\n    BlockHeight[476] = 32;\n    BlockWidth[479] = 64;\n    BlockHeight[479] = 32;\n    BlockWidth[505] = 64;\n    BlockHeight[505] = 32;\n    BlockWidth[506] = 64;\n    BlockHeight[506] = 64;\n\n    BlockWidth[613] = 64;\n\n    BlockWidth[507] = 64;\n    BlockHeight[507] = 32;\n\n    BlockWidth[599] = 64;\n    BlockHeight[599] = 64;\n\n\n    BlockWidth[508] = 64;\n    BlockHeight[508] = 32;\n\n    BlockWidth[529] = 32;\n    BlockHeight[529] = 64;\n\n    BlockWidth[527] = 64;\n    BlockHeight[527] = 96;\n\n\n\n\n    BlockWidth[534] = 48;\n    BlockHeight[534] = 128;\n    BlockWidth[535] = 48;\n    BlockHeight[535] = 128;\n    BlockWidth[536] = 128;\n    BlockHeight[536] = 48;\n    BlockWidth[537] = 128;\n    BlockHeight[537] = 48;\n    BlockWidth[540] = 64;\n\n\n\n\n    BlockSlope[472] = -1;\n    BlockSlope[474] = 1;\n    BlockSlope2[476] = 1;\n    BlockSlope2[479] = -1;\n\n\n    BlockSlope2[77] = 1;\n    BlockSlope2[78] = 1;\n    BlockSlope2[613] = -1;\n    BlockSlope2[614] = -1;\n\n\n    BlockSlope[480] = -1;\n    BlockSlope[482] = 1;\n    BlockSlope2[486] = 1;\n    BlockSlope2[485] = -1;\n\n\n    BlockOnlyHitspot1[372] = true;\n    BlockOnlyHitspot1[373] = true;\n    BlockOnlyHitspot1[374] = true;\n    BlockOnlyHitspot1[375] = true;\n    BlockOnlyHitspot1[379] = true;\n    BlockOnlyHitspot1[380] = true;\n    BlockOnlyHitspot1[381] = true;\n    BlockOnlyHitspot1[382] = true;\n    BlockOnlyHitspot1[389] = true;\n    BlockOnlyHitspot1[391] = true;\n    BlockOnlyHitspot1[392] = true;\n\n    BlockOnlyHitspot1[506] = true;\n    BlockOnlyHitspot1[507] = true;\n    BlockOnlyHitspot1[508] = true;\n\n    BlockKills[371] = true;\n    BlockKills[404] = true;\n    BlockKills[406] = true;\n    BlockKills[405] = true;\n    BlockKills[420] = true;\n    BlockHasNoMask[336] = true;\n    BlockHasNoMask[337] = true;\n    BlockHasNoMask[338] = true;\n    BlockHasNoMask[339] = true;\n    BlockHasNoMask[303] = true;\n    BlockHasNoMask[304] = true;\n    BlockHasNoMask[348] = true;\n    BlockHasNoMask[353] = true;\n    BlockHasNoMask[354] = true;\n    BlockHasNoMask[355] = true;\n    BlockHasNoMask[356] = true;\n    BlockHasNoMask[3] = true;\n    BlockHasNoMask[4] = true;\n    BlockHasNoMask[13] = true;\n    BlockHasNoMask[15] = true;\n    BlockHasNoMask[16] = true;\n    BlockHasNoMask[17] = true;\n    BlockHasNoMask[19] = true;\n    BlockHasNoMask[21] = true;\n    BlockHasNoMask[34] = true;\n    BlockHasNoMask[36] = true;\n    BlockHasNoMask[23] = true;\n    BlockHasNoMask[29] = true;\n    BlockHasNoMask[40] = true;\n    BlockHasNoMask[43] = true;\n    BlockHasNoMask[46] = true;\n    BlockHasNoMask[47] = true;\n    BlockHasNoMask[48] = true;\n    BlockHasNoMask[49] = true;\n    BlockHasNoMask[50] = true;\n    BlockHasNoMask[51] = true;\n    BlockHasNoMask[52] = true;\n    BlockHasNoMask[53] = true;\n    BlockHasNoMask[54] = true;\n    BlockHasNoMask[59] = true;\n    BlockHasNoMask[60] = true;\n    BlockHasNoMask[61] = true;\n    BlockHasNoMask[63] = true;\n    BlockHasNoMask[65] = true;\n    BlockHasNoMask[67] = true;\n    BlockHasNoMask[70] = true;\n    BlockHasNoMask[71] = true;\n    BlockHasNoMask[72] = true;\n    BlockHasNoMask[73] = true;\n    BlockHasNoMask[74] = true;\n    BlockHasNoMask[75] = true;\n    BlockHasNoMask[76] = true;\n    BlockHasNoMask[81] = true;\n    BlockHasNoMask[83] = true;\n    BlockHasNoMask[84] = true;\n    BlockHasNoMask[85] = true;\n    BlockHasNoMask[86] = true;\n    BlockHasNoMask[87] = true;\n    BlockHasNoMask[91] = true;\n    BlockHasNoMask[93] = true;\n    BlockHasNoMask[94] = true;\n    BlockHasNoMask[95] = true;\n    BlockHasNoMask[96] = true;\n    BlockHasNoMask[97] = true;\n    BlockHasNoMask[98] = true;\n    BlockHasNoMask[99] = true;\n    BlockHasNoMask[100] = true;\n    BlockHasNoMask[101] = true;\n    BlockHasNoMask[102] = true;\n    BlockHasNoMask[103] = true;\n    BlockHasNoMask[111] = true;\n    BlockHasNoMask[118] = true;\n    BlockHasNoMask[119] = true;\n    BlockHasNoMask[120] = true;\n    BlockHasNoMask[121] = true;\n    BlockHasNoMask[122] = true;\n    BlockHasNoMask[123] = true;\n    BlockHasNoMask[124] = true;\n    BlockHasNoMask[125] = true;\n    BlockHasNoMask[126] = true;\n    BlockHasNoMask[127] = true;\n    BlockHasNoMask[131] = true;\n    BlockHasNoMask[134] = true;\n    BlockHasNoMask[136] = true;\n    BlockHasNoMask[159] = true;\n    BlockHasNoMask[160] = true;\n    BlockHasNoMask[166] = true;\n    BlockHasNoMask[183] = true;\n    BlockHasNoMask[184] = true;\n    BlockHasNoMask[186] = true;\n    BlockHasNoMask[187] = true;\n    BlockHasNoMask[188] = true;\n    BlockHasNoMask[190] = true;\n    BlockHasNoMask[198] = true;\n    BlockHasNoMask[199] = true;\n    BlockHasNoMask[200] = true;\n    BlockHasNoMask[201] = true;\n    BlockHasNoMask[202] = true;\n    BlockHasNoMask[203] = true;\n    BlockHasNoMask[204] = true;\n    BlockHasNoMask[205] = true;\n    BlockHasNoMask[206] = true;\n    BlockHasNoMask[216] = true;\n    BlockHasNoMask[217] = true;\n    BlockHasNoMask[218] = true;\n    BlockHasNoMask[223] = true;\n    BlockHasNoMask[226] = true;\n    BlockHasNoMask[227] = true;\n    BlockHasNoMask[228] = true;\n    BlockHasNoMask[229] = true;\n    BlockHasNoMask[230] = true;\n    BlockHasNoMask[231] = true;\n    BlockHasNoMask[232] = true;\n    BlockHasNoMask[233] = true;\n    BlockHasNoMask[234] = true;\n    BlockHasNoMask[235] = true;\n    BlockHasNoMask[236] = true;\n    BlockHasNoMask[237] = true;\n    BlockHasNoMask[238] = true;\n    BlockHasNoMask[239] = true;\n    BlockHasNoMask[250] = true;\n    BlockHasNoMask[251] = true;\n    BlockHasNoMask[252] = true;\n    BlockHasNoMask[253] = true;\n    BlockHasNoMask[254] = true;\n    BlockHasNoMask[255] = true;\n    BlockHasNoMask[256] = true;\n    BlockHasNoMask[257] = true;\n    BlockHasNoMask[258] = true;\n    // BlockHasNoMask[261] = true; // invalid, Block 261 is sizable and this flag is never read for sizable blocks in SMBX 1.3\n    BlockHasNoMask[262] = true;\n    BlockHasNoMask[264] = true;\n    BlockHasNoMask[263] = true;\n    BlockHasNoMask[273] = true;\n    BlockHasNoMask[272] = true;\n    BlockHasNoMask[276] = true;\n    BlockHasNoMask[281] = true;\n    BlockHasNoMask[282] = true;\n    BlockHasNoMask[283] = true;\n    BlockHasNoMask[291] = true;\n    BlockHasNoMask[292] = true;\n    BlockHasNoMask[320] = true;\n    BlockHasNoMask[322] = true;\n    BlockHasNoMask[323] = true;\n    BlockHasNoMask[330] = true;\n    BlockHasNoMask[331] = true;\n    BlockHasNoMask[369] = true;\n    BlockHasNoMask[370] = true;\n    BlockOnlyHitspot1[8] = true;\n    BlockOnlyHitspot1[121] = true;\n    BlockOnlyHitspot1[122] = true;\n    BlockOnlyHitspot1[123] = true;\n    BlockOnlyHitspot1[168] = true;\n    BlockOnlyHitspot1[289] = true;\n    BlockOnlyHitspot1[290] = true;\n    BlockOnlyHitspot1[370] = true;\n\n    BlockKills[30] = true;\n    BlockHurts[109] = true;\n    BlockHurts[598] = true;\n    BlockHurts[110] = true;\n    BlockHurts[267] = true;\n    BlockHurts[268] = true;\n    BlockHurts[269] = true;\n    BlockHurts[511] = true;\n    BlockPSwitch[4] = true;\n    BlockPSwitch[60] = true;\n    BlockPSwitch[89] = true;\n    BlockPSwitch[188] = true;\n    BlockPSwitch[280] = true;\n    BlockPSwitch[293] = true;\n    BlockNoClipping[172] = true;\n    BlockNoClipping[175] = true;\n    BlockNoClipping[178] = true;\n    BlockNoClipping[181] = true;\n    BlockSlope[299] = -1;\n    BlockSlope[300] = 1;\n    BlockSlope[301] = 1;\n    BlockSlope[302] = -1;\n    BlockSlope[305] = -1;\n    BlockSlope[306] = -1;\n    BlockSlope[307] = 1;\n    BlockSlope[308] = 1;\n    BlockSlope[324] = -1;\n    BlockSlope[325] = 1;\n\n    BlockSlope2[528] = 1;\n    BlockSlope2[523] = -1;\n\n    BlockSlope2[309] = 1;\n    BlockSlope2[310] = -1;\n    BlockSlope2[311] = 1;\n    BlockSlope2[312] = 1;\n    BlockSlope2[313] = -1;\n    BlockSlope2[314] = -1;\n    BlockSlope[315] = 1;\n    BlockSlope[316] = -1;\n    BlockSlope2[317] = 1;\n    BlockSlope2[318] = -1;\n    BlockSlope[319] = 1;\n    BlockSlope[321] = -1;\n    BlockSlope[326] = -1;\n    BlockSlope[327] = 1;\n    BlockSlope2[328] = -1;\n    BlockSlope2[329] = 1;\n    BlockSlope[332] = -1;\n    BlockSlope[333] = 1;\n    BlockSlope2[334] = -1;\n    BlockSlope2[335] = 1;\n    BlockSlope[340] = -1;\n    BlockSlope[341] = -1;\n    BlockSlope[342] = 1;\n    BlockSlope[343] = 1;\n    BlockSlope[357] = -1;\n    BlockSlope[358] = -1;\n    BlockSlope[359] = 1;\n    BlockSlope[360] = 1;\n    BlockSlope2[361] = 1;\n    BlockSlope2[362] = 1;\n    BlockSlope2[363] = -1;\n    BlockSlope2[364] = -1;\n    BlockSlope[365] = -1;\n    BlockSlope[366] = 1;\n    BlockSlope2[367] = -1;\n    BlockSlope2[368] = 1;\n\n    BlockKills[459] = true;\n\n    BlockKills[460] = true;\n    BlockKills[461] = true;\n    BlockKills[462] = true;\n    BlockKills[463] = true;\n    BlockKills[464] = true;\n    BlockKills[465] = true;\n    BlockKills[466] = true;\n    BlockKills[467] = true;\n    BlockKills[468] = true;\n    BlockKills[469] = true;\n    BlockKills[470] = true;\n    BlockKills[471] = true;\n\n    BlockKills2[460] = true;\n    BlockKills2[461] = true;\n    BlockKills2[462] = true;\n    BlockKills2[463] = true;\n    BlockKills2[464] = true;\n    BlockKills2[465] = true;\n    BlockKills2[466] = true;\n    BlockKills2[467] = true;\n    BlockKills[472] = true;\n    BlockKills[473] = true;\n    BlockKills[474] = true;\n    BlockKills[475] = true;\n    BlockKills[476] = true;\n    BlockKills[478] = true;\n    BlockKills[479] = true;\n    BlockKills2[472] = true;\n    BlockKills2[474] = true;\n    BlockKills2[476] = true;\n    BlockKills2[479] = true;\n\n    BlockKills[480] = true;\n    BlockKills[481] = true;\n    BlockKills[482] = true;\n    BlockKills[483] = true;\n    BlockKills[484] = true;\n    BlockKills[485] = true;\n    BlockKills[486] = true;\n    BlockKills[487] = true;\n    BlockKills2[480] = true;\n    BlockKills2[482] = true;\n    BlockKills2[485] = true;\n    BlockKills2[486] = true;\n\n\n\n    BlockHurts[407] = true;\n    BlockHurts[408] = true;\n    BlockHurts[428] = true;\n    BlockHurts[429] = true;\n    BlockHurts[430] = true;\n    BlockHurts[431] = true;\n    BlockHurts[511] = true;\n    BlockOnlyHitspot1[447] = true;\n    BlockOnlyHitspot1[446] = true;\n    BlockOnlyHitspot1[448] = true;\n\n\n\n\n    BlockWidth[604] = 64;\n    BlockWidth[605] = 64;\n\n    BlockSlope[600] = -1;\n    BlockSlope[604] = -1;\n    BlockSlope[601] = 1;\n    BlockSlope[605] = 1;\n\n\n    BlockSlope[451] = 1;\n    BlockSlope[452] = -1;\n    For(A, 459, 487)\n    {\n        BlockKills[A] = true;\n    }\n    BackgroundHasNoMask[187] = true;\n    BackgroundHasNoMask[188] = true;\n    BackgroundHasNoMask[189] = true;\n    BackgroundHasNoMask[190] = true;\n\n    BackgroundHasNoMask[172] = true;\n    BackgroundHasNoMask[167] = true;\n    BackgroundHasNoMask[164] = true;\n    BackgroundHasNoMask[165] = true;\n    BackgroundHasNoMask[158] = true;\n    BackgroundHasNoMask[146] = true;\n    BackgroundHasNoMask[12] = true;\n    BackgroundHasNoMask[14] = true;\n    BackgroundHasNoMask[15] = true;\n    BackgroundHasNoMask[22] = true;\n    BackgroundHasNoMask[30] = true;\n    BackgroundHasNoMask[39] = true;\n    BackgroundHasNoMask[40] = true;\n    BackgroundHasNoMask[41] = true;\n    BackgroundHasNoMask[42] = true;\n    BackgroundHasNoMask[43] = true;\n    BackgroundHasNoMask[44] = true;\n    BackgroundHasNoMask[47] = true;\n    BackgroundHasNoMask[52] = true;\n    BackgroundHasNoMask[53] = true;\n    BackgroundHasNoMask[55] = true;\n    BackgroundHasNoMask[56] = true;\n    BackgroundHasNoMask[60] = true;\n    BackgroundHasNoMask[61] = true;\n    BackgroundHasNoMask[64] = true;\n    BackgroundHasNoMask[75] = true;\n    BackgroundHasNoMask[76] = true;\n    BackgroundHasNoMask[77] = true;\n    BackgroundHasNoMask[78] = true;\n    BackgroundHasNoMask[79] = true;\n    BackgroundHasNoMask[83] = true;\n    BackgroundHasNoMask[87] = true;\n    BackgroundHasNoMask[88] = true;\n    BackgroundHasNoMask[91] = true;\n    BackgroundHasNoMask[98] = true;\n    BackgroundHasNoMask[99] = true;\n    BackgroundHasNoMask[107] = true;\n    BackgroundHasNoMask[115] = true;\n    BackgroundHasNoMask[116] = true;\n    BackgroundHasNoMask[117] = true;\n    BackgroundHasNoMask[118] = true;\n    BackgroundHasNoMask[119] = true;\n    BackgroundHasNoMask[122] = true;\n    BackgroundHasNoMask[123] = true;\n    BackgroundHasNoMask[124] = true;\n\n    BackgroundHasNoMask[139] = true;\n    BackgroundHasNoMask[140] = true;\n    BackgroundHasNoMask[141] = true;\n    BackgroundHasNoMask[144] = true;\n    BackgroundHasNoMask[145] = true;\n    BlockOnlyHitspot1[69] = true;\n\n\n    Foreground[187] = true;\n    Foreground[188] = true;\n    Foreground[143] = true;\n    // Foreground[165] = true;\n    Foreground[145] = true;\n    Foreground[23] = true;\n    Foreground[24] = true;\n    Foreground[25] = true;\n    Foreground[45] = true;\n    Foreground[46] = true;\n    Foreground[49] = true;\n    Foreground[50] = true;\n    Foreground[51] = true;\n    // Foreground[65] = true;\n    Foreground[68] = true;\n    Foreground[69] = true;\n    Foreground[106] = true;\n    Foreground[137] = true;\n    Foreground[138] = true;\n\n    Foreground[154] = true;\n    Foreground[155] = true;\n    Foreground[156] = true;\n    Foreground[157] = true;\n\n    BackgroundHeight[158] = 32;\n    BackgroundHeight[159] = 32;\n\n    BackgroundHeight[187] = 32;\n    BackgroundHeight[188] = 32;\n    BackgroundHeight[189] = 32;\n    BackgroundHeight[190] = 32;\n\n\n\n    BackgroundHeight[170] = 32;\n    BackgroundHeight[171] = 32;\n\n    BackgroundHeight[26] = 64;\n    BackgroundHeight[18] = 32;\n    BackgroundHeight[19] = 32;\n    BackgroundHeight[20] = 32;\n    BackgroundHeight[36] = 96;\n    BackgroundHeight[65] = 96;\n\n    BackgroundHeight[66] = 32;\n\n    BackgroundHeight[68] = 64;\n    BackgroundHeight[70] = 32;\n    BackgroundHeight[172] = 32;\n    BackgroundHeight[82] = 32;\n    BackgroundHeight[100] = 32;\n    BackgroundHeight[125] = 64;\n    BackgroundHeight[134] = 32;\n    BackgroundHeight[135] = 32;\n    BackgroundHeight[136] = 32;\n    BackgroundHeight[137] = 32;\n    BackgroundHeight[138] = 32;\n    BackgroundWidth[163] = 64;\n\n    BackgroundWidth[173] = 112;\n    BackgroundHeight[173] = 120;\n\n\n    BackgroundWidth[160] = 24;\n    BackgroundHeight[160] = 24;\n\n    BackgroundWidth[161] = 64;\n    BackgroundHeight[161] = 48;\n\n    BackgroundHeight[168] = 32;\n    BackgroundWidth[168] = 64;\n\n    BackgroundHeight[169] = 64;\n    BackgroundWidth[169] = 64;\n    BackgroundHasNoMask[169] = true;\n\n\n\n    For(A, 1, maxSceneType)\n    {\n        SceneWidth[A] = 32;\n        SceneHeight[A] = 32;\n        if((A >= 15 && A <= 18) || (A == 21) || (A == 24) || (A == 58) || (A == 59) || (A == 63))\n        {\n            SceneWidth[A] = 16;\n            SceneHeight[A] = 16;\n        }\n    }\n    SceneWidth[20] = 64;\n    SceneHeight[20] = 64;\n    SceneWidth[27] = 48;\n    SceneHeight[27] = 16;\n    SceneWidth[28] = 48;\n    SceneHeight[28] = 16;\n    SceneWidth[29] = 64;\n    SceneHeight[29] = 16;\n    SceneWidth[30] = 64;\n    SceneHeight[30] = 16;\n    SceneWidth[33] = 14;\n    SceneHeight[33] = 14;\n    SceneWidth[34] = 14;\n    SceneHeight[34] = 14;\n    SceneWidth[44] = 64;\n    SceneHeight[50] = 48;\n    SceneWidth[50] = 64;\n    SceneWidth[54] = 30;\n    SceneHeight[54] = 24;\n    SceneWidth[55] = 30;\n    SceneHeight[55] = 24;\n    SceneWidth[57] = 64;\n    SceneHeight[57] = 64;\n    SceneWidth[60] = 48;\n    SceneHeight[60] = 48;\n    SceneWidth[61] = 64;\n    SceneHeight[61] = 76;\n\n    For(A, 1, maxTileType)\n    {\n        TileWidth[A] = 32;\n        TileHeight[A] = 32;\n    }\n    TileWidth[8] = 64;\n    TileHeight[8] = 64;\n    TileWidth[9] = 96;\n    TileHeight[9] = 96;\n    TileWidth[12] = 64;\n    TileHeight[12] = 64;\n    TileWidth[13] = 96;\n    TileHeight[13] = 96;\n    TileWidth[27] = 128;\n    TileHeight[27] = 128;\n    TileWidth[325] = 64;\n    TileHeight[325] = 64;\n    Points[1] = 10;\n    Points[2] = 100;\n    Points[3] = 200;\n    Points[4] = 400;\n    Points[5] = 800;\n    Points[6] = 1000;\n    Points[7] = 2000;\n    Points[8] = 4000;\n    Points[9] = 8000;\n    Points[10] = 1;\n    Points[11] = 2;\n    Points[12] = 3;\n    Points[13] = 5;\n    For(A, 1, maxNPCType)\n    {\n        if(NPCTraits[A].IsFish)\n            NPCTraits[A].Foreground = true;\n    }\n    For(A, 1, maxEffectType)\n    {\n#ifndef LOW_MEM\n        // kept only for debugging that it's safe to remove them\n        EffectDefaults.EffectHeight[A] = EffectHeight[A];\n        EffectDefaults.EffectWidth[A] = EffectWidth[A];\n#endif // #ifndef LOW_MEM\n\n        if(EffectHeight[A] > 0)\n        {\n            // EffectDefaults.EffectFrames[A] = vb6Round(double(GFXEffectBMP[A].h) / EffectHeight[A]);\n            EffectDefaults.EffectFrames[A] = (GFXEffectBMP[A].h * 2 + EffectHeight[A]) / (EffectHeight[A] * 2);\n            if(EffectDefaults.EffectFrames[A] <= 0)\n                EffectDefaults.EffectFrames[A] = 1;\n        }\n        else\n            EffectDefaults.EffectFrames[A] = 1;\n    }\n    SaveNPCDefaults();\n    SavePlayerDefaults();\n}\n"
  },
  {
    "path": "src/main/speedrunner.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include <fmt_format_ne.h>\n#include \"speedrunner.h\"\n#include \"globals.h\"\n#include \"graphics.h\"\n#include \"core/render.h\"\n#include \"config.h\"\n#include \"../controls.h\"\n\n#include \"main/screen_quickreconnect.h\"\n#include \"main/menu_main.h\"\n\n#include \"gameplay_timer.h\"\n\nstatic      GameplayTimer s_gamePlayTimer;\n\nvoid speedRun_loadStats()\n{\n    if(!g_config.enable_playtime_tracking)\n        return; // Do nothing\n\n    s_gamePlayTimer.load();\n}\n\nvoid speedRun_saveStats()\n{\n    if(!g_config.enable_playtime_tracking)\n        return; // Do nothing\n\n    if(GameMenu || GameOutro || BattleMode)\n        return; // Do nothing when out of the game\n\n    s_gamePlayTimer.save();\n}\n\nvoid speedRun_resetCurrent()\n{\n    if(!g_config.enable_playtime_tracking)\n        return; // Do nothing\n\n    s_gamePlayTimer.resetCurrent();\n}\n\nvoid speedRun_resetTotal()\n{\n    if(!g_config.enable_playtime_tracking)\n        return; // Do nothing\n\n    s_gamePlayTimer.reset();\n}\n\nstatic constexpr XTColor s_green  = XTColorF(0.0_n, 1.0_n, 0.0_n);\nstatic constexpr XTColor s_blue   = XTColorF(0.0_n, 0.0_n, 1.0_n);\nstatic constexpr XTColor s_red    = XTColorF(1.0_n, 0.0_n, 0.0_n);\nstatic constexpr XTColor s_yellow = XTColorF(0.9_n, 0.9_n, 0.0_n);\nstatic constexpr XTColor s_gray   = XTColorF(0.9_n, 0.9_n, 0.9_n);\nstatic constexpr XTColor s_legacy = XTColorF(0.7_n, 0.7_n, 0.7_n);\n\nstatic constexpr inline XTColor bool2(XTColor color, bool btn, uint8_t alpha)\n{\n    return btn ? color.with_alpha(alpha) : XTColor(32, 32, 32, alpha);\n}\n\nstatic Controls_t s_displayControls[maxLocalPlayers] = {Controls_t()};\n\n\nvoid speedRun_renderTimer()\n{\n    if((g_config.speedrun_mode == SPEEDRUN_MODE_OFF && g_config.show_playtime_counter == Config_t::PLAYTIME_COUNTER_OFF) || !g_config.enable_playtime_tracking)\n        return; // Do nothing\n\n    if(g_config.speedrun_mode != SPEEDRUN_MODE_OFF)\n    {\n        // place speedrun mode below game version display on main menu\n        int topY = (GameMenu) ? 22 : 2;\n\n        SuperPrintRightAlign(fmt::format_ne(Cheater ? \"CMode {0}\" : \"Mode {0}\", g_config.speedrun_mode), 3, XRender::TargetW - XRender::TargetOverscanX - 2, topY, XTColorF(1.0_n, 0.3_n, 0.3_n, 0.5_n));\n\n        if(g_config.allow_multires)\n            SuperPrintRightAlign(fmt::format_ne(\"{0}x{1}\", l_screen->W, l_screen->H), 3, XRender::TargetW - XRender::TargetOverscanX - 2, topY + 20, XTColorF(1.0_n, 0.3_n, 0.3_n, 0.5_n));\n    }\n\n    if(GameMenu || GameOutro || BattleMode || LevelEditor)\n        return; // Don't draw things at Menu and Outro\n\n    s_gamePlayTimer.render();\n}\n\nstatic void GetControllerColor(int l_player_i, XTColor& color, bool* drawLabel = nullptr)\n{\n    color = XTColorF(0.4_n, 0.4_n, 0.4_n);\n    if(drawLabel)\n        *drawLabel = false;\n\n    if(GameMenu)\n        return;\n\n    if(l_player_i < 0 || l_player_i >= l_screen->player_count)\n        return;\n\n    int player = l_screen->players[l_player_i];\n\n    if(player < 1 || player > numPlayers)\n        return;\n\n    if(numPlayers > 1 && !g_ClonedPlayerMode)\n    {\n        auto &p = Player[player];\n\n        switch(p.Character) // TODO: Add changing of these colors by gameinfo.ini\n        {\n        case 1:\n            color = XTColorF(0.7_n, 0.3_n, 0.3_n);\n            break;\n        case 2:\n            color = XTColorF(0.3_n, 0.7_n, 0.3_n);\n            break;\n        case 3:\n            color = XTColorF(1.0_n, 0.6_n, 0.7_n);\n            break;\n        case 4:\n            color = XTColorF(0.04_n, 0.43_n, 1.0_n);\n            break;\n        case 5:\n            color = {192, 168, 72};\n            break;\n        }\n\n        if(drawLabel)\n            *drawLabel = true;\n    }\n}\n\nvoid RenderControls_priv(int player, const Controls_t* controls, int x, int y, int w, int h, bool missing, uint8_t alpha, bool connect_screen)\n{\n    if(!controls && (player < 0 || player >= maxLocalPlayers))\n        return;\n\n    uint8_t alphaBtn = missing ? alpha / 2 : alpha;\n    uint8_t alphaText = alpha / 2;\n\n    XTColor color;\n    bool drawLabel = false;\n\n    if(!connect_screen)\n        GetControllerColor(player, color, &drawLabel);\n    else if(player == -1)\n        color = XTColorF(0.4_n, 0.4_n, 0.4_n);\n\n    XRender::renderRect(x, y, w, h, {0, 0, 0, alpha}, true);//Edge\n    XRender::renderRect(x + 2, y + 2, w - 4, h - 4, {color, alpha}, true);//Box\n\n    if(missing)\n    {\n        num_t tick = 2 * num_t::PI() * (CommonFrame % 128) / 128;\n        num_t coord = (num_t::sin(tick) + 1) / 2 + 0.25_n;\n        alphaBtn = (uint8_t)(coord * alphaBtn);\n        alphaText = (uint8_t)(coord * alphaText);\n    }\n\n    const Controls_t& c = (controls) ? *controls : s_displayControls[player];\n\n    XRender::renderRect(x + 10, y + 12, 6, 6, {32, 32, 32, alphaBtn}, true);//Cender of D-Pad\n\n    XRender::renderRect(x + 10, y + 6, 6, 6, bool2(s_gray, c.Up, alphaBtn), true);\n    XRender::renderRect(x + 10, y + 18, 6, 6, bool2(s_gray, c.Down, alphaBtn), true);\n    XRender::renderRect(x + 4, y + 12, 6, 6, bool2(s_gray, c.Left, alphaBtn), true);\n    XRender::renderRect(x + 16, y + 12, 6, 6, bool2(s_gray, c.Right, alphaBtn), true);\n\n    // This was suggested before, but perhaps don't do it: AltJump is still used for dismounting.\n    // auto altjump_color = (g_config.disable_spin_jump) ? s_green : s_red;\n\n    XRender::renderRect(x + 64, y + 18, 6, 6, bool2(s_green, c.Jump, alphaBtn), true);\n    XRender::renderRect(x + 66, y + 8, 6, 6, bool2(s_red, c.AltJump, alphaBtn), true);\n    XRender::renderRect(x + 54, y + 16, 6, 6, bool2(s_blue, c.Run, alphaBtn), true);\n    XRender::renderRect(x + 56, y + 6, 6, 6, bool2(s_yellow, c.AltRun, alphaBtn), true);\n\n    XRender::renderRect(x + 26, y + 22, 10, 4, bool2(s_gray, c.Drop, alphaBtn), true);\n    if(SharedPauseLegacy)\n        XRender::renderRect(x + 40, y + 22, 10, 4, bool2(s_legacy, c.Start, alphaBtn), true);\n    else\n        XRender::renderRect(x + 40, y + 22, 10, 4, bool2(s_gray, c.Start, alphaBtn), true);\n\n    if(!connect_screen && (drawLabel || missing))\n    {\n        const char* label_fmt = (missing ? \"P{0}?\" : \"P{0}\");\n        SuperPrintCenter(fmt::format_ne(label_fmt, player + 1), 3, x + w / 2, y + 2, XTAlpha(alphaText));\n    }\n    else if(missing)\n    {\n        SuperPrintCenter(\"?\", 3, x + w / 2, y + 4, XTAlpha(alphaText));\n    }\n}\n\nvoid RenderControls(int player, int x, int y, int w, int h, bool missing, uint8_t alpha, bool connect_screen)\n{\n    return RenderControls_priv(player, nullptr, x, y, w, h, missing, alpha, connect_screen);\n}\n\nvoid RenderControls(const Controls_t& controls, int x, int y, int w, int h, bool missing, uint8_t alpha)\n{\n    return RenderControls_priv(-1, &controls, x, y, w, h, missing, alpha, true);\n}\n\n\n\nvoid RenderPowerInfo(int player, int bx, int by, int bw, int bh, uint8_t alpha, const XPower::StatusInfo* status)\n{\n    // force to 2x\n    bx &= ~1;\n    by &= ~1;\n    bw &= ~1;\n    bh &= ~1;\n\n    uint8_t alphaBox = alpha;\n    uint8_t alphaBtn = alpha;\n\n    XTColor color;\n    GetControllerColor(player, color);\n    if(color.r >= 128 || color.g >= 128 || color.b >= 128)\n    {\n        // make it darker so it can be distinguished from segments\n        color.r /= 2;\n        color.g /= 2;\n        color.b /= 2;\n    }\n\n    XPower::StatusInfo status_info;\n\n    if(status)\n        status_info = *status;\n    else if(player >= 0 && player < maxLocalPlayers)\n        status_info = Controls::GetStatus(player);\n    else\n        status_info = XPower::devicePowerStatus();\n\n    // don't draw segments for states without battery info\n    if(status_info.power_status == XPower::StatusInfo::POWER_WIRED || status_info.power_status == XPower::StatusInfo::POWER_UNKNOWN)\n        status_info.power_level = 0;\n\n    if(status_info.power_status != XPower::StatusInfo::POWER_DISABLED)\n    {\n        XRender::renderRect(bx, by, bw - 4, bh, {0, 0, 0, alphaBox}, true);//Edge\n        XRender::renderRect(bx + 2, by + 2, bw - 8, bh - 4, {color, alphaBox}, true);//Box\n        XRender::renderRect(bx + 36, by + 6, 4, 10, {0, 0, 0, alphaBox}, true);//Edge\n        XRender::renderRect(bx + 34, by + 8, 4, 6, {color, alphaBox}, true);//Box\n\n        // segment count\n        int segments;\n\n        if(status_info.power_level > 0.90_nf)\n            segments = 4;\n        else if(status_info.power_level > 0.60_nf)\n            segments = 3;\n        else if(status_info.power_level > 0.30_nf)\n            segments = 2;\n        else\n            segments = 1;\n\n        // new color for the inner segments\n        color = XTColor(0, 0, 0);\n\n        if(status_info.power_level <= 0.5_nf)\n            color.r = (255 - uint8_t(status_info.power_level * 510));\n\n        if(status_info.power_status == XPower::StatusInfo::POWER_CHARGING)\n        {\n            color.g = 255;\n            color.g -= color.r;\n        }\n\n        if(status_info.power_status == XPower::StatusInfo::POWER_CHARGED)\n        {\n            color.b = XTColor::from_num(0.8_n);\n            color.g = XTColor::from_num(0.4_n);\n        }\n\n        // size for segment\n        int s;\n\n        switch(segments)\n        {\n        case 4:\n            // 2 is the width of the segment, 10 lets us scale to the last 0.1_nf\n            s = (int32_t)((status_info.power_level - 0.9_nf) * (2 * 10));\n            if(s > 2) s = 2;\n            // if(flash && status_info.power_status == XPower::StatusInfo::POWER_CHARGING)\n            //     s = 2;\n            XRender::renderRect(bx + 34, by + 10, s, 2, {color, alphaBtn}, true); // fallthrough\n        case 3:\n            // 4 is the width of the segment, 10 / 3 lets us scale to the 0.6-0.9 segment\n            s = (int32_t)((status_info.power_level - 0.6_nf) * (4 * 10) / 3);\n            if(s > 4) s = 4;\n            // if(flash && status_info.power_status == XPower::StatusInfo::POWER_CHARGING)\n            //     s = 4;\n            XRender::renderRect(bx + 24, by + 4, s*2, 14, {color, alphaBtn}, true); // fallthrough\n        case 2:\n            // 4 is the width of the segment, 10 / 3 lets us scale to the 0.3-0.6 segment\n            s = (int32_t)((status_info.power_level - 0.3_nf) * (4 * 10) / 3);\n            if(s > 4) s = 4;\n            // if(flash && status_info.power_status == XPower::StatusInfo::POWER_CHARGING)\n            //     s = 4;\n            XRender::renderRect(bx + 14, by + 4, s*2, 14, {color, alphaBtn}, true); // fallthrough\n        case 1:\n            // 4 is the width of the segment, 10 / 3 lets us scale to the 0.0-0.3 segment\n            s = (int32_t)(status_info.power_level * (4 * 10) / 3);\n            if(s > 4) s = 4;\n            // if(flash && status_info.power_status == XPower::StatusInfo::POWER_CHARGING)\n            //     s = 4;\n            XRender::renderRect(bx + 4, by + 4, s*2, 14, {color, alphaBtn}, true);\n            break;\n        }\n\n        if(status_info.power_status == XPower::StatusInfo::POWER_UNKNOWN)\n            SuperPrintCenter(\"?\", 3, (bx + bw / 2) & ~1, (by + bh / 2 - 8) & ~1, XTAlpha(alpha));\n\n        if(status_info.power_status == XPower::StatusInfo::POWER_WIRED)\n            SuperPrintCenter(\"W\", 3, (bx + bw / 2) & ~1, (by + bh / 2 - 8) & ~1, XTAlpha(alpha));\n\n        if(status_info.power_status == XPower::StatusInfo::POWER_CHARGING)\n            SuperPrintCenter(\"+\", 3, (bx + bw / 2) & ~1, (by + bh / 2 - 7) & ~1, XTAlpha(alpha));\n    }\n\n    if(status_info.info_string)\n        SuperPrintCenter(status_info.info_string, 3, bx + bw / 2, by - 30, XTAlpha(alpha));\n}\n\nvoid speedRun_renderControls(int player, int screenZ, int align)\n{\n    if(GameOutro || LevelEditor)\n        return; // Don't draw things at Editor and Outro\n\n    if(GameMenu && (!g_config.show_controllers || MenuMode == MENU_CHARACTER_SELECT_NEW || MenuMode == MENU_CHARACTER_SELECT_NEW_BM))\n        return;\n\n    // player is an index into the local screen's player array, not a global player index\n    if(player < 0 || player >= maxLocalPlayers)\n        return;\n\n    if(SingleCoop)\n        player = 0;\n\n    const bool player_missing = (player >= (int)Controls::g_InputMethods.size() || !Controls::g_InputMethods[player]);\n    const bool player_newly_connected = !player_missing && QuickReconnectScreen::g_active && QuickReconnectScreen::g_toast_duration[player];\n    XPower::StatusInfo status_info = Controls::GetStatus(player);\n\n    // Controller\n    int x, y;\n    int w = 76;\n    int h = 30;\n\n    // Battery status\n    int bx, by;\n    int bw = 40;\n    int bh = 22;\n\n    int num_players = 1;\n\n    if(screenZ >= 0)\n    {\n        auto &scr = vScreen[screenZ];\n        y = (int)scr.Height - 34;\n        by = y + 4;\n\n        if(align == SPEEDRUN_ALIGN_AUTO)\n            align = scr.Left > 0 ? SPEEDRUN_ALIGN_RIGHT : SPEEDRUN_ALIGN_LEFT;\n\n        // this keeps the locations fixed even when the vScreens expand/contract\n        if(align == SPEEDRUN_ALIGN_LEFT)\n        {\n            x = 4;\n            bx = x + w + 4;\n        }\n        else\n        {\n            x = (int)scr.Width - (w + 4);\n            bx = x - (bw + 4);\n        }\n    }\n    else\n    {\n        const Screen_t& plr_screen = *l_screen;\n        num_players = plr_screen.player_count;\n\n        int plr_i = player;\n\n        if(GameMenu)\n        {\n            num_players = SDL_min(num_players, (int)Controls::g_InputMethods.size());\n\n            if(plr_i >= num_players)\n                return;\n        }\n\n        if(align == SPEEDRUN_ALIGN_AUTO && !GameMenu)\n        {\n            if(plr_i == 0 || num_players <= 1)\n                align = SPEEDRUN_ALIGN_LEFT;\n            else if(plr_i >= num_players - 1)\n                align = SPEEDRUN_ALIGN_RIGHT;\n        }\n\n        if(align == SPEEDRUN_ALIGN_LEFT)\n        {\n            x = XRender::TargetOverscanX + 4;\n            bx = x + w + 4;\n        }\n        else if(align == SPEEDRUN_ALIGN_RIGHT)\n        {\n            x = (XRender::TargetW - XRender::TargetOverscanX - (w + 4));\n            bx = x - (bw + 4);\n        }\n        else if(GameMenu)\n        {\n            x = XRender::TargetOverscanX + 4 + (w + 4) * plr_i;\n            bx = x + w + 4;\n        }\n        else\n        {\n            int display_w = (status_info.power_status != XPower::StatusInfo::POWER_DISABLED) ? (w + 4 + bw + 4) : (w + 4);\n\n            x = (XRender::TargetW - display_w) * plr_i / (num_players - 1);\n            bx = x + (w + 4);\n        }\n\n        y = XRender::TargetH - 34;\n        by = y + 4;\n    }\n\n    bool show_always = g_config.show_controllers;\n    bool show_controls = false;\n\n    // render controls if enabled or quick-reconnect logic is active\n    if(show_always || player_missing || player_newly_connected)\n    {\n        show_controls = true;\n    }\n    else\n    {\n        // reposition battery correctly\n        if(x < bx)\n            bx = x;\n        else\n            bx = x + w - bw;\n    }\n\n    uint8_t controls_alpha = 255;\n    uint8_t battery_alpha = 255;\n\n    if(player_newly_connected)\n    {\n        const Controls::InputMethod* input_method = Controls::g_InputMethods[player];\n\n        const std::string& profile_name = (input_method->Profile ? input_method->Profile->Name : \"\");\n\n        uint8_t alpha = 255;\n        int toast_duration = QuickReconnectScreen::g_toast_duration[player];\n        int time_from_edge = SDL_min(toast_duration, QuickReconnectScreen::MAX_TOAST_DURATION - toast_duration);\n\n        if(time_from_edge < 33)\n        {\n            num_t linear_coord = num_t::PI() * (33 - time_from_edge) / 33;\n            XTColor alpha = XTAlphaF(num_t::cos(linear_coord) / 2 + 0.5_n);\n\n            if(!show_always && toast_duration < 33)\n                controls_alpha = alpha.a;\n\n            // battery just appeared\n            if(toast_duration > 33)\n                battery_alpha = alpha.a;\n        }\n\n        // code for lower-resolution case\n        if(XRender::TargetW < 600 || (XRender::TargetW < 800 && status_info.power_status != XPower::StatusInfo::POWER_DISABLED) || num_players > 2)\n        {\n            if(align == SPEEDRUN_ALIGN_RIGHT)\n                SuperPrintRightAlign(profile_name, 3, x + w, y - 20, XTAlpha(alpha));\n            else if(align == SPEEDRUN_ALIGN_LEFT)\n                SuperPrint(profile_name, 3, x, y - 20, XTAlpha(alpha));\n            else\n                SuperPrintCenter(profile_name, 3, x + w / 2, y - 20, XTAlpha(alpha));\n        }\n        // code for higher resolution case, including battery\n        else if(status_info.power_status != XPower::StatusInfo::POWER_DISABLED)\n        {\n            if(align == SPEEDRUN_ALIGN_RIGHT)\n                SuperPrintRightAlign(profile_name, 3, bx - 4, by + 2, XTAlpha(alpha));\n            else if(align == SPEEDRUN_ALIGN_LEFT)\n                SuperPrint(profile_name, 3, bx + bw + 4, by + 2, XTAlpha(alpha));\n            else\n                SuperPrintCenter(profile_name, 3, x + w, y - 20, XTAlpha(alpha));\n        }\n        // code for normal case\n        else\n        {\n            if(align == SPEEDRUN_ALIGN_RIGHT)\n                SuperPrintRightAlign(profile_name, 3, x - 4, by + 2, XTAlpha(alpha));\n            else if(align == SPEEDRUN_ALIGN_LEFT)\n                SuperPrint(profile_name, 3, x + w + 4, by + 2, XTAlpha(alpha));\n            else\n                SuperPrintCenter(profile_name, 3, x + w / 2, y - 20, XTAlpha(alpha));\n        }\n    }\n\n    if(!GameMenu)\n        RenderPowerInfo(player, bx, by, bw, bh, battery_alpha, &status_info);\n\n    if(show_controls)\n        RenderControls(player, x, y, w, h, player_missing, controls_alpha);\n}\n\nvoid speedRun_tick()\n{\n    if(!g_config.enable_playtime_tracking)\n        return; // Do nothing\n\n    s_gamePlayTimer.tick();\n}\n\nvoid speedRun_triggerEnter()\n{\n    if(!g_config.enable_playtime_tracking)\n        return; // Do nothing\n\n    if(g_config.speedrun_stop_timer_by != Config_t::SPEEDRUN_STOP_ENTER_LEVEL)\n        return;\n\n    if(SDL_strcasecmp(FileName.c_str(), static_cast<const std::string&>(g_config.speedrun_stop_timer_at).c_str()) == 0)\n        speedRun_bossDeadEvent();\n}\n\nvoid speedRun_triggerLeave()\n{\n    if(!g_config.enable_playtime_tracking)\n        return; // Do nothing\n\n    if(g_config.speedrun_stop_timer_by != Config_t::SPEEDRUN_STOP_LEAVE_LEVEL)\n        return;\n\n    if(SDL_strcasecmp(FileName.c_str(), static_cast<const std::string&>(g_config.speedrun_stop_timer_at).c_str()) == 0)\n        speedRun_bossDeadEvent();\n}\n\n\nvoid speedRun_bossDeadEvent()\n{\n    if(!g_config.enable_playtime_tracking)\n        return; // Do nothing\n\n    if(GameMenu || GameOutro || BattleMode)\n        return; // Don't draw things at Menu and Outro\n\n    s_gamePlayTimer.onBossDead();\n}\n\nvoid speedRun_syncControlKeys(int plr, const Controls_t &keys)\n{\n    // there are still reasons to sync control keys (eg control tests)\n    // if(g_speedRunnerMode == SPEEDRUN_MODE_OFF && !g_drawController)\n    //     return; // Do nothing\n\n    SDL_assert(plr >= 0 && plr < maxLocalPlayers);\n    SDL_memcpy(&s_displayControls[plr], &keys, sizeof(Controls_t));\n}\n"
  },
  {
    "path": "src/main/speedrunner.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SPEEDRUNNER_H\n#define SPEEDRUNNER_H\n\n#include \"control_types.h\"\n\nnamespace XPower { struct StatusInfo; }\n\nenum\n{\n    SPEEDRUN_MODE_OFF = 0,\n    SPEEDRUN_MODE_1,\n    SPEEDRUN_MODE_2,\n    SPEEDRUN_MODE_3\n};\n\nvoid speedRun_tick();\n\nvoid speedRun_syncControlKeys(int l_player_i, const Controls_t &keys);\n\nvoid speedRun_loadStats();\nvoid speedRun_saveStats();\n\nvoid speedRun_resetCurrent();\nvoid speedRun_resetTotal();\n\nvoid speedRun_triggerEnter();\nvoid speedRun_triggerLeave();\n\nvoid speedRun_bossDeadEvent();\n\nvoid RenderPowerInfo(int l_player_i, int bx, int by, int bw, int bh, uint8_t alpha, const XPower::StatusInfo* status);\n\n// l_player_i is an index into the local screen's player array, not a global player index anymore.\nvoid RenderControls(int l_player_i, int x, int y, int w, int h, bool missing, uint8_t alpha, bool connect_screen = false);\nvoid RenderControls(const Controls_t& controls, int x, int y, int w, int h, bool missing, uint8_t alpha);\n\nenum\n{\n    SPEEDRUN_ALIGN_LEFT = -1,\n    SPEEDRUN_ALIGN_AUTO,\n    SPEEDRUN_ALIGN_RIGHT,\n};\n\n// player is an index into the local screen's player array, not a global player index\nvoid speedRun_renderControls(int l_player_i, int screenZ = -1, int align = SPEEDRUN_ALIGN_AUTO);\nvoid speedRun_renderTimer();\n\n#endif // SPEEDRUNNER_H\n"
  },
  {
    "path": "src/main/translate/tr_level.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef XTECH_TRANSLATE_EPISODE\n#include <unordered_map>\n#include <vector>\n#include <json/json.hpp>\n#include <Logger/logger.h>\n#include <Utils/strings.h>\n#include \"globals.h\"\n#include \"layers.h\"\n#endif /* XTECH_TRANSLATE_EPISODE */\n\nclass TrLevelParser : public nlohmann::json_sax<nlohmann::json>\n{\npublic:\n    TrLevelParser()\n    {\n        // Build the map of tr-id objects\n        for(int i = 1; i <= numNPCs; ++i)\n        {\n            auto &it = m_trIdMap[GetS(NPC[i].Text)];\n            it.push_back(&NPC[i].Text);\n        }\n\n        for(int i = 0; i < numEvents; ++i)\n        {\n            auto &it = m_trIdMap[GetS(Events[i].Text)];\n            it.push_back(&Events[i].Text);\n        }\n    }\n\n    TrLevelParser(const TrLevelParser&) = default;\n    ~TrLevelParser() = default;\n\n    std::unordered_map<std::string, std::vector<stringindex_t*> > m_trIdMap;\n\n    std::string m_wantedKey;\n    std::string m_curKey;\n\n    int m_outKey = -1;\n    std::string m_outTrId;\n    std::string m_outValue;\n\n    enum Where\n    {\n        W_SKIP = 0,\n        W_ROOT,\n        W_LEVEL,\n        W_NPC,    W_NPC_OBJ,\n        W_EVENT,  W_EVENT_OBJ\n    } m_where = W_SKIP;\n\n    void flushData()\n    {\n        if((m_outKey < 0 && m_outTrId.empty()) || m_outValue.empty())\n            return;\n\n        if(!m_outTrId.empty()) // By TrId\n        {\n            D_pLogDebug(\"JSON: Written %s into %s\", m_outTrId.c_str(), m_outValue.c_str());\n            auto it = m_trIdMap.find(m_outTrId);\n            if(it != m_trIdMap.end())\n            {\n                for(auto *k : it->second)\n                    SetS(*k, m_outValue);\n            }\n        }\n        else // By object Index\n        {\n            D_pLogDebug(\"JSON: Written %d into %s\", m_outKey, m_outValue.c_str());\n\n            if(m_where == W_NPC_OBJ && m_outKey < numNPCs)\n                SetS(NPC[m_outKey + 1].Text, m_outValue);\n            else if(m_where == W_EVENT_OBJ && m_outKey < numEvents)\n                SetS(Events[m_outKey].Text, m_outValue);\n        }\n\n        m_outKey = -1;\n        m_outTrId.clear();\n        m_outValue.clear();\n    }\n\n\n    // called when null is parsed\n    bool null()\n    {\n        return true;\n    }\n\n    // called when a boolean is parsed; value is passed\n    bool boolean(bool)\n    {\n        return true;\n    }\n\n    bool number_integer(number_integer_t val)\n    {\n        if((m_where == W_NPC_OBJ || m_where == W_EVENT_OBJ) && m_curKey == \"i\")\n            m_outKey = val;\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t val)\n    {\n        if((m_where == W_NPC_OBJ || m_where == W_EVENT_OBJ) && m_curKey == \"i\")\n            m_outKey = (int)val;\n        return true;\n    }\n\n    bool number_float(number_float_t, const string_t&)\n    {\n        return true;\n    }\n\n    bool string(string_t& val)\n    {\n        if((m_where == W_NPC_OBJ || m_where == W_EVENT_OBJ) && m_curKey == \"tr-id\")\n            m_outTrId = val;\n        else if(m_where == W_NPC_OBJ && m_curKey == \"talk\")\n            m_outValue = val;\n        else if(m_where == W_EVENT_OBJ && m_curKey == \"msg\")\n            m_outValue = val;\n        else if(m_where == W_LEVEL && m_curKey == \"title\")\n            LevelName = m_outValue;\n\n        return true;\n    }\n\n    bool binary(binary_t&)\n    {\n        return true;\n    }\n\n    // called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)\n    bool start_object(std::size_t)\n    {\n        if(m_where == W_SKIP)\n            return true;\n\n        if(m_where == W_ROOT)\n            m_where = W_LEVEL;\n        else if(m_where == W_NPC)\n            m_where = W_NPC_OBJ;\n        else if(m_where == W_EVENT)\n            m_where = W_EVENT_OBJ;\n\n        D_pLogDebug(\"JSON: Start Object (where=%d)\", m_where);\n\n        return true;\n    }\n\n    bool end_object()\n    {\n        if(m_where == W_SKIP)\n            return true;\n\n        D_pLogDebug(\"JSON: End Object (where=%d)\", m_where);\n\n        if(m_where == W_NPC_OBJ)\n        {\n            flushData();\n            m_where = W_NPC;\n        }\n        else if(m_where == W_EVENT_OBJ)\n        {\n            flushData();\n            m_where = W_EVENT;\n        }\n        else if(m_where == W_NPC)\n            m_where = W_LEVEL;\n        else if(m_where == W_EVENT)\n            m_where = W_LEVEL;\n        else if(m_where == W_LEVEL)\n            return false; // All enough data has been taken\n\n        return true;\n    }\n\n    bool start_array(std::size_t)\n    {\n        if(m_where == W_SKIP)\n            return true;\n\n        if(m_where == W_ROOT)\n            m_where = W_LEVEL;\n        else if(m_where == W_LEVEL)\n        {\n            if(m_curKey == \"npc\")\n                m_where = W_NPC;\n            else if(m_curKey == \"events\")\n                m_where = W_EVENT;\n        }\n\n        D_pLogDebug(\"JSON: Start Array (where=%d)\", m_where);\n\n        return true;\n    }\n\n    bool end_array()\n    {\n        if(m_where == W_SKIP)\n            return true;\n\n        if(m_where == W_NPC || m_where == W_EVENT)\n        {\n            flushData();\n            m_where = W_LEVEL;\n            return true; // All enough data has been taken\n        }\n\n        return true;\n    }\n\n    // called when an object key is parsed; value is passed and can be safely moved away\n    bool key(string_t& val)\n    {\n        m_curKey = val;\n\n        if(m_where == W_SKIP)\n        {\n            if(Strings::endsWith(m_wantedKey, \".lvlx\"))\n                m_wantedKey.erase(m_wantedKey.end() - 1);\n\n            if(Strings::endsWith(m_curKey, \".lvlx\"))\n                m_curKey.erase(m_curKey.end() - 1);\n\n            if(m_curKey == m_wantedKey)\n            {\n                m_where = W_ROOT;\n                D_pLogDebug(\"JSON: FOUND A KEY %s\", val.c_str());\n            }\n            return true;\n        }\n\n        D_pLogDebug(\"JSON: Key=%s\", val.c_str());\n\n        return true;\n    }\n\n    bool parse_error(std::size_t position, const std::string& last_token, const nlohmann::detail::exception& ex)\n    {\n        pLogWarning(\"JSON parsing error: pos: %zu, last token: %s, exception: %s\",\n                     position, last_token.c_str(), ex.what());\n        return false;\n    }\n};\n"
  },
  {
    "path": "src/main/translate/tr_script.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef XTECH_TRANSLATE_EPISODE\n#include <unordered_map>\n#include <vector>\n#include <json/json.hpp>\n#include <Logger/logger.h>\n#endif /* XTECH_TRANSLATE_EPISODE */\n\nclass TrScriptParser : public nlohmann::json_sax<nlohmann::json>\n{\npublic:\n    TrScriptParser() = default;\n    TrScriptParser(const TrScriptParser&) = default;\n    ~TrScriptParser() = default;\n\n    std::unordered_map<int, std::string> *m_outputLines;\n    std::unordered_map<std::string, std::string> *m_outputTrIdLines;\n    std::string m_wantedKey;\n    std::string m_curKey;\n\n    int m_outKey = -1;\n    std::string m_outTrId;\n    std::string m_outValue;\n\n    enum Where\n    {\n        W_SKIP = 0,\n        W_ROOT,\n        W_DLLTXT,\n        W_LINES,\n        W_LINES_OBJ\n    } m_where = W_SKIP;\n\n    void flushData()\n    {\n        if((m_outKey < 0 && m_outTrId.empty()) || m_outValue.empty())\n            return;\n\n        if(!m_outTrId.empty()) // By TrId\n        {\n            D_pLogDebug(\"JSON: Written %s into %s\", m_outTrId.c_str(), m_outValue.c_str());\n            m_outputTrIdLines->insert({m_outTrId, m_outValue});\n        }\n        else\n        {\n            D_pLogDebug(\"JSON: Written %d into %s\", m_outKey, m_outValue.c_str());\n            m_outputLines->insert({m_outKey, m_outValue});\n        }\n\n        m_outKey = -1;\n        m_outTrId.clear();\n        m_outValue.clear();\n    }\n\n\n    // called when null is parsed\n    bool null()\n    {\n        return true;\n    }\n\n    // called when a boolean is parsed; value is passed\n    bool boolean(bool)\n    {\n        return true;\n    }\n\n    bool number_integer(number_integer_t val)\n    {\n        if(m_where == W_LINES_OBJ && m_curKey == \"i\")\n            m_outKey = val;\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t val)\n    {\n        if(m_where == W_LINES_OBJ && m_curKey == \"i\")\n            m_outKey = (int)val;\n        return true;\n    }\n\n    bool number_float(number_float_t, const string_t&)\n    {\n        return true;\n    }\n\n    bool string(string_t& val)\n    {\n        if(m_where == W_LINES_OBJ && m_curKey == \"tr-id\")\n            m_outTrId = val;\n        else if(m_where == W_LINES_OBJ && m_curKey == \"tr\")\n            m_outValue = val;\n        return true;\n    }\n\n    bool binary(binary_t&)\n    {\n        return true;\n    }\n\n    // called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)\n    bool start_object(std::size_t)\n    {\n        if(m_where == W_SKIP)\n            return true;\n\n        if(m_where == W_ROOT)\n            m_where = W_DLLTXT;\n        else if(m_where == W_LINES)\n            m_where = W_LINES_OBJ;\n\n        D_pLogDebug(\"JSON: Start Object (where=%d)\", m_where);\n\n        return true;\n    }\n\n    bool end_object()\n    {\n        if(m_where == W_SKIP)\n            return true;\n\n        D_pLogDebug(\"JSON: End Object (where=%d)\", m_where);\n\n        if(m_where == W_LINES_OBJ)\n        {\n            flushData();\n            m_where = W_LINES;\n        }\n        else if(m_where == W_LINES)\n            m_where = W_DLLTXT;\n        else if(m_where == W_DLLTXT)\n            return false; // All enough data has been taken\n\n        return true;\n    }\n\n    bool start_array(std::size_t)\n    {\n        if(m_where == W_SKIP)\n            return true;\n\n        if(m_where == W_ROOT)\n            m_where = W_DLLTXT;\n        else if(m_where == W_DLLTXT && m_curKey == \"lines\")\n            m_where = W_LINES;\n\n        D_pLogDebug(\"JSON: Start Array (where=%d)\", m_where);\n\n        return true;\n    }\n\n    bool end_array()\n    {\n        if(m_where == W_SKIP)\n            return true;\n\n        if(m_where == W_LINES)\n        {\n            flushData();\n            m_where = W_DLLTXT;\n            return true; // All enough data has been taken\n        }\n\n        return true;\n    }\n\n    // called when an object key is parsed; value is passed and can be safely moved away\n    bool key(string_t& val)\n    {\n        m_curKey = val;\n\n        if(m_where == W_SKIP)\n        {\n            if(m_curKey == m_wantedKey)\n            {\n                m_where = W_ROOT;\n                D_pLogDebug(\"JSON: FOUND A KEY %s\", val.c_str());\n            }\n            return true;\n        }\n\n        D_pLogDebug(\"JSON: Key=%s\", val.c_str());\n\n        return true;\n    }\n\n    bool parse_error(std::size_t position, const std::string& last_token, const nlohmann::detail::exception& ex)\n    {\n        pLogWarning(\"JSON parsing error: pos: %zu, last token: %s, exception: %s\",\n                     position, last_token.c_str(), ex.what());\n        return false;\n    }\n};\n"
  },
  {
    "path": "src/main/translate/tr_title.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef XTECH_TRANSLATE_EPISODE\n#include <json/json.hpp>\n#include <Logger/logger.h>\n#endif /* XTECH_TRANSLATE_EPISODE */\n\nclass TrTitleParser : public nlohmann::json_sax<nlohmann::json>\n{\npublic:\n    TrTitleParser() = default;\n    TrTitleParser(const TrTitleParser&) = default;\n    ~TrTitleParser() = default;\n\n    const std::string m_wantedKeyTitle = \" episode_title\";\n    const std::string m_wantedKeyWorld = \" episode_world\";\n    std::string m_wantedWorld;\n    std::string m_curKey;\n    std::string m_curWorld;\n    int depth = 0;\n\n    std::string *m_toWrite = nullptr;\n    std::string m_outValue;\n    bool m_hasValue = false;\n    bool m_hasWorldTitle = false;\n\n    // called when null is parsed\n    bool null()\n    {\n        return true;\n    }\n\n    // called when a boolean is parsed; value is passed\n    bool boolean(bool)\n    {\n        return true;\n    }\n\n    bool number_integer(number_integer_t)\n    {\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t)\n    {\n        return true;\n    }\n\n    bool number_float(number_float_t, const string_t&)\n    {\n        return true;\n    }\n\n    bool string(string_t& val)\n    {\n        if(m_curKey == m_wantedKeyTitle && !val.empty())\n        {\n            m_outValue = val;\n            m_hasValue = true;\n        }\n\n        else if(m_curKey == m_wantedKeyWorld && !val.empty())\n        {\n            m_curWorld = val;\n            m_hasWorldTitle = true;\n        }\n\n        if(m_hasWorldTitle && m_hasValue)\n        {\n            if(m_curWorld == m_wantedWorld)\n                *m_toWrite = m_outValue;\n            return false; // We found that we looked for\n        }\n\n        return true;\n    }\n\n    bool binary(binary_t&)\n    {\n        return true;\n    }\n\n    // called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)\n    bool start_object(std::size_t)\n    {\n        ++depth;\n        D_pLogDebug(\"JSON: Start Object, depth=%d\", depth);\n        if(depth > 1)\n            return false;\n        return true;\n    }\n\n    bool end_object()\n    {\n        --depth;\n        D_pLogDebug(\"JSON: End Object, depth=%d\", depth);\n        return true;\n    }\n\n    bool start_array(std::size_t)\n    {\n        ++depth;\n        return true;\n    }\n\n    bool end_array()\n    {\n        --depth;\n        return true;\n    }\n\n    // called when an object key is parsed; value is passed and can be safely moved away\n    bool key(string_t& val)\n    {\n        m_curKey = val;\n        D_pLogDebug(\"JSON: Key=%s\", val.c_str());\n\n        return true;\n    }\n\n    bool parse_error(std::size_t position, const std::string& last_token, const nlohmann::detail::exception& ex)\n    {\n        pLogWarning(\"JSON parsing error: pos: %zu, last token: %s, exception: %s\",\n                     position, last_token.c_str(), ex.what());\n        return false;\n    }\n};\n"
  },
  {
    "path": "src/main/translate/tr_world.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef XTECH_TRANSLATE_EPISODE\n#include <unordered_map>\n#include <vector>\n#include <json/json.hpp>\n#include <Logger/logger.h>\n#include <Utils/strings.h>\n#include \"globals.h\"\n#endif /* XTECH_TRANSLATE_EPISODE */\n\nclass TrWorldParser : public nlohmann::json_sax<nlohmann::json>\n{\npublic:\n    TrWorldParser()\n    {\n        // Build the map of tr-id objects\n        for(int i = 1; i <= numWorldLevels; ++i)\n        {\n            auto &it = m_trIdMapLevel[WorldLevel[i].LevelName];\n            it.push_back(i);\n        }\n    }\n\n    TrWorldParser(const TrWorldParser&) = default;\n    ~TrWorldParser() = default;\n\n    std::string m_wantedKey;\n    std::string m_curKey;\n\n    int m_outKey = -1;\n    std::string m_outTrId;\n    std::string m_outValue;\n\n    enum Where\n    {\n        W_SKIP = 0,\n        W_ROOT,\n        W_WORLD,\n        W_LEVELS,  W_LEVEL_OBJ\n    } m_where = W_SKIP;\n    int m_depth = 0;\n\n    std::unordered_map<std::string, std::vector<int /*WorldLevel index*/> > m_trIdMapLevel;\n\n    void flushData()\n    {\n        if((m_outKey < 0 && m_outTrId.empty()) || m_outValue.empty())\n            return;\n\n        if(!m_outTrId.empty()) // By TrId\n        {\n            D_pLogDebug(\"JSON: Written %s into %s\", m_outTrId.c_str(), m_outValue.c_str());\n            auto it = m_trIdMapLevel.find(m_outTrId);\n            if(it != m_trIdMapLevel.end())\n            {\n                for(auto &k : it->second)\n                    WorldLevel[k].LevelName = m_outValue;\n            }\n        }\n        else\n        {\n            D_pLogDebug(\"JSON: Written %d into %s\", m_outKey, m_outValue.c_str());\n\n            if(m_where == W_LEVEL_OBJ && m_outKey < numWorldLevels)\n                WorldLevel[m_outKey + 1].LevelName = m_outValue;\n        }\n\n        m_outKey = -1;\n        m_outTrId.clear();\n        m_outValue.clear();\n    }\n\n\n    // called when null is parsed\n    bool null()\n    {\n        return true;\n    }\n\n    // called when a boolean is parsed; value is passed\n    bool boolean(bool)\n    {\n        return true;\n    }\n\n    bool number_integer(number_integer_t val)\n    {\n        if(m_where == W_LEVEL_OBJ && m_curKey == \"i\")\n            m_outKey = val;\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t val)\n    {\n        if(m_where == W_LEVEL_OBJ && m_curKey == \"i\")\n            m_outKey = (int)val;\n        return true;\n    }\n\n    bool number_float(number_float_t, const string_t&)\n    {\n        return true;\n    }\n\n    bool string(string_t& val)\n    {\n        if(m_where == W_LEVEL_OBJ && m_curKey == \"tr-id\")\n            m_outTrId = val;\n        else if(m_where == W_LEVEL_OBJ && m_curKey == \"tit\")\n            m_outValue = val;\n        else if(m_where == W_WORLD && m_curKey == \"title\")\n            WorldName = val;\n        else if(m_where == W_WORLD && m_curKey == \"credits\")\n        {\n            std::string credits(val);\n            int B = 0;\n            std::vector<std::string> authorsList;\n\n            if(!credits.empty())\n            {\n                numWorldCredits = 0;\n                for(int i = 1; i <= maxWorldCredits; i++)\n                    WorldCredits[i].clear();\n\n                Strings::split(authorsList, credits, \"\\n\");\n                for(auto &c : authorsList)\n                {\n                    ++B;\n                    if(B > maxWorldCredits)\n                        break;\n                    WorldCredits[B] = c;\n                    numWorldCredits = B;\n                }\n            }\n        }\n\n        return true;\n    }\n\n    bool binary(binary_t&)\n    {\n        return true;\n    }\n\n    // called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)\n    bool start_object(std::size_t)\n    {\n        ++m_depth;\n        if(m_where == W_SKIP)\n            return true;\n\n        if(m_where == W_ROOT)\n            m_where = W_WORLD;\n        else if(m_where == W_LEVELS)\n            m_where = W_LEVEL_OBJ;\n\n        D_pLogDebug(\"JSON: Start Object (where=%d)\", m_where);\n\n        return true;\n    }\n\n    bool end_object()\n    {\n        --m_depth;\n        if(m_where == W_SKIP)\n            return true;\n\n        D_pLogDebug(\"JSON: End Object (where=%d)\", m_where);\n\n        if(m_where == W_LEVEL_OBJ)\n        {\n            flushData();\n            m_where = W_LEVELS;\n        }\n        else if(m_where == W_LEVELS)\n            m_where = W_WORLD;\n        else if(m_where == W_WORLD)\n            return false; // All enough data has been taken\n\n        return true;\n    }\n\n    bool start_array(std::size_t)\n    {\n        ++m_depth;\n        if(m_where == W_SKIP)\n            return true;\n\n        if(m_where == W_ROOT)\n            m_where = W_WORLD;\n        else if(m_where == W_WORLD)\n        {\n            if(m_curKey == \"levels\")\n                m_where = W_LEVELS;\n        }\n\n        D_pLogDebug(\"JSON: Start Array (where=%d, depth=%d)\", m_where, m_depth);\n\n        return true;\n    }\n\n    bool end_array()\n    {\n        --m_depth;\n        if(m_where == W_SKIP)\n            return true;\n\n        if(m_where == W_LEVELS)\n        {\n            flushData();\n            m_where = W_WORLD;\n            return true; // All enough data has been taken\n        }\n\n        return true;\n    }\n\n    // called when an object key is parsed; value is passed and can be safely moved away\n    bool key(string_t& val)\n    {\n        m_curKey = val;\n\n        if(m_where == W_SKIP)\n        {\n            if(Strings::endsWith(m_wantedKey, \".wldx\"))\n                m_wantedKey.erase(m_wantedKey.end() - 1);\n\n            if(Strings::endsWith(m_curKey, \".wldx\"))\n                m_curKey.erase(m_curKey.end() - 1);\n\n            if(m_curKey == m_wantedKey)\n            {\n                m_where = W_ROOT;\n                D_pLogDebug(\"JSON: FOUND A KEY %s\", val.c_str());\n            }\n            return true;\n        }\n\n        D_pLogDebug(\"JSON: Key=%s\", val.c_str());\n\n        return true;\n    }\n\n    bool parse_error(std::size_t position, const std::string& last_token, const nlohmann::detail::exception& ex)\n    {\n        pLogWarning(\"JSON parsing error: pos: %zu, last token: %s, exception: %s\",\n                     position, last_token.c_str(), ex.what());\n        return false;\n    }\n};\n"
  },
  {
    "path": "src/main/translate.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n#include <DirManager/dirman.h>\n#include <AppPath/app_path.h>\n#include <Utils/files.h>\n#include <Utils/strings.h>\n#include <json/json_rwops_input.hpp>\n#include <json/json.hpp>\n#include <fmt_format_ne.h>\n\n#ifndef SDL_SDL_STDINC_H\n#include \"sdl_proxy/sdl_stdinc.h\"\n#endif\n\n#ifndef GLOBALS_H\n#include \"globals.h\"\n#endif\n\n#include \"fontman/font_manager.h\"\n#include \"core/language.h\"\n\n#include \"translate.h\"\n#include \"main/menu_main.h\"\n#include \"main/game_info.h\"\n#include \"main/outro_loop.h\"\n#include \"main/game_strings.h\"\n#include \"main/hints.h\"\n\n#include \"controls.h\"\n#include \"config.h\"\n#include \"control/controls_strings.h\"\n\n#include \"editor/editor_strings.h\"\n#include \"editor/editor_custom.h\"\n\n#include \"script/luna/luna.h\"\n\n#if TR_MAP_TYPE == 1 && !defined(THEXTECH_DISABLE_LANG_TOOLS)\n#   include <sorting/tinysort.h>\n#endif\n\n\nenum class PluralRules\n{\n    SingularOnly,\n    OneIsSingular,\n    Slavic,\n};\n\nstatic PluralRules s_CurrentPluralRules = PluralRules::OneIsSingular;\n\nconst std::string& LanguageFormatSlavic(int number, const std::string& singular, const std::string& dual, const std::string& plural)\n{\n    if(((number % 100) != 11) && ((number % 10) == 1))\n        return singular;\n    else if((number % 10) >= 2 && (number % 10) <= 4 && (number % 100 < 10 || number % 100 > 20))\n        return dual;\n    else\n        return plural;\n}\n\nconst std::string& LanguageFormatNumber(int number, const std::string& singular, const std::string& dual, const std::string& plural)\n{\n    // Ensure it's always positive number\n    number = SDL_abs(number);\n\n    if(s_CurrentPluralRules == PluralRules::OneIsSingular)\n        return (number == 1) ? singular : plural;\n    else if(s_CurrentPluralRules == PluralRules::Slavic)\n        return LanguageFormatSlavic(number, singular, dual, plural);\n    else // if(s_CurrentPluralRules == PluralRules::SingularOnly)\n        return singular;\n}\n\n#ifndef THEXTECH_DISABLE_LANG_TOOLS\nstatic void setJsonValue(nlohmann::ordered_json &j, const std::string &key, const std::string &value)\n{\n    auto dot = key.find(\".\");\n    if(dot == std::string::npos)\n    {\n        j[key] = value;\n        return;\n    }\n\n    std::string subKey = key.substr(0, dot);\n    setJsonValue(j[subKey], key.substr(dot + 1), value);\n}\n\nstatic bool setJsonValueIfNotExist(nlohmann::ordered_json &j, const std::string &key, const std::string &value, bool noBlank)\n{\n    auto dot = key.find(\".\");\n    if(dot == std::string::npos)\n    {\n        if(!j.contains(key) || j.is_null())\n        {\n            std::printf(\"-- ++ Added new string: %s = %s\\n\", key.c_str(), value.c_str());\n            std::fflush(stdout);\n            if(!noBlank || !value.empty())\n                j[key] = value;\n            else if(j.contains(key))\n                j.erase(key);\n            return true;\n        }\n        return false; // Nothing changed\n    }\n\n    std::string subKey = key.substr(0, dot);\n    bool ret = setJsonValueIfNotExist(j[subKey], key.substr(dot + 1), value, noBlank);\n\n    if(noBlank && (j[subKey].is_null() || j[subKey].empty()))\n        j.erase(subKey);\n\n    return ret;\n}\n#endif\n\nstatic std::string getJsonValue(nlohmann::ordered_json &j, const std::string &key, const std::string &defVal)\n{\n    auto dot = key.find(\".\");\n    if(dot == std::string::npos)\n    {\n        if(!j.contains(key) || j[key].is_null())\n            return defVal;\n\n        auto out = j.value(key, defVal);\n\n        if(out.empty()) // For empty lines, return default value\n            return defVal;\n\n        return out;\n    }\n\n    std::string subKey = key.substr(0, dot);\n    return getJsonValue(j[subKey], key.substr(dot + 1), defVal);\n}\n\n#ifndef THEXTECH_DISABLE_LANG_TOOLS\nstatic bool saveFile(const std::string &inPath, const std::string &inData)\n{\n    bool ret = true;\n    SDL_RWops *in = Files::open_file(inPath, \"w\");\n    if(!in)\n        return false;\n\n    SDL_RWwrite(in, inData.c_str(), 1, inData.size());\n    SDL_RWwrite(in, \"\\n\", 1, 1);\n\n    SDL_RWclose(in);\n\n    return ret;\n}\n#endif\n\n\nvoid XTechTranslate::insert(TrList &list, const char *key, std::string *value)\n{\n#if defined(DEBUG_BUILD) && TR_MAP_TYPE == 1\n    for(auto &k : list)\n    {\n        if(k.first.compare(key) == 0)\n            SDL_assert_release(false && \"Duplicated translation line detected!\");\n    }\n#endif\n    list.TR_MAP_INSERT({key, value});\n}\n\nvoid XTechTranslate::insert(TrList &list, std::string &&key, std::string *value)\n{\n#if defined(DEBUG_BUILD) && TR_MAP_TYPE == 1\n    for(auto &k : list)\n    {\n        if(k.first.compare(key) == 0)\n            SDL_assert_release(false && \"Duplicated translation line detected!\");\n    }\n#endif\n    list.TR_MAP_INSERT({key, value});\n}\n\nXTechTranslate::XTechTranslate()\n{\n    // List of all translatable strings of the engine\n    m_engineMap.clear();\n\n    insert(m_engineMap, \"menu.main.mainPlayEpisode\",            &g_mainMenu.mainPlayEpisode);\n    insert(m_engineMap, \"menu.main.main1PlayerGame\",            &g_mainMenu.main1PlayerGame);\n    insert(m_engineMap, \"menu.main.mainMultiplayerGame\",        &g_mainMenu.mainMultiplayerGame);\n    insert(m_engineMap, \"menu.main.mainBattleGame\",             &g_mainMenu.mainBattleGame);\n    insert(m_engineMap, \"menu.main.mainEditor\",                 &g_mainMenu.mainEditor);\n    insert(m_engineMap, \"menu.main.mainOptions\",                &g_mainMenu.mainOptions);\n    insert(m_engineMap, \"menu.main.mainExit\",                   &g_mainMenu.mainExit);\n\n    insert(m_engineMap, \"menu.loading\",                         &g_mainMenu.loading);\n\n    insert(m_engineMap, \"languageName\",                         &g_mainMenu.languageName);\n    insert(m_engineMap, \"pluralRules\",                          &g_mainMenu.pluralRules);\n\n    insert(m_engineMap, \"menu.editor.battles\",                  &g_mainMenu.editorBattles);\n    insert(m_engineMap, \"menu.editor.newWorld\",                 &g_mainMenu.editorNewWorld);\n    insert(m_engineMap, \"menu.editor.makeFor\",                  &g_mainMenu.editorMakeFor);\n    insert(m_engineMap, \"menu.editor.errorMissingResources\",    &g_mainMenu.editorErrorMissingResources);\n    insert(m_engineMap, \"menu.editor.promptNewWorldName\",       &g_mainMenu.editorPromptNewWorldName);\n\n    insert(m_engineMap, \"menu.game.gameNoEpisodesToPlay\",       &g_mainMenu.gameNoEpisodesToPlay);\n    insert(m_engineMap, \"menu.game.gameNoBattleLevels\",         &g_mainMenu.gameNoBattleLevels);\n    insert(m_engineMap, \"menu.game.gameBattleRandom\",           &g_mainMenu.gameBattleRandom);\n\n    insert(m_engineMap, \"menu.game.warnEpCompat\",               &g_mainMenu.warnEpCompat);\n\n    insert(m_engineMap, \"menu.game.gameSlotContinue\",           &g_mainMenu.gameSlotContinue);\n    insert(m_engineMap, \"menu.game.gameSlotNew\",                &g_mainMenu.gameSlotNew);\n\n    insert(m_engineMap, \"menu.game.gameCopySave\",               &g_mainMenu.gameCopySave);\n    insert(m_engineMap, \"menu.game.gameEraseSave\",              &g_mainMenu.gameEraseSave);\n\n    insert(m_engineMap, \"menu.game.gameSourceSlot\",             &g_mainMenu.gameSourceSlot);\n    insert(m_engineMap, \"menu.game.gameTargetSlot\",             &g_mainMenu.gameTargetSlot);\n    insert(m_engineMap, \"menu.game.gameEraseSlot\",              &g_mainMenu.gameEraseSlot);\n\n    insert(m_engineMap, \"menu.game.phraseScore\",                &g_mainMenu.phraseScore);\n    insert(m_engineMap, \"menu.game.phraseTime\",                 &g_mainMenu.phraseTime);\n\n    insert(m_engineMap, \"menu.battle.errorNoLevels\",            &g_mainMenu.errorBattleNoLevels);\n\n    insert(m_engineMap, \"menu.options.restartEngine\",           &g_mainMenu.optionsRestartEngine);\n\n    insert(m_engineMap, \"menu.character.selectCharacter\",       &g_mainMenu.selectCharacter);\n\n\n    insert(m_engineMap, \"menu.controls.controlsTitle\",          &g_mainMenu.controlsTitle);\n    insert(m_engineMap, \"menu.controls.controlsConnected\",      &g_mainMenu.controlsConnected);\n    insert(m_engineMap, \"menu.controls.controlsDeleteKey2\",     &g_mainMenu.controlsDeleteKey);\n    insert(m_engineMap, \"menu.controls.controlsDeviceTypes\",    &g_mainMenu.controlsDeviceTypes);\n    insert(m_engineMap, \"menu.controls.controlsInUse\",          &g_mainMenu.controlsInUse);\n    insert(m_engineMap, \"menu.controls.controlsNotInUse\",       &g_mainMenu.controlsNotInUse);\n\n    insert(m_engineMap, \"menu.controls.wordProfiles\",           &g_mainMenu.wordProfiles);\n    insert(m_engineMap, \"menu.controls.wordButtons\",            &g_mainMenu.wordButtons);\n\n    insert(m_engineMap, \"menu.controls.controlsReallyDeleteProfile\", &g_mainMenu.controlsReallyDeleteProfile);\n    insert(m_engineMap, \"menu.controls.controlsNewProfile\",     &g_mainMenu.controlsNewProfile);\n    insert(m_engineMap, \"menu.controls.caseInvalid\",            &g_controlsStrings.sharedCaseInvalid);\n\n    insert(m_engineMap, \"menu.controls.profile.renameProfile\",  &g_mainMenu.controlsRenameProfile);\n    insert(m_engineMap, \"menu.controls.profile.deleteProfile\",  &g_mainMenu.controlsDeleteProfile);\n    insert(m_engineMap, \"menu.controls.profile.playerControls\", &g_mainMenu.controlsPlayerControls);\n    insert(m_engineMap, \"menu.controls.profile.cursorControls\", &g_mainMenu.controlsCursorControls);\n    insert(m_engineMap, \"menu.controls.profile.editorControls\", &g_mainMenu.controlsEditorControls);\n    insert(m_engineMap, \"menu.controls.profile.hotkeys\",        &g_mainMenu.controlsHotkeys);\n\n    insert(m_engineMap, \"menu.controls.options.rumble\",         &g_mainMenu.controlsOptionRumble);\n    insert(m_engineMap, \"menu.controls.options.batteryStatus\",  &g_mainMenu.controlsOptionBatteryStatus);\n    insert(m_engineMap, \"menu.controls.options.altMenuControls\",  &g_mainMenu.controlsOptionAltMenuControls);\n    insert(m_engineMap, \"menu.controls.options.maxPlayers\",     &g_controlsStrings.sharedOptionMaxPlayers);\n\n    insert(m_engineMap, \"menu.controls.buttons.up\",      &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Up]);\n    insert(m_engineMap, \"menu.controls.buttons.down\",    &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Down]);\n    insert(m_engineMap, \"menu.controls.buttons.left\",    &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Left]);\n    insert(m_engineMap, \"menu.controls.buttons.right\",   &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Right]);\n    insert(m_engineMap, \"menu.controls.buttons.jump\",    &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Jump]);\n    insert(m_engineMap, \"menu.controls.buttons.run\",     &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Run]);\n    insert(m_engineMap, \"menu.controls.buttons.altJump\", &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::AltJump]);\n    insert(m_engineMap, \"menu.controls.buttons.altRun\",  &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::AltRun]);\n    insert(m_engineMap, \"menu.controls.buttons.start\",   &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Start]);\n    insert(m_engineMap, \"menu.controls.buttons.drop\",    &Controls::PlayerControls::g_button_name_UI[Controls::PlayerControls::Drop]);\n\n    insert(m_engineMap, \"menu.controls.cursor.up\",        &Controls::CursorControls::g_button_name_UI[Controls::CursorControls::CursorUp]);\n    insert(m_engineMap, \"menu.controls.cursor.down\",      &Controls::CursorControls::g_button_name_UI[Controls::CursorControls::CursorDown]);\n    insert(m_engineMap, \"menu.controls.cursor.left\",      &Controls::CursorControls::g_button_name_UI[Controls::CursorControls::CursorLeft]);\n    insert(m_engineMap, \"menu.controls.cursor.right\",     &Controls::CursorControls::g_button_name_UI[Controls::CursorControls::CursorRight]);\n    insert(m_engineMap, \"menu.controls.cursor.primary\",   &Controls::CursorControls::g_button_name_UI[Controls::CursorControls::Primary]);\n    insert(m_engineMap, \"menu.controls.cursor.secondary\", &Controls::CursorControls::g_button_name_UI[Controls::CursorControls::Secondary]);\n    insert(m_engineMap, \"menu.controls.cursor.tertiary\",  &Controls::CursorControls::g_button_name_UI[Controls::CursorControls::Tertiary]);\n\n    insert(m_engineMap, \"menu.controls.editor.scrollUp\",      &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::ScrollUp]);\n    insert(m_engineMap, \"menu.controls.editor.scrollDown\",    &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::ScrollDown]);\n    insert(m_engineMap, \"menu.controls.editor.scrollLeft\",    &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::ScrollLeft]);\n    insert(m_engineMap, \"menu.controls.editor.scrollRight\",   &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::ScrollRight]);\n    insert(m_engineMap, \"menu.controls.editor.fastScroll\",    &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::FastScroll]);\n    insert(m_engineMap, \"menu.controls.editor.modeSelect\",    &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::ModeSelect]);\n    insert(m_engineMap, \"menu.controls.editor.modeErase\",     &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::ModeErase]);\n    insert(m_engineMap, \"menu.controls.editor.prevSection\",   &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::PrevSection]);\n    insert(m_engineMap, \"menu.controls.editor.nextSection\",   &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::NextSection]);\n    insert(m_engineMap, \"menu.controls.editor.switchScreens\", &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::SwitchScreens]);\n    insert(m_engineMap, \"menu.controls.editor.testPlay\",      &Controls::EditorControls::g_button_name_UI[Controls::EditorControls::TestPlay]);\n\n#ifndef RENDER_FULLSCREEN_ALWAYS\n    insert(m_engineMap, \"menu.controls.hotkeys.fullscreen\",  &Controls::Hotkeys::g_button_name_UI[Controls::Hotkeys::Fullscreen]);\n#endif\n#ifdef USE_SCREENSHOTS_AND_RECS\n    insert(m_engineMap, \"menu.controls.hotkeys.screenshot\",  &Controls::Hotkeys::g_button_name_UI[Controls::Hotkeys::Screenshot]);\n#endif\n#ifdef PGE_ENABLE_VIDEO_REC\n    insert(m_engineMap, \"menu.controls.hotkeys.recordGif\",   &Controls::Hotkeys::g_button_name_UI[Controls::Hotkeys::RecordGif]);\n#endif\n    insert(m_engineMap, \"menu.controls.hotkeys.debugInfo\",   &Controls::Hotkeys::g_button_name_UI[Controls::Hotkeys::DebugInfo]);\n    insert(m_engineMap, \"menu.controls.hotkeys.vanillaCam\",  &Controls::Hotkeys::g_button_name_UI[Controls::Hotkeys::VanillaCam]);\n    insert(m_engineMap, \"menu.controls.hotkeys.enterCheats\", &Controls::Hotkeys::g_button_name_UI[Controls::Hotkeys::EnterCheats]);\n    insert(m_engineMap, \"menu.controls.hotkeys.toggleHUD\",   &Controls::Hotkeys::g_button_name_UI[Controls::Hotkeys::ToggleHUD]);\n    insert(m_engineMap, \"menu.controls.hotkeys.legacyPause\", &Controls::Hotkeys::g_button_name_UI[Controls::Hotkeys::LegacyPause]);\n\n#ifdef CONTROLS_KEYBOARD_STRINGS\n    insert(m_engineMap, \"menu.controls.types.keyboard\",    &g_controlsStrings.nameKeyboard);\n    insert(m_engineMap, \"menu.controls.options.textEntryStyle\",    &g_controlsStrings.keyboardOptionTextEntryStyle);\n    insert(m_engineMap, \"menu.controls.caseMouse\",         &g_controlsStrings.caseMouse);\n#endif\n\n#if defined(CONTROLS_KEYBOARD_STRINGS) || defined(CONTROLS_JOYSTICK_STRINGS)\n    insert(m_engineMap, \"menu.controls.types.gamepad\",     &g_controlsStrings.nameGamepad);\n#endif\n\n#if defined(CONTROLS_JOYSTICK_STRINGS)\n    insert(m_engineMap, \"menu.controls.phraseNewProfOldJoy\",&g_controlsStrings.phraseNewProfOldJoy);\n    insert(m_engineMap, \"menu.controls.types.oldJoystick\", &g_controlsStrings.nameOldJoy);\n#endif\n\n#if defined(CONTROLS_JOYSTICK_STRINGS) || defined(CONTROLS_WII_STRINGS)\n    insert(m_engineMap, \"menu.controls.joystickSimpleEditor\",&g_controlsStrings.joystickSimpleEditor);\n#endif\n\n#if defined(CONTROLS_TOUCHSCREEN_STRINGS)\n    insert(m_engineMap, \"menu.controls.types.touchscreen\", &g_controlsStrings.nameTouchscreen);\n\n    insert(m_engineMap, \"menu.controls.caseTouch\",         &g_controlsStrings.caseTouch);\n\n    insert(m_engineMap, \"menu.controls.touchscreen.option.layoutStyle\",    &g_controlsStrings.touchscreenOptionLayoutStyle);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.scaleFactor\",    &g_controlsStrings.touchscreenOptionScaleFactor);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.scaleDPad\",      &g_controlsStrings.touchscreenOptionScaleDPad);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.scaleButtons\",   &g_controlsStrings.touchscreenOptionScaleButtons);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.sStartSpacing\",  &g_controlsStrings.touchscreenOptionSStartSpacing);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.resetLayout\",    &g_controlsStrings.touchscreenOptionResetLayout);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.interfaceStyle\", &g_controlsStrings.touchscreenOptionInterfaceStyle);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.feedbackStrength\", &g_controlsStrings.touchscreenOptionFeedbackStrength);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.feedbackLength\", &g_controlsStrings.touchscreenOptionFeedbackLength);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.holdRun\",        &g_controlsStrings.touchscreenOptionHoldRun);\n    insert(m_engineMap, \"menu.controls.touchscreen.option.showCodeButton\", &g_controlsStrings.touchscreenOptionShowCodeButton);\n\n\n    insert(m_engineMap, \"menu.controls.touchscreen.layout.tight\",          &g_controlsStrings.touchscreenLayoutTight);\n    insert(m_engineMap, \"menu.controls.touchscreen.layout.tinyOld\",        &g_controlsStrings.touchscreenLayoutTinyOld);\n    insert(m_engineMap, \"menu.controls.touchscreen.layout.phoneOld\",       &g_controlsStrings.touchscreenLayoutPhoneOld);\n    insert(m_engineMap, \"menu.controls.touchscreen.layout.longOld\",        &g_controlsStrings.touchscreenLayoutLongOld);\n    insert(m_engineMap, \"menu.controls.touchscreen.layout.phabletOld\",     &g_controlsStrings.touchscreenLayoutPhabletOld);\n    insert(m_engineMap, \"menu.controls.touchscreen.layout.tabletOld\",      &g_controlsStrings.touchscreenLayoutTabletOld);\n    insert(m_engineMap, \"menu.controls.touchscreen.layout.standard\",       &g_controlsStrings.touchscreenLayoutStandard);\n\n    insert(m_engineMap, \"menu.controls.touchscreen.style.actions\",         &g_controlsStrings.touchscreenStyleActions);\n    insert(m_engineMap, \"menu.controls.touchscreen.style.ABXY\",            &g_controlsStrings.touchscreenStyleABXY);\n    insert(m_engineMap, \"menu.controls.touchscreen.style.XODA\",            &g_controlsStrings.touchscreenStyleXODA);\n#endif // #if defined(CONTROLS_TOUCHSCREEN_STRINGS)\n\n#if defined(CONTROLS_16M_STRINGS)\n    insert(m_engineMap, \"menu.controls.tDS.buttonA\",      &g_controlsStrings.tdsButtonA);\n    insert(m_engineMap, \"menu.controls.tDS.buttonB\",      &g_controlsStrings.tdsButtonB);\n    insert(m_engineMap, \"menu.controls.tDS.buttonX\",      &g_controlsStrings.tdsButtonX);\n    insert(m_engineMap, \"menu.controls.tDS.buttonY\",      &g_controlsStrings.tdsButtonY);\n    insert(m_engineMap, \"menu.controls.tDS.buttonL\",      &g_controlsStrings.tdsButtonL);\n    insert(m_engineMap, \"menu.controls.tDS.buttonR\",      &g_controlsStrings.tdsButtonR);\n    insert(m_engineMap, \"menu.controls.tDS.buttonSelect\", &g_controlsStrings.tdsButtonSelect);\n    insert(m_engineMap, \"menu.controls.tDS.buttonStart\",  &g_controlsStrings.tdsButtonStart);\n\n    insert(m_engineMap, \"menu.controls.tDS.casePen\",      &g_controlsStrings.tdsCasePen);\n#endif // #ifdef CONTROLS_16M_STRINGS\n\n#if defined(CONTROLS_3DS_STRINGS)\n    insert(m_engineMap, \"menu.controls.tDS.buttonZL\",     &g_controlsStrings.tdsButtonZL);\n    insert(m_engineMap, \"menu.controls.tDS.buttonZR\",     &g_controlsStrings.tdsButtonZR);\n    insert(m_engineMap, \"menu.controls.tDS.dPad\",         &g_controlsStrings.tdsDpad);\n    insert(m_engineMap, \"menu.controls.tDS.tStick\",       &g_controlsStrings.tdsTstick);\n    insert(m_engineMap, \"menu.controls.tDS.cStick\",       &g_controlsStrings.tdsCstick);\n#endif // #ifdef CONTROLS_3DS_STRINGS\n\n#if defined(CONTROLS_WII_STRINGS)\n    insert(m_engineMap, \"menu.controls.wii.typeWiimote\",         &g_controlsStrings.wiiTypeWiimote);\n    insert(m_engineMap, \"menu.controls.wii.typeNunchuck\",        &g_controlsStrings.wiiTypeNunchuck);\n    insert(m_engineMap, \"menu.controls.wii.typeClassic\",         &g_controlsStrings.wiiTypeClassic);\n    insert(m_engineMap, \"menu.controls.wii.typeGamecube\",        &g_controlsStrings.wiiTypeGamecube);\n    insert(m_engineMap, \"menu.controls.wii.phraseNewNunchuck\",   &g_controlsStrings.wiiPhraseNewNunchuck);\n    insert(m_engineMap, \"menu.controls.wii.phraseNewClassic\",    &g_controlsStrings.wiiPhraseNewClassic);\n\n    insert(m_engineMap, \"menu.controls.wii.wiimote.dPad\",        &g_controlsStrings.wiiDpad);\n    insert(m_engineMap, \"menu.controls.wii.wiimote.buttonA\",     &g_controlsStrings.wiiButtonA);\n    insert(m_engineMap, \"menu.controls.wii.wiimote.buttonB\",     &g_controlsStrings.wiiButtonB);\n    insert(m_engineMap, \"menu.controls.wii.wiimote.buttonMinus\", &g_controlsStrings.wiiButtonMinus);\n    insert(m_engineMap, \"menu.controls.wii.wiimote.buttonPlus\",  &g_controlsStrings.wiiButtonPlus);\n    insert(m_engineMap, \"menu.controls.wii.wiimote.buttonHome\",  &g_controlsStrings.wiiButtonHome);\n    insert(m_engineMap, \"menu.controls.wii.wiimote.button2\",     &g_controlsStrings.wiiButton2);\n    insert(m_engineMap, \"menu.controls.wii.wiimote.button1\",     &g_controlsStrings.wiiButton1);\n    insert(m_engineMap, \"menu.controls.wii.wiimote.shake\",       &g_controlsStrings.wiiShake);\n    insert(m_engineMap, \"menu.controls.wii.wiimote.caseIR\",      &g_controlsStrings.wiiCaseIR);\n\n    insert(m_engineMap, \"menu.controls.wii.nunchuck.prefixN\",    &g_controlsStrings.wiiPrefixNunchuck);\n    insert(m_engineMap, \"menu.controls.wii.nunchuck.buttonZ\",    &g_controlsStrings.wiiButtonZ);\n    insert(m_engineMap, \"menu.controls.wii.nunchuck.buttonC\",    &g_controlsStrings.wiiButtonC);\n\n    insert(m_engineMap, \"menu.controls.wii.classic.lStick\",      &g_controlsStrings.wiiLStick);\n    insert(m_engineMap, \"menu.controls.wii.classic.rStick\",      &g_controlsStrings.wiiRStick);\n    insert(m_engineMap, \"menu.controls.wii.classic.buttonZL\",    &g_controlsStrings.wiiButtonZL);\n    insert(m_engineMap, \"menu.controls.wii.classic.buttonZR\",    &g_controlsStrings.wiiButtonZR);\n    insert(m_engineMap, \"menu.controls.wii.classic.buttonLT\",    &g_controlsStrings.wiiButtonLT);\n    insert(m_engineMap, \"menu.controls.wii.classic.buttonRT\",    &g_controlsStrings.wiiButtonRT);\n    insert(m_engineMap, \"menu.controls.wii.classic.buttonX\",     &g_controlsStrings.wiiButtonX);\n    insert(m_engineMap, \"menu.controls.wii.classic.buttonY\",     &g_controlsStrings.wiiButtonY);\n#endif // #ifdef CONTROLS_WII_STRINGS\n\n    insert(m_engineMap, \"menu.wordNo\",         &g_mainMenu.wordNo);\n    insert(m_engineMap, \"menu.wordYes\",        &g_mainMenu.wordYes);\n    insert(m_engineMap, \"menu.caseNone\",       &g_mainMenu.caseNone);\n    insert(m_engineMap, \"menu.wordOn\",         &g_mainMenu.wordOn);\n    insert(m_engineMap, \"menu.wordOff\",        &g_mainMenu.wordOff);\n    insert(m_engineMap, \"menu.wordShow\",       &g_mainMenu.wordShow);\n    insert(m_engineMap, \"menu.wordHide\",       &g_mainMenu.wordHide);\n    insert(m_engineMap, \"menu.wordPlayer\",     &g_mainMenu.wordPlayer);\n    insert(m_engineMap, \"menu.wordProfile\",    &g_mainMenu.wordProfile);\n    insert(m_engineMap, \"menu.wordBack\",       &g_mainMenu.wordBack);\n    insert(m_engineMap, \"menu.wordResume\",     &g_mainMenu.wordResume);\n    insert(m_engineMap, \"menu.wordWaiting\",    &g_mainMenu.wordWaiting);\n\n    insert(m_engineMap, \"menu.abbrevMilliseconds\", &g_mainMenu.abbrevMilliseconds);\n\n\n    insert(m_engineMap, \"outro.gameCredits\",           &g_outroScreen.gameCredits);\n    insert(m_engineMap, \"outro.engineCredits\",         &g_outroScreen.engineCredits);\n    insert(m_engineMap, \"outro.originalBy\",            &g_outroScreen.originalBy);\n    insert(m_engineMap, \"outro.nameAndrewSpinks\",      &g_outroScreen.nameAndrewSpinks);\n    insert(m_engineMap, \"outro.cppPortDevelopers\",     &g_outroScreen.cppPortDevelopers);\n    insert(m_engineMap, \"outro.nameVitalyNovichkov\",   &g_outroScreen.nameVitalyNovichkov);\n    insert(m_engineMap, \"outro.qualityControl\",        &g_outroScreen.qualityControl);\n    insert(m_engineMap, \"outro.psVitaPortBy\",          &g_outroScreen.psVitaPortBy);\n    insert(m_engineMap, \"outro.levelDesign\",           &g_outroScreen.levelDesign);\n    insert(m_engineMap, \"outro.customSprites\",         &g_outroScreen.customSprites);\n    insert(m_engineMap, \"outro.specialThanks\",         &g_outroScreen.specialThanks);\n\n\n    insert(m_engineMap, \"game.msgbox.sysInfoTitle\",                &g_gameStrings.msgBoxTitleInfo);\n    insert(m_engineMap, \"game.msgbox.sysInfoWarning\",              &g_gameStrings.msgBoxTitleWarning);\n    insert(m_engineMap, \"game.msgbox.sysInfoError\",                &g_gameStrings.msgBoxTitleError);\n\n    insert(m_engineMap, \"game.loader.loading\",                     &g_gameStrings.loaderLoading);\n    insert(m_engineMap, \"game.loader.statusLoadData\",              &g_gameStrings.loaderStatusLoadData);\n    insert(m_engineMap, \"game.loader.statusLoadFile\",              &g_gameStrings.loaderStatusLoadFile);\n    insert(m_engineMap, \"game.loader.statusGameInfo\",              &g_gameStrings.loaderStatusGameInfo);\n    insert(m_engineMap, \"game.loader.statusTranslations\",          &g_gameStrings.loaderStatusTranslations);\n    insert(m_engineMap, \"game.loader.statusAssetPacks\",            &g_gameStrings.loaderStatusAssetPacks);\n    insert(m_engineMap, \"game.loader.statusFinishing\",             &g_gameStrings.loaderStatusFinishing);\n\n    insert(m_engineMap, \"game.error.openFileFailed\",               &g_gameStrings.errorOpenFileFailed);\n#if defined(THEXTECH_INTERPROC_SUPPORTED) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    insert(m_engineMap, \"game.error.openIPCDataFailed\",            &g_gameStrings.errorOpenIPCDataFailed);\n#endif\n    insert(m_engineMap, \"game.error.errorTooOldEngine\",            &g_gameStrings.errorTooOldEngine);\n    insert(m_engineMap, \"game.error.errorTooOldGameAssets\",        &g_gameStrings.errorTooOldGameAssets);\n    insert(m_engineMap, \"game.error.errorInvalidEnterWarp\",        &g_gameStrings.errorInvalidEnterWarp);\n    insert(m_engineMap, \"game.error.errorNoStartPoint\",            &g_gameStrings.errorNoStartPoint);\n#if defined(THEXTECH_INTERPROC_SUPPORTED) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    insert(m_engineMap, \"game.error.IPCTimeOut\",                   &g_gameStrings.errorIPCTimeOut);\n#endif\n\n    insert(m_engineMap, \"game.error.warpNeedStarCount\",            &g_gameStrings.warpNeedStarCount);\n\n    insert(m_engineMap, \"game.message.scanningLevels\",             &g_gameStrings.messageScanningLevels);\n    insert(m_engineMap, \"game.format.minutesSeconds\",              &g_gameStrings.formatMinutesSeconds);\n\n#if defined(THEXTECH_INTERPROC_SUPPORTED) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    insert(m_engineMap, \"game.ipcStatus.waitingInput\",             &g_gameStrings.ipcStatusWaitingInput);\n    insert(m_engineMap, \"game.ipcStatus.dataTransferStarted\",      &g_gameStrings.ipcStatusDataTransferStarted);\n    insert(m_engineMap, \"game.ipcStatus.dataAccepted\",             &g_gameStrings.ipcStatusDataAccepted);\n    insert(m_engineMap, \"game.ipcStatus.dataValid\",                &g_gameStrings.ipcStatusDataValid);\n    insert(m_engineMap, \"game.ipcStatus.errorTimeout\",             &g_gameStrings.ipcStatusErrorTimeout);\n    insert(m_engineMap, \"game.ipcStatus.loadingdone\",              &g_gameStrings.ipcStatusLoadingDone);\n#endif\n\n#if !defined(NO_WINDOW_FOCUS_TRACKING) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    insert(m_engineMap, \"game.screenPaused\",               &g_gameStrings.screenPaused);\n#endif\n\n    insert(m_engineMap, \"game.pause.continue\",             &g_gameStrings.pauseItemContinue);\n    insert(m_engineMap, \"game.pause.restartLevel\",         &g_gameStrings.pauseItemRestartLevel);\n    insert(m_engineMap, \"game.pause.resetCheckpoints\",     &g_gameStrings.pauseItemResetCheckpoints);\n    insert(m_engineMap, \"game.pause.quitTesting\",          &g_gameStrings.pauseItemQuitTesting);\n    insert(m_engineMap, \"game.pause.returnToEditor\",       &g_gameStrings.pauseItemReturnToEditor);\n    insert(m_engineMap, \"game.pause.playerSetup\",          &g_gameStrings.pauseItemPlayerSetup);\n    insert(m_engineMap, \"game.pause.enterCode\",            &g_gameStrings.pauseItemEnterCode);\n    insert(m_engineMap, \"game.pause.saveAndContinue\",      &g_gameStrings.pauseItemSaveAndContinue);\n    insert(m_engineMap, \"game.pause.saveAndQuit\",          &g_gameStrings.pauseItemSaveAndQuit);\n    insert(m_engineMap, \"game.pause.quit\",                 &g_gameStrings.pauseItemQuit);\n\n    insert(m_engineMap, \"game.connect.reconnectTitle\",            &g_gameStrings.connectReconnectTitle);\n\n    insert(m_engineMap, \"game.connect.phrasePressAButton\",        &g_gameStrings.connectPressAButton);\n\n    insert(m_engineMap, \"game.connect.phraseTestProfile\",        &g_gameStrings.connectTestProfile);\n    insert(m_engineMap, \"game.connect.phraseHoldStart\",           &g_gameStrings.connectHoldStart);\n    insert(m_engineMap, \"game.connect.wordDisconnect\",            &g_gameStrings.connectDisconnect);\n\n    insert(m_engineMap, \"game.connect.phraseForceResume\",         &g_gameStrings.connectForceResume);\n    insert(m_engineMap, \"game.connect.phraseDropPX\",              &g_gameStrings.connectDropPX);\n\n    insert(m_engineMap, \"game.connect.phraseWaitingForInput\",     &g_gameStrings.connectWaitingForInputDevice);\n    insert(m_engineMap, \"game.connect.splitPressSelect_1\",        &g_gameStrings.connectPressSelectForControlsOptions_P1);\n    insert(m_engineMap, \"game.connect.splitPressSelect_2\",        &g_gameStrings.connectPressSelectForControlsOptions_P2);\n\n    insert(m_engineMap, \"game.connect.phraseDropMe\",              &g_gameStrings.connectDropMe);\n\n#if defined(THEXTECH_ENABLE_EDITOR) || !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    insert(m_engineMap, \"editor.block.pickContents\",       &g_editorStrings.pickBlockContents);\n\n    insert(m_engineMap, \"editor.block.letterWidth\",        &g_editorStrings.blockLetterWidth);\n    insert(m_engineMap, \"editor.block.letterHeight\",       &g_editorStrings.blockLetterHeight);\n    insert(m_engineMap, \"editor.block.canBreak\",           &g_editorStrings.blockCanBreak);\n    insert(m_engineMap, \"editor.block.canBreakTooltip\",    &g_editorStrings.blockTooltipCanBreak);\n    insert(m_engineMap, \"editor.block.slick\",              &g_editorStrings.blockSlick);\n    insert(m_engineMap, \"editor.block.invis\",              &g_editorStrings.blockInvis);\n    insert(m_engineMap, \"editor.block.inside\",             &g_editorStrings.blockInside);\n\n    insert(m_engineMap, \"editor.warp.title\",               &g_editorStrings.warpTitle);\n    insert(m_engineMap, \"editor.warp.placing\",             &g_editorStrings.warpPlacing);\n    insert(m_engineMap, \"editor.warp.in\",                  &g_editorStrings.warpIn);\n    insert(m_engineMap, \"editor.warp.out\",                 &g_editorStrings.warpOut);\n    insert(m_engineMap, \"editor.warp.dir\",                 &g_editorStrings.warpDir);\n    insert(m_engineMap, \"editor.warp.twoWay\",              &g_editorStrings.warpTwoWay);\n    insert(m_engineMap, \"editor.warp.style.style\",         &g_editorStrings.warpStyle);\n    insert(m_engineMap, \"editor.warp.style.pipe\",          &g_editorStrings.warpStylePipe);\n    insert(m_engineMap, \"editor.warp.style.door\",          &g_editorStrings.warpStyleDoor);\n    insert(m_engineMap, \"editor.warp.style.blipInstant\",   &g_editorStrings.warpStyleBlipInstant);\n    insert(m_engineMap, \"editor.warp.style.portal\",        &g_editorStrings.warpStylePortal);\n    insert(m_engineMap, \"editor.warp.effect\",              &g_editorStrings.warpEffect);\n    insert(m_engineMap, \"editor.warp.allow\",               &g_editorStrings.warpAllow);\n    insert(m_engineMap, \"editor.warp.item\",                &g_editorStrings.warpItem);\n    insert(m_engineMap, \"editor.warp.ride\",                &g_editorStrings.warpRide);\n    insert(m_engineMap, \"editor.warp.cannonExit\",          &g_editorStrings.warpCannonExit);\n    insert(m_engineMap, \"editor.warp.speed\",               &g_editorStrings.warpSpeed);\n    insert(m_engineMap, \"editor.warp.needStarCount\",       &g_editorStrings.warpNeedStarCount);\n    insert(m_engineMap, \"editor.warp.needKey\",             &g_editorStrings.warpNeedKey);\n    insert(m_engineMap, \"editor.warp.needFloor\",           &g_editorStrings.warpNeedFloor);\n    insert(m_engineMap, \"editor.warp.starLockMessage\",     &g_editorStrings.warpStarLockMessage);\n    insert(m_engineMap, \"editor.warp.toMap\",               &g_editorStrings.warpToMap);\n    insert(m_engineMap, \"editor.warp.lvlWarp\",             &g_editorStrings.warpLvlWarp);\n    insert(m_engineMap, \"editor.warp.target\",              &g_editorStrings.warpTarget);\n    insert(m_engineMap, \"editor.warp.to\",                  &g_editorStrings.warpTo);\n    insert(m_engineMap, \"editor.warp.showStartScene\",      &g_editorStrings.warpShowStartScene);\n    insert(m_engineMap, \"editor.warp.showStarCount\",       &g_editorStrings.warpShowStarCount);\n\n    insert(m_engineMap, \"editor.water.title\",              &g_editorStrings.waterTitle);\n\n    insert(m_engineMap, \"editor.npc.inContainer\",          &g_editorStrings.npcInContainer);\n    insert(m_engineMap, \"editor.npc.inertNice\",            &g_editorStrings.npcInertNice);\n    insert(m_engineMap, \"editor.npc.stuckStop\",            &g_editorStrings.npcStuckStop);\n    insert(m_engineMap, \"editor.npc.props.active\",         &g_editorStrings.npcPropertyActive);\n    insert(m_engineMap, \"editor.npc.props.attachSurface\",  &g_editorStrings.npcPropertyAttachSurface);\n    insert(m_engineMap, \"editor.npc.props.facing\",         &g_editorStrings.npcPropertyFacing);\n    insert(m_engineMap, \"editor.npc.abbrevGen\",            &g_editorStrings.npcAbbrevGen);\n\n    insert(m_engineMap, \"editor.npc.ai.aiIs\",              &g_editorStrings.npcAiIs);\n    insert(m_engineMap, \"editor.npc.ai.target\",            &g_editorStrings.npcAiTarget);\n    insert(m_engineMap, \"editor.npc.ai.jump\",              &g_editorStrings.npcAiJump);\n    insert(m_engineMap, \"editor.npc.ai.leap\",              &g_editorStrings.npcAiLeap);\n    insert(m_engineMap, \"editor.npc.ai.swim\",              &g_editorStrings.npcAiSwim);\n    insert(m_engineMap, \"editor.npc.ai.LR\",                &g_editorStrings.npcAiLR);\n    insert(m_engineMap, \"editor.npc.ai.UD\",                &g_editorStrings.npcAiUD);\n\n    insert(m_engineMap, \"editor.npc.ai.headerCustomAi\",    &g_editorStrings.npcCustomAi);\n\n    insert(m_engineMap, \"editor.npc.ai.use1_0Ai\",          &g_editorStrings.npcUse1_0Ai);\n    insert(m_engineMap, \"editor.npc.tooltipExpandSection\", &g_editorStrings.npcTooltipExpandSection);\n\n    insert(m_engineMap, \"editor.npc.gen.header\",           &g_editorStrings.npcGenHeader);\n    insert(m_engineMap, \"editor.npc.gen.direction\",        &g_editorStrings.npcGenDirection);\n    insert(m_engineMap, \"editor.npc.gen.effectIs\",         &g_editorStrings.npcGenEffectIs);\n    insert(m_engineMap, \"editor.npc.gen.effectWarp\",       &g_editorStrings.npcGenEffectWarp);\n    insert(m_engineMap, \"editor.npc.gen.effectShoot\",      &g_editorStrings.npcGenEffectShoot);\n\n    insert(m_engineMap, \"editor.wordNPC.nominative\",       &g_editorStrings.wordNPC);\n    insert(m_engineMap, \"editor.wordNPC.genitive\",         &g_editorStrings.wordNPCGenitive);\n\n    insert(m_engineMap, \"editor.wordEvent.nominative\",     &g_editorStrings.wordEvent);\n    insert(m_engineMap, \"editor.wordEvent.genitive\",       &g_editorStrings.wordEventGenitive);\n    insert(m_engineMap, \"editor.wordEvent.typeLabel\",      &g_editorStrings.phraseTypeLabelEvent);\n\n    insert(m_engineMap, \"editor.wordCoins\",                &g_editorStrings.wordCoins);\n\n    insert(m_engineMap, \"editor.wordEnabled\",              &g_editorStrings.wordEnabled);\n    insert(m_engineMap, \"editor.wordText\",                 &g_editorStrings.wordText);\n    insert(m_engineMap, \"editor.wordInstant\",              &g_editorStrings.wordInstant);\n    insert(m_engineMap, \"editor.wordMode\",                 &g_editorStrings.wordMode);\n    insert(m_engineMap, \"editor.wordHeight\",               &g_editorStrings.wordHeight);\n    insert(m_engineMap, \"editor.wordWidth\",                &g_editorStrings.wordWidth);\n\n    insert(m_engineMap, \"editor.labelSortLayer\",           &g_editorStrings.labelSortLayer);\n    insert(m_engineMap, \"editor.labelSortOffset\",          &g_editorStrings.labelSortOffset);\n\n    insert(m_engineMap, \"editor.phraseTextOf\",             &g_editorStrings.phraseTextOf);\n    insert(m_engineMap, \"editor.phraseSectionIndex\",       &g_editorStrings.phraseSectionIndex);\n    insert(m_engineMap, \"editor.phraseRadiusIndex\",        &g_editorStrings.phraseRadiusIndex);\n    insert(m_engineMap, \"editor.phraseWarpIndex\",          &g_editorStrings.phraseWarpIndex);\n    insert(m_engineMap, \"editor.phraseGenericIndex\",       &g_editorStrings.phraseGenericIndex);\n    insert(m_engineMap, \"editor.phraseDelayIsMs\",          &g_editorStrings.phraseDelayIsMs);\n    insert(m_engineMap, \"editor.phraseCountMore\",          &g_editorStrings.phraseCountMore);\n    insert(m_engineMap, \"editor.mapPos\",                   &g_editorStrings.mapPos);\n    insert(m_engineMap, \"editor.phraseAreYouSure\",         &g_editorStrings.phraseAreYouSure);\n    insert(m_engineMap, \"editor.pageBlankOfBlank\",         &g_editorStrings.pageBlankOfBlank);\n\n    insert(m_engineMap, \"editor.letterUp\",                 &g_editorStrings.letterUp);\n    insert(m_engineMap, \"editor.letterDown\",               &g_editorStrings.letterDown);\n    insert(m_engineMap, \"editor.letterLeft\",               &g_editorStrings.letterLeft);\n    insert(m_engineMap, \"editor.letterRight\",              &g_editorStrings.letterRight);\n    insert(m_engineMap, \"editor.letterCoordX\",             &g_editorStrings.letterCoordX);\n    insert(m_engineMap, \"editor.letterCoordY\",             &g_editorStrings.letterCoordY);\n\n    insert(m_engineMap, \"editor.toggleMagicBlock\",         &g_editorStrings.toggleMagicBlock);\n\n    insert(m_engineMap, \"editor.testPlay.magicHand\",       &g_editorStrings.testMagicHand);\n    insert(m_engineMap, \"editor.testPlay.char\",            &g_editorStrings.testChar);\n    insert(m_engineMap, \"editor.testPlay.power\",           &g_editorStrings.testPower);\n    insert(m_engineMap, \"editor.testPlay.boot\",            &g_editorStrings.testBoot);\n    insert(m_engineMap, \"editor.testPlay.pet\",             &g_editorStrings.testPet);\n\n    insert(m_engineMap, \"editor.events.header\",            &g_editorStrings.eventsHeader);\n\n    insert(m_engineMap, \"editor.events.letter.activate\",    &g_editorStrings.eventsLetterActivate);\n    insert(m_engineMap, \"editor.events.letter.death\",       &g_editorStrings.eventsLetterDeath);\n    insert(m_engineMap, \"editor.events.letter.talk\",        &g_editorStrings.eventsLetterTalk);\n    insert(m_engineMap, \"editor.events.letter.layerClear\",  &g_editorStrings.eventsLetterLayerClear);\n    insert(m_engineMap, \"editor.events.letter.hit\",         &g_editorStrings.eventsLetterHit);\n    insert(m_engineMap, \"editor.events.letter.destroy\",     &g_editorStrings.eventsLetterDestroy);\n    insert(m_engineMap, \"editor.events.letter.enter\",       &g_editorStrings.eventsLetterEnter);\n\n    insert(m_engineMap, \"editor.events.label.next\",        &g_editorStrings.eventsLabelNext);\n    insert(m_engineMap, \"editor.events.label.activate\",    &g_editorStrings.eventsLabelActivate);\n    insert(m_engineMap, \"editor.events.label.death\",       &g_editorStrings.eventsLabelDeath);\n    insert(m_engineMap, \"editor.events.label.talk\",        &g_editorStrings.eventsLabelTalk);\n    insert(m_engineMap, \"editor.events.label.layerClear\",  &g_editorStrings.eventsLabelLayerClear);\n    insert(m_engineMap, \"editor.events.label.hit\",         &g_editorStrings.eventsLabelHit);\n    insert(m_engineMap, \"editor.events.label.destroy\",     &g_editorStrings.eventsLabelDestroy);\n    insert(m_engineMap, \"editor.events.label.enter\",       &g_editorStrings.eventsLabelEnter);\n\n    insert(m_engineMap, \"editor.events.desc.activate\",     &g_editorStrings.eventsDescActivate);\n    insert(m_engineMap, \"editor.events.desc.death\",        &g_editorStrings.eventsDescDeath);\n    insert(m_engineMap, \"editor.events.desc.talk\",         &g_editorStrings.eventsDescTalk);\n    insert(m_engineMap, \"editor.events.desc.layerClear\",   &g_editorStrings.eventsDescLayerClear);\n    insert(m_engineMap, \"editor.events.desc.hit\",          &g_editorStrings.eventsDescHit);\n    insert(m_engineMap, \"editor.events.desc.destroy\",      &g_editorStrings.eventsDescDestroy);\n    insert(m_engineMap, \"editor.events.desc.enter\",        &g_editorStrings.eventsDescEnter);\n\n    insert(m_engineMap, \"editor.events.desc.phraseTriggersWhenTemplate\",   &g_editorStrings.eventsDescPhraseTriggersWhenTemplate);\n    insert(m_engineMap, \"editor.events.desc.phraseTriggersAfterTemplate\",   &g_editorStrings.eventsDescPhraseTriggersAfterTemplate);\n\n    insert(m_engineMap, \"editor.events.deletion.deletingEvent\",    &g_editorStrings.eventsDeletingEvent);\n    insert(m_engineMap, \"editor.events.deletion.confirm\",          &g_editorStrings.eventsDeletionConfirm);\n    insert(m_engineMap, \"editor.events.deletion.cancel\",           &g_editorStrings.eventsDeletionCancel);\n\n    insert(m_engineMap, \"editor.events.promptEventText\",  &g_editorStrings.eventsPromptEventText);\n    insert(m_engineMap, \"editor.events.promptEventName\",  &g_editorStrings.eventsPromptEventName);\n    insert(m_engineMap, \"editor.events.itemNewEvent\",     &g_editorStrings.eventsItemNewEvent);\n\n    insert(m_engineMap, \"editor.events.controlsForEventN\",      &g_editorStrings.eventsControlsForEvent);\n    insert(m_engineMap, \"editor.events.settingsForEvent\",       &g_editorStrings.eventsSettingsForEvent);\n\n    insert(m_engineMap, \"editor.events.layers.headerShow\",      &g_editorStrings.eventsHeaderShow);\n    insert(m_engineMap, \"editor.events.layers.headerHide\",      &g_editorStrings.eventsHeaderHide);\n    insert(m_engineMap, \"editor.events.layers.headerToggle\",    &g_editorStrings.eventsHeaderToggle);\n    insert(m_engineMap, \"editor.events.layers.headerMove\",      &g_editorStrings.eventsHeaderMove);\n\n    insert(m_engineMap, \"editor.events.sections.actionKeep\",    &g_editorStrings.eventsActionKeep);\n    insert(m_engineMap, \"editor.events.sections.actionReset\",   &g_editorStrings.eventsActionReset);\n    insert(m_engineMap, \"editor.events.sections.actionSet\",     &g_editorStrings.eventsActionSet);\n\n    insert(m_engineMap, \"editor.events.sections.propMusic\",     &g_editorStrings.eventsCaseMusic);\n    insert(m_engineMap, \"editor.events.sections.propBackground\", &g_editorStrings.eventsCaseBackground);\n    insert(m_engineMap, \"editor.events.sections.propBounds\",    &g_editorStrings.eventsCaseBounds);\n\n    insert(m_engineMap, \"editor.events.sections.phraseAllSections\",     &g_editorStrings.eventsPhraseAllSections);\n\n    insert(m_engineMap, \"editor.events.props.autostart\",     &g_editorStrings.eventsPropAutostart);\n    insert(m_engineMap, \"editor.events.props.sound\",         &g_editorStrings.eventsPropSound);\n    insert(m_engineMap, \"editor.events.props.endGame\",       &g_editorStrings.eventsPropEndGame);\n    insert(m_engineMap, \"editor.events.props.controls\",      &g_editorStrings.eventsPropControls);\n    insert(m_engineMap, \"editor.events.props.layerSmoke\",    &g_editorStrings.eventsPropLayerSmoke);\n\n    insert(m_engineMap, \"editor.events.headerTriggerEvent\",  &g_editorStrings.eventsHeaderTriggerEvent);\n\n    insert(m_engineMap, \"editor.level.levelName\",            &g_editorStrings.levelName);\n    insert(m_engineMap, \"editor.level.startPos\",             &g_editorStrings.levelStartPos);\n    insert(m_engineMap, \"editor.level.pathBG\",               &g_editorStrings.levelPathBG);\n    insert(m_engineMap, \"editor.level.bigBG\",                &g_editorStrings.levelBigBG);\n    insert(m_engineMap, \"editor.level.gameStart\",            &g_editorStrings.levelGameStart);\n    insert(m_engineMap, \"editor.level.alwaysVis\",            &g_editorStrings.levelAlwaysVis);\n    insert(m_engineMap, \"editor.level.pathUnlocks\",          &g_editorStrings.levelPathUnlocks);\n\n    insert(m_engineMap, \"editor.section.scroll\",             &g_editorStrings.sectionScroll);\n    insert(m_engineMap, \"editor.section.horizWrap\",          &g_editorStrings.sectionHorizWrap);\n    insert(m_engineMap, \"editor.section.vertWrap\",           &g_editorStrings.sectionVertWrap);\n    insert(m_engineMap, \"editor.section.underwater\",         &g_editorStrings.sectionUnderwater);\n    insert(m_engineMap, \"editor.section.noTurnBack\",         &g_editorStrings.sectionNoTurnBack);\n    insert(m_engineMap, \"editor.section.offscreenExit\",      &g_editorStrings.sectionOffscreenExit);\n\n    insert(m_engineMap, \"editor.world.name\",                 &g_editorStrings.worldName);\n    insert(m_engineMap, \"editor.world.introLevel\",           &g_editorStrings.worldIntroLevel);\n    insert(m_engineMap, \"editor.world.hubWorld\",             &g_editorStrings.worldHubWorld);\n    insert(m_engineMap, \"editor.world.retryOnFail\",          &g_editorStrings.worldRetryOnFail);\n    insert(m_engineMap, \"editor.world.totalStars\",           &g_editorStrings.worldTotalStars);\n    insert(m_engineMap, \"editor.world.allowChars\",           &g_editorStrings.worldAllowChars);\n    insert(m_engineMap, \"editor.world.phraseCreditIndex\",    &g_editorStrings.worldCreditIndex);\n\n    insert(m_engineMap, \"editor.select.soundForEventN\",              &g_editorStrings.selectSoundForEvent);\n    insert(m_engineMap, \"editor.select.sectBlankPropBlankForEventN\", &g_editorStrings.selectSectBlankPropBlankForEvent);\n    insert(m_engineMap, \"editor.select.allSectPropBlankForEventN\",   &g_editorStrings.selectAllSectPropBlankForEvent);\n    insert(m_engineMap, \"editor.select.sectionBlankPropBlank\",     &g_editorStrings.selectSectionBlankPropBlank);\n    insert(m_engineMap, \"editor.select.pathBlankUnlock\",           &g_editorStrings.selectPathBlankUnlock);\n    insert(m_engineMap, \"editor.select.warpTransitEffect\",         &g_editorStrings.selectWarpTransitionEffect);\n    insert(m_engineMap, \"editor.select.worldMusic\",                &g_editorStrings.selectWorldMusic);\n\n    insert(m_engineMap, \"editor.layers.header\",              &g_editorStrings.layersHeader);\n\n    insert(m_engineMap, \"editor.layers.label\",               &g_editorStrings.labelLayer);\n    insert(m_engineMap, \"editor.layers.labelAttached\",       &g_editorStrings.layersLabelAttached);\n    insert(m_engineMap, \"editor.layers.abbrevAttLayer\",      &g_editorStrings.layersAbbrevAttLayer);\n    insert(m_engineMap, \"editor.layers.default\",             &g_editorStrings.layersLayerDefault);\n\n    insert(m_engineMap, \"editor.layers.labelAttachedLayer\",  &g_editorStrings.layersLabelAttachedLayer);\n    insert(m_engineMap, \"editor.layers.labelMoveLayer\",      &g_editorStrings.layersLabelMoveLayer);\n\n    insert(m_engineMap, \"editor.layers.deletion.header\",                   &g_editorStrings.layersDeletionHeader);\n    insert(m_engineMap, \"editor.layers.deletion.preserveLayerContents\",    &g_editorStrings.layersDeletionPreserveLayerContents);\n    insert(m_engineMap, \"editor.layers.deletion.confirmPreserve\",          &g_editorStrings.layersDeletionConfirmPreserve);\n    insert(m_engineMap, \"editor.layers.deletion.confirmDelete\",            &g_editorStrings.layersDeletionConfirmDelete);\n    insert(m_engineMap, \"editor.layers.deletion.cancel\",                   &g_editorStrings.layersDeletionCancel);\n\n    insert(m_engineMap, \"editor.layers.desc.att\", &g_editorStrings.layersDescAtt);\n\n    insert(m_engineMap, \"editor.layers.promptLayerName\", &g_editorStrings.layersPromptLayerName);\n    insert(m_engineMap, \"editor.layers.itemNewLayer\",    &g_editorStrings.layersItemNewLayer);\n\n    insert(m_engineMap, \"editor.file.actionClearLevel\",            &g_editorStrings.fileActionClearLevel);\n    insert(m_engineMap, \"editor.file.actionClearWorld\",            &g_editorStrings.fileActionClearWorld);\n    insert(m_engineMap, \"editor.file.actionOpen\",                  &g_editorStrings.fileActionOpen);\n    insert(m_engineMap, \"editor.file.actionRevert\",                &g_editorStrings.fileActionRevert);\n    insert(m_engineMap, \"editor.file.actionExit\",                  &g_editorStrings.fileActionExit);\n    insert(m_engineMap, \"editor.file.confirmSaveBeforeAction\",     &g_editorStrings.fileConfirmationSaveBeforeAction);\n    insert(m_engineMap, \"editor.file.confirmConfirmAction\",        &g_editorStrings.fileConfirmationConfirmAction);\n    insert(m_engineMap, \"editor.file.confirmConvertFormatTo\",      &g_editorStrings.fileConfirmationConvertFormatTo);\n    insert(m_engineMap, \"editor.file.optionYesSaveThenAction\",     &g_editorStrings.fileOptionYesSaveThenAction);\n    insert(m_engineMap, \"editor.file.optionActionWithoutSave\",     &g_editorStrings.fileOptionActionWithoutSave);\n    insert(m_engineMap, \"editor.file.optionCancelAction\",          &g_editorStrings.fileOptionCancelAction);\n    insert(m_engineMap, \"editor.file.optionProceedWithConversion\", &g_editorStrings.fileOptionProceedWithConversion);\n    insert(m_engineMap, \"editor.file.optionCancelConversion\",      &g_editorStrings.fileOptionCancelConversion);\n\n    insert(m_engineMap, \"editor.file.labelCurrentFile\",    &g_editorStrings.fileLabelCurrentFile);\n    insert(m_engineMap, \"editor.file.labelFormat\",         &g_editorStrings.fileLabelFormat);\n\n    insert(m_engineMap, \"editor.file.formatModern\",        &g_editorStrings.fileFormatModern);\n    insert(m_engineMap, \"editor.file.formatLegacy\",        &g_editorStrings.fileFormatLegacy);\n\n    insert(m_engineMap, \"editor.file.sectionLevel\",        &g_editorStrings.fileSectionLevel);\n    insert(m_engineMap, \"editor.file.sectionWorld\",        &g_editorStrings.fileSectionWorld);\n    insert(m_engineMap, \"editor.file.commandNew\",          &g_editorStrings.fileCommandNew);\n    insert(m_engineMap, \"editor.file.commandOpen\",         &g_editorStrings.fileCommandOpen);\n    insert(m_engineMap, \"editor.file.commandSave\",         &g_editorStrings.fileCommandSave);\n    insert(m_engineMap, \"editor.file.commandSaveAs\",       &g_editorStrings.fileCommandSaveAs);\n\n    insert(m_engineMap, \"editor.file.convert.descNew\",             &g_editorStrings.fileConvertDesc);\n\n#if 0\n    insert(m_engineMap, \"editor.file.convert._38aUnsupported\",     &g_editorStrings.fileConvert38aUnsupported);\n    insert(m_engineMap, \"editor.file.convert.formatUnknown\",       &g_editorStrings.fileConvertFormatUnknown);\n\n    insert(m_engineMap, \"editor.file.convert.featureWarpTransit\",      &g_editorStrings.fileConvertFeatureWarpTransit);\n    insert(m_engineMap, \"editor.file.convert.featureWarpNeedsStand\",   &g_editorStrings.fileConvertFeatureWarpNeedsStand);\n    insert(m_engineMap, \"editor.file.convert.featureWarpCannonExit\",   &g_editorStrings.fileConvertFeatureWarpCannonExit);\n    insert(m_engineMap, \"editor.file.convert.featureWarpEnterEvent\",   &g_editorStrings.fileConvertFeatureWarpEnterEvent);\n    insert(m_engineMap, \"editor.file.convert.featureWarpCustomStarsMsg\", &g_editorStrings.fileConvertFeatureWarpCustomStarsMsg);\n    insert(m_engineMap, \"editor.file.convert.featureWarpNoPrintStars\", &g_editorStrings.fileConvertFeatureWarpNoPrintStars);\n    insert(m_engineMap, \"editor.file.convert.featureWarpNoStartScene\", &g_editorStrings.fileConvertFeatureWarpNoStartScene);\n    insert(m_engineMap, \"editor.file.convert.featureWarpPortal\",       &g_editorStrings.fileConvertFeatureWarpPortal);\n\n    insert(m_engineMap, \"editor.file.convert.featureEventCustomMusic\", &g_editorStrings.fileConvertFeatureEventCustomMusic);\n    insert(m_engineMap, \"editor.file.convert.featureEventAutoscroll\",  &g_editorStrings.fileConvertFeatureEventAutoscroll);\n\n    insert(m_engineMap, \"editor.file.convert.featureNPCVariant\",       &g_editorStrings.fileConvertFeatureNPCVariant);\n    insert(m_engineMap, \"editor.file.convert.featureBlockForceSmashable\", &g_editorStrings.fileConvertFeatureBlockForceSmashable);\n    insert(m_engineMap, \"editor.file.convert.featureBgoOrder\",         &g_editorStrings.fileConvertFeatureBgoOrder);\n\n    insert(m_engineMap, \"editor.file.convert.featureCustomWorldMusic\", &g_editorStrings.fileConvertFeatureCustomWorldMusic);\n    insert(m_engineMap, \"editor.file.convert.featureWorldStarDisplay\", &g_editorStrings.fileConvertFeatureWorldStarDisplay);\n    insert(m_engineMap, \"editor.file.convert.featureLevelStarDisplay\", &g_editorStrings.fileConvertFeatureLevelStarDisplay);\n    insert(m_engineMap, \"editor.file.convert.featureWorldMapSections\", &g_editorStrings.fileConvertFeatureWorldMapSections);\n#endif\n\n    insert(m_engineMap, \"editor.browser.newFile\",          &g_editorStrings.browserNewFile);\n    insert(m_engineMap, \"editor.browser.saveFile\",         &g_editorStrings.browserSaveFile);\n    insert(m_engineMap, \"editor.browser.openFile\",         &g_editorStrings.browserOpenFile);\n\n    insert(m_engineMap, \"editor.browser.itemNewFile\",      &g_editorStrings.browserItemNewFile);\n    insert(m_engineMap, \"editor.browser.itemNewFolder\",    &g_editorStrings.browserItemNewFolder);\n\n    insert(m_engineMap, \"editor.browser.askOverwriteFile\", &g_editorStrings.browserAskOverwriteFile);\n\n    insert(m_engineMap, \"editor.tooltip.select\",   &g_editorStrings.tooltipSelect);\n    insert(m_engineMap, \"editor.tooltip.erase\",    &g_editorStrings.tooltipErase);\n    insert(m_engineMap, \"editor.tooltip.eraseAll\", &g_editorStrings.tooltipEraseAll);\n    insert(m_engineMap, \"editor.tooltip.blocks\",   &g_editorStrings.tooltipBlocks);\n    insert(m_engineMap, \"editor.tooltip.BGOs\",     &g_editorStrings.tooltipBGOs);\n    insert(m_engineMap, \"editor.tooltip.NPCs\",     &g_editorStrings.tooltipNPCs);\n    insert(m_engineMap, \"editor.tooltip.warps\",    &g_editorStrings.tooltipWarps);\n    insert(m_engineMap, \"editor.tooltip.water\",    &g_editorStrings.tooltipWater);\n    insert(m_engineMap, \"editor.tooltip.settings\", &g_editorStrings.tooltipSettings);\n    insert(m_engineMap, \"editor.tooltip.layers\",   &g_editorStrings.tooltipLayers);\n    insert(m_engineMap, \"editor.tooltip.events\",   &g_editorStrings.tooltipEvents);\n    insert(m_engineMap, \"editor.tooltip.tiles\",    &g_editorStrings.tooltipTiles);\n    insert(m_engineMap, \"editor.tooltip.scenes\",   &g_editorStrings.tooltipScenes);\n    insert(m_engineMap, \"editor.tooltip.levels\",   &g_editorStrings.tooltipLevels);\n    insert(m_engineMap, \"editor.tooltip.paths\",    &g_editorStrings.tooltipPaths);\n    insert(m_engineMap, \"editor.tooltip.music\",    &g_editorStrings.tooltipMusic);\n    insert(m_engineMap, \"editor.tooltip.area\",     &g_editorStrings.tooltipArea);\n    insert(m_engineMap, \"editor.tooltip.file\",     &g_editorStrings.tooltipFile);\n    insert(m_engineMap, \"editor.tooltip.show\",     &g_editorStrings.tooltipShow);\n\n#endif // THEXTECH_ENABLE_EDITOR\n    // };\n\n#ifdef THEXTECH_ENABLE_EDITOR\n    for(int i = 0; i < 6; ++i)\n    {\n        insert(m_engineMap, fmt::format_ne(\"editor.warp.transit.name{0}\", i), &g_editorStrings.listWarpTransitNames[i]);\n    }\n#endif\n\n\n    m_assetsMap =\n    {\n        {\"languageName\", &g_mainMenu.languageName},\n\n        {\"objects.wordStarAccusativeSingular\",      &g_gameInfo.wordStarAccusativeSingular},\n        {\"objects.wordStarAccusativeDualOrCounter\", &g_gameInfo.wordStarAccusativeDual_Cnt},\n        {\"objects.wordStarAccusativePlural\",        &g_gameInfo.wordStarAccusativePlural},\n\n        {\"objects.wordFails\", &g_gameInfo.fails_counter_title}\n    };\n\n    for(int i = 1; i <= numCharacters; ++i)\n        insert(m_assetsMap, fmt::format_ne(\"character.name{0}\", i), &g_gameInfo.characterName[i]);\n\n    // reset all strings for options to hardcoded defaults\n    g_options.reset_options();\n\n    // add config fields to engine map\n    g_options.make_translation(*this);\n\n#ifdef THEXTECH_ENABLE_EDITOR\n    // adds dynamic fields to the asset map\n    EditorCustom::Load(this);\n#endif\n\n    XHints::InitTranslations(*this);\n\n#if TR_MAP_TYPE == 1 && !defined(THEXTECH_DISABLE_LANG_TOOLS)\n    tinysort(m_engineMap.begin(), m_engineMap.end(),\n        [](const TrEntry &a, const TrEntry &b)->bool\n        {\n            return a.first.compare(b.first) < 0;\n        });\n\n    tinysort(m_assetsMap.begin(), m_assetsMap.end(),\n        [](const TrEntry &a, const TrEntry &b)->bool\n        {\n            return a.first.compare(b.first) < 0;\n        });\n#endif\n}\n\nvoid XTechTranslate::reset()\n{\n    initOutroContent();\n    initMainMenu();\n    initGameStrings();\n\n#ifdef THEXTECH_ENABLE_EDITOR\n    initEditorStrings();\n\n    // don't need to reset EditorCustom because we reloaded it in the initializer\n    // it would be dangerous to reload it here because it would invalidate a lot of references\n#endif\n\n    // likewise, unsafe to reset g_options here because it would invalidate pointers to data inside vectors\n    // g_options.reset_options();\n\n    Controls::InitStrings();\n    g_controlsStrings = ControlsStrings_t();\n\n    XHints::ResetStrings();\n\n    s_CurrentPluralRules = PluralRules::OneIsSingular;\n}\n\nvoid XTechTranslate::exportTemplate()\n{\n#ifndef THEXTECH_DISABLE_LANG_TOOLS\n    reset();\n\n    try\n    {\n        nlohmann::ordered_json langFile;\n\n        for(auto &k : m_engineMap)\n        {\n#ifdef DEBUG_BUILD\n            std::printf(\"-- writing %s -> %s\\n\", k.first.c_str(), k.second->c_str());\n#endif\n            setJsonValue(langFile, k.first, *k.second);\n        }\n\n        std::printf(\"Lang file data (Engine): \\n\\n%s\\n\\n\", langFile.dump(4, ' ', false).c_str());\n        std::fflush(stdout);\n\n        langFile.clear();\n\n        for(auto &k : m_assetsMap)\n        {\n#ifdef DEBUG_BUILD\n            std::printf(\"-- writing %s -> %s\\n\", k.first.c_str(), k.second->c_str());\n#endif\n            setJsonValue(langFile, k.first, *k.second);\n        }\n\n        std::printf(\"Lang file data (Assets): \\n\\n%s\\n\\n\", langFile.dump(4, ' ', false).c_str());\n        std::fflush(stdout);\n    }\n    catch(const std::exception &e)\n    {\n        std::printf(\"JSON: Caught an exception: %s\", e.what());\n        std::fflush(stdout);\n    }\n#endif\n}\n\nvoid XTechTranslate::updateLanguages(const std::string &outPath, bool noBlank)\n{\n#ifndef THEXTECH_DISABLE_LANG_TOOLS\n    std::vector<std::string> list;\n    DirMan langs(outPath.empty() ? AppPathManager::languagesDir() : outPath);\n\n    if(!langs.exists())\n    {\n        std::printf(\"Can't open the languages directory: %s\", langs.absolutePath().c_str());\n        std::fflush(stdout);\n        return;\n    }\n\n    if(!langs.getListOfFiles(list, {\".json\"}))\n    {\n        std::printf(\"Can't show the content of the languages directory: %s\", langs.absolutePath().c_str());\n        std::fflush(stdout);\n        return;\n    }\n\n    reset();\n\n    for(auto &f : list)\n    {\n        bool isEnglish = Strings::endsWith(f, \"_en.json\");\n        std::printf(\"-- Processing file %s...\\n\", f.c_str());\n        std::string fullFilePath = langs.absolutePath() + \"/\" + f;\n        bool changed = false;\n\n        TrList &trList = Strings::startsWith(f, \"thextech_\") ? m_engineMap : m_assetsMap;\n\n        try\n        {\n            Files::Data data = Files::load_file(fullFilePath);\n\n            if(!data.valid())\n            {\n                std::printf(\"Warning: Failed to load the translation file %s: can't open file\\n\", fullFilePath.c_str());\n                continue;\n            }\n\n            nlohmann::ordered_json langFile = nlohmann::ordered_json::parse(data.begin(), data.end());\n\n            for(const auto &k : trList)\n            {\n                const std::string &res = *k.second;\n                changed |= setJsonValueIfNotExist(langFile, k.first, isEnglish ? res : std::string(), noBlank);\n            }\n\n            if(!changed)\n            {\n                std::printf(\"Lang file remain unchanged!\\n\");\n                std::fflush(stdout);\n                continue;\n            }\n\n            if(saveFile(fullFilePath, langFile.dump(4, ' ', false)))\n            {\n                std::printf(\"Lang file has been updated!\\n\");\n                std::fflush(stdout);\n            }\n            else\n            {\n                std::printf(\"Error: Failed to write out the file %s!\\n\", fullFilePath.c_str());\n                std::fflush(stdout);\n            }\n        }\n        catch(const std::exception &e)\n        {\n            std::printf(\"Warning: Failed to process the translation file %s: %s\\n\", fullFilePath.c_str(), e.what());\n        }\n    }\n\n    std::fflush(stdout);\n#else\n    UNUSED(outPath);\n    UNUSED(noBlank);\n#endif\n}\n\n\n\nbool XTechTranslate::translate()\n{\n    if(!FontManager::isInitied() || FontManager::isLegacy())\n    {\n        pLogWarning(\"Translations aren't supported without new font engine loaded (the 'fonts' directory is required)\");\n        return false;\n    }\n\n    s_CurrentPluralRules = PluralRules::OneIsSingular;\n\n    const std::string &langEngineFile = XLanguage::getEngineFile();\n    const std::string &langAssetsFile = XLanguage::getAssetsFile();\n\n    if(!langEngineFile.empty() && Files::fileExists(langEngineFile))\n    {\n        // Engine translations\n        if(!translateFile(langEngineFile, m_engineMap, \"engine\"))\n            pLogWarning(\"Failed to apply the engine translation file %s\", langEngineFile.c_str());\n    }\n\n    if(!langAssetsFile.empty() && Files::fileExists(langAssetsFile))\n    {\n        // assets translations\n        if(!translateFile(langAssetsFile, m_assetsMap, \"assets\"))\n            pLogWarning(\"Failed to apply the assets translation file %s\", langAssetsFile.c_str());\n    }\n\n    if(langEngineFile.empty() && langAssetsFile.empty())\n        return false; // No language was found at all\n\n    if(!Files::fileExists(langEngineFile) && !Files::fileExists(langAssetsFile))\n        return false; // Files are not exists, do nothing\n\n    if(g_mainMenu.pluralRules == \"one-is-singular\")\n        s_CurrentPluralRules = PluralRules::OneIsSingular;\n    else if(g_mainMenu.pluralRules == \"singular-only\")\n        s_CurrentPluralRules = PluralRules::SingularOnly;\n    else if(g_mainMenu.pluralRules == \"slavic\")\n        s_CurrentPluralRules = PluralRules::Slavic;\n\n    return true;\n}\n\nbool XTechTranslate::translateFile(const std::string& file, TrList& list, const char *trTypeName)\n{\n    try\n    {\n        // Engine translations\n        Files::Data data = Files::load_file(file);\n\n        if(!data.valid())\n        {\n            pLogWarning(\"Failed to load the %s translation file %s: can't open file\", trTypeName, file.c_str());\n            return false;\n        }\n\n        nlohmann::ordered_json langFile = nlohmann::ordered_json::parse(data.begin(), data.end());\n\n        for(auto &k : list)\n        {\n            std::string &res = *k.second;\n            res = getJsonValue(langFile, k.first, *k.second);\n        }\n    }\n    catch(const std::exception &e)\n    {\n        reset();\n        pLogWarning(\"Failed to load the %s translation file %s: %s\", trTypeName, file.c_str(), e.what());\n        return false;\n    }\n\n    return true;\n}\n\nvoid ReloadTranslations()\n{\n    XLanguage::resolveLanguage(g_config.language);\n\n    FontManager::updateDefaultFontByLang(CurrentLanguage, CurrentLangDialect);\n\n    XTechTranslate translator;\n    translator.reset();\n    if(translator.translate())\n    {\n        pLogDebug(\"Reloaded translation for language %s-%s\",\n                  CurrentLanguage.c_str(),\n                  CurrentLangDialect.empty() ? \"??\" : CurrentLangDialect.c_str());\n    }\n}\n\n"
  },
  {
    "path": "src/main/translate.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef TRANSLATE_H\n#define TRANSLATE_H\n\n#include <map>\n#include <string>\n\n#include \"editor/editor_custom.h\"\n#include \"main/hints.h\"\n\nconst std::string& LanguageFormatNumber(int number, const std::string& singular, const std::string& dual, const std::string& plural);\n\ntemplate<bool writable>\nclass _Config_t;\n\ntemplate<bool writable>\nclass BaseConfigOption_t;\n\ntemplate<bool writable>\nclass ConfigSection_t;\n\ntemplate<bool writable, class value_t>\nclass ConfigEnumOption_t;\n\nclass XTechTranslate\n{\n#ifdef THEXTECH_DISABLE_LANG_TOOLS\n    typedef std::pair<std::string, std::string*> TrEntry;\n    typedef std::vector<TrEntry> TrList;\n#   define TR_MAP_TYPE 1\n#   define TR_MAP_INSERT push_back\n#else\n    typedef std::map<std::string, std::string*> TrList;\n#   define TR_MAP_TYPE 0\n#   define TR_MAP_INSERT insert\n#endif\n    //! Map of the engine specific translation key and the actual string container, used to simplify the maintenance\n    TrList m_engineMap;\n\n    //! Map of the assets specific translation key and the actual string container, used to simplify the maintenance\n    TrList m_assetsMap;\n\n    /**\n     * @brief Inserts a new translation line\n     * @param list Translation list to insert\n     * @param key Key of the translation line\n     * @param value Pointer to the contained string that should be translated\n     */\n    static void insert(TrList &list, const char *key, std::string *value);\n\n    /**\n     * @brief Inserts a new translation line\n     * @param list Translation list to insert\n     * @param key Key of the translation line\n     * @param value Pointer to the contained string that should be translated\n     */\n    static void insert(TrList &list, std::string &&key, std::string *value);\n\npublic:\n    XTechTranslate();\n\n    /*!\n     * \\brief Load default built-in translation\n     */\n    void reset();\n\n    /*!\n     * \\brief Print the content of default translation file to the terminal\n     */\n    void exportTemplate();\n\n    /*!\n     * \\brief Fetch all translation files and update their content (add any missing entries)\n     */\n    void updateLanguages(const std::string& outPath, bool noBlank);\n\n    /*!\n     * \\brief Switch the language of the engine\n     * \\return True if language was been successfully loaded\n     */\n    bool translate();\n\n#ifdef THEXTECH_ENABLE_EDITOR\n    friend void EditorCustom::Load(XTechTranslate* translate);\n    friend struct EditorCustom::ItemList_t;\n#endif\n\n    friend void XHints::InitTranslations(XTechTranslate& translate);\n\n    friend class _Config_t<false>;\n    friend class BaseConfigOption_t<false>;\n    friend class ConfigSection_t<false>;\n\n    template<bool writable, class value_t>\n    friend class ConfigEnumOption_t;\n\nprivate:\n    bool translateFile(const std::string &file, TrList &list, const char* trTypeName);\n};\n\n// safe to call at any time; reloads all engine-level string translations (no level / episode translations)\nvoid ReloadTranslations();\n\n#endif // TRANSLATE_H\n"
  },
  {
    "path": "src/main/translate_episode.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <json/json_rwops_input.hpp>\n#include <json/json.hpp>\n#include <Logger/logger.h>\n#include <Utils/files.h>\n#include <Utils/strings.h>\n#include <fmt_format_ne.h>\n#include <fstream>\n#include <unordered_map>\n#include <vector>\n\n#include \"globals.h\"\n#ifndef LAYERS_H // Workaround for Clazy\n#include \"layers.h\"\n#endif\n\n#include \"fontman/font_manager.h\"\n#include \"translate_episode.h\"\n#define XTECH_TRANSLATE_EPISODE\n#include \"translate/tr_title.h\"\n#include \"translate/tr_level.h\"\n#include \"translate/tr_world.h\"\n#include \"translate/tr_script.h\"\n\n\nstatic std::string getTrFileLang(const std::string &lang, const std::string &dialect, const std::string &subDir, const std::string &episodePath = std::string())\n{\n    std::string langFile;\n\n    const auto &p = episodePath.empty() ?\n                        FileNamePath :\n                        episodePath;\n\n    // Trying to find the dialect-specific translation\n    if(!dialect.empty())\n    {\n        if(!subDir.empty())\n        {\n            // Try to find the translation at the i18n at data sub-directory\n            langFile = p + fmt::format_ne(\"{0}/i18n/translation_{1}-{2}.json\", subDir, lang.c_str(), dialect.c_str());\n            if(!Files::fileExists(langFile))\n                langFile.clear();\n        }\n\n        // Now try at the i18n at episode root\n        if(langFile.empty())\n        {\n            langFile = p + fmt::format_ne(\"i18n/translation_{0}-{1}.json\", lang.c_str(), dialect.c_str());\n            if(!Files::fileExists(langFile))\n                langFile.clear();\n        }\n    }\n\n    // Trying the general translation\n    if(langFile.empty())\n    {\n        // Try to find the translation at the data sub-directory\n        if(!subDir.empty())\n        {\n            // Try to find the translation at the i18n at data sub-directory\n            langFile = p + fmt::format_ne(\"{0}/i18n/translation_{1}.json\", subDir, lang.c_str());\n            if(!Files::fileExists(langFile))\n                langFile.clear();\n        }\n\n        // Now try at the i18n at episode root\n        if(langFile.empty())\n        {\n            langFile = p + fmt::format_ne(\"i18n/translation_{0}.json\", lang.c_str());\n            if(!Files::fileExists(langFile))\n                langFile.clear();\n        }\n    }\n\n    return langFile;\n}\n\n\nstatic std::string getTrFile(const std::string &subDir, const std::string &episodePath = std::string())\n{\n    std::string langFile = getTrFileLang(CurrentLanguage, CurrentLangDialect, subDir, episodePath);\n\n    if(langFile.empty()) // If no language detected or invalid language set, fallback to English\n        langFile = getTrFileLang(\"en\", \"gb\", subDir, episodePath);\n\n    return langFile;\n}\n\n\nTranslateEpisode::TranslateEpisode()\n{}\n\nvoid TranslateEpisode::loadLevelTranslation(const std::string& key)\n{\n    if(!FontManager::isInitied() || FontManager::isLegacy())\n    {\n        pLogWarning(\"TranslateEpisode: Translations aren't supported without new font engine loaded (the 'fonts' directory is required)\");\n        return;\n    }\n\n    std::string langFile = getTrFile(FileName);\n\n    if(langFile.empty())\n        return; // No translation found\n\n    TrLevelParser parser;\n    parser.m_wantedKey = key;\n\n    SDL_RWops *f_in = Files::open_file(langFile, \"rb\");\n    if(!f_in)\n        return;\n\n    if(nlohmann::json::sax_parse(f_in, &parser))\n        pLogDebug(\"JSON SaX returned TRUE\");\n    else\n        pLogDebug(\"JSON SaX returned FALSE\");\n\n    SDL_RWclose(f_in);\n}\n\nvoid TranslateEpisode::loadWorldTranslation(const std::string& key)\n{\n    if(!FontManager::isInitied() || FontManager::isLegacy())\n    {\n        pLogWarning(\"TranslateEpisode: Translations aren't supported without new font engine loaded (the 'fonts' directory is required)\");\n        return;\n    }\n\n    std::string langFile = getTrFile(FileNameWorld);\n\n    if(langFile.empty())\n        return; // No translation found\n\n    TrWorldParser parser;\n    parser.m_wantedKey = key;\n\n    SDL_RWops *f_in = Files::open_file(langFile, \"rb\");\n    if(!f_in)\n        return;\n\n    if(nlohmann::json::sax_parse(f_in, &parser))\n        pLogDebug(\"JSON SaX returned TRUE\");\n    else\n        pLogDebug(\"JSON SaX returned FALSE\");\n\n    SDL_RWclose(f_in);\n}\n\nvoid TranslateEpisode::loadLunaScript(const std::string& key)\n{\n    m_scriptLines.clear();\n    m_scriptTrId.clear();\n\n    if(!FontManager::isInitied() || FontManager::isLegacy())\n    {\n        pLogWarning(\"TranslateEpisode: Translations aren't supported without new font engine loaded (the 'fonts' directory is required)\");\n        return;\n    }\n\n    std::string langFile = getTrFile(FileName);\n\n    if(langFile.empty())\n        return; // No translation found\n\n    TrScriptParser parser;\n    parser.m_wantedKey = key;\n    parser.m_outputLines = &m_scriptLines;\n    parser.m_outputTrIdLines = &m_scriptTrId;\n\n    SDL_RWops *f_in = Files::open_file(langFile, \"rb\");\n    if(!f_in)\n        return;\n\n    if(nlohmann::json::sax_parse(f_in, &parser))\n        pLogDebug(\"JSON SaX returned TRUE\");\n    else\n        pLogDebug(\"JSON SaX returned FALSE\");\n\n    SDL_RWclose(f_in);\n}\n\nbool TranslateEpisode::tryTranslateTitle(const std::string& episodePath,\n                                         const std::string& worldFile,\n                                         std::string& output)\n{\n    if(!FontManager::isInitied() || FontManager::isLegacy())\n    {\n        pLogWarning(\"TranslateEpisode: Translations aren't supported without new font engine loaded (the 'fonts' directory is required)\");\n        return false;\n    }\n\n    std::string langFile = getTrFile(std::string(), episodePath);\n\n    if(langFile.empty())\n        return false; // No translation found\n\n    TrTitleParser parser;\n    parser.m_toWrite = &output;\n    parser.m_wantedWorld = worldFile;\n\n    SDL_RWops *f_in = Files::open_file(langFile, \"rb\");\n    if(!f_in)\n        return false;\n\n    if(nlohmann::json::sax_parse(f_in, &parser))\n        pLogDebug(\"JSON SaX returned TRUE\");\n    else\n        pLogDebug(\"JSON SaX returned FALSE\");\n\n    SDL_RWclose(f_in);\n\n    return true;\n}\n\nvoid TranslateEpisode::trScriptLine(std::string& data, int line)\n{\n    if(!FontManager::isInitied() || FontManager::isLegacy())\n    {\n        pLogWarning(\"TranslateEpisode: Translations aren't supported without new font engine loaded (the 'fonts' directory is required)\");\n        return;\n    }\n\n    if(m_scriptLines.empty() && m_scriptTrId.empty())\n        return;\n\n    if(!m_scriptTrId.empty())\n    {\n        auto f = m_scriptTrId.find(data);\n        if(f != m_scriptTrId.end())\n        {\n            data = f->second;\n            return; // translated by TrId\n        }\n    }\n\n    auto f = m_scriptLines.find(line);\n    if(f != m_scriptLines.end())\n        data = f->second; // translated by line number\n}\n"
  },
  {
    "path": "src/main/translate_episode.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef TRANSLATEEPISODE_H\n#define TRANSLATEEPISODE_H\n\n#include <unordered_map>\n#include <string>\n\n\nclass TranslateEpisode\n{\n    std::unordered_map<int, std::string> m_scriptLines;\n    std::unordered_map<std::string, std::string> m_scriptTrId;\n\npublic:\n    TranslateEpisode();\n\n    /*!\n     * \\brief Translate the level file data by key\n     * \\param lvl Level data\n     * \\param key Translation key\n     */\n    void loadLevelTranslation(const std::string &key);\n\n    /*!\n     * \\brief Translate the world map file data by key\n     * \\param wld World map data\n     * \\param key Translation key\n     */\n    void loadWorldTranslation(const std::string &key);\n\n    /*!\n     * \\brief Load the script translation data by key\n     * \\param key Translation key\n     */\n    void loadLunaScript(const std::string &key);\n\n    /*!\n     * \\brief Attempt to find the translated episode title\n     * \\param episodePath Path to the episode directory to prove\n     * \\return true if translation has been found\n     */\n    bool tryTranslateTitle(const std::string &episodePath, const std::string &worldFile, std::string &output);\n\n    /*!\n     * \\brief Translate script line by number\n     * \\param data Line data to translate\n     * \\param line line number where the line appears\n     */\n    void trScriptLine(std::string &data, int line);\n};\n\n#endif // TRANSLATEEPISODE_H\n"
  },
  {
    "path": "src/main/trees.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <memory>\n#include <sorting/pdqsort.h>\n\n#include \"layers.h\"\n#include \"config.h\"\n\n#include \"main/trees.h\"\n#include \"main/block_table.h\"\n#include \"main/block_table.hpp\"\n\n\nstd::vector<BaseRef_t> treeresult_vec[MAX_TREEQUERY_DEPTH] = {std::vector<BaseRef_t>(400), std::vector<BaseRef_t>(400), std::vector<BaseRef_t>(50), std::vector<BaseRef_t>(50)};\nptrdiff_t cur_treeresult_vec = 0;\n\nstatic std::unique_ptr<table_t<TileRef_t>> s_worldTilesTree;\nstatic std::unique_ptr<table_t<SceneRef_t>> s_worldSceneTree;\nstatic std::unique_ptr<table_t<WorldPathRef_t>> s_worldPathTree;\nstatic std::unique_ptr<table_t<WorldLevelRef_t>> s_worldLevelTree;\nstatic std::unique_ptr<table_t<WorldMusicRef_t>> s_worldMusicTree;\n\ntemplate<class Q>\nvoid clearTree(Q &tree)\n{\n    if(tree.get())\n    {\n        tree->clear();\n        tree.reset();\n    }\n}\n\nvoid treeWorldCleanAll()\n{\n    clearTree(s_worldTilesTree);\n    clearTree(s_worldSceneTree);\n    clearTree(s_worldPathTree);\n    clearTree(s_worldLevelTree);\n    clearTree(s_worldMusicTree);\n}\n\nvoid treeLevelCleanAll()\n{\n    treeLevelCleanBlockLayers();\n    treeLevelCleanBackgroundLayers();\n    treeLevelCleanWaterLayers();\n    treeTempBlockFullClear();\n    treeNPCClear();\n}\n\ntemplate<class ItemRef_t, class Arr>\nvoid treeInsert(Arr &p, ItemRef_t obj)\n{\n    if(!p.get())\n        p.reset(new table_t<ItemRef_t>());\n    p->insert(obj);\n}\n\ntemplate<class ItemRef_t, class Arr>\nvoid treeUpdate(Arr &p, ItemRef_t obj)\n{\n    if(p.get())\n        p->update(obj);\n}\n\ntemplate<class ItemRef_t, class Arr>\nvoid treeRemove(Arr &p, ItemRef_t obj)\n{\n    if(p.get())\n        p->erase(obj);\n}\n\ntemplate<class ItemRef_t>\nTreeResult_Sentinel<ItemRef_t> treeWorldQuery(std::unique_ptr<table_t<ItemRef_t>> &p,\n    int Left, int Top, int Right, int Bottom, int sort_mode)\n{\n    TreeResult_Sentinel<ItemRef_t> result;\n\n    if(!p.get())\n        return result;\n\n    p->query(*result.i_vec, rect_external(s_floor_div_64(Left),\n                                  s_floor_div_64(Right + 63),\n                                  s_floor_div_64(Top),\n                                  s_floor_div_64(Bottom + 63)));\n\n    if(sort_mode == SORTMODE_ID || sort_mode == SORTMODE_Z)\n        trees_sort_by_index(*result.i_vec);\n    else if(sort_mode == SORTMODE_LOC)\n    {\n        pdqsort(result.i_vec->begin(), result.i_vec->end(),\n            [](BaseRef_t a, BaseRef_t b) {\n                return (((ItemRef_t)a)->Location.X < ((ItemRef_t)b)->Location.X\n                    || (((ItemRef_t)a)->Location.X == ((ItemRef_t)b)->Location.X\n                        && ((ItemRef_t)a)->Location.Y < ((ItemRef_t)b)->Location.Y));\n            });\n    }\n\n    return result;\n}\n\n\n/* ================= Terrain Tile ================= */\n\nvoid treeWorldTileAdd(TileRef_t obj)\n{\n    treeInsert(s_worldTilesTree, obj);\n}\n\nvoid treeWorldTileUpdate(TileRef_t obj)\n{\n    treeUpdate(s_worldTilesTree, obj);\n}\n\nvoid treeWorldTileRemove(TileRef_t obj)\n{\n    treeRemove(s_worldTilesTree, obj);\n}\n\nTreeResult_Sentinel<TileRef_t> treeWorldTileQuery(int Left, int Top, int Right, int Bottom, int sort_mode, int margin)\n{\n    return treeWorldQuery(s_worldTilesTree,\n                   Left - margin,\n                   Top - margin,\n                   Right + margin,\n                   Bottom + margin,\n                   sort_mode);\n}\n\nTreeResult_Sentinel<TileRef_t> treeWorldTileQuery(const TinyLocation_t &loc, int sort_mode, int margin)\n{\n    return treeWorldQuery(s_worldTilesTree,\n                   loc.X - margin,\n                   loc.Y - margin,\n                   loc.X + loc.Width + margin,\n                   loc.Y + loc.Height + margin,\n                   sort_mode);\n}\n\n\n/* ================= Scenery ================= */\n\nvoid treeWorldSceneAdd(SceneRef_t obj)\n{\n    treeInsert(s_worldSceneTree, obj);\n}\n\nvoid treeWorldSceneUpdate(SceneRef_t obj)\n{\n    treeUpdate(s_worldSceneTree, obj);\n}\n\nvoid treeWorldSceneRemove(SceneRef_t obj)\n{\n    treeRemove(s_worldSceneTree, obj);\n}\n\nTreeResult_Sentinel<SceneRef_t> treeWorldSceneQuery(int Left, int Top, int Right, int Bottom, int sort_mode, int margin)\n{\n    return treeWorldQuery(s_worldSceneTree,\n                   Left - margin,\n                   Top - margin,\n                   Right + margin,\n                   Bottom + margin,\n                   sort_mode);\n}\n\nTreeResult_Sentinel<SceneRef_t> treeWorldSceneQuery(const TinyLocation_t &loc, int sort_mode, int margin)\n{\n    return treeWorldQuery(s_worldSceneTree,\n                   loc.X - margin,\n                   loc.Y - margin,\n                   loc.X + loc.Width + margin,\n                   loc.Y + loc.Height + margin,\n                   sort_mode);\n}\n\n\n/* ================= Paths ================= */\n\nvoid treeWorldPathAdd(WorldPathRef_t obj)\n{\n    treeInsert(s_worldPathTree, obj);\n}\n\nvoid treeWorldPathUpdate(WorldPathRef_t obj)\n{\n    treeUpdate(s_worldPathTree, obj);\n}\n\nvoid treeWorldPathRemove(WorldPathRef_t obj)\n{\n    treeRemove(s_worldPathTree, obj);\n}\n\nTreeResult_Sentinel<WorldPathRef_t> treeWorldPathQuery(int Left, int Top, int Right, int Bottom,\n                        int sort_mode, int margin)\n{\n    return treeWorldQuery(s_worldPathTree,\n                   Left - margin,\n                   Top - margin,\n                   Right + margin,\n                   Bottom + margin,\n                   sort_mode);\n}\n\nTreeResult_Sentinel<WorldPathRef_t> treeWorldPathQuery(const TinyLocation_t &loc, int sort_mode, int margin)\n{\n    return treeWorldQuery(s_worldPathTree,\n                   loc.X - margin,\n                   loc.Y - margin,\n                   loc.X + loc.Width + margin,\n                   loc.Y + loc.Height + margin,\n                   sort_mode);\n}\n\n/* ================= Levels ================= */\n\nvoid treeWorldLevelAdd(WorldLevelRef_t obj)\n{\n    treeInsert(s_worldLevelTree, obj);\n}\n\nvoid treeWorldLevelUpdate(WorldLevelRef_t obj)\n{\n    treeUpdate(s_worldLevelTree, obj);\n}\n\nvoid treeWorldLevelRemove(WorldLevelRef_t obj)\n{\n    treeRemove(s_worldLevelTree, obj);\n}\n\nTreeResult_Sentinel<WorldLevelRef_t> treeWorldLevelQuery(int Left, int Top, int Right, int Bottom,\n                         int sort_mode, int margin)\n{\n    return treeWorldQuery(s_worldLevelTree,\n                   Left - margin,\n                   Top - margin,\n                   Right + margin,\n                   Bottom + margin,\n                   sort_mode);\n}\n\nTreeResult_Sentinel<WorldLevelRef_t> treeWorldLevelQuery(const TinyLocation_t &loc, int sort_mode, int margin)\n{\n    return treeWorldQuery(s_worldLevelTree,\n                   loc.X - margin,\n                   loc.Y - margin,\n                   loc.X + loc.Width + margin,\n                   loc.Y + loc.Height + margin,\n                   sort_mode);\n}\n\n\n/* ================= Music ================= */\n\nvoid treeWorldMusicAdd(WorldMusicRef_t obj)\n{\n    treeInsert(s_worldMusicTree, obj);\n}\n\nvoid treeWorldMusicUpdate(WorldMusicRef_t obj)\n{\n    treeUpdate(s_worldMusicTree, obj);\n}\n\nvoid treeWorldMusicRemove(WorldMusicRef_t obj)\n{\n    treeRemove(s_worldMusicTree, obj);\n}\n\nTreeResult_Sentinel<WorldMusicRef_t> treeWorldMusicQuery(int Left, int Top, int Right, int Bottom,\n                         int sort_mode,\n                         int margin)\n{\n    return treeWorldQuery(s_worldMusicTree,\n                   Left - margin,\n                   Top - margin,\n                   Right + margin,\n                   Bottom + margin,\n                   sort_mode);\n}\n\nTreeResult_Sentinel<WorldMusicRef_t> treeWorldMusicQuery(const TinyLocation_t &loc,\n                         int sort_mode,\n                         int margin)\n{\n    return treeWorldQuery(s_worldMusicTree,\n                   loc.X - margin,\n                   loc.Y - margin,\n                   loc.X + loc.Width + margin,\n                   loc.Y + loc.Height + margin,\n                   sort_mode);\n}\n\n/* ================= Tile block search ================= */\n// removed in favor of block quadtree\n\n#if 0\nvoid blockTileGet(const Location_t &loc, int64_t &fBlock, int64_t &lBlock)\n{\n    int f = vb6Round(loc.X / 32) - 1;\n    int l = vb6Round((loc.X + loc.Width) / 32.0) + 1;\n    fBlock = FirstBlock[f < -FLBlocks ? -FLBlocks : f];\n    lBlock = LastBlock[l > FLBlocks ? FLBlocks : l];\n}\n\nvoid blockTileGet(double x, double w, int64_t &fBlock, int64_t &lBlock)\n{\n    int f = vb6Round(x / 32) - 1;\n    int l = vb6Round((x + w) / 32.0) + 1;\n    fBlock = FirstBlock[f < -FLBlocks ? -FLBlocks : f];\n    lBlock = LastBlock[l > FLBlocks ? FLBlocks : l];\n}\n#endif\n\ntemplate<>\nvoid UpdatableQuery<BlockRef_t>::update(const Location_t& loc, const UpdatableQuery<BlockRef_t>::it& current_step)\n{\n    SDL_assert(sort_mode != SORTMODE_NONE); // impossible to update unsorted query\n\n    bool use_fl_block = (block_query_mode == QUERY_FLBLOCK && BlocksSorted);\n\n    // check if this query is a subset of the previous query\n    if((loc.Y >= bounds.Y && loc.Y + loc.Height <= bounds.Y + bounds.Height)\n        && (use_fl_block || (loc.X >= bounds.X && loc.X + loc.Width <= bounds.X + bounds.Width)))\n    {\n        return;\n    }\n\n    // update bounds, and expand by 6px on each side\n    bounds.Y = loc.Y - 6;\n    bounds.Height = loc.Height + 6;\n\n    // in compat mode, use a special formula for the FLBlock bounds\n    if(use_fl_block && g_config.emulate_classic_block_order)\n    {\n        bounds.X = num_t::vb6round(loc.X / 32 - 1) * 32;\n        bounds.Width = num_t::vb6round((loc.X + loc.Width) / 32 + 1) * 32 - bounds.X;\n    }\n    else\n    {\n        bounds.X = loc.X - 6;\n        bounds.Width = loc.Width + 6;\n    }\n\n    // set up comparison functions\n    int sort_mode_use = sort_mode;\n\n    if(sort_mode_use == SORTMODE_COMPAT)\n    {\n        if(block_query_mode == QUERY_FLBLOCK && g_config.emulate_classic_block_order)\n            sort_mode_use = SORTMODE_ID;\n        else\n            sort_mode_use = SORTMODE_LOC;\n    }\n\n    auto compare_func = (sort_mode_use == SORTMODE_Z) ? Comparisons::Z<BlockRef_t>\n        : (sort_mode_use == SORTMODE_LOC) ? Comparisons::Loc<BlockRef_t>\n        : Comparisons::ID<BlockRef_t>;\n\n    // helps track range to sort following update\n    bool current_step_valid = (current_step != end());\n    ptrdiff_t start_sort = current_step_valid ? current_step.index + 1 : end().index;\n    BlockRef_t lower_bound = current_step_valid ? *current_step : BlockRef_t();\n\n    size_t start_new = sent.i_vec->size();\n\n    // add the normal blocks\n    if(block_query_mode != QUERY_TEMPBLOCK)\n        treeFLBlockQuery(*sent.i_vec, bounds, SORTMODE_NONE);\n\n    // add the temp blocks\n    if(block_query_mode != QUERY_FLBLOCK)\n        treeTempBlockQuery(*sent.i_vec, bounds, SORTMODE_NONE);\n\n    // filter out the invalid blocks\n    if(current_step_valid)\n    {\n        for(size_t i = start_new; i < sent.i_vec->size();)\n        {\n            // need lower_bound to be strictly before the new item\n            if(compare_func(lower_bound, (*sent.i_vec)[i]))\n            {\n                i++;\n                continue;\n            }\n\n            // must remove the item!\n            (*sent.i_vec)[i] = (*sent.i_vec)[sent.i_vec->size() - 1];\n            sent.i_vec->resize(sent.i_vec->size() - 1);\n        }\n    }\n\n    // sort all of the blocks after the current step (manually separating cases to maximize chances of successful inlining)\n    if(sort_mode_use == SORTMODE_Z)\n        pdqsort(sent.i_vec->begin() + start_sort, sent.i_vec->end(), Comparisons::Z<BlockRef_t>);\n    else if(sort_mode_use == SORTMODE_LOC)\n        pdqsort(sent.i_vec->begin() + start_sort, sent.i_vec->end(), Comparisons::Loc<BlockRef_t>);\n    else\n        trees_sort_by_index(*sent.i_vec);\n}\n"
  },
  {
    "path": "src/main/trees.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef TREES_HHHH\n#define TREES_HHHH\n\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"globals.h\"\n\n#define MAX_TREEQUERY_DEPTH 4\nextern std::vector<BaseRef_t> treeresult_vec[MAX_TREEQUERY_DEPTH];\nextern ptrdiff_t cur_treeresult_vec;\n\nenum SortMode\n{\n    SORTMODE_NONE = 0,\n    SORTMODE_ID = 1,\n    SORTMODE_LOC = 2,\n    SORTMODE_Z = 3,\n    SORTMODE_COMPAT = 4, // sort according to ID (location at last sort) in compat mode, and according to current location normally\n};\n\nenum QueryMode\n{\n    QUERY_NORMAL = 0,\n    QUERY_FLBLOCK = 1,\n    QUERY_TEMPBLOCK = 2,\n};\n\nnamespace Comparisons\n{\n    // sort by location\n    template<class ItemRef_t>\n    inline bool Loc(BaseRef_t a, BaseRef_t b)\n    {\n        return (((ItemRef_t)a)->Location.X <= ((ItemRef_t)b)->Location.X\n            && (((ItemRef_t)a)->Location.X < ((ItemRef_t)b)->Location.X\n                || ((ItemRef_t)a)->Location.Y < ((ItemRef_t)b)->Location.Y));\n    }\n\n    // sort by index\n    template<class ItemRef_t>\n    inline bool ID(BaseRef_t a, BaseRef_t b)\n    {\n        return a < b;\n    }\n\n    // sort by index when unimplemented\n    template<class ItemRef_t>\n    inline bool Z(BaseRef_t a, BaseRef_t b)\n    {\n        return a < b;\n    }\n\n    // sort blocks by Y, then index\n    template<>\n    inline bool Z<BlockRef_t>(BaseRef_t a, BaseRef_t b)\n    {\n        return (((BlockRef_t)a)->Location.Y < ((BlockRef_t)b)->Location.Y) ||\n            (((BlockRef_t)a)->Location.Y == ((BlockRef_t)b)->Location.Y && a < b);\n    }\n\n    // sort BGOs by SortPriority\n    template<>\n    inline bool Z<BackgroundRef_t>(BaseRef_t a, BaseRef_t b)\n    {\n        return (((BackgroundRef_t)a)->SortPriority < ((BackgroundRef_t)b)->SortPriority)\n            || (((BackgroundRef_t)a)->SortPriority == ((BackgroundRef_t)b)->SortPriority\n                && ((BackgroundRef_t)a)->Location.X < ((BackgroundRef_t)b)->Location.X);\n    }\n}\n\ntemplate<class ItemRef_t>\nclass TreeResult_Sentinel\n{\npublic:\n    struct it\n    {\n        using iterator_category = std::input_iterator_tag;\n        using difference_type   = std::ptrdiff_t;\n        using value_type        = typename ItemRef_t::value_type;\n        using pointer           = ItemRef_t*;\n        using reference         = ItemRef_t;\n\n        reference operator*() const { return (reference)*it_internal; }\n        pointer operator->() { return &((reference)*it_internal); }\n\n        // Prefix increment\n        it& operator++() { it_internal++; return *this; }\n\n        // Postfix increment\n        it operator++(int) { it tmp = *this; ++(*this); return tmp; }\n\n        // Prefix decrement\n        it& operator--() { it_internal--; return *this; }\n\n        // Postfix decrement\n        it operator--(int) { it tmp = *this; --(*this); return tmp; }\n\n        friend bool operator== (const it& a, const it& b) { return a.it_internal == b.it_internal; }\n        friend bool operator!= (const it& a, const it& b) { return a.it_internal != b.it_internal; }\n        friend bool operator<= (const it& a, const it& b) { return a.it_internal <= b.it_internal; }\n        friend bool operator>= (const it& a, const it& b) { return a.it_internal >= b.it_internal; }\n        friend bool operator< (const it& a, const it& b) { return a.it_internal < b.it_internal; }\n        friend bool operator> (const it& a, const it& b) { return a.it_internal > b.it_internal; }\n\n        std::vector<BaseRef_t>::iterator it_internal;\n    };\n\n    std::vector<BaseRef_t>* i_vec = nullptr;\n\n    TreeResult_Sentinel()\n    {\n        SDL_assert(cur_treeresult_vec >= 0); // invalid state\n        SDL_assert_release(cur_treeresult_vec < MAX_TREEQUERY_DEPTH); // insufficient sentinels: move recursive calls out of sentinel scope\n        i_vec = &treeresult_vec[cur_treeresult_vec];\n        i_vec->clear();\n        cur_treeresult_vec ++;\n    }\n\n    TreeResult_Sentinel(const TreeResult_Sentinel& other) = delete;\n    const TreeResult_Sentinel& operator=(const TreeResult_Sentinel& other) = delete;\n\n    TreeResult_Sentinel(TreeResult_Sentinel&& other)\n    {\n        i_vec = other.i_vec;\n        other.i_vec = nullptr;\n    }\n\n    const TreeResult_Sentinel& operator=(const TreeResult_Sentinel&& other) = delete;\n\n    it begin() const\n    {\n        SDL_assert(i_vec); // invalid use of discarded sentinel\n        it ret;\n        ret.it_internal = i_vec->begin();\n        return ret;\n    }\n\n    it end() const\n    {\n        SDL_assert(i_vec); // invalid use of discarded sentinel\n        it ret;\n        ret.it_internal = i_vec->end();\n        return ret;\n    }\n\n    ~TreeResult_Sentinel()\n    {\n        if(!i_vec)\n            return;\n        cur_treeresult_vec --;\n        SDL_assert(cur_treeresult_vec == i_vec - &treeresult_vec[0]); // scopes have been switched\n    }\n};\n\nextern void treeWorldCleanAll();\nextern void treeLevelCleanAll();\n\nextern void treeWorldTileAdd(TileRef_t obj);\nextern void treeWorldTileUpdate(TileRef_t obj);\nextern void treeWorldTileRemove(TileRef_t obj);\nextern TreeResult_Sentinel<TileRef_t> treeWorldTileQuery(int Left, int Top, int Right, int Bottom,\n                               int sort_mode, int margin = 0);\nextern TreeResult_Sentinel<TileRef_t> treeWorldTileQuery(const TinyLocation_t &loc, int sort_mode, int margin = 0);\n\n\nextern void treeWorldSceneAdd(SceneRef_t obj);\nextern void treeWorldSceneUpdate(SceneRef_t obj);\nextern void treeWorldSceneRemove(SceneRef_t obj);\nextern TreeResult_Sentinel<SceneRef_t> treeWorldSceneQuery(int Left, int Top, int Right, int Bottom,\n                               int sort_mode, int margin = 16);\nextern TreeResult_Sentinel<SceneRef_t> treeWorldSceneQuery(const TinyLocation_t &loc, int sort_mode, int margin = 16);\n\n\nextern void treeWorldPathAdd(WorldPathRef_t obj);\nextern void treeWorldPathUpdate(WorldPathRef_t obj);\nextern void treeWorldPathRemove(WorldPathRef_t obj);\nextern TreeResult_Sentinel<WorldPathRef_t> treeWorldPathQuery(int Left, int Top, int Right, int Bottom,\n                               int sort_mode, int margin = 16);\nextern TreeResult_Sentinel<WorldPathRef_t> treeWorldPathQuery(const TinyLocation_t &loc, int sort_mode, int margin = 16);\n\n\nextern void treeWorldLevelAdd(WorldLevelRef_t obj);\nextern void treeWorldLevelUpdate(WorldLevelRef_t obj);\nextern void treeWorldLevelRemove(WorldLevelRef_t obj);\nextern TreeResult_Sentinel<WorldLevelRef_t> treeWorldLevelQuery(int Left, int Top, int Right, int Bottom,\n                               int sort_mode, int margin = 16);\nextern TreeResult_Sentinel<WorldLevelRef_t> treeWorldLevelQuery(const TinyLocation_t &loc, int sort_mode, int margin = 16);\n\nextern void treeWorldMusicAdd(WorldMusicRef_t obj);\nextern void treeWorldMusicUpdate(WorldMusicRef_t obj);\nextern void treeWorldMusicRemove(WorldMusicRef_t obj);\nextern TreeResult_Sentinel<WorldMusicRef_t> treeWorldMusicQuery(int Left, int Top, int Right, int Bottom,\n                               int sort_mode, int margin = 16);\nextern TreeResult_Sentinel<WorldMusicRef_t> treeWorldMusicQuery(const TinyLocation_t &loc, int sort_mode, int margin = 16);\n\n\n// declared in block_table.cpp\n\nextern void treeLevelCleanBlockLayers();\nextern void treeBlockAddLayer(int layer, BlockRef_t obj);\nextern void treeBlockRemoveLayer(int layer, BlockRef_t obj);\nextern void treeBlockUpdateLayer(int layer, BlockRef_t obj);\n\n/**\n * \\brief queries the block table, and excludes tempBlocks from the results if called while tempBlocks are active\n *\n * use this to replace VB6 iterations over block columns (that previously used FLBlock), or code that specifically wants to exclude tempBlocks\n **/\nextern TreeResult_Sentinel<BlockRef_t> treeFLBlockQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                               int sort_mode, num_t margin = 2);\nextern void treeFLBlockQuery(std::vector<BaseRef_t>& out, const Location_t &loc, int sort_mode);\nextern TreeResult_Sentinel<BlockRef_t> treeFLBlockQuery(const Location_t &loc, int sort_mode);\n\n/**\n * \\brief queries the block table, and includes tempBlocks in the results if called while tempBlocks are active\n *\n * use this in new code, and to replace VB6 iterations over the full block array\n **/\nextern TreeResult_Sentinel<BlockRef_t> treeBlockQuery(const Location_t &loc, int sort_mode);\n\nextern void treeTempBlockFullClear();\nextern void treeTempBlockEnable();\nextern void treeTempBlockClear();\nextern void treeTempBlockAdd(BlockRef_t obj);\nextern void treeTempBlockUpdate(BlockRef_t obj);\nextern void treeTempBlockRemove(BlockRef_t obj);\nextern TreeResult_Sentinel<BlockRef_t> treeTempBlockQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                               int sort_mode, num_t margin = 2);\nextern void treeTempBlockQuery(std::vector<BaseRef_t>& out, const Location_t &loc, int sort_mode);\nextern TreeResult_Sentinel<BlockRef_t> treeTempBlockQuery(const Location_t &loc, int sort_mode);\n\nextern void treeLevelCleanBackgroundLayers();\nextern void treeBackgroundAddLayer(int layer, BackgroundRef_t obj);\nextern void treeBackgroundRemoveLayer(int layer, BackgroundRef_t obj);\nextern void treeBackgroundUpdateLayer(int layer, BackgroundRef_t obj);\nextern TreeResult_Sentinel<BackgroundRef_t> treeBackgroundQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                               int sort_mode, num_t margin = 2);\nextern void treeBackgroundQuery(std::vector<BaseRef_t>& out, const Location_t &loc, int sort_mode);\nextern TreeResult_Sentinel<BackgroundRef_t> treeBackgroundQuery(const Location_t &loc, int sort_mode);\n\nextern void treeNPCClear();\nextern void treeNPCAdd(NPCRef_t obj);\nextern void treeNPCRemove(NPCRef_t obj);\nextern bool treeNPCUpdate(NPCRef_t obj);\nextern void treeNPCSplitTempBlock(NPCRef_t obj);\nextern void treeNPCUpdateTempBlock(NPCRef_t obj);\nextern TreeResult_Sentinel<NPCRef_t> treeNPCQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                               int sort_mode, num_t margin = 2);\nextern void treeNPCQuery(std::vector<BaseRef_t>& out, const Location_t &loc, int sort_mode);\nextern TreeResult_Sentinel<NPCRef_t> treeNPCQuery(const Location_t &loc, int sort_mode);\n\nextern void treeLevelCleanWaterLayers();\nextern void treeWaterAddLayer(int layer, WaterRef_t obj);\nextern void treeWaterRemoveLayer(int layer, WaterRef_t obj);\nextern void treeWaterUpdateLayer(int layer, WaterRef_t obj);\nextern TreeResult_Sentinel<WaterRef_t> treeWaterQuery(num_t Left, num_t Top, num_t Right, num_t Bottom,\n                               int sort_mode, num_t margin = 2);\nextern TreeResult_Sentinel<WaterRef_t> treeWaterQuery(const Location_t &loc, int sort_mode);\n\n// removed in favor of block quadtree\n\n// extern void blockTileGet(const Location_t &loc, int64_t &fBlock, int64_t &lBlock);\n// extern void blockTileGet(double x, double w, int64_t &fBlock, int64_t &lBlock);\n\n// special class used to handle situation where query may need to be significantly updated after location changes\ntemplate<class ItemRef_t>\nclass UpdatableQuery\n{\n    TreeResult_Sentinel<ItemRef_t> sent;\n    Location_t bounds; // bounds that were used for the previous query, including margin\n    const SortMode sort_mode;\n    const QueryMode block_query_mode;\n\npublic:\n    struct it\n    {\n        using iterator_category = std::input_iterator_tag;\n        using difference_type   = std::ptrdiff_t;\n        using value_type        = typename ItemRef_t::value_type;\n        using pointer           = ItemRef_t*;\n        using reference         = ItemRef_t;\n\n        it(const TreeResult_Sentinel<ItemRef_t>& sent, ptrdiff_t index)\n            : parent(*sent.i_vec), index(index) {}\n\n        reference operator*() const { return (reference)(parent[index]); }\n        pointer operator->() { return &*this; }\n\n        // Prefix increment\n        it& operator++() { index++; return *this; }\n\n        // Postfix increment\n        it operator++(int) { it tmp = *this; ++(*this); return tmp; }\n\n        // Prefix decrement\n        it& operator--() { index--; return *this; }\n\n        // Postfix decrement\n        it operator--(int) { it tmp = *this; --(*this); return tmp; }\n\n        friend bool operator== (const it& a, const it& b) { return a.index == b.index; }\n        friend bool operator!= (const it& a, const it& b) { return a.index != b.index; }\n        friend bool operator<= (const it& a, const it& b) { return a.index <= b.index; }\n        friend bool operator>= (const it& a, const it& b) { return a.index >= b.index; }\n        friend bool operator< (const it& a, const it& b) { return a.index < b.index; }\n        friend bool operator> (const it& a, const it& b) { return a.index > b.index; }\n\n        const std::vector<BaseRef_t>& parent;\n        ptrdiff_t index;\n    };\n\n    UpdatableQuery(const Location_t& target, SortMode sort_mode, QueryMode block_query_mode = QUERY_NORMAL)\n        : sort_mode(sort_mode), block_query_mode(block_query_mode)\n    {\n        update(target, end());\n    }\n\n    it begin() const\n    {\n        SDL_assert(sent.i_vec); // invalid use of discarded sentinel\n        it ret(sent, 0);\n        return ret;\n    }\n\n    it end() const\n    {\n        SDL_assert(sent.i_vec); // invalid use of discarded sentinel\n        it ret(sent, sent.i_vec->size());\n        return ret;\n    }\n\n    void update(const Location_t& loc, const it& current_step);\n};\n\n#endif // TREES_HHHH\n"
  },
  {
    "path": "src/main/world_file.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_timer.h\"\n\n#include <json/json_rwops_input.hpp>\n#include <json/json.hpp>\n#include <fmt_format_ne.h>\n\n#include \"core/render.h\"\n\n#include \"../globals.h\"\n#include \"../frame_timer.h\"\n#include \"../game_main.h\"\n#include \"../load_gfx.h\"\n#include \"../sound.h\"\n#include \"../custom.h\"\n#include \"config.h\"\n#include \"../main/trees.h\"\n#include \"level_file.h\"\n#include \"world_file.h\"\n#include \"saved_layers.h\"\n#include \"main/game_info.h\"\n#include \"main/level_save_info.h\"\n#include \"main/screen_progress.h\"\n#include \"main/game_strings.h\"\n#include \"translate_episode.h\"\n#include \"fontman/font_manager.h\"\n#include \"../version.h\"\n\n#include <Utils/strings.h>\n#include <Utils/files.h>\n#include <Utils/dir_list_ci.h>\n#include <Archives/archives.h>\n#include <Logger/logger.h>\n#include <PGE_File_Formats/file_formats.h>\n\n#include \"global_dirs.h\"\n\n#include \"editor/editor_custom.h\"\n#include \"editor/editor_strings.h\"\n\nstruct WorldLoad\n{\n    int FileRelease = 64;\n};\n\n#ifdef PGEFL_CALLBACK_API\nWorldLoadCallbacks OpenWorld_SetupCallbacks(WorldLoad& load);\nusing callback_error = PGE_FileFormats_misc::callback_error;\n#else\nbool OpenWorld_Unpack(WorldLoad& load, WorldData& wld);\nusing callback_error = std::runtime_error;\n#endif\n\nbool OpenWorld_Post(const WorldLoad& load);\n\n// defined in level_file.cpp\n[[ noreturn ]] void priv_FeatureLevelError(const std::string& errMsg, int reqFeatureLevel, int curFeatureLevel);\n\nbool OpenWorld(std::string FilePath)\n{\n    // USE PGE-FL here\n    // std::string newInput = \"\";\n    WorldLoad load;\n    // long long zCounter = 0;\n\n    ClearWorld();\n\n//    for(A = FilePath.length(); A >= 1; A--)\n//    {\n//        if(FilePath.substr(A - 1, 1) == \"/\" || FilePath.substr(A - 1, 1) == \"\\\\\")\n//            break;\n//    }\n\n    // set the file path and load custom configuration\n    FileNamePath = Files::dirname(FilePath) + \"/\";\n    g_dirEpisode.setCurDir(FileNamePath);\n\n    FileName = g_dirEpisode.resolveDirCase(Files::basenameNoSuffix(FilePath));\n    g_dirCustom.setCurDir(FileNamePath + FileName);\n\n    FileNameFull = Files::basename(FilePath);\n    FullFileName = FilePath;\n\n    // Preserve these values for quick restoring when going to the world map\n    FileNameFullWorld = FileNameFull;\n    FileNameWorld = FileName;\n    FileNamePathWorld = FileNamePath;\n    FileFormatWorld = FileFormat;\n\n    FontManager::loadCustomFonts();\n\n    LoadCustomConfig();\n    FindCustomPlayers();\n    LoadCustomGFX(true);\n    if(!LoadDefaultSavedLayers())\n    {\n        MessageText = \"savedlayers.ini invalid\";\n        return false;\n    }\n\n    // bool compatModern = (g_config.compatibility_mode == Config_t::COMPAT_OFF);\n\n    numTiles = 0;\n    numScenes = 0;\n    numWorldLevels = 0;\n    numWorldPaths = 0;\n    numWorldMusic = 0;\n    numWorldAreas = 0;\n\n    // FileFormats::OpenWorldFile(FilePath, wld);\n    PGE_FileFormats_misc::RWopsTextInput in(Files::open_file(FilePath, \"r\"), FilePath);\n\n    if(in.eof())\n    {\n        pLogWarning(\"File [%s] missing!\", FilePath.c_str());\n        MessageText = \"File does not exist\";\n        return false;\n    }\n\n#ifdef PGEFL_CALLBACK_API\n    WorldLoadCallbacks callbacks = OpenWorld_SetupCallbacks(load);\n    if(!FileFormats::OpenWorldFileT(in, callbacks))\n    {\n        pLogDebug(\"Failed to load [%s]\", FilePath.c_str());\n        return false;\n    }\n#else // #ifdef PGEFL_CALLBACK_API\n    {\n        WorldData wld;\n\n        if(!FileFormats::OpenWorldFileT(in, wld))\n        {\n            pLogWarning(\"Error of world \\\"%s\\\" file loading: %s (line %ld).\",\n                        FilePath.c_str(),\n                        wld.meta.ERROR_info.c_str(),\n                        wld.meta.ERROR_linenum);\n            MessageText = std::move(wld.meta.ERROR_info);\n            return false;\n        }\n\n        if(!OpenWorld_Unpack(load, wld))\n            return false;\n    }\n#endif // #ifdef PGEFL_CALLBACK_API\n\n    return OpenWorld_Post(load);\n}\n\n#ifdef PGEFL_CALLBACK_API\nvoid OpenWorld_Error(void*, FileFormatsError& e)\n{\n    pLogWarning(\"Error of world file loading: %s (line %ld).\",\n                e.ERROR_info.c_str(),\n                e.ERROR_linenum);\n\n    MessageText = std::move(e.ERROR_info);\n}\n#endif\n\n#ifdef PGEFL_CALLBACK_API\nbool OpenWorld_Head(void* userdata, WorldHead& wld)\n#else\nbool OpenWorld_Head(void* userdata, WorldData& wld)\n#endif\n{\n    WorldLoad& load = *static_cast<WorldLoad*>(userdata);\n\n    constexpr unsigned int engineFeatureLevel = V_FEATURE_LEVEL;\n\n#ifdef PGEFL_CALLBACK_API\n    FileFormat = wld.RecentFormat;\n    unsigned int reqFeatureLevel = wld.engineFeatureLevel;\n    if(wld.RecentFormat == LevelData::SMBX64)\n        load.FileRelease = int(wld.RecentFormatVersion);\n#else\n    FileFormat = wld.meta.RecentFormat;\n    unsigned int reqFeatureLevel = wld.meta.engineFeatureLevel;\n    if(wld.meta.RecentFormat == LevelData::SMBX64)\n        load.FileRelease = int(wld.meta.RecentFormatVersion);\n#endif\n\n    if(reqFeatureLevel > engineFeatureLevel)\n        priv_FeatureLevelError(g_gameStrings.errorTooOldEngine, reqFeatureLevel, engineFeatureLevel);\n    else if(reqFeatureLevel > g_gameInfo.contentFeatureLevel)\n        priv_FeatureLevelError(g_gameStrings.errorTooOldGameAssets, reqFeatureLevel, g_gameInfo.contentFeatureLevel);\n\n    WorldName = wld.EpisodeTitle;\n\n    // cancel block if cheat is active\n    if(g_forceCharacter && !LevelEditor && !WorldEditor)\n    {\n        for(int A = 1; A <= numCharacters; A++)\n            blockCharacter[A] = false;\n    }\n    // load character block\n    else\n    {\n        for(size_t A = 1; A <= numCharacters && A - 1 < wld.nocharacter.size(); A++)\n            blockCharacter[A] = wld.nocharacter[A - 1];\n    }\n\n    StartLevel = wld.IntroLevel_file;\n    addMissingLvlSuffix(StartLevel);\n    StartLevel = g_dirEpisode.resolveFileCase(StartLevel);\n\n    NoMap = wld.HubStyledWorld;\n    RestartLevel = wld.restartlevel;\n\n    // new:\n    WorldStarsShowPolicy = wld.starsShowPolicy;\n\n    MaxWorldStars = int(wld.stars);\n\n    // world extra settings:\n    WldxCustomParams.clear();\n    SubHubLevels.clear();\n\n    Strings::dealloc(g_recentWorldIntro);\n    Strings::dealloc(g_recentWorldOutro);\n\n    if(!wld.custom_params.empty())\n    {\n        WldxCustomParams = wld.custom_params;\n\n        try\n        {\n            const nlohmann::json world_data = nlohmann::json::parse(wld.custom_params);\n\n            if(world_data.contains(\"sub_hubs_list\"))\n            {\n                for(const nlohmann::json& sub_hub : world_data[\"sub_hubs_list\"])\n                    SubHubLevels.push_back(sub_hub.get<std::string>());\n            }\n\n            if(world_data.contains(\"custom_intro\"))\n            {\n                auto p = world_data[\"custom_intro\"];\n                if(p.contains(\"intro_path\"))\n                {\n                    std::string pp = p.value(\"intro_path\", std::string());\n                    if(!pp.empty())\n                    {\n                        if(FileNamePath[0] == ':' && FileNamePath[1] == 'e')\n                            g_recentWorldIntro = \"@\" + Archives::episode_archive_path() + \":/\" + pp;\n                        else\n                            g_recentWorldIntro = FileNamePath + pp;\n                    }\n                }\n            }\n\n            if(world_data.contains(\"custom_outro\"))\n            {\n                auto p = world_data[\"custom_outro\"];\n                if(p.contains(\"outro_path\"))\n                {\n                    std::string pp = p.value(\"outro_path\", std::string());\n                    g_recentWorldOutro = FileNamePath + pp;\n                }\n            }\n        }\n        catch(const std::exception &e)\n        {\n            pLogWarning(\"Failed to load World %s JSON data: %s\", FileNameFull.c_str(), e.what());\n        }\n    }\n\n    // world credits\n    numWorldCredits = 0;\n    for(int i = 1; i <= maxWorldCredits; i++)\n        WorldCredits[i].clear();\n\n    int B = 0;\n    std::vector<std::string> authorsList;\n    if(!wld.authors.empty())\n    {\n        Strings::split(authorsList, wld.authors, \"\\n\");\n        for(auto &c : authorsList)\n        {\n            ++B;\n            if(B > maxWorldCredits)\n                break;\n            WorldCredits[B] = c;\n            numWorldCredits = B;\n        }\n    }\n\n    return true;\n}\n\nbool OpenWorld_Tile(void*, WorldTerrainTile& t)\n{\n    {\n        numTiles++;\n        if(numTiles > maxTiles)\n        {\n            numTiles = maxTiles;\n            return false;\n        }\n\n        auto &terra = Tile[numTiles];\n\n        terra = Tile_t();\n\n        terra.Location.X = t.x;\n        terra.Location.Y = t.y;\n        terra.Type = int(t.id);\n        terra.Location.Width = TileWidth[terra.Type];\n        terra.Location.Height = TileHeight[terra.Type];\n        // terra.Z = zCounter++;\n        treeWorldTileAdd(&terra);\n\n        if(IF_OUTRANGE(terra.Type, 1, maxTileType)) // Drop ID to 1 for Tiles of out of range IDs\n        {\n            pLogWarning(\"TILE-%d ID is out of range (max types %d), reset to TILE-1\", terra.Type, maxTileType);\n            terra.Type = 1;\n        }\n    }\n\n    return true;\n}\n\nbool OpenWorld_Scene(void*, WorldScenery& s)\n{\n    {\n        numScenes++;\n        if(numScenes > maxScenes)\n        {\n            numScenes = maxScenes;\n            return false;\n        }\n\n        auto &scene = Scene[numScenes];\n\n        scene = Scene_t();\n\n        scene.Type = int(s.id);\n        scene.Location.X = s.x;\n        scene.Location.Y = s.y;\n        scene.Location.Width = SceneWidth[scene.Type];\n        scene.Location.Height = SceneHeight[scene.Type];\n        scene.Active = true;\n        // scene.Z = zCounter++;\n        treeWorldSceneAdd(&scene);\n\n        if(IF_OUTRANGE(scene.Type, 1, maxSceneType)) // Drop ID to 1 for Scenery of out of range IDs\n        {\n            pLogWarning(\"TILE-%d ID is out of range (max types %d), reset to TILE-1\", scene.Type, maxSceneType);\n            scene.Type = 1;\n        }\n    }\n\n    return true;\n}\n\nbool OpenWorld_Path(void*, WorldPathTile& p)\n{\n    {\n        numWorldPaths++;\n        if(numWorldPaths > maxWorldPaths)\n        {\n            numWorldPaths = maxWorldPaths;\n            return false;\n        }\n\n        auto &pp = WorldPath[numWorldPaths];\n\n        pp = WorldPath_t();\n\n        pp.Location.X = p.x;\n        pp.Location.Y = p.y;\n        pp.Type = int(p.id);\n        pp.Location.Width = 32;\n        pp.Location.Height = 32;\n        pp.Active = false;\n        // pp.Z = zCounter++;\n        treeWorldPathAdd(&pp);\n//        if(LevelEditor == true)\n//            pp.Active = true;\n\n        if(IF_OUTRANGE(pp.Type, 1, maxPathType)) // Drop ID to 1 for Path of out of range IDs\n        {\n            pLogWarning(\"PATH-%d ID is out of range (max types %d), reset to PATH-1\", pp.Type, maxPathType);\n            pp.Type = 1;\n        }\n    }\n\n    return true;\n}\n\nbool OpenWorld_Level(void*, WorldLevelTile& l)\n{\n    {\n        numWorldLevels++;\n        if(numWorldLevels > maxWorldLevels)\n        {\n            numWorldLevels = maxWorldLevels;\n            return false;\n        }\n\n        auto &ll = WorldLevel[numWorldLevels];\n\n        ll = WorldLevel_t();\n\n        ll.Location.X = l.x;\n        ll.Location.Y = l.y;\n        ll.Type = int(l.id);\n        validateLevelName(ll.FileName, l.lvlfile);\n        ll.LevelName = l.title;\n        ll.LevelExit[1] = l.top_exit;\n        ll.LevelExit[2] = l.left_exit;\n        ll.LevelExit[3] = l.bottom_exit;\n        ll.LevelExit[4] = l.right_exit;\n        ll.Location.Width = 32;\n        ll.Location.Height = 32;\n        ll.StartWarp = int(l.entertowarp);\n        ll.Visible = l.alwaysVisible;\n        ll.Active = ll.Visible;\n        ll.Path = l.pathbg;\n        ll.Start = l.gamestart;\n        ll.WarpX = l.gotox;\n        ll.WarpY = l.gotoy;\n        ll.Path2 = l.bigpathbg;\n\n        // new:\n        ll.starsShowPolicy = l.starsShowPolicy;\n\n        // ll.Z = zCounter++;\n        treeWorldLevelAdd(&ll);\n\n        if(IF_OUTRANGE(ll.Type, 0, maxLevelType)) // Drop ID to 1 for Levels of out of range IDs\n        {\n            pLogWarning(\"PATH-%d ID is out of range (max types %d), reset to PATH-1\", ll.Type, maxLevelType);\n            ll.Type = 1;\n        }\n    }\n\n    return true;\n}\n\nbool OpenWorld_Music(void*, WorldMusicBox& m)\n{\n    {\n        numWorldMusic++;\n        if(numWorldMusic > maxWorldMusic)\n        {\n            numWorldMusic = maxWorldMusic;\n            return false;\n        }\n\n        auto &box = WorldMusic[numWorldMusic];\n\n        box = WorldMusic_t();\n\n        box.Location.X = m.x;\n        box.Location.Y = m.y;\n        box.Type = int(m.id);\n\n        // new:\n        std::string music_file = g_dirEpisode.resolveFileCase(m.music_file);\n        if(!music_file.empty())\n        {\n            SetS(box.MusicFile, music_file); // adds to LevelString\n        }\n\n        // In game they are smaller (30x30), in world they are 32x32\n        if(!LevelEditor)\n        {\n            box.Location.Width = 30;\n            box.Location.Height = 30;\n            box.Location.Y += 1;\n            box.Location.X += 1;\n        }\n        else\n        {\n            box.Location.Width = 32;\n            box.Location.Height = 32;\n        }\n\n        // box.Z = zCounter++;\n        treeWorldMusicAdd(&box);\n    }\n\n    return true;\n}\n\nbool OpenWorld_AreaRect(void*, WorldAreaRect& m)\n{\n    {\n        if(!(m.flags & WorldAreaRect::SETUP_SET_VIEWPORT))\n            return true;\n\n        numWorldAreas++;\n        if(numWorldAreas > maxWorldAreas)\n        {\n            numWorldAreas = maxWorldAreas;\n            return false;\n        }\n\n        auto &area = WorldArea[numWorldAreas];\n\n        area = WorldArea_t();\n\n        area.Location.X = m.x;\n        area.Location.Y = m.y;\n        area.Location.Width = m.w;\n        area.Location.Height = m.h;\n    }\n\n    return true;\n}\n\n#ifdef PGEFL_CALLBACK_API\n\nWorldLoadCallbacks OpenWorld_SetupCallbacks(WorldLoad& load)\n{\n    WorldLoadCallbacks callbacks;\n\n    callbacks.on_error = OpenWorld_Error;\n    callbacks.load_head = OpenWorld_Head;\n    callbacks.load_level = OpenWorld_Level;\n    callbacks.load_path = OpenWorld_Path;\n    callbacks.load_scene = OpenWorld_Scene;\n    callbacks.load_tile = OpenWorld_Tile;\n    callbacks.load_music = OpenWorld_Music;\n    callbacks.load_arearect = OpenWorld_AreaRect;\n\n    callbacks.userdata = &load;\n\n    return callbacks;\n}\n\n#else // #ifdef PGEFL_CALLBACK_API\n\nbool OpenWorld_Unpack(WorldLoad& load, WorldData& wld)\n{\n    try\n    {\n        OpenWorld_Head(&load, wld);\n    }\n    catch(const callback_error& e)\n    {\n        pLogWarning(\"Error of level \\\"%s\\\" file loading: %s.\",\n                    wld.meta.filename.c_str(),\n                    e.what());\n\n        MessageText = e.what();\n\n        return false;\n    }\n\n    for(auto &t : wld.tiles)\n    {\n        if(!OpenWorld_Tile(&load, t))\n            break;\n    }\n    for(auto &s : wld.scenery)\n    {\n        if(!OpenWorld_Scene(&load, s))\n            break;\n    }\n    for(auto &p : wld.paths)\n    {\n        if(!OpenWorld_Path(&load, p))\n            break;\n    }\n    for(auto &l : wld.levels)\n    {\n        if(!OpenWorld_Level(&load, l))\n            break;\n    }\n    for(auto &m : wld.music)\n    {\n        if(!OpenWorld_Music(&load, m))\n            break;\n    }\n    for(auto &a : wld.arearects)\n    {\n        if(!OpenWorld_AreaRect(&load, a))\n            break;\n    }\n\n    return true;\n}\n\n#endif // #ifdef PGEFL_CALLBACK_API\n\nbool OpenWorld_Post(const WorldLoad& load)\n{\n    TranslateEpisode tr;\n\n    if(!LevelEditor)\n        tr.loadWorldTranslation(FileNameFull);\n\n    LoadCustomSound();\n\n    // the version targeting below is from SMBX 1.3\n    const int FileRelease = load.FileRelease;\n\n    if(!LevelEditor)\n    {\n        for(int A = 1; A <= numWorldLevels; A++)\n        {\n            auto &ll = WorldLevel[A];\n            if((FileRelease <= 20 && ll.Type == 1) || (FileRelease > 20 && ll.Start))\n            {\n                WorldPlayer[1].Type = 1;\n                WorldPlayer[1].Location = WorldLevel[A].Location;\n                break;\n            }\n        }\n\n        for(int A = 1; A <= numWorldLevels; A++)\n        {\n            auto &ll = WorldLevel[A];\n            if((FileRelease <= 20 && ll.Type == 1) || (FileRelease > 20 && ll.Start))\n            {\n                ll.Active = true;\n                LevelPath(WorldLevel[A], 5, true);\n            }\n        }\n    }\n    else\n    {\n        for(int A = 1; A <= numWorldLevels; A++)\n        {\n            auto &ll = WorldLevel[A];\n            if(FileRelease <= 20 && ll.Type == 1)\n                ll.Start = true;\n        }\n\n        vScreen[1].X = (XRender::TargetW / 2) - (800 / 2);\n        vScreen[1].Y = (XRender::TargetH / 2) - (600 / 2);\n    }\n\n    SaveWorldStrings();\n    resetFrameTimer();\n\n    return true;\n}\n\nvoid ClearWorld(bool quick)\n{\n    int A = 0;\n\n    for(A = 1; A <= numCharacters; A++)\n    {\n        blockCharacter[A] = false;\n//        if(LevelEditor == true)\n//            frmWorld::chkChar(A).Value = 0;\n    }\n\n    treeWorldCleanAll();\n\n    for(A = 1; A <= numTiles; A++)\n        Tile[A] = Tile_t();\n    for(A = 1; A <= numWorldPaths; A++)\n        WorldPath[A] = WorldPath_t();\n    for(A = 1; A <= numScenes; A++)\n        Scene[A] = Scene_t();\n    for(A = 1; A <= numWorldLevels; A++)\n        WorldLevel[A] = WorldLevel_t();\n    for(A = 1; A <= numWorldMusic; A++)\n        WorldMusic[A] = WorldMusic_t();\n\n    WorldPlayer[1] = WorldPlayer_t();\n\n    if(!quick)\n    {\n#ifdef __16M__\n        XRender::clearAllTextures();\n#endif\n\n        ClearStringsBank();\n        UnloadCustomGFX();\n        UnloadWorldCustomGFX();\n        UnloadCustomSound();\n        LoadPlayerDefaults();\n    }\n\n    ClearSavedLayers();\n\n    MaxWorldStars = 0;\n    numTiles = 0;\n    numWorldPaths = 0;\n    numScenes = 0;\n    numWorldLevels = 0;\n    numWorldPaths = 0;\n    numWorldMusic = 0;\n    numWorldAreas = 0;\n    RestartLevel = false;\n    WorldStarsShowPolicy = WorldData::STARS_UNSPECIFIED;\n    NoMap = false;\n    IsEpisodeIntro = false;\n    WldxCustomParams.clear();\n    SubHubLevels.clear();\n    StartLevel.clear();\n    BeatTheGame = false;\n    numWorldCredits = 0;\n    // Clear custom outro if that was presented\n    g_recentWorldOutro.clear();\n    // default file format if world header is missing\n    FileFormat = FileFormats::LVL_PGEX;\n\n    for(int i = 1; i <= maxWorldCredits; i++)\n        WorldCredits[i].clear();\n\n    if(LevelEditor)\n    {\n        vScreen[1].X = 0;\n        vScreen[1].Y = 0;\n    }\n//    if(LevelEditor == true)\n//    {\n//        frmLevelEditor::optCursor(14).Value = true;\n//        frmWorld.txtWorldName = \"\";\n//        frmWorld.txtStartLevel = \"\";\n//        frmWorld::chkNoMap.Value = false;\n//        frmWorld.chkRestartLevel = false;\n//        vScreen[1].X = 0;\n//        vScreen[1].Y = 0;\n//        for(A = 1; A <= 5; A++)\n//            frmWorld::txtCredits(A).Text = \"\";\n//        frmWorld.txtStars = \"\";\n//        MaxWorldStars = 0;\n//    }\n}\n\nvoid FindWldStars()\n{\n    LevelData tempData;\n    uint32_t start_time = SDL_GetTicks();\n\n    bool world_must_show_stars = (WorldStarsShowPolicy == Config_t::MAP_STARS_SHOW);\n\n    for(int A = 1; A <= numWorldLevels; A++)\n    {\n        IndicateProgress(start_time, num_t(A) / numWorldLevels, g_gameStrings.messageScanningLevels);\n\n        auto &l = WorldLevel[A];\n\n        if(!l.FileName.empty())\n        {\n            l.curStars = 0;\n\n            for(const auto& star : Star)\n            {\n                if(SDL_strcasecmp(star.level.c_str(), Files::basename(l.FileName).c_str()) == 0)\n                    l.curStars++;\n            }\n\n            bool level_must_show_stars = (l.starsShowPolicy == Config_t::MAP_STARS_SHOW);\n            bool level_can_show_stars = (world_must_show_stars && l.starsShowPolicy == Config_t::MAP_STARS_UNSPECIFIED);\n\n            // skip check for max stars and medals if it's already been inited, OR if the star count isn't needed\n            if(l.save_info.inited() || !(level_must_show_stars || level_can_show_stars))\n                continue;\n\n            std::string lFile = l.FileName;\n            addMissingLvlSuffix(lFile);\n\n            std::string fullPath = g_dirEpisode.resolveFileCaseExistsAbs(lFile);\n\n            if(!fullPath.empty())\n                l.save_info = InitLevelSaveInfo(fullPath, tempData);\n        }\n    }\n}\n\n#if 0\n// Is there any unsupported content for this format in the world?\nbool CanConvertWorld(int format, std::string* reasons)\n{\n    if(format == FileFormats::WLD_PGEX)\n        return true;\n\n    if(format == FileFormats::WLD_SMBX38A)\n    {\n        if(reasons)\n        {\n            *reasons = g_editorStrings.fileConvert38aUnsupported;\n            *reasons += '\\n';\n        }\n        return false;\n    }\n\n    if(format != FileFormats::WLD_SMBX64)\n    {\n        if(reasons)\n        {\n            *reasons = g_editorStrings.fileConvertFormatUnknown;\n            *reasons += '\\n';\n        }\n        return false;\n    }\n\n    bool can_convert = true;\n    if(reasons)\n        reasons->clear();\n\n    for(int i = 1; i <= numWorldMusic; i++)\n    {\n        if(!GetS(WorldMusic[i].MusicFile).empty())\n        {\n            can_convert = false;\n            if(reasons)\n            {\n                *reasons = g_editorStrings.fileConvertFeatureCustomWorldMusic;\n                *reasons += '\\n';\n            }\n            break;\n        }\n    }\n\n    if(WorldStarsShowPolicy != WorldData::STARS_UNSPECIFIED)\n    {\n        can_convert = false;\n        if(reasons)\n        {\n            *reasons = g_editorStrings.fileConvertFeatureWorldStarDisplay;\n            *reasons += '\\n';\n        }\n    }\n\n    for(int i = 1; i <= numWorldLevels; i++)\n    {\n        if(WorldLevel[i].starsShowPolicy != WorldData::STARS_UNSPECIFIED)\n        {\n            can_convert = false;\n            if(reasons)\n            {\n                *reasons = g_editorStrings.fileConvertFeatureLevelStarDisplay;\n                *reasons += '\\n';\n            }\n        }\n    }\n\n    if(numWorldAreas > 0)\n    {\n        can_convert = false;\n        if(reasons)\n        {\n            *reasons = g_editorStrings.fileConvertFeatureLevelStarDisplay;\n            *reasons += '\\n';\n        }\n    }\n\n    return can_convert;\n}\n\n// Strips all unsupported content from the world.\nvoid ConvertWorld(int format)\n{\n    FileFormat = format;\n    if(format == FileFormats::LVL_SMBX64 || format == FileFormats::LVL_SMBX38A)\n    {\n        if(!FileNameFull.empty() && FileNameFull.back() == 'x')\n            FileNameFull.resize(FileNameFull.size() - 1);\n        if(!FullFileName.empty() && FullFileName.back() == 'x')\n            FullFileName.resize(FullFileName.size() - 1);\n    }\n    else\n    {\n        if(!FileNameFull.empty() && FileNameFull.back() != 'x')\n            FileNameFull += \"x\";\n        if(!FullFileName.empty() && FullFileName.back() != 'x')\n            FullFileName += \"x\";\n    }\n\n    if(format != FileFormats::WLD_SMBX64)\n        return;\n\n    for(int i = 1; i <= numWorldMusic; i++)\n        SetS(WorldMusic[i].MusicFile, \"\");\n\n    WorldStarsShowPolicy = WorldData::STARS_UNSPECIFIED;\n\n    for(int i = 1; i <= numWorldLevels; i++)\n        WorldLevel[i].starsShowPolicy = WorldData::STARS_UNSPECIFIED;\n\n    numWorldAreas = 0;\n}\n#endif\n"
  },
  {
    "path": "src/main/world_file.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#pragma once\n#ifndef WORLD_FILE_H\n#define WORLD_FILE_H\n\n#include <string>\n\n//! loads the world\nbool OpenWorld(std::string FilePath);\nvoid FindWldStars();\n//! clears the world.\n//  when \"quick\" is true, does not unload custom assets. this is used only for creating new worlds at menu.\nvoid ClearWorld(bool quick = false);\n\n//! NEW: routines to check if it is possible to convert to legacy file formats and to remove all non-legacy content\n// Removed, let PGE-FL handle conversion on its own.\n// bool CanConvertWorld(int format, std::string* reasons);\n// void ConvertWorld(int format);\n\n#endif // WORLD_FILE_H\n"
  },
  {
    "path": "src/main/world_globals.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef WORLD_GLOBALS_H\n#define WORLD_GLOBALS_H\n\n#include \"../screen_fader.h\"\n\n//! Holds the screen overlay for the world map\nextern ScreenFader g_worldScreenFader;\n\n//! Multiplier for world map qScreen\nextern num_t g_worldCamSpeed;\n\n//! Play sound if world map qScreen stays active next frame\nextern bool g_worldPlayCamSound;\n\n//! NEW: set the world player's section variable based on its position\nextern void worldCheckSection(WorldPlayer_t& wp);\n\n//! NEW: reset the world players' section variables without invoking qScreen\nextern void worldResetSection();\n\nextern void worldWaitForFade(int waitTicks = -1);\n\nextern bool worldHasFrameAssets();\n\n#endif // WORLD_GLOBALS_H\n"
  },
  {
    "path": "src/main/world_loop.cpp",
    "content": "﻿/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Utils/files.h>\n#include <Integrator/integrator.h>\n#include <pge_delay.h>\n#include <fmt_format_ne.h>\n#include <sdl_proxy/sdl_stdinc.h>\n\n#include \"../globals.h\"\n#include \"../frame_timer.h\"\n#include \"../game_main.h\"\n#include \"../sound.h\"\n#include \"../controls.h\"\n#include \"../graphics.h\"\n#include \"../collision.h\"\n#include \"../player.h\"\n#include \"../main/trees.h\"\n#include \"../core/events.h\"\n#include \"../config.h\"\n#include \"gfx.h\"\n#include \"message.h\"\n#include \"main/world_globals.h\"\n#include \"main/level_file.h\"\n#include \"main/speedrunner.h\"\n#include \"main/screen_quickreconnect.h\"\n#include \"main/screen_connect.h\"\n#include \"main/game_strings.h\"\n#include \"graphics/gfx_world.h\"\n\n#include \"global_dirs.h\"\n\n//! Holds the screen overlay for the world map\nScreenFader g_worldScreenFader;\n\n//! Multiplier for world map qScreen\nnum_t g_worldCamSpeed = 1.5_n;\n\n//! Play sound if world map qScreen stays active next frame\nbool g_worldPlayCamSound = false;\n\nvoid worldWaitForFade(int waitTicks)\n{\n    bool ticks = waitTicks > 0;\n    while(((!ticks && !g_worldScreenFader.isComplete()) || (ticks && waitTicks >= 0)) && GameIsActive)\n    {\n        XEvents::doEvents();\n\n        if(canProceedFrame())\n        {\n            computeFrameTime1();\n            Controls::Update(false);\n            // FIXME: strip logic out of UpdateGraphics2\n            UpdateGraphics2();\n            UpdateSound();\n            XEvents::doEvents();\n            computeFrameTime2();\n            g_worldScreenFader.update();\n            if(waitTicks >= 0)\n                waitTicks--;\n        }\n\n        if(!g_config.unlimited_framerate)\n            PGE_Delay(1);\n    }\n\n    // Ensure everything is clear\n    if(GameIsActive)\n    {\n        XEvents::doEvents();\n        GraphicsClearScreen();\n    }\n}\n\n\nbool g_isWorldMusicNotSame(WorldMusic_t &mus)\n{\n    bool ret = false;\n    ret |= (curWorldMusic != mus.Type);\n    ret |= (mus.Type == CustomWorldMusicId() && curWorldMusicFile != GetS(mus.MusicFile));\n    return ret;\n}\n\nvoid g_playWorldMusic(WorldMusic_t &mus)\n{\n    curWorldMusicFile = GetS(mus.MusicFile);\n    StartMusic(mus.Type);\n}\n\n// returns size of largest box centered around loc contained in s\nstatic inline int s_worldSectionArea(WorldAreaRef_t s, const TinyLocation_t& loc)\n{\n    int fX = loc.X + loc.Width / 2;\n    int fY = loc.Y + loc.Height / 2;\n\n    int side_x = SDL_min(fX - s->Location.X, s->Location.X + s->Location.Width - fX);\n    int side_y = SDL_min(fY - s->Location.Y, s->Location.Y + s->Location.Height - fY);\n\n    return side_x * side_y;\n}\n\nstatic inline bool s_worldUpdateMusic(const TinyLocation_t &loc)\n{\n    bool ret = false;\n\n    for(auto t : treeWorldMusicQuery(loc, false))\n    {\n        WorldMusic_t &mus = *t;\n        if(CheckCollision(loc, mus.Location))\n        {\n            if(g_isWorldMusicNotSame(mus))\n            {\n                g_playWorldMusic(mus);\n                ret = true;\n            }\n        }\n    }\n\n    return ret;\n}\n\nstatic void s_worldCheckSection(WorldPlayer_t& wp, const TinyLocation_t& loc)\n{\n    int best_section = 0;\n    int best_section_area = 0;\n    int best_wasted_area = 0;\n\n    for(int A = 1; A <= numWorldAreas; A++)\n    {\n        WorldArea_t &area = WorldArea[A];\n        if(CheckCollision(loc, area.Location))\n        {\n            int section_area = s_worldSectionArea(A, loc);\n            int wasted_area = area.Location.Width * area.Location.Height - section_area;\n\n            if(section_area >= best_section_area && (section_area > best_section_area || wasted_area < best_wasted_area))\n            {\n                best_section = A;\n                best_section_area = section_area;\n                best_wasted_area = wasted_area;\n            }\n        }\n    }\n\n    if(best_section != wp.Section)\n    {\n        wp.Section = best_section;\n\n        // enable a qScreen to the new section\n        if(!qScreen)\n        {\n            const int Z = l_screen->vScreen_refs[0];\n\n            qScreen = true;\n            g_worldPlayCamSound = true;\n            qScreenLoc[Z] = vScreen[Z];\n\n            // move camera quickly for transitions!\n            if(g_worldCamSpeed < 4)\n                g_worldCamSpeed = 4;\n        }\n    }\n}\n\nvoid worldCheckSection(WorldPlayer_t& wp)\n{\n    s_worldCheckSection(wp, wp.Location);\n}\n\nstatic inline int getWPHeight()\n{\n    switch(WorldPlayer[1].Type)\n    {\n    case 3:\n        return 44;\n        break;\n    case 4:\n        return 40;\n        break;\n    default:\n        return 32;\n        break;\n    }\n}\n\nvoid worldResetSection()\n{\n    worldCheckSection(WorldPlayer[1]);\n    qScreen = false;\n    GetvScreenWorld(l_screen->vScreen(1));\n}\n\n//static inline double getWorldPlayerX()\n//{\n//    return WorldPlayer[1].Location.X;\n//}\n\n//static inline double getWorldPlayerY()\n//{\n//    return WorldPlayer[1].Location.Y - 10 + WorldPlayer[1].Location.Height - getWPHeight();\n//}\n\nstatic inline int getWorldPlayerCenterX()\n{\n    return (int)(WorldPlayer[1].Location.X + WorldPlayer[1].Location.Width / 2);\n}\n\nstatic inline int getWorldPlayerCenterY()\n{\n    return (int)(WorldPlayer[1].Location.Y + WorldPlayer[1].Location.Height) - 10 - getWPHeight() / 2;\n}\n\nvoid WorldLoop()\n{\n    bool musicReset = false;\n    const int Z = l_screen->vScreen_refs[0];\n\n    if(GamePaused != PauseCode::None)\n    {\n        PauseLoop();\n\n        if(GamePaused != PauseCode::None)\n            return;\n        else\n            goto resume_from_pause;\n    }\n\n    // Location_t tempLocation;\n    // int A = 0;\n    // int B = 0;\n\n    if(SingleCoop > 0)\n        SingleCoop = 1;\n\n    // remove any temporary path focus\n    vScreen[Z].TempDelay = 0;\n    vScreen[Z].tempX = 0;\n    vScreen[Z].TempY = 0;\n\n    // disable cloned player mode\n    if(g_ClonedPlayerMode)\n    {\n        numPlayers = (int)Controls::g_InputMethods.size();\n        g_ClonedPlayerMode = false;\n    }\n\n    if(numPlayers < 1)\n        numPlayers = 1;\n\n    if(numPlayers > maxLocalPlayers)\n        numPlayers = maxLocalPlayers;\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        if(Player[B].Mount == 2)\n            Player[B].Mount = 0;\n    }\n\n    speedRun_tick();\n    Integrator::sync();\n    UpdateGraphics2();\n\n    if(!Controls::Update())\n    {\n        QuickReconnectScreen::g_active = true;\n\n        if(g_config.allow_drop_add && XMessage::GetStatus() == XMessage::Status::local)\n            PauseGame(PauseCode::DropAdd, 0);\n    }\n\n    if(QuickReconnectScreen::g_active)\n        QuickReconnectScreen::Logic();\n\n    UpdateSound();\n\n    g_worldScreenFader.update();\n\n    if(curWorldLevel > 0)\n    {\n        if(LevelBeatCode > 0)\n        {\n            s_worldUpdateMusic(WorldPlayer[1].Location);\n            worldResetSection();\n\n            for(int A = 1; A <= 4; A++)\n            {\n                if(WorldLevel[curWorldLevel].LevelExit[A] == LevelBeatCode || WorldLevel[curWorldLevel].LevelExit[A] == -1)\n                {\n                    WorldPlayer[1].LevelIndex = curWorldLevel;\n                    LevelPath(WorldLevel[curWorldLevel], A);\n                }\n            }\n\n            SaveGame();\n            LevelBeatCode = BEATCODE_NONE;\n        }\n        // case where the player manually quit or there was an error\n        // (these should probably be split up -- the error logic is from SMBX 1.3 and should be preserved)\n        else if(LevelBeatCode == BEATCODE_QUIT)\n        {\n            s_worldUpdateMusic(WorldPlayer[1].Location);\n            worldResetSection();\n\n            //for(A = 1; A <= numWorldLevels; A++)\n            for(WorldLevelRef_t t : treeWorldLevelQuery(WorldPlayer[1].Location, SORTMODE_NONE))\n            {\n                WorldLevel_t &l = *t;\n                if(CheckCollision(WorldPlayer[1].Location, l.Location))\n                {\n                    WorldPlayer[1].LevelIndex = t;\n                    break;\n                }\n            }\n\n            if(curWorldLevel > 0)\n                LevelPath(WorldLevel[curWorldLevel], 5);\n\n            SaveGame();\n            LevelBeatCode = BEATCODE_NONE;\n        }\n    }\n    else\n        LevelBeatCode = BEATCODE_NONE;\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        Player[A].Bumped = false;\n        Player[A].Bumped2 = 0;\n        Player[A].CanFly = false;\n        Player[A].CanFly2 = false;\n        Player[A].Effect = PLREFF_NORMAL;\n        Player[A].Effect2 = 0;\n        Player[A].FlyCount = 0;\n        Player[A].TailCount = 0;\n        Player[A].Stoned = false;\n    }\n\n    if(WorldPlayer[1].Move > 0)\n    {\n        WorldPlayer[1].Frame2 += 1;\n\n        if(WorldPlayer[1].Frame2 >= 8)\n        {\n            WorldPlayer[1].Frame2 = 0;\n            WorldPlayer[1].Frame += 1;\n        }\n\n        if(WorldPlayer[1].Move == 1)\n        {\n            if(WorldPlayer[1].Frame < 6)\n                WorldPlayer[1].Frame = 7;\n            if(WorldPlayer[1].Frame > 7)\n                WorldPlayer[1].Frame = 6;\n        }\n\n        if(WorldPlayer[1].Move == 4)\n        {\n            if(WorldPlayer[1].Frame < 2)\n                WorldPlayer[1].Frame = 3;\n            if(WorldPlayer[1].Frame > 3)\n                WorldPlayer[1].Frame = 2;\n        }\n\n        if(WorldPlayer[1].Move == 3)\n        {\n            if(WorldPlayer[1].Frame < 0)\n                WorldPlayer[1].Frame = 1;\n            if(WorldPlayer[1].Frame > 1)\n                WorldPlayer[1].Frame = 0;\n        }\n\n        if(WorldPlayer[1].Move == 2)\n        {\n            if(WorldPlayer[1].Frame < 4)\n                WorldPlayer[1].Frame = 5;\n            if(WorldPlayer[1].Frame > 5)\n                WorldPlayer[1].Frame = 4;\n        }\n    }\n\n\n    if(WorldPlayer[1].Move == 0)\n    {\n        if(SharedPause)\n        {\n            PauseInit(PauseCode::PauseScreen, 0);\n            return;\n        }\n\n        for(int i = 1; i <= numPlayers; i++)\n        {\n            if(Player[i].Controls.Start && Player[i].UnStart)\n            {\n                PauseInit(PauseCode::PauseScreen, 0);\n                return;\n            }\n\n            // only allow P1 to pause if multiplayer pause controls disabled\n            if(!g_config.multiplayer_pause_controls)\n                break;\n        }\n\nresume_from_pause:\n\n        // find player's current level\n        TinyLocation_t tempLocation = WorldPlayer[1].Location;\n        tempLocation.Width -= 8;\n        tempLocation.Height -= 8;\n        tempLocation.X += 4;\n        tempLocation.Y += 4;\n\n        WorldPlayer[1].LevelIndex = 0;\n\n        //for(A = 1; A <= numWorldLevels; A++)\n        for(auto t : treeWorldLevelQuery(tempLocation, SORTMODE_Z))\n        {\n            WorldLevel_t &l = *t;\n            if(CheckCollision(tempLocation, l.Location))\n            {\n                WorldPlayer[1].LevelIndex = t;\n                break;\n            }\n        }\n\n        // NEW: allow CanAltJump to start levels (for starting levels using AltJump)\n        if(g_config.multiplayer_pause_controls)\n        {\n            if(Player[1].Controls.AltJump)\n            {\n                if(Player[1].CanAltJump)\n                    Player[1].Controls.Jump = true;\n\n                Player[1].CanAltJump = false;\n            }\n            else\n                Player[1].CanAltJump = true;\n        }\n\n        // geneeral controls / movement logic\n        if(Player[1].Controls.Up)\n        {\n            tempLocation.Y -= 32;\n\n            //for(A = 1; A <= numWorldPaths; A++)\n            for(auto t : treeWorldPathQuery(tempLocation, SORTMODE_ID))\n            {\n                WorldPath_t &path = *t;\n                if(CheckCollision(tempLocation, path.Location) && path.Active)\n                {\n                    WorldPlayer[1].Move = 1;\n                    break;\n                }\n            }\n\n            //for(A = 1; A <= numWorldLevels; A++)\n            for(auto t : treeWorldLevelQuery(tempLocation, SORTMODE_ID))\n            {\n                WorldLevel_t &lvl = *t;\n                if(WorldPlayer[1].Move == 0)\n                {\n                    if(CheckCollision(tempLocation, lvl.Location) && lvl.Active)\n                    {\n                        WorldPlayer[1].Move = 1;\n                        break;\n                    }\n                }\n            }\n\n            if(WalkAnywhere)\n                WorldPlayer[1].Move = 1;\n\n            if(WorldPlayer[1].Move == 0)\n            {\n                WorldPlayer[1].Move3 = false;\n                PlaySound(SFX_BlockHit);\n                SoundPause[SFX_BlockHit] = 2;\n            }\n        }\n        else if(Player[1].Controls.Left)\n        {\n            tempLocation.X -= 32;\n\n            //for(A = 1; A <= numWorldPaths; A++)\n            for(auto t : treeWorldPathQuery(tempLocation, SORTMODE_ID))\n            {\n                WorldPath_t &path = *t;\n                if(CheckCollision(tempLocation, path.Location) && path.Active)\n                {\n                    WorldPlayer[1].Move = 2;\n                    break;\n                }\n            }\n\n            //for(A = 1; A <= numWorldLevels; A++)\n            for(auto t : treeWorldLevelQuery(tempLocation, SORTMODE_ID))\n            {\n                WorldLevel_t &lvl = *t;\n                if(WorldPlayer[1].Move == 0)\n                {\n                    if(CheckCollision(tempLocation, lvl.Location) && lvl.Active)\n                    {\n                        WorldPlayer[1].Move = 2;\n                        break;\n                    }\n                }\n            }\n\n            if(WalkAnywhere)\n                WorldPlayer[1].Move = 2;\n            if(WorldPlayer[1].Move == 0)\n            {\n                WorldPlayer[1].Move3 = false;\n                PlaySound(SFX_BlockHit);\n                SoundPause[SFX_BlockHit] = 2;\n            }\n        }\n        else if(Player[1].Controls.Down)\n        {\n            tempLocation.Y += 32;\n\n            //for(A = 1; A <= numWorldPaths; A++)\n            for(auto t : treeWorldPathQuery(tempLocation, SORTMODE_ID))\n            {\n                WorldPath_t &path = *t;\n                if(CheckCollision(tempLocation, path.Location) && path.Active)\n                {\n                    WorldPlayer[1].Move = 3;\n                    break;\n                }\n            }\n\n            //for(A = 1; A <= numWorldLevels; A++)\n            for(auto t : treeWorldLevelQuery(tempLocation, SORTMODE_ID))\n            {\n                WorldLevel_t &lvl = *t;\n                if(WorldPlayer[1].Move == 0)\n                {\n                    if(CheckCollision(tempLocation, lvl.Location) && lvl.Active)\n                    {\n                        WorldPlayer[1].Move = 3;\n                        break;\n                    }\n                }\n            }\n\n            if(WalkAnywhere)\n                WorldPlayer[1].Move = 3;\n            if(WorldPlayer[1].Move == 0)\n            {\n                WorldPlayer[1].Move3 = false;\n                PlaySound(SFX_BlockHit);\n                SoundPause[SFX_BlockHit] = 2;\n            }\n        }\n        else if(Player[1].Controls.Right)\n        {\n            tempLocation.X += 32;\n\n            //for(A = 1; A <= numWorldPaths; A++)\n            for(auto t : treeWorldPathQuery(tempLocation, SORTMODE_ID))\n            {\n                WorldPath_t &path = *t;\n                if(CheckCollision(tempLocation, path.Location) && path.Active)\n                {\n                    WorldPlayer[1].Move = 4;\n                    break;\n                }\n            }\n\n            //for(A = 1; A <= numWorldLevels; A++)\n            for(auto t : treeWorldLevelQuery(tempLocation, SORTMODE_ID))\n            {\n                WorldLevel_t &lvl = *t;\n                if(WorldPlayer[1].Move == 0)\n                {\n                    if(CheckCollision(tempLocation, lvl.Location) && lvl.Active)\n                    {\n                        WorldPlayer[1].Move = 4;\n                        break;\n                    }\n                }\n            }\n\n            if(WalkAnywhere)\n                WorldPlayer[1].Move = 4;\n            if(WorldPlayer[1].Move == 0)\n            {\n                WorldPlayer[1].Move3 = false;\n                PlaySound(SFX_BlockHit);\n                SoundPause[SFX_BlockHit] = 2;\n            }\n        }\n        else if(Player[1].Controls.Jump && Player[1].UnStart)\n        {\n            //for(A = 1; A <= numWorldLevels; A++)\n            for(WorldLevelRef_t t : treeWorldLevelQuery(tempLocation, SORTMODE_ID))\n            {\n                WorldLevel_t &lvl = *t;\n                if(CheckCollision(tempLocation, lvl.Location))\n                {\n#if 0 // Moved into the handler of level ending\n                    if(int(lvl.WarpX) != -1)\n                        WorldPlayer[1].Location.X = lvl.WarpX;\n\n                    if(int(lvl.WarpY) != -1)\n                        WorldPlayer[1].Location.Y = lvl.WarpY;\n\n                    if(int(lvl.WarpY) != -1 || int(lvl.WarpX) != -1)\n                    {\n                        LevelBeatCode = 6;\n                        //for(B = 1; B <= numWorldLevels; B++)\n                        for(auto t2 : treeWorldLevelQuery(WorldPlayer[1].Location, SORTMODE_ID))\n                        {\n                            WorldLevel_t &level2 = *t2;\n                            if(CheckCollision(WorldPlayer[1].Location, level2.Location))\n                            {\n                                level2.Active = true;\n                                curWorldLevel = t2;\n                            }\n                        }\n                    }\n#endif\n\n                    if(!lvl.FileName.empty() && lvl.FileName != \".lvl\" && lvl.FileName != \".lvlx\")\n                    {\n                        addMissingLvlSuffix(lvl.FileName);\n                        std::string levelPath = g_dirEpisode.resolveFileCaseExistsAbs(lvl.FileName);\n\n                        if(!levelPath.empty())\n                        {\n                            // save which characters were present at level start\n                            if(SwapCharAllowed())\n                            {\n                                pLogDebug(\"Save drop/add characters configuration at WorldLoop()\");\n                                ConnectScreen::SaveChars();\n                            }\n\n                            StartWarp = lvl.StartWarp;\n                            StopMusic();\n                            PlaySound(SFX_LevelSelect);\n\n                            // NOTE: this is TheXTech's timing. For TAS Mode 3: SMBX 1.3's timing waits 33 fewer frames (33 * 15.6 fewer ms).\n                            int GameThingTicks = 1000;\n\n                            if(g_config.EnableInterLevelFade)\n                            {\n                                g_worldScreenFader.setupFader(2, 0, 65, ScreenFader::S_RECT,\n                                                              true,\n                                                              getWorldPlayerCenterX(), getWorldPlayerCenterY(), l_screen->vScreen_refs[0]);\n                                worldWaitForFade();\n                            }\n                            else\n                                GameThingTicks += (33 * 156 / 10);\n\n                            SoundPause[SFX_Slide] = 200;\n                            curWorldLevel = t;\n                            LevelSelect = false;\n\n                            delayedMusicReset(); // Reset delayed music to prevent unexpected behaviour at loaded level\n\n                            ClearLevel();\n\n                            if(!OpenLevel(levelPath))\n                            {\n                                delayedMusicStart(); // Allow music being started\n                                ReportLoadFailure(lvl.FileName);\n                                ErrorQuit = true;\n                            }\n\n                            GameThing(GameThingTicks, 3);\n\n                            break;\n                        }\n                        else\n                        {\n                            pLogWarning(\"Level file name \\\"%s\\\" at %d x %d (id=%d) was not found (directory %s)\",\n                                        lvl.FileName.c_str(),\n                                        (int)lvl.Location.X,\n                                        (int)lvl.Location.Y,\n                                        lvl.Type,\n                                        g_dirEpisode.getCurDir().c_str()\n                            );\n                        }\n                    }\n                    else if(lvl.WarpX != -1 || lvl.WarpY != -1)\n                    {\n                        musicReset = true;\n                        StopMusic();\n                        PlaySound(SFX_Warp);\n//                        frmMain.setTargetTexture();\n//                        frmMain.clearBuffer();\n//                        frmMain.repaint();\n//                        DoEvents();\n//                        PGE_Delay(1000);\n                        if(g_config.EnableInterLevelFade)\n                        {\n                            g_worldScreenFader.setupFader(3, 0, 65, ScreenFader::S_RECT,\n                                                          true,\n                                                          getWorldPlayerCenterX(), getWorldPlayerCenterY(), l_screen->vScreen_refs[0]);\n                            worldWaitForFade(65);\n                        }\n                        else\n                            worldWaitForFade(65);\n\n                        // Moved from above\n                        if(lvl.WarpX != -1)\n                            WorldPlayer[1].Location.X = lvl.WarpX;\n                        if(lvl.WarpY != -1)\n                            WorldPlayer[1].Location.Y = lvl.WarpY;\n\n                        worldResetSection();\n\n                        LevelBeatCode = BEATCODE_WARP;\n\n                        //for(B = 1; B <= numWorldLevels; B++)\n                        for(WorldLevelRef_t t2 : treeWorldLevelQuery(WorldPlayer[1].Location, SORTMODE_ID))\n                        {\n                            WorldLevel_t &level2 = *t2;\n                            if(CheckCollision(WorldPlayer[1].Location, level2.Location))\n                            {\n                                level2.Active = true;\n                                curWorldLevel = t2;\n                            }\n                        }\n                        // -----------------------\n\n                        if(g_config.EnableInterLevelFade)\n                        {\n                            g_worldScreenFader.setupFader(3, 65, 0, ScreenFader::S_RECT,\n                                                          true,\n                                                          getWorldPlayerCenterX(), getWorldPlayerCenterY(), l_screen->vScreen_refs[0]);\n                        }\n//                        resetFrameTimer();\n                    }\n                }\n            }\n        }\n        // else\n\n        if(WorldPlayer[1].Move3 && WorldPlayer[1].Move == 0)\n        {\n            if(g_config.world_map_fast_move)\n                PlayerPath(WorldPlayer[1]);\n\n            if(WorldPlayer[1].Move == 0)\n            {\n                WorldPlayer[1].Move3 = false;\n                PlaySound(SFX_Slide);\n            }\n        }\n\n        if(WorldPlayer[1].Move == 0)\n        {\n            if(WorldPlayer[1].Frame == 5)\n                WorldPlayer[1].Frame = 4;\n            if(WorldPlayer[1].Frame == 3)\n                WorldPlayer[1].Frame = 2;\n        }\n\n//        if(WorldPlayer[1].Move3 && WorldPlayer[1].Move == 0)\n//        {\n//            WorldPlayer[1].Move3 = false;\n//            PlaySound(SFX_Slide);\n//        }\n        WorldPlayer[1].LastMove = 0;\n\n        worldCheckSection(WorldPlayer[1]);\n\n        if(s_worldUpdateMusic(WorldPlayer[1].Location))\n            musicReset = false;\n\n        if(musicReset) // Resume the last playing music after teleportation\n        {\n            StartMusic(curWorldMusic);\n            // musicReset = false;\n        }\n\n        if(delayMusicIsSet())\n            delayedMusicStart();\n    }\n    else if(WorldPlayer[1].Move == 1)\n    {\n        WorldPlayer[1].Move2 += 2;\n        WorldPlayer[1].Location.Y -= 2;\n\n        if(WalkAnywhere || g_config.world_map_fast_move)\n        {\n            WorldPlayer[1].Move2 += 2;\n            WorldPlayer[1].Location.Y -= 2;\n        }\n\n        if(WorldPlayer[1].Move2 >= 32)\n        {\n            WorldPlayer[1].LastMove = WorldPlayer[1].Move;\n            WorldPlayer[1].Move2 = 0;\n            WorldPlayer[1].Move = 0;\n            WorldPlayer[1].Move3 = true;\n        }\n    }\n    else if(WorldPlayer[1].Move == 2)\n    {\n        WorldPlayer[1].Move2 += 2;\n        WorldPlayer[1].Location.X -= 2;\n\n        if(WalkAnywhere || g_config.world_map_fast_move)\n        {\n            WorldPlayer[1].Move2 += 2;\n            WorldPlayer[1].Location.X -= 2;\n        }\n\n        if(WorldPlayer[1].Move2 >= 32)\n        {\n            WorldPlayer[1].LastMove = WorldPlayer[1].Move;\n            WorldPlayer[1].Move2 = 0;\n            WorldPlayer[1].Move = 0;\n            WorldPlayer[1].Move3 = true;\n        }\n    }\n    else if(WorldPlayer[1].Move == 3)\n    {\n        WorldPlayer[1].Move2 += 2;\n        WorldPlayer[1].Location.Y += 2;\n\n        if(WalkAnywhere || g_config.world_map_fast_move)\n        {\n            WorldPlayer[1].Move2 += 2;\n            WorldPlayer[1].Location.Y += 2;\n        }\n\n        if(WorldPlayer[1].Move2 >= 32)\n        {\n            WorldPlayer[1].LastMove = WorldPlayer[1].Move;\n            WorldPlayer[1].Move2 = 0;\n            WorldPlayer[1].Move = 0;\n            WorldPlayer[1].Move3 = true;\n        }\n    }\n    else if(WorldPlayer[1].Move == 4)\n    {\n        WorldPlayer[1].Move2 += 2;\n        WorldPlayer[1].Location.X += 2;\n\n        if(WalkAnywhere || g_config.world_map_fast_move)\n        {\n            WorldPlayer[1].Move2 += 2;\n            WorldPlayer[1].Location.X += 2;\n        }\n\n        if(WorldPlayer[1].Move2 >= 32)\n        {\n            WorldPlayer[1].LastMove = WorldPlayer[1].Move;\n            WorldPlayer[1].Move2 = 0;\n            WorldPlayer[1].Move = 0;\n            WorldPlayer[1].Move3 = true;\n        }\n    }\n}\n\nvoid LevelPath(const WorldLevel_t &Lvl, int Direction, bool Skp)\n{\n    TinyLocation_t tempLocation;\n//    int A = 0;\n\n    bool hit = false;\n\n    // Up\n    if(Direction == 1 || Direction == 5)\n    {\n        tempLocation = Lvl.Location;\n        tempLocation.X +=  4;\n        tempLocation.Y +=  4;\n        tempLocation.Width -= 8;\n        tempLocation.Height -=  8;\n        tempLocation.Y -= 32;\n\n        //for(A = 1; A <= numWorldPaths; A++)\n        for(auto t : treeWorldPathQuery(tempLocation, SORTMODE_NONE))\n        {\n            WorldPath_t &path = *t;\n            if(!path.Active)\n            {\n                if(CheckCollision(tempLocation, path.Location))\n                {\n                    PathPath(path, Skp);\n                    hit = true;\n                    // move camera quickly for path branch switch\n                    if(g_worldCamSpeed < 4 && !Skp)\n                        g_worldCamSpeed = 4;\n                }\n            }\n        }\n    }\n\n    // Left\n    if(Direction == 2 || Direction == 5)\n    {\n        tempLocation = Lvl.Location;\n        tempLocation.X += 4;\n        tempLocation.Y += 4;\n        tempLocation.Width -= 8;\n        tempLocation.Height -= 8;\n        tempLocation.X -= 32;\n\n        //for(A = 1; A <= numWorldPaths; A++)\n        for(auto t : treeWorldPathQuery(tempLocation, SORTMODE_NONE))\n        {\n            WorldPath_t &path = *t;\n            if(!path.Active)\n            {\n                if(CheckCollision(tempLocation, path.Location))\n                {\n                    PathPath(path, Skp);\n                    hit = true;\n                    // move camera quickly for path branch switch\n                    if(g_worldCamSpeed < 4 && !Skp)\n                        g_worldCamSpeed = 4;\n                }\n            }\n        }\n    }\n\n    // Down\n    if(Direction == 3 || Direction == 5)\n    {\n        tempLocation = Lvl.Location;\n        tempLocation.X += 4;\n        tempLocation.Y += 4;\n        tempLocation.Width -= 8;\n        tempLocation.Height -= 8;\n        tempLocation.Y += 32;\n\n        //for(A = 1; A <= numWorldPaths; A++)\n        for(auto t : treeWorldPathQuery(tempLocation, SORTMODE_NONE))\n        {\n            WorldPath_t &path = *t;\n            if(!path.Active)\n            {\n                if(CheckCollision(tempLocation, path.Location))\n                {\n                    PathPath(path, Skp);\n                    hit = true;\n                    // move camera quickly for path branch switch\n                    if(g_worldCamSpeed < 4 && !Skp)\n                        g_worldCamSpeed = 4;\n                }\n            }\n        }\n    }\n\n    // Right\n    if(Direction == 4 || Direction == 5)\n    {\n        tempLocation = Lvl.Location;\n        tempLocation.X += 4;\n        tempLocation.Y += 4;\n        tempLocation.Width -= 8;\n        tempLocation.Height -= 8;\n        tempLocation.X += 32;\n\n        //for(A = 1; A <= numWorldPaths; A++)\n        for(auto t : treeWorldPathQuery(tempLocation, SORTMODE_NONE))\n        {\n            WorldPath_t &path = *t;\n            if(!path.Active)\n            {\n                if(CheckCollision(tempLocation, path.Location))\n                {\n                    PathPath(path, Skp);\n                    hit = true;\n                    // move camera quickly for path branch switch\n                    if(g_worldCamSpeed < 4 && !Skp)\n                        g_worldCamSpeed = 4;\n                }\n            }\n        }\n    }\n\n    // quickly return to player\n    if(g_config.EnableInterLevelFade && hit && !Skp)\n    {\n        qScreen = true;\n\n        const int Z = l_screen->vScreen_refs[0];\n        qScreenLoc[Z] = vScreen[Z];\n\n        if(g_worldCamSpeed < 8)\n            g_worldCamSpeed = 8;\n    }\n}\n\nvoid PlayerPath(WorldPlayer_t &p)\n{\n    if(p.LevelIndex)\n        return;\n\n    TinyLocation_t tempLocation = p.Location;\n\n    tempLocation.X += 4;\n    tempLocation.Y += 4;\n    tempLocation.Width -= 8;\n    tempLocation.Height -= 8;\n\n    // stop at level\n    for(auto t : treeWorldLevelQuery(tempLocation, false))\n    {\n        WorldLevel_t& lvl = *t;\n        if(CheckCollision(tempLocation, lvl.Location) && lvl.Active)\n        {\n            p.Move = 0;\n            return;\n        }\n    }\n\n    // stop at branch point\n    int n_moves = 0;\n    for(int B = 1; B <= 4; B++)\n    {\n        if(B == 1)\n            tempLocation.Y -= 32; // Up\n        else if(B == 2)\n        {\n            tempLocation.Y += 32; // Down\n            tempLocation.X -= 32; // Left\n        }\n        else if(B == 3)\n        {\n            tempLocation.X += 32; // Right\n            tempLocation.Y += 32; // Down\n        }\n        else if(B == 4)\n        {\n            tempLocation.Y -= 32; // Up\n            tempLocation.X += 32; // Right\n        }\n\n        // don't consider the backwards path\n        if(B != p.LastMove && B % 2 == p.LastMove % 2)\n            continue;\n\n        for(auto t : treeWorldPathQuery(tempLocation, false))\n        {\n            WorldPath_t& path = *t;\n            if(CheckCollision(tempLocation, path.Location) && path.Active)\n            {\n                p.Move = B;\n                n_moves ++;\n                break;\n            }\n        }\n\n        if(p.Move == B)\n            continue;\n\n        for(auto t : treeWorldLevelQuery(tempLocation, false))\n        {\n            WorldLevel_t& lvl = *t;\n            if(CheckCollision(tempLocation, lvl.Location) && lvl.Active)\n            {\n                p.Move = B;\n                n_moves ++;\n                break;\n            }\n        }\n    }\n\n    if(n_moves > 1)\n        p.Move = 0;\n}\n\nvoid PathPath(WorldPath_t &Pth, bool Skp)\n{\n    const int Z = l_screen->vScreen_refs[0];\n\n    //int A = 0;\n    int B = 0;\n\n    TinyLocation_t tempLocation;\n    tempLocation = Pth.Location;\n    tempLocation.X += 4;\n    tempLocation.Y += 4;\n    tempLocation.Width -= 8;\n    tempLocation.Height -= 8;\n\n    //for(A = 1; A <= numScenes; A++)\n    for(auto t : treeWorldSceneQuery(tempLocation, SORTMODE_ID))\n    {\n        Scene_t &scene = *t;\n        if(scene.Active)\n        {\n            if(CheckCollision(tempLocation, scene.Location))\n                scene.Active = false;\n        }\n    }\n\n    if(!Pth.Active && !Skp)\n    {\n        Pth.Active = true;\n\n        // set a temporary vScreen focus\n        vScreen[Z].tempX = Pth.Location.X + Pth.Location.Width / 2;\n        vScreen[Z].TempY = Pth.Location.Y + Pth.Location.Height / 2;\n        vScreen[Z].TempDelay = 1;\n\n        // update section (no cam sound)\n        s_worldCheckSection(WorldPlayer[1], Pth.Location);\n        g_worldPlayCamSound = false;\n\n        // force qScreen in modern mode\n        if(g_config.EnableInterLevelFade)\n        {\n            qScreen = true;\n            qScreenLoc[Z] = vScreen[Z];\n        }\n        // fully disable otherwise\n        else\n        {\n            qScreen = false;\n        }\n\n        PlaySound(SFX_NewPath);\n        PathWait();\n    }\n\n    Pth.Active = true;\n\n    for(B = 1; B <= 4; B++)\n    {\n        if(B == 1)\n            tempLocation.Y -= 32; // Up\n        else if(B == 2)\n        {\n            tempLocation.Y += 32; // Down\n            tempLocation.X -= 32; // Left\n        }\n        else if(B == 3)\n        {\n            tempLocation.X += 32; // Right\n            tempLocation.Y += 32; // Down\n        }\n        else if(B == 4)\n        {\n            tempLocation.Y -= 32; // Up\n            tempLocation.X += 32; // Right\n        }\n\n        //for(A = 1; A <= numWorldPaths; A++)\n        WorldPath_t* found = nullptr;\n        for(WorldPath_t *path : treeWorldPathQuery(tempLocation, SORTMODE_ID))\n        {\n            D_pLogDebug(\"Found path activity: %d\", (int)path->Active);\n            if(!path->Active)\n            {\n                D_pLogDebugNA(\"Collision with path...\");\n                if(CheckCollision(tempLocation, path->Location))\n                {\n                    D_pLogDebugNA(\"Collision with path FOUND...\");\n                    found = path;\n                    break;\n                }\n            }\n        }\n\n        if(found)\n            PathPath(*found, Skp);\n\n\n        //for(A = 1; A <= numWorldLevels; A++)\n        for(auto t : treeWorldLevelQuery(tempLocation, SORTMODE_ID))\n        {\n            WorldLevel_t &lev = *t;\n            D_pLogDebug(\"Found level activity: %d\", (int)lev.Active);\n            if(!lev.Active)\n            {\n                D_pLogDebugNA(\"Collision with level...\");\n                if(CheckCollision(tempLocation, lev.Location))\n                {\n                    D_pLogDebugNA(\"Collision with level FOUND...\");\n                    lev.Active = true;\n                    if(!Skp)\n                    {\n                        // set a temporary vScreen focus\n                        vScreen[Z].tempX = lev.Location.X + lev.Location.Width / 2;\n                        vScreen[Z].TempY = lev.Location.Y + lev.Location.Height / 2;\n                        vScreen[Z].TempDelay = 1;\n\n                        // update world map section (no cam sound)\n                        s_worldCheckSection(WorldPlayer[1], lev.Location);\n                        g_worldPlayCamSound = false;\n\n                        // force qScreen in modern mode\n                        if(g_config.EnableInterLevelFade)\n                        {\n                            qScreen = true;\n                            qScreenLoc[Z] = vScreen[Z];\n                        }\n                        // fully disable it otherwise\n                        else\n                        {\n                            qScreen = false;\n                        }\n\n                        PlaySound(SFX_NewPath);\n                        PathWait();\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid PathWait()\n{\n    int C = 0;\n\n    resetFrameTimer();\n\n    do\n    {\n        XEvents::doEvents();\n        if(canProceedFrame())\n        {\n            Controls::Update(false);\n            speedRun_tick();\n            UpdateGraphics2();\n            UpdateSound();\n            g_worldScreenFader.update();\n\n            if(delayMusicIsSet())\n                delayedMusicStart();\n\n            C++;\n            computeFrameTime1();\n            XEvents::doEvents();\n            computeFrameTime2();\n        }\n\n        if(!g_config.unlimited_framerate)\n            PGE_Delay(1);\n    } while(C < 24);\n\n    resetFrameTimer();\n}\n"
  },
  {
    "path": "src/main.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <ctime>\n#include \"sdl_proxy/sdl_head.h\"\n\n#include \"../version.h\"\n\n#include \"game_main.h\"\n#include \"frm_main.h\"\n#include \"gfx.h\"\n#include \"rand.h\"\n#include \"sound.h\"\n#include \"main/game_info.h\"\n#include \"main/speedrunner.h\"\n#include \"main/game_info.h\"\n#include \"main/asset_pack.h\"\n#include \"main/translate.h\"\n#include \"core/language.h\"\n#include \"config.h\"\n#include \"controls.h\"\n#include <AppPath/app_path.h>\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n#   include \"capabilities.h\"\n#endif\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n#   include \"main/client_methods.h\"\n#endif\n\n#ifndef THEXTECH_NO_ARGV_HANDLING\n#   include <tclap/CmdLine.h>\n#endif\n\n#include <Integrator/integrator.h>\n#include <Utils/strings.h>\n#include <Utils/files.h>\n#include <Archives/archives.h>\n\n#ifdef THEXTECH_CRASHHANDLER_SUPPORTED\n#   include <CrashHandler/crash_handler.h>\n#endif\n\n#if defined(_WIN32) || defined(_WIN64)\n#include <SDL2/SDL_main.h>\n#endif\n\n#ifdef VITA\n#include \"core/vita/vita_memory.h\"\n#endif\n\n#ifdef __16M__\n#include <nds.h>\n#endif\n\n#ifdef __CALICO__\n#define main _entry_point\n#endif\n\n#ifdef __WII__\n#include <gccore.h>\n#endif\n\n#ifdef __WIIU__\n#include <coreinit/title.h>\n#include <coreinit/systeminfo.h>\n#include <sysapp/launch.h>\n#   define HBL_TITLE_ID             (0x0005000013374842)\n#   define MII_MAKER_JPN_TITLE_ID   (0x000500101004A000)\n#   define MII_MAKER_USA_TITLE_ID   (0x000500101004A100)\n#   define MII_MAKER_EUR_TITLE_ID   (0x000500101004A200)\n#endif\n\n#ifdef __EMSCRIPTEN__\n#include <emscripten.h>\n#endif\n\n#ifdef ENABLE_XTECH_LUA\n#include \"xtech_lua_main.h\"\n#endif\n\n#ifdef __APPLE__\n#include <SDL2/SDL.h>\n#include <SDL2/SDL_events.h>\n#include <Utils/files.h>\n#include <Logger/logger.h>\n\nstatic std::string g_fileToOpen;\n/**\n * @brief Receive an opened file from the Finder (Must be created at least one window!)\n */\nstatic void macosReceiveOpenFile()\n{\n    if(g_fileToOpen.empty())\n    {\n        pLogDebug(\"Attempt to take Finder args...\");\n        SDL_EventState(SDL_DROPFILE, SDL_ENABLE);\n        SDL_Event event;\n        while(SDL_PollEvent(&event))\n        {\n            if(event.type == SDL_DROPFILE)\n            {\n                std::string file(event.drop.file);\n                if(Files::fileExists(file))\n                {\n                    g_fileToOpen = file;\n                    pLogDebug(\"Got file path: [%s]\", file.c_str());\n                }\n                else\n                    pLogWarning(\"Invalid file path, sent by Mac OS X Finder event: [%s]\", file.c_str());\n            }\n        }\n        SDL_EventState(SDL_DROPFILE, SDL_DISABLE);\n    }\n}\n#endif\n\n#ifdef __3DS__\n#include <3ds.h>\n#include <malloc.h>\nint n3ds_clocked = 0; // eventually move elsewhere\n\nvoid InitClockSpeed()\n{\n    bool isN3DS;\n\n    APT_CheckNew3DS(&isN3DS);\n\n    if(!isN3DS)\n        n3ds_clocked = -1;\n\n    // I've made this configurable.\n    // if(isN3DS) // make this configurable...\n    // {\n    //     SwapClockSpeed();\n    //     printf(\"N3DS clock enabled\\n\");\n    // }\n}\n\nvoid SwapClockSpeed()\n{\n    if(n3ds_clocked == -1)\n        return;\n\n    n3ds_clocked = !n3ds_clocked;\n    osSetSpeedupEnable(n3ds_clocked);\n}\n\n#endif\n\n#ifndef THEXTECH_NO_ARGV_HANDLING\nstatic void strToPlayerSetup(int player, const std::string &setupString)\n{\n    if(setupString.empty())\n        return; // Do nothing\n\n    std::vector<std::string> keys;\n\n    auto &p = testPlayer[player];\n    bool hasHealth = false;\n\n    Strings::split(keys, setupString, \";\");\n    for(auto &k : keys)\n    {\n        if(k.empty())\n            continue;\n        if(k[0] == 'c') // Character\n            p.Character = int(SDL_strtol(k.substr(1).c_str(), nullptr, 10));\n        else if(k[0] == 's') // State\n            p.State = int(SDL_strtol(k.substr(1).c_str(), nullptr, 10));\n        else if(k[0] == 'm') // Mounts\n            p.Mount = int(SDL_strtol(k.substr(1).c_str(), nullptr, 10));\n        else if(k[0] == 't') // Mount types\n            p.MountType = int(SDL_strtol(k.substr(1).c_str(), nullptr, 10));\n        else if(k[0] == 'h') // Health level\n        {\n            p.Hearts = int(SDL_strtol(k.substr(1).c_str(), nullptr, 10));\n            hasHealth = true;\n        }\n        else if(k[0] == 'r') // Reserved NPC-ID\n            p.HeldBonus = NPCID(SDL_strtol(k.substr(1).c_str(), nullptr, 10));\n    }\n\n    if(p.Character < 1)\n        p.Character = 1;\n    else if(p.Character > numCharacters)\n        p.Character = numCharacters;\n\n    if(p.State < 1)\n        p.State = 1;\n    else if(p.State >= numStates)\n        p.State = numStates;\n\n    if(p.HeldBonus > maxNPCType)\n        p.HeldBonus = NPCID_NULL;\n\n    if(hasHealth)\n    {\n        if(p.Hearts <= 1 && p.State == 2)\n            p.Hearts = 2;\n\n        if(p.Hearts > 3)\n            p.Hearts = 3;\n    }\n    else if(p.Character >= 3 && p.State > 2)\n        p.Hearts = 3; // Ensure the health is 3 when any state is bigger than 2 is assigned\n    else\n        p.Hearts = 0;\n\n\n    switch(p.Mount)\n    {\n    default:\n    case 0:\n        p.Mount = 0;\n        p.MountType = 0;\n        break; // Rejected aliens\n    case 1: case 2: case 3:\n        break; // Allowed\n    }\n\n    switch(p.Mount)\n    {\n    case 1:\n        if((p.MountType < 1) || (p.MountType > 3)) // Socks\n            p.MountType = 1;\n        break;\n    default:\n        break;\n    case 3:\n        if((p.MountType < 1) || (p.MountType > 8)) // Cat Llamas\n            p.MountType = 1;\n        break;\n    }\n}\n#endif\n\n#if defined(SDL_MAIN_NEEDED) || defined(__16M__)\nextern \"C\"\n#endif\nint main(int argc, char**argv)\n{\n#ifdef __16M__\n    // install the default exception handler\n    defaultExceptionHandler();\n\n    videoSetModeSub(MODE_0_2D);\n    vramSetBankH(VRAM_H_SUB_BG);\n    vramSetBankI(VRAM_I_SUB_BG_0x06208000);\n\n#ifdef __BLOCKS__\n    // give ourselves an extra 32kb of stack space -- thanks @AntonioND!\n    reduceHeapSize(0x8000);\n\n    const int DEFAULT_CONSOLE_MAP_BASE = 22;\n    const int DEFAULT_CONSOLE_GFX_BASE = 3;\n    const int DEFAULT_CONSOLE_BG_LAYER = 0;\n    consoleInit(nullptr, DEFAULT_CONSOLE_BG_LAYER, BgType_Text4bpp, BgSize_T_256x256, DEFAULT_CONSOLE_MAP_BASE, DEFAULT_CONSOLE_GFX_BASE, false, true);\n#else\n    const PrintConsole* defaultConsole = consoleGetDefault();\n    consoleInit(nullptr, defaultConsole->bgLayer, BgType_Text4bpp, BgSize_T_256x256, defaultConsole->mapBase, defaultConsole->gfxBase, false, true);\n#endif\n\n    printf(\"Hello, 16MB world!\\n\");\n#endif\n\n#ifdef __WII__\n    VIDEO_Init();\n    VIDEO_SetBlack(TRUE);\n#endif\n\n#ifdef __WIIU__\n    g_isHBLauncher = false;\n    uint64_t titleID = OSGetTitleID();\n\n    if(titleID == HBL_TITLE_ID ||\n       titleID == MII_MAKER_JPN_TITLE_ID ||\n       titleID == MII_MAKER_USA_TITLE_ID ||\n       titleID == MII_MAKER_EUR_TITLE_ID)\n    {\n        // Important: OSEnableHomeButtonMenu must be called before ProcUIInitEx.\n        OSEnableHomeButtonMenu(FALSE);\n        g_isHBLauncher = true;\n    }\n#endif\n\n    CmdLineSetup_t setup;\n\n#if defined(__APPLE__) && defined(USE_APPLE_X11)\n    char *x11_display_env = getenv(\"DISPLAY\");\n    if(!x11_display_env || x11_display_env[0] == '\\0')\n        setenv(\"DISPLAY\", \":0\", 1); // Automatically set the display to :0 if not defined\n#endif\n\n#ifdef THEXTECH_CRASHHANDLER_SUPPORTED\n    CrashHandler::initSigs();\n#endif\n\n#ifdef __3DS__\n    InitClockSpeed();\n    SwapClockSpeed();\n\n#   ifdef THEXTECH_ENABLE_SDL_NET\n    // spend 256kb on a buffer\n    uint32_t* socket_buffer = (uint32_t*)memalign(0x1000, 0x40000);\n    socInit(socket_buffer, 0x40000);\n#   endif\n#endif\n\n    seedRandom(std::time(NULL));\n\n    testPlayer.fill(Player_t());\n    for(int i = 1; i <= maxLocalPlayers; i++)\n    {\n        testPlayer[i].Character = i;\n    }\n\n#ifndef THEXTECH_NO_ARGV_HANDLING\n    try\n    {\n        // Define the command line object.\n        TCLAP::CmdLine  cmd(\"TheXTech Engine\\n\"\n                            \"Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\\n\\n\"\n                            \"This program is distributed under the GPLv3 license\\n\\n\", ' ', V_LATEST_STABLE \" [\" V_BUILD_BRANCH \", #\" V_BUILD_VER \"]\");\n\n        TCLAP::ValueArg<std::string> customAssetsPath(\"c\", \"asset-pack\", \"Specify the different assets pack name or directory to play\",\n                                                      false, \"\",\n                                                      \"string or directory path\",\n                                                      cmd);\n\n        TCLAP::ValueArg<std::string> customUserDirectory(\"u\", \"user-directory\", \"Specify the different writable user directory to store settings, gamesaves, logs, screenshots, etc.\",\n                                                         false, \"\",\n                                                         \"directory path\",\n                                                         cmd);\n\n        TCLAP::ValueArg<std::string> customGameDirName(std::string(), \"game-dirname\",\n                                                       \"Specify the game directory name for default locations (ignored if user-directory is specified)\",\n                                                       false, \"\",\n                                                       \"directory name\",\n                                                       cmd);\n\n\n        TCLAP::SwitchArg switchFrameSkip(\"f\", \"frameskip\", \"Enable frame skipping mode\", false);\n        TCLAP::SwitchArg switchDisableFrameSkip(std::string(), \"no-frameskip\", \"Disable frame skipping mode\", false);\n        TCLAP::SwitchArg switchNoSound(\"s\", \"no-sound\", \"Disable sound\", false);\n        TCLAP::SwitchArg switchNoPause(\"p\", \"never-pause\", \"Never pause game when window losts a focus\", false);\n        TCLAP::SwitchArg switchBgInput(std::string(), \"bg-input\", \"Allow background input for joysticks\", false);\n        TCLAP::SwitchArg switchVSync(std::string(), \"vsync\", \"Limit the framerate to the screen refresh rate\", false);\n        TCLAP::ValueArg<std::string> renderType(\"r\", \"render\", \"Sets the graphics mode:\\n\"\n                                                \"  sw - software SDL2 render (fallback)\\n\"\n                                                \"  hw - generic hardware accelerated render (currently SDL2) [Default]\\n\"\n                                                \"  vsync - generic hardware accelerated render with vSync [deprecated]\\n\"\n                                                \"  sdl - hardware accelerated SDL2 render\\n\"\n#   ifdef THEXTECH_BUILD_GL_DESKTOP_MODERN\n                                                \"  opengl - hardware accelerated OpenGL 2.1+ render\\n\"\n#   endif\n#   ifdef THEXTECH_BUILD_GL_ES_MODERN\n                                                \"  opengles - hardware accelerated OpenGL ES (mobile) 2.0+ render\\n\"\n#   endif\n#   ifdef THEXTECH_BUILD_GL_DESKTOP_LEGACY\n                                                \"  opengl11 - hardware accelerated OpenGL 1.1-2.0 render (legacy)\\n\"\n#   endif\n#   ifdef THEXTECH_BUILD_GL_ES_LEGACY\n                                                \"  opengles11 - hardware accelerated OpenGL ES 1.1 render (legacy)\"\n#   endif\n                                                ,\n                                                false, \"\",\n                                                \"render type\",\n                                                cmd);\n\n        TCLAP::ValueArg<std::string> testLevel(\"l\", \"leveltest\", \"Start a level test of a given level file.\\n\"\n                                                \"[OBSOLETE OPTION]: now you able to specify the file path without -l or --leveltest argument.\",\n                                                false, \"\",\n                                                \"file path\",\n                                                cmd);\n\n        TCLAP::ValueArg<unsigned int> numPlayers(\"n\", \"num-players\", \"Count of players\",\n                                                    false, 1u,\n                                                   \"number 1 or 2\",\n                                                   cmd);\n\n        TCLAP::ValueArg<unsigned int> startWarp(\"w\", \"start-warp\", \"Warp index to start level test at\",\n                                                    false, 0u,\n                                                   \"start warp index\",\n                                                   cmd);\n\n        TCLAP::SwitchArg switchBattleMode(\"b\", \"battle\", \"Test level in battle mode\", false);\n\n        TCLAP::ValueArg<std::string> playerCharacter1(\"1\",\n                                                      \"player1\",\n                                                      \"Setup of playable character for player 1:\\n\"\n                                                      \"  Semicolon separated key-argument values:\\n\"\n                                                      \"  c - character, s - state, m - mount, t - mount type, h - health, r - reserved NPC-ID.\\n\\n\"\n                                                      \"Example:\\n\"\n                                                      \"  c1;s2;m3;t0;r10;h2 - Character as 1, State as 2, Mount as 3, M.Type as 0\",\n                                                      false, \"\",\n                                                      \"c1;s2;m0;t0;r10;h2\",\n                                                      cmd);\n\n        TCLAP::ValueArg<std::string> playerCharacter2(\"2\",\n                                                      \"player2\",\n                                                      \"Setup of playable character for player 2:\\n\"\n                                                      \"  Semicolon separated key-argument values:\\n\"\n                                                      \"  c - character, s - state, m - mount, t - mount type.\\n\\n\"\n                                                      \"Example:\\n\"\n                                                      \"  c1;s2;m3;t0;r10;h2 - Character as 1, State as 2, Mount as 3, M.Type as 0\",\n                                                      false, \"\",\n                                                      \"c1;s2;m0;t0;r10;h2\",\n                                                      cmd);\n\n        TCLAP::SwitchArg switchTestGodMode(\"g\", \"god-mode\", \"Enable god mode in level testing\", false);\n        TCLAP::SwitchArg switchTestGrabAll(\"a\", \"grab-all\", \"Enable ability to grab everything while level testing\", false);\n        TCLAP::SwitchArg switchTestShowFPS(\"m\", \"show-fps\", \"Show FPS counter on the screen\", false);\n        TCLAP::SwitchArg switchTestMaxFPS(\"x\", \"max-fps\", \"Run FPS as fast as possible\", false);\n        TCLAP::SwitchArg switchTestMagicHand(\"k\", \"magic-hand\", \"Enable magic hand functionality while level test running\", false);\n        TCLAP::SwitchArg switchTestEditor(\"e\", \"editor\", \"Open level in the editor\", false);\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n        TCLAP::SwitchArg switchTestInterprocess(\"i\", \"interprocessing\", \"Enable an interprocessing mode with Editor\", false);\n        TCLAP::SwitchArg switchPrintCapabilities(std::string(), \"capabilities\", \"Print the JSON string of this build's capabilities\", false);\n#endif\n\n        TCLAP::ValueArg<std::string> compatLevel(std::string(), \"compat-level\",\n                                                   \"Enforce the specific gameplay compatibility level. Supported values:\\n\"\n                                                   \"  modern - TheXTech native, all features and fixes enabled [Default]\\n\"\n                                                   \"  classic - Disables all features and bugfixes except of crticial updates\\n\"\n                                                   \"  vanilla - Enforces the full compatibility with the SMBX 1.3 behaviour\\n\"\n                                                   \"\\n\"\n                                                   \"  Deprecated: acts as an alias for speed-run mode. Will be overridden if speed-run mode is set.\",\n                                                    false, \"modern\",\n                                                   \"modern, classic, vanilla\",\n                                                   cmd);\n        TCLAP::ValueArg<unsigned int> speedRunMode(std::string(), \"speed-run-mode\",\n                                                   \"Enable the speed-runer mode: the playthrough timer will be shown, \"\n                                                   \"and some gameplay limitations will be enabled. Supported values:\\n\"\n                                                   \"  0 - Disabled [Default]\\n\"\n                                                   \"  1 - TheXTech native\\n\"\n                                                   \"  2 - Disable time-winning updates (Classic mode)\\n\"\n                                                   \"  3 - Strict vanilla SMBX 1.3, enable all bugs\",\n                                                    false, 0u,\n                                                   \"0, 1, 2, or 3\",\n                                                   cmd);\n        TCLAP::SwitchArg switchSpeedRunSemiTransparent(std::string(), \"speed-run-semitransparent\",\n                                                       \"Make the speed-runner mode timer be drawn transparently\", false);\n        TCLAP::ValueArg<std::string> speedRunBlinkMode(std::string(), \"speed-run-blink-mode\",\n                                                   \"Choose the speed-run timer blinking effect for a level/episode completion\\n\"\n                                                   \"Supported values:\\n\"\n                                                   \"  opaque - Blink effect works when semi-transparent mode is not enabled\\n\"\n                                                   \"  always - Blink effect will work always\\n\"\n                                                   \"  never - Disable blink effect completely\",\n                                                    false, \"undefined\",\n                                                   \"opaque, always, never\",\n                                                    cmd);\n        TCLAP::SwitchArg switchDisplayControls(std::string(), \"show-controls\", \"Display current controller state while the game process\", false);\n        TCLAP::ValueArg<unsigned int> showBatteryStatus(std::string(), \"show-battery-status\",\n                                                   \"Display the battery status indicator (if available):\\n\"\n                                                   \"  0 - Never show [Default]\\n\"\n                                                   \"  1 - Show on fullscreen only when battery low\\n\"\n                                                   \"  2 - Show when battery low\\n\"\n                                                   \"  3 - Show always on fullscreen only\\n\"\n                                                   \"  4 - Always show\",\n                                                    false, 0u,\n                                                   \"0, 1, 2, 3, or 4\",\n                                                   cmd);\n        TCLAP::ValueArg<int> saveSlot(std::string(), \"save-slot\", \"Save slot to use for world play\", false, 0, std::string(\"number from 1 to \") + std::to_string(maxSaveSlots), cmd);\n\n#ifndef THEXTECH_DISABLE_LANG_TOOLS\n        TCLAP::SwitchArg switchMakeLangTemplate(std::string(), \"export-lang\", \"Exports the default language template\", false);\n        TCLAP::SwitchArg switchLangUpdate(std::string(), \"lang-update\", \"Updated all language of assets package: missing lines will be added\", false);\n        TCLAP::SwitchArg switchLangNoBlank(std::string(), \"lang-no-blank\", \"Don't put blank lines into translation files\", false);\n        TCLAP::ValueArg<std::string> langOutputPath(std::string(), \"lang-output\",\n                                                    \"Path to the languages directory that needs to be updated (by default, the in-assets directory is used)\",\n                                                    false, std::string(),\n                                                    \"path to directory\");\n#endif\n        TCLAP::ValueArg<std::string> lang(std::string(), \"lang\", \"Set the engine's language by code\", false, \"\", \"en, ru, zh-cn, etc.\");\n\n        TCLAP::SwitchArg switchVerboseLog(std::string(), \"verbose\", \"Enable log output into the terminal\", false);\n\n        TCLAP::UnlabeledMultiArg<std::string> inputFileNames(\"levelpath\", \"Path to level file or replay data to run the test\", false, std::string(), \"path to file\");\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n        TCLAP::ValueArg<std::string> server(std::string(), \"server\", \"Server address\", false, \"\", \"\");\n        cmd.add(&server);\n\n        TCLAP::ValueArg<std::string> room_key(std::string(), \"room-key\", \"Room key\", false, \"\", \"\");\n        cmd.add(&room_key);\n#endif\n\n        cmd.add(&switchFrameSkip);\n        cmd.add(&switchDisableFrameSkip);\n        cmd.add(&switchNoSound);\n        cmd.add(&switchNoPause);\n        cmd.add(&switchBgInput);\n        cmd.add(&switchVSync);\n        cmd.add(&switchBattleMode);\n\n        cmd.add(&switchTestGodMode);\n        cmd.add(&switchTestGrabAll);\n        cmd.add(&switchTestShowFPS);\n        cmd.add(&switchTestMaxFPS);\n        cmd.add(&switchTestMagicHand);\n        cmd.add(&switchTestEditor);\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n        cmd.add(&switchTestInterprocess);\n        cmd.add(&switchPrintCapabilities);\n#endif\n        cmd.add(&switchVerboseLog);\n        cmd.add(&switchSpeedRunSemiTransparent);\n        cmd.add(&switchDisplayControls);\n#ifndef THEXTECH_DISABLE_LANG_TOOLS\n        cmd.add(&switchMakeLangTemplate);\n        cmd.add(&switchLangUpdate);\n        cmd.add(&switchLangNoBlank);\n        cmd.add(&langOutputPath);\n#endif\n        cmd.add(&lang);\n        cmd.add(&inputFileNames);\n\n        cmd.parse(argc, argv);\n\n        // Initialize the assets and user paths\n        {\n            setup.assetPack = customAssetsPath.getValue();\n            std::string customUserDir = customUserDirectory.getValue();\n            std::string customGameDir = customGameDirName.getValue();\n\n            if(!setup.assetPack.empty())\n                AppPathManager::addAssetsRoot(setup.assetPack);\n\n            if(!customUserDir.empty())\n                AppPathManager::setUserDirectory(customUserDir);\n\n            if(!customGameDir.empty())\n                AppPathManager::setGameDirName(customGameDir);\n\n            AppPathManager::initAppPath();\n        }\n\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n        if(switchPrintCapabilities.isSet() && switchPrintCapabilities.getValue())\n        {\n            std::fprintf(stdout, \"%s\\n\", g_capabilities);\n            std::fflush(stdout);\n            return 0;\n        }\n#endif\n\n        OpenConfig();\n\n\n#ifndef THEXTECH_DISABLE_LANG_TOOLS\n        // Print the language template to the screen\n        if(switchMakeLangTemplate.isSet() && switchMakeLangTemplate.getValue())\n        {\n            // Locate asset pack. The `true` here indicates to skip actually loading UI GFX.\n            if(!InitUIAssetsFrom(setup.assetPack, true))\n                return 1;\n\n            printf(\"== Language template for [%s] ==\\n\", AppPath.c_str());\n\n            initGameInfo();\n\n            XTechTranslate translate;\n            translate.reset();\n            translate.exportTemplate();\n            return 0;\n        }\n\n        // Update all translation files at current assets pack\n        if(switchLangUpdate.isSet() && switchLangUpdate.getValue())\n        {\n            // Locate asset pack. The `true` here indicates to skip actually loading UI GFX.\n            if(!InitUIAssetsFrom(setup.assetPack, true))\n                return 1;\n\n            printf(\"== Updating language files at [%s] ==\\n\", AppPath.c_str());\n\n            initGameInfo();\n\n            XTechTranslate translate;\n            translate.reset();\n            translate.updateLanguages(langOutputPath.getValue(), switchLangNoBlank.isSet());\n            return 0;\n        }\n#endif\n\n        // Store all of the command-line config options\n        ConfigChangeSentinel sent(ConfigSetLevel::cmdline);\n\n        if(compatLevel.isSet() && !speedRunMode.isSet())\n        {\n            std::string compatModeVal = compatLevel.getValue();\n            if(compatModeVal == \"classic\" || compatModeVal == \"smbx2\")\n                g_config.speedrun_mode = 2;\n            else if(compatModeVal == \"smbx13\" || compatModeVal == \"vanilla\")\n                g_config.speedrun_mode = 3;\n            else if(compatModeVal == \"modern\")\n                g_config.speedrun_mode = 1;\n            else\n            {\n                std::cerr << \"Error: Invalid value for the --compat-level argument: \" << compatModeVal << std::endl;\n                std::cerr.flush();\n                return 2;\n            }\n        }\n\n        if(speedRunMode.isSet())\n            g_config.speedrun_mode = speedRunMode.getValue();\n\n        if(speedRunBlinkMode.isSet())\n        {\n            bool is_transparent = false;\n            if(switchSpeedRunSemiTransparent.isSet())\n                is_transparent = switchSpeedRunSemiTransparent.getValue();\n\n            std::string mode = speedRunBlinkMode.getValue();\n            if(mode == \"always\")\n                g_config.show_playtime_counter = Config_t::PLAYTIME_COUNTER_ANIMATED;\n            else if(is_transparent)\n                g_config.show_playtime_counter = Config_t::PLAYTIME_COUNTER_SUBTLE;\n            else if(mode == \"opaque\")\n                g_config.show_playtime_counter = Config_t::PLAYTIME_COUNTER_ANIMATED;\n            else if(mode == \"never\")\n                g_config.show_playtime_counter = Config_t::PLAYTIME_COUNTER_OPAQUE;\n            else\n            {\n                std::cerr << \"Error: Invalid value for the --speed-run-blink argument: \" << mode << std::endl;\n                std::cerr.flush();\n                return 2;\n            }\n        }\n        else if(switchSpeedRunSemiTransparent.isSet())\n        {\n            if(switchSpeedRunSemiTransparent.getValue())\n                g_config.show_playtime_counter = Config_t::PLAYTIME_COUNTER_SUBTLE;\n            else\n                g_config.show_playtime_counter = Config_t::PLAYTIME_COUNTER_ANIMATED;\n        }\n\n        if(switchTestShowFPS.isSet())\n            g_config.show_fps = switchTestShowFPS.getValue();\n\n        if(switchTestMaxFPS.isSet())\n            g_config.unlimited_framerate = switchTestMaxFPS.getValue();\n\n        if(switchDisplayControls.isSet())\n            g_config.show_controllers = switchDisplayControls.getValue();\n\n        if(showBatteryStatus.isSet() && IF_INRANGE(showBatteryStatus.getValue(), 1, 4))\n            g_config.show_battery_status = showBatteryStatus.getValue();\n\n        if(switchDisableFrameSkip.isSet())\n            g_config.enable_frameskip = false;\n        else if(switchFrameSkip.isSet())\n            g_config.enable_frameskip = true;\n\n#ifndef THEXTECH_NO_SDL_BUILD\n        if(switchNoSound.isSet())\n            g_config.audio_enable    = !switchNoSound.getValue();\n#endif\n\n        if(switchVSync.isSet())\n            g_config.render_vsync    = switchVSync.getValue();\n\n#ifndef NO_WINDOW_FOCUS_TRACKING\n        if(switchNoPause.isSet())\n            g_config.background_work = switchNoPause.getValue();\n\n        if(switchBgInput.isSet())\n            g_config.background_work = switchBgInput.getValue();\n#endif // NO_WINDOW_FOCUS_TRACKING\n\n#ifndef RENDER_CUSTOM\n        if(renderType.isSet())\n        {\n            std::string rt = renderType.getValue();\n            if(rt == \"sw\")\n                g_config.render_mode = Config_t::RENDER_SOFTWARE;\n            else if(rt == \"vsync\")\n            {\n                g_config.render_mode = Config_t::RENDER_ACCELERATED_AUTO;\n                g_config.render_vsync = true;\n            }\n            else if(rt == \"hw\")\n                g_config.render_mode = Config_t::RENDER_ACCELERATED_AUTO;\n            else if(rt == \"sdl\")\n                g_config.render_mode = Config_t::RENDER_ACCELERATED_SDL;\n            else if(rt == \"opengl\")\n                g_config.render_mode = Config_t::RENDER_ACCELERATED_OPENGL;\n            else if(rt == \"opengl11\")\n                g_config.render_mode = Config_t::RENDER_ACCELERATED_OPENGL_LEGACY;\n            else if(rt == \"opengles\")\n                g_config.render_mode = Config_t::RENDER_ACCELERATED_OPENGL_ES;\n            else if(rt == \"opengles11\")\n                g_config.render_mode = Config_t::RENDER_ACCELERATED_OPENGL_ES_LEGACY;\n            else\n            {\n                std::cerr << \"Error: Invalid value for the --render argument: \" << rt << std::endl;\n                std::cerr.flush();\n                return 2;\n            }\n#   ifdef DEBUG_BUILD\n            std::cerr << \"Manually selected renderer: \" << rt << \" - \" << g_config.render_mode << std::endl;\n            std::cerr.flush();\n#   endif\n        }\n#endif\n\n        // store the game setup options\n\n        setup.testLevel = testLevel.getValue();\n\n        if(inputFileNames.isSet())\n        {\n            for(const auto &fpath : inputFileNames.getValue())\n            {\n                if(Files::hasSuffix(fpath, \".lvl\") || Files::hasSuffix(fpath, \".lvlx\"))\n                    setup.testLevel = fpath;\n                else if(Files::hasSuffix(fpath, \".wld\") || Files::hasSuffix(fpath, \".wldx\"))\n                    setup.testLevel = fpath;\n                else if(Files::hasSuffix(fpath, \".rec\"))\n                    setup.testReplay = fpath;\n\n//                //TODO: Implement a world map running and testing\n//                else if(Files::hasSuffix(fpath, \".wld\") || Files::hasSuffix(fpath, \".wldx\"))\n//                {\n\n//                }\n            }\n        }\n\n        setup.verboseLogging = switchVerboseLog.getValue();\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n        setup.interprocess = switchTestInterprocess.getValue();\n#endif\n        setup.testLevelMode = !setup.testLevel.empty() || setup.interprocess;\n        setup.testNumPlayers = int(numPlayers.getValue());\n        if(setup.testNumPlayers > 2)\n            setup.testNumPlayers = 2;\n        setup.testBattleMode = switchBattleMode.getValue();\n        if(setup.testLevelMode)\n        {\n            strToPlayerSetup(1, playerCharacter1.getValue());\n            strToPlayerSetup(2, playerCharacter2.getValue());\n        }\n\n        setup.testGodMode = switchTestGodMode.getValue();\n        setup.testGrabAll = switchTestGrabAll.getValue();\n        setup.testMagicHand = switchTestMagicHand.getValue();\n        setup.testEditor = switchTestEditor.getValue();\n        setup.testSave = saveSlot.getValue();\n\n        if(startWarp.isSet() && startWarp.getValue() > 0 && startWarp.getValue() < maxWarps)\n            testStartWarp = startWarp.getValue();\n\n        if(lang.isSet())\n            g_config.language = lang;\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n        if(!setup.testLevel.empty() && server.isSet())\n        {\n            XMessage::Connect(server.getValue().c_str());\n\n            uint32_t room_key_int = XMessage::RoomFromString(room_key.getValue());\n            if(!room_key_int)\n                XMessage::JoinNewRoom(XMessage::RoomInfo{});\n            else\n                XMessage::JoinRoom(room_key_int);\n        }\n#endif\n    }\n    catch(TCLAP::ArgException &e)   // catch any exceptions\n    {\n        std::cerr << \"Error: \" << e.error() << \" for arg \" << e.argId() << std::endl;\n        std::cerr.flush();\n        return 2;\n    }\n#else\n    UNUSED(argc);\n    UNUSED(argv);\n\n    printf(\"Launching AppPath...\\n\");\n    AppPathManager::initAppPath();\n\n    OpenConfig();\n\n    setup.verboseLogging = true;\n#endif\n\n#if defined(__EMSCRIPTEN__) && defined(THEXTECH_DEBUG_INFO)\n    setup.verboseLogging = true;\n#endif\n\n#ifdef __16M__\n    // setup.testMaxFPS = true;\n    setCpuClock(true);\n#endif\n\n    UpdateConfig();\n\n    // set this flag before SDL initialization to allow game be quit when closing a window before a loading process will be completed\n    GameIsActive = true;\n\n    if(g_frmMain.initSystem(setup))\n    {\n        g_frmMain.freeSystem();\n        return 1;\n    }\n\n#ifdef __APPLE__\n    macosReceiveOpenFile();\n    if(!g_fileToOpen.empty())\n    {\n        setup.testLevel = g_fileToOpen;\n        setup.testLevelMode = !setup.testLevel.empty();\n        setup.testNumPlayers = 1;\n        setup.testGodMode = false;\n        setup.testGrabAll = false;\n    }\n#endif\n\n#ifdef ENABLE_XTECH_LUA\n    if(!xtech_lua_init())\n        return 1;\n#endif\n\n    Controls::Init();\n    Controls::LoadConfig();\n\n    int ret = GameMain(setup);\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n    XMessage::Shutdown();\n#endif\n\n    Integrator::quitIntegrations();\n\n#ifdef ENABLE_XTECH_LUA\n    if(!xtech_lua_quit())\n        ret = 1;\n#endif\n\n#ifdef __WIIU__\n    if(g_isHBLauncher)\n        SYSRelaunchTitle(0, NULL);\n#endif\n\n    Controls::Quit();\n    QuitMixerX();\n\n    g_frmMain.freeSystem();\n\n    Archives::unmount_assets();\n    Archives::unmount_episode();\n    Archives::unmount_temp();\n\n#ifdef __EMSCRIPTEN__\n    AppPathManager::syncFs();\n    EM_ASM(\n        setTimeout(() => {\n            console.log(\"Attempting to close window following game exit...\");\n            document.getElementById(\"canvas\").style.display = 'none';\n            document.getElementById(\"exit-msg\").style.display = null;\n            window.close();\n        }, 250);\n    );\n#endif\n\n    return ret;\n}\n"
  },
  {
    "path": "src/message.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <deque>\n\n#include \"controls.h\"\n#include \"message.h\"\n#include \"globals.h\"\n\n#include \"graphics.h\"\n#include \"player.h\"\n#include \"main/cheat_code.h\"\n#include \"main/screen_pause.h\"\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n#   include \"main/client_methods.h\"\n#endif\n\nnamespace XMessage\n{\n\nstatic std::deque<Message> s_message_vector;\nSession g_session;\n\nstatic Controls_t s_last_controls[maxNetplayPlayers + 1];\n\nvoid Handle(const Message& m)\n{\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // server control messages\n    if(m.screen == 255)\n    {\n        if(m.type == Type::add_client)\n        {\n            Screen_t& screen = Screens[m.message];\n\n            // add player if there isn't any\n            if(screen.player_count == 0)\n                AddPlayer((m.message % 5) + 1, screen);\n\n            SetupScreens();\n        }\n        else if(m.type == Type::drop_client)\n        {\n            Screen_t& screen = Screens[m.message];\n\n            // reset screen parameters\n            screen.W = 800;\n            screen.H = 600;\n            screen.CameraOverscanX = 0;\n            screen.two_screen_pref = MultiplayerPrefs::Dynamic;\n            screen.four_screen_pref = MultiplayerPrefs::Shared;\n            screen.canonical_screen().two_screen_pref = screen.two_screen_pref;\n            screen.canonical_screen().four_screen_pref = screen.four_screen_pref;\n\n            // drop players other than the last player\n            for(int p = screen.player_count - 1; p >= 0; p--)\n            {\n                // reassign last player to Screen 0\n                if(numPlayers == 1)\n                {\n                    Screens_DropPlayer(1);\n                    Screens_AssignPlayer(1, Screens[0]);\n                }\n                else\n                    DropPlayer(screen.players[p]);\n            }\n        }\n\n    }\n#endif // #ifdef THEXTECH_ENABLE_SDL_NET\n\n    if(m.screen >= maxNetplayClients)\n        return;\n\n    Screen_t& screen = Screens[m.screen];\n\n    if(m.type == Type::press || m.type == Type::release)\n    {\n        if(m.player >= maxLocalPlayers || m.message >= Controls::PlayerControls::n_buttons)\n            return;\n\n        auto& controls = s_last_controls[screen.players[m.player]];\n        bool& button = Controls::PlayerControls::GetButton(controls, m.message);\n        bool is_press = (m.type == Type::press);\n\n        button = is_press;\n    }\n    else if(m.type == Type::char_swap)\n    {\n        if(m.player >= screen.player_count || m.message < 1 || m.message > numCharacters || !SwapCharAllowed())\n            return;\n\n        SwapCharacter(screen.players[m.player], m.message);\n\n        if(LevelSelect && !GameMenu)\n            SetupPlayers();\n    }\n    else if(m.type == Type::add_player || m.type == Type::add_player_dead)\n    {\n        if(screen.player_count >= maxLocalPlayers || m.message < 1 || m.message > numCharacters)\n            return;\n\n        // after AddPlayer, numPlayers is always the new player\n        AddPlayer(m.message, screen);\n\n        // set the player to be dead if needed\n        if(m.type == Type::add_player_dead)\n        {\n            Player[numPlayers].Dead = true;\n\n            // initialize ghost logic for player\n            int living = CheckNearestLiving(numPlayers);\n            if(living)\n            {\n                Player[numPlayers].Effect2 = -living;\n                Player[numPlayers].Location.X = Player[living].Location.X;\n                Player[numPlayers].Location.Y = Player[living].Location.Y;\n                Player[numPlayers].Section    = Player[living].Section;\n            }\n            else\n                Player[numPlayers].Effect2 = 0;\n        }\n    }\n    else if(m.type == Type::drop_player)\n    {\n        if(m.player >= screen.player_count)\n            return;\n\n        DropPlayer(screen.players[m.player]);\n    }\n    else if(m.type == Type::menu_action)\n    {\n        PauseScreen::g_pending_action = m.message;\n    }\n    else if(m.type == Type::shared_controls)\n    {\n        if(m.message == 0)\n            SharedPause = true;\n        else if(m.message == 1)\n            SharedPauseLegacy = true;\n        else if(m.message == 2)\n            SharedPauseForce = true;\n    }\n    else if(m.type == Type::enter_code)\n        run_cheat(m);\n    else if(m.type == Type::screen_w)\n    {\n        screen.W = m.player * 256 + m.message;\n    }\n    else if(m.type == Type::screen_h)\n    {\n        screen.H = m.player * 256 + m.message;\n    }\n    else if(m.type == Type::camera_overscan_x)\n    {\n        screen.CameraOverscanX = m.message;\n    }\n    else if(m.type == Type::multiplayer_prefs)\n    {\n        int two_screen_pref = m.player;\n        int four_screen_pref = m.message;\n\n        if(two_screen_pref > MultiplayerPrefs::Max_2P)\n            two_screen_pref = 0;\n\n        if(four_screen_pref > MultiplayerPrefs::Max_4P)\n            four_screen_pref = 0;\n\n        screen.two_screen_pref = two_screen_pref;\n        screen.four_screen_pref = four_screen_pref;\n\n        screen.canonical_screen().two_screen_pref = two_screen_pref;\n        screen.canonical_screen().four_screen_pref = four_screen_pref;\n\n        SetupScreens();\n        PlayersEnsureNearby(screen);\n    }\n}\n\nvoid InitSession()\n{\n    for(int A = 0; A <= maxNetplayPlayers; A++)\n        s_last_controls[A] = Controls_t();\n}\n\nvoid Tick()\n{\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // sync state with other clients here\n    if(CurrentRoom())\n        ClientFrameSync(s_message_vector);\n#endif\n\n    // update player controls based on message queue\n    Message m;\n    while((m = PopMessage()))\n        Handle(m);\n\n    int numPlayers_p = numPlayers;\n\n    // fix a bug affecting main menu dead mode\n    if(GameMenu || GameOutro)\n        numPlayers_p = maxLocalPlayers;\n\n    for(int A = 1; A <= numPlayers_p && A <= maxNetplayPlayers; A++)\n        Player[A].Controls = s_last_controls[A];\n}\n\nvoid PushMessage_Direct(Message message)\n{\n    s_message_vector.push_back(message);\n}\n\nvoid PushMessage(Message message)\n{\n    message.screen = l_screen - &Screens[0];\n    PushMessage_Direct(message);\n}\n\nMessage PopMessage()\n{\n    Message ret;\n    if(s_message_vector.empty())\n        return ret;\n\n    ret = s_message_vector.front();\n    s_message_vector.pop_front();\n    return ret;\n}\n\nvoid PushControls(int l_player_i, const Controls_t& controls)\n{\n    Controls_t& last_controls = Controls::g_RawControls[l_player_i];\n\n    Message m;\n    m.screen = l_screen - &Screens[0];\n    m.player = l_player_i;\n\n    for(uint8_t i = 0; i < Controls::PlayerControls::n_buttons; i++)\n    {\n        bool new_pressed = Controls::PlayerControls::GetButton(controls, i);\n        bool old_pressed = Controls::PlayerControls::GetButton(last_controls, i);\n\n        if(new_pressed && !old_pressed)\n        {\n            m.type = Type::press;\n            m.message = i;\n            PushMessage_Direct(m);\n        }\n        else if(old_pressed && !new_pressed)\n        {\n            m.type = Type::release;\n            m.message = i;\n            PushMessage_Direct(m);\n        }\n    }\n\n    last_controls = controls;\n}\n\n} // namespace XMessage\n"
  },
  {
    "path": "src/message.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef XMESSAGE_H\n#define XMESSAGE_H\n\n#include <cstdint>\n\nstruct Controls_t;\n\n\n// The purpose of these methods is to track the set of player-initiated events that may affect gameplay\nnamespace XMessage\n{\n\nenum class Status : uint8_t\n{\n    local,\n    replay,\n    connected,\n};\n\nstruct Session\n{\n    uint32_t random_seed = 0;\n};\n\nenum class Type : uint8_t\n{\n    empty,\n    press,\n    release,\n    menu_action,\n    char_swap,\n    add_player,\n    add_player_dead,\n    drop_player,\n    shared_controls,\n    enter_code,\n    screen_w,\n    screen_h,\n    multiplayer_prefs,\n    camera_overscan_x,\n    frame_begin = 32, // meta-message: the following messages belong to the named frame\n#ifdef THEXTECH_ENABLE_SDL_NET\n    // special server control messages\n    add_client = 33,\n    drop_client = 34,\n    frame_end = 35, // meta-message: closes the previously named frame (only used by TCP)\n    transmit_start = 36, // meta-message: acknowledges client messages up to named frame (only used by UDP)\n#endif // #ifdef THEXTECH_ENABLE_SDL_NET\n};\n\nstruct Message\n{\n    Type type = Type::empty;\n    uint8_t screen;\n    uint8_t player;\n    uint8_t message;\n\n    inline Message() : screen(0), player(0) {}\n    inline Message(Type type, uint8_t player, uint8_t message) : type(type), screen(0), player(player), message(message) {}\n    operator bool() const\n    {\n        return type != Type::empty;\n    }\n};\n\nextern Session g_session;\n\nvoid InitSession();\nvoid Tick();\n\nvoid PushMessage_Direct(Message message);\nvoid PushMessage(Message message);\nMessage PopMessage();\n\nvoid PushControls(int l_player_i, const Controls_t& controls);\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n\n// defined in client_methods.cpp\nStatus GetStatus();\n\n#else // #ifdef THEXTECH_ENABLE_SDL_NET\n\nstatic inline Status GetStatus()\n{\n    return Status::local;\n}\n\n#endif // #ifdef THEXTECH_ENABLE_SDL_NET\n\n} // namespace XMessage\n\n#endif // #ifndef XMESSAGE_H\n"
  },
  {
    "path": "src/npc/npc_activation.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <bitset>\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"globals.h\"\n#include \"layers.h\"\n#include \"config.h\"\n#include \"collision.h\"\n\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n#include \"npc_queues.h\"\n\n#include \"main/trees.h\"\n\ninline static bool s_Event_SoundOnly(const Events_t& evt, int test_section)\n{\n    if(!(evt.Text == STRINGINDEX_NONE\n        && evt.HideLayer.empty()\n        && evt.ShowLayer.empty()\n        && evt.ToggleLayer.empty()\n        && evt.MoveLayer == LAYER_NONE\n        && evt.TriggerEvent == EVENT_NONE\n        && evt.EndGame == 0))\n    {\n        return false;\n    }\n\n    bool autoscroll_okay = !AutoUseModern || evt.AutoSection != test_section\n        || (g_config.fix_autoscroll_speed\n            ? (!(evt.AutoX != 0 || evt.AutoY != 0)\n                || (AutoX[evt.AutoSection] == evt.AutoX && AutoY[evt.AutoSection] == evt.AutoY))\n            : (!IF_INRANGE(evt.AutoSection, 0, SDL_min(maxSections, maxEvents))\n                || (AutoX[evt.AutoSection] == Events[evt.AutoSection].AutoX\n                    && AutoY[evt.AutoSection] == Events[evt.AutoSection].AutoY)));\n\n    if(!autoscroll_okay)\n        return false;\n\n    const EventSection_t& s = const_cast<Events_t&>(evt).section[test_section];\n    bool section_okay = s.music_id == EventSection_t::LESet_Nothing\n        && s.background_id == EventSection_t::LESet_Nothing\n        && s.position.X == EventSection_t::LESet_Nothing\n        && s.autoscroll == false;\n\n    return section_okay;\n}\n\n// from section of UpdateNPCs called \"process chain activations\"\nstatic bool s_NPC_CanActivate(const NPC_t& n)\n{\n    return n.Type != NPCID_CONVEYOR && n.Type != NPCID_FALL_BLOCK_RED &&\n        n.Type != NPCID_FALL_BLOCK_BROWN && !n->IsACoin;\n}\n\nstatic bool s_NPC_MustBeCanonical_internal(const NPC_t& n)\n{\n    if(!s_NPC_CanActivate(n))\n        return n.Generator;\n\n    return n.Generator\n        || n->UseDefaultCam\n        || (n->IsFish && n.Special == 2)\n        || n.AttLayer != LAYER_NONE\n        || (n.Legacy && (n.Type == NPCID_MINIBOSS || n.Type == NPCID_SPIT_BOSS || n.Type == NPCID_VILLAIN_S3))\n        || (n.TriggerActivate != EVENT_NONE && !s_Event_SoundOnly(Events[n.TriggerActivate], n.Section));\n}\n\nbool NPC_MustBeCanonical(NPCRef_t n)\n{\n    return n->_priv_force_canonical;\n}\n\nbool NPC_InactiveIgnore(const NPC_t& n)\n{\n    return (n->IsFish && n.Special == 2)\n        || n->InactiveRender == NPCTraits_t::SKIP\n        || n._priv_self_hide_event;\n}\n\nbool NPC_InactiveRender(const NPC_t& n)\n{\n    if(n->InactiveRender == NPCTraits_t::SMOKE)\n        return false;\n\n    if(n.DefaultType == 0)\n        return false;\n\n    return n.Inert\n        || n.Stuck\n        || n->InactiveRender == NPCTraits_t::SHOW_ALWAYS\n        || n->InactiveRender == NPCTraits_t::SHOW_STATIC;\n}\n\nbool NPC_InactiveSmoke(const NPC_t& n)\n{\n    return n->InactiveRender == NPCTraits_t::SMOKE;\n}\n\nvoid NPC_ConstructCanonicalSet()\n{\n    std::vector<int16_t> to_check;\n    to_check.reserve(64);\n\n    for(int16_t n = 1; n <= numNPCs; n++)\n    {\n        if(s_NPC_MustBeCanonical_internal(NPC[n]) || NPC[n]._priv_force_canonical)\n        {\n            NPC[n]._priv_force_canonical = true;\n            to_check.push_back(n);\n        }\n\n        // check if NPC has a hide self event\n        if(NPC[n].TriggerActivate != EVENT_NONE && NPC[n].Layer != LAYER_NONE && NPC[n].Layer != LAYER_DEFAULT)\n        {\n            const Events_t& e = Events[NPC[n].TriggerActivate];\n\n            for(layerindex_t l : e.HideLayer)\n            {\n                if(l == NPC[n].Layer)\n                {\n                    NPC[n]._priv_self_hide_event = true;\n                    break;\n                }\n            }\n        }\n    }\n\n    // find all NPCs that could cause the above NPCs to activate via chain activation\n    for(size_t i = 0; i < to_check.size(); i++)\n    {\n        int16_t n = to_check[i];\n\n        Location_t tempLocation = NPC[n].Location;\n        tempLocation.Y -= 32;\n        tempLocation.X -= 32;\n        tempLocation.Width += 64;\n        tempLocation.Height += 64;\n\n        bool n_is_generator = NPC[n].Generator;\n\n        for(int16_t B : treeNPCQuery(tempLocation, SORTMODE_NONE))\n        {\n            if(B != n && CheckCollision(tempLocation, NPC[B].Location))\n            {\n                // check that B is capable of chain-activation (or that n is a generator, in which case B would block it)\n                if(!n_is_generator && !s_NPC_CanActivate(NPC[B]))\n                    continue;\n\n                if(!NPC[B]._priv_force_canonical)\n                {\n                    NPC[B]._priv_force_canonical = true;\n                    to_check.push_back(B);\n                }\n            }\n        }\n    }\n}\n\nvoid NPCActivationLogic(int A)\n{\n    if(NPC[A].Active)\n    {\n        if((NPC[A]->IsFish && NPC[A].Special == 2) || NPC[A].Type == NPCID_BULLET || NPC[A].Type == NPCID_BIG_BULLET || NPC[A].Type == NPCID_GHOST_FAST) // Special Start for Jumping Fish and Bullet Bills\n        {\n            if(NPC[A].TimeLeft <= 1)\n            {\n                NPC[A].Active = false;\n                NPC[A].TimeLeft = 0;\n            }\n            else if(NPC[A].Direction == -1 && NPC[A].Location.X < Player[NPC[A].JustActivated].Location.X)\n            {\n                NPC[A].Active = false;\n                NPC[A].TimeLeft = 0;\n            }\n            else if(NPC[A].Direction == 1 && NPC[A].Location.X > Player[NPC[A].JustActivated].Location.X)\n            {\n                NPC[A].Active = false;\n                NPC[A].TimeLeft = 0;\n            }\n            else if(NPC[A]->IsFish && NPC[A].Special == 2)\n            {\n                NPC[A].Location.Y = level[Player[NPC[A].JustActivated].Section].Height - 0.1_n;\n                NPC[A].Location.SpeedX = (1 + (NPC[A].Location.Y - NPC[A].DefaultLocationY) / 200) * NPC[A].Direction;\n                NPC[A].Special5 = 1;\n                treeNPCUpdate(A);\n                if(NPC[A].tempBlock > 0)\n                    treeNPCSplitTempBlock(A);\n            }\n            else if(NPC[A].Type != NPCID_GHOST_FAST && !FreezeNPCs)\n                PlaySoundSpatial(SFX_Bullet, NPC[A].Location);\n        }\n        else if(NPC[A].Type == NPCID_GOALTAPE)\n        {\n            Location_t tempLocation = NPC[A].Location;\n            tempLocation.Height = 8000;\n            int C = 0;\n            for(int B : treeBlockQuery(tempLocation, SORTMODE_COMPAT))\n            {\n                if(CheckCollision(tempLocation, Block[B].Location))\n                {\n                    if(C == 0)\n                        C = B;\n                    else\n                    {\n                        if(Block[B].Location.Y < Block[C].Location.Y)\n                            C = B;\n                    }\n                }\n            }\n\n            if(C > 0)\n            {\n                // save location of ground below goal tape (was previously Special2)\n                NPC[A].SpecialY = Block[C].Location.Y + 4;\n                NPC[A].Location.Y = Block[C].Location.Y - NPC[A].Location.Height;\n                NPC[A].Special = 1;\n\n                treeNPCUpdate(A);\n                if(NPC[A].tempBlock > 0)\n                    treeNPCSplitTempBlock(A);\n            }\n        }\n        else if(NPC[A].Type == NPCID_LAVA_MONSTER) // blaarg\n        {\n            NPC[A].Location.Y = NPC[A].DefaultLocationY + NPC[A].Location.Height + 36;\n            treeNPCUpdate(A);\n            if(NPC[A].tempBlock > 0)\n                treeNPCSplitTempBlock(A);\n        }\n        else if(NPC[A].Type == NPCID_CANNONENEMY)\n            NPC[A].Special = 100;\n    }\n\n    // NOTE: these were not guarded by the Active check above in SMBX 1.3\n    // they can't be safely moved into it because the iRand call has side effects\n    if(NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_CANNONITEM)\n        NPC[A].Projectile = false;\n    else if(NPC[A].Type == NPCID_STATUE_S3 || NPC[A].Type == NPCID_STATUE_S4)\n        NPC[A].Special = iRand(200);\n    // new, clip wings from buried items\n    else if(NPC[A].Type == NPCID_ITEM_BURIED)\n        NPC[A].Wings = WING_NONE;\n\n    NPC[A].JustActivated = 0;\n    NPC[A].CantHurt = 0;\n    NPC[A].CantHurtPlayer = 0;\n\n    // in addition to removing cancelled NPCs from the Active list,\n    // this also allows us to exclude Vines from the set of active NPCs\n    // (without checking their status every frame if they do need to be active)\n    if(!NPCQueues::check_active(NPC[A]))\n        NPCQueues::Active.erase(A);\n}\n"
  },
  {
    "path": "src/npc/npc_activation.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef NPC_ACTIVATION_H\n#define NPC_ACTIVATION_H\n\n#include \"globals.h\"\n\nbool NPC_MustBeCanonical(NPCRef_t n);\n\n// four possible render outcomes for an NPC: ignore (hide with no effect), render normally, hide with smoke effect, or shade (the default)\nbool NPC_InactiveIgnore(const NPC_t& n);\n\nbool NPC_InactiveRender(const NPC_t& n);\n\nbool NPC_InactiveSmoke(const NPC_t& n);\n\nvoid NPC_ConstructCanonicalSet();\n\n#endif // #ifndef NPC_ACTIVATION_H\n"
  },
  {
    "path": "src/npc/npc_bonus.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../globals.h\"\n#include \"../npc.h\"\n#include \"../npc_id.h\"\n#include \"../eff_id.h\"\n#include \"../sound.h\"\n#include \"../collision.h\"\n#include \"../effect.h\"\n#include \"../graphics.h\"\n#include \"../player.h\"\n#include \"../game_main.h\"\n#include \"../core/events.h\"\n#include \"main/game_globals.h\"\n#include \"../config.h\"\n#include \"../layers.h\"\n\n#include \"npc_traits.h\"\n\n#include \"main/level_medals.h\"\n\n#include \"npc/npc_queues.h\"\n\n#include \"../controls.h\"\n\n#include <Logger/logger.h>\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n#   include <InterProcess/intproc.h>\n#endif\n\n\nstatic void s_PowerupScore(NPCRef_t n)\n{\n    if(g_config.custom_powerup_collect_score)\n        MoreScore((*n)->Score, n->Location);\n    else\n        MoreScore(6, n->Location);\n}\n\n\ninline void RumbleForPowerup(int A)\n{\n    Controls::Rumble(A, 200, 0.25);\n}\n\nvoid DropBonus(int A)\n{\n    const Player_t& plr = Player[A];\n\n    // does player not have a bonus?\n    if(plr.HeldBonus <= 0)\n        return;\n\n    // is player a clone?\n    if(g_ClonedPlayerMode && A != 1)\n    {\n        Player[A].HeldBonus = NPCID(0);\n        return;\n    }\n\n    // is player not meant to have a bonus?\n    if(Player[A].Character == 3 || Player[A].Character == 4)\n    {\n        Player[A].HeldBonus = NPCID(0);\n        return;\n    }\n\n    if(g_config.alt_powerdown)\n        return;\n\n    // drop the bonus!\n    PlaySoundSpatial(SFX_DropItem, plr.Location);\n    numNPCs++;\n    NPC[numNPCs].Type = Player[A].HeldBonus;\n    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n    NPC[numNPCs].Location.Height = 32;\n\n    // need to find a position to place the bonus -- look for ths HUD\n    const Screen_t& screen = ScreenByPlayer(A);\n    bool is_shared = (screen.player_count > 1) && (screen.Type != 6) && (screen.active_end() == screen.active_begin() + 1);\n\n    // HUD offset for player\n    int hud_offset = 0;\n\n    if(is_shared && screen.player_count > 2)\n    {\n        int hud_width = 84 * screen.player_count;\n        for(int i = 0; i < screen.player_count; i++)\n        {\n            if(A == screen.players[i])\n            {\n                // find center for player item box; this is the center of the i'th portion of the HUD (out of plr_count portions)\n                hud_offset = (hud_width * (i * 2 + 1)) / (screen.player_count * 2) - hud_width / 2;\n                break;\n            }\n        }\n    }\n    else if(is_shared)\n    {\n        if(A == screen.players[0])\n            hud_offset = -40;\n        if(A == screen.players[1])\n            hud_offset = 40;\n    }\n\n    // update the vScreen for the player\n    vScreen_t& vscreen = vScreenByPlayer(A);\n    GetvScreenAuto(vscreen);\n\n    // also update canonical vScreen if needed\n    if(!screen.is_canonical())\n        GetvScreenAuto(vScreenByPlayer_canonical(A));\n\n    // find the HUD\n    num_t ScreenTop = -vscreen.Y;\n\n    if(vscreen.Height > 600)\n        ScreenTop += vscreen.Height / 2 - 300;\n\n    num_t CenterX = -vscreen.X + vscreen.Width / 2;\n\n    // place NPC at HUD\n    NPC[numNPCs].Location.X = CenterX + hud_offset - NPC[numNPCs].Location.Width / 2;\n    NPC[numNPCs].Location.Y = ScreenTop + 16 + 12;\n\n    // finish initializing the NPC\n    NPC[numNPCs].Location.SpeedX = 0;\n    NPC[numNPCs].Location.SpeedY = 0;\n    NPC[numNPCs].Effect = NPCEFF_DROP_ITEM;\n    NPC[numNPCs].Effect2 = 1;\n    NPC[numNPCs].Active = true;\n    NPC[numNPCs].TimeLeft = 200;\n    syncLayers_NPC(numNPCs);\n    CheckSectionNPC(numNPCs);\n\n    // enable modern NPC spawn code\n    bool small_screen_cam = (g_config.small_screen_cam && screen.W < screen.canonical_screen().W);\n    bool always_shared = (is_shared && screen.Type == ScreenTypes::SharedScreen);\n    bool multi_screen = (numPlayers > screen.player_count) && !g_ClonedPlayerMode;\n    if(!ForcedControls && (always_shared || small_screen_cam || multi_screen))\n    {\n        NPC[numNPCs].Special5 = 120;\n        NPC[numNPCs].Effect3 = A;\n        NPC[numNPCs].Effect2 = 1;\n    }\n\n    // erase bonus\n    Player[A].HeldBonus = NPCID(0);\n}\n\nvoid CheckAfterStarTake(bool many)\n{\n    int allBGOs = numBackground + numLocked;\n    for(int c = 1; c <= numWarps; c++)\n    {\n        auto &w = Warp[c];\n        if((!many && (w.Stars == numStars)) || (many && (w.Stars <= numStars)))\n        {\n            for(int d = numBackground + 1; d <= allBGOs; d++)\n            {\n                auto &b = Background[d];\n                if(b.Type == 160 &&\n                    (CheckCollision(w.Entrance, b.Location) ||\n                     (w.twoWay && CheckCollision(w.Exit, b.Location)))\n                )\n                {\n                    // this makes the background permanently disappear\n                    b.Layer = LAYER_NONE;\n                    b.Hidden = true;\n                    syncLayers_BGO(d);\n                }\n            }\n        }\n    }\n}\n\nstatic void s_MovePlayersToExit(int got_exit_A)\n{\n    const auto& p_A = Player[got_exit_A];\n\n    for(int C = 1; C <= numPlayers; C++)\n    {\n        if(got_exit_A == C) // And DScreenType <> 5 Then\n            continue;\n\n        auto& p_C = Player[C];\n\n        p_C.Section = p_A.Section;\n        p_C.Location.Y = p_A.Location.Y + p_A.Location.Height - p_C.Location.Height;\n        p_C.Location.X = p_A.Location.X + (p_A.Location.Width - p_C.Location.Width) / 2;\n        p_C.Location.SpeedX = 0;\n        p_C.Location.SpeedY = 0;\n        p_C.Effect = PLREFF_WAITING;\n        p_C.Effect2 = -got_exit_A;\n    }\n\n    clearScreenFaders();\n}\n\nvoid CollectMedal(const NPC_t& medal)\n{\n    PlaySoundSpatial(SFX_MedalGet, medal.Location);\n\n    auto& medal_score = NPCTraits[medal.Type].Score;\n    MoreScore(medal_score, medal.Location);\n\n    medal_score += 1;\n    if(medal_score > 14)\n        medal_score = 14;\n\n    if(!GameMenu && !GameOutro)\n        g_curLevelMedals.get(medal.Variant - 1);\n}\n\nvoid TouchBonus(int A, int B)\n{\n    // INCORRECT NOTE: the only way to reach this code in SMBX 1.3 when Player[A].Effect is not PLREFF_NORMAL is if NPC[B]->IsACoin is true and NPC[B] is on Char4's boomerang\n    // NOTE: this is not true. It can also be reached if two bonuses are touched in the same frame.\n\n    // ban collecting dropped item during first stage of modern item drop process\n    if(NPC[B].Effect == NPCEFF_DROP_ITEM && NPC[B].Effect3 != 0)\n        return;\n\n    // don't get just-thrown items, except coins\n    if(NPC[B].CantHurtPlayer == A && !(NPC[B]->IsACoin && Player[A].HoldingNPC != B && NPC[B].Killed == 0))\n        return;\n\n    int sfx_extra_item     = (Player[A].Character == 5) ? SFX_HeroHeart : SFX_GotItem;\n    int sfx_grow_item      = (Player[A].Character == 5) ? SFX_HeroItem  : SFX_PlayerGrow;\n    int sfx_transform_item = (Player[A].Character == 5) ? SFX_HeroItem  : SFX_Transform;\n\n    // moved up here so that the hearts logic works\n    NPCID civilian_type = NPCID(0);\n\n    if(NPCIsToad(NPC[B]))\n    {\n        civilian_type = NPC[B].Type;\n        NPC[B].Type = NPCID_POWER_S3;\n    }\n\n    // give hearts to heart chars\n    if(Player[A].Character == 3 || Player[A].Character == 4 || Player[A].Character == 5)\n    {\n        // only the first three types were checked in SMBX 1.3, the others were moved here for simplicity\n        if(NPC[B].Type == NPCID_LEAF_POWER || NPC[B].Type == NPCID_STATUE_POWER || NPC[B].Type == NPCID_HEAVY_POWER\n            || NPC[B].Type == NPCID_POWER_S3 || NPC[B].Type == NPCID_POWER_S1 || NPC[B].Type == NPCID_POWER_S4 || NPC[B].Type == NPCID_POWER_S2 || NPC[B].Type == NPCID_POWER_S5\n            || NPC[B].Type == NPCID_FIRE_POWER_S3 || NPC[B].Type == NPCID_FIRE_POWER_S1 || NPC[B].Type == NPCID_FIRE_POWER_S4\n            || NPC[B].Type == NPCID_ICE_POWER_S3 || NPC[B].Type == NPCID_ICE_POWER_S4\n            || NPC[B].Type == NPCID_AQUATIC_POWER || NPC[B].Type == NPCID_POLAR_POWER\n            || NPC[B].Type == NPCID_CYCLONE_POWER || NPC[B].Type == NPCID_SHELL_POWER)\n        {\n            Player[A].Hearts += 1;\n\n            if(Player[A].Hearts > 3)\n                Player[A].Hearts = 3;\n        }\n    }\n\n    // If .Character = 3 Or .Character = 4 Then  'for chars 3/4 turn leaf and suits into a mushroom\n    // If NPC(B).Type = 34 Or NPC(B).Type = 169 Or NPC(B).Type = 170 Then NPC(B).Type = 9\n    // End If\n\n    if(NPC[B].Type == NPCID_SWAP_POWER) // ? mushroom\n    {\n        int touched_power_i = A;\n        int target_i = CheckNearestLiving(A);\n\n        // NOTE: this previously used hardcoded 1 and 2; now it targets the living player nearest A, prioritizing local players\n        if(g_ClonedPlayerMode)\n        {\n            touched_power_i = 1;\n            target_i = 2;\n        }\n\n        Player_t& p_touched = Player[touched_power_i];\n        Player_t& p_target = Player[target_i];\n\n        if(target_i != 0\n            && !p_touched.Dead && p_touched.TimeToLive == 0 && p_touched.Immune == 0\n            && !p_target.Dead && p_target.TimeToLive == 0 && p_target.Immune == 0)\n        {\n            // tempLocation = p_touched.Location;\n\n            // swap location\n            num_t touched_X = p_touched.Location.X;\n            num_t touched_Y = p_touched.Location.Y;\n            p_touched.Location.X = p_target.Location.X + (p_target.Location.Width  - p_touched.Location.Width) / 2;\n            p_touched.Location.Y = p_target.Location.Y + p_target.Location.Height  - p_touched.Location.Height;\n            p_target.Location.X  = touched_X           + (p_touched.Location.Width - p_target.Location.Width) / 2;\n            p_target.Location.Y  = touched_Y           + p_touched.Location.Height - p_target.Location.Height;\n\n            // swap some variables\n            std::swap(p_touched.Direction, p_target.Direction);\n            std::swap(p_touched.Slope, p_target.Slope);\n            std::swap(p_touched.StandingOnNPC, p_target.StandingOnNPC);\n\n            // FIXME: verify whether warps should also be swapped...\n\n            // BUGFIX: swap sections; fixes vanilla bug where both players could be killed / trapped\n            if(g_config.fix_multiplayer_targeting)\n            {\n                CheckSection(touched_power_i);\n                CheckSection(target_i);\n            }\n\n            // NEW: swap status for maze zones\n            std::swap(p_touched.CurMazeZone, p_target.CurMazeZone);\n            std::swap(p_touched.MazeZoneStatus, p_target.MazeZoneStatus);\n\n            // make players immune\n            if(p_touched.Immune < 10)\n                p_touched.Immune = 10;\n            if(p_target.Immune < 10)\n                p_target.Immune = 10;\n\n            // play sound in both locations (so both players will hear if remote)\n            int old_pause = SoundPause[SFX_BossBeat];\n            PlaySoundSpatial(SFX_BossBeat, p_touched.Location);\n            SoundPause[SFX_BossBeat] = old_pause;\n            PlaySoundSpatial(SFX_BossBeat, p_target.Location);\n        }\n    }\n    else if(NPC[B].Type == NPCID_FLY_POWER) // Player is a fairy\n    {\n        // don't kill the powerup if it can't be used!\n        if(Player[A].Mount == 2)\n            return;\n\n        if(!Player[A].Fairy)\n        {\n            Player[A].Immune = 30;\n            Player[A].Effect = PLREFF_WAITING;\n            Player[A].Effect2 = 4;\n            Player[A].Fairy = true;\n            SizeCheck(Player[A]);\n            NewEffect(EFFID_SMOKE_S5, Player[A].Location);\n        }\n\n        PlaySoundSpatial(SFX_HeroFairy, NPC[B].Location);\n        Player[A].FairyTime = -1;\n    }\n    else if(NPC[B].Type == NPCID_LIFE_S3 || NPC[B].Type == NPCID_LIFE_S4 || NPC[B].Type == NPCID_LIFE_S1) // player touched a 1up mushroom\n    {\n        MoreScore(10, NPC[B].Location);\n    }\n    else if(NPC[B].Type == NPCID_TIMER_S3 && NPC[B].Effect != NPCEFF_DROP_ITEM && (Player[A].Character == 1 || Player[A].Character == 2)) // send the clock to the item container\n    {\n        Player[A].HeldBonus = NPCID_TIMER_S3;\n        PlaySoundSpatial(SFX_GotItem, NPC[B].Location);\n    }\n    else if(NPC[B].Type == NPCID_TIMER_S2 || NPC[B].Type == NPCID_TIMER_S3) // player touched the clock\n    {\n        PSwitchStop = Physics.NPCPSwitch;\n        FreezeNPCs = true;\n        PSwitchPlayer = A;\n    }\n    else if(NPC[B].Type == NPCID_CHECKPOINT) // player touched the chekpoint\n    {\n        RumbleForPowerup(A);\n\n        if(Player[A].State == 1)\n            Player[A].State = 2;\n        if(Player[A].Hearts == 1)\n            Player[A].Hearts = 2;\n        SizeCheck(Player[A]);\n\n        PlaySoundSpatial(SFX_Checkpoint, NPC[B].Location);\n\n        Checkpoint = FullFileName;\n        Checkpoint_t cp;\n        cp.id = NPC[B].Special;\n        CheckpointsList.push_back(cp);\n        g_curLevelMedals.on_checkpoint();\n        pLogDebug(\"Added checkpoint ID %d\", cp.id);\n    }\n    else if(NPC[B].Type == NPCID_3_LIFE) // player touched the 3up moon\n    {\n        MoreScore(12, NPC[B].Location);\n    }\n    else if(NPC[B].Type == NPCID_AXE)\n    {\n        // go straight to code to kill NPC\n    }\n    else if(NPC[B].Type == NPCID_FLAG_EXIT)\n    {\n        // do nothing, we just want the other traits of bonus\n        return;\n    }\n    else if(NPC[B].Type == NPCID_POISON) // Bonus is a POISON mushroom\n    {\n        PlayerHurt(A);\n    }\n    else if(NPC[B].Type == NPCID_INVINCIBILITY_POWER)\n    {\n        PSwitchPlayer = A;\n        InvincibilityTime = Physics.NPCPSwitch;\n    }\n    else if(NPC[B].Type == NPCID_POWER_S3 || NPC[B].Type == NPCID_POWER_S1 || NPC[B].Type == NPCID_POWER_S4 || NPC[B].Type == NPCID_POWER_S2 || NPC[B].Type == NPCID_POWER_S5) // Bonus is a mushroom\n    {\n        if(Player[A].Character == 5 && Player[A].State == 1)\n            Player[A].State = 2;\n\n        UpdatePlayerBonus(A, NPC[B].Type);\n\n        if(Player[A].State == 1 && Player[A].Character != 5)\n        {\n            RumbleForPowerup(A);\n\n            // UnDuck checks Duck internally, so let's call it directly\n            // if(Player[A].Duck)\n            //     UnDuck(Player[A]);\n\n            int BackupGrabTime = Player[A].GrabTime;\n\n            // force UnDuck to work, fixes a vanilla downwards clip while ducking\n            if(g_config.fix_player_grab_clip && Player[A].Character >= 3)\n                Player[A].GrabTime = 0;\n\n            UnDuck(Player[A]);\n\n            Player[A].GrabTime = BackupGrabTime;\n\n            Player[A].StateNPC = NPC[B].Type;\n            Player[A].Frame = 1;\n            Player[A].Effect = PLREFF_TURN_BIG;\n\n            // Duck already unset above\n            // if(Player[A].Mount > 0)\n            //     UnDuck(Player[A]);\n\n            PlaySoundSpatial(SFX_PlayerGrow, NPC[B].Location);\n        }\n        else if(NPC[B].Type == NPCID_POWER_S5)\n            PlaySoundSpatial(SFX_HeroHeart, NPC[B].Location);\n        else\n            PlaySoundSpatial(SFX_GotItem, NPC[B].Location);\n\n        if(NPC[B].Effect != NPCEFF_DROP_ITEM)\n            s_PowerupScore(B);\n    }\n    // logic combined across all powerup NPCs\n    else if(NPC[B].Type == NPCID_FIRE_POWER_S3 || NPC[B].Type == NPCID_FIRE_POWER_S1 || NPC[B].Type == NPCID_FIRE_POWER_S4\n        || NPC[B].Type == NPCID_ICE_POWER_S3 || NPC[B].Type == NPCID_ICE_POWER_S4\n        || NPC[B].Type == NPCID_LEAF_POWER || NPC[B].Type == NPCID_STATUE_POWER || NPC[B].Type == NPCID_HEAVY_POWER\n        || NPC[B].Type == NPCID_AQUATIC_POWER || NPC[B].Type == NPCID_POLAR_POWER\n        || NPC[B].Type == NPCID_CYCLONE_POWER || NPC[B].Type == NPCID_SHELL_POWER)\n    {\n        int target_state = PLR_STATE_BIG;\n        PlayerEffect target_effect = PLREFF_TURN_TO_STATE;\n        bool reset_effect2 = true;\n        int use_sfx = sfx_transform_item;\n\n        if(NPC[B].Type == NPCID_FIRE_POWER_S3 || NPC[B].Type == NPCID_FIRE_POWER_S1 || NPC[B].Type == NPCID_FIRE_POWER_S4)\n        {\n            target_state = PLR_STATE_FIRE;\n            target_effect = PLREFF_GROW_TO_STATE;\n            reset_effect2 = false;\n            use_sfx = sfx_grow_item;\n        }\n        else if(NPC[B].Type == NPCID_ICE_POWER_S3 || NPC[B].Type == NPCID_ICE_POWER_S4)\n        {\n            target_state = PLR_STATE_ICE;\n            target_effect = PLREFF_GROW_TO_STATE;\n            reset_effect2 = false;\n            use_sfx = sfx_grow_item;\n        }\n        else if(NPC[B].Type == NPCID_CYCLONE_POWER)\n        {\n            target_state = PLR_STATE_CYCLONE;\n            target_effect = PLREFF_GROW_TO_STATE;\n            reset_effect2 = false;\n            use_sfx = sfx_grow_item;\n\n            // award a double-jump!\n            if(Player[A].State != PLR_STATE_CYCLONE)\n                Player[A].DoubleJump = true;\n        }\n        else if(NPC[B].Type == NPCID_LEAF_POWER)\n            target_state = PLR_STATE_LEAF;\n        else if(NPC[B].Type == NPCID_STATUE_POWER)\n            target_state = PLR_STATE_STATUE;\n        else if(NPC[B].Type == NPCID_HEAVY_POWER)\n            target_state = PLR_STATE_HEAVY;\n        else if(NPC[B].Type == NPCID_AQUATIC_POWER)\n            target_state = PLR_STATE_AQUATIC;\n        else if(NPC[B].Type == NPCID_POLAR_POWER)\n            target_state = PLR_STATE_POLAR;\n        else if(NPC[B].Type == NPCID_SHELL_POWER)\n            target_state = PLR_STATE_SHELL;\n\n        UpdatePlayerBonus(A, NPC[B].Type);\n        Player[A].StateNPC = NPC[B].Type;\n\n        if(Player[A].State != target_state)\n        {\n            RumbleForPowerup(A);\n\n            // fixes a vanilla downwards clip that happens even if the player is just ducking (not even digging)\n            if(g_config.fix_player_grab_clip && Player[A].State == 1 && Player[A].Character >= 3 && reset_effect2)\n            {\n                int BackupGrabTime = Player[A].GrabTime;\n                Player[A].GrabTime = 0;\n                UnDuck(Player[A]);\n                Player[A].GrabTime = BackupGrabTime;\n            }\n\n            if(!Player[A].AquaticSwim && !Player[A].Rolling)\n                Player[A].Frame = 1;\n\n            Player[A].Effect = (PlayerEffect)(target_effect + target_state);\n\n            if(reset_effect2)\n                Player[A].Effect2 = 0;\n\n            if(Player[A].Mount > 0 || (Player[A].AquaticSwim && target_state != PLR_STATE_AQUATIC && target_state != PLR_STATE_POLAR) || (Player[A].Rolling && target_state != PLR_STATE_SHELL && target_state != PLR_STATE_POLAR))\n            {\n                Player[A].AquaticSwim = false;\n                Player[A].Rolling = false;\n\n                UnDuck(Player[A]);\n\n                if(Player[A].Mount == 0)\n                    PlayerFrame(A);\n            }\n\n            PlaySoundSpatial(use_sfx, NPC[B].Location);\n        }\n        else\n            PlaySoundSpatial(sfx_extra_item, NPC[B].Location);\n\n        if(NPC[B].Effect != NPCEFF_DROP_ITEM)\n            s_PowerupScore(B);\n    }\n#if 0\n    else if(NPC[B].Type == NPCID_FIRE_POWER_S3 || NPC[B].Type == NPCID_FIRE_POWER_S1 || NPC[B].Type == NPCID_FIRE_POWER_S4) // Bonus is a fire flower\n    {\n        UpdatePlayerBonus(A, NPC[B].Type);\n        Player[A].StateNPC = NPC[B].Type;\n\n        if(Player[A].State != 3)\n        {\n            RumbleForPowerup(A);\n            Player[A].Frame = 1;\n            Player[A].Effect = PLREFF_TURN_FIRE;\n            if(Player[A].Mount > 0)\n                UnDuck(Player[A]);\n\n            PlaySoundSpatial(sfx_grow_item, NPC[B].Location);\n        }\n        else\n            PlaySoundSpatial(sfx_extra_item, NPC[B].Location);\n\n        if(NPC[B].Effect != NPCEFF_DROP_ITEM)\n            s_PowerupScore(B);\n    }\n    else if(NPC[B].Type == NPCID_ICE_POWER_S3 || NPC[B].Type == NPCID_ICE_POWER_S4) // Bonus is an ice flower\n    {\n        UpdatePlayerBonus(A, NPC[B].Type);\n        Player[A].StateNPC = NPC[B].Type;\n\n        if(Player[A].State != 7)\n        {\n            RumbleForPowerup(A);\n            Player[A].Frame = 1;\n            Player[A].Effect = PLREFF_TURN_ICE;\n            if(Player[A].Mount > 0)\n                UnDuck(Player[A]);\n\n            PlaySoundSpatial(sfx_grow_item, NPC[B].Location);\n        }\n        else\n            PlaySoundSpatial(sfx_extra_item, NPC[B].Location);\n\n        if(NPC[B].Effect != NPCEFF_DROP_ITEM)\n            s_PowerupScore(B);\n    }\n    else if(NPC[B].Type == NPCID_LEAF_POWER) // Bonus is a leaf\n    {\n        UpdatePlayerBonus(A, NPC[B].Type);\n        Player[A].StateNPC = NPC[B].Type;\n\n        if(Player[A].State != 4)\n        {\n            RumbleForPowerup(A);\n            Player[A].Frame = 1;\n            Player[A].Effect = PLREFF_TURN_LEAF;\n            Player[A].Effect2 = 0;\n            if(Player[A].Mount > 0)\n                UnDuck(Player[A]);\n\n            PlaySoundSpatial(sfx_transform_item, NPC[B].Location);\n        }\n        else\n            PlaySoundSpatial(sfx_extra_item, NPC[B].Location);\n\n        if(NPC[B].Effect != NPCEFF_DROP_ITEM)\n            s_PowerupScore(B);\n    }\n    else if(NPC[B].Type == NPCID_STATUE_POWER) // Bonus is a Tanooki Suit\n    {\n        UpdatePlayerBonus(A, NPC[B].Type);\n        Player[A].StateNPC = NPC[B].Type;\n\n        if(Player[A].State != 5)\n        {\n            RumbleForPowerup(A);\n            Player[A].Frame = 1;\n            Player[A].Effect = PLREFF_TURN_STATUE;\n            Player[A].Effect2 = 0;\n            if(Player[A].Mount > 0)\n                UnDuck(Player[A]);\n\n            PlaySoundSpatial(sfx_transform_item, NPC[B].Location);\n        }\n        else\n            PlaySoundSpatial(sfx_extra_item, NPC[B].Location);\n\n        if(NPC[B].Effect != NPCEFF_DROP_ITEM)\n            s_PowerupScore(B);\n    }\n    else if(NPC[B].Type == NPCID_HEAVY_POWER) // Bonus is a Hammer Suit\n    {\n        UpdatePlayerBonus(A, NPC[B].Type);\n        Player[A].StateNPC = NPC[B].Type;\n\n        if(Player[A].State != 6)\n        {\n            RumbleForPowerup(A);\n            Player[A].Frame = 1;\n            Player[A].Effect = PLREFF_TURN_HEAVY;\n            Player[A].Effect2 = 0;\n            if(Player[A].Mount > 0)\n                UnDuck(Player[A]);\n\n            PlaySoundSpatial(sfx_transform_item, NPC[B].Location);\n        }\n        else\n            PlaySoundSpatial(sfx_extra_item, NPC[B].Location);\n\n        if(NPC[B].Effect != NPCEFF_DROP_ITEM)\n            s_PowerupScore(B);\n    }\n#endif\n    else if(NPC[B]->IsACoin) // Bonus is a coin\n    {\n        if(NPC[B].Type == NPCID_RING)\n            PlaySoundSpatial(SFX_RingGet, NPC[B].Location);\n        else if(NPC[B].Type == NPCID_GEM_1 || NPC[B].Type == NPCID_GEM_5 || NPC[B].Type == NPCID_GEM_20)\n            PlaySoundSpatial(SFX_HeroRupee, NPC[B].Location);\n        else if(NPC[B].Type != NPCID_MEDAL)\n            PlaySoundSpatial(SFX_Coin, NPC[B].Location);\n\n        if(NPC[B].Type == NPCID_GEM_5 || NPC[B].Type == NPCID_COIN_5)\n            Coins += 5;\n        else if(NPC[B].Type == NPCID_GEM_20)\n            Coins += 20;\n        else\n            Coins += 1;\n\n        if(Coins >= 100)\n            Got100Coins();\n\n        if(NPC[B].Type == NPCID_MEDAL)\n            CollectMedal(NPC[B]);\n        else\n            MoreScore(1, NPC[B].Location);\n\n        NewEffect(EFFID_COIN_COLLECT, NPC[B].Location);\n    }\n    else if(NPCIsAnExit(NPC[B]) && LevelMacro == LEVELMACRO_OFF) // Level exit\n    {\n        if(NPC[B].Type != NPCID_STAR_COLLECT)\n        {\n            TurnNPCsIntoCoins();\n            FreezeNPCs = false;\n            if(g_ClonedPlayerMode)\n                Player[1] = Player[A];\n        }\n\n        if(NPC[B].Type == NPCID_ITEMGOAL)\n        {\n            if(NPC[B].Frame == 0)\n                MoreScore(10, Player[A].Location);\n            if(NPC[B].Frame == 1)\n                MoreScore(6, Player[A].Location);\n            if(NPC[B].Frame == 2)\n                MoreScore(8, Player[A].Location);\n\n            LevelMacro = LEVELMACRO_CARD_ROULETTE_EXIT;\n\n            s_MovePlayersToExit(A);\n\n            StopMusic();\n            // XEvents::doEvents();\n            PlaySound(SFX_CardRouletteClear);\n        }\n        else if(NPC[B].Type == NPCID_GOALORB_S3)\n        {\n            LevelMacro = LEVELMACRO_QUESTION_SPHERE_EXIT;\n\n            s_MovePlayersToExit(A);\n\n            StopMusic();\n            PlaySound(SFX_DungeonClear);\n        }\n        else if(NPC[B].Type == NPCID_GOALORB_S2)\n        {\n            LevelMacro = LEVELMACRO_CRYSTAL_BALL_EXIT;\n\n            s_MovePlayersToExit(A);\n\n            StopMusic();\n            PlaySound(SFX_CrystalBallExit);\n        }\n        else if(NPC[B].Type == NPCID_STAR_EXIT || NPC[B].Type == NPCID_STAR_COLLECT)\n        {\n            bool star_gotten = false;\n\n            for(const auto& star : Star)\n            {\n                bool bySection = NPC[B].Variant == 0 && (star.Section == NPC[B].Section || star.Section == -1);\n                bool byId = NPC[B].Variant > 0 && -(star.Section + 100) == int(NPC[B].Variant);\n\n                if(star.level == FileNameFull && (bySection || byId))\n                    star_gotten = true;\n            }\n\n            if(!star_gotten)\n            {\n                Star_t star;\n                star.level = FileNameFull;\n                // Positive - section number, Negative - UID of each star per level\n                int special = (int)NPC[B].Variant;\n                star.Section = special <= 0 ? NPC[B].Section : -special - 100;\n                if(special > 0)\n                    pLogDebug(\"Got a star with UID=%d\", special);\n                Star.push_back(std::move(star));\n                numStars = (int)Star.size();\n#ifdef THEXTECH_INTERPROC_SUPPORTED\n                IntProc::sendStarsNumber(numStars);\n#endif\n                CheckAfterStarTake(false);\n            }\n\n            if(NPC[B].Type == NPCID_STAR_EXIT)\n            {\n                LevelMacro = LEVELMACRO_STAR_EXIT;\n\n                s_MovePlayersToExit(A);\n\n                StopMusic();\n                PlaySound(SFX_GotStar);\n            }\n            else\n                PlaySoundSpatial(SFX_MedalGet, NPC[B].Location);\n        }\n    }\n\n    if(civilian_type > 0)\n        NPC[B].Type = civilian_type;\n\n    NPC[B].Killed = 9;\n    NPCQueues::Killed.push_back(B);\n}\n"
  },
  {
    "path": "src/npc/npc_cockpit_bits.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef NPC_COCKPIT_BITS_H\n#define NPC_COCKPIT_BITS_H\n\nenum NPC_CockpitBits\n{\n    NPC_COCKPIT_DRIVING = (1 << 0),\n    NPC_COCKPIT_LEFT = (1 << 1),\n    NPC_COCKPIT_RIGHT = (1 << 2),\n    NPC_COCKPIT_UP = (1 << 3),\n    NPC_COCKPIT_DOWN = (1 << 4),\n};\n\n#endif // #ifndef NPC_COCKPIT_BITS_H\n"
  },
  {
    "path": "src/npc/npc_frames.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"npc.h\"\n#include \"sound.h\"\n#include \"collision.h\"\n#include \"effect.h\"\n#include \"npc_id.h\"\n#include \"eff_id.h\"\n#include \"npc_traits.h\"\n\n#include \"main/trees.h\"\n\nstatic void s_makeHeavySparkle(const NPC_t& n, int offY)\n{\n    NewEffect(EFFID_SPARKLE, newLoc(n.Location.X + n.Location.Width / 2 - 4, n.Location.Y + n.Location.Height / 2 - offY), 1, n.Shadow);\n    Effect[numEffects].Location.SpeedX = dRand() - 0.5_n;\n    Effect[numEffects].Location.SpeedY = dRand() - 0.5_n;\n}\n\n\nvoid NPCFrames(int A)\n{\n    if(NPC[A]->TFrames > 0) // custom frames\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A]->FrameStyle == 2 && (NPC[A].Projectile || NPC[A].HoldingPlayer > 0))\n            NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= NPC[A]->FrameSpeed)\n        {\n            if(NPC[A]->FrameStyle == 0)\n                NPC[A].Frame += 1 * NPC[A].Direction;\n            else\n                NPC[A].Frame += 1;\n            NPC[A].FrameCount = 0;\n        }\n        if(NPC[A]->FrameStyle == 0)\n        {\n            if(NPC[A].Frame >= NPC[A]->TFrames)\n                NPC[A].Frame = 0;\n            if(NPC[A].Frame < 0)\n                NPC[A].Frame = NPC[A]->TFrames - 1;\n        }\n        else if(NPC[A]->FrameStyle == 1)\n        {\n            if(NPC[A].Direction == -1)\n            {\n                if(NPC[A].Frame >= NPC[A]->TFrames)\n                    NPC[A].Frame = 0;\n                if(NPC[A].Frame < 0)\n                    NPC[A].Frame = NPC[A]->TFrames;\n            }\n            else\n            {\n                if(NPC[A].Frame >= NPC[A]->TFrames * 2)\n                    NPC[A].Frame = NPC[A]->TFrames;\n                if(NPC[A].Frame < NPC[A]->TFrames)\n                    NPC[A].Frame = NPC[A]->TFrames;\n            }\n        }\n        else if(NPC[A]->FrameStyle == 2)\n        {\n            if(NPC[A].HoldingPlayer == 0 && !NPC[A].Projectile)\n            {\n                if(NPC[A].Direction == -1)\n                {\n                    if(NPC[A].Frame >= NPC[A]->TFrames)\n                        NPC[A].Frame = 0;\n                    if(NPC[A].Frame < 0)\n                        NPC[A].Frame = NPC[A]->TFrames - 1;\n                }\n                else\n                {\n                    if(NPC[A].Frame >= NPC[A]->TFrames * 2)\n                        NPC[A].Frame = NPC[A]->TFrames;\n                    if(NPC[A].Frame < NPC[A]->TFrames)\n                        NPC[A].Frame = NPC[A]->TFrames * 2 - 1;\n                }\n            }\n            else\n            {\n                if(NPC[A].Direction == -1)\n                {\n                    if(NPC[A].Frame >= NPC[A]->TFrames * 3)\n                        NPC[A].Frame = NPC[A]->TFrames * 2;\n                    if(NPC[A].Frame < NPC[A]->TFrames * 2)\n                        NPC[A].Frame = NPC[A]->TFrames * 3 - 1;\n                }\n                else\n                {\n                    if(NPC[A].Frame >= NPC[A]->TFrames * 4)\n                        NPC[A].Frame = NPC[A]->TFrames * 3;\n                    if(NPC[A].Frame < NPC[A]->TFrames * 3)\n                        NPC[A].Frame = NPC[A]->TFrames * 4 - 1;\n                }\n            }\n        }\n    }\n    // massive conditional over NPC's Type\n    else if(NPC[A].Type == NPCID_VILLAIN_S3 || NPC[A].Type == NPCID_ITEM_THROWER || NPC[A].Type == NPCID_SPIKY_THROWER)\n    {\n        // do nothing\n    }\n    else if(NPC[A].Type == NPCID_SQUID_S3 || NPC[A].Type == NPCID_SQUID_S1 || NPC[A].Type == NPCID_SPIT_BOSS_BALL ||\n            NPC[A].Type == NPCID_FALL_BLOCK_RED || NPC[A].Type == NPCID_FALL_BLOCK_BROWN ||\n            NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT ||\n            NPC[A].Type == NPCID_VPIPE_LONG || NPC[A].Type == NPCID_BIG_SHELL || NPCIsVeggie(NPC[A].Type) || NPC[A].Type == NPCID_SHORT_WOOD ||\n            NPC[A].Type == NPCID_LONG_WOOD || NPC[A].Type == NPCID_SLANT_WOOD_R || NPC[A].Type == NPCID_SLANT_WOOD_M || NPC[A].Type == NPCID_PLATFORM_S3 ||\n            NPC[A].Type == NPCID_CHECKER_PLATFORM || NPC[A].Type == NPCID_PLATFORM_S1 || NPC[A].Type == NPCID_SPIT_GUY_BALL || NPC[A].Type == NPCID_SIGN ||\n            (NPC[A].Type >= NPCID_CARRY_BLOCK_A && NPC[A].Type <= NPCID_CARRY_BLOCK_D) || NPC[A].Type == NPCID_LIFT_SAND || NPC[A].Type == NPCID_CHECKPOINT ||\n            NPC[A].Type == NPCID_GOALTAPE || NPC[A].Type == NPCID_ICE_BLOCK || NPC[A].Type == NPCID_TNT ||\n            NPC[A].Type == NPCID_TIMER_S2 || NPC[A].Type == NPCID_POWER_S5 || NPC[A].Type == NPCID_MAGIC_DOOR || NPC[A].Type == NPCID_COCKPIT ||\n            NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_COIN_SWITCH) // no frames\n    {\n        if(A == 0) // Reset Frame to 0\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A].Type == NPCID_STATUE_POWER || NPC[A].Type == NPCID_HEAVY_POWER)\n    {\n        int new_frame = 0;\n\n        num_t min_dist = 0;\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            if(!Player[B].Dead && Player[B].Section == NPC[A].Section && Player[B].TimeToLive == 0)\n            {\n                auto L1_dist = num_t::abs(NPC[A].Location.minus_center_x(Player[B].Location)) + num_t::abs(NPC[A].Location.minus_center_y(Player[B].Location));\n                if(min_dist == 0 || L1_dist < min_dist)\n                {\n                    min_dist = L1_dist;\n\n                    if(Player[B].Character == 5)\n                        new_frame = 1;\n                    else\n                        new_frame = 0;\n                }\n            }\n        }\n\n        if(new_frame != NPC[A].Frame)\n        {\n            if(NPC[A].FrameCount > 0)\n                NewEffect(EFFID_SMOKE_S4, NPC[A].Location);\n\n            NPC[A].Frame = new_frame;\n        }\n\n        NPC[A].FrameCount = 1;\n    }\n    else if(NPC[A].Type == NPCID_FLY_BLOCK || NPC[A].Type == NPCID_FLY_CANNON) // fly block\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].HoldingPlayer > 0)\n            NPC[A].FrameCount += 1;\n        if(NPC[A].Location.SpeedY != 0)\n            NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount <= 6)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount <= 12)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount <= 18)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount <= 24)\n            NPC[A].Frame = 3;\n        else if(NPC[A].FrameCount <= 30)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount <= 36)\n            NPC[A].Frame = 1;\n        else\n        {\n            NPC[A].Frame = 0;\n            NPC[A].FrameCount = 0;\n        }\n        if(NPC[A].Type == NPCID_FLY_CANNON && NPC[A].Direction == 1)\n            NPC[A].Frame += 4;\n    }\n    else if(NPC[A].Type == NPCID_QUAD_SPITTER) // fire plant thing\n    {\n        if(NPC[A].Special == 0)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 0;\n            else if(NPC[A].FrameCount < 16)\n                NPC[A].Frame = 2;\n            else\n            {\n                NPC[A].Frame = 0;\n                NPC[A].FrameCount = 0;\n            }\n        }\n        else if(NPC[A].Special == 1)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 4)\n                NPC[A].Frame = 0;\n            else if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 1;\n            else\n            {\n                NPC[A].Frame = 0;\n                NPC[A].FrameCount = 0;\n            }\n        }\n        else\n            NPC[A].Frame = 3;\n    }\n    else if(NPC[A].Type == NPCID_DOOR_MAKER) // potion\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n        {\n            NPC[A].Frame += 1;\n            NPC[A].FrameCount = 0;\n        }\n        if(NPC[A].Frame >= 4)\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A].Type == NPCID_ITEM_BUBBLE) // bubble\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount < 6)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount < 12)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount < 18)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount < 24)\n            NPC[A].Frame = 2;\n        else\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_VINE_BUG) // spider\n    {\n        if(NPC[A].Projectile || NPC[A].Location.SpeedY >= 0 || NPC[A].HoldingPlayer > 0)\n            NPC[A].Frame = 0;\n        else\n            NPC[A].Frame = 2;\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount > 15)\n            NPC[A].FrameCount = 0;\n        else if(NPC[A].FrameCount >= 8)\n            NPC[A].Frame += 1;\n    }\n    else if(NPC[A].Type == NPCID_BAT) // bat thing\n    {\n        if(NPC[A].Special == 0)\n            NPC[A].Frame = 0;\n        else\n        {\n            NPC[A].Frame = 1;\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount > 15)\n                NPC[A].FrameCount = 0;\n            else if(NPC[A].FrameCount >= 8)\n                NPC[A].Frame = 2;\n        }\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 3;\n\n\n    }\n    else if(NPC[A].Type == NPCID_JUMP_PLANT) // jumping plant\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].Frame += 1;\n            NPC[A].FrameCount = 0;\n        }\n        if(NPC[A].Frame >= 4)\n            NPC[A].Frame = 0;\n\n    }\n    else if(NPC[A].Type == NPCID_FIRE_BOSS) // ludwig koopa\n    {\n        if(NPC[A].Location.SpeedY != 0 && !NPC[A].Wings)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 4)\n                NPC[A].Frame = 10;\n            else if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 11;\n            else\n            {\n                NPC[A].Frame = 10;\n                NPC[A].FrameCount = 0;\n            }\n        }\n        else\n        {\n            if(NPC[A].Special == 0)\n            {\n                NPC[A].FrameCount += 1;\n                if(NPC[A].Location.SpeedX == 0)\n                    NPC[A].FrameCount = 10;\n                if(NPC[A].FrameCount < 4)\n                    NPC[A].Frame = 0;\n                else if(NPC[A].FrameCount < 8)\n                    NPC[A].Frame = 1;\n                else if(NPC[A].FrameCount < 12)\n                    NPC[A].Frame = 2;\n                else\n                {\n                    NPC[A].Frame = 0;\n                    NPC[A].FrameCount = 0;\n                }\n            }\n            else if(NPC[A].Special == 1)\n                NPC[A].Frame = 3;\n            else if(NPC[A].Special == 2)\n                NPC[A].Frame = 4;\n            if(NPC[A].Direction == 1)\n                NPC[A].Frame += 5;\n        }\n\n\n\n    }\n    else if(NPC[A].Type == NPCID_FIRE_BOSS_SHELL) // ludwig shell\n    {\n        if(NPC[A].Location.SpeedX == 0)\n        {\n            if(NPC[A].Frame > 2)\n                NPC[A].Frame = 0;\n        }\n        else\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 4)\n            {\n                NPC[A].Frame += NPC[A].Direction;\n                NPC[A].FrameCount = 0;\n            }\n            if(NPC[A].Frame < 0)\n                NPC[A].Frame = 2;\n            if(NPC[A].Frame > 2)\n                NPC[A].Frame = 0;\n        }\n\n    }\n    else if(NPC[A].Type == NPCID_FIRE_BOSS_FIRE) // ludwig fire\n    {\n        NPC[A].FrameCount += 1;\n        NPC[A].Frame = 0;\n        if(NPC[A].FrameCount > 8)\n            NPC[A].FrameCount = 0;\n        else if(NPC[A].FrameCount >= 4)\n            NPC[A].Frame = 1;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 2;\n\n    }\n    else if(NPC[A].Type == NPCID_MAGIC_BOSS_BALL) // larry magic\n    {\n        if(NPC[A].Special == 0)\n            NPC[A].Frame = 2;\n        else if(NPC[A].Special == 1)\n            NPC[A].Frame = 1;\n        else\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A].Type == NPCID_MAGIC_BOSS_SHELL) // larry shell\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].Frame += NPC[A].Direction;\n            NPC[A].FrameCount = 0;\n        }\n        if(NPC[A].Frame < 0)\n            NPC[A].Frame = 5;\n        if(NPC[A].Frame > 5)\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A].Type == NPCID_MAGIC_BOSS) // larry koopa\n    {\n\n        NPC[A].Frame = 0;\n        if(NPC[A].Special == 0)\n        {\n            if(NPC[A].Location.SpeedY == 0)\n            {\n                if(NPC[A].Location.SpeedX == 0)\n                    NPC[A].Frame = 0;\n                else\n                {\n                    NPC[A].FrameCount += 1;\n                    if(NPC[A].FrameCount < 8)\n                        NPC[A].Frame = 0;\n                    else if(NPC[A].FrameCount < 16)\n                        NPC[A].Frame = 1;\n                    else\n                    {\n                        NPC[A].Frame = 0;\n                        NPC[A].FrameCount = 0;\n                    }\n                }\n            }\n            else\n                NPC[A].Frame = 1;\n        }\n        else if(NPC[A].Special == 1)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 2)\n                NPC[A].Frame = 2;\n            else if(NPC[A].FrameCount < 4)\n                NPC[A].Frame = 3;\n            else if(NPC[A].FrameCount < 6)\n                NPC[A].Frame = 4;\n            else if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 5;\n            else\n            {\n                NPC[A].Frame = 2;\n                NPC[A].FrameCount = 0;\n            }\n        }\n        else if(NPC[A].Special == 2)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 2)\n                NPC[A].Frame = 6;\n            else if(NPC[A].FrameCount < 4)\n                NPC[A].Frame = 7;\n            else if(NPC[A].FrameCount < 6)\n                NPC[A].Frame = 8;\n            else if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 9;\n            else\n            {\n                NPC[A].Frame = 6;\n                NPC[A].FrameCount = 0;\n            }\n        }\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 10;\n\n\n    }\n    else if(NPC[A].Type == NPCID_SWORDBEAM) // sword beam\n    {\n        NPC[A].Frame = 0;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = 4;\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount < 2)\n        {\n        }\n        else if(NPC[A].FrameCount < 4)\n            NPC[A].Frame += 1;\n        else if(NPC[A].FrameCount < 6)\n            NPC[A].Frame += 2;\n        else if(NPC[A].FrameCount < 8)\n            NPC[A].Frame += 3;\n        else\n            NPC[A].FrameCount = 0;\n\n\n    }\n    else if(NPC[A].Type == NPCID_BOMBER_BOSS) // mouser\n    {\n        if(NPC[A].Immune > 0)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 4)\n                NPC[A].Frame = 3;\n            else if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 4;\n            else if(NPC[A].FrameCount < 12)\n                NPC[A].Frame = 5;\n            else if(NPC[A].FrameCount < 15)\n                NPC[A].Frame = 6;\n            else\n            {\n                NPC[A].Frame = 6;\n                NPC[A].FrameCount = 0;\n            }\n            if(NPC[A].Direction == 1)\n                NPC[A].Frame += 7;\n        }\n        else if(NPC[A].Special <= 0)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 1;\n            else if(NPC[A].FrameCount < 15)\n                NPC[A].Frame = 2;\n            else\n            {\n                NPC[A].Frame = 2;\n                NPC[A].FrameCount = 0;\n            }\n            if(NPC[A].Direction == 1)\n                NPC[A].Frame += 7;\n        }\n        else\n        {\n            NPC[A].Frame = 0;\n            if(NPC[A].Direction == 1)\n                NPC[A].Frame += 7;\n        }\n\n    }\n    else if(NPC[A].Type == NPCID_WALK_PLANT)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount < 8)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount < 15)\n            NPC[A].Frame = 1;\n        else\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame = 1;\n        }\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 4;\n        if(NPC[A].Special > 0 && NPC[A].Location.SpeedY <= 0)\n            NPC[A].Frame += 2;\n\n    }\n    else if(NPC[A].Type == NPCID_FIRE_CHAIN)\n    {\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = SpecialFrame[2];\n        else\n            NPC[A].Frame = 3 - SpecialFrame[2];\n    }\n    else if(NPC[A].Type == NPCID_LOCK_DOOR)\n    {\n        // NPC has no frames so do nothing\n    }\n    else if(NPC[A].Type == NPCID_FIRE_DISK)\n    {\n        NPC[A].Frame += 1;\n        if(NPC[A].Frame >= 5)\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A].Type == NPCID_GEM_1 || NPC[A].Type == NPCID_GEM_5 || NPC[A].Type == NPCID_GEM_20)\n        NPC[A].Frame = SpecialFrame[8];\n    else if(NPC[A].Type == NPCID_TIME_SWITCH)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame += 1;\n        }\n        if(NPC[A].Frame >= 3)\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A].Type == NPCID_STACKER)\n    {\n        // Special less than zero - body, zero - head\n        if(NPC[A].Special < 0 && NPC[A].Location.SpeedY == 0)\n            NPC[A].Special += 1;\n        if(NPC[A].Projectile || NPC[A].HoldingPlayer > 0)\n            NPC[A].Frame = 4;\n        else\n        {\n            if(NPC[A].Special < 0)\n                NPC[A].Frame = 1;\n            else\n                NPC[A].Frame = 0;\n        }\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 16)\n            NPC[A].FrameCount = 0;\n        else if(NPC[A].FrameCount > 8)\n        {\n            if(NPC[A].Projectile || NPC[A].HoldingPlayer > 0)\n                NPC[A].Frame += 1;\n            else\n                NPC[A].Frame += 2;\n        }\n    }\n    else if(NPC[A].Type == NPCID_FIRE_PLANT)\n    {\n        NPC[A].Frame = 0;\n        if(Player[NPC[A].Special4].Location.to_right_of(NPC[A].Location))\n            NPC[A].Frame = 2;\n        if(Player[NPC[A].Special4].Location.Y + Player[NPC[A].Special4].Location.Height / 2 < NPC[A].Location.Y + 16)\n            NPC[A].Frame += 1;\n    }\n    else if(NPC[A].Type == NPCID_FLY_FODDER_S5)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame >= 2)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_EARTHQUAKE_BLOCK) // POW block\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame >= 7)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_SLANT_WOOD_L) // 1 frame left or right\n    {\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = 1;\n        else\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A].Type == NPCID_HOMING_BALL_GEN)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount <= 6)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount <= 12)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount <= 18)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount <= 24)\n            NPC[A].Frame = 3;\n        else if(NPC[A].FrameCount <= 30)\n            NPC[A].Frame = 4;\n        else if(NPC[A].FrameCount <= 36)\n            NPC[A].Frame = 5;\n        else if(NPC[A].FrameCount <= 42)\n            NPC[A].Frame = 4;\n        else if(NPC[A].FrameCount <= 48)\n            NPC[A].Frame = 3;\n        else if(NPC[A].FrameCount <= 54)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount <= 60)\n            NPC[A].Frame = 1;\n        else\n            NPC[A].FrameCount = 0;\n    }\n    else if(NPC[A].Type == NPCID_HOMING_BALL)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount <= 8)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount <= 16)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount <= 24)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount <= 32)\n            NPC[A].Frame = 3;\n        else if(NPC[A].FrameCount <= 38)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount <= 46)\n            NPC[A].Frame = 1;\n        else\n            NPC[A].FrameCount = 0;\n    }\n    else if(NPC[A].Type == NPCID_BOSS_FRAGILE)\n    {\n        NPC[A].Frame = 0;\n        if(NPC[A].Special > 0 && NPC[A].Special < 15)\n            NPC[A].Frame = 1;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 2;\n    }\n    else if(NPC[A].Type == NPCID_BOSS_CASE)\n    {\n        if(NPC[A].Damage < 3)\n            NPC[A].Frame = 0;\n        else if(NPC[A].Damage < 6)\n            NPC[A].Frame = 1;\n        else if(NPC[A].Damage < 9)\n            NPC[A].Frame = 2;\n        else if(NPC[A].Damage < 12)\n            NPC[A].Frame = 3;\n        else\n            NPC[A].Frame = 4;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 5;\n    }\n    else if(NPC[A].Type == NPCID_WALL_TURTLE)\n    {\n        NPC[A].FrameCount += 1;\n        NPC[A].Frame = 0;\n        if(NPC[A].FrameCount >= 16)\n            NPC[A].FrameCount = 0;\n        else if(NPC[A].FrameCount > 8)\n            NPC[A].Frame = 1;\n        if(NPC[A].Special == 4)\n            NPC[A].Frame += 4;\n        else if(NPC[A].Special == 3)\n            NPC[A].Frame += 8;\n        else if(NPC[A].Special == 2)\n            NPC[A].Frame += 12;\n        if(NPC[A].Special2 == 1)\n            NPC[A].Frame += 2;\n\n\n    }\n    else if(NPC[A].Type == NPCID_WALL_BUG)\n    {\n        NPC[A].FrameCount += 1;\n        NPC[A].Frame = 0;\n        if(NPC[A].FrameCount <= 6)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount <= 12)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount <= 18)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount <= 24)\n            NPC[A].Frame = 3;\n        else if(NPC[A].FrameCount <= 30)\n            NPC[A].Frame = 4;\n        else\n            NPC[A].FrameCount = 0;\n        if(NPC[A].Special == 4)\n            NPC[A].Frame += 5;\n        else if(NPC[A].Special == 3)\n            NPC[A].Frame += 10;\n        else if(NPC[A].Special == 2)\n            NPC[A].Frame += 15;\n\n\n    }\n    else if(NPC[A].Type == NPCID_FLIER || NPC[A].Type == NPCID_ROCKET_FLIER)\n    {\n        NPC[A].FrameCount += 1;\n        NPC[A].Frame = 0;\n        if(NPC[A].FrameCount <= 6)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount <= 12)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount <= 18)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount <= 24)\n            NPC[A].Frame = 1;\n        else\n            NPC[A].FrameCount = 0;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 4;\n    }\n    else if(NPC[A].Type == NPCID_SICK_BOSS)\n    {\n        NPC[A].Frame = 0;\n        if(NPC[A].Special == 0)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 0;\n            else if(NPC[A].FrameCount < 16)\n                NPC[A].Frame = 1;\n            else\n                NPC[A].FrameCount = 0;\n        }\n        else if(NPC[A].Special == 1)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame = 2;\n\n        }\n        else if(NPC[A].Special == 2)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 3;\n            else if(NPC[A].FrameCount < 16)\n                NPC[A].Frame = 4;\n            else\n            {\n                NPC[A].Frame = 3;\n                NPC[A].FrameCount = 0;\n            }\n        }\n\n        if(NPC[A].Special == 3 || NPC[A].Special == 2)\n        {\n            NPC[A].Frame = 0;\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 4)\n                NPC[A].Frame = 5;\n            else if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 6;\n            else if(NPC[A].FrameCount < 12)\n                NPC[A].Frame = 7;\n            else\n            {\n                NPC[A].Frame = 7;\n                NPC[A].FrameCount = 0;\n            }\n        }\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 8;\n    }\n    else if(NPC[A].Type == NPCID_VILLAIN_S1) // King Koopa\n    {\n        NPC[A].Frame = 0;\n        if(NPC[A].Special == 0)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount <= 8)\n                NPC[A].Frame = 1;\n            else if(NPC[A].FrameCount <= 16)\n                NPC[A].Frame = 0;\n            else if(NPC[A].FrameCount <= 24)\n                NPC[A].Frame = 2;\n            else if(NPC[A].FrameCount <= 32)\n                NPC[A].Frame = 0;\n            else\n                NPC[A].FrameCount = 0;\n        }\n        else if(NPC[A].Special == 1)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame = 3;\n        }\n        else if(NPC[A].Special == 2)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame = 4;\n        }\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 5;\n    }\n    else if(NPC[A].Type == NPCID_STAR_COLLECT)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame >= 2)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_STONE_S4)\n    {\n        NPC[A].Frame = 0;\n\n        if(NPC[A].Special == 1)\n            NPC[A].Frame = 2;\n\n        for(int B = 1; B <= numPlayers; ++B)\n        {\n            if(!CanComeOut(NPC[A].Location, Player[B].Location) && Player[B].Location.Y >= NPC[A].Location.Y)\n            {\n                NPC[A].Frame = 2;\n                break;\n            }\n        }\n\n        if(NPC[A].Frame == 0)\n        {\n            Location_t tempLocation = NPC[A].Location;\n            tempLocation.Width = NPC[A].Location.Width * 2;\n            tempLocation.X = NPC[A].Location.X - NPC[A].Location.Width / 2;\n\n            for(int B = 1; B <= numPlayers; ++B)\n            {\n                if(!CanComeOut(tempLocation, Player[B].Location) && Player[B].Location.Y >= NPC[A].Location.Y)\n                {\n                    NPC[A].Frame = 1;\n                    break;\n                }\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_CHAR4_HEAVY) // toad boomerang\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 6)\n        {\n            NPC[A].FrameCount = 0;\n\n            if(NPC[A].Location.SpeedX > 0)\n            {\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame == 1)\n                    NPC[A].Location.X += 4;\n                else if(NPC[A].Frame == 3)\n                    NPC[A].Location.X -= 4;\n                else if(NPC[A].Frame == 2)\n                    NPC[A].Location.Y += 4;\n                else\n                    NPC[A].Location.Y -= 4;\n            }\n            else\n            {\n                NPC[A].Frame -= 1;\n                if(NPC[A].Frame == 0)\n                    NPC[A].Location.X -= 4;\n                else if(NPC[A].Frame == 1)\n                    NPC[A].Location.Y -= 4;\n                else if(NPC[A].Frame == 2)\n                    NPC[A].Location.X += 4;\n                else\n                    NPC[A].Location.Y += 4;\n            }\n\n            if(NPC[A].Frame > 3)\n                NPC[A].Frame = 0;\n            else if(NPC[A].Frame < 0)\n                NPC[A].Frame = 3;\n\n            treeNPCUpdate(A);\n            if(NPC[A].tempBlock > 0)\n                treeNPCSplitTempBlock(A);\n        }\n\n        if(iRand(4) == 0)\n            s_makeHeavySparkle(NPC[A], 4);\n    }\n    else if(NPC[A].Type == NPCID_PLR_HEAVY) // Mario Hammer\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].FrameCount = 0;\n\n            if(NPC[A].Location.SpeedX > 0)\n            {\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame == 1)\n                    NPC[A].Location.X += 8;\n                else if(NPC[A].Frame == 3)\n                    NPC[A].Location.X -= 8;\n                else if(NPC[A].Frame == 2)\n                    NPC[A].Location.Y += 12;\n                else\n                    NPC[A].Location.Y -= 12;\n            }\n            else\n            {\n                NPC[A].Frame -= 1;\n                if(NPC[A].Frame == 0)\n                    NPC[A].Location.X -= 8;\n                else if(NPC[A].Frame == 1)\n                    NPC[A].Location.Y -= 12;\n                else if(NPC[A].Frame == 2)\n                    NPC[A].Location.X += 8;\n                else\n                    NPC[A].Location.Y += 12;\n            }\n\n            if(NPC[A].Frame > 3)\n                NPC[A].Frame = 0;\n            else if(NPC[A].Frame < 0)\n                NPC[A].Frame = 3;\n\n            treeNPCUpdate(A);\n            if(NPC[A].tempBlock > 0)\n                treeNPCSplitTempBlock(A);\n\n            s_makeHeavySparkle(NPC[A], 8);\n        }\n    }\n    else if(NPC[A].Type == NPCID_FLY_CARRY_FODDER) // smw paragoomba\n    {\n        NPC[A].FrameCount += 1;\n\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = 4;\n        else\n            NPC[A].Frame = 0;\n\n        if(NPC[A].FrameCount >= 16)\n            NPC[A].FrameCount = 0;\n        else if(NPC[A].FrameCount >= 8)\n            NPC[A].Frame += 1;\n\n        if(NPC[A].Effect == NPCEFF_NORMAL)\n        {\n            if(NPC[A].Special == 0)\n                NPC[A].Special2 += 2;\n            else if(NPC[A].Special <= 60)\n                NPC[A].Special2 = 0;\n            else if(NPC[A].Special < 65)\n                NPC[A].Special2 += 1;\n            else\n                NPC[A].Special2 += 2;\n\n            if(NPC[A].Special2 >= 16)\n                NPC[A].Special2 = 0;\n            else if(NPC[A].Special2 >= 8)\n                NPC[A].Frame += 2;\n        }\n    }\n    else if(NPC[A].Type == NPCID_RED_FLY_FODDER || NPC[A].Type == NPCID_FLY_FODDER_S3) // Flying Goomba\n    {\n        if(NPC[A].Location.SpeedY == 0 || NPC[A].Slope > 0)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 8)\n            {\n                NPC[A].FrameCount = 0;\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 2)\n                    NPC[A].Frame = 0;\n            }\n        }\n        else\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 4)\n            {\n                NPC[A].FrameCount = 0;\n                if(NPC[A].Frame == 0)\n                    NPC[A].Frame = 2;\n                else if(NPC[A].Frame == 1)\n                    NPC[A].Frame = 3;\n                else if(NPC[A].Frame == 2)\n                    NPC[A].Frame = 1;\n                else if(NPC[A].Frame == 3)\n                    NPC[A].Frame = 0;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_BOMB) // bomb\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount < 4)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount < 8)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount < 11)\n            NPC[A].Frame = 2;\n        else\n            NPC[A].FrameCount = 0;\n        if(NPC[A].Special2 == 1)\n        {\n            NPC[A].Special3 += 1;\n            if(NPC[A].Special3 < 4)\n            {\n            }\n            else if(NPC[A].Special3 < 8)\n                NPC[A].Frame += 9;\n            else if(NPC[A].Special3 < 12)\n                NPC[A].Frame += 3;\n            else if(NPC[A].Special3 < 15)\n                NPC[A].Frame += 6;\n            else\n                NPC[A].Special3 = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_CHAR3_HEAVY) // heart bomb\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount < 4)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount < 8)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount < 11)\n            NPC[A].Frame = 2;\n        else\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame = 0;\n        }\n\n        NPC[A].Special3 += 1;\n        if(NPC[A].Special3 < 4)\n        {}\n        else if(NPC[A].Special3 < 8)\n            NPC[A].Frame += 3;\n        else if(NPC[A].Special3 < 12)\n            NPC[A].Frame += 6;\n        else // If .Special3 >= 16 Then\n            NPC[A].Special3 = 0;\n\n        if(iRand(100) >= 92)\n            s_makeHeavySparkle(NPC[A], 6);\n    }\n    else if(NPC[A].Type == NPCID_ITEM_BURIED)\n        NPC[A].Frame = SpecialFrame[5];\n    else if(NPC[A].Type == NPCID_ITEM_POD)\n    {\n        NPC[A].Frame = 0;\n        if(NPC[A].Special == 98)\n            NPC[A].Frame = 1;\n        else if(NPC[A].Special == 99)\n            NPC[A].Frame = 2;\n        else if(NPC[A].Special == 100)\n            NPC[A].Frame = 3;\n        else if(NPC[A].Special == 148)\n            NPC[A].Frame = 4;\n        else if(NPC[A].Special == 149)\n            NPC[A].Frame = 5;\n        else if(NPC[A].Special == 150)\n            NPC[A].Frame = 6;\n        else if(NPC[A].Special == 228)\n            NPC[A].Frame = 7;\n    }\n    else if(NPC[A].Type == NPCID_RAINBOW_SHELL || NPC[A].Type == NPCID_FLIPPED_RAINBOW_SHELL) // Glowy Shell\n    {\n        NPC[A].Special5 += 1;\n        if(NPC[A].Special5 >= 16)\n            NPC[A].Special5 = 0;\n        if(NPC[A].Location.SpeedX > 0)\n        {\n            if(NPC[A].Type == NPCID_RAINBOW_SHELL)\n                NPC[A].FrameCount += 1;\n            else\n                NPC[A].FrameCount -= 1;\n        }\n        else if(NPC[A].Location.SpeedX < 0)\n        {\n            if(NPC[A].Type == NPCID_RAINBOW_SHELL)\n                NPC[A].FrameCount -= 1;\n            else\n                NPC[A].FrameCount += 1;\n        }\n        else\n        {\n            if(NPC[A].Type == NPCID_RAINBOW_SHELL)\n                NPC[A].FrameCount = 0;\n            else\n                NPC[A].FrameCount = 12;\n        }\n        if(NPC[A].FrameCount < 0)\n            NPC[A].FrameCount = 15;\n        if(NPC[A].FrameCount >= 16)\n            NPC[A].FrameCount = 0;\n        if(NPC[A].FrameCount < 4)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount < 8)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount < 12)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount < 16)\n            NPC[A].Frame = 3;\n        if(NPC[A].Special5 < 4)\n        {\n        }\n        else if(NPC[A].Special5 < 8)\n            NPC[A].Frame += 4;\n        else if(NPC[A].Special5 < 12)\n            NPC[A].Frame += 8;\n        else if(NPC[A].Special5 < 16)\n            NPC[A].Frame += 12;\n    }\n    else if(NPC[A].Type == NPCID_JUMPER_S4) // black ninja\n    {\n        if(NPC[A].Location.SpeedY == 0 || NPC[A].Slope > 0)\n        {\n            NPC[A].Frame = 0;\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 12)\n                NPC[A].FrameCount = 0;\n            else if(NPC[A].FrameCount >= 6)\n                NPC[A].Frame = 1;\n        }\n        else if(NPC[A].Location.SpeedY < 0)\n        {\n            NPC[A].Frame = 0;\n            NPC[A].FrameCount = 6;\n        }\n        else\n        {\n            NPC[A].Frame = 1;\n            NPC[A].FrameCount = 0;\n        }\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 2;\n    }\n    else if(NPC[A].Type == NPCID_CONVEYOR) // smb3 belt\n    {\n        if(NPC[A].Direction == -1)\n            NPC[A].Frame = SpecialFrame[4];\n        else\n            NPC[A].Frame = 3 - SpecialFrame[4];\n    }\n    else if(NPC[A].Type == NPCID_YEL_PLATFORM || NPC[A].Type == NPCID_BLU_PLATFORM || NPC[A].Type == NPCID_GRN_PLATFORM || NPC[A].Type == NPCID_RED_PLATFORM)\n    {\n        NPC[A].Frame = 1;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A].Type == NPCID_CHASER) // Bully\n    {\n        NPC[A].Frame = 0;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 3;\n\n        if(NPC[A].Projectile || NPC[A].Special2 != 0)\n        {\n            NPC[A].Frame += 2;\n            NPC[A].FrameCount = 0;\n        }\n        else\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 16)\n                NPC[A].FrameCount = 0;\n            else if(NPC[A].FrameCount >= 8)\n                NPC[A].Frame += 1;\n        }\n\n\n    }\n    else if(NPC[A].Type == NPCID_TANK_TREADS) // tank treads\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount >= 4)\n            NPC[A].Frame = 1;\n        else\n            NPC[A].Frame = 0;\n        if(NPC[A].FrameCount > 12)\n            NPC[A].FrameCount = 0;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 3;\n    }\n    else if(NPC[A].Type == NPCID_EXT_TURTLE) // nekkid koopa\n    {\n        if(NPC[A].Special == 0)\n        {\n            NPC[A].Frame = 0;\n            if(NPC[A].Direction == 1)\n                NPC[A].Frame = 3;\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 15)\n                NPC[A].FrameCount = 0;\n            else if(NPC[A].FrameCount >= 8)\n                NPC[A].Frame += 1;\n        }\n        else\n        {\n            if(NPC[A].Direction == -1)\n                NPC[A].Frame = 2;\n            else\n                NPC[A].Frame = 5;\n        }\n    }\n    else if(NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4) // beach koopa\n    {\n        if(NPC[A].Projectile)\n        {\n            if(NPC[A].Location.SpeedX < -0.5_n || NPC[A].Location.SpeedX > 0.5_n)\n                NPC[A].Frame = 3;\n            else\n            {\n                NPC[A].Frame = 3;\n                NPC[A].FrameCount += 1;\n                if(NPC[A].FrameCount >= 15)\n                    NPC[A].FrameCount = 0;\n                else if(NPC[A].FrameCount >= 8)\n                    NPC[A].Frame = 4;\n            }\n        }\n        else\n        {\n            if(NPC[A].Special == 0)\n            {\n                NPC[A].Frame = 0;\n                NPC[A].FrameCount += 1;\n                if(NPC[A].FrameCount >= 15)\n                    NPC[A].FrameCount = 0;\n                else if(NPC[A].FrameCount >= 8)\n                    NPC[A].Frame = 1;\n            }\n            else\n                NPC[A].Frame = 2;\n        }\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 5;\n    }\n    else if(NPC[A].Type == NPCID_FLY) // bouncy bee\n    {\n        if(NPC[A].Location.SpeedY == 0 || NPC[A].Slope > 0)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame = 0;\n        }\n        else\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 3)\n            {\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 2)\n                    NPC[A].Frame = 0;\n                NPC[A].FrameCount = 0;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_VEHICLE)\n    {\n        NPC[A].Frame = SpecialFrame[2];\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 4;\n    }\n    else if(NPC[A].Type == NPCID_SLIDE_BLOCK) // ice block\n    {\n        if(NPC[A].Special == 0)\n            NPC[A].Frame = BlockFrame[4];\n        else\n        {\n            if(NPC[A].Frame < 4)\n                NPC[A].Frame = 4;\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 4)\n            {\n                NPC[A].FrameCount = 0;\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 6)\n                    NPC[A].Frame = 4;\n            }\n        }\n        // bowser fireball\n    }\n    else if(NPC[A].Type == NPCID_VILLAIN_FIRE)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 20)\n            NPC[A].FrameCount = 0;\n        // NPC[A].Frame = static_cast<int>(floor(static_cast<double>(NPC[A].FrameCount / 5)));\n        // integer division now\n        NPC[A].Frame = NPC[A].FrameCount / 5;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 4;\n        // statue fireball\n    }\n    else if(NPC[A].Type == NPCID_STATUE_FIRE)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n            NPC[A].FrameCount = 0;\n        // NPC[A].Frame = static_cast<int>(floor(static_cast<double>(NPC[A].FrameCount / 2)));\n        // integer division now\n        NPC[A].Frame = NPC[A].FrameCount / 2;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 4;\n        // winged koopa\n    }\n    else if(NPC[A].Type == NPCID_GRN_FLY_TURTLE_S3 || NPC[A].Type == NPCID_RED_FLY_TURTLE_S3)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].Direction == -1 && NPC[A].Frame >= 4)\n            NPC[A].Frame = 0;\n        else if(NPC[A].Direction == 1 && NPC[A].Frame < 4)\n            NPC[A].Frame = 4;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].FrameCount = 0;\n            if(NPC[A].Direction == -1)\n            {\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 4)\n                    NPC[A].Frame = 0;\n            }\n            else\n            {\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 8)\n                    NPC[A].Frame = 4;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_LIT_BOMB_S3) // SMB3 Bomb\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount < 8)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount < 15)\n            NPC[A].Frame = 1;\n        else\n        {\n            NPC[A].Frame = 1;\n            NPC[A].FrameCount = 0;\n        }\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 6;\n        if(NPC[A].Special2 == 1)\n        {\n            NPC[A].Special3 += 1;\n            if(NPC[A].Special3 < 4)\n            {\n            }\n            else if(NPC[A].Special3 < 8)\n                NPC[A].Frame += 2;\n            else if(NPC[A].Special3 < 11)\n                NPC[A].Frame += 4;\n            else\n            {\n                NPC[A].Frame += 4;\n                NPC[A].Special3 = 0;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_ROCKET_WOOD) // Airship Jet\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].Direction == -1 && NPC[A].Frame >= 4)\n            NPC[A].Frame = 0;\n        else if(NPC[A].Direction == 1 && NPC[A].Frame < 4)\n            NPC[A].Frame = 8;\n        if(NPC[A].FrameCount >= 2)\n        {\n            NPC[A].FrameCount = 0;\n            if(NPC[A].Direction == -1)\n            {\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 4)\n                    NPC[A].Frame = 0;\n            }\n            else\n            {\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 8)\n                    NPC[A].Frame = 4;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_AXE)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame >= 3)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_GRN_TURTLE_S3 || NPC[A].Type == NPCID_RED_TURTLE_S3 || NPC[A].Type == NPCID_GLASS_TURTLE || NPC[A].Type == NPCID_SPIKY_S3 || NPC[A].Type == NPCID_SPIKY_S4 || NPC[A].Type == NPCID_GHOST_FAST || NPC[A].Type == NPCID_SIDE_PLANT || NPC[A].Type == NPCID_BIG_TURTLE || (NPC[A].Type >= NPCID_GRN_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_TURTLE_S4) || (NPC[A].Type >= NPCID_GRN_FLY_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_FLY_TURTLE_S4) || NPC[A].Type == NPCID_WALK_BOMB_S3 || NPC[A].Type == NPCID_LIFT_SAND || NPC[A].Type == NPCID_BRUTE || NPC[A].Type == NPCID_BRUTE_SQUISHED || NPC[A].Type == NPCID_BIG_GUY || NPC[A].Type == NPCID_CARRY_FODDER || NPC[A].Type == NPCID_HIT_CARRY_FODDER || NPC[A].Type == NPCID_GRN_TURTLE_S1 || NPC[A].Type == NPCID_RED_TURTLE_S1 || NPC[A].Type == NPCID_GRN_FLY_TURTLE_S1 || NPC[A].Type == NPCID_RED_FLY_TURTLE_S1 || NPC[A].Type == NPCID_LAVA_MONSTER || NPC[A].Type == NPCID_GRN_FISH_S3 || NPC[A].Type == NPCID_YEL_FISH_S4 || NPC[A].Type == NPCID_RED_FISH_S3 || NPC[A].Type == NPCID_GRN_FISH_S4 || NPC[A].Type == NPCID_GRN_FISH_S1) // Walking koopa troopa / hard thing / spiney\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].Type == NPCID_HIT_CARRY_FODDER && NPC[A].Special > 360)\n            NPC[A].FrameCount += 1;\n        if(NPC[A].Direction == -1 && NPC[A].Frame >= 2)\n            NPC[A].Frame = 0;\n        else if(NPC[A].Direction == 1 && NPC[A].Frame < 2)\n            NPC[A].Frame = 2;\n        if(NPC[A].FrameCount >= 8)\n        {\n            NPC[A].FrameCount = 0;\n            if(NPC[A].Direction == -1)\n            {\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 2)\n                    NPC[A].Frame = 0;\n            }\n            else\n            {\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 4)\n                    NPC[A].Frame = 2;\n            }\n        }\n\n    }\n    else if(NPC[A].Type == NPCID_BONE_FISH)\n    {\n        NPC[A].FrameCount += 1;\n        NPC[A].Frame = 0;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = 3;\n\n        if(NPC[A].FrameCount > 8)\n            NPC[A].Frame += 1;\n\n        if(NPC[A].FrameCount > 16)\n            NPC[A].Frame += 1;\n        if(NPC[A].FrameCount > 24)\n            NPC[A].Frame -= 1;\n        if(NPC[A].FrameCount > 32)\n            NPC[A].FrameCount = 0;\n\n    }\n    else if(NPC[A].Type == NPCID_SKELETON) // dry bones\n    {\n        if(NPC[A].Special == 0)\n        {\n            NPC[A].FrameCount += 1;\n            // was not commented in VB6, but it's obviously impossible\n            // if(NPC[A].Type == NPCID_HIT_CARRY_FODDER && NPC[A].Special > 360)\n            //     NPC[A].FrameCount += 1;\n            if(NPC[A].Direction == -1 && NPC[A].Frame >= 2)\n                NPC[A].Frame = 0;\n            else if(NPC[A].Direction == 1 && NPC[A].Frame < 2)\n                NPC[A].Frame = 2;\n            if(NPC[A].FrameCount >= 8)\n            {\n                NPC[A].FrameCount = 0;\n                if(NPC[A].Direction == -1)\n                {\n                    NPC[A].Frame += 1;\n                    if(NPC[A].Frame >= 2)\n                        NPC[A].Frame = 0;\n                }\n                else\n                {\n                    NPC[A].Frame += 1;\n                    if(NPC[A].Frame >= 4)\n                        NPC[A].Frame = 2;\n                }\n            }\n        }\n        else\n        {\n            if(NPC[A].Special2 < 10 || NPC[A].Special2 > 400 - 10)\n                NPC[A].Frame = 4;\n            else\n                NPC[A].Frame = 5;\n            if(NPC[A].Direction == 1)\n                NPC[A].Frame += 2;\n        }\n    }\n    else if(NPC[A].Type == NPCID_MEDAL) // dragon coin\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount < 6)\n            NPC[A].Frame = 0;\n        else if(NPC[A].FrameCount < 12)\n            NPC[A].Frame = 1;\n        else if(NPC[A].FrameCount < 18)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount < 24)\n            NPC[A].Frame = 3;\n        else if(NPC[A].FrameCount < 30)\n            NPC[A].Frame = 2;\n        else if(NPC[A].FrameCount < 36)\n            NPC[A].Frame = 1;\n        else\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_ITEMGOAL) // Frame finder for Star/Flower/Mushroom Exit\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame == 3)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_TOOTHY) // killer plant\n    {\n        // .vehiclePlr = A\n        NPC[A].Frame = 0;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = 2;\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n            NPC[A].Frame += 1;\n        if(NPC[A].FrameCount >= 16)\n            NPC[A].FrameCount = 0;\n    }\n    else if(NPC[A].Type == NPCID_TOOTHYPIPE) // killer pipe\n    {\n        if(NPC[A].HoldingPlayer == 0 && !Player[NPC[A].vehiclePlr].Controls.Run && !NPC[A].Projectile)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 4)\n            {\n                NPC[A].Frame += 1;\n                NPC[A].FrameCount = 0;\n            }\n            if(NPC[A].Frame >= 5)\n                NPC[A].Frame = 0;\n        }\n        else\n        {\n            if(NPC[A].Direction == -1)\n            {\n                NPC[A].FrameCount += 1;\n                if(NPC[A].FrameCount >= 4)\n                {\n                    NPC[A].Frame += 1;\n                    NPC[A].FrameCount = 0;\n                }\n                if(NPC[A].Frame >= 10 || NPC[A].Frame < 5)\n                    NPC[A].Frame = 5;\n            }\n            else\n            {\n                NPC[A].FrameCount += 1;\n                if(NPC[A].FrameCount >= 4)\n                {\n                    NPC[A].Frame += 1;\n                    NPC[A].FrameCount = 0;\n                }\n                if(NPC[A].Frame >= 15 || NPC[A].Frame < 10)\n                    NPC[A].Frame = 10;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_LAVABUBBLE) // Frame finder for big fireball\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame += 1;\n            if(NPC[A].Location.SpeedY < 0)\n            {\n                if(NPC[A].Frame >= 2)\n                    NPC[A].Frame = 0;\n            }\n            else\n            {\n                if(NPC[A].Frame >= 4)\n                    NPC[A].Frame = 2;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_HEAVY_THROWN || NPC[A].Type == NPCID_PLANT_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL) // Frame finder for Fireball / Hammer\n    {\n        if((NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL) && NPC[A].Quicksand == 0)\n        {\n            Location_t tempLocation;\n            bool make_trail = true;\n\n            if(NPC[A].Wet > 0)\n            {\n                if(iRand(20) == 0)\n                {\n                    tempLocation = newLoc(NPC[A].Location.X + 4, NPC[A].Location.Y + 4, 8, 8);\n                    if(!UnderWater[NPC[A].Section])\n                    {\n                        // set NewNpc to 0 -- pop when no longer in water\n                        NewEffect(EFFID_AIR_BUBBLE, tempLocation, 1, NPC[A].Shadow);\n                    }\n                    else\n                    {\n                        // set NewNpc to 1 -- keep forever\n                        if(NewEffect(EFFID_AIR_BUBBLE, tempLocation, 1, NPC[A].Shadow))\n                            Effect[numEffects].NewNpc = 1;\n                    }\n                }\n\n                if(iRand(100) >= 85)\n                    make_trail = true;\n                else\n                    make_trail = false;\n            }\n\n            if(make_trail)\n            {\n                if(NPC[A].Type == NPCID_PLR_ICEBALL)\n                {\n                    if(NPC[A].Special == 5)\n                    {\n                        NewEffect(EFFID_PLR_ICEBALL_TRAIL, NPC[A].Location, 1, NPC[A].Shadow);\n                        if(iRand(5) == 0)\n                        {\n                            tempLocation.Height = EffectHeight[EFFID_SPARKLE];\n                            tempLocation.Width = EffectWidth[EFFID_SPARKLE];\n                            tempLocation.SpeedX = 0;\n                            tempLocation.SpeedY = 0;\n                            tempLocation.X = NPC[A].Location.X + dRand() * 16 - EffectWidth[EFFID_SPARKLE] * 0.5_n - 4 - NPC[A].Location.SpeedX * 3;\n                            tempLocation.Y = NPC[A].Location.Y + dRand() * 16 - EffectHeight[EFFID_SPARKLE] * 0.5_n - 4;\n                            NewEffect(EFFID_SPARKLE, tempLocation);\n                            Effect[numEffects].Location.SpeedX = NPC[A].Location.SpeedX / 2;\n                            Effect[numEffects].Location.SpeedY = NPC[A].Location.SpeedY / 2;\n                            Effect[numEffects].Frame = iRand(3);\n                        }\n                    }\n                    else if(iRand(5) >= 3)\n                    {\n                        NewEffect_IceSparkle(NPC[A], tempLocation);\n                        Effect[numEffects].Location.SpeedX = NPC[A].Location.SpeedX / 4;\n                        Effect[numEffects].Location.SpeedY = NPC[A].Location.SpeedY / 4;\n                        Effect[numEffects].Frame = iRand(3);\n                    }\n                }\n                else\n                    NewEffect(EFFID_PLR_FIREBALL_TRAIL, NPC[A].Location, NPC[A].Special, NPC[A].Shadow);\n            }\n        }\n\n        NPC[A].FrameCount += 1;\n\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].FrameCount = 0;\n            NPC[A].Frame += -NPC[A].Direction;\n        }\n\n        if(NPC[A].Special < 2 || (NPC[A].Type == NPCID_PLR_ICEBALL && NPC[A].Special != 5))\n        {\n            if(NPC[A].Frame >= 4)\n                NPC[A].Frame = 0;\n            if(NPC[A].Frame < 0)\n                NPC[A].Frame = 3;\n        }\n        else if(NPC[A].Special == 2 || (NPC[A].Type == NPCID_PLR_ICEBALL && NPC[A].Special == 5))\n        {\n            if(NPC[A].Frame >= 7)\n                NPC[A].Frame = 4;\n            if(NPC[A].Frame < 4)\n                NPC[A].Frame = 6;\n        }\n        else if(NPC[A].Special == 3)\n        {\n            if(NPC[A].Frame >= 11)\n                NPC[A].Frame = 8;\n            if(NPC[A].Frame < 8)\n                NPC[A].Frame = 10;\n        }\n        else if(NPC[A].Special == 4)\n        {\n            if(NPC[A].Frame >= 15)\n                NPC[A].Frame = 12;\n            if(NPC[A].Frame < 12)\n                NPC[A].Frame = 14;\n        }\n        else if(NPC[A].Special == 5)\n        {\n            if(NPC[A].Frame >= 19)\n                NPC[A].Frame = 16;\n            if(NPC[A].Frame < 16)\n                NPC[A].Frame = 18;\n        }\n    }\n    else if(NPC[A].Type == NPCID_MINIBOSS) // Frame finder for Big Koopa\n    {\n        if(NPC[A].Special == 0)\n        {\n            if(NPC[A].Location.SpeedY != 0)\n                NPC[A].Frame = 0;\n            else\n            {\n                if(NPC[A].FrameCount >= 0)\n                    NPC[A].FrameCount += 1;\n                else\n                    NPC[A].FrameCount -= 1;\n                if(NPC[A].FrameCount >= 5 || NPC[A].FrameCount <= -5)\n                {\n                    if(NPC[A].FrameCount >= 0)\n                    {\n                        NPC[A].Frame += 1;\n                        NPC[A].FrameCount = 1;\n                    }\n                    else\n                    {\n                        NPC[A].Frame -= 1;\n                        NPC[A].FrameCount = -1;\n                    }\n                    if(NPC[A].Frame >= 5)\n                    {\n                        NPC[A].Frame = 3;\n                        NPC[A].FrameCount = -1;\n                    }\n                    else if(NPC[A].Frame <= 0)\n                    {\n                        NPC[A].Frame = 2;\n                        NPC[A].FrameCount = 1;\n                    }\n                }\n            }\n        }\n        else if(NPC[A].Special == 1)\n            NPC[A].Frame = 6;\n        else if(NPC[A].Special == 4)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].Frame < 7)\n                NPC[A].Frame = 7;\n            if(NPC[A].FrameCount >= 8)\n            {\n                NPC[A].FrameCount = 0;\n                if(NPC[A].Frame == 7)\n                    NPC[A].Frame = 8;\n                else\n                    NPC[A].Frame = 7;\n            }\n        }\n        else\n            NPC[A].Frame = 5;\n    }\n    else if(NPC[A].Type == NPCID_STONE_S3 || NPC[A].Type == NPCID_STONE_S4) // Thwomp\n    {\n        // Bullet Bills / Key / ONLY DIRECTION FRAMES\n    }\n    else if(NPC[A].Type == NPCID_BULLET || NPC[A].Type == NPCID_BIG_BULLET || NPC[A].Type == NPCID_KEY || NPC[A].Type == NPCID_STATUE_S3 || NPC[A].Type == NPCID_CIVILIAN || NPC[A].Type == NPCID_CHAR3 || NPCIsYoshi(NPC[A].Type) || NPC[A].Type == NPCID_CHAR2 || NPC[A].Type == NPCID_CHAR5 || NPC[A].Type == NPCID_STATUE_S4)\n    {\n        if(NPC[A].Direction == -1)\n            NPC[A].Frame = 0;\n        else\n            NPC[A].Frame = 1;\n        // Leaf\n    }\n    else if(NPC[A].Type == NPCID_LEAF_POWER)\n    {\n        if(NPC[A].Direction == -1)\n            NPC[A].Frame = 1;\n        else\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A].Type == NPCID_BLU_GUY || NPC[A].Type == NPCID_RED_GUY || NPC[A].Type == NPCID_RED_FISH_S1 || (NPC[A].Type >= NPCID_BIRD && NPC[A].Type <= NPCID_GRY_SPIT_GUY) || NPC[A].Type == NPCID_WALK_BOMB_S2 || NPC[A].Type == NPCID_CARRY_BUDDY) // Shy guys / Jumping Fish\n    {\n        if(NPC[A].Type == NPCID_WALK_BOMB_S2 && NPC[A].Special2 == 1)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 4)\n                NPC[A].Frame = 8;\n            else if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 9;\n            else if(NPC[A].FrameCount < 11)\n                NPC[A].Frame = 10;\n            else\n            {\n                NPC[A].Frame = 10;\n                NPC[A].FrameCount = 0;\n            }\n            if(NPC[A].HoldingPlayer > 0 || NPC[A].Projectile)\n                NPC[A].Frame += 6;\n            if(NPC[A].Direction == 1)\n                NPC[A].Frame += 3;\n        }\n        else if(NPC[A].HoldingPlayer == 0 && !NPC[A].Projectile)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].Direction == -1 && NPC[A].Frame >= 2)\n                NPC[A].Frame = 0;\n            else if(NPC[A].Direction == 1 && NPC[A].Frame < 2)\n                NPC[A].Frame = 2;\n            if(NPC[A].FrameCount >= 8)\n            {\n                NPC[A].FrameCount = 0;\n                if(NPC[A].Direction == -1)\n                {\n                    NPC[A].Frame += 1;\n                    if(NPC[A].Frame >= 2)\n                        NPC[A].Frame = 0;\n                }\n                else\n                {\n                    NPC[A].Frame += 1;\n                    if(NPC[A].Frame >= 4)\n                        NPC[A].Frame = 2;\n                }\n            }\n        }\n        else\n        {\n            if(NPC[A].Frame < 4)\n                NPC[A].Frame = 4;\n            NPC[A].FrameCount += 1;\n            if(NPC[A].Direction == -1 && NPC[A].Frame >= 6)\n                NPC[A].Frame = 4;\n            else if(NPC[A].Direction == 1 && NPC[A].Frame < 6)\n                NPC[A].Frame = 6;\n            if(NPC[A].FrameCount >= 4)\n            {\n                NPC[A].FrameCount = 0;\n                if(NPC[A].Direction == -1)\n                {\n                    NPC[A].Frame += 1;\n                    if(NPC[A].Frame >= 6)\n                        NPC[A].Frame = 4;\n                }\n                else\n                {\n                    NPC[A].Frame += 1;\n                    if(NPC[A].Frame >= 8)\n                        NPC[A].Frame = 6;\n                }\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_JUMPER_S3) // Bouncy Star things\n    {\n        if(NPC[A].HoldingPlayer == 0 && !NPC[A].Projectile)\n        {\n            if(NPC[A].Location.SpeedY == 0 || NPC[A].Slope > 0)\n            {\n                if(NPC[A].Direction == -1)\n                    NPC[A].Frame = 0;\n                else if(NPC[A].Direction == 1)\n                    NPC[A].Frame = 2;\n            }\n            else\n            {\n                if(NPC[A].Direction == -1)\n                    NPC[A].Frame = 1;\n                else if(NPC[A].Direction == 1)\n                    NPC[A].Frame = 3;\n            }\n        }\n        else\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].Direction == -1 && NPC[A].Frame >= 6)\n                NPC[A].Frame = 4;\n            else if(NPC[A].Direction == 1 && NPC[A].Frame < 6)\n                NPC[A].Frame = 6;\n            if(NPC[A].FrameCount >= 4)\n            {\n                NPC[A].FrameCount = 0;\n                if(NPC[A].Direction == -1)\n                {\n                    NPC[A].Frame += 1;\n                    if(NPC[A].Frame >= 6)\n                        NPC[A].Frame = 4;\n                }\n                else\n                {\n                    NPC[A].Frame += 1;\n                    if(NPC[A].Frame >= 8)\n                        NPC[A].Frame = 6;\n                }\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_CANNONITEM) // Bullet bill Gun\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].FrameCount = 1;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame == 5)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_PINK_CIVILIAN)\n    {\n        if(NPC[A].Location.SpeedX == 0)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 4)\n                NPC[A].Frame = 0;\n            else if(NPC[A].FrameCount < 7)\n                NPC[A].Frame = 1;\n            else\n            {\n                NPC[A].Frame = 1;\n                NPC[A].FrameCount = 0;\n            }\n        }\n        else\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount < 4)\n                NPC[A].Frame = 0;\n            else if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 1;\n            else if(NPC[A].FrameCount < 12)\n                NPC[A].Frame = 2;\n            else if(NPC[A].FrameCount < 15)\n                NPC[A].Frame = 3;\n            else\n            {\n                NPC[A].Frame = 3;\n                NPC[A].FrameCount = 0;\n            }\n        }\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 4;\n    }\n    else if(NPC[A].Type == NPCID_SPRING) // Spring thing\n    {\n        if(!LevelEditor)\n        {\n            if(NPC[A].Location.Height == 32)\n            {\n                NPC[A].Location.Height = 16;\n                NPC[A].Location.Y += 16;\n            }\n            if(NPC[A].HoldingPlayer > 0)\n                NPC[A].Frame = 0;\n            else\n            {\n                int C = 0;\n                Location_t tempLocation = NPC[A].Location;\n                tempLocation.Height = 24;\n                tempLocation.Y -= 8;\n                for(int B = 1; B <= numPlayers; ++B)\n                {\n                    if(CheckCollision(tempLocation, Player[B].Location) && Player[B].Mount != 2 && (Player[B].Location.SpeedY > 0 || Player[B].Location.SpeedY < Physics.PlayerJumpVelocity))\n                    {\n                        C = 2;\n                        break;\n                    }\n                }\n                if(C == 0)\n                {\n                    tempLocation = NPC[A].Location;\n                    tempLocation.Height = 32;\n                    tempLocation.Y -= 16;\n                    for(int B = 1; B <= numPlayers; ++B)\n                    {\n                        if(CheckCollision(tempLocation, Player[B].Location) && Player[B].Mount != 2 && (Player[B].Location.SpeedY > 0 || Player[B].Location.SpeedY < Physics.PlayerJumpVelocity))\n                        {\n                            C = 1;\n                            break;\n                        }\n                    }\n                }\n                NPC[A].Frame = C;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_SPIT_BOSS) // birdo\n    {\n        NPC[A].Frame = 0;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = 5;\n\n        // changed from Special when it became a container\n        if(NPC[A].Special5 == 0)\n        {\n            if(NPC[A].Location.SpeedX != 0)\n            {\n                NPC[A].FrameCount += 1;\n                if(NPC[A].FrameCount > 12)\n                    NPC[A].FrameCount = 0;\n                else if(NPC[A].FrameCount >= 6)\n                    NPC[A].Frame += 1;\n            }\n        }\n        else if(NPC[A].Special5 < 0)\n        {\n            NPC[A].Frame += 3;\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount > 8)\n                NPC[A].FrameCount = 0;\n            else if(NPC[A].FrameCount >= 4)\n                NPC[A].Frame += 1;\n        }\n        else\n            NPC[A].Frame += 2;\n    }\n    else if(NPC[A].Type == NPCID_KNIGHT) // Rat Head\n    {\n        NPC[A].Frame = NPC[A].FrameCount;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 2;\n    }\n    else if(NPC[A].Type == NPCID_HEAVY_THROWER) // SMB Hammer Bro\n    {\n        // the throw counter was previously Special3, and it has been moved to SpecialX\n        if(NPC[A].SpecialX >= 0)\n        {\n            if((NPC[A].Location.SpeedY < 1 && NPC[A].Location.SpeedY >= 0) || NPC[A].Slope > 0 || NPC[A].HoldingPlayer > 0)\n            {\n                NPC[A].FrameCount += 1;\n                if(NPC[A].Direction == -1 && NPC[A].Frame >= 2)\n                    NPC[A].Frame = 0;\n                else if(NPC[A].Direction == 1 && NPC[A].Frame < 3)\n                    NPC[A].Frame = 3;\n                if(NPC[A].FrameCount >= 8)\n                {\n                    NPC[A].FrameCount = 0;\n                    if(NPC[A].Direction == -1)\n                    {\n                        NPC[A].Frame += 1;\n                        if(NPC[A].Frame >= 2)\n                            NPC[A].Frame = 0;\n                    }\n                    else\n                    {\n                        NPC[A].Frame += 1;\n                        if(NPC[A].Frame >= 5)\n                            NPC[A].Frame = 3;\n                    }\n                }\n            }\n            else\n            {\n                if(NPC[A].Direction == -1)\n                    NPC[A].Frame = 0;\n                else\n                    NPC[A].Frame = 3;\n            }\n        }\n        else\n        {\n            if(NPC[A].Direction == -1)\n                NPC[A].Frame = 2;\n            else\n                NPC[A].Frame = 5;\n        }\n    }\n    else if(NPC[A].Type == NPCID_PET_FIRE) // Yoshi Fireball\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n        {\n            NPC[A].Frame = 1;\n            NPC[A].FrameCount = 0;\n        }\n        else if(NPC[A].FrameCount > 4)\n            NPC[A].Frame = 1;\n        else\n            NPC[A].Frame = 0;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 2;\n    }\n    else if(NPC[A].Type == NPCID_GRN_BOOT || NPC[A].Type == NPCID_RED_BOOT || NPC[A].Type == NPCID_BLU_BOOT) // Goombas Shoe\n    {\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = 2 + SpecialFrame[1];\n        else\n            NPC[A].Frame = 0 + SpecialFrame[1];\n    }\n    else if(NPC[A].Type == NPCID_GHOST_S3 || NPC[A].Type == NPCID_GHOST_S4 || NPC[A].Type == NPCID_BIG_GHOST) // Boo\n    {\n        NPC[A].Frame = 0;\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame = 2;\n        if(NPC[A].Special == 1 || NPC[A].HoldingPlayer > 0)\n            NPC[A].Frame += 1;\n    }\n    else if(NPC[A].Type == NPCID_GOALORB_S2) // smb2 birdo exit\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n        {\n            NPC[A].FrameCount = 1;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame == 8)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_STAR_EXIT) // SMB3 Star\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].Special == 0)\n        {\n            if(NPC[A].FrameCount < 8)\n                NPC[A].Frame = 0;\n            else if(NPC[A].FrameCount < 12)\n                NPC[A].Frame = 1;\n            else if(NPC[A].FrameCount < 16)\n                NPC[A].Frame = 2;\n            else if(NPC[A].FrameCount < 20)\n                NPC[A].Frame = 1;\n            else\n                NPC[A].FrameCount = 0;\n        }\n        else\n        {\n            if(NPC[A].FrameCount < 60)\n                NPC[A].Frame = 2;\n            // ElseIf .FrameCount < 64 Then\n            // .Frame = 1\n            else\n                NPC[A].FrameCount = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_FIRE_POWER_S4 || NPC[A].Type == NPCID_ICE_POWER_S4)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 12)\n        {\n            NPC[A].FrameCount = 1;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame == 2)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_FIRE_POWER_S1)\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 4)\n        {\n            NPC[A].FrameCount = 1;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame == 4)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_SPIKY_BALL_S3 || NPC[A].Type == NPCID_WALL_SPARK)\n    {\n        NPC[A].FrameCount += 2;\n        if(NPC[A].FrameCount >= 8)\n        {\n            NPC[A].FrameCount = 1;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame == 2)\n                NPC[A].Frame = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_COIN_S2)\n    {\n        NPC[A].Frame = CoinFrame[2];\n    }\n    else if(NPC[A].Type == NPCID_RING)\n    {\n        NPC[A].Frame = CoinFrame[3];\n    }\n    // non-type based logic\n    else if(NPC[A]->IsACoin) // Coin\n    {\n        NPC[A].Frame = CoinFrame[3];\n    }\n    else if(NPC[A]->IsAVine || NPC[A]->IsABonus) // no frames for these\n    {\n        if(A == 0)\n            NPC[A].Frame = 0;\n    }\n    else if(NPC[A]->IsAShell) // Turtle shell\n    {\n        if(NPC[A].Location.SpeedX == 0)\n            NPC[A].Frame = 0;\n        else\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount >= 4)\n            {\n                NPC[A].FrameCount = 0;\n                NPC[A].Frame += 1;\n                if(NPC[A].Frame >= 4)\n                    NPC[A].Frame = 0;\n            }\n        }\n    }\n    else // Frame finder for everything else\n    {\n        NPC[A].FrameCount += 1;\n        if(NPC[A].FrameCount >= 8)\n        {\n            NPC[A].FrameCount = 1;\n            NPC[A].Frame += 1;\n            if(NPC[A].Frame == 2)\n                NPC[A].Frame = 0;\n        }\n    }\n}\n"
  },
  {
    "path": "src/npc/npc_hit.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"config.h\"\n\n#include \"../globals.h\"\n#include \"../npc.h\"\n#include \"../sound.h\"\n#include \"../collision.h\"\n#include \"../effect.h\"\n#include \"../editor.h\"\n#include \"../game_main.h\"\n#include \"../blocks.h\"\n#include \"../graphics.h\"\n#include \"../npc_id.h\"\n#include \"../eff_id.h\"\n#include \"../layers.h\"\n\n#include \"npc_traits.h\"\n\n#include \"npc/npc_queues.h\"\n#include \"main/trees.h\"\n\n#include <Logger/logger.h>\n\nvoid NPCHit(int A, int B, int C)\n{\n    // NPC_t tempNPC;\n    // Location_t tempLocation;\n\n    // if(B == 1 && C != 0)\n    //     Controls::Rumble(C, 50, .25);\n\n    // ------+  HIT CODES  +-------\n    // B = 1      Jumped on by a player (or kicked)\n    // B = 2      Hit by a shaking block\n    // B = 3      Hit by projectile\n    // B = 4      Hit something as a projectile\n    // B = 5      Hit something while being held\n    // B = 6      Touched a lava block\n    // B = 7      Hit by a tail\n    // B = 8      Stomped by Boot\n    // B = 9      Fell of a cliff\n    // B = 10     Link stab\n    // Frost Bolt check\n    if(B == 3 && NPC[A].Type != NPCID_ICE_CUBE && NPC[A].Type != NPCID_PLR_ICEBALL)\n    {\n        if(NPC[C].Type == NPCID_PLR_ICEBALL && NPC[A].Location.Width > 8 && NPC[A].Location.Height > 8)\n        {\n            if(NPC[A].Type == NPCID_ITEM_BUBBLE)\n            {\n                if(NPC[3].Type == NPCID_PLR_ICEBALL)\n                {\n                    // FIXME: warn user that game would have crashed\n\n                    if(NPC[0].Type == NPCID_PLR_ICEBALL)\n                        NPC[0].Type = NPCID_PLR_FIREBALL;\n\n                    NPCHit(A, 3, 0);\n                }\n                else\n                {\n                    // B is always 3, this caused a crash if NPC[3].Type was NPCID_PLR_ICEBALL\n                    NPCHit(A, 3, B);\n                }\n            }\n\n            if(NPC[A]->NoIceBall || NPC[A].Location.Width > 128 || NPC[A].Location.Height > 128)\n                return;\n\n            bool reset_frame = true;\n\n            if(NPC[A].Type == NPCID_RED_FLY_FODDER)\n                NPC[A].Type = NPCID_RED_FODDER;\n            else if(NPC[A].Type == NPCID_GRN_TURTLE_S3)\n                NPC[A].Type = NPCID_GRN_SHELL_S3;\n            else if(NPC[A].Type == NPCID_RED_TURTLE_S3 || NPC[A].Type == NPCID_RED_FLY_TURTLE_S3)\n                NPC[A].Type = NPCID_RED_SHELL_S3;\n            else if(NPC[A].Type == NPCID_BIG_TURTLE)\n                NPC[A].Type = NPCID_BIG_SHELL;\n            else if(NPC[A].Type == NPCID_GRN_FLY_TURTLE_S3)\n                NPC[A].Type = NPCID_GRN_SHELL_S3;\n            else if(NPC[A].Type == NPCID_GRN_TURTLE_S4 || NPC[A].Type == NPCID_GRN_FLY_TURTLE_S4)\n                NPC[A].Type = NPCID_GRN_SHELL_S4;\n            else if(NPC[A].Type == NPCID_RED_TURTLE_S4 || NPC[A].Type == NPCID_RED_FLY_TURTLE_S4)\n                NPC[A].Type = NPCID_RED_SHELL_S4;\n            else if(NPC[A].Type == NPCID_BLU_TURTLE_S4 || NPC[A].Type == NPCID_BLU_FLY_TURTLE_S4)\n                NPC[A].Type = NPCID_BLU_SHELL_S4;\n            else if(NPC[A].Type == NPCID_YEL_TURTLE_S4 || NPC[A].Type == NPCID_YEL_FLY_TURTLE_S4)\n                NPC[A].Type = NPCID_YEL_SHELL_S4;\n            else if(NPC[A].Type == NPCID_RED_TURTLE_S1 || NPC[A].Type == NPCID_RED_FLY_TURTLE_S1)\n                NPC[A].Type = NPCID_RED_SHELL_S1;\n            else if(NPC[A].Type == NPCID_GRN_TURTLE_S1 || NPC[A].Type == NPCID_GRN_FLY_TURTLE_S1)\n                NPC[A].Type = NPCID_GRN_SHELL_S1;\n            else if(NPC[A].Type == NPCID_FLY_FODDER_S5)\n                NPC[A].Type = NPCID_FODDER_S5;\n            else if(NPC[A].Type == NPCID_FLY_FODDER_S3)\n                NPC[A].Type = NPCID_FODDER_S3;\n            else\n                reset_frame = false;\n\n            if(reset_frame)\n                NPC[A].Frame = EditorNPCFrame(NPC[A].Type, NPC[A].Direction);\n\n            NPC[A].Wings = WING_NONE;\n\n            NPC[A].Special = NPC[A].Type;\n            NPC[A].Special2 = NPC[A].Frame;\n            // If .Type = 52 Or .Type = 51 Then\n            // End If\n\n            if(NPC[A].Type == NPCID_SIDE_PLANT)\n            {\n                if(NPC[A].Direction == -1)\n                    NPC[A].Location.Width = num_t::floor(NPC[A].Location.Width) - 0.01_n;\n                else\n                    NPC[A].Location.X = num_t::floor(NPC[A].Location.X) + 0.01_n;\n\n                NPCQueues::Unchecked.push_back(A);\n            }\n\n            NPC[A].Location.Height = num_t::floor(NPC[A].Location.Height);\n            NPC[A].Type = NPCID_ICE_CUBE;\n            NPC[A].BeltSpeed = 0;\n            NPC[A].RealSpeedX = 0;\n            NPC[A].Projectile = false;\n\n            if(NPC[A].Effect == NPCEFF_MAZE)\n            {\n                // disable no-gravity for an item frozen within a maze\n                NPC[A].Special3 = 0;\n                NPC[A].Projectile = true;\n\n                if(NPC[A].Effect3 != NPC[C].Effect3)\n                    NPC[A].TurnAround = true;\n            }\n            else\n            {\n                // this freezes the item in mid-air until it's grabbed\n                NPC[A].Special3 = 1;\n                NPC[A].Location.SpeedY = 0;\n                NPC[A].Location.SpeedX = 0;\n            }\n\n            NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n            Location_t tempLocation;\n            for(C = 1; C <= 20; C++)\n            {\n                NewEffect_IceSparkle(NPC[A], tempLocation);\n                Effect[numEffects].Location.SpeedX = dRand() * 2 - 1;\n                Effect[numEffects].Location.SpeedY = dRand() * 2 - 1;\n                Effect[numEffects].Frame = iRand(3);\n            }\n\n            PlaySoundSpatial(SFX_Freeze, NPC[A].Location);\n            // NPCHit C, 3, C\n\n            treeNPCUpdate(A);\n            if(NPC[A].tempBlock > 0)\n                treeNPCSplitTempBlock(A);\n            return;\n        }\n    }\n\n    // Online code\n    //    if(nPlay.Online == true)\n    //    {\n    //        if(B == 1 || B == 7 || B == 8)\n    //        {\n    //            if(C == nPlay.MySlot + 1 || nPlay.Allow == true || C <= 0)\n    //            {\n    //                if(C == nPlay.MySlot + 1)\n    //                    Netplay::sendData \"2d\" + std::to_string(A) + \"|\" + std::to_string(B) + \"|\" + std::to_string(C) + \"|\" + NPC[A].Type + LB;\n    //            }\n    //            else\n    //                return;\n    //        }\n    //    }\n\n    if(!NPC[A].Active)\n        return;\n\n\n    // Safety\n    StopHit += 1;\n    if(NPC[A].Killed > 0)\n        return;\n    if(B == 3 || B == 4)\n    {\n        if(NPC[C].Generator)\n            return;\n    }\n    if((NPC[A].Type == NPCID_SQUID_S1 || NPC[A].Type == NPCID_SQUID_S3 || NPC[A]->IsFish) && B == 1)\n    {\n        if(Player[C].Wet > 0)\n            return;\n    }\n    if(NPC[A].Inert || StopHit > 2 || NPC[A].Immune > 0 || NPC[A].Effect == NPCEFF_ENCASED || NPC[A].Generator)\n        return;\n    if(B == 3 || B == 4 || B == 5) // Things immune to fire\n    {\n        if(NPC[C].Type == NPCID_PLR_FIREBALL)\n        {\n            if(NPC[A]->NoFireBall)\n                return;\n        }\n    }\n\n\n    if(B == 1 && NPC[A]->JumpHurt && NPC[A].Type != NPCID_ITEM_BUBBLE) // Things that don't die from jumping\n        return;\n\n    if(B == 10 && NPC[A].Type == NPCID_KEY)\n    {\n        if(Player[C].Character == 5 && !Player[C].HasKey)\n        {\n            NPC[A].Killed = 9;\n            NPCQueues::Killed.push_back(A);\n            Player[C].HasKey = true;\n            PlaySoundSpatial(SFX_HeroKey, NPC[A].Location);\n            return;\n        }\n    }\n\n    // NEWLY used to check when NPC's tree location needs to be updated\n    NPC_t oldNPC = NPC[A];\n\n    // BEGIN NORMAL LOGIC: giant switch over NPC's type\n\n    // Yoshi Ice\n    if(NPC[A].Type == NPCID_ICE_BLOCK)\n    {\n        if(B != 1 && B != 7 && B != 8 && B != 4)\n            NPC[A].Killed = B;\n    }\n    // Frozen NPC\n    else if(NPC[A].Type == NPCID_ICE_CUBE)\n    {\n        if(B == 3 && NPC[C].Type == NPCID_PLR_FIREBALL)\n        {\n            NPC[A].Type = NPCID(NPC[A].Special);\n\n            if(NPC[A].Location.SpeedX > 0)\n                NPC[A].Direction = 1;\n            else if(NPC[A].Location.SpeedX < 0)\n                NPC[A].Direction = -1;\n            else\n                NPC[A].Direction = NPC[A].DefaultDirection;\n\n            NPC[A].Frame = EditorNPCFrame(NPC[A].Type, NPC[A].Direction);\n            NPC[A].Special = NPC[A].DefaultSpecial;\n            NPC[A].Special2 = 0;\n            NPC[A].Special3 = 0;\n            NPC[A].Special4 = 0;\n            NPC[A].Special5 = 0;\n            // NPC[A].Special6 = 0;\n            NPC[A].SpecialX = 0;\n            NPC[A].SpecialY = 0;\n\n            if(NPC[A].Effect == NPCEFF_MAZE)\n                NPC[A].Projectile = false;\n        }\n        else if(B == 3 || B == 5)\n        {\n            if(A == C || B == 6 || B == 5)\n                NPC[A].Killed = B;\n\n            if(B == 3 && NPC[C].Type == NPC[A].Type)\n                NPC[A].Killed = B;\n        }\n        else if(B == 10 || B == 2)\n            NPC[A].Killed = B;\n    }\n    // Things that link can move with his sword\n    else if(B == 10 && NPC[A].Type == NPCID_BOMB && NPC[A].CantHurt == 0 && !NPC[A].Projectile) // link picks up bombs\n    {\n        if(Player[C].Bombs < 9)\n            Player[C].Bombs += 1;\n        // .Location.X += .Location.Width / 2 - EffectWidth(10) / 2\n        // .Location.Y += .Location.Height / 2 - EffectHeight(10) / 2\n        // NewEffect 10, .Location\n        NPC[A].Killed = 9;\n        PlaySoundSpatial(SFX_HeroHeart, NPC[A].Location);\n    }\n    else if(B == 10 && ((NPC[A].Type >= NPCID_CARRY_BLOCK_A && NPC[A].Type <= NPCID_CARRY_BLOCK_D) || NPC[A].Type == NPCID_SPRING || NPC[A].Type == NPCID_COIN_SWITCH || NPC[A].Type == NPCID_TIME_SWITCH || NPC[A].Type == NPCID_EARTHQUAKE_BLOCK || NPC[A].Type == NPCID_ITEM_POD || NPC[A].Type == NPCID_CANNONITEM || NPC[A].Type == NPCID_BOMB))\n    {\n        PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n        NPC[A].Bouce = true;\n        if((NPC[A].Type >= NPCID_CARRY_BLOCK_A && NPC[A].Type <= NPCID_CARRY_BLOCK_D) || NPC[A].Type == NPCID_CANNONITEM)\n        {\n            NPC[A].Location.SpeedX = 3 * Player[C].Direction;\n            NPC[A].Location.SpeedY = -5;\n        }\n        else\n        {\n            NPC[A].Location.SpeedX = 4 * Player[C].Direction;\n            NPC[A].Location.SpeedY = -4;\n        }\n        NPC[A].Projectile = true;\n        NPC[A].CantHurt = 30;\n        NPC[A].CantHurtPlayer = C;\n        NPC[A].BattleOwner = C;\n        if(NPC[A].Type == NPCID_CANNONITEM)\n            NPC[A].Direction = Player[C].Direction;\n    }\n    // SMB2 Grass\n    else if(B == 10 && (NPC[A].Type == NPCID_ITEM_BURIED || NPCIsVeggie(NPC[A].Type)))\n    {\n        // Unbury code (v2)\n        if(NPC[A].Type == NPCID_ITEM_BURIED)\n        {\n            NPC[A].Location.Y += -NPC[A].Location.Height;\n            NPC[A].Type = NPCID(NPC[A].Special);\n            NPC[A].Wings = NPC[A].DefaultWings;\n        }\n\n        PlaySoundSpatial(SFX_HeroGrass, NPC[A].Location);\n        NewEffect(EFFID_SMOKE_S5, NPC[A].Location);\n\n        if(NPC[A].Type == NPCID_BULLET)\n        {\n            PlaySoundSpatial(SFX_Bullet, NPC[A].Location);\n            NPC[A].Location.SpeedX = 5 * Player[C].Direction;\n            NPC[A].Location.Y += NPC[A].Location.Height;\n        }\n\n        NPC[A].Direction = Player[C].Direction;\n\n        NPCUnbury(A, 2);\n\n        NPC[A].Location.Y += -NPC[A].Location.Height;\n        NPC[A].Location.SpeedX = (3 + dRand()) * Player[C].Direction;\n        if(NPC[A].Type == NPCID_BULLET)\n            NPC[A].Location.SpeedX = 5 * Player[C].Direction;\n        NPC[A].Location.SpeedY = -4;\n        NPC[A].CantHurtPlayer = C;\n        NPC[A].CantHurt = 30;\n        if(NPCIsVeggie(NPC[A].Type) || NPC[A].Type == NPCID_HEAVY_THROWER || NPC[A].Type == NPCID_GEM_1 || NPC[A].Type == NPCID_GEM_5)\n        {\n            if(NPC[A].Type != NPCID_GEM_5)\n                NPC[A].Type = NPCID_GEM_1;\n            NPC[A].Location.SpeedX = (1 + dRand() / 2) * Player[C].Direction;\n            NPC[A].Location.SpeedY = -5;\n            if(iRand(20) < 3)\n                NPC[A].Type = NPCID_GEM_5;\n            if(iRand(40) < 3)\n                NPC[A].Type = NPCID_GEM_20;\n            NPC[A].Location.set_width_center(NPC[A]->TWidth);\n            NPC[A].Location.set_height_floor(NPC[A]->THeight);\n        }\n        if(NPC[A]->IsAShell)\n            NPC[A].Location.SpeedX = Physics.NPCShellSpeed * Player[C].Direction;\n\n        NPCQueues::Unchecked.push_back(A);\n\n        NPCFrames(A);\n\n        if(!NPC[A]->IsACoin)\n            NPC[A].Projectile = true;\n        else\n            NPC[A].Special = 1;\n\n        NPC[A].Immune = 10;\n\n        if(NPC[A].Type == NPCID_BOMB)\n        {\n            // .Location.SpeedX = 5 * Player(C).Direction + Player(C).Location.SpeedX\n            // .Location.SpeedY = -5\n            NPC[A].Projectile = false;\n        }\n\n        if(Player[C].StandingOnNPC == A)\n            Player[C].StandingOnNPC = 0;\n    }\n\n    // bubble\n    else if(NPC[A].Type == NPCID_ITEM_BUBBLE)\n        NPC[A].Special3 = 1;\n\n    // Larry Koopa\n    else if(NPC[A].Type == NPCID_MAGIC_BOSS || NPC[A].Type == NPCID_FIRE_BOSS)\n    {\n        if(B != 7)\n            NPC[A].Immune = 10;\n        if(B == 1 || B == 2 || B == 8)\n        {\n            NPC[A].Damage += 5;\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            NPC[A].Special = 5;\n            if(NPC[A].Type == NPCID_MAGIC_BOSS && NPC[A].Damage < 15)\n                PlaySoundSpatial(SFX_MagicBossShell, NPC[A].Location);\n        }\n        else if(B == 3 || B == 4 || B == 5)\n        {\n            if(NPC[C].Type == NPCID_PLR_FIREBALL || NPC[C].Type == NPCID_PET_FIRE)\n            {\n                NPC[A].Damage += 1;\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            }\n            else\n            {\n                NPCHit(C, 3, B);\n                NPC[A].Special = 5;\n                NPC[A].Damage += 5;\n                PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                if(NPC[A].Type == NPCID_MAGIC_BOSS && NPC[A].Damage < 15)\n                    PlaySoundSpatial(SFX_MagicBossShell, NPC[A].Location);\n            }\n        }\n        else if(B == 10)\n        {\n            NPC[A].Damage += 2;\n            PlaySoundSpatial(SFX_HeroHit, NPC[A].Location);\n        }\n        else if(B == 6)\n            NPC[A].Killed = B;\n        if(NPC[A].Damage >= 15)\n            NPC[A].Killed = B;\n        else if(NPC[A].Special == 5 && !(NPC[A].Type == NPCID_MAGIC_BOSS_SHELL || NPC[A].Type == NPCID_FIRE_BOSS_SHELL))\n        {\n            NPC[A].Special = 0;\n            NPC[A].Special2 = 0;\n            NPC[A].Special3 = 0;\n            NPC[A].Special4 = 0;\n            NPC[A].Special5 = 0;\n            // NPC[A].Special6 = 0;\n            NPC[A].SpecialX = 0;\n            NPC[A].SpecialY = 0;\n            if(NPC[A].Type == NPCID_MAGIC_BOSS)\n                NPC[A].Type = NPCID_MAGIC_BOSS_SHELL;\n            else\n                NPC[A].Type = NPCID_FIRE_BOSS_SHELL;\n            NPC[A].Location.set_width_center(NPC[A]->TWidth);\n            NPC[A].Location.set_height_floor(NPC[A]->THeight);\n            NPC[A].Location.SpeedX = 0;\n            NPC[A].Location.SpeedY = 0;\n\n            // update now because oldNPC (which we use to check for deferred update later) has been updated\n            NPCQueues::Unchecked.push_back(A);\n            treeNPCUpdate(A);\n            if(NPC[A].tempBlock > 0)\n                treeNPCSplitTempBlock(A);\n\n            oldNPC = NPC[A];\n        }\n\n    }\n    // Larry Koop Shell\n    else if(NPC[A].Type == NPCID_MAGIC_BOSS_SHELL || NPC[A].Type == NPCID_FIRE_BOSS_SHELL)\n    {\n        if(B != 7 && B != 1 && B != 2 && B != 8)\n            NPC[A].Immune = 10;\n        if(B == 1 || B == 2 || B == 8)\n        {\n            if(B == 1 || B == 8)\n            {\n\n                if(NPC[A].Location.to_right_of(Player[C].Location))\n                    Player[C].Location.SpeedX -= 3;\n                else\n                    Player[C].Location.SpeedX += 3;\n\n            }\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n        }\n        else if(B == 3 || B == 4 || B == 5)\n        {\n            if(NPC[C].Type == NPCID_PLR_FIREBALL || NPC[C].Type == NPCID_PET_FIRE)\n            {\n                NPC[A].Damage += 1;\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            }\n            else\n            {\n                NPCHit(C, 3, B);\n                NPC[A].Special = 5;\n                NPC[A].Damage += 5;\n                PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n            }\n        }\n        else if(B == 10)\n        {\n            NPC[A].Damage += 2;\n            PlaySoundSpatial(SFX_HeroHit, NPC[A].Location);\n        }\n        else if(B == 6)\n            NPC[A].Killed = B;\n        if(NPC[A].Damage >= 15)\n            NPC[A].Killed = B;\n    }\n    // Zelda 2 Locked Door\n    else if(NPC[A].Type == NPCID_LOCK_DOOR)\n    {\n        if(B == 10)\n        {\n            if(Player[C].HasKey)\n            {\n                Player[C].HasKey = false;\n                NPC[A].Killed = 3;\n            }\n        }\n    }\n    // Goomba / Nekkid Koopa\n    else if(NPC[A].Type == NPCID_FODDER_S3 || NPC[A].Type == NPCID_RED_FODDER || NPC[A].Type == NPCID_FODDER_S5 || NPC[A].Type == NPCID_UNDER_FODDER ||\n            NPC[A].Type == NPCID_EXT_TURTLE || NPC[A].Type == NPCID_YELSWITCH_FODDER || NPC[A].Type == NPCID_BLUSWITCH_FODDER || NPC[A].Type == NPCID_GRNSWITCH_FODDER ||\n            NPC[A].Type == NPCID_REDSWITCH_FODDER || NPC[A].Type == NPCID_BIG_FODDER || NPC[A].Type == NPCID_JUMPER_S4 || NPC[A].Type == NPCID_BAT ||\n            NPC[A].Type == NPCID_FODDER_S1 || (NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4) || NPC[A].Type == NPCID_BRUTE ||\n            NPC[A].Type == NPCID_BRUTE_SQUISHED || NPC[A].Type == NPCID_GRN_FISH_S3 || NPC[A].Type == NPCID_YEL_FISH_S4 || NPC[A].Type == NPCID_RED_FISH_S3 ||\n            NPC[A].Type == NPCID_GRN_FISH_S4 || NPC[A].Type == NPCID_GRN_FISH_S1 || NPC[A].Type == NPCID_BONE_FISH)\n    {\n        if(B == 1)\n        {\n            if(NPC[A].Type == NPCID_BRUTE && !NPC[A].Wings)\n            {\n                NPC[A].Location.set_height_floor(32);\n                NPC[A].Type = NPCID_BRUTE_SQUISHED;\n                PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            }\n            else if(NPC[A].Type != NPCID_BONE_FISH)\n                NPC[A].Killed = B;\n        }\n        else if(NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4)\n        {\n            if(B == 3 && NPC[A].CantHurt > 0)\n            {\n            }\n            else\n                NPC[A].Killed = B;\n        }\n        else\n        {\n            if(NPC[A].Type == NPCID_BONE_FISH && B == 3)\n            {\n                if(NPC[C].Type != NPCID_PLR_FIREBALL && NPC[C].Type != NPCID_PET_FIRE)\n                    NPC[A].Killed = B;\n            }\n            else\n                NPC[A].Killed = B;\n        }\n    }\n    // Mother Brain\n    else if(NPC[A].Type == NPCID_BOSS_FRAGILE)\n    {\n        // int D = 0;\n        bool tempBool;\n        tempBool = false;\n\n        for(int D : treeNPCQuery(NPC[A].Location, SORTMODE_NONE))\n        {\n            if(NPC[D].Type == NPCID_BOSS_CASE)\n            {\n                if(NPC[D].Active)\n                {\n                    if(NPC[D].Section == NPC[A].Section)\n                    {\n                        if(CheckCollision(NPC[A].Location, NPC[D].Location))\n                        {\n                            tempBool = true;\n                            NPC[A].Immune = 65;\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n        if(!tempBool)\n        {\n            if(B == 3 || B == 10)\n            {\n                if(NPC[C].Type == NPCID_PLR_FIREBALL)\n                {\n                }\n                else\n                {\n                    if(NPC[C].Type == NPCID_PLR_HEAVY)\n                        NPC[A].Immune = 60;\n                    else\n                        NPC[A].Immune = 20;\n                    NPC[A].Special = 1;\n                    PlaySoundSpatial(SFX_SMBossHit, NPC[A].Location);\n                    NPC[A].Damage += 1;\n                    if(NPC[A].Damage >= 10)\n                        NPC[A].Killed = 3;\n                }\n            }\n        }\n    }\n    // Metroid Cherrio\n    else if(NPC[A].Type == NPCID_HOMING_BALL)\n    {\n        if(B == 3 || B == 4 || B == 5 || B == 7 || B == 9 || B == 10)\n            NPC[A].Killed = B;\n    }\n    // Metroid Glass\n    else if(NPC[A].Type == NPCID_BOSS_CASE)\n    {\n        if(B == 3)\n        {\n            PlaySoundSpatial(SFX_SMBlockHit, NPC[A].Location);\n            if(NPC[C].Type == NPCID_PLR_FIREBALL)\n            {\n            }\n            else\n            {\n                if(NPC[C].Type == NPCID_PLR_HEAVY)\n                    NPC[A].Immune = 60;\n                else\n                    NPC[A].Immune = 20;\n                NPC[A].Damage += 3;\n            }\n        }\n        else if(B == 10)\n        {\n            NPC[A].Damage += 3;\n            NPC[A].Immune = 10;\n        }\n        if(NPC[A].Damage >= 15)\n            NPC[A].Killed = B;\n    }\n    // Metroid Floating Things\n    else if(NPC[A].Type == NPCID_FLIER || NPC[A].Type == NPCID_ROCKET_FLIER)\n    {\n        if(B == 3)\n        {\n            if(NPC[C].Type != NPCID_PLR_FIREBALL && NPC[C].Type != NPCID_PET_FIRE)\n                NPC[A].Killed = B;\n        }\n        else if(B == 2 || B == 4 || B == 5 || B == 6 || B == 9 || B == 10)\n            NPC[A].Killed = B;\n    }\n    // Spike Top\n    else if(NPC[A].Type == NPCID_WALL_TURTLE)\n    {\n        if(B == 3)\n        {\n            if(NPC[C].Type != NPCID_PLR_FIREBALL && NPC[C].Type != NPCID_PET_FIRE)\n                NPC[A].Killed = B;\n        }\n        else if(B != 1)\n            NPC[A].Killed = B;\n    }\n    // Metroid Crawler\n    else if(NPC[A].Type == NPCID_WALL_BUG)\n    {\n        if(B == 3)\n        {\n            if(NPC[C].Type == NPCID_PLR_FIREBALL)\n                NPC[A].Damage += 1;\n            else\n                NPC[A].Damage += 3;\n            if(NPC[A].Damage >= 3)\n                NPC[A].Killed = B;\n            else\n                PlaySoundSpatial(SFX_SMHurt, NPC[A].Location);\n        }\n        else if(B == 8)\n        {\n            NPC[A].Damage += 1;\n            if(NPC[A].Damage >= 3)\n                NPC[A].Killed = B;\n            else\n                PlaySoundSpatial(SFX_SMHurt, NPC[A].Location);\n        }\n        else if(B == 10)\n            NPC[A].Killed = B;\n        else if(B == 4 || B == 2 || B == 9 || B == 6)\n            NPC[A].Killed = B;\n    }\n    // mouser\n    else if(NPC[A].Type == NPCID_BOMBER_BOSS)\n    {\n        if(B == 1)\n        {\n        }\n        else if(B == 3)\n        {\n            if(NPC[C].Type != NPCID_PLR_FIREBALL)\n            {\n                NPC[A].Damage += 5;\n                NPC[A].Immune = 60;\n            }\n            else\n                NPC[A].Damage += 1;\n        }\n        else if(B == 6)\n        {\n            NPC[A].Killed = B;\n            PlaySoundSpatial(SFX_SickBossKilled, NPC[A].Location);\n        }\n        else if(B == 10)\n        {\n            NPC[A].Damage += 2;\n            NPC[A].Immune = 20;\n        }\n        if(NPC[A].Damage >= 20)\n        {\n            NPC[A].Killed = 3;\n            PlaySoundSpatial(SFX_SpitBossBeat, NPC[A].Location);\n        }\n        else if(B == 3)\n        {\n            if(NPC[C].Type == NPCID_PLR_FIREBALL)\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            else\n                PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n        }\n        else if(B == 10)\n            PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n    }\n    // Wart\n    else if(NPC[A].Type == NPCID_SICK_BOSS)\n    {\n        if(B == 1)\n        {\n        }\n        else if(B == 3)\n        {\n            if(NPCIsVeggie(NPC[C].Type))\n            {\n                if(NPC[A].Special == 1)\n                {\n                    PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                    NPC[A].Damage += 5;\n                    NPC[A].Immune = 20;\n                    NPC[C].Killed = 9;\n                    NPCQueues::Killed.push_back(C);\n                }\n            }\n            else\n            {\n                if(NPC[C].Type != NPCID_PLR_FIREBALL)\n                {\n                    PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                    NPC[A].Damage += 5;\n                    NPC[A].Immune = 20;\n                }\n                else\n                {\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                    NPC[A].Damage += 1;\n                }\n            }\n        }\n        else if(B == 6)\n        {\n            NPC[A].Killed = B;\n            PlaySoundSpatial(SFX_SickBossKilled, NPC[A].Location);\n        }\n        else if(B == 10)\n        {\n            PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n            NPC[A].Damage += 5;\n            NPC[A].Immune = 20;\n        }\n    }\n    // King Koopa\n    else if(NPC[A].Type == NPCID_VILLAIN_S1)\n    {\n        if(B == 1)\n        {\n        }\n        else if(B == 3)\n        {\n            NPC[A].Immune = 20;\n            if(NPC[C].Type != NPCID_PLR_FIREBALL)\n            {\n                PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                NPC[A].Damage += 3;\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NPC[A].Damage += 1;\n            }\n        }\n        else if(B == 10)\n        {\n            PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n            NPC[A].Immune = 20;\n            NPC[A].Damage += 1;\n        }\n        else if(B == 6)\n            NPC[A].Killed = B;\n        if(NPC[A].Damage >= 12)\n            NPC[A].Killed = B;\n        // SMW Dry Bones\n    }\n    else if(NPC[A].Type == NPCID_SKELETON)\n    {\n        if(B == 1 || B == 8 || B == 10)\n        {\n            PlaySoundSpatial(SFX_Skeleton, NPC[A].Location);\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            NPC[A].Special = 1;\n            NPC[A].Special2 = 0;\n            NPC[A].Inert = true;\n            NPC[A].Stuck = true;\n        }\n        else if(B == 3)\n        {\n            if(NPC[C].Type == NPCID_PLR_FIREBALL || NPC[C].Type == NPCID_PET_FIRE)\n            {\n            }\n            else\n                NPC[A].Killed = B;\n        }\n        else\n            NPC[A].Killed = B;\n\n        // Big Boo\n    }\n    else if(NPC[A].Type == NPCID_BIG_GHOST)\n    {\n        if((B == 3 && NPC[C].Type != NPCID_PLR_FIREBALL) || B == 4)\n        {\n            if(B == 3 && NPC[C].Type == NPCID_SLIDE_BLOCK)\n                NPCHit(C, 3, C);\n            NPC[A].Damage += 1;\n            NPC[A].Immune = 30;\n            if(NPC[A].Damage >= 3)\n                NPC[A].Killed = B;\n            else\n                PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n        }\n        else if(B == 6)\n            NPC[A].Killed = B;\n\n        // Projectile Only Death (Ghosts, Thwomps, Etc.)\n    }\n    else if(NPC[A].Type == NPCID_STONE_S3 || NPC[A].Type == NPCID_GHOST_S3 || NPC[A].Type == NPCID_GHOST_FAST || NPC[A].Type == NPCID_GHOST_S4 || NPC[A].Type == NPCID_STONE_S4 || NPC[A].Type == NPCID_SAW || NPC[A].Type == NPCID_WALL_SPARK || NPC[A].Type == NPCID_FIRE_DISK)\n    {\n        if((B == 3 && NPC[C].Type != NPCID_PLR_FIREBALL) || B == 4)\n        {\n            if(NPC[A].Type != NPCID_FIRE_DISK && NPC[C].Type != NPCID_METALBARREL) // roto disks don't die form falling blocks\n            {\n                if(NPC[A].Type != NPCID_SAW)\n                    NPC[A].Killed = B;\n                if(B == 3 && (NPC[A].Type == NPCID_STONE_S3 || NPC[A].Type == NPCID_STONE_S4 || NPC[A].Type == NPCID_FIRE_DISK))\n                {\n                    if(NPC[C].Location.SpeedX > 0)\n                    {\n                        NPC[A].Direction = 1;\n                        NPC[A].Location.SpeedX = 2;\n                    }\n                    else\n                    {\n                        NPC[A].Direction = -1;\n                        NPC[A].Location.SpeedX = -2;\n                    }\n                }\n            }\n        }\n        else if(B == 6)\n            NPC[A].Killed = B;\n        else if(NPC[A].Type == NPCID_WALL_SPARK && B == 10)\n            NPC[A].Killed = B;\n    }\n    // Mega Mole\n    else if(NPC[A].Type == NPCID_BIG_GUY)\n    {\n        if(B == 6 || B == 7 || B == 9 || B == 2 || B == 10)\n            NPC[A].Killed = B;\n        else if(B == 3)\n        {\n            if(NPC[C].Type != NPCID_PLR_FIREBALL)\n                NPC[A].Killed = B;\n        }\n    }\n    // SMW Goombas\n    else if(NPC[A].Type == NPCID_CARRY_FODDER || NPC[A].Type == NPCID_HIT_CARRY_FODDER ||\n            NPC[A].Type == NPCID_FLY_CARRY_FODDER || NPC[A].Type == NPCID_CHASER)\n    {\n        if(B == 1)\n        {\n            if(NPC[A].Type == NPCID_FLY_CARRY_FODDER)\n            {\n                NPC[A].Type = NPCID_CARRY_FODDER;\n                if(!NPC[A].Projectile)\n                {\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                    NPC[A].Projectile = true;\n                }\n            }\n            else if(NPC[A].Type == NPCID_CHASER)\n            {\n                PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                if(NPC[A].Special2 == 0)\n                {\n                    NPC[A].Special2 = 1;\n                    NPC[A].Location.SpeedX = 4 * Player[C].Direction;\n                    NPC[A].Location.SpeedY = -2;\n                }\n            }\n            else if(NPC[A].Type == NPCID_CARRY_FODDER)\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NPC[A].Projectile = true;\n                NPC[A].Type = NPCID_HIT_CARRY_FODDER;\n            }\n            else if(NPC[A].Type == NPCID_HIT_CARRY_FODDER)\n            {\n                if(NPC[A].CantHurt == 0)\n                {\n                    NPC[A].CantHurtPlayer = C;\n                    NPC[A].CantHurt = 10;\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                    NPC[A].Projectile = true;\n                    if(Player[C].Location.to_right_of(NPC[A].Location))\n                        NPC[A].Direction = -1;\n                    else\n                        NPC[A].Direction = 1;\n                    NPC[A].Location.SpeedX = 5 * NPC[A].Direction;\n                    NPC[A].Location.SpeedY = -2.5_n;\n                }\n            }\n            NPC[A].Special = 0;\n        }\n        else if(B == 2)\n        {\n            if(NPC[A].Type != NPCID_CHASER)\n                NPC[A].Type = NPCID_HIT_CARRY_FODDER;\n            NPC[A].Special = 0;\n            if(NPC[A].Location.SpeedY > -4)\n            {\n                PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                if(NPC[A].Type == NPCID_CHASER)\n                    NPC[A].Special2 = 1;\n                else\n                    NPC[A].Projectile = true;\n                NPC[A].Location.SpeedY = -5;\n                NPC[A].Location.Y = Block[C].Location.Y - NPC[A].Location.Height - 0.01_n;\n            }\n        }\n        else if(B == 7)\n        {\n            if(NPC[A].Type != NPCID_CHASER)\n                NPC[A].Type = NPCID_HIT_CARRY_FODDER;\n            NPC[A].Special = 0;\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            NPC[A].Location.SpeedY = -5;\n            NPC[A].Location.SpeedX = 3 * Player[C].Direction;\n            if(NPC[A].Type == NPCID_CHASER)\n                NPC[A].Special2 = 1;\n            else\n                NPC[A].Projectile = true;\n        }\n        else\n        {\n            if(NPC[A].Type == NPCID_CHASER && (B == 3 || B == 4 || B == 8))\n            {\n                if(B == 3 && NPC[C].Type != NPCID_PLR_FIREBALL && NPC[C].Type != NPCID_PET_FIRE)\n                {\n                    NPC[A].Location.SpeedY = -7;\n                    NPC[A].Killed = B;\n                }\n            }\n            else\n                NPC[A].Killed = B;\n        }\n    }\n    // SMB2 Bomb\n    else if(NPC[A].Type == NPCID_BOMB)\n    {\n        if(B == 9)\n            NPC[A].Killed = B;\n        else if(B != 8 && B != 7)\n            NPC[A].Special = 10000;\n    }\n    // Heart bomb\n    else if(NPC[A].Type == NPCID_CHAR3_HEAVY)\n    {\n        if(B == 9)\n            NPC[A].Killed = B;\n        else if(B != 1 && B != 8 && B != 7 && B != 10)\n        {\n            if(NPC[A].HoldingPlayer > 0)\n            {\n                Player[NPC[A].HoldingPlayer].HoldingNPC = 0;\n                NPC[A].HoldingPlayer = 0;\n            }\n            NPC[A].Special4 = 1;\n        }\n    }\n    // SMB2 Bob-omb\n    else if(NPC[A].Type == NPCID_WALK_BOMB_S2)\n    {\n        if(B == 9)\n            NPC[A].Killed = B;\n        else if(B == 7)\n        {\n            NPC[A].Projectile = true;\n            NPC[A].Location.SpeedX = 3 * Player[C].Direction;\n            NPC[A].Location.SpeedY = -5;\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            if(NPC[A].Special < 520)\n                NPC[A].Special = 520;\n        }\n        else if(B != 8)\n            NPC[A].Special = 10000;\n    }\n    // Thwomps\n    else if(NPC[A].Type == NPCID_STATUE_S3 || NPC[A].Type == NPCID_STATUE_S4 || NPC[A].Type == NPCID_STONE_S4)\n    {\n        if(B == 6)\n            NPC[A].Killed = B;\n    }\n    // Zelda NPCs\n    else if(NPC[A].Type == NPCID_KNIGHT)\n        NPC[A].Killed = B;\n    // Zelda Bots\n    else if(NPCIsABot(NPC[A]))\n        NPC[A].Killed = B;\n    // Switch Platforms\n    else if(NPC[A].Type == NPCID_YEL_PLATFORM || NPC[A].Type == NPCID_BLU_PLATFORM || NPC[A].Type == NPCID_GRN_PLATFORM || NPC[A].Type == NPCID_RED_PLATFORM)\n    {\n        if(B == 9)\n            NPC[A].Killed = B;\n    }\n    // Veggies\n    else if(NPCIsVeggie(NPC[A].Type))\n    {\n        if(B == 5)\n        {\n            Player[NPC[A].HoldingPlayer].HoldingNPC = 0;\n            NPC[A].CantHurtPlayer = NPC[A].HoldingPlayer;\n            NPC[A].HoldingPlayer = 0;\n            NPC[A].CantHurt = 1000;\n            NPC[A].Location.SpeedX = 3 * -NPC[A].Direction;\n            NPC[A].Location.SpeedY = -3;\n            NPC[A].Projectile = true;\n        }\n        // Because C++, second part of this condition never gets checked\n        // in VB6, it does check and causes a crash, because C is an index of block when B is 4\n        else if(B == 4 && (NPC[C].Type != NPC[A].Type || A == C))\n        {\n            if(NPC[C].Type != NPCID_SICK_BOSS_BALL && NPC[C].Type != NPCID_SICK_BOSS)\n            {\n                if(NPC[A].Location.SpeedY > -4)\n                    NPC[A].Location.SpeedY = -4;\n                if(NPC[A].Location.SpeedX == 0)\n                {\n                    if(iRand(2) == 0)\n                        NPC[A].Location.SpeedX = 2;\n                    else\n                        NPC[A].Location.SpeedX = -2;\n                }\n            }\n        }\n        else if(B == 6)\n            NPC[A].Killed = B;\n    }\n    // SMB3 Bomb\n    else if(NPC[A].Type == NPCID_LIT_BOMB_S3)\n    {\n        if(B == 1)\n        {\n            if(NPC[A].CantHurt == 0)\n            {\n                NPC[A].CantHurt = 10;\n                NPC[A].CantHurtPlayer = C;\n                PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                NPC[A].Projectile = true;\n                if(Player[C].Location.to_right_of(NPC[A].Location))\n                    NPC[A].Direction = -1;\n                else\n                    NPC[A].Direction = 1;\n                NPC[A].Location.SpeedX = 5 * NPC[A].Direction;\n                NPC[A].Location.SpeedY = -2;\n            }\n        }\n        else if(B == 2)\n        {\n            if(NPC[A].Location.SpeedY > -4)\n            {\n                PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                NPC[A].Projectile = true;\n                NPC[A].Location.SpeedY = -5;\n                NPC[A].Location.Y = Block[C].Location.Y - NPC[A].Location.Height - 0.01_n;\n            }\n        }\n        else if(B == 7 || B == 10)\n        {\n            NPC[A].Type = NPCID_LIT_BOMB_S3;\n            NPC[A].Special = 0;\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            NPC[A].Location.SpeedY = -5;\n            NPC[A].Location.SpeedX = 3 * Player[C].Direction;\n            NPC[A].Projectile = true;\n        }\n        else\n        {\n            if(B == 3)\n            {\n                if(NPC[C].Type != NPCID_PLR_FIREBALL && NPC[C].Type != NPCID_PET_FIRE)\n                    NPC[A].Killed = B;\n            }\n            else if(B != 8)\n                NPC[A].Killed = B;\n        }\n    }\n    // SMB3 Bob-om\n    else if(NPC[A].Type == NPCID_WALK_BOMB_S3)\n    {\n        if(B == 1)\n        {\n            NPC[A].CantHurt = 11;\n            NPC[A].Type = NPCID_LIT_BOMB_S3;\n            NPC[A].Special = 0;\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            NPC[A].Projectile = true;\n        }\n        else if(B == 2)\n        {\n            if(NPC[A].Location.SpeedY > -4)\n            {\n                PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                NPC[A].Location.SpeedY = -5;\n                NPC[A].Projectile = true;\n                NPC[A].Location.Y = Block[C].Location.Y - NPC[A].Location.Height - 0.01_n;\n            }\n            NPC[A].Type = NPCID_LIT_BOMB_S3;\n            NPC[A].Special = 0;\n        }\n        else if(B == 7 || B == 10)\n        {\n            NPC[A].Type = NPCID_LIT_BOMB_S3;\n            NPC[A].Special = 0;\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            NPC[A].Location.SpeedY = -5;\n            NPC[A].Location.SpeedX = 3 * Player[C].Direction;\n            NPC[A].Projectile = true;\n        }\n        else\n        {\n            if(B == 3 || B == 5)\n            {\n                if(NPC[C].Type != NPCID_PLR_FIREBALL && NPC[C].Type != NPCID_PET_FIRE)\n                    NPC[A].Killed = B;\n            }\n            else if(B != 8)\n                NPC[A].Killed = B;\n        }\n        if(NPC[A].Type == NPCID_LIT_BOMB_S3)\n            NPC[A].Location.Height = 28;\n    }\n    // Friendly NPCs (Toad, Peach, Link, Luigi, Etc.)\n    else if(NPCIsToad(NPC[A].Type))\n    {\n        if(B == 2 && NPC[A].Location.SpeedY > -4)\n        {\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            NPC[A].Location.SpeedY = -5;\n            NPC[A].Location.Y = Block[C].Location.Y - NPC[A].Location.Height - 0.01_n;\n        }\n        else if(B == 3 || B == 4 || B == 5 || B == 6 || B == 9)\n        {\n            NPC[A].Killed = B;\n            if(B == 3)\n                NPC[A].Location.SpeedX = 2 * NPC[B].Direction;\n        }\n    }\n    // SMB3 Red Paragoomba\n    else if(NPC[A].Type == NPCID_RED_FLY_FODDER)\n    {\n        if(B == 1)\n        {\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            if(NPC[A].Location.SpeedY < 0)\n                NPC[A].Location.SpeedY = 0;\n            NPC[A].Type = NPCID_RED_FODDER;\n            NPC[A].Frame = 0;\n        }\n        else\n            NPC[A].Killed = B;\n    }\n    // SML2 Paragoomba\n    else if(NPC[A].Type == NPCID_FLY_FODDER_S5)\n    {\n        if(B == 1)\n        {\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            if(NPC[A].Location.SpeedY < 0)\n                NPC[A].Location.SpeedY = 0;\n            NPC[A].Type = NPCID_FODDER_S5;\n            NPC[A].Frame = 0;\n        }\n        else\n            NPC[A].Killed = B;\n    }\n    // SMB3 Brown Paragoomba\n    else if(NPC[A].Type == NPCID_FLY_FODDER_S3)\n    {\n        if(B == 1)\n        {\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            if(NPC[A].Location.SpeedY < 0)\n                NPC[A].Location.SpeedY = 0;\n            NPC[A].Type = NPCID_FODDER_S3;\n            NPC[A].Frame = 0;\n        }\n        else\n            NPC[A].Killed = B;\n    }\n    // SMB3 Ice Block\n    else if(NPC[A].Type == NPCID_SLIDE_BLOCK)\n    {\n        if(B == 1 || (B == 10 && !NPC[A].Projectile))\n        {\n            NPC[A].Special = 1;\n            PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            NPC[A].Location.SpeedX = Physics.NPCShellSpeed * Player[C].Direction;\n            NPC[A].CantHurt = Physics.NPCCanHurtWait;\n            NPC[A].CantHurtPlayer = C;\n        }\n        else if(B == 6 || B == 2 || B == 5 || B == 4 || (B == 3 && NPC[A].Special == 1) || B == 9 || (B == 10 && NPC[A].Projectile))\n        {\n            if(B == 10)\n                B = 3;\n            if(B == 4)\n            {\n                if(C == A)\n                {\n                    NPC[A].Killed = B;\n                    NewEffect(EFFID_WHACK, NPC[A].Location);\n                    Effect[numEffects].Location.X += NPC[A].Location.SpeedX;\n                    Effect[numEffects].Location.Y += NPC[A].Location.SpeedY;\n                }\n            }\n            else if(B == 3)\n            {\n                if(NPC[C].Type != NPCID_PLR_FIREBALL)\n                    NPC[A].Killed = B;\n                if(NPC[C].Killed == 0)\n                    NPCHit(C, 3, A);\n            }\n            else\n                NPC[A].Killed = B;\n        }\n    }\n    // Bower Statues\n    else if(NPC[A].Type == NPCID_STATUE_S3 || NPC[A].Type == NPCID_STATUE_S4)\n    {\n        if(B == 2)\n        {\n            NPC[A].Location.Y -= 1;\n            NPC[A].Location.SpeedY = -1;\n        }\n    }\n    // Things With Shells (Koopa Troopa, Buzzy Beetle, Etc.)\n    else if(NPC[A].Type == NPCID_GRN_TURTLE_S3 || NPC[A].Type == NPCID_RED_TURTLE_S3 || NPC[A].Type == NPCID_GLASS_TURTLE || NPC[A].Type == NPCID_BIG_TURTLE || NPCIsAParaTroopa(NPC[A].Type)\n        || (NPC[A].Type >= NPCID_GRN_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_TURTLE_S4)\n        || NPC[A].Type == NPCID_GRN_TURTLE_S1 || NPC[A].Type == NPCID_RED_TURTLE_S1\n        /* || NPC[A].Type == NPCID_GRN_FLY_TURTLE_S1 || NPC[A].Type == NPCID_RED_FLY_TURTLE_S1 */ // implied by NPCIsAParaTroopa\n    )\n    {\n        if((B == 1 && !NPC[A].Wings) || B == 2 || B == 7)\n        {\n            PlaySoundSpatial((B == 1) ? SFX_Stomp : SFX_ShellHit, NPC[A].Location);\n\n            if(B != 1)\n                NPC[A].Projectile = true;\n\n            NPC[A].Location.Y += NPC[A].Location.Height;\n            NPC[A].Location.X += NPC[A].Location.Width / 2;\n            if(NPC[A].Type == NPCID_GRN_TURTLE_S3)\n                NPC[A].Type = NPCID_GRN_SHELL_S3;\n            else if(NPC[A].Type == NPCID_RED_TURTLE_S3)\n                NPC[A].Type = NPCID_RED_SHELL_S3;\n            else if(NPC[A].Type == NPCID_BIG_TURTLE)\n                NPC[A].Type = NPCID_BIG_SHELL;\n            else if(NPC[A].Type == NPCID_GRN_FLY_TURTLE_S3) // winged green koopa\n                NPC[A].Type = (B == 1) ? NPCID_GRN_TURTLE_S3 : NPCID_GRN_SHELL_S3;\n            else if(NPC[A].Type == NPCID_RED_FLY_TURTLE_S3) // winged red koopa\n                NPC[A].Type = (B == 1) ? NPCID_RED_TURTLE_S3 : NPCID_RED_SHELL_S3;\n            else if(NPC[A].Type == NPCID_GRN_FLY_TURTLE_S1 || NPC[A].Type == NPCID_RED_FLY_TURTLE_S1) // smb1 winged koopa\n            {\n                NPC[A].Type = (NPC[A].Type == NPCID_GRN_FLY_TURTLE_S1) ? NPCID_GRN_TURTLE_S1 : NPCID_RED_TURTLE_S1;\n                if(B != 1)\n                {\n                    NPC[A].Type = NPCID(NPC[A].Type - 1); // to shell\n                    // NPC[A].Location.Height = 28; // reset below\n                }\n            }\n            else if(NPC[A].Type == NPCID_GRN_TURTLE_S1 || NPC[A].Type == NPCID_RED_TURTLE_S1) // smb1 koopa\n            {\n                NPC[A].Type = NPCID(NPC[A].Type - 1); // to shell\n                // NPC[A].Location.Height = 28; // reset below\n            }\n            else if(NPC[A].Type == NPCID_GLASS_TURTLE)\n                NPC[A].Type = NPCID_GLASS_SHELL;\n            else if(NPC[A].Type >= NPCID_GRN_FLY_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_FLY_TURTLE_S4)\n            {\n                NPC[A].Type = NPCID(NPC[A].Type - 12);\n                // NPC[A].Special = 0; // also set below\n            }\n            else if(B != 2) // turn S4 turtles into shells and release inner turtles\n            {\n                // release inner (hit) turtle\n                numNPCs++;\n                NPC[numNPCs].Location = NPC[A].Location;\n                NPC[numNPCs].Location.Y -= 32;\n                NPC[numNPCs].Type = NPCID(NPC[A].Type + 8); // to hit turtle\n                NPC[numNPCs].Projectile = true;\n                NPC[numNPCs].Direction = Player[C].Direction;\n                NPC[numNPCs].Location.SpeedY = 0;\n                NPC[numNPCs].Location.SpeedX = Physics.NPCShellSpeed * NPC[numNPCs].Direction;\n                // logic to prevent double-hitting is different in stomp and whip cases\n                NPC[numNPCs].Location.X += -16 + (B == 1 ? NPC[numNPCs].Location.SpeedX : (32 * NPC[numNPCs].Direction));\n                NPC[numNPCs].CantHurtPlayer = C;\n                NPC[numNPCs].CantHurt = 6;\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].TimeLeft = 100;\n                CheckSectionNPC(numNPCs);\n                syncLayers_NPC(numNPCs);\n\n                NPC[A].Type = NPCID(NPC[A].Type + 4); // to shell\n            }\n            else // turn S4 turtles into shells without inner turtles if hit from below\n                NPC[A].Type = NPCID(NPC[A].Type + 4);\n\n            NPC[A].Special = 0;\n            NPC[A].Frame = 0;\n\n            NPC[A].Location.Height = NPC[A]->THeight;\n            NPC[A].Location.Width = NPC[A]->TWidth;\n            NPC[A].Location.Y -= NPC[A].Location.Height;\n            NPC[A].Location.X += -(NPC[A].Location.Width / 2) - NPC[A].Direction * 2;\n            NPC[A].Location.SpeedX = 0;\n\n            if(B == 1)\n            {\n                NPC[A].Location.SpeedY = 0;\n                NPC[A].RealSpeedX = 0;\n\n                D_pLogDebug(\"Shell stomp, X distance: [%g], Y=[%g]\", (double)num_t::abs(NPC[numNPCs].Location.X - NPC[A].Location.X), (double)NPC[numNPCs].Location.Y);\n\n                if(NPC[A].Type >= NPCID_GRN_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4)\n                    NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n            }\n            else\n            {\n                NPC[A].Location.SpeedY = -5;\n                if(B == 2)\n                    NPC[A].Location.Y = Block[C].Location.Y - NPC[A].Location.Height - 0.01_n;\n            }\n\n            NPCQueues::Unchecked.push_back(A);\n        }\n        else\n        {\n            if(B == 3)\n            {\n                if(!(NPC[A].Type == NPCID_GLASS_TURTLE && (NPC[C].Type == NPCID_PLR_FIREBALL || NPC[C].Type == NPCID_PET_FIRE)))\n                    NPC[A].Killed = B;\n            }\n            else\n                NPC[A].Killed = B;\n        }\n\n        if(NPC[A]->IsAShell)\n            NPC[A].Stuck = false;\n    }\n    // SMB3 Bowser\n    else if(NPC[A].Type == NPCID_VILLAIN_S3)\n    {\n        if(B == 9)\n        {\n            NPC[A].Killed = 6;\n            if(NPC[A].Legacy)\n            {\n                bgMusic[NPC[A].Section] = 0;\n                StopMusic();\n            }\n        }\n        if(B == 1)\n        {\n            // PlaySound 2\n        }\n        else if(B == 3)\n        {\n            if(NPC[C].Type != NPCID_VILLAIN_S3)\n            {\n                NPC[A].Immune = 10;\n                if(NPC[C].Type == NPCID_BULLET)\n                {\n                    NPC[C].Location.SpeedX = -NPC[C].Location.SpeedX;\n                    NPCHit(C, 4, C);\n                }\n                else\n                    NPCHit(C, 3, A);\n                if(NPC[C].Type != NPCID_PLR_FIREBALL)\n                {\n                    PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                    NPC[A].Damage += 10;\n                }\n                else\n                {\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                    NPC[A].Damage += 1;\n                }\n            }\n        }\n        else if(B == 6)\n            NPC[A].Killed = B;\n        else if(B == 10)\n        {\n            NPC[A].Immune = 10;\n            PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n            NPC[A].Damage += 10;\n        }\n        if(NPC[A].Damage >= 200)\n        {\n            NPC[A].Location.SpeedY = -13;\n            NPC[A].Location.SpeedX = 4 * NPC[C].Direction;\n            NPC[A].Killed = B;\n\n            if(NPC[A].Legacy)\n            {\n                bgMusic[NPC[A].Section] = 0;\n                StopMusic();\n            }\n\n            // cancel MoreScore for invincibility-triggered kill\n            if(B == 3 && C > numNPCs)\n                C = A;\n        }\n    }\n    // SMW Rainbow Shell\n    else if(NPC[A].Type == NPCID_RAINBOW_SHELL)\n    {\n        if(B == 1)\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n        else if(B == 2 || B == 7)\n        {\n            PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            NPC[A].Location.SpeedY = -5;\n            if(B == 7)\n            {\n                NPC[A].Type = NPCID_FLIPPED_RAINBOW_SHELL;\n                NPC[A].Location.SpeedX = 0;\n                MoreScore(6, NPC[A].Location, Player[C].Multiplier);\n            }\n        }\n        else if(B == 6)\n        {\n            Location_t tempLocation;\n            tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height - 2;\n            tempLocation.X = NPC[A].Location.X - 4 + dRand().times(NPC[A].Location.Width + 8) - 4;\n            NewEffect(EFFID_SKID_DUST, tempLocation);\n        }\n        else if(B == 8)\n        {\n            NPC[A].Killed = 8;\n            PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n        }\n    }\n    // Shells\n    else if(NPC[A].Type == NPCID_GRN_SHELL_S3 || NPC[A].Type == NPCID_RED_SHELL_S3 || NPC[A].Type == NPCID_GLASS_SHELL || NPC[A].Type == NPCID_BIG_SHELL || (NPC[A].Type >= NPCID_GRN_SHELL_S4 && NPC[A].Type <= NPCID_YEL_SHELL_S4) || NPC[A].Type == NPCID_RED_SHELL_S1 || NPC[A].Type == NPCID_GRN_SHELL_S1 || NPC[A].Type == NPCID_FLIPPED_RAINBOW_SHELL)\n    {\n        if(B == 1)\n        {\n            if(NPC[A].Effect == NPCEFF_DROP_ITEM)\n                NPC[A].Effect = NPCEFF_NORMAL;\n\n            if(Player[C].Dismount <= 0 && Player[C].Mount != 2)\n            {\n                if(NPC[A].Location.SpeedX == 0 && NPC[A].CantHurtPlayer != C)\n                {\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                    NPC[A].Location.SpeedX = Physics.NPCShellSpeed * Player[C].Direction;\n                    NPC[A].CantHurt = Physics.NPCCanHurtWait;\n                    NPC[A].CantHurtPlayer = C;\n                    NPC[A].Projectile = true;\n                    NPC[A].Location.SpeedY = 0;\n                }\n                else if(NPC[A].CantHurtPlayer != C || (NPC[A].Slope == 0 && Player[C].Vine == 0))\n                {\n                    PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                    NPC[A].Location.SpeedX = 0;\n                    NPC[A].Location.SpeedY = 0;\n                    if(NPC[A].Wet > 0)\n                    {\n                        NPC[A].RealSpeedX = 0;\n                        NPC[A].Projectile = false;\n                    }\n                }\n            }\n        }\n        else if(B == 2 || B == 7)\n        {\n            PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            NPC[A].Projectile = true;\n            NPC[A].Location.SpeedY = -5;\n            NPC[A].Location.SpeedX = 0;\n        }\n        else if(B == 6)\n        {\n            if(NPC[A].Type == NPCID_FLIPPED_RAINBOW_SHELL)\n            {\n                Location_t tempLocation;\n                tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height - 2;\n                tempLocation.X = NPC[A].Location.X - 4 + dRand().times(NPC[A].Location.Width + 8) - 4;\n                NewEffect(EFFID_SKID_DUST, tempLocation);\n            }\n            else\n                NPC[A].Killed = B;\n        }\n        else if(B != 4)\n        {\n            if(B == 3)\n            {\n                if(!(NPC[A].Type == NPCID_GLASS_SHELL && (NPC[C].Type == NPCID_PLR_FIREBALL || NPC[C].Type == NPCID_PET_FIRE)))\n                    NPC[A].Killed = B;\n            }\n            else\n                NPC[A].Killed = B;\n        }\n        else if(B == 4)\n        {\n            if(NPC[C].Projectile && !(NPC[C].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[C].Type <= NPCID_YEL_HIT_TURTLE_S4))\n            {\n                // FIXME: Why the condition is here if it always assigns B? [PVS Studio]\n#if 0\n                if(!(NPC[A].Type == NPCID_GLASS_SHELL && NPC[C].Type == NPCID_PLR_FIREBALL))\n                    NPC[A].Killed = B;\n                else\n                    NPC[A].Killed = B;\n#endif\n                NPC[A].Killed = B;\n            }\n        }\n\n        if(NPC[A].Type == NPCID_FLIPPED_RAINBOW_SHELL)\n        {\n            NPC[A].Killed = 0;\n            if(B == 5)\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NPC[A].Location.SpeedX = Physics.NPCShellSpeed * -NPC[A].Direction;\n                NPC[A].Location.X += NPC[A].Location.SpeedX;\n                NPC[A].CantHurt = Physics.NPCCanHurtWait;\n                NPC[A].CantHurtPlayer = C;\n\n                // NEW code: does not alter behavior, but keeps the value in uint8_t range\n                if(C > maxPlayers)\n                    NPC[A].CantHurtPlayer = maxPlayers + 1;\n\n                NPC[A].Projectile = true;\n                NPC[A].Location.SpeedY = 0;\n                Player[NPC[A].HoldingPlayer].HoldingNPC = 0;\n                NPC[A].HoldingPlayer = 0;\n            }\n        }\n    }\n    // Big Piranha Plant\n    else if(NPC[A].Type == NPCID_LONG_PLANT_UP || NPC[A].Type == NPCID_LONG_PLANT_DOWN)\n    {\n        if(!(B == 1 || B == 2 || B == 6))\n        {\n            if(NPC[A].Special3 == 0)\n            {\n                if(B == 3)\n                {\n                    if(NPC[C].Type == NPCID_PLR_FIREBALL)\n                    {\n                        PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                        NPC[A].Damage += 1;\n                        NPC[A].Special3 = 10;\n                        if(NPC[A].Special2 == 2)\n                            NPC[A].Special = 50;\n                    }\n                    else\n                    {\n                        NPC[A].Damage += 3;\n                        PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                        NPC[A].Special3 = 30;\n                        if(NPC[A].Special2 == 2)\n                            NPC[A].Special = 50;\n                    }\n                }\n                else if(B == 10)\n                {\n                    NPC[A].Damage += 2;\n                    PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                    NPC[A].Special3 = 10;\n                    if(NPC[A].Special2 == 2)\n                        NPC[A].Special = 50;\n                }\n            }\n            if(NPC[A].Damage >= 6)\n                NPC[A].Killed = B;\n        }\n    }\n\n    // Piranha Plants\n    else if(NPC[A].Type == NPCID_PLANT_S3 || NPC[A].Type == NPCID_QUAD_SPITTER || NPC[A].Type == NPCID_PLANT_S1 || NPC[A].Type == NPCID_BOTTOM_PLANT || NPC[A].Type == NPCID_SIDE_PLANT || NPC[A].Type == NPCID_BIG_PLANT || NPC[A].Type == NPCID_FIRE_PLANT || NPC[A].Type == NPCID_JUMP_PLANT)\n    {\n        if(!(B == 1 || B == 2 || B == 6))\n        {\n            if(B != 8 && B != 10 && !(B == 9 && g_config.fix_visual_bugs))\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n\n            NPC[A].Killed = B;\n        }\n    }\n    // Podoboo\n    else if(NPC[A].Type == NPCID_LAVABUBBLE)\n    {\n        if(B == 9)\n        {\n        }\n        else if(B == 8)\n            NPC[A].Killed = B;\n        else if(B == 3 || B == 5 || B == 7)\n        {\n            PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            NPC[A].Killed = B;\n        }\n        else if(B == 4 && C > 0)\n        {\n            if(NPC[C].Projectile)\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NPC[A].Killed = 3;\n            }\n        }\n    }\n    // Player Fireball\n    else if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL)\n    {\n        if(B != 7 && B != 9 && B != 2)\n        {\n            if(B == 3 || B == 4)\n            {\n                if(NPC[C].Type != NPCID_PLR_HEAVY)\n                {\n                    // if(B != 6) // Always true\n                    if(NPC[A].Special == 5 && HasSound(SFX_HeroFireRod))\n                        PlaySoundSpatial(SFX_HeroFire, NPC[A].Location);\n                    else\n                        PlaySoundSpatial(SFX_BlockHit, NPC[A].Location);\n                    NPC[A].Killed = B;\n                }\n            }\n            else\n            {\n                if(B != 6)\n                {\n                    if(NPC[A].Special == 5 && HasSound(SFX_HeroFireRod))\n                        PlaySoundSpatial(SFX_HeroFire, NPC[A].Location);\n                    else\n                        PlaySoundSpatial(SFX_BlockHit, NPC[A].Location);\n                }\n                NPC[A].Killed = B;\n            }\n        }\n    }\n    // Yoshi Fireball\n    else if(NPC[A].Type == NPCID_PET_FIRE)\n    {\n        if(B == 3 || B == 5)\n        {\n            PlaySoundSpatial(SFX_BlockHit, NPC[A].Location);\n            NPC[A].Killed = B;\n        }\n    }\n    // Hammer Bros.\n    else if(NPC[A].Type == NPCID_HEAVY_THROWER)\n        NPC[A].Killed = B;\n    // Hammer Bros. Hammer\n    else if(NPC[A].Type == NPCID_HEAVY_THROWN)\n    {\n        if(B == 3)\n            NPC[A].Killed = B;\n    }\n    // Boom Boom\n    else if(NPC[A].Type == NPCID_MINIBOSS)\n    {\n        if(NPC[A].Special != 4)\n        {\n            if((B == 1 || B == 10) && NPC[A].Special == 0)\n            {\n                NPC[A].Damage += 3;\n                if(B == 1)\n                    PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                else\n                    PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                NPC[A].Special = 4;\n                // (was previously Special2)\n                NPC[A].SpecialY = 0;\n                NPC[A].Location.SpeedX = 0;\n                NPC[A].Location.SpeedY = 0;\n            }\n            else if(B == 3)\n            {\n                NPC[A].Immune = 20;\n                if(C > 0)\n                    NPCHit(C, 3, A);\n                NPC[A].Damage++;\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            }\n            else if(B == 6)\n            {\n                NPC[A].Killed = B;\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            }\n            else if(B == 10)\n            {\n#if XTECH_ENABLE_WEIRD_GFX_UPDATES\n                UpdateGraphics(true);\n#endif\n                NPC[A].Immune = 10;\n                NPC[A].Damage++;\n                PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n            }\n        }\n\n        if(NPC[A].Damage >= 9)\n            NPC[A].Killed = B;\n    }\n    // Bullet Bills\n    else if(NPC[A].Type == NPCID_BULLET || NPC[A].Type == NPCID_BIG_BULLET)\n    {\n        if(B == 1 || B == 3 || B == 4 || B == 5 || B == 7 || B == 8 || B == 10)\n        {\n            if(!((B == 3 || B == 4) && (NPC[C].Type == NPCID_PLR_FIREBALL || NPC[C].Type == NPCID_LAVABUBBLE)))\n            {\n                if(!(B == 7 && NPC[A].Projectile))\n                {\n                    if(!(B == 3 && NPC[A].CantHurt > 0) && !(B == 3 && NPC[C].Type == NPCID_PET_FIRE))\n                    {\n                        if(!(NPC[A].Type == NPCID_BIG_BULLET && B == 4))\n                        {\n                            if(!(B == 10 && NPC[A].Projectile)) // Link can't stab friendly bullets\n                            {\n                                NPC[A].Killed = B;\n                                if(A != C && B != 8 && B != 10)\n                                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n    // Birdo\n    else if(NPC[A].Type == NPCID_SPIT_BOSS)\n    {\n        // changed from Special when it became a container\n        if(NPC[A].Special5 >= 0)\n        {\n            if(B == 3)\n            {\n                if(NPC[C].Type == NPC[A].Special && NPC[C].Immune)\n                {\n                    // new logic: don't get hurt by a just-shot item\n                }\n                else if(NPC[C].Type != NPCID_PLR_FIREBALL)\n                {\n                    NPC[A].Special5 = -30;\n                    NPC[A].Damage += 1;\n                    NPC[A].Direction = -NPC[A].Direction;\n                    PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n                }\n            }\n            else if(B == 4)\n                NPC[A].Damage = 3;\n            else if(B == 10)\n            {\n                NPC[A].Special5 = -30;\n                NPC[A].Damage += 1;\n                NPC[A].Direction = -NPC[A].Direction;\n                PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n            }\n\n            if(NPC[A].Damage >= 3)\n                NPC[A].Killed = B;\n        }\n\n        if(B == 6)\n        {\n            NPC[A].Killed = B;\n            PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n        }\n    }\n    // Eggs\n    else if(NPC[A].Type == NPCID_SPIT_BOSS_BALL)\n    {\n        if(B == 3)\n        {\n            if(NPC[C].Type == NPCID_PLR_FIREBALL)\n                B = 0;\n        }\n\n        if(B == 3 || B == 4 || B == 5 || B == 7 || B == 10)\n        {\n            if(C == A)\n                NPC[A].Special = 1;\n            else\n            {\n                NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.6_r;\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            }\n\n            NPC[A].Killed = B;\n        }\n    }\n    // Indestructable Objects\n    else if(NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_CANNONITEM || NPC[A].Type == NPCID_SPRING || NPC[A].Type == NPCID_KEY || NPC[A].Type == NPCID_COIN_SWITCH || NPC[A].Type == NPCID_TIME_SWITCH || NPC[A].Type == NPCID_TNT || NPC[A].Type == NPCID_GRN_BOOT || NPC[A].Type == NPCID_RED_BOOT || NPC[A].Type == NPCID_BLU_BOOT || NPC[A].Type == NPCID_TOOTHYPIPE || NPCIsYoshi(NPC[A].Type) || NPC[A].Type == NPCID_ITEM_POD || (NPC[A].Type >= NPCID_CARRY_BLOCK_A && NPC[A].Type <= NPCID_CARRY_BLOCK_D) || NPC[A].Type == NPCID_TIMER_S2 || NPC[A].Type == NPCID_EARTHQUAKE_BLOCK || NPC[A].Type == NPCID_FLY_BLOCK || NPC[A].Type == NPCID_FLY_CANNON)\n    {\n        if(NPC[A].Type == NPCID_EARTHQUAKE_BLOCK && (B == 4 || B == 5 || B == 10))\n        {\n            NPC[A].Killed = 4;\n            PowBlock();\n        }\n\n        if(NPC[A].Type == NPCID_ITEM_POD && (B == 4 || B == 5))\n        {\n            if(NPC[C].Type != NPC[A].Type)\n                NPC[A].Killed = B;\n        }\n        else if(NPC[A].Type == NPCID_ITEM_POD && B == 10)\n        {\n            PlaySoundSpatial(SFX_HeroKill, NPC[A].Location);\n            NPC[A].Special2 = 1;\n        }\n        else\n        {\n            if(B == 1 && NPC[A].Type == NPCID_SPRING)\n            {\n                PlaySoundSpatial(SFX_Spring, NPC[A].Location);\n\n                // prevent wing loss\n                if(NPC[A].Wings)\n                    NPC[A].Damage = 1;\n            }\n\n            if(B == 1 && (NPC[A].Type == NPCID_COIN_SWITCH || NPC[A].Type == NPCID_TIME_SWITCH || NPC[A].Type == NPCID_TNT))\n            {\n                NPC[A].Killed = 1;\n                NPC[A].Wings = WING_NONE;\n                if(NPC[A].Type == NPCID_COIN_SWITCH)\n                {\n                    PSwitchTime = Physics.NPCPSwitch;\n                    PSwitchPlayer = C;\n                }\n                else if(NPC[A].Type == NPCID_TIME_SWITCH)\n                {\n                    PSwitchStop = Physics.NPCPSwitch;\n                    FreezeNPCs = true;\n                    PSwitchPlayer = C;\n                }\n            }\n\n            if(B == 2 && NPC[A].Location.SpeedY > -4 && NPC[A].CantHurt == 0)\n            {\n                NPC[A].CantHurt = 10;\n                PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                NPC[A].Location.SpeedY = -5;\n                // If .Type = 96 Then .Location.SpeedY = -4\n                NPC[A].Location.Y = Block[C].Location.Y - NPC[A].Location.Height - 0.01_n;\n            }\n            else if(B == 6)\n            {\n                if(NPC[A].Type == NPCID_RED_BOOT)\n                {\n                    Location_t tempLocation;\n                    tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height - 2;\n                    // tempLocation.X = .Location.X + .Location.Width / 2 - 4 '+ 4 * .Direction\n                    tempLocation.X = NPC[A].Location.X - 4 + dRand().times(NPC[A].Location.Width + 8) - 4;\n                    NewEffect(EFFID_SKID_DUST, tempLocation);\n                }\n                else\n                {\n                    if(NPC[A].Type == NPCID_SPRING)\n                        NPC[A].Location.Y -= 16;\n                    NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                    if(!NPC[A].NoLavaSplash)\n                        NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n                    PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                    NPC[A].ResetLocation();\n\n                    if(NPC[A].Active)\n                    {\n                        NPC[A].Active = false;\n                        NPCQueues::update(A);\n                    }\n                    else\n                    {\n                        NPCQueues::Unchecked.push_back(A);\n                    }\n\n                    // TODO: any other hooks for inactive?\n\n                    NPC[A].TimeLeft = 0;\n                    NPC[A].Projectile = false;\n                    NPC[A].Direction = NPC[A].DefaultDirection;\n                    NPC[A].CantHurt = 0;\n                    NPC[A].CantHurtPlayer = 0;\n                    NPC[A].Reset[1] = false;\n                    NPC[A].Reset[2] = false;\n                    NPCQueues::NoReset.push_back(A);\n                }\n            }\n        }\n    }\n    // Misc. Things With No Jump Death (SMB2 Shy Guys, SMB2 Ninji, SMB2 Pokey)\n    else if(NPC[A].Type == NPCID_BLU_GUY || NPC[A].Type == NPCID_RED_GUY || NPC[A].Type == NPCID_STACKER || NPC[A].Type == NPCID_JUMPER_S3 || NPC[A].Type == NPCID_RED_FISH_S1 || NPC[A].Type == NPCID_SPIKY_S3 || NPC[A].Type == NPCID_SPIKY_S4 || NPC[A].Type == NPCID_SPIKY_BALL_S4 || NPC[A].Type == NPCID_SPIKY_THROWER || NPC[A].Type == NPCID_ITEM_THROWER || NPC[A].Type == NPCID_SPIKY_BALL_S3 || NPC[A].Type == NPCID_CRAB || NPC[A].Type == NPCID_FLY || (NPC[A].Type >= NPCID_BIRD && NPC[A].Type <= NPCID_GRY_SPIT_GUY) || NPC[A].Type == NPCID_CARRY_BUDDY || NPC[A].Type == NPCID_SQUID_S3 || NPC[A].Type == NPCID_SQUID_S1 || NPC[A].Type == NPCID_WALK_PLANT || NPC[A].Type == NPCID_VINE_BUG)\n    {\n        if(B == 10 && NPC[A].Type != NPCID_CARRY_BUDDY)\n            NPC[A].Killed = B;\n        else if(B != 1)\n        {\n            if(B == 6)\n                NPC[A].Killed = B;\n            else if(B == 2 && NPC[A].Type == NPCID_CARRY_BUDDY)\n            {\n                if(NPC[A].CantHurt == 0)\n                {\n                    NPC[A].CantHurt = 10;\n                    PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                    NPC[A].Location.SpeedY = -5;\n                    NPC[A].Location.Y = Block[C].Location.Y - NPC[A].Location.Height - 0.01_n;\n                    NPC[A].Projectile = true;\n                    NPC[A].Location.SpeedX /= 2;\n                }\n            }\n            else if(NPC[A].Type == NPCID_CARRY_BUDDY && B == 5)\n            {\n                Player[NPC[A].HoldingPlayer].HoldingNPC = 0;\n                NPC[A].Projectile = true;\n                NPC[A].Location.SpeedX = 3 * -Player[NPC[A].HoldingPlayer].Direction;\n                NPC[A].Location.SpeedY = -4;\n                NPC[A].WallDeath = 0;\n                NPC[A].HoldingPlayer = 0;\n            }\n            else if(NPC[A].Type == NPCID_CARRY_BUDDY && B == 3)\n            {\n                if(NPC[C].HoldingPlayer == 0 && NPC[C].Type != NPC[A].Type)\n                {\n                    NPC[A].Immune = 30;\n                    NPC[A].Projectile = true;\n                    NPC[A].Location.SpeedY = -5;\n                    NPC[A].Location.SpeedX = (NPC[C].Location.SpeedX + NPC[A].Location.SpeedX) / 2;\n                    if(NPC[A].Location.SpeedX < 1.2_n && NPC[A].Location.SpeedX > -1.2_n)\n                    {\n                        if(NPC[C].Direction == -1)\n                            NPC[A].Location.SpeedX = 3;\n                        else\n                            NPC[A].Location.SpeedX = -3;\n                    }\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                }\n            }\n            else if(NPC[A].Type == NPCID_CARRY_BUDDY && B == 10)\n            {\n                NPC[A].Immune = 30;\n                NPC[A].Projectile = true;\n                NPC[A].Location.SpeedY = -5;\n                NPC[A].Location.SpeedX = Player[C].Location.SpeedX + 4 * Player[C].Direction;\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            }\n            else if(!(NPC[A].Type == NPCID_CARRY_BUDDY && (B == 4 || B == 8 || (B == 3 && NPC[C].Type == NPCID_PLR_FIREBALL))))\n            {\n                if(NPC[A].Type == NPCID_CARRY_BUDDY && B == 7)\n                {\n                    NPC[A].Direction = Player[C].Direction;\n                    NPC[A].Location.SpeedX = num_t::abs(NPC[A].Location.SpeedX) * NPC[A].Direction;\n                    NPC[A].TurnAround = false;\n                    NPC[A].Location.SpeedY = -6;\n                    NPC[A].Projectile = true;\n                    PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                }\n                else\n                    NPC[A].Killed = B;\n            }\n        }\n        else if(B == 1 && NPC[A].Type == NPCID_RED_FISH_S1)\n        {\n            NPC[A].Killed = B;\n            NPC[A].Location.SpeedY = 0;\n            NPC[A].Location.SpeedX = 0;\n        }\n        else if(B == 1 && !NPC[A]->CanWalkOn /*&& !NPC[A]->JumpHurt*/) // JumpHurt checked at the top\n        {\n            NPC[A].Killed = B;\n            NPC[A].Location.SpeedY = 0.123_n;\n            NPC[A].Location.SpeedX = 0;\n        }\n\n        if(B == 1 && NPC[A].Type == NPCID_CARRY_BUDDY)\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n        if((B == 1 || B == 8) && (NPC[A].Type == NPCID_SPIKY_THROWER || NPC[A].Type == NPCID_ITEM_THROWER))\n            NPC[A].Killed = B;\n    }\n    // Exits\n    else if(NPCIsAnExit(NPC[A].Type))\n    {\n        if(B == 6)\n        {\n            NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n            NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n            NPC[A].ResetLocation();\n            NPCQueues::Unchecked.push_back(A);\n        }\n    }\n    // non type-based logic\n    // Coins\n    else if(NPC[A]->IsACoin)\n    {\n        if(LevelEditor)\n            PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n\n        if(B == 2)\n        {\n            if(NPC[A].Type == NPCID_GEM_1 || NPC[A].Type == NPCID_GEM_5 || NPC[A].Type == NPCID_GEM_20)\n            {\n                PlaySoundSpatial(SFX_HeroRupee, NPC[A].Location);\n                NewEffect(EFFID_COIN_COLLECT, NPC[A].Location);\n                MoreScore(1, NPC[A].Location);\n            }\n            else if(NPC[A].Type == NPCID_COIN_5 || NPC[A].Type == NPCID_RED_COIN)\n            {\n                PlaySoundSpatial(SFX_Coin, NPC[A].Location);\n                NewEffect(EFFID_COIN_COLLECT, NPC[A].Location);\n                MoreScore(1, NPC[A].Location);\n            }\n            else if(g_config.fix_medal_kill && NPC[A].Type == NPCID_MEDAL)\n            {\n                NewEffect(EFFID_COIN_COLLECT, NPC[A].Location);\n                CollectMedal(NPC[A]);\n            }\n            else\n            {\n                // useless self-assignment code [PVS-Studio]\n                //NPC[A].Location.X = NPC[A].Location.X; // - (32 - .Location.Width) / 2\n                NPC[A].Location.set_height_floor(0);\n                PlaySoundSpatial(SFX_Coin, NPC[A].Location);\n                NewEffect(EFFID_COIN_BLOCK_S3, NPC[A].Location);\n            }\n\n            NPC[A].Killed = 9;\n\n            if(NPC[A].Type == NPCID_GEM_5 || NPC[A].Type == NPCID_COIN_5)\n                Coins += 5;\n            else if(NPC[A].Type == NPCID_GEM_20)\n                Coins += 20;\n            else\n                Coins += 1;\n\n            if(Coins >= 100)\n                Got100Coins();\n        }\n        else if(B == 5 || B == 3 || B == 4 || B == 6)\n            NPC[A].Killed = B;\n        else if(B == 10)\n        {\n            if(C > 0)\n                TouchBonus(C, A);\n        }\n    }\n    // Bonus Items\n    else if(NPC[A]->IsABonus)\n    {\n        if(B == 2 && NPC[A].Location.SpeedY > -4)\n        {\n            PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            NPC[A].Location.SpeedY = -5;\n            NPC[A].Location.Y = Block[C].Location.Y - NPC[A].Location.Height - 0.01_n;\n        }\n        // B == 6 - touched a lava block, C is a block, not NPC!!!\n        else if(B == 6 && g_config.fix_powerup_lava_bug)\n        {\n            NPC[A].Killed = B;\n        }\n        else if(B == 6 && C > maxNPCs)\n        {\n            pLogWarning(\"SMBX64 engine would have crashed on illegal index to NPC %d (maximum legal index %d)\", C, maxNPCs);\n            NPC[A].Killed = B;\n        }\n        // B == 6 - old behavior, access index C as an NPC\n        else if(B == 6 || B == 5 || B == 4)\n        {\n            if(!(NPC[C].Type == NPCID_PLR_FIREBALL || NPC[C].Type == NPCID_PET_FIRE || NPC[C].Type == NPCID_PLR_HEAVY || NPCIsVeggie(NPC[C].Type)))\n                NPC[A].Killed = B;\n        }\n        else if(B == 7)\n        {\n            if(NPC[A].Type == NPCID_POWER_S3 || NPC[A].Type == NPCID_SWAP_POWER || NPC[A].Type == NPCID_FIRE_POWER_S3 ||\n               NPC[A].Type == NPCID_LIFE_S3 || NPC[A].Type == NPCID_POISON || NPC[A].Type == NPCID_STATUE_POWER ||\n               (NPC[A].Type >= NPCID_FIRE_POWER_S1 && NPC[A].Type <= NPCID_3_LIFE) || NPC[A].Type == NPCID_HEAVY_POWER\n               || NPC[A].Type == NPCID_AQUATIC_POWER || NPC[A].Type == NPCID_POLAR_POWER || NPC[A].Type == NPCID_SHELL_POWER)\n            {\n                NPC[A].Direction = Player[C].Direction;\n                NPC[A].Location.SpeedX = num_t::abs(NPC[A].Location.SpeedX) * NPC[A].Direction;\n                NPC[A].TurnAround = false;\n                NPC[A].Location.SpeedY = -6;\n                PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n            }\n        }\n        else if(B == 10 && NPC[A].Type != NPCID_POISON)\n        {\n            if(C > 0 && NPC[A].Effect == NPCEFF_NORMAL)\n                TouchBonus(C, A);\n        }\n    }\n\n    if(NPC[A].Killed == 10)\n        NPC[A].Direction = Player[C].Direction;\n\n    bool tempBool = false;\n    if(NPC[A].Killed == 3)\n    {\n        if(NPC[C].Type == NPCID_PLR_FIREBALL && NPC[C].Special == 5 && NPC[A].Type != NPCID_PLR_FIREBALL)\n        {\n            NPC[A].Direction = NPC[C].Direction;\n            NPC[A].Killed = 10;\n            tempBool = true;\n        }\n    }\n\n    if(NPC[A].Killed == 10)\n    {\n        if(tempBool)\n            MoreScore(NPC[A]->Score, NPC[A].Location, NPC[C].Multiplier);\n        else\n            MoreScore(NPC[A]->Score, NPC[A].Location, Player[C].Multiplier);\n    }\n    // Calculate Score\n    Player[0].Multiplier = 0;\n    if((B == 1 || B == 8) && C <= numPlayers && !NPC[A]->IsABonus)\n    {\n        if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_COIN_SWITCH || NPC[A].Type == NPCID_TIME_SWITCH || NPC[A].Type == NPCID_TNT)\n        {\n        }\n        else if(oldNPC.Type == NPCID_LIT_BOMB_S3 || NPC[A].Type == NPCID_SLIDE_BLOCK || oldNPC.Type == NPCID_HIT_CARRY_FODDER || NPC[A].Type == NPCID_CHASER || (oldNPC->IsAShell && B != 8))\n        {\n            if(B != 8) // (.Type = 45 And B = 8) Then\n            {\n                if(NPC[A].Type != NPCID_FLIPPED_RAINBOW_SHELL)\n                {\n                    // MoreScore 1, .Location\n                    if(Player[C].Multiplier > NPC[A].Multiplier)\n                        NPC[A].Multiplier = Player[C].Multiplier;\n                }\n            }\n        }\n        else if(NPC[A].Location.SpeedX != oldNPC.Location.SpeedX || NPC[A].Location.SpeedY != oldNPC.Location.SpeedY ||\n                /*NPC[A].Projectile != NPC[A].Projectile ||*/ // FIXME: Wrong condition, always false [PVS Studio]\n                NPC[A].Killed != oldNPC.Killed ||\n                NPC[A].Type != oldNPC.Type || NPC[A].Inert != oldNPC.Inert)\n        {\n            if(NPC[A].Type == NPCID_MINIBOSS && NPC[A].Killed == 0)\n            {\n                MoreScore(2, NPC[A].Location, Player[C].Multiplier);\n                if(Player[C].Multiplier > NPC[A].Multiplier)\n                    NPC[A].Multiplier = Player[C].Multiplier;\n            }\n            else\n            {\n                MoreScore(NPC[A]->Score, NPC[A].Location, Player[C].Multiplier);\n                if(Player[C].Multiplier > NPC[A].Multiplier)\n                    NPC[A].Multiplier = Player[C].Multiplier;\n            }\n        }\n    }\n\n    if((B == 2 || B == 7) && !NPC[A]->IsABonus && oldNPC.Type != NPCID_RAINBOW_SHELL && NPC[A].Type != NPCID_EARTHQUAKE_BLOCK)\n    {\n        if(NPC[A].Killed != 0 || NPC[A].Type != oldNPC.Type)\n        {\n            MoreScore(NPC[A]->Score, NPC[A].Location);\n            if(B == 2)\n                NewEffect(EFFID_WHACK, newLoc(NPC[A].Location.X, NPC[A].Location.Y + NPC[A].Location.Height - 16));\n        }\n        else if(NPC[A].Location.SpeedX != oldNPC.Location.SpeedX ||\n                NPC[A].Location.SpeedY != oldNPC.Location.SpeedY /*||\n                NPC[A].Projectile != NPC[A].Projectile*/) // FIXME: Wrong condition, always false [PVS Studio]\n        {\n            // MoreScore 1, .Location\n            if(B == 2)\n                NewEffect(EFFID_WHACK, newLoc(NPC[A].Location.X, NPC[A].Location.Y + NPC[A].Location.Height - 16));\n        }\n    }\n\n    if(B == 4 && NPC[A].Killed == 4 && !NPC[A]->IsACoin && C != A && NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_PLR_ICEBALL && NPC[A].Type != NPCID_PET_FIRE && NPC[A].Type != NPCID_EARTHQUAKE_BLOCK)\n    {\n        if(NPC[C].Type != NPCID_BOSS_CASE)\n        {\n            if(!(NPC[A].Type == NPCID_BULLET && NPC[A].CantHurt > 0))\n            {\n                if(NPC[C].Multiplier < NPC[A].Multiplier)\n                    NPC[C].Multiplier = NPC[A].Multiplier;\n                MoreScore(NPC[A]->Score, NPC[A].Location, NPC[C].Multiplier);\n            }\n        }\n    }\n\n    if(B == 5 && NPC[A].Killed == 5)\n    {\n        if(NPC[A].Multiplier < NPC[C].Multiplier)\n            NPC[A].Multiplier = NPC[C].Multiplier;\n        MoreScore(NPC[A]->Score, NPC[A].Location, NPC[A].Multiplier);\n    }\n\n    if(B == 6 && NPC[A].Killed == 6 && (NPC[A].Type == NPCID_BOSS_FRAGILE || NPC[A].Type == NPCID_VILLAIN_S1 || NPC[A].Type == NPCID_SICK_BOSS || NPC[A].Type == NPCID_MINIBOSS || NPC[A].Type == NPCID_SPIT_BOSS || NPC[A].Type == NPCID_VILLAIN_S3))\n    {\n        if(!NPC[A]->WontHurt && !NPC[A]->IsABonus && NPC[A].Type != NPCID_PLR_FIREBALL)\n            MoreScore(NPC[A]->Score, NPC[A].Location);\n    }\n\n    if(!NPC[A]->IsACoin && B == 3 && C != A &&\n       (NPC[A].Killed == B || NPC[A].Damage != oldNPC.Damage) &&\n        NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_PET_FIRE && NPC[A].Type != NPCID_SLIDE_BLOCK &&\n        NPC[A].Type != NPCID_HOMING_BALL && NPC[A].Type != NPCID_EARTHQUAKE_BLOCK)\n    {\n        if(NPC[A].Killed == B)\n        {\n            if(NPC[C].Multiplier < NPC[A].Multiplier)\n                NPC[C].Multiplier = NPC[A].Multiplier;\n            MoreScore(NPC[A]->Score, NPC[A].Location, NPC[C].Multiplier);\n        }\n        if(NPC[A].Type != NPCID_BOSS_CASE && NPC[A].Type != NPCID_BOSS_FRAGILE)\n        {\n            Location_t tempLocation = NPC[C].Location;\n\n            if(NPC[A].Location.Width >= 64 || NPC[A].Location.Height >= 64)\n            {\n                tempLocation.X = NPC[C].Location.X + NPC[C].Location.Width / 2 - 16 + NPC[C].Location.SpeedX;\n                tempLocation.Y = NPC[C].Location.Y + NPC[C].Location.Height / 2 - 16 + NPC[C].Location.SpeedY;\n            }\n            else\n            {\n                tempLocation.Y = (NPC[C].Location.Y + tempLocation.Y + (NPC[C].Location.Height + tempLocation.Height) / 2) / 2 - 16;\n                tempLocation.X = (NPC[C].Location.X + tempLocation.X + (NPC[C].Location.Width + tempLocation.Width) / 2) / 2 - 16;\n            }\n\n            NewEffect(EFFID_WHACK, tempLocation);\n        }\n    }\n\n    if(NPC[A].Killed == 6)\n    {\n        if(BlockKills2[Block[C].Type])\n            NPC[A].NoLavaSplash = true;\n    }\n\n    // lose wings and get an extra hit if not a boss\n    if(NPC[A].Wings && NPC[A].Damage == 0 && (B == 1 || B == 2 || B == 7))\n    {\n        if(B == 1 && NPC[A].Location.SpeedY < 0)\n            NPC[A].Location.SpeedY = 0;\n\n        PlaySoundSpatial((B == 1) ? SFX_Stomp : SFX_ShellHit, NPC[A].Location);\n\n        NPC[A].Killed = 0;\n        if(!NPC[A].Immune)\n            NPC[A].Immune = 4;\n        NPC[A].Wings = WING_NONE;\n    }\n\n    if(NPC[A].Killed == 0 && NPC[A].Location.SpeedX == 0 && oldNPC.Location.SpeedX != 0)\n        NPC[A].RealSpeedX = 0;\n\n    if(NPC[A].Killed != 0 && oldNPC.Killed == 0)\n        NPCQueues::Killed.push_back(A);\n\n    if(NPC[A].Type != oldNPC.Type)\n    {\n        NPC[A].Location.set_height_floor(NPC[A]->THeight);\n        NPC[A].Location.set_width_center(NPC[A]->TWidth);\n    }\n\n    if(NPC[A].Location.Width != oldNPC.Location.Width\n        || NPC[A].Location.Height != oldNPC.Location.Height\n        || NPC[A].Location.X != oldNPC.Location.X\n        || NPC[A].Location.Y != oldNPC.Location.Y)\n    {\n        if(NPC[A].Location.Width != oldNPC.Location.Width)\n            NPCQueues::Unchecked.push_back(A);\n\n        bool changed = treeNPCUpdate(A);\n        if(changed && NPC[A].tempBlock > 0)\n            treeNPCSplitTempBlock(A);\n\n    }\n\n    StopHit = 0;\n}\n"
  },
  {
    "path": "src/npc/npc_kill.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../globals.h\"\n#include \"../npc.h\"\n#include \"../npc_id.h\"\n#include \"../eff_id.h\"\n#include \"../sound.h\"\n#include \"../effect.h\"\n#include \"../layers.h\"\n#include \"../game_main.h\"\n#include \"../main/speedrunner.h\"\n#include \"config.h\"\n#include \"../controls.h\"\n#include \"../layers.h\"\n\n#include \"npc_traits.h\"\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"npc/npc_queues.h\"\n\n#include \"main/game_loop_interrupt.h\"\n\nstatic void s_makeSparkles(const NPC_t& npc, int speed_random, int speed_mult)\n{\n    NewEffect(EFFID_SMOKE_S3, npc.Location);\n\n    Location_t tempLocation;\n    tempLocation.Height = EffectHeight[EFFID_SPARKLE];\n    tempLocation.Width = EffectWidth[EFFID_SPARKLE];\n    tempLocation.SpeedX = 0;\n    tempLocation.SpeedY = 0;\n\n    num_t tempLocX_base = npc.Location.X - EffectWidth[EFFID_SPARKLE] / 2;\n    num_t tempLocY_base = npc.Location.Y - EffectHeight[EFFID_SPARKLE] / 2;\n\n    for(int C = 1; C <= 50; C++)\n    {\n        tempLocation.X = tempLocX_base + dRand().times(npc.Location.Width);\n        tempLocation.Y = tempLocY_base + dRand().times(npc.Location.Height);\n        NewEffect(EFFID_SPARKLE, tempLocation);\n\n        Effect[numEffects].Location.SpeedX = (dRand() - 0.5_n) * speed_random - npc.Location.SpeedX * speed_mult / 10;\n        Effect[numEffects].Location.SpeedY = (dRand() - 0.5_n) * speed_random + npc.Location.SpeedY * speed_mult / 10;\n        Effect[numEffects].Frame = iRand(3);\n    }\n}\n\nbool KillNPC(int A, int B)\n{\n    // ------+  KILL CODES  +-------\n    // B = 1      Jumped on by a player (or kicked)\n    // B = 2      Hit by a shaking block\n    // B = 3      Hit by projectile\n    // B = 4      Hit something as a projectile\n    // B = 5      Hit something while being held\n    // B = 6      Touched a lava block\n    // B = 8      Stomped by boot\n    // B = 9      Time to DIE\n    // B = 10     Zelda Stab\n    // bool DontSpawnExit = false;\n    // bool DontResetMusic = false;\n    // bool tempBool = false;\n    // NPC_t blankNPC;\n    int C = 0;\n    Location_t tempLocation;\n\n    switch(g_gameLoopInterrupt.site)\n    {\n    case GameLoopInterrupt::UpdateNPCs_KillNPC:\n        if(g_gameLoopInterrupt.bool1)\n            goto resume_TriggerLast;\n        else\n            goto resume_TriggerDeath;\n    default:\n        break;\n    }\n\n    // don't need to worry about updating NPC A's tree because that will certainly happen in either the syncLayersNPC or the Deactivate call at the end of the procedure\n\n    if(NPC[A].Type == NPCID_ICE_CUBE && NPC[A].Special > 0 && NPC[A].Killed != 9)\n    {\n        s_makeSparkles(NPC[A], 4, 2);\n        PlaySoundSpatial(SFX_Icebreak, NPC[A].Location);\n        NPC[A].Type = NPCID(NPC[A].Special);\n\n        NPCQueues::update(A);\n\n        if(B != 10)\n        {\n            MoreScore(NPC[A]->Score, NPC[A].Location, NPC[A].Multiplier);\n        }\n\n        NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.4_r;\n        if(NPC[A].Location.SpeedX < 2 && NPC[A].Location.SpeedX > -2)\n            NPC[A].Location.SpeedX = 2 * NPC[A].Direction;\n    }\n\n    if(NPC[A].Killed == 8 && NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_KNIGHT && !NPCIsABot(NPC[A]) && NPC[A].Type != NPCID_FLIER && NPC[A].Type != NPCID_ROCKET_FLIER && NPC[A].Type != NPCID_WALL_BUG && NPC[A].Type != NPCID_HOMING_BALL)\n        NewEffect(EFFID_BOOT_STOMP, NPC[A].Location);\n\n    if(NPC[A].Type == NPCID_YELSWITCH_FODDER || NPC[A].Type == NPCID_BLUSWITCH_FODDER || NPC[A].Type == NPCID_GRNSWITCH_FODDER || NPC[A].Type == NPCID_REDSWITCH_FODDER\n        || NPC[A].DefaultType == NPCID_YELSWITCH_FODDER || NPC[A].DefaultType == NPCID_BLUSWITCH_FODDER || NPC[A].DefaultType == NPCID_GRNSWITCH_FODDER || NPC[A].DefaultType == NPCID_REDSWITCH_FODDER)\n    {\n        bool tempBool = false;\n\n        for(C = 1; C <= numNPCs; C++)\n        {\n            // WARNING: this does not check DefaultType\n            if(NPC[C].Type == NPC[A].Type && NPC[C].Section == NPC[A].Section && C != A)\n            {\n                tempBool = true;\n                break;\n            }\n        }\n\n        if(LevelEditor)\n            tempBool = true;\n\n        if(!tempBool)\n        {\n            if(NPC[A].Type == NPCID_YELSWITCH_FODDER || NPC[A].DefaultType == NPCID_YELSWITCH_FODDER)\n            {\n                PlaySoundSpatial(SFX_PSwitch, NPC[A].Location);\n                for(C = 1; C <= numBlock; C++)\n                {\n                    if(Block[C].Type == 171)\n                        Block[C].Type = 172;\n                    else if(Block[C].Type == 172)\n                        Block[C].Type = 171;\n                }\n                for(C = 1; C <= numNPCs; C++)\n                {\n                    if(NPC[C].Type == NPCID_YEL_PLATFORM)\n                        NPC[C].Direction = -NPC[C].Direction;\n                }\n            }\n            else if(NPC[A].Type == NPCID_BLUSWITCH_FODDER || NPC[A].DefaultType == NPCID_BLUSWITCH_FODDER)\n            {\n                PlaySoundSpatial(SFX_PSwitch, NPC[A].Location);\n                for(C = 1; C <= numBlock; C++)\n                {\n                    if(Block[C].Type == 174)\n                        Block[C].Type = 175;\n                    else if(Block[C].Type == 175)\n                        Block[C].Type = 174;\n                }\n                for(C = 1; C <= numNPCs; C++)\n                {\n                    if(NPC[C].Type == NPCID_BLU_PLATFORM)\n                        NPC[C].Direction = -NPC[C].Direction;\n                }\n            }\n            else if(NPC[A].Type == NPCID_GRNSWITCH_FODDER || NPC[A].DefaultType == NPCID_GRNSWITCH_FODDER)\n            {\n                PlaySoundSpatial(SFX_PSwitch, NPC[A].Location);\n                for(C = 1; C <= numBlock; C++)\n                {\n                    if(Block[C].Type == 177)\n                        Block[C].Type = 178;\n                    else if(Block[C].Type == 178)\n                        Block[C].Type = 177;\n                }\n                for(C = 1; C <= numNPCs; C++)\n                {\n                    if(NPC[C].Type == NPCID_GRN_PLATFORM)\n                        NPC[C].Direction = -NPC[C].Direction;\n                }\n            }\n            else if(NPC[A].Type == NPCID_REDSWITCH_FODDER || NPC[A].DefaultType == NPCID_REDSWITCH_FODDER)\n            {\n                PlaySoundSpatial(SFX_PSwitch, NPC[A].Location);\n                for(C = 1; C <= numBlock; C++)\n                {\n                    if(Block[C].Type == 180)\n                        Block[C].Type = 181;\n                    else if(Block[C].Type == 181)\n                        Block[C].Type = 180;\n                }\n                for(C = 1; C <= numNPCs; C++)\n                {\n                    if(NPC[C].Type == NPCID_RED_PLATFORM)\n                        NPC[C].Direction = -NPC[C].Direction;\n                }\n            }\n        }\n    }\n\n    if(NPC[A].TriggerDeath != EVENT_NONE && !LevelEditor)\n    {\n        eventindex_t resume_index;\n        resume_index = ProcEvent_Safe(false, NPC[A].TriggerDeath, 0);\n        while(resume_index != EVENT_NONE)\n        {\n            g_gameLoopInterrupt.C = resume_index;\n            g_gameLoopInterrupt.bool1 = false; // marks as TriggerDeath\n            return true;\n\nresume_TriggerDeath:\n            resume_index = g_gameLoopInterrupt.C;\n            g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n            resume_index = ProcEvent_Safe(true, resume_index, 0);\n        }\n    }\n\n    if(NPC[A].TriggerLast != EVENT_NONE)\n    {\n        bool last_in_layer;\n\n        {\n            last_in_layer = true;\n\n            int lr = NPC[A].Layer;\n            if(lr != LAYER_NONE)\n            {\n                for(int other_npc : Layer[lr].NPCs)\n                {\n                    if(other_npc != A && !NPC[other_npc].Generator)\n                    {\n                        last_in_layer = false;\n                        break;\n                    }\n                }\n\n                if(!Layer[lr].blocks.empty())\n                    last_in_layer = false;\n            }\n        }\n\n        if(last_in_layer)\n        {\n            eventindex_t resume_index;\n            resume_index = ProcEvent_Safe(false, NPC[A].TriggerLast, 0);\n            while(resume_index != EVENT_NONE)\n            {\n                g_gameLoopInterrupt.C = resume_index;\n                g_gameLoopInterrupt.bool1 = true; // marks as TriggerLast\n                return true;\n\nresume_TriggerLast:\n                resume_index = g_gameLoopInterrupt.C;\n                g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n                resume_index = ProcEvent_Safe(true, resume_index, 0);\n            }\n        }\n    }\n\n    num_t NPC_CenterX = NPC[A].Location.X + NPC[A].Location.Width / 2;\n    num_t NPC_CenterY = NPC[A].Location.Y + NPC[A].Location.Height / 2;\n    num_t NPC_BottomY = NPC[A].Location.Y + NPC[A].Location.Height;\n\n    if(NPC[A].HoldingPlayer > 0)\n    {\n        if(!NPCIsAnExit(NPC[A])) // Tell the player the NPC he was holding is dead\n            Player[NPC[A].HoldingPlayer].HoldingNPC = 0;\n    }\n\n    if(NPC[A].Killed == 10) // Things that die by Link's sword\n    {\n        if(!(NPC[A].Type == NPCID_MINIBOSS || NPC[A].Type == NPCID_SPIT_BOSS || NPC[A].Type == NPCID_VILLAIN_S3 || NPC[A].Type == NPCID_BOSS_FRAGILE || NPC[A].Type == NPCID_VILLAIN_S1 || NPC[A].Type == NPCID_SICK_BOSS || NPC[A].Type == NPCID_FLIER || NPC[A].Type == NPCID_ROCKET_FLIER || NPC[A].Type == NPCID_WALL_BUG || NPC[A].Type == NPCID_HOMING_BALL || NPC[A].Type == NPCID_BOSS_CASE))\n        {\n            PlaySoundSpatial(SFX_HeroKill, NPC[A].Location);\n            NewEffect(EFFID_SMOKE_S5, NPC[A].Location);\n            B = 9;\n            if(iRand(10) < 3)\n            {\n                numNPCs++;\n                NPC[numNPCs].Type = NPCID_GEM_1;\n                if(iRand(5) == 0)\n                    NPC[numNPCs].Type = NPCID_GEM_5;\n                if(iRand(40) < 3)\n                    NPC[numNPCs].Type = NPCID_GEM_20;\n                NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                NPC[numNPCs].Location.X = NPC_CenterX - NPC[numNPCs].Location.Width / 2;\n                NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                if(NPC[A].Location.Height >= 32)\n                    NPC[numNPCs].Location.Y = NPC_CenterY - NPC[numNPCs].Location.Height / 2;\n                else if(NPC[A].Type == NPCID_BOTTOM_PLANT || NPC[A].Type == NPCID_LONG_PLANT_DOWN) // Stops the rupees from spawning in blocks\n                    NPC[numNPCs].Location.Y = NPC[A].Location.Y + 1;\n                else // Stops the rupees from spawning in blocks\n                    NPC[numNPCs].Location.Y = NPC_BottomY - NPC[numNPCs].Location.Height - 1;\n                NPC[numNPCs].Section = NPC[A].Section;\n                NPC[numNPCs].TimeLeft = Physics.NPCTimeOffScreen;\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].TailCD = 10;\n                NPC[numNPCs].Special = 1;\n                NPC[numNPCs].Location.SpeedY = -5;\n                NPC[numNPCs].Location.SpeedX = (1 + dRand() / 2) * NPC[A].Direction;\n                syncLayers_NPC(numNPCs);\n                CheckSectionNPC(numNPCs);\n            }\n        }\n    }\n\n    if(B != 9)\n    {\n        NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n        if(NPC[A].Type == NPCID_SQUID_S1 || NPC[A].Type == NPCID_SQUID_S3 || NPC[A].Type == NPCID_FODDER_S3 || NPC[A].Type == NPCID_FODDER_S5 || NPC[A].Type == NPCID_FLY_FODDER_S5 || NPC[A].Type == NPCID_FLY_FODDER_S3 || NPC[A].Type == NPCID_BRUTE || NPC[A].Type == NPCID_BRUTE_SQUISHED || NPC[A].Type == NPCID_BIG_GUY || NPC[A].Type == NPCID_CARRY_FODDER || NPC[A].Type == NPCID_HIT_CARRY_FODDER || NPC[A].Type == NPCID_FLY_CARRY_FODDER || NPC[A].Type == NPCID_GRN_FISH_S3 || NPC[A].Type == NPCID_YEL_FISH_S4 || NPC[A].Type == NPCID_RED_FISH_S3 || NPC[A].Type == NPCID_GRN_FISH_S4 || NPC[A].Type == NPCID_GRN_FISH_S1 || NPC[A].Type == NPCID_BONE_FISH || NPC[A].Type == NPCID_ICE_BLOCK || NPC[A].Type == NPCID_ICE_CUBE) // Goomba / Rex\n        {\n            if(B == 1 && NPC[A].Type != NPCID_GRN_FISH_S3 && NPC[A].Type != NPCID_YEL_FISH_S4 && NPC[A].Type != NPCID_RED_FISH_S3 && NPC[A].Type != NPCID_GRN_FISH_S4 && NPC[A].Type != NPCID_GRN_FISH_S1 && NPC[A].Type != NPCID_BONE_FISH)\n            {\n                if(NPC[A].Type == NPCID_FODDER_S3)\n                    NewEffect_NpcSquish(EFFID_FODDER_S3_SQUISH, NPC[A]);\n                else if(NPC[A].Type == NPCID_FODDER_S5)\n                    NewEffect_NpcSquish(EFFID_FODDER_S5_SQUISH, NPC[A]);\n                else if(NPC[A].Type == NPCID_SQUID_S1 && g_config.fix_squid_stomp_effect)\n                {\n                    NPC[A].Location.SpeedY = 0.123_n;\n                    NewEffect_NpcDie(EFFID_SQUID_S1_DIE, NPC[A]);\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                }\n                else if(NPC[A].Type == NPCID_SQUID_S3 && g_config.fix_squid_stomp_effect)\n                {\n                    NPC[A].Location.SpeedY = 0.123_n;\n                    NewEffect_NpcDie(EFFID_SQUID_S3_DIE, NPC[A]);\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                }\n                else\n                    NewEffect_NpcSquish(EFFID_BRUTE_SQUISH, NPC[A]);\n            }\n            else if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NPC[A].Location.X = NPC_CenterX - 16;\n                NPC[A].Location.Width = 32;\n                NPC[A].Location.Y = NPC_BottomY - 32;\n                NPC[A].Location.Height = 32;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                if(NPC[A].Type == NPCID_ICE_BLOCK) // Yoshi's ice break\n                    PlaySoundSpatial(SFX_Icebreak, NPC[A].Location);\n                else\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n\n                if(NPC[A].Type == NPCID_FODDER_S3 || NPC[A].Type == NPCID_FLY_FODDER_S3)\n                    NewEffect_NpcDie(EFFID_FODDER_S3_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_FODDER_S5 || NPC[A].Type == NPCID_FLY_FODDER_S5)\n                    NewEffect_NpcDie(EFFID_FODDER_S5_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_SQUID_S3)\n                    NewEffect_NpcDie(EFFID_SQUID_S3_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_SQUID_S1)\n                    NewEffect_NpcDie(EFFID_SQUID_S1_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_BRUTE)\n                    NewEffect_NpcDie(EFFID_BRUTE_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_BIG_GUY)\n                    NewEffect_NpcDie(EFFID_BIG_GUY_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_BRUTE_SQUISHED)\n                    NewEffect_NpcDie(EFFID_BRUTE_SQUISHED_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_GRN_FISH_S3)\n                {\n                    NPC[A].Location.SpeedY = -11;\n                    if(B == 1)\n                        NPC[A].Location.SpeedY = -2;\n                    NewEffect_NpcDie(EFFID_GRN_FISH_S3_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_YEL_FISH_S4)\n                {\n                    NPC[A].Location.SpeedY = -11;\n                    if(B == 1)\n                        NPC[A].Location.SpeedY = -2;\n                    NewEffect_NpcDie(EFFID_YEL_FISH_S4_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_RED_FISH_S3)\n                {\n                    NPC[A].Location.SpeedY = -11;\n                    if(B == 1)\n                        NPC[A].Location.SpeedY = -2;\n                    NewEffect_NpcDie(EFFID_RED_FISH_S3_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_GRN_FISH_S4)\n                {\n                    NPC[A].Location.SpeedY = -11;\n                    if(B == 1)\n                        NPC[A].Location.SpeedY = -2;\n                    NewEffect_NpcDie(EFFID_GRN_FISH_S4_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_GRN_FISH_S1)\n                {\n                    NPC[A].Location.SpeedY = -11;\n                    if(B == 1)\n                        NPC[A].Location.SpeedY = -2;\n                    NewEffect_NpcDie(EFFID_GRN_FISH_S1_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_BONE_FISH)\n                {\n                    NPC[A].Location.SpeedY = -11;\n                    if(B == 1)\n                        NPC[A].Location.SpeedY = -2;\n                    NewEffect_NpcDie(EFFID_BONE_FISH_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_ICE_BLOCK || NPC[A].Type == NPCID_ICE_CUBE)\n                {\n                    s_makeSparkles(NPC[A], 2, 3);\n                }\n                else\n                    NewEffect_NpcDie(EFFID_CARRY_FODDER_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_FLIER || NPC[A].Type == NPCID_ROCKET_FLIER || NPC[A].Type == NPCID_WALL_BUG || NPC[A].Type == NPCID_HOMING_BALL)\n        {\n            PlaySoundSpatial(SFX_SMKilled, NPC[A].Location);\n            NewEffect(EFFID_BOSS_FRAGILE_EXPLODE, NPC[A].Location);\n        }\n        else if(NPC[A].Type == NPCID_BOSS_CASE)\n        {\n            PlaySoundSpatial(SFX_SMGlass, NPC[A].Location);\n            for(C = 1; C <= 100; C++)\n            {\n                num_t dx = dRand();\n                num_t dy = dRand();\n                NewEffect(EFFID_BOSS_CASE_BREAK, newLoc(NPC[A].Location.X + dx.times(NPC[A].Location.Width) - 16, NPC[A].Location.Y + dy.times(NPC[A].Location.Height) - 16));\n            }\n        }\n        else if(NPC[A].Type == NPCID_BOSS_FRAGILE)\n        {\n            PlaySoundSpatial(SFX_SMCry, NPC[A].Location);\n            PlaySoundSpatial(SFX_SMExplosion, NPC[A].Location);\n            NewEffect(EFFID_BOSS_FRAGILE_DIE, NPC[A].Location, NPC[A].Direction);\n        }\n        else if(NPC[A].Type == NPCID_GHOST_S3 || NPC[A].Type == NPCID_GHOST_FAST || NPC[A].Type == NPCID_GHOST_S4 || NPC[A].Type == NPCID_BIG_GHOST || NPC[A].Type == NPCID_WALL_SPARK || NPC[A].Type == NPCID_FIRE_DISK)\n        {\n            PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n            NPC[A].Location.SpeedY = -10;\n            if(NPC[A].Type == NPCID_GHOST_S3)\n                NewEffect_NpcDie(EFFID_GHOST_S3_DIE, NPC[A]);\n            else if(NPC[A].Type == NPCID_GHOST_FAST)\n                NewEffect_NpcDie(EFFID_GHOST_FAST_DIE, NPC[A]);\n            else if(NPC[A].Type == NPCID_GHOST_S4)\n                NewEffect_NpcDie(EFFID_GHOST_S4_DIE, NPC[A]);\n            else if(NPC[A].Type == NPCID_WALL_SPARK)\n                NewEffect_NpcDie(EFFID_WALL_SPARK_DIE, NPC[A]);\n            else if(NPC[A].Type == NPCID_FIRE_DISK)\n            {\n                NewEffect_NpcDie(EFFID_FIRE_DISK_DIE, NPC[A]);\n                Effect[numEffects].Frame = NPC[A].Frame;\n                Effect[numEffects].Life = 100;\n            }\n            else\n                NewEffect_NpcDie(EFFID_BIG_GHOST_DIE, NPC[A]);\n        }\n        else if(NPC[A].Type == NPCID_CHASER) // bully\n        {\n            NPC[A].Location.SpeedY = -8;\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_BottomY - EffectHeight[EFFID_SMOKE_S3];\n                NPC[A].Location.Width = 32;\n                NPC[A].Location.Height = 32;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NewEffect_NpcDie(EFFID_CHASER_DIE, NPC[A]);\n            }\n\n        }\n        // turnips\n        else if(NPCIsVeggie(NPC[A]))\n        {\n            NPC[A].Location.Y = NPC_BottomY - 32;\n            NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n            NPC[A].Location.Y += NPC[A].Location.Height / 2 - EffectHeight[EFFID_SMOKE_S3] / 2;\n            NPC[A].Location.Height = 32;\n            NPC[A].Location.Width = 32;\n            if(NPC[A].Killed == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n        }\n        else if(NPC[A].Type == NPCID_COIN_SWITCH) // P Switch\n        {\n            if(B == 1)\n            {\n                NPC[A].Location.Y += 2;\n                NewEffect(EFFID_COIN_SWITCH_PRESS, NPC[A].Location);\n            }\n            else if(B == 2)\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n            }\n        }\n        else if(NPC[A].Type == NPCID_TIME_SWITCH) // P Switch Time\n        {\n            if(B == 1)\n            {\n                NPC[A].Location.Y += 2;\n                NewEffect(EFFID_TIME_SWITCH_PRESS, NPC[A].Location);\n            }\n            else if(B == 2)\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n            }\n        }\n        else if(NPC[A].Type == NPCID_TNT) // Red Switch\n        {\n            if(B == 1)\n            {\n                NPC[A].Location.Y += 2;\n                NewEffect(EFFID_TNT_PRESS, NPC[A].Location);\n            }\n            else if(B == 2)\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n            }\n        }\n        else if((NPC[A].Type == NPCID_STATUE_S3 || NPC[A].Type == NPCID_STATUE_S4) && B == 6) // lava only\n        {\n            NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n            NPC[A].Location.Y = NPC_BottomY - 32;\n            NPC[A].Location.Height = 32;\n            NPC[A].Location.Width = 32;\n            PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n            NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n            if(!NPC[A].NoLavaSplash)\n                NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n        }\n        else if(NPC[A].Type == NPCID_STONE_S3 || NPC[A].Type == NPCID_SAW || NPC[A].Type == NPCID_STONE_S4) // thwomp\n        {\n            if(B == 6)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_BottomY - 32;\n                NPC[A].Location.Height = 32;\n                NPC[A].Location.Width = 32;\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(NPC[A].Type == NPCID_SAW)\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NPC[A].Location.SpeedY = -10;\n                NPC[A].Location.Width = 64;\n                NPC[A].Location.Height = 64;\n                NPC[A].Location.X -= 8;\n                NPC[A].Location.Y -= 8;\n                NewEffect_NpcDie(EFFID_SAW_DIE, NPC[A]);\n            }\n            else if(B == 3 || B == 4 || B == 2)\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NPC[A].Location.SpeedY = -10;\n                if(NPC[A].Type == NPCID_STONE_S3)\n                    NewEffect_NpcDie(EFFID_STONE_S3_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_STONE_S4)\n                    NewEffect_NpcDie(EFFID_STONE_S4_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_WALK_BOMB_S3 || NPC[A].Type == NPCID_LIT_BOMB_S3)\n        {\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NPC[A].Location.SpeedY = -11;\n                NewEffect_NpcDie(EFFID_WALK_BOMB_S3_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_FODDER_S1) // SMB1 Goomba\n        {\n            if(B == 1)\n                NewEffect_NpcSquish(EFFID_FODDER_S1_SQUISH, NPC[A]);\n            else if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect_NpcDie(EFFID_FODDER_S1_DIE, NPC[A]);\n            }\n        // Zelda NPCs\n        }\n        else if(NPC[A].Type == NPCID_KNIGHT || NPCIsABot(NPC[A]) || NPC[A].Type == NPCID_LOCK_DOOR)\n        {\n            PlaySoundSpatial(SFX_HeroKill, NPC[A].Location);\n            NewEffect(EFFID_SMOKE_S5, NPC[A].Location);\n        }\n        else if(NPC[A].Type == NPCID_SPIT_GUY_BALL)\n        {\n            NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX * 0.3_r;\n            NewEffect(EFFID_SPIT_GUY_BALL_DIE, NPC[A].Location);\n        }\n        else if(NPC[A].Type == NPCID_YELSWITCH_FODDER || NPC[A].Type == NPCID_BLUSWITCH_FODDER || NPC[A].Type == NPCID_GRNSWITCH_FODDER || NPC[A].Type == NPCID_REDSWITCH_FODDER) // switch goombas\n        {\n            if(B == 1)\n            {\n                NewEffect_NpcSquish((EFFID)((NPC[A].Type - NPCID_YELSWITCH_FODDER) + EFFID_YELSWITCH_FODDER_SQUISH), NPC[A]);\n            }\n            else if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect_NpcDie((EFFID)((NPC[A].Type - NPCID_YELSWITCH_FODDER) + EFFID_YELSWITCH_FODDER_DIE), NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_MAGIC_BOSS || NPC[A].Type == NPCID_MAGIC_BOSS_SHELL || NPC[A].Type == NPCID_FIRE_BOSS || NPC[A].Type == NPCID_FIRE_BOSS_SHELL) // larry koopa\n        {\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(NPC[A].Type == NPCID_FIRE_BOSS || NPC[A].Type == NPCID_FIRE_BOSS_SHELL)\n                NewEffect(EFFID_FIRE_BOSS_DIE, NPC[A].Location);\n            else\n                NewEffect(EFFID_MAGIC_BOSS_DIE, NPC[A].Location);\n\n        }\n        else if(NPC[A].Type == NPCID_SICK_BOSS || NPC[A].Type == NPCID_BOMBER_BOSS) // wart, smb2 bosses\n        {\n            if(NPC[A].Type == NPCID_BOMBER_BOSS && NPC[A].Killed != 3 && NPC[A].Killed != 6 && NPC[A].Killed != 10)\n                PlaySoundSpatial(SFX_SpitBossBeat, NPC[A].Location);\n\n            // If B <> 6 Then MoreScore NPCScore(.Type), .Location\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y += 24;\n                NPC[A].Location.Width = 32;\n                NPC[A].Location.Height = 32;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else\n            {\n                NPC[A].Location.SpeedY = -7;\n                if(NPC[A].Type == NPCID_BOMBER_BOSS)\n                {\n                    NewEffect_NpcDie(EFFID_BOMBER_BOSS_DIE, NPC[A]);\n                    Effect[numEffects].Location.SpeedX = 0;\n                    Effect[numEffects].Location.SpeedY = -8;\n                }\n                else\n                    NewEffect(EFFID_SICK_BOSS_DIE, NPC[A].Location, NPC[A].Direction);\n            }\n        }\n        else if(NPC[A].Type == NPCID_VILLAIN_S1) // king koopa\n        {\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y += 24;\n                NPC[A].Location.Width = 32;\n                NPC[A].Location.Height = 32;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else\n            {\n                // .Location.Width += 2\n                // .Location.X += -1\n                NewEffect_NpcDie(EFFID_VILLAIN_S1_DIE, NPC[A]);\n            }\n            PlaySoundSpatial(SFX_VillainKilled, NPC[A].Location);\n        }\n        else if(NPC[A].Type == NPCID_VILLAIN_S3) // bowser\n        {\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y += 24;\n                NPC[A].Location.Width = 32;\n                NPC[A].Location.Height = 32;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else\n            {\n                NPC[A].Location.Width += 2;\n                NPC[A].Location.X -= 1;\n                NewEffect_NpcDie(EFFID_VILLAIN_S3_DIE, NPC[A]);\n            }\n            PlaySoundSpatial(SFX_VillainKilled, NPC[A].Location);\n\n            if(NPC[A].Legacy)\n            {\n                bool tempBool = false;\n                for(B = 1; B <= numNPCs; B++)\n                {\n                    if(B != A && NPC[B].Type == NPCID_VILLAIN_S3)\n                    {\n                        tempBool = true;\n                        break;\n                    }\n                }\n\n                if(!tempBool)\n                {\n                    speedRun_bossDeadEvent();\n                    LevelMacroCounter = 0;\n                    LevelMacro = LEVELMACRO_GAME_COMPLETE_EXIT;\n                }\n            }\n        }\n        else if(NPC[A].Type == NPCID_RED_FODDER || NPC[A].Type == NPCID_RED_FLY_FODDER) // Red goomba\n        {\n            if(B == 1)\n                NewEffect_NpcSquish(EFFID_RED_FODDER_SQUISH, NPC[A]);\n            else if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect_NpcDie(EFFID_RED_FODDER_DIE, NPC[A]);\n            }\n        }\n        else if(NPCIsToad(NPC[A])) // toad\n        {\n            if(B != 2)\n            {\n                if(B == 6)\n                {\n                    NPC[A].Location.Y += (NPC[A].Location.Height - 32);\n                    PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                    NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                    if(!NPC[A].NoLavaSplash)\n                        NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n                }\n                else\n                {\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                    NewEffect(EFFID_POWER_S3_DIE, NPC[A].Location, NPC[A].Direction);\n                }\n            }\n        }\n        else if(NPC[A].Type == NPCID_ITEM_POD) // yoshi egg\n        {\n            C = NPC[A].Special;\n            CharStuff(A, true);\n            if(NPC[A].Special == NPCID_RANDOM_POWER)\n                NPC[A].Special = RandomBonus();\n\n            if(NewEffect(EFFID_ITEM_POD_OPEN, NPC[A].Location, 1, false))\n            {\n                if(NPC[A].Special != NPCID_ITEM_POD)\n                    Effect[numEffects].NewNpc = NPC[A].Special;\n                Effect[numEffects].NewNpcSpecial = NPC[A].Variant;\n\n                // moved from NewEffect\n                PlaySoundSpatial((Effect[numEffects].NewNpc != 0) ? SFX_PetBirth : SFX_Smash, NPC[A].Location);\n            }\n\n            if(C == 98)\n                Effect[numEffects].Frame += 3;\n            else if(C == 99)\n                Effect[numEffects].Frame += 5;\n            else if(C == 100)\n                Effect[numEffects].Frame += 7;\n            else if(C == 148)\n                Effect[numEffects].Frame += 9;\n            else if(C == 149)\n                Effect[numEffects].Frame += 11;\n            else if(C == 150)\n                Effect[numEffects].Frame += 13;\n            else if(C == 228)\n                Effect[numEffects].Frame += 15;\n        }\n        else if(NPC[A].Type == NPCID_BIG_FODDER) // giagnormous goomba\n        {\n            if(B == 1)\n                NewEffect_NpcSquish(EFFID_BIG_FODDER_SQUISH, NPC[A]);\n            else if(B == 6)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_BottomY - 32;\n                NPC[A].Location.Height = 32;\n                NPC[A].Location.Width = 32;\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect_NpcDie(EFFID_BIG_FODDER_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_UNDER_FODDER) // Grey goomba\n        {\n            if(B == 1)\n                NewEffect_NpcSquish(EFFID_UNDER_FODDER_SQUISH, NPC[A]);\n            else if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                if(B != 5)\n                    NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                NewEffect_NpcDie(EFFID_UNDER_FODDER_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_EXT_TURTLE) // nekkid koopa\n        {\n            if(B == 1)\n                NewEffect_NpcSquish(EFFID_EXT_TURTLE_SQUISH, NPC[A]);\n            else if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                if(B != 5)\n                    NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                NewEffect_NpcDie(EFFID_EXT_TURTLE_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_SKELETON) // Dry Bones\n        {\n            if(B == 6)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_BottomY - 32;\n                NPC[A].Location.Height = 32;\n                NPC[A].Location.Width = 32;\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else\n            {\n                NPC[A].Location.Width = 48;\n                NPC[A].Location.X -= 8;\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NewEffect_NpcDie(EFFID_SKELETON_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4) // SMW Beach Koopas\n        {\n            if(B == 1)\n            {\n                NewEffect_NpcSquish(EFFID_HIT_TURTLE_S4_SQUISH, NPC[A]);\n                Effect[numEffects].Frame = NPC[A].Type - NPCID_GRN_HIT_TURTLE_S4;\n            }\n            else if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                if(B != 5)\n                    NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                NewEffect_NpcDie(EFFID_HIT_TURTLE_S4_DIE, NPC[A]);\n                Effect[numEffects].Frame = (NPC[A].Type - NPCID_GRN_HIT_TURTLE_S4) * 4;\n                if(NPC[A].Direction == 1)\n                    Effect[numEffects].Frame += 2;\n            }\n        }\n        else if(NPC[A].Type == NPCID_GRN_TURTLE_S3 || NPC[A].Type == NPCID_GRN_SHELL_S3 || NPC[A].Type == NPCID_GRN_FLY_TURTLE_S3) // Green Koopa\n        {\n            NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n            NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_SMOKE_S3] / 2;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect_NpcDie(EFFID_GRN_SHELL_S3_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_WALL_TURTLE) // spike top\n        {\n            if(B == 6)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect(EFFID_WALL_TURTLE_DIE, NPC[A].Location, NPC[A].Frame);\n            }\n        }\n        else if(NPC[A].Type == NPCID_GRN_SHELL_S1 || NPC[A].Type == NPCID_GRN_TURTLE_S1 || NPC[A].Type == NPCID_GRN_FLY_TURTLE_S1) // smb1 Green Koopa\n        {\n            NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n            NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_SMOKE_S3] / 2;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect_NpcDie(EFFID_GRN_SHELL_S1_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_RED_SHELL_S1 || NPC[A].Type == NPCID_RED_TURTLE_S1 || NPC[A].Type == NPCID_RED_FLY_TURTLE_S1) // smb1 red Koopa\n        {\n            NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n            NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_SMOKE_S3] / 2;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect_NpcDie(EFFID_RED_SHELL_S1_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_EARTHQUAKE_BLOCK)\n        {\n            Controls::RumbleAllPlayers(200, 1.0);\n\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else\n                NewEffect(EFFID_EARTHQUAKE_BLOCK_HIT, NPC[A].Location);\n        }\n        else if(NPC[A].Type == NPCID_BIG_TURTLE || NPC[A].Type == NPCID_BIG_SHELL) // giant Green Koopa\n        {\n            if(B == 6)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_BottomY - 32;\n                NPC[A].Location.Height = 32;\n                NPC[A].Location.Width = 32;\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_GRN_SHELL_S3_DIE] * 0.5_n;\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect_NpcDie(EFFID_BIG_SHELL_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_SPIT_BOSS) // Birdo\n        {\n            PlaySoundSpatial(SFX_SpitBossHit, NPC[A].Location);\n            if(NPC[A].Legacy && !LevelEditor)\n            {\n                bool DontSpawnExit = false;\n\n                for(C = 1; C <= numNPCs; C++)\n                {\n                    if(NPC[C].Type == NPCID_SPIT_BOSS && C != A)\n                    {\n                        DontSpawnExit = true;\n                        break;\n                    }\n                }\n\n                if(!DontSpawnExit)\n                {\n                    numNPCs++;\n                    NPC[numNPCs].Type = NPCID_GOALORB_S2;\n                    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                    NPC[numNPCs].Location.X = NPC[A].Location.X;\n                    NPC[numNPCs].Location.Y = NPC[A].Location.Y;\n                    NPC[numNPCs].Location.SpeedY = -6;\n                    NPC[numNPCs].Active = true;\n                    NPC[numNPCs].TimeLeft = 100;\n                    NPC[numNPCs].Frame = 0;\n                    syncLayers_NPC(numNPCs);\n                    CheckSectionNPC(numNPCs);\n                    PlaySoundSpatial(SFX_SpitBossBeat, NPC[A].Location);\n                }\n                else\n                {\n                    bool DontResetMusic = false;\n\n                    for(int nc : NPCQueues::Active.no_change)\n                    {\n                        if(NPC[nc].Type == NPCID_SPIT_BOSS && NPC[nc].Active && nc != A)\n                        {\n                            DontResetMusic = true;\n                            break;\n                        }\n                    }\n\n                    if(!DontResetMusic)\n                    {\n                        bgMusic[NPC[A].Section] = bgMusicREAL[NPC[A].Section];\n                        StartMusicIfOnscreen(NPC[A].Section);\n                    }\n                }\n            }\n\n            NPC[A].Location.Y += -NPC[A].Location.Height / 2 + 32;\n            NPC[A].Location.X += -NPC[A].Location.Width / 2 + 20;\n            NewEffect_NpcDie(EFFID_SPIT_BOSS_DIE, NPC[A]);\n        }\n        else if(NPC[A].Type == NPCID_SPIT_BOSS_BALL) // Egg\n        {\n            if(NPC[A].Special == 1)\n                NPC[A].Location.SpeedY = -5.1_n;\n            NewEffect_NpcDie(EFFID_SPIT_BOSS_BALL_DIE, NPC[A]);\n        }\n        else if(NPC[A].Type == NPCID_RED_TURTLE_S3 || NPC[A].Type == NPCID_RED_SHELL_S3 || NPC[A].Type == NPCID_RED_FLY_TURTLE_S3) // Red Koopa\n        {\n            NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_RED_SHELL_S3_DIE] * 0.5_n;\n            NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_RED_SHELL_S3_DIE] * 0.5_n;\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_SMOKE_S3] / 2;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                NewEffect_NpcDie(EFFID_RED_SHELL_S3_DIE, NPC[A]);\n            }\n\n        }\n        else if((NPC[A].Type >= NPCID_GRN_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_SHELL_S4) || (NPC[A].Type >= NPCID_GRN_FLY_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_FLY_TURTLE_S4)) // SMW Koopas\n        {\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NewEffect(EFFID_SHELL_S4_DIE, NPC[A].Location);\n                if(NPC[A].Type <= NPCID_YEL_TURTLE_S4)\n                    Effect[numEffects].Frame = NPC[A].Type - NPCID_GRN_TURTLE_S4;\n                else if(NPC[A].Type <= NPCID_YEL_SHELL_S4)\n                    Effect[numEffects].Frame = NPC[A].Type - NPCID_GRN_SHELL_S4;\n                else\n                    Effect[numEffects].Frame = NPC[A].Type - NPCID_GRN_FLY_TURTLE_S4;\n            }\n        }\n        else if(NPC[A].Type == NPCID_GLASS_TURTLE || NPC[A].Type == NPCID_GLASS_SHELL || NPC[A].Type == NPCID_SPIKY_S3 || NPC[A].Type == NPCID_CRAB || NPC[A].Type == NPCID_FLY || NPC[A].Type == NPCID_SPIKY_S4 || NPC[A].Type == NPCID_SPIKY_BALL_S4) // Hard thing / Spiney\n        {\n            NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_RED_SHELL_S3_DIE] * 0.5_n;\n            NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_RED_SHELL_S3_DIE] * 0.5_n;\n            if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_SMOKE_S3] / 2;\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n                if(NPC[A].Type == NPCID_SPIKY_S3)\n                    NewEffect_NpcDie(EFFID_SPIKY_S3_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_SPIKY_S4 || NPC[A].Type == NPCID_SPIKY_BALL_S4)\n                    NewEffect_NpcDie(EFFID_SPIKY_S4_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_CRAB)\n                    NewEffect_NpcDie(EFFID_CRAB_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_FLY)\n                    NewEffect_NpcDie(EFFID_FLY_DIE, NPC[A]);\n                else\n                    NewEffect_NpcDie(EFFID_GLASS_SHELL_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_HEAVY_THROWN) // Hammer\n        {\n            if(B == 3)\n            {\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n            }\n        }\n        else if(NPC[A].Type == NPCID_LONG_PLANT_UP || NPC[A].Type == NPCID_LONG_PLANT_DOWN)\n        {\n            NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_SMOKE_S3] / 2;\n            tempLocation = NPC[A].Location;\n            if(NPC[A].Type == NPCID_LONG_PLANT_DOWN)\n            {\n                int npcH = (int)NPC[A].Location.Height;\n                for(C = 0; C <= npcH; C += 32)\n                {\n                    if(NPC[A].Location.Height - C > 16)\n                    {\n                        tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height - 32 - C;\n                        NewEffect(EFFID_SMOKE_S3, tempLocation);\n                    }\n                }\n            }\n            else\n            {\n                int npcH = (int)NPC[A].Location.Height;\n                for(C = 0; C <= npcH; C += 32)\n                {\n                    if(NPC[A].Location.Height - C > 16)\n                    {\n                        tempLocation.Y = NPC[A].Location.Y + C;\n                        NewEffect(EFFID_SMOKE_S3, tempLocation);\n                    }\n                }\n            }\n            PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n        }\n        else if(NPC[A].Type == NPCID_PLANT_S3 || NPC[A].Type == NPCID_QUAD_SPITTER || NPC[A].Type == NPCID_PLANT_S1 || NPC[A].Type == NPCID_LAVABUBBLE || NPC[A].Type == NPCID_BOTTOM_PLANT || NPC[A].Type == NPCID_SIDE_PLANT || NPC[A].Type == NPCID_BIG_PLANT || NPC[A].Type == NPCID_STONE_S3 || NPC[A].Type == NPCID_GHOST_S3 || NPC[A].Type == NPCID_GHOST_FAST || NPC[A].Type == NPCID_GHOST_S4 || NPC[A].Type == NPCID_BIG_GHOST || NPC[A].Type == NPCID_FIRE_PLANT || NPC[A].Type == NPCID_JUMP_PLANT) // Piranha Plant / Fireball\n        {\n            NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n            if(B == 8)\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            else\n                PlaySoundSpatial(SFX_ShellHit, NPC[A].Location); // Shell hit sound\n        }\n        else if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PET_FIRE || NPC[A].Type == NPCID_PLR_ICEBALL) // Small Fireball / Yoshi Fireball\n        {\n            if(B == 6)\n            {\n                NPC[A].Location.X = NPC_CenterX - EffectWidth[EFFID_RED_SHELL_S3_DIE] * 0.5_n;\n                NPC[A].Location.Y = NPC_CenterY - EffectHeight[EFFID_RED_SHELL_S3_DIE] * 0.5_n;\n                NPC[A].Location.Width = 32;\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location, 1, NPC[A].Shadow);\n                NPC[A].Location.X += 2;\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else\n            {\n                for(C = 1; C <= 10; C++)\n                {\n                    if(NPC[A].Type == NPCID_PLR_ICEBALL)\n                        NewEffect(EFFID_PLR_ICEBALL_TRAIL, NPC[A].Location, NPC[A].Special, NPC[A].Shadow);\n                    else\n                        NewEffect(EFFID_PLR_FIREBALL_TRAIL, NPC[A].Location, NPC[A].Special, NPC[A].Shadow);\n\n                    Effect[numEffects].Location.SpeedX = dRand() * 3 - 1.5_n + NPC[A].Location.SpeedX / 10;\n                    Effect[numEffects].Location.SpeedY = dRand() * 3 - 1.5_n - NPC[A].Location.SpeedY / 10;\n                }\n                if((NPC[A].Type == NPCID_PLR_FIREBALL && NPC[A].Special == 5) || NPC[A].Type == NPCID_PET_FIRE)\n                    NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location, 1, NPC[A].Shadow);\n                else\n                    NewEffect(EFFID_SMOKE_S4, NPC[A].Location, 1, NPC[A].Shadow);\n            }\n        }\n        else if(NPC[A].Type == NPCID_MINIBOSS) // Big Koopa\n        {\n            NPC[A].Location.Y += -(NPC[A]->THeight - NPC[A].Location.Height);\n            NPC[A].Location.Height = NPC[A]->THeight;\n            if(NPC[A].Legacy)\n            {\n                bool DontSpawnExit = false;\n\n                for(B = 1; B <= numNPCs; B++)\n                {\n                    if(NPC[B].Type == NPCID_MINIBOSS && NPC[B].Killed == 0 && B != A)\n                    {\n                        DontSpawnExit = true;\n                        break;\n                    }\n                }\n\n                if(LevelEditor)\n                    DontSpawnExit = true;\n\n                if(!DontSpawnExit)\n                {\n                    if(NewEffect(EFFID_MINIBOSS_DIE, NPC[A].Location, 1))\n                        Effect[numEffects].NewNpc = NPCID_GOALORB_S3;\n                }\n                else\n                {\n                    bool DontResetMusic = false;\n\n                    NewEffect(EFFID_MINIBOSS_DIE, NPC[A].Location);\n\n                    for(int nc : NPCQueues::Active.no_change)\n                    {\n                        if(NPC[nc].Type == NPCID_MINIBOSS && NPC[nc].Active && nc != A && NPC[nc].Killed == 0)\n                        {\n                            DontResetMusic = true;\n                            break;\n                        }\n                    }\n\n                    if(!DontResetMusic)\n                    {\n                        bgMusic[NPC[A].Section] = bgMusicREAL[NPC[A].Section];\n                        StartMusicIfOnscreen(NPC[A].Section);\n                    }\n                }\n            }\n            else\n                NewEffect(EFFID_MINIBOSS_DIE, NPC[A].Location);\n        }\n        else if(NPC[A].Type == NPCID_BULLET || NPC[A].Type == NPCID_BIG_BULLET) // Bullet Bills\n        {\n            NPC[A].Location.SpeedX = NPC[A].Location.SpeedX / 2;\n            if(B == 1)\n                NPC[A].Location.SpeedX = 0.0001_n * NPC[A].Direction;\n            else if(B == 5)\n                NPC[A].Location.SpeedX = 3 * NPC[A].Direction;\n            else\n                NPC[A].Location.SpeedX = NPC[A].Location.SpeedX / 2;\n\n            if(B != 1)\n                NPC[A].Location.SpeedY = -9;\n\n            if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                if(B == 3 || B == 2)\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                if(NPC[A].Type == NPCID_BULLET)\n                    NewEffect_NpcDie(EFFID_BULLET_DIE, NPC[A]);\n                else\n                    NewEffect_NpcDie(EFFID_BIG_BULLET_DIE, NPC[A]);\n            }\n        }\n        else if(NPC[A].Type == NPCID_STACKER || NPC[A].Type == NPCID_BLU_GUY || NPC[A].Type == NPCID_RED_GUY || NPC[A].Type == NPCID_JUMPER_S3 || NPC[A].Type == NPCID_RED_FISH_S1 || NPC[A].Type == NPCID_HEAVY_THROWER || NPC[A].Type == NPCID_SPIKY_THROWER || NPC[A].Type == NPCID_ITEM_THROWER || NPC[A].Type == NPCID_SPIKY_BALL_S3 || NPC[A].Type == NPCID_JUMPER_S4 || NPC[A].Type == NPCID_BAT || (NPC[A].Type >= NPCID_BIRD && NPC[A].Type <= NPCID_GRY_SPIT_GUY) || NPC[A].Type == NPCID_CARRY_BUDDY || NPC[A].Type == NPCID_WALK_PLANT || NPC[A].Type == NPCID_VINE_BUG) // Misc Things\n        {\n            if(B == 6)\n            {\n                NPC[A].Location.Y += (NPC[A].Location.Height - 32);\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n            else if(B == 8)\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n            else\n            {\n                if(NPC[A].Type >= NPCID_BIRD && NPC[A].Type <= NPCID_GRY_SPIT_GUY)\n                    NewEffect((EFFID)((NPC[A].Type - NPCID_BIRD) + EFFID_BIRD_DIE), NPC[A].Location, NPC[A].Direction);\n                else if(NPC[A].Type == NPCID_BLU_GUY)\n                    NewEffect_NpcDie(EFFID_RED_GUY_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_RED_GUY)\n                    NewEffect_NpcDie(EFFID_BLU_GUY_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_WALK_PLANT)\n                    NewEffect_NpcDie(EFFID_WALK_PLANT_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_JUMPER_S3)\n                    NewEffect_NpcDie(EFFID_JUMPER_S3_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_RED_FISH_S1)\n                {\n                    NPC[A].Location.SpeedY = -11;\n                    if(NPC[A].Killed == 1)\n                        NPC[A].Location.SpeedY = 0;\n                    NewEffect_NpcDie(EFFID_RED_FISH_S1_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_CARRY_BUDDY)\n                    NewEffect_NpcDie(EFFID_CARRY_BUDDY_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_STACKER)\n                    NewEffect_NpcDie(EFFID_STACKER_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_VINE_BUG)\n                    NewEffect_NpcDie(EFFID_VINE_BUG_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_JUMPER_S4)\n                {\n                    if(B == 1)\n                    {\n                        NPC[A].Location.SpeedY = 0;\n                        NPC[A].Location.SpeedX = 0;\n                        PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                    }\n                    else\n                    {\n                        NPC[A].Location.SpeedY = -11;\n                        PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                    }\n\n                    NewEffect_NpcDie(EFFID_JUMPER_S4_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_BAT)\n                {\n                    if(B == 1)\n                    {\n                        NPC[A].Location.SpeedY = 0;\n                        NPC[A].Location.SpeedX = 0;\n                    }\n                    else\n                    {\n                        NPC[A].Location.SpeedY = -11;\n                        PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                    }\n\n                    NewEffect_NpcDie(EFFID_BAT_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_HEAVY_THROWER)\n                {\n                    if(B == 1)\n                    {\n                        NPC[A].Location.SpeedY = 0;\n                        NPC[A].Location.SpeedX = 0;\n                        PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                    }\n                    else\n                    {\n                        NPC[A].Location.SpeedY = -11;\n                        PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                    }\n\n                    NewEffect_NpcDie(EFFID_HEAVY_THROWER_DIE, NPC[A]);\n                }\n                else if(NPC[A].Type == NPCID_SPIKY_BALL_S3)\n                    NewEffect_NpcDie(EFFID_SPIKY_BALL_S3_DIE, NPC[A]);\n                else if(NPC[A].Type == NPCID_SPIKY_THROWER || NPC[A].Type == NPCID_ITEM_THROWER)\n                {\n                    if(NPC[A].Location.SpeedX > 4)\n                        NPC[A].Location.SpeedX = 4;\n                    if(NPC[A].Location.SpeedX < -4)\n                        NPC[A].Location.SpeedX = -4;\n\n                    if(B == 1)\n                    {\n                        NPC[A].Location.SpeedY = 0;\n                        NPC[A].Location.SpeedX = 0;\n                        PlaySoundSpatial(SFX_Stomp, NPC[A].Location);\n                    }\n                    else\n                    {\n                        NPC[A].Location.SpeedY = -11;\n                        PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                    }\n\n                    if(NPC[A].Type == NPCID_ITEM_THROWER)\n                        NewEffect_NpcDie(EFFID_ITEM_THROWER_DIE, NPC[A]);\n                    else\n                    {\n                        NPC[A].Location.Y -= 14;\n                        NewEffect_NpcDie(EFFID_SPIKY_THROWER_DIE, NPC[A]);\n                    }\n                }\n\n                if(NPC[A].Type != NPCID_HEAVY_THROWER && NPC[A].Type != NPCID_SPIKY_THROWER && NPC[A].Type != NPCID_JUMPER_S4)\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n            }\n        }\n        else if(NPC[A].Type == NPCID_SLIDE_BLOCK) // ice block\n        {\n            if(B == 6)\n            {\n                NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n            }\n            else\n            {\n                NewEffect(EFFID_SLIDE_BLOCK_SMASH, NPC[A].Location);\n                PlaySoundSpatial(SFX_BlockSmashed, NPC[A].Location);\n            }\n        }\n        else if(NPC[A]->IsABonus) // NPC is a bonus\n        {\n            if(B == 3 || B == 4 || B == 5)\n            {\n                if(!NPC[A]->IsACoin || LevelEditor || TestLevel) // Shell hit sound\n                    PlaySoundSpatial(SFX_ShellHit, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n            }\n            else if(B == 6)\n            {\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                if(!NPC[A].NoLavaSplash)\n                    NewEffect(EFFID_LAVA_SPLASH, NPC[A].Location);\n            }\n        }\n        else if(LevelEditor || MagicHand)\n        {\n            if(!(NPC[A].Type == NPCID_COIN_SWITCH && B == 1))\n            {\n                NewEffect(EFFID_SMOKE_S3_CENTER, NPC[A].Location);\n                PlaySoundSpatial(SFX_Smash, NPC[A].Location);\n            }\n        }\n    }\n\n    if(BattleMode)\n        NPC[A].RespawnDelay = true;\n\n    // note: this is the \"defective\" version of SetLayerSpeed, which (in classic mode) only sets the speed to 0, and does nothing else\n    if(NPC[A].AttLayer != LAYER_NONE && NPC[A].AttLayer != LAYER_DEFAULT)\n        SetLayerSpeed(NPC[A].AttLayer, 0, 0, false, true);\n\n    if((!GameMenu && !BattleMode) || NPC[A].DefaultType == 0)\n    {\n        for(B = 1; B <= numPlayers; B++) // Tell the player to stop standing on me because im dead kthnx\n        {\n            if(Player[B].StandingOnNPC == A)\n            {\n                Player[B].StandingOnNPC = 0;\n                if(NPC[A].Type != NPCID_VEHICLE)\n                    Player[B].Location.SpeedY = NPC[A].Location.SpeedY;\n            }\n            else if(Player[B].StandingOnNPC == numNPCs)\n                Player[B].StandingOnNPC = A;\n\n            if(Player[B].YoshiNPC == numNPCs)\n                Player[B].YoshiNPC = A;\n            if(Player[B].VineNPC == numNPCs)\n                Player[B].VineNPC = A;\n        }\n\n        SDL_assert_release(A > 0);\n        NPC[A] = NPC[numNPCs];\n        NPC[numNPCs] = NPC_t();\n        numNPCs--;\n        syncLayers_NPC(A);\n        syncLayers_NPC(numNPCs + 1);\n\n        if(NPC[A].HoldingPlayer > 0)\n        {\n            Player[NPC[A].HoldingPlayer].HoldingNPC = A;\n//            if(nPlay.Online == true && nPlay.Mode == 1)\n//                Netplay::sendData \"K\" + std::to_string(C) + \"|\" + NPC[A].Type + \"|\" + NPC[A].Location.X + \"|\" + NPC[A].Location.Y + \"|\" + std::to_string(NPC[A].Location.Width) + \"|\" + std::to_string(NPC[A].Location.Height) + \"|\" + NPC[A].Location.SpeedX + \"|\" + NPC[A].Location.SpeedY + \"|\" + NPC[A].Section + \"|\" + NPC[A].TimeLeft + \"|\" + NPC[A].Direction + \"|\" + std::to_string(static_cast<int>(floor(static_cast<double>(NPC[A].Projectile)))) + \"|\" + NPC[A].Special + \"|\" + NPC[A].Special2 + \"|\" + NPC[A].Special3 + \"|\" + NPC[A].Special4 + \"|\" + NPC[A].Special5 + \"|\" + NPC[A].Effect + LB + \"1n\" + NPC[A].HoldingPlayer + \"|\" + std::to_string(A) + \"|\" + NPC[A].Type + LB;\n        }\n\n        if(NPC[A].Effect == NPCEFF_PET_TONGUE || NPC[A].Effect == NPCEFF_PET_INSIDE)\n            Player[NPC[A].Effect2].YoshiNPC = A;\n\n        if(NPC[A].Type == NPCID_TOOTHYPIPE && NPC[A].Special == 1)\n        {\n            for(int C : NPCQueues::Active.no_change)\n            {\n                if(NPC[C].Type == NPCID_TOOTHY && NPC[C].Special2 == numNPCs + 1)\n                    NPC[C].Special2 = A;\n            }\n        }\n        else if(NPC[A].Type == NPCID_TOOTHYPIPE && NPC[A].Special2 > 0)\n            NPC[NPC[A].Special2].Special2 = A;\n        else if(NPC[A].Type == NPCID_TOOTHY && NPC[A].Special2 > 0)\n            NPC[NPC[A].Special2].Special2 = A;\n    }\n    else\n    {\n        Deactivate(A);\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/npc/npc_queues.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <sorting/pdqsort.h>\n\n#include <vector>\n#include <set>\n\n#include \"globals.h\"\n\n#include \"npc/npc_queues.h\"\n\nnamespace NPCQueues\n{\n\nstd::vector<NPCRef_t> NoReset;\n\nstd::vector<NPCRef_t> Killed;\nstd::vector<NPCRef_t> Unchecked;\n\nstd::vector<NPCRef_t> PlayerTemp;\n\nSafeSet<NPCRef_t> Active;\n\nstd::set<NPCRef_t> RespawnDelay;\n\nvoid clear()\n{\n    NoReset.clear();\n\n    Killed.clear();\n    Unchecked.clear();\n\n    PlayerTemp.clear();\n\n    Active.clear();\n\n    RespawnDelay.clear();\n}\n\nvoid update(NPCRef_t npc)\n{\n    if((int)npc > numNPCs)\n    {\n        Active.erase(npc);\n        RespawnDelay.erase(npc);\n\n        return;\n    }\n\n    // cheaper to do this than to check if it's already there\n    NoReset.push_back(npc);\n    Unchecked.push_back(npc);\n\n    if(npc->playerTemp)\n        PlayerTemp.push_back(npc);\n\n    if(check_active(npc))\n        Active.insert(npc);\n    else\n        Active.erase(npc);\n\n    if(npc->RespawnDelay)\n        RespawnDelay.insert(npc);\n    else\n        RespawnDelay.erase(npc);\n\n    if(npc->Killed)\n        Killed.push_back(npc);\n}\n\nvoid reverse_sort(int16_t* start, int16_t* end)\n{\n    pdqsort(start, end,\n        [](int16_t a, int16_t b)\n        {\n            return a > b;\n        }\n    );\n}\n\n} // namespace NPCQueues\n"
  },
  {
    "path": "src/npc/npc_queues.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#pragma once\n\n#ifndef NPC_QUEUES_H\n#define NPC_QUEUES_H\n\n/*\n * The goal of this file is to maintain certain queues to track which NPCs\n * need to be accessed at which times. Ultimately, the intention is to\n * eliminate all loops over the entire NPC array.\n *\n *\n * All queues except paired write/read vector queues share a common interface:\n *   - C++11 iteration over `NPCRef_t`s\n *   - clear()\n *   - insert(NPCRef_t)\n *   - erase(NPCRef_t)\n *   - size_t size()\n *\n *\n * Each type of queue must be chosen for the optimal use case:\n *\n * - Some queues are write-once/read-once per-frame, like the set of NPCs that\n *   currently have one of the Reset flags disabled. They can be simple\n *   vectors, or pairs of vectors if they are simultaneously read and written.\n *\n * - Other queues are read and written frequently, but persist across frames,\n *   like the set of NPCs that are Active or have TimeLeft > 0.\n *   They should carefully be tuned as sets, well-constructed linked lists,\n *   or indexed sorted vectors, depending on use case.\n *\n * - Some queues need to be sorted, and others don't.\n *\n *\n * When implementing a new queue, it is best to start with the set-backed\n * queue because this one has the simplest implementation. Then if profiling\n * reveals a need, we can change the queue.\n *\n * -- ds-sloth\n */\n\n#include <vector>\n#include <set>\n#include \"npc/safe_set.hpp\"\n#include \"npc_traits.h\"\n\n#include \"globals.h\"\n\nnamespace NPCQueues\n{\n\n// NPC[A].Reset[X] = false -> NPCQueues::NoReset.push_back(A);\nextern std::vector<NPCRef_t> NoReset;\n\n// NPC[A].Killed = X -> NPCQueues::Killed.push_back(A);\nextern std::vector<NPCRef_t> Killed;\n\n// NPC[A].playerTemp = true -> NPCQueues::PlayerTemp.push_back(A);\nextern std::vector<NPCRef_t> PlayerTemp;\n\n// NPC[A].Location.Width modified or NPC A deactivated -> NPCQueues::Unchecked.push_back(A);\n//   Also add if certain fields (RealSpeedX and various counters) are unexpectedly modified while NPC inactive\nextern std::vector<NPCRef_t> Unchecked;\n\n// Contained in any of the following cases:\n// - NPC[A].Active true\n// - NPC[A].Generator true\n// - NPC[A].JustActivated true\n// - check_active_type(NPC[A]) true -- check when changing NPC[A].Type\n// - NPC[A].AttLayer not LAYER_NONE or LAYER_DEFAULT\nextern SafeSet<NPCRef_t> Active;\n\n// NPC[A].RespawnDelay modified -> NPCQueues::RespawnDelay.insert(A) / erase(A);\nextern std::set<NPCRef_t> RespawnDelay;\n\ninline bool check_active_type(NPCRef_t n)\n{\n    return n->Type == NPCID_CONVEYOR || n->Type == NPCID_YEL_PLATFORM || n->Type == NPCID_BLU_PLATFORM || n->Type == NPCID_GRN_PLATFORM || n->Type == NPCID_RED_PLATFORM;\n}\n\ninline bool check_exclude_vine(NPCRef_t n)\n{\n    return (*n)->IsAVine && !((*n)->IsABlock || (*n)->IsAHit1Block || (*n)->CanWalkOn);\n}\n\ninline bool check_active(NPCRef_t n)\n{\n    return (n->Active && !check_exclude_vine(n)) || n->Generator || n->JustActivated || check_active_type(n) || (n->AttLayer != LAYER_NONE && n->AttLayer != LAYER_DEFAULT);\n}\n\nvoid clear();\n\nvoid update(NPCRef_t npc);\n\n/* utility function to help with multiple NPC-related routines */\nvoid reverse_sort(int16_t* start, int16_t* end);\n\ntemplate<class T>\nvoid reverse_sort(std::vector<T>& vec)\n{\n    static_assert(sizeof(*vec.data()) == sizeof(int16_t), \"reverse_sort can only be called on BaseRef_t and its children\");\n    reverse_sort(reinterpret_cast<int16_t*>(vec.data()), reinterpret_cast<int16_t*>(vec.data() + vec.size()));\n}\n\n} // namespace NPCQueues\n\n#endif // #ifndef NPC_QUEUES_H\n"
  },
  {
    "path": "src/npc/npc_update/npc_block_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"blocks.h\"\n#include \"npc.h\"\n#include \"npc_traits.h\"\n#include \"npc/npc_queues.h\"\n#include \"config.h\"\n#include \"collision.h\"\n#include \"layers.h\"\n#include \"effect.h\"\n#include \"eff_id.h\"\n#include \"blk_id.h\"\n\n#include \"main/trees.h\"\n\nvoid NPCBlockLogic(int A, num_t& tempHit, int& tempHitBlock, tempf_t& tempSpeedA, const int numTempBlock, const tempf_t speedVar)\n{\n    bool resetBeltSpeed = false;\n    bool beltClear = false; // \"stops belt movement when on a wall\" (Redigit)\n    bool onWall = false; // temporary variable, moved from NPC_t struct\n    tempf_t beltCount = 0;\n    tempf_t addBelt = 0;\n\n    bool SlopeTurn = false;\n\n    // int tempBlockHit[3] = {0}; // keeps track of up to two blocks hit from below\n    int tempBlockHit1 = 0;\n    int tempBlockHit2 = 0;\n    int tempHitIsSlope = 0;\n\n    int oldSlope = NPC[A].Slope; // previous sloped block the npc was on\n    tempf_t oldBeltSpeed = (tempf_t)NPC[A].BeltSpeed;\n\n    NPC[A].Slope = 0;\n    // now, use tempf_t to do all BeltSpeed computations (accumulation, etc) in full precision in fixed-point builds\n    // NPC[A].BeltSpeed = 0;\n    tempf_t beltSpeed = 0;\n\n    bool is_winged = (NPCIsAParaTroopa(NPC[A]) || (NPC[A].Wings && NPC[A].Wings != WING_JUMP));\n\n    if((!NPC[A]->NoClipping || NPC[A].Projectile || NPC[A].Wings) &&\n       !(NPC[A].Type == NPCID_SPIT_BOSS_BALL && NPC[A].Projectile) && NPC[A].Type != NPCID_TOOTHY &&\n        NPC[A].vehiclePlr == 0 && !(NPCIsVeggie(NPC[A]) && NPC[A].Projectile) &&\n       NPC[A].Type != NPCID_HEAVY_THROWN && NPC[A].Type != NPCID_BIG_BULLET && NPC[A].Type != NPCID_PET_FIRE &&\n       !(NPC[A]->IsFish && NPC[A].Special == 2) && NPC[A].Type != NPCID_VINE_BUG)\n    {\n        for(int bCheck = 1; bCheck <= 2; bCheck++)\n        {\n            // if(bCheck == 1)\n            // {\n            //     // fBlock = FirstBlock[(int)SDL_floor(NPC[A].Location.X / 32) - 1];\n            //     // lBlock = LastBlock[(int)SDL_floor((NPC[A].Location.X + NPC[A].Location.Width) / 32.0) + 1];\n            //     blockTileGet(NPC[A].Location, fBlock, lBlock);\n            // }\n            // else\n            // {\n            //     fBlock = numBlock + 1 - numTempBlock;\n            //     lBlock = numBlock;\n            // }\n            auto collBlockSentinel = (bCheck == 1)\n                ? treeFLBlockQuery(NPC[A].Location, SORTMODE_COMPAT)\n                : treeTempBlockQuery(NPC[A].Location, SORTMODE_LOC);\n\n            for(BlockRef_t block : collBlockSentinel)\n            {\n                int B = block;\n                // If Not .Block = B And Not .tempBlock = B And Not (.Projectile = True And Block(B).noProjClipping = True) And BlockNoClipping(Block(B).Type) = False And Block(B).Hidden = False And Block(B).Hidden = False Then\n\n\n\n\n                if(NPC[A].Location.X + NPC[A].Location.Width >= Block[B].Location.X)\n                {\n                    if(NPC[A].Location.X <= Block[B].Location.X + Block[B].Location.Width)\n                    {\n                        if(NPC[A].Location.Y + NPC[A].Location.Height >= Block[B].Location.Y)\n                        {\n                            if(NPC[A].Location.Y <= Block[B].Location.Y + Block[B].Location.Height)\n                            {\n\n                                // If CheckCollision(.Location, Block(B).Location) = True Then\n\n\n\n                                // the coinSwitchBlockType != B check is an SMBX 1.3 bug, probably because the field was called \"Block\"\n                                if(NPC[A].coinSwitchBlockType() != B && NPC[A].tempBlock != B &&\n                                   !(NPC[A].Projectile && Block[B].tempBlockNoProjClipping()) &&\n                                   !BlockNoClipping[Block[B].Type] && !Block[B].Hidden)\n                                {\n                                    if(Block[B].tempBlockNpcType == NPCID_TANK_TREADS && !NPC[A]->NoClipping && NPC[A].Type != NPCID_BULLET)\n                                        NPCHit(A, 8);\n\n                                    // traits of the block's NPC (if it is actually an NPC)\n                                    const NPCTraits_t& blk_npc_tr = NPCTraits[Block[B].tempBlockNpcType];\n\n                                    bool block_is_not_conveyor = (Block[B].tempBlockNpcType != NPCID_CONVEYOR) && !(Block[B].Type >= BLKID_CONVEYOR_L_START && Block[B].Type <= BLKID_CONVEYOR_R_END);\n\n                                    int HitSpot;\n                                    if(block_is_not_conveyor && (blk_npc_tr.IsABlock || blk_npc_tr.IsAHit1Block || blk_npc_tr.CanWalkOn))\n                                        HitSpot = NPCFindCollision(NPC[A].Location, Block[B].Location);\n                                    else\n                                        HitSpot = FindCollisionBelt(NPC[A].Location, Block[B].Location, oldBeltSpeed);\n\n                                    // block-based cancellation logic (plus some type-based cancellation logic that was placed between these in SMBX 1.3)\n                                    if(Block[B].tempBlockVehiclePlr > 0 && ((!NPC[A]->StandsOnPlayer && NPC[A].Type != NPCID_PLR_FIREBALL) || NPC[A].Inert))\n                                        HitSpot = 0;\n\n                                    if(Block[B].Invis)\n                                    {\n                                        if(HitSpot != 3)\n                                            HitSpot = 0;\n                                    }\n\n                                    if(HitSpot == 5)\n                                    {\n                                        if(CheckHitSpot1(NPC[A].Location, Block[B].Location))\n                                            HitSpot = 1;\n                                        else if(Block[B].tempBlockNpcType == NPCID_CONVEYOR) // && HitSpot == 5\n                                        {\n                                            if(Block[B].Location.to_right_of(NPC[A].Location))\n                                                HitSpot = 4;\n                                            else\n                                                HitSpot = 2;\n                                        }\n                                        // after CheckHitSpot1 and NPCID_CONVEYOR check, before sizable check\n                                        else if(NPC[A].Type == NPCID_MINIBOSS) // && HitSpot == 5\n                                        {\n                                            if(NPC[A].WallDeath >= 5)\n                                            {\n                                                NPC[A].Killed = 3;\n                                                NPCQueues::Killed.push_back(A);\n                                            }\n                                            else\n                                                HitSpot = 3;\n                                        }\n                                    }\n\n                                    // after CheckHitSpot1, before sizable check\n                                    if(NPC[A].Type == NPCID_VILLAIN_S3)\n                                    {\n                                        if(HitSpot != 1 && NPC[A].Special > 0)\n                                        {\n                                            if(Block[B].Location.X < level[NPC[A].Section].X + 48 || Block[B].Location.X > level[NPC[A].Section].Width - 80)\n                                                NPC[A].Special = 0;\n                                            else\n                                            {\n                                                HitSpot = 0;\n                                                resetBeltSpeed = true;\n                                            }\n                                        }\n                                        else if(HitSpot == 3)\n                                        {\n                                            if(NPC[A].Special4 == 3)\n                                            {\n                                                NPC[A].Frame = 10;\n                                                NPC[A].Special3 = 21;\n                                                NPC[A].Special = 1;\n                                                NPC[A].Location.SpeedX = 0;\n                                            }\n                                        }\n                                    }\n\n                                    if(BlockIsSizable[Block[B].Type] || BlockOnlyHitspot1[Block[B].Type])\n                                    {\n                                        if(HitSpot != 1 || (NPCIsAParaTroopa(NPC[A]) && NPC[A].Special != WING_JUMP) || (NPC[A].Wings && NPC[A].Wings != WING_JUMP))\n                                            HitSpot = 0;\n                                    }\n\n                                    // Unified type-based cancellation / collision logic -- prior to slope and lava checks. Deprecate this and don't allow users to define.\n                                    if(NPC[A]->IsFish)\n                                    {\n                                        // fish ignore block collisions while out of water (eg, jumping)\n                                        if(NPC[A].Wet == 0)\n                                        {\n                                            if(NPC[A].WallDeath >= 9)\n                                                HitSpot = 0;\n                                        }\n\n                                        // leaping fish always ignore non-ceiling collisions\n                                        if(NPC[A].Special == 2 && HitSpot != 3)\n                                            HitSpot = 0;\n                                    }\n                                    else if(NPC[A].Type == NPCID_PLR_HEAVY || NPC[A].Type == NPCID_SWORDBEAM || NPC[A].Type == NPCID_CHAR4_HEAVY)\n                                    {\n                                        if(Block[B].Type == 457)\n                                            SafelyKillBlock(B);\n\n                                        HitSpot = 0;\n                                    }\n                                    else if(NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT || NPC[A].Type == NPCID_VPIPE_LONG)\n                                    {\n                                        if(Block[B].tempBlockVehiclePlr > 0 || Block[B].tempBlockNpcType == NPCID_VEHICLE)\n                                        {\n                                            HitSpot = 0;\n                                            NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                                        }\n                                    }\n                                    else if(NPC[A].Type >= NPCID_TANK_TREADS && NPC[A].Type <= NPCID_SLANT_WOOD_M)\n                                    {\n                                        if(HitSpot != 1)\n                                            HitSpot = 0;\n                                    }\n                                    else if(NPC[A].Type == NPCID_SPIKY_BALL_S3)\n                                    {\n                                        if(Block[B].tempBlockNpcType == NPCID_CANNONITEM || Block[B].tempBlockNpcType == NPCID_TOOTHYPIPE) // spiney eggs don't walk on special items\n                                            HitSpot = 0;\n                                    }\n                                    else if(NPC[A].Type == NPCID_RAFT) // Skull raft\n                                    {\n                                        if(Block[B].tempBlockNpcType > 0)\n                                            HitSpot = 0;\n\n                                        if(g_config.fix_skull_raft) // reached a solid wall\n                                        {\n                                            auto bt = Block[B].Type;\n                                            if(Block[B].tempBlockNpcType <= 0 && NPC[A].Special == 1 &&\n                                              (HitSpot == COLLISION_LEFT || HitSpot == COLLISION_RIGHT) &&\n                                               BlockSlope[bt] == SLOPE_FLOOR && BlockSlope2[bt] == SLOPE_CEILING)\n                                            {\n                                                if(npcHasFloor(NPC[A]) || NPC[A].Special2 > 0)\n                                                {\n                                                    SkullRide(A, false, &Block[B].Location);\n                                                    NPC[A].Special = 3; // 3 - watcher, 2 - waiter\n                                                }\n                                            }\n                                        }\n                                    }\n                                    else if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL || NPC[A].Type == NPCID_PET_FIRE)\n                                    {\n                                        if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL)\n                                        {\n                                            // NEW: bugfix to make ice balls properly respect char blocks\n                                            int special = (g_config.fix_char_pass_balls) ? NPC[A].Variant : NPC[A].Special;\n\n                                            if(Block[B].Type == 626 && special == 1)\n                                                HitSpot = 0;\n                                            if(Block[B].Type == 627 && special == 2)\n                                                HitSpot = 0;\n                                            if(Block[B].Type == 628 && special == 3)\n                                                HitSpot = 0;\n                                            if(Block[B].Type == 629 && special == 4)\n                                                HitSpot = 0;\n                                            if(Block[B].Type == 632 && special == 5 && g_config.fix_char_pass_balls) // NEW\n                                                HitSpot = 0;\n\n                                            if(NPCTraits[Block[B].tempBlockNpcType].IsABonus)\n                                                HitSpot = 0;\n\n                                            if(NPC[A].Type == NPCID_PLR_FIREBALL && Block[B].tempBlockNpcType == NPCID_ICE_CUBE)\n                                                HitSpot = 0;\n                                        }\n\n                                        if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PET_FIRE)\n                                        {\n                                            if(Block[B].Type == 621 || Block[B].Type == 620)\n                                            {\n                                                NPCHit(A, 3, A);\n                                                if(Block[B].Type == 621)\n                                                    Block[B].Type = 109;\n                                                else\n                                                {\n                                                    Block[B].Layer = LAYER_DESTROYED_BLOCKS;\n                                                    Block[B].Hidden = true;\n                                                    syncLayersTrees_Block(B);\n                                                    numNPCs++;\n                                                    NPC[numNPCs].Location.Width = 28;\n                                                    NPC[numNPCs].Location.Height = 32;\n                                                    NPC[numNPCs].Type = NPCID_COIN_S3;\n                                                    NPC[numNPCs].Location.Y = Block[B].Location.Y;\n                                                    NPC[numNPCs].Location.X = Block[B].Location.X + 2;\n                                                    NPC[numNPCs].Active = true;\n                                                    NPC[numNPCs].DefaultType = NPC[numNPCs].Type;\n                                                    NPC[numNPCs].DefaultLocationX = NPC[numNPCs].Location.X;\n                                                    NPC[numNPCs].DefaultLocationY = NPC[numNPCs].Location.Y;\n                                                    NPC[numNPCs].TimeLeft = 100;\n                                                    syncLayers_NPC(numNPCs);\n                                                    CheckSectionNPC(numNPCs);\n                                                }\n                                            }\n                                        }\n                                    }\n                                    else if(NPC[A].Type == NPCID_STONE_S3 || NPC[A].Type == NPCID_STONE_S4)\n                                    {\n                                        if(HitSpot != 1)\n                                            HitSpot = 0;\n                                    }\n                                    else if(NPC[A].Type == NPCID_SPIT_GUY_BALL)\n                                    {\n                                        if(HitSpot > 0)\n                                        {\n                                            NPC[A].Killed = 4;\n                                            NPCQueues::Killed.push_back(A);\n                                        }\n                                    }\n                                    else if(NPC[A].Type == NPCID_BOMB)\n                                    {\n                                        if(NPC[A].Projectile && HitSpot != 0)\n                                            NPC[A].Special = 1000;\n                                    }\n                                    // vine makers\n                                    else if(NPC[A].Type == NPCID_RED_VINE_TOP_S3 || NPC[A].Type == NPCID_GRN_VINE_TOP_S3 || NPC[A].Type == NPCID_GRN_VINE_TOP_S4)\n                                    {\n                                        if(HitSpot == 3)\n                                            NPC[A].Special = 1;\n                                    }\n                                    else if(NPC[A].Type == NPCID_GOALTAPE)\n                                    {\n                                        if(Block[B].tempBlockNpcType > 0)\n                                            HitSpot = 0;\n                                    }\n                                    else if(NPC[A].Type == NPCID_WALL_BUG || NPC[A].Type == NPCID_WALL_SPARK || NPC[A].Type == NPCID_WALL_TURTLE)\n                                    {\n                                        // tells the NPC that it's supported\n                                        NPC[A].Special5 = 0;\n\n                                        if(HitSpot == 1)\n                                        {\n                                            if(NPC[A].Special == 4 && NPC[A].Location.X + 0.99_n == Block[B].Location.X + Block[B].Location.Width)\n                                                HitSpot = 0;\n\n                                            if(NPC[A].Special == 2 && NPC[A].Location.X + NPC[A].Location.Width - 0.99_n == Block[B].Location.X)\n                                                HitSpot = 0;\n                                        }\n\n                                        if(BlockIsSizable[Block[B].Type] || BlockOnlyHitspot1[Block[B].Type])\n                                            HitSpot = 0;\n\n                                        if(BlockSlope2[Block[B].Type] != 0 && HitSpot == 3)\n                                        {\n                                            if(NPC[A].Special == 4 && NPC[A].Special2 == -1)\n                                            {\n                                                if(NPC[A].Location.X - 0.01_n == Block[B].Location.X)\n                                                {\n                                                    NPC[A].Special = 3;\n                                                    NPC[A].Special2 = 1;\n                                                    NPC[A].Location.SpeedX = -NPC[A].Location.SpeedY;\n                                                }\n                                            }\n                                        }\n\n                                        if(BlockSlope2[Block[B].Type] != 0 && HitSpot == 1)\n                                        {\n                                            if(NPC[A].Special == 4)\n                                                HitSpot = 2;\n                                            if(NPC[A].Special == 2)\n                                                HitSpot = 4;\n                                        }\n\n                                        if(NPC[A].Special == 3)\n                                        {\n                                            // if(BlockSlope2[Block[B].Type] != 0)\n                                            // {\n                                            //     if(HitSpot == 2 || HitSpot == 4)\n                                            //         HitSpot = 0;\n                                            // }\n                                            // else\n                                            if(HitSpot == 2 || HitSpot == 4)\n                                                HitSpot = 0;\n                                        }\n\n                                        if(Block[B].tempBlockNpcType > 0)\n                                            HitSpot = 0;\n\n                                        // hit a wall, cancel wings and switch routines\n                                        if(HitSpot)\n                                            NPC[A].Wings = WING_NONE;\n                                    }\n                                    else if(NPC[A].Type == NPCID_SAW)\n                                    {\n                                        if(Block[B].tempBlockNpcType > 0)\n                                            HitSpot = 0;\n                                    }\n\n                                    // shadow mode cancellation\n                                    if(NPC[A].Shadow && HitSpot != 1 && !(Block[B].Special > 0 && NPC[A].Projectile))\n                                        HitSpot = 0;\n\n                                    if(BlockSlope2[Block[B].Type] != 0 && HitSpot > 0 && ((NPC[A].Location.Y > Block[B].Location.Y) || ((NPC[A].Type == NPCID_WALL_BUG || NPC[A].Type == NPCID_WALL_SPARK || NPC[A].Type == NPCID_WALL_TURTLE) && NPC[A].Special == 3)))\n                                    {\n                                        // the modifications to Special and Special2 here are invalid and could affect any NPC. They were meant to affect wall climbers.\n                                        num_t NPC_A_Special = NPC[A].Special;\n                                        num_t NPC_A_Special2 = NPC[A].Special2;\n\n                                        char Special_from = 0;\n                                        char Special2_from = 0;\n\n                                        if(NPC[A].Type == NPCID_YEL_PLATFORM || NPC[A].Type == NPCID_BLU_PLATFORM || NPC[A].Type == NPCID_GRN_PLATFORM || NPC[A].Type == NPCID_RED_PLATFORM || NPC[A].Type == NPCID_PLATFORM_S3 || NPC[A].Type == NPCID_SAW)\n                                        {\n                                            Special_from = 'Y';\n                                            Special2_from = 'X';\n                                            NPC_A_Special = NPC[A].SpecialY;\n                                            NPC_A_Special2 = NPC[A].SpecialX;\n                                        }\n                                        else if(NPC[A].Type == NPCID_HOMING_BALL_GEN)\n                                        {\n                                            Special_from = 'Y';\n                                            NPC_A_Special = NPC[A].SpecialY;\n                                        }\n                                        else if(NPC[A].Type == NPCID_VILLAIN_S3 || NPC[A].Type == NPCID_MINIBOSS || NPC[A].Type == NPCID_GOALTAPE || NPC[A].Type == NPCID_BAT)\n                                        {\n                                            Special2_from = 'Y';\n                                            NPC_A_Special2 = NPC[A].SpecialY;\n                                        }\n                                        else if(NPC[A].Type == NPCID_SPIT_BOSS)\n                                        {\n                                            Special_from = '5';\n                                            NPC_A_Special = NPC[A].Special5;\n                                        }\n\n                                        // previously going down a wall? now turn around this outer corner\n                                        if(HitSpot == 5 && (NPC_A_Special == 2 || NPC_A_Special == 4) && NPC_A_Special2 == 1)\n                                        {\n                                            // left side -> move right on ceiling slope\n                                            if(NPC_A_Special == 2)\n                                                NPC_A_Special2 = 1;\n                                             // right side -> move left on ceiling slope\n                                            else\n                                                NPC_A_Special2 = -1;\n\n                                            NPC_A_Special = 3;\n                                        }\n\n                                        HitSpot = 0;\n                                        num_t PlrMid;\n                                        if(BlockSlope2[Block[B].Type] == 1)\n                                            PlrMid = NPC[A].Location.X + NPC[A].Location.Width;\n                                        else\n                                            PlrMid = NPC[A].Location.X;\n\n                                        num_t Slope = (PlrMid - Block[B].Location.X) / (int_ok)Block[B].Location.Width;\n                                        if(BlockSlope2[Block[B].Type] > 0)\n                                            Slope = 1 - Slope;\n                                        if(Slope < 0)\n                                            Slope = 0;\n                                        if(Slope > 1)\n                                            Slope = 1;\n\n                                        if(NPC[A].Location.Y < Block[B].Location.Y + Block[B].Location.Height - ((int_ok)Block[B].Location.Height * Slope) - 0.1_n)\n                                        {\n                                            if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_BULLET || NPC[A].Type == NPCID_PLR_ICEBALL)\n                                                NPCHit(A, 3, A);\n\n                                            if(NPC[A].Type == NPCID_ICE_CUBE)\n                                            {\n                                                if(NPC[A].Location.SpeedY < -2)\n                                                    NPCHit(A, 3, A);\n                                            }\n\n                                            if(num_t::fEqual_d(NPC[A].Location.SpeedY, Physics.NPCGravity) || NPC[A].Slope > 0 || oldSlope > 0)\n                                            {\n                                                // previously going up a wall? now turn within this inner corner\n                                                if((NPC_A_Special == 2 || NPC_A_Special == 4) && NPC_A_Special2 == -1)\n                                                {\n                                                    // right side -> move right on ceiling slope\n                                                    if(NPC_A_Special == 4)\n                                                        NPC_A_Special2 = 1;\n                                                    // left side -> move left on ceiling slope\n                                                    else\n                                                        NPC_A_Special2 = -1;\n\n                                                    NPC_A_Special = 3;\n                                                }\n\n                                                PlrMid = NPC[A].Location.Y;\n                                                Slope = (PlrMid - Block[B].Location.Y) / (int_ok)Block[B].Location.Height;\n                                                if(Slope < 0)\n                                                    Slope = 0;\n                                                if(Slope > 1)\n                                                    Slope = 1;\n                                                if(BlockSlope2[Block[B].Type] < 0)\n                                                    NPC[A].Location.X = Block[B].Location.X + Block[B].Location.Width - ((int_ok)Block[B].Location.Width * Slope);\n                                                else\n                                                    NPC[A].Location.X = Block[B].Location.X + ((int_ok)Block[B].Location.Width * Slope) - NPC[A].Location.Width;\n                                                SlopeTurn = true;\n                                                if(NPC[A].Location.SpeedX < 0)\n                                                    HitSpot = 2;\n                                                else\n                                                    HitSpot = 4;\n                                            }\n                                            else\n                                            {\n                                                NPC[A].Location.Y = Block[B].Location.Y + Block[B].Location.Height - ((int_ok)Block[B].Location.Height * Slope);\n                                                if(NPC[A].Type == NPCID_WALL_BUG || NPC[A].Type == NPCID_WALL_SPARK || NPC[A].Type == NPCID_WALL_TURTLE)\n                                                {\n                                                    NPC[A].Location.Y += 1;\n                                                    tempBlockHit1 = 0;\n                                                    tempBlockHit2 = 0;\n                                                }\n\n                                                if(NPC[A].Location.SpeedY < -0.01_n)\n                                                    NPC[A].Location.SpeedY = -0.01_n + Block[B].Location.SpeedY;\n\n                                                if(is_winged)\n                                                    NPC[A].Location.SpeedY += 2;\n                                            }\n                                        }\n\n                                        // could make compat flag here and refuse to clobber these values for cases other than wall climbers\n                                        if(Special2_from == 'X')\n                                            NPC[A].SpecialX = NPC_A_Special2;\n                                        else if(Special2_from == 'Y')\n                                            NPC[A].SpecialY = NPC_A_Special2;\n                                        else\n                                            NPC[A].Special2 = (vbint_t)NPC_A_Special2;\n\n                                        if(Special_from == 'X')\n                                            NPC[A].SpecialX = NPC_A_Special;\n                                        else if(Special_from == 'Y')\n                                            NPC[A].SpecialY = NPC_A_Special;\n                                        else if(Special_from == '5')\n                                            NPC[A].Special5 = (vbint_t)NPC_A_Special;\n                                        else\n                                            NPC[A].Special = (vbint_t)NPC_A_Special;\n                                    }\n\n\n                                    if(BlockSlope[Block[B].Type] != 0 && HitSpot > 0)\n                                    {\n                                        HitSpot = 0;\n                                        if(NPC[A].Location.Y + NPC[A].Location.Height <= Block[B].Location.Y + Block[B].Location.Height + NPC[A].Location.SpeedY + 4)\n                                        {\n                                            if(NPC[A].Location.X < Block[B].Location.X + Block[B].Location.Width && NPC[A].Location.X + NPC[A].Location.Width > Block[B].Location.X)\n                                            {\n                                                num_t PlrMid;\n                                                if(BlockSlope[Block[B].Type] == 1)\n                                                    PlrMid = NPC[A].Location.X;\n                                                else\n                                                    PlrMid = NPC[A].Location.X + NPC[A].Location.Width;\n\n                                                num_t Slope = (PlrMid - Block[B].Location.X) / (int_ok)Block[B].Location.Width;\n\n                                                if(BlockSlope[Block[B].Type] < 0)\n                                                    Slope = 1 - Slope;\n                                                if(Slope < 0)\n                                                    Slope = 0;\n                                                if(Slope > 100)\n                                                    Slope = 100;\n\n                                                if(tempHitBlock > 0) // VERIFY ME\n                                                {\n                                                    if(!BlockIsSizable[Block[tempHitBlock].Type])\n                                                    {\n                                                        if(Block[tempHitBlock].Location.Y != Block[B].Location.Y)\n                                                        {\n                                                            tempHitBlock = 0;\n                                                            tempHit = 0;\n                                                        }\n                                                    }\n                                                }\n\n                                                if(NPC[A].Location.Y >= Block[B].Location.Y + ((int_ok)Block[B].Location.Height * Slope) - NPC[A].Location.Height - 0.1_n)\n                                                {\n                                                    if(NPC[A].Type == NPCID_EARTHQUAKE_BLOCK && NPC[A].Location.SpeedY > 2)\n                                                        NPCHit(A, 4, A);\n\n                                                    if((NPC[A].Type == NPCID_WALL_BUG || NPC[A].Type == NPCID_WALL_SPARK || NPC[A].Type == NPCID_WALL_TURTLE) && NPC[A].Special == 3)\n                                                    {\n                                                        NPC[A].Special = 1;\n                                                        NPC[A].Special2 = -NPC[A].Special2;\n                                                    }\n\n                                                    if(NPC[A].Type == NPCID_BULLET || NPC[A].Type == NPCID_SPIT_BOSS_BALL) // Bullet bills crash on slopes\n                                                    {\n                                                        NPC[A].Slope = 1;\n                                                        if(NPC[A].Location.SpeedX < 0)\n                                                            HitSpot = 2;\n                                                        else\n                                                            HitSpot = 4;\n                                                    }\n                                                    else\n                                                    {\n                                                        NPC[A].Location.Y = Block[B].Location.Y + ((int_ok)Block[B].Location.Height * Slope) - NPC[A].Location.Height - 0.1_n;\n\n                                                        if(NPC[A]->IsFish)\n                                                            NPC[A].TurnAround = true;\n\n                                                        NPC[A].Slope = B;\n                                                        HitSpot = 1;\n\n                                                        // Fireballs dont go up steep slopes\n                                                        if(Block[B].Location.Height >= Block[B].Location.Width && ((BlockSlope[Block[B].Type] == -1 && NPC[A].Location.SpeedX > 0) || (BlockSlope[Block[B].Type] == 1 && NPC[A].Location.SpeedX < 0)))\n                                                        {\n                                                            if((NPC[A].Type == NPCID_PLR_FIREBALL && NPC[A].Special != 2 && NPC[A].Special != 3) || (NPC[A].Type == NPCID_PLR_ICEBALL && NPC[A].Special == 5))\n                                                            {\n                                                                if(NPC[A].Location.SpeedX < 0)\n                                                                    HitSpot = 2;\n                                                                else\n                                                                    HitSpot = 4;\n                                                            }\n                                                        }\n\n                                                        if(NPC[A]->IsAShell || (NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 1) || NPC[A].Type == NPCID_ICE_CUBE)\n                                                        {\n                                                            if(NPC[A].Location.SpeedY > NPC[A].Location.SpeedX * (int_ok)Block[B].Location.Height / (int_ok)Block[B].Location.Width * BlockSlope[Block[B].Type])\n                                                            {\n                                                                NPC[A].Location.SpeedY = NPC[A].Location.SpeedX * (int_ok)Block[B].Location.Height / (int_ok)Block[B].Location.Width * BlockSlope[Block[B].Type];\n                                                                HitSpot = 0;\n                                                                if(NPC[A].Location.SpeedY > 0)\n                                                                    NPC[A].Location.SpeedY = 0;\n                                                            }\n                                                        }\n                                                    }\n                                                }\n                                            }\n                                        }\n                                    }\n\n                                    if(BlockKills[Block[B].Type] && (HitSpot > 0 || NPC[A].Slope == B))\n                                        NPCHit(A, 6, B);\n\n                                    if(HitSpot == 5)\n                                    {\n                                        if(NPC[A].Slope > 0 && Block[B].Location.Y + Block[B].Location.Height < NPC[A].Location.Y + 4)\n                                        {\n                                            if(Block[B].Location.X + Block[B].Location.Width < NPC[A].Location.X + 4 || Block[B].Location.X > NPC[A].Location.X + NPC[A].Location.Width - 4)\n                                                HitSpot = 0;\n                                        }\n                                    }\n\n                                    if(Block[B].tempBlockNpcType == NPCID_BOSS_CASE || Block[B].tempBlockNpcType == NPCID_BOSS_FRAGILE)\n                                    {\n                                        if(NPC[A].Projectile)\n                                        {\n                                            NPCHit(Block[B].tempBlockNpcIdx, 3, A);\n                                            NPCHit(A, 4, Block[B].tempBlockNpcIdx);\n                                        }\n                                    }\n\n                                    // At this point, the hit is confirmed and the HitSpot will not change.\n\n                                    // Unified type-based logic on confirmed hit -- post slope and lava checks. Probably do allow users to define this for non-floor collisions\n                                    if(NPC[A].Type == NPCID_WALL_SPARK || NPC[A].Type == NPCID_WALL_BUG || NPC[A].Type == NPCID_WALL_TURTLE)\n                                    {\n                                        if(NPC[A].Special == 3 && (HitSpot == 2 || HitSpot == 4))\n                                        {\n                                            if(Block[B].Location.Y + Block[B].Location.Height <= NPC[A].Location.Y + 1)\n                                                HitSpot = 3;\n                                        }\n                                    }\n                                    // beech koopa kicking an ice block\n                                    else if(NPC[A].Type == NPCID_EXT_TURTLE || NPC[A].Type == NPCID_BLU_HIT_TURTLE_S4)\n                                    {\n                                        if(Block[B].tempBlockNpcType == NPCID_SLIDE_BLOCK\n                                            && (g_config.fix_npc55_kick_ice_blocks || NPC[A].Type == NPCID_BLU_HIT_TURTLE_S4)\n                                            && (HitSpot == 2 || HitSpot == 4))\n                                        {\n                                            if(NPC[A].Location.SpeedY == Physics.NPCGravity ||\n                                               NPC[A].Location.SpeedY == 0 || NPC[A].Slope > 0 ||\n                                               (oldSlope > 0 && !NPC[Block[B].tempBlockNpcIdx].Projectile))\n                                            {\n                                                NPC[Block[B].tempBlockNpcIdx].Special = 1;\n                                                NPC[A].Special = 10;\n                                                Player[numPlayers + 1].Direction = NPC[A].Direction;\n                                                NPC[A].Location.X += -NPC[A].Direction;\n                                                NPCHit(Block[B].tempBlockNpcIdx, 1, numPlayers + 1);\n                                                HitSpot = 0;\n                                            }\n                                        }\n                                    }\n                                    // slightly rearranged -- note that these types ignore HitCode 4 and that ICE_CUBE is Killed by HitCode 3, so this is safe\n                                    else if(NPC[A].Type == NPCID_ICE_BLOCK || NPC[A].Type == NPCID_ICE_CUBE)\n                                    {\n                                        if(HitSpot == 2 || HitSpot == 4 || HitSpot == 5)\n                                        {\n                                            if(Block[B].tempBlockNpcType == NPCID_ICE_CUBE)\n                                            {\n                                                NPCHit(Block[B].tempBlockNpcIdx, 3, Block[B].tempBlockNpcIdx);\n                                                NPC[Block[B].tempBlockNpcIdx].Location.SpeedX = -NPC[A].Location.SpeedX;\n                                                NPC[A].Multiplier += 1;\n                                            }\n\n                                            NPCHit(A, 3, A);\n                                        }\n                                        else if(NPC[A].Type == NPCID_ICE_CUBE && (HitSpot == 1 || HitSpot == 3 /* || HitSpot == 5*/))\n                                        {\n                                            if(NPC[A].Location.SpeedX > -Physics.NPCShellSpeed * 0.8_r && NPC[A].Location.SpeedX < Physics.NPCShellSpeed * 0.8_r)\n                                            {\n                                                if(NPC[A].Location.SpeedY > 5 || NPC[A].Location.SpeedY < -2)\n                                                    NPCHit(A, 3, A);\n                                            }\n                                        }\n                                    }\n                                    // quite rearranged. these came after the below pinch logic before.\n                                    // I confirmed that each of their NPCHit calls commute with being hit by NPC 0 with HitCode 3. -- ds-sloth\n                                    else if(NPC[A].Type == NPCID_PLR_ICEBALL)\n                                    {\n                                        if(HitSpot > 1 || (NPC[A].Special == 5 && HitSpot > 0))\n                                            NPCHit(A, 3, A);\n                                    }\n                                    else if(NPC[A].Type == NPCID_ITEM_BUBBLE)\n                                    {\n                                        if(!BlockIsSizable[Block[B].Type])\n                                            NPCHit(A, 3, A);\n                                    }\n                                    else if(NPC[A].Type == NPCID_CHAR3_HEAVY)\n                                    {\n                                        if(HitSpot > 0)\n                                            NPCHit(A, 3, A);\n                                    }\n                                    else if(NPC[A].Type == NPCID_PLR_FIREBALL)\n                                    {\n                                        if(NPC[A].Special == 5 && HitSpot > 0)\n                                            NPCHit(A, 3, A);\n                                    }\n\n                                    // dead code: this procedure isn't called for coins with NPC[A].Special == 0\n                                    // if(NPC[A]->IsACoin && NPC[A].Special == 0 && HitSpot > 0)\n                                    //     NPCHit(A, 3, A);\n\n                                    // NPC pinch logic\n                                    if(Block[B].Location.SpeedX != 0 && (HitSpot == 2 || HitSpot == 4))\n                                        NPC[A].Pinched.Moving = 2;\n\n                                    if(Block[B].Location.SpeedY != 0 && (HitSpot == 1 || HitSpot == 3))\n                                        NPC[A].Pinched.Moving = 2;\n\n                                    if(NPC[A].TimeLeft > 1)\n                                    {\n                                        if(HitSpot == 1)\n                                            NPC[A].Pinched.Bottom1 = 2;\n                                        else if(HitSpot == 2)\n                                            NPC[A].Pinched.Left2 = 2;\n                                        else if(HitSpot == 3)\n                                            NPC[A].Pinched.Top3 = 2;\n                                        else if(HitSpot == 4)\n                                            NPC[A].Pinched.Right4 = 2;\n                                        else if(HitSpot == 5)\n                                        {\n                                            num_t dx = Block[B].Location.minus_center_x(NPC[A].Location);\n                                            num_t dy = Block[B].Location.minus_center_y(NPC[A].Location);\n\n                                            int D = 0;\n\n                                            if(dx > 0)\n                                                D = 2;\n                                            else\n                                            {\n                                                dx = -dx;\n                                                D = 4;\n                                            }\n\n                                            if(dy > 0)\n                                            {\n                                                if(dx < dy)\n                                                    D = 1;\n                                            }\n                                            else\n                                            {\n                                                if(dx < -dy)\n                                                    D = 3;\n                                            }\n\n                                            if(D == 1)\n                                                NPC[A].Pinched.Bottom1 = 2;\n                                            if(D == 2)\n                                                NPC[A].Pinched.Left2 = 2;\n                                            if(D == 3)\n                                                NPC[A].Pinched.Top3 = 2;\n                                            if(D == 4)\n                                                NPC[A].Pinched.Right4 = 2;\n\n                                            if(Block[B].Location.SpeedX != 0 && (D == 2 || D == 4))\n                                                NPC[A].Pinched.Moving = 2;\n                                            if(Block[B].Location.SpeedY != 0 && (D == 1 || D == 3))\n                                                NPC[A].Pinched.Moving = 2;\n\n                                            // If Not (.Location.Y + .Location.Height - .Location.SpeedY <= Block(B).Location.Y - Block(B).Location.SpeedY) Then .Pinched1 = 2\n                                            // If Not (.Location.Y - .Location.SpeedY >= Block(B).Location.Y + Block(B).Location.Height - Block(B).Location.SpeedY) Then .Pinched3 = 2\n                                            // If Not (.Location.X + .Location.Width - .Location.SpeedX <= Block(B).Location.X - Block(B).Location.SpeedX) Then .Pinched2 = 2\n                                            // If Not (.Location.X - .Location.SpeedX >= Block(B).Location.X + Block(B).Location.Width - Block(B).Location.SpeedX) Then .Pinched4 = 2\n                                        }\n\n                                        if(NPC[A].Pinched.Moving > 0)\n                                        {\n                                            if((NPC[A].Pinched.Bottom1 > 0 && NPC[A].Pinched.Top3 > 0) || (NPC[A].Pinched.Left2 > 0 && NPC[A].Pinched.Right4 > 0))\n                                            {\n                                                if(HitSpot > 1)\n                                                    HitSpot = 0;\n\n                                                // for coins, Damage was overloaded to store coinSwitchBlockType. don't clobber it here!\n                                                if(!NPCTraits[NPC[A].DefaultType].IsACoin)\n                                                {\n                                                    NPC[A].Damage += 10000;\n\n                                                    // NEW bounds check\n                                                    // this reduces the risk of a signed integer overflow, and SMBX 1.3 includes no comparisons to values above 20000\n                                                    // SMBX 1.3 uses a float for Damage, which effectively saturates at such high values\n                                                    if(NPC[A].Damage > 16000)\n                                                        NPC[A].Damage = 16000;\n                                                }\n\n                                                NPC[A].Immune = 0;\n                                                NPC[0].Multiplier = 0;\n                                                NPCHit(A, 3, 0);\n                                            }\n                                        }\n                                    }\n\n                                    // safe to leave uninitialized, they're only read if g_config.fix_npc_downward_clip is set\n                                    num_t tempHitOld;\n                                    int tempHitOldBlock;\n\n                                    if(g_config.fix_npc_downward_clip)\n                                    {\n                                        tempHitOld = tempHit;\n                                        tempHitOldBlock = tempHitBlock;\n                                    }\n\n                                    // hitspot 1\n                                    if(HitSpot == 1) // Hitspot 1\n                                    {\n                                        tempSpeedA = (tempf_t)Block[B].Location.SpeedY;\n                                        if(tempSpeedA < 0)\n                                            tempSpeedA = 0;\n\n                                        if(Block[B].tempBlockNpcIdx > 0 && NPC[Block[B].tempBlockNpcIdx].Type != NPCID_CONVEYOR\n                                            && NPC[Block[B].tempBlockNpcIdx].Type != NPCID_YEL_PLATFORM && NPC[Block[B].tempBlockNpcIdx].Type != NPCID_BLU_PLATFORM && NPC[Block[B].tempBlockNpcIdx].Type != NPCID_GRN_PLATFORM && NPC[Block[B].tempBlockNpcIdx].Type != NPCID_RED_PLATFORM)\n                                        {\n                                            if(NPC[Block[B].tempBlockNpcIdx].TimeLeft < NPC[A].TimeLeft - 1)\n                                                NPC[Block[B].tempBlockNpcIdx].TimeLeft = NPC[A].TimeLeft - 1;\n                                            else if(NPC[Block[B].tempBlockNpcIdx].TimeLeft - 1 > NPC[A].TimeLeft)\n                                                NPC[A].TimeLeft = NPC[Block[B].tempBlockNpcIdx].TimeLeft - 1;\n                                        }\n\n                                        // type-based logic for floor collisions\n                                        if(NPC[A].Type == NPCID_STACKER)\n                                        {\n                                            // If a Pokey head stands on a top of another Pokey segment\n                                            // FIXME: need a compat guard for second condition\n                                            if(Block[B].tempBlockNpcType == NPCID_STACKER\n                                                && NPC[Block[B].tempBlockNpcIdx].Type == NPCID_STACKER) // Make sure Pokey didn't transformed into ice cube or anything also\n                                            {\n                                                NPC[Block[B].tempBlockNpcIdx].Special = -3;\n                                                NPC[A].Special2 = 0;\n                                            }\n                                        }\n                                        // item pod hatching (moved from above)\n                                        else if(NPC[A].Type == NPCID_ITEM_POD /* && HitSpot == 1 */)\n                                        {\n                                            if(NPC[A].Location.SpeedY > 2) /* && HitSpot == 1) || (NPC[A].Location.SpeedY < -2 && HitSpot == 3) || (NPC[A].Location.SpeedX > 2 && HitSpot == 4) || (NPC[A].Location.SpeedX < -2 && HitSpot == 2)) */\n                                                NPC[A].Special2 = 1;\n                                        }\n                                        else if(NPC[A].Type == NPCID_SPIKY_BALL_S4 || NPC[A].Type == NPCID_GOALTAPE || NPC[A].Type == NPCID_FLAG_EXIT)\n                                            NPC[A].Special = 1;\n                                        else if(NPC[A].Type == NPCID_SQUID_S3 || NPC[A].Type == NPCID_SQUID_S1)\n                                            NPC[A].Special4 = 1;\n                                        else if(NPC[A].Type == NPCID_STONE_S3 || NPC[A].Type == NPCID_STONE_S4)\n                                            NPC[A].Special = 2;\n                                        else if(NPC[A].Type == NPCID_DOOR_MAKER)\n                                        {\n                                            NPC[A].Special3 = 1;\n                                            NPC[A].Projectile = false;\n                                        }\n                                        else if(NPC[A].Type == NPCID_EARTHQUAKE_BLOCK)\n                                        {\n                                            if(NPC[A].Location.SpeedY > 2)\n                                                NPCHit(A, 4, A);\n                                        }\n                                        else if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL)\n                                        {\n                                            if(NPC[A].Location.SpeedX == 0)\n                                                NPCHit(A, 4, A);\n                                        }\n                                        else if(NPC[A].Type == NPCID_SLIDE_BLOCK)\n                                        {\n                                            if(NPC[A].Special == 1 && NPC[A].Location.SpeedX == 0 && NPC[A].Location.SpeedY > 7.95_n)\n                                                NPCHit(A, 4, A);\n                                        }\n                                        else if(NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT || NPC[A].Type == NPCID_VPIPE_LONG)\n                                        {\n                                            if(NPC[A].Location.SpeedY > Physics.NPCGravity * 20)\n                                                PlaySoundSpatial(SFX_Stone, NPC[A].Location);\n                                        }\n                                        else if(NPC[A].Type == NPCID_TANK_TREADS)\n                                        {\n                                            if(NPC[A].Location.SpeedY > Physics.NPCGravity * 10)\n                                                PlaySoundSpatial(SFX_Stone, NPC[A].Location);\n                                        }\n\n                                        if(WalkingCollision3(NPC[A].Location, Block[B].Location, oldBeltSpeed) || NPC[A].Location.Width > 32)\n                                        {\n                                            resetBeltSpeed = true;\n\n                                            if(Block[B].tempBlockNpcType != 0)\n                                            {\n                                                if(Block[B].Location.SpeedY > 0 && Block[B].tempBlockNpcType >= NPCID_YEL_PLATFORM && Block[B].tempBlockNpcType <= NPCID_RED_PLATFORM)\n                                                    tempHit = Block[B].Location.Y - NPC[A].Location.Height - 0.01_n + Block[B].Location.SpeedY;\n                                                else\n                                                    tempHit = Block[B].Location.Y - NPC[A].Location.Height - 0.01_n;\n\n                                                tempHitBlock = B;\n                                            }\n                                            else\n                                            {\n                                                tempHitBlock = B;\n                                                tempHit = Block[B].Location.Y - NPC[A].Location.Height - 0.01_n;\n                                            }\n\n                                            if(Block[B].tempBlockNpcType >= NPCID_YEL_PLATFORM && Block[B].tempBlockNpcType <= NPCID_RED_PLATFORM)\n                                            {\n                                                beltSpeed = 0;\n                                                beltCount = 0;\n                                            }\n\n                                            num_t C = 0;\n\n                                            if(NPC[A].Location.X > Block[B].Location.X)\n                                                C = NPC[A].Location.X - 0.01_n;\n                                            else\n                                                C = Block[B].Location.X - 0.01_n;\n\n                                            if(NPC[A].Location.X + NPC[A].Location.Width < Block[B].Location.X + Block[B].Location.Width)\n                                                C = (NPC[A].Location.X + NPC[A].Location.Width - C + 0.01_n);\n                                            else\n                                                C = (Block[B].Location.X + Block[B].Location.Width - C + 0.01_n);\n\n                                            if(Block[B].tempBlockVehiclePlr == 0)\n                                            {\n                                                if(Block[B].tempBlockNpcType > 0)\n                                                    beltSpeed = (tempf_t)((num_t)beltSpeed + Block[B].Location.SpeedX.times(C).times((num_t)blk_npc_tr.Speedvar));\n                                                else\n                                                    beltSpeed = (tempf_t)((num_t)beltSpeed + Block[B].Location.SpeedX.times(C));\n\n                                                beltCount += tempf_t(C);\n                                            }\n                                        }\n\n                                        if(tempHitBlock == B)\n                                        {\n                                            if(NPC[A].Type == NPCID_SPIKY_BALL_S3)\n                                            {\n                                                if(NPC[A].Location.SpeedY > 2)\n                                                {\n                                                    NPC[A].Location.SpeedY = -NPC[A].Location.SpeedY * 0.7_r + Block[B].Location.SpeedY;\n\n                                                    if(NPC[A].Slope == 0)\n                                                        NPC[A].Location.Y = Block[B].Location.Y - NPC[A].Location.Height - 0.01_n;\n\n                                                    tempHit = 0;\n                                                    tempHitBlock = 0;\n                                                }\n                                            }\n\n                                            if(((NPC[A]->StandsOnPlayer && !NPC[A].Projectile) || (NPC[A]->IsAShell && NPC[A].Location.SpeedX == 0)) && Block[B].tempBlockVehiclePlr > 0)\n                                            {\n                                                // IMPORTANT: this case was truncation until v1.3.7.1-dev. Confirm that changing to VB6 rounding does not cause any issues.\n                                                NPC[A].vehicleYOffset = Block[B].tempBlockVehicleYOffset + num_t::vb6round(NPC[A].Location.Height);\n                                                NPC[A].vehiclePlr = Block[B].tempBlockVehiclePlr;\n                                                if(NPC[A].vehiclePlr == 0 && Block[B].tempBlockNpcType == NPCID_VEHICLE)\n                                                    NPC[A].TimeLeft = 100;\n                                            }\n\n                                            if(NPC[A].Projectile)\n                                            {\n                                                if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL)\n                                                {\n                                                    if(NPC[A].Type == NPCID_PLR_ICEBALL)\n                                                        NPC[A].Location.SpeedY = -7 + Block[B].Location.SpeedY;\n                                                    else if(NPC[A].Special == 4)\n                                                        NPC[A].Location.SpeedY = -3 + Block[B].Location.SpeedY;\n                                                    else\n                                                        NPC[A].Location.SpeedY = -5 + Block[B].Location.SpeedY;\n\n                                                    if(NPC[A].Slope == 0)\n                                                        NPC[A].Location.Y = Block[B].Location.Y - NPC[A].Location.Height - 0.01_n;\n\n                                                    tempHit = 0;\n                                                    tempHitOld = 0;\n\n                                                    if(NPC[A].Type == NPCID_PLR_ICEBALL)\n                                                    {\n                                                        if(!Block[B].Slippy)\n                                                            NPC[A].Special5 += 1;\n\n                                                        if(NPC[A].Special5 >= 3)\n                                                            NPCHit(A, 3, A);\n                                                    }\n                                                }\n                                                else if(NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4)\n                                                {\n                                                    // Yes, you aren't mistook, it's just a blank block, hehehe (made by Redigit originally)\n\n\n                                                }\n                                                else if(NPC[A].Bouce || NPC[A].Location.SpeedY > 5.8_n || ((NPC[A].Type == NPCID_CANNONITEM || NPC[A].Type == NPCID_TOOTHYPIPE) && (NPC[A].Location.SpeedY > 2 || (NPC[A].Location.SpeedX > 1 || NPC[A].Location.SpeedX < -1))))\n                                                {\n                                                    NPC[A].Bouce = false;\n                                                    if(NPC[A]->IsAShell || (NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 1) || NPC[A].Type == NPCID_ICE_CUBE)\n                                                    {\n                                                        if(NPC[A].Slope == 0)\n                                                            NPC[A].Location.SpeedY = -NPC[A].Location.SpeedY / 2;\n                                                        for(int C = 1; C <= numPlayers; C++)\n                                                        {\n                                                            if(Player[C].StandingOnNPC == A)\n                                                            {\n                                                                NPC[A].Location.SpeedY = 0;\n                                                                break;\n                                                            }\n                                                        }\n\n                                                    }\n                                                    else if(NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT || NPC[A].Type == NPCID_VPIPE_LONG || (NPC[A].Type >= NPCID_TANK_TREADS && NPC[A].Type <= NPCID_SLANT_WOOD_M))\n                                                        NPC[A].Location.SpeedY = 0;\n                                                    else if(NPC[A].Type == NPCID_VILLAIN_S3 || NPC[A].Type == NPCID_ITEM_POD)\n                                                    {\n                                                        NPC[A].Projectile = false;\n                                                        NPC[A].Location.SpeedY = 0;\n                                                    }\n                                                    else\n                                                        NPC[A].Location.SpeedY = -NPC[A].Location.SpeedY * 0.6_r;\n                                                    if(NPC[A].Slope == 0)\n                                                        NPC[A].Location.Y = Block[B].Location.Y - NPC[A].Location.Height - 0.01_n;\n                                                    tempHit = 0;\n                                                    tempHitOld = 0;\n                                                }\n                                                else if(NPC[A].Type != NPCID_TANK_TREADS && NPC[A].Type != NPCID_BULLET /* && NPC[A].Type != NPCID_PLR_FIREBALL*/) // FIREBALL checked above\n                                                {\n                                                    if(NPC[A]->MovesPlayer)\n                                                    {\n                                                        if(NPC[A].Location.SpeedX == 0)\n                                                        {\n                                                            bool tempBool = false;\n                                                            for(int C = 1; C <= numPlayers; C++)\n                                                            {\n                                                                if(CheckCollision(NPC[A].Location, Player[C].Location))\n                                                                {\n                                                                    tempBool = true;\n                                                                    break;\n                                                                }\n                                                            }\n\n                                                            if(!tempBool)\n                                                                NPC[A].Projectile = false;\n                                                        }\n                                                    }\n                                                    else if(NPC[A].Type == NPCID_CHASER)\n                                                    {\n                                                        if(NPC[A].Location.SpeedX > -0.1_n && NPC[A].Location.SpeedX < 0.1_n)\n                                                            NPC[A].Projectile = false;\n                                                    }\n                                                    else if(!(NPC[A]->IsAShell || (NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 1) || NPC[A].Type == NPCID_SPIKY_BALL_S3))\n                                                        NPC[A].Projectile = false;\n                                                    else if(NPC[A].Location.SpeedX == 0)\n                                                        NPC[A].Projectile = false;\n                                                }\n                                            }\n                                        }\n                                    }\n                                    else if(HitSpot == 2 || HitSpot == 4) // Horizontal collisions\n                                    {\n                                        if(HitSpot == 2 && BlockSlope[Block[oldSlope].Type] == 1 && Block[oldSlope].Location.Y == Block[B].Location.Y)\n                                        {\n                                        }\n                                        else\n                                        {\n                                            beltClear = true;\n                                            if(NPC[A].Type == NPCID_VILLAIN_S3 && !NPC[A].Wings)\n                                                NPC[A].Location.SpeedX = 0;\n\n                                            resetBeltSpeed = true;\n                                            if(NPC[A].Type == NPCID_PLR_FIREBALL && NPC[A].Special == 3)\n                                            {\n                                                if(NPC[A].Special2 == 0)\n                                                {\n                                                    NPC[A].Special2 = 1;\n                                                    NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                                                }\n                                                else\n                                                    NPCHit(A, 4, A);\n                                            }\n                                            else if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_SPIT_BOSS_BALL)\n                                                NPCHit(A, 4, A);\n                                            if(NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 1)\n                                                NPCHit(A, 4, A);\n\n                                            addBelt = (tempf_t)NPC[A].Location.X;\n\n                                            if(NPC[A].Slope == 0 && !SlopeTurn)\n                                            {\n                                                if(HitSpot == 2)\n                                                    NPC[A].Location.X = Block[B].Location.X + Block[B].Location.Width + 0.01_n;\n                                                else\n                                                    NPC[A].Location.X = Block[B].Location.X - NPC[A].Location.Width - 0.01_n;\n                                            }\n\n                                            addBelt = (tempf_t)(NPC[A].Location.X - (num_t)addBelt);\n\n                                            // the only read-site in the entire SMBX 1.3 codebase checks for these types and ignores them\n                                            // if(!(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_TANK_TREADS || NPC[A].Type == NPCID_BULLET))\n                                            NPC[A].TurnAround = true;\n\n                                            if(is_winged)\n                                                NPC[A].Location.SpeedX += -Block[B].Location.SpeedX * 1.2_r;\n                                            else if(NPC[A]->IsAShell && !NPC[A].Wings)\n                                                NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                                        }\n                                    }\n                                    else if(HitSpot == 3) // Hitspot 3\n                                    {\n                                        // ignore the hit if it is an NPC that a Winged Block NPC will be supporting\n                                        if(NPC[A].Wings && NPC[A].tempBlock && Block[B].tempBlockNpcIdx && !blk_npc_tr.NoGravity)\n                                            continue;\n\n                                        if(NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 1)\n                                            NPCHit(A, 4, A);\n\n                                        if(NPC[A].Type == NPCID_MINIBOSS)\n                                            NPC[A].Special3 = 0;\n\n                                        if(tempBlockHit1 == 0)\n                                            tempBlockHit1 = B;\n                                        else\n                                            tempBlockHit2 = B;\n\n                                        if(is_winged)\n                                        {\n                                            NPC[A].Location.SpeedY = 2 + Block[B].Location.SpeedY;\n                                            NPC[A].Location.Y = Block[B].Location.Y + Block[B].Location.Height + 0.1_n;\n                                        }\n                                    }\n                                    else if(HitSpot == 5) // Hitspot 5\n                                    {\n                                        if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_SPIT_BOSS_BALL)\n                                            NPCHit(A, 4, A);\n\n                                        beltClear = true;\n\n                                        if(NPC[A].Type == NPCID_VILLAIN_S3)\n                                            NPC[A].Location.SpeedX = 0;\n\n                                        onWall = true;\n\n                                        // item was thrown into a wall this frame (or is a fish that just re-entered water)\n                                        if(NPC[A].WallDeath >= 5 && !NPC[A]->IsABonus && NPC[A].Type != NPCID_FLY_BLOCK &&\n                                           NPC[A].Type != NPCID_FLY_CANNON && NPC[A].Type != NPCID_RED_BOOT && NPC[A].Type != NPCID_CANNONENEMY && NPC[A].Type != NPCID_CANNONITEM &&\n                                           NPC[A].Type != NPCID_SPRING && NPC[A].Type != NPCID_HEAVY_THROWER && NPC[A].Type != NPCID_KEY && NPC[A].Type != NPCID_COIN_SWITCH &&\n                                           NPC[A].Type != NPCID_GRN_BOOT &&\n                                           // Duplicated segment [PVS Studio]\n                                           // NPC[A].Type != NPCID_RED_BOOT &&\n                                           NPC[A].Type != NPCID_BLU_BOOT && NPC[A].Type != NPCID_TOOTHYPIPE &&\n                                           NPC[A].Type != NPCID_BOMB && NPC[A].Type != NPCID_CARRY_BUDDY && NPC[A].Type != NPCID_FLIPPED_RAINBOW_SHELL && NPC[A].Type != NPCID_EARTHQUAKE_BLOCK &&\n                                           !((NPC[A].Type >= NPCID_CARRY_BLOCK_A && NPC[A].Type <= NPCID_CARRY_BLOCK_D))) // walldeath stuff\n                                        {\n                                            NPC[A].Location.SpeedX = Physics.NPCShellSpeed / 2 * NPC[A].Direction;\n                                            if(NPCIsVeggie(NPC[A]))\n                                                NPC[A].Projectile = true;\n                                            else if(NPC[A].Type == NPCID_WALK_BOMB_S2)\n                                                NPCHit(A, 4, A);\n                                            else if(NPC[A].Type == NPCID_CHAR3_HEAVY)\n                                                NPCHit(A, 3, A);\n                                            else\n                                            {\n                                                NewEffect(EFFID_WHACK, NPC[A].Location);\n                                                NPC[A].Killed = 3;\n                                                NPCQueues::Killed.push_back(A);\n                                            }\n                                        }\n                                        else if(NPC[A].Type != NPCID_SPIKY_BALL_S3 && !(NPC[A]->IsABlock && Block[B].tempBlockNpcType > 0) && block_is_not_conveyor)\n                                        {\n                                            addBelt = (tempf_t)NPC[A].Location.X;\n\n                                            if(NPC[A].Location.X + NPC[A].Location.Width / 2 < Block[B].Location.X + Block[B].Location.Width / 2)\n                                                NPC[A].Location.X = Block[B].Location.X - NPC[A].Location.Width - 0.01_n;\n                                            else\n                                                NPC[A].Location.X = Block[B].Location.X + Block[B].Location.Width + 0.01_n;\n\n                                            addBelt = (tempf_t)(NPC[A].Location.X - (num_t)addBelt);\n\n                                            if(NPC[A].Type == NPCID_MINIBOSS)\n                                            {\n                                                NPC[A].Location.SpeedY = 0;\n                                                NPC[A].Location.Y = Block[B].Location.Y - NPC[A].Location.Height - 0.01_n;\n                                            }\n\n                                            // the only read-site in the entire SMBX 1.3 codebase checks for fireball and tank_treads and ignores them,\n                                            //   and kills iceball (which must have already been killed if this code was reached)\n                                            // if(NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_TANK_TREADS && NPC[A].Type != NPCID_PLR_ICEBALL)\n                                            NPC[A].TurnAround = true;\n\n                                            if(NPC[A]->IsAShell)\n                                            {\n                                                if(NPC[A].Location.X < Block[B].Location.X && NPC[A].Location.SpeedX > 0)\n                                                    NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                                                else if(NPC[A].Location.X + NPC[A].Location.Width > Block[B].Location.X + Block[B].Location.Width && NPC[A].Location.SpeedX < 0)\n                                                    NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                                            }\n                                        }\n                                    }\n\n                                    // Find best block here\n                                    if(g_config.fix_npc_downward_clip && (tempHitBlock != tempHitOldBlock))\n                                    {\n                                        CompareNpcWalkBlock(tempHitBlock, tempHitOldBlock,\n                                                            tempHit, tempHitOld,\n                                                            tempHitIsSlope, &NPC[A]);\n                                    }\n\n                                    if(NPC[A].Projectile && NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_PLR_ICEBALL && NPC[A].Type != NPCID_METALBARREL && !(NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT || NPC[A].Type == NPCID_VPIPE_LONG)) // Hit the block if the NPC is a projectile\n                                    {\n                                        if(HitSpot == 2 || HitSpot == 4 || HitSpot == 5)\n                                        {\n                                            BlockHit(B);\n                                            PlaySoundSpatial(SFX_BlockHit, NPC[A].Location);\n                                            if(NPC[A].Type == NPCID_BULLET) // Bullet Bills\n                                            {\n                                                NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                                                NPCHit(A, 4, A);\n                                                BlockHitHard(B);\n                                                break;\n                                            }\n\n                                            if(NPC[A]->IsAShell || (NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 1) || NPC[A].Type == NPCID_ICE_CUBE)\n                                            {\n                                                BlockHitHard(B);\n                                                if(Block[B].Type == 4 || Block[B].Type == 188 || Block[B].Type == 60 || Block[B].Type == 90)\n                                                    NPC[A].TimeLeft = Physics.NPCTimeOffScreen;\n                                            }\n                                        }\n                                    }\n                                }\n                                // End If\n                            }\n                        }\n                    }\n                }\n                else\n                {\n                    if((bCheck == 2 || BlocksSorted) && PSwitchTime == 0)\n                        break;\n                }\n            }\n\n            if(numTempBlock == 0)\n                break;\n        }\n    } // do block collisions\n\n    // ceiling logic (safe to move into above braces if desired; it's impossible for tempBlockHit to be set unless that clause executed)\n    if(tempBlockHit1 > 0) // find out which block was hit from below\n    {\n        int winningBlock = 0;\n\n        if(tempBlockHit2 == 0)\n            winningBlock = tempBlockHit1;\n        else\n        {\n            num_t C = num_t::abs(Block[tempBlockHit1].Location.minus_center_x(NPC[A].Location));\n            num_t D = num_t::abs(Block[tempBlockHit2].Location.minus_center_x(NPC[A].Location));\n\n            if(C < D)\n                winningBlock = tempBlockHit1;\n            else\n                winningBlock = tempBlockHit2;\n        }\n\n        if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL) // Kill the fireball\n            NPCHit(A, 4);\n        else if(NPC[A].Projectile || Block[winningBlock].Invis) // Hit the block hard if the NPC is a projectile\n        {\n            if(!(NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT || NPC[A].Type == NPCID_VPIPE_LONG || NPC[A].Type == NPCID_CANNONENEMY))\n            {\n                if(NPC[A].Location.SpeedY < -0.05_n)\n                {\n                    BlockHit(winningBlock);\n                    PlaySoundSpatial(SFX_BlockHit, NPC[A].Location);\n                    if(NPC[A]->IsAShell || NPC[A].Type == NPCID_ICE_CUBE)\n                        BlockHitHard(winningBlock);\n                }\n                else\n                    NPC[A].Projectile = false;\n            }\n        }\n\n        if(!is_winged)\n        {\n            NPC[A].Location.Y = Block[winningBlock].Location.Y + Block[winningBlock].Location.Height + 0.01_n;\n\n            if(g_config.fix_npc_ceiling_speed)\n                NPC[A].Location.SpeedY = 0.01_n + Block[winningBlock].Location.SpeedY;\n            else\n            {\n                // This is the original extremely buggy line, using an arbitrary B from the end of the old fBlock/lBlock or tempBlock query.\n                // NPC[A].Location.SpeedY = 0.01 + Block[B].Location.SpeedY;\n\n                // Unfortunately, we need to emulate it. We're lucky that NPCs don't frequently hit ceilings.\n\n                // our old... friends?\n                int fBlock, lBlock;\n\n                // if no temp blocks, then no second pass occurred in the check loops above where B was set\n                if(numTempBlock != 0)\n                {\n                    fBlock = numBlock + 1 - numTempBlock;\n                    lBlock = numBlock;\n                }\n                else\n                {\n                    fBlock = 1;\n                    lBlock = numBlock - numTempBlock;\n                }\n\n                int B = -1;\n\n                // The first line contains the original condition that guarded the \"break\" in our loop above B.\n                // Note that this means the bug would be different during PSwitch or after a horiz layer has moved.\n                // The second line lets us find the Block that is accessed upon overflow of the original FLBlock column.\n                if((PSwitchTime == 0 && (BlocksSorted || numTempBlock != 0))\n                    || (BlocksSorted && numTempBlock == 0))\n                {\n                    // We could use a quadtree here, but this is a rare case. Just do it.\n\n                    // Normally, need to find the first Block (in sorted order) which is to the right of the NPC.\n                    num_t right_border = NPC[A].Location.X + NPC[A].Location.Width;\n\n                    int first_after_block = -1;\n                    num_t first_after_x = 0, first_after_y = 0;\n\n                    // IF PSwitchTime != 0 but the blocks are sorted,\n                    // then we would have iterated over the NPC's entire FLBlock column without breaking,\n                    // and ended with B set to the first block after it.\n                    // To emulate, move the right_border to the end of its FLBlock column.\n                    // vb6 rounds its array indexes from doubles to integers\n                    if(PSwitchTime != 0)\n                        right_border = num_t::vb6round(right_border / 32 + 1) * 32;\n\n                    // If the loop never invoked break and was not over a single column,\n                    // then the game would have accessed numBlock + 1 here, but we won't.\n                    // We'll assume it was properly deallocated and has SpeedY = 0.\n\n                    for(int block = fBlock; block <= lBlock; block++)\n                    {\n                        // Old code checked coordinates first, then properties without affecting the loop.\n                        // This means we don't need to touch any properties here.\n\n                        const Block_t& b = Block[block];\n\n                        num_t bx = b.Location.X;\n                        num_t by = b.Location.Y;\n\n                        // sort as they were in the original array\n                        if(b.Layer != LAYER_NONE)\n                        {\n                            bx -= Layer[b.Layer].OffsetX;\n                            by -= Layer[b.Layer].OffsetY;\n                        }\n\n                        if(b.Location.X > right_border)\n                        {\n                            if(first_after_block == -1 || bx < first_after_x || (bx == first_after_x && by < first_after_y))\n                            {\n                                first_after_block = block;\n                                first_after_x = bx;\n                                first_after_y = by;\n\n                                // want the first one in the SMBX sorted order, which might not be accurate\n                                if(g_config.emulate_classic_block_order && numTempBlock == 0)\n                                    break;\n                            }\n                        }\n                    }\n\n                    B = first_after_block;\n                }\n                else\n                {\n                    // The game went through the full loop and B = numBlock + 1 in vanilla.\n                    // We'll assume it was properly deallocated and has SpeedY = 0.\n                }\n\n                if(B != -1)\n                {\n                    NPC[A].Location.SpeedY = 0.01_n + Block[B].Location.SpeedY;\n                    // pLogDebug(\"NPC %d hits ceiling, set SpeedY using block %d with speed %f\\n\", A, B, Block[B].Location.SpeedY);\n                }\n                else\n                {\n                    NPC[A].Location.SpeedY = 0.01_n;\n                    // pLogDebug(\"NPC %d hits ceiling, set SpeedY using OOB block with speed 0\\n\", A);\n                }\n            }\n        }\n    }\n\n    // beltspeed code\n    if(!resetBeltSpeed)\n    {\n        if(NPC[A].Type == NPCID_VILLAIN_S3 && NPC[A].Special == 1)\n            NPC[A].Special = 0;\n\n        if(oldBeltSpeed >= 1 || oldBeltSpeed <= -1)\n        {\n            beltSpeed = oldBeltSpeed - (tempf_t)NPC[A].oldAddBelt;\n            beltCount = 1;\n            if(beltSpeed >= (tempf_t)2.1_n)\n                beltSpeed -= (tempf_t)0.1_n;\n            else if(beltSpeed <= -(tempf_t)2.1_n)\n                beltSpeed += (tempf_t)0.1_n;\n        }\n    }\n\n    if(beltSpeed != 0)\n    {\n        Location_t preBeltLoc = NPC[A].Location;\n        beltSpeed = beltSpeed.divided_by(beltCount).times(speedVar);\n        NPC[A].Location.X += num_t(beltSpeed);\n//                            D = NPC[A].BeltSpeed; // Idk why this is needed as this value gets been overriden and never re-used\n        Location_t tempLocation = NPC[A].Location;\n        tempLocation.Y += 1;\n        tempLocation.Height -= 2;\n        tempLocation.Width = tempLocation.Width / 2;\n\n        if(beltSpeed > 0)\n            tempLocation.X += tempLocation.Width;\n\n        if(!(NPC[A].Type >= NPCID_SHORT_WOOD && NPC[A].Type <= NPCID_SLANT_WOOD_M) && !NPC[A].Inert)\n        {\n            for(int C : treeNPCQuery(tempLocation, SORTMODE_NONE))\n            {\n                if(A != C && NPC[C].Active && !NPC[C].Projectile)\n                {\n                    if(NPC[C].Killed == 0 && NPC[C].vehiclePlr == 0 && NPC[C].HoldingPlayer == 0 &&\n                       !NPC[C]->NoClipping && NPC[C].Effect == NPCEFF_NORMAL && !NPC[C].Inert) // And Not NPCIsABlock(NPC(C).Type) Then\n                    {\n                        Location_t tempLocation2 = preBeltLoc;\n                        tempLocation2.Width -= 4;\n                        tempLocation2.X += 2;\n                        if(CheckCollision(tempLocation, NPC[C].Location))\n                        {\n                            if(!CheckCollision(tempLocation2, NPC[C].Location))\n                            {\n                                if(NPC[A].TimeLeft - 1 > NPC[C].TimeLeft)\n                                    NPC[C].TimeLeft = NPC[A].TimeLeft - 1;\n                                else if(NPC[A].TimeLeft < NPC[C].TimeLeft - 1)\n                                    NPC[A].TimeLeft = NPC[C].TimeLeft - 1;\n                                onWall = true;\n                                if((NPC[A].Location.SpeedX > 0 && NPC[C].Location.X > NPC[A].Location.X) || (NPC[A].Location.SpeedX < 0 && NPC[C].Location.X < NPC[A].Location.X))\n                                    NPC[A].TurnAround = true;\n                                if((NPC[C].Location.SpeedX > 0 && NPC[A].Location.X > NPC[C].Location.X) || (NPC[C].Location.SpeedX < 0 && NPC[A].Location.X < NPC[C].Location.X))\n                                    NPC[C].TurnAround = true;\n                                NPC[A].Location = preBeltLoc;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        if(NPC[A].Location.X == preBeltLoc.X)\n        {\n            beltSpeed = 0;\n            addBelt = 0;\n            if(NPC[A].tempBlock > 0)\n                Block[NPC[A].tempBlock].Location.SpeedX = 0;\n        }\n    }\n\n    if(!onWall)\n        beltSpeed += addBelt;\n\n    NPC[A].oldAddBelt = (numf_t)addBelt;\n\n    if(beltClear || NPC[A].Type == NPCID_STONE_S3 || NPC[A].Type == NPCID_STONE_S4)\n        NPC[A].BeltSpeed = 0;\n    else\n        NPC[A].BeltSpeed = (numf_t)beltSpeed;\n}\n"
  },
  {
    "path": "src/npc/npc_update/npc_collide.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"npc.h\"\n#include \"npc_traits.h\"\n#include \"npc/npc_queues.h\"\n#include \"config.h\"\n#include \"collision.h\"\n#include \"layers.h\"\n#include \"effect.h\"\n#include \"eff_id.h\"\n#include \"editor.h\"\n\n#include \"main/trees.h\"\n\nvoid NPCCollide(int A)\n{\n    // first exclusion condition\n    // if(!(!NPC[A].Inert && NPC[A].Type != NPCID_LIFT_SAND && NPC[A].Type != NPCID_CANNONITEM && NPC[A].Type != NPCID_SPRING &&\n    //         !(NPC[A].Type == NPCID_HEAVY_THROWN && !NPC[A].Projectile) && NPC[A].Type != NPCID_COIN_SWITCH && NPC[A].Type != NPCID_GRN_BOOT &&\n    //         !(NPC[A].Type == NPCID_SPIT_BOSS_BALL && !NPC[A].Projectile) &&\n    //         !((NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_YEL_PLATFORM || NPC[A].Type == NPCID_BLU_PLATFORM || NPC[A].Type == NPCID_GRN_PLATFORM ||\n    //    NPC[A].Type == NPCID_RED_PLATFORM || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT ||\n    //    NPC[A].Type == NPCID_VPIPE_LONG || NPC[A].Type == NPCID_CANNONENEMY) && !NPC[A].Projectile) &&\n    //         !(NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 0) && !(NPC[A].Type == NPCID_SPIKY_BALL_S3 && !NPC[A].Projectile) &&\n    //    NPC[A].Type != NPCID_TOOTHYPIPE && NPC[A].Type != NPCID_FALL_BLOCK_RED && NPC[A].Type != NPCID_VEHICLE && NPC[A].Type != NPCID_CONVEYOR &&\n    //         !NPCIsYoshi(NPC[A]) && !(NPC[A].Type >= NPCID_TANK_TREADS && NPC[A].Type <= NPCID_SLANT_WOOD_M) &&\n    //         !(NPC[A].Type == NPCID_ITEM_POD && !NPC[A].Projectile) && !(NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 &&\n    //    NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4 && NPC[A].Projectile && NPC[A].CantHurt > 0) &&\n    //         !(NPC[A]->IsAShell && !NPC[A].Projectile) &&\n    //         !(NPC[A].Projectile && NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4) &&\n    //    NPC[A].Type != NPCID_SPIT_GUY_BALL && !(!NPCIsToad(NPC[A]) && !NPC[A].Projectile &&\n    //    NPC[A].Location.SpeedX == 0 && (NPC[A].Location.SpeedY == 0 || NPC[A].Location.SpeedY == Physics.NPCGravity))))\n    // {\n    //     return;\n    // }\n\n    // second exclusion condition\n    // if(!(!NPC[A]->IsACoin && NPC[A].Type != NPCID_TIMER_S2 && NPC[A].Type != NPCID_FALL_BLOCK_BROWN &&\n    //     NPC[A].Type != NPCID_WALL_BUG && NPC[A].Type != NPCID_WALL_SPARK && NPC[A].Type != NPCID_WALL_TURTLE && NPC[A].Type != NPCID_RED_BOOT &&\n    //     NPC[A].Type != NPCID_BLU_BOOT && !(NPC[A]->IsFish && NPC[A].Special == 2) &&\n    //    !NPC[A].Generator && NPC[A].Type != NPCID_PLANT_FIREBALL && NPC[A].Type != NPCID_FIRE_CHAIN &&\n    //     NPC[A].Type != NPCID_QUAD_BALL && NPC[A].Type != NPCID_FLY_BLOCK && NPC[A].Type != NPCID_FLY_CANNON &&\n    //     NPC[A].Type != NPCID_FIRE_BOSS_FIRE && NPC[A].Type != NPCID_DOOR_MAKER && NPC[A].Type != NPCID_MAGIC_DOOR))\n    // {\n    //     return;\n    // }\n\n    // These NPC types prevent all physics updates and make it so that NPCCollide can never be called. They are removed now.\n    // NPC[A].Type == NPCID_LIFT_SAND || NPC[A].Type == NPCID_PLANT_FIREBALL || NPC[A].Type == NPCID_FIRE_CHAIN\n\n    // NPC properties that block collision query\n    if(NPC[A].Inert || NPC[A].Generator)\n        return;\n\n    // NPC traits that prevent collision query\n    if(NPC[A]->IsACoin || NPCIsYoshi(NPC[A]))\n        return;\n\n    // NPC types that prevent collision query\n    if(NPC[A].Type == NPCID_CANNONITEM || NPC[A].Type == NPCID_SPRING || NPC[A].Type == NPCID_COIN_SWITCH || NPC[A].Type == NPCID_GRN_BOOT\n        || NPC[A].Type == NPCID_TOOTHYPIPE || NPC[A].Type == NPCID_FALL_BLOCK_RED || NPC[A].Type == NPCID_VEHICLE || NPC[A].Type == NPCID_CONVEYOR\n        || (NPC[A].Type >= NPCID_TANK_TREADS && NPC[A].Type <= NPCID_SLANT_WOOD_M) || NPC[A].Type == NPCID_SPIT_GUY_BALL\n        || NPC[A].Type == NPCID_TIMER_S2 || NPC[A].Type == NPCID_FALL_BLOCK_BROWN\n        || NPC[A].Type == NPCID_WALL_BUG || NPC[A].Type == NPCID_WALL_SPARK || NPC[A].Type == NPCID_WALL_TURTLE\n        || NPC[A].Type == NPCID_RED_BOOT || NPC[A].Type == NPCID_BLU_BOOT || NPC[A].Type == NPCID_QUAD_BALL\n        || NPC[A].Type == NPCID_FLY_BLOCK || NPC[A].Type == NPCID_FLY_CANNON || NPC[A].Type == NPCID_FIRE_BOSS_FIRE\n        || NPC[A].Type == NPCID_DOOR_MAKER || NPC[A].Type == NPCID_MAGIC_DOOR)\n    {\n        return;\n    }\n\n    // NPC types that can only query collisions as projectiles\n    if(!NPC[A].Projectile && (NPC[A].Type == NPCID_HEAVY_THROWN || NPC[A].Type == NPCID_SPIT_BOSS_BALL || NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_YEL_PLATFORM || NPC[A].Type == NPCID_BLU_PLATFORM || NPC[A].Type == NPCID_GRN_PLATFORM ||\n       NPC[A].Type == NPCID_RED_PLATFORM || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT ||\n       NPC[A].Type == NPCID_VPIPE_LONG || NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_SPIKY_BALL_S3 || NPC[A].Type == NPCID_ITEM_POD || NPC[A]->IsAShell))\n    {\n        return;\n    }\n\n    // most NPCs cannot query collisions if static and not projectile\n    if(!NPCIsToad(NPC[A]) && !NPC[A].Projectile && NPC[A].Location.SpeedX == 0 && (NPC[A].Location.SpeedY == 0 || NPC[A].Location.SpeedY == Physics.NPCGravity))\n        return;\n\n    // slide blocks only query collisions after activation\n    if(NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 0)\n        return;\n\n    // newly spawned hit turtles do not query collisions\n    if(NPC[A].Projectile && NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4)\n        return;\n\n    // duplicate condition\n    // if(NPC[A].Projectile && NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4 && NPC[A].CantHurt > 0)\n    //     return;\n\n    if(NPC[A]->IsFish && NPC[A].Special == 2)\n        return;\n\n    for(int B : treeNPCQuery(NPC[A].Location, SORTMODE_ID))\n    {\n        if(B == A || !NPC[B].Active || NPC[B]->IsACoin)\n            continue;\n\n        if(!CheckCollision(NPC[A].Location, NPC[B].Location))\n            continue;\n\n        // first exclusion condition\n        // if(!(!(NPC[B].Type == NPCID_MINIBOSS && NPC[B].Special == 4) && !(NPCIsToad(NPC[B])) &&\n        //    !(NPC[B].Type >= NPCID_PLATFORM_S3 && NPC[B].Type <= NPCID_PLATFORM_S1) && !(NPC[B].Type >= NPCID_CARRY_BLOCK_A && NPC[B].Type <= NPCID_CARRY_BLOCK_D) &&\n        //    NPC[B].Type != NPCID_LIFT_SAND && NPC[B].Type != NPCID_SICK_BOSS_BALL && !NPC[B]->IsAVine &&\n        //    NPC[B].Type != NPCID_PLR_ICEBALL && NPC[B].Type != NPCID_FIRE_CHAIN && NPC[B].Type != NPCID_CHAR3_HEAVY))\n        // {\n        //     continue;\n        // }\n\n        // second exclusion condition\n        // If Not (NPC(B).Type = 133) And NPC(B).HoldingPlayer = 0 And .Killed = 0 And NPC(B).JustActivated = 0 And NPC(B).Inert = False And NPC(B).Killed = 0 Then\n        // if(!(NPC[B].Type != NPCID_SPIT_GUY_BALL && !(NPCIsVeggie(NPC[B]) && NPCIsVeggie(NPC[A])) &&\n        //    NPC[B].HoldingPlayer == 0 && NPC[A].Killed == 0 &&\n        //    NPC[B].JustActivated == 0 && !NPC[B].Inert && NPC[B].Killed == 0))\n        // {\n        //     continue;\n        // }\n\n        // third exclusion condition\n        // if(!(NPC[B].Type != NPCID_CANNONITEM && NPC[B].Type != NPCID_SWORDBEAM && NPC[B].Type != NPCID_TOOTHYPIPE && NPC[B].Type != NPCID_SPRING &&\n        //    NPC[B].Type != NPCID_HEAVY_THROWN && NPC[B].Type != NPCID_KEY && NPC[B].Type != NPCID_COIN_SWITCH && NPC[B].Type != NPCID_GRN_BOOT &&\n        //    NPC[B].Type != NPCID_VEHICLE && NPC[B].Type != NPCID_TOOTHY && NPC[B].Type != NPCID_CONVEYOR && NPC[B].Type != NPCID_METALBARREL &&\n        //    NPC[B].Type != NPCID_RED_BOOT && NPC[B].Type != NPCID_BLU_BOOT && !NPC[B].Generator &&\n        //    !((NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL) && NPC[B].Type == NPCID_FLIPPED_RAINBOW_SHELL) &&\n        //    NPC[B].Type != NPCID_TIMER_S2 && NPC[B].Type != NPCID_FLY_BLOCK && NPC[B].Type != NPCID_FLY_CANNON && NPC[B].Type != NPCID_DOOR_MAKER &&\n        //    NPC[B].Type != NPCID_MAGIC_DOOR && NPC[B].Type != NPCID_CHAR3_HEAVY && NPC[B].Type != NPCID_PLR_HEAVY && NPC[B].Type != NPCID_CHAR4_HEAVY))\n        // {\n        //     continue;\n        // }\n\n        // fourth exclusion condition\n        // if(!(!(NPC[B].Type == NPCID_HPIPE_SHORT || NPC[B].Type == NPCID_YEL_PLATFORM || NPC[B].Type == NPCID_BLU_PLATFORM ||\n        //    NPC[B].Type == NPCID_GRN_PLATFORM || NPC[B].Type == NPCID_RED_PLATFORM || NPC[B].Type == NPCID_HPIPE_LONG ||\n        //    NPC[B].Type == NPCID_VPIPE_SHORT || NPC[B].Type == NPCID_VPIPE_LONG) && !(!NPC[A].Projectile &&\n        //    NPC[B].Type == NPCID_SPIKY_BALL_S3) && !NPCIsYoshi(NPC[B]) && NPC[B].Type != NPCID_FALL_BLOCK_RED &&\n        //    NPC[B].Type != NPCID_FALL_BLOCK_BROWN && !(NPC[B].Type == NPCID_SLIDE_BLOCK && NPC[B].Special == 0) &&\n        //    NPC[B].Type != NPCID_CONVEYOR && !(NPC[B].Type >= NPCID_TANK_TREADS && NPC[B].Type <= NPCID_SLANT_WOOD_M) &&\n        //    NPC[B].Type != NPCID_STATUE_S3 && NPC[B].Type != NPCID_STATUE_FIRE && !(NPC[B].Type == NPCID_BULLET &&\n        //    NPC[B].CantHurt > 0) && NPC[B].Type != NPCID_ITEM_BURIED && !(NPC[A].CantHurtPlayer == NPC[B].CantHurtPlayer &&\n        //    NPC[A].CantHurtPlayer > 0) && !(NPC[B].Type == NPCID_ITEM_POD && !NPC[B].Projectile) &&\n        //    NPC[B].Type != NPCID_PET_FIRE && NPC[B].Type != NPCID_PLANT_FIREBALL && NPC[B].Type != NPCID_QUAD_BALL &&\n        //    NPC[B].Type != NPCID_FIRE_BOSS_FIRE && NPC[B].Type != NPCID_RED_VINE_TOP_S3 && NPC[B].Type != NPCID_GRN_VINE_TOP_S3 && NPC[B].Type != NPCID_GRN_VINE_TOP_S4))\n        // {\n        //     continue;\n        // }\n\n        // don't allow vines to be collision targets\n        if(NPC[B]->IsAVine)\n            continue;\n\n        // don't allow killed NPCs to collide, and don't allow held, just-activated, or friendly NPCs to be collision targets\n        if(NPC[A].Killed != 0 || NPC[B].Killed != 0 || NPC[B].HoldingPlayer != 0 || NPC[B].JustActivated != 0 || NPC[B].Inert)\n            continue;\n\n        // don't allow generators to be collision targets\n        if(NPC[B].Generator)\n            continue;\n\n        // don't allow friendly fire\n        if(NPC[A].CantHurtPlayer == NPC[B].CantHurtPlayer && NPC[A].CantHurtPlayer > 0)\n            continue;\n\n        // these types can't be collision targets\n        if((NPC[B].Type >= NPCID_PLATFORM_S3 && NPC[B].Type <= NPCID_PLATFORM_S1) || (NPC[B].Type >= NPCID_CARRY_BLOCK_A && NPC[B].Type <= NPCID_CARRY_BLOCK_D) ||\n            NPC[B].Type == NPCID_LIFT_SAND || NPC[B].Type == NPCID_SICK_BOSS_BALL || NPC[B].Type == NPCID_PLR_ICEBALL || NPC[B].Type == NPCID_FIRE_CHAIN || NPC[B].Type == NPCID_CHAR3_HEAVY ||\n            NPC[B].Type == NPCID_FALL_BLOCK_RED || NPC[B].Type == NPCID_FALL_BLOCK_BROWN ||\n            NPC[B].Type == NPCID_SPIT_GUY_BALL || NPC[B].Type == NPCID_CANNONITEM || NPC[B].Type == NPCID_SWORDBEAM || NPC[B].Type == NPCID_TOOTHYPIPE || NPC[B].Type == NPCID_SPRING ||\n            NPC[B].Type == NPCID_HEAVY_THROWN || NPC[B].Type == NPCID_KEY || NPC[B].Type == NPCID_COIN_SWITCH || NPC[B].Type == NPCID_GRN_BOOT ||\n            NPC[B].Type == NPCID_VEHICLE || NPC[B].Type == NPCID_TOOTHY || NPC[B].Type == NPCID_CONVEYOR || NPC[B].Type == NPCID_METALBARREL ||\n            NPC[B].Type == NPCID_RED_BOOT || NPC[B].Type == NPCID_BLU_BOOT ||\n            NPC[B].Type == NPCID_TIMER_S2 || NPC[B].Type == NPCID_FLY_BLOCK || NPC[B].Type == NPCID_FLY_CANNON || NPC[B].Type == NPCID_DOOR_MAKER ||\n            NPC[B].Type == NPCID_MAGIC_DOOR || NPC[B].Type == NPCID_CHAR3_HEAVY || NPC[B].Type == NPCID_PLR_HEAVY || NPC[B].Type == NPCID_CHAR4_HEAVY ||\n            NPC[B].Type == NPCID_HPIPE_SHORT || NPC[B].Type == NPCID_YEL_PLATFORM || NPC[B].Type == NPCID_BLU_PLATFORM ||\n            NPC[B].Type == NPCID_GRN_PLATFORM || NPC[B].Type == NPCID_RED_PLATFORM || NPC[B].Type == NPCID_HPIPE_LONG ||\n            NPC[B].Type == NPCID_VPIPE_SHORT || NPC[B].Type == NPCID_VPIPE_LONG || NPC[B].Type == NPCID_CONVEYOR || (NPC[B].Type >= NPCID_TANK_TREADS && NPC[B].Type <= NPCID_SLANT_WOOD_M) ||\n            NPC[B].Type == NPCID_STATUE_S3 || NPC[B].Type == NPCID_STATUE_FIRE || NPC[B].Type == NPCID_ITEM_BURIED || NPC[B].Type == NPCID_PET_FIRE || NPC[B].Type == NPCID_PLANT_FIREBALL || NPC[B].Type == NPCID_QUAD_BALL ||\n            NPC[B].Type == NPCID_FIRE_BOSS_FIRE || NPC[B].Type == NPCID_RED_VINE_TOP_S3 || NPC[B].Type == NPCID_GRN_VINE_TOP_S3 || NPC[B].Type == NPCID_GRN_VINE_TOP_S4 || NPCIsYoshi(NPC[B].Type) || NPCIsToad(NPC[B].Type))\n        {\n            continue;\n        }\n        // miniboss can't be collision target if in guard state\n        else if(NPC[B].Type == NPCID_MINIBOSS)\n        {\n            if(NPC[B].Special == 4)\n                continue;\n        }\n        // bullet can't be collision target if just player-shot\n        else if(NPC[B].Type == NPCID_BULLET)\n        {\n            if(NPC[B].CantHurt > 0)\n                continue;\n        }\n        // item pod can't be collision target until thrown\n        else if(NPC[B].Type == NPCID_ITEM_POD)\n        {\n            if(!NPC[B].Projectile)\n                continue;\n        }\n        // slide block can't be collision target until activation\n        else if(NPC[B].Type == NPCID_SLIDE_BLOCK)\n        {\n            if(NPC[B].Special == 0)\n                continue;\n        }\n        // spiky balls can't be collision target unless hit by thrown item\n        else if(NPC[B].Type == NPCID_SPIKY_BALL_S3)\n        {\n            if(!NPC[A].Projectile)\n                continue;\n        }\n        // flipped rainbow shells can't be collision target of fireball / iceball\n        else if(NPC[B].Type == NPCID_FLIPPED_RAINBOW_SHELL)\n        {\n            if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL)\n                continue;\n        }\n        // veggies can't be collision target of other veggies\n        else if(NPCIsVeggie(NPC[B].Type))\n        {\n            if(NPCIsVeggie(NPC[A].Type))\n                continue;\n        }\n\n        // NPC-NPC collisions must be handled a function pointer defined by NPC A, but also shouldn't be hardcoded based on NPC B's type\n        //   this logic will be quite difficult (but necessary) to convert\n\n        // NOTE: There are a number of assignments to HitSpot here, but they are never read from (in the entire UpdateNPCs routine).\n        // All reads are preceded by other writes.\n        // I am commenting out these assignments.\n\n        // if(NPC[A].Type == NPCID_MAGIC_BOSS_BALL || NPC[B].Type == NPCID_MAGIC_BOSS_BALL || NPC[A].Type == NPCID_FIRE_BOSS_FIRE || NPC[B].Type == NPCID_FIRE_BOSS_FIRE)\n        //     HitSpot = 0;\n\n        if(NPC[A].Type == NPCID_ITEM_BUBBLE)\n        {\n            NPCHit(A, 3, B);\n            // HitSpot = 0;\n        }\n        else if(NPC[B].Type == NPCID_ITEM_BUBBLE)\n            NPCHit(B, 3, A);\n\n\n        if(NPC[A].Type == NPCID_SWORDBEAM)\n        {\n            if(!NPC[B]->IsABonus)\n                NPCHit(B, 10, NPC[A].CantHurtPlayer);\n            // HitSpot = 0;\n        }\n\n        // toad code\n        if(NPCIsToad(NPC[A]))\n        {\n            if(!(NPC[B]->WontHurt && !NPC[B].Projectile) && !NPC[B]->IsABonus &&\n               NPC[B].Type != NPCID_PLR_FIREBALL && /* NPC[B].Type != NPCID_PLR_ICEBALL && !(NPC[B].Type == NPCID_BULLET && NPC[B].CantHurt > 0) &&\n               NPC[B].Type != NPCID_TOOTHY && NPC[B].Type != NPCID_PLR_HEAVY && NPC[B].Type != NPCID_CHAR4_HEAVY && */ NPC[B].Type != NPCID_FLIPPED_RAINBOW_SHELL)\n            {\n                NPCHit(A, 3, B);\n                // HitSpot = 0;\n            }\n        }\n\n        // turtle enters a shell\n        if((NPC[A].Type == NPCID_GRN_HIT_TURTLE_S4 || NPC[A].Type == NPCID_RED_HIT_TURTLE_S4 || NPC[A].Type == NPCID_YEL_HIT_TURTLE_S4) && /* !NPC[A].Projectile && */\n           (!NPC[B].Projectile && NPC[B].Type >= NPCID_GRN_SHELL_S4 && NPC[B].Type <= NPCID_YEL_SHELL_S4))\n        {\n            Location_t tempLocation = NPC[A].Location;\n            Location_t tempLocation2 = NPC[B].Location;\n            tempLocation.Width = 8;\n            tempLocation.X += 12;\n            tempLocation2.Width = 8;\n            tempLocation2.X += 12;\n\n            if(CheckCollision(tempLocation, tempLocation2))\n            {\n                NPC[B].Type = NPCID(NPC[B].Type - 4);\n                if(NPC[B].Type == NPCID_YEL_TURTLE_S4)\n                    NPC[B].Type = NPCID_RAINBOW_SHELL;\n                NPC[A].Killed = 9;\n                NPCQueues::Killed.push_back(A);\n                NPC[B].Direction = NPC[A].Direction;\n                NPC[B].Frame = EditorNPCFrame(NPC[B].Type, NPC[B].Direction);\n            }\n        }\n        // NPC is a projectile\n        else if(NPC[A].Projectile && /*!(NPC[B].Type == NPCID_SLIDE_BLOCK && NPC[B].Special == 0.0) &&*/ NPC[A].Type != NPCID_SWORDBEAM)\n        {\n            if(!(NPC[A].Projectile && NPC[B].Projectile && NPC[A].Type == NPCID_BULLET && NPC[B].Type == NPCID_BULLET && NPC[A].CantHurtPlayer != NPC[B].CantHurtPlayer))\n            {\n                if(!((NPC[A].Type == NPCID_PLR_FIREBALL && NPC[B]->IsABonus) || NPC[B].Type == NPCID_PLR_FIREBALL || NPC[B].Type == NPCID_VILLAIN_FIRE))\n                {\n                    // allow turtle (B) to kick shell (A) if it is facing the shell\n                    if(NPC[A]->IsAShell &&\n                            (NPC[B].Type == NPCID_EXT_TURTLE || NPC[B].Type == NPCID_BLU_HIT_TURTLE_S4) &&\n                            (NPC[A].Direction != NPC[B].Direction || NPC[A].Special > 0) && !NPC[B].Projectile)\n                    {\n                        if(NPC[A].Direction == -1)\n                        {\n                            NPC[B].Frame = 3;\n                            if(NPC[B].Type == NPCID_BLU_HIT_TURTLE_S4)\n                                NPC[B].Frame = 5;\n                            NPC[B].FrameCount = 0;\n                        }\n                        else\n                        {\n                            NPC[B].Frame = 0;\n                            NPC[B].FrameCount = 0;\n                        }\n\n                        if(NPC[A].CantHurt < 25)\n                            NPC[A].Special = 1;\n                        if(NPC[A].Location.to_right_of(NPC[B].Location))\n                        {\n                            NPC[B].Location.X = NPC[A].Location.X - NPC[B].Location.Width - 1;\n                            NPC[B].Direction = 1;\n                        }\n                        else\n                        {\n                            NPC[B].Location.X = NPC[A].Location.X + NPC[A].Location.Width + 1;\n                            NPC[B].Direction = -1;\n                        }\n\n                        if(NPC[A].Location.SpeedY < NPC[B].Location.SpeedY)\n                            NPC[A].Location.SpeedY = NPC[B].Location.SpeedY;\n                        NPC[A].Frame = 0;\n                        NPC[A].FrameCount = 0;\n                        if(NPC[A].CantHurt < 25)\n                            NPC[A].Special = 2;\n                        NPC[B].Special = 0;\n                        Location_t tempLocation = NPC[B].Location;\n                        tempLocation.Y += 1;\n                        tempLocation.Height -= 2;\n\n                        for(int bCheck2 = 1; bCheck2 <= 2; bCheck2++)\n                        {\n                            // if(bCheck2 == 1)\n                            // {\n                            //     // fBlock2 = FirstBlock[(NPC[B].Location.X / 32) - 1];\n                            //     // lBlock2 = LastBlock[((NPC[B].Location.X + NPC[B].Location.Width) / 32.0) + 1];\n                            //     blockTileGet(NPC[B].Location, fBlock2, lBlock2);\n                            // }\n                            // else\n                            // {\n                                   // ds-sloth comment: this was a \"bug\",\n                                   // but it never affected the result so wasn't fixed\n                                   // should be numBlock - numTempBlock + 1,\n                                   // this will double-count numBlock - numTempBlock.\n                                   // not a problem because it is the last of the non-temp blocks\n                                   // and the first of the temp blocks in the original check,\n                                   // so order is the same if we exclusively count it is a non-temp block,\n                                   // which is what the new code does\n                            //     fBlock2 = numBlock - numTempBlock;\n                            //     lBlock2 = numBlock;\n                            // }\n\n                            auto collBlockSentinel2 = (bCheck2 == 1)\n                                ? treeFLBlockQuery(NPC[B].Location, SORTMODE_COMPAT)\n                                : treeTempBlockQuery(NPC[B].Location, SORTMODE_LOC);\n\n                            for(BlockRef_t block2 : collBlockSentinel2)\n                            {\n                                int C = block2;\n\n                                if(!BlockIsSizable[Block[C].Type] && !BlockOnlyHitspot1[Block[C].Type] && !Block[C].Hidden && BlockSlope[Block[C].Type] == 0)\n                                {\n                                    if(CheckCollision(tempLocation, Block[C].Location))\n                                    {\n                                        if(int(NPC[A].Direction) == -1)\n                                        {\n                                            NPC[B].Location.X = Block[C].Location.X + Block[C].Location.Width + 0.1_n;\n                                            NPC[A].Location.X = NPC[B].Location.X + NPC[B].Location.Width + 0.1_n;\n                                        }\n                                        else\n                                        {\n                                            NPC[B].Location.X = Block[C].Location.X - NPC[B].Location.Width - 0.1_n;\n                                            NPC[A].Location.X = NPC[B].Location.X - NPC[A].Location.Width - 0.1_n;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n\n                        treeNPCUpdate(B);\n                        if(NPC[B].tempBlock > 0)\n                            treeNPCSplitTempBlock(B);\n                    }\n                    else if(NPC[A].Type == NPCID_TANK_TREADS)\n                        NPCHit(B, 8, A);\n                    else\n                    {\n                        if(!NPC[B]->IsABonus)\n                        {\n                            if(NPC[A].Type == NPCID_CANNONENEMY && NPC[B].Type == NPCID_BULLET)\n                                NPC[B].Projectile = true;\n                            else\n                            {\n                                bool tempBool = false; // This whole cluster stops friendly projectiles form killing riddin shells\n\n                                if(NPC[A]->IsAShell)\n                                {\n                                    for(auto C = 1; C <= numPlayers; C++)\n                                    {\n                                        if(Player[C].StandingOnNPC == A && NPC[B].CantHurtPlayer == C)\n                                        {\n                                            tempBool = true;\n                                            break;\n                                        }\n                                    }\n                                }\n\n                                if(NPC[B]->IsAShell)\n                                {\n                                    for(auto C = 1; C <= numPlayers; C++)\n                                    {\n                                        if(Player[C].StandingOnNPC == B && NPC[A].CantHurtPlayer == C)\n                                        {\n                                            tempBool = true;\n                                            break;\n                                        }\n                                    }\n                                }\n\n                                if(!(NPC[A].Type == NPCID_BULLET && NPC[A].Projectile))\n                                {\n                                    if(NPC[B]->IsAShell && NPC[B].Projectile)\n                                    {\n                                        if(!tempBool)\n                                            NPCHit(A, 3, B);\n                                    }\n                                    else\n                                    {\n                                        if(!tempBool)\n                                            NPCHit(A, 4, B);\n                                    }\n                                }\n\n                                if(!tempBool) // end cluster\n                                    NPCHit(B, 3, A);\n\n                                if(NPC[A].Type == NPCID_BULLET)\n                                {\n                                    if(NPC[B].Type == NPCID_MINIBOSS)\n                                    {\n                                        NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                                        NPCHit(A, 4, B);\n                                    }\n                                    else if(NPC[B].Type == NPCID_CANNONENEMY)\n                                    {\n                                        NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                                        PlaySoundSpatial(SFX_BlockHit, NPC[A].Location);\n                                        NPCHit(A, 4, A);\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        else if(!(NPC[B].Type == NPCID_SPIT_BOSS_BALL && !NPC[B].Projectile))\n        {\n            int HitSpot = FindCollision(NPC[A].Location, NPC[B].Location);\n\n            if(NPCIsToad(NPC[A]) && NPC[A].Killed > 0)\n                HitSpot = 0;\n\n            if((NPCIsAParaTroopa(NPC[A]) || NPC[A].Wings) && (NPCIsAParaTroopa(NPC[B]) || NPC[B].Wings))\n            {\n                // don't do these special movement routines for winged NPCs if one of the NPCs is a block\n                if(!NPC[A].Wings || !(NPC[A]->IsABlock || (HitSpot == 3 && (NPC[A]->IsAHit1Block || NPC[A]->CanWalkOn)) || NPC[B]->IsABlock || (HitSpot == 1 && (NPC[B]->IsAHit1Block || NPC[B]->CanWalkOn))))\n                {\n                    if(NPC[A].Location.to_right_of(NPC[B].Location))\n                        NPC[A].Location.SpeedX += 0.05_n;\n                    else\n                        NPC[A].Location.SpeedX -= 0.05_n;\n\n                    if(NPC[A].Location.Y + NPC[A].Location.Height / 2 > NPC[B].Location.Y + NPC[B].Location.Height / 2)\n                        NPC[A].Location.SpeedY += 0.05_n;\n                    else\n                        NPC[A].Location.SpeedY -= 0.05_n;\n                }\n\n                HitSpot = 0;\n            }\n\n            if(!NPC[B].Projectile && !NPC[A]->NoClipping && !NPC[B]->NoClipping)\n            {\n                // NPC A can't be a shell in this condition, because A is not a projectile, and shells only query collisions as projectiles\n                if(((NPC[A].Type == NPCID_EXT_TURTLE || NPC[A].Type == NPCID_BLU_HIT_TURTLE_S4) && NPC[B]->IsAShell) /* || ((NPC[B].Type == NPCID_EXT_TURTLE || NPC[B].Type == NPCID_BLU_HIT_TURTLE_S4) && NPC[A]->IsAShell)*/) // Nekkid koopa kicking a shell\n                {\n                    // if(NPC[A].Type == NPCID_EXT_TURTLE || NPC[A].Type == NPCID_BLU_HIT_TURTLE_S4)\n                    if(NPC[A].Location.SpeedY == Physics.NPCGravity || NPC[A].Slope > 0)\n                    {\n                        // If .Direction = 1 And .Location.X + .Location.Width < NPC(B).Location.X + 3 Or (.Direction = -1 And .Location.X > NPC(B).Location.X + NPC(B).Location.Width - 3) Then\n                        if((NPC[A].Direction == 1  && NPC[A].Location.X + NPC[A].Location.Width < NPC[B].Location.X + 4) ||\n                           (NPC[A].Direction == -1 && NPC[A].Location.X > NPC[B].Location.X + NPC[B].Location.Width - 4))\n                        {\n                            if(NPC[B].Location.SpeedX == 0 && NPC[B].Effect == NPCEFF_NORMAL)\n                            {\n                                NPC[A].Special = 10;\n                                Player[numPlayers + 1].Direction = NPC[A].Direction;\n                                NPC[A].Location.X += -NPC[A].Direction;\n                                NPCHit(B, 1, numPlayers + 1);\n                            }\n                        }\n                    }\n                }\n                else if(NPC[A].Effect == NPCEFF_MAZE && NPC[B].Effect == NPCEFF_MAZE && HitSpot != 5)\n                {\n                    if(!NPC[B].TurnAround)\n                        NPC[A].TurnAround = true;\n\n                    if(NPC[A].Effect3 != NPC[B].Effect3)\n                        NPC[B].TurnAround = true;\n                }\n                else if((HitSpot == 2 || HitSpot == 4) && NPC[A].Type != NPCID_SAW && NPC[B].Type != NPCID_SAW)\n                {\n                    // set in SMBX 1.3 but reset to false before it got read\n                    // NPC[A].onWall = true;\n\n                    if(NPC[A].Direction == NPC[B].Direction)\n                    {\n                        if(NPC[A].Location.SpeedX * NPC[A].Direction > NPC[B].Location.SpeedX * NPC[B].Direction)\n                        {\n                            if(NPC[A].Type != NPCID_BULLET && NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_PLR_ICEBALL)\n                                NPC[A].TurnAround = true;\n                        }\n                        else if(NPC[A].Location.SpeedX * NPC[A].Direction < NPC[B].Location.SpeedX * NPC[B].Direction)\n                            NPC[B].TurnAround = true;\n                        else\n                        {\n                            NPC[A].TurnAround = true;\n                            NPC[B].TurnAround = true;\n                        }\n                    }\n                    else\n                    {\n                        if(NPC[A].Type != NPCID_BULLET && NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_PLR_ICEBALL)\n                            NPC[A].TurnAround = true;\n                        NPC[B].TurnAround = true;\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid NPCCollideHeld(int A)\n{\n    // these types can't collide while held\n    if(NPC[A].Type == NPCID_FLIPPED_RAINBOW_SHELL || NPC[A].Type == NPCID_CANNONITEM || NPC[A].Type == NPCID_SPRING || NPC[A].Type == NPCID_COIN_SWITCH ||\n         NPC[A].Type == NPCID_TIME_SWITCH || NPC[A].Type == NPCID_TNT || NPC[A].Type == NPCID_BLU_BOOT || NPC[A].Type == NPCID_GRN_BOOT || NPC[A].Type == NPCID_RED_BOOT ||\n         NPC[A].Type == NPCID_TOOTHYPIPE || NPC[A].Type == NPCID_BOMB || (NPC[A].Type >= NPCID_CARRY_BLOCK_A && NPC[A].Type <= NPCID_CARRY_BLOCK_D) ||\n         NPC[A].Type == NPCID_KEY || NPC[A].Type == NPCID_TIMER_S2 || NPC[A].Type == NPCID_FLY_BLOCK || NPC[A].Type == NPCID_FLY_CANNON || NPC[A].Type == NPCID_CHAR4_HEAVY)\n    {\n        return;\n    }\n\n    for(int B : treeNPCQuery(NPC[A].Location, SORTMODE_ID))\n    {\n        // original exclusion condition\n        // if(!(B != A && NPC[B].Active &&\n        //    (NPC[B].HoldingPlayer == 0 || (BattleMode && NPC[B].HoldingPlayer != NPC[A].HoldingPlayer)) &&\n        //    !NPC[B]->IsABonus &&\n        //    (NPC[B].Type != NPCID_PLR_FIREBALL  || (BattleMode && NPC[B].CantHurtPlayer != NPC[A].HoldingPlayer)) &&\n        //    (NPC[B].Type != NPCID_PLR_ICEBALL || (BattleMode && NPC[B].CantHurtPlayer != NPC[A].HoldingPlayer)) &&\n        //     NPC[B].Type != NPCID_CANNONENEMY && NPC[B].Type != NPCID_CANNONITEM &&  NPC[B].Type != NPCID_SPRING && NPC[B].Type != NPCID_KEY &&\n        //     NPC[B].Type != NPCID_COIN_SWITCH && NPC[B].Type != NPCID_TIME_SWITCH && NPC[B].Type != NPCID_TNT && NPC[B].Type != NPCID_RED_BOOT &&\n        //     NPC[B].Type != NPCID_GRN_BOOT && !(NPC[B].Type == NPCID_BLU_BOOT && NPC[A].Type == NPCID_BLU_BOOT) &&\n        //     NPC[B].Type != NPCID_STONE_S3 && NPC[B].Type != NPCID_STONE_S4 && NPC[B].Type != NPCID_GHOST_S3 &&\n        //     NPC[B].Type != NPCID_SPIT_BOSS && !(NPC[B].Type == NPCID_SLIDE_BLOCK && NPC[B].Special == 0.0) &&\n        //     NPC[B].Type != NPCID_ITEM_BURIED && NPC[B].Type != NPCID_LIFT_SAND && NPC[B].Type != NPCID_FLIPPED_RAINBOW_SHELL &&\n        //    !(NPC[B].Type == NPCID_HEAVY_THROWN && NPC[B].Projectile) && NPC[B].Type != NPCID_EARTHQUAKE_BLOCK && NPC[B].Type != NPCID_ICE_CUBE && NPC[B].Type != NPCID_CHAR3_HEAVY))\n        // {\n        //     continue;\n        // }\n\n        if(B == A || !NPC[B].Active)\n            continue;\n\n        // don't kill dead or friendly NPC\n        if(NPC[B].Killed != 0 || NPC[B].Inert)\n            continue;\n\n        // don't kill powerups\n        if(NPC[B]->IsABonus)\n            continue;\n\n        // don't kill other players' NPCs except in Battle Mode\n        if(NPC[B].HoldingPlayer != 0 && (!BattleMode || NPC[B].HoldingPlayer == NPC[A].HoldingPlayer))\n            continue;\n\n        // no friendly fire\n        if(NPC[A].CantHurtPlayer == NPC[B].CantHurtPlayer)\n            continue;\n\n        // don't kill NPC player is standing on\n        if(Player[NPC[A].HoldingPlayer].StandingOnNPC == B)\n            continue;\n\n        // these types can't be collision targets\n        if(NPC[B].Type == NPCID_CANNONENEMY || NPC[B].Type == NPCID_CANNONITEM || NPC[B].Type == NPCID_SPRING || NPC[B].Type == NPCID_KEY ||\n            NPC[B].Type == NPCID_COIN_SWITCH || NPC[B].Type == NPCID_TIME_SWITCH || NPC[B].Type == NPCID_TNT || NPC[B].Type == NPCID_RED_BOOT ||\n            NPC[B].Type == NPCID_GRN_BOOT || NPC[B].Type == NPCID_STONE_S3 || NPC[B].Type == NPCID_STONE_S4 || NPC[B].Type == NPCID_GHOST_S3 ||\n            NPC[B].Type == NPCID_SPIT_BOSS || NPC[B].Type == NPCID_ITEM_BURIED || NPC[B].Type == NPCID_LIFT_SAND || NPC[B].Type == NPCID_FLIPPED_RAINBOW_SHELL ||\n            NPC[B].Type == NPCID_EARTHQUAKE_BLOCK || NPC[B].Type == NPCID_ICE_CUBE || NPC[B].Type == NPCID_CHAR3_HEAVY)\n        {\n            continue;\n        }\n        // slide block can't be collision target until activation\n        else if(NPC[B].Type == NPCID_SLIDE_BLOCK)\n        {\n            if(NPC[B].Special == 0)\n                continue;\n        }\n        else if(NPC[B].Type == NPCID_HEAVY_THROWN)\n        {\n            if(NPC[B].Projectile)\n                continue;\n        }\n        // probably redundant check (see above friendly fire note), but need to confirm when CantHurtPlayer and HoldingPlayer may be out of sync\n        else if(NPC[B].Type == NPCID_PLR_FIREBALL || NPC[B].Type == NPCID_PLR_ICEBALL)\n        {\n            if(!BattleMode || NPC[B].CantHurtPlayer == NPC[A].HoldingPlayer)\n                continue;\n        }\n\n#if 0\n        // impossible\n        if(NPC[B].Type == NPCID_BLU_BOOT && NPC[A].Type == NPCID_BLU_BOOT)\n            continue;\n#endif\n\n        if(!CheckCollision(NPC[A].Location, NPC[B].Location))\n            continue;\n\n        NPCHit(B, 3, A);\n\n        if(NPC[B].Killed > 0)\n        {\n            NPC[B].Location.SpeedX = Physics.NPCShellSpeed / 2 * -Player[NPC[A].HoldingPlayer].Direction;\n            NPCHit(A, 5, B);\n        }\n\n        if(NPC[A].Killed > 0)\n            NPC[A].Location.SpeedX = Physics.NPCShellSpeed / 2 * Player[NPC[A].HoldingPlayer].Direction;\n\n        if(!g_config.fix_held_item_cancel || NPC[A].Killed || NPC[B].Killed)\n            break;\n    }\n}\n"
  },
  {
    "path": "src/npc/npc_update/npc_effects.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"globals.h\"\n#include \"npc.h\"\n#include \"npc_traits.h\"\n#include \"npc/npc_queues.h\"\n#include \"config.h\"\n#include \"collision.h\"\n#include \"layers.h\"\n#include \"editor.h\"\n#include \"player.h\"\n#include \"npc_update_priv.h\"\n#include \"phys_env.h\"\n\n#include \"main/trees.h\"\n\nstatic inline void NPCEffectLogic_EmergeUp(int A)\n{\n    if(NPC[A].Direction == 0) // Move toward the closest player\n    {\n        int target_plr = NPCTargetPlayer(NPC[A]);\n\n        if(target_plr)\n            NPC[A].Direction = -Player[target_plr].Direction;\n    }\n\n    NPC[A].Frame = EditorNPCFrame(NPC[A].Type, NPC[A].Direction, A);\n    NPC[A].Effect2 += 1;\n    NPC[A].Location.Y -= 1; // .01\n    NPC[A].Location.Height += 1;\n\n    if(NPC[A]->HeightGFX > 0)\n    {\n        if(NPC[A].Effect2 >= NPC[A]->HeightGFX)\n        {\n            NPC[A].Effect = NPCEFF_NORMAL;\n            NPC[A].Effect2 = 0;\n            NPC[A].Location.set_height_floor(NPC[A]->THeight);\n        }\n    }\n    else\n    {\n        if(NPC[A].Effect2 >= NPC[A]->THeight)\n        {\n            NPC[A].Effect = NPCEFF_NORMAL;\n            NPC[A].Effect2 = 0;\n            NPC[A].Location.Height = NPC[A]->THeight;\n        }\n    }\n}\n\nstatic inline void NPCEffectLogic_EmergeDown(int A)\n{\n    if(NPC[A].Type == NPCID_LEAF_POWER)\n        NPC[A].Direction = 1;\n    else if(NPC[A].Direction == 0) // Move toward the closest player\n    {\n        int target_plr = NPCTargetPlayer(NPC[A]);\n\n        if(target_plr)\n            NPC[A].Direction = -Player[target_plr].Direction;\n    }\n\n    NPC[A].Effect2 += 1;\n    NPC[A].Location.Y += 1;\n\n    if(NPC[A].Effect2 == 32)\n    {\n        NPC[A].Effect = NPCEFF_NORMAL;\n        NPC[A].Effect2 = 0;\n\n        NPC[A].Location.Height = (g_config.fix_npc_emerge_size) ? NPC[A]->THeight : 32;\n\n        for(int bCheck = 1; bCheck <= 2; bCheck++)\n        {\n            // if(bCheck == 1)\n            // {\n            //     // fBlock = FirstBlock[(NPC[A].Location.X / 32) - 1];\n            //     // lBlock = LastBlock[((NPC[A].Location.X + NPC[A].Location.Width) / 32.0) + 1];\n            //     blockTileGet(NPC[A].Location, fBlock, lBlock);\n            // }\n            // else\n            // {\n                   // buggy, mentioned above, should be numBlock - numTempBlock + 1 -- ds-sloth\n                   // it's not a problem here because the NPC is moved out of the way of the block\n                   // during the first loop, so can't collide during the second loop.\n            //     fBlock = numBlock - numTempBlock;\n            //     lBlock = numBlock;\n            // }\n\n            auto collBlockSentinel = (bCheck == 1)\n                ? treeFLBlockQuery(NPC[A].Location, SORTMODE_COMPAT)\n                : treeTempBlockQuery(NPC[A].Location, SORTMODE_LOC);\n\n            for(BlockRef_t block : collBlockSentinel)\n            {\n                int B = block;\n\n                if(!Block[B].Invis && !(BlockIsSizable[Block[B].Type] && NPC[A].Location.Y > Block[B].Location.Y) && !Block[B].Hidden)\n                {\n                    if(CheckCollision(NPC[A].Location, Block[B].Location))\n                    {\n                        NPC[A].Location.Y = Block[B].Location.Y - NPC[A].Location.Height - 0.1_n;\n                        break;\n                    }\n                }\n            }\n        }\n    }\n}\n\nstatic inline void NPCEffectLogic_Encased(int A)\n{\n    bool still_encased = false;\n\n    // Note: since SMBX64, this logic doesn't check for Hidden or Active, so an encased NPC will not escape encased mode properly in Battle Mode (where killed NPCs just become inactive)\n    // Note 2: NPCID_BOSS_FRAGILE does not use the encased logic, it has its own specific logic to check for nearby NPCID_BOSS_CASE\n    for(int B : treeNPCQuery(NPC[A].Location, SORTMODE_NONE))\n    {\n        if(NPC[B].Type == NPCID_BOSS_CASE)\n        {\n            if(CheckCollision(NPC[A].Location, NPC[B].Location))\n            {\n                still_encased = true;\n                break;\n            }\n        }\n    }\n\n    if(!still_encased)\n        NPC[A].Effect = NPCEFF_NORMAL;\n}\n\nstatic inline void NPCEffectLogic_DropItem(int A)\n{\n    // modern item drop\n    if(NPC[A].Effect3 != 0)\n    {\n        const Player_t& p = Player[NPC[A].Effect3];\n        const Location_t& pLoc = p.Location;\n        Location_t& nLoc = NPC[A].Location;\n\n        // Y logic\n        vScreen_t& vscreen = vScreenByPlayer(NPC[A].Effect3);\n\n        // put above player\n        num_t target_X = pLoc.X + (pLoc.Width - nLoc.Width) / 2;\n        num_t target_Y = pLoc.Y + pLoc.Height - 192;\n\n        // anticipate player movement\n        if(PlayerNormal(p))\n        {\n            target_X += pLoc.SpeedX;\n            target_Y += pLoc.SpeedY;\n        }\n\n        // never allow item to go fully offscreen\n        if(target_Y < -vscreen.Y - 8)\n            target_Y = -vscreen.Y - 8;\n\n        // perform movement\n        num_t delta_X = target_X - nLoc.X;\n        num_t delta_Y = target_Y - nLoc.Y;\n\n        num_t move_X = delta_X / 8;\n        num_t move_Y = delta_Y / 8;\n\n        num_t dist = num_t::dist(move_X, move_Y);\n\n        // this magic number is 8 * sqrt(2)\n        if(dist > 0 && dist < 11.31371_n)\n        {\n            move_X = (move_X * 11.31371_r).divided_by(dist);\n            move_Y = (move_Y * 11.31371_r).divided_by(dist);\n        }\n\n        if(num_t::abs(delta_Y) < num_t::abs(move_Y) || NPC[A].Special5 <= 66)\n            nLoc.Y = target_Y;\n        else\n            nLoc.Y += move_Y;\n\n        if(num_t::abs(delta_X) < num_t::abs(move_X) || NPC[A].Special5 <= 66)\n            nLoc.X = target_X;\n        else\n            nLoc.X += move_X;\n\n        // timer logic\n        if(NPC[A].Special5 <= 66)\n            NPC[A].Special5 -= 1;\n        else if(nLoc.X == target_X && nLoc.Y == target_Y)\n            NPC[A].Special5 = 66;\n\n        // enter SMBX mode on timer expiration\n        if(NPC[A].Special5 <= 0)\n        {\n            NPC[A].Effect3 = 0;\n            NPC[A].Special5 = 0;\n        }\n    }\n    else\n    {\n        NPC[A].Location.Y += 2.2_n;\n\n        NPC[A].Effect2 += 1;\n        if(NPC[A].Effect2 == 5)\n            NPC[A].Effect2 = 1;\n    }\n}\n\nstatic inline void NPCEffectLogic_Warp(int A)\n{\n    // NOTE: this code previously used Effect2 to store destination position, and now it uses SpecialX/Y\n    if(NPC[A].Effect3 == 1)\n    {\n        NPC[A].Location.Y -= 1;\n        if(NPC[A].Type == NPCID_PLATFORM_S1)\n            NPC[A].Location.Y -= 1;\n\n        if(NPC[A].Location.Y + NPC[A].Location.Height <= NPC[A].SpecialY)\n        {\n            NPC[A].Effect = NPCEFF_NORMAL;\n            NPC[A].SpecialY = 0;\n            NPC[A].Effect3 = 0;\n        }\n    }\n    else if(NPC[A].Effect3 == 3)\n    {\n        NPC[A].Location.Y += 1;\n\n        if(NPC[A].Type == NPCID_PLATFORM_S1)\n            NPC[A].Location.Y += 1;\n\n        if(NPC[A].Location.Y >= NPC[A].SpecialY)\n        {\n            NPC[A].Effect = NPCEFF_NORMAL;\n            NPC[A].SpecialY = 0;\n            NPC[A].Effect3 = 0;\n        }\n    }\n    else if(NPC[A].Effect3 == 2)\n    {\n        if(NPC[A].Type == NPCID_POWER_S3 || NPC[A].Type == NPCID_LIFE_S3 || NPC[A].Type == NPCID_POISON || NPC[A].Type == NPCID_POWER_S1 || NPC[A].Type == NPCID_POWER_S4 || NPC[A].Type == NPCID_LIFE_S1 || NPC[A].Type == NPCID_LIFE_S4 || NPC[A].Type == NPCID_BRUTE_SQUISHED || NPC[A].Type == NPCID_BIG_GUY)\n            NPC[A].Location.X -= Physics.NPCMushroomSpeed;\n        else if(NPC[A]->CanWalkOn)\n            NPC[A].Location.X -= 1;\n        else\n            NPC[A].Location.X -= Physics.NPCWalkingSpeed;\n\n        if(NPC[A].Location.X + NPC[A].Location.Width <= NPC[A].SpecialX)\n        {\n            NPC[A].Effect = NPCEFF_NORMAL;\n            NPC[A].SpecialX = 0;\n            NPC[A].Effect3 = 0;\n        }\n    }\n    else if(NPC[A].Effect3 == 4)\n    {\n        if(NPC[A].Type == NPCID_POWER_S3 || NPC[A].Type == NPCID_LIFE_S3 || NPC[A].Type == NPCID_POISON || NPC[A].Type == NPCID_POWER_S1 || NPC[A].Type == NPCID_POWER_S4 || NPC[A].Type == NPCID_LIFE_S1 || NPC[A].Type == NPCID_LIFE_S4 || NPC[A].Type == NPCID_BRUTE_SQUISHED || NPC[A].Type == NPCID_BIG_GUY)\n            NPC[A].Location.X += Physics.NPCMushroomSpeed;\n        else if(NPC[A]->CanWalkOn)\n            NPC[A].Location.X += 1;\n        else\n            NPC[A].Location.X += Physics.NPCWalkingSpeed;\n\n        if(NPC[A].Location.X >= NPC[A].SpecialX)\n        {\n            NPC[A].Effect = NPCEFF_NORMAL;\n            NPC[A].SpecialX = 0;\n            NPC[A].Effect3 = 0;\n        }\n    }\n\n    NPCFrames(A);\n\n    if(NPC[A].Effect == NPCEFF_NORMAL && NPC[A].Type != NPCID_ITEM_BURIED)\n    {\n        NPC[A].Layer = LAYER_SPAWNED_NPCS;\n        syncLayers_NPC(A);\n    }\n}\n\nstatic inline void NPCEffectLogic_PetTongue(int A)\n{\n    NPC[A].TimeLeft = 100;\n    NPC[A].Effect3 -= 1;\n    if(NPC[A].Effect3 <= 0)\n    {\n        NPC[A].Effect = NPCEFF_NORMAL;\n        NPC[A].Effect2 = 0;\n        NPC[A].Effect3 = 0;\n    }\n}\n\nstatic inline void NPCEffectLogic_PetInside(int A)\n{\n    NPC[A].TimeLeft = 100;\n    if(Player[NPC[A].Effect2].YoshiNPC != A)\n    {\n        NPC[A].Effect = NPCEFF_NORMAL;\n        NPC[A].Effect2 = 0;\n        NPC[A].Effect3 = 0;\n    }\n}\n\nstatic inline void NPCEffectLogic_Waiting(int A)\n{\n    NPC[A].Effect2 -= 1;\n    if(NPC[A].Effect2 <= 0)\n    {\n        NPC[A].Effect = NPCEFF_NORMAL;\n        NPC[A].Effect2 = 0;\n        NPC[A].Effect3 = 0;\n    }\n}\n\nstatic inline void NPCEffectLogic_Maze(int A)\n{\n    NPC_t& npc = NPC[A];\n\n    NPCFrames(A);\n    NPCSectionWrap(npc);\n    NPCCollide(A);\n\n    if(npc.TurnAround)\n    {\n        npc.Effect3 = (npc.Effect3 & 3) ^ MAZE_DIR_FLIP_BIT;\n        npc.TurnAround = false;\n    }\n\n    // make balls go very slightly faster, and give them speed when they leave so they don't bounce forever\n    bool is_ball = (npc.Type == NPCID_PLR_FIREBALL || npc.Type == NPCID_PLR_ICEBALL);\n    bool is_bullet = (npc.Type == NPCID_BULLET && npc.CantHurt);\n    bool is_player_thrown = is_ball || (npc.Type == NPCID_CHAR3_HEAVY || npc.Type == NPCID_BOMB);\n\n    PhysEnv_Maze(npc.Location, npc.Effect2, npc.Effect3, A, 0, npc.Quicksand ? 1 : (npc.Wet ? 2 : 4) + is_player_thrown + 4 * is_bullet, {false, false, false, false});\n\n    if(npc.Effect3 == MAZE_DIR_LEFT)\n        npc.Direction = -1;\n    else if(npc.Effect3 == MAZE_DIR_RIGHT)\n        npc.Direction = 1;\n\n    if(!npc.Effect2)\n    {\n        npc.Effect = NPCEFF_NORMAL;\n\n        bool is_vert = (npc.Effect3 % 4 == MAZE_DIR_UP || npc.Effect3 % 4 == MAZE_DIR_DOWN);\n        npc.Effect3 = 0;\n\n        if(is_bullet)\n            npc.Location.SpeedX = (npc.CantHurt ? 8 : 4) * npc.Direction;\n        else if(is_vert)\n        {\n            if(!npc.Projectile && !npc.Wet)\n            {\n                npc.Projectile = true;\n                npc.Effect3 = 128;\n            }\n\n            if(is_ball && npc.Special != 5)\n            {\n                int is_left = iRand(2);\n                npc.Location.SpeedX = 1 - 4 * is_left + 2 * dRand();\n            }\n        }\n    }\n}\n\n\nvoid NPCEffects(int A)\n{\n    if(NPC[A].Effect == NPCEFF_EMERGE_UP) // Bonus coming out of a block effect\n        NPCEffectLogic_EmergeUp(A);\n    else if(NPC[A].Effect == NPCEFF_ENCASED)\n        NPCEffectLogic_Encased(A);\n    else if(NPC[A].Effect == NPCEFF_DROP_ITEM) // Bonus item is falling from the players container effect\n        NPCEffectLogic_DropItem(A);\n    else if(NPC[A].Effect == NPCEFF_EMERGE_DOWN) // Bonus falling out of a block\n        NPCEffectLogic_EmergeDown(A);\n    else if(NPC[A].Effect == NPCEFF_WARP) // Warp Generator\n        NPCEffectLogic_Warp(A);\n    else if(NPC[A].Effect == NPCEFF_PET_TONGUE) // Grabbed by Yoshi\n        NPCEffectLogic_PetTongue(A);\n    else if(NPC[A].Effect == NPCEFF_PET_INSIDE) // Held by Yoshi\n        NPCEffectLogic_PetInside(A);\n    else if(NPC[A].Effect == NPCEFF_WAITING) // Holding Pattern\n        NPCEffectLogic_Waiting(A);\n    else if(NPC[A].Effect == NPCEFF_MAZE) // In a maze zone\n        NPCEffectLogic_Maze(A);\n}\n"
  },
  {
    "path": "src/npc/npc_update/npc_generator.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"npc.h\"\n#include \"effect.h\"\n#include \"sound.h\"\n#include \"collision.h\"\n#include \"layers.h\"\n#include \"editor.h\"\n#include \"main/trees.h\"\n#include \"npc_id.h\"\n#include \"eff_id.h\"\n#include \"npc_traits.h\"\n\n#include \"npc/npc_queues.h\"\n#include \"npc/npc_update/npc_update_priv.h\"\n\n// returns true if an NPC should be generated\nbool NPCGeneratorLogic(int A)\n{\n    bool should_make_new = false;\n\n    NPC[A].TimeLeft = 0;\n\n    // Old timer logic\n    // this did not achieve anything other than keeping the value from growing large, but was likely the reason Redigit thought floats were necessary\n#if 0\n    NPC[A].GeneratorTime() += 1;\n\n    if(NPC[A].GeneratorTime() >= NPC[A].GeneratorTimeMax() * 6.5f)\n        NPC[A].GeneratorTime() = NPC[A].GeneratorTimeMax() * 6.5f;\n#endif\n\n    // increase activation timer if not ready yet\n    // (Note: the + 1 tests whether the NPC would have exceeded the limit *after* adding 1, matching the original logic. It's immaterial whether the final += 1 ever occurs.)\n    if((NPC[A].GeneratorTime() + 1) * 10 < NPC[A].GeneratorTimeMax() * 65)\n        NPC[A].GeneratorTime() += 1;\n    // if ready and onscreen, try to activate!\n    else if(NPC[A].GeneratorActive)\n    {\n        bool blocked = false;\n\n        if(numNPCs == maxNPCs - 100)\n            blocked = true;\n\n        // check if blocked by players\n        if(NPC[A].Type != NPCID_ITEM_BURIED && !blocked)\n        {\n            for(int B = 1; B <= numPlayers; B++)\n            {\n                if(!Player[B].Dead && Player[B].TimeToLive == 0)\n                {\n                    if(CheckCollision(NPC[A].Location, Player[B].Location))\n                    {\n                        blocked = true;\n                        break;\n                    }\n                }\n            }\n        }\n\n        // check if blocked by blocks\n        if(NPC[A].Type != NPCID_ITEM_BURIED && !blocked)\n        {\n            for(int B : treeBlockQuery(NPC[A].Location, SORTMODE_NONE))\n            {\n                if(!Block[B].Hidden && !BlockIsSizable[Block[B].Type])\n                {\n                    if(CheckCollision(NPC[A].Location,\n                                      newLoc(Block[B].Location.X + 0.1_n, Block[B].Location.Y + 0.1_n,\n                                             Block[B].Location.Width - 0.2_n, Block[B].Location.Height - 0.2_n)))\n                    {\n                        blocked = true;\n                        break;\n                    }\n                }\n            }\n        }\n\n        // check if blocked by NPCs\n        if(!blocked)\n        {\n            for(int B : treeNPCQuery(NPC[A].Location, SORTMODE_NONE))\n            {\n                if(B != A && NPC[B].Active && NPC[B].Type != NPCID_CONVEYOR)\n                {\n                    if(CheckCollision(NPC[A].Location, NPC[B].Location))\n                    {\n                        blocked = true;\n                        break;\n                    }\n                }\n            }\n        }\n\n        // if blocked, reset GeneratorTime (measured in ticks) to GeneratorTimeMax (measured in deciseconds)\n        // the effect is that we will wait for (6.5 - 1) / 6.5 = 11/13 of the normal generator time before checking again\n        if(blocked)\n            NPC[A].GeneratorTime() = NPC[A].GeneratorTimeMax();\n        // generate a new NPC!\n        else\n        {\n            NPC[A].GeneratorTime() = 0;\n            should_make_new = true;\n        }\n    }\n\n    NPC[A].GeneratorActive = false;\n\n    return should_make_new;\n}\n\n// does NOT call the activation event (because that requires safe ProcEvent handling)\nvoid NPCGeneratorMakeNew(int A)\n{\n    numNPCs++;\n    NPC[numNPCs] = NPC[A];\n\n    if(NPC[A].GeneratorEffect() == 1) // Warp NPC\n    {\n        // NOTE: this code previously used Effect2 to store the destination position, and now it uses SpecialX/Y\n        NPC[numNPCs].Layer = NPC[A].Layer;\n        NPC[numNPCs].Effect3 = NPC[A].GeneratorDirection();\n        NPC[numNPCs].Effect2 = 0;\n        NPC[numNPCs].Effect = NPCEFF_WARP;\n        NPC[numNPCs].Location.SpeedX = 0;\n        NPC[numNPCs].TimeLeft = 100;\n        if(NPC[A].GeneratorDirection() == 1)\n        {\n            if(NPC[A]->HeightGFX > NPC[A].Location.Height)\n            {\n                NPC[numNPCs].Location.Y = NPC[A].Location.Y + NPC[A]->HeightGFX;\n                NPC[numNPCs].SpecialY = NPC[numNPCs].Location.Y - (NPC[A]->HeightGFX - NPC[A].Location.Height);\n            }\n            else\n            {\n                NPC[numNPCs].Location.Y = NPC[A].Location.Y + NPC[A].Location.Height;\n                NPC[numNPCs].SpecialY = NPC[numNPCs].Location.Y;\n            }\n        }\n        else if(NPC[A].GeneratorDirection() == 3)\n        {\n            if(NPC[A]->HeightGFX > NPC[A].Location.Height)\n            {\n                NPC[numNPCs].Location.Y = NPC[A].Location.Y - NPC[A].Location.Height;\n                NPC[numNPCs].SpecialY = NPC[numNPCs].Location.Y + NPC[A].Location.Height + (NPC[A]->HeightGFX - NPC[A].Location.Height);\n            }\n            else\n            {\n                NPC[numNPCs].Location.Y = NPC[A].Location.Y - NPC[A].Location.Height;\n                NPC[numNPCs].SpecialY = NPC[numNPCs].Location.Y + NPC[A].Location.Height;\n            }\n        }\n        else if(NPC[A].GeneratorDirection() == 2)\n        {\n            NPC[numNPCs].Location.Y -= 4;\n            NPC[numNPCs].Location.X = NPC[A].Location.X + NPC[A].Location.Width;\n            NPC[numNPCs].SpecialX = NPC[numNPCs].Location.X;\n        }\n        else if(NPC[A].GeneratorDirection() == 4)\n        {\n            NPC[numNPCs].Location.Y -= 4;\n            NPC[numNPCs].Location.X = NPC[A].Location.X - NPC[A].Location.Width;\n            NPC[numNPCs].SpecialX = NPC[numNPCs].Location.X + NPC[A].Location.Width;\n        }\n    }\n    else if(NPC[A].GeneratorEffect() == 2) // projectile\n    {\n        NPC[numNPCs].Layer = LAYER_SPAWNED_NPCS;\n        PlaySoundSpatial(SFX_Bullet, NPC[A].Location);\n        NPC[numNPCs].Projectile = true;\n        if(NPC[numNPCs].Type == NPCID_BULLET) // Normal Bullet Bills\n            NPC[numNPCs].Projectile = false;\n\n        if(NPC[numNPCs].Type == NPCID_SLIDE_BLOCK)\n            NPC[numNPCs].Special = 1;\n\n        if(NPC[A].GeneratorDirection() == 1)\n        {\n            NPC[numNPCs].Location.SpeedY = -10;\n            NPC[numNPCs].Location.SpeedX = 0;\n            NewEffect(EFFID_SMOKE_S3, newLoc(NPC[A].Location.X, NPC[A].Location.Y + 16, 32, 32));\n            if(NPCIsVeggie(NPC[numNPCs]))\n                NPC[numNPCs].Location.SpeedX = dRand() * 2 - 1;\n            // NPC(numNPCs).Location.SpeedY = -1\n        }\n        else if(NPC[A].GeneratorDirection() == 2)\n        {\n            NPC[numNPCs].Location.SpeedX = -Physics.NPCShellSpeed;\n            NewEffect(EFFID_SMOKE_S3, newLoc(NPC[A].Location.X + 16, NPC[A].Location.Y, 32, 32));\n        }\n        else if(NPC[A].GeneratorDirection() == 3)\n        {\n            NPC[numNPCs].Location.SpeedY = 8;\n            NPC[numNPCs].Location.SpeedX = 0;\n            NewEffect(EFFID_SMOKE_S3, newLoc(NPC[A].Location.X, NPC[A].Location.Y - 16, 32, 32));\n        }\n        else\n        {\n            NPC[numNPCs].Location.SpeedX = Physics.NPCShellSpeed;\n            SoundPause[SFX_BlockHit] = 1;\n            NewEffect(EFFID_SMOKE_S3, newLoc(NPC[A].Location.X - 16, NPC[A].Location.Y, 32, 32));\n        }\n    }\n\n    NPC[numNPCs].Direction = NPC[numNPCs].DefaultDirection;\n    NPC[numNPCs].Frame = EditorNPCFrame(NPC[numNPCs].Type, NPC[numNPCs].Direction);\n    NPC[numNPCs].DefaultDirection = NPC[numNPCs].Direction;\n    NPC[numNPCs].DefaultType = NPCID_NULL;\n    NPC[numNPCs].Generator = false;\n    NPC[numNPCs].Active = true;\n    NPC[numNPCs].TimeLeft = 100;\n    NPC[numNPCs].TriggerActivate = NPC[A].TriggerActivate;\n    NPC[numNPCs].TriggerDeath = NPC[A].TriggerDeath;\n    NPC[numNPCs].TriggerLast = NPC[A].TriggerLast;\n    NPC[numNPCs].TriggerTalk = NPC[A].TriggerTalk;\n    // new because generator variables now share memory with Special3/4/5\n    NPC[numNPCs].Special3 = 0;\n    NPC[numNPCs].GeneratorTime() = 0;\n    NPC[numNPCs].GeneratorTimeMax() = 0;\n    CheckSectionNPC(numNPCs);\n    syncLayers_NPC(numNPCs);\n}\n"
  },
  {
    "path": "src/npc/npc_update/npc_movement_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"npc.h\"\n#include \"npc_traits.h\"\n#include \"config.h\"\n#include \"collision.h\"\n\n#include \"npc/npc_update/npc_update_priv.h\"\n\n#include \"main/trees.h\"\n\nvoid NPCMovementLogic(int A, tempf_t& speedVar)\n{\n    num_t Wings_SpeedX = NPC[A].Location.SpeedX;\n    num_t Wings_SpeedY = NPC[A].Location.SpeedY;\n\n    // POSSIBLE SUBROUTINE: setSpeed\n\n    // this condition was duplicated in two places in SMBX 1.3\n    bool use_default_movement = (NPCDefaultMovement(NPC[A].Type) || (NPC[A]->IsFish && NPC[A].Special != 2)) && !((NPC[A].Type == NPCID_EXT_TURTLE || NPC[A].Type == NPCID_BLU_HIT_TURTLE_S4) && NPC[A].Special > 0);\n\n    // Default Movement Code\n    if(use_default_movement && NPC[A].Type != NPCID_ITEM_BURIED)\n    {\n        if(NPC[A].Direction == 0)\n        {\n            if(iRand(2) == 0)\n                NPC[A].Direction = -1;\n            else\n                NPC[A].Direction = 1;\n        }\n\n        if(NPC[A]->CanWalkOn)\n        {\n            if(NPC[A].Location.SpeedX < Physics.NPCWalkingOnSpeed && NPC[A].Location.SpeedX > -Physics.NPCWalkingOnSpeed)\n            {\n                if(!NPC[A].Projectile)\n                    NPC[A].Location.SpeedX = Physics.NPCWalkingOnSpeed * NPC[A].Direction;\n            }\n\n            if(NPC[A].Location.SpeedX > Physics.NPCWalkingOnSpeed)\n            {\n                NPC[A].Location.SpeedX -= 0.05_n;\n                if(!NPC[A].Projectile)\n                    NPC[A].Location.SpeedX -= 0.1_n;\n            }\n            else if(NPC[A].Location.SpeedX < -Physics.NPCWalkingOnSpeed)\n            {\n                NPC[A].Location.SpeedX += 0.05_n;\n                if(!NPC[A].Projectile)\n                    NPC[A].Location.SpeedX += 0.1_n;\n            }\n        }\n        else if(NPC[A].Type == NPCID_KNIGHT)\n        {\n            if(NPC[A].Location.SpeedX < 2 && NPC[A].Location.SpeedX > -2)\n            {\n                if(!NPC[A].Projectile)\n                    NPC[A].Location.SpeedX = 2 * NPC[A].Direction;\n            }\n\n            if(NPC[A].Location.SpeedX > 2)\n                NPC[A].Location.SpeedX -= 0.05_n;\n            else if(NPC[A].Location.SpeedX < -2)\n                NPC[A].Location.SpeedX += 0.05_n;\n        }\n        else if(!(NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4 && NPC[A].Projectile))\n        {\n            if(NPC[A].Location.SpeedX < Physics.NPCWalkingSpeed && NPC[A].Location.SpeedX > -Physics.NPCWalkingSpeed)\n            {\n                if(!NPC[A].Projectile)\n                    NPC[A].Location.SpeedX = Physics.NPCWalkingSpeed * NPC[A].Direction;\n            }\n\n            if(NPC[A].Location.SpeedX > Physics.NPCWalkingSpeed)\n                NPC[A].Location.SpeedX -= 0.05_n;\n            else if(NPC[A].Location.SpeedX < -Physics.NPCWalkingSpeed)\n                NPC[A].Location.SpeedX += 0.05_n;\n        }\n    }\n    else if(NPC[A].Type == NPCID_FLIER)\n    {\n        if(NPC[A].Location.SpeedX > -2 && NPC[A].Location.SpeedX < 2)\n            NPC[A].Location.SpeedX = 2 * NPC[A].Direction;\n    }\n    else if(NPC[A].Type == NPCID_ROCKET_FLIER)\n    {\n        if(NPC[A].Location.SpeedX > -2.5_n && NPC[A].Location.SpeedX < 2.5_n)\n            NPC[A].Location.SpeedX = 2.5_n * NPC[A].Direction;\n    }\n    // Slow things down that shouldnt move\n    else if(NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_CANNONITEM || NPC[A].Type == NPCID_JUMPER_S3 || NPC[A].Type == NPCID_SPRING ||\n            NPC[A].Type == NPCID_KEY || NPC[A].Type == NPCID_COIN_SWITCH || NPC[A].Type == NPCID_TIME_SWITCH || NPC[A].Type == NPCID_TNT ||\n            NPC[A].Type == NPCID_GRN_BOOT || NPC[A].Type == NPCID_RED_BOOT || NPC[A].Type == NPCID_BLU_BOOT ||\n            (NPC[A].Type == NPCID_SPIT_BOSS_BALL && NPC[A].Projectile) || NPC[A].Type == NPCID_TOOTHYPIPE || NPC[A].Type == NPCID_METALBARREL ||\n            NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT || NPC[A].Type == NPCID_VPIPE_LONG ||\n            (NPCIsVeggie(NPC[A].Type) && !NPC[A].Projectile) ||\n            (NPC[A].Type == NPCID_HEAVY_THROWER && NPC[A].Projectile) ||\n            /*(NPC[A].Projectile && (NPC[A].Type == NPCID_FLY && NPC[A].Type == NPCID_MINIBOSS)) ||*/ // FIXME: This segment is always false (type equal both 54 and 15, impossible!) [PVS Studio]\n            NPC[A].Type == NPCID_CIVILIAN_SCARED || NPC[A].Type == NPCID_STATUE_S3 || NPC[A].Type == NPCID_STATUE_S4 || NPC[A].Type == NPCID_CIVILIAN ||\n            NPC[A].Type == NPCID_CHAR3 || NPC[A].Type == NPCID_ITEM_POD || NPC[A].Type == NPCID_BOMB || NPC[A].Type == NPCID_LIT_BOMB_S3 ||\n            NPC[A].Type == NPCID_CHAR2 || NPC[A].Type == NPCID_CHAR5 || (NPCIsYoshi(NPC[A].Type) && NPC[A].Special == 0) ||\n            (NPC[A].Type >= NPCID_CARRY_BLOCK_A && NPC[A].Type <= NPCID_CARRY_BLOCK_D) || NPC[A].Type == NPCID_HIT_CARRY_FODDER || (NPC[A].Type == NPCID_SPIT_BOSS && NPC[A].Projectile) ||\n            NPC[A].Type == NPCID_HEAVY_POWER || NPC[A].Type == NPCID_STATUE_POWER || NPC[A].Type == NPCID_FIRE_POWER_S4 || NPC[A].Type == NPCID_3_LIFE ||\n            NPC[A].Type == NPCID_STAR_EXIT || NPC[A].Type == NPCID_STAR_COLLECT || NPC[A].Type == NPCID_FIRE_POWER_S1 || NPC[A].Type == NPCID_TIMER_S2 ||\n            NPC[A].Type == NPCID_EARTHQUAKE_BLOCK || NPC[A].Type == NPCID_POWER_S2 || NPC[A].Type == NPCID_POWER_S5 || NPC[A].Type == NPCID_FLY_POWER ||\n            NPC[A].Type == NPCID_LOCK_DOOR || NPC[A].Type == NPCID_FLY_BLOCK || NPC[A].Type == NPCID_FLY_CANNON || NPC[A].Type == NPCID_ICE_POWER_S4 ||\n            NPC[A].Type == NPCID_ICE_POWER_S3 || NPC[A].Type == NPCID_DOOR_MAKER || NPC[A].Type == NPCID_QUAD_SPITTER || NPC[A].Type == NPCID_AQUATIC_POWER ||\n            NPC[A].Type == NPCID_POLAR_POWER || NPC[A].Type == NPCID_SHELL_POWER)\n    {\n        if(NPC[A].Location.SpeedX > 0)\n            NPC[A].Location.SpeedX -= 0.05_n;\n        else if(NPC[A].Location.SpeedX < 0)\n            NPC[A].Location.SpeedX += 0.05_n;\n\n        if(NPC[A].Location.SpeedX >= -0.05_n && NPC[A].Location.SpeedX <= 0.05_n)\n            NPC[A].Location.SpeedX = 0;\n\n        if(NPC[A].Location.SpeedY >= -Physics.NPCGravity && NPC[A].Location.SpeedY <= Physics.NPCGravity)\n        {\n            if(NPC[A].Location.SpeedX > 0)\n                NPC[A].Location.SpeedX -= 0.3_n;\n            else if(NPC[A].Location.SpeedX < 0)\n                NPC[A].Location.SpeedX += 0.3_n;\n\n            if(NPC[A].Location.SpeedX >= -0.3_n && NPC[A].Location.SpeedX <= 0.3_n)\n                NPC[A].Location.SpeedX = 0;\n        }\n    }\n#if 0\n    // dead code because NPCDefaultMovement(NPCID_TANK_TREADS) is true\n    else if(NPC[A].Type == NPCID_TANK_TREADS)\n    {\n        NPC[A].Projectile = true;\n        NPC[A].Direction = NPC[A].DefaultDirection;\n        NPC[A].Location.SpeedX = 1 * NPC[A].DefaultDirection;\n\n        // the conditions here are outdated\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            if(!(Player[B].Effect == PLREFF_NORMAL || Player[B].Effect == PLREFF_WARP_PIPE))\n            {\n                NPC[A].Location.SpeedX = 0;\n                NPC[A].Location.SpeedY = 0;\n            }\n        }\n    }\n#endif\n    // Mushroom Movement Code\n    else if(NPC[A].Type == NPCID_POWER_S3 || NPC[A].Type == NPCID_SWAP_POWER || NPC[A].Type == NPCID_LIFE_S3 ||\n            NPC[A].Type == NPCID_POISON || NPC[A].Type == NPCID_POWER_S1 || NPC[A].Type == NPCID_POWER_S4 ||\n            NPC[A].Type == NPCID_LIFE_S1 || NPC[A].Type == NPCID_LIFE_S4 || NPC[A].Type == NPCID_BRUTE_SQUISHED ||\n            NPC[A].Type == NPCID_BIG_GUY || NPC[A].Type == NPCID_INVINCIBILITY_POWER || NPC[A].Type == NPCID_CYCLONE_POWER)\n    {\n        if(NPC[A].Direction == 0) // Move toward the closest player\n        {\n            int target_plr = NPCTargetPlayer(NPC[A]);\n\n            if(target_plr)\n                NPC[A].Direction = -Player[target_plr].Direction;\n        }\n\n        if(NPC[A].Location.SpeedX < Physics.NPCMushroomSpeed && NPC[A].Location.SpeedX > -Physics.NPCMushroomSpeed)\n        {\n            if(!NPC[A].Projectile)\n                NPC[A].Location.SpeedX = Physics.NPCMushroomSpeed * NPC[A].Direction;\n        }\n\n        if(NPC[A].Location.SpeedX > Physics.NPCMushroomSpeed)\n            NPC[A].Location.SpeedX -= 0.05_n;\n        else if(NPC[A].Location.SpeedX < -Physics.NPCMushroomSpeed)\n            NPC[A].Location.SpeedX += 0.05_n;\n    }\n    else if(NPC[A].Type == NPCID_RAINBOW_SHELL)\n    {\n        NPC[A].Projectile = true;\n\n        NPCFaceNearestPlayer(NPC[A]);\n\n        NPC[A].Location.SpeedX += 0.1_n * NPC[A].Direction;\n\n        if(NPC[A].Location.SpeedX < -4)\n            NPC[A].Location.SpeedX = -4;\n\n        if(NPC[A].Location.SpeedX > 4)\n            NPC[A].Location.SpeedX = 4;\n    }\n    // Yoshi Fireball\n    else if(NPC[A].Type == NPCID_PET_FIRE)\n    {\n        NPC[A].Projectile = true;\n        if(NPC[A].Location.SpeedX == 0)\n            NPC[A].Location.SpeedX = 5 * NPC[A].Direction;\n    }\n    // bully\n    else if(NPC[A].Type == NPCID_CHASER)\n    {\n        if(!NPC[A].Projectile && NPC[A].Special2 == 0)\n        {\n            NPCFaceNearestPlayer(NPC[A]);\n\n            NPC[A].Location.SpeedX += 0.05_n * NPC[A].Direction;\n\n            if(NPC[A].Location.SpeedX >= 3)\n                NPC[A].Location.SpeedX = 3;\n\n            if(NPC[A].Location.SpeedX <= -3)\n                NPC[A].Location.SpeedX = -3;\n        }\n        else\n        {\n            if(NPC[A].Location.SpeedX > 0.1_n)\n                NPC[A].Location.SpeedX -= 0.075_n;\n            else if(NPC[A].Location.SpeedX < -0.1_n)\n                NPC[A].Location.SpeedX += 0.075_n;\n\n            if(NPC[A].Location.SpeedX >= -0.1_n && NPC[A].Location.SpeedX <= 0.1_n)\n                NPC[A].Special2 = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_RAFT)\n    {\n        if(NPC[A].Special == 1)\n            NPC[A].Location.SpeedX = 2 * NPC[A].Direction;\n    }\n    // Big Koopa Movement Code\n    else if(NPC[A].Type == NPCID_MINIBOSS)\n    {\n        if(NPC[A].Location.SpeedX < 0)\n            NPC[A].Direction = -1;\n        else\n            NPC[A].Direction = 1;\n\n        if(NPC[A].Special == 0 || NPC[A].Special == 3)\n        {\n            if(NPC[A].Location.SpeedX < 3.5_n && NPC[A].Location.SpeedX > -3.5_n)\n                NPC[A].Location.SpeedX += (0.1_n * NPC[A].Direction);\n\n            if(NPC[A].Location.SpeedX > 3.5_n)\n                NPC[A].Location.SpeedX -= 0.05_n;\n            else if(NPC[A].Location.SpeedX < -3.5_n)\n                NPC[A].Location.SpeedX += 0.05_n;\n\n            if(NPC[A].Special == 3)\n                NPC[A].Location.SpeedY = -6;\n        }\n        else if(NPC[A].Special == 2)\n            NPC[A].Location.SpeedX += (0.2_n * NPC[A].Direction);\n        else if(NPC[A].Special == 3)\n            NPC[A].Location.SpeedY = -6;\n        else\n        {\n            if(NPC[A].Location.SpeedX > 0)\n                NPC[A].Location.SpeedX -= 0.05_n;\n            else if(NPC[A].Location.SpeedX < 0)\n                NPC[A].Location.SpeedX += 0.05_n;\n\n            if(NPC[A].Location.SpeedX > -0.5_n && NPC[A].Location.SpeedX < 0.5_n)\n                NPC[A].Location.SpeedX = 0.0001_n * NPC[A].Direction;\n        }\n    }\n    // spiney eggs\n    else if(NPC[A].Type == NPCID_SPIKY_BALL_S3)\n    {\n        if(NPC[A].CantHurt > 0)\n        {\n            NPC[A].Projectile = true;\n            NPC[A].CantHurt = 100;\n        }\n        else\n        {\n            NPCFaceNearestPlayer(NPC[A]);\n\n            if(NPC[A].Direction == 1 && NPC[A].Location.SpeedX < 4)\n                NPC[A].Location.SpeedX += 0.04_n;\n            if(NPC[A].Direction == -1 && NPC[A].Location.SpeedX > -4)\n                NPC[A].Location.SpeedX -= 0.04_n;\n        }\n    }\n    else if(NPC[A].Type == NPCID_BULLET || NPC[A].Type == NPCID_BIG_BULLET)\n    {\n        if(NPC[A].CantHurt < 1000)\n            NPC[A].Location.SpeedX = 4 * NPC[A].Direction;\n    }\n    else if(NPC[A].Type == NPCID_GHOST_FAST)\n        NPC[A].Location.SpeedX = 2 * NPC[A].Direction;\n\n    // yoshi\n    if(NPCIsYoshi(NPC[A].Type))\n    {\n        if(NPC[A].Special == 0)\n        {\n            if(NPC[A].Location.SpeedY == 0 || NPC[A].Slope > 0)\n            {\n                if(NPC[A].Wet == 0)\n                    NPC[A].Location.SpeedY = -2.1_n;\n                else\n                    NPC[A].Location.SpeedY = -1.1_n;\n            }\n        }\n        else\n        {\n            if(NPC[A].Location.SpeedX < 3 && NPC[A].Location.SpeedX > -3)\n            {\n                if(!NPC[A].Projectile)\n                    NPC[A].Location.SpeedX = 3 * NPC[A].Direction;\n            }\n        }\n    }\n\n    if(NPC[A].Type != NPCID_SPIT_BOSS && NPC[A].Type != NPCID_FALL_BLOCK_RED && NPC[A].Type != NPCID_FALL_BLOCK_BROWN && NPC[A].Type != NPCID_VEHICLE &&\n       NPC[A].Type != NPCID_CONVEYOR && NPC[A].Type != NPCID_YEL_PLATFORM && NPC[A].Type != NPCID_BLU_PLATFORM && NPC[A].Type != NPCID_GRN_PLATFORM &&\n       NPC[A].Type != NPCID_RED_PLATFORM && NPC[A].Type != NPCID_STATUE_S3 && NPC[A].Type != NPCID_STATUE_S4 && NPC[A].Type != NPCID_STATUE_FIRE &&\n       NPC[A].Type != NPCID_CANNONITEM && NPC[A].Type != NPCID_TOOTHYPIPE && NPC[A].Type != NPCID_TOOTHY && !(NPC[A].Type >= NPCID_PLATFORM_S3 &&\n       NPC[A].Type <= NPCID_PLATFORM_S1))\n    {\n        if(NPC[A].Location.SpeedX < 0) // Find the NPCs direction\n            NPC[A].Direction = -1;\n        else if(NPC[A].Location.SpeedX > 0)\n            NPC[A].Direction = 1;\n    }\n\n    // Reset Speed when no longer a projectile\n    // If Not (NPCIsAShell(.Type) Or .Type = 8 Or .Type = 93 Or .Type = 74 Or .Type = 51 Or .Type = 52 Or .Type = 12 Or .Type = 14 Or .Type = 13 Or .Type = 15 Or NPCIsABonus(.Type) Or .Type = 17 Or .Type = 18 Or .Type = 21 Or .Type = 22 Or .Type = 25 Or .Type = 26 Or .Type = 29 Or .Type = 30 Or .Type = 31 Or .Type = 32 Or .Type = 35 Or .Type = 37 Or .Type = 38 Or .Type = 39 Or .Type = 40 Or .Type = 42 Or .Type = 43 Or .Type = 44 Or .Type = 45 Or .Type = 46 Or .Type = 47 Or .Type = 48 Or .Type = 76 Or .Type = 49 Or .Type = 54 Or .Type = 56 Or .Type = 57 Or .Type = 58 Or .Type = 60 Or .Type = 62 Or .Type = 64 Or .Type = 66 Or .Type = 67 Or .Type = 68 Or .Type = 69 Or .Type = 70 Or .Type = 78 Or .Type = 84 Or .Type = 85 Or .Type = 87 Or (.Type = 55 And .Special > 0) Or (.Type >= 79 And .Type <= 83) Or .Type = 86 Or .Type = 92 Or .Type = 94 Or NPCIsYoshi(.Type) Or .Type = 96 Or .Type = 101 Or .Type = 102) And .Projectile = False Then\n    if(use_default_movement && !NPC[A].Projectile)\n    {\n        if(!NPC[A]->CanWalkOn)\n        {\n            if(NPC[A]->CanWalkOn)\n                NPC[A].Location.SpeedX = Physics.NPCWalkingOnSpeed * NPC[A].Direction;\n            else if(NPC[A].Type == NPCID_KNIGHT)\n                NPC[A].Location.SpeedX = 2 * NPC[A].Direction;\n            else\n                NPC[A].Location.SpeedX = Physics.NPCWalkingSpeed * NPC[A].Direction;\n\n\n            if((NPC[A]->IsFish && NPC[A].Special != 1) && !NPC[A].Projectile)\n            {\n                // NOTE: SpeedX was previously stored in Special3. That value was only read here (fish with Special == 1 use Special3 in a different way).\n                if(NPC[A].Wet == 0)\n                {\n                    if(NPC[A].Special5 >= 0)\n                        NPC[A].Special2 -= 1;\n                }\n                else\n                {\n                    NPC[A].Special2 = 6;\n                    NPC[A].SpecialX = NPC[A].Location.SpeedX;\n                }\n\n                if(NPC[A].Special2 <= 0)\n                {\n                    NPC[A].SpecialX = NPC[A].SpecialX * 0.99_r;\n                    if(NPC[A].SpecialX > -0.1_n && NPC[A].SpecialX < 0.1_n)\n                        NPC[A].SpecialX = 0;\n                    NPC[A].Location.SpeedX = NPC[A].SpecialX;\n                }\n            }\n\n            if(NPC[A]->IsFish && NPC[A].Special == 1 && !NPC[A].Projectile)\n                NPC[A].Location.SpeedX = Physics.NPCWalkingOnSpeed * 2 * NPC[A].Direction;\n        }\n\n        // moved into here from immediately below, use_def_mov is certainly true for NPCID_WALK_BOMB_S2 (cross-ref NPCDefaultMovement)\n        if(NPC[A].Type == NPCID_WALK_BOMB_S2 /* && !NPC[A].Projectile */ && NPC[A].Special2 == 1)\n            NPC[A].Location.SpeedX = 0;\n    }\n\n\n    // NPC Gravity\n    if(!NPC[A]->NoGravity)\n    {\n        // POSSIBLE SUBROUTINE: calcGravity\n\n        if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL)\n        {\n            NPC[A].CantHurt = 100;\n            if(NPC[A].Special < 2)\n                NPC[A].Location.SpeedY += Physics.NPCGravity * 1.5_rb;\n            else if(NPC[A].Special == 3)\n            {\n                // peach fireball changes\n                NPC[A].Location.SpeedY += Physics.NPCGravity * 0.9_r;\n                if(NPC[A].Location.SpeedX > 3)\n                    NPC[A].Location.SpeedX -= 0.04_n;\n                else if(NPC[A].Location.SpeedX < -3)\n                    NPC[A].Location.SpeedX += 0.04_n;\n            }\n            else if(NPC[A].Special == 4)\n            {\n\n                // toad fireball changes\n                NPC[A].Location.SpeedY += Physics.NPCGravity * 1.3_r;\n                if(NPC[A].Location.SpeedX < 8 && NPC[A].Location.SpeedX > 0)\n                    NPC[A].Location.SpeedX += 0.03_n;\n                else if(NPC[A].Location.SpeedX > -8 && NPC[A].Location.SpeedX < 0)\n                    NPC[A].Location.SpeedX -= 0.03_n;\n            }\n            else if(NPC[A].Special == 5) // link fireballs float\n            {\n            }\n            else\n                NPC[A].Location.SpeedY += Physics.NPCGravity * 1.3_r;\n\n        }\n        else if(NPC[A].Type == NPCID_BULLET || NPC[A].Type == NPCID_BIG_BULLET)\n            NPC[A].Location.SpeedY = 0;\n\n        else if((NPC[A]->IsFish && NPC[A].Special == 2) && !NPC[A].Projectile)\n        {\n            if(NPC[A].Special5 == 1)\n            {\n                if(NPC[A].Location.Y > NPC[A].DefaultLocationY)\n                    NPC[A].Location.SpeedY = -4 - (NPC[A].Location.Y - NPC[A].DefaultLocationY) / 50;\n                else\n                    NPC[A].Special5 = 0;\n            }\n            else\n            {\n                // If .Location.SpeedY < 2 + (.Location.Y - .DefaultLocationY) * 0.02 Then\n                NPC[A].Location.SpeedY += Physics.NPCGravity * 0.4_r;\n                // End If\n            }\n        }\n        else if(NPC[A].Type != NPCID_RED_VINE_TOP_S3 && NPC[A].Type != NPCID_GRN_VINE_TOP_S3 && NPC[A].Type != NPCID_GRN_VINE_TOP_S4 &&\n                !(NPC[A]->IsFish && NPC[A].Special == 2) && NPC[A].Type != NPCID_HOMING_BALL &&\n                NPC[A].Type != NPCID_HOMING_BALL_GEN && NPC[A].Type != NPCID_SPIT_GUY_BALL && NPC[A].Type != NPCID_STAR_EXIT && NPC[A].Type != NPCID_STAR_COLLECT &&\n                NPC[A].Type != NPCID_VILLAIN_FIRE && NPC[A].Type != NPCID_PLANT_S3 && NPC[A].Type != NPCID_FIRE_PLANT && NPC[A].Type != NPCID_PLANT_FIREBALL &&\n                NPC[A].Type != NPCID_PLANT_S1 && NPC[A].Type != NPCID_BIG_PLANT && NPC[A].Type != NPCID_LONG_PLANT_UP && NPC[A].Type != NPCID_LONG_PLANT_DOWN &&\n                !NPCIsAParaTroopa(NPC[A].Type) && NPC[A].Type != NPCID_BOTTOM_PLANT && NPC[A].Type != NPCID_SIDE_PLANT &&\n                NPC[A].Type != NPCID_LEAF_POWER && NPC[A].Type != NPCID_STONE_S3 && NPC[A].Type != NPCID_STONE_S4 && NPC[A].Type != NPCID_GHOST_S3 &&\n                NPC[A].Type != NPCID_GHOST_FAST && NPC[A].Type != NPCID_GHOST_S4 && NPC[A].Type != NPCID_BIG_GHOST && NPC[A].Type != NPCID_SPIKY_THROWER &&\n                NPC[A].Type != NPCID_VEHICLE && NPC[A].Type != NPCID_CONVEYOR && NPC[A].Type != NPCID_YEL_PLATFORM &&\n                NPC[A].Type != NPCID_BLU_PLATFORM && NPC[A].Type != NPCID_GRN_PLATFORM && NPC[A].Type != NPCID_RED_PLATFORM &&\n                NPC[A].Type != NPCID_STATUE_FIRE && !(NPC[A]->IsACoin && NPC[A].Special == 0) &&\n                NPC[A].Type != NPCID_CHECKER_PLATFORM && NPC[A].Type != NPCID_PLATFORM_S1 && NPC[A].Type != NPCID_PET_FIRE &&\n                NPC[A].Type != NPCID_GOALTAPE && NPC[A].Type != NPCID_LAVA_MONSTER && NPC[A].Type != NPCID_FLIER &&\n                NPC[A].Type != NPCID_ROCKET_FLIER &&\n                ((NPC[A].Type != NPCID_WALL_BUG && NPC[A].Type != NPCID_WALL_SPARK && NPC[A].Type != NPCID_WALL_TURTLE)) &&\n                NPC[A].Type != NPCID_BOSS_FRAGILE && NPC[A].Type != NPCID_ITEM_BURIED &&\n                NPC[A].Type != NPCID_MAGIC_BOSS_BALL && NPC[A].Type != NPCID_JUMP_PLANT && NPC[A].Type != NPCID_LOCK_DOOR)\n        {\n            if(NPC[A].Type != NPCID_BAT && NPC[A].Type != NPCID_VINE_BUG && NPC[A].Type != NPCID_QUAD_BALL && NPC[A].Type != NPCID_FIRE_BOSS_FIRE && NPC[A].Type != NPCID_ITEM_BUBBLE && NPC[A].Type != NPCID_ITEM_THROWER && NPC[A].Type != NPCID_MAGIC_DOOR && NPC[A].Type != NPCID_COCKPIT && NPC[A].Type != NPCID_CHAR3_HEAVY && NPC[A].Type != NPCID_CHAR4_HEAVY) // no gravity\n            {\n                if(NPC[A]->IsFish && NPC[A].Special == 4 && !NPC[A].Projectile)\n                    NPC[A].Location.SpeedX = 0;\n                if(NPC[A].Wet == 2 && (NPC[A].Type == NPCID_RAFT))\n                    NPC[A].Location.SpeedY += -Physics.NPCGravity / 2;\n                else if(NPC[A].Wet == 2 && NPC[A]->IsFish && NPC[A].Special != 2 && !NPC[A].Projectile) // Fish cheep\n                {\n                    if((NPC[A].Location.X < NPC[A].DefaultLocationX - 100 && NPC[A].Direction == -1) || (NPC[A].Location.X > NPC[A].DefaultLocationX + 100 && NPC[A].Direction == 1))\n                    {\n                        if(NPC[A].Special == 3)\n                            NPC[A].TurnAround = true;\n                    }\n\n                    if(NPC[A].Special == 4)\n                    {\n\n\n                        if(NPC[A].Location.SpeedY == 0)\n                            NPC[A].Special4 = 1;\n                        if(NPC[A].Location.SpeedY == 0.01_n)\n                            NPC[A].Special4 = 0;\n\n                        NPC[A].Location.SpeedX = 0;\n                        if(NPC[A].Location.SpeedY > 2)\n                            NPC[A].Location.SpeedY = 2;\n                        if(NPC[A].Location.SpeedY < -2)\n                            NPC[A].Location.SpeedY = -2;\n                        if(NPC[A].Location.Y > NPC[A].DefaultLocationY + 25)\n                            NPC[A].Special4 = 1;\n                        else if(NPC[A].Location.Y < NPC[A].DefaultLocationY - 25)\n                            NPC[A].Special4 = 0;\n                        if(NPC[A].Special4 == 0)\n                            NPC[A].Location.SpeedY += 0.05_n;\n                        else\n                            NPC[A].Location.SpeedY -= 0.05_n;\n                    }\n                    else\n                    {\n                        if(NPC[A].Special4 == 0)\n                        {\n                            NPC[A].Location.SpeedY -= 0.025_n;\n                            if(NPC[A].Location.SpeedY <= -1)\n                                NPC[A].Special4 = 1;\n                            if(NPC[A].Special == 3 && NPC[A].Location.SpeedY <= -0.5_n)\n                                NPC[A].Special4 = 1;\n                        }\n                        else\n                        {\n                            NPC[A].Location.SpeedY += 0.025_n;\n                            if(NPC[A].Location.SpeedY >= 1)\n                                NPC[A].Special4 = 0;\n                            if(NPC[A].Special == 3 && NPC[A].Location.SpeedY >= 0.5_n)\n                                NPC[A].Special4 = 0;\n                        }\n                    }\n                }\n                else if(NPC[A]->IsFish && NPC[A].Special == 1 && NPC[A].Special5 == 1)\n                    NPC[A].Location.SpeedY += Physics.NPCGravity * 0.6_r;\n                else if(NPC[A].Type == NPCID_FLY_BLOCK || (g_config.fix_flamethrower_gravity && NPC[A].Type == NPCID_FLY_CANNON))\n                {\n                    NPC[A].Location.SpeedY += Physics.NPCGravity * 0.75_rb;\n                    if(NPC[A].Location.SpeedY > Physics.NPCGravity * 15)\n                        NPC[A].Location.SpeedY = Physics.NPCGravity * 15;\n                }\n                else if(NPC[A].Type != NPCID_FIRE_DISK && NPC[A].Type != NPCID_FIRE_CHAIN)\n                    NPC[A].Location.SpeedY += Physics.NPCGravity;\n            }\n        }\n\n\n        if(NPC[A].Type == NPCID_CHAR3_HEAVY)\n        {\n            NPC[A].Location.SpeedY += Physics.NPCGravity * 0.8_r;\n            // If .Location.SpeedY >= 5 Then .Location.SpeedY = 5\n            if(NPC[A].Location.SpeedX < -0.005_n)\n                NPC[A].Location.SpeedX += 0.02_n;\n            else if(NPC[A].Location.SpeedX > 0.005_n)\n                NPC[A].Location.SpeedX -= 0.02_n;\n            else\n                NPC[A].Location.SpeedX = 0;\n        }\n    }\n    else if(NPC[A].Projectile)\n    {\n        NPC[A].Location.SpeedY = NPC[A].Location.SpeedY * 0.95_r;\n        if(NPC[A].Location.SpeedY > -0.1_n && NPC[A].Location.SpeedY < 0.1_n)\n        {\n            NPC[A].Projectile = false;\n            NPC[A].Location.SpeedY = 0;\n        }\n    }\n\n    if(NPC[A].Location.SpeedY >= 8 && NPC[A].Type != NPCID_FIRE_DISK && NPC[A].Type != NPCID_FIRE_CHAIN)\n        NPC[A].Location.SpeedY = 8;\n\n    // POSSIBLE SUBROUTINE: preMovement\n\n    NPCSpecial(A);\n\n    // there was lots of speed cancel code (and some TheXTech logic for the Raft NPC) here; moved into NPCSpecial\n\n    if(NPC[A].Type == NPCID_STACKER && !NPC[A].Projectile)\n    {\n        speedVar = speedVar * 7 / 10;\n        if(NPC[A].Special2 < 2)\n        {\n            speedVar = speedVar * 7 / 10;\n            NPC[A].Special2 += 1;\n        }\n    }\n\n    // POSSIBLE SUBROUTINE: applyMovement\n\n    // Dont move\n    if(NPC[A].Stuck && !NPC[A].Projectile && NPC[A].Type != NPCID_LEAF_POWER) // face closest player\n    {\n        NPC[A].Location.SpeedX = 0;\n        if(!(NPC[A].Type == NPCID_SKELETON && NPC[A].Special > 0))\n            NPCFaceNearestPlayer(NPC[A]);\n    }\n\n    // Actual Movement (SpeedX / SpeedY application code)\n    if(NPC[A].Wings)\n    {\n        // don't do anything here, the movement will be applied after SpecialNPC\n    }\n    else if(/*(!NPCIsAnExit(NPC[A]) || NPC[A].Type == NPCID_STAR_EXIT || NPC[A].Type == NPCID_STAR_COLLECT) */\n        NPC[A].Type != NPCID_ITEMGOAL && NPC[A].Type != NPCID_GOALORB_S3 && NPC[A].Type != NPCID_GOALORB_S2 && NPC[A].Type != NPCID_FLAG_EXIT &&\n        NPC[A].Type != NPCID_FIRE_POWER_S3 && NPC[A].Type != NPCID_CONVEYOR)\n    {\n        // ParaTroopa speed application happens in SpecialNPC, buried item can't move at all\n        if(!NPCIsAParaTroopa(NPC[A].Type) && NPC[A].Type != NPCID_ITEM_BURIED)\n        {\n            NPC[A].Location.X += NPC[A].Location.SpeedX.times((num_t)speedVar);\n            NPC[A].Location.Y += NPC[A].Location.SpeedY;\n        }\n    }\n    else\n    {\n        if(!(NPC[A].Location.X == NPC[A].DefaultLocationX && NPC[A].Location.Y == NPC[A].DefaultLocationY) || NPC[A].Type == NPCID_FIRE_POWER_S3)\n        {\n            NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.99_r;\n            NPC[A].Location.X += NPC[A].Location.SpeedX;\n            NPC[A].Location.Y += NPC[A].Location.SpeedY;\n            if(!NPC[A].Projectile)\n                NPC[A].Location.SpeedX = 0;\n        }\n    }\n    // End If 'end of freezenpcs\n\n\n    // POSSIBLE SUBROUTINE: postMovement\n\n    // projectile checks moved into SpecialNPC\n\n    // Special NPCs code\n    SpecialNPC(A);\n\n    // Wings movement code\n    if(NPC[A].Wings)\n    {\n        // reset speed to its old value\n        if(NPC[A].Wings != WING_OVERRIDE)\n        {\n            NPC[A].Location.SpeedX = Wings_SpeedX;\n            NPC[A].Location.SpeedY = Wings_SpeedY;\n        }\n\n        // do wings movement!\n        NPCMovementLogic_Wings(A, (num_t)speedVar);\n    }\n}\n\nvoid NPCSectionWrap(NPC_t& npc)\n{\n    if((LevelWrap[npc.Section] || LevelVWrap[npc.Section]) && npc.Type != NPCID_HEAVY_THROWN && npc.Type != NPCID_PET_FIRE) // Level wraparound\n    {\n        if(LevelWrap[npc.Section])\n        {\n            if(npc.Location.X + npc.Location.Width < level[npc.Section].X)\n                npc.Location.X = level[npc.Section].Width - 1;\n            else if(npc.Location.X > level[npc.Section].Width)\n                npc.Location.X = level[npc.Section].X - npc.Location.Width + 1;\n        }\n\n        if(LevelVWrap[npc.Section])\n        {\n            if(npc.Location.Y + npc.Location.Height < level[npc.Section].Y)\n                npc.Location.Y = level[npc.Section].Height - 1;\n            else if(npc.Location.Y > level[npc.Section].Height)\n                npc.Location.Y = level[npc.Section].Y - npc.Location.Height + 1;\n        }\n    }\n}\n\nvoid NPCMovementLogic_Wings(int A, const num_t speedVar)\n{\n    WingBehaviors behavior = (NPC[A].Wings) ? NPC[A].Wings : (WingBehaviors)NPC[A].Special;\n\n    if(NPC[A].Wings && NPC[A].Projectile && !NPC[A]->IsAShell)\n    {\n        NPC[A].Location.SpeedY += Physics.NPCGravity;\n\n        if(num_t::abs(NPC[A].Location.SpeedY) < 0.5_n)\n            NPC[A].Projectile = false;\n    }\n    else if(behavior == WING_CHASE || behavior == WING_PARA_CHASE || behavior == WING_FLEE) // chase\n    {\n        if(NPC[A].CantHurt > 0)\n            NPC[A].CantHurt = 100;\n\n        NPC[A].Projectile = false;\n\n        int target_plr = 0;\n        num_t min_dist = 0;\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            if(!Player[B].Dead && Player[B].Section == NPC[A].Section && Player[B].TimeToLive == 0 && NPC[A].CantHurtPlayer != B)\n            {\n                num_t dist = NPCPlayerTargetDist(NPC[A], Player[B]);\n                if(min_dist == 0 || dist < min_dist)\n                {\n                    min_dist = dist;\n                    target_plr = B;\n                }\n            }\n        }\n\n        if(NPC[A].Wings && target_plr == 0 && NPC[A].CantHurtPlayer)\n            target_plr = NPC[A].CantHurtPlayer;\n\n        if(target_plr > 0)\n        {\n            int D;\n            if(NPC[A].Location.to_right_of(Player[target_plr].Location))\n                D = -1;\n            else\n                D = 1;\n\n            NPC[A].Direction = D;\n            int E = 0; // X\n            int F_div = -1; // Y\n\n            if(NPC[A].Location.Y > Player[target_plr].Location.Y)\n                F_div = -1;\n            else if(NPC[A].Location.Y < Player[target_plr].Location.Y - 128)\n                F_div = 1;\n\n            if(NPC[A].Location.X > Player[target_plr].Location.X + Player[target_plr].Location.Width + 64)\n                E = -1;\n            else if(NPC[A].Location.X + NPC[A].Location.Width + 64 < Player[target_plr].Location.X)\n                E = 1;\n\n            if(NPC[A].Location.X + NPC[A].Location.Width + 150 > Player[target_plr].Location.X && NPC[A].Location.X - 150 < Player[target_plr].Location.X + Player[target_plr].Location.Width)\n            {\n                if(NPC[A].Location.Y > Player[target_plr].Location.Y + Player[target_plr].Location.Height)\n                {\n\n                    // If Player(C).Location.SpeedX + NPC(Player(C).StandingOnNPC).Location.SpeedX > 0 And .Location.X + .Location.Width / 2 > Player(C).Location.X + Player(C).Location.Width / 2 Then\n                        // E = -D\n                    // ElseIf Player(C).Location.SpeedX + NPC(Player(C).StandingOnNPC).Location.SpeedX <= 0 And .Location.X + .Location.Width / 2 < Player(C).Location.X + Player(C).Location.Width / 2 Then\n                        E = -D;\n                    // End If\n                    if(NPC[A].Location.Y < Player[target_plr].Location.Y + Player[target_plr].Location.Height + 160)\n                    {\n                        if(NPC[A].Location.X + NPC[A].Location.Width + 100 > Player[target_plr].Location.X && NPC[A].Location.X - 100 < Player[target_plr].Location.X + Player[target_plr].Location.Width)\n                            F_div = 5;\n                    }\n                }\n                else\n                {\n                    E = D;\n                    F_div = 1;\n                }\n            }\n\n            num_t factor = (NPC[A].Wet == 2) ? 0.025_n : 0.05_n;\n            if(behavior == WING_FLEE && min_dist < (g_config.fix_multiplayer_targeting ? 122500_n : 350_n))\n                factor = -factor;\n\n            NPC[A].Location.SpeedX += factor * E;\n            NPC[A].Location.SpeedY += factor / F_div;\n\n            if(NPC[A].Location.SpeedX > 4)\n                NPC[A].Location.SpeedX = 4;\n            else if(NPC[A].Location.SpeedX < -4)\n                NPC[A].Location.SpeedX = -4;\n\n            if(NPC[A].Location.SpeedY > 3)\n                NPC[A].Location.SpeedY = 3;\n            else if(NPC[A].Location.SpeedY < -3)\n                NPC[A].Location.SpeedY = -3;\n        }\n    }\n    else if(behavior == WING_JUMP)\n    {\n        NPC[A].Location.SpeedY += Physics.NPCGravity;\n\n        if(NPC[A].Wings && NPC[A].Location.SpeedX)\n        {\n            if(NPC[A].Location.SpeedX < 0)\n                NPC[A].Location.SpeedX = -Physics.NPCWalkingSpeed;\n            else\n                NPC[A].Location.SpeedX = Physics.NPCWalkingSpeed;\n        }\n        else\n            NPC[A].Location.SpeedX = Physics.NPCWalkingSpeed * NPC[A].Direction;\n    }\n    else if(behavior == WING_LEFTRIGHT)\n    {\n        if(NPC[A].Wings)\n        {\n            if(NPC[A].Location.Y == NPC[A].DefaultLocationY && NPC[A].Location.SpeedY == 0)\n                NPC[A].Location.SpeedY = 1;\n\n            if(NPC[A].Location.Y > NPC[A].DefaultLocationY)\n                NPC[A].Location.SpeedY -= 0.02_n;\n            else if(NPC[A].Location.Y < NPC[A].DefaultLocationY)\n                NPC[A].Location.SpeedY += 0.02_n;\n\n            if(NPC[A].Location.SpeedY < -2)\n                NPC[A].Location.SpeedY += Physics.NPCGravity;\n            else if(num_t::abs(NPC[A].Location.SpeedY) >= 1)\n                NPC[A].Location.SpeedY *= 0.9921875_rb;\n        }\n        else\n        {\n            if(NPC[A].Special3 == 0)\n            {\n                NPC[A].Location.SpeedY += 0.05_n;\n                if(NPC[A].Location.SpeedY > 1)\n                    NPC[A].Special3 = 1;\n            }\n            else\n            {\n                NPC[A].Location.SpeedY -= 0.05_n;\n                if(NPC[A].Location.SpeedY < -1)\n                    NPC[A].Special3 = 0;\n            }\n        }\n\n        if(NPC[A].Location.X == NPC[A].DefaultLocationX && NPC[A].Location.SpeedX == 0)\n            NPC[A].Location.SpeedX = 2 * NPC[A].Direction;\n        if(NPC[A].Location.X < NPC[A].DefaultLocationX - 64)\n            NPC[A].Location.SpeedX += 0.02_n;\n        else if(NPC[A].Location.X > NPC[A].DefaultLocationX + 64)\n            NPC[A].Location.SpeedX -= 0.02_n;\n        else if(NPC[A].Direction == -1)\n            NPC[A].Location.SpeedX -= 0.02_n;\n        else if(NPC[A].Direction == 1)\n            NPC[A].Location.SpeedX += 0.02_n;\n\n        if(NPC[A].Location.SpeedX > 2)\n            NPC[A].Location.SpeedX = 2;\n        if(NPC[A].Location.SpeedX < -2)\n            NPC[A].Location.SpeedX = -2;\n    }\n    else if(behavior == WING_UPDOWN)\n    {\n        NPC[A].Location.SpeedX = 0;\n\n        if(NPC[A].Location.Y == NPC[A].DefaultLocationY && NPC[A].Location.SpeedY == 0)\n            NPC[A].Location.SpeedY = 2 * NPC[A].Direction;\n        if(NPC[A].Location.Y < NPC[A].DefaultLocationY - 64)\n            NPC[A].Location.SpeedY += 0.02_n;\n        else if(NPC[A].Location.Y > NPC[A].DefaultLocationY + 64)\n            NPC[A].Location.SpeedY -= 0.02_n;\n        else if(NPC[A].Location.SpeedY < 0)\n            NPC[A].Location.SpeedY -= 0.02_n;\n        else\n            NPC[A].Location.SpeedY += 0.02_n;\n\n        if(NPC[A].Location.SpeedY > 2)\n            NPC[A].Location.SpeedY = 2;\n        if(NPC[A].Location.SpeedY < -2)\n            NPC[A].Location.SpeedY = -2;\n\n        NPCFaceNearestPlayer(NPC[A], true);\n    }\n\n    if(NPC[A].Stuck && !NPC[A].Projectile)\n        NPC[A].Location.SpeedX = 0;\n\n    // slightly slower terminal falling velocity than normal (normal is 8)\n    if(NPC[A].Location.SpeedY >= 6)\n        NPC[A].Location.SpeedY = 6;\n\n    // apply speed\n    NPC[A].Location.X += NPC[A].Location.SpeedX.times(speedVar);\n    NPC[A].Location.Y += NPC[A].Location.SpeedY;\n    // deferring tree update to end of the NPC physics update\n}\n"
  },
  {
    "path": "src/npc/npc_update/npc_special_maybe_held.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"npc.h\"\n#include \"npc_traits.h\"\n#include \"npc/npc_queues.h\"\n#include \"npc/section_overlap.h\"\n#include \"sound.h\"\n#include \"effect.h\"\n#include \"eff_id.h\"\n#include \"config.h\"\n#include \"collision.h\"\n#include \"player.h\"\n#include \"layers.h\"\n#include \"blocks.h\"\n#include \"graphics.h\"\n#include \"player/player_update_priv.h\"\n\n#include \"main/trees.h\"\n\nvoid NPCSpecialMaybeHeld(int A)\n{\n    // Special Code for things that work while held\n    // perfectly factored by NPC Type\n    if(NPC[A].Type == NPCID_BOMB) // SMB2 Bomb\n    {\n        // If .Location.SpeedX < -2 Or .Location.SpeedX > 2 Or .Location.SpeedY < -2 Or .Location.SpeedY > 5 Then .Projectile = True\n        NPC[A].Special += 1;\n        if(NPC[A].Special > 250)\n            NPC[A].Special2 = 1;\n\n        if(NPC[A].Special >= 350 || NPC[A].Special < 0)\n        {\n            Bomb(NPC[A].Location, 2);\n            NPC[A].Killed = 9;\n            NPCQueues::Killed.push_back(A);\n        }\n    }\n    else if(NPC[A].Type == NPCID_WALK_BOMB_S2) // SMB2 Bob-om\n    {\n        NPC[A].Special += 1;\n        if(NPC[A].Special > 450)\n            NPC[A].Special2 = 1;\n        if(NPC[A].Special >= 550 || NPC[A].Special < 0)\n        {\n            Bomb(NPC[A].Location, 2);\n            NPC[A].Killed = 9;\n            NPCQueues::Killed.push_back(A);\n        }\n    }\n    else if(NPC[A].Type == NPCID_LIT_BOMB_S3) // SMB3 Bomb\n    {\n        if(!NPC[A].Inert)\n            NPC[A].Special += 1;\n        if(NPC[A].Special > 250)\n            NPC[A].Special2 = 1;\n        if(NPC[A].Special >= 350 || NPC[A].Special < 0)\n            Bomb(NPC[A].Location, 3);\n    }\n    else if(NPC[A].Type == NPCID_SKELETON)\n    {\n        if(NPC[A].Special > 0)\n        {\n            NPC[A].Special2 += 1;\n            if(NPC[A].Special2 >= 400 && NPC[A].Special3 == 0)\n            {\n                NPC[A].Special = 0;\n                NPC[A].Special2 = 0;\n                NPC[A].Inert = false;\n                NPC[A].Stuck = false;\n                NPC[A].Wings = NPC[A].DefaultWings;\n            }\n            else if(NPC[A].Special2 >= 300)\n            {\n                if(NPC[A].Special3 == 0)\n                {\n                    NPC[A].Location.X += 2;\n                    NPC[A].Special3 = 1;\n                }\n                else\n                {\n                    NPC[A].Location.X -= 2;\n                    NPC[A].Special3 = 0;\n                }\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_VILLAIN_S3) // smb3 bowser\n    {\n        // special5 - the player\n        // special4 - what bowser is doing\n        // special3 - counter for what bowser is doing\n        // specialY - counter for what bowser needs to do (was Special2)\n        if(NPC[A].Legacy)\n        {\n            if(NPC[A].TimeLeft > 1)\n            {\n                NPC[A].TimeLeft = 100;\n                if(bgMusic[NPC[A].Section] != 21)\n                {\n                    bgMusic[NPC[A].Section] = 21;\n                    StartMusicIfOnscreen(NPC[A].Section);\n                }\n            }\n        }\n\n        if(NPC[A].Special4 == 0)\n        {\n            NPC[A].Wings = NPC[A].DefaultWings;\n            NPC[A].Special3 = 0; // reset counter when done\n            if(NPC[A].Direction < 0)\n                NPC[A].Frame = 0;\n            else\n                NPC[A].Frame = 5;\n        }\n\n        if(NPC[A].Special5 == 0) // find player\n        {\n            bool tempBool = false;\n            for(int B = 1; B <= numPlayers; B++)\n            {\n                if(!Player[B].Dead && Player[B].TimeToLive == 0)\n                    tempBool = true;\n            }\n\n            if(!tempBool)\n            {\n                NPC[A].Special5 = 0;\n                NPC[A].Special4 = 2;\n            }\n            else\n            {\n                int B;\n                do\n                    B = iRand(numPlayers) + 1;\n                while(Player[B].Dead || Player[B].TimeToLive > 0);\n                NPC[A].Special5 = B;\n            }\n        }\n\n        // see if facing the player\n        // new: don't wait to face player if using wings movement\n        bool tempBool = NPC[A].Wings;\n        if(NPC[A].Special5 > 0)\n        {\n            if(NPC[A].Location.to_right_of(Player[NPC[A].Special5].Location))\n            {\n                if(NPC[A].Direction < 0)\n                    tempBool = true;\n            }\n            else\n            {\n                if(NPC[A].Direction > 0)\n                    tempBool = true;\n            }\n        }\n\n        NPC[A].SpecialY += dRand();\n\n        if(NPC[A].Special4 == 0 && tempBool)\n        {\n            num_t r1 = dRand();\n            num_t r2 = dRand();\n            if(NPC[A].SpecialY >= 200 + r1 * 400 - r2 * 200) // hop on player\n            {\n                if(NPC[A].SpecialY >= 200 + dRand() * 600)\n                    NPC[A].SpecialY = 0;\n                NPC[A].Special4 = 3;\n            }\n            // Even though this looks risky, it imposes strict sequencing semantics on the dRand() calls,\n            // matching TheXTech's lifelong logic, and smbx-experiments' logic since 2021\n            else if((NPC[A].SpecialY >= 80 && NPC[A].SpecialY <= 130) || (NPC[A].SpecialY >= 160 + dRand() * 300 && NPC[A].SpecialY <= 180 + dRand() * 800)) // shoot fireball\n                NPC[A].Special4 = 4;\n        }\n\n        if(NPC[A].Inert)\n        {\n            if(NPC[A].Special4 == 4 || NPC[A].Special4 == 3)\n                NPC[A].Special4 = 0;\n        }\n\n        if(NPC[A].Special4 == 0) // when not doing anything turn to player\n        {\n            if(!tempBool)\n            {\n                if(NPC[A].Direction > 0)\n                    NPC[A].Special4 = -1;\n                if(NPC[A].Direction < 0)\n                    NPC[A].Special4 = 1;\n            }\n        }\n\n        if(NPC[A].Special4 == 0) // hop\n            NPC[A].Special4 = 2;\n\n        if(NPC[A].HoldingPlayer > 0)\n        {\n            if(NPC[A].Direction == -1)\n                NPC[A].Frame = 0;\n            else\n                NPC[A].Frame = 5;\n            NPC[A].Special4 = 9000;\n        }\n        else if(NPC[A].Special4 == 9000)\n        {\n            NPC[A].Special5 = NPC[A].CantHurtPlayer;\n            NPC[A].Special4 = 0;\n            NPC[A].Location.SpeedX = 0;\n            NPC[A].Location.SpeedY = 0;\n        }\n\n        if(NPC[A].Special4 == -1) // turn left\n        {\n            NPC[A].Special3 -= 1;\n            if(NPC[A].Special3 > -5)\n                NPC[A].Frame = 9;\n            else if(NPC[A].Special3 > -10)\n                NPC[A].Frame = 8;\n            else if(NPC[A].Special3 > -15)\n                NPC[A].Frame = 12;\n            else if(NPC[A].Special3 > -20)\n                NPC[A].Frame = 3;\n            else if(NPC[A].Special3 > -25)\n                NPC[A].Frame = 4;\n            else\n            {\n                NPC[A].Special4 = 0;\n                NPC[A].Direction = -1;\n            }\n        }\n        else if(NPC[A].Special4 == 1) // turn right\n        {\n            NPC[A].Special3 += 1;\n            if(NPC[A].Special3 < 5)\n                NPC[A].Frame = 4;\n            else if(NPC[A].Special3 < 10)\n                NPC[A].Frame = 3;\n            else if(NPC[A].Special3 < 15)\n                NPC[A].Frame = 12;\n            else if(NPC[A].Special3 < 20)\n                NPC[A].Frame = 8;\n            else if(NPC[A].Special3 < 25)\n                NPC[A].Frame = 9;\n            else\n            {\n                NPC[A].Special4 = 0;\n                NPC[A].Direction = 1;\n            }\n        }\n        else if(NPC[A].Special4 == -10) // look left\n        {\n            NPC[A].Special3 -= 1;\n            if(NPC[A].Special3 > -5)\n                NPC[A].Frame = 3;\n            else if(NPC[A].Special3 > -10)\n                NPC[A].Frame = 4;\n            else\n            {\n                NPC[A].Special4 = 0;\n                NPC[A].Direction = -1;\n            }\n        }\n        else if(NPC[A].Special4 == 10) // look right\n        {\n            NPC[A].Special3 += 1;\n            if(NPC[A].Special3 < 5)\n                NPC[A].Frame = 8;\n            else if(NPC[A].Special3 < 10)\n                NPC[A].Frame = 9;\n            else\n            {\n                NPC[A].Special4 = 0;\n                NPC[A].Direction = 1;\n            }\n        }\n        else if(NPC[A].Special4 == 2) // hops\n        {\n            if(NPC[A].Wings)\n            {\n                NPC[A].Special3 += 1;\n                if(NPC[A].Special3 >= 10)\n                    NPC[A].Special4 = 0;\n            }\n            else if(NPC[A].Location.SpeedY == 0 || NPC[A].Slope > 0)\n            {\n                if(NPC[A].Special3 < 5)\n                {\n                    NPC[A].Special3 += 1;\n                    if(NPC[A].Direction < 0)\n                        NPC[A].Frame = 1;\n                    else\n                        NPC[A].Frame = 6;\n                }\n                else if(NPC[A].Special3 == 5)\n                {\n                    NPC[A].Special3 += 1;\n                    NPC[A].Location.SpeedY = -3;\n                    NPC[A].Location.Y -= 0.1_n;\n                    if(NPC[A].Direction < 0)\n                        NPC[A].Frame = 0;\n                    else\n                        NPC[A].Frame = 5;\n                }\n                else if(NPC[A].Special3 < 10)\n                {\n                    NPC[A].Special3 += 1;\n                    if(NPC[A].Direction < 0)\n                        NPC[A].Frame = 1;\n                    else\n                        NPC[A].Frame = 6;\n                }\n                else\n                    NPC[A].Special4 = 0;\n            }\n        }\n        else if(NPC[A].Special4 == 3) // jump on player\n        {\n            if(NPC[A].Wings)\n            {\n                // -9 is jump speed, this means that the NPC is currently in its walking routine\n                if(NPC[A].Wings != WING_JUMP || NPC[A].Location.SpeedY == -9)\n                    NPC[A].Wings = WING_NONE;\n            }\n            else if(NPC[A].Special3 < -1)\n            {\n                if(NPC[A].Special > 1)\n                    NPC[A].Special -= 1;\n                NPC[A].Special3 += 1;\n                if(NPC[A].Special3 == -1)\n                    NPC[A].Special3 = 6;\n            }\n            else if(NPC[A].Special3 < 5)\n            {\n                NPC[A].Special3 += 1;\n                if(NPC[A].Direction < 0)\n                    NPC[A].Frame = 1;\n                else\n                    NPC[A].Frame = 6;\n            }\n            else if(NPC[A].Special3 == 5)\n            {\n                auto &sx = NPC[A].Location.SpeedX;\n                auto &pl = Player[NPC[A].Special5].Location;\n                NPC[A].Special3 += 1;\n                NPC[A].Location.SpeedY = -12;\n                NPC[A].BeltSpeed = 0;\n                NPC[A].Location.Y -= 0.1_n;\n                // This formula got been compacted: If something will glitch, feel free to restore back this crap\n                // NPC[A].Location.SpeedX = (static_cast<int>(std::floor(static_cast<num_t>(((Player[NPC[A].Special5].Location.X + Player[NPC[A].Special5].Location.Width / 2 - 16) + 1) / 32))) * 32 + 1 - NPC[A].Location.X) / 50;\n                num_t pCenter = pl.X + pl.Width / 2;\n                sx = num_t::floor((pCenter - 15) / 32) * 32 + 1;\n                sx -= NPC[A].Location.X;\n                sx /= 50;\n                if(sx > 15)\n                    sx = 15;\n                else if(sx < -15)\n                    sx = -15;\n                NPC[A].Special3 = -50;\n                NPC[A].Special = 10;\n\n                // reset direction if NPC normally has wings\n                if(NPC[A].DefaultWings != WING_NONE)\n                    NPC[A].Direction = (sx > 0) ? 1 : -1;\n\n                if(NPC[A].Direction < 0)\n                    NPC[A].Frame = 0;\n                else\n                    NPC[A].Frame = 5;\n            }\n            else if(NPC[A].Special3 == 6)\n            {\n                if(NPC[A].Location.SpeedY > 0)\n                {\n                    NPC[A].Frame = 10;\n                    NPC[A].Location.SpeedX = 0;\n                    NPC[A].Location.SpeedY = 0;\n                    NPC[A].Special3 = 10;\n                    NPC[A].Projectile = true;\n                }\n            }\n            else if(NPC[A].Special3 < 13)\n            {\n                NPC[A].Location.SpeedY = -2;\n                NPC[A].Special3 += 1;\n            }\n            else if(NPC[A].Special3 < 16)\n            {\n                NPC[A].Location.SpeedY = 2;\n                NPC[A].Special3 += 1;\n            }\n            else if(NPC[A].Special3 < 19)\n            {\n                NPC[A].Location.SpeedY = -2;\n                NPC[A].Special3 += 1;\n            }\n            else if(NPC[A].Special3 < 21)\n            {\n                NPC[A].Location.SpeedY = 2;\n                NPC[A].Special3 += 1;\n            }\n            else if(NPC[A].Special3 == 21)\n            {\n                if(NPC[A].Location.SpeedY != 0)\n                    NPC[A].Location.SpeedY = 10;\n                else\n                {\n                    bool legacy = /*NPC[A].Legacy &&*/ (NPC[A].Variant == 1);\n                    PlaySoundSpatial(SFX_Stone, NPC[A].Location);\n                    NPC[A].Special3 = 30;\n                    NPC[A].Frame = 11;\n                    NPC[A].Projectile = false;\n                    Location_t tempLocation = NPC[A].Location;\n\n                    // Useless self-assignment code [PVS Studio]\n                    //tempLocation.X = tempLocation.X; // + 16\n                    //tempLocation.Width = tempLocation.Width; // - 32\n\n                    tempLocation.Y += tempLocation.Height - 8;\n                    tempLocation.Height = 16;\n                    // fBlock = FirstBlock[long(NPC[A].Location.X / 32) - 1];\n                    // lBlock = LastBlock[long((NPC[A].Location.X + NPC[A].Location.Width) / 32.0) + 1];\n                    // blockTileGet(NPC[A].Location, fBlock, lBlock);\n\n                    for(BlockRef_t block : treeFLBlockQuery(tempLocation, false))\n                    {\n                        int B = block;\n                        if(Block[B].Type == 186 && CheckCollision(tempLocation, Block[B].Location) && !Block[B].Hidden)\n                            SafelyKillBlock(B);\n                    }\n\n                    if(!legacy && g_config.extra_screen_shake)\n                        doShakeScreen(0, 4, SHAKE_SEQUENTIAL, 7, 150, NPC[A].Location);\n\n                    if(legacy) // Classic SMBX 1.0's behavior when Bowser stomps a floor\n                    {\n                        // fBlock = FirstBlock[long(level[NPC[A].Section].X / 32) - 1];\n                        // lBlock = LastBlock[long((level[NPC[A].Section].Width) / 32.0) + 2];\n                        // {\n                        //     auto &sec = level[NPC[A].Section];\n                        //     Location_t toShake;\n                        //     toShake.X = sec.X;\n                        //     toShake.Width = (sec.Width - sec.X);\n                        //     blockTileGet(toShake, fBlock, lBlock);\n                        // }\n\n                        // Shake all blocks up\n                        // for(int B = (int)fBlock; B <= lBlock; B++)\n                        //     BlockShakeUp(B);\n                        {\n                            const auto &sec = level[NPC[A].Section];\n                            Location_t toShake;\n                            toShake.X = sec.X;\n                            toShake.Width = (sec.Width - sec.X);\n                            toShake.Y = sec.Y;\n                            toShake.Height = (sec.Height - sec.Y);\n                            for(BlockRef_t block : treeFLBlockQuery(toShake, false))\n                            {\n                                int B = block;\n                                BlockShakeUp(B);\n                            }\n                        }\n\n                        // expand down a section at the bottom of destroyed blocks\n                        for(int B = 0; B < numSections; B++)\n                        {\n                            auto &n = NPC[A];\n                            auto &s = level[B];\n\n                            if(n.Location.X >= s.X &&\n                               n.Location.X + n.Location.Width <= s.Width &&\n                               n.Location.Y + n.Location.Height + 48 >= s.Y &&\n                               n.Location.Y + n.Location.Height + 48 <= s.Height &&\n                               B != n.Section)\n                            {\n                                n.SpecialY = 0;\n                                n.Special3 = 0;\n                                n.Special4 = 2;\n\n                                auto &ns = level[n.Section];\n                                if(s.X < ns.X)\n                                    ns.X = s.X;\n                                if(s.Y < ns.Y)\n                                    ns.Y = s.Y;\n                                if(s.Width > ns.Width)\n                                    ns.Width = s.Width;\n                                if(s.Height > ns.Height)\n                                    ns.Height = s.Height;\n\n                                s.X = 0;\n                                s.Y = 0;\n                                s.Width = 0;\n                                s.Height = 0;\n\n                                UpdateSectionOverlaps(B);\n                                UpdateSectionOverlaps(n.Section);\n\n                                for(int C = 1; C <= numNPCs; C++)\n                                {\n                                    auto &nc = NPC[C];\n                                    if(nc.Section == B)\n                                        nc.Section = n.Section;\n                                }\n                            }\n                        } // for\n                        SoundPause[SFX_Stomp] = 12;\n                    }\n                }\n            }\n            else if(NPC[A].Special3 < 35)\n            {\n                NPC[A].Frame = 11;\n                NPC[A].Special3 += 1;\n            }\n            else if(NPC[A].Special3 < 40)\n            {\n                NPC[A].Frame = 12;\n                NPC[A].Special3 += 1;\n                NPC[A].Special5 = 0;\n            }\n            else\n            {\n                if(NPC[A].Location.SpeedY == 0 || NPC[A].Slope > 0)\n                {\n                    NPC[A].Special3 = 0;\n                    if(NPC[A].Location.to_right_of(Player[NPC[A].Special5].Location))\n                        NPC[A].Special4 = -10;\n                    else\n                        NPC[A].Special4 = 10;\n                }\n            }\n        }\n        else if(NPC[A].Special4 == 4) // shoot a fireball\n        {\n            NPC[A].Special3 += 1;\n            if(NPC[A].Special3 < 15)\n            {\n                if(NPC[A].Direction < 0)\n                    NPC[A].Frame = 13;\n                else\n                    NPC[A].Frame = 14;\n            }\n            else if(NPC[A].Special3 < 30)\n            {\n                if(NPC[A].Direction < 0)\n                    NPC[A].Frame = 2;\n                else\n                    NPC[A].Frame = 7;\n\n                if(NPC[A].Special3 == 29)\n                {\n                    numNPCs++;\n                    NPC[numNPCs].Active = true;\n                    NPC[numNPCs].TimeLeft = 100;\n                    NPC[numNPCs].Direction = NPC[A].Direction;\n                    NPC[numNPCs].Section = NPC[A].Section;\n                    NPC[numNPCs].Type = NPCID_VILLAIN_FIRE;\n                    if(NPC[numNPCs].Direction > 0)\n                        NPC[numNPCs].Frame = 4;\n                    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                    if(NPC[numNPCs].Direction < 0)\n                        NPC[numNPCs].Location.X = NPC[A].Location.X - 40;\n                    else\n                        NPC[numNPCs].Location.X = NPC[A].Location.X + 54;\n                    NPC[numNPCs].Location.Y = NPC[A].Location.Y + 19;\n\n                    NPCSetSpeedTarget_FixedX(NPC[numNPCs], Player[NPC[A].Special5].Location, 4, 1);\n\n                    syncLayers_NPC(numNPCs);\n                    PlaySoundSpatial(SFX_BigFireball, NPC[A].Location);\n                }\n            }\n            else if(NPC[A].Special3 < 45)\n            {\n                if(NPC[A].Direction == -1)\n                    NPC[A].Frame = 0;\n                else\n                    NPC[A].Frame = 5;\n            }\n            else\n                NPC[A].Special4 = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_HEAVY_THROWER)\n    {\n        if(NPC[A].HoldingPlayer > 0)\n        {\n            // the throw counter was previously Special3, but it uses a double in the non-held logic, so it has been moved to SpecialX\n            if(Player[NPC[A].HoldingPlayer].Effect == PLREFF_NORMAL)\n                NPC[A].SpecialX += 1;\n\n            if(NPC[A].SpecialX >= 20)\n            {\n                PlaySoundSpatial(SFX_HeavyToss, NPC[A].Location);\n                NPC[A].SpecialX = 0; // -15\n                numNPCs++;\n                NPC[numNPCs].Location.Height = 32;\n                NPC[numNPCs].Location.Width = 32;\n                NPC[numNPCs].Location.X = NPC[A].Location.X;\n                NPC[numNPCs].Location.Y = NPC[A].Location.Y;\n                NPC[numNPCs].Direction = NPC[A].Direction;\n                NPC[numNPCs].Type = NPCID_HEAVY_THROWN;\n                NPC[numNPCs].Shadow = NPC[A].Shadow;\n                NPC[numNPCs].CantHurt = 200;\n                NPC[numNPCs].CantHurtPlayer = NPC[A].HoldingPlayer;\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].Projectile = true;\n                NPC[numNPCs].TimeLeft = 50;\n                NPC[numNPCs].Location.SpeedY = -8;\n                NPC[numNPCs].Location.SpeedX = 3 * Player[NPC[A].HoldingPlayer].Direction + Player[NPC[A].HoldingPlayer].Location.SpeedX * 0.8_r;\n                syncLayers_NPC(numNPCs);\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_CANNONITEM) // Bullet Bill Shooter\n    {\n        if(NPC[A].Type == NPCID_CANNONENEMY)\n        {\n            NPC[A].Special += 1;\n            if(NPC[A].HoldingPlayer > 0)\n            {\n                if(Player[NPC[A].HoldingPlayer].Effect == PLREFF_NORMAL)\n                    NPC[A].Special += 6;\n            }\n        }\n        else\n        {\n            int shootStep = 10;\n            int shootStepSpin = 20;\n            int shootStepCar = 5;\n            bool keepProjectile = false;\n\n            int shootBehavior = NPC[A].Variant;\n\n            switch(shootBehavior)\n            {\n            default:\n            case 0:\n                // SMBX 1.2.1 and newer (shoot fast, don't shoot while projectile)\n                break;\n            case 1:\n                // SMBX 1.2 (shoot fast, keep shoot while projectile)\n                keepProjectile = true;\n                break;\n            case 2:\n                // SMBX older than 1.2 (shoot slow, keep shoot while projectile)\n                keepProjectile = true;\n                shootStep = 5;\n                shootStepSpin = 10;\n                break;\n            }\n\n            if(NPC[A].HoldingPlayer > 0)\n            {\n                if(Player[NPC[A].HoldingPlayer].SpinJump)\n                {\n                    if(NPC[A].Direction != Player[NPC[A].HoldingPlayer].SpinFireDir)\n                    {\n                        if(Player[NPC[A].HoldingPlayer].Effect == PLREFF_NORMAL)\n                            NPC[A].Special += shootStepSpin;\n                    }\n                }\n                else\n                {\n                    if(Player[NPC[A].HoldingPlayer].Effect == PLREFF_NORMAL)\n                        NPC[A].Special += shootStep;\n                }\n            }\n            else if(NPC[A].vehiclePlr > 0)\n                NPC[A].Special += shootStepCar;\n            else if(NPC[A].Projectile && keepProjectile)\n                NPC[A].Special += shootStep;\n        }\n\n        if(NPC[A].Special >= 200)\n        {\n            bool can_come_out = true;\n\n            if(NPC[A].HoldingPlayer > 0)\n            {\n                if(Player[NPC[A].HoldingPlayer].SpinJump)\n                    Player[NPC[A].HoldingPlayer].SpinFireDir = int(NPC[A].Direction);\n            }\n\n            if(NPC[A].HoldingPlayer == 0 && NPC[A].vehiclePlr == 0 && NPC[A].Type == NPCID_CANNONENEMY)\n            {\n                num_t C = 0;\n                for(int B = 1; B <= numPlayers; B++)\n                {\n                    if(!Player[B].Dead && Player[B].Section == NPC[A].Section)\n                    {\n                        num_t dist = NPCPlayerTargetDist(NPC[A], Player[B]);\n                        if(C == 0 || dist < C)\n                        {\n                            C = dist;\n                            if(NPC[A].Location.to_right_of(Player[B].Location))\n                                NPC[A].Direction = -1;\n                            else\n                                NPC[A].Direction = 1;\n\n                            if(!CanComeOut(NPC[A].Location, Player[B].Location))\n                                can_come_out = false;\n                        }\n                    }\n                }\n            }\n\n            if(numNPCs < maxNPCs)\n            {\n                if(!can_come_out)\n                    NPC[A].Special = 0;\n                else if(Player[NPC[A].vehiclePlr].Controls.Run || NPC[A].vehiclePlr == 0)\n                {\n                    NPC[A].Special = 0;\n                    numNPCs++;\n                    NPC[numNPCs].Inert = NPC[A].Inert;\n                    bool tempBool = false;\n                    NPC[numNPCs].Direction = NPC[A].Direction;\n                    NPC[numNPCs].DefaultDirection = NPC[A].Direction;\n                    if(NPC[A].HoldingPlayer > 0 || NPC[A].vehiclePlr > 0 || (NPC[A].Type == NPCID_CANNONITEM && NPC[A].Projectile))\n                    {\n                        NPC[numNPCs].Projectile = true;\n                        NPC[numNPCs].CantHurt = 10000;\n                        NPC[numNPCs].CantHurtPlayer = NPC[A].HoldingPlayer;\n                        NPC[numNPCs].Location.SpeedX = 8 * NPC[numNPCs].Direction;\n                    }\n                    else if(NPC[A].CantHurtPlayer > 0)\n                    {\n                        NPC[numNPCs].Projectile = true;\n                        NPC[numNPCs].CantHurt = 1000;\n                        NPC[numNPCs].CantHurtPlayer = NPC[A].CantHurtPlayer;\n                    }\n                    else if(NPC[A].Type == NPCID_CANNONITEM)\n                    {\n                        tempBool = true;\n                        numNPCs--;\n                    }\n\n                    if(!tempBool)\n                    {\n                        NPC[numNPCs].Shadow = NPC[A].Shadow;\n                        NPC[numNPCs].Active = true;\n                        NPC[numNPCs].TimeLeft = 100;\n                        NPC[numNPCs].JustActivated = 0;\n                        NPC[numNPCs].Section = NPC[A].Section;\n                        NPC[numNPCs].Type = NPCID_BULLET;\n                        NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                        NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n\n                        if(NPC[numNPCs].Direction > 0)\n                            NPC[numNPCs].Location.X = NPC[A].Location.X + NPC[A].Location.Width / 2;\n                        else\n                            NPC[numNPCs].Location.X = NPC[A].Location.X + NPC[A].Location.Width / 2 - NPC[numNPCs].Location.Width;\n\n                        if(NPC[numNPCs].Direction > 0)\n                            NPC[numNPCs].Frame = 1;\n                        else\n                            NPC[numNPCs].Frame = 0;\n                        NPC[numNPCs].Location.Y = NPC[A].Location.Y + (NPC[A].Location.Height - NPC[numNPCs].Location.Height) / 2;\n\n                        if(NPC[A].HoldingPlayer)\n                            PlayerThrownNpcMazeCheck(Player[NPC[A].HoldingPlayer], NPC[numNPCs]);\n\n                        syncLayers_NPC(numNPCs);\n\n                        Location_t tempLocation;\n                        tempLocation.X = NPC[numNPCs].Location.X + (NPC[numNPCs].Location.Width / 2) * NPC[numNPCs].Direction;\n                        tempLocation.Y = NPC[numNPCs].Location.Y + NPC[numNPCs].Location.Height / 2 - EffectHeight[EFFID_SMOKE_S3] / 2;\n                        NewEffect(EFFID_SMOKE_S3, tempLocation);\n\n                        PlaySoundSpatial(SFX_Bullet, NPC[A].Location);\n                    }\n                }\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_TOOTHY)\n    {\n        int B = 0;\n        if(NPC[A].Special > 0)\n        {\n            if(Player[NPC[A].Special].HoldingNPC > 0)\n            {\n                if(NPC[Player[NPC[A].Special].HoldingNPC].Type == 49)\n                    B = 1;\n            }\n        }\n        else if(NPC[NPC[A].Special2].Projectile && NPC[NPC[A].Special2].Active)\n        {\n            B = 1;\n            NPC[A].Projectile = true;\n            NPC[A].Direction = NPC[NPC[A].Special2].Direction;\n            if(NPC[A].Direction > 0)\n                NPC[A].Location.X = NPC[NPC[A].Special2].Location.X + 32;\n            else\n                NPC[A].Location.X = NPC[NPC[A].Special2].Location.X - NPC[A].Location.Width;\n            NPC[A].Location.Y = NPC[NPC[A].Special2].Location.Y;\n        }\n\n        if(Player[NPC[A].vehiclePlr].Controls.Run)\n            B = 1;\n\n        if(NPC[A].Special2 > 0 && NPC[NPC[A].Special2].Special2 != A)\n            B = 0;\n\n        if(NPC[A].Special > 0)\n        {\n            if(Player[NPC[A].Special].Effect != PLREFF_NORMAL)\n                B = 0;\n        }\n\n        if(B == 0)\n        {\n            NPC[A].Killed = 9;\n            NPCQueues::Killed.push_back(A);\n        }\n    }\n    else if(NPC[A].Type == NPCID_TOOTHYPIPE)\n    {\n        if(NPC[A].HoldingPlayer == 0 && NPC[A].vehiclePlr == 0)\n            NPC[A].Special = 0;\n\n        if(NPC[A].HoldingPlayer > 0 && NPC[A].Special2 > 0)\n            NPC[NPC[A].Special2].Direction = NPC[A].Direction;\n\n        if(Player[NPC[A].HoldingPlayer].Effect != PLREFF_NORMAL)\n            NPC[A].Special = 0;\n#if 0\n        // Important: this also makes a thrown handheld plant harm NPCs, so it is a major balance change.\n        // Since it was disabled in SMBX code, better not to change it. -- ds-sloth\n        // In original game, this is a dead code because of \"And 0\" condition at end.\n        // In this sample, the \"& false\" was been commented\n        // This code makes Toothy shown off the pipe when the pipe is a projectile, shooted by generator\n        if(NPC[A].Projectile && NPC[A].Special2 == 0 && NPC[A].Special == 0 /*&& false*/)\n        {\n            numNPCs++;\n            NPC[A].Special2 = numNPCs;\n            NPC[numNPCs].Active = true;\n            NPC[numNPCs].Section = NPC[A].Section;\n            NPC[numNPCs].TimeLeft = 100;\n            NPC[numNPCs].Type = NPCID_TOOTHY;\n            NPC[numNPCs].Location.Height = 32;\n            NPC[numNPCs].Location.Width = 48;\n            NPC[numNPCs].Special = 0;\n            NPC[numNPCs].Special2 = A;\n            NPC[numNPCs].Projectile = true;\n            NPC[numNPCs].Direction = NPC[A].Direction;\n            if(NPC[numNPCs].Direction > 0)\n            {\n                NPC[numNPCs].Location.X = NPC[A].Location.X + 32;\n                NPC[numNPCs].Frame = 2;\n            }\n            else\n                NPC[numNPCs].Location.X = NPC[A].Location.X - NPC[numNPCs].Location.Width;\n            NPC[numNPCs].Location.Y = NPC[A].Location.Y;\n            syncLayers_NPC(numNPCs);\n        }\n#endif\n\n        if(NPC[NPC[A].Special2].Type == NPCID_TOOTHY && NPC[NPC[A].Special2].Special2 == A)\n        {\n            NPC[NPC[A].Special2].Projectile = true;\n            NPC[NPC[A].Special2].Direction = NPC[A].Direction;\n            if(NPC[A].Direction > 0)\n                NPC[NPC[A].Special2].Location.X = NPC[A].Location.X + 32;\n            else\n                NPC[NPC[A].Special2].Location.X = NPC[A].Location.X - NPC[NPC[A].Special2].Location.Width;\n            NPC[NPC[A].Special2].Location.Y = NPC[A].Location.Y;\n\n            treeNPCUpdate(NPC[A].Special2);\n            if(NPC[NPC[A].Special2].tempBlock != 0)\n                treeNPCSplitTempBlock(NPC[A].Special2);\n        }\n\n        if(NPC[A].vehiclePlr > 0 && !Player[NPC[A].vehiclePlr].Controls.Run)\n            NPC[A].Special = 0;\n    }\n    else if(NPC[A].Type == NPCID_KEY)\n    {\n        if(NPC[A].HoldingPlayer > 0)\n            KeyholeCheck(NPC[A].HoldingPlayer, NPC[A].Location);\n    }\n    else if(NPCIsABot(NPC[A]))\n    {\n        if(NPC[A].Projectile || NPC[A].HoldingPlayer > 0)\n        {\n            NPC[A].Special = -1;\n            NPC[A].Special2 = 0;\n            NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.98_r;\n        }\n        else\n        {\n            if(NPC[A].Special == 0)\n            {\n                int target_plr = NPCTargetPlayer(NPC[A]);\n\n                if(target_plr == 0)\n                    target_plr = 1;\n\n                if(Player[target_plr].Location.X + Player[target_plr].Location.Width / 2 > NPC[A].Location.X + 16)\n                    NPC[A].Direction = 1;\n                else\n                    NPC[A].Direction = -1;\n            }\n\n            if(NPC[A].Location.SpeedY == 0 || NPC[A].Slope > 0)\n            {\n                NPC[A].Location.SpeedX = 0;\n\n                if(NPC[A].Special == 0)\n                    NPC[A].Special = iRand(3) + 1;\n\n                if(NPC[A].Special == 1)\n                {\n                    NPC[A].FrameCount += 1;\n                    NPC[A].Special2 += 1;\n                    NPC[A].Location.SpeedX = 0;\n                    if(NPC[A].Special2 >= 90)\n                    {\n                        NPC[A].Special2 = 0;\n                        NPC[A].Special = -1;\n                        NPC[A].Location.SpeedY = -7;\n                        NPC[A].Location.SpeedX = 2 * NPC[A].Direction;\n                    }\n                }\n                else if(NPC[A].Special == 3)\n                {\n                    NPC[A].FrameCount += 1;\n                    NPC[A].Special2 += 30;\n                    NPC[A].Location.SpeedX = 0;\n                    if(NPC[A].Special2 >= 30)\n                    {\n                        NPC[A].Special2 = 0;\n                        NPC[A].Special = -1;\n                        NPC[A].Location.SpeedY = -3;\n                        NPC[A].Location.SpeedX = 2.5_n * NPC[A].Direction;\n                    }\n                }\n                else if(NPC[A].Special == 2)\n                {\n                    NPC[A].Location.SpeedX = 0.5_n * NPC[A].Direction;\n                    NPC[A].Special2 += 1;\n                    if(NPC[A].Special2 == 120)\n                    {\n                        NPC[A].Special2 = 0;\n                        NPC[A].Special = -2;\n                    }\n                }\n                else\n                {\n                    NPC[A].Special2 += 1;\n                    if(NPC[A].Special2 == 30)\n                    {\n                        NPC[A].Special2 = 0;\n                        NPC[A].Special = 0;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/npc/npc_update/npc_update_priv.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef NPC_UPDATE_PRIV_H\n#define NPC_UPDATE_PRIV_H\n\n#include \"numeric_types.h\"\n\nstruct NPC_t;\n\n// most of these routines were originally part of UpdateNPCs\nvoid NPCMovementLogic(int A, tempf_t& speedVar);\n\nvoid NPCMovementLogic_Wings(int A, const num_t speedVar);\n\nvoid NPCSectionWrap(NPC_t& npc);\n\nvoid NPCBlockLogic(int A, num_t& tempHit, int& tempHitBlock, tempf_t& tempSpeedA, const int numTempBlock, const tempf_t speedVar);\n\nvoid NPCCollide(int A);\n\nvoid NPCCollideHeld(int A);\n\nvoid NPCWalkingLogic(int A, const num_t tempHit, const int tempHitBlock, tempf_t tempSpeedA);\n\nvoid NPCEffects(int A);\n\nvoid NPCSpecialMaybeHeld(int A);\n\nvoid NPCActivationLogic(int A);\n\n// returns true if an NPC should be generated\nbool NPCGeneratorLogic(int A);\n// does NOT call the activation event (because that requires safe ProcEvent handling)\nvoid NPCGeneratorMakeNew(int A);\n\n#endif // NPC_UPDATE_PRIV_H\n"
  },
  {
    "path": "src/npc/npc_update/npc_walking_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"npc.h\"\n#include \"npc_traits.h\"\n#include \"config.h\"\n#include \"collision.h\"\n\n#include \"main/trees.h\"\n\nvoid NPCWalkingLogic(int A, const num_t tempHit, const int tempHitBlock, tempf_t tempSpeedA)\n{\n    // tempSpeedA does not check for walking collisions in vanilla\n    if(g_config.fix_npc_downward_clip)\n    {\n        tempSpeedA = (tempf_t)Block[tempHitBlock].Location.SpeedY;\n        if(tempSpeedA < 0)\n            tempSpeedA = 0;\n    }\n\n    if(NPC[A].Wings)\n    {\n        if(NPC[A].Wings == WING_JUMP)\n            NPC[A].Location.SpeedY = -9;\n        else\n        {\n            if(NPC[A].Location.SpeedY > 0)\n                NPC[A].Location.SpeedY = -NPC[A].Location.SpeedY;\n        }\n    }\n    else if(NPC[A].Type == NPCID_RED_FLY_FODDER) // Walking code for Flying Goomba\n    {\n        if(NPC[A].Special <= 30)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = 0;\n\n            if(NPC[A].Slope > 0)\n            {\n                NPC[A].Location.SpeedY = NPC[A].Location.SpeedX * (int_ok)Block[NPC[A].Slope].Location.Height / (int_ok)Block[NPC[A].Slope].Location.Width * BlockSlope[Block[NPC[A].Slope].Type];\n                if(NPC[A].Location.SpeedY < 0)\n                    NPC[A].Location.SpeedY = 0;\n            }\n\n            if(tempSpeedA != 0)\n                NPC[A].Location.SpeedY = (num_t)tempSpeedA;\n        }\n        else if(NPC[A].Special == 31)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = -4;\n        }\n        else if(NPC[A].Special == 32)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = -4;\n        }\n        else if(NPC[A].Special == 33)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = -4;\n        }\n        else if(NPC[A].Special == 34)\n        {\n            NPC[A].Special = 0;\n            NPC[A].Location.SpeedY = -7;\n        }\n    }\n    else if(NPC[A].Type == NPCID_FLY_CARRY_FODDER) // Walking code for SMW Flying Goomba\n    {\n        if(NPC[A].Special <= 60)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = 0;\n\n            if(NPC[A].Slope > 0)\n            {\n                NPC[A].Location.SpeedY = NPC[A].Location.SpeedX * (int_ok)Block[NPC[A].Slope].Location.Height / (int_ok)Block[NPC[A].Slope].Location.Width * BlockSlope[Block[NPC[A].Slope].Type];\n                if(NPC[A].Location.SpeedY < 0)\n                    NPC[A].Location.SpeedY = 0;\n            }\n\n            if(tempSpeedA != 0)\n                NPC[A].Location.SpeedY = (num_t)tempSpeedA;\n        }\n        else if(NPC[A].Special == 61)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = -3;\n        }\n        else if(NPC[A].Special == 62)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = -3;\n        }\n        else if(NPC[A].Special == 63)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = -3;\n        }\n        else if(NPC[A].Special == 64)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = -3;\n        }\n        else if(NPC[A].Special == 65)\n        {\n            NPC[A].Special = 0;\n            NPC[A].Location.SpeedY = -7;\n        }\n    }\n    else if(NPC[A]->TurnsAtCliffs && !NPC[A].Projectile) // Walking code NPCs that turn\n    {\n        bool tempTurn = true; // used for turning the npc around\n        Location_t tempLocation = NPC[A].Location;\n        tempLocation.SpeedX = 0;\n        tempLocation.SpeedY = 0;\n        tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height - 8;\n        tempLocation.Height = 16;\n        if(NPC[A].Slope > 0)\n            tempLocation.Height = 32;\n        tempLocation.Width = 16;\n\n        bool isPokeyHead = (NPC[A].Type == NPCID_STACKER && NPC[A].Special2 == 0);\n\n        // If .Location.SpeedX > 0 Then\n        if(NPC[A].Direction > 0)\n        {\n            tempLocation.X += NPC[A].Location.Width - 20;\n            if(isPokeyHead)\n                tempLocation.X += 16;\n            // If .Type = 189 Then tempLocation.X += -10\n        }\n        else\n        {\n            tempLocation.X += -tempLocation.Width + 20;\n            if(isPokeyHead)\n                tempLocation.X -= 16;\n            // If .Type = 189 Then tempLocation.X += 10\n        }\n\n        for(int bCheck2 = 1; bCheck2 <= 2; bCheck2++)\n        {\n            // if(bCheck2 == 1)\n            // {\n            //     // fBlock2 = FirstBlock[(tempLocation.X / 32) - 1];\n            //     // lBlock2 = LastBlock[((tempLocation.X + tempLocation.Width) / 32.0) + 1];\n            //     blockTileGet(tempLocation, fBlock2, lBlock2);\n            // }\n            // else\n            // {\n            //     fBlock2 = numBlock - numTempBlock;\n            //     lBlock2 = numBlock;\n            // }\n            auto collBlockSentinel2 = (bCheck2 == 1)\n                ? treeFLBlockQuery(tempLocation, SORTMODE_NONE)\n                : treeTempBlockQuery(tempLocation, SORTMODE_NONE);\n\n            for(BlockRef_t block : collBlockSentinel2)\n            {\n                int B = block;\n                //If BlockNoClipping(Block(B).Type) = False And Block(B).Invis = False And Block(B).Hidden = False And Not (BlockIsSizable(Block(B).Type) And Block(B).Location.Y < .Location.Y + .Location.Height - 3) Then\n\n                // Don't collapse Pokey during walking on slopes and other touching surfaces\n                if(g_config.fix_npc247_collapse && isPokeyHead && Block[B].tempBlockNpcType != NPCID_STACKER)\n                    continue;\n\n                if((tempLocation.X + tempLocation.Width >= Block[B].Location.X) &&\n                   (tempLocation.X <= Block[B].Location.X + Block[B].Location.Width) &&\n                   (tempLocation.Y + tempLocation.Height >= Block[B].Location.Y) &&\n                   (tempLocation.Y <= Block[B].Location.Y + Block[B].Location.Height) &&\n                   (!BlockNoClipping[Block[B].Type] && !Block[B].Invis && !Block[B].Hidden && !(BlockIsSizable[Block[B].Type] && Block[B].Location.Y < NPC[A].Location.Y + NPC[A].Location.Height - 3)))\n                {\n                    // If CheckCollision(tempLocation, Block(B).Location) = True Then\n                    tempTurn = false;\n                    break;\n                    // End If\n                }\n                else\n                {\n                    // not working\n                }\n\n                // End If\n            }\n\n            if(!tempTurn)\n                break;\n        }\n\n        if(tempTurn)\n            NPC[A].TurnAround = true;\n\n        NPC[A].Location.SpeedY = 0;\n        if(NPC[A].Slope > 0)\n        {\n            NPC[A].Location.SpeedY = NPC[A].Location.SpeedX * (int_ok)Block[NPC[A].Slope].Location.Height / (int_ok)Block[NPC[A].Slope].Location.Width * BlockSlope[Block[NPC[A].Slope].Type];\n            if(NPC[A].Location.SpeedY < 0)\n                NPC[A].Location.SpeedY = 0;\n        }\n\n        if(tempSpeedA != 0)\n            NPC[A].Location.SpeedY = (num_t)tempSpeedA;\n    }\n    else if(NPC[A].Type == NPCID_JUMPER_S4) // ninja code\n    {\n        bool tempTurn = true; // used for turning the npc around\n\n        Location_t tempLocation = NPC[A].Location;\n        tempLocation.SpeedX = 0;\n        tempLocation.SpeedY = 0;\n        tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height - 8;\n        tempLocation.Height = 16;\n\n        if(NPC[A].Slope > 0)\n            tempLocation.Height = 32;\n\n        tempLocation.Width = 16;\n\n        if(NPC[A].Location.SpeedX > 0)\n            tempLocation.X = NPC[A].Location.X + NPC[A].Location.Width - 16;\n        else\n            tempLocation.X = NPC[A].Location.X - tempLocation.Width + 16;\n\n        for(int bCheck2 = 1; bCheck2 <= 2; bCheck2++)\n        {\n            // if(bCheck2 == 1)\n            // {\n            //     // fBlock2 = FirstBlock[(tempLocation.X / 32) - 1];\n            //     // lBlock2 = LastBlock[((tempLocation.X + tempLocation.Width) / 32.0) + 1];\n            //     blockTileGet(tempLocation, fBlock2, lBlock2);\n            // }\n            // else\n            // {\n            //     fBlock2 = numBlock - numTempBlock;\n            //     lBlock2 = numBlock;\n            // }\n            auto collBlockSentinel2 = (bCheck2 == 1)\n                ? treeFLBlockQuery(tempLocation, SORTMODE_NONE)\n                : treeTempBlockQuery(tempLocation, SORTMODE_NONE);\n\n            for(BlockRef_t block : collBlockSentinel2)\n            {\n                int B = block;\n                if(!BlockNoClipping[Block[B].Type] && !Block[B].Invis && !Block[B].Hidden && !(BlockIsSizable[Block[B].Type] && Block[B].Location.Y < NPC[A].Location.Y + NPC[A].Location.Height - 3))\n                {\n                    if(CheckCollision(tempLocation, Block[B].Location))\n                    {\n                        tempTurn = false;\n                        break;\n                    }\n                }\n\n                if(!tempTurn)\n                    break;\n            }\n        }\n\n        tempLocation = NPC[A].Location;\n        tempLocation.SpeedX = 0;\n        tempLocation.SpeedY = 0;\n        tempLocation.Y = NPC[A].Location.Y + 8;\n        tempLocation.Height -= 16;\n        tempLocation.Width = 32;\n        if(NPC[A].Location.SpeedX > 0)\n            tempLocation.X = NPC[A].Location.X + NPC[A].Location.Width;\n        else\n            tempLocation.X = NPC[A].Location.X - tempLocation.Width;\n\n        // we are able to wrap this whole thing in the inner-loop check that (NPC[A].Slope <= 0)\n        // commenting for now to avoid inadvertently introducing any bugs\n        // if(NPC[A].Slope <= 0)\n        for(int bCheck2 = 1; bCheck2 <= 2; bCheck2++)\n        {\n            // if(bCheck2 == 1)\n            // {\n            //     // fBlock2 = FirstBlock[(tempLocation.X / 32) - 1];\n            //     // lBlock2 = LastBlock[((tempLocation.X + tempLocation.Width) / 32.0) + 1];\n            //     blockTileGet(tempLocation, fBlock2, lBlock2);\n            // }\n            // else\n            // {\n            //     fBlock2 = numBlock - numTempBlock;\n            //     lBlock2 = numBlock;\n            // }\n            auto collBlockSentinel2 = (bCheck2 == 1)\n                ? treeFLBlockQuery(tempLocation, SORTMODE_NONE)\n                : treeTempBlockQuery(tempLocation, SORTMODE_NONE);\n\n            for(BlockRef_t block : collBlockSentinel2)\n            {\n                int B = block;\n                if(!BlockNoClipping[Block[B].Type] && !Block[B].Invis && !Block[B].Hidden && !(BlockIsSizable[Block[B].Type] && Block[B].Location.Y < NPC[A].Location.Y + NPC[A].Location.Height - 1))\n                {\n                    if(CheckCollision(tempLocation, Block[B].Location))\n                    {\n                        if(NPC[A].Slope > 0)\n                        {\n\n                        }\n                        else if(BlockSlope[Block[B].Type] == 0)\n                            tempTurn = true;\n                        break;\n                    }\n                }\n\n                if(tempTurn)\n                    break;\n            }\n        }\n\n        if(tempTurn)\n        {\n            NPC[A].Location.Y -= 0.1_n;\n            NPC[A].Location.SpeedY = -6.55_n;\n        }\n        else\n        {\n            NPC[A].Location.SpeedY = 0;\n            if(NPC[A].Slope > 0)\n            {\n                NPC[A].Location.SpeedY = NPC[A].Location.SpeedX * (int_ok)Block[NPC[A].Slope].Location.Height / (int_ok)Block[NPC[A].Slope].Location.Width * BlockSlope[Block[NPC[A].Slope].Type];\n                if(NPC[A].Location.SpeedY < 0)\n                    NPC[A].Location.SpeedY = 0;\n            }\n        }\n\n        if(tempSpeedA != 0)\n            NPC[A].Location.SpeedY += (num_t)tempSpeedA;\n    }\n    else // Walking code for everything else\n    {\n        if(NPCIsAParaTroopa(NPC[A]))\n        {\n            if(NPC[A].Special == WING_JUMP)\n                NPC[A].Location.SpeedY = -9;\n            else\n            {\n                if(NPC[A].Location.SpeedY > 0)\n                    NPC[A].Location.SpeedY = -NPC[A].Location.SpeedY;\n            }\n        }\n        else\n        {\n            NPC[A].Location.SpeedY = 0;\n            if(NPC[A].Slope > 0 && !NPC[A]->IsAShell && NPC[A].Type != NPCID_SLIDE_BLOCK)\n            {\n                NPC[A].Location.SpeedY = NPC[A].Location.SpeedX * (int_ok)Block[NPC[A].Slope].Location.Height / (int_ok)Block[NPC[A].Slope].Location.Width * BlockSlope[Block[NPC[A].Slope].Type];\n                if(NPC[A].Location.SpeedY < 0)\n                    NPC[A].Location.SpeedY = 0;\n            }\n        }\n\n        if(NPC[A].Type == NPCID_BIRD)\n        {\n            NPC[A].Special += 1;\n            if(NPC[A].Special <= 3)\n                NPC[A].Location.SpeedY = -3.5_n;\n            else\n            {\n                NPC[A].Location.SpeedY = -5.5_n;\n                NPC[A].Special = 0;\n            }\n        }\n\n        if(NPC[A].Type == NPCID_KNIGHT)\n        {\n            NPC[A].FrameCount += 1;\n            if(NPC[A].FrameCount > 1)\n                NPC[A].FrameCount = 0;\n            NPC[A].Location.SpeedY = -3;\n        }\n\n        if(NPC[A].Type == NPCID_INVINCIBILITY_POWER)\n            NPC[A].Location.SpeedY = -6_n;\n\n        if(tempSpeedA != 0)\n            NPC[A].Location.SpeedY = (num_t)tempSpeedA;\n\n        // assigned to Special in SMBX 1.3\n        if(NPC[A].Type == NPCID_SAW)\n            NPC[A].SpecialY = NPC[A].Location.SpeedY;\n    }\n\n    if(NPC[A].Slope == 0)\n        NPC[A].Location.Y = tempHit;\n\n#if 0\n    tempHit = 0;\n    tempHitBlock = 0;\n\n    // impossible\n    if(Block[tempHitBlock].tempBlockNpcType > 0 && NPC[Block[tempHitBlock].tempBlockNpcIdx].Slope > 0)\n    {\n        // .Location.SpeedY = 0\n        NPC[A].Slope = NPC[Block[tempHitBlock].tempBlockNpcIdx].Slope;\n        // Stop\n    }\n#endif\n}\n"
  },
  {
    "path": "src/npc/npc_update.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <algorithm>\n#include <array>\n\n#include <Logger/logger.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"../globals.h\"\n#include \"../npc.h\"\n#include \"player.h\"\n#include \"../sound.h\"\n#include \"../graphics.h\"\n#include \"../collision.h\"\n#include \"../effect.h\"\n#include \"../layers.h\"\n#include \"../editor.h\"\n#include \"../blocks.h\"\n#include \"../sorting.h\"\n#include \"../config.h\"\n#include \"../main/trees.h\"\n#include \"../npc_id.h\"\n#include \"../eff_id.h\"\n#include \"../layers.h\"\n\n#include \"main/game_loop_interrupt.h\"\n\n#include \"game_main.h\"\n#include \"npc_traits.h\"\n#include \"phys_env.h\"\n\n#include \"npc/npc_queues.h\"\n#include \"npc/section_overlap.h\"\n#include \"npc/npc_update/npc_update_priv.h\"\n\n// moved into the function, as a static array\n// static RangeArr<int, 0, maxNPCs> newAct;\n// Why this array is here? to don't reallocate it every call of UpdateNPCs()\n\nvoid CheckNPCWidth(NPC_t& n)\n{\n    if(num_t::fEqual_d(n.Location.Width, 32))\n    {\n        if(n.Type != NPCID_CONVEYOR && n.Type != NPCID_STATUE_S3)\n        {\n            // If .Type = 58 Or .Type = 21 Then\n            if(!(NPCIsAnExit(n) || n.Type == NPCID_PLANT_S3 || n.Type == NPCID_BOTTOM_PLANT ||\n                 n.Type == NPCID_SIDE_PLANT || n.Type == NPCID_BIG_PLANT || n.Type == NPCID_LONG_PLANT_UP ||\n                 n.Type == NPCID_LONG_PLANT_DOWN || n.Type == NPCID_PLANT_S1 || n.Type == NPCID_FIRE_PLANT))\n            {\n                n.Location.X += 0.015_n;\n            }\n\n            n.Location.Width -= 0.03_n;\n        }\n    }\n    else if(num_t::fEqual_d(n.Location.Width, 256))\n        n.Location.Width = 255.9_n;\n    else if(num_t::fEqual_d(n.Location.Width, 128))\n        n.Location.Width = 127.9_n;\n\n    // unset a bunch of variables that would normally decay for inactive NPCs\n    if(!n.Active)\n    {\n        n.tempBlock = 0;\n\n        n.TailCD = 0;\n        n.Immune = 0;\n\n        // Failing to update Location.SpeedX causes inaccurate initial movement speed of some NPCs, including squids\n        //   (cross-ref UpdateNPCs condition RealSpeedX != 0 for Active NPCs.)\n        // Technically, the final values should depend on the value of speedVar,\n        //   which invalidly exchanged information between loop iterations,\n        //   but in the valid case this is the outcome.\n        //   (cross-ref UpdateNPCs clause updating RealSpeedX for Active, formerly all, NPCs)\n        if(n.RealSpeedX != 0)\n        {\n            n.Location.SpeedX = (num_t)n.RealSpeedX;\n            n.RealSpeedX = 0;\n        }\n\n        if(!n.Projectile || n.Type == NPCID_TOOTHY || n.Type == NPCID_TANK_TREADS)\n            n.Multiplier = 0;\n    }\n}\n\nbool UpdateNPCs()\n{\n    // this is 1 of the 2 clusterfuck subs in the code, be weary\n\n    // misc variables used mainly for arrays\n    // int A = 0;\n    // int B = 0;\n//    float C = 0;\n//    float D = 0;\n//    double E = 0;\n//    float F = 0;\n\n//    std::string tempStr;\n    // int oldSlope = 0; // previous sloped block the npc was on\n//    NPC_t tempNPC;\n    // int HitSpot = 0; // used for collision detection\n    // double tempHit = 0;\n    // double tempHitOld = 0;\n    // Block_t tmpBlock;\n    // int tempHitBlock = 0;\n    // int tempHitOldBlock = 0;\n    // int tempHitIsSlope = 0;\n    // float tempSpeedA = 0;\n//    float tempSpeedB = 0;\n    // bool tempTurn = false; // used for turning the npc around\n    // Location_t tempLocation;\n    // Location_t tempLocation2;\n    // Location_t preBeltLoc;\n    // float beltCount = 0;\n    // int tempBlockHit[3] = {0}; // Hit block from below code\n    // int winningBlock = 0; // More code for hitting the block from below\n    int numTempBlock = 0;\n    // float speedVar = 0; // percent of the NPC it should actually moved. this helps when underwater\n\n    // bool tempBool = false;\n//    bool tempBool2 = false;\n//    bool tempBool3 = false;\n    // float newY = 0; // fully unused\n//    bool straightLine = false;\n    const Block_t blankBlock;\n//    bool noBelt = false;\n    // float oldBeltSpeed = 0;\n//    float beltFixX = 0;\n    // int oldDirection = 0;\n\n    // used for collision detection\n    // int64_t fBlock = 0;\n    // int64_t lBlock = 0;\n    // int64_t fBlock2 = 0;\n    // int64_t lBlock2 = 0;\n    // int bCheck2 = 0;\n    // int bCheck = 0;\n    // float addBelt = 0;\n    // int numAct = 0;\n    // bool beltClear = false; // stops belt movement when on a wall\n    // bool resetBeltSpeed = false;\n    // double PlrMid = 0;\n    // double Slope = 0;\n    // bool SlopeTurn = false;\n//    std::string timeStr;\n\n    num_t lyrX = 0; // for attaching to layers\n    num_t lyrY = 0; // for attaching to layers\n\n    auto activation_it = NPCQueues::Active.may_insert_erase.begin();\n\n    switch(g_gameLoopInterrupt.site)\n    {\n    case GameLoopInterrupt::UpdateNPCs_Activation_Generator:\n    case GameLoopInterrupt::UpdateNPCs_Activation_Self:\n    case GameLoopInterrupt::UpdateNPCs_Activation_Chain:\n        goto resume_Activation;\n    case GameLoopInterrupt::UpdateNPCs_KillNPC:\n        goto resume_KillNPC;\n    default:\n        break;\n    }\n\n    // newAct.fill(0);\n\n    NPC[0].Location.SpeedX = 0;\n    NPC[0].Location.SpeedY = 0;\n\n    if(LevelMacro > LEVELMACRO_OFF)\n    {\n        if(PSwitchTime > 0)\n            PSwitchTime = 100;\n        if(PSwitchStop > 0)\n            PSwitchStop = 100;\n    }\n\n    // used for the p switches\n    if(PSwitchStop > 0) // time stop\n    {\n        if(PSwitchStop == Physics.NPCPSwitch)\n        {\n            StopMusic();\n            StartMusic(-1);\n            PlaySound(SFX_PSwitch);\n        }\n\n        if(PSwitchTime > 2)\n            PSwitchTime = 2;\n\n        PSwitchStop -= 1;\n\n        if(PSwitchStop <= 0)\n        {\n            FreezeNPCs = false;\n            SwitchEndResumeMusic();\n        }\n    }\n\n    if(FreezeNPCs) // When time is paused\n    {\n        StopHit = 0;\n\n        // handle active NPCs\n        for(; activation_it != NPCQueues::Active.may_insert_erase.end(); ++activation_it)\n        {\n            int A = *activation_it;\n            if(NPCIsBoot(NPC[A]) || NPCIsYoshi(NPC[A]))\n            {\n                if(NPC[A].CantHurt > 0)\n                {\n                    NPC[A].CantHurt -= 1;\n                    if(NPC[A].CantHurt == 0)\n                        NPC[A].CantHurtPlayer = 0;\n                }\n            }\n\n            if(NPC[A].TimeLeft > 0)\n                NPC[A].TimeLeft -= 1;\n\n            if(NPC[A].Immune > 0)\n                NPC[A].Immune -= 1;\n\n            NPC[A].Chat = false;\n\n            if(NPC[A].TimeLeft == 0)\n            {\n                Deactivate(A);\n\n                if(g_config.fix_timestop_respawn)\n                {\n                    // NPCMovementLogic gets called here to reset the NPC's position -- important for plants\n                    if(!NPC[A].Generator)\n                    {\n                        tempf_t speedVar = 1;\n                        NPCMovementLogic(A, speedVar);\n                    }\n\n                    // don't keep deactivating the NPC every frame\n                    NPC[A].TimeLeft = -1;\n                }\n            }\n\n            if(NPC[A].JustActivated)\n            {\n                // update the NPC's position -- important for bullets and jumping fish\n                if(g_config.fix_timestop_respawn && !NPC[A].Generator)\n                {\n                    NPCActivationLogic(A);\n\n                    if(NPC[A].TriggerActivate != EVENT_NONE)\n                        TriggerEvent(NPC[A].TriggerActivate, 0);\n                }\n\n                NPC[A].JustActivated = 0;\n                NPCQueues::update(A);\n            }\n        }\n\n        // keep inactive NPCs from respawning during FreezeNPCs (SMBX 1.3 behavior)\n        for(int A : NPCQueues::Unchecked)\n        {\n            if(A <= numNPCs && NPC[A].TimeLeft == 0)\n            {\n                NPC[A].Reset[1] = false;\n                NPC[A].Reset[2] = false;\n                NPCQueues::NoReset.push_back(A);\n            }\n        }\n\n        goto kill_NPCs_and_CharStuff;\n    }\n\n\n    if(CoinMode) // this is a cheat code\n    {\n        if(!g_config.modern_lives_system && Lives >= 99 && Coins >= 99)\n            CoinMode = false;\n        else\n        {\n            PlaySound(SFX_Coin);\n            Coins += 1;\n            if(Coins >= 100)\n            {\n                if(g_config.modern_lives_system)\n                    CoinMode = false;\n\n                Got100Coins();\n            }\n        }\n    }\n\n\n\n    // need this complex loop syntax because RespawnDelay can be modified within it\n    for(auto it = NPCQueues::RespawnDelay.begin(); it != NPCQueues::RespawnDelay.end();)\n    {\n        int A = *(it++);\n\n        if(NPC[A].RespawnDelay && !NPC[A].Active && NPC[A].Effect2 > 0)\n        {\n            NPC[A].Reset[1] = false;\n            NPC[A].Reset[2] = false;\n            NPC[A].Effect2 -= 1;\n\n            NPCQueues::NoReset.push_back(A);\n        }\n        else\n        {\n            // don't reset Effect2 in case the NPC is somehow using it now\n            NPC[A].RespawnDelay = false;\n            NPCQueues::RespawnDelay.erase(A);\n        }\n    }\n\n    for(int A = maxNPCs - 100 + 1; A <= numNPCs; A++)\n    {\n        // if(A > maxNPCs - 100)\n        NPC[A].Killed = 9;\n        NPCQueues::Killed.push_back(A);\n    }\n\n    for(int A : NPCQueues::Unchecked)\n        CheckNPCWidth(NPC[A]);\n\n    // gets restored by resumption here\n    int numNPCsMax;\n    numNPCsMax = numNPCs;\n\n    // need this complex loop syntax because Active can be modified within it\n    for(; activation_it != NPCQueues::Active.may_insert_erase.end(); ++activation_it)\n    {\n        int A;\n        if(true)\n        {\n            A = *activation_it;\n        }\n        else\n        {\nresume_Activation:\n            A = g_gameLoopInterrupt.A;\n            numNPCsMax = g_gameLoopInterrupt.B;\n            activation_it.last_val = A;\n            NPCQueues::Active.invalidate();\n\n            switch(g_gameLoopInterrupt.site)\n            {\n            case GameLoopInterrupt::UpdateNPCs_Activation_Generator:\n                goto resume_Activation_Generator;\n            case GameLoopInterrupt::UpdateNPCs_Activation_Self:\n                goto resume_Activation_Self;\n            case GameLoopInterrupt::UpdateNPCs_Activation_Chain:\n                goto resume_Activation_Chain;\n            default:\n                break;\n            }\n        }\n\n        if(A > numNPCsMax)\n            break;\n\n        if(NPC[A].Hidden)\n        {\n            Deactivate(A);\n            CheckNPCWidth(NPC[A]);\n        }\n        else if(NPC[A].TailCD > 0)\n            NPC[A].TailCD -= 1;\n\n        // generator code\n        if(NPC[A].Generator)\n        {\n            NPC[A].Active = false;\n\n            if(!NPC[A].Hidden)\n            {\n                if(NPCGeneratorLogic(A))\n                {\n                    NPCGeneratorMakeNew(A);\n\n                    if(NPC[numNPCs].TriggerActivate != EVENT_NONE)\n                    {\n                        eventindex_t resume_index;\n                        resume_index = ProcEvent_Safe(false, NPC[numNPCs].TriggerActivate, 0);\n                        while(resume_index != EVENT_NONE)\n                        {\n                            g_gameLoopInterrupt.C = resume_index;\n                            g_gameLoopInterrupt.site = GameLoopInterrupt::UpdateNPCs_Activation_Generator;\n                            goto interrupt_Activation;\n\nresume_Activation_Generator:\n                            resume_index = g_gameLoopInterrupt.C;\n                            g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n                            resume_index = ProcEvent_Safe(true, resume_index, 0);\n                        }\n                    }\n\n                    if(NPC[numNPCs].Type == NPCID_RANDOM_POWER)\n                        NPC[numNPCs].Type = RandomBonus();\n                }\n            }\n\n            // skip the rest of this logic unless NPC is a force-active type (in which case the result will be buggy)\n            if(!NPCQueues::check_active_type(NPC[A]))\n                continue;\n        }\n\n        // Force-active NPCs, part 1\n        if(NPC[A].Type == NPCID_CONVEYOR && !NPC[A].Hidden)\n        {\n            CheckSectionNPC(A);\n            bool sameSection = false;\n            for(int B = 1; B <= numPlayers; B++)\n            {\n                if(Player[B].Section == NPC[A].Section)\n                    sameSection = true;\n            }\n\n            if(sameSection)\n            {\n                NPC[A].TimeLeft = 100;\n                NPC[A].Active = true;\n                NPC[A].JustActivated = 0;\n            }\n        }\n        else if(NPC[A].Type == NPCID_STATUE_POWER || NPC[A].Type == NPCID_HEAVY_POWER)\n        {\n            if(NPC[A].TimeLeft == 1 || NPC[A].JustActivated != 0)\n                NPC[A].Frame = EditorNPCFrame(NPC[A].Type, NPC[A].Direction, A);\n        }\n        // platforms returned to their SMBX 1.3 position below chain activation logic to prevent mistaken chain-activations\n\n        // process chain activations\n        if(NPC[A].JustActivated != 0)\n        {\n            static std::array<vbint_t, maxNPCs> newAct;\n            int numAct;\n            numAct = 0;\n\n            if(NPC[A].Active && NPC[A].TimeLeft > 1 &&\n               NPC[A].Type != NPCID_CONVEYOR && NPC[A].Type != NPCID_FALL_BLOCK_RED &&\n               NPC[A].Type != NPCID_FALL_BLOCK_BROWN && !NPC[A]->IsACoin) // And .Type <> NPCID_SPIKY_THROWER\n            {\n                // if activated by a shared screen, don't make the event player-specific\n                int activ_player;\n                {\n                    const vScreen_t& activ_vscreen = vScreen[NPC[A].JustActivated];\n                    const Screen_t& activ_screen = Screens[activ_vscreen.screen_ref];\n                    bool shared_screen = (activ_screen.player_count > 1) && (activ_screen.active_end() - activ_screen.active_begin() == 1);\n\n                    activ_player = (shared_screen) ? 0 : activ_vscreen.player;\n                }\n\n                if(NPC[A].TriggerActivate != EVENT_NONE)\n                {\n                    eventindex_t resume_index;\n                    resume_index = ProcEvent_Safe(false, NPC[A].TriggerActivate, activ_player);\n                    while(resume_index != EVENT_NONE)\n                    {\n                        g_gameLoopInterrupt.C = resume_index;\n                        g_gameLoopInterrupt.D = activ_player;\n                        g_gameLoopInterrupt.site = GameLoopInterrupt::UpdateNPCs_Activation_Self;\n                        goto interrupt_Activation;\n\nresume_Activation_Self:\n                        resume_index = g_gameLoopInterrupt.C;\n                        activ_player = g_gameLoopInterrupt.D;\n                        numAct = 0;\n                        g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n                        resume_index = ProcEvent_Safe(true, resume_index, activ_player);\n                    }\n                }\n\n                newAct[numAct] = A;\n                numAct++;\n\n                // chain activation\n                int C;\n                for(C = 0; C < numAct; C++)\n                {\n                    if(NPC[newAct[C]].Type != NPCID_CONVEYOR && NPC[newAct[C]].Type != NPCID_FALL_BLOCK_RED &&\n                       NPC[newAct[C]].Type != NPCID_FALL_BLOCK_BROWN && (C == 0 || NPC[newAct[C]].Type != NPCID_SPIKY_THROWER) &&\n                       !NPC[newAct[C]]->IsACoin)\n                    {\n                        // start of NPCs to check for events (not preserved by resume)\n                        int activated_by_C_begin;\n                        activated_by_C_begin = numAct;\n\n                        {\n                            Location_t tempLocation2 = NPC[newAct[C]].Location;\n                            tempLocation2.Y -= 32;\n                            tempLocation2.X -= 32;\n                            tempLocation2.Width += 64;\n                            tempLocation2.Height += 64;\n\n                            for(int B : treeNPCQuery(tempLocation2, SORTMODE_ID))\n                            {\n                                // In SMBX 1.3, Deactivate was called every frame for every Hidden NPC (in this loop over A). That set Reset to false. Now we need to emulate it.\n                                bool reset_should_be_false = (B < A && NPC[B].Hidden);\n\n                                if(!NPC[B].Active && B != A\n                                    && !reset_should_be_false\n                                    && (C == 0 || !NPC[B].Hidden || !g_config.fix_npc_activation_event_loop_bug)\n                                    && NPC[B].Reset[1] && NPC[B].Reset[2])\n                                {\n                                    if(CheckCollision(tempLocation2, NPC[B].Location))\n                                    {\n                                        SDL_assert_release(numAct < maxNPCs);\n                                        newAct[numAct] = B;\n                                        numAct++;\n\n                                        NPC[B].Active = true;\n                                        NPC[B].TimeLeft = NPC[newAct[C]].TimeLeft;\n                                        NPC[B].Section = NPC[newAct[C]].Section;\n\n                                        if(g_config.fix_npc_camera_logic)\n                                            NPC[B].JustActivated = NPC[A].JustActivated;\n                                        else\n                                            NPC[B].JustActivated = 1;\n\n                                        NPCQueues::Active.insert(B);\n\n                                        // event for B was previously triggered here\n                                        // this should not be a logic change from SMBX 1.3:\n                                        // - ShowLayer turns Hidden NPCs (which can't be activated) into already-active NPCs\n                                        // - HideLayer calls Deactivate on NPCs, so their Reset flags will be false\n                                    }\n                                }\n                                else if(C == 0 && B != A && NPC[B].Active && NPC[B].TimeLeft < NPC[A].TimeLeft - 1)\n                                {\n                                    if(CheckCollision(tempLocation2, NPC[B].Location))\n                                        NPC[B].TimeLeft = NPC[A].TimeLeft - 1;\n                                }\n                            }\n                        }\n\n                        // trigger events for all NPCs activated by C (outside of the query loop above)\n\n                        // restored by resume\n                        int i;\n                        for(i = activated_by_C_begin; i < numAct; i++)\n                        {\n                            // not used by resume\n                            int B;\n                            B = newAct[i];\n\n                            if(B < A)\n                            {\n                                if(NPC[B].TriggerActivate != EVENT_NONE)\n                                {\n                                    eventindex_t resume_index;\n                                    resume_index = ProcEvent_Safe(false, NPC[B].TriggerActivate, activ_player);\n                                    while(resume_index != EVENT_NONE)\n                                    {\n                                        g_gameLoopInterrupt.C = resume_index;\n                                        g_gameLoopInterrupt.D = activ_player;\n                                        g_gameLoopInterrupt.E = numAct;\n                                        g_gameLoopInterrupt.F = C;\n                                        g_gameLoopInterrupt.G = i;\n\n                                        g_gameLoopInterrupt.site = GameLoopInterrupt::UpdateNPCs_Activation_Chain;\n                                        goto interrupt_Activation;\n\nresume_Activation_Chain:\n                                        resume_index = g_gameLoopInterrupt.C;\n                                        activ_player = g_gameLoopInterrupt.D;\n                                        numAct = g_gameLoopInterrupt.E;\n                                        C = g_gameLoopInterrupt.F;\n                                        i = g_gameLoopInterrupt.G;\n\n                                        g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n                                        resume_index = ProcEvent_Safe(true, resume_index, activ_player);\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            } // NPC[A].Active ...\n\n            if(NPC[A].Type == NPCID_BOSS_CASE)\n            {\n                for(int B : NPCQueues::Active.no_change)\n                {\n                    if(NPC[B].Type != NPCID_BOSS_CASE && NPC[B].Effect == NPCEFF_NORMAL && NPC[B].Active)\n                    {\n                        if(!NPC[B]->NoClipping)\n                        {\n                            // TRIES to check if A contains B, but actually checks the Y axis only\n                            // cross-reference the NPC Effect 208 code, which fixes this but wastes a frame\n                            if(NPC[A].Location.Y < NPC[B].Location.Y)\n                            {\n                                if(NPC[A].Location.Y + NPC[A].Location.Height > NPC[B].Location.Y + NPC[B].Location.Height)\n                                {\n                                    if(NPC[A].Location.Y < NPC[B].Location.Y)\n                                    {\n                                        if(NPC[A].Location.Y + NPC[A].Location.Height > NPC[B].Location.Y + NPC[B].Location.Height)\n                                        {\n                                            NPC[B].Frame = EditorNPCFrame(NPC[B].Type, NPC[B].Direction);\n                                            NPC[B].Effect = NPCEFF_ENCASED;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            } // NPCID_BOSS_CASE\n        } // .JustActivated != 0\n\n        // force-activate platforms (but don't mistakenly chain-activate -- doing this logic above caused a bug from v1.3.6.1 until v1.3.7.1)\n        if(NPC[A].Type == NPCID_YEL_PLATFORM || NPC[A].Type == NPCID_BLU_PLATFORM || NPC[A].Type == NPCID_GRN_PLATFORM || NPC[A].Type == NPCID_RED_PLATFORM)\n        {\n            NPC[A].Active = true;\n            NPC[A].TimeLeft = 100;\n        }\n\n#if 0\n        // this code became the function CheckNPCWidth above\n        if(fEqual(NPC[A].Location.Width, 32.0))\n        {\n            if(NPC[A].Type != NPCID_CONVEYOR && NPC[A].Type != NPCID_STATUE_S3)\n            {\n                // If .Type = 58 Or .Type = 21 Then\n                if(!(NPCIsAnExit(NPC[A]) || NPC[A].Type == NPCID_PLANT_S3 || NPC[A].Type == NPCID_BOTTOM_PLANT ||\n                     NPC[A].Type == NPCID_SIDE_PLANT || NPC[A].Type == NPCID_BIG_PLANT || NPC[A].Type == NPCID_LONG_PLANT_UP ||\n                     NPC[A].Type == NPCID_LONG_PLANT_DOWN || NPC[A].Type == NPCID_PLANT_S1 || NPC[A].Type == NPCID_FIRE_PLANT))\n                {\n                    NPC[A].Location.X += 0.015;\n                }\n\n                NPC[A].Location.Width -= 0.03;\n            }\n        }\n        else if(fEqual(NPC[A].Location.Width, 256.0))\n        {\n            NPC[A].Location.Width = 255.9;\n        }\n        else if(fEqual(NPC[A].Location.Width, 128.0))\n        {\n            NPC[A].Location.Width = 127.9;\n        }\n#endif\n\n        // construct tempBlock tree\n        NPC[A].tempBlock = 0;\n        NPC[A].tempBlockInTree = false;\n\n        if(NPC[A].Active && NPC[A].TimeLeft > 1)\n        {\n            if(NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 1)\n            {\n                if(NPC[A].Projectile)\n                    NPC[A].Special2 = 0;\n                else\n                {\n                    NPC[A].Special2 += 1;\n                    if(NPC[A].Special2 >= 450)\n                    {\n                        NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                        NPC[A].Killed = 9;\n                        NPCQueues::Killed.push_back(A);\n                    }\n                }\n            }\n\n            else if(NPC[A]->IsABlock || NPC[A]->IsAHit1Block || (NPC[A]->CanWalkOn && !(NPC[A]->IsFish && NPC[A].Special == 2)))\n            {\n                if(\n                    (\n                        !NPC[A].Projectile && NPC[A].HoldingPlayer == 0 &&\n                        NPC[A].Effect == NPCEFF_NORMAL && /* !(NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 1) && */\n                       !((NPC[A].Type == NPCID_FALL_BLOCK_RED || NPC[A].Type == NPCID_FALL_BLOCK_BROWN) && NPC[A].Special == 1)\n                    ) || NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG ||\n                    NPC[A].Type == NPCID_VPIPE_SHORT || NPC[A].Type == NPCID_VPIPE_LONG\n                )\n                {\n                    numBlock++;\n\n                    Block[numBlock] = blankBlock;\n                    Block[numBlock].Type = 0;\n                    Block[numBlock].Location = NPC[A].Location;\n                    Block[numBlock].Location.Y = num_t::floor(Block[numBlock].Location.Y + 0.02_n);\n                    Block[numBlock].tempBlockVehiclePlr = NPC[A].vehiclePlr;\n                    Block[numBlock].tempBlockVehicleYOffset = NPC[A].vehicleYOffset;\n                    Block[numBlock].tempBlockNpcIdx = A;\n\n                    if(NPC[A].Type == NPCID_VEHICLE)\n                        Block[numBlock].Type = 25;\n\n                    if(NPC[A]->IsAHit1Block || (NPC[A]->CanWalkOn && !NPC[A]->IsABlock))\n                        Block[numBlock].Type = 26;\n\n                    // moved to Block_t::tempBlockNoProjClipping(), defined in npc_traits.h\n                    // if(NPC[A]->CanWalkOn && !NPC[A]->IsAHit1Block && !NPC[A]->IsABlock)\n                    //     Block[numBlock].noProjClipping = true;\n\n                    if(NPC[A].Type == NPCID_SPRING && Block[numBlock].Location.Height != 32)\n                    {\n                        Block[numBlock].Location.Y -= 16;\n                        Block[numBlock].Location.Height += 16;\n                    }\n\n                    Block[numBlock].Location.SpeedX += (num_t)NPC[A].BeltSpeed;\n                    Block[numBlock].tempBlockNpcType = NPC[A].Type;\n                    // not syncing the block layer here because we'll sync all of them together later\n                    numTempBlock++;\n                    NPC[A].tempBlock = numBlock;\n                }\n            }\n        }\n\n        if(false)\n        {\ninterrupt_Activation:\n            g_gameLoopInterrupt.A = A;\n            g_gameLoopInterrupt.B = numNPCsMax;\n            return true;\n        }\n    }\n\n    // only need to add NPC temp blocks to the quadtree when they are at a diff loc than the NPC\n    treeTempBlockEnable();\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Mount == 2)\n        {\n            numBlock++;\n            Block[numBlock] = blankBlock;\n            Block[numBlock].Type = 25;\n            Block[numBlock].Location = Player[A].Location;\n            Block[numBlock].Location.X = num_t::floor(Block[numBlock].Location.X) + 1;\n            Block[numBlock].Location.Y = num_t::floor(Block[numBlock].Location.Y) + 1;\n            Block[numBlock].Location.Width = num_t::floor(Block[numBlock].Location.Width) + 1;\n            Block[numBlock].tempBlockVehiclePlr = A;\n\n            // delay add to below if it will be sorted\n            if(!g_config.emulate_classic_block_order)\n                treeTempBlockAdd(numBlock);\n\n            numTempBlock++;\n        }\n    }\n\n    // need to sort the temp blocks in strict compatibility mode, to fully emulate the specific way that switched block clipping works in X64 (#739)\n    if(g_config.emulate_classic_block_order)\n    {\n        if(numTempBlock > 1)\n            qSortBlocksX(numBlock + 1 - numTempBlock, numBlock);\n\n        // restore tempBlock tracking\n        for(int A = numBlock + 1 - numTempBlock; A <= numBlock; A++)\n        {\n            if(Block[A].tempBlockNpcIdx > 0)\n                NPC[Block[A].tempBlockNpcIdx].tempBlock = A;\n            else if(Block[A].tempBlockVehiclePlr > 0)\n                treeTempBlockAdd(A);\n        }\n    }\n\n    for(int A : NPCQueues::Unchecked)\n    {\n        if(NPC[A].Active)\n            continue;\n\n        CheckNPCWidth(NPC[A]);\n\n        if(NPCQueues::check_active(NPC[A]))\n            continue;\n\n        // check for inactive NPCs that are falling off\n        if(NPC[A].JustActivated == 0 && !(NPC[A]->IsFish && NPC[A].Special == 2) && NPC[A].Type != NPCID_LAVABUBBLE)\n        {\n            if(!GameMenu && NPC[A].Location.Y > level[NPC[A].Section].Height + 16)\n            {\n                StopHit = 0;\n                NPCHit(A, 9);\n            }\n        }\n\n        // check for inactive NPCs that got the wrong location\n        if(NPC[A].Hidden)\n        {\n            // essential part of Deactivate(A);\n            NPC[A].Location.X = NPC[A].DefaultLocationX;\n            NPC[A].Location.Y = NPC[A].DefaultLocationY;\n            NPC[A].Location.SpeedX = 0;\n            NPC[A].Location.SpeedY = 0;\n        }\n    }\n\n    NPCQueues::Unchecked.clear();\n\n\n    // need this complex loop syntax because Active can be modified within it\n    for(int A : NPCQueues::Active.may_insert_erase)\n    {\n        if(NPC[A].AttLayer != LAYER_NONE)\n        {\n            lyrX = NPC[A].Location.X;\n            lyrY = NPC[A].Location.Y;\n        }\n\n        // all of these are moved into the Active section\n#if 0\n        Physics.NPCGravity = Physics.NPCGravityReal;\n\n        lyrX = NPC[A].Location.X;\n        lyrY = NPC[A].Location.Y;\n\n        if(NPC[A].RealSpeedX != 0)\n        {\n            NPC[A].Location.SpeedX = NPC[A].RealSpeedX;\n            NPC[A].RealSpeedX = 0;\n        }\n\n\n        StopHit = 0;\n        if(!NPC[A].Projectile || NPC[A].Type == NPCID_TOOTHY || NPC[A].Type == NPCID_TANK_TREADS)\n            NPC[A].Multiplier = 0;\n        if(NPC[A].Immune > 0)\n            NPC[A].Immune -= 1;\n        if(NPC[A].Type == NPCID_VEHICLE && NPC[A].TimeLeft > 1)\n            NPC[A].TimeLeft = 100;\n#endif\n\n        // Activation collisions\n        if(NPC[A].JustActivated != 0)\n            NPCActivationLogic(A);\n        // check for active NPCs that are falling off\n        else if(NPC[A].Location.Y > level[NPC[A].Section].Height && NPC[A].Location.Y > level[NPC[A].Section].Height + 16)\n        {\n            if(!GameMenu && !(NPC[A]->IsFish && NPC[A].Special == 2) && NPC[A].Type != NPCID_LAVABUBBLE)\n                NPCHit(A, 9);\n        }\n\n\n\n        // Normal operations start here\n\n\n        if(NPC[A]->IsACoin && NPC[A].Special == 0 && NPC[A].HoldingPlayer == 0 && !NPC[A].Inert && !NPC[A].Wings && NPC[A].Effect == NPCEFF_NORMAL && g_config.optimize_coins)\n        {\n            if(NPC[A].Active && NPC[A].Killed == 0 && !NPC[A].Generator)\n            {\n                if(NPC[A].TimeLeft > 10)\n                {\n                    if(NoTurnBack[NPC[A].Section])\n                       NPC[A].TurnBackWipe = true;\n                    else if(NPC[A].DefaultType)\n                        NPC[A].TimeLeft = 10;\n                }\n\n                if(NPC[A].TimeLeft < 1)\n                    Deactivate(A);\n\n                NPC[A].TimeLeft -= 1;\n\n                NPCFrames(A);\n            }\n        }\n        else if(NPC[A]->IsAVine)\n        {\n            // .Location.SpeedX = 0\n            // .Location.SpeedY = 0\n\n            // moved into UpdateGraphicsLogic, now only applies to visible vines\n            // if(NPC[A].Type == NPCID_GRN_VINE_S3 || NPC[A].Type == NPCID_RED_VINE_S3)\n            //     NPC[A].Frame = BlockFrame[5];\n            // else if(NPC[A].Type >= NPCID_GRN_VINE_S2 && NPC[A].Type <= NPCID_BLU_VINE_BASE_S2)\n            //     NPC[A].Frame = SpecialFrame[7];\n        }\n/////////////// BEGIN ACTIVE CODE /////////////////////////////////////////////////////////////\n        else if(NPC[A].Active && NPC[A].Killed == 0 && !NPC[A].Generator)\n        {\n            // don't worry about updating A's tree within this block -- it is done at the end if needed.\n            num_t prevX = NPC[A].Location.X;\n            num_t prevY = NPC[A].Location.Y;\n            num_t prevW = NPC[A].Location.Width;\n            num_t prevH = NPC[A].Location.Height;\n\n            // all this cleanup code was moved here from the top of the loop\n            Physics.NPCGravity = (num_t)Physics.NPCGravityReal;\n\n            StopHit = 0;\n\n            if(NPC[A].RealSpeedX != 0)\n            {\n                NPC[A].Location.SpeedX = (num_t)NPC[A].RealSpeedX;\n                NPC[A].RealSpeedX = 0;\n            }\n\n            if(!NPC[A].Projectile || NPC[A].Type == NPCID_TOOTHY || NPC[A].Type == NPCID_TANK_TREADS)\n                NPC[A].Multiplier = 0;\n            if(NPC[A].Immune > 0)\n                NPC[A].Immune -= 1;\n            if(NPC[A].Type == NPCID_VEHICLE && NPC[A].TimeLeft > 1)\n                NPC[A].TimeLeft = 100;\n\n            tempf_t speedVar = 1; // percent of the NPC it should actually moved. this helps when underwater\n\n            // dead code in VB6\n#if 0\n            if(NPC[A].Slope > 0 && !(NPC[A]->IsAShell || (NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 1)))\n            {\n                if((NPC[A].Location.SpeedX > 0 && BlockSlope[Block[NPC[A].Slope].Type] == -1) ||\n                   (NPC[A].Location.SpeedX < 0 && BlockSlope[Block[NPC[A].Slope].Type] == 1))\n                {\n                    if(!NPC[A]->CanWalkOn || NPC[A]->IsABlock || NPC[A].Type == NPCID_TANK_TREADS)\n                        speedVar = (float)(1 - Block[NPC[A].Slope].Location.Height / Block[NPC[A].Slope].Location.Width * 0.4);\n                }\n            }\n            speedVar = 1;\n#endif\n\n            if(!NPC[A].Projectile)\n                speedVar = (tempf_t)NPC[A]->Speedvar;\n\n            // water check\n\n            // Things immune to water's effects\n            if(NPC[A].Type == NPCID_LAVABUBBLE || NPC[A].Type == NPCID_BIG_BULLET || NPC[A].Type == NPCID_HEAVY_THROWN || NPC[A].Type == NPCID_CYCLONE_POWER\n                || NPC[A].Type == NPCID_GHOST_S3 || NPC[A].Type == NPCID_GHOST_FAST || NPC[A].Type == NPCID_GHOST_S4 || NPC[A].Type == NPCID_BIG_GHOST\n                || NPC[A].Type == NPCID_STATUE_FIRE || NPC[A].Type == NPCID_VILLAIN_FIRE || NPC[A].Type == NPCID_PET_FIRE || NPC[A].Type == NPCID_PLR_HEAVY\n                || NPC[A].Type == NPCID_CHAR4_HEAVY || NPC[A].Type == NPCID_GOALTAPE || NPC[A].Type == NPCID_SICK_BOSS_BALL || NPC[A].Type == NPCID_HOMING_BALL\n                || NPC[A].Type == NPCID_RED_VINE_TOP_S3 || NPC[A].Type == NPCID_GRN_VINE_TOP_S3 || NPC[A].Type == NPCID_GRN_VINE_TOP_S4 || NPC[A].Type == NPCID_SPIKY_THROWER\n                || NPC[A].Type == NPCID_ITEM_THROWER || NPC[A].Type == NPCID_SAW || NPC[A].Type == NPCID_JUMP_PLANT || NPC[A].Type == NPCID_MAGIC_BOSS_BALL\n                || (NPC[A]->IsACoin && NPC[A].Special == 0) || NPC[A].Type == NPCID_SWORDBEAM || NPC[A].Type == NPCID_FIRE_DISK || NPC[A].Type == NPCID_FIRE_CHAIN\n                || NPC[A].Type == NPCID_FLAG_EXIT)\n            {\n                NPC[A].Wet = 0;\n                NPC[A].Quicksand = 0;\n            }\n            else\n            {\n                if(NPC[A].Wet > 0)\n                    NPC[A].Wet -= 1;\n\n                if(NPC[A].Quicksand > 0)\n                    NPC[A].Quicksand -= 1;\n\n                if(UnderWater[NPC[A].Section] && NPC[A].Type != NPCID_BULLET)\n                    NPC[A].Wet = 2;\n\n                bool already_in_maze = (NPC[A].Effect == NPCEFF_MAZE);\n\n                for(int B : treeWaterQuery(NPC[A].Location, SORTMODE_NONE))\n                {\n                    if(!Water[B].Hidden)\n                    {\n                        if(Water[B].Type == PHYSID_MAZE)\n                        {\n                            if(NPC[A]->TWidth > 64 || NPC[A]->THeight > 64)\n                                continue;\n\n                            if(NPC[A].Effect != NPCEFF_NORMAL && NPC[A].Effect != NPCEFF_MAZE)\n                                continue;\n\n                            if(NPC[A].Effect == NPCEFF_MAZE && (already_in_maze || NPC[A].Effect2 < B))\n                                continue;\n\n                            if(NPCIsYoshi(NPC[A]) || NPCIsBoot(NPC[A]) || (NPC[A].Type == NPCID_WALL_SPARK || NPC[A].Type == NPCID_WALL_BUG || NPC[A].Type == NPCID_WALL_TURTLE))\n                                continue;\n\n                            if((NPCIsVeggie(NPC[A]) && NPC[A].Projectile) || (NPC[A]->NoClipping && !NPC[A].Projectile) || NPC[A].WallDeath || NPC[A].HoldingPlayer)\n                                continue;\n                        }\n                        else if(NPC[A].Type == NPCID_BULLET)\n                            continue;\n\n                        if(CheckCollision(NPC[A].Location, Water[B].Location))\n                        {\n                            if(Water[B].Type == PHYSID_MAZE)\n                            {\n                                NPC[A].Effect = NPCEFF_MAZE;\n                                NPC[A].Effect2 = B;\n\n                                if(NPC[A].Effect3 == 128)\n                                    NPC[A].Projectile = false;\n\n                                PhysEnv_Maze_PickDirection(NPC[A].Location, B, NPC[A].Effect3);\n\n                                // cancel if not currently moving down\n                                if(NPC[A].Effect3 == MAZE_DIR_DOWN)\n                                {\n                                    num_t rel_speed = NPC[A].Location.SpeedY - (num_t)Layer[Water[B].Layer].SpeedY;\n                                    if(rel_speed < 0 || (NPC[A]->IsABlock && rel_speed <= 4))\n                                        NPC[A].Effect = NPCEFF_NORMAL;\n                                }\n\n                                continue;\n                            }\n\n                            if(NPC[A].Wet == 0 && !NPC[A]->IsACoin)\n                            {\n                                if(NPC[A].Location.SpeedY >= 1 && (!g_config.fix_submerged_splash_effect || !CheckCollisionIntersect(NPC[A].Location, static_cast<Location_t>(Water[B].Location))))\n                                {\n                                    Location_t tempLocation;\n                                    tempLocation.Width = 32;\n                                    tempLocation.Height = 32;\n                                    tempLocation.X = NPC[A].Location.X + (NPC[A].Location.Width - tempLocation.Width) / 2;\n                                    tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height - tempLocation.Height;\n                                    NewEffect(EFFID_WATER_SPLASH, tempLocation);\n                                }\n\n                                if(!(NPC[A]->IsFish && NPC[A].Special == 1) && NPC[A].Type != NPCID_LEAF_POWER && NPC[A].Type != NPCID_PLR_FIREBALL)\n                                {\n                                    if(NPC[A].Location.SpeedY > 0.5_n)\n                                        NPC[A].Location.SpeedY = 0.5_n;\n                                    if(NPC[A].Location.SpeedY < -0.5_n)\n                                        NPC[A].Location.SpeedY = -0.5_n;\n                                }\n                                else\n                                {\n                                    if(NPC[A].Location.SpeedY > 2)\n                                        NPC[A].Location.SpeedY = 2;\n                                    if(NPC[A].Location.SpeedY < -2)\n                                        NPC[A].Location.SpeedY = -2;\n                                }\n\n                                // assigned to Special in SMBX 1.3\n                                if(NPC[A].Type == NPCID_PLATFORM_S3)\n                                    NPC[A].SpecialY = NPC[A].Location.SpeedY;\n                            }\n\n                            if(Water[B].Type == PHYSID_QUICKSAND)\n                                NPC[A].Quicksand = 2;\n\n                            NPC[A].Wet = 2;\n                        }\n                    }\n                }\n            }\n\n            if(NPC[A].Wet == 1 && NPC[A].Location.SpeedY < -1)\n            {\n                Location_t tempLocation;\n                tempLocation.Width = 32;\n                tempLocation.Height = 32;\n                tempLocation.X = NPC[A].Location.X + (NPC[A].Location.Width - tempLocation.Width) / 2;\n                tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height - tempLocation.Height;\n                NewEffect(EFFID_WATER_SPLASH, tempLocation);\n            }\n\n\n            if(NPC[A].Wet > 0)\n            {\n                if(NPC[A].Type == NPCID_ICE_CUBE)\n                {\n                    NPC[A].Projectile = true;\n                    Physics.NPCGravity = num_t(-(tempf_t)Physics.NPCGravityReal / 5);\n                }\n                else\n                    Physics.NPCGravity = num_t((tempf_t)Physics.NPCGravityReal / 5);\n\n                if(NPC[A].Type == NPCID_FLIPPED_RAINBOW_SHELL && NPC[A].Special4 == 1)\n                    NPC[A].Special5 = 0;\n                else if(!NPC[A]->IsFish && NPC[A].Type != NPCID_RAFT && NPC[A].Type != NPCID_WALL_BUG && NPC[A].Type != NPCID_WALL_SPARK && NPC[A].Type != NPCID_WALL_TURTLE)\n                    speedVar /= 2;\n                else if(NPC[A]->IsFish && NPC[A].Special == 2 && NPC[A].Location.SpeedY > 0)\n                    speedVar /= 2;\n\n                // new logic for iceballs fired upwards by a Polar Swim player\n                if(NPC[A].Type == NPCID_PLR_ICEBALL && NPC[A].Special4)\n                    Physics.NPCGravity /= 4;\n                // SMBX 1.3 logic: Terminal Velocity in water\n                else if(NPC[A].Location.SpeedY >= 3)\n                    NPC[A].Location.SpeedY = 3;\n                else if(NPC[A].Location.SpeedY < -3)\n                    NPC[A].Location.SpeedY = -3;\n\n                if(NPC[A].Type == NPCID_RAFT)\n                    NPC[A].Special2 = 10; // Set a counter of being dry while floating on water top\n            }\n            // as far as I'm aware it would make absolutely no difference if this did not happen for NPCID_RAFT\n            else if(NPC[A].Type == NPCID_RAFT || NPC[A]->IsFish)\n            {\n                if(NPC[A].Type == NPCID_RAFT && NPC[A].Special2 > 0)\n                    --NPC[A].Special2; // Once it's dry, count down\n\n                // detect if fish is out of water for an extended period so that it can clip through walls\n                NPC[A].WallDeath += 2;\n\n                if(NPC[A].WallDeath >= 10)\n                    NPC[A].WallDeath = 10;\n            }\n\n            if(NPC[A].Quicksand > 0 && !NPC[A]->NoClipping)\n            {\n                NPC[A].Location.SpeedY += 1;\n\n                if(NPC[A].Location.SpeedY < -1)\n                    NPC[A].Location.SpeedY = -1;\n                else if(NPC[A].Location.SpeedY > 0.5_n)\n                    NPC[A].Location.SpeedY = 0.5_n;\n\n                speedVar = speedVar * 3 / 10;\n            }\n\n            if(NPC[A].Type == NPCID_VEGGIE_RANDOM)\n            {\n                int B = iRand(9);\n                NPC[A].Type = NPCID(NPCID_VEGGIE_2 + B);\n                if(NPC[A].Type == NPCID_VEGGIE_RANDOM)\n                    NPC[A].Type = NPCID_VEGGIE_1;\n                NPC[A].Location.set_width_center(NPC[A]->TWidth);\n                NPC[A].Location.set_height_center(NPC[A]->THeight);\n                NPCQueues::Unchecked.push_back(A);\n            }\n\n            if(NPC[A].Text != STRINGINDEX_NONE)\n            {\n                NPC[A].Chat = false;\n                Location_t tempLocation = NPC[A].Location;\n                tempLocation.Y -= 25;\n                tempLocation.Height += 50;\n                tempLocation.X -= 25;\n                tempLocation.Width += 50;\n                for(int B = 1; B <= numPlayers; B++)\n                {\n                    if(CheckCollision(tempLocation, Player[B].Location))\n                        NPC[A].Chat = true;\n                }\n            }\n\n            // oldDirection = NPC[A].Direction;\n\n            if(NPC[A].Type == NPCID_BULLET || NPC[A].Type == NPCID_BIG_BULLET)\n            {\n                if(NPC[A].CantHurt > 0)\n                {\n                    NPC[A].CantHurt = 10000;\n                    if(NPC[A].Type == NPCID_BIG_BULLET)\n                        NPC[A].Location.SpeedX = 4 * NPC[A].Direction;\n                }\n\n                if(NPC[A].TimeLeft > 3 && !BattleMode)\n                    NPC[A].TimeLeft = 3;\n            }\n\n            if(NPC[A].Type == NPCID_MAGIC_BOSS || NPC[A].Type == NPCID_MAGIC_BOSS_SHELL || NPC[A].Type == NPCID_FIRE_BOSS || NPC[A].Type == NPCID_FIRE_BOSS_SHELL) // koopalings\n            {\n                if(NPC[A].TimeLeft > 1)\n                    NPC[A].TimeLeft = Physics.NPCTimeOffScreen;\n            }\n\n            CheckSectionNPC(A);\n\n            if((NPC[A].Type == NPCID_VILLAIN_S3 || NPC[A].Type == NPCID_FIRE_DISK || NPC[A].Type == NPCID_FIRE_CHAIN) && NPC[A].TimeLeft > 1)\n                NPC[A].TimeLeft = 100;\n\n            if(!(NPC[A].Type == NPCID_PLR_FIREBALL || (NPC[A]->IsFish && NPC[A].Special == 2) ||\n                 NPC[A].Type == NPCID_TOOTHY || NPC[A].Type == NPCID_VEHICLE || NPC[A].Type == NPCID_YEL_PLATFORM || NPC[A].Type == NPCID_BLU_PLATFORM ||\n                 NPC[A].Type == NPCID_GRN_PLATFORM || NPC[A].Type == NPCID_RED_PLATFORM || NPC[A].Type == NPCID_VILLAIN_S3 || NPCIsYoshi(NPC[A])) &&\n                 NPC[A].HoldingPlayer == 0)\n            {\n                int C = 0;\n                for(int B = 1; B <= numPlayers; B++)\n                {\n                    if(Player[B].Section == NPC[A].Section)\n                        C = 1;\n                }\n\n                if(C == 0 && NPC[A].TimeLeft > 1)\n                    NPC[A].TimeLeft = 0;\n            }\n\n            if((NPC[A].Type == NPCID_RED_VINE_TOP_S3 || NPC[A].Type == NPCID_GRN_VINE_TOP_S3 || NPC[A].Type == NPCID_GRN_VINE_TOP_S4) && NPC[A].TimeLeft > 10)\n                NPC[A].TimeLeft = 100;\n\n            if(NPC[A].TimeLeft > 10 && NoTurnBack[NPC[A].Section])\n                NPC[A].TurnBackWipe = true;\n\n            if(NPC[A].TimeLeft < 1)\n                Deactivate(A);\n\n            NPC[A].TimeLeft -= 1;\n\n            if(NPC[A].Effect == NPCEFF_NORMAL)\n            {\n                // this code is for NPCs that are being held by a player\n\n                if(NPC[A].HoldingPlayer > 0) // NPC is held\n                {\n                    NPC[A].vehiclePlr = 0;\n                    if(NPC[A].Type == NPCID_VEHICLE)\n                    {\n                        Player[NPC[A].HoldingPlayer].HoldingNPC = 0;\n                        NPC[A].HoldingPlayer = 0;\n                    }\n\n                    if(Player[NPC[A].HoldingPlayer].HoldingNPC == A && Player[NPC[A].HoldingPlayer].TimeToLive == 0 && !Player[NPC[A].HoldingPlayer].Dead) // Player and NPC are on the same page\n                    {\n                        NPC[A].Multiplier = 0;\n\n                        if(NPC[A].Type == NPCID_LIFT_SAND)\n                        {\n                            Player[NPC[A].HoldingPlayer].HoldingNPC = 0;\n                            NPC[A].HoldingPlayer = 0;\n                            NPC[A].Killed = 9;\n                            NPCQueues::Killed.push_back(A);\n                            NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                        }\n\n                        if(NPCIsYoshi(NPC[A]))\n                        {\n                            NPC[A].Special = NPC[A].Type;\n                            NPC[A].Type = NPCID_ITEM_POD;\n                        }\n\n                        // Unbury code (v1)\n                        if(NPC[A].Type == NPCID_ITEM_BURIED)\n                        {\n                            if(NPC[A].Special == 0)\n                                NPC[A].Special = NPCID_VEGGIE_RANDOM;\n\n                            NPCUnbury(A, 1);\n                            NPCQueues::Unchecked.push_back(A);\n                        }\n\n                        if(NPC[A].Type == NPCID_SLIDE_BLOCK)\n                        {\n                            NPC[A].Special = 1;\n                            NPC[A].Wings = WING_NONE;\n                        }\n\n                        if(NPC[A].Type == NPCID_SPIT_GUY_BALL)\n                        {\n                            NPC[A].Type = NPCID_COIN_S2;\n                            NPC[A].Location.set_height_center(NPC[A]->THeight);\n                            NPC[A].Location.set_width_center(NPC[A]->TWidth);\n\n                            NPCQueues::Unchecked.push_back(A);\n                        }\n\n                        NPC[A].TimeLeft = 100;\n                        NPC[A].BeltSpeed = 0;\n\n                        // dead code, impossible condition in SMBX 1.3 source (IsFish did not apply for NPC Type 1)\n                        // if(NPC[A].Type == (NPC[A]->IsFish && NPC[A].Special == 2))\n                        //     NPC[A].Special5 = 0;\n\n                        NPC[A].Direction = Player[NPC[A].HoldingPlayer].Direction; // Face the player\n                        NPC[A].Location.SpeedY = Player[NPC[A].HoldingPlayer].Location.SpeedY;\n                        NPC[A].Location.SpeedX = 0;\n\n                        NPCCollideHeld(A);\n\n                        if(NPC[A].Type == NPCID_ICE_BLOCK || NPC[A].Type == NPCID_ICE_CUBE) // Yoshi Ice\n                        {\n                            if(iRand(100) >= 93)\n                            {\n                                Location_t tempLocation;\n                                NewEffect_IceSparkle(NPC[A], tempLocation);\n                            }\n                        }\n                    }\n                    else // Player and NPC are not on the same page\n                    {\n                        Player[NPC[A].HoldingPlayer].HoldingNPC = 0;\n\n                        if(NPC[A].Type == NPCID_VINE_BUG)\n                            NPC[A].Projectile = true;\n\n                        NPC[A].Location.SpeedX = 0;\n                        NPC[A].Location.SpeedY = 0;\n\n                        if(NPC[A].Type == NPCID_HEAVY_THROWER)\n                        {\n                            NPC[A].Killed = 3;\n                            NPCQueues::Killed.push_back(A);\n                            NPC[A].Direction = -NPC[A].Direction;\n                        }\n\n                        if(NPC[A].Type == NPCID_BULLET)\n                        {\n                            PlaySoundSpatial(SFX_Bullet, NPC[A].Location);\n                            NPC[A].Location.SpeedX = 5 * NPC[A].Direction;\n                            NPC[A].Projectile = true;\n                            NPC[A].CantHurt = 1000;\n                            NPC[A].CantHurtPlayer = NPC[A].HoldingPlayer;\n                        }\n\n                        NPC[A].HoldingPlayer = 0;\n                    }\n                }\n                else // NPC is not held\n                {\n                    if(NPC[A].CantHurt <= 0)\n                        NPC[A].CantHurtPlayer = 0;\n                    // tempHit = 0;\n                    // tempHitBlock = 0;\n                    // tempBlockHit[1] = 0;\n                    // tempBlockHit[2] = 0;\n                    // winningBlock = 0;\n\n                    NPCSectionWrap(NPC[A]);\n\n                    if(NoTurnBack[NPC[A].Section] && NPC[A].Location.X < level[NPC[A].Section].X - NPC[A].Location.Width - 32)\n                        NPCHit(A, 9);\n\n                    if(NPC[A].CantHurt > 0)\n                    {\n                        if(NPC[A].Type != NPCID_CANNONENEMY)\n                            NPC[A].CantHurt -= 1;\n                    }\n                    else\n                        NPC[A].CantHurtPlayer = 0;\n\n                    if(NPC[A].Projectile)\n                    {\n                        if(NPC[A].CantHurtPlayer != 0)\n                            NPC[A].BattleOwner = NPC[A].CantHurtPlayer;\n                    }\n                    else\n                        NPC[A].BattleOwner = 0;\n\n                    if(NPC[A]->IsAShell)\n                    {\n                        NPC[A].Special4 -= 1;\n                        if(NPC[A].Special4 < 0)\n                            NPC[A].Special4 = 0;\n                    }\n\n                    if(NPC[A].TurnAround)\n                    {\n                        if((NPC[A].Type == NPCID_MAGIC_BOSS || NPC[A].Type == NPCID_FIRE_BOSS) && NPC[A].Special == 0) // larry koopa\n                        {\n                            if(NPC[A].Location.to_right_of(Player[NPC[A].Special5].Location))\n                            {\n                                if(NPC[A].Special2 < 0)\n                                    NPC[A].Special3 += 30;\n                                NPC[A].Special2 = -1;\n                            }\n                            else\n                            {\n                                if(NPC[A].Special2 > 0)\n                                    NPC[A].Special3 += 30;\n                                NPC[A].Special2 = 1;\n                            }\n\n                        }\n\n                        if(NPC[A].Type == NPCID_PLR_ICEBALL)\n                            NPCHit(A, 3, A);\n\n                        if(NPC[A]->IsAShell && NPC[A].Location.SpeedX != 0 && NPC[A].Special4 == 0)\n                        {\n                            NPC[A].Special4 = 5;\n                            Location_t tempLocation;\n                            tempLocation.Height = 0;\n                            tempLocation.Width = 0;\n                            tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height / 2 - 16;\n                            tempLocation.X = NPC[A].Location.X - 16;\n                            if(NPC[A].Direction == 1)\n                                tempLocation.X = NPC[A].Location.X + NPC[A].Location.Width - 16;\n                            NewEffect(EFFID_STOMP_INIT, tempLocation);\n                        }\n\n                        // was Special2\n                        if(NPC[A].Type == NPCID_SAW)\n                            NPC[A].SpecialX = -NPC[A].SpecialX;\n\n                        // Don't turn around if a shell or a fireball\n                        bool can_turn_around = !NPC[A]->IsAShell && NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_TANK_TREADS &&\n                           NPC[A].Type != NPCID_BULLET && NPC[A].Type != NPCID_VILLAIN_S3 && !NPCIsABot(NPC[A]) &&\n                           NPC[A].Type != NPCID_SPIT_BOSS_BALL && NPC[A].Type != NPCID_SPIT_GUY_BALL && !NPCIsVeggie(NPC[A]) &&\n                           NPC[A].Type != NPCID_ROCKET_WOOD && NPC[A].Type != NPCID_WALL_SPARK && NPC[A].Type != NPCID_WALL_BUG &&\n                           NPC[A].Type != NPCID_WALL_TURTLE && NPC[A].Type != NPCID_PLR_ICEBALL && NPC[A].Type != NPCID_SWORDBEAM;\n\n                        // wings override their parents' behavior\n                        if(NPC[A].Wings)\n                            can_turn_around = true;\n\n                        if(can_turn_around)\n                        {\n                            NPC[A].Location.SpeedX = -NPC[A].Location.SpeedX;\n                            if(NPC[A].tempBlock > 0)\n                                Block[NPC[A].tempBlock].Location.SpeedX = -Block[NPC[A].tempBlock].Location.SpeedX;\n                        }\n\n                        NPC[A].TurnAround = false;\n                    }\n\n                    // NPC Movement Code\n\n                    // Probably make a single function pointer for this whole block (until RESUME UNIFIED CODE),\n                    //   but create subroutines for the most commonly repeated code\n\n                    NPCMovementLogic(A, speedVar);\n\n                    // RESUME UNIFIED CODE\n\n                    // Block Collision\n\n                    if(NPC[A].Pinched.Bottom1 > 0)\n                        NPC[A].Pinched.Bottom1 -= 1;\n                    if(NPC[A].Pinched.Left2 > 0)\n                        NPC[A].Pinched.Left2 -= 1;\n                    if(NPC[A].Pinched.Top3 > 0)\n                        NPC[A].Pinched.Top3 -= 1;\n                    if(NPC[A].Pinched.Right4 > 0)\n                        NPC[A].Pinched.Right4 -= 1;\n                    if(NPC[A].Pinched.Moving > 0)\n                        NPC[A].Pinched.Moving -= 1;\n\n                    // removed, only ever read in NPCBlockLogic\n                    // NPC[A].onWall = false;\n                    if(NPC[A].Location.X < -(FLBlocks - 1) * 32)\n                        NPC[A].Location.X = -(FLBlocks - 1) * 32;\n                    if(NPC[A].Location.X + NPC[A].Location.Width > (FLBlocks + 1) * 32)\n                        NPC[A].Location.X = (FLBlocks + 1) * 32 - NPC[A].Location.Width;\n\n                    bool can_do_blocks = !(NPC[A]->IsACoin && NPC[A].Special == 0) && !(NPC[A].Type == NPCID_SLIDE_BLOCK && NPC[A].Special == 0) &&\n                       NPC[A].Type != NPCID_CONVEYOR && NPC[A].Type != NPCID_STATUE_FIRE && NPC[A].Type != NPCID_ITEM_BURIED && NPC[A].Type != NPCID_STAR_EXIT &&\n                       NPC[A].Type != NPCID_STAR_COLLECT && !(NPC[A].Type >= NPCID_PLATFORM_S3 && NPC[A].Type <= NPCID_PLATFORM_S1) &&\n                       NPC[A].Type != NPCID_LIFT_SAND && NPC[A].Type != NPCID_CHECKPOINT && NPC[A].Type != NPCID_SICK_BOSS_BALL &&\n                       !(NPC[A].Type == NPCID_PLANT_FIREBALL || NPC[A].Type == NPCID_LOCK_DOOR || NPC[A].Type == NPCID_FIRE_DISK || NPC[A].Type == NPCID_FIRE_CHAIN) &&\n                       !(NPCIsAnExit(NPC[A]) && ((NPC[A].DefaultLocationX == NPC[A].Location.X && NPC[A].DefaultLocationY == NPC[A].Location.Y) || NPC[A].Inert));\n\n                    // wings override their parents' behavior\n                    if(NPC[A].Wings)\n                        can_do_blocks = true;\n\n                    if(can_do_blocks)\n                    {\n                        // only the top half of the saw collides with blocks (gets restored after block collisions)\n                        if(NPC[A].Type == NPCID_SAW)\n                        {\n                            PlaySoundSpatial(SFX_Saw, NPC[A].Location);\n                            NPC[A].Location.Height = 24;\n                        }\n\n                        num_t tempHit = 0; // height of block NPC is walking on\n                        int tempHitBlock = 0; // index of block NPC is walking on\n                        tempf_t tempSpeedA = 0; // speed of ground the NPC is possibly standing on\n\n                        NPCBlockLogic(A, tempHit, tempHitBlock, tempSpeedA, numTempBlock, speedVar);\n\n\n                        // End Block Collision\n                        // possible usually-nulled function pointer for logic between block and NPC collisions\n\n                        if(NPC[A]->IsAShell)\n                        {\n                            if(NPC[A].Special > 0)\n                            {\n                                NPC[A].Location.SpeedX *= 0.9_r;\n                                NPC[A].Frame = 0;\n                                NPC[A].FrameCount = 0;\n                                if(NPC[A].Location.SpeedX > -0.3_n && NPC[A].Location.SpeedX < 0.3_n)\n                                {\n                                    NPC[A].Location.SpeedX = 0;\n                                    NPC[A].Special = 0;\n                                    NPC[A].Projectile = false;\n                                }\n                            }\n                        }\n\n                        if(NPC[A].Type == NPCID_TANK_TREADS && NPC[A].Location.SpeedX != 0)\n                            NPC[A].Location.SpeedX = 1 * NPC[A].DefaultDirection;\n\n                        // If .Type = 12 Then .Projectile = True 'Stop the big fireballs from getting killed from tha lava\n                        if(NPC[A].Type == NPCID_RAINBOW_SHELL)\n                            NPC[A].Projectile = true;\n\n                        // restore saw height after block collision and belt logic\n                        if(NPC[A].Type == NPCID_SAW)\n                        {\n                            NPC[A].Location.Height = NPC[A]->THeight;\n                            NPC[A].Projectile = true;\n                        }\n\n\n                        // NPC Collision\n                        NPCCollide(A);\n\n                        // reset WallDeath variable for thrown items and decay it for fish re-entering water\n                        if(NPC[A].WallDeath > 0)\n                        {\n                            if(NPC[A]->IsFish)\n                                NPC[A].WallDeath -= 1;\n                            else\n                                NPC[A].WallDeath = 0;\n                        }\n\n                        if(tempHit != 0) // Walking   // VERIFY ME\n                            NPCWalkingLogic(A, tempHit, tempHitBlock, tempSpeedA);\n                    }\n                    else\n                    {\n                        NPC[A].BeltSpeed = 0;\n                        NPC[A].Slope = 0;\n                    }\n                }\n\n                if(NPC[A].tempBlock > 0 && (NPC[A].Type < NPCID_YEL_PLATFORM || NPC[A].Type > NPCID_RED_PLATFORM) && NPC[A].Type != NPCID_CONVEYOR)\n                {\n                    if((NPC[A].Type < NPCID_TANK_TREADS || NPC[A].Type > NPCID_SLANT_WOOD_M) && NPC[A].Type != NPCID_SPRING)\n                    {\n                        Block[NPC[A].tempBlock].Location = NPC[A].Location;\n                        if(NPC[A].Type == NPCID_SPRING)\n                        {\n                            // the NPC tree accounts for this; no need to split tempBlock\n                            Block[NPC[A].tempBlock].Location.Y -= 16;\n                            Block[NPC[A].tempBlock].Location.Height += 16;\n                        }\n\n                        // tempBlock comes back in sync with NPC\n                        if(NPC[A].Location.X != prevX\n                            || NPC[A].Location.Y != prevY\n                            || NPC[A].Location.Width != prevW\n                            || NPC[A].Location.Height != prevH)\n                        {\n                            prevX = NPC[A].Location.X;\n                            prevY = NPC[A].Location.Y;\n                            prevW = NPC[A].Location.Width;\n                            prevH = NPC[A].Location.Height;\n\n                            treeNPCUpdate(A);\n\n                            if(NPC[A].tempBlockInTree)\n                            {\n                                NPC[A].tempBlockInTree = false;\n                                treeTempBlockRemove(NPC[A].tempBlock);\n                            }\n                        }\n\n                        // no longer needed; maintaining the sort\n#if 0\n                        while(Block[NPC[A].tempBlock].Location.X < Block[NPC[A].tempBlock - 1].Location.X && NPC[A].tempBlock > numBlock + 1 - numTempBlock)\n                        {\n\n                            tmpBlock = Block[NPC[A].tempBlock - 1];\n                            Block[NPC[A].tempBlock - 1] = Block[NPC[A].tempBlock];\n                            Block[NPC[A].tempBlock] = tmpBlock;\n\n                            NPC[Block[NPC[A].tempBlock].tempBlockNpcIdx].tempBlock = NPC[A].tempBlock;\n                            NPC[A].tempBlock -= 1;\n\n                        }\n                        while(Block[NPC[A].tempBlock].Location.X > Block[NPC[A].tempBlock + 1].Location.X && NPC[A].tempBlock < numBlock)\n                        {\n\n\n                            tmpBlock = Block[NPC[A].tempBlock + 1];\n                            Block[NPC[A].tempBlock + 1] = Block[NPC[A].tempBlock];\n                            Block[NPC[A].tempBlock] = tmpBlock;\n\n                            NPC[Block[NPC[A].tempBlock].tempBlockNpcIdx].tempBlock = NPC[A].tempBlock;\n                            NPC[A].tempBlock += 1;\n\n\n\n\n                            // NPC(Block(.tempBlock).tempBlockNpcIdx).tempBlock = .tempBlock\n                            // NPC(Block(.tempBlock + 1).tempBlockNpcIdx).tempBlock = .tempBlock + 1\n\n\n                        }\n#endif\n                    }\n                    Block[NPC[A].tempBlock].Location.SpeedX = NPC[A].Location.SpeedX + (num_t)NPC[A].BeltSpeed;\n                }\n\n                if(NPC[A].Projectile)\n                {\n                    if(NPC[A].Type == NPCID_SAW || NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_HPIPE_SHORT || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT || NPC[A].Type == NPCID_VPIPE_LONG || (NPC[A].Type >= NPCID_TANK_TREADS && NPC[A].Type <= NPCID_SLANT_WOOD_M))\n                        NPC[A].Projectile = false;\n                }\n\n                // obsolete code commented out in SMBX64\n\n                // Pinched code\n                // If .Direction <> oldDirection Then\n                // .PinchCount += 10\n                // Else\n                // If .PinchCount > 0 Then\n                // .PinchCount += -1\n                // If .Pinched = False Then .PinchCount += -1\n                // End If\n                // End If\n                // If .PinchCount >= 14 And .Pinched = False Then\n                // .Pinched = True\n                // .PinchedDirection = .Direction\n                // ElseIf .PinchCount >= 15 Then\n                // .PinchCount = 15\n                // ElseIf .PinchCount = 0 Then\n                // .Pinched = False\n                // End If\n\n                // NPC[A].Pinched = false;    // never set to true since SMBX64, removed\n\n                // Special Code for things that work while held\n                NPCSpecialMaybeHeld(A);\n\n\n                // If FreezeNPCs = True Then\n                // .Direction = .DefaultDirection\n                // .Special = .DefaultSpecial\n                // .Special2 = 0\n                // .Special3 = 0\n                // .Special4 = 0\n                // .Special5 = 0\n                // End If\n\n\n                NPCFrames(A);\n            }\n            // Effects\n            else\n                NPCEffects(A);\n\n            // Originally applied for all NPCs, even if inactive.\n            // Moved here because speedVar is only validly set here.\n            if(!num_t::fEqual_f(speedVar, 1) && !num_t::fEqual_f(speedVar, 0))\n            {\n                NPC[A].RealSpeedX = numf_t(NPC[A].Location.SpeedX);\n                NPC[A].Location.SpeedX = NPC[A].Location.SpeedX.times(num_t(speedVar));\n            }\n\n            // finally update NPC's tree status if needed, and split the tempBlock (since it has not been updated)\n            if(NPC[A].Location.X != prevX\n                || NPC[A].Location.Y != prevY\n                || NPC[A].Location.Width != prevW\n                || NPC[A].Location.Height != prevH)\n            {\n                bool changed = treeNPCUpdate(A);\n\n                if(changed && NPC[A].tempBlock > 0)\n                    treeNPCSplitTempBlock(A);\n            }\n        }\n\n        if(NPC[A].AttLayer != LAYER_NONE && NPC[A].AttLayer != LAYER_DEFAULT && NPC[A].HoldingPlayer == 0)\n            SetLayerSpeed(NPC[A].AttLayer, NPC[A].Location.X - lyrX, NPC[A].Location.Y - lyrY, false);\n    }\n\n    numBlock -= numTempBlock; // clean up the temp npc blocks\n\n    treeTempBlockClear();\n\nkill_NPCs_and_CharStuff:\n\n    // kill the NPCs, from last to first\n    NPCQueues::reverse_sort(NPCQueues::Killed);\n\n    // all of these are preserved by the interrupt / resume routine\n    int last_NPC;\n    size_t KilledQueue_check;\n    size_t KilledQueue_known;\n    size_t i;\n    last_NPC = maxNPCs + 1;\n    KilledQueue_check = NPCQueues::Killed.size();\n    KilledQueue_known = NPCQueues::Killed.size();\n\n    for(i = 0; i < KilledQueue_check; i++) // KILL THE NPCS <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><\n    {\n        // preserved by the interrupt / resume routine\n        int A;\n        A = NPCQueues::Killed[i];\n\n        // in rare cases, the game may accidentally kill an NPC outside of the NPC array (storage glitch example)\n        if(A > numNPCs)\n            continue;\n\n        // duplicated entry, no problem\n        if(A == last_NPC)\n            continue;\n\n        SDL_assert(A < last_NPC); // something's wrong in the sort order\n\n        if(NPC[A].Killed > 0)\n        {\n            if(NPC[A].Location.SpeedX == 0)\n            {\n                NPC[A].Location.SpeedX = dRand() * 2 - 1;\n                if(NPC[A].Location.SpeedX < 0)\n                    NPC[A].Location.SpeedX -= 0.5_n;\n                else\n                    NPC[A].Location.SpeedX += 0.5_n;\n            }\n\n            // preserved by the interrupt / resume routine\n            int KillCode;\n            KillCode = NPC[A].Killed;\n\n            while(KillNPC(A, NPC[A].Killed))\n            {\n                g_gameLoopInterrupt.site = GameLoopInterrupt::UpdateNPCs_KillNPC;\n                g_gameLoopInterrupt.A = i; // NOT A\n                g_gameLoopInterrupt.B = KillCode;\n                // C is reserved as the event index\n                g_gameLoopInterrupt.D = last_NPC;\n                g_gameLoopInterrupt.E = KilledQueue_check;\n                g_gameLoopInterrupt.F = KilledQueue_known;\n                return true;\n\nresume_KillNPC:\n                i = g_gameLoopInterrupt.A;\n                A = NPCQueues::Killed[i];\n                KillCode = g_gameLoopInterrupt.B;\n\n                last_NPC = g_gameLoopInterrupt.D;\n                KilledQueue_check = g_gameLoopInterrupt.E;\n                KilledQueue_known = g_gameLoopInterrupt.F;\n            }\n\n            // KillNPC sometimes adds duplicate / unnecessary members to NPCQueues::Killed\n            bool real_new_killed = false;\n            if(NPCQueues::Killed.size() > KilledQueue_known)\n            {\n                for(size_t j = NPCQueues::Killed.size() - 1; j >= KilledQueue_known; j--)\n                {\n                    int K = NPCQueues::Killed[j];\n                    if(K != A && K >= 1 && K <= numNPCs)\n                    {\n                        real_new_killed = true;\n                        break;\n                    }\n                }\n\n                // ignore if no real new NPCs added\n                if(!real_new_killed)\n                    NPCQueues::Killed.resize(KilledQueue_known);\n                else\n                {\n                    pLogDebug(\"During KillNPC(%d), %d actual new killed NPC indexes added.\", A, (int)(NPCQueues::Killed.size() - KilledQueue_known));\n                    KilledQueue_known = NPCQueues::Killed.size();\n                }\n            }\n\n            // rare cases exist where a real new NPC is killed (mostly events that hide layers)\n            // sort and check the ones smaller than A\n            if(real_new_killed)\n            {\n                size_t old_check = KilledQueue_check;\n\n                // partition to check all the ones < A this frame\n                auto first_bigger_it = std::partition(NPCQueues::Killed.begin() + KilledQueue_check, NPCQueues::Killed.end(),\n                [A](NPCRef_t a)\n                {\n                    return (int)a < A;\n                });\n\n                KilledQueue_check = first_bigger_it - NPCQueues::Killed.begin();\n\n                if(old_check != KilledQueue_check)\n                {\n                    pLogDebug(\"Found %d indexes lower than %d. Sorting to check this frame.\", (int)(first_bigger_it - NPCQueues::Killed.begin()) - (int)old_check, A);\n\n                    // re-sort the range to check this frame\n                    NPCQueues::reverse_sort(\n                        reinterpret_cast<int16_t*>(NPCQueues::Killed.data() + i + 1),\n                        reinterpret_cast<int16_t*>(NPCQueues::Killed.data() + KilledQueue_check)\n                    );\n                }\n            }\n        }\n    }\n\n    if(NPCQueues::Killed.size() > KilledQueue_check)\n    {\n        NPCQueues::Killed.erase(NPCQueues::Killed.begin(), NPCQueues::Killed.begin() + KilledQueue_check);\n        pLogDebug(\"Checking %d newly killed NPC indexes next frame.\", (int)NPCQueues::Killed.size());\n    }\n    else\n        NPCQueues::Killed.clear();\n\n    //    if(nPlay.Online == true)\n    //    {\n    //        if(nPlay.Mode == 1)\n    //        {\n    //            nPlay.NPCWaitCount += 10;\n    //            if(nPlay.NPCWaitCount >= 5)\n    //            {\n    //                tempStr = \"L\" + LB;\n    //                for(int A = 1; A <= numNPCs; A++)\n    //                {\n    //                    if(NPC[A].Active == true && NPC[A].TimeLeft > 1)\n    //                    {\n    //                        if(NPC[A].HoldingPlayer <= 1)\n    //                        {\n    //                            tempStr += \"K\" + std::to_string(A) + \"|\" + NPC[A].Type + \"|\" + NPC[A].Location.X + \"|\" + NPC[A].Location.Y + \"|\" + std::to_string(NPC[A].Location.Width) + \"|\" + std::to_string(NPC[A].Location.Height) + \"|\" + NPC[A].Location.SpeedX + \"|\" + NPC[A].Location.SpeedY + \"|\" + NPC[A].Section + \"|\" + NPC[A].TimeLeft + \"|\" + NPC[A].Direction + \"|\" + std::to_string(static_cast<int>(floor(static_cast<double>(NPC[A].Projectile)))) + \"|\" + NPC[A].Special + \"|\" + NPC[A].Special2 + \"|\" + NPC[A].Special3 + \"|\" + NPC[A].Special4 + \"|\" + NPC[A].Special5 + \"|\" + NPC[A].Effect + LB;\n    //                            if(NPC[A].Effect != NPCEFF_NORMAL)\n    //                                tempStr += \"2c\" + std::to_string(A) + \"|\" + NPC[A].Effect2 + \"|\" + NPC[A].Effect3 + LB;\n    //                        }\n    //                    }\n    //                }\n    //                Netplay::sendData tempStr + \"O\" + std::to_string(numPlayers) + LB;\n    //                nPlay.NPCWaitCount = 0;\n    //            }\n    //        }\n    //    }\n    CharStuff();\n\n    return false;\n}\n"
  },
  {
    "path": "src/npc/safe_set.hpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#pragma once\n\n#ifndef SAFE_SET_HPP\n#define SAFE_SET_HPP\n\n#include <set>\n\ntemplate<class T>\nclass SafeSet\n{\n    using PlainSet = std::set<T>;\n    using PlainIt = typename std::set<T>::iterator;\n\n    bool invalid = false;\n    PlainSet m_set;\n\npublic:\n    struct InsertEraseSafeIteration\n    {\n        SafeSet& parent;\n\n        InsertEraseSafeIteration(SafeSet& parent) : parent(parent) {}\n\n        struct iterator\n        {\n            SafeSet* m_parent;\n            PlainIt it;\n            int prev_invalid;\n\n            T last_val;\n\n            inline iterator(SafeSet& parent) : m_parent(&parent), it(parent.m_set.begin()), prev_invalid(parent.invalid)\n            {\n                if(it != parent.m_set.end())\n                    last_val = *it;\n                m_parent->invalid = false;\n            }\n\n            inline iterator(SafeSet& parent, std::nullptr_t ptr) : m_parent(ptr), it(parent.m_set.end()), prev_invalid(-1) {}\n\n            inline ~iterator()\n            {\n                if(m_parent && prev_invalid > 0)\n                    m_parent->invalid = true;\n\n                m_parent = nullptr;\n            }\n\n            inline bool operator!=(const iterator& o)\n            {\n                return it != o.it;\n            }\n\n            inline bool operator!=(const PlainIt oit)\n            {\n                return it != oit;\n            }\n\n            inline T operator*()\n            {\n                return last_val;\n            }\n\n            inline const iterator& operator++()\n            {\n                if(m_parent->invalid)\n                    it = m_parent->m_set.upper_bound(last_val);\n                else\n                    ++it;\n\n                if(it != m_parent->m_set.end())\n                    last_val = *it;\n\n                return *this;\n            }\n        };\n\n        iterator begin()\n        {\n            return iterator(parent);\n        }\n\n        iterator end()\n        {\n            return iterator(parent, nullptr);\n        }\n    };\n\n    InsertEraseSafeIteration safe{*this};\n\n    std::set<T>& no_change = m_set;\n    std::set<T>& may_insert = m_set;\n    InsertEraseSafeIteration& may_erase = safe;\n    InsertEraseSafeIteration& may_insert_erase = safe;\n\n    inline void clear()\n    {\n        invalid = true;\n        m_set.clear();\n    }\n\n    inline void insert(const T& t)\n    {\n        m_set.insert(t);\n    }\n\n    inline void erase(const T& t)\n    {\n        invalid = true;\n        m_set.erase(t);\n    }\n\n    inline void invalidate()\n    {\n        invalid = true;\n    }\n};\n\n#endif // #ifndef SAFE_SET_HPP\n"
  },
  {
    "path": "src/npc/section_overlap.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n\n#include \"globals.h\"\n\n#include \"npc/section_overlap.h\"\n\nstd::array<uint8_t, maxSections + 1> g_SectionFirstOverlap;\n\n//! Check if two section bounds collide\nstatic bool s_SectionCollide(const SpeedlessLocation_t& bounds, const SpeedlessLocation_t& other_bounds)\n{\n    // remember that section bounds are stored \"incorrectly\" with \"Width\" / \"Height\" referring to the Right / Bottom\n    return (bounds.Width >= other_bounds.X\n        && bounds.X <= other_bounds.Width\n        && bounds.Height >= other_bounds.Y\n        && bounds.Y <= other_bounds.Height);\n}\n\n//! Find the first section (from start) that overlaps with S, and store it into g_SectionFirstOverlap[S]\nstatic void s_FindFirstOverlap(int S, int start)\n{\n    g_SectionFirstOverlap[S] = S;\n\n    for(int O = start; O < S; O++)\n    {\n        if(s_SectionCollide(level[S], level[O]))\n        {\n            g_SectionFirstOverlap[S] = O;\n            D_pLogDebug(\"Note: section %d is the first section overlapping section %d\", O, S);\n            break;\n        }\n    }\n}\n\n//! Find the first section that overlaps with each section\nvoid CalculateSectionOverlaps()\n{\n    for(int S = 0; S < numSections; S++)\n        s_FindFirstOverlap(S, 0);\n}\n\nvoid UpdateSectionOverlaps(int S, bool shrink)\n{\n    // if not a shrink, find first section overlapping S\n    if(!shrink)\n        s_FindFirstOverlap(S, 0);\n    // if a shrink, only check sections starting from current first overlap\n    else if(g_SectionFirstOverlap[S] != S)\n        s_FindFirstOverlap(S, g_SectionFirstOverlap[S]);\n\n    // update all sections after S (for which S might be first overlap)\n    for(int O = S + 1; O < numSections; O++)\n    {\n        // if S is the current overlap, check that it still applies\n        if(g_SectionFirstOverlap[O] == S)\n        {\n            if(!s_SectionCollide(level[S], level[O]))\n                s_FindFirstOverlap(O, S + 1);\n        }\n        // if S is earlier than the current overlap (and not a shrink), check if S is the new first overlap\n        else if(!shrink && g_SectionFirstOverlap[O] > S)\n        {\n            if(s_SectionCollide(level[S], level[O]))\n                g_SectionFirstOverlap[O] = S;\n        }\n    }\n}\n"
  },
  {
    "path": "src/npc/section_overlap.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SECTION_OVERLAP_H\n#define SECTION_OVERLAP_H\n\n#include <array>\n#include <cstdint>\n\n#include \"global_constants.h\"\n\nextern std::array<uint8_t, maxSections + 1> g_SectionFirstOverlap;\n\n//! calculate all section overlaps, called on level load\nvoid CalculateSectionOverlaps();\n\n//! Update section overlaps after section S's bounds are changed.\n//  Set shrink if section S's new bounds are contained in its old bounds.\nvoid UpdateSectionOverlaps(int S, bool shrink = false);\n\n\n#endif\n"
  },
  {
    "path": "src/npc.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"globals.h\"\n#include \"npc.h\"\n#include \"sound.h\"\n#include \"graphics.h\"\n#include \"effect.h\"\n#include \"game_main.h\"\n#include \"player.h\"\n#include \"collision.h\"\n#include \"editor.h\"\n#include \"blocks.h\"\n#include \"controls.h\"\n#include \"config.h\"\n#include \"main/trees.h\"\n#include \"core/events.h\"\n#include \"npc_id.h\"\n#include \"eff_id.h\"\n#include \"npc_traits.h\"\n#include \"layers.h\"\n\n#include \"npc/npc_queues.h\"\n#include \"npc/npc_activation.h\"\n#include \"npc/section_overlap.h\"\n#include \"npc/npc_cockpit_bits.h\"\n#include \"npc/npc_update/npc_update_priv.h\"\n\nvoid NPC_t::ResetLocation()\n{\n    Location.X = DefaultLocationX;\n    Location.Y = DefaultLocationY;\n    Location.Width = (*this)->TWidth;\n    Location.Height = (*this)->THeight;\n    Location.SpeedX = 0;\n    Location.SpeedY = 0;\n\n    if(DefaultLocationHeight_Force32)\n        Location.Height = 32;\n}\n\n// UpdateNPCs at npc/npc_update.cpp\n\n// NpcHit at npc/npc_hit.cpp\n\nnum_t NPCPlayerTargetDist(const NPC_t& npc, const Player_t& player)\n{\n    num_t dx = npc.Location.minus_center_x(player.Location);\n\n    if(g_config.fix_multiplayer_targeting)\n    {\n        num_t dy = npc.Location.minus_center_y(player.Location);\n        return num_t::dist2(dx, dy);\n    }\n    else\n        return num_t::abs(dx);\n}\n\nint NPCTargetPlayer(const NPC_t& npc)\n{\n    int target_plr = 0;\n    num_t min_dist = 0;\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        if(!Player[B].Dead && Player[B].Section == npc.Section)\n        {\n            if(numPlayers == 1)\n                return 1;\n\n            num_t dist = NPCPlayerTargetDist(npc, Player[B]);\n            if(min_dist == 0 || dist < min_dist)\n            {\n                min_dist = dist;\n                target_plr = B;\n            }\n        }\n    }\n\n    return target_plr;\n}\n\nint NPCFaceNearestPlayer(NPC_t& npc, bool old_version)\n{\n    int target = NPCTargetPlayer(npc);\n\n    num_t target_center = Player[target].Location.X + Player[target].Location.Width / 2;\n\n    // broken version of logic that appears in a few places\n    if(old_version)\n    {\n        if(target_center > npc.Location.X + 16)\n            npc.Direction = 1;\n        else\n            npc.Direction = -1;\n    }\n    else\n    {\n        if(target != 0)\n        {\n            if(npc.Location.X + npc.Location.Width / 2 > target_center)\n                npc.Direction = -1;\n            else\n                npc.Direction = 1;\n        }\n    }\n\n    return target;\n}\n\nstatic void s_NPCSetSpeedTarget_FixedX_dist(NPC_t& npc, num_t dist_x, num_t dist_y, int speed_x, int speed_y_cap)\n{\n    // originally only applied to plant, but good to apply in all cases (and was UB in VB6)\n    if(dist_x == 0)\n        dist_x = -0.00001_n;\n\n    npc.Location.SpeedX = speed_x * npc.Direction;\n    npc.Location.SpeedY = speed_x * npc.Direction * dist_y.divided_by(dist_x);\n\n    if(NPC[numNPCs].Location.SpeedY > speed_y_cap)\n        NPC[numNPCs].Location.SpeedY = speed_y_cap;\n    else if(NPC[numNPCs].Location.SpeedY < -speed_y_cap)\n        NPC[numNPCs].Location.SpeedY = -speed_y_cap;\n}\n\nvoid NPCSetSpeedTarget_FixedX(NPC_t& npc, const Location_t& target, int speed_x, int speed_y_cap)\n{\n    num_t dist_x = target.minus_center_x(npc.Location);\n    num_t dist_y = target.minus_center_y(npc.Location);\n\n    s_NPCSetSpeedTarget_FixedX_dist(npc, dist_x, dist_y, speed_x, speed_y_cap);\n}\n\nvoid NPCSetSpeedTarget_FixedX(NPC_t& npc, num_t target_x, num_t target_y, int speed_x, int speed_y_cap)\n{\n    num_t dist_x = target_x - (npc.Location.X + npc.Location.Width / 2);\n    num_t dist_y = target_y - (npc.Location.Y + npc.Location.Height / 2);\n\n    s_NPCSetSpeedTarget_FixedX_dist(npc, dist_x, dist_y, speed_x, speed_y_cap);\n}\n\nvoid NPCSetSpeedTarget_FixedSpeed(NPC_t& npc, const Location_t& target, int speed)\n{\n    num_t dist_x = target.minus_center_x(npc.Location);\n    num_t dist_y = target.minus_center_y(npc.Location);\n\n    num_t dist = num_t::dist(dist_x, dist_y);\n\n    npc.Location.SpeedX = speed * dist_x.divided_by(dist);\n    npc.Location.SpeedY = speed * dist_y.divided_by(dist);\n}\n\nvoid CheckSectionNPC(int A)\n{\n    if(GameMenu)\n        return;\n\n    if(NPC[A].HoldingPlayer > 0)\n    {\n        if(NPC[A].TimeLeft < 10)\n            NPC[A].TimeLeft = 10;\n        NPC[A].Section = Player[NPC[A].HoldingPlayer].Section;\n    }\n\n    // disabled in 1.3.6.1 because of bugs when overlapping sections change\n    // Previously, it optimized for section 0. Now, it optimizes for NPC's current section.\n    int B = NPC[A].Section;\n    if(B <= maxSections && g_SectionFirstOverlap[B] == B && NPC[A].Location.X >= level[B].X)\n    {\n        if(NPC[A].Location.X + NPC[A].Location.Width <= level[B].Width)\n        {\n            if(NPC[A].Location.Y >= level[B].Y)\n            {\n                if(NPC[A].Location.Y + NPC[A].Location.Height <= level[B].Height)\n                {\n                    return;\n                }\n            }\n        }\n    }\n\n    for(int B = 0; B < numSections; B++)\n    {\n        if(NPC[A].Location.X >= level[B].X)\n        {\n            if(NPC[A].Location.X + NPC[A].Location.Width <= level[B].Width)\n            {\n                if(NPC[A].Location.Y >= level[B].Y)\n                {\n                    if(NPC[A].Location.Y + NPC[A].Location.Height <= level[B].Height)\n                    {\n                        NPC[A].Section = B;\n                        return;\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid Deactivate(int A)\n{\n    if(NPC[A].DefaultType > 0)\n    {\n        if(NPC[A].TurnBackWipe && NoTurnBack[NPC[A].Section])\n        {\n            NPC[A].Killed = 9;\n            NPCQueues::Killed.push_back(A);\n        }\n        else\n        {\n            if(NPC[A].Type == NPCID_SKELETON && NPC[A].Special > 0)\n            {\n                NPC[A].Inert = false;\n                NPC[A].Stuck = false;\n            }\n\n            // reset variables back to default\n            NPC[A].Quicksand = 0;\n            NPC[A].NoLavaSplash = false;\n            NPC[A].Active = false;\n\n            // prevent instantly respawning, unless the animation will be totally seamless\n            if(!g_config.fix_npc_camera_logic\n                || !NPC_InactiveRender(NPC[A])\n                || num_t::floor(NPC[A].Location.X) != num_t::floor(NPC[A].DefaultLocationX)\n                || num_t::floor(NPC[A].Location.Y) != num_t::floor(NPC[A].DefaultLocationY))\n            {\n                NPC[A].Reset[1] = false;\n                NPC[A].Reset[2] = false;\n            }\n\n            // reset variables\n            NPC[A].Type = NPC[A].DefaultType;\n            NPC[A].ResetLocation();\n            NPC[A].Direction = NPC[A].DefaultDirection;\n            NPC[A].Stuck = NPC[A].DefaultStuck;\n\n            // NEW: reset wings (unless the NPC is a container that bans wings)\n            NPC[A].Wings = NPC[A].DefaultWings;\n            if(NPC[A].Type == NPCID_ITEM_BURIED || NPC[A].Type == NPCID_ITEM_BUBBLE)\n                NPC[A].Wings = WING_NONE;\n\n            NPC[A].TimeLeft = 0;\n            NPC[A].Projectile = false;\n            NPC[A].Effect = NPCEFF_NORMAL;\n\n            // counter to respawn\n            if(NPC[A].RespawnDelay)\n                NPC[A].Effect2 = 65 * 30;\n            else\n                NPC[A].Effect2 = 0;\n\n            NPC[A].Effect3 = 0;\n            NPC[A].BeltSpeed = 0;\n            NPC[A].vehiclePlr = 0;\n            NPC[A].vehicleYOffset = 0;\n            NPC[A].Frame = 0;\n            NPC[A].Killed = 0;\n            NPC[A].Shadow = false;\n            NPC[A].oldAddBelt = 0;\n\n            // DefaultSpecial\n            NPC[A].Special = NPC[A].DefaultSpecial;\n            NPC[A].Special2 = 0; // NPC[A].DefaultSpecial2; // moved to Variant for the one type (NPCID_MAGIC_DOOR) that used it\n\n            // for generators, these store critical variables\n            if(!NPC[A].Generator)\n            {\n                NPC[A].Special3 = 0;\n                NPC[A].Special4 = 0;\n                NPC[A].Special5 = 0;\n            }\n\n            // NPC[A].Special6 = 0;\n            NPC[A].SpecialX = 0;\n            NPC[A].SpecialY = 0;\n            if(!NPC[A]->IsACoin)\n                NPC[A].Damage = 0;\n            NPC[A].HoldingPlayer = 0;\n\n            // NPC[A].Pinched1 = 0;\n            // NPC[A].Pinched2 = 0;\n            // NPC[A].Pinched3 = 0;\n            // NPC[A].Pinched4 = 0;\n            // NPC[A].Pinched = 0;\n            // NPC[A].MovingPinched = 0;\n\n            NPC[A].Pinched = PinchedInfo_t();\n\n            // new-added logic: if CURRENTLY offscreen for all vScreens, allow it to reset\n            if(g_config.fix_frame_perfect_despawn && (g_config.fix_timestop_respawn || !FreezeNPCs))\n            {\n                bool hit = false;\n\n                for(int screen_i = 0; !hit && screen_i < c_screenCount; screen_i++)\n                {\n                    const Screen_t& screen = Screens[screen_i];\n\n                    for(int vscreen_i = screen.active_begin(); !hit && vscreen_i < screen.active_end(); vscreen_i++)\n                    {\n                        int vscreen_Z = screen.vScreen_refs[vscreen_i];\n\n                        if(vScreenCollision(vscreen_Z, NPC[A].Location))\n                            hit = true;\n                    }\n                }\n\n                if(!hit)\n                {\n                    NPC[A].Reset[1] = true;\n                    NPC[A].Reset[2] = true;\n                }\n            }\n\n            // NEW now that we have the new NPC Queues\n            NPCQueues::update(A);\n            bool changed = treeNPCUpdate(A);\n            if(changed && NPC[A].tempBlock > 0)\n                treeNPCSplitTempBlock(A);\n        }\n    }\n    else if(NPCIsAnExit(NPC[A]))\n        NPC[A].TimeLeft = 100;\n    else\n    {\n        NPC[A].Killed = 9;\n        NPCQueues::Killed.push_back(A);\n    }\n}\n\nvoid Bomb(Location_t Location, int Game, int ImmunePlayer)\n{\n    num_t Radius = 0;\n    // int i = 0;\n    num_t X = 0;\n    num_t Y = 0;\n    num_t A = 0;\n    num_t B = 0;\n    num_t C = 0;\n\n    NPC[0].Multiplier = 0;\n    if(Game == 0)\n    {\n        NewEffect(EFFID_CHAR3_HEAVY_EXPLODE, Location);\n        PlaySoundSpatial(SFX_Bullet, Location);\n        Radius = 32;\n    }\n    if(Game == 2)\n    {\n        Controls::RumbleAllPlayers(150, 1.0);\n        NewEffect(EFFID_BOMB_S2_EXPLODE, Location);\n        PlaySoundSpatial(SFX_Fireworks, Location);\n        Radius = 52;\n    }\n    if(Game == 3)\n    {\n        Controls::RumbleAllPlayers(200, 1.0);\n        NewEffect(EFFID_BOMB_S3_EXPLODE_SEED, Location);\n        PlaySoundSpatial(SFX_Fireworks, Location);\n        Radius = 64;\n    }\n\n    X = Location.X + Location.Width / 2;\n    Y = Location.Y + Location.Height / 2;\n\n    for(int i : treeNPCQuery(newLoc(X - Radius, Y - Radius, Radius * 2, Radius * 2), SORTMODE_ID))\n    {\n        if(!NPC[i].Hidden && NPC[i].Active && !NPC[i].Inert && !NPC[i].Generator && !NPC[i]->IsABonus)\n        {\n            if(NPC[i].Type != NPCID_PLR_FIREBALL && NPC[i].Type != NPCID_CHAR3_HEAVY)\n            {\n                A = NPC[i].Location.X + NPC[i].Location.Width / 2 - X;\n                B = NPC[i].Location.Y + NPC[i].Location.Height / 2 - Y;\n                C = num_t::dist(A, B);\n\n                if(C <= Radius + NPC[i].Location.Width / 4 + NPC[i].Location.Height / 4)\n                {\n                    NPC[0].Location = NPC[i].Location;\n                    NPCHit(i, 3, 0);\n                    if(NPCIsVeggie(NPC[i]))\n                    {\n                        NPC[i].Projectile = true;\n                        NPC[i].Location.SpeedY = -5;\n                        NPC[i].Location.SpeedX = dRand() * 4 - 2;\n                    }\n                }\n            }\n        }\n    }\n\n    for(int i : treeBlockQuery(newLoc(X - Radius, Y - Radius, Radius * 2, Radius * 2), SORTMODE_COMPAT))\n    {\n        if(!Block[i].Hidden && !BlockNoClipping[Block[i].Type])\n        {\n            A = Block[i].Location.X + Block[i].Location.Width / 2 - X;\n            B = Block[i].Location.Y + Block[i].Location.Height / 2 - Y;\n            C = num_t::dist(A, B);\n\n            if(C <= Radius + Block[i].Location.Width / 4 + Block[i].Location.Height / 4)\n            {\n                BlockHit(i);\n                BlockHitHard(i);\n                if(Game == 0 && Block[i].Type == 457)\n                    SafelyKillBlock(i);\n            }\n        }\n    }\n\n    if(Game != 0 || BattleMode)\n    {\n        for(int i = 1; i <= numPlayers; i++)\n        {\n            if(Game != 0 || i != ImmunePlayer)\n            {\n                A = Player[i].Location.X + Player[i].Location.Width / 2 - X;\n                B = Player[i].Location.Y + Player[i].Location.Height / 2 - Y;\n                C = num_t::dist(A, B);\n\n                if(C <= Radius + Player[i].Location.Width / 4 + Player[i].Location.Height / 4)\n                    PlayerHurt(i);\n            }\n        }\n    }\n}\n\n#if 0\n// dead code, removed\n\nvoid DropNPC(int A, int NPCType)\n{\n    int B = 0;\n    if(A == 1 || numPlayers == 2)\n    {\n        PlaySound(SFX_DropItem);\n        numNPCs++;\n        NPC[numNPCs].Type = NPCType;\n        NPC[numNPCs].Location.Width = NPCWidth(NPCType);\n        NPC[numNPCs].Location.Height = NPCHeight(NPCType);\n        if(ScreenType == 5 && !vScreen[2].Visible)\n        {\n            if(A == 1)\n                B = -40;\n            if(A == 2)\n                B = 40;\n            NPC[numNPCs].Location.X = -vScreen[1].X + vScreen[1].Width / 2 - NPC[numNPCs].Location.Width / 2 + B;\n            NPC[numNPCs].Location.Y = -vScreen[1].Y + 16 + 12;\n        }\n        else\n        {\n            NPC[numNPCs].Location.X = -vScreen[A].X + vScreen[A].Width / 2 - NPC[numNPCs].Location.Width / 2;\n            NPC[numNPCs].Location.Y = -vScreen[A].Y + 16 + 12;\n        }\n        NPC[numNPCs].Location.SpeedX = 0;\n        NPC[numNPCs].Location.SpeedY = 0;\n        NPC[numNPCs].Effect = NPCEFF_DROP_ITEM;\n        NPC[numNPCs].Active = true;\n        NPC[numNPCs].TimeLeft = 200;\n        syncLayers_NPC(numNPCs);\n    }\n}\n#endif\n\nvoid TurnNPCsIntoCoins()\n{\n    // need this complex loop syntax because Active can be modified within it\n    for(int A : NPCQueues::Active.may_erase)\n    {\n        if(NPC[A].Active && !NPC[A].Generator)\n        {\n            if(!NPC[A].Hidden && NPC[A].Killed == 0 && !NPCIsAnExit(NPC[A]) && !NPC[A].Inert)\n            {\n                if(!NPCIsYoshi(NPC[A]) && !NPCIsBoot(NPC[A]) &&\n                   !NPC[A]->IsABonus && NPC[A].Type != NPCID_PLR_ICEBALL && NPC[A].Type != NPCID_PLR_FIREBALL &&\n                   NPC[A].Type != NPCID_PET_FIRE && NPC[A].Type != NPCID_SPRING && !NPCIsVeggie(NPC[A]) &&\n                   NPC[A].Type != NPCID_ITEM_BURIED && NPC[A].Type != NPCID_PLR_HEAVY && !NPC[A]->IsAVine &&\n                   NPC[A].Type != NPCID_VEHICLE && NPC[A].Type != NPCID_YEL_PLATFORM && NPC[A].Type != NPCID_BLU_PLATFORM &&\n                   NPC[A].Type != NPCID_GRN_PLATFORM && NPC[A].Type != NPCID_RED_PLATFORM && NPC[A].Type != NPCID_PLATFORM_S3 &&\n                   !(NPC[A].Projectile && NPC[A].Type == NPCID_HEAVY_THROWN) &&\n                   !(NPC[A].Projectile && NPC[A].Type == NPCID_BULLET) &&\n                   NPC[A].Type != NPCID_CHAR3_HEAVY && NPC[A].Type != NPCID_CHAR4_HEAVY && NPC[A].Type != NPCID_SWORDBEAM &&\n                   NPC[A].Type != NPCID_CONVEYOR && NPC[A].Type != NPCID_METALBARREL &&\n                   !(NPC[A].Type >= NPCID_TANK_TREADS && NPC[A].Type <= NPCID_SLANT_WOOD_M) &&\n                   NPC[A].Type != NPCID_ITEM_BURIED && NPC[A].Type != NPCID_FIRE_CHAIN && NPC[A].Type != NPCID_FIRE_DISK)\n                {\n                    NPC[A].Location.Y += 32;\n                    NewEffect(EFFID_COIN_BLOCK_S3, NPC[A].Location);\n                    PlaySoundSpatial(SFX_Coin, NPC[A].Location);\n                    Coins += 1;\n                    if(Coins >= 100)\n                        Got100Coins();\n\n                    NPC[A].Killed = 9;\n                    NPC[A].Location.Height = 0;\n\n                    NPCQueues::Killed.push_back(A);\n\n                    if(NPC[A].Active)\n                        NPCQueues::Active.erase(A);\n\n                    NPC[A].Active = false;\n\n                    treeNPCUpdate(A);\n                    if(NPC[A].tempBlock != 0)\n                        treeNPCSplitTempBlock(A);\n                }\n                else if(NPC[A].Type == NPCID_GOALTAPE || NPC[A].Type == NPCID_FIRE_CHAIN || NPC[A].Type == NPCID_FIRE_DISK)\n                {\n                    NPC[A].Active = false;\n                    NPCQueues::update(A);\n                }\n            }\n        }\n        else if(NPC[A].Generator)\n        {\n            NPC[A].Killed = 9;\n            NPC[A].Hidden = true;\n\n            NPCQueues::Killed.push_back(A);\n        }\n    }\n}\n\n#if 0\n// previous recursive version of the function\n\nvoid SkullRide(int A, bool reEnable)\n{\n    Location_t loc = NPC[A].Location;\n    loc.Width += 16;\n    loc.X -= 8;\n    if(g_config.fix_skull_raft) // Detect by height in condition skull ruft cells were on slopes\n    {\n        loc.Height += 30;\n        loc.Y -= 15;\n    }\n\n    int spec = reEnable ? 2 : 0;\n\n    for(int B = 1; B <= numNPCs; B++) // Recursively activate all neihbour skull-ride segments\n    {\n        auto &npc = NPC[B];\n        if(npc.Type == NPCID_RAFT)\n        {\n            if(npc.Active)\n            {\n                if(npc.Special == spec)\n                {\n                    if(CheckCollision(loc, npc.Location))\n                    {\n                        npc.Special = 1;\n                        SkullRide(B, reEnable);\n                    }\n                }\n            }\n        }\n    }\n}\n#endif\n\nstatic void s_alignRuftCell(NPC_t &me, const Location_t &alignAt)\n{\n    num_t w = me.Location.Width;\n    me.Location.SpeedX = 0;\n    me.RealSpeedX = 0;\n\n    if(me.Direction > 0)\n    {\n        auto p = alignAt.X;\n        do\n        {\n            p -= w;\n        } while(num_t::abs(p - me.Location.X) >= w / 2 && p > me.Location.X);\n        me.Location.X = p;\n    }\n    else\n    {\n        auto p = alignAt.X + alignAt.Width;\n        while(num_t::abs(p - me.Location.X) >= w / 2 && p < me.Location.X + me.Location.Width)\n        {\n            p += w;\n        }\n        me.Location.X = p;\n    }\n\n    me.SpecialX = me.Location.X;\n    treeNPCUpdate(&me);\n    if(me.tempBlock != 0)\n        treeNPCSplitTempBlock(&me);\n}\n\n#if 0\n// previous recursive version of the function\n\nvoid SkullRideDone(int A, const Location_t &alignAt)\n{\n    auto &me = NPC[A];\n\n    Location_t loc = me.Location;\n    loc.Width += 16;\n    loc.X -= 8;\n    loc.Height += 30;\n    loc.Y -= 15;\n\n    for(int B = 1; B <= numNPCs; B++) // Recursively DE-activate all neighbour skull-ride segments\n    {\n        auto &npc = NPC[B];\n        if(npc.Type == NPCID_RAFT)\n        {\n            if(npc.Active)\n            {\n                if(npc.Special == 1.0)\n                {\n                    if(CheckCollision(loc , npc.Location))\n                    {\n                        npc.Special = 2;\n                        npc.Location.SpeedX = 0.0;\n                        s_alignRuftCell(npc, alignAt);\n                        SkullRideDone(B, alignAt);\n                    }\n                }\n            }\n        }\n    }\n}\n#endif\n\n\n// this mimics the above functions and should match it in all cases\nvoid SkullRide(int A, bool reEnable, const Location_t *alignAt)\n{\n    int spec = (alignAt) ? 1 : (reEnable ? 2 : 0);\n\n    // allocates and guards a vector of BaseRef_t in its i_vec.\n    TreeResult_Sentinel<NPCRef_t> frontier;\n\n    // LIFO stack of NPCs to trigger, imitates recursion from old SkullRide\n    std::vector<BaseRef_t>& vec = *frontier.i_vec;\n\n    vec.push_back(A);\n\n    while(!vec.empty())\n    {\n        // pop from top of stack\n        const NPC_t& me = static_cast<NPCRef_t>(vec.back());\n        vec.pop_back();\n\n        // construct location query (head of original SkullRide)\n        Location_t loc = me.Location;\n        loc.Width += 16;\n        loc.X -= 8;\n\n        if(g_config.fix_skull_raft) // Detect by height in condition skull ruft cells were on slopes\n        {\n            loc.Height += 30;\n            loc.Y -= 15;\n        }\n\n        // add all queried NPCs to the frontier's internal vector (but check them, and remove them if invalid)\n        size_t current_frontier_size = vec.size();\n\n        treeNPCQuery(vec, loc, SORTMODE_NONE);\n\n        size_t unchecked = current_frontier_size;\n        while(unchecked < vec.size())\n        {\n            NPC_t& npc = static_cast<NPCRef_t>(vec[unchecked]);\n\n            // conditions from inside of loop in original SkullRide\n            if(npc.Type == NPCID_RAFT && npc.Active && npc.Special == spec && CheckCollision(loc, npc.Location))\n            {\n                if(alignAt)\n                {\n                    npc.Special = 2;\n                    npc.Location.SpeedX = 0;\n                    s_alignRuftCell(npc, *alignAt);\n                }\n                else\n                {\n                    // set active flag\n                    npc.Special = 1;\n                }\n\n                // previously, used recursion\n                // SkullRide(B, reEnable);\n\n                // this will preserve the NPC in the stack\n                unchecked++;\n                continue;\n            }\n\n            // remove the NPC from the stack if conditions fail\n            vec[unchecked] = vec[vec.size() - 1];\n            vec.pop_back();\n        }\n\n        // reverse sort the frontier of the stack because this is a depth-first approach\n        NPCQueues::reverse_sort(\n            reinterpret_cast<int16_t*>(vec.data() + current_frontier_size),\n            reinterpret_cast<int16_t*>(vec.data() + vec.size())\n        );\n    }\n}\n\nstatic void s_NPCSpawnCustomThrown(NPC_t& thrower, NPC_t& new_npc)\n{\n    new_npc.Location.Width = new_npc->TWidth;\n    new_npc.Location.Height = new_npc->THeight;\n    new_npc.Variant = thrower.Variant;\n\n    num_t floor_y = thrower.Location.Y + thrower.Location.Height - new_npc.Location.Height;\n    if(new_npc.Location.Y > floor_y)\n        new_npc.Location.Y = floor_y;\n\n    new_npc.Immune = 12;\n\n    if(new_npc->IsACoin || new_npc.Type == NPCID_SLIDE_BLOCK)\n    {\n        if(NPC[numNPCs]->IsACoin)\n            NPC[numNPCs].Location.SpeedX /= 2;\n\n        new_npc.Special = 1;\n    }\n    else if(new_npc.Type == NPCID_HOMING_BALL)\n        new_npc.Special2 = 95;\n\n    if(new_npc->IsABlock)\n    {\n        if(new_npc.Direction > 0)\n            new_npc.Location.X = thrower.Location.X + thrower.Location.Width;\n        else\n            new_npc.Location.X = thrower.Location.X - new_npc.Location.Width;\n    }\n\n    if(new_npc.Type != NPCID_BULLET && new_npc.Type != NPCID_BOMB && new_npc.Type != NPCID_HOMING_BALL\n        && new_npc.Type != NPCID_HOMING_BALL_GEN && new_npc.Type != NPCID_STACKER && new_npc.Type != NPCID_WALK_PLANT\n        && new_npc.Type != NPCID_VINE_BUG && new_npc.Type != NPCID_BOSS_CASE && new_npc.Type != NPCID_FLY\n        && !new_npc->IsACoin)\n    {\n        new_npc.Projectile = true;\n    }\n\n    if(thrower.Inert && !(new_npc->IsACoin || new_npc->IsABonus || new_npc->WontHurt))\n        new_npc.Inert = true;\n}\n\nvoid NPCSpecial(int A)\n{\n    // double C = 0;\n    // double D = 0;\n    // double E = 0;\n    // double F = 0;\n    // int64_t fBlock = 0;\n    // int64_t lBlock = 0;\n    // bool straightLine = false; // SET BUT NOT USED\n    bool tempBool = false;\n    // bool tempBool2 = false;\n    // Location_t tempLocation;\n    // NPC_t tempNPC;\n    auto &npc = NPC[A];\n\n    if(npc.Type == NPCID_BOSS_CASE) // previously included many other bosses, now moved into their logic\n    {\n        // dont despawn\n        if(npc.TimeLeft > 1)\n            npc.TimeLeft = 100;\n    }\n    else if(npc.Type == NPCID_RED_VINE_TOP_S3 || npc.Type == NPCID_GRN_VINE_TOP_S3 || npc.Type == NPCID_GRN_VINE_TOP_S4) // Vine Maker\n    {\n        npc.Location.SpeedY = -2;\n        Location_t tempLocation;\n        tempLocation.Height = 28;\n        tempLocation.Width = 30;\n        tempLocation.Y = npc.Location.Y + (npc.Location.Height - tempLocation.Height) / 2;\n        tempLocation.X = npc.Location.X + (npc.Location.Width - tempLocation.Width) / 2;\n        tempBool = false;\n\n        for(int i : treeNPCQuery(tempLocation, SORTMODE_NONE))\n        {\n            auto &n = NPC[i];\n            if(n->IsAVine && !n.Hidden && CheckCollision(tempLocation, n.Location))\n            {\n                tempBool = true;\n                break;\n            }\n        }\n\n        // this block had no effect because the inner `tempBool = True` was commented in the original VB6 code\n#if 0\n        if(!tempBool)\n        {\n            // fBlock = FirstBlock[static_cast<int>(floor(static_cast<double>(tempLocation.X / 32))) - 1];\n            // lBlock = LastBlock[floor((tempLocation.X + tempLocation.Width) / 32.0) + 1];\n\n            for(Block_t* block : treeFLBlockQuery(tempLocation, false))\n            {\n                auto &b = *block;\n                if(!b.Hidden && !BlockNoClipping[b.Type] && !BlockIsSizable[b.Type] && !BlockOnlyHitspot1[b.Type])\n                {\n                    if(CheckCollision(tempLocation, b.Location) && BlockSlope[b.Type] == 0)\n                    {\n                        // tempBool = True\n                    }\n                }\n            }\n        }\n#endif\n        // ============================================================\n\n        if(!tempBool || npc.Special == 1)\n        {\n            numNPCs++;\n            if(npc.Type == NPCID_RED_VINE_TOP_S3)\n                NPC[numNPCs].Type = NPCID_RED_VINE_S3;\n            else if(npc.Type == NPCID_GRN_VINE_TOP_S3)\n                NPC[numNPCs].Type = NPCID_GRN_VINE_S3;\n            else if(npc.Type == NPCID_GRN_VINE_TOP_S4)\n                NPC[numNPCs].Type = NPCID_GRN_VINE_S4;\n            NPC[numNPCs].Location.Y = num_t::vb6round(npc.Location.Y / 32) * 32;\n            NPC[numNPCs].Location.Height = 32;\n            NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n            NPC[numNPCs].Location.X = npc.Location.X + (npc.Location.Width - NPC[numNPCs].Location.Width) / 2;\n            NPC[numNPCs].Active = true;\n            NPC[numNPCs].TimeLeft = 100;\n            NPC[numNPCs].Section = npc.Section;\n            NPC[numNPCs].DefaultLocationX = NPC[numNPCs].Location.X;\n            NPC[numNPCs].DefaultLocationY = NPC[numNPCs].Location.Y;\n            NPC[numNPCs].DefaultLocationHeight_Force32 = true;\n            NPC[numNPCs].DefaultType = NPC[numNPCs].Type;\n            NPC[numNPCs].Layer = npc.Layer;\n            NPC[numNPCs].Shadow = npc.Shadow;\n            syncLayers_NPC(numNPCs);\n        }\n\n        if(npc.Special == 1)\n        {\n            npc.Killed = 9;\n            NPCQueues::Killed.push_back(A);\n        }\n\n        // driving block\n    }\n    else if(npc.Type == NPCID_COCKPIT)\n    {\n        // Previously, used Special4 (1 if being driven), Special5 (-1 for left, 1 for right), and Special6 (-1 for up, 1 for down)\n        // Now, uses a bitfield in Special4\n\n        // if(npc.Special4 > 0)\n        //     npc.Special4 = 0;\n        // else\n        // {\n        //     npc.Special5 = 0;\n        //     npc.Special6 = 0;\n        // }\n\n        // if(npc.Special5 > 0)\n        if(npc.Special4 & NPC_COCKPIT_RIGHT)\n        {\n            if(npc.Location.SpeedX < 0)\n                npc.Location.SpeedX = npc.Location.SpeedX * 0.95_r;\n            npc.Location.SpeedX += 0.1_n;\n        }\n        // else if(npc.Special5 < 0)\n        else if(npc.Special4 & NPC_COCKPIT_LEFT)\n        {\n            if(npc.Location.SpeedX > 0)\n                npc.Location.SpeedX = npc.Location.SpeedX * 0.95_r;\n            npc.Location.SpeedX -= 0.1_n;\n        }\n        else\n        {\n            npc.Location.SpeedX = npc.Location.SpeedX * 0.95_r;\n            if(npc.Location.SpeedX > -0.1_n && npc.Location.SpeedX < 0.1_n)\n                npc.Location.SpeedX = 0;\n        }\n\n        // if(npc.Special6 > 0)\n        if(npc.Special4 & NPC_COCKPIT_DOWN)\n        {\n            if(npc.Location.SpeedY < 0)\n                npc.Location.SpeedY = npc.Location.SpeedY * 0.95_r;\n            npc.Location.SpeedY += 0.1_n;\n        }\n        // else if(npc.Special6 < 0)\n        else if(npc.Special4 & NPC_COCKPIT_UP)\n        {\n            if(npc.Location.SpeedY > 0)\n                npc.Location.SpeedY = npc.Location.SpeedY * 0.95_r;\n            npc.Location.SpeedY -= 0.1_n;\n        }\n        else\n        {\n            npc.Location.SpeedY = npc.Location.SpeedY * 0.95_r;\n            if(npc.Location.SpeedY > -0.1_n && npc.Location.SpeedY < 0.1_n)\n                npc.Location.SpeedY = 0;\n        }\n\n        // reset after reading\n        npc.Special4 = 0;\n\n        if(npc.Location.SpeedY > 4)\n            npc.Location.SpeedY = 4;\n\n        if(npc.Location.SpeedY < -4)\n            npc.Location.SpeedY = -4;\n\n        if(npc.Location.SpeedX > 6)\n            npc.Location.SpeedX = 6;\n\n        if(npc.Location.SpeedX < -6)\n            npc.Location.SpeedX = -6;\n\n    }\n    else if(npc.Type == NPCID_CHAR3_HEAVY) // heart bomb\n    {\n        if(npc.Special4 != 0)\n        {\n            npc.Killed = 9;\n            NPCQueues::Killed.push_back(A);\n\n            int C = npc.BattleOwner;\n\n            if(npc.CantHurtPlayer > 0)\n                C = npc.CantHurtPlayer;\n\n            Bomb(npc.Location, 0, C);\n\n            for(int i = 1; i <= 5; i++)\n            {\n                NewEffect(EFFID_SPARKLE, newLoc(npc.Location.X + npc.Location.Width / 2 - 4, npc.Location.Y + npc.Location.Height / 2 - 6), 1, npc.Shadow);\n                Effect[numEffects].Location.SpeedX = dRand() * 6 - 3;\n                Effect[numEffects].Location.SpeedY = dRand() * 6 - 3;\n                Effect[numEffects].Frame = iRand(3);\n            }\n        }\n    }\n    else if(npc.Type == NPCID_VINE_BUG)\n    {\n        if(!npc.Projectile)\n        {\n            npc.Location.SpeedX = 0;\n            if(npc.Location.SpeedY < 0)\n                npc.Special = -1;\n            else\n                npc.Special = 1;\n\n            Location_t tempLocation = npc.Location;\n\n            if(npc.Special == -1)\n                tempLocation.Y -= 1;\n            else\n                tempLocation.Y += tempLocation.Height + 1;\n\n            tempLocation.Height = 1;\n            tempBool = false;\n\n            for(int i : treeNPCQuery(tempLocation, SORTMODE_NONE))\n            {\n                auto &n = NPC[i];\n                if(n.Active && !n.Hidden && n->IsAVine && CheckCollision(tempLocation, n.Location))\n                {\n                    tempBool = true;\n                    break;\n                }\n            }\n\n            if(!tempBool)\n            {\n                for(BackgroundRef_t i : treeBackgroundQuery(tempLocation, SORTMODE_NONE))\n                {\n                    if((int)i > numBackground)\n                        continue;\n\n                    Background_t &b = i;\n                    if(!b.Hidden && ((b.Type >= 174 && b.Type <= 186) || b.Type == 63) && CheckCollision(tempLocation, b.Location))\n                    {\n                        tempBool = true;\n                        break;\n                    }\n                }\n            }\n\n            if(tempBool)\n            {\n                if(npc.Special == 1)\n                    npc.Location.SpeedY = 2;\n                else\n                    npc.Location.SpeedY = -1;\n            }\n            else\n            {\n                if(npc.Special == -1)\n                {\n                    npc.Location.SpeedY = 2;\n                    npc.Special = 2;\n                }\n                else\n                {\n                    npc.Location.SpeedY = -1;\n                    npc.Special = -1;\n                }\n            }\n        }\n        else\n        {\n            npc.Location.SpeedY += Physics.NPCGravity;\n            npc.Location.SpeedX *= 0.987_r;\n\n            if(npc.Location.SpeedX > -0.1_n && npc.Location.SpeedX < 0.1_n)\n                npc.Location.SpeedX = 0;\n        }\n    }\n    else if(npc.Type == NPCID_RANDOM_POWER)\n        npc.Type = RandomBonus();\n    else if(npc.Type == NPCID_SPIKY_BALL_S4) // falling spiney\n    {\n        if(npc.Special != 0)\n        {\n            npc.Type = NPCID_SPIKY_S4;\n            npc.Special = 0;\n\n            num_t min_dist = 0;\n\n            for(int i = 1; i <= numPlayers; i++)\n            {\n                auto &p = Player[i];\n                if(!p.Dead && p.Section == npc.Section && p.TimeToLive == 0)\n                {\n                    num_t horiz_dist = NPCPlayerTargetDist(npc, p);\n                    if(min_dist == 0 || horiz_dist < min_dist)\n                    {\n                        min_dist = horiz_dist;\n                        if(npc.Location.to_right_of(p.Location))\n                            npc.Direction = -1;\n                        else\n                            npc.Direction = 1;\n                    }\n                }\n            }\n\n            npc.Location.SpeedX = Physics.NPCWalkingOnSpeed * npc.Direction;\n        }\n    }\n    else if(npc.Type == NPCID_ITEM_BUBBLE) // bubble\n    {\n        npc.Wings = WING_NONE;\n\n        if(npc.Special == NPCID_RANDOM_POWER)\n        {\n            npc.Special = RandomBonus();\n            npc.DefaultSpecial = npc.Special;\n        }\n\n        npc.Location.SpeedX = 0.75_n * npc.DefaultDirection;\n        if(npc.Special2 == 0)\n            npc.Special2 = -1;\n\n        npc.Location.SpeedY += 0.05_n * npc.Special2;\n        if(npc.Location.SpeedY > 1)\n        {\n            npc.Location.SpeedY = 1;\n            npc.Special2 = -1;\n        }\n        else if(npc.Location.SpeedY < -1)\n        {\n            npc.Location.SpeedY = -1;\n            npc.Special2 = 1;\n        }\n\n        if(npc.Special3 > 0)\n        {\n            NewEffect(EFFID_BUBBLE_POP, npc.Location);\n            PlaySoundSpatial(SFX_Bubble, npc.Location);\n            npc.Type = NPCID(npc.Special);\n            npc.Special3 = 0;\n            npc.Special2 = 0;\n            npc.Special = 0;\n            npc.Frame = EditorNPCFrame(npc.Type, npc.Direction);\n            npc.FrameCount = 0;\n            npc.Location.set_width_center(npc->TWidth);\n            npc.Location.set_height_center(npc->THeight);\n            npc.Location.SpeedX = 0;\n            npc.Location.SpeedY = 0;\n            npc.Direction = npc.DefaultDirection;\n            npc.Wings = npc.DefaultWings;\n\n            NPCQueues::update(A);\n\n            // deferring tree update to end of the NPC physics update\n\n            if(npc->IsACoin)\n            {\n                npc.Special = 1;\n                npc.Location.SpeedX = dRand() - 0.5_n;\n            }\n\n            if(npc.Direction == 0)\n            {\n                if(iRand(2) == 0)\n                    npc.Direction = 1;\n                else\n                    npc.Direction = -1;\n            }\n\n            npc.TurnAround = false;\n            if(npc.Type == NPCID_BOMB)\n                npc.Projectile = true;\n        }\n\n    }\n    else if(npc.Type == NPCID_QUAD_SPITTER) // fire plant thing\n    {\n        if(npc.Special == 0)\n        {\n            npc.Special2 += 1;\n            if(npc.Special2 >= 170)\n            {\n                npc.Special2 = 0;\n                npc.Special = 1;\n            }\n        }\n        else if(npc.Special == 1)\n        {\n            npc.Special2 += 1;\n            if(npc.Special2 >= 70)\n            {\n                npc.Special2 = 0;\n                npc.Special = 2;\n            }\n        }\n        else if(npc.Special == 2)\n        {\n            if(npc.Special2 == 0) // spit fireballs\n            {\n                for(int i = 1; i <= 4; i++)\n                {\n                    numNPCs++;\n                    NPC[numNPCs].Type = NPCID_QUAD_BALL;\n                    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                    NPC[numNPCs].Section = npc.Section;\n                    NPC[numNPCs].TimeLeft = npc.TimeLeft;\n                    NPC[numNPCs].Location.X = npc.Location.X + (npc.Location.Width - NPC[numNPCs].Location.Width) / 2;\n                    NPC[numNPCs].Location.Y = npc.Location.Y - NPC[numNPCs].Location.Height;\n                    NPC[numNPCs].Active = true;\n                    NPC[numNPCs].Layer = npc.Layer;\n\n                    if(i == 1 || i == 4)\n                    {\n                        NPC[numNPCs].Location.SpeedX = -4.0_n; // -2.5 * 1.6\n                        NPC[numNPCs].Location.SpeedY = -2.4_n; // -1.5 * 1.6\n                    }\n                    else\n                    {\n                        NPC[numNPCs].Location.SpeedX = -1.6_n; // -1.0 * 1.6\n                        NPC[numNPCs].Location.SpeedY = -3.2_n; // -2 * 1.6\n                    }\n\n                    if(i == 3 || i == 4)\n                        NPC[numNPCs].Location.SpeedX = -NPC[numNPCs].Location.SpeedX;\n\n                    // NPC[numNPCs].Location.SpeedX = NPC[numNPCs].Location.SpeedX * 1.6;\n                    // NPC[numNPCs].Location.SpeedY = NPC[numNPCs].Location.SpeedY * 1.6;\n\n                    syncLayers_NPC(numNPCs);\n                }\n            }\n\n            npc.Special2 += 1;\n            if(npc.Special2 >= 50)\n            {\n                npc.Special2 = 0;\n                npc.Special = 0;\n            }\n        }\n    }\n    else if(npc.Type == NPCID_QUAD_BALL) // plant fireballs\n    {\n        if(npc.Special == 0)\n        {\n            npc.Location.SpeedY *= 0.98_r;\n            npc.Location.SpeedX *= 0.98_r;\n            if(npc.Location.SpeedY > -0.5_n)\n            {\n                npc.Location.SpeedX = 0;\n                npc.Location.SpeedY = 0;\n                npc.Special2 = 0;\n                npc.Special = 1;\n            }\n        }\n        else\n        {\n            npc.Location.SpeedY += 0.02_n;\n            if(npc.Location.SpeedY > 2)\n                npc.Location.SpeedY = 2;\n\n            if(npc.Location.SpeedY > 0.25_n)\n            {\n                npc.Special2 += 1;\n                if(npc.Special2 < 7)\n                    npc.Location.SpeedX = -0.8_n;\n                else if(npc.Special2 < 13)\n                    npc.Location.SpeedX = 0.8_n;\n                else\n                {\n                    npc.Special2 = 0;\n                    npc.Location.SpeedX = 0;\n                }\n            }\n\n        }\n    }\n    else if(npc.Type == NPCID_BAT) // bat thing\n    {\n        // Special is an activation flag\n        // SpecialY (previously Special2) is the target Y coordinate\n        if(npc.Special == 0)\n        {\n            if(!g_config.fix_bat_start_while_inactive || npc.Active)\n            {\n                for(int i = 1; i <= numPlayers; i++)\n                {\n                    auto &p = Player[i];\n                    if(p.Section == npc.Section && !p.Dead && p.TimeToLive == 0)\n                    {\n                        Location_t tempLocation = npc.Location;\n                        tempLocation.Width = 400;\n                        tempLocation.Height = 800;\n                        tempLocation.X -= tempLocation.Width / 2;\n                        tempLocation.Y -= tempLocation.Height / 2;\n\n                        if(CheckCollision(tempLocation, p.Location))\n                        {\n                            npc.Special = 1;\n                            if(p.Location.X < npc.Location.X)\n                                npc.Direction = -1;\n                            else\n                                npc.Direction = 1;\n                            npc.Location.SpeedX = 0.01_n * npc.Direction;\n\n                            if(p.Location.Y > npc.Location.Y)\n                            {\n                                npc.Location.SpeedY = 2.5_n;\n                                npc.SpecialY = p.Location.Y - 130;\n                            }\n                            else\n                            {\n                                npc.Location.SpeedY = -2.5_n;\n                                npc.SpecialY = p.Location.Y + 130;\n                            }\n\n                            if(npc.Active)\n                                PlaySoundSpatial(SFX_BatFlap, npc.Location);\n                        }\n                    }\n                }\n            }\n        }\n        else\n        {\n            npc.Location.SpeedX = (3 - num_t::abs(npc.Location.SpeedY)) * npc.Direction;\n            if((npc.Location.SpeedY > 0 && npc.Location.Y > npc.SpecialY) || (npc.Location.SpeedY < 0 && npc.Location.Y < npc.SpecialY))\n            {\n                npc.Location.SpeedY = npc.Location.SpeedY * 0.98_r;\n                if(npc.Location.SpeedY > -0.1_n && npc.Location.SpeedY < 0.1_n)\n                    npc.Location.SpeedY = 0;\n            }\n        }\n    }\n    else if(npc.Type == NPCID_MAGIC_BOSS_BALL) // larry magic\n    {\n        if(npc.Special < 2)\n        {\n            npc.Special2 += 1;\n            if(npc.Special2 >= 30 && npc.Special != 2)\n            {\n                npc.Location.X += npc.Location.Width;\n                npc.Location.Width = 16;\n                npc.Location.set_height_floor(32);\n                npc.Special = 2;\n                npc.Location.X += -npc.Location.Width;\n                npc.Special2 = 21;\n\n                NPCQueues::Unchecked.push_back(A);\n                // deferring tree update to end of the NPC physics update\n            }\n            else if(npc.Special2 >= 15 && npc.Special != 1)\n            {\n                npc.Location.X += npc.Location.Width;\n                npc.Location.Width = 10;\n                npc.Location.set_height_floor(20);\n                npc.Special = 1;\n                npc.Location.X += -npc.Location.Width;\n\n                NPCQueues::Unchecked.push_back(A);\n                // deferring tree update to end of the NPC physics update\n            }\n        }\n    }\n    else if(npc.Type == NPCID_MAGIC_BOSS_SHELL || npc.Type == NPCID_FIRE_BOSS_SHELL) // larry/ludwig shell\n    {\n        // temporarily remove any wings if present\n        npc.Wings = WING_NONE;\n\n        if(npc.Special5 == 0) // Target a Random Player\n        {\n            int C = 0;\n            do\n            {\n                // COMPAT BUGFIX. Should be iRand(numPlayers) + 1\n                int i = (g_config.fix_multiplayer_targeting) ? (iRand(numPlayers) + 1) : (iRand(1) * numPlayers + 1); // always 1\n                if(!Player[i].Dead && Player[i].Section == npc.Section && Player[i].TimeToLive == 0)\n                    npc.Special5 = i;\n                C += 1;\n                if(C >= 20)\n                    npc.Special5 = 1;\n            }\n            while(npc.Special5 <= 0);\n        }\n\n        if(npc.Location.to_right_of(Player[npc.Special5].Location))\n            npc.Direction = -1;\n        else\n            npc.Direction = 1;\n        if(npc.Special == 0)\n        {\n            npc.Special2 += 1;\n            if(npc.Special2 >= 60)\n            {\n                npc.Special = 1;\n                npc.Special2 = 0;\n            }\n        }\n        else if(npc.Special == 1)\n        {\n            if(npc.Type == NPCID_FIRE_BOSS_SHELL && num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity))\n                npc.Location.SpeedX += 0.1_n * npc.Direction;\n            else\n                npc.Location.SpeedX += 0.2_n * npc.Direction;\n            if(npc.Type == NPCID_FIRE_BOSS_SHELL && npc.Damage >= 5)\n            {\n                if(npc.Location.SpeedX > 5.5_n)\n                    npc.Location.SpeedX = 5.5_n;\n                else if(npc.Location.SpeedX < -5.5_n)\n                    npc.Location.SpeedX = -5.5_n;\n            }\n            else if(npc.Type == NPCID_FIRE_BOSS_SHELL && npc.Damage >= 10)\n            {\n                if(npc.Location.SpeedX > 6)\n                    npc.Location.SpeedX = 6;\n                else if(npc.Location.SpeedX < -6)\n                    npc.Location.SpeedX = -6;\n            }\n            else\n            {\n                if(npc.Location.SpeedX > 5)\n                    npc.Location.SpeedX = 5;\n                else if(npc.Location.SpeedX < -5)\n                    npc.Location.SpeedX = -5;\n            }\n\n            if(npc.Type == NPCID_FIRE_BOSS_SHELL && num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity))\n            {\n                npc.Special3 += 1;\n                if((npc.Location.SpeedX < -2 && npc.Direction < 0) || (npc.Location.SpeedX > 2 && npc.Direction > 0))\n                {\n                    if(npc.Special3 >= 20 - npc.Damage * 2)\n                    {\n                        npc.Special3 = 0;\n                        npc.Location.SpeedY = -3 - dRand() * 2;\n                    }\n                }\n            }\n\n            npc.Special2 += 1;\n\n            if(npc.Special2 >= 300 && num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity))\n            {\n                npc.Special = 2;\n                npc.Special2 = 0;\n            }\n        }\n        else if(npc.Special == 2)\n        {\n            npc.Location.SpeedY = -5 - dRand() * 3;\n            if(npc.Type == NPCID_FIRE_BOSS_SHELL)\n                npc.Location.SpeedY -= 2;\n            npc.Special = 3;\n        }\n        else if(npc.Special == 3)\n        {\n            if(npc.Location.SpeedX > 2.5_n)\n                npc.Location.SpeedX -= 0.2_n;\n            else if(npc.Location.SpeedX < -2.5_n)\n                npc.Location.SpeedX += 0.2_n;\n            npc.Special2 += 1;\n\n            if(npc.Type == NPCID_FIRE_BOSS_SHELL)\n            {\n                npc.Special2 = 20;\n                PlaySoundSpatial(SFX_Spring, npc.Location);\n            }\n\n            if(npc.Special2 == 20)\n            {\n                npc.Special = 0;\n                npc.Special2 = npc.Direction;\n                npc.Special3 = 0;\n                npc.Special4 = 0;\n                npc.Special5 = 0;\n                // npc.Special6 = 0;\n                npc.Type = NPCID(npc.Type - 1);\n                npc.Location.set_width_center(npc->TWidth);\n                npc.Location.set_height_floor(npc->THeight);\n\n                // restore wings if lost\n                npc.Wings = npc.DefaultWings;\n\n                NPCQueues::Unchecked.push_back(A);\n                // deferring tree update to end of the NPC physics update\n            }\n        }\n        else\n            npc.Special = 0;\n\n    }\n    else if(npc.Type == NPCID_MAGIC_BOSS) // larry koopa\n    {\n        // special is phase\n        // special5 is targetted player\n        // special3 is jump counter\n        // special2 is direction\n\n        // special4 is attack timer\n        // special3 is attack count\n        if(npc.Inert)\n        {\n            npc.Special4 = 0;\n            npc.Special3 = 0;\n        }\n\n        if(npc.Special5 == 0) // Target a Random Player\n        {\n            int C = 0;\n            do\n            {\n                // COMPAT BUGFIX. Should be iRand(numPlayers) + 1\n                int i = (g_config.fix_multiplayer_targeting) ? (iRand(numPlayers) + 1) : (iRand(1) * numPlayers + 1); // always 1\n                // should be int i = iRand(numPlayers) + 1;\n                if(!Player[i].Dead && Player[i].Section == npc.Section && Player[i].TimeToLive == 0)\n                    npc.Special5 = i;\n                C += 1;\n                if(C >= 20)\n                    npc.Special5 = 1;\n            }\n            while(npc.Special5 <= 0);\n        }\n\n        if(npc.Location.to_right_of(Player[npc.Special5].Location))\n            npc.Direction = -1;\n        else\n            npc.Direction = 1;\n\n        if(npc.Special2 == 0)\n            npc.Special2 = npc.Direction;\n\n        if(npc.Special == 0)\n        {\n            if(npc.Special2 == -1)\n                npc.Location.SpeedX = -2.5_n;\n            else\n                npc.Location.SpeedX = 2.5_n;\n\n            // movement\n            if(npc.Location.X < Player[npc.Special5].Location.X - 400)\n                npc.Special2 = 1;\n            else if(npc.Location.X > Player[npc.Special5].Location.X + 400)\n                npc.Special2 = -1;\n\n            // jumping\n            if(num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity))\n            {\n                npc.Special3 += 1;\n                if(npc.Special3 > 30 + iRand(100))\n                {\n                    npc.Special3 = 0;\n                    npc.Location.SpeedY = -5 - dRand() * 4;\n                }\n            }\n            else\n                npc.Special3 = 0;\n\n            // attack timer\n            npc.Special4 += 1;\n            if(npc.Special4 > 100 + iRand(100) && (num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity) || npc.Wings))\n            {\n                npc.Special = 1;\n                npc.Special5 = 0;\n                npc.Special3 = 0;\n                npc.Special4 = 0;\n            }\n        }\n        else if(npc.Special == 1)\n        {\n            if(npc.Location.to_right_of(Player[npc.Special5].Location))\n                npc.Direction = -1;\n            else\n                npc.Direction = 1;\n\n            npc.Special2 = npc.Direction;\n            npc.Location.SpeedX = 0;\n            npc.Special3 += 1;\n\n            if(npc.Special3 >= 10)\n            {\n                npc.Special3 = 0;\n                npc.Special = 2;\n            }\n        }\n        else if(npc.Special == 2)\n        {\n            npc.Location.SpeedX = 0;\n            if(npc.Special3 == 0 || npc.Special3 == 6 || npc.Special3 == 12) // shoot\n            {\n\n                if(npc.Special3 == 0)\n                {\n                    npc.SpecialX = Player[npc.Special5].Location.X + Player[npc.Special5].Location.Width / 2;\n                    npc.SpecialY = Player[npc.Special5].Location.Y + Player[npc.Special5].Location.Height / 2 + 16;\n                }\n\n                if(npc.Special3 == 0)\n                    PlaySoundSpatial(SFX_Transform, npc.Location);\n\n                numNPCs++;\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].TimeLeft = 100;\n                NPC[numNPCs].Direction = npc.Direction;\n                NPC[numNPCs].Section = npc.Section;\n                NPC[numNPCs].Type = NPCID_MAGIC_BOSS_BALL;\n                NPC[numNPCs].Location.Width = 10;\n                NPC[numNPCs].Location.Height = 8;\n                NPC[numNPCs].Frame = 3;\n                NPC[numNPCs].Special2 = npc.Special3;\n\n                if(NPC[numNPCs].Direction == -1)\n                    NPC[numNPCs].Location.X = npc.Location.X - 20;\n                else\n                    NPC[numNPCs].Location.X = npc.Location.X + npc.Location.Width - NPC[numNPCs].Location.Width + 20;\n\n                NPC[numNPCs].Location.Y = npc.Location.Y + 47;\n\n                NPCSetSpeedTarget_FixedX(NPC[numNPCs], npc.SpecialX, npc.SpecialY, 3, 3);\n\n                syncLayers_NPC(numNPCs);\n            }\n\n            npc.Special3 += 1;\n            if(npc.Special3 >= 30)\n            {\n                npc.Special = 0;\n                npc.Special4 = 0;\n                npc.Special5 = 0;\n                // npc.Special6 = 0;\n                npc.SpecialX = 0;\n                npc.SpecialY = 0;\n            }\n        }\n\n        // ludwig koopa\n    }\n    else if(npc.Type == NPCID_FIRE_BOSS)\n    {\n        // special is phase\n        // special5 is targetted player\n        // special3 is jump counter\n        // special2 is direction\n\n        // special4 is attack timer\n        // special3 is attack count\n\n        // SpecialX (Special6 in SMBX 1.3) appears to be an additional attack timer\n\n        if(npc.Inert)\n        {\n            npc.Special4 = 0;\n            npc.Special3 = 0;\n            npc.SpecialX = 0;\n        }\n\n        if(npc.Special5 == 0) // Target a Random Player\n        {\n            int C = 0;\n            do\n            {\n                // COMPAT BUGFIX. Should be iRand(numPlayers) + 1\n                int i = (g_config.fix_multiplayer_targeting) ? (iRand(numPlayers) + 1) : (iRand(1) * numPlayers + 1); // always 1\n                if(!Player[i].Dead && Player[i].Section == npc.Section && Player[i].TimeToLive == 0)\n                    npc.Special5 = i;\n                C += 1;\n                if(C >= 20)\n                    npc.Special5 = 1;\n            }\n            while(npc.Special5 <= 0); // TESTME: !(npc.Special5 > 0)\n        }\n\n        if(npc.Location.to_right_of(Player[npc.Special5].Location))\n            npc.Direction = -1;\n        else\n            npc.Direction = 1;\n\n        if(npc.Special2 == 0)\n            npc.Special2 = npc.Direction;\n\n        if(npc.Special == 0)\n        {\n            if((npc.Damage < 5 && npc.SpecialX > 60) || (npc.Damage < 10 && npc.SpecialX > 80) || npc.Inert)\n            {\n                if(npc.Special2 == -1)\n                    npc.Location.SpeedX = -1.5_n;\n                else\n                    npc.Location.SpeedX = 1.5_n;\n\n                // movement\n                if(npc.Location.X < Player[npc.Special5].Location.X - 400)\n                    npc.Special2 = 1;\n                else if(npc.Location.X > Player[npc.Special5].Location.X + 400)\n                    npc.Special2 = -1;\n            }\n            else\n            {\n                npc.Location.SpeedX = npc.Location.SpeedX * 0.98_r;\n                if(num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity))\n                    npc.Location.SpeedX = 0;\n\n            }\n\n            // attack timer\n            bool can_advance = (num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity) || npc.Wings);\n            if(can_advance)\n                npc.SpecialX += 1;\n\n            if(npc.SpecialX == 20 || npc.SpecialX == 40 || npc.SpecialX == 60 || (npc.Damage >= 5 && npc.SpecialX == 80) || (npc.Damage >= 10 && npc.SpecialX == 100))\n            {\n                npc.Special = 1;\n                npc.Special5 = 0;\n                npc.Special3 = 0;\n                npc.Special4 = 0;\n            }\n\n            if(npc.Damage >= 10 && npc.Special == 0 && npc.SpecialX >= 100)\n                npc.SpecialX = 200;\n\n            if(npc.SpecialX >= 160 && can_advance)\n            {\n                npc.SpecialX = 0;\n                npc.Special = 3;\n                PlaySoundSpatial(SFX_Spring, npc.Location);\n                npc.Location.SpeedY = -7 - dRand() * 2;\n                // clip wings if present\n                npc.Wings = WING_NONE;\n            }\n        }\n        else if(npc.Special == 3)\n        {\n            if(npc.Location.SpeedY > 0) // turn into shell\n            {\n                npc.Special = 1;\n                npc.Special2 = 0;\n                npc.Special3 = 0;\n                npc.Special4 = 0;\n                npc.Special5 = 0;\n                // npc.Special6 = 0;\n                npc.SpecialX = 0;\n                npc.Type = NPCID_FIRE_BOSS_SHELL;\n                npc.Location.set_width_center(npc->TWidth);\n                npc.Location.set_height_floor(npc->THeight);\n\n                NPCQueues::Unchecked.push_back(A);\n                // deferring tree update to end of the NPC physics update\n            }\n            else\n            {\n                npc.Location.SpeedX += 0.2_n * npc.Special2;\n                if(npc.Location.SpeedX > 5)\n                    npc.Location.SpeedX = 5;\n                else if(npc.Location.SpeedX < -5)\n                    npc.Location.SpeedX = -5;\n            }\n        }\n        else if(npc.Special == 1 || npc.Special == 2)\n        {\n            if(npc.Location.to_right_of(Player[npc.Special5].Location))\n                npc.Direction = -1;\n            else\n                npc.Direction = 1;\n\n            npc.Special2 = npc.Direction;\n            npc.Location.SpeedX = 0;\n\n            if(npc.Special3 == 20) // shoot\n            {\n                PlaySoundSpatial(SFX_BigFireball, npc.Location);\n                numNPCs++;\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].TimeLeft = 100;\n                NPC[numNPCs].Direction = npc.Direction;\n                NPC[numNPCs].Section = npc.Section;\n                NPC[numNPCs].Type = NPCID_FIRE_BOSS_FIRE;\n                NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                NPC[numNPCs].Frame = 0;\n\n                if(NPC[numNPCs].Direction == -1)\n                    NPC[numNPCs].Location.X = npc.Location.X - 24;\n                else\n                    NPC[numNPCs].Location.X = npc.Location.X + npc.Location.Width - NPC[numNPCs].Location.Width + 24;\n                NPC[numNPCs].Location.Y = npc.Location.Y + 4;\n\n                NPCSetSpeedTarget_FixedX(NPC[numNPCs], Player[npc.Special5].Location, 4, 2);\n\n                syncLayers_NPC(numNPCs);\n            }\n\n            npc.Special3 += 1;\n            if(npc.Special3 < 20)\n                npc.Special = 1;\n            else\n                npc.Special = 2;\n\n            if(npc.Special3 >= 40)\n            {\n                npc.Special = 0;\n                npc.Special5 = 0;\n                npc.Special3 = 0;\n                npc.Special4 = 0;\n            }\n        }\n    }\n    else if(npc.Type == NPCID_SWORDBEAM) // link sword beam\n    {\n        npc.Special += 1;\n        if(npc.Special == 40)\n        {\n            npc.Killed = 9;\n            NPCQueues::Killed.push_back(A);\n        }\n\n        // unified sparkle code (previously there was a 1-4 loop above, and the iRand(2) code was below)\n        for(int i = 1; i <= 5; i++)\n        {\n            if((i < 5 && npc.Special == 40)\n                || (i == 5 && iRand(2) == 0))\n            {\n                Location_t tempLocation;\n                tempLocation.Height = EffectHeight[EFFID_SPARKLE];\n                tempLocation.Width = EffectWidth[EFFID_SPARKLE];\n                tempLocation.SpeedX = 0;\n                tempLocation.SpeedY = 0;\n                tempLocation.X = npc.Location.X + dRand() * 16 - (num_t)EffectWidth[EFFID_SPARKLE] / 2 - 4; // + .Location.SpeedX\n                tempLocation.Y = npc.Location.Y + dRand() * 4 - (num_t)EffectHeight[EFFID_SPARKLE] / 2 - 2;\n                NewEffect(EFFID_SPARKLE, tempLocation);\n\n                if(i < 5)\n                {\n                    Effect[numEffects].Location.SpeedX = npc.Location.SpeedX * 0.3_r + dRand() * 2 - 1;\n                    Effect[numEffects].Location.SpeedY = dRand() * 1 - 0.5_n;\n                    Effect[numEffects].Frame = iRand(3);\n                }\n                else\n                {\n                    Effect[numEffects].Location.SpeedX = npc.Location.SpeedX * 0.15_r;\n                    Effect[numEffects].Location.SpeedY = npc.Location.SpeedY; // + Rnd * 2 - 1\n                    Effect[numEffects].Frame = iRand(2) + 1;\n                }\n            }\n        }\n    }\n    else if(npc.Type == NPCID_BOMBER_BOSS) // mouser\n    {\n        // don't despawn -- moved here from the top of the routine\n        if(npc.TimeLeft > 1)\n            npc.TimeLeft = 100;\n\n        if(npc.Immune == 0)\n            NPCFaceNearestPlayer(npc);\n        else\n        {\n            if(iRand(10) == 0)\n                npc.Direction = -npc.Direction;\n            npc.Special3 = 0;\n            npc.Special4 = 0;\n        }\n\n        if(npc.Immune != 0)\n            npc.Location.SpeedX = 0;\n        else if(npc.Special == 0)\n        {\n            if(num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity) || npc.Wings)\n            {\n                if(npc.Special2 == 0)\n                    npc.Special2 = npc.Direction;\n\n                npc.Location.SpeedX = 2 * npc.Special2;\n\n                if(npc.Location.X < npc.DefaultLocationX - 64)\n                    npc.Special2 = 1;\n                else if(npc.Location.X > npc.DefaultLocationX + 64)\n                    npc.Special2 = -1;\n\n                npc.Special3 += 1;\n                npc.Special4 += 1;\n\n                if(npc.Special3 > 100 + iRand(200))\n                {\n                    npc.Special3 = 0;\n                    npc.Location.SpeedX = 0;\n                    npc.Location.SpeedY = -5;\n                }\n                else if(npc.Special4 > 20 + iRand(200))\n                {\n                    npc.Special4 = 0;\n                    npc.Special = -10;\n                }\n            }\n            else\n                npc.Location.SpeedX = 0;\n        }\n        else\n        {\n            if(npc.Special == -1)\n            {\n                npc.Special = 20;\n                numNPCs++;\n                NPC[numNPCs].Layer = LAYER_SPAWNED_NPCS;\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].Direction = npc.Direction;\n                NPC[numNPCs].Type = NPCID_BOMB;\n                NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                NPC[numNPCs].Location.Y = npc.Location.Y + npc.Location.Height - 48;\n                NPC[numNPCs].Location.X = npc.Location.X + (npc.Location.Width - NPC[numNPCs].Location.Width) / 2 - 12 * NPC[numNPCs].Direction;\n                NPC[numNPCs].TimeLeft = 100;\n                NPC[numNPCs].Section = npc.Section;\n                NPC[numNPCs].Location.SpeedX = (5 + dRand() * 3) * NPC[numNPCs].Direction;\n                NPC[numNPCs].Location.SpeedY = -5 - (dRand() * 3);\n\n                syncLayers_NPC(numNPCs);\n            }\n\n            npc.Location.SpeedX = 0;\n\n            if(npc.Special < 0)\n                npc.Special += 1;\n            else\n                npc.Special -= 1;\n        }\n    }\n    else if(npc.Type == NPCID_WALK_PLANT) // muncher thing\n    {\n        if(npc.Special == 0)\n        {\n            for(int i = 1; i <= numPlayers; i++)\n            {\n                auto &p = Player[i];\n                if(!p.Dead && p.TimeToLive == 0 && p.Section == npc.Section)\n                {\n                    Location_t tempLocation = npc.Location;\n                    tempLocation.Height = 256;\n                    tempLocation.Y -= tempLocation.Height;\n\n                    if(CheckCollision(tempLocation, p.Location))\n                    {\n                        npc.Special = 1;\n                        npc.Location.SpeedY = -7;\n                        npc.Location.SpeedX = 0;\n                    }\n                }\n            }\n        }\n        else if(num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity))\n            npc.Special = 0;\n\n        if(!npc.Stuck && npc.Special == 0)\n        {\n            if(npc.Special2 == 0)\n            {\n                if(npc.Location.X < npc.DefaultLocationX - 128 && npc.Direction == -1)\n                    npc.Special2 = 60;\n                else if(npc.Location.X > npc.DefaultLocationX + 128 && npc.Direction == 1)\n                    npc.Special2 = 60;\n\n                npc.Location.SpeedX = 1.4_n * npc.Direction;\n\n                if(num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity))\n                    npc.Location.SpeedY = -1.5_n;\n            }\n            else\n            {\n                npc.Special2 -= 1;\n\n                if(num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity))\n                    npc.Location.SpeedX = 0;\n\n                if(npc.Special2 == 0)\n                {\n                    if(npc.Location.X < npc.DefaultLocationX)\n                        npc.Direction = 1;\n                    else\n                        npc.Direction = -1;\n                }\n            }\n        }\n    }\n    // newly combined (note that Special was previously the stage counter for FIRE_DISK; now Special2 is the counter for both)\n    else if(npc.Type == NPCID_FIRE_CHAIN || npc.Type == NPCID_FIRE_DISK)\n    {\n        num_t C;\n        num_t B;\n        num_t thresh;\n\n        if(npc.Type == NPCID_FIRE_DISK)\n        {\n            npc.Special4 += 1;\n\n            if(npc.Special4 == 4)\n            {\n                NewEffect(EFFID_FIRE_DISK_DIE, npc.Location);\n                Effect[numEffects].Frame = npc.Frame;\n                Effect[numEffects].Location.SpeedX = 0;\n                Effect[numEffects].Location.SpeedY = 0;\n                npc.Special4 = 0;\n            }\n\n            C = 0.2_n;\n            B = 6.05_n;\n            thresh = 0_n;\n        }\n        else\n        {\n            C = 0.03_n * npc.DefaultSpecial;\n            B = 0.98_n * npc.DefaultSpecial;\n            thresh = 0.001_n;\n        }\n\n        if(npc.Special2 == 0)\n        {\n            npc.Location.SpeedX += C;\n            npc.Location.SpeedY += C * npc.DefaultDirection;\n\n            if(npc.Special5 == 0)\n            {\n                npc.Special5 = 1;\n                npc.Location.SpeedX = -B;\n                npc.Location.SpeedY = 0;\n            }\n\n            if(npc.Location.SpeedX >= -thresh)\n            {\n                if(npc.DefaultDirection > 0)\n                    npc.Special2 = 1;\n                else if(npc.DefaultDirection < 0 || npc.Type == NPCID_FIRE_CHAIN)\n                    npc.Special2 = 3;\n\n                npc.Special5 = 0;\n            }\n        }\n        else if(npc.Special2 == 1)\n        {\n            npc.Location.SpeedX += C * npc.DefaultDirection;\n            npc.Location.SpeedY -= C;\n\n            if(npc.Special5 == 0)\n            {\n                npc.Special5 = 1;\n                npc.Location.SpeedY = B;\n                npc.Location.SpeedX = 0;\n            }\n\n            if(npc.Location.SpeedY <= thresh)\n            {\n                npc.Special2 += npc.DefaultDirection;\n                npc.Special5 = 0;\n            }\n        }\n        else if(npc.Special2 == 2)\n        {\n            npc.Location.SpeedX -= C;\n            npc.Location.SpeedY -= C * npc.DefaultDirection;\n\n            if(npc.Special5 == 0)\n            {\n                npc.Special5 = 1;\n                npc.Location.SpeedX = B;\n                npc.Location.SpeedY = 0;\n            }\n\n            if(npc.Location.SpeedX <= thresh)\n            {\n                npc.Special2 += npc.DefaultDirection;\n                npc.Special5 = 0;\n            }\n        }\n        else if(npc.Special2 == 3)\n        {\n            npc.Location.SpeedX -= C * npc.DefaultDirection;\n            npc.Location.SpeedY += C;\n\n            if(npc.Special5 == 0)\n            {\n                npc.Special5 = 1;\n                npc.Location.SpeedY = -B;\n                npc.Location.SpeedX = 0;\n            }\n\n            if(npc.Location.SpeedY >= -thresh)\n            {\n                if(npc.DefaultDirection > 0)\n                    npc.Special2 = 0;\n                else\n                    npc.Special2 += npc.DefaultDirection;\n\n                npc.Special5 = 0;\n            }\n        }\n    }\n    else if(npc.Type == NPCID_LOCK_DOOR)\n    {\n        for(int i : treeNPCQuery(npc.Location, SORTMODE_NONE))\n        {\n            auto &n = NPC[i];\n            if(n.Type == NPCID_KEY && n.Active && n.HoldingPlayer != 0 && CheckCollision(npc.Location, n.Location))\n            {\n                n.Killed = 9;\n                NewEffect(EFFID_SMOKE_S3, n.Location);\n                npc.Killed = 3;\n                NPCQueues::Killed.push_back(A);\n                NPCQueues::Killed.push_back(i);\n            }\n        }\n    }\n    else if(npc.Type == NPCID_BOSS_FRAGILE) // Mother Brain\n    {\n        // don't despawn -- moved here from the top of the routine\n        if(npc.TimeLeft > 1)\n            npc.TimeLeft = 100;\n\n        if(npc.Special >= 1)\n        {\n            // double B = 1 - (npc.Special / 45.0);\n            // double C = B * 0.5;\n            // B *= 15;\n            // C *= 15;\n            // npc.Location.X = npc.DefaultLocationX + dRand() * B - dRand() * C;\n            // npc.Location.Y = npc.DefaultLocationY + dRand() * B - dRand() * C;\n\n            int time_left = 45 - npc.Special;\n\n            num_t dx1 = dRand();\n            num_t dx2 = dRand();\n            num_t dy1 = dRand();\n            num_t dy2 = dRand();\n            npc.Location.X = npc.DefaultLocationX + (dx1 * 2 - dx2) * time_left / 6;\n            npc.Location.Y = npc.DefaultLocationY + (dy1 * 2 - dy2) * time_left / 6;\n\n            npc.Special += 1;\n            if(npc.Special >= 45)\n                npc.Special = 0;\n\n            // deferring tree update to end of the NPC physics update\n        }\n        else\n        {\n            npc.ResetLocation();\n            NPCQueues::Unchecked.push_back(A);\n            // deferring tree update to end of the NPC physics update\n        }\n    }\n    else if(npc.Type == NPCID_HOMING_BALL) // O thing\n    {\n        if(npc.Special == 0)\n        {\n            npc.Special2 += 1;\n\n            if(npc.Special2 > 80 + iRand(20))\n            {\n                npc.Special = 1;\n\n                int ip = NPCTargetPlayer(npc);\n                NPCSetSpeedTarget_FixedSpeed(npc, Player[ip].Location, 3);\n            }\n        }\n    }\n    else if(npc.Type == NPCID_HOMING_BALL_GEN) // Metroid O shooter thing\n    {\n        // moved from pre-NPCSpecial code\n        npc.Location.SpeedX = 0;\n        npc.Location.SpeedY = 0;\n\n        // was Special in SMBX 1.3\n        npc.SpecialY += 1 + dRand();\n        if(npc.SpecialY >= 200 + dRand() * 200)\n        {\n            npc.SpecialY = 0;\n            numNPCs++;\n            NPC[numNPCs].Inert = npc.Inert;\n            NPC[numNPCs].Location.Height = 32;\n            NPC[numNPCs].Location.Width = 28;\n            NPC[numNPCs].Location.X = npc.Location.X + 2;\n            NPC[numNPCs].Location.Y = npc.Location.Y;\n            NPC[numNPCs].Section = npc.Section;\n            NPC[numNPCs].Layer = LAYER_SPAWNED_NPCS;\n            NPC[numNPCs].Type = NPCID_HOMING_BALL;\n            NPC[numNPCs].Active = true;\n            NPC[numNPCs].TimeLeft = 50;\n\n            syncLayers_NPC(numNPCs);\n        }\n    }\n    else if(npc.Type == NPCID_WALL_SPARK || npc.Type == NPCID_WALL_BUG || npc.Type == NPCID_WALL_TURTLE) // sparky\n    {\n        // was F\n        int speed_mult = (npc.Type == NPCID_WALL_SPARK) ? 2 : 1;\n\n        // special stores wall attachment side, special2 stores current direction (1 is right, -1 is left)\n\n        if(npc.Special == 0)\n        {\n            if(npc.Direction == 0)\n            {\n                if(iRand(2) == 1)\n                    npc.Direction = 1;\n                else\n                    npc.Direction = -1;\n            }\n\n            npc.Special = 1;\n            npc.Special2 = npc.Direction;\n        }\n\n        // handle floor slopes\n        if(npc.Slope > 0 && (npc.Special == 2 || npc.Special == 4))\n        {\n            // \"right side of a wall\" -> moving left\n            if(npc.Special == 2)\n                npc.Special2 = 1;\n            // \"left side of a wall\" -> moving left\n            else if(npc.Special == 4)\n                npc.Special2 = -1;\n\n            // you're on a floor!\n            npc.Special = 1;\n        }\n\n        // timer for being unsupported while on floor\n        npc.Special5 += 1;\n        if(npc.Special5 >= 8 && npc.Special == 1)\n        {\n            npc.Special5 = 8;\n            npc.Special = 0;\n            npc.Location.SpeedY += Physics.NPCGravity;\n            if(npc.Location.SpeedY > 8)\n                npc.Location.SpeedY = 8;\n        }\n\n        // the following code was previously separated into conditions 1/2/3/4\n        if(npc.Special < 1 || npc.Special > 4)\n            return;\n\n        // set speed: push NPC into wall it is currently climbing\n        // FIXME: is Special2 ever anything other than -1 or 1 at this point? Would be nice to remove the abs calls.\n        if(npc.Special == 1)\n        {\n            npc.Location.SpeedY = speed_mult * std::abs(npc.Special2);\n            npc.Location.SpeedX = speed_mult * npc.Special2;\n        }\n        else if(npc.Special == 2)\n        {\n            npc.Location.SpeedY = speed_mult * npc.Special2;\n            npc.Location.SpeedX = std::abs(npc.Special2);\n        }\n        else if(npc.Special == 3)\n        {\n            npc.Location.SpeedY = -std::abs(npc.Special2);\n            npc.Location.SpeedX = speed_mult * npc.Special2;\n        }\n        else if(npc.Special == 4)\n        {\n            npc.Location.SpeedY = speed_mult * npc.Special2;\n            npc.Location.SpeedX = -std::abs(npc.Special2);\n        }\n\n        // check whether the NPC is about to turn an inner corner (eg, hit a wall when on ground)\n        bool inner_corner = false;\n        Location_t tempLocation;\n        tempLocation.Width = 2;\n        tempLocation.Height = 2;\n        tempLocation.X = npc.Location.X + npc.Location.Width / 2 - 1;\n        tempLocation.Y = npc.Location.Y + npc.Location.Height / 2 - 1;\n\n        // check the point 18px to the left/right of the NPC's center (horizontal climbing)\n        if(npc.Special == 1 || npc.Special == 3)\n            tempLocation.X += 18 * npc.Special2;\n        // check the point 18px to the top/bottom of the NPC's center (vertical climbing)\n        else if(npc.Special == 2 || npc.Special == 4)\n            tempLocation.Y += 18 * npc.Special2;\n\n        for(Block_t& block : treeFLBlockQuery(tempLocation, SORTMODE_NONE))\n        {\n            if(!block.Hidden && !BlockNoClipping[block.Type] && !BlockIsSizable[block.Type] && !BlockOnlyHitspot1[block.Type])\n            {\n                if(npc.Special == 1 && BlockSlope[block.Type])\n                    continue;\n\n                if((npc.Special == 3 || npc.Special == 4) && BlockSlope2[block.Type])\n                    continue;\n\n                if(CheckCollision(tempLocation, block.Location))\n                {\n                    inner_corner = true;\n                    break;\n                }\n            }\n        }\n\n        // routine for turning an inner corner\n        if(inner_corner)\n        {\n            int old_side = npc.Special;\n\n            // on a ceiling / floor?\n            if(npc.Special == 1 || npc.Special == 3)\n            {\n                // going right -> hit left side of wall\n                if(npc.Special2 == 1)\n                    npc.Special = 2;\n                // hit right side of wall\n                else\n                    npc.Special = 4;\n\n                // was on floor -> now you're going up\n                if(old_side == 1)\n                {\n                    npc.Location.SpeedY = 0;\n                    npc.Special2 = -1;\n                }\n                // now you're going down\n                else\n                    npc.Special2 = 1;\n            }\n            // on a wall?\n            else if(npc.Special == 2 || npc.Special == 4)\n            {\n                // going down -> hit a floor\n                if(npc.Special2 == 1)\n                    npc.Special = 1;\n                // hit a ceiling\n                else\n                    npc.Special = 3;\n\n                // was on left side of wall -> now you're going left\n                if(old_side == 2)\n                    npc.Special2 = -1;\n                // now you're going right\n                else\n                    npc.Special2 = 1;\n            }\n\n            return;\n        }\n\n        // check whether the wall the NPC is pressing into is gone -> it's on an outer corner\n        bool outer_corner = true;\n\n        // check the 8px below (case 1) / above (case 3) the NPC\n        if(npc.Special == 1 || npc.Special == 3)\n        {\n            tempLocation.Width = npc.Location.Width + ((npc.Special == 1) ? 2 : 0);\n            tempLocation.Height = 8;\n            tempLocation.X = npc.Location.X;\n            tempLocation.Y = npc.Location.Y + ((npc.Special == 1) ? npc.Location.Height : -8);\n        }\n        // check the 8px to the right (case 2) / left (case 4) of the NPC\n        else if(npc.Special == 2 || npc.Special == 4)\n        {\n            tempLocation.Width = 8;\n            tempLocation.Height = npc.Location.Height;\n            tempLocation.Y = npc.Location.Y;\n            tempLocation.X = npc.Location.X + ((npc.Special == 2) ? npc.Location.Width : -8);\n        }\n\n        for(BlockRef_t block_p : treeFLBlockQuery(tempLocation, SORTMODE_COMPAT))\n        {\n            Block_t& block = *block_p;\n\n            if(!block.Hidden && !BlockNoClipping[block.Type] && !BlockIsSizable[block.Type] && !BlockOnlyHitspot1[block.Type])\n            {\n                if(CheckCollision(tempLocation, block.Location))\n                {\n                    // this saves the last Block that the NPC was on top of\n                    if(npc.Special == 1)\n                        npc.Special3 = (vbint_t)block_p;\n                    // this makes the climbing work when on a ceiling slope, without slamming into ceilings otherwise\n                    else if(npc.Special == 3)\n                    {\n                        if(BlockSlope2[block.Type] != 0)\n                            npc.Location.SpeedY *= speed_mult;\n                    }\n\n                    outer_corner = false;\n                    break;\n                }\n            }\n        }\n\n        // outer corner turning logic\n        if(outer_corner)\n        {\n            int old_side = npc.Special;\n\n            // moving on floor / ceiling\n            if(npc.Special == 1 || npc.Special == 3)\n            {\n                // was moving right -> now you're on right side of floor/ceiling block\n                if(npc.Special2 == 1)\n                {\n                    // VB6 logic: turn right off of floor extra beautifully (but note that this could potentially cause the NPC to return to a location from long ago)\n                    if(npc.Special == 1 && npc.Special3 > 0)\n                    {\n                        npc.Location.X = Block[npc.Special3].Location.X + Block[npc.Special3].Location.Width + 2;\n                        npc.Location.Y += 2;\n                    }\n\n                    npc.Special = 4;\n                }\n                // now you're on left side of floor/ceiling block\n                else\n                    npc.Special = 2;\n\n                // on floor -> moving down\n                if(old_side == 1)\n                    npc.Special2 = 1;\n                // on ceiling -> moving up\n                else\n                    npc.Special2 = -1;\n            }\n            // moving on wall\n            else if(npc.Special == 2 || npc.Special == 4)\n            {\n                // was moving down -> now you're on bottom of wall block\n                if(npc.Special2 == 1)\n                    npc.Special = 3;\n                // now you're on top of wall block\n                else\n                    npc.Special = 1;\n\n                // on left side of wall -> moving right\n                if(old_side == 2)\n                    npc.Special2 = 1;\n                // on right side of wall -> moving left\n                else\n                    npc.Special2 = -1;\n            }\n        }\n    }\n    else if(npc.Type == NPCID_SICK_BOSS) // Wart\n    {\n        // don't despawn -- moved from top of this routine\n        if(npc.TimeLeft > 1)\n            npc.TimeLeft = 100;\n\n        if(!npc.Wings)\n            npc.Direction = npc.DefaultDirection;\n\n        if(npc.Immune > 0 && npc.Special != 3)\n            npc.Special = 2;\n\n        if(npc.Damage >= 30)\n        {\n            if(npc.Special != 3)\n                PlaySoundSpatial(SFX_SickBossKilled, npc.Location);\n            npc.Special = 3;\n        }\n\n        if(npc.Special == 0)\n        {\n            npc.Special3 += 1;\n\n            if(npc.Special3 > 160 + iRand(140))\n            {\n                npc.Special = 1;\n                npc.Special3 = 0;\n            }\n\n            if(npc.Special2 == 1)\n            {\n                npc.Location.SpeedX = 1;\n                if(npc.Location.X > npc.DefaultLocationX + npc.Location.Width * 1)\n                    npc.Special2 = -1;\n            }\n            else if(npc.Special2 == -1)\n            {\n                npc.Location.SpeedX = -1;\n                if(npc.Location.X < npc.DefaultLocationX - npc.Location.Width * 1)\n                    npc.Special2 = 1;\n            }\n            else\n                npc.Special2 = npc.Direction;\n        }\n        else if(npc.Special == 1)\n        {\n            npc.Location.SpeedX = 0;\n            if(npc.Special3 == 0)\n                PlaySoundSpatial(SFX_SickBossSpit, npc.Location);\n\n            npc.Special3 += 1;\n            if((npc.Special3 % 10) == 0)\n            {\n                numNPCs++;\n                NPC[numNPCs].Inert = npc.Inert;\n                NPC[numNPCs].Location.Height = 32;\n                NPC[numNPCs].Location.Width = 32;\n                NPC[numNPCs].Location.X = npc.Location.X + npc.Location.Width / 2 - 16 + (32 * npc.Direction);\n                NPC[numNPCs].Location.Y = npc.Location.Y + 18;\n                NPC[numNPCs].Direction = npc.Direction;\n                NPC[numNPCs].Type = NPCID_SICK_BOSS_BALL;\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].TimeLeft = 50;\n                NPC[numNPCs].Location.SpeedY = -7;\n                NPC[numNPCs].Location.SpeedY += dRand() * 6 - 3;\n                NPC[numNPCs].Location.SpeedX = 7 * NPC[numNPCs].Direction;\n                NPC[numNPCs].Location.SpeedX *= (140 - npc.Special3);\n                NPC[numNPCs].Location.SpeedX /= 140;\n\n                syncLayers_NPC(numNPCs);\n            }\n\n            if(npc.Special3 > 120 + iRand(40))\n            {\n                npc.Special = 0;\n                npc.Special3 = 0;\n            }\n        }\n        else if(npc.Special == 2)\n        {\n            npc.Location.SpeedX = 0;\n            npc.Special4 += 1;\n            if(npc.Special4 >= 120)\n            {\n                npc.Special4 = 0;\n                npc.Special = 0;\n            }\n        }\n        else if(npc.Special == 3)\n        {\n            npc.Location.SpeedX = 0;\n            npc.Special4 += 1;\n            if(npc.Special4 >= 120)\n            {\n                npc.Killed = 3;\n                NPCQueues::Killed.push_back(A);\n            }\n        }\n\n    }\n    // Platform movement\n    else if(npc.Type == NPCID_YEL_PLATFORM || npc.Type == NPCID_BLU_PLATFORM || npc.Type == NPCID_GRN_PLATFORM ||\n            npc.Type == NPCID_RED_PLATFORM || npc.Type == NPCID_PLATFORM_S3 || npc.Type == NPCID_SAW)\n    {\n        // SpecialX (prev Special2) stores the previous X speed (ignoring platform pause)\n        // SpecialY (prev Special) stores the previous Y speed (ignoring platform pause)\n\n        // straightLine = false; // SET BUT NOT USED\n        // UNUSED(straightLine);\n        tempBool = false;\n\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            if(Player[B].Section == npc.Section)\n                tempBool = true;\n        }\n\n        int railAlgo = int(npc.Variant);\n\n        if(npc.Type == NPCID_SAW && railAlgo != 2) // Grinder\n        {\n            npc.Location.X -= 24;\n            npc.Location.Width = 96;\n            npc.Location.Y += 8;\n            npc.Location.Height = 32;\n\n            NPCQueues::Unchecked.push_back(A);\n            // deferring tree update to end of the NPC physics update\n        }\n\n        const num_t speed = 2; // The Speed\n\n        if((npc.Direction == 1 && tempBool) || npc.Type == NPCID_SAW) // Player in same section, enabled, or, grinder\n        {\n            bool pausePlatforms = !AllPlayersNormal();\n\n            // this code ran unconditionally in SMBX 1.3\n            if(!g_config.fix_platforms_acceleration || !pausePlatforms) // Keep zeroed speed when player required the pause of the move effect\n            {\n                npc.Location.SpeedY = npc.SpecialY;\n                npc.Location.SpeedX = npc.SpecialX;\n            }\n\n            tempBool = false;\n            // tempBool2 = false; // \"value set, but never unused\"\n            bool centered = false;\n\n            Location_t tempLocation = npc.Location;\n\n            switch(railAlgo) // The hot spot (the rail attachment point)\n            {\n            case 0:\n            case 1: // Fixed 15x47 hot spot\n                tempLocation.Y = npc.Location.Y + 15;\n                if(railAlgo == 1)\n                    tempLocation.Y -= 1; // Exclusive workaround for The Invasion 1 to fix the stuck at the \"Clown Car Parking\" level\n                tempLocation.Height = 2;\n                tempLocation.X = npc.Location.X + 47;\n                tempLocation.Width = 2;\n                break;\n\n            case 2: // Centered hot spot\n                centered = true;\n                tempLocation.Y = npc.Location.Y + (npc.Location.Height / 2);\n                tempLocation.Height = 2;\n                tempLocation.X = npc.Location.X + (npc.Location.Width / 2) - 1;\n                tempLocation.Width = 2;\n                break;\n            }\n\n            num_t speed_y = 0; // D = 0;\n            num_t speed_x = 0; // E = 0;\n            vbint_t bgo_type = 0; // F = 0;\n            // tempNPC = npc;\n            Location_t oldLoc = npc.Location;\n\n            for(int i : treeBackgroundQuery(tempLocation, SORTMODE_ID))\n            {\n                if(i > numBackground)\n                    break;\n\n                // Any nearest BGO touched? (rails and reverse buffers)\n                if((Background[i].Type >= 70 && Background[i].Type <= 74) || Background[i].Type == 100)\n                {\n                    // Not hidden\n                    if(!Background[i].Hidden)\n                    {\n                        if(CheckCollision(tempLocation, Background[i].Location))\n                        {\n                            if(bgo_type > 0)\n                            {\n                                if(Background[i].Type == npc.Special5 || Background[i].Type == 70 || Background[i].Type == 100)\n                                {\n                                    bgo_type = 0;\n                                    speed_x = 0;\n                                    speed_y = 0;\n                                    // npc = tempNPC;\n                                    npc.Location = oldLoc;\n                                }\n                            }\n\n                            if(bgo_type == 0)\n                            {\n                                // Vertical rail\n                                if(Background[i].Type == 72)\n                                {\n                                    if(npc.Location.SpeedY <= 0)\n                                        npc.Location.SpeedY = -speed;\n                                    else\n                                        npc.Location.SpeedY = speed;\n\n                                    npc.Location.SpeedX = 0;\n\n                                    if(centered)\n                                        speed_x = -(npc.Location.X + (npc.Location.Width / 2)) + (Background[i].Location.X + (Background[i].Location.Width / 2));\n                                    else\n                                        speed_x = -npc.Location.X + Background[i].Location.X - 32;\n                                }\n                                // Horizontal rail\n                                else if(Background[i].Type == 71)\n                                {\n                                    if(npc.Location.SpeedX >= 0)\n                                        npc.Location.SpeedX = speed;\n                                    else\n                                        npc.Location.SpeedX = -speed;\n\n                                    npc.Location.SpeedY = 0;\n\n                                    if(centered)\n                                        speed_y = -(npc.Location.Y + (npc.Location.Height / 2)) + (Background[i].Location.Y + (Background[i].Location.Height / 2));\n                                    else\n                                        speed_y = -npc.Location.Y + Background[i].Location.Y;\n                                }\n                                // Diagonal rail left-bottom, right-top\n                                else if(Background[i].Type == 73)\n                                {\n                                    if(npc.Location.SpeedY < 0)\n                                        npc.Location.SpeedX = speed;\n                                    else if(npc.Location.SpeedY > 0)\n                                        npc.Location.SpeedX = -speed;\n\n                                    if(npc.Location.SpeedX > 0)\n                                        npc.Location.SpeedY = -speed;\n                                    else if(npc.Location.SpeedX < 0)\n                                        npc.Location.SpeedY = speed;\n                                }\n                                // Diagonal rail left-top, right-bottom\n                                else if(Background[i].Type == 74)\n                                {\n                                    if(npc.Location.SpeedY < 0)\n                                        npc.Location.SpeedX = -speed;\n                                    else if(npc.Location.SpeedY > 0)\n                                        npc.Location.SpeedX = speed;\n\n                                    if(npc.Location.SpeedX > 0)\n                                        npc.Location.SpeedY = speed;\n                                    else if(npc.Location.SpeedX < 0)\n                                        npc.Location.SpeedY = -speed;\n                                }\n                                // Reverse buffer\n                                else if(Background[i].Type == 70 || Background[i].Type == 100)\n                                {\n                                    npc.Location.SpeedX = -npc.Location.SpeedX;\n                                    npc.Location.SpeedY = -npc.Location.SpeedY;\n                                    tempBool = true;\n                                    break;\n                                }\n\n                                tempBool = true;\n                                bgo_type = Background[i].Type;\n                            }\n                        }\n                    }//Not hidden\n                } // any important BGO?\n            } // for BGOs\n\n            // store current rail type in Special5\n            npc.Special5 = bgo_type;\n\n            if(!tempBool)\n            {\n                if(npc.Type == NPCID_PLATFORM_S3 && npc.Wet == 2)\n                    npc.Location.SpeedY -= Physics.NPCGravity / 4;\n                else\n                    npc.Location.SpeedY += Physics.NPCGravity;\n            }\n            else\n            {\n                npc.Location.SpeedX += speed_x;\n                npc.Location.SpeedY += speed_y;\n            }\n\n            // this code ran unconditionally in SMBX 1.3\n            // it does not run in modern mode so that the SpecialY / SpecialX keep the speed from before the pause\n            if(!g_config.fix_platforms_acceleration || !pausePlatforms)\n            {\n                npc.SpecialY = npc.Location.SpeedY;\n                npc.SpecialX = npc.Location.SpeedX;\n            }\n\n            // NEW: add a terminal velocity for the platforms in modern mode\n            if(npc.SpecialY > 40)\n            {\n                if(g_config.fix_platforms_acceleration)\n                    npc.SpecialY = 40;\n\n                // in either mode, try to \"cancel\" the NPC once it has fallen below everything\n                // fixes some serious memory exhaustion bugs\n                int below_section = (int)(npc.Location.Y - level[npc.Section].Height);\n\n                // kill 4096 below section in Vanilla, and 1024 below section in Classic / Modern\n                int kill_threshold = (!g_config.fix_platforms_acceleration) ? 4096 : 1024;\n\n                if(below_section >= kill_threshold)\n                {\n                    bool below_all = true;\n\n                    for(int B = 0; B < numSections; B++)\n                    {\n                        if(npc.Location.Y < level[B].Height)\n                        {\n                            if(npc.Location.X >= level[B].X && npc.Location.X + npc.Location.Width <= level[B].Width)\n                            {\n                                below_all = false;\n                                break;\n                            }\n                        }\n                    }\n\n                    if(below_all)\n                    {\n                        npc.Effect = NPCEFF_WAITING;\n                        npc.Effect2 = npc.TimeLeft + 1;\n                    }\n                }\n            }\n\n            // SMBX 1.3 logic: zero the actual speed of the platforms\n            if(pausePlatforms) // Or zero the speed and don't change special values\n            {\n                npc.Location.SpeedX = 0;\n                npc.Location.SpeedY = 0;\n            }\n        }\n        else\n        {\n            npc.Location.SpeedX = 0;\n            npc.Location.SpeedY = 0;\n        }\n\n        Block[npc.tempBlock].Location = npc.Location;\n        Block[npc.tempBlock].Location.X += npc.Location.SpeedX;\n\n        if(npc.Location.SpeedY < 0)\n            Block[npc.tempBlock].Location.Y += npc.Location.SpeedY;\n\n        // The tempBlock has been moved, above, and the NPC hasn't.\n        // So, the tempBlock goes out of sync with the NPC and joins the tree.\n        if(npc.tempBlock > 0 && (npc.Location.SpeedX != 0 || npc.Location.SpeedY < 0))\n            treeNPCUpdateTempBlock(&npc);\n\n        if(npc.Type == NPCID_SAW)\n        {\n            if(railAlgo != 2)\n            {\n                npc.Location.X += 24;\n                npc.Location.Width = 48;\n                npc.Location.Y -= 8;\n                npc.Location.Height = 48;\n\n                NPCQueues::Unchecked.push_back(A);\n                // deferring tree update to end of the NPC physics update\n            }\n\n            if(npc.Location.SpeedX == 0 && num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity))\n            {\n                npc.Location.SpeedX = speed * npc.Direction;\n                npc.SpecialX = npc.Location.SpeedX;\n            }\n        }\n    }\n    else if(npc.Type == NPCID_VILLAIN_S1) // King Koopa\n    {\n        // don't despawn -- moved from top of this routine\n        if(npc.TimeLeft > 1)\n            npc.TimeLeft = 100;\n\n        int target_plr = NPCFaceNearestPlayer(npc, true); // was D\n\n        npc.Special5 = target_plr;\n\n        if(iRand(300) >= 297 && npc.Special == 0)\n            npc.Special = 1;\n\n        npc.Special4 += 1;\n\n        if(npc.Inert)\n            npc.Special4 = 150;\n\n        if(npc.Special4 <= 80 + iRand(40))\n        {\n            if((iRand(100) >= 40) && npc.Special4 % 16 == 0)\n            {\n                PlaySoundSpatial(SFX_HeavyToss, npc.Location);\n                numNPCs++;\n                NPC[numNPCs].Inert = npc.Inert;\n                NPC[numNPCs].Location.Height = 32;\n                NPC[numNPCs].Location.Width = 32;\n                NPC[numNPCs].Location.X = npc.Location.X + npc.Location.Width / 2 - 16;\n                NPC[numNPCs].Location.Y = npc.Location.Y - 32;\n                NPC[numNPCs].Direction = npc.Direction;\n                NPC[numNPCs].Type = NPCID_HEAVY_THROWN;\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].TimeLeft = 50;\n                NPC[numNPCs].Layer = LAYER_SPAWNED_NPCS;\n                NPC[numNPCs].Location.SpeedY = -8;\n                NPC[numNPCs].Location.SpeedX = 3 * NPC[numNPCs].Direction;\n\n                syncLayers_NPC(numNPCs);\n            }\n        }\n        else if(npc.Special4 > 300 + iRand(50))\n            npc.Special4 = 0;\n\n        if(npc.Inert)\n            npc.Special = 0;\n\n        if(npc.Special > 0)\n        {\n            npc.Special3 += 1;\n            if(npc.Special3 < 40)\n                npc.Special = 1;\n            else if(npc.Special3 < 70)\n            {\n                if(npc.Special3 == 40)\n                {\n                    numNPCs++;\n                    NPC[numNPCs].Active = true;\n                    NPC[numNPCs].TimeLeft = 100;\n                    NPC[numNPCs].Direction = npc.Direction;\n                    NPC[numNPCs].Section = npc.Section;\n                    NPC[numNPCs].Layer = LAYER_SPAWNED_NPCS;\n                    NPC[numNPCs].Type = NPCID_VILLAIN_FIRE;\n                    if(NPC[numNPCs].Direction == 1)\n                        NPC[numNPCs].Frame = 4;\n                    NPC[numNPCs].Location.Height = 32;\n                    NPC[numNPCs].Location.Width = 48;\n                    if(NPC[numNPCs].Direction == -1)\n                        NPC[numNPCs].Location.X = npc.Location.X - 40;\n                    else\n                        NPC[numNPCs].Location.X = npc.Location.X + 54;\n                    NPC[numNPCs].Location.Y = npc.Location.Y + 19;\n\n                    NPCSetSpeedTarget_FixedX(NPC[numNPCs], Player[npc.Special5].Location, 4, 1);\n\n                    PlaySoundSpatial(SFX_BigFireball, npc.Location);\n\n                    syncLayers_NPC(numNPCs);\n                }\n                npc.Special = 2;\n            }\n            else\n            {\n                npc.Special = 0;\n                npc.Special3 = 0;\n            }\n        }\n        else if(npc.Special == 0)\n        {\n            if(npc.Special2 == 0)\n            {\n                npc.Location.SpeedX = -0.5_n;\n                if(npc.Location.X < npc.DefaultLocationX - npc.Location.Width * 1.5_rb)\n                    npc.Special2 = 1;\n            }\n            else\n            {\n                npc.Location.SpeedX = 0.5_n;\n                if(npc.Location.X > npc.DefaultLocationX + npc.Location.Width * 1.5_rb)\n                    npc.Special2 = 0;\n            }\n\n            if(num_t::fEqual_d(npc.Location.SpeedY, Physics.NPCGravity) || npc.Slope > 0)\n            {\n                if(iRand(100) == 0)\n                    npc.Location.SpeedY = -8;\n            }\n        }\n    }\n    else if(npc.Type == NPCID_GOALTAPE || npc.Type == NPCID_FLAG_EXIT) // SMW Exit\n    {\n        // Special is whether the tape is going up or down\n        // SpecialY is the Y coordinate of the ground below the tape (was previously Special2)\n\n        if(npc.Type == NPCID_FLAG_EXIT)\n        {\n            if(LevelMacro == LEVELMACRO_FLAG_EXIT)\n            {\n                if(npc.Special)\n                    npc.Location.SpeedY = 0;\n                else\n                    npc.Location.SpeedY = 2;\n\n                return;\n            }\n        }\n        else if(npc.Special == 0)\n            npc.Location.SpeedY = 2;\n        else\n            npc.Location.SpeedY = -2;\n\n        if(npc.Location.Y <= npc.DefaultLocationY)\n            npc.Special = 0;\n\n        if(npc.SpecialY == 0)\n        {\n            Location_t tempLocation = npc.Location;\n            tempLocation.Height = 8000;\n            int C = 0;\n            for(int i : treeBlockQuery(tempLocation, SORTMODE_COMPAT))\n            {\n                if(CheckCollision(tempLocation, Block[i].Location))\n                {\n                    if(C == 0)\n                        C = i;\n                    else\n                    {\n                        if(Block[i].Location.Y < Block[C].Location.Y)\n                            C = i;\n                    }\n                }\n            }\n            if(C > 0)\n                npc.SpecialY = Block[C].Location.Y + 4;\n        }\n\n        num_t left_boundary = npc.Location.X;\n        if(npc.Type == NPCID_GOALTAPE)\n            left_boundary += npc.Location.Width - 8;\n\n        for(int i = 1; i <= numPlayers; i++)\n        {\n            auto &p = Player[i];\n\n            if(p.Section == npc.Section)\n            {\n                if(p.Location.Y + npc.Location.Height <= npc.SpecialY)\n                {\n                    if(p.Location.X + p.Location.Width >= left_boundary)\n                    {\n                        if(p.Location.X <= npc.Location.X + 80)\n                        {\n                            if(npc.Type == NPCID_FLAG_EXIT)\n                            {\n                                // have some stricter checks for this exit\n                                if(p.Dead || p.TimeToLive || p.Effect != PLREFF_NORMAL || p.CurMazeZone)\n                                    continue;\n\n                                // give tapes for flag exit\n                                int score = 10;\n\n                                int gap = num_t::floor(p.Location.Y) - num_t::floor(npc.Location.Y);\n                                if(gap > 0)\n                                    score -= (gap + 31) / 32;\n\n                                if(score < 1)\n                                    score = 1;\n\n                                MoreScore(score, npc.Location);\n                            }\n                            // give points for goal tape if touched\n                            else if(CheckCollision(p.Location, npc.Location))\n                            {\n                                MoreScore(num_t::vb6round((1 - (npc.Location.Y - npc.DefaultLocationY).divided_by(npc.SpecialY - npc.DefaultLocationY)) * 10) + 1, npc.Location);\n                                npc.Killed = 9;\n                                NPCQueues::Killed.push_back(A);\n                                PlaySoundSpatial(SFX_Stone, npc.Location);\n                            }\n\n                            FreezeNPCs = false;\n                            TurnNPCsIntoCoins();\n\n                            if(g_ClonedPlayerMode)\n                                Player[1] = Player[A];\n\n                            for(int j = 1; j <= numPlayers; j++)\n                            {\n                                if(i == j) // And DScreenType <> 5 Then\n                                    continue;\n                                Player[j].Section = p.Section;\n                                // yes, it's a VB6 bug\n                                Player[j].Location.Y = p.Location.Y /* + p.Location.Height - p.Location.Height*/;\n                                Player[j].Location.X = p.Location.X /* + (p.Location.Width - p.Location.Width) / 2*/;\n                                Player[j].Location.SpeedX = 0;\n                                Player[j].Location.SpeedY = 0;\n                                Player[j].Effect = PLREFF_WAITING;\n                                Player[j].Effect2 = -i;\n                            }\n\n                            StopMusic();\n\n                            if(npc.Type == NPCID_FLAG_EXIT)\n                            {\n                                LevelMacro = LEVELMACRO_FLAG_EXIT;\n                                // go towards the ground\n                                npc.Location.Y += 2;\n                                // wait for player to reach ground\n                                LevelMacroWhich = -16;\n                                PlaySound(SFX_FlagExit);\n                            }\n                            else\n                            {\n                                LevelMacro = LEVELMACRO_GOAL_TAPE_EXIT;\n                                PlaySound(SFX_TapeExit);\n                            }\n\n                            // XEvents::doEvents();\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n    }\n    else if(npc.Type == NPCID_CHECKER_PLATFORM)\n    {\n        if(npc.Special == 1)\n        {\n            npc.Location.SpeedY += Physics.NPCGravity / 4;\n            npc.Special = 0;\n        }\n        else\n            npc.Location.SpeedY = 0;\n    }\n    else if(npc.Type == NPCID_PLATFORM_S1)\n        npc.Location.SpeedY = npc.Direction * 2;\n\n    else if(npc.Type == NPCID_LAVA_MONSTER)\n    {\n        if(npc.Special == 0)\n        {\n            Location_t tempLocation = npc.Location;\n            tempLocation.Height = 400;\n            tempLocation.Y -= tempLocation.Height;\n            tempLocation.set_width_center(600);\n\n            for(int i = 1; i <= numPlayers; i++)\n            {\n                if(CheckCollision(tempLocation, Player[i].Location))\n                {\n                    if(npc.Location.to_right_of(Player[i].Location))\n                        npc.Direction = -1;\n                    else\n                        npc.Direction = 1;\n\n                    npc.Special = 1;\n                    NewEffect(EFFID_LAVA_MONSTER_LOOK, npc.Location, npc.Direction);\n                    break;\n                }\n            }\n        }\n        else if(npc.Special == 1)\n        {\n            npc.Special2 += 1;\n            if(npc.Special2 == 90)\n            {\n                npc.Location.SpeedX = 1 * npc.Direction;\n                npc.Location.SpeedY = -4.2_n;\n                npc.Special = 2;\n                PlaySoundSpatial(SFX_LavaMonster, npc.Location);\n            }\n        }\n        else if(npc.Special == 2)\n        {\n            npc.Location.SpeedY += Physics.NPCGravity * 0.4_r;\n            if(npc.Location.Y > npc.DefaultLocationY + npc.Location.Height + 48)\n                Deactivate(A);\n        }\n\n\n        // End If\n        // Sniffits\n    }\n    else if(npc.Type >= NPCID_RED_SPIT_GUY && npc.Type <= NPCID_GRY_SPIT_GUY)\n    {\n        if(npc.Projectile)\n        {\n            npc.Special = 0;\n            npc.Special2 = 0;\n        }\n\n        tempBool = false;\n\n        if(npc.Type < NPCID_GRY_SPIT_GUY)\n        {\n            npc.Special += 1;\n            if(npc.Special > 120)\n            {\n                npc.FrameCount -= 1;\n\n                if(npc.Special2 == 0)\n                {\n                    npc.Special2 = 1;\n                    npc.Location.X -= 2;\n                }\n                else\n                {\n                    npc.Special2 = 0;\n                    npc.Location.X += 2;\n                }\n\n                npc.Location.SpeedX = 0;\n\n                if(npc.Special >= 150)\n                {\n                    tempBool = true;\n                    npc.Special = 0;\n                }\n            }\n        }\n        else\n        {\n            npc.Special += 1;\n\n            if(npc.Special > 160)\n            {\n                tempBool = true;\n\n                if(npc.Special3 != 2)\n                {\n                    npc.Special3 -= 1;\n                    npc.Special = 0;\n                }\n                else\n                {\n                    npc.Special = 140;\n                    npc.Special3 -= 1;\n                    if(num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity) || npc.Slope > 0)\n                        npc.Special2 = 90;\n                }\n\n                if(npc.Special3 < 0)\n                    npc.Special3 = 2;\n            }\n\n            if(num_t::fEqual_f(npc.Location.SpeedY, Physics.NPCGravity) || npc.Slope > 0)\n            {\n                npc.Special2 += 1;\n                if(npc.Special2 >= 100)\n                {\n                    npc.Special2 = 0;\n                    npc.Location.SpeedY = -3.9_n;\n                    npc.Location.Y -= Physics.NPCGravity;\n                    // deferring tree update to end of the NPC physics update\n                }\n            }\n            else\n                npc.FrameCount -= 1;\n        }\n\n        if(tempBool)\n        {\n            numNPCs++;\n            NPC[numNPCs].Active = true;\n            NPC[numNPCs].Section = npc.Section;\n            NPC[numNPCs].TimeLeft = 100;\n            NPC[numNPCs].Type = NPCID_SPIT_GUY_BALL;\n            NPC[numNPCs].Layer = npc.Layer;\n            NPC[numNPCs].Inert = npc.Inert;\n            NPC[numNPCs].Direction = npc.Direction;\n            NPC[numNPCs].Location.SpeedX = 4 * NPC[numNPCs].Direction;\n            NPC[numNPCs].Location.Width = 16;\n            NPC[numNPCs].Location.Height = 16;\n            NPC[numNPCs].Location.X = npc.Location.X + 8 + 16 * NPC[numNPCs].Direction;\n            NPC[numNPCs].Location.Y = npc.Location.Y + 13;\n            syncLayers_NPC(numNPCs);\n        }\n    }\n    // code moved from pre-NPCSpecial call\n    else if(npc.Type == NPCID_SPIT_BOSS_BALL)\n    {\n        if(!npc.Projectile)\n        {\n            npc.Location.SpeedY = 0; // egg code\n            if(npc.Location.SpeedX == 0)\n                npc.Projectile = true;\n        }\n    }\n    else if(npc.Type == NPCID_SLIDE_BLOCK || npc.Type == NPCID_FALL_BLOCK_RED || npc.Type == NPCID_FALL_BLOCK_BROWN)\n    {\n        if(npc.Special == 0)\n            npc.Location.SpeedY = 0;\n    }\n    else if(npc.Type == NPCID_TOOTHY)\n    {\n        npc.Location.SpeedX = 0;\n        npc.Location.SpeedY = 0;\n    }\n    // code moved from post-NPCSpecial call\n    else if(npc.Type == NPCID_TANK_TREADS)\n    {\n        if(!AllPlayersNormal())\n            npc.Location.SpeedX = 0;\n    }\n    else if(npc.Type == NPCID_ICE_CUBE)\n    {\n        if(npc.Projectile || npc.Wet > 0 || npc.HoldingPlayer > 0)\n            npc.Special3 = 0;\n        else if(npc.Special3 == 1)\n        {\n            npc.Location.SpeedX = 0;\n            npc.Location.SpeedY = 0;\n        }\n\n        // this part was from SpecialNPC\n        if(npc.Special == 3)\n            npc.BeltSpeed = 0;\n    }\n    else if(npc.Type == NPCID_ITEM_POD)\n    {\n        if(npc.Special2 == 1)\n        {\n            // this part was from the below section\n            npc.Location.SpeedX = 0;\n            npc.Location.SpeedY = 0;\n\n            // this part was from SpecialNPC\n            npc.Killed = 1;\n            NPCQueues::Killed.push_back(A);\n        }\n    }\n    else if(npc.Type == NPCID_SIGN || npc.Type == NPCID_LIFT_SAND)\n    {\n        npc.Location.SpeedX = 0;\n        npc.Location.SpeedY = 0;\n    }\n    else if(npc.Type == NPCID_ROCKET_WOOD || npc.Type == NPCID_3_LIFE)\n        npc.Location.SpeedY = 0;\n    else if(npc.Type == NPCID_CHECKPOINT)\n    {\n        npc.Projectile = false;\n        npc.Location.SpeedX = 0;\n        npc.Location.SpeedY = 0;\n    }\n    else if(npc.Type == NPCID_CYCLONE_POWER)\n    {\n        npc.Special += 1;\n\n        if(npc.Special == 96 || npc.Special >= 256)\n            npc.Special = 128;\n\n        npc.Location.SpeedX /= 2;\n\n        if(npc.Stuck)\n        {\n            // do nothing!\n        }\n        // accelerate upwards for 80 frames\n        else if(npc.Special < 80)\n        {\n            if(npc.Special < 32)\n                npc.Special2 += (32 - npc.Special);\n            else if(npc.Special >= 62)\n                npc.Special2 -= (84 - npc.Special) * 2;\n\n            npc.Location.SpeedY = -0.004_n * npc.Special2;\n            npc.Location.SpeedX = 0.003_n * (npc.Special2 * npc.Direction);\n        }\n        // pause for a few frames\n        else if(npc.Special < 88)\n        {\n            npc.Location.SpeedX = 0;\n\n            if(npc.Location.SpeedY < 0_n)\n                npc.Location.SpeedY += 0.05_n;\n        }\n        // ease into movement\n        else if(npc.Special < 96)\n        {\n            npc.Location.SpeedX = Physics.NPCMushroomSpeed * npc.Direction * (npc.Special - 80) / 32;\n            npc.Location.SpeedY += 0.05_n;\n        }\n        // bob movement pattern\n        else if(npc.Special < 192)\n        {\n            if(npc.Location.SpeedY < 1.5_n)\n                npc.Location.SpeedY += 0.1_n;\n        }\n        else\n        {\n            if(npc.Location.SpeedY > -1.5_n)\n                npc.Location.SpeedY -= 0.1_n;\n        }\n    }\n    else if(npc.Type == NPCID_RAFT) // Skull raft\n    {\n        if(!AllPlayersNormal())\n        {\n            npc.Location.SpeedX = 0;\n            npc.Location.SpeedY = 0;\n        }\n\n        // the following is all new code!\n\n        if((npc.Special == 2 || npc.Special == 3) && (npc.SpecialX != 0))\n        {\n            npc.Location.X = npc.SpecialX; // Finish alignment\n            npc.SpecialX = 0;\n        }\n\n        if(npc.Special == 3) // Watch for wall collisions. If one got dissappear (hidden layer, toggled switch), resume a ride\n        {\n            auto loc = npc.Location;\n            loc.X += 1 * npc.Direction;\n            loc.SpeedX += 2 * npc.Direction;\n\n            // int64_t fBlock;// = FirstBlock[static_cast<int>(floor(static_cast<double>(loc.X / 32))) - 1];\n            // int64_t lBlock;// = LastBlock[floor((loc.X + loc.Width) / 32.0) + 1];\n            // blockTileGet(loc, fBlock, lBlock);\n            bool stillCollide = false;\n\n            for(BlockRef_t block : treeBlockQuery(loc, SORTMODE_NONE))\n            {\n                int B = block;\n                if(!CheckCollision(loc, Block[B].Location))\n                    continue;\n\n                if(npc.tempBlock == B || Block[B].tempBlockNoProjClipping() ||\n                   BlockOnlyHitspot1[Block[B].Type] || BlockIsSizable[Block[B].Type] ||\n                   BlockNoClipping[Block[B].Type] || Block[B].Hidden)\n                {\n                    continue;\n                }\n\n                int hs = NPCFindCollision(loc, Block[B].Location);\n                if(Block[B].tempBlockNpcType > 0)\n                    hs = 0;\n                if(hs == 2 || hs == 4)\n                    stillCollide = true;\n            }\n\n            if((!npcHasFloor(npc) && NPC[A].Special2 == 0) || !stillCollide)\n            {\n                npc.Special = 2;\n                SkullRide(A, true);\n            }\n        }\n    }\n}\n\nvoid SpecialNPC(int A)\n{\n    // int B = 0;\n    // float C = 0;\n    // float D = 0;\n    // float E = 0;\n    // float F = 0;\n    // bool tempTurn = false;\n    // Location_t tempLocation;\n    // Location_t tempLocation2;\n    // NPC_t tempNPC;\n\n    if(NPC[A].Type == NPCID_VILLAIN_FIRE || NPC[A].Type == NPCID_QUAD_BALL || NPC[A].Type == NPCID_STATUE_FIRE ||\n       NPC[A].Type == NPCID_SPIT_GUY_BALL || NPC[A].Type == NPCID_PLANT_FIREBALL || NPC[A].Type == NPCID_HEAVY_THROWN ||\n       NPC[A].Type == NPCID_SICK_BOSS_BALL || NPC[A].Type == NPCID_HOMING_BALL ||\n       NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_HEAVY || NPC[A].Type == NPCID_PLR_ICEBALL) // Link shield block\n    {\n        if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_HEAVY || NPC[A].Type == NPCID_PLR_ICEBALL)\n        {\n            // Projectile check, previously set immediately before calling SpecialNPC\n            NPC[A].Projectile = true;\n\n            if(!BattleMode)\n                return;\n        }\n\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            if(Player[B].Character == 5 && !Player[B].Dead && Player[B].TimeToLive == 0 &&\n               Player[B].Effect == PLREFF_NORMAL && Player[B].SwordPoke == 0 && !Player[B].Fairy &&\n               !(NPC[A].Type == NPCID_PLR_FIREBALL && NPC[A].CantHurtPlayer == B) &&\n               !(NPC[A].Type == NPCID_PLR_HEAVY && NPC[A].CantHurtPlayer == B))\n            {\n                Location_t tempLocation;\n                if(!Player[B].Duck)\n                    tempLocation.Y = Player[B].Location.Y + Player[B].Location.Height - 52;\n                else\n                    tempLocation.Y = Player[B].Location.Y + Player[B].Location.Height - 28;\n                tempLocation.Height = 24;\n                tempLocation.Width = 6;\n                if(Player[B].Direction == 1)\n                    tempLocation.X = Player[B].Location.X + Player[B].Location.Width - 2;\n                else\n                    tempLocation.X = Player[B].Location.X - tempLocation.Width + 4;\n                if(CheckCollision(NPC[A].Location, tempLocation))\n                {\n#if XTECH_ENABLE_WEIRD_GFX_UPDATES\n                    UpdateGraphics(true);\n#endif\n                    PlaySoundSpatial(SFX_HeroShield, NPC[A].Location);\n                    if(NPC[A].Type == NPCID_SPIT_GUY_BALL)\n                    {\n                        NPC[A].Killed = 3;\n                        NPCQueues::Killed.push_back(A);\n                    }\n                    else\n                    {\n                        NPC[A].Killed = 9;\n                        NPCQueues::Killed.push_back(A);\n\n                        if(NPC[A].Type == NPCID_PLR_FIREBALL || NPC[A].Type == NPCID_PLR_ICEBALL)\n                            NPC[A].Killed = 3;\n\n                        if(NPC[A].Type != NPCID_HEAVY_THROWN && NPC[A].Type != NPCID_SICK_BOSS_BALL && NPC[A].Type != NPCID_HOMING_BALL &&\n                           NPC[A].Type != NPCID_PLR_HEAVY && NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_PLR_ICEBALL)\n                        {\n                            for(int Ci = 1; Ci <= 10; Ci++)\n                            {\n                                NewEffect(EFFID_PLR_FIREBALL_TRAIL, NPC[A].Location, NPC[A].Special);\n                                Effect[numEffects].Location.SpeedX = dRand() * 3 - 1.5_n + NPC[A].Location.SpeedX / 10;\n                                Effect[numEffects].Location.SpeedY = dRand() * 3 - 1.5_n - NPC[A].Location.SpeedY / 10;\n\n                                if(Effect[numEffects].Frame == 0)\n                                    Effect[numEffects].Frame = -iRand(3);\n                                else\n                                    Effect[numEffects].Frame = 5 + iRand(3);\n                            }\n                        }\n\n                        if(NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_PLR_ICEBALL)\n                        {\n                            NPC[A].Location.X += (NPC[A].Location.Width - EffectWidth[EFFID_SMOKE_S3]) / 2;\n                            NPC[A].Location.Y += (NPC[A].Location.Height - EffectHeight[EFFID_SMOKE_S3]) / 2;\n                            NewEffect(EFFID_SMOKE_S3, NPC[A].Location);\n                            // deferring tree update to end of the NPC physics update\n                        }\n                    }\n                }\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_CHAR4_HEAVY) // Toad Boomerang\n    {\n        // Projectile check, previously set immediately before calling SpecialNPC\n        NPC[A].Projectile = true;\n\n        // Special5 is player that threw NPC\n        // Special4 is the direction that the player was facing when throwing (Special6 in SMBX 1.3)\n\n        num_t npcHCenter = NPC[A].Location.X + NPC[A].Location.Width / 2;\n        num_t npcVCenter = NPC[A].Location.Y + NPC[A].Location.Height / 2;\n        num_t playerHCenter = Player[NPC[A].Special5].Location.X + Player[NPC[A].Special5].Location.Width / 2;\n        num_t playerVCenter = Player[NPC[A].Special5].Location.Y + Player[NPC[A].Special5].Location.Height / 2;\n\n        if(NPC[A].CantHurt > 0)\n            NPC[A].CantHurt = 100;\n        if(NPC[A].Location.SpeedY > 8)\n            NPC[A].Location.SpeedY = 8;\n        if(NPC[A].Location.SpeedY < -8)\n            NPC[A].Location.SpeedY = -8;\n        if(NPC[A].Location.SpeedX > 12 + Player[NPC[A].Special5].Location.SpeedX)\n            NPC[A].Location.SpeedX = 12 + Player[NPC[A].Special5].Location.SpeedX;\n        if(NPC[A].Location.SpeedX < -12 + Player[NPC[A].Special5].Location.SpeedX)\n            NPC[A].Location.SpeedX = -12 + Player[NPC[A].Special5].Location.SpeedX;\n\n        if(npcHCenter > playerHCenter)\n        {\n            NPC[A].Location.SpeedX -= 0.2_n;\n            if(NPC[A].Location.SpeedX > -4 && NPC[A].Location.SpeedX < 4)\n                NPC[A].Location.SpeedX -= 0.5_n;\n\n        }\n        else if(npcHCenter < playerHCenter)\n        {\n            NPC[A].Location.SpeedX += 0.2_n;\n            if(NPC[A].Location.SpeedX > -4 && NPC[A].Location.SpeedX < 4)\n                NPC[A].Location.SpeedX += 0.5_n;\n        }\n\n        NPC[A].Location.SpeedX += (playerHCenter - npcHCenter) / 2000;\n\n        if(NPC[A].Location.Y + NPC[A].Location.Height / 2 > playerVCenter)\n        {\n            NPC[A].Location.SpeedY -= 0.2_n;\n            if(NPC[A].Location.SpeedY > 0 && NPC[A].Direction != NPC[A].Special4)\n                NPC[A].Location.SpeedY += -num_t::abs(NPC[A].Location.SpeedY) / 25;\n        }\n        else if(NPC[A].Location.Y + NPC[A].Location.Height / 2 < playerVCenter)\n        {\n            NPC[A].Location.SpeedY += 0.2_n;\n            if(NPC[A].Location.SpeedY < 0 && NPC[A].Direction != NPC[A].Special4)\n                NPC[A].Location.SpeedY += num_t::abs(NPC[A].Location.SpeedY) / 25;\n        }\n        NPC[A].Location.SpeedY += (playerVCenter - NPC[A].Location.Y + NPC[A].Location.Height / 2) / 250;\n\n\n        bool picked_up = false;\n        bool atCenter = (npcHCenter > playerHCenter && NPC[A].Special4 == 1) ||\n                        (npcHCenter < playerHCenter && NPC[A].Special4 == -1);\n        if(!atCenter)\n        {\n            NPC[A].Special2 = 1;\n            if(CheckCollision(NPC[A].Location, Player[NPC[A].Special5].Location))\n            {\n                NPC[A].Killed = 9;\n                NPCQueues::Killed.push_back(A);\n                Player[NPC[A].Special5].FrameCount = 115;\n                PlaySoundSpatial(SFX_Grab2, NPC[A].Location);\n                picked_up = true;\n            }\n        }\n\n        // look for coins, and pick them up if needed\n        for(int B : treeNPCQuery(NPC[A].Location, (picked_up) ? SORTMODE_ID : SORTMODE_NONE))\n        {\n            if(NPC[B].Active)\n            {\n                if(NPC[B]->IsACoin)\n                {\n                    if(CheckCollision(NPC[A].Location, NPC[B].Location))\n                    {\n                        NPC[B].Special = 0;\n                        NPC[B].Projectile = false;\n\n                        if(picked_up)\n                        {\n                            NPC[B].Location.X = playerHCenter - NPC[B].Location.Width / 2;\n                            NPC[B].Location.Y = playerVCenter - NPC[B].Location.Height / 2;\n                            TouchBonus(NPC[A].Special5, B);\n                        }\n                        else\n                        {\n                            NPC[B].Location.X = npcHCenter - NPC[B].Location.Width / 2;\n                            NPC[B].Location.Y = npcVCenter - NPC[B].Location.Height / 2;\n                        }\n\n                        // must update now because B won't be checked later\n                        treeNPCUpdate(B);\n                        if(NPC[B].tempBlock > 0)\n                            treeNPCSplitTempBlock(B);\n                    }\n                }\n            }\n        }\n\n        if(NPC[A].Special2 == 1)\n        {\n            if(NPC[A].Location.X + NPC[A].Location.Width / 2 > playerHCenter)\n            {\n                if(NPC[A].Location.SpeedX > 0)\n                    NPC[A].Location.SpeedX -= 0.1_n;\n            }\n            else if(NPC[A].Location.X + NPC[A].Location.Width / 2 < playerHCenter)\n            {\n                if(NPC[A].Location.SpeedX < 0)\n                    NPC[A].Location.SpeedX += 0.1_n;\n            }\n\n            if(NPC[A].Location.Y + NPC[A].Location.Height / 2 > playerVCenter)\n            {\n                if(NPC[A].Location.SpeedY > 0)\n                    NPC[A].Location.SpeedY -= 0.3_n;\n            }\n            else if(NPC[A].Location.Y + NPC[A].Location.Height / 2 < playerVCenter)\n            {\n                if(NPC[A].Location.SpeedY < 0)\n                    NPC[A].Location.SpeedY += 0.1_n;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_ITEM_POD) // yoshi egg\n    {\n        if(NPC[A].Location.SpeedY > 2)\n            NPC[A].Projectile = true;\n\n        // moved to NPCSpecial\n        // if(NPC[A].Special2 == 1)\n        // {\n        //     NPC[A].Killed = 1;\n        //     NPCQueues::Killed.push_back(A);\n        // }\n    }\n    else if(NPC[A].Type == NPCID_GEM_1 || NPC[A].Type == NPCID_GEM_5 || NPC[A].Type == NPCID_GEM_20) // Rupee\n    {\n        if(NPC[A].Location.SpeedX < -0.02_n)\n            NPC[A].Location.SpeedX += 0.02_n;\n        else if(NPC[A].Location.SpeedX > 0.02_n)\n            NPC[A].Location.SpeedX -= 0.02_n;\n        else\n            NPC[A].Location.SpeedX = 0;\n    }\n    else if(NPC[A].Type == NPCID_ICE_BLOCK || NPC[A].Type == NPCID_ICE_CUBE || NPC[A].Type == NPCID_INVINCIBILITY_POWER) // Yoshi Ice\n    {\n        // moved to NPCSpecial\n        // if(NPC[A].Type == NPCID_ICE_CUBE && NPC[A].Special == 3)\n        //     NPC[A].BeltSpeed = 0;\n\n        Location_t tempLocation;\n\n        // two chances to make a sparkle: 7 frames out of 100 when stationary, and 1 frame out of 5 when thrown\n        if(iRand(100) >= 93)\n            NewEffect_IceSparkle(NPC[A], tempLocation);\n\n        if(NPC[A].Projectile && iRand(5) == 0)\n            NewEffect_IceSparkle(NPC[A], tempLocation);\n    }\n    else if(NPC[A].Type == NPCID_SQUID_S3 || NPC[A].Type == NPCID_SQUID_S1) // Blooper\n    {\n        if(NPC[A].Wet == 2 && NPC[A].Quicksand == 0)\n        {\n            if(NPC[A].Special == 0)\n            {\n                int target_plr = NPCTargetPlayer(NPC[A]);\n\n                if(target_plr == 0)\n                    target_plr = 1;\n\n                NPC[A].Special = target_plr;\n            }\n\n            if(NPC[A].Special2 == 0 || NPC[A].Special4 == 1)\n            {\n                if(NPC[A].Location.Y + NPC[A].Location.Height >= Player[NPC[A].Special].Location.Y - 24 || NPC[A].Special4 == 1)\n                {\n                    NPC[A].Special2 = 60;\n                    if(Player[NPC[A].Special].Location.to_right_of(NPC[A].Location))\n                        NPC[A].Location.SpeedX = 4;\n                    else\n                        NPC[A].Location.SpeedX = -4;\n                    if(NPC[A].Special4 == 1)\n                    {\n                        NPC[A].Special4 = 0;\n                        NPC[A].Location.Y -= 0.1_n;\n                    }\n                }\n            }\n\n            if(NPC[A].Special2 > 0)\n            {\n                NPC[A].Special2 -= 1;\n                NPC[A].Location.SpeedY = -1.75_n;\n                NPC[A].Frame = 0;\n            }\n            else\n            {\n                NPC[A].Location.SpeedY = 1;\n                NPC[A].Frame = 1;\n            }\n\n            if(NPC[A].Special2 == 0)\n                NPC[A].Special2 = -20;\n\n            if(NPC[A].Special2 < 0)\n                NPC[A].Special2 += 1;\n\n            if(NPC[A].Location.SpeedY >= 0)\n                NPC[A].Location.SpeedX = 0;\n        }\n        else\n        {\n            NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.7_r;\n            if(NPC[A].Location.SpeedY < -1)\n                NPC[A].Location.SpeedY = -1;\n            NPC[A].Special2 = -60;\n        }\n\n        if(Player[NPC[A].Special].Dead || Player[NPC[A].Special].Section != NPC[A].Section)\n            NPC[A].Special = 0;\n    }\n    else if(NPC[A].Type == NPCID_DOOR_MAKER)\n    {\n        if(NPC[A].Special3 == 1)\n        {\n            Location_t tempLocation = NPC[A].Location;\n            tempLocation.Y -= 32;\n            NewEffect(EFFID_SMOKE_S2, NPC[A].Location);\n            NewEffect(EFFID_SMOKE_S2, tempLocation);\n            NPC[A].Frame = 0;\n            NPC[A].Location.SpeedX = 0;\n            NPC[A].Location.SpeedY = 0;\n            NPC[A].Special3 = 0;\n            NPC[A].Effect = NPCEFF_WAITING;\n            NPC[A].Effect2 = 16;\n            NPC[A].Projectile = false;\n            NPC[A].Type = NPCID_MAGIC_DOOR;\n            NPC[A].Wings = WING_NONE;\n            PlaySoundSpatial(SFX_SpitBossBeat, NPC[A].Location);\n        }\n    }\n    // jumping plant\n    else if(NPC[A].Type == NPCID_JUMP_PLANT)\n    {\n        if(NPC[A].Projectile)\n        {\n            NPC[A].Location.SpeedY += Physics.NPCGravity;\n            NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.98_r;\n        }\n        else\n        {\n            if(NPC[A].Special == 0) // hiding\n            {\n                NPC[A].Location.Y = NPC[A].DefaultLocationY + NPC[A]->THeight + 1.5_n;\n                NPC[A].Location.Height = 0;\n                NPC[A].Special2 -= 1;\n\n                if(NPC[A].Special2 <= -30)\n                {\n                    bool tempTurn = true;\n\n                    if(!NPC[A].Inert)\n                    {\n                        for(int B = 1; B <= numPlayers; B++)\n                        {\n                            if(!Player[B].Dead && Player[B].TimeToLive == 0)\n                            {\n                                if(!CanComeOut(NPC[A].Location, Player[B].Location))\n                                {\n                                    tempTurn = false;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n\n                    if(tempTurn)\n                    {\n                        NPC[A].Special = 1;\n                        NPC[A].Special2 = 0;\n                    }\n                    else\n                        NPC[A].Special2 = 1000;\n                }\n            }\n            else if(NPC[A].Special == 1) // jumping\n            {\n                NPC[A].Location.Height = NPC[A]->THeight;\n\n                if(NPC[A].Special2 == 0)\n                    NPC[A].Location.SpeedY = -6;\n                else if(NPC[A].Location.SpeedY < -4)\n                    NPC[A].Location.SpeedY += 0.2_n;\n                else if(NPC[A].Location.SpeedY < -3)\n                    NPC[A].Location.SpeedY += 0.15_n;\n                else if(NPC[A].Location.SpeedY < -2)\n                    NPC[A].Location.SpeedY += 0.1_n;\n                else if(NPC[A].Location.SpeedY < -1)\n                    NPC[A].Location.SpeedY += 0.05_n;\n                else\n                    NPC[A].Location.SpeedY += 0.02_n;\n\n                NPC[A].Special2 += 1;\n\n                if(NPC[A].Location.SpeedY >= 0)\n                {\n                    NPC[A].Special = 2;\n                    NPC[A].Special2 = 0;\n                }\n\n            }\n            else if(NPC[A].Special == 2) // falling\n            {\n                NPC[A].Location.Height = NPC[A]->THeight;\n\n                NPC[A].Location.SpeedY += 0.01_n;\n                if(NPC[A].Location.SpeedY >= 0.75_n)\n                    NPC[A].Location.SpeedY = 0.75_n;\n\n                if(NPC[A].Location.Y + NPC[A]->THeight >= NPC[A].DefaultLocationY + NPC[A]->THeight)\n                {\n                    NPC[A].Location.Height = (NPC[A].DefaultLocationY + NPC[A]->THeight) - (NPC[A].Location.Y);\n                    if(NPC[A].Location.Y >= NPC[A].DefaultLocationY + NPC[A]->THeight)\n                    {\n                        NPC[A].Location.Height = 0;\n                        NPC[A].Location.Y = NPC[A].DefaultLocationY + NPC[A]->THeight;\n                        NPC[A].Special = 0;\n                        NPC[A].Special2 = 60;\n                    }\n                }\n            }\n\n            if(NPC[A].Location.Height < 0)\n                NPC[A].Location.Height = 0;\n            // deferring tree update to end of the NPC physics update\n\n            if(NPC[A].Location.Height == 0)\n                NPC[A].Immune = 100;\n            else\n                NPC[A].Immune = 0;\n        }\n    }\n    // Piranha Plant code\n    else if(NPC[A].Type == NPCID_PLANT_S3 || NPC[A].Type == NPCID_BIG_PLANT || NPC[A].Type == NPCID_PLANT_S1\n        || NPC[A].Type == NPCID_LONG_PLANT_UP || NPC[A].Type == NPCID_FIRE_PLANT)\n    {\n        if(NPC[A].Special3 > 0)\n            NPC[A].Special3 -= 1;\n\n        if(NPC[A].Type == NPCID_FIRE_PLANT)\n        {\n            int target_plr = NPCFaceNearestPlayer(NPC[A]);\n\n            if(target_plr != 0)\n                NPC[A].Special4 = target_plr;\n        }\n\n        if(NPC[A].Location.X != NPC[A].DefaultLocationX)\n        {\n            NPC[A].Killed = 2;\n            NPCQueues::Killed.push_back(A);\n\n            NPC[A].Location.Y += -NPC[A].Location.SpeedY;\n            // deferring tree update to end of the NPC physics update\n        }\n        else\n        {\n            if(NPC[A].Special2 == 0 && !NPC[A].Inert)\n            {\n                NPC[A].Location.Y += NPC[A]->THeight + 1.5_n;\n                NPC[A].Special2 = 4;\n                NPC[A].Special = 70;\n            }\n\n            if(NPC[A].Special2 == 1)\n            {\n                NPC[A].Special += 1;\n                NPC[A].Location.Y -= 1.5_n;\n\n                // NPC[A].Special >= NPC[A]->THeight * 0.65 + 1\n                if(NPC[A].Special * 100 >= NPC[A]->THeight * 65 + 100)\n                {\n                    if(g_config.fix_plant_wobble)\n                    {\n                        NPC[A].Location.Y = num_t::round(NPC[A].Location.Y);\n                        NPC[A].Location.Height = NPC[A]->THeight;\n                    }\n\n                    NPC[A].Special2 = 2;\n                    NPC[A].Special = 0;\n                }\n            }\n            else if(NPC[A].Special2 == 2)\n            {\n                if(NPC[A].Type != NPCID_LONG_PLANT_UP)\n                    NPC[A].Special += 1;\n\n                vbint_t pause_length = (NPC[A].Type == NPCID_FIRE_PLANT) ? 100 : 50;\n                if(NPC[A].Special >= pause_length)\n                {\n                    NPC[A].Special2 = 3;\n                    NPC[A].Special = 0;\n                }\n                // fire plant shoots fire at middle of pause\n                else if(NPC[A].Type == NPCID_FIRE_PLANT && NPC[A].Special == 50)\n                {\n                    numNPCs++;\n                    NPC[numNPCs].Active = true;\n                    NPC[numNPCs].TimeLeft = 100;\n                    NPC[numNPCs].Direction = NPC[A].Direction;\n                    NPC[numNPCs].Section = NPC[A].Section;\n                    NPC[numNPCs].Type = NPCID_PLANT_FIREBALL;\n                    NPC[numNPCs].Frame = 1;\n                    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n\n                    if(NPC[numNPCs]->TWidth == 16)\n                    {\n                        NPC[numNPCs].Location.X = NPC[A].Location.X + 8;\n                        NPC[numNPCs].Location.Y = NPC[A].Location.Y + 8;\n                    }\n                    else // modified fireball\n                    {\n                        NPC[numNPCs].Location.X = NPC[A].Location.X;\n                        NPC[numNPCs].Location.Y = NPC[A].Location.Y;\n                    }\n\n                    NPCSetSpeedTarget_FixedX(NPC[numNPCs], Player[NPC[A].Special4].Location, 3, 2);\n\n                    NPC[numNPCs].Location.X += NPC[numNPCs].Location.SpeedX * 4;\n                    NPC[numNPCs].Location.Y += NPC[numNPCs].Location.SpeedY * 4;\n\n                    syncLayers_NPC(numNPCs);\n                }\n            }\n            else if(NPC[A].Special2 == 3)\n            {\n                NPC[A].Special += 1;\n                NPC[A].Location.Y += 1.5_n;\n\n                // NPC[A].Special >= NPC[A]->THeight * 0.65 + 1\n                if(NPC[A].Special * 100 >= NPC[A]->THeight * 65 + 100)\n                {\n                    NPC[A].Special2 = 4;\n                    if(NPC[A].Type == NPCID_LONG_PLANT_UP)\n                        NPC[A].Special = 0;\n                }\n            }\n            else if(NPC[A].Special2 == 4)\n            {\n                NPC[A].Special += 1;\n\n                vbint_t hidden_length = (NPC[A].Type == NPCID_FIRE_PLANT) ? 150 : 75;\n                if(NPC[A].Special >= hidden_length)\n                {\n                    bool tempTurn = true;\n                    if(!NPC[A].Inert)\n                    {\n                        for(int B = 1; B <= numPlayers; B++)\n                        {\n                            if(!Player[B].Dead && Player[B].TimeToLive == 0)\n                            {\n                                if(!CanComeOut(NPC[A].Location, Player[B].Location))\n                                {\n                                    tempTurn = false;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n\n                    if(tempTurn)\n                    {\n                        NPC[A].Special2 = 1;\n                        NPC[A].Special = 0;\n                    }\n                    else\n                        NPC[A].Special = 140;\n                }\n            }\n\n            NPC[A].Location.Height = NPC[A]->THeight - (NPC[A].Location.Y - NPC[A].DefaultLocationY);\n\n            if(NPC[A].Location.Height < 0)\n                NPC[A].Location.Height = 0;\n            // deferring tree update to end of the NPC physics update\n\n            if(NPC[A].Location.Height == 0)\n                NPC[A].Immune = 100;\n            else\n                NPC[A].Immune = 0;\n        }\n    }\n    // down piranha plant\n    else if(NPC[A].Type == NPCID_BOTTOM_PLANT || NPC[A].Type == NPCID_LONG_PLANT_DOWN)\n    {\n        if(NPC[A].Special3 > 0)\n            NPC[A].Special3 -= 1;\n\n        if(NPC[A].Location.X != NPC[A].DefaultLocationX)\n        {\n            NPC[A].Killed = 2;\n            NPCQueues::Killed.push_back(A);\n\n            NPC[A].Location.Y -= NPC[A].Location.SpeedY;\n            // deferring tree update to end of the NPC physics update\n        }\n        else\n        {\n            if(NPC[A].Special2 == 0 && !NPC[A].Inert)\n            {\n                // .Location.Y += -NPCHeight(.Type) - 1.5\n                NPC[A].Location.Height = 0;\n                NPC[A].Special2 = 1;\n                NPC[A].Special = 0;\n            }\n            else if(NPC[A].Special2 == 1)\n            {\n                NPC[A].Special += 1;\n                // .Location.Y += 1.5\n                NPC[A].Location.Height += 1.5_n;\n\n                // NPC[A].Special >= NPC[A]->THeight * 0.65 + 1\n                if(NPC[A].Special * 100 >= NPC[A]->THeight * 65 + 100)\n                {\n                    if(g_config.fix_plant_wobble)\n                        NPC[A].Location.Height = NPC[A]->THeight;\n\n                    NPC[A].Special2 = 2;\n                    NPC[A].Special = 0;\n                }\n            }\n            else if(NPC[A].Special2 == 2)\n            {\n                if(NPC[A].Type != NPCID_LONG_PLANT_DOWN)\n                    NPC[A].Special += 1;\n\n                if(NPC[A].Special >= 50)\n                {\n                    NPC[A].Special2 = 3;\n                    NPC[A].Special = 0;\n                }\n            }\n            else if(NPC[A].Special2 == 3)\n            {\n                NPC[A].Special += 1;\n                // .Location.Y += -1.5\n                NPC[A].Location.Height -= 1.5_n;\n\n                // NPC[A].Special >= NPC[A]->THeight * 0.65 + 1\n                if(NPC[A].Special * 100 >= NPC[A]->THeight * 65 + 100)\n                {\n                    if(g_config.fix_plant_wobble)\n                        NPC[A].Location.Height = 0;\n\n                    NPC[A].Special2 = 4;\n                }\n            }\n            else if(NPC[A].Special2 == 4)\n            {\n                NPC[A].Special += 1;\n                if(NPC[A].Special >= 110)\n                {\n                    NPC[A].Special2 = 1;\n                    NPC[A].Special = 0;\n                }\n            }\n            // deferring tree update to end of the NPC physics update\n\n            if(NPC[A].Location.Height == 0)\n                NPC[A].Immune = 100;\n            else\n                NPC[A].Immune = 0;\n        }\n    // left/right piranha plant\n    }\n    else if(NPC[A].Type == NPCID_SIDE_PLANT)\n    {\n        NPC[A].Direction = NPC[A].DefaultDirection;\n        if(NPC[A].Location.Y != NPC[A].DefaultLocationY)\n        {\n            NPC[A].Location.Y += -NPC[A].Location.SpeedY;\n            NPCHit(A, 4);\n            // deferring tree update to end of the NPC physics update\n        }\n        else\n        {\n            if(NPC[A].Special2 == 0 && !NPC[A].Inert)\n            {\n                if(NPC[A].Direction == 1)\n                {\n                    // .Location.x += -NPCWidth(.Type) - 1.5\n                    NPC[A].Location.Width += -NPC[A]->TWidth - 1.5_n;\n                }\n                else\n                    NPC[A].Location.X += NPC[A]->TWidth + 1.5_n;\n                NPC[A].Special2 = 1;\n                NPC[A].Special = 0;\n            }\n            else if(NPC[A].Special2 == 1)\n            {\n                NPC[A].Special += 1;\n\n                if(NPC[A].Direction == -1)\n                    NPC[A].Location.X += 1.5_n * NPC[A].Direction;\n                else\n                    NPC[A].Location.Width += 1.5_n * NPC[A].Direction;\n\n                // NPC[A].Special >= NPC[A]->TWidth * 0.65 + 1\n                if(NPC[A].Special * 100 >= NPC[A]->TWidth * 65 + 100)\n                {\n                    if(g_config.fix_plant_wobble)\n                    {\n                        NPC[A].Location.Width = NPC[A]->TWidth;\n                        NPCQueues::Unchecked.push_back(A);\n                    }\n\n                    NPC[A].Special2 = 2;\n                    NPC[A].Special = 0;\n                }\n            }\n            else if(NPC[A].Special2 == 2)\n            {\n                NPC[A].Special += 1;\n\n                if(NPC[A].Special >= 50)\n                {\n                    NPC[A].Special2 = 3;\n                    NPC[A].Special = 0;\n                }\n            }\n            else if(NPC[A].Special2 == 3)\n            {\n                NPC[A].Special += 1;\n\n                if(NPC[A].Direction == -1)\n                    NPC[A].Location.X -= 1.5_n * NPC[A].Direction;\n                else\n                    NPC[A].Location.Width -= 1.5_n * NPC[A].Direction;\n\n                // NPC[A].Special >= NPC[A]->TWidth * 0.65 + 1\n                if(NPC[A].Special * 100 >= NPC[A]->TWidth * 65 + 100)\n                {\n                    NPC[A].Special2 = 4;\n\n                    if(g_config.fix_plant_wobble)\n                    {\n                        NPC[A].Location.Width = 0;\n                        NPCQueues::Unchecked.push_back(A);\n                    }\n                }\n            }\n            else if(NPC[A].Special2 == 4)\n            {\n                NPC[A].Special += 1;\n                if(NPC[A].Special >= 110)\n                {\n                    NPC[A].Special2 = 1;\n                    NPC[A].Special = 0;\n                }\n            }\n\n            if(NPC[A].Direction == -1)\n            {\n                NPC[A].Location.Width = NPC[A]->TWidth - (NPC[A].Location.X - NPC[A].DefaultLocationX);\n                if(NPC[A].Location.Width < 0)\n                    NPC[A].Location.Width = 0;\n\n                NPCQueues::Unchecked.push_back(A);\n            }\n\n            // deferring tree update to end of the NPC physics update\n\n            if(NPC[A].Location.Width == 0)\n                NPC[A].Immune = 100;\n            else\n                NPC[A].Immune = 0;\n\n        }\n    }\n    // smb3 belt code\n    else if(NPC[A].Type == NPCID_CONVEYOR)\n    {\n        NPC[A].Location.SpeedX = 0.8_n * NPC[A].DefaultDirection * BeltDirection;\n        NPC[A].Location.X = NPC[A].DefaultLocationX;\n        NPC[A].Location.Y = NPC[A].DefaultLocationY;\n        NPC[A].Direction = NPC[A].DefaultDirection * BeltDirection;\n        // deferring tree update to end of the NPC physics update\n    }\n    else if(NPC[A].Type == NPCID_CIVILIAN_SCARED)\n    {\n        if(NPC[A].Location.SpeedY == Physics.NPCGravity)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Frame = 0;\n            if(NPC[A].Special >= 100)\n                NPC[A].Special = 1;\n            else if(NPC[A].Special >= 10)\n            {\n                NPC[A].Special = 0;\n                NPC[A].Frame = 1;\n                NPC[A].Location.Y -= 1;\n                NPC[A].Location.SpeedY = -4.6_n;\n                // deferring tree update to end of the NPC physics update\n            }\n        }\n        else\n        {\n            if(NPC[A].Special <= 8)\n            {\n                NPC[A].Special += 1;\n                NPC[A].Frame = 1;\n            }\n            else\n            {\n                NPC[A].Frame = 2;\n                NPC[A].Special = 100;\n            }\n\n        }\n\n        if(NPC[A].Direction == 1)\n            NPC[A].Frame += 3;\n    }\n    // Fireball code (Podoboo)\n    else if(NPC[A].Type == NPCID_LAVABUBBLE)\n    {\n        Location_t tempLocation;\n\n        if(NPC[A].Location.Y > NPC[A].DefaultLocationY + NPC[A].Location.Height + 16)\n            NPC[A].Location.Y = NPC[A].DefaultLocationY + NPC[A]->THeight + 16;\n\n        NPC[A].Projectile = true;\n\n        // If .Location.X <> .DefaultLocationX Then .Killed = 2\n        if(NPC[A].Special2 == 0)\n        {\n            NPC[A].Location.Y = NPC[A].DefaultLocationY + NPC[A].Location.Height + 1.5_n;\n            NPC[A].Special2 = 1;\n            NPC[A].Special = 0;\n            PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n            tempLocation = NPC[A].Location;\n            tempLocation.Y -= 32;\n            NewEffect(EFFID_LAVA_SPLASH, tempLocation);\n        }\n        else if(NPC[A].Special2 == 1)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedY = -6;\n\n            if(NPC[A].Location.Y < NPC[A].DefaultLocationY - 10)\n            {\n                if(NPC[A].Special % 5 == 0) {\n                    NewEffect(EFFID_BIG_FIREBALL_TAIL, NPC[A].Location);\n                }\n            }\n\n            if(NPC[A].Special >= 30)\n            {\n                NPC[A].Special2 = 2;\n                NPC[A].Special = 0;\n            }\n        }\n        else if(NPC[A].Special2 == 2)\n        {\n            NPC[A].Special += 1;\n\n            if(NPC[A].Special == 61)\n            {\n                tempLocation = NPC[A].Location;\n                tempLocation.Y += 2;\n                NewEffect(EFFID_LAVA_SPLASH, tempLocation);\n                PlaySoundSpatial(SFX_Lava, NPC[A].Location);\n            }\n\n            if(NPC[A].Special >= 150)\n            {\n                NPC[A].Special2 = 0;\n                NPC[A].Special = 0;\n            }\n        }\n\n        if(NPC[A].Location.Y > level[NPC[A].Section].Height + 1)\n            NPC[A].Location.Y = level[NPC[A].Section].Height;\n        // deferring tree update to end of the NPC physics update\n\n        // Stop the big fireballs from getting killed from tha lava\n        NPC[A].Projectile = false;\n    }\n    else if(NPC[A].Type == NPCID_FALL_BLOCK_RED || NPC[A].Type == NPCID_FALL_BLOCK_BROWN)\n    {\n        if(LevelMacro == LEVELMACRO_OFF && NPC[A].Special == 0)\n        {\n            if(NPC[A].Special2 == 1)\n            {\n                NPC[A].Special3 += 1;\n                NPC[A].Special2 = 0;\n\n                // new shake logic because wings allow this object to move horizontally\n                if(NPC[A].Wings)\n                {\n                    NPC[A].Special4 = (NPC[A].Special4 + 1) & 3;\n                    NPC[A].Location.X += (NPC[A].Special4 >= 2) ? 2 : -2;\n                }\n                else\n                {\n                    NPC[A].Location.X += NPC[A].Direction * 2;\n                    if(NPC[A].Location.X >= NPC[A].DefaultLocationX + 2)\n                        NPC[A].Direction = -1;\n                    if(NPC[A].Location.X <= NPC[A].DefaultLocationX - 2)\n                        NPC[A].Direction = 1;\n                }\n            }\n            else\n            {\n                if(NPC[A].Special3 > 0)\n                    NPC[A].Special3 -= 1;\n\n                // new shake logic because wings allow this object to move horizontally\n                if(NPC[A].Wings)\n                    NPC[A].Special4 = 0;\n                else\n                    NPC[A].Location.X = NPC[A].DefaultLocationX;\n            }\n\n            if((NPC[A].Special3 >= 5 && NPC[A].Type == NPCID_FALL_BLOCK_RED) || (NPC[A].Special3 >= 30 && NPC[A].Type == NPCID_FALL_BLOCK_BROWN))\n            {\n                NPC[A].Special = 1;\n\n                if(NPC[A].Wings)\n                    NPC[A].Wings = WING_NONE;\n                else\n                    NPC[A].Location.X = NPC[A].DefaultLocationX;\n            }\n            // deferring tree update to end of the NPC physics update\n        }\n    // Big Koopa Code\n    }\n    else if(NPC[A].Type == NPCID_MINIBOSS)\n    {\n        // Special is the current state\n        // SpecialY is the main counter (was previously Special2)\n        if(NPC[A].Legacy)\n        {\n            if(NPC[A].TimeLeft > 1)\n                NPC[A].TimeLeft = 100;\n            if(bgMusic[NPC[A].Section] != 6 && bgMusic[NPC[A].Section] != 15 && bgMusic[NPC[A].Section] != 21 && NPC[A].TimeLeft > 1)\n            {\n                bgMusic[NPC[A].Section] = 6;\n                StartMusicIfOnscreen(NPC[A].Section);\n            }\n        }\n\n        // temporarily remove any wings if not in normal state\n        if(NPC[A].Special != 0)\n            NPC[A].Wings = WING_NONE;\n\n        if(NPC[A].Special == 0)\n        {\n            if(NPC[A].Location.Height != 54)\n            {\n                NPC[A].Location.Y += NPC[A].Location.Height - 54;\n                NPC[A].Location.Height = 54;\n\n                // restore wings if lost\n                NPC[A].Wings = NPC[A].DefaultWings;\n            }\n\n            NPC[A].SpecialY += dRand() * 2;\n            if(NPC[A].SpecialY >= 250 + iRand(250))\n            {\n                NPC[A].Special = 2;\n                NPC[A].SpecialY = 0;\n            }\n        }\n        else if(NPC[A].Special == 2)\n        {\n            NPC[A].SpecialY += 1;\n            if(NPC[A].SpecialY >= 10)\n            {\n                NPC[A].Special = 1;\n                NPC[A].SpecialY = 0;\n            }\n        }\n        else if(NPC[A].Special == 1)\n        {\n            if(NPC[A].Location.Height != 40)\n            {\n                NPC[A].Location.Y += NPC[A].Location.Height - 40;\n                NPC[A].Location.Height = 40;\n            }\n\n            NPC[A].SpecialY += dRand() * 2;\n            if(NPC[A].SpecialY >= 100 + iRand(100))\n            {\n                NPC[A].Special = 3;\n                NPC[A].SpecialY = 0;\n            }\n        }\n        else if(NPC[A].Special == 3)\n        {\n            NPC[A].SpecialY += 1;\n            if(NPC[A].SpecialY >= 10)\n            {\n                NPC[A].Special = 0;\n                NPC[A].SpecialY = 0;\n            }\n        }\n        else if(NPC[A].Special == 4)\n        {\n            if(NPC[A].Location.Height != 34)\n            {\n                NPC[A].Location.Y += NPC[A].Location.Height - 34;\n                NPC[A].Location.Height = 34;\n            }\n\n            NPC[A].SpecialY += 1;\n            if(NPC[A].SpecialY >= 100)\n            {\n                NPC[A].Special = 1;\n                NPC[A].SpecialY = 0;\n            }\n        }\n        // deferring tree update to end of the NPC physics update\n    }\n    else if(NPCIsAParaTroopa(NPC[A].Type)) // para-troopas\n    {\n        // do wings logic if it won't be handled elsewhere\n        if(!NPC[A].Wings)\n            NPCMovementLogic_Wings(A, 1);\n    }\n    // Jumpy bee thing\n    else if(NPC[A].Type == NPCID_FLY)\n    {\n        if(NPC[A].Location.SpeedY == Physics.NPCGravity || NPC[A].Slope > 0)\n        {\n            NPC[A].Location.SpeedX = 0;\n            NPC[A].Special += 1;\n            if(NPC[A].Special == 30)\n            {\n                NPC[A].Special = 0;\n                NPC[A].Location.Y -= 1;\n                NPC[A].Location.SpeedY = -6;\n                NPC[A].Location.SpeedX = 1.4_n * NPC[A].Direction;\n                // deferring tree update to end of the NPC physics update\n            }\n        }\n    }\n    // Bouncy Star thing code\n    else if(NPC[A].Type == NPCID_JUMPER_S3)\n    {\n        NPCFaceNearestPlayer(NPC[A]);\n\n        if(NPC[A].Location.SpeedY == Physics.NPCGravity || NPC[A].Slope > 0)\n        {\n            NPC[A].Special += 1;\n            if(NPC[A].Special == 8)\n            {\n                NPC[A].Location.SpeedY = -7;\n                NPC[A].Location.Y -= 1;\n                NPC[A].Special = 0;\n                // deferring tree update to end of the NPC physics update\n            }\n        }\n        else\n            NPC[A].Special = 0;\n    }\n    // bowser statue\n    else if(NPC[A].Type == NPCID_STATUE_S3 || NPC[A].Type == NPCID_STATUE_S4)\n    {\n        NPC[A].Special += 1;\n        if(NPC[A].Special > 200 + iRand(200))\n        {\n            NPC[A].Special = 0;\n            numNPCs++;\n            NPC[numNPCs].Inert = NPC[A].Inert;\n            NPC[numNPCs].Type = NPCID_STATUE_FIRE;\n            NPC[numNPCs].Direction = NPC[A].Direction;\n            NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n            NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n            NPC[numNPCs].TimeLeft = 100;\n            NPC[numNPCs].Active = true;\n            NPC[numNPCs].Section = NPC[A].Section;\n            NPC[numNPCs].Location.Y = NPC[A].Location.Y + 16;\n            NPC[numNPCs].Location.X = NPC[A].Location.X + 24 * NPC[numNPCs].Direction;\n            if(NPC[A].Type == NPCID_STATUE_S4)\n            {\n                NPC[numNPCs].Location.Y -= 5;\n                NPC[numNPCs].Location.X = NPC[A].Location.X + 6 + 30 * NPC[numNPCs].Direction;\n            }\n            NPC[numNPCs].Location.SpeedX = 4 * NPC[numNPCs].Direction;\n            if(NPC[numNPCs].Direction == 1)\n                NPC[numNPCs].Frame = 4;\n            NPC[numNPCs].FrameCount = iRand(8);\n            PlaySoundSpatial(SFX_BigFireball, NPC[A].Location);\n\n            syncLayers_NPC(numNPCs);\n        }\n    }\n    // Hammer Bro\n    else if(NPC[A].Type == NPCID_HEAVY_THROWER)\n    {\n        if(NPC[A].Projectile)\n            return;\n\n        NPCFaceNearestPlayer(NPC[A]);\n\n        if(NPC[A].Special > 0)\n        {\n            NPC[A].Special += 1;\n            NPC[A].Location.SpeedX = 0.6_n;\n            if(NPC[A].Special >= 100 && NPC[A].Location.SpeedY == Physics.NPCGravity)\n                NPC[A].Special = -1;\n        }\n        else\n        {\n            NPC[A].Special -= 1;\n            NPC[A].Location.SpeedX = -0.6_n;\n            if(NPC[A].Special <= -100 && NPC[A].Location.SpeedY == Physics.NPCGravity)\n                NPC[A].Special = 1;\n        }\n\n        if(NPC[A].Location.SpeedY == Physics.NPCGravity)\n        {\n            NPC[A].Special2 += 1;\n            if(NPC[A].Special2 >= 250)\n            {\n                NPC[A].Location.SpeedY = -7;\n                NPC[A].Location.Y -= 1;\n                NPC[A].Special2 = 0;\n                // deferring tree update to end of the NPC physics update\n            }\n        }\n\n        // the throw counter was previously Special3\n        NPC[A].SpecialX += dRand() * 2;\n        if(NPC[A].SpecialX >= 50 + dRand() * 1000)\n        {\n            if(NPC[A].Location.SpeedY == Physics.NPCGravity)\n            {\n                NPC[A].Location.SpeedY = -3;\n                NPC[A].Location.Y -= 1;\n                // deferring tree update to end of the NPC physics update\n            }\n\n            PlaySoundSpatial(SFX_HeavyToss, NPC[A].Location);\n            NPC[A].SpecialX = -15;\n            numNPCs++;\n            NPC[numNPCs].Inert = NPC[A].Inert;\n            NPC[numNPCs].Location.Height = 32;\n            NPC[numNPCs].Location.Width = 32;\n            NPC[numNPCs].Location.X = NPC[A].Location.X;\n            NPC[numNPCs].Location.Y = NPC[A].Location.Y;\n            NPC[numNPCs].Direction = NPC[A].Direction;\n            NPC[numNPCs].Type = NPCID_HEAVY_THROWN;\n            NPC[numNPCs].Section = NPC[A].Section;\n            NPC[numNPCs].Active = true;\n            NPC[numNPCs].TimeLeft = 50;\n            NPC[numNPCs].Location.SpeedY = -8;\n            NPC[numNPCs].Location.SpeedX = 3 * NPC[numNPCs].Direction;\n\n            syncLayers_NPC(numNPCs);\n        }\n    }\n    // leaf\n    else if(NPC[A].Type == NPCID_LEAF_POWER) // Leaf\n    {\n        if(NPC[A].Stuck && !NPC[A].Projectile)\n            NPC[A].Location.SpeedX = 0;\n        else if(NPC[A].Stuck)\n        {\n            NPC[A].Location.SpeedY += Physics.NPCGravity;\n            if(NPC[A].Location.SpeedY >= 8)\n                NPC[A].Location.SpeedY = 8;\n        }\n        else if(NPC[A].Special == 0)\n        {\n            NPC[A].Location.SpeedY += Physics.NPCGravity;\n            if(NPC[A].Projectile)\n            {\n                if(NPC[A].Location.SpeedY >= 2)\n                {\n                    NPC[A].Location.SpeedX = 1.2_n;\n                    NPC[A].Special = 1;\n                    NPC[A].Projectile = false;\n                }\n\n            }\n            else if(NPC[A].Location.SpeedY >= 0)\n                NPC[A].Special = 6;\n        }\n        else\n        {\n            if(NPC[A].Special == 1)\n            {\n                NPC[A].Location.SpeedY -= 0.25_n;\n                NPC[A].Location.SpeedX += 0.3_n;\n                if(NPC[A].Location.SpeedY <= 0)\n                    NPC[A].Special = 2;\n            }\n            else if(NPC[A].Special == 2)\n            {\n                NPC[A].Location.SpeedX -= 0.3_n;\n                NPC[A].Location.SpeedY -= 0.02_n;\n                if(NPC[A].Location.SpeedX <= 0)\n                {\n                    NPC[A].Special = 3;\n                    NPC[A].Location.SpeedX = 0;\n                }\n            }\n            else if(NPC[A].Special == 3)\n            {\n                NPC[A].Location.SpeedY += 0.4_n;\n                NPC[A].Location.SpeedX -= 0.1_n;\n                if(NPC[A].Location.SpeedY >= 3)\n                    NPC[A].Special = 4;\n            }\n            else if(NPC[A].Special == 4)\n            {\n                NPC[A].Location.SpeedY -= 0.25_n;\n                NPC[A].Location.SpeedX -= 0.3_n;\n                if(NPC[A].Location.SpeedY <= 0)\n                    NPC[A].Special = 5;\n            }\n            else if(NPC[A].Special == 5)\n            {\n                NPC[A].Location.SpeedX += 0.3_n;\n                NPC[A].Location.SpeedY -= 0.02_n;\n                if(NPC[A].Location.SpeedX >= 0)\n                {\n                    NPC[A].Special = 6;\n                    NPC[A].Location.SpeedX = 0;\n                }\n            }\n            else if(NPC[A].Special == 6)\n            {\n                NPC[A].Location.SpeedY += 0.4_n;\n                NPC[A].Location.SpeedX += 0.1_n;\n                if(NPC[A].Location.SpeedY >= 3)\n                    NPC[A].Special = 1;\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_SPIKY_THROWER || NPC[A].Type == NPCID_ITEM_THROWER) // combined code for spiky and item throwers\n    {\n        if(NPC[A].Type == NPCID_ITEM_THROWER && NPC[A].Special == 0)\n            NPC[A].Special = NPC[A].Type;\n\n        NPC[A].Projectile = false;\n        if(NPC[A].TimeLeft > 1)\n            NPC[A].TimeLeft = 100;\n        if(NPC[A].CantHurt > 0)\n            NPC[A].CantHurt = 100;\n\n        num_t min_dist = 0;\n        int target_plr = 0;\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            if(NPC[A].Type == NPCID_ITEM_THROWER && Player[B].TimeToLive != 0)\n                continue;\n\n            if(!Player[B].Dead && Player[B].Section == NPC[A].Section && B != NPC[A].CantHurtPlayer)\n            {\n                num_t dist = NPCPlayerTargetDist(NPC[A], Player[B]);\n                if(min_dist == 0 || dist < min_dist)\n                {\n                    min_dist = dist;\n                    target_plr = B;\n                }\n            }\n        }\n\n        const auto& p = Player[target_plr];\n\n        if(target_plr > 0)\n        {\n            // this direction indicator used Special (for spiky throwers) and Special6 (for item throwers) in SMBX 1.3\n            // now it uses SpecialX (as a simple boolean flag)\n\n            // speed control code: check distance to player\n            num_t dx = NPC[A].Location.minus_center_x(p.Location);\n            num_t speed_bound = num_t::abs(dx) / 100 + num_t::abs(p.Location.SpeedX) / 2 + 5;\n\n            // adjust expected distance by player's speed\n            dx -= p.Location.SpeedX * 15;\n\n            // movement left\n            if(NPC[A].SpecialX == 0)\n            {\n                if(NPC[A].Location.SpeedX >= -speed_bound)\n                    NPC[A].Location.SpeedX -= 0.2_n;\n                // if we are more than 50px to the left of the player's expected location\n                if(dx < -50)\n                    NPC[A].SpecialX = 1;\n            }\n            // movement right\n            else\n            {\n                if(NPC[A].Location.SpeedX <= speed_bound)\n                    NPC[A].Location.SpeedX += 0.2_n;\n                // if we are more than 50px to the right of the player's expected location\n                if(dx > 50)\n                    NPC[A].SpecialX = 0;\n            }\n\n            // target_plr is the targeted player\n            // D is the targeted vScreen\n\n            int D;\n            if(g_config.fix_npc_camera_logic)\n                D = vScreenIdxByPlayer_canonical(target_plr);\n            else\n            {\n                const Screen_t& plr_screen = ScreenByPlayer(target_plr);\n\n                int vscreen_idx = 0;\n\n                if(plr_screen.player_count == 2)\n                {\n                    if(plr_screen.Type == 5)\n                    {\n                        if(plr_screen.DType != 5)\n                            vscreen_idx = 1;\n                    }\n                }\n\n                D = plr_screen.vScreen_refs[vscreen_idx];\n            }\n\n            if(NPC[A].Location.Y + NPC[A].Location.Height > p.Location.Y - 248)\n                NPC[A].Special2 = 1;\n            if(NPC[A].Location.Y + NPC[A].Location.Height < p.Location.Y - 256 || NPC[A].Location.Y < -vScreen[D].Y)\n                NPC[A].Special2 = 0;\n            if(NPC[A].Location.Y > -vScreen[D].Y + 64)\n                NPC[A].Special2 = 1;\n            if(NPC[A].Location.Y < -vScreen[D].Y + 72)\n                NPC[A].Special2 = 0;\n\n            if(NPC[A].Special2 == 0)\n            {\n                NPC[A].Location.SpeedY += 0.05_n;\n                if(NPC[A].Location.SpeedY > 2)\n                    NPC[A].Location.SpeedY = 2;\n            }\n            else\n            {\n                NPC[A].Location.SpeedY -= 0.05_n;\n                if(NPC[A].Location.SpeedY < -2)\n                    NPC[A].Location.SpeedY = -2;\n            }\n\n            if(NPC[A].Inert)\n            {\n                if(NPC[A].Special3 > 1)\n                    NPC[A].Special3 = 0;\n            }\n\n            // frame timing code for spiky thrower\n            if(NPC[A].Type != NPCID_ITEM_THROWER)\n            {\n                if(NPC[A].Special3 == 0)\n                {\n                    NPC[A].FrameCount += 1;\n                    if(NPC[A].FrameCount >= 10)\n                    {\n                        NPC[A].FrameCount = 0;\n                        NPC[A].Frame += 1;\n                        if(NPC[A].Frame >= 2)\n                            NPC[A].Special3 = 1;\n                    }\n                }\n                else if(NPC[A].Special3 == 1)\n                {\n                    NPC[A].FrameCount += 1;\n                    if(NPC[A].FrameCount >= 10)\n                    {\n                        NPC[A].FrameCount = 0;\n                        NPC[A].Frame -= 1;\n                        if(NPC[A].Frame <= 0)\n                            NPC[A].Special3 = 0;\n                    }\n                }\n                else if(NPC[A].Special3 == 2)\n                {\n                    NPC[A].FrameCount += 1;\n                    if(NPC[A].FrameCount >= 16)\n                    {\n                        NPC[A].FrameCount = 10;\n                        if(NPC[A].Frame < 5)\n                            NPC[A].Frame += 1;\n                        if(NPC[A].Frame <= 5)\n                            NPC[A].Special5 += 1;\n                    }\n                }\n                else if(NPC[A].Special3 == 3)\n                {\n                    NPC[A].FrameCount += 1;\n                    if(NPC[A].FrameCount >= 2)\n                    {\n                        NPC[A].FrameCount = 0;\n                        NPC[A].Frame -= 1;\n                        if(NPC[A].Frame <= 0)\n                        {\n                            NPC[A].Special3 = 0;\n                            NPC[A].Frame = 0;\n                        }\n                    }\n                }\n            }\n\n            // yes, it's a VB6 bug, p.Location.Width / 2 should be subtracted, not added\n            if(num_t::abs(NPC[A].Location.X - p.Location.X + (NPC[A].Location.Width + p.Location.Width) / 2) < 100)\n            {\n                if(NPC[A].Special4 == 0)\n                {\n                    NPC[A].Special3 = 2;\n                    NPC[A].Special4 = 100;\n                }\n            }\n        }\n\n        if(NPC[A].Special4 > 0)\n            NPC[A].Special4 -= 1;\n\n        // frame timing code for item thrower\n        if(NPC[A].Type == NPCID_ITEM_THROWER)\n        {\n            NPC[A].Frame = 0;\n            if(NPC[A].FrameCount < 100)\n            {\n                NPC[A].FrameCount += 1;\n                if(NPC[A].FrameCount < 8)\n                    NPC[A].Frame = 0;\n                else if(NPC[A].FrameCount < 16)\n                    NPC[A].Frame = 1;\n                else if(NPC[A].FrameCount < 24)\n                    NPC[A].Frame = 2;\n                else if(NPC[A].FrameCount < 32)\n                    NPC[A].Frame = 1;\n                else\n                {\n                    NPC[A].Frame = 0;\n                    NPC[A].FrameCount = 0;\n                }\n            }\n            else\n            {\n                NPC[A].FrameCount += 1;\n                if(NPC[A].FrameCount < 108)\n                    NPC[A].Frame = 6;\n                else if(NPC[A].FrameCount < 116)\n                    NPC[A].Frame = 7;\n                else if(NPC[A].FrameCount < 124)\n                    NPC[A].Frame = 8;\n                else if(NPC[A].FrameCount < 132)\n                    NPC[A].Frame = 7;\n                else\n                {\n                    NPC[A].Frame = 0;\n                    NPC[A].FrameCount = 0;\n                }\n            }\n\n            if(NPC[A].Direction == 1)\n                NPC[A].Frame += 3;\n\n            NPC[A].Special5 += 1;\n        }\n\n        int timer = (NPC[A].Type == NPCID_ITEM_THROWER) ? 150 : 20;\n\n        if(NPC[A].Special5 >= timer)\n        {\n            NPC[A].Special5 = timer;\n            Location_t tempLocation = NPC[A].Location;\n            tempLocation.X -= 16;\n            tempLocation.Y -= 16;\n            tempLocation.Width += 32;\n            tempLocation.Height += 32;\n\n            bool do_not_throw = false;\n\n            if(NPC[A].Location.Y + NPC[A].Location.Height > p.Location.Y)\n                do_not_throw = true;\n            else\n            {\n                for(int Ei : treeBlockQuery(tempLocation, SORTMODE_NONE))\n                {\n                    if(NPC[A].Type != NPCID_ITEM_THROWER)\n                    {\n                        if(BlockIsSizable[Block[Ei].Type] ||\n                           BlockOnlyHitspot1[Block[Ei].Type])\n                        {\n                            continue;\n                        }\n                    }\n\n                    if(CheckCollision(tempLocation, Block[Ei].Location) && !BlockNoClipping[Block[Ei].Type])\n                    {\n                        do_not_throw = true;\n                        break;\n                    }\n                }\n            }\n\n            if(!do_not_throw)\n            {\n                if(NPC[A].Type == NPCID_ITEM_THROWER)\n                    NPC[A].FrameCount = 100;\n                else\n                    NPC[A].FrameCount = 0;\n\n                NPC[A].Special3 = 3;\n                NPC[A].Special5 = 0;\n\n                numNPCs++;\n\n                if(NPC[A].Location.to_right_of(p.Location))\n                    NPC[numNPCs].Direction = -1;\n                else\n                    NPC[numNPCs].Direction = 1;\n\n                if(NPC[A].CantHurt > 0)\n                {\n                    NPC[numNPCs].CantHurt = 100;\n                    NPC[numNPCs].CantHurtPlayer = NPC[A].CantHurtPlayer;\n                }\n\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].Section = NPC[A].Section;\n                NPC[numNPCs].TimeLeft = 100;\n\n                if(NPC[A].Type == NPCID_ITEM_THROWER)\n                {\n                    NPC[numNPCs].Type = NPCID(NPC[A].Special);\n\n                    if(NPC[numNPCs].Type == NPCID_RANDOM_POWER)\n                        NPC[numNPCs].Type = RandomBonus();\n\n                    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                    NPC[numNPCs].Location.X = NPC[A].Location.X + (NPC[A].Location.Width - NPC[numNPCs].Location.Width) / 2;\n                    NPC[numNPCs].Location.Y = NPC[A].Location.Y + 8;\n\n                    NPC[numNPCs].Location.SpeedX = (1 + dRand() * 2) * NPC[numNPCs].Direction;\n                    NPC[numNPCs].Location.SpeedY = -7;\n\n                    NPC[numNPCs].Variant = NPC[A].Variant;\n\n                    if(NPC[numNPCs]->IsACoin)\n                    {\n                        NPC[numNPCs].Special = 1;\n                        NPC[numNPCs].Location.SpeedX /= 2;\n                    }\n\n                    // commented out in SMBX 1.3\n                    // tempNPC = NPC(A)\n                    // NPC(A) = NPC(numNPCs)\n                    // NPC(numNPCs) = tempNPC\n                }\n                else\n                {\n                    NPC[numNPCs].Type = NPCID_SPIKY_BALL_S3;\n\n                    NPC[numNPCs].Location.X = NPC[A].Location.X;\n                    NPC[numNPCs].Location.Y = NPC[A].Location.Y + 8;\n                    NPC[numNPCs].Location.Height = 32;\n                    NPC[numNPCs].Location.Width = 32;\n\n                    NPC[numNPCs].Location.SpeedX = (1.5_n + num_t::abs(p.Location.SpeedX) * 0.75_rb) * NPC[numNPCs].Direction;\n                    NPC[numNPCs].Location.SpeedY = -8;\n\n                    // tempNPC = NPC[A];\n                    NPC_t tempNPC = NPC[A];\n                    NPC[A] = NPC[numNPCs];\n                    NPC[numNPCs] = tempNPC;\n                    PlaySoundSpatial(SFX_HeavyToss, NPC[A].Location);\n\n                    syncLayers_NPC(A);\n                }\n\n                syncLayers_NPC(numNPCs);\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_HIT_CARRY_FODDER) // smw goomba\n    {\n        NPC[A].Special += 1;\n        if(NPC[A].Special >= 400)\n        {\n            if(NPC[A].Slope > 0 || NPC[A].Location.SpeedY == Physics.NPCGravity || NPC[A].Location.SpeedY == 0)\n            {\n                NPC[A].Location.SpeedY = -5;\n                NPC[A].Type = NPCID_CARRY_FODDER;\n                NPC[A].Special = 0;\n                NPC[A].Location.Y -= 1;\n                // deferring tree update to end of the NPC physics update\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_STONE_S3 || NPC[A].Type == NPCID_STONE_S4) // thwomp\n    {\n            if(NPC[A].Special == 0)\n            {\n                NPC[A].Location.SpeedY = 0;\n                if(!NPC[A].Wings)\n                    NPC[A].Location.Y = NPC[A].DefaultLocationY;\n                for(int B = 1; B <= numPlayers; B++)\n                {\n                    bool playerLower = Player[B].Location.Y >= NPC[A].Location.Y;\n                    // When mode 1, simulate older behavior and do always fall\n                    playerLower |= (NPC[A].Variant == 1);\n                    if(!CanComeOut(NPC[A].Location, Player[B].Location) && playerLower)\n                    {\n                        NPC[A].Special = 1;\n                        break;\n                    }\n                }\n            }\n            else if(NPC[A].Special == 1)\n            {\n                NPC[A].Location.SpeedY = 6;\n\n                if(NPC[A].Wings)\n                {\n                    // remember current Y coordinate when rising back up\n                    NPC[A].SpecialY = NPC[A].Location.Y;\n                    NPC[A].Location.SpeedX = 0;\n                    NPC[A].Wings = WING_NONE;\n                }\n            }\n            else if(NPC[A].Special == 2)\n            {\n                if(NPC[A].Special2 == 0 && !NPC[A].Wings)\n                {\n                    PlaySoundSpatial(SFX_Stone, NPC[A].Location);\n                    if(g_config.extra_screen_shake)\n                        doShakeScreen(0, 4, SHAKE_SEQUENTIAL, 5, 200, NPC[A].Location);\n                    Location_t tempLocation;\n                    tempLocation.Width = 32;\n                    tempLocation.Height = 32;\n                    tempLocation.Y = NPC[A].Location.Y + NPC[A].Location.Height - 16;\n\n//                    tempLocation.X = NPC[A].Location.X;\n                    tempLocation.X = (NPC[A].Location.X + NPC[A].Location.Width / 8);\n                    NewEffect(EFFID_SMOKE_S3, tempLocation);\n                    // in SMBX 1.3, this was 1.5_n and SpeedX was doubled for EFFID_SMOKE_S3.\n                    Effect[numEffects].Location.SpeedX = -3;\n\n//                    tempLocation.X += tempLocation.Width - EffectWidth[EFFID_SMOKE_S3];\n                    tempLocation.X = (NPC[A].Location.X + NPC[A].Location.Width - EffectWidth[EFFID_SMOKE_S3]) - (NPC[A].Location.Width / 8);\n                    NewEffect(EFFID_SMOKE_S3, tempLocation);\n                    // in SMBX 1.3, this was 1.5_n and SpeedX was doubled for EFFID_SMOKE_S3.\n                    Effect[numEffects].Location.SpeedX = 3;\n                }\n                NPC[A].Location.SpeedY = 0;\n                if(NPC[A].Slope > 0)\n                    NPC[A].Location.Y -= 0.1_n;\n                NPC[A].Special2 += 1;\n                if(NPC[A].Special2 >= 100)\n                {\n                    NPC[A].Location.Y -= 1;\n                    NPC[A].Special = 3;\n                    NPC[A].Special2 = 0;\n                }\n            }\n            else if(NPC[A].Special == 3)\n            {\n                NPC[A].Location.SpeedY = -2;\n\n                num_t target_y = (NPC[A].DefaultWings) ? NPC[A].SpecialY : NPC[A].DefaultLocationY;\n                if(NPC[A].Location.Y <= target_y + 1)\n                {\n                    if(!NPC[A].Wings)\n                        NPC[A].Location.Y = target_y;\n\n                    NPC[A].Location.SpeedY = 0;\n                    NPC[A].Special = 0;\n                    NPC[A].Special2 = 0;\n                    NPC[A].Wings = NPC[A].DefaultWings;\n                }\n            }\n        // End If\n        // deferring tree update to end of the NPC physics update\n    }\n    else if(NPC[A].Type == NPCID_GHOST_S3 || NPC[A].Type == NPCID_GHOST_S4 || NPC[A].Type == NPCID_BIG_GHOST) // boo\n    {\n        if(BattleMode && NPC[A].CantHurt > 0)\n            NPC[A].CantHurt = 100;\n\n        if(NPC[A].Projectile)\n        {\n            if(NPC[A].CantHurtPlayer > 0)\n                NPC[A].BattleOwner = NPC[A].CantHurtPlayer;\n\n            NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.95_r;\n            NPC[A].Location.SpeedY = NPC[A].Location.SpeedY * 0.95_r;\n\n            if(NPC[A].Location.SpeedX > -2 && NPC[A].Location.SpeedX < 2)\n            {\n                if(NPC[A].Location.SpeedY > -2 && NPC[A].Location.SpeedY < 2)\n                    NPC[A].Projectile = false;\n            }\n        }\n\n        num_t min_dist = 0;\n        int target_plr = 0;\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            if(!Player[B].Dead && Player[B].Section == NPC[A].Section && B != NPC[A].CantHurtPlayer)\n            {\n                num_t dist = NPCPlayerTargetDist(NPC[A], Player[B]);\n                if(min_dist == 0 || dist < min_dist)\n                {\n                    min_dist = dist;\n                    target_plr = B;\n                }\n            }\n        }\n\n        const auto& p = Player[target_plr];\n\n        if(target_plr > 0)\n        {\n            num_t D = NPC[A].Location.X + NPC[A].Location.Width / 2;\n            num_t E = p.Location.X + p.Location.Width / 2;\n\n            // hide\n            if((D <= E && p.Direction == -1) || (D >= E && p.Direction == 1) || p.SpinJump)\n            {\n                // set wings to override to use normal ghost speed logic\n                if(NPC[A].Wings)\n                    NPC[A].Wings = WING_OVERRIDE;\n\n                NPC[A].Special = 0;\n\n                if(NPC[A].Type == NPCID_GHOST_S3)\n                {\n                    NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.9_r;\n                    NPC[A].Location.SpeedY = NPC[A].Location.SpeedY * 0.9_r;\n                }\n                else if(NPC[A].Type == NPCID_GHOST_S4)\n                {\n                    NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.85_r;\n                    NPC[A].Location.SpeedY = NPC[A].Location.SpeedY * 0.85_r;\n                }\n                else if(NPC[A].Type == NPCID_BIG_GHOST)\n                {\n                    NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.8_r;\n                    NPC[A].Location.SpeedY = NPC[A].Location.SpeedY * 0.8_r;\n                }\n\n                if(NPC[A].Location.SpeedX < 0.1_n && NPC[A].Location.SpeedX > -0.1_n)\n                    NPC[A].Location.SpeedX = 0;\n\n                if(NPC[A].Location.SpeedY < 0.1_n && NPC[A].Location.SpeedY > -0.1_n)\n                    NPC[A].Location.SpeedY = 0;\n            }\n            // chase\n            else\n            {\n                // restore wings to their default value\n                NPC[A].Wings = NPC[A].DefaultWings;\n\n                NPC[A].Special = 1;\n                NPC[A].Direction = p.Direction;\n\n                num_t F = 0;\n                if(NPC[A].Type == NPCID_GHOST_S3)\n                    F = 0.03_n;\n                else if(NPC[A].Type == NPCID_GHOST_S4)\n                    F = 0.025_n;\n                else if(NPC[A].Type == NPCID_BIG_GHOST)\n                    F = 0.02_n;\n\n                if(D <= E && NPC[A].Location.SpeedX < 1.5_n)\n                    NPC[A].Location.SpeedX += F;\n                else if(NPC[A].Location.SpeedX > -1.5_n)\n                    NPC[A].Location.SpeedX += -F;\n\n                D = NPC[A].Location.Y + NPC[A].Location.Height / 2;\n                E = p.Location.Y + p.Location.Height / 2;\n\n                if(D <= E && NPC[A].Location.SpeedY < 1.5_n)\n                    NPC[A].Location.SpeedY += F;\n                else if(NPC[A].Location.SpeedY > -1.5_n)\n                    NPC[A].Location.SpeedY += -F;\n            }\n        }\n        else\n        {\n            NPC[A].Special = 0;\n            NPC[A].Location.SpeedX = 0;\n            NPC[A].Location.SpeedY = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_STAR_EXIT || NPC[A].Type == NPCID_STAR_COLLECT)\n    {\n        if(NPC[A].Projectile)\n        {\n            NPC[A].Location.SpeedX = NPC[A].Location.SpeedX * 0.95_r;\n            NPC[A].Location.SpeedY = NPC[A].Location.SpeedY * 0.95_r;\n            if(NPC[A].Location.SpeedY < 1 && NPC[A].Location.SpeedY > -1)\n            {\n                if(NPC[A].Location.SpeedX < 1 && NPC[A].Location.SpeedX > -1)\n                    NPC[A].Projectile = false;\n            }\n        }\n\n        // unified sparkle timer\n        NPC[A].Special4 += 1;\n        if(NPC[A].Special4 >= ((NPC[A].Special == 0) ? 5 : 10))\n        {\n            NPC[A].Special4 = 0;\n            num_t dx = dRand();\n            num_t dy = dRand();\n            NewEffect(EFFID_SPARKLE, newLoc(NPC[A].Location.X + dx.times(NPC[A].Location.Width) - 2, NPC[A].Location.Y + dy.times(NPC[A].Location.Height)));\n            Effect[numEffects].Location.SpeedX = dRand() - 0.5_n;\n            Effect[numEffects].Location.SpeedY = dRand() - 0.5_n;\n\n            if(NPC[A].Special != 0)\n                Effect[numEffects].Frame = 1;\n        }\n\n        if(NPC[A].Special2 == 0)\n        {\n            NPC[A].Location.SpeedY -= 0.04_n;\n            if(NPC[A].Location.SpeedY <= -1.4_n)\n                NPC[A].Special2 = 1;\n        }\n        else\n        {\n            NPC[A].Location.SpeedY += 0.04_n;\n            if(NPC[A].Location.SpeedY >= 1.4_n)\n                NPC[A].Special2 = 0;\n        }\n\n        if(NPC[A].Special3 == 0)\n        {\n            NPC[A].Location.SpeedX -= 0.03_n;\n            if(NPC[A].Location.SpeedX <= -0.6_n)\n                NPC[A].Special3 = 1;\n        }\n        else\n        {\n            NPC[A].Location.SpeedX += 0.03_n;\n            if(NPC[A].Location.SpeedX >= 0.6_n)\n                NPC[A].Special3 = 0;\n        }\n    }\n    else if(NPC[A].Type == NPCID_SPIT_BOSS) // birdo\n    {\n        if(NPC[A].Projectile)\n            return;\n\n        if(NPC[A].Legacy)\n        {\n            if(NPC[A].TimeLeft > 1)\n                NPC[A].TimeLeft = 100;\n            if(bgMusic[NPC[A].Section] != 6 && bgMusic[NPC[A].Section] != 15 && bgMusic[NPC[A].Section] != 21 && NPC[A].TimeLeft > 1)\n            {\n                bgMusic[NPC[A].Section] = 15;\n                StartMusicIfOnscreen(NPC[A].Section);\n            }\n        }\n\n        // previously Special, changed to Special5 when it became a container\n        if(NPC[A].Special5 >= 0)\n        {\n            NPCFaceNearestPlayer(NPC[A]);\n\n            NPC[A].Special2 += 1;\n            if(NPC[A].Special2 == 125)\n            {\n                NPC[A].Location.Y -= 1;\n                NPC[A].Location.SpeedY = -5;\n                // previously never shot anything, now it shoots if contents are configured\n                if(NPC[A].Inert && !NPC[A].Special)\n                    NPC[A].Special2 = 0;\n                // deferring tree update to end of the NPC physics update\n            }\n            else if(NPC[A].Special2 >= 240)\n            {\n                if(NPC[A].Special2 == 260)\n                {\n                    numNPCs++;\n                    NPC[numNPCs].Active = true;\n                    NPC[numNPCs].Direction = NPC[A].Direction;\n                    NPC[numNPCs].Type = (NPC[A].Special) ? (NPCID)NPC[A].Special : NPCID_SPIT_BOSS_BALL;\n\n                    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                    NPC[numNPCs].Location.Y = NPC[A].Location.Y + 14 - NPC[numNPCs].Location.Height / 2;\n\n                    if(NPC[numNPCs].Direction == 1)\n                        NPC[numNPCs].Location.X = NPC[A].Location.X + NPC[A].Location.Width / 2;\n                    else\n                        NPC[numNPCs].Location.X = NPC[A].Location.X + NPC[A].Location.Width / 2 - NPC[numNPCs].Location.Width;\n\n                    NPC[numNPCs].TimeLeft = 100;\n                    NPC[numNPCs].Section = NPC[A].Section;\n                    NPC[numNPCs].Location.SpeedX = 4 * NPC[numNPCs].Direction;\n\n                    if(NPC[A].Special)\n                    {\n                        s_NPCSpawnCustomThrown(NPC[A], NPC[numNPCs]);\n\n                        if(NPC[numNPCs].Type == NPCID_SLIDE_BLOCK || NPC[numNPCs]->IsAShell)\n                            NPC[numNPCs].Location.SpeedX = Physics.NPCShellSpeed * NPC[numNPCs].Direction;\n                    }\n\n                    PlaySoundSpatial(SFX_SpitBossSpit, NPC[A].Location);\n                    syncLayers_NPC(numNPCs);\n                }\n                NPC[A].Special5 = 1;\n                if(NPC[A].Special2 > 280)\n                {\n                    NPC[A].Special2 = 0;\n                    NPC[A].Special5 = 0;\n                }\n            }\n            if(NPC[A].Special5 == 0 && NPC[A].Location.SpeedY == Physics.NPCGravity)\n            {\n                NPC[A].Special3 += 1;\n                if(NPC[A].Special3 <= 200)\n                    NPC[A].Location.SpeedX = -1;\n                else if(NPC[A].Special3 > 500)\n                    NPC[A].Special3 = 0;\n                else if(NPC[A].Special3 > 250 && NPC[A].Special3 <= 450)\n                    NPC[A].Location.SpeedX = 1;\n                else\n                    NPC[A].Location.SpeedX = 0;\n            }\n            else\n                NPC[A].Location.SpeedX = 0;\n        }\n        else\n        {\n            NPC[A].Special5 += 1;\n            NPC[A].Location.SpeedX = 0;\n        }\n\n        if(NPC[A].Stuck)\n            NPC[A].Location.SpeedX = 0;\n    }\n    else if(NPC[A].Type == NPCID_EXT_TURTLE)\n    {\n        if(NPC[A].Special > 0)\n        {\n            NPC[A].Special -= 1;\n            NPC[A].Location.SpeedX = 0;\n        }\n    // beach koopa\n    }\n    else if(NPC[A].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[A].Type <= NPCID_YEL_HIT_TURTLE_S4)\n    {\n        if(NPC[A].Type == NPCID_BLU_HIT_TURTLE_S4 && NPC[A].Special > 0)\n        {\n            NPC[A].Special -= 1;\n            NPC[A].Location.SpeedX = 0;\n        }\n\n        if(NPC[A].Projectile)\n        {\n            NPC[A].Location.SpeedX *= 0.96_r;\n            if(NPC[A].Location.SpeedX > -0.003_n && NPC[A].Location.SpeedX < 0.003_n)\n            {\n                NPC[A].Projectile = false;\n                NPC[A].Location.Y -= Physics.NPCGravity;\n                NPC[A].Location.SpeedY = -5;\n                NPC[A].Direction = -NPC[A].Direction;\n                // deferring tree update to end of the NPC physics update\n            }\n        }\n        else\n        {\n            if(NPC[A].Type != NPCID_BLU_HIT_TURTLE_S4)\n            {\n                if(num_t::fEqual_f(NPC[A].Location.SpeedY, Physics.NPCGravity))\n                {\n                    Location_t tempLocation = NPC[A].Location;\n                    tempLocation.Width += 32;\n                    tempLocation.X -= 16;\n\n                    for(int B : treeNPCQuery(tempLocation, SORTMODE_NONE))\n                    {\n                        if(NPC[B].Active && NPC[B].Section == NPC[A].Section && !NPC[B].Hidden && NPC[B].HoldingPlayer == 0)\n                        {\n                            if(NPC[B].Type >= NPCID_GRN_SHELL_S4 && NPC[B].Type <= NPCID_YEL_SHELL_S4)\n                            {\n                                if(CheckCollision(tempLocation, NPC[B].Location))\n                                {\n                                    NPC[A].Location.Y -= Physics.NPCGravity;\n                                    NPC[A].Location.SpeedY = -4;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n    else if(NPC[A].Type == NPCID_TOOTHY) // killer plant destroys blocks\n    {\n        for(int B : treeBlockQuery(NPC[A].Location, SORTMODE_COMPAT))\n        {\n            if(CheckCollision(NPC[A].Location, Block[B].Location))\n            {\n                BlockHitHard(B);\n            }\n        }\n    }\n    // Projectile code\n    else if(NPC[A].Type == NPCID_SLIDE_BLOCK)\n    {\n        if(NPC[A].Special == 1 && NPC[A].Location.SpeedX != 0)\n            NPC[A].Projectile = true;\n    }\n    else if(NPC[A].Type == NPCID_BULLET)\n    {\n        if(NPC[A].CantHurt > 0)\n            NPC[A].Projectile = true;\n    }\n    // Projectile check, previously set immediately before calling SpecialNPC\n    else if(NPC[A].Type == NPCID_PET_FIRE || NPC[A].Type == NPCID_SWORDBEAM || NPC[A].Type == NPCID_TANK_TREADS)\n        NPC[A].Projectile = true;\n    else if(NPC[A].Type == NPCID_METALBARREL || NPC[A].Type == NPCID_CANNONENEMY || NPC[A].Type == NPCID_HPIPE_SHORT\n        || NPC[A].Type == NPCID_HPIPE_LONG || NPC[A].Type == NPCID_VPIPE_SHORT || NPC[A].Type == NPCID_VPIPE_LONG\n        || (NPC[A].Type >= NPCID_SHORT_WOOD && NPC[A].Type <= NPCID_SLANT_WOOD_M))\n    {\n        if(NPC[A].Location.SpeedY > Physics.NPCGravity * 20)\n            NPC[A].Projectile = true;\n        else\n            NPC[A].Projectile = false;\n    }\n    else if(NPC[A].Type == NPCID_EARTHQUAKE_BLOCK)\n    {\n        if(NPC[A].Location.SpeedY > 2 || NPC[A].Location.SpeedY < -2)\n            NPC[A].Projectile = true;\n    }\n    // NPCs selected by trait (instead of type)\n    else\n    {\n        if(NPC[A]->IsAShell)\n        {\n            if(NPC[A].Location.SpeedX != 0)\n                NPC[A].Projectile = true;\n        }\n        // moved from above\n        else if(NPC[A]->IsFish)\n        {\n            if(NPC[A].Special == 1 && !NPC[A].Projectile)\n            {\n                if(NPC[A].Wet == 2)\n                    NPC[A].Special5 = 0;\n\n                int B = NPCTargetPlayer(NPC[A]);\n                if(B == 0)\n                    B = 1;\n\n                if(!Player[B].WetFrame && Player[B].Location.Y + Player[B].Location.Height < NPC[A].Location.Y)\n                {\n                    if((NPC[A].Direction == 1 && Player[B].Location.X > NPC[A].Location.X) ||\n                       (NPC[A].Direction == -1 && Player[B].Location.X < NPC[A].Location.X))\n                    {\n                        if(NPC[A].Location.X > Player[B].Location.X - 200 && NPC[A].Location.X + NPC[A].Location.Width < Player[B].Location.X + Player[B].Location.Width + 200)\n                        {\n                            if(NPC[A].Wet == 2)\n                            {\n                                if(NPC[A].Location.SpeedY > -3)\n                                    NPC[A].Location.SpeedY -= 0.1_n;\n                                NPC[A].Special3 = 1;\n                            }\n                        }\n                        else\n                            NPC[A].Special3 = 0;\n                    }\n                    else\n                        NPC[A].Special3 = 0;\n\n                    if(NPC[A].Special3 == 1 && NPC[A].Wet == 0)\n                    {\n                        NPC[A].Location.SpeedY = -(NPC[A].Location.Y - Player[B].Location.Y + Player[B].Location.Height / 2) / 20 + dRand() * 4 - 2;\n                        if(NPC[A].Location.SpeedY < -9)\n                            NPC[A].Location.SpeedY = -9;\n                        NPC[A].Special3 = 0;\n                        NPC[A].Special5 = 1;\n\n                        // this allows the fish to go through walls after leaping\n                        NPC[A].WallDeath = 10;\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid CharStuff(int WhatNPC, bool CheckEggs)\n{\n    bool SMBX = false;\n    bool SMB2 = false;\n    bool TLOZ = false;\n    int A = 0;\n    int NPCStart = 0;\n    int NPCStop = 0;\n    if(GameMenu)\n        return;\n\n    for(A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Character == 1 || Player[A].Character == 2)\n            SMBX = true;\n        if(Player[A].Character == 3 || Player[A].Character == 4)\n            SMB2 = true;\n        if(Player[A].Character == 5)\n            TLOZ = true;\n    }\n\n    if(WhatNPC == 0)\n    {\n        NPCStart = 1;\n        NPCStop = numNPCs;\n    }\n    else\n    {\n        NPCStart = WhatNPC;\n        NPCStop = WhatNPC;\n    }\n\n    if(!SMBX && SMB2 && CheckEggs) // Turn SMBX stuff into SMB2 stuff\n    {\n        for(A = NPCStart; A <= NPCStop; A++)\n        {\n            if(NPC[A].Type == NPCID_ITEM_POD && NPC[A].Special > 0 /* && CheckEggs*/) // Check Eggs\n            {\n                if(NPCIsYoshi((NPCID)NPC[A].Special)) // Yoshi into mushroom (Egg)\n                {\n                    // NPC(A).Special = 249\n                    NPC[A].Special = NPCID_GRN_BOOT; // Yoshi into boot\n                }\n            }\n        }\n    }\n\n    if(!SMBX && !SMB2 && TLOZ) // Turn SMBX stuff into Zelda stuff\n    {\n        for(int A : NPCQueues::Active.no_change)\n        {\n            if(WhatNPC != 0)\n                A = WhatNPC;\n\n            if(NPC[A].Active && !NPC[A].Generator && !NPC[A].Inert)\n            {\n                if(NPC[A].Type == NPCID_POWER_S3 || NPC[A].Type == NPCID_POWER_S1 || NPC[A].Type == NPCID_POWER_S4 || NPCIsBoot(NPC[A])) // turn mushrooms into hearts\n                {\n                    NPC[A].Frame = 0;\n                    NPC[A].Type = NPCID_POWER_S5;\n                    NPC[A].Location.SpeedX = 0;\n                    NPC[A].Location.Y += NPC[A].Location.Height - NPC[A]->THeight - 1;\n                    NPC[A].Location.X += (NPC[A].Location.Width - NPC[A]->TWidth) / 2;\n                    NPC[A].Location.Width = 32;\n                    NPC[A].Location.Height = 32;\n\n                    NPCQueues::Unchecked.push_back(A);\n                    treeNPCUpdate(A);\n                }\n                else if(NPC[A].Type == NPCID_COIN_S3 || NPC[A].Type == NPCID_COIN_S4 || NPC[A].Type == NPCID_COIN_S1 || NPC[A].Type == NPCID_COIN_S2 || NPC[A].Type == NPCID_COIN_5) // turn coins into rupees\n                {\n                    if(NPC[A].Type == NPCID_COIN_5)\n                        NPC[A].Type = NPCID_GEM_5;\n                    else\n                        NPC[A].Type = NPCID_GEM_1;\n                    NPC[A].Location.Y += NPC[A].Location.Height - NPC[A]->THeight;\n                    NPC[A].Location.X += (NPC[A].Location.Width - NPC[A]->TWidth) / 2;\n                    NPC[A].Location.Width = NPC[A]->TWidth;\n                    NPC[A].Location.Height = NPC[A]->THeight;\n                    NPC[A].Frame = 0;\n\n                    NPCQueues::Unchecked.push_back(A);\n                    treeNPCUpdate(A);\n                }\n            }\n\n            if(WhatNPC != 0)\n                break;\n        }\n    }\n\n    if(!SMBX && !SMB2 && TLOZ && CheckEggs) // Turn SMBX stuff into Zelda stuff\n    {\n        for(A = NPCStart; A <= NPCStop; A++)\n        {\n            if(NPC[A].Type == NPCID_ITEM_POD && NPC[A].Special > 0 /* && CheckEggs*/) // Check Eggs\n            {\n                if(NPCIsYoshi((NPCID)NPC[A].Special) || NPCIsBoot((NPCID)NPC[A].Special)) // Yoshi / boot into mushroom (Egg)\n                    NPC[A].Special = NPCID_POWER_S5;\n                if(NPC[A].Special == NPCID_POWER_S3 || NPC[A].Special == NPCID_POWER_S1 || NPC[A].Special == NPCID_POWER_S4) // mushrooms into hearts (eggs)\n                    NPC[A].Special = NPCID_POWER_S5;\n                if(NPC[A].Special == NPCID_COIN_S3 || NPC[A].Special == NPCID_COIN_S4 || NPC[A].Special == NPCID_COIN_S1 || (!SMB2 && NPC[A].Special == NPCID_COIN_S2)) // coins into rupees (eggs)\n                    NPC[A].Special = NPCID_GEM_1;\n            }\n        }\n    }\n}\n\nNPCID RandomBonus()\n{\n    int B = iRand(6);\n\n    switch(B)\n    {\n    default:\n    case 0:\n        return NPCID_POWER_S3;\n    case 1:\n        return NPCID_FIRE_POWER_S3;\n    case 2:\n        return NPCID_LEAF_POWER;\n    case 3:\n        return NPCID_STATUE_POWER;\n    case 4:\n        return NPCID_HEAVY_POWER;\n    case 5:\n        return NPCID_ICE_POWER_S3;\n    }\n}\n\nbool npcHasFloor(const struct NPC_t &npc)\n{\n    bool hasFloor = false;\n\n    if(npc.Type < 1 || npc.Type > maxNPCType)\n        return false; // invalid NPC type\n\n    if(npc->NoClipping)\n        return false; // No collision with blocks\n\n    const auto &l = npc.Location;\n\n    auto checkLoc = l;\n    checkLoc.Y = l.Y + l.Height;\n    checkLoc.Height = 4;\n    checkLoc.Width = l.Width / 2;\n    checkLoc.X = l.X + (l.Width / 2) - (checkLoc.Width / 2);\n\n    // Ensure that there is a floor under feet\n    for(BlockRef_t sb : treeBlockQuery(checkLoc, SORTMODE_NONE))\n    {\n        int idx = sb;\n\n        if(npc.tempBlock == idx)\n            continue; // Skip collision check to self\n\n        if(BlockNoClipping[sb->Type] || sb->Hidden || sb->Invis || (npc.Projectile && sb->tempBlockNoProjClipping()))\n            continue;\n\n        if(CheckCollision(checkLoc, sb->Location))\n        {\n            hasFloor = true;\n            break;\n        }\n    }\n\n    return hasFloor;\n}\n\nbool NPCNewContainerType(int Type)\n{\n    return Type == NPCID_SPIT_BOSS;\n}\n\nbool NPCIsContainer(const NPC_t& npc)\n{\n    return npc.Type == NPCID_ITEM_BURIED || npc.Type == NPCID_ITEM_POD\n        || npc.Type == NPCID_ITEM_BUBBLE || npc.Type == NPCID_ITEM_THROWER\n        || (NPCNewContainerType(npc.Type) && npc.Special != 0);\n}\n\nvoid NPCUnbury(int A, int Callsite)\n{\n    if(NPC[A].Generator)\n    {\n        NPC[A].Generator = false;\n        NPCQueues::update(A);\n    }\n\n    NPC[A].Frame = 0;\n\n    if(Callsite != 1)\n        NPC[A].Frame = EditorNPCFrame(NPC[A].Type, NPC[A].Direction);\n\n    if(Callsite != 2)\n    {\n        NPC[A].Type = NPCID(NPC[A].Special);\n        NPC[A].Wings = NPC[A].DefaultWings;\n    }\n\n    if(Callsite != 1 && Callsite != 2 && NPC[A].Type == NPCID_RANDOM_POWER)\n    {\n        NPC[A].Type = RandomBonus();\n        NPC[A].DefaultSpecial = NPC[A].Type;\n    }\n\n    if(Callsite != 1)\n        CharStuff(A);\n\n    NPC[A].Special = 0;\n\n    if(NPCIsYoshi(NPC[A]))\n    {\n        NPC[A].Special = NPC[A].Type;\n        NPC[A].Type = NPCID_ITEM_POD;\n    }\n\n    if(Callsite != 2 && Callsite != 3 && (NPC[A].Type == NPCID_TIME_SWITCH || NPC[A].Type == NPCID_TNT))\n    {\n        // those types were in the below sequence of checks at Callsite 1\n    }\n    else if(!(NPC[A].Type == NPCID_CANNONENEMY\n        || NPC[A].Type == NPCID_CANNONITEM\n        || NPC[A].Type == NPCID_SPRING\n        || NPC[A].Type == NPCID_KEY\n        || NPC[A].Type == NPCID_COIN_SWITCH\n        // || NPC[A].Type == NPCID_TIME_SWITCH\n        // || NPC[A].Type == NPCID_TNT\n        || NPC[A].Type == NPCID_GRN_BOOT\n        || NPC[A].Type == NPCID_RED_BOOT\n        || NPC[A].Type == NPCID_BLU_BOOT\n        || NPC[A].Type == NPCID_TOOTHYPIPE\n        || NPCIsAnExit(NPC[A])))\n    {\n        if(Callsite == 1 || !BattleMode)\n            NPC[A].DefaultType = NPCID_NULL;\n    }\n\n    NPC[A].Location.Height = NPC[A]->THeight;\n    NPC[A].Location.Width = NPC[A]->TWidth;\n\n    if(Callsite != 2 && NPC[A].Type == NPCID_VEGGIE_RANDOM)\n    {\n        int B = iRand(9);\n        NPC[A].Type = NPCID(NPCID_VEGGIE_2 + B);\n\n        if(NPC[A].Type == NPCID_VEGGIE_RANDOM)\n            NPC[A].Type = NPCID_VEGGIE_1;\n\n        NPC[A].Location.set_width_center(NPC[A]->TWidth);\n        NPC[A].Location.set_height_center(NPC[A]->THeight);\n    }\n}\n\nstatic bool s_TypeBansWings(NPCID Type)\n{\n    const auto& tr = NPCTraits[Type];\n    if(tr.IsAVine)\n        return true;\n\n    switch(Type)\n    {\n    // ban plants\n    case NPCID_JUMP_PLANT:\n    case NPCID_FIRE_PLANT:\n    case NPCID_PLANT_S3:\n    case NPCID_BIG_PLANT:\n    case NPCID_PLANT_S1:\n    case NPCID_LONG_PLANT_UP:\n    case NPCID_BOTTOM_PLANT:\n    case NPCID_LONG_PLANT_DOWN:\n    case NPCID_SIDE_PLANT:\n    // ban these types which set location to default location\n    case NPCID_LAVA_MONSTER:\n    case NPCID_LAVABUBBLE:\n    case NPCID_CONVEYOR:\n    case NPCID_BOSS_CASE:\n    case NPCID_BOSS_FRAGILE:\n        return true;\n    default:\n        return false;\n    }\n}\n\nbool NPCBansWings(const NPC_t& npc)\n{\n    if(s_TypeBansWings(npc.Type))\n        return true;\n\n    if(npc.Type == NPCID_ITEM_BUBBLE || npc.Type == NPCID_ITEM_BURIED)\n    {\n        if(s_TypeBansWings((NPCID)npc.Special))\n            return true;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/npc.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef NPC_H\n#define NPC_H\n\n#include \"location.h\"\n#include \"global_constants.h\"\n\nenum NPCID : vbint_t;\nstruct NPC_t;\nstruct Player_t;\n\n// Returns true if the game has been paused\nbool UpdateNPCs();\n// Public Sub DropBonus(A As Integer) 'Drops a bonus item that was held by the player\n// Drops a bonus item that was held by the player\nvoid DropBonus(int A);\n\n// EXTRA: After adding one new star, hide all over-star BGOs\nvoid CheckAfterStarTake(bool many = false);\n\n// EXTRA: all the logic for collecting a medal NPC\nvoid CollectMedal(const NPC_t& medal);\n// Public Sub TouchBonus(A As Integer, B As Integer) 'Code for dealing with bonus and player\n// Code for dealing with bonus and player\nvoid TouchBonus(int A, int B);\n// Public Sub NPCHit(A As Integer, B As Integer, Optional C As Integer = 0) 'For NPCs that were hit\n// For NPCs that were hit\nvoid NPCHit(int A, int B, int C = 0);\n// Public Sub KillNPC(A As Integer, B As Integer) 'Handles NPC deaths and death effects\n// Handles NPC deaths and death effects\n// Returns true if this should be resumed later\nbool KillNPC(int A, int B);\n// Public Sub CheckSectionNPC(A As Integer) 'find out what section the NPC is in\n// find out what section the NPC is in\nvoid CheckSectionNPC(int A);\n// Public Sub Deactivate(A As Integer) 'deactive and reset the NPC when it goes offscreen\n// deactive and reset the NPC when it goes offscreen\nvoid Deactivate(int A);\n// Public Sub Bomb(Location As Location, Game As Integer, Optional ImmunePlayer As Integer = 0) 'for bomb explosions\n// for bomb explosions\nvoid Bomb(Location_t Location, int Game, int ImmunePlayer = 0);\n// Public Sub DropNPC(A As Integer, NPCType As Integer)  'Drops an NPC from the screen\n// Drops an NPC from the screen (dead code, removed)\n// void DropNPC(int A, int NPCType);\n// Public Sub TurnNPCsIntoCoins() 'turns some NPCs into coins when the player reaches the level exit\n// turns some NPCs into coins when the player reaches the level exit\nvoid TurnNPCsIntoCoins();\n// Public Sub NPCFrames(A As Integer) 'updates the NPCs graphics\n// updates the NPCs graphics\nvoid NPCFrames(int A);\n// Public Sub SkullRide(A As Integer)\nvoid SkullRide(int A, bool reEnable = false, const Location_t *alignAt = nullptr);\n// Public Sub NPCSpecial(A As Integer)\n// called before movement is applied\nvoid NPCSpecial(int A);\n// Public Sub SpecialNPC(A As Integer)\n// called after movement is applied\nvoid SpecialNPC(int A);\n// Public Sub CharStuff(Optional WhatNPC As Integer = 0, Optional CheckEggs As Boolean = False)\nvoid CharStuff(int WhatNPC = 0, bool CheckEggs = false);\n// Public Function RandomBonus()\nNPCID RandomBonus();\n\n// totally new function, used in the Raft logic\nbool npcHasFloor(const struct NPC_t &npc);\n\n// totally new function, used in editor and medals-tracking logic\nbool NPCIsContainer(const NPC_t& npc);\nbool NPCNewContainerType(int Type);\n\n// NEW: release an NPC from burial -- only made because the call was duplicated in 3 places. If callsite is 0, the logic should be \"correct\".\nvoid NPCUnbury(int A, int Callsite);\n\n// totally new function, used in editor and level-loading\nbool NPCBansWings(const NPC_t& npc);\n\n// totally new function, used for compatibility (in compat mode, horizontal distance; in modern mode, squared Euclidean distance)\nnum_t NPCPlayerTargetDist(const NPC_t& npc, const Player_t& player);\n\n// totally new function covering old logic. returns nearest player (using NPCPlayerTargetDist) that is not Dead and is in NPC's section.\nint NPCTargetPlayer(const NPC_t& npc);\n\n// totally new function covering old logic. checks nearest player (using NPCTargetPlayer) then faces it.\nint NPCFaceNearestPlayer(NPC_t& npc, bool old_version = false);\n\n// totally new function covering old logic. sets SpeedY (based on SpeedX) such that the NPC is approaching a target location at a constant X speed.\nvoid NPCSetSpeedTarget_FixedX(NPC_t& npc, const Location_t& target, int speed_x, int speed_y_cap);\nvoid NPCSetSpeedTarget_FixedX(NPC_t& npc, num_t target_x, num_t target_y, int speed_x, int speed_y_cap);\n\n// totally new function covering old logic. sets SpeedY (based on SpeedX) such that the NPC is approaching a target location at a constant speed.\nvoid NPCSetSpeedTarget_FixedSpeed(NPC_t& npc, const Location_t& target, int speed);\n\n#endif // NPC_H\n"
  },
  {
    "path": "src/npc_constant_traits.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef NPC_CONSTANT_TRAITS_H\n#define NPC_CONSTANT_TRAITS_H\n\n#include \"globals.h\"\n\n// force-inline these definitions because they are often used in switch blocks depending only on NPC_t::Type\n// inlining allows the compiler to seek arithmetic patterns and to know that it is safe to reorder cases if desired\n// force-inlining should be used extremely sparingly but this only causes a 3kb codesize increase across the full application\n#if (defined(__GNUC__) || defined(__llvm__))\n#    define XT_FORCE_INLINE __attribute__((always_inline))\n#else\n#    define XT_FORCE_INLINE\n#endif\n\n// constant traits that determine NPC behavior based on Type\n// will be replaced with other mechanisms during the NPC function pointer update\n\n#if 0\n// Should turn into trait.\n//'Flags the NPC type if it is a shell\nconstexpr bool NPCIsAShell(int Type)\n{\n    return (\n        Type == 237 ||\n        Type == 113 ||\n        Type == 114 ||\n        Type == 115 ||\n        Type == 116 ||\n        Type == 172 ||\n        Type == 174 ||\n        Type == 195 ||\n        Type == 5 ||\n        Type == 7 ||\n        Type == 24 ||\n        Type == 73\n    );\n}\n\nconstexpr bool NPCIsAShell(const NPC_t& n)\n{\n    return NPCIsAShell(n.Type);\n}\n\n// Should turn into trait.\n//'Flags the NPC type if it is a bonus\nconstexpr bool NPCIsABonus(int Type)\n{\n    return (\n        Type == 254 ||\n        Type == 251 ||\n        Type == 252 ||\n        Type == 253 ||\n        Type == 250 ||\n        Type == 240 ||//32;\n        Type == 248 ||//32;\n        Type == 249 ||\n        Type == 274 ||\n        Type == 138 ||\n        Type == 152 ||\n        Type == 169 ||\n        Type == 170 ||\n        Type == 197 ||\n        Type == 182 || // SMB1 Flower\n        Type == 183 || // SMW Flower\n        Type == 184 || // SMB1 Mushroom\n        Type == 185 || // SMW Mushroom\n        Type == 186 || // SMB1 1-up\n        Type == 187 || // SMW 1-up\n        Type == 188 || // SMW 3 up\n        Type == 192 ||\n        Type == 178 ||\n        Type == 9 ||\n        Type == 273 ||\n        Type == 10 ||\n        Type == 11 ||\n        Type == 14 ||\n        Type == 264 ||\n        Type == 277 ||\n        Type == 16 ||\n        Type == 33 ||\n        Type == 258 ||\n        Type == 34 ||\n        Type == 41 ||\n        Type == 75 ||\n        Type == 88 ||\n        Type == 90 ||\n        Type == 94 ||\n        Type == 198 ||\n        Type == 97 ||\n        Type == 101 ||\n        Type == 102 ||\n        Type == 103 ||\n        Type == 107 ||\n        Type == 153 ||\n        Type == 196\n    );\n}\n\nconstexpr bool NPCIsABonus(const NPC_t& n)\n{\n    return NPCIsABonus(n.Type);\n}\n\n// Should turn into trait.\n//'Flags the NPC type if it is a coin\nconstexpr bool NPCIsACoin(int Type)\n{\n    return (\n        Type == 251 ||\n        Type == 252 ||\n        Type == 253 ||\n        Type == 274 ||\n        Type == 138 ||\n        Type == 152 ||\n        Type == 10 ||\n        Type == 33 ||\n        Type == 258 ||\n        Type == 88 ||\n        Type == 103\n    );\n}\n\nconstexpr bool NPCIsACoin(const NPC_t& n)\n{\n    return NPCIsACoin(n.Type);\n}\n\n// Should turn into trait.\n//'Flags the NPC type if it is a vine\nconstexpr bool NPCIsAVine(int Type)\n{\n    return (Type >= 213 && Type <= 224);\n}\n\nconstexpr bool NPCIsAVine(const NPC_t& n)\n{\n    return NPCIsAVine(n.Type);\n}\n#endif\n\n// OKAY TO KEEP AS FUNCTION. Usages outside of NPC methods should be replaced with new usage-specific NPC traits.\n//'Flags the NPC type if it is a level exit\nXT_FORCE_INLINE constexpr bool NPCIsAnExit(NPCID Type)\n{\n    return (\n        Type == NPCID_ITEMGOAL ||\n        Type == NPCID_GOALORB_S3 ||\n        Type == NPCID_GOALORB_S2 ||\n        Type == NPCID_STAR_EXIT ||\n        Type == NPCID_STAR_COLLECT ||\n        Type == NPCID_FLAG_EXIT\n    );\n}\n\nXT_FORCE_INLINE constexpr bool NPCIsAnExit(const NPC_t& n)\n{\n    return NPCIsAnExit(n.Type);\n}\n\n// OKAY TO KEEP AS FUNCTION. Usages in UpdatePlayer may become an OnPlayerCollide NPC method.\n//'Flags the NPC type as a para-troopa\nXT_FORCE_INLINE constexpr bool NPCIsAParaTroopa(NPCID Type)\n{\n    return (\n        Type == NPCID_FLY_FODDER_S3 ||\n        Type == NPCID_FLY_FODDER_S5 ||\n        Type == NPCID_GRN_FLY_TURTLE_S3 ||\n        Type == NPCID_RED_FLY_TURTLE_S3 ||\n        Type == NPCID_GRN_FLY_TURTLE_S4 ||\n        Type == NPCID_RED_FLY_TURTLE_S4 ||\n        Type == NPCID_BLU_FLY_TURTLE_S4 ||\n        Type == NPCID_YEL_FLY_TURTLE_S4 ||\n        Type == NPCID_GRN_FLY_TURTLE_S1 ||\n        Type == NPCID_RED_FLY_TURTLE_S1\n    );\n}\n\nXT_FORCE_INLINE constexpr bool NPCIsAParaTroopa(const NPC_t& n)\n{\n    return NPCIsAParaTroopa(n.Type);\n}\n\n// OKAY TO KEEP AS FUNCTION. Usages in UpdatePlayer may become an OnPlayerCollide NPC method, usages in Blocks should become a new flag.\n//'npc is a kurbo's shoe\nXT_FORCE_INLINE constexpr bool NPCIsBoot(NPCID Type)\n{\n    return (Type == NPCID_GRN_BOOT || Type == NPCID_RED_BOOT || Type == NPCID_BLU_BOOT);\n}\n\nXT_FORCE_INLINE constexpr bool NPCIsBoot(const NPC_t& n)\n{\n    return NPCIsBoot(n.Type);\n}\n\n// OKAY TO KEEP AS FUNCTION. Usages in UpdatePlayer may become an OnPlayerCollide NPC method, usages in Blocks should become a new flag.\n//'npc is a yoshi\nXT_FORCE_INLINE constexpr bool NPCIsYoshi(NPCID Type)\n{\n    return (\n        Type == NPCID_PET_GREEN ||\n        Type == NPCID_PET_BLUE ||\n        Type == NPCID_PET_YELLOW ||\n        Type == NPCID_PET_RED ||\n        Type == NPCID_PET_BLACK ||\n        Type == NPCID_PET_PURPLE ||\n        Type == NPCID_PET_PINK ||\n        Type == NPCID_PET_CYAN\n    );\n}\n\nXT_FORCE_INLINE constexpr bool NPCIsYoshi(const NPC_t& n)\n{\n    return NPCIsYoshi(n.Type);\n}\n\n// OKAY TO KEEP AS FUNCTION. Usages outside of NPC methods should be replaced with new usage-specific NPC traits.\n//'npc is a toad\nXT_FORCE_INLINE constexpr bool NPCIsToad(NPCID Type)\n{\n    return (\n        Type == NPCID_CIVILIAN_SCARED ||\n        Type == NPCID_CIVILIAN ||\n        Type == NPCID_CHAR3 ||\n        Type == NPCID_CHAR2 ||\n        Type == NPCID_CHAR5 ||\n        Type == NPCID_PINK_CIVILIAN\n    );\n}\n\nXT_FORCE_INLINE constexpr bool NPCIsToad(const NPC_t& n)\n{\n    return NPCIsToad(n.Type);\n}\n\n// OKAY TO KEEP AS FUNCTION. Usages outside of NPC methods should be replaced with new usage-specific NPC traits.\n//'Zelda 2 Bot monster\nXT_FORCE_INLINE constexpr bool NPCIsABot(NPCID Type)\n{\n    return (Type == NPCID_BLU_SLIME || Type == NPCID_CYAN_SLIME || Type == NPCID_RED_SLIME);\n}\n\nXT_FORCE_INLINE constexpr bool NPCIsABot(const NPC_t& n)\n{\n    return NPCIsABot(n.Type);\n}\n\n// OKAY TO KEEP AS FUNCTION. Unused outside of NPC methods.\n//'default NPC movement\nXT_FORCE_INLINE constexpr bool NPCDefaultMovement(NPCID Type)\n{\n    return (\n        Type == NPCID_FODDER_S5 ||\n        Type == NPCID_GRN_TURTLE_S4 ||\n        Type == NPCID_RED_TURTLE_S4 ||\n        Type == NPCID_BLU_TURTLE_S4 ||\n        Type == NPCID_YEL_TURTLE_S4 ||\n        Type == NPCID_GRN_HIT_TURTLE_S4 ||\n        Type == NPCID_RED_HIT_TURTLE_S4 ||\n        Type == NPCID_BLU_HIT_TURTLE_S4 ||\n        Type == NPCID_YEL_HIT_TURTLE_S4 ||\n        Type == NPCID_KNIGHT ||\n        Type == NPCID_BIRD ||\n        Type == NPCID_RED_SPIT_GUY ||\n        Type == NPCID_BLU_SPIT_GUY ||\n        Type == NPCID_GRY_SPIT_GUY ||\n        Type == NPCID_WALK_BOMB_S2 ||\n        Type == NPCID_WALK_BOMB_S3 ||\n        Type == NPCID_SKELETON ||\n        Type == NPCID_GRN_TURTLE_S1 ||\n        Type == NPCID_RED_TURTLE_S1 ||\n        Type == NPCID_BRUTE ||\n        Type == NPCID_CARRY_FODDER ||\n        Type == NPCID_FLY_CARRY_FODDER ||\n        Type == NPCID_ROCKET_WOOD ||\n        Type == NPCID_FODDER_S3 ||\n        Type == NPCID_RED_FODDER ||\n        Type == NPCID_RED_FLY_FODDER ||\n        Type == NPCID_GRN_TURTLE_S3 ||\n        Type == NPCID_RED_TURTLE_S3 ||\n        Type == NPCID_BLU_GUY ||\n        Type == NPCID_RED_GUY ||\n        Type == NPCID_STACKER ||\n        Type == NPCID_GLASS_TURTLE ||\n        Type == NPCID_UNDER_FODDER ||\n        Type == NPCID_SPIKY_S3 ||\n        Type == NPCID_SPIKY_S4 ||\n        Type == NPCID_TOOTHY ||\n        Type == NPCID_CRAB ||\n        Type == NPCID_EXT_TURTLE ||\n        Type == NPCID_YELSWITCH_FODDER ||\n        Type == NPCID_BLUSWITCH_FODDER ||\n        Type == NPCID_GRNSWITCH_FODDER ||\n        Type == NPCID_REDSWITCH_FODDER ||\n        Type == NPCID_BIG_FODDER ||\n        Type == NPCID_BIG_TURTLE ||\n        Type == NPCID_JUMPER_S4 ||\n        Type == NPCID_TANK_TREADS ||\n        Type == NPCID_FODDER_S1 ||\n        Type == NPCID_ITEM_BURIED ||\n        Type == NPCID_PINK_CIVILIAN ||\n        Type == NPCID_CARRY_BUDDY\n    );\n}\n\nXT_FORCE_INLINE constexpr bool NPCDefaultMovement(const NPC_t& n)\n{\n    return NPCDefaultMovement(n.Type);\n}\n\n// OKAY TO KEEP AS FUNCTION. Usages outside of NPC methods should be replaced with new usage-specific NPC traits.\n//'turnips\nXT_FORCE_INLINE constexpr bool NPCIsVeggie(NPCID Type)\n{\n    return Type == NPCID_VEGGIE_1 || (Type >= NPCID_VEGGIE_2 && Type <= NPCID_VEGGIE_RANDOM);\n}\n\nXT_FORCE_INLINE constexpr bool NPCIsVeggie(const NPC_t& n)\n{\n    return NPCIsVeggie(n.Type);\n}\n\n// factored out of block hit / UpdateGraphics code\nconstexpr inline bool NPCLongLife(NPCID Type)\n{\n    return (NPCIsYoshi(Type) || NPCIsBoot(Type) || Type == NPCID_POWER_S3\n        || Type == NPCID_FIRE_POWER_S3 || Type == NPCID_CANNONITEM || Type == NPCID_LIFE_S3\n        || Type == NPCID_POISON || Type == NPCID_STATUE_POWER || Type == NPCID_HEAVY_POWER || Type == NPCID_FIRE_POWER_S1\n        || Type == NPCID_FIRE_POWER_S4 || Type == NPCID_POWER_S1 || Type == NPCID_POWER_S4\n        || Type == NPCID_LIFE_S1 || Type == NPCID_LIFE_S4 || Type == NPCID_3_LIFE || Type == NPCID_FLIPPED_RAINBOW_SHELL\n        // || Type == NPCID_PLATFORM_S3 // exclusive to UpdateGraphics\n        // TheXTech-exclusive Types with long lives\n        || Type == NPCID_INVINCIBILITY_POWER || Type == NPCID_AQUATIC_POWER\n        || Type == NPCID_POLAR_POWER || Type == NPCID_CYCLONE_POWER || Type == NPCID_SHELL_POWER);\n}\n\n#endif // #ifndef NPC_CONSTANT_TRAITS_H\n"
  },
  {
    "path": "src/npc_effect.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef ENUMNPCEFFECT_HHH\n#define ENUMNPCEFFECT_HHH\n\n#include \"global_constants.h\"\n\nenum NPCEffect : vbint_t\n{\n    NPCEFF_NORMAL = 0,\n    NPCEFF_EMERGE_UP = 1,\n    NPCEFF_DROP_ITEM = 2,\n    NPCEFF_EMERGE_DOWN = 3,\n    NPCEFF_WARP = 4,\n    NPCEFF_PET_TONGUE = 5,\n    NPCEFF_PET_INSIDE = 6,\n    NPCEFF_WAITING = 8,\n    NPCEFF_ENCASED = 208,\n    NPCEFF_MAZE = 9,\n};\n\n\n#endif // ENUMNPCEFFECT_HHH\n"
  },
  {
    "path": "src/npc_id.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef ENUMNPCID_HHH\n#define ENUMNPCID_HHH\n\n#include \"global_constants.h\"\n\nenum NPCID : vbint_t\n{\n    NPCID_NULL = 0,\n\n    NPCID_FODDER_S3 = 1,\n    NPCID_RED_FODDER = 2,\n    NPCID_RED_FLY_FODDER = 3,\n    NPCID_GRN_TURTLE_S3 = 4,\n    NPCID_GRN_SHELL_S3 = 5,\n    NPCID_RED_TURTLE_S3 = 6,\n    NPCID_RED_SHELL_S3 = 7,\n    NPCID_PLANT_S3 = 8,\n    NPCID_POWER_S3 = 9,\n    NPCID_COIN_S3 = 10,\n\n    NPCID_ITEMGOAL = 11,\n    NPCID_LAVABUBBLE = 12,\n    NPCID_PLR_FIREBALL = 13,\n    NPCID_FIRE_POWER_S3 = 14,\n    NPCID_MINIBOSS = 15,\n    NPCID_GOALORB_S3 = 16,\n    NPCID_BULLET = 17,\n    NPCID_BIG_BULLET = 18,\n    NPCID_BLU_GUY = 19,\n    NPCID_RED_GUY = 20,\n\n    NPCID_CANNONENEMY = 21,\n    NPCID_CANNONITEM = 22,\n    NPCID_GLASS_TURTLE = 23,\n    NPCID_GLASS_SHELL = 24,\n    NPCID_JUMPER_S3 = 25,\n    NPCID_SPRING = 26,\n    NPCID_UNDER_FODDER = 27,\n    NPCID_RED_FISH_S1 = 28,\n    NPCID_HEAVY_THROWER = 29,\n    NPCID_HEAVY_THROWN = 30,\n\n    NPCID_KEY = 31,\n    NPCID_COIN_SWITCH = 32,\n    NPCID_COIN_S4 = 33,\n    NPCID_LEAF_POWER = 34,\n    NPCID_GRN_BOOT = 35,\n    NPCID_SPIKY_S3 = 36,\n    NPCID_STONE_S3 = 37,\n    NPCID_GHOST_S3 = 38,\n    NPCID_SPIT_BOSS = 39,\n    NPCID_SPIT_BOSS_BALL = 40,\n\n    NPCID_GOALORB_S2 = 41,\n    NPCID_GHOST_FAST = 42,\n    NPCID_GHOST_S4 = 43,\n    NPCID_BIG_GHOST = 44,\n    NPCID_SLIDE_BLOCK = 45,\n    NPCID_FALL_BLOCK_RED = 46,\n    NPCID_SPIKY_THROWER = 47,\n    NPCID_SPIKY_BALL_S3 = 48,\n    NPCID_TOOTHYPIPE = 49,\n    NPCID_TOOTHY = 50,\n\n    NPCID_BOTTOM_PLANT = 51,\n    NPCID_SIDE_PLANT = 52,\n    NPCID_CRAB = 53,\n    NPCID_FLY = 54,\n    NPCID_EXT_TURTLE = 55,\n    NPCID_VEHICLE = 56,\n    NPCID_CONVEYOR = 57,\n    NPCID_METALBARREL = 58,\n\n    NPCID_YELSWITCH_FODDER = 59,\n    NPCID_YEL_PLATFORM = 60,\n    NPCID_BLUSWITCH_FODDER = 61,\n    NPCID_BLU_PLATFORM = 62,\n    NPCID_GRNSWITCH_FODDER = 63,\n    NPCID_GRN_PLATFORM = 64,\n    NPCID_REDSWITCH_FODDER = 65,\n    NPCID_RED_PLATFORM = 66,\n\n    NPCID_HPIPE_SHORT = 67,\n    NPCID_HPIPE_LONG = 68,\n    NPCID_VPIPE_SHORT = 69,\n    NPCID_VPIPE_LONG = 70,\n\n    NPCID_BIG_FODDER = 71,\n    NPCID_BIG_TURTLE = 72,\n    NPCID_BIG_SHELL = 73,\n    NPCID_BIG_PLANT = 74,\n    NPCID_CIVILIAN_SCARED = 75,\n    NPCID_GRN_FLY_TURTLE_S3 = 76,\n    NPCID_JUMPER_S4 = 77,\n    NPCID_TANK_TREADS = 78,\n    NPCID_SHORT_WOOD = 79,\n    NPCID_LONG_WOOD = 80,\n\n    NPCID_SLANT_WOOD_L = 81,\n    NPCID_SLANT_WOOD_R = 82,\n    NPCID_SLANT_WOOD_M = 83,\n    NPCID_STATUE_S3 = 84,\n    NPCID_STATUE_FIRE = 85,\n    NPCID_VILLAIN_S3 = 86,\n    NPCID_VILLAIN_FIRE = 87,\n    NPCID_COIN_S1 = 88,\n    NPCID_FODDER_S1 = 89,\n    NPCID_LIFE_S3 = 90,\n\n    NPCID_ITEM_BURIED = 91,\n    NPCID_VEGGIE_1 = 92,\n    NPCID_PLANT_S1 = 93,\n    NPCID_CIVILIAN = 94,\n    NPCID_PET_GREEN = 95,\n    NPCID_ITEM_POD = 96,\n    NPCID_STAR_EXIT = 97,\n    NPCID_PET_BLUE = 98,\n    NPCID_PET_YELLOW = 99,\n    NPCID_PET_RED = 100,\n\n    NPCID_CHAR2 = 101,\n    NPCID_CHAR5 = 102,\n    NPCID_RED_COIN = 103,\n    NPCID_PLATFORM_S3 = 104,\n    NPCID_CHECKER_PLATFORM = 105,\n    NPCID_PLATFORM_S1 = 106,\n    NPCID_PINK_CIVILIAN = 107,\n    NPCID_PET_FIRE = 108,\n\n    NPCID_GRN_TURTLE_S4 = 109,\n    NPCID_RED_TURTLE_S4 = 110,\n    NPCID_BLU_TURTLE_S4 = 111,\n    NPCID_YEL_TURTLE_S4 = 112,\n\n    NPCID_GRN_SHELL_S4 = 113,\n    NPCID_RED_SHELL_S4 = 114,\n    NPCID_BLU_SHELL_S4 = 115,\n    NPCID_YEL_SHELL_S4 = 116,\n\n    NPCID_GRN_HIT_TURTLE_S4 = 117,\n    NPCID_RED_HIT_TURTLE_S4 = 118,\n    NPCID_BLU_HIT_TURTLE_S4 = 119,\n    NPCID_YEL_HIT_TURTLE_S4 = 120,\n\n    NPCID_GRN_FLY_TURTLE_S4 = 121,\n    NPCID_RED_FLY_TURTLE_S4 = 122,\n    NPCID_BLU_FLY_TURTLE_S4 = 123,\n    NPCID_YEL_FLY_TURTLE_S4 = 124,\n\n    NPCID_KNIGHT = 125,\n    NPCID_BLU_SLIME = 126,\n    NPCID_CYAN_SLIME = 127,\n    NPCID_RED_SLIME = 128,\n\n    NPCID_BIRD = 129,\n    NPCID_RED_SPIT_GUY = 130,\n    NPCID_BLU_SPIT_GUY = 131,\n    NPCID_GRY_SPIT_GUY = 132,\n\n    NPCID_SPIT_GUY_BALL = 133,\n    NPCID_BOMB = 134,\n\n    NPCID_WALK_BOMB_S2 = 135,\n    NPCID_WALK_BOMB_S3 = 136,\n    NPCID_LIT_BOMB_S3 = 137,\n    NPCID_COIN_S2 = 138,\n\n    NPCID_VEGGIE_2 = 139,\n    NPCID_VEGGIE_3 = 140,\n    NPCID_VEGGIE_4 = 141,\n    NPCID_VEGGIE_5 = 142,\n    NPCID_VEGGIE_6 = 143,\n    NPCID_VEGGIE_7 = 144,\n    NPCID_VEGGIE_8 = 145,\n    NPCID_VEGGIE_9 = 146,\n    NPCID_VEGGIE_RANDOM = 147,\n\n    NPCID_PET_BLACK = 148,\n    NPCID_PET_PURPLE = 149,\n    NPCID_PET_PINK = 150,\n\n    NPCID_SIGN = 151,\n    NPCID_RING = 152,\n    NPCID_POISON = 153,\n    NPCID_CARRY_BLOCK_A = 154,\n    NPCID_CARRY_BLOCK_B = 155,\n    NPCID_CARRY_BLOCK_C = 156,\n    NPCID_CARRY_BLOCK_D = 157,\n    NPCID_CARRY_BUDDY = 158,\n    NPCID_LIFT_SAND = 159,\n    NPCID_ROCKET_WOOD = 160,\n\n    NPCID_RED_FLY_TURTLE_S3 = 161,\n    NPCID_BRUTE = 162,\n    NPCID_BRUTE_SQUISHED = 163,\n    NPCID_BIG_GUY = 164,\n    NPCID_CARRY_FODDER = 165,\n    NPCID_HIT_CARRY_FODDER = 166,\n    NPCID_FLY_CARRY_FODDER = 167,\n    NPCID_CHASER = 168,\n\n    NPCID_STATUE_POWER = 169,\n    NPCID_HEAVY_POWER = 170,\n    NPCID_PLR_HEAVY = 171,\n\n    NPCID_GRN_SHELL_S1 = 172,\n    NPCID_GRN_TURTLE_S1 = 173,\n    NPCID_RED_SHELL_S1 = 174,\n    NPCID_RED_TURTLE_S1 = 175,\n    NPCID_GRN_FLY_TURTLE_S1 = 176,\n    NPCID_RED_FLY_TURTLE_S1 = 177,\n\n    NPCID_AXE = 178,\n    NPCID_SAW = 179,\n\n    NPCID_STONE_S4 = 180,\n\n    NPCID_STATUE_S4 = 181,\n    NPCID_FIRE_POWER_S1 = 182,\n    NPCID_FIRE_POWER_S4 = 183,\n    NPCID_POWER_S1 = 184,\n    NPCID_POWER_S4 = 185,\n    NPCID_LIFE_S1 = 186,\n    NPCID_LIFE_S4 = 187,\n    NPCID_3_LIFE = 188,\n    NPCID_SKELETON = 189,\n    NPCID_RAFT = 190,\n\n    NPCID_RED_BOOT = 191,\n    NPCID_CHECKPOINT = 192,\n    NPCID_BLU_BOOT = 193,\n    NPCID_RAINBOW_SHELL = 194,\n    NPCID_FLIPPED_RAINBOW_SHELL = 195,\n    NPCID_STAR_COLLECT = 196,\n    NPCID_GOALTAPE = 197,\n    NPCID_CHAR3 = 198,\n    NPCID_LAVA_MONSTER = 199,\n    NPCID_VILLAIN_S1 = 200,\n\n    NPCID_SICK_BOSS = 201,\n    NPCID_SICK_BOSS_BALL = 202,\n\n    NPCID_FLIER = 203,\n    NPCID_ROCKET_FLIER = 204,\n    NPCID_WALL_BUG = 205,\n    NPCID_WALL_SPARK = 206,\n    NPCID_WALL_TURTLE = 207,\n    NPCID_BOSS_CASE = 208,\n    NPCID_BOSS_FRAGILE = 209,\n    NPCID_HOMING_BALL = 210,\n\n    NPCID_HOMING_BALL_GEN = 211,\n    NPCID_FALL_BLOCK_BROWN = 212,\n    NPCID_GRN_VINE_S3 = 213,\n    NPCID_RED_VINE_S3 = 214,\n    NPCID_GRN_VINE_S2 = 215,\n    NPCID_YEL_VINE_S2 = 216,\n    NPCID_BLU_VINE_S2 = 217,\n    NPCID_GRN_VINE_BASE_S2 = 218,\n    NPCID_YEL_VINE_BASE_S2 = 219,\n    NPCID_BLU_VINE_BASE_S2 = 220,\n\n    NPCID_LADDER = 221,\n    NPCID_GRN_VINE_S1 = 222,\n    NPCID_GRN_VINE_TOP_S1 = 223,\n    NPCID_GRN_VINE_S4 = 224,\n    NPCID_RED_VINE_TOP_S3 = 225,\n    NPCID_GRN_VINE_TOP_S3 = 226,\n    NPCID_GRN_VINE_TOP_S4 = 227,\n    NPCID_PET_CYAN = 228,\n    NPCID_GRN_FISH_S3 = 229,\n    NPCID_RED_FISH_S3 = 230,\n\n    NPCID_SQUID_S3 = 231,\n    NPCID_GRN_FISH_S4 = 232,\n    NPCID_GRN_FISH_S1 = 233,\n    NPCID_BONE_FISH = 234,\n    NPCID_SQUID_S1 = 235,\n    NPCID_YEL_FISH_S4 = 236,\n    NPCID_ICE_BLOCK = 237,\n    NPCID_TIME_SWITCH = 238,\n    NPCID_TNT = 239,\n    NPCID_TIMER_S2 = 240,\n\n    NPCID_EARTHQUAKE_BLOCK = 241,\n    NPCID_FODDER_S5 = 242,\n    NPCID_FLY_FODDER_S5 = 243,\n    NPCID_FLY_FODDER_S3 = 244,\n    NPCID_FIRE_PLANT = 245,\n    NPCID_PLANT_FIREBALL = 246,\n    NPCID_STACKER = 247,\n    NPCID_TIMER_S3 = 248,\n    NPCID_POWER_S2 = 249,\n    NPCID_POWER_S5 = 250,\n\n    NPCID_GEM_1 = 251,\n    NPCID_GEM_5 = 252,\n    NPCID_GEM_20 = 253,\n    NPCID_FLY_POWER = 254,\n    NPCID_LOCK_DOOR = 255,\n    NPCID_LONG_PLANT_UP = 256,\n    NPCID_LONG_PLANT_DOWN = 257,\n    NPCID_COIN_5 = 258,\n    NPCID_FIRE_DISK = 259,\n    NPCID_FIRE_CHAIN = 260,\n\n    NPCID_WALK_PLANT = 261,\n    NPCID_BOMBER_BOSS = 262,\n    NPCID_ICE_CUBE = 263,\n    NPCID_ICE_POWER_S3 = 264,\n    NPCID_PLR_ICEBALL = 265,\n    NPCID_SWORDBEAM = 266,\n    NPCID_MAGIC_BOSS = 267,\n    NPCID_MAGIC_BOSS_SHELL = 268,\n    NPCID_MAGIC_BOSS_BALL = 269,\n    NPCID_JUMP_PLANT = 270,\n\n    NPCID_BAT = 271,\n    NPCID_VINE_BUG = 272,\n    NPCID_SWAP_POWER = 273,\n    NPCID_MEDAL = 274,\n    NPCID_QUAD_SPITTER = 275,\n    NPCID_QUAD_BALL = 276,\n    NPCID_ICE_POWER_S4 = 277,\n    NPCID_FLY_BLOCK = 278,\n    NPCID_FLY_CANNON = 279,\n\n    NPCID_FIRE_BOSS = 280,\n    NPCID_FIRE_BOSS_SHELL = 281,\n    NPCID_FIRE_BOSS_FIRE = 282,\n    NPCID_ITEM_BUBBLE = 283,\n    NPCID_ITEM_THROWER = 284,\n    NPCID_SPIKY_S4 = 285,\n    NPCID_SPIKY_BALL_S4 = 286,\n    NPCID_RANDOM_POWER = 287,\n    NPCID_DOOR_MAKER = 288,\n    NPCID_MAGIC_DOOR = 289,\n    NPCID_COCKPIT = 290,\n\n    NPCID_CHAR3_HEAVY = 291,\n    NPCID_CHAR4_HEAVY = 292,\n\n    // SMBX 1.3 custom NPC types\n    NPCID_RESERVED_293 = 293,\n    NPCID_RESERVED_294 = 294,\n    NPCID_RESERVED_295 = 295,\n    NPCID_RESERVED_296 = 296,\n    NPCID_RESERVED_297 = 297,\n    NPCID_RESERVED_298 = 298,\n    NPCID_RESERVED_299 = 299,\n    NPCID_RESERVED_300 = 300,\n\n    // TheXTech-exclusive NPCs\n    NPCID_INVINCIBILITY_POWER = 301,\n    NPCID_AQUATIC_POWER = 302,\n    NPCID_POLAR_POWER = 303,\n    NPCID_CYCLONE_POWER = 304,\n    NPCID_SHELL_POWER = 305,\n    NPCID_FLAG_EXIT = 306,\n};\n\n\n#endif // ENUMNPCID_HHH\n"
  },
  {
    "path": "src/npc_special_data.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n// the purpose of this header is define the configurable\n// Variant values for NPCs in the PGE-X standard.\n\n// these are generally used in TheXTech to indicate unique\n// modifications to the NPC's behavior / AI.\n\n#pragma once\n#ifndef NPC_SPECIAL_DATA_H\n#define NPC_SPECIAL_DATA_H\n\n#include <cstddef>\n#include \"npc_id.h\"\n\n// the first value listed is assumed to be the modern default.\n// the string array must be nullptr-terminated.\nstruct NPC_Variant_Data_t\n{\n    // strings to describe behaviors in the editor\n    // may be two lines of up to 7 characters each\n    // (a sequence of constant pointers to constant characters)\n    // MUST have an extra nullptr at the end\n    const char* const * strings;\n\n    // initial Variant values for each behavior\n    const uint8_t* values;\n\n    // first version of VB6 game that did NOT use a legacy behavior\n    // special values:\n    // 66: modern SMBX (forwards compatible, includes any future experiments)\n    // 65: SMBX64\n    // -1: modern TheXTech\n    // -2: X38A default behavior\n    // -3: SMBX2 default behavior\n    const int* active_below;\n\n    // returns the limit i (with strings[i] == nullptr) if not found\n    inline size_t find_current(uint8_t value) const\n    {\n        size_t i;\n\n        for(i = 0; strings[i] != nullptr; i++)\n        {\n            if(values[i] == value)\n                return i;\n        }\n\n        return i;\n    }\n\n    // returns the limit i (with strings[i] == nullptr) if not found\n    inline size_t find_legacy(int version) const\n    {\n        size_t earliest_behavior = 0;\n\n        size_t i;\n\n        for(i = 1; strings[i] != nullptr; i++)\n        {\n            if(version < active_below[i]\n                && active_below[i] < active_below[earliest_behavior])\n            {\n                earliest_behavior = i;\n            }\n        }\n\n        // return limit i if none of the versions are above the current version\n        if(earliest_behavior == 0 && version >= active_below[earliest_behavior])\n            return i;\n\n        return earliest_behavior;\n    }\n};\n\n// when multiple NPC types share the same NPC_Variant, we don't\n//   need to reinitialize Variant when switching between them\nstruct NPC_Variant_Map_t\n{\n    int Type;\n    const NPC_Variant_Data_t* data;\n};\n\n// NPCID_CANNONITEM // billy gun\nconstexpr const char* cannon_strings[] = {\"1.2.1+\", \"1.2.0\", \"Pre-1.2\", nullptr};\nconstexpr uint8_t cannon_values[] = {0, 1, 2};\nconstexpr int cannon_active_below[] = {66, 51, 28};\nconstexpr NPC_Variant_Data_t Variant_cannon = {cannon_strings, cannon_values, cannon_active_below};\n\n// NPCID_STONE_S3\nconstexpr const char* thwomp_strings[] = {\"Modern\", \"Fall\\nAlways\", nullptr};\nconstexpr uint8_t thwomp_values[] = {0, 1};\nconstexpr int thwomp_active_below[] = {66, 9};\nconstexpr NPC_Variant_Data_t Variant_thwomp = {thwomp_strings, thwomp_values, thwomp_active_below};\n\n// NPCID_VILLAIN_S3\nconstexpr const char* bowser3_strings[] = {\"Modern\", \"Section\\nExpand\", nullptr};\nconstexpr uint8_t bowser3_values[] = {0, 1};\nconstexpr int bowser3_active_below[] = {66, 30};\nconstexpr NPC_Variant_Data_t Variant_bowser3 = {bowser3_strings, bowser3_values, bowser3_active_below};\n\n// NPCID_YEL_PLATFORM, NPCID_BLU_PLATFORM, NPCID_GRN_PLATFORM, NPCID_RED_PLATFORM, NPCID_PLATFORM_S3, NPCID_SAW\nconstexpr const char* platform_strings[] = {\"XTech\", \"1.3\", \"Pre-1.2\", nullptr};\nconstexpr uint8_t platform_values[] = {2, 0, 1};\nconstexpr int platform_active_below[] = {-1, 66, 30};\nconstexpr NPC_Variant_Data_t Variant_platform = {platform_strings, platform_values, platform_active_below};\n\nconstexpr NPC_Variant_Map_t NPC_Variant_map[] =\n{\n    {NPCID_CANNONITEM, &Variant_cannon},\n    {NPCID_STONE_S3, &Variant_thwomp},\n    {NPCID_VILLAIN_S3, &Variant_bowser3},\n    {NPCID_YEL_PLATFORM, &Variant_platform},\n    {NPCID_BLU_PLATFORM, &Variant_platform},\n    {NPCID_GRN_PLATFORM, &Variant_platform},\n    {NPCID_RED_PLATFORM, &Variant_platform},\n    {NPCID_PLATFORM_S3, &Variant_platform},\n    {NPCID_SAW, &Variant_platform},\n};\n\nconstexpr size_t NPC_Variant_count = sizeof(NPC_Variant_map) / sizeof(NPC_Variant_Map_t);\n\ninline const NPC_Variant_Data_t* find_Variant_Data(int Type)\n{\n    for(size_t i = 0; i < NPC_Variant_count; i++)\n    {\n        if(NPC_Variant_map[i].Type == Type)\n            return NPC_Variant_map[i].data;\n    }\n    return nullptr;\n}\n\ninline uint8_t find_modern_Variant(int Type)\n{\n    const NPC_Variant_Data_t* data = find_Variant_Data(Type);\n\n    if(!data)\n        return 0;\n\n    return data->values[0];\n}\n\ninline uint8_t find_legacy_Variant(int Type, int version)\n{\n    const NPC_Variant_Data_t* data = find_Variant_Data(Type);\n\n    if(!data)\n        return 0;\n\n    size_t legacy_index = data->find_legacy(version);\n\n    if(data->strings[legacy_index] == nullptr)\n        return 0;\n\n    return data->values[legacy_index];\n}\n\n#endif // NPC_SPECIAL_DATA_H\n"
  },
  {
    "path": "src/npc_traits.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef NPC_TRAITS_H\n#define NPC_TRAITS_H\n\n#include <cstdint>\n\n#include \"range_arr.hpp\"\n\n#include \"globals.h\"\n#include \"npc_constant_traits.h\"\n\nstruct NPCTraits_t\n{\n    //Public NPCFrameOffsetX(0 To maxNPCType) As Integer 'NPC frame offset X\n    int16_t FrameOffsetX = 0;\n    //Public NPCFrameOffsetY(0 To maxNPCType) As Integer 'NPC frame offset Y\n    int16_t FrameOffsetY = 0;\n    //Public NPCWidth(0 To maxNPCType) As Integer 'NPC width\n    int16_t TWidth = 32;\n    //Public NPCHeight(0 To maxNPCType) As Integer 'NPC height\n    int16_t THeight = 32;\n    //Public NPCWidthGFX(0 To maxNPCType) As Integer 'NPC gfx width\n    int16_t WidthGFX = 0;\n    //Public NPCHeightGFX(0 To maxNPCType) As Integer 'NPC gfx height\n    int16_t HeightGFX = 0;\n    //Public NPCSpeedvar(0 To maxNPCType) As Single 'NPC Speed Change\n    numf_t Speedvar = 1;\n\n    //Public NPCIsABlock(0 To maxNPCType) As Boolean 'Flag NPC as a block\n    bool IsABlock = false;\n    //Public NPCIsAHit1Block(0 To maxNPCType) As Boolean 'Flag NPC as a hit1 block\n    bool IsAHit1Block = false;\n    //Public NPCJumpHurt(0 To maxNPCType) As Boolean 'Hurts the player even if it jumps on the NPC\n    bool JumpHurt = false;\n    //Public NPCNoClipping(0 To maxNPCType) As Boolean 'NPC can go through blocks\n    bool NoClipping = false;\n    //Public NPCScore(0 To maxNPCType) As Integer 'NPC score value\n    int8_t Score = 2;\n    //Public NPCCanWalkOn(0 To maxNPCType) As Boolean  'NPC can be walked on\n    bool CanWalkOn = false;\n    //Public NPCGrabFromTop(0 To maxNPCType) As Boolean  'NPC can be grabbed from the top\n    bool GrabFromTop = false;\n    //Public NPCTurnsAtCliffs(0 To maxNPCType) As Boolean  'NPC turns around at cliffs\n    bool TurnsAtCliffs = false;\n    //Public NPCWontHurt(0 To maxNPCType) As Boolean  'NPC wont hurt the player\n    bool WontHurt = false;\n    //Public NPCMovesPlayer(0 To maxNPCType) As Boolean 'Player can not walk through the NPC\n    bool MovesPlayer = false;\n    //Public NPCStandsOnPlayer(0 To maxNPCType) As Boolean 'for the clown car\n    bool StandsOnPlayer = false;\n    //Public NPCIsGrabbable(0 To maxNPCType) As Boolean 'Player can grab the NPC\n    bool IsGrabbable = false;\n    //Public NPCNoYoshi(0 To maxNPCType) As Boolean 'Player can't eat the NPC\n    bool NoYoshi = false;\n    //Public NPCForeground(0 To maxNPCType) As Boolean 'draw the npc in front\n    bool Foreground = false;\n    //Public NPCNoFireBall(0 To maxNPCType) As Boolean 'not hurt by fireball\n    bool NoFireBall = false;\n    //Public NPCNoIceBall(0 To maxNPCType) As Boolean 'not hurt by fireball\n    bool NoIceBall = false;\n    //Public NPCNoGravity(0 To maxNPCType) As Boolean 'not affected by gravity\n    bool NoGravity = false;\n\n    //Public NPCFrame(0 To maxNPCType) As Integer\n    int16_t TFrames = 0;\n    //Public NPCFrameSpeed(0 To maxNPCType) As Integer\n    int16_t FrameSpeed = 8;\n    //Public NPCFrameStyle(0 To maxNPCType) As Integer\n    int16_t FrameStyle = 0;\n\n    // Uses fish AI. Redigit's comment: 'Flags the NPC type as a cheep cheep\n    bool IsFish : 1;\n    //'Flags the NPC type if it is a coin\n    bool IsACoin : 1;\n    //'Flags the NPC type if it is a bonus\n    bool IsABonus : 1;\n    //'Flags the NPC type if it is a vine\n    bool IsAVine : 1;\n    //'Flags the NPC type if it is a shell\n    bool IsAShell : 1;\n\n    //NEW: does the NPC require the canonical activation zone?\n    bool UseDefaultCam : 1;\n\n    enum InactiveRender_t\n    {\n        SHADE = 0,   // shades NPC until it becomes active\n        SHOW_ALWAYS, // always show the NPC (including animation)\n        SHOW_STATIC, // always show the NPC (without animation)\n        SKIP,        // skips NPC's render (assume it handles own intro animation from invisibility)\n        SMOKE,       // hides the NPC and reveals it with a EFFID_SMOKE_S3 effect\n    };\n\n    //NEW: how should the NPC render when inactive?\n    InactiveRender_t InactiveRender : 3;\n\n    constexpr NPCTraits_t() : IsFish(false), IsACoin(false), IsABonus(false), IsAVine(false), IsAShell(false), UseDefaultCam(false), InactiveRender(SHADE) {}\n};\n\nextern RangeArr<NPCTraits_t, 0, maxNPCType> NPCTraits;\n\ninline const NPCTraits_t* NPC_t::operator->() const\n{\n    return &NPCTraits[Type];\n}\n\n\n// some read-only accessors\ninline int16_t NPCHeight(int Type)\n{\n    return NPCTraits[Type].THeight;\n}\n\ninline int16_t NPCWidth(int Type)\n{\n    return NPCTraits[Type].TWidth;\n}\n\ninline int16_t NPCHeightGFX(int Type)\n{\n    return NPCTraits[Type].HeightGFX;\n}\n\ninline int16_t NPCWidthGFX(int Type)\n{\n    return NPCTraits[Type].WidthGFX;\n}\n\ninline int16_t NPCFrameOffsetX(int Type)\n{\n    return NPCTraits[Type].FrameOffsetX;\n}\n\ninline int16_t NPCFrameOffsetY(int Type)\n{\n    return NPCTraits[Type].FrameOffsetY;\n}\n\ninline bool NPCNoYoshi(int Type)\n{\n    return NPCTraits[Type].NoYoshi;\n}\n\ninline bool Block_t::tempBlockNoProjClipping() const\n{\n    if(tempBlockNpcType <= 0)\n        return false;\n\n    // cross-ref NPC temp block creation code in UpdateNPCs\n    const NPCTraits_t& npc_tr = NPCTraits[tempBlockNpcType];\n    return (npc_tr.CanWalkOn && !npc_tr.IsAHit1Block && !npc_tr.IsABlock);\n}\n\ninline vbint_t NPC_t::coinSwitchBlockType() const\n{\n    if(NPCTraits[this->DefaultType].IsACoin)\n        return this->Damage;\n\n    return 0;\n}\n\n#endif // #ifndef NPC_TRAITS_H\n"
  },
  {
    "path": "src/phys_env.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"collision.h\"\n#include \"blocks.h\"\n#include \"layers.h\"\n#include \"npc_traits.h\"\n\n#include \"main/trees.h\"\n\n#include \"phys_env.h\"\n\nvoid PhysEnv_Maze(Location_t& loc, vbint_t& maze_index, uint8_t& maze_state, int npc_A, int plr_A, int speedvar, std::array<bool, 4> controls)\n{\n    // check if on same page with water\n    if(maze_index < 1 || maze_index > numWater || Water[maze_index].Hidden)\n    {\n        maze_index = 0;\n        return;\n    }\n\n    Water_t& cur_maze = Water[maze_index];\n\n    num_t layer_speed_x = (num_t)Layer[cur_maze.Layer].ApplySpeedX;\n    num_t layer_speed_y = (num_t)Layer[cur_maze.Layer].ApplySpeedY;\n\n    loc.X += layer_speed_x;\n    loc.Y += layer_speed_y;\n    loc.SpeedX -= layer_speed_x;\n    loc.SpeedY -= layer_speed_y;\n\n    int direction = maze_state % 4;\n    bool cleared_to_exit = maze_state & MAZE_CAN_EXIT;\n    bool do_cancel = false;\n\n    num_t center_x = loc.X + loc.Width / 2;\n    num_t center_y = loc.Y + loc.Height / 2;\n\n    num_t target_speed_x = 0;\n    num_t target_speed_y = 0;\n\n    num_t space_left = 0;\n    num_t space_to_leave = 0;\n    num_t space_to_cancel = 0;\n\n    num_t exit_check_x, exit_check_y;\n\n    // direction logic\n    if(direction == MAZE_DIR_UP || direction == MAZE_DIR_DOWN)\n    {\n        if(direction == MAZE_DIR_UP)\n        {\n            exit_check_y = cur_maze.Location.Y;\n            space_left = center_y - exit_check_y;\n            target_speed_y = -4;\n        }\n        else\n        {\n            exit_check_y = cur_maze.Location.Y + cur_maze.Location.Height;\n            space_left = exit_check_y - center_y;\n            target_speed_y = 4;\n        }\n\n        space_to_cancel = cur_maze.Location.Height;\n        space_to_leave = loc.Height / 2;\n\n        num_t maze_center_x = cur_maze.Location.X + cur_maze.Location.Width / 2;\n        exit_check_x = maze_center_x;\n        target_speed_x = (maze_center_x - center_x) / 8;\n\n        if(num_t::abs(target_speed_x) > 8_n)\n        {\n            do_cancel = true;\n            target_speed_x = 0;\n        }\n        else if(num_t::abs(target_speed_x) < 0.25_n)\n        {\n            loc.X = maze_center_x - loc.Width / 2;\n            loc.SpeedX = 0;\n            target_speed_x = 0;\n        }\n    }\n    else\n    {\n        if(direction == MAZE_DIR_LEFT)\n        {\n            exit_check_x = cur_maze.Location.X;\n            space_left = center_x - exit_check_x;\n            target_speed_x = -4;\n        }\n        else\n        {\n            exit_check_x = cur_maze.Location.X + cur_maze.Location.Width;\n            space_left = exit_check_x - center_x;\n            target_speed_x = 4;\n        }\n\n        space_to_cancel = cur_maze.Location.Width;\n        space_to_leave = loc.Width / 2;\n\n        num_t maze_center_y = cur_maze.Location.Y + cur_maze.Location.Height / 2;\n        exit_check_y = maze_center_y;\n\n        if(plr_A && Player[plr_A].Mount == 3)\n            maze_center_y += 8;\n\n        target_speed_y = (maze_center_y - center_y) / 8;\n\n        if(num_t::abs(target_speed_y) > 8_n)\n        {\n            do_cancel = true;\n            target_speed_y = 0;\n        }\n        else if(num_t::abs(target_speed_y) < 0.25_n)\n        {\n            loc.Y = maze_center_y - loc.Height / 2;\n            loc.SpeedY = 0;\n            target_speed_y = 0;\n        }\n    }\n\n    if(space_left > space_to_cancel + 128 || space_left < -128 || do_cancel)\n    {\n        do_cancel = true;\n        exit_check_x = center_x;\n        exit_check_y = center_y;\n    }\n\n    if(cleared_to_exit && !do_cancel)\n    {\n        if(space_left < -space_to_leave)\n        {\n            maze_index = 0;\n            return;\n        }\n\n        if(direction == MAZE_DIR_UP)\n            target_speed_y *= 2;\n    }\n    else if(space_left < 32 || do_cancel)\n    {\n        // need to find a new direction\n        Location_t edgeLoc;\n        edgeLoc.X = exit_check_x - 15 + target_speed_x * 4;\n        edgeLoc.Y = exit_check_y - 15 + target_speed_y * 4;\n        edgeLoc.Width = 30;\n        edgeLoc.Height = 30;\n\n        cleared_to_exit = true;\n\n        for(int B : treeBlockQuery(edgeLoc, SORTMODE_NONE))\n        {\n            const Block_t& b = Block[B];\n\n            if((!npc_A || b.tempBlockNpcIdx != npc_A) && (!plr_A || !BlockCheckPlayerFilter(B, plr_A)) && !b.Hidden && !b.Invis && !BlockOnlyHitspot1[b.Type] && !BlockIsSizable[b.Type] && !BlockNoClipping[b.Type])\n            {\n                if(CheckCollision(edgeLoc, b.Location))\n                {\n                    cleared_to_exit = false;\n                    break;\n                }\n            }\n        }\n\n        if(!npc_A && cleared_to_exit)\n        {\n            for(int N : treeNPCQuery(edgeLoc, SORTMODE_NONE))\n            {\n                const NPC_t& n = NPC[N];\n\n                if(n.Active && !n.Generator && n->IsABlock && (!plr_A || N != Player[plr_A].HoldingNPC))\n                {\n                    if(CheckCollision(edgeLoc, n.Location))\n                    {\n                        cleared_to_exit = false;\n                        break;\n                    }\n                }\n            }\n        }\n\n        if(cleared_to_exit && !do_cancel)\n            maze_state |= MAZE_CAN_EXIT;\n        else\n        {\n            int new_maze[4] = {0, 0, 0, 0};\n\n            edgeLoc.X = exit_check_x - 15;\n            edgeLoc.Y = exit_check_y - 15;\n\n            for(int W : treeWaterQuery(edgeLoc, SORTMODE_NONE))\n            {\n                const Water_t& w = Water[W];\n\n                if(!w.Hidden && w.Type == PHYSID_MAZE)\n                {\n                    if(CheckCollision(edgeLoc, w.Location))\n                    {\n                        if(w.Location.Height > w.Location.Width)\n                        {\n                            if(w.Location.Y <= exit_check_y - 32 && new_maze[MAZE_DIR_UP] < W)\n                                new_maze[MAZE_DIR_UP] = W;\n                            if(w.Location.Y + w.Location.Height >= exit_check_y + 32 && new_maze[MAZE_DIR_DOWN] < W)\n                                new_maze[MAZE_DIR_DOWN] = W;\n                        }\n                        else\n                        {\n                            if(w.Location.X <= exit_check_x - 32 && new_maze[MAZE_DIR_LEFT] < W)\n                                new_maze[MAZE_DIR_LEFT] = W;\n                            if(w.Location.X + w.Location.Width >= exit_check_x + 32 && new_maze[MAZE_DIR_RIGHT] < W)\n                                new_maze[MAZE_DIR_RIGHT] = W;\n                        }\n                    }\n                }\n            }\n\n            // if things are normal, return to the same maze segment if the other directions are blocked\n            if(!do_cancel)\n                new_maze[direction ^ MAZE_DIR_FLIP_BIT] = maze_index;\n\n            bool random_choice = iRand(2);\n            bool choice_by_control = false;\n\n            // don't allow player to choose to go backwards\n            controls[direction ^ MAZE_DIR_FLIP_BIT] = false;\n\n            for(int i = 0; i < 4; i++)\n            {\n                if(controls[i] && new_maze[i])\n                {\n                    maze_state = i;\n                    choice_by_control = true;\n                    break;\n                }\n            }\n\n            if(choice_by_control)\n            {\n                // already have a new state\n            }\n            else if(new_maze[direction])\n                maze_state = direction;\n            else if(direction == MAZE_DIR_UP || direction == MAZE_DIR_DOWN)\n            {\n                if(new_maze[MAZE_DIR_LEFT] && new_maze[MAZE_DIR_RIGHT])\n                    maze_state = (random_choice) ? MAZE_DIR_LEFT : MAZE_DIR_RIGHT;\n                else if(new_maze[MAZE_DIR_LEFT])\n                    maze_state = MAZE_DIR_LEFT;\n                else if(new_maze[MAZE_DIR_RIGHT])\n                    maze_state = MAZE_DIR_RIGHT;\n                else\n                    maze_state = (direction ^ MAZE_DIR_FLIP_BIT);\n            }\n            else\n            {\n                if(new_maze[MAZE_DIR_UP] && new_maze[MAZE_DIR_DOWN])\n                    maze_state = (random_choice) ? MAZE_DIR_UP : MAZE_DIR_DOWN;\n                else if(new_maze[MAZE_DIR_UP])\n                    maze_state = MAZE_DIR_UP;\n                else if(new_maze[MAZE_DIR_DOWN])\n                    maze_state = MAZE_DIR_DOWN;\n                else\n                    maze_state = (direction ^ MAZE_DIR_FLIP_BIT);\n            }\n\n            maze_index = new_maze[maze_state];\n        }\n    }\n\n    target_speed_x *= speedvar;\n    target_speed_y *= speedvar;\n    target_speed_x /= 4;\n    target_speed_y /= 4;\n\n    loc.SpeedX = loc.SpeedX / 2 + target_speed_x / 2;\n    loc.SpeedY = loc.SpeedY / 2 + target_speed_y / 2;\n\n    loc.X += loc.SpeedX;\n    loc.Y += loc.SpeedY;\n\n    loc.SpeedX += (num_t)Layer[cur_maze.Layer].SpeedX;\n    loc.SpeedY += (num_t)Layer[cur_maze.Layer].SpeedY;\n}\n\nvoid PhysEnv_Maze_PickDirection(const Location_t& loc, vbint_t maze_index, uint8_t& maze_state)\n{\n    if(Water[maze_index].Location.Width > Water[maze_index].Location.Height)\n    {\n        num_t dx = Water[maze_index].Location.X - loc.X + (Water[maze_index].Location.Width - loc.Width) / 2;\n        if(dx > 0)\n            maze_state = MAZE_DIR_RIGHT;\n        else\n            maze_state = MAZE_DIR_LEFT;\n    }\n    else\n    {\n        num_t dy = Water[maze_index].Location.Y - loc.Y + (Water[maze_index].Location.Height - loc.Height) / 2;\n        if(dy > 0)\n            maze_state = MAZE_DIR_DOWN;\n        else\n            maze_state = MAZE_DIR_UP;\n    }\n}\n"
  },
  {
    "path": "src/phys_env.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef PHYS_ENV_H\n#define PHYS_ENV_H\n\n#include <array>\n\n#include \"globals.h\"\n\nenum PhysEnv_Maze_Status\n{\n    MAZE_DIR_LEFT = 0,\n    MAZE_DIR_UP = 1,\n    MAZE_DIR_RIGHT = 2,\n    MAZE_DIR_DOWN = 3,\n    MAZE_CAN_EXIT = 4,\n    MAZE_PLAYER_FLIP = 128,\n    MAZE_DIR_FLIP_BIT = 2,\n};\n\n// applies maze physics to a location. modifies maze_index and effect_data as needed.\n// indicates leaving the env by setting maze_index to 0.\nvoid PhysEnv_Maze(Location_t& loc, vbint_t& maze_index, uint8_t& maze_state, int npc_A, int plr_A, int speedvar, std::array<bool, 4> controls);\n\n// picks direction for a possible maze entry\nvoid PhysEnv_Maze_PickDirection(const Location_t& loc, vbint_t maze_index, uint8_t& maze_state);\n\n#endif // #ifndef PHYS_ENV_H\n"
  },
  {
    "path": "src/phys_id.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef ENUM_PHYSID_HHH\n#define ENUM_PHYSID_HHH\n\n#include \"global_constants.h\"\n\nenum PHYSID : vbint_t\n{\n    PHYSID_WATER = 0,\n    PHYSID_QUICKSAND = 1,\n    PHYSID_MAZE = 17,\n};\n\n#endif // #ifndef ENUM_PHYSID_HHH\n"
  },
  {
    "path": "src/pinched_info.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef PINCHED_INFO_H\n#define PINCHED_INFO_H\n\n#include <stdint.h>\n\n// structure used to store information about whether a player / NPC is being crushed by blocks\nstruct PinchedInfo_t\n{\n    uint16_t Bottom1   : 2;\n    uint16_t Left2     : 2;\n    uint16_t Top3      : 2;\n    uint16_t Right4    : 2;\n    uint16_t Moving    : 2;\n    bool     MovingLR  : 1;\n    bool     MovingUD  : 1;\n\n    // players only: frame counter to use classic (strict) pinched death condition\n    uint16_t Strict    : 4;\n\n    inline PinchedInfo_t()\n    {\n        reset_non_strict();\n        Strict = 0;\n    }\n\n    inline void reset_non_strict()\n    {\n        Bottom1 = 0;\n        Left2 = 0;\n        Top3 = 0;\n        Right4 = 0;\n        Moving = 0;\n        MovingLR = false;\n        MovingUD = false;\n    }\n};\n\n#endif // #ifndef PINCHED_INFO_H\n"
  },
  {
    "path": "src/player/player_action_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"player/player_update_priv.h\"\n#include \"config.h\"\n#include \"layers.h\"\n#include \"npc.h\"\n#include \"npc_traits.h\"\n#include \"phys_env.h\"\n#include \"npc/npc_queues.h\"\n\nvoid PlayerThrowItemMaze(const Player_t& p, Location_t& loc, uint8_t& maze_status)\n{\n    loc.SpeedX = 0;\n    loc.SpeedY = 0;\n\n    int direction = p.MazeZoneStatus % 4;\n\n    if(direction == MAZE_DIR_UP || direction == MAZE_DIR_DOWN)\n        loc.X = p.Location.X + (p.Location.Width - loc.Width) / 2;\n    else\n        loc.Y = p.Location.Y + (p.Location.Height - loc.Height) / 2;\n\n    if((direction == MAZE_DIR_UP && !p.Controls.Down) || (direction == MAZE_DIR_DOWN && p.Controls.Up))\n    {\n        maze_status = MAZE_DIR_UP;\n        loc.SpeedY = -6;\n        loc.Y = p.Location.Y - loc.Height - 24;\n    }\n    else if(direction == MAZE_DIR_UP || direction == MAZE_DIR_DOWN)\n    {\n        maze_status = MAZE_DIR_DOWN;\n        loc.SpeedY = 6;\n        loc.Y = p.Location.Y + p.Location.Height + 24;\n    }\n    else if(p.Direction <= 0)\n    {\n        maze_status = MAZE_DIR_LEFT;\n        loc.SpeedX = -6;\n        loc.X = p.Location.X - loc.Width - 24;\n    }\n    else\n    {\n        maze_status = MAZE_DIR_RIGHT;\n        loc.SpeedX = 6;\n        loc.X = p.Location.X + p.Location.Width + 24;\n    }\n}\n\nvoid PlayerThrownNpcMazeCheck(const Player_t& p, NPC_t& npc)\n{\n    if(!p.CurMazeZone)\n        return;\n\n    if(npc->NoClipping && npc.Type != NPCID_BULLET)\n        return;\n\n    npc.Effect = NPCEFF_MAZE;\n    npc.Effect2 = p.CurMazeZone;\n    PlayerThrowItemMaze(p, npc.Location, npc.Effect3);\n}\n\nvoid PlayerPoundLogic(int A)\n{\n    if(Player[A].Location.SpeedY != 0 && Player[A].StandingOnNPC == 0 && Player[A].Slope == 0)\n    {\n        if(Player[A].Mount == 3 && Player[A].MountType == 6) // Purple Yoshi Pound\n        {\n            bool groundPoundByAltRun = !ForcedControls && g_config.pound_by_alt_run;\n            bool poundKeyPressed = groundPoundByAltRun ? Player[A].Controls.AltRun : Player[A].Controls.Down;\n            bool poundKeyRelease = groundPoundByAltRun ? Player[A].AltRunRelease   : Player[A].DuckRelease;\n\n            if(poundKeyPressed && poundKeyRelease && Player[A].CanPound)\n            {\n                Player[A].GroundPound = true;\n                Player[A].GroundPound2 = true;\n                if(Player[A].Location.SpeedY < 0)\n                    Player[A].Location.SpeedY = 0;\n            }\n        }\n    }\n    else\n        Player[A].CanPound = false;\n\n    if(Player[A].GroundPound)\n    {\n        if(!Player[A].CanPound && Player[A].Location.SpeedY < 0)\n            Player[A].GroundPound = false;\n\n        bool groundPoundByAltRun = !ForcedControls && g_config.pound_by_alt_run;\n        if(groundPoundByAltRun)\n            Player[A].Controls.AltRun = true;\n        else\n            Player[A].Controls.Down = true;\n\n        Player[A].CanJump = false;\n        Player[A].Controls.Left = false;\n        Player[A].Controls.Up = false;\n        Player[A].Controls.Right = false;\n        Player[A].Controls.Jump = true;\n        Player[A].Location.SpeedX = Player[A].Location.SpeedX * 0.95_r;\n        Player[A].RunRelease = false;\n        Player[A].CanFly = false;\n        Player[A].FlyCount = 0;\n        Player[A].CanFly2 = false;\n        Player[A].Location.SpeedY += 1;\n        Player[A].CanPound = false;\n        Player[A].Jump = 0;\n    }\n    else\n    {\n        // allow pounding again\n        if(Player[A].Location.SpeedY < -5 && ((Player[A].Jump < 15 && Player[A].Jump != 0) || Player[A].CanFly))\n            Player[A].CanPound = true;\n\n        // rebound from hitting the ground\n        if(Player[A].GroundPound2)\n        {\n            Player[A].Location.SpeedY = -4;\n            Player[A].StandingOnNPC = 0;\n            Player[A].GroundPound2 = false;\n        }\n    }\n}\n\nvoid PlayerShootChar5Beam(int A)\n{\n    // TODO: State-dependent moment\n\n    Player[A].FireBallCD2 = 40;\n    if(Player[A].State == 6)\n        Player[A].FireBallCD2 = 25;\n\n    if(Player[A].State == 6)\n        PlaySoundSpatial(SFX_HeroSwordBeam, Player[A].Location);\n    else if(Player[A].State == 7 || Player[A].State == PLR_STATE_POLAR)\n        PlaySoundSpatial(SFX_HeroIce, Player[A].Location);\n    else\n        PlaySoundSpatial(SFX_HeroFireRod, Player[A].Location);\n\n    numNPCs++;\n\n    if(ShadowMode)\n        NPC[numNPCs].Shadow = true;\n\n    NPC[numNPCs].Type = NPCID_PLR_FIREBALL;\n\n    if(Player[A].State == 7 || Player[A].State == PLR_STATE_POLAR)\n        NPC[numNPCs].Type = NPCID_PLR_ICEBALL;\n\n    if(Player[A].State == 6)\n        NPC[numNPCs].Type = NPCID_SWORDBEAM;\n\n    NPC[numNPCs].Projectile = true;\n    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n    NPC[numNPCs].Location.X = Player[A].Location.X + Player[A].Location.Width / 2 + (40 * Player[A].Direction) - 8;\n\n    if(!Player[A].Duck)\n    {\n        NPC[numNPCs].Location.Y = Player[A].Location.Y + 5;\n        if(Player[A].State == 6)\n            NPC[numNPCs].Location.Y += 7;\n    }\n    else\n    {\n        NPC[numNPCs].Location.Y = Player[A].Location.Y + 18;\n        if(Player[A].State == 6)\n            NPC[numNPCs].Location.Y += 4;\n    }\n\n\n    NPC[numNPCs].Active = true;\n    NPC[numNPCs].TimeLeft = 100;\n    NPC[numNPCs].Location.SpeedY = 20;\n    NPC[numNPCs].CantHurt = 100;\n    NPC[numNPCs].CantHurtPlayer = A;\n    NPC[numNPCs].Special = Player[A].Character;\n    NPC[numNPCs].Variant = Player[A].Character; // NEW: to fix a bug where ice balls did not respect char blocks\n\n    if(NPC[numNPCs].Type == NPCID_PLR_FIREBALL)\n        NPC[numNPCs].Frame = 16;\n\n    // kill item if it is inside a wall next frame\n    NPC[numNPCs].WallDeath = 5;\n    NPC[numNPCs].Location.SpeedY = 0;\n    NPC[numNPCs].Location.SpeedX = 5 * Player[A].Direction + (Player[A].Location.SpeedX / 3);\n\n    if(Player[A].State == 6)\n        NPC[numNPCs].Location.SpeedX = 9 * Player[A].Direction + (Player[A].Location.SpeedX / 3);\n\n    if(Player[A].StandingOnNPC != 0)\n        NPC[numNPCs].Location.Y += -Player[A].Location.SpeedY;\n\n    if(Player[A].State != 6)\n        PlayerThrownNpcMazeCheck(Player[A], NPC[numNPCs]);\n\n    syncLayers_NPC(numNPCs);\n    CheckSectionNPC(numNPCs);\n}\n\nvoid PlayerThrowBomb(int A)\n{\n    Player_t& p = Player[A];\n\n    p.Bombs -= 1;\n\n    numNPCs++;\n    NPC[numNPCs].Active = true;\n    NPC[numNPCs].TimeLeft = Physics.NPCTimeOffScreen;\n    NPC[numNPCs].Section = p.Section;\n    NPC[numNPCs].Type = NPCID_BOMB;\n    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n    NPC[numNPCs].CantHurtPlayer = A;\n    NPC[numNPCs].CantHurt = 1000;\n\n    if(p.Duck && (p.Location.SpeedY == 0 || p.Slope > 0 || p.StandingOnNPC != 0))\n    {\n        NPC[numNPCs].Location.X = p.Location.X + (p.Location.Width - NPC[numNPCs].Location.Width) / 2;\n        NPC[numNPCs].Location.Y = p.Location.Y + p.Location.Height - NPC[numNPCs].Location.Height;\n        NPC[numNPCs].Location.SpeedX = 0;\n        NPC[numNPCs].Location.SpeedY = 0;\n        PlaySoundSpatial(SFX_Grab, p.Location);\n    }\n    else\n    {\n        NPC[numNPCs].Location.X = p.Location.X + (p.Location.Width - NPC[numNPCs].Location.Width) / 2;\n        NPC[numNPCs].Location.Y = p.Location.Y;\n        NPC[numNPCs].Location.SpeedX = 5 * p.Direction;\n        NPC[numNPCs].Location.SpeedY = -6;\n        NPC[numNPCs].Projectile = true;\n\n        if(p.Location.SpeedY == 0 || p.Slope > 0 || p.StandingOnNPC != 0)\n            p.SwordPoke = -10;\n\n        PlaySoundSpatial(SFX_Throw, p.Location);\n    }\n\n    PlayerThrownNpcMazeCheck(Player[A], NPC[numNPCs]);\n\n    syncLayers_NPC(numNPCs);\n}\n\nbool PlayerChar4HeavyOut(const int A)\n{\n    if(Player[A].Character != 4)\n        return false;\n\n    for(int B : NPCQueues::Active.no_change)\n    {\n        if(NPC[B].Active)\n        {\n            if(NPC[B].Type == NPCID_CHAR4_HEAVY)\n            {\n                if(NPC[B].Special5 == A)\n                    return true;\n            }\n        }\n    }\n\n    return false;\n}\n\nvoid PlayerThrowHeavy(const int A)\n{\n    auto &p = Player[A];\n\n    if(p.RunRelease && PlayerChar4HeavyOut(A))\n        return;\n\n    if(numNPCs >= maxNPCs - 100)\n        return;\n\n    p.FrameCount = 110;\n    p.FireBallCD = 25;\n\n    numNPCs++;\n    NPC[numNPCs].Type = NPCID_PLR_HEAVY;\n    if(ShadowMode)\n        NPC[numNPCs].Shadow = true;\n\n    if(p.Character == 3)\n    {\n        p.FireBallCD = 45;\n        NPC[numNPCs].Type = NPCID_CHAR3_HEAVY;\n\n        if(p.Controls.AltRun && p.Mount == 0)\n        {\n            NPC[numNPCs].HoldingPlayer = A;\n            p.HoldingNPC = numNPCs;\n            PlaySoundSpatial(SFX_Grab2, p.Location);\n        }\n        else\n            PlaySoundSpatial(SFX_Throw, p.Location);\n    }\n    else if(p.Character == 4)\n    {\n\n        p.FireBallCD = 0;\n        if(FlameThrower)\n            p.FireBallCD = 40;\n        NPC[numNPCs].Type = NPCID_CHAR4_HEAVY;\n        NPC[numNPCs].Special5 = A;\n        NPC[numNPCs].Special4 = p.Direction; // Special6 in SMBX 1.3\n        PlaySoundSpatial(SFX_Throw, p.Location);\n    }\n    else\n        PlaySoundSpatial(playerHammerSFX, p.Location);\n\n    NPC[numNPCs].Projectile = true;\n    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n    NPC[numNPCs].Location.X = p.Location.X + Physics.PlayerGrabSpotX[p.Character][p.State] * p.Direction;\n    NPC[numNPCs].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State];\n    NPC[numNPCs].Active = true;\n    NPC[numNPCs].TimeLeft = 100;\n    NPC[numNPCs].Location.SpeedY = 20;\n    NPC[numNPCs].CantHurt = 100;\n    NPC[numNPCs].CantHurtPlayer = A;\n\n    if(p.Controls.Up)\n    {\n        NPC[numNPCs].Location.SpeedX = 2 * p.Direction + p.Location.SpeedX * 0.9_r;\n\n        if(p.StandingOnNPC == 0)\n            NPC[numNPCs].Location.SpeedY = -8 + p.Location.SpeedY * 0.3_r;\n        else\n            NPC[numNPCs].Location.SpeedY = -8 + NPC[p.StandingOnNPC].Location.SpeedY * 0.3_r;\n\n        NPC[numNPCs].Location.Y -= 24;\n        NPC[numNPCs].Location.X += -6 * p.Direction;\n\n        if(p.Character == 3)\n        {\n            NPC[numNPCs].Location.SpeedY += 1;\n            NPC[numNPCs].Location.SpeedX = NPC[numNPCs].Location.SpeedX * 1.5_rb;\n        }\n        else if(p.Character == 4)\n        {\n            NPC[numNPCs].Location.SpeedY = -8;\n            NPC[numNPCs].Location.SpeedX = 12 * p.Direction + p.Location.SpeedX;\n        }\n    }\n    else\n    {\n        NPC[numNPCs].Location.SpeedX = 4 * p.Direction + p.Location.SpeedX * 0.9_r;\n\n        if(p.StandingOnNPC == 0)\n            NPC[numNPCs].Location.SpeedY = -5 + p.Location.SpeedY * 0.3_r;\n        else\n            NPC[numNPCs].Location.SpeedY = -5 + NPC[p.StandingOnNPC].Location.SpeedY * 0.3_r;\n\n        if(p.Character == 3)\n            NPC[numNPCs].Location.SpeedY += 1;\n        else if(p.Character == 4)\n        {\n            NPC[numNPCs].Location.SpeedY = -5;\n            NPC[numNPCs].Location.SpeedX = 10 * p.Direction + p.Location.SpeedX;\n            NPC[numNPCs].Location.Y -= 12;\n        }\n    }\n\n    if(p.Character == 4)\n        NPC[numNPCs].Location.X = p.Location.X + (p.Location.Width - NPC[numNPCs].Location.Width) / 2;\n\n    PlayerThrownNpcMazeCheck(p, NPC[numNPCs]);\n\n    syncLayers_NPC(numNPCs);\n    CheckSectionNPC(numNPCs);\n}\n\nvoid PlayerThrowBall(const int A)\n{\n    // TODO: State-dependent moment\n    Player_t& p = Player[A];\n\n    if(p.SpinJump)\n        p.SpinFireDir = p.Direction;\n\n    if(numNPCs >= maxNPCs - 100)\n        return;\n\n    numNPCs++;\n    NPC[numNPCs].Type = NPCID_PLR_FIREBALL;\n\n    bool throw_ice = (p.State == PLR_STATE_ICE || p.State == PLR_STATE_POLAR);\n\n    if(throw_ice)\n        NPC[numNPCs].Type = NPCID_PLR_ICEBALL;\n\n    if(ShadowMode)\n        NPC[numNPCs].Shadow = true;\n\n    NPC[numNPCs].Projectile = true;\n    NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n    NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n    NPC[numNPCs].Location.X = p.Location.X + Physics.PlayerGrabSpotX[p.Character][p.State] * p.Direction + 4;\n    NPC[numNPCs].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State];\n    NPC[numNPCs].Active = true;\n    NPC[numNPCs].TimeLeft = 100;\n    NPC[numNPCs].CantHurt = 100;\n    NPC[numNPCs].CantHurtPlayer = A;\n    NPC[numNPCs].Special = p.Character;\n    NPC[numNPCs].Variant = p.Character; // NEW: to fix a bug where ice balls did not respect char blocks\n\n    if((p.Character == 3 || p.Character == 4) && p.Mount == 0 && p.Controls.AltRun) // peach holds fireballs\n    {\n        p.HoldingNPC = numNPCs;\n        NPC[numNPCs].HoldingPlayer = A;\n    }\n\n    p.FireBallCD = 30;\n    if(p.Character == 2)\n        p.FireBallCD = 35;\n    if(p.Character == 3)\n        p.FireBallCD = 40;\n    if(p.Character == 4)\n        p.FireBallCD = 25;\n\n    if(p.State == PLR_STATE_POLAR && p.Slippy)\n        p.FireBallCD -= 5;\n\n    bool throw_up = p.Controls.Up;\n    int throw_dir = p.Direction;\n\n    NPC[numNPCs].Location.SpeedX = 5 * throw_dir + (p.Location.SpeedX) / 3.5_ri;\n\n    if(throw_ice)\n    {\n        NPC[numNPCs].Special = 1;\n\n        PlaySoundSpatial(SFX_Iceball, p.Location);\n\n        NPC[numNPCs].Location.SpeedY = (throw_up) ? -8 : 5;\n        NPC[numNPCs].Location.SpeedX = NPC[numNPCs].Location.SpeedX * 0.8_r;\n    }\n    else\n    {\n        PlaySoundSpatial(SFX_Fireball, p.Location);\n\n        NPC[numNPCs].Location.SpeedY = (throw_up) ? -6 : 20;\n\n        if(NPC[numNPCs].Special == 2)\n            NPC[numNPCs].Location.SpeedX = NPC[numNPCs].Location.SpeedX * 0.85_r;\n\n        if(NPC[numNPCs].Special == 2)\n            NPC[numNPCs].Frame = 4;\n        if(NPC[numNPCs].Special == 3)\n            NPC[numNPCs].Frame = 8;\n        if(NPC[numNPCs].Special == 4)\n            NPC[numNPCs].Frame = 12;\n    }\n\n    if(throw_up)\n    {\n        if(p.StandingOnNPC != 0)\n            NPC[numNPCs].Location.SpeedY += NPC[p.StandingOnNPC].Location.SpeedY / 10;\n        else\n            NPC[numNPCs].Location.SpeedY += p.Location.SpeedY / 10;\n\n        NPC[numNPCs].Location.SpeedX = NPC[numNPCs].Location.SpeedX * 0.9_r;\n    }\n\n    if(FlameThrower)\n    {\n        NPC[numNPCs].Location.SpeedX = NPC[numNPCs].Location.SpeedX * 1.5_rb;\n        NPC[numNPCs].Location.SpeedY = NPC[numNPCs].Location.SpeedY * 1.5_rb;\n    }\n\n    if(p.StandingOnNPC != 0)\n        NPC[numNPCs].Location.SpeedX = 5 * p.Direction + (p.Location.SpeedX + NPC[p.StandingOnNPC].Location.SpeedX) / 3.5_ri;\n\n    // special speed and animation code for polar swimming\n    if(p.AquaticSwim)\n    {\n        // player animation code\n        p.FireBallCD -= 10;\n\n        int plr_frame = 16;\n\n        if(p.Controls.Left || p.Controls.Right)\n        {\n            // use left/right frame\n        }\n        else if((p.Controls.Down && !p.Controls.Up) || Player[A].Frame == 19 || Player[A].Frame == 20 || Player[A].Frame == 21)\n            plr_frame = 19;\n        else if(p.Controls.Up || Player[A].Frame == 40 || Player[A].Frame == 41 || Player[A].Frame == 42)\n        {\n            throw_up = true;\n            plr_frame = 40;\n        }\n\n        if(!p.SwimCount)\n        {\n            p.Frame = plr_frame;\n            p.FrameCount = 60;\n        }\n\n        // center NPC if player is moving up/down\n        if(plr_frame != 16)\n        {\n            NPC[numNPCs].Location.X = p.Location.X + (p.Location.Width - NPC[numNPCs].Location.Width) / 2;\n            NPC[numNPCs].Location.Y += (plr_frame == 19) ? 8 : -8;\n            NPC[numNPCs].Direction = throw_dir;\n            throw_dir = 0;\n        }\n\n        // don't slow NPC down in its first frame of processing\n        NPC[numNPCs].Wet = 2;\n\n        // reset NPC speed\n        NPC[numNPCs].Location.SpeedX = 4 * throw_dir;\n        NPC[numNPCs].Location.SpeedY = (throw_dir) ? 2 : 3;\n\n        // special logic for throwing upwards during Polar Swim\n        if(throw_up)\n        {\n            NPC[numNPCs].Special4 = 1; // low gravity and no speed cap\n            NPC[numNPCs].Special5 = 3; // prevent bouncing\n            NPC[numNPCs].Location.SpeedY = -NPC[numNPCs].Location.SpeedY;\n        }\n\n        // add player momentum\n        NPC[numNPCs].Location.SpeedX += p.Location.SpeedX;\n        NPC[numNPCs].Location.SpeedY += p.Location.SpeedY;\n    }\n    else if(!p.SpinJump)\n        p.FrameCount = 110;\n\n    PlayerThrownNpcMazeCheck(p, NPC[numNPCs]);\n\n    syncLayers_NPC(numNPCs);\n    CheckSectionNPC(numNPCs);\n}\n\nvoid PowerUps(const int A)\n{\n    auto &p = Player[A];\n    //int B = 0;\n\n    if(p.Fairy)\n    {\n        p.SwordPoke = 0;\n        p.FireBallCD = 0;\n        p.FireBallCD2 = 0;\n        p.TailCount = 0;\n        return;\n    }\n\n    // this condition was moved into PlayerThrowHeavy\n    // if(p.State == 6 && p.Character == 4 && p.Controls.Run && p.RunRelease)\n    //     BoomOut = PlayerChar4HeavyOut(A);\n\n    // Run-triggered actions; many of the conditions were combined here, so please search for the distinctive comments to find the corresponding code in legacy source\n    if(!p.Slide && p.Vine == 0 && p.Character != 5 && (!p.Duck || p.AquaticSwim) && p.Mount <= 1 && p.HoldingNPC <= 0)\n    {\n        // Hammer Throw Code\n        if(p.State == 6)\n        {\n            if(p.FireBallCD <= 0)\n            {\n                if(p.Controls.Run && !p.SpinJump /* && !BoomOut*/)\n                {\n                    if(p.RunRelease || p.SpinJump || FlameThrower)\n                        PlayerThrowHeavy(A);\n                }\n            }\n        }\n        // Fire Mario / Luigi code ---- FIRE FLOWER ACTION BALLS OF DOOM\n        // State-dependent moment!\n        else if(p.State == 3 || p.State == 7 || p.State == PLR_STATE_POLAR)\n        {\n            if(p.FireBallCD <= 0)\n            {\n                if((p.Controls.Run && !p.SpinJump) || (p.SpinJump && p.Direction != p.SpinFireDir))\n                {\n                    if(p.RunRelease || p.SpinJump || FlameThrower)\n                        PlayerThrowBall(A);\n                }\n            }\n        }\n        // RacoonMario\n        else if(p.State == 4 || p.State == 5)\n        {\n            // NOTE: when PowerUps is called, HoldingNPC < 0 if and only if (Player[A].Mount != 0 || Player[A].Stoned || Player[A].Fairy), and Effect is always PLREFF_NORMAL\n            if(p.HoldingNPC == 0 /* && p.Mount != 2 && !p.Stoned && p.Effect == PLREFF_NORMAL */)\n            {\n                if((p.Controls.Run && p.RunRelease) || p.SpinJump)\n                {\n                    if(p.TailCount == 0 || p.TailCount >= 12)\n                    {\n                        p.TailCount = 1;\n\n                        if(!p.SpinJump)\n                            PlaySoundSpatial(SFX_Whip, p.Location);\n                    }\n                }\n            }\n        }\n    }\n\n    if(p.TailCount > 0)\n    {\n        p.TailCount += 1;\n        if(p.TailCount == 25)\n            p.TailCount = 0;\n\n        if(p.TailCount % 7 == 0 || (p.SpinJump && (p.TailCount % 2) == 0))\n            TailSwipe(A, true);\n        else\n            TailSwipe(A);\n\n        if(p.HoldingNPC > 0)\n            p.TailCount = 0;\n    }\n\n\n    // link stab\n    if(p.Character == 5 && p.Vine == 0 && p.Mount == 0 && !p.Stoned && p.FireBallCD == 0)\n        PlayerChar5StabLogic(A);\n\n\n    // cooldown timer\n    p.FireBallCD2 -= 1;\n    if(p.FireBallCD2 < 0)\n        p.FireBallCD2 = 0;\n\n    if(!(p.Character == 3 && NPC[p.HoldingNPC].Type == NPCID_PLR_FIREBALL))\n    {\n        p.FireBallCD -= 1;\n        if(FlameThrower)\n            p.FireBallCD -= 3;\n        if(p.FireBallCD < 0)\n            p.FireBallCD = 0;\n    }\n}\n\nvoid Tanooki(const int A)\n{\n    auto &p = Player[A];\n\n    if(p.Fairy)\n       return;\n\n    // tanooki\n    if(p.Stoned && p.Controls.Down && p.StandingOnNPC == 0)\n    {\n        p.Location.SpeedX = p.Location.SpeedX * 0.8_r;\n        if(p.Location.SpeedX >= -0.5_n && p.Location.SpeedX <= 0.5_n)\n            p.Location.SpeedX = 0;\n        if(p.Location.SpeedY < 8)\n            p.Location.SpeedY += 0.25_n;\n    }\n\n    if(p.StonedCD == 0)\n    {\n        // If .Mount = 0 And .State = 5 And .Controls.Run = True And .Controls.Down = True Then\n        if(p.Mount == 0 && p.State == 5 && p.Controls.AltRun && p.Bombs == 0)\n        {\n            if(!p.Stoned)\n                p.Effect = PLREFF_STONE;\n        }\n        else if(p.Stoned)\n            p.Effect = PLREFF_STONE;\n    }\n    else\n        p.StonedCD -= 1;\n\n    if(p.Stoned)\n    {\n        p.StonedTime += 1;\n        if(p.StonedTime >= 240)\n        {\n            p.Effect = PLREFF_STONE;\n            p.StonedCD = 60;\n        }\n        else if(p.StonedTime >= 180)\n        {\n            p.Immune += 1;\n            if(p.Immune % 3 == 0)\n                p.Immune2 = !p.Immune2;\n        }\n    }\n}\n"
  },
  {
    "path": "src/player/player_block_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n\n#include \"globals.h\"\n\n#include \"player.h\"\n#include \"blocks.h\"\n#include \"sound.h\"\n#include \"layers.h\"\n#include \"effect.h\"\n#include \"eff_id.h\"\n#include \"collision.h\"\n#include \"eff_id.h\"\n#include \"blk_id.h\"\n#include \"npc_traits.h\"\n#include \"config.h\"\n#include \"phys_env.h\"\n\n#include \"main/trees.h\"\n\nvoid PlayerBlockLogic(int A, int& floorBlock, bool& movingBlock, bool& DontResetGrabTime, tempf_t cursed_value_C, const int oldStandingOnNpc)\n{\n    int oldSlope = Player[A].Slope;\n    Player[A].Slope = 0;\n\n    int blockPushX = 0;\n    bool hitCeiling = false; // previously called tempHit\n    bool hitWall = false; // previously called tempHit2\n\n    int ceilingSlope = 0; // was previously called tempSlope\n    int wallBlock = 0; // was previously called tempSlope2\n    int insideBlock = 0; // keeps track of hit 5 for slope detection -- was previously called tempSlope3\n\n    int ceilingBlock1 = 0;\n    int ceilingBlock2 = 0;\n\n    Location_t floorLocation; // was previously called tempLocation3\n\n    // This was previously shared between players, but is safe unless Block[wallBlock] satisfies certain properties before wallBlock gets set\n    num_t preWallX = 0; // The old X before player was moved -- was previously called tempSlope2X\n\n    if(Player[A].Character == 5 && Player[A].Duck && (Player[A].Location.SpeedY == Physics.PlayerGravity || Player[A].StandingOnNPC != 0 || Player[A].Slope != 0))\n        Player[A].Location.set_height_floor(30);\n\n\n    // block collision optimization\n    // fBlock = FirstBlock[(Player[A].Location.X / 32) - 1];\n    // lBlock = LastBlock[((Player[A].Location.X + Player[A].Location.Width) / 32.0) + 1];\n    // blockTileGet(Player[A].Location, fBlock, lBlock);\n\n    UpdatableQuery<BlockRef_t> q(Player[A].Location, SORTMODE_COMPAT, QUERY_FLBLOCK);\n\n    for(auto it = q.begin(); it != q.end(); ++it)\n    {\n        int B = *it;\n\n        // checks to see if a collision happened\n        if(Player[A].Location.X + Player[A].Location.Width >= Block[B].Location.X)\n        {\n            if(Player[A].Location.X <= Block[B].Location.X + Block[B].Location.Width)\n            {\n                if(Player[A].Location.Y + Player[A].Location.Height >= Block[B].Location.Y)\n                {\n                    if(Player[A].Location.Y <= Block[B].Location.Y + Block[B].Location.Height)\n                    {\n\n                        if(!Block[B].Hidden)\n                        {\n                            // the hitspot is used for collision detection to find out where to put the player after it collides with a block\n                            // the numbers tell what side the collision happened so it can move the plaer to the correct position\n                            // 1 means the player hit the block from the top\n                            // 2 is from the right\n                            // 3 is from the bottom\n                            // 4 is from the left\n                            num_t block_belt_speed = 0;\n                            if(Block[B].Type >= BLKID_CONVEYOR_L_START && Block[B].Type <= BLKID_CONVEYOR_L_END)\n                                block_belt_speed = -0.8_n;\n                            else if(Block[B].Type >= BLKID_CONVEYOR_R_START && Block[B].Type <= BLKID_CONVEYOR_R_END)\n                                block_belt_speed = 0.8_n;\n\n                            int HitSpot = FindRunningCollision(Player[A].Location, Block[B].Location, block_belt_speed); // this finds what part of the block the player collided\n\n                            // force hitspot 3 during cyclone jump (prevents catching on sides of ceiling blocks)\n                            if(Player[A].State == PLR_STATE_CYCLONE && !Player[A].DoubleJump && HitSpot != 1)\n                            {\n                                // cross-ref FindRunningCollision\n                                if(Player[A].Location.Y - Player[A].Location.SpeedY >= Block[B].Location.Y + Block[B].Location.Height - Block[B].Location.SpeedY)\n                                    HitSpot = 3;\n                            }\n\n                            if(BlockNoClipping[Block[B].Type]) // blocks that the player can't touch are forced to hitspot 0 (which means no collision)\n                                HitSpot = 0;\n\n                            if(BlockIsSizable[Block[B].Type] || BlockOnlyHitspot1[Block[B].Type]) // for sizable blocks, if the player didn't land on them from the top then he can walk through them\n                            {\n                                if(HitSpot != 1)\n                                    HitSpot = 0;\n\n                                if(Player[A].Mount == 2 || Player[A].StandingOnVehiclePlr != 0)\n                                    HitSpot = 0;\n                            }\n\n                            // for blocks that hurt the player\n                            if(BlockHurts[Block[B].Type])\n                            {\n                                if(Player[A].Mount == 2 ||\n                                    InvincibilityTime ||\n                                   (\n                                       ((HitSpot == 1 && Player[A].Mount) || (Player[A].Rolling && Player[A].State == PLR_STATE_SHELL)) &&\n                                       Block[B].Type != 598\n                                   )\n                                 )\n                                {}\n                                else\n                                {\n                                    if(HitSpot == 1 && (Block[B].Type == 110 || Block[B].Type == 408 || Block[B].Type == 430 || Block[B].Type == 511))\n                                        PlayerHurt(A);\n                                    if(HitSpot == 4 && (Block[B].Type == 269 || Block[B].Type == 429))\n                                        PlayerHurt(A);\n                                    if(HitSpot == 3 && (Block[B].Type == 268 || Block[B].Type == 407 || Block[B].Type == 431))\n                                        PlayerHurt(A);\n                                    if(HitSpot == 2 && (Block[B].Type == 267 || Block[B].Type == 428))\n                                        PlayerHurt(A);\n                                    if(Block[B].Type == 109)\n                                        PlayerHurt(A);\n                                    if(Block[B].Type == 598)\n                                    {\n                                        if(Player[A].Mount > 0 && HitSpot == 1)\n                                        {\n                                            cursed_value_C = tempf_t(Player[A].Location.Y + Player[A].Location.Height);\n                                            Player[A].Location.Y = Block[B].Location.Y - Player[A].Location.Height;\n                                            PlayerHurt(A);\n                                            Player[A].Location.Y = (num_t)cursed_value_C - Player[A].Location.Height;\n                                        }\n                                        else\n                                            PlayerHurt(A);\n                                    }\n\n\n                                    if(Player[A].TimeToLive > 0)\n                                        break;\n                                }\n                            }\n\n                            // hitspot 5 means the game doesn't know where the collision happened\n                            // if the player just stopped ducking and there is a hitspot 5 then force hitspot 3 (hit block from below)\n                            if(HitSpot == 5 && (Player[A].StandUp || NPC[Player[A].StandingOnNPC].Location.SpeedY < 0))\n                            {\n                                if(BlockSlope[Block[B].Type] == 0)\n                                    HitSpot = 3;\n                            }\n\n                            // if the block is invisible and the player didn't hit it from below then the player won't collide with it\n                            if(Block[B].Invis)\n                            {\n                                if(HitSpot != 3)\n                                    HitSpot = 0;\n                            }\n\n                            // unclear that this does what Redigit thought; maybe it did with a previous version of the coin switch code\n\n                            // ' fixes a bug with holding an npc that is really a block\n                            if(Player[A].HoldingNPC > 0)\n                            {\n                                if(NPC[Player[A].HoldingNPC].coinSwitchBlockType() == B)\n                                    HitSpot = 0;\n                            }\n\n                            // destroy some blocks if the player is touching it as a statue\n                            if(Block[B].Type == 457 && Player[A].Stoned)\n                            {\n                                HitSpot = 0;\n                                SafelyKillBlock(B);\n                            }\n\n                            // shadowmode is a cheat that allows the player to walk through walls\n                            if(ShadowMode && HitSpot != 1 && !(Block[B].Special > 0 && HitSpot == 3))\n                                HitSpot = 0;\n\n                            // this handles the collision for blocks that are sloped on the bottom\n                            if(BlockSlope2[Block[B].Type] != 0 && (Player[A].Location.Y > Block[B].Location.Y || (HitSpot != 2 && HitSpot != 4)) && HitSpot != 1 && !ShadowMode)\n                            {\n                                HitSpot = 0;\n                                ceilingSlope = B;\n\n                                num_t PlrMid;\n                                if(BlockSlope2[Block[B].Type] == 1)\n                                    PlrMid = Player[A].Location.X + Player[A].Location.Width;\n                                else\n                                    PlrMid = Player[A].Location.X;\n\n                                num_t Slope = (PlrMid - Block[B].Location.X) / (int_ok)Block[B].Location.Width;\n\n                                if(BlockSlope2[Block[B].Type] > 0)\n                                    Slope = 1 - Slope;\n\n                                if(Slope < 0)\n                                    Slope = 0;\n\n                                if(Slope > 1)\n                                    Slope = 1;\n\n                                if(Player[A].Location.Y <= Block[B].Location.Y + Block[B].Location.Height - ((int_ok)Block[B].Location.Height * Slope))\n                                {\n                                    if(BlockKills[Block[B].Type] && !(Player[A].Rolling && Player[A].Character == 5 && Player[A].State == PLR_STATE_SHELL))\n                                    {\n                                        if(!GodMode)\n                                            PlayerDead(A);\n                                    }\n\n                                    if(Player[A].Location.SpeedY == 0 ||\n                                       num_t::fEqual_f(Player[A].Location.SpeedY, Physics.PlayerGravity) || Player[A].Slope > 0 || Player[A].StandingOnNPC != 0)\n                                    {\n                                        num_t PlrMid = Player[A].Location.Y;\n                                        Slope = (PlrMid - Block[B].Location.Y) / (int_ok)Block[B].Location.Height;\n\n                                        if(Slope < 0)\n                                            Slope = 0;\n\n                                        if(Slope > 1)\n                                            Slope = 1;\n\n                                        if(BlockSlope2[Block[B].Type] < 0)\n                                            Player[A].Location.X = Block[B].Location.X + Block[B].Location.Width - ((int_ok)Block[B].Location.Width * Slope);\n                                        else\n                                            Player[A].Location.X = Block[B].Location.X + ((int_ok)Block[B].Location.Width * Slope) - Player[A].Location.Width;\n\n                                        Player[A].Location.SpeedX = 0;\n\n                                    }\n                                    else\n                                    {\n                                        Player[A].Location.Y = Block[B].Location.Y + Block[B].Location.Height - ((int_ok)Block[B].Location.Height * Slope);\n                                        if(Player[A].Location.SpeedY < 0)\n                                            PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n                                        if(Player[A].Location.SpeedY < -0.01_n)\n                                            Player[A].Location.SpeedY = -0.01_n;\n                                        if(Player[A].Mount == 2)\n                                            Player[A].Location.SpeedY = 2;\n                                        if(Player[A].CanFly2)\n                                            Player[A].Location.SpeedY = 2;\n                                    }\n\n                                    // cancel any jump except for a cyclone jump\n                                    if(!(Player[A].State == PLR_STATE_CYCLONE && !Player[A].DoubleJump))\n                                        Player[A].Jump = 0;\n                                }\n                            }\n\n                            // collision for blocks that are sloped on the top\n                            // MOST CURSED LINE: cursed_value_C (C in VB6 code) is entirely arbitrary at this point. BE WARNED, this may be a cause of incompatibilities with SMBX 1.3, and if so, we will need to improve this logic in the future.\n                            if(BlockSlope[Block[B].Type] != 0 && HitSpot != 3 && !(BlockSlope[Block[B].Type] == -1 && HitSpot == 2) && !(BlockSlope[Block[B].Type] == 1 && HitSpot == 4) && (Player[A].Location.Y + Player[A].Location.Height - 4 - (num_t)cursed_value_C <= Block[B].Location.Y + Block[B].Location.Height || (Player[A].Location.Y + Player[A].Location.Height - 12 <= Block[B].Location.Y + Block[B].Location.Height && Player[A].StandingOnNPC != 0)))\n                            {\n                                HitSpot = 0;\n                                if(\n                                        (Player[A].Mount == 1 || Player[A].Location.SpeedY >= 0 ||\n                                         Player[A].Slide || SuperSpeed || Player[A].Stoned) &&\n                                        (Player[A].Location.Y + Player[A].Location.Height <= Block[B].Location.Y + Block[B].Location.Height + Player[A].Location.SpeedY + 0.001_n ||\n                                         (Player[A].Slope == 0 && Block[B].Location.SpeedY < 0))\n                                        )\n                                {\n                                    num_t PlrMid;\n                                    if(BlockSlope[Block[B].Type] == 1)\n                                        PlrMid = Player[A].Location.X;\n                                    else\n                                        PlrMid = Player[A].Location.X + Player[A].Location.Width;\n\n                                    num_t Slope = (PlrMid - Block[B].Location.X) / (int_ok)Block[B].Location.Width;\n\n                                    if(BlockSlope[Block[B].Type] < 0)\n                                        Slope = 1 - Slope;\n\n                                    if(Slope < 0)\n                                        Slope = 0;\n\n                                    if(Slope > 1)\n                                        Slope = 1;\n\n                                    // if we're already on top of another (higher or more leftwards, at level load time) block this frame, consider canceling it\n                                    if(floorBlock > 0)\n                                    {\n                                        // the bug this is fixing is vanilla, but this case happens for a single frame every time a slope falls through ground since TheXTech 1.3.6,\n                                        // and only in the rare case where a slope falls through ground *it was originally below* in vanilla\n                                        if(g_config.fix_player_downward_clip && !CompareWalkBlock(floorBlock, B, Player[A].Location))\n                                        {\n                                            // keep the old block, other conditions are VERY likely to cancel it\n                                        }\n                                        else if(!BlockIsSizable[Block[floorBlock].Type])\n                                        {\n                                            if(Block[floorBlock].Location.Y != Block[B].Location.Y)\n                                                floorBlock = 0;\n                                        }\n                                        else\n                                        {\n                                            // NOTE: looks like a good place for a vb6-style fEqual\n                                            if(Block[floorBlock].Location.Y == Block[B].Location.Y + Block[B].Location.Height)\n                                                floorBlock = 0;\n                                        }\n                                    }\n\n                                    if(hitWall)\n                                    {\n                                        // NOTE: looks like a good place for a vb6-style fEqual\n                                        if(Block[wallBlock].Location.Y + Block[wallBlock].Location.Height == Block[B].Location.Y && BlockSlope[Block[wallBlock].Type] == BlockSlope[Block[B].Type])\n                                        {\n                                            hitWall = false;\n                                            wallBlock = 0;\n                                            Player[A].Location.X = preWallX;\n                                        }\n                                    }\n\n                                    if(insideBlock > 0)\n                                    {\n                                        Player[A].Location.Y = Block[insideBlock].Location.Y + Block[insideBlock].Location.Height + 0.01_n;\n                                        num_t PlrMid = Player[A].Location.Y + Player[A].Location.Height;\n                                        num_t Slope = 1 - (PlrMid - Block[B].Location.Y) / (int_ok)Block[B].Location.Height;\n\n                                        if(Slope < 0)\n                                            Slope = 0;\n\n                                        if(Slope > 1)\n                                            Slope = 1;\n\n                                        if(BlockSlope[Block[B].Type] > 0)\n                                            Player[A].Location.X = Block[B].Location.X + Block[B].Location.Width - ((int_ok)Block[B].Location.Width * Slope);\n                                        else\n                                            Player[A].Location.X = Block[B].Location.X + ((int_ok)Block[B].Location.Width * Slope) - Player[A].Location.Width;\n\n                                        Player[A].Location.SpeedX = 0;\n                                    }\n                                    else\n                                    {\n                                        if(Player[A].Location.Y >= Block[B].Location.Y + ((int_ok)Block[B].Location.Height * Slope) - Player[A].Location.Height - 0.1_n)\n                                        {\n\n                                            if(Player[A].GroundPound)\n                                            {\n                                                YoshiPound(A, Player[A].Mount, true);\n                                                Player[A].GroundPound = false;\n                                            }\n                                            else if(Player[A].YoshiYellow)\n                                            {\n                                                if(oldSlope == 0)\n                                                    YoshiPound(A, Player[A].Mount);\n                                            }\n\n                                            Player[A].Location.Y = Block[B].Location.Y + ((int_ok)Block[B].Location.Height * Slope) - Player[A].Location.Height - 0.1_n;\n\n                                            if(Player[A].Location.SpeedY > Player[A].Location.SpeedX * (int_ok)Block[B].Location.Height / (int_ok)Block[B].Location.Width * BlockSlope[Block[B].Type] || !Player[A].Slide)\n                                            {\n                                                if(!Player[A].WetFrame)\n                                                {\n                                                    cursed_value_C = (tempf_t)(Player[A].Location.SpeedX * (int_ok)Block[B].Location.Height / (int_ok)Block[B].Location.Width * BlockSlope[Block[B].Type]);\n                                                    Player[A].Location.SpeedY = (num_t)cursed_value_C;\n                                                    if(Player[A].Location.SpeedY > 0 && !Player[A].Slide && Player[A].Mount != 1 && Player[A].Mount != 2)\n                                                        Player[A].Location.SpeedY = Player[A].Location.SpeedY * 4;\n                                                }\n                                            }\n\n                                            Player[A].Slope = B;\n                                            if(BlockSlope[Block[B].Type] == 1 && GameMenu && Player[A].Location.SpeedX >= 2)\n                                            {\n                                                if(Player[A].Mount == 0 && Player[A].HoldingNPC == 0 && Player[A].Character <= 2)\n                                                {\n                                                    if(Player[A].Duck)\n                                                        UnDuck(Player[A]);\n                                                    Player[A].Slide = true;\n                                                }\n                                            }\n\n\n\n                                            if(Player[A].Location.SpeedY < 0 && !Player[A].Slide && !SuperSpeed && !Player[A].Stoned)\n                                                Player[A].Location.SpeedY = 0;\n                                            if(Block[B].Location.SpeedX != 0 || Block[B].Location.SpeedY != 0)\n                                            {\n                                                NPC[-A] = NPC_t();\n                                                NPC[-A].Location = Block[B].Location;\n                                                NPC[-A].Type = NPCID_METALBARREL;\n                                                NPC[-A].Active = true;\n                                                NPC[-A].TimeLeft = 100;\n                                                NPC[-A].Section = Player[A].Section;\n                                                NPC[-A].Special = B;\n                                                NPC[-A].Special2 = BlockSlope[Block[B].Type];\n                                                Player[A].StandingOnNPC = -A;\n                                                movingBlock = true;\n                                                // NOTE: Here was a bug that makes compare bool with 0 is always false\n                                                if(\n                                                        (g_config.fix_player_slope_speed &&\n                                                         Player[A].Location.SpeedX - NPC[Player[A].StandingOnNPC].Location.SpeedX < 0 &&\n                                                         BlockSlope[Block[B].Type]/*)*/ < 0) ||\n                                                        (Player[A].Location.SpeedX - NPC[Player[A].StandingOnNPC].Location.SpeedX > 0 &&\n                                                         BlockSlope[Block[B].Type] > 0)\n                                                        )\n                                                {\n                                                    if((Player[A].Location.SpeedX < 0 && Block[B].Location.SpeedX > 0) || (Player[A].Location.SpeedX > 0 && Block[B].Location.SpeedX < 0))\n                                                        Player[A].Location.SpeedY = 12;\n                                                }\n\n                                                NPC[-A].Location.Y = Player[A].Location.Y + Player[A].Location.Height;\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n\n\n                            if(BlockKills[Block[B].Type] && !GodMode)\n                            {\n                                bool hot_boot = (Player[A].Mount == 1 && Player[A].MountType == 2);\n                                bool lava_roller = (Player[A].Rolling && Player[A].Character == 5 && Player[A].State == PLR_STATE_SHELL);\n                                hot_boot |= lava_roller;\n\n                                // this is a fix to help the player deal with lava blocks a bit easier\n                                // it moves the blocks hitbox down a few pixels\n                                if(BlockSlope[Block[B].Type] == 0 && !hot_boot)\n                                {\n                                    if(Player[A].Location.Y + Player[A].Location.Height < Block[B].Location.Y + 6)\n                                        HitSpot = 0;\n                                }\n\n                                // kill the player if touching a lava block\n                                if(HitSpot > 0 || Player[A].Slope == B)\n                                {\n                                    if(!hot_boot)\n                                    {\n                                        PlayerDead(A);\n                                        break;\n                                    }\n                                    else if(HitSpot != 1 && BlockSlope[Block[B].Type] == 0 && !lava_roller)\n                                    {\n                                        PlayerDead(A);\n                                        break;\n                                    }\n                                    else\n                                    {\n                                        Location_t tempLocation;\n                                        tempLocation.Y = Player[A].Location.Y + Player[A].Location.Height - 2;\n                                        tempLocation.X = Player[A].Location.X - 4 + dRand() * ((int)Player[A].Location.Width + 8) - 4;\n                                        NewEffect(EFFID_SKID_DUST, tempLocation);\n                                    }\n                                }\n                            }\n\n                            // if hitspot 5 with a sloped block then don't collide with it. the collision should have already been handled by the slope code above\n                            if(HitSpot == 5 && BlockSlope[Block[B].Type] != 0)\n                                HitSpot = 0;\n\n                            // shelsurfing code\n                            if(HitSpot > 1 && Player[A].ShellSurf)\n                            {\n                                Player[A].ShellSurf = false;\n                                Player[A].Location.SpeedY = NPC[Player[A].StandingOnNPC].Location.SpeedY + Physics.PlayerJumpVelocity * 0.75_rb;\n                                Player[A].StandingOnNPC = 0;\n                                PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n                            }\n\n                            if(BlockCheckPlayerFilter(B, A))  // Optmizied\n                                HitSpot = 0;\n                            //if(Block[B].Type == 626 && Player[A].Character == 1)\n                            //    HitSpot = 0;\n                            //if(Block[B].Type == 627 && Player[A].Character == 2)\n                            //    HitSpot = 0;\n                            //if(Block[B].Type == 628 && Player[A].Character == 3)\n                            //    HitSpot = 0;\n                            //if(Block[B].Type == 629 && Player[A].Character == 4)\n                            //    HitSpot = 0;\n                            //if(Block[B].Type == 632 && Player[A].Character == 5)\n                            //    HitSpot = 0;\n\n                            if(g_config.fix_player_clip_wall_at_npc && (HitSpot == 5 || HitSpot == 3) && oldStandingOnNpc > 0 && Player[A].Jump)\n                            {\n                                // Re-compute the collision with a block to avoid the unnecessary clipping through the wall\n                                auto pLoc = Player[A].Location;\n                                pLoc.SpeedX += NPC[oldStandingOnNpc].Location.SpeedX;\n                                pLoc.SpeedY += NPC[oldStandingOnNpc].Location.SpeedY;\n                                HitSpot = FindRunningCollision(pLoc, Block[B].Location, block_belt_speed);\n                                D_pLogDebug(\"Conveyor: Recomputed collision with block %d\", B);\n                            }\n\n                            // the following code is where the collisions are handled\n\n\n                            if((HitSpot == 1 || Player[A].Slope == B) && Block[B].Slippy)\n                                Player[A].Slippy = true;\n\n\n                            if(HitSpot == 5 && Player[A].Quicksand > 0) // fixes quicksand hitspot 3 bug\n                            {\n                                if(Player[A].Location.Y - Player[A].Location.SpeedY < Block[B].Location.Y + Block[B].Location.Height)\n                                    HitSpot = 3;\n                            }\n                            // don't stop rolling when you clip into a block -- just bounce on it\n                            // (if there are multiple blocks, eventually the player will get pinched and leave rolling state)\n                            else if(HitSpot == 5 && Player[A].Rolling && Player[A].State == PLR_STATE_SHELL)\n                            {\n                                if(Block[B].Location.to_right_of(Player[A].Location))\n                                    HitSpot = 4;\n                                else\n                                    HitSpot = 2;\n                            }\n\n                            if(HitSpot == 1) // landed on the block from the top V\n                            {\n                                if(Player[A].Fairy && (Player[A].FairyCD > 0 || Player[A].Location.SpeedY > 0))\n                                    Player[A].FairyTime = 0;\n\n                                Player[A].Pinched.Bottom1 = 2; // for players getting squashed\n\n                                if(Block[B].Location.SpeedY != 0)\n                                {\n                                    Player[A].Pinched.Moving = 2;\n                                    Player[A].Pinched.MovingUD = true;\n                                }\n\n                                Player[A].Vine = 0; // stop climbing because you are now walking\n                                if(Player[A].Mount == 2) // for the clown car, make a niose and pound the ground if moving down fast enough\n                                {\n                                    if(Player[A].Location.SpeedY > 3)\n                                    {\n                                        PlaySoundSpatial(SFX_Stone, Player[A].Location);\n                                        YoshiPound(A, Player[A].Mount, true);\n                                    }\n                                }\n\n                                bool player_drill = (Player[A].State == PLR_STATE_CYCLONE && !Player[A].DoubleJump && Player[A].Controls.Down && Player[A].Location.SpeedY > Physics.PlayerTerminalVelocity * 0.9_n);\n                                if(player_drill && BlockIsBreakable(Block[B]) && Block[B].Type != 90)\n                                {\n                                    BlockHitHard(B);\n                                }\n                                else if(player_drill)\n                                {\n                                    if(Block[B].Special)\n                                    {\n                                        BlockHit(B, true, A);\n                                        PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n                                    }\n\n                                    player_drill = false;\n                                }\n\n                                if(player_drill)\n                                {\n                                    // just keep going down\n                                }\n                                else if(floorBlock == 0) // For walking\n                                {\n                                    floorBlock = B;\n                                    floorLocation = Block[B].Location;\n                                }\n                                else // Find the best block to walk on if touching multiple blocks\n                                {\n                                    if(g_config.fix_player_downward_clip)\n                                    {\n                                        if(CompareWalkBlock(floorBlock, B, Player[A].Location))\n                                        {\n                                            floorBlock = B;\n                                            floorLocation = Block[B].Location;\n                                        }\n                                    }\n                                    else // Using old code\n                                    {\n                                        if(Block[B].Location.SpeedY != 0 && Block[floorBlock].Location.SpeedY == 0)\n                                        {\n                                            floorBlock = B;\n                                            floorLocation = Block[B].Location;\n                                        }\n                                        else if(Block[B].Location.SpeedY == 0 && Block[floorBlock].Location.SpeedY != 0)\n                                        {\n                                        }\n                                        else\n                                        {\n                                            num_t C = num_t::abs(Block[B].Location.minus_center_x(Player[A].Location));\n                                            num_t D = num_t::abs(Block[floorBlock].Location.minus_center_x(Player[A].Location));\n\n                                            if(C < D)\n                                                floorBlock = B;\n\n                                            // this is the case where an unbound C gets written to\n                                            cursed_value_C = (tempf_t)C;\n                                        }\n\n                                        // if this block is moving up give it priority\n                                        if(Block[B].Location.SpeedY < 0 && Block[B].Location.Y < Block[floorBlock].Location.Y)\n                                        {\n                                            floorBlock = B;\n                                            floorLocation = Block[B].Location;\n                                        }\n                                    }\n                                }\n\n                            }\n                            else if(HitSpot == 2 || HitSpot == 4) // hit the block from the right <---- (or left now! -------.)\n                            {\n                                if(HitSpot == 2 && BlockSlope[Block[oldSlope].Type] == 1 && Block[oldSlope].Location.Y <= Block[B].Location.Y)\n                                {\n                                    // Just a blank block :-P\n                                }\n                                else\n                                {\n                                    preWallX = Player[A].Location.X;\n\n                                    if(HitSpot == 4)\n                                    {\n                                        Player[A].Location.X = Block[B].Location.X - Player[A].Location.Width - 0.01_n;\n                                        Player[A].Pinched.Right4 = 2;\n                                    }\n                                    else\n                                    {\n                                        Player[A].Location.X = Block[B].Location.X + Block[B].Location.Width + 0.01_n;\n                                        Player[A].Pinched.Left2 = 2;\n                                    }\n\n                                    if(Block[B].Location.SpeedX != 0)\n                                    {\n                                        Player[A].Pinched.Moving = 2;\n                                        Player[A].Pinched.MovingLR = true;\n                                    }\n\n                                    if(Player[A].Mount == 2)\n                                    {\n                                        // cast through float because in VB6 the old X location was temporarily stored in mountBump, which is a float\n                                        Player[A].mountBump = (numf_t)(Player[A].Location.X - (num_t)(tempf_t)preWallX);\n                                    }\n\n                                    wallBlock = B;\n                                    hitWall = true;\n\n                                    // IMPORTANT: this case was truncation until v1.3.7.1-dev. Confirm that changing to VB6 rounding does not cause any issues.\n                                    blockPushX = num_t::vb6round(Block[B].Location.SpeedX);\n\n                                    if(Block[B].Slippy)\n                                        Player[A].SlippyWall = true;\n                                }\n                            }\n                            else if(HitSpot == 3) // hit the block from below\n                            {\n                                // add more generous margin to prevent unfair crush death with sloped ceiling\n                                bool ignore = (g_config.fix_player_crush_death\n                                    && (Block[B].Location.X + Block[B].Location.Width - 2 < Player[A].Location.X\n                                        || Player[A].Location.X + Player[A].Location.Width - 2 < Block[B].Location.X));\n\n                                if(!Player[A].ForceHitSpot3 && !Player[A].StandUp && !ignore)\n                                    Player[A].Pinched.Top3 = 2;\n\n                                if(Block[B].Location.SpeedY != 0 && !ignore)\n                                {\n                                    Player[A].Pinched.Moving = 2;\n                                    Player[A].Pinched.MovingUD = true;\n                                }\n\n                                hitCeiling = true;\n                                if(ceilingBlock1 == 0)\n                                    ceilingBlock1 = B;\n                                else\n                                    ceilingBlock2 = B;\n                            }\n                            else if(HitSpot == 5) // try to find out where the player hit the block from\n                            {\n                                if(oldSlope > 0)\n                                {\n                                    if(Block[oldSlope].Location.Height == 0)\n                                    {\n                                        // SMBX 1.3 would have crashed here\n                                    }\n                                    else\n                                    {\n                                        Player[A].Location.Y = Block[B].Location.Y + Block[B].Location.Height + 0.01_n;\n                                        num_t PlrMid = Player[A].Location.Y + Player[A].Location.Height;\n                                        num_t Slope = 1 - (PlrMid - Block[oldSlope].Location.Y) / (int_ok)Block[oldSlope].Location.Height;\n                                        if(Slope < 0)\n                                            Slope = 0;\n                                        if(Slope > 1)\n                                            Slope = 1;\n                                        if(BlockSlope[Block[oldSlope].Type] > 0)\n                                            Player[A].Location.X = Block[oldSlope].Location.X + Block[oldSlope].Location.Width - ((int_ok)Block[oldSlope].Location.Width * Slope);\n                                        else\n                                            Player[A].Location.X = Block[oldSlope].Location.X + ((int_ok)Block[oldSlope].Location.Width * Slope) - Player[A].Location.Width;\n                                        Player[A].Location.SpeedX = 0;\n                                    }\n                                }\n                                else\n                                {\n                                    insideBlock = B;\n\n                                    if(Block[B].Location.to_right_of(Player[A].Location))\n                                        Player[A].Pinched.Right4 = 2;\n                                    else\n                                        Player[A].Pinched.Left2 = 2;\n\n                                    if(Block[B].Location.SpeedX != 0 || Block[B].Location.SpeedY != 0)\n                                    {\n                                        Player[A].Pinched.Moving = 2;\n\n                                        if(Block[B].Location.SpeedX != 0)\n                                            Player[A].Pinched.MovingLR = true;\n\n                                        if(Block[B].Location.SpeedY != 0)\n                                            Player[A].Pinched.MovingUD = true;\n                                    }\n\n                                    Location_t playerFeet; // previously tempLocation\n                                    playerFeet.X = Player[A].Location.X;\n                                    playerFeet.Width = Player[A].Location.Width;\n                                    playerFeet.Y = Player[A].Location.Y + Player[A].Location.Height;\n                                    playerFeet.Height = 0.1_n;\n                                    bool hasFloor = false; // previously tempBool\n\n                                    // this could have caused an unusual TheXTech bug where lBlock would get overwritten\n                                    // (this wouldn't affect VB6 because loop bounds are evaluated only on loop start)\n\n                                    // fBlock = FirstBlock[(playerFeet.X / 32) - 1];\n                                    // lBlock = LastBlock[((playerFeet.X + playerFeet.Width) / 32.0) + 1];\n                                    // blockTileGet(playerFeet, fBlock, lBlock);\n\n                                    for(int C : treeFLBlockQuery(playerFeet, SORTMODE_COMPAT))\n                                    {\n                                        if(CheckCollision(playerFeet, Block[C].Location) && !Block[C].Hidden)\n                                        {\n                                            if(BlockSlope[Block[C].Type] == 0)\n                                                hasFloor = true;\n                                            else\n                                            {\n                                                Player[A].Location.Y = Block[B].Location.Y + Block[B].Location.Height; // + 0.01\n                                                num_t PlrMid = Player[A].Location.Y + Player[A].Location.Height;\n                                                num_t Slope = 1 - (PlrMid - Block[C].Location.Y) / (int_ok)Block[C].Location.Height;\n                                                if(Slope < 0)\n                                                    Slope = 0;\n                                                if(Slope > 1)\n                                                    Slope = 1;\n                                                if(BlockSlope[Block[C].Type] > 0)\n                                                    Player[A].Location.X = Block[C].Location.X + Block[C].Location.Width - ((int_ok)Block[C].Location.Width * Slope);\n                                                else\n                                                    Player[A].Location.X = Block[C].Location.X + ((int_ok)Block[C].Location.Width * Slope) - Player[A].Location.Width;\n                                                Player[A].Location.SpeedX = 0;\n                                                break;\n                                            }\n                                        }\n                                    }\n\n                                    // arbitrary but fine for here (for now)\n                                    // FIXME: look for cases where this is too high\n                                    cursed_value_C = numBlock;\n\n                                    /// TODO: find all cases where C is set and could \"stick\", and make them persistent\n                                    //// decided whether or not to care about 2P and higher (C values from NPC checks)\n\n                                    if(hasFloor)\n                                    {\n                                        Player[A].CanJump = false;\n                                        Player[A].Jump = 0;\n                                        Player[A].Location.X += -4 * Player[A].Direction;\n                                        Player[A].Location.Y += -Player[A].Location.SpeedY;\n                                        Player[A].Location.SpeedX = 0;\n                                        Player[A].Location.SpeedY = 0;\n\n                                        q.update(Player[A].Location, it);\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        else\n        {\n        }\n    }\n\n    if(Player[A].Character == 5 && Player[A].Duck && !Player[A].Rolling)\n        Player[A].Location.set_height_floor(Physics.PlayerDuckHeight[Player[A].Character][Player[A].State]);\n\n\n    // helps the player run down slopes at different angles\n    if(Player[A].Slope == 0 && oldSlope > 0 && Player[A].Mount != 1 && Player[A].Mount != 2 && !Player[A].Slide)\n    {\n        if(Block[oldSlope].Location.Width == 0)\n        {\n            // SMBX 1.3 would have crashed here\n        }\n        else if(Player[A].Location.SpeedY > 0)\n        {\n            tempf_t C = (tempf_t)(Player[A].Location.SpeedX * (int_ok)Block[oldSlope].Location.Height / (int_ok)Block[oldSlope].Location.Width * BlockSlope[Block[oldSlope].Type]);\n            if(C > 0)\n                Player[A].Location.SpeedY = (num_t)C;\n        }\n    }\n\n    if(hitWall && Player[A].Rolling)\n    {\n        int slope = BlockSlope[Block[wallBlock].Type];\n\n        // don't crash on slopes you should be going up!\n        if(slope != -Player[A].Direction)\n        {\n            BlockHit(wallBlock, false, A);\n            BlockHitHard(wallBlock);\n            PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n\n            // uses layer's speed to fix a bug where the sides of conveyor belts changed the player's speed\n            num_t block_speed = (num_t)Layer[Block[wallBlock].Layer].ApplySpeedX;\n            num_t rel_speed = Player[A].Location.SpeedX - block_speed;\n\n            if((rel_speed > 0) == (Player[A].Direction > 0) && !hitCeiling && Player[A].State == PLR_STATE_SHELL)\n            {\n                Player[A].Location.SpeedX = block_speed - rel_speed;\n\n                // help player go over 1-block gaps here\n                if(!floorBlock && !Player[A].StandingOnNPC && !Player[A].Slope)\n                {\n                    Player[A].Location.Y -= 4;\n                    Player[A].StandUp = true;\n                    Player[A].ForceHitSpot3 = true; // sets standup the next frame\n                }\n            }\n        }\n\n        hitWall = false;\n\n        // in polar, if the block can't be destroyed, do stop!\n        if(Player[A].State == PLR_STATE_POLAR)\n        {\n            if(!BlockIsBreakable(Block[wallBlock]))\n                hitWall = true;\n        }\n    }\n\n    if(floorBlock > 0) // For walking\n    {\n        if(Player[A].StandingOnNPC == -A) // fors standing on movable blocks\n        {\n            if(NPC[Player[A].StandingOnNPC].Special2 != 0)\n            {\n                Player[A].Location.SpeedX += -NPC[Player[A].StandingOnNPC].Location.SpeedX;\n                movingBlock = false;\n                Player[A].StandingOnNPC = 0;\n            }\n        }\n\n        // diggable dirt\n        if(Block[floorBlock].Type == 370 && Player[A].StandingOnNPC <= 0) // dig dirt\n        {\n            DontResetGrabTime = true;\n            // B = floorBlock;\n            if(Player[A].TailCount == 0 && Player[A].Controls.Down && Player[A].Controls.Run && Player[A].Mount == 0 && !Player[A].Stoned && Player[A].HoldingNPC == 0 && (Player[A].GrabTime > 0 || Player[A].RunRelease))\n            {\n                if((Player[A].GrabTime >= 12 && Player[A].Character < 3) || (Player[A].GrabTime >= 16 && Player[A].Character == 3) || (Player[A].GrabTime >= 8 && Player[A].Character == 4))\n                {\n                    Player[A].Location.SpeedX = (num_t)Player[A].GrabSpeed;\n                    Player[A].GrabSpeed = 0;\n                    Block[floorBlock].Hidden = true;\n                    Block[floorBlock].Layer = LAYER_DESTROYED_BLOCKS;\n                    syncLayersTrees_Block(floorBlock);\n                    NewEffect(EFFID_SMOKE_S3, Block[floorBlock].Location);\n                    // NOTE: SpeedY was not doubled for EFFID_SMOKE_S3 in SMBX 1.3\n                    Effect[numEffects].Location.SpeedY = -2;\n                    Player[A].GrabTime = 0;\n                }\n                else\n                {\n                    if(Player[A].GrabTime == 0)\n                    {\n                        PlaySoundSpatial(SFX_Grab, Player[A].Location);\n                        Player[A].FrameCount = 0;\n                        Player[A].GrabSpeed = (numf_t)Player[A].Location.SpeedX;\n                    }\n                    Player[A].Location.SpeedX = 0;\n                    Player[A].Slide = false;\n                    Player[A].GrabTime += 1;\n                }\n            }\n            else if(g_config.fix_player_stuck_on_dirt)\n                DontResetGrabTime = false;\n        }\n\n        bool cancelWalk = false;\n\n        if(hitWall)\n        {\n            if(!WalkingCollision(Player[A].Location, Block[floorBlock].Location))\n                cancelWalk = true;\n\n            // the logic below was previously duplicated with minor differences depending on the balue of hitWall\n        }\n\n        if(!cancelWalk)\n        {\n            Player[A].Location.Y = floorLocation.Y - Player[A].Location.Height;\n\n            // NEW: move aquatic swimming player away from the floor -- helps with spikes\n            if(Player[A].AquaticSwim)\n                Player[A].Location.Y -= 0.01_n;\n\n            if(!hitWall && Player[A].StandingOnNPC != 0)\n            {\n                if(NPC[Player[A].StandingOnNPC].Location.Y <= floorLocation.Y && Player[A].StandingOnNPC != Player[A].HoldingNPC)\n                    Player[A].Location.Y = NPC[Player[A].StandingOnNPC].Location.Y - Player[A].Location.Height;\n            }\n\n            if(Player[A].GroundPound)\n            {\n                YoshiPound(A, Player[A].Mount, true);\n                Player[A].GroundPound = false;\n            }\n            else if(Player[A].YoshiYellow)\n                YoshiPound(A, Player[A].Mount);\n\n            if(hitWall || Player[A].Slope == 0 || Player[A].Slide)\n                Player[A].Location.SpeedY = 0;\n\n            if(floorLocation.SpeedX != 0 || floorLocation.SpeedY != 0)\n            {\n                NPC[-A] = NPC_t();\n                NPC[-A].Location = floorLocation;\n                NPC[-A].Type = NPCID_METALBARREL;\n                NPC[-A].Active = true;\n                NPC[-A].TimeLeft = 100;\n                NPC[-A].Section = Player[A].Section;\n                NPC[-A].Special = floorBlock;\n                Player[A].StandingOnNPC = -A;\n                movingBlock = true;\n                Player[A].Location.SpeedY = 12;\n            }\n\n            if(hitWall)\n            {\n                // this series of if clauses did not exist in the hitWall case\n            }\n            else if(Player[A].StandingOnNPC != 0 && !movingBlock)\n            {\n                Player[A].Location.SpeedY = 1;\n                // the single Pinched variable has always been false since SMBX64\n                // if(!NPC[Player[A].StandingOnNPC].Pinched && !FreezeNPCs)\n                if(!FreezeNPCs)\n                    Player[A].Location.SpeedX += -NPC[Player[A].StandingOnNPC].Location.SpeedX - (num_t)NPC[Player[A].StandingOnNPC].BeltSpeed;\n                Player[A].StandingOnNPC = 0;\n            }\n            else if(movingBlock)\n            {\n                Player[A].Location.SpeedY = NPC[-A].Location.SpeedY + 1;\n                if(Player[A].Location.SpeedY < 0)\n                    Player[A].Location.SpeedY = 0;\n            }\n#if 0\n            // in the only case where this code is reachable, it has no effect\n            else\n            {\n                if(Player[A].Slope == 0 || Player[A].Slide)\n                    Player[A].Location.SpeedY = 0;\n            }\n#endif\n\n            if(Block[floorBlock].Type == 55 && !FreezeNPCs) // Make the player jump if the block is bouncy\n            {\n                BlockHit(floorBlock, true);\n\n                if(!Player[A].Slide)\n                    Player[A].Multiplier = 0;\n\n                Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n                PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n\n                if(Player[A].Controls.Jump || Player[A].Controls.AltJump)\n                {\n                    PlaySoundSpatial(SFX_Jump, Player[A].Location);\n                    Player[A].Jump = Physics.PlayerBlockJumpHeight;\n\n                    if(Player[A].Character == 2)\n                        Player[A].Jump += 3;\n\n                    if(Player[A].SpinJump)\n                        Player[A].Jump -= 6;\n                }\n            }\n\n            // smash turn blocks\n            if(Player[A].SpinJump && (Block[floorBlock].Type == 90 || Block[floorBlock].Type == 526) && Player[A].State > 1 && Block[floorBlock].Special == 0)\n            {\n                Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n                Block[floorBlock].Kill = true;\n                if(iBlocks < maxBlocks)\n                {\n                    iBlocks++;\n                    iBlock[iBlocks] = floorBlock;\n                }\n                // HitSpot = 0; // this definition is never used\n                floorBlock = 0;\n                Player[A].Jump = 7;\n\n                if(Player[A].Character == 2)\n                    Player[A].Jump += 3;\n\n                if(Player[A].Controls.Down)\n                {\n                    Player[A].Jump = 0;\n                    Player[A].Location.SpeedY = Physics.PlayerJumpVelocity / 2;\n                }\n            }\n        }\n    }\n\n    if(wallBlock > 0 && ceilingSlope > 0)\n    {\n        if(Block[ceilingSlope].Location.Y + Block[ceilingSlope].Location.Height == Block[wallBlock].Location.Y + Block[wallBlock].Location.Height)\n            hitWall = false;\n    }\n\n    if(!hitCeiling && hitWall)\n    {\n        if(Player[A].Location.SpeedX + NPC[Player[A].StandingOnNPC].Location.SpeedX > 0 && Player[A].Controls.Right)\n        {\n            Player[A].Location.SpeedX = 0.2_n * Player[A].Direction;\n            if(blockPushX > 0)\n                Player[A].Location.SpeedX += blockPushX;\n        }\n        else if(Player[A].Location.SpeedX + NPC[Player[A].StandingOnNPC].Location.SpeedX < 0 && Player[A].Controls.Left)\n        {\n            Player[A].Location.SpeedX = 0.2_n * Player[A].Direction;\n            if(blockPushX < 0)\n                Player[A].Location.SpeedX += blockPushX;\n        }\n        else\n        {\n            if(Player[A].Controls.Right || Player[A].Controls.Left)\n                Player[A].Location.SpeedX = -NPC[Player[A].StandingOnNPC].Location.SpeedX + 0.2_n * Player[A].Direction;\n            else\n                Player[A].Location.SpeedX = 0;\n        }\n\n        if(Player[A].Mount == 2)\n            Player[A].Location.SpeedX = 0;\n\n        // disable mid-hop jumps (fixes a clipping bug)\n        if(Player[A].State == PLR_STATE_AQUATIC && !Player[A].Mount && Player[A].MountSpecial)\n            Player[A].MountSpecial = 2;\n    }\n\n    int ceilingBlock = 0; // was called B\n\n    if(ceilingBlock2 != 0) // Hitting a block from below\n    {\n        num_t C = num_t::abs(Block[ceilingBlock1].Location.minus_center_x(Player[A].Location));\n        num_t D = num_t::abs(Block[ceilingBlock2].Location.minus_center_x(Player[A].Location));\n\n        if(C < D)\n            ceilingBlock = ceilingBlock1;\n        else\n            ceilingBlock = ceilingBlock2;\n    }\n    else if(ceilingBlock1 != 0)\n    {\n        ceilingBlock = ceilingBlock1;\n        if(Block[ceilingBlock].Location.X + Block[ceilingBlock].Location.Width - Player[A].Location.X <= 4)\n        {\n            Player[A].Location.X = Block[ceilingBlock].Location.X + Block[ceilingBlock].Location.Width + 0.1_n;\n            ceilingBlock = 0;\n        }\n        else if(Player[A].Location.X + Player[A].Location.Width - Block[ceilingBlock].Location.X <= 4)\n        {\n            Player[A].Location.X = Block[ceilingBlock].Location.X - Player[A].Location.Width - 0.1_n;\n            ceilingBlock = 0;\n        }\n    }\n\n    if(ceilingBlock > 0)\n    {\n        // NEW: bounce an aquatic-swimming player off the ceiling if it has an item\n        if(Player[A].AquaticSwim && Block[ceilingBlock].Special)\n            Player[A].SwimCount = MAZE_DIR_DOWN * 16 + 2;\n\n        // play ceiling hit sound if player is not aquatic swimming (normal) or if the ceiling block has an item\n        if(!Player[A].AquaticSwim || Block[ceilingBlock].Special)\n            PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n\n        Player[A].Location.Y = Block[ceilingBlock].Location.Y + Block[ceilingBlock].Location.Height + 0.01_n;\n\n        if(Player[A].State == PLR_STATE_CYCLONE && !Player[A].DoubleJump && Player[A].Jump)\n        {\n            Player[A].Location.SpeedY += 2.0_n;\n            Player[A].Jump -= 1;\n        }\n        else\n        {\n            Player[A].Location.SpeedY = -0.01_n + Block[ceilingBlock].Location.SpeedY;\n            Player[A].Jump = 0;\n        }\n\n        if(Player[A].Vine > 0)\n            Player[A].Location.Y += 0.1_n;\n\n        if(Player[A].Fairy || Player[A].Mount == 2 || Player[A].CanFly2)\n            Player[A].Location.SpeedY = 2;\n\n        if(Player[A].Mount != 2) // Tell the block it was hit\n            BlockHit(ceilingBlock, false, A);\n\n        if(Block[ceilingBlock].Type == 55) // If it is a bouncy block the knock the player down\n            Player[A].Location.SpeedY = 3;\n\n        if(Player[A].State > 1 && (Player[A].Character != 5 || Player[A].Rolling)) // If the player was big ask the block nicely to die\n        {\n            if(Player[A].Mount != 2 && Block[ceilingBlock].Type != 293)\n                BlockHitHard(ceilingBlock);\n        }\n    }\n\n    if(Player[A].StandingOnNPC != 0)\n    {\n        if(!hitWall)\n        {\n            // the single Pinched variable has always been false since SMBX64\n            // if(!NPC[Player[A].StandingOnNPC].Pinched && !FreezeNPCs)\n            if(!FreezeNPCs)\n                Player[A].Location.SpeedX += -NPC[Player[A].StandingOnNPC].Location.SpeedX - (num_t)NPC[Player[A].StandingOnNPC].BeltSpeed;\n        }\n    }\n\n    if(Player[A].Slide && oldSlope > 0 && Player[A].Slope == 0 && Player[A].Location.SpeedY < 0)\n    {\n        if(Player[A].NoGravity == 0)\n        {\n            // Player[A].NoGravity = static_cast<int>(floor(static_cast<double>(Player[A].Location.SpeedY / Physics.PlayerJumpVelocity * 8)));\n            // PlayerJumpVelocity is -5.7, SpeedY is negative\n            Player[A].NoGravity = (int)(Player[A].Location.SpeedY * -80) / 57;\n        }\n    }\n    else if(Player[A].Slope > 0 || oldSlope > 0 || !Player[A].Slide)\n        Player[A].NoGravity = 0;\n\n    // if(Player[A].Slide)  // Simplified below\n    // {\n    //     if(Player[A].Location.SpeedX > 1 || Player[A].Location.SpeedX < -1)\n    //         Player[A].SlideKill = true;\n    //     else\n    //         Player[A].SlideKill = false;\n    // }\n    // else\n    //     Player[A].SlideKill = false;\n\n    Player[A].SlideKill = Player[A].Slide && (num_t::abs(Player[A].Location.SpeedX) > 1);\n}\n"
  },
  {
    "path": "src/player/player_char5_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"config.h\"\n#include \"sound.h\"\n#include \"effect.h\"\n#include \"eff_id.h\"\n#include \"collision.h\"\n\n#include \"player/player_update_priv.h\"\n#include \"main/trees.h\"\n\n// called after movement code and before block / NPC / player collisions\nvoid PlayerChar5Logic(int A)\n{\n    // TODO: State-dependent moment\n    if(Player[A].State == 4 || Player[A].State == 5)\n    {\n        bool hasNoMonts = (g_config.fix_char5_vehicle_climb && Player[A].Mount <= 0) ||\n                           !g_config.fix_char5_vehicle_climb;\n\n        bool turnFairy = Player[A].FlyCount > 0 ||\n                        ((Player[A].Controls.AltJump || (Player[A].Controls.Jump && Player[A].FloatRelease)) &&\n                          Player[A].Location.SpeedY != Physics.PlayerGravity && Player[A].Slope == 0 &&\n                          Player[A].StandingOnNPC == 0);\n\n        if(turnFairy && hasNoMonts)\n        {\n            if(Player[A].FlyCount > 0)\n                Player[A].FairyCD = 0;\n\n            if(!Player[A].Fairy && Player[A].FairyCD == 0 && Player[A].Jump == 0 && Player[A].Wet == 0)\n            {\n                Player[A].Jump = 0;\n                if(Player[A].FlyCount == 0)\n                    Player[A].FlyCount = 50;\n                Player[A].FairyTime = Player[A].FlyCount;\n                Player[A].FairyCD = 1;\n                Player[A].FlyCount = 0;\n                Player[A].Fairy = true;\n                SizeCheck(Player[A]);\n                PlaySoundSpatial(SFX_HeroFairy, Player[A].Location);\n                Player[A].Immune = 10;\n                Player[A].Effect = PLREFF_WAITING;\n                Player[A].Effect2 = 4;\n                NewEffect(EFFID_SMOKE_S5, Player[A].Location);\n            }\n        }\n\n        if(Player[A].Controls.Run && Player[A].RunRelease && (Player[A].FairyTime > 0 || Player[A].Effect == PLREFF_WAITING))\n        {\n            Player[A].FairyTime = 0;\n            Player[A].Controls.Run = false;\n        }\n\n        if(Player[A].Fairy)\n        {\n            if(Player[A].Slope > 0 || Player[A].StandingOnNPC > 0)\n            {\n                Player[A].FairyTime = 0;\n                Player[A].FairyCD = 0;\n            }\n        }\n        // Coins += -1\n        // If Coins < 0 Then\n        // Lives += -1\n        // Coins += 99\n        // If Lives < 0 Then\n        // Lives = 0\n        // Coins = 0\n        // .FairyTime = 0\n        // End If\n        // End If\n        // End If\n    }\n\n    if(Player[A].HasKey)\n        KeyholeCheck(A, Player[A].Location);\n\n    if(Player[A].SwordPoke < 0)\n    {\n        Player[A].SwordPoke -= 1;\n\n        if(Player[A].SwordPoke == -7)\n            Player[A].SwordPoke = 1;\n\n        if(Player[A].SwordPoke == -40)\n            Player[A].SwordPoke = 0;\n\n        if(!(Player[A].Slippy && !Player[A].Controls.Left && !Player[A].Controls.Right))\n        {\n            if(Player[A].FireBallCD == 0 && Player[A].Location.SpeedX != 0)\n                Player[A].SwordPoke = 0;\n        }\n    }\n    else if(Player[A].SwordPoke > 0)\n    {\n        // NEW LOGIC: bit 6 (32) of SwordPoke is a cancel flag\n        if(Player[A].SwordPoke == 1)\n        {\n            TailSwipe(A, true, true);\n            PlaySoundSpatial(SFX_HeroStab, Player[A].Location);\n\n            // TODO: State-dependent moment\n            if((Player[A].State == 3 || Player[A].State == 7 || Player[A].State == 6 || Player[A].State == PLR_STATE_POLAR) && Player[A].FireBallCD2 == 0)\n                PlayerShootChar5Beam(A);\n        }\n        else if((Player[A].SwordPoke & 32) == 0)\n            TailSwipe(A, false, true);\n\n        Player[A].SwordPoke += 1;\n\n        if((Player[A].SwordPoke & 31) >= 10)\n        {\n            if(Player[A].Duck)\n            {\n                Player[A].SwordPoke = 0;\n                Player[A].FireBallCD = 7;\n            }\n            else\n            {\n                Player[A].SwordPoke = -11;\n                Player[A].FireBallCD = 0;\n            }\n        }\n    }\n\n    if(Player[A].FireBallCD == 0 && Player[A].Wet == 0 && !Player[A].Fairy && Player[A].Mount == 0 && !Player[A].Rolling)\n    {\n        // Link ducks when jumping\n        // Holding Up cancels this and allows upwards stab\n        if(!Player[A].Duck && Player[A].Location.SpeedY < Physics.PlayerGravity && Player[A].StandingOnNPC == 0 &&\n            Player[A].Slope == 0 && !Player[A].Controls.Up && !Player[A].Stoned)\n        {\n            Player[A].SwordPoke = 0;\n            Player[A].Duck = true;\n            Player[A].Location.set_height_floor(Physics.PlayerDuckHeight[Player[A].Character][Player[A].State]);\n        }\n        // Link stands when falling\n        else if(Player[A].Duck && Player[A].Location.SpeedY > Physics.PlayerGravity && Player[A].StandingOnNPC == 0 && Player[A].Slope == 0)\n        {\n            Player[A].SwordPoke = 0;\n            UnDuck(Player[A]);\n        }\n    }\n\n    if(Player[A].Mount > 0 && Player[A].Mount != 2)\n    {\n        PlayerHurt(A);\n        Player[A].Mount = 0;\n    }\n\n    Player[A].HoldingNPC = -1;\n}\n\n// called in PowerUps() -- executes before movement code\nvoid PlayerChar5StabLogic(int A)\n{\n    Player_t& p = Player[A];\n\n    if(p.Bombs > 0 && p.Controls.AltRun && p.RunRelease)\n    {\n        p.FireBallCD = 10;\n        PlayerThrowBomb(A);\n    }\n    else if(/*p.FireBallCD == 0 && */ p.Controls.Run && p.RunRelease) // cooldown is 0 whenever this code is reached\n    {\n        p.FireBallCD = 20;\n\n        if(p.Location.SpeedY != Physics.PlayerGravity && p.StandingOnNPC == 0 && p.Slope == 0) // Link ducks when jumping\n        {\n            if(p.Wet == 0 && !p.WetFrame)\n            {\n                if(p.Controls.Down && !p.Duck && p.Mount == 0)\n                {\n                    p.Duck = true;\n                    p.Location.set_height_floor(Physics.PlayerDuckHeight[p.Character][p.State]);\n                }\n                else if(!p.Controls.Down && p.Duck)\n                    UnDuck(Player[A]);\n            }\n        }\n\n        if(p.Duck)\n            p.SwordPoke = 1;\n        else\n            p.SwordPoke = -1;\n    }\n    else if(p.Controls.Up && p.Location.SpeedY < 0 && !p.Duck && p.SwordPoke == 0) // Link stabs up\n    {\n        if(!p.WetFrame && p.Frame == 10)\n            TailSwipe(A, true, true, 1);\n    }\n    else if(p.Controls.Down && (p.Location.SpeedY > 0 && p.StandingOnNPC == 0 && p.Slope == 0) && !p.Duck && p.SwordPoke == 0) // Link stabs down\n    {\n        if(!p.WetFrame && p.Frame == 9)\n            TailSwipe(A, true, true, 2);\n    }\n}\n"
  },
  {
    "path": "src/player/player_death_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"config.h\"\n#include \"message.h\"\n#include \"script/luna/lunacounter.h\"\n\n// updates the position of a player that has just died\nvoid UpdatePlayerTimeToLive(int A)\n{\n    Player[A].TimeToLive += 1;\n\n    if(Player[A].TimeToLive == 50 && !g_ClonedPlayerMode)\n        gDeathCounter.MarkDeath();\n\n    const Screen_t& screen = ScreenByPlayer(A);\n    bool dynamic_screen = (screen.Type == ScreenTypes::Dynamic);\n    bool shared_screen = (screen.Type == ScreenTypes::SharedScreen);\n    bool split_screen = (screen.active_end() - screen.active_begin() > 1);\n    bool normal_multiplayer = (dynamic_screen || shared_screen || split_screen || XMessage::GetStatus() != XMessage::Status::local);\n\n    bool player_timer_done = (Player[A].TimeToLive >= 200);\n\n    // checks if the dead player can move towards a target location\n    bool player_can_move = (player_timer_done || shared_screen || !normal_multiplayer);\n\n    // there was a guard here that has now been moved into the subclauses\n    // if(Player[A].TimeToLive >= 200 || ScreenType != 5)\n    int B = CheckNearestLiving(A);\n\n    bool someone_else_alive = false;\n    for(int o_A = 1; o_A <= numPlayers; o_A++)\n    {\n        if(o_A == A)\n            continue;\n\n        if(!Player[o_A].Dead || (BattleMode && BattleLives[o_A] > 0))\n        {\n            someone_else_alive = true;\n\n            if(shared_screen && B == 0 && Player[o_A].TimeToLive < Player[A].TimeToLive)\n                B = o_A;\n\n            break;\n        }\n    }\n\n    // allow smooth panning in cloned player mode\n    if(g_config.multiplayer_pause_controls && !normal_multiplayer && g_ClonedPlayerMode && B == 0 && someone_else_alive)\n        B = 1;\n\n    // move dead player towards start point in BattleMode\n    bool battle_respawn = (BattleMode && BattleLives[A] > 0 && someone_else_alive && BattleWinner == 0);\n\n    if(battle_respawn && player_can_move)\n    {\n        // using B as a temporary slot here\n        B = maxPlayers - 1;\n\n        Player[B].Location.Width = Player[A].Location.Width;\n        Player[B].Location.Height = Player[A].Location.Height;\n\n        // eventually, check for valid starts\n        constexpr int valid_start_count = 2;\n        int use_start = (A - 1) % valid_start_count + 1;\n\n        // NOTE, there is a bugfix here without a compat flag, previously the * 0.5 did not exist\n        constexpr bool do_bugfix = true;\n        Player[B].Location.X = PlayerStart[use_start].X + PlayerStart[use_start].Width / 2 - Player[A].Location.Width / (do_bugfix ? 2 : 1);\n        Player[B].Location.Y = PlayerStart[use_start].Y + PlayerStart[use_start].Height - Player[A].Location.Height;\n        CheckSection(B);\n        if(Player[A].Section != Player[B].Section)\n        {\n            Player[A].Location = Player[B].Location;\n            Player[A].Section = Player[B].Section;\n        }\n    }\n\n    if(B > 0 && player_can_move) // Move camera to the other living players\n    {\n        // previously floats\n        num_t A1, B1;\n        if(shared_screen)\n        {\n            const vScreen_t& vscreen = screen.vScreen(screen.active_begin() + 1);\n            A1 = (Player[B].Location.X - Player[A].Location.X) + (Player[B].Location.Width - Player[A].Location.Width) / 2;\n            if(!g_config.multiplayer_pause_controls)\n                B1 = ((-vscreen.Y + vscreen.Height / 2) - Player[A].Location.Y);\n            else\n                B1 = ((-vscreen.Y + vscreen.Height / 2) - Player[A].Location.Y - Player[A].Location.Height);\n        }\n        else if(normal_multiplayer)\n        {\n            A1 = (Player[B].Location.X - Player[A].Location.X) + (Player[B].Location.Width - Player[A].Location.Width) / 2;\n            if(!g_config.multiplayer_pause_controls)\n                B1 = Player[B].Location.Y - Player[A].Location.Y;\n            else\n                B1 = Player[B].Location.Y + Player[B].Location.Height - Player[A].Location.Y - Player[A].Location.Height;\n        }\n        else\n        {\n            const vScreen_t& vscreen = screen.vScreen(screen.active_begin() + 1);\n            A1 = ((-vscreen.X + vscreen.Width / 2) - (Player[A].Location.X + Player[A].Location.Width / 2));\n            if(!g_config.multiplayer_pause_controls)\n                B1 = ((-vscreen.Y + vscreen.Height / 2) - Player[A].Location.Y);\n            else\n                B1 = ((-vscreen.Y + vscreen.Height / 2) - Player[A].Location.Y - Player[A].Location.Height);\n        }\n\n        num_t C1 = num_t::dist(A1, B1);\n        num_t X, Y;\n        if(C1 != 0)\n        {\n            X = A1.divided_by(C1);\n            Y = B1.divided_by(C1);\n        }\n        else\n        {\n            X = 0;\n            Y = 0;\n        }\n        Player[A].Location.X += X * 10;\n        Player[A].Location.Y += Y * 10;\n\n        // update Player A section (was previously guarded in ScreenType == 5)\n        // code previously used Player 1 and Player 2 but this doesn't differ from that logic in cheat-free SMBX64\n        if(normal_multiplayer && Player[A].Section != Player[B].Section)\n        {\n            C1 = 0;\n\n            Player[A].Location.X = Player[B].Location.X;\n            Player[A].Location.Y = Player[B].Location.Y;\n            CheckSection(A);\n        }\n\n        if(C1 < 10 && C1 > -10)\n        {\n            KillPlayer(A);\n\n            // new logic: mark which player A's ghost is following\n            if(normal_multiplayer && !shared_screen && Player[A].Dead)\n            {\n                Player[A].Effect2 = -B;\n\n                // new logic: fix player's location\n                if(g_config.multiplayer_pause_controls)\n                {\n                    Player[A].Location.X = Player[B].Location.X + (Player[B].Location.Width - Player[A].Location.Width) / 2;\n                    Player[A].Location.Y = Player[B].Location.Y + Player[B].Location.Height - Player[A].Location.Height;\n                }\n            }\n        }\n    }\n    // start fadeout (65 / 3) frames before level end\n    else if(!BattleMode && B == 0 && Player[A].TimeToLive == 200 - (65 / 3))\n    {\n        ProcessLastDead(); // Fade out screen if the last player died\n    }\n    else if((!BattleMode || B == 0) && player_timer_done) // ScreenType = 1\n    {\n        num_t old_LocX = Player[A].Location.X;\n        num_t old_LocY = Player[A].Location.Y;\n        KillPlayer(A); // Time to die\n\n        // new logic: fix player's location\n        if(g_config.multiplayer_pause_controls)\n        {\n            Player[A].Location.X = old_LocX;\n            Player[A].Location.Y = old_LocY;\n        }\n    }\n}\n\n// updates the position of a player that is fully dead\nvoid UpdatePlayerDead(int A)\n{\n    // safer than the below code, should always be used except for compatibility concerns\n    if(numPlayers > 2 || g_config.multiplayer_pause_controls)\n    {\n        int B;\n\n        // continue following currently-tracked player if possible\n        if(Player[A].Effect2 < 0)\n        {\n            B = -Player[A].Effect2;\n\n            // check if tracked dead player is gone\n            if(B > numPlayers || Player[B].Dead || Player[B].TimeToLive > 0)\n            {\n                Player[A].Effect2 = 0;\n                B = 0;\n\n                // put player back in TimeToLive state if there are still other players\n                if(CheckNearestLiving(A))\n                {\n                    Player[A].Dead = false;\n                    Player[A].TimeToLive = 200;\n                }\n            }\n        }\n        else\n            B = CheckNearestLiving(A);\n\n        if(B)\n        {\n            Player[A].Location.X = Player[B].Location.X + (Player[B].Location.Width - Player[A].Location.Width) / 2;\n            Player[A].Location.Y = Player[B].Location.Y + Player[B].Location.Height - Player[A].Location.Height;\n\n            if(Player[B].Section != Player[A].Section)\n            {\n                int new_sect = Player[B].Section;\n\n#ifdef THEXTECH_ENABLE_SDL_NET\n                // do the music update thing here\n                if(&ScreenByPlayer(A) == l_screen && &ScreenByPlayer(B) != l_screen)\n                {\n                    if(curMusic != bgMusic[new_sect] || (curMusic == 24 && CustomMusic[new_sect] != CustomMusic[Player[A].Section]))\n                        StartMusic(new_sect);\n                }\n#endif\n\n                Player[A].Section = new_sect;\n            }\n        }\n    }\n    else\n    {\n        if(A == 1)\n        {\n            Player[A].Location.X = Player[2].Location.X;\n            Player[A].Location.Y = Player[2].Location.Y;\n            CheckSection(A);\n        }\n        else\n        {\n            Player[A].Location.X = Player[1].Location.X;\n            Player[A].Location.Y = Player[1].Location.Y;\n            CheckSection(A);\n        }\n    }\n}\n"
  },
  {
    "path": "src/player/player_effect.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef ENUM_PLAYER_EFFECT_HHH\n#define ENUM_PLAYER_EFFECT_HHH\n\n#include \"global_constants.h\"\n\nenum PlayerState : vbint_t\n{\n    PLR_STATE_SMALL = 1,\n    PLR_STATE_BIG = 2,\n    PLR_STATE_FIRE = 3,\n    PLR_STATE_LEAF = 4,\n    PLR_STATE_STATUE = 5,\n    PLR_STATE_HEAVY = 6,\n    PLR_STATE_ICE = 7,\n    PLR_STATE_AQUATIC = 8,\n    PLR_STATE_POLAR = 9,\n    PLR_STATE_CYCLONE = 10,\n    PLR_STATE_SHELL = 11,\n};\n\nenum PlayerEffect : vbint_t\n{\n    PLREFF_NORMAL = 0,\n    PLREFF_TURN_BIG = 1,\n    PLREFF_TURN_SMALL = 2,\n    PLREFF_WARP_PIPE = 3,\n    PLREFF_RESPAWN = 6,\n    PLREFF_WARP_DOOR = 7,\n    PLREFF_WAITING = 8,\n    PLREFF_NO_COLLIDE = 9,\n    PLREFF_PET_INSIDE = 10,\n    PLREFF_COOP_WINGS = 13,\n    PLREFF_GROW_TO_STATE = 16,\n    PLREFF_GROW_TO_STATE_END = 32,\n    PLREFF_TURN_TO_STATE = 32,\n    PLREFF_TURN_TO_STATE_END = 48,\n    PLREFF_STATE_TO_BIG = 48,\n    PLREFF_STATE_TO_BIG_END = 64,\n    PLREFF_STONE = 500,\n\n    // old SMBX 1.3 Effect IDs\n    // PLREFF_TURN_FIRE = 4,\n    // PLREFF_TURN_LEAF = 5,\n    // PLREFF_TURN_STATUE = 11,\n    // PLREFF_TURN_HEAVY = 12,\n    // PLREFF_TURN_ICE = 41,\n    // PLREFF_FIRE_TO_BIG = 227,\n    // PLREFF_ICE_TO_BIG = 228,\n};\n\n\n#endif // ENUM_PLAYER_EFFECT_HHH\n"
  },
  {
    "path": "src/player/player_fairy_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"collision.h\"\n#include \"effect.h\"\n#include \"eff_id.h\"\n#include \"sound.h\"\n#include \"config.h\"\n#include \"npc_traits.h\"\n\n#include \"player/player_update_priv.h\"\n\n#include \"main/trees.h\"\n\nvoid PlayerFairyTimerUpdate(int A)\n{\n    if(Player[A].FairyTime != 0 && Player[A].Fairy)\n    {\n        if(iRand(10) == 0)\n            p_PlayerMakeFlySparkle(Player[A].Location);\n\n        if(Player[A].FairyTime > 0)\n            Player[A].FairyTime -= 1;\n\n        if(Player[A].FairyTime != -1 && Player[A].FairyTime < 20 && Player[A].Character == 5 && PlayerFairyOnVine(A))\n        {\n            Player[A].FairyTime = 20;\n            Player[A].FairyCD = 0;\n        }\n    }\n    // lose fairy power\n    else if(Player[A].Fairy)\n    {\n        PlaySoundSpatial(SFX_HeroFairy, Player[A].Location);\n        Player[A].Immune = 10;\n        Player[A].Effect = PLREFF_WAITING;\n        Player[A].Effect2 = 4;\n        Player[A].Fairy = false;\n        SizeCheck(Player[A]);\n        NewEffect(EFFID_SMOKE_S5, Player[A].Location);\n        PlayerPush(A, 3);\n    }\n    else\n        Player[A].FairyTime = 0;\n\n    if(Player[A].FairyCD != 0 && (Player[A].Location.SpeedY == 0 || Player[A].Slope != 0 || Player[A].StandingOnNPC != 0 || Player[A].WetFrame))\n        Player[A].FairyCD -= 1;\n}\n\nvoid PlayerFairyMovementX(int A)\n{\n    if(Player[A].Controls.Right)\n    {\n        if(Player[A].Location.SpeedX < 3)\n            Player[A].Location.SpeedX += 0.15_n;\n        if(Player[A].Location.SpeedX < 0)\n            Player[A].Location.SpeedX += 0.1_n;\n    }\n    else if(Player[A].Controls.Left)\n    {\n        if(Player[A].Location.SpeedX > -3)\n            Player[A].Location.SpeedX -= 0.15_n;\n        if(Player[A].Location.SpeedX > 0)\n            Player[A].Location.SpeedX -= 0.1_n;\n    }\n    else if(Player[A].Location.SpeedX > 0.1_n)\n        Player[A].Location.SpeedX -= 0.1_n;\n    else if(Player[A].Location.SpeedX < -0.1_n)\n        Player[A].Location.SpeedX += 0.1_n;\n    else\n        Player[A].Location.SpeedX = 0;\n}\n\nvoid PlayerFairyMovementY(int A)\n{\n    Player[A].WetFrame = false;\n    Player[A].Wet = 0;\n\n    // this was previously two separate clauses in VB6 (split by whether FairyCD was 0)\n    if(Player[A].Controls.Jump || Player[A].Controls.AltJump || Player[A].Controls.Up)\n    {\n        Player[A].Location.SpeedY -= 0.15_n;\n        if(Player[A].Location.SpeedY > 0)\n            Player[A].Location.SpeedY -= 0.1_n;\n    }\n    else if(Player[A].FairyCD != 0 || Player[A].Location.SpeedY < -0.1_n || Player[A].Controls.Down)\n    {\n        if(Player[A].Location.SpeedY < 3)\n            Player[A].Location.SpeedY += Physics.PlayerGravity / 20;\n        if(Player[A].Location.SpeedY < 0)\n            Player[A].Location.SpeedY += Physics.PlayerGravity / 20;\n        Player[A].Location.SpeedY += Physics.PlayerGravity / 10;\n        if(Player[A].Controls.Down)\n            Player[A].Location.SpeedY += 0.05_n;\n    }\n    else if(Player[A].Location.SpeedY > 0.1_n)\n        Player[A].Location.SpeedY -= 0.15_n;\n    else\n        Player[A].Location.SpeedY = 0;\n\n    if(Player[A].Location.SpeedY > 4)\n        Player[A].Location.SpeedY = 4;\n    else if(Player[A].Location.SpeedY < -3)\n        Player[A].Location.SpeedY = -3;\n}\n"
  },
  {
    "path": "src/player/player_movement_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"sound.h\"\n#include \"player.h\"\n#include \"config.h\"\n#include \"effect.h\"\n#include \"eff_id.h\"\n#include \"phys_env.h\"\n\n#include \"player/player_update_priv.h\"\n#include \"npc/npc_cockpit_bits.h\"\n\nvoid s_playerSlopeMomentum(int A);\n\nvoid PlayerMovementX(int A, tempf_t& cursed_value_C)\n{\n    bool is_grounded = (Player[A].Location.SpeedY == 0 || Player[A].Slope != 0 || Player[A].StandingOnNPC != 0);\n    bool aquatic_jumps = (Player[A].State == PLR_STATE_AQUATIC && Player[A].Character != 5 && !Player[A].Mount && !Player[A].HoldingNPC && (is_grounded || (Player[A].Duck && !Player[A].Jump)));\n\n    // Modify player's speed if he is running up/down hill\n    tempf_t speedVar = 1; // Speed var is a percentage of the player's speed\n    if(Player[A].Slope > 0)\n    {\n        if(Block[Player[A].Slope].Location.Width == 0)\n        {\n            // SMBX 1.3 would have crashed here\n        }\n        else if(\n                (Player[A].Location.SpeedX > 0 && BlockSlope[Block[Player[A].Slope].Type] == -1) ||\n                (Player[A].Location.SpeedX < 0 && BlockSlope[Block[Player[A].Slope].Type] == 1)\n                )\n            speedVar = (tempf_t)(1 - Block[Player[A].Slope].Location.Height / (int_ok)Block[Player[A].Slope].Location.Width / 2);\n        else if(!Player[A].Slide)\n            speedVar = (tempf_t)(1 + (Block[Player[A].Slope].Location.Height / (int_ok)Block[Player[A].Slope].Location.Width / 4));\n    }\n\n    if(Player[A].Stoned) // if statue form reset to normal\n        speedVar = 1;\n\n    if(Player[A].Character == 3)\n        speedVar = (tempf_t)((num_t)speedVar * 0.93_r);\n\n    if(Player[A].Character == 4)\n        speedVar = (tempf_t)((num_t)speedVar * 1.07_r);\n\n    if(Player[A].State == PLR_STATE_AQUATIC)\n        speedVar = (tempf_t)((num_t)speedVar * 0.87_r);\n\n    if(Player[A].State == PLR_STATE_POLAR && Player[A].Slippy)\n        speedVar += (tempf_t)0.05_n;\n\n    // modify speedvar to slow the player down under water\n    if(Player[A].Wet > 0)\n    {\n        if(is_grounded)\n            speedVar /= 4; // if walking go really slow\n        else\n            speedVar /= 2; // if swimming go slower faster the walking\n    }\n\n    // special logic for shell: just keep going!\n    if(((Player[A].State == PLR_STATE_SHELL && !Player[A].Wet) || Player[A].State == PLR_STATE_POLAR) && Player[A].Controls.Run && !Player[A].HoldingNPC && !Player[A].Mount)\n    {\n        // 7.1 is the NPC shellspeed\n        num_t shell_speed = (num_t)speedVar * 7.1_r;\n\n        if(shell_speed >= 7.5_n)\n            shell_speed = 7.5_n;\n\n        bool can_begin = is_grounded;\n        if(Player[A].Slope && BlockSlope[Block[Player[A].Slope].Type] != Player[A].Direction)\n            can_begin = false;\n\n        num_t stop_speed = 0.5_n;\n        if(Player[A].Slope)\n        {\n            // allow turning around on slopes\n            stop_speed = 0.0_n;\n        }\n\n        num_t start_speed = shell_speed;\n\n        if(Player[A].State == PLR_STATE_POLAR)\n        {\n            shell_speed -= 0.75_n;\n            if(Player[A].Slippy)\n                shell_speed += 3;\n\n            // allow starting quickly\n            start_speed -= 2;\n\n            // or extremely quickly on a slope or ice\n            if(Player[A].Slope || Player[A].Slippy)\n                start_speed = 0.5_n;\n\n            // require holding down to start\n            if(!Player[A].Controls.Down)\n                start_speed = 128;\n        }\n\n        // start rolling\n        if(can_begin && num_t::abs(Player[A].Location.SpeedX) >= start_speed)\n            Player[A].Rolling = true;\n\n        // keep rolling\n        if(Player[A].Rolling && (!is_grounded || num_t::abs(Player[A].Location.SpeedX) > stop_speed))\n        {\n            if(!Player[A].Duck)\n            {\n                Player[A].Duck = true;\n                SizeCheck(Player[A]);\n            }\n\n            Player[A].Direction = (Player[A].Location.SpeedX > 0) ? 1 : -1;\n\n            // necessarily in air in this case, use controls to pick direction if possible, otherwise don't change speed\n            if(Player[A].Location.SpeedX == 0)\n            {\n                if(Player[A].Controls.Right)\n                    Player[A].Direction = 1;\n                else if(!Player[A].Controls.Left)\n                    return;\n            }\n\n            if(Player[A].Slope)\n                s_playerSlopeMomentum(A);\n            else\n                Player[A].Location.SpeedX = (Player[A].Location.SpeedX * 127 + Player[A].Direction * shell_speed) / 128;\n\n            return;\n        }\n\n        // stop rolling\n        // allow the player to go faster if they aren't a shell yet\n        if(Player[A].State == PLR_STATE_SHELL)\n            speedVar = speedVar * 5 / 4;\n    }\n\n    if(Player[A].Rolling)\n        Player[A].Slide = false;\n\n    Player[A].Rolling = false;\n\n    // ducking for link\n    if(Player[A].Duck && Player[A].WetFrame)\n    {\n        if(!is_grounded)\n            UnDuck(Player[A]);\n    }\n\n    // the following code controls the players ability to duck\n    if(!(Player[A].Character == 5 && (!is_grounded || Player[A].FireBallCD != 0))) // Link can't duck/unduck in air\n    {\n        if(Player[A].Controls.Down && !Player[A].SpinJump &&\n           !Player[A].Stoned && Player[A].Vine == 0 && !Player[A].Slide &&\n           (Player[A].Slope == 0 || Player[A].Mount > 0 || Player[A].WetFrame ||\n            Player[A].Character >= 3 || Player[A].GrabTime > 0) &&\n           ((!Player[A].WetFrame || Player[A].Character >= 3) ||\n            is_grounded || Player[A].Mount == 1) &&\n           !Player[A].Fairy && !Player[A].ShellSurf && !Player[A].Driving)\n        {\n            Player[A].Bumped = false;\n            if(Player[A].Mount != 2) // cant duck in the clown car\n            {\n                if(Player[A].Mount == 3) // duck on a yoshi\n                {\n                    if(!Player[A].Duck)\n                    {\n                        Player[A].Location.set_height_floor(31);\n                        Player[A].Duck = true;\n                        // If nPlay.Online = True And A = nPlay.MySlot + 1 Then Netplay.sendData Netplay.PutPlayerLoc(nPlay.MySlot) & \"1q\" & A & LB\n//                                        if(nPlay.Online == true && A == nPlay.MySlot + 1)\n//                                            Netplay::sendData \"1q\" + std::to_string(A) + LB;\n                    }\n                }\n                else // normal duck\n                {\n                    if((Player[A].State > 1 && Player[A].HoldingNPC <= 0) || (Player[A].Character == 3 || Player[A].Character == 4 || Player[A].Character == 5))\n                    {\n                        if(!Player[A].Duck && Player[A].TailCount == 0) // Player ducks\n                        {\n                            if(Player[A].Character == 5)\n                                Player[A].SwordPoke = 0;\n                            Player[A].Duck = true;\n                            Player[A].Location.set_height_floor(Physics.PlayerDuckHeight[Player[A].Character][Player[A].State]);\n//                                            if(nPlay.Online == true && A == nPlay.MySlot + 1)\n//                                                Netplay::sendData \"1q\" + std::to_string(A) + LB;\n                        }\n                    }\n                    else if(Player[A].Mount == 1)\n                    {\n                        if(!Player[A].Duck && Player[A].TailCount == 0) // Player ducks\n                        {\n                            Player[A].Duck = true;\n                            Player[A].Location.Height = Physics.PlayerDuckHeight[1][2];\n                            Player[A].Location.Y += -Physics.PlayerDuckHeight[1][2] + Physics.PlayerHeight[1][2];\n//                                            if(nPlay.Online == true && A == nPlay.MySlot + 1)\n//                                                Netplay::sendData \"1q\" + std::to_string(A) + LB;\n                        }\n                    }\n                }\n            }\n        }\n        else\n        {\n            if(Player[A].Duck)\n                UnDuck(Player[A]);\n        }\n    }\n\n    qdec_t local_C = 1.0_r;\n    cursed_value_C = 1;\n    // If .Character = 5 Then C = 0.94\n    if(Player[A].Character == 5)\n    {\n        cursed_value_C = (tempf_t)0.95_n;\n        local_C = 0.95_r;\n    }\n\n    // deduplicated (was previously separate sections for holding Left and Right)\n    if((Player[A].Controls.Left || Player[A].Controls.Right) &&\n       !Player[A].JumpOffWall &&\n       !aquatic_jumps &&\n       ((!Player[A].Duck && Player[A].GrabTime == 0) ||\n        !is_grounded ||\n        Player[A].Mount == 1)\n    )\n    {\n        int dir = (Player[A].Controls.Left) ? -1 : 1;\n        Player[A].Bumped = false;\n\n        if(Player[A].Controls.Run || dir * Player[A].Location.SpeedX < Physics.PlayerWalkSpeed * (num_t)speedVar || Player[A].Character == 5)\n        {\n            // turning around or not yet walking\n            if(dir * Player[A].Location.SpeedX < Physics.PlayerWalkSpeed * (num_t)speedVar * local_C)\n            {\n                if(Player[A].Character == 2) // LUIGI\n                    Player[A].Location.SpeedX += dir * num_t::from_double(-0.1 * 0.175);\n                else if(Player[A].Character == 3) // PEACH\n                    Player[A].Location.SpeedX += dir * num_t::from_double(-0.05 * 0.175);\n                else if(Player[A].Character == 4) // toad\n                    Player[A].Location.SpeedX += dir * num_t::from_double(0.05 * 0.175);\n\n                Player[A].Location.SpeedX += dir * (num_t)speedVar / 10;\n            }\n            // running\n            else\n            {\n                if(Player[A].Character == 2) // LUIGI\n                    Player[A].Location.SpeedX += dir * num_t::from_double(-0.05 * 0.175);\n                else if(Player[A].Character == 3) // PEACH\n                    Player[A].Location.SpeedX += dir * num_t::from_double(-0.025 * 0.175);\n                else if(Player[A].Character == 4) // toad\n                    Player[A].Location.SpeedX += dir * num_t::from_double(0.025 * 0.175);\n\n                if(Player[A].Character == 5) // Link\n                    Player[A].Location.SpeedX += dir * (num_t)speedVar / 40;\n                else // Mario\n                    Player[A].Location.SpeedX += dir * (num_t)speedVar / 20;\n            }\n\n            // turning around\n            if(dir * Player[A].Location.SpeedX < 0)\n            {\n                if(Player[A].Character == 2) // LUIGI\n                    Player[A].Location.SpeedX += dir * num_t::from_double(-0.18 * 0.29 + 0.18);\n                else if(Player[A].Character == 3) // PEACH\n                    Player[A].Location.SpeedX += dir * num_t::from_double(-0.09 * 0.29 + 0.18);\n                else if(Player[A].Character == 4) // toad\n                    Player[A].Location.SpeedX += dir * num_t::from_double(0.09 * 0.29 + 0.18);\n                else\n                    Player[A].Location.SpeedX += dir * 0.18_n;\n\n                if(SuperSpeed)\n                    Player[A].Location.SpeedX = Player[A].Location.SpeedX * 0.95_r;\n            }\n        }\n\n        if(SuperSpeed && Player[A].Controls.Run)\n            Player[A].Location.SpeedX += dir * 0.1_n;\n    }\n    else\n    {\n        if(is_grounded || Player[A].WetFrame) // Only lose speed when not in the air\n        {\n            // direct slowdown -- add extra friction during aquatic jumps\n            auto slowdown = (num_t)speedVar * ((aquatic_jumps) ? 0.14_r : 0.07_r);\n            if(Player[A].Location.SpeedX > 0)\n                Player[A].Location.SpeedX -= slowdown;\n            if(Player[A].Location.SpeedX < 0)\n                Player[A].Location.SpeedX += slowdown;\n\n            // ratio-based slowdown\n            if(Player[A].Character == 2) // LUIGI\n                Player[A].Location.SpeedX *= 1.003_r;\n            else if(Player[A].Character == 3) // PEACH\n                Player[A].Location.SpeedX *= 1.0015_r;\n            else if(Player[A].Character == 4) // toad\n                Player[A].Location.SpeedX *= 0.9985_r;\n\n            if(SuperSpeed || (aquatic_jumps && Player[A].Controls.Run))\n                Player[A].Location.SpeedX *= 0.95_r;\n        }\n\n        if(Player[A].Location.SpeedX > -0.18_n && Player[A].Location.SpeedX < 0.18_n)\n        {\n            Player[A].Bumped = false;\n            Player[A].Location.SpeedX = 0;\n        }\n    }\n\n    // hard speed cap\n    if(Player[A].Location.SpeedX < -16)\n        Player[A].Location.SpeedX = -16;\n    else if(Player[A].Location.SpeedX > 16)\n        Player[A].Location.SpeedX = 16;\n\n    if(Player[A].WarpShooted &&\n       Player[A].Location.SpeedX < Physics.PlayerRunSpeed * (num_t)speedVar &&\n       Player[A].Location.SpeedX > -Physics.PlayerRunSpeed * (num_t)speedVar)\n    {\n        Player[A].WarpShooted = false;\n    }\n\n    // soft speed cap\n    if(!Player[A].WarpShooted && (Player[A].Controls.Run || Player[A].Character == 5))\n    {\n        if(Player[A].Location.SpeedX >= Physics.PlayerRunSpeed * (num_t)speedVar)\n        {\n            if(!SuperSpeed)\n                Player[A].Location.SpeedX = Physics.PlayerRunSpeed * (num_t)speedVar;\n        }\n        else if(Player[A].Location.SpeedX <= -Physics.PlayerRunSpeed * (num_t)speedVar)\n        {\n            if(!SuperSpeed)\n                Player[A].Location.SpeedX = -Physics.PlayerRunSpeed * (num_t)speedVar;\n        }\n//                        else  // REDURANT GARBAGE\n//                        {\n//                        }\n    }\n    else\n    {\n        // smooth run->walk deceleration\n        // (note: this is an SMBX 1.3 bug, the correct expression would be Physics.PlayerWalkSpeed * speedVar + 0.1)\n        if(Player[A].Location.SpeedX > Physics.PlayerWalkSpeed + (num_t)speedVar / 10)\n            Player[A].Location.SpeedX -= 0.1_n;\n        else if(Player[A].Location.SpeedX < -Physics.PlayerWalkSpeed - (num_t)speedVar / 10)\n            Player[A].Location.SpeedX += 0.1_n;\n        else if(num_t::abs(Player[A].Location.SpeedX) > Physics.PlayerWalkSpeed * (num_t)speedVar)\n        {\n            if(Player[A].Location.SpeedX > 0)\n                Player[A].Location.SpeedX = Physics.PlayerWalkSpeed * (num_t)speedVar;\n            else\n                Player[A].Location.SpeedX = -Physics.PlayerWalkSpeed * (num_t)speedVar;\n        }\n    }\n\n    if(Player[A].Mount == 1 && Player[A].MountType == 3)\n    {\n        Player[A].CanFly2 = true;\n        Player[A].FlyCount = 1000;\n    }\n\n    if(Player[A].Mount != 3)\n        Player[A].YoshiBlue = false;\n\n    if(FlyForever && !Player[A].GroundPound)\n    {\n        if(Player[A].Mount == 3)\n            Player[A].YoshiBlue = true;\n\n        if((Player[A].State == 4 || Player[A].State == 5) || (Player[A].YoshiBlue && Player[A].Mount == 3) || (Player[A].Mount == 1 && Player[A].MountType == 3))\n            Player[A].CanFly2 = true;\n        else\n        {\n            Player[A].CanFly2 = false;\n            Player[A].CanFly = false;\n            Player[A].FlyCount = 0;\n            Player[A].YoshiBlue = false;\n        }\n    }\n\n    // Racoon/Tanooki Mario.  this handles the ability to fly after running\n    if((Player[A].State == 4 || Player[A].State == 5) && Player[A].Wet == 0)\n    {\n        // note: RunCount was previously a float, so its values have been multiplied by 10 everywhere\n        bool is_running = (num_t::abs(Player[A].Location.SpeedX) >= Physics.PlayerRunSpeed ||\n            (Player[A].Character == 3 && num_t::abs(Player[A].Location.SpeedX) >= 5.579_n)); // Rounding error of SpeedX makes an evil here (FIXME: does this match VB6?)\n\n        if( (Player[A].CanFly2 ||\n             is_grounded) &&\n            is_running)\n        {\n            Player[A].RunCount += 10;\n        }\n        else if(!is_running)\n        {\n            Player[A].RunCount -= 3;\n        }\n\n        if(Player[A].RunCount >= 350 && Player[A].Character == 1)\n        {\n            Player[A].CanFly = true;\n            Player[A].RunCount = 350;\n        }\n        else if(Player[A].RunCount >= 400 && Player[A].Character == 2)\n        {\n            Player[A].CanFly = true;\n            Player[A].RunCount = 400;\n        }\n        else if(Player[A].RunCount >= 800 && Player[A].Character == 3)\n        {\n            Player[A].CanFly = true;\n            Player[A].RunCount = 800;\n        }\n        else if(Player[A].RunCount >= 600 && Player[A].Character == 4)\n        {\n            Player[A].CanFly = true;\n            Player[A].RunCount = 600;\n        }\n        else if(Player[A].RunCount >= 100 && Player[A].Character == 5) // link flying\n        {\n            Player[A].CanFly = true;\n            Player[A].RunCount = 100;\n        }\n        else\n        {\n            Player[A].CanFly = false;\n            if(Player[A].RunCount < 0)\n                Player[A].RunCount = 0;\n        }\n    }\n\n    if(is_grounded)\n        Player[A].FlyCount = 1;\n\n    if(Player[A].FlyCount > 1)\n        Player[A].FlyCount -= 1;\n    else if(Player[A].FlyCount == 1)\n    {\n        Player[A].CanFly2 = false;\n        Player[A].FlyCount = 0;\n    }\n}\n\nvoid s_playerSlopeMomentum(int A)\n{\n    if(Block[Player[A].Slope].Location.Width == 0)\n    {\n        // SMBX 1.3 would have crashed here\n        return;\n    }\n\n    // Angle = 1 / (Block[Player[A].Slope].Location.Width / Block[Player[A].Slope].Location.Height);\n    num_t Angle = Block[Player[A].Slope].Location.Height / (int_ok)Block[Player[A].Slope].Location.Width;\n    num_t slideSpeed = Angle * BlockSlope[Block[Player[A].Slope].Type] / 10;\n\n    num_t add_uphill = (Player[A].Rolling) ? slideSpeed : slideSpeed * 2;\n\n    if(slideSpeed > 0 && Player[A].Location.SpeedX < 0)\n        Player[A].Location.SpeedX += add_uphill;\n    else if(slideSpeed < 0 && Player[A].Location.SpeedX > 0)\n        Player[A].Location.SpeedX += add_uphill;\n    else\n        Player[A].Location.SpeedX += slideSpeed;\n}\n\nvoid PlayerSlideMovementX(int A)\n{\n    if(Player[A].Slope > 0)\n        s_playerSlopeMomentum(A);\n    else if(Player[A].Location.SpeedY == 0 || Player[A].StandingOnNPC != 0)\n    {\n        if(Player[A].Location.SpeedX > 0.2_n)\n            Player[A].Location.SpeedX -= 0.1_n;\n        else if(Player[A].Location.SpeedX < -0.2_n)\n            Player[A].Location.SpeedX += 0.1_n;\n        else\n        {\n            Player[A].Location.SpeedX = 0;\n            Player[A].Slide = false;\n        }\n    }\n\n    if(Player[A].Location.SpeedX > 11)\n        Player[A].Location.SpeedX = 11;\n\n    if(Player[A].Location.SpeedX < -11)\n        Player[A].Location.SpeedX = -11;\n\n    if(Player[A].Controls.Jump || Player[A].Controls.AltJump)\n        Player[A].Slide = false;\n}\n\nvoid PlayerCockpitMovementX(int A)\n{\n    if(Player[A].Duck)\n        UnDuck(Player[A]);\n\n    Player[A].Driving = false;\n    if(Player[A].StandingOnNPC > 0)\n    {\n        NPC[Player[A].StandingOnNPC].Special4 = NPC_COCKPIT_DRIVING;\n\n        if(Player[A].Controls.Left)\n            NPC[Player[A].StandingOnNPC].Special4 |= NPC_COCKPIT_LEFT;\n        else if(Player[A].Controls.Right)\n            NPC[Player[A].StandingOnNPC].Special4 |= NPC_COCKPIT_RIGHT;\n\n        if(Player[A].Controls.Up)\n            NPC[Player[A].StandingOnNPC].Special4 |= NPC_COCKPIT_UP;\n        else if(Player[A].Controls.Down)\n            NPC[Player[A].StandingOnNPC].Special4 |= NPC_COCKPIT_DOWN;\n    }\n\n    Player[A].Location.SpeedX = 0;\n}\n\nvoid PlayerMovementY(int A)\n{\n    bool aquatic_jumps = (Player[A].State == PLR_STATE_AQUATIC && Player[A].Character != 5 && !Player[A].Mount && !Player[A].HoldingNPC);\n\n    // this gives the player the bounce when in the kurbio's shoe, or newly when in aquatic hopping state\n    if(Player[A].Mount == 1 || (aquatic_jumps && !Player[A].Duck && !Player[A].Slide))\n    {\n        num_t floor_speed = NPC[Player[A].StandingOnNPC].Location.SpeedX + (num_t)NPC[Player[A].StandingOnNPC].BeltSpeed;\n\n        if((Player[A].Controls.Left || Player[A].Controls.Right) && (!aquatic_jumps || Player[A].CanAltJump))\n        {\n            if(Player[A].Location.SpeedY == 0 || Player[A].Slope > 0 || (Player[A].StandingOnNPC != 0 && Player[A].Location.Y + Player[A].Location.Height >= NPC[Player[A].StandingOnNPC].Location.Y - NPC[Player[A].StandingOnNPC].Location.SpeedY))\n            {\n                num_t rel_speed = Player[A].Location.SpeedX - floor_speed;\n                if(aquatic_jumps && !SuperSpeed && num_t::abs(rel_speed) > 0.18_n)\n                {\n                    // wait for player to get some friction\n                }\n                else if((Player[A].Controls.Left && rel_speed <= 0) || (Player[A].Controls.Right && rel_speed >= 0))\n                {\n                    Player[A].Location.SpeedY = -4.1_n + NPC[Player[A].StandingOnNPC].Location.SpeedY;\n\n                    if(aquatic_jumps)\n                    {\n                        Player[A].StandingOnNPC = 0;\n                        Player[A].Direction = (Player[A].Controls.Left) ? -1 : 1;\n                        Player[A].Location.SpeedX = Player[A].Direction * ((Player[A].Controls.Run) ? 3 : 1.5_n) + floor_speed;\n\n                        if(SuperSpeed)\n                            Player[A].Location.SpeedX *= 2;\n                    }\n                }\n                else\n                    PlaySoundSpatial(SFX_Skid, Player[A].Location);\n\n                // allow jump during hop\n                if(!aquatic_jumps || (Player[A].CanJump && Player[A].CanAltJump))\n                    Player[A].MountSpecial = 1;\n            }\n        }\n\n        if(Player[A].Location.SpeedY < -4.1_n)\n            Player[A].MountSpecial = 0;\n        else if(Player[A].Location.SpeedY > 4.1_n)\n            Player[A].MountSpecial = 0;\n\n        if(Player[A].Controls.Jump && Player[A].MountSpecial == 1 && Player[A].CanJump)\n        {\n            Player[A].Location.SpeedY = 0;\n\n            if(!aquatic_jumps)\n                Player[A].StandUp = true;\n            else\n                Player[A].MountSpecial = 0;\n        }\n    }\n    else if(Player[A].State == PLR_STATE_AQUATIC && !Player[A].Mount)\n        Player[A].MountSpecial = 0;\n\n    if(Player[A].Mount == 1)\n    {\n        if(Player[A].Controls.AltJump && Player[A].CanAltJump) // check to see if the player should jump out of the shoe\n            PlayerDismount(A);\n    }\n    else if(Player[A].Mount == 3)\n    {\n        if(Player[A].Controls.AltJump && Player[A].CanAltJump) // jump off of yoshi\n            PlayerDismount(A);\n    }\n\n    if((Player[A].Location.SpeedY == 0 || (Player[A].Jump > 0 && !Player[A].JumpOffWall) || Player[A].Vine > 0) && Player[A].FloatTime == 0 && !(Player[A].State == PLR_STATE_CYCLONE && Player[A].SpinJump)) // princess float\n        Player[A].CanFloat = true;\n\n    if(Player[A].Wet > 0 || Player[A].WetFrame)\n        Player[A].CanFloat = false;\n\n    bool has_wall_traction = CanWallJump && (Player[A].Pinched.Left2 == 2 || Player[A].Pinched.Right4 == 2) && !Player[A].SpinJump && (!Player[A].SlippyWall || Player[A].State == PLR_STATE_POLAR) && Player[A].HoldingNPC == 0 && Player[A].Mount == 0 && !Player[A].Duck && !(Player[A].State == PLR_STATE_CYCLONE && !Player[A].DoubleJump);\n\n    // handles the regular jump\n    if(Player[A].Controls.Jump || (Player[A].Controls.AltJump &&\n       ((Player[A].Character > 2 && Player[A].Character != 4) || Player[A].Quicksand > 0 || Player[A].Rolling || aquatic_jumps || g_config.disable_spin_jump) &&\n       Player[A].CanAltJump))\n    {\n        num_t tempSpeed;\n        if(Player[A].Location.SpeedX > 0)\n            tempSpeed = Player[A].Location.SpeedX / 5; // tempSpeed gives the player a height boost when jumping while running, based off his SpeedX\n        else\n            tempSpeed = -Player[A].Location.SpeedX / 5;\n\n        if(Player[A].ShellSurf) // this code modifies the jump based on him riding a shell\n        {\n            if(NPC[Player[A].StandingOnNPC].Location.SpeedY == 0 || NPC[Player[A].StandingOnNPC].Slope > 0)\n            {\n                if(Player[A].CanJump)\n                {\n                    PlaySoundSpatial(SFX_Whip, Player[A].Location); // Jump sound\n                    Player[A].Jump = Physics.PlayerJumpHeight * 3 / 5;\n                    NPC[Player[A].StandingOnNPC].Location.SpeedY = Physics.PlayerJumpVelocity * 0.9_r;\n                }\n            }\n            else if(Player[A].Jump > 0)\n                NPC[Player[A].StandingOnNPC].Location.SpeedY = Physics.PlayerJumpVelocity * 0.9_r;\n        }\n        // if not surfing a shell then proceed like normal\n        else\n        {\n            if((Player[A].Vine > 0 || Player[A].Location.SpeedY == 0 || Player[A].StandingOnNPC != 0 ||\n                MultiHop || Player[A].Slope > 0 || (Player[A].Location.SpeedY > 0 && Player[A].Quicksand > 0)) && Player[A].CanJump)\n            {\n                PlaySoundSpatial(SFX_Jump, Player[A].Location); // Jump sound\n                Player[A].Location.SpeedY = Physics.PlayerJumpVelocity - tempSpeed;\n                Player[A].Jump = Physics.PlayerJumpHeight;\n\n                if(Player[A].Character == 4 && (Player[A].State == 4 || Player[A].State == 5) && !Player[A].SpinJump)\n                    Player[A].DoubleJump = true;\n\n                if(Player[A].Character == 2)\n                    Player[A].Jump += 3;\n\n                if(Player[A].SpinJump)\n                    Player[A].Jump -= 6;\n\n                if(aquatic_jumps)\n                {\n                    Player[A].Jump += 10;\n                    Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n                    Player[A].Location.SpeedX /= 4;\n                    Player[A].MountSpecial = 0;\n                }\n                else if(Player[A].State == PLR_STATE_AQUATIC)\n                    Player[A].Jump += 4;\n\n                if(Player[A].StandingOnNPC > 0 && !FreezeNPCs)\n                {\n                    if(NPC[Player[A].StandingOnNPC].Type != NPCID_ITEM_BURIED)\n                        Player[A].Location.SpeedX += -NPC[Player[A].StandingOnNPC].Location.SpeedX;\n                }\n\n                Player[A].StandingOnNPC = 0; // the player can't stand on an NPC after jumping\n\n                if(Player[A].CanFly) // let's the player fly if the conditions are met\n                {\n                    Player[A].StandingOnNPC = 0;\n                    Player[A].Jump = 30;\n                    if(Player[A].Character == 2)\n                        Player[A].Jump += 3;\n                    if(Player[A].SpinJump)\n                        Player[A].Jump -= 6;\n                    Player[A].CanFly = false;\n                    Player[A].RunCount = 0;\n                    Player[A].CanFly2 = true;\n\n                    if(Player[A].Character == 2) // luigi doesn't fly as long as mario\n                        Player[A].FlyCount = 300; // Length of flight time\n                    else if(Player[A].Character == 3) // special handling for peach\n                    {\n                        Player[A].FlyCount = 0;\n                        Player[A].RunCount = 800; // multiplied by 10 vs VB6 code, since it's an int now\n                        Player[A].CanFly2 = false;\n                        Player[A].Jump = 70;\n                        Player[A].CanFloat = true;\n                        Player[A].FlySparks = true;\n                    }\n#if 0\n                    // FIXME: Duplicated \"Character == 3\" condition branch [PVS Studio]\n                    else if(Player[A].Character == 3) // special handling for peach\n                        Player[A].FlyCount = 280; // Length of flight time\n#endif\n                    else\n                        Player[A].FlyCount = 320; // Length of flight time\n                }\n            }\n            else if(has_wall_traction && Player[A].CanJump)\n            {\n                NewEffect(EFFID_WHACK, newLoc(Player[A].Location.X + Player[A].Location.Width / 2 - 16, Player[A].Location.Y + Player[A].Location.Height - 16));\n                PlaySoundSpatial(SFX_Jump, Player[A].Location); // Jump sound\n                Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n                Player[A].Location.SpeedX -= 5 * Player[A].Direction;\n                Player[A].Location.X += Player[A].Location.SpeedX;\n                Player[A].Jump = 8;\n                Player[A].JumpOffWall = true;\n\n                if(Player[A].Character == 2)\n                    Player[A].Jump += 3;\n            }\n            else if(Player[A].Jump > 0) // controls the height of the jump\n            {\n                Player[A].Location.SpeedY = Physics.PlayerJumpVelocity - tempSpeed;\n                if(Player[A].Jump > 20)\n                {\n                    if(Player[A].Jump > 40)\n                        Player[A].Location.SpeedY += -(40 - 20) * 0.2_n;\n                    else\n                        Player[A].Location.SpeedY += -(Player[A].Jump - 20) * 0.2_n;\n                }\n            }\n            else if(Player[A].CanFly2)\n            {\n                if(Player[A].Location.SpeedY > Physics.PlayerJumpVelocity / 2)\n                {\n                    Player[A].Location.SpeedY -= 1;\n                    Player[A].CanPound = true;\n                    if(Player[A].YoshiBlue || (Player[A].Mount == 1 && Player[A].MountType == 3))\n                        PlaySoundSpatial(SFX_PetTongue, Player[A].Location);\n                }\n            }\n        }\n\n        if(!(aquatic_jumps && Player[A].MountSpecial))\n            Player[A].CanJump = false;\n    }\n    else\n        Player[A].CanJump = true;\n\n    if(Player[A].Jump > 0)\n        Player[A].Slope = 0;\n\n    bool is_supported = (Player[A].StandingOnNPC != 0 || Player[A].Slope != 0 || Player[A].Location.SpeedY == 0 || Player[A].Wet || Player[A].Vine || Player[A].WetFrame);\n    // cyclone: allow double jump as save after falling off cliff\n    if(Player[A].State == PLR_STATE_CYCLONE && !Player[A].SpinJump)\n    {\n        if(is_supported)\n            Player[A].DoubleJump = true;\n    }\n    // this is the old condition which subtly differs from the supported condition tested above and below\n    else if(Player[A].SpinJump || (Player[A].State != 4 && Player[A].State != 5) || Player[A].StandingOnNPC > 0 || Player[A].Slope > 0 || Player[A].Location.SpeedY == 0)\n        Player[A].DoubleJump = false;\n\n    // double jump code\n    if(Player[A].DoubleJump && Player[A].Jump == 0 && !is_supported && !Player[A].Fairy && !Player[A].CanFly2)\n    {\n        if(Player[A].State == PLR_STATE_CYCLONE)\n        {\n            if(!Player[A].Mount && Player[A].Controls.AltJump && Player[A].CanAltJump)\n            {\n                PlaySoundSpatial(SFX_Whip, Player[A].Location);\n                Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n                Player[A].Jump = 32;\n                Player[A].DoubleJump = false;\n\n                if(Player[A].Character != 5)\n                {\n                    Player[A].SpinJump = true;\n                    UnDuck(Player[A]);\n                }\n            }\n        }\n        else if(Player[A].Controls.Jump && Player[A].JumpRelease)\n        {\n            PlaySoundSpatial(SFX_Jump, Player[A].Location);\n            Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n            Player[A].Jump = 10;\n            Player[A].DoubleJump = false;\n            Location_t tempLocation = Player[A].Location;\n            tempLocation.Y = Player[A].Location.Y + Player[A].Location.Height - EffectHeight[EFFID_SPARKLE] * 0.5_n + Player[A].Location.SpeedY;\n            tempLocation.Height = EffectHeight[EFFID_SPARKLE];\n            tempLocation.Width = EffectWidth[EFFID_SPARKLE];\n            tempLocation.X = Player[A].Location.X;\n\n            for(int B = 1; B <= 10; B++)\n            {\n                NewEffect(EFFID_SPARKLE, tempLocation);\n                Effect[numEffects].Location.SpeedX = (dRand() * 3) - 1.5_n;\n                Effect[numEffects].Location.SpeedY = (dRand() / 2) + (1.5_n - num_t::abs(Effect[numEffects].Location.SpeedX)) / 2;\n                Effect[numEffects].Location.SpeedX += -Player[A].Location.SpeedX / 5;\n            }\n        }\n    }\n\n\n\n#if 0\n    // never set since SMBX 1.3, see dead code below\n    if(Player[A].NoShellKick > 0) // countdown for the next time the player kicks a turtle shell\n        Player[A].NoShellKick--;\n#endif\n\n    if(Player[A].ShellSurf)\n    {\n        if(Player[A].Mount != 0)\n            Player[A].ShellSurf = false;\n\n        // FIXME: SOME DEAD CODE BECAUSE OF \"1 == 2\"\n#if 0\n        if(Player[A].Direction != NPC[Player[A].StandingOnNPC].Direction && 1 == 2)\n        {\n            Player[A].ShellSurf = false;\n            Player[A].Location.SpeedY = NPC[Player[A].StandingOnNPC].Location.SpeedY;\n            if(Player[A].Location.SpeedY > 0)\n                Player[A].Location.SpeedY = 0;\n            PlaySoundSpatial(SFX_Skid, Player[A].Location);\n            NPC[Player[A].StandingOnNPC].CantHurt = 30;\n            NPC[Player[A].StandingOnNPC].CantHurtPlayer = A;\n            Player[A].Location.SpeedX = NPC[Player[A].StandingOnNPC].Location.SpeedX / 2;\n            Player[A].StandingOnNPC = 0;\n            Player[A].NoShellKick = 30;\n        }\n        else\n#endif\n        {\n            if(iRand(10) >= 3)\n            {\n                Location_t tempLocation;\n                tempLocation.Y = Player[A].Location.Y + Player[A].Location.Height - 2 + dRand().times(NPC[Player[A].StandingOnNPC].Location.Height - 8) + 4;\n                tempLocation.X = Player[A].Location.X - 4 + dRand() * ((int)Player[A].Location.Width - 8) + 4 - 8 * Player[A].Direction;\n                NewEffect(EFFID_SPARKLE, tempLocation, 1, ShadowMode);\n                Effect[numEffects].Frame = iRand(3);\n                Effect[numEffects].Location.SpeedY = (Player[A].Location.Y + Player[A].Location.Height + NPC[Player[A].StandingOnNPC].Location.Height / 32 - tempLocation.Y + 12) / 20;\n            }\n        }\n\n        if(NPC[Player[A].StandingOnNPC].Wet == 2)\n        {\n            if(NPC[Player[A].StandingOnNPC].Type == NPCID_FLIPPED_RAINBOW_SHELL)\n                NPC[Player[A].StandingOnNPC].Special4 = 1;\n            NPC[Player[A].StandingOnNPC].Location.SpeedY += -Physics.NPCGravity * 1.5_rb;\n        }\n    }\n\n    // START ALT JUMP - this code does the player's spin jump\n    if(Player[A].Controls.AltJump && (Player[A].Character == 1 || Player[A].Character == 2 || Player[A].Character == 4 ||\n                                      (g_config.fix_char3_escape_shell_surf && Player[A].Character == 3 && Player[A].ShellSurf))\n                                  && (!g_config.disable_spin_jump || Player[A].ShellSurf))\n    {\n        num_t tempSpeed;\n        if(Player[A].Location.SpeedX > 0)\n            tempSpeed = Player[A].Location.SpeedX / 5;\n        else\n            tempSpeed = -Player[A].Location.SpeedX / 5;\n\n        if((Player[A].Vine > 0 || Player[A].Location.SpeedY == 0 || Player[A].StandingOnNPC != 0 || Player[A].Slope > 0 || MultiHop) && Player[A].CanAltJump) // Player Jumped\n        {\n            if(!Player[A].Duck && !aquatic_jumps)\n            {\n                Player[A].Slope = 0;\n                Player[A].SpinFireDir = Player[A].Direction;\n                Player[A].Location.SpeedY = Physics.PlayerJumpVelocity - tempSpeed;\n                Player[A].Jump = Physics.PlayerJumpHeight;\n                if(Player[A].Character == 2)\n                    Player[A].Jump += 3;\n\n                if(Player[A].StandingOnNPC > 0 && !FreezeNPCs)\n                {\n                    if(NPC[Player[A].StandingOnNPC].Type != NPCID_ITEM_BURIED)\n                        Player[A].Location.SpeedX += -NPC[Player[A].StandingOnNPC].Location.SpeedX;\n                }\n\n                PlaySoundSpatial(SFX_Whip, Player[A].Location); // Jump sound\n                Player[A].Jump -= 6;\n                if(Player[A].Direction == 1)\n                    Player[A].SpinFrame = 0;\n                else\n                    Player[A].SpinFrame = 6;\n                Player[A].SpinJump = true;\n//                                    if(nPlay.Online == true && nPlay.MySlot + 1 == A)\n//                                        Netplay::sendData Netplay::PutPlayerLoc(nPlay.MySlot) + \"1l\" + std::to_string(A) + LB;\n\n                // just checked that Player[A].Duck wasn't true!\n                // if(Player[A].Duck)\n                //     UnDuck(Player[A]);\n\n                if(Player[A].State == PLR_STATE_CYCLONE)\n                    Player[A].Jump += 32;\n\n                if(Player[A].ShellSurf)\n                {\n                    Player[A].ShellSurf = false;\n                    Player[A].Location.SpeedX = NPC[Player[A].StandingOnNPC].Location.SpeedX + (num_t)NPC[Player[A].StandingOnNPC].BeltSpeed * 0.8_r;\n                    Player[A].Jump = 0;\n\n                    if(g_config.disable_spin_jump)\n                        Player[A].SpinJump = false;\n                }\n\n                Player[A].StandingOnNPC = 0;\n\n                if(Player[A].CanFly)\n                {\n                    Player[A].StandingOnNPC = 0;\n                    Player[A].Jump = 30;\n                    if(Player[A].Character == 2)\n                        Player[A].Jump += 3;\n                    if(Player[A].SpinJump)\n                        Player[A].Jump -= 6;\n                    Player[A].CanFly = false;\n                    Player[A].RunCount = 0;\n                    Player[A].CanFly2 = true;\n                    Player[A].FlyCount = 150; // Length of flight time\n                }\n            }\n        }\n        else if(has_wall_traction && Player[A].CanAltJump && !Player[A].JumpOffWall)\n        {\n            NewEffect(EFFID_WHACK, newLoc(Player[A].Location.X + Player[A].Location.Width / 2 - 16, Player[A].Location.Y + Player[A].Location.Height - 16));\n            PlaySoundSpatial(SFX_Whip, Player[A].Location); // Jump sound\n            Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n            Player[A].Location.SpeedX -= 7 * Player[A].Direction;\n            Player[A].Location.X += Player[A].Location.SpeedX;\n            Player[A].Jump = 5;\n            Player[A].JumpOffWall = true;\n            Player[A].SpinJump = true;\n        }\n        else if(Player[A].Jump > 0)\n        {\n            Player[A].Location.SpeedY = Physics.PlayerJumpVelocity - tempSpeed;\n            if(Player[A].Jump > 20)\n                Player[A].Location.SpeedY += -(Player[A].Jump - 20) * 0.2_n;\n        }\n        else if(Player[A].CanFly2)\n        {\n            if(Player[A].Location.SpeedY > Physics.PlayerJumpVelocity / 2)\n            {\n                Player[A].Location.SpeedY -= 1;\n                Player[A].CanPound = true;\n                if(Player[A].YoshiBlue)\n                    PlaySoundSpatial(SFX_PetTongue, Player[A].Location);\n            }\n        }\n        // End If\n\n        if(!(aquatic_jumps && Player[A].MountSpecial))\n            Player[A].CanAltJump = false;\n    }\n    else\n        Player[A].CanAltJump = true;\n    // END ALT JUMP\n\n\n    if((Player[A].Location.SpeedY == 0 || Player[A].StandingOnNPC != 0 || Player[A].Slope > 0) && Player[A].SpinJump)\n    {\n        Player[A].SpinJump = false;\n//                            if(nPlay.Online == true && nPlay.MySlot + 1 == A)\n//                                Netplay::sendData Netplay::PutPlayerLoc(nPlay.MySlot) + \"1m\" + std::to_string(A) + LB;\n        Player[A].TailCount = 0;\n    }\n\n    if(Player[A].Mount > 0 || aquatic_jumps)\n        Player[A].SpinJump = false;\n\n    if(!Player[A].Controls.AltJump && !Player[A].Controls.Jump)\n        Player[A].Jump = 0;\n\n    if(Player[A].Jump > 0)\n        Player[A].Jump -= 1;\n\n    if(Player[A].Jump > 0)\n        Player[A].Vine = 0;\n    else\n        Player[A].JumpOffWall = false;\n\n\n    if(Player[A].Quicksand > 1)\n    {\n        Player[A].Slide = false;\n        if(Player[A].Location.SpeedY < -0.7_n)\n        {\n            Player[A].Location.SpeedY = -0.7_n;\n            Player[A].Jump -= 1;\n        }\n        else if(Player[A].Location.SpeedY < 0)\n        {\n            Player[A].Location.SpeedY += 0.1_n;\n            Player[A].Jump = 0;\n        }\n\n        if(Player[A].Location.SpeedY >= 0.1_n)\n            Player[A].Location.SpeedY = 0.1_n;\n        Player[A].Location.Y += Player[A].Location.SpeedY;\n    }\n\n\n    // gravity\n    if(Player[A].Vine == 0)\n    {\n        if(Player[A].NoGravity == 0)\n        {\n            if(has_wall_traction && Player[A].Location.SpeedY > 0)\n            {\n                Player[A].Location.SpeedY += Physics.PlayerGravity / 2;\n\n                if(Player[A].Location.SpeedY > Physics.PlayerTerminalVelocity / 2)\n                    Player[A].Location.SpeedY = Physics.PlayerTerminalVelocity / 2;\n            }\n            else if(Player[A].Character == 2)\n                Player[A].Location.SpeedY += Physics.PlayerGravity * 0.9_r;\n            else\n                Player[A].Location.SpeedY += Physics.PlayerGravity;\n\n            bool has_fly_block = (Player[A].HoldingNPC > 0) && (NPC[Player[A].HoldingNPC].Type == NPCID_FLY_BLOCK || NPC[Player[A].HoldingNPC].Type == NPCID_FLY_CANNON);\n            bool no_cyclone_glide = (Player[A].Location.SpeedY >= 0) && Player[A].Mount && !Player[A].GroundPound; // glide down in cases where player can't cyclone\n\n            if(Player[A].State == PLR_STATE_CYCLONE && (!Player[A].DoubleJump || no_cyclone_glide))\n            {\n                if(Player[A].Controls.Down && Player[A].Location.SpeedY >= 0)\n                    Player[A].Location.SpeedY += Physics.PlayerGravity / 2;\n                else if((Player[A].Controls.Jump || Player[A].Controls.AltJump) && Player[A].Location.SpeedY >= 0)\n                {\n                    Player[A].Location.SpeedY += -Physics.PlayerGravity * 0.625_rb;\n\n                    if(Player[A].Location.SpeedY > Physics.PlayerTerminalVelocity * 0.375_n)\n                        Player[A].Location.SpeedY = Physics.PlayerTerminalVelocity * 0.375_n;\n                }\n            }\n            else if(has_fly_block)\n            {\n                if(Player[A].Controls.Jump || Player[A].Controls.AltJump)\n                {\n                    if(Player[A].Character == 2)\n                        Player[A].Location.SpeedY += -Physics.PlayerGravity * 0.72_r;\n                    else\n                        Player[A].Location.SpeedY += -Physics.PlayerGravity * 0.8_r;\n\n                    if(Player[A].Location.SpeedY > Physics.PlayerGravity * 3)\n                        Player[A].Location.SpeedY = Physics.PlayerGravity * 3;\n                }\n                else\n                    NPC[Player[A].HoldingNPC].Special = 0;\n            }\n\n            if(Player[A].Location.SpeedY > Physics.PlayerTerminalVelocity)\n                Player[A].Location.SpeedY = Physics.PlayerTerminalVelocity;\n        }\n        else\n            Player[A].NoGravity -= 1;\n    }\n\n    // princess float\n    if(Player[A].Character == 3 && Player[A].Wet == 0 && !Player[A].WetFrame && (!aquatic_jumps || !Player[A].MountSpecial))\n    {\n        if(Player[A].Location.SpeedY == 0 || Player[A].StandingOnNPC > 0 || Player[A].Slope > 0 || Player[A].CanFly2)\n            Player[A].CanFloat = true;\n        else if(Player[A].CanFloat)\n        {\n            if(Player[A].Jump == 0 && ((Player[A].Controls.Jump && Player[A].FloatRelease) ||\n              (Player[A].Controls.AltJump && Player[A].Location.SpeedY > 0)))\n            {\n                // float time is longer during glide\n                if(Player[A].State == 4 || Player[A].State == 5)\n                {\n                    Player[A].FloatTime = 100;\n                    Player[A].FlySparks = true;\n                }\n                else\n                    Player[A].FloatTime = 65;\n\n                Player[A].FloatDir = 1;\n\n                if(Player[A].Location.SpeedY < -0.5_n)\n                    Player[A].FloatSpeed = 0.5_nf;\n                else if(Player[A].Location.SpeedY > 0.5_n)\n                    Player[A].FloatSpeed = 0.5_nf;\n                else\n                    Player[A].FloatSpeed = (numf_t)(Player[A].Location.SpeedY);\n\n                Player[A].CanFloat = false;\n            }\n        }\n    }\n\n    if(Player[A].Character == 3 && Player[A].FlySparks)\n    {\n        if(Player[A].FloatTime == 0 && Player[A].Location.SpeedY >= 0)\n            Player[A].FlySparks = false;\n    }\n\n    if(Player[A].CanFloat)\n        Player[A].FloatTime = 0;\n\n    if(Player[A].FloatTime > 0 && Player[A].Character == 3)\n    {\n        if((Player[A].Controls.Jump || Player[A].Controls.AltJump) && Player[A].Vine == 0)\n        {\n            tempf_t floatSpeed = (tempf_t)Player[A].FloatSpeed;\n            floatSpeed += Player[A].FloatDir * (tempf_t)0.1_n;\n\n            if(floatSpeed > (tempf_t)0.8_n)\n                Player[A].FloatDir = -1;\n\n            if(floatSpeed < -(tempf_t)0.8_n)\n                Player[A].FloatDir = 1;\n\n            Player[A].Location.SpeedY = (num_t)floatSpeed;\n            Player[A].FloatSpeed = (numf_t)floatSpeed;\n\n            Player[A].FloatTime -= 1;\n            if(Player[A].FloatTime == 0 && Player[A].Location.SpeedY == 0)\n                Player[A].Location.SpeedY = 0.1_n;\n        }\n        else\n            Player[A].FloatTime = 0;\n    }\n\n\n    // glide ' Racoon Mario\n    if((Player[A].State == PLR_STATE_LEAF || Player[A].State == PLR_STATE_STATUE) || Player[A].YoshiBlue || (Player[A].Mount == 1 && Player[A].MountType == 3))\n    {\n        if((Player[A].Controls.Jump || Player[A].Controls.AltJump) &&\n          ((Player[A].Location.SpeedY > Physics.PlayerGravity * 5 && Player[A].Character != 3 && Player[A].Character != 4) ||\n            (Player[A].Location.SpeedY > Physics.PlayerGravity * 10 && Player[A].Character == 3) ||\n            (Player[A].Location.SpeedY > Physics.PlayerGravity * 7.5_rb && Player[A].Character == 4)) &&\n            !Player[A].GroundPound && Player[A].Slope == 0 && Player[A].Character != 5)\n        {\n            if(!Player[A].ShellSurf)\n            {\n                if(Player[A].Character == 3)\n                    Player[A].Location.SpeedY = Physics.PlayerGravity * 10;\n                else if(Player[A].Character == 4)\n                    Player[A].Location.SpeedY = Physics.PlayerGravity * 7.5_rb;\n                else\n                    Player[A].Location.SpeedY = Physics.PlayerGravity * 5;\n            }\n            else\n            {\n                if(NPC[Player[A].StandingOnNPC].Location.SpeedY > Physics.PlayerGravity * 5)\n                    NPC[Player[A].StandingOnNPC].Location.SpeedY = Physics.PlayerGravity * 5;\n            }\n\n            if(\n                !(\n                    (!Player[A].YoshiBlue && (Player[A].CanFly || Player[A].CanFly2)) ||\n                    (Player[A].Mount == 3 && Player[A].CanFly2)\n                )\n            )\n            {\n                if(iRand(10) == 0)\n                    p_PlayerMakeFlySparkle(Player[A].Location);\n            }\n        }\n    }\n}\n\nvoid PlayerSwimMovementY(int A)\n{\n    if(Player[A].Mount == 1)\n    {\n        if(Player[A].Controls.AltJump && Player[A].CanAltJump)\n            PlayerDismount(A);\n    }\n    else if(Player[A].Mount == 3)\n    {\n        if(Player[A].Controls.AltJump && Player[A].CanAltJump)\n            PlayerDismount(A);\n    }\n\n    if(Player[A].Duck)\n    {\n        if(Player[A].StandingOnNPC == 0 && Player[A].Slope == 0 && Player[A].Location.SpeedY != 0 && Player[A].Mount != 1)\n        {\n            if(Player[A].Character <= 2) // unduck wet players that aren't peach o toad\n                UnDuck(Player[A]);\n        }\n    }\n\n    Player[A].Location.SpeedY += Physics.PlayerGravity / 10;\n\n    if(Player[A].Location.SpeedY >= 3) // Terminal Velocity in water\n        Player[A].Location.SpeedY = 3;\n\n    if(Player[A].Mount == 1)\n    {\n        if(Player[A].Controls.Left || Player[A].Controls.Right)\n        {\n            if(Player[A].Location.SpeedY == Physics.PlayerGravity / 10 || Player[A].Slope > 0 || (Player[A].StandingOnNPC != 0 && Player[A].Location.Y + Player[A].Location.Height >= NPC[Player[A].StandingOnNPC].Location.Y - NPC[Player[A].StandingOnNPC].Location.SpeedY))\n            {\n                if(Player[A].Controls.Left && Player[A].Location.SpeedX - NPC[Player[A].StandingOnNPC].Location.SpeedX - (num_t)NPC[Player[A].StandingOnNPC].BeltSpeed <= 0)\n                    Player[A].Location.SpeedY = -1.1_n + NPC[Player[A].StandingOnNPC].Location.SpeedY;\n                else if(Player[A].Controls.Right && Player[A].Location.SpeedX - NPC[Player[A].StandingOnNPC].Location.SpeedX - (num_t)NPC[Player[A].StandingOnNPC].BeltSpeed >= 0)\n                    Player[A].Location.SpeedY = -1.1_n + NPC[Player[A].StandingOnNPC].Location.SpeedY;\n                else\n                    PlaySoundSpatial(SFX_Skid, Player[A].Location);\n\n                Player[A].MountSpecial = 1;\n            }\n        }\n\n        if(Player[A].Location.SpeedY < -1.1_n)\n            Player[A].MountSpecial = 0;\n        else if(Player[A].Location.SpeedY > 1.1_n)\n            Player[A].MountSpecial = 0;\n        else if(Player[A].FloatTime >= 0)\n            Player[A].MountSpecial = 0;\n\n\n        if(Player[A].Controls.Jump && Player[A].MountSpecial == 1 && Player[A].CanJump)\n        {\n            Player[A].Location.SpeedY = Physics.PlayerGravity / 10;\n            Player[A].MountSpecial = 0;\n            Player[A].StandUp = true;\n        }\n    }\n\n    if(Player[A].SwimCount > 0)\n        Player[A].SwimCount -= 1;\n\n    if(Player[A].SwimCount == 0)\n    {\n        if(Player[A].Mount != 1 || Player[A].Location.SpeedY == Physics.PlayerGravity / 10 || Player[A].Slope != 0 || Player[A].StandingOnNPC != 0)\n        {\n            if((Player[A].Controls.Jump && Player[A].CanJump) ||\n               (Player[A].Controls.AltJump && Player[A].CanAltJump))\n            {\n                if(Player[A].Duck && Player[A].Mount != 1 && Player[A].Character <= 2)\n                    UnDuck(Player[A]);\n\n                if(Player[A].Slope != 0)\n                    Player[A].Location.SpeedY = 0;\n\n                Player[A].Vine = 0;\n                if(Player[A].StandingOnNPC != 0)\n                {\n                    Player[A].Location.SpeedY = NPC[Player[A].StandingOnNPC].Location.SpeedY;\n                    Player[A].StandingOnNPC = 0;\n                }\n\n                Player[A].SwimCount = 15;\n                // If .Location.SpeedY = 0 Then .Location.Y += -1\n                if(Player[A].Controls.Down)\n                {\n                    if(Player[A].Location.SpeedY >= Physics.PlayerJumpVelocity / 5)\n                    {\n                        Player[A].Location.SpeedY += Physics.PlayerJumpVelocity / 5;\n                        if(Player[A].Location.SpeedY < Physics.PlayerJumpVelocity / 5)\n                            Player[A].Location.SpeedY = Physics.PlayerJumpVelocity / 5;\n                    }\n                }\n                else\n                {\n                    if(Player[A].Controls.Up)\n                        Player[A].Location.SpeedY += Physics.PlayerJumpVelocity / 2;\n                    else\n                        Player[A].Location.SpeedY += Physics.PlayerJumpVelocity * 0.4_r;\n\n                    if(Player[A].Mount == 1)\n                        Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n                }\n\n                if(Player[A].Location.SpeedY > 0)\n                    Player[A].Location.SpeedY = Physics.PlayerJumpVelocity / 5;\n\n                PlaySoundSpatial(SFX_Swim, Player[A].Location);\n            }\n        }\n    }\n\n    Player[A].CanJump = !Player[A].Controls.Jump;\n    Player[A].CanAltJump = !Player[A].Controls.AltJump;\n\n    if(Player[A].Controls.Up)\n    {\n        if(Player[A].Location.SpeedY < -4)\n            Player[A].Location.SpeedY = -4;\n    }\n    else\n    {\n        if(Player[A].Location.SpeedY < -3)\n            Player[A].Location.SpeedY = -3;\n    }\n}\n\nvoid PlayerAquaticSwimMovement(int A)\n{\n    Player[A].Rolling = false;\n\n    if(!Player[A].Duck)\n    {\n        Player[A].Duck = true;\n        SizeCheck(Player[A]);\n    }\n\n    if(!Player[A].Wet)\n    {\n        if(Player[A].Pinched.Bottom1)\n            Player[A].WetFrame = false;\n\n        tempf_t C = 0;\n\n        PlayerMovementX(A, C);\n        return;\n    }\n\n    int current_swim_dir = -1;\n    if(Player[A].SwimCount > 0)\n    {\n        current_swim_dir = Player[A].SwimCount / 16;\n\n        Player[A].SwimCount -= 1;\n\n        if((Player[A].SwimCount & 15) == 0)\n            Player[A].SwimCount = 0;\n    }\n\n    num_t base_speed = (Player[A].State == PLR_STATE_POLAR) ? 2_n : 2.5_n;\n\n    num_t target_speed = base_speed;\n    int rate = 16; // out of 256\n\n    if(Player[A].Controls.Run)\n        target_speed += 1;\n\n    if(Player[A].Character == 2)\n    {\n        target_speed *= 0.9_r;\n        rate = rate / 2;\n    }\n    else if(Player[A].Character == 3)\n        target_speed *= 0.85_r;\n    else if(Player[A].Character == 4)\n        target_speed *= 1.1_r;\n    else if(Player[A].Character == 5)\n    {\n        target_speed *= 0.95_r;\n        rate = rate * 3 / 4;\n    }\n\n    num_t target_speed_x = 0;\n    num_t target_speed_y = 0;\n    int rate_x = rate / 2;\n    int rate_y = rate / 2;\n\n    int old_swim_dir = (Player[A].Direction > 0) ? MAZE_DIR_RIGHT : MAZE_DIR_LEFT;\n\n    // keep old direction if present\n    if(Player[A].Frame == 19 || Player[A].Frame == 20 || Player[A].Frame == 21)\n        old_swim_dir = MAZE_DIR_DOWN;\n    else if(Player[A].Frame == 40 || Player[A].Frame == 41 || Player[A].Frame == 42)\n        old_swim_dir = MAZE_DIR_UP;\n\n    int new_swim_dir = old_swim_dir;\n\n    if((Player[A].Controls.Up && current_swim_dir != MAZE_DIR_DOWN) || current_swim_dir == MAZE_DIR_UP)\n    {\n        new_swim_dir = MAZE_DIR_UP;\n        target_speed_y = -target_speed;\n\n        if(Player[A].Location.SpeedY < 0)\n            rate_y = rate;\n        else\n            rate_y = rate * 2;\n\n        if(current_swim_dir == MAZE_DIR_UP)\n        {\n            target_speed_y -= base_speed;\n            rate_y *= 2;\n        }\n    }\n    else if(Player[A].Controls.Down || current_swim_dir == MAZE_DIR_DOWN)\n    {\n        new_swim_dir = MAZE_DIR_DOWN;\n        target_speed_y = target_speed;\n\n        if(Player[A].Location.SpeedY > 0)\n            rate_y = rate;\n        else\n            rate_y = rate * 2;\n\n        if(current_swim_dir == MAZE_DIR_DOWN)\n        {\n            target_speed_y += base_speed;\n            rate_y *= 2;\n        }\n    }\n\n    if((Player[A].Controls.Left && current_swim_dir != MAZE_DIR_RIGHT) || current_swim_dir == MAZE_DIR_LEFT)\n    {\n        new_swim_dir = MAZE_DIR_LEFT;\n        target_speed_x = -target_speed;\n\n        if(Player[A].Location.SpeedX < 0)\n            rate_x = rate;\n        else\n            rate_x = rate * 2;\n\n        if(current_swim_dir == MAZE_DIR_LEFT)\n        {\n            target_speed_x -= base_speed;\n            rate_x *= 2;\n        }\n\n        Player[A].Direction = -1;\n    }\n    else if(Player[A].Controls.Right || current_swim_dir == MAZE_DIR_RIGHT)\n    {\n        new_swim_dir = MAZE_DIR_RIGHT;\n        target_speed_x = target_speed;\n\n        if(Player[A].Location.SpeedX > 0)\n            rate_x = rate;\n        else\n            rate_x = rate * 2;\n\n        if(current_swim_dir == MAZE_DIR_RIGHT)\n        {\n            target_speed_x += base_speed;\n            rate_x *= 2;\n        }\n\n        Player[A].Direction = 1;\n    }\n\n    // interrupt animation when the swim dir changes\n    if(new_swim_dir != old_swim_dir)\n        Player[A].FrameCount = 0;\n\n    // go a bit slower vertically\n    target_speed_y = target_speed_y * 3 / 4;\n\n    Player[A].Location.SpeedX = (target_speed_x * rate_x + Player[A].Location.SpeedX * (256 - rate_x)) / 256;\n    Player[A].Location.SpeedY = (target_speed_y * rate_y + Player[A].Location.SpeedY * (256 - rate_y)) / 256;\n\n    // stop (X)\n    if(num_t::abs(Player[A].Location.SpeedX) < 0.03125_n)\n        Player[A].Location.SpeedX = 0;\n\n    // stop (Y)\n    if(num_t::abs(Player[A].Location.SpeedY) < 0.03125_n)\n        Player[A].Location.SpeedY = 0;\n\n    // apply gravity if about to stop being wet -- this keeps us in the water\n    if(Player[A].Wet == 1)\n        Player[A].Location.SpeedY += Physics.PlayerGravity;\n\n    if(Player[A].SwimCount == 0)\n    {\n        if((Player[A].Controls.Jump && Player[A].CanJump) ||\n           (Player[A].Controls.AltJump && Player[A].CanAltJump))\n        {\n            Player[A].SwimCount = 16 * new_swim_dir + 15;\n            PlaySoundSpatial(SFX_Swim, Player[A].Location);\n        }\n    }\n\n    Player[A].CanJump = !Player[A].Controls.Jump;\n    Player[A].CanAltJump = !Player[A].Controls.AltJump;\n}\n\nvoid PlayerMazeZoneMovement(int A)\n{\n    if(Player[A].Mount == 3)\n    {\n        Player[A].Duck = true;\n        SizeCheck(Player[A]);\n    }\n    else if(!Player[A].Rolling && !Player[A].AquaticSwim)\n        UnDuck(Player[A]);\n\n    Player[A].Bumped = 0;\n    Player[A].Bumped2 = 0;\n    Player[A].Jump = 0;\n    Player[A].SpinJump = false;\n\n    if(Player[A].MazeZoneStatus & MAZE_PLAYER_FLIP)\n        Player[A].MazeZoneStatus = (Player[A].MazeZoneStatus & 3) ^ MAZE_DIR_FLIP_BIT;\n\n    PhysEnv_Maze(Player[A].Location, Player[A].CurMazeZone, Player[A].MazeZoneStatus, 0, A, Player[A].Rolling ? 6 : (Player[A].Quicksand ? 1 : (Player[A].Wet && !Player[A].AquaticSwim ? 2 : 4)), {Player[A].Controls.Left, Player[A].Controls.Up, Player[A].Controls.Right, Player[A].Controls.Down});\n\n    if(!Player[A].CurMazeZone)\n    {\n        Player[A].WetFrame = Player[A].Wet && !Player[A].Quicksand;\n\n        // prevent unexpected block clipping\n        if(Player[A].MazeZoneStatus % 4 == MAZE_DIR_DOWN)\n        {\n            // make sure the player won't clip into blocks below the maze zone\n            PlayerPush(A, 1);\n\n            // unduck the player now if they were on Mount 3, and then make sure they don't hit the top of the maze zone ceiling\n            if(Player[A].Duck && !Player[A].Controls.Down && !Player[A].Rolling)\n            {\n                UnDuck(Player[A]);\n                PlayerPush(A, 3);\n            }\n        }\n        // help the player hit blocks above the maze zone\n        else if(Player[A].MazeZoneStatus % 4 == MAZE_DIR_UP && !Player[A].Rolling)\n        {\n            Player[A].StandUp = true;\n            Player[A].StandUp2 = true;\n            Player[A].ForceHitSpot3 = true;\n        }\n\n        // don't allow jumping\n        Player[A].CanJump = false;\n        Player[A].CanAltJump = false;\n\n        PlaySoundSpatial(SFX_HeroDash, Player[A].Location);\n    }\n}\n\nvoid PlayerFlagSlideMovement(int A)\n{\n    Player[A].Location.SpeedX = 0;\n\n    // have we hit the ground yet?\n    if(Player[A].Location.SpeedY == 0 || Player[A].StandingOnNPC)\n    {\n        // this results in waiting 16 frames after hitting the ground\n        LevelMacroWhich++;\n    }\n    else\n    {\n        Player[A].Location.SpeedY = 2;\n        Player[A].Location.Y += Player[A].Location.SpeedY;\n    }\n}\n"
  },
  {
    "path": "src/player/player_npc_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n\n#include \"player.h\"\n#include \"npc.h\"\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n#include \"npc/npc_queues.h\"\n#include \"sound.h\"\n#include \"editor.h\"\n#include \"effect.h\"\n#include \"game_main.h\"\n#include \"eff_id.h\"\n#include \"collision.h\"\n#include \"layers.h\"\n#include \"config.h\"\n#include \"phys_env.h\"\n\n#include \"player/player_update_priv.h\"\n\n#include \"main/trees.h\"\n\nvoid PlayerNPCLogic(int A, bool& tempSpring, bool& tempShell, int& MessageNPC, const bool movingBlock, const int floorBlock, const tempf_t oldSpeedY)\n{\n    int floorNpc1 = 0;\n    int floorNpc2 = 0;\n    // previously tempHitSpeed. was previously a float, converted through numf_t at all assignments\n    num_t wallNpcSpeed = 0;\n    bool spinKill = false;\n\n    // cleanup variables for NPC collisions\n\n    int bounceNpc = 0; // previously tempHit. Used for JUMP detection -- new: it's the ID of the last NPC that was jumped on\n    bool hitWall = false; // previously tempHit2\n    num_t bouncePlrY = 0; // previously shared with tempLocation.Y\n    // Location_t tempLocation;\n\n    for(int B : treeNPCQuery(Player[A].Location, SORTMODE_ID))\n    {\n        if(NPC[B].Active && NPC[B].Killed == 0 && NPC[B].Effect != NPCEFF_PET_TONGUE && NPC[B].Effect != NPCEFF_PET_INSIDE)\n        {\n            // If Not (NPC(B).Type = 17 And NPC(B).CantHurt > 0) And Not (.Mount = 2 And NPC(B).Type = 56) And Not NPC(B).vehiclePlr = A And Not NPC(B).Type = 197 And Not NPC(B).Type = 237 Then\n            if(!(Player[A].Mount == 2 && NPC[B].Type == NPCID_VEHICLE) &&\n                NPC[B].vehiclePlr != A &&\n                NPC[B].Type != NPCID_GOALTAPE &&\n                NPC[B].Type != NPCID_ICE_BLOCK\n            )\n            {\n                if(NPC[B].HoldingPlayer == 0 || NPC[B]->IsABonus || (BattleMode && NPC[B].HoldingPlayer != A))\n                {\n                    if(CheckCollision(Player[A].Location, NPC[B].Location))\n                    {\n                        if((NPC[B].Type == NPCID_METALBARREL || NPC[B].Type == NPCID_CANNONENEMY || NPC[B].Type == NPCID_HPIPE_SHORT || NPC[B].Type == NPCID_HPIPE_LONG || NPC[B].Type == NPCID_VPIPE_SHORT || NPC[B].Type == NPCID_VPIPE_LONG) && NPC[B].Projectile)\n                            PlayerHurt(A);\n\n                        // the hitspot is used for collision detection to find out where to put the player after it collides with a block\n                        // the numbers tell what side the collision happened so it can move the plaer to the correct position\n                        // 1 means the player hit the block from the top\n                        // 2 is from the right\n                        // 3 is from the bottom\n                        // 4 is from the left\n                        int HitSpot;\n                        if((Player[A].Mount == 1 || Player[A].Mount == 3 || Player[A].SpinJump || (Player[A].ShellSurf && NPC[B]->IsAShell) || (Player[A].Stoned && !NPC[B]->CanWalkOn)) && !NPC[B]->MovesPlayer)\n                            HitSpot = BootCollision(Player[A].Location, NPC[B].Location, NPC[B]->CanWalkOn); // find the hitspot for normal mario\n                        else\n                            HitSpot = EasyModeCollision(Player[A].Location, NPC[B].Location, NPC[B]->CanWalkOn); // find the hitspot when in a shoe or on a yoshi\n\n                        if(GameOutro)\n                            HitSpot = 0;\n\n                        if(NPC[B].Inert) // if the npc is friendly then you can't touch it\n                        {\n                            HitSpot = 0;\n                            if(NPC[B].Text != STRINGINDEX_NONE && Player[A].Controls.Up && !FreezeNPCs)\n                                MessageNPC = B;\n                        }\n\n                        // BEGIN type-based \"onCollidePlayer\" logic\n\n                        if(!NPC[B].Inert)\n                        {\n                            // battlemode stuff\n                            if(NPC[B].Type == NPCID_PLR_FIREBALL || NPC[B].Type == NPCID_PLR_HEAVY || NPC[B].Type == NPCID_PLR_ICEBALL || NPC[B].Type == NPCID_SWORDBEAM || NPC[B].Type == NPCID_PET_FIRE || NPC[B].Type == NPCID_CHAR3_HEAVY || NPC[B].Type == NPCID_CHAR4_HEAVY)\n                            {\n                                if(BattleMode && NPC[B].CantHurtPlayer != A)\n                                {\n                                    // duck projectile-resistance in heavy suit\n                                    if(Player[A].State == 6 && Player[A].Duck && Player[A].Character != 5)\n                                        NPCHit(B, 3, B);\n                                    else\n                                    {\n                                        if(Player[A].Immune == 0)\n                                        {\n                                            NPCHit(B, 3, B);\n                                            if(NPC[B].Type == NPCID_SWORDBEAM)\n                                                PlaySoundSpatial(SFX_HeroHit, Player[A].Location);\n                                        }\n                                        PlayerHurt(A);\n                                    }\n                                }\n                                HitSpot = 0;\n                            }\n\n                            if(NPC[B].Type == NPCID_BULLET && NPC[B].CantHurt > 0)\n                            {\n                                if(!BattleMode)\n                                    HitSpot = 0;\n                                else if(NPC[B].CantHurtPlayer != A)\n                                {\n                                    if(HitSpot != 1)\n                                        PlayerHurt(A);\n                                    else\n                                    {\n                                        NPC[B].CantHurt = 0;\n                                        NPC[B].CantHurtPlayer = 0;\n                                        NPC[B].Projectile = false;\n                                    }\n                                }\n                            }\n\n                            if((NPC[B].Type == NPCID_TOOTHY || NPC[B].Type == NPCID_HEAVY_THROWN) && BattleMode && NPC[B].CantHurtPlayer != A)\n                                PlayerHurt(A);\n\n                            if((NPC[B].Type == NPCID_ICE_CUBE || NPC[B].Type == NPCID_ITEM_POD) && BattleMode &&\n                                NPC[B].CantHurtPlayer != A && NPC[B].Projectile != 0 && NPC[B].BattleOwner != A)\n                            {\n                                if(Player[A].Immune == 0 && NPC[B].Type == NPCID_ITEM_POD)\n                                    NPC[B].Special2 = 1;\n\n                                PlayerHurt(A);\n                                HitSpot = 0;\n                            }\n\n                            if((NPC[B]->IsAShell || NPCIsVeggie(NPC[B]) ||\n                                NPC[B].Type == NPCID_ICE_CUBE || NPC[B].Type == NPCID_SLIDE_BLOCK) &&\n                                BattleMode && NPC[B].HoldingPlayer > 0 && NPC[B].HoldingPlayer != A)\n                            {\n                                if(Player[A].Immune == 0)\n                                {\n                                    PlayerHurt(A);\n                                    NPCHit(B, 5, B);\n                                }\n                            }\n\n                            if(NPCIsAParaTroopa(NPC[B]) && BattleMode && NPC[B].CantHurtPlayer == A)\n                                HitSpot = 0;\n\n                            if(BattleMode && NPCIsVeggie(NPC[B]) && NPC[B].Projectile != 0)\n                            {\n                                if(NPC[B].CantHurtPlayer != A)\n                                {\n                                    if(Player[A].Immune == 0)\n                                    {\n                                        PlayerHurt(A);\n                                        NPCHit(B, 4, B);\n                                        PlaySoundSpatial(SFX_SpitBossHit, Player[A].Location);\n                                    }\n                                }\n                            }\n\n                            if(BattleMode && NPC[B].HoldingPlayer > 0 && NPC[B].HoldingPlayer != A)\n                            {\n                                if(NPC[B]->WontHurt)\n                                    HitSpot = 0;\n                                else\n                                    HitSpot = 5;\n                            }\n\n                            if(BattleMode && NPC[B].BattleOwner != A && NPC[B].Projectile != 0 && NPC[B].CantHurtPlayer != A)\n                            {\n                                if(NPC[B].Type == NPCID_BOMB || NPC[B].Type == NPCID_LIT_BOMB_S3 || NPC[B].Type == NPCID_CARRY_BLOCK_A || NPC[B].Type == NPCID_CARRY_BLOCK_B || NPC[B].Type == NPCID_CARRY_BLOCK_C || NPC[B].Type == NPCID_CARRY_BLOCK_D || NPC[B].Type == NPCID_HIT_CARRY_FODDER || ((NPC[B]->IsAShell || NPC[B].Type == NPCID_SLIDE_BLOCK) && NPC[B].Location.SpeedX == 0))\n                                {\n                                    if(NPC[B]->IsAShell && HitSpot == 1 && Player[A].SpinJump)\n                                    {}\n                                    else if(Player[A].Immune == 0)\n                                    {\n                                        if(NPC[B].Type != NPCID_SLIDE_BLOCK && !NPC[B]->IsAShell)\n                                            NPCHit(B, 3, B);\n                                        PlayerHurt(A);\n                                        HitSpot = 0;\n                                    }\n                                }\n                            }\n                            // end battlemode\n                        }\n\n                        if(NPC[B].Type == NPCID_ITEM_BUBBLE)\n                        {\n                            NPCHit(B, 1, A);\n                            HitSpot = 0;\n                        }\n\n                        if(NPC[B].Type == NPCID_HEAVY_THROWN && NPC[B].CantHurt > 0)\n                            HitSpot = 0;\n\n                        if(NPC[B].Type == NPCID_ITEM_POD && HitSpot == 1)\n                            HitSpot = 0;\n\n                        // ' Fireball immune for ducking in the hammer suit\n                        if((Player[A].State == 6 && Player[A].Duck && Player[A].Mount == 0 && Player[A].Character != 5) || (Player[A].Mount == 1 && Player[A].MountType == 2))\n                        {\n                            if(NPC[B].Type == NPCID_STATUE_FIRE || NPC[B].Type == NPCID_VILLAIN_FIRE || NPC[B].Type == NPCID_PLANT_FIREBALL || NPC[B].Type == NPCID_QUAD_BALL)\n                            {\n                                PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n                                HitSpot = 0;\n                                NPC[B].Killed = 9;\n                                NPCQueues::Killed.push_back(B);\n                                for(int C = 1; C <= 10; ++C)\n                                {\n                                    NewEffect(EFFID_PLR_FIREBALL_TRAIL, NPC[B].Location, NPC[B].Special);\n                                    Effect[numEffects].Location.SpeedX = dRand() * 3 - 1.5_n + NPC[B].Location.SpeedX / 10;\n                                    Effect[numEffects].Location.SpeedY = dRand() * 3 - 1.5_n - NPC[B].Location.SpeedY / 10;\n                                    if(Effect[numEffects].Frame == 0)\n                                        Effect[numEffects].Frame = -iRand(3);\n                                    else\n                                        Effect[numEffects].Frame = 5 + iRand(3);\n                                }\n                                NPC[B].Location.X += NPC[B].Location.Width / 2 - EffectWidth[EFFID_SMOKE_S3] * 0.5_n;\n                                NPC[B].Location.Y += NPC[B].Location.Height / 2 - EffectHeight[EFFID_SMOKE_S3] * 0.5_n;\n                                NewEffect(EFFID_SMOKE_S3, NPC[B].Location);\n\n                                treeNPCUpdate(B);\n                            }\n                        }\n\n\n                        if(NPC[B]->IsAVine) // if the player collided with a vine then see if he should climb it\n                            p_PlayerTouchVine(Player[A], NPC[B].Location.Y, B, 0);\n\n                        // subcon warps\n                        if(NPC[B].Type == NPCID_MAGIC_DOOR && HitSpot > 0 && Player[A].Controls.Up)\n                        {\n                            if(NPC[B].Variant <= maxSections)\n                            {\n                                NPC[B].Killed = 9;\n                                NPCQueues::Killed.push_back(B);\n                                PlaySoundSpatial(SFX_Door, Player[A].Location);\n                                Player[A].Effect = PLREFF_WARP_DOOR;\n                                Player[A].Warp = numWarps + 1;\n                                Player[A].WarpBackward = false;\n                                Warp[numWarps + 1].Entrance = static_cast<SpeedlessLocation_t>(NPC[B].Location);\n\n                                Location_t tempLocation = NPC[B].Location;\n                                tempLocation.X = NPC[B].Location.X - level[Player[A].Section].X + level[NPC[B].Variant].X;\n                                tempLocation.Y = NPC[B].Location.Y - level[Player[A].Section].Y + level[NPC[B].Variant].Y;\n                                Warp[numWarps + 1].Exit = static_cast<SpeedlessLocation_t>(tempLocation);\n                                Warp[numWarps + 1].Hidden = false;\n                                Warp[numWarps + 1].NoYoshi = false;\n                                Warp[numWarps + 1].WarpNPC = true;\n                                Warp[numWarps + 1].Locked = false;\n                                Warp[numWarps + 1].Stars = 0;\n                                Player[A].Location.SpeedX = 0;\n                                Player[A].Location.SpeedY = 0;\n                                // Stop\n                                Player[A].Location.X = Warp[Player[A].Warp].Entrance.X + (Warp[Player[A].Warp].Entrance.Width - Player[A].Location.Width) / 2;\n                                Player[A].Location.Y = Warp[Player[A].Warp].Entrance.Y + Warp[Player[A].Warp].Entrance.Height - Player[A].Location.Height;\n                                tempLocation = static_cast<Location_t>(Warp[numWarps + 1].Entrance);\n                                tempLocation.Y -= 32;\n                                tempLocation.Height = 64;\n                                NewEffect(EFFID_DOOR_S2_OPEN, tempLocation);\n                                tempLocation = static_cast<Location_t>(Warp[numWarps + 1].Exit);\n                                tempLocation.Y -= 32;\n                                tempLocation.Height = 64;\n                                NewEffect(EFFID_DOOR_S2_OPEN, tempLocation);\n\n                                // this is a bug that came from re-using tempLocation.Y in the original code\n                                bouncePlrY = tempLocation.Y;\n                            }\n                        }\n\n                        if(NPC[B].Type == NPCID_LOCK_DOOR && Player[A].HasKey)\n                        {\n                            Player[A].HasKey = false;\n                            HitSpot = 0;\n                            NPC[B].Killed = 3;\n                            NPCQueues::Killed.push_back(B);\n                        }\n\n                        if(NPC[B].Type == NPCID_MINIBOSS && NPC[B].Special == 4 && HitSpot > 1)\n                            HitSpot = 0;\n\n                        // END type-based \"onCollidePlayer\" logic\n\n                        if(Player[A].Stoned && HitSpot != 1) // if you are a statue then SLAM into the npc\n                        {\n                            if(Player[A].Location.SpeedX > 3 || Player[A].Location.SpeedX < -3)\n                                NPCHit(B, 3, B);\n                        }\n\n                        // kill things with the vehicle\n                        if(Player[A].Mount == 2 && !Player[A].SpinJump && !(Player[A].Stoned && !NPC[B]->CanWalkOn))\n                        {\n                            // already checked at top\n                            // if(NPC[B].vehiclePlr == A)\n                            //     HitSpot = 0;\n                            // else\n                            if(!(NPC[B].Type == NPCID_BULLET && NPC[B].CantHurt > 0))\n                            {\n                                if((NPC[B].Location.Y + NPC[B].Location.Height > Player[A].Location.Y + 18 && HitSpot != 3) || HitSpot == 1)\n                                {\n                                    NPCHit(B, 8, A);\n                                    if(NPC[B].Killed == 8)\n                                        HitSpot = 0;\n\n                                    if(NPC[B].Type == NPCID_WALK_BOMB_S2 || NPC[B].Type == NPCID_WALK_BOMB_S3 || NPC[B].Type == NPCID_LIT_BOMB_S3)\n                                    {\n                                        NPCHit(B, 3, B);\n                                        if(NPC[B].Killed == 3)\n                                            HitSpot = 0;\n                                    }\n                                }\n                            }\n                        }\n\n                        // prevented player from grabbing active slide blocks; logic moved to side grab code\n                        // if(NPC[B].Type == NPCID_SLIDE_BLOCK && NPC[B].Projectile != 0 && HitSpot > 1)\n                        //     HitSpot = 5;\n\n                        if(HitSpot == 1) // Player landed on a NPC\n                        {\n                            // the following code is for spin jumping and landing on things as yoshi/shoe\n                            if(!InvincibilityTime && (Player[A].Mount == 1 || Player[A].Mount == 3 || Player[A].SpinJump || (Player[A].Stoned && !NPC[B]->CanWalkOn)))\n                            {\n                                // these types were not hit during spin bounces in SMBX 1.3 (but may be vulnerable to stomps by mounts)\n                                bool dont_hit_spin_bounce = (NPC[B].Type == NPCID_FIRE_PLANT || NPC[B].Type == NPCID_QUAD_SPITTER || NPC[B].Type == NPCID_PLANT_S3 || NPC[B].Type == NPCID_LAVABUBBLE ||\n                                          NPC[B].Type == NPCID_SPIKY_S3 || NPC[B].Type == NPCID_SPIKY_S4 || NPC[B].Type == NPCID_SPIKY_BALL_S4 || NPC[B].Type == NPCID_BOTTOM_PLANT ||\n                                          NPC[B].Type == NPCID_SIDE_PLANT || NPC[B].Type == NPCID_CRAB || NPC[B].Type == NPCID_FLY || NPC[B].Type == NPCID_BIG_PLANT ||\n                                          NPC[B].Type == NPCID_PLANT_S1 || NPC[B].Type == NPCID_VILLAIN_S1 || NPC[B].Type == NPCID_WALL_BUG || NPC[B].Type == NPCID_WALL_TURTLE ||\n                                          NPC[B].Type == NPCID_SICK_BOSS || NPC[B].Type == NPCID_WALK_PLANT || NPC[B].Type == NPCID_JUMP_PLANT);\n\n                                // these types are immune to spin bounces and were hit (with no effect) in SMBX 1.3 but are no longer hit\n                                bool immune_to_spin_bounce = (NPC[B].Type == NPCID_SAW || NPC[B].Type == NPCID_STONE_S3 || NPC[B].Type == NPCID_STONE_S4 || NPC[B].Type == NPCID_GHOST_S3 ||\n                                   NPC[B].Type == NPCID_GHOST_FAST || NPC[B].Type == NPCID_GHOST_S4 || NPC[B].Type == NPCID_BIG_GHOST || NPC[B].Type == NPCID_LAVA_MONSTER || NPC[B].Type == NPCID_LONG_PLANT_UP);\n\n                                bool force_bounce = (dont_hit_spin_bounce | immune_to_spin_bounce);\n\n                                if(Player[A].Mount == 1 || Player[A].Mount == 2 || Player[A].Stoned)\n                                    NPCHit(B, 8, A);\n                                else if(!force_bounce && !NPC[B]->CanWalkOn)\n                                {\n                                    if(Player[A].Wet > 0 && (NPC[B]->IsFish || NPC[B].Type == NPCID_SQUID_S3 || NPC[B].Type == NPCID_SQUID_S1))\n                                    {\n                                    }\n                                    else\n                                        NPCHit(B, 8, A);\n                                }\n\n                                if(NPC[B].Killed == 8 || NPC[B]->IsFish || force_bounce) // tap\n                                {\n                                    if(NPC[B].Killed == 8 && Player[A].Mount == 1 && Player[A].MountType == 2)\n                                    {\n                                        for(int i = 0; i < 2; i++)\n                                        {\n                                            numNPCs++;\n                                            NPC[numNPCs].Active = true;\n                                            NPC[numNPCs].TimeLeft = 100;\n                                            NPC[numNPCs].Section = Player[A].Section;\n                                            NPC[numNPCs].Type = NPCID_PLR_FIREBALL;\n                                            NPC[numNPCs].Special = Player[A].Character;\n                                            NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                                            NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                                            NPC[numNPCs].Location.Y = Player[A].Location.Height + Player[A].Location.Y - NPC[numNPCs].Location.Height;\n                                            NPC[numNPCs].Location.X = Player[A].Location.X + (Player[A].Location.Width - NPC[numNPCs].Location.Width) / 2;\n                                            NPC[numNPCs].Location.SpeedX = (i == 0) ? -4 : 4;\n                                            NPC[numNPCs].Location.SpeedY = 10;\n                                            syncLayers_NPC(numNPCs);\n                                        }\n                                    }\n\n                                    Player[A].ForceHitSpot3 = true;\n                                    if(/*HitSpot == 1 && */ !(Player[A].GroundPound && NPC[B].Killed == 8))\n                                    {\n                                        bounceNpc = B;\n                                        bouncePlrY = NPC[B].Location.Y - Player[A].Location.Height;\n\n                                        if(NPC[B].Killed == 0)\n                                            PlaySoundSpatial(SFX_Stomp, Player[A].Location);\n                                        else if(Player[A].SpinJump)\n                                        {\n                                            if(Player[A].Controls.Down)\n                                                bounceNpc = 0;\n                                            else\n                                                spinKill = true;\n                                        }\n                                    }\n\n                                    continue;\n                                }\n                            }\n\n                            // moved from above\n                            if(/* HitSpot == 1 && */ (NPC[B].Type == NPCID_COIN_SWITCH || NPC[B].Type == NPCID_TIME_SWITCH || NPC[B].Type == NPCID_TNT) && NPC[B].Projectile != 0)\n                                HitSpot = 0;\n                            // NPCs that can be walked on\n                            else if(NPC[B]->CanWalkOn || (Player[A].ShellSurf && NPC[B]->IsAShell))\n                            {\n                                // the player landed on an NPC he can stand on\n                                if(floorNpc1 == 0)\n                                    floorNpc1 = B;\n                                else if(floorNpc2 == 0)\n                                    floorNpc2 = B;\n                                else if(Player[A].StandingOnNPC == B)\n                                {\n                                    // if standing on 2 or more NPCs find out the best one to stand on\n                                    num_t C = num_t::abs(NPC[floorNpc1].Location.minus_center_x(Player[A].Location));\n                                    num_t D = num_t::abs(NPC[floorNpc2].Location.minus_center_x(Player[A].Location));\n\n                                    if(C < D)\n                                        floorNpc2 = B;\n                                    else\n                                        floorNpc1 = B;\n                                }\n                                else\n                                    floorNpc2 = B;\n                            }\n                            // if landing on a yoshi or boot, mount up!\n                            else if((NPCIsYoshi(NPC[B]) || NPCIsBoot(NPC[B])) && Player[A].Character != 5 && !Player[A].Fairy)\n                            {\n                                if(Player[A].Mount == 0 && NPC[B].CantHurtPlayer != A && Player[A].Dismount == 0)\n                                {\n                                    if(NPCIsBoot(NPC[B]))\n                                    {\n                                        UnDuck(Player[A]);\n                                        NPC[B].Killed = 9;\n                                        NPCQueues::Killed.push_back(B);\n\n                                        if(Player[A].State == 1)\n                                        {\n                                            Player[A].Location.Height = Physics.PlayerHeight[1][2];\n                                            Player[A].Location.Y += -Physics.PlayerHeight[1][2] + Physics.PlayerHeight[Player[A].Character][1];\n                                        }\n\n                                        Player[A].Mount = 1;\n\n                                        if(NPC[B].Type == NPCID_GRN_BOOT)\n                                            Player[A].MountType = 1;\n\n                                        if(NPC[B].Type == NPCID_RED_BOOT)\n                                            Player[A].MountType = 2;\n\n                                        if(NPC[B].Type == NPCID_BLU_BOOT)\n                                            Player[A].MountType = 3;\n\n                                        PlaySoundSpatial(SFX_Stomp, Player[A].Location);\n                                    }\n                                    else if(NPCIsYoshi(NPC[B]) && (Player[A].Character == 1 || Player[A].Character == 2))\n                                    {\n                                        UnDuck(Player[A]);\n                                        NPC[B].Killed = 9;\n                                        NPCQueues::Killed.push_back(B);\n                                        Player[A].Mount = 3;\n\n                                        if(NPC[B].Type == NPCID_PET_GREEN)\n                                            Player[A].MountType = 1;\n                                        else if(NPC[B].Type == NPCID_PET_BLUE)\n                                            Player[A].MountType = 2;\n                                        else if(NPC[B].Type == NPCID_PET_YELLOW)\n                                            Player[A].MountType = 3;\n                                        else if(NPC[B].Type == NPCID_PET_RED)\n                                            Player[A].MountType = 4;\n                                        else if(NPC[B].Type == NPCID_PET_BLACK)\n                                            Player[A].MountType = 5;\n                                        else if(NPC[B].Type == NPCID_PET_PURPLE)\n                                            Player[A].MountType = 6;\n                                        else if(NPC[B].Type == NPCID_PET_PINK)\n                                            Player[A].MountType = 7;\n                                        else if(NPC[B].Type == NPCID_PET_CYAN)\n                                            Player[A].MountType = 8;\n\n                                        Player[A].YoshiNPC = 0;\n                                        Player[A].YoshiPlayer = 0;\n                                        Player[A].MountSpecial = 0;\n                                        Player[A].YoshiTonugeBool = false;\n                                        Player[A].YoshiTongueLength = 0;\n                                        PlaySoundSpatial(SFX_Pet, Player[A].Location);\n                                        UpdateYoshiMusic();\n                                        YoshiHeight(A);\n                                    }\n                                }\n                            }\n                            else if(NPC[B].Type == NPCID_CANNONITEM || NPC[B].Type == NPCID_KEY ||\n                                    NPC[B].Type == NPCID_TOOTHYPIPE || NPC[B].Type == NPCID_TOOTHY ||\n                                    ((Player[A].SlideKill || InvincibilityTime) && !NPC[B]->WontHurt)) // NPCs that cannot be walked on\n                            {\n                                // cancel jump\n                            }\n#if 0\n                            // dead code since SMBX 1.3, because NoShellKick was never set\n                            else if(NPC[B].CantHurtPlayer == A && Player[A].NoShellKick > 0)\n                            {\n                                // Do nothing!\n                            }\n#endif\n                            else\n                            {\n                                if(NPC[B]->IsABonus) // Bonus\n                                    TouchBonus(A, B);\n                                else if(Player[A].Rolling && Player[A].State == PLR_STATE_SHELL)\n                                {\n                                    if(NPC[B].Type == NPCID_SPRING)\n                                    {\n                                        tempSpring = true;\n                                        bounceNpc = B;\n                                        bouncePlrY = NPC[B].Location.Y - Player[A].Location.Height;\n                                    }\n                                    else if(NPC[B].CantHurtPlayer != A)\n                                        NPCHit(B, 3, B);\n\n                                    continue;\n                                }\n                                else if(NPC[B]->IsAShell && NPC[B].Location.SpeedX == 0 && Player[A].HoldingNPC == 0 && Player[A].Controls.Run && !g_config.no_shell_grab_top)\n                                {\n                                    // grab turtle shells\n                                    //if(nPlay.Online == false || nPlay.MySlot + 1 == A)\n                                    {\n                                        if(Player[A].Character >= 3)\n                                            PlaySoundSpatial(SFX_Grab, Player[A].Location);\n                                        else\n                                            UnDuck(Player[A]);\n\n                                        Player[A].HoldingNPC = B;\n                                        NPC[B].HoldingPlayer = A;\n                                        NPC[B].CantHurt = Physics.NPCCanHurtWait;\n                                        NPC[B].CantHurtPlayer = A;\n                                    }\n\n                                }\n                                else if(NPC[B]->JumpHurt || (NPC[B]->IsFish && Player[A].WetFrame)) // NPCs that cause damage even when jumped on\n                                {\n                                    if(!(NPC[B].Type == NPCID_PLANT_S3 && NPC[B].Special2 == 4) && !NPC[B]->WontHurt && NPC[B].CantHurtPlayer != A)\n                                    {\n\n                                        // the n00bcollision function reduces the size of the npc's hit box before it damages the player\n                                        if(n00bCollision(Player[A].Location, NPC[B].Location))\n                                            PlayerHurt(A);\n                                    }\n                                }\n                                else if(NPC[B].Type == NPCID_MINIBOSS) // Special code for BOOM BOOM\n                                {\n                                    if(NPC[B].Special == 0 || Player[A].Mount == 1 || Player[A].Mount == 3)\n                                    {\n                                        if(NPC[B].Special != 0)\n                                            PlaySoundSpatial(SFX_Stomp, Player[A].Location);\n                                        bounceNpc = B;\n                                        bouncePlrY = NPC[B].Location.Y - Player[A].Location.Height;\n                                    }\n                                    else if(NPC[B].Special != 4)\n                                    {\n                                        if(n00bCollision(Player[A].Location, NPC[B].Location))\n                                            PlayerHurt(A);\n                                    }\n                                }\n                                else if((NPC[B].Type == NPCID_LIT_BOMB_S3) || NPC[B].Type == NPCID_HIT_CARRY_FODDER)\n                                    NPCHit(B, 1, A); // NPC 'B' was jumped on '1' by player 'A'\n                                else if(NPC[B].Killed != 10 && !NPCIsBoot(NPC[B]) && !NPCIsYoshi(NPC[B]) && !(NPC[B]->IsAShell && NPC[B].CantHurtPlayer == A)) // Bounce off everything except Bonus and Piranha Plants\n                                {\n                                    if(NPC[B].Type == NPCID_SPRING)\n                                        tempSpring = true;\n\n                                    if(NPC[B]->IsAShell && NPC[B].Location.SpeedX == 0 && NPC[B].Location.SpeedY == 0)\n                                        tempShell = true;\n\n                                    bounceNpc = B;\n                                    bouncePlrY = NPC[B].Location.Y - Player[A].Location.Height;\n\n                                    if(NPC[B].Type == NPCID_COIN_SWITCH || NPC[B].Type == NPCID_TIME_SWITCH || NPC[B].Type == NPCID_TNT)\n                                    {\n                                        bounceNpc = 0;\n                                        Player[A].Jump = false;\n                                        Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n                                        Player[A].Location.SpeedY = -Physics.PlayerGravity;\n                                    }\n                                }\n\n                                // If Not (.WetFrame = True And (NPC(B).Type = 229 Or NPC(B).Type = 230) Or NPCIsAVine(NPC(B).Type)) And .HoldingNPC <> B Then\n                                if(\n                                    !(\n                                        (Player[A].WetFrame && (NPC[B].Type == NPCID_GRN_FISH_S3 || NPC[B].Type == NPCID_RED_FISH_S3)) ||\n                                        NPC[B]->IsAVine\n                                    ) && (Player[A].HoldingNPC != B)\n                                )\n                                {\n                                    if(Player[A].Vine > 0)\n                                    {\n                                        Player[A].Vine = 0;\n                                        Player[A].Jump = 1;\n                                    }\n\n                                    if(!(NPC[B]->IsAShell && NPC[B].CantHurtPlayer == A))\n                                        NPCHit(B, 1, A); // NPC 'B' was jumped on '1' by player 'A'\n                                }\n                            }\n                        }\n                        else if(HitSpot == 0)\n                        {\n                            // if hitspot = 0 then do nothing\n                        }\n                        // player touched an npc anywhere except from the top\n                        else // Player touched an NPC\n                        {\n\n/* If (.CanGrabNPCs = True Or NPCIsGrabbable(NPC(B).Type) = True Or (NPC(B).Effect = 2 And NPCIsABonus(NPC(B).Type) = False)) And (NPC(B).Effect = 0 Or NPC(B).Effect = 2) Or (NPCIsAShell(NPC(B).Type) And FreezeNPCs = True) Then      'GRAB EVERYTHING\n*/\n\n                            // grab from side\n                            if(\n                                ((Player[A].CanGrabNPCs || NPC[B]->IsGrabbable || (NPC[B].Effect == NPCEFF_DROP_ITEM && !NPC[B]->IsABonus)) && (NPC[B].Effect == NPCEFF_NORMAL || NPC[B].Effect == NPCEFF_DROP_ITEM || NPC[B].Effect == NPCEFF_MAZE)) ||\n                                 (NPC[B]->IsAShell && FreezeNPCs)\n                            ) // GRAB EVERYTHING\n                            {\n                                if(Player[A].Controls.Run && !Player[A].Rolling)\n                                {\n                                    if((HitSpot == 2 && Player[A].Direction == -1) ||\n                                       (HitSpot == 4 && Player[A].Direction == 1) ||\n                                       (NPC[B].Type == NPCID_CANNONITEM || NPC[B].Type == NPCID_TOOTHYPIPE || NPC[B].Effect == NPCEFF_DROP_ITEM || (NPCIsVeggie(NPC[B]) && NPC[B].CantHurtPlayer != A)))\n                                    {\n                                        if(NPC[B].Type == NPCID_SLIDE_BLOCK && NPC[B].Projectile != 0)\n                                        {\n                                            // don't grab active slide blocks\n                                        }\n                                        else if(Player[A].HoldingNPC == 0)\n                                        {\n                                            if(!NPC[B]->IsAShell || Player[A].Character >= 3)\n                                            {\n                                                if(NPCIsVeggie(NPC[B]))\n                                                    PlaySoundSpatial(SFX_Grab2, Player[A].Location);\n                                                else\n                                                    PlaySoundSpatial(SFX_Grab, Player[A].Location);\n                                            }\n\n                                            if(Player[A].Character <= 2)\n                                                UnDuck(Player[A]);\n                                            Player[A].HoldingNPC = B;\n                                            NPC[B].Direction = Player[A].Direction;\n                                            NPC[B].Frame = EditorNPCFrame(NPC[B].Type, NPC[B].Direction);\n                                            NPC[B].HoldingPlayer = A;\n                                            NPC[B].CantHurt = Physics.NPCCanHurtWait;\n                                            NPC[B].CantHurtPlayer = A;\n                                        }\n                                    }\n                                }\n                            }\n\n                            if(NPC[B]->IsAShell || (NPC[B].Type == NPCID_SLIDE_BLOCK && NPC[B].Special == 1)) // Turtle shell\n                            {\n                                if(Player[A].Rolling && NPC[B].HoldingPlayer == 0 && NPC[B].CantHurtPlayer != A) // Kill the shell!\n                                    NPCHit(B, 3, B);\n                                else if(NPC[B].Location.SpeedX == 0 && NPC[B].Location.SpeedY >= 0) // Shell is not moving\n                                {\n                                    if(((Player[A].Controls.Run && Player[A].HoldingNPC == 0) || Player[A].HoldingNPC == B) && NPC[B].CantHurtPlayer != A) // Grab the shell\n                                    {\n                                        if(Player[A].Character >= 3)\n                                            PlaySoundSpatial(SFX_Grab, Player[A].Location);\n                                        else\n                                            UnDuck(Player[A]);\n\n                                        Player[A].HoldingNPC = B;\n                                        NPC[B].HoldingPlayer = A;\n                                        NPC[B].CantHurt = Physics.NPCCanHurtWait;\n                                        NPC[B].CantHurtPlayer = A;\n                                    }\n                                    else if(NPC[B].HoldingPlayer == 0) // Kick the shell\n                                    {\n                                        if((Player[A].Mount == 1 || Player[A].Mount == 2 || Player[A].Mount == 3) && NPC[B].Type != NPCID_SLIDE_BLOCK)\n                                        {\n                                            if(NPC[B].Type != NPCID_FLIPPED_RAINBOW_SHELL)\n                                            {\n                                                bouncePlrY = Player[A].Location.Y;\n                                                bounceNpc = B;\n                                                NPCHit(B, 8, A);\n                                            }\n                                        }\n                                        else\n                                        {\n                                            Location_t tempLocation;\n                                            tempLocation.Height = 0;\n                                            tempLocation.Width = 0;\n                                            tempLocation.Y = (Player[A].Location.Y + NPC[B].Location.Y * 4) / 5;\n                                            tempLocation.X = (Player[A].Location.X + NPC[B].Location.X * 4) / 5;\n                                            NewEffect(EFFID_STOMP_INIT, tempLocation);\n\n                                            NPC[B].CantHurt = 0;\n                                            NPC[B].CantHurtPlayer = 0;\n                                            NPCHit(B, 1, A);\n\n                                            // this is a bug that came from re-using tempLocation.Y in the original code\n                                            bouncePlrY = tempLocation.Y;\n                                        }\n                                    }\n                                }\n                                else if(NPC[B].Location.SpeedX != 0) // Got hit by the shell\n                                {\n                                    if(NPC[B].CantHurtPlayer != A && !FreezeNPCs && NPC[B].Type != NPCID_FLIPPED_RAINBOW_SHELL)\n                                    {\n                                        if(n00bCollision(Player[A].Location, NPC[B].Location))\n                                            PlayerHurt(A);\n                                    }\n                                }\n                            }\n                            else if(NPC[B]->IsABonus) // Bonus\n                                TouchBonus(A, B);\n                            else // Everything else\n                            {\n                                // player-npc hurt routine:\n\n                                // kick the bob-om\n                                if((NPC[B].Type == NPCID_LIT_BOMB_S3 || NPC[B].Type == NPCID_HIT_CARRY_FODDER) && NPC[B].HoldingPlayer != A)\n                                {\n                                    if(NPC[B].TailCD == 0)\n                                    {\n                                        NPC[B].TailCD = 12;\n                                        if(NPC[B].Type != NPCID_HIT_CARRY_FODDER && NPC[B].Type != NPCID_LIT_BOMB_S3)\n                                            NewEffect(EFFID_WHACK, newLoc((Player[A].Location.X + NPC[B].Location.X + (Player[A].Location.Width + NPC[B].Location.Width) / 2) / 2, (Player[A].Location.Y + NPC[B].Location.Y + (Player[A].Location.Height + NPC[B].Location.Height) / 2) / 2));\n                                        NPCHit(B, 1, A);\n                                    }\n                                }\n                                // don't hurt own thrown items\n                                else if(NPC[B].CantHurtPlayer == A) {}\n                                // don't hurt NPC of friendly type\n                                else if(NPC[B]->WontHurt && !(Player[A].Rolling && NPC[B].Type == NPCID_BOSS_CASE)) {}\n                                // also cancel hurt for player-shot bullet\n                                else if(NPC[B].Type == NPCID_BULLET && NPC[B].Projectile != 0) {}\n                                // special routine for hit turtles that haven't stood up yet\n                                else if(NPC[B].Type >= NPCID_GRN_HIT_TURTLE_S4 && NPC[B].Type <= NPCID_YEL_HIT_TURTLE_S4 && NPC[B].Projectile != 0)\n                                    NPCHit(B, 3, B);\n                                // cancel hurt for dropped items\n                                else if(NPC[B].Effect == NPCEFF_DROP_ITEM) {}\n                                // someone's going to get hurt...\n                                else\n                                {\n                                    if(InvincibilityTime || (Player[A].SlideKill && !NPC[B]->JumpHurt) || (Player[A].Rolling && Player[A].State == PLR_STATE_SHELL))\n                                    {\n                                        if(Player[A].Rolling && (NPC[B].Type == NPCID_FIRE_BOSS_SHELL || NPC[B].Type == NPCID_MAGIC_BOSS_SHELL || NPC[B].Type == NPCID_HEAVY_THROWN || NPC[B].Type == NPCID_HOMING_BALL))\n                                        {\n                                            // be vulnerable to these NPCs while rolling\n                                        }\n                                        // DO cause damage to VILLAIN_S3 even though it's meant to be immune to this kind of damage.\n                                        else if(InvincibilityTime && NPC[B].Type == NPCID_VILLAIN_S3)\n                                        {\n                                            // But use fireball -- this will take ~6 rounds of invincibility power to kill the boss\n                                            NPC[numNPCs + 1].Type = NPCID_PLR_FIREBALL;\n                                            NPCHit(B, 3, numNPCs + 1);\n                                        }\n                                        else\n                                            NPCHit(B, 3, B);\n                                    }\n\n                                    if(NPC[B].Killed == 0)\n                                    {\n                                        // don't hurt player by just-hurt boss\n                                        if(Player[A].Rolling && (NPC[B].Immune != 0 || (NPC[B].Type == NPCID_SPIT_BOSS && NPC[B].Special5 < 0))) {}\n                                        else if(n00bCollision(Player[A].Location, NPC[B].Location))\n                                        {\n                                            if(BattleMode && NPC[B].HoldingPlayer != A && NPC[B].HoldingPlayer > 0 && Player[A].Immune == 0)\n                                                NPCHit(B, 5, B);\n                                            PlayerHurt(A);\n                                        }\n                                    }\n                                    else\n                                        MoreScore(NPC[B]->Score, NPC[B].Location, Player[A].Multiplier);\n                                }\n\n                                // npc block routine\n\n                                // this is for NPC that physically push the player\n                                if(NPC[B]->MovesPlayer && NPC[B].Projectile == 0 && Player[A].HoldingNPC != B &&\n                                   !(Player[A].Mount == 2 && (NPC[B].Type == NPCID_KEY || NPC[B].Type == NPCID_COIN_SWITCH)) &&\n                                   !ShadowMode && NPC[B].Effect != NPCEFF_DROP_ITEM)\n                                {\n                                    if(Player[A].StandUp && Player[A].StandingOnNPC == 0)\n                                    {\n                                        if(HitSpot == 5 && Player[A].Location.Y + Player[A].Location.Height - Physics.PlayerDuckHeight[Player[A].Character][Player[A].State] - Player[A].Location.SpeedY >= NPC[B].Location.Y + NPC[B].Location.Height)\n                                            HitSpot = 3;\n                                    }\n\n                                    if(Player[A].CurMazeZone != 0)\n                                    {\n                                        // don't actually collide\n                                    }\n                                    else if(HitSpot == 3)\n                                    {\n                                        if(NPC[B].Type == NPCID_ICE_CUBE && Player[A].Character != 5 && Player[A].State > 1)\n                                            NPCHit(B, 3, B);\n\n                                        Location_t tempLocation = Player[A].Location;\n\n                                        // this is a bug that came from re-using tempLocation.Y in the original code\n                                        bouncePlrY = tempLocation.Y;\n\n                                        Player[A].Location.SpeedY = 0.1_n + NPC[B].Location.SpeedY;\n                                        Player[A].Location.Y = NPC[B].Location.Y + NPC[B].Location.Height + 0.1_n;\n\n                                        // fBlock = FirstBlock[(Player[A].Location.X / 32) - 1];\n                                        // lBlock = LastBlock[((Player[A].Location.X + Player[A].Location.Width) / 32.0) + 1];\n                                        // blockTileGet(Player[A].Location, fBlock, lBlock);\n\n                                        for(int C : treeFLBlockQuery(Player[A].Location, SORTMODE_NONE))\n                                        {\n                                            if(CheckCollision(Player[A].Location, Block[C].Location) &&\n                                               !Block[C].Hidden && !BlockIsSizable[Block[C].Type] &&\n                                               !BlockOnlyHitspot1[Block[C].Type])\n                                            {\n                                                Player[A].Location = tempLocation;\n                                                break;\n                                            }\n                                        }\n\n                                        PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n                                        Player[A].Jump = 0;\n                                        if(Player[A].Mount == 2)\n                                            Player[A].Location.SpeedY += 2;\n\n                                        if(NPC[B].Type == NPCID_METALBARREL || NPC[B].Type == NPCID_CANNONENEMY || NPC[B].Type == NPCID_HPIPE_SHORT || NPC[B].Type == NPCID_HPIPE_LONG || NPC[B].Type == NPCID_VPIPE_SHORT || NPC[B].Type == NPCID_VPIPE_LONG || (NPC[B].Type >= NPCID_TANK_TREADS && NPC[B].Type <= NPCID_SLANT_WOOD_M))\n                                        {\n                                            if(NPC[B].Location.SpeedY >= Physics.NPCGravity * 20)\n                                                PlayerHurt(A);\n                                        }\n                                    }\n                                    // player gets pushed left/right by NPC\n                                    else\n                                    {\n                                        hitWall = true;\n                                        // this variable was previously a numf_t\n                                        wallNpcSpeed = (num_t)(tempf_t)(NPC[B].Location.SpeedX + (num_t)NPC[B].BeltSpeed);\n\n                                        // reset player speed if not on conveyor belt\n                                        bool tempBool = false;\n                                        if(Player[A].StandingOnNPC != 0)\n                                        {\n                                            if(NPC[Player[A].StandingOnNPC].Type == NPCID_CONVEYOR)\n                                                tempBool = true;\n                                        }\n\n                                        if(Player[A].Rolling)\n                                        {\n                                            PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n\n                                            // handle conveyor belts (which move with their layer, not their own SpeedX) correctly\n                                            num_t npc_speed = wallNpcSpeed;\n                                            if(NPC[B].Type == NPCID_CONVEYOR)\n                                                npc_speed = (num_t)Layer[NPC[B].Layer].ApplySpeedX;\n\n                                            // bounce player!\n                                            num_t rel_speed = Player[A].Location.SpeedX - npc_speed;\n                                            if((rel_speed > 0) == (Player[A].Direction > 0))\n                                                Player[A].Location.SpeedX = npc_speed - rel_speed;\n\n                                            if(Player[A].State != PLR_STATE_POLAR)\n                                                hitWall = false;\n                                        }\n                                        else if(!tempBool && NPC[B].Type != NPCID_CHASER)\n                                            Player[A].Location.SpeedX = 0.2_n * Player[A].Direction;\n\n                                        // reset player run count\n                                        Player[A].RunCount = 0;\n\n                                        // mark moving pinched (if needed)\n                                        if(NPC[B].Type != NPCID_KEY && NPC[B].Type != NPCID_COIN_SWITCH && NPC[B].Type != NPCID_CONVEYOR && (NPC[B].Location.SpeedX != 0 || NPC[B].Location.SpeedY != 0 || NPC[B].BeltSpeed) && !(NPC[B].Wings && NPC[B].CantHurtPlayer == A))\n                                        {\n                                            Player[A].Pinched.Moving = 2;\n\n                                            if(NPC[B].Location.SpeedX != 0 || NPC[B].BeltSpeed)\n                                                Player[A].Pinched.MovingLR = true;\n                                        }\n\n                                        // save current X location (for NPCs riding player's vehicle)\n                                        tempf_t D = (tempf_t)Player[A].Location.X;\n\n                                        // actually move the player\n                                        if(NPC[B].Location.to_right_of(Player[A].Location))\n                                        {\n                                            Player[A].Pinched.Right4 = 2;\n\n                                            if(floorBlock != 0 && num_t::abs(Block[floorBlock].Location.X - NPC[B].Location.X) < 1)\n                                            {\n                                                Player[A].Location.X = NPC[B].Location.X - Player[A].Location.Width - 1;\n                                                Player[A].Location.SpeedY = (num_t)oldSpeedY;\n                                            }\n                                            else\n                                                Player[A].Location.X = NPC[B].Location.X - Player[A].Location.Width - 0.1_n;\n\n                                            if(NPC[Player[A].StandingOnNPC].Type == NPCID_CONVEYOR)\n                                                Player[A].Location.X -= 1;\n\n                                            // forget about floors no longer supporting player\n                                            if(floorNpc1 > 0)\n                                            {\n                                                if(NPC[B].Location.X >= NPC[floorNpc1].Location.X - 2 && NPC[B].Location.X <= NPC[floorNpc1].Location.X + 2)\n                                                    floorNpc1 = floorNpc2;\n                                            }\n\n                                            if(floorNpc2 > 0)\n                                            {\n                                                if(NPC[B].Location.X >= NPC[floorNpc2].Location.X - 2 && NPC[B].Location.X <= NPC[floorNpc2].Location.X + 2)\n                                                    floorNpc2 = 0;\n                                            }\n                                        }\n                                        else\n                                        {\n                                            Player[A].Pinched.Left2 = 2;\n\n                                            if(floorBlock != 0 && num_t::abs(Block[floorBlock].Location.X + Block[floorBlock].Location.Width - NPC[B].Location.X - NPC[B].Location.Width) < 1)\n                                            {\n                                                Player[A].Location.X = NPC[B].Location.X + NPC[B].Location.Width + 1;\n                                                Player[A].Location.SpeedY = (num_t)oldSpeedY;\n                                            }\n                                            else\n                                                Player[A].Location.X = NPC[B].Location.X + NPC[B].Location.Width + 0.01_n;\n\n                                            // forget about floors no longer supporting player\n                                            if(floorNpc1 > 0)\n                                            {\n                                                if(NPC[B].Location.X + NPC[B].Location.Width >= NPC[floorNpc1].Location.X + NPC[floorNpc1].Location.Width - 2 && NPC[B].Location.X + NPC[B].Location.Width <= NPC[floorNpc1].Location.X + NPC[floorNpc1].Location.Width + 2)\n                                                    floorNpc1 = floorNpc2;\n                                            }\n\n                                            if(floorNpc2 > 0)\n                                            {\n                                                if(NPC[B].Location.X + NPC[B].Location.Width >= NPC[floorNpc2].Location.X + NPC[floorNpc2].Location.Width - 2 && NPC[B].Location.X + NPC[B].Location.Width <= NPC[floorNpc2].Location.X + NPC[floorNpc2].Location.Width + 2)\n                                                    floorNpc2 = 0;\n                                            }\n                                        }\n\n                                        // apply speed to vehicle riders if needed\n                                        if(Player[A].Mount == 2)\n                                        {\n                                            D = (tempf_t)(Player[A].Location.X - (num_t)D);\n\n                                            for(int C : NPCQueues::Active.no_change)\n                                            {\n                                                if(NPC[C].vehiclePlr == A)\n                                                {\n                                                    NPC[C].Location.X += (num_t)D;\n                                                    treeNPCUpdate(C);\n                                                }\n                                            }\n\n                                            for(int C = 1; C <= numPlayers; C++)\n                                            {\n                                                if(Player[C].StandingOnVehiclePlr && (g_ClonedPlayerMode || Player[C].StandingOnVehiclePlr == A))\n                                                    Player[C].Location.X += (num_t)D;\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    // if the player collided on the left or right of some npcs then stop his movement\n    if(hitWall)\n    {\n        if(Player[A].Location.SpeedX + NPC[Player[A].StandingOnNPC].Location.SpeedX + wallNpcSpeed > 0 && Player[A].Controls.Right)\n            Player[A].Location.SpeedX = 0.2_n * Player[A].Direction + wallNpcSpeed;\n        else if(Player[A].Location.SpeedX + NPC[Player[A].StandingOnNPC].Location.SpeedX + wallNpcSpeed < 0 && Player[A].Controls.Left)\n            Player[A].Location.SpeedX = 0.2_n * Player[A].Direction + wallNpcSpeed;\n        else\n        {\n            if(Player[A].Controls.Right || Player[A].Controls.Left)\n                Player[A].Location.SpeedX = -NPC[Player[A].StandingOnNPC].Location.SpeedX + 0.2_n * Player[A].Direction;\n            else\n                Player[A].Location.SpeedX = 0;\n        }\n\n        // disable mid-hop jumps (fixes a clipping bug)\n        if(Player[A].State == PLR_STATE_AQUATIC && !Player[A].Mount && Player[A].MountSpecial)\n            Player[A].MountSpecial = 2;\n    }\n\n    if(bounceNpc && Player[A].CurMazeZone)\n    {\n        if(NPC[bounceNpc].Effect == NPCEFF_MAZE && NPC[bounceNpc].Effect3 != MAZE_DIR_DOWN)\n            NPC[bounceNpc].TurnAround = true;\n\n        Player[A].Location.Y = bouncePlrY;\n        Player[A].Location.SpeedY += Physics.PlayerJumpVelocity / 2;\n\n        if(Player[A].MazeZoneStatus == MAZE_DIR_DOWN)\n            Player[A].MazeZoneStatus |= MAZE_PLAYER_FLIP;\n    }\n    else if(bounceNpc) // For multiple NPC hits\n    {\n        // enable another double-jump when Char4 bounces on an NPC\n        if(Player[A].Character == 4 && (Player[A].State == 4 || Player[A].State == 5) && !Player[A].SpinJump)\n            Player[A].DoubleJump = true;\n\n        Player[A].CanJump = true;\n\n        if(tempSpring)\n        {\n            Player[A].Jump = Physics.PlayerSpringJumpHeight;\n            Player[A].Location.SpeedY = Physics.PlayerJumpVelocity - 4;\n        }\n        else\n        {\n            Player[A].Jump = Physics.PlayerNPCJumpHeight;\n            Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n        }\n\n        if(Player[A].Character == 2)\n            Player[A].Jump += 3;\n\n        if(Player[A].SpinJump)\n            Player[A].Jump -= 6;\n\n        if(Player[A].State == PLR_STATE_AQUATIC)\n            Player[A].Jump += 3;\n\n        if(Player[A].Wet > 0)\n            Player[A].Location.SpeedY *= 0.3_r;\n\n        // this is very likely but not certain to be the y value stored when bounceNpc was set\n        Player[A].Location.Y = bouncePlrY;\n\n        if(tempShell)\n            NewEffect(EFFID_STOMP_INIT, newLoc(Player[A].Location.X + Player[A].Location.Width / 2 - EffectWidth[EFFID_STOMP_INIT] * 0.5_n, Player[A].Location.Y + Player[A].Location.Height - EffectHeight[EFFID_STOMP_INIT] * 0.5_n));\n        else if(!tempSpring)\n            NewEffect(EFFID_WHACK, newLoc(Player[A].Location.X + Player[A].Location.Width / 2 - 16, Player[A].Location.Y + Player[A].Location.Height - 16));\n        else\n            tempSpring = false;\n\n        PlayerPush(A, 3);\n\n        if(Player[A].YoshiBlue)\n        {\n            Player[A].CanFly2 = true;\n            Player[A].FlyCount = 300;\n        }\n\n        if(spinKill)\n        {\n            Player[A].Jump = 0;\n            Player[A].Location.SpeedY = Physics.PlayerJumpVelocity; // * 0.5\n        }\n    }\n\n\n\n    // Find out which NPC to stand on\n\n    int B = 0;\n\n    // this code is for standing on moving NPCs.\n    if(floorNpc2 != 0)\n    {\n        if(NPC[floorNpc1].Location.Y == NPC[floorNpc2].Location.Y)\n        {\n            num_t C = num_t::abs(NPC[floorNpc1].Location.minus_center_x(Player[A].Location));\n            num_t D = num_t::abs(NPC[floorNpc2].Location.minus_center_x(Player[A].Location));\n\n            if(C < D)\n                B = floorNpc1;\n            else\n                B = floorNpc2;\n        }\n        else if(NPC[floorNpc1].Location.Y < NPC[floorNpc2].Location.Y)\n            B = floorNpc1;\n        else\n            B = floorNpc2;\n    }\n    else if(floorNpc1 != 0)\n        B = floorNpc1;\n\n    if(NPC[floorNpc1].Type >= NPCID_YEL_PLATFORM && NPC[floorNpc1].Type <= NPCID_RED_PLATFORM)\n        B = floorNpc1;\n    else if(NPC[floorNpc2].Type >= NPCID_YEL_PLATFORM && NPC[floorNpc2].Type <= NPCID_RED_PLATFORM)\n        B = floorNpc2;\n\n    if(NPC[B].Effect == NPCEFF_DROP_ITEM)\n        B = 0;\n\n    if(NPC[B].Projectile != 0 && NPCIsVeggie(NPC[B]))\n        B = 0;\n\n    // B is the number of the NPC that the player is standing on\n    // .StandingOnNPC is the number of the NPC that the player was standing on last cycle\n    // if B = 0 and .standingonnpc > 0 then the player was standing on something and is no longer standing on something\n\n\n    if(B > 0 && Player[A].SpinJump)\n    {\n        if(NPC[B].Type == NPCID_ICE_CUBE)\n        {\n            Player[A].Location.SpeedY = Physics.PlayerJumpVelocity;\n            NPC[B].Multiplier += Player[A].Multiplier;\n            NPCHit(B, 3, B);\n            Player[A].Jump = 7;\n\n            if(Player[A].Character == 2)\n                Player[A].Jump += 3;\n\n            if(Player[A].Controls.Down)\n            {\n                Player[A].Jump = 0;\n                Player[A].Location.SpeedY = Physics.PlayerJumpVelocity / 2;\n            }\n\n            B = 0;\n        }\n    }\n\n    if(Player[A].HoldingNPC == B) // cant hold an npc that you are standing on\n        B = 0;\n\n    // don't stand on NPCs during maze zone\n    if(Player[A].CurMazeZone)\n        B = 0;\n\n    // confusing logic, but safe, because StandingOnNPC gets set in ClownCar()\n    if(B == 0 && Player[A].StandingOnVehiclePlr > 0 && Player[A].Mount == 0)\n        Player[A].Location.SpeedX += (NPC[Player[A].StandingOnNPC].Location.SpeedX + (num_t)NPC[Player[A].StandingOnNPC].BeltSpeed);\n    else if(B > 0 && Player[A].StandingOnNPC == 0 && NPC[B].playerTemp && Player[A].Location.SpeedY >= 0)\n        Player[A].Location.SpeedX += -(NPC[B].Location.SpeedX + (num_t)NPC[B].BeltSpeed);\n\n    if(movingBlock) // this is for when the player is standing on a moving block\n    {\n        if(B > 0)\n        {\n            if(NPC[B].Type == NPCID_ITEM_BURIED)\n            {\n                // movingBlock = false;\n            }\n            else\n                B = -A;\n        }\n        else\n            B = -A;\n    }\n\n    if(B != 0)\n    {\n        if(Player[A].StandingOnNPC == 0 && (Player[A].GroundPound || Player[A].YoshiYellow))\n        {\n            numBlock++;\n            Block[numBlock].Location.Y = NPC[B].Location.Y;\n            // seems weird but I'll sync it since we don't know what could happen inside YoshiPound\n            syncLayersTrees_Block(numBlock);\n\n            YoshiPound(A, Player[A].Mount, Player[A].GroundPound);\n            Player[A].GroundPound = false;\n\n            Block[numBlock].Location.Y = 0;\n            numBlock--;\n            syncLayersTrees_Block(numBlock + 1);\n        }\n\n        if(NPC[B].playerTemp == 0)\n            Player[A].StandingOnVehiclePlr = 0;\n\n        if(Player[A].Location.SpeedY >= 0)\n            Player[A].StandingOnNPC = B;\n\n        Player[A].Location.Y = NPC[B].Location.Y - Player[A].Location.Height;\n\n        // NPC has been stood on (condition over NPC types)\n        if(NPC[B].Type == NPCID_FALL_BLOCK_RED || NPC[B].Type == NPCID_FALL_BLOCK_BROWN)\n            NPC[B].Special2 = 1;\n        else if(NPC[B].Type == NPCID_CHECKER_PLATFORM)\n            NPC[B].Special = 1;\n        else if(NPC[B].Type == NPCID_PLATFORM_S3)\n        {\n            if(Player[A].Location.SpeedY > 0)\n                NPC[B].Direction = 1;\n        }\n        else if(NPC[B].Type == NPCID_RAFT)\n        {\n            if(NPC[B].Special == 0)\n            {\n                NPC[B].Special = 1;\n                SkullRide(B);\n            }\n        }\n        else if(NPC[B].Type == NPCID_CONVEYOR)\n            Player[A].Location.SpeedY = 0;\n        else if(NPC[B].Type == NPCID_VEHICLE)\n        {\n            if(Player[A].Controls.Down && Player[A].Mount == 0 &&\n               !NPC[B].playerTemp && Player[A].DuckRelease &&\n               (Player[A].HoldingNPC == 0 || Player[A].Character == 5))\n            {\n                UnDuck(Player[A]);\n\n                if(g_config.fix_char5_vehicle_climb && Player[A].Fairy) // Avoid the mortal glitch\n                {\n                    Player[A].Fairy = false;\n                    PlaySoundSpatial(SFX_HeroFairy, Player[A].Location);\n                    NewEffect(EFFID_SMOKE_S5, Player[A].Location);\n                }\n\n                if(g_config.fix_vehicle_altjump_bug)\n                    Player[A].SpinJump = false;\n\n                Player[A].Location = NPC[B].Location;\n                Player[A].Mount = 2;\n                NPC[B].Killed = 9;\n                NPCQueues::Killed.push_back(B);\n                Player[A].HoldingNPC = 0;\n                Player[A].StandingOnNPC = 0;\n                PlaySoundSpatial(SFX_Stomp, Player[A].Location);\n                for(int C = 1; C <= numPlayers; ++C)\n                {\n                    if(Player[C].StandingOnNPC == B)\n                        Player[C].StandingOnVehiclePlr = A;\n                }\n\n                B = 0;\n            }\n        }\n\n        if(Player[A].Mount == 2 && B != 0)\n        {\n            Player[A].StandingOnNPC = 0;\n            if(Player[A].Location.SpeedY > 4 + NPC[B].Location.SpeedY)\n                PlaySoundSpatial(SFX_Stone, Player[A].Location);\n            Player[A].Location.SpeedY = NPC[B].Location.SpeedY;\n        }\n    }\n    else if(Player[A].Mount == 1 && Player[A].Jump == 0)\n    {\n        if(Player[A].StandingOnNPC != 0)\n        {\n            if(Player[A].Location.X > NPC[Player[A].StandingOnNPC].Location.X + NPC[Player[A].StandingOnNPC].Location.Width || Player[A].Location.X + Player[A].Location.Width < NPC[Player[A].StandingOnNPC].Location.X)\n            {\n                Player[A].StandingOnNPC = 0;\n                Player[A].StandingOnVehiclePlr = 0;\n\n                if(Player[A].Location.SpeedY > 4.1_n)\n                {\n                    Player[A].Location.Y += -Player[A].Location.SpeedY;\n                    Player[A].Location.SpeedY = NPC[0 /*Player[A].StandingOnNPC*/].Location.SpeedY; // SMBX 1.3 bug\n\n                    if(Player[A].Location.SpeedY > Physics.PlayerTerminalVelocity)\n                        Player[A].Location.SpeedY = Physics.PlayerTerminalVelocity;\n\n                    Player[A].Location.Y += Player[A].Location.SpeedY;\n                }\n            }\n        }\n    }\n    else if(Player[A].Mount == 1 && Player[A].Jump > 0)\n    {\n        // confusing logic, but safe, because StandingOnNPC gets set in ClownCar()\n        // NOTE: we know that B == 0!\n        if(/*B == 0 &&*/ Player[A].StandingOnVehiclePlr > 0)\n            Player[A].Location.SpeedX += (NPC[Player[A].StandingOnNPC].Location.SpeedX + (num_t)NPC[Player[A].StandingOnNPC].BeltSpeed);\n        // else if(B > 0 && Player[A].StandingOnNPC == 0 && NPC[B].playerTemp)\n        //     Player[A].Location.SpeedX += -(NPC[B].Location.SpeedX + NPC[B].BeltSpeed);\n\n        Player[A].StandingOnNPC = 0;\n        Player[A].StandingOnVehiclePlr = 0;\n    }\n    else\n    {\n        // Vanilla bugfix: the player is normally forced to stay on a moving NPC by allowing its downwards speed to reach the terminal velocity (12).\n        // And then this code \"unsets\" that extreme downwards speed. But if the player's SpeedY has been changed since that time, unexpected things can happen:\n        // * If it became negative (upwards), then the player will clip downward through the moving NPC here (by subtracting that upwards value from the Y coordinate). This occurs when Char5 is hurt on Mushroom Heights in Princess Cliche.\n        // * If the player hit ground, then the player's speed should now be zero. But this will reset it to being downward, possibly allowing downward clipping. This occurs with NPCID_CHECKER_PLATFORM in Frozen Valley in The Fallen Spirits.\n        // * This fix shouldn't apply to moving blocks (which have negative NPC indexes) because the player's speed is sometimes set to 0 or 1 while on a moving block, and in those cases this code helps the player keep moving with the block. (Check with Autumn Area in Marathon: ATWAB.)\n        // * Since v1.3.7.2: this fix shouldn't apply when the player would be pushed upwards by staying with the NPC (instead of downwards). This replaces the hardcoded check from v1.3.7.1 where the fix required the player's speed to be less than 8 (NPC terminal velocity). (Fixes running on series of NPCID_FALL_BLOCK_RED with gaps.)\n        if(Player[A].StandingOnNPC != 0 && !(Player[A].StandingOnNPC > 0 && g_config.fix_player_downward_clip && Player[A].Location.SpeedY < NPC[Player[A].StandingOnNPC].Location.SpeedY))\n        {\n            if(Player[A].StandingOnNPC < 0)\n                Player[A].Location.SpeedX += NPC[Player[A].StandingOnNPC].Location.SpeedX;\n\n            Player[A].Location.Y += -Player[A].Location.SpeedY;\n            Player[A].Location.SpeedY = NPC[Player[A].StandingOnNPC].Location.SpeedY;\n\n            if(FreezeNPCs)\n                Player[A].Location.SpeedY = 0;\n            if(Player[A].Location.SpeedY > Physics.PlayerTerminalVelocity)\n                Player[A].Location.SpeedY = Physics.PlayerTerminalVelocity;\n\n            Player[A].Location.Y += Player[A].Location.SpeedY;\n        }\n\n        Player[A].StandingOnNPC = 0;\n        Player[A].StandingOnVehiclePlr = 0;\n    }\n\n    if(Player[A].StandingOnNPC > 0 && Player[A].Mount == 0) // driving stuff\n    {\n        if(NPC[Player[A].StandingOnNPC].Type == NPCID_COCKPIT)\n        {\n            Player[A].Driving = true;\n            Player[A].Location.X = NPC[Player[A].StandingOnNPC].Location.X + (NPC[Player[A].StandingOnNPC].Location.Width - Player[A].Location.Width) / 2;\n            Player[A].Direction = NPC[Player[A].StandingOnNPC].DefaultDirection;\n        }\n    }\n}\n"
  },
  {
    "path": "src/player/player_pinched_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"config.h\"\n\nvoid PlayerPinchedTimerUpdate(int A)\n{\n    if(Player[A].Pinched.Bottom1 > 0)\n        Player[A].Pinched.Bottom1 -= 1;\n\n    if(Player[A].Pinched.Left2 > 0)\n        Player[A].Pinched.Left2 -= 1;\n\n    if(Player[A].Pinched.Top3 > 0)\n        Player[A].Pinched.Top3 -= 1;\n\n    if(Player[A].Pinched.Right4 > 0)\n        Player[A].Pinched.Right4 -= 1;\n\n    if(Player[A].Pinched.Moving > 0)\n    {\n        Player[A].Pinched.Moving -= 1;\n\n        if(Player[A].Pinched.Moving == 0)\n        {\n            Player[A].Pinched.MovingLR = false;\n            Player[A].Pinched.MovingUD = false;\n        }\n    }\n\n    if(Player[A].Effect == PLREFF_NORMAL && Player[A].Pinched.Strict > 0)\n        Player[A].Pinched.Strict -= 1;\n}\n\nvoid PlayerPinchedDeathCheck(int A)\n{\n    const auto& pi = Player[A].Pinched;\n\n    bool vcrush = pi.Bottom1 && pi.Top3;\n    bool hcrush = pi.Left2 && pi.Right4;\n\n    // something strange happened to rolling player and they would have been killed -- force them into normal state instead\n    if(pi.MovingLR && hcrush && Player[A].Rolling)\n    {\n        Player[A].Location.SpeedX = 0;\n        Player[A].Immune = 5;\n        hcrush = false;\n    }\n\n    // When the player is pushed through the floor or stops ducking while being crushed, they get left+right hits but no bottom hit\n    bool vcrush_plus = vcrush || (hcrush && (pi.Bottom1 || pi.Top3));\n\n    bool old_condition = pi.Moving && (vcrush || hcrush);\n\n    bool new_condition = (pi.MovingUD && vcrush_plus) || (pi.MovingLR && hcrush);\n\n    bool pinch_death = (g_config.fix_player_crush_death && !pi.Strict) ? new_condition : old_condition;\n\n    if(pinch_death && Player[A].Mount != 2)\n    {\n        if(Player[A].Mount != 2)\n            Player[A].Mount = 0;\n\n        Player[A].State = 1;\n        Player[A].Immune = 0;\n        Player[A].Immune2 = false;\n\n        // Pinch death should occur (but might get cancelled for some reason); set a timer of 15 frames to use stricter old condition\n        // Why is that needed here? The details of hitspot detection mean that if a player is pushed through the floor, they get left+right hits, but no bottom hit\n        Player[A].Pinched.Strict = 15;\n        PlayerHurt(A);\n    }\n}\n"
  },
  {
    "path": "src/player/player_screen_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"graphics.h\"\n#include \"screen.h\"\n#include \"blocks.h\"\n#include \"collision.h\"\n#include \"config.h\"\n#include \"phys_env.h\"\n\n#include \"main/trees.h\"\n#include \"main/game_globals.h\"\n\nvoid PlayerSharedScreenLogic(int A)\n{\n    Screen_t& screen = ScreenByPlayer(A);\n\n    // if(!LevelWrap[Player[A].Section] && LevelMacro == LEVELMACRO_OFF)\n    // This section is fully new logic\n    if(screen.Type == ScreenTypes::SharedScreen)\n    {\n        Player_t& p = Player[A];\n        const SpeedlessLocation_t& section = level[p.Section];\n\n        const vScreen_t& vscreen = vScreenByPlayer(A);\n\n        // section for shared screen push\n        bool check_left = true;\n        bool check_right = true;\n\n        bool vscreen_at_section_bound_left = -vscreen.X <= section.X + 8;\n        bool vscreen_at_section_bound_right = -vscreen.X + vscreen.Width >= section.Width - 8;\n\n        // normally, don't use the shared screen push at section boundaries\n        if(vscreen_at_section_bound_left)\n            check_left = false;\n\n        if(vscreen_at_section_bound_right)\n            check_right = false;\n\n        // do use shared screen push if there's a different player at the other side of the screen\n        for(int o_p_i = 0; o_p_i < screen.player_count; o_p_i++)\n        {\n            const Player_t& o_p = Player[screen.players[o_p_i]];\n\n            if(o_p.Dead || o_p.TimeToLive || o_p.Effect == PLREFF_COOP_WINGS)\n                continue;\n\n            if(o_p.Location.X <= -vscreen.X + 8 && !vscreen_at_section_bound_left)\n                check_right = true;\n            else if(o_p.Location.X + o_p.Location.Width >= -vscreen.X + vscreen.Width - 8 && !vscreen_at_section_bound_right)\n                check_left = true;\n        }\n\n        if(p.Location.X <= -vscreen.X + 8 && check_left)\n        {\n            if(p.Location.X <= -vscreen.X)\n            {\n                p.Location.X = -vscreen.X;\n\n                // give wings to a player that would be killed by pinching\n                if(p.Pinched.Right4 && p.Pinched.Moving && !p.Pinched.Left2)\n                    p.Effect = PLREFF_COOP_WINGS;\n                else\n                    p.Pinched.Left2 = 2;\n\n                if(p.Location.SpeedX < 0)\n                    p.Location.SpeedX = 0;\n\n                if(p.CurMazeZone && (p.MazeZoneStatus % 4) == MAZE_DIR_LEFT)\n                    p.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                else if(p.CurMazeZone && (p.MazeZoneStatus % 4) != MAZE_DIR_RIGHT)\n                {\n                    p.CurMazeZone = 0;\n                    p.Effect = PLREFF_COOP_WINGS;\n                }\n            }\n\n            if(p.Location.SpeedX >= 0 && p.Location.SpeedX < 1)\n                p.Location.SpeedX = 1;\n\n            // give wings to a player that gets stuck to the left\n            if(NoTurnBack[p.Section] && p.Pinched.Right4)\n                p.Effect = PLREFF_COOP_WINGS;\n        }\n        else if(p.Location.X + p.Location.Width >= -vscreen.X + vscreen.Width - 8 && check_right)\n        {\n            if(p.Location.X + p.Location.Width >= -vscreen.X + vscreen.Width)\n            {\n                p.Location.X = -vscreen.X + vscreen.Width - p.Location.Width;\n\n                // give wings to a player that would be killed by pinching\n                if(p.Pinched.Left2 && p.Pinched.Moving && !p.Pinched.Right4)\n                    p.Effect = PLREFF_COOP_WINGS;\n                else\n                    p.Pinched.Right4 = 2;\n\n                if(p.Location.SpeedX > 0)\n                    p.Location.SpeedX = 0;\n\n                if(p.CurMazeZone && (p.MazeZoneStatus % 4) == MAZE_DIR_RIGHT)\n                    p.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                else if(p.CurMazeZone && (p.MazeZoneStatus % 4) != MAZE_DIR_LEFT)\n                {\n                    p.CurMazeZone = 0;\n                    p.Effect = PLREFF_COOP_WINGS;\n                }\n            }\n\n            if(p.Location.SpeedX > -1 && p.Location.SpeedX <= 0)\n                p.Location.SpeedX = -1;\n        }\n\n\n        // give wings to a player that falls offscreen but not off-section\n        if(p.Location.Y > -vscreen.Y + vscreen.Height + 64 && p.Location.Y <= level[p.Section].Height)\n            p.Effect = PLREFF_COOP_WINGS;\n\n        if(p.Effect == PLREFF_COOP_WINGS)\n        {\n            SharedScreenAvoidJump_Pre(screen);\n            p.Dead = true;\n            p.Effect2 = 0;\n            SizeCheck(p);\n            SharedScreenAvoidJump_Post(screen, 0);\n        }\n    }\n}\n\nvoid PlayerEffectWings(int A)\n{\n    Screen_t& screen = ScreenByPlayer(A);\n    Player_t& p = Player[A];\n\n    // check if the player should be moving towards another player or towards the screen\n    int target_plr = CheckNearestLiving(A);\n    if(target_plr)\n        p.Section = Player[target_plr].Section;\n\n    int vscreen_i;\n    if(target_plr && screen.Type != ScreenTypes::SharedScreen)\n        vscreen_i = vScreenIdxByPlayer(target_plr);\n    else\n        vscreen_i = vScreenIdxByPlayer(A);\n\n    const vScreen_t& vscreen = vScreen[vscreen_i];\n\n    p.Fairy = false;\n\n    bool button_pressed = p.Controls.Jump && p.CanJump;\n    if(p.Controls.Jump)\n        p.CanJump = false;\n\n    int WingsFrame = 0;\n\n    if(!button_pressed)\n        target_plr = 0;\n\n    bool offscreen = !vScreenCollision(vscreen_i, p.Location);\n\n    // force to be close to the screen\n    if(offscreen)\n    {\n        if(p.Location.X + p.Location.Width < -vscreen.X - 100)\n            p.Location.X = -vscreen.X - 100 - p.Location.Width;\n        if(p.Location.X > -vscreen.X + vscreen.Width + 100)\n            p.Location.X = -vscreen.X + vscreen.Width + 100;\n        if(p.Location.Y + p.Location.Height < -vscreen.Y - 100)\n            p.Location.Y = -vscreen.Y - 100 - p.Location.Height;\n        if(p.Location.Y > -vscreen.Y + vscreen.Height + 100)\n            p.Location.Y = -vscreen.Y + vscreen.Height + 100;\n    }\n\n    // move towards screen center or living player\n    if(target_plr || offscreen)\n    {\n        num_t target_X = (target_plr) ? (Player[target_plr].Location.X + Player[target_plr].Location.Width / 2) : -vscreen.X + vscreen.Width / 2;\n        num_t target_Y = (target_plr) ? Player[target_plr].Location.Y - 8 : -vscreen.Y + vscreen.Height / 2;\n\n        int randomness = (target_plr) ? 8 : vscreen.Width / 10;\n        target_X += iRand(randomness * 2) - randomness;\n        target_Y += iRand(randomness * 2) - randomness;\n\n        num_t center_X = p.Location.X + p.Location.Width / 2;\n        num_t center_Y = p.Location.Y + p.Location.Height / 2;\n\n        num_t target_SpeedX = (target_X - center_X);\n        num_t target_SpeedY = (target_Y - center_Y);\n\n        num_t target_speed = num_t::dist(target_SpeedX, target_SpeedY);\n\n        if(target_speed != 0)\n        {\n            target_SpeedX = (target_SpeedX * 8).divided_by(target_speed);\n            target_SpeedY = (target_SpeedY * 8).divided_by(target_speed);\n        }\n\n        p.Location.SpeedX = (p.Location.SpeedX + target_SpeedX) / 2;\n        p.Location.SpeedY = (p.Location.SpeedY + target_SpeedY) / 2;\n\n        if(SoundPause[SFX_Swim] == 0)\n        {\n            PlaySoundSpatial(SFX_Swim, p.Location);\n            SoundPause[SFX_Swim] = 15;\n        }\n    }\n    // deceleration\n    else\n    {\n        num_t sq_speed = num_t::dist2(p.Location.SpeedX, p.Location.SpeedY);\n        qdec_t decelerate_rate = (sq_speed > 4) ? 0.95_r : (sq_speed > 1) ? 0.99_r : 0.999_r;\n\n        if(sq_speed <= 4)\n        {\n            WingsFrame = 1;\n\n            if(!p.Controls.Jump)\n                p.CanJump = true;\n        }\n\n        p.Location.SpeedX *= decelerate_rate;\n        p.Location.SpeedY *= decelerate_rate;\n    }\n\n    // update frame\n    p.SpinJump = false;\n    p.WetFrame = false;\n    if(p.Location.SpeedY == 0)\n        p.Location.SpeedY = 0.00001_n;\n    PlayerFrame(p);\n    if(p.Location.SpeedX >= 0)\n    {\n        p.Direction = 1;\n        p.YoshiWingsFrame = WingsFrame + 2;\n    }\n    else\n    {\n        p.Direction = -1;\n        p.YoshiWingsFrame = WingsFrame;\n    }\n\n    // apply movement\n    p.Location.X += p.Location.SpeedX;\n    p.Location.Y += p.Location.SpeedY;\n\n    // tag other players\n    bool found_player = false;\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        if(Player[B].Dead || Player[B].TimeToLive != 0 || Player[B].Effect != PLREFF_NORMAL)\n            continue;\n\n        if(!CheckCollision(Player[A].Location, Player[B].Location))\n            continue;\n\n        found_player = true;\n        break;\n    }\n\n    // just collided with player\n    if(found_player && !p.Effect2)\n    {\n        // if we're inside a block, then we can't respawn yet\n        bool hit_block = false;\n        for(BlockRef_t b_ref : treeBlockQuery(Player[A].Location, SORTMODE_NONE))\n        {\n            const Block_t& b = b_ref;\n            int B = (int)b_ref;\n\n            if(b.Hidden || b.Invis || BlockIsSizable[b.Type] || BlockOnlyHitspot1[b.Type] || BlockNoClipping[b.Type])\n                continue;\n\n            if(BlockCheckPlayerFilter(B, A))\n                continue;\n\n            if(!CheckCollision(Player[A].Location, b.Location))\n                continue;\n\n            if(BlockSlope[b.Type] || BlockSlope2[b.Type])\n                continue;\n\n            hit_block = true;\n            break;\n        }\n\n        if(!hit_block)\n        {\n            p.Effect = PLREFF_NORMAL;\n            PlayerCollide(A);\n            PlaySoundSpatial(SFX_Transform, p.Location);\n        }\n    }\n\n    p.Effect2 = found_player;\n\n    if(p.Effect != PLREFF_COOP_WINGS)\n    {\n        SharedScreenAvoidJump_Pre(screen);\n\n        p.Dead = false;\n        p.CanJump = false;\n        p.Effect2 = 0;\n        p.Immune = 50;\n        p.Immune2 = false;\n        CheckSection(A);\n        PlayerFrame(p);\n\n        SharedScreenAvoidJump_Post(screen, 0);\n    }\n}\n\nvoid PlayerLevelWrapLogic(int A)\n{\n    const Screen_t& screen = ScreenByPlayer(A);\n    vScreen_t& vscreen = vScreenByPlayer(A);\n    Location_t& pLoc = Player[A].Location;\n    const SpeedlessLocation_t& section = level[Player[A].Section];\n\n    // track whether screen wrapped in each of the four ways\n    bool did_wrap_lr = false;\n    bool did_wrap_rl = false;\n    bool did_wrap_tb = false;\n    bool did_wrap_bt = false;\n\n    // horizontally\n    if(LevelWrap[Player[A].Section])\n    {\n        if(pLoc.X + pLoc.Width < section.X)\n        {\n            pLoc.X = section.Width - 1;\n\n            if(vscreen.Width < section.Width - section.X)\n                did_wrap_lr = true;\n        }\n        else if(pLoc.X > section.Width)\n        {\n            pLoc.X = section.X - pLoc.Width + 1;\n\n            if(vscreen.Width < section.Width - section.X)\n                did_wrap_rl = true;\n        }\n    }\n\n    // vertically\n    if(LevelVWrap[Player[A].Section])\n    {\n        if(pLoc.Y + pLoc.Height < section.Y)\n        {\n            pLoc.Y = section.Height - 1;\n\n            if(vscreen.Height < section.Height - section.Y)\n                did_wrap_tb = true;\n        }\n        else if(pLoc.Y > section.Height)\n        {\n            pLoc.Y = section.Y - pLoc.Height + 1;\n\n            if(vscreen.Height < section.Height - section.Y)\n                did_wrap_bt = true;\n        }\n    }\n\n    // shared screen: teleport other players to other side of section\n    if(screen.Type == ScreenTypes::SharedScreen && (did_wrap_lr || did_wrap_rl || did_wrap_tb || did_wrap_bt))\n    {\n        num_t target_Y = pLoc.Y + ((Player[A].Mount != 2) ? pLoc.Height : 0);\n\n        for(int i = 0; i < screen.player_count; i++)\n        {\n            int o_A = screen.players[i];\n\n            if(o_A == A)\n                continue;\n\n            Player_t& o_p = Player[o_A];\n\n            if(o_p.Dead)\n                continue;\n\n            // center on player that wrapped\n            o_p.Location.X = pLoc.X + (pLoc.Width - o_p.Location.Width) / 2;\n            o_p.Location.Y = target_Y - ((o_p.Mount != 2) ? o_p.Location.Height : 0);\n\n            // make sure fully in section\n            if(did_wrap_lr)\n            {\n                o_p.Location.X = section.Width - 1;\n                if(o_p.Location.SpeedX > 0)\n                    o_p.Location.SpeedX = 0;\n            }\n\n            if(did_wrap_rl)\n            {\n                o_p.Location.X = section.X - o_p.Location.Width + 1;\n                if(o_p.Location.SpeedX < 0)\n                    o_p.Location.SpeedX = 0;\n            }\n\n            if(did_wrap_tb)\n                o_p.Location.Y = section.Height - 1;\n\n            if(did_wrap_bt)\n                o_p.Location.Y = section.Y - o_p.Location.Height + 1;\n\n            // following effects only needed for living players\n            if(o_p.TimeToLive != 0)\n                continue;\n\n            // remove from any pet's mouth that doesn't belong to this screen\n            bool onscreen_pet = InOnscreenPet(o_A, screen);\n\n            // disable collisions and remove from any offscreen pets\n            if(!onscreen_pet)\n            {\n                RemoveFromPet(o_A);\n                o_p.Effect = PLREFF_RESPAWN;\n                o_p.RespawnY = o_p.Location.Y;\n            }\n        }\n\n        GetvScreenAuto(vscreen);\n    }\n}\n\nvoid PlayerOffscreenExitCheck(int A)\n{\n    bool offScreenExit = false;\n    num_t nearby_left = (Player[A].Location.X + Player[A].Location.Width) - level[Player[A].Section].X;\n    num_t nearby_right = level[Player[A].Section].Width - Player[A].Location.X;\n    if(nearby_left < 0)\n    {\n        offScreenExit = true;\n        for(int B = 1; B <= numPlayers; B++)\n            Player[B].TailCount = 0;\n    }\n    else if(nearby_right < 0)\n    {\n        offScreenExit = true;\n    }\n\n    if(offScreenExit)\n    {\n        // Always quit to the world map by off-screen exit\n        if(!NoMap && !FileRecentSubHubLevel.empty())\n        {\n            FileRecentSubHubLevel.clear();\n            ReturnWarp = 0;\n            ReturnWarpSaved = 0;\n        }\n\n        LevelBeatCode = BEATCODE_OFFSCREEN;\n        EndLevel = true;\n        LevelMacro = LEVELMACRO_OFF;\n        LevelMacroCounter = 0;\n\n        if(!GoToLevelNoGameThing)\n        {\n            if(g_config.EnableInterLevelFade)\n                g_levelScreenFader.setupFader(4, 0, 65, ScreenFader::S_FADE);\n            else\n                g_levelScreenFader.setupFader(65, 0, 65, ScreenFader::S_FADE);\n\n            levelWaitForFade(16);\n            g_ShortDelay = true;\n        }\n    }\n}\n\nvoid PlayerLevelEdgeCheck(int A, bool check_X)\n{\n    if(check_X)\n    {\n        // Check edge of levels\n        if(Player[A].Location.X < level[Player[A].Section].X)\n        {\n            Player[A].Location.X = level[Player[A].Section].X;\n\n            if(Player[A].Location.SpeedX < 0)\n                Player[A].Location.SpeedX = 0;\n\n            Player[A].Pinched.Left2 = 2;\n\n            if(AutoX[Player[A].Section] != 0)\n            {\n                Player[A].Pinched.Moving = 2;\n                Player[A].Pinched.MovingLR = true;\n            }\n        }\n        else if(Player[A].Location.X + Player[A].Location.Width > level[Player[A].Section].Width)\n        {\n            Player[A].Location.X = level[Player[A].Section].Width - Player[A].Location.Width;\n\n            if(Player[A].Location.SpeedX > 0)\n                Player[A].Location.SpeedX = 0;\n\n            Player[A].Pinched.Right4 = 2;\n\n            if(AutoX[Player[A].Section] != 0)\n            {\n                Player[A].Pinched.Moving = 2;\n                Player[A].Pinched.MovingLR = true;\n            }\n        }\n    }\n\n    if(Player[A].Location.Y < level[Player[A].Section].Y - Player[A].Location.Height - 32 && Player[A].StandingOnVehiclePlr == 0)\n    {\n        Player[A].Location.Y = level[Player[A].Section].Y - Player[A].Location.Height - 32;\n\n        if(AutoY[Player[A].Section] != 0)\n        {\n            Player[A].Pinched.Moving = 3;\n            Player[A].Pinched.MovingUD = true;\n        }\n    }\n}\n\nvoid PlayerLevelBoundsLogic(int A)\n{\n    // When it's true - don't check horizontal section's bounds\n    bool hBoundsHandled = false;\n\n    // level wrap\n    if(LevelWrap[Player[A].Section] || LevelVWrap[Player[A].Section])\n        PlayerLevelWrapLogic(A);\n\n    if(LevelWrap[Player[A].Section])\n        hBoundsHandled = true;\n\n    // Walk offscreen exit\n    if(!hBoundsHandled && OffScreenExit[Player[A].Section])\n    {\n        PlayerOffscreenExitCheck(A);\n        hBoundsHandled = true;\n    }\n\n    if(LevelMacro == LEVELMACRO_CARD_ROULETTE_EXIT || LevelMacro == LEVELMACRO_GOAL_TAPE_EXIT || LevelMacro == LEVELMACRO_FLAG_EXIT || GameMenu)\n        hBoundsHandled = true;\n\n    PlayerLevelEdgeCheck(A, !hBoundsHandled);\n}\n"
  },
  {
    "path": "src/player/player_update.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n\n#include \"../globals.h\"\n#include \"../config.h\"\n#include \"../player.h\"\n#include \"player/player_effect.h\"\n#include \"player/player_update_priv.h\"\n#include \"../collision.h\"\n#include \"../sound.h\"\n#include \"../blocks.h\"\n#include \"../npc.h\"\n#include \"../effect.h\"\n#include \"../layers.h\"\n#include \"../editor.h\"\n#include \"../game_main.h\"\n#include \"../main/trees.h\"\n#include \"../frame_timer.h\"\n#include \"../graphics.h\"\n#include \"../controls.h\"\n#include \"../script/msg_preprocessor.h\"\n\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n#include \"eff_id.h\"\n\n#include \"npc/npc_queues.h\"\n#include \"main/game_loop_interrupt.h\"\n\n\nvoid p_PlayerMakeFlySparkle(const Location_t& loc, int Frame)\n{\n    num_t dx = dRand();\n    num_t dy = dRand();\n    NewEffect(EFFID_SPARKLE,\n        newLoc(loc.X - 8 + dx * ((int)loc.Width + 16) - 4,\n            loc.Y - 8 + dy * ((int)loc.Height + 16)),\n        1, ShadowMode);\n    Effect[numEffects].Location.SpeedX = (dRand() / 2) - 0.25_n;\n    Effect[numEffects].Location.SpeedY = (dRand() / 2) - 0.25_n;\n    Effect[numEffects].Frame = Frame;\n}\n\nstatic void UpdateInvincibility()\n{\n    if(InvincibilityTime <= 0 || FreezeNPCs || LevelMacro != LEVELMACRO_OFF || !CheckLiving())\n        return;\n\n    if(InvincibilityTime == Physics.NPCPSwitch && !GameMenu)\n    {\n        StopMusic();\n        StartMusic(-1);\n    }\n\n    InvincibilityTime--;\n\n    if(InvincibilityTime == 195)\n        PlaySound(SFX_CoinSwitchTimeout);\n\n    if(InvincibilityTime == 0)\n    {\n        for(int A = 1; A <= numPlayers; A++)\n        {\n            if(!Player[A].Dead && Player[A].TimeToLive == 0)\n                Player[A].Immune += 120;\n        }\n\n        if(!GameMenu)\n            SwitchEndResumeMusic();\n    }\n}\n\nbool UpdatePlayer()\n{\n    // these variables do not persist over the interrupt/resume routine\n    num_t SlippySpeedX;\n    tempf_t cursed_value_C;\n\n    switch(g_gameLoopInterrupt.site)\n    {\n    case GameLoopInterrupt::UpdatePlayer_MessageNPC:\n        goto resume_MessageNPC;\n    case GameLoopInterrupt::UpdatePlayer_TriggerTalk:\n        goto resume_TriggerTalk;\n    case GameLoopInterrupt::UpdatePlayer_SuperWarp:\n        goto resume_SuperWarp;\n    default:\n        break;\n    }\n\n    // these variables persist over the interrupt/resume routine\n    int A;\n    bool tempSpring;\n    bool tempShell;\n    tempSpring = false; // this one is probably safe to not share between players. it is reset between players unless tempShell is also hit\n    tempShell = false; // this one marks whether a player has collided with a shell in the current frame and modifies the effects created when any player hits the top of NPC\n\n    UpdateInvincibility(); // updates player invincibility status\n\n    StealBonus(); // allows a dead player to come back to life by using a 1-up\n    ClownCar(); // updates players in the clown car\n\n\n    // A is the current player, numPlayers is the last player. this loop updates all the players\n    for(A = 1; A <= numPlayers; A++)\n    {\n        // reset variables\n        Player[A].ShowWarp = 0;\n        Player[A].mountBump = 0;\n\n        // this was shared over players in SMBX 1.3 -- if the line marked \"MOST CURSED LINE\" in player_block_logic.cpp becomes a source of incompatibility, we will need to restore that logic\n        // this will include modifying the GameLoopInterrupt struct to include it\n        // for now, this variable does not persist over the interrupt/resume routine\n        cursed_value_C = 0;\n\n        if(Player[A].GrabTime > 0) // if grabbing something, take control away from the player\n        {\n            Player[A].Slide = false;\n            Player[A].Controls.Run = true;\n            Player[A].Controls.Down = true;\n            Player[A].Controls.AltRun = false;\n            Player[A].Controls.Jump = false;\n            Player[A].Controls.AltJump = false;\n        }\n\n        if(Player[A].Dismount > 0) // count down to being able to hop in a shoe or yoshi\n            Player[A].Dismount -= 1;\n\n        if(Player[A].Mount != 0 || Player[A].Stoned || Player[A].Fairy) // if .holdingnpc is -1 then the player can't grab anything. this stops the player from grabbing things while on a yoshi/shoe\n            Player[A].HoldingNPC = -1;\n        else if(Player[A].HoldingNPC == -1)\n            Player[A].HoldingNPC = 0;\n\n        if(Player[A].Controls.Drop && Player[A].DropRelease && Player[A].Effect != PLREFF_COOP_WINGS)\n        {\n            // this is for the single player coop cheat code\n            if(SingleCoop > 0 && Player[A].Controls.Down)\n                SwapCoop();\n            else\n                DropBonus(A);\n        }\n\n        // for dropping something from the container. this makes the player have to let go of the drop button before dropping something else\n        Player[A].DropRelease = !Player[A].Controls.Drop;\n\n        // Handle the death effecs\n        if(Player[A].TimeToLive > 0)\n            UpdatePlayerTimeToLive(A);\n        else if(Player[A].Effect == PLREFF_COOP_WINGS)\n            PlayerEffectWings(A);\n        else if(Player[A].Dead)\n            UpdatePlayerDead(A);\n        else\n        {\n            if(Player[A].SlideCounter > 0) // for making the slide Effect\n                Player[A].SlideCounter -= 1;\n\n            if(Player[A].Effect == PLREFF_NORMAL)\n            {\n                // NEW: used for compat flag \"fix-player-clip-wall-at-npc\"\n                // this variable does not persist over the interrupt/resume routine\n                int oldStandingOnNpc;\n                oldStandingOnNpc = Player[A].StandingOnNPC;\n\n                // for the pound pet mount logic\n                PlayerPoundLogic(A);\n\n                SizeCheck(Player[A]); // check that the player is the correct size for it's character/state/mount and set it if not\n\n                if(Player[A].Stoned) // stop the player from climbing/spinning/jumping when in tanooki statue form\n                {\n                    Player[A].Jump = 0;\n                    Player[A].Vine = 0;\n                    Player[A].SpinJump = false;\n                    Player[A].Controls.Left = false;\n                    Player[A].Controls.Right = false;\n                    Player[A].Controls.AltJump = false;\n                    Player[A].Controls.Jump = false;\n                    Player[A].CanAltJump = false;\n                    Player[A].CanJump = false;\n                }\n\n                // let the player slide if not on a mount and holding something\n                if(Player[A].GrabTime > 0)\n                    Player[A].Slide = false;\n\n                if(Player[A].Rolling)\n                    Player[A].Slide = true;\n                else if(Player[A].Slope > 0 && Player[A].Controls.Down &&\n                   Player[A].Mount == 0 && Player[A].HoldingNPC == 0 &&\n                   !(Player[A].Character == 3 || Player[A].Character == 4 || Player[A].Character == 5) &&\n                   Player[A].GrabTime == 0)\n                {\n                    // prioritize rolling for Polar power\n                    if(!(Player[A].State == PLR_STATE_POLAR && num_t::abs(Player[A].Location.SpeedX) > 1 && BlockSlope[Block[Player[A].Slope].Type] == Player[A].Direction))\n                    {\n                        if(Player[A].Duck)\n                            UnDuck(Player[A]);\n                        Player[A].Slide = true;\n                    }\n                }\n                else if(Player[A].Location.SpeedX == 0)\n                    Player[A].Slide = false;\n\n                if(Player[A].Mount > 0 || Player[A].HoldingNPC > 0)\n                    Player[A].Slide = false;\n\n                // unduck a player that shouldn't be able to duck\n                if(Player[A].Duck && (Player[A].Character == 1 || Player[A].Character == 2) && Player[A].State == 1 && (Player[A].Mount == 0 || Player[A].Mount == 2))\n                    UnDuck(Player[A]);\n\n                if(GameMenu && !Player[A].SpinJump) // force the player to look right when on the game menu\n                    Player[A].Direction = 1;\n\n                WaterCheck(A); // This sub handles all the water related stuff\n\n                PowerUps(A); // misc power-up code\n\n                if(Player[A].StandingOnNPC > 0)\n                {\n                    if(NPC[Player[A].StandingOnNPC].Type == NPCID_ICE_CUBE && NPC[Player[A].StandingOnNPC].Location.SpeedX == 0)\n                        Player[A].Slippy = true;\n                }\n\n                if((Player[A].State == PLR_STATE_AQUATIC || Player[A].State == PLR_STATE_POLAR) && !Player[A].Mount && !Player[A].HoldingNPC && !Player[A].Vine && Player[A].WetFrame && !Player[A].Quicksand)\n                {\n                    if(Player[A].CurMazeZone)\n                    {\n                        // just keep things as they are\n                    }\n                    else if(!Player[A].AquaticSwim)\n                    {\n                        Player[A].SwimCount = 0;\n                        Player[A].FrameCount = 0;\n\n                        // face up!\n                        if(Player[A].Location.SpeedY < 0 && !Player[A].Immune && Player[A].State == PLR_STATE_POLAR)\n                            Player[A].Frame = 42;\n                        else\n                            Player[A].Frame = 18;\n\n                        Player[A].AquaticSwim = true;\n                    }\n                }\n                else\n                    Player[A].AquaticSwim = false;\n\n                // maze zone movement fully bypasses normal movement routines\n                if(Player[A].CurMazeZone)\n                    PlayerMazeZoneMovement(A);\n                // first stage of flag exit\n                else if(LevelMacro == LEVELMACRO_FLAG_EXIT && LevelMacroWhich < 0)\n                    PlayerFlagSlideMovement(A);\n                // normal player movement\n                else\n                {\n                    // this variable does not persist over the interrupt/resume routine\n                    SlippySpeedX = Player[A].Location.SpeedX;\n\n\n                    // Player's X movement. ---------------------------\n\n                    // Code used to move the player while sliding down a slope\n                    if(Player[A].Slide && !Player[A].Rolling)\n                        PlayerSlideMovementX(A);\n                    // TODO: mount-dependent logic\n                    // if not sliding and in the clown car\n                    else if(Player[A].Mount == 2)\n                        PlayerVehicleDismountCheck(A);\n                    // driving (standing on NPCID_COCKPIT)\n                    else if(Player[A].Driving)\n                        PlayerCockpitMovementX(A);\n                    // if a fairy\n                    else if(Player[A].Fairy)\n                        PlayerFairyMovementX(A);\n                    // TODO: state-dependent logic\n                    // if the player is climbing a vine\n                    else if(Player[A].Vine > 0)\n                        PlayerVineMovement(A);\n                    else if(Player[A].AquaticSwim)\n                        PlayerAquaticSwimMovement(A);\n                    // if none of the above apply then the player controls like normal. remeber this is for the players X movement\n                    else\n                        PlayerMovementX(A, cursed_value_C);\n\n\n                    // stop link when stabbing\n                    if(Player[A].Character == 5)\n                    {\n                        if(Player[A].FireBallCD > 0 && (Player[A].Location.SpeedY == 0 || Player[A].Slope != 0 || Player[A].StandingOnNPC != 0) && Player[A].State != PLR_STATE_SHELL)\n                        {\n                            if(Player[A].Slippy)\n                                Player[A].Location.SpeedX = Player[A].Location.SpeedX * 0.75_rb;\n                            else\n                                Player[A].Location.SpeedX = 0;\n                        }\n                    }\n\n                    // fairy stuff\n                    PlayerFairyTimerUpdate(A);\n\n\n                    // the single pinched variable has been always false since SMBX64\n                    if(Player[A].StandingOnNPC != 0 && /*!NPC[Player[A].StandingOnNPC].Pinched && */ !FreezeNPCs)\n                    {\n                        if(Player[A].StandingOnNPC < 0)\n                            NPC[Player[A].StandingOnNPC].Location = Block[NPC[Player[A].StandingOnNPC].Special].Location;\n\n                        Player[A].Location.SpeedX += NPC[Player[A].StandingOnNPC].Location.SpeedX + (num_t)NPC[Player[A].StandingOnNPC].BeltSpeed;\n                    }\n\n                    if(GameOutro) // force the player to walk a specific speed during the credits\n                    {\n                        if(Player[A].Location.SpeedX < -2)\n                            Player[A].Location.SpeedX = -2;\n                        if(Player[A].Location.SpeedX > 2)\n                            Player[A].Location.SpeedX = 2;\n                    }\n\n\n\n                    // slippy code\n                    if(Player[A].Slippy && (!Player[A].Slide || Player[A].Slope == 0) && (Player[A].State != PLR_STATE_POLAR || Player[A].Mount))\n                    {\n                        if(Player[A].Slope > 0)\n                        {\n                            Player[A].Location.SpeedX = (Player[A].Location.SpeedX + SlippySpeedX * 4) / 5;\n                            if(Player[A].Location.SpeedX > -0.01_n && Player[A].Location.SpeedX < 0.01_n)\n                                Player[A].Location.SpeedX = 0;\n                        }\n                        else\n                        {\n                            Player[A].Location.SpeedX = (Player[A].Location.SpeedX + SlippySpeedX * 3) / 4;\n                            if(Player[A].Location.SpeedX > -0.01_n && Player[A].Location.SpeedX < 0.01_n)\n                                Player[A].Location.SpeedX = 0;\n                        }\n                    }\n\n                    // moved Slippy reset to immediately before the player Block logic\n                    // bool wasSlippy = Player[A].Slippy;\n                    // Player[A].Slippy = false;\n\n                    if(Player[A].Quicksand > 1)\n                    {\n                        Player[A].Slide = false;\n                        if(Player[A].Location.SpeedY >= 0)\n                            Player[A].Location.SpeedX = Player[A].Location.SpeedX / 2;\n                    }\n\n                    // Apply movement -- this is where the actual movement happens\n                    Player[A].Location.X += Player[A].Location.SpeedX;\n\n\n                    // Players Y movement.\n                    if(Block[Player[A].Slope].Location.SpeedY != 0 && Player[A].Slope != 0)\n                        Player[A].Location.Y += Block[Player[A].Slope].Location.SpeedY;\n\n                    if(Player[A].AquaticSwim && Player[A].Wet)\n                    {\n                        // Aquatic swim handles both X and Y movement\n                    }\n                    else if(Player[A].Fairy) // the player is a fairy\n                        PlayerFairyMovementY(A);\n                    // TODO: state-dependent logic\n                    else if(Player[A].Wet > 0 && Player[A].Quicksand == 0) // the player is swimming\n                        PlayerSwimMovementY(A);\n                    else if(Player[A].Mount == 2)\n                    {\n                        // vehicle has own Y movement code in ClownCar()\n                    }\n                    else // the player is not swimming\n                        PlayerMovementY(A);\n\n                    Player[A].Location.Y += Player[A].Location.SpeedY;\n                }\n\n                // princess peach and toad stuff\n                if(Player[A].Character == 3 || Player[A].Character == 4 || Player[A].Character == 5)\n                {\n                    Player[A].HeldBonus = NPCID(0);\n                    // power up limiter\n                    // If (.Character = 3 Or .Character = 4) And .State > 3 And .State <> 7 Then .State = 2\n\n                    if(Player[A].Mount == 3)\n                    {\n                        PlayerHurt(A);\n                        Player[A].Mount = 0;\n                        UpdateYoshiMusic();\n                    }\n\n                    if(Player[A].Slide && !Player[A].Rolling)\n                        Player[A].Slide = false;\n\n                    // If .Stoned = True Then .Stoned = False\n                    if(Player[A].Hearts == 1 && Player[A].State > 1)\n                        Player[A].Hearts = 2;\n\n                    if(Player[A].Hearts > 1 && Player[A].State == 1)\n                        Player[A].Hearts = 1;\n\n                    if(Player[A].Hearts == 0)\n                    {\n                        if(Player[A].State == 1)\n                            Player[A].Hearts = 1;\n\n                        if(Player[A].State >= 2)\n                            Player[A].Hearts = 2;\n                    }\n                }\n\n                // link stuff\n                if(Player[A].Character == 5)\n                    PlayerChar5Logic(A);\n\n                Player[A].FloatRelease = !Player[A].Controls.Jump;\n\n                // Player interactions\n                Player[A].Location.SpeedX += Player[A].Bumped2;\n                Player[A].Location.X += Player[A].Bumped2;\n                Player[A].Bumped2 = 0;\n                if(Player[A].Mount == 0)\n                    Player[A].YoshiYellow = false;\n\n                // perform various level bounds checks (level wrap, offscreen exit, left/right/top boundaries)\n                PlayerLevelBoundsLogic(A);\n\n                // gives the players the sparkles when he is flying\n                if(\n                        (\n                                (!Player[A].YoshiBlue && (Player[A].CanFly || Player[A].CanFly2)) ||\n                                (Player[A].Mount == 3 && Player[A].CanFly2)\n                        ) || Player[A].FlySparks || InvincibilityTime\n                        )\n                {\n                    if(iRand(4) == 0)\n                        p_PlayerMakeFlySparkle(Player[A].Location, 0);\n                }\n\n                Tanooki(A); // tanooki suit code\n\n                // this whole section is dead code, since there are no uses of these definitions of PlrMid and Slope\n#if 0\n                if(Player[A].StandingOnNPC == -A)\n                {\n                    if(Player[A].Slope != 0)\n                    {\n                        B = NPC[Player[A].StandingOnNPC].Special;\n\n                        double PlrMid;\n                        if(BlockSlope[Block[B].Type] == 1)\n                            PlrMid = Player[A].Location.X;\n                        else\n                            PlrMid = Player[A].Location.X + Player[A].Location.Width;\n\n                        double Slope = (PlrMid - Block[B].Location.X) / Block[B].Location.Width;\n\n                        if(BlockSlope[Block[B].Type] < 0)\n                            Slope = 1 - Slope;\n                        if(Slope < 0)\n                            Slope = 0;\n                        if(Slope > 1)\n                            Slope = 1;\n                    }\n                }\n#endif\n\n                // decrement pinched timers\n                PlayerPinchedTimerUpdate(A);\n\n                // these variables persist over the interrupt/resume routine\n                int MessageNPC;\n                bool DontResetGrabTime;\n                MessageNPC = 0;\n                DontResetGrabTime = false; // helps with grabbing things from the top\n\n                // scoping variables shared between block logic and NPC logic\n                {\n                    tempf_t oldSpeedY = (tempf_t)Player[A].Location.SpeedY; // holds the players previous Y speed\n                    bool movingBlock = false; // helps with collisions for moving blocks\n                    int floorBlock = 0; // was previously called tempHit3\n\n                    Player[A].Slippy = false;\n                    Player[A].SlippyWall = false;\n\n                    if(!Player[A].CurMazeZone)\n                    {\n                        // Block collisions.\n                        PlayerBlockLogic(A, floorBlock, movingBlock, DontResetGrabTime, cursed_value_C, oldStandingOnNpc);\n\n                        // Vine collisions.\n                        PlayerVineLogic(A);\n                    }\n\n                    // Check NPC collisions\n                    PlayerNPCLogic(A, tempSpring, tempShell, MessageNPC, movingBlock, floorBlock, oldSpeedY);\n                }\n\n                // reduce player's multiplier\n                if((Player[A].Location.SpeedY == 0 || Player[A].StandingOnNPC != 0 || Player[A].Slope > 0) && !Player[A].Slide && !FreezeNPCs && !InvincibilityTime)\n                    Player[A].Multiplier = 0;\n\n                if(Player[A].Mount == 2)\n                    Player[A].Multiplier = 0;\n\n                // Player-player collisions\n                PlayerCollide(A);\n\n                // Talk to NPC\n                if(MessageNPC > 0)\n                {\n                    MessageText = GetS(NPC[MessageNPC].Text);\n                    preProcessMessage(MessageText, A);\n                    PauseInit(PauseCode::Message, A);\n\n                    // store entire routine state into g_gameLoopInterrupt\n                    g_gameLoopInterrupt.site = GameLoopInterrupt::UpdatePlayer_MessageNPC;\n                    g_gameLoopInterrupt.A = A;\n                    g_gameLoopInterrupt.B = MessageNPC;\n                    g_gameLoopInterrupt.bool1 = DontResetGrabTime;\n                    g_gameLoopInterrupt.bool2 = tempSpring;\n                    g_gameLoopInterrupt.bool3 = tempShell;\n                    return true;\n\nresume_MessageNPC:\n                    // restore only the essentials for now, we may need to pause/resume again\n                    A = g_gameLoopInterrupt.A;\n                    MessageNPC = g_gameLoopInterrupt.B;\n\n                    MessageText.clear();\n\n                    if(NPC[MessageNPC].TriggerTalk != EVENT_NONE)\n                    {\n                        eventindex_t resume_index;\n                        resume_index = ProcEvent_Safe(false, NPC[MessageNPC].TriggerTalk, A);\n                        while(resume_index != EVENT_NONE)\n                        {\n                            g_gameLoopInterrupt.B = resume_index;\n                            g_gameLoopInterrupt.site = GameLoopInterrupt::UpdatePlayer_TriggerTalk;\n                            return true;\n\nresume_TriggerTalk:\n                            A = g_gameLoopInterrupt.A;\n                            resume_index = g_gameLoopInterrupt.B;\n                            resume_index = ProcEvent_Safe(true, resume_index, A);\n                        }\n                    }\n\n                    // finish restoring state from g_gameLoopInterrupt\n                    DontResetGrabTime = g_gameLoopInterrupt.bool1;\n                    tempSpring = g_gameLoopInterrupt.bool2;\n                    tempShell = g_gameLoopInterrupt.bool3;\n                    g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n\n                    MessageNPC = 0;\n                }\n\n                YoshiEatCode(A);\n\n                // pinch code\n                if(!GodMode)\n                    PlayerPinchedDeathCheck(A);\n\n                if(false)\n                {\nresume_SuperWarp:\n                    A = g_gameLoopInterrupt.A;\n\n                    // restore some locals from this procedure\n                    DontResetGrabTime = g_gameLoopInterrupt.bool1;\n                    tempSpring = g_gameLoopInterrupt.bool2;\n                    tempShell = g_gameLoopInterrupt.bool3;\n\n                    // SuperWarp will resume at the correct index based on g_gameLoopInterrupt.B, and then clear g_gameLoopInterrupt.site\n                }\n\n                SuperWarp(A); // this sub checks warps\n\n                if(g_gameLoopInterrupt.site != GameLoopInterrupt::None)\n                {\n                    // save some locals from this procedure\n                    g_gameLoopInterrupt.bool1 = DontResetGrabTime;\n                    g_gameLoopInterrupt.bool2 = tempSpring;\n                    g_gameLoopInterrupt.bool3 = tempShell;\n                    return true;\n                }\n\n                // shell surf\n                if(Player[A].ShellSurf && Player[A].StandingOnNPC != 0)\n                {\n                    Player[A].Location.X = NPC[Player[A].StandingOnNPC].Location.X + (NPC[Player[A].StandingOnNPC].Location.Width - Player[A].Location.Width) / 2;\n                    Player[A].Location.SpeedX = 0; // 1 * .Direction\n\n                    if(NPC[Player[A].StandingOnNPC].Location.SpeedX == 0)\n                        Player[A].ShellSurf = false;\n                }\n\n                // Check edge of screen\n                PlayerSharedScreenLogic(A);\n\n                if(Player[A].Location.Y > level[Player[A].Section].Height + 64)\n                    PlayerDead(A);\n\n                if(!NPC[Player[A].StandingOnNPC]->IsAShell)\n                    Player[A].ShellSurf = false;\n\n                PlayerGrabCode(A, DontResetGrabTime); // Player holding NPC code **GRAB CODE**\n\n                Player[A].RunRelease = !Player[A].Controls.Run && !Player[A].Controls.AltRun;\n                Player[A].JumpRelease = !Player[A].Controls.Jump && !Player[A].Controls.AltJump;\n\n                PlayerFrame(Player[A]); // Update players frames\n                Player[A].StandUp = false; // Fixes a block collision bug\n                Player[A].StandUp2 = false;\n                if(Player[A].ForceHitSpot3)\n                    Player[A].StandUp = true;\n                Player[A].ForceHitSpot3 = false;\n                if(Player[A].ForceHold > 0)\n                    Player[A].ForceHold -= 1;\n            }\n            else // Player special effects\n                PlayerEffects(A);\n        }\n\n        Player[A].DuckRelease = !Player[A].Controls.Down;\n        Player[A].AltRunRelease = !Player[A].Controls.AltRun;\n    }\n\n    CleanupVehicleNPCs();\n\n    return false;\n}\n\nvoid CleanupVehicleNPCs()\n{\n    // int C = 0;\n\n    // kill the player temp NPCs, from last to first\n    NPCQueues::reverse_sort(NPCQueues::PlayerTemp);\n\n    // for(A = numNPCs; A >= 1; A--)\n    int last_NPC = numNPCs + 1;\n    for(int A : NPCQueues::PlayerTemp)\n    {\n        // duplicated entry, no problem\n        if(A == last_NPC)\n            continue;\n\n        if(NPC[A].playerTemp)\n        {\n            for(int B = 1; B <= numPlayers; B++)\n            {\n                if(Player[B].StandingOnNPC == A)\n                    Player[B].StandingOnVehiclePlr = NPC[A].Variant; // newly stores the player who owns the vehicle\n            }\n            NPC[0] = NPC[A]; // was NPC[C] = NPC[A] but C was not mutated\n            KillNPC(A, 9);\n        }\n    }\n\n    NPCQueues::PlayerTemp.clear();\n}\n"
  },
  {
    "path": "src/player/player_update_priv.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef PLAYER_UPDATE_PRIV_H\n#define PLAYER_UPDATE_PRIV_H\n\n#include \"numeric_types.h\"\n\nstruct Location_t;\nstruct Player_t;\n\nvoid p_PlayerMakeFlySparkle(const Location_t& loc, int Frame = 1);\n\n// most of these routines were originally part of UpdatePlayer\nvoid PlayerNPCLogic(int A, bool& tempSpring, bool& tempShell, int& MessageNPC, const bool movingBlock, const int floorBlock, const tempf_t oldSpeedY);\n\nvoid PlayerBlockLogic(int A, int& floorBlock, bool& movingBlock, bool& DontResetGrabTime, tempf_t cursed_value_C, const int oldStandingOnNpc);\n\nbool p_PlayerTouchVine(Player_t& p, num_t vine_top, int VineNPC, int VineBGO);\nvoid PlayerVineLogic(int A);\nbool PlayerFairyOnVine(int A);\nvoid PlayerVineMovement(int A);\n\nvoid PlayerSharedScreenLogic(int A);\nvoid PlayerLevelBoundsLogic(int A);\nvoid PlayerEffectWings(int A);\n\nvoid PlayerFairyTimerUpdate(int A);\nvoid PlayerFairyMovementX(int A);\nvoid PlayerFairyMovementY(int A);\n\nvoid PlayerPinchedTimerUpdate(int A);\nvoid PlayerPinchedDeathCheck(int A);\n\nvoid PlayerMovementX(int A, tempf_t& cursed_value_C);\nvoid PlayerSlideMovementX(int A);\nvoid PlayerCockpitMovementX(int A);\nvoid PlayerMovementY(int A);\nvoid PlayerSwimMovementY(int A);\nvoid PlayerAquaticSwimMovement(int A);\n\nvoid PlayerPoundLogic(int A);\nvoid PlayerShootChar5Beam(int A);\nvoid PlayerThrowBomb(int A); // (from PowerUps)\n\nvoid PlayerChar5Logic(int A);\nvoid PlayerChar5StabLogic(int A); // (from PowerUps)\n\nvoid UpdatePlayerTimeToLive(int A);\nvoid UpdatePlayerDead(int A);\n\nvoid PlayerVehicleDismountCheck(int A);\n\nvoid PlayerEffectWarpPipe(int A);\nvoid PlayerEffectWarpDoor(int A);\nvoid PlayerEffectWarpWait(int A);\n\nvoid PlayerMazeZoneMovement(int A);\nvoid PlayerFlagSlideMovement(int A);\nvoid PlayerThrowItemMaze(const Player_t& p, Location_t& loc, uint8_t& maze_status);\nvoid PlayerThrownNpcMazeCheck(const Player_t& p, NPC_t& npc);\n\n#endif // PLAYER_UPDATE_PRIV_H\n"
  },
  {
    "path": "src/player/player_vehicle_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"collision.h\"\n#include \"config.h\"\n#include \"sound.h\"\n#include \"npc_traits.h\"\n\n#include \"main/trees.h\"\n\nvoid PlayerVehicleDismountCheck(int A)\n{\n    if(!Player[A].Controls.Jump)\n        Player[A].CanJump = true;\n\n    if(!Player[A].Controls.AltJump && g_config.fix_vehicle_altjump_lock)\n        Player[A].CanAltJump = true;\n\n    if(Player[A].Controls.AltJump && Player[A].CanAltJump) // Jump out of the Clown Car\n    {\n        if(g_config.fix_vehicle_altjump_lock)\n            Player[A].CanAltJump = false;\n\n        Player[A].CanJump = false;\n\n        bool dismount_safe = true;\n\n        Location_t tempLocation = Player[A].Location;\n        tempLocation.Height = Physics.PlayerHeight[Player[A].Character][Player[A].State];\n        tempLocation.Y += -Physics.PlayerHeight[Player[A].Character][Player[A].State];\n        tempLocation.Width = Physics.PlayerWidth[Player[A].Character][Player[A].State];\n        tempLocation.X += 64 - tempLocation.Width / 2;\n\n        // fBlock = FirstBlock[(tempLocation.X / 32) - 1];\n        // lBlock = LastBlock[((tempLocation.X + tempLocation.Width) / 32.0) + 1];\n        // blockTileGet(tempLocation, fBlock, lBlock);\n\n        for(int B : treeFLBlockQuery(tempLocation, SORTMODE_NONE))\n        {\n            if(!Block[B].Invis && !BlockIsSizable[Block[B].Type] && !BlockOnlyHitspot1[Block[B].Type] &&\n               !BlockNoClipping[Block[B].Type] && !Block[B].Hidden)\n            {\n                if(CheckCollision(tempLocation, Block[B].Location))\n                {\n                    dismount_safe = false;\n                    PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n                    break;\n                }\n            }\n        }\n\n        if(dismount_safe) for(int B : treeNPCQuery(tempLocation, SORTMODE_NONE))\n        {\n            if(NPC[B]->IsABlock && !NPC[B]->StandsOnPlayer && NPC[B].Active && NPC[B].Type != NPCID_VEHICLE)\n            {\n                if(CheckCollision(tempLocation, NPC[B].Location))\n                {\n                    dismount_safe = false;\n                    PlaySoundSpatial(SFX_BlockHit, Player[A].Location);\n                    break;\n                }\n            }\n        }\n\n        if(dismount_safe)\n            PlayerDismount(A);\n    }\n}\n"
  },
  {
    "path": "src/player/player_vine_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"collision.h\"\n#include \"effect.h\"\n#include \"eff_id.h\"\n#include \"sound.h\"\n#include \"config.h\"\n#include \"npc_traits.h\"\n#include \"layers.h\"\n\n#include \"main/trees.h\"\n\nbool p_PlayerTouchVine(Player_t& p, num_t vine_top, int VineNPC, int VineBGO)\n{\n    if(p.Character == 5)\n    {\n        bool hasNoMonts = (g_config.fix_char5_vehicle_climb && p.Mount <= 0) ||\n                           !g_config.fix_char5_vehicle_climb;\n        if(hasNoMonts && p.Immune == 0 && p.Controls.Up)\n        {\n            p.FairyCD = 0;\n\n            if(!p.Fairy)\n            {\n                p.Fairy = true;\n                SizeCheck(p);\n                PlaySoundSpatial(SFX_HeroFairy, p.Location);\n                p.Immune = 10;\n                p.Effect = PLREFF_WAITING;\n                p.Effect2 = 4;\n                NewEffect(EFFID_SMOKE_S5, p.Location);\n            }\n\n            if(p.FairyTime != -1 && p.FairyTime < 20)\n                p.FairyTime = 20;\n        }\n\n        return true;\n    }\n    else if(!p.Fairy && !p.Stoned && !p.AquaticSwim)\n    {\n        if(p.Mount == 0 && p.HoldingNPC <= 0)\n        {\n            if(p.Vine > 0)\n            {\n                if(p.Duck)\n                    UnDuck(p);\n\n                if(p.Location.Y >= vine_top - 20 && p.Vine < 2)\n                    p.Vine = 2;\n\n                if(p.Location.Y >= vine_top - 18)\n                    p.Vine = 3;\n            }\n            else if((p.Controls.Up ||\n                     (p.Controls.Down &&\n                      !num_t::fEqual_d(p.Location.SpeedY, 0) && // Not .Location.SpeedY = 0\n                      p.StandingOnNPC == 0 && // Not .StandingOnNPC <> 0\n                      p.Slope <= 0) // Not .Slope > 0\n                    ) && p.Jump == 0)\n            {\n                if(p.Duck)\n                    UnDuck(p);\n\n                if(p.Location.Y >= vine_top - 20 && p.Vine < 2)\n                    p.Vine = 2;\n\n                if(p.Location.Y >= vine_top - 18)\n                    p.Vine = 3;\n            }\n\n            if(p.Vine > 0)\n            {\n                p.VineNPC = VineNPC;\n                if(g_config.fix_climb_bgo_speed_adding)\n                    p.VineBGO = VineBGO;\n            }\n\n            if(p.Vine == 3)\n                return true;\n        }\n    }\n\n    return false;\n}\n\nvoid PlayerVineLogic(int A)\n{\n    if(Player[A].Vine > 0)\n        Player[A].Vine -= 1;\n\n    // check vine backgrounds\n    for(int B : treeBackgroundQuery(Player[A].Location, SORTMODE_NONE))\n    {\n        if(B > numBackground)\n            continue;\n\n        if(BackgroundFence[Background[B].Type] && (!g_config.fix_climb_invisible_fences || !Background[B].Hidden))\n        {\n            // if(CheckCollision(Player[A].Location, Background[B].Location))\n            //{\n            SpeedlessLocation_t tempLocation = Background[B].Location;\n            tempLocation.Height -= 16;\n            tempLocation.Width -= 20;\n            tempLocation.X += 10;\n\n            if(CheckCollision(Player[A].Location, tempLocation))\n            {\n                if(p_PlayerTouchVine(Player[A], Background[B].Location.Y, -1, B))\n                    break;\n            } // Collide player and temp location\n            // }// Collide player and BGO\n        } // Is BGO climbable and visible?\n    } // Next A\n}\n\nbool PlayerFairyOnVine(int A)\n{\n    Location_t tempLocation = Player[A].Location;\n    tempLocation.Width += 32;\n    tempLocation.Height += 32;\n    tempLocation.X -= 16;\n    tempLocation.Y -= 16;\n\n    for(int Bi : treeNPCQuery(tempLocation, SORTMODE_NONE))\n    {\n        if(NPC[Bi].Active && !NPC[Bi].Hidden && NPC[Bi]->IsAVine)\n        {\n            if(CheckCollision(tempLocation, NPC[Bi].Location))\n                return true;\n        }\n    }\n\n    for(int B : treeBackgroundQuery(tempLocation, SORTMODE_NONE))\n    {\n        if(B > numBackground)\n            continue;\n\n        if(BackgroundFence[Background[B].Type] && !Background[B].Hidden)\n        {\n            if(CheckCollision(tempLocation, Background[B].Location))\n                return true;\n        }\n    }\n\n    return false;\n}\n\nvoid PlayerVineMovement(int A)\n{\n    if(Player[A].StandingOnNPC > 0 && !Player[A].Controls.Up)\n        Player[A].Vine = 0;\n    Player[A].CanFly = false;\n    Player[A].CanFly2 = false;\n    Player[A].RunCount = 0;\n    Player[A].SpinJump = false;\n\n    if(Player[A].Controls.Left)\n        Player[A].Location.SpeedX = -1.5_n;\n    else if(Player[A].Controls.Right)\n        Player[A].Location.SpeedX = 1.5_n;\n    else\n        Player[A].Location.SpeedX = 0;\n\n    if(Player[A].Controls.Up && Player[A].Vine > 2)\n        Player[A].Location.SpeedY = -2;\n    else if(Player[A].Controls.Down)\n        Player[A].Location.SpeedY = 3;\n    else\n        Player[A].Location.SpeedY = 0;\n\n    if(g_config.fix_climb_bgo_speed_adding && Player[A].VineBGO > 0)\n    {\n        const Layer_t& layer = Layer[Background[Player[A].VineBGO].Layer];\n        if(g_config.enable_climb_bgo_layer_move)\n        {\n            Player[A].Location.SpeedX += (num_t)layer.ApplySpeedX;\n            Player[A].Location.SpeedY += (num_t)layer.ApplySpeedY;\n        }\n    }\n    else\n    {\n        Player[A].Location.SpeedX += NPC[Player[A].VineNPC].Location.SpeedX;\n        Player[A].Location.SpeedY += NPC[Player[A].VineNPC].Location.SpeedY;\n    }\n}\n"
  },
  {
    "path": "src/player/player_warp_logic.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <fmt_format_ne.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"globals.h\"\n\n#include \"player.h\"\n#include \"graphics.h\"\n#include \"npc.h\"\n#include \"npc_id.h\"\n#include \"npc_traits.h\"\n#include \"npc/npc_queues.h\"\n#include \"sound.h\"\n#include \"editor.h\"\n#include \"effect.h\"\n#include \"game_main.h\"\n#include \"eff_id.h\"\n#include \"collision.h\"\n#include \"layers.h\"\n#include \"config.h\"\n#include \"message.h\"\n\n#include \"main/game_globals.h\"\n#include \"main/translate.h\"\n#include \"main/trees.h\"\n#include \"main/game_strings.h\"\n#include \"main/game_info.h\"\n#include \"main/game_loop_interrupt.h\"\n\n\nstatic constexpr int plr_warp_scroll_speed = 8; // 8px / frame\nstatic constexpr int plr_warp_scroll_max_frames = 260; // 4 seconds\n\nstatic void s_TriggerDoorEffects(const Location_t& loc, bool do_big_door = true)\n{\n    for(Background_t& bgo : treeBackgroundQuery(loc, SORTMODE_ID))\n    {\n        if(CheckCollision(loc, bgo.Location))\n        {\n            if(bgo.Type == 88)\n                NewEffect(EFFID_DOOR_S2_OPEN, static_cast<Location_t>(bgo.Location));\n            else if(bgo.Type == 87)\n                NewEffect(EFFID_DOOR_DOUBLE_S3_OPEN, static_cast<Location_t>(bgo.Location));\n            else if(bgo.Type == 107)\n                NewEffect(EFFID_DOOR_SIDE_S3_OPEN, static_cast<Location_t>(bgo.Location));\n            else if(do_big_door && bgo.Type == 141)\n            {\n                Location_t bLoc = static_cast<Location_t>(bgo.Location);\n                bLoc.set_width_center(104);\n                NewEffect(EFFID_BIG_DOOR_OPEN, bLoc);\n            }\n        }\n    }\n}\n\n// copied logic from checkWarp function\n// parameter release_at_warp determines whether NPC should be dropped at the warp entrance\n//   (set to false in new behavior where players are being teleported to warp)\nstatic void s_WarpReleaseItems(const Warp_t& warp, int A, bool backward, bool release_at_warp = true)\n{\n    Player_t& plr = Player[A];\n\n    int direction  = backward ? warp.Direction2 : warp.Direction;\n    auto &entrance = backward ? warp.Exit       : warp.Entrance;\n\n    if(warp.NoYoshi && plr.YoshiPlayer > 0)\n        YoshiSpit(A);\n\n    if(!warp.WarpNPC || (plr.Mount == 3 && (plr.YoshiNPC != 0 || plr.YoshiPlayer != 0) && warp.NoYoshi))\n    {\n        if(plr.HoldingNPC > 0)\n        {\n            if(NPC[plr.HoldingNPC].Type == NPCID_HEAVY_THROWER)\n                NPCHit(plr.HoldingNPC, 3, plr.HoldingNPC);\n        }\n\n        if((plr.Character == 3 && release_at_warp) ||\n          (plr.Character == 4 && warp.Effect == 1 && direction == 1 && release_at_warp))\n        {\n            NPC[plr.HoldingNPC].Location.Y = entrance.Y;\n\n            if(plr.HoldingNPC > 0 && plr.HoldingNPC <= numNPCs)\n                treeNPCUpdate(plr.HoldingNPC);\n        }\n\n        plr.HoldingNPC = 0;\n\n        if(plr.YoshiNPC > 0)\n            YoshiSpit(A);\n    }\n\n    if(plr.HoldingNPC > 0)\n    {\n        if(NPC[plr.HoldingNPC].Type == NPCID_ICE_CUBE) // can't bring ice through warps\n        {\n            NPC[plr.HoldingNPC].HoldingPlayer = 0;\n            plr.HoldingNPC = 0;\n        }\n    }\n\n    plr.StandingOnNPC = 0;\n}\n\n// steal the mount from a player (because they just entered a no-mount warp)\nstatic void s_WarpStealMount(int A)\n{\n    Player_t& p = Player[A];\n    if(OwedMount[A] == 0 && p.Mount > 0 && p.Mount != 2)\n    {\n        OwedMount[A] = p.Mount;\n        OwedMountType[A] = p.MountType;\n    }\n    p.Mount = 0;\n    p.MountType = 0;\n    p.MountOffsetY = 0;\n    SizeCheck(Player[A]);\n    UpdateYoshiMusic();\n}\n\n// fix the location of any players in pets, for after a player gets teleported\nstatic void s_FixPlayersInPets(const Screen_t& screen)\n{\n    for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n    {\n        int o_A = screen.players[plr_i];\n\n        const Player_t& o_p = Player[o_A];\n        if(o_p.Effect == PLREFF_PET_INSIDE)\n            PlayerEffects(o_A);\n    }\n}\n\nstatic void s_CheckWarpLevelExit(Player_t& plr, const Warp_t& warp, int lvl_counter, int map_counter)\n{\n    if(warp.level != STRINGINDEX_NONE)\n    {\n        GoToLevel = GetS(warp.level);\n        GoToLevelNoGameThing = warp.noEntranceScene;\n        plr.Effect = PLREFF_WAITING;\n        plr.Effect2 = lvl_counter;\n        ReturnWarp = plr.Warp;\n\n        if(IsHubLevel)\n            ReturnWarpSaved = ReturnWarp;\n\n        StartWarp = warp.LevelWarp;\n    }\n    else if(warp.MapWarp)\n    {\n        plr.Effect = PLREFF_WAITING;\n        plr.Effect2 = map_counter;\n    }\n}\n\nstatic void s_InitWarpScroll(Player_t& p, const Location_t& warp_enter, const Location_t& warp_exit, int min_frames = 0)\n{\n    unsigned int warp_dist = (int)num_t::dist(warp_enter.X - warp_exit.X, warp_enter.Y - warp_exit.Y);\n\n    int scroll_frames = warp_dist / plr_warp_scroll_speed;\n    if(scroll_frames < min_frames)\n        scroll_frames = min_frames;\n    if(scroll_frames > plr_warp_scroll_max_frames)\n        scroll_frames = plr_warp_scroll_max_frames;\n\n    p.Effect2 = 128 + scroll_frames;\n}\n\nstatic void s_WarpFaderLogic(bool is_reverse, int A, int transitEffect, const Location_t& focus, bool normal_ready, bool none_ready)\n{\n    int fader_index = vScreenIdxByPlayer(A);\n    SDL_assert_release(0 <= fader_index && fader_index <= c_vScreenCount);\n\n    auto& fader = g_levelVScreenFader[fader_index];\n\n    if(is_reverse && !fader.isVisible())\n        return;\n\n    int fade_from = (is_reverse) ? 65 :  0;\n    int fade_to   = (is_reverse) ?  0 : 65;\n    int rate      = 3;\n    bool ready    = normal_ready;\n\n    switch(transitEffect)\n    {\n    default:\n        if(transitEffect >= ScreenFader::S_CUSTOM)\n            goto generic_fade;\n\n    // fallthrough\n    case LevelDoor::TRANSIT_SCROLL:\n    case LevelDoor::TRANSIT_NONE:\n        ready = none_ready;\n        rate = (g_config.EnableInterLevelFade ? 8 : 64);\n\n    // fallthrough\n    case LevelDoor::TRANSIT_FADE:\n        transitEffect = ScreenFader::S_FADE;\n        goto generic_fade;\n\n    case LevelDoor::TRANSIT_CIRCLE_FADE:\n        transitEffect = ScreenFader::S_CIRCLE;\n        goto generic_fade;\n\n    case LevelDoor::TRANSIT_FLIP_H:\n        transitEffect = ScreenFader::S_FLIP_H;\n        goto generic_fade;\n\n    case LevelDoor::TRANSIT_FLIP_V:\n        transitEffect = ScreenFader::S_FLIP_V;\n        goto generic_fade;\n\n    generic_fade:\n        if(ready)\n            fader.setupFader(rate, fade_from, fade_to, transitEffect,\n                             true,\n                             num_t::round(focus.X + focus.Width / 2),\n                             num_t::round(focus.Y + focus.Height / 2),\n                             fader_index);\n        break;\n    }\n}\n\nstatic void s_delay_pipe_exit(int A)\n{\n    Player_t& p = Player[A];\n\n    const auto& warp = Warp[p.Warp];\n    const auto warp_dir_exit = (p.WarpBackward) ? warp.Direction : warp.Direction2;\n    bool warp_vertical = (warp_dir_exit == LevelDoor::EXIT_UP) || (warp_dir_exit == LevelDoor::EXIT_DOWN);\n\n    // number of players ahead of this one\n    int frames_before = 0;\n\n    for(int o_A = 1; o_A <= numPlayers; o_A++)\n    {\n        if(o_A == A)\n            continue;\n\n        Player_t& o_p = Player[o_A];\n        if(!o_p.Dead && o_p.TimeToLive == 0 && o_p.Effect == PLREFF_WARP_PIPE && o_p.Warp == p.Warp && o_p.WarpBackward == p.WarpBackward && o_p.Effect2 > 1 && (o_p.Effect2 < 128 || o_p.Effect2 >= 2000))\n        {\n            // wait for player to warp\n            if(warp_vertical)\n                frames_before += (int)o_p.Location.Height;\n            else\n                frames_before += (int)o_p.Location.Width;\n\n            // pause between players\n            frames_before += 70;\n        }\n    }\n\n    // put in new pipe holding state\n    if(frames_before)\n    {\n        p.Effect = PLREFF_WARP_PIPE;\n        p.Effect2 = 2010 + frames_before;\n    }\n}\n\n// important: each of these generalizes the cases in SMBX 1.3 and is fully compatible with SMBX 1.3 when entrance.Width is 32\nstatic num_t s_warp_offset_x(const Location_t& pLoc, const SpeedlessLocation_t& entrance, const SpeedlessLocation_t& exit)\n{\n    num_t ratio_x = 0.5_n;\n    if(entrance.Width > 32)\n        ratio_x = (pLoc.X + pLoc.Width / 2 - entrance.X - 16) / (int_ok)(entrance.Width - 32);\n\n    if(ratio_x < 0)\n        ratio_x = 0;\n    else if(ratio_x > 1)\n        ratio_x = 1;\n\n    return ratio_x * (int_ok)(exit.Width - 32) - pLoc.Width / 2 + 16;\n}\n\nstatic num_t s_warp_offset_y(const Location_t& pLoc, const SpeedlessLocation_t& entrance, const SpeedlessLocation_t& exit)\n{\n    num_t ratio_y = 1.0_n;\n    if(entrance.Height > 32)\n        ratio_y = (pLoc.Y + pLoc.Height - entrance.Y - 32) / (int_ok)(entrance.Height - 32);\n\n    if(ratio_y < 0)\n        ratio_y = 0;\n    else if(ratio_y > 1)\n        ratio_y = 1;\n\n    return ratio_y * (int_ok)(exit.Height - 32) - pLoc.Height + 32;\n}\n\nbool PlayerWaitingInWarp(const Player_t& p)\n{\n    return (p.Effect == PLREFF_WARP_PIPE && p.Effect2 >= 2000)\n        || (p.Effect == PLREFF_WAITING && p.Effect2 > 30 && p.Effect2 <= 2000);\n}\n\nbool PlayerScrollingInWarp(const Player_t& p)\n{\n    return (p.Effect == PLREFF_WARP_PIPE || p.Effect == PLREFF_WARP_DOOR)\n        && (p.Effect2 >= 128 && p.Effect2 <= 128 + plr_warp_scroll_max_frames);\n}\n\nvoid PlayerEffectWarpPipe(int A)\n{\n    Player_t& p = Player[A];\n\n    p.SpinJump = false;\n    p.TailCount = 0;\n    p.Location.SpeedY = 0;\n\n    bool backward = p.WarpBackward;\n    const auto &warp = Warp[p.Warp];\n    SpeedlessLocation_t sl_warp_enter = (backward ? warp.Exit : warp.Entrance);\n    SpeedlessLocation_t sl_warp_exit = (backward ? warp.Entrance : warp.Exit);\n    Location_t warp_enter = static_cast<Location_t>(sl_warp_enter);\n    Location_t warp_exit = static_cast<Location_t>(sl_warp_exit);\n    const auto &warp_dir_enter = backward ? warp.Direction2 : warp.Direction;\n    const auto &warp_dir_exit = backward ? warp.Direction : warp.Direction2;\n\n    bool same_section = SectionCollision(p.Section, warp_exit);\n    bool do_scroll = (warp.transitEffect == LevelDoor::TRANSIT_SCROLL) && same_section;\n    bool is_level_quit = warp.level != STRINGINDEX_NONE || warp.MapWarp;\n\n    // teleport other players into warp in shared screen mode\n    Screen_t& screen = ScreenByPlayer(A);\n    bool is_shared_screen = (screen.Type == 3);\n\n    if(p.Effect2 == 0) // Entering pipe\n    {\n        // how many *frames* left until the player crosses the boundary of the pipe and becomes invisible\n        int leftToGoal = 0;\n        // how many *frames* to wait after the player crosses the boundary of the pipe and becomes invisible (set to -16 for left/right warps)\n        int leftToGoal_end = -8;\n\n        if(warp_dir_enter == LevelDoor::ENTRANCE_DOWN || warp_dir_enter == LevelDoor::ENTRANCE_UP)\n        {\n            if(warp_dir_enter == LevelDoor::ENTRANCE_DOWN)\n            {\n                p.Location.Y += 1;\n                leftToGoal = num_t::floor(warp_enter.Y + warp_enter.Height - p.Location.Y);\n            }\n            else\n            {\n                p.Location.Y -= 1;\n                leftToGoal = num_t::floor(p.Location.Y + p.Location.Height - warp_enter.Y);\n            }\n\n            p.Location.X = warp_enter.X + s_warp_offset_x(p.Location, sl_warp_enter, sl_warp_enter);\n\n            if(p.Mount == 0)\n                p.Frame = 15;\n\n            if(p.HoldingNPC > 0)\n            {\n                NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n                NPC[p.HoldingNPC].Location.X = p.Location.X + (p.Location.Width - NPC[p.HoldingNPC].Location.Width) / 2;\n            }\n        }\n        else if(warp_dir_enter == LevelDoor::ENTRANCE_LEFT || warp_dir_enter == LevelDoor::ENTRANCE_RIGHT)\n        {\n            if(warp_dir_enter == LevelDoor::ENTRANCE_LEFT)\n            {\n                p.Location.X -= 0.5_n;\n                p.Location.SpeedX = -0.5_n;\n                leftToGoal = num_t::floor((p.Location.X + p.Location.Width - warp_enter.X) * 2);\n                p.Direction = -1; // makes (p.Direction > 0) always false\n            }\n            else\n            {\n                p.Location.X += 0.5_n;\n                p.Location.SpeedX = 0.5_n;\n                leftToGoal = num_t::floor((warp_enter.X + warp_enter.Width - p.Location.X) * 2);\n                p.Direction = 1; // Makes (p.Direction > 0) always true\n            }\n\n            if(p.Mount == 3)\n            {\n                p.Duck = true;\n                p.Location.Height = 30;\n            }\n\n            p.Location.Y = warp_enter.Y + warp_enter.Height - p.Location.Height - 2;\n\n            if(p.HoldingNPC > 0)\n            {\n                NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n\n                if(warp_dir_enter == LevelDoor::ENTRANCE_LEFT)\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + p.Location.Width - Physics.PlayerGrabSpotX[p.Character][p.State] - NPC[p.HoldingNPC].Location.Width;\n                else\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + Physics.PlayerGrabSpotX[p.Character][p.State];\n            }\n\n            PlayerFrame(p);\n            p.Location.SpeedX = 0;\n\n            // -8px as always, but leftToGoal is measured in frames (cross-ref * 2 above)\n            leftToGoal_end = -16;\n        }\n\n        // checks whether the player is 8 *pixels* beyond the pipe boundary\n        if(leftToGoal < leftToGoal_end)\n        {\n            if(do_scroll)\n                s_InitWarpScroll(p, warp_enter, warp_exit);\n            else\n                p.Effect2 = 1;\n        }\n\n        if(p.HoldingNPC > 0 && p.HoldingNPC <= numNPCs)\n            treeNPCUpdate(p.HoldingNPC);\n\n        // teleport other players into the pipe warp 8 frames before player crosses boundary\n        if(is_shared_screen && leftToGoal == 8)\n        {\n            int vscreen_A = vScreenIdxByPlayer(A);\n            bool do_tele = !vScreenCollision(vscreen_A, warp_exit);\n\n            if(do_tele)\n            {\n                SharedScreenAvoidJump_Pre(screen);\n\n                for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n                {\n                    int o_A = screen.players[plr_i];\n                    if(o_A == A)\n                        continue;\n\n                    Player_t& o_p = Player[o_A];\n\n                    // revive someone in wings and bring to warp\n                    if(o_p.Effect == PLREFF_COOP_WINGS)\n                        o_p.Dead = false;\n\n                    // in the mouth of an onscreen player's Pet?\n                    bool in_onscreen_pet = !warp.NoYoshi && InOnscreenPet(o_A, screen);\n\n                    bool status_match = (o_p.Effect == p.Effect && o_p.Warp == p.Warp && o_p.WarpBackward == p.WarpBackward);\n\n                    if(!o_p.Dead && o_p.TimeToLive == 0 && !in_onscreen_pet && !status_match)\n                    {\n                        RemoveFromPet(o_A);\n\n                        s_WarpReleaseItems(warp, o_A, p.WarpBackward, false);\n\n                        o_p.Warp = p.Warp;\n                        o_p.WarpBackward = p.WarpBackward;\n                        o_p.Effect = p.Effect;\n                        // make other player behind so that this player will exit first\n                        o_p.Effect2 = 0;\n                        o_p.Location.X = warp_enter.X + (warp_enter.Width - o_p.Location.Width) / 2;\n                        o_p.Location.Y = warp_enter.Y + (warp_enter.Height - o_p.Location.Height) / 2;\n                        o_p.Location.SpeedX = 0;\n                        o_p.Location.SpeedY = 0;\n                    }\n                }\n\n                SharedScreenAvoidJump_Post(screen, (do_scroll) ? 0 : 200);\n            }\n        }\n\n        // D_pLogDebug(\"Warping: %g (same section? %s!)\", leftToGoal, SectionCollision(p.Section, warp_exit) ? \"yes\" : \"no\");\n\n        // trigger fader when there are 16 frames left (for normal effects) or 0 frames left (for none or scroll)\n        s_WarpFaderLogic(false, A, warp.transitEffect, warp_enter, leftToGoal == 16, !is_level_quit && !same_section && leftToGoal == 0);\n    }\n    else if(p.Effect2 == 1)  // Exiting pipe (initialization)\n    {\n        if(warp.NoYoshi)\n        {\n            UnDuck(p);\n            s_WarpStealMount(A);\n        }\n\n        if(warp_dir_exit == LevelDoor::EXIT_DOWN || warp_dir_exit == LevelDoor::EXIT_UP)\n        {\n            if(warp_dir_exit == LevelDoor::EXIT_DOWN)\n                p.Location.Y = warp_exit.Y - p.Location.Height - 8;\n            else\n                p.Location.Y = warp_exit.Y + warp_exit.Height + 8;\n\n            p.Location.X = warp_exit.X + s_warp_offset_x(p.Location, (do_scroll) ? sl_warp_exit : sl_warp_enter, sl_warp_exit);\n\n            if(p.Mount == 0)\n                p.Frame = 15;\n\n            if(p.HoldingNPC > 0)\n            {\n                NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n                NPC[p.HoldingNPC].Location.X = p.Location.X + (p.Location.Width - NPC[p.HoldingNPC].Location.Width) / 2;\n            }\n\n            if(p.Duck)\n                UnDuck(Player[A]);\n        }\n        else if(warp_dir_exit == LevelDoor::EXIT_RIGHT || warp_dir_exit == LevelDoor::EXIT_LEFT)\n        {\n            if(warp_dir_exit == LevelDoor::EXIT_RIGHT)\n            {\n                p.Location.X = warp_exit.X - p.Location.Width - 8;\n                p.Direction = 1;\n            }\n            else\n            {\n                p.Location.X = warp_exit.X + warp_exit.Width + 8;\n                p.Direction = -1;\n            }\n\n            if(p.Mount == 3)\n            {\n                p.Duck = true;\n                p.Location.Height = 30;\n            }\n\n            p.Location.Y = warp_exit.Y + warp_exit.Height - p.Location.Height - 2;\n\n            if(p.Mount == 0)\n                p.Frame = 1;\n\n            if(p.HoldingNPC > 0)\n            {\n                if(p.State == 1)\n                    p.Frame = 5;\n                else\n                    p.Frame = 8;\n\n                NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n\n                p.Direction = -p.Direction;\n\n                if(warp_dir_exit == LevelDoor::EXIT_RIGHT)\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + p.Location.Width - Physics.PlayerGrabSpotX[p.Character][p.State] - NPC[p.HoldingNPC].Location.Width;\n                else\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + Physics.PlayerGrabSpotX[p.Character][p.State];\n            }\n        }\n\n        if(p.HoldingNPC > 0 && p.HoldingNPC <= numNPCs)\n            treeNPCUpdate(p.HoldingNPC);\n\n        p.Effect2 = 100;\n\n        CheckSection(A);\n\n        if(p.HoldingNPC > 0)\n            CheckSectionNPC(p.HoldingNPC);\n\n        // set any other players warping to the same pipe to this state (needed to avoid splitting a shared screen)\n        if(is_shared_screen && !do_scroll)\n        {\n            for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n            {\n                int o_A = screen.players[plr_i];\n                if(A == o_A)\n                    continue;\n\n                Player_t& o_p = Player[o_A];\n                if(!o_p.Dead && o_p.TimeToLive == 0 && o_p.Effect == PLREFF_WARP_PIPE && o_p.Effect2 == 0 && o_p.Warp == Player[A].Warp && o_p.WarpBackward == Player[A].WarpBackward)\n                {\n                    o_p.Location.X = p.Location.X + (p.Location.Width - o_p.Location.Width) / 2;\n                    o_p.Location.Y = p.Location.Y + p.Location.Height - o_p.Location.Height;\n\n                    CheckSection(o_A);\n\n                    o_p.Effect = PLREFF_WARP_PIPE;\n                    o_p.Effect2 = 1;\n                }\n            }\n\n            // update position of any players in pets\n            s_FixPlayersInPets(screen);\n\n            // disable any tempX/TempY (no longer needed)\n            SharedScreenResetTemp(screen);\n        }\n\n        // delay based on number of players ahead of this one\n        if(is_shared_screen)\n            s_delay_pipe_exit(A);\n\n        // many-player code\n        if(g_ClonedPlayerMode)\n        {\n            for(int B = 1; B <= numPlayers; B++)\n            {\n                if(B != A)\n                {\n                    if(warp_dir_exit != 3)\n                        Player[B].Location.Y = p.Location.Y + p.Location.Height - Player[B].Location.Height;\n                    else\n                        Player[B].Location.Y = p.Location.Y;\n\n                    Player[B].Location.X = p.Location.X + (p.Location.Width - Player[B].Location.Width) / 2;\n                    Player[B].Location.SpeedY = dRand() * 24 - 12;\n                    Player[B].Effect = PLREFF_WAITING;\n                    Player[B].Effect2 = 0;\n                    CheckSection(B);\n\n                    if(Player[B].HoldingNPC > 0)\n                        CheckSectionNPC(Player[B].HoldingNPC);\n                }\n            }\n        }\n\n        // reverse screen fade\n        if(!is_level_quit)\n            s_WarpFaderLogic(true, A, warp.transitEffect, warp_exit, true, true);\n\n        s_CheckWarpLevelExit(p, warp, 2970, 2970);\n    }\n    else if(p.Effect2 >= 2000) // NEW >2P holding state for pipe exit\n    {\n        p.Effect2 -= 1;\n\n        if(p.Effect2 <= 2000)\n        {\n            SharedScreenAvoidJump_Pre(screen);\n\n            p.Effect2 = 2;\n            if(backward || !warp.cannonExit)\n                PlaySoundSpatial(SFX_Warp, p.Location);\n\n            SharedScreenAvoidJump_Post(screen, 0);\n        }\n    }\n    else if(p.Effect2 > 128) // Scrolling between pipes\n    {\n        num_t targetX = p.Location.X;\n        num_t targetY = p.Location.Y;\n\n        if(warp_dir_exit == 1)\n        {\n            targetX = warp_exit.X + (warp_exit.Width - p.Location.Width) / 2;\n            targetY = warp_exit.Y - p.Location.Height - 8;\n        }\n        else if(warp_dir_exit == 3)\n        {\n            targetX = warp_exit.X + (warp_exit.Width - p.Location.Width) / 2;\n            targetY = warp_exit.Y + warp_exit.Height + 8;\n        }\n        else if(warp_dir_exit == 2)\n        {\n            if(p.Mount == 3)\n                p.Location.Height = 30;\n\n            targetX = warp_exit.X - p.Location.Width - 8;\n            targetY = warp_exit.Y + warp_exit.Height - p.Location.Height - 2;\n        }\n        else if(warp_dir_exit == 4)\n        {\n            if(p.Mount == 3)\n                p.Location.Height = 30;\n\n            targetX = warp_exit.X + warp_exit.Width + 8;\n            targetY = warp_exit.Y + warp_exit.Height - p.Location.Height - 2;\n        }\n\n        int frames_left = p.Effect2 - 128;\n\n        p.Location.X += (targetX - p.Location.X) / frames_left;\n        p.Location.Y += (targetY - p.Location.Y) / frames_left;\n\n        p.Effect2 -= 1;\n\n        if(p.Effect2 <= 128)\n            p.Effect2 = 1;\n    }\n    else if(p.Effect2 >= 100) // Waiting until exit pipe\n    {\n        p.Effect2 += 1;\n\n        if(p.Effect2 >= 110)\n        {\n            p.Effect2 = 2;\n            if(backward || !warp.cannonExit)\n                PlaySoundSpatial(SFX_Warp, p.Location);\n        }\n    }\n    else if(p.Effect2 == 2) // Proceeding the pipe exiting\n    {\n        if(!backward && warp.cannonExit)\n        {\n            switch(warp_dir_exit)\n            {\n            case LevelDoor::EXIT_DOWN:\n                p.Location.Y = warp_exit.Y;\n                break;\n            case LevelDoor::EXIT_UP:\n                p.Location.Y = (warp_exit.Y + warp_exit.Height) - p.Location.Height;\n                break;\n            case LevelDoor::EXIT_LEFT:\n                p.Location.X = (warp_exit.X + warp_exit.Width) - p.Location.Width;\n                p.Direction = -1;\n                break;\n            case LevelDoor::EXIT_RIGHT:\n                p.Location.X = warp_exit.X;\n                p.Direction = +1;\n                break;\n            }\n            p.Effect2 = 3;\n            if(p.HoldingNPC > 0)\n            {\n                if(p.ForceHold < 5) // Prevent NPC being stuck in the wall/ceiling\n                    p.ForceHold = 5;\n                PlayerGrabCode(A);\n            }\n        }\n        else if(warp_dir_exit == LevelDoor::EXIT_DOWN || warp_dir_exit == LevelDoor::EXIT_UP)\n        {\n            if(warp_dir_exit == LevelDoor::EXIT_DOWN)\n            {\n                p.Location.Y += 1;\n                if(p.Location.Y >= warp_exit.Y)\n                    p.Effect2 = 3;\n            }\n            else\n            {\n                p.Location.Y -= 1;\n                if(p.Location.Y + p.Location.Height <= warp_exit.Y + warp_exit.Height)\n                    p.Effect2 = 3;\n\n                // make players less likely to collide chaotically out of UP exits\n                if(is_shared_screen || (numPlayers > 2 && !g_ClonedPlayerMode))\n                    p.StandUp2 = true;\n            }\n\n            if(p.HoldingNPC > 0)\n            {\n                NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n                NPC[p.HoldingNPC].Location.X = p.Location.X + (p.Location.Width - NPC[p.HoldingNPC].Location.Width) / 2;\n            }\n\n            if(p.Mount == 0)\n                p.Frame = 15;\n        }\n        else if(warp_dir_exit == LevelDoor::EXIT_LEFT || warp_dir_exit == LevelDoor::EXIT_RIGHT)\n        {\n            if(warp_dir_exit == LevelDoor::EXIT_LEFT)\n            {\n                p.Location.X -= 0.5_n;\n                p.Direction = -1; // makes (p.Direction < 0) always true\n\n                if(p.Location.X + p.Location.Width <= warp_exit.X + warp_exit.Width)\n                    p.Effect2 = 3;\n            }\n            else\n            {\n                p.Location.X += 0.5_n;\n                p.Direction = 1; // makes (p.Direction < 0) always false\n\n                if(p.Location.X >= warp_exit.X)\n                    p.Effect2 = 3;\n            }\n\n            if(p.HoldingNPC > 0)\n            {\n                if(p.Character >= 3) // peach/toad leaving a pipe\n                {\n                    // VB6 bug: should have been -1 when going left\n                    p.Location.SpeedX = 1;\n                    PlayerFrame(p);\n                    NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n                }\n                else\n                {\n                    p.Direction = -p.Direction;\n\n                    if(p.State == 1)\n                        p.Frame = 5;\n                    else\n                        p.Frame = 8;\n\n                    NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n                }\n\n                // this logic was substantially simplified from VB6, which also appeared to depend on Direction (but actually didn't)\n                if(warp_dir_exit == LevelDoor::EXIT_LEFT)\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + Physics.PlayerGrabSpotX[p.Character][p.State];\n                else\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + p.Location.Width - Physics.PlayerGrabSpotX[p.Character][p.State] - NPC[p.HoldingNPC].Location.Width;\n            }\n            else\n            {\n                // VB6 bug: this should have been 0.5 when going right\n                p.Location.SpeedX = -0.5_n;\n                PlayerFrame(p);\n                p.Location.SpeedX = 0;\n            }\n        }\n\n        if(p.HoldingNPC > 0 && p.HoldingNPC <= numNPCs)\n            treeNPCUpdate(p.HoldingNPC);\n    }\n    else if(p.Effect2 == 3) // Finishing the pipe exiting / shooting\n    {\n        if(!backward && warp.cannonExit)\n        {\n            PlaySoundSpatial(SFX_Bullet, p.Location);\n            auto loc = warp_exit;\n            if(warp_dir_exit == LevelDoor::EXIT_LEFT || warp_dir_exit == LevelDoor::EXIT_RIGHT)\n                loc.Y += loc.Height - (p.Location.Height / 2) - (loc.Height / 2);\n            NewEffect(EFFID_STOMP_INIT, loc, p.Direction); // Cannon pipe shoot effect\n        }\n\n        if(p.HoldingNPC > 0)\n        {\n            if(warp_dir_exit == LevelDoor::EXIT_LEFT || warp_dir_exit == LevelDoor::EXIT_RIGHT)\n            {\n                if(warp_dir_exit == 2)\n                    p.Direction = 1;\n                else if(warp_dir_exit == 4)\n                    p.Direction = -1;\n\n                if(p.State == 1)\n                    p.Frame = 5;\n                else\n                    p.Frame = 8;\n\n                if(!p.Controls.Run)\n                    p.Controls.Run = true;\n\n                PlayerGrabCode(A);\n            }\n        }\n\n        p.Effect = PLREFF_NORMAL;\n        p.Effect2 = 0;\n        p.WarpCD = 20;\n        p.CanJump = false;\n        p.CanAltJump = false;\n        p.Bumped2 = 0;\n\n        if(!backward && warp.cannonExit)\n        {\n            switch(warp_dir_exit)\n            {\n            case LevelDoor::EXIT_DOWN:\n                p.Location.SpeedY = warp.cannonExitSpeed;\n                break;\n            case LevelDoor::EXIT_UP:\n                p.Location.SpeedY = -warp.cannonExitSpeed;\n                break;\n            case LevelDoor::EXIT_LEFT:\n                p.Location.SpeedX = -warp.cannonExitSpeed;\n                p.Direction = -1;\n                break;\n            case LevelDoor::EXIT_RIGHT:\n                p.Location.SpeedX = warp.cannonExitSpeed;\n                p.Direction = +1;\n                break;\n            }\n\n            if(warp_dir_exit == LevelDoor::EXIT_LEFT || warp_dir_exit == LevelDoor::EXIT_RIGHT)\n                p.WarpShooted = true;\n        }\n        else\n        {\n            p.Location.SpeedY = 0;\n            p.Location.SpeedX = 0;\n        }\n\n        if(p.HoldingNPC > 0)\n            NPC[p.HoldingNPC].Effect = NPCEFF_NORMAL;\n\n        if(g_ClonedPlayerMode)\n        {\n            for(int B = 1; B <= numPlayers; B++)\n            {\n                if(B != A)\n                {\n                    if(warp_dir_exit != 1)\n                        Player[B].Location.Y = p.Location.Y + p.Location.Height - Player[B].Location.Height;\n                    else\n                        Player[B].Location.Y = p.Location.Y;\n\n                    Player[B].Location.X = p.Location.X + (p.Location.Width - Player[B].Location.Width) / 2;\n                    Player[B].Location.SpeedY = dRand() * 24 - 12;\n                    Player[B].Effect = PLREFF_NORMAL;\n                    Player[B].Effect2 = 0;\n                    CheckSection(B);\n                }\n            }\n        }\n\n        if(warp.eventExit != EVENT_NONE)\n            TriggerEvent(warp.eventExit, A);\n    }\n}\n\nvoid PlayerEffectWarpDoor(int A)\n{\n    Player_t& p = Player[A];\n\n    bool backward = p.WarpBackward;\n    const Warp_t &warp = Warp[p.Warp];\n    SpeedlessLocation_t sl_warp_enter = (backward ? warp.Exit : warp.Entrance);\n    SpeedlessLocation_t sl_warp_exit = (backward ? warp.Entrance : warp.Exit);\n    Location_t warp_enter = static_cast<Location_t>(sl_warp_enter);\n    Location_t warp_exit = static_cast<Location_t>(sl_warp_exit);\n\n    bool same_section = SectionCollision(p.Section, warp_exit);\n    bool do_scroll = (warp.transitEffect == LevelDoor::TRANSIT_SCROLL) && same_section;\n    bool is_level_quit = warp.level != STRINGINDEX_NONE || warp.MapWarp;\n\n    if(p.HoldingNPC > 0)\n    {\n        NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n        NPC[p.HoldingNPC].Location.X = p.Location.X + (p.Location.Width - NPC[p.HoldingNPC].Location.Width) / 2;\n        treeNPCUpdate(p.HoldingNPC);\n    }\n\n    p.Effect2 += 1;\n\n    if(p.Mount == 0 && p.Character != 5)\n        p.Frame = 13;\n\n    if(p.Character == 5)\n        p.Frame = 1;\n\n    // just stay in the door after entering it during a flag exit\n    if(LevelMacro == LEVELMACRO_FLAG_EXIT)\n    {\n        if(p.Effect2 == 15)\n        {\n            p.Effect = PLREFF_WAITING;\n            p.Effect2 = -A;\n        }\n        return;\n    }\n\n    // trigger warp fader when p.Effect2 is 5 (for normal animations) or 20 (for none/scroll animations)\n    s_WarpFaderLogic(false, A, warp.transitEffect, warp_enter, p.Effect2 == 5, !is_level_quit && !same_section && (p.Effect2 == 20));\n\n    // teleport other players into door in shared screen mode\n    Screen_t& screen = ScreenByPlayer(A);\n    bool is_shared_screen = (screen.Type == 3);\n    if(is_shared_screen && (p.Effect2 == 15))\n    {\n        int vscreen_A = vScreenIdxByPlayer(A);\n        bool do_tele = is_shared_screen && !vScreenCollision(vscreen_A, warp_exit);\n\n        if(do_tele)\n        {\n            SharedScreenAvoidJump_Pre(screen);\n\n            for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n            {\n                int o_A = screen.players[plr_i];\n                if(o_A == A)\n                    continue;\n\n                Player_t& o_p = Player[o_A];\n\n                // revive someone in wings and bring to warp\n                if(o_p.Effect == PLREFF_COOP_WINGS)\n                    o_p.Dead = false;\n\n                // in the mouth of an onscreen player's Pet?\n                bool in_onscreen_pet = !warp.NoYoshi && InOnscreenPet(o_A, screen);\n\n                bool status_match = (o_p.Effect == p.Effect && o_p.Warp == p.Warp && o_p.WarpBackward == p.WarpBackward);\n\n                if(!o_p.Dead && o_p.TimeToLive == 0 && !in_onscreen_pet && !status_match)\n                {\n                    RemoveFromPet(o_A);\n\n                    s_WarpReleaseItems(warp, o_A, p.WarpBackward, false);\n\n                    o_p.Warp = p.Warp;\n                    o_p.WarpBackward = p.WarpBackward;\n                    o_p.Effect = p.Effect;\n                    // 1 frame behind so that this player will exit first\n                    o_p.Effect2 = 14;\n                    o_p.Location.X = warp_enter.X + (warp_enter.Width - o_p.Location.Width) / 2;\n                    o_p.Location.Y = warp_enter.Y + warp_enter.Height - o_p.Location.Height;\n                    o_p.Location.SpeedX = 0;\n                    o_p.Location.SpeedY = 0;\n                }\n            }\n\n            SharedScreenAvoidJump_Post(screen, (do_scroll) ? 0 : 200);\n        }\n    }\n\n    // start the scroll effect\n    if(do_scroll && p.Effect2 == 29)\n    {\n        s_InitWarpScroll(p, warp_enter, warp_exit, 30);\n        SoundPause[SFX_Door] = 60;\n    }\n    // process the scroll effect\n    else if(p.Effect2 >= 128)\n    {\n        num_t targetX = warp_exit.X + (warp_exit.Width - p.Location.Width) / 2;\n        num_t targetY = warp_exit.Y + warp_exit.Height - p.Location.Height;\n\n        // += 1 above\n        p.Effect2 -= 1;\n\n        int frames_left = p.Effect2 - 128;\n\n        p.Location.X += (targetX - p.Location.X) / frames_left;\n        p.Location.Y += (targetY - p.Location.Y) / frames_left;\n\n        if(frames_left == 30)\n        {\n            s_TriggerDoorEffects(warp_exit);\n            PlaySoundSpatial(SFX_Door, warp_exit);\n        }\n\n        p.Effect2 -= 1;\n\n        if(p.Effect2 <= 128)\n            p.Effect2 = 30;\n    }\n\n    // finalize the warp\n    if(p.Effect2 >= 30 && p.Effect2 < 128)\n    {\n        if(warp.NoYoshi)\n        {\n            s_WarpStealMount(A);\n            p.Frame = 1;\n        }\n\n        p.Location.X = warp_exit.X + s_warp_offset_x(p.Location, (do_scroll) ? sl_warp_exit : sl_warp_enter, sl_warp_exit);\n        p.Location.Y = warp_exit.Y + warp_exit.Height - p.Location.Height;\n\n        // set any other players warping to the same door into the door holding pattern (needed to avoid splitting a shared screen)\n        if(is_shared_screen)\n        {\n            for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n            {\n                int o_A = screen.players[plr_i];\n                if(A == o_A)\n                    continue;\n\n                Player_t& o_p = Player[o_A];\n                if(!o_p.Dead && o_p.TimeToLive == 0 && o_p.Effect == PLREFF_WARP_DOOR && o_p.Warp == Player[A].Warp && o_p.WarpBackward == Player[A].WarpBackward)\n                {\n                    o_p.Location.X = warp_exit.X + (warp_exit.Width - o_p.Location.Width) / 2;\n                    o_p.Location.Y = warp_exit.Y + warp_exit.Height - o_p.Location.Height;\n\n                    CheckSection(o_A);\n\n                    o_p.Effect = PLREFF_WAITING;\n                    o_p.Effect2 = 131;\n                    o_p.WarpCD = 40;\n\n                    if(warp.NoYoshi)\n                    {\n                        s_WarpStealMount(A);\n                        p.Frame = 1;\n                    }\n                }\n            }\n\n            // update position of any players in pets\n            s_FixPlayersInPets(screen);\n\n            SharedScreenResetTemp(screen);\n        }\n\n        CheckSection(A);\n\n        if(p.HoldingNPC > 0)\n        {\n            if(!p.Controls.Run)\n                p.Controls.Run = true;\n\n            PlayerGrabCode(A);\n        }\n\n        p.Effect = PLREFF_NORMAL;\n        p.Effect2 = 0;\n        p.WarpCD = 40;\n\n        if(!is_level_quit)\n        {\n            // reverse warp fader\n            s_WarpFaderLogic(true, A, warp.transitEffect, warp_exit, true, true);\n\n            if(warp.eventExit != EVENT_NONE)\n                TriggerEvent(warp.eventExit, A);\n        }\n\n        s_CheckWarpLevelExit(p, warp, 3000, 2970);\n\n        if(g_ClonedPlayerMode)\n        {\n            for(int B = 1; B <= numPlayers; B++)\n            {\n                if(B != A)\n                {\n                    Player[B].Location.Y = p.Location.Y + p.Location.Height - Player[B].Location.Height;\n                    Player[B].Location.X = p.Location.X + (p.Location.Width - Player[B].Location.Width) / 2;\n                    Player[B].Location.SpeedY = dRand() * 24 - 12;\n                    CheckSection(B);\n\n                    if(Player[B].HoldingNPC > 0)\n                    {\n                        if(Player[B].Direction > 0)\n                            NPC[Player[B].HoldingNPC].Location.X = Player[B].Location.X + Physics.PlayerGrabSpotX[Player[B].Character][Player[B].State];\n                        else\n                            NPC[Player[B].HoldingNPC].Location.X = Player[B].Location.X + Player[B].Location.Width - Physics.PlayerGrabSpotX[Player[B].Character][Player[B].State] - NPC[p.HoldingNPC].Location.Width;\n\n                        // TODO: investigate this possible crashing bug (should be Player[B].HoldingNPC)\n                        NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n                        NPC[Player[B].HoldingNPC].Section = Player[B].Section;\n\n                        if(p.HoldingNPC > 0 && p.HoldingNPC <= numNPCs)\n                            treeNPCUpdate(p.HoldingNPC);\n\n                        // already checked this one > 0 above\n                        if(Player[B].HoldingNPC <= numNPCs)\n                            treeNPCUpdate(Player[B].HoldingNPC);\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid PlayerEffectWarpWait(int A)\n{\n    Player_t& p = Player[A];\n\n    // door exit holding pattern (exit blocked)\n    if(p.Effect2 == 131)\n    {\n        bool tempBool = false;\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            // Was previously only B != A. New conditions only apply in >2P\n            bool check_coll = B != A && !Player[B].Dead && (Player[B].Effect != PLREFF_WAITING || B < A) && (Player[B].Effect != PLREFF_PET_INSIDE);\n            if(check_coll && CheckCollision(p.Location, Player[B].Location))\n                tempBool = true;\n        }\n\n        if(!tempBool)\n        {\n            p.Effect2 = 130;\n\n            const auto& warp_exit = p.WarpBackward ? Warp[p.Warp].Entrance : Warp[p.Warp].Exit;\n\n            s_TriggerDoorEffects(static_cast<Location_t>(warp_exit), false);\n\n            SoundPause[SFX_Door] = 0;\n            PlaySoundSpatial(SFX_Door, p.Location);\n        }\n    }\n    // door exit wait\n    else if(p.Effect2 <= 130)\n    {\n        p.Effect2 -= 1;\n        if(p.Effect2 == 100)\n        {\n            Screen_t& screen = ScreenByPlayer(A);\n\n            SharedScreenAvoidJump_Pre(screen);\n\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n\n            SharedScreenAvoidJump_Post(screen, 0);\n        }\n    }\n    // 2P holding condition for start warp (pipe exit)\n    else if(p.Effect2 <= 300)\n    {\n        p.Effect2 -= 1;\n        if(p.Effect2 == 200)\n        {\n            Screen_t& screen = ScreenByPlayer(A);\n\n            SharedScreenAvoidJump_Pre(screen);\n\n            p.Effect2 = 100;\n            p.Effect = PLREFF_WARP_PIPE;\n\n            SharedScreenAvoidJump_Post(screen, 0);\n        }\n    }\n    else if(p.Effect2 <= 1000) // Start Wait for pipe\n    {\n        p.Effect2 -= 1;\n        if(p.Effect2 == 900)\n        {\n            p.Effect = PLREFF_WARP_PIPE;\n            p.Effect2 = 100;\n\n            const Screen_t& screen = ScreenByPlayer(A);\n\n            bool do_modern = !g_ClonedPlayerMode && (numPlayers > 2 || screen.Type == ScreenTypes::SharedScreen || XMessage::GetStatus() != XMessage::Status::local);\n            if(!do_modern)\n            {\n                // 2P holding condition for start warp\n                if(A == screen.players[1])\n                {\n                    p.Effect = PLREFF_WAITING;\n                    p.Effect2 = 300;\n                }\n            }\n            else\n                s_delay_pipe_exit(A);\n        }\n    }\n    else if(p.Effect2 <= 2000) // Start Wait for door\n    {\n        p.Effect2 -= 1;\n\n        if(p.Effect2 == 1900)\n        {\n            s_TriggerDoorEffects(static_cast<Location_t>(Warp[p.Warp].Exit), false);\n\n            SoundPause[SFX_Door] = 0;\n\n            // SMBX 1.3 logic for P1, see below\n            // p.Effect = PLREFF_WAITING;\n            // p.Effect2 = 30;\n\n            if(A >= 2 && !g_ClonedPlayerMode)\n            {\n                p.Effect = PLREFF_WAITING;\n                p.Effect2 = 131;\n            }\n            else\n                PlaySoundSpatial(SFX_Door, p.Location);\n        }\n        // new code to replicate SMBX 1.3 logic setting Effect2 to 30\n        else if(p.Effect2 == 1870)\n        {\n            // Trigger an exit event at door warp that was used to enter the level\n            if(Warp[p.Warp].eventExit != EVENT_NONE)\n                TriggerEvent(Warp[p.Warp].eventExit, A);\n\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n        }\n    }\n    else if(p.Effect2 <= 3000) // exit warp wait\n    {\n        p.Effect2 -= 1;\n\n        auto &w = Warp[p.Warp];\n\n        if(g_config.EnableInterLevelFade && (w.MapWarp || w.level != STRINGINDEX_NONE) && (p.Effect2 == 2955) && !g_levelScreenFader.isFadingIn())\n            g_levelScreenFader.setupFader(2, 0, 65, ScreenFader::S_FADE);\n\n        if(p.Effect2 == 2920)\n        {\n            if(w.MapWarp)\n            {\n                LevelBeatCode = BEATCODE_WARP;\n\n                if(!(w.MapX == -1 && w.MapY == -1))\n                {\n                    WorldPlayer[1].Location.X = w.MapX;\n                    WorldPlayer[1].Location.Y = w.MapY;\n\n                    for(int l = 1; l <= numWorldLevels; ++l)\n                    {\n                        if(CheckCollision(WorldPlayer[1].Location, WorldLevel[l].Location))\n                        {\n                            WorldLevel[l].Active = true;\n                            curWorldLevel = l;\n                        }\n                    }\n                }\n            }\n            EndLevel = true;\n            return;\n        }\n    }\n}\n\nstatic inline bool checkWarp(Warp_t &warp, int B, Player_t &plr, int A, bool backward)\n{\n    bool onGround = !warp.stoodRequired || (plr.Pinched.Bottom1 == 2 || plr.Slope != 0 || plr.StandingOnNPC != 0);\n\n    const auto &entrance      = backward ? warp.Exit        : warp.Entrance;\n    const auto &exit          = backward ? warp.Entrance    : warp.Exit;\n    const auto &direction     = backward ? warp.Direction2  : warp.Direction;\n\n    if(!CheckCollision(plr.Location, entrance))\n        return false; // continue\n\n    // moved from SuperWarp so that we can stop WarpCD for portal warps until collision is done\n    if(plr.WarpCD > 0)\n    {\n        // if a portal warp is the most recent warp used by player A, and they are still colliding with it, don't reset WarpCD until the collision is over.\n        if(warp.Effect == 3 && B == plr.Warp)\n            plr.WarpCD = 10;\n\n        return false;\n    }\n\n    plr.ShowWarp = B;\n\n    if(warp.LevelEnt)\n        return false;\n\n    // can't enter pipe/door warps from maze zone\n    if(plr.CurMazeZone && (warp.Effect == 1 || warp.Effect == 2))\n        return false;\n\n    bool canWarp = false;\n\n    if(warp.Effect == 3) // Portal\n        canWarp = true;\n    else if(direction == 1 && plr.Controls.Up) // Pipe\n    {\n        if(WarpCollision(plr.Location, entrance, direction) && (warp.Effect != 2 || onGround))\n            canWarp = true;\n    }\n    else if(direction == 2 && plr.Controls.Left)\n    {\n        if(WarpCollision(plr.Location, entrance, direction) && onGround)\n            canWarp = true;\n    }\n    else if(direction == 3 && plr.Controls.Down)\n    {\n        if(WarpCollision(plr.Location, entrance, direction) && onGround)\n            canWarp = true;\n    }\n    else if(direction == 4 && plr.Controls.Right)\n    {\n        if(WarpCollision(plr.Location, entrance, direction) && onGround)\n            canWarp = true;\n    }\n    // NOTE: Would be correct to move this up, but leave this here for a compatibility to keep the same behavior\n    else if(warp.Effect == 0) // Instant\n        canWarp = true;\n\n    if(!canWarp)\n        return false;\n\n    if(warp.Stars > numStars)\n    {\n        bool do_swap_frame = (g_config.fix_visual_bugs && warp.Effect == 1 && direction == 3 && plr.Duck && plr.SwordPoke == 0);\n        int old_plr_frame = plr.Frame;\n\n        if(do_swap_frame)\n        {\n            // Show the duck frame only when attempting to go down\n            plr.Frame = (plr.Character == 5) ? 5 : 7;\n        }\n\n        if(warp.StarsMsg == STRINGINDEX_NONE)\n            MessageText = fmt::format_ne(g_gameStrings.warpNeedStarCount, warp.Stars, LanguageFormatNumber(warp.Stars, g_gameInfo.wordStarAccusativeSingular, g_gameInfo.wordStarAccusativeDual_Cnt, g_gameInfo.wordStarAccusativePlural), g_gameInfo.wordStarAccusativeDual_Cnt);\n        else\n            MessageText = GetS(warp.StarsMsg);\n\n        // store all warp-local vars\n        PauseInit(PauseCode::Message, A);\n        g_gameLoopInterrupt.site = GameLoopInterrupt::UpdatePlayer_SuperWarp;\n        g_gameLoopInterrupt.A = A;\n        g_gameLoopInterrupt.B = B;\n        g_gameLoopInterrupt.C = old_plr_frame;\n        g_gameLoopInterrupt.bool4 = do_swap_frame;\n\n        // return true to break out of the loop\n        return true;\n    }\n\n    plr.Slide = false;\n\n    if(warp.Effect != 3)\n        plr.Stoned = false;\n\n    if(warp.Locked)\n    {\n        // if player has a key, consume it and allow unlocking the warp\n        if(plr.HoldingNPC > 0 && NPC[plr.HoldingNPC].Type == NPCID_KEY)\n        {\n            NPC[plr.HoldingNPC].Killed = 9;\n            NPCQueues::Killed.push_back(plr.HoldingNPC);\n\n            NewEffect(EFFID_SMOKE_S3, NPC[plr.HoldingNPC].Location);\n        }\n        else if(plr.Mount == 3 && plr.YoshiNPC > 0 && NPC[plr.YoshiNPC].Type == NPCID_KEY)\n        {\n            NPC[plr.YoshiNPC].Killed = 9;\n            NPCQueues::Killed.push_back(plr.YoshiNPC);\n            plr.YoshiNPC = 0;\n        }\n        else if(plr.HasKey)\n        {\n            plr.HasKey = false;\n        }\n        // otherwise, don't allow unlocking\n        else\n            return false;\n\n        // if player can still warp, unlock the warp\n        warp.Locked = false;\n\n        // remove warp lock icon\n        int allBGOs = numBackground + numLocked;\n        for(int C = numBackground + 1; C <= allBGOs; C++)\n        {\n            if(Background[C].Type == 98)\n            {\n                if(CheckCollision(entrance, Background[C].Location) ||\n                   (warp.twoWay && CheckCollision(exit, Background[C].Location)))\n                {\n                    // this makes Background[C] disappear and never reappear\n                    Background[C].Layer = LAYER_NONE;\n                    Background[C].Hidden = true;\n                    syncLayers_BGO(C);\n                }\n            }\n        }\n    }\n\n    // execute the warp!\n    plr.AquaticSwim = false;\n    UnDuck(Player[A]);\n    plr.YoshiTongueLength = 0;\n    plr.MountSpecial = 0;\n    plr.FrameCount = 0;\n    plr.TailCount = 0;\n    plr.CanFly = false;\n    plr.CanFly2 = false;\n    plr.RunCount = 0;\n\n    s_WarpReleaseItems(warp, A, backward);\n\n    if(warp.Effect != 3) // Don't zero speed when passing a portal warp\n    {\n        plr.Location.SpeedX = 0;\n        plr.Location.SpeedY = 0;\n    }\n\n    if(warp.eventEnter != EVENT_NONE)\n        TriggerEvent(warp.eventEnter, A);\n\n    if(warp.Effect == 0 || warp.Effect == 3) // Instant / Portal\n    {\n        if(warp.Effect == 3)\n            plr.Warp = B;\n\n        if(warp.Effect == 3 && (warp.level != STRINGINDEX_NONE || warp.MapWarp))\n        {\n            plr.WarpBackward = backward;\n            s_CheckWarpLevelExit(plr, warp, 2921, 2921);\n\n            return true;\n        }\n\n        plr.WarpCD = (warp.Effect == 3) ? 10 : 50;\n\n        num_t off_x = s_warp_offset_x(plr.Location, entrance, exit);\n        num_t off_y = s_warp_offset_y(plr.Location, entrance, exit) - 0.1_n;\n\n        // special logic to keep location correct in maze zones\n        if(plr.CurMazeZone)\n        {\n            off_x = plr.Location.X - entrance.X;\n            off_y = plr.Location.Y - entrance.Y;\n            plr.WarpCD = 80;\n        }\n\n        plr.Location.X = exit.X + off_x;\n        plr.Location.Y = exit.Y + off_y;\n        CheckSection(A);\n\n        const Screen_t& screen = ScreenByPlayer(A);\n        int vscreen_A = vScreenIdxByPlayer(A);\n        bool is_shared_screen = (screen.Type == 3);\n        bool do_tele = is_shared_screen && !vScreenCollision(vscreen_A, exit);\n\n        // teleport other players using the instant/portal warp in shared screen mode\n        if(do_tele)\n        {\n            for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n            {\n                int o_A = screen.players[plr_i];\n                if(o_A == A)\n                    continue;\n\n                Player_t& o_p = Player[o_A];\n\n                // revive someone in wings and bring to warp\n                if(o_p.Effect == PLREFF_COOP_WINGS)\n                    o_p.Dead = false;\n\n                // in the mouth of an onscreen player's Pet?\n                bool in_onscreen_pet = !warp.NoYoshi && InOnscreenPet(o_A, screen);\n\n                if(!o_p.Dead && o_p.TimeToLive == 0 && !in_onscreen_pet)\n                {\n                    RemoveFromPet(o_A);\n\n                    o_p.Location.X = exit.X + (exit.Width - o_p.Location.Width) / 2;\n                    o_p.Location.Y = exit.Y + exit.Height - o_p.Location.Height - 0.1_n;\n\n                    // override if in maze zone\n                    if(plr.CurMazeZone)\n                    {\n                        o_p.CurMazeZone = plr.CurMazeZone;\n                        o_p.MazeZoneStatus = plr.MazeZoneStatus;\n                        o_p.Location.X = plr.Location.X;\n                        o_p.Location.Y = plr.Location.Y;\n                    }\n\n                    CheckSection(o_A);\n\n                    if(warp.Effect != 3) // Don't zero speed when passing a portal warp\n                    {\n                        o_p.Location.SpeedX = 0;\n                        o_p.Location.SpeedY = 0;\n                    }\n\n                    o_p.Vine = plr.Vine;\n                    o_p.WarpCD = (warp.Effect == 3) ? 10 : 50;\n\n                    // put other player in no-collide mode\n                    o_p.Effect = PLREFF_NO_COLLIDE;\n                    o_p.Effect2 = A;\n                }\n            }\n\n            // update position of any players in pets\n            s_FixPlayersInPets(screen);\n\n            GetvScreenAuto(vScreen[vscreen_A]);\n        }\n\n        if(warp.eventExit != EVENT_NONE)\n            TriggerEvent(warp.eventExit, A);\n\n        return true; // break\n    }\n    else if(warp.Effect == 1) // Pipe\n    {\n        PlaySoundSpatial(SFX_Warp, plr.Location);\n        plr.Effect = PLREFF_WARP_PIPE;\n        if(g_config.fix_fairy_stuck_in_pipe)\n            plr.Effect2 = 0;\n        plr.Warp = B;\n        plr.WarpBackward = backward;\n//                        if(nPlay.Online && A == nPlay.MySlot + 1)\n//                            Netplay::sendData Netplay::PutPlayerLoc(nPlay.MySlot) + \"1j\" + std::to_string(A) + \"|\" + plr.Warp + LB;\n    }\n    else if(warp.Effect == 2) // Door\n    {\n        PlaySoundSpatial(SFX_Door, plr.Location);\n        plr.Effect = PLREFF_WARP_DOOR;\n\n        if(g_config.fix_fairy_stuck_in_pipe)\n            plr.Effect2 = 0;\n\n        plr.Warp = B;\n        plr.WarpBackward = backward;\n//                        if(nPlay.Online && A == nPlay.MySlot + 1)\n//                            Netplay::sendData Netplay::PutPlayerLoc(nPlay.MySlot) + \"1j\" + std::to_string(A) + \"|\" + plr.Warp + LB;\n        plr.Location.X = entrance.X + s_warp_offset_x(plr.Location, entrance, entrance);\n        plr.Location.Y = entrance.Y + entrance.Height - plr.Location.Height;\n\n        bool same_section = SectionCollision(plr.Section, static_cast<Location_t>(exit));\n        bool do_scroll = (warp.transitEffect == LevelDoor::TRANSIT_SCROLL) && same_section;\n\n        s_TriggerDoorEffects(static_cast<Location_t>(entrance));\n\n        if(!do_scroll)\n            s_TriggerDoorEffects(static_cast<Location_t>(exit));\n    }\n\n    return false; // continue\n}\n\nvoid SuperWarp(const int A)\n{\n    auto &plr = Player[A];\n    int B = 1;\n\n    if(g_gameLoopInterrupt.site != GameLoopInterrupt::None)\n    {\n        // clear message text\n        MessageText.clear();\n\n        // Restore previous frame if needed\n        if(g_gameLoopInterrupt.bool4)\n            plr.Frame = g_gameLoopInterrupt.C;\n\n        // check the warp after the one that triggered the message\n        B = g_gameLoopInterrupt.B + 1;\n\n        // reset the g_gameLoopInterrupt state\n        g_gameLoopInterrupt.site = GameLoopInterrupt::None;\n    }\n\n    if(plr.Mount == 2)\n        return;\n\n    for(; B <= numWarps; B++)\n    {\n        auto &warp = Warp[B];\n\n        if(warp.Hidden)\n            continue;\n\n        // In normal mode, ignore pounds only for pipe / door warps. In compat mode, ignore pounds for all warps.\n        bool ground_pound = plr.GroundPound || plr.GroundPound2;\n        bool skip_pounds = !g_config.fix_pound_skip_warp || warp.Effect == 1 || warp.Effect == 2;\n        if(ground_pound && skip_pounds)\n            continue;\n\n        if(checkWarp(warp, B, plr, A, false))\n            break;\n\n        if(warp.twoWay) // Check the same warp again if two-way\n        {\n            if(checkWarp(warp, B, plr, A, true))\n                break;\n        }\n    }\n\n    if(plr.WarpCD > 0)\n        plr.WarpCD--;\n}\n"
  },
  {
    "path": "src/player.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <cmath>\n#include <ctime>\n#include <pge_delay.h>\n#include <Logger/logger.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"globals.h\"\n#include \"player.h\"\n#include \"player/player_effect.h\"\n#include \"player/player_update_priv.h\"\n#include \"graphics.h\"\n#include \"collision.h\"\n#include \"npc.h\"\n#include \"npc_id.h\"\n#include \"eff_id.h\"\n#include \"npc_traits.h\"\n#include \"sound.h\"\n#include \"game_main.h\"\n#include \"effect.h\"\n#include \"blocks.h\"\n#include \"editor.h\"\n#include \"layers.h\"\n#include \"config.h\"\n#include \"phys_env.h\"\n#include \"main/level_file.h\"\n#include \"main/game_globals.h\"\n#include \"main/trees.h\"\n#include \"main/menu_main.h\"\n#include \"main/level_medals.h\"\n#include \"core/render.h\"\n#include \"core/events.h\"\n#include \"script/luna/lunacounter.h\"\n\n#include \"npc/npc_queues.h\"\n\n#include \"controls.h\"\n\n\n//void WaterCheck(const int A);\n//// Private Sub Tanooki(A As Integer)\n//void Tanooki(const int A);\n//// Private Sub PowerUps(A As Integer)\n//void PowerUps(const int A);\n//// Private Sub SuperWarp(A As Integer)\n//void SuperWarp(const int A);\n//// Private Sub PlayerCollide(A As Integer)\n//void PlayerCollide(const int A);\n//// Private Sub PlayerEffects(A As Integer)\n//void PlayerEffects(const int A);\n\n\nstatic void setupPlayerAtCheckpoints(NPC_t &npc, Checkpoint_t &cp)\n{\n    Location_t tempLocation;\n    int B;\n    int C;\n    tempLocation = npc.Location;\n    tempLocation.Height = 600;\n\n    C = 0;\n    for(int bi : treeBlockQuery(tempLocation, SORTMODE_COMPAT))\n    {\n        if(CheckCollision(tempLocation, Block[bi].Location))\n        {\n            if(C == 0)\n                C = bi;\n            else\n            {\n                if(Block[bi].Location.Y < Block[C].Location.Y)\n                    C = bi;\n            }\n        }\n    }\n\n    for(B = 1; B <= numPlayers; B++)\n    {\n        Player[B].Location.Y = Block[C].Location.Y - Player[B].Location.Height;\n        Player[B].Location.X = npc.Location.X + (npc.Location.Width - Player[B].Location.Width) / 2;\n        CheckSection(B);\n        pLogDebug(\"Restore player %d at checkpoint ID=%d by X=%d, Y=%d\",\n                  B, cp.id, (int)Player[B].Location.X, (int)Player[B].Location.Y);\n    }\n\n    if(numPlayers > 1 && g_config.multiplayer_pause_controls && !g_ClonedPlayerMode)\n    {\n        for(B = 1; B <= numPlayers; B++)\n            DodgePlayers(B);\n    }\n    else if(numPlayers > 1)\n    {\n        Player[1].Location.X -= 16;\n        Player[2].Location.X += 16;\n    }\n}\n\nstatic void setupCheckpoints()\n{\n    if(Checkpoint != FullFileName || Checkpoint.empty())\n    {\n        if(!IsHubLevel && !LevelSelect)\n        {\n            pLogDebug(\"Clear check-points at SetupPlayers()\");\n            Checkpoint.clear();\n            CheckpointsList.clear();\n            g_curLevelMedals.reset_checkpoint();\n        }\n        return;\n    }\n\n    // restore medals from checkpoint\n    g_curLevelMedals.resume_from_checkpoint();\n\n    pLogDebug(\"Trying to restore %zu checkpoints...\", CheckpointsList.size());\n    if(!g_config.fix_vanilla_checkpoints && CheckpointsList.empty())\n    {\n        pLogDebug(\"Using legacy algorithm\");\n        CheckpointsList.push_back(Checkpoint_t());\n    }\n    for(int cpId = 0; cpId < int(CheckpointsList.size()); cpId++)\n    {\n        auto &cp = CheckpointsList[size_t(cpId)];\n\n        for(int A = 1; A <= numNPCs; A++)\n        {\n            if(NPC[A].Type != NPCID_CHECKPOINT)\n                continue;\n\n            if(g_config.fix_vanilla_checkpoints && cp.id != NPC[A].Special)\n                continue;\n\n            NPC[A].Killed = 9;\n            NPCQueues::Killed.push_back(A);\n\n            // found a last id, leave player here\n            if(!g_config.fix_vanilla_checkpoints || cpId == int(CheckpointsList.size() - 1))\n            {\n                setupPlayerAtCheckpoints(NPC[A], cp);\n                if(g_config.fix_vanilla_checkpoints)\n                    break;// Stop to find NPCs\n            }\n        }// for NPCs\n\n        if(!g_config.fix_vanilla_checkpoints)\n            break;\n    } // for Check points\n}\n\nstruct PlayerStartInfo_t\n{\n    static constexpr int start_pos_count = 2;\n    std::array<uint8_t, start_pos_count> players_at_start{};\n    std::array<uint8_t, c_screenCount> start_for_screen{}; // used by shared screens only\n};\n\nstatic void s_PlacePlayerAtStart(int A, PlayerStartInfo_t& player_start_info)\n{\n    /**************************************\n    ** (1) pick which start point to use **\n    **************************************/\n    int use_start = -1;\n\n    // check for shared screen\n    const int plr_screen_i = ScreenIdxByPlayer(A);\n    const Screen_t& plr_screen = ScreenByPlayer(A);\n    if(plr_screen.Type == ScreenTypes::SharedScreen)\n        use_start = (int)player_start_info.start_for_screen[plr_screen_i] - 1;\n\n    // if not forced by shared screen, choose start with the least players\n    if(use_start == -1)\n    {\n        // default to initial start\n        use_start = 0;\n\n        // check whether a later start has fewer players\n        for(int start = 1; start < PlayerStartInfo_t::start_pos_count; start++)\n        {\n            if(PlayerStart[start + 1].isNull())\n                continue;\n\n            if(player_start_info.players_at_start[start] < player_start_info.players_at_start[use_start])\n                use_start = start;\n        }\n\n        // add either the player alone, or all players on the player's screen, to the count\n        if(plr_screen.Type == ScreenTypes::SharedScreen)\n        {\n            player_start_info.players_at_start[use_start] += plr_screen.player_count;\n            player_start_info.start_for_screen[plr_screen_i] = use_start + 1;\n        }\n        else\n            player_start_info.players_at_start[use_start] += 1;\n    }\n\n\n    /*******************************\n    ** (2) basic player placement **\n    *******************************/\n\n    // place at start\n    const auto& ps = PlayerStart[use_start + 1];\n    auto& pLoc = Player[A].Location;\n\n    num_t ps_X = ps.X + (ps.Width - pLoc.Width) / 2;\n    num_t ps_Y = ps.Y + ps.Height - pLoc.Height;\n\n    pLoc.X = ps_X;\n    pLoc.Y = ps_Y;\n    Player[A].Direction = ps.Direction;\n\n\n    /**********************************\n    ** (3) logic to avoid collisions **\n    **********************************/\n\n    // ignore collisions in clone mode\n    if(g_ClonedPlayerMode)\n        return;\n\n    DodgePlayers(A);\n}\n\nstatic void s_makePetMount(int A)\n{\n    const Player_t& p = Player[A];\n\n    numNPCs++;\n\n    NPC[numNPCs].Direction = p.Direction;\n    NPC[numNPCs].Active = true;\n    NPC[numNPCs].TimeLeft = 100;\n\n    if(p.MountType == 1)\n        NPC[numNPCs].Type = NPCID_PET_GREEN;\n    else if(p.MountType == 2)\n        NPC[numNPCs].Type = NPCID_PET_BLUE;\n    else if(p.MountType == 3)\n        NPC[numNPCs].Type = NPCID_PET_YELLOW;\n    else if(p.MountType == 4)\n        NPC[numNPCs].Type = NPCID_PET_RED;\n    else if(p.MountType == 5)\n        NPC[numNPCs].Type = NPCID_PET_BLACK;\n    else if(p.MountType == 6)\n        NPC[numNPCs].Type = NPCID_PET_PURPLE;\n    else if(p.MountType == 7)\n        NPC[numNPCs].Type = NPCID_PET_PINK;\n    else if(p.MountType == 8)\n        NPC[numNPCs].Type = NPCID_PET_CYAN;\n\n    NPC[numNPCs].Location.Height = 32;\n    NPC[numNPCs].Location.Width = 32;\n    NPC[numNPCs].Location.Y = p.Location.Y + p.Location.Height - 32;\n    NPC[numNPCs].Location.X = num_t::floor(p.Location.X + p.Location.Width / 2 - 16);\n    NPC[numNPCs].Location.SpeedY = 0.5_n;\n    NPC[numNPCs].Location.SpeedX = 0;\n    NPC[numNPCs].CantHurt = 10;\n    NPC[numNPCs].CantHurtPlayer = A;\n\n    PlayerThrownNpcMazeCheck(p, NPC[numNPCs]);\n\n    syncLayers_NPC(numNPCs);\n}\n\nstatic void s_makeVehicleMount(int A)\n{\n    const Player_t& p = Player[A];\n\n    numNPCs++;\n\n    NPC[numNPCs].Direction = p.Direction;\n    if(NPC[numNPCs].Direction == 1)\n        NPC[numNPCs].Frame = 4;\n\n    NPC[numNPCs].Frame += SpecialFrame[2];\n    NPC[numNPCs].Active = true;\n    NPC[numNPCs].TimeLeft = 100;\n    NPC[numNPCs].Type = NPCID_VEHICLE;\n\n    NPC[numNPCs].Location.Height = 128;\n    NPC[numNPCs].Location.Width = 128;\n    NPC[numNPCs].Location.Y = num_t::floor(p.Location.Y);\n    NPC[numNPCs].Location.X = num_t::floor(p.Location.X);\n    NPC[numNPCs].Location.SpeedY = 0;\n    NPC[numNPCs].Location.SpeedX = 0;\n\n    NPC[numNPCs].CantHurt = 10;\n    NPC[numNPCs].CantHurtPlayer = A;\n    syncLayers_NPC(numNPCs);\n}\n\nvoid DodgePlayers(int plr_A)\n{\n    auto& pLoc = Player[plr_A].Location;\n    const Screen_t& plr_screen = ScreenByPlayer(plr_A);\n\n    // save current position\n    num_t orig_X = pLoc.X;\n    num_t orig_Y = pLoc.Y;\n\n\n    // check section of current position for later use\n    int cur_section = -1;\n    for(int B = 0; B < numSections; B++)\n    {\n        if(pLoc.X + pLoc.Width >= level[B].X\n            && pLoc.X <= level[B].Width\n            && pLoc.Y + pLoc.Height >= level[B].Y\n            && pLoc.Y <= level[B].Height)\n        {\n            cur_section = B;\n        }\n    }\n\n\n    // check for floor of current position for later use\n    bool orig_has_floor = false;\n    const Location_t orig_floor_check = newLoc(orig_X + pLoc.Width / 2, pLoc.Y + pLoc.Height, 1, 48);\n\n    for(BlockRef_t b_ref : treeBlockQuery(orig_floor_check, SORTMODE_NONE))\n    {\n        const Block_t& b = b_ref;\n        int B = (int)b_ref;\n\n        if(b.Hidden || b.Invis || BlockNoClipping[b.Type])\n            continue;\n\n        if(BlockCheckPlayerFilter(B, plr_A))\n            continue;\n\n        if(CheckCollision(orig_floor_check, b.Location))\n        {\n            orig_has_floor = true;\n            break;\n        }\n    }\n\n\n    // first try to place players backwards from current position, then do forwards if that doesn't work\n    bool forwards_direction = false;\n\n    // this loop repeats each time the player is placed to avoid collisions\n    while(true)\n    {\n        // (a) check for player collision\n        bool hit = false;\n\n        for(int B = 1; B < plr_A; B++)\n        {\n            if(CheckCollision(pLoc, Player[B].Location))\n            {\n                hit = true;\n                break;\n            }\n        }\n\n        if(!hit)\n            break;\n\n\n        // (b) prepare to restore old position on failure\n        bool failed = false;\n\n        num_t old_X = pLoc.X;\n        num_t old_Y = pLoc.Y;\n\n\n        // (c) X logic: move player backwards, and check it hasn't moved off section / off screen\n        constexpr int plr_spacing = 40;\n        const int X_move = (plr_spacing - plr_spacing * 2 * forwards_direction) * Player[plr_A].Direction;\n        pLoc.X -= X_move;\n\n        // check for failures of being outside of section X bounds (partially offscreen)\n        if(!failed && cur_section != -1 && (pLoc.X < level[cur_section].X || pLoc.X + pLoc.Width > level[cur_section].Width))\n            failed = true;\n\n        // also check being too far from start point (Shared Screen mode)\n        if(!failed && plr_screen.Type == ScreenTypes::SharedScreen && num_t::abs(pLoc.X - orig_X) > plr_screen.W * 0.75_n)\n            failed = true;\n\n\n        // (d) Y logic: do floor checks, check player hasn't moved off screen, and confirm the player didn't cross a ceiling\n        constexpr int max_height_add = 160;\n        num_t top_bound = pLoc.Y - max_height_add;\n        bool check_floor = true;\n\n        // perform floor check (move player upwards until they are above blocks)\n        while(!failed && check_floor)\n        {\n            check_floor = false;\n\n            // check the whole range from player's old position to player's new position (prevents crossing walls)\n            num_t left_X = pLoc.X;\n            num_t right_X = pLoc.X + pLoc.Width;\n            if(X_move < 0)\n                right_X -= X_move;\n            else\n                left_X -= X_move;\n\n            const Location_t floor_check_range = newLoc(left_X, pLoc.Y + pLoc.Height, right_X - left_X, pLoc.Height);\n\n            for(BlockRef_t b_ref : treeBlockQuery(floor_check_range, SORTMODE_NONE))\n            {\n                const Block_t& b = b_ref;\n                int B = (int)b_ref;\n\n                if(b.Hidden || b.Invis || BlockIsSizable[b.Type] || BlockOnlyHitspot1[b.Type] || BlockNoClipping[b.Type])\n                    continue;\n\n                if(BlockCheckPlayerFilter(B, plr_A))\n                    continue;\n\n                if(CheckCollision(floor_check_range, b.Location))\n                {\n                    num_t new_Y = blockGetTopYTouching(b, pLoc);\n\n                    if(new_Y - pLoc.Height < pLoc.Y)\n                    {\n                        check_floor = true;\n                        pLoc.Y = new_Y - pLoc.Height;\n\n                        break;\n                    }\n                }\n            }\n\n            // check we didn't go too high\n            if(pLoc.Y < top_bound)\n                failed = true;\n        }\n\n        // perform cliff check (move player downwards until they are on blocks) if the original position had a floor\n        if(!failed && orig_has_floor && pLoc.Y >= old_Y)\n        {\n            bool found_floor = false;\n            num_t top_Y = 0;\n\n            const Location_t new_floor_check = newLoc(pLoc.X, pLoc.Y + pLoc.Height, pLoc.Width, max_height_add);\n\n            for(BlockRef_t b_ref : treeBlockQuery(new_floor_check, SORTMODE_NONE))\n            {\n                const Block_t& b = b_ref;\n                int B = (int)b_ref;\n\n                if(b.Hidden || b.Invis || BlockNoClipping[b.Type])\n                    continue;\n\n                if(BlockCheckPlayerFilter(B, plr_A))\n                    continue;\n\n                if((BlockIsSizable[b.Type] || BlockOnlyHitspot1[b.Type]) && b.Location.Y < new_floor_check.Y)\n                    continue;\n\n                if(CheckCollision(new_floor_check, b.Location))\n                {\n                    num_t new_Y = blockGetTopYTouching(b, new_floor_check);\n\n                    if(!found_floor || new_Y < top_Y)\n                    {\n                        found_floor = true;\n                        top_Y = new_Y;\n                    }\n                }\n            }\n\n            if(found_floor)\n                pLoc.Y = top_Y - pLoc.Height;\n            else\n                failed = true;\n        }\n\n        // check for failures of being outside of section Y bounds (totally offscreen)\n        if(!failed && cur_section != -1 && (pLoc.Y + pLoc.Height < level[cur_section].Y || pLoc.Y > level[cur_section].Height))\n            failed = true;\n\n        // check being too far from original position (Shared Screen mode)\n        if(!failed && plr_screen.Type == ScreenTypes::SharedScreen && num_t::abs(pLoc.Y - orig_Y) > plr_screen.H * 0.75_n)\n            failed = true;\n\n        // check we didn't cross a ceiling block (in the previous column)\n        if(!failed && pLoc.Y < old_Y)\n        {\n            const Location_t ceiling_check = newLoc(old_X + pLoc.Width / 2, pLoc.Y, 1, old_Y - pLoc.Y);\n\n            for(BlockRef_t b_ref : treeBlockQuery(pLoc, SORTMODE_NONE))\n            {\n                const Block_t& b = b_ref;\n                int B = (int)b_ref;\n\n                if(b.Hidden || b.Invis || BlockIsSizable[b.Type] || BlockOnlyHitspot1[b.Type] || BlockNoClipping[b.Type])\n                    continue;\n\n                if(BlockCheckPlayerFilter(B, plr_A))\n                    continue;\n\n                if(CheckCollision(ceiling_check, b.Location))\n                {\n                    failed = true;\n                    break;\n                }\n            }\n        }\n\n        // perform lava check\n        if(!failed)\n        {\n            const Location_t lava_check = newLoc(pLoc.X, pLoc.Y + pLoc.Height, pLoc.Width, 31);\n\n            for(BlockRef_t b_ref : treeBlockQuery(lava_check, SORTMODE_NONE))\n            {\n                const Block_t& b = b_ref;\n                int B = (int)b_ref;\n\n                if(b.Hidden || b.Invis || BlockNoClipping[b.Type])\n                    continue;\n\n                if(BlockCheckPlayerFilter(B, plr_A))\n                    continue;\n\n                if(CheckCollision(lava_check, b.Location))\n                {\n                    if(BlockHurts[b.Type] || BlockKills[b.Type])\n                    {\n                        failed = true;\n                        break;\n                    }\n                }\n            }\n        }\n\n\n        // (d) on failure, first restart and try forwards direction\n        if(failed && !forwards_direction)\n        {\n            pLoc.X = orig_X;\n            pLoc.Y = orig_Y;\n            forwards_direction = true;\n        }\n        // otherwise, restore old position and disable player collisions\n        else if(failed)\n        {\n            pLoc.X = old_X;\n            pLoc.Y = old_Y;\n\n            Player[plr_A].Effect = PLREFF_RESPAWN;\n            Player[plr_A].RespawnY = pLoc.Y;\n            break;\n        }\n    }\n}\n\nvoid SetupPlayers()\n{\n//    Location_t tempLocation;\n//    Controls_t blankControls;\n    int A = 0;\n    int B = 0;\n//    int C = 0;\n\n    InvincibilityTime = 0;\n    FreezeNPCs = false;\n    qScreen = false;\n    qScreen_canonical = false;\n    ForcedControls = false;\n    // online stuff\n    //    if(nPlay.Online)\n    //    {\n    //        for(A = 0; A <= 15; A++)\n    //        {\n    //            nPlay.Player[A].Controls = blankControls;\n    //            nPlay.MyControls = blankControls;\n    //        }\n    //    }\n    //    if(nPlay.Online)\n    //    {\n    //        if(nPlay.Mode == 1)\n    //            nPlay.MySlot = 0;\n    //        else\n    //        {\n    //            for(A = 1; A <= 15; A++)\n    //            {\n    //                if(nPlay.Player[A].IsMe)\n    //                {\n    //                    nPlay.MySlot = A;\n    //                    break;\n    //                }\n    //            }\n    //        }\n    //    }\n\n    // battle mode\n    if(BattleMode)\n    {\n        for(A = 1; A <= numPlayers; A++)\n        {\n            Player[A].State = 2;\n            Player[A].Hearts = 2;\n        }\n        pLogDebug(\"Clear check-points at Battle Mode begining\");\n        Checkpoint.clear();\n        CheckpointsList.clear();\n        g_curLevelMedals.reset_checkpoint();\n    }\n    else\n    {\n        BattleIntro = 0;\n        BattleOutro = 0;\n    }\n\n\n    // new-added struct to handle start points in >2P\n    PlayerStartInfo_t player_start_info; ////// Some sort of corruption here?? Seems unable to select the start correctly. Fix before pushing.\n\n    for(int numPlayersMax = numPlayers, A = 1; A <= numPlayersMax; A++) // set up players\n    {\n        if(Player[A].Character == 0) // player has no character\n        {\n            Player[A].Character = 1; // Sets as Mario\n            if(numPlayers == 2 && A == 2 /*&& nPlay.Online == false*/) // Sets as Luigi\n                Player[A].Character = 2;\n        }\n        //        if(nPlay.Online) // online stuff\n        //        {\n        //            Player[A].State = 2; // Super mario\n        //            Player[A].Mount = 0;\n        //            if(A == nPlay.MySlot + 1)\n        //            {\n        //                if(frmNetplay::optPlayer(2).Value)\n        //                    Player[A].Character = 2;\n        //                else if(frmNetplay::optPlayer(3).Value)\n        //                    Player[A].Character = 3;\n        //                else if(frmNetplay::optPlayer(4).Value)\n        //                    Player[A].Character = 4;\n        //                else\n        //                    Player[A].Character = 1;\n        //            }\n        //        }\n        if(Player[A].State == 0) // if no state it defaults to small mario\n            Player[A].State = 1;\n        // box to hearts\n\n        if(Player[A].Character == 3 || Player[A].Character == 4 || Player[A].Character == 5) // Peach and Toad\n        {\n            if(Player[A].Hearts <= 0)\n                Player[A].Hearts = 1;\n\n            // power up limiter\n            // If (.Character = 3 Or .Character = 4) And .State > 3 And .State <> 7 Then .State = 2\n\n            if(Player[A].Hearts <= 1 && Player[A].State > 1 && Player[A].Character != 5)\n                Player[A].Hearts = 2;\n            if(Player[A].HeldBonus > 0)\n            {\n                Player[A].Hearts += 1;\n                Player[A].HeldBonus = NPCID(0);\n            }\n            if(Player[A].State == 1 && Player[A].Hearts > 1)\n                Player[A].State = 2;\n            if(Player[A].Hearts > 3)\n                Player[A].Hearts = 3;\n            if(Player[A].Mount == 3)\n                Player[A].Mount = 0;\n        }\n        else // Mario and Luigi\n        {\n            if(Player[A].Hearts == 3 && Player[A].HeldBonus == 0)\n                Player[A].HeldBonus = NPCID_POWER_S3;\n            Player[A].Hearts = 0;\n        }\n        if(Player[A].Character == 5)\n            Player[A].Mount = 0;\n\n        Player[A].Direction = 1; // Moved from below to here\n        Player[A].Location.Width = Physics.PlayerWidth[Player[A].Character][Player[A].State]; // set height\n        Player[A].Location.Height = Physics.PlayerHeight[Player[A].Character][Player[A].State]; // set width\n        if(Player[A].State == 1 && Player[A].Mount == 1) // if small and in a shoe then set the height to super mario\n            Player[A].Location.Height = Physics.PlayerHeight[1][2];\n\n        // moved from below to here\n        Player[A].Effect = PLREFF_NORMAL;\n        Player[A].Effect2 = 0;\n        Player[A].RespawnY = 0;\n\n        // modern multiplayer placement code\n        if(g_config.multiplayer_pause_controls)\n        {\n            s_PlacePlayerAtStart(A, player_start_info);\n        }\n        // legacy multiplayer placement code\n        else\n        {\n            if(numPlayers == 2 && A == 2)\n                B = 2;\n            else\n                B = 1;\n\n            if(A == 2 && PlayerStart[B].X == 0 && PlayerStart[B].Y == 0)\n            {\n                Player[A].Location.X = PlayerStart[1].X + (PlayerStart[1].Width - Player[A].Location.Width) / 2;\n                Player[A].Location.Y = PlayerStart[1].Y + PlayerStart[1].Height - Player[A].Location.Height; // - 2\n                Player[A].Direction = PlayerStart[1].Direction; // manually defined direction of player\n            }\n            else\n            {\n                Player[A].Location.X = PlayerStart[B].X + (PlayerStart[B].Width - Player[A].Location.Width) / 2;\n                Player[A].Location.Y = PlayerStart[B].Y + PlayerStart[B].Height - Player[A].Location.Height; // - 2\n                Player[A].Direction = PlayerStart[B].Direction; // manually defined direction of player\n            }\n        }\n\n        Player[A].CanGrabNPCs = GrabAll;\n\n        // reset all variables\n        if(Player[A].Mount == 2)\n            Player[A].Mount = 0;\n        if(Player[A].Character >= 3 && Player[A].Mount == 3)\n            Player[A].Mount = 0;\n\n        Player[A].Slippy = false;\n        Player[A].DoubleJump = false;\n        Player[A].FlySparks = false;\n        Player[A].Quicksand = 0;\n        Player[A].Bombs = 0;\n        Player[A].Wet = 0;\n        Player[A].ShellSurf = false;\n        Player[A].Rolling = false;\n        Player[A].AquaticSwim = false;\n        Player[A].WetFrame = false;\n        Player[A].Slide = false;\n        Player[A].Vine = 0;\n        Player[A].VineNPC = 0;\n        Player[A].VineBGO = 0;\n        Player[A].Fairy = false;\n        Player[A].GrabSpeed = 0;\n        Player[A].GrabTime = 0;\n        Player[A].SwordPoke = 0;\n        Player[A].FireBallCD2 = 0;\n        Player[A].SpinJump = false;\n        Player[A].Stoned = false;\n        Player[A].Slope = 0;\n        Player[A].SpinFireDir = 0;\n        Player[A].SpinFrame = 0;\n        Player[A].YoshiNPC = 0;\n        Player[A].YoshiPlayer = 0;\n        Player[A].YoshiRed = false;\n        Player[A].YoshiBlue = false;\n        Player[A].YoshiYellow = false;\n        Player[A].YoshiBFrame = 0;\n        Player[A].YoshiBFrameCount = 0;\n        Player[A].YoshiTFrame = 0;\n        Player[A].YoshiTFrameCount = 0;\n        Player[A].CanFly = false;\n        Player[A].CanFly2 = false;\n        Player[A].RunCount = 0;\n        Player[A].FlyCount = 0;\n        Player[A].ForceHitSpot3 = false;\n        Player[A].StandUp = false;\n        Player[A].StandUp2 = false;\n        Player[A].TailCount = 0;\n        Player[A].HasKey = false;\n        Player[A].TimeToLive = 0;\n        Player[A].Warp = 0;\n        Player[A].WarpCD = 0;\n        Player[A].WarpBackward = false;\n        Player[A].WarpShooted = false;\n        Player[A].CanPound = false;\n        Player[A].AltRunRelease = false;\n        Player[A].GroundPound = false;\n        Player[A].GroundPound2 = false;\n        Player[A].Duck = false;\n        Player[A].MountSpecial = 0;\n        Player[A].YoshiTongueLength = 0;\n\n//        Player[A].Direction = 1; // Moved to above\n        Player[A].Location.SpeedX = 0;\n        Player[A].Location.SpeedY = 2;\n        Player[A].Frame = 1;\n        Player[A].FrameCount = 0;\n\n        // Player[A].NPCPinched = 0;\n        // Player[A].Pinched1 = 0;\n        // Player[A].Pinched2 = 0;\n        // Player[A].Pinched3 = 0;\n        // Player[A].Pinched4 = 0;\n        Player[A].Pinched = PinchedInfo_t();\n\n        Player[A].StandingOnNPC = 0;\n        Player[A].StandingOnVehiclePlr = 0;\n        Player[A].HoldingNPC = 0;\n        Player[A].Dead = false;\n        //        if(nPlay.Online && nPlay.Mode == 0)\n        //        {\n        //            if(nPlay.Player[A - 1].Active == false)\n        //                Player[A].Dead = true;\n        //        }\n        Player[A].TimeToLive = 0;\n        Player[A].Bumped = false;\n        Player[A].Bumped2 = 0;\n        // Player[A].Effect = PLREFF_NORMAL; // moved above, possibly set in start-pos code\n        // Player[A].Effect2 = 0;\n        Player[A].Immune = 0;\n        Player[A].Immune2 = false;\n\n        // new code to prevent char5 from attacking at spawn (thanks to Sapphire Bullet Bill for the suggestion)\n        if(!LevelEditor && BattleMode && numPlayers > 2)\n            Player[A].Immune = 90;\n\n        Player[A].Jump = 0;\n        Player[A].Frame = 1;\n        Player[A].FrameCount = 0;\n        Player[A].RunRelease = false;\n        Player[A].FloatTime = 0;\n        Player[A].CanFloat = false;\n        Player[A].CurMazeZone = 0;\n\n        if(Player[A].Character == 3)\n            Player[A].CanFloat = true;\n\n        if(Player[A].Character == 3 || Player[A].Character == 4)\n        {\n            if(Player[A].State == 1)\n                Player[A].Hearts = 1;\n            if(Player[A].State > 1 && Player[A].Hearts < 2)\n                Player[A].Hearts = 2;\n        }\n\n        // legacy code for >2P, unused in modern gameplay\n        if(numPlayers > 2 && !GameMenu) // find correct positions without start locations\n        {\n            if(GameOutro)\n            {\n                Player[A].Location = Player[1].Location;\n                Player[A].Location.X += A * 52 - 52;\n            }\n            // many-player code\n            else if(g_ClonedPlayerMode)\n            {\n                Player[A].Location = Player[1].Location;\n                Player[A].Location.SpeedY = dRand() * -12 - 6;\n            }\n        }\n\n        // section check\n        CheckSection_Init(A); // find the section the player is in\n\n        // Set player's direction to left automatically when a start point is located at right side of the section\n        if(Player[A].Location.X + Player[A].Location.Width / 2 > level[Player[A].Section].X + (level[Player[A].Section].Width - level[Player[A].Section].X) / 2)\n            Player[A].Direction = -1;\n\n        //        if(nPlay.Online && A <= 15)\n        //        {\n        //            if(nPlay.Player[A - 1].Active == false && A != 1)\n        //                Player[A].Dead = true;\n        //        }\n        SizeCheck(Player[A]);\n    }\n    //    if(nPlay.Online)\n    //    {\n    //        Netplay::sendData \"1d\" + (nPlay.MySlot + 1) + \"|\" + Player[nPlay.MySlot + 1].Character + \"|\" + Player[nPlay.MySlot + 1].State + LB + Netplay::PutPlayerLoc(nPlay.MySlot + 1);\n    //        StartMusic Player[nPlay.MySlot + 1].Section;\n    //    }\n    UpdateYoshiMusic();\n    if(!LevelSelect)\n        SetupScreens(); // setup the screen depending on how many players there are\n    setupCheckpoints(); // setup the checkpoint and restpore the player at it if needed\n\n    // prepare vScreens for SharedScreen since UpdatePlayer happens before UpdateGraphics\n    for(int screen_i = 0; screen_i < c_screenCount; screen_i++)\n    {\n        Screen_t& screen = Screens[screen_i];\n        if(screen.Type == ScreenTypes::SharedScreen)\n        {\n            // CenterScreens(screen);\n            GetvScreenAuto(screen.vScreen(1));\n        }\n    }\n}\n\nvoid PlayerHurt(const int A)\n{\n    if(GodMode || GameOutro || BattleOutro > 0)\n        return;\n\n    auto &p = Player[A];\n    Location_t tempLocation;\n    int B = 0;\n\n    if(InvincibilityTime && !p.Pinched.Moving)\n        return;\n\n    if(p.Dead || p.TimeToLive > 0 || p.Stoned || p.Immune > 0 || p.Effect > 0)\n        return;\n\n//    if(nPlay.Online) // netplay stuffs\n//    {\n//        if(nPlay.Allow == false && A != nPlay.MySlot + 1)\n//            return;\n//        if(A == nPlay.MySlot + 1)\n//            Netplay::sendData Netplay::PutPlayerLoc(nPlay.MySlot) + \"1a\" + std::to_string(A) + \"|\" + p.State + LB;\n//    }\n    p.DoubleJump = false;\n    p.GrabSpeed = 0;\n    p.GrabTime = 0;\n    p.Slide = false;\n    p.SlideKill = false;\n    p.CanFly = false;\n    p.CanFly2 = false;\n    p.FlyCount = 0;\n    p.RunCount = 0;\n    p.Rolling = false;\n\n    if(p.Fairy)\n    {\n        PlaySoundSpatial(SFX_HeroFairy, p.Location);\n        p.Immune = 30;\n        p.Effect = PLREFF_WAITING;\n        p.Effect2 = 4;\n        p.Fairy = false;\n        p.FairyTime = 0;\n\n        // FIXME: Here is a possible vanilla bug: B is always 0 even at original code\n        SizeCheck(Player[B]);\n\n        NewEffect(EFFID_SMOKE_S5, p.Location);\n        if(p.Character == 5)\n        {\n            p.FrameCount = -10;\n            p.Location.SpeedX = 3 * -p.Direction;\n            p.Location.SpeedY = -7.01_n;\n            p.StandingOnNPC = 0;\n            p.FireBallCD = 20;\n            PlaySoundSpatial(SFX_HeroHurt, p.Location);\n        }\n        return;\n    }\n\n    if(GameMenu)\n    {\n        if(p.State > 1)\n            p.Hearts = 2;\n        else\n            p.Hearts = 1;\n    }\n\n    if(NPC[p.HoldingNPC].Type == NPCID_PLR_FIREBALL)\n        p.HoldingNPC = 0;\n\n    if(LevelMacro == LEVELMACRO_OFF)\n    {\n        if(p.Immune == 0)\n        {\n            Controls::Rumble(A, 250, 0.5);\n\n            if(p.Mount == 1)\n            {\n                p.Mount = 0;\n                PlaySoundSpatial(SFX_Boot, p.Location);\n                UnDuck(Player[A]);\n                tempLocation = p.Location;\n                tempLocation.SpeedX = 5 * -p.Direction;\n                if(p.MountType == 1)\n                    NewEffect(EFFID_GRN_BOOT_DIE, tempLocation);\n                else if(p.MountType == 2)\n                    NewEffect(EFFID_RED_BOOT_DIE, tempLocation);\n                else\n                    NewEffect(EFFID_BLU_BOOT_DIE, tempLocation);\n                p.Location.set_height_floor(Physics.PlayerHeight[p.Character][p.State]);\n                p.Immune = 150;\n                p.Immune2 = true;\n            }\n            else if(p.Mount == 3)\n            {\n                UnDuck(Player[A]);\n                PlaySoundSpatial(SFX_PetHurt, p.Location);\n                p.Immune = 100;\n                p.Immune2 = true;\n                p.CanJump = false;\n                p.Location.SpeedX = 0;\n                if(p.Location.SpeedY > Physics.PlayerJumpVelocity)\n                    p.Location.SpeedY = Physics.PlayerJumpVelocity;\n                p.Jump = 0;\n                p.Mount = 0;\n                p.YoshiBlue = false;\n                p.YoshiRed = false;\n                p.GroundPound = false;\n                p.GroundPound2 = false;\n                p.YoshiYellow = false;\n                p.Dismount = p.Immune;\n                UpdateYoshiMusic();\n\n                // was previously after numNPCs++ but will not touch numNPCs because p.YoshiRed was just set to false\n                if(p.YoshiNPC > 0 || p.YoshiPlayer > 0)\n                    YoshiSpit(A);\n\n                s_makePetMount(A);\n\n                NPC[numNPCs].Special = 1;\n                NPC[numNPCs].Location.Y -= 1;\n\n                p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            }\n            else\n            {\n                if(p.Character == 3 || p.Character == 4)\n                {\n                    p.Hearts -= 1;\n\n                    // 2 hearts left -> lose powerup, drop to big\n                    if(p.Hearts == 2 && p.State != PLR_STATE_SMALL)\n                    {\n                        // TODO: State-dependent moment\n                        if(p.State == PLR_STATE_FIRE || p.State == PLR_STATE_ICE || p.State == PLR_STATE_CYCLONE)\n                        {\n                            p.Effect = (PlayerEffect)(PLREFF_STATE_TO_BIG + p.State);\n                            p.Effect2 = 0;\n                            PlaySoundSpatial(SFX_PlayerShrink, p.Location);\n                        }\n                        else\n                        {\n                            p.State = PLR_STATE_BIG;\n                            p.Immune = 150;\n                            p.Immune2 = true;\n                            PlaySoundSpatial(SFX_PlayerHit, p.Location);\n                        }\n\n                        return;\n                    }\n                    // 0 hearts left -> kill the player\n                    else if(p.Hearts == 0)\n                        p.State = 1;\n                    // 1 heart (or invalid state) -> shrink to small\n                    else\n                        p.State = 2;\n                }\n                else if(p.Character == 5)\n                {\n                    p.Hearts -= 1;\n                    if(p.Hearts > 0)\n                    {\n                        p.State = (p.Hearts == 1) ? 1 : 2;\n//                        if(p.Hearts == 1)\n//                            p.State = 1;\n//                        else\n//                            p.State = 2;\n                        // Always false because of previous setup\n//                        if(p.State < 1)\n//                            p.State = 1;\n                        if(p.Mount == 0)\n                        {\n                            p.FrameCount = -10;\n                            p.Location.SpeedX = 3 * -p.Direction;\n                            p.Location.SpeedY = -7.01_n;\n                            p.FireBallCD = 30;\n                            p.SwordPoke = 0;\n                        }\n                        p.Immune = 150;\n                        p.Immune2 = true;\n                        PlaySoundSpatial(SFX_HeroHurt, p.Location);\n                        return;\n                    }\n                }\n\n                if(p.State > 1)\n                {\n                    PlaySoundSpatial(SFX_PlayerShrink, p.Location);\n                    p.StateNPC = NPCID_NULL;\n                    p.Effect = PLREFF_TURN_SMALL;\n\n                    if(p.State > 2 && g_config.alt_powerdown)\n                    {\n                        p.Effect = (PlayerEffect)(PLREFF_STATE_TO_BIG + p.State);\n                        p.Effect2 = 0;\n                    }\n                }\n                else\n                {\n                    PlayerDead(A);\n#if 0\n                    // dead code, p.Mount and p.HoldingNPC are guaranteed to be set to 0 in PlayerDead\n                    p.HoldingNPC = 0;\n                    if(p.Mount == 2)\n                    {\n                        p.Mount = 0;\n                        numNPCs++;\n                        NPC[numNPCs].Direction = p.Direction;\n                        if(NPC[numNPCs].Direction == 1)\n                            NPC[numNPCs].Frame = 4;\n                        NPC[numNPCs].Frame += SpecialFrame[2];\n                        NPC[numNPCs].Active = true;\n                        NPC[numNPCs].TimeLeft = 100;\n                        NPC[numNPCs].Type = NPCID_VEHICLE;\n                        NPC[numNPCs].Location.Height = 128;\n                        NPC[numNPCs].Location.Width = 128;\n                        NPC[numNPCs].Location.Y = static_cast<int>(floor(static_cast<double>(p.Location.Y)));\n                        NPC[numNPCs].Location.X = static_cast<int>(floor(static_cast<double>(p.Location.X)));\n                        NPC[numNPCs].Location.SpeedY = 0;\n                        NPC[numNPCs].Location.SpeedX = 0;\n                        NPC[numNPCs].CantHurt = 10;\n                        NPC[numNPCs].CantHurtPlayer = A;\n                        syncLayers_NPC(numNPCs);\n\n                        p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                        p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                        p.Location.X += 64 - Physics.PlayerWidth[p.Character][p.State] / 2;\n                        p.ForceHitSpot3 = true;\n                        p.Location.Y = NPC[numNPCs].Location.Y - p.Location.Height;\n\n                        for(int B : NPCQueues::Active.no_change)\n                        {\n                            if(NPC[B].vehiclePlr == A)\n                            {\n                                NPC[B].vehiclePlr = 0;\n                                NPC[B].Location.SpeedY = 0;\n                                NPC[B].Location.Y = NPC[numNPCs].Location.Y - 0.1 - NPC[B].vehicleYOffset;\n                                treeNPCUpdate(B);\n                                if(NPC[B].tempBlock > 0)\n                                    treeNPCSplitTempBlock(B);\n\n                                NPC[B].vehicleYOffset = 0;\n                                if(NPC[B].Type == NPCID_CANNONITEM)\n                                    NPC[B].Special = 0;\n                                if(NPC[B].Type == NPCID_TOOTHY)\n                                {\n                                    NPC[B].Killed = 9;\n                                    NPCQueues::Killed.push_back(B);\n                                    NPC[B].Special = 0;\n                                }\n                                else if(NPC[B].Type == NPCID_TOOTHYPIPE)\n                                    NPC[B].Special = 0;\n                            }\n                        }\n                    }\n#endif\n                }\n            }\n        }\n    }\n}\n\nvoid PlayerDeathEffect(int A)\n{\n    if(Player[A].Character == 1)\n        NewEffect(EFFID_CHAR1_DIE, Player[A].Location, 1, ShadowMode);\n    else if(Player[A].Character == 2)\n        NewEffect(EFFID_CHAR2_DIE, Player[A].Location, 1, ShadowMode);\n    else if(Player[A].Character == 3)\n        NewEffect(EFFID_CHAR3_DIE, Player[A].Location, 1, ShadowMode);\n    else if(Player[A].Character == 4)\n        NewEffect(EFFID_CHAR4_DIE, Player[A].Location, 1, ShadowMode);\n    else if(Player[A].Character == 5)\n    {\n        NewEffect(EFFID_CHAR5_DIE, Player[A].Location, Player[A].Direction, ShadowMode);\n        Effect[numEffects].Location.SpeedX = 2 * -Player[A].Direction;\n    }\n}\n\nvoid PlayerDead(int A)\n{\n    Controls::Rumble(A, 400, 0.8f);\n\n    g_curLevelMedals.on_any_death();\n\n    bool tempBool = false;\n    int B = 0;\n    auto &p = Player[A];\n\n//    if(nPlay.Online) // netplay stuffs\n//    {\n//        if(nPlay.Allow == false && A != nPlay.MySlot + 1)\n//            return;\n//        if(A == nPlay.MySlot + 1)\n//            Netplay::sendData Netplay::PutPlayerLoc(nPlay.MySlot) + \"1b\" + std::to_string(A) + LB;\n//    }\n\n    if(p.Character == 5)\n        PlaySoundSpatial(SFX_HeroDied, p.Location);\n    else\n    {\n        if(BattleMode)\n            PlaySoundSpatial(SFX_PlayerDied2, p.Location);\n        else if(g_ClonedPlayerMode)\n        {\n            for(B = 1; B <= numPlayers; B++)\n            {\n                if(!Player[B].Dead && Player[B].TimeToLive == 0 && A != B)\n                    tempBool = true;\n            }\n            if(tempBool)\n                PlaySoundSpatial(SFX_PlayerDied2, p.Location);\n            else\n                PlaySoundSpatial(SFX_PlayerDied, p.Location);\n        }\n        else\n            PlaySoundSpatial(SFX_PlayerDied, p.Location);\n    }\n\n    if(p.YoshiNPC > 0 || p.YoshiPlayer > 0)\n    {\n        YoshiSpit(A);\n    }\n\n    p.Location.SpeedX = 0;\n    p.Location.SpeedY = 0;\n    p.Hearts = 0;\n    p.Wet = 0;\n    p.WetFrame = false;\n    p.Quicksand = 0;\n    p.Effect = PLREFF_NORMAL;\n    p.Effect2 = 0;\n    p.Fairy = false;\n\n    if(p.Mount == 2)\n    {\n        s_makeVehicleMount(A);\n\n        p.Mount = 0;\n        p.Location.Y -= 32;\n        p.Location.Height = 32;\n        SizeCheck(Player[A]);\n    }\n\n    p.CurMazeZone = 0;\n    p.Mount = 0;\n    p.State = 1;\n    p.HoldingNPC = 0;\n    p.GroundPound = false;\n    p.GroundPound2 = false;\n\n    PlayerDeathEffect(A);\n\n    p.TimeToLive = 1;\n\n    if(CheckLiving() == 0 && !GameMenu && !BattleMode)\n    {\n        g_curLevelMedals.on_all_dead();\n        StopMusic();\n        FreezeNPCs = false;\n    }\n\n    if(A == SingleCoop)\n        SwapCoop();\n}\n\nvoid KillPlayer(const int A)\n{\n    auto &p = Player[A];\n\n    p.Location.SpeedX = 0;\n    p.Location.SpeedY = 0;\n    p.State = 1;\n    p.Stoned = false;\n\n    // p.Pinched1 = 0;\n    // p.Pinched2 = 0;\n    // p.Pinched3 = 0;\n    // p.Pinched4 = 0;\n    // p.NPCPinched = 0;\n    p.Pinched = PinchedInfo_t();\n\n    p.TimeToLive = 0;\n    p.Direction = 1;\n    p.Frame = 1;\n    p.Mount = 0;\n    p.Dead = true;\n    p.Location.X = 0;\n    p.Location.Y = 0;\n    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n\n    if(p.HoldingNPC > 0)\n    {\n        if(NPC[p.HoldingNPC].Type == NPCID_VINE_BUG)\n            NPC[p.HoldingNPC].Projectile = true;\n    }\n\n    p.HoldingNPC = 0;\n    if(BattleMode)\n    {\n        if(BattleLives[A] <= 0)\n        {\n            int other_alive = 0;\n            bool two_others_alive = false;\n            for(int o_A = 1; o_A <= numPlayers; o_A++)\n            {\n                if(o_A == A)\n                    continue;\n\n                if(!Player[o_A].Dead || BattleLives[o_A] > 0)\n                {\n                    if(other_alive != 0)\n                    {\n                        two_others_alive = true;\n                        break;\n                    }\n                    else\n                        other_alive = o_A;\n                }\n            }\n\n            if(!two_others_alive && BattleOutro == 0)\n            {\n                BattleOutro = 1;\n                PlaySound(SFX_GotStar);\n                StopMusic();\n            }\n\n            if(!two_others_alive && BattleWinner == 0)\n                BattleWinner = other_alive;\n        }\n\n        if(A == BattleWinner || (BattleWinner == 0 && BattleLives[A] > 0))\n        {\n            if(BattleLives[A] > 0)\n                BattleLives[A] -= 1;\n\n            PlaySoundSpatial(SFX_Transform, p.Location);\n            p.Frame = 1;\n            p.Location.SpeedX = 0;\n            p.Location.SpeedY = 0;\n            p.Mount = 0;\n            p.State = 2;\n            p.Hearts = 2;\n            p.Effect = PLREFF_NORMAL;\n\n            p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n            p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n\n            // eventually, check for valid starts\n            constexpr int valid_start_count = 2;\n            int use_start = (A - 1) % valid_start_count + 1;\n\n            p.Location.X = PlayerStart[use_start].X + (PlayerStart[use_start].Width - p.Location.Width) / 2;\n            p.Location.Y = PlayerStart[use_start].Y + PlayerStart[use_start].Height - p.Location.Height;\n            p.Direction = 1;\n\n            p.Dead = false;\n            CheckSection(A);\n            if(p.Location.X + p.Location.Width / 2 > level[p.Section].X + (level[p.Section].Width - level[p.Section].X) / 2)\n                p.Direction = -1;\n            p.Immune = 300;\n            NewEffect(EFFID_SMOKE_S4, p.Location);\n            UpdateYoshiMusic();\n        }\n    }\n}\n\nint CheckDead()\n{\n    int A = 0;\n    for(A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Dead && Player[A].State > 0 && Player[A].Character > 0)\n        {\n//            if(nPlay.Online == false)\n            return A;\n//            else\n//            {\n//                if(nPlay.Player[A - 1].Active || A == 1)\n//                    return A;\n//            }\n        }\n    }\n    return 0;\n}\n\nint CheckLiving()\n{\n    int A = 0;\n    for(A = 1; A <= numPlayers; A++)\n    {\n        if(!Player[A].Dead && Player[A].TimeToLive == 0)\n            return A;\n    }\n    return 0;\n}\n\nint CheckNearestLiving(const int A)\n{\n    const Player_t& p = Player[A];\n    const Screen_t& screen = ScreenByPlayer(A);\n\n    int   closest      = 0;\n    num_t closest_dist = 0;\n\n    // first, look on same screen\n    for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n    {\n        int o_A = screen.players[plr_i];\n        const Player_t& o_p = Player[o_A];\n\n        if(o_A != A && !o_p.Dead && o_p.TimeToLive == 0 && o_A != p.YoshiPlayer)\n        {\n            num_t dist = num_t::dist2(o_p.Location.X - p.Location.X, o_p.Location.Y - p.Location.Y);\n\n            if(closest == 0 || closest_dist > dist)\n            {\n                closest = o_A;\n                closest_dist = dist;\n            }\n        }\n    }\n\n    if(closest)\n        return closest;\n\n    // next, look at all players\n    for(int o_A = 1; o_A <= numPlayers; o_A++)\n    {\n        const Player_t& o_p = Player[o_A];\n\n        if(o_A != A && !o_p.Dead && o_p.TimeToLive == 0 && o_A != p.YoshiPlayer)\n        {\n            num_t dist = num_t::dist2(o_p.Location.X - p.Location.X, o_p.Location.Y - p.Location.Y);\n\n            if(closest == 0 || closest_dist > dist)\n            {\n                closest = o_A;\n                closest_dist = dist;\n            }\n        }\n    }\n\n    return closest;\n}\n\nint LivingPlayersLeft()\n{\n    int ret = 0;\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        if(!Player[A].Dead)\n            ret++;\n    }\n\n    return ret;\n}\n\nbool LivingPlayers() // Checks if anybody alive\n{\n    bool ret = false;\n\n    for(int A = 1; A <= numPlayers; A++)\n    {\n        if(!Player[A].Dead)\n        {\n            ret = true;\n            break;\n        }\n    }\n\n    return ret;\n}\n\nvoid ProcessLastDead()\n{\n    if(!g_config.EnableInterLevelFade || BattleMode)\n        return;\n\n    if(LivingPlayersLeft() <= 1)\n    {\n        FadeOutMusic(500);\n        g_levelScreenFader.setupFader(3, 0, 65, ScreenFader::S_FADE);\n    }\n}\n\nstatic void s_gameOver()\n{\n    Lives = 3;\n    Coins = 0;\n    Score = 0;\n    SaveGame();\n    LevelMacro = LEVELMACRO_OFF;\n    LevelMacroCounter = 0;\n    ResetSoundFX();\n    ClearLevel();\n    LevelSelect = true;\n    GameMenu = true;\n    MenuMode = MENU_INTRO;\n    MenuCursor = 0;\n#ifdef ENABLE_ANTICHEAT_TRAP\n    CheaterMustDie = false;\n#endif\n}\n\nvoid EveryonesDead()\n{\n//    int A = 0; // UNUSED\n    if(BattleMode)\n        return;\n\n    StopMusic();\n    LevelMacro = LEVELMACRO_OFF;\n    FreezeNPCs = false;\n\n    // Quit to world map if died on sub-hub\n    if(!NoMap && IsHubLevel && !FileRecentSubHubLevel.empty())\n    {\n        FileRecentSubHubLevel.clear();\n        ReturnWarp = 0;\n        ReturnWarpSaved = 0;\n    }\n\n    // Ensure everything is clear\n    GraphicsClearScreen();\n\n// Play fade effect instead of wait (see ProcessLastDead() above)\n    if(!g_config.EnableInterLevelFade)\n    {\n//    if(MagicHand)\n//        BitBlt frmLevelWindow::vScreen[1].hdc, 0, 0, frmLevelWindow::vScreen[1].ScaleWidth, frmLevelWindow::vScreen[1].ScaleHeight, 0, 0, 0, vbWhiteness;\n        if(!g_config.unlimited_framerate)\n            PGE_Delay(500);\n    }\n\n#ifdef ENABLE_ANTICHEAT_TRAP\n    if(CheaterMustDie)\n    {\n        s_gameOver();\n        return;\n    }\n#endif\n\n    if(g_ClonedPlayerMode)\n        gDeathCounter.MarkDeath();\n\n    if(g_config.modern_lives_system)\n    {\n        g_100s--;\n\n        if(g_100s < 0)\n            Score = 0;\n    }\n    else\n        Lives--;\n\n    if(g_config.modern_lives_system || Lives >= 0)\n    {\n        LevelMacro = LEVELMACRO_OFF;\n        LevelMacroCounter = 0;\n\n        ResetSoundFX();\n        ClearLevel();\n\n        if(RestartLevel)\n        {\n            OpenLevel(FullFileName);\n            LevelSelect = false;\n            LevelRestartRequested = true;\n//            SetupPlayers();\n        }\n        else\n            LevelSelect = true;\n    }\n    else // no more lives\n    {\n// GAME OVER\n        s_gameOver();\n    }\n\n    XEvents::doEvents();\n}\n\nvoid UnDuck(Player_t &p)\n{\n    if(p.AquaticSwim)\n        return;\n\n    if(p.Rolling)\n    {\n        p.Rolling = false;\n        p.Slide = false;\n    }\n\n    if(p.Duck && p.GrabTime == 0) // Player stands up\n    {\n        if(p.Location.SpeedY != 0) // Fixes a block collision bug\n            p.StandUp = true;\n        p.StandUp2 = true;\n        p.Frame = 1;\n        p.FrameCount = 0;\n        p.Duck = false;\n\n        if(p.Mount == 3)\n        {\n            p.Location.set_height_floor((p.State == 1) ? 54 : 60);\n        }\n        else\n        {\n            if(p.State == 1 && p.Mount == 1)\n            {\n                p.Location.Height = Physics.PlayerHeight[1][2];\n                p.Location.Y += -Physics.PlayerHeight[1][2] + Physics.PlayerDuckHeight[1][2];\n            }\n            else\n            {\n                p.Location.set_height_floor(Physics.PlayerHeight[p.Character][p.State]);\n            }\n        }\n\n        SizeCheck(p);\n    }\n}\n\nvoid CheckSection(const int A)\n{\n    // finds out what section the player is in and handles the music for section changes\n    if(LevelSelect)\n        return;\n\n    auto &p = Player[A];\n\n    int oldSection = p.Section;\n    int foundSection_loop = 0;\n\n    for(int B = 0; B < numSections; B++)\n    {\n        if(p.Location.X + p.Location.Width >= level[B].X)\n        {\n            if(p.Location.X <= level[B].Width)\n            {\n                if(p.Location.Y + p.Location.Height >= level[B].Y)\n                {\n                    if(p.Location.Y <= level[B].Height)\n                    {\n                        foundSection_loop = 1;\n\n                        if(oldSection != B /*&& (nPlay.Online == false || nPlay.MySlot == A - 1)*/)\n                        {\n                            p.Section = B;\n\n                            // there was a music update here that used a less strict criterion of never interrupting music tracks below 0\n\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    if(foundSection_loop == 0)\n    {\n        for(int B = 0; B < numSections; B++)\n        {\n            if(p.Location.X + p.Location.Width >= LevelREAL[B].X)\n            {\n                if(p.Location.X <= LevelREAL[B].Width)\n                {\n                    if(p.Location.Y + p.Location.Height >= LevelREAL[B].Y)\n                    {\n                        if(p.Location.Y <= LevelREAL[B].Height)\n                        {\n                            p.Section = B;\n                            foundSection_loop = 2;\n\n                            // there was a music update here that used the stricter criterion of never interrupting music tracks below 0, or 6 or 15\n\n                            // move player to a different player in the section\n                            for(int C = 1; C <= numPlayers; C++)\n                            {\n                                if(Player[C].Section == p.Section && C != A)\n                                {\n                                    p.Location.X = Player[C].Location.X + (Player[C].Location.Width - p.Location.Width) / 2;\n                                    p.Location.Y = Player[C].Location.Y + Player[C].Location.Height - p.Location.Height - 0.01_n;\n                                    break;\n                                }\n                            }\n\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n\n    // audiovisual updates if player's section changed (ignore if menu is active or player is offscreen)\n    if(p.Section != oldSection && !GameMenu && (&ScreenByPlayer(A) == l_screen))\n    {\n        int B = p.Section;\n\n        UpdateSoundFX(B);\n\n        bool boss_track = (curMusic == 6 || curMusic == 15);\n\n        if(curMusic < 0)\n        {\n            // don't interrupt switch music\n        }\n        else if(foundSection_loop == 2 && boss_track)\n        {\n            // don't interrupt boss track (this condition only existed in the second loop above)\n        }\n        // do change music -- normal music change\n        else if(curMusic != bgMusic[B] || (delayMusicIsSet() && bgMusic[B] != 24))\n        {\n            StartMusic(B);\n        }\n        // custom music change\n        else if(bgMusic[B] == 24)\n        {\n            if(oldSection >= 0 && CustomMusic[oldSection] != CustomMusic[p.Section])\n                StartMusic(B);\n        }\n\n        ClearBuffer = true;\n\n#ifdef PGE_MIN_PORT\n        // unload old background when changing sections (this helps prevent texture memory exhaustion)\n        D_pLogDebug(\"Check for background clean-up: oldSection=%d, B=%d\", oldSection, B);\n        if(numPlayers == 1 && oldSection >= 0 && Background2[oldSection] != Background2[B])\n        {\n            D_pLogDebug(\"Trying to unload background texture at old section %d\", oldSection);\n            // (note: GFXBackground2 doesn't always line up exactly with Background2, but this solution still helps)\n            XRender::unloadTexture(GFXBackground2[Background2[oldSection]]);\n        }\n#endif\n    }\n}\n\n// routine moved from SetupPlayers and now used at several other call sites during level start\nvoid CheckSection_Init(const int A)\n{\n    Player[A].Section = -1;\n\n    CheckSection(A);\n\n    if(Player[A].Section == -1)\n    {\n        Player[A].Section = 0;\n        // SetupPlayers originally called CheckSection again here but the result could not have been different\n    }\n}\n\nstatic void s_makeDust(Player_t& p, int dir_offset, Location_t& tempLocation)\n{\n    if(p.SlideCounter <= 0)\n    {\n        tempLocation.Y = p.Location.Y + p.Location.Height - 5;\n        tempLocation.X = p.Location.X + p.Location.Width / 2 - 4 + dir_offset * p.Direction;\n\n        int add_slide = 2;\n        EFFID effid = EFFID_SKID_DUST;\n        if(p.State == PLR_STATE_POLAR && (p.Slippy || p.SlippyWall) && !p.Mount)\n        {\n            effid = EFFID_SPARKLE;\n            add_slide = 12;\n            tempLocation.Y -= 4;\n        }\n\n        NewEffect(effid, tempLocation, 1, ShadowMode);\n\n        p.SlideCounter = 2 + iRand_round(add_slide);\n    }\n}\n\nvoid PlayerFrame(const int A)\n{\n    PlayerFrame(Player[A]);\n}\n\nvoid PlayerFrame(Player_t &p)\n{\n// updates the players GFX\n    Location_t tempLocation;\n//    auto &p = Player[A];\n\n// cause the flicker when he is immune\n    if(p.Effect != PLREFF_NO_COLLIDE)\n    {\n        if(p.Immune > 0)\n        {\n            p.Immune -= 1;\n            if(p.Immune % 3 == 0)\n            {\n                p.Immune2 = !p.Immune2;\n//                if(!p.Immune2)\n//                    p.Immune2 = true;\n//                else\n//                    p.Immune2 = false;\n            }\n        }\n        else\n            p.Immune2 = false;\n    }\n\n    if(p.State == PLR_STATE_POLAR && !p.Mount && p.Slippy && p.Location.SpeedX)\n        s_makeDust(p, 0, tempLocation);\n\n    if(p.State == PLR_STATE_CYCLONE && !p.DoubleJump && (p.Jump || (p.Controls.Down && p.Location.SpeedY > Physics.PlayerTerminalVelocity * 0.9_n)))\n        s_makeDust(p, 2, tempLocation);\n\n// find frames for link\n    if(p.Character == 5)\n    {\n        LinkFrame(p);\n        return;\n    }\n\n// for the grab animation when picking something up from the top\n    if(p.GrabTime > 0)\n    {\n        p.FrameCount += 1;\n        if(p.FrameCount <= 6)\n            p.Frame = 23;\n        else if(p.FrameCount <= 12)\n            p.Frame = 22;\n        else\n        {\n            p.FrameCount = 0;\n            p.Frame = 23;\n        }\n        return;\n    }\n\n    // Rolling frames\n    if(p.Rolling)\n    {\n        if(p.State == PLR_STATE_SHELL)\n        {\n            p.FrameCount = (p.FrameCount + 1) & 15;\n            p.Frame = 16 + p.FrameCount / 4;\n        }\n        else if(p.State == PLR_STATE_POLAR)\n        {\n            p.Frame = 14;\n        }\n        return;\n    }\n\n    // Aquatic frames\n    if(p.AquaticSwim)\n    {\n        if(p.SwimCount)\n        {\n            p.FrameCount = (p.SwimCount & 15) * 4;\n\n            if(p.FrameCount == 60)\n            {\n                if(p.SwimCount / 16 == MAZE_DIR_UP)\n                    p.Frame = 40;\n                else if(p.SwimCount / 16 == MAZE_DIR_DOWN)\n                    p.Frame = 19;\n                else\n                    p.Frame = 16;\n            }\n        }\n        else\n        {\n            p.FrameCount -= (p.Controls.Run) ? 2 : 1;\n\n            if(p.FrameCount < 0)\n            {\n                p.FrameCount = 0;\n\n                if(p.Controls.Left || p.Controls.Right || p.Controls.Up || p.Controls.Down)\n                {\n                    p.FrameCount = 60;\n\n                    if(p.Controls.Left || p.Controls.Right)\n                        p.Frame = 16;\n                    else if(p.Controls.Up)\n                        p.Frame = 40;\n                    else // if(p.Controls.Down)\n                        p.Frame = 19;\n                }\n            }\n        }\n\n        if(p.FrameCount == 32 || p.FrameCount == 48)\n            p.Frame += 1;\n\n        return;\n    }\n\n// statue frames\n    if(p.Stoned)\n    {\n        p.Frame = 0;\n        p.FrameCount = 0;\n        if(p.Location.SpeedX != 0)\n        {\n            if(p.Location.SpeedY == 0 || p.Slope > 0 || p.StandingOnNPC != 0)\n            {\n                if(p.SlideCounter <= 0)\n                {\n                    p.SlideCounter = 2 + iRand_round(2); // p(2) = 25%, p(3) = 50%, p(4) = 25%\n                    tempLocation.Y = p.Location.Y + p.Location.Height - 5;\n                    tempLocation.X = p.Location.X + p.Location.Width / 2 - 4;\n                    NewEffect(EFFID_SKID_DUST, tempLocation, 1, ShadowMode);\n                }\n            }\n        }\n        return;\n    }\n\n// sliding frames\n    if(p.Slide && (p.Character == 1 || p.Character == 2))\n    {\n        if(p.Location.SpeedX != 0)\n        {\n            if(p.Location.SpeedY == 0 || p.Slope > 0 || p.StandingOnNPC != 0)\n            {\n                if(p.SlideCounter <= 0 && p.SlideKill)\n                {\n                    p.SlideCounter = 2 + iRand_round(2);\n                    tempLocation.Y = p.Location.Y + p.Location.Height - 4;\n                    if(p.Location.SpeedX < 0)\n                        tempLocation.X = p.Location.X + p.Location.Width / 2 - 4 + 6;\n                    else\n                        tempLocation.X = p.Location.X + p.Location.Width / 2 - 4 - 6;\n                    NewEffect(EFFID_SKID_DUST, tempLocation, 1, ShadowMode);\n                }\n            }\n        }\n        p.Frame = 24;\n        return;\n    }\n\n// climbing a vine/ladder\n    if(p.Vine > 0)\n    {\n        bool doesPlayerMoves = false;\n\n        if(g_config.fix_climb_bgo_speed_adding && p.VineBGO > 0)\n        {\n            const Layer_t& layer = Layer[Background[p.VineBGO].Layer];\n\n            doesPlayerMoves = !num_t::fEqual_d(p.Location.SpeedX, (num_t)layer.ApplySpeedX) ||\n                               p.Location.SpeedY < (num_t)layer.ApplySpeedY - 0.1_n;\n        }\n        else\n        {\n            doesPlayerMoves = !num_t::fEqual_d(p.Location.SpeedX, NPC[p.VineNPC].Location.SpeedX) ||\n                               p.Location.SpeedY < NPC[p.VineNPC].Location.SpeedY - 0.1_n;\n        }\n\n        if(doesPlayerMoves) // Or .Location.SpeedY > 0.1 Then\n        {\n            p.FrameCount += 1;\n            if(p.FrameCount >= 8)\n            {\n                p.Frame += 1;\n                p.FrameCount = 0;\n            }\n            PlaySoundSpatial(SFX_Climbing, p.Location);\n        }\n\n        if(p.Frame < 25)\n            p.Frame = 26;\n        else if(p.Frame > 26)\n            p.Frame = 25;\n\n        return;\n    }\n\n// this finds the players direction\n    if(!LevelSelect && p.Effect != PLREFF_WARP_PIPE)\n    {\n        if(p.JumpOffWall)\n        {\n            if(p.Location.SpeedX > 0)\n                p.Direction = 1;\n            else\n                p.Direction = -1;\n        }\n        else if(p.State == PLR_STATE_AQUATIC && !p.Mount && p.MountSpecial)\n        {\n            // in the middle of a hop, don't change direction\n        }\n        else if(!(p.Mount == 3 && p.MountSpecial > 0))\n        {\n            if(p.Controls.Left)\n                p.Direction = -1;\n            if(p.Controls.Right)\n                p.Direction = 1;\n        }\n    }\n\n    if(p.Driving && p.StandingOnNPC > 0)\n        p.Direction = NPC[p.StandingOnNPC].DefaultDirection;\n\n    if(p.Fairy)\n        return;\n\n// ducking and holding\n    if(p.HoldingNPC > 0 && p.Duck)\n    {\n        p.Frame = 27;\n        return;\n    }\n\n    p.MountOffsetY = 0;\n\n    // cyclone frames\n    if(p.SpinJump && p.State == PLR_STATE_CYCLONE && !p.HoldingNPC)\n    {\n        if(p.Location.SpeedY > 0)\n            p.SpinFrame += 1;\n        else\n            p.SpinFrame += 2;\n\n        p.SpinFrame = unsigned(p.SpinFrame) % 32;\n\n        if(p.SpinFrame < 16)\n            p.Direction = 1;\n        else\n            p.Direction = -1;\n\n        p.Frame = 16 + p.SpinFrame / 8;\n        if(p.Character == 4)\n        {\n            if(p.Frame == 17)\n                p.Frame = 18;\n            else if(p.Frame == 19)\n                p.Frame = 16;\n        }\n        else\n        {\n            if(p.Frame == 19)\n                p.Frame = 17;\n        }\n    }\n// for the spinjump/shellsurf\n    else if((p.SpinJump || p.ShellSurf) && p.Mount == 0)\n    {\n        if(p.SpinFrame < 4 || p.SpinFrame >= 9)\n            p.Direction = -1;\n        else\n            p.Direction = 1;\n        if(p.ShellSurf)\n        {\n            if(NPC[p.StandingOnNPC].Location.SpeedX > 0)\n                p.Direction = -p.Direction;\n        }\n        p.SpinFrame += 1;\n        if(p.SpinFrame < 0)\n            p.SpinFrame = 14;\n        if(p.SpinFrame < 3)\n        {\n            p.Frame = 1;\n            if(p.HoldingNPC > 0)\n            {\n                if(p.State == 1)\n                    p.Frame = 5;\n                else\n                    p.Frame = 8;\n            }\n            if(p.State == 4 || p.State == 5)\n                p.Frame = 12;\n        }\n        else if(p.SpinFrame < 6)\n            p.Frame = 13;\n        else if(p.SpinFrame < 9)\n        {\n            p.Frame = 1;\n            if(p.HoldingNPC > 0)\n            {\n                if(p.State == 1)\n                    p.Frame = 5;\n                else\n                    p.Frame = 8;\n            }\n            if(p.State == 4 || p.State == 5)\n                p.Frame = 12;\n        }\n        else if(p.SpinFrame < 12 - 1)\n            p.Frame = 15;\n        else\n        {\n            p.Frame = 15;\n            p.SpinFrame = -1;\n        }\n    }\n    else\n    {\n        bool grounded = (p.Location.SpeedY == 0 || p.StandingOnNPC != 0 || p.Slope > 0);\n        bool wetframe = p.WetFrame;\n\n        if(p.CurMazeZone)\n        {\n            grounded = false;\n\n            if(p.State != PLR_STATE_POLAR && p.State != PLR_STATE_AQUATIC)\n                wetframe = true;\n        }\n\n        if(p.State == 1 && (p.Character == 1 || p.Character == 2)) // Small Mario & Luigi\n        {\n            if(p.HoldingNPC == 0) // not holding anything\n            {\n                if(wetframe && !grounded && !p.Duck && p.Quicksand == 0) // swimming\n                {\n                    if(p.CurMazeZone)\n                        p.Frame = 40;\n                    else if(p.Location.SpeedY < 0 || p.Frame == 42 || p.Frame == 43)\n                    {\n                        if(p.Frame != 40 && p.Frame != 42 && p.Frame != 43)\n                            p.FrameCount = 6;\n\n                        p.FrameCount += 1;\n\n                        if(p.FrameCount < 6)\n                            p.Frame = 40;\n                        else if(p.FrameCount < 12)\n                            p.Frame = 42;\n                        else if(p.FrameCount < 18)\n                            p.Frame = 43;\n                        else\n                        {\n                            p.Frame = 43;\n                            p.FrameCount = 0;\n                        }\n                    }\n                    else\n                    {\n                        p.FrameCount += 1;\n\n                        if(p.FrameCount < 10)\n                            p.Frame = 40;\n                        else if(p.FrameCount < 20)\n                            p.Frame = 41;\n                        else\n                        {\n                            p.Frame = 41;\n                            p.FrameCount = 0;\n                        }\n                    }\n                }\n                else // not swimming\n                {\n                    if(grounded || (p.Location.SpeedY > 0 && p.Quicksand > 0))\n                    {\n                        if(p.Location.SpeedX > 0 && (p.Controls.Left || (p.Direction == -1 && p.Bumped)) && p.Effect == PLREFF_NORMAL && p.Quicksand == 0)\n                        {\n                            if(!LevelSelect)\n                            {\n                                if(p.Mount != 2 && !wetframe && !p.Duck)\n                                {\n                                    PlaySoundSpatial(SFX_Skid, p.Location);\n                                    s_makeDust(p, -8, tempLocation);\n                                }\n\n                                p.Frame = 4;\n                            }\n                        }\n                        else if(p.Location.SpeedX < 0 && (p.Controls.Right || (p.Direction == 1 && p.Bumped)) && p.Effect == PLREFF_NORMAL && p.Quicksand == 0)\n                        {\n                            if(!LevelSelect)\n                            {\n                                if(p.Mount != 2 && !wetframe && !p.Duck)\n                                {\n                                    PlaySoundSpatial(SFX_Skid, p.Location);\n                                    s_makeDust(p, -8, tempLocation);\n                                }\n\n                                p.Frame = 4;\n                            }\n                        }\n                        else\n                        {\n                            if(p.Location.SpeedX != 0 && !(p.Slippy && !p.Controls.Left && !p.Controls.Right && p.State != PLR_STATE_POLAR))\n                            {\n                                p.FrameCount += 1;\n\n                                if(p.Location.SpeedX > Physics.PlayerWalkSpeed - 1.5_n || p.Location.SpeedX < -Physics.PlayerWalkSpeed + 1.5_n)\n                                    p.FrameCount += 1;\n\n                                if(p.Location.SpeedX > Physics.PlayerWalkSpeed || p.Location.SpeedX < -Physics.PlayerWalkSpeed)\n                                    p.FrameCount += 1;\n\n                                if(p.Location.SpeedX > Physics.PlayerWalkSpeed + 1 || p.Location.SpeedX < -Physics.PlayerWalkSpeed - 1)\n                                    p.FrameCount += 1;\n\n                                if(p.Location.SpeedX > Physics.PlayerWalkSpeed + 2 || p.Location.SpeedX < -Physics.PlayerWalkSpeed - 2)\n                                    p.FrameCount += 1;\n\n                                if(p.FrameCount >= 10)\n                                {\n                                    p.FrameCount = 0;\n                                    if(p.Frame == 1)\n                                        p.Frame = 2;\n                                    else\n                                        p.Frame = 1;\n                                }\n                            }\n                            else\n                            {\n                                p.Frame = 1;\n                                p.FrameCount = 0;\n                            }\n                        }\n                    }\n                    else if(CanWallJump && (p.Pinched.Left2 == 2 || p.Pinched.Right4 == 2) && p.Mount == 0 && !p.SpinJump && !p.Duck && (!p.SlippyWall || p.State == PLR_STATE_POLAR))\n                    {\n                        s_makeDust(p, 8, tempLocation);\n                        p.Frame = -4;\n                    }\n                    else\n                        p.Frame = 3;\n                }\n            }\n            else\n            {\n                if(grounded)\n                {\n                    if(p.Mount != 2 &&\n                       ((p.Controls.Left && p.Location.SpeedX > 0) || (p.Controls.Right && p.Location.SpeedX < 0)) &&\n                        p.Effect == PLREFF_NORMAL && !p.Duck)\n                    {\n                        PlaySoundSpatial(SFX_Skid, p.Location);\n                        s_makeDust(p, -10, tempLocation);\n                    }\n\n                    if(p.Location.SpeedX != 0)\n                    {\n                        p.FrameCount += 2;\n\n                        if(p.Location.SpeedX > Physics.PlayerWalkSpeed || p.Location.SpeedX < -Physics.PlayerWalkSpeed)\n                            p.FrameCount += 3;\n\n                        if(p.FrameCount >= 10)\n                        {\n                            p.FrameCount = 0;\n                            if(p.Frame == 5)\n                                p.Frame = 6;\n                            else\n                                p.Frame = 5;\n                        }\n                    }\n                    else\n                    {\n                        p.Frame = 5;\n                        p.FrameCount = 0;\n                    }\n                }\n                else\n                    p.Frame = 6;\n            }\n        }\n        // TODO: state-dependent moment\n        else if(p.FrameCount >= 100 && p.FrameCount <= 118 && (p.State == 3 || p.State == 6 || p.State == 7 || p.State == PLR_STATE_POLAR)) // Fire Mario and Luigi\n        {\n            if(p.Duck)\n            {\n                p.FrameCount = 0;\n                p.Frame = 7;\n            }\n            else\n            {\n                if(p.FrameCount <= 106)\n                {\n                    p.Frame = 11;\n                    if(wetframe && p.Quicksand == 0 && p.Location.SpeedY != 0 && p.Slope == 0 && p.StandingOnNPC == 0 && p.Character <= 2)\n                        p.Frame = 43;\n                }\n                else if(p.FrameCount <= 112)\n                {\n                    p.Frame = 12;\n                    if(wetframe && p.Quicksand == 0 && p.Location.SpeedY != 0 && p.Slope == 0 && p.StandingOnNPC == 0 && p.Character <= 2)\n                        p.Frame = 44;\n                }\n                else\n                {\n                    p.Frame = 11;\n                    if(wetframe && p.Quicksand == 0 && p.Location.SpeedY != 0 && p.Slope == 0 && p.StandingOnNPC == 0 && p.Character <= 2)\n                        p.Frame = 43;\n                }\n\n                p.FrameCount += 1;\n\n                if(FlameThrower)\n                    p.FrameCount += 2;\n\n                if(p.FrameCount > 118)\n                    p.FrameCount = 0;\n            }\n        }\n        else if(p.TailCount > 0) // Racoon Mario\n        {\n            if(p.TailCount < 5 || p.TailCount >= 20)\n                p.Frame = 12;\n            else if(p.TailCount < 10)\n                p.Frame = 15;\n            else if(p.TailCount < 15)\n                p.Frame = 14;\n            else\n                p.Frame = 13;\n        }\n        else // Large Mario, Luigi, and Peach\n        {\n            if(p.HoldingNPC == 0 || (p.Effect == PLREFF_WARP_PIPE && p.Character >= 3))\n            {\n                if(wetframe && !grounded && !p.Duck && p.Quicksand == 0)\n                {\n                    if(p.CurMazeZone)\n                    {\n                        if(p.State == PLR_STATE_POLAR || p.State == PLR_STATE_AQUATIC)\n                        {\n                            // no normal swim frames for those states\n                            p.Frame = 5;\n                        }\n                        else\n                            p.Frame = 40;\n                    }\n                    else if(p.Location.SpeedY < 0 || p.Frame == 43 || p.Frame == 44)\n                    {\n                        if(p.Character <= 2)\n                        {\n                            if(p.Frame != 40 && p.Frame != 43 && p.Frame != 44)\n                                p.FrameCount = 6;\n                        }\n\n                        p.FrameCount += 1;\n                        if(p.FrameCount < 6)\n                            p.Frame = 40;\n                        else if(p.FrameCount < 12)\n                            p.Frame = 43;\n                        else if(p.FrameCount < 18)\n                            p.Frame = 44;\n                        else\n                        {\n                            p.Frame = 44;\n                            p.FrameCount = 0;\n                        }\n                    }\n                    else\n                    {\n                        p.FrameCount += 1;\n                        if(p.FrameCount < 10)\n                            p.Frame = 40;\n                        else if(p.FrameCount < 20)\n                            p.Frame = 41;\n                        else if(p.FrameCount < 30)\n                            p.Frame = 42;\n                        else if(p.FrameCount < 40)\n                            p.Frame = 41;\n                        else\n                        {\n                            p.Frame = 41;\n                            p.FrameCount = 0;\n                        }\n                    }\n\n                    if(p.Character >= 3)\n                    {\n                        if(p.Frame == 43)\n                            p.Frame = 1;\n                        else if(p.Frame == 44)\n                            p.Frame = 2;\n                        else\n                            p.Frame = 5;\n                    }\n                }\n                else\n                {\n                    if(grounded || (p.Quicksand > 0 && p.Location.SpeedY > 0))\n                    {\n                        if(p.State == PLR_STATE_AQUATIC && !p.Bumped)\n                        {\n                            p.Frame = 1;\n                            p.FrameCount = 0;\n                        }\n                        else if(p.Location.SpeedX > 0 && (p.Controls.Left || (p.Direction == -1 && p.Bumped)) &&\n                           p.Effect == PLREFF_NORMAL && !p.Duck && p.Quicksand == 0)\n                        {\n                            if(!LevelSelect)\n                            {\n                                if(p.Mount != 2 && p.Wet == 0)\n                                {\n                                    PlaySoundSpatial(SFX_Skid, p.Location);\n                                    s_makeDust(p, -6, tempLocation);\n                                }\n                                p.Frame = 6;\n                            }\n                        }\n                        else if(p.Location.SpeedX < 0 && (p.Controls.Right || (p.Direction == 1 && p.Bumped)) &&\n                                p.Effect == PLREFF_NORMAL && !p.Duck && p.Quicksand == 0)\n                        {\n                            if(!LevelSelect)\n                            {\n                                if(p.Mount != 2 && p.Wet == 0)\n                                {\n                                    PlaySoundSpatial(SFX_Skid, p.Location);\n                                    s_makeDust(p, -10, tempLocation);\n                                }\n                                p.Frame = 6;\n                            }\n                        }\n                        else\n                        {\n                            if(p.Location.SpeedX != 0 && !(p.Slippy && !p.Controls.Left && !p.Controls.Right && p.State != PLR_STATE_POLAR) && p.State != PLR_STATE_AQUATIC)\n                            {\n                                p.FrameCount += 1;\n\n                                if(p.Location.SpeedX >= Physics.PlayerWalkSpeed || p.Location.SpeedX <= -Physics.PlayerWalkSpeed)\n                                    p.FrameCount += 1;\n\n                                if(p.Location.SpeedX > Physics.PlayerWalkSpeed + 1.5_n || p.Location.SpeedX < -Physics.PlayerWalkSpeed - 1.5_n)\n                                    p.FrameCount += 1;\n\n                                if(p.FrameCount >= 5 && p.FrameCount < 10)\n                                {\n                                    if(p.CanFly && p.Character != 3)\n                                        p.Frame = 16;\n                                    else\n                                        p.Frame = 1;\n                                }\n                                else if(p.FrameCount >= 10 && p.FrameCount < 15)\n                                {\n                                    if(p.CanFly && p.Character != 3)\n                                        p.Frame = 17;\n                                    else\n                                        p.Frame = 2;\n                                }\n                                else if(p.FrameCount >= 15 && p.FrameCount < 20)\n                                {\n                                    if(p.CanFly && p.Character != 3)\n                                        p.Frame = 18;\n                                    else\n                                        p.Frame = 3;\n                                }\n                                else if(p.FrameCount >= 20)\n                                {\n                                    p.FrameCount -= 20;\n                                    if(p.CanFly && p.Character != 3)\n                                        p.Frame = 17;\n                                    else\n                                        p.Frame = 2;\n                                }\n                            }\n                            else\n                            {\n                                p.Frame = 1;\n                                p.FrameCount = 0;\n                            }\n                        }\n                    }\n                    else if(CanWallJump && (p.Pinched.Left2 == 2 || p.Pinched.Right4 == 2) && p.Mount == 0 && !p.SpinJump && !p.Duck && (!p.SlippyWall || p.State == PLR_STATE_POLAR))\n                    {\n                        s_makeDust(p, 8, tempLocation);\n                        p.Frame = -6;\n                    }\n                    else\n                    {\n                        if(p.CanFly2)\n                        {\n                            if(!p.Controls.Jump && !p.Controls.AltJump)\n                            {\n                                if(p.Location.SpeedY < 0)\n                                    p.Frame = 19;\n                                else\n                                    p.Frame = 21;\n                            }\n                            else\n                            {\n                                p.FrameCount += 1;\n                                if(!(p.Frame == 19 || p.Frame == 20 || p.Frame == 21))\n                                    p.Frame = 19;\n                                if(p.FrameCount >= 5)\n                                {\n                                    p.FrameCount = 0;\n                                    if(p.Frame == 19)\n                                        p.Frame = 20;\n                                    else if(p.Frame == 20)\n                                        p.Frame = 21;\n                                    else\n                                        p.Frame = 19;\n                                }\n                            }\n                        }\n                        else\n                        {\n                            if(p.Location.SpeedY < 0)\n                                p.Frame = 4;\n                            else\n                            {\n                                if((p.State == 4 || p.State == 5) && p.Controls.Jump && !(p.Character == 3 || p.Character == 4))\n                                {\n                                    p.FrameCount += 1;\n                                    if(!(p.Frame == 3 || p.Frame == 5 || p.Frame == 11))\n                                        p.Frame = 11;\n                                    if(p.FrameCount >= 5)\n                                    {\n                                        p.FrameCount = 0;\n                                        if(p.Frame == 11)\n                                            p.Frame = 3;\n                                        else if(p.Frame == 3)\n                                            p.Frame = 5;\n                                        else\n                                            p.Frame = 11;\n                                    }\n                                }\n                                else\n                                    p.Frame = 5;\n                            }\n                        }\n                    }\n                    if(p.Duck)\n                        p.Frame = 7;\n                }\n            }\n            else\n            {\n                if(grounded)\n                {\n                    if(p.Mount != 2 &&\n                       ((p.Controls.Left && p.Location.SpeedX > 0) || (p.Controls.Right && p.Location.SpeedX < 0)) &&\n                       p.Effect == PLREFF_NORMAL && !p.Duck)\n                    {\n                        PlaySoundSpatial(SFX_Skid, p.Location);\n                        s_makeDust(p, -10, tempLocation);\n                    }\n                    if(p.Location.SpeedX != 0)\n                    {\n                        p.FrameCount += 1;\n                        if(p.Location.SpeedX > Physics.PlayerWalkSpeed || p.Location.SpeedX < -Physics.PlayerWalkSpeed)\n                            p.FrameCount += 1;\n                        if(p.FrameCount >= 5 && p.FrameCount < 10)\n                            p.Frame = 8;\n                        else if(p.FrameCount >= 10 && p.FrameCount < 15)\n                            p.Frame = 9;\n                        else if(p.FrameCount >= 15 && p.FrameCount < 20)\n                            p.Frame = 10;\n                        else if(p.FrameCount >= 20)\n                        {\n                            p.FrameCount = 0;\n                            p.Frame = 9;\n                        }\n                    }\n                    else\n                    {\n                        p.Frame = 8;\n                        p.FrameCount = 0;\n                    }\n                }\n                else\n                {\n                    p.Frame = 10;\n                    if(p.Character == 3)\n                        p.Frame = 9;\n                }\n            }\n        }\n\n        if(p.Mount == 1) // Goomba's Shoe\n        {\n            p.MountOffsetY = 0;//-p.Location.SpeedY / 2; FIXME: Verify this didn't broke anything\n\n            if(p.Duck || p.StandingOnNPC != 0)\n                p.MountOffsetY = 0;\n            if(p.Direction == 1)\n                p.MountFrame = 2 + SpecialFrame[1];\n            else\n                p.MountFrame = 0 + SpecialFrame[1];\n\n            p.Frame = 1;\n\n            if(p.State == PLR_STATE_AQUATIC)\n                p.Frame = 8;\n        }\n        else if(p.Mount == 2) // Koopa Clown Car\n        {\n            p.Frame = 1;\n            p.MountFrame = SpecialFrame[2];\n\n            if(p.Direction == 1)\n                p.MountFrame += 4;\n        }\n        else if(p.Mount == 3) // Green Yoshi\n        {\n            p.YoshiBY = 42;\n            p.YoshiBX = 0;\n            p.YoshiTY = 10;\n            p.YoshiTX = 20;\n            p.Frame = 30;\n            p.YoshiBFrame = 0;\n            p.YoshiTFrame = 0;\n            p.MountOffsetY = 0;\n\n            if(p.Location.SpeedY < 0 && p.StandingOnNPC == 0 && p.Slope == 0)\n            {\n                p.YoshiBFrame = 3;\n                p.YoshiTFrame = 2;\n            }\n            else if(p.Location.SpeedY > 0 && p.StandingOnNPC == 0 && p.Slope == 0)\n            {\n                p.YoshiBFrame = 2;\n                p.YoshiTFrame = 0;\n            }\n            else\n            {\n                if(p.Location.SpeedX != 0)\n                {\n                    if(p.Effect == PLREFF_NORMAL)\n                    {\n                        // IMPORTANT: this case was truncation until v1.3.7.1-dev. Confirm that changing to VB6 rounding does not cause any issues.\n                        p.YoshiBFrameCount += 1 + num_t::vb6round(num_t::abs(p.Location.SpeedX * 0.7_r));\n                    }\n\n                    if((p.Direction == -1 && p.Location.SpeedX > 0) || (p.Direction == 1 && p.Location.SpeedX < 0))\n                        p.YoshiBFrameCount = 24;\n\n                    if(p.YoshiBFrameCount < 0)\n                        p.YoshiBFrameCount = 0;\n\n                    if(p.YoshiBFrameCount > 32)\n                    {\n                        p.YoshiBFrame = 0;\n                        p.YoshiBFrameCount = 0;\n                    }\n                    else if(p.YoshiBFrameCount > 24)\n                    {\n                        p.YoshiBFrame = 1;\n                        p.YoshiTX -= 1;\n                        p.YoshiTY += 2;\n                        p.YoshiBY += 1;\n                        p.MountOffsetY += 1;\n                    }\n                    else if(p.YoshiBFrameCount > 16)\n                    {\n                        p.YoshiBFrame = 2;\n                        p.YoshiTX -= 2;\n                        p.YoshiTY += 4;\n                        p.YoshiBY += 2;\n                        p.MountOffsetY += 2;\n                    }\n                    else if(p.YoshiBFrameCount > 8)\n                    {\n                        p.YoshiBFrame = 1;\n                        p.YoshiTX -= 1;\n                        p.YoshiTY += 2;\n                        p.YoshiBY += 1;\n                        p.MountOffsetY += 1;\n                    }\n                    else\n                        p.YoshiBFrame = 0;\n                }\n                else\n                    p.YoshiBFrameCount = 0;\n            }\n\n            if(p.MountSpecial == 1)\n            {\n                if(p.Controls.Up ||\n                   (p.StandingOnNPC == 0 && p.Location.SpeedY != 0 &&\n                    p.Slope == 0 && !p.Controls.Down))\n                {\n                    // .YoshiBFrame = 0\n                    p.YoshiTFrame = 3;\n                    // useless self-assignment code [PVS-Studio]\n                    // p.MountOffsetY = p.MountOffsetY;\n                    p.YoshiTongue.Y += p.MountOffsetY;\n                    //p.YoshiTongue.Y = p.YoshiTongue.Y + p.MountOffsetY;\n                }\n                else\n                {\n                    // defaults\n                    p.YoshiBY = 42;\n                    p.YoshiBX = 0;\n                    p.YoshiTY = 10;\n                    p.YoshiTX = 20;\n                    p.YoshiBFrame = 5;\n                    p.YoshiTFrame = 4;\n                    p.YoshiBY += 8;\n                    p.YoshiTY += 24;\n                    p.YoshiTX += 12;\n                    /*\n                    p.MountOffsetY = 0;\n                    p.MountOffsetY += 8;\n                    */\n                    p.MountOffsetY = 8;\n                }\n            }\n\n            if(p.Duck)\n            {\n                p.Frame = 31;\n                if(p.MountSpecial == 0)\n                    p.YoshiBFrame = 6;\n                p.YoshiBFrameCount = 0;\n            }\n\n            if(p.YoshiTFrameCount > 0)\n            {\n                if(p.YoshiNPC == 0 && p.YoshiPlayer == 0)\n                    p.YoshiTFrameCount += 1;\n\n                if(p.YoshiTFrameCount < 10)\n                    p.YoshiTFrame = 1;\n                else if(p.YoshiTFrameCount < 20)\n                    p.YoshiTFrame = 2;\n                else\n                    p.YoshiTFrameCount = 0;\n            }\n            else if(p.YoshiTFrameCount < 0)\n            {\n                p.YoshiTFrameCount -= 1;\n                if(p.YoshiTFrameCount > -10)\n                    p.YoshiTFrame = 3;\n                else\n                    p.YoshiTFrameCount = 0;\n            }\n\n            if(p.Direction == 1)\n            {\n                p.YoshiTFrame += 5;\n                p.YoshiBFrame += 7;\n            }\n            else\n            {\n                p.YoshiBX = -p.YoshiBX;\n                p.YoshiTX = -p.YoshiTX;\n            }\n\n            if(!p.Duck || p.MountSpecial > 0)\n            {\n                p.MountOffsetY -= (72 - (int)p.Location.Height);\n                p.YoshiBY -= (72 - (int)p.Location.Height);\n                p.YoshiTY -= (72 - (int)p.Location.Height);\n            }\n            else\n            {\n                p.MountOffsetY -= (64 - (int)p.Location.Height);\n                p.YoshiBY -= (64 - (int)p.Location.Height);\n                p.YoshiTY -= (64 - (int)p.Location.Height);\n            }\n\n            p.YoshiBX -= 4;\n            p.YoshiTX -= 4;\n\n            if(p.YoshiBlue)\n            {\n                if(p.Location.SpeedY == 0 || p.StandingOnNPC != 0)\n                    p.YoshiWingsFrame = 1;\n                else if(p.Location.SpeedY < 0)\n                {\n                    p.YoshiWingsFrameCount += 1;\n                    if(p.YoshiWingsFrameCount < 6)\n                        p.YoshiWingsFrame = 1;\n                    else if(p.YoshiWingsFrameCount < 12)\n                        p.YoshiWingsFrame = 0;\n                    else\n                    {\n                        p.YoshiWingsFrameCount = 0;\n                        p.YoshiWingsFrame = 0;\n                    }\n                }\n                else\n                {\n                    p.YoshiWingsFrameCount += 1;\n                    if(p.YoshiWingsFrameCount < 12)\n                        p.YoshiWingsFrame = 1;\n                    else if(p.YoshiWingsFrameCount < 24)\n                        p.YoshiWingsFrame = 0;\n                    else\n                    {\n                        p.YoshiWingsFrameCount = 0;\n                        p.YoshiWingsFrame = 0;\n                    }\n                }\n                if(p.GroundPound)\n                    p.YoshiWingsFrame = 0;\n                if(p.Direction == 1)\n                    p.YoshiWingsFrame += 2;\n            }\n        }\n    }\n\n    if(p.Mount == 1 && p.MountType == 3)\n    {\n        if(p.Location.SpeedY == 0 || p.StandingOnNPC != 0)\n            p.YoshiWingsFrame = 1;\n        else if(p.Location.SpeedY < 0)\n        {\n            p.YoshiWingsFrameCount += 1;\n            if(p.YoshiWingsFrameCount < 6)\n                p.YoshiWingsFrame = 1;\n            else if(p.YoshiWingsFrameCount < 12)\n                p.YoshiWingsFrame = 0;\n            else\n            {\n                p.YoshiWingsFrameCount = 0;\n                p.YoshiWingsFrame = 0;\n            }\n        }\n        else\n        {\n            p.YoshiWingsFrameCount += 1;\n            if(p.YoshiWingsFrameCount < 12)\n                p.YoshiWingsFrame = 1;\n            else if(p.YoshiWingsFrameCount < 24)\n                p.YoshiWingsFrame = 0;\n            else\n            {\n                p.YoshiWingsFrameCount = 0;\n                p.YoshiWingsFrame = 0;\n            }\n        }\n\n        if(p.GroundPound)\n            p.YoshiWingsFrame = 0;\n\n        if(p.Direction == 1)\n            p.YoshiWingsFrame += 2;\n    }\n}\n\nvoid UpdatePlayerBonus(const int A, const NPCID B)\n{\n    auto &p = Player[A];\n\n    // INCORRECT NOTE: I have traced all paths into this code, and it is unreachable if p.Effect != PLREFF_NORMAL\n    // NOTE: this is not true if the player touches two bonuses in the same frame.\n\n    int effect_state = PLR_STATE_SMALL;\n\n    if(p.Effect >= PLREFF_TURN_TO_STATE && p.Effect < PLREFF_TURN_TO_STATE_END)\n        effect_state = p.Effect - PLREFF_TURN_TO_STATE;\n    else if(p.Effect >= PLREFF_GROW_TO_STATE && p.Effect < PLREFF_GROW_TO_STATE_END)\n        effect_state = p.Effect - PLREFF_GROW_TO_STATE;\n    else if(p.Effect == PLREFF_TURN_BIG)\n        effect_state = PLR_STATE_BIG;\n\n    if(p.State != PLR_STATE_SMALL || (effect_state == PLR_STATE_BIG || effect_state == PLR_STATE_FIRE || effect_state == PLR_STATE_LEAF))\n    {\n        if(B == NPCID_POWER_S3 || B == NPCID_POWER_S4 || B == NPCID_POWER_S1 || B == NPCID_POWER_S5)\n        {\n            if(p.HeldBonus == 0)\n                p.HeldBonus = B;\n        }\n        else if((p.State == PLR_STATE_BIG || effect_state == PLR_STATE_BIG) && !(effect_state == PLR_STATE_FIRE || effect_state == PLR_STATE_LEAF))\n        {\n            if(p.HeldBonus == 0)\n            {\n                if(p.StateNPC == NPCID_POWER_S1)\n                    p.HeldBonus = NPCID_POWER_S1;\n                else if(p.StateNPC == NPCID_POWER_S4)\n                    p.HeldBonus = NPCID_POWER_S4;\n                else\n                    p.HeldBonus = NPCID_POWER_S3;\n            }\n        }\n        else\n        {\n            if(p.State == PLR_STATE_FIRE || effect_state == PLR_STATE_FIRE)\n            {\n                if(p.StateNPC == NPCID_FIRE_POWER_S4)\n                    p.HeldBonus = NPCID_FIRE_POWER_S4;\n                else if(p.StateNPC == NPCID_FIRE_POWER_S1)\n                    p.HeldBonus = NPCID_FIRE_POWER_S1;\n                else\n                    p.HeldBonus = NPCID_FIRE_POWER_S3;\n            }\n\n            if(p.State == PLR_STATE_LEAF || effect_state == PLR_STATE_LEAF)\n                p.HeldBonus = NPCID_LEAF_POWER;\n\n            if(p.State == PLR_STATE_STATUE || effect_state == PLR_STATE_STATUE)\n                p.HeldBonus = NPCID_STATUE_POWER;\n\n            if(p.State == PLR_STATE_HEAVY || effect_state == PLR_STATE_HEAVY)\n                p.HeldBonus = NPCID_HEAVY_POWER;\n\n            if(p.State == PLR_STATE_ICE || effect_state == PLR_STATE_ICE)\n            {\n                if(p.StateNPC == NPCID_ICE_POWER_S4)\n                    p.HeldBonus = NPCID_ICE_POWER_S4;\n                else\n                    p.HeldBonus = NPCID_ICE_POWER_S3;\n            }\n\n            // State-dependent moment, extend for states > 7, using the 38A NPCIDs and then our own dedicated range. It's not safe to rely on StateNPC because it doesn't get saved.\n            if(p.State == PLR_STATE_AQUATIC || effect_state == PLR_STATE_AQUATIC)\n                p.HeldBonus = NPCID_AQUATIC_POWER;\n            else if(p.State == PLR_STATE_POLAR || effect_state == PLR_STATE_POLAR)\n                p.HeldBonus = NPCID_POLAR_POWER;\n            else if(p.State == PLR_STATE_CYCLONE || effect_state == PLR_STATE_CYCLONE)\n                p.HeldBonus = NPCID_CYCLONE_POWER;\n            else if(p.State == PLR_STATE_SHELL || effect_state == PLR_STATE_SHELL)\n                p.HeldBonus = NPCID_SHELL_POWER;\n        }\n    }\n\n    if(p.Character == 3 || p.Character == 4 || p.Character == 5)\n        p.HeldBonus = NPCID(0);\n}\n\nvoid TailSwipe(const int plr, bool boo, bool Stab, int StabDir)\n{\n    auto &p = Player[plr];\n    Location_t tailLoc;\n    Location_t tempLoc;\n    Location_t stabLoc;\n\n    // int A = 0;\n    // long long B = 0;\n    // int C = 0;\n    // int64_t fBlock = 0;\n    // int64_t lBlock = 0;\n\n    if(Stab)\n    {\n        if(!p.Duck)\n        {\n            if(StabDir == 1)\n            {\n                tailLoc.Width = 6;\n                tailLoc.Height = 14;\n                tailLoc.Y = p.Location.Y - tailLoc.Height;\n                if(p.Direction == 1)\n                    tailLoc.X = p.Location.X + p.Location.Width - 4;\n                else\n                    tailLoc.X = p.Location.X - tailLoc.Width + 4;\n            }\n            else if(StabDir == 2)\n            {\n                // tailLoc.Width = 8\n                tailLoc.Height = 8;\n                if(p.Location.SpeedY >= 10)\n                    tailLoc.Height = 12;\n                else if(p.Location.SpeedY >= 8)\n                    tailLoc.Height = 10;\n                tailLoc.Y = p.Location.Y + p.Location.Height;\n                // tailLoc.X = .Location.X + .Location.Width / 2 - tailLoc.Width / 2 + (2 * .Direction)\n                tailLoc.Width = p.Location.Width - 2;\n                tailLoc.X = p.Location.X + 1;\n            }\n            else\n            {\n                tailLoc.Width = 38;\n                tailLoc.Height = 6;\n                tailLoc.Y = p.Location.Y + p.Location.Height - 42;\n                if(p.Direction == 1)\n                    tailLoc.X = p.Location.X + p.Location.Width;\n                else\n                    tailLoc.X = p.Location.X - tailLoc.Width;\n            }\n        }\n        else\n        {\n            tailLoc.Width = 38;\n            tailLoc.Height = 8;\n            tailLoc.Y = p.Location.Y + p.Location.Height - 22;\n            if(p.Direction == 1)\n                tailLoc.X = p.Location.X + p.Location.Width;\n            else\n                tailLoc.X = p.Location.X - tailLoc.Width;\n        }\n    }\n    else\n    {\n        tailLoc.Width = 18;\n        tailLoc.Height = 12;\n        tailLoc.Y = p.Location.Y + p.Location.Height - 26;\n        if(p.Direction == 1)\n            tailLoc.X = p.Location.X + p.Location.Width;\n        else\n            tailLoc.X = p.Location.X - tailLoc.Width;\n    }\n\n    if(p.Character == 4) // move tail down for toad\n        tailLoc.Y += 4;\n\n    if(boo) // the bool flag means hit a block\n    {\n        // fBlock = FirstBlock[(tailLoc.X / 32) - 1];\n        // lBlock = LastBlock[((tailLoc.X + tailLoc.Width) / 32.0) + 1];\n        // blockTileGet(tailLoc, fBlock, lBlock);\n\n        for(BlockRef_t block_p : treeFLBlockQuery(tailLoc, SORTMODE_COMPAT))\n        {\n            Block_t& block = *block_p;\n            int A = (int)block_p;\n\n            if(!BlockIsSizable[block.Type] && !block.Hidden && (block.Type != 293 || Stab) && !block.Invis && !BlockNoClipping[block.Type])\n            {\n                if(CheckCollision(tailLoc, block.Location))\n                {\n                    // if(block.ShakeY == 0 && block.ShakeY2 == 0 && block.ShakeY3 == 0)\n                    if(block.ShakeCounter == 0)\n                    {\n                        if(block.Special > 0 || block.Type == 55 || block.Type == 159 || block.Type == 90)\n                            PlaySoundSpatial(SFX_BlockHit, block.Location);\n//                        if(nPlay.Online && plr - 1 == nPlay.MySlot)\n//                            Netplay::sendData Netplay::PutPlayerLoc(nPlay.MySlot) + \"1g\" + std::to_string(plr) + \"|\" + p.TailCount - 1;\n#if XTECH_ENABLE_WEIRD_GFX_UPDATES\n                        UpdateGraphics(true); // FIXME: Why this extra graphics update is here? It causes the lag while whipping blocks by the tail\n#endif\n\n                        BlockHit(A, (StabDir == 2), plr);\n                        //if(StabDir == 2)\n                        //{\n                        //    BlockHit(A, true, plr);\n                        //}\n                        //else\n                        //{\n                        //    BlockHit(A, false, plr);\n                        //}\n\n                        BlockHitHard(A);\n                        if(!Stab)\n                        {\n                            // if(block.ShakeY != 0)\n                            if(block.ShakeCounter != 0)\n                            {\n                                tempLoc.X = (block.Location.X + tailLoc.X + (block.Location.Width + tailLoc.Width) / 2) / 2 - 16;\n                                tempLoc.Y = (block.Location.Y + tailLoc.Y + (block.Location.Height + tailLoc.Height) / 2) / 2 - 16;\n                                NewEffect(EFFID_WHIP, tempLoc);\n                            }\n                            break;\n                        }\n                        else\n                        {\n                            if(StabDir == 2)\n                            {\n                                if(block.Type == 293 || block.Type == 370\n                                    /* || block.ShakeY != 0 || block.ShakeY2 != 0 || block.ShakeY3 != 0 */\n                                    || block.ShakeCounter != 0\n                                    || block.Hidden || BlockHurts[block.Type])\n                                {\n                                    if(BlockHurts[block.Type])\n                                        PlaySoundSpatial(SFX_Spring, block.Location);\n                                    p.Location.Y -= 0.1_n;\n                                    p.Location.SpeedY = Physics.PlayerJumpVelocity;\n                                    p.StandingOnNPC = 0;\n                                    if(p.Controls.Jump || p.Controls.AltJump)\n                                        p.Jump = 10;\n                                }\n                            }\n\n                            if(block.Type == 370)\n                            {\n                                PlaySoundSpatial(SFX_HeroGrass, block.Location);\n                                block.Hidden = true;\n                                block.Layer = LAYER_DESTROYED_BLOCKS;\n                                syncLayersTrees_Block(A);\n                                NewEffect(EFFID_SMOKE_S3, block.Location);\n                                // SpeedY didn't get doubled for SMOKE_S3 in SMBX 1.3\n                                Effect[numEffects].Location.SpeedY = -2;\n                            }\n\n                            // allow Char5 to stab gray bricks when it has heavy power\n                            if(block.Type == 457 && p.State == 6)\n                                SafelyKillBlock(A);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    int numNPCsMax5 = numNPCs;\n\n    // need this complex loop syntax because Active can be modified within it\n    for(int A : NPCQueues::Active.may_erase)\n    {\n        if(A > numNPCsMax5)\n            continue;\n\n        if(NPC[A].Active && NPC[A].Effect == NPCEFF_NORMAL && !(NPCIsAnExit(NPC[A]) || (NPC[A]->IsACoin && !Stab)) &&\n            NPC[A].CantHurtPlayer != plr && !(p.StandingOnNPC == A && p.ShellSurf))\n        {\n            if(NPC[A].Type != NPCID_PLR_FIREBALL && NPC[A].Type != NPCID_PLR_ICEBALL && !(NPC[A].Type == NPCID_BULLET && NPC[A].Projectile) &&\n                NPC[A].Type != NPCID_PET_FIRE && NPC[A].Type != NPCID_GOALTAPE && NPC[A].Type != NPCID_CHECKPOINT)\n            {\n                stabLoc = NPC[A].Location;\n                if(NPC[A]->HeightGFX > NPC[A].Location.Height && NPC[A].Type != NPCID_PLANT_S3 && NPC[A].Type != NPCID_MINIBOSS &&\n                    NPC[A].Type != NPCID_WALL_BUG && NPC[A].Type != NPCID_POWER_S3 && NPC[A].Type != NPCID_BOTTOM_PLANT && NPC[A].Type != NPCID_SIDE_PLANT &&\n                    NPC[A].Type != NPCID_BIG_PLANT && NPC[A].Type != NPCID_PLANT_S1 && NPC[A].Type != NPCID_FIRE_PLANT)\n                {\n                    stabLoc.set_height_floor(NPC[A]->HeightGFX);\n                }\n\n                if(NPC[A].Type == NPCID_ITEM_BURIED && Stab)\n                    stabLoc.Y += -stabLoc.Height;\n\n                if(CheckCollision(tailLoc, stabLoc) && NPC[A].Killed == 0 && NPC[A].TailCD == 0 && !(StabDir != 0 && NPC[A].Type == NPCID_ITEM_BURIED))\n                {\n                    if(Stab)\n                    {\n                        if(StabDir == 2 && ((NPC[A].Type >= NPCID_CARRY_BLOCK_A && NPC[A].Type <= NPCID_CARRY_BLOCK_D) || NPC[A].Type == NPCID_SPRING || NPC[A].Type == NPCID_COIN_SWITCH || NPC[A].Type == NPCID_TIME_SWITCH || NPC[A].Type == NPCID_EARTHQUAKE_BLOCK))\n                        {\n                        }\n                        else\n                        {\n                            vbint_t oldDamage = NPC[A].Damage;\n                            NPCID oldType = NPC[A].Type;\n\n                            if(NPC[A].Type == NPCID_SLIDE_BLOCK && StabDir != 0)\n                            {\n                                NPC[A].Special = 1;\n                                NPC[A].Projectile = true;\n                                NPCHit(A, 3, A);\n                                p.Location.SpeedY = Physics.PlayerJumpVelocity;\n                                p.StandingOnNPC = 0;\n                                if(p.Controls.Jump || p.Controls.AltJump)\n                                    p.Jump = 10;\n                            }\n                            else\n                            {\n                                NPCHit(A, 10, plr);\n                            }\n\n                            if(StabDir == 2 && (NPC[A].Killed == 10 || NPC[A].Damage != oldDamage || NPC[A].Type != oldType))\n                            {\n                                p.Location.SpeedY = Physics.PlayerJumpVelocity;\n                                p.StandingOnNPC = 0;\n                                if(p.Controls.Jump || p.Controls.AltJump)\n                                    p.Jump = 10;\n                            }\n                        }\n                    }\n                    else\n                    {\n                        NPCID oldType = NPC[A].Type;\n                        bool oldProj = NPC[A].Projectile;\n                        num_t oldSpeedY = NPC[A].Location.SpeedY;\n\n                        NPCHit(A, 7, plr);\n\n                        if(NPC[A].Killed > 0 || NPC[A].Type != oldType || NPC[A].Projectile != oldProj || (NPC[A].Location.SpeedY != oldSpeedY))\n                        {\n//                            if(nPlay.Online && plr - 1 == nPlay.MySlot)\n//                                Netplay::sendData Netplay::PutPlayerLoc(nPlay.MySlot) + \"1g\" + std::to_string(plr) + \"|\" + p.TailCount - 1;\n                            tempLoc.X = (NPC[A].Location.X + tailLoc.X + (NPC[A].Location.Width + tailLoc.Width) / 2) / 2 - 16;\n                            tempLoc.Y = (NPC[A].Location.Y + tailLoc.Y + (NPC[A].Location.Height + tailLoc.Height) / 2) / 2 - 16;\n                            NPC[A].BattleOwner = plr;\n                            NewEffect(EFFID_WHIP, tempLoc);\n                        }\n                    }\n\n                    NPC[A].TailCD = 8;\n                }\n            }\n        }\n    }\n\n    if(BattleMode)\n    {\n        for(int A = 1; A <= numPlayers; A++)\n        {\n            if(A != plr)\n            {\n                if(CheckCollision(tailLoc, Player[A].Location) && Player[A].Effect == PLREFF_NORMAL &&\n                    Player[A].Immune == 0 && !Player[A].Dead && Player[A].TimeToLive == 0)\n                {\n                    if(Stab)\n                    {\n                        if(StabDir == 2)\n                        {\n                            p.Location.SpeedY = Physics.PlayerJumpVelocity;\n                            p.StandingOnNPC = 0;\n                            if(p.Controls.Jump || p.Controls.AltJump)\n                                p.Jump = 10;\n                        }\n                        // NEW LOGIC: Char5 shield block -- the logic for Player[A] is based on Char5 shield logic in SpecialNPC\n                        else if(StabDir == 0 && Player[A].Character == 5 && Player[A].Direction != p.Direction\n                            && Player[A].Effect == PLREFF_NORMAL && Player[A].SwordPoke == 0 && !Player[A].Fairy)\n                        {\n                            num_t shield_t = Player[A].Location.Y + Player[A].Location.Height;\n                            shield_t -= (Player[A].Duck) ? 28 : 52;\n\n                            num_t shield_b = shield_t + 24;\n\n                            // Player[A]'s shield blocks attack and pushes p away, canceling p's SwordPoke and creating an opening for attack\n                            if(tailLoc.Y <= shield_b && tailLoc.Y + tailLoc.Height >= shield_t)\n                            {\n                                PlaySoundSpatial(SFX_HeroShield, tailLoc);\n\n                                // knock p and Player[A] back a bit\n                                p.Location.SpeedX = Player[A].Location.SpeedX - 2 * p.Direction;\n                                Player[A].Location.SpeedX += p.Direction;\n                                p.Location.Y -= 1.5_n;\n                                p.Location.SpeedY -= 1.5_n;\n\n                                // cancel stab\n                                p.SwordPoke |= 32;\n                                continue;\n                            }\n                        }\n\n                        PlayerHurt(A);\n                        PlaySoundSpatial(SFX_HeroHit, Player[A].Location);\n                    }\n                    else\n                    {\n                        Player[A].Location.SpeedX = 6 * p.Direction;\n                        Player[A].Location.SpeedY = -5;\n                        PlaySoundSpatial(SFX_Stomp, Player[A].Location);\n                    }\n                }\n            }\n        }\n    }\n\n    if(!Stab)\n    {\n        if(((p.TailCount) % 10 == 0 && !p.SpinJump) || ((p.TailCount) % 5 == 0 && p.SpinJump))\n        {\n            num_t dx = dRand();\n            num_t dy = dRand();\n            NewEffect(EFFID_SPARKLE, newLoc(tailLoc.X + (dx * (int_ok)tailLoc.Width) - 4, tailLoc.Y + (dy * (int_ok)tailLoc.Height)), 1, ShadowMode);\n            Effect[numEffects].Location.SpeedX = (0.5_n + dRand()) * p.Direction;\n            Effect[numEffects].Location.SpeedY = dRand() - 0.5_n;\n        }\n    }\n}\n\nvoid YoshiHeight(const int A)\n{\n    auto &p = Player[A];\n\n    if(p.Mount == 3)\n        p.Location.set_height_floor((p.State == 1) ? 54 : 60);\n}\n\nvoid YoshiEat(const int A)\n{\n    Location_t tempLocation;\n    auto &p = Player[A];\n\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        auto &p2 = Player[B];\n\n        if(B != A && p2.Effect == PLREFF_NORMAL && !p2.Dead && p2.TimeToLive == 0 && p2.Mount == 0)\n        {\n            if(CheckCollision(p.YoshiTongue, p2.Location))\n            {\n                p.YoshiPlayer = B;\n                p2.HoldingNPC = 0;\n                return;\n            }\n        }\n    }\n\n    for(int B : treeNPCQuery(static_cast<Location_t>(p.YoshiTongue), SORTMODE_ID))\n    {\n        auto &n = NPC[B];\n        if(((n->IsACoin && n.Special == 1) || !n->NoYoshi) &&\n           n.Active && ((!n->IsACoin || n.Special == 1) || n.Type == NPCID_RED_COIN) &&\n           !NPCIsAnExit(n) && !n.Generator && !n.Inert && !NPCIsYoshi(n) &&\n            n.Effect != NPCEFF_PET_TONGUE && n.Immune == 0 && n.Type != NPCID_ITEM_BURIED &&\n            !(n.Projectile && n.Type == NPCID_BULLET) && n.HoldingPlayer == 0)\n        {\n            tempLocation = n.Location;\n            // dead code\n#if 0\n            if(n.Type == NPCID_ITEM_BURIED)\n                tempLocation.Y = n.Location.Y - 16;\n#endif\n\n            if(CheckCollision(p.YoshiTongue, tempLocation))\n            {\n                // dead code, check n.Type != 91 condition above\n#if 0\n                if(n.Type == NPCID_ITEM_BURIED)\n                {\n                    if(!NPCTraits[(int)n.Special].NoYoshi)\n                    {\n                        PlaySound(SFX_Grab);\n                        n.Generator = false;\n                        n.Frame = 0;\n                        n.Type = n.Special;\n                        n.Special = 0;\n\n                        if(NPCIsYoshi(n))\n                        {\n                            n.Special = n.Type;\n                            n.Type = 96;\n                        }\n\n                        n.Location.Height = n->THeight;\n                        n.Location.Width = n->TWidth;\n\n                        if(!(n.Type == 21 || n.Type == 22 || n.Type == 26 || n.Type == 31 || n.Type == 32 || n.Type == 35 || n.Type == 49 || NPCIsAnExit(n)))\n                            n.DefaultType = 0;\n\n                        n.Effect = 5;\n                        n.Effect2 = A;\n                        p.YoshiNPC = B;\n\n                        NPCQueues::Unchecked.push_back(B);\n                        treeNPCUpdate(B);\n                    }\n                }\n                else\n#endif\n                if(n.Type == NPCID_ITEM_BUBBLE)\n                {\n                    NPCHit(B, 3, B);\n                }\n                else\n                {\n                    n.Effect = NPCEFF_PET_TONGUE;\n                    n.Effect2 = A;\n                    n.Location.Height = n->THeight;\n                    p.YoshiNPC = B;\n                    treeNPCUpdate(B);\n                }\n\n                if(n.Type == NPCID_VEGGIE_RANDOM)\n                {\n                    n.Type = NPCID(NPCID_VEGGIE_2 + iRand(9));\n                    if(n.Type == NPCID_VEGGIE_RANDOM)\n                        n.Type = NPCID_VEGGIE_1;\n\n                    n.Location.set_width_center(n->TWidth);\n                    n.Location.set_height_center(n->THeight);\n\n                    NPCQueues::Unchecked.push_back(B);\n                    treeNPCUpdate(B);\n                }\n                break;\n            }\n        }\n    }\n}\n\nvoid YoshiSpit(const int A)\n{\n    int B = 0;\n    auto &p = Player[A];\n\n//    if(nPlay.Online && A - 1 == nPlay.MySlot)\n//        Netplay::sendData Netplay::PutPlayerControls(nPlay.MySlot);\n    p.YoshiTFrameCount = -1;\n\n    if(p.YoshiPlayer > 0)\n    {\n        Player[p.YoshiPlayer].Section = p.Section;\n        Player[p.YoshiPlayer].Effect = PLREFF_NORMAL;\n        Player[p.YoshiPlayer].Effect2 = 0;\n        Player[p.YoshiPlayer].Slide = true;\n\n        if(Player[p.YoshiPlayer].State > 1)\n            Player[p.YoshiPlayer].Location.Height = Physics.PlayerDuckHeight[Player[p.YoshiPlayer].Character][Player[p.YoshiPlayer].State];\n        // Player(.YoshiPlayer).Location.Y = Player(.YoshiPlayer).Location.Y - Physics.PlayerDuckHeight(Player(.YoshiPlayer).Character, Player(.YoshiPlayer).State) + Physics.PlayerHeight(Player(.YoshiPlayer).Character, Player(.YoshiPlayer).State)\n        // Player(.YoshiPlayer).Duck = True\n\n        if(p.Controls.Down)\n        {\n            Player[p.YoshiPlayer].Location.X = p.Location.X + p.YoshiTX + Player[p.YoshiPlayer].Location.Width * p.Direction;\n            Player[p.YoshiPlayer].Location.X += 5;\n            Player[p.YoshiPlayer].Location.Y = p.Location.Y + p.Location.Height - Player[p.YoshiPlayer].Location.Height;\n            Player[p.YoshiPlayer].Location.SpeedX = 0 + p.Location.SpeedX * 0.3_r;\n            Player[p.YoshiPlayer].Location.SpeedY = 1 + p.Location.SpeedY * 0.3_r;\n        }\n        else\n        {\n            Player[p.YoshiPlayer].Location.X = p.Location.X + p.YoshiTX + Player[p.YoshiPlayer].Location.Width * p.Direction;\n            Player[p.YoshiPlayer].Location.X += 5;\n            Player[p.YoshiPlayer].Location.Y = p.Location.Y + 1;\n            Player[p.YoshiPlayer].Location.SpeedX = 7 * p.Direction + p.Location.SpeedX * 0.3_r;\n            Player[p.YoshiPlayer].Location.SpeedY = -3 + p.Location.SpeedY * 0.3_r;\n        }\n\n        Player[p.YoshiPlayer].Direction = -p.Direction;\n        Player[p.YoshiPlayer].Bumped = true;\n\n        // Simplified code\n        if(p.CurMazeZone)\n        {\n            Player[p.YoshiPlayer].CurMazeZone = p.CurMazeZone;\n            PlayerThrowItemMaze(p, Player[p.YoshiPlayer].Location, Player[p.YoshiPlayer].MazeZoneStatus);\n        }\n        else\n            PlayerPush(p.YoshiPlayer, (p.Direction == 1) ? 2 : 4);\n        //if(p.Direction == 1)\n        //    PlayerPush(p.YoshiPlayer, 2);\n        //else\n        //    PlayerPush(p.YoshiPlayer, 4);\n\n        p.YoshiPlayer = 0;\n        PlaySoundSpatial(SFX_SpitBossSpit, p.Location);\n    }\n    else\n    {\n        NPC[p.YoshiNPC].RealSpeedX = 0;\n        if(NPC[p.YoshiNPC]->IsAShell)\n            NPC[p.YoshiNPC].Special = 0;\n\n        if((NPC[p.YoshiNPC]->IsAShell || NPCIsABot(NPC[p.YoshiNPC]) || NPC[p.YoshiNPC].Type == NPCID_RAINBOW_SHELL) &&\n           NPC[p.YoshiNPC].Type != NPCID_GLASS_SHELL && p.YoshiRed)\n        {\n            NPC[p.YoshiNPC].Killed = 9;\n            NPCQueues::Killed.push_back(p.YoshiNPC);\n            PlaySoundSpatial(SFX_BigFireball, p.Location);\n            for(B = 1; B <= 3; B++)\n            {\n                numNPCs++;\n                NPC[numNPCs].Direction = p.Direction;\n                NPC[numNPCs].Type = NPCID_PET_FIRE;\n                NPC[numNPCs].Frame = EditorNPCFrame(NPC[numNPCs].Type, NPC[numNPCs].Direction);\n                NPC[numNPCs].Active = true;\n                NPC[numNPCs].Section = p.Section;\n                NPC[numNPCs].TimeLeft = 100;\n                NPC[numNPCs].Effect = NPCEFF_NORMAL;\n                NPC[numNPCs].Location.X = p.Location.X + p.YoshiTX + 32 * p.Direction;\n                NPC[numNPCs].Location.Y = p.Location.Y + p.YoshiTY;\n                NPC[numNPCs].Location.Width = 32;\n                NPC[numNPCs].Location.Height = 32;\n\n                if(B == 1)\n                {\n                    NPC[numNPCs].Location.SpeedY = -0.8_n;\n                    NPC[numNPCs].Location.SpeedX = 5 * p.Direction;\n                }\n                else if(B == 2)\n                {\n                    NPC[numNPCs].Location.SpeedY = 0;\n                    NPC[numNPCs].Location.SpeedX = 5.5_n * p.Direction;\n                }\n                else\n                {\n                    NPC[numNPCs].Location.SpeedY = 0.8_n;\n                    NPC[numNPCs].Location.SpeedX = 5 * p.Direction;\n                }\n\n                syncLayers_NPC(numNPCs);\n            }\n        }\n        else\n        {\n            NPC[p.YoshiNPC].Direction = p.Direction;\n            NPC[p.YoshiNPC].Frame = 0;\n            // this means that the NPC will be killed if it is inside a wall next frame\n            NPC[p.YoshiNPC].WallDeath = 5;\n            NPC[p.YoshiNPC].FrameCount = 0;\n            NPC[p.YoshiNPC].Frame = EditorNPCFrame(NPC[p.YoshiNPC].Type, NPC[p.YoshiNPC].Direction);\n            NPC[p.YoshiNPC].Active = true;\n            NPC[p.YoshiNPC].Section = p.Section;\n            NPC[p.YoshiNPC].TimeLeft = 100;\n            NPC[p.YoshiNPC].Effect = NPCEFF_NORMAL;\n            NPC[p.YoshiNPC].Effect2 = 0;\n            NPC[p.YoshiNPC].Location.X = p.Location.X + p.YoshiTX + 32 * p.Direction;\n            NPC[p.YoshiNPC].Location.Y = p.Location.Y + p.YoshiTY;\n            if(p.Duck)\n                NPC[p.YoshiNPC].Location.Y -= 8;\n            NPC[p.YoshiNPC].Location.Y -= 2;\n            NPC[p.YoshiNPC].Location.SpeedX = 0;\n            NPC[p.YoshiNPC].Location.SpeedY = 0;\n\n            if(NPC[p.YoshiNPC].Type == NPCID_SLIDE_BLOCK)\n                NPC[p.YoshiNPC].Special = 1;\n\n            PlaySoundSpatial(SFX_SpitBossSpit, p.Location);\n\n            if(!p.Controls.Down || (p.Location.SpeedY != 0 && p.StandingOnNPC == 0 && p.Slope == 0))\n            {\n                if(NPC[p.YoshiNPC]->IsAShell)\n                {\n                    SoundPause[SFX_ShellHit] = 2;\n                    // NPCHit .YoshiNPC, 1, A\n                    NPC[p.YoshiNPC].Location.SpeedX = Physics.NPCShellSpeed * p.Direction;\n                }\n                else if(NPC[p.YoshiNPC].Type == NPCID_SLIDE_BLOCK)\n                    NPC[p.YoshiNPC].Location.SpeedX = Physics.NPCShellSpeed * p.Direction;\n                else\n                {\n                    NPC[p.YoshiNPC].Projectile = true;\n                    NPC[p.YoshiNPC].Location.SpeedX = 7 * p.Direction;\n                    NPC[p.YoshiNPC].Location.SpeedY = -1.3_n;\n                }\n            }\n\n            if(NPC[p.YoshiNPC].Type == NPCID_ICE_BLOCK)\n            {\n                NPC[p.YoshiNPC].Direction = p.Direction;\n                NPC[p.YoshiNPC].Projectile = true;\n                NPC[p.YoshiNPC].Location.SpeedX = Physics.NPCShellSpeed * p.Direction * 0.6_r + p.Location.SpeedX * 0.4_r;\n                NPC[p.YoshiNPC].TurnAround = false;\n            }\n\n            PlayerThrownNpcMazeCheck(p, NPC[p.YoshiNPC]);\n\n            if(p.YoshiNPC > 0 && p.YoshiNPC <= numNPCs)\n            {\n                NPCQueues::Active.insert(p.YoshiNPC);\n                NPCQueues::Unchecked.push_back(p.YoshiNPC);\n                treeNPCUpdate(p.YoshiNPC);\n            }\n        }\n    }\n\n    p.FireBallCD = 20;\n    p.YoshiNPC = 0;\n    p.YoshiRed = false;\n\n    if(p.YoshiBlue)\n    {\n        p.CanFly = false;\n        p.CanFly2 = false;\n    }\n\n    p.YoshiBlue = false;\n    p.YoshiYellow = false;\n}\n\nvoid YoshiPound(const int A, int mount, bool BreakBlocks)\n{\n    // int B = 0;\n    Location_t tempLocation;\n    Location_t tempLocation2;\n    auto &p = Player[A];\n\n    if(p.Location.SpeedY > 3)\n    {\n\n        tempLocation.Width = 128;\n        tempLocation.X = p.Location.X + (p.Location.Width - tempLocation.Width) / 2;\n        tempLocation.Height = 32;\n        tempLocation.Y = p.Location.Y + p.Location.Height - 16;\n\n        for(int B : NPCQueues::Active.may_erase)\n        {\n            if(!NPC[B].Hidden && NPC[B].Active && NPC[B].Effect == NPCEFF_NORMAL)\n            {\n                tempLocation2 = NPC[B].Location;\n                tempLocation2.Y += tempLocation2.Height - 4;\n                tempLocation2.Height = 8;\n                if(CheckCollision(tempLocation, tempLocation2))\n                {\n                    Block[0].Location.Y = NPC[B].Location.Y + NPC[B].Location.Height;\n                    NPCHit(B, 2, 0);\n                }\n            }\n        }\n\n        if(BreakBlocks)\n        {\n            for(BlockRef_t block : treeBlockQuery(p.Location, SORTMODE_COMPAT))\n            {\n                Block_t& b = block;\n                int B = block;\n\n                if(b.Hidden || b.Invis || BlockNoClipping[b.Type] || BlockIsSizable[b.Type])\n                    continue;\n\n                if(g_config.fix_vehicle_char_switch && mount == 2 &&\n                    ((b.Type >= 622 && b.Type <= 625) || b.Type == 631))\n                    continue; // Forbid playable character switch when riding a clown car\n\n                if(!CheckCollision(p.Location, b.Location))\n                    continue;\n\n                BlockHit(B, true, A);\n                BlockHitHard(B);\n            }\n        }\n\n        tempLocation.Width = 32;\n        tempLocation.Height = 32;\n        tempLocation.Y = p.Location.Y + p.Location.Height - 16;\n        tempLocation.X = p.Location.X + p.Location.Width / 2 - 16 - 16;\n        NewEffect(EFFID_SMOKE_S3, tempLocation);\n        // in SMBX 1.3, this was -2, and then SpeedX was doubled for EFFID_SMOKE_S3\n        Effect[numEffects].Location.SpeedX = -4;\n        tempLocation.X = p.Location.X + p.Location.Width / 2 - 16 + 16;\n        NewEffect(EFFID_SMOKE_S3, tempLocation);\n        // in SMBX 1.3, this was -2, and then SpeedX was doubled for EFFID_SMOKE_S3\n        Effect[numEffects].Location.SpeedX = 4;\n        PlaySoundSpatial(SFX_Stone, p.Location);\n        if(BreakBlocks && g_config.extra_screen_shake)\n            doShakeScreen(0, 4, SHAKE_SEQUENTIAL, 4, 200, p.Location);\n    }\n}\n\nvoid PlayerDismount(const int A)\n{\n    auto &p = Player[A];\n\n    num_t tempSpeed;\n    if(Player[A].Location.SpeedX > 0)\n        tempSpeed = Player[A].Location.SpeedX / 5; // tempSpeed gives the player a height boost when jumping while running, based off their SpeedX\n    else\n        tempSpeed = -Player[A].Location.SpeedX / 5;\n\n    // jump out of boot\n    if(Player[A].Mount == 1)\n    {\n        // if not swimming\n        if(Player[A].Wet <= 0 || Player[A].Quicksand != 0)\n            UnDuck(Player[A]);\n        Player[A].CanJump = false;\n        PlaySoundSpatial(SFX_Jump, p.Location); // Jump sound\n        PlaySoundSpatial(SFX_Boot, p.Location);\n        Player[A].Location.SpeedY = Physics.PlayerJumpVelocity - tempSpeed;\n        Player[A].Jump = Physics.PlayerJumpHeight;\n        if(Player[A].Character == 2)\n            Player[A].Jump += 3;\n        if(Player[A].SpinJump)\n            Player[A].Jump -= 6;\n        Player[A].Mount = 0;\n        Player[A].StandingOnNPC = 0;\n        numNPCs++;\n        Player[A].FlyCount = 0;\n        Player[A].RunCount = 0;\n        Player[A].CanFly = false;\n        Player[A].CanFly2 = false;\n        NPC[numNPCs].Direction = Player[A].Direction;\n        NPC[numNPCs].Active = true;\n        NPC[numNPCs].TimeLeft = 100;\n\n        if(Player[A].MountType == 1)\n            NPC[numNPCs].Type = NPCID_GRN_BOOT;\n        else if(Player[A].MountType == 2)\n            NPC[numNPCs].Type = NPCID_RED_BOOT;\n        else if(Player[A].MountType == 3)\n            NPC[numNPCs].Type = NPCID_BLU_BOOT;\n\n        NPC[numNPCs].Location.Height = 32;\n        NPC[numNPCs].Location.Width = 32;\n        NPC[numNPCs].Location.Y = Player[A].Location.Y + Player[A].Location.Height - 32;\n        NPC[numNPCs].Location.X = num_t::floor(Player[A].Location.X + Player[A].Location.Width / 2 - 16);\n        NPC[numNPCs].Location.SpeedY = 1;\n        NPC[numNPCs].Location.SpeedX = (Player[A].Location.SpeedX - NPC[Player[A].StandingOnNPC].Location.SpeedX) * 0.8_r;\n        NPC[numNPCs].CantHurt = 10;\n        NPC[numNPCs].CantHurtPlayer = A;\n\n        syncLayers_NPC(numNPCs);\n\n        Player[A].Location.Y = Player[A].Location.Y + Player[A].Location.Height;\n        Player[A].Location.Height = Physics.PlayerHeight[Player[A].Character][Player[A].State];\n        Player[A].Location.Y = Player[A].Location.Y - Player[A].Location.Height;\n    }\n    // jump out of clown car\n    else if(Player[A].Mount == 2)\n    {\n        Player[A].CanJump = false;\n        PlaySoundSpatial(SFX_Jump, p.Location); // Jump sound\n        PlaySoundSpatial(SFX_Boot, p.Location);\n        Player[A].Jump = Physics.PlayerJumpHeight;\n        if(Player[A].Character == 2)\n            Player[A].Jump = Player[A].Jump + 3;\n        if(Player[A].SpinJump)\n            Player[A].Jump = Player[A].Jump - 6;\n        Player[A].Mount = 0;\n\n        s_makeVehicleMount(A);\n\n        Player[A].Location.SpeedY = Physics.PlayerJumpVelocity - tempSpeed;\n        Player[A].Location.Height = Physics.PlayerHeight[Player[A].Character][Player[A].State];\n        Player[A].Location.Width = Physics.PlayerWidth[Player[A].Character][Player[A].State];\n        Player[A].Location.X = Player[A].Location.X + 64 - Physics.PlayerWidth[Player[A].Character][Player[A].State] / 2;\n        Player[A].StandUp = true;\n        Player[A].StandUp2 = true;\n        Player[A].ForceHitSpot3 = true;\n        Player[A].Dismount = 30;\n        Player[A].Slope = 0;\n        Player[A].Location.Y = NPC[numNPCs].Location.Y - Player[A].Location.Height;\n\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            if(B != A && Player[B].Mount != 2 && CheckCollision(Player[A].Location, Player[B].Location))\n                Player[B].Location.Y = Player[A].Location.Y - Player[B].Location.Height;\n\n            if(Player[B].StandingOnVehiclePlr && (g_ClonedPlayerMode || Player[B].StandingOnVehiclePlr == A))\n            {\n                Player[B].StandingOnNPC = numNPCs;\n                Player[B].StandingOnVehiclePlr = 0;\n            }\n        }\n\n        for(int B : NPCQueues::Active.no_change)\n        {\n            if(NPC[B].vehiclePlr == A)\n            {\n                NPC[B].vehiclePlr = 0;\n                NPC[B].Location.SpeedY = 0;\n                NPC[B].Location.Y = NPC[numNPCs].Location.Y - 0.1_n - NPC[B].vehicleYOffset;\n                treeNPCUpdate(B);\n\n                NPC[B].vehicleYOffset = 0;\n                if(NPC[B].Type == NPCID_CANNONITEM)\n                    NPC[B].Special = 0;\n                if(NPC[B].Type == NPCID_TOOTHY)\n                {\n                    NPC[B].Killed = 9;\n                    NPC[B].Special = 0;\n                    NPCQueues::Killed.push_back(B);\n                }\n                else if(NPC[B].Type == NPCID_TOOTHYPIPE)\n                    NPC[B].Special = 0;\n            }\n        }\n    }\n    // jump off yoshi\n    else if(Player[A].Mount == 3)\n    {\n        UnDuck(Player[A]);\n        if(Player[A].YoshiNPC > 0 || Player[A].YoshiPlayer > 0)\n            YoshiSpit(A);\n        Player[A].CanJump = false;\n        Player[A].StandingOnNPC = 0;\n        Player[A].Mount = 0;\n        UpdateYoshiMusic();\n\n        s_makePetMount(A);\n\n        Player[A].Location.Height = Physics.PlayerHeight[Player[A].Character][Player[A].State];\n        // if not swimming\n        if(Player[A].Wet <= 0 || Player[A].Quicksand != 0)\n        {\n            PlaySoundSpatial(SFX_Jump, p.Location); // Jump sound\n            Player[A].Location.SpeedY = Physics.PlayerJumpVelocity - tempSpeed;\n            Player[A].Jump = Physics.PlayerJumpHeight;\n            if(Player[A].Character == 2)\n                Player[A].Jump = Player[A].Jump + 3;\n            if(Player[A].SpinJump)\n                Player[A].Jump = Player[A].Jump - 6;\n        }\n    }\n}\n\nvoid SwapCoop()\n{\n    if(SingleCoop == 1)\n    {\n        if(Player[2].Dead || Player[2].TimeToLive > 0)\n            return;\n\n        SingleCoop = 2;\n    }\n    else\n    {\n        if(Player[1].Dead || Player[1].TimeToLive > 0)\n            return;\n        SingleCoop = 1;\n    }\n\n    Player[1].DropRelease = false;\n    Player[1].Controls.Drop = true;\n    Player[2].DropRelease = false;\n    Player[2].Controls.Drop = true;\n    PlaySound(SFX_Camera);\n\n    Player[SingleCoop].Immune = 50;\n\n    if(curMusic >= 0 && curMusic != bgMusic[Player[SingleCoop].Section])\n    {\n        StopMusic();\n        StartMusic(Player[SingleCoop].Section);\n    }\n}\n\nvoid PlayerPush(const int A, int HitSpot)\n{\n    Location_t tempLocation;\n    // int64_t fBlock = 0;\n    // int64_t lBlock = 0;\n\n    if(ShadowMode)\n        return;\n\n    auto &p = Player[A];\n\n    // fBlock = FirstBlock[(p.Location.X / 32) - 1];\n    // lBlock = LastBlock[((p.Location.X + p.Location.Width) / 32.0) + 1];\n    // blockTileGet(p.Location, fBlock, lBlock);\n\n    UpdatableQuery<BlockRef_t> q(p.Location, SORTMODE_COMPAT, QUERY_FLBLOCK);\n\n    for(auto it = q.begin(); it != q.end(); ++it)\n    {\n        int B = *it;\n        Block_t& b = **it;\n\n        if(b.Hidden || BlockIsSizable[b.Type])\n            continue;\n\n        // Note: could also apply this for a downwards clip when colliding with an invisible block\n        if(g_config.fix_player_filter_bounce && BlockCheckPlayerFilter(B, A))\n            continue;\n\n        if(BlockSlope[b.Type] == 0 && BlockSlope2[b.Type] == 0)\n        {\n            tempLocation = p.Location;\n            tempLocation.Height -= 1;\n            if(CheckCollision(tempLocation, b.Location))\n            {\n                if(!BlockOnlyHitspot1[b.Type] && !BlockNoClipping[b.Type])\n                {\n                    if(HitSpot == 2)\n                    {\n                        // Note: this vanilla peculiarity (Width should be subtracted) only affects a victim spat rightwards out of a pet's mouth.\n                        // If spat rightwards against a wall, the victim teleports to the left through the player who spat them out.\n                        // If spat leftwards against a wall (non-bugged behavior), the player who spat them out gets pushed rightwards.\n                        p.Location.X = b.Location.X - p.Location.Height - 0.01_n;\n                    }\n                    else if(HitSpot == 3)\n                        p.Location.Y = b.Location.Y + b.Location.Height + 0.01_n;\n                    else if(HitSpot == 4)\n                        p.Location.X = b.Location.X + b.Location.Width + 0.01_n;\n                    else if(HitSpot == 1) // new-added\n                        p.Location.Y = b.Location.Y - p.Location.Height - 0.01_n;\n                    else if(HitSpot == 5) // non-bugged 2\n                        p.Location.X = b.Location.X - p.Location.Width - 0.01_n;\n\n                    q.update(p.Location, it);\n                }\n            }\n        }\n    }\n}\n\nvoid SizeCheck(Player_t &p)\n{\n//On Error Resume Next\n\n// player size fix\n// height\n    if(p.State == 0)\n        p.State = 1;\n    if(p.Character == 0)\n        p.Character = 1;\n\n    if(p.Character == 5 && p.Rolling)\n    {\n        p.Location.set_height_floor(30);\n        return;\n    }\n\n    if(p.Fairy)\n    {\n        if(p.Duck)\n        {\n            UnDuck(p);\n        }\n\n        if(p.Location.Width != 22)\n            p.Location.set_width_center(22);\n\n        if(p.Location.Height != 26)\n            p.Location.set_height_floor(26);\n    }\n    else if(p.Mount == 0)\n    {\n        if(!p.Duck)\n        {\n            if(p.Location.Height != Physics.PlayerHeight[p.Character][p.State])\n                p.Location.set_height_floor(Physics.PlayerHeight[p.Character][p.State]);\n        }\n        else\n        {\n            if(p.Location.Height != Physics.PlayerDuckHeight[p.Character][p.State])\n                p.Location.set_height_floor(Physics.PlayerDuckHeight[p.Character][p.State]);\n        }\n    }\n    else if(p.Mount == 1)\n    {\n        if(p.Duck)\n        {\n            if(p.Location.Height != Physics.PlayerDuckHeight[p.Character][2])\n                p.Location.set_height_floor(Physics.PlayerDuckHeight[p.Character][2]);\n        }\n        else if(p.Character == 2 && p.State > 1)\n        {\n            if(p.Location.Height != Physics.PlayerHeight[1][2])\n                p.Location.set_height_floor(Physics.PlayerHeight[p.Character][p.State]);\n        }\n        else\n        {\n            if(p.Location.Height != Physics.PlayerHeight[1][2])\n                p.Location.set_height_floor(Physics.PlayerHeight[1][2]);\n        }\n    }\n    else if(p.Mount == 2)\n    {\n        if(p.Location.Height != 128)\n            p.Location.set_height_floor(128);\n    }\n    else if(p.Mount == 3)\n    {\n        if(!p.Duck)\n        {\n            if(p.State == 1)\n            {\n                if(p.Location.Height != Physics.PlayerHeight[1][2])\n                    p.Location.set_height_floor(Physics.PlayerHeight[1][2]);\n            }\n            else\n            {\n                if(p.Location.Height != Physics.PlayerHeight[2][2])\n                    p.Location.set_height_floor(Physics.PlayerHeight[2][2]);\n            }\n        }\n        else\n        {\n            if(p.Location.Height != 31)\n                p.Location.set_height_floor(31);\n        }\n    }\n// width\n    if(p.Mount == 2)\n    {\n        if(p.Location.Width != 127.9_n)\n            p.Location.set_width_center(127.9_n);\n    }\n    else\n    {\n        if(p.Location.Width != Physics.PlayerWidth[p.Character][p.State])\n            p.Location.set_width_center(Physics.PlayerWidth[p.Character][p.State]);\n    }\n}\n\nvoid YoshiEatCode(const int A)\n{\n    int B = 0;\n    // Location_t tempLocation;\n    auto &p = Player[A];\n\n    if(p.Mount == 3 && !p.Fairy)\n    {\n    // Shell Colors\n        if(p.MountSpecial == 0)\n        {\n            if(p.YoshiNPC > 0)\n            {\n                if(NPC[p.YoshiNPC].Type == NPCID_RED_SHELL_S3 || NPC[p.YoshiNPC].Type == NPCID_RED_TURTLE_S3)\n                    p.YoshiRed = true;\n                if(NPC[p.YoshiNPC].Type == NPCID_RED_TURTLE_S4 || NPC[p.YoshiNPC].Type == NPCID_RED_SHELL_S4 || NPC[p.YoshiNPC].Type == NPCID_RED_SLIME)\n                    p.YoshiRed = true;\n                if(NPC[p.YoshiNPC].Type == NPCID_RED_TURTLE_S4 || NPC[p.YoshiNPC].Type == NPCID_RED_SHELL_S4 || NPC[p.YoshiNPC].Type == NPCID_RED_SLIME)\n                    p.YoshiRed = true;\n                if(NPC[p.YoshiNPC].Type == NPCID_RED_SHELL_S1 || NPC[p.YoshiNPC].Type == NPCID_RED_TURTLE_S1 || NPC[p.YoshiNPC].Type == NPCID_RED_FLY_TURTLE_S1 || NPC[p.YoshiNPC].Type == NPCID_RAINBOW_SHELL)\n                    p.YoshiRed = true;\n\n                if(NPC[p.YoshiNPC].Type == NPCID_BLU_TURTLE_S4 || NPC[p.YoshiNPC].Type == NPCID_BLU_SHELL_S4 || NPC[p.YoshiNPC].Type == NPCID_RAINBOW_SHELL || NPC[p.YoshiNPC].Type == NPCID_FLIPPED_RAINBOW_SHELL)\n                {\n                    if(!p.YoshiBlue)\n                    {\n                        p.CanFly2 = true;\n                        p.CanFly2 = true;\n                        p.FlyCount = 300;\n                    }\n                    p.YoshiBlue = true;\n                }\n\n                if(NPC[p.YoshiNPC].Type == NPCID_YEL_TURTLE_S4 || NPC[p.YoshiNPC].Type == NPCID_YEL_SHELL_S4 ||\n                   NPC[p.YoshiNPC].Type == NPCID_CYAN_SLIME || NPC[p.YoshiNPC].Type == NPCID_RAINBOW_SHELL ||\n                   NPC[p.YoshiNPC].Type == NPCID_FLIPPED_RAINBOW_SHELL)\n                    p.YoshiYellow = true;\n            }\n\n            if(p.YoshiNPC > 0 || p.YoshiPlayer > 0)\n            {\n                if(p.MountType == 2 || p.MountType == 5)\n                {\n                    if(!p.YoshiBlue)\n                    {\n                        p.CanFly2 = true;\n                        p.CanFly2 = true;\n                        p.FlyCount = 300;\n                    }\n                    p.YoshiBlue = true;\n                }\n                if(p.MountType == 3 || p.MountType == 5)\n                    p.YoshiYellow = true;\n                if(p.MountType == 4 || p.MountType == 5)\n                    p.YoshiRed = true;//4;\n            }\n        }\n\n        if(p.YoshiBlue)\n        {\n            p.CanFly = true;\n            p.RunCount = 10000; // multiplied by 10 vs VB6 code, since it's an int now\n        }\n\n        if(p.MountSpecial == 0)\n        {\n            if(NPC[p.YoshiNPC].Type == NPCID_KEY) // key check\n                KeyholeCheck(A, p.Location);\n            else if(NPC[p.YoshiNPC].Type == NPCID_SLIDE_BLOCK)\n                NPC[p.YoshiNPC].Special = 1;\n\n            if(p.FireBallCD > 0)\n                p.FireBallCD -= 1;\n\n            if(p.Controls.Run)\n            {\n                if(p.RunRelease)\n                {\n                    if(p.YoshiNPC == 0 && p.YoshiPlayer == 0)\n                    {\n                        if(p.FireBallCD == 0)\n                        {\n                            p.MountSpecial = 1;\n                            p.YoshiTongueLength = 0;\n                            p.YoshiTonugeBool = false;\n                            PlaySoundSpatial(SFX_PetTongue, p.Location);\n                        }\n                    }\n                    else\n                    {\n                        YoshiSpit(A);\n                    }\n                }\n            }\n        }\n\n        if(p.MountSpecial != 0)\n        {\n            p.YoshiTFrameCount = 0;\n\n            if(p.YoshiNPC > 0 || p.YoshiPlayer > 0)\n                p.YoshiTonugeBool = true;\n\n            if(!p.YoshiTonugeBool)\n            {\n                if(p.MountType <= 4)\n                {\n                    if(p.YoshiTongueLength < 64 * 0.7)\n                        p.YoshiTongueLength += 6;\n                    else\n                        p.YoshiTongueLength += 3;\n                }\n                else\n                {\n                    if(p.YoshiTongueLength < 80 * 0.7)\n                        p.YoshiTongueLength += 8; // 7.5 will be rounded into 8;\n                    else\n                        p.YoshiTongueLength += 4; // 3.75 will be rounded into 4\n                }\n\n//                if(p.YoshiTongueLength >= 64 && p.MountType <= 4)\n//                    p.YoshiTonugeBool = true;\n//                else if(p.YoshiTongueLength >= 80)\n//                    p.YoshiTonugeBool = true;\n\n                // Simplified expression than commented above\n                if((p.YoshiTongueLength >= 64 && p.MountType <= 4) || (p.YoshiTongueLength >= 80))\n                    p.YoshiTonugeBool = true;\n            }\n            else\n            {\n                if(p.MountType <= 4)\n                {\n                    if(p.YoshiTongueLength < 64 * 0.7)\n                        p.YoshiTongueLength -= 6;\n                    else\n                        p.YoshiTongueLength -= 3;\n                }\n                else\n                {\n                    if(p.YoshiTongueLength < 80 * 0.7)\n                        p.YoshiTongueLength -= 8; // 7.5;\n                    else\n                        p.YoshiTongueLength -= 4; // 3.75;\n                }\n                if(p.YoshiTongueLength <= -8)\n                {\n                    p.YoshiTongueLength = 0;\n                    p.YoshiTonugeBool = false;\n                    p.MountSpecial = 0;\n                }\n            }\n\n            p.YoshiTongue.Height = 12;\n            p.YoshiTongue.Width = 16;\n\n            tempf_t TongueX = tempf_t(p.Location.X + p.Location.Width / 2);\n\n            if(p.Controls.Up || (p.StandingOnNPC == 0 && p.Slope == 0 && p.Location.SpeedY != 0 && !p.Controls.Down))\n            {\n                TongueX += p.Direction * (22);\n                p.YoshiTongue.Y = p.Location.Y + 8 + (p.Location.Height - 54);\n                p.YoshiTongue.X = num_t(TongueX + p.YoshiTongueLength * p.Direction);\n            }\n            else\n            {\n                TongueX += p.Direction * (34);\n                p.YoshiTongue.Y = p.Location.Y + 30 + (p.Location.Height - 54);\n                p.YoshiTongue.X = num_t(TongueX + p.YoshiTongueLength * p.Direction);\n            }\n\n            if(p.Direction == -1)\n                p.YoshiTongue.X -= 16;\n\n            if(p.YoshiNPC == 0 && p.YoshiPlayer == 0)\n            {\n                YoshiEat(A);\n            }\n\n            if(p.YoshiNPC > 0)\n            {\n                NPC[p.YoshiNPC].Effect2 = A;\n                NPC[p.YoshiNPC].Effect3 = 5;\n                if(!p.YoshiTonugeBool)\n                    p.YoshiTonugeBool = true;\n                NPC[p.YoshiNPC].Location.X = p.YoshiTongue.X - NPC[p.YoshiNPC].Location.Width / 2 + 8 + 4 * p.Direction;\n                NPC[p.YoshiNPC].Location.Y = p.YoshiTongue.Y - NPC[p.YoshiNPC].Location.Height / 2 + 6;\n\n                if(p.YoshiNPC <= numNPCs)\n                    treeNPCUpdate(p.YoshiNPC);\n            }\n\n            if(p.YoshiPlayer > 0)\n            {\n                Player[p.YoshiPlayer].Effect = PLREFF_NO_COLLIDE;\n                Player[p.YoshiPlayer].Effect2 = A;\n                Player[p.YoshiPlayer].Location.X = p.YoshiTongue.X + (p.YoshiTongue.Width - Player[p.YoshiPlayer].Location.Width) / 2;\n                Player[p.YoshiPlayer].Location.Y = p.YoshiTongue.Y + (p.YoshiTongue.Height - Player[p.YoshiPlayer].Location.Height) / 2;\n                if(Player[p.YoshiPlayer].Location.Y + Player[p.YoshiPlayer].Location.Height > p.Location.Y + p.Location.Height)\n                    Player[p.YoshiPlayer].Location.Y = p.Location.Y + p.Location.Height - Player[p.YoshiPlayer].Location.Height;\n            }\n        }\n        if(p.MountSpecial == 0 && p.YoshiNPC > 0)\n        {\n            p.YoshiTFrameCount = 1;\n\n            if(NPC[p.YoshiNPC].Type == NPCID_GRN_TURTLE_S3 || NPC[p.YoshiNPC].Type == NPCID_GRN_FLY_TURTLE_S3)\n                NPC[p.YoshiNPC].Type = NPCID_GRN_SHELL_S3;\n            else if(NPC[p.YoshiNPC].Type == NPCID_RED_TURTLE_S3 || NPC[p.YoshiNPC].Type == NPCID_RED_FLY_TURTLE_S3)\n                NPC[p.YoshiNPC].Type = NPCID_RED_SHELL_S3;\n            else if(NPC[p.YoshiNPC].Type == NPCID_GLASS_TURTLE)\n                NPC[p.YoshiNPC].Type = NPCID_GLASS_SHELL;\n            else if(NPC[p.YoshiNPC].Type == NPCID_BIG_TURTLE)\n                NPC[p.YoshiNPC].Type = NPCID_BIG_SHELL;\n            else if(NPC[p.YoshiNPC].Type >= NPCID_GRN_TURTLE_S4 && NPC[p.YoshiNPC].Type <= NPCID_YEL_TURTLE_S4)\n                NPC[p.YoshiNPC].Type = NPCID(NPC[p.YoshiNPC].Type + 4);\n            else if(NPC[p.YoshiNPC].Type >= NPCID_GRN_FLY_TURTLE_S4 && NPC[p.YoshiNPC].Type <= NPCID_YEL_FLY_TURTLE_S4)\n            {\n                NPC[p.YoshiNPC].Type = NPCID(NPC[p.YoshiNPC].Type - 8);\n                NPC[p.YoshiNPC].Special = 0;\n            }\n            else if(NPC[p.YoshiNPC].Type == NPCID_GRN_TURTLE_S1 || NPC[p.YoshiNPC].Type == NPCID_GRN_FLY_TURTLE_S1)\n            {\n                NPC[p.YoshiNPC].Type = NPCID_GRN_SHELL_S1;\n                NPC[p.YoshiNPC].Location.Height = 28;\n            }\n            else if(NPC[p.YoshiNPC].Type == NPCID_RED_TURTLE_S1 || NPC[p.YoshiNPC].Type == NPCID_RED_FLY_TURTLE_S1)\n            {\n                NPC[p.YoshiNPC].Type = NPCID_RED_SHELL_S1;\n                NPC[p.YoshiNPC].Location.Height = 28;\n            }\n\n            NPC[p.YoshiNPC].Location.Height = NPC[p.YoshiNPC]->THeight;\n            if((NPC[p.YoshiNPC]->IsGrabbable || NPC[p.YoshiNPC]->IsAShell || NPC[p.YoshiNPC].Type == NPCID_SPIT_BOSS_BALL || NPCIsABot(NPC[p.YoshiNPC]) || NPC[p.YoshiNPC].Type == NPCID_RAINBOW_SHELL || NPC[p.YoshiNPC].Type == NPCID_WALK_BOMB_S2 || NPC[p.YoshiNPC].Type == NPCID_WALK_BOMB_S3 || NPC[p.YoshiNPC].Type == NPCID_LIT_BOMB_S3) && (NPC[p.YoshiNPC].Type != NPCID_HIT_CARRY_FODDER))\n            {\n                if(NPC[p.YoshiNPC].Type == NPCID_WALK_BOMB_S2)\n                    NPC[p.YoshiNPC].Special = 450;\n                if(NPC[p.YoshiNPC].Type == NPCID_BOMB)\n                    NPC[p.YoshiNPC].Special = 250;\n                if(NPC[p.YoshiNPC].Type == NPCID_WALK_BOMB_S3 || NPC[p.YoshiNPC].Type == NPCID_LIT_BOMB_S3)\n                {\n                    NPC[p.YoshiNPC].Special = 250;\n                    NPC[p.YoshiNPC].Type = NPCID_LIT_BOMB_S3;\n                    NPC[p.YoshiNPC].Location.Height = NPC[p.YoshiNPC]->THeight;\n                }\n\n                NPC[p.YoshiNPC].Effect = NPCEFF_PET_INSIDE;\n                NPC[p.YoshiNPC].Effect2 = A;\n\n                if(NPC[p.YoshiNPC].Active)\n                {\n                    NPC[p.YoshiNPC].Active = false;\n                    NPCQueues::update(p.YoshiNPC);\n                }\n\n                if(NPC[p.YoshiNPC].Type == NPCID_TOOTHYPIPE)\n                {\n                    NPC[p.YoshiNPC].Special = 0;\n                    NPC[p.YoshiNPC].Special2 = 0;\n                }\n            }\n            else if(p.MountType == 7 && !NPC[p.YoshiNPC]->IsABonus)\n            {\n                B = iRand(9);\n                NPC[p.YoshiNPC].Type = NPCID(NPCID_VEGGIE_2 + B);\n                if(NPC[p.YoshiNPC].Type == NPCID_VEGGIE_RANDOM)\n                    NPC[p.YoshiNPC].Type = NPCID_VEGGIE_1;\n                NPC[p.YoshiNPC].Location.set_width_center(NPC[p.YoshiNPC]->TWidth);\n                NPC[p.YoshiNPC].Location.set_height_center(NPC[p.YoshiNPC]->THeight);\n                NPC[p.YoshiNPC].Effect = NPCEFF_PET_INSIDE;\n                NPC[p.YoshiNPC].Effect2 = A;\n\n                NPCQueues::Unchecked.push_back(p.YoshiNPC);\n\n                if(NPC[p.YoshiNPC].Active)\n                {\n                    NPC[p.YoshiNPC].Active = false;\n                    NPCQueues::update(p.YoshiNPC);\n                }\n            }\n            else if(p.MountType == 8 && !NPC[p.YoshiNPC]->IsABonus)\n            {\n                NPC[p.YoshiNPC].Type = NPCID_ICE_BLOCK;\n                NPC[p.YoshiNPC].Location.set_width_center(NPC[p.YoshiNPC]->TWidth);\n                NPC[p.YoshiNPC].Location.set_height_center(NPC[p.YoshiNPC]->THeight);\n                NPC[p.YoshiNPC].Effect = NPCEFF_PET_INSIDE;\n                NPC[p.YoshiNPC].Effect2 = A;\n\n                NPCQueues::Unchecked.push_back(p.YoshiNPC);\n\n                if(NPC[p.YoshiNPC].Active)\n                {\n                    NPC[p.YoshiNPC].Active = false;\n                    NPCQueues::update(p.YoshiNPC);\n                }\n            }\n            else\n            {\n                if(NPC[p.YoshiNPC]->IsABonus)\n                {\n                    TouchBonus(A, p.YoshiNPC);\n                    p.YoshiNPC = 0;\n                }\n                else\n                {\n                    MoreScore(NPC[p.YoshiNPC]->Score, NPC[p.YoshiNPC].Location, p.Multiplier);\n                    NPC[p.YoshiNPC].Killed = 9;\n                    NPCQueues::Killed.push_back(p.YoshiNPC);\n\n                    p.YoshiNPC = 0;\n                    p.FireBallCD = 30;\n                    Coins += 1;\n\n                    if(Coins >= 100)\n                        Got100Coins();\n\n                    PlaySoundSpatial(SFX_PetSwallow, p.Location);\n                }\n            }\n\n            if(p.YoshiNPC != 0)\n            {\n                NPCQueues::Unchecked.push_back(p.YoshiNPC);\n\n                if(p.YoshiNPC <= numNPCs)\n                    treeNPCUpdate(p.YoshiNPC);\n            }\n        }\n        else if(p.MountSpecial == 0 && p.YoshiPlayer > 0)\n        {\n            Player[p.YoshiPlayer].CurMazeZone = 0;\n            Player[p.YoshiPlayer].Effect = PLREFF_PET_INSIDE;\n            Player[p.YoshiPlayer].Effect2 = A;\n            Player[p.YoshiPlayer].Location.X = p.Location.X + (p.Location.Width - Player[p.YoshiPlayer].Location.Width) / 2;\n            Player[p.YoshiPlayer].Location.Y = p.Location.Y + (p.Location.Height - Player[p.YoshiPlayer].Location.Height) / 2;\n            p.YoshiTFrameCount = 1;\n        }\n    }\n}\n\nvoid RespawnPlayer(int A, int Direction, num_t CenterX, num_t StopY, const vScreen_t& target_screen)\n{\n    Player[A].Location.Width = Physics.PlayerWidth[Player[A].Character][Player[A].State];\n    Player[A].Location.Height = Physics.PlayerHeight[Player[A].Character][Player[A].State];\n    Player[A].Frame = 1;\n    Player[A].Direction = Direction;\n    Player[A].Dead = false;\n    Player[A].Location.SpeedX = 0;\n    Player[A].Location.SpeedY = 0;\n    Player[A].Effect = PLREFF_RESPAWN;\n    // location where player stops flashing\n    Player[A].RespawnY = StopY - Player[A].Location.Height;\n    Player[A].Location.Y = -target_screen.Y - Player[A].Location.Height;\n    Player[A].Location.X = CenterX - Player[A].Location.Width / 2;\n}\n\nvoid RespawnPlayerTo(int A, int TargetPlayer)\n{\n    num_t CenterX = Player[TargetPlayer].Location.X + Player[TargetPlayer].Location.Width / 2;\n\n    // don't lose a player when it targets a player who is already respawning\n    num_t StopY;\n    if(Player[TargetPlayer].Effect == PLREFF_RESPAWN)\n        StopY = Player[TargetPlayer].RespawnY + Player[TargetPlayer].Location.Height;\n    else if(Player[TargetPlayer].Mount == 2)\n        StopY = Player[TargetPlayer].Location.Y;\n    else\n        StopY = Player[TargetPlayer].Location.Y + Player[TargetPlayer].Location.Height;\n\n    // technically this would fix a vanilla bug (possible weird effects after Player 2 dies, Player 1 goes through Warp, Player 2 respawns)\n    //   so I will do it where it only affects the new code.\n    // Player[A].Section = Player[TargetPlayer].Section;\n\n    // respawn at top of vScreen 1 in SMBX dynamic splitscreen, otherwise at top of target player's vScreen\n    int screen_idx_A = ScreenIdxByPlayer(A);\n    int screen_idx_tgt = ScreenIdxByPlayer(TargetPlayer);\n    const Screen_t& screen = Screens[screen_idx_A];\n    const vScreen_t& target_vscreen = (screen.Type == ScreenTypes::Dynamic) ? screen.vScreen(1) : vScreenByPlayer(TargetPlayer);\n\n    // force a modern respawn when players are on different screens (clients), or when a player was added during a cutscene\n    bool force_modern_respawn = (screen_idx_A != screen_idx_tgt) || ForcedControls;\n\n    RespawnPlayer(A, Player[TargetPlayer].Direction, CenterX, StopY, target_vscreen);\n\n    // if TargetPlayer is scrolling in warp, we can't spawn them directly.\n    if(PlayerScrollingInWarp(Player[TargetPlayer]) || Player[TargetPlayer].CurMazeZone || force_modern_respawn)\n    {\n        // Give player A wings in shared screen\n        if(screen.Type == ScreenTypes::SharedScreen || Player[TargetPlayer].CurMazeZone || force_modern_respawn)\n        {\n            Player[A].Location.Y -= 100;\n            Player[A].Dead = true;\n            Player[A].Effect = PLREFF_COOP_WINGS;\n            Player[A].Effect2 = 0;\n            Player[A].RespawnY = 0;\n        }\n        // respawn them to the target player's warp otherwise\n        else\n        {\n            const auto& warp = Warp[Player[TargetPlayer].Warp];\n            const auto& warp_exit = (Player[TargetPlayer].WarpBackward) ? warp.Entrance : warp.Exit;\n            const auto warp_exit_dir = (Player[TargetPlayer].WarpBackward) ? warp.Direction : warp.Direction2;\n\n            Player[A].Location.X = warp_exit.X + (warp_exit.Width - Player[A].Location.Width) / 2;\n\n            if(Player[TargetPlayer].Effect == PLREFF_WARP_PIPE && warp_exit_dir == 1)\n                Player[A].RespawnY = warp_exit.Y;\n            else\n                Player[A].RespawnY = warp_exit.Y + warp_exit.Height - Player[A].Location.Height;\n\n            Player[A].Location.Y = Player[A].RespawnY - target_vscreen.Height * 3 / 4;\n        }\n    }\n}\n\nvoid StealBonus()\n{\n    int A = 0;\n    int B = 0;\n    int C = 0;\n    UNUSED(C);\n\n    // dead players steal life\n    if(BattleMode || GameMenu || GameOutro || g_ClonedPlayerMode)\n        return;\n\n    // NOTE: legacy code here accepted TimeToLive <= 0, CheckLiving() requires TimeToLive == 0. TimeToLive should never be negative.\n    int alive = CheckLiving();\n    if(!alive)\n        return;\n\n    for(A = 1; A <= numPlayers; A++)\n    {\n        if(Player[A].Dead && Player[A].Effect != PLREFF_COOP_WINGS)\n        {\n            // find other player\n            if((g_config.modern_lives_system || Lives > 0) && LevelMacro == LEVELMACRO_OFF)\n            {\n                if(Player[A].Controls.Jump || Player[A].Controls.Run)\n                {\n                    B = CheckNearestLiving(A);\n\n                    if(g_config.modern_lives_system)\n                    {\n                        g_100s--;\n\n                        if(g_100s < 0)\n                            Score = 0;\n                    }\n                    else\n                        Lives -= 1;\n\n                    Player[A].State = 1;\n                    Player[A].Hearts = 1;\n                    RespawnPlayerTo(A, B);\n                    PlaySoundSpatial(SFX_DropItem, Player[B].Location);\n                }\n            }\n        }\n    }\n}\n\nvoid ClownCar()\n{\n    // for when the player is in the clown car\n    // int A = 0;\n    // int B = 0;\n//    int C = 0;\n    // NPC_t blankNPC;\n\n    for(int A = 1; A <= numPlayers; A++) // Code for running the Koopa Clown Car\n    {\n        // commenting out because:\n        //   (1) misplaced; (2) doesn't work with abstract controls\n        // logic moved to Controls::Update()\n\n        // if(numPlayers > 2 && GameMenu == false && LevelMacro == LEVELMACRO_OFF && nPlay.Online == false)\n        //     Player[A].Controls = Player[1].Controls;\n\n        Player_t& p = Player[A];\n        Location_t& pLoc = p.Location;\n\n        // this code should only apply to living players on the vehicle mount\n        if(!(p.Mount == 2 && p.Dead == false && p.TimeToLive == 0))\n            continue;\n\n        // vehicle movement code\n        if(p.Effect == PLREFF_NORMAL)\n        {\n            if(p.Controls.Left)\n            {\n                pLoc.SpeedX -= 0.1_n;\n\n                if(pLoc.SpeedX > 0)\n                    pLoc.SpeedX -= 0.15_n;\n            }\n            else if(p.Controls.Right)\n            {\n                pLoc.SpeedX += 0.1_n;\n\n                if(pLoc.SpeedX < 0)\n                    pLoc.SpeedX += 0.15_n;\n            }\n            else\n            {\n                if(pLoc.SpeedX > 0.2_n)\n                    pLoc.SpeedX -= 0.05_n;\n                else if(pLoc.SpeedX < -0.2_n)\n                    pLoc.SpeedX += 0.05_n;\n                else\n                    pLoc.SpeedX = 0;\n            }\n\n            if(p.Controls.Up)\n            {\n                pLoc.SpeedY -= 0.1_n;\n\n                if(pLoc.SpeedY > 0)\n                    pLoc.SpeedY -= 0.2_n;\n            }\n            else if(p.Controls.Down)\n            {\n                pLoc.SpeedY += 0.2_n;\n\n                if(pLoc.SpeedY < 0)\n                    pLoc.SpeedY += 0.2_n;\n            }\n            else\n            {\n                if(pLoc.SpeedY > 0.1_n)\n                    pLoc.SpeedY -= 0.1_n;\n                else if(pLoc.SpeedY < -0.1_n)\n                    pLoc.SpeedY += 0.1_n;\n                else\n                    pLoc.SpeedY = 0;\n            }\n\n            if(pLoc.SpeedX > 4)\n                pLoc.SpeedX = 4;\n            else if(pLoc.SpeedX < -4)\n                pLoc.SpeedX = -4;\n            if(pLoc.SpeedY > 10)\n                pLoc.SpeedY = 10;\n            else if(pLoc.SpeedY < -4)\n                pLoc.SpeedY = -4;\n        }\n\n        // create playerTemp NPC for the Vehicle\n        numNPCs++;\n        NPC[numNPCs] = NPC_t();\n        NPC[numNPCs].playerTemp = true;\n        NPC[numNPCs].Type = NPCID_VEHICLE;\n        NPC[numNPCs].Variant = A; // newly-added to allow setting StandingOnVehiclePlr\n        NPC[numNPCs].Active = true;\n        NPC[numNPCs].TimeLeft = 100;\n        NPC[numNPCs].Location = pLoc;\n\n        if(p.Effect != PLREFF_NORMAL)\n        {\n            NPC[numNPCs].Location.SpeedX = 0;\n            NPC[numNPCs].Location.SpeedY = 0;\n        }\n\n        NPC[numNPCs].Location.Y += NPC[numNPCs].Location.SpeedY;\n        NPC[numNPCs].Location.X += NPC[numNPCs].Location.SpeedX;\n        NPC[numNPCs].Section = p.Section;\n        syncLayers_NPC(numNPCs);\n\n        // update other players' StandingOnNPC\n        for(int B = 1; B <= numPlayers; B++)\n        {\n            if(Player[B].StandingOnVehiclePlr && (g_ClonedPlayerMode || Player[B].StandingOnVehiclePlr == A))\n            {\n                Player[B].StandingOnNPC = numNPCs;\n                Player[B].Location.X += num_t(p.mountBump);\n\n                if(Player[B].Effect != PLREFF_NORMAL)\n                {\n                    Player[B].Location.Y = pLoc.Y - Player[B].Location.Height;\n                    Player[B].Location.X += pLoc.SpeedX;\n                }\n            }\n        }\n\n        // handle NPCs on the vehicle\n        for(int B : NPCQueues::Active.may_insert)\n        {\n            // only want non-toothy NPCs on A's vehicle\n            if(!(NPC[B].vehiclePlr == A && NPC[B].Type != NPCID_TOOTHY))\n                continue;\n\n            if(p.Effect == PLREFF_NORMAL)\n                NPC[B].Location.X += pLoc.SpeedX + num_t(p.mountBump);\n\n            NPC[B].TimeLeft = 100;\n            NPC[B].Location.SpeedY = pLoc.SpeedY;\n            NPC[B].Location.SpeedX = 0;\n\n            if(p.Effect != PLREFF_NORMAL)\n                NPC[B].Location.SpeedY = 0;\n\n            NPC[B].Location.Y = pLoc.Y + NPC[B].Location.SpeedY + 0.1_n - NPC[B].vehicleYOffset;\n            treeNPCUpdate(B);\n\n            // extend toothy pipe\n            if(p.Controls.Run && NPC[B].Type == NPCID_TOOTHYPIPE)\n            {\n                // create toothy if it doesn't exist already\n                if(NPC[B].Special == 0)\n                {\n                    NPC[B].Special = 1;\n                    numNPCs++;\n                    NPC[B].Special2 = numNPCs;\n\n                    NPC[numNPCs].Active = true;\n                    NPC[numNPCs].Section = p.Section;\n                    NPC[numNPCs].TimeLeft = 100;\n                    NPC[numNPCs].Type = NPCID_TOOTHY;\n                    NPC[numNPCs].Location.Height = 32;\n                    NPC[numNPCs].Location.Width = 48;\n                    NPC[numNPCs].Special = A;\n                    NPC[numNPCs].Special2 = B;\n                    NPC[numNPCs].Direction = NPC[B].Direction;\n\n                    if(NPC[numNPCs].Direction == 1)\n                        NPC[numNPCs].Frame = 2;\n                    syncLayers_NPC(numNPCs);\n                }\n\n                // update toothy's position\n                for(int C : NPCQueues::Active.no_change)\n                {\n                    if(NPC[C].Type == NPCID_TOOTHY && NPC[C].Special == A && NPC[C].Special2 == B)\n                    {\n                        NPC[C].vehiclePlr = A;\n                        NPC[C].Projectile = true;\n                        NPC[C].Direction = NPC[B].Direction;\n\n                        if(NPC[C].Direction > 0)\n                            NPC[C].Location.X = NPC[B].Location.X + 32;\n                        else\n                            NPC[C].Location.X = NPC[B].Location.X - NPC[C].Location.Width;\n\n                        NPC[C].Location.Y = NPC[B].Location.Y;\n                        NPC[C].TimeLeft = 100;\n                        treeNPCUpdate(C);\n                        break;\n                    }\n                }\n            }\n\n            // check if NPC should stay on the vehicle\n            bool still_on_vehicle = false;\n            Location_t query_loc = NPC[B].Location;\n            query_loc.Y += query_loc.Height + 0.1_n;\n            query_loc.X += 0.5_n;\n            query_loc.Width -= 1;\n            query_loc.Height = 1;\n\n            for(int C : treeNPCQuery(query_loc, SORTMODE_NONE))\n            {\n                if(B != C && (NPC[C].vehiclePlr == A || NPC[C].playerTemp))\n                {\n                    if(CheckCollision(query_loc, NPC[C].Location))\n                    {\n                        still_on_vehicle = true;\n                        break;\n                    }\n                }\n            }\n\n            if(!still_on_vehicle)\n            {\n                NPC[B].vehiclePlr = 0;\n                NPC[B].vehicleYOffset = 0;\n            }\n            else\n                NPC[B].Location.SpeedX = 0;\n        }\n    }\n}\n\nvoid WaterCheck(const int A)\n{\n    Location_t tempLocation;\n    auto &p = Player[A];\n\n    if(p.Wet > 0)\n    {\n        p.Wet -= 1;\n        if(!InvincibilityTime)\n            p.Multiplier = 0;\n    }\n\n    if(p.Quicksand > 0)\n    {\n        p.Quicksand -= 1;\n        if(p.Quicksand == 0)\n            p.WetFrame = false;\n    }\n\n    if(UnderWater[p.Section])\n        p.Wet = 2;\n\n    if(p.Wet > 0)\n    {\n        p.SpinJump = false;\n        p.WetFrame = true;\n        p.Slide = false;\n    }\n    else if(p.WetFrame)\n    {\n        if(p.Location.SpeedY >= 3.1_n || p.Location.SpeedY <= -3.1_n)\n        {\n            p.WetFrame = false;\n            tempLocation.Width = 32;\n            tempLocation.Height = 32;\n            tempLocation.X = p.Location.X + (p.Location.Width - tempLocation.Width) / 2;\n            tempLocation.Y = p.Location.Y + p.Location.Height - tempLocation.Height;\n            NewEffect(EFFID_WATER_SPLASH, tempLocation);\n        }\n    }\n\n    bool already_in_maze = p.CurMazeZone;\n\n    Location_t query_loc = p.Location;\n\n    // check the hitbox the player will get when leaving water if in aquatic swim state\n    if(p.AquaticSwim)\n        query_loc.set_height_floor(Physics.PlayerHeight[p.Character][p.State]);\n\n    for(int B : treeWaterQuery(query_loc, SORTMODE_NONE))\n    {\n        if(!Water[B].Hidden)\n        {\n            if(CheckCollision(query_loc, Water[B].Location))\n            {\n                if(Water[B].Type == PHYSID_MAZE)\n                {\n                    if(p.CurMazeZone && (already_in_maze || p.CurMazeZone < B))\n                        continue;\n\n                    if(p.Mount == 2 || p.Fairy)\n                        continue;\n\n                    if(query_loc.Y + query_loc.Height - Physics.PlayerGravity < Water[B].Location.Y)\n                        continue;\n\n                    p.CurMazeZone = B;\n\n                    PhysEnv_Maze_PickDirection(p.Location, B, p.MazeZoneStatus);\n\n                    std::array<bool, 4> maze_controls = {Player[A].Controls.Left, Player[A].Controls.Up, Player[A].Controls.Right, Player[A].Controls.Down};\n\n                    if(maze_controls[p.MazeZoneStatus] || (p.Rolling && !maze_controls[0] && !maze_controls[1] && !maze_controls[2] && !maze_controls[3]))\n                        PlaySoundSpatial(SFX_HeroDash, p.Location);\n                    else\n                        p.CurMazeZone = 0;\n\n                    continue;\n                }\n\n                if(p.Wet == 0 && p.Mount != 2)\n                {\n                    p.FlyCount = 0;\n                    p.CanFly = false;\n                    p.CanFly2 = false;\n\n                    if(!p.Controls.Jump && !p.Controls.AltJump)\n                        p.CanJump = true;\n\n                    if(p.State == PLR_STATE_CYCLONE)\n                        p.DoubleJump = true;\n\n                    p.SwimCount = 0;\n\n                    if(p.Location.SpeedY > 0.5_n)\n                        p.Location.SpeedY = 0.5_n;\n                    if(p.Location.SpeedY < -1.5_n)\n                        p.Location.SpeedY = -1.5_n;\n\n                    if(!p.WetFrame && !p.Rolling)\n                    {\n                        if(p.Location.SpeedX > 0.5_n)\n                            p.Location.SpeedX = 0.5_n;\n                        if(p.Location.SpeedX < -0.5_n)\n                            p.Location.SpeedX = -0.5_n;\n                    }\n\n                    if(p.Location.SpeedY > 0 && !p.WetFrame)\n                    {\n                        tempLocation.Width = 32;\n                        tempLocation.Height = 32;\n                        tempLocation.X = p.Location.X + (p.Location.Width - tempLocation.Width) / 2;\n                        tempLocation.Y = p.Location.Y + p.Location.Height - tempLocation.Height;\n                        NewEffect(EFFID_WATER_SPLASH, tempLocation);\n                    }\n                }\n\n                p.Wet = 2;\n                p.SpinJump = false;\n\n                if(Water[B].Type == PHYSID_QUICKSAND)\n                    p.Quicksand = 3;\n            }\n        }\n    }\n\n    if(p.Mount == 2)\n    {\n        p.Wet = 0;\n        p.WetFrame = false;\n    }\n\n    if(p.Wet == 1)\n    {\n        if(p.Location.SpeedY < 0 && (p.Controls.AltJump || p.Controls.Jump) && !p.Controls.Down)\n        {\n            p.Jump = 12;\n            p.Location.SpeedY = Physics.PlayerJumpVelocity;\n\n            if(p.State == PLR_STATE_AQUATIC && p.AquaticSwim)\n                p.Jump += 8;\n        }\n    }\n    else if(p.Wet == 2 && p.Quicksand == 0)\n    {\n        if(iRand(100) >= 97)\n        {\n            num_t dx = dRand();\n            num_t dy = dRand();\n            if(p.Direction == 1)\n                tempLocation = newLoc(p.Location.X + p.Location.Width - dx * 8, p.Location.Y + 4 + dy * 8, 8, 8);\n            else\n                tempLocation = newLoc(p.Location.X - 8 + dx * 8, p.Location.Y + 4 + dy * 8, 8, 8);\n            if(!UnderWater[p.Section])\n            {\n                for(int B : treeWaterQuery(tempLocation, SORTMODE_NONE))\n                {\n                    if(CheckCollision(Water[B].Location, tempLocation))\n                    {\n                        // NewNpc set to 0 (pop it when it's no longer colliding with water)\n                        NewEffect(EFFID_AIR_BUBBLE, tempLocation, 1, ShadowMode);\n                        break;\n                    }\n                }\n            }\n            else\n            {\n                // NewNpc set to 1 (keep it on forever)\n                if(NewEffect(EFFID_AIR_BUBBLE, tempLocation, 1, ShadowMode))\n                    Effect[numEffects].NewNpc = 1;\n            }\n        }\n    }\n}\n\n// checks if player A is in the mouth of the pet of any player on the screen\nbool InOnscreenPet(int plr_A, const Screen_t& screen)\n{\n    const Player_t& p = Player[plr_A];\n\n    // in the mouth of any player's Pet?\n    if(p.Effect == PLREFF_PET_INSIDE)\n    {\n        // check it really is a player on this screen\n        for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n        {\n            int B = screen.players[plr_i];\n\n            if(p.Effect2 == B && Player[B].YoshiPlayer == plr_A)\n                return true;\n        }\n    }\n\n    return false;\n}\n\n// removes a player from a pet's mouth (so that their effect can be changed successfully)\nvoid RemoveFromPet(int plr_A)\n{\n    Player_t& p = Player[plr_A];\n\n    if(p.Effect == PLREFF_PET_INSIDE && p.Effect2 > 0 && p.Effect2 <= numPlayers && Player[p.Effect2].YoshiPlayer == plr_A)\n        Player[p.Effect2].YoshiPlayer = 0;\n\n    // set to no-collide mode with the other player (but this will be changed at the calling code)\n    p.Effect = PLREFF_NO_COLLIDE;\n}\n\nvoid PlayerCollide(const int A)\n{\n    Location_t tempLocation;\n    Location_t tempLocation3;\n    int HitSpot = 0;\n    auto &p1 = Player[A];\n\n// Check player collisions\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        auto &p2 = Player[B];\n\n        if(B != A && !p2.Dead && p2.TimeToLive == 0 &&\n           (p2.Effect == PLREFF_NORMAL || p2.Effect == PLREFF_WARP_PIPE) &&\n           !(p2.Mount == 2 || p1.Mount == 2) &&\n           (!BattleMode || (p1.Immune == 0 && p2.Immune == 0)))\n        {\n            tempLocation = p1.Location;\n\n            if(p1.StandingOnNPC != 0 && !FreezeNPCs)\n                tempLocation.SpeedY = NPC[p1.StandingOnNPC].Location.SpeedY;\n\n            tempLocation3 = p2.Location;\n\n            if(p2.StandingOnNPC != 0 && !FreezeNPCs)\n                tempLocation3.SpeedY = NPC[p2.StandingOnNPC].Location.SpeedY;\n\n            if(CheckCollision(tempLocation, tempLocation3))\n            {\n                HitSpot = FindCollision(tempLocation, tempLocation3);\n                if(HitSpot == 5)\n                {\n                    if(p1.StandUp2 && p1.Location.Y > p2.Location.Y)\n                        HitSpot = 3;\n                    else if(p2.StandUp2 && p1.Location.Y < p2.Location.Y)\n                        HitSpot = 1;\n                }\n\n                if(BattleMode)\n                {\n                    if(p2.SlideKill)\n                    {\n                        HitSpot = 0;\n                        PlayerHurt(A);\n                    }\n                    if(p1.SlideKill)\n                    {\n                        HitSpot = 0;\n                        PlayerHurt(B);\n                    }\n                    if(p1.Stoned && (p1.Location.SpeedX > 3 || p1.Location.SpeedX < -3 || HitSpot == 1))\n                    {\n                        PlayerHurt(B);\n                    }\n                    else if(p2.Stoned && (p2.Location.SpeedX > 3 || p2.Location.SpeedX < -3 || HitSpot == 3))\n                    {\n                        PlayerHurt(A);\n                    }\n                }\n\n                if(p1.Stoned || p2.Stoned)\n                    HitSpot = 0;\n\n                if(HitSpot == 2 || HitSpot == 4)\n                {\n                    if(!g_ClonedPlayerMode)\n                        PlaySoundSpatial(SFX_Skid, p1.Location);\n                    tempLocation = p1.Location;\n                    p1.Location.SpeedX = p2.Location.SpeedX;\n                    p2.Location.SpeedX = tempLocation.SpeedX;\n                    p1.Bumped = true;\n                    p2.Bumped = true;\n                }\n                else if(HitSpot == 1 || HitSpot == 3)\n                {\n                    Player_t& top_player = (HitSpot == 1) ? p1 : p2;\n                    Player_t& bottom_player = (HitSpot == 1) ? p2 : p1;\n                    int top_player_idx = (HitSpot == 1) ? A : B;\n\n                    if(!g_ClonedPlayerMode)\n                        PlaySoundSpatial(SFX_Stomp, top_player.Location);\n\n                    if(!top_player.CurMazeZone)\n                    {\n                        top_player.Location.Y = bottom_player.Location.Y - top_player.Location.Height - 0.1_n;\n                        PlayerPush(top_player_idx, 3);\n                    }\n\n                    top_player.Location.SpeedY = Physics.PlayerJumpVelocity;\n\n                    top_player.Jump = Physics.PlayerHeadJumpHeight;\n\n                    // yes, it's an SMBX 1.3 bug... :'(\n                    if(top_player.Character == 2)\n                        p1.Jump += 3;\n                    if(top_player.SpinJump)\n                        p1.Jump -= 6;\n\n                    bottom_player.Jump = 0;\n                    if(bottom_player.Location.SpeedY <= 0)\n                        bottom_player.Location.SpeedY = 0.1_n;\n                    bottom_player.CanJump = false;\n                    NewEffect(EFFID_WHACK, newLoc(top_player.Location.X + top_player.Location.Width / 2 - 16, top_player.Location.Y + top_player.Location.Height - 16));\n                }\n                else if(HitSpot == 5)\n                {\n                    if(p1.Location.to_right_of(p2.Location))\n                    {\n                        p1.Bumped2 = 1;\n                        p2.Bumped2 = -1;\n                    }\n                    else if(p2.Location.to_right_of(p1.Location))\n                    {\n                        p1.Bumped2 = -1;\n                        p2.Bumped2 = 1;\n                    }\n                    else if(iRand(2) == 0)\n                    {\n                        p1.Bumped2 = -1;\n                        p2.Bumped2 = 1;\n                    }\n                    else\n                    {\n                        p1.Bumped2 = 1;\n                        p2.Bumped2 = -1;\n                    }\n                    // If Player(A).Bumped2 < -1 Then Player(A).Bumped2 = -1 - Rnd\n                    // If Player(A).Bumped2 > 1 Then Player(A).Bumped2 = 1 + Rnd\n                    // If Player(B).Bumped2 < -1 Then Player(B).Bumped2 = -1 - Rnd\n                    // If Player(B).Bumped2 > 1 Then Player(B).Bumped2 = 1 + Rnd\n                }\n\n                if(HitSpot == 1)\n                {\n                    if((p1.MazeZoneStatus & 3) == MAZE_DIR_DOWN)\n                        p1.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                    if((p2.MazeZoneStatus & 3) == MAZE_DIR_UP)\n                        p2.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                }\n                else if(HitSpot == 2)\n                {\n                    if((p1.MazeZoneStatus & 3) == MAZE_DIR_LEFT)\n                        p1.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                    if((p2.MazeZoneStatus & 3) == MAZE_DIR_RIGHT)\n                        p2.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                }\n                else if(HitSpot == 3)\n                {\n                    if((p1.MazeZoneStatus & 3) == MAZE_DIR_UP)\n                        p1.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                    if((p2.MazeZoneStatus & 3) == MAZE_DIR_DOWN)\n                        p2.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                }\n                else if(HitSpot == 4)\n                {\n                    if((p1.MazeZoneStatus & 3) == MAZE_DIR_RIGHT)\n                        p1.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                    if((p2.MazeZoneStatus & 3) == MAZE_DIR_LEFT)\n                        p2.MazeZoneStatus |= MAZE_PLAYER_FLIP;\n                }\n\n                if(BattleMode)\n                {\n                    if(HitSpot == 1 && p1.Mount == 1)\n                    {\n                        PlayerHurt(B);\n                    }\n                    else if(HitSpot == 3 && p2.Mount == 1)\n                    {\n                        PlayerHurt(A);\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid PlayerGrabCode(const int A, bool DontResetGrabTime)\n{\n    // this code handles all the grab related stuff\n    // for grabbing something while walking into it, refer to the NPC collision code in sub UpdatePlayer()\n    Location_t tempLocation;\n    int LayerNPC = 0;\n    int B = 0;\n    bool tempBool = false;\n    num_t lyrX = 0;\n    num_t lyrY = 0;\n    auto &p = Player[A];\n\n    if(p.StandingOnNPC != 0 && p.HoldingNPC == 0)\n    {\n        if(NPC[p.StandingOnNPC]->GrabFromTop)\n        {\n            if(((p.Controls.Run && p.Controls.Down) || ((p.Controls.Down || p.Controls.Run) && p.GrabTime > 0)) && (p.RunRelease || p.GrabTime > 0) && p.TailCount == 0)\n            {\n                if((p.GrabTime >= 12 && p.Character < 3) || (p.GrabTime >= 16 && p.Character == 3) || (p.GrabTime >= 8 && p.Character == 4))\n                {\n                    p.Location.SpeedX = (num_t)p.GrabSpeed;\n                    p.GrabSpeed = 0;\n                    p.GrabTime = 0;\n                    p.TailCount = 0;\n\n                    if(p.Character == 1 || p.Character == 2)\n                    {\n                        UnDuck(Player[A]);\n                    }\n\n                    p.HoldingNPC = p.StandingOnNPC;\n                    p.Location.SpeedY = NPC[p.StandingOnNPC].Location.SpeedY;\n                    if(p.Location.SpeedY == 0)\n                        p.Location.SpeedY = 0.01_n;\n                    p.CanJump = false;\n                    if(NPC[p.StandingOnNPC]->IsAShell)\n                        p.Location.SpeedX = NPC[p.StandingOnNPC].Location.SpeedX;\n                    NPC[p.StandingOnNPC].HoldingPlayer = A;\n                    NPC[p.StandingOnNPC].CantHurt = Physics.NPCCanHurtWait;\n                    NPC[p.StandingOnNPC].CantHurtPlayer = A;\n                    NPC[p.StandingOnNPC].Direction = p.Direction;\n                    NPCFrames(p.StandingOnNPC);\n\n                    // Unbury code (v3)\n                    if(NPC[p.StandingOnNPC].Type == NPCID_ITEM_BURIED)\n                    {\n                        p.Location.SpeedX += NPC[p.StandingOnNPC].Location.SpeedX;\n\n                        NPC[p.StandingOnNPC].Direction = p.Direction;\n\n                        NPCUnbury(p.StandingOnNPC, 3);\n\n                        NPCFrames(p.StandingOnNPC);\n\n                        if(p.StandingOnNPC > 0 && p.StandingOnNPC <= numNPCs)\n                        {\n                            NPCQueues::Unchecked.push_back(p.StandingOnNPC);\n                            treeNPCUpdate(p.StandingOnNPC);\n                        }\n\n                        p.StandingOnNPC = 0;\n                    }\n                }\n                else\n                {\n                    if(p.GrabTime == 0)\n                    {\n                        if(NPC[p.StandingOnNPC].Type == NPCID_ITEM_BURIED || NPCIsVeggie(NPC[p.StandingOnNPC]))\n                            PlaySoundSpatial(SFX_Grab2, p.Location);\n                        else\n                            PlaySoundSpatial(SFX_Grab, p.Location);\n                        p.FrameCount = 0;\n                        p.GrabSpeed = (numf_t)p.Location.SpeedX;\n                    }\n                    p.Location.SpeedX = 0;\n                    p.GrabTime += 1;\n                    p.Slide = false;\n                }\n            }\n            else\n                p.GrabTime = 0;\n        }\n        else\n            p.GrabTime = 0;\n    }\n    else if(!DontResetGrabTime)\n        p.GrabTime = 0;\n\n    if(p.HoldingNPC > numNPCs) // Can't hold an NPC that is dead\n        p.HoldingNPC = 0;\n\n    if(p.HoldingNPC > 0)\n    {\n        lyrX = NPC[p.HoldingNPC].Location.X;\n        lyrY = NPC[p.HoldingNPC].Location.Y;\n        LayerNPC = p.HoldingNPC;\n        if(NPC[p.HoldingNPC].Type == NPCID_FLY_BLOCK || NPC[p.HoldingNPC].Type == NPCID_FLY_CANNON)\n        {\n            if(p.Jump == 0)\n                NPC[p.HoldingNPC].Special = 1;\n            else if(p.Jump > 0 && NPC[p.HoldingNPC].Special == 1)\n            {\n                // IMPORTANT: this was truncated until v1.3.8-dev. The new behavior is accurate to VB6. Confirm that it does not cause issues.\n                // FIXME: simplify this -- add a new vb6round_div2 call\n                p.Jump = num_t::vb6round(p.Jump * 1.5_n);\n                NPC[p.HoldingNPC].Special = 0;\n            }\n            if(NPC[p.HoldingNPC].Type == NPCID_FLY_CANNON)\n            {\n                NPC[p.HoldingNPC].Special2 += 1;\n                if(p.SpinJump)\n                {\n                    if(NPC[p.HoldingNPC].Special3 == 0)\n                        NPC[p.HoldingNPC].Special3 = p.Direction;\n                    else if(NPC[p.HoldingNPC].Special3 == -p.Direction && NPC[p.HoldingNPC].Special2 >= 25)\n                    {\n                        NPC[p.HoldingNPC].Special3 = p.Direction;\n                        NPC[p.HoldingNPC].Special2 = 100;\n                    }\n                    else if(NPC[p.HoldingNPC].Special2 >= 25)\n                        NPC[p.HoldingNPC].Special2 = 25;\n\n                }\n\n\n                if(NPC[p.HoldingNPC].Special2 == 20 || NPC[p.HoldingNPC].Special2 == 40 || NPC[p.HoldingNPC].Special2 == 60 || NPC[p.HoldingNPC].Special2 == 80)\n                {\n                    if(NPC[p.HoldingNPC].Special2 == 20 || NPC[p.HoldingNPC].Special2 == 60)\n                        B = 1;\n                    else if(NPC[p.HoldingNPC].Special2 == 40)\n                        B = 2;\n                    else if(NPC[p.HoldingNPC].Special2 == 80)\n                    {\n                        B = 3;\n                        NPC[p.HoldingNPC].Special2 = 0;\n                    }\n\n                        PlaySoundSpatial(SFX_FlameThrower, p.Location);\n\n                    // For B = 1 To 3\n                        numNPCs++;\n                        NPC[numNPCs].CantHurt = 10000;\n                        NPC[numNPCs].CantHurtPlayer = A;\n                        NPC[numNPCs].BattleOwner = A;\n                        NPC[numNPCs].Type = NPCID_PET_FIRE;\n                        NPC[numNPCs].Location.Width = NPC[numNPCs]->TWidth;\n                        NPC[numNPCs].Location.Height = NPC[numNPCs]->THeight;\n                        NPC[numNPCs].Active = true;\n                        NPC[numNPCs].TimeLeft = NPC[p.HoldingNPC].TimeLeft;\n                        NPC[numNPCs].Layer = LAYER_SPAWNED_NPCS;\n                        NPC[numNPCs].Location.Y = NPC[p.HoldingNPC].Location.Y + NPC[p.HoldingNPC].Location.Height - NPC[numNPCs].Location.Height;\n                        NPC[numNPCs].Direction = p.Direction;\n\n                        if(NPC[numNPCs].Direction == 1)\n                            NPC[numNPCs].Location.X = NPC[p.HoldingNPC].Location.X + NPC[p.HoldingNPC].Location.Width * 2 - 8;\n                        else\n                            NPC[numNPCs].Location.X = NPC[p.HoldingNPC].Location.X - NPC[numNPCs].Location.Width - NPC[p.HoldingNPC].Location.Width + 8;\n\n                        if(B == 1)\n                            NPC[numNPCs].Location.SpeedX = 7 * NPC[numNPCs].Direction;\n                        else if(B == 2)\n                        {\n                            NPC[numNPCs].Location.SpeedX = 6.5_n * NPC[numNPCs].Direction;\n                            NPC[numNPCs].Location.SpeedY = -1.5_n;\n                        }\n                        else\n                        {\n                            NPC[numNPCs].Location.SpeedX = 6.5_n * NPC[numNPCs].Direction;\n                            NPC[numNPCs].Location.SpeedY = 1.5_n;\n                        }\n\n                        NPC[numNPCs].Location.SpeedX += p.Location.SpeedX / 3.5_ri;\n\n                        NPC[numNPCs].Projectile = true;\n                        NPC[numNPCs].Frame = EditorNPCFrame(NPC[numNPCs].Type, NPC[numNPCs].Direction);\n\n                        syncLayers_NPC(numNPCs);\n                    // Next B\n                }\n            }\n        }\n\n        if(NPC[p.HoldingNPC].Type == NPCID_ICE_CUBE)\n            NPC[p.HoldingNPC].Special3 = 0;\n        NPC[p.HoldingNPC].TimeLeft = Physics.NPCTimeOffScreen;\n        NPC[p.HoldingNPC].Effect = NPCEFF_NORMAL;\n        NPC[p.HoldingNPC].CantHurt = Physics.NPCCanHurtWait;\n        NPC[p.HoldingNPC].CantHurtPlayer = A;\n        if(NPCIsVeggie(NPC[p.HoldingNPC]))\n            NPC[p.HoldingNPC].CantHurt = 1000;\n\n        if(p.Controls.Run || p.ForceHold > 0)\n        {\n            // fix a graphical bug where the NPC would stutter in the player's hands\n            num_t use_w = (g_config.fix_visual_bugs) ? num_t::round(NPC[p.HoldingNPC].Location.Width) : NPC[p.HoldingNPC].Location.Width;\n\n        // hold above head\n            if(p.Character == 3 || p.Character == 4 || (p.Duck))\n            {\n                NPC[p.HoldingNPC].Bouce = true;\n                NPC[p.HoldingNPC].Location.X = p.Location.X + (p.Location.Width - use_w) / 2;\n\n                if(p.Character == 3) // princess peach\n                {\n                    if(p.State == 1)\n                        NPC[p.HoldingNPC].Location.Y = p.Location.Y - NPC[p.HoldingNPC].Location.Height;\n                    else\n                        NPC[p.HoldingNPC].Location.Y = p.Location.Y - NPC[p.HoldingNPC].Location.Height + 6;\n                }\n                else // toad\n                {\n                    if(p.State == 1)\n                        NPC[p.HoldingNPC].Location.Y = p.Location.Y - NPC[p.HoldingNPC].Location.Height + 6;\n                    else\n                    {\n                        if(NPC[p.HoldingNPC].Type == NPCID_PLR_FIREBALL || NPC[p.HoldingNPC].Type == NPCID_PLR_ICEBALL)\n                        {\n                            NPC[p.HoldingNPC].Location.X += dRand() * 4 - 2;\n                            NPC[p.HoldingNPC].Location.Y = p.Location.Y - NPC[p.HoldingNPC].Location.Height - 4 + dRand() * 4 - 2;\n                        }\n                        else\n                            NPC[p.HoldingNPC].Location.Y = p.Location.Y - NPC[p.HoldingNPC].Location.Height + 10;\n                    }\n                }\n            }\n            else\n            {\n                if(p.Direction > 0)\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + Physics.PlayerGrabSpotX[p.Character][p.State];\n                else\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + p.Location.Width - Physics.PlayerGrabSpotX[p.Character][p.State] - use_w;\n\n                NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n            }\n\n            if(NPC[p.HoldingNPC].Type == NPCID_TOOTHYPIPE && !FreezeNPCs)\n            {\n                if(NPC[p.HoldingNPC].Special == 0)\n                {\n                    NPC[p.HoldingNPC].Special = 1;\n                    NPC[p.HoldingNPC].Special2 = numNPCs + 1;\n                    numNPCs++;\n                    NPC[numNPCs].Active = true;\n                    NPC[numNPCs].Section = p.Section;\n                    NPC[numNPCs].TimeLeft = 100;\n                    NPC[numNPCs].Type = NPCID_TOOTHY;\n                    NPC[numNPCs].Location.Height = 32;\n                    NPC[numNPCs].Location.Width = 48;\n                    NPC[numNPCs].Special = A;\n                    if(p.Direction > 0)\n                        NPC[numNPCs].Frame = 2;\n                    syncLayers_NPC(numNPCs);\n                }\n\n                for(int B : NPCQueues::Active.no_change)\n                {\n                    if(NPC[B].Type == NPCID_TOOTHY && NPC[B].Special == A)\n                    {\n                        NPC[B].CantHurt = 10;\n                        NPC[B].CantHurtPlayer = A;\n                        NPC[B].Projectile = true;\n                        NPC[B].Direction = p.Direction;\n                        NPC[B].TimeLeft = 100;\n                        if(p.Direction > 0)\n                            NPC[B].Location.X = NPC[p.HoldingNPC].Location.X + 32;\n                        else\n                            NPC[B].Location.X = NPC[p.HoldingNPC].Location.X - NPC[B].Location.Width;\n                        NPC[B].Location.Y = NPC[p.HoldingNPC].Location.Y;\n                    }\n                }\n            }\n        }\n        else\n        {\n            NPC[p.HoldingNPC].Location.SpeedX = 0;\n            NPC[p.HoldingNPC].Location.SpeedY = 0;\n            // this means that the NPC will be killed if it is inside a wall next frame\n            NPC[p.HoldingNPC].WallDeath = 5;\n\n            if(NPC[p.HoldingNPC].Type == NPCID_HEAVY_THROWER)\n                NPCHit(p.HoldingNPC, 3, p.HoldingNPC);\n\n            if(NPC[p.HoldingNPC]->IsACoin && !p.Controls.Down) // Smoke effect for coins\n                NewEffect(EFFID_SMOKE_S3, NPC[p.HoldingNPC].Location);\n\n            if(p.Controls.Up && !NPC[p.HoldingNPC]->IsACoin && NPC[p.HoldingNPC].Type != NPCID_BULLET) // Throw the npc up\n            {\n                if(NPC[p.HoldingNPC]->IsAShell || NPC[p.HoldingNPC].Type == NPCID_SLIDE_BLOCK || NPC[p.HoldingNPC].Type == NPCID_ICE_CUBE)\n                {\n                    if(p.Controls.Left || p.Controls.Right) // Up and forward\n                    {\n                        NPC[p.HoldingNPC].Location.SpeedX = Physics.NPCShellSpeed * p.Direction;\n                        NPC[p.HoldingNPC].Location.SpeedY = -7;\n                        tempLocation.Height = 0;\n                        tempLocation.Width = 0;\n                        tempLocation.Y = (p.Location.Y + NPC[p.HoldingNPC].Location.Y * 4) / 5;\n                        tempLocation.X = (p.Location.X + NPC[p.HoldingNPC].Location.X * 4) / 5;\n                        if(NPC[p.HoldingNPC].Type != NPCID_ICE_CUBE)\n                            NewEffect(EFFID_STOMP_INIT, tempLocation);\n                    }\n                    else\n                    {\n                        NPC[p.HoldingNPC].Location.SpeedY = -Physics.NPCShellSpeedY;\n                        tempLocation.Height = 0;\n                        tempLocation.Width = 0;\n                        tempLocation.Y = (p.Location.Y + NPC[p.HoldingNPC].Location.Y * 4) / 5;\n                        tempLocation.X = (p.Location.X + NPC[p.HoldingNPC].Location.X * 4) / 5;\n                        if(NPC[p.HoldingNPC].Type != NPCID_ICE_CUBE)\n                            NewEffect(EFFID_STOMP_INIT, tempLocation);\n                    }\n                }\n                else\n                {\n                    if(p.Controls.Left || p.Controls.Right) // Up and forward\n                    {\n                        if(p.Character == 3 || p.Character == 4)\n                        {\n                            NPC[p.HoldingNPC].Location.SpeedX = 5 * p.Direction;\n                            NPC[p.HoldingNPC].Location.SpeedY = -6;\n                        }\n                        else\n                        {\n                            NPC[p.HoldingNPC].Location.SpeedY = -8;\n                            NPC[p.HoldingNPC].Location.SpeedX = 3 * p.Direction;\n                        }\n                    }\n                    else\n                    {\n                        NPC[p.HoldingNPC].Location.SpeedY = -10;\n                        if(p.Character == 3) // peach\n                            NPC[p.HoldingNPC].Location.SpeedY = -9;\n                    }\n                }\n\n                if(NPCIsVeggie(NPC[p.HoldingNPC]) || NPC[p.HoldingNPC].Type == NPCID_BLU_GUY || NPC[p.HoldingNPC].Type == NPCID_RED_GUY || NPC[p.HoldingNPC].Type == NPCID_JUMPER_S3 || NPC[p.HoldingNPC].Type == NPCID_BIRD || NPC[p.HoldingNPC].Type == NPCID_RED_SPIT_GUY || NPC[p.HoldingNPC].Type == NPCID_BLU_SPIT_GUY || NPC[p.HoldingNPC].Type == NPCID_GRY_SPIT_GUY || NPC[p.HoldingNPC].Type == NPCID_BOMB || NPC[p.HoldingNPC].Type == NPCID_WALK_BOMB_S2 || NPC[p.HoldingNPC].Type == NPCID_CARRY_BLOCK_A || NPC[p.HoldingNPC].Type == NPCID_CARRY_BLOCK_B || NPC[p.HoldingNPC].Type == NPCID_CARRY_BLOCK_C || NPC[p.HoldingNPC].Type == NPCID_CARRY_BLOCK_D || NPC[p.HoldingNPC].Type == NPCID_SPIT_BOSS_BALL || NPC[p.HoldingNPC].Type == NPCID_TIMER_S2 || NPC[p.HoldingNPC].Type == NPCID_PLR_FIREBALL || NPC[p.HoldingNPC].Type == NPCID_PLR_ICEBALL || NPC[p.HoldingNPC].Type == NPCID_DOOR_MAKER || NPC[p.HoldingNPC].Type == NPCID_CHAR3_HEAVY)\n                    PlaySoundSpatial(SFX_Throw, p.Location);\n                else\n                    PlaySoundSpatial(SFX_ShellHit, p.Location);\n\n                NPC[p.HoldingNPC].Projectile = true;\n            }\n            else if(p.Controls.Down && NPC[p.HoldingNPC].Type != NPCID_BULLET) // Drop\n            {\n                tempBool = false;\n                if((p.Direction == 1 && p.Location.SpeedX > 3) || (p.Direction == -1 && p.Location.SpeedX < -3))\n                    tempBool = true;\n\n                if(tempBool && NPC[p.HoldingNPC].Type == NPCID_FLIPPED_RAINBOW_SHELL)\n                {\n                    p.Location.SpeedX = 0;\n                    NPC[p.HoldingNPC].Location.SpeedX = Physics.NPCShellSpeed * p.Direction;\n                    NPC[p.HoldingNPC].Projectile = true;\n                    NPC[p.HoldingNPC].CantHurt = 0;\n                    NPC[p.HoldingNPC].CantHurtPlayer = 0;\n                    NPC[p.HoldingNPC].HoldingPlayer = 0;\n                    PlaySoundSpatial(SFX_ShellHit, p.Location);\n                    NewEffect(EFFID_WHIP, newLoc(NPC[p.HoldingNPC].Location.X, NPC[p.HoldingNPC].Location.Y + NPC[p.HoldingNPC].Location.Height - 16));\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + (p.Location.Width - NPC[p.HoldingNPC].Location.Width) / 2;\n                    NPC[p.HoldingNPC].Location.Y = p.Location.Y + p.Location.Height - NPC[p.HoldingNPC].Location.Height;\n                    p.Location.Y = NPC[p.HoldingNPC].Location.Y - p.Location.Height;\n                    NPC[p.HoldingNPC].Location.SpeedY = p.Location.SpeedY;\n                    p.StandingOnNPC = p.HoldingNPC;\n                    p.HoldingNPC = 0;\n                    p.ShellSurf = true;\n                    p.Jump = 0;\n                    p.Location.SpeedY = 10;\n                }\n                else\n                {\n                    if(p.Direction == 1)\n                        NPC[p.HoldingNPC].Location.X = p.Location.X + p.Location.Width + 0.1_n;\n                    else\n                        NPC[p.HoldingNPC].Location.X = p.Location.X - NPC[p.HoldingNPC].Location.Width - 0.1_n;\n                    NPC[p.HoldingNPC].Projectile = false;\n                    if(NPC[p.HoldingNPC].Type == NPCID_VINE_BUG)\n                        NPC[p.HoldingNPC].Projectile = true;\n                    if(p.StandingOnNPC != 0)\n                        NPC[p.HoldingNPC].Location.Y += NPC[p.StandingOnNPC].Location.SpeedY;\n                }\n                if(NPC[p.HoldingNPC].Type == NPCID_PLR_FIREBALL || NPC[p.HoldingNPC].Type == NPCID_PLR_ICEBALL || NPC[p.HoldingNPC].Type == NPCID_CHAR3_HEAVY)\n                {\n                    NPC[p.HoldingNPC].Location.X = p.Location.X + (p.Location.Width - NPC[p.HoldingNPC].Location.Width) / 2;\n                    if(p.State == 1)\n                        NPC[p.HoldingNPC].Location.Y = p.Location.Y - NPC[p.HoldingNPC].Location.Height;\n                    else\n                        NPC[p.HoldingNPC].Location.Y = p.Location.Y - NPC[p.HoldingNPC].Location.Height + 6;\n                    PlaySoundSpatial(SFX_Throw, p.Location);\n                    NPC[p.HoldingNPC].Location.SpeedX = 0;\n                    NPC[p.HoldingNPC].Location.SpeedY = 20;\n                }\n            }\n            else if(!NPC[p.HoldingNPC]->IsAShell &&\n                    NPC[p.HoldingNPC].Type != NPCID_SLIDE_BLOCK &&\n                    NPC[p.HoldingNPC].Type != NPCID_ICE_CUBE &&\n                    !NPC[p.HoldingNPC]->IsACoin) // if not a shell or a coin the kick it up and forward\n            {\n            // peach\n                if(p.Character == 3)\n                {\n                    if(NPC[p.HoldingNPC].Type == NPCID_PLR_FIREBALL || NPC[p.HoldingNPC].Type == NPCID_PLR_ICEBALL || (p.Location.SpeedY != 0 && p.StandingOnNPC == 0 && p.Slope == 0))\n                    {\n                        NPC[p.HoldingNPC].Location.SpeedX = 5 * p.Direction + p.Location.SpeedX * 0.3_r;\n                        NPC[p.HoldingNPC].Location.SpeedY = 3;\n                    }\n                    else\n                    {\n                        NPC[p.HoldingNPC].Location.SpeedX = 5 * p.Direction + p.Location.SpeedX * 0.3_r;\n                        NPC[p.HoldingNPC].Location.SpeedY = 0;\n                    }\n            // toad\n                }\n                else if(p.Character == 4)\n                {\n                    if(NPC[p.HoldingNPC].Type == NPCID_PLR_FIREBALL || NPC[p.HoldingNPC].Type == NPCID_PLR_ICEBALL || (p.Location.SpeedY != 0 && p.StandingOnNPC == 0 && p.Slope == 0))\n                    {\n                        NPC[p.HoldingNPC].Location.SpeedX = 6 * p.Direction + p.Location.SpeedX * 0.4_r;\n                        NPC[p.HoldingNPC].Location.SpeedY = 3.5_n;\n                    }\n                    else\n                    {\n                        NPC[p.HoldingNPC].Location.SpeedX = 6 * p.Direction + p.Location.SpeedX * 0.4_r;\n                        NPC[p.HoldingNPC].Location.SpeedY = 0;\n                        NPC[p.HoldingNPC].CantHurt = NPC[p.HoldingNPC].CantHurt * 2;\n                    }\n                }\n                else\n                {\n                    NPC[p.HoldingNPC].Location.SpeedX = 5 * p.Direction;\n                    NPC[p.HoldingNPC].Location.SpeedY = -6;\n                }\n                NPC[p.HoldingNPC].Projectile = true;\n                if(NPCIsVeggie(NPC[p.HoldingNPC]) || NPC[p.HoldingNPC].Type == NPCID_BLU_GUY || NPC[p.HoldingNPC].Type == NPCID_RED_GUY || NPC[p.HoldingNPC].Type == NPCID_JUMPER_S3 || NPC[p.HoldingNPC].Type == NPCID_BIRD || NPC[p.HoldingNPC].Type == NPCID_RED_SPIT_GUY || NPC[p.HoldingNPC].Type == NPCID_BLU_SPIT_GUY || NPC[p.HoldingNPC].Type == NPCID_GRY_SPIT_GUY || NPC[p.HoldingNPC].Type == NPCID_BOMB || NPC[p.HoldingNPC].Type == NPCID_WALK_BOMB_S2 || NPC[p.HoldingNPC].Type == NPCID_CARRY_BLOCK_A || NPC[p.HoldingNPC].Type == NPCID_CARRY_BLOCK_B || NPC[p.HoldingNPC].Type == NPCID_CARRY_BLOCK_C || NPC[p.HoldingNPC].Type == NPCID_CARRY_BLOCK_D || NPC[p.HoldingNPC].Type == NPCID_SPIT_BOSS_BALL || NPC[p.HoldingNPC].Type == NPCID_TIMER_S2 || NPC[p.HoldingNPC].Type == NPCID_PLR_FIREBALL || NPC[p.HoldingNPC].Type == NPCID_PLR_ICEBALL || NPC[p.HoldingNPC].Type == NPCID_DOOR_MAKER || NPC[p.HoldingNPC].Type == NPCID_CHAR3_HEAVY)\n                    PlaySoundSpatial(SFX_Throw, p.Location);\n                else if(NPC[p.HoldingNPC].Type == NPCID_BULLET)\n                    PlaySoundSpatial(SFX_Bullet, p.Location);\n                else\n                    PlaySoundSpatial(SFX_ShellHit, p.Location);\n            }\n            else if(NPC[p.HoldingNPC]->IsAShell)\n            {\n                NPC[p.HoldingNPC].Location.SpeedY = 0;\n                NPC[p.HoldingNPC].Location.SpeedX = 0;\n                NPC[p.HoldingNPC].HoldingPlayer = 0;\n                NPC[p.HoldingNPC].CantHurt = 0;\n                NPC[p.HoldingNPC].CantHurtPlayer = 0;\n                NPC[p.HoldingNPC].Projectile = false;\n                NPCHit(p.HoldingNPC, 1, A);\n                tempLocation.Height = 0;\n                tempLocation.Width = 0;\n                tempLocation.Y = (p.Location.Y + NPC[p.HoldingNPC].Location.Y * 4) / 5;\n                tempLocation.X = (p.Location.X + NPC[p.HoldingNPC].Location.X * 4) / 5;\n                NewEffect(EFFID_STOMP_INIT, tempLocation);\n            }\n            else if(NPC[p.HoldingNPC].Type == NPCID_ICE_CUBE)\n            {\n                PlaySoundSpatial(SFX_ShellHit, p.Location);\n                NPC[p.HoldingNPC].Location.SpeedX = Physics.NPCShellSpeed * p.Direction;\n                NPC[p.HoldingNPC].CantHurt = Physics.NPCCanHurtWait;\n                NPC[p.HoldingNPC].CantHurtPlayer = A;\n                NPC[p.HoldingNPC].Projectile = true;\n            }\n            if(NPC[p.HoldingNPC].Type == NPCID_BOMB && NPC[p.HoldingNPC].Location.SpeedX != 0)\n            {\n                NPC[p.HoldingNPC].Location.SpeedX += p.Location.SpeedX / 2;\n                if(p.StandingOnNPC != 0)\n                    NPC[p.HoldingNPC].Location.SpeedX += NPC[p.StandingOnNPC].Location.SpeedX;\n            }\n            if(NPC[p.HoldingNPC].Type == NPCID_PLR_FIREBALL && NPC[p.HoldingNPC].Special == 4) // give toad fireballs a little spunk\n            {\n                if(NPC[p.HoldingNPC].Location.SpeedY < 0)\n                    NPC[p.HoldingNPC].Location.SpeedY += NPC[p.HoldingNPC].Location.SpeedY / 10;\n            }\n            if(NPC[p.HoldingNPC].Type == NPCID_CHAR3_HEAVY)\n            {\n                if(p.Location.SpeedX != 0 && NPC[p.HoldingNPC].Location.SpeedX != 0)\n                    NPC[p.HoldingNPC].Location.SpeedX += p.Location.SpeedX / 2;\n            }\n\n            // this block was misleadingly left-indented in VB6, but its nesting in C++ is accurate to its nesting in VB6, and now I'm fixing the indentation to match the nesting -- ds-sloth\n            if(NPC[p.HoldingNPC].Type == NPCID_CHAR4_HEAVY)\n            {\n                NPC[p.HoldingNPC].Special5 = A;\n                NPC[p.HoldingNPC].Special4 = p.Direction; // Special6 in SMBX 1.3\n                NPC[p.HoldingNPC].Location.SpeedY = -8;\n                NPC[p.HoldingNPC].Location.SpeedX = 12 * p.Direction + p.Location.SpeedX;\n                NPC[p.HoldingNPC].Projectile = true;\n            }\n\n            PlayerThrownNpcMazeCheck(p, NPC[p.HoldingNPC]);\n\n            NPC[p.HoldingNPC].HoldingPlayer = 0;\n            p.HoldingNPC = 0;\n        }\n\n        if(LayerNPC > 0 && LayerNPC <= numNPCs && (NPC[LayerNPC].Location.X != lyrX || NPC[LayerNPC].Location.Y != lyrY))\n            treeNPCUpdate(LayerNPC);\n    }\n\n    if(LayerNPC > 0)\n    {\n        int B = NPC[LayerNPC].AttLayer;\n        if(B != LAYER_NONE && B != LAYER_DEFAULT)\n            SetLayerSpeed(B, NPC[LayerNPC].Location.X - lyrX, NPC[LayerNPC].Location.Y - lyrY, false);\n    }\n}\n\nvoid LinkFrame(const int A)\n{\n    LinkFrame(Player[A]);\n}\n\nvoid LinkFrame(Player_t &p)\n{\n    Location_t tempLocation;\n    //auto &p = Player[A];\n\n    p.MountOffsetY = 0;\n\n    // Hurt frame\n    if(p.FrameCount == -10)\n    {\n        if(p.SwordPoke == 0)\n        {\n            if(p.Location.SpeedY == 0 ||\n               p.StandingOnNPC != 0 ||\n               p.Slope != 0 || p.Wet > 0 ||\n               p.Immune == 0) // Hurt Frame\n               p.FrameCount = 0;\n            else\n            {\n                p.Frame = 11;\n                return;\n            }\n        }\n        else\n            p.FrameCount = 0;\n    }\n\n    if(p.Stoned)\n    {\n        p.Frame = 12;\n        if(p.Location.SpeedX != 0)\n        {\n            if(p.Location.SpeedY == 0 || p.Slope > 0 || p.StandingOnNPC != 0)\n            {\n                if(p.SlideCounter <= 0)\n                {\n                    p.SlideCounter = 2 + iRand_round(2);\n                    tempLocation.Y = p.Location.Y + p.Location.Height - 5;\n                    tempLocation.X = p.Location.X + p.Location.Width / 2 - 4;\n                    NewEffect(EFFID_SKID_DUST, tempLocation, 1, ShadowMode);\n                }\n            }\n        }\n        return;\n    }\n\n    if(p.Rolling)\n    {\n        p.FrameCount = (p.FrameCount + 1) & 15;\n        p.Frame = 12 + p.FrameCount / 4;\n\n        if(p.State == PLR_STATE_POLAR)\n            p.Frame = 11;\n\n        return;\n    }\n\n    if(!LevelSelect && p.Effect == PLREFF_NORMAL && p.FireBallCD == 0)\n    {\n        if(p.Controls.Left)\n            p.Direction = -1;\n        if(p.Controls.Right)\n            p.Direction = 1;\n    }\n\n    if(p.Fairy)\n        return;\n\n    if(p.SwordPoke < 0) // Drawing back\n    {\n        if(!p.Duck)\n            p.Frame = 6;\n        else\n            p.Frame = 8;\n    }\n    else if(p.SwordPoke > 0) // Stabbing\n    {\n        if(!p.Duck)\n            p.Frame = 7;\n        else\n            p.Frame = 8;\n    }\n    else if(p.Mount == 2) // Clown Car\n    {\n        p.Frame = 1;\n        p.MountFrame = SpecialFrame[2];\n        if(p.Direction == 1)\n            p.MountFrame += 4;\n    }\n    else if(p.Duck) // Ducking\n        p.Frame = 5;\n    else if(p.WetFrame && p.Location.SpeedY != 0 && p.Slope == 0 && p.StandingOnNPC == 0 && !p.Duck && p.Quicksand == 0) // Link is swimming\n    {\n        if(p.Location.SpeedY < 0.5_n || p.Frame != 3)\n        {\n            if(p.Frame != 1 && p.Frame != 2 && p.Frame != 3 && p.Frame != 4)\n                p.FrameCount = 6;\n\n            p.FrameCount += 1;\n\n            if(p.FrameCount < 6)\n                p.Frame = 3;\n            else if(p.FrameCount < 12)\n                p.Frame = 2;\n            else if(p.FrameCount < 18)\n                p.Frame = 3;\n            else if(p.FrameCount < 24)\n                p.Frame = 1;\n            else\n            {\n                p.Frame = 3;\n                p.FrameCount = 0;\n            }\n        }\n        else\n            p.Frame = 3;\n    }\n    else if(p.Location.SpeedY != 0 && p.StandingOnNPC == 0 && p.Slope == 0 && !(p.Quicksand > 0 && p.Location.SpeedY > 0)) // Jumping/falling\n    {\n        if(CanWallJump && (p.Pinched.Left2 == 2 || p.Pinched.Right4 == 2) && (!p.SlippyWall || p.State == PLR_STATE_POLAR) && !(p.State == PLR_STATE_CYCLONE && !p.DoubleJump))\n        {\n            s_makeDust(p, 8, tempLocation);\n            p.Frame = 11;\n        }\n        else if(p.Location.SpeedY < 0)\n        {\n            if(p.Controls.Up)\n                p.Frame = 10;\n            else\n                p.Frame = 5;\n        }\n        else\n        {\n            if(p.Controls.Down)\n                p.Frame = 9;\n            else\n                p.Frame = 3;\n        }\n    }\n    else if(p.Location.SpeedX == 0 || (p.Slippy && !p.Controls.Left && !p.Controls.Right)) // Standing\n        p.Frame = 1;\n    else // Running\n    {\n        p.FrameCount += 1;\n\n        if(p.Location.SpeedX > Physics.PlayerWalkSpeed - 1.5_n || p.Location.SpeedX < -Physics.PlayerWalkSpeed + 1.5_n)\n            p.FrameCount += 1;\n\n        if(p.Location.SpeedX > Physics.PlayerWalkSpeed || p.Location.SpeedX < -Physics.PlayerWalkSpeed)\n            p.FrameCount += 1;\n\n        if(p.Location.SpeedX > Physics.PlayerWalkSpeed + 1 || p.Location.SpeedX < -Physics.PlayerWalkSpeed - 1)\n            p.FrameCount += 1;\n\n        if(p.Location.SpeedX > Physics.PlayerWalkSpeed + 2 || p.Location.SpeedX < -Physics.PlayerWalkSpeed - 2)\n            p.FrameCount += 1;\n\n        if(p.FrameCount >= 8)\n        {\n            p.FrameCount = 0;\n            p.Frame -= 1;\n        }\n\n        if(p.Frame <= 0)\n            p.Frame = 4;\n        else if(p.Frame >= 5)\n            p.Frame = 1;\n\n        if(p.Location.SpeedX >= Physics.PlayerRunSpeed * 0.9_n || p.Location.SpeedX <= -Physics.PlayerRunSpeed * 0.9_n)\n        {\n            if(p.SlideCounter <= 0)\n            {\n                PlaySoundSpatial(SFX_HeroDash, p.Location);\n                p.SlideCounter = 2 + iRand_round(2);\n                tempLocation.Y = p.Location.Y + p.Location.Height - 4;\n\n                if(p.Location.SpeedX < 0)\n                    tempLocation.X = p.Location.X + p.Location.Width / 2 - 6 - 4;\n                else\n                    tempLocation.X = p.Location.X + p.Location.Width / 2 + 6 - 4;\n\n                NewEffect(EFFID_SKID_DUST, tempLocation, 1, ShadowMode);\n            }\n        }\n    }\n}\n\nvoid PlayerEffects(const int A)\n{\n    int B = 0;\n    // float C = 0;\n    // float D = 0;\n    bool tempBool = false;\n    auto &p = Player[A];\n\n    if(p.Effect != PLREFF_WAITING && p.Fairy)\n    {\n        p.Fairy = false;\n        SizeCheck(Player[A]);\n    }\n\n    p.TailCount = 0;\n    // p.Pinched1 = 0;\n    // p.Pinched2 = 0;\n    // p.Pinched3 = 0;\n    // p.Pinched4 = 0;\n    // p.NPCPinched = 0;\n    p.Pinched.reset_non_strict();\n    p.SwordPoke = 0;\n\n    if(!p.YoshiBlue && p.Effect != PLREFF_STONE)\n    {\n        p.CanFly = false;\n        p.CanFly2 = false;\n        p.RunCount = 0;\n    }\n\n    p.Immune2 = false;\n\n    // in shared screen mode, give wings to players who are above the screen waiting to exit a warp\n    if(PlayerWaitingInWarp(p))\n    {\n        Screen_t& s = ScreenByPlayer(A);\n        if(s.Type == ScreenTypes::SharedScreen && (p.Location.Y + p.Location.Height < -vScreenByPlayer(A).Y))\n        {\n            int O = CheckNearestLiving(A);\n\n            if(O && p.Location.Y < Player[O].Location.Y - 300)\n            {\n                SharedScreenAvoidJump_Pre(s);\n                p.Dead = true;\n                p.Effect = PLREFF_COOP_WINGS;\n                p.Effect2 = 0;\n                SizeCheck(p);\n                SharedScreenAvoidJump_Post(s, 0);\n                return;\n            }\n        }\n    }\n\n    if(p.Effect == PLREFF_TURN_BIG) // Player growing effect\n    {\n        p.Frame = 1;\n        p.Effect2 += 1;\n        if(p.Effect2 % 5 == 0)\n        {\n            if(p.State == 1)\n            {\n                p.State = 2;\n                if(p.Mount == 0)\n                {\n                    p.Location.X += (-Physics.PlayerWidth[p.Character][2] + Physics.PlayerWidth[p.Character][1]) / 2;\n                    p.Location.Y += -Physics.PlayerHeight[p.Character][2] + Physics.PlayerHeight[p.Character][1];\n                    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n                else if(p.Mount == 3)\n                {\n                    YoshiHeight(A);\n                }\n                else if(p.Character == 2 && p.Mount != 2)\n                {\n                    p.Location.Y += -Physics.PlayerHeight[2][2] + Physics.PlayerHeight[1][2];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n            }\n            else\n            {\n                p.State = 1;\n                if(p.Mount == 0)\n                {\n                    p.Location.X += (-Physics.PlayerWidth[p.Character][1] + Physics.PlayerWidth[p.Character][2]) / 2;\n                    p.Location.Y += -Physics.PlayerHeight[p.Character][1] + Physics.PlayerHeight[p.Character][2];\n                    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][1];\n                }\n                else if(p.Mount == 3)\n                {\n                    YoshiHeight(A);\n                }\n                else if(p.Character == 2 && p.Mount != 2)\n                {\n                    p.Location.Y += -Physics.PlayerHeight[1][2] + Physics.PlayerHeight[2][2];\n                    p.Location.Height = Physics.PlayerHeight[1][2];\n                }\n            }\n        }\n        if(p.Effect2 >= 50 && p.State == 2)\n        {\n            p.Immune += 50;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.StandUp = true;\n        }\n    }\n    else if(p.Effect == PLREFF_TURN_SMALL) // Player shrinking effect\n    {\n        if(p.Duck)\n        {\n            p.StandUp = true; // Fixes a block collision bug\n            p.Duck = false;\n            p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            p.Location.Y += -Physics.PlayerHeight[p.Character][p.State] + Physics.PlayerDuckHeight[p.Character][p.State];\n        }\n        p.Frame = 1;\n        p.Effect2 += 1;\n        if(p.Effect2 % 5 == 0)\n        {\n            if(p.State == 1)\n            {\n                p.State = 2;\n                if(p.Mount == 3)\n                {\n                    YoshiHeight(A);\n                }\n                else if(p.Mount != 2)\n                {\n                    p.Location.X += (-Physics.PlayerWidth[p.Character][2] + Physics.PlayerWidth[p.Character][1]) / 2;\n                    p.Location.Y += -Physics.PlayerHeight[p.Character][2] + Physics.PlayerHeight[p.Character][1];\n                    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n            }\n            else\n            {\n                p.State = 1;\n                if(p.Mount == 3)\n                {\n                    YoshiHeight(A);\n                }\n                else if(p.Mount != 2)\n                {\n                    p.Location.X += (-Physics.PlayerWidth[p.Character][1] + Physics.PlayerWidth[p.Character][2]) / 2;\n                    p.Location.Y += -Physics.PlayerHeight[p.Character][1] + Physics.PlayerHeight[p.Character][2];\n                    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][1];\n                }\n            }\n        }\n        if(p.Effect2 >= 50)\n        {\n            if(p.State == 2)\n            {\n                p.State = 1;\n                if(p.Mount != 2)\n                {\n                    p.Location.X += (-Physics.PlayerWidth[p.Character][1] + Physics.PlayerWidth[p.Character][2]) / 2;\n                    p.Location.Y += -Physics.PlayerHeight[p.Character][1] + Physics.PlayerHeight[p.Character][2];\n                    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n            }\n            p.Immune = 150;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            // If numPlayers <= 2 Then DropBonus A\n        }\n    }\n    // logic combined across all powerup-to-big effects\n    else if(p.Effect >= PLREFF_STATE_TO_BIG && p.Effect < PLREFF_STATE_TO_BIG_END)\n    {\n        int prev_state = p.Effect - PLREFF_STATE_TO_BIG;\n\n        if(p.Duck)\n        {\n            p.StandUp = true; // Fixes a block collision bug\n            p.Duck = false;\n            p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            p.Location.Y += -Physics.PlayerHeight[p.Character][p.State] + Physics.PlayerDuckHeight[p.Character][p.State];\n        }\n\n        p.Frame = 1;\n        p.Effect2 += 1;\n\n        if(p.Effect2 % 5 == 0)\n        {\n            if(p.State == 2)\n                p.State = prev_state;\n            else\n                p.State = 2;\n        }\n\n        if(p.Effect2 >= 50)\n        {\n            if(p.State == prev_state)\n                p.State = 2;\n\n            p.Immune = 150;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            // If numPlayers <= 2 Then DropBonus A\n        }\n    }\n#if 0\n    else if(p.Effect == PLREFF_FIRE_TO_BIG) // Player losing firepower\n    {\n        if(p.Duck)\n        {\n            p.StandUp = true; // Fixes a block collision bug\n            p.Duck = false;\n            p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            p.Location.Y += -Physics.PlayerHeight[p.Character][p.State] + Physics.PlayerDuckHeight[p.Character][p.State];\n        }\n        p.Frame = 1;\n        p.Effect2 += 1;\n        if(p.Effect2 % 5 == 0)\n        {\n            if(p.State == 2)\n                p.State = 3;\n            else\n                p.State = 2;\n        }\n        if(p.Effect2 >= 50)\n        {\n            if(p.State == 3)\n                p.State = 2;\n            p.Immune = 150;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            // If numPlayers <= 2 Then DropBonus A\n        }\n    }\n    else if(p.Effect == PLREFF_ICE_TO_BIG) // Player losing icepower\n    {\n        if(p.Duck)\n        {\n            p.StandUp = true; // Fixes a block collision bug\n            p.Duck = false;\n            p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            p.Location.Y += -Physics.PlayerHeight[p.Character][p.State] + Physics.PlayerDuckHeight[p.Character][p.State];\n        }\n\n        p.Frame = 1;\n        p.Effect2 += 1;\n\n        if(p.Effect2 % 5 == 0)\n        {\n            if(p.State == 2)\n                p.State = 7;\n            else\n                p.State = 2;\n        }\n\n        if(p.Effect2 >= 50)\n        {\n            if(p.State == 7)\n                p.State = 2;\n            p.Immune = 150;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            // If numPlayers <= 2 Then DropBonus A\n        }\n    }\n#endif\n    else if(p.Effect == PLREFF_WARP_PIPE) // Warp effect\n        PlayerEffectWarpPipe(A);\n    else if(p.Effect == PLREFF_WARP_DOOR) // Door effect\n        PlayerEffectWarpDoor(A);\n    else if(p.Effect == PLREFF_WAITING) // Holding Pattern\n    {\n        // tracking a player that got an exit\n        if(p.Effect2 < 0)\n        {\n            p.Location.X = Player[-p.Effect2].Location.X;\n            p.Location.Y = Player[-p.Effect2].Location.Y;\n\n            if(Player[-p.Effect2].Dead)\n                p.Dead = true;\n\n            // a player has entered a door, make some fireworks!\n            if(LevelMacro == LEVELMACRO_FLAG_EXIT && -p.Effect2 == A)\n            {\n                if(((LevelMacroCounter & 63) == 0) && iRand(2))\n                {\n                    int dx = iRand(256);\n                    int dy = iRand(192);\n                    Location_t target = newLoc(p.Location.X + (16 - 128 + dx),\n                                               p.Location.Y - 256 + dy);\n                    PlaySoundSpatial(SFX_Fireworks, target);\n                    NewEffect(EFFID_BOSS_FRAGILE_EXPLODE, target);\n                }\n            }\n        }\n        // temporary immunity and invisibility\n        else if(p.Effect2 <= 30)\n        {\n            p.Effect2 -= 1;\n            if(p.Effect2 == 0)\n            {\n                p.Effect = PLREFF_NORMAL;\n                p.Effect2 = 0;\n            }\n        }\n        else\n            PlayerEffectWarpWait(A);\n    }\n    // logic combined across all \"grow\" powerup effects\n    else if(p.Effect >= PLREFF_GROW_TO_STATE && p.Effect < PLREFF_GROW_TO_STATE_END) // Player got fire power\n    {\n        int target_state = p.Effect - PLREFF_GROW_TO_STATE;\n\n        if(p.Duck && p.Character != 5)\n        {\n            int BackupGrabTime = Player[A].GrabTime;\n\n            // forces UnDuck to work, this fixes a vanilla downwards clip for Chars 3+4 and a visual bug for the other chars\n            if(g_config.fix_player_grab_clip)\n                Player[A].GrabTime = 0;\n\n            UnDuck(Player[A]);\n\n            Player[A].GrabTime = BackupGrabTime;\n\n            p.Frame = 1;\n        }\n\n        p.Effect2 += 1;\n\n        if(p.Effect2 % 5 == 0)\n        {\n            if(p.State == 1 && p.Character != 5)\n            {\n                p.State = 2;\n\n                if(p.Mount == 0)\n                {\n                    p.Location.X += (-Physics.PlayerWidth[p.Character][2] + Physics.PlayerWidth[p.Character][1]) / 2;\n                    p.Location.Y += -Physics.PlayerHeight[p.Character][2] + Physics.PlayerHeight[p.Character][1];\n                    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n                else if(p.Mount == 3)\n                {\n                    YoshiHeight(A);\n                }\n                else if(p.Character == 2 && p.Mount != 2)\n                {\n                    p.Location.Y += -Physics.PlayerHeight[2][2] + Physics.PlayerHeight[1][2];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n            }\n            else if(p.State != target_state)\n                p.State = target_state;\n            else\n                p.State = 2;\n        }\n\n        if(p.Effect2 >= 50)\n        {\n            if(p.State == 2)\n                p.State = target_state;\n\n            p.Immune += 50;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.StandUp = true;\n        }\n    }\n#if 0\n    else if(p.Effect == PLREFF_TURN_FIRE) // Player got fire power\n    {\n        if(p.Duck && p.Character != 5)\n        {\n            UnDuck(Player[A]);\n            p.Frame = 1;\n        }\n\n        p.Effect2 += 1;\n\n        if(p.Effect2 % 5 == 0)\n        {\n            if(p.State == 1 && p.Character != 5)\n            {\n                p.State = 2;\n\n                if(p.Mount == 0)\n                {\n                    p.Location.X += -Physics.PlayerWidth[p.Character][2] * 0.5 + Physics.PlayerWidth[p.Character][1] * 0.5;\n                    p.Location.Y += -Physics.PlayerHeight[p.Character][2] + Physics.PlayerHeight[p.Character][1];\n                    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n                else if(p.Mount == 3)\n                {\n                    YoshiHeight(A);\n                }\n                else if(p.Character == 2 && p.Mount != 2)\n                {\n                    p.Location.Y += -Physics.PlayerHeight[2][2] + Physics.PlayerHeight[1][2];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n            }\n            else if(p.State != 3)\n                p.State = 3;\n            else\n                p.State = 2;\n        }\n\n        if(p.Effect2 >= 50)\n        {\n            if(p.State == 2)\n                p.State = 3;\n            p.Immune += 50;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.StandUp = true;\n        }\n    }\n    else if(p.Effect == PLREFF_TURN_ICE) // Player got ice power\n    {\n        if(p.Duck && p.Character != 5)\n        {\n            UnDuck(Player[A]);\n            p.Frame = 1;\n        }\n\n        p.Effect2 += 1;\n\n        if(p.Effect2 % 5 == 0)\n        {\n            if(p.State == 1 && p.Character != 5)\n            {\n                p.State = 2;\n                if(p.Mount == 0)\n                {\n                    p.Location.X += -Physics.PlayerWidth[p.Character][2] * 0.5 + Physics.PlayerWidth[p.Character][1] * 0.5;\n                    p.Location.Y += -Physics.PlayerHeight[p.Character][2] + Physics.PlayerHeight[p.Character][1];\n                    p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n                else if(p.Mount == 3)\n                {\n                    YoshiHeight(A);\n                }\n                else if(p.Character == 2 && p.Mount != 2)\n                {\n                    p.Location.Y += -Physics.PlayerHeight[2][2] + Physics.PlayerHeight[1][2];\n                    p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n                }\n            }\n            else if(p.State != 7)\n                p.State = 7;\n            else\n                p.State = 2;\n        }\n\n        if(p.Effect2 >= 50)\n        {\n            if(p.State == 2)\n                p.State = 7;\n            p.Immune += 50;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.StandUp = true;\n        }\n    }\n#endif\n    // logic combined across all \"transform\" powerup effects\n    else if(p.Effect >= PLREFF_TURN_TO_STATE && p.Effect < PLREFF_TURN_TO_STATE_END)\n    {\n        int target_state = p.Effect - PLREFF_TURN_TO_STATE;\n\n        if(target_state != PLR_STATE_LEAF)\n            p.Immune2 = true;\n\n        if(!p.AquaticSwim && !p.Rolling)\n            p.Frame = 1;\n\n        if(p.Effect2 == 0)\n        {\n            if(p.State == 1 && p.Mount == 0)\n            {\n                p.Location.X += (-Physics.PlayerWidth[p.Character][2] + Physics.PlayerWidth[p.Character][1]) / 2;\n                p.Location.Y += -Physics.PlayerHeight[p.Character][2] + Physics.PlayerHeight[p.Character][1];\n                p.State = target_state;\n                p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            }\n            else if(p.Mount == 3)\n            {\n                YoshiHeight(A);\n            }\n            else if(p.Character == 2 && p.State == 1 && p.Mount == 1)\n            {\n                p.Location.Y += -Physics.PlayerHeight[2][2] + Physics.PlayerHeight[1][2];\n                p.Location.Height = Physics.PlayerHeight[p.Character][target_state];\n            }\n\n            p.State = target_state;\n            NewEffect(EFFID_SMOKE_S4, p.Location, 1, ShadowMode);\n        }\n\n        p.Effect2 += 1;\n\n        if(p.Effect2 == 14)\n        {\n            p.Immune += 50;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.StandUp = true;\n        }\n    }\n#if 0\n    else if(p.Effect == PLREFF_TURN_LEAF) // Player got a leaf\n    {\n        p.Frame = 1;\n\n        if(p.Effect2 == 0.0)\n        {\n            if(p.State == 1 && p.Mount == 0)\n            {\n                p.Location.X += -Physics.PlayerWidth[p.Character][2] * 0.5 + Physics.PlayerWidth[p.Character][1] * 0.5;\n                p.Location.Y += -Physics.PlayerHeight[p.Character][2] + Physics.PlayerHeight[p.Character][1];\n                p.State = 4;\n                p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            }\n            else if(p.Mount == 3)\n            {\n                YoshiHeight(A);\n            }\n            else if(p.Character == 2 && p.State == 1 && p.Mount == 1)\n            {\n                p.Location.Y += -Physics.PlayerHeight[2][2] + Physics.PlayerHeight[1][2];\n                p.Location.Height = Physics.PlayerHeight[p.Character][4];\n            }\n\n            p.State = 4;\n            tempLocation.Width = 32;\n            tempLocation.Height = 32;\n            tempLocation.X = p.Location.X + (p.Location.Width - tempLocation.Width) / 2;\n            tempLocation.Y = p.Location.Y + (p.Location.Height - tempLocation.Height) / 2;\n            NewEffect(EFFID_SMOKE_S4, tempLocation, 1, ShadowMode);\n        }\n\n        p.Effect2 += 1;\n\n        if(fEqual(p.Effect2, 14))\n        {\n            p.Immune += 50;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.StandUp = true;\n        }\n    }\n    else if(p.Effect == PLREFF_TURN_STATUE) // Player got a tanooki suit\n    {\n        p.Frame = 1;\n        p.Immune2 = true;\n\n        if(p.Effect2 == 0)\n        {\n            if(p.State == 1 && p.Mount == 0)\n            {\n                p.Location.X += -Physics.PlayerWidth[p.Character][2] * 0.5 + Physics.PlayerWidth[p.Character][1] * 0.5;\n                p.Location.Y += -Physics.PlayerHeight[p.Character][2] + Physics.PlayerHeight[p.Character][1];\n                p.State = 5;\n                p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            }\n            else if(p.Mount == 3)\n            {\n                YoshiHeight(A);\n            }\n            else if(p.Character == 2 && p.State == 1 && p.Mount == 1)\n            {\n                p.Location.Y += -Physics.PlayerHeight[2][2] + Physics.PlayerHeight[1][2];\n                p.Location.Height = Physics.PlayerHeight[p.Character][5]; // was 4 in SMBX 1.3, but the value was the same for all characters\n            }\n\n            p.State = 5;\n            tempLocation.Width = 32;\n            tempLocation.Height = 32;\n            tempLocation.X = p.Location.X + (p.Location.Width - tempLocation.Width) / 2;\n            tempLocation.Y = p.Location.Y + (p.Location.Height - tempLocation.Height) / 2;\n            NewEffect(EFFID_SMOKE_S4, tempLocation, 1, ShadowMode);\n        }\n\n        p.Effect2 += 1;\n\n        if(fEqual(p.Effect2, 14))\n        {\n            p.Immune += 50;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.StandUp = true;\n        }\n    }\n    else if(p.Effect == PLREFF_TURN_HEAVY) // Player got a hammer suit\n    {\n        p.Frame = 1;\n        p.Immune2 = true;\n\n        if(p.Effect2 == 0)\n        {\n            if(p.State == 1 && p.Mount == 0)\n            {\n                p.Location.X += -Physics.PlayerWidth[p.Character][2] * 0.5 + Physics.PlayerWidth[p.Character][1] * 0.5;\n                p.Location.Y += -Physics.PlayerHeight[p.Character][2] + Physics.PlayerHeight[p.Character][1];\n                p.State = 6; // was 5 in SMBX 1.3, but this is fine because the dimensions are the same, and it gets set correctly below\n                p.Location.Width = Physics.PlayerWidth[p.Character][p.State];\n                p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            }\n            else if(p.Mount == 3)\n            {\n                YoshiHeight(A);\n            }\n            else if(p.Character == 2 && p.State == 1 && p.Mount == 1)\n            {\n                p.Location.Y += -Physics.PlayerHeight[2][2] + Physics.PlayerHeight[1][2];\n                p.Location.Height = Physics.PlayerHeight[p.Character][6];\n            }\n\n            p.State = 6;\n            tempLocation.Width = 32;\n            tempLocation.Height = 32;\n            tempLocation.X = p.Location.X + (p.Location.Width - tempLocation.Width) / 2;\n            tempLocation.Y = p.Location.Y + (p.Location.Height - tempLocation.Height) / 2;\n            NewEffect(EFFID_SMOKE_S4, tempLocation, 1, ShadowMode);\n        }\n\n        p.Effect2 += 1;\n\n        if(p.Effect2 == 14.0)\n        {\n            p.Immune += 50;\n            p.Immune2 = true;\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.StandUp = true;\n        }\n    }\n#endif\n    else if(p.Effect == PLREFF_STONE) // Change to / from tanooki\n    {\n        for(B = 1; B <= 2; B++)\n        {\n            num_t dx = dRand();\n            num_t dy = dRand();\n            NewEffect(EFFID_SPARKLE, newLoc(p.Location.X + dx * ((int)p.Location.Width + 8) - 8,\n                                 p.Location.Y + dy * ((int)p.Location.Height + 8) - 4), 1, ShadowMode);\n            Effect[numEffects].Location.SpeedX = dRand() * 2 - 1;\n            Effect[numEffects].Location.SpeedY = dRand() * 2 - 1;\n        }\n\n        if(p.Effect2 == 0)\n        {\n            UnDuck(Player[A]);\n            PlaySoundSpatial(SFX_Transform, p.Location);\n            NewEffect(EFFID_SMOKE_S3_CENTER, p.Location, 1, ShadowMode);\n\n            if(!p.Stoned)\n            {\n                p.Frame = 0;\n                p.Stoned = true;\n                p.StonedCD = 15;\n            }\n            else\n            {\n                p.StonedCD = 60;\n                p.Frame = 1;\n                p.Stoned = false;\n            }\n        }\n\n        p.Effect2 += 1;\n        p.Immune = 10;\n        p.Immune2 = true;\n        p.StonedTime = 0;\n\n        if(p.Effect2 >= 5)\n        {\n            p.Effect2 = 0;\n            p.Effect = PLREFF_NORMAL;\n            p.Immune = 0;\n            p.Immune2 = false;\n        }\n    }\n    else if(p.Effect == PLREFF_NO_COLLIDE) // MultiMario\n    {\n        if(p.HoldingNPC > numNPCs) // Can't hold an NPC that is dead\n            p.HoldingNPC = 0;\n\n        if(p.HoldingNPC > 0)\n        {\n            NPC[p.HoldingNPC].Effect = NPCEFF_NORMAL;\n            NPC[p.HoldingNPC].CantHurt = Physics.NPCCanHurtWait;\n            NPC[p.HoldingNPC].CantHurtPlayer = A;\n\n            if(p.Direction > 0)\n                NPC[p.HoldingNPC].Location.X = p.Location.X + Physics.PlayerGrabSpotX[p.Character][p.State];\n            else\n                NPC[p.HoldingNPC].Location.X = p.Location.X + p.Location.Width - Physics.PlayerGrabSpotX[p.Character][p.State] - NPC[p.HoldingNPC].Location.Width;\n\n            NPC[p.HoldingNPC].Location.Y = p.Location.Y + Physics.PlayerGrabSpotY[p.Character][p.State] + 32 - NPC[p.HoldingNPC].Location.Height;\n\n            if(p.HoldingNPC <= numNPCs)\n                treeNPCUpdate(p.HoldingNPC);\n        }\n\n        p.MountSpecial = 0;\n        p.YoshiTongueLength = 0;\n        p.Immune += 1;\n\n        if(p.Immune >= 5)\n        {\n            p.Immune = 0;\n            if(p.Immune2)\n                p.Immune2 = false;\n            else\n                p.Immune2 = true;\n        }\n\n        tempBool = true;\n\n        for(B = 1; B <= numPlayers; B++)\n        {\n            if(B != A && (Player[B].Effect == PLREFF_NORMAL || B == p.Effect2) && !Player[B].Dead && Player[B].TimeToLive == 0 && CheckCollision(p.Location, Player[B].Location))\n                tempBool = false;\n        }\n\n        if(tempBool)\n        {\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.Immune = 0;\n            p.Immune2 = false;\n            p.Location.SpeedY = 0.01_n;\n        }\n        else if(p.Effect2 > 0)\n        {\n            int D = p.Effect2;\n\n            if(Player[D].Effect == PLREFF_NORMAL)\n                p.Effect2 = 0;\n\n            p.Immune2 = true;\n            p.Location.X = Player[D].Location.X + (Player[D].Location.Width - p.Location.Width) / 2;\n            p.Location.Y = Player[D].Location.Y + Player[D].Location.Height - p.Location.Height;\n        }\n    }\n#if 0 /* FIXME: Dead code, because of Redigit's mistake */\n    else if(p.Effect == 9) // Yoshi eat\n    {\n        p.HoldingNPC = 0;\n        p.StandingOnNPC = 0;\n\n        if(Player[p.Effect2].YoshiPlayer != A)\n        {\n            p.Effect = 0;\n            p.Effect2 = 0;\n        }\n    }\n#endif\n    else if(p.Effect == PLREFF_PET_INSIDE) // Yoshi swallow\n    {\n        p.HoldingNPC = 0;\n        p.StandingOnNPC = 0;\n        p.Section = Player[p.Effect2].Section;\n        p.Location.X = Player[p.Effect2].Location.X + (Player[p.Effect2].Location.Width - p.Location.Width) / 2;\n        p.Location.Y = Player[p.Effect2].Location.Y + (Player[p.Effect2].Location.Height - p.Location.Height) / 2;\n\n        if(Player[p.Effect2].YoshiPlayer != A)\n        {\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n        }\n\n        // new logic: allow escape!\n        if(BattleMode && g_config.multiplayer_pause_controls)\n        {\n            if(p.Effect == PLREFF_NORMAL)\n            {\n                p.Immune = 0;\n                p.CanJump = false;\n            }\n            else if(p.Immune > 0)\n                p.Immune--;\n\n            if(p.Controls.Jump || p.Controls.AltJump)\n            {\n                if(p.Immune == 0 && p.CanJump && p.Effect2)\n                {\n                    p.Immune = 15;\n                    p.CanJump = false;\n\n                    // this gives a median carry time of 10 seconds (22 taps)\n                    if(iRand(32) == 0)\n                        YoshiSpit(p.Effect2);\n                }\n            }\n            else\n                p.CanJump = true;\n        }\n    }\n    else if(p.Effect == PLREFF_RESPAWN) // player stole a heldbonus\n    {\n        p.Immune += 1;\n\n        if(p.Immune >= 5)\n        {\n            p.Immune = 0;\n            if(p.Immune2)\n                p.Immune2 = false;\n            else\n                p.Immune2 = true;\n        }\n\n        p.Location.Y += 2.2_n;\n\n        if(p.Location.Y >= p.RespawnY)\n        {\n            p.Location.Y = p.RespawnY;\n            tempBool = true;\n            for(B = 1; B <= numPlayers; B++)\n            {\n                // !Player[B].Dead condition was added to prevent confusing Drop/Add cases where player gets locked in immune state\n                if(B != A && Player[B].Effect != PLREFF_RESPAWN && ((!g_config.allow_drop_add && (numPlayers < 3 || g_ClonedPlayerMode)) || (!Player[B].Dead && Player[B].Effect != PLREFF_PET_INSIDE)) && CheckCollision(p.Location, Player[B].Location))\n                    tempBool = false;\n            }\n            if(tempBool)\n            {\n                p.Effect = PLREFF_NORMAL;\n                p.RespawnY = 0;\n                p.Immune = 50;\n                p.Immune2 = false;\n                p.Location.SpeedY = 0.01_n;\n            }\n        }\n        for(B = 1; B <= numPlayers; B++)\n        {\n            if(B != A && CheckCollision(p.Location, Player[B].Location))\n            {\n                if(Player[B].Mount == 2)\n                {\n                    p.Effect = PLREFF_NORMAL;\n                    p.Immune = 50;\n                    p.Immune2 = false;\n                    p.Location.Y = Player[B].Location.Y - p.Location.Height;\n                    p.Location.SpeedY = 0.01_n;\n                }\n            }\n        }\n    }\n\n    if(p.Mount == 3 && p.Effect != PLREFF_NO_COLLIDE)\n        PlayerFrame(p);\n\n//    if(Player[A].Effect == 0)\n//    {\n//        if(nPlay.Online && A == nPlay.MySlot + 1)\n//            Netplay::sendData Netplay::PutPlayerControls(nPlay.MySlot) + \"1c\" + std::to_string(A) + \"|\" + Player[A].Effect + \"|\" + Player[A].Effect2 + LB + \"1h\" + std::to_string(A) + \"|\" + Player[A].State + LB;\n//    }\n}\n\nbool PlayerNormal(const Player_t& p)\n{\n    return p.Effect == PLREFF_NORMAL || p.Effect == PLREFF_WARP_PIPE || p.Effect == PLREFF_NO_COLLIDE || p.Effect == PLREFF_PET_INSIDE || p.Effect == PLREFF_COOP_WINGS;\n}\n\nbool AllPlayersNormal()\n{\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        if(!PlayerNormal(Player[B]))\n            return false;\n    }\n\n    return true;\n}\n\n// NEW: ensures the players on a screen are nearby if the screen is shared\nvoid PlayersEnsureNearby(const Screen_t& screen)\n{\n    if(screen.Type != ScreenTypes::SharedScreen)\n        return;\n\n    // get extreme bounds on screen players\n    num_t l = 0;\n    num_t r = 0;\n    num_t t = 0;\n    num_t b = 0;\n\n    // also get center of alive screen players\n    num_t cx = 0;\n    num_t cy = 0;\n    int c_count = 0;\n\n    // if a player is currently warping, always re-calculate position, and prefer to center players on another player\n    bool exists_warping = false;\n    bool exists_non_warping = false;\n\n    for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n    {\n        int plr_A = screen.players[plr_i];\n        const Player_t& p = Player[plr_A];\n        const Location_t& pLoc = p.Location;\n\n        num_t p_x = pLoc.X + pLoc.Width / 2;\n        num_t p_top = (p.Effect == PLREFF_RESPAWN) ? p.RespawnY : pLoc.Y;\n        num_t p_y = p_top + pLoc.Height / 2;\n\n        // dead players don't affect camera\n        if(p.Dead)\n            continue;\n\n        if(plr_i == 0 || p_x < l)\n            l = p_x;\n\n        if(plr_i == 0 || p_x > r)\n            r = p_x;\n\n        if(plr_i == 0 || p_y < t)\n            t = p_y;\n\n        if(plr_i == 0 || p_y > b)\n            b = p_y;\n\n        if(!p.Dead && p.TimeToLive == 0)\n        {\n            cx += p_x;\n            cy += p_y;\n            c_count += 1;\n\n            if(p.Effect == PLREFF_WARP_PIPE || p.Effect == PLREFF_WARP_DOOR)\n                exists_warping = true;\n            else\n                exists_non_warping = true;\n        }\n    }\n\n    // no problem if players are nearby, just return\n    if(!exists_warping && r - l <= screen.W && b - t <= screen.H)\n        return;\n\n    // need to calculate center and figure out which player is closest\n    if(c_count > 0)\n    {\n        cx /= c_count;\n        cy /= c_count;\n    }\n    else\n    {\n        cx = (l + r) / 2;\n        cy = (t + b) / 2;\n    }\n\n    // figure out which player is closest\n    int    closest      = 0;\n    num_t closest_dist = 0;\n\n    for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n    {\n        int plr_A = screen.players[plr_i];\n        const Player_t& p = Player[plr_A];\n        const Location_t& pLoc = p.Location;\n\n        // prefer non-warping player\n        if(exists_non_warping && (p.Effect == PLREFF_WARP_PIPE || p.Effect == PLREFF_WARP_DOOR))\n            continue;\n\n        if(!p.Dead && p.TimeToLive == 0)\n        {\n            num_t p_top = (p.Effect == PLREFF_RESPAWN) ? p.RespawnY : pLoc.Y;\n            num_t dist = num_t::dist2(pLoc.X - cx, p_top - cy);\n\n            if(closest == 0 || closest_dist > dist)\n            {\n                closest = plr_A;\n                closest_dist = dist;\n            }\n        }\n    }\n\n    // if all players dead, choose randomly\n    if(closest == 0)\n        closest = screen.players[0];\n\n    Player_t& pClosest = Player[closest];\n    const Location_t& pClosestLoc = pClosest.Location;\n\n    // if the winner is currently respawning, place them at their target loc\n    if(pClosest.Effect == PLREFF_RESPAWN)\n        pClosest.Location.Y = pClosest.RespawnY;\n\n    // move all players to winning player's location, and set effect if alive\n    for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n    {\n        int plr_A = screen.players[plr_i];\n\n        if(plr_A == closest)\n            continue;\n\n        Player_t& p = Player[plr_A];\n\n        // winged player will naturally return to screen\n        if(p.Effect == PLREFF_COOP_WINGS)\n            continue;\n\n        Location_t& pLoc = p.Location;\n\n        p.Section = pClosest.Section;\n\n        pLoc.X = pClosestLoc.X + (pClosestLoc.Width - pLoc.Width) / 2;\n        pLoc.Y = pClosestLoc.Y + pClosestLoc.Height - pLoc.Height;\n    }\n\n    bool in_door_scroll = PlayerScrollingInWarp(pClosest);\n\n    for(int plr_i = 0; plr_i < screen.player_count; plr_i++)\n    {\n        int plr_A = screen.players[plr_i];\n\n        Player_t& p = Player[plr_A];\n\n        if(p.Dead || p.TimeToLive != 0)\n            continue;\n\n        p.RespawnY = 0;\n\n        if(in_door_scroll)\n        {\n            p.Effect = pClosest.Effect;\n            p.Effect2 = pClosest.Effect2;\n            p.Warp = pClosest.Warp;\n            p.WarpCD = pClosest.WarpCD;\n            p.WarpBackward = pClosest.WarpBackward;\n        }\n        else\n        {\n            p.Effect = PLREFF_NORMAL;\n            p.Effect2 = 0;\n            p.Warp = 0;\n            p.WarpCD = 0;\n            p.WarpBackward = false;\n            p.WarpShooted = false;\n\n            DodgePlayers(plr_A);\n        }\n    }\n}\n\nvoid KeyholeCheck(const int A, const Location_t& loc)\n{\n    UNUSED(A);\n\n    for(int B : treeBackgroundQuery(loc, SORTMODE_NONE))\n    {\n        if(B > numBackground)\n            continue;\n\n        if(Background[B].Type == 35)\n        {\n            SpeedlessLocation_t tempLocation = Background[B].Location;\n            tempLocation.Width = 16;\n            tempLocation.X += 8;\n            tempLocation.Height = 26;\n            tempLocation.Y += 2;\n\n            if(CheckCollision(loc, tempLocation))\n            {\n                PlaySound(SFX_Key);\n                StopMusic();\n                LevelMacro = LEVELMACRO_KEYHOLE_EXIT;\n                LevelMacroWhich = B;\n                break;\n            }\n        }\n    }\n}\n\n// make a death effect for player and release all items linked to them.\n// used for the Die SwapCharacter and for the DropPlayer.\n// do this BEFORE changing/erasing any player fields\nvoid PlayerGone(const int A)\n{\n    PlaySoundSpatial(SFX_PlayerDied2, Player[A].Location);\n    if(!Player[A].Dead && Player[A].TimeToLive == 0)\n    {\n        PlayerDismount(A);\n        if(Player[A].HoldingNPC > 0)\n        {\n            NPC[Player[A].HoldingNPC].HoldingPlayer = 0;\n            Player[A].HoldingNPC = 0;\n        }\n        PlayerDeathEffect(A);\n    }\n}\n\nvoid AddPlayer(int Character, Screen_t& screen)\n{\n    numPlayers++;\n\n    // add player to screen\n    Screens_AssignPlayer(numPlayers, screen);\n\n    Player_t& p = Player[numPlayers];\n\n    p = Player_t();\n    p.Character = Character;\n    p.State = SavedChar[p.Character].State;\n    p.HeldBonus = NPCID(SavedChar[p.Character].HeldBonus);\n    p.Mount = SavedChar[p.Character].Mount;\n    p.MountType = SavedChar[p.Character].MountType;\n    p.Hearts = SavedChar[p.Character].Hearts;\n\n    if(p.State == 0)\n        p.State = 1;\n\n    p.Frame = 1;\n    if(p.Character == 3)\n        p.CanFloat = true;\n    p.Direction = 1;\n\n    SizeCheck(Player[numPlayers]);\n\n    // the rest only matters during level play\n    if(LevelSelect)\n        return;\n\n    int alivePlayer = CheckNearestLiving(numPlayers);\n    if(alivePlayer == 0 || alivePlayer == numPlayers)\n        alivePlayer = 1;\n\n    p.Section = Player[alivePlayer].Section;\n    RespawnPlayerTo(numPlayers, alivePlayer);\n\n    // the rest only matters during level play\n    if(LevelSelect)\n        return;\n\n    SetupScreens();\n    PlayersEnsureNearby(screen);\n}\n\nvoid DropPlayer(const int A)\n{\n    if(A < 1 || A > numPlayers)\n        return;\n\n    const Screen_t& screen = ScreenByPlayer(A);\n\n    // in levels, make a death effect (and leave behind mount)\n    if(!LevelSelect)\n        PlayerGone(A);\n\n    // IMPORTANT - removes all references to player A,\n    //   decrements all references to higher players\n\n    // NPC player references\n    for(int C = 1; C <= numNPCs; C++)\n    {\n        NPC_t& n = NPC[C];\n\n        // most of these should not be equal because PlayerGone has already been called.\n        if(n.vehiclePlr > A && n.vehiclePlr <= numPlayers)\n            n.vehiclePlr --;\n        else if(n.vehiclePlr == A)\n            n.vehiclePlr = 0;\n\n        if(n.HoldingPlayer > A && n.HoldingPlayer <= numPlayers)\n            n.HoldingPlayer --;\n        else if(n.HoldingPlayer == A)\n            n.HoldingPlayer = 0;\n\n        if(n.CantHurtPlayer > A && n.CantHurtPlayer <= numPlayers)\n            n.CantHurtPlayer --;\n        else if(n.CantHurtPlayer == A)\n            n.CantHurtPlayer = 0;\n\n        if(n.BattleOwner > A && n.BattleOwner <= numPlayers)\n            n.BattleOwner --;\n        else if(n.BattleOwner == A)\n            n.BattleOwner = 0;\n\n        // this is not quite right (the vScreen index doesn't necessarily equal the player index, and might not get removed)\n        if(n.JustActivated > A)\n            n.JustActivated --;\n        else if(n.JustActivated == A)\n            n.JustActivated = 1;\n    }\n\n    // Block player references\n    // Block[B].IsPlayer is only set for tempBlocks, so no correction here\n\n    // Player player references\n    for(int B = 1; B <= numPlayers; B++)\n    {\n        if(Player[B].YoshiPlayer == A)\n            Player[B].YoshiPlayer = 0;\n        else if(Player[B].YoshiPlayer > A)\n            Player[B].YoshiPlayer --;\n\n        if(Player[B].StandingOnVehiclePlr == A)\n            Player[B].StandingOnVehiclePlr = 0;\n        else if(Player[B].StandingOnVehiclePlr > A)\n            Player[B].StandingOnVehiclePlr --;\n    }\n\n    // saves player without their mount, but mount is still onscreen and available\n    SavedChar[Player[A].Character] = Player[A];\n    for(int B = A; B < numPlayers; B++)\n    {\n        Player[B] = std::move(Player[B + 1]);\n        OwedMount[B] = OwedMount[B + 1];\n        OwedMountType[B] = OwedMountType[B + 1];\n        BattleLives[B] = BattleLives[B + 1];\n    }\n\n    numPlayers --;\n\n    // remove player from screens\n    Screens_DropPlayer(A);\n\n    // the rest only matters during level play\n    if(LevelSelect)\n        return;\n\n    SetupScreens();\n    PlayersEnsureNearby(screen);\n}\n\nvoid SwapCharacter(int A, int Character, bool FromBlock)\n{\n    SavedChar[Player[A].Character] = Player[A];\n\n    // the following is identical to the code moved from blocks.cpp\n    Player[A].Character = Character;\n    auto &p = Player[A];\n    p.State = SavedChar[p.Character].State;\n    p.HeldBonus = NPCID(SavedChar[p.Character].HeldBonus);\n    p.Mount = SavedChar[p.Character].Mount;\n    p.MountType = SavedChar[p.Character].MountType;\n    p.Hearts = SavedChar[p.Character].Hearts;\n    if(p.State == 0)\n    {\n        p.State = 1;\n    }\n    p.FlySparks = false;\n    p.Immune = 50;\n    if(FromBlock)\n    {\n        p.Effect = PLREFF_WAITING;\n        p.Effect2 = 14;\n    }\n    // NEW CODE that plays same role as old call to SetupPlayers() in the original world map char swap code\n    else\n    {\n        p.Frame = 1;\n        p.FrameCount = 0;\n\n        if(LevelSelect)\n            p.Immune = 0;\n    }\n\n    if(FromBlock)\n    {\n        // make player top match old player top, for bricks (from blocks.cpp)\n        if(p.Mount <= 1)\n        {\n            p.Location.Height = Physics.PlayerHeight[p.Character][p.State];\n            if(p.Mount == 1 && p.State == 1)\n            {\n                p.Location.Height = Physics.PlayerHeight[1][2];\n            }\n            p.StandUp = true;\n        }\n    }\n    else\n    {\n        num_t saved_respawn_StopY = 0;\n        if(p.Effect == PLREFF_RESPAWN)\n            saved_respawn_StopY = p.RespawnY + p.Location.Height;\n\n        // make player bottom match old player bottom, to avoid floor glitches\n        UnDuck(Player[A]);\n        SizeCheck(Player[A]);\n\n        // if player effect is 6 (respawn downwards), update target similarly\n        if(p.Effect == PLREFF_RESPAWN)\n            p.RespawnY = saved_respawn_StopY - p.Location.Height;\n    }\n\n    if(!LevelSelect)\n        NewEffect(EFFID_SMOKE_S3_CENTER, p.Location);\n\n    UpdateYoshiMusic();\n}\n\n// returns whether a player is allowed to swap characters\nbool SwapCharAllowed()\n{\n    if(LevelSelect || GameMenu || (g_config.allow_drop_add && IsHubLevel))\n        return true;\n    else\n        return false;\n}\n"
  },
  {
    "path": "src/player.h",
    "content": "﻿/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef PLAYER_H\n#define PLAYER_H\n\nstruct Screen_t;\nstruct Player_t;\n\n// NEW: moves a player to avoid collisions with other players, and sets to no-clipping if this is impossible\nvoid DodgePlayers(int plr_A);\n\n// Public Sub SetupPlayers() 'this set's the players values to their defaults and prepares them for playing a level\n// this set's the players values to their defaults and prepares them for playing a level\nvoid SetupPlayers();\n// Public Sub UpdatePlayer() 'This is the main sub for the players\n// This is the main sub for the players\n// Returns true if the game has been paused\nbool UpdatePlayer();\n// Public Sub PlayerHurt(A As Integer) 'Player got hurt\n// Player got hurt\nvoid PlayerHurt(const int A);\n// Public Sub PlayerDead(A As Integer) 'Set player up to die\n// Set player up to die\nvoid PlayerDead(const int A);\n// Public Sub KillPlayer(A As Integer) 'Kill the player\n// Kill the player\nvoid KillPlayer(const int A);\n// Public Function CheckDead() As Integer 'Check if players are dead and return which one, returns 0 if everyones dead\n// Check if players are dead and return which one, returns 0 if everyones dead\nint CheckDead();\n// Public Function CheckLiving() As Integer 'Check if players are alive, returns the first player or 0 if everyones dead\n// Check if players are alive, returns the first player or 0 if everyones dead\nint CheckLiving();\n// NEW: returns the closest player to A (prioritizing players on same screen as A) or 0 if everyone's dead\nint CheckNearestLiving(const int A);\n// Unknown\nint LivingPlayersLeft();\n// Public Function LivingPlayers() As Boolean 'true if there are still living players\n// true if there are still living players\nbool LivingPlayers();\n// Public Sub EveryonesDead() 'Handles the game when all players have died\n// Handles the game when all players have died\nvoid EveryonesDead();\n// Initiate level fadeout procedure if all players have died\nvoid ProcessLastDead();\n// Public Sub UnDuck(A As Integer) 'Un Duck the player\n// Un Duck the player\nvoid UnDuck(struct Player_t &p);\n// Public Sub CheckSection(A As Integer)\nvoid CheckSection(const int A);\n// special CheckSection routine used to initialize players, allows music in section 0 to play\nvoid CheckSection_Init(const int A);\n// Public Sub PlayerFrame(A As Integer)\nvoid PlayerFrame(const int A);\nvoid PlayerFrame(struct Player_t &p);\n// Public Sub UpdatePlayerBonus(A As Integer, B As Integer)\nvoid UpdatePlayerBonus(const int A, const NPCID B);\n// Public Sub TailSwipe(plr As Integer, Optional bool As Boolean = False, Optional Stab As Boolean = False, Optional StabDir As Integer = 0)  'for whacking something with the tail\n// for whacking something with the tail\nvoid TailSwipe(const int plr, bool boo = false, bool Stab = false, int StabDir = 0);\n// Public Sub YoshiHeight(A As Integer) 'fix the players height when on a yoshi\n// fix the players height when on a yoshi\nvoid YoshiHeight(const int A);\n// Public Sub YoshiEat(A As Integer)\nvoid YoshiEat(const int A);\n// Public Sub YoshiSpit(A As Integer)\nvoid YoshiSpit(const int A);\n// Public Sub YoshiPound(A As Integer, C As Integer, Optional BreakBlocks As Boolean = False)\nvoid YoshiPound(int A, int mount, bool BreakBlocks = false);\n\n\n// NEW (but derived from existing code) forces player to jump out of mount as they do for AltJump, bypassing all checks.\nvoid PlayerDismount(const int A);\n\n// NEW: checks if player A is in the mouth of the pet of any player on a screen\nbool InOnscreenPet(int plr_A, const Screen_t& screen);\n\n// NEW: removes a player from a pet's mouth (so that their effect can be changed successfully)\nvoid RemoveFromPet(int plr_A);\n\n// Public Sub SwapCoop()\nvoid SwapCoop();\n// Public Sub PlayerPush(A As Integer, HitSpot As Integer)\nvoid PlayerPush(const int A, int HitSpot);\n// Public Sub SizeCheck(A As Integer)\nvoid SizeCheck(struct Player_t &p);\n// Public Sub YoshiEatCode(A As Integer)\nvoid YoshiEatCode(const int A);\n// Public Sub StealBonus()\nvoid StealBonus();\n// Public Sub ClownCar()\nvoid ClownCar();\n\nvoid CleanupVehicleNPCs();\n\n// Private Sub WaterCheck(A As Integer)\nvoid WaterCheck(const int A); //PRIVATE\n// Private Sub Tanooki(A As Integer)\nvoid Tanooki(const int A); //PRIVATE\n// Private Sub PowerUps(A As Integer)\nvoid PowerUps(const int A); //PRIVATE\n// Private Sub SuperWarp(A As Integer)\nvoid SuperWarp(const int A); //PRIVATE\n// NEW: reports whether a player is currently waiting to exit a warp (for shared screen)\nbool PlayerWaitingInWarp(const Player_t& p);\n// NEW: reports whether a player is currently scrolling between warps\nbool PlayerScrollingInWarp(const Player_t& p);\n// Private Sub PlayerCollide(A As Integer)\nvoid PlayerCollide(const int A); //PRIVATE\n\n// Public Sub PlayerGrabCode(A As Integer, Optional DontResetGrabTime As Boolean = False)\nvoid PlayerGrabCode(const int A, bool DontResetGrabTime = false);\n// Public Sub LinkFrame(A As Integer)\nvoid LinkFrame(const int A);\nvoid LinkFrame(struct Player_t &p);\n// Private Sub PlayerEffects(A As Integer)\nvoid PlayerEffects(const int A);\n\n// NEW: checks if player has normal state\nbool PlayerNormal(const Player_t& p);\n// NEW: checks if all players have normal state (if false, layer movement and events get disabled)\nbool AllPlayersNormal();\n\n// NEW: ensures the players on a screen are nearby if the screen is shared\nvoid PlayersEnsureNearby(const Screen_t& screen);\n\n// NEW: checks for a keyhole at a location\nvoid KeyholeCheck(const int A, const Location_t& loc);\n\n// main Drop/Add functions\nvoid DropPlayer(const int A);\nvoid AddPlayer(int Character, Screen_t& screen);\n\n// NEW but, when Die is false and FromBlock is true, identical to hitting character block.\nvoid SwapCharacter(int A, int Character, bool FromBlock = false);\n\n// returns whether a player is allowed to swap characters\nbool SwapCharAllowed();\n\n#endif // PLAYER_H\n"
  },
  {
    "path": "src/pseudo_vb.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef PSEUDO_VB_H\n#define PSEUDO_VB_H\n\n#define False false\n#define True true\n#define If if(\n#define Then ) {\n#define End }\n\n#define CDbl(x) static_cast<double>(x)\n#define CSng(x) static_cast<float>(x)\n#define CBool(x) static_cast<bool>(x)\n#define CInt(x) static_cast<int>(x)\n\n#endif // PSEUDO_VB_H\n"
  },
  {
    "path": "src/rand.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <cstdlib>\n\n#include <pcg/pcg_random.hpp>\n\n#include \"globals.h\"\n#include \"rand.h\"\n\nstatic pcg32 g_random_engine;\nstatic long g_random_n_calls = 0;\n\nstatic pcg32 g_random_engine_isolated;\n\n#ifdef DEBUG_RANDOM_CALLS\nstd::vector<void*> g_random_calls;\n#endif\n\nstatic int last_seed = 310;\n\nvoid seedRandom(int seed)\n{\n    last_seed = seed;\n    g_random_n_calls = 0;\n#ifdef DEBUG_RANDOM_CALLS\n    g_random_calls.clear();\n#endif\n    g_random_engine.seed(seed);\n    g_random_engine_isolated.seed(seed);\n}\n\nint readSeed()\n{\n    g_random_engine.seed(last_seed);\n    return last_seed;\n}\n\nlong random_ncalls()\n{\n    return g_random_n_calls;\n}\n\nvoid random_set_ncalls(long ncalls)\n{\n    g_random_engine.seed(last_seed);\n\n    for(long i = 0; i < ncalls; i++)\n        g_random_engine();\n\n    g_random_n_calls = ncalls;\n}\n\n// Also note that many VB6 calls use dRand * x\n// and then assign the result to an Integer.\n// The result is NOT iRand(x) but rather vb6Round(dRand()*x),\n// iRand_round, which has a different probability distribution\n// (prob 1/(2x) of being 0 or x and 1/x of being each number in between)\n\nint iRand(int max)\n{\n    g_random_n_calls ++;\n#ifdef DEBUG_RANDOM_CALLS\n    void* stack[2] = {nullptr, nullptr};\n    backtrace(stack, 2);\n    g_random_calls.push_back(stack[1]);\n#endif\n\n    if(max == 0)\n    {\n        g_random_engine();\n        return 0;\n    }\n\n    return g_random_engine() % max;\n}\n\nint iRand2(int max)\n{\n    if(max == 0)\n    {\n        g_random_engine_isolated();\n        return 0;\n    }\n\n    return g_random_engine_isolated() % max;\n}\n\n\nnum_t dRand()\n{\n    g_random_n_calls ++;\n\n#ifdef DEBUG_RANDOM_CALLS\n    void* stack[2] = {nullptr, nullptr};\n    backtrace(stack, 2);\n    g_random_calls.push_back(stack[1]);\n#endif\n\n#ifdef THEXTECH_FIXED_POINT\n    return num_t((int64_t)g_random_engine(), nullptr);\n#else\n    return num_t(std::ldexp(g_random_engine(), -32), nullptr);\n#endif\n}\n"
  },
  {
    "path": "src/rand.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef RAND_H\n#define RAND_H\n\n#include <cmath>\n\n#include \"numeric_types.h\"\n\n// supported only on gcc\n// #define DEBUG_RANDOM_CALLS\n#ifdef DEBUG_RANDOM_CALLS\n#   include <execinfo.h>\n#   include <vector>\nextern std::vector<void*> g_random_calls;\n#endif\n\n/**\n * @brief Seeds the random number generator with argument seed for reproducible results\n */\nextern void seedRandom(int seed);\n\n/**\n * @brief Reads the most recently set seed and resets the seed to that seed\n * @return current seed\n */\nextern int readSeed();\n\n/**\n * @brief Reads the number of calls to random functions since the seed was set\n * @return number of calls\n */\nextern long random_ncalls();\n\n/**\n * @brief Resets the random state to simulate the requested number of calls since the seed was set\n */\nextern void random_set_ncalls(long ncalls);\n\n/**\n * @brief Random number generator in double format, between 0.0 to 1.0 (exclusive)\n * @return random double value\n */\nextern num_t dRand();\n\n/**\n * @brief Random number generator in integer format, between 0 to argument max (exclusive)\n * Distribution equivalent to `Int(dRand() * max)`\n * @return random integer value\n */\nextern int iRand(int max);\n\ntemplate<class T>\nint iRandT(T max)\n{\n    return iRand(static_cast<int>(max));\n}\n\n/**\n * @brief Random number generator in integer format, between 0 to argument max (exclusive)\n * Distribution equivalent to `Int(dRand() * max)`\n *\n * Note: It's the second isolated randomizer\n * @return random integer value\n */\nextern int iRand2(int max);\n\ntemplate<class T>\nint iRand2T(T max)\n{\n    return iRand2(static_cast<int>(max));\n}\n\n/**\n * @brief Random number generator in integer format, between 0 to argument max (inclusive)\n * Each midpoint has probability 1/max. Each endpoint has probability 1/2max.\n * Distribution equivalent to implicitly casting `dRand() * max` to an Int in vb6\n * @return random integer value\n */\ninline int iRand_round(int max)\n{\n    // let 0 represent top endpoint, otherwise use iRand(max*2)/2.\n    int i = iRand(max*2);\n    if(i == 0)\n        return max;\n    return i/2;\n}\n\n\n#endif // RAND_H\n"
  },
  {
    "path": "src/range_arr.hpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef RANGE_ARR_HPP\n#define RANGE_ARR_HPP\n\n#include <cstddef>\n\n#ifndef RANGE_ARR_UNSAFE_MODE\n#include \"core/xerror.h\"\n#endif\n\n#if defined(__clang__)\n#   define RANGE_ARR_UNREACHABLE(cond) __builtin_assume(cond)\n#elif defined(__GNUC__)\n#   define RANGE_ARR_UNREACHABLE(cond) __builtin_unreachable()\n#elif defined(_MSC_VER)\n#   define RANGE_ARR_UNREACHABLE(cond) __assume(cond)\n#else\n#   define RANGE_ARR_UNREACHABLE(cond)\n#endif\n\n#define For(A, From, To) for(int A = From; A <= To; ++A)\n\ntemplate <class T, long begin, long end>\nclass RangeArr\n{\n    static constexpr long range_diff = begin - end;\n    static constexpr size_t size = (range_diff < 0 ? -range_diff : range_diff) + 1;\n    static constexpr long offset = -begin;\n\n#ifdef RANGE_ARR_USE_HEAP\n    T *array = nullptr;\n#else\n    T array[size];\n#endif\n\npublic:\n    using Type = T;\n\n    RangeArr() noexcept\n    {\n#ifdef RANGE_ARR_USE_HEAP\n        array = new T[size];\n#endif\n    }\n\n#ifdef RANGE_ARR_USE_HEAP\n    ~RangeArr()\n    {\n        delete[] array;\n    }\n#endif\n\n    RangeArr(const RangeArr &o)\n    {\n#ifdef RANGE_ARR_USE_HEAP\n        array = new T[size];\n#endif\n        for(size_t i = 0; i < size; i++)\n            array[i] = o.array[i];\n    }\n\n    RangeArr& operator=(const RangeArr &o)\n    {\n#ifdef RANGE_ARR_USE_HEAP\n        if(array)\n            delete [] array;\n        array = new T[size];\n#endif\n        for(size_t i = 0; i < size; i++)\n            array[i] = o.array[i];\n        return *this;\n    }\n\n    void fill(const T &o)\n    {\n        for(size_t i = 0; i < size; i++)\n            array[i] = o;\n    }\n\n    constexpr T *base() const\n    {\n        return array + offset;\n    }\n\n    constexpr T *baseReal() const\n    {\n        return array;\n    }\n\n#ifdef RANGE_ARR_UNSAFE_MODE\n    constexpr T& operator[](long index) const\n    {\n        return *(const_cast<T*>(array) + index + offset);\n    }\n#else\n    inline T& operator[](long index)\n    {\n#   ifdef RANGE_ARR_USE_HEAP\n        SDL_assert_release(array); // When array won't initialize\n#   endif\n        if(index > end || index < begin)\n        {\n            fatal_assert_rangearr(begin, end, index);\n            RANGE_ARR_UNREACHABLE(index <= end || index >= begin);\n        }\n\n        return *(array + index + offset);\n    }\n\n    inline const T& operator[](long index) const\n    {\n#   ifdef RANGE_ARR_USE_HEAP\n        SDL_assert_release(array); // When array won't initialize\n#   endif\n        if(index > end || index < begin)\n        {\n            fatal_assert_rangearr(begin, end, index);\n            RANGE_ARR_UNREACHABLE(index >= begin || index <= end);\n        }\n\n        return *(array + index + offset);\n    }\n#endif\n};\n\ntemplate <class T, long begin, long end, T defaultValue>\nclass RangeArrI\n{\n    static constexpr long range_diff = begin - end;\n    static constexpr size_t size = (range_diff < 0 ? -range_diff : range_diff) + 1;\n    static constexpr long offset = -begin;\n\n#ifdef RANGE_ARR_USE_HEAP\n    T *array = nullptr;\n#else\n    T array[size];\n#endif\n\npublic:\n    using Type = T;\n\n    RangeArrI() noexcept\n    {\n#ifdef RANGE_ARR_USE_HEAP\n        array = new T[size];\n#endif\n        for(size_t i = 0; i < size; i++)\n            array[i] = defaultValue;\n    }\n\n#ifdef RANGE_ARR_USE_HEAP\n    ~RangeArrI()\n    {\n        delete[] array;\n    }\n#endif\n\n    RangeArrI(const RangeArrI &o)\n    {\n#ifdef RANGE_ARR_USE_HEAP\n        array = new T[size];\n#endif\n        for(size_t i = 0; i < size; i++)\n            array[i] = o.array[i];\n    }\n\n    RangeArrI& operator=(const RangeArrI &o)\n    {\n#ifdef RANGE_ARR_USE_HEAP\n        if(array)\n            delete [] array;\n        array = new T[size];\n#endif\n        for(size_t i = 0; i < size; i++)\n            array[i] = o.array[i];\n        return *this;\n    }\n\n    void fill(const T &o)\n    {\n        for(size_t i = 0; i < size; i++)\n            array[i] = o;\n    }\n\n    constexpr T *base() const\n    {\n        return array + offset;\n    }\n\n    constexpr T *baseReal() const\n    {\n        return array;\n    }\n\n#ifdef RANGE_ARR_UNSAFE_MODE\n    constexpr T& operator[](long index) const\n    {\n        return *(const_cast<T*>(array) + index + offset);\n    }\n#else\n    inline T& operator[](long index)\n    {\n#   ifdef RANGE_ARR_USE_HEAP\n        SDL_assert_release(array); // When array won't initialize\n#   endif\n        if(index > end || index < begin)\n        {\n            fatal_assert_rangearr(begin, end, index);\n            RANGE_ARR_UNREACHABLE(index >= begin || index <= end);\n        }\n\n        return *(array + index + offset);\n    }\n\n    inline const T& operator[](long index) const\n    {\n#   ifdef RANGE_ARR_USE_HEAP\n        SDL_assert_release(array); // When array won't initialize\n#   endif\n        if(index > end || index < begin)\n        {\n            fatal_assert_rangearr(begin, end, index);\n            RANGE_ARR_UNREACHABLE(index >= begin || index <= end);\n        }\n\n        return *(array + index + offset);\n    }\n#endif\n};\n\n#endif // RANGE_ARR_HPP\n"
  },
  {
    "path": "src/ref_type.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef REF_TYPE_H\n#define REF_TYPE_H\n\n#include <cstdint>\n#include <functional>\n#include <type_traits>\n\n\nstruct BaseRef_t\n{\n    int16_t index;\n\n    inline constexpr BaseRef_t() : index{INT16_MIN} {}\n    inline constexpr BaseRef_t(int16_t i) : index{i} {}\n    inline constexpr BaseRef_t(const BaseRef_t& r) : index(r.index) {}\n\n    inline operator int() const { return index; }\n    inline operator int16_t() const { return index; }\n    inline operator size_t() const { return index; }\n    inline operator bool() const { return *this != nullptr; }\n\n    inline BaseRef_t& operator=(const BaseRef_t& o) { index = o.index; return *this; }\n    inline BaseRef_t& operator=(std::nullptr_t /* o */) { index = INT16_MIN; return *this; }\n\n    inline bool operator==(std::nullptr_t /* o */) const { return index == INT16_MIN; }\n    inline bool operator!=(std::nullptr_t /* o */) const { return index != INT16_MIN; }\n\n    inline bool operator==(const BaseRef_t& o) const { return index == o.index; }\n    inline bool operator!=(const BaseRef_t& o) const { return index != o.index; }\n    inline bool operator<(const BaseRef_t& o) const { return index < o.index; }\n    inline bool operator>(const BaseRef_t& o) const { return index > o.index; }\n    inline bool operator<=(const BaseRef_t& o) const { return index <= o.index; }\n    inline bool operator>=(const BaseRef_t& o) const { return index >= o.index; }\n};\n\n#if defined(_MSC_VER)\n#    define XTECH_TYPEOF(x) typename std::remove_reference<decltype(x)>::type\n#else\n#    define XTECH_TYPEOF(x) __typeof__(x)\n#endif\n\ntemplate<class _target, _target& target>\nstruct Ref_t : public BaseRef_t\n{\n    using T = XTECH_TYPEOF(target[1]);\n    using value_type = T;\n\n    inline constexpr Ref_t() : BaseRef_t{} {}\n    inline constexpr Ref_t(int16_t i) : BaseRef_t{i} {}\n    inline constexpr Ref_t(const BaseRef_t& r) : BaseRef_t{r.index} {}\n    inline constexpr Ref_t(const Ref_t& r) : BaseRef_t{r.index} {}\n    inline Ref_t(T* p) : BaseRef_t{(int16_t)(p - &target[1] + 1)} {}\n    inline Ref_t(T& p) : BaseRef_t{(int16_t)(&p - &target[1] + 1)} {}\n\n    inline operator int() const { return index; }\n    inline operator int16_t() const { return index; }\n    inline operator size_t() const { return index; }\n    inline operator bool() const { return *this != nullptr; }\n    inline operator T*() const { return &target[index]; }\n    inline operator T&() const { return target[index]; }\n\n    inline T& operator*() const { return target[index]; }\n    inline T* operator->() const { return &target[index]; }\n\n    inline Ref_t& operator=(const Ref_t& o) { index = o.index; return *this; }\n    inline BaseRef_t& operator=(std::nullptr_t /* o */) { index = INT16_MIN; return *this; }\n\n    inline bool operator==(std::nullptr_t /* o */) const { return index == INT16_MIN; }\n    inline bool operator!=(std::nullptr_t /* o */) const { return index != INT16_MIN; }\n\n    inline bool operator==(const Ref_t& o) const { return index == o.index; }\n    inline bool operator!=(const Ref_t& o) const { return index != o.index; }\n    inline bool operator<(const Ref_t& o)  const { return index <  o.index; }\n    inline bool operator>(const Ref_t& o)  const { return index >  o.index; }\n    inline bool operator<=(const Ref_t& o) const { return index <= o.index; }\n    inline bool operator>=(const Ref_t& o) const { return index >= o.index; }\n};\n\n#undef XTECH_TYPEOF\n\nnamespace std\n{\n    template <>\n    struct hash<BaseRef_t>\n    {\n        uint16_t operator()(const BaseRef_t& k) const noexcept\n        {\n            return (uint16_t)k.index;\n        }\n    };\n\n    template<class _target, _target& target>\n    struct hash<Ref_t<_target, target>>\n    {\n        uint16_t operator()(const Ref_t<_target, target>& k) const noexcept\n        {\n            return (uint16_t)k.index;\n        }\n    };\n}\n\n#define DECLREF_T(target) using target ## Ref_t = Ref_t<decltype(target), target>\n\n#endif // REF_TYPE_H\n"
  },
  {
    "path": "src/saved_layers.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <IniProcessor/ini_processing.h>\n#include \"Utils/files_ini.h\"\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"saved_layers.h\"\n#include \"global_dirs.h\"\n\nint numSavedLayers;\nstd::array<SavedLayer_t, maxSavedLayers> SavedLayers;\n\n// Load and save procedures\n\n// totally resets saved layer array\nvoid ClearSavedLayers()\n{\n    numSavedLayers = 0;\n    SDL_memset(&SavedLayers, 0, sizeof(decltype(SavedLayers)));\n}\n\n// loads default saved layer values from the episode's savedlayers.ini\nbool LoadDefaultSavedLayers()\n{\n    ClearSavedLayers();\n\n    std::string savedlayers_ini_path = g_dirEpisode.resolveFileCaseExistsAbs(\"savedlayers.ini\");\n    if(savedlayers_ini_path.empty())\n        return true;\n\n    IniProcessing savedlayers_ini = Files::load_ini(savedlayers_ini_path);\n    savedlayers_ini.beginGroup(\"General\");\n\n    for(const std::string& k : savedlayers_ini.allKeys())\n    {\n        if(numSavedLayers >= maxSavedLayers || k.size() > 22)\n            return false;\n\n        auto& target = SavedLayers[numSavedLayers++];\n\n        memcpy(target.Name.data(), k.c_str(), k.size());\n\n        savedlayers_ini.read(k.c_str(), target.Visible, false);\n    }\n\n    return true;\n}\n\n// loads a single saved layer entry from a gamesave\nbool LoadSavedLayer(const void* /*userdata*/, savedLayerSaveEntry& obj)\n{\n    for(int i = 0; i < numSavedLayers; i++)\n    {\n        if(SDL_strcmp(obj.first.c_str(), SavedLayers[i].Name.data()) != 0)\n            continue;\n\n        SavedLayers[i].Visible = obj.second;\n        break;\n    }\n\n    return true;\n}\n\n// exports a single saved layer entry to a gamesave\nbool ExportSavedLayer(const void* /*userdata*/, savedLayerSaveEntry& obj, pge_size_t index)\n{\n    if((int)index >= numSavedLayers)\n        return false;\n\n    obj.first = SavedLayers[index].Name.data();\n    obj.second = SavedLayers[index].Visible;\n\n    return true;\n}\n"
  },
  {
    "path": "src/saved_layers.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SAVED_LAYERS_H\n#define SAVED_LAYERS_H\n\n#include <array>\n\n#include <PGE_File_Formats/save_filedata.h>\n\n// NEW: saved layers. These are saved / loaded as an *unordered* lists of lower-cased strings corresponding to Visible and Hidden.\nconst int maxSavedLayers = 32;\n\nstruct SavedLayer_t\n{\n    std::array<char, 22> Name;\n    const char null_term = '\\0';\n    bool Visible = false;\n};\n\n// These belong to the episode, NOT the level\nextern int numSavedLayers;\nextern std::array<SavedLayer_t, maxSavedLayers> SavedLayers;\n\n// Load and save procedures\n\n// totally resets saved layer array\nvoid ClearSavedLayers();\n// loads default saved layer values from the episode's savedlayers.ini\nbool LoadDefaultSavedLayers();\n// loads a single saved layer entry from a gamesave\nbool LoadSavedLayer(const void* /*userdata*/, savedLayerSaveEntry& obj);\n// exports a single saved layer entry to a gamesave\nbool ExportSavedLayer(const void* /*userdata*/, savedLayerSaveEntry& obj, pge_size_t index);\n\n#endif // #ifndef SAVED_LAYERS_H\n"
  },
  {
    "path": "src/screen.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"screen.h\"\n#include \"globals.h\" // SingleCoop\n#include \"config.h\"\n\n#include \"core/render.h\" // XRender::TargetX\n\nRangeArr<Screen_t, 0, c_screenCount - 1> Screens;\nScreen_t* l_screen = &Screens[0];\n\nRangeArr<qScreen_t, 0, c_vScreenCount> qScreenLoc;\nRangeArr<vScreen_t, 0, c_vScreenCount> vScreen;\n\nstatic std::array<uint8_t, maxNetplayPlayers + 1> s_screenIdxByPlayer;\n\n\nint vScreen_t::TargetX() const\n{\n    return Screens[screen_ref].TargetX() + ScreenLeft;\n}\n\nint vScreen_t::TargetY() const\n{\n    return Screens[screen_ref].TargetY() + ScreenTop;\n}\n\nnum_t vScreen_t::CameraAddX() const\n{\n#ifdef PGE_MIN_PORT\n    if(!Player[this->player].StandingOnNPC)\n        return num_t::ceil(X / 2 - 0.5_n) * 2 + 1.0_n;\n\n    return X + 1.0_n;\n#else\n    // this will be added to numeric object coordinates and then the floor will be taken.\n\n    // the logic here is that if the player is not on a moving NPC / layer,\n    //   use whole-number world coordinates to keep static objects from jittering, then round all positions\n    // when the player *is* on a moving NPC / layer,\n    //   use player-centric numeric coordinates to keep nearby objects from jittering, then round all positions\n    if(!Player[this->player].StandingOnNPC)\n        return num_t::ceil(X - 0.5_n) + 0.5_n;\n\n    return X + 0.5_n;\n#endif\n}\n\nnum_t vScreen_t::CameraAddY() const\n{\n#ifdef PGE_MIN_PORT\n    if(!Player[this->player].StandingOnNPC)\n        return num_t::ceil(Y / 2 - 0.5_n) * 2 + 1.0_n;\n\n    return Y + 1.0_n;\n#else\n    if(!Player[this->player].StandingOnNPC)\n        return num_t::ceil(Y - 0.5_n) + 0.5_n;\n\n    return Y + 0.5_n;\n#endif\n}\n\nint vScreen_t::CameraAddX_i() const\n{\n    return num_t::floor(CameraAddX());\n}\n\nint vScreen_t::CameraAddY_i() const\n{\n    return num_t::floor(CameraAddY());\n}\n\nScreen_t& Screen_t::canonical_screen()\n{\n    if(is_canonical())\n        return *this;\n\n    return Screens[m_CanonicalScreen];\n}\n\nconst Screen_t& Screen_t::canonical_screen() const\n{\n    if(is_canonical())\n        return *this;\n\n    return Screens[m_CanonicalScreen];\n}\n\nScreen_t& Screen_t::visible_screen()\n{\n    if(Visible || m_VisibleScreen >= c_screenCount)\n        return *this;\n\n    return Screens[m_VisibleScreen];\n}\n\nconst Screen_t& Screen_t::visible_screen() const\n{\n    if(Visible || m_VisibleScreen >= c_screenCount)\n        return *this;\n\n    return Screens[m_VisibleScreen];\n}\n\nvoid Screen_t::set_canonical_screen(uint8_t index)\n{\n    m_CanonicalScreen = index;\n\n    if(m_CanonicalScreen != 0)\n    {\n        Screen_t *mainScreen = &Screens[0];\n        Screen_t &canScreen = canonical_screen();\n\n        if(this >= mainScreen && this <= &Screens[c_screenCount - 1])\n            canScreen.m_VisibleScreen = static_cast<uint8_t>(this - mainScreen);\n        else\n            canScreen.m_VisibleScreen = 0;\n\n        canScreen.m_CanonicalScreen = 0;\n        canScreen.players = players;\n        canScreen.two_screen_pref = two_screen_pref;\n        canScreen.four_screen_pref = four_screen_pref;\n    }\n}\n\nint Screen_t::active_begin() const\n{\n    if(Type == ScreenTypes::SingleCoop && SingleCoop == 2)\n        return 1;\n\n    return 0;\n}\n\nint Screen_t::active_end() const\n{\n    if(!is_active())\n        return 0;\n\n    if(Type == ScreenTypes::SingleCoop && SingleCoop == 2)\n        return 2;\n\n    if(Type == ScreenTypes::TopBottom || Type == ScreenTypes::LeftRight)\n        return 2;\n\n    if(Type == ScreenTypes::Dynamic && vScreen(2).Visible)\n        return 2;\n\n    if(Type == ScreenTypes::Quad)\n        return player_count <= maxLocalPlayers ? player_count : maxLocalPlayers;\n\n    return 1;\n}\n\nint Screen_t::TargetX() const\n{\n    return XRender::TargetW / 2 - W / 2;\n}\n\nint Screen_t::TargetY() const\n{\n    return XRender::TargetH / 2 - H / 2;\n}\n\nvoid InitScreens()\n{\n    for(int s = 0; s < c_screenCount; s++)\n    {\n        // reset screen size and prefs\n        Screens[s].W = 800;\n        Screens[s].H = 600;\n        Screens[s].CameraOverscanX = 0;\n        Screens[s].two_screen_pref = MultiplayerPrefs::Dynamic;\n        Screens[s].four_screen_pref = MultiplayerPrefs::Shared;\n\n        // assign vScreens to screens\n        for(int v = 0; v < maxLocalPlayers; v++)\n        {\n            Screens[s].vScreen_refs[v] = s * maxLocalPlayers + v + 1;\n            Screens[s].vScreen(v + 1).screen_ref = s;\n        }\n    }\n\n    // set canonical screens\n    for(int s = 0; s < maxNetplayClients; s++)\n    {\n        Screens[s].set_canonical_screen(maxNetplayClients + s);\n        Screens[maxNetplayClients + s].Visible = false;\n    }\n\n    // reset screens' players\n    for(int s = 0; s < maxNetplayClients; s++)\n    {\n        Screens[s].players = {};\n        Screens[s].player_count = 0;\n    }\n\n    UpdateScreenPlayers();\n}\n\nvoid UpdateScreenPlayers()\n{\n    // update canonical screens\n    for(int s = 0; s < maxNetplayClients; s++)\n    {\n        Screens[s].canonical_screen().players = Screens[s].players;\n        Screens[s].canonical_screen().player_count = Screens[s].player_count;\n    }\n\n    // assign players to vScreens\n    for(int s = 0; s < c_screenCount; s++)\n    {\n        for(int v = 0; v < maxLocalPlayers; v++)\n        {\n            Screens[s].vScreen(v + 1).player = Screens[s].players[v];\n        }\n    }\n\n    // update player screen indexes\n    s_screenIdxByPlayer = {};\n    for(int s = 0; s < maxNetplayClients; s++)\n    {\n        for(int p = 0; p < Screens[s].player_count; p++)\n        {\n            if(Screens[s].players[p] <= maxNetplayPlayers)\n                s_screenIdxByPlayer[Screens[s].players[p]] = s;\n        }\n    }\n}\n\nvoid Screens_AssignPlayer(int player, Screen_t& screen)\n{\n    if(screen.player_count >= maxLocalPlayers)\n        return;\n\n    screen.players[screen.player_count] = player;\n    screen.player_count++;\n\n    UpdateScreenPlayers();\n}\n\nvoid Screens_DropPlayer(int player)\n{\n    // drop player itself\n    for(int s = 0; s < maxNetplayClients; s++)\n    {\n        for(int i = 0; i < Screens[s].player_count; i++)\n        {\n            if(Screens[s].players[i] == player)\n            {\n                // shift other players left\n                for(int j = i; j + 1 < Screens[s].player_count; j++)\n                    Screens[s].players[j] = Screens[s].players[j + 1];\n\n                // reduce player count\n                Screens[s].player_count--;\n            }\n        }\n    }\n\n    // shift all player indices down\n    for(int s = 0; s < maxNetplayClients; s++)\n    {\n        for(int i = 0; i < Screens[s].player_count; i++)\n        {\n            if(Screens[s].players[i] > player)\n                Screens[s].players[i]--;\n        }\n    }\n\n    UpdateScreenPlayers();\n}\n\n// finds the visible Screen that contains a specific player\nint ScreenIdxByPlayer(int player)\n{\n    if(player > maxNetplayPlayers)\n        return 0;\n\n    return s_screenIdxByPlayer[player];\n}\n\n// finds the canonical Screen that contains a specific player\nScreen_t& ScreenByPlayer_canonical(int player)\n{\n    Screen_t& visible_screen = ScreenByPlayer(player);\n\n    return visible_screen.canonical_screen();\n}\n\n// finds the visible vScreen that contains a specific player\nint vScreenIdxByPlayer(int player)\n{\n    if(player < 1 || player > maxLocalPlayers)\n        return 0;\n\n    Screen_t& screen = ScreenByPlayer(player);\n\n    bool is_splitscreen = (screen.Type == 1 || screen.Type == 4 || (screen.Type == 5 && screen.vScreen(2).Visible) || screen.Type == 6 || screen.Type == ScreenTypes::Quad);\n\n    if(is_splitscreen)\n    {\n        for(int i = 0; i < screen.player_count; i++)\n        {\n            if(player == screen.players[i])\n                return screen.vScreen_refs[i];\n        }\n    }\n\n    return screen.vScreen_refs[0];\n}\n\n// finds the canonical vScreen that contains a specific player\nint vScreenIdxByPlayer_canonical(int player)\n{\n    if(player < 1 || player > maxLocalPlayers)\n        return 0;\n\n    Screen_t& screen = ScreenByPlayer_canonical(player);\n\n    bool is_splitscreen = (screen.Type == 1 || screen.Type == 4 || (screen.Type == 5 && screen.vScreen(2).Visible) || screen.Type == 6 || screen.Type == ScreenTypes::Quad);\n\n    if(is_splitscreen)\n    {\n        for(int i = 0; i < screen.player_count; i++)\n        {\n            if(player == screen.players[i])\n                return screen.vScreen_refs[i];\n        }\n    }\n\n    return screen.vScreen_refs[0];\n}\n"
  },
  {
    "path": "src/screen.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n\n#ifndef SCREEN_HHH\n#define SCREEN_HHH\n\n#include <array>\n\n#include \"range_arr.hpp\"\n#include \"global_constants.h\"\n\n#include \"numeric_types.h\"\n\n//Public Type vScreen 'Screen controls\nstruct qScreen_t\n{\n    // previously their own arrays, vScreenX and vScreenY\n    num_t X = 0_n;\n    num_t Y = 0_n;\n\n//    Left As Double\n    int Left = 0;\n//    Top As Double\n    int Top = 0;\n//    Width As Double\n    int Width = 0;\n//    Height As Double\n    int Height = 0;\n//End Type\n\n//    NEW: location on screen when vScreens are smaller due to level size\n    int ScreenTop = 0;\n    int ScreenLeft = 0;\n};\n\nstruct vScreen_t : public qScreen_t\n{\n    struct SmallScreenFeatures_t\n    {\n        int16_t offset_x = 0;\n        int16_t offset_y = 0;\n        int16_t offset_y_hold = 0;\n        int8_t last_buttons_held = 0;\n    };\n\n    SmallScreenFeatures_t small_screen_features;\n\n//    tempX As Double\n    num_t tempX = 0_n;\n//    TempY As Double\n    num_t TempY = 0_n;\n//    TempDelay As Integer\n    int TempDelay = 0;\n//    Visible As Boolean\n    bool Visible = false;\n\n    // NEW: which screen the vScreen belongs to.\n    uint8_t screen_ref = 0;\n\n    // NEW: which player is associated with the vScreen. IMPORTANT: unused when shared screen mode is active\n    uint8_t player = 0;\n\n    //! left x-coordinate for drawing on the currently active render target\n    int TargetX() const;\n\n    //! top y-coordinate for start drawing on the currently active render target\n    int TargetY() const;\n\n    //! x-offset to add for render calls (based on X, and facilitates rounding)\n    num_t CameraAddX() const;\n\n    //! Y-offset to add for render calls (based on Y, and facilitates rounding)\n    num_t CameraAddY() const;\n\n    //! x-offset to add for render calls (based on X, but may be rounded)\n    int CameraAddX_i() const;\n\n    //! Y-offset to add for render calls (based on Y, but may be rounded)\n    int CameraAddY_i() const;\n};\n\nconstexpr int maxNetplayClients = 8;\nconstexpr int maxNetplayPlayers = maxNetplayClients * maxLocalPlayers;\nconstexpr int c_screenCount = 2 * maxNetplayClients;\nconstexpr int c_vScreenCount = c_screenCount * maxLocalPlayers;\nconstexpr int c_vScreenCount_visible = maxNetplayClients * maxLocalPlayers;\n\n//Public vScreen(0 To 2) As vScreen 'Sets up the players screens\nextern RangeArr<vScreen_t, 0, c_vScreenCount> vScreen;\n\n//Public vScreen(0 To 2) As vScreen 'Sets up the players screens\nextern RangeArr<qScreen_t, 0, c_vScreenCount> qScreenLoc;\n\nnamespace ScreenTypes\n{\n    enum ScreenTypes\n    {\n        SinglePlayer = 0,\n        TopBottom = 1,\n        Average = 2,\n        SharedScreen = 3,\n        LeftRight = 4,\n        Dynamic = 5,\n        SingleCoop = 6,\n        Credits = 7,\n        Quad = 9,\n    };\n}\n\nnamespace DScreenTypes\n{\n    enum DScreenTypes\n    {\n        Inactive = 0,\n        LeftRight = 1,\n        RightLeft = 2,\n        BottomTop = 3,\n        TopBottom = 4,\n        Shared = 5,\n        DiffSections = 6,\n    };\n}\n\n// player-specified per-screen preferences for multiplayer mode\n// (overridden by cheats SingleCoop and g_ClonedPlayerMode)\nnamespace MultiplayerPrefs\n{\n    enum MultiplayerPrefs\n    {\n        Dynamic = 0,\n        Split = 1,     // means LeftRight in 2P mode\n        Shared = 2,\n        Max_4P = 2,\n        TopBottom = 3, // 2P mode only\n        Max_2P = 3,\n    };\n}\n\nstruct Screen_t\n{\nprivate:\n    //! a reference to the canonical Screen for this screen (an 800x600 screen with the same players); 0 indicates that the screen itself is canonical\n    uint8_t m_CanonicalScreen = 0;\n    //! a reference to the visible Screen for a canonical screen\n    uint8_t m_VisibleScreen = 0;\n\npublic:\n    using localarr_t = std::array<uint8_t, maxLocalPlayers>;\n\n    //! which vScreens belong to the screen\n    localarr_t vScreen_refs;\n\n    //! which players belong to the screen (no player may belong to multiple visible screens).\n    localarr_t players;\n\n    //! which characters have been requested\n    localarr_t charSelect;\n\n    //! how many active players in this screen (excluding cloned-player mode)\n    int player_count = 0;\n\n    //! whether this is being rendered by any client (not necessarily the local one)\n    bool Visible = true;\n\n    //! the logical width of the screen in pixels\n    int W = 800;\n\n    //! the logical height of the screen in pixels\n    int H = 600;\n\n    //! the amount of horizontal camera overscan in pixels (used for 3DS)\n    int CameraOverscanX = 0;\n\n    //! the currently active split mode for the screen\n    int Type = ScreenTypes::SinglePlayer;\n\n    //! the currently active dynamic split mode for the screen\n    int DType = DScreenTypes::Inactive;\n\n    //! the currently requested multiplayer modes for the screen\n    int two_screen_pref = MultiplayerPrefs::Dynamic;\n    int four_screen_pref = MultiplayerPrefs::Shared;\n\n    inline bool is_active() const\n    {\n        return player_count != 0;\n    }\n\n    inline bool is_canonical() const\n    {\n        return m_CanonicalScreen == 0;\n    }\n\n    Screen_t& canonical_screen();\n    const Screen_t& canonical_screen() const;\n\n    Screen_t& visible_screen();\n    const Screen_t& visible_screen() const;\n\n    void set_canonical_screen(uint8_t index);\n\n    // uses a 1-index to simplify conversion of legacy code\n    inline vScreen_t& vScreen(size_t index) const\n    {\n        return ::vScreen[vScreen_refs[index - 1]];\n    }\n\n    //! First active vScreen index (0-indexed). Use to replace numScreens logic.\n    int active_begin() const;\n\n    //! Bound on active vScreen indexes (0-indexed). Use to replace numScreens logic.\n    int active_end() const;\n\n    //! left x-coordinate for drawing on the currently active render target\n    int TargetX() const;\n\n    //! top y-coordinate for start drawing on the currently active render target\n    int TargetY() const;\n};\n\n//! a list of all screens (local and remote, visible and virtual)\nextern RangeArr<Screen_t, 0, c_screenCount - 1> Screens;\n\nvoid InitScreens();\n\n// ensure that the vScreen players and canonical screen players are consistent with the primary screens' players\nvoid UpdateScreenPlayers();\n\n// assigns a player to a screen\nvoid Screens_AssignPlayer(int player, Screen_t& screen);\n\n// drop a player from the screens system, lowering the indices of all higher players (but does no other player drop logic)\nvoid Screens_DropPlayer(int player);\n\n// finds the visible Screen that contains a specific player\nint ScreenIdxByPlayer(int player);\n\n// finds the visible Screen that contains a specific player\ninline Screen_t& ScreenByPlayer(int player)\n{\n    return Screens[ScreenIdxByPlayer(player)];\n}\n\n// finds the canonical Screen that contains a specific player\nScreen_t& ScreenByPlayer_canonical(int player);\n\n// finds the visible vScreen that contains a specific player\nint vScreenIdxByPlayer(int player);\n\n// finds the visible vScreen that contains a specific player\ninline vScreen_t& vScreenByPlayer(int player)\n{\n    return vScreen[vScreenIdxByPlayer(player)];\n}\n\n// finds the canonical vScreen that contains a specific player\nint vScreenIdxByPlayer_canonical(int player);\n\n// finds the visible vScreen that contains a specific player\ninline vScreen_t& vScreenByPlayer_canonical(int player)\n{\n    return vScreen[vScreenIdxByPlayer_canonical(player)];\n}\n\n//! the screen being rendered by the local client (or, try to make all uses of ScreenW / ScreenH occur in functions that get passed a screen?)\nextern Screen_t* l_screen;\n\n// define this macro at the top of a source file to force the file to access the screens in the modern way\n\n#ifndef SCREEN_H_NO_HELPERS\n//Public ScreenType As Integer 'The screen/view type\n// extern int& ScreenType;\n//Public DScreenType As Integer 'The dynamic screen setup\n// extern int& DScreenType;\n\n//Public ScreenType As Integer 'The screen/view type\n// extern int& ScreenW;\n//Public DScreenType As Integer 'The dynamic screen setup\n// extern int& ScreenH;\n#endif\n\n#endif // #ifndef SCREEN_HHH\n"
  },
  {
    "path": "src/screen_fader.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <cmath>\n#include <algorithm>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"global_constants.h\"\n#include \"globals.h\" // vScreen\n#include \"range_arr.hpp\"\n\n#include \"core/render.h\"\n\n#include \"screen_fader.h\"\n\n\n#ifdef THEXTECH_BUILD_GL_MODERN\n#    include <vector>\n#    include \"core/opengl/gl_program_bank.h\"\n\nstatic std::vector<LoadedGLProgramRef_t> s_loaded_effects;\n#endif // #ifdef THEXTECH_BUILD_GL_MODERN\n\n\nvoid ScreenFader::clearTransitEffects()\n{\n#ifdef THEXTECH_BUILD_GL_MODERN\n    s_loaded_effects.clear();\n#endif\n}\n\nint ScreenFader::loadTransitEffect(const std::string& name)\n{\n#ifdef THEXTECH_BUILD_GL_MODERN\n    LoadedGLProgramRef_t effect = ResolveGLProgram(\"transit-\" + name);\n\n    if((int)effect < 0)\n        return S_FADE;\n\n    auto it = std::find(s_loaded_effects.begin(), s_loaded_effects.end(), effect);\n\n    if(it != s_loaded_effects.end())\n        return static_cast<int>(it - s_loaded_effects.begin()) + S_CUSTOM;\n\n    s_loaded_effects.push_back(effect);\n\n    return static_cast<int>(s_loaded_effects.size() - 1) + S_CUSTOM;\n#else\n    UNUSED(name);\n    return S_FADE;\n#endif\n}\n\nvoid ScreenFader::clearFader()\n{\n    m_active = false;\n    m_current_fade = 0;\n    m_target_fade = 0;\n    m_step = 0;\n    m_full = false;\n    m_focusX = -1;\n    m_focusY = -1;\n    m_focusScreen = -1;\n    m_focusSet = false;\n    m_focusTrackX = nullptr;\n    m_focusTrackY = nullptr;\n    m_focusOffsetX = 0;\n    m_focusOffsetY = 0;\n}\n\nvoid ScreenFader::setupFader(int step, int start, int goal, int shape, bool useFocus, int focusX, int focusY, int screen)\n{\n    m_shape = shape;\n    m_current_fade = start;\n    m_target_fade = goal;\n    m_step = step;\n    m_dirUp = start < goal;\n    m_active = true;\n    m_full = false;\n    m_complete = false;\n    m_focusSet = useFocus;\n    m_focusX = focusX;\n    m_focusY = focusY;\n    m_focusScreen = screen;\n    m_focusTrackX = nullptr;\n    m_focusTrackY = nullptr;\n    m_focusOffsetX = 0;\n    m_focusOffsetY = 0;\n\n#ifdef THEXTECH_BUILD_GL_MODERN\n    if(m_shape >= S_CUSTOM && m_shape - S_CUSTOM < (int)s_loaded_effects.size() && s_loaded_effects[m_shape - S_CUSTOM]->get())\n        m_focusUniform = XRender::registerUniform(*s_loaded_effects[m_shape - S_CUSTOM]->get(), \"u_focus\");\n    else\n        m_focusUniform = -1;\n#endif\n}\n\nvoid ScreenFader::setTrackedFocus(num_t *x, num_t *y, num_t offX, num_t offY)\n{\n    m_focusTrackX = x;\n    m_focusTrackY = y;\n    m_focusOffsetX = offX;\n    m_focusOffsetY = offY;\n}\n\nbool ScreenFader::isComplete()\n{\n    return m_complete;\n}\n\nbool ScreenFader::isVisible()\n{\n    return m_active || m_full;\n}\n\nbool ScreenFader::isFadingIn()\n{\n    return m_active && m_dirUp;\n}\n\nbool ScreenFader::isFadingOut()\n{\n    return m_active && !m_dirUp;\n}\n\nvoid ScreenFader::update()\n{\n    if(!m_active)\n        return;\n\n    if(m_focusSet)\n    {\n        if(m_focusTrackX)\n            m_focusX = (int)(*m_focusTrackX + m_focusOffsetX);\n        if(m_focusTrackY)\n            m_focusY = (int)(*m_focusTrackY + m_focusOffsetY);\n    }\n\n    if(m_current_fade < m_target_fade)\n    {\n        m_current_fade += m_step;\n\n        if(m_current_fade >= m_target_fade)\n        {\n            m_current_fade = m_target_fade;\n            m_step = 0;\n\n            if(m_current_fade >= 65)\n            {\n                m_full = true;\n                m_complete = true;\n            }\n        }\n    }\n    else if(m_current_fade > m_target_fade)\n    {\n        m_current_fade -= m_step;\n\n        if(m_current_fade <= m_target_fade)\n        {\n            m_current_fade = m_target_fade;\n            m_step = 0;\n\n            if(m_current_fade <= 0)\n            {\n                m_active = false;\n                m_complete = true;\n            }\n        }\n    }\n}\n\nvoid ScreenFader::draw(bool fullscreen)\n{\n    if(!m_active)\n        return;\n\n    int drawW = XRender::TargetW;\n    int drawH = XRender::TargetH;\n\n    if(m_focusScreen > 0 && m_focusScreen <= c_vScreenCount && !fullscreen)\n    {\n        drawW = vScreen[m_focusScreen].Width;\n        drawH = vScreen[m_focusScreen].Height;\n    }\n\n    int focusX = m_focusSet ? m_focusX : (drawW / 2);\n    int focusY = m_focusSet ? m_focusY : (drawH / 2);\n\n    if(m_focusSet && m_focusScreen > 0 && m_focusScreen <= c_vScreenCount)\n    {\n        focusX += (int)vScreen[m_focusScreen].X;\n        focusY += (int)vScreen[m_focusScreen].Y;\n\n        if(fullscreen)\n        {\n            focusX += vScreen[m_focusScreen].TargetX();\n            focusY += vScreen[m_focusScreen].TargetY();\n        }\n    }\n\n    uint8_t alpha = m_current_fade * 255 / 65;\n\n    switch(m_shape)\n    {\n    default:\n#ifdef THEXTECH_BUILD_GL_MODERN\n        if(XRender::userShadersSupported() && m_shape >= S_CUSTOM && m_shape - S_CUSTOM < (int)s_loaded_effects.size() && s_loaded_effects[m_shape - S_CUSTOM]->get())\n        {\n            StdPicture& effect = *s_loaded_effects[m_shape - S_CUSTOM]->get();\n\n            if(m_focusUniform >= 0)\n                XRender::assignUniform(effect, m_focusUniform, UniformValue_t((GLfloat)focusX, (GLfloat)focusY));\n\n            XRender::renderTextureScale(0, 0, drawW, drawH, effect, XTAlpha(alpha));\n\n            // if catastrophic failure, fallback to normal fader\n            if(effect.inited)\n                break;\n        }\n#endif // #ifdef THEXTECH_BUILD_GL_MODERN\n\n    // fallthrough\n    case S_FADE:\n        XRender::renderRect(0, 0, drawW, drawH, color.with_alpha(alpha), true);\n        break;\n\n    case S_RECT:\n        if(m_current_fade >= 65)\n            XRender::renderRect(0, 0, drawW, drawH, color.with_alpha(alpha), true);\n        else\n        {\n            int rightW = (drawW - focusX),\n                bottomH = (drawH - focusY),\n                leftW = focusX * m_current_fade / 65, // left side\n                topY = focusY * m_current_fade / 65, // top side\n                rightX = drawW - ((rightW * m_current_fade + 64) / 65) + 1, // right side\n                bottomY = drawH - ((bottomH * m_current_fade + 64) / 65) + 1; // bottom side\n\n            // Left side\n            XRender::renderRect(0, 0, leftW, drawH, color, true);\n            // right side\n            XRender::renderRect(rightX, 0, rightW * m_current_fade / 65, drawH, color, true);\n            // Top side\n            XRender::renderRect(0, 0, drawW, topY, color, true);\n            // Bottom side\n            XRender::renderRect(0, bottomY, drawW, bottomH * m_current_fade / 65, color, true);\n        }\n        break;\n\n    case S_CIRCLE:\n        if(m_current_fade >= 65)\n            XRender::renderRect(0, 0, drawW, drawH, color.with_alpha(alpha), true);\n        else\n        {\n            // int radius = drawH - (drawH * m_scale);\n            int maxRadius = 0, maxRadiusPre;\n\n            // top-left corner\n            maxRadiusPre = (int)num_t::sqrt(focusX * focusX + focusY * focusY);\n            if(maxRadius < maxRadiusPre)\n                maxRadius = maxRadiusPre;\n\n            // top-right corner\n            maxRadiusPre = (int)num_t::sqrt((drawW - focusX) * (drawW - focusX) + focusY * focusY);\n            if(maxRadius < maxRadiusPre)\n                maxRadius = maxRadiusPre;\n\n            // bottom-left corner\n            maxRadiusPre = (int)num_t::sqrt(focusX * focusX + (drawH - focusY) * (drawH - focusY));\n            if(maxRadius < maxRadiusPre)\n                maxRadius = maxRadiusPre;\n\n            // bottom-right corner\n            maxRadiusPre = (int)num_t::sqrt((drawW - focusX) * (drawW - focusX) + (drawH - focusY) * (drawH - focusY));\n            if(maxRadius < maxRadiusPre)\n                maxRadius = maxRadiusPre;\n\n            int radius = maxRadius - (maxRadius * m_current_fade / 65);\n\n            XRender::renderCircleHole(focusX, focusY, radius, color);\n            // left side\n            XRender::renderRect(0, 0, focusX - radius, drawH, color, true);\n            // right side\n            XRender::renderRect(focusX + radius, 0, drawW - (focusX + radius), drawH, color, true);\n            // Top side\n            XRender::renderRect(0, 0, drawW, focusY - radius + 1, color, true);\n            // Bottom side\n            XRender::renderRect(0, focusY + radius, drawW, drawH - (focusY + radius), color, true);\n        }\n        break;\n\n    case S_FLIP_H:\n        if(m_current_fade >= 65)\n            XRender::renderRect(0, 0, drawW, drawH, color.with_alpha(alpha), true);\n        else\n        {\n            int center = (drawH / 2);\n            int sideHeight = (center * m_current_fade + 64) / 65;\n            XRender::renderRect(0, 0, drawW, sideHeight, color, true);\n            XRender::renderRect(0, drawH - sideHeight, drawW, sideHeight, color, true);\n        }\n        break;\n\n    case S_FLIP_V:\n        if(m_current_fade >= 65)\n            XRender::renderRect(0, 0, drawW, drawH, color.with_alpha(alpha), true);\n        else\n        {\n            int center = (drawW / 2);\n            int sideWidth = (center * m_current_fade + 64) / 65;\n            XRender::renderRect(0, 0, sideWidth, drawH, color, true);\n            XRender::renderRect(drawW - sideWidth, 0, sideWidth, drawH, color, true);\n        }\n        break;\n    }\n}\n"
  },
  {
    "path": "src/screen_fader.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SCREEN_FADER_H\n#define SCREEN_FADER_H\n\n#include <string>\n\nstruct ScreenFader\n{\n    enum Shape\n    {\n        S_FADE = 0,\n        S_RECT,\n        S_CIRCLE,\n        S_FLIP_H,\n        S_FLIP_V,\n        // must be higher than all LevelDoor::TRANSIT_*\n        S_CUSTOM = 16,\n    };\n\n    bool m_active = false;\n    bool m_full = false;\n    bool m_complete = false;\n    bool m_dirUp = false;\n\n    // each with a denominator of 65\n    int m_current_fade = 0;\n    int m_target_fade = 0;\n    int m_step = 0;\n\n    XTColor color = {0, 0, 0};\n\n    // Focus on the point (using circle or rectangular effect)\n    int m_focusUniform = -1;\n    int m_focusX = -1;\n    int m_focusY = -1;\n    int m_focusScreen = -1;\n    bool m_focusSet = false;\n\n    num_t *m_focusTrackX = nullptr;\n    num_t *m_focusTrackY = nullptr;\n    num_t m_focusOffsetX = 0.0_n;\n    num_t m_focusOffsetY = 0.0_n;\n\n    int m_shape = S_FADE;\n\n    static void clearTransitEffects();\n    static int loadTransitEffect(const std::string& name);\n\n    void clearFader();\n\n    void setupFader(int step, int start, int goal, int shape, bool useFocus = false, int focusX = -1, int focusY = -1, int screen = -1);\n    void setTrackedFocus(num_t *x, num_t *y, num_t offX, num_t offY);\n\n    bool isComplete();\n    bool isVisible();\n    bool isFadingIn();\n    bool isFadingOut();\n\n    void update();\n\n    // fullscreen should be false if the fader is being drawn inside a vScreen, otherwise true\n    void draw(bool fullscreen = true);\n};\n\n#endif // SCREEN_FADER_H\n"
  },
  {
    "path": "src/script/luau/test.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <cstdlib>\n#include <Utils/files.h>\n#include <Logger/logger.h>\n\n#include <lua.h>\n#include <luacode.h>\n#include <lualib.h>\n\n#include \"global_dirs.h\"\n\n#include \"script/luau/test.h\"\n\nlua_State* L = nullptr;\nbool working = false;\n\nstatic void *l_alloc (void *ud, void *ptr, size_t osize,\n                                           size_t nsize) {\n  (void)ud;  (void)osize;  /* not used */\n  if (nsize == 0) {\n    free(ptr);\n    return NULL;\n  }\n  else\n    return realloc(ptr, nsize);\n}\n\nvoid load_test_script()\n{\n    working = false;\n    if(L)\n    {\n        lua_close(L);\n        L = nullptr;\n    }\n\n    Files::Data d = Files::load_file(g_dirEpisode.resolveFileCaseExistsAbs(\"test.luau\"));\n\n    if(d.empty())\n        return;\n\n    size_t bytecodeSize = 0;\n    char* bytecode = luau_compile(d.c_str(), d.size(), NULL, &bytecodeSize);\n    if(!bytecode)\n        return;\n\n    L = lua_newstate(l_alloc, nullptr);\n    luaopen_base(L);\n\n    if(!L)\n    {\n        free(bytecode);\n        return;\n    }\n\n    int result = luau_load(L, \"chunk\", bytecode, bytecodeSize, 0);\n    if(result == 0)\n    {\n        if(lua_pcall(L, 0, 0, 0) == 0)\n            working = true;\n        else\n        {\n            pLogWarning(\"Error when loading code: %s\", lua_tostring(L, -1));\n            lua_pop(L, 1);\n        }\n    }\n    else\n    {\n        pLogWarning(\"Error when compiling code: %s\", lua_tostring(L, -1));\n        lua_pop(L, 1);\n    }\n\n    luaL_sandbox(L);\n\n    run_test_script();\n    run_test_script();\n}\n\nvoid run_test_script()\n{\n    if(working)\n    {\n        lua_getfield(L, LUA_GLOBALSINDEX, \"main\");\n        if(lua_pcall(L, 0, 0, 0) != 0)\n        {\n            pLogWarning(\"Error when calling main: %s\", lua_tostring(L, -1));\n            lua_pop(L, 1);\n        }\n    }\n}\n"
  },
  {
    "path": "src/script/luau/test.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef THEXTECH_LUAU_TEST_H\n#define THEXTECH_LUAU_TEST_H\n\nvoid load_test_script();\nvoid run_test_script();\n\n#endif // #ifndef THEXTECH_LUAU_TEST_H\n"
  },
  {
    "path": "src/script/luna/autocode.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <list>\n#include <unordered_map>\n\n#include <Logger/logger.h>\n#include <fmt_format_ne.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"autocode.h\"\n#include \"autocode_manager.h\"\n#include \"lunamisc.h\"\n#include \"sprite_funcs.h\"\n#include \"csprite.h\"\n#include \"globals.h\"\n#include \"global_dirs.h\"\n#include \"player.h\"\n#include \"graphics.h\"\n#include \"sound.h\"\n#include \"layers.h\"\n#include \"core/msgbox.h\"\n#include \"main/cheat_code.h\"\n#include \"luna.h\"\n#include \"lunarender.h\"\n#include \"lunaplayer.h\"\n#include \"lunalevel.h\"\n#include \"lunanpc.h\"\n#include \"lunalayer.h\"\n#include \"lunablock.h\"\n#include \"lunaspriteman.h\"\n#include \"lunainput.h\"\n#include \"lunavarbank.h\"\n#include \"lunacounter.h\"\n#include \"renderop_string.h\"\n#include \"mememu.h\"\n#include \"config.h\"\n\n#include \"main/trees.h\"\n\nstatic inline int s_round2int(num_t d)\n{\n    return num_t::floor(d + 0.5_n);\n}\n\nstatic FIELDTYPE StrToFieldtype(std::string string)\n{\n    string.erase(string.find_last_not_of(\" \\n\\r\\t\") + 1);\n    if(string == \"b\")\n        return FT_BYTE;\n    else if(string == \"s\" || string == \"w\")\n        return FT_WORD;\n    else if(string == \"dw\")\n        return FT_DWORD;\n    else if(string == \"f\")\n        return FT_FLOAT;\n    else if(string == \"df\")\n        return FT_DFLOAT;\n\n    return FT_BYTE;\n}\n\n\nAutocode::Autocode()\n{\n    m_Type = AT_Invalid;\n}\n\nAutocode::Autocode(AutocodeType iType, num_t iTarget, num_t ip1, num_t ip2, num_t ip3,\n                   const stringindex_t& ip4, num_t iLength, int iSection, const stringindex_t& iVarRef)\n{\n    m_Type = iType;\n    Target = iTarget;\n    Param1 = ip1;\n    Param2 = ip2;\n    Param3 = ip3;\n    Length = iLength;\n    MyString = ip4;\n    MyRef = iVarRef;\n    m_OriginalTime = iLength;\n    ftype = FT_INVALID;\n    Activated = true;\n    Expired = false;\n    //comp = nullptr;\n\n    // Adjust section\n    ActiveSection = (iSection < 1000 ? --iSection : iSection);\n    Activated = (iSection < 1000);\n}\n\nAutocode::Autocode(const Autocode &o)\n{\n    operator=(o);\n}\n\nAutocode &Autocode::operator=(const Autocode &o)\n{\n    m_Type = o.m_Type;\n\n    Target = o.Target;\n    Param1 = o.Param1;\n    Param2 = o.Param2;\n    Param3 = o.Param3;\n    Length = o.Length;\n    // de-duplicate strings, while re-using allocated string indices if possible\n    MyString = o.MyString;\n    MyRef = o.MyRef;\n\n    m_OriginalTime = o.m_OriginalTime;\n    ActiveSection = o.ActiveSection;\n    ftype = o.ftype;\n    Activated = o.Activated;\n    Expired = o.Expired;\n\n    return *this;\n}\n\n// DO - Perform autocodes for this section. Only does init codes if \"init\" is set\nvoid Autocode::Do(bool init)\n{\n    // Is it expired?\n    if(Expired || !Activated)\n        return;\n\n    // Make sure game is in OK state to run\n    Player_t *demo = PlayerF::Get(1);\n    const vScreen_t& vscreen = vScreenByPlayer(1);\n\n    if(!demo)\n        return;\n\n    // Only allow initrun on codes set to section -1\n    if(init)\n        init = (uint8_t)ActiveSection == (uint8_t)0xFE;\n\n    // Run this code if \"always\" section, or if current section is a match, or forced by init\n    if((uint8_t)ActiveSection == (uint8_t)0xFF || demo->Section == ActiveSection || init)\n    {\n        int pretick_len = (int)this->Length;\n        this->SelfTick();\n\n        // Run specified autocode\n        switch(m_Type)\n        {\n        // INVALID\n        case 0 /*Invalid*/:\n        default:\n            break;\n\n        // FILTERS\n        case AT_FilterToSmall:\n        {\n            PlayerF::FilterToSmall(demo);\n            break;\n        }\n        case AT_FilterToBig:\n        {\n            PlayerF::FilterToBig(demo);\n            break;\n        }\n        case AT_FilterToFire:\n        {\n            PlayerF::FilterToFire(demo);\n            break;\n        }\n        case AT_FilterMount:\n        {\n            PlayerF::FilterMount(demo);\n            break;\n        }\n        case AT_FilterReservePowerup:\n        {\n            PlayerF::FilterReservePowerup(demo);\n            break;\n        }\n\n        case AT_FilterPlayer:\n        {\n            // Player_t *demo = PlayerF::Get(1); // Already declared above\n            //if(demo) // Useless, null check was done above\n            //{\n            if(demo->Character == Param1)\n            {\n                if(Param2 > 0 && Param2 < 6)\n                    demo->Character = (int)Param2;\n            }\n            //}\n            break;\n        }\n\n        // INFINITE FLYING\n        case AT_InfiniteFlying:\n            PlayerF::InfiniteFlying(1);\n            break;\n\n        // SET HEARTS\n        case AT_SetHearts:\n            demo->Hearts = (short)Param1;\n            if(Param1 > 1 && demo->State < 2)\n                demo->State = 2;\n            break;\n\n        // HEART SYSTEM\n        case AT_HeartSystem:\n            HeartSystem();\n            break;\n\n        // SCREEN EDGE BUFFER\n        case AT_ScreenEdgeBuffer:\n        {\n            //char* dbg = \"SCREEN EDGE DBG\";\n\n            // Get all target NPCs in section into a list (slow, could easily optimize using NPCQueues::Active.no_change)\n            std::list<NPC_t *> npcs;\n            NpcF::FindAll((int)Target, demo->Section, &npcs);\n\n            if(!npcs.empty())\n            {\n                // Loop over list of NPCs\n                for(auto npc : npcs)\n                {\n                    switch((int)Param1)\n                    {\n                    default:\n                    case 0: // UP\n                    {\n                        //double* pCamera = vScreenY;\n                        num_t top = -vscreen.Y;\n                        if(npc->Location.Y < top + Param2)\n                        {\n                            npc->Location.Y = (top + Param2) + 1;\n                            treeNPCUpdate(npc);\n                        }\n                        break;\n                    }\n\n                    case 1:  // DOWN\n                    {\n                        //double* pCamera = GM_CAMERA_Y;\n                        num_t bot = -vscreen.Y + vscreen.Height;\n                        if(npc->Location.Y > bot - Param2)\n                        {\n                            npc->Location.Y = (bot - Param2) - 1;\n                            treeNPCUpdate(npc);\n                        }\n                        break;\n                    }\n\n                    case 2: // LEFT\n                    {\n                        //double* pCamera = GM_CAMERA_X;\n                        num_t left = -vscreen.X;\n                        if(npc->Location.X < left + Param2)\n                        {\n                            npc->Location.X = (left + Param2) + 1;\n                            treeNPCUpdate(npc);\n                        }\n                        break;\n                    }\n\n                    case 3: // RIGHT\n                    {\n                        //double* pCamera = GM_CAMERA_X;\n                        num_t rt = -vscreen.X + vscreen.Width;\n                        if(npc->Location.Y > rt - Param2)\n                        {\n                            npc->Location.Y = (rt - Param2) - 1;\n                            treeNPCUpdate(npc);\n                        }\n                        break;\n                    }\n                    }\n                }\n            }\n            break;\n        }\n\n\n\n        // SHOW TEXT\n        case AT_ShowText:\n            Renderer::Get().AddOp(new RenderStringOp(GetS(MyString), (int)Param3, s_round2int(Param1), s_round2int(Param2)));\n            break;\n\n        // SHOW NPC LIFE LEFT\n        case AT_ShowNPCLifeLeft:\n        {\n            int base_health = SDL_atoi(GetS(MyString).data());\n            NPC_t *npc = NpcF::GetFirstMatch((int)Target, (int)Param3 - 1);\n            if(npc != nullptr)\n            {\n                //float hits = *(((float *)((&(*(uint8_t *)npc)) + 0x148)));\n                int hits = npc->Damage;\n                Renderer::Get().AddOp(new RenderStringOp(fmt::format_ne(\"{0}\", (base_health - hits)), 3, s_round2int(Param1), s_round2int(Param2)));\n            }\n            else\n                Renderer::Get().AddOp(new RenderStringOp(\"?\", 3, s_round2int(Param1), s_round2int(Param2)));\n            break;\n        }\n\n        // Show level's internal name (or show the filename if empty)\n        case AT_ShowLevelName:\n            Renderer::Get().AddOp(new RenderStringOp(LevelName.empty() ? FileName : LevelName, (int)Param3, s_round2int(Param1), s_round2int(Param2)));\n            break;\n\n        // Show level's file name (without extension)\n        case AT_ShowLevelFile:\n            Renderer::Get().AddOp(new RenderStringOp(FileName, (int)Param3, s_round2int(Param1), s_round2int(Param2)));\n            break;\n\n        // AUDIO\n        case AT_SFX:\n        {\n            if(this->Length <= 1) // Play once when delay runs out\n            {\n                // Play built in sound\n                if(Param1 > 0)\n                    PlaySound((int)Param1);\n                else\n                {\n                    // Sound from level folder\n                    if(GetS(MyString).length() > 0)\n                    {\n                        //char* dbg = \"CUSTOM SOUND PLAY DBG\";\n                        std::string full_path = g_dirCustom.resolveFileCaseAbs(GetS(MyString));\n                        PlayExtSound(full_path);\n                    }\n\n                }\n                expire();\n            }\n            break;\n        }\n\n        case AT_PlaySFX:\n        {\n            if(this->Length <= 1) // Play once when delay runs out\n            {\n                // Play built in sound\n                if(Param1 > 0)\n                    PlaySound((int)Param1, (int)Param2, (int)(Param3 <= 0 ? 128 : Param3));\n                else\n                {\n                    // Sound from level folder\n                    if(GetS(MyString).length() > 0)\n                    {\n                        //char* dbg = \"CUSTOM SOUND PLAY DBG\";\n                        std::string full_path = g_dirCustom.resolveFileCaseAbs(GetS(MyString));\n                        PlayExtSound(full_path, (int)Param2, (int)(Param3 <= 0 ? 128 : Param3));\n                    }\n\n                }\n                expire();\n            }\n            break;\n        }\n\n        case AT_StopSFX:\n        {\n            if(this->Length <= 1) // Stop once when delay runs out\n            {\n                // Sound from level folder\n                if(GetS(MyString).length() > 0)\n                {\n                    //char* dbg = \"CUSTOM SOUND STOP DBG\";\n                    std::string full_path = g_dirCustom.resolveFileCaseAbs(GetS(MyString));\n                    StopExtSound(full_path);\n                }\n                expire();\n            }\n            break;\n        }\n\n        case AT_SFXPreLoad:\n        {\n            if(this->Length <= 1) // Preload custom SFX file\n            {\n                // Sound from level folder\n                if(GetS(MyString).length() > 0)\n                {\n                    //char* dbg = \"CUSTOM SOUND PLAY DBG\";\n                    std::string full_path = g_dirCustom.resolveFileCaseAbs(GetS(MyString));\n                    PreloadExtSound(full_path);\n                }\n                expire();\n            }\n            break;\n        }\n\n        case AT_SetMusic:\n        {\n            int sec = (int)Target - 1;\n            if(sec >= 0 && sec < numSections)\n            {\n                if(Param1 >= 0 && Param1 <= 24)\n                    bgMusic[sec] = (int)Param1;\n                if(GetS(MyString).length() >= 5)\n                    CustomMusic[sec] = GetS(MyString);\n            }\n            break;\n        }\n\n        case AT_PlayMusic:\n        {\n            if(Length <= 1)   // Play once when delay runs out\n            {\n                StartMusic((int)Param1 - 1);\n                expire();\n            }\n            break;\n        }\n\n        // EVENTS & BRANCHES\n        case AT_Trigger:\n        {\n            expire();\n            gAutoMan.ActivateCustomEvents((int)Target, (int)Param1);\n            break;\n        }\n\n        case AT_Timer:\n            if(Param2 != 0) // Display timer?\n            {\n                Renderer::Get().AddOp(new RenderStringOp(\"TIMER\", 3, 600, 27));\n                Renderer::Get().AddOp(new RenderStringOp(fmt::format_ne(\"{0}\", (int64_t)Length / 60), 3, 618, 48));\n            }\n\n            if(Length == 1 || Length == 0)\n            {\n                if(Length == 1)\n                    expire();\n\n                Autocode::DoPredicate((int)Target, (int)Param1);\n\n                // Reset time?\n                if(Param3 != 0)\n                {\n                    Activated = true;\n                    Expired = false;\n                    Length = m_OriginalTime;\n                }\n            }\n            break;\n\n        case AT_IfNPC:\n        {\n            if(numNPCs < 1)\n                break;\n\n            if(Autocode::NPCConditional((int)Target, (int)Param1))\n            {\n                RunSelfOption();\n                gAutoMan.ActivateCustomEvents((int)Param2, (int)Param3);\n            }\n            break;\n        }\n\n        case AT_BlockTrigger:\n        {\n            if(Target == 0) //if target is player\n            {\n                if(BlocksF::IsPlayerTouchingType((int)Param1, (int)Param2, demo))\n                {\n                    RunSelfOption();\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                }\n            }\n            break;\n        }\n\n        case AT_IfCompatMode:\n        {\n            if(CheckConditionI(g_config.compatibility_mode, (int)Param2, (COMPARETYPE)(int)Param1))\n                gAutoMan.ActivateCustomEvents(0, (int)Param3);\n            break;\n        }\n\n        case AT_IfSpeedRunMode:\n        {\n            if(CheckConditionI(g_config.speedrun_mode.m_value, (int)Param2, (COMPARETYPE)(int)Param1))\n                gAutoMan.ActivateCustomEvents(0, (int)Param3);\n            break;\n        }\n\n        case AT_TriggerRandom:\n        {\n            int choice = iRand2(4);\n            switch(choice)\n            {\n            default:\n                break;\n            case 0:\n                gAutoMan.ActivateCustomEvents(0, (int)Target);\n                break;\n            case 1:\n                gAutoMan.ActivateCustomEvents(0, (int)Param1);\n                break;\n            case 2:\n                gAutoMan.ActivateCustomEvents(0, (int)Param2);\n                break;\n            case 3:\n                gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                break;\n            }\n            break;\n        }\n\n        case AT_TriggerRandomRange:\n        {\n            if(Target >= Param1) // rule out bad values\n                break;\n\n            int diff = (int)Param1 - (int)Target;\n            int choice = iRand2(diff);\n            gAutoMan.ActivateCustomEvents(0, (int)Target + choice);\n            break;\n        }\n\n        case AT_TriggerZone:\n        {\n            Length++; // undo length decrement\n            if(demo->Location.X > Param3 &&\n               demo->Location.Y > Param1 &&\n               demo->Location.Y < Param2 &&\n               demo->Location.X < Length)\n            {\n                gAutoMan.ActivateCustomEvents(0, (int)Target);\n                RunSelfOption();\n            }\n            break;\n        }\n\n        case AT_ScreenBorderTrigger:\n        {\n            LunaRect player_screen_rect = PlayerF::GetScreenPosition(demo);\n            int depth = 0;\n            if(GetS(MyString).length() > 0)\n                depth = SDL_atoi(GetS(MyString).c_str());\n\n            double L_edge = 0 + depth;\n            double U_edge = 0 + depth;\n            double D_edge = vscreen.Height - depth;\n            double R_edge = vscreen.Width - depth;\n\n            if(demo->WarpCD < 1)\n            {\n                if(player_screen_rect.left <= L_edge && demo->Location.SpeedX < 0)\n                {\n                    gAutoMan.ActivateCustomEvents(0, (int)Target);\n                    demo->WarpCD = 2;\n                }\n                else if(player_screen_rect.top <= U_edge  && demo->Location.SpeedY < 0)\n                {\n                    gAutoMan.ActivateCustomEvents(0, (int)Param1);\n                    demo->WarpCD = 2;\n                }\n                else if(player_screen_rect.right >= R_edge  && demo->Location.SpeedX > 0)\n                {\n                    gAutoMan.ActivateCustomEvents(0, (int)Param2);\n                    demo->WarpCD = 2;\n                }\n                else if(player_screen_rect.bottom >= D_edge && demo->Location.SpeedY > 0)\n                {\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                    demo->WarpCD = 2;\n                }\n            }\n            break;\n        }\n\n        case AT_OnInput:\n        {\n            switch((int)Param1)\n            {\n            case 1: // Up\n                if(Param2 == 1 && Input::UpThisFrame()) // First frame press only\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                else if(Param2 != 1 && Input::PressingUp()) // Any press\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                break;\n\n            case 2: // Down\n                if(Param2 == 1 && Input::DownThisFrame()) // First frame press only\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                else if(Param2 != 1 && Input::PressingDown()) // Any press\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                break;\n\n            case 3: // Left\n                if(Param2 == 1 && Input::LeftThisFrame()) // First frame press only\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                else if(Param2 != 1 && Input::PressingLeft()) // Any press\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                break;\n\n            case 4: // Right\n                if(Param2 == 1 && Input::RightThisFrame()) // First frame press only\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                else if(Param2 != 1 && Input::PressingRight()) // Any press\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                break;\n\n            case 5: //Run\n                if(Param2 == 1 && Input::RunThisFrame()) // First frame press only\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                else if(Param2 != 1 && Input::PressingRun()) // Any press\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                break;\n\n            case 6: //Jump\n                if(Param2 == 1 && Input::JumpThisFrame()) // First frame press only\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                else if(Param2 != 1 && Input::PressingJump()) // Any press\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                break;\n            }\n            break;\n        }\n\n        case AT_OnPlayerMem:\n        {\n            if(ftype == FT_INVALID)\n            {\n                ftype = FT_BYTE;\n                ftype = StrToFieldtype(GetS(MyString));\n            }\n//            uint8_t *ptr = (uint8_t *)demo;\n//            ptr += (int)Target; // offset\n            bool triggered = CheckMem(demo, (size_t)Target, Param1, (COMPARETYPE)(int)Param2, ftype);\n            if(triggered)\n                gAutoMan.ActivateCustomEvents(0, (int)Param3);\n            break;\n        }\n\n        case AT_OnGlobalMem:\n        {\n            if(ftype == FT_INVALID)\n            {\n                ftype = FT_BYTE;\n                ftype = StrToFieldtype(GetS(MyString));\n            }\n\n            bool triggered = CheckMem((size_t)Target, Param1, (COMPARETYPE)(int)Param2, ftype);\n            if(triggered)\n                gAutoMan.ActivateCustomEvents(0, (int)Param3);\n            break;\n        }\n\n        // USER VARS\n        case AT_SetVar:\n        {\n            if(ReferenceOK())\n                gAutoMan.VarOperation(GetS(MyRef), Param2, (OPTYPE)(int)Param1);\n            else\n                gAutoMan.VarOperation(GetS(MyString), Param2, (OPTYPE)(int)Param1);\n            break;\n        }\n\n        case AT_CopyVar:\n        {\n            if(ReferenceOK() && MyString != STRINGINDEX_NONE && gAutoMan.VarExists(GetS(MyString)))\n                gAutoMan.VarOperation(GetS(MyRef), gAutoMan.GetVar(GetS(MyString)), (OPTYPE)(int)Param1);\n            break;\n        }\n\n        case AT_LoadPlayerVar:\n        {\n            if(!this->ReferenceOK() || Param1 > (0x184 * 99))\n                break;\n\n            if(ftype == FT_INVALID)\n            {\n                ftype = FT_BYTE;\n                ftype = StrToFieldtype(GetS(MyString));\n            }\n\n            // Get the memory\n            //uint8_t *ptr = (uint8_t *)demo;\n            //ptr += (int)Param1; // offset\n            num_t gotval = GetMem(demo, (size_t)Param1, ftype);\n\n            // Perform the load/add/sub/etc operation on the banked variable using the ref as the name\n            gAutoMan.VarOperation(GetS(MyRef), gotval, (OPTYPE)(int)Param2);\n\n            break;\n        }\n\n        case AT_LoadNPCVar:\n        {\n            if(!this->ReferenceOK() || Param1 > (0x158))\n                break;\n            if(ftype == FT_INVALID)\n            {\n                ftype = FT_BYTE;\n                ftype = StrToFieldtype(GetS(MyString));\n            }\n\n            NPC_t *pFound_npc = NpcF::GetFirstMatch((int)Target, (int)Param3);\n            if(pFound_npc != nullptr)\n            {\n                num_t gotval = GetMem(pFound_npc, (size_t)Param1, ftype);\n                gAutoMan.VarOperation(GetS(MyRef), gotval, (OPTYPE)(int)Param2);\n            }\n\n            break;\n        }\n\n        case AT_LoadGlobalVar:\n        {\n            if(Target >= GM_BASE && Param1 <=  GM_END && ReferenceOK())\n            {\n                if(ftype == FT_INVALID)\n                {\n                    ftype = FT_BYTE;\n                    ftype = StrToFieldtype(GetS(MyString));\n                }\n\n                // byte *ptr = (byte *)(int)Target;\n                num_t gotval = GetMem((size_t)Target, ftype);\n                gAutoMan.VarOperation(GetS(MyRef), gotval, (OPTYPE)(int)Param1);\n            }\n            break;\n        }\n\n        case AT_IfVar:\n        {\n            if(!ReferenceOK())\n            {\n                InitIfMissing(&gAutoMan.m_UserVars, GetS(MyString), 0);// Initalize var if not existing\n            }\n            num_t varval;\n            if(ReferenceOK())\n                varval = gAutoMan.m_UserVars[GetS(MyRef)];\n            else\n                varval = gAutoMan.m_UserVars[GetS(MyString)];\n\n            // Check if the value meets the criteria and activate event if so\n            if(CheckConditionD(varval, Param2, (COMPARETYPE)(int)Param1))\n                gAutoMan.ActivateCustomEvents(0, (int)Param3);\n\n            break;\n        }\n\n        case AT_CompareVar:\n        {\n            if(ReferenceOK())\n            {\n                auto compare_type = (COMPARETYPE)(int)Param1;\n                InitIfMissing(&gAutoMan.m_UserVars, GetS(MyString), 0);\n                InitIfMissing(&gAutoMan.m_UserVars, GetS(MyRef), 0);\n\n                num_t var1 = gAutoMan.m_UserVars[GetS(MyRef)];\n                num_t var2 = gAutoMan.m_UserVars[GetS(MyString)];\n\n                if(CheckConditionD(var1, var2, compare_type))\n                    gAutoMan.ActivateCustomEvents(0, (int)Param3);\n            }\n            break;\n        }\n\n        case AT_ShowVar:\n        {\n            if(ReferenceOK())\n            {\n                std::string str = fmt::format_ne(\"{0}\", (double)gAutoMan.GetVar(GetS(MyRef)));\n                if(GetS(MyString).length() > 0)\n                    str = GetS(MyString) + str;\n                Renderer::Get().AddOp(new RenderStringOp(str, (int)Param3, s_round2int(Param1), s_round2int(Param2)));\n            }\n            break;\n        }\n\n        case AT_BankVar:\n        {\n            if(GetS(MyString).length() > 0)\n                gSavedVarBank.SetVar(GetS(MyString), gAutoMan.GetVar(GetS(MyString)));\n            break;\n        }\n\n        case AT_WriteBank:\n        {\n            gSavedVarBank.WriteBank();\n            break;\n        }\n\n\n        // LUNA CONTROL\n        case AT_LunaControl:\n        {\n            Autocode::LunaControl((LunaControlAct)(int)Target, (int)Param1);\n            break;\n        }\n\n\n        // DELETE COMMAND\n        case AT_DeleteCommand:\n        {\n            gAutoMan.DeleteEvent(GetS(MyString));\n            break;\n        }\n\n        // MOD PARAM\n        case AT_ModParam:\n        {\n            if(Target < 6 && Target > 0)\n            {\n                Autocode *coderef = nullptr;\n                coderef = gAutoMan.GetEventByRef(GetS(MyString));\n                if(coderef)\n                {\n                    switch((int)Target)\n                    {\n                    case 1:\n                        Autocode::modParam(coderef->Target, Param1, (OPTYPE)(int)Param2);\n                        break;\n                    case 2:\n                        Autocode::modParam(coderef->Param1, Param1, (OPTYPE)(int)Param2);\n                        break;\n                    case 3:\n                        Autocode::modParam(coderef->Param2, Param1, (OPTYPE)(int)Param2);\n                        break;\n                    case 4:\n                        Autocode::modParam(coderef->Param3, Param1, (OPTYPE)(int)Param2);\n                        break;\n                    case 5:\n                        Autocode::modParam(coderef->Length, Param1, (OPTYPE)(int)Param2);\n                        break;\n                    }\n                }\n            }\n            break;\n        }\n\n        // ChangeTime\n        case AT_ChangeTime:\n        {\n            Autocode *coderef = nullptr;\n            coderef = gAutoMan.GetEventByRef(GetS(MyString));\n            if(coderef)\n                Autocode::modParam(coderef->Length, Param1, (OPTYPE)(int)Param2);\n            break;\n        }\n\n        // INPUT BUFFER STUFF\n        case AT_OnCustomCheat:\n        {\n            if(cheats_contains(GetS(MyString)))\n            {\n                gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                cheats_clearBuffer();\n                if(Param2 != 0)\n                    this->expire();\n            }\n            break;\n        }\n\n        case AT_ClearInputString:\n        {\n            //wchar_t* dbg = L\"ClearInputString debug\";\n            cheats_clearBuffer();\n            break;\n        }\n\n        case AT_RunCheat:\n        {\n            if(this->Length <= 1) // Play once when delay runs out\n            {\n                cheats_setBuffer(GetS(MyString), true);\n                this->expire();\n            }\n            break;\n        }\n\n        case AT_DeleteEventsFrom:\n        {\n            gAutoMan.ForceExpire((int)Target);\n            break;\n        }\n\n        // LAYER CONTROL\n        case AT_LayerXSpeed:\n        {\n            Layer_t *layer = LayerF::Get((int)Target);\n            if(layer)\n            {\n                LayerF::SetXSpeed(layer, num_t::from_double(SDL_atof(GetS(MyString).c_str())));\n                if(Length == 1 && Param1 != 0)\n                    LayerF::SetXSpeed(layer, 0.0001_n);\n            }\n            break;\n        }\n\n        case AT_LayerYSpeed:\n        {\n            Layer_t *layer = LayerF::Get((int)Target);\n            if(layer)\n            {\n                LayerF::SetYSpeed(layer, num_t::from_double(SDL_atof(GetS(MyString).c_str())));\n                if(Length == 1 && Param1 != 0)\n                    LayerF::SetYSpeed(layer, 0.0001_n);\n            }\n            break;\n        }\n\n        case AT_AccelerateLayerX:\n        {\n            Layer_t *layer = LayerF::Get((int)Target);\n            if(layer)\n            {\n                num_t accel = num_t::from_double(SDL_atof(GetS(MyString).c_str()));\n                if(num_t::abs((num_t)layer->SpeedX) + num_t::abs(accel) >= num_t::abs(Param1))\n                    LayerF::SetXSpeed(layer, Param1);\n                else\n                    LayerF::SetXSpeed(layer, (num_t)layer->SpeedX + accel);\n            }\n            break;\n        }\n\n        case AT_AccelerateLayerY:\n        {\n            Layer_t *layer = LayerF::Get((int)Target);\n            if(layer)\n            {\n                num_t accel = num_t::from_double(SDL_atof(GetS(MyString).c_str()));\n                if(num_t::abs((num_t)layer->SpeedY) + num_t::abs(accel) >= num_t::abs((num_t)Param1))\n                    LayerF::SetYSpeed(layer, Param1);\n                else\n                    LayerF::SetYSpeed(layer, (num_t)layer->SpeedY + accel);\n            }\n            break;\n        }\n\n        case AT_DeccelerateLayerX:\n        {\n            Layer_t *layer = LayerF::Get((int)Target);\n            if(layer)\n            {\n                num_t deccel = num_t::from_double(SDL_atof(GetS(MyString).c_str()));\n                deccel = num_t::abs(deccel);\n                if(layer->SpeedX > 0)\n                {\n                    layer->SpeedX = numf_t((num_t)layer->SpeedX - deccel);\n                    if(layer->SpeedX < 0)\n                        LayerF::Stop(layer);\n                }\n                else if(layer->SpeedX < 0)\n                {\n                    layer->SpeedX = numf_t((num_t)layer->SpeedX + deccel);\n                    if(layer->SpeedX > 0)\n                        LayerF::Stop(layer);\n                }\n            }\n            break;\n        }\n\n\n        case AT_DeccelerateLayerY:\n        {\n            Layer_t *layer = LayerF::Get((int)Target);\n            if(layer)\n            {\n                num_t deccel = num_t::from_double(SDL_atof(GetS(MyString).c_str()));\n                deccel = num_t::abs(deccel);\n                if(layer->SpeedY > 0)\n                {\n                    layer->SpeedY = numf_t((num_t)layer->SpeedY - deccel);\n                    if(layer->SpeedY < 0)\n                        LayerF::Stop(layer);\n                }\n                else if(layer->SpeedY < 0)\n                {\n                    layer->SpeedY = numf_t((num_t)layer->SpeedY + deccel);\n                    if(layer->SpeedY > 0)\n                        LayerF::Stop(layer);\n                }\n            }\n            break;\n        }\n\n        // BLOCK MODIFIERS\n        case AT_SetAllBlocksID:\n        {\n            BlocksF::SetAll((int)Target, (int)Param1);\n            break;\n        }\n\n        case AT_SwapAllBlocks:\n        {\n            BlocksF::SwapAll((int)Target, (int)Param1);\n            break;\n        }\n\n        case AT_ShowAllBlocks:\n        {\n            BlocksF::ShowAll((int)Target);\n            break;\n        }\n\n        case AT_HideAllBlocks:\n        {\n            BlocksF::HideAll((int)Target);\n            break;\n        }\n\n\n        case AT_PushScreenBoundary:\n        {\n            int sec = (int)Target - 1;\n            if(sec >= 0 && sec < numSections && Param1 >= 0 && Param1 < 5)\n                LevelF::PushSectionBoundary(sec, (int)Param1, num_t::from_double(SDL_atof(GetS(MyString).c_str())));\n            break;\n        }\n\n        case AT_SnapSectionBounds:\n        {\n            int sec = (int)Target - 1;\n            if(sec >= 0 && sec < numSections) // Make sure valid section\n            {\n                //RECT current_bounds;\n                //int sec = ((int)Target) - 1;\n                //Level::GetBoundary(&current_bounds, sec);\n                //double x_dist = Param1 - current_bounds.left;\n                //double y_dist = Param2 - current_bounds.top;\n                //double x_stepsize = x_dist / Length;\n                //double y_stepsize = y_dist / Length;\n                //double x_stepped =  current_bounds.left + x_stepsize;\n                //double y_stepped =  current_bounds.top + y_stepsize;\n                //Level::SetSectionBounds(sec, x_stepped,  y_stepped, x_stepped + 800, y_stepped + 600);\n\n                //if(Length <= 1) { // When travel time is up, force screen into the right place\n                LevelF::SetSectionBounds(sec, Param1, Param2, Param1 + 800, Param2 + 600);\n                //}\n            }\n            break;\n        }\n\n        // PLAYER CYCLE\n        case AT_CyclePlayerRight:\n        {\n            PlayerF::CycleRight(demo);\n            break;\n        }\n\n        case AT_CyclePlayerLeft:\n        {\n            PlayerF::CycleLeft(demo);\n            break;\n        }\n\n\n        // SET HITS\n        case AT_SetHits:\n        {\n            NpcF::AllSetHits((int)Target, (int)Param1 - 1, (int)Param2);\n            break;\n        }\n\n        // FORCE FACING\n        case AT_ForceFacing:\n        {\n            // Player_t *demo = PlayerF::Get(1); // No need, everything was done above\n            // if(demo != 0)\n            NpcF::AllFace((int)Target, (int)Param1 - 1, demo->Location.SpeedX);\n            break;\n        }\n\n        case AT_TriggerSMBXEvent:\n        {\n            TriggerEvent(FindEvent(GetS(MyString)), (int)Param1);\n            break;\n        }\n\n        case AT_OnEvent:\n        {\n            if(EventWasTriggered(FindEvent(GetS(MyString))))\n            {\n                gAutoMan.ActivateCustomEvents(0, (int)Param3);\n                if(Param2 != 0)\n                    this->expire();\n            }\n            break;\n        }\n\n        case AT_CancelSMBXEvent:\n        {\n            if(Length <= 1) // Cancel event after delay\n            {\n                CancelNewEvent(FindEvent(GetS(MyString)));\n                expire();\n            }\n            break;\n        }\n\n        // PREDICATES\n        case AT_Hurt:\n        {\n            short tempint = 1;\n            if(Target == 0)\n                PlayerHurt(tempint);\n            RunSelfOption();\n            break;\n        }\n\n        case AT_Kill:\n        {\n            short tempint = 1;\n            if(Target == 0)\n                PlayerDead(tempint);\n            RunSelfOption();\n            break;\n        }\n\n        // NPC MEMORY SET\n        case AT_NPCMemSet:\n        {\n            if(ftype == FT_INVALID)\n            {\n                ftype = FT_BYTE;\n                ftype = StrToFieldtype(GetS(MyString));\n            }\n\n            // Assign the mem\n            if(ReferenceOK())   // Use referenced var as value\n            {\n                num_t gotval = gAutoMan.GetVar(GetS(MyRef));\n                NpcF::MemSet((int)Target, (size_t)Param1, gotval, (OPTYPE)(int)Param3, ftype);\n            }\n            else   // Use given value as value\n            {\n                NpcF::MemSet((int)Target, (size_t)Param1, Param2, (OPTYPE)(int)Param3, ftype); // NPC ID, offset in obj, value, op, field type\n            }\n\n            break;\n        }\n\n        // PLAYER MEMORY SET\n        case AT_PlayerMemSet:\n        {\n            if(ftype == FT_INVALID)\n            {\n                ftype = FT_BYTE;\n                ftype = StrToFieldtype(GetS(MyString));\n            }\n\n            if(ReferenceOK())\n            {\n                num_t gotval = gAutoMan.GetVar(GetS(MyRef));\n                PlayerF::MemSet((size_t)Param1, gotval, (OPTYPE)(int)Param3, ftype);\n            }\n            else\n                PlayerF::MemSet((size_t)Param1, Param2, (OPTYPE)(int)Param3, ftype);\n            break;\n        }\n\n\n        // MEM ASSIGN\n        case AT_MemAssign:\n        {\n            if(Target >= GM_BASE && Param1 <=  GM_END)\n            {\n                if(ftype == FT_INVALID)\n                {\n                    ftype = FT_BYTE;\n                    ftype = StrToFieldtype(GetS(MyString));\n                }\n\n                if(ReferenceOK())\n                {\n                    num_t gotval = gAutoMan.GetVar(GetS(MyRef));\n                    MemAssign((size_t)Target, gotval, (OPTYPE)(int)Param2, ftype);\n                }\n                else\n                    MemAssign((size_t)Target, Param1, (OPTYPE)(int)Param2, ftype);\n            }\n            break;\n        }\n\n        // DEBUG\n        case AT_DebugPrint:\n        {\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"LunaScript (TheXTech) VERSION-%d\", LUNA_VERSION), 3, 50, 250));\n            //Renderer::Get().SafePrint(, 3, 340, 250);\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"Globl: %zu\", gAutoMan.m_GlobalCodes.size()), 3, 50, 280));\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"Init:  %zu\", gAutoMan.m_InitAutocodes.size()), 3, 50, 300));\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"Codes: %zu\", gAutoMan.m_Autocodes.size()), 3, 50, 320));\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"Queue: %zu\", gAutoMan.m_CustomCodes.size()), 3, 50, 340));\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"Sprites: %d\", gSpriteMan.CountSprites()), 3, 50, 360));\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"BlueePrints: %d\", gSpriteMan.CountBlueprints()), 3, 50, 380));\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"Components: %zu\", gSpriteMan.m_ComponentList.size()), 3, 50, 400));\n\n            // some stats of lunacell were printed here\n\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"STRINGS: %zu\", StringsBankSize()), 3, 50, 460));\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"STRINGS-Unused: %zu\", StringsUnusedEntries()), 3, 50, 480));\n            Renderer::Get().AddOp(new RenderStringOp(fmt::sprintf_ne(\"Allocator-Max: %zu, prev %zu\", g_rAlloc.maximum(), g_rAlloc.prevMaximum()), 3, 50, 500));\n            break;\n        }\n\n        case AT_DebugWindow:\n        {\n            XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_INFORMATION, \"LunaScript debug message\", GetS(MyString));\n            expire();\n            break;\n        }\n\n        case AT_CollisionScan:\n        {\n            // this is now a no-op because the block lookup table is always filled\n            break;\n        }\n\n        // SPRITE FUNCTIONS\n        case AT_LoadImage:\n        {\n            // Only allow loading image during init phase\n            if(init)\n                Renderer::Get().LoadBitmapResource(GetS(MyString), (int)Target, (uint32_t)(int32_t)Param1);\n            expire();\n            break;\n        }\n\n        case AT_SpriteBlueprint:\n        {\n            if(ReferenceOK())\n            {\n                auto *blueprint = new CSprite();\n                gSpriteMan.AddBlueprint(GetS(MyRef).c_str(), blueprint);\n            }\n\n            expire();\n            break;\n        }\n\n        case AT_Attach:\n        {\n            //char* dbg = \"!!! ATTACH DEBUG !!!\";\n            if(ReferenceOK() && !GetS(MyString).empty())\n            {\n                if(gSpriteMan.m_SpriteBlueprints.find(GetS(MyRef)) != gSpriteMan.m_SpriteBlueprints.end()) // BLueprint exists\n                {\n                    CSprite *pSpr = gSpriteMan.m_SpriteBlueprints[GetS(MyRef)];                   // Get blueprint\n                    Autocode *pComponent = gAutoMan.GetEventByRef(GetS(MyString));                // Get autocode containing component\n                    if(pComponent)\n                    {\n                        switch((BlueprintAttachType)(int)Target)\n                        {\n                        case BPAT_Behavior:\n                            pSpr->AddBehaviorComponent(GenerateComponent(*pComponent));\n                            break;\n                        case BPAT_Draw:\n                            pSpr->AddDrawComponent(GetDrawFunc(*pComponent));\n                            break;\n                        case BPAT_Birth:\n                            pSpr->AddBirthComponent(GenerateComponent(*pComponent));\n                            break;\n                        case BPAT_Death:\n                            pSpr->AddDeathComponent(GenerateComponent(*pComponent));\n                            break;\n                        default:\n                            break;\n                        }\n                    }\n                }\n            }\n\n            expire();\n            break;\n        }\n\n        case AT_PlaceSprite:\n        {\n            //1: Type   2: ImgResource  3: Xpos         4: Ypos     5:              6: Extra\n            CSpriteRequest req;\n            req.type = (int)Target;\n            req.img_resource_code = (int)Param1;\n            req.x = (int)Param2;\n            req.y = (int)Param3;\n            req.time = pretick_len;\n            req.str = GetS(MyString);\n            gSpriteMan.InstantiateSprite(&req, false);\n\n            expire();\n            break;\n        }\n\n        }//switch\n\n    }//section\n}\n\n// SELF TICK\nvoid Autocode::SelfTick()\n{\n    if(Length == 1)\n        expire();\n    else if(Length == 0)\n        return;\n    else\n        Length -= 1;\n}\n\n// DO PREDICATE\nvoid Autocode::DoPredicate(int target, int predicate)\n{\n    // Activate custom event?\n    if(predicate >= 1000 && predicate < 100000)\n    {\n        gAutoMan.ActivateCustomEvents(target, predicate);\n        return;\n    }\n\n    // Else, do predicate\n    auto pred = (AutocodePredicate)predicate;\n    short tempint = 1;\n\n    switch(pred)\n    {\n    // DEATH PREDICATE\n    case AP_Hurt:\n        PlayerHurt(tempint);\n        break;\n\n    case AP_Death:\n        PlayerDead(tempint);\n        break;\n\n    default:\n        break;\n    }\n}\n\n// NPC CONDITIONAL\nbool Autocode::NPCConditional(int target, int cond)\n{\n    //const char* dbg = \"NPC COND DBG\";\n    bool ret = false;\n\n    switch((AC_Conditional)cond)\n    {\n    case AC_Invalid:\n    default:\n        return ret;\n\n    case AC_Exists:\n        return NpcF::GetFirstMatch(target, -1) != nullptr;\n\n    case AC_DoesNotExist:\n        return NpcF::GetFirstMatch(target, -1) == nullptr;\n    }\n}\n\nbool Autocode::CheckConditionI(int value1, int value2, COMPARETYPE cond)\n{\n    switch(cond)\n    {\n    case CMPT_EQUALS:\n        if(value1 == value2)\n            return true;\n        break;\n    case CMPT_GREATER:\n        if(value1 > value2)\n            return true;\n        break;\n    case CMPT_LESS:\n        if(value1 < value2)\n            return true;\n        break;\n    case CMPT_NOTEQ:\n        if(value1 != value2)\n            return true;\n        break;\n    default:\n        break;\n    }\n\n    return false;\n}\n\nbool Autocode::CheckConditionD(num_t value1, num_t value2, COMPARETYPE cond)\n{\n    switch(cond)\n    {\n    case CMPT_EQUALS:\n        if(num_t::fEqual_d(value1, value2))\n            return true;\n        break;\n    case CMPT_GREATER:\n        if(value1 > value2)\n            return true;\n        break;\n    case CMPT_LESS:\n        if(value1 < value2)\n            return true;\n        break;\n    case CMPT_NOTEQ:\n        if(!num_t::fEqual_d(value1, value2))\n            return true;\n        break;\n    default:\n        break;\n    }\n\n    return false;\n}\n\n// RUN SELF OPTION\nvoid Autocode::RunSelfOption()\n{\n    if(GetS(this->MyString).find(\"once\") != std::string::npos)\n        this->expire();\n}\n\n// REFERENCE OK\nbool Autocode::ReferenceOK() const\n{\n    return (!GetS(this->MyRef).empty());\n}\n\n\n\nvoid Autocode::HeartSystem() const\n{\n    Player_t *sheath = PlayerF::Get(1);\n\n    if(sheath)\n    {\n        // Don't run for demo or iris\n        if(!PlayerF::UsesHearts(sheath))\n            return;\n\n        // Detect extra heart pickup and hold displayed hearts at 2\n        if(sheath->Hearts > 2)\n        {\n            gAutoMan.m_Hearts++;\n            sheath->Hearts = 2;\n            sheath->State = (sheath->State > 2 ? sheath->State : 2);\n        }\n\n        if(sheath->Hearts == 2 && gAutoMan.m_Hearts < 2)\n            gAutoMan.m_Hearts = 2;\n\n        // Limit tracked max hearts\n        if(gAutoMan.m_Hearts > Param2)\n            gAutoMan.m_Hearts = (int)Param2;\n\n        // If damaged, take hearts from extra hearts\n        if(sheath->Hearts == 1 && gAutoMan.m_Hearts > 2)\n        {\n            //char* dbg = \"HEART SET\";\n            sheath->Hearts = 2;\n            sheath->State = (sheath->State > 2 ? sheath->State : 2);\n            gAutoMan.m_Hearts--;\n        }\n        else if(sheath->Hearts == 1)\n        {\n            sheath->State = 1;\n            gAutoMan.m_Hearts = 1;\n        }\n        else if(sheath->Hearts == 0)\n            gAutoMan.m_Hearts = 0;\n\n        // Display life stuff on screen\n        Renderer::Get().AddOp(new RenderStringOp(fmt::format_ne(\"HP: {}\", gAutoMan.m_Hearts),\n                                                 3, s_round2int(Target), s_round2int(Param1)));\n    }//if heartuser\n}\n\n\nvoid Autocode::LunaControl(LunaControlAct act, int val)\n{\n    switch(act)\n    {\n    case LCA_DemoCounter:\n        {\n            ConfigChangeSentinel sent(ConfigSetLevel::script);\n            g_config.show_fails_counter = (val == 1);\n        }\n\n        if(g_config.show_fails_counter && !g_config.enable_fails_tracking && !gEnableDemoCounterByLC) // Initialize the demos counter if wasn't enabled before\n        {\n            if(!GameMenu && !GameOutro && !BattleMode && !LevelEditor && !TestLevel)\n            {\n                gEnableDemoCounterByLC = true;\n                gDeathCounter.init();\n                gDeathCounter.Recount();\n            }\n        }\n        break;\n\n    case LCA_SMBXHUD:\n        gSMBXHUDSettings.skip = (val == 1);\n        break;\n\n    case LCA_Invalid:\n    default:\n        return;\n    }\n}\n\nvoid Autocode::expire()\n{\n    Expired = true;\n    gAutoMan.m_hasExpired = true;\n}\n\nvoid Autocode::modParam(num_t &dst, num_t src, OPTYPE operation)\n{\n    switch(operation)\n    {\n    default: // Do nothing\n        break;\n    case OP_Assign:\n        dst = src;\n        break;\n    case OP_Add:\n        dst += src;\n        break;\n    case OP_Sub:\n        dst -= src;\n        break;\n    case OP_Mult:\n        dst = dst.times(src);\n        break;\n    case OP_Div:\n        dst = dst.divided_by(src);\n        break;\n    case OP_XOR:\n        dst = (int)dst ^ (int)src;\n        break;\n    }\n}\n\nstatic const std::unordered_map<std::string, AutocodeType> s_commandMap =\n{\n    {\"FilterToSmall\", AT_FilterToSmall},\n    {\"FilterToBig\", AT_FilterToBig},\n    {\"FilterToFire\", AT_FilterToFire},\n    {\"FilterMount\", AT_FilterMount},\n    {\"FilterReservePowerup\", AT_FilterReservePowerup},\n    {\"FilterPlayer\", AT_FilterPlayer},\n\n    {\"SetHearts\", AT_SetHearts},\n    {\"HeartSystem\", AT_HeartSystem},\n    {\"InfiniteFlying\", AT_InfiniteFlying},\n\n    {\"ScreenEdgeBuffer\", AT_ScreenEdgeBuffer},\n\n    {\"ShowText\", AT_ShowText},\n    {\"ShowNPCLifeLeft\", AT_ShowNPCLifeLeft},\n    {\"ShowLevelName\", AT_ShowLevelName},\n    {\"ShowLevelFile\", AT_ShowLevelFile},\n\n    {\"Trigger\", AT_Trigger},\n    {\"Timer\", AT_Timer},\n    {\"IfNPC\", AT_IfNPC},\n    {\"BlockTrigger\", AT_BlockTrigger},\n    {\"IfCompatMode\", AT_IfCompatMode},\n    {\"IfSpeedRunMode\", AT_IfSpeedRunMode},\n    {\"TriggerRandom\", AT_TriggerRandom},\n    {\"TriggerRandomRange\", AT_TriggerRandomRange},\n    {\"TriggerZone\", AT_TriggerZone},\n    {\"ScreenBorderTrigger\", AT_ScreenBorderTrigger},\n    {\"OnInput\", AT_OnInput},\n    {\"OnCustomCheat\", AT_OnCustomCheat},\n    {\"OnPlayerMem\", AT_OnPlayerMem},\n    {\"OnGlobalMem\", AT_OnGlobalMem},\n    {\"RunCheat\", AT_RunCheat},\n\n    {\"SetVar\", AT_SetVar},\n    {\"CopyVar\", AT_CopyVar},\n    {\"LoadPlayerVar\", AT_LoadPlayerVar},\n    {\"LoadNPCVar\", AT_LoadNPCVar},\n    {\"LoadGlobalVar\", AT_LoadGlobalVar},\n    {\"ShowVar\", AT_ShowVar},\n    {\"IfVar\", AT_IfVar},\n    {\"CompareVar\", AT_CompareVar},\n    {\"BankVar\", AT_BankVar},\n    {\"WriteBank\", AT_WriteBank},\n\n    {\"LunaControl\", AT_LunaControl},\n\n    {\"DeleteCommand\", AT_DeleteCommand},\n    {\"ModParam\", AT_ModParam},\n    {\"ChangeTime\", AT_ChangeTime},\n\n    {\"DeleteEventsFrom\", AT_DeleteEventsFrom},\n    {\"ClearInputString\", AT_ClearInputString},\n\n    {\"LayerXSpeed\", AT_LayerXSpeed},\n    {\"LayerYSpeed\", AT_LayerYSpeed},\n    {\"AccelerateLayerX\", AT_AccelerateLayerX},\n    {\"AccelerateLayerY\", AT_AccelerateLayerY},\n    {\"DeccelerateLayerX\", AT_DeccelerateLayerX},\n    {\"DeccelerateLayerY\", AT_DeccelerateLayerY},\n    {\"SetAllBlocksID\", AT_SetAllBlocksID},\n    {\"SwapAllBlocks\", AT_SwapAllBlocks},\n    {\"ShowAllBlocks\", AT_ShowAllBlocks},\n    {\"HideAllBlocks\", AT_HideAllBlocks},\n\n    {\"PushScreenBoundary\", AT_PushScreenBoundary},\n    {\"SnapSectionBounds\", AT_SnapSectionBounds},\n\n    {\"CyclePlayerRight\", AT_CyclePlayerRight},\n    {\"CyclePlayerLeft\", AT_CyclePlayerLeft},\n\n    {\"SFX\", AT_SFX},\n    {\"PlaySFX\", AT_PlaySFX},\n    {\"StopSFX\", AT_StopSFX},\n    {\"SFXPreLoad\", AT_SFXPreLoad},\n    {\"SetMusic\", AT_SetMusic},\n    {\"PlayMusic\", AT_PlayMusic},\n\n    {\"TriggerSMBXEvent\", AT_TriggerSMBXEvent},\n    {\"OnEvent\", AT_OnEvent},\n    {\"CancelSMBXEvent\", AT_CancelSMBXEvent},\n\n    {\"Kill\", AT_Kill},\n    {\"Hurt\", AT_Hurt},\n\n    {\"SetHits\", AT_SetHits},\n    {\"NPCMemSet\", AT_NPCMemSet},\n    {\"PlayerMemSet\", AT_PlayerMemSet},\n    {\"ForceFacing\", AT_ForceFacing},\n    {\"MemAssign\", AT_MemAssign},\n    {\"DebugPrint\", AT_DebugPrint},\n    {\"DebugWindow\", AT_DebugWindow},\n\n    {\"CollisionScan\", AT_CollisionScan},\n\n    {\"LoadImage\", AT_LoadImage},\n    {\"SpriteBlueprint\", AT_SpriteBlueprint},\n    {\"Attach\", AT_Attach},\n    {\"PlaceSprite\", AT_PlaceSprite},\n\n    {\"OnPlayerCollide\", AT_OnPlayerCollide},\n    {\"OnPlayerDistance\", AT_OnPlayerDistance},\n    {\"WaitForPlayer\", AT_WaitForPlayer},\n    {\"PlayerHoldingSprite\", AT_PlayerHoldingSprite},\n    {\"RandomComponent\", AT_RandomComponent},\n    {\"RandomComponentRange\", AT_RandomComponentRange},\n    {\"SetSpriteVar\", AT_SetSpriteVar},\n    {\"IfSpriteVar\", AT_IfSpriteVar},\n    {\"IfLunaVar\", AT_IfLunaVar},\n    {\"Die\", AT_Die},\n    {\"Deccelerate\", AT_Deccelerate},\n    {\"AccelToPlayer\", AT_AccelToPlayer},\n    {\"ApplyVariableGravity\", AT_ApplyVariableGravity},\n    {\"PhaseMove\", AT_PhaseMove},\n    {\"BumpMove\", AT_BumpMove},\n    {\"CrashMove\", AT_CrashMove},\n    {\"SetXSpeed\", AT_SetXSpeed},\n    {\"SetYSpeed\", AT_SetYSpeed},\n    {\"SetAlwaysProcess\", AT_SetAlwaysProcess},\n    {\"SetVisible\", AT_SetVisible},\n    {\"SetHitbox\", AT_SetHitbox},\n    {\"TeleportNearPlayer\", AT_TeleportNearPlayer},\n    {\"TeleportTo\", AT_TeleportTo},\n    {\"HarmPlayer\", AT_HarmPlayer},\n    {\"GenerateInRadius\", AT_GenerateInRadius},\n    {\"GenerateAtAngle\", AT_GenerateAtAngle},\n    {\"BasicAnimate\", AT_BasicAnimate},\n    {\"Blink\", AT_Blink},\n    {\"AnimateFloat\", AT_AnimateFloat},\n    {\"TriggerLunaEvent\", AT_TriggerLunaEvent},\n    {\"HarmPlayer\", AT_HarmPlayer},\n    {\"SpriteTimer\", AT_SpriteTimer},\n    {\"SpriteDebug\", AT_SpriteDebug},\n    {\"StaticDraw\", AT_StaticDraw},\n    {\"RelativeDraw\", AT_RelativeDraw}\n};\n\nAutocodeType Autocode::EnumerizeCommand(char *wbuf, int lineNumber)\n{\n    if(wbuf)\n    {\n        char command[100];\n        SDL_memset(command, 0, 100 * sizeof(char));\n\n        int success = XTECH_sscanf(wbuf, \" %99[^,] ,\", command);\n        if(!success)\n        {\n            // Bad or mistyped command?\n            std::string line = std::string(wbuf);\n            gAutoMan.addError(lineNumber, line, \"Syntax error\");\n            gAutoMan.m_hasExpired = true;\n            return AT_Invalid;\n        }\n\n        auto cmd = s_commandMap.find(std::string(command));\n        if(cmd != s_commandMap.end())\n            return cmd->second;\n    }\n\n    if(wbuf)\n    {\n        // Nothing matched. Bad or mistyped command?\n        std::string line = std::string(wbuf);\n        gAutoMan.addError(lineNumber, line, \"Unknown command\");\n    }\n\n    gAutoMan.m_hasExpired = true;\n    return AT_Invalid;\n}\n\nSpriteComponent Autocode::GenerateComponent(const Autocode &obj_to_convert)\n{\n    SpriteComponent comp;\n    comp.Init((int)obj_to_convert.Length);\n    comp.data1 = obj_to_convert.Target;\n    comp.data2 = obj_to_convert.Param1;\n    comp.data3 = obj_to_convert.Param2;\n    comp.data4 = obj_to_convert.Param3;\n    comp.data5 = GetS(obj_to_convert.MyString);\n    comp.lookup_code = obj_to_convert.ActiveSection;\n\n    comp.func = Autocode::GetSpriteFunc(obj_to_convert);\n    return comp;\n}\n\npfnSprFunc Autocode::GetSpriteFunc(const Autocode &pAC)\n{\n    switch(pAC.m_Type)\n    {\n    case AT_OnPlayerCollide:\n        return SpriteFunc::OnPlayerCollide;\n    case AT_OnPlayerDistance:\n        return SpriteFunc::OnPlayerDistance;\n    case AT_WaitForPlayer:\n        return SpriteFunc::WaitForPlayer;\n    case AT_PlayerHoldingSprite:\n        return SpriteFunc::PlayerHoldingSprite;\n    case AT_RandomComponent:\n        return SpriteFunc::RandomComponent;\n    case AT_RandomComponentRange:\n        return SpriteFunc::RandomComponentRange;\n    case AT_SetSpriteVar:\n        return SpriteFunc::SetSpriteVar;\n    case AT_IfSpriteVar:\n        return SpriteFunc::IfSpriteVar;\n    case AT_IfLunaVar:\n        return SpriteFunc::IfLunaVar;\n    case AT_Die:\n        return SpriteFunc::Die;\n    case AT_Deccelerate:\n        return SpriteFunc::Deccelerate;\n    case AT_AccelToPlayer:\n        return SpriteFunc::AccelToPlayer;\n    case AT_ApplyVariableGravity:\n        return SpriteFunc::ApplyVariableGravity;\n    case AT_PhaseMove:\n        return SpriteFunc::PhaseMove;\n    case AT_BumpMove:\n        return SpriteFunc::BumpMove;\n    case AT_CrashMove:\n        return SpriteFunc::CrashMove;\n    case AT_SetXSpeed:\n        return SpriteFunc::SetXSpeed;\n    case AT_SetYSpeed:\n        return SpriteFunc::SetYSpeed;\n    case AT_SetAlwaysProcess:\n        return SpriteFunc::SetAlwaysProcess;\n    case AT_SetVisible:\n        return SpriteFunc::SetVisible;\n    case AT_SetHitbox:\n        return SpriteFunc::SetHitbox;\n    case AT_TeleportNearPlayer:\n        return SpriteFunc::TeleportNearPlayer;\n    case AT_TeleportTo:\n        return SpriteFunc::TeleportTo;\n    case AT_GenerateInRadius:\n        return SpriteFunc::GenerateInRadius;\n    case AT_GenerateAtAngle:\n        return SpriteFunc::GenerateAtAngle;\n    case AT_BasicAnimate:\n        return SpriteFunc::BasicAnimate;\n    case AT_Blink:\n        return SpriteFunc::Blink;\n    case AT_AnimateFloat:\n        return SpriteFunc::AnimateFloat;\n    case AT_TriggerLunaEvent:\n        return SpriteFunc::TriggerLunaEvent;\n    case AT_HarmPlayer:\n        return SpriteFunc::HarmPlayer;\n    case AT_SpriteTimer:\n        return SpriteFunc::SpriteTimer;\n    case AT_SpriteDebug:\n        return SpriteFunc::SpriteDebug;\n    default:\n        return nullptr;\n    }\n}\n\npfnSprDraw Autocode::GetDrawFunc(const Autocode &pAC)\n{\n    switch(pAC.m_Type)\n    {\n    case AT_StaticDraw:\n        return SpriteFunc::StaticDraw;\n    case AT_RelativeDraw:\n        return SpriteFunc::RelativeDraw;\n    default:\n        return nullptr;\n    }\n}\n"
  },
  {
    "path": "src/script/luna/autocode.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef AutoCode_hhh\n#define AutoCode_hhh\n\n#include <string>\n\n#include \"numeric_types.h\"\n#include \"lunadefs.h\"\n#include \"global_strings.h\"\n\nenum LunaControlAct\n{\n    LCA_Invalid = 0,\n    LCA_DemoCounter = 1,\n    LCA_SMBXHUD = 2,\n};\n\nenum BlueprintAttachType\n{\n    BPAT_Behavior = 0,\n    BPAT_Draw = 1,\n    BPAT_Birth = 2,\n    BPAT_Death = 3,\n};\n\nenum AutocodeType\n{\n    AT_Invalid = 0,\n    AT_FilterToSmall,           //1:        2:              3:              4:          5: Active time  6:\n    AT_FilterToBig,             //1:        2:              3:              4:          5: Active time  6:\n    AT_FilterToFire,            //1:        2:              3:              4:          5: Active time  6:\n    AT_FilterMount,             //1:        2:              3:              4:          5: Active time  6:\n    AT_FilterReservePowerup,    //1:        2:              3:              4:          5: Active time  6:\n    AT_FilterPlayer,            //1:        2: Filter ID    3: Filter to    4:          5: Active time  6:\n\n    AT_InfiniteFlying,          //1:        2:              3:              4:          5: Active time  6:\n    AT_HeartSystem,             //1: X pos  2: Y pos        3: Max hearts   4:          5: Active time  6:\n\n    AT_ScreenEdgeBuffer,        //1: NPC ID 2: UDLR Bordr   3: Buffer space 4:          5: Active time  6:\n\n    AT_ShowText,                //1:        2: Xpos         3: Ypos         4: Font #   5: Active time  6: Text\n    AT_ShowNPCLifeLeft,         //1: NpcID  2: Xpos         3: Ypos         4: Section  5: Active time  6: Base health\n    AT_ShowLevelName,           //1:        2: Xpos         3: Ypos         4: Font #   5: Active time  6:\n    AT_ShowLevelFile,           //1:        2: Xpos         3: Ypos         4: Font #   5: Active time  6:\n\n    AT_Trigger,                 //1: Newsec 2: Event        3:              4:          5: Active time  6:\n    AT_Timer,                   //1: Target 2: Effect       3: Display Y/N  4: Repeat?  5: Active time  6:\n    AT_IfNPC,                   //1: NpcID  2: Condition    3: Argument     4: Event    5: Active time  6: Option\n    AT_BlockTrigger,            //1: NpcID  2: Block Type   3: Argument     4: Event    5: Active time  6: Option\n    AT_IfCompatMode,            //1:        2: Condition    3: Mode         4: Event    5: Active time  6:\n    AT_IfSpeedRunMode,          //1:        2: Condition    3: Mode         4: Event    5: Active time  6:\n    AT_TriggerRandom,           //1: Event  2: Event        3: Event        4: Event    5: Active time  6:\n    AT_TriggerRandomRange,      //1: StartEv2: EndEv        3:              4:          5: Active time  6:\n    AT_TriggerZone,             //1: Event  2: Top          3: Bottom       4: Left     5: Right        6: Option\n    AT_ScreenBorderTrigger,     //1: EventL 2: EventU       3: EventR       4: EventD   5: ActiveTime   6: Inward depth\n\n    AT_OnInput,                 //1:        2: Button       3: First frame? 4: Event    5: Active time  6:\n    AT_OnCustomCheat,           //1:        2:              3: Only once?   4: Event    5: Active time  6: Cheat string\n    AT_OnPlayerMem,             //1: Offset 2: Value        3: Comparison   4: Event    5: Active time  6: Type (b, w, dw, f, qw/df)\n    AT_OnGlobalMem,             //1: Addr   2: Value        3: Comparison   4: Event    5: Active time  6: Type (b, w, dw, f, qw/df)\n    AT_RunCheat,                //1:        2:              3:              4:          5: Delay        6: Cheat string\n\n    AT_SetVar,                  //1:        2: Op           3: Value        4:          5: Active time  6:\n    AT_CopyVar,                 //1:        2: Op           3:              4:          5: Active time  6: Source variable name; $ Target Variable Name\n    AT_LoadPlayerVar,           //1:        2: Offset       3: Op           4:          5: Active time  6: Type (b, w, dw, f, qw/df)\n    AT_LoadNPCVar,              //1: Type   2: Offset       3: Op           4: Section  5: Active time  6: Type (b, w, dw, f, qw/df)\n    AT_LoadGlobalVar,           //1: Addr   2: Op           3:              4:          5: Active time  6: Type (b, w, dw, f, qw/df)\n    AT_IfVar,                   //1:        2: CompareType  3: Value        4: Event    5: Active time  6:\n    AT_CompareVar,              //$Var 1    1:  2:CompareType 3:            4: Event    5: ActiveTime   6: Var 2\n    AT_ShowVar,                 //1:        2: Xpos         3: Ypos         4: Font #   5: Active time  6: Base text\n    AT_BankVar,                 //1:        2:              3:              4:          5: Active time  6: Name of luna var\n    AT_WriteBank,               //1:        2:              3:              4:          5:              6:\n\n    AT_LunaControl,             //1: Action 2: Value        3:              4:          5: Active time  6:\n\n    AT_DeleteCommand,           //1:        2:              3:              4:          5: Active time  6: Refernece to cmd\n    AT_ModParam,                //1: To mod#2: Value        3: Operation    4:          5: Active time  6: Refernece to cmd\n    AT_ChangeTime,              //1:        2: Value        3: Operation    4:          5: Active time  6: Refernece to cmd\n\n    AT_DeleteEventsFrom,        //1: Section2:              3:              4:          5: Active time  6:\n    AT_ClearInputString,        //1:        2:              3:              4:          5: Active time  6:\n\n    AT_LayerXSpeed,             //1: Layer #2: Stop after?  3:              4:          5: Active time  6: Speed\n    AT_LayerYSpeed,             //1: Layer #2: Stop after?  3:              4:          5: Active time  6: Speed\n    AT_AccelerateLayerX,        //1: Layer #2: Max speed    3:              4:          5: Active time  6: Speed\n    AT_AccelerateLayerY,        //1: Layer #2: Max speed    3:              4:          5: Active time  6: Speed\n    AT_DeccelerateLayerX,       //1: Layer #2: Deccel to    3:              4:          5: Active time  6: Speed\n    AT_DeccelerateLayerY,       //1: Layer #2: Deccel to    3:              4:          5: Active time  6: Speed\n\n    AT_SetAllBlocksID,          //1: Type   2: Set to       3:              4:          5: Active time  6:\n    AT_SwapAllBlocks,           //1: Type 1 2: Type 2       3:              4:          5: Active time  6:\n    AT_ShowAllBlocks,           //1: Type 1 2:              3:              4:          5: Active time  6:\n    AT_HideAllBlocks,           //1: Type 1 2:              3:              4:          5: Active time  6:\n\n    AT_PushScreenBoundary,      //1: Section2: UDLR         3:              4:          5: Active time  6: Speed\n    AT_SnapSectionBounds,       //1: Section2: Target X     3: Target Y     4:          5: Active time  6:\n\n    AT_CyclePlayerRight,        //1:        2:              3:              4:          5: Active time  6:\n    AT_CyclePlayerLeft,         //1:        2:              3:              4:          5: Active time  6:\n\n    AT_SetHearts,               //1:        2: Hearts       3:              4:          5: Active time  6:\n    AT_SetHits,                 //1: NpcID  2: Section      3: Hits         4:          5: Active time  6:\n    AT_ForceFacing,             //1: NpcID  2: Section      3:              4:          5: Active time  6:\n\n    AT_SFX,                     //1:        2: Index        3:              4:          5: Delay        6:\n    AT_PlaySFX,                 //1:        2: Index        3: Loops        4: Volume   5: Delay        6:\n    AT_StopSFX,                 //1:        2: Index        3:              4:          5: Delay        6:\n    AT_SFXPreLoad,              //1:        2: Index        3:              4:          5: Delay        6:\n    AT_SetMusic,                //1: Section2: Music #      3:              4:          5: Active time  6: Optional file name\n    AT_PlayMusic,               //1:        2: Section      3:              4:          5: Delay        6:\n\n    AT_TriggerSMBXEvent,        //1:        2: Arg          3:              4:          5: Active time  6: Name of event\n    AT_OnEvent,                 //1:        2:              3: Only once?   4: Event    5: Active time  6: Name of event\n    AT_CancelSMBXEvent,         //1:        2: Arg          3:              4:          5: Active time  6: Name of event\n\n    AT_Hurt,                    //1: Target 2:              3:              4:          5: Active time  6: Option\n    AT_Kill,                    //1: Target 2:              3:              4:          5: Active time  6: Option\n\n    AT_NPCMemSet,               //1: Type   2: Offset       3: Value        4: Op       5: Active time  6: Type (b, w, dw, f, qw/df)\n    AT_PlayerMemSet,            //1:        2: Offset       3: Value        4: Op       5: Active time  6: Type (b, w, dw, f, qw/df)\n    AT_MemAssign,               //1: Addr   2: Value        3: Operation    4:          5: Active time  6: Type (b, w, dw, f, qw/df)\n\n    AT_DebugPrint,              //1:        2:              3:              4:          5: Active time  6:\n    AT_DebugWindow,             //1:        2:              3:              4:          5:              6: Text\n\n    AT_CollisionScan,           //1:        2:              3:              4:          5: Active time  6:\n\n    /// Sprite stuff ///\n    AT_LoadImage,               //1: Code   2: TransColor   3:              4:          5:              6: File name\n    AT_SpriteBlueprint,         //0: $RefName, others empty\n    AT_Attach,                  //0: $Target blueprint,     1: Attach type  2: 3: 4: 5: 6: Source component name\n    AT_PlaceSprite,             //1: Type   2: ImgResource  3: Xpos         4: Ypos     5: Sprite time  6:\n\n    // Sprite components //\n    AT_OnPlayerCollide = 10000, //1:        2: Player small circle hitbox? 3: 4: Comp   5: Active time  6:\n    AT_OnPlayerDistance,        //1: Dist   2: far/close    3:              4: Comp     5: Active time  6:\n    AT_WaitForPlayer,           //1: Offset 2: Value        3: Comparison   4: Comp     5: Active time  6: Type (b, w, dw, f, qw/df)\n    AT_PlayerHoldingSprite,     //1: Type   2:              3:              4: Comp     5: Active time  6:\n\n    AT_RandomComponent,         //1: choice 2: choice       3: choice       4: choice   5: Active time  6:\n    AT_RandomComponentRange,    //1: start  2: end          3:              4:          5: Active time  6:\n\n    AT_SetSpriteVar,            //1:        2: Op           3: Value        4:          5: Active time  6:\n    AT_IfSpriteVar,             //1:        2: CompareType  3: Value        4: Event    5: Active time  6:\n    AT_IfLunaVar,               //1:        2: CompareType  3: Value        4: Event    5: Active time  6:\n\n    AT_Die,                     //1:        2:              3:              4:          5: Active time  6:\n\n    AT_Deccelerate,             //1: X rate 2: Y rate       3: Minimum      4:          5: Active time  6:\n    //AT_Accelerate             //1: X rate 2: Y rate       3: Maximum      4:          5: Active time  6: Option\n    AT_AccelToPlayer,           //1: X rate 2: Y rate       3: Maximum      4:          5: Active time  6:\n    AT_ApplyVariableGravity,    //1:        2: 0=x 1=y      3:              4:          5: Active time  6: Name of lunadll variable\n\n    AT_TeleportNearPlayer,      //1: Radius 2:              3:              4:          5: Active time  6:\n    AT_TeleportTo,              //1: x      2: y            3:              4:          5: Active time  6:\n\n    AT_PhaseMove,               //1:        2:              3:              4:          5: Active time  6:\n    AT_BumpMove,                //1:        2: NRGLoss%     3:              4:          5: Active time  6:\n    AT_CrashMove,               //1:        2:              3:              4:          5: Active time  6:\n\n    AT_SetXSpeed,               //1: Speed  2:              3:              4:          5: Active time  6:\n    AT_SetYSpeed,               //1: Speed  2:              3:              4:          5: Active time  6:\n    AT_SetAlwaysProcess,        //1: Off/On\n    AT_SetVisible,              //1: Off/On\n    AT_SetHitbox,               //1: LeftOff2: Top offset   3: Width        4: Height   5: Active time  6: option\n\n    AT_BasicAnimate,            //1: Height 2: Speed\n    AT_Blink,                   //1: Speed  2: Reverse      3:              4:          5: Active time  6:\n    AT_AnimateFloat,            //1: Speed  2: X magnitude  3: Y magnitude\n\n    AT_TriggerLunaEvent,        //1: Event  2:              3:              4:          5: Active time  6:\n    AT_HarmPlayer,              //1:        2:              3:              4:          5: Active time  6:\n\n    AT_GenerateInRadius,        //1:        2: ImgResource  3: Radius       4: SprTime  5: Active time  6: Sprite blueprint name\n    AT_GenerateAtAngle,         //1:        2: ImgResource  3: Speed        4: SprTime  5: Active time  6: Sprite blueprint name\n\n    AT_SpriteTimer,             //1:        2:              3: Repeat       4: Comp     5: Active time  6:\n\n    AT_SpriteDebug,             //1:        2:              3: Repeat       4: Comp     5: Active time  6:\n\n    AT_StaticDraw,\n    AT_RelativeDraw\n};\n\nenum AutocodePredicate\n{\n    AP_Invalid = 0,\n    AP_Death,\n    AP_Hurt\n};\n\nenum AC_Conditional\n{\n    AC_Invalid = 0,\n    AC_Exists,\n    AC_DoesNotExist\n};\n\nstruct SpriteComponent; // forward dec\n\n// An autocode event\nclass Autocode\n{\n\npublic:\n    // Ctors\n    Autocode();\n    Autocode(AutocodeType, num_t Target, num_t p1, num_t p2, num_t p3,\n             const stringindex_t &p4, num_t Length, int Section, const stringindex_t &VarRef);\n    Autocode(const Autocode &o);\n    ~Autocode() = default;\n\n    Autocode &operator=(const Autocode &o);\n\n    void Do(bool init);\n    static void DoPredicate(int target, int predicate);\n\n    static bool NPCConditional(int NPCID, int condition);\n    static bool CheckConditionI(int value1, int value2, COMPARETYPE cond);\n    static bool CheckConditionD(num_t value1, num_t value2, COMPARETYPE cond);\n\n    static AutocodeType EnumerizeCommand(char *wbuf, int lineNumber = -1);\n    static SpriteComponent GenerateComponent(const Autocode &obj_to_convert); // Convert an autocode object to a sprite component\n    static pfnSprFunc GetSpriteFunc(const Autocode &pAC);                     // Get sprite function address from AC object, or NULL\n    static pfnSprDraw GetDrawFunc(const Autocode &pAC);                       // Get draw function address from AC object, or NULL\n\n    /// Command functions ///\n    void HeartSystem() const;\n    static void LunaControl(LunaControlAct act, int val);\n\n    /// Members ///\n    AutocodeType m_Type = AT_Invalid;\n\n    num_t Target = 0;                          // arg 1 \"Target\"\n    num_t Param1 = 0;                          // arg 2 \"Param 1\"\n    num_t Param2 = 0;                          // arg 3 \"Param 2\"\n    num_t Param3 = 0;                          // arg 4 \"Param 3\"\n    num_t Length = 0;                          // arg 5 \"Length\"\n    stringindex_t MyString = STRINGINDEX_NONE; // arg 6 \"string\"\n    stringindex_t MyRef = STRINGINDEX_NONE;    // Optional arg 0 value\n\n    num_t m_OriginalTime = 0;\n    int ActiveSection = 0;          // Section to be active in, or custom event ID if > 1000\n    FIELDTYPE ftype = FT_INVALID;\n    bool Activated = false;             // False for custom event blueprints\n    bool Expired = false;\n\n    void expire();\n\n    //SpriteComponent* comp;\n\nprivate:\n    static void modParam(num_t &dst, num_t src, OPTYPE operation);\n    void SelfTick();\n    void RunSelfOption(); // activate the string portion of this code on self\n    bool ReferenceOK() const; // check if this object has a valid reference (not empty)\n};\n\n#endif // AutoCode_hhh\n"
  },
  {
    "path": "src/script/luna/autocode_manager.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Utils/files.h>\n#include <Utils/dir_list_ci.h>\n#include <AppPath/app_path.h>\n#include <Logger/logger.h>\n#include <fmt_format_ne.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"core/msgbox.h\"\n#include \"main/translate_episode.h\"\n\n#include \"autocode_manager.h\"\n#include \"globals.h\"\n#include \"global_dirs.h\"\n#include \"lunamisc.h\"\n#include \"lunaspriteman.h\"\n\n#define PARSEDEBUG true\n\nAutocodeManager gAutoMan;\n\n// CTOR\nAutocodeManager::AutocodeManager() noexcept\n{\n    Clear();\n}\n\nAutocodeManager::~AutocodeManager()\n{\n    Clear();\n}\n\n// CLEAR - Delete all autocodes and clear lists (and reset hearts to 2)\nvoid AutocodeManager::Clear()\n{\n    // Clear global codes\n    m_GlobalCodes.clear();\n    m_GlobalEnabled = false;\n\n    m_globcodeIdxRef.clear();\n    m_globcodeIdxSection.clear();\n\n\n    // Clear level local and index tables\n    m_Autocodes.clear();\n    m_InitAutocodes.clear();\n    m_CustomCodes.clear();\n\n    m_autocodeIdxRef.clear();\n    m_autocodeIdxSection.clear();\n\n    m_Hearts = 2;\n\n    m_Enabled = false;\n}\n\nbool AutocodeManager::LoadFiles()\n{\n    bool ret = false;\n    std::string lunaLevel, lunaWorld,\n                keyLevel, keyWorld;\n\n    g_dirEpisode.setCurDir(FileNamePath);\n    g_dirCustom.setCurDir(FileNamePath + FileName);\n\n    Clear();\n\n    // Load autocode\n    lunaLevel = g_dirCustom.resolveFileCaseExistsAbs(AUTOCODE_FNAME);\n    keyLevel = FileName + \"/\" + g_dirCustom.resolveFileCase(AUTOCODE_FNAME);\n    if(!lunaLevel.empty())\n        ret |= ReadFile(lunaLevel, keyLevel);\n\n    // Try to load world codes\n    lunaWorld = g_dirEpisode.resolveFileCaseExistsAbs(WORLDCODE_FNAME);\n    keyWorld = g_dirEpisode.resolveFileCase(WORLDCODE_FNAME);\n    if(!lunaWorld.empty())\n        ret |= ReadWorld(lunaWorld, keyWorld);\n\n    // Attempt to load global codes at the assets directory\n    ret |= ReadGlobals(AppPathManager::assetsRoot() + GLOBALCODE_FNAME);\n\n    // Do some stuff\n    DoEvents(true); // do with init\n\n    return ret;\n}\n\n// READ FILE - Read the autocode file in the level folder\nbool AutocodeManager::ReadFile(const std::string &script_path, const std::string& tr_key)\n{\n    SDL_RWops *code_file = Files::open_file(script_path, \"rb\");\n    if(!code_file)\n        return false;\n\n    pLogDebug(\"Loading %s level local autocode script...\", script_path.c_str());\n\n    m_Enabled = true;\n    Parse(code_file, false, tr_key);\n    SDL_RWclose(code_file);\n    showErrors(script_path);\n\n    return true;\n}\n\n// READ WORLD - Read the world autocode file in the world folder\nbool AutocodeManager::ReadWorld(const std::string &script_path, const std::string& tr_key)\n{\n    SDL_RWops *code_file = Files::open_file(script_path, \"rb\");\n    if(!code_file)\n        return false;\n\n    pLogDebug(\"Loading %s episode wide autocode script...\", script_path.c_str());\n\n    m_Enabled = true;\n    Parse(code_file, false, tr_key);\n    SDL_RWclose(code_file);\n    showErrors(script_path);\n\n    return true;\n}\n\n// READ GLOBALS - Read the global code file\nbool AutocodeManager::ReadGlobals(const std::string &script_path)\n{\n    SDL_RWops *code_file = Files::open_file(script_path, \"rb\");\n    if(!code_file)\n        return false;\n\n    pLogDebug(\"Loading %s global autocode script...\", script_path.c_str());\n\n    m_Enabled = true;\n    Parse(code_file, true);\n    SDL_RWclose(code_file);\n    m_GlobalEnabled = true;\n    showErrors(script_path);\n\n    return true;\n}\n\ntemplate<int temp_buf_size>\nstruct read_buf\n{\n    char temp_buf[temp_buf_size];\n    int temp_buf_cur = 0;\n    int temp_buf_end = 0;\n    bool eof = false;\n\n    bool fgets(char* out_buf, int out_buf_size, SDL_RWops *code_file)\n    {\n        char* out_buf_end = out_buf + out_buf_size;\n        if(out_buf >= out_buf_end)\n            return false;\n\n        while(true)\n        {\n            while(temp_buf_cur < temp_buf_end)\n            {\n                char c = temp_buf[temp_buf_cur++];\n\n                if(c == '\\r')\n                    continue;\n\n                *(out_buf++) = c;\n                if(out_buf >= out_buf_end)\n                {\n                    out_buf[-1] = '\\0';\n                    return true;\n                }\n\n                if(c == '\\n')\n                {\n                    *(out_buf++) = '\\0';\n                    return true;\n                }\n            }\n\n            temp_buf_cur = 0;\n            temp_buf_end = SDL_RWread(code_file, temp_buf, 1, sizeof(temp_buf));\n\n            if(temp_buf_end == 0)\n                eof = true;\n\n            if(eof)\n            {\n                *(out_buf++) = '\\0';\n                return false;\n            }\n        }\n    }\n};\n\n// PARSE    - Parse the autocode file and populate manager with the contained code/settings\n//            Doesn't delete codes already in the lists\nvoid AutocodeManager::Parse(SDL_RWops *code_file, bool add_to_globals, const std::string& tr_key)\n{\n    read_buf<2048> readbuf;\n\n    char wbuf[2000];\n    char wmidbuf[2000];\n    size_t wbuflen = 0;\n    char combuf[150];\n    int cur_section = 0;\n    AutocodeType ac_type = AT_Invalid;\n\n    char wstrbuf[1000];\n    char wrefbuf[128];\n    int lineNum = 0;\n\n    SDL_memset(wbuf, 0, 2000);\n    SDL_memset(combuf, 0, 150);\n    SDL_memset(wstrbuf, 0, 1000);\n    SDL_memset(wrefbuf, 0, 128);\n\n    m_errors.clear();\n\n    TranslateEpisode tr;\n    tr.loadLunaScript(tr_key);\n\n    SDL_RWseek(code_file, 0, RW_SEEK_SET);\n\n    // Check and skip a BOM marker\n    const char *charset;\n    if(Files::skipBom(code_file, &charset) != Files::CHARSET_UTF8)\n    {\n        addError(lineNum, charset, \"File uses an unsupported charset. Please save it as UTF-8.\");\n        return;\n    }\n\n    //char* dbg = \"ParseDbgEOF\";\n    while(!readbuf.eof)\n    {\n        // Get a line and reset buffers\n        SDL_memset(wbuf, 0, sizeof(wbuf));\n        SDL_memset(wmidbuf, 0, sizeof(wmidbuf));\n        SDL_memset(wstrbuf, 0, sizeof(wstrbuf));\n        SDL_memset(wrefbuf, 0, sizeof(wrefbuf));\n        SDL_memset(combuf, 0, sizeof(combuf));\n\n        num_t target = 0;\n        num_t param1 = 0;\n        num_t param2 = 0;\n        num_t param3 = 0;\n        num_t length = 0;\n        double dtarget = 0;\n        double dparam1 = 0;\n        double dparam2 = 0;\n        double dparam3 = 0;\n        double dlength = 0;\n        int btarget = 0;\n        int bparam1 = 0;\n        int bparam2 = 0;\n        int bparam3 = 0;\n        int blength = 0;\n\n        if(!readbuf.fgets(wbuf, 2000, code_file))\n            break; // End of file has reached\n        lineNum++;\n\n        // Is it a comment?\n        char *commentLine = SDL_strstr(wbuf, \"//\");\n        if(commentLine != nullptr)\n            commentLine[0] = '\\0'; // Cut the comment line at here\n\n        // does this line contains anything useful?\n        wbuflen = SDL_strlen(wbuf);\n        while(wbuflen > 0)\n        {\n            auto c = wbuf[wbuflen - 1];\n            if(c != '\\n' && c != '\\r' && c != '\\t' && c != ' ')\n                break;\n            wbuf[wbuflen - 1] = '\\0';\n            wbuflen--;\n        }\n\n        // Is it an empty line?\n        if(wbuflen <= 0)\n            continue;\n\n        // Is it a new section header?\n        if(wbuf[0] == '#')\n        {\n            // Is it the level load header?\n            if(wbuf[1] == '-')\n                cur_section = -1;\n            else if(SDL_strncasecmp(wbuf + 1, \"end\", sizeof(wbuf) - 1) == 0)\n                continue; // \"END\" keyword, just do nothing\n            else // Else, parse the section number\n            {\n                try\n                {\n                    cur_section = std::stoi(wbuf + 1);\n                }\n                catch (const std::exception &e)\n                {\n                    addError(lineNum, wbuf, fmt::format_ne(\"Bad section format ({0})\", e.what()));\n                    // Keep section number unchanged\n                }\n            }\n        }\n        else   // Else, parse as a command\n        {\n            // Is there a variable reference marker?\n            if(wbuf[0] == '$')\n            {\n                int i = 1;\n                while(wbuf[i] != ',' && wbuf[i] != '\\x0D' && wbuf[i] != '\\x0A' && i < 126)\n                    ++i;\n\n                wbuf[i] = '\\x00'; // Turn the comma into a null terminator\n                SDL_strlcpy(wrefbuf, &wbuf[1], sizeof(wrefbuf)); // Copy new string into wrefbuf\n                SDL_memcpy(wmidbuf, wbuf, sizeof(wmidbuf));\n                SDL_strlcpy(wbuf, &wmidbuf[i + 1], sizeof(wbuf)); // The rest of the line minus the ref is the new wbuf\n            }\n\n            ac_type = Autocode::EnumerizeCommand(wbuf, lineNum);\n\n            // Decimal pass\n            int hits = XTECH_sscanf(wbuf, PARSE_FMT_STR, combuf, &dtarget, &dparam1, &dparam2, &dparam3, &dlength, wstrbuf);\n\n            // Integer pass\n            int bhits = XTECH_sscanf(wbuf, PARSE_FMT_STR_2, combuf, &btarget, &bparam1, &bparam2, &bparam3, &blength, wstrbuf);\n\n            // Check for formatting failure\n            if(hits < 3 && bhits < 3)\n            {\n                addError(lineNum, wbuf, \"Bad line format\");\n                continue;\n            }\n\n            // Check for hexadecimal inputs\n            //if(true) // Always true\n            {\n                target = num_t::from_double(dtarget);\n                param1 = num_t::from_double(dparam1);\n                param2 = num_t::from_double(dparam2);\n                param3 = num_t::from_double(dparam3);\n                length = num_t::from_double(dlength);\n\n                if(target == 0 && btarget != 0)\n                    target = btarget;\n                if(param1 == 0 && bparam1 != 0)\n                    param1 = bparam1;\n                if(param2 == 0 && bparam2 != 0)\n                    param2 = bparam2;\n                if(param3 == 0 && bparam3 != 0)\n                    param3 = bparam3;\n                if(length == 0 && blength != 0)\n                    length = blength;\n            }\n\n            // Register new autocode\n\n            std::string ac_str = std::string(wstrbuf); // Get the string out of strbuf\n\n            switch(ac_type) // Translate string if possible\n            {\n            case AT_ShowText:\n            case AT_ShowVar:\n                tr.trScriptLine(ac_str, lineNum);\n                break;\n            default:\n                break;\n            }\n\n            ac_str.erase(ac_str.find_last_not_of(\" \\n\\r\\t\") + 1);\n\n            std::string ref_str = std::string(wrefbuf); // Get var reference string if any\n\n            Autocode newcode(ac_type, target, param1, param2, param3, AllocS(ac_str), length, cur_section, AllocS(ref_str));\n            if(!add_to_globals)\n            {\n                if(newcode.m_Type < 10000 || newcode.MyRef != STRINGINDEX_NONE)\n                {\n                    m_Autocodes.emplace_back(std::move(newcode));\n                    addToIndex(&m_Autocodes.back());\n                }\n                else   // Sprite components (type 10000+) with no reference go into callable component list\n                    gSpriteMan.m_ComponentList.push_back(Autocode::GenerateComponent(newcode));\n            }\n            else\n            {\n                if(newcode.m_Type < 10000)\n                {\n                    m_GlobalCodes.emplace_back(std::move(newcode));\n                    addToIndexGlob(&m_GlobalCodes.back());\n                }\n            }\n        }\n    }//while\n}\n\nstd::string AutocodeManager::resolveWorldFileCase(const std::string &in_name)\n{\n    return g_dirEpisode.resolveFileCaseExistsAbs(in_name);\n}\n\nstd::string AutocodeManager::resolveCustomFileCase(const std::string &in_name)\n{\n    return g_dirCustom.resolveFileCaseExistsAbs(in_name);\n}\n\n// DO EVENTS\nvoid AutocodeManager::DoEvents(bool init)\n{\n    //char* dbg = \"DO EVENTS DBG\";\n    if(m_Enabled)\n    {\n        // Add any outstanding custom events\n        while(!m_CustomCodes.empty())\n        {\n            m_Autocodes.push_back(m_CustomCodes.back());\n            m_CustomCodes.pop_back();\n            addToIndex(&m_Autocodes.back());\n        }\n\n        // Do each code\n        for(auto &m_Autocode : m_Autocodes)\n            m_Autocode.Do(init);\n    }\n\n    if(m_GlobalEnabled)\n    {\n        // Do each global code\n        for(auto &m_GlobalCode : m_GlobalCodes)\n            m_GlobalCode.Do(init);\n    }\n}\n\n// GET EVENT BY REF\nAutocode *AutocodeManager::GetEventByRef(const std::string &ref_name)\n{\n    if(ref_name.length() > 0)\n    {\n        auto ref = m_autocodeIdxRef.find(ref_name);\n        if(ref == m_autocodeIdxRef.end() || ref->second.empty())\n            return nullptr;\n\n        return ref->second.front();\n    }\n    return nullptr;\n}\n\n// DELETE EVENT -- Expires any command that matches the given name\nvoid AutocodeManager::DeleteEvent(const std::string &ref_name)\n{\n    if(ref_name.length() > 0)\n    {\n        auto ref = m_autocodeIdxRef.find(ref_name);\n        if(ref != m_autocodeIdxRef.end())\n        {\n            for(auto *code : ref->second)\n                code->expire();\n        }\n    }\n}\n\n// CLEAN EXPIRED - Don't call this while iterating over codes\nvoid AutocodeManager::ClearExpired()\n{\n    if(!m_hasExpired)\n        return; // Nothing to do\n\n// #define DEBUG_CLEAN_EXPIRED\n\n#ifdef DEBUG_CLEAN_EXPIRED\n    int cleanedAutos = 0, cleanedGlobs = 0;\n#endif\n\n    //char* dbg = \"CLEAN EXPIRED DBG\";\n    auto iter = m_Autocodes.begin();\n    auto end  = m_Autocodes.end();\n\n    while(iter != m_Autocodes.end())\n    {\n        //Autocode* ac = *iter;\n        if((*iter).Expired || (*iter).m_Type == AT_Invalid)\n        {\n            removeFromIndex(&*iter);\n//            delete(*iter);\n            iter = m_Autocodes.erase(iter);\n#ifdef DEBUG_CLEAN_EXPIRED\n            cleanedAutos++;\n#endif\n        }\n        else\n            ++iter;\n    }\n\n    iter = m_GlobalCodes.begin();\n    end  = m_GlobalCodes.end();\n\n    while(iter != m_GlobalCodes.end())\n    {\n        //Autocode* ac = *iter;\n        if((*iter).Expired || (*iter).m_Type == AT_Invalid)\n        {\n//            delete(*iter);\n            removeFromIndexGlob(&*iter);\n            iter = m_GlobalCodes.erase(iter);\n#ifdef DEBUG_CLEAN_EXPIRED\n            cleanedGlobs++;\n#endif\n        }\n        else\n            ++iter;\n    }\n\n#ifdef DEBUG_CLEAN_EXPIRED\n    if(cleanedAutos > 0)\n        D_pLogDebug(\"Autocode: Cleaned %d expired autocodes\", cleanedAutos);\n\n    if(cleanedGlobs > 0)\n        D_pLogDebug(\"Autocode: Cleaned %d expired global autocodes\", cleanedGlobs);\n#endif\n\n    m_hasExpired = false;\n}\n\n// ACTIVATE CUSTOM EVENTS\nvoid AutocodeManager::ActivateCustomEvents(int new_section, int eventcode)\n{\n    //char* dbg = \"ACTIVATE CUSTOM DBG\";\n    if(m_Enabled)\n    {\n        auto secA = m_autocodeIdxSection.find(eventcode);\n        if(secA != m_autocodeIdxSection.end())\n        {\n            for(auto *code : secA->second)\n            {\n                // Activate copies of events with 'eventcode' and move them to 'new_section'\n                if(!code->Activated && !code->Expired)\n                {\n                    Autocode newcode = *code;\n                    newcode.Activated = true;\n                    newcode.ActiveSection = (new_section < 1000 ? (new_section - 1) : new_section);\n                    newcode.Length = code->m_OriginalTime;\n                    m_CustomCodes.push_front(std::move(newcode));\n                }\n            }\n        }\n\n        auto secG = m_globcodeIdxSection.find(eventcode);\n        if(secG != m_globcodeIdxSection.end())\n        {\n            for(auto *code : secG->second)\n            {\n                // Activate copies of events with 'eventcode' and move them to 'new_section'\n                if(!code->Activated && !code->Expired)\n                {\n                    Autocode newcode = *code;\n                    newcode.Activated = true;\n                    newcode.ActiveSection = (new_section < 1000 ? (new_section - 1) : new_section);\n                    newcode.Length = code->m_OriginalTime;\n                    m_CustomCodes.push_front(std::move(newcode));\n                }\n            }\n        }\n    }\n}\n\n// FORCE EXPIRE -- Expire all codes in section\nvoid AutocodeManager::ForceExpire(int section)\n{\n    //char* dbg = \"FORCE EXPIRE DBG\";\n    if(m_Enabled)\n    {\n        auto sec = m_autocodeIdxSection.find(section);\n        if(sec != m_autocodeIdxSection.end())\n        {\n            for(auto *code : sec->second)\n                code->expire();\n        }\n\n        auto secG = m_globcodeIdxSection.find(section);\n        if(secG != m_globcodeIdxSection.end())\n        {\n            for(auto *code : secG->second)\n                code->expire();\n        }\n    }\n}\n\n// FIND MATCHING -- Return a reference to the first autocode that matches, or 0\nAutocode *AutocodeManager::FindMatching(int section, const std::string &soughtstr)\n{\n    //char* dbg = \"FIND MATCHING DBG\";\n    // Find at m_Autocodes\n    auto s = m_autocodeIdxSection.find(section);\n    if(s == m_autocodeIdxSection.end())\n        return nullptr;\n\n    for(auto *code : s->second)\n    {\n        if(GetS(code->MyString) == soughtstr)\n            return code;\n    }\n\n    return nullptr;\n}\n\n// VAR OPERATION -- Do something to a variable in the user variable bank\nbool AutocodeManager::VarOperation(const std::string &var_name, num_t value, OPTYPE operation_to_do)\n{\n    if(var_name.length() > 0)\n    {\n        // Create var if doesn't exist\n        InitIfMissing(&gAutoMan.m_UserVars, var_name, 0);\n\n        num_t var_val = m_UserVars[var_name];\n\n        // Do the operation\n        OPTYPE oper = operation_to_do;\n        switch(oper)\n        {\n        case OP_Assign:\n            m_UserVars[var_name] = value;\n            return true;\n        case OP_Add:\n            m_UserVars[var_name] = var_val + value;\n            return true;\n        case OP_Sub:\n            m_UserVars[var_name] = var_val - value;\n            return true;\n        case OP_Mult:\n            m_UserVars[var_name] = var_val.times(value);\n            return true;\n        case OP_Div:\n            if(value == 0)\n                return false;\n            m_UserVars[var_name] = var_val.divided_by(value);\n            return true;\n        case OP_XOR:\n            m_UserVars[var_name] = (int)var_val ^ (int)value;\n            return true;\n        default:\n            return true;\n        }\n    }\n    return false;\n}\n\nvoid AutocodeManager::addToIndex(Autocode *code)\n{\n    m_autocodeIdxSection[code->ActiveSection].push_back(code);\n    if(!GetS(code->MyRef).empty())\n        m_autocodeIdxRef[GetS(code->MyRef)].push_back(code);\n}\n\nvoid AutocodeManager::removeFromIndex(Autocode *code)\n{\n    auto &sec = m_autocodeIdxSection[code->ActiveSection];\n    for(auto it = sec.begin(); it != sec.end(); )\n    {\n        if(*it == code)\n            it = sec.erase(it);\n        else\n            ++it;\n    }\n\n    if(!GetS(code->MyRef).empty())\n    {\n        auto &ref = m_autocodeIdxRef[GetS(code->MyRef)];\n        for(auto it = ref.begin(); it != ref.end(); )\n        {\n            if(*it == code)\n                it = ref.erase(it);\n            else\n                ++it;\n        }\n    }\n}\n\nvoid AutocodeManager::addToIndexGlob(Autocode *code)\n{\n    m_globcodeIdxSection[code->ActiveSection].push_back(code);\n    if(!GetS(code->MyRef).empty())\n        m_globcodeIdxRef[GetS(code->MyRef)].push_back(code);\n}\n\nvoid AutocodeManager::removeFromIndexGlob(Autocode *code)\n{\n    auto &sec = m_globcodeIdxSection[code->ActiveSection];\n    for(auto it = sec.begin(); it != sec.end(); )\n    {\n        if(*it == code)\n            it = sec.erase(it);\n        else\n            ++it;\n    }\n\n    if(!GetS(code->MyRef).empty())\n    {\n        auto &ref = m_globcodeIdxRef[GetS(code->MyRef)];\n        for(auto it = ref.begin(); it != ref.end(); )\n        {\n            if(*it == code)\n                it = ref.erase(it);\n            else\n                ++it;\n        }\n    }\n}\n\nvoid AutocodeManager::addError(int lineNumber, const std::string &line, const std::string &msg)\n{\n    ParseError err;\n    err.lineNumber = lineNumber;\n    err.line = line;\n    err.message = msg;\n\n    // Trim the tail of the line\n    while(!err.line.empty())\n    {\n        auto c = err.line.back();\n        if(c != '\\n' && c != '\\r' && c != '\\t' && c != ' ')\n            break;\n        err.line.pop_back();\n    }\n\n    // Cut the line to 80 symbols\n    if(err.line.size() > 80)\n    {\n        err.line.erase(80);\n        err.line += \"...\";\n    }\n\n    pLogWarning(\"Autocode: Script parse error: [%s] at %d: %s\",\n                err.message.c_str(), err.lineNumber, err.line.c_str());\n\n    m_errors.push_back(std::move(err));\n}\n\nvoid AutocodeManager::showErrors(const std::string &file)\n{\n    if(m_errors.empty())\n        return; // No errors\n\n    std::string out = fmt::format_ne(\"While parsing the {0} file, next errors has ocurred:\\n\\n\", file);\n\n    int lines = 0;\n    for(auto &e : m_errors)\n    {\n        out += fmt::format_ne(\"{0}, {1}:{2}\\n\", e.message, e.lineNumber, e.line);\n        lines++;\n        if(lines > 25)\n        {\n            out += \"... \";\n            break;\n        }\n    }\n\n    XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_ERROR, \"Autocode script parse errors\", out);\n}\n\n// VAR EXISTS\nbool AutocodeManager::VarExists(const std::string &var_name)\n{\n    return (m_UserVars.find(var_name) != m_UserVars.end());\n}\n\n// GET VAR\nnum_t AutocodeManager::GetVar(const std::string &var_name)\n{\n    if(!VarExists(var_name))\n        return 0;\n\n    return m_UserVars[var_name];\n}\n"
  },
  {
    "path": "src/script/luna/autocode_manager.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef AutoCodeManager_hhh\n#define AutoCodeManager_hhh\n\n#include <string>\n#include <map>\n#include <unordered_map>\n#include <list>\n#include <SDL2/SDL_rwops.h>\n\n#include \"autocode.h\"\n\n#define AUTOCODE_FNAME      \"lunadll.txt\"\n#define WORLDCODE_FNAME     \"lunaworld.txt\"\n#define GLOBALCODE_FNAME    \"lunaglobal.txt\"\n#define PARSE_FMT_STR       \" %149[^,], %lf , %lf , %lf , %lf , %lf , %999[^\\n]\"\n#define PARSE_FMT_STR_2     \" %149[^,], %i , %i , %i , %i , %i , %999[^\\n]\"\n//                       Cmd    Trg   P1    P2    P3    Len   String\n\nstruct AutocodeManager\n{\n    AutocodeManager() noexcept;\n    ~AutocodeManager();\n\n    bool LoadFiles();\n\n    // File funcs\n    bool ReadFile(const std::string &script_path, const std::string &tr_key); // Load level codes from dir_path\n    bool ReadWorld(const std::string &script_path, const std::string &tr_key); // Load worldwide codes from dir_path\n    bool ReadGlobals(const std::string &script_path); // Load global codes from dir_path\n    void Parse(SDL_RWops *open_file, bool add_to_globals, const std::string &tr_key = std::string());\n\n    static std::string resolveWorldFileCase(const std::string &in_name);\n    static std::string resolveCustomFileCase(const std::string &in_name);\n\n    // Management funcs\n    void Clear();\n    void ForceExpire(int section);\n    void ClearExpired();\n    void DeleteEvent(const std::string &event_reference_name);     // Look up event with given name and expire it\n    void DoEvents(bool init);\n    void ActivateCustomEvents(int new_section, int eventcode);\n    Autocode *GetEventByRef(const std::string &event_reference_name);   // Return ptr to event with the given ref, or NULL if it fails\n    Autocode *FindMatching(int section, const std::string &string);\n\n    // Variable bank funcs\n    num_t GetVar(const std::string &var_name);        // returns 0 if var doesn't exist in bank\n    bool VarExists(const std::string &var_name);\n    bool VarOperation(const std::string &var_name, num_t value, OPTYPE operation_to_do);\n\n    // Members\n    bool                    m_Enabled = false;          // Whether or not individual level scripts enabled\n    bool                    m_GlobalEnabled = false;    // Whether or not global game scripts enabled\n    std::list<Autocode>     m_Autocodes;\n    std::list<Autocode>     m_InitAutocodes;\n    std::list<Autocode>     m_CustomCodes;\n    std::list<Autocode>     m_GlobalCodes;\n\n    //! When any event gets expired, this field gets set to proceed the cleanup of expired events\n    bool                    m_hasExpired = false;\n\n    //! Index table to find autocodes by section\n    std::unordered_map<int, std::list<Autocode*>>          m_autocodeIdxSection;\n    //! Index table to find autocodes by referrence\n    std::unordered_map<std::string, std::list<Autocode*>>  m_autocodeIdxRef;\n\n    //! Index table to find global autocodes by section\n    std::unordered_map<int, std::list<Autocode*>>          m_globcodeIdxSection;\n    //! Index table to find global autocodes by reference\n    std::unordered_map<std::string, std::list<Autocode*>>  m_globcodeIdxRef;\n\n    void addToIndex(Autocode *code);\n    void removeFromIndex(Autocode *code);\n\n    void addToIndexGlob(Autocode *code);\n    void removeFromIndexGlob(Autocode *code);\n\n    struct ParseError\n    {\n        int lineNumber = -1;\n        std::string line;\n        std::string message;\n    };\n\n    std::list<ParseError> m_errors;\n    void addError(int lineNumber, const std::string &line, const std::string &msg);\n    void showErrors(const std::string &file);\n\n    std::map<std::string, num_t> m_UserVars;\n\n    // Hearts manager stuff\n    int m_Hearts = 2;\n};\n\nextern AutocodeManager gAutoMan;\n\n#endif // AutoCodeManager_hhh\n"
  },
  {
    "path": "src/script/luna/csprite.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"csprite.h\"\n#include \"sprite_funcs.h\"\n#include \"renderop_rect.h\"\n#include \"lunaimgbox.h\"\n\n\n// CTOR\nCSprite::CSprite()\n{\n    Init();\n}\n\n// INIT\nvoid CSprite::Init()\n{\n    m_ImgResCode = -1;\n    m_CollisionCode = -1;\n    m_StaticScreenPos = false;\n    m_Visible = true;\n    m_Birthed = false;\n    m_Died = false;\n    m_Invalidated = false;\n    m_LimitedFrameLife = false;\n    m_AnimationSet = false;\n    m_AlwaysProcess = false;\n    m_FramesLeft = 0;\n    m_DrawPriorityLevel = 1;\n    m_OffscreenCount = 0;\n    m_FrameCounter = 0;\n    m_GfxXOffset = 0;\n    m_GfxYOffset = 0;\n    m_Xpos = 0;\n    m_Ypos = 0;\n    m_Ht = 0;\n    m_Wd = 0;\n    m_Xspd = 0;\n    m_Yspd = 0;\n    m_AnimationPhase = 0;\n    m_AnimationFrame = 0;\n    m_AnimationTimer = 0;\n    m_Hitbox.pParent = this;\n}\n\n// CLEAR EXPIRED COMPONENTS\nvoid CSprite::ClearExpiredComponents()\n{\n    std::list<SpriteComponent>::iterator iter = m_BehavComponents.begin();\n    // std::list<SpriteComponent>::iterator end  = m_BehavComponents.end();\n\n    while(iter != m_BehavComponents.end())\n    {\n        SpriteComponent comp = *iter;\n        if((*iter).expired)\n            iter = m_BehavComponents.erase(iter);\n        else\n            ++iter;\n    }\n}\n\n//#ifndef __MINGW32__\n//#pragma region Add functions\n//#endif\n\n// ADD BIRTH -- Add a birth function to sprite\nvoid CSprite::AddBirthComponent(SpriteComponent comp)\n{\n    if(comp.func != nullptr)\n        m_BirthComponents.push_back(comp);\n}\n\n// ADD BEHAVIOR -- Add a behavior component to sprite\nvoid CSprite::AddBehaviorComponent(SpriteComponent comp)\n{\n    if(comp.func != nullptr)\n        m_BehavComponents.push_back(comp);\n}\n\n// ADD DRAW -- Add a draw component to sprite. Updates m_StaticScreenPos if static drawing detected.\nvoid CSprite::AddDrawComponent(pfnSprDraw func)\n{\n    if(func != nullptr)\n        m_DrawFuncs.push_back(func);\n    if(func == SpriteFunc::StaticDraw)\n        m_StaticScreenPos = true;\n}\n\n// ADD DEATH -- Add a death function to sprite\nvoid CSprite::AddDeathComponent(SpriteComponent comp)\n{\n    if(comp.func != nullptr)\n        m_DeathComponents.push_back(comp);\n}\n\n//#ifndef __MINGW32__\n//#pragma endregion\n//#endif\n\n// SET IMAGE RESOURCE\nvoid CSprite::SetImageResource(int _resource_code)\n{\n    m_ImgResCode = _resource_code;\n}\nvoid CSprite::SetImage(LunaImage *in_img)\n{\n    m_directImg = in_img;\n}\n\n// MAKE LIMITED LIFETIME\nvoid CSprite::MakeLimitedLifetime(int new_lifetime)\n{\n    this->m_LimitedFrameLife = true;\n    this->m_FramesLeft = new_lifetime;\n}\n\n// BIRTH -- Run the birth components\nvoid CSprite::Birth()\n{\n    if(!m_Birthed)\n    {\n        for(std::list<SpriteComponent>::iterator iter = m_BirthComponents.begin(); iter != m_BirthComponents.end(); ++iter)\n            (*iter).func(this, &(*iter));\n    }\n    m_Birthed = true;\n}\n\n// PROCESS -- Process 1 frame of lifetime. Call all of the running behavior components\nvoid CSprite::Process()\n{\n    ClearExpiredComponents();\n\n    if(!m_Birthed)\n        Birth();\n\n    m_FrameCounter++;\n\n    for(std::list<SpriteComponent>::iterator iter = m_BehavComponents.begin(); iter != m_BehavComponents.end(); ++iter)\n    {\n        (*iter).func(this, &(*iter));\n        (*iter).Tick();\n    }\n\n    // Die?\n    if(this->m_LimitedFrameLife)\n    {\n        this->m_FramesLeft--;\n        if(m_FramesLeft <= 0 && !m_Died)\n            Die();\n    }\n}\n\n// DRAW -- Call all of the registered draw components\nvoid CSprite::Draw()\n{\n    for(auto &iter  : m_DrawFuncs)\n        iter(this);\n\n#if 0   //debug\n    {\n        RenderRectOp op;\n        op.x1 = m_Xpos;\n        op.y1 = m_Ypos;\n        op.x2 = m_Xpos + m_Wd;\n        op.y2 = m_Xpos + m_Ht;\n        op.m_FramesLeft = 1;\n        op.Draw(&Renderer::Get());\n    }\n#endif\n}\n\n// DIE -- Run the death components\nvoid CSprite::Die()\n{\n    for(auto &iter : m_DeathComponents)\n        iter.func(this, &iter);\n\n    m_Died = true;\n    m_Invalidated = true;\n}\n\n// SET CUSTOM VAR\nvoid CSprite::SetCustomVar(std::string var_name, OPTYPE operation_to_do, num_t value)\n{\n    if(var_name.length() > 0)\n    {\n        // Create var if doesn't exist\n        if(m_CustomVars.find(var_name) == m_CustomVars.end())\n            m_CustomVars[var_name] = 0;\n\n        num_t var_val = m_CustomVars[var_name];\n\n        // Do the operation\n        OPTYPE oper = operation_to_do;\n        switch(oper)\n        {\n        case OP_Assign:\n            m_CustomVars[var_name] = value;\n            break;\n        case OP_Add:\n            m_CustomVars[var_name] = var_val + value;\n            break;\n        case OP_Sub:\n            m_CustomVars[var_name] = var_val - value;\n            break;\n        case OP_Mult:\n            m_CustomVars[var_name] = var_val.times(value);\n            break;\n        case OP_Div:\n            if(value == 0)\n                break;\n            m_CustomVars[var_name] = var_val.divided_by(value);\n            break;\n        case OP_XOR:\n            m_CustomVars[var_name] = (int)var_val ^ (int)value;\n            break;\n        default:\n            break;\n        }\n    }\n}\n\n// CUSTOM VAR EXISTS\nbool CSprite::CustomVarExists(std::string var_name)\n{\n    return m_CustomVars.find(var_name) == m_CustomVars.end() ? false : true;\n}\n\n// GET CUSTOM VAR\nnum_t CSprite::GetCustomVar(std::string var_name)\n{\n    if(m_CustomVars.find(var_name) == m_CustomVars.end())\n        return 0;\n    return m_CustomVars[var_name];\n}\n"
  },
  {
    "path": "src/script/luna/csprite.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CSPRITE_HHHHH\n#define CSPRITE_HHHHH\n\n#include <list>\n#include <map>\n#include <vector>\n\n#include \"lunadefs.h\"\n#include \"hitbox.h\"\n#include \"sprite_component.h\"\n\n/// CSprite builtin custom variables\n#define CVAR_GEN_ANGLE \"_GenAngle\"\n\nclass LunaImage;\nclass CSprite;\nstruct CSpriteRequest;\nstruct SpriteComponent;\n\ntypedef void (*pfnSprFunc)(CSprite*, SpriteComponent* obj);\ntypedef void (*pfnSprDraw)(CSprite*);\n\n// General custom sprite class\nclass CSprite\n{\npublic:\n\n    /// Functions ///\n    CSprite();\n    void Init();\n\n    void ClearExpiredComponents();\n\n    void AddBirthComponent(SpriteComponent component);\n    void AddBehaviorComponent(SpriteComponent component);\n    void AddDrawComponent(pfnSprDraw component);\n    void AddDeathComponent(SpriteComponent component);\n\n    void SetImageResource(int _resource_code);\n    void SetImage(LunaImage *in_img);\n    void MakeLimitedLifetime(int new_lifetime);\n\n    void Birth();\n    void Process();\n    void Draw();\n    void Die();\n\n    void SetCustomVar(std::string var_name, OPTYPE operation, num_t val);\n    bool CustomVarExists(std::string var_name);\n    num_t GetCustomVar(std::string var_name);\n\n    /// Members///\n    //! Image bank code of image resource the sprite uses\n    int m_ImgResCode = 0;\n    LunaImage *m_directImg = nullptr;\n    //! Collision code for collision blueprint bank (-1 == all blocks collide as solid)\n    int m_CollisionCode = 0;\n\n    //! How many frames are left if dying automatically\n    int m_FramesLeft = 0;\n    //! 0 = Low  1 = Mid  2 = High (drawn in front)\n    int m_DrawPriorityLevel = 0;\n    //! How many frames this sprite has been offscreen\n    int m_OffscreenCount = 0;\n    //! Incremented each Process()\n    int m_FrameCounter = 0;\n    int m_GfxXOffset = 0;\n    int m_GfxYOffset = 0;\n    //! Whether or not sprite uses absolute screen coords for drawing\n    bool m_StaticScreenPos = false;\n    //! Whether or not this sprite should be drawn\n    bool m_Visible = true;\n    //! Whether or not sprite's birth funcs have been run yet\n    bool m_Birthed = false;\n    //! Whether or not sprite's death funcs have been run yet\n    bool m_Died = false;\n    //! Whether or not this sprite will be removed during the next cleanup phase\n    bool m_Invalidated = false;\n    //! Whether or not the sprite dies automatically after a number of frames\n    bool m_LimitedFrameLife = false;\n    //! Whether or not the animation parameters have been set for this sprite yet\n    bool m_AnimationSet = false;\n    //! Whether or not this sprite should be processed regardless of player's current section\n    bool m_AlwaysProcess = false;\n\n    num_t m_Xpos;\n    num_t m_Ypos;\n    //! Height Of loaded image graphic for backwards compat with native SMBX sprites\n    int m_Ht;\n    //! Width Of loaded image graphic for backwards compat with native SMBX sprites\n    int m_Wd;\n    num_t m_Xspd;\n    num_t m_Yspd;\n\n    //! Hitbox relative to sprite position\n    Hitbox m_Hitbox;\n\n    //! Time per animation\n    int m_AnimationPhase;\n    //! Current timer for animation (animate when reaching 0)\n    int m_AnimationTimer;\n    //! The current frame in m_GfxRects to be drawn (0 == first frame)\n    int m_AnimationFrame;\n    //! Spritesheet areas to draw, indexed by animation frame\n    std::vector<LunaRect> m_GfxRects;\n\n    std::list<SpriteComponent> m_BirthComponents;\n    //! Currently loaded behavioral components\n    std::list<SpriteComponent> m_BehavComponents;\n    std::list<pfnSprDraw> m_DrawFuncs;\n    std::list<SpriteComponent> m_DeathComponents;\n\n    //! User-defined vars\n    std::map<std::string, num_t> m_CustomVars;\n};\n\n\n// Obj for interfacing with sprite factory\nstruct CSpriteRequest\n{\n    CSpriteRequest() : type(0), x(0), y(0), time(0), img_resource_code(0), x_speed(0), y_speed(0), spawned(false) {}\n    int type = 0;\n    int x = 0;\n    int y = 0;\n    int time = 0;\n    int img_resource_code = 0;\n    LunaImage *direct_img = nullptr;\n    std::string str;\n\n    // Optional parameters\n    num_t x_speed = 0;\n    num_t y_speed = 0;\n    //! \"spawned\" means the sprite will be deleted after being offscreen for some time\n    bool spawned = false;\n};\n\n#endif // CSPRITE_HHHHH\n"
  },
  {
    "path": "src/script/luna/hitbox.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"hitbox.h\"\n#include \"csprite.h\"\n\n// TEST -- fast rectangle\nbool Hitbox::Test(int Left2, int Up2, int iW, int iH) const\n{\n    if(pParent)\n    {\n        if(this->CollisionType == 0)   // square aabb collision detection\n        {\n            // bool rightcol = true;\n            // bool leftcol = true;\n            // bool upcol = true;\n            // bool downcol = true;\n\n            if(CalcRight() < Left2)\n                return false;\n            if(CalcLeft() > Left2 + iW)\n                return false;\n            if(CalcTop() > Up2 + iH)\n                return false;\n            if(CalcBottom() < Up2)\n                return false;\n\n            // This condition is ALWAYS FALSE\n            //if(!rightcol || !leftcol || !upcol || !downcol)\n            //    return false;\n            return true;\n        }\n        else   // circle vs aaab -> convert to circle\n        {\n            int hW = iW / 2;\n            int hH = iH / 2;\n            int halfwidth = hW;\n            int halfheight = hH;\n            return Test((int)(Left2 + halfwidth), (int)(Up2 + halfheight), (int)(halfwidth <= halfheight ? halfwidth : halfheight));\n        }\n    }\n\n    return false;\n}\n\n// TEST -- fast circle/distance\nbool Hitbox::Test(int cx, int cy, int radius) const\n{\n    int radi_total = radius + (W <= H ? W / 2 : H / 2); // Other obj radius + my radius\n    radi_total *= radi_total;\n    int x_dist = (int)CenterX() - cx;\n    int y_dist = (int)CenterY() - cy;\n    int sqr_dist = x_dist * x_dist + y_dist * y_dist;\n\n    if(sqr_dist > radi_total)\n        return false;\n\n    return true;\n}\n\nnum_t Hitbox::CalcLeft() const\n{\n    if(pParent)\n        return pParent->m_Xpos + Left_off;\n    return 0;\n}\n\nnum_t Hitbox::CalcTop() const\n{\n    if(pParent)\n        return pParent->m_Ypos + Top_off;\n    return 0;\n}\n\nnum_t Hitbox::CalcBottom() const\n{\n    if(pParent)\n        return pParent->m_Ypos + H;\n    return 0;\n}\n\nnum_t Hitbox::CalcRight() const\n{\n    if(pParent)\n        return pParent->m_Xpos + W;\n    return 0;\n}\n\nnum_t Hitbox::CenterX() const\n{\n    return CalcLeft() + W * 0.5_n;\n}\n\nnum_t Hitbox::CenterY() const\n{\n    return CalcTop() + H * 0.5_n;\n}\n"
  },
  {
    "path": "src/script/luna/hitbox.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef HitBox_HHHHH\n#define HitBox_HHHHH\n\n#include <cstddef>\n#include \"numeric_types.h\"\n\nclass CSprite;\n\nenum COLLISION_TYPE\n{\n    COLTYPE_NONE = 0,\n    COLTYPE_LEFT,\n    COLTYPE_RIGHT,\n    COLTYPE_TOP,\n    COLTYPE_BOT,\n};\n\nstruct Hitbox\n{\n    Hitbox() = default;\n\n    num_t CalcLeft() const;      // Get the left absolute position (with parent base coords)\n    num_t CalcRight() const;     // Get the right absolute position (with parent base coords)\n    num_t CalcTop() const;       // Get the top absolute position (with parent base coords)\n    num_t CalcBottom() const;    // Get the bottom absolute position (with parent base coords)\n    num_t CenterX() const;       // Get the center X position (from parent base coords)\n    num_t CenterY() const;       // Get the center Y position (from parent base coords)\n\n    bool Test(int left, int up, int iW, int iH) const; // Test hitbox against given rect\n    bool Test(int cx, int cy, int radius) const;              // Test hitbox against given circle\n\n    short Left_off = 0;         // Offset from 0,0 on sprite\n    short Top_off = 0;          // Offset from 0,0 on sprite\n    short W = 0;                // Width\n    short H = 0;                // Height\n\n    //COLLISION_TYPE GetCollisionDir(int left, int up, int right, int down); // Get the direction this hitbox is colliding with given rect\n    char CollisionType = 0;     // 0 = square aabb  1 = circle/dist\n    CSprite *pParent = nullptr;   // Pointer to parent sprite (for calculating actual coordinates + collision area)\n\n};\n\n#endif // HitBox_HHHHH\n"
  },
  {
    "path": "src/script/luna/levels/Docopoper-AbstractAssault.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*************************************************\n *  Episode:    A2MBXT, Episode 1: Analog Funk   *\n *  Level:      Abstract Assault                 *\n *  Filename:   Docopoper-AbstractAssault.lvl    *\n *  Author:     Docopoper                        *\n *************************************************/\n\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"globals.h\"\n#include \"layers.h\"\n#include \"../lunaplayer.h\"\n#include \"../lunalayer.h\"\n#include \"Docopoper-AbstractAssault.h\"\n\n\n#define X_MAX(a, b) (((a) > (b)) ? (a) : (b))\n#define X_MIN(a, b) (((a) < (b)) ? (a) : (b))\n#define X_CLAMP(x, l, h) (((x) > (h)) ? (h) : (((x) < (l)) ? (l) : (x)))\n\n\nvoid AbstractAssaultCode()\n{\n    Player_t *demo = PlayerF::Get(1);\n    Layer_t *layerDefault = LayerF::Get(0);\n    Layer_t *layerStartingPlatform = LayerF::Get(3);\n\n    SDL_assert_release(demo);\n    SDL_assert_release(layerDefault);\n    SDL_assert_release(layerStartingPlatform);\n\n    static num_t hspeed = 0, vspeed = 0;\n    static char gameStarted = 0;\n    static unsigned short noControlTimer = 0;\n\n    bool press_up = demo->Controls.Up;\n    bool press_left = demo->Controls.Left;\n    bool press_down = demo->Controls.Down;\n    bool press_right = demo->Controls.Right;\n\n    auto &powerup = demo->State;\n\n    if(layerStartingPlatform -> SpeedY == 0)\n    {\n        demo -> Character = 1; //Demo\n        //player_id_set = 1;\n        gameStarted = 0;\n        powerup = 1;\n    }\n    else if(!gameStarted)\n    {\n        powerup = 6;\n        demo -> SpinJump = false;\n        demo -> Hearts = 3;\n        vspeed = -16;\n        hspeed = -4;\n        gameStarted = 1;\n        noControlTimer = 30;\n        demo->Character = 5; //Sheath\n    }\n\n    if(gameStarted)\n    {\n        if(layerStartingPlatform -> SpeedY == 0)\n            gameStarted = 0;\n\n        demo -> SpinJump = false;\n\n        layerDefault -> SpeedX = (numf_t) X_MAX((num_t)layerDefault -> SpeedX - 0.015_n, -2.5_n);\n\n        if(demo -> Hearts > 1)\n            powerup = 6;\n\n        demo -> Direction = 1;\n        demo -> Multiplier %= 9;\n\n        if(noControlTimer == 0)\n        {\n            vspeed = X_CLAMP(vspeed + (press_down - press_up) * 0.5_n, -10, 10);\n            hspeed = X_CLAMP(hspeed + (press_right - press_left) * 0.5_n, -10, 10);\n        }\n        else\n            noControlTimer--;\n\n        demo->Location.SpeedY = -0.4_n + vspeed;\n        demo->Location.SpeedX = hspeed;\n\n        hspeed *= 0.9_r;\n        vspeed *= 0.9_r;\n    }\n}\n"
  },
  {
    "path": "src/script/luna/levels/Docopoper-AbstractAssault.h",
    "content": "extern void AbstractAssaultCode();"
  },
  {
    "path": "src/script/luna/levels/Docopoper-Calleoca.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*************************************************\n *  Episode:    A2MBXT, Episode 1: Analog Funk   *\n *  Level:      Custom C++ Code, Example #3      *\n *  Filename:   Docopoper-Calleoca.lvl           *\n *  Author:     Docopoper                        *\n *************************************************/\n\n#include <cstdlib>\n#include <cmath>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"Docopoper-Calleoca.h\"\n#include \"globals.h\"\n#include \"main/trees.h\"\n#include \"../lunaplayer.h\"\n#include \"../lunanpc.h\"\n\n\n#define PI 3.14159_r\n#define PI_DIV 3.14159_ri\n\n#define NPCID_SIGN 151\n#define NPCID_COIN 10\n#define NPCID_FIRE_CHAIN 260\n#define NPCID_GOAL 16\n\n#define OFFSCREEN_DIST 400\n#define MIDPOINT_X -194620\n#define END_X -188792\n\n\nNPC_t* FindNPC(vbint_t identity);\nvoid HurtPlayer();\nbool TriggerBox(num_t x1, num_t y1, num_t x2, num_t y2);\nint AngleDifference(int angle1, int angle2);\n\nstatic bool init_doonce = false;\n\nstatic Player_t* demo = nullptr;\n\nstatic NPC_t* calleoca_npc1 = nullptr;\nstatic NPC_t* calleoca_npc2 = nullptr;\nstatic NPC_t* hurt_npc = nullptr;\nstatic NPC_t* goal_npc = nullptr;\n\nstatic int win_timer = 0;\nstatic int freeze_timer = 0;\nstatic int phase = 0;\nstatic num_t calleoca_x = 0, calleoca_y = 0, storage_x = 0, storage_y = 0;\n\nstatic num_t thwomp_hspeed = 0;\nstatic num_t thwomp_vspeed = 0;\nstatic num_t thwomp_height = 0, thwomp_bottom = 0;\n\nstatic num_t missile_direction = 0;\nstatic num_t missile_hspeed = 0;\nstatic num_t missile_vspeed = 0;\nstatic num_t missile_top = 0, missile_bottom = 0;\nstatic int missile_fuel = 0;\n\nstatic num_t fishingboo_hspeed = 0;\nstatic num_t fishingboo_vspeed = 0;\nstatic num_t fishingboo_ferocity = 1;\n\ntemplate <typename T> T Clamp(const T& value, const T& low, const T& high)\n{\n  return value < low ? low : (value > high ? high : value);\n}\n\nvoid CalleocaInitCode()\n{\n    init_doonce = false;\n\n    phase = 0;\n\n    win_timer = 100;\n\n    thwomp_hspeed = 0;\n    thwomp_vspeed = 0;\n\n    freeze_timer = 0;\n\n    missile_direction = 0;\n    missile_hspeed = 0;\n    missile_vspeed = 0;\n\n    fishingboo_hspeed = 0;\n    fishingboo_vspeed = 0;\n    fishingboo_ferocity = 1;\n}\n\nvoid Phase0()\n{\n    calleoca_npc1->Frame = 0;\n\n    if(demo->Location.X > calleoca_x + 128)\n    {\n        freeze_timer = 45;\n        phase = 1;\n    }\n\n    if(demo->Location.X >= MIDPOINT_X)\n    {\n        phase = 3;\n        calleoca_x = MIDPOINT_X - 256;\n        calleoca_y -= 256;\n    }\n}\n\n/*************************************************************************************************/\n//Standing there\nvoid Phase1()\n{\n    thwomp_hspeed += (demo->Location.X - calleoca_x) / 1000;\n    if(num_t::abs(thwomp_height - calleoca_y) < 8)\n    {\n        thwomp_hspeed = Clamp<num_t>(thwomp_hspeed, -9, 9);\n\n        if (TriggerBox(calleoca_x, calleoca_y + 64, calleoca_x + 64, calleoca_y + 512))\n            phase = 2;\n    }\n    else\n        thwomp_hspeed = 0;\n\n    thwomp_vspeed = (thwomp_height - calleoca_y) / 10;\n    thwomp_vspeed = Clamp<num_t>(thwomp_vspeed, -6, 6);\n\n    if(freeze_timer > 0)\n    {\n        thwomp_vspeed = 0;\n        freeze_timer--;\n    }\n\n    calleoca_x += thwomp_hspeed;\n    calleoca_y += thwomp_vspeed;\n\n    calleoca_npc1->Frame = 2;\n    if(TriggerBox(calleoca_x + 10, calleoca_y + 0, calleoca_x + 54, calleoca_y + 64))\n        HurtPlayer();\n\n    if(demo->Location.X >= MIDPOINT_X)\n    {\n        freeze_timer = 150;\n        phase = 3;\n    }\n}\n\n/*************************************************************************************************/\n//Thwomp rising / moving\nvoid Phase2()\n{\n    thwomp_hspeed *= 0.9_r;\n    if(demo->Location.Y <= calleoca_y)\n        thwomp_hspeed *= 0.9_r;\n\n    thwomp_vspeed += (thwomp_bottom - calleoca_y) / 20;\n    thwomp_vspeed = Clamp<num_t>(thwomp_vspeed, -8, 8);\n\n    calleoca_x += thwomp_hspeed;\n    calleoca_y += thwomp_vspeed;\n    calleoca_npc1->Frame = 1;\n    if(TriggerBox(calleoca_x + 10, calleoca_y + 0, calleoca_x + 54, calleoca_y + 64))\n        HurtPlayer();\n\n    if(calleoca_y >= thwomp_bottom)\n    {\n        freeze_timer = 22;\n        phase = 1;\n    }\n\n    if(demo->Location.X >= MIDPOINT_X)\n    {\n        freeze_timer = 150;\n        phase = 3;\n    }\n}\n\n/*************************************************************************************************/\n//Missile\nvoid Phase3()\n{\n    calleoca_npc1->Frame = 3 + num_t::round(missile_direction / 45) % 8;\n\n    int dir = (int)(num_t::atan2((demo->Location.Y + demo->Location.Height / 2) - (calleoca_y + 32),\n                              -(demo->Location.X + demo->Location.Width / 2) + (calleoca_x + 32))\n                                * 180 / PI_DIV);\n\n    missile_direction += AngleDifference((int)missile_direction, dir) * 0.015_n;\n\n    if(missile_direction >= 360)\n        missile_direction -= 360;\n    else if(missile_direction < 0)\n        missile_direction += 360;\n\n    missile_hspeed += num_t::cos(missile_direction * PI / 180) / 4;\n    missile_vspeed -= num_t::sin(missile_direction * PI / 180) / 4;\n\n    missile_hspeed = Clamp<num_t>(missile_hspeed * 0.975_r, -15, 15);\n    missile_vspeed = Clamp<num_t>(missile_vspeed * 0.975_r, -15, 15);\n\n    if(freeze_timer > 0)\n    {\n        freeze_timer--;\n        missile_hspeed = 0;\n        missile_vspeed = 0;\n        missile_fuel = 600;\n    }\n    else\n    {\n        missile_fuel--;\n        if(missile_fuel <= 0 && !TriggerBox(calleoca_x - 100, calleoca_y - 100, calleoca_x + 164, calleoca_y + 164))\n            freeze_timer = 110;\n    }\n\n\n    calleoca_x = Clamp<num_t>(calleoca_x + missile_hspeed, demo->Location.X - 464, demo->Location.X + 464);\n    calleoca_y = Clamp<num_t>(calleoca_y + missile_vspeed, missile_top, missile_bottom);\n\n    if(TriggerBox(calleoca_x + 18, calleoca_y + 18, calleoca_x + 46, calleoca_y + 46))\n        HurtPlayer();\n\n    if(demo->Location.X >= END_X)\n    {\n        if (calleoca_x > END_X - 64)\n            calleoca_x = END_X - 64;\n        freeze_timer = 150;\n        phase = 4;\n    }\n}\n\nvoid Phase4()\n{\n    if(freeze_timer > 0)\n    {\n        freeze_timer--;\n    }\n    else\n    {\n        fishingboo_hspeed += (demo->Location.X - calleoca_x).times(fishingboo_ferocity) / 1000;\n        fishingboo_vspeed += (demo->Location.Y - calleoca_y).times(fishingboo_ferocity) / 1000;\n        fishingboo_hspeed = Clamp<num_t>(fishingboo_hspeed, -7 * fishingboo_ferocity, 7 * fishingboo_ferocity);\n        fishingboo_vspeed = Clamp<num_t>(fishingboo_vspeed, -0.5_rb * fishingboo_ferocity, 0.5_rb * fishingboo_ferocity);\n\n        calleoca_x += fishingboo_hspeed;\n        calleoca_y += fishingboo_vspeed;\n\n        fishingboo_ferocity += 0.0002_n;\n    }\n\n    calleoca_x = Clamp<num_t>(calleoca_x, demo->Location.X - 512, demo->Location.X + 464);\n}\n\nvoid CalleocaCode()\n{\n    demo = PlayerF::Get(1);\n\n    demo->Character = 1;\n\n    if(calleoca_npc2 == nullptr && init_doonce)\n    {\n        if(win_timer > 0)\n            win_timer--;\n        else\n        {\n            goal_npc = FindNPC(NPCID_GOAL);\n            if(goal_npc != nullptr)\n            {\n                goal_npc->Location.X = demo->Location.X;\n                goal_npc->Location.Y = demo->Location.Y;\n                treeNPCUpdate(goal_npc);\n            }\n        }\n\n        return; //boss beaten\n    }\n\n    if(!init_doonce)\n    {\n        calleoca_npc1 = FindNPC(NPCID_SIGN);\n        calleoca_npc2 = FindNPC(NPCID_COIN);\n        hurt_npc\t  = FindNPC(NPCID_FIRE_CHAIN);\n\n        calleoca_x = calleoca_npc1->Location.X;\n        calleoca_y = calleoca_npc1->Location.Y;\n        storage_x  = calleoca_npc2->Location.X;\n        storage_y  = calleoca_npc2->Location.Y;\n\n        thwomp_height = calleoca_y - 64 * 6 + 16;\n        thwomp_bottom = calleoca_y + 128;\n\n        missile_top = calleoca_y - 64 * 7 - 16;\n        missile_bottom = calleoca_y + 180;\n\n        init_doonce = true;\n    }\n\n    if(calleoca_npc1->Type != NPCID_SIGN)\n        calleoca_npc1 = FindNPC(NPCID_SIGN);\n\n    if(calleoca_npc2->Type != NPCID_COIN)\n        calleoca_npc2 = FindNPC(NPCID_COIN);\n\n    if(hurt_npc->Type != NPCID_FIRE_CHAIN)\n        hurt_npc = FindNPC(NPCID_FIRE_CHAIN);\n\n    hurt_npc->Location.X = demo->Location.X;\n    hurt_npc->Location.Y = demo->Location.Y - 128;\n    treeNPCUpdate(hurt_npc);\n\n    switch (phase)\n    {\n    case 0: //Standing there\n        Phase0();\n        break;\n\n    case 1: //Thwomp rising\n        Phase1();\n        break;\n\n    case 2: //Thwomp attacking\n        Phase2();\n        break;\n\n    case 3: //Missile\n        Phase3();\n        break;\n\n    case 4: //Fishing Boo\n        Phase4();\n        break;\n\n    default:\n        break;\n    }\n\n    if(!calleoca_npc2)\n        return; //boss beaten\n\n    if(phase < 4)\n    {\n        calleoca_npc1->Location.X = calleoca_x;\n        calleoca_npc1->Location.Y = calleoca_y;\n        calleoca_npc2->Location.X = storage_x;\n        calleoca_npc2->Location.Y = storage_y;\n    }\n    else\n    {\n        calleoca_npc2->Location.X = calleoca_x;\n        calleoca_npc2->Location.Y = calleoca_y;\n        calleoca_npc1->Location.X = storage_x;\n        calleoca_npc1->Location.Y = storage_y;\n    }\n\n    treeNPCUpdate(calleoca_npc1);\n    treeNPCUpdate(calleoca_npc2);\n\n    //Renderer::Get().SafePrint(std::wstring(L\"FUEL: \" + std::to_wstring(missile_fuel)), 3, 0, 256);\n    //Renderer::Get().SafePrint(std::wstring(L\"DEMO X: \" + std::to_wstring(demo->CurYPos)), 3, 0, 256 + 32);\n}\n\nNPC_t* FindNPC(vbint_t identity)\n{\n    NPC_t* currentnpc = nullptr;\n\n    for(int i = 0; i < numNPCs; i++)\n    {\n        currentnpc = NpcF::Get(i);\n        if(currentnpc && currentnpc->Type == identity)\n            return currentnpc;\n    }\n\n    return nullptr;\n}\n\nbool TriggerBox(num_t x1, num_t y1, num_t x2, num_t y2)\n{\n    return (demo->Location.X + demo->Location.Width     > x1 &&\n            demo->Location.X                            < x2 &&\n            demo->Location.Y + demo->Location.Height    > y1 &&\n            demo->Location.Y                            < y2);\n}\n\nvoid HurtPlayer()\n{\n    hurt_npc->Location.Y = demo->Location.Y;\n}\n\nint AngleDifference(int angle1, int angle2)\n{\n    return ((((angle1 - angle2) % 360) + 540) % 360) - 180;\n}\n"
  },
  {
    "path": "src/script/luna/levels/Docopoper-Calleoca.h",
    "content": "extern void CalleocaInitCode();\nextern void CalleocaCode();\n"
  },
  {
    "path": "src/script/luna/levels/Docopoper-TheFloorisLava.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*************************************************\n *  Episode:    A2MBXT, Episode 1: Analog Funk   *\n *  Level:      The Floor is Lava!               *\n *  Filename:   Docopoper-TheFloorisLava.lvl     *\n *  Author:     Docopoper                        *\n *************************************************/\n\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"globals.h\"\n#include \"../lunaplayer.h\"\n#include \"../lunalayer.h\"\n#include \"layers.h\"\n#include \"Docopoper-TheFloorisLava.h\"\n\n\nvoid TheFloorisLavaInit()\n{\n    Player_t* demo = PlayerF::Get(1);\n    if(demo)\n        demo->Character = 1;\n}\n\nvoid TheFloorisLavaCode()\n{\n    Player_t *demo = PlayerF::Get(1);\n    Layer_t *layerSecretExit = LayerF::Get(3);\n    Layer_t *layerSinUpDown = LayerF::Get(4);\n    Layer_t *layerSinRightLeft = LayerF::Get(5);\n    Layer_t *layerSinUpDownAlternate = LayerF::Get(6);\n    Layer_t *layerSinUpDownWeak = LayerF::Get(7);\n    Layer_t *layerSinUpDownWeakAlt = LayerF::Get(8);\n    Layer_t *layerSinRightLeftAlternate = LayerF::Get(9);\n\n    static num_t layerSinUpDown_displacement = 0;\n    static num_t layerSinRightLeft_displacement = 0;\n    static char secretExitFailedStage = 0;\n\n    if(!demo)\n        return;\n\n    SDL_assert_release(layerSecretExit);\n    SDL_assert_release(layerSinUpDown);\n    SDL_assert_release(layerSinRightLeft);\n    SDL_assert_release(layerSinUpDownAlternate);\n    SDL_assert_release(layerSinUpDownWeak);\n    SDL_assert_release(layerSinUpDownWeakAlt);\n    SDL_assert_release(layerSinRightLeftAlternate);\n\n    // The player has touched the ground, trigger the fail sequence\n    if(demo->Slippy && secretExitFailedStage == 0)\n    {\n        secretExitFailedStage = 1;\n        layerSecretExit -> SpeedY = -0.4_nf;\n    }\n\n    //different movements to create the dropping off the world effect\n    switch(secretExitFailedStage)\n    {\n    case 0:\n        layerSecretExit -> SpeedY = 0;\n        break;\n\n    case 1:\n        layerSecretExit -> SpeedY -= 0.2_nf;\n\n        if(layerSecretExit -> SpeedY < -4)\n            secretExitFailedStage = 2;\n        break;\n\n    case 2:\n        layerSecretExit -> SpeedY += 0.4_nf;\n\n        if(layerSecretExit -> SpeedY > -2.5_nf)\n            secretExitFailedStage = 3;\n        break;\n\n    case 3:\n        layerSecretExit -> SpeedY += 1.5_nf;\n\n        if(layerSecretExit -> SpeedY > 50)\n            secretExitFailedStage = 4;\n        break;\n\n    case 4:\n        layerSecretExit -> SpeedY = 0;\n        secretExitFailedStage = 5;\n        break;\n\n    default:\n        break;\n    }\n\n    //Up Down sine wave motion\n    if(layerSinUpDown -> SpeedY == 0)\n    {\n        secretExitFailedStage = 0;\n\n        layerSinRightLeft_displacement = 0;\n        layerSinRightLeft -> SpeedX = 3;\n\n        layerSinUpDown_displacement = 0;\n        layerSinUpDown -> SpeedY = 3;\n    }\n\n    layerSinUpDown -> SpeedY -= (numf_t)layerSinUpDown_displacement / 1000;\n    layerSinUpDown_displacement += (num_t)layerSinUpDown -> SpeedY;\n\n    layerSinUpDownAlternate -> SpeedY = -(layerSinUpDown -> SpeedY);\n    layerSinUpDownWeak -> SpeedY = (layerSinUpDown -> SpeedY) / 3;\n    layerSinUpDownWeakAlt -> SpeedY = -(layerSinUpDown -> SpeedY) / 3;\n\n    //stop the initial trigger going off again\n    if(layerSinUpDown -> SpeedY == 0)\n        layerSinUpDown -> SpeedY = 0.001_nf;\n\n    //Right Left sine wave motion\n    layerSinRightLeft -> SpeedX -= (numf_t)layerSinRightLeft_displacement / 2000;\n    layerSinRightLeft_displacement += (num_t)layerSinRightLeft -> SpeedX;\n    layerSinRightLeftAlternate -> SpeedX = -(layerSinRightLeft -> SpeedX);\n}\n"
  },
  {
    "path": "src/script/luna/levels/Docopoper-TheFloorisLava.h",
    "content": "extern void TheFloorisLavaInit();\nextern void TheFloorisLavaCode();\n"
  },
  {
    "path": "src/script/luna/levels/KilArmoryCode.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**************************************************\n *  Episode:    MAGL X2 ?????                     *\n *  Level:      Thou starts a new video           *\n *  Filename:   LUNA12-thou_starts_a_new_video.lvl*\n *  Author:     ??????                            *\n **************************************************/\n\n#include \"KilArmoryCode.h\"\n#include \"globals.h\"\n#include \"../lunaplayer.h\"\n#include \"../lunainput.h\"\n#include \"../lunarender.h\"\n#include \"../renderop_effect.h\"\n#include \"../autocode_manager.h\"\n\n\nvoid KilArmoryInit()\n{\n    auto *demo = PlayerF::Get(1);\n    if(demo)\n    {\n        PlayerF::FilterToBig(demo);\n        PlayerF::FilterMount(demo);\n        PlayerF::FilterReservePowerup(demo);\n        demo->Character = 1;\n    }\n}\n\nvoid KilArmoryCode()\n{\n    Player_t *demo = PlayerF::Get(1);\n\n    if(demo)\n    {\n        // Section 20(19) glow effect code\n        if(gFrames > 60 && demo->Section == 19)\n        {\n            int intensity = (int)(num_t::sin((num_t)(gFrames) / 22) * 35) + 60;\n            intensity <<= 16;\n            auto *op = new RenderEffectOp(RNDEFF_ScreenGlow, BLEND_Additive, intensity, 100);\n            op->m_FramesLeft = 1;\n            Renderer::Get().AddOp(op);\n        }\n\n        // Section 1(0) glow effect code\n        if(gFrames > 60 && demo->Section == 0 && gAutoMan.GetVar(\"GOTSANGRE\") == 0)\n        {\n            int intensity = (int)(num_t::sin((num_t)(gFrames) / 10) * 45) + 48;\n            intensity <<= 16;\n            auto *op = new RenderEffectOp(RNDEFF_ScreenGlow, BLEND_Additive, intensity, 100);\n            op->m_FramesLeft = 1;\n            Renderer::Get().AddOp(op);\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/script/luna/levels/KilArmoryCode.h",
    "content": "extern void KilArmoryInit();\nextern void KilArmoryCode();\n"
  },
  {
    "path": "src/script/luna/levels/README.txt",
    "content": "There are level specific custom levels made by Talkhaus members.\n\nMost of these codes targeted to work at the \"A Second Mario Bros. Thing,\nEpisode 1: Analog Funk\" game, with an except for \"KilArmoryCode.cpp\" targeted\nprobably for MAGL X2 or something also (absolutely no idea for which thing this\ncode is).\n\nThese codes was originally created as a part of LunaDLL which initially had no\nscripting language at all, except for LunaDLL Autocode simple language that\nwasn't enough to make the complex logic.\n"
  },
  {
    "path": "src/script/luna/levels/SAJewers-QraestoliaCaverns.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*************************************************\n *  Episode:    A2MBXT, Episode 1: Analog Funk   *\n *  Level:      Qraestolia Caverns               *\n *  Filename:   SAJewers-QraestoliaCaverns.lvl   *\n *  Author:     SAJewers                         *\n *************************************************/\n\n#include \"SAJewers-QraestoliaCaverns.h\"\n#include \"globals.h\"\n#include \"../lunaplayer.h\"\n#include \"../lunainput.h\"\n\n\nvoid QraestoliaCavernsCode()\n{\n    static int lastDownPress = 0;\n    Player_t *demo = PlayerF::Get(1);\n\n    if(!demo)\n        return;\n\n    // Player pressed down, what do we do?\n    if(PlayerF::PressingDown(demo))\n    {\n        // Else, see if pressed down in the last 7 frames\n        if(gFrames < lastDownPress + 10 && gFrames > lastDownPress + 1)\n        {\n            if(demo->Character == 2 && demo->MountType != 0)\n                PlayerF::CycleLeft(demo);\n            else\n                PlayerF::CycleRight(demo);\n\n            lastDownPress = gFrames - 9;\n            return;\n        }\n\n        // Else, set last press frame as this one\n        lastDownPress = gFrames;\n    }\n}\n"
  },
  {
    "path": "src/script/luna/levels/SAJewers-QraestoliaCaverns.h",
    "content": "extern void QraestoliaCavernsCode();"
  },
  {
    "path": "src/script/luna/levels/SAJewers-Snowboardin.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*************************************************\n *  Episode:    A2MBXT, Episode 1: Analog Funk   *\n *  Level:      Snowboardin'                     *\n *  Filename:   SAJewers-Snowboardin.lvl         *\n *  Author:     SAJewers                         *\n *************************************************/\n\n#include \"globals.h\"\n#include \"SAJewers-Snowboardin.h\"\n#include \"../lunanpc.h\"\n\n#define NPCID_SHELL 195\n\nnamespace SAJSnowbordin\n{\n\nNPC_t *FindNPC(short identity);\nint combo_start;\n\nvoid SnowbordinInitCode()\n{\n    combo_start = 0;\n}\n\nvoid SnowbordinCode()\n{\n    NPC_t *shell_npc = FindNPC(NPCID_SHELL);\n\n    if(!shell_npc)\n        return;\n\n    uint8_t &shell_kills = shell_npc->Multiplier;\n\n    if(shell_kills >= 9)\n    {\n        if(combo_start < 6)\n            combo_start += 2;\n        shell_kills = combo_start;\n    }\n\n    //Renderer::Get().SafePrint(std::wstring(L\"KILLS: \" + std::to_wstring(*shell_kills)), 3, 0, 256);\n}\n\nNPC_t *FindNPC(short identity)\n{\n    NPC_t *currentnpc = nullptr;\n\n    for(int i = 0; i < numNPCs; i++)\n    {\n        currentnpc = NpcF::Get(i);\n        if(currentnpc && currentnpc->Type == identity)\n            return currentnpc;\n    }\n\n    return nullptr;\n}\n\n} // SAJSnowbordin\n"
  },
  {
    "path": "src/script/luna/levels/SAJewers-Snowboardin.h",
    "content": "namespace SAJSnowbordin\n{\nextern void SnowbordinInitCode();\nextern void SnowbordinCode();\n}"
  },
  {
    "path": "src/script/luna/levels/Talkhaus-Science_Final_Battle.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/**************************************************\n *  Episode:    A2MBXT, Episode 1: Analog Funk    *\n *  Level:      A Final Thing (Name Pending)      *\n *  Filename:   Talkhaus-Science_Final_Battle.lvl *\n *  Authors:    Septentrion_Pleiades, Docopoper,  *\n *              SAJewers, Sturgyman, Demolition   *\n **************************************************/\n\n#include <vector>\n\n#include \"sdl_proxy/sdl_assert.h\"\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"Talkhaus-Science_Final_Battle.h\"\n#include \"globals.h\"\n#include \"main/trees.h\"\n#include \"../lunanpc.h\"\n#include \"../lunaplayer.h\"\n\n\n#define NPCID_FIRE_CHAIN 260\n#define NPCID_DOUGHNUT 210\n#define NPCID_SCIENCE 209\n\nnamespace ScienceBattle\n{\n\nstatic NPC_t *FindNPC(short identity);\nstatic std::vector<NPC_t *> FindAllNPC(short identity);\nstatic bool TriggerBox(num_t x1, num_t y1, num_t x2, num_t y2);\nstatic void HurtPlayer();\n\nstatic bool init_doonce;\nstatic NPC_t *hurt_npc; //, *science_npc, *friendly_doughnut;\nstatic std::vector<NPC_t *> doughnuts;\nstatic Player_t *demo;\nstatic int hurt_timer;\nstatic int grace_timer;\nstatic int throw_timer;\n\nvoid ScienceInitCode()\n{\n    init_doonce = false;\n}\n\nvoid ScienceCode()\n{\n    if(!init_doonce)\n    {\n        init_doonce     = true;\n        hurt_timer      = 0;\n        throw_timer     = 0;\n        demo            = PlayerF::Get(1);\n        SDL_assert_release(demo);\n    }\n\n    hurt_npc = FindNPC(NPCID_FIRE_CHAIN);\n\n    if(!hurt_npc)\n        return;\n\n\n    if(hurt_timer <= 0)\n        hurt_npc->Location.Y = demo->Location.Y - 128;\n    else\n    {\n        hurt_timer--;\n        hurt_npc ->Location.Y = demo->Location.Y;\n    }\n\n    hurt_npc->Location.X = demo->Location.X;\n    treeNPCUpdate(hurt_npc);\n\n    doughnuts = FindAllNPC(NPCID_DOUGHNUT);\n\n    if(demo->HoldingNPC > 0)\n        throw_timer = 30;\n\n    //Renderer::Get().SafePrint(std::wstring(L\"ID: \" + std::to_wstring(demo->HeldNPCIndex)), 3, 0, 256);\n\n\n    if(grace_timer >= 0)\n    {\n        for(auto doughnut : doughnuts)\n        {\n            num_t x_diff, y_diff, m;\n\n            x_diff = doughnut->Location.X - demo->Location.X;\n            y_diff = doughnut->Location.Y - demo->Location.Y;\n            m = num_t::dist(x_diff, y_diff);\n\n            if(m == 0)\n                continue;\n\n            x_diff = x_diff.divided_by(m);\n            y_diff = y_diff.divided_by(m);\n\n            doughnut->Location.X += x_diff * 15;\n            doughnut->Location.Y += y_diff * 15;\n            treeNPCUpdate(doughnut);\n        }\n        grace_timer--;\n    }\n    else\n    {\n        if(throw_timer <= 0)\n        {\n            for(auto doughnut : doughnuts)\n            {\n                //Ignore generators\n                if(doughnut->Hidden) // if((*((int *)doughnut + 16)) != 0)\n                    continue;\n                // Explanation why \"Hidden\":\n                //   1) pointer turned into int* format\n                //   2) +16 made an offset with 16 steps of int, i.e. 4 bytes. So, offset is 4x16 = 64\n                //   3) at 64 (0x40) position, the \"Hidden\" field, not \"generator\"\n\n                num_t x1, x2, y1, y2;\n\n                x1 = doughnut->Location.X + 28 * 0.42_n;\n                y1 = doughnut->Location.Y + 32 * 0.42_n;\n                x2 = doughnut->Location.X + 28 * 0.57_n;\n                y2 = doughnut->Location.Y + 32 * 0.57_n;\n\n                if(TriggerBox(x1, y1, x2, y2))\n                    HurtPlayer();\n            }\n        }\n    }\n\n\n    if(throw_timer > 0)\n        throw_timer--;\n}\n\nstatic NPC_t *FindNPC(short identity)\n{\n    NPC_t *currentnpc = nullptr;\n\n    for(int i = 0; i < numNPCs; i++)\n    {\n        currentnpc = NpcF::Get(i);\n        if(currentnpc && currentnpc->Type == identity)\n            return currentnpc;\n    }\n\n    return nullptr;\n}\n\nstatic std::vector<NPC_t *> FindAllNPC(short identity)\n{\n    std::vector<NPC_t *> npcs_found = std::vector<NPC_t *>();\n    NPC_t *currentnpc = nullptr;\n\n    for(int i = 0; i < numNPCs; i++)\n    {\n        currentnpc = NpcF::Get(i);\n        if(currentnpc && currentnpc->Type == identity)\n            npcs_found.push_back(currentnpc);\n    }\n\n    return npcs_found;\n}\n\nstatic void HurtPlayer()\n{\n    hurt_timer = 3;\n    grace_timer = 120;\n}\n\nstatic bool TriggerBox(num_t x1, num_t y1, num_t x2, num_t y2)\n{\n    return (demo->Location.X + demo->Location.Width     > x1 &&\n            demo->Location.X                            < x2 &&\n            demo->Location.Y + demo->Location.Height    > y1 &&\n            demo->Location.Y                            < y2);\n}\n\n} // ScienceBattle\n"
  },
  {
    "path": "src/script/luna/levels/Talkhaus-Science_Final_Battle.h",
    "content": "namespace ScienceBattle\n{\nextern void ScienceInitCode();\nextern void ScienceCode();\n}"
  },
  {
    "path": "src/script/luna/luna.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"luna.h\"\n#include \"autocode.h\"\n#include \"autocode_manager.h\"\n#include \"lunainput.h\"\n#include \"lunarender.h\"\n#include \"lunaspriteman.h\"\n#include \"lunaplayer.h\"\n#include \"lunacounter.h\"\n#include \"lunalevels.h\"\n#include \"lunavarbank.h\"\n#include \"config.h\"\n\n#include \"globals.h\"\n\n\nSDL_FORCE_INLINE bool lunaAllowed()\n{\n    return g_config.luna_enable_engine;\n}\n\nvoid lunaReset()\n{\n    lunaLevelsClear();\n\n    gFrames = 0;\n\n    gLastDownPress = 0;\n    gDownTapped = 0;\n    gLastUpPress = 0;\n    gUpTapped = 0;\n    gLastLeftPress = 0;\n    gLeftTapped = 0;\n    gLastRightPress = 0;\n    gRightTapped = 0;\n\n    gLastJumpPress = 0;\n    gJumpTapped = 0;\n    gLastRunPress = 0;\n    gRunTapped = 0;\n\n    gEnableDemoCounterByLC = false;\n    gSMBXHUDSettings = SMBXHUDSettings();\n\n    gAutoMan.Clear();\n    Renderer::Get().ClearAllDebugMessages();\n    Renderer::Get().ClearAllLoadedImages();\n    Renderer::Get().ClearQueue();\n    gSpriteMan.ResetSpriteManager();\n    Input::ResetAll();\n\n    gDeathCounter.quit();\n}\n\nvoid lunaLoad()\n{\n    lunaReset();\n\n    bool isGame = !GameMenu && !GameOutro && !BattleMode && !LevelEditor && !TestLevel;\n    bool dcAllow = g_config.enable_fails_tracking;\n\n    if(dcAllow && isGame)\n        gDeathCounter.init();\n\n    if(!LevelEditor && g_config.luna_enable_engine && lunaAllowed())\n    {\n        // Load autocode\n        gAutoMan.LoadFiles();\n\n        // Init var bank\n        gSavedVarBank.CopyBank(&gAutoMan.m_UserVars);\n\n        // Init some stuff\n        if(g_config.luna_allow_level_codes)\n            lunaLevelsInit();\n        gAutoMan.m_Hearts = 2;\n    }\n\n    if(dcAllow && isGame)\n        gDeathCounter.Recount();\n}\n\nvoid lunaLoop()\n{\n    if(g_config.luna_enable_engine)\n    {\n        // Clean up\n        gAutoMan.ClearExpired();\n\n        // Update inputs\n        Input::CheckSpecialCheats();\n        Input::UpdateInputTasks();\n    }\n\n    if(!LevelEditor && g_config.luna_enable_engine && lunaAllowed())\n    {\n#if COMPILE_PLAYGROUND\n        Playground::doPlaygroundStuff();\n#endif\n//        g_EventHandler.hookLevelLoop();\n\n        Renderer::Get().StartRenderLogic();\n\n        // Run autocode\n        gAutoMan.DoEvents(false);\n\n        // Update some stuff\n        gFrames++;\n        gSavedVarBank.SaveIfNeeded();\n\n        // Run any framecode\n//        TestFrameCode();\n        if(g_config.luna_allow_level_codes)\n            lunaLevelsDo();\n\n        Renderer::Get().EndRenderLogic();\n    }\n}\n\n\nvoid lunaRenderHud(int screenZ)\n{\n    bool dcAllow = g_config.enable_fails_tracking || gEnableDemoCounterByLC;\n    if(dcAllow && g_config.show_fails_counter && ShowOnScreenHUD)\n        gDeathCounter.Draw(screenZ);\n\n    Renderer::Get().RenderBelowPriority(PLANE_INTERNAL_FG);\n}\n\nvoid lunaRender(int screenZ)\n{\n    if(!LevelEditor && g_config.luna_enable_engine && lunaAllowed())\n    {\n        Renderer::Get().StartCameraRender(screenZ);\n        gSpriteMan.RunSprites();\n        Renderer::Get().RenderBelowPriority((PLANE)(PLANE_LVL_SECTION_FG + 4));\n    }\n}\n\nvoid lunaRenderStart()\n{\n    if(!LevelEditor && g_config.luna_enable_engine && lunaAllowed())\n        Renderer::Get().StartFrameRender();\n}\n\nvoid lunaRenderEnd()\n{\n    if(!LevelEditor && g_config.luna_enable_engine && lunaAllowed())\n        Renderer::Get().EndFrameRender();\n}\n"
  },
  {
    "path": "src/script/luna/luna.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNA_H\n#define LUNA_H\n\n#include <string>\n\nstruct SMBXHUDSettings\n{\n    bool              skip = false; // Skip the whole HUD drawing\n    // WORLD_HUD_CONTROL overworldHudState;\n    // bool              skipStarCount;\n};\n\nextern SMBXHUDSettings gSMBXHUDSettings;\n\n//! Enable Demos counter locally by LunaControl command\nextern bool gEnableDemoCounterByLC;\n\nextern void lunaReset();\nextern void lunaLoad();\nextern void lunaLoop();\nextern void lunaRenderStart();\nextern void lunaRenderHud(int screenZ);\nextern void lunaRender(int screenZ);\nextern void lunaRenderEnd();\n\n#endif // LUNA_H\n"
  },
  {
    "path": "src/script/luna/lunablock.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunablock.h\"\n#include \"collision.h\"\n\n#include \"main/trees.h\"\n\n#include \"graphics/gfx_update.h\" // invalidateDrawBlocks\n\nBlock_t *BlocksF::Get(int index)\n{\n    return &Block[index];\n}\n\nint BlocksF::TestCollision(Player_t *pMobPOS, Block_t *pBlockPOS)\n{\n    return FindCollision(pMobPOS->Location, pBlockPOS->Location);\n}\n\nvoid BlocksF::SetAll(int type1, int type2)\n{\n    for(int i = 1; i <= numBlock; i++)\n    {\n        if(Block[i].Type == type1)\n            Block[i].Type = type2;\n    }\n}\n\nvoid BlocksF::SwapAll(int type1, int type2)\n{\n    for(int i = 1; i <= numBlock; i++)\n    {\n        if(Block[i].Type == type1)\n            Block[i].Type = type2;\n        else if(Block[i].Type == type2)\n            Block[i].Type = type1;\n    }\n}\n\nvoid BlocksF::ShowAll(int type)\n{\n    bool any_change = false;\n\n    for(int i = 1; i <= numBlock; i++)\n    {\n        if(Block[i].Type == type)\n        {\n            Block[i].Invis = false;\n            any_change = true;\n        }\n    }\n\n    if(any_change)\n        invalidateDrawBlocks();\n}\n\nvoid BlocksF::HideAll(int type)\n{\n    bool any_change = false;\n\n    for(int i = 1; i <= numBlock; i++)\n    {\n        if(Block[i].Type == type)\n        {\n            Block[i].Invis = true;\n            any_change = true;\n        }\n    }\n\n    if(any_change)\n        invalidateDrawBlocks();\n}\n\nbool BlocksF::IsPlayerTouchingType(int type, int sought, Player_t *demo)\n{\n    //    Block* blocks = Blocks::GetBase();\n    num_t playerX = demo->Location.X - 0.20_n;\n    num_t playerY = demo->Location.Y - 0.20_n;\n    num_t playerX2 = demo->Location.X + demo->Location.Width + 0.20_n;\n    num_t playerY2 = demo->Location.Y + demo->Location.Height + 0.20_n;\n\n    for(Block_t* block : treeFLBlockQuery(demo->Location, SORTMODE_NONE))\n    {\n        if(block->Type == type)\n        {\n            if(playerX > block->Location.X + block->Location.Width ||\n               playerX2 < block->Location.X  ||\n               playerY > block->Location.Y + block->Location.Height ||\n               playerY2 < block->Location.Y)\n                continue;\n\n            int ret = TestCollision(demo, block);\n\n            if(sought != 0 && ret == sought)\n                return true;\n            else if(sought == 0 && ret != 0)\n                return false;\n        }\n    }\n\n    return (sought == 0); // no collision\n}\n"
  },
  {
    "path": "src/script/luna/lunablock.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNABLOCK_H\n#define LUNABLOCK_H\n\n#include \"globals.h\"\n\nnamespace BlocksF\n{\nBlock_t* Get(int index);\t\t\t// Get ptr to a block\n\nint TestCollision(Player_t* pMobPOS, Block_t* pBlockPOS);\n\nvoid SetAll(int type1, int type2);  // Set ID of all blocks of type 1 to type 2\nvoid SwapAll(int type1, int type2); // Swap ID of all blocks of type 1 to type 2, and vice versa\nvoid ShowAll(int type);\t\t\t\t// Show all blocks of type\nvoid HideAll(int type);\t\t\t\t// Hide all blocks of type\n\nbool IsPlayerTouchingType(int BlockType, int sought_collision, Player_t* pMobPOS); // See if player touching block of BlockType\n\n}\n\n#endif // LUNABLOCK_H\n"
  },
  {
    "path": "src/script/luna/lunacounter.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Utils/files.h>\n#include <Logger/logger.h>\n#include <fmt_format_ne.h>\n\n#include \"luna.h\"\n#include \"lunacounter.h\"\n#include \"lunacounter_util.h\"\n#include \"lunaplayer.h\"\n#include \"lunarender.h\"\n#include \"renderop_string.h\"\n#include \"globals.h\"\n#include \"config.h\"\n#include \"graphics.h\"\n#include \"config.h\"\n#include \"game_main.h\"\n#include \"core/render.h\"\n#include \"main/game_info.h\"\n#include \"main/menu_main.h\"\n\n\nDeathCounter gDeathCounter;\n\n// CTOR\nDeathCounter::DeathCounter() noexcept\n{\n    mStatFileOK = false;\n    mEnabled = false;\n    mCurTotalDeaths = 0;\n    mCurLevelDeaths = 0;\n\n    // Print Demos counter with a font 3\n    m_print.font = 3;\n}\n\nDeathCounter::~DeathCounter() noexcept\n{\n    if(m_openFile)\n        SDL_RWclose(m_openFile);\n}\n\nvoid DeathCounter::init()\n{\n    if(counterFile.empty())\n    {\n        // prevent a segfault\n        if(TestLevel || selWorld < 0 || selWorld >= (int)SelectWorld.size() || BattleMode || LevelEditor || selSave <= 0)\n        {\n            mEnabled = false;\n            return;\n        }\n\n        std::string oldFile = makeGameSavePath(SelectWorld[selWorld].WorldFilePath,\n                                               fmt::format_ne(\"demos-{0}.dmo\", selSave));\n\n        std::string oldFile2 = makeGameSavePath(SelectWorld[selWorld].WorldFilePath,\n                                               fmt::format_ne(\"deaths-{0}.rip\", selSave));\n\n        counterFile = makeGameSavePath(SelectWorld[selWorld].WorldFilePath,\n                                       fmt::format_ne(\"fails-{0}.rip\", selSave));\n\n        if(Files::fileExists(oldFile)) // Rename old file ino the new name\n            Files::moveFile(counterFile, oldFile);\n        else if(Files::fileExists(oldFile2)) // Rename old file ino the new name\n            Files::moveFile(counterFile, oldFile2);\n\n        if(!TryLoadStats())\n            mEnabled = false;\n    }\n}\n\nvoid DeathCounter::quit()\n{\n    counterFile.clear();\n    mStatFileOK = false;\n    mEnabled = false;\n    mCurTotalDeaths = 0;\n    mCurLevelDeaths = 0;\n    mDeathRecords.clear();\n\n    if(m_openFile)\n    {\n        SDL_RWclose(m_openFile);\n        m_openFile = nullptr;\n    }\n}\n\n// TRY LOAD STATS - Attempts to load stats from stats file. Creates and inits the file if it doesn't exist.\nbool DeathCounter::TryLoadStats()\n{\n    // Try to open the file\n    int32_t tempint = 0;\n    size_t got;\n\n    // If file is not exist yet, try to create empty file\n    if(!Files::fileExists(counterFile))\n    {\n        SDL_RWops *statsfile = Files::open_file(counterFile, \"wb\");\n        if(!statsfile)\n        {\n            mStatFileOK = false;\n            mEnabled = false;\n            pLogWarning(\"Unable to initialize Demos counter: %s\", counterFile.c_str());\n            return false;\n        }\n\n        SDL_RWwrite(statsfile, &tempint, 1, sizeof(int));\n        SDL_RWclose(statsfile);\n    }\n\n    // close old open file if present\n    if(m_openFile)\n    {\n        SDL_RWclose(m_openFile);\n        m_openFile = nullptr;\n    }\n\n    m_openFile = Files::open_file(counterFile, \"r+b\");\n\n    // If create failed, disable death counter\n    if(!m_openFile)\n    {\n        pLogWarning(\"Unable to open the Demos counter: %s\", counterFile.c_str());\n        mStatFileOK = false;\n        mEnabled = false;\n        return false;\n    }\n\n    // If size less than 100, init new file\n    int cursize = (int)SDL_RWsize(m_openFile);\n\n    if(cursize < 50)\n    {\n        InitStatsFile(m_openFile);\n        Files::flush_file(m_openFile);\n        mStatFileOK = true;\n        SDL_RWseek(m_openFile, 0, SEEK_SET);\n    }\n\n//    if(statsfile.good() == false)\n//    {\n//        mStatFileOK = false;\n//        mEnabled = false;\n//        return false;\n//    }\n\n    // Check version\n    got = LunaCounterUtil::readIntLE(m_openFile, tempint);\n    if(got != sizeof(int32_t) || tempint < 5)\n    {\n        if(got != sizeof(int32_t))\n            pLogWarning(\"Fails counter: Failed to read version number at the %s file\", counterFile.c_str());\n\n        mStatFileOK = false;\n        mEnabled = false;\n\n        SDL_RWclose(m_openFile);\n        m_openFile = nullptr;\n\n        return false;\n    }\n\n    mStatFileOK = true;\n    mEnabled = true;\n\n    ClearRecords();\n    ReadRecords(m_openFile);\n\n    return true;\n}\n\n// mark that a death occurred in the current level\nvoid DeathCounter::MarkDeath(bool write_save)\n{\n    bool dcAllow = (gEnableDemoCounterByLC || g_config.enable_fails_tracking);\n\n    if(!dcAllow)\n        return;\n\n    AddDeath(FileNameFull, 1);\n\n    if(write_save)\n    {\n        TrySave();\n        Recount();\n    }\n}\n\n// ADD DEATH\nvoid DeathCounter::AddDeath(const std::string &lvlname, int amount)\n{\n    if(!mEnabled)\n        return;\n\n    for(auto &iter : mDeathRecords)\n    {\n        if(iter.m_levelName == lvlname)   // On first name match...\n        {\n            iter.m_deaths += amount;      // Inc death count\n            return;                        // and exit\n        }\n    }\n\n    // if no match, create new death record and add it to list\n    DeathRecord newrec;\n    newrec.m_levelName = lvlname;\n    newrec.m_deaths = amount;\n    mDeathRecords.push_back(newrec);\n}\n\n// INIT STATS FILE\nvoid DeathCounter::InitStatsFile(SDL_RWops *statsfile)\n{\n    WriteHeader(statsfile);\n}\n\n\n// WRITE HEADER - Write the death counter file header at beginning of file\nvoid DeathCounter::WriteHeader(SDL_RWops *statsfile)\n{\n    // Write dll version\n    SDL_RWseek(statsfile, 0, RW_SEEK_SET);\n    LunaCounterUtil::writeIntLE(statsfile, LUNA_VERSION);\n\n    // Init reserved\n    uint8_t writebyte = 0;\n    off_t offset = SDL_RWtell(statsfile);\n    while(offset < 100)\n    {\n        SDL_RWwrite(statsfile, &writebyte, 1, sizeof(writebyte));\n        offset = SDL_RWtell(statsfile);\n    }\n\n    // Write record count at 100 bytes (0 record count)\n    LunaCounterUtil::writeIntLE(statsfile, 0);\n}\n\n// READ RECORDS - Add death records from file into death record list\nvoid DeathCounter::ReadRecords(SDL_RWops *statsfile)\n{\n    int32_t tempint = 0;\n    size_t got;\n\n    // Read the record count at 100 bytes\n    SDL_RWseek(statsfile, 100, RW_SEEK_SET);\n    got = LunaCounterUtil::readIntLE(statsfile, tempint);\n\n    if(got != sizeof(tempint))\n    {\n        pLogWarning(\"Fails counter: Failed to read the number of records\");\n        return;\n    }\n\n    if(tempint == 0)\n        return;\n\n    for(int i = 0; i < tempint; i++)\n    {\n        DeathRecord newrec;\n        if(newrec.Load(statsfile))\n            mDeathRecords.push_back(newrec);\n    }\n}\n\n\n// WRITE RECORDS - Writes death record count at pos 100 in the file followed by each record\nvoid DeathCounter::WriteRecords(SDL_RWops *statsfile)\n{\n    int32_t reccount = (int32_t)mDeathRecords.size();\n    SDL_RWseek(statsfile, 100, RW_SEEK_SET);\n    LunaCounterUtil::writeIntLE(statsfile, reccount);\n\n    // Write each record, if any exist\n    if(!mDeathRecords.empty())\n    {\n        for(auto &mDeathRecord : mDeathRecords)\n            mDeathRecord.Save(statsfile);\n    }\n}\n\n// TRY SAVE - Externally callable, safe auto-save function\nvoid DeathCounter::TrySave()\n{\n    if(mStatFileOK && mEnabled && m_openFile)\n    {\n        if(SDL_RWseek(m_openFile, 0, RW_SEEK_SET))\n        {\n            // attempt to reopen the file\n            SDL_RWclose(m_openFile);\n            m_openFile = Files::open_file(counterFile, \"wb\");\n        }\n\n        if(m_openFile)\n        {\n            Save(m_openFile);\n            Files::flush_file(m_openFile);\n        }\n    }\n}\n\n// SAVE\nvoid DeathCounter::Save(SDL_RWops *statsfile)\n{\n    if(mStatFileOK && mEnabled)\n    {\n        WriteHeader(statsfile);\n        WriteRecords(statsfile);\n    }\n}\n\n\n// CLEAR RECORDS - Clear the death record list and dealloc its records\nvoid DeathCounter::ClearRecords()\n{\n    mDeathRecords.clear();\n}\n\n// RECOUNT - Recount and relist the death count for the current level and the total deathcount\nvoid DeathCounter::Recount()\n{\n    if(!mEnabled)\n        return;\n\n    int total = 0;\n    mCurLevelDeaths = 0;\n\n    for(const auto &iter : mDeathRecords)\n    {\n        total += iter.m_deaths;\n        if(iter.m_levelName == FileNameFull)\n            mCurLevelDeaths = iter.m_deaths;\n    }\n\n    mCurTotalDeaths = total;\n\n}\n\n// DRAW - Print the death counter in its current state\nvoid DeathCounter::Draw(int screenZ)\n{\n    if(!mEnabled)\n        return;\n\n    // Format string to print\n    m_print.syncCache(mCurLevelDeaths, mCurTotalDeaths);\n    m_print.syncCache(g_gameInfo.fails_counter_title);\n\n    const vScreen_t& vscreen = vScreen[screenZ];\n    const Screen_t& screen = Screens[vscreen.screen_ref];\n\n    // HUD is very wide in >2P shared screen\n    bool wide_hud = (screen.Type == ScreenTypes::SharedScreen && screen.player_count > 2);\n\n    int ScreenTop = 0;\n    if(vscreen.Height > 600)\n        ScreenTop = vscreen.Height / 2 - 300;\n    int HUDLeft = vscreen.Width / 2 - 400;\n\n    // With normal res, print to screen in upper left\n    if(vscreen.Width >= 800 && !wide_hud)\n    {\n        int title_X   = 123 - m_print.titlePixLen / 2;\n        int counter_X = 123 - m_print.counterPixLen / 2;\n\n        // make even\n        title_X &= ~1;\n        counter_X &= ~1;\n\n        SuperPrint(g_gameInfo.fails_counter_title, m_print.font, HUDLeft + title_X, ScreenTop + 26);\n        SuperPrint(m_print.counterOut, m_print.font, HUDLeft + counter_X, ScreenTop + 48);\n    }\n    // At low res, print to top of screen\n    else\n    {\n        int total_W = m_print.titlePixLen + 16 + m_print.counterPixLen;\n        int title_X = vscreen.Width / 2 - total_W / 2;\n\n        if(vscreen.Width < 640)\n            title_X -= 64;\n\n        int counter_X = title_X + m_print.titlePixLen + 16;\n\n        // make even\n        title_X &= ~1;\n        counter_X &= ~1;\n\n        SuperPrint(g_gameInfo.fails_counter_title, m_print.font, title_X, ScreenTop);\n        SuperPrint(m_print.counterOut, m_print.font, counter_X, ScreenTop);\n    }\n}\n\nvoid DeathCounter::CachedPrint::syncCache(int curLevel, int total)\n{\n    if(curLevel != counterLevel || total != counterTotal)\n    {\n        counterLevel = curLevel;\n        counterTotal = total;\n        counterOut = fmt::format_ne(\"{0} / {1}\", curLevel, total);\n        counterPixLen = SuperTextPixLen(counterOut.c_str(), font);\n    }\n}\n\nvoid DeathCounter::CachedPrint::syncCache(const std::string &title)\n{\n    intptr_t ptr = reinterpret_cast<intptr_t>(title.c_str());\n\n    if(titlePointer != ptr || titleSize != title.size())\n    {\n        titlePointer = ptr;\n        titleSize = title.size();\n        titlePixLen = SuperTextPixLen(title.c_str(), font);\n    }\n}\n"
  },
  {
    "path": "src/script/luna/lunacounter.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef DEATHCOUNTER_H\n#define DEATHCOUNTER_H\n\n#include <string>\n#include <list>\n\n#include \"lunacounter_record.h\"\n\n#define DEATHCT_FNAME \"demos.dmo\"\n\nstruct SDL_RWops;\n\nstruct DeathCounter\n{\n    DeathCounter() noexcept;\n    ~DeathCounter() noexcept;\n\n    void init();\n    void quit();\n\n    bool TryLoadStats();\n    // Marks a death on the current level\n    void MarkDeath(bool write_save = true);\n    void AddDeath(const std::string &, int amount);\n    void TrySave();\n    void Draw(int screenZ);\n    void Recount();\n    void ClearRecords();\n\n    struct CachedPrint\n    {\n        // Cache meta-data for counter\n        void syncCache(int curLevel, int total);\n        int counterLevel = -1;\n        int counterTotal = -1;\n        int counterPixLen = 0;\n        int font = -1;\n        std::string counterOut;\n\n        // Cache meta-data for title\n        void syncCache(const std::string &title);\n        int titlePixLen = 0;\n        intptr_t titlePointer = 0;\n        size_t titleSize = std::string::npos;\n    } m_print;\n\nprivate:\n    SDL_RWops* m_openFile = nullptr;\n\n    friend struct DeathRecord;\n    static void InitStatsFile(SDL_RWops *openfile);\n    static void WriteHeader(SDL_RWops *openfile);\n    void WriteRecords(SDL_RWops *statsfile);\n    void ReadRecords(SDL_RWops *openfile);\n    void Save(SDL_RWops *openfile);\n\n    // Members\npublic:\n    bool mStatFileOK = false;\n    bool mEnabled = false;\n\n    int mCurTotalDeaths = 0;\n    int mCurLevelDeaths = 0;\n\n    std::list<DeathRecord> mDeathRecords;\n\n    std::string counterFile;\n};\n\nextern DeathCounter\tgDeathCounter;\n\n#endif // DEATHCOUNTER_H\n"
  },
  {
    "path": "src/script/luna/lunacounter_record.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunacounter_record.h\"\n#include \"lunacounter_util.h\"\n#include <Logger/logger.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n\nvoid DeathRecord::Save(SDL_RWops *openfile)\n{\n    // Write character count\n    auto tempint = (uint32_t)m_levelName.size();\n    LunaCounterUtil::writeUIntLE(openfile, tempint);\n\n    // Write string data\n    int16_t nullt = 0;\n    SDL_RWwrite(openfile, m_levelName.data(), 1, tempint);\n    SDL_RWwrite(openfile, &nullt, 1, sizeof(int16_t));\n\n    // Write death count\n    LunaCounterUtil::writeIntLE(openfile, m_deaths);\n}\n\nbool DeathRecord::Load(SDL_RWops *openfile)\n{\n    size_t got;\n    char buf[151];\n    SDL_memset(buf, 0, 151);\n\n    // Read string length\n    uint32_t length;\n    uint32_t skip = 0;\n\n    got = LunaCounterUtil::readUIntLE(openfile, length);\n    if(got != sizeof(uint32_t))\n    {\n        pLogWarning(\"Demos counter Record: Failed to read the length of the level name\");\n        return false;\n    }\n\n    if(length > 150)\n    {\n        skip = length - 150;\n        length = 150;\n    }\n\n    // Read string data\n    got = SDL_RWread(openfile, buf, 1, length);\n    if(got != length)\n    {\n        pLogWarning(\"Demos counter Record: Failed to read the level name\");\n        return false;\n    }\n\n    if(skip > 0)\n        SDL_RWseek(openfile, skip, RW_SEEK_CUR);\n\n    m_levelName = std::string(buf);\n    SDL_RWseek(openfile, 2, RW_SEEK_CUR);\n\n    // Read death count\n    got = LunaCounterUtil::readIntLE(openfile, m_deaths);\n    if(got != sizeof(int32_t))\n    {\n        pLogWarning(\"Demos counter Record: Failed to read the counter value\");\n        return false;\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/script/luna/lunacounter_record.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef DEATHRECORD_H\n#define DEATHRECORD_H\n\n#include <cstdint>\n#include <cstdio>\n#include <string>\n#include <SDL2/SDL_rwops.h>\n\nstruct DeathRecord\n{\n    void Save(SDL_RWops *openfile);\n    bool Load(SDL_RWops *openfile);\n\n    std::string m_levelName;\n    int32_t m_deaths = 0;\n};\n\n#endif // DEATHRECORD_H\n"
  },
  {
    "path": "src/script/luna/lunacounter_util.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNACOUNTER_UTIL_H\n#define LUNACOUNTER_UTIL_H\n\n#include <cstdio>\n#include <cstdint>\n\n\nnamespace LunaCounterUtil\n{\n\ninline size_t writeIntLE(SDL_RWops *openfile, int32_t inValue)\n{\n    uint8_t out[4];\n    out[0] = 0xFF & (static_cast<uint32_t>(inValue) >> 0);\n    out[1] = 0xFF & (static_cast<uint32_t>(inValue) >> 8);\n    out[2] = 0xFF & (static_cast<uint32_t>(inValue) >> 16);\n    out[3] = 0xFF & (static_cast<uint32_t>(inValue) >> 24);\n    return SDL_RWwrite(openfile, out, 1, 4);\n}\n\ninline size_t readIntLE(SDL_RWops *openfile, int32_t &outValue)\n{\n    uint8_t in[4];\n    size_t ret = SDL_RWread(openfile, in, 1, 4);\n\n    if(ret != 4)\n        return ret;\n\n    outValue = (int32_t)\n               ((static_cast<uint32_t>(in[0]) << 0) & 0x000000FF)\n             | ((static_cast<uint32_t>(in[1]) << 8) & 0x0000FF00)\n             | ((static_cast<uint32_t>(in[2]) << 16) & 0x00FF0000)\n             | ((static_cast<uint32_t>(in[3]) << 24) & 0xFF000000);\n\n    return ret;\n}\n\ninline size_t writeUIntLE(SDL_RWops *openfile, uint32_t inValue)\n{\n    uint8_t out[4];\n    out[0] = 0xFF & (inValue >> 0);\n    out[1] = 0xFF & (inValue >> 8);\n    out[2] = 0xFF & (inValue >> 16);\n    out[3] = 0xFF & (inValue >> 24);\n    return SDL_RWwrite(openfile, out, 1, 4);\n}\n\ninline size_t readUIntLE(SDL_RWops *openfile, uint32_t &outValue)\n{\n    uint8_t in[4];\n    size_t ret = SDL_RWread(openfile, in, 1, 4);\n\n    if(ret != 4)\n        return ret;\n\n    outValue = ((static_cast<uint32_t>(in[0]) << 0) & 0x000000FF)\n             | ((static_cast<uint32_t>(in[1]) << 8) & 0x0000FF00)\n             | ((static_cast<uint32_t>(in[2]) << 16) & 0x00FF0000)\n             | ((static_cast<uint32_t>(in[3]) << 24) & 0xFF000000);\n\n    return ret;\n}\n\n} // namespace\n\n\n#endif // LUNACOUNTER_UTIL_H\n"
  },
  {
    "path": "src/script/luna/lunadefs.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNADEFS_H\n#define LUNADEFS_H\n\n#include \"numeric_types.h\"\n\n#define LUNA_VERSION    9\n\nenum FIELDTYPE\n{\n    FT_INVALID = 0,\n    FT_BYTE = 1,\n    FT_WORD = 2,\n    FT_DWORD = 3,\n    FT_FLOAT = 4,\n    FT_DFLOAT = 5,\n\n    FT_MAX = 5\n};\n\nenum OPTYPE\n{\n    OP_Assign = 0,\n    OP_Add = 1,\n    OP_Sub = 2,\n    OP_Mult = 3,\n    OP_Div = 4,\n    OP_XOR = 5,\n    OP_ABS = 6\n};\n\nenum COMPARETYPE\n{\n    CMPT_EQUALS = 0,\n    CMPT_GREATER = 1,\n    CMPT_LESS = 2,\n    CMPT_NOTEQ = 3\n};\n\nenum DIRECTION\n{\n    DIR_UP = 1,\n    DIR_RIGHT = 2,\n    DIR_DOWN = 3,\n    DIR_LEFT = 4\n};\n\nenum PRIORITY\n{\n    PRI_LOW = 0,\n    PRI_MID,\n    PRI_HIGH\n};\n\nenum WORLD_HUD_CONTROL\n{\n    WHUD_ALL,\n    WHUD_ONLY_OVERLAY,\n    WHUD_NONE\n};\n\nenum LEVEL_HUD_CONTROL\n{\n    LHUD_UNKNOWN1 // Only temporary\n};\n\nstruct LunaRect\n{\n    int left = 0;\n    int top = 0;\n    int right = 0;\n    int bottom = 0;\n};\n\nclass LunaImage;\nclass CSprite;\nstruct CSpriteRequest;\nstruct SpriteComponent;\n\ntypedef void (*pfnSprFunc)(CSprite*, SpriteComponent* obj);\ntypedef void (*pfnSprDraw)(CSprite*);\n\n#endif // LUNADEFS_H\n"
  },
  {
    "path": "src/script/luna/lunaglobals.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"luna.h\"\n\nSMBXHUDSettings gSMBXHUDSettings;\n\nbool gEnableDemoCounterByLC = false;\n\n/* Fallback dummy calls for the case when Luna Autocode has been disabled */\n\n#ifndef THEXTECH_ENABLE_LUNA_AUTOCODE\n\nvoid lunaReset()\n{}\n\nvoid lunaLoad()\n{}\n\nvoid lunaLoop()\n{}\n\nvoid lunaRenderStart()\n{}\n\nvoid lunaRenderHud()\n{}\n\nvoid lunaRender(int)\n{}\n\nvoid lunaRenderEnd()\n{}\n\n#endif\n"
  },
  {
    "path": "src/script/luna/lunaimgbox.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunaimgbox.h\"\n#include \"core/render.h\"\n#include <Utils/files.h>\n\nuint64_t LunaImage::p_uidCounter = 1;\n\nuint64_t LunaImage::getNewUID()\n{\n    return p_uidCounter++;\n}\n\nLunaImage::LunaImage()\n{\n    Init();\n}\n\nLunaImage::~LunaImage()\n{}\n\nLunaImage::LunaImage(const std::string &filename)\n{\n    if(filename.empty())\n        return;\n\n    Init();\n\n    XRender::lazyLoadPicture(m_image, filename);\n    if(Files::hasSuffix(filename, \".jpg\") || Files::hasSuffix(filename, \".bmp\"))\n    {\n        m_useTransColor = true;\n        XRender::setTransparentColor(m_image, m_TransColor);\n    }\n\n    m_uid = getNewUID();\n\n    m_W = m_image.w;\n    m_H = m_image.h;\n}\n\nLunaImage::LunaImage(LunaImage &&o)\n{\n    operator=(std::move(o));\n}\n\nLunaImage &LunaImage::operator=(LunaImage &&o)\n{\n    m_H = o.m_H;\n    m_W = o.m_W;\n    m_uid = o.m_uid;\n    m_TransColor = o.m_TransColor;\n\n    // clear current texture from renderer if it exists\n    m_image.reset();\n\n    // initialize load data from other texture\n    static_cast<StdPicture_Sub&>(m_image) = std::move(static_cast<StdPicture_Sub&>(o.m_image));\n\n    o.Unload();\n\n    return *this;\n}\n\nvoid LunaImage::Init()\n{\n    m_W = 0;\n    m_H = 0;\n    m_uid = 0;\n    m_TransColor = DEFAULT_TRANS_COLOR;\n}\n\nvoid LunaImage::Unload()\n{\n    m_image.reset();\n    Init();\n}\n\nbool LunaImage::ImageLoaded()\n{\n    return m_image.inited;\n}\n\nvoid LunaImage::setTransparentColor(uint32_t rgb)\n{\n    m_TransColor = rgb;\n    if(m_useTransColor)\n        XRender::setTransparentColor(m_image, rgb);\n}\n"
  },
  {
    "path": "src/script/luna/lunaimgbox.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNAIMGBOX_H\n#define LUNAIMGBOX_H\n\n#include <string>\n#include \"std_picture.h\"\n\n#define DEFAULT_TRANS_COLOR 0xFF00DC\n\n// A user-loaded bitmap container\nclass LunaImage\n{\n    static uint64_t p_uidCounter;\n    static uint64_t getNewUID();\n    uint64_t m_uid = 0;\n\n    //! Height of bitmap\n    int m_H = 0;\n    //! Width of bitmap\n    int m_W = 0;\n    //! Value that represents transparency (will write nothing)\n    uint32_t m_TransColor = DEFAULT_TRANS_COLOR;\n    bool     m_useTransColor = false;\n\npublic:\n    /// Functions ///\n    LunaImage();\n    ~LunaImage();\n\n    LunaImage(const std::string &filename);\n    LunaImage(const LunaImage &o) = delete;\n    LunaImage(LunaImage &&o);\n    LunaImage &operator=(const LunaImage &o) = delete;\n    LunaImage &operator=(LunaImage &&o);\n\n    void Init();\n\n    void Unload();\n\n    bool ImageLoaded();\n\n    void setTransparentColor(uint32_t rgb);\n\n    inline uint64_t getUID() const\n    {\n        return m_uid;\n    }\n\n    inline int32_t getW() const\n    {\n        return m_W;\n    }\n\n    inline int32_t getH() const\n    {\n        return m_H;\n    }\n\n\n\n    //! Image handler\n    StdPicture m_image;\n};\n\n#endif // LUNAIMGBOX_H\n"
  },
  {
    "path": "src/script/luna/lunainput.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"config.h\"\n#include \"luna.h\"\n#include \"lunainput.h\"\n#include \"lunaplayer.h\"\n#include \"lunacounter.h\"\n#include \"main/cheat_code.h\"\n#include \"sound.h\"\n#include \"autocode_manager.h\"\n\nint gFrames;\n\nint gLastDownPress;\nint gDownTapped;\nint gLastUpPress;\nint gUpTapped;\nint gLastLeftPress;\nint gLeftTapped;\nint gLastRightPress;\nint gRightTapped;\n\nint gLastJumpPress;\nint gJumpTapped;\nint gLastRunPress;\nint gRunTapped;\n\n\nbool Input::PressingUp()\n{\n    return gLastUpPress == gFrames;\n}\n\nbool Input::PressingDown()\n{\n    return gLastDownPress == gFrames;\n}\n\nbool Input::PressingLeft()\n{\n    return gLastLeftPress == gFrames;\n}\n\nbool Input::PressingRight()\n{\n    return gLastRightPress == gFrames;\n}\n\nbool Input::PressingRun()\n{\n    return gLastRunPress == gFrames;\n}\n\nbool Input::PressingJump()\n{\n    return gLastJumpPress == gFrames;\n}\n\nbool Input::UpThisFrame()\n{\n    return gUpTapped != 0;\n}\n\nbool Input::DownThisFrame()\n{\n    return gDownTapped != 0;\n}\n\nbool Input::LeftThisFrame()\n{\n    return gLeftTapped != 0;\n}\n\nbool Input::RightThisFrame()\n{\n    return gRightTapped != 0;\n}\n\nbool Input::RunThisFrame()\n{\n    return gRunTapped != 0;\n}\n\nbool Input::JumpThisFrame()\n{\n    return gJumpTapped != 0;\n}\n\nvoid Input::UpdateKeyRecords(Player_t *pPlayer)\n{\n    if(!pPlayer)\n        return;\n\n    //wchar_t* dbg = L\"Update keys debug\";\n\n    // Up\n    if(PlayerF::PressingUp(pPlayer))\n    {\n        // If still holding from last frame\n        if(gFrames - 1 == gLastUpPress)\n            gLastUpPress = gFrames;\n        else   //else set tapped this frame\n        {\n            gLastUpPress = gFrames;\n            gUpTapped = gFrames;\n        }\n    }\n\n    // Down\n    if(PlayerF::PressingDown(pPlayer))\n    {\n        // If still holding from last frame\n        if(gFrames - 1 == gLastDownPress)\n            gLastDownPress = gFrames;\n        else   //else set tapped this frame\n        {\n            gLastDownPress = gFrames;\n            gDownTapped = gFrames;\n        }\n    }\n\n    // Left\n    if(PlayerF::PressingLeft(pPlayer))\n    {\n        // If still holding from last frame\n        if(gFrames - 1 == gLastLeftPress)\n            gLastLeftPress = gFrames;\n        else   //else set tapped this frame\n        {\n            gLastLeftPress = gFrames;\n            gLeftTapped = gFrames;\n        }\n    }\n\n    // Right\n    if(PlayerF::PressingRight(pPlayer))\n    {\n        // If still holding from last frame\n        if(gFrames - 1 == gLastRightPress)\n            gLastRightPress = gFrames;\n        else   //else set tapped this frame\n        {\n            gLastRightPress = gFrames;\n            gRightTapped = gFrames;\n        }\n    }\n\n    // Jump\n    if(PlayerF::PressingJump(pPlayer))\n    {\n        // If still holding from last frame\n        if(gFrames - 1 == gLastJumpPress)\n            gLastJumpPress = gFrames;\n        else   //else set tapped this frame\n        {\n            gLastJumpPress = gFrames;\n            gJumpTapped = gFrames;\n        }\n    }\n\n    // Run\n    if(PlayerF::PressingRun(pPlayer))\n    {\n        // If still holding from last frame\n        if(gFrames - 1 == gLastRunPress)\n            gLastRunPress = gFrames;\n        else   //else set tapped this frame\n        {\n            gLastRunPress = gFrames;\n            gRunTapped = gFrames;\n        }\n    }\n}\n\nstatic const std::string FULL_LUNA_TOGGLE_CHT    = \"thouartdamned\";\nstatic const std::string TOGGLE_DEMO_COUNTER_CHT = \"toggledemocounter\";\nstatic const std::string DELETE_ALL_RECORDS_CHT  = \"formatcdrive\";\nstatic const std::string LUNA_DEBUG_CHT          = \"lunadebug\";\nstatic const std::string LUNA_LONG_DEBUG_CHT     = \"lunalongdebug\";\n\nvoid Input::CheckSpecialCheats()\n{\n    if(cheats_contains(FULL_LUNA_TOGGLE_CHT))\n    {\n        g_config.luna_enable_engine = !g_config.luna_enable_engine;\n        PlaySound(SFX_Smash);\n        cheats_clearBuffer();\n        return;\n    }\n\n    else if(cheats_contains(LUNA_DEBUG_CHT) || cheats_contains(LUNA_LONG_DEBUG_CHT))\n    {\n        int length = cheats_contains(LUNA_LONG_DEBUG_CHT) ? 99999 : 600;\n        // FIXME: Replace this with the boolean toggle than adding this command infinitely times\n        gAutoMan.m_CustomCodes.emplace_back(AT_DebugPrint, 0, 0, 0, 0, STRINGINDEX_NONE, length, 0, STRINGINDEX_NONE);\n        PlaySound(SFX_Stomp);\n        cheats_clearBuffer();\n        return;\n    }\n\n    if(g_config.enable_fails_tracking)\n    {\n        if(cheats_contains(TOGGLE_DEMO_COUNTER_CHT))\n        {\n            g_config.show_fails_counter = !g_config.show_fails_counter;\n            PlaySound(SFX_Smash);\n            cheats_clearBuffer();\n            return;\n        }\n        else if(cheats_contains(DELETE_ALL_RECORDS_CHT))\n        {\n            gDeathCounter.ClearRecords();\n            gDeathCounter.TrySave();\n            gDeathCounter.Recount();\n            PlaySound(SFX_Smash);\n            cheats_clearBuffer();\n            return;\n        }\n    }\n}\n\nvoid Input::UpdateInputTasks()\n{\n    //wchar_t* dbg = L\"Input tasks debug\";\n    ResetTaps();\n    UpdateKeyRecords(PlayerF::Get(1));\n}\n\nvoid Input::ResetTaps()\n{\n    gDownTapped = 0;\n    gUpTapped = 0;\n    gLeftTapped = 0;\n    gRightTapped = 0;\n    gRunTapped = 0;\n    gJumpTapped = 0;\n}\n\nvoid Input::ResetAll()\n{\n    gLastDownPress = 0;\n    gDownTapped = 0;\n    gLastUpPress = 0;\n    gUpTapped = 0;\n    gLastLeftPress = 0;\n    gLeftTapped = 0;\n    gLastRightPress = 0;\n    gRightTapped = 0;\n    gLastJumpPress = 0;\n    gJumpTapped = 0;\n    gLastRunPress = 0;\n    gRunTapped = 0;\n}\n"
  },
  {
    "path": "src/script/luna/lunainput.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNAINPUT_H\n#define LUNAINPUT_H\n\nstruct Player_t;\n\nextern int gFrames;\n\nextern int gLastDownPress;\nextern int gDownTapped;\nextern int gLastUpPress;\nextern int gUpTapped;\nextern int gLastLeftPress;\nextern int gLeftTapped;\nextern int gLastRightPress;\nextern int gRightTapped;\n\nextern int gLastJumpPress;\nextern int gJumpTapped;\nextern int gLastRunPress;\nextern int gRunTapped;\n\n\nnamespace Input {\n\nvoid CheckSpecialCheats();\n\nvoid UpdateInputTasks();\n\nvoid ResetTaps();\nvoid ResetAll();\nvoid UpdateKeyRecords(Player_t* player);\n\nbool PressingUp();\nbool PressingDown();\nbool PressingLeft();\nbool PressingRight();\nbool PressingRun();\nbool PressingJump();\n\nbool UpThisFrame();\nbool DownThisFrame();\nbool LeftThisFrame();\nbool RightThisFrame();\nbool RunThisFrame();\nbool JumpThisFrame();\n\n}\n\n\n#endif // LUNAINPUT_H\n"
  },
  {
    "path": "src/script/luna/lunalayer.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunalayer.h\"\n#include \"layers.h\"\n\nLayer_t *LayerF::Get(int layerIdx)\n{\n    if(layerIdx < 0 || layerIdx >= numLayers)\n        return nullptr;\n    return &Layer[layerIdx];\n}\n\nvoid LayerF::Stop(Layer_t *layer)\n{\n    layer->EffectStop = false;\n    layer->SpeedX = 0.0001_nf;\n    layer->SpeedY = 0.0001_nf;\n\n    // NOTE from ds-sloth: why non-zero? probably, to make sure all objects get updated.\n    // It would be better to set all objects (Blocks, BGOs, NPCs) in layer to speed 0.\n    // keep it this way to preserve compatibility with existing Autocode content.\n}\n\nvoid LayerF::SetYSpeed(Layer_t *layer, num_t setY)\n{\n    layer->SpeedY = (setY == 0 ?  0.0001_nf : (numf_t)setY);\n    layer->EffectStop = true;\n}\n\nvoid LayerF::SetXSpeed(Layer_t *layer, num_t setX)\n{\n    layer->SpeedX = (setX == 0 ?  0.0001_nf : (numf_t)setX);\n    layer->EffectStop = true;\n}\n"
  },
  {
    "path": "src/script/luna/lunalayer.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNALAYER_H\n#define LUNALAYER_H\n\n#include \"numeric_types.h\"\n\nstruct Layer_t;\n\nnamespace LayerF\n{\n\nLayer_t *Get(int layerIdx);\n\n// Stop a layer\nvoid Stop(Layer_t *layer);\n\nvoid SetYSpeed(Layer_t *layer, num_t setY);\n\nvoid SetXSpeed(Layer_t *layer, num_t setX);\n\n} // LayerF\n\n#endif // LUNALAYER_H\n"
  },
  {
    "path": "src/script/luna/lunalevel.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunalevel.h\"\n#include \"globals.h\"\n\n#include \"npc/section_overlap.h\"\n\n\nvoid LevelF::PushSectionBoundary(int section, int which_boundary_UDLR, num_t push_val)\n{\n    SpeedlessLocation_t &boundarray = level[section];\n\n    switch(which_boundary_UDLR)\n    {\n    case 0:     // U\n        boundarray.Y += push_val;\n        break;\n\n    case 1:     // D\n        boundarray.Height += push_val;\n        break;\n\n    case 2:     // L\n        boundarray.X += push_val;\n        break;\n\n    case 3:     // R\n        boundarray.Width += push_val;\n        break;\n\n    default:\n        break;\n    }\n\n    UpdateSectionOverlaps(section);\n}\n\nvoid LevelF::SetSectionBounds(int section, num_t left_bound, num_t top_bound, num_t right_bound, num_t bot_bound)\n{\n    SpeedlessLocation_t &boundarray = level[section];\n    boundarray.X = left_bound;\n    boundarray.Y = top_bound;\n    boundarray.Height = bot_bound;\n    boundarray.Width = right_bound;\n    UpdateSectionOverlaps(section);\n}\n\nnum_t LevelF::GetBoundary(int section, int which_boundary_UDLR)\n{\n    const SpeedlessLocation_t &boundarray = level[section];\n\n    switch(which_boundary_UDLR)\n    {\n    case 0:     // U\n        return boundarray.Y;\n    case 1:     // D\n        return boundarray.Height;\n    case 2:     // L\n        return boundarray.X;\n    case 3:     // R\n        return boundarray.Width;\n    default:\n        break;\n    }\n\n    return 0;\n}\n\nvoid LevelF::GetBoundary(LunaRect *rect, int section)\n{\n    if(section >= 0 && section < numSections)\n    {\n        const SpeedlessLocation_t &boundarray = level[section];\n        rect->top = (int)boundarray.Y;\n        rect->bottom = (int)boundarray.Height;\n        rect->left = (int)boundarray.X;\n        rect->right = (int)boundarray.Width;\n    }\n}\n\nstd::string LevelF::GetName()\n{\n    return LevelName;\n}\n"
  },
  {
    "path": "src/script/luna/lunalevel.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNALEVEL_H\n#define LUNALEVEL_H\n\n#include <string>\n#include \"lunadefs.h\"\n\nnamespace LevelF\n{\n\nnum_t GetBoundary(int section, int which_boundary_UDLR);\nvoid GetBoundary(LunaRect* rectangle, int section);\nvoid PushSectionBoundary(int section, int which_boundary_UDLR, num_t push_val);\nvoid SetSectionBounds(int section, num_t left_bound, num_t top_bound, num_t right_bound, num_t bot_bound);\n\nstd::string GetName();\n\n}\n\n#endif // LUNALEVEL_H\n"
  },
  {
    "path": "src/script/luna/lunalevels.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <unordered_map>\n#include <string>\n#include <functional>\n#include \"lunalevels.h\"\n\n#include \"globals.h\"\n\n#include \"levels/Docopoper-Calleoca.h\"\n#include \"levels/Docopoper-AbstractAssault.h\"\n#include \"levels/Docopoper-TheFloorisLava.h\"\n#include \"levels/SAJewers-QraestoliaCaverns.h\"\n#include \"levels/SAJewers-Snowboardin.h\"\n#include \"levels/Talkhaus-Science_Final_Battle.h\"\n#include \"levels/KilArmoryCode.h\"\n\n\nstatic void (*levelCodeRun)() = nullptr;\n\nstatic const std::unordered_map<std::string, std::function<void()>> s_levelInit =\n{\n\n// Abtract Assault init block\n{\n    \"Docopoper-AbstractAssault.lvl\",\n    []()->void\n    {\n        levelCodeRun = AbstractAssaultCode;\n    }\n},\n// Calleoca init block\n{\n    \"Docopoper-Calleoca.lvl\",\n    []()->void\n    {\n        CalleocaInitCode();\n        levelCodeRun = CalleocaCode;\n    }\n},\n// The Floor is Lava init block\n{\n    \"Docopoper-TheFloorisLava.lvl\",\n    []()->void\n    {\n        levelCodeRun = TheFloorisLavaCode;\n        TheFloorisLavaInit();\n    }\n},\n\n// Qraestolia Caverns init block\n{\n    \"SAJewers-QraestoliaCaverns.lvl\",\n    []()->void\n    {\n        levelCodeRun = QraestoliaCavernsCode;\n    }\n},\n\n// Snowbordin init block\n{\n    \"SAJewers-Snowboardin.lvl\",\n    []()->void\n    {\n        levelCodeRun = SAJSnowbordin::SnowbordinCode;\n        SAJSnowbordin::SnowbordinInitCode();\n    }\n},\n\n// Science init block\n{\n    \"Talkhaus-Science_Final_Battle.lvl\",\n    []()->void\n    {\n        levelCodeRun = ScienceBattle::ScienceCode;\n        ScienceBattle::ScienceInitCode();\n    }\n},\n\n{\n    \"LUNA12-thou_starts_a_new_video.lvl\",\n    []()->void\n    {\n        levelCodeRun = KilArmoryCode;\n        KilArmoryInit();\n    }\n},\n\n};\n\n\nvoid lunaLevelsInit()\n{\n    levelCodeRun = nullptr;\n\n    auto l = s_levelInit.find(FileNameFull);\n    if(l != s_levelInit.end())\n        l->second();\n}\n\nvoid lunaLevelsClear()\n{\n    levelCodeRun = nullptr;\n}\n\nvoid lunaLevelsDo()\n{\n    if(levelCodeRun)\n        levelCodeRun();\n}\n"
  },
  {
    "path": "src/script/luna/lunalevels.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNALEVELS_H\n#define LUNALEVELS_H\n\n/*!\n * \\brief Initialize level custom codes and attach to current runner\n */\nextern void lunaLevelsInit();\n\n/*!\n * \\brief Remove the custom code attachment\n */\nextern void lunaLevelsClear();\n\n/*!\n * \\brief Process one step of level custom code if attached\n */\nextern void lunaLevelsDo();\n\n#endif // LUNALEVELS_H\n"
  },
  {
    "path": "src/script/luna/lunamisc.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunamisc.h\"\n#include \"globals.h\"\n#include \"collision.h\"\n#include \"rand.h\"\n#include <Utils/files.h>\n#include \"global_dirs.h\"\n\n\nvoid InitIfMissing(std::map<std::string, num_t> *pMap, const std::string& sought_key, num_t init_val)\n{\n    if(pMap->find(sought_key) == pMap->end())\n        (*pMap)[sought_key] = init_val;\n}\n\nbool FastTestCollision(int L1, int U1, int R1, int D1, int L2, int U2, int R2, int D2)\n{\n    bool rightcol = true;\n    bool leftcol = true;\n    bool upcol = true;\n    bool downcol = true;\n\n    if(R1 < L2)\n        rightcol = false;\n    if(L1 > R2)\n        leftcol = false;\n    if(U1 > D2)\n        downcol = false;\n    if(D1 < U2)\n        upcol = false;\n\n    if(!rightcol || !leftcol || !upcol || !downcol)\n        return false;\n\n    return true;\n}\n\nint ComputeLevelSection(int x, int y)\n{\n    Location_t l;\n    l.X = x - 64;\n    l.Y = y - 64;\n    l.Width = 128;\n    l.Height = 128;\n\n    int ret = -1;\n\n    for(int i = 0; i < numSections; ++i)\n    {\n        if(SectionCollision(i, l))\n        {\n            ret = i;\n            break;\n        }\n    }\n\n    return ret;\n}\n\nvoid RandomPointInRadius(num_t *ox, num_t *oy, num_t cx, num_t cy, int radius)\n{\n    // this does not do what the author thinks it does -- terribly biased towards the edges of the square enclosing the circle of interest\n    num_t phase1 = iRand2(360);\n    num_t phase2 = iRand2(360);\n\n    num_t xoff = num_t::sin(phase1) * radius;\n    num_t yoff = num_t::cos(phase2) * radius;\n\n    *ox = cx + xoff;\n    *oy = cy + yoff;\n}\n\nstd::string resolveIfNotAbsolutePath(const std::string &filename)\n{\n    if(Files::isAbsolute(filename)) {\n        return filename;\n    }\n\n    std::vector<DirListCI *> dirs =\n    {\n        &g_dirCustom,\n        &g_dirEpisode\n    };\n\n    for(DirListCI *nextSearchDir : dirs)\n    {\n        std::string nextEntry = nextSearchDir->resolveFileCaseExistsAbs(filename);\n        if(nextEntry.empty())\n            continue;\n        return nextEntry;\n    }\n\n    return filename;\n}\n"
  },
  {
    "path": "src/script/luna/lunamisc.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNAMISC_H\n#define LUNAMISC_H\n\n#include \"numeric_types.h\"\n\n#include <map>\n#include <string>\n\nextern void InitIfMissing(std::map<std::string, num_t>* pMap, const std::string& sought_key, num_t init_val);\n\nextern bool FastTestCollision(int Left1, int Up1, int Right1, int Down1, int Left2, int Up2, int Right2, int Down2);\n\n// Compute the current SMBX level section for the given coords, or -1 if invalid\nextern int ComputeLevelSection(int x, int y);\n\nextern void RandomPointInRadius(num_t* ox, num_t* oy, num_t cx, num_t cy, int radius);\n\nextern std::string resolveIfNotAbsolutePath(const std::string &filename);\n\n#endif // LUNAMISC_H\n"
  },
  {
    "path": "src/script/luna/lunanpc.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunanpc.h\"\n#include \"globals.h\"\n#include \"mememu.h\"\n#include \"npc.h\"\n\n\nNPC_t *NpcF::Get(int index)\n{\n    if(index < 0 || index >= numNPCs)\n        return nullptr;\n\n    return &NPC[index + 1];\n}\n\nNPC_t *NpcF::GetRaw(int index)\n{\n    return &NPC[index];\n}\n\nvoid NpcF::FindAll(int ID, int section, std::list<NPC_t *> *return_list)\n{\n    bool anyID = (ID == -1);\n    bool anySec = (section == -1);\n    NPC_t *thisnpc = nullptr;\n\n    for(int i = 1; i <= numNPCs; i++)\n    {\n        thisnpc = &NPC[i];\n        if(thisnpc->Type == ID || anyID)\n        {\n            if(thisnpc->Section == section || anySec)\n                return_list->push_back(thisnpc);\n        }\n    }\n}\n\nNPC_t *NpcF::GetFirstMatch(int ID, int section)\n{\n    bool anyID = (ID == -1);\n    bool anySec = (section == -1);\n    NPC_t *thisnpc = nullptr;\n\n    for(int i = 1; i <= numNPCs; i++)\n    {\n        thisnpc = &NPC[i];\n        if(thisnpc->Type == ID || anyID)\n        {\n            if(thisnpc->Section == section || anySec)\n                return thisnpc; //matched\n        }\n    }\n\n    return nullptr; //not matched\n}\n\nvoid NpcF::MemSet(int ID, size_t offset, num_t value, OPTYPE operation, FIELDTYPE ftype)\n{\n    //    char* dbg =  \"MemSetDbg\";\n    if(ftype == FT_INVALID || offset > 0x15C)\n        return;\n\n    bool anyID = (ID == -1);\n    NPC_t *thisnpc;\n\n    for(int i = 1; i <= numNPCs; i++)\n    {\n        thisnpc = &NPC[i];\n        if(anyID || thisnpc->Type == ID)\n            MemAssign(thisnpc, offset, value, operation, ftype);\n    }//for\n}\n\nvoid NpcF::AllSetHits(int identity, int section, int hits)\n{\n    bool anyID = (identity == -1);\n    bool anySec = (section == -1);\n    NPC_t *thisnpc;\n\n    for(int i = 1; i <= numNPCs; i++)\n    {\n        thisnpc = &NPC[i];\n        if(anyID || thisnpc->Type == identity)\n        {\n            if(anySec || thisnpc->Section == section)\n                thisnpc->Damage = hits;\n        }\n    }\n}\n\nvoid NpcF::AllFace(int identity, int section, num_t x)\n{\n    bool anyID = (identity == -1);\n    bool anySec = (section == -1);\n    NPC_t *thisnpc;\n\n    for(int i = 1; i <= numNPCs; i++)\n    {\n        thisnpc = &NPC[i];\n        if(anyID || thisnpc->Type == identity)\n        {\n            if(anySec || thisnpc->Section == section)\n            {\n                // thisnpc->Special6 = (x < thisnpc->Location.X) ? -1.0 : 1.0;\n                thisnpc->Direction = (x < thisnpc->Location.X) ? -1 : 1;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/script/luna/lunanpc.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNANPC_H\n#define LUNANPC_H\n\n#include <cstddef>\n#include <list>\n\n#include \"lunadefs.h\"\n\nstruct NPC_t;\n\nnamespace NpcF\n{\n\nNPC_t* Get(int index); //Get ptr to an NPC\nNPC_t* GetRaw(int index);\n\nvoid FindAll(int ID, int section, std::list<NPC_t *> *return_list);\n\n// GET FIRST MATCH\nNPC_t *GetFirstMatch(int ID, int section);\n\nvoid MemSet(int ID, size_t offset, num_t value, OPTYPE operation, FIELDTYPE ftype); // ID -1 for ALL\n\n// ITERATORS\nvoid AllSetHits(int identity, int section, int hits);\t\t// Set all specified NPC hits\nvoid AllFace(int identity, int section, num_t x);\t// All specified NPCs face the supplied x/y point\n\n} // NpcF\n\n#endif // LUNANPC_H\n"
  },
  {
    "path": "src/script/luna/lunaplayer.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunaplayer.h\"\n#include \"lunanpc.h\"\n#include \"globals.h\"\n#include \"sound.h\"\n#include \"mememu.h\"\n\n\nPlayer_t *PlayerF::Get(int num)\n{\n    if(num > numPlayers || num < 1)\n        return nullptr;\n\n    return &Player[num];\n}\n\nvoid PlayerF::MemSet(size_t offset, num_t value, OPTYPE operation, FIELDTYPE ftype)\n{\n    //    char* dbg =  \"PLAYER MEM SET\";\n    if(ftype == FT_INVALID || offset > (0x184 * 99))\n        return;\n    Player_t *pPlayer = Get(1);\n    MemAssign(pPlayer, offset, value, operation, ftype);\n}\n\n\nbool PlayerF::PressingDown(Player_t *player)\n{\n    return player->Controls.Down;\n}\n\nbool PlayerF::PressingUp(Player_t *player)\n{\n    return player->Controls.Up;\n}\n\nbool PlayerF::PressingLeft(Player_t *player)\n{\n    return player->Controls.Left;\n}\n\nbool PlayerF::PressingRight(Player_t *player)\n{\n    return player->Controls.Right;\n}\n\nbool PlayerF::PressingJump(Player_t *player)\n{\n    return player->Controls.Jump;\n}\n\nbool PlayerF::PressingRun(Player_t *player)\n{\n    return player->Controls.Run;\n}\n\nbool PlayerF::PressingSEL(Player_t *player)\n{\n    return player->Controls.Drop;\n}\n\n\n\nvoid PlayerF::FilterToFire(Player_t *player)\n{\n    if(player->State > 3)\n        player->State = 3;\n}\n\nvoid PlayerF::FilterToBig(Player_t *player)\n{\n    if(player->State > 2)\n        player->State = 2;\n}\n\nvoid PlayerF::FilterToSmall(Player_t *player)\n{\n    if(player->State > 1)\n        player->State = 1;\n}\n\nvoid PlayerF::FilterReservePowerup(Player_t *player)\n{\n    player->HeldBonus = NPCID(0);\n}\n\nvoid PlayerF::FilterMount(Player_t *player)\n{\n    player->MountType = 0;\n    player->Mount = 0;\n    UpdateYoshiMusic();\n}\n\n\nvoid PlayerF::InfiniteFlying(int player)\n{\n    Player_t *demo = Get(player);\n\n    if(demo)\n        demo->FlyCount = 50;\n}\n\nbool PlayerF::UsesHearts(Player_t *p)\n{\n    return p->Character != 1 && p->Character != 2;\n}\n\nLunaRect PlayerF::GetScreenPosition(Player_t *player)\n{\n    //    double* pCameraY = (double*)GM_CAMERA_Y;\n    //    double* pCameraX = (double*)GM_CAMERA_X;\n    ptrdiff_t plr_index = (player - &Player[1] + 1);\n    if(plr_index < 1 || plr_index > numPlayers)\n        plr_index = 1;\n\n    const auto& vscreen = vScreenByPlayer(plr_index);\n    num_t cam_y = -vscreen.Y;\n    num_t cam_x = -vscreen.X;\n    //    double cam_d = cam_y + 600;\n    //    double cam_r = cam_x + 800;\n\n    LunaRect ret_rect;\n    ret_rect.left = num_t::round(player->Location.X - cam_x);\n    ret_rect.top = num_t::round(player->Location.Y - cam_y);\n    ret_rect.right = num_t::round(ret_rect.left + player->Location.Width);\n    ret_rect.bottom = num_t::round(ret_rect.top + player->Location.Height);\n    return ret_rect;\n}\n\nvoid PlayerF::CycleRight(Player_t *player)\n{\n    player->Character++;\n    if(player->Character > 5)\n        player->Character = 1;\n\n    UpdateYoshiMusic();\n}\n\nvoid PlayerF::CycleLeft(Player_t *player)\n{\n    player->Character--;\n    if(player->Character <= 0)\n        player->Character = 5;\n\n    UpdateYoshiMusic();\n}\n\nbool PlayerF::IsHoldingSpriteType(Player_t *player, int NPC_ID)\n{\n    if(player->HoldingNPC != 0)\n    {\n        NPC_t *npc = &NPC[player->HoldingNPC];\n        if(npc->Type == NPC_ID)\n            return true;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/script/luna/lunaplayer.h",
    "content": "﻿/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LunaPlayer_HHH\n#define LunaPlayer_HHH\n\n#include <cstddef>\n#include \"lunadefs.h\"\n\nstruct Player_t;\n\nnamespace PlayerF\n{\n\nPlayer_t *Get(int num);\n\n// PLAYER MANAGEMENT\n\nvoid MemSet(size_t offset, num_t value, OPTYPE operation, FIELDTYPE ftype);\n\n// PLAYER BUTTONS\nbool PressingDown(Player_t* player);\nbool PressingUp(Player_t* player);\nbool PressingLeft(Player_t* player);\nbool PressingRight(Player_t* player);\nbool PressingJump(Player_t* player);\nbool PressingRun(Player_t* player);\nbool PressingSEL(Player_t* pPlayer);\n\n// CYCLE PLAYER\nvoid CycleRight(Player_t* player);\t// Changes player identity to the next character, or around to Demo\nvoid CycleLeft(Player_t* player);\t// Changes player identity to the previous character, or around to Sheath\n\n// PLAYER STATES\nbool IsHoldingSpriteType(Player_t* player, int NPC_ID);\n\n// FILTER FUNCS\nvoid FilterToFire(Player_t *player);\nvoid FilterToBig(Player_t *player);\nvoid FilterToSmall(Player_t *player);\nvoid FilterReservePowerup(Player_t *player);\nvoid FilterMount(Player_t *player);\n\nvoid InfiniteFlying(int player);\n\nbool UsesHearts(Player_t *p);\n\n// GET SCREEN POSITION\nLunaRect GetScreenPosition(Player_t *player);\n\n} // PlayerF\n\n#endif // LunaPlayer_HHH\n"
  },
  {
    "path": "src/script/luna/lunarender.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunarender.h\"\n#include \"autocode_manager.h\"\n#include \"renderop.h\"\n#include \"globals.h\"\n#include \"config.h\"\n#include \"lunamisc.h\"\n#include \"renderop_string.h\"\n#include \"game_main.h\"\n#include <fmt_format_ne.h>\n\n#include <algorithm>\n\n\nPoolAllocator g_rAlloc(c_rAllocTotalSize, c_rAllocChunkSize);\n\nstatic Renderer sLunaRender;\n\nRenderer &Renderer::Get()\n{\n    return sLunaRender;\n}\n\nvoid Renderer::SetAltThread()\n{}\n\nvoid Renderer::UnsetAltThread()\n{}\n\nbool Renderer::IsAltThreadActive()\n{\n    return false;\n}\n\nRenderer::Renderer() noexcept :\n    m_queueState(),\n    m_legacyResourceCodeImages()\n{\n    g_rAlloc.Init();\n}\n\nRenderer::~Renderer()\n{\n    ClearQueue();\n}\n\nbool Renderer::LoadBitmapResource(const std::string& filename, int resource_code, uint32_t transparency_color)\n{\n    DeleteImage(resource_code);\n\n    auto absPath = AutocodeManager::resolveCustomFileCase(filename);\n\n    if(absPath.empty())\n        absPath = AutocodeManager::resolveWorldFileCase(filename);\n\n    if(absPath.empty())\n    {\n        pLogWarning(\"LunaRender: image file %s is not found\", filename.c_str());\n        return false;\n    }\n\n    LunaImage img(absPath);\n    if(!img.ImageLoaded())\n    {\n        pLogWarning(\"LunaRender: Failed to load image: %s\", absPath.c_str());\n        return false;\n    }\n\n    img.setTransparentColor(transparency_color);\n    StoreImage(std::move(img), resource_code);\n\n    return true;\n}\n\nbool Renderer::LoadBitmapResource(const std::string& filename, int resource_code)\n{\n    return LoadBitmapResource(filename, resource_code, DEFAULT_TRANS_COLOR);\n}\n\nvoid Renderer::StoreImage(LunaImage &&bmp, int resource_code)\n{\n    m_legacyResourceCodeImages[resource_code] = std::move(bmp);\n}\n\nbool Renderer::DeleteImage(int resource_code)\n{\n    auto it = m_legacyResourceCodeImages.find(resource_code);\n    if(it != m_legacyResourceCodeImages.end())\n    {\n        it->second.Unload();\n        m_legacyResourceCodeImages.erase(it);\n        return true;\n    }\n\n    return false;\n}\n\nLunaImage *Renderer::GetImageForResourceCode(int resource_code)\n{\n    auto it = m_legacyResourceCodeImages.find(resource_code);\n    if(it != m_legacyResourceCodeImages.end())\n        return &it->second;\n\n    return nullptr;\n}\n\nvoid Renderer::AddOp(RenderOp *op)\n{\n    if(op->m_selectedCamera == 0)\n    {\n        // If the rendering operation was created in the middle of handling a\n        // camera's rendering, lock the rendering operation to that camera.\n        op->m_selectedCamera = m_queueState.m_curCamIdx;\n    }\n\n    this->m_queueState.m_currentRenderOps.push_back(op);\n}\n\nvoid Renderer::DebugPrint(const std::string &message)\n{\n    this->m_queueState.m_debugMessages.push_back(message);\n}\n\nvoid Renderer::DebugPrint(const std::string &message, num_t val)\n{\n    this->m_queueState.m_debugMessages.push_back(fmt::format_ne(\"{0} {1}\", message, (double)val));\n}\n\nstatic bool CompareRenderPriority(const RenderOp *lhs, const RenderOp *rhs)\n{\n    return lhs->m_renderPriority < rhs->m_renderPriority;\n}\n\nvoid Renderer::RenderBelowPriority(PLANE maxPriority)\n{\n    if(!m_queueState.m_InFrameRender) return;\n\n    //    if (this == &sLunaRender)\n    //    {\n    //        // Make sure we kill the loadscreen before main thread rendering\n    //        LunaLoadScreenKill();\n    //    }\n\n    auto &ops = m_queueState.m_currentRenderOps;\n    if(ops.size() <= m_queueState.m_renderOpsProcessedCount) return;\n\n    // Flush pending BltBlt\n    //    g_BitBltEmulation.flushPendingBlt();\n\n    // Assume operations already processed were already sorted\n    if(m_queueState.m_renderOpsSortedCount == 0)\n    {\n        std::stable_sort(ops.begin(), ops.end(), CompareRenderPriority);\n        m_queueState.m_renderOpsSortedCount = ops.size();\n    }\n    else if(m_queueState.m_renderOpsSortedCount < ops.size())\n    {\n        // Sort the new operations\n        std::stable_sort(ops.begin() + m_queueState.m_renderOpsSortedCount, ops.end(), CompareRenderPriority);\n\n        // Render things as many of the new items as we should before merging the sorted lists\n        PLANE maxPassPriority = maxPriority;\n        if(m_queueState.m_renderOpsSortedCount > m_queueState.m_renderOpsProcessedCount)\n        {\n            PLANE nextPriorityInOldList = ops[m_queueState.m_renderOpsProcessedCount]->m_renderPriority;\n            if(nextPriorityInOldList < maxPassPriority)\n                maxPassPriority = nextPriorityInOldList;\n        }\n\n        for(auto iter = ops.cbegin() + m_queueState.m_renderOpsSortedCount, end = ops.cend(); iter != end; ++iter)\n        {\n            RenderOp &op = **iter;\n            if(op.m_renderPriority >= maxPassPriority)\n                break;\n            DrawOp(op);\n            m_queueState.m_renderOpsProcessedCount++;\n        }\n\n        // Merge sorted list sections (note, std::inplace_merge is a stable sort)\n        std::inplace_merge(ops.begin(), ops.begin() + m_queueState.m_renderOpsSortedCount, ops.end(), CompareRenderPriority);\n        m_queueState.m_renderOpsSortedCount = ops.size();\n    }\n\n    // Render other operations\n    for(auto iter = ops.cbegin() + m_queueState.m_renderOpsProcessedCount, end = ops.cend(); iter != end; ++iter)\n    {\n        RenderOp &op = **iter;\n        if(op.m_renderPriority >= maxPriority)\n            break;\n        DrawOp(op);\n        m_queueState.m_renderOpsProcessedCount++;\n    }\n\n    if(maxPriority >= RENDEROP_PRIORITY_MAX)\n    {\n        // Format debug messages and enter them into renderstring list\n        int dbg_x = 325;\n        int dbg_y = 160;\n\n        for(auto &dbg : m_queueState.m_debugMessages)\n        {\n            RenderStringOp(dbg, 4, dbg_x, dbg_y).Draw(this);\n            dbg_y += 20;\n            if(dbg_y > 560)\n            {\n                dbg_y = 160;\n                dbg_x += 190;\n            }\n        }\n\n        this->m_queueState.m_debugMessages.clear();\n    }\n}\n\nvoid Renderer::ClearAllDebugMessages()\n{\n    this->m_queueState.m_debugMessages.clear();\n}\n\nvoid Renderer::ClearAllLoadedImages()\n{\n    for(auto &i : m_legacyResourceCodeImages)\n        i.second.Unload();\n    m_legacyResourceCodeImages.clear();\n}\n\nvoid Renderer::StartCameraRender(int idx)\n{\n    m_queueState.m_curCamIdx = idx;\n    m_queueState.m_renderOpsProcessedCount = 0;\n}\n\nvoid Renderer::StoreCameraPosition(int idx)\n{\n    UNUSED(idx);\n    //    if (g_GLEngine.IsEnabled())\n    //    {\n    //        std::shared_ptr<GLEngineCmd_SetCamera> cmd = std::make_shared<GLEngineCmd_SetCamera>();\n    //        cmd->mX = SMBX_CameraInfo::getCameraX(idx);\n    //        cmd->mY = SMBX_CameraInfo::getCameraY(idx);\n    //        g_GLEngine.QueueCmd(cmd);\n    //    }\n}\n\nvoid Renderer::StartRenderLogic()\n{\n    // Decrement life time counters\n    for(auto &m_currentRenderOp : m_queueState.m_currentRenderOps)\n        m_currentRenderOp->m_FramesLeft--;\n}\n\nvoid Renderer::EndRenderLogic()\n{\n    // Remove cleared operations\n    std::vector<RenderOp *> nonExpiredOps;\n    for(auto &m_currentRenderOp : m_queueState.m_currentRenderOps)\n    {\n        RenderOp *pOp = m_currentRenderOp;\n        if(pOp->m_FramesLeft <= 0)\n        {\n            m_currentRenderOp = nullptr;\n            delete pOp;\n        }\n        else\n            nonExpiredOps.push_back(pOp);\n    }\n\n    m_queueState.m_currentRenderOps.swap(nonExpiredOps);\n}\n\nvoid Renderer::StartFrameRender()\n{\n    m_queueState.m_curCamIdx = 0;\n    m_queueState.m_InFrameRender = true;\n    m_queueState.m_renderOpsSortedCount = m_queueState.m_currentRenderOps.size();\n}\n\nvoid Renderer::EndFrameRender()\n{\n    if(!m_queueState.m_InFrameRender)\n        return;\n\n    m_queueState.m_curCamIdx = 0;\n    m_queueState.m_renderOpsProcessedCount = 0;\n    m_queueState.m_InFrameRender = false;\n}\n\nvoid Renderer::ClearQueue()\n{\n    m_queueState.m_curCamIdx = 0;\n    for(auto &m_currentRenderOp : m_queueState.m_currentRenderOps)\n        delete m_currentRenderOp;\n    g_rAlloc.Reset();\n    m_queueState.m_currentRenderOps.clear();\n    m_queueState.m_renderOpsProcessedCount = 0;\n    m_queueState.m_renderOpsSortedCount = 0;\n    m_queueState.m_InFrameRender = false;\n}\n\nvoid Renderer::DrawOp(RenderOp &op)\n{\n    if((op.m_selectedCamera == 0 || op.m_selectedCamera == m_queueState.m_curCamIdx) && (op.m_FramesLeft >= 1 || GamePaused != PauseCode::None))\n        op.Draw(this);\n}\n\n\nbool Render::IsOnScreen(int x, int y, int w, int h)\n{\n    int cam_x = vScreen[1].CameraAddX_i();\n    int cam_y = vScreen[1].CameraAddY_i();\n    int cam_w = vScreen[1].Width;\n    int cam_h = vScreen[1].Height;\n\n    return FastTestCollision((int)cam_x, (int)cam_y, (int)cam_x + (int)cam_w, (int)cam_y + (int)cam_h,\n                             (int)x, (int)y, (int)x + (int)w, (int)y + (int)h);\n}\n\n#if 0\nvoid Render::CalcCameraPos(double *ret_x, double *ret_y)\n{\n    // Old camera func, using \"camera\" memory\n    double val;\n\n    if(ret_x != nullptr)\n    {\n        val = vScreen[1].X;\n        *ret_x = val - val - val; // Fix backwards smbx camera\n    }\n\n    if(ret_y != nullptr)\n    {\n        val = vScreen[1].Y;\n        *ret_y = val - val - val; // Fix backwards smbx camera\n    }\n}\n#endif\n\nvoid Render::TranslateScreenCoords(int &x, int &y, int w, int h)\n{\n    // FIXME: What we shall to do with w and h?\n    UNUSED(w);\n    UNUSED(h);\n\n    if(g_config.autocode_translate_coords)\n    {\n        int top = 0;\n        if(vScreen[1].Height > 600)\n            top = vScreen[1].Height / 2 - 300;\n        int left = vScreen[1].Width / 2 - 400;\n\n        x += left;\n        y += top;\n\n        if(vScreen[1].Height < 600)\n            y = (y * vScreen[1].Height) / 600;\n    }\n}\n"
  },
  {
    "path": "src/script/luna/lunarender.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNARENDER_H\n#define LUNARENDER_H\n\n#include \"numeric_types.h\"\n\n#include <unordered_map>\n#include <vector>\n#include <string>\n#include <list>\n#include <Allocator/PoolAllocator.h>\n\n#include \"draw_planes.h\"\n#include \"lunaimgbox.h\"\n\nclass RenderOp;\nclass LunaImage;\n\nconstexpr size_t c_rAllocChunkSize = 96;\nconstexpr size_t c_rAllocTotalSize = c_rAllocChunkSize * 1000;\nextern PoolAllocator g_rAlloc;\n\nstruct Renderer\n{\n    static Renderer &Get();\n    static void SetAltThread();\n    static void UnsetAltThread();\n    static bool IsAltThreadActive();\n\n    Renderer() noexcept;\n    ~Renderer();\n\n    bool LoadBitmapResource(const std::string &filename, int resource_code, uint32_t transparency_color); // don't give full path\n    bool LoadBitmapResource(const std::string &filename, int resource_code);\n    void StoreImage(LunaImage&& bmp, int resource_code);\n    bool DeleteImage(int resource_code);\n    LunaImage *GetImageForResourceCode(int resource_code);\n\n    void AddOp(RenderOp *op);                           // Add a drawing operation to the list\n    // void GLCmd(const std::shared_ptr<GLEngineCmd>& cmd, double renderPriority = 1.0);\n\n    void DebugPrint(const std::string &message);                // Print a debug message on the screen\n    void DebugPrint(const std::string &message, num_t val);    // Print a debug message on the screen and display a related value\n\n    void RenderBelowPriority(PLANE maxPriority);\n\n    void ClearAllDebugMessages();\n\n    void ClearAllLoadedImages();\n\n    // Calls from hooks\n    void StartCameraRender(int idx);\n    void StoreCameraPosition(int idx);\n\n    // Render logic\n    void StartRenderLogic();\n    void EndRenderLogic();\n\n    // Render begin/end\n    void StartFrameRender();\n    void EndFrameRender();\n\n    void ClearQueue();\nprivate:\n    void DrawOp(RenderOp &render_operation);\n\n\n    // Members //\npublic:\n    class QueueState\n    {\n    public:\n        bool m_InFrameRender;\n        int m_curCamIdx; // Camera state\n\n        std::size_t m_renderOpsSortedCount;\n        std::size_t m_renderOpsProcessedCount;\n        std::vector<RenderOp *> m_currentRenderOps; // render operations to be performed\n\n        std::list<std::string> m_debugMessages;    // Debug message to be printed\n\n    public:\n        QueueState() :\n            m_InFrameRender(false),\n            m_curCamIdx(1),\n            m_renderOpsSortedCount(0),\n            m_renderOpsProcessedCount(0),\n            m_currentRenderOps(),\n            m_debugMessages()\n        {}\n    };\n\nprivate:\n    QueueState m_queueState;\n    std::unordered_map<int, LunaImage> m_legacyResourceCodeImages;  // loaded image resources\n\n    // Simple getters //\npublic:\n    int GetCameraIdx() const\n    {\n        return m_queueState.m_curCamIdx;\n    }\n    // HDC GetScreenDC() { return (HDC)GM_SCRN_HDC; }\n\n    class QueueStateStacker\n    {\n    private:\n        Renderer &m_renderer;\n        QueueState m_savedState;\n\n    public:\n        QueueStateStacker() :\n            m_renderer(Renderer::Get())\n        {\n            m_savedState = m_renderer.m_queueState;\n            // Don't use m_renderer.ClearQueue() for this because we're effectively moving things and ClearQueue frees some pointers\n            m_renderer.m_queueState.m_InFrameRender = false;\n            m_renderer.m_queueState.m_curCamIdx = 1;\n            m_renderer.m_queueState.m_renderOpsSortedCount = 0;\n            m_renderer.m_queueState.m_renderOpsProcessedCount = 0;\n            m_renderer.m_queueState.m_currentRenderOps.clear();\n            m_renderer.m_queueState.m_debugMessages.clear();\n        }\n\n        ~QueueStateStacker()\n        {\n            m_renderer.ClearQueue();\n            m_renderer.m_queueState = m_savedState;\n        }\n    };\n};\n\nnamespace Render\n{\n\nbool IsOnScreen(int x, int y, int w, int h);              // Returns whether or not the digven rectangle is on screen this frame\n// void CalcCameraPos(double *p_X, double *p_Y);                         // Tries to read smbx memory to return the camera coords in the 2 passed args\nvoid TranslateScreenCoords(int &x, int &y, int w, int h); // Converts 800x600 coordinates to dynamic-res coordinates, depending on config options\n\n}\n\n\n#endif // LUNARENDER_H\n"
  },
  {
    "path": "src/script/luna/lunaspriteman.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunaspriteman.h\"\n#include \"csprite.h\"\n#include \"lunarender.h\"\n#include \"sprite_funcs.h\"\n#include \"lunaplayer.h\"\n#include \"lunamisc.h\"\n#include \"lunaimgbox.h\"\n#include \"game_main.h\" // GamePaused\n\n#include \"globals.h\"\n\n\nCSpriteManager gSpriteMan;\n\n\nvoid CSpriteManager::ResetSpriteManager()\n{\n    ClearAllSprites();\n    m_ComponentList.clear();\n}\n\nvoid CSpriteManager::BasicInit(CSprite *spr, CSpriteRequest *pReq, bool center)\n{\n    spr->m_Xpos = pReq->x;\n    spr->m_Ypos = pReq->y;\n    //spr->m_StaticScreenPos = false;\n    spr->SetImageResource(pReq->img_resource_code);\n    spr->SetImage(pReq->direct_img);\n    InitializeDimensions(spr, center);\n    if(pReq->time != 0)\n        spr->MakeLimitedLifetime(pReq->time);\n}\n\nvoid CSpriteManager::InitializeDimensions(CSprite *spr, bool center_coords)\n{\n    const LunaImage *box = spr->m_directImg;\n\n    if(!box)\n        box = Renderer::Get().GetImageForResourceCode(spr->m_ImgResCode);\n\n    if(box)\n    {\n        LunaRect rect;\n        rect.left = 0;\n        rect.top = 0;\n        rect.right = box->getW();\n        rect.bottom = box->getH();\n        spr->m_GfxRects.clear();\n        spr->m_GfxRects.push_back(rect);\n\n        spr->m_Ht = rect.bottom;\n        spr->m_Wd = rect.right;\n        spr->m_AnimationSet = false;\n\n        spr->m_Hitbox.Left_off = 0;\n        spr->m_Hitbox.Top_off = 0;\n        spr->m_Hitbox.W = (short)rect.right;\n        spr->m_Hitbox.H = (short)rect.bottom;\n        spr->m_Hitbox.CollisionType = 0;\n        spr->m_Hitbox.pParent = spr;\n\n        if(center_coords) // Fix to generate with x/y as center instead of minimum left/top\n        {\n            spr->m_Xpos -= spr->m_Wd * 0.5_n;\n            spr->m_Ypos -= spr->m_Ht * 0.5_n;\n        }\n    }\n}\n\nvoid CSpriteManager::InstantiateSprite(CSpriteRequest *req, bool center_coords)\n{\n    CSprite *spr = nullptr;\n\n    // For built in sprites\n    if(req != nullptr && req->type != (BUILTIN_SPRITE_TYPE)BST_Custom)\n    {\n        switch(req->type)\n        {\n\n        // STATIC SPRITE\n        case BST_Static:\n        {\n            spr = new CSprite;\n            BasicInit(spr, req, center_coords);\n            spr->m_StaticScreenPos = true;\n            spr->AddDrawComponent(&SpriteFunc::StaticDraw);\n            break;\n        }\n\n        // NORMAL SPRITE\n        case BST_Normal:\n        {\n            spr = new CSprite;\n            BasicInit(spr, req, center_coords);\n            spr->AddDrawComponent(&SpriteFunc::RelativeDraw);\n            break;\n        }\n\n        // ITEM SPRITE\n        case BST_Item:\n        {\n            spr = new CSprite;\n            BasicInit(spr, req, center_coords);\n            spr->AddDrawComponent(&SpriteFunc::RelativeDraw);\n\n            // Add collectible event # if applicable\n            int str_arg = std::atoi(req->str.c_str());\n            if(str_arg > 21)\n            {\n                SpriteComponent comp;                       // Add collectible component\n                comp.func = SpriteFunc::PlayerCollectible;\n                spr->AddBehaviorComponent(comp);\n\n//                if(str_arg > 21) // Useless condition as it's already true from above\n//                {\n                // Add trigger event on death\n                comp.func = SpriteFunc::TriggerLunaEvent;\n                comp.data1 = str_arg;\n                spr->AddDeathComponent(comp);\n//                }\n\n            }\n            break;\n        }\n\n        // PROGRESS BAR\n        case BST_Bar:\n        {\n            //spr = new CSprite;\n            //TODO: MAKE IT\n            spr = nullptr;\n            // WHO ATTEMPTED TO USE THE NULL POINTER? EXECUTE!\n            // (Joke! It's all my fault that I left this code piece taken from LunaDLL being uncommented)\n            //spr->m_Xpos = req->x;\n            //spr->m_Ypos = req->y;\n            break;\n        }\n\n        // PHANTO\n        case BST_Phanto:\n        {\n            // Need to add phanto to blueprints?\n            if(m_SpriteBlueprints.find(\"__DefaultPhanto\") == m_SpriteBlueprints.end())\n            {\n                spr = new CSprite;\n                SpriteComponent comp;\n                BasicInit(spr, req, center_coords);\n                spr->AddDrawComponent(&SpriteFunc::RelativeDraw);\n\n                // Always Decelerate\n                comp.Init(0);\n                comp.func = SpriteFunc::Deccelerate;\n                comp.data1 = 0.04_n;\n                comp.data2 = 0.04_n;\n                comp.data3 = 0.00_n;\n                spr->AddBehaviorComponent(comp);\n\n                // Wait for player to have key -- activate 100\n                comp.Init(0);\n                comp.func = SpriteFunc::PlayerHoldingSprite;\n                comp.data1 = 31; // key ID\n                comp.data4 = 100; // activates 100\n                spr->AddBehaviorComponent(comp);\n\n                comp.Init(0);\n                comp.func = SpriteFunc::PlayerHoldingSprite;\n                comp.data1 = 0x0E; // key ID alternate\n                comp.data4 = 100; // activates 100\n                spr->AddBehaviorComponent(comp);\n\n                comp.Init(0); // Phase Move\n                comp.func = SpriteFunc::PhaseMove;\n                //comp.lookup_code;\n                spr->AddBehaviorComponent(comp);\n\n                // #100 -- Chase player / active mode\n                comp.Init(1);\n                comp.func = SpriteFunc::AccelToPlayer;\n                comp.data1 = 0.11_n;\n                comp.data2 = 0.11_n;\n                comp.data3 = 5.6_n;\n                comp.lookup_code = 100;\n                gSpriteMan.m_ComponentList.push_back(comp);\n\n                comp.Init(1); // Check for collision -- activate 101\n                comp.func = SpriteFunc::OnPlayerCollide;\n                comp.data4 = 101; // harm player\n                comp.lookup_code = 100;\n                gSpriteMan.m_ComponentList.push_back(comp);\n\n                comp.Init(1); // Check for too much distance from player -- activate 102\n                comp.func = SpriteFunc::OnPlayerDistance;\n                comp.data1 = 3000;\n                comp.data4 = 102; // tele near player\n                comp.lookup_code = 100;\n                gSpriteMan.m_ComponentList.push_back(comp);\n\n                // #101 -- harm player\n                comp.Init(1); // Check for too much distance from player\n                comp.func = SpriteFunc::HarmPlayer;\n                comp.lookup_code = 101;\n                gSpriteMan.m_ComponentList.push_back(comp);\n\n                // #102 -- teleport near player\n                comp.Init(1); // Check for too much distance from player\n                comp.func = SpriteFunc::TeleportNearPlayer;\n                comp.data1 = 1000;\n                comp.lookup_code = 102;\n                gSpriteMan.m_ComponentList.push_back(comp);\n                m_SpriteBlueprints[\"__DefaultPhanto\"] = spr;\n            }\n\n            const char *defPhant = \"__DefaultPhanto\";\n            CSprite *from_bp = CopyFromBlueprint(defPhant);\n            from_bp->m_Xpos = req->x;\n            from_bp->m_Ypos = req->y;\n            from_bp->SetImageResource(req->img_resource_code);\n            from_bp->SetImage(req->direct_img);\n            InitializeDimensions(from_bp, center_coords);\n            if(req->time != 0)\n                from_bp->MakeLimitedLifetime(req->time);\n            spr = from_bp;\n            break;\n        }\n\n        } //< Switch\n\n        // Add the sprite\n        if(spr != nullptr)\n            AddSprite(spr);\n    }\n    // Else, instantiate custom sprite?\n    else if(req != nullptr && req->type == BST_Custom)\n    {\n        CSprite *from_bp = CopyFromBlueprint(const_cast<char *>(req->str.c_str()));\n\n        if(from_bp)\n        {\n            from_bp->m_Xpos = req->x;\n            from_bp->m_Ypos = req->y;\n            from_bp->m_Xspd = req->x_speed;\n            from_bp->m_Yspd = req->y_speed;\n            from_bp->SetImageResource(req->img_resource_code);\n            from_bp->SetImage(req->direct_img);\n            InitializeDimensions(from_bp, center_coords);\n            if(req->time != 0)\n                from_bp->MakeLimitedLifetime(req->time);\n            from_bp->Birth();\n            AddSprite(from_bp);\n        }\n    }\n}\n\nvoid CSpriteManager::AddBlueprint(const char *blueprint_name, CSprite *spr)\n{\n    m_SpriteBlueprints[blueprint_name] = spr;\n}\n\nCSprite *CSpriteManager::CopyFromBlueprint(const char *blueprint_name)\n{\n    if(m_SpriteBlueprints.find(blueprint_name) != m_SpriteBlueprints.end())\n    {\n        auto *newspr = new CSprite;\n        *newspr = *m_SpriteBlueprints[blueprint_name];\n        return newspr;\n    }\n    else\n        return nullptr;\n}\n\nvoid CSpriteManager::RunSprites()\n{\n    ClearInvalidSprites();\n    Player_t *demo = PlayerF::Get(1);\n\n    if(demo && GamePaused == PauseCode::None)\n    {\n        // Process each\n        for(auto &iter : m_SpriteList)\n        {\n            if(!iter->m_Invalidated)  // Don't process invalids\n            {\n                if(ComputeLevelSection((int)iter->m_Xpos, (int)iter->m_Ypos) == demo->Section + 1 ||\n                   iter->m_AlwaysProcess || iter->m_StaticScreenPos)   // Valid level section to process in?\n                    iter->Process();\n            }\n            else\n                m_hasInvalid = true;\n        }\n\n        // Draw each\n        for(auto &iter : m_SpriteList)\n        {\n            if(!iter->m_Invalidated)\n            {\n                if(iter->m_StaticScreenPos || Render::IsOnScreen((int)iter->m_Xpos, (int)iter->m_Ypos, (int)iter->m_Wd, (int)iter->m_Ht))\n                    iter->Draw();\n            }\n            else\n                m_hasInvalid = true;\n        }\n    }\n}\n\nvoid CSpriteManager::ClearInvalidSprites()\n{\n    if(!m_hasInvalid)\n        return;\n\n    auto iter = m_SpriteList.begin();\n    // std::list<CSprite*>::iterator end = m_SpriteList.end();\n\n    while(iter != m_SpriteList.end())\n    {\n        //CSprite* spr = *iter;\n        if((*iter)->m_Invalidated)\n        {\n            delete(*iter);\n            iter = m_SpriteList.erase(iter);\n        }\n        else\n            ++iter;\n    }\n\n    m_hasInvalid = false;\n}\n\nvoid CSpriteManager::ClearAllSprites()\n{\n    while(!m_SpriteList.empty())\n    {\n        delete m_SpriteList.back();\n        m_SpriteList.pop_back();\n    }\n}\n\nvoid CSpriteManager::ClearSprites(int imgResourceCode, int xPos, int yPos)\n{\n    auto iter = m_SpriteList.begin();\n    //    std::list<CSprite*>::iterator end = m_SpriteList.end();\n\n    while(iter != m_SpriteList.end())\n    {\n        //CSprite* spr = *iter;\n        CSprite *next = *iter;\n        if(next->m_ImgResCode == imgResourceCode && (int)next->m_Xpos == xPos && (int)next->m_Ypos == yPos)\n        {\n            delete(*iter);\n            iter = m_SpriteList.erase(iter);\n        }\n        else\n            ++iter;\n    }\n}\n\nvoid CSpriteManager::ClearSprites(int imgResourceCode)\n{\n    auto iter = m_SpriteList.begin();\n    //    std::list<CSprite*>::iterator end = m_SpriteList.end();\n\n    while(iter != m_SpriteList.end())\n    {\n        //CSprite* spr = *iter;\n        CSprite *next = *iter;\n\n        if(!next->m_directImg && next->m_ImgResCode == imgResourceCode)\n        {\n            delete(*iter);\n            iter = m_SpriteList.erase(iter);\n        }\n        else\n            ++iter;\n    }\n}\n\nvoid CSpriteManager::ClearSprites(LunaImage *img, int xPos, int yPos)\n{\n    std::list<CSprite *>::iterator iter = m_SpriteList.begin();\n    //    std::list<CSprite*>::iterator end = m_SpriteList.end();\n\n    while(iter != m_SpriteList.end())\n    {\n        //CSprite* spr = *iter;\n        CSprite *next = *iter;\n        if(next->m_directImg && next->m_directImg->getUID() == img->getUID() &&\n           (int)next->m_Xpos == xPos && (int)next->m_Ypos == yPos)\n        {\n            delete(*iter);\n            iter = m_SpriteList.erase(iter);\n        }\n        else\n            ++iter;\n    }\n}\n\nvoid CSpriteManager::ClearSprites(LunaImage *img)\n{\n    std::list<CSprite *>::iterator iter = m_SpriteList.begin();\n    // std::list<CSprite*>::iterator end = m_SpriteList.end();\n\n    while(iter != m_SpriteList.end())\n    {\n        //CSprite* spr = *iter;\n        CSprite *next = *iter;\n        if(next->m_directImg->getUID() == img->getUID())\n        {\n            delete(*iter);\n            iter = m_SpriteList.erase(iter);\n        }\n        else\n            ++iter;\n    }\n}\n\nvoid CSpriteManager::AddSprite(CSprite *spr)\n{\n    m_SpriteList.push_back(spr);\n    if(spr->m_Invalidated)\n        m_hasInvalid = true;\n}\n\nvoid CSpriteManager::GetComponents(int code, std::list<SpriteComponent *> *component_list)\n{\n    for(auto &iter : m_ComponentList)\n    {\n        // Lookup components that match the code\n        if(iter.lookup_code == code)\n            component_list->push_back(&iter);\n    }\n}\n"
  },
  {
    "path": "src/script/luna/lunaspriteman.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef CSPRITEMANAGER_H\n#define CSPRITEMANAGER_H\n\n#include <list>\n#include <map>\n#include <list>\n\n#include \"sprite_component.h\"\n\n\nclass LunaImage;\n\n////////////////////////////\n/// BUILTIN SPRITE TYPES /// ****************************************************************************************************\n////////////////////////////\n//\n// 1 = Static / HUD sprite      1: Type     2: Img code     3: Xpos     4: Ypos     5: Active lifetime  6:\n// 2 = \"In level\" sprite        1: Type     2: Img code     3: Xpos     4: Ypos     5: Active lifetime  6:\n// 3 = Collectible item         1: Type     2: Img code     3: Xpos     4: Ypos     5: Active lifetime  6: Lundll event to trigger\n// 4 = Progress bar             1: Type     2: Img code     3: Xpos     4: Ypos     5: Active lifetime  6:\n// 5 = Phanto                   1: Type     2: Img code     3: Xpos     4: Ypos     5: Active lifetime  6:\n//\n//////////////////////////// ****************************************************************************************************\n\nenum BUILTIN_SPRITE_TYPE\n{\n    BST_Custom = 0,\n    BST_Static = 1,\n    BST_Normal = 2,\n    BST_Item = 3,\n    BST_Bar = 4,\n    BST_Phanto\n};\n\n\nstruct CSpriteRequest;\nclass CSprite;\n\n// Basic manager class for sprites\nstruct CSpriteManager\n{\n    void ResetSpriteManager();                                          // Will be called on level load to re-init everything\n\n    void BasicInit(CSprite *spr, CSpriteRequest *req, bool center);     // Quickly set frequently-used sprite attributes\n    void InitializeDimensions(CSprite *spr, bool center_coords);        // Set hitbox and dimensions to size of image\n    void InstantiateSprite(CSpriteRequest *req, bool center_coords);    // Officially place sprite in level (m_SpriteList)\n\n    void AddBlueprint(const char *blueprint_name, CSprite *spr);\n    CSprite *CopyFromBlueprint(const char *blueprint_name);// Generate new sprite based on blueprint\n\n    void RunSprites();\n    int CountSprites() const\n    {\n        return (int)m_SpriteList.size();\n    }\n\n    int CountBlueprints() const\n    {\n        return (int)m_SpriteBlueprints.size();\n    }\n\n    int CountComponents() const\n    {\n        return (int)m_ComponentList.size();\n    }\n\n    void ClearInvalidSprites(); // Don't call while iterating\n    void ClearAllSprites();\n    void ClearSprites(int imgResourceCode, int xPos, int yPos);\n    void ClearSprites(int imgResourceCode);\n    void ClearSprites(LunaImage *img, int xPos, int yPos);\n    void ClearSprites(LunaImage *img);\n\n    void AddSprite(CSprite *spr);\n\n    void GetComponents(int code, std::list<SpriteComponent *> *component_list); // Get components with the given code #\n\n    std::list<CSprite *> m_SpriteList;\n    std::map<std::string, CSprite *> m_SpriteBlueprints;\n    std::list<SpriteComponent> m_ComponentList;     // User components that can be copied (activated) into a sprite's behavior list\n    bool m_hasInvalid = false;\n};\n\nextern CSpriteManager gSpriteMan;\n\n#endif // CSPRITEMANAGER_H\n"
  },
  {
    "path": "src/script/luna/lunavarbank.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"lunavarbank.h\"\n#include \"lunadefs.h\"\n#include \"globals.h\"\n#include <fmt_format_ne.h>\n#include <Utils/files.h>\n\nsaveUserData::DataSection gLunaVarBank;\nSavedVariableBank gSavedVarBank;\n\n#define SPECIAL_SAVE_STR \"__LunaVarBankSpecialCounter\"\n\n// INIT\nvoid SavedVariableBank::Init()\n{}\n\n// TRY LOAD WORLD VARS\nSavedVariableBank::SavedVariableBank()\n{\n    Init();\n}\n\nbool SavedVariableBank::TryLoadWorldVars()\n{\n    if(selSave <= 0 || selSave > maxSaveSlots)\n        return false;\n\n    ClearBank();\n    ImportBank();\n\n    return true;\n}\n\n// INIT SAVE FILE\nvoid SavedVariableBank::InitSaveFile()\n{\n    const char *lunaKey = \"__Lunadll_Version\";\n    if(m_VarBank.find(lunaKey) == m_VarBank.end())\n        m_VarBank.insert({lunaKey, LUNA_VERSION});\n}\n\nvoid SavedVariableBank::ImportBank()\n{\n    if(gLunaVarBank.data.empty())\n        InitSaveFile();\n\n    for(auto &d : gLunaVarBank.data)\n        SetVar(d.key, num_t::from_double(std::atof(d.value.c_str())));\n\n    lastStarsNumber = num_t::round(GetVar(SPECIAL_SAVE_STR));\n}\n\n// SET VAR\nvoid SavedVariableBank::SetVar(const std::string &k, num_t v)\n{\n    m_VarBank[k] = v;\n}\n\n// GET VAR\nnum_t SavedVariableBank::GetVar(const std::string &key)\n{\n    if(!VarExists(key))\n        return 0;\n    return m_VarBank[key];\n}\n\n// VAR EXISTS\nbool SavedVariableBank::VarExists(const std::string &k)\n{\n    if(m_VarBank.find(k) == m_VarBank.end())\n        return false;\n    return true;\n}\n\n// CLEAR BANK\nvoid SavedVariableBank::ClearBank()\n{\n    lastStarsNumber = 0;\n    m_VarBank.clear();\n}\n\n// WRITE BANK\nvoid SavedVariableBank::WriteBank()\n{\n    if(selSave <= 0 || selSave > maxSaveSlots)\n        return;\n\n    gLunaVarBank.name = \"LunaDLL\";\n    gLunaVarBank.location = saveUserData::DATA_GLOBAL;\n    gLunaVarBank.data.clear();\n\n    for(auto &it : m_VarBank)\n    {\n        saveUserData::DataEntry de;\n        de.key = it.first;\n        de.value = fmt::format_ne(\"{0}\", (double)it.second);\n        gLunaVarBank.data.push_back(std::move(de));\n    }\n}\n\n// SaveIfNeeded\nvoid SavedVariableBank::SaveIfNeeded()\n{\n    // Basically, force save if the player collected a star\n    if(numStars > lastStarsNumber)\n    {\n        lastStarsNumber = numStars;\n        SetVar(SPECIAL_SAVE_STR, numStars);\n        WriteBank();\n    }\n}\n\n//COPY BANK\nvoid SavedVariableBank::CopyBank(std::map<std::string, num_t> *target_map)\n{\n    if(!target_map)\n        return;\n\n    for(auto &it : m_VarBank)\n    {\n        auto n = (*target_map).find(it.first);\n        if(n == (*target_map).end())\n            (*target_map).insert(it);\n        else\n            n->second = it.second;\n    }\n}\n"
  },
  {
    "path": "src/script/luna/lunavarbank.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef LUNAVARBANK_H\n#define LUNAVARBANK_H\n\n#include \"numeric_types.h\"\n\n#include <string>\n#include <map>\n#include <PGE_File_Formats/save_filedata.h>\n\nextern saveUserData::DataSection gLunaVarBank;\n\n// -- Custom user variables saving/loading manager --\n// How it works: (On level load) Reads global \"LunaDLL\" section from the game save and reads all vars into object's local variable bank\n// Whole variable bank will be written back to the gamesave when calling WriteBank\n// Object is basically a wrapper around a map with some file management functions\nclass SavedVariableBank\n{\n    int lastStarsNumber = 0;\npublic:\n    SavedVariableBank();\n\n    /*!\n     * \\brief Try to read in the saved variables for this world & current save slot\n     * \\return True on success, false on error\n     */\n    bool TryLoadWorldVars();\n\n    /*!\n     * \\brief Sets or adds a key/value pair to the bank\n     * \\param key Key\n     * \\param val Value\n     */\n    void SetVar(const std::string &key, num_t val);\n\n    /*!\n     * \\brief Returns whether or not this var exists in the bank\n     * \\param key Key\n     * \\return true if exists\n     */\n    bool VarExists(const std::string &key);\n\n    /*!\n     * \\brief Get value of a key, or 0 if key not found\n     * \\param key Key\n     * \\return Value\n     */\n    num_t GetVar(const std::string &key);\n\n    /*!\n     * \\brief Copy all k,v pairs of variable bank to another map\n     * \\param target Destination map of copy\n     */\n    void CopyBank(std::map<std::string, num_t> *target);\n\n    void ClearBank();\n\n    /*!\n     * \\brief Save vars by writing the current bank back to world folder\n     */\n    void WriteBank();\n\n    /*!\n     * \\brief Saves if it detects the player has collected a new star\n     */\n    void SaveIfNeeded();\n\n    /// Members ///\n    std::map<std::string, num_t> m_VarBank;\n\nprivate:\n    // Init the object\n    void Init();\n    // Init a new save file\n    void InitSaveFile();\n    // Read all vars from open file into bank\n    void ImportBank();\n};\n\nextern SavedVariableBank gSavedVarBank;\n\n#endif // LUNAVARBANK_H\n"
  },
  {
    "path": "src/script/luna/mememu.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <Logger/logger.h>\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"mememu.h\"\n#include \"globals.h\"\n#include \"global_constants.h\"\n#include \"layers.h\"\n#include \"config.h\"\n#include \"game_main.h\" // GamePaused\n#include \"main/trees.h\" // treeNPCUpdate\n#include \"npc/npc_queues.h\"\n#include \"main/cheat_code.h\"\n\n#include <unordered_map>\n#include <functional>\n#include <type_traits>\n#include <limits>\n\n\n#if defined(arm) && !defined(__SOFTFP__) && !defined(__VFP_FP__) && !defined(__MAVERICK__)\n#   define ARM_BIDI_ENDIAN\n//inline void swap_halfes(uint64_t &x)\n//{\n//    uint64_t y = ((x & 0xFFFFFFFF00000000) >> 32) & 0xFFFFFFFF;\n//    x = ((x << 32) & 0xFFFFFFFF00000000) & y;\n//}\n//#else // Do nothing\n//#   define swap_halfes(x)\n#endif\n\n\n#if 0 // Unused yet\nSDL_FORCE_INLINE void toX86Endian(num_t in_d, uint8_t out[8])\n{\n    auto *in = reinterpret_cast<uint8_t*>(&in_d);\n\n#if defined(THEXTECH_BIG_ENDIAN)\n    out[0] = in[7];\n    out[1] = in[6];\n    out[2] = in[5];\n    out[3] = in[4];\n    out[4] = in[3];\n    out[5] = in[2];\n    out[6] = in[1];\n    out[7] = in[0];\n#elif defined(ARM_BIDI_ENDIAN) // some old devices\n    out[4] = in[0];\n    out[5] = in[1];\n    out[6] = in[2];\n    out[7] = in[3];\n    out[0] = in[4];\n    out[1] = in[5];\n    out[2] = in[6];\n    out[3] = in[7];\n#else // normal little endian\n    out[0] = in[0];\n    out[1] = in[1];\n    out[2] = in[2];\n    out[3] = in[3];\n    out[4] = in[4];\n    out[5] = in[5];\n    out[6] = in[6];\n    out[7] = in[7];\n#endif\n}\n\nSDL_FORCE_INLINE void fromX86Endian(const uint8_t in[8], num_t &out_d)\n{\n    auto *out = reinterpret_cast<uint8_t*>(&out_d);\n\n#if defined(THEXTECH_BIG_ENDIAN)\n    out[0] = in[7];\n    out[1] = in[6];\n    out[2] = in[5];\n    out[3] = in[4];\n    out[4] = in[3];\n    out[5] = in[2];\n    out[6] = in[1];\n    out[7] = in[0];\n#elif defined(ARM_BIDI_ENDIAN) // some old devices\n    out[4] = in[0];\n    out[5] = in[1];\n    out[6] = in[2];\n    out[7] = in[3];\n    out[0] = in[4];\n    out[1] = in[5];\n    out[2] = in[6];\n    out[3] = in[7];\n#else // normal little endian\n    out[0] = in[0];\n    out[1] = in[1];\n    out[2] = in[2];\n    out[3] = in[3];\n    out[4] = in[4];\n    out[5] = in[5];\n    out[6] = in[6];\n    out[7] = in[7];\n#endif\n}\n#endif\n\n\ntemplate<class T>\nT f2i_cast(num_t in)\n{\n#ifndef THEXTECH_FIXED_POINT\n    if(std::is_same<T, uint64_t>::value)\n    {\n        if(in < static_cast<num_t>(std::numeric_limits<uint64_t>::min()) || (in > 2.0_n * 0x8000000000000000))\n            in = num_t::from_double(SDL_fmod((double)in, 2.0 * 0x8000000000000000));\n        return static_cast<T>(in);\n    }\n\n    if(in < static_cast<num_t>(std::numeric_limits<int64_t>::min()) || in > static_cast<num_t>(std::numeric_limits<int64_t>::max()))\n        in = num_t::from_double(SDL_fmod((double)in, 0x8000000000000000));\n#endif\n\n    return static_cast<T>(static_cast<int64_t>(in));\n}\n\nSDL_FORCE_INLINE void modifyByteX86(num_t &dst, size_t byte, uint8_t data)\n{\n#ifdef THEXTECH_FIXED_POINT\n    return;\n#endif\n\n    auto *in = reinterpret_cast<uint8_t*>(&dst);\n    SDL_assert(byte < 8);\n\n#if defined(THEXTECH_BIG_ENDIAN)\n    in[7 - byte] = data;\n#elif defined(ARM_BIDI_ENDIAN) // some old devices\n    byte += (byte < 4) ? +4 : -4;\n    in[byte] = data;\n#else // normal little endian\n    in[byte] = data;\n#endif\n}\n\nSDL_FORCE_INLINE void modifyByteX86(numf_t &dst, size_t byte, uint8_t data)\n{\n#ifdef THEXTECH_FIXED_POINT\n    return;\n#endif\n\n    auto *in = reinterpret_cast<uint8_t*>(&dst);\n    SDL_assert(byte < 4);\n\n#if defined(THEXTECH_BIG_ENDIAN)\n    in[3 - byte] = data;\n#else // normal little endian\n    in[byte] = data;\n#endif\n}\n\nSDL_FORCE_INLINE void modifyByteX86(int16_t &dst, size_t byte, uint8_t data)\n{\n    auto *in = reinterpret_cast<uint8_t*>(&dst);\n    SDL_assert(byte < 2);\n\n#if defined(THEXTECH_BIG_ENDIAN)\n    in[1 - byte] = data;\n#else // normal little endian\n    in[byte] = data;\n#endif\n}\n\n\nSDL_FORCE_INLINE uint8_t getByteX86(const num_t &src, size_t byte)\n{\n#ifdef THEXTECH_FIXED_POINT\n    return 0;\n#endif\n\n    const auto *in = reinterpret_cast<const uint8_t*>(&src);\n    SDL_assert(byte < 8);\n#if defined(THEXTECH_BIG_ENDIAN)\n    return in[7 - byte];\n#elif defined(ARM_BIDI_ENDIAN) // some old devices\n    byte += (byte < 4) ? +4 : -4;\n    return in[byte];\n#else // normal little endian\n    return in[byte];\n#endif\n}\n\nSDL_FORCE_INLINE uint8_t getByteX86(const numf_t &src, size_t byte)\n{\n#ifdef THEXTECH_FIXED_POINT\n    return 0;\n#endif\n\n    const auto *in = reinterpret_cast<const uint8_t*>(&src);\n    SDL_assert(byte < 4);\n#if defined(THEXTECH_BIG_ENDIAN)\n    return in[3 - byte];\n#else // normal little endian\n    return in[byte];\n#endif\n}\n\nSDL_FORCE_INLINE uint8_t getByteX86(const int16_t &src, size_t byte)\n{\n    const auto *in = reinterpret_cast<const uint8_t*>(&src);\n    SDL_assert(byte < 2);\n#if defined(THEXTECH_BIG_ENDIAN)\n    return in[1 - byte];\n#else // normal little endian\n    return in[byte];\n#endif\n}\n\n\n/*----------------------------------------------*\n *          Write memory value                  *\n *----------------------------------------------*/\n\nSDL_FORCE_INLINE void memToValue(num_t &target, num_t value, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        target =static_cast<num_t>(f2i_cast<uint8_t>(value));\n        break;\n    case FT_WORD:\n        target =static_cast<num_t>(static_cast<int16_t>(f2i_cast<uint16_t>(value)));\n        break;\n    case FT_DWORD:\n        target =static_cast<num_t>(f2i_cast<int32_t>(value));\n        break;\n    case FT_FLOAT:\n        target =static_cast<num_t>(static_cast<numf_t>(value));\n        break;\n    case FT_DFLOAT:\n        target = value;\n        break;\n    default:\n        break; // Don't change\n    }\n}\n\nSDL_FORCE_INLINE void memToValue(numf_t &target, num_t value, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        target = static_cast<numf_t>(f2i_cast<uint8_t>(value));\n        break;\n    case FT_WORD:\n        target = static_cast<numf_t>(static_cast<int16_t>(f2i_cast<uint16_t>(value)));\n        break;\n    case FT_DWORD:\n        target = static_cast<numf_t>(f2i_cast<int32_t>(value));\n        break;\n    case FT_FLOAT:\n        target = static_cast<numf_t>(value);\n        break;\n    case FT_DFLOAT:\n        target = static_cast<numf_t>(value);\n        break;\n    default: //Don't change\n        break;\n    }\n}\n\nSDL_FORCE_INLINE void memToValue(int &target, num_t value, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        target = static_cast<int32_t>(f2i_cast<uint8_t>(value));\n        break;\n    case FT_WORD:\n        target = static_cast<int32_t>(static_cast<int16_t>(f2i_cast<uint16_t>(value)));\n        break;\n    case FT_DWORD:\n    case FT_DFLOAT:\n        target = static_cast<int32_t>(value);\n        break;\n    case FT_FLOAT:\n        target = static_cast<int32_t>(f2i_cast<tempf_t>(value));\n        break;\n//    case FT_DFLOAT: // United with FT_DWORD\n//        target = static_cast<int32_t>(value);\n//        break;\n    default: //Don't change\n        break;\n    }\n}\n\nSDL_FORCE_INLINE void memToValue(short &target, num_t value, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        target = static_cast<int16_t>(f2i_cast<uint8_t>(value));\n        break;\n    case FT_WORD:\n        target = static_cast<int16_t>(static_cast<int16_t>(f2i_cast<uint16_t>(value)));\n        break;\n    case FT_DWORD:\n    case FT_DFLOAT:\n        target = static_cast<int16_t>(value);\n        break;\n    case FT_FLOAT:\n        target = static_cast<int16_t>(f2i_cast<tempf_t>(value));\n        break;\n//    case FT_DFLOAT: // United with FT_DWORD\n//        target = static_cast<int32_t>(value);\n//        break;\n    default: //Don't change\n        break;\n    }\n}\n\nSDL_FORCE_INLINE void memToValue(uint8_t &target, num_t value, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        target = static_cast<uint8_t>(f2i_cast<uint8_t>(value));\n        break;\n    case FT_WORD:\n        target = static_cast<uint8_t>(static_cast<int16_t>(f2i_cast<uint16_t>(value)));\n        break;\n    case FT_DWORD:\n    case FT_DFLOAT:\n        target = static_cast<uint8_t>(value);\n        break;\n    case FT_FLOAT:\n        target = static_cast<uint8_t>(f2i_cast<tempf_t>(value));\n        break;\n//    case FT_DFLOAT: // United with FT_DWORD\n//        target = static_cast<int32_t>(value);\n//        break;\n    default: //Don't change\n        break;\n    }\n}\n\nSDL_FORCE_INLINE void memToValue(bool &target, num_t value, FIELDTYPE ftype)\n{\n    UNUSED(ftype);\n    target = (value != 0);\n}\n\n\n/*----------------------------------------------*\n *           Read memory value                  *\n *----------------------------------------------*/\n\nSDL_FORCE_INLINE num_t valueToMem(const num_t &source, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        return static_cast<num_t>(static_cast<uint8_t>(source));\n    case FT_WORD:\n        return static_cast<num_t>(static_cast<int16_t>(static_cast<uint16_t>(source)));\n    case FT_DWORD:\n        return static_cast<num_t>(static_cast<int32_t>(source));\n    case FT_FLOAT:\n        return static_cast<num_t>(static_cast<numf_t>(source));\n    default:\n    case FT_DFLOAT:\n        return source;\n    }\n}\n\nSDL_FORCE_INLINE num_t valueToMem(const numf_t &source, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        return static_cast<num_t>(static_cast<uint8_t>(source));\n    case FT_WORD:\n        return static_cast<num_t>(static_cast<int16_t>(static_cast<uint16_t>(source)));\n    case FT_DWORD:\n        return static_cast<num_t>(static_cast<int32_t>(source));\n    default:\n    case FT_FLOAT:\n    case FT_DFLOAT:\n        return static_cast<num_t>(source);\n    }\n}\n\nSDL_FORCE_INLINE num_t valueToMem(const int &source, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        return static_cast<num_t>(static_cast<uint8_t>(source));\n    case FT_WORD:\n        return static_cast<num_t>(static_cast<int16_t>(source));\n    default:\n    case FT_DWORD:\n        return static_cast<num_t>(source);\n    case FT_FLOAT:\n        return static_cast<num_t>(static_cast<numf_t>(source));\n    case FT_DFLOAT:\n        return static_cast<num_t>(source);\n    }\n}\n\nSDL_FORCE_INLINE num_t valueToMem(const short &source, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        return static_cast<num_t>(static_cast<uint8_t>(source));\n    case FT_WORD:\n        return static_cast<num_t>(static_cast<int16_t>(source));\n    default:\n    case FT_DWORD:\n        return static_cast<num_t>(source);\n    case FT_FLOAT:\n        return static_cast<num_t>(static_cast<numf_t>(source));\n    case FT_DFLOAT:\n        return static_cast<num_t>(source);\n    }\n}\n\nSDL_FORCE_INLINE num_t valueToMem(const uint8_t &source, FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        return static_cast<num_t>(static_cast<uint8_t>(source));\n    case FT_WORD:\n        return static_cast<num_t>(static_cast<int16_t>(source));\n    default:\n    case FT_DWORD:\n        return static_cast<num_t>(source);\n    case FT_FLOAT:\n        return static_cast<num_t>(static_cast<numf_t>(source));\n    case FT_DFLOAT:\n        return static_cast<num_t>(source);\n    }\n}\n\nSDL_FORCE_INLINE num_t valueToMem(const bool &source, FIELDTYPE ftype)\n{\n    UNUSED(ftype);\n    return source ? 0xFFFF : 0;\n}\n\n\n/*!\n * \\brief Convert field type into string\n * \\param ftype Field type\n * \\return Human-readable string\n */\nstatic const char *FieldtypeToStr(FIELDTYPE ftype)\n{\n    switch(ftype)\n    {\n    case FT_BYTE:\n        return \"Uint8\";\n    case FT_WORD:\n        return \"SInt16\";\n    case FT_DWORD:\n        return \"SInt32\";\n    case FT_FLOAT:\n        return \"Float\";\n    case FT_DFLOAT:\n        return \"Double\";\n    default:\n        return \"<Invalid>\";\n    }\n}\n\n\n\n/*!\n * \\brief Global memory emulator\n */\nclass SMBXMemoryEmulator\n{\n    std::unordered_map<size_t, num_t *> m_df;\n    std::unordered_map<size_t, numf_t *>  m_ff;\n    std::unordered_map<size_t, short *>  m_i16f;\n    std::unordered_map<size_t, int *>    m_i32f;\n    std::unordered_map<size_t, bool *>   m_bf;\n    std::unordered_map<size_t, std::string *>   m_sf;\n\n    typedef std::function<num_t(FIELDTYPE)> Getter;\n    typedef std::function<void(num_t,FIELDTYPE)> Setter;\n    std::unordered_map<size_t, std::pair<Getter, Setter>> m_lff;\n\n    typedef std::function<std::string()> StrGetter;\n    typedef std::function<void(const std::string&)> StrSetter;\n    std::unordered_map<size_t, std::pair<StrGetter, StrSetter>> m_sff;\n\n    enum ValueType\n    {\n        VT_UNKNOWN = 0,\n        VT_DOUBLE,\n        VT_FLOAT,\n        VT_INT16,\n        VT_INT32,\n        VT_BOOL,\n        VT_STRING,\n        VT_LAMBDA,\n        VT_STRLAMBDA\n    };\n\n    std::unordered_map<int, ValueType> m_type;\n\n    void insert(size_t address, short *field)\n    {\n        m_i16f.insert({address, field});\n        m_type.insert({address, VT_INT16});\n    }\n\n    void insert(size_t address, int *field)\n    {\n        m_i32f.insert({address, field});\n        m_type.insert({address, VT_INT32});\n    }\n\n    void insert(size_t address, num_t *field)\n    {\n        m_df.insert({address, field});\n        m_type.insert({address, VT_DOUBLE});\n    }\n\n    void insert(size_t address, numf_t *field)\n    {\n        m_ff.insert({address, field});\n        m_type.insert({address, VT_FLOAT});\n    }\n\n    void insert(size_t address, bool *field)\n    {\n        m_bf.insert({address, field});\n        m_type.insert({address, VT_BOOL});\n    }\n\n    void insert(size_t address, std::string *field)\n    {\n        m_sf.insert({address, field});\n        m_type.insert({address, VT_STRING});\n    }\n\n    void insert(size_t address, Getter g, Setter s)\n    {\n        m_lff.insert({address, {g, s}});\n        m_type.insert({address, VT_LAMBDA});\n    }\n\n    void insert(size_t address, StrGetter g, StrSetter s)\n    {\n        m_sff.insert({address, {g, s}});\n        m_type.insert({address, VT_STRLAMBDA});\n    }\n\npublic:\n    SMBXMemoryEmulator() noexcept\n    {\n        buildTable();\n    }\n\n    void buildTable()\n    {\n        insert(0x00B2504C, &TakeScreen);\n        insert(0x00B250D6, &numLocked);\n        insert(0x00B250E2, // Pause menu visible\n            [](FIELDTYPE ftype)->num_t\n            {\n                bool tmp = (GamePaused != PauseCode::None);\n                return valueToMem(tmp, ftype);\n            },\n            [](num_t in, FIELDTYPE ftype)->void\n            {\n                // FIXME: Verify this, if it needs to be written, try to work around this\n                pLogWarning(\"Attempt to write the read-only field at 0x00B250E2 \"\n                            \"(GamePaused) with value %d as %s\", (int)in, FieldtypeToStr(ftype));\n            }\n        );\n        insert(0x00B25134, &LevelEditor);\n\n        insert(0x00B251E0, &numStars); // HUD star count\n        insert(0x00B25700, &numWater);\n\n        insert(0x00B25724, &StartLevel);\n        insert(0x00B25728, &NoMap);\n        insert(0x00B2572A, &RestartLevel);\n\n        // should all be read-only\n        insert(0x00B257A4, &numTiles);\n        insert(0x00B257A6, &numScenes);\n        insert(0x00B258E0, &numWorldPaths);\n        insert(0x00B258E2, &numWarps);\n        insert(0x00B25956, &numBlock);\n        insert(0x00B25958, &numBackground);\n        insert(0x00B2595A, &numNPCs); // NPC count\n        insert(0x00B2595E, &numPlayers); // Player Count\n        insert(0x00B25960, &numWorldLevels);\n        insert(0x00B25980, &numWorldMusic);\n\n        insert(0x00B2C5A8, &Coins); // HUD coins count\n        insert(0x00B2C5AC, // HUD lives count\n            [](FIELDTYPE ftype)->num_t\n            {\n                int tmp = g_config.modern_lives_system ? g_100s : Lives;\n                if(tmp < 0)\n                    tmp = 0;\n                if(tmp > 99)\n                    tmp = 99;\n                return valueToMem(tmp, ftype);\n            },\n            [](num_t in, FIELDTYPE ftype)->void\n            {\n                if(!g_config.modern_lives_system)\n                    memToValue(Lives, in, ftype);\n\n                int old = g_100s;\n                if(old < 0)\n                    old = 0;\n                if(old > 99)\n                    old = 99;\n\n                int out = old;\n                memToValue(out, in, ftype);\n\n                g_100s += out - old;\n            }\n        );  // lives now replaced by g_100s\n\n        insert(0x00B2C624, &WorldName);\n\n        insert(0x00B2C62C, &PSwitchTime); // P-Switch Timer\n        insert(0x00B2C62E, &PSwitchStop); // Stopwatch Timer\n        insert(0x00B2C630, &PSwitchPlayer); // P-Switch/Stopwatch Player\n\n        // FIXME: properly report as modified when set\n        insert(0x00B2C684, &g_config.enable_frameskip.m_value);\n\n        insert(0x00B2C6DC, &Physics.PlayerJumpHeight);\n        insert(0x00B2C6DE, &Physics.PlayerBlockJumpHeight);\n        insert(0x00B2C6E0, &Physics.PlayerHeadJumpHeight);\n        insert(0x00B2C6E2, &Physics.PlayerNPCJumpHeight);\n        insert(0x00B2C6E4, &Physics.PlayerSpringJumpHeight);\n        insert(0x00B2C6E8, &Physics.PlayerJumpVelocity);\n        insert(0x00B2C6EC, &Physics.PlayerRunSpeed);\n        insert(0x00B2C6F0, &Physics.PlayerWalkSpeed);\n        insert(0x00B2C6F4, &Physics.PlayerTerminalVelocity);\n        insert(0x00B2C6F8, &Physics.PlayerGravity);\n        insert(0x00B2C860, &Physics.NPCShellSpeed);\n        insert(0x00B2C864, &Physics.NPCShellSpeedY);\n        insert(0x00B2C868, &Physics.NPCWalkingSpeed);\n        insert(0x00B2C86C, &Physics.NPCWalkingOnSpeed);\n        insert(0x00B2C870, &Physics.NPCMushroomSpeed);\n        insert(0x00B2C874, &Physics.NPCGravity);\n        insert(0x00B2C878, &Physics.NPCGravityReal);\n        insert(0x00B2C87C, &Physics.NPCPSwitch); // P-Switch/Stopwatch Length\n\n        insert(0x00B2C880, &MenuCursor); // Current menu choice\n        insert(0x00B2C882, &MenuMode); // Current menu mode\n\n        // insert(0x00B2C884, ???}; // Key Released!!!\n        // insert(0x00B2C894, &BlocksSorted); // removed by block quadtree\n        insert(0x00B2C894,\n            [](FIELDTYPE ftype)->num_t\n            {\n                bool ret = true;\n                return valueToMem(ret, ftype);\n            },\n            [](num_t in, FIELDTYPE ftype)->void\n            {\n                pLogWarning(\"Attempt to write the read-only field at 0x00B2C894 \"\n                            \"(BlocksSorted) with value %d as %s\", (int)in, FieldtypeToStr(ftype));\n            }\n        );\n\n        insert(0x00B2C896, &SingleCoop);\n        insert(0x00B2C898,\n            []()->std::string\n            {\n                D_pLogDebugNA(\"Attempt to read unsupported field at 0x00B2C898 (cheats buffer)\");\n                return std::string();\n            },\n            [](const std::string &in)->void\n            {\n                cheats_setBuffer(in, true);\n            }\n        );\n\n        insert(0x00B2C89C, &GameOutro);\n        insert(0x00B2C8A0, &CreditChop);\n        insert(0x00B2C8A4, &EndCredits);\n\n        insert(0x00B2C8A6, &curStars);\n        // insert(0x00B2C8A8, &maxStars);\n\n        insert(0x00B2C8AA, &ShadowMode);\n        insert(0x00B2C8AC, &MultiHop);\n        insert(0x00B2C8AE, &SuperSpeed);\n        insert(0x00B2C8B0, &WalkAnywhere);\n        insert(0x00B2C8B2, &FlyForever);\n        insert(0x00B2C8B4, &FreezeNPCs);\n        insert(0x00B2C8B6, &CaptainN);\n        insert(0x00B2C8B8, &FlameThrower);\n        insert(0x00B2C8BA, &CoinMode);\n\n        // FIXME: properly report as modified when set\n        insert(0x00B2C8BE, &g_config.unlimited_framerate.m_value);\n        insert(0x00B2C8C0, &GodMode);\n        insert(0x00B2C8C2, &GrabAll);\n\n        insert(0x00B2C8C4, &Cheater);\n\n        insert(0x00B2C8E4, &Score); // HUD points count\n        insert(0x00B2C906, &MaxWorldStars); // Max stars at episode\n\n        // insert(0x00B2C908, &Debugger);\n\n        // insert(0x00B2D6B8, &PlayerCharacter);\n        // insert(0x00B2D6BA, &PlayerCharacter2);\n\n        insert(0x00B2D6BC, &SharedCursor.X); // Mouse cursor X\n        insert(0x00B2D6C4, &SharedCursor.Y); // Mouse cursor Y\n        insert(0x00B2D6CC, &SharedCursor.Primary);\n        insert(0x00B2D6D0, &MenuMouseRelease);\n        insert(0x00B2D6D2, &SharedCursor.Move);\n        insert(0x00B2D710, &numEvents);\n        insert(0x00B2D740, &BattleMode);\n        // was previously unlinked with sound engine status, replaced by (1) !g_config.audio_enable and (2) !g_mixerLoaded\n        // If we wanted to reimplement, should probably read from (2) and write to (1), including the (expensive!) UpdateConfig hook\n        // insert(0x00B2D734, &noSound);\n    }\n\n    num_t getValue(size_t address, FIELDTYPE ftype)\n    {\n        if(ftype == FT_INVALID)\n        {\n            pLogWarning(\"MemEmu: Requested value of invalid type: <Global> 0x%x\", static_cast<unsigned>(address));\n            return 0;\n        }\n\n        auto ft = m_type.find(address);\n        if(ft == m_type.end())\n        {\n            pLogWarning(\"MemEmu: Unknown %s address to read: <Global> 0x%x\", FieldtypeToStr(ftype), static_cast<unsigned>(address));\n            return 0;\n        }\n\n        switch(ft->second)\n        {\n        case VT_DOUBLE:\n        {\n            auto dres = m_df.find(address);\n            if(dres != m_df.end())\n            {\n                if(ftype != FT_DFLOAT)\n                    pLogWarning(\"MemEmu: Read type missmatched at 0x%x (Double expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n\n                return valueToMem(*dres->second, ftype);\n            }\n            break;\n        }\n\n        case VT_FLOAT:\n        {\n            auto fres = m_ff.find(address);\n            if(fres != m_ff.end())\n            {\n                if(ftype != FT_FLOAT)\n                    pLogWarning(\"MemEmu: Read type missmatched at 0x%x (Float expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n\n                return valueToMem(*fres->second, ftype);\n            }\n            break;\n        }\n\n        case VT_INT32:\n        {\n            auto ires = m_i32f.find(address);\n            if(ires != m_i32f.end())\n            {\n                if(ftype != FT_DWORD && ftype != FT_WORD)\n                    pLogWarning(\"MemEmu: Read type missmatched at 0x%x (SInt16 or SInt32 expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n\n                return valueToMem(*ires->second, ftype);\n            }\n            break;\n        }\n\n        case VT_INT16:\n        {\n            auto ires = m_i16f.find(address);\n            if(ires != m_i16f.end())\n            {\n                if(ftype != FT_WORD)\n                    pLogWarning(\"MemEmu: Read type missmatched at 0x%x (SInt16 expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n\n                return valueToMem(*ires->second, ftype);\n            }\n            break;\n        }\n\n        case VT_BOOL:\n        {\n            auto bres = m_bf.find(address);\n            if(bres != m_bf.end())\n            {\n                if(ftype != FT_WORD && ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Read type missmatched at 0x%x (Sint16 or Uint8 as boolean expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                return *bres->second ? 0xffff : 0x0000;\n            }\n            break;\n        }\n\n        case VT_LAMBDA:\n        {\n            auto lfres = m_lff.find(address);\n            if(lfres != m_lff.end())\n            {\n                auto &l = lfres->second;\n                return l.first(ftype);\n            }\n            break;\n        }\n\n        default:\n            break;\n        }\n\n        return 0;\n    }\n\n    void setValue(size_t address, num_t value, FIELDTYPE ftype)\n    {\n        if(ftype == FT_INVALID)\n        {\n            pLogWarning(\"MemEmu: Passed value of invalid type: <Global> 0x%x\", static_cast<unsigned>(address));\n            return;\n        }\n\n        auto ft = m_type.find(address);\n        if(ft == m_type.end())\n        {\n            pLogWarning(\"MemEmu: Unknown %s address to write: 0x%x\", FieldtypeToStr(ftype), static_cast<unsigned>(address));\n            return;\n        }\n\n        switch(ft->second)\n        {\n        case VT_DOUBLE:\n        {\n            auto dres = m_df.find(address);\n            if(dres != m_df.end())\n            {\n                if(ftype != FT_DFLOAT)\n                    pLogWarning(\"MemEmu: Write type missmatched at 0x%x (Double expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n\n                memToValue(*dres->second, value, ftype);\n                return;\n            }\n            break;\n        }\n\n        case VT_FLOAT:\n        {\n            auto fres = m_ff.find(address);\n            if(fres != m_ff.end())\n            {\n                if(ftype != FT_FLOAT)\n                    pLogWarning(\"MemEmu: Write type missmatched at 0x%x (Float expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n\n                memToValue(*fres->second, value, ftype);\n                return;\n            }\n            break;\n        }\n\n        case VT_INT32:\n        {\n            auto ires = m_i32f.find(address);\n            if(ires != m_i32f.end())\n            {\n                if(ftype != FT_DWORD && ftype != FT_WORD)\n                    pLogWarning(\"MemEmu: Write type missmatched at 0x%x (SInt16 or SInt32 expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n\n                memToValue(*ires->second, value, ftype);\n                return;\n            }\n            break;\n        }\n\n        case VT_INT16:\n        {\n            auto ires = m_i16f.find(address);\n            if(ires != m_i16f.end())\n            {\n                if(ftype != FT_WORD)\n                    pLogWarning(\"MemEmu: Write type missmatched at 0x%x (SInt16 expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n\n                memToValue(*ires->second, value, ftype);\n                return;\n            }\n            break;\n        }\n\n        case VT_BOOL:\n        {\n            auto bres = m_bf.find(address);\n            if(bres != m_bf.end())\n            {\n                if(ftype != FT_WORD && ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Write type missmatched at 0x%x (Sint16 or Uint8 as boolean expected, %s actually)\", static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                *bres->second = (value != 0);\n                return;\n            }\n            break;\n        }\n\n        case VT_LAMBDA:\n        {\n            auto lfres = m_lff.find(address);\n            if(lfres != m_lff.end())\n            {\n                auto &l = lfres->second;\n                l.second(value, ftype);\n            }\n            break;\n        }\n\n        default:\n            break;\n        }\n    }\n};\n\n/*!\n * \\brief Per-Object memory emulator\n */\ntemplate<class T, char const *objName, size_t maxAddr>\nclass SMBXObjectMemoryEmulator\n{\nprotected:\n\n    typedef std::function<num_t(const T&,FIELDTYPE)> Getter;\n    typedef std::function<void(T&,num_t,FIELDTYPE)> Setter;\n\n    // typedef std::function<std::string(const T&)> StrGetter;\n    // typedef std::function<void(T&,const std::string&)> StrSetter;\n\n    enum ValueType : uint8_t\n    {\n        VT_UNKNOWN = 0,\n        VT_DOUBLE,\n        VT_FLOAT,\n        VT_UINT8,\n        VT_INT16,\n        VT_INT32,\n        VT_BOOL,\n        VT_STRING,\n        VT_BYTE_HACK,\n        VT_LAMBDA,\n    };\n\n    struct Value\n    {\n        //! Type of field\n        ValueType type = VT_UNKNOWN;\n        //! Base type for byte hacking mode\n        ValueType baseType = VT_UNKNOWN;\n        //! Byte offset\n        uint8_t offset = 0;\n        //! Base address of real value\n        int baseAddress = 0;\n\n        union\n        {\n            //! Double-type field pointer\n            num_t       T::* d = nullptr;\n            //! Float-type field pointer\n            numf_t      T::* f;\n            //! UInt8-type field pointer (note: used exclusively for i16s converted to u8s)\n            uint8_t     T::* u8;\n            //! Int-type field pointer\n            short       T::* i16;\n            //! Int-type field pointer\n            int         T::* i32;\n            //! Boolean type field pointer\n            bool        T::* b;\n            //! String-type field pointer\n            std::string T::* s;\n        } field;\n\n        //! Lambda-type field pointer\n        std::pair<Getter, Setter> field_lf;\n    };\n\n    //! Basic map of addresses\n    Value m_type[maxAddr];\n\n    void insert(size_t address, uint8_t T::*field)\n    {\n        Value v;\n\n        // Normal field\n        v.field.u8 = field;\n        v.type = VT_UINT8;\n        v.baseType = VT_UINT8;\n        v.offset = 0; //-V1048\n        v.baseAddress = address;\n        m_type[address] = v;\n    }\n\n    void insert(size_t address, short T::*field)\n    {\n        Value v;\n\n        // Normal field\n        v.field.i16 = field;\n        v.type = VT_INT16;\n        v.baseType = VT_INT16;\n        v.offset = 0; //-V1048\n        v.baseAddress = address;\n        m_type[address] = v;\n\n        // Byte hack fields\n        v.type = VT_BYTE_HACK;\n        for(int i = 0; i < 2; ++i)\n        {\n            v.offset = i;\n            if(i > 0)\n                m_type[address + i] = v;\n        }\n    }\n\n    void insert(size_t address, int T::*field)\n    {\n        Value v;\n\n        // Normal field\n        v.field.i32 = field;\n        v.type = VT_INT32;\n        v.baseType = VT_INT32;\n        v.offset = 0; //-V1048\n        v.baseAddress = address;\n        m_type[address] = v;\n\n        // Byte hack fields\n        v.type = VT_BYTE_HACK;\n        for(int i = 0; i < 2; ++i)\n        {\n            v.offset = i;\n            if(i > 0)\n                m_type[address + i] = v;\n        }\n    }\n\n    void insert(size_t address, num_t T::*field)\n    {\n        Value v;\n\n        // Normal field\n        v.field.d = field;\n        v.type = VT_DOUBLE;\n        v.baseType = VT_DOUBLE;\n        v.offset = 0;\n        v.baseAddress = address;\n        m_type[address] = v;\n\n        // Byte hack fields\n        v.type = VT_BYTE_HACK;\n        for(int i = 0; i < 8; ++i)\n        {\n            v.offset = i;\n            if(i > 0)\n                m_type[address + i] = v;\n        }\n    }\n\n    void insert(size_t address, numf_t T::*field)\n    {\n        Value v;\n\n        // Normal field\n        v.field.f = field;\n        v.type = VT_FLOAT;\n        v.baseType = VT_FLOAT;\n        v.offset = 0;\n        v.baseAddress = address;\n        m_type[address] = v;\n\n        // Byte hack fields\n        v.type = VT_BYTE_HACK;\n        for(int i = 0; i < 4; ++i)\n        {\n            v.offset = i;\n            if(i > 0)\n                m_type[address + i] = v;\n        }\n    }\n\n    void insert(size_t address, bool T::*field)\n    {\n        Value v;\n\n        // Normal field\n        v.field.b = field;\n        v.type = VT_BOOL;\n        v.baseType = VT_BOOL;\n        v.offset = 0;\n        v.baseAddress = address;\n        m_type[address] = v;\n    }\n\n    void insert(size_t address, std::string T::*field)\n    {\n        Value v;\n\n        // Normal field\n        v.field.s = field; // -V820 // False positive, it's a pointer, not a std::string\n        v.type = VT_STRING;\n        v.baseType = VT_STRING;\n        v.offset = 0;\n        v.baseAddress = address;\n        m_type[address] = v;\n    }\n\n    void insert(size_t address, Getter g, Setter s)\n    {\n        Value v;\n\n        // Normal field\n        v.field_lf = {g, s};\n        v.type = VT_LAMBDA;\n        v.baseType = VT_LAMBDA;\n        v.offset = 0;\n        v.baseAddress = address;\n        m_type[address] = v;\n    }\n\npublic:\n    SMBXObjectMemoryEmulator() noexcept\n    {}\n\n    virtual num_t getValue(T *obj, size_t address, FIELDTYPE ftype)\n    {\n        Value *t = nullptr;\n\n        if(address >= maxAddr)\n        {\n            pLogWarning(\"MemEmu: Requested value of out-of-range address: %s 0x%x\", objName, static_cast<unsigned>(address));\n            return 0;\n        }\n\n        if(ftype == FT_INVALID)\n        {\n            pLogWarning(\"MemEmu: Requested value of invalid type: %s 0x%x\", objName, static_cast<unsigned>(address));\n            return 0;\n        }\n\n        t = &m_type[address];\n        auto vtype = t->type;\n\n        if(ftype == FT_BYTE) // byte hacking\n        {\n            if(vtype != VT_BOOL && vtype != VT_UINT8 && vtype != VT_UNKNOWN)\n                vtype = VT_BYTE_HACK;\n        }\n\n        if(vtype == VT_UNKNOWN)\n        {\n            pLogWarning(\"MemEmu: Unknown %s::%s address to read: 0x%x\", objName, FieldtypeToStr(ftype), static_cast<unsigned>(address));\n            return 0;\n        }\n\n        switch(vtype)\n        {\n        case VT_DOUBLE:\n        {\n            SDL_assert(t->field.d);\n            if(ftype != FT_DFLOAT)\n                pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (Double expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            return valueToMem(obj->*(t->field.d), ftype);\n        }\n\n        case VT_FLOAT:\n        {\n            SDL_assert(t->field.f);\n            if(ftype != FT_FLOAT)\n                pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (Float expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            return valueToMem(obj->*(t->field.f), ftype);\n        }\n\n        case VT_INT32:\n        {\n            SDL_assert(t->field.i32);\n            if(ftype != FT_DWORD && ftype != FT_WORD)\n                pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (SInt16 or SInt32 expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            return valueToMem(obj->*(t->field.i32), ftype);\n        }\n\n        case VT_INT16:\n        {\n            SDL_assert(t->field.i16);\n            if(ftype != FT_WORD)\n                pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (SInt16 expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            return valueToMem(obj->*(t->field.i16), ftype);\n        }\n\n        case VT_UINT8:\n        {\n            SDL_assert(t->field.u8);\n            if(ftype != FT_WORD)\n                pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (SInt16 expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            return valueToMem(obj->*(t->field.u8), ftype);\n        }\n\n        case VT_BOOL:\n        {\n            SDL_assert(t->field.b);\n            if(ftype != FT_WORD && ftype != FT_BYTE)\n                pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (Sint16 or Uint8 as boolean expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            return valueToMem(obj->*(t->field.b), ftype);\n        }\n\n        case VT_LAMBDA:\n        {\n            return t->field_lf.first(*obj, ftype);\n        }\n\n        case VT_BYTE_HACK:\n        {\n            switch(t->baseType)\n            {\n            case VT_DOUBLE:\n            {\n                auto &bt = m_type[t->baseAddress];\n                SDL_assert(bt.type == VT_DOUBLE && bt.field.d);\n                if(ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (byte expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                return (num_t)getByteX86(obj->*(bt.field.d), t->offset);\n            }\n\n            case VT_FLOAT:\n            {\n                auto &bt = m_type[t->baseAddress];\n                SDL_assert(bt.type == VT_FLOAT && bt.field.f);\n                if(ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (byte expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                return (num_t)getByteX86(obj->*(bt.field.f), t->offset);\n            }\n\n            case VT_INT16:\n            {\n                auto &bt = m_type[t->baseAddress];\n                SDL_assert(bt.type == VT_INT16 && bt.field.i16);\n                if(ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (byte expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                int16_t s = static_cast<int16_t>(obj->*(bt.field.i16));\n                return (num_t)getByteX86(s, t->offset);\n            }\n\n            case VT_INT32:\n            {\n                auto &bt = m_type[t->baseAddress];\n                SDL_assert(bt.type == VT_INT32 && bt.field.i32);\n                if(ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Read type missmatched at %s 0x%x (byte expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                int16_t s = static_cast<int16_t>(obj->*(bt.field.i32));\n                return (num_t)getByteX86(s, t->offset);\n            }\n\n            default:\n                break;\n            }\n\n            break;\n        }\n\n        default:\n            break;\n        }\n\n        return 0;\n    }\n\n    virtual void setValue(T *obj, size_t address, num_t value, FIELDTYPE ftype)\n    {\n        Value *t = nullptr;\n\n        if(address >= maxAddr)\n        {\n            pLogWarning(\"MemEmu: Requested value of out-of-range address: %s 0x%x\", objName, static_cast<unsigned>(address));\n            return;\n        }\n\n        if(ftype == FT_INVALID)\n        {\n            pLogWarning(\"MemEmu: Passed value of invalid type: %s 0x%x\", objName, static_cast<unsigned>(address));\n            return;\n        }\n\n        t = &m_type[address];\n        auto vtype = t->type;\n\n        if(ftype == FT_BYTE) // byte hacking\n        {\n            if(vtype != VT_BOOL && vtype != VT_UINT8 && vtype != VT_UNKNOWN)\n                vtype = VT_BYTE_HACK;\n        }\n\n        if(vtype == VT_UNKNOWN)\n        {\n            pLogWarning(\"MemEmu: Unknown %s::%s address to write: 0x%x\", objName, FieldtypeToStr(ftype), static_cast<unsigned>(address));\n            return;\n        }\n\n        switch(vtype)\n        {\n        case VT_DOUBLE:\n        {\n            SDL_assert(t->field.d);\n            if(ftype != FT_DFLOAT)\n                pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (Double expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            memToValue(obj->*(t->field.d), value, ftype);\n            return;\n        }\n\n        case VT_FLOAT:\n        {\n            SDL_assert(t->field.f);\n            if(ftype != FT_FLOAT)\n                pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (Float expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            memToValue(obj->*(t->field.f), value, ftype);\n            return;\n        }\n\n        case VT_INT32:\n        {\n            SDL_assert(t->field.i32);\n            if(ftype != FT_DWORD && ftype != FT_WORD)\n                pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (SInt16 or SInt32 expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            memToValue(obj->*(t->field.i32), value, ftype);\n            return;\n        }\n\n        case VT_INT16:\n        {\n            SDL_assert(t->field.i16);\n            if(ftype != FT_WORD)\n                pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (SInt16 expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            memToValue(obj->*(t->field.i16), value, ftype);\n            return;\n        }\n\n        case VT_UINT8:\n        {\n            SDL_assert(t->field.u8);\n            if(ftype != FT_WORD)\n                pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (SInt16 expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            memToValue(obj->*(t->field.u8), value, ftype);\n            return;\n        }\n\n        case VT_BOOL:\n        {\n            SDL_assert(t->field.b);\n            if(ftype != FT_WORD && ftype != FT_BYTE)\n                pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (Sint16 or Uint8 as boolean expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n            memToValue(obj->*(t->field.b), value, ftype);\n            return;\n        }\n\n        case VT_LAMBDA:\n        {\n            t->field_lf.second(*obj, value, ftype);\n            return;\n        }\n\n        case VT_BYTE_HACK:\n        {\n            switch(t->baseType)\n            {\n            case VT_DOUBLE:\n            {\n                auto &bt = m_type[t->baseAddress];\n                SDL_assert(bt.type == VT_DOUBLE && bt.field.d);\n                if(ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (byte expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                modifyByteX86(obj->*(bt.field.d), t->offset, f2i_cast<uint8_t>(value));\n                return;\n            }\n\n            case VT_FLOAT:\n            {\n                auto &bt = m_type[t->baseAddress];\n                SDL_assert(bt.type == VT_FLOAT && bt.field.f);\n                if(ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (byte expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                modifyByteX86(obj->*(bt.field.f), t->offset, f2i_cast<uint8_t>(value));\n                return;\n            }\n\n            case VT_INT16:\n            {\n                auto &bt = m_type[t->baseAddress];\n                SDL_assert(bt.type == VT_INT16 && bt.field.i16);\n                if(ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (byte expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                int16_t s = static_cast<int16_t>(obj->*(bt.field.i16));\n                modifyByteX86(s, t->offset, f2i_cast<uint8_t>(value));\n                obj->*(bt.field.i16) = static_cast<int>(s);\n                return;\n            }\n\n            case VT_INT32:\n            {\n                auto &bt = m_type[t->baseAddress];\n                SDL_assert(bt.type == VT_INT32 && bt.field.i32);\n                if(ftype != FT_BYTE)\n                    pLogWarning(\"MemEmu: Write type missmatched at %s 0x%x (byte expected, %s actually)\", objName, static_cast<unsigned>(address), FieldtypeToStr(ftype));\n                int16_t s = static_cast<int16_t>(obj->*(bt.field.i32));\n                modifyByteX86(s, t->offset, f2i_cast<uint8_t>(value));\n                obj->*(bt.field.i32) = static_cast<int>(s);\n                return;\n            }\n\n            default:\n                break;\n            }\n\n            break;\n        }\n\n        default:\n            break;\n        }\n    }\n};\n\nstatic constexpr char location_t_name[] = \"Location_t\";\ntypedef SMBXObjectMemoryEmulator<Location_t, location_t_name, 0x31> LocationParent;\nclass LocationMemory final : public LocationParent\n{\npublic:\n    LocationMemory() noexcept : LocationParent()\n    {\n        buildTable();\n    }\n\n    void buildTable()\n    {\n        insert(0x00, &Location_t::X);\n        insert(0x08, &Location_t::Y);\n        insert(0x10, &Location_t::Height);\n        insert(0x18, &Location_t::Width);\n        insert(0x20, &Location_t::SpeedX);\n        insert(0x28, &Location_t::SpeedY);\n    }\n};\n\nstatic constexpr char speedless_location_t_name[] = \"SpeedlessLocation_t\";\ntypedef SMBXObjectMemoryEmulator<SpeedlessLocation_t, speedless_location_t_name, 0x20> SpeedlessLocationParent;\nclass SpeedlessLocationMemory final : public SpeedlessLocationParent\n{\npublic:\n    SpeedlessLocationMemory() noexcept : SpeedlessLocationParent()\n    {\n        buildTable();\n    }\n\n    void buildTable()\n    {\n        insert(0x00, &SpeedlessLocation_t::X);\n        insert(0x08, &SpeedlessLocation_t::Y);\n        insert(0x10, &SpeedlessLocation_t::Height);\n        insert(0x18, &SpeedlessLocation_t::Width);\n    }\n};\n\n\nstatic constexpr char controls_t_name[] = \"Controls_t\";\ntypedef SMBXObjectMemoryEmulator<Controls_t, controls_t_name, 0x16> ControlsParent;\nclass ControlsMemory final : public ControlsParent\n{\npublic:\n    ControlsMemory() noexcept :  ControlsParent()\n    {\n        buildTable();\n    }\n\n    void buildTable()\n    {\n        insert(0x00, &Controls_t::Up);\n        insert(0x02, &Controls_t::Down);\n        insert(0x04, &Controls_t::Left);\n        insert(0x06, &Controls_t::Right);\n        insert(0x08, &Controls_t::Jump);\n        insert(0x0A, &Controls_t::AltJump);\n        insert(0x0C, &Controls_t::Run);\n        insert(0x0E, &Controls_t::AltRun);\n        insert(0x10, &Controls_t::Drop);\n        insert(0x12, &Controls_t::Start);\n    }\n};\n\nstatic ControlsMemory s_conMem;\nstatic LocationMemory s_locMem;\nstatic SpeedlessLocationMemory s_spLocMem;\n\n\nstatic constexpr char playere_t_name[] = \"Player_t\";\ntypedef SMBXObjectMemoryEmulator<Player_t, playere_t_name, 0x186> PlayerParent;\nclass PlayerMemory final : public PlayerParent\n{\npublic:\n    PlayerMemory() noexcept : PlayerParent()\n    {\n        buildTable();\n    }\n\n    void buildTable()\n    {\n        insert(0x00000000, &Player_t::DoubleJump);\n        insert(0x00000002, &Player_t::FlySparks);\n        insert(0x00000004, &Player_t::Driving);\n        insert(0x00000006, &Player_t::Quicksand);\n        insert(0x00000008, &Player_t::Bombs);\n        insert(0x0000000a, &Player_t::Slippy);\n\n        insert(0x0000000c, &Player_t::Fairy);\n        insert(0x0000000e, &Player_t::FairyCD);\n        insert(0x00000010, &Player_t::FairyTime);\n        insert(0x00000012, &Player_t::HasKey);\n        insert(0x00000014, &Player_t::SwordPoke);\n        insert(0x00000016, &Player_t::Hearts);\n\n        insert(0x00000018, &Player_t::CanFloat);\n        insert(0x0000001a, &Player_t::FloatRelease);\n        insert(0x0000001c, &Player_t::FloatTime);\n        insert(0x00000020, &Player_t::FloatSpeed);\n        insert(0x00000024, &Player_t::FloatDir);\n        insert(0x00000026, &Player_t::GrabTime);\n        insert(0x00000028, &Player_t::GrabSpeed);\n        insert(0x0000002c, &Player_t::VineNPC);\n\n        insert(0x00000034, &Player_t::Wet);\n        insert(0x00000036, &Player_t::WetFrame);\n        insert(0x00000038, &Player_t::SwimCount);\n        insert(0x0000003a, &Player_t::NoGravity);\n        insert(0x0000003c, &Player_t::Slide);\n        insert(0x0000003e, &Player_t::SlideKill);\n        insert(0x00000040, &Player_t::Vine);\n        // insert(0x00000042, &Player_t::NoShellKick);\n        insert(0x00000044, &Player_t::ShellSurf);\n        static_assert(sizeof(Player_t::StateNPC) == sizeof(vbint_t), \"underlying type of Player_t::StateNPC must be vbint_t\");\n        insert(0x00000046, reinterpret_cast<vbint_t Player_t::*>(&Player_t::StateNPC));\n        insert(0x00000048, &Player_t::Slope);\n        insert(0x0000004a, &Player_t::Stoned);\n        insert(0x0000004c, &Player_t::StonedCD);\n        insert(0x0000004e, &Player_t::StonedTime);\n        insert(0x00000050, &Player_t::SpinJump);\n        insert(0x00000052, &Player_t::SpinFrame);\n        insert(0x00000054, &Player_t::SpinFireDir);\n        insert(0x00000056, &Player_t::Multiplier);\n        insert(0x00000058, &Player_t::SlideCounter);\n        insert(0x0000005a, &Player_t::ShowWarp);\n        // pound state handled below\n        // insert(0x0000005c, &Player_t::GroundPound);\n        // insert(0x0000005e, &Player_t::GroundPound2);\n        // insert(0x00000060, &Player_t::CanPound);\n        insert(0x00000062, &Player_t::ForceHold);\n        insert(0x00000064, &Player_t::YoshiYellow);\n        insert(0x00000066, &Player_t::YoshiBlue);\n        insert(0x00000068, &Player_t::YoshiRed);\n        insert(0x0000006a, &Player_t::YoshiWingsFrame);\n        insert(0x0000006c, &Player_t::YoshiWingsFrameCount);\n        insert(0x0000006e, &Player_t::YoshiTX);\n        insert(0x00000070, &Player_t::YoshiTY);\n        insert(0x00000072, &Player_t::YoshiTFrame);\n        insert(0x00000074, &Player_t::YoshiTFrameCount);\n        insert(0x00000076, &Player_t::YoshiBX);\n        insert(0x00000078, &Player_t::YoshiBY);\n        insert(0x0000007a, &Player_t::YoshiBFrame);\n        insert(0x0000007c, &Player_t::YoshiBFrameCount);\n        //Location_t YoshiTongue; (Between 0x80 and 0xB0)\n        // insert(0x000000b0, &Player_t::YoshiTongueX);\n        insert(0x000000b4, &Player_t::YoshiTongueLength);\n        insert(0x000000b6, &Player_t::YoshiTonugeBool);\n        insert(0x000000b8, &Player_t::YoshiNPC);\n        insert(0x000000ba, &Player_t::YoshiPlayer);\n        insert(0x000000bc, &Player_t::Dismount);\n        // insert(0x000000be, &Player_t::NoPlayerCol);\n        //Location_t Location; (Between 0xC0 and 0xF0)\n        insert(0x000000f0, &Player_t::Character);\n        //Controls_t Controls; (Between 0xF2 and 0x105)\n        insert(0x00000106, &Player_t::Direction);\n        insert(0x00000108, &Player_t::Mount);\n        insert(0x0000010a, &Player_t::MountType);\n        insert(0x0000010c, &Player_t::MountSpecial);\n        insert(0x0000010e, &Player_t::MountOffsetY);\n        insert(0x00000110, &Player_t::MountFrame);\n        insert(0x00000112, &Player_t::State);\n        insert(0x00000114, &Player_t::Frame);\n        insert(0x00000118, &Player_t::FrameCount);\n        insert(0x0000011c, &Player_t::Jump);\n        insert(0x0000011e, &Player_t::CanJump);\n\n        // CanAltJump -- enforce read-only if compat flag `disable-spin-jump` is set\n        insert(0x00000120, // CanAltJump\n            [](const Player_t& p, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(p.CanAltJump, ftype);\n            },\n            [](Player_t& p, num_t in, FIELDTYPE ftype)->void\n            {\n                bool temp = p.CanAltJump;\n                memToValue(temp, in, ftype);\n                if(!g_config.disable_spin_jump)\n                    p.CanAltJump = temp;\n            }\n        );\n\n        static_assert(sizeof(Player_t::Effect) == sizeof(vbint_t), \"underlying type of Player_t::Effect must be vbint_t\");\n        insert(0x00000122, reinterpret_cast<vbint_t Player_t::*>(&Player_t::Effect));\n        insert(0x00000124, &Player_t::Effect2);\n        // pound state handled below\n        // insert(0x0000012c, &Player_t::DuckRelease);\n        insert(0x0000012e, &Player_t::Duck);\n        insert(0x00000130, &Player_t::DropRelease);\n        insert(0x00000132, &Player_t::StandUp);\n        insert(0x00000134, &Player_t::StandUp2);\n        insert(0x00000136, &Player_t::Bumped);\n        insert(0x00000138, &Player_t::Bumped2);\n        insert(0x0000013c, &Player_t::Dead);\n        insert(0x0000013e, &Player_t::TimeToLive);\n        insert(0x00000140, &Player_t::Immune);\n        insert(0x00000142, &Player_t::Immune2);\n        insert(0x00000144, &Player_t::ForceHitSpot3);\n\n        // handlers below\n        // insert(0x00000146, &Player_t::Pinched1);\n        // insert(0x00000148, &Player_t::Pinched2);\n        // insert(0x0000014a, &Player_t::Pinched3);\n        // insert(0x0000014c, &Player_t::Pinched4);\n        // insert(0x0000014e, &Player_t::NPCPinched);\n\n        // insert(0x00000150, &Player_t::m2Speed);\n        insert(0x00000154, &Player_t::HoldingNPC);\n        insert(0x00000156, &Player_t::CanGrabNPCs);\n        static_assert(sizeof(Player_t::HeldBonus) == sizeof(vbint_t), \"underlying type of Player_t::HeldBonus must be vbint_t\");\n        insert(0x00000158, reinterpret_cast<vbint_t Player_t::*>(&Player_t::HeldBonus));\n        insert(0x0000015a, &Player_t::Section);\n        insert(0x0000015c, &Player_t::WarpCD);\n        insert(0x0000015e, &Player_t::Warp);\n        insert(0x00000160, &Player_t::FireBallCD);\n        insert(0x00000162, &Player_t::FireBallCD2);\n        insert(0x00000164, &Player_t::TailCount);\n        // insert(0x00000168, &Player_t::RunCount);\n        insert(0x0000016c, &Player_t::CanFly);\n        insert(0x0000016e, &Player_t::CanFly2);\n        insert(0x00000170, &Player_t::FlyCount);\n        insert(0x00000172, &Player_t::RunRelease);\n        insert(0x00000174, &Player_t::JumpRelease);\n        insert(0x00000176, &Player_t::StandingOnNPC);\n        insert(0x00000178, &Player_t::StandingOnVehiclePlr);\n        insert(0x0000017a, &Player_t::UnStart);\n        insert(0x0000017c, &Player_t::mountBump);\n        // insert(0x00000180, &Player_t::SpeedFixY);\n    }\n\n    num_t getValue(Player_t *obj, size_t address, FIELDTYPE ftype) override\n    {\n        if(address >= 0x80 && address < 0xB0) // YoshiTongue\n            return s_spLocMem.getValue(&obj->YoshiTongue, address - 0x80, ftype);\n        else if(address >= 0xC0 && address < 0xF0) // Location\n            return s_locMem.getValue(&obj->Location, address - 0xC0, ftype);\n        else if(address >= 0xF2 && address < 0x106) // Controls\n            return s_conMem.getValue(&obj->Controls, address - 0xF2, ftype);\n        else if((address >= 0x5C && address < 0x62) || address == 0x12C) // pound state\n        {\n            switch(address)\n            {\n            case 0x5c: // GroundPound\n                return valueToMem((bool)obj->GroundPound, ftype);\n            case 0x5e: // GroundPound2\n                return valueToMem((bool)obj->GroundPound2, ftype);\n            case 0x60: // CanPound\n                return valueToMem((bool)obj->CanPound, ftype);\n            case 0x12C: // DuckRelease\n                return valueToMem((bool)obj->DuckRelease, ftype);\n            default:\n                pLogWarning(\"MemEmu: Attempt to read player address 0x%x (invalid byte hacking)\", static_cast<unsigned>(address));\n                break;\n            }\n        }\n        else if(address >= 0x146 && address < 0x150) // Pinched\n        {\n            switch(address)\n            {\n            case 0x146: // Pinched1\n                return valueToMem((int)obj->Pinched.Bottom1, ftype);\n            case 0x148: // Pinched2\n                return valueToMem((int)obj->Pinched.Left2, ftype);\n            case 0x14a: // Pinched3\n                return valueToMem((int)obj->Pinched.Top3, ftype);\n            case 0x14c: // Pinched4\n                return valueToMem((int)obj->Pinched.Right4, ftype);\n            case 0x14e: // NPCPinched\n                return valueToMem((int)obj->Pinched.Moving, ftype);\n            default:\n                pLogWarning(\"MemEmu: Attempt to read player address 0x%x (invalid byte hacking)\", static_cast<unsigned>(address));\n                break;\n            }\n        }\n        return PlayerParent::getValue(obj, address, ftype);\n    }\n\n    void setValue(Player_t *obj, size_t address, num_t value, FIELDTYPE ftype) override\n    {\n        if(address >= 0x80 && address < 0xB0) // YoshiTongue\n        {\n            s_spLocMem.setValue(&obj->YoshiTongue, static_cast<unsigned>(address) - 0x80, value, ftype);\n            return;\n        }\n        else if(address >= 0xC0 && address < 0xF0) // Location\n        {\n            s_locMem.setValue(&obj->Location, static_cast<unsigned>(address) - 0xC0, value, ftype);\n            return;\n        }\n        else if(address >= 0xF2 && address < 0x106) // Controls\n        {\n            s_conMem.setValue(&obj->Controls, static_cast<unsigned>(address) - 0xF2, value, ftype);\n            return;\n        }\n        else if((address >= 0x5C && address < 0x62) || address == 0x12C) // pound state\n        {\n            bool in = false;\n\n            memToValue(in, value, ftype);\n\n            switch(address)\n            {\n            case 0x5c: // GroundPound\n                obj->GroundPound = in;\n                return;\n            case 0x5e: // GroundPound2\n                obj->GroundPound2 = in;\n                return;\n            case 0x60: // CanPound\n                obj->CanPound = in;\n                return;\n            case 0x12C: // DuckRelease\n                obj->DuckRelease = in;\n                return;\n            default:\n                pLogWarning(\"MemEmu: Attempt to set player address 0x%x to %d (invalid byte hacking)\", static_cast<unsigned>(address), (int)in);\n                break;\n            }\n        }\n        else if(address >= 0x146 && address < 0x150) // Pinched\n        {\n            int in = 0;\n\n            memToValue(in, value, ftype);\n\n            // clamp to range\n            if(in < 0)\n                in = 0;\n            else if(in > 3)\n                in = 3;\n\n            switch(address)\n            {\n            case 0x146: // Pinched1\n                obj->Pinched.Bottom1 = in;\n                return;\n            case 0x148: // Pinched2\n                obj->Pinched.Left2 = in;\n                return;\n            case 0x14a: // Pinched3\n                obj->Pinched.Top3 = in;\n                return;\n            case 0x14c: // Pinched4\n                obj->Pinched.Right4 = in;\n                return;\n            case 0x14e: // NPCPinched\n                obj->Pinched.Moving = in;\n\n                obj->Pinched.MovingLR = (bool)in;\n                obj->Pinched.MovingUD = (bool)in;\n\n                return;\n            default:\n                pLogWarning(\"MemEmu: Attempt to set player address 0x%x to %d (invalid byte hacking)\", static_cast<unsigned>(address), in);\n                break;\n            }\n        }\n\n        PlayerParent::setValue(obj, address, value, ftype);\n    }\n};\n\nstatic constexpr char npc_t_name[] = \"NPC_t\";\ntypedef SMBXObjectMemoryEmulator<NPC_t, npc_t_name, 0x160> NpcParent;\nclass NPCMemory final : public NpcParent\n{\npublic:\n    NPCMemory() noexcept : NpcParent()\n    {\n        buildTable();\n    }\n\n    void buildTable()\n    {\n        // Note: strings that became indices have been blocked out temporarily\n        // (mememu does not support setting strings yet)\n\n        // insert(0x00000000, &NPC_t::AttLayer);\n        // insert(0x00000004, &NPC_t::Quicksand); // turned into bitfield, unused in Analogue Funk\n        // insert(0x00000006, &NPC_t::RespawnDelay); // only usable in Battle Mode\n        insert(0x00000008, // Bouce\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.Bouce, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                bool temp = n.Bouce;\n                memToValue(temp, in, ftype);\n                n.Bouce = temp;\n            }\n        );\n\n        // handler below\n        // insert(0x0000000a, &NPC_t::Pinched1);\n        // insert(0x0000000c, &NPC_t::Pinched2);\n        // insert(0x0000000e, &NPC_t::Pinched3);\n        // insert(0x00000010, &NPC_t::Pinched4);\n        // insert(0x00000012, &NPC_t::MovingPinched);\n\n        // insert(0x00000014, &NPC_t::NetTimeout); // unused since SMBX64, now removed\n        insert(0x00000018, // RealSpeedX\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.RealSpeedX, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                memToValue(n.RealSpeedX, in, ftype);\n                NPCQueues::Unchecked.push_back(n);\n            }\n        );\n        insert(0x0000001c, &NPC_t::Wet);\n        // insert(0x0000001e, &NPC_t::Settings); // unused since SMBX64, now removed\n        insert(0x00000020, // NoLavaSplash\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.NoLavaSplash, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                bool temp = n.NoLavaSplash;\n                memToValue(temp, in, ftype);\n                n.NoLavaSplash = temp;\n            }\n        );\n        insert(0x00000022, &NPC_t::Slope);\n        insert(0x00000024, // Multiplier\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.Multiplier, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                memToValue(n.Multiplier, in, ftype);\n                NPCQueues::Unchecked.push_back(n);\n            }\n        );\n        insert(0x00000026, // TailCD\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.TailCD, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                memToValue(n.TailCD, in, ftype);\n                NPCQueues::Unchecked.push_back(n);\n            }\n        );\n        // insert(0x00000028, &NPC_t::Shadow); // turned into bitfield, unused in Analogue Funk\n        // insert(0x0000002c, &NPC_t::TriggerActivate);\n        // insert(0x00000030, &NPC_t::TriggerDeath);\n        // insert(0x00000034, &NPC_t::TriggerTalk);\n        // insert(0x00000038, &NPC_t::TriggerLast);\n        // insert(0x0000003c, &NPC_t::Layer);\n        insert(0x00000040, &NPC_t::Hidden);\n        insert(0x00000042, // Legacy\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.Legacy, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                bool temp = n.Legacy;\n                memToValue(temp, in, ftype);\n                n.Legacy = temp;\n            }\n        );\n        insert(0x00000044, // Chat\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.Chat, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                bool temp = n.Chat;\n                memToValue(temp, in, ftype);\n                n.Chat = temp;\n            }\n        );\n        insert(0x00000046, &NPC_t::Inert);\n        // insert(0x00000048, &NPC_t::Stuck); // turned into bitfield, unused in Analogue Funk\n        // insert(0x0000004a, &NPC_t::DefaultStuck); // turned into bitfield, unused in Analogue Funk\n        // insert(0x0000004c, &NPC_t::Text);\n        insert(0x00000050, &NPC_t::oldAddBelt);\n        // insert(0x00000054, &NPC_t::PinchCount); // unused since SMBX64, now removed\n        // insert(0x00000056, &NPC_t::Pinched); // unused since SMBX64, now removed\n        // insert(0x00000058, &NPC_t::PinchedDirection); // unused since SMBX64, now removed\n        insert(0x0000005c, &NPC_t::BeltSpeed);\n        insert(0x00000060, &NPC_t::vehiclePlr);\n        insert(0x00000062, &NPC_t::vehicleYOffset);\n        // insert(0x00000064, // Generator // turned into bitfield, unused in Analogue Funk\n        //     [](const NPC_t& n, FIELDTYPE ftype)->num_t\n        //     {\n        //         return valueToMem(n.Generator, ftype);\n        //     },\n        //     [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n        //     {\n        //         memToValue(n.Generator, in, ftype);\n\n        //         if(n.Generator)\n        //             NPCQueues::Active.insert(n);\n        //         else if(!NPCQueues::check_active(n))\n        //             NPCQueues::Active.erase(n);\n        //     }\n        // );\n        // all removed\n        // insert(0x00000068, &NPC_t::GeneratorTimeMax);\n        // insert(0x0000006c, &NPC_t::GeneratorTime);\n        // insert(0x00000070, &NPC_t::GeneratorDirection);\n        // insert(0x00000072, &NPC_t::GeneratorEffect);\n        // insert(0x00000074, &NPC_t::GeneratorActive); // turned into bitfield, unused in Analogue Funk\n        insert(0x00000076, // playerTemp\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.playerTemp, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                bool prev = n.playerTemp;\n\n                bool temp = n.playerTemp;\n                memToValue(temp, in, ftype);\n                n.playerTemp = temp;\n\n                if(!prev && n.playerTemp)\n                    NPCQueues::PlayerTemp.push_back(n);\n            }\n        );\n        // insert(0x00000078, &NPC_t::Location); // between 0x78 and 0xA8\n        // insert(0x000000a8, &NPC_t::DefaultLocation); // between 0xA8 and 0xD8\n        insert(0x000000a8, &NPC_t::DefaultLocationX); // 0xA8\n        insert(0x000000b0, &NPC_t::DefaultLocationY); // 0xB0\n        insert(0x000000d8, // DefaultDirection\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem((vbint_t)n.DefaultDirection, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                vbint_t direction = 0;\n                memToValue(direction, in, ftype);\n\n                n.DefaultDirection = (int8_t)direction;\n            }\n        );\n        static_assert(sizeof(NPC_t::DefaultType) == sizeof(vbint_t), \"underlying type of NPC_t::DefaultType must be vbint_t\");\n        insert(0x000000dc, reinterpret_cast<vbint_t NPC_t::*>(&NPC_t::DefaultType));\n        insert(0x000000de, &NPC_t::DefaultSpecial);\n        // insert(0x000000e0, &NPC_t::DefaultSpecial2); // removed, was unused except by NPCID_MAGIC_DOOR and friends\n        insert(0x000000e2, // Type\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.Type, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                int type = NPCID(0);\n                memToValue(type, in, ftype);\n\n                n.Type = NPCID(type);\n\n                // may have switched to/from an always-active type\n                if(NPCQueues::check_active(n))\n                    NPCQueues::Active.insert(n);\n                else\n                    NPCQueues::Active.erase(n);\n            }\n        );\n        insert(0x000000e4, &NPC_t::Frame);\n        insert(0x000000e8, &NPC_t::FrameCount);\n        insert(0x000000ec, &NPC_t::Direction);\n        insert(0x000000f0, &NPC_t::Special);\n        insert(0x000000f8, &NPC_t::Special2);\n        insert(0x00000100, &NPC_t::Special3);\n        insert(0x00000108, &NPC_t::Special4);\n        insert(0x00000110, &NPC_t::Special5);\n        // insert(0x00000118, &NPC_t::Special6); // removed!\n        insert(0x00000120, // TurnAround\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.TurnAround, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                bool temp = n.TurnAround;\n                memToValue(temp, in, ftype);\n                n.TurnAround = temp;\n            }\n        );\n        insert(0x00000122, // Killed\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.Killed, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                bool prev = (n.Killed != 0);\n\n                memToValue(n.Killed, in, ftype);\n\n                if(!prev && n.Killed != 0)\n                    NPCQueues::Killed.push_back(n);\n            }\n        );\n        insert(0x00000124, // Active\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.Active, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                memToValue(n.Active, in, ftype);\n\n                if(NPCQueues::check_active(n))\n                    NPCQueues::Active.insert(n);\n                else\n                {\n                    NPCQueues::Active.erase(n);\n                    NPCQueues::Unchecked.push_back(n);\n                }\n            }\n        );\n        // insert(0x00000126, &NPC_t::Reset);\n        insert(0x0000012a, &NPC_t::TimeLeft);\n        insert(0x0000012c, &NPC_t::HoldingPlayer);\n        insert(0x0000012e, &NPC_t::CantHurt);\n        insert(0x00000130, &NPC_t::CantHurtPlayer);\n        insert(0x00000132, &NPC_t::BattleOwner);\n        insert(0x00000134, &NPC_t::WallDeath);\n        insert(0x00000136, &NPC_t::Projectile);\n        static_assert(sizeof(NPC_t::Effect) == sizeof(vbint_t), \"underlying type of NPC_t::Effect must be vbint_t\");\n        insert(0x00000138, reinterpret_cast<vbint_t NPC_t::*>(&NPC_t::Effect));\n        insert(0x0000013c, &NPC_t::Effect2);\n        insert(0x00000144, &NPC_t::Effect3);\n        insert(0x00000146, &NPC_t::Section);\n        insert(0x00000148, &NPC_t::Damage);\n        insert(0x0000014c, // JustActivated\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.JustActivated, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                memToValue(n.JustActivated, in, ftype);\n\n                if(n.JustActivated)\n                    NPCQueues::Active.insert(n);\n                else if(!NPCQueues::check_active(n))\n                    NPCQueues::Active.erase(n);\n            }\n        );\n        // insert(0x0000014e, &NPC_t::coinSwitchBlockType); // never-used for non-coins, now shares storage with Damage (never-used for coins)\n        insert(0x00000150, &NPC_t::tempBlock);\n        // insert(0x00000152, &NPC_t::onWall); // removed temporary variable\n        // insert(0x00000154, &NPC_t::TurnBackWipe); // turned into bitfield, unused in Analogue Funk\n        insert(0x00000156, // Immune\n            [](const NPC_t& n, FIELDTYPE ftype)->num_t\n            {\n                return valueToMem(n.Immune, ftype);\n            },\n            [](NPC_t& n, num_t in, FIELDTYPE ftype)->void\n            {\n                memToValue(n.Immune, in, ftype);\n                NPCQueues::Unchecked.push_back(n);\n            }\n        );\n    }\n\n    num_t getValue(NPC_t *obj, size_t address, FIELDTYPE ftype) override\n    {\n        if(address >= 0x78 && address < 0xA8) // Location\n            return s_locMem.getValue(&obj->Location, address - 0x78, ftype);\n        else if(address >= 0xB8 && address < 0xD8) // invalid part of DefaultLocation\n            pLogWarning(\"MemEmu: Attempt to read NPC address 0x%x (removed part of DefaultLocation)\", static_cast<unsigned>(address));\n        else if(address == 0x126)\n            return obj->Reset[1] ? 0xFFFF : 0;\n        else if(address == 0x128)\n            return obj->Reset[2] ? 0xFFFF : 0;\n        else if(address >= 0x0A && address < 0x14) // Pinched\n        {\n            switch(address)\n            {\n            case 0x0A: // Pinched1\n                return valueToMem((short)obj->Pinched.Bottom1, ftype);\n            case 0x0C: // Pinched2\n                return valueToMem((short)obj->Pinched.Left2, ftype);\n            case 0x0E: // Pinched3\n                return valueToMem((short)obj->Pinched.Top3, ftype);\n            case 0x10: // Pinched4\n                return valueToMem((short)obj->Pinched.Right4, ftype);\n            case 0x12: // MovingPinched\n                return valueToMem((short)obj->Pinched.Moving, ftype);\n            default:\n                pLogWarning(\"MemEmu: Attempt to read NPC address 0x%x (invalid byte hacking)\", static_cast<unsigned>(address));\n                break;\n            }\n        }\n        return NpcParent::getValue(obj, address, ftype);\n    }\n\n    void setValue(NPC_t *obj, size_t address, num_t value, FIELDTYPE ftype) override\n    {\n        if(address >= 0x78 && address < 0xA8) // Location\n        {\n            s_locMem.setValue(&obj->Location, address - 0x78, value, ftype);\n\n            NPCQueues::Unchecked.push_back(obj);\n            treeNPCUpdate(obj);\n            return;\n        }\n        else if(address >= 0xB8 && address < 0xD8) // DefaultLocation, invalid part\n        {\n            pLogWarning(\"MemEmu: Attempt to set NPC address 0x%x (removed part of DefaultLocation)\", static_cast<unsigned>(address));\n            return;\n        }\n        else if(address == 0x126)\n        {\n            obj->Reset[1] = value != 0;\n\n            if(value == 0)\n                NPCQueues::NoReset.push_back(obj);\n\n            return;\n        }\n        else if(address == 0x128)\n        {\n            obj->Reset[2] = value != 0;\n\n            if(value == 0)\n                NPCQueues::NoReset.push_back(obj);\n\n            return;\n        }\n        else if(address >= 0x0A && address < 0x14) // Pinched\n        {\n            int in = 0;\n\n            memToValue(in, value, ftype);\n\n            // clamp to range\n            if(in < 0)\n                in = 0;\n            else if(in > 3)\n                in = 3;\n\n            switch(address)\n            {\n            case 0x0A: // Pinched1\n                obj->Pinched.Bottom1 = in;\n                return;\n            case 0x0C: // Pinched2\n                obj->Pinched.Left2 = in;\n                return;\n            case 0x0E: // Pinched3\n                obj->Pinched.Top3 = in;\n                return;\n            case 0x10: // Pinched4\n                obj->Pinched.Right4 = in;\n                return;\n            case 0x12: // MovingPinched\n                obj->Pinched.Moving = in;\n                return;\n            default:\n                pLogWarning(\"MemEmu: Attempt to set NPC address 0x%x to %d (invalid byte hacking)\", static_cast<unsigned>(address), in);\n                return;\n            }\n        }\n\n        NpcParent::setValue(obj, address, value, ftype);\n\n#if 0 // Layer sync hook, but setting it is not yet supported.\n        if(address == 0x3C)\n        {\n            int index = obj - &NPC[0];\n            if(index >= -128 && index <= maxNPCs)\n                syncLayers_NPC(index);\n        }\n#endif\n    }\n};\n\nstatic SMBXMemoryEmulator   s_emu;\nstatic PlayerMemory         s_emuPlayer;\nstatic NPCMemory            s_emuNPC;\n\ntemplate<typename T, class D>\nSDL_FORCE_INLINE void opAdd(D &mem, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(addr, ftype);\n    T res = static_cast<T>(o1) + static_cast<T>(o2);\n    mem.setValue(addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<typename T, class D>\nSDL_FORCE_INLINE void opSub(D &mem, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(addr, ftype);\n    T res = static_cast<T>(o1) - static_cast<T>(o2);\n    mem.setValue(addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<typename T, class D>\nSDL_FORCE_INLINE void opMul(D &mem, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(addr, ftype);\n    T res = static_cast<T>(o1) * static_cast<T>(o2);\n    mem.setValue(addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<class D>\nSDL_FORCE_INLINE void opMul_numf_t(D &mem, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(addr, ftype);\n    numf_t res = static_cast<numf_t>(o1).times(static_cast<numf_t>(o2));\n    mem.setValue(addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<class D>\nSDL_FORCE_INLINE void opMul_num_t(D &mem, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(addr, ftype);\n    num_t res = o1.times(o2);\n    mem.setValue(addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<typename T, class D>\nSDL_FORCE_INLINE void opDiv(D &mem, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(addr, ftype);\n    T res = static_cast<T>(o1) / static_cast<T>(o2);\n    mem.setValue(addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<class D>\nSDL_FORCE_INLINE void opDiv_numf_t(D &mem, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(addr, ftype);\n    numf_t res = static_cast<numf_t>(o1).divided_by(static_cast<numf_t>(o2));\n    mem.setValue(addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<class D>\nSDL_FORCE_INLINE void opDiv_num_t(D &mem, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(addr, ftype);\n    num_t res = o1.divided_by(o2);\n    mem.setValue(addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<typename T, class D>\nSDL_FORCE_INLINE void opXor(D &mem, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(addr, ftype);\n    T res = static_cast<T>(o1) ^ static_cast<T>(o2);\n    mem.setValue(addr, static_cast<num_t>(res), ftype);\n}\n\n\nvoid MemAssign(size_t address, num_t value, OPTYPE operation, FIELDTYPE ftype)\n{\n    if((address < GM_BASE) || (address > GM_END))\n    {\n        pLogWarning(\"MemEmu: MemAssign Requested value of out-of-range global address: 0x%x\", static_cast<unsigned>(address));\n        return;\n    }\n\n    if(ftype == FT_INVALID)\n        return;\n\n    if(operation == OP_Div && value == 0)\n        return;\n\n    switch(operation)\n    {\n    case OP_Assign:\n        s_emu.setValue(address, value, ftype);\n        break;\n\n    case OP_Add:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opAdd<uint8_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opAdd<int16_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opAdd<int32_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_FLOAT:\n        {\n            opAdd<numf_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_DFLOAT:\n            opAdd<num_t>(s_emu, address, value, ftype);\n            break;\n        default:\n            break;\n        }\n    }//OP Add\n    break;\n\n    case OP_Sub:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opSub<uint8_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opSub<int16_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opSub<int32_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_FLOAT:\n        {\n            opSub<numf_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_DFLOAT:\n            opSub<num_t>(s_emu, address, value, ftype);\n            break;\n        default:\n            break;\n        }\n    }//OP Sub\n    break;\n\n    case OP_Mult:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opMul<uint8_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opMul<int16_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opMul<int32_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_FLOAT:\n        {\n            opMul_numf_t(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_DFLOAT:\n            opMul_num_t(s_emu, address, value, ftype);\n            break;\n        default:\n            break;\n        }\n    }//OP Mult\n    break;\n\n    case OP_Div:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opDiv<uint8_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opDiv<int16_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opDiv<int32_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_FLOAT:\n        {\n            opDiv_numf_t(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_DFLOAT:\n            opDiv_num_t(s_emu, address, value, ftype);\n            break;\n        default:\n            break;\n        }\n    }//OP Div\n    break;\n\n    case OP_XOR:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opXor<uint8_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opXor<int16_t>(s_emu, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opXor<int32_t>(s_emu, address, value, ftype);\n            break;\n        }\n        default:\n            break;\n        }\n    }//OP XOR\n    break;\n\n    default:\n        break;\n    }// switch on op\n}\n\nbool CheckMem(size_t address, num_t value, COMPARETYPE ctype, FIELDTYPE ftype)\n{\n    if((address < GM_BASE) || (address > GM_END))\n    {\n        pLogWarning(\"MemEmu: CheckMem Requested value of out-of-range global address: 0x%x\", static_cast<unsigned>(address));\n        return false;\n    }\n\n    num_t cur = s_emu.getValue(address, ftype);\n\n    switch(ctype)\n    {\n    case CMPT_EQUALS:\n        switch(ftype)\n        {\n        case FT_BYTE:\n            return static_cast<uint8_t>(cur) == static_cast<uint8_t>(value);\n        case FT_WORD:\n            return static_cast<int16_t>(cur) == static_cast<int16_t>(value);\n        case FT_DWORD:\n            return static_cast<int32_t>(cur) == static_cast<int32_t>(value);\n        case FT_FLOAT:\n            return num_t::fEqual_f(static_cast<numf_t>(cur), static_cast<numf_t>(value));\n        case FT_DFLOAT:\n            return num_t::fEqual_d(cur, value);\n        default:\n            return false;\n        }\n\n    case CMPT_GREATER:\n        switch(ftype)\n        {\n        case FT_BYTE:\n            return static_cast<uint8_t>(cur) > static_cast<uint8_t>(value);\n        case FT_WORD:\n            return static_cast<int16_t>(cur) > static_cast<int16_t>(value);\n        case FT_DWORD:\n            return static_cast<int32_t>(cur) > static_cast<int32_t>(value);\n        case FT_FLOAT:\n            return static_cast<numf_t>(cur) > static_cast<numf_t>(value);\n        case FT_DFLOAT:\n            return cur > value;\n        default:\n            return false;\n        }\n\n    case CMPT_LESS:\n        switch(ftype)\n        {\n        case FT_BYTE:\n            return static_cast<uint8_t>(cur) < static_cast<uint8_t>(value);\n        case FT_WORD:\n            return static_cast<int16_t>(cur) < static_cast<int16_t>(value);\n        case FT_DWORD:\n            return static_cast<int32_t>(cur) < static_cast<int32_t>(value);\n        case FT_FLOAT:\n            return static_cast<numf_t>(cur) < static_cast<numf_t>(value);\n        case FT_DFLOAT:\n            return cur < value;\n        default:\n            return false;\n        }\n\n    case CMPT_NOTEQ:\n        switch(ftype)\n        {\n        case FT_BYTE:\n            return static_cast<uint8_t>(cur) != static_cast<uint8_t>(value);\n        case FT_WORD:\n            return static_cast<int16_t>(cur) != static_cast<int16_t>(value);\n        case FT_DWORD:\n            return static_cast<int32_t>(cur) != static_cast<int32_t>(value);\n        case FT_FLOAT:\n            return !num_t::fEqual_f(static_cast<numf_t>(cur), static_cast<numf_t>(value));\n        case FT_DFLOAT:\n            return !num_t::fEqual_d(cur, value);\n        default:\n            return false;\n        }\n    }\n\n    return false;\n}\n\nnum_t GetMem(size_t addr, FIELDTYPE ftype)\n{\n    if((addr < GM_BASE) || (addr > GM_END))\n    {\n        pLogWarning(\"MemEmu: GetMem Requested value of out-of-range global address: 0x%x\", static_cast<unsigned>(addr));\n        return 0;\n    }\n\n    num_t cur = s_emu.getValue(addr, ftype);\n\n    switch(ftype)\n    {\n    case FT_BYTE:\n        return static_cast<num_t>(static_cast<uint8_t>(cur));\n    case FT_WORD:\n        return static_cast<num_t>(static_cast<int16_t>(cur));\n    case FT_DWORD:\n        return static_cast<num_t>(static_cast<int32_t>(cur));\n    case FT_FLOAT:\n        return static_cast<num_t>(static_cast<numf_t>(cur));\n    default:\n    case FT_DFLOAT:\n        return cur;\n    }\n}\n\n\ntemplate<typename T, class D, class U>\nSDL_FORCE_INLINE void opAdd(D &mem, U *obj, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(obj, addr, ftype);\n    T res = static_cast<T>(o1) + static_cast<T>(o2);\n    mem.setValue(obj, addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<typename T, class D, class U>\nSDL_FORCE_INLINE void opSub(D &mem, U *obj, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(obj, addr, ftype);\n    T res = static_cast<T>(o1) - static_cast<T>(o2);\n    mem.setValue(obj, addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<typename T, class D, class U>\nSDL_FORCE_INLINE void opMul(D &mem, U *obj, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(obj, addr, ftype);\n    T res = static_cast<T>(o1) * static_cast<T>(o2);\n    mem.setValue(obj, addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<class D, class U>\nSDL_FORCE_INLINE void opMul_numf_t(D &mem, U *obj, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(obj, addr, ftype);\n    numf_t res = static_cast<numf_t>(o1).times(static_cast<numf_t>(o2));\n    mem.setValue(obj, addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<class D, class U>\nSDL_FORCE_INLINE void opMul_num_t(D &mem, U *obj, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(obj, addr, ftype);\n    num_t res = o1.times(o2);\n    mem.setValue(obj, addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<typename T, class D, class U>\nSDL_FORCE_INLINE void opDiv(D &mem, U *obj, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(obj, addr, ftype);\n    T res = static_cast<T>(o1) / static_cast<T>(o2);\n    mem.setValue(obj, addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<class D, class U>\nSDL_FORCE_INLINE void opDiv_numf_t(D &mem, U *obj, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(obj, addr, ftype);\n    numf_t res = static_cast<numf_t>(o1).divided_by(static_cast<numf_t>(o2));\n    mem.setValue(obj, addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<class D, class U>\nSDL_FORCE_INLINE void opDiv_num_t(D &mem, U *obj, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(obj, addr, ftype);\n    num_t res = o1.divided_by(o2);\n    mem.setValue(obj, addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<typename T, class D, class U>\nSDL_FORCE_INLINE void opXor(D &mem, U *obj, size_t addr, num_t o2, FIELDTYPE ftype)\n{\n    num_t o1 = mem.getValue(obj, addr, ftype);\n    T res = static_cast<T>(o1) ^ static_cast<T>(o2);\n    mem.setValue(obj, addr, static_cast<num_t>(res), ftype);\n}\n\ntemplate<class T, class U>\nstatic void MemAssignType(T &mem, U *obj, size_t address, num_t value, OPTYPE operation, FIELDTYPE ftype)\n{\n    if(ftype == FT_INVALID)\n        return;\n\n    if(operation == OP_Div && value == 0)\n        return;\n\n    switch(operation)\n    {\n    case OP_Assign:\n        mem.setValue(obj, address, value, ftype);\n        break;\n\n    case OP_Add:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opAdd<uint8_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opAdd<int16_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opAdd<int32_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_FLOAT:\n        {\n            opAdd<numf_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_DFLOAT:\n            opAdd<num_t>(mem, obj, address, value, ftype);\n            break;\n        default:\n            break;\n        }\n    }//OP Add\n    break;\n\n    case OP_Sub:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opSub<uint8_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opSub<int16_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opSub<int32_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_FLOAT:\n        {\n            opSub<numf_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_DFLOAT:\n            opSub<num_t>(mem, obj, address, value, ftype);\n            break;\n        default:\n            break;\n        }\n    }//OP Sub\n    break;\n\n    case OP_Mult:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opMul<uint8_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opMul<int16_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opMul<int32_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_FLOAT:\n        {\n            opMul_numf_t(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_DFLOAT:\n            opMul_num_t(mem, obj, address, value, ftype);\n            break;\n        default:\n            break;\n        }\n    }//OP Mult\n    break;\n\n    case OP_Div:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opDiv<uint8_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opDiv<int16_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opDiv<int32_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_FLOAT:\n        {\n            opDiv_numf_t(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_DFLOAT:\n            opDiv_num_t(mem, obj, address, value, ftype);\n            break;\n        default:\n            break;\n        }\n    }//OP Div\n    break;\n\n    case OP_XOR:\n    {\n        switch(ftype)\n        {\n        case FT_BYTE:\n        {\n            opXor<uint8_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_WORD:\n        {\n            opXor<int16_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        case FT_DWORD:\n        {\n            opXor<int32_t>(mem, obj, address, value, ftype);\n            break;\n        }\n        default:\n            break;\n        }\n    }//OP XOR\n    break;\n\n    default:\n        break;\n    }// switch on op\n}\n\ntemplate<class T, class U>\nstatic bool ChecmMemType(T &mem, U *obj, size_t offset, num_t value, COMPARETYPE ctype, FIELDTYPE ftype)\n{\n    num_t cur = mem.getValue(obj, offset, ftype);\n\n    switch(ctype)\n    {\n    case CMPT_EQUALS:\n        switch(ftype)\n        {\n        case FT_BYTE:\n            return static_cast<uint8_t>(cur) == static_cast<uint8_t>(value);\n        case FT_WORD:\n            return static_cast<int16_t>(cur) == static_cast<int16_t>(value);\n        case FT_DWORD:\n            return static_cast<int32_t>(cur) == static_cast<int32_t>(value);\n        case FT_FLOAT:\n            return num_t::fEqual_f(static_cast<numf_t>(cur), static_cast<numf_t>(value));\n        case FT_DFLOAT:\n            return num_t::fEqual_d(cur, value);\n        default:\n            return false;\n        }\n\n    case CMPT_GREATER:\n        switch(ftype)\n        {\n        case FT_BYTE:\n            return static_cast<uint8_t>(cur) > static_cast<uint8_t>(value);\n        case FT_WORD:\n            return static_cast<int16_t>(cur) > static_cast<int16_t>(value);\n        case FT_DWORD:\n            return static_cast<int32_t>(cur) > static_cast<int32_t>(value);\n        case FT_FLOAT:\n            return static_cast<numf_t>(cur) > static_cast<numf_t>(value);\n        case FT_DFLOAT:\n            return cur > value;\n        default:\n            return false;\n        }\n\n    case CMPT_LESS:\n        switch(ftype)\n        {\n        case FT_BYTE:\n            return static_cast<uint8_t>(cur) < static_cast<uint8_t>(value);\n        case FT_WORD:\n            return static_cast<int16_t>(cur) < static_cast<int16_t>(value);\n        case FT_DWORD:\n            return static_cast<int32_t>(cur) < static_cast<int32_t>(value);\n        case FT_FLOAT:\n            return static_cast<numf_t>(cur) < static_cast<numf_t>(value);\n        case FT_DFLOAT:\n            return cur < value;\n        default:\n            return false;\n        }\n\n    case CMPT_NOTEQ:\n        switch(ftype)\n        {\n        case FT_BYTE:\n            return static_cast<uint8_t>(cur) != static_cast<uint8_t>(value);\n        case FT_WORD:\n            return static_cast<int16_t>(cur) != static_cast<int16_t>(value);\n        case FT_DWORD:\n            return static_cast<int32_t>(cur) != static_cast<int32_t>(value);\n        case FT_FLOAT:\n            return !num_t::fEqual_f(static_cast<numf_t>(cur), static_cast<numf_t>(value));\n        case FT_DFLOAT:\n            return !num_t::fEqual_d(cur, value);\n        default:\n            return false;\n        }\n    }\n\n    return false;\n}\n\n\n// #define DEBUG_MEMEMU_TRACE\n\nvoid MemAssign(Player_t *obj, size_t address, num_t value, OPTYPE operation, FIELDTYPE ftype)\n{\n#ifdef DEBUG_MEMEMU_TRACE\n    D_pLogDebug(\"Player mem TRACE: Write 0x%08X, %g, op-%d, ft-%d\", address, value, (int)operation, (int)ftype);\n#endif\n    MemAssignType(s_emuPlayer, obj, address, value, operation, ftype);\n}\n\nbool CheckMem(Player_t *obj, size_t offset, num_t value, COMPARETYPE ctype, FIELDTYPE ftype)\n{\n#ifdef DEBUG_MEMEMU_TRACE\n    D_pLogDebug(\"Player mem TRACE: Compare 0x%08X, %g, cp-%d, ft-%d\", offset, value, (int)ctype, (int)ftype);\n#endif\n    return ChecmMemType(s_emuPlayer, obj, offset, value, ctype, ftype);\n}\n\nnum_t GetMem(Player_t *obj, size_t offset, FIELDTYPE ftype)\n{\n#ifdef DEBUG_MEMEMU_TRACE\n    num_t value = s_emuPlayer.getValue(obj, offset, ftype);\n    D_pLogDebug(\"Player mem TRACE: Read 0x%08X, %g, ft-%d\", offset, value, (int)ftype);\n    return value;\n#else\n    return s_emuPlayer.getValue(obj, offset, ftype);\n#endif\n}\n\n\nvoid MemAssign(NPC_t *obj, size_t address, num_t value, OPTYPE operation, FIELDTYPE ftype)\n{\n#ifdef DEBUG_MEMEMU_TRACE\n    D_pLogDebug(\"NPC mem TRACE: Write 0x%08X, %g, op-%d, ft-%d\", address, value, (int)operation, (int)ftype);\n#endif\n    MemAssignType(s_emuNPC, obj, address, value, operation, ftype);\n}\n\nbool CheckMem(NPC_t *obj, size_t offset, num_t value, COMPARETYPE ctype, FIELDTYPE ftype)\n{\n#ifdef DEBUG_MEMEMU_TRACE\n    D_pLogDebug(\"NPC mem TRACE: Compare 0x%08X, %g, cp-%d, ft-%d\", offset, value, (int)ctype, (int)ftype);\n#endif\n    return ChecmMemType(s_emuNPC, obj, offset, value, ctype, ftype);\n}\n\nnum_t GetMem(NPC_t *obj, size_t offset, FIELDTYPE ftype)\n{\n#ifdef DEBUG_MEMEMU_TRACE\n    num_t value = s_emuNPC.getValue(obj, offset, ftype);\n    D_pLogDebug(\"NPC mem TRACE: Read 0x%08X, %g, ft-%d\", offset, value, (int)ftype);\n    return value;\n#else\n    return s_emuNPC.getValue(obj, offset, ftype);\n#endif\n}\n"
  },
  {
    "path": "src/script/luna/mememu.h",
    "content": "﻿/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef MEMEMU_H\n#define MEMEMU_H\n\n#include <cstddef>\n#include \"lunadefs.h\"\n\nstruct Player_t;\nstruct NPC_t;\n\n#define GM_BASE             0x00B25000\n#define GM_END              0x00B2E000\n\n//Global\nvoid MemAssign(size_t address, num_t value, OPTYPE operation, FIELDTYPE ftype);\nbool CheckMem(size_t address, num_t value, COMPARETYPE ctype, FIELDTYPE ftype);\nnum_t GetMem(size_t addr, FIELDTYPE ftype);\n\n// Player relative\nvoid MemAssign(Player_t *obj, size_t address, num_t value, OPTYPE operation, FIELDTYPE ftype);\nbool CheckMem(Player_t *obj, size_t offset, num_t value, COMPARETYPE ctype, FIELDTYPE ftype);\nnum_t GetMem(Player_t *obj, size_t offset, FIELDTYPE ftype);\n\n// NPC relative\nvoid MemAssign(NPC_t *obj, size_t address, num_t value, OPTYPE operation, FIELDTYPE ftype);\nbool CheckMem(NPC_t *obj, size_t offset, num_t value, COMPARETYPE ctype, FIELDTYPE ftype);\nnum_t GetMem(NPC_t *obj, size_t offset, FIELDTYPE ftype);\n\n#endif // MEMEMU_H\n"
  },
  {
    "path": "src/script/luna/renderop.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef RENDEROP_H\n#define RENDEROP_H\n\n#include \"lunarender.h\"\n#include \"sdl_proxy/sdl_assert.h\"\n\n#include \"xt_color.h\"\n#include \"draw_planes.h\"\n\nstatic const PLANE RENDEROP_PRIORITY_MIN = PLANE_INTERNAL_BG;\nstatic const PLANE RENDEROP_PRIORITY_MAX = PLANE_INTERNAL_FG;\nstatic const PLANE RENDEROP_DEFAULT_PRIORITY_RENDEROP = (PLANE)(PLANE_LVL_HUD - 2); // Default priority for RenderOp and RenderImage\nstatic const PLANE RENDEROP_DEFAULT_PRIORITY_CGFX = (PLANE)(PLANE_LVL_HUD - 1); // Default priority for Custom GFX\nstatic const PLANE RENDEROP_DEFAULT_PRIORITY_TEXT = (PLANE)(PLANE_LVL_HUD); // Default priority for Text\n\nstatic const PLANE RENDEROP_DEFAULT_PRIORITY_RENDEROP_SCENE = (PLANE)(PLANE_LVL_SECTION_FG - 2); // Default priority for RenderOp and RenderImage (scene)\nstatic const PLANE RENDEROP_DEFAULT_PRIORITY_CGFX_SCENE = (PLANE)(PLANE_LVL_SECTION_FG - 1); // Default priority for Custom GFX (scene)\nstatic const PLANE RENDEROP_DEFAULT_PRIORITY_TEXT_SCENE = (PLANE)(PLANE_LVL_SECTION_FG); // Default priority for Text (scene)\n\n// Base class respresenting a rendering operation\n// Rendering operations include a draw function and a count of how many frames of activity remain\nclass RenderOp\n{\npublic:\n    RenderOp() : m_FramesLeft(1), m_selectedCamera(0), m_renderPriority(RENDEROP_DEFAULT_PRIORITY_RENDEROP) {}\n    explicit RenderOp(PLANE priority) : m_FramesLeft(1), m_selectedCamera(0), m_renderPriority(priority) {}\n    virtual ~RenderOp() = default;\n    virtual void Draw(Renderer* /*renderer*/) {}\n\n    inline void* operator new(size_t size)\n    {\n        // Note: If you creating any chunks with a size bigger than current size, please increase it\n        SDL_assert_release(size < c_rAllocChunkSize);\n        auto *ret = g_rAlloc.Allocate(c_rAllocChunkSize);\n        return ret;\n    }\n\n    inline void operator delete(void* memory)\n    {\n        g_rAlloc.Free(memory);\n    }\n\n    int m_FramesLeft;\t\t// How many frames until this op should be destroyed\n    int m_selectedCamera;\n    PLANE m_renderPriority;\n};\n\n#endif // RENDEROP_H\n"
  },
  {
    "path": "src/script/luna/renderop_bitmap.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"renderop_bitmap.h\"\n#include \"core/render.h\"\n#include \"globals.h\"\n#include \"lunaimgbox.h\"\n\n\nRenderBitmapOp::RenderBitmapOp() : RenderOp()\n{\n    static_assert(sizeof(RenderBitmapOp) <= c_rAllocChunkSize,\n                  \"Size of RenderBitmapOp class must be smaller than c_rAllocChunkSize\");\n}\n\nvoid RenderBitmapOp::Draw(Renderer *renderer)\n{\n    if(!direct_img || (direct_img->getH() == 0) || (direct_img->getW() == 0))\n        return;\n\n    int screenX = this->x;\n    int screenY = this->y;\n\n    if(sceneCoords)\n    {\n        screenX -= vScreen[renderer->GetCameraIdx()].CameraAddX_i();\n        screenY -= vScreen[renderer->GetCameraIdx()].CameraAddY_i();\n    }\n    else\n    {\n        Render::TranslateScreenCoords(screenX, screenY, this->sw, this->sh);\n    }\n\n    // Get integer values as current rendering backends prefer that\n    int x = screenX;\n    int y = screenY;\n    int sx = this->sx;\n    int sy = this->sy;\n    int width = this->sw;\n    int height = this->sh;\n\n    // Trim height/width if necessary\n    if(direct_img->getW() < width + sx)\n        width = direct_img->getW() - sx;\n\n    if(direct_img->getH() < height + sy)\n        height = direct_img->getH() - sy;\n\n    // Don't render if no size\n    if((width <= 0) || (height <= 0))\n        return;\n\n    XRender::renderTextureBasic(x, y, width, height, direct_img->m_image, sx, sy, color);\n}\n"
  },
  {
    "path": "src/script/luna/renderop_bitmap.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef RENDERBITMAPOP_H\n#define RENDERBITMAPOP_H\n\n#include <memory>\n#include \"renderop.h\"\n\nclass RenderBitmapOp final : public RenderOp\n{\npublic:\n    RenderBitmapOp();\n    ~RenderBitmapOp() override = default;\n\n    void Draw(Renderer* renderer) override;\n\n    inline void* operator new(size_t size)\n    {\n        // Note: If you creating any chunks with a size bigger than current size, please increase it\n        SDL_assert_release(size < c_rAllocChunkSize);\n        auto *ret = g_rAlloc.Allocate(c_rAllocChunkSize);\n        return ret;\n    }\n\n    inline void operator delete(void* memory)\n    {\n        g_rAlloc.Free(memory);\n    }\n\n    int x = 0;\t\t\t\t// Absolute screen x position\n    int y = 0;\t\t\t\t// Absolute screen y position\n    int sx = 0;\t\t\t\t// Source x1 (left edge)\n    int sy = 0;\t\t\t\t// Source y1 (top edge)\n    int sw = 0;\t\t\t\t// Source x1 (right edge)\n    int sh = 0;\t\t\t\t// Source x1 (bottom edge)\n\n    XTColor color = XTAlpha(255);         // Opacity value\n    bool    sceneCoords = false;     // If true, x and y are scene coordinates\n\n    LunaImage *direct_img = nullptr;\n};\n\n#endif // RENDERBITMAPOP_H\n"
  },
  {
    "path": "src/script/luna/renderop_effect.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"renderop_effect.h\"\n#include \"core/render.h\"\n#include \"globals.h\"\n\n// DRAW\nRenderEffectOp::RenderEffectOp() : RenderOp(),\n    effect_type(RNDEFF_ScreenGlow),\n    blend_type(BLEND_Additive),\n    color(0x00000000),\n    intensity(0),\n    flip_type(FLIP_TYPE_NONE)\n{\n    static_assert(sizeof(RenderEffectOp) <= c_rAllocChunkSize,\n                  \"Size of RenderBitmapOp class must be smaller than c_rAllocChunkSize\");\n}\n\nRenderEffectOp::RenderEffectOp(RENDER_EFFECT effect, BLEND_TYPE blend, COLORREF col, int intensity)\n{\n    effect_type = effect;\n    blend_type = blend;\n    color = col;\n    this->intensity = intensity;\n    flip_type = FLIP_TYPE_NONE;\n}\n\nvoid RenderEffectOp::Draw(Renderer *g)\n{\n    switch(effect_type)\n    {\n    case RNDEFF_ScreenGlow:\n        ScreenGlow(g);\n        break;\n    case RNDEFF_Flip:\n        Flip(g);\n        break;\n    default:\n        break;\n    }\n}\n\n// SCREEN GLOW\nvoid RenderEffectOp::ScreenGlow(Renderer *g)\n{\n    UNUSED(g);\n    // TODO: Re-Implement this differently\n\n//    HDC hScreen = g->GetScreenDC();\n\n//    HBITMAP hOld = (HBITMAP)SelectObject(ghMemDC, ghGeneralDIB);\n\n//    if(ghGeneralDIB && ghMemDC && gpScreenBits)\n//    {\n//        BitBlt(ghMemDC, 0, 0, 800, 600, hScreen, 0, 0, SRCCOPY);\n\n//#ifndef __MINGW32__\n//        // MMX code and loop\n//        _mm_empty();\n//        int nLoops = (800 * 600) / 2;\n//        __m64 color64 = _mm_set_pi32(color, color);\n//        __m64 *pDest = (__m64 *)gpScreenBits;\n\n//        if(blend_type == BLEND_Additive)\n//        {\n//            for(int i = 0; i < nLoops; i++)\n//            {\n//                //tmp =\n//                pDest[i] = _mm_adds_pu8(color64, pDest[i]);\n\n//                //pDest[i] = tmp;\n//            }\n//        }\n//        else if(blend_type == BLEND_Subtractive)\n//        {\n//            for(int i = 0; i < nLoops; i++)\n//            {\n//                //tmp =\n//                pDest[i] = _mm_subs_pu8(color64, pDest[i]);\n\n//                //pDest[i] = tmp;\n//            }\n//        }\n\n//        _mm_empty();\n//#endif\n\n//        BitBlt(hScreen, 0, 0, 800, 600, ghMemDC, 0, 0, SRCCOPY);\n\n//        SelectObject(ghMemDC, hOld);\n//    }\n}\n\n\n// Flip\nvoid RenderEffectOp::Flip(Renderer *g)\n{\n    UNUSED(g);\n//    HDC hScreen = g->GetScreenDC();\n\n//    HBITMAP hOld = (HBITMAP)SelectObject(ghMemDC, ghGeneralDIB);\n//    if(!(ghGeneralDIB && ghMemDC && gpScreenBits)) return;\n\n//    BitBlt(ghMemDC, 0, 0, 800, 600, hScreen, 0, 0, SRCCOPY);\n\n//    StretchBlt(hScreen,\n//               (flip_type & FLIP_TYPE_X) ? 800 : 0,\n//               (flip_type & FLIP_TYPE_Y) ? 600 : 0,\n//               (flip_type & FLIP_TYPE_X) ? -800 : 800,\n//               (flip_type & FLIP_TYPE_Y) ? -600 : 600,\n//               ghMemDC, 0, 0, 800, 600, SRCCOPY);\n\n//    SelectObject(ghMemDC, hOld);\n}\n"
  },
  {
    "path": "src/script/luna/renderop_effect.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef RENDEROP_EFFECT_H\n#define RENDEROP_EFFECT_H\n\n#include \"renderop.h\"\n\ntypedef uint32_t COLORREF;\n\nenum RENDER_EFFECT\n{\n    RNDEFF_ScreenGlow,\n    RNDEFF_Flip\n};\n\nenum BLEND_TYPE\n{\n    BLEND_Additive,\n    BLEND_Subtractive\n};\n\nenum FLIP_TYPE\n{\n    FLIP_TYPE_NONE = 0x0,\n    FLIP_TYPE_X = 0x1,\n    FLIP_TYPE_Y = 0x2,\n    FLIP_TYPE_XY = 0x3,\n};\n\nclass RenderEffectOp final : public RenderOp\n{\npublic:\n    RenderEffectOp();\n    explicit RenderEffectOp(RENDER_EFFECT effect, BLEND_TYPE blend, COLORREF col, int intensity);\n\n    ~RenderEffectOp() override = default;\n\n    void Draw(Renderer *renderer) override;\n\n    inline void* operator new(size_t size)\n    {\n        // Note: If you creating any chunks with a size bigger than current size, please increase it\n        SDL_assert_release(size < c_rAllocChunkSize);\n        auto *ret = g_rAlloc.Allocate(c_rAllocChunkSize);\n        return ret;\n    }\n\n    inline void operator delete(void* memory)\n    {\n        g_rAlloc.Free(memory);\n    }\n\n    // Effects //\n    void ScreenGlow(Renderer *renderer);\n    void Flip(Renderer *renderer);\n\n    // Members //\n    RENDER_EFFECT effect_type;\n    BLEND_TYPE blend_type;\n    COLORREF color;\n    int intensity;\n    FLIP_TYPE flip_type;\n};\n\n\n#endif // RENDEROP_EFFECT_H\n"
  },
  {
    "path": "src/script/luna/renderop_rect.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"renderop_rect.h\"\n#include \"core/render.h\"\n#include \"globals.h\"\n\n\nRenderRectOp::RenderRectOp() : RenderOp(),\n    x1(0), y1(0), x2(0), y2(0),\n    fillColor(0, 0, 0, 0),\n    borderColor(255, 255, 255, 255),\n    sceneCoords(false)\n{\n    static_assert(sizeof(RenderRectOp) <= c_rAllocChunkSize,\n            \"Size of RenderRectOp class must be smaller than c_rAllocChunkSize\");\n}\n\nvoid RenderRectOp::Draw(Renderer *renderer)\n{\n    if(borderColor.a <= 0 && fillColor.a <= 0) return;\n\n    // Convert coordinates\n    int sx1 = this->x1, sy1 = this->y1, sx2 = this->x2, sy2 = this->y2;\n    if(sceneCoords)\n    {\n        sx1 -= vScreen[renderer->GetCameraIdx()].CameraAddX_i();\n        sy1 -= vScreen[renderer->GetCameraIdx()].CameraAddY_i();\n        sx2 -= vScreen[renderer->GetCameraIdx()].CameraAddX_i();\n        sy2 -= vScreen[renderer->GetCameraIdx()].CameraAddY_i();\n    }\n    else\n    {\n        if(sx1 == 0 && sx2 == 800 && sy1 == 0 && sy2 == 600)\n        {\n            sx2 = vScreen[1].Width;\n            sy2 = vScreen[1].Height;\n        }\n        else\n        {\n            Render::TranslateScreenCoords(sx1, sy1, sx2 - sx1, sy2 - sy1);\n        }\n    }\n\n    if(fillColor.a > 0)\n        XRender::renderRect(sx1,\n                            sy1,\n                            sx2 - sx1,\n                            sy2 - sy1,\n                            fillColor, true);\n\n    if(borderColor.a > 0)\n        XRender::renderRect(sx1,\n                            sy1,\n                            sx2 - sx1,\n                            sy2 - sy1,\n                            borderColor, false);\n}\n\n"
  },
  {
    "path": "src/script/luna/renderop_rect.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef RENDERRECTOP_H\n#define RENDERRECTOP_H\n\n#include \"renderop.h\"\n\nclass RenderRectOp final : public RenderOp\n{\npublic:\n    RenderRectOp();\n\n    ~RenderRectOp() override = default;\n\n    void Draw(Renderer *renderer) override;\n\n    inline void* operator new(size_t size)\n    {\n        // Note: If you creating any chunks with a size bigger than current size, please increase it\n        SDL_assert_release(size < c_rAllocChunkSize);\n        auto *ret = g_rAlloc.Allocate(c_rAllocChunkSize);\n        return ret;\n    }\n\n    inline void operator delete(void* memory)\n    {\n        g_rAlloc.Free(memory);\n    }\n\n    int x1;\n    int y1;\n    int x2;\n    int y2;\n    XTColor fillColor;\n    XTColor borderColor;\n    bool   sceneCoords;        // If true, x and y are scene coordinates\n};\n\n\n#endif // RENDERRECTOP_H\n"
  },
  {
    "path": "src/script/luna/renderop_string.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"renderop_string.h\"\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"core/render.h\"\n\nRenderStringOp::RenderStringOp() :\n    RenderStringOp(std::string(), 1, 400, 400)\n{\n    static_assert(sizeof(RenderStringOp) <= c_rAllocChunkSize,\n                  \"Size of RenderStringOp class must be smaller than c_rAllocChunkSize\");\n}\n\nRenderStringOp::RenderStringOp(const std::string &str, int font_type, int X, int Y) :\n    RenderOp(RENDEROP_DEFAULT_PRIORITY_TEXT),\n    m_FontType(font_type),\n    m_X(X),\n    m_Y(Y),\n    sceneCoords(false)\n{\n    m_StringSize = str.size();\n    m_StringDup = (m_StringSize >= c_rAllocChunkSize);\n\n    if(m_StringDup) // fallback if string is longer than chunk size, that shouldn't happen usually\n        m_String = SDL_strdup(str.c_str());\n    else\n    {\n        m_String = (char*)g_rAlloc.Allocate(c_rAllocChunkSize);\n        m_StringSize = SDL_strlcpy(m_String, str.c_str(), c_rAllocChunkSize);\n    }\n}\n\nRenderStringOp::~RenderStringOp()\n{\n    if(m_StringDup)\n        SDL_free(m_String);\n    else\n        g_rAlloc.Free(m_String);\n    m_String = nullptr;\n}\n\nvoid RenderStringOp::Draw(Renderer *renderer)\n{\n    //        VB6StrPtr text(m_String);\n    int x = m_X, y = m_Y;\n\n    if(sceneCoords)\n    {\n        x -= vScreen[renderer->GetCameraIdx()].CameraAddX_i();\n        y -= vScreen[renderer->GetCameraIdx()].CameraAddY_i();\n    }\n    else\n    {\n        int w = SuperTextPixLen(m_StringSize, m_String, m_FontType);\n        Render::TranslateScreenCoords(x, y, w, 18);\n    }\n\n    SuperPrint(m_StringSize, m_String, m_FontType, x, y);\n}\n"
  },
  {
    "path": "src/script/luna/renderop_string.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef RENDEROP_STRING_H\n#define RENDEROP_STRING_H\n\n#include \"renderop.h\"\n#include \"graphics.h\"\n\n// String object to be rendered later\nclass RenderStringOp final : public RenderOp\n{\npublic:\n    // Quick ctor\n    RenderStringOp();\n\n    RenderStringOp(const std::string &str, int font_type, int X, int Y);\n\n    ~RenderStringOp() override;\n\n    void Draw(Renderer *renderer) override;\n\n    inline void* operator new(size_t size)\n    {\n        // Note: If you creating any chunks with a size bigger than current size, please increase it\n        SDL_assert_release(size < c_rAllocChunkSize);\n        auto *ret = g_rAlloc.Allocate(c_rAllocChunkSize);\n        return ret;\n    }\n\n    inline void operator delete(void* memory)\n    {\n        g_rAlloc.Free(memory);\n    }\n\n    // FIXME: Replace this with the string data index\n    // Every autocode should use the string index storage, and this thing won't be needed\n    char*  m_String = nullptr;\n    size_t m_StringSize = 0;\n    bool   m_StringDup = false;\n\n    int m_FontType;\n    int m_X;\n    int m_Y;\n    bool   sceneCoords;     // If true, x and y are scene coordinates\n};\n\n#endif // RENDEROP_STRING_H\n"
  },
  {
    "path": "src/script/luna/sprite_component.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sprite_component.h\"\n\nvoid SpriteComponent::Tick()\n{\n    if(org_time == 0)   // endless component\n        return;\n\n    run_time--;         // normal behavior\n\n    if(run_time == 0)   // time to delete\n        expired = true;\n}\n"
  },
  {
    "path": "src/script/luna/sprite_component.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SpriteComponent_HHH\n#define SpriteComponent_HHH\n\n#include \"numeric_types.h\"\n#include <string>\n\nstruct SpriteComponent;\nclass CSprite;\n\ntypedef void (*pfnSprFunc)(CSprite *, SpriteComponent *obj);\n\n// Class for wrapping a sprite component.\n// Sprite manager will run it (calling func) until run_time == 0\nstruct SpriteComponent\n{\n    SpriteComponent() = default;\n\n    void Init(int set_time)\n    {\n        func = nullptr;\n        lookup_code = 0;\n        data1 = 0;\n        data2 = 0;\n        data3 = 0;\n        data4 = 0;\n        run_time = set_time;\n        org_time = set_time;\n        expired = false;\n    }\n\n    void Tick();            // Tick down the timer of this component. Expires self when run_time reaches 1\n\n    num_t data1 = 0;\n    num_t data2 = 0;\n    num_t data3 = 0;\n    num_t data4 = 0;\n    pfnSprFunc func = nullptr;\n    int lookup_code = 0;        // Which \"bucket\" of custom components this component is a part of\n    int run_time = 0;           // Run time frame countdown. 0 = infinite lifetime, always run\n    int org_time = 0;           // Frame count to begin countdown at\n\n    std::string data5;\n    bool expired = false;           // If this component should be deleted at the next clean up cycle\n};\n\n#endif // SpriteComponent_HHH\n"
  },
  {
    "path": "src/script/luna/sprite_funcs.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n\n#include \"sprite_funcs.h\"\n#include \"sprite_component.h\"\n#include \"autocode_manager.h\"\n#include \"csprite.h\"\n#include \"globals.h\"\n#include \"player.h\"\n#include \"rand.h\"\n#include \"lunarender.h\"\n#include \"lunaplayer.h\"\n#include \"main/trees.h\"\n#include \"lunamisc.h\"\n#include \"lunaspriteman.h\"\n#include \"renderop_bitmap.h\"\n#include \"mememu.h\"\n\nstatic inline int s_round2int(num_t d)\n{\n    return num_t::floor(d + 0.5_n);\n}\n\n\n// Activate -- Formally trigger a component link in the given sprite (copy all matches into behavior list)\nvoid Activate(int code, CSprite *spr)\n{\n    if(code > 99)\n    {\n        std::list<SpriteComponent *> complist;\n        gSpriteMan.GetComponents(code, &complist);\n        while(!complist.empty())\n        {\n            spr->AddBehaviorComponent(*complist.front());\n            complist.pop_front();\n        }\n    }\n}\n\n// PLAYER COLLECTIBLE -- Call sprite's \"Die()\" when touched by player\nvoid SpriteFunc::PlayerCollectible(CSprite *me, SpriteComponent *comp)\n{\n    UNUSED(comp);\n    Player_t *demo = PlayerF::Get(1);\n    if(demo)\n    {\n        if(me->m_Hitbox.Test((int)demo->Location.X, (int)demo->Location.Y,\n                             (int)demo->Location.Width, (int)demo->Location.Height))\n        {\n            me->Die();\n            gSpriteMan.m_hasInvalid = true;\n        }\n    }\n}\n\n// WAIT FOR PLAYER -- Activate the linked component when a player condition becomes true\nvoid SpriteFunc::WaitForPlayer(CSprite *me, SpriteComponent *obj)\n{\n    UNUSED(me);\n    Player_t *demo = PlayerF::Get(1);\n    if(demo)\n    {\n        auto ftype = (FIELDTYPE)SDL_atoi(obj->data5.c_str());\n//        uint8_t *ptr = (uint8_t *)demo;\n//        ptr += (int)obj->data1; // offset\n        bool triggered = CheckMem(demo, (int)obj->data1, num_t::floor(obj->data2), (COMPARETYPE)(int)obj->data3, ftype);\n        if(triggered)\n        {\n            //TODO: FINISH IT\n            // me->AddBehaviorComponent(\n        }\n    }\n}\n\n// PLAYER HOLDING SPRITE\nvoid SpriteFunc::PlayerHoldingSprite(CSprite *me, SpriteComponent *obj)\n{\n    Player_t *demo = PlayerF::Get(1);\n    if(demo)\n    {\n        if(PlayerF::IsHoldingSpriteType(demo, (int)obj->data1))\n            Activate((int)obj->data4, me);\n    }\n}\n\n// SET SPRITE VAR\nvoid SpriteFunc::SetSpriteVar(CSprite *me, SpriteComponent *obj)\n{\n    if(obj->data5.length() > 0)\n        me->SetCustomVar(obj->data5, (OPTYPE)(int)obj->data2, obj->data3);\n}\n\n// IF SPRITE VAR\nvoid SpriteFunc::IfSpriteVar(CSprite *me, SpriteComponent *obj)\n{\n    if(obj->data5.length() > 0)\n    {\n        if(me->CustomVarExists(obj->data5))\n        {\n            num_t var_val = me->GetCustomVar(obj->data5);\n            num_t check_against = obj->data3;\n            num_t component_to_activate = obj->data4;\n\n            switch((COMPARETYPE)(int)obj->data2)\n            {\n            case CMPT_EQUALS:\n                if(var_val == check_against)\n                    Activate((int)component_to_activate, me);\n                break;\n            case CMPT_GREATER:\n                if(var_val > check_against)\n                    Activate((int)component_to_activate, me);\n                break;\n            case CMPT_LESS:\n                if(var_val < check_against)\n                    Activate((int)component_to_activate, me);\n                break;\n            case CMPT_NOTEQ:\n                if(var_val != check_against)\n                    Activate((int)component_to_activate, me);\n                break;\n            default:\n                break;\n            }\n        }\n    }\n}\n\n// IF LUNA VAR\nvoid SpriteFunc::IfLunaVar(CSprite *me, SpriteComponent *obj)\n{\n    if(obj->data5.length() > 0)\n    {\n        if(gAutoMan.VarExists(obj->data5))\n        {\n            num_t var_val = gAutoMan.GetVar(obj->data5);\n            num_t check_against = obj->data3;\n            num_t component_to_activate = obj->data4;\n\n            switch((COMPARETYPE)(int)obj->data2)\n            {\n            case CMPT_EQUALS:\n                if(var_val == check_against)\n                    Activate((int)component_to_activate, me);\n                break;\n            case CMPT_GREATER:\n                if(var_val > check_against)\n                    Activate((int)component_to_activate, me);\n                break;\n            case CMPT_LESS:\n                if(var_val < check_against)\n                    Activate((int)component_to_activate, me);\n                break;\n            case CMPT_NOTEQ:\n                if(var_val != check_against)\n                    Activate((int)component_to_activate, me);\n                break;\n            default:\n                break;\n            }\n        }\n    }\n}\n\n// RANDOM COMPONENT\nvoid SpriteFunc::RandomComponent(CSprite *me, SpriteComponent *obj)\n{\n    int choice = iRand2(4);\n\n    switch(choice)\n    {\n    default:\n        break;\n    case 0:\n        Activate((int)obj->data1, me);\n        break;\n    case 1:\n        Activate((int)obj->data2, me);\n        break;\n    case 2:\n        Activate((int)obj->data3, me);\n        break;\n    case 3:\n        Activate((int)obj->data4, me);\n        break;\n    }\n}\n\n// RANDOM COMPONENT RANGE\nvoid SpriteFunc::RandomComponentRange(CSprite *me, SpriteComponent *obj)\n{\n    int val1 = (int)obj->data1;\n    int val2 = (int)obj->data2;\n    if(val1 < val2)   // rule out bad values\n    {\n        int diff = val2 - val1;\n        int choice = iRand2(diff);\n        Activate(val1 + choice, me);\n    }\n}\n\n// DIE\nvoid SpriteFunc::Die(CSprite *me, SpriteComponent *obj)\n{\n    UNUSED(obj);\n    me->Die();\n    gSpriteMan.m_hasInvalid = true;\n}\n\n// DECCELERATE\nvoid SpriteFunc::Deccelerate(CSprite *me, SpriteComponent *obj)\n{\n    num_t XRate = obj->data1;\n    num_t YRate = obj->data2;\n    num_t Min  = obj->data3;\n    num_t accum = 0;\n    if(me->m_Xspd >= 0 && XRate != 0)\n    {\n        accum = me->m_Xspd - XRate;\n        if(accum < Min)\n            accum = Min;\n        me->m_Xspd = accum;\n    }\n    else if(XRate != 0)\n    {\n        accum = me->m_Xspd + XRate;\n        if(accum > Min)\n            accum = Min;\n        me->m_Xspd = accum;\n    }\n\n    if(me->m_Yspd >= 0 && YRate != 0)\n    {\n        accum = me->m_Yspd - YRate;\n        if(accum < Min)\n            accum = Min;\n        me->m_Yspd = accum;\n    }\n    else if(YRate != 0)\n    {\n        accum = me->m_Yspd + YRate;\n        if(accum > Min)\n            accum = Min;\n        me->m_Yspd = accum;\n    }\n}\n\n// ACCELERATE\nvoid SpriteFunc::Accelerate(CSprite *me, SpriteComponent *obj)\n{\n    UNUSED(me);\n    UNUSED(obj);\n    //TODO\n}\n\n// ACCEL TO PLAYER\nvoid SpriteFunc::AccelToPlayer(CSprite *me, SpriteComponent *obj)\n{\n    Player_t *demo = PlayerF::Get(1);\n    if(demo)\n    {\n        num_t negmax = obj->data3 * -1;\n        if(demo->Location.X < me->m_Xpos)\n            me->m_Xspd -= obj->data1;\n        else\n            me->m_Xspd += obj->data1;\n        if(demo->Location.Y < me->m_Ypos)\n            me->m_Yspd -= obj->data2;\n        else\n            me->m_Yspd += obj->data2;\n        if(obj->data3 != 0)\n        {\n            if(me->m_Xspd > obj->data3)\n                me->m_Xspd = obj->data3;\n            else if(me->m_Xspd < negmax)\n                me->m_Xspd = negmax;\n            if(me->m_Yspd > obj->data3)\n                me->m_Yspd = obj->data3;\n            else if(me->m_Yspd < negmax)\n                me->m_Yspd = negmax;\n        }\n    }\n}\n\n// APPLY VARIABLE GRAVITY\nvoid SpriteFunc::ApplyVariableGravity(CSprite *me, SpriteComponent *obj)\n{\n    num_t var = gAutoMan.GetVar(obj->data5);\n    if(obj->data2 == 0)  // x\n        me->m_Xspd += var;\n    else   // y\n        me->m_Yspd += var;\n}\n\n// ON PLAYER COLLIDE\nvoid SpriteFunc::OnPlayerCollide(CSprite *me, SpriteComponent *obj)\n{\n    Player_t *demo = PlayerF::Get(1);\n\n    if(demo)\n    {\n        if(obj->data2 == 0)   // player normal hitbox\n        {\n            if(me->m_Hitbox.Test((int)demo->Location.X, (int)demo->Location.Y, (int)demo->Location.Width, (int)demo->Location.Height))\n                Activate((int)obj->data4, me);\n        }\n        else   // special small circle hitbox\n        {\n            num_t extent = obj->data2 / 2;\n\n            num_t cx = demo->Location.X + (demo->Location.Width / 2);\n            num_t cy = demo->Location.Y + (demo->Location.Height / 2);\n            if(me->m_Hitbox.Test((int)cx, (int)cy, (int)extent))\n                Activate((int)obj->data4, me);\n        }\n    }\n}\n\n// ON PLAYER DISTANCE\nvoid SpriteFunc::OnPlayerDistance(CSprite *me, SpriteComponent *obj)\n{\n    Player_t *demo = PlayerF::Get(1);\n    if(demo)\n    {\n        num_t xdist = num_t::abs(demo->Location.X - me->m_Xpos);\n        num_t ydist = num_t::abs(demo->Location.Y - me->m_Ypos);\n\n        // Checking farness or nearness?\n        if(obj->data2 == 0)\n        {\n            if(xdist + ydist >= obj->data1)\n                Activate((int)obj->data4, me);\n        }\n        else\n        {\n            if(xdist + ydist <= obj->data1)\n                Activate((int)obj->data4, me);\n        }\n    }\n}\n\n// PHASE MOVE\nvoid SpriteFunc::PhaseMove(CSprite *me, SpriteComponent *obj)\n{\n    UNUSED(obj);\n    me->m_Xpos += me->m_Xspd;\n    me->m_Ypos += me->m_Yspd;\n}\n\n// BUMP MOVE\nvoid SpriteFunc::BumpMove(CSprite *me, SpriteComponent *obj)\n{\n    num_t energy_loss_mod = (100 - obj->data2) / 100;\n\n    me->m_Xpos += me->m_Xspd;\n    me->m_Ypos += me->m_Yspd;\n\n    // uint32_t COLOR = 0x11111111; // ACTUALLY UNUSED\n\n    bool collided_left = false;\n    bool collided_right = false;\n    bool collided_top = false;\n//    bool collided_bot = false;\n\n    // NOTE: these were previously sorted closest first. If we keep this function, we should replicate that logic here.\n\n    for(BlockRef_t block : treeBlockQuery(newLoc(me->m_Hitbox.CalcLeft(), me->m_Hitbox.CalcRight(), me->m_Hitbox.W, me->m_Hitbox.H), SORTMODE_COMPAT))\n    {\n        if(!block->Invis && !block->Hidden\n           && me->m_Hitbox.Test((int)block->Location.X, (int)block->Location.Y, (int)block->Location.Width, (int)block->Location.Height))\n        {\n            num_t sprite_bot = me->m_Hitbox.CalcBottom();\n            num_t sprite_right = me->m_Hitbox.CalcRight();\n            num_t sprite_top = me->m_Hitbox.CalcTop();\n            num_t sprite_left = me->m_Hitbox.CalcLeft();\n\n            if(me->m_CollisionCode == -1)   // default solid collision\n            {\n                num_t block_topcol = num_t::abs(block->Location.Y - sprite_bot);\n                num_t block_botcol = num_t::abs((block->Location.Y + block->Location.Height) - sprite_top);\n                num_t block_leftcol = num_t::abs(block->Location.X - sprite_right);\n                num_t block_rightcol = num_t::abs((block->Location.X + block->Location.Width) - sprite_left);\n\n                // Determine best direction to free sprite\n                // Top collision, push sprite up and out\n                if(block_topcol <= block_botcol && block_topcol <= block_leftcol &&\n                   block_topcol <= block_rightcol && !collided_top)\n                {\n                    me->m_Ypos = (block->Location.Y - me->m_Hitbox.H) - 1;\n                    me->m_Yspd = -(me->m_Yspd.times(energy_loss_mod));\n                    collided_top = true;\n                }\n\n                // Bot collision, push sprite down\n                else if(block_botcol <= block_leftcol && block_botcol <= block_rightcol && !collided_right)\n                {\n                    me->m_Ypos = ((block->Location.Y + block->Location.Height) - me->m_Hitbox.Top_off) + 1;\n                    me->m_Yspd = -(me->m_Yspd.times(energy_loss_mod));\n                    // collided_bot = true;\n                }\n\n                // Left collision, push sprite left\n                else if(block_leftcol <= block_rightcol && !collided_left)\n                {\n                    me->m_Xpos = (block->Location.X - me->m_Hitbox.W) - 1;\n                    me->m_Xspd = -(me->m_Xspd.times(energy_loss_mod));\n                    collided_left = true;\n                }\n\n                // Right collision, push sprite right\n                else if(!collided_right)\n                {\n                    me->m_Xpos = ((block->Location.X + block->Location.Width) - me->m_Hitbox.Left_off) + 1;\n                    me->m_Xspd = -(me->m_Xspd.times(energy_loss_mod));\n                    collided_right = true;\n                }\n            }\n        }\n    }\n}\n\n// CRASH MOVE\nvoid SpriteFunc::CrashMove(CSprite *me, SpriteComponent *obj)\n{\n    UNUSED(obj);\n    me->m_Xpos += me->m_Xspd;\n    me->m_Ypos += me->m_Yspd;\n\n    for(BlockRef_t block : treeBlockQuery(newLoc(me->m_Hitbox.CalcLeft(), me->m_Hitbox.CalcRight(), me->m_Hitbox.W, me->m_Hitbox.H), SORTMODE_NONE))\n    {\n        if(!block->Invis && !block->Hidden &&\n            me->m_Hitbox.Test((int)block->Location.X,\n                              (int)block->Location.Y,\n                              (int)block->Location.Width,\n                              (int)block->Location.Height))\n        {\n            me->Die();\n            gSpriteMan.m_hasInvalid = true;\n            break;\n        }\n    }\n}\n\n// SET X SPEED\nvoid SpriteFunc::SetXSpeed(CSprite *me, SpriteComponent *obj)\n{\n    me->m_Xspd = obj->data1;\n}\n\n// SET Y SPEED\nvoid SpriteFunc::SetYSpeed(CSprite *me, SpriteComponent *obj)\n{\n    me->m_Yspd = obj->data1;\n}\n\n// SET ALWAYS PROCESS\nvoid SpriteFunc::SetAlwaysProcess(CSprite *me, SpriteComponent *obj)\n{\n    me->m_AlwaysProcess = (bool)obj->data1;\n}\n\n// SET VISIBLE\nvoid SpriteFunc::SetVisible(CSprite *me, SpriteComponent *obj)\n{\n    me->m_Visible = (bool)obj->data1;\n}\n\n// SET HITBOX\nvoid SpriteFunc::SetHitbox(CSprite *me, SpriteComponent *obj)\n{\n    if(obj->data5.find(\"circle\") != std::wstring::npos)\n        me->m_Hitbox.CollisionType = 1;\n    else\n        me->m_Hitbox.CollisionType = 0;\n    me->m_Hitbox.Left_off = (short)obj->data1;\n    me->m_Hitbox.Top_off = (short)obj->data2;\n    me->m_Hitbox.W = (short)obj->data3;\n    me->m_Hitbox.H = (short)obj->data4;\n}\n\n// TELEPORT NEAR PLAYER\nvoid SpriteFunc::TeleportNearPlayer(CSprite *me, SpriteComponent *obj)\n{\n    Player_t *demo = PlayerF::Get(1);\n    if(demo)\n    {\n        num_t cx = demo->Location.X;\n        num_t cy = demo->Location.Y;\n        num_t phase = iRand2(360);\n        num_t xoff = num_t::sin(phase).times(obj->data1);\n        num_t yoff = num_t::cos(phase).times(obj->data1);\n        me->m_Xpos = cx + xoff;\n        me->m_Ypos = cy + yoff;\n    }\n}\n\n// TELEPORT TO\nvoid SpriteFunc::TeleportTo(CSprite *me, SpriteComponent *obj)\n{\n    me->m_Xpos = obj->data1;\n    me->m_Ypos = obj->data2;\n}\n\n// TRIGGER LUNA EVENT -- Trigger a lunadll script event (such as #1000)\nvoid SpriteFunc::TriggerLunaEvent(CSprite *me, SpriteComponent *obj)\n{\n    UNUSED(me);\n    if(obj->data1 > 21)\n        gAutoMan.ActivateCustomEvents(0, (int)obj->data1);\n}\n\n// HARM PLAYER\nvoid SpriteFunc::HarmPlayer(CSprite *me, SpriteComponent *obj)\n{\n    UNUSED(me);\n    UNUSED(obj);\n    PlayerHurt(1);\n}\n\n// GENERATE IN RADIUS\nvoid SpriteFunc::GenerateInRadius(CSprite *me, SpriteComponent *obj)\n{\n    num_t rand_x;\n    num_t rand_y;\n    RandomPointInRadius(&rand_x, &rand_y, me->m_Hitbox.CenterX(), me->m_Hitbox.CenterY(), (int)obj->data3);\n\n    CSpriteRequest req;\n    req.type = 0;\n    req.img_resource_code = (int)obj->data2;\n    req.x = (int)rand_x;\n    req.y = (int)rand_y;\n    req.time = (int)obj->data4;\n    req.str = obj->data5;\n    req.spawned = true;\n\n    gSpriteMan.InstantiateSprite(&req, false);\n}\n\n// GENERATE AT ANGLE\nvoid SpriteFunc::GenerateAtAngle(CSprite *me, SpriteComponent *obj)\n{\n    num_t angle = me->GetCustomVar(CVAR_GEN_ANGLE);\n    num_t speed = obj->data3;\n\n    num_t vx = num_t::cos(angle).times(speed);     // vector x speed\n    num_t vy = num_t::sin(angle).times(speed);     // vector y speed\n    num_t gx = me->m_Hitbox.CenterX() + (vx * 2);  // generation point\n    num_t gy = me->m_Hitbox.CenterY() + (vy * 2);  // generation point\n\n    CSpriteRequest req;\n    req.type = 0;\n    req.img_resource_code = (int)obj->data2;\n    req.x = (int)gx;\n    req.y = (int)gy;\n    req.time = (int)obj->data4;\n    req.str = obj->data5;\n\n    req.x_speed = vx;\n    req.y_speed = vy;\n    req.spawned = true;\n\n    gSpriteMan.InstantiateSprite(&req, false);\n}\n\n// SPRITE TIMER\nvoid SpriteFunc::SpriteTimer(CSprite *me, SpriteComponent *obj)\n{\n    bool repeat = (bool)obj->data3;\n    int timer = (int)obj->run_time;\n    if(timer == 1 || timer == 0)\n    {\n        if(timer == 1)\n            obj->expired = true;\n        Activate((int)obj->data4, me);\n\n        if(repeat)\n        {\n            obj->expired = false;\n            obj->run_time = obj->org_time;\n        }\n    }\n}\n\n// BASIC ANIMATE\nvoid SpriteFunc::BasicAnimate(CSprite *me, SpriteComponent *obj)\n{\n    int anim_height = (int)obj->data1;\n    if(anim_height == 0)\n        anim_height = 1;\n    int implicit_frames = (int)me->m_Ht / anim_height;\n\n    // Init animation state if necessary\n    if(!me->m_AnimationSet)\n    {\n        me->m_Hitbox.H = static_cast<short>(anim_height);\n        me->m_AnimationFrame = 0;\n        me->m_AnimationPhase = (int)obj->data2;\n        me->m_AnimationTimer = (int)obj->data2;\n        me->m_GfxRects.clear();\n        for(int i = 0; i < implicit_frames; i++)\n        {\n            LunaRect temp;\n            temp.left = 0;\n            temp.right = (int)me->m_Wd;\n            temp.top = (int)anim_height * i;\n            temp.bottom = (int)anim_height; // <this is a HEIGHT argument\n            me->m_GfxRects.push_back(temp);\n        }\n        me->m_AnimationSet = true;\n    }\n\n    // Process animation\n    me->m_AnimationTimer--;\n    if(me->m_AnimationTimer <= 0)\n    {\n        me->m_AnimationTimer = me->m_AnimationPhase;\n        me->m_AnimationFrame += 1;\n        if(me->m_AnimationFrame + 1 > implicit_frames)\n            me->m_AnimationFrame = 0;\n    }\n}\n\n// ANIMATE FLOAT\nvoid SpriteFunc::AnimateFloat(CSprite *me, SpriteComponent *obj)\n{\n    num_t speed = obj->data1;\n    num_t x_mag = obj->data2;\n    num_t y_mag = obj->data3;\n    if(speed != 0 && (x_mag != 0 || y_mag != 0))\n    {\n        num_t frame_val = num_t(me->m_FrameCounter).divided_by(speed);\n        if(x_mag != 0)\n            me->m_GfxXOffset = (int)(num_t::cos(frame_val).times(x_mag));\n        if(y_mag != 0)\n            me->m_GfxYOffset = (int)(num_t::sin(frame_val).times(y_mag));\n    }\n}\n\n// BLINK\nvoid SpriteFunc::Blink(CSprite *me, SpriteComponent *obj)\n{\n    int mod = (int)obj->run_time % (int)obj->data1;\n    if(mod == 0)\n        me->m_Visible = (bool)obj->data2;\n    else\n        me->m_Visible = (bool)(obj->data2 == 0 ? 1 : 0);\n}\n\n// SPRITE DEBUG\nvoid SpriteFunc::SpriteDebug(CSprite *me, SpriteComponent *obj)\n{\n    UNUSED(obj);\n    Renderer::Get().DebugPrint(\"XPOS - \", me->m_Xpos);\n    Renderer::Get().DebugPrint(\"YPOS - \", me->m_Ypos);\n    Renderer::Get().DebugPrint(\"XSPD - \", me->m_Xspd);\n    Renderer::Get().DebugPrint(\"YSPD - \", me->m_Yspd);\n    Renderer::Get().DebugPrint(\"FRAME - \", me->m_AnimationFrame);\n    Renderer::Get().DebugPrint(\"VISIBLE - \", (me->m_Visible ? 1 : 0));\n    Renderer::Get().DebugPrint(\"CVARS - \", (uint32_t)me->m_CustomVars.size());\n    Renderer::Get().DebugPrint(\"BEHAVIORS - \", (uint32_t)me->m_BehavComponents.size());\n}\n\n\n\n\n\n// STATIC DRAW - Simply draw the sprite at its absolute screen coordinates\n//               by registering a new bitmap render operation\nvoid SpriteFunc::StaticDraw(CSprite *me)\n{\n    if(me != nullptr && me->m_Visible)\n    {\n        if(!me->m_directImg && me->m_GfxRects.empty()) // Workaround\n        {\n            auto *direct_img = Renderer::Get().GetImageForResourceCode(me->m_ImgResCode);\n            if(direct_img)\n                gSpriteMan.InitializeDimensions(me, false);\n        }\n\n        if(me->m_AnimationFrame < (signed)me->m_GfxRects.size())   // Frame should be less than size of GfxRect container\n        {\n            auto *op = new RenderBitmapOp();\n            op->m_FramesLeft = 1;\n            op->x = s_round2int(me->m_Xpos) + me->m_GfxXOffset;\n            op->y = s_round2int(me->m_Ypos) + me->m_GfxYOffset;\n            auto &r = me->m_GfxRects[me->m_AnimationFrame];\n            op->sx = r.left;\n            op->sy = r.top;\n            op->sw = r.right;\n            op->sh = r.bottom;\n            if(me->m_directImg)\n                op->direct_img = me->m_directImg;\n            else\n                op->direct_img = Renderer::Get().GetImageForResourceCode(me->m_ImgResCode);\n\n            Renderer::Get().AddOp(op);\n        }\n    }\n}\n\n// RELATIVE DRAW - Calculate sprite position inside level and draw relative\n//                 to camera position by registering new bitmap render operation\nvoid SpriteFunc::RelativeDraw(CSprite *me)\n{\n    if(me != nullptr && me->m_Visible)\n    {\n        if(!me->m_directImg && me->m_GfxRects.empty()) // Workaround\n        {\n            auto *direct_img = Renderer::Get().GetImageForResourceCode(me->m_ImgResCode);\n            if(direct_img)\n                gSpriteMan.InitializeDimensions(me, false);\n        }\n\n        if(me->m_AnimationFrame < (signed)me->m_GfxRects.size())\n        {\n            // double cx = 0;              // camera x (top left of screen)\n            // double cy = 0;              // camera y (top left of screen)\n            int sx = s_round2int(me->m_Xpos);     // sprite x position (top left of sprite)\n            int sy = s_round2int(me->m_Ypos);     // sprite y position (top left of sprite)\n            sx +=  me->m_GfxXOffset;\n            sy +=  me->m_GfxYOffset;\n\n            // Calc screen draw position based on camera position\n            // Render::CalcCameraPos(&cx, &cy);\n            // sx = sx - cx;\n            // sy = sy - cy;\n\n            // Register drawing operation\n            auto *op = new RenderBitmapOp();\n            op->m_FramesLeft = 1;\n            op->x = sx;\n            op->y = sy;\n\n            auto &r = me->m_GfxRects[me->m_AnimationFrame];\n            op->sx = r.left;\n            op->sy = r.top;\n            op->sw = r.right;\n            op->sh = r.bottom;\n\n            if(me->m_directImg)\n                op->direct_img = me->m_directImg;\n            else\n                op->direct_img = Renderer::Get().GetImageForResourceCode(me->m_ImgResCode);\n            op->sceneCoords = true;\n            op->m_renderPriority = RENDEROP_DEFAULT_PRIORITY_RENDEROP_SCENE;\n\n            Renderer::Get().AddOp(op);\n            return;\n        }\n    }\n}\n"
  },
  {
    "path": "src/script/luna/sprite_funcs.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SpriteFuncs_HHHH\n#define SpriteFuncs_HHHH\n\nclass CSprite;\nstruct SpriteComponent;\n\nvoid Activate(int code, CSprite *spr);  // Formally add the components specified by \"code\" to sprite behaviors\n\nnamespace SpriteFunc\n{\n\n/////////////////////\n/// Behavior funcs///\n/////////////////////\n\n\n// Collision funcs\nvoid OnPlayerCollide(CSprite *me, SpriteComponent *obj);        // Activate given behavior when colliding with player\nvoid OnPlayerDistance(CSprite *me, SpriteComponent *obj);\nvoid PlayerCollectible(CSprite *me, SpriteComponent *obj);      // Calls Die() if detecting collision with player\n\n// Sprite personal variable funcs\nvoid SetSpriteVar(CSprite *me, SpriteComponent *obj);           // Set sprite custom variable\nvoid IfSpriteVar(CSprite *me, SpriteComponent *obj);            // Activate component based on custom variable comparison\nvoid IfLunaVar(CSprite *me, SpriteComponent *obj);              // Activate component based on lunadll user variable state\n\n// Wait-on-condition funcs\nvoid WaitForPlayer(CSprite *me, SpriteComponent *obj);          // Activate linked component on a player memory condition\nvoid PlayerHoldingSprite(CSprite *me, SpriteComponent *obj);    // Activate linked component when player holding certain sprite\n\n// Random funcs\nvoid RandomComponent(CSprite *me, SpriteComponent *obj);        // Trigger one of 5 random component codes at a 25% chance each\nvoid RandomComponentRange(CSprite *me, SpriteComponent *obj);   // Trigger a component code in the range of two numbers\n\n// Self-action funcs\nvoid Die(CSprite *me, SpriteComponent *obj);\n\n// Speed funcs\nvoid Deccelerate(CSprite *me, SpriteComponent *obj);        // Subtract sprite's speed towards minimum for each active frame\nvoid Accelerate(CSprite *me, SpriteComponent *obj);         // Add to sprite's speed towards maximum for each active frame\nvoid AccelToPlayer(CSprite *me, SpriteComponent *obj);      // Push towards player for each active frame\nvoid ApplyVariableGravity(CSprite *me, SpriteComponent *obj); // Apply gravity specified in a lunadll variable\n\n// Teleport funcs\nvoid TeleportNearPlayer(CSprite *me, SpriteComponent *obj); // Direct set x/y position to somewhere in a circle around the player\nvoid TeleportTo(CSprite *me, SpriteComponent *obj);         // Teleport to direct coordinates\n\n// Move funcs\nvoid PhaseMove(CSprite *me, SpriteComponent *obj);          // Move directly according to speed, ignoring collisions\nvoid BumpMove(CSprite *me, SpriteComponent *obj);           // Move according to speed, bump into and reverse speed when colliding\nvoid CrashMove(CSprite *me, SpriteComponent *obj);          // Calls die() when colliding with any block\n\n// Set funcs\nvoid SetXSpeed(CSprite *me, SpriteComponent *obj);\nvoid SetYSpeed(CSprite *me, SpriteComponent *obj);\nvoid SetAlwaysProcess(CSprite *me, SpriteComponent *obj);\nvoid SetVisible(CSprite *me, SpriteComponent *obj);\nvoid SetHitbox(CSprite *me, SpriteComponent *obj);\n\n// Action funcs\nvoid TriggerLunaEvent(CSprite *me, SpriteComponent *obj);   // Trigger a lunadll eventAT_BumpMove\nvoid HarmPlayer(CSprite *me, SpriteComponent *obj);\n\n// Generator funcs\nvoid GenerateInRadius(CSprite *me, SpriteComponent *obj);   // Generate a sprite in a random radius around this sprite\nvoid GenerateAtAngle(CSprite *me, SpriteComponent *obj);    // Generate a sprite with direction from CVAR_GEN_ANGLE\n\n// Time funcs\nvoid SpriteTimer(CSprite *me, SpriteComponent *obj);        // Counts down to 0 and then activates a component\n\n// Animation funcs\nvoid BasicAnimate(CSprite *me, SpriteComponent *obj);       // Simple animation func (moves drawing rect down the spritesheet)\nvoid Blink(CSprite *me, SpriteComponent *obj);              // Cause sprite to blink by toggling visiblity flag on and off quickly\nvoid AnimateFloat(CSprite *me, SpriteComponent *obj);       // Create \"floaty\" effect by oscillating graphics offset height\n\n// Debug funcs\nvoid SpriteDebug(CSprite *me, SpriteComponent *obj);        // Prints diagnostic information about the sprite this is attached to\n\n\n//////////////////\n/// Draw funcs ///\n//////////////////\n\nvoid StaticDraw(CSprite *me);       // Draw sprite to absolute position on the screen\nvoid RelativeDraw(CSprite *me);     // Draw sprite inside level, relative to camera position\n}\n\n#endif // SpriteFuncs_HHHH\n"
  },
  {
    "path": "src/script/msg_macros.cpp",
    "content": "/*\n * Moondust, a free game engine and development kit for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#include <cstring>\n#include <string>\n#include <climits>\n#include <array>\n#include \"msg_macros.h\"\n\n#ifdef MOONDUST_MSG_MACROS_ERROR_HANDLING\n#   define MSG_MACRO_ERROR_ARG , MsgMacroErrors *error\n#   define D_ERROR(arg) if(error) *error = arg\n#   define D_ELSE_SEEN(arg) else_seen = arg\n#   define D_CHECK_ELSE_SEEN else_seen\n#else\n#   define MSG_MACRO_ERROR_ARG\n#   define D_ERROR(arg) (void)1\n#   define D_ELSE_SEEN(arg) (void)1\n#   define D_CHECK_ELSE_SEEN false\n#endif\n\n\nstatic void skip_spaces(const char*& buffer)\n{\n    while(*buffer == ' ')\n        buffer++;\n}\n\nstruct Keyword\n{\n    const char* str;\n    const int length;\n};\n\nstatic int enumerateKeyword(const char*& buffer, const Keyword* keywords, int num_keywords)\n{\n    skip_spaces(buffer);\n\n    for(int keyword_i = 0; keyword_i < num_keywords; keyword_i++)\n    {\n        const Keyword& keyword = keywords[keyword_i];\n\n        // check whether keyword is a prefix of buffer\n        if(strncmp(buffer, keyword.str, keyword.length) != 0)\n            continue;\n\n        // check whether buffer has a token separator following keyword (more validation can be done later)\n        const char end = buffer[keyword.length];\n        if(!(end == '\\n' || end == ' ' || end == '\\0' || end == '(' || end == ',' || end == ')'))\n            continue;\n\n        buffer += keyword.length;\n        skip_spaces(buffer);\n\n        return keyword_i;\n    }\n\n    return -1;\n}\n\nenum CondCmd\n{\n    CondCmd_Unknown = -1,\n    // Regular \"if\"\n    CondCmd_If = 0,\n    // Regular \"elif\"\n    CondCmd_Elif,\n    // Regular \"endif\"\n    CondCmd_Else,\n    // Regular \"else\"\n    CondCmd_Endif,\n    // \"if\" without line breaking, space will be appended\n    CondCmd_IW_If,\n    // \"if\" without line breaking, nothing will be appended\n    CondCmd_IN_If,\n    // \"endif\" without line breaking, space will be appended\n    CondCmd_IW_Endif,\n    // \"endif\" without line breaking, nothing will be appended\n    CondCmd_IN_Endif,\n    // dummy limit item\n    CondCmd_MAX,\n};\n\nconst std::array<Keyword, CondCmd_MAX> commands = {Keyword{\"if\", 2}, Keyword{\"elif\", 4}, Keyword{\"else\", 4}, Keyword{\"endif\", 5}, Keyword{\"if_iw\", 5}, Keyword{\"if_in\", 5}, Keyword{\"endif_iw\", 8}, Keyword{\"endif_in\", 8}};\n\nenum CondFunc\n{\n    CondFunc_Unknown = -1,\n    CondFunc_Player = 0,\n    CondFunc_State,\n    CondFunc_MAX,\n};\n\nconst std::array<Keyword, CondFunc_MAX> funcs = {Keyword{\"player\", 6}, Keyword{\"state\", 5}};\n\nstatic CondCmd enumerateCmd(const char*& buffer)\n{\n    return (CondCmd)enumerateKeyword(buffer, commands.data(), commands.size());\n}\n\nstatic CondFunc enumerateFunc(const char*& buffer)\n{\n    return (CondFunc)enumerateKeyword(buffer, funcs.data(), funcs.size());\n}\n\nstatic int parseValue(const char*& buffer)\n{\n    skip_spaces(buffer);\n    if(!std::isdigit(*buffer))\n        return INT_MIN;\n\n    int ret = std::atoi(buffer);\n\n    while(std::isdigit(*buffer))\n        buffer++;\n\n    return ret;\n}\n\nvoid msgMacroProcess(const std::string &in, std::string &ret, int macro_player, int macro_state MSG_MACRO_ERROR_ARG)\n{\n    enum class Status\n    {\n        outside = 0,   // outside of if/else clause, lines go to output, wait for #if\n        inside,        // inside an active clause, lines go to output, wait for #elif / #else / #endif\n        skip_to_else,  // no clause has been triggered yet, look for next elif/else\n        skip_to_endif, // a clause has been triggered, look for else\n    } status = Status::outside;\n\n    bool line_start = true;\n\n#ifdef MOONDUST_MSG_MACROS_ERROR_HANDLING\n    bool else_seen = false;\n#endif\n\n    for(const char* in_ptr = in.c_str(); *in_ptr != '\\0';)\n    {\n        char cur = *in_ptr;\n        in_ptr++;\n\n        // check if this is a macro\n        bool is_macro = (line_start && cur == '#');\n\n        if(!is_macro)\n        {\n            if(status == Status::outside || status == Status::inside)\n                ret.push_back(cur);\n\n            line_start = (cur == '\\n');\n            continue;\n        }\n\n        // exit a current block\n        if(status == Status::inside)\n            status = Status::skip_to_endif;\n\n        // advances in_ptr\n        CondCmd cmd = enumerateCmd(in_ptr);\n\n        if(cmd == CondCmd_Unknown)\n        {\n            D_ERROR(MSG_MACRO_ERROR_UNKNOWN_CMD);\n            return;\n        }\n        else if(cmd == CondCmd_Endif || cmd == CondCmd_IW_Endif || cmd == CondCmd_IN_Endif)\n        {\n            if(status == Status::outside)\n            {\n                D_ERROR(MSG_MACRO_ERROR_ILLEGAL_CMD);\n                return;\n            }\n\n            // if anything has been written from the current condition\n            if(ret.size() && ret.back() == '\\n' && status != Status::skip_to_else)\n            {\n                if(cmd == CondCmd_IN_Endif)\n                    ret.pop_back();\n                else if(cmd == CondCmd_IW_Endif)\n                    ret.back() = ' ';\n            }\n\n            D_ELSE_SEEN(false);\n            status = Status::outside;\n        }\n        else if(cmd == CondCmd_Else)\n        {\n            if(status == Status::outside || D_CHECK_ELSE_SEEN)\n            {\n                D_ERROR(MSG_MACRO_ERROR_ILLEGAL_CMD);\n                return;\n            }\n\n            D_ELSE_SEEN(true);\n\n            // enter the else clause if we haven't already matched\n            if(status != Status::skip_to_endif)\n                status = Status::inside;\n        }\n        // condition macros\n        else\n        {\n            if(cmd == CondCmd_If || cmd == CondCmd_IW_If || cmd == CondCmd_IN_If)\n            {\n                if(status != Status::outside)\n                {\n                    D_ERROR(MSG_MACRO_ERROR_ILLEGAL_CMD);\n                    return;\n                }\n\n                if(ret.size() && ret.back() == '\\n')\n                {\n                    if(cmd == CondCmd_IN_If)\n                        ret.pop_back();\n                    else if(cmd == CondCmd_IW_If)\n                        ret.back() = ' ';\n                }\n            }\n            else if(status == Status::outside)\n            {\n                D_ERROR(MSG_MACRO_ERROR_ILLEGAL_CMD);\n                return;\n            }\n            else if(D_CHECK_ELSE_SEEN)\n            {\n                D_ERROR(MSG_MACRO_ERROR_ILLEGAL_CMD);\n                return;\n            }\n\n            // -- CONDITION CHECK CODE --\n            // we have a condition to check, and decide whether to enter Status::inside or Status::skip_to_else\n            CondFunc func = enumerateFunc(in_ptr);\n\n            int true_value = 0;\n            if(func == CondFunc_Unknown)\n            {\n                D_ERROR((*in_ptr == '\\n') ? MSG_MACRO_ERROR_BAD_CMD_SYNTAX : MSG_MACRO_ERROR_UNKNOWN_FUNC);\n                return;\n            }\n            else if(func == CondFunc_Player)\n                true_value = macro_player;\n            else if(func == CondFunc_State)\n                true_value = macro_state;\n\n            if(*(in_ptr++) != '(')\n            {\n                D_ERROR(MSG_MACRO_ERROR_BAD_FUNC_SYNTAX);\n                return;\n            }\n\n            // look for value that matches true_value\n            bool matched = false;\n            while(true)\n            {\n                int value = parseValue(in_ptr);\n                if(value == INT_MIN)\n                {\n                    D_ERROR((*in_ptr == '\\n' || *in_ptr == '(' || *in_ptr == ',' || *in_ptr == ')') ? MSG_MACRO_ERROR_BAD_FUNC_SYNTAX : MSG_MACRO_ERROR_BAD_FUNC_ARGS);\n                    return;\n                }\n\n#ifdef MOONDUST_MSG_MACROS_ERROR_HANDLING\n                bool has_trailing_space = (*in_ptr == ' ');\n#endif\n                skip_spaces(in_ptr);\n\n                if(value == true_value)\n                    matched = true;\n\n                char delim = *(in_ptr++);\n                switch(delim)\n                {\n                case ')':\n                    // double break below\n                    break;\n                case ',':\n                    continue;\n                default:\n                    D_ERROR((delim == '\\n' || delim == '(' || delim == '\\0' || has_trailing_space) ? MSG_MACRO_ERROR_BAD_FUNC_SYNTAX : MSG_MACRO_ERROR_BAD_FUNC_ARGS);\n\n                    return;\n                }\n\n                // case ')' from above\n                break;\n            }\n\n            skip_spaces(in_ptr);\n\n            if(status != Status::skip_to_endif)\n            {\n                if(matched)\n                    status = Status::inside;\n                else\n                    status = Status::skip_to_else;\n            }\n        }\n\n        // done with macro, time to proceed\n        if(*in_ptr == '\\0')\n            break;\n\n        if(*in_ptr != '\\n')\n        {\n            D_ERROR(MSG_MACRO_ERROR_EXTRA_SYMBOLS_AT_END);\n            return;\n        }\n\n        line_start = true;\n        in_ptr++;\n    }\n\n    // remove trailing whitespace for legacy purposes\n    if(ret.size() && ret.back() == '\\n')\n        ret.pop_back();\n\n    D_ERROR(MSG_MACRO_ERROR_OK);\n}\n"
  },
  {
    "path": "src/script/msg_macros.h",
    "content": "/*\n * Moondust, a free game engine and development kit for platform game making\n * Copyright (c) 2014-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This software is licensed under a dual license system (MIT or GPL version 3 or later).\n * This means you are free to choose with which of both licenses (MIT or GPL version 3 or later)\n * you want to use this software.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * You can see text of MIT license in the LICENSE.mit file you can see in Engine folder,\n * or see https://mit-license.org/.\n *\n * You can see text of GPLv3 license in the LICENSE.gpl3 file you can see in Engine folder,\n * or see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef MSGMACROS_H\n#define MSGMACROS_H\n\n#include <string>\n#ifdef MOONDUST_UNIT_TEST\n#   include <vector>\n#   ifndef MOONDUST_MSG_MACROS_ERROR_HANDLING\n#       define MOONDUST_MSG_MACROS_ERROR_HANDLING\n#   endif\n#endif\n\n#ifdef MOONDUST_MSG_MACROS_ERROR_HANDLING\nenum MsgMacroErrors\n{\n    MSG_MACRO_ERROR_OK = 0,\n    MSG_MACRO_ERROR_BAD_CMD_SYNTAX,\n    MSG_MACRO_ERROR_BAD_FUNC_SYNTAX,\n    MSG_MACRO_ERROR_UNKNOWN_CMD,\n    MSG_MACRO_ERROR_UNKNOWN_FUNC,\n    MSG_MACRO_ERROR_BAD_FUNC_ARGS,\n    MSG_MACRO_ERROR_EXTRA_SYMBOLS_AT_END,\n    MSG_MACRO_ERROR_ILLEGAL_CMD,\n};\n#endif\n\n#ifdef MOONDUST_UNIT_TEST\nextern bool msgMacroParseTokens(const std::string &line, std::vector<std::string> &tokens);\n#endif\n\n#ifdef MOONDUST_MSG_MACROS_ERROR_HANDLING\n/**\n * @brief Pre-Process the message (apply conditions, or place player names where is possible)\n * @param in Input text to pre-process\n * @param out Output text of pre-processing result\n * @param macro_player ID of playable character to match the condition\n * @param macro_state ID of character state to match the condition\n * @param error Optional parse error\n */\nextern void msgMacroProcess(const std::string &in, std::string &out, int macro_player, int macro_state, MsgMacroErrors *error = nullptr);\n#else // MOONDUST_MSG_MACROS_ERROR_HANDLING\n/**\n * @brief Pre-Process the message (apply conditions, or place player names where is possible)\n * @param in Input text to pre-process\n * @param out Output text of pre-processing result\n * @param macro_player ID of playable character to match the condition\n * @param macro_state ID of character state to match the condition\n */\nextern void msgMacroProcess(const std::string &in, std::string &out, int macro_player, int macro_state);\n#endif // MOONDUST_MSG_MACROS_ERROR_HANDLING\n\n#endif // MSGMACROS_H\n"
  },
  {
    "path": "src/script/msg_preprocessor.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"msg_preprocessor.h\"\n#include \"msg_macros.h\"\n\nvoid preProcessMessage(std::string &text, int playerWho)\n{\n    std::string ret;\n    bool canCheckPlayers = playerWho > 0 && playerWho <= numPlayers;\n    int macro_player = canCheckPlayers ? Player[playerWho].Character : -1;\n    int macro_state = canCheckPlayers ? Player[playerWho].State : -1;\n\n    msgMacroProcess(text, ret, macro_player, macro_state);\n\n    text = ret;\n}\n"
  },
  {
    "path": "src/script/msg_preprocessor.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef MSG_PREPROCESSOR_H\n#define MSG_PREPROCESSOR_H\n\n#include <string>\n\n/**\n * @brief Pre-Process the message (apply conditions, or place player names where is possible)\n * @param text Input-Output text to pre-process\n * @param playerWho The player number who triggered this message box, or -1 to heuristically detect somebody.\n */\nextern void preProcessMessage(std::string &text, int playerWho);\n\n#endif // MSG_PREPROCESSOR_H\n"
  },
  {
    "path": "src/sorting.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"globals.h\"\n#include \"sorting.h\"\n#include \"npc_traits.h\"\n\n#include <algorithm>\n\n// these are now used only when saving levels\nvoid qSortBlocksY(int min, int max)\n{\n    Block_t medBlock;\n    int hi = 0;\n    int lo = 0;\n    int i = 0;\n    if(min >= max)\n        return;\n    i = (max + min) / 2;\n    medBlock = Block[i];\n    Block[i] = Block[min];\n    lo = min;\n    hi = max;\n    do\n    {\n        while(Block[hi].Location.Y >= medBlock.Location.Y)\n        {\n            hi -= 1;\n            if(hi <= lo)\n                break;\n        }\n        if(hi <= lo)\n        {\n            Block[lo] = medBlock;\n            break;\n        }\n        Block[lo] = Block[hi];\n        lo += 1;\n        while(Block[lo].Location.Y < medBlock.Location.Y)\n        {\n            lo += 1;\n            if(lo >= hi)\n                break;\n        }\n        if(lo >= hi)\n        {\n            lo = hi;\n            Block[hi] = medBlock;\n            break;\n        }\n        Block[hi] = Block[lo];\n    } while(true);\n    qSortBlocksY(min, lo - 1);\n    qSortBlocksY(lo + 1, max);\n}\n\nvoid qSortBlocksX(int min, int max)\n{\n\n    Block_t medBlock;\n    int hi = 0;\n    int lo = 0;\n    int i = 0;\n    if(min >= max)\n        return;\n    i = (max + min) / 2;\n    medBlock = Block[i];\n    Block[i] = Block[min];\n    lo = min;\n    hi = max;\n    do\n    {\n        while(Block[hi].Location.X >= medBlock.Location.X)\n        {\n            hi -= 1;\n            if(hi <= lo)\n                break;\n        }\n        if(hi <= lo)\n        {\n            Block[lo] = medBlock;\n            break;\n        }\n        Block[lo] = Block[hi];\n        lo += 1;\n        while(Block[lo].Location.X < medBlock.Location.X)\n        {\n            lo += 1;\n            if(lo >= hi)\n                break;\n        }\n        if(lo >= hi)\n        {\n            lo = hi;\n            Block[hi] = medBlock;\n            break;\n        }\n        Block[hi] = Block[lo];\n    } while(true);\n    qSortBlocksX(min, lo - 1);\n    qSortBlocksX(lo + 1, max);\n}\n\nvoid qSortBlocks(int min, int max)\n{\n    if(min >= max)\n        return;\n\n    std::stable_sort(&Block[min], (&Block[max]) + 1,\n    [](const Block_t& a, const Block_t& b)\n    {\n        return (a.Location.X <= b.Location.X\n            && (a.Location.X < b.Location.X\n                || a.Location.Y < b.Location.Y));\n    });\n}\n\nvoid qSortBackgrounds(int min, int max, bool use_x)\n{\n    if(min >= max)\n        return;\n\n    std::stable_sort(&Background[min], (&Background[max]) + 1,\n    [use_x](const Background_t& a, const Background_t& b)\n    {\n        return a.SortPriority < b.SortPriority || (use_x && a.SortPriority == b.SortPriority && a.Location.X < b.Location.X);\n    });\n\n    // old code was acceptable but didn't make it easy to sort by SortPriority first, Location second\n#if 0\n    Background_t medBackground;\n    double medBackgroundPri = 0.0;\n    int hi = 0;\n    int lo = 0;\n    int i = 0;\n    if(min >= max)\n        return;\n    i = floor((max + min) / 2.0);\n    medBackground = Background[i];\n    medBackgroundPri = BackGroundPri(i);\n    Background[i] = Background[min];\n    lo = min;\n    hi = max;\n    do\n    {\n        while(BackGroundPri(hi) >= medBackgroundPri)\n        {\n\n            hi -= 1;\n            if(hi <= lo)\n                break;\n        }\n        if(hi <= lo)\n        {\n            Background[lo] = medBackground;\n            break;\n        }\n        Background[lo] = Background[hi];\n        lo += 1;\n        while(BackGroundPri(lo) < medBackgroundPri)\n        {\n            lo += 1;\n            if(lo >= hi)\n                break;\n        }\n        if(lo >= hi)\n        {\n            lo = hi;\n            Background[hi] = medBackground;\n            break;\n        }\n        Background[hi] = Background[lo];\n    } while(true);\n    qSortBackgrounds(min, lo - 1);\n    qSortBackgrounds(lo + 1, max);\n#endif\n}\n\n// deprecated by block quadtree\n\n#if 0\nvoid FindBlocks()\n{\n    int A = 0;\n    int B = 0;\n//    int C = 0;\n    int curBlk = 0;\n//    bool fBool = false;\n    curBlk = 1;\n    for(A = -FLBlocks; A <= FLBlocks; A++)\n    {\n        for(B = curBlk; B <= numBlock; B++)\n        {\n            if(Block[B].Location.X + Block[B].Location.Width >= A * 32)\n            {\n                curBlk = int(B);\n                break;\n            }\n        }\n        FirstBlock[A] = curBlk;\n    }\n    curBlk = numBlock;\n\n    for(A = FLBlocks; A >= -FLBlocks; A--)\n    {\n//        fBool = false;\n        for(B = curBlk; B >= 1; B--)\n        {\n            if(Block[B].Location.X <= A * 32)\n            {\n                curBlk = int(B);\n                break;\n            }\n        }\n        LastBlock[A] = curBlk;\n    }\n    // BlocksSorted = true;\n}\n\nvoid BlockSort()\n{\n    int A = 0;\n    int B = 0;\n    Block_t tempBlock;\n\n    // Sort Sizable Blocks\n    for(A = 1; A <= numBlock; A++)\n    {\n        if(BlockIsSizable[Block[A].Type])\n        {\n            for(B = 1; B < A; B++)\n            {\n                if(!BlockIsSizable[Block[B].Type])\n                {\n                    tempBlock = Block[A];\n                    Block[A] = Block[B];\n                    Block[B] = tempBlock;\n                    break;\n                }\n            }\n        }\n    }\n\n    for(A = 1; A <= numBlock; A++)\n    {\n        if(BlockIsSizable[Block[A].Type])\n        {\n            for(B = 1; B <= numBlock; B++)\n            {\n                if(BlockIsSizable[Block[B].Type])\n                {\n                    if(B != 1)\n                    {\n                        if(Block[A].Location.Y < Block[B].Location.Y && A > B)\n                        {\n                            tempBlock = Block[A];\n                            Block[A] = Block[B];\n                            Block[B] = tempBlock;\n                        }\n                        else if(Block[A].Location.Y > Block[B].Location.Y && A < B)\n                        {\n                            tempBlock = Block[A];\n                            Block[A] = Block[B];\n                            Block[B] = tempBlock;\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid BlockSort2()\n{\n    int A = 0;\n    int B = 0;\n    Block_t tempBlock;\n    bool sortAgain = false;\n    do\n    {\n        sortAgain = false;\n        for(A = 1; A <= numBlock; A++)\n        {\n            for(B = 1; B <= numBlock; B++)\n            {\n                if(B != A)\n                {\n                    if(Block[A].Location.Y < Block[B].Location.Y && A > B)\n                    {\n                        tempBlock = Block[A];\n                        Block[A] = Block[B];\n                        Block[B] = tempBlock;\n                        sortAgain = true;\n                    }\n                }\n            }\n        }\n    } while(sortAgain);\n}\n\nvoid BackgroundSort()\n{\n\n    int A = 0;\n    int B = 0;\n    Background_t tempBackground;\n    bool sortAgain = false;\n\n    do\n    {\n        sortAgain = false;\n        for(A = 1; A <= numBackground; A++)\n        {\n            for(B = 1; B <= numBackground; B++)\n            {\n                if(B != A)\n                {\n                    if(BackGroundPri(A) < BackGroundPri(B) && A > B)\n                    {\n                        tempBackground = Background[A];\n                        Background[A] = Background[B];\n                        Background[B] = tempBackground;\n                        sortAgain = true;\n                    }\n                }\n            }\n        }\n    } while(sortAgain);\n}\n#endif\n\n//! checks if a custom layer is set, returns -2, -1, +1, or +2 if so, 0 if not\nint Background_t::GetCustomLayer() const\n{\n    switch(SortPriority & 0xf8)\n    {\n    case(0x08):\n    case(0x18):\n    case(0x28):\n        return -2;\n    case(0x30):\n    case(0x48):\n    case(0x98):\n        return -1;\n    case(0xC0):\n    case(0xD8):\n        return +1;\n    case(0xF8):\n        return +2;\n    default:\n        return 0;\n    }\n}\n\n//! returns the custom offset of a BGO\nint Background_t::GetCustomOffset() const\n{\n    return int((SortPriority) & 0x07) - 4;\n}\n\n//! update a background's priority based on its current type, custom layer, and custom offset\nvoid Background_t::UpdateSortPriority()\n{\n    // check user-specified Z-layer\n    int user_order = GetCustomLayer();\n\n    // clear upper 5 bits\n    SortPriority &= ~0xf8;\n\n    // set upper 5 bits by user order\n    if(user_order)\n    {\n        int user_offset = GetCustomOffset();\n\n        if(user_order == -2 && user_offset < 0)\n            SortPriority |= 0x08;\n        else if(user_order == -2 && user_offset == 0)\n            SortPriority |= 0x18;\n        else if(user_order == -2)\n            SortPriority |= 0x28;\n        else if(user_order == -1 && user_offset < 0)\n            SortPriority |= 0x30;\n        else if(user_order == -1 && user_offset == 0)\n            SortPriority |= 0x48;\n        else if(user_order == -1)\n            SortPriority |= 0x98;\n        else if(user_order == +1 && user_offset <= 0)\n            SortPriority |= 0xC0;\n        else if(user_order == +1)\n            SortPriority |= 0xD8;\n        else\n            SortPriority |= 0xF8;\n\n        return;\n    }\n\n    // set upper 5 bits by Type-based order\n\n    // PLANE_LVL_BGO_LOW\n    // custom -2 with - offset is 0x08\n    if(Type == 75 || Type == 76 || Type == 77 || Type == 78 || Type == 14)\n        SortPriority |= 0x10;\n    // custom -2 with 0 offset is 0x18\n    else if(Type == 11 || Type == 12 || Type == 60 || Type == 61)\n        SortPriority |= 0x20;\n    // custom -2 with + offset is 0x28\n    // PLANE_LVL_BGO_NORM\n    // custom -1 with - offset is 0x30\n    else if(Type == 168 || Type == 159 || Type == 172 || Type == 66 || Type == 158) // WATER FALLS\n        SortPriority |= 0x38;\n    else if(Type == 65 || Type == 26 || Type == 82 || Type == 83 || Type == 164 || Type == 165 || Type == 166 || Type == 167 || Type == 168 || Type == 169) // WATER\n        SortPriority |= 0x40;\n    // custom -1 with 0 offset is 0x48\n    else if(Type == 79 || Type == 52)\n        SortPriority |= 0x50;\n    else if(Type == 66)\n        SortPriority |= 0x58;\n    // default is 0x60\n    else if(Type == 1)\n        SortPriority |= 0x68;\n    else if(Type >= 129 && Type <= 131)\n        SortPriority |= 0x70;\n    else if(Type == 139 || Type == 140 || Type == 48)\n        SortPriority |= 0x78;\n    else if(Type == 70 || Type == 71 || Type == 72 || Type == 73 || Type == 74 || Type == 141)\n        SortPriority |= 0x80;\n    else if(Type == 87 || Type == 88 || Type == 92 || Type == 107 || Type == 105 || Type == 104) // Doors\n        SortPriority |= 0x88;\n    else if(Type == 99)\n        SortPriority |= 0x90; // Always doors + 1\n    // custom -1 with + offset is 0x98\n    // PLANE_LVL_BGO_FG\n    // custom +1 with - or 0 offset is 0xC0\n    else if(Foreground[Type])\n        SortPriority |= 0xC8;\n    else if(Type == 65 || Type == 165)\n        SortPriority |= 0xD0;\n    // custom +1 with + offset is 0xD8\n    // PLANE_LVL_BGO_TOP\n    // custom +2 is 0xF8\n    else\n        SortPriority |= 0x60;\n}\n\n//! sets custom layer and offset bits for a bgo and updates its sort priority\nvoid Background_t::SetSortPriority(int layer, int offset)\n{\n    SortPriority = 0;\n\n    // set upper 5 bits by layer\n    if(layer == -2)\n        SortPriority = 0x18;\n    else if(layer == -1)\n        SortPriority = 0x48;\n    else if(layer == +1)\n        SortPriority = 0xC0;\n    else if(layer == +2)\n        SortPriority = 0xF8;\n\n    // set lower 3 bits by offset\n    uint8_t offset_bits = uint8_t(offset + 4) & 7;\n    SortPriority |= offset_bits;\n\n    UpdateSortPriority();\n}\n\n\n#if 0\ndouble BackGroundPri(int A)\n{\n    double tempBackGroundPri = 0;\n\n\n    if(Background[A].SortPriority > 0) // Custom priority per every BGO\n        tempBackGroundPri = Background[A].SortPriority;\n    // Lower Numbers get drawn first\n    else if(Background[A].Type == 11 || Background[A].Type == 12 || Background[A].Type == 60 || Background[A].Type == 61)\n        tempBackGroundPri = 20;\n    else if(Background[A].Type == 65 || Background[A].Type == 26 || Background[A].Type == 82 || Background[A].Type == 83 || Background[A].Type == 164 || Background[A].Type == 165 || Background[A].Type == 166 || Background[A].Type == 167 || Background[A].Type == 168 || Background[A].Type == 169) // WATER\n\n        tempBackGroundPri = 26;\n    else if(Background[A].Type == 168 || Background[A].Type == 159 || Background[A].Type == 172 || Background[A].Type == 66 || Background[A].Type == 158) // WATER FALLS\n        tempBackGroundPri = 25;\n    else if(Background[A].Type == 75 || Background[A].Type == 76 || Background[A].Type == 77 || Background[A].Type == 78 || Background[A].Type == 14)\n        tempBackGroundPri = 10;\n    else if(Background[A].Type == 79 || Background[A].Type == 52)\n        tempBackGroundPri = 30;\n    else if(Background[A].Type == 70 || Background[A].Type == 71 || Background[A].Type == 72 || Background[A].Type == 73 || Background[A].Type == 74 || Background[A].Type == 141)\n        tempBackGroundPri = 90;\n    else if(Background[A].Type == 139 || Background[A].Type == 140 || Background[A].Type == 48)\n        tempBackGroundPri = 80;\n    else if(Background[A].Type == 65 || Background[A].Type == 165)\n        tempBackGroundPri = 150;\n    else if(Foreground[Background[A].Type])\n        tempBackGroundPri = 125;\n    else if(Background[A].Type == 66)\n        tempBackGroundPri = 50;\n    else if(Background[A].Type == 99)\n        tempBackGroundPri = 99; // Always doors + 1\n    else if(Background[A].Type == 87 || Background[A].Type == 88 || Background[A].Type == 92 || Background[A].Type == 107 || Background[A].Type == 105 || Background[A].Type == 104) // Doors\n        tempBackGroundPri = 98;\n    else if(Background[A].Type >= 129 && Background[A].Type <= 131)\n        tempBackGroundPri = 76;\n    else if(Background[A].Type == 1)\n        tempBackGroundPri = 77;\n    else\n        tempBackGroundPri = 75;\n\n    //tempBackGroundPri += Background[A].Location.X / 10000000.0;\n    tempBackGroundPri += Background[A].uid / 100000000000.0;\n    tempBackGroundPri += Background[A].zOffset;\n\n\n    return tempBackGroundPri;\n}\n#endif\n\nvoid NPCSort()\n{\n    int A = 0;\n    int B = 0;\n    NPC_t tempNPC;\n\n    for(A = 1; A <= numNPCs; A++)\n    {\n        if(NPC[A]->IsACoin)\n        {\n            for(B = 1; B < A; B++)\n            {\n                if(!NPC[B]->IsACoin)\n                {\n                    tempNPC = NPC[A];\n                    NPC[A] = NPC[B];\n                    NPC[B] = tempNPC;\n                    break;\n                }\n            }\n        }\n    }\n}\n\n// deprecated\n#if 0\nvoid FindSBlocks()\n{\n    int A = 0;\n    sBlockNum = 0;\n    for(A = 1; A <= numBlock; A++)\n    {\n        if(BlockIsSizable[Block[A].Type])\n        {\n            sBlockNum += 1;\n            sBlockArray[sBlockNum] = A;\n        }\n    }\n    qSortSBlocks(1, sBlockNum);\n}\n\nvoid qSortSBlocks(int min, int max)\n{\n    int medBlock = 0;\n    int hi = 0;\n    int lo = 0;\n    int i = 0;\n    if(min >= max)\n        return;\n\n    i = floor((max + min) / 2.0);\n    medBlock = sBlockArray[i];\n    sBlockArray[i] = sBlockArray[min];\n    lo = min;\n    hi = max;\n    do\n    {\n        while(Block[sBlockArray[hi]].Location.Y >= Block[medBlock].Location.Y)\n        {\n            hi -= 1;\n            if(hi <= lo)\n                break;\n        }\n        if(hi <= lo)\n        {\n            sBlockArray[lo] = medBlock;\n            break;\n        }\n        sBlockArray[lo] = sBlockArray[hi];\n        lo += 1;\n        while(Block[sBlockArray[lo]].Location.Y < Block[medBlock].Location.Y)\n        {\n            lo += 1;\n            if(lo >= hi)\n                break;\n        }\n        if(lo >= hi)\n        {\n            lo = hi;\n            sBlockArray[hi] = medBlock;\n            break;\n        }\n        sBlockArray[hi] = sBlockArray[lo];\n    } while(true);\n    qSortSBlocks(min, lo - 1);\n    qSortSBlocks(lo + 1, max);\n}\n#endif\n\nvoid qSortNPCsY(int min, int max)\n{\n    NPC_t medNPC;\n    int hi = 0;\n    int lo = 0;\n    int i = 0;\n    if(min >= max)\n        return;\n    i = (max + min) / 2;\n    medNPC = NPC[i];\n    NPC[i] = NPC[min];\n    lo = min;\n    hi = max;\n    do\n    {\n        while(NPC[hi].Location.Y < medNPC.Location.Y)\n        {\n            hi -= 1;\n            if(hi <= lo)\n                break;\n        }\n        if(hi <= lo)\n        {\n            NPC[lo] = medNPC;\n            break;\n        }\n        NPC[lo] = NPC[hi];\n        lo += 1;\n        while(NPC[lo].Location.Y >= medNPC.Location.Y)\n        {\n            lo += 1;\n            if(lo >= hi)\n                break;\n        }\n        if(lo >= hi)\n        {\n            lo = hi;\n            NPC[hi] = medNPC;\n            break;\n        }\n        NPC[hi] = NPC[lo];\n    } while(true);\n    qSortNPCsY(min, lo - 1);\n    qSortNPCsY(lo + 1, max);\n}\n\nvoid UpdateBackgrounds()\n{\n    int A = 0;\n    int B = 0;\n    LastBackground = numBackground;\n    MidBackground = 1;\n    for(A = 1; A <= numBackground; A++)\n    {\n        if(Background[A].SortPriority >= Background_t::PRI_NORM_START)\n        {\n            for(B = A; B <= numBackground; B++)\n            {\n                if(Background[B].SortPriority >= Background_t::PRI_FG_START)\n                {\n                    LastBackground = B - 1;\n                    break;\n                }\n            }\n            break;\n        }\n    }\n    MidBackground = A;\n//    if(noUpdate == false)\n//        Netplay::sendData \"s\" + std::to_string(numBackground) + LB;\n}\n\n// deprecated\n#if 0\nvoid qSortTempBlocksX(int min, int max)\n{\n    Block_t medBlock;\n    int hi = 0;\n    int lo = 0;\n    int i = 0;\n\n    if(min >= max)\n        return;\n\n    i = floor((max + min) / 2.0);\n    medBlock = Block[i];\n    Block[i] = Block[min];\n    lo = min;\n    hi = max;\n\n    do\n    {\n        while(Block[hi].Location.X >= medBlock.Location.X)\n        {\n            hi -= 1;\n            if(hi <= lo)\n                break;\n        }\n        if(hi <= lo)\n        {\n            Block[lo] = medBlock;\n            break;\n        }\n        Block[lo] = Block[hi];\n        lo += 1;\n        while(Block[lo].Location.X < medBlock.Location.X)\n        {\n            lo += 1;\n            if(lo >= hi)\n                break;\n        }\n        if(lo >= hi)\n        {\n            lo = hi;\n            Block[hi] = medBlock;\n            break;\n        }\n        Block[hi] = Block[lo];\n    } while(true);\n\n    qSortBlocksX(min, lo - 1);\n    qSortBlocksX(lo + 1, max);\n}\n#endif\n"
  },
  {
    "path": "src/sorting.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SORTING_H\n#define SORTING_H\n\n// most functions here have been deprecated by the corresponding quadtree functions, but should be preserved for historical comparison\n\n// Public Sub qSortBlocksY(min As Integer, max As Integer) 'quicksort the blocks Y\n// quicksort the blocks Y (kept for compatibility with coin switch logic)\nvoid qSortBlocksY(int min, int max);\n// Public Sub qSortBlocksX(min As Integer, max As Integer) 'quicksort the blocks X\n// quicksort the blocks X (kept for compatibility with coin switch and tempBlock logic)\nvoid qSortBlocksX(int min, int max);\n// NEW: just sort them all at once, stably (used at save to prevent altering order as much as possible)\nvoid qSortBlocks(int min, int max);\n// Public Sub qSortBackgrounds(min As Integer, max As Integer) 'quicksort the backgrounds\n// quicksort the backgrounds\nvoid qSortBackgrounds(int min, int max, bool use_x = true);\n\n// Public Sub FindBlocks() 'create a table of contents for blocks for an optimization\n// create a table of contents for blocks for an optimization\n// void FindBlocks();\n\n// Public Sub BlockSort() 'sizable block sorting\n// sizable block sorting\n// void BlockSort();\n// Public Sub BlockSort2() 'Super Block sorting / slow and only used when saving\n// Super Block sorting / slow and only used when saving\n// void BlockSort2();\n// Public Sub BackgroundSort()\n// void BackgroundSort();\n\n// Public Function BackGroundPri(A As Integer) As Double 'finds where the backgrounds should be put to set drawing priority\n// finds where the backgrounds should be put to set drawing priority\n// double BackGroundPri(int A);\n// Public Sub NPCSort()\nvoid NPCSort();\n// Public Sub FindSBlocks() 'sorts sizable blocks\n// sorts sizable blocks\n// void FindSBlocks();\n// Public Sub qSortSBlocks(min As Integer, max As Integer)\n// void qSortSBlocks(int min, int max);\n// Public Sub qSortNPCsY(min As Integer, max As Integer)\nvoid qSortNPCsY(int min, int max);\n// Public Sub UpdateBackgrounds()\nvoid UpdateBackgrounds();\n// Public Sub qSortTempBlocksX(min As Integer, max As Integer)\n// void qSortTempBlocksX(int min, int max);\n\n\n#endif // SORTING_H\n"
  },
  {
    "path": "src/sound/fx/fx_common.hpp",
    "content": "#ifndef FX_COMMON_HPP\n#define FX_COMMON_HPP\n\n#include <stdint.h>\n#include \"fx_format.h\"\n\n#ifndef INT8_MIN\n#define INT8_MIN    (-0x7f - 1)\n#endif\n#ifndef INT8_MAX\n#define INT8_MAX    0x7f\n#endif\n\n#ifndef UINT8_MAX\n#define UINT8_MAX   0xff\n#endif\n\n#ifndef INT16_MIN\n#define INT16_MIN   (-0x7fff - 1)\n#endif\n#ifndef INT16_MAX\n#define INT16_MAX   0x7fff\n#endif\n\n#ifndef UINT16_MAX\n#define UINT16_MAX  0xffff\n#endif\n\n#ifndef INT32_MIN\n#define INT32_MIN   (-0x7fffffff - 1)\n#endif\n\n#ifndef INT32_MAX\n#define INT32_MAX   0x7fffffff\n#endif\n\n#define MAX_CHANNELS    10\n\n// Float32-LE\nstatic float getFloatLSBSample(uint8_t *raw, int c)\n{\n    union\n    {\n        float f;\n        uint32_t r;\n    } o;\n    raw += (c * sizeof(float));\n    o.r = (((uint32_t)raw[0] <<  0) & 0x000000FF) |\n          (((uint32_t)raw[1] <<  8) & 0x0000FF00) |\n          (((uint32_t)raw[2] << 16) & 0x00FF0000) |\n          (((uint32_t)raw[3] << 24) & 0xFF000000);\n    return o.f;\n}\nstatic void setFloatLSBSample(uint8_t **raw, float ov)\n{\n    void *t = &ov;\n    uint32_t r = *(uint32_t*)t;\n    *(*raw)++ = (uint8_t)((r >> 0) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 8) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 16) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 24) & 0xFF);\n}\n\n// Float32-BE\nstatic float getFloatMSBSample(uint8_t *raw, int c)\n{\n    union\n    {\n        float f;\n        uint32_t r;\n    } o;\n    raw += (c * sizeof(float));\n    o.r = (((uint32_t)raw[3] <<  0) & 0x000000FF) |\n          (((uint32_t)raw[2] <<  8) & 0x0000FF00) |\n          (((uint32_t)raw[1] << 16) & 0x00FF0000) |\n          (((uint32_t)raw[0] << 24) & 0xFF000000);\n    return o.f;\n}\nstatic void setFloatMSBSample(uint8_t **raw, float ov)\n{\n    void *t = &ov;\n    uint32_t r = *(uint32_t*)t;\n    *(*raw)++ = (uint8_t)((r >> 24) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 16) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 8) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 0) & 0xFF);\n}\n\n// int32_t-LE\nstatic float getInt32LSB(uint8_t *raw, int c)\n{\n    uint32_t r;\n    int32_t f;\n    raw += (c * sizeof(int32_t));\n    r = (((uint32_t)raw[0] <<  0) & 0x000000FF) |\n        (((uint32_t)raw[1] <<  8) & 0x0000FF00) |\n        (((uint32_t)raw[2] << 16) & 0x00FF0000) |\n        (((uint32_t)raw[3] << 24) & 0xFF000000);\n    f = *(int32_t*)(&r);\n    return (float)((double)f / INT32_MAX);\n}\nstatic void setInt32LSB(uint8_t **raw, float ov)\n{\n    int32_t f = ((int32_t)(double(ov) * INT32_MAX));\n    uint32_t r = *(uint32_t*)(&f);\n    *(*raw)++ = (uint8_t)((r >> 0) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 8) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 16) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 24) & 0xFF);\n}\n\n// int32_t-BE\nstatic float getInt32MSB(uint8_t *raw, int c)\n{\n    uint32_t r;\n    int32_t f;\n    raw += (c * sizeof(int32_t));\n    r = (((uint32_t)raw[3] <<  0) & 0x000000FF) |\n        (((uint32_t)raw[2] <<  8) & 0x0000FF00) |\n        (((uint32_t)raw[1] << 16) & 0x00FF0000) |\n        (((uint32_t)raw[0] << 24) & 0xFF000000);\n    f = *(int32_t*)(&r);\n    return (float)((double)f / INT32_MAX);\n}\nstatic void setInt32MSB(uint8_t **raw, float ov)\n{\n    int32_t f = (int32_t(double(ov) * INT32_MAX));\n    uint32_t r = *(uint32_t*)(&f);\n    *(*raw)++ = (uint8_t)((r >> 24) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 16) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 8) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 0) & 0xFF);\n}\n\n// int16_t-LE\nstatic float getInt16LSB(uint8_t *raw, int c)\n{\n    uint16_t r;\n    int16_t f;\n    raw += (c * sizeof(int16_t));\n    r = (((uint16_t)raw[0] <<  0) & 0x00FF) |\n        (((uint16_t)raw[1] <<  8) & 0xFF00);\n    f = *(int16_t*)(&r);\n    return (float)f / INT16_MAX;\n}\nstatic void setInt16LSB(uint8_t **raw, float ov)\n{\n    int16_t f = int16_t(ov * INT16_MAX);\n    uint16_t r = *(uint16_t*)(&f);\n    *(*raw)++ = (uint8_t)((r >> 0) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 8) & 0xFF);\n}\n\n// int16_t-BE\nstatic float getInt16MSB(uint8_t *raw, int c)\n{\n    uint16_t r;\n    int16_t f;\n    raw += (c * sizeof(int16_t));\n    r = (((uint16_t)raw[1] <<  0) & 0x00FF) |\n        (((uint16_t)raw[0] <<  8) & 0xFF00);\n    f = *(int16_t*)(&r);\n    return (float)f / INT16_MIN;\n}\nstatic void setInt16MSB(uint8_t **raw, float ov)\n{\n    int16_t f = int16_t(ov * INT16_MAX);\n    uint16_t r = *(uint16_t*)(&f);\n    *(*raw)++ = (uint8_t)((r >> 8) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 0) & 0xFF);\n}\n\n// uint16_t-LE\nstatic float getuint16_tLSB(uint8_t *raw, int c)\n{\n    uint16_t r;\n    float f;\n    raw += (c * sizeof(uint16_t));\n    r = (((uint16_t)raw[0] <<  0) & 0x00FF) |\n        (((uint16_t)raw[1] <<  8) & 0xFF00);\n    f = ((float)r + INT16_MIN) / INT16_MAX;\n    return f;\n}\nstatic void setuint16_tLSB(uint8_t **raw, float ov)\n{\n    int16_t f = int16_t((ov * INT16_MAX) - INT16_MIN);\n    uint16_t r = *(uint16_t*)(&f);\n    *(*raw)++ = (uint8_t)((r >> 0) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 8) & 0xFF);\n}\n\n// uint16_t-BE\nstatic float getuint16_tMSB(uint8_t *raw, int c)\n{\n    uint16_t r;\n    float f;\n    raw += (c * sizeof(uint16_t));\n    r = (((uint16_t)raw[1] <<  0) & 0x00FF) |\n        (((uint16_t)raw[0] <<  8) & 0xFF00);\n    f = ((float)r + INT16_MIN) / INT16_MAX;\n    return f;\n}\nstatic void setuint16_tMSB(uint8_t **raw, float ov)\n{\n    int16_t f = int16_t((ov * INT16_MAX) - INT16_MIN);\n    uint16_t r = *(uint16_t*)(&f);\n    *(*raw)++ = (uint8_t)((r >> 8) & 0xFF);\n    *(*raw)++ = (uint8_t)((r >> 0) & 0xFF);\n}\n\n// int8_t\nstatic float getInt8(uint8_t *raw, int c)\n{\n    float f;\n    raw += (c * sizeof(int8_t));\n    f = (float)(int8_t)(*raw) / INT8_MAX;\n    return f;\n}\nstatic void setInt8(uint8_t **raw, float ov)\n{\n    *(*raw)++ = (uint8_t)(ov * INT8_MAX);\n}\n\n// uint8_t\nstatic float getuint8_t(uint8_t *raw, int c)\n{\n    float f;\n    raw += (c * sizeof(int8_t));\n    f = (float)((int)*raw + INT8_MIN) / INT8_MAX;\n    return f;\n}\nstatic void setuint8_t(uint8_t **raw, float ov)\n{\n    *(*raw)++ = (uint8_t)((ov * INT8_MAX) - INT8_MIN);\n}\n\n\ntypedef float (*ReadSampleCB)(uint8_t* raw, int channel);\ntypedef  void (*WriteSampleCB)(uint8_t** raw, float sample);\n\nstatic bool initFormat(ReadSampleCB &readSample,\n                       WriteSampleCB &writeSample,\n                       int &sample_size, uint16_t format)\n{\n    switch(format)\n    {\n    case AUDIO_U8:\n        readSample = getuint8_t;\n        writeSample = setuint8_t;\n        sample_size = sizeof(uint8_t);\n        break;\n\n    case AUDIO_S8:\n        readSample = getInt8;\n        writeSample = setInt8;\n        sample_size = sizeof(int8_t);\n        break;\n\n    case AUDIO_S16LSB:\n        readSample = getInt16LSB;\n        writeSample = setInt16LSB;\n        sample_size = sizeof(int16_t);\n        break;\n\n    case AUDIO_S16MSB:\n        readSample = getInt16MSB;\n        writeSample = setInt16MSB;\n        sample_size = sizeof(int16_t);\n        break;\n\n    case AUDIO_U16LSB:\n        readSample = getuint16_tLSB;\n        writeSample = setuint16_tLSB;\n        sample_size = sizeof(uint16_t);\n        break;\n\n    case AUDIO_U16MSB:\n        readSample = getuint16_tMSB;\n        writeSample = setuint16_tMSB;\n        sample_size = sizeof(uint16_t);\n        break;\n\n    case AUDIO_S32LSB:\n        readSample = getInt32LSB;\n        writeSample = setInt32LSB;\n        sample_size = sizeof(int32_t);\n        break;\n\n    case AUDIO_S32MSB:\n        readSample = getInt32MSB;\n        writeSample = setInt32MSB;\n        sample_size = sizeof(int32_t);\n        break;\n\n    case AUDIO_F32LSB:\n        readSample = getFloatLSBSample;\n        writeSample = setFloatLSBSample;\n        sample_size = sizeof(float);\n        break;\n\n    case AUDIO_F32MSB:\n        readSample = getFloatMSBSample;\n        writeSample = setFloatMSBSample;\n        sample_size = sizeof(float);\n        break;\n\n    default:\n        return false; /* Unsupported format */\n    }\n\n    return true;\n}\n\n\n#endif // FX_COMMON_HPP\n"
  },
  {
    "path": "src/sound/fx/fx_format.h",
    "content": "#ifndef FX_FORMAT_H\n#define FX_FORMAT_H\n\n/* Types are same as at the SDL2 */\n#ifndef AUDIO_U8\n#define AUDIO_U8        0x0008  /**< Unsigned 8-bit samples */\n#define AUDIO_S8        0x8008  /**< Signed 8-bit samples */\n#define AUDIO_U16LSB    0x0010  /**< Unsigned 16-bit samples */\n#define AUDIO_S16LSB    0x8010  /**< Signed 16-bit samples */\n#define AUDIO_U16MSB    0x1010  /**< As above, but big-endian byte order */\n#define AUDIO_S16MSB    0x9010  /**< As above, but big-endian byte order */\n#define AUDIO_U16       AUDIO_U16LSB\n#define AUDIO_S16       AUDIO_S16LSB\n#define AUDIO_S32LSB    0x8020  /**< 32-bit integer samples */\n#define AUDIO_S32MSB    0x9020  /**< As above, but big-endian byte order */\n#define AUDIO_S32       AUDIO_S32LSB\n#define AUDIO_F32LSB    0x8120  /**< 32-bit floating point samples */\n#define AUDIO_F32MSB    0x9120  /**< As above, but big-endian byte order */\n#define AUDIO_F32       AUDIO_F32LSB\n#endif\n\n#endif // FX_FORMAT_H\n"
  },
  {
    "path": "src/sound/fx/reverb.cpp",
    "content": "/*\n * Simple Reverb sound effect\n *\n * Copyright (c) 2022-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include <cstddef>\n#include <vector>\n#include <deque>\n#include <cmath>\n#include <tgmath.h>\n#include \"reverb.h\"\n#include \"fx_common.hpp\"\n\n\n// Code was taken from FreeVerb: https://github.com/sinshu/freeverb (Public Domain)\n\n// Written by Jezar at Dreampoint, June 2000\n// http://www.dreampoint.co.uk\n// This code is public domain\n\nconst int   numcombs        = 8;\nconst int   numallpasses    = 4;\nconst float muted           = 0;\nconst float fixedgain       = 0.015f;\nconst float scalewet        = 3;\nconst float scaledry        = 2;\nconst float scaledamp       = 0.4f;\nconst float scaleroom       = 0.28f;\nconst float offsetroom      = 0.7f;\nconst float initialroom     = 0.5f;\nconst float initialdamp     = 0.5f;\nconst float initialwet      = 1 / scalewet;\nconst float initialdry      = 0;\nconst float initialwidth    = 1;\nconst float initialmode     = 0;\nconst float freezemode      = 0.5f;\nconst int   stereospread    = 23;\n\n// These values assume 44.1KHz sample rate\n// they will probably be OK for 48KHz sample rate\n// but would need scaling for 96KHz (or other) sample rates.\n// The values were obtained by listening tests.\nconst int combtuningL1      = 1116;\nconst int combtuningR1      = 1116 + stereospread;\nconst int combtuningL2      = 1188;\nconst int combtuningR2      = 1188 + stereospread;\nconst int combtuningL3      = 1277;\nconst int combtuningR3      = 1277 + stereospread;\nconst int combtuningL4      = 1356;\nconst int combtuningR4      = 1356 + stereospread;\nconst int combtuningL5      = 1422;\nconst int combtuningR5      = 1422 + stereospread;\nconst int combtuningL6      = 1491;\nconst int combtuningR6      = 1491 + stereospread;\nconst int combtuningL7      = 1557;\nconst int combtuningR7      = 1557 + stereospread;\nconst int combtuningL8      = 1617;\nconst int combtuningR8      = 1617 + stereospread;\nconst int allpasstuningL1   = 556;\nconst int allpasstuningR1   = 556 + stereospread;\nconst int allpasstuningL2   = 441;\nconst int allpasstuningR2   = 441 + stereospread;\nconst int allpasstuningL3   = 341;\nconst int allpasstuningR3   = 341 + stereospread;\nconst int allpasstuningL4   = 225;\nconst int allpasstuningR4   = 225 + stereospread;\n\n\nstatic inline void undenormalise(float &sample)\n{\n    uint32_t i = *reinterpret_cast<uint32_t*>(&sample);\n    if((i & 0x7f800000) == 0)\n        sample = 0.0f;\n}\n\n\nclass comb\n{\npublic:\n    comb()\n    {\n        filterstore = 0;\n        bufidx = 0;\n    }\n\n    void setbuffer(float* buf, int size)\n    {\n        buffer = buf;\n        bufsize = size;\n    }\n\n    // Big to inline - but crucial for speed\n    inline float process(float input)\n    {\n        float output;\n\n        output = buffer[bufidx];\n        undenormalise(output);\n\n        filterstore = (output * damp2) + (filterstore * damp1);\n        undenormalise(filterstore);\n\n        buffer[bufidx] = input + (filterstore * feedback);\n\n        if(++bufidx >= bufsize) bufidx = 0;\n\n        return output;\n    }\n\n    void mute()\n    {\n        for(int i = 0; i < bufsize; i++)\n            buffer[i] = 0;\n    }\n\n    void setdamp(float val)\n    {\n        damp1 = val;\n        damp2 = 1 - val;\n    }\n\n    float getdamp()\n    {\n        return damp1;\n    }\n\n    void setfeedback(float val)\n    {\n        feedback = val;\n    }\n\n    float getfeedback()\n    {\n        return feedback;\n    }\n\nprivate:\n    float   feedback = 0.f;\n    float   filterstore = 0.f;\n    float   damp1 = 0.f;\n    float   damp2 = 0.f;\n    float*  buffer = nullptr;\n    int     bufsize = 0;\n    int     bufidx = 0;\n};\n\n\nclass allpass\n{\npublic:\n    allpass()\n    {\n        bufidx = 0;\n    }\n\n    void setbuffer(float* buf, int size)\n    {\n        buffer = buf;\n        bufsize = size;\n    }\n\n    // Big to inline - but crucial for speed\n    inline float process(float input)\n    {\n        float output;\n        float bufout;\n\n        bufout = buffer[bufidx];\n        undenormalise(bufout);\n\n        output = -input + bufout;\n        buffer[bufidx] = input + (bufout * feedback);\n\n        if(++bufidx >= bufsize)\n            bufidx = 0;\n\n        return output;\n    }\n\n    void mute()\n    {\n        for(int i = 0; i < bufsize; i++)\n            buffer[i] = 0;\n    }\n\n    void setfeedback(float val)\n    {\n        feedback = val;\n    }\n\n    float getfeedback()\n    {\n        return feedback;\n    }\n\n    // private:\n    float   feedback = 0.f;\n    float*  buffer = nullptr;\n    int     bufsize = 0;\n    int     bufidx = 0;\n};\n\n\nclass revmodel\n{\n    double rateScale = 1.0;\n\npublic:\n    void setSampleRate(int rate)\n    {\n        rateScale = rate / 44100.0;\n        // Prepare buffers\n        bufcombL1.resize(static_cast<size_t>(combtuningL1 * rateScale));\n        bufcombR1.resize(static_cast<size_t>(combtuningR1 * rateScale));\n        bufcombL2.resize(static_cast<size_t>(combtuningL2 * rateScale));\n        bufcombR2.resize(static_cast<size_t>(combtuningR2 * rateScale));\n        bufcombL3.resize(static_cast<size_t>(combtuningL3 * rateScale));\n        bufcombR3.resize(static_cast<size_t>(combtuningR3 * rateScale));\n        bufcombL4.resize(static_cast<size_t>(combtuningL4 * rateScale));\n        bufcombR4.resize(static_cast<size_t>(combtuningR4 * rateScale));\n        bufcombL5.resize(static_cast<size_t>(combtuningL5 * rateScale));\n        bufcombR5.resize(static_cast<size_t>(combtuningR5 * rateScale));\n        bufcombL6.resize(static_cast<size_t>(combtuningL6 * rateScale));\n        bufcombR6.resize(static_cast<size_t>(combtuningR6 * rateScale));\n        bufcombL7.resize(static_cast<size_t>(combtuningL7 * rateScale));\n        bufcombR7.resize(static_cast<size_t>(combtuningR7 * rateScale));\n        bufcombL8.resize(static_cast<size_t>(combtuningL8 * rateScale));\n        bufcombR8.resize(static_cast<size_t>(combtuningR8 * rateScale));\n        bufallpassL1.resize(static_cast<size_t>(allpasstuningL1 * rateScale));\n        bufallpassR1.resize(static_cast<size_t>(allpasstuningR1 * rateScale));\n        bufallpassL2.resize(static_cast<size_t>(allpasstuningL2 * rateScale));\n        bufallpassR2.resize(static_cast<size_t>(allpasstuningR2 * rateScale));\n        bufallpassL3.resize(static_cast<size_t>(allpasstuningL3 * rateScale));\n        bufallpassR3.resize(static_cast<size_t>(allpasstuningR3 * rateScale));\n        bufallpassL4.resize(static_cast<size_t>(allpasstuningL4 * rateScale));\n        bufallpassR4.resize(static_cast<size_t>(allpasstuningR4 * rateScale));\n\n        // Tie the components to their buffers\n        combL[0].setbuffer(bufcombL1.data(), static_cast<int>(bufcombL1.size()));\n        combR[0].setbuffer(bufcombR1.data(), static_cast<int>(bufcombR1.size()));\n        combL[1].setbuffer(bufcombL2.data(), static_cast<int>(bufcombL2.size()));\n        combR[1].setbuffer(bufcombR2.data(), static_cast<int>(bufcombR2.size()));\n        combL[2].setbuffer(bufcombL3.data(), static_cast<int>(bufcombL3.size()));\n        combR[2].setbuffer(bufcombR3.data(), static_cast<int>(bufcombR3.size()));\n        combL[3].setbuffer(bufcombL4.data(), static_cast<int>(bufcombL4.size()));\n        combR[3].setbuffer(bufcombR4.data(), static_cast<int>(bufcombR4.size()));\n        combL[4].setbuffer(bufcombL5.data(), static_cast<int>(bufcombL5.size()));\n        combR[4].setbuffer(bufcombR5.data(), static_cast<int>(bufcombR5.size()));\n        combL[5].setbuffer(bufcombL6.data(), static_cast<int>(bufcombL6.size()));\n        combR[5].setbuffer(bufcombR6.data(), static_cast<int>(bufcombR6.size()));\n        combL[6].setbuffer(bufcombL7.data(), static_cast<int>(bufcombL7.size()));\n        combR[6].setbuffer(bufcombR7.data(), static_cast<int>(bufcombR7.size()));\n        combL[7].setbuffer(bufcombL8.data(), static_cast<int>(bufcombL8.size()));\n        combR[7].setbuffer(bufcombR8.data(), static_cast<int>(bufcombR8.size()));\n        allpassL[0].setbuffer(bufallpassL1.data(), static_cast<int>(bufallpassL1.size()));\n        allpassR[0].setbuffer(bufallpassR1.data(), static_cast<int>(bufallpassR1.size()));\n        allpassL[1].setbuffer(bufallpassL2.data(), static_cast<int>(bufallpassL2.size()));\n        allpassR[1].setbuffer(bufallpassR2.data(), static_cast<int>(bufallpassR2.size()));\n        allpassL[2].setbuffer(bufallpassL3.data(), static_cast<int>(bufallpassL3.size()));\n        allpassR[2].setbuffer(bufallpassR3.data(), static_cast<int>(bufallpassR3.size()));\n        allpassL[3].setbuffer(bufallpassL4.data(), static_cast<int>(bufallpassL4.size()));\n        allpassR[3].setbuffer(bufallpassR4.data(), static_cast<int>(bufallpassR4.size()));\n    }\n\n    revmodel()\n    {\n        // Set default values\n        allpassL[0].setfeedback(0.5f);\n        allpassR[0].setfeedback(0.5f);\n        allpassL[1].setfeedback(0.5f);\n        allpassR[1].setfeedback(0.5f);\n        allpassL[2].setfeedback(0.5f);\n        allpassR[2].setfeedback(0.5f);\n        allpassL[3].setfeedback(0.5f);\n        allpassR[3].setfeedback(0.5f);\n        setwet(initialwet);\n        setroomsize(initialroom);\n        setdry(initialdry);\n        setdamp(initialdamp);\n        setwidth(initialwidth);\n        setmode(initialmode);\n\n        // Buffer will be full of rubbish - so we MUST mute them\n        mute();\n    }\n\n    void mute()\n    {\n        if(getmode() >= freezemode)\n            return;\n\n        for(int i = 0; i < numcombs; i++)\n        {\n            combL[i].mute();\n            combR[i].mute();\n        }\n\n        for(int i = 0; i < numallpasses; i++)\n        {\n            allpassL[i].mute();\n            allpassR[i].mute();\n        }\n    }\n\n    void processmix(float* inputL, float* inputR, float* outputL, float* outputR, long numsamples, int skip)\n    {\n        float outL, outR, input;\n\n        while(numsamples-- > 0)\n        {\n            outL = outR = 0;\n            input = (*inputL + *inputR) * gain;\n\n            // Accumulate comb filters in parallel\n            for(int i = 0; i < numcombs; i++)\n            {\n                outL += combL[i].process(input);\n                outR += combR[i].process(input);\n            }\n\n            // Feed through allpasses in series\n            for(int i = 0; i < numallpasses; i++)\n            {\n                outL = allpassL[i].process(outL);\n                outR = allpassR[i].process(outR);\n            }\n\n            // Calculate output MIXING with anything already there\n            *outputL += outL * wet1 + outR * wet2 + *inputL * dry;\n            *outputR += outR * wet1 + outL * wet2 + *inputR * dry;\n\n            // Increment sample pointers, allowing for interleave (if any)\n            inputL += skip;\n            inputR += skip;\n            outputL += skip;\n            outputR += skip;\n        }\n    }\n\n    void processreplace(float* inputL, float* inputR, float* outputL, float* outputR, long numsamples, int skip)\n    {\n        float outL, outR, input;\n\n        while(numsamples-- > 0)\n        {\n            outL = outR = 0;\n            input = (*inputL + *inputR) * gain;\n\n            // Accumulate comb filters in parallel\n            for(int i = 0; i < numcombs; i++)\n            {\n                outL += combL[i].process(input);\n                outR += combR[i].process(input);\n            }\n\n            // Feed through allpasses in series\n            for(int i = 0; i < numallpasses; i++)\n            {\n                outL = allpassL[i].process(outL);\n                outR = allpassR[i].process(outR);\n            }\n\n            // Calculate output REPLACING anything already there\n            *outputL = outL * wet1 + outR * wet2 + *inputL * dry;\n            *outputR = outR * wet1 + outL * wet2 + *inputR * dry;\n\n            // Increment sample pointers, allowing for interleave (if any)\n            inputL += skip;\n            inputR += skip;\n            outputL += skip;\n            outputR += skip;\n        }\n    }\n\n\n    // The following get/set functions are not inlined, because\n    // speed is never an issue when calling them, and also\n    // because as you develop the reverb model, you may\n    // wish to take dynamic action when they are called.\n\n    void setroomsize(float value)\n    {\n        roomsize = (value * scaleroom) + offsetroom;\n        update();\n    }\n\n    float getroomsize()\n    {\n        return (roomsize - offsetroom) / scaleroom;\n    }\n\n    void setdamp(float value)\n    {\n        damp = value * scaledamp;\n        update();\n    }\n\n    float getdamp()\n    {\n        return damp / scaledamp;\n    }\n\n    void setwet(float value)\n    {\n        wet = value * scalewet;\n        update();\n    }\n\n    float getwet()\n    {\n        return wet / scalewet;\n    }\n\n    void setdry(float value)\n    {\n        dry = value * scaledry;\n    }\n\n    float getdry()\n    {\n        return dry / scaledry;\n    }\n\n    void setwidth(float value)\n    {\n        width = value;\n        update();\n    }\n\n    float getwidth()\n    {\n        return width;\n    }\n\n    void setmode(float value)\n    {\n        mode = value;\n        update();\n    }\n\n    float getmode()\n    {\n        if(mode >= freezemode)\n            return 1;\n        else\n            return 0;\n    }\n\nprivate:\n    void update()\n    {\n        // Recalculate internal values after parameter change\n\n        int i;\n\n        wet1 = wet * (width / 2 + 0.5f);\n        wet2 = wet * ((1 - width) / 2);\n\n        if(mode >= freezemode)\n        {\n            roomsize1 = 1;\n            damp1 = 0;\n            gain = muted;\n        }\n        else\n        {\n            roomsize1 = roomsize;\n            damp1 = damp;\n            gain = fixedgain;\n        }\n\n        for(i = 0; i < numcombs; i++)\n        {\n            combL[i].setfeedback(roomsize1);\n            combR[i].setfeedback(roomsize1);\n        }\n\n        for(i = 0; i < numcombs; i++)\n        {\n            combL[i].setdamp(damp1);\n            combR[i].setdamp(damp1);\n        }\n    }\n\nprivate:\n    float   gain = 0.0f;\n    float   roomsize = 0.0f, roomsize1 = 0.0f;\n    float   damp = 0.0f, damp1 = 0.0f;\n    float   wet = 0.0f, wet1 = 0.0f, wet2 = 0.0f;\n    float   dry = 0.0f;\n    float   width = 0.0f;\n    float   mode = 0.0f;\n\n    // The following are all declared inline\n    // to remove the need for dynamic allocation\n    // with its subsequent error-checking messiness\n\n    // Comb filters\n    comb    combL[numcombs];\n    comb    combR[numcombs];\n\n    // Allpass filters\n    allpass allpassL[numallpasses];\n    allpass allpassR[numallpasses];\n\n    // Buffers for the combs\n    std::vector<float>  bufcombL1;\n    std::vector<float>  bufcombR1;\n    std::vector<float>  bufcombL2;\n    std::vector<float>  bufcombR2;\n    std::vector<float>  bufcombL3;\n    std::vector<float>  bufcombR3;\n    std::vector<float>  bufcombL4;\n    std::vector<float>  bufcombR4;\n    std::vector<float>  bufcombL5;\n    std::vector<float>  bufcombR5;\n    std::vector<float>  bufcombL6;\n    std::vector<float>  bufcombR6;\n    std::vector<float>  bufcombL7;\n    std::vector<float>  bufcombR7;\n    std::vector<float>  bufcombL8;\n    std::vector<float>  bufcombR8;\n\n    // Buffers for the allpasses\n    std::vector<float> bufallpassL1;\n    std::vector<float> bufallpassR1;\n    std::vector<float> bufallpassL2;\n    std::vector<float> bufallpassR2;\n    std::vector<float> bufallpassL3;\n    std::vector<float> bufallpassR3;\n    std::vector<float> bufallpassL4;\n    std::vector<float> bufallpassR4;\n};\n\n\ntypedef struct FxReverb\n{\n    int         channels = 0;\n    int         sampleRate = 0;\n    uint16_t    format = AUDIO_F32LSB;\n    bool        isValid = false;\n    ReverbSetup m_setup;\n\n    revmodel    rev[MAX_CHANNELS / 2];\n\n    std::vector<std::vector<float>> inBuffer;\n    std::vector<std::vector<float>> outBuffer;\n    int                             lastBufferSize = 0;\n\n    ReadSampleCB    readSample = nullptr;\n    WriteSampleCB   writeSample = nullptr;\n    int             sample_size = 2;\n\n    int init(int i_rate, uint16_t i_format, int i_channels)\n    {\n        isValid = false;\n\n        if(channels > MAX_CHANNELS)\n            return -1;\n\n        format = i_format;\n        sampleRate = i_rate;\n        channels = i_channels;\n\n        if(!initFormat(readSample, writeSample, sample_size, format))\n            return -1;\n\n        for(int i = 0; i < channels; i += 2)\n        {\n            auto &c = rev[i / 2];\n            c.setSampleRate(sampleRate);\n        }\n\n        setSettings(m_setup);\n\n        inBuffer.clear();\n        outBuffer.clear();\n        inBuffer.resize(channels + (channels % 2));\n        outBuffer.resize(channels + (channels % 2));\n        lastBufferSize = 0;\n\n        isValid = true;\n        return 0;\n    }\n\n    void updateSetup(const ReverbSetup& setup)\n    {\n        m_setup = setup;\n    }\n\n    void getSetup(ReverbSetup& setup)\n    {\n        setup = m_setup;\n    }\n\n    void setSettings(const ReverbSetup& setup)\n    {\n        for(int i = 0; i < channels; i += 2)\n        {\n            auto &c = rev[i / 2];\n            c.setroomsize(setup.roomSize);\n            c.setdamp(setup.damping);\n            c.setmode(setup.mode);\n            c.setdry(setup.dryLevel);\n            c.setwet(setup.wetLevel);\n            c.setwidth(setup.width);\n        }\n    }\n\n    void setMode(float val)\n    {\n        m_setup.mode = val;\n        for(int i = 0; i < channels; i += 2)\n        {\n            auto &c = rev[i / 2];\n            c.setmode(val);\n        }\n    }\n\n    void setRoomSize(float val)\n    {\n        m_setup.roomSize = val;\n        for(int i = 0; i < channels; i += 2)\n        {\n            auto &c = rev[i / 2];\n            c.setroomsize(val);\n        }\n    }\n\n    void setDamping(float val)\n    {\n        m_setup.damping = val;\n        for(int i = 0; i < channels; i += 2)\n        {\n            auto &c = rev[i / 2];\n            c.setdamp(val);\n        }\n    }\n\n    void setWetLevel(float val)\n    {\n        m_setup.wetLevel = val;\n        for(int i = 0; i < channels; i += 2)\n        {\n            auto &c = rev[i / 2];\n            c.setwet(val);\n        }\n    }\n\n    void setDryLevel(float val)\n    {\n        m_setup.dryLevel = val;\n        for(int i = 0; i < channels; i += 2)\n        {\n            auto &c = rev[i / 2];\n            c.setdry(val);\n        }\n    }\n\n    void setWidth(float val)\n    {\n        m_setup.width = val;\n        for(int i = 0; i < channels; i += 2)\n        {\n            auto &c = rev[i / 2];\n            c.setwidth(val);\n        }\n    }\n\n    void close()\n    {\n        isValid = false;\n    }\n\n    void process(uint8_t* stream, int len)\n    {\n        if(!isValid)\n            return; // Do nothing\n\n        int frames = len / (sample_size * channels);\n        uint8_t* in_stream = stream;\n        uint8_t* out_stream = stream;\n\n        for(int i = 0; i < channels; i += 2)\n        {\n            if(inBuffer[i].size() < size_t(frames))\n                inBuffer[i].resize(frames);\n            if(inBuffer[i + 1].size() < size_t(frames))\n                inBuffer[i + 1].resize(frames);\n            if(outBuffer[i].size() < size_t(frames))\n                outBuffer[i].resize(frames);\n            if(outBuffer[i + 1].size() < size_t(frames))\n                outBuffer[i + 1].resize(frames);\n        }\n\n        for(int i = 0; i < frames; ++i)\n        {\n            for(int c = 0; c < channels; ++c)\n            {\n                inBuffer[c][i] = readSample(in_stream, c);\n                if(channels % 2 == 1 && c == channels - 1) // Mono to Stereo\n                    inBuffer[c + 1][i] = inBuffer[c][i];\n            }\n\n            in_stream += sample_size * channels;\n        }\n\n        for(int i = 0; i < channels; i += 2)\n        {\n            auto &c = rev[i / 2];\n            c.processreplace(inBuffer[i].data(), inBuffer[i + 1].data(),\n                             outBuffer[i].data(), outBuffer[i + 1].data(), frames, 1);\n        }\n\n        for(int p = 0; p < frames; ++p)\n        {\n            for(int w = 0; w < channels; ++w)\n            {\n                if(channels % 2 == 1 && w == channels - 1) // Stereo to Mono\n                    outBuffer[w][p] = (outBuffer[w][p] + outBuffer[w + 1][p]) / 2.0f;\n\n                writeSample(&out_stream, outBuffer[w][p]);\n            }\n        }\n    }\n} FxReverb;\n\n\nFxReverb* reverbEffectInit(int rate, uint16_t format, int channels)\n{\n    FxReverb* out = new FxReverb();\n    out->init(rate, format, channels);\n    return out;\n}\n\nvoid reverbEffectFree(FxReverb* context)\n{\n    if(context)\n    {\n        context->close();\n        delete context;\n    }\n}\n\nvoid reverbEffect(int, void* stream, int len, void* context)\n{\n    FxReverb* out = reinterpret_cast<FxReverb*>(context);\n\n    if(!out)\n        return; // Effect doesn't working\n\n    out->process((uint8_t*)stream, len);\n}\n\nvoid reverbUpdateSetup(FxReverb* context, const ReverbSetup& setup)\n{\n    if(context)\n    {\n        context->setSettings(setup);\n        context->updateSetup(setup);\n    }\n}\n\nvoid reverbGetSetup(FxReverb *context, ReverbSetup &setup)\n{\n    if(context)\n        context->getSetup(setup);\n}\n\nvoid reverbUpdateMode(FxReverb *context, float mode)\n{\n    if(context)\n        context->setMode(mode);\n}\n\nvoid reverbUpdateRoomSize(FxReverb *context, float roomSize)\n{\n    if(context)\n        context->setRoomSize(roomSize);\n}\n\nvoid reverbUpdateDamping(FxReverb *context, float damping)\n{\n    if(context)\n        context->setDamping(damping);\n}\n\nvoid reverbUpdateWetLevel(FxReverb *context, float wet)\n{\n    if(context)\n        context->setWetLevel(wet);\n}\n\nvoid reverbUpdateDryLevel(FxReverb *context, float dry)\n{\n    if(context)\n        context->setDryLevel(dry);\n}\n\nvoid reverbUpdateWidth(FxReverb *context, float width)\n{\n    if(context)\n        context->setWidth(width);\n}\n"
  },
  {
    "path": "src/sound/fx/reverb.h",
    "content": "/*\n * Simple Reverb sound effect\n *\n * Copyright (c) 2022-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef REVERB_H\n#define REVERB_H\n\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"fx_format.h\"\n\ntypedef struct FxReverb FxReverb;\n\ntypedef struct ReverbSetup\n{\n    float mode         = 0.0f; // Normal (0) or Freeze (>0.5)\n    float roomSize     = 0.7f;\n    float damping      = 0.5f; // 0.0...1.0\n    float wetLevel     = 0.2f;\n    float dryLevel     = 0.4f;\n    float width        = 1.0f; // 0.0...1.0\n} ReverbSetup;\n\nextern FxReverb *reverbEffectInit(int rate, uint16_t format, int channels);\nextern void reverbEffectFree(FxReverb *context);\n\nextern void reverbEffect(int chan, void *stream, int len, void *context);\n\n// Update all setup at once\nextern void reverbUpdateSetup(FxReverb *context, const ReverbSetup &setup);\nextern void reverbGetSetup(FxReverb *context, ReverbSetup &setup);\n\n// Update every single setting\nextern void reverbUpdateMode(FxReverb *context, float mode);\nextern void reverbUpdateRoomSize(FxReverb *context, float roomSize);\nextern void reverbUpdateDamping(FxReverb *context, float damping);\nextern void reverbUpdateWetLevel(FxReverb *context, float wet);\nextern void reverbUpdateDryLevel(FxReverb *context, float dry);\nextern void reverbUpdateWidth(FxReverb *context, float width);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // REVERB_H\n"
  },
  {
    "path": "src/sound/fx/spc_echo.cpp",
    "content": "/*\n * SPC Echo sound effect\n *\n * Copyright (c) 2022-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#include <tgmath.h>\n#include <string.h>\n#include \"spc_echo.h\"\n#include \"fx_common.hpp\"\n\n\n#define CLAMP16F( io )\\\n    {\\\n        if(io < -1.f)\\\n            io = -1.f;\\\n        else if(io > 1.f)\\\n            io = 1.f;\\\n    }\\\n\n#define ECHO_HIST_SIZE  8\n#define SDSP_RATE       32000\n#define MAX_CHANNELS    10\n#define ECHO_BUFFER_SIZE (32 * 1024 * MAX_CHANNELS)\n\n//// Global registers\n//enum\n//{\n//    r_mvoll = 0x0C, r_mvolr = 0x1C,\n//    r_evoll = 0x2C, r_evolr = 0x3C,\n//    r_kon   = 0x4C, r_koff  = 0x5C,\n//    r_flg   = 0x6C, r_endx  = 0x7C,\n//    r_efb   = 0x0D, r_pmon  = 0x2D,\n//    r_non   = 0x3D, r_eon   = 0x4D,\n//    r_dir   = 0x5D, r_esa   = 0x6D,\n//    r_edl   = 0x7D,\n//    r_fir   = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F\n//};\n\n//static uint8_t_t const initial_regs[register_count] =\n//{\n///*      0      1     2     3    4     5     6     7     8      9     A    B     C      D     E     F  */\n///*00*/ 0x45, 0x8B, 0x5A, 0x9A, 0xE4, 0x82, 0x1B, 0x78, 0x00, 0x00, 0xAA, 0x96, 0x89, 0x0E, 0xE0, 0x80, /*00*/\n///*10*/ 0x2A, 0x49, 0x3D, 0xBA, 0x14, 0xA0, 0xAC, 0xC5, 0x00, 0x00, 0x51, 0xBB, 0x9C, 0x4E, 0x7B, 0xFF, /*10*/\n///*20*/ 0xF4, 0xFD, 0x57, 0x32, 0x37, 0xD9, 0x42, 0x22, 0x00, 0x00, 0x5B, 0x3C, 0x9F, 0x1B, 0x87, 0x9A, /*20*/\n///*30*/ 0x6F, 0x27, 0xAF, 0x7B, 0xE5, 0x68, 0x0A, 0xD9, 0x00, 0x00, 0x9A, 0xC5, 0x9C, 0x4E, 0x7B, 0xFF, /*30*/\n///*40*/ 0xEA, 0x21, 0x78, 0x4F, 0xDD, 0xED, 0x24, 0x14, 0x00, 0x00, 0x77, 0xB1, 0xD1, 0x36, 0xC1, 0x67, /*40*/\n///*50*/ 0x52, 0x57, 0x46, 0x3D, 0x59, 0xF4, 0x87, 0xA4, 0x00, 0x00, 0x7E, 0x44, 0x9C, 0x4E, 0x7B, 0xFF, /*50*/\n///*60*/ 0x75, 0xF5, 0x06, 0x97, 0x10, 0xC3, 0x24, 0xBB, 0x00, 0x00, 0x7B, 0x7A, 0xE0, 0x60, 0x12, 0x0F, /*60*/\n///*70*/ 0xF7, 0x74, 0x1C, 0xE5, 0x39, 0x3D, 0x73, 0xC1, 0x00, 0x00, 0x7A, 0xB3, 0xFF, 0x4E, 0x7B, 0xFF  /*70*/\n///*      0      1     2     3    4     5     6     7     8      9     A    B     C      D     E     F  */\n//};\n\n\ntypedef struct SpcEcho\n{\n    int is_valid = 0;\n    float echo_ram[ECHO_BUFFER_SIZE];\n\n    // Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)\n    float echo_hist[ECHO_HIST_SIZE * 2 * MAX_CHANNELS][MAX_CHANNELS];\n    float (*echo_hist_pos)[MAX_CHANNELS] = echo_hist; // &echo_hist [0 to 7]\n\n    //! offset from ESA in echo buffer\n    int echo_offset = 0;\n    //! number of bytes that echo_offset will stop at\n    int echo_length = 0;\n\n    double  rate_factor = 1.0;\n    int     rate = SDSP_RATE;\n    int     channels = 2;\n    uint16_t format = AUDIO_F32;\n\n    //! Flags\n    uint8_t reg_flg = 0;\n    //! $4d rw EON - Echo enable\n    uint8_t reg_eon = 0;\n    //! $7d rw EDL - Echo delay (ring buffer size)\n    uint8_t reg_edl = 0;\n    //! $0d rw EFB - Echo feedback volume\n    int8_t reg_efb = 0;\n\n    int8_t reg_mvoll = 0;\n    int8_t reg_mvolr = 0;\n    int8_t reg_evoll = 0;\n    int8_t reg_evolr = 0;\n\n    //! FIR Defaults: 80 FF 9A FF 67 FF 0F FF\n    const uint8_t reg_fir_initial[8] = {0x80, 0xFF, 0x9A, 0xFF, 0x67, 0xFF, 0x0F, 0xFF};\n    //! $xf rw FFCx - Echo FIR Filter Coefficient (FFC) X\n    int8_t reg_fir[8] = {0, 0, 0, 0, 0, 0, 0, 0};\n    int8_t reg_fir_resampled[8];\n\n    void recomputeFirResampled()\n    {\n        const double y_factor1 = 1.0;\n        const double y_factor2 = rate_factor;\n        for(size_t i = 0; i < 8; i++)\n        {\n            double newFactor = y_factor2 + ((y_factor1 - y_factor2) / (0.0 - 7.0)) * (i - 7.0);\n            reg_fir_resampled[i] = (int8_t)(reg_fir[i] * (1.0 + ((newFactor - 1.0) / 100.0)));\n        }\n    }\n\n    void setDefaultFir()\n    {\n        memcpy(reg_fir, reg_fir_initial, 8);\n        recomputeFirResampled();\n    }\n\n    void setDefaultRegs()\n    {\n        reg_flg = 0;\n\n        reg_mvoll = (int8_t)0x89;\n        reg_mvolr = (int8_t)0x9C;\n        reg_evoll = (int8_t)0x9F;\n        reg_evolr = (int8_t)0x9C;\n\n        setDefaultFir();\n\n        // $0d rw EFB - Echo feedback volume\n        reg_efb = (int8_t)0x0E;\n        // $4d rw EON - Echo enable\n        reg_eon = 1;\n        // $7d rw EDL - Echo delay (ring buffer size)\n        reg_edl = 3;\n    }\n\n    ReadSampleCB    readSample = nullptr;\n    WriteSampleCB   writeSample = nullptr;\n    int             sample_size = 2;\n\n    int init(int i_rate, uint16_t i_format, int i_channels)\n    {\n        is_valid = 0;\n        rate = i_rate;\n        format = i_format;\n        channels = i_channels;\n        rate_factor = (double)i_rate / SDSP_RATE;\n\n        if(rate_factor > 50.0)\n            return -1; /* Too big scale factor */\n\n        if(i_rate < 4000)\n            return -1; /* Too small sample rate */\n\n        memset(echo_ram, 0, sizeof(echo_ram));\n        memset(echo_hist, 0, sizeof(echo_hist));\n        memset(reg_fir_resampled, 0, sizeof(reg_fir_resampled));\n\n        if(!initFormat(readSample, writeSample, sample_size, format))\n            return -1;\n\n        setDefaultRegs();\n\n        is_valid = 1;\n        return 0;\n    }\n\n    void close()\n    {}\n\n    void process(uint8_t *stream, int len)\n    {\n        int frames = len / (sample_size * channels);\n\n        float main_out[MAX_CHANNELS];\n        float echo_out[MAX_CHANNELS];\n        float echo_in[MAX_CHANNELS];\n\n        int c;\n        float ov;\n\n        int f, e_offset;\n        float v;\n\n        float mvoll[2] = {(float)reg_mvoll, (float)reg_mvolr};\n        float evoll[2] = {(float)reg_evoll, (float)reg_evolr};\n\n        float (*echohist_pos)[MAX_CHANNELS];\n        float *echo_ptr;\n\n        memset(main_out, 0, sizeof(main_out));\n        memset(echo_out, 0, sizeof(echo_out));\n        memset(echo_in, 0, sizeof(echo_in));\n\n        if(!is_valid)\n            return;\n\n        do\n        {\n            for(c = 0; c < channels; ++c)\n                main_out[c] = readSample(stream, c) * 128;\n\n            if(reg_eon & 1)\n            {\n                for(c = 0; c < channels; c++)\n                    echo_out[c] = main_out[c];\n            }\n\n            e_offset = echo_offset;\n            echo_ptr = echo_ram + echo_offset;\n\n            if(!echo_offset)\n                echo_length = (int)round((((reg_edl & 0x0F) * 0x400 * channels) / 2.0) * rate_factor);\n            e_offset += channels;\n            if(e_offset >= echo_length)\n                e_offset = 0;\n            echo_offset = e_offset;\n\n            /* FIR */\n            for(c = 0; c < channels; c++)\n                echo_in[c] = echo_ptr[c];\n\n            echohist_pos = echo_hist_pos;\n            if(++echohist_pos >= &echo_hist[ECHO_HIST_SIZE])\n                echohist_pos = echo_hist;\n            echo_hist_pos = echohist_pos;\n\n            /* --------------- FIR filter-------------- */\n            for(c = 0; c < channels; c++)\n                echohist_pos[0][c] = echohist_pos[8][c] = echo_in[c];\n\n            for(c = 0; c < channels; ++c)\n                echo_in[c] *= reg_fir_resampled[7];\n\n            for(f = 0; f <= 6; ++f)\n            {\n                for(c = 0; c < channels; ++c)\n                    echo_in[c] += echo_hist_pos[f + 1][c] * reg_fir_resampled[f];\n            }\n            /* ---------------------------------------- */\n\n            /* Echo out */\n            if(!(reg_flg & 0x20))\n            {\n                for(c = 0; c < channels; c++)\n                {\n                    //v = (echo_out[c] >> 7) + ((echo_in[c] * reg_efb) >> 14);\n                    v = (echo_out[c] / 128) + ((echo_in[c] * reg_efb) / 16384.f);\n                    CLAMP16F(v);\n                    echo_ptr[c] = v;\n                }\n            }\n\n            /* Sound out */\n            for(c = 0; c < channels; c++)\n            {\n                ov = (main_out[c] * mvoll[c % 2] + echo_in[c] * evoll[c % 2]) / 16384;\n                CLAMP16F(ov);\n                if((reg_flg & 0x40))\n                    ov = 0;\n                writeSample(&stream, ov);\n            }\n        }\n        while(--frames);\n    }\n} SpcEcho;\n\n\nSpcEcho *echoEffectInit(int rate, uint16_t format, int channels)\n{\n    SpcEcho *out = new SpcEcho();\n    out->init(rate, format, channels);\n    return out;\n}\n\nvoid echoEffectFree(SpcEcho *context)\n{\n    if(context)\n    {\n        context->close();\n        delete context;\n    }\n}\n\nvoid spcEchoEffect(int, void *stream, int len, void *context)\n{\n    SpcEcho *out = reinterpret_cast<SpcEcho *>(context);\n    if(!out)\n        return; // Effect doesn't working\n    out->process((uint8_t*)stream, len);\n}\n\nvoid echoEffectSetReg(SpcEcho *out, EchoSetup key, int val)\n{\n    if(!out)\n        return;\n\n    switch(key)\n    {\n    case ECHO_EON:\n        out->reg_eon = (uint8_t)val;\n        break;\n    case ECHO_EDL:\n        out->reg_edl = (uint8_t)val;\n        break;\n    case ECHO_EFB:\n        out->reg_efb = (int8_t)val;\n        break;\n    case ECHO_MVOLL:\n        out->reg_mvoll = (int8_t)val;\n        break;\n    case ECHO_MVOLR:\n        out->reg_mvolr = (int8_t)val;\n        break;\n    case ECHO_EVOLL:\n        out->reg_evoll = (int8_t)val;\n        break;\n    case ECHO_EVOLR:\n        out->reg_evolr = (int8_t)val;\n        break;\n\n    case ECHO_FIR0:\n        out->reg_fir[0]= (int8_t)val;\n        out->recomputeFirResampled();\n        break;\n    case ECHO_FIR1:\n        out->reg_fir[1]= (int8_t)val;\n        out->recomputeFirResampled();\n        break;\n    case ECHO_FIR2:\n        out->reg_fir[2]= (int8_t)val;\n        out->recomputeFirResampled();\n        break;\n    case ECHO_FIR3:\n        out->reg_fir[3]= (int8_t)val;\n        out->recomputeFirResampled();\n        break;\n    case ECHO_FIR4:\n        out->reg_fir[4]= (int8_t)val;\n        out->recomputeFirResampled();\n        break;\n    case ECHO_FIR5:\n        out->reg_fir[5]= (int8_t)val;\n        out->recomputeFirResampled();\n        break;\n    case ECHO_FIR6:\n        out->reg_fir[6]= (int8_t)val;\n        out->recomputeFirResampled();\n        break;\n    case ECHO_FIR7:\n        out->reg_fir[7]= (int8_t)val;\n        out->recomputeFirResampled();\n        break;\n    }\n}\n\nint echoEffectGetReg(SpcEcho *out, EchoSetup key)\n{\n    if(!out)\n        return 0;\n\n    switch(key)\n    {\n    case ECHO_EON:\n        return out->reg_eon;\n    case ECHO_EDL:\n        return out->reg_edl;\n    case ECHO_EFB:\n        return out->reg_efb;\n    case ECHO_MVOLL:\n        return out->reg_mvoll;\n    case ECHO_MVOLR:\n        return out->reg_mvolr;\n    case ECHO_EVOLL:\n        return out->reg_evoll;\n    case ECHO_EVOLR:\n        return out->reg_evolr;\n\n    case ECHO_FIR0:\n        return out->reg_fir[0];\n    case ECHO_FIR1:\n        return out->reg_fir[1];\n    case ECHO_FIR2:\n        return out->reg_fir[2];\n    case ECHO_FIR3:\n        return out->reg_fir[3];\n    case ECHO_FIR4:\n        return out->reg_fir[4];\n    case ECHO_FIR5:\n        return out->reg_fir[5];\n    case ECHO_FIR6:\n        return out->reg_fir[6];\n    case ECHO_FIR7:\n        return out->reg_fir[7];\n    }\n\n    return 0;\n}\n\nvoid echoEffectResetFir(SpcEcho *out)\n{\n    if(!out)\n        return;\n    out->setDefaultFir();\n}\n\nvoid echoEffectResetDefaults(SpcEcho *out)\n{\n    if(!out)\n        return;\n    out->setDefaultRegs();\n}\n"
  },
  {
    "path": "src/sound/fx/spc_echo.h",
    "content": "/*\n * SPC Echo sound effect\n *\n * Copyright (c) 2022-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included\n * in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n */\n\n#ifndef SPC_ECHO_HHHH\n#define SPC_ECHO_HHHH\n\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n#include \"fx_format.h\"\n\ntypedef struct SpcEcho SpcEcho;\n\nextern SpcEcho *echoEffectInit(int rate, uint16_t format, int channels);\nextern void echoEffectFree(SpcEcho *context);\n\ntypedef enum EchoSetup\n{\n    ECHO_EON = 0,\n    ECHO_EDL,\n    ECHO_EFB,\n\n    ECHO_MVOLL,\n    ECHO_MVOLR,\n    ECHO_EVOLL,\n    ECHO_EVOLR,\n\n    ECHO_FIR0,\n    ECHO_FIR1,\n    ECHO_FIR2,\n    ECHO_FIR3,\n    ECHO_FIR4,\n    ECHO_FIR5,\n    ECHO_FIR6,\n    ECHO_FIR7\n} EchoSetup;\n\nextern void spcEchoEffect(int chan, void *stream, int len, void *context);\n\nextern void echoEffectResetFir(SpcEcho *out);\nextern void echoEffectResetDefaults(SpcEcho *out);\n\nextern void echoEffectSetReg(SpcEcho *out, EchoSetup key, int val);\nextern int  echoEffectGetReg(SpcEcho *out, EchoSetup key);\n#ifdef __cplusplus\n}\n#endif\n\n#endif // SPC_ECHO_HHHH\n"
  },
  {
    "path": "src/sound/sound_msgsnd.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/mixer.h\"\n\n#include \"../sound.h\"\n#include \"sound_msgsnd.h\"\n\nextern bool g_mixerLoaded;\n\nstatic unsigned char s_sndGlas[] =\n{\n    0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x05,\n    0x03, 0xc0, 0x4d, 0x54, 0x72, 0x6b, 0x00, 0x00, 0x00, 0x57, 0x00, 0xff,\n    0x03, 0x08, 0x75, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x64, 0x00, 0xff,\n    0x02, 0x1a, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20,\n    0xa9, 0x20, 0x32, 0x30, 0x30, 0x31, 0x20, 0x62, 0x79, 0x20, 0x3c, 0x6e,\n    0x61, 0x6d, 0x65, 0x3e, 0x00, 0xff, 0x01, 0x10, 0x56, 0x69, 0x74, 0x61,\n    0x6c, 0x79, 0x20, 0x4e, 0x6f, 0x76, 0x69, 0x63, 0x68, 0x6b, 0x6f, 0x76,\n    0x00, 0xff, 0x58, 0x04, 0x04, 0x02, 0x18, 0x08, 0x00, 0xff, 0x59, 0x02,\n    0x00, 0x00, 0x00, 0xff, 0x51, 0x03, 0x09, 0x27, 0xc0, 0x00, 0xff, 0x2f,\n    0x00, 0x4d, 0x54, 0x72, 0x6b, 0x00, 0x00, 0x00, 0x1b, 0x00, 0xc0, 0x74,\n    0x00, 0x90, 0x28, 0x64, 0x00, 0x27, 0x64, 0x00, 0x25, 0x64, 0x87, 0x40,\n    0x25, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28, 0x00, 0x00, 0xff, 0x2f, 0x00,\n    0x4d, 0x54, 0x72, 0x6b, 0x00, 0x00, 0x00, 0x1b, 0x00, 0xc1, 0x7f, 0x00,\n    0x91, 0x44, 0x64, 0x00, 0x40, 0x64, 0x00, 0x37, 0x64, 0x87, 0x40, 0x37,\n    0x00, 0x00, 0x40, 0x00, 0x00, 0x44, 0x00, 0x00, 0xff, 0x2f, 0x00, 0x4d,\n    0x54, 0x72, 0x6b, 0x00, 0x00, 0x01, 0x66, 0x00, 0xc2, 0x71, 0x00, 0x92,\n    0x66, 0x7f, 0x00, 0x61, 0x7f, 0x00, 0x57, 0x7f, 0x00, 0x58, 0x7f, 0x00,\n    0x5a, 0x7f, 0x00, 0x55, 0x7f, 0x00, 0x64, 0x7f, 0x00, 0x60, 0x7f, 0x00,\n    0x5d, 0x7f, 0x00, 0x6a, 0x7f, 0x26, 0x63, 0x67, 0x1a, 0x5f, 0x68, 0x19,\n    0x5c, 0x6a, 0x1f, 0x55, 0x00, 0x00, 0x58, 0x00, 0x00, 0x66, 0x00, 0x00,\n    0x57, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x61, 0x00, 0x00, 0x5e, 0x6b, 0x00,\n    0x62, 0x6b, 0x26, 0x63, 0x00, 0x1a, 0x5f, 0x00, 0x19, 0x5c, 0x00, 0x1f,\n    0x5e, 0x00, 0x00, 0x62, 0x00, 0x00, 0x5f, 0x62, 0x00, 0x5b, 0x62, 0x00,\n    0x65, 0x62, 0x78, 0x65, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x5b, 0x00, 0x00,\n    0x66, 0x69, 0x24, 0x5e, 0x69, 0x1a, 0x62, 0x69, 0x3a, 0x66, 0x00, 0x00,\n    0x61, 0x70, 0x00, 0x63, 0x70, 0x24, 0x5e, 0x00, 0x1a, 0x62, 0x00, 0x2e,\n    0x58, 0x73, 0x0c, 0x63, 0x00, 0x00, 0x61, 0x00, 0x00, 0x5c, 0x35, 0x00,\n    0x60, 0x35, 0x28, 0x5e, 0x01, 0x19, 0x66, 0x01, 0x2b, 0x58, 0x00, 0x08,\n    0x5f, 0x01, 0x04, 0x60, 0x00, 0x00, 0x5c, 0x00, 0x28, 0x5e, 0x00, 0x19,\n    0x66, 0x00, 0x33, 0x5f, 0x00, 0x04, 0x66, 0x18, 0x00, 0x62, 0x18, 0x6b,\n    0x57, 0x45, 0x0d, 0x62, 0x00, 0x00, 0x60, 0x00, 0x00, 0x5d, 0x00, 0x00,\n    0x64, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x66, 0x00, 0x40, 0x60, 0x1a, 0x19,\n    0x5d, 0x4e, 0x12, 0x57, 0x00, 0x3b, 0x62, 0x67, 0x12, 0x60, 0x00, 0x19,\n    0x5d, 0x00, 0x1f, 0x60, 0x67, 0x00, 0x63, 0x67, 0x2e, 0x62, 0x00, 0x4a,\n    0x63, 0x00, 0x00, 0x60, 0x00, 0x3e, 0x5a, 0x43, 0x10, 0x5f, 0x66, 0x00,\n    0x65, 0x66, 0x00, 0x60, 0x66, 0x56, 0x5d, 0x59, 0x12, 0x5a, 0x00, 0x10,\n    0x60, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x65, 0x00, 0x2a, 0x63, 0x2c, 0x00,\n    0x61, 0x2c, 0x0e, 0x5f, 0x7f, 0x1e, 0x5d, 0x00, 0x4c, 0x63, 0x00, 0x00,\n    0x61, 0x00, 0x0e, 0x5f, 0x00, 0x62, 0x61, 0x70, 0x78, 0x61, 0x00, 0x08,\n    0x5d, 0x2f, 0x00, 0x62, 0x2f, 0x78, 0x62, 0x00, 0x00, 0x5d, 0x00, 0x00,\n    0x5f, 0x6d, 0x54, 0x62, 0x77, 0x24, 0x5f, 0x00, 0x1c, 0x64, 0x7c, 0x38,\n    0x62, 0x00, 0x40, 0x64, 0x00, 0x5c, 0x5e, 0x64, 0x13, 0x5f, 0x5d, 0x26,\n    0x62, 0x76, 0x3f, 0x5e, 0x00, 0x13, 0x5f, 0x00, 0x26, 0x62, 0x00, 0x08,\n    0x62, 0x65, 0x78, 0x62, 0x00, 0x81, 0x62, 0x5e, 0x36, 0x00, 0x62, 0x36,\n    0x78, 0x62, 0x00, 0x00, 0x5e, 0x00, 0x81, 0x2e, 0x5e, 0x2b, 0x78, 0x5e,\n    0x00, 0x00, 0xff, 0x2f, 0x00, 0x4d, 0x54, 0x72, 0x6b, 0x00, 0x00, 0x00,\n    0x04, 0x00, 0xff, 0x2f, 0x00,\n    0x00\n};\n\nstatic unsigned char s_sndMsg[] =\n{\n    0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x03,\n    0x03, 0xc0, 0x4d, 0x54, 0x72, 0x6b, 0x00, 0x00, 0x00, 0x57, 0x00, 0xff,\n    0x03, 0x08, 0x75, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x64, 0x00, 0xff,\n    0x02, 0x1a, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20,\n    0xa9, 0x20, 0x32, 0x30, 0x30, 0x31, 0x20, 0x62, 0x79, 0x20, 0x3c, 0x6e,\n    0x61, 0x6d, 0x65, 0x3e, 0x00, 0xff, 0x01, 0x10, 0x56, 0x69, 0x74, 0x61,\n    0x6c, 0x79, 0x20, 0x4e, 0x6f, 0x76, 0x69, 0x63, 0x68, 0x6b, 0x6f, 0x76,\n    0x00, 0xff, 0x58, 0x04, 0x04, 0x02, 0x18, 0x08, 0x00, 0xff, 0x59, 0x02,\n    0x00, 0x00, 0x00, 0xff, 0x51, 0x03, 0x06, 0x8a, 0x1b, 0x00, 0xff, 0x2f,\n    0x00, 0x4d, 0x54, 0x72, 0x6b, 0x00, 0x00, 0x00, 0x22, 0x00, 0xc0, 0x0a,\n    0x00, 0x90, 0x48, 0x64, 0x00, 0x54, 0x64, 0x87, 0x40, 0x48, 0x64, 0x00,\n    0x3c, 0x64, 0x88, 0x38, 0x54, 0x00, 0x00, 0x48, 0x00, 0x78, 0x48, 0x00,\n    0x00, 0x3c, 0x00, 0x00, 0xff, 0x2f, 0x00, 0x4d, 0x54, 0x72, 0x6b, 0x00,\n    0x00, 0x00, 0x04, 0x00, 0xff, 0x2f, 0x00,\n    0x00\n};\n\n#ifndef CUSTOM_AUDIO\nvoid playFallbackSfx(int sfxId, int loops, int volume)\n{\n    if(!g_mixerLoaded)\n        return;\n\n    SDL_RWops *sfx = nullptr;\n\n    switch(sfxId)\n    {\n    case SFX_Message:\n        sfx = SDL_RWFromConstMem(s_sndMsg, sizeof(s_sndMsg));\n        break;\n    case SFX_SMGlass:\n        sfx = SDL_RWFromConstMem(s_sndGlas, sizeof(s_sndGlas));\n        break;\n    }\n\n    if(sfx)\n    {\n        Mix_Music *sfxF = Mix_LoadMUS_RW_ARG(sfx, 1, \"s0;b58;e0;c1;\");\n        if(!sfxF)\n        {\n            SDL_RWclose(sfx);\n            return;\n        }\n\n        Mix_VolumeMusicStream(sfxF, volume);\n        Mix_PlayMusicStream(sfxF, loops + 1);\n        Mix_SetFreeOnStop(sfxF, 1);\n    }\n}\n#else\nvoid playFallbackSfx(int, int, int) {}\n#endif\n"
  },
  {
    "path": "src/sound/sound_msgsnd.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nvoid playFallbackSfx(int sfxId, int loops, int volume);\n"
  },
  {
    "path": "src/sound.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"sdl_proxy/sdl_stdinc.h\"\n#include \"sdl_proxy/sdl_atomic.h\"\n#include \"sdl_proxy/sdl_assert.h\"\n#include \"sdl_proxy/sdl_timer.h\"\n#include \"sdl_proxy/mixer.h\"\n\n#include \"globals.h\"\n#include \"config.h\"\n#include \"global_dirs.h\"\n#include \"frame_timer.h\"\n#include \"message.h\"\n\n#include \"load_gfx.h\"\n#include \"core/msgbox.h\"\n#include \"main/screen_progress.h\"\n\n#include \"sound.h\"\n#include \"sound_thread.h\"\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\n#include \"sound/fx/reverb.h\"\n#include \"sound/fx/spc_echo.h\"\n#endif\n\n#include \"sound/sound_msgsnd.h\"\n\n#include <Logger/logger.h>\n#include <IniProcessor/ini_processing.h>\n#include <Utils/files.h>\n#include <Utils/files_ini.h>\n#include <Utils/strings.h>\n#include <unordered_map>\n#include <fmt_format_ne.h>\n\nenum class SoundScope\n{\n    global,\n    episode,\n    custom\n};\n\n// Public musicPlaying As Boolean\nbool musicPlaying = false;\n// Public musicLoop As Integer\nint musicLoop = 0;\n// Public musicName As String\nstd::string musicName;\n\nint playerHammerSFX = SFX_Fireball;\n\nconst AudioDefaults_t g_audioDefaults =\n#if defined(__WII__) /* Defaults for Nintendo Wii */\n{\n    32000,\n    2,\n    1536,\n    (int)AUDIO_S16SYS\n};\n#elif defined(__WIIU__)\n{\n    44100,\n    2,\n    1024,\n    (int)AUDIO_F32SYS\n};\n#elif defined(__3DS__)\n{\n    44100,\n    2,\n    2048,\n    (int)AUDIO_S16SYS\n};\n#elif defined(__SWITCH__) /* Defaults for Nintendo Switch */\n{\n    48000,\n    2,\n    1024,\n    (int)AUDIO_S16SYS\n};\n#else /* Defaults for all other platforms */\n{\n    44100,\n    2,\n    512,\n    (int)AUDIO_F32SYS\n};\n#endif\n\nstatic Mix_Music *g_curMusic = nullptr;\nbool g_mixerLoaded = false;\n\n//! most recent argument to StartMusic. Could be a world map music ID or a section index.\nstatic constexpr int s_null_music = -5;\nstatic int s_recentMusicA = s_null_music;\n\nstatic int g_customLvlMusicId = 24;\nstatic int g_customWldMusicId = 17;\nstatic int g_reservedChannels = 0;\n\n//! Total count of loaded default sounds\nstatic unsigned int g_totalSounds = 0;\n//! Are custom sounds was loaded from the level/world data folder?\nstatic bool g_customSoundsInDataFolder = false;\n//! Are custom music files was loaded from the level/world data folder?\nstatic bool g_customMusicInDataFolder = false;\n//! Total count of level music\nstatic unsigned int g_totalMusicLevel = 0;\n//! Total count of world map music\nstatic unsigned int g_totalMusicWorld = 0;\n//! Total count of special music\nstatic unsigned int g_totalMusicSpecial = 0;\n//! Enable using the unique iceball SFX when available\nstatic bool s_useIceBallSfx = false;\n//! Enable using of the new ice SFX: NPC freeze and breaking of the frozen NPC\nstatic bool s_useNewIceSfx = false;\n\nstatic int g_errorsSfx = 0;\n// static int g_errorsMusic = 0; // Unued yet\n\nstatic bool s_musicHasYoshiMode = false;\nstatic int  s_musicYoshiTrackNumber = -1;\n#ifdef THEXTECH_ENABLE_AUDIO_FX\nstatic bool s_musicDisableSpcEcho = false;\n#endif\nstatic int s_musicDefaultVolume = 64;\n\nstatic std::string MusicRoot;\nstatic std::string SfxRoot;\n\nstatic std::string musicIni; // = \"music.ini\";\nstatic std::string sfxIni; // = \"sounds.ini\";\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\nstruct SectionEffect_t\n{\n    enum FX\n    {\n        FX_None = 0,\n        FX_Echo,\n        FX_Reverb\n    };\n    //! Reverb effect settings\n    SoundFXReverb rev;\n    //! Echo effect settings\n    SoundFXEchoSetup echo;\n    //! Selected effect\n    int fx = FX_None;\n    //! Disable echo of playing SPC files\n    bool disableSpcEcho = false;\n};\n\nstatic SectionEffect_t s_sectionEffect[maxSections + 1];\nstatic std::unordered_map<std::string, SectionEffect_t> s_effectsList;\n#endif\n\nstruct Music_t\n{\n    std::string path;\n    int volume = 52;\n    int yoshiModeTrack = -1;\n};\n\nstruct SFX_t\n{\n    std::string path;\n    std::string customPath;\n    Mix_Chunk *chunk = nullptr;\n    Mix_Music *music = nullptr;\n    Mix_Chunk *chunkOrig = nullptr;\n    Mix_Music *musicOrig = nullptr;\n    int8_t channel = -1;\n    bool isCustom = false;\n    bool isSilent = false;\n    bool isSilentOrig = false;\n};\n\nstatic std::unordered_map<std::string, Music_t> music;\nstatic std::unordered_map<int, SFX_t>           sound;\n\n//! Sounds played by scripts\nstatic SDL_atomic_t                                extSfxBusy;\nstatic std::unordered_map<std::string, Mix_Chunk*> extSfx;\nstatic std::unordered_map<int, std::string>        extSfxPlaying;\n#ifndef THEXTECH_NO_SDL_BUILD\nstatic void extSfxStopCallback(int channel);\n#endif\n\nstatic const int maxSfxChannels = 91;\n\n#ifdef LOW_MEM\nstatic const double c_max_chunk_duration = 1.25; // max length of an in-memory chunk in seconds\n#else\nstatic const double c_max_chunk_duration = 5.0;  // max length of an in-memory chunk in seconds\n#endif\n\n#ifndef THEXTECH_NO_SDL_BUILD\nstatic const char *audio_format_to_string(SDL_AudioFormat f)\n{\n    switch(f)\n    {\n    default:\n        return \"<unknown>\";\n    case AUDIO_U8:\n        return \"U8\";\n    case AUDIO_S8:\n        return \"S8\";\n    case AUDIO_S16LSB:\n        return \"S16-LE\";\n    case AUDIO_S16MSB:\n        return \"S16-BE\";\n    case AUDIO_U16LSB:\n        return \"U16-LE\";\n    case AUDIO_U16MSB:\n        return \"U16-BE\";\n    case AUDIO_S32LSB:\n        return \"S32-LE\";\n    case AUDIO_S32MSB:\n        return \"S32-BE\";\n    case AUDIO_F32LSB:\n        return \"F32-LE\";\n    case AUDIO_F32MSB:\n        return \"F32-BE\";\n    }\n}\n#endif\n\nstatic void clear_sfx(SFX_t &s)\n{\n    if(s.chunk)\n        Mix_FreeChunk(s.chunk);\n\n    s.chunk = nullptr;\n\n    if(s.chunkOrig)\n        Mix_FreeChunk(s.chunkOrig);\n\n    s.chunkOrig = nullptr;\n\n    if(s.music)\n    {\n        Mix_HaltMusicStream(s.music);\n        Mix_FreeMusic(s.music);\n    }\n\n    s.music = nullptr;\n\n    if(s.musicOrig)\n        Mix_FreeMusic(s.musicOrig);\n\n    s.musicOrig = nullptr;\n}\n\n\nint CustomWorldMusicId()\n{\n    return g_customWldMusicId;\n}\n\nvoid InitMixerX()\n{\n#ifndef THEXTECH_NO_SDL_BUILD\n    if(!g_config.audio_enable)\n        return;\n\n    int ret;\n    const int initFlags = MIX_INIT_MID | MIX_INIT_MOD | MIX_INIT_FLAC | MIX_INIT_OGG | MIX_INIT_OPUS | MIX_INIT_MP3;\n\n    MusicRoot = AppPath + \"music/\";\n    SfxRoot = AppPath + \"sound/\";\n\n    Mix_SetRWFromFile(Files::open_file);\n\n    SDL_AtomicSet(&extSfxBusy, 0);\n\n    if(g_mixerLoaded)\n        return;\n\n#ifdef __3DS__\n    // Create the blank \"dspfirm.cdc\" if not exists (it's required to exist for the 3DS audio work on Citra HLE)\n    if(!Files::fileExists(\"/3ds/dspfirm.cdc\"))\n    {\n        FILE *x = Files::utf8_fopen(\"/3ds/dspfirm.cdc\", \"wb\");\n        fclose(x);\n    }\n#endif\n\n    pLogDebug(\"Opening sound (wanted: rate=%d hz, format=%s, channels=%d, buffer=%d frames)...\",\n              (int)g_config.audio_sample_rate,\n              audio_format_to_string((int)g_config.audio_format),\n              (int)g_config.audio_channels,\n              (int)g_config.audio_buffer_size);\n\n    ret = Mix_Init(initFlags);\n\n    if(ret != initFlags)\n    {\n        pLogWarning(\"MixerX: Some modules aren't properly initialized\");\n        if((ret & MIX_INIT_MID) != MIX_INIT_MID)\n            pLogWarning(\"MixerX: Failed to initialize MIDI module\");\n        if((ret & MIX_INIT_MOD) != MIX_INIT_MOD)\n            pLogWarning(\"MixerX: Failed to initialize Tracker music module\");\n        if((ret & MIX_INIT_FLAC) != MIX_INIT_FLAC)\n            pLogWarning(\"MixerX: Failed to initialize FLAC module\");\n        if((ret & MIX_INIT_OGG) != MIX_INIT_OGG)\n            pLogWarning(\"MixerX: Failed to initialize OGG Vorbis module\");\n        if((ret & MIX_INIT_OPUS) != MIX_INIT_OPUS)\n            pLogWarning(\"MixerX: Failed to initialize Opus module\");\n        if((ret & MIX_INIT_MP3) != MIX_INIT_MP3)\n            pLogWarning(\"MixerX: Failed to initialize MP3 module\");\n    }\n\n    ret = Mix_OpenAudio(g_config.audio_sample_rate,\n                        g_config.audio_format,\n                        g_config.audio_channels,\n                        g_config.audio_buffer_size);\n\n    if(ret < 0)\n    {\n        std::string msg = fmt::format_ne(\"Can't open audio stream, continuing without audio: ({0})\", Mix_GetError());\n        pLogCritical(\"%s\", msg.c_str());\n        XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_ERROR, \"Sound opening error\", msg);\n    }\n    else\n    {\n        SDL_AudioSpec ob;\n        ret = Mix_QuerySpecEx(&ob);\n\n        if(ret == 0)\n        {\n            pLogCritical(\"Failed to call the Mix_QuerySpec!\");\n            g_config.audio_sample_rate.obtained = g_config.audio_sample_rate;\n            g_config.audio_format.obtained = g_config.audio_format;\n            g_config.audio_channels.obtained = g_config.audio_channels;\n            g_config.audio_buffer_size.obtained = g_config.audio_buffer_size;\n        }\n        else\n        {\n            g_config.audio_sample_rate.obtained = ob.freq;\n            g_config.audio_format.obtained = ob.format;\n            g_config.audio_channels.obtained = ob.channels;\n            g_config.audio_buffer_size.obtained = ob.samples;\n\n            pLogDebug(\"Sound opened (obtained: rate=%d hz, format=%s, channels=%d, buffer=%d frames)...\",\n                      ob.freq,\n                      audio_format_to_string(ob.format),\n                      ob.channels,\n                      ob.samples);\n        }\n\n#if defined(__3DS__)\n        // Set fastest emulators to be default\n        Mix_OPNMIDI_setEmulator(OPNMIDI_OPN2_EMU_GENS);\n        Mix_OPNMIDI_setChannelAllocMode(MIX_CHIP_CHANALLOC_SameInst);\n        Mix_OPNMIDI_setChipsCount(2);\n        Mix_ADLMIDI_setEmulator(ADLMIDI_OPL3_EMU_DOSBOX);\n        Mix_ADLMIDI_setChannelAllocMode(MIX_CHIP_CHANALLOC_SameInst);\n        Mix_ADLMIDI_setChipsCount(2);\n#elif defined(__WII__)\n        // Set fastest emulators to be default\n        Mix_OPNMIDI_setEmulator(OPNMIDI_OPN2_EMU_MAME_OPN2);\n        Mix_OPNMIDI_setChipsCount(2);\n        Mix_ADLMIDI_setEmulator(ADLMIDI_OPL3_EMU_DOSBOX);\n        Mix_ADLMIDI_setChipsCount(2);\n#elif defined(__vita__)\n        Mix_OPNMIDI_setEmulator(OPNMIDI_OPN2_EMU_MAME_OPN2);\n        Mix_OPNMIDI_setChipsCount(2);\n        Mix_ADLMIDI_setEmulator(ADLMIDI_OPL3_EMU_DOSBOX);\n        Mix_ADLMIDI_setChipsCount(2);\n#endif\n\n        Mix_VolumeMusic(MIX_MAX_VOLUME);\n        Mix_AllocateChannels(maxSfxChannels);\n\n        // Set channel finished callback to handle finished custom SFX\n        Mix_ChannelFinished(&extSfxStopCallback);\n\n        StartSfxThread();\n\n        g_mixerLoaded = true;\n    }\n#endif // #ifndef THEXTECH_NO_SDL_BUILD\n}\n\nvoid RestartMixerX()\n{\n    int recent_music = s_recentMusicA;\n\n    if(g_mixerLoaded && !musicPlaying)\n        recent_music = s_null_music;\n\n    UnloadSound();\n    QuitMixerX();\n\n    InitMixerX();\n    InitSound();\n    LoadCustomSound();\n\n    if(recent_music != s_null_music)\n        StartMusic(recent_music);\n}\n\nvoid QuitMixerX()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    EndSfxThread();\n\n    UnloadExtSounds();\n\n    if(g_curMusic)\n        Mix_FreeMusic(g_curMusic);\n\n    g_curMusic = nullptr;\n    g_reservedChannels = 0;\n\n    for(auto & it : sound)\n    {\n        auto &s = it.second;\n        clear_sfx(s);\n    }\n\n    sound.clear();\n    music.clear();\n\n    Mix_CloseAudio();\n    Mix_Quit();\n\n    g_mixerLoaded = false;\n}\n\nstatic void AddMusic(SoundScope root,\n                     IniProcessing &ini,\n                     const std::string &alias,\n                     const std::string &group,\n                     int volume)\n{\n    std::string f;\n    ini.beginGroup(group);\n    ini.read(\"file\", f, std::string());\n\n    if(!f.empty())\n    {\n        Music_t m;\n        if(root == SoundScope::global)\n            m.path = MusicRoot + f;\n        else if(root == SoundScope::episode)\n            m.path = g_dirEpisode.resolveFileCaseAbs(f);\n        else if(root == SoundScope::custom)\n            m.path = g_dirCustom.resolveFileCaseAbs(f);\n\n        ini.read(\"yoshi-mode-track\", m.yoshiModeTrack, -1);\n        m.volume = volume;\n        pLogDebug(\"Adding music [%s] '%s'\", alias.c_str(), m.path.c_str());\n\n        auto a = music.find(alias);\n\n        if(a == music.end())\n            music.insert({alias, m});\n        else\n            a->second = m;\n    }\n\n    ini.endGroup();\n}\n\nstatic void RestoreSfx(SFX_t &u)\n{\n    if(u.isCustom)\n    {\n        if(u.chunk)\n            Mix_FreeChunk(u.chunk);\n\n        if(u.music)\n            Mix_FreeMusic(u.music);\n\n        u.chunk = u.chunkOrig;\n        u.music = u.musicOrig;\n        u.isSilent = u.isSilentOrig;\n\n        u.chunkOrig = nullptr;\n        u.musicOrig = nullptr;\n        u.isSilentOrig = false;\n\n        u.isCustom = false;\n    }\n}\n\nstatic void AddSfx(SoundScope root,\n                   IniProcessing &ini,\n                   int alias,\n                   const std::string &group,\n                   bool isCustom = false)\n{\n    std::string f;\n    bool isSilent;\n    ini.beginGroup(group);\n    ini.read(\"file\", f, std::string());\n    ini.read(\"silent\", isSilent, false);\n\n    if(!f.empty() || isSilent)\n    {\n        if(LoadingInProcess)\n            LoaderUpdateDebugString(fmt::format_ne(\"sound {0}\", f));\n\n        if(isCustom)\n        {\n            auto s = sound.find(alias);\n\n            if(s != sound.end())\n            {\n                auto &m = s->second;\n\n                std::string newPath;\n                if(root == SoundScope::global)\n                    newPath = SfxRoot + f;\n                else if(root == SoundScope::episode)\n                    newPath = g_dirEpisode.resolveFileCaseAbs(f);\n                else if(root == SoundScope::custom)\n                    newPath = g_dirCustom.resolveFileCaseAbs(f);\n\n                if(!isSilent && m.isCustom && newPath == m.customPath)\n                {\n                    ini.endGroup();\n                    return;  // Don't load the same file twice!\n                }\n\n                Mix_Chunk *backup_chunk = m.chunk;\n                Mix_Music *backup_music = m.music;\n                bool backup_isSilent = m.isSilent;\n                m.customPath = newPath;\n\n                m.chunk = nullptr;\n                m.music = nullptr;\n                m.isSilent = false;\n\n                if(!isSilent)\n                {\n                    m.music = Mix_LoadMUS((newPath).c_str());\n                    if(m.music)\n                    {\n                        // check, if short enough, load it as a chunk instead\n                        double duration = Mix_MusicDuration(m.music);\n                        if(duration >= 0 && duration < c_max_chunk_duration)\n                        {\n                            Mix_FreeMusic(m.music);\n                            m.music = nullptr;\n                        }\n                        else\n                            pLogInfo(\"Will load SFX %s as a multi-music\", newPath.c_str());\n                    }\n\n                    if(!m.music)\n                        m.chunk = Mix_LoadWAV((newPath).c_str());\n                }\n\n                if(m.chunk || m.music || isSilent)\n                {\n                    if(!m.isCustom && !m.chunkOrig && !m.musicOrig)\n                    {\n                        m.chunkOrig = backup_chunk;\n                        m.musicOrig = backup_music;\n                        m.isSilentOrig = backup_isSilent;\n                    }\n                    else\n                    {\n                        if(backup_chunk)\n                            Mix_FreeChunk(backup_chunk);\n\n                        if(backup_music)\n                            Mix_FreeMusic(backup_music);\n                    }\n\n                    m.isCustom = true;\n                    m.isSilent = isSilent;\n                }\n                else\n                {\n                    m.chunk = backup_chunk;\n                    m.music = backup_music;\n                    m.isSilent = backup_isSilent;\n                    pLogWarning(\"ERROR: SFX '%s' loading error: %s\", m.path.c_str(), Mix_GetError());\n                }\n            }\n        }\n        else\n        {\n            SFX_t m;\n\n            if(root == SoundScope::global)\n                m.path = SfxRoot + f;\n            else if(root == SoundScope::episode)\n                m.path = g_dirEpisode.resolveFileCaseAbs(f);\n            else if(root == SoundScope::custom)\n                m.path = g_dirCustom.resolveFileCaseAbs(f);\n\n            m.isSilent = isSilent;\n            pLogDebug(\"Adding SFX [sound%d] '%s'\", alias, isSilent ? \"<silence>\" : m.path.c_str());\n            if(!isSilent)\n            {\n                m.music = Mix_LoadMUS((m.path).c_str());\n                if(m.music)\n                {\n                    // check, if short enough, load it as a chunk instead\n                    double duration = Mix_MusicDuration(m.music);\n                    if(duration >= 0 && duration < c_max_chunk_duration)\n                    {\n                        Mix_FreeMusic(m.music);\n                        m.music = nullptr;\n                    }\n                    else\n                        pLogInfo(\"Will load SFX %s as a multi-music\", m.path.c_str());\n                }\n\n                if(!m.music)\n                    m.chunk = Mix_LoadWAV((m.path).c_str());\n            }\n\n            if(m.chunk || m.music || isSilent)\n            {\n                bool isSingleChannel = false;\n                ini.read(\"single-channel\", isSingleChannel, false);\n                if(isSingleChannel && m.chunk)\n                    m.channel = g_reservedChannels++;\n\n                auto se = sound.find(alias);\n                if(se != sound.end()) // Avoid memory leaks\n                {\n                    auto &s = se->second;\n                    clear_sfx(s);\n                    s = std::move(m);\n                }\n                else\n                    sound.insert({alias, m});\n            }\n            else\n            {\n                pLogWarning(\"ERROR: SFX '%s' loading error: %s\", m.path.c_str(), Mix_GetError());\n                g_errorsSfx++;\n            }\n        }\n    }\n    ini.endGroup();\n}\n\nvoid SetMusicVolume(const std::string &Alias, long Volume)\n{\n    auto mus = music.find(Alias);\n    if(mus != music.end())\n    {\n        mus->second.volume = int(Volume);\n    }\n}\n\nvoid SoundPauseAll()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    pLogDebug(\"Pause all sound\");\n    Mix_PauseAudio(1);\n}\n\nvoid SoundResumeAll()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    pLogDebug(\"Resume all sound\");\n    Mix_PauseAudio(0);\n}\n\nvoid SoundPauseEngine(int paused)\n{\n    if(!g_mixerLoaded)\n        return;\n\n    Mix_PauseAudio(paused);\n}\n\nstatic void processPathArgs(std::string &path,\n                            const std::string &episodeRoot,\n                            const std::string &dataDirName,\n                            int *yoshiModeTrack = nullptr)\n{\n    if(path.find('|') == std::string::npos)\n        return; // Nothing to do\n    Strings::List p;\n    Strings::split(p, path, '|');\n\n    if(yoshiModeTrack)\n    {\n        *yoshiModeTrack = -1;\n        Strings::List args;\n        Strings::split(args, p[1], ';');\n        for(auto &arg : args)\n        {\n            if(arg.compare(0, 3, \"ym=\") != 0)\n                continue;\n            *yoshiModeTrack = SDL_atoi(arg.substr(3).c_str());\n        }\n    }\n\n    Strings::replaceInAll(p[1], \"{e}\", episodeRoot);\n    Strings::replaceInAll(p[1], \"{d}\", episodeRoot + dataDirName);\n    Strings::replaceInAll(p[1], \"{r}\", MusicRoot);\n    path = p[0] + \"|\" + p[1];\n}\n\nvoid PlayMusic(const std::string &Alias, int fadeInMs)\n{\n    if(!g_mixerLoaded)\n        return;\n\n    if(g_curMusic)\n    {\n        Mix_HaltMusicStream(g_curMusic);\n        Mix_FreeMusic(g_curMusic);\n        g_curMusic = nullptr;\n        g_stats.currentMusic.clear();\n        g_stats.currentMusicFile.clear();\n    }\n\n    auto mus = music.find(Alias);\n    if(mus != music.end())\n    {\n        auto &m = mus->second;\n        std::string p = m.path;\n        processPathArgs(p, FileNamePath + \"/\", FileName + \"/\");\n        g_curMusic = Mix_LoadMUS(p.c_str());\n\n        if(!g_curMusic)\n            pLogWarning(\"Music '%s' opening error: %s\", m.path.c_str(), Mix_GetError());\n        else\n        {\n            int ret;\n\n            Mix_VolumeMusicStream(g_curMusic, m.volume * g_config.audio_mus_volume / 100);\n            s_musicYoshiTrackNumber = m.yoshiModeTrack;\n            s_musicHasYoshiMode = (s_musicYoshiTrackNumber >= 0 && (Mix_GetMusicTracks(g_curMusic) > s_musicYoshiTrackNumber));\n            s_musicDefaultVolume = m.volume;\n            UpdateYoshiMusic();\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\n            if(s_musicDisableSpcEcho)\n                Mix_GME_SetSpcEchoDisabled(g_curMusic, s_musicDisableSpcEcho);\n#endif\n\n            if(fadeInMs > 0)\n                ret = Mix_FadeInMusic(g_curMusic, -1, fadeInMs);\n            else\n                ret = Mix_PlayMusic(g_curMusic, -1);\n\n            if(ret >= 0)\n            {\n                g_stats.currentMusic = Mix_GetMusicTitle(g_curMusic);\n                g_stats.currentMusicFile = Files::basename(m.path);\n            }\n            else\n                pLogWarning(\"Music '%s' playing error: %s\", m.path.c_str(), Mix_GetError());\n        }\n    }\n    else\n        pLogWarning(\"Unknown music alias '%s'\", Alias.c_str());\n}\n\nstatic int getFallbackSfx(int A);\nvoid PlaySfx_Blocking(int Alias, int loops, int volume, uint8_t left, uint8_t right)\n{\n    if(!g_mixerLoaded || (int)g_config.audio_sfx_volume == 0 || XMessage::GetStatus() == XMessage::Status::replay)\n        return;\n\n    auto sfx = sound.find(Alias);\n\n    if(sfx == sound.end() || (!sfx->second.chunk && !sfx->second.music && !sfx->second.isSilent))\n        sfx = sound.find(getFallbackSfx(Alias));\n\n    if(sfx != sound.end())\n    {\n        auto &s = sfx->second;\n        if(s.chunk)\n        {\n            int channel = Mix_PlayChannelVol(s.channel, s.chunk, loops, volume * g_config.audio_sfx_volume / 100);\n\n            if(channel >= 0)\n                Mix_SetPanning(channel, left, right);\n        }\n        else if(s.music)\n        {\n            if(Mix_PlayingMusicStream(s.music))\n                Mix_RewindMusicStream(s.music);\n\n            Mix_VolumeMusicStream(s.music, volume * g_config.audio_sfx_volume / 100);\n            Mix_SetMusicEffectPanning(s.music, left, right);\n            Mix_PlayMusicStream(s.music, loops);\n        }\n    }\n}\n\nvoid StopSfx(int Alias)\n{\n    auto sfx = sound.find(Alias);\n    if(sfx != sound.end())\n    {\n        auto &s = sfx->second;\n        if(s.chunk)\n            Mix_HaltChannel(s.channel);\n        else if(s.music)\n            Mix_HaltMusicStream(s.music);\n    }\n}\n\n\nstatic bool s_delayMusic = false;\nstatic bool s_delayedMusicRequested = false;\nstatic int  s_delayedMusicA = 0;\nstatic int  s_delayedMusicFadeInMs = 0;\n\nvoid setMusicStartDelay()\n{\n    s_delayedMusicRequested = false;\n    s_delayMusic = true;\n}\n\nbool delayMusicIsSet()\n{\n    return s_delayMusic;\n}\n\nvoid delayedMusicStart()\n{\n    s_delayMusic = false;\n    if(s_delayedMusicRequested)\n    {\n        D_pLogDebug(\"Restored delayed music request A=%d\", s_delayedMusicA);\n        StartMusic(s_delayedMusicA, s_delayedMusicFadeInMs);\n        s_delayedMusicRequested = false;\n    }\n}\n\nvoid delayedMusicReset()\n{\n    s_delayMusic = false;\n    if(s_delayedMusicRequested)\n        D_pLogDebug(\"Saved music request erase A=%d\", s_delayedMusicA);\n    s_delayedMusicRequested = false;\n}\n\nvoid StartMusic(int A, int fadeInMs)\n{\n    if(s_delayMusic)\n    {\n        s_delayedMusicA = A;\n        s_delayedMusicFadeInMs = fadeInMs;\n        s_delayedMusicRequested = true;\n        D_pLogDebug(\"Saved start music request A=%d\", A);\n        return;\n    }\n\n    D_pLogDebug(\"Start music A=%d\", A);\n\n    if(!g_mixerLoaded || (int)g_config.audio_mus_volume == 0 || XMessage::GetStatus() == XMessage::Status::replay)\n    {\n        if(g_mixerLoaded && g_curMusic)\n            StopMusic();\n\n        // Keep world map music being remembered when sound disabled\n        if((LevelSelect || WorldEditor) && !GameMenu && !GameOutro)\n            curWorldMusic = A;\n\n        s_recentMusicA = A;\n        musicPlaying = true;\n\n        return;\n    }\n\n    if((LevelSelect || WorldEditor) && !GameMenu && !GameOutro) // music on the world map\n    {\n        StopMusic();\n        curWorldMusic = A;\n        std::string mus = fmt::format_ne(\"wmusic{0}\", A);\n        if(curWorldMusic == g_customWldMusicId)\n        {\n            pLogDebug(\"Starting custom music [%s]\", curWorldMusicFile.c_str());\n            if(g_curMusic)\n                Mix_FreeMusic(g_curMusic);\n            std::string p = FileNamePath + \"/\" + curWorldMusicFile;\n            processPathArgs(p, FileNamePath + \"/\", FileName + \"/\");\n            g_curMusic = Mix_LoadMUS(p.c_str());\n            s_musicHasYoshiMode = false;\n            s_musicYoshiTrackNumber = -1;\n            Mix_VolumeMusicStream(g_curMusic, 64 * g_config.audio_mus_volume / 100);\n            s_musicDefaultVolume = 64;\n            if(fadeInMs > 0)\n                Mix_FadeInMusic(g_curMusic, -1, fadeInMs);\n            else\n                Mix_PlayMusic(g_curMusic, -1);\n        }\n        else\n        {\n            pLogDebug(\"Starting world music [%s]\", mus.c_str());\n            PlayMusic(mus, fadeInMs);\n        }\n        musicName = std::move(mus);\n    }\n    else if(A == -1) // P switch music\n    {\n        StopMusic();\n        if(FreezeNPCs)\n        {\n            pLogDebug(\"Starting special music [stmusic]\");\n            PlayMusic(\"stmusic\", fadeInMs);\n            musicName = \"stmusic\";\n        }\n        else if(InvincibilityTime && !PSwitchTime && g_totalMusicSpecial >= 4)\n        {\n            pLogDebug(\"Starting special music [smusic4]\");\n            PlayMusic(\"smusic4\", fadeInMs);\n            musicName = \"smusic4\";\n        }\n        else\n        {\n            pLogDebug(\"Starting special music [smusic]\");\n            PlayMusic(\"smusic\", fadeInMs);\n            musicName = \"smusic\";\n        }\n        curMusic = -1;\n    }\n    else if(PSwitchTime == 0 && PSwitchStop == 0) // level music\n    {\n        StopMusic();\n        curMusic = bgMusic[A];\n        std::string mus = fmt::format_ne(\"music{0}\", curMusic);\n        if(curMusic == g_customLvlMusicId)\n        {\n            int ret = 0;\n\n            pLogDebug(\"Starting custom music [%s%s]\", FileNamePath.c_str(), CustomMusic[A].c_str());\n            if(g_curMusic)\n                Mix_FreeMusic(g_curMusic);\n            std::string p = FileNamePath + CustomMusic[A];\n            s_musicYoshiTrackNumber = -1;\n            processPathArgs(p, FileNamePath, FileName + \"/\", &s_musicYoshiTrackNumber);\n            g_curMusic = Mix_LoadMUS(p.c_str());\n            if(!g_curMusic)\n                pLogWarning(\"Failed to open the music [%s]: %s\", p.c_str(), Mix_GetError());\n            else\n            {\n                s_musicHasYoshiMode = (s_musicYoshiTrackNumber >= 0 && (Mix_GetMusicTracks(g_curMusic) > s_musicYoshiTrackNumber));\n                UpdateYoshiMusic();\n                Mix_VolumeMusicStream(g_curMusic, 52 * g_config.audio_mus_volume / 100);\n                s_musicDefaultVolume = 52;\n                if(fadeInMs > 0)\n                {\n                    ret = Mix_FadeInMusic(g_curMusic, -1, fadeInMs);\n                    if(ret < 0)\n                        pLogWarning(\"Failed to fade-in the music [%s]: %s\", p.c_str(), Mix_GetError());\n                }\n                else\n                {\n                    ret = Mix_PlayMusic(g_curMusic, -1);\n                    if(ret < 0)\n                        pLogWarning(\"Failed to play the music [%s]: %s\", p.c_str(), Mix_GetError());\n                }\n            }\n\n            if(ret >= 0)\n            {\n                g_stats.currentMusic = Mix_GetMusicTitle(g_curMusic);\n                g_stats.currentMusicFile = CustomMusic[A];\n            }\n        }\n        else\n        {\n            pLogDebug(\"Starting level music [%s]\", mus.c_str());\n            PlayMusic(mus);\n        }\n        musicName = std::move(mus);\n    }\n\n    s_recentMusicA = A;\n    musicPlaying = true;\n}\n\nvoid StartMusicIfOnscreen(int section)\n{\n    for(int i = 0; i < l_screen->player_count; i++)\n    {\n        if(Player[l_screen->players[i]].Section == section)\n        {\n            StartMusic(section);\n            break;\n        }\n    }\n}\n\nvoid PauseMusic()\n{\n    if(!musicPlaying || !g_mixerLoaded)\n        return;\n\n    if(g_curMusic && Mix_PlayingMusicStream(g_curMusic))\n        Mix_PauseMusicStream(g_curMusic);\n}\n\nvoid ResumeMusic()\n{\n    if(!musicPlaying || !g_mixerLoaded)\n        return;\n\n    if(g_curMusic && Mix_PausedMusicStream(g_curMusic))\n        Mix_ResumeMusicStream(g_curMusic);\n}\n\nvoid StopMusic()\n{\n    if(!musicPlaying || !g_mixerLoaded)\n        return;\n\n    pLogDebug(\"Stopping music\");\n\n    if(g_curMusic)\n    {\n        Mix_HaltMusicStream(g_curMusic);\n        Mix_FreeMusic(g_curMusic);\n    }\n    g_curMusic = nullptr;\n    musicPlaying = false;\n    s_recentMusicA = s_null_music;\n    g_stats.currentMusic.clear();\n    g_stats.currentMusicFile.clear();\n}\n\nvoid FadeOutMusic(int ms)\n{\n    if(!musicPlaying || !g_mixerLoaded)\n        return;\n\n    pLogDebug(\"Fading out music\");\n    if(g_curMusic)\n        Mix_FadeOutMusicStream(g_curMusic, ms);\n    musicPlaying = false;\n    s_recentMusicA = s_null_music;\n}\n\nvoid UpdateMusicVolume()\n{\n    if(!musicPlaying || !g_mixerLoaded)\n        return;\n\n    if(g_curMusic)\n    {\n        if((int)g_config.audio_mus_volume == 0)\n            StartMusic(s_recentMusicA); // will actually STOP it\n        else\n            Mix_VolumeMusicStream(g_curMusic, s_musicDefaultVolume * g_config.audio_mus_volume / 100);\n    }\n    else if(s_recentMusicA != s_null_music)\n        StartMusic(s_recentMusicA); // will restart it\n}\n\nvoid PlayInitSound()\n{\n    MusicRoot = AppPath + \"music/\";\n    SfxRoot = AppPath + \"sound/\";\n\n    // std::string doSound = AppPath + \"sound/\";\n    IniProcessing sounds = Files::load_ini(AppPath + \"sounds.ini\");\n    unsigned int totalSounds;\n    sounds.beginGroup(\"sound-main\");\n    sounds.read(\"total\", totalSounds, 0);\n    sounds.endGroup();\n\n    if(totalSounds >= 29)\n    {\n        std::string p;\n        sounds.beginGroup(\"sound-29\");\n        sounds.read(\"file\", p, std::string());\n        sounds.endGroup();\n\n        if(!p.empty())\n        {\n            Mix_Music *loadsfx = Mix_LoadMUS((SfxRoot + p).c_str());\n            if(loadsfx)\n            {\n                if(Mix_PlayMusicStream(loadsfx, 0) < 0)\n                    Mix_FreeMusic(loadsfx);\n                else\n                    Mix_SetFreeOnStop(loadsfx, 1);\n            }\n        }\n    }\n}\n\nstatic void loadMusicIni(SoundScope root, const std::string &path, bool isLoadingCustom)\n{\n    IniProcessing musicSetup = Files::load_ini(path);\n    if(!isLoadingCustom)\n    {\n        music.clear();\n        g_totalMusicLevel = 0;\n        g_totalMusicWorld = 0;\n        g_totalMusicSpecial = 0;\n        musicSetup.beginGroup(\"music-main\");\n        musicSetup.read(\"total-level\", g_totalMusicLevel, 0);\n        musicSetup.read(\"total-world\", g_totalMusicWorld, 0);\n        musicSetup.read(\"total-special\", g_totalMusicSpecial, 0);\n        musicSetup.read(\"level-custom-music-id\", g_customLvlMusicId, 0);\n        musicSetup.read(\"world-custom-music-id\", g_customWldMusicId, 0);\n        musicSetup.endGroup();\n\n        if(LoadingInProcess)\n            UpdateLoad();\n    }\n\n    for(unsigned int i = 1; i <= g_totalMusicLevel; ++i)\n    {\n        std::string alias = fmt::format_ne(\"music{0}\", i);\n        std::string group = fmt::format_ne(\"level-music-{0}\", i);\n        AddMusic(root, musicSetup, alias, group, 52);\n    }\n\n    if(!isLoadingCustom && LoadingInProcess)\n        UpdateLoad();\n\n    for(unsigned int i = 1; i <= g_totalMusicWorld; ++i)\n    {\n        std::string alias = fmt::format_ne(\"wmusic{0}\", i);\n        std::string group = fmt::format_ne(\"world-music-{0}\", i);\n        AddMusic(root, musicSetup, alias, group, 64);\n    }\n\n    if(!isLoadingCustom && LoadingInProcess)\n        UpdateLoad();\n\n    for(unsigned int i = 1; i <= g_totalMusicSpecial; ++i)\n    {\n        std::string alias = fmt::format_ne(\"smusic{0}\", i);\n        if(i == 1)\n            alias = \"smusic\";\n        else if(i == 2)\n            alias = \"stmusic\";\n        else if(i == 3)\n            alias = \"tmusic\";\n        std::string group = fmt::format_ne(\"special-music-{0}\", i);\n        AddMusic(root, musicSetup, alias, group, 64);\n    }\n}\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\nstatic void readFx(IniProcessing &sounds, SectionEffect_t &s)\n{\n    const IniProcessing::StrEnumMap fxType =\n    {\n        {\"none\", SectionEffect_t::FX_None},\n        {\"reverb\", SectionEffect_t::FX_Reverb},\n        {\"echo\", SectionEffect_t::FX_Echo}\n    };\n\n    sounds.readEnum(\"fx\", s.fx, (int)SectionEffect_t::FX_None, fxType);\n    sounds.read(\"spc-echo-off\", s.disableSpcEcho, false);\n\n    switch(s.fx)\n    {\n    case SectionEffect_t::FX_Reverb:\n        sounds.read(\"mode\", s.rev.mode, 0.0f);\n        sounds.read(\"room-size\", s.rev.roomSize, 0.7f);\n        sounds.read(\"damping\", s.rev.damping, 0.5f);\n        sounds.read(\"wet-level\", s.rev.wetLevel, 0.2f);\n        sounds.read(\"dry-level\", s.rev.dryLevel, 0.4f);\n        sounds.read(\"width\", s.rev.width, 1.0f);\n        break;\n\n    case SectionEffect_t::FX_Echo:\n        sounds.read(\"echo-on\", s.echo.echoOn, 1);\n        sounds.read(\"delay\", s.echo.echoDelay, 6);\n        sounds.read(\"feedback\", s.echo.echoFeedBack, 30);\n        sounds.read(\"main-volume-left\", s.echo.echoMainVolL, 127);\n        sounds.read(\"main-volume-right\", s.echo.echoMainVolR, 127);\n        sounds.read(\"echo-volume-left\", s.echo.echoVolL, 28);\n        sounds.read(\"echo-volume-right\", s.echo.echoVolR, 28);\n        sounds.read(\"fir-0\", s.echo.echoFir[0], 99);\n        sounds.read(\"fir-1\", s.echo.echoFir[1], -52);\n        sounds.read(\"fir-2\", s.echo.echoFir[2], 32);\n        sounds.read(\"fir-3\", s.echo.echoFir[3], 50);\n        sounds.read(\"fir-4\", s.echo.echoFir[4], 25);\n        sounds.read(\"fir-5\", s.echo.echoFir[5], 51);\n        sounds.read(\"fir-6\", s.echo.echoFir[6], -35);\n        sounds.read(\"fir-7\", s.echo.echoFir[7], 56);\n        break;\n\n    default:\n    case SectionEffect_t::FX_None:\n        break;\n    }\n}\n#endif // THEXTECH_ENABLE_AUDIO_FX\n\nstatic void loadCustomSfxIni(SoundScope root, const std::string &path)\n{\n    IniProcessing sounds = Files::load_ini(path);\n    for(unsigned int i = 1; i <= g_totalSounds; ++i)\n    {\n        int alias = i;\n        std::string group = fmt::format_ne(\"sound-{0}\", i);\n        AddSfx(root, sounds, alias, group, true);\n    }\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\n    auto ch = sounds.childGroups();\n    for(const auto &g : ch)\n    {\n        if(g.find(\"fx-\") == 0)\n        {\n            sounds.beginGroup(g);\n            SectionEffect_t fx;\n            auto e = g;\n            e.erase(e.begin(), e.begin() + 3);\n            readFx(sounds, fx);\n            s_effectsList.insert({e, fx});\n            sounds.endGroup();\n        }\n    }\n\n    // FX settings\n    for(int i = 0; i <= maxSections; ++i)\n    {\n        auto &s = s_sectionEffect[i];\n        std::string cfx;\n\n        sounds.beginGroup(fmt::format_ne(\"section-fx-{0}\", i));\n        sounds.read(\"custom-fx\", cfx, std::string());\n        if(!cfx.empty() && s_effectsList.find(cfx) != s_effectsList.end())\n            s = s_effectsList[cfx];\n        else\n            readFx(sounds, s);\n        sounds.endGroup();\n    }\n#endif // THEXTECH_ENABLE_AUDIO_FX\n}\n\nstatic void restoreDefaultSfx()\n{\n    for(auto &s : sound)\n    {\n        auto &u = s.second;\n        RestoreSfx(u);\n    }\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\n    s_effectsList.clear();\n\n    for(int i = 0; i <= maxSections; ++i)\n    {\n        s_sectionEffect[i].fx = SectionEffect_t::FX_None;\n        s_sectionEffect[i].rev = SoundFXReverb();\n        s_sectionEffect[i].echo = SoundFXEchoSetup();\n    }\n    SoundFX_Clear();\n#endif\n}\n\nvoid InitSound()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    uint32_t start_time = SDL_GetTicks();\n\n    MusicRoot = AppPath + \"music/\";\n    SfxRoot = AppPath + \"sound/\";\n\n    musicIni = AppPath + \"music.ini\";\n    sfxIni = AppPath + \"sounds.ini\";\n\n\n    if(LoadingInProcess)\n    {\n        LoaderUpdateDebugString(\"Sound configs\");\n        UpdateLoad();\n    }\n\n    if(!Files::fileExists(musicIni) && !Files::fileExists(sfxIni))\n    {\n        pLogWarning(\"music.ini and sounds.ini are missing\");\n        XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_ERROR,\n                     \"music.ini and sounds.ini are missing\",\n                     \"Files music.ini and sounds.ini are not exist, game will work without default music and SFX.\");\n        g_customLvlMusicId = 24;\n        g_customWldMusicId = 17;\n        return;\n    }\n    else if(!Files::fileExists(musicIni))\n    {\n        pLogWarning(\"music.ini is missing\");\n        XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_ERROR,\n                     \"music.ini is missing\",\n                     \"File music.ini is not exist, game will work without default music.\");\n    }\n    else if(!Files::fileExists(sfxIni))\n    {\n        pLogWarning(\"sounds.ini is missing\");\n        XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_ERROR,\n                     \"sounds.ini is missing\",\n                     \"File sounds.ini is not exist, game will work without SFX.\");\n    }\n\n    loadMusicIni(SoundScope::global, musicIni, false);\n\n    if(LoadingInProcess)\n        UpdateLoad();\n    else\n        IndicateProgress(start_time, 0.01_n, \"\");\n\n    IniProcessing sounds = Files::load_ini(sfxIni);\n    sounds.beginGroup(\"sound-main\");\n    sounds.read(\"total\", g_totalSounds, 0);\n    sounds.read(\"use-iceball-sfx\", s_useIceBallSfx, false);\n    sounds.read(\"use-new-ice-sfx\", s_useNewIceSfx, false);\n    bool playerUseNPCHammer;\n    bool playerUseOwnHammer;\n    sounds.read(\"player-use-npc-hammer-sfx\", playerUseNPCHammer, false);\n    sounds.read(\"player-use-own-hammer-sfx\", playerUseOwnHammer, false);\n    sounds.endGroup();\n\n    if(playerUseOwnHammer)\n        playerHammerSFX = SFX_PlayerHeavy;\n    else if(playerUseNPCHammer)\n        playerHammerSFX = SFX_Throw;\n    else\n        playerHammerSFX = SFX_Fireball;\n\n    if(LoadingInProcess)\n        UpdateLoad();\n    else\n        IndicateProgress(start_time, 0.75_n / g_totalSounds, \"\");\n\n    for(unsigned int i = 1; i <= g_totalSounds; ++i)\n    {\n        int alias = i;\n        std::string group = fmt::format_ne(\"sound-{0}\", i);\n        AddSfx(SoundScope::global, sounds, alias, group);\n\n        if(!LoadingInProcess)\n            IndicateProgress(start_time, num_t(i) / g_totalSounds, \"\");\n#ifdef PGE_NO_THREADING\n        else\n            UpdateLoad();\n#endif\n\n        if(!GameIsActive)\n            return;\n    }\n\n    if(LoadingInProcess)\n    {\n        LoaderUpdateDebugString(\"All sounds loaded\");\n        UpdateLoad();\n    }\n\n    Mix_ReserveChannels(g_reservedChannels);\n\n    if(g_errorsSfx > 0 && GameIsActive)\n    {\n        XMsgBox::simpleMsgBox(XMsgBox::MESSAGEBOX_ERROR,\n                              \"Sounds loading error\",\n                              fmt::format_ne(\"Failed to load some SFX assets. Loo a log file to get more details:\\n{0}\", getLogFilePath()));\n        g_errorsSfx = 0;\n    }\n\n    // Print the stats of loaded sound files\n    int statSfxDump = 0;\n    int statSfxAsMusic = 0;\n\n    for(auto & it : sound)\n    {\n        auto &s = it.second;\n\n        if(s.music)\n            statSfxAsMusic++;\n\n        if(s.chunk)\n            statSfxDump++;\n    }\n\n    pLogInfo(\"Loaded sound effects: dumped=%d; as music=%d\", statSfxDump, statSfxAsMusic);\n}\n\nvoid UnloadSound()\n{\n    restoreDefaultSfx();\n\n    UnloadExtSounds();\n\n    if(g_curMusic)\n        Mix_FreeMusic(g_curMusic);\n\n    g_curMusic = nullptr;\n    g_reservedChannels = 0;\n\n    for(auto & it : sound)\n    {\n        auto &s = it.second;\n        clear_sfx(s);\n    }\n\n    sound.clear();\n    music.clear();\n}\n\nstatic const std::unordered_map<int, int> s_soundDelays =\n{\n    {2, 12}, {3, 12},  {4, 12},  {5, 30}, {8, 10},  {9, 4},\n    {10, 8}, {12, 10}, {17, 10}, {26, 8}, {31, 20}, {37, 10},\n    {42, 16},{50, 8},  {54, 8},  {71, 9}, {74, 8},  {81, 5},\n    {86, 8}, {SFX_Icebreak, 4}\n};\n\nstatic void s_resetSoundDelay(int A)\n{\n    // set the delay before a sound can be played again\n    auto i = s_soundDelays.find(A);\n    if(i == s_soundDelays.end())\n        SoundPause[A] = 4;\n    else\n        SoundPause[A] = i->second;\n\n#if 0 // Very old code, replaced with a more flexible thing at above\n    switch(A)\n    {\n    case 2: SoundPause[A] = 12; break;\n    case 3: SoundPause[A] = 12; break;\n    case 4: SoundPause[A] = 12; break;\n    case 5: SoundPause[A] = 30; break;\n    case 8: SoundPause[A] = 10; break;\n    case 9: SoundPause[A] = 4; break;\n    case 10: SoundPause[A] = 8; break;\n    case 12: SoundPause[A] = 10; break;\n    case 17: SoundPause[A] = 10; break;\n    case 26: SoundPause[A] = 8; break;\n    case 31: SoundPause[A] = 20; break;\n    case 37: SoundPause[A] = 10; break;\n    case 42: SoundPause[A] = 16; break;\n    case 50: SoundPause[A] = 8; break;\n    case 54: SoundPause[A] = 8; break;\n    case 71: SoundPause[A] = 9; break;\n    case 74: SoundPause[A] = 8; break;\n    case 81: SoundPause[A] = 5; break;\n    case 86: SoundPause[A] = 8; break;\n    default: SoundPause[A] = 4; break;\n    }\n#endif\n}\n\nstatic const std::unordered_map<int, int> s_soundFallback =\n{\n    {SFX_Iceball, SFX_Fireball},\n    {SFX_Freeze, SFX_ShellHit},\n    {SFX_Icebreak, SFX_ShellHit},\n    {SFX_SproutVine, SFX_ItemEmerge},\n    {SFX_FireBossKilled, SFX_SickBossKilled},\n    {SFX_HeroIce, SFX_HeroFire},\n    {SFX_HeroFireRod, SFX_HeroFire},\n    {SFX_FlameThrower, SFX_HeroFire},\n    {SFX_FlagExit, SFX_TapeExit},\n    {SFX_PlayerHeavy, SFX_Fireball},\n};\n\nstatic int getFallbackSfx(int A)\n{\n    auto fb = s_soundFallback.find(A);\n    if(fb != s_soundFallback.end())\n        A = fb->second;\n    return A;\n}\n\nvoid PlaySound(int A, int loops, int volume)\n{\n    if(!g_mixerLoaded)\n        return;\n\n    if(g_totalSounds == 0)\n    {\n        playFallbackSfx(A, loops, volume);\n        return;\n    }\n\n    if(GameMenu || GameOutro) // || A == 26 || A == 27 || A == 29)\n        return;\n\n    if(A > (int)g_totalSounds || !g_config.sfx_modern) // Play fallback sound for the missing SFX\n        A = getFallbackSfx(A);\n    else if(!s_useIceBallSfx && A == SFX_Iceball)\n        A = SFX_Fireball; // Fell back into fireball when iceball sound isn't preferred\n    else if(!s_useIceBallSfx && A == SFX_HeroIce)\n        A = SFX_HeroFire;\n    else if(!s_useNewIceSfx && (A == SFX_Freeze || A == SFX_Icebreak))\n        A = SFX_ShellHit; // Restore the old behavior\n\n    if(g_ClonedPlayerMode)\n        SoundPause[SFX_Skid] = 1;\n\n    if(SoundPause[A] == 0) // if the sound wasn't just played\n    {\n        int alias = A;\n        PlaySfx(alias, loops, volume);\n        s_resetSoundDelay(A);\n    }\n}\n\nvoid PlaySoundSpatial(int A, int l, int t, int r, int b, int loops, int volume)\n{\n    if(!g_mixerLoaded)\n        return;\n\n    if(GameMenu || GameOutro) // || A == 26 || A == 27 || A == 29)\n        return;\n\n    if(A > (int)g_totalSounds || !g_config.sfx_modern) // Play fallback sound for the missing SFX\n        A = getFallbackSfx(A);\n    else if(!s_useIceBallSfx && A == SFX_Iceball)\n        A = SFX_Fireball; // Fell back into fireball when iceball sound isn't preferred\n    else if(!s_useIceBallSfx && A == SFX_HeroIce)\n        A = SFX_HeroFire;\n    else if(!s_useNewIceSfx && (A == SFX_Freeze || A == SFX_Icebreak))\n        A = SFX_ShellHit; // Restore the old behavior\n\n    if(g_ClonedPlayerMode)\n        SoundPause[SFX_Skid] = 1;\n\n    if(SoundPause[A] == 0) // if the sound wasn't just played\n    {\n        uint8_t left = 255, right = 255;\n\n        if(g_config.sfx_spatial_audio)\n            Sound_ResolveSpatialMod(left, right, l, t, r, b);\n\n        int alias = A;\n        PlaySfx(alias, loops, volume, left, right);\n        s_resetSoundDelay(A);\n    }\n}\n\nbool HasSound(int A)\n{\n    return A <= (int)g_totalSounds;\n}\n\nvoid PlaySoundMenu(int A, int loops)\n{\n    if(SoundPause[A] > 0) // if the sound wasn't just played\n        return;\n\n    if(g_totalSounds == 0 || A > (int)g_totalSounds)\n    {\n        playFallbackSfx(A, loops, 128);\n        return;\n    }\n\n    int alias = A;\n    PlaySfx(alias, loops);\n    s_resetSoundDelay(A);\n}\n\n#if defined(THEXTECH_ASSERTS_INGAME_MESSAGE) && !defined(THEXTECH_NO_SDL_BUILD)\nvoid PlayErrorSound(int A, int loops)\n{\n    playFallbackSfx(A, loops, 128);\n}\n#endif\n\n// stops all sound from being played for 10 cycles\nvoid BlockSound()\n{\n    For(A, 1, numSounds)\n    {\n        SoundPause[A] = 10;\n    }\n}\n\nvoid UpdateSound()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    For(A, 1, numSounds)\n    {\n        if(SoundPause[A] > 0)\n            SoundPause[A] -= 1;\n    }\n}\n\nvoid LoadCustomSound()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    if(GameMenu || GameOutro)\n        return; // Don't load custom music in menu mode\n\n    // To avoid bugs like custom local sounds was transferred into another level, it's need to clean-up old one if that was\n    if(g_customMusicInDataFolder)\n    {\n        loadMusicIni(SoundScope::global, musicIni, true);\n        g_customMusicInDataFolder = false;\n    }\n\n    if(g_customSoundsInDataFolder)\n    {\n        restoreDefaultSfx();\n        g_customSoundsInDataFolder = false;\n    }\n\n    if(FileNamePath == AppPath)\n        return; // Don't treat default music/sounds ini as custom\n\n    std::string mIni = g_dirEpisode.resolveFileCaseExistsAbs(\"music.ini\");\n    if(!mIni.empty()) // Load music.ini from an episode folder\n        loadMusicIni(SoundScope::episode, mIni, true);\n\n    std::string mIniC = g_dirCustom.resolveFileCaseExistsAbs(\"music.ini\");\n    if(!mIniC.empty()) // Load music.ini from a level/world custom folder\n    {\n        loadMusicIni(SoundScope::custom, mIniC, true);\n        g_customMusicInDataFolder = true;\n    }\n\n    std::string sIni = g_dirEpisode.resolveFileCaseExistsAbs(\"sounds.ini\");\n    if(!sIni.empty()) // Load sounds.ini from an episode folder\n        loadCustomSfxIni(SoundScope::episode, sIni);\n\n    std::string sIniC = g_dirCustom.resolveFileCaseExistsAbs(\"sounds.ini\");\n    if(!sIniC.empty()) // Load sounds.ini from a level/world custom folder\n    {\n        loadCustomSfxIni(SoundScope::custom, sIniC);\n        g_customSoundsInDataFolder = true;\n    }\n}\n\nvoid UnloadCustomSound()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    loadMusicIni(SoundScope::global, musicIni, true);\n    restoreDefaultSfx();\n    g_customMusicInDataFolder = false;\n    g_customSoundsInDataFolder = false;\n\n    UnloadExtSounds();\n}\n\nvoid UpdateYoshiMusic()\n{\n    if(!s_musicHasYoshiMode || !g_mixerLoaded)\n        return;\n\n    bool hasYoshi = false;\n\n    if(!g_config.sfx_pet_beat)\n        hasYoshi = false;\n    else\n    {\n        for(int i = 1; i <= numPlayers; ++i)\n            hasYoshi |= (Player[i].Mount == 3);\n    }\n\n    Mix_SetMusicTrackMute(g_curMusic, s_musicYoshiTrackNumber, hasYoshi ? 0 : 1);\n}\n\nvoid PreloadExtSound(const std::string& path)\n{\n    if(!g_mixerLoaded)\n        return;\n\n    auto f = extSfx.find(path);\n    if(f == extSfx.end())\n    {\n        pLogDebug(\"Preloading custom sound [%s]...\", path.c_str());\n        SDL_RWops *f = Files::open_file(path, \"rb\");\n        if(!f)\n        {\n            pLogWarning(\"Custom sound preload: Can't acquire a file handle for [%s] (SDL Error: %s)\", path.c_str(), SDL_GetError());\n            return;\n        }\n\n        auto *ch = Mix_LoadWAV_RW(f, 1);\n        if(!ch)\n        {\n            pLogWarning(\"Custom sound preload: Can't load custom sound [%s]: %s\", path.c_str(), Mix_GetError());\n            return;\n        }\n        extSfx.insert({path, ch});\n    }\n}\n\nvoid UnloadExtSounds()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    SDL_AtomicSet(&extSfxBusy, 1);\n\n    for(auto &f : extSfx)\n        Mix_FreeChunk(f.second);\n\n    extSfx.clear();\n    extSfxPlaying.clear();\n\n    SDL_AtomicSet(&extSfxBusy, 0);\n}\n\nvoid PlayExtSound(const std::string &path, int loops, int volume)\n{\n    int play_ch = -1;\n\n    if(!g_mixerLoaded || (int)g_config.audio_sfx_volume == 0 || XMessage::GetStatus() == XMessage::Status::replay)\n        return;\n\n    auto f = extSfx.find(path);\n    if(f == extSfx.end())\n    {\n        auto *ch = Mix_LoadWAV(path.c_str());\n        if(!ch)\n        {\n            pLogWarning(\"Can't load custom sound %s: %s\", path.c_str(), Mix_GetError());\n            return;\n        }\n\n        extSfx.insert({path, ch});\n        play_ch = Mix_PlayChannelVol(-1, ch, loops, volume * g_config.audio_sfx_volume / 100);\n    }\n    else\n        play_ch = Mix_PlayChannelVol(-1, f->second, loops, volume * g_config.audio_sfx_volume / 100);\n\n    if(play_ch >= 0)\n    {\n        SDL_AtomicSet(&extSfxBusy, 1);\n        // Never re-use the same channel!\n        SDL_assert_release(extSfxPlaying.find(play_ch) == extSfxPlaying.end());\n        extSfxPlaying.insert({play_ch, path});\n        SDL_AtomicSet(&extSfxBusy, 0);\n    }\n    else\n        pLogWarning(\"Can't play custom sound %s: %s\", path.c_str(), Mix_GetError());\n}\n\n#ifndef THEXTECH_NO_SDL_BUILD\nstatic void extSfxStopCallback(int channel)\n{\n    if(SDL_AtomicGet(&extSfxBusy) == 1)\n        return; // Do nothing!\n\n    auto i = extSfxPlaying.find(channel);\n    if(i != extSfxPlaying.end())\n        extSfxPlaying.erase(i);\n}\n#endif\n\nvoid StopExtSound(const std::string& path)\n{\n    if(!g_mixerLoaded)\n        return;\n\n    SDL_AtomicSet(&extSfxBusy, 1);\n\n    for(auto i = extSfxPlaying.begin(); i != extSfxPlaying.end();)\n    {\n        if(i->second == path)\n        {\n            Mix_HaltChannel(i->first);\n            i = extSfxPlaying.erase(i);\n        }\n        else\n            ++i;\n    }\n\n    SDL_AtomicSet(&extSfxBusy, 0);\n}\n\nvoid StopAllExtSounds()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    SDL_AtomicSet(&extSfxBusy, 1);\n\n    for(auto i = extSfxPlaying.begin(); i != extSfxPlaying.end(); ++i)\n        Mix_HaltChannel(i->first);\n\n    extSfxPlaying.clear();\n\n    SDL_AtomicSet(&extSfxBusy, 0);\n}\n\nvoid StopAllSounds()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    SDL_AtomicSet(&extSfxBusy, 1);\n    Mix_HaltChannel(-1);\n    extSfxPlaying.clear();\n    SDL_AtomicSet(&extSfxBusy, 0);\n}\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\n\nstatic bool     enableEffectEcho = false;\nstatic SpcEcho *effectEcho = nullptr;\n\nstatic void echoEffectDone(int, void *context)\n{\n    SpcEcho *out = reinterpret_cast<SpcEcho *>(context);\n    if(out == effectEcho)\n    {\n        echoEffectFree(effectEcho);\n        effectEcho = nullptr;\n        enableEffectEcho = false;\n    }\n}\n\nvoid SoundFX_SetEcho(const SoundFXEchoSetup& setup)\n{\n    if(!g_mixerLoaded)\n        return;\n\n    bool isNew = false;\n\n    // Clear previously installed effects first\n    if(!effectEcho)\n    {\n        SoundFX_Clear();\n\n        effectEcho = echoEffectInit(g_config.audio_sample_rate.obtained,\n                                    g_config.audio_format.obtained,\n                                    g_config.audio_channels.obtained);\n        isNew = true;\n    }\n\n    if(effectEcho)\n    {\n        echoEffectSetReg(effectEcho, ECHO_EON, setup.echoOn);\n        echoEffectSetReg(effectEcho, ECHO_EDL, setup.echoDelay);\n        echoEffectSetReg(effectEcho, ECHO_EFB, setup.echoFeedBack);\n\n        echoEffectSetReg(effectEcho, ECHO_MVOLL, setup.echoMainVolL);\n        echoEffectSetReg(effectEcho, ECHO_MVOLR, setup.echoMainVolR);\n        echoEffectSetReg(effectEcho, ECHO_EVOLL, setup.echoVolL);\n        echoEffectSetReg(effectEcho, ECHO_EVOLR, setup.echoVolR);\n\n        echoEffectSetReg(effectEcho, ECHO_FIR0, setup.echoFir[0]);\n        echoEffectSetReg(effectEcho, ECHO_FIR1, setup.echoFir[1]);\n        echoEffectSetReg(effectEcho, ECHO_FIR2, setup.echoFir[2]);\n        echoEffectSetReg(effectEcho, ECHO_FIR3, setup.echoFir[3]);\n        echoEffectSetReg(effectEcho, ECHO_FIR4, setup.echoFir[4]);\n        echoEffectSetReg(effectEcho, ECHO_FIR5, setup.echoFir[5]);\n        echoEffectSetReg(effectEcho, ECHO_FIR6, setup.echoFir[6]);\n        echoEffectSetReg(effectEcho, ECHO_FIR7, setup.echoFir[7]);\n        if(isNew)\n            Mix_RegisterEffect(MIX_CHANNEL_POST, spcEchoEffect, echoEffectDone, effectEcho);\n        enableEffectEcho = true;\n    }\n}\n\nstatic bool enableEffectReverb = false;\nstatic FxReverb *effectReverb = nullptr;\n\nstatic void reverbEffectDone(int, void *context)\n{\n    FxReverb *out = reinterpret_cast<FxReverb *>(context);\n    if(out == effectReverb)\n    {\n        reverbEffectFree(effectReverb);\n        effectReverb = nullptr;\n        enableEffectReverb = false;\n    }\n}\n\nvoid SoundFX_SetReverb(const SoundFXReverb& setup)\n{\n    if(!g_mixerLoaded)\n        return;\n\n    bool isNew = false;\n\n    if(!effectReverb)\n    {\n        // Clear previously installed effects first\n        SoundFX_Clear();\n\n        effectReverb = reverbEffectInit(g_config.audio_sample_rate.obtained,\n                                        g_config.audio_format.obtained,\n                                        g_config.audio_channels.obtained);\n        isNew = true;\n    }\n\n    if(effectReverb)\n    {\n        ReverbSetup set;\n        set.mode = setup.mode;\n        set.roomSize = setup.roomSize;\n        set.damping = setup.damping;\n        set.wetLevel = setup.wetLevel;\n        set.dryLevel = setup.dryLevel;\n        set.width = setup.width;\n        reverbUpdateSetup(effectReverb, set);\n        if(isNew)\n            Mix_RegisterEffect(MIX_CHANNEL_POST, reverbEffect, reverbEffectDone, effectReverb);\n        enableEffectReverb = true;\n    }\n}\n\nvoid SoundFX_Clear()\n{\n    if(!g_mixerLoaded)\n        return;\n\n    if(effectEcho)\n    {\n        Mix_UnregisterEffect(MIX_CHANNEL_POST, spcEchoEffect);\n        if(effectEcho)\n        {\n            echoEffectFree(effectEcho);\n            effectEcho = nullptr;\n        }\n        enableEffectEcho = false;\n    }\n\n    if(effectReverb)\n    {\n        Mix_UnregisterEffect(MIX_CHANNEL_POST, reverbEffect);\n        if(effectReverb)\n        {\n            reverbEffectFree(effectReverb);\n            effectReverb = nullptr;\n        }\n        enableEffectReverb = false;\n    }\n}\n\n#endif // THEXTECH_ENABLE_AUDIO_FX\n\n\nvoid ResetSoundFX()\n{\n#ifdef THEXTECH_ENABLE_AUDIO_FX\n    SoundFX_Clear();\n    s_musicDisableSpcEcho = false;\n#endif\n}\n\nvoid UpdateSoundFX(int recentSection)\n{\n#ifndef THEXTECH_ENABLE_AUDIO_FX\n    UNUSED(recentSection);\n#else\n    if(!g_mixerLoaded || LevelSelect)\n        return;\n\n    SDL_assert_release(recentSection >= 0 && recentSection <= maxSections);\n    const auto &s = g_config.sfx_audio_fx ? s_sectionEffect[recentSection] : SectionEffect_t();\n\n    s_musicDisableSpcEcho = s.disableSpcEcho;\n    if(g_curMusic)\n        Mix_GME_SetSpcEchoDisabled(g_curMusic, s.disableSpcEcho);\n\n    switch(s.fx)\n    {\n    default:\n    case SectionEffect_t::FX_None:\n        SoundFX_Clear();\n        break;\n\n    case SectionEffect_t::FX_Echo:\n        SoundFX_SetEcho(s.echo);\n        break;\n\n    case SectionEffect_t::FX_Reverb:\n        SoundFX_SetReverb(s.rev);\n        break;\n    }\n#endif // THEXTECH_ENABLE_AUDIO_FX\n}\n"
  },
  {
    "path": "src/sound.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SOUND_H\n#define SOUND_H\n\n#include <string>\n#include <cstdint>\n\n#include \"location.h\"\n\n// Public musicPlaying As Boolean\nextern bool musicPlaying;\n// Public musicLoop As Integer\nextern int musicLoop;\n// Public musicName As String\nextern std::string musicName;\n\nextern int playerHammerSFX;\n\nextern const struct AudioDefaults_t\n{\n    int sampleRate;\n    int channels;\n    int bufferSize;\n    uint16_t format;\n} g_audioDefaults;\n\nstruct AudioSetup_t\n{\n    bool disableSound = false;\n    int sampleRate = 44100;\n    int channels = 2;\n    int bufferSize = 512;\n    uint16_t format = 0x8120;\n};\n\n\nenum\n{\n    SFX_Jump            = 1,\n    SFX_Stomp,\n    SFX_BlockHit,\n    SFX_BlockSmashed,\n    SFX_PlayerShrink,\n    SFX_PlayerGrow,\n    SFX_ItemEmerge,\n    SFX_PlayerDied,\n    SFX_ShellHit,\n    SFX_Skid            = 10,\n    SFX_DropItem,\n    SFX_GotItem,\n    SFX_Camera,\n    SFX_Coin,\n    SFX_1up,\n    SFX_Lava,\n    SFX_Warp,\n    SFX_Fireball,\n    SFX_CardRouletteClear,\n    SFX_BossBeat        = 20,\n    SFX_DungeonClear,\n    SFX_Bullet,\n    SFX_Grab,\n    SFX_Spring,\n    SFX_HeavyToss,\n    SFX_Slide,\n    SFX_NewPath,\n    SFX_LevelSelect,\n    SFX_Do,\n    SFX_Pause           = 30,\n    SFX_Key,\n    SFX_PSwitch,\n    SFX_Whip,\n    SFX_Transform,\n    SFX_Boot,\n    SFX_Smash,\n    SFX_Stone,\n    SFX_SpitBossSpit,\n    SFX_SpitBossHit,\n    SFX_CrystalBallExit = 40,\n    SFX_SpitBossBeat,\n    SFX_BigFireball,\n    SFX_Fireworks,\n    SFX_VillainKilled,\n    SFX_GameBeat,\n    SFX_Door,\n    SFX_Message,\n    SFX_Pet,\n    SFX_PetHurt,\n    SFX_PetTongue       = 50,\n    SFX_PetBirth,\n    SFX_GotStar,\n    SFX_HeroKill,\n    SFX_PlayerDied2,\n    SFX_PetSwallow,\n    SFX_RingGet,\n    SFX_Skeleton,\n    SFX_Checkpoint,\n    SFX_MedalGet,\n    SFX_TapeExit        = 60,\n    SFX_LavaMonster,\n    SFX_SickBossSpit,\n    SFX_SickBossKilled,\n    SFX_SMBlockHit,\n    SFX_SMKilled,\n    SFX_SMHurt,\n    SFX_SMGlass,\n    SFX_SMBossHit,\n    SFX_SMCry,\n    SFX_SMExplosion     = 70,\n    SFX_Climbing,\n    SFX_Swim,\n    SFX_Grab2,\n    SFX_Saw,\n    SFX_Throw,\n    SFX_PlayerHit,\n    SFX_HeroStab,\n    SFX_HeroHurt,\n    SFX_HeroHeart,\n    SFX_HeroDied        = 80,\n    SFX_HeroRupee,\n    SFX_HeroFire,\n    SFX_HeroItem,\n    SFX_HeroKey,\n    SFX_HeroShield,\n    SFX_HeroDash,\n    SFX_HeroFairy,\n    SFX_HeroGrass,\n    SFX_HeroHit,\n    SFX_HeroSwordBeam   = 90,\n    SFX_Bubble,\n\n    // non-SMBX64, Extra\n    SFX_CoinSwitchTimeout,\n    SFX_BatFlap,\n    SFX_Iceball,\n    SFX_Freeze,\n    SFX_Icebreak,\n    SFX_PlayerHeavy, // Reserved\n    SFX_SproutVine,\n    SFX_MagicBossShell,\n    SFX_MagicBossKilled = 100,\n    SFX_FireBossKilled,\n    SFX_HeroIce,\n    SFX_HeroFireRod,\n    SFX_FlameThrower,\n    SFX_FlagExit,\n};\n\nint CustomWorldMusicId();\n\nvoid InitSoundDefaults();\n\n// Public Sub InitMixerX()\nvoid InitMixerX();\n// Public Sub RestartMixerX()\nvoid RestartMixerX();\n// Public Sub QuitMixerX()\nvoid QuitMixerX();\n// Public Sub SetMusicVolume(Alias As String, Volume As Long)\nvoid SetMusicVolume(const std::string &Alias, long Volume);\n// Public Sub SoundPauseAll()\nvoid SoundPauseAll();\n// Public Sub SoundResumeAll()\nvoid SoundResumeAll();\nvoid SoundPauseEngine(int paused);\n// Public Sub PlayMusic(Alias As String)\nvoid PlayMusic(const std::string &Alias, int fadeInMs = 0);\n// Public Sub PlaySfx(Alias As String)\nvoid PlaySfx_Blocking(int Alias, int loops = 0, int volume = 128, uint8_t left = 255, uint8_t right = 255);\n// Public Sub StopSfx(Alias As String)\nvoid StopSfx(int Alias);\n// Public Sub StartMusic(A As Integer) 'play music\nvoid setMusicStartDelay();\nvoid delayedMusicStart();\nvoid delayedMusicReset();\nbool delayMusicIsSet();\n// play music\nvoid StartMusic(int A, int fadeInMs = 0);\nvoid StartMusicIfOnscreen(int section);\n// Public Sub StopMusic() 'stop playing music\nvoid PauseMusic();\nvoid ResumeMusic();\n// stop playing music\nvoid StopMusic();\n// Public Sub PlayInitSound()\nvoid PlayInitSound();\n// Public Sub InitSound() 'readys sound and music to be played\n// readys sound and music to be played\nvoid InitSound();\n// unloads all sounds for asset pack switch\nvoid UnloadSound();\n// Public Sub PlaySound(A As Integer) 'play a sound\n// play a sound\nvoid PlaySound(int A, int loops = 0, int volume = 128);\n\n// NEW: play a sound with spatial awareness\nvoid PlaySoundSpatial(int A, int l, int t, int r, int b, int loops = 0, int volume = 128);\n\n// public signatures for spatial sound feature\ninline void PlaySoundSpatial(int A, const Location_t& loc, int loops = 0, int volume = 128)\n{\n    PlaySoundSpatial(A, (int)loc.X, (int)loc.Y, (int)(loc.X + loc.Width), (int)(loc.Y + loc.Height), loops, volume);\n}\n\ninline void PlaySoundSpatial(int A, const SpeedlessLocation_t& loc, int loops = 0, int volume = 128)\n{\n    PlaySoundSpatial(A, (int)loc.X, (int)loc.Y, (int)(loc.X + loc.Width), (int)(loc.Y + loc.Height), loops, volume);\n}\n\ninline void PlaySoundSpatial(int A, int x, int y, int loops = 0, int volume = 128)\n{\n    PlaySoundSpatial(A, x, y, x, y, loops, volume);\n}\n\n// NEW: internal function to determine the panning for a sound (defined in sound_spatial.cpp)\nvoid Sound_ResolveSpatialMod(uint8_t& left, uint8_t& right, int l, int t, int r, int b);\n\n// Check does sound is defined at sounds.ini\nbool HasSound(int A);\nvoid PlaySoundMenu(int A, int loops = 0);\n\n#if defined(THEXTECH_ASSERTS_INGAME_MESSAGE) && !defined(THEXTECH_NO_SDL_BUILD)\n// always uses fallback sound\nvoid PlayErrorSound(int A, int loops = 0);\n#endif\n\n// Public Sub BlockSound() 'stops all sound from being played for 10 cycles\n// stops all sound from being played for 10 cycles\nvoid BlockSound();\n// Public Sub UpdateSound() 'checks to loop music and update the soundpause variable\n// checks to loop music and update the soundpause variable\nvoid UpdateSound();\n\nvoid UpdateYoshiMusic();\n// EXTRA: Fade out music\nvoid FadeOutMusic(int ms);\n// EXTRA: load custom sounds.ini and music.ini from episode and custom folder!\nvoid LoadCustomSound();\n// EXTRA: Unload custom-loaded music and sounds, and restore originals\nvoid UnloadCustomSound();\n\nvoid UpdateMusicVolume();\n\nvoid PreloadExtSound(const std::string &path);\nvoid UnloadExtSounds();\nvoid PlayExtSound(const std::string &path, int loops = 0, int volume = 128);\nvoid StopExtSound(const std::string &path);\nvoid StopAllExtSounds();\nvoid StopAllSounds();\n\n#ifdef THEXTECH_ENABLE_AUDIO_FX\nstruct SoundFXEchoSetup\n{\n    int echoOn = 0;\n    int echoDelay = 0;\n    int echoFeedBack = 0;\n\n    int echoMainVolL = 0;\n    int echoMainVolR = 0;\n\n    int echoVolL = 0;\n    int echoVolR = 0;\n\n    int echoFir[8] = {0, 0, 0, 0, 0, 0, 0, 0};\n};\nvoid SoundFX_SetEcho(const SoundFXEchoSetup &setup);\n\nstruct SoundFXReverb\n{\n    float mode         = 0.0f; // Normal (0) or Freeze (>0.5)\n    float roomSize     = 0.7f;\n    float damping      = 0.5f; // 0.0...1.0\n    float wetLevel     = 0.2f;\n    float dryLevel     = 0.4f;\n    float width        = 1.0f; // 0.0...1.0\n};\nvoid SoundFX_SetReverb(const SoundFXReverb &setup);\n\nvoid SoundFX_Clear();\n#endif // THEXTECH_ENABLE_AUDIO_FX\n\nvoid ResetSoundFX();\n\nvoid UpdateSoundFX(int recentSection);\n\n#endif // SOUND_H\n"
  },
  {
    "path": "src/sound_spatial.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"screen.h\"\n#include \"sound.h\"\n\nvoid Sound_ResolveSpatialMod(uint8_t& left, uint8_t& right, int l, int t, int r, int b)\n{\n    const Screen_t& screen = *l_screen;\n\n    left = 0;\n    right = 0;\n\n    for(int vscreen_i = screen.active_begin(); vscreen_i != screen.active_end(); vscreen_i++)\n    {\n        const vScreen_t& vscreen = vScreen[screen.vScreen_refs[vscreen_i]];\n\n        int vscreen_l = -(int)vscreen.X;\n        int vscreen_r = vscreen_l + vscreen.Width;\n        int vscreen_c = vscreen_l + vscreen.Width / 2;\n        int vscreen_t = -(int)vscreen.Y;\n        int vscreen_b = vscreen_t + vscreen.Height;\n\n        int l_dist = (r < vscreen_l) ? vscreen_l - r\n                   : (l > vscreen_c) ? l - vscreen_c\n                   : 0;\n\n        int r_dist = (r < vscreen_c) ? vscreen_c - r\n                   : (l > vscreen_r) ? l - vscreen_r\n                   : 0;\n\n        int x_dist = (r < vscreen_l) ? vscreen_l - r\n                   : (l > vscreen_r) ? l - vscreen_r\n                   : 0;\n\n        int y_dist = (b < vscreen_t) ? vscreen_t - b\n                   : (t > vscreen_b) ? t - vscreen_b\n                   : 0;\n\n        // decay constant: a sound 200px away should be 10dB quieter\n        int64_t x_num = 200 * 200;\n        int64_t y_num = 200 * 200;\n\n        if(x_num <= 0)\n            x_num = 1;\n        if(y_num <= 0)\n            y_num = 1;\n\n        int64_t l_div = l_dist * l_dist;\n        int64_t r_div = r_dist * r_dist;\n\n        int64_t c_div = x_dist * x_dist;\n        int64_t y_div = y_dist * y_dist;\n\n        int l_calc = 127 * (x_num + y_num) / (l_div + y_div + x_num + y_num);\n        int r_calc = 127 * (x_num + y_num) / (r_div + y_div + x_num + y_num);\n\n        int c_calc = 128 * (x_num + y_num) / (c_div + y_div + x_num + y_num);\n\n        l_calc = l_calc + c_calc;\n        r_calc = r_calc + c_calc;\n\n        if(l_calc > 255)\n            l_calc = 255;\n        if(r_calc > 255)\n            r_calc = 255;\n\n        if(vscreen.ScreenLeft >= screen.W / 2)\n            l_calc = (l_calc * 3) / 4;\n        else if(vscreen.ScreenLeft + vscreen.Width <= screen.W / 2)\n            r_calc = (r_calc * 3) / 4;\n\n        if(l_calc > (int)left)\n            left = l_calc;\n        if(r_calc > (int)right)\n            right = r_calc;\n    }\n}\n"
  },
  {
    "path": "src/sound_thread.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <vector>\n#include <cstdint>\n#include <exception>\n\n#include <SDL2/SDL_thread.h>\n#include <SDL2/SDL_mutex.h>\n\n#include \"sound.h\"\n#include \"config.h\"\n#include \"message.h\"\n#include \"sound_thread.h\"\n\nstruct EnqueuedSfx_t\n{\n    int Alias;\n    uint8_t loops;\n    uint8_t volume;\n    uint8_t left;\n    uint8_t right;\n};\n\nstatic std::vector<EnqueuedSfx_t> s_sfx_queue;\n\nstatic SDL_Thread* s_sound_thread = nullptr;\nstatic SDL_mutex*  s_sound_thread_mutex = nullptr;\nstatic SDL_cond*   s_sound_thread_cond = nullptr;\n\nstatic bool        s_sound_thread_quit = false;\n\nstatic int s_sound_thread_main(void*)\n{\n    static std::vector<EnqueuedSfx_t> s_sfx_queue_pop;\n    s_sfx_queue_pop.clear();\n\n    while(true)\n    {\n        // load state from the main thread\n        SDL_LockMutex(s_sound_thread_mutex);\n\n        if(!s_sound_thread_quit && s_sfx_queue.empty())\n            SDL_CondWait(s_sound_thread_cond, s_sound_thread_mutex);\n\n        bool quit = s_sound_thread_quit;\n        std::swap(s_sfx_queue, s_sfx_queue_pop);\n\n        SDL_UnlockMutex(s_sound_thread_mutex);\n\n        if(quit)\n            break;\n\n        // execute SFX queue\n        for(const EnqueuedSfx_t& sfx : s_sfx_queue_pop)\n            PlaySfx_Blocking(sfx.Alias, sfx.loops, sfx.volume, sfx.left, sfx.right);\n\n        s_sfx_queue_pop.clear();\n    }\n\n    return 0;\n}\n\nvoid PlaySfx(int Alias, int loops, int volume, uint8_t left, uint8_t right)\n{\n    if((int)g_config.audio_sfx_volume == 0 || XMessage::GetStatus() == XMessage::Status::replay)\n        return;\n\n    if(!s_sound_thread)\n    {\n        PlaySfx_Blocking(Alias, loops, volume, left, right);\n        return;\n    }\n\n    SDL_LockMutex(s_sound_thread_mutex);\n\n    try\n    {\n        s_sfx_queue.push_back(EnqueuedSfx_t{Alias, (uint8_t)loops, (uint8_t)volume, left, right});\n    }\n    catch(...)\n    {\n        SDL_UnlockMutex(s_sound_thread_mutex);\n\n        std::exception_ptr e = std::current_exception();\n\n        if(e)\n            std::rethrow_exception(e);\n    }\n\n    SDL_UnlockMutex(s_sound_thread_mutex);\n    SDL_CondSignal(s_sound_thread_cond);\n}\n\nvoid StartSfxThread()\n{\n    EndSfxThread();\n\n    s_sound_thread_mutex = SDL_CreateMutex();\n    if(!s_sound_thread_mutex)\n        return;\n\n    s_sound_thread_cond = SDL_CreateCond();\n    if(!s_sound_thread_cond)\n        return;\n\n    s_sound_thread = SDL_CreateThread(s_sound_thread_main, \"SFX thread\", nullptr);\n}\n\nvoid EndSfxThread()\n{\n    if(s_sound_thread)\n    {\n        SDL_LockMutex(s_sound_thread_mutex);\n        s_sound_thread_quit = true;\n        SDL_UnlockMutex(s_sound_thread_mutex);\n        SDL_CondSignal(s_sound_thread_cond);\n\n        SDL_WaitThread(s_sound_thread, nullptr);\n        s_sound_thread = nullptr;\n        s_sound_thread_quit = false;\n    }\n\n    if(s_sound_thread_cond)\n    {\n        SDL_DestroyCond(s_sound_thread_cond);\n        s_sound_thread_cond = nullptr;\n    }\n\n    if(s_sound_thread_mutex)\n    {\n        SDL_DestroyMutex(s_sound_thread_mutex);\n        s_sound_thread_mutex = nullptr;\n    }\n}\n"
  },
  {
    "path": "src/sound_thread.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef SOUND_THREAD_H\n#define SOUND_THREAD_H\n\n#include <stdint.h>\n\n#ifndef THEXTECH_NO_SDL_BUILD\n\n/**\n * @brief Enqueues the SFX but does not block\n * @param Alias The alias of the sound\n * @param loops Number loops to play (n-1 value. When -1 - loop forever)\n * @param volume The volume level between 0 and 128\n * @param left The panning volume of left channel between 0 and 255\n * @param right The panning volume of right channel between 0 and 255\n */\nvoid PlaySfx(int Alias, int loops = 0, int volume = 128, uint8_t left = 255, uint8_t right = 255);\n\n/**\n * @brief Starts sound thread\n */\nvoid StartSfxThread();\n\n/**\n * @brief ends sound thread\n */\nvoid EndSfxThread();\n\n#else\n\n// fallback: just call directly\n#define PlaySfx PlaySfx_Blocking\n\nstatic inline void StartSfxThread() {}\nstatic inline void EndSfxThread() {}\n\n#endif\n\n#endif // #ifndef SOUND_THREAD_H\n"
  },
  {
    "path": "src/std_picture.cpp",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"std_picture.h\"\n#include \"core/render.h\"\n\nStdPicture::~StdPicture()\n{\n    if(d.hasTexture())\n        XRender::unloadTexture(*this);\n}\n\nvoid StdPicture::reset()\n{\n    if(d.hasTexture())\n        XRender::unloadTexture(*this);\n\n    static_cast<StdPicture_Sub&>(*this) = StdPicture_Sub();\n}\n"
  },
  {
    "path": "src/std_picture.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef STD_PICTURE_H\n#define STD_PICTURE_H\n\n#include \"xt_color.h\"\n\n#include \"core/picture_data.h\"\n#include \"core/picture_load.h\"\n\n#ifdef DEBUG_BUILD\n#   include <string>\n#   define STD_PICTURE_HAS_ORIG_PATH\n#endif\n\n\nstruct SDL_Texture;\n\n/**\n * @brief Handler of a graphical texture, excluding renderer-specific data\n */\nstruct StdPicture_Sub\n{\n#ifdef STD_PICTURE_HAS_ORIG_PATH\n    //! Debug-only file path to the original picture\n    std::string origPath;\n#endif\n\n    //! Was this texture initialized by graphical engine or not?\n    bool inited = false;\n\n    //! Width of texture\n    int w = 0;\n    //! Height of texture\n    int h = 0;\n\n    // [UNUSED] Frame width and height (for animation sprite textures)\n    //! Animation frame width\n    // int frame_w = 0;\n    //1 Animation frame height\n    // int frame_h = 0;\n\n    // These colors were used to auto-choose the fill color for the background\n    //! Left-top pixel color\n    XTColor ColorUpper;\n    //! Left-bottom pixel color\n    XTColor ColorLower;\n\n    /*!\n     * \\brief Reset colors into black\n     */\n    inline void resetColors()\n    {\n        ColorUpper.r = 0;\n        ColorUpper.g = 0;\n        ColorUpper.b = 0;\n        ColorUpper.a = 255;\n\n        ColorLower.r = 0;\n        ColorLower.g = 0;\n        ColorLower.b = 0;\n        ColorLower.a = 255;\n    }\n\n\n    /* Loader related stuff */\n\n    //! Loader-related data\n    StdPictureLoad l;\n\n};\n\n\n/**\n * @brief Handler of a graphical texture, including renderer-specific data\n */\nstruct StdPicture : public StdPicture_Sub\n{\n    StdPicture() = default;\n\n    //! Platform specific texture data\n    StdPictureData d;\n\n    /*!\n     * \\brief Prevent any assignment of textures to preserve renderer references to loaded textures\n     */\n    StdPicture& operator=(const StdPicture& o) = delete;\n    StdPicture(const StdPicture& o) = delete;\n\n    /*!\n     * \\brief reset operation unloads StdPictureData and also resets the StdPicture_Sub state\n     */\n    void reset();\n\n    /*!\n     * \\brief Explicit destructor ensures that renderer unloads StdPictureData\n     */\n    ~StdPicture();\n};\n\n// This macro allows to get the original texture path when debug build is on,\n// and safely return the blank string when the release build is\n#ifdef STD_PICTURE_HAS_ORIG_PATH\n#   define StdPictureGetOrigPath(x) x.origPath\n#else\n#   define StdPictureGetOrigPath(x) std::string()\n#endif\n\n\n#endif // STD_PICTURE_H\n"
  },
  {
    "path": "src/video.h",
    "content": ""
  },
  {
    "path": "src/xt_color.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#pragma once\n#ifndef XT_COLOR_H\n#define XT_COLOR_H\n\n#include <cstdint>\n#include <string>\n\n#include \"numeric_types.h\"\n\n/*!\n * \\brief RGBA pixel color\n */\nstruct alignas(uint32_t) XTColor\n{\n    uint8_t r = 255;\n    uint8_t g = 255;\n    uint8_t b = 255;\n    uint8_t a = 255;\n\n    // helper functions\n    static inline constexpr uint8_t mul(uint8_t a, uint8_t b)\n    {\n        return uint8_t((uint16_t(a) * uint16_t(b)) >> 8);\n    }\n\n    static inline constexpr uint8_t from_num(num_t f)\n    {\n        return static_cast<uint8_t>(f * 255);\n    }\n\n    // initializers\n    inline constexpr XTColor() {}\n    inline constexpr XTColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) : r(r), g(g), b(b), a(a) {}\n    inline constexpr XTColor(XTColor o, uint8_t alpha) : r(o.r), g(o.g), b(o.b), a(alpha) {}\n\n    // multiply operator\n    inline constexpr XTColor operator*(XTColor o) const\n    {\n        return XTColor(mul(r, o.r), mul(g, o.g), mul(b, o.b), mul(a, o.a));\n    }\n\n    // intensity scale\n    inline constexpr XTColor operator*(num_t o) const\n    {\n        return XTColor(uint8_t(r * o), uint8_t(g * o), uint8_t(b * o), a);\n    }\n\n    inline constexpr XTColor operator*(uint8_t o) const\n    {\n        return XTColor(mul(r, o), mul(g, o), mul(b, o), a);\n    }\n\n    inline constexpr XTColor operator*(float o) const = delete;\n\n    // (in)equality operators\n    inline constexpr bool operator==(XTColor o) const\n    {\n        return r == o.r && g == o.g && b == o.b && a == o.a;\n    }\n\n    inline constexpr bool operator!=(XTColor o) const\n    {\n        return !(*this == o);\n    }\n\n    // alpha change operators (return new colors)\n    inline constexpr XTColor with_alpha(uint8_t new_a) const\n    {\n        return XTColor(r, g, b, new_a);\n    }\n\n    inline constexpr XTColor with_alphaF(num_t new_a) const\n    {\n        return XTColor(r, g, b, from_num(new_a));\n    }\n};\n\nstatic inline constexpr XTColor XTColorF(num_t r, num_t g, num_t b, num_t a = 1)\n{\n    return XTColor(XTColor::from_num(r),\n        XTColor::from_num(g),\n        XTColor::from_num(b),\n        XTColor::from_num(a));\n}\n\nstatic inline constexpr XTColor XTAlpha(uint8_t a)\n{\n    return XTColor().with_alpha(a);\n}\n\nstatic inline constexpr XTColor XTAlphaF(num_t a)\n{\n    return XTColor().with_alpha(XTColor::from_num(a));\n}\n\n/**\n * \\brief converts a color string into an XTColor instance\n *\n * \\param s string reference\n *\n * Currently supports hex strings only. Defined in globals.cpp.\n **/\nXTColor XTColorString(const std::string& s);\n\n#endif\n"
  },
  {
    "path": "test/CI-tests.py",
    "content": "#!/usr/bin/python3\n\n#\n#  TheXTech - A platform game engine ported from old source code for VB6\n#\n#  Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n#  Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n#\n#  This program is free software: you can redistribute it and/or modify\n#  it under the terms of the GNU General Public License as published by\n#  the Free Software Foundation, either version 3 of the License, or\n#  any later version.\n#\n#  This program is distributed in the hope that it will be useful,\n#  but WITHOUT ANY WARRANTY; without even the implied warranty of\n#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#  GNU General Public License for more details.\n#\n#  You should have received a copy of the GNU General Public License\n#  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n\n# This script is a WIP way to measure the game's compatibility and performance on recordings made with SMBX-R.\n# It will be used in the future to automate compatibility and performance testing for TheXTech.\n\nimport argparse\nimport subprocess\nimport json\nimport sys\nimport os\n\nparser = argparse.ArgumentParser(\n                    prog='CI-tests.py',\n                    description='This script is a WIP way to measure the game\\'s compatibility and performance on using gameplay recordings.')\n\nparser.add_argument('-n', '--name', help='name to record this test with', required=True)\nparser.add_argument('-e', '--executable', help='path to the game executable (should generally be a command-line build)', required=True)\nparser.add_argument('-d', '--records-dir', help='directory of gameplay records to use', required=True)\nparser.add_argument('-o', '--output', help='output file to append a CSV row to')\nargs = parser.parse_args()\n\ntitle = args.name\nbinary = args.executable\ntest_dir = args.records_dir\n\nbenches = [os.path.join(test_dir, test) for test in os.listdir(test_dir) if test.endswith('.rec')]\n\nsections = subprocess.check_output(['size', binary]).decode().strip().split('\\n')[1].split()\ntext = int(sections[0]) // 1024\nstatic_ram = (int(sections[1]) + int(sections[2])) // 1024\n\nmax_heap = 0\ntotal_instructions = 0\ntotal_cycles = 0\n\ntotal_pass = 0\ntotal_warn = 0\ntotal_fail = 0\ntotal_invalid = 0\n\nprint(f'Code size {text} KB, static RAM use {static_ram} KB')\n\nfor bench in benches:\n    print(f'On {bench}...')\n    output_lines = subprocess.check_output(['/usr/bin/time', '-f', '%M', 'perf', 'stat', '-j', binary, bench], stderr=subprocess.STDOUT).decode().strip().split('\\n')\n\n    # 1-2 lines to indicate logs, then results\n    if output_lines[1].strip() == '':\n        start = 2\n    else:\n        start = 1\n\n    test_result = output_lines[start]\n    mem_peak = int(output_lines[-1])\n    stack_heap = mem_peak - (text + static_ram)\n\n    if stack_heap > max_heap:\n        max_heap = stack_heap\n\n\n    for line in output_lines[start + 1:-1]:\n        line_out = json.loads(line)\n\n        if not 'event' in line_out:\n            continue\n\n        if line_out['event'] == 'cycles:u':\n            cycles = int(float(line_out['counter-value']))\n        elif line_out['event'] == 'instructions:u':\n            instructions = int(float(line_out['counter-value']))\n\n    total_instructions += instructions\n    total_cycles += cycles\n\n    if 'CONGRATULATIONS' in test_result:\n        total_pass += 1\n    elif 'MINOR' in test_result:\n        total_warn += 1\n    elif 'DIVERGED' in test_result:\n        total_fail += 1\n    else:\n        total_invalid += 1\n\n    print(f'  {test_result}')\n    print(f'  Used {stack_heap} KB RAM')\n    print(f'  Took {instructions} instructions ({cycles} cycles)')\n\n# an output to be added to a CSV\ntemplate = 'title,pass,warn,fail,invalid,codesizeKB,staticmemKB,maxheapKB,Minstructions,Mcycles'\noutput_row = f'{title},{total_pass},{total_warn},{total_fail},{total_invalid},{text},{static_ram},{max_heap},{total_instructions // 1000000},{total_cycles // 1000000}'\n\nif args.output:\n    open(args.output, 'a').write(output_row+'\\n')\nelse:\n    print()\n    print('    The template is:')\n    print(template)\n    print('    The next row should be:')\n    print(output_row)\n"
  },
  {
    "path": "test/CMakeLists.txt",
    "content": "\nset(CMAKE_CXX_STANDARD 14)\n\nadd_library(test_common INTERFACE)\ntarget_include_directories(test_common INTERFACE\n    ${CMAKE_CURRENT_SOURCE_DIR}/common\n    ${TheXTech_SOURCE_DIR}/include\n    ${TheXTech_SOURCE_DIR}/src\n)\n\nadd_subdirectory(test_msg_macro)\n\nadd_library(Catch-objects OBJECT \"common/catch_amalgamated.cpp\")\ntarget_include_directories(Catch-objects PRIVATE \"common\")\n"
  },
  {
    "path": "test/common/catch_amalgamated.cpp",
    "content": "\n//              Copyright Catch2 Authors\n// Distributed under the Boost Software License, Version 1.0.\n//   (See accompanying file LICENSE.txt or copy at\n//        https://www.boost.org/LICENSE_1_0.txt)\n\n// SPDX-License-Identifier: BSL-1.0\n\n//  Catch v3.9.0\n//  Generated: 2025-07-24 22:00:25.173359\n//  ----------------------------------------------------------\n//  This file is an amalgamation of multiple different files.\n//  You probably shouldn't edit it directly.\n//  ----------------------------------------------------------\n\n#include \"catch_amalgamated.hpp\"\n\n\n#ifndef CATCH_WINDOWS_H_PROXY_HPP_INCLUDED\n#define CATCH_WINDOWS_H_PROXY_HPP_INCLUDED\n\n\n#if defined(CATCH_PLATFORM_WINDOWS)\n\n// We might end up with the define made globally through the compiler,\n// and we don't want to trigger warnings for this\n#if !defined(NOMINMAX)\n#  define NOMINMAX\n#endif\n#if !defined(WIN32_LEAN_AND_MEAN)\n#  define WIN32_LEAN_AND_MEAN\n#endif\n\n#include <windows.h>\n\n#endif // defined(CATCH_PLATFORM_WINDOWS)\n\n#endif // CATCH_WINDOWS_H_PROXY_HPP_INCLUDED\n\n\n\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            ChronometerConcept::~ChronometerConcept() = default;\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n\n// Adapted from donated nonius code.\n\n\n#include <vector>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last) {\n                if (!cfg.benchmarkNoAnalysis()) {\n                    std::vector<double> samples;\n                    samples.reserve(static_cast<size_t>(last - first));\n                    for (auto current = first; current != last; ++current) {\n                        samples.push_back( current->count() );\n                    }\n\n                    auto analysis = Catch::Benchmark::Detail::analyse_samples(\n                        cfg.benchmarkConfidenceInterval(),\n                        cfg.benchmarkResamples(),\n                        samples.data(),\n                        samples.data() + samples.size() );\n                    auto outliers = Catch::Benchmark::Detail::classify_outliers(\n                        samples.data(), samples.data() + samples.size() );\n\n                    auto wrap_estimate = [](Estimate<double> e) {\n                        return Estimate<FDuration> {\n                            FDuration(e.point),\n                                FDuration(e.lower_bound),\n                                FDuration(e.upper_bound),\n                                e.confidence_interval,\n                        };\n                    };\n                    std::vector<FDuration> samples2;\n                    samples2.reserve(samples.size());\n                    for (auto s : samples) {\n                        samples2.push_back( FDuration( s ) );\n                    }\n\n                    return {\n                        CATCH_MOVE(samples2),\n                        wrap_estimate(analysis.mean),\n                        wrap_estimate(analysis.standard_deviation),\n                        outliers,\n                        analysis.outlier_variance,\n                    };\n                } else {\n                    std::vector<FDuration> samples;\n                    samples.reserve(static_cast<size_t>(last - first));\n\n                    FDuration mean = FDuration(0);\n                    int i = 0;\n                    for (auto it = first; it < last; ++it, ++i) {\n                        samples.push_back(*it);\n                        mean += *it;\n                    }\n                    mean /= i;\n\n                    return SampleAnalysis{\n                        CATCH_MOVE(samples),\n                        Estimate<FDuration>{ mean, mean, mean, 0.0 },\n                        Estimate<FDuration>{ FDuration( 0 ),\n                                             FDuration( 0 ),\n                                             FDuration( 0 ),\n                                             0.0 },\n                        OutlierClassification{},\n                        0.0\n                    };\n                }\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n\n\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            struct do_nothing {\n                void operator()() const {}\n            };\n\n            BenchmarkFunction::callable::~callable() = default;\n            BenchmarkFunction::BenchmarkFunction():\n                f( new model<do_nothing>{ {} } ){}\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n\n\n\n#include <exception>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            struct optimized_away_error : std::exception {\n                const char* what() const noexcept override;\n            };\n\n            const char* optimized_away_error::what() const noexcept {\n                return \"could not measure benchmark, maybe it was optimized away\";\n            }\n\n            void throw_optimized_away_error() {\n                Catch::throw_exception(optimized_away_error{});\n            }\n\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n\n// Adapted from donated nonius code.\n\n\n\n#include <algorithm>\n#include <cassert>\n#include <cmath>\n#include <cstddef>\n#include <numeric>\n#include <random>\n\n\n#if defined(CATCH_CONFIG_USE_ASYNC)\n#include <future>\n#endif\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            namespace {\n\n                template <typename URng, typename Estimator>\n                static sample\n                resample( URng& rng,\n                          unsigned int resamples,\n                          double const* first,\n                          double const* last,\n                          Estimator& estimator ) {\n                    auto n = static_cast<size_t>( last - first );\n                    Catch::uniform_integer_distribution<size_t> dist( 0, n - 1 );\n\n                    sample out;\n                    out.reserve( resamples );\n                    std::vector<double> resampled;\n                    resampled.reserve( n );\n                    for ( size_t i = 0; i < resamples; ++i ) {\n                        resampled.clear();\n                        for ( size_t s = 0; s < n; ++s ) {\n                            resampled.push_back( first[dist( rng )] );\n                        }\n                        const auto estimate =\n                            estimator( resampled.data(), resampled.data() + resampled.size() );\n                        out.push_back( estimate );\n                    }\n                    std::sort( out.begin(), out.end() );\n                    return out;\n                }\n\n                static double outlier_variance( Estimate<double> mean,\n                                                Estimate<double> stddev,\n                                                int n ) {\n                    double sb = stddev.point;\n                    double mn = mean.point / n;\n                    double mg_min = mn / 2.;\n                    double sg = (std::min)( mg_min / 4., sb / std::sqrt( n ) );\n                    double sg2 = sg * sg;\n                    double sb2 = sb * sb;\n\n                    auto c_max = [n, mn, sb2, sg2]( double x ) -> double {\n                        double k = mn - x;\n                        double d = k * k;\n                        double nd = n * d;\n                        double k0 = -n * nd;\n                        double k1 = sb2 - n * sg2 + nd;\n                        double det = k1 * k1 - 4 * sg2 * k0;\n                        return static_cast<int>( -2. * k0 /\n                                                 ( k1 + std::sqrt( det ) ) );\n                    };\n\n                    auto var_out = [n, sb2, sg2]( double c ) {\n                        double nc = n - c;\n                        return ( nc / n ) * ( sb2 - nc * sg2 );\n                    };\n\n                    return (std::min)( var_out( 1 ),\n                                       var_out(\n                                           (std::min)( c_max( 0. ),\n                                                       c_max( mg_min ) ) ) ) /\n                           sb2;\n                }\n\n                static double erf_inv( double x ) {\n                    // Code accompanying the article \"Approximating the erfinv\n                    // function\" in GPU Computing Gems, Volume 2\n                    double w, p;\n\n                    w = -log( ( 1.0 - x ) * ( 1.0 + x ) );\n\n                    if ( w < 6.250000 ) {\n                        w = w - 3.125000;\n                        p = -3.6444120640178196996e-21;\n                        p = -1.685059138182016589e-19 + p * w;\n                        p = 1.2858480715256400167e-18 + p * w;\n                        p = 1.115787767802518096e-17 + p * w;\n                        p = -1.333171662854620906e-16 + p * w;\n                        p = 2.0972767875968561637e-17 + p * w;\n                        p = 6.6376381343583238325e-15 + p * w;\n                        p = -4.0545662729752068639e-14 + p * w;\n                        p = -8.1519341976054721522e-14 + p * w;\n                        p = 2.6335093153082322977e-12 + p * w;\n                        p = -1.2975133253453532498e-11 + p * w;\n                        p = -5.4154120542946279317e-11 + p * w;\n                        p = 1.051212273321532285e-09 + p * w;\n                        p = -4.1126339803469836976e-09 + p * w;\n                        p = -2.9070369957882005086e-08 + p * w;\n                        p = 4.2347877827932403518e-07 + p * w;\n                        p = -1.3654692000834678645e-06 + p * w;\n                        p = -1.3882523362786468719e-05 + p * w;\n                        p = 0.0001867342080340571352 + p * w;\n                        p = -0.00074070253416626697512 + p * w;\n                        p = -0.0060336708714301490533 + p * w;\n                        p = 0.24015818242558961693 + p * w;\n                        p = 1.6536545626831027356 + p * w;\n                    } else if ( w < 16.000000 ) {\n                        w = sqrt( w ) - 3.250000;\n                        p = 2.2137376921775787049e-09;\n                        p = 9.0756561938885390979e-08 + p * w;\n                        p = -2.7517406297064545428e-07 + p * w;\n                        p = 1.8239629214389227755e-08 + p * w;\n                        p = 1.5027403968909827627e-06 + p * w;\n                        p = -4.013867526981545969e-06 + p * w;\n                        p = 2.9234449089955446044e-06 + p * w;\n                        p = 1.2475304481671778723e-05 + p * w;\n                        p = -4.7318229009055733981e-05 + p * w;\n                        p = 6.8284851459573175448e-05 + p * w;\n                        p = 2.4031110387097893999e-05 + p * w;\n                        p = -0.0003550375203628474796 + p * w;\n                        p = 0.00095328937973738049703 + p * w;\n                        p = -0.0016882755560235047313 + p * w;\n                        p = 0.0024914420961078508066 + p * w;\n                        p = -0.0037512085075692412107 + p * w;\n                        p = 0.005370914553590063617 + p * w;\n                        p = 1.0052589676941592334 + p * w;\n                        p = 3.0838856104922207635 + p * w;\n                    } else {\n                        w = sqrt( w ) - 5.000000;\n                        p = -2.7109920616438573243e-11;\n                        p = -2.5556418169965252055e-10 + p * w;\n                        p = 1.5076572693500548083e-09 + p * w;\n                        p = -3.7894654401267369937e-09 + p * w;\n                        p = 7.6157012080783393804e-09 + p * w;\n                        p = -1.4960026627149240478e-08 + p * w;\n                        p = 2.9147953450901080826e-08 + p * w;\n                        p = -6.7711997758452339498e-08 + p * w;\n                        p = 2.2900482228026654717e-07 + p * w;\n                        p = -9.9298272942317002539e-07 + p * w;\n                        p = 4.5260625972231537039e-06 + p * w;\n                        p = -1.9681778105531670567e-05 + p * w;\n                        p = 7.5995277030017761139e-05 + p * w;\n                        p = -0.00021503011930044477347 + p * w;\n                        p = -0.00013871931833623122026 + p * w;\n                        p = 1.0103004648645343977 + p * w;\n                        p = 4.8499064014085844221 + p * w;\n                    }\n                    return p * x;\n                }\n\n                static double\n                standard_deviation( double const* first, double const* last ) {\n                    auto m = Catch::Benchmark::Detail::mean( first, last );\n                    double variance =\n                        std::accumulate( first,\n                                         last,\n                                         0.,\n                                         [m]( double a, double b ) {\n                                             double diff = b - m;\n                                             return a + diff * diff;\n                                         } ) /\n                        static_cast<double>( last - first );\n                    return std::sqrt( variance );\n                }\n\n                static sample jackknife( double ( *estimator )( double const*,\n                                                                double const* ),\n                                         double* first,\n                                         double* last ) {\n                    const auto second = first + 1;\n                    sample results;\n                    results.reserve( static_cast<size_t>( last - first ) );\n\n                    for ( auto it = first; it != last; ++it ) {\n                        std::iter_swap( it, first );\n                        results.push_back( estimator( second, last ) );\n                    }\n\n                    return results;\n                }\n\n\n            } // namespace\n        }     // namespace Detail\n    }         // namespace Benchmark\n} // namespace Catch\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n\n            double weighted_average_quantile( int k,\n                                              int q,\n                                              double* first,\n                                              double* last ) {\n                auto count = last - first;\n                double idx = static_cast<double>((count - 1) * k) / static_cast<double>(q);\n                int j = static_cast<int>(idx);\n                double g = idx - j;\n                std::nth_element(first, first + j, last);\n                auto xj = first[j];\n                if ( Catch::Detail::directCompare( g, 0 ) ) {\n                    return xj;\n                }\n\n                auto xj1 = *std::min_element(first + (j + 1), last);\n                return xj + g * (xj1 - xj);\n            }\n\n            OutlierClassification\n            classify_outliers( double const* first, double const* last ) {\n                std::vector<double> copy( first, last );\n\n                auto q1 = weighted_average_quantile( 1, 4, copy.data(), copy.data() + copy.size() );\n                auto q3 = weighted_average_quantile( 3, 4, copy.data(), copy.data() + copy.size() );\n                auto iqr = q3 - q1;\n                auto los = q1 - ( iqr * 3. );\n                auto lom = q1 - ( iqr * 1.5 );\n                auto him = q3 + ( iqr * 1.5 );\n                auto his = q3 + ( iqr * 3. );\n\n                OutlierClassification o;\n                for ( ; first != last; ++first ) {\n                    const double t = *first;\n                    if ( t < los ) {\n                        ++o.low_severe;\n                    } else if ( t < lom ) {\n                        ++o.low_mild;\n                    } else if ( t > his ) {\n                        ++o.high_severe;\n                    } else if ( t > him ) {\n                        ++o.high_mild;\n                    }\n                    ++o.samples_seen;\n                }\n                return o;\n            }\n\n            double mean( double const* first, double const* last ) {\n                auto count = last - first;\n                double sum = 0.;\n                while (first != last) {\n                    sum += *first;\n                    ++first;\n                }\n                return sum / static_cast<double>(count);\n            }\n\n            double normal_cdf( double x ) {\n                return std::erfc( -x / std::sqrt( 2.0 ) ) / 2.0;\n            }\n\n            double erfc_inv(double x) {\n                return erf_inv(1.0 - x);\n            }\n\n            double normal_quantile(double p) {\n                static const double ROOT_TWO = std::sqrt(2.0);\n\n                double result = 0.0;\n                assert(p >= 0 && p <= 1);\n                if (p < 0 || p > 1) {\n                    return result;\n                }\n\n                result = -erfc_inv(2.0 * p);\n                // result *= normal distribution standard deviation (1.0) * sqrt(2)\n                result *= /*sd * */ ROOT_TWO;\n                // result += normal disttribution mean (0)\n                return result;\n            }\n\n            Estimate<double>\n            bootstrap( double confidence_level,\n                       double* first,\n                       double* last,\n                       sample const& resample,\n                       double ( *estimator )( double const*, double const* ) ) {\n                auto n_samples = last - first;\n\n                double point = estimator( first, last );\n                // Degenerate case with a single sample\n                if ( n_samples == 1 )\n                    return { point, point, point, confidence_level };\n\n                sample jack = jackknife( estimator, first, last );\n                double jack_mean =\n                    mean( jack.data(), jack.data() + jack.size() );\n                double sum_squares = 0, sum_cubes = 0;\n                for ( double x : jack ) {\n                    auto difference = jack_mean - x;\n                    auto square = difference * difference;\n                    auto cube = square * difference;\n                    sum_squares += square;\n                    sum_cubes += cube;\n                }\n\n                double accel = sum_cubes / ( 6 * std::pow( sum_squares, 1.5 ) );\n                long n = static_cast<long>( resample.size() );\n                double prob_n = static_cast<double>(\n                    std::count_if( resample.begin(),\n                                   resample.end(),\n                                   [point]( double x ) { return x < point; } )) /\n                    static_cast<double>( n );\n                // degenerate case with uniform samples\n                if ( Catch::Detail::directCompare( prob_n, 0. ) ) {\n                    return { point, point, point, confidence_level };\n                }\n\n                double bias = normal_quantile( prob_n );\n                double z1 = normal_quantile( ( 1. - confidence_level ) / 2. );\n\n                auto cumn = [n]( double x ) -> long {\n                    return std::lround( normal_cdf( x ) *\n                                        static_cast<double>( n ) );\n                };\n                auto a = [bias, accel]( double b ) {\n                    return bias + b / ( 1. - accel * b );\n                };\n                double b1 = bias + z1;\n                double b2 = bias - z1;\n                double a1 = a( b1 );\n                double a2 = a( b2 );\n                auto lo = static_cast<size_t>( (std::max)( cumn( a1 ), 0l ) );\n                auto hi =\n                    static_cast<size_t>( (std::min)( cumn( a2 ), n - 1 ) );\n\n                return { point, resample[lo], resample[hi], confidence_level };\n            }\n\n            bootstrap_analysis analyse_samples(double confidence_level,\n                                               unsigned int n_resamples,\n                                               double* first,\n                                               double* last) {\n                auto mean = &Detail::mean;\n                auto stddev = &standard_deviation;\n\n#if defined(CATCH_CONFIG_USE_ASYNC)\n                auto Estimate = [=](double(*f)(double const*, double const*)) {\n                    std::random_device rd;\n                    auto seed = rd();\n                    return std::async(std::launch::async, [=] {\n                        SimplePcg32 rng( seed );\n                        auto resampled = resample(rng, n_resamples, first, last, f);\n                        return bootstrap(confidence_level, first, last, resampled, f);\n                    });\n                };\n\n                auto mean_future = Estimate(mean);\n                auto stddev_future = Estimate(stddev);\n\n                auto mean_estimate = mean_future.get();\n                auto stddev_estimate = stddev_future.get();\n#else\n                auto Estimate = [=](double(*f)(double const* , double const*)) {\n                    std::random_device rd;\n                    auto seed = rd();\n                    SimplePcg32 rng( seed );\n                    auto resampled = resample(rng, n_resamples, first, last, f);\n                    return bootstrap(confidence_level, first, last, resampled, f);\n                };\n\n                auto mean_estimate = Estimate(mean);\n                auto stddev_estimate = Estimate(stddev);\n#endif // CATCH_USE_ASYNC\n\n                auto n = static_cast<int>(last - first); // seriously, one can't use integral types without hell in C++\n                double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n);\n\n                return { mean_estimate, stddev_estimate, outlier_variance };\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n\n\n#include <cmath>\n#include <limits>\n\nnamespace {\n\n// Performs equivalent check of std::fabs(lhs - rhs) <= margin\n// But without the subtraction to allow for INFINITY in comparison\nbool marginComparison(double lhs, double rhs, double margin) {\n    return (lhs + margin >= rhs) && (rhs + margin >= lhs);\n}\n\n}\n\nnamespace Catch {\n\n    Approx::Approx ( double value )\n    :   m_epsilon( static_cast<double>(std::numeric_limits<float>::epsilon())*100. ),\n        m_margin( 0.0 ),\n        m_scale( 0.0 ),\n        m_value( value )\n    {}\n\n    Approx Approx::custom() {\n        return Approx( 0 );\n    }\n\n    Approx Approx::operator-() const {\n        auto temp(*this);\n        temp.m_value = -temp.m_value;\n        return temp;\n    }\n\n\n    std::string Approx::toString() const {\n        ReusableStringStream rss;\n        rss << \"Approx( \" << ::Catch::Detail::stringify( m_value ) << \" )\";\n        return rss.str();\n    }\n\n    bool Approx::equalityComparisonImpl(const double other) const {\n        // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value\n        // Thanks to Richard Harris for his help refining the scaled margin value\n        return marginComparison(m_value, other, m_margin)\n            || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value)));\n    }\n\n    void Approx::setMargin(double newMargin) {\n        CATCH_ENFORCE(newMargin >= 0,\n            \"Invalid Approx::margin: \" << newMargin << '.'\n            << \" Approx::Margin has to be non-negative.\");\n        m_margin = newMargin;\n    }\n\n    void Approx::setEpsilon(double newEpsilon) {\n        CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0,\n            \"Invalid Approx::epsilon: \" << newEpsilon << '.'\n            << \" Approx::epsilon has to be in [0, 1]\");\n        m_epsilon = newEpsilon;\n    }\n\nnamespace literals {\n    Approx operator \"\"_a(long double val) {\n        return Approx(val);\n    }\n    Approx operator \"\"_a(unsigned long long val) {\n        return Approx(val);\n    }\n} // end namespace literals\n\nstd::string StringMaker<Catch::Approx>::convert(Catch::Approx const& value) {\n    return value.toString();\n}\n\n} // end namespace Catch\n\n\n\nnamespace Catch {\n\n    AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const& _lazyExpression):\n        lazyExpression(_lazyExpression),\n        resultType(_resultType) {}\n\n    std::string AssertionResultData::reconstructExpression() const {\n\n        if( reconstructedExpression.empty() ) {\n            if( lazyExpression ) {\n                ReusableStringStream rss;\n                rss << lazyExpression;\n                reconstructedExpression = rss.str();\n            }\n        }\n        return reconstructedExpression;\n    }\n\n    AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData&& data )\n    :   m_info( info ),\n        m_resultData( CATCH_MOVE(data) )\n    {}\n\n    // Result was a success\n    bool AssertionResult::succeeded() const {\n        return Catch::isOk( m_resultData.resultType );\n    }\n\n    // Result was a success, or failure is suppressed\n    bool AssertionResult::isOk() const {\n        return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );\n    }\n\n    ResultWas::OfType AssertionResult::getResultType() const {\n        return m_resultData.resultType;\n    }\n\n    bool AssertionResult::hasExpression() const {\n        return !m_info.capturedExpression.empty();\n    }\n\n    bool AssertionResult::hasMessage() const {\n        return !m_resultData.message.empty();\n    }\n\n    std::string AssertionResult::getExpression() const {\n        // Possibly overallocating by 3 characters should be basically free\n        std::string expr; expr.reserve(m_info.capturedExpression.size() + 3);\n        if (isFalseTest(m_info.resultDisposition)) {\n            expr += \"!(\";\n        }\n        expr += m_info.capturedExpression;\n        if (isFalseTest(m_info.resultDisposition)) {\n            expr += ')';\n        }\n        return expr;\n    }\n\n    std::string AssertionResult::getExpressionInMacro() const {\n        if ( m_info.macroName.empty() ) {\n            return static_cast<std::string>( m_info.capturedExpression );\n        }\n        std::string expr;\n        expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 );\n        expr += m_info.macroName;\n        expr += \"( \";\n        expr += m_info.capturedExpression;\n        expr += \" )\";\n        return expr;\n    }\n\n    bool AssertionResult::hasExpandedExpression() const {\n        return hasExpression() && getExpandedExpression() != getExpression();\n    }\n\n    std::string AssertionResult::getExpandedExpression() const {\n        std::string expr = m_resultData.reconstructExpression();\n        return expr.empty()\n                ? getExpression()\n                : expr;\n    }\n\n    StringRef AssertionResult::getMessage() const {\n        return m_resultData.message;\n    }\n    SourceLineInfo AssertionResult::getSourceInfo() const {\n        return m_info.lineInfo;\n    }\n\n    StringRef AssertionResult::getTestMacroName() const {\n        return m_info.macroName;\n    }\n\n} // end namespace Catch\n\n\n\n#include <fstream>\n\nnamespace Catch {\n\n    namespace {\n        static bool enableBazelEnvSupport() {\n#if defined( CATCH_CONFIG_BAZEL_SUPPORT )\n            return true;\n#else\n            return Detail::getEnv( \"BAZEL_TEST\" ) != nullptr;\n#endif\n        }\n\n        struct bazelShardingOptions {\n            unsigned int shardIndex, shardCount;\n            std::string shardFilePath;\n        };\n\n        static Optional<bazelShardingOptions> readBazelShardingOptions() {\n            const auto bazelShardIndex = Detail::getEnv( \"TEST_SHARD_INDEX\" );\n            const auto bazelShardTotal = Detail::getEnv( \"TEST_TOTAL_SHARDS\" );\n            const auto bazelShardInfoFile = Detail::getEnv( \"TEST_SHARD_STATUS_FILE\" );\n\n\n            const bool has_all =\n                bazelShardIndex && bazelShardTotal && bazelShardInfoFile;\n            if ( !has_all ) {\n                // We provide nice warning message if the input is\n                // misconfigured.\n                auto warn = []( const char* env_var ) {\n                    Catch::cerr()\n                        << \"Warning: Bazel shard configuration is missing '\"\n                        << env_var << \"'. Shard configuration is skipped.\\n\";\n                };\n                if ( !bazelShardIndex ) {\n                    warn( \"TEST_SHARD_INDEX\" );\n                }\n                if ( !bazelShardTotal ) {\n                    warn( \"TEST_TOTAL_SHARDS\" );\n                }\n                if ( !bazelShardInfoFile ) {\n                    warn( \"TEST_SHARD_STATUS_FILE\" );\n                }\n                return {};\n            }\n\n            auto shardIndex = parseUInt( bazelShardIndex );\n            if ( !shardIndex ) {\n                Catch::cerr()\n                    << \"Warning: could not parse 'TEST_SHARD_INDEX' ('\" << bazelShardIndex\n                    << \"') as unsigned int.\\n\";\n                return {};\n            }\n            auto shardTotal = parseUInt( bazelShardTotal );\n            if ( !shardTotal ) {\n                Catch::cerr()\n                    << \"Warning: could not parse 'TEST_TOTAL_SHARD' ('\"\n                    << bazelShardTotal << \"') as unsigned int.\\n\";\n                return {};\n            }\n\n            return bazelShardingOptions{\n                *shardIndex, *shardTotal, bazelShardInfoFile };\n\n        }\n    } // end namespace\n\n\n    bool operator==( ProcessedReporterSpec const& lhs,\n                     ProcessedReporterSpec const& rhs ) {\n        return lhs.name == rhs.name &&\n               lhs.outputFilename == rhs.outputFilename &&\n               lhs.colourMode == rhs.colourMode &&\n               lhs.customOptions == rhs.customOptions;\n    }\n\n    Config::Config( ConfigData const& data ):\n        m_data( data ) {\n        // We need to trim filter specs to avoid trouble with superfluous\n        // whitespace (esp. important for bdd macros, as those are manually\n        // aligned with whitespace).\n\n        for (auto& elem : m_data.testsOrTags) {\n            elem = trim(elem);\n        }\n        for (auto& elem : m_data.sectionsToRun) {\n            elem = trim(elem);\n        }\n\n        // Insert the default reporter if user hasn't asked for a specific one\n        if ( m_data.reporterSpecifications.empty() ) {\n#if defined( CATCH_CONFIG_DEFAULT_REPORTER )\n            const auto default_spec = CATCH_CONFIG_DEFAULT_REPORTER;\n#else\n            const auto default_spec = \"console\";\n#endif\n            auto parsed = parseReporterSpec(default_spec);\n            CATCH_ENFORCE( parsed,\n                           \"Cannot parse the provided default reporter spec: '\"\n                               << default_spec << '\\'' );\n            m_data.reporterSpecifications.push_back( std::move( *parsed ) );\n        }\n\n        if ( enableBazelEnvSupport() ) {\n            readBazelEnvVars();\n        }\n\n        // Bazel support can modify the test specs, so parsing has to happen\n        // after reading Bazel env vars.\n        TestSpecParser parser( ITagAliasRegistry::get() );\n        if ( !m_data.testsOrTags.empty() ) {\n            m_hasTestFilters = true;\n            for ( auto const& testOrTags : m_data.testsOrTags ) {\n                parser.parse( testOrTags );\n            }\n        }\n        m_testSpec = parser.testSpec();\n\n\n        // We now fixup the reporter specs to handle default output spec,\n        // default colour spec, etc\n        bool defaultOutputUsed = false;\n        for ( auto const& reporterSpec : m_data.reporterSpecifications ) {\n            // We do the default-output check separately, while always\n            // using the default output below to make the code simpler\n            // and avoid superfluous copies.\n            if ( reporterSpec.outputFile().none() ) {\n                CATCH_ENFORCE( !defaultOutputUsed,\n                               \"Internal error: cannot use default output for \"\n                               \"multiple reporters\" );\n                defaultOutputUsed = true;\n            }\n\n            m_processedReporterSpecs.push_back( ProcessedReporterSpec{\n                reporterSpec.name(),\n                reporterSpec.outputFile() ? *reporterSpec.outputFile()\n                                          : data.defaultOutputFilename,\n                reporterSpec.colourMode().valueOr( data.defaultColourMode ),\n                reporterSpec.customOptions() } );\n        }\n    }\n\n    Config::~Config() = default;\n\n\n    bool Config::listTests() const          { return m_data.listTests; }\n    bool Config::listTags() const           { return m_data.listTags; }\n    bool Config::listReporters() const      { return m_data.listReporters; }\n    bool Config::listListeners() const      { return m_data.listListeners; }\n\n    std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }\n    std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }\n\n    std::vector<ReporterSpec> const& Config::getReporterSpecs() const {\n        return m_data.reporterSpecifications;\n    }\n\n    std::vector<ProcessedReporterSpec> const&\n    Config::getProcessedReporterSpecs() const {\n        return m_processedReporterSpecs;\n    }\n\n    TestSpec const& Config::testSpec() const { return m_testSpec; }\n    bool Config::hasTestFilters() const { return m_hasTestFilters; }\n\n    bool Config::showHelp() const { return m_data.showHelp; }\n\n    // IConfig interface\n    bool Config::allowThrows() const                   { return !m_data.noThrow; }\n    StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }\n    bool Config::includeSuccessfulResults() const      { return m_data.showSuccessfulTests; }\n    bool Config::warnAboutMissingAssertions() const {\n        return !!( m_data.warnings & WarnAbout::NoAssertions );\n    }\n    bool Config::warnAboutUnmatchedTestSpecs() const {\n        return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec );\n    }\n    bool Config::zeroTestsCountAsSuccess() const       { return m_data.allowZeroTests; }\n    ShowDurations Config::showDurations() const        { return m_data.showDurations; }\n    double Config::minDuration() const                 { return m_data.minDuration; }\n    TestRunOrder Config::runOrder() const              { return m_data.runOrder; }\n    uint32_t Config::rngSeed() const                   { return m_data.rngSeed; }\n    unsigned int Config::shardCount() const            { return m_data.shardCount; }\n    unsigned int Config::shardIndex() const            { return m_data.shardIndex; }\n    ColourMode Config::defaultColourMode() const       { return m_data.defaultColourMode; }\n    bool Config::shouldDebugBreak() const              { return m_data.shouldDebugBreak; }\n    int Config::abortAfter() const                     { return m_data.abortAfter; }\n    bool Config::showInvisibles() const                { return m_data.showInvisibles; }\n    Verbosity Config::verbosity() const                { return m_data.verbosity; }\n\n    bool Config::skipBenchmarks() const                           { return m_data.skipBenchmarks; }\n    bool Config::benchmarkNoAnalysis() const                      { return m_data.benchmarkNoAnalysis; }\n    unsigned int Config::benchmarkSamples() const                 { return m_data.benchmarkSamples; }\n    double Config::benchmarkConfidenceInterval() const            { return m_data.benchmarkConfidenceInterval; }\n    unsigned int Config::benchmarkResamples() const               { return m_data.benchmarkResamples; }\n    std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); }\n\n    void Config::readBazelEnvVars() {\n        // Register a JUnit reporter for Bazel. Bazel sets an environment\n        // variable with the path to XML output. If this file is written to\n        // during test, Bazel will not generate a default XML output.\n        // This allows the XML output file to contain higher level of detail\n        // than what is possible otherwise.\n        const auto bazelOutputFile = Detail::getEnv( \"XML_OUTPUT_FILE\" );\n\n        if ( bazelOutputFile ) {\n            m_data.reporterSpecifications.push_back(\n                { \"junit\", std::string( bazelOutputFile ), {}, {} } );\n        }\n\n        const auto bazelTestSpec = Detail::getEnv( \"TESTBRIDGE_TEST_ONLY\" );\n        if ( bazelTestSpec ) {\n            // Presumably the test spec from environment should overwrite\n            // the one we got from CLI (if we got any)\n            m_data.testsOrTags.clear();\n            m_data.testsOrTags.push_back( bazelTestSpec );\n        }\n\n        const auto bazelShardOptions = readBazelShardingOptions();\n        if ( bazelShardOptions ) {\n            std::ofstream f( bazelShardOptions->shardFilePath,\n                             std::ios_base::out | std::ios_base::trunc );\n            if ( f.is_open() ) {\n                f << \"\";\n                m_data.shardIndex = bazelShardOptions->shardIndex;\n                m_data.shardCount = bazelShardOptions->shardCount;\n            }\n        }\n    }\n\n} // end namespace Catch\n\n\n\n\n\nnamespace Catch {\n    std::uint32_t getSeed() {\n        return getCurrentContext().getConfig()->rngSeed();\n    }\n}\n\n\n\n#include <cassert>\n#include <stack>\n\nnamespace Catch {\n\n    ////////////////////////////////////////////////////////////////////////////\n\n\n    ScopedMessage::ScopedMessage( MessageBuilder&& builder ):\n        m_info( CATCH_MOVE(builder.m_info) ) {\n        m_info.message = builder.m_stream.str();\n        getResultCapture().pushScopedMessage( m_info );\n    }\n\n    ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:\n        m_info( CATCH_MOVE( old.m_info ) ) {\n        old.m_moved = true;\n    }\n\n    ScopedMessage::~ScopedMessage() {\n        if ( !m_moved ){\n            getResultCapture().popScopedMessage(m_info);\n        }\n    }\n\n\n    Capturer::Capturer( StringRef macroName,\n                        SourceLineInfo const& lineInfo,\n                        ResultWas::OfType resultType,\n                        StringRef names ):\n        m_resultCapture( getResultCapture() ) {\n        auto trimmed = [&] (size_t start, size_t end) {\n            while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {\n                ++start;\n            }\n            while (names[end] == ',' || isspace(static_cast<unsigned char>(names[end]))) {\n                --end;\n            }\n            return names.substr(start, end - start + 1);\n        };\n        auto skipq = [&] (size_t start, char quote) {\n            for (auto i = start + 1; i < names.size() ; ++i) {\n                if (names[i] == quote)\n                    return i;\n                if (names[i] == '\\\\')\n                    ++i;\n            }\n            CATCH_INTERNAL_ERROR(\"CAPTURE parsing encountered unmatched quote\");\n        };\n\n        size_t start = 0;\n        std::stack<char> openings;\n        for (size_t pos = 0; pos < names.size(); ++pos) {\n            char c = names[pos];\n            switch (c) {\n            case '[':\n            case '{':\n            case '(':\n            // It is basically impossible to disambiguate between\n            // comparison and start of template args in this context\n//            case '<':\n                openings.push(c);\n                break;\n            case ']':\n            case '}':\n            case ')':\n//           case '>':\n                openings.pop();\n                break;\n            case '\"':\n            case '\\'':\n                pos = skipq(pos, c);\n                break;\n            case ',':\n                if (start != pos && openings.empty()) {\n                    m_messages.emplace_back(macroName, lineInfo, resultType);\n                    m_messages.back().message = static_cast<std::string>(trimmed(start, pos));\n                    m_messages.back().message += \" := \";\n                    start = pos;\n                }\n                break;\n            default:; // noop\n            }\n        }\n        assert(openings.empty() && \"Mismatched openings\");\n        m_messages.emplace_back(macroName, lineInfo, resultType);\n        m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));\n        m_messages.back().message += \" := \";\n    }\n    Capturer::~Capturer() {\n        assert( m_captured == m_messages.size() );\n        for ( size_t i = 0; i < m_captured; ++i )\n            m_resultCapture.popScopedMessage( m_messages[i] );\n    }\n\n    void Capturer::captureValue( size_t index, std::string const& value ) {\n        assert( index < m_messages.size() );\n        m_messages[index].message += value;\n        m_resultCapture.pushScopedMessage( m_messages[index] );\n        m_captured++;\n    }\n\n} // end namespace Catch\n\n\n\n\n#include <exception>\n\nnamespace Catch {\n\n    namespace {\n\n        class RegistryHub : public IRegistryHub,\n                            public IMutableRegistryHub,\n                            private Detail::NonCopyable {\n\n        public: // IRegistryHub\n            RegistryHub() = default;\n            ReporterRegistry const& getReporterRegistry() const override {\n                return m_reporterRegistry;\n            }\n            ITestCaseRegistry const& getTestCaseRegistry() const override {\n                return m_testCaseRegistry;\n            }\n            IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override {\n                return m_exceptionTranslatorRegistry;\n            }\n            ITagAliasRegistry const& getTagAliasRegistry() const override {\n                return m_tagAliasRegistry;\n            }\n            StartupExceptionRegistry const& getStartupExceptionRegistry() const override {\n                return m_exceptionRegistry;\n            }\n\n        public: // IMutableRegistryHub\n            void registerReporter( std::string const& name, IReporterFactoryPtr factory ) override {\n                m_reporterRegistry.registerReporter( name, CATCH_MOVE(factory) );\n            }\n            void registerListener( Detail::unique_ptr<EventListenerFactory> factory ) override {\n                m_reporterRegistry.registerListener( CATCH_MOVE(factory) );\n            }\n            void registerTest( Detail::unique_ptr<TestCaseInfo>&& testInfo, Detail::unique_ptr<ITestInvoker>&& invoker ) override {\n                m_testCaseRegistry.registerTest( CATCH_MOVE(testInfo), CATCH_MOVE(invoker) );\n            }\n            void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) override {\n                m_exceptionTranslatorRegistry.registerTranslator( CATCH_MOVE(translator) );\n            }\n            void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override {\n                m_tagAliasRegistry.add( alias, tag, lineInfo );\n            }\n            void registerStartupException() noexcept override {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n                m_exceptionRegistry.add(std::current_exception());\n#else\n                CATCH_INTERNAL_ERROR(\"Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n#endif\n            }\n            IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override {\n                return m_enumValuesRegistry;\n            }\n\n        private:\n            TestRegistry m_testCaseRegistry;\n            ReporterRegistry m_reporterRegistry;\n            ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;\n            TagAliasRegistry m_tagAliasRegistry;\n            StartupExceptionRegistry m_exceptionRegistry;\n            Detail::EnumValuesRegistry m_enumValuesRegistry;\n        };\n    }\n\n    using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>;\n\n    IRegistryHub const& getRegistryHub() {\n        return RegistryHubSingleton::get();\n    }\n    IMutableRegistryHub& getMutableRegistryHub() {\n        return RegistryHubSingleton::getMutable();\n    }\n    void cleanUp() {\n        cleanupSingletons();\n        cleanUpContext();\n    }\n    std::string translateActiveException() {\n        return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();\n    }\n\n\n} // end namespace Catch\n\n\n\n#include <cassert>\n#include <exception>\n#include <iomanip>\n#include <set>\n\nnamespace Catch {\n\n    namespace {\n        IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) {\n            auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config));\n            CATCH_ENFORCE(reporter, \"No reporter registered with name: '\" << reporterName << '\\'');\n\n            return reporter;\n        }\n\n        IEventListenerPtr prepareReporters(Config const* config) {\n            if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()\n                    && config->getProcessedReporterSpecs().size() == 1) {\n                auto const& spec = config->getProcessedReporterSpecs()[0];\n                return createReporter(\n                    spec.name,\n                    ReporterConfig( config,\n                                    makeStream( spec.outputFilename ),\n                                    spec.colourMode,\n                                    spec.customOptions ) );\n            }\n\n            auto multi = Detail::make_unique<MultiReporter>(config);\n\n            auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners();\n            for (auto const& listener : listeners) {\n                multi->addListener(listener->create(config));\n            }\n\n            for ( auto const& reporterSpec : config->getProcessedReporterSpecs() ) {\n                multi->addReporter( createReporter(\n                    reporterSpec.name,\n                    ReporterConfig( config,\n                                    makeStream( reporterSpec.outputFilename ),\n                                    reporterSpec.colourMode,\n                                    reporterSpec.customOptions ) ) );\n            }\n\n            return multi;\n        }\n\n        class TestGroup {\n        public:\n            explicit TestGroup(IEventListenerPtr&& reporter, Config const* config):\n                m_reporter(reporter.get()),\n                m_config{config},\n                m_context{config, CATCH_MOVE(reporter)} {\n\n                assert( m_config->testSpec().getInvalidSpecs().empty() &&\n                        \"Invalid test specs should be handled before running tests\" );\n\n                auto const& allTestCases = getAllTestCasesSorted(*m_config);\n                auto const& testSpec = m_config->testSpec();\n                if ( !testSpec.hasFilters() ) {\n                    for ( auto const& test : allTestCases ) {\n                        if ( !test.getTestCaseInfo().isHidden() ) {\n                            m_tests.emplace( &test );\n                        }\n                    }\n                } else {\n                    m_matches =\n                        testSpec.matchesByFilter( allTestCases, *m_config );\n                    for ( auto const& match : m_matches ) {\n                        m_tests.insert( match.tests.begin(),\n                                        match.tests.end() );\n                    }\n                }\n\n                m_tests = createShard(m_tests, m_config->shardCount(), m_config->shardIndex());\n            }\n\n            Totals execute() {\n                Totals totals;\n                for (auto const& testCase : m_tests) {\n                    if (!m_context.aborting())\n                        totals += m_context.runTest(*testCase);\n                    else\n                        m_reporter->skipTest(testCase->getTestCaseInfo());\n                }\n\n                for (auto const& match : m_matches) {\n                    if (match.tests.empty()) {\n                        m_unmatchedTestSpecs = true;\n                        m_reporter->noMatchingTestCases( match.name );\n                    }\n                }\n\n                return totals;\n            }\n\n            bool hadUnmatchedTestSpecs() const {\n                return m_unmatchedTestSpecs;\n            }\n\n\n        private:\n            IEventListener* m_reporter;\n            Config const* m_config;\n            RunContext m_context;\n            std::set<TestCaseHandle const*> m_tests;\n            TestSpec::Matches m_matches;\n            bool m_unmatchedTestSpecs = false;\n        };\n\n        void applyFilenamesAsTags() {\n            for (auto const& testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) {\n                testInfo->addFilenameTag();\n            }\n        }\n\n    } // anon namespace\n\n    Session::Session() {\n        static bool alreadyInstantiated = false;\n        if( alreadyInstantiated ) {\n            CATCH_TRY { CATCH_INTERNAL_ERROR( \"Only one instance of Catch::Session can ever be used\" ); }\n            CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); }\n        }\n\n        // There cannot be exceptions at startup in no-exception mode.\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n        const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions();\n        if ( !exceptions.empty() ) {\n            config();\n            getCurrentMutableContext().setConfig(m_config.get());\n\n            m_startupExceptions = true;\n            auto errStream = makeStream( \"%stderr\" );\n            auto colourImpl = makeColourImpl(\n                ColourMode::PlatformDefault, errStream.get() );\n            auto guard = colourImpl->guardColour( Colour::Red );\n            errStream->stream() << \"Errors occurred during startup!\" << '\\n';\n            // iterate over all exceptions and notify user\n            for ( const auto& ex_ptr : exceptions ) {\n                try {\n                    std::rethrow_exception(ex_ptr);\n                } catch ( std::exception const& ex ) {\n                    errStream->stream() << TextFlow::Column( ex.what() ).indent(2) << '\\n';\n                }\n            }\n        }\n#endif\n\n        alreadyInstantiated = true;\n        m_cli = makeCommandLineParser( m_configData );\n    }\n    Session::~Session() {\n        Catch::cleanUp();\n    }\n\n    void Session::showHelp() const {\n        Catch::cout()\n                << \"\\nCatch2 v\" << libraryVersion() << '\\n'\n                << m_cli << '\\n'\n                << \"For more detailed usage please see the project docs\\n\\n\" << std::flush;\n    }\n    void Session::libIdentify() {\n        Catch::cout()\n                << std::left << std::setw(16) << \"description: \" << \"A Catch2 test executable\\n\"\n                << std::left << std::setw(16) << \"category: \" << \"testframework\\n\"\n                << std::left << std::setw(16) << \"framework: \" << \"Catch2\\n\"\n                << std::left << std::setw(16) << \"version: \" << libraryVersion() << '\\n' << std::flush;\n    }\n\n    int Session::applyCommandLine( int argc, char const * const * argv ) {\n        if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; }\n\n        auto result = m_cli.parse( Clara::Args( argc, argv ) );\n\n        if( !result ) {\n            config();\n            getCurrentMutableContext().setConfig(m_config.get());\n            auto errStream = makeStream( \"%stderr\" );\n            auto colour = makeColourImpl( ColourMode::PlatformDefault, errStream.get() );\n\n            errStream->stream()\n                << colour->guardColour( Colour::Red )\n                << \"\\nError(s) in input:\\n\"\n                << TextFlow::Column( result.errorMessage() ).indent( 2 )\n                << \"\\n\\n\";\n            errStream->stream() << \"Run with -? for usage\\n\\n\" << std::flush;\n            return UnspecifiedErrorExitCode;\n        }\n\n        if( m_configData.showHelp )\n            showHelp();\n        if( m_configData.libIdentify )\n            libIdentify();\n\n        m_config.reset();\n        return 0;\n    }\n\n#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)\n    int Session::applyCommandLine( int argc, wchar_t const * const * argv ) {\n\n        char **utf8Argv = new char *[ argc ];\n\n        for ( int i = 0; i < argc; ++i ) {\n            int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr );\n\n            utf8Argv[ i ] = new char[ bufSize ];\n\n            WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr );\n        }\n\n        int returnCode = applyCommandLine( argc, utf8Argv );\n\n        for ( int i = 0; i < argc; ++i )\n            delete [] utf8Argv[ i ];\n\n        delete [] utf8Argv;\n\n        return returnCode;\n    }\n#endif\n\n    void Session::useConfigData( ConfigData const& configData ) {\n        m_configData = configData;\n        m_config.reset();\n    }\n\n    int Session::run() {\n        if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) {\n            Catch::cout() << \"...waiting for enter/ return before starting\\n\" << std::flush;\n            static_cast<void>(std::getchar());\n        }\n        int exitCode = runInternal();\n        if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {\n            Catch::cout() << \"...waiting for enter/ return before exiting, with code: \" << exitCode << '\\n' << std::flush;\n            static_cast<void>(std::getchar());\n        }\n        return exitCode;\n    }\n\n    Clara::Parser const& Session::cli() const {\n        return m_cli;\n    }\n    void Session::cli( Clara::Parser const& newParser ) {\n        m_cli = newParser;\n    }\n    ConfigData& Session::configData() {\n        return m_configData;\n    }\n    Config& Session::config() {\n        if( !m_config )\n            m_config = Detail::make_unique<Config>( m_configData );\n        return *m_config;\n    }\n\n    int Session::runInternal() {\n        if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; }\n\n        if (m_configData.showHelp || m_configData.libIdentify) {\n            return 0;\n        }\n\n        if ( m_configData.shardIndex >= m_configData.shardCount ) {\n            Catch::cerr() << \"The shard count (\" << m_configData.shardCount\n                          << \") must be greater than the shard index (\"\n                          << m_configData.shardIndex << \")\\n\"\n                          << std::flush;\n            return UnspecifiedErrorExitCode;\n        }\n\n        CATCH_TRY {\n            config(); // Force config to be constructed\n\n            seedRng( *m_config );\n\n            if (m_configData.filenamesAsTags) {\n                applyFilenamesAsTags();\n            }\n\n            // Set up global config instance before we start calling into other functions\n            getCurrentMutableContext().setConfig(m_config.get());\n\n            // Create reporter(s) so we can route listings through them\n            auto reporter = prepareReporters(m_config.get());\n\n            auto const& invalidSpecs = m_config->testSpec().getInvalidSpecs();\n            if ( !invalidSpecs.empty() ) {\n                for ( auto const& spec : invalidSpecs ) {\n                    reporter->reportInvalidTestSpec( spec );\n                }\n                return InvalidTestSpecExitCode;\n            }\n\n\n            // Handle list request\n            if (list(*reporter, *m_config)) {\n                return 0;\n            }\n\n            TestGroup tests { CATCH_MOVE(reporter), m_config.get() };\n            auto const totals = tests.execute();\n\n            if ( tests.hadUnmatchedTestSpecs()\n                && m_config->warnAboutUnmatchedTestSpecs() ) {\n                // UnmatchedTestSpecExitCode\n                return UnmatchedTestSpecExitCode;\n            }\n\n            if ( totals.testCases.total() == 0\n                && !m_config->zeroTestsCountAsSuccess() ) {\n                return NoTestsRunExitCode;\n            }\n\n            if ( totals.testCases.total() > 0 &&\n                 totals.testCases.total() == totals.testCases.skipped\n                && !m_config->zeroTestsCountAsSuccess() ) {\n                return AllTestsSkippedExitCode;\n            }\n\n            if ( totals.assertions.failed ) { return TestFailureExitCode; }\n            return 0;\n\n        }\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n        catch( std::exception& ex ) {\n            Catch::cerr() << ex.what() << '\\n' << std::flush;\n            return UnspecifiedErrorExitCode;\n        }\n#endif\n    }\n\n} // end namespace Catch\n\n\n\n\nnamespace Catch {\n\n    RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) {\n        CATCH_TRY {\n            getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo);\n        } CATCH_CATCH_ALL {\n            // Do not throw when constructing global objects, instead register the exception to be processed later\n            getMutableRegistryHub().registerStartupException();\n        }\n    }\n\n}\n\n\n\n#include <cassert>\n#include <cctype>\n#include <algorithm>\n\nnamespace Catch {\n\n    namespace {\n        using TCP_underlying_type = uint8_t;\n        static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type),\n                      \"The size of the TestCaseProperties is different from the assumed size\");\n\n        constexpr TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) {\n            return static_cast<TestCaseProperties>(\n                static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)\n            );\n        }\n\n        constexpr TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) {\n            lhs = static_cast<TestCaseProperties>(\n                static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)\n            );\n            return lhs;\n        }\n\n        constexpr TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) {\n            return static_cast<TestCaseProperties>(\n                static_cast<TCP_underlying_type>(lhs) & static_cast<TCP_underlying_type>(rhs)\n            );\n        }\n\n        constexpr bool applies(TestCaseProperties tcp) {\n            static_assert(static_cast<TCP_underlying_type>(TestCaseProperties::None) == 0,\n                          \"TestCaseProperties::None must be equal to 0\");\n            return tcp != TestCaseProperties::None;\n        }\n\n        TestCaseProperties parseSpecialTag( StringRef tag ) {\n            if( !tag.empty() && tag[0] == '.' )\n                return TestCaseProperties::IsHidden;\n            else if( tag == \"!throws\"_sr )\n                return TestCaseProperties::Throws;\n            else if( tag == \"!shouldfail\"_sr )\n                return TestCaseProperties::ShouldFail;\n            else if( tag == \"!mayfail\"_sr )\n                return TestCaseProperties::MayFail;\n            else if( tag == \"!nonportable\"_sr )\n                return TestCaseProperties::NonPortable;\n            else if( tag == \"!benchmark\"_sr )\n                return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden;\n            else\n                return TestCaseProperties::None;\n        }\n        bool isReservedTag( StringRef tag ) {\n            return parseSpecialTag( tag ) == TestCaseProperties::None\n                && tag.size() > 0\n                && !std::isalnum( static_cast<unsigned char>(tag[0]) );\n        }\n        void enforceNotReservedTag( StringRef tag, SourceLineInfo const& _lineInfo ) {\n            CATCH_ENFORCE( !isReservedTag(tag),\n                          \"Tag name: [\" << tag << \"] is not allowed.\\n\"\n                          << \"Tag names starting with non alphanumeric characters are reserved\\n\"\n                          << _lineInfo );\n        }\n\n        std::string makeDefaultName() {\n            static size_t counter = 0;\n            return \"Anonymous test case \" + std::to_string(++counter);\n        }\n\n        constexpr StringRef extractFilenamePart(StringRef filename) {\n            size_t lastDot = filename.size();\n            while (lastDot > 0 && filename[lastDot - 1] != '.') {\n                --lastDot;\n            }\n            // In theory we could have filename without any extension in it\n            if ( lastDot == 0 ) { return StringRef(); }\n\n            --lastDot;\n            size_t nameStart = lastDot;\n            while (nameStart > 0 && filename[nameStart - 1] != '/' && filename[nameStart - 1] != '\\\\') {\n                --nameStart;\n            }\n\n            return filename.substr(nameStart, lastDot - nameStart);\n        }\n\n        // Returns the upper bound on size of extra tags ([#file]+[.])\n        constexpr size_t sizeOfExtraTags(StringRef filepath) {\n            // [.] is 3, [#] is another 3\n            const size_t extras = 3 + 3;\n            return extractFilenamePart(filepath).size() + extras;\n        }\n    } // end unnamed namespace\n\n    bool operator<(  Tag const& lhs, Tag const& rhs ) {\n        Detail::CaseInsensitiveLess cmp;\n        return cmp( lhs.original, rhs.original );\n    }\n    bool operator==( Tag const& lhs, Tag const& rhs ) {\n        Detail::CaseInsensitiveEqualTo cmp;\n        return cmp( lhs.original, rhs.original );\n    }\n\n    Detail::unique_ptr<TestCaseInfo>\n        makeTestCaseInfo(StringRef _className,\n                         NameAndTags const& nameAndTags,\n                         SourceLineInfo const& _lineInfo ) {\n        return Detail::make_unique<TestCaseInfo>(_className, nameAndTags, _lineInfo);\n    }\n\n    TestCaseInfo::TestCaseInfo(StringRef _className,\n                               NameAndTags const& _nameAndTags,\n                               SourceLineInfo const& _lineInfo):\n        name( _nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name ),\n        className( _className ),\n        lineInfo( _lineInfo )\n    {\n        StringRef originalTags = _nameAndTags.tags;\n        // We need to reserve enough space to store all of the tags\n        // (including optional hidden tag and filename tag)\n        auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file);\n        backingTags.reserve(requiredSize);\n\n        // We cannot copy the tags directly, as we need to normalize\n        // some tags, so that [.foo] is copied as [.][foo].\n        size_t tagStart = 0;\n        size_t tagEnd = 0;\n        bool inTag = false;\n        for (size_t idx = 0; idx < originalTags.size(); ++idx) {\n            auto c = originalTags[idx];\n            if (c == '[') {\n                CATCH_ENFORCE(\n                    !inTag,\n                    \"Found '[' inside a tag while registering test case '\"\n                        << _nameAndTags.name << \"' at \" << _lineInfo );\n\n                inTag = true;\n                tagStart = idx;\n            }\n            if (c == ']') {\n                CATCH_ENFORCE(\n                    inTag,\n                    \"Found unmatched ']' while registering test case '\"\n                        << _nameAndTags.name << \"' at \" << _lineInfo );\n\n                inTag = false;\n                tagEnd = idx;\n                assert(tagStart < tagEnd);\n\n                // We need to check the tag for special meanings, copy\n                // it over to backing storage and actually reference the\n                // backing storage in the saved tags\n                StringRef tagStr = originalTags.substr(tagStart+1, tagEnd - tagStart - 1);\n                CATCH_ENFORCE( !tagStr.empty(),\n                               \"Found an empty tag while registering test case '\"\n                                   << _nameAndTags.name << \"' at \"\n                                   << _lineInfo );\n\n                enforceNotReservedTag(tagStr, lineInfo);\n                properties |= parseSpecialTag(tagStr);\n                // When copying a tag to the backing storage, we need to\n                // check if it is a merged hide tag, such as [.foo], and\n                // if it is, we need to handle it as if it was [foo].\n                if (tagStr.size() > 1 && tagStr[0] == '.') {\n                    tagStr = tagStr.substr(1, tagStr.size() - 1);\n                }\n                // We skip over dealing with the [.] tag, as we will add\n                // it later unconditionally and then sort and unique all\n                // the tags.\n                internalAppendTag(tagStr);\n            }\n        }\n        CATCH_ENFORCE( !inTag,\n                       \"Found an unclosed tag while registering test case '\"\n                           << _nameAndTags.name << \"' at \" << _lineInfo );\n\n\n        // Add [.] if relevant\n        if (isHidden()) {\n            internalAppendTag(\".\"_sr);\n        }\n\n        // Sort and prepare tags\n        std::sort(begin(tags), end(tags));\n        tags.erase(std::unique(begin(tags), end(tags)),\n                   end(tags));\n    }\n\n    bool TestCaseInfo::isHidden() const {\n        return applies( properties & TestCaseProperties::IsHidden );\n    }\n    bool TestCaseInfo::throws() const {\n        return applies( properties & TestCaseProperties::Throws );\n    }\n    bool TestCaseInfo::okToFail() const {\n        return applies( properties & (TestCaseProperties::ShouldFail | TestCaseProperties::MayFail ) );\n    }\n    bool TestCaseInfo::expectedToFail() const {\n        return applies( properties & (TestCaseProperties::ShouldFail) );\n    }\n\n    void TestCaseInfo::addFilenameTag() {\n        std::string combined(\"#\");\n        combined += extractFilenamePart(lineInfo.file);\n        internalAppendTag(combined);\n    }\n\n    std::string TestCaseInfo::tagsAsString() const {\n        std::string ret;\n        // '[' and ']' per tag\n        std::size_t full_size = 2 * tags.size();\n        for (const auto& tag : tags) {\n            full_size += tag.original.size();\n        }\n        ret.reserve(full_size);\n        for (const auto& tag : tags) {\n            ret.push_back('[');\n            ret += tag.original;\n            ret.push_back(']');\n        }\n\n        return ret;\n    }\n\n    void TestCaseInfo::internalAppendTag(StringRef tagStr) {\n        backingTags += '[';\n        const auto backingStart = backingTags.size();\n        backingTags += tagStr;\n        const auto backingEnd = backingTags.size();\n        backingTags += ']';\n        tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart));\n    }\n\n    bool operator<( TestCaseInfo const& lhs, TestCaseInfo const& rhs ) {\n        // We want to avoid redoing the string comparisons multiple times,\n        // so we store the result of a three-way comparison before using\n        // it in the actual comparison logic.\n        const auto cmpName = lhs.name.compare( rhs.name );\n        if ( cmpName != 0 ) {\n            return cmpName < 0;\n        }\n        const auto cmpClassName = lhs.className.compare( rhs.className );\n        if ( cmpClassName != 0 ) {\n            return cmpClassName < 0;\n        }\n        return lhs.tags < rhs.tags;\n    }\n\n} // end namespace Catch\n\n\n\n#include <algorithm>\n#include <string>\n#include <vector>\n#include <ostream>\n\nnamespace Catch {\n\n    TestSpec::Pattern::Pattern( std::string const& name )\n    : m_name( name )\n    {}\n\n    TestSpec::Pattern::~Pattern() = default;\n\n    std::string const& TestSpec::Pattern::name() const {\n        return m_name;\n    }\n\n\n    TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString )\n    : Pattern( filterString )\n    , m_wildcardPattern( toLower( name ), CaseSensitive::No )\n    {}\n\n    bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const {\n        return m_wildcardPattern.matches( testCase.name );\n    }\n\n    void TestSpec::NamePattern::serializeTo( std::ostream& out ) const {\n        out << '\"' << name() << '\"';\n    }\n\n\n    TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString )\n    : Pattern( filterString )\n    , m_tag( tag )\n    {}\n\n    bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const {\n        return std::find( begin( testCase.tags ),\n                          end( testCase.tags ),\n                          Tag( m_tag ) ) != end( testCase.tags );\n    }\n\n    void TestSpec::TagPattern::serializeTo( std::ostream& out ) const {\n        out << name();\n    }\n\n    bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {\n        bool should_use = !testCase.isHidden();\n        for (auto const& pattern : m_required) {\n            should_use = true;\n            if (!pattern->matches(testCase)) {\n                return false;\n            }\n        }\n        for (auto const& pattern : m_forbidden) {\n            if (pattern->matches(testCase)) {\n                return false;\n            }\n        }\n        return should_use;\n    }\n\n    void TestSpec::Filter::serializeTo( std::ostream& out ) const {\n        bool first = true;\n        for ( auto const& pattern : m_required ) {\n            if ( !first ) {\n                out << ' ';\n            }\n            out << *pattern;\n            first = false;\n        }\n        for ( auto const& pattern : m_forbidden ) {\n            if ( !first ) {\n                out << ' ';\n            }\n            out << *pattern;\n            first = false;\n        }\n    }\n\n\n    std::string TestSpec::extractFilterName( Filter const& filter ) {\n        Catch::ReusableStringStream sstr;\n        sstr << filter;\n        return sstr.str();\n    }\n\n    bool TestSpec::hasFilters() const {\n        return !m_filters.empty();\n    }\n\n    bool TestSpec::matches( TestCaseInfo const& testCase ) const {\n        return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } );\n    }\n\n    TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const {\n        Matches matches;\n        matches.reserve( m_filters.size() );\n        for ( auto const& filter : m_filters ) {\n            std::vector<TestCaseHandle const*> currentMatches;\n            for ( auto const& test : testCases )\n                if ( isThrowSafe( test, config ) &&\n                     filter.matches( test.getTestCaseInfo() ) )\n                    currentMatches.emplace_back( &test );\n            matches.push_back(\n                FilterMatch{ extractFilterName( filter ), currentMatches } );\n        }\n        return matches;\n    }\n\n    const TestSpec::vectorStrings& TestSpec::getInvalidSpecs() const {\n        return m_invalidSpecs;\n    }\n\n    void TestSpec::serializeTo( std::ostream& out ) const {\n        bool first = true;\n        for ( auto const& filter : m_filters ) {\n            if ( !first ) {\n                out << ',';\n            }\n            out << filter;\n            first = false;\n        }\n    }\n\n}\n\n\n\n#include <chrono>\n\nnamespace Catch {\n\n    namespace {\n        static auto getCurrentNanosecondsSinceEpoch() -> uint64_t {\n            return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();\n        }\n    } // end unnamed namespace\n\n    void Timer::start() {\n       m_nanoseconds = getCurrentNanosecondsSinceEpoch();\n    }\n    auto Timer::getElapsedNanoseconds() const -> uint64_t {\n        return getCurrentNanosecondsSinceEpoch() - m_nanoseconds;\n    }\n    auto Timer::getElapsedMicroseconds() const -> uint64_t {\n        return getElapsedNanoseconds()/1000;\n    }\n    auto Timer::getElapsedMilliseconds() const -> unsigned int {\n        return static_cast<unsigned int>(getElapsedMicroseconds()/1000);\n    }\n    auto Timer::getElapsedSeconds() const -> double {\n        return static_cast<double>(getElapsedMicroseconds())/1000000.0;\n    }\n\n\n} // namespace Catch\n\n\n\n\n#include <iomanip>\n\nnamespace Catch {\n\nnamespace Detail {\n\n    namespace {\n        const int hexThreshold = 255;\n\n        struct Endianness {\n            enum Arch : uint8_t {\n                Big,\n                Little\n            };\n\n            static Arch which() {\n                int one = 1;\n                // If the lowest byte we read is non-zero, we can assume\n                // that little endian format is used.\n                auto value = *reinterpret_cast<char*>(&one);\n                return value ? Little : Big;\n            }\n        };\n\n        template<typename T>\n        std::string fpToString(T value, int precision) {\n            if (Catch::isnan(value)) {\n                return \"nan\";\n            }\n\n            ReusableStringStream rss;\n            rss << std::setprecision(precision)\n                << std::fixed\n                << value;\n            std::string d = rss.str();\n            std::size_t i = d.find_last_not_of('0');\n            if (i != std::string::npos && i != d.size() - 1) {\n                if (d[i] == '.')\n                    i++;\n                d = d.substr(0, i + 1);\n            }\n            return d;\n        }\n    } // end unnamed namespace\n\n    std::string convertIntoString(StringRef string, bool escapeInvisibles) {\n        std::string ret;\n        // This is enough for the \"don't escape invisibles\" case, and a good\n        // lower bound on the \"escape invisibles\" case.\n        ret.reserve(string.size() + 2);\n\n        if (!escapeInvisibles) {\n            ret += '\"';\n            ret += string;\n            ret += '\"';\n            return ret;\n        }\n\n        ret += '\"';\n        for (char c : string) {\n            switch (c) {\n            case '\\r':\n                ret.append(\"\\\\r\");\n                break;\n            case '\\n':\n                ret.append(\"\\\\n\");\n                break;\n            case '\\t':\n                ret.append(\"\\\\t\");\n                break;\n            case '\\f':\n                ret.append(\"\\\\f\");\n                break;\n            default:\n                ret.push_back(c);\n                break;\n            }\n        }\n        ret += '\"';\n\n        return ret;\n    }\n\n    std::string convertIntoString(StringRef string) {\n        return convertIntoString(string, getCurrentContext().getConfig()->showInvisibles());\n    }\n\n    std::string rawMemoryToString( const void *object, std::size_t size ) {\n        // Reverse order for little endian architectures\n        int i = 0, end = static_cast<int>( size ), inc = 1;\n        if( Endianness::which() == Endianness::Little ) {\n            i = end-1;\n            end = inc = -1;\n        }\n\n        unsigned char const *bytes = static_cast<unsigned char const *>(object);\n        ReusableStringStream rss;\n        rss << \"0x\" << std::setfill('0') << std::hex;\n        for( ; i != end; i += inc )\n             rss << std::setw(2) << static_cast<unsigned>(bytes[i]);\n       return rss.str();\n    }\n} // end Detail namespace\n\n\n\n//// ======================================================= ////\n//\n//   Out-of-line defs for full specialization of StringMaker\n//\n//// ======================================================= ////\n\nstd::string StringMaker<std::string>::convert(const std::string& str) {\n    return Detail::convertIntoString( str );\n}\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\nstd::string StringMaker<std::string_view>::convert(std::string_view str) {\n    return Detail::convertIntoString( StringRef( str.data(), str.size() ) );\n}\n#endif\n\nstd::string StringMaker<char const*>::convert(char const* str) {\n    if (str) {\n        return Detail::convertIntoString( str );\n    } else {\n        return{ \"{null string}\" };\n    }\n}\nstd::string StringMaker<char*>::convert(char* str) { // NOLINT(readability-non-const-parameter)\n    if (str) {\n        return Detail::convertIntoString( str );\n    } else {\n        return{ \"{null string}\" };\n    }\n}\n\n#ifdef CATCH_CONFIG_WCHAR\nstd::string StringMaker<std::wstring>::convert(const std::wstring& wstr) {\n    std::string s;\n    s.reserve(wstr.size());\n    for (auto c : wstr) {\n        s += (c <= 0xff) ? static_cast<char>(c) : '?';\n    }\n    return ::Catch::Detail::stringify(s);\n}\n\n# ifdef CATCH_CONFIG_CPP17_STRING_VIEW\nstd::string StringMaker<std::wstring_view>::convert(std::wstring_view str) {\n    return StringMaker<std::wstring>::convert(std::wstring(str));\n}\n# endif\n\nstd::string StringMaker<wchar_t const*>::convert(wchar_t const * str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::wstring{ str });\n    } else {\n        return{ \"{null string}\" };\n    }\n}\nstd::string StringMaker<wchar_t *>::convert(wchar_t * str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::wstring{ str });\n    } else {\n        return{ \"{null string}\" };\n    }\n}\n#endif\n\n#if defined(CATCH_CONFIG_CPP17_BYTE)\n#include <cstddef>\nstd::string StringMaker<std::byte>::convert(std::byte value) {\n    return ::Catch::Detail::stringify(std::to_integer<unsigned long long>(value));\n}\n#endif // defined(CATCH_CONFIG_CPP17_BYTE)\n\nstd::string StringMaker<int>::convert(int value) {\n    return ::Catch::Detail::stringify(static_cast<long long>(value));\n}\nstd::string StringMaker<long>::convert(long value) {\n    return ::Catch::Detail::stringify(static_cast<long long>(value));\n}\nstd::string StringMaker<long long>::convert(long long value) {\n    ReusableStringStream rss;\n    rss << value;\n    if (value > Detail::hexThreshold) {\n        rss << \" (0x\" << std::hex << value << ')';\n    }\n    return rss.str();\n}\n\nstd::string StringMaker<unsigned int>::convert(unsigned int value) {\n    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));\n}\nstd::string StringMaker<unsigned long>::convert(unsigned long value) {\n    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));\n}\nstd::string StringMaker<unsigned long long>::convert(unsigned long long value) {\n    ReusableStringStream rss;\n    rss << value;\n    if (value > Detail::hexThreshold) {\n        rss << \" (0x\" << std::hex << value << ')';\n    }\n    return rss.str();\n}\n\nstd::string StringMaker<signed char>::convert(signed char value) {\n    if (value == '\\r') {\n        return \"'\\\\r'\";\n    } else if (value == '\\f') {\n        return \"'\\\\f'\";\n    } else if (value == '\\n') {\n        return \"'\\\\n'\";\n    } else if (value == '\\t') {\n        return \"'\\\\t'\";\n    } else if ('\\0' <= value && value < ' ') {\n        return ::Catch::Detail::stringify(static_cast<unsigned int>(value));\n    } else {\n        char chstr[] = \"' '\";\n        chstr[1] = value;\n        return chstr;\n    }\n}\nstd::string StringMaker<char>::convert(char c) {\n    return ::Catch::Detail::stringify(static_cast<signed char>(c));\n}\nstd::string StringMaker<unsigned char>::convert(unsigned char value) {\n    return ::Catch::Detail::stringify(static_cast<char>(value));\n}\n\nint StringMaker<float>::precision = std::numeric_limits<float>::max_digits10;\n\nstd::string StringMaker<float>::convert(float value) {\n    return Detail::fpToString(value, precision) + 'f';\n}\n\nint StringMaker<double>::precision = std::numeric_limits<double>::max_digits10;\n\nstd::string StringMaker<double>::convert(double value) {\n    return Detail::fpToString(value, precision);\n}\n\n} // end namespace Catch\n\n\n\nnamespace Catch {\n\n    Counts Counts::operator - ( Counts const& other ) const {\n        Counts diff;\n        diff.passed = passed - other.passed;\n        diff.failed = failed - other.failed;\n        diff.failedButOk = failedButOk - other.failedButOk;\n        diff.skipped = skipped - other.skipped;\n        return diff;\n    }\n\n    Counts& Counts::operator += ( Counts const& other ) {\n        passed += other.passed;\n        failed += other.failed;\n        failedButOk += other.failedButOk;\n        skipped += other.skipped;\n        return *this;\n    }\n\n    std::uint64_t Counts::total() const {\n        return passed + failed + failedButOk + skipped;\n    }\n    bool Counts::allPassed() const {\n        return failed == 0 && failedButOk == 0 && skipped == 0;\n    }\n    bool Counts::allOk() const {\n        return failed == 0;\n    }\n\n    Totals Totals::operator - ( Totals const& other ) const {\n        Totals diff;\n        diff.assertions = assertions - other.assertions;\n        diff.testCases = testCases - other.testCases;\n        return diff;\n    }\n\n    Totals& Totals::operator += ( Totals const& other ) {\n        assertions += other.assertions;\n        testCases += other.testCases;\n        return *this;\n    }\n\n    Totals Totals::delta( Totals const& prevTotals ) const {\n        Totals diff = *this - prevTotals;\n        if( diff.assertions.failed > 0 )\n            ++diff.testCases.failed;\n        else if( diff.assertions.failedButOk > 0 )\n            ++diff.testCases.failedButOk;\n        else if ( diff.assertions.skipped > 0 )\n            ++ diff.testCases.skipped;\n        else\n            ++diff.testCases.passed;\n        return diff;\n    }\n\n}\n\n\n\n\nnamespace Catch {\n    namespace Detail {\n        void registerTranslatorImpl(\n            Detail::unique_ptr<IExceptionTranslator>&& translator ) {\n            getMutableRegistryHub().registerTranslator(\n                CATCH_MOVE( translator ) );\n        }\n    } // namespace Detail\n} // namespace Catch\n\n\n#include <ostream>\n\nnamespace Catch {\n\n    Version::Version\n        (   unsigned int _majorVersion,\n            unsigned int _minorVersion,\n            unsigned int _patchNumber,\n            char const * const _branchName,\n            unsigned int _buildNumber )\n    :   majorVersion( _majorVersion ),\n        minorVersion( _minorVersion ),\n        patchNumber( _patchNumber ),\n        branchName( _branchName ),\n        buildNumber( _buildNumber )\n    {}\n\n    std::ostream& operator << ( std::ostream& os, Version const& version ) {\n        os  << version.majorVersion << '.'\n            << version.minorVersion << '.'\n            << version.patchNumber;\n        // branchName is never null -> 0th char is \\0 if it is empty\n        if (version.branchName[0]) {\n            os << '-' << version.branchName\n               << '.' << version.buildNumber;\n        }\n        return os;\n    }\n\n    Version const& libraryVersion() {\n        static Version version( 3, 9, 0, \"\", 0 );\n        return version;\n    }\n\n}\n\n\n\n\nnamespace Catch {\n\n    const char* GeneratorException::what() const noexcept {\n        return m_msg;\n    }\n\n} // end namespace Catch\n\n\n\n\nnamespace Catch {\n\n    IGeneratorTracker::~IGeneratorTracker() = default;\n\nnamespace Generators {\n\nnamespace Detail {\n\n    [[noreturn]]\n    void throw_generator_exception(char const* msg) {\n        Catch::throw_exception(GeneratorException{ msg });\n    }\n} // end namespace Detail\n\n    GeneratorUntypedBase::~GeneratorUntypedBase() = default;\n\n    IGeneratorTracker* acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const& lineInfo ) {\n        return getResultCapture().acquireGeneratorTracker( generatorName, lineInfo );\n    }\n\n    IGeneratorTracker* createGeneratorTracker( StringRef generatorName,\n                                 SourceLineInfo lineInfo,\n                                 GeneratorBasePtr&& generator ) {\n        return getResultCapture().createGeneratorTracker(\n            generatorName, lineInfo, CATCH_MOVE( generator ) );\n    }\n\n} // namespace Generators\n} // namespace Catch\n\n\n\n\n#include <random>\n\nnamespace Catch {\n    namespace Generators {\n        namespace Detail {\n            std::uint32_t getSeed() { return sharedRng()(); }\n        } // namespace Detail\n\n        struct RandomFloatingGenerator<long double>::PImpl {\n            PImpl( long double a, long double b, uint32_t seed ):\n                rng( seed ), dist( a, b ) {}\n\n            Catch::SimplePcg32 rng;\n            std::uniform_real_distribution<long double> dist;\n        };\n\n        RandomFloatingGenerator<long double>::RandomFloatingGenerator(\n            long double a, long double b, std::uint32_t seed) :\n            m_pimpl(Catch::Detail::make_unique<PImpl>(a, b, seed)) {\n            static_cast<void>( next() );\n        }\n\n        RandomFloatingGenerator<long double>::~RandomFloatingGenerator() =\n            default;\n        bool RandomFloatingGenerator<long double>::next() {\n            m_current_number = m_pimpl->dist( m_pimpl->rng );\n            return true;\n        }\n    } // namespace Generators\n} // namespace Catch\n\n\n\n\nnamespace Catch {\n    IResultCapture::~IResultCapture() = default;\n}\n\n\n\n\nnamespace Catch {\n    IConfig::~IConfig() = default;\n}\n\n\n\n\nnamespace Catch {\n    IExceptionTranslator::~IExceptionTranslator() = default;\n    IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default;\n}\n\n\n\n#include <string>\n\nnamespace Catch {\n    namespace Generators {\n\n        bool GeneratorUntypedBase::countedNext() {\n            auto ret = next();\n            if ( ret ) {\n                m_stringReprCache.clear();\n                ++m_currentElementIndex;\n            }\n            return ret;\n        }\n\n        StringRef GeneratorUntypedBase::currentElementAsString() const {\n            if ( m_stringReprCache.empty() ) {\n                m_stringReprCache = stringifyImpl();\n            }\n            return m_stringReprCache;\n        }\n\n    } // namespace Generators\n} // namespace Catch\n\n\n\n\nnamespace Catch {\n    IRegistryHub::~IRegistryHub() = default;\n    IMutableRegistryHub::~IMutableRegistryHub() = default;\n}\n\n\n\n#include <cassert>\n\nnamespace Catch {\n\n    ReporterConfig::ReporterConfig(\n        IConfig const* _fullConfig,\n        Detail::unique_ptr<IStream> _stream,\n        ColourMode colourMode,\n        std::map<std::string, std::string> customOptions ):\n        m_stream( CATCH_MOVE(_stream) ),\n        m_fullConfig( _fullConfig ),\n        m_colourMode( colourMode ),\n        m_customOptions( CATCH_MOVE( customOptions ) ) {}\n\n    Detail::unique_ptr<IStream> ReporterConfig::takeStream() && {\n        assert( m_stream );\n        return CATCH_MOVE( m_stream );\n    }\n    IConfig const * ReporterConfig::fullConfig() const { return m_fullConfig; }\n    ColourMode ReporterConfig::colourMode() const { return m_colourMode; }\n\n    std::map<std::string, std::string> const&\n    ReporterConfig::customOptions() const {\n        return m_customOptions;\n    }\n\n    ReporterConfig::~ReporterConfig() = default;\n\n    AssertionStats::AssertionStats( AssertionResult const& _assertionResult,\n                                    std::vector<MessageInfo> const& _infoMessages,\n                                    Totals const& _totals )\n    :   assertionResult( _assertionResult ),\n        infoMessages( _infoMessages ),\n        totals( _totals )\n    {\n        if( assertionResult.hasMessage() ) {\n            // Copy message into messages list.\n            // !TBD This should have been done earlier, somewhere\n            MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() );\n            builder.m_info.message = static_cast<std::string>(assertionResult.getMessage());\n\n            infoMessages.push_back( CATCH_MOVE(builder.m_info) );\n        }\n    }\n\n    SectionStats::SectionStats(  SectionInfo&& _sectionInfo,\n                                 Counts const& _assertions,\n                                 double _durationInSeconds,\n                                 bool _missingAssertions )\n    :   sectionInfo( CATCH_MOVE(_sectionInfo) ),\n        assertions( _assertions ),\n        durationInSeconds( _durationInSeconds ),\n        missingAssertions( _missingAssertions )\n    {}\n\n\n    TestCaseStats::TestCaseStats(  TestCaseInfo const& _testInfo,\n                                   Totals const& _totals,\n                                   std::string&& _stdOut,\n                                   std::string&& _stdErr,\n                                   bool _aborting )\n    : testInfo( &_testInfo ),\n        totals( _totals ),\n        stdOut( CATCH_MOVE(_stdOut) ),\n        stdErr( CATCH_MOVE(_stdErr) ),\n        aborting( _aborting )\n    {}\n\n\n    TestRunStats::TestRunStats(   TestRunInfo const& _runInfo,\n                    Totals const& _totals,\n                    bool _aborting )\n    :   runInfo( _runInfo ),\n        totals( _totals ),\n        aborting( _aborting )\n    {}\n\n    IEventListener::~IEventListener() = default;\n\n} // end namespace Catch\n\n\n\n\nnamespace Catch {\n    IReporterFactory::~IReporterFactory() = default;\n    EventListenerFactory::~EventListenerFactory() = default;\n}\n\n\n\n\nnamespace Catch {\n    ITestCaseRegistry::~ITestCaseRegistry() = default;\n}\n\n\n\nnamespace Catch {\n\n    AssertionHandler::AssertionHandler\n        (   StringRef macroName,\n            SourceLineInfo const& lineInfo,\n            StringRef capturedExpression,\n            ResultDisposition::Flags resultDisposition )\n    :   m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition },\n        m_resultCapture( getResultCapture() )\n    {\n        m_resultCapture.notifyAssertionStarted( m_assertionInfo );\n    }\n\n    void AssertionHandler::handleExpr( ITransientExpression const& expr ) {\n        m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction );\n    }\n    void AssertionHandler::handleMessage(ResultWas::OfType resultType, std::string&& message) {\n        m_resultCapture.handleMessage( m_assertionInfo, resultType, CATCH_MOVE(message), m_reaction );\n    }\n\n    auto AssertionHandler::allowThrows() const -> bool {\n        return getCurrentContext().getConfig()->allowThrows();\n    }\n\n    void AssertionHandler::complete() {\n        m_completed = true;\n        if( m_reaction.shouldDebugBreak ) {\n\n            // If you find your debugger stopping you here then go one level up on the\n            // call-stack for the code that caused it (typically a failed assertion)\n\n            // (To go back to the test and change execution, jump over the throw, next)\n            CATCH_BREAK_INTO_DEBUGGER();\n        }\n        if (m_reaction.shouldThrow) {\n            throw_test_failure_exception();\n        }\n        if ( m_reaction.shouldSkip ) {\n            throw_test_skip_exception();\n        }\n    }\n\n    void AssertionHandler::handleUnexpectedInflightException() {\n        m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction );\n    }\n\n    void AssertionHandler::handleExceptionThrownAsExpected() {\n        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n    }\n    void AssertionHandler::handleExceptionNotThrownAsExpected() {\n        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n    }\n\n    void AssertionHandler::handleUnexpectedExceptionNotThrown() {\n        m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction );\n    }\n\n    void AssertionHandler::handleThrowingCallSkipped() {\n        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n    }\n\n    // This is the overload that takes a string and infers the Equals matcher from it\n    // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp\n    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str ) {\n        handleExceptionMatchExpr( handler, Matchers::Equals( str ) );\n    }\n\n} // namespace Catch\n\n\n\n\n#include <algorithm>\n\nnamespace Catch {\n    namespace Detail {\n\n        bool CaseInsensitiveLess::operator()( StringRef lhs,\n                                              StringRef rhs ) const {\n            return std::lexicographical_compare(\n                lhs.begin(), lhs.end(),\n                rhs.begin(), rhs.end(),\n                []( char l, char r ) { return toLower( l ) < toLower( r ); } );\n        }\n\n        bool\n        CaseInsensitiveEqualTo::operator()( StringRef lhs,\n                                            StringRef rhs ) const {\n            return std::equal(\n                lhs.begin(), lhs.end(),\n                rhs.begin(), rhs.end(),\n                []( char l, char r ) { return toLower( l ) == toLower( r ); } );\n        }\n\n    } // namespace Detail\n} // namespace Catch\n\n\n\n\n#include <algorithm>\n#include <ostream>\n\nnamespace {\n    bool isOptPrefix( char c ) {\n        return c == '-'\n#ifdef CATCH_PLATFORM_WINDOWS\n               || c == '/'\n#endif\n            ;\n    }\n\n    Catch::StringRef normaliseOpt( Catch::StringRef optName ) {\n        if ( optName[0] == '-'\n#if defined(CATCH_PLATFORM_WINDOWS)\n             || optName[0] == '/'\n#endif\n        ) {\n            return optName.substr( 1, optName.size() );\n        }\n\n        return optName;\n    }\n\n    static size_t find_first_separator(Catch::StringRef sr) {\n        auto is_separator = []( char c ) {\n            return c == ' ' || c == ':' || c == '=';\n        };\n        size_t pos = 0;\n        while (pos < sr.size()) {\n            if (is_separator(sr[pos])) { return pos; }\n            ++pos;\n        }\n\n        return Catch::StringRef::npos;\n    }\n\n} // namespace\n\nnamespace Catch {\n    namespace Clara {\n        namespace Detail {\n\n            void TokenStream::loadBuffer() {\n                m_tokenBuffer.clear();\n\n                // Skip any empty strings\n                while ( it != itEnd && it->empty() ) {\n                    ++it;\n                }\n\n                if ( it != itEnd ) {\n                    StringRef next = *it;\n                    if ( isOptPrefix( next[0] ) ) {\n                        auto delimiterPos = find_first_separator(next);\n                        if ( delimiterPos != StringRef::npos ) {\n                            m_tokenBuffer.push_back(\n                                { TokenType::Option,\n                                  next.substr( 0, delimiterPos ) } );\n                            m_tokenBuffer.push_back(\n                                { TokenType::Argument,\n                                  next.substr( delimiterPos + 1, next.size() ) } );\n                        } else {\n                            if ( next.size() > 1 && next[1] != '-' && next.size() > 2 ) {\n                                // Combined short args, e.g. \"-ab\" for \"-a -b\"\n                                for ( size_t i = 1; i < next.size(); ++i ) {\n                                    m_tokenBuffer.push_back(\n                                        { TokenType::Option,\n                                          next.substr( i, 1 ) } );\n                                }\n                            } else {\n                                m_tokenBuffer.push_back(\n                                    { TokenType::Option, next } );\n                            }\n                        }\n                    } else {\n                        m_tokenBuffer.push_back(\n                            { TokenType::Argument, next } );\n                    }\n                }\n            }\n\n            TokenStream::TokenStream( Args const& args ):\n                TokenStream( args.m_args.begin(), args.m_args.end() ) {}\n\n            TokenStream::TokenStream( Iterator it_, Iterator itEnd_ ):\n                it( it_ ), itEnd( itEnd_ ) {\n                loadBuffer();\n            }\n\n            TokenStream& TokenStream::operator++() {\n                if ( m_tokenBuffer.size() >= 2 ) {\n                    m_tokenBuffer.erase( m_tokenBuffer.begin() );\n                } else {\n                    if ( it != itEnd )\n                        ++it;\n                    loadBuffer();\n                }\n                return *this;\n            }\n\n            ParserResult convertInto( std::string const& source,\n                                      std::string& target ) {\n                target = source;\n                return ParserResult::ok( ParseResultType::Matched );\n            }\n\n            ParserResult convertInto( std::string const& source,\n                                      bool& target ) {\n                std::string srcLC = toLower( source );\n\n                if ( srcLC == \"y\" || srcLC == \"1\" || srcLC == \"true\" ||\n                     srcLC == \"yes\" || srcLC == \"on\" ) {\n                    target = true;\n                } else if ( srcLC == \"n\" || srcLC == \"0\" || srcLC == \"false\" ||\n                            srcLC == \"no\" || srcLC == \"off\" ) {\n                    target = false;\n                } else {\n                    return ParserResult::runtimeError(\n                        \"Expected a boolean value but did not recognise: '\" +\n                        source + '\\'' );\n                }\n                return ParserResult::ok( ParseResultType::Matched );\n            }\n\n            size_t ParserBase::cardinality() const { return 1; }\n\n            InternalParseResult ParserBase::parse( Args const& args ) const {\n                return parse( static_cast<std::string>(args.exeName()), TokenStream( args ) );\n            }\n\n            ParseState::ParseState( ParseResultType type,\n                                    TokenStream remainingTokens ):\n                m_type( type ), m_remainingTokens( CATCH_MOVE(remainingTokens) ) {}\n\n            ParserResult BoundFlagRef::setFlag( bool flag ) {\n                m_ref = flag;\n                return ParserResult::ok( ParseResultType::Matched );\n            }\n\n            ResultBase::~ResultBase() = default;\n\n            bool BoundRef::isContainer() const { return false; }\n\n            bool BoundRef::isFlag() const { return false; }\n\n            bool BoundFlagRefBase::isFlag() const { return true; }\n\n} // namespace Detail\n\n        Detail::InternalParseResult Arg::parse(std::string const&,\n                                               Detail::TokenStream tokens) const {\n            auto validationResult = validate();\n            if (!validationResult)\n                return Detail::InternalParseResult(validationResult);\n\n            auto token = *tokens;\n            if (token.type != Detail::TokenType::Argument)\n                return Detail::InternalParseResult::ok(Detail::ParseState(\n                    ParseResultType::NoMatch, CATCH_MOVE(tokens)));\n\n            assert(!m_ref->isFlag());\n            auto valueRef =\n                static_cast<Detail::BoundValueRefBase*>(m_ref.get());\n\n            auto result = valueRef->setValue(static_cast<std::string>(token.token));\n            if ( !result )\n                return Detail::InternalParseResult( result );\n            else\n                return Detail::InternalParseResult::ok(\n                    Detail::ParseState( ParseResultType::Matched,\n                                        CATCH_MOVE( ++tokens ) ) );\n        }\n\n        Opt::Opt(bool& ref) :\n            ParserRefImpl(std::make_shared<Detail::BoundFlagRef>(ref)) {}\n\n        Detail::HelpColumns Opt::getHelpColumns() const {\n            ReusableStringStream oss;\n            bool first = true;\n            for (auto const& opt : m_optNames) {\n                if (first)\n                    first = false;\n                else\n                    oss << \", \";\n                oss << opt;\n            }\n            if (!m_hint.empty())\n                oss << \" <\" << m_hint << '>';\n            return { oss.str(), m_description };\n        }\n\n        bool Opt::isMatch(StringRef optToken) const {\n            auto normalisedToken = normaliseOpt(optToken);\n            for (auto const& name : m_optNames) {\n                if (normaliseOpt(name) == normalisedToken)\n                    return true;\n            }\n            return false;\n        }\n\n        Detail::InternalParseResult Opt::parse(std::string const&,\n                                       Detail::TokenStream tokens) const {\n            auto validationResult = validate();\n            if (!validationResult)\n                return Detail::InternalParseResult(validationResult);\n\n            if (tokens &&\n                tokens->type == Detail::TokenType::Option) {\n                auto const& token = *tokens;\n                if (isMatch(token.token)) {\n                    if (m_ref->isFlag()) {\n                        auto flagRef =\n                            static_cast<Detail::BoundFlagRefBase*>(\n                                m_ref.get());\n                        auto result = flagRef->setFlag(true);\n                        if (!result)\n                            return Detail::InternalParseResult(result);\n                        if (result.value() ==\n                            ParseResultType::ShortCircuitAll)\n                            return Detail::InternalParseResult::ok(Detail::ParseState(\n                                result.value(), CATCH_MOVE(tokens)));\n                    } else {\n                        auto valueRef =\n                            static_cast<Detail::BoundValueRefBase*>(\n                                m_ref.get());\n                        ++tokens;\n                        if (!tokens)\n                            return Detail::InternalParseResult::runtimeError(\n                                \"Expected argument following \" +\n                                token.token);\n                        auto const& argToken = *tokens;\n                        if (argToken.type != Detail::TokenType::Argument)\n                            return Detail::InternalParseResult::runtimeError(\n                                \"Expected argument following \" +\n                                token.token);\n                        const auto result = valueRef->setValue(static_cast<std::string>(argToken.token));\n                        if (!result)\n                            return Detail::InternalParseResult(result);\n                        if (result.value() ==\n                            ParseResultType::ShortCircuitAll)\n                            return Detail::InternalParseResult::ok(Detail::ParseState(\n                                result.value(), CATCH_MOVE(tokens)));\n                    }\n                    return Detail::InternalParseResult::ok(Detail::ParseState(\n                        ParseResultType::Matched, CATCH_MOVE(++tokens)));\n                }\n            }\n            return Detail::InternalParseResult::ok(\n                Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens)));\n        }\n\n        Detail::Result Opt::validate() const {\n            if (m_optNames.empty())\n                return Detail::Result::logicError(\"No options supplied to Opt\");\n            for (auto const& name : m_optNames) {\n                if (name.empty())\n                    return Detail::Result::logicError(\n                        \"Option name cannot be empty\");\n#ifdef CATCH_PLATFORM_WINDOWS\n                if (name[0] != '-' && name[0] != '/')\n                    return Detail::Result::logicError(\n                        \"Option name must begin with '-' or '/'\");\n#else\n                if (name[0] != '-')\n                    return Detail::Result::logicError(\n                        \"Option name must begin with '-'\");\n#endif\n            }\n            return ParserRefImpl::validate();\n        }\n\n        ExeName::ExeName() :\n            m_name(std::make_shared<std::string>(\"<executable>\")) {}\n\n        ExeName::ExeName(std::string& ref) : ExeName() {\n            m_ref = std::make_shared<Detail::BoundValueRef<std::string>>(ref);\n        }\n\n        Detail::InternalParseResult\n            ExeName::parse(std::string const&,\n                           Detail::TokenStream tokens) const {\n            return Detail::InternalParseResult::ok(\n                Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens)));\n        }\n\n        ParserResult ExeName::set(std::string const& newName) {\n            auto lastSlash = newName.find_last_of(\"\\\\/\");\n            auto filename = (lastSlash == std::string::npos)\n                ? newName\n                : newName.substr(lastSlash + 1);\n\n            *m_name = filename;\n            if (m_ref)\n                return m_ref->setValue(filename);\n            else\n                return ParserResult::ok(ParseResultType::Matched);\n        }\n\n\n\n\n        Parser& Parser::operator|=( Parser const& other ) {\n            m_options.insert( m_options.end(),\n                              other.m_options.begin(),\n                              other.m_options.end() );\n            m_args.insert(\n                m_args.end(), other.m_args.begin(), other.m_args.end() );\n            return *this;\n        }\n\n        std::vector<Detail::HelpColumns> Parser::getHelpColumns() const {\n            std::vector<Detail::HelpColumns> cols;\n            cols.reserve( m_options.size() );\n            for ( auto const& o : m_options ) {\n                cols.push_back(o.getHelpColumns());\n            }\n            return cols;\n        }\n\n        void Parser::writeToStream( std::ostream& os ) const {\n            if ( !m_exeName.name().empty() ) {\n                os << \"usage:\\n\"\n                   << \"  \" << m_exeName.name() << ' ';\n                bool required = true, first = true;\n                for ( auto const& arg : m_args ) {\n                    if ( first )\n                        first = false;\n                    else\n                        os << ' ';\n                    if ( arg.isOptional() && required ) {\n                        os << '[';\n                        required = false;\n                    }\n                    os << '<' << arg.hint() << '>';\n                    if ( arg.cardinality() == 0 )\n                        os << \" ... \";\n                }\n                if ( !required )\n                    os << ']';\n                if ( !m_options.empty() )\n                    os << \" options\";\n                os << \"\\n\\nwhere options are:\\n\";\n            }\n\n            auto rows = getHelpColumns();\n            size_t consoleWidth = CATCH_CONFIG_CONSOLE_WIDTH;\n            size_t optWidth = 0;\n            for ( auto const& cols : rows )\n                optWidth = ( std::max )( optWidth, cols.left.size() + 2 );\n\n            optWidth = ( std::min )( optWidth, consoleWidth / 2 );\n\n            for ( auto& cols : rows ) {\n                auto row = TextFlow::Column( CATCH_MOVE(cols.left) )\n                               .width( optWidth )\n                               .indent( 2 ) +\n                           TextFlow::Spacer( 4 ) +\n                           TextFlow::Column( static_cast<std::string>(cols.descriptions) )\n                               .width( consoleWidth - 7 - optWidth );\n                os << row << '\\n';\n            }\n        }\n\n        Detail::Result Parser::validate() const {\n            for ( auto const& opt : m_options ) {\n                auto result = opt.validate();\n                if ( !result )\n                    return result;\n            }\n            for ( auto const& arg : m_args ) {\n                auto result = arg.validate();\n                if ( !result )\n                    return result;\n            }\n            return Detail::Result::ok();\n        }\n\n        Detail::InternalParseResult\n        Parser::parse( std::string const& exeName,\n                       Detail::TokenStream tokens ) const {\n\n            struct ParserInfo {\n                ParserBase const* parser = nullptr;\n                size_t count = 0;\n            };\n            std::vector<ParserInfo> parseInfos;\n            parseInfos.reserve( m_options.size() + m_args.size() );\n            for ( auto const& opt : m_options ) {\n                parseInfos.push_back( { &opt, 0 } );\n            }\n            for ( auto const& arg : m_args ) {\n                parseInfos.push_back( { &arg, 0 } );\n            }\n\n            m_exeName.set( exeName );\n\n            auto result = Detail::InternalParseResult::ok(\n                Detail::ParseState( ParseResultType::NoMatch, CATCH_MOVE(tokens) ) );\n            while ( result.value().remainingTokens() ) {\n                bool tokenParsed = false;\n\n                for ( auto& parseInfo : parseInfos ) {\n                    if ( parseInfo.parser->cardinality() == 0 ||\n                         parseInfo.count < parseInfo.parser->cardinality() ) {\n                        result = parseInfo.parser->parse(\n                            exeName, CATCH_MOVE(result).value().remainingTokens() );\n                        if ( !result )\n                            return result;\n                        if ( result.value().type() !=\n                             ParseResultType::NoMatch ) {\n                            tokenParsed = true;\n                            ++parseInfo.count;\n                            break;\n                        }\n                    }\n                }\n\n                if ( result.value().type() == ParseResultType::ShortCircuitAll )\n                    return result;\n                if ( !tokenParsed )\n                    return Detail::InternalParseResult::runtimeError(\n                        \"Unrecognised token: \" +\n                        result.value().remainingTokens()->token );\n            }\n            // !TBD Check missing required options\n            return result;\n        }\n\n        Args::Args(int argc, char const* const* argv) :\n            m_exeName(argv[0]), m_args(argv + 1, argv + argc) {}\n\n        Args::Args(std::initializer_list<StringRef> args) :\n            m_exeName(*args.begin()),\n            m_args(args.begin() + 1, args.end()) {}\n\n\n        Help::Help( bool& showHelpFlag ):\n            Opt( [&]( bool flag ) {\n                showHelpFlag = flag;\n                return ParserResult::ok( ParseResultType::ShortCircuitAll );\n            } ) {\n            static_cast<Opt&> ( *this )(\n                \"display usage information\" )[\"-?\"][\"-h\"][\"--help\"]\n                .optional();\n        }\n\n    } // namespace Clara\n} // namespace Catch\n\n\n\n\n#include <fstream>\n#include <string>\n\nnamespace Catch {\n\n    Clara::Parser makeCommandLineParser( ConfigData& config ) {\n\n        using namespace Clara;\n\n        auto const setWarning = [&]( std::string const& warning ) {\n            if ( warning == \"NoAssertions\" ) {\n                config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::NoAssertions);\n                return ParserResult::ok( ParseResultType::Matched );\n            } else if ( warning == \"UnmatchedTestSpec\" ) {\n                config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::UnmatchedTestSpec);\n                return ParserResult::ok( ParseResultType::Matched );\n            }\n\n            return ParserResult ::runtimeError(\n                \"Unrecognised warning option: '\" + warning + '\\'' );\n        };\n        auto const loadTestNamesFromFile = [&]( std::string const& filename ) {\n                std::ifstream f( filename.c_str() );\n                if( !f.is_open() )\n                    return ParserResult::runtimeError( \"Unable to load input file: '\" + filename + '\\'' );\n\n                std::string line;\n                while( std::getline( f, line ) ) {\n                    line = trim(line);\n                    if( !line.empty() && !startsWith( line, '#' ) ) {\n                        if( !startsWith( line, '\"' ) )\n                            line = '\"' + CATCH_MOVE(line) + '\"';\n                        config.testsOrTags.push_back( line );\n                        config.testsOrTags.emplace_back( \",\" );\n                    }\n                }\n                //Remove comma in the end\n                if(!config.testsOrTags.empty())\n                    config.testsOrTags.erase( config.testsOrTags.end()-1 );\n\n                return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const setTestOrder = [&]( std::string const& order ) {\n                if( startsWith( \"declared\", order ) )\n                    config.runOrder = TestRunOrder::Declared;\n                else if( startsWith( \"lexical\", order ) )\n                    config.runOrder = TestRunOrder::LexicographicallySorted;\n                else if( startsWith( \"random\", order ) )\n                    config.runOrder = TestRunOrder::Randomized;\n                else\n                    return ParserResult::runtimeError( \"Unrecognised ordering: '\" + order + '\\'' );\n                return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const setRngSeed = [&]( std::string const& seed ) {\n                if( seed == \"time\" ) {\n                    config.rngSeed = generateRandomSeed(GenerateFrom::Time);\n                    return ParserResult::ok(ParseResultType::Matched);\n                } else if (seed == \"random-device\") {\n                    config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice);\n                    return ParserResult::ok(ParseResultType::Matched);\n                }\n\n                // TODO: ideally we should be parsing uint32_t directly\n                //       fix this later when we add new parse overload\n                auto parsedSeed = parseUInt( seed, 0 );\n                if ( !parsedSeed ) {\n                    return ParserResult::runtimeError( \"Could not parse '\" + seed + \"' as seed\" );\n                }\n                config.rngSeed = *parsedSeed;\n                return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const setDefaultColourMode = [&]( std::string const& colourMode ) {\n            Optional<ColourMode> maybeMode = Catch::Detail::stringToColourMode(toLower( colourMode ));\n            if ( !maybeMode ) {\n                return ParserResult::runtimeError(\n                    \"colour mode must be one of: default, ansi, win32, \"\n                    \"or none. '\" +\n                    colourMode + \"' is not recognised\" );\n            }\n            auto mode = *maybeMode;\n            if ( !isColourImplAvailable( mode ) ) {\n                return ParserResult::runtimeError(\n                    \"colour mode '\" + colourMode +\n                    \"' is not supported in this binary\" );\n            }\n            config.defaultColourMode = mode;\n            return ParserResult::ok( ParseResultType::Matched );\n        };\n        auto const setWaitForKeypress = [&]( std::string const& keypress ) {\n                auto keypressLc = toLower( keypress );\n                if (keypressLc == \"never\")\n                    config.waitForKeypress = WaitForKeypress::Never;\n                else if( keypressLc == \"start\" )\n                    config.waitForKeypress = WaitForKeypress::BeforeStart;\n                else if( keypressLc == \"exit\" )\n                    config.waitForKeypress = WaitForKeypress::BeforeExit;\n                else if( keypressLc == \"both\" )\n                    config.waitForKeypress = WaitForKeypress::BeforeStartAndExit;\n                else\n                    return ParserResult::runtimeError( \"keypress argument must be one of: never, start, exit or both. '\" + keypress + \"' not recognised\" );\n            return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const setVerbosity = [&]( std::string const& verbosity ) {\n            auto lcVerbosity = toLower( verbosity );\n            if( lcVerbosity == \"quiet\" )\n                config.verbosity = Verbosity::Quiet;\n            else if( lcVerbosity == \"normal\" )\n                config.verbosity = Verbosity::Normal;\n            else if( lcVerbosity == \"high\" )\n                config.verbosity = Verbosity::High;\n            else\n                return ParserResult::runtimeError( \"Unrecognised verbosity, '\" + verbosity + '\\'' );\n            return ParserResult::ok( ParseResultType::Matched );\n        };\n        auto const setReporter = [&]( std::string const& userReporterSpec ) {\n            if ( userReporterSpec.empty() ) {\n                return ParserResult::runtimeError( \"Received empty reporter spec.\" );\n            }\n\n            Optional<ReporterSpec> parsed =\n                parseReporterSpec( userReporterSpec );\n            if ( !parsed ) {\n                return ParserResult::runtimeError(\n                    \"Could not parse reporter spec '\" + userReporterSpec +\n                    \"'\" );\n            }\n\n            auto const& reporterSpec = *parsed;\n\n            auto const& factories =\n                getRegistryHub().getReporterRegistry().getFactories();\n            auto result = factories.find( reporterSpec.name() );\n\n            if ( result == factories.end() ) {\n                return ParserResult::runtimeError(\n                    \"Unrecognized reporter, '\" + reporterSpec.name() +\n                    \"'. Check available with --list-reporters\" );\n            }\n\n\n            const bool hadOutputFile = reporterSpec.outputFile().some();\n            config.reporterSpecifications.push_back( CATCH_MOVE( *parsed ) );\n            // It would be enough to check this only once at the very end, but\n            // there is  not a place where we could call this check, so do it\n            // every time it could fail. For valid inputs, this is still called\n            // at most once.\n            if (!hadOutputFile) {\n                int n_reporters_without_file = 0;\n                for (auto const& spec : config.reporterSpecifications) {\n                    if (spec.outputFile().none()) {\n                        n_reporters_without_file++;\n                    }\n                }\n                if (n_reporters_without_file > 1) {\n                    return ParserResult::runtimeError( \"Only one reporter may have unspecified output file.\" );\n                }\n            }\n\n            return ParserResult::ok( ParseResultType::Matched );\n        };\n        auto const setShardCount = [&]( std::string const& shardCount ) {\n            auto parsedCount = parseUInt( shardCount );\n            if ( !parsedCount ) {\n                return ParserResult::runtimeError(\n                    \"Could not parse '\" + shardCount + \"' as shard count\" );\n            }\n            if ( *parsedCount == 0 ) {\n                return ParserResult::runtimeError(\n                    \"Shard count must be positive\" );\n            }\n            config.shardCount = *parsedCount;\n            return ParserResult::ok( ParseResultType::Matched );\n        };\n\n        auto const setShardIndex = [&](std::string const& shardIndex) {\n            auto parsedIndex = parseUInt( shardIndex );\n            if ( !parsedIndex ) {\n                return ParserResult::runtimeError(\n                    \"Could not parse '\" + shardIndex + \"' as shard index\" );\n            }\n            config.shardIndex = *parsedIndex;\n            return ParserResult::ok( ParseResultType::Matched );\n        };\n\n        auto cli\n            = ExeName( config.processName )\n            | Help( config.showHelp )\n            | Opt( config.showSuccessfulTests )\n                [\"-s\"][\"--success\"]\n                ( \"include successful tests in output\" )\n            | Opt( config.shouldDebugBreak )\n                [\"-b\"][\"--break\"]\n                ( \"break into debugger on failure\" )\n            | Opt( config.noThrow )\n                [\"-e\"][\"--nothrow\"]\n                ( \"skip exception tests\" )\n            | Opt( config.showInvisibles )\n                [\"-i\"][\"--invisibles\"]\n                ( \"show invisibles (tabs, newlines)\" )\n            | Opt( config.defaultOutputFilename, \"filename\" )\n                [\"-o\"][\"--out\"]\n                ( \"default output filename\" )\n            | Opt( accept_many, setReporter, \"name[::key=value]*\" )\n                [\"-r\"][\"--reporter\"]\n                ( \"reporter to use (defaults to console)\" )\n            | Opt( config.name, \"name\" )\n                [\"-n\"][\"--name\"]\n                ( \"suite name\" )\n            | Opt( [&]( bool ){ config.abortAfter = 1; } )\n                [\"-a\"][\"--abort\"]\n                ( \"abort at first failure\" )\n            | Opt( [&]( int x ){ config.abortAfter = x; }, \"no. failures\" )\n                [\"-x\"][\"--abortx\"]\n                ( \"abort after x failures\" )\n            | Opt( accept_many, setWarning, \"warning name\" )\n                [\"-w\"][\"--warn\"]\n                ( \"enable warnings\" )\n            | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, \"yes|no\" )\n                [\"-d\"][\"--durations\"]\n                ( \"show test durations\" )\n            | Opt( config.minDuration, \"seconds\" )\n                [\"-D\"][\"--min-duration\"]\n                ( \"show test durations for tests taking at least the given number of seconds\" )\n            | Opt( loadTestNamesFromFile, \"filename\" )\n                [\"-f\"][\"--input-file\"]\n                ( \"load test names to run from a file\" )\n            | Opt( config.filenamesAsTags )\n                [\"-#\"][\"--filenames-as-tags\"]\n                ( \"adds a tag for the filename\" )\n            | Opt( config.sectionsToRun, \"section name\" )\n                [\"-c\"][\"--section\"]\n                ( \"specify section to run\" )\n            | Opt( setVerbosity, \"quiet|normal|high\" )\n                [\"-v\"][\"--verbosity\"]\n                ( \"set output verbosity\" )\n            | Opt( config.listTests )\n                [\"--list-tests\"]\n                ( \"list all/matching test cases\" )\n            | Opt( config.listTags )\n                [\"--list-tags\"]\n                ( \"list all/matching tags\" )\n            | Opt( config.listReporters )\n                [\"--list-reporters\"]\n                ( \"list all available reporters\" )\n            | Opt( config.listListeners )\n                [\"--list-listeners\"]\n                ( \"list all listeners\" )\n            | Opt( setTestOrder, \"decl|lex|rand\" )\n                [\"--order\"]\n                ( \"test case order (defaults to decl)\" )\n            | Opt( setRngSeed, \"'time'|'random-device'|number\" )\n                [\"--rng-seed\"]\n                ( \"set a specific seed for random numbers\" )\n            | Opt( setDefaultColourMode, \"ansi|win32|none|default\" )\n                [\"--colour-mode\"]\n                ( \"what color mode should be used as default\" )\n            | Opt( config.libIdentify )\n                [\"--libidentify\"]\n                ( \"report name and version according to libidentify standard\" )\n            | Opt( setWaitForKeypress, \"never|start|exit|both\" )\n                [\"--wait-for-keypress\"]\n                ( \"waits for a keypress before exiting\" )\n            | Opt( config.skipBenchmarks)\n                [\"--skip-benchmarks\"]\n                ( \"disable running benchmarks\")\n            | Opt( config.benchmarkSamples, \"samples\" )\n                [\"--benchmark-samples\"]\n                ( \"number of samples to collect (default: 100)\" )\n            | Opt( config.benchmarkResamples, \"resamples\" )\n                [\"--benchmark-resamples\"]\n                ( \"number of resamples for the bootstrap (default: 100000)\" )\n            | Opt( config.benchmarkConfidenceInterval, \"confidence interval\" )\n                [\"--benchmark-confidence-interval\"]\n                ( \"confidence interval for the bootstrap (between 0 and 1, default: 0.95)\" )\n            | Opt( config.benchmarkNoAnalysis )\n                [\"--benchmark-no-analysis\"]\n                ( \"perform only measurements; do not perform any analysis\" )\n            | Opt( config.benchmarkWarmupTime, \"benchmarkWarmupTime\" )\n                [\"--benchmark-warmup-time\"]\n                ( \"amount of time in milliseconds spent on warming up each test (default: 100)\" )\n            | Opt( setShardCount, \"shard count\" )\n                [\"--shard-count\"]\n                ( \"split the tests to execute into this many groups\" )\n            | Opt( setShardIndex, \"shard index\" )\n                [\"--shard-index\"]\n                ( \"index of the group of tests to execute (see --shard-count)\" )\n            | Opt( config.allowZeroTests )\n                [\"--allow-running-no-tests\"]\n                ( \"Treat 'No tests run' as a success\" )\n            | Arg( config.testsOrTags, \"test name|pattern|tags\" )\n                ( \"which test or tests to use\" );\n\n        return cli;\n    }\n\n} // end namespace Catch\n\n\n#if defined(__clang__)\n#    pragma clang diagnostic push\n#    pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#endif\n\n\n\n#include <cassert>\n#include <ostream>\n#include <utility>\n\nnamespace Catch {\n\n    ColourImpl::~ColourImpl() = default;\n\n    ColourImpl::ColourGuard ColourImpl::guardColour( Colour::Code colourCode ) {\n        return ColourGuard(colourCode, this );\n    }\n\n    void ColourImpl::ColourGuard::engageImpl( std::ostream& stream ) {\n        assert( &stream == &m_colourImpl->m_stream->stream() &&\n                \"Engaging colour guard for different stream than used by the \"\n                \"parent colour implementation\" );\n        static_cast<void>( stream );\n\n        m_engaged = true;\n        m_colourImpl->use( m_code );\n    }\n\n    ColourImpl::ColourGuard::ColourGuard( Colour::Code code,\n                                          ColourImpl const* colour ):\n        m_colourImpl( colour ), m_code( code ) {\n    }\n    ColourImpl::ColourGuard::ColourGuard( ColourGuard&& rhs ) noexcept:\n        m_colourImpl( rhs.m_colourImpl ),\n        m_code( rhs.m_code ),\n        m_engaged( rhs.m_engaged ) {\n        rhs.m_engaged = false;\n    }\n    ColourImpl::ColourGuard&\n    ColourImpl::ColourGuard::operator=( ColourGuard&& rhs ) noexcept {\n        using std::swap;\n        swap( m_colourImpl, rhs.m_colourImpl );\n        swap( m_code, rhs.m_code );\n        swap( m_engaged, rhs.m_engaged );\n\n        return *this;\n    }\n    ColourImpl::ColourGuard::~ColourGuard() {\n        if ( m_engaged ) {\n            m_colourImpl->use( Colour::None );\n        }\n    }\n\n    ColourImpl::ColourGuard&\n    ColourImpl::ColourGuard::engage( std::ostream& stream ) & {\n        engageImpl( stream );\n        return *this;\n    }\n\n    ColourImpl::ColourGuard&&\n    ColourImpl::ColourGuard::engage( std::ostream& stream ) && {\n        engageImpl( stream );\n        return CATCH_MOVE(*this);\n    }\n\n    namespace {\n        //! A do-nothing implementation of colour, used as fallback for unknown\n        //! platforms, and when the user asks to deactivate all colours.\n        class NoColourImpl final : public ColourImpl {\n        public:\n            NoColourImpl( IStream* stream ): ColourImpl( stream ) {}\n\n        private:\n            void use( Colour::Code ) const override {}\n        };\n    } // namespace\n\n\n} // namespace Catch\n\n\n#if defined ( CATCH_CONFIG_COLOUR_WIN32 ) /////////////////////////////////////////\n\nnamespace Catch {\nnamespace {\n\n    class Win32ColourImpl final : public ColourImpl {\n    public:\n        Win32ColourImpl(IStream* stream):\n            ColourImpl(stream) {\n            CONSOLE_SCREEN_BUFFER_INFO csbiInfo;\n            GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ),\n                                        &csbiInfo );\n            originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );\n            originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );\n        }\n\n        static bool useImplementationForStream(IStream const& stream) {\n            // Win32 text colour APIs can only be used on console streams\n            // We cannot check that the output hasn't been redirected,\n            // so we just check that the original stream is console stream.\n            return stream.isConsole();\n        }\n\n    private:\n        void use( Colour::Code _colourCode ) const override {\n            switch( _colourCode ) {\n                case Colour::None:      return setTextAttribute( originalForegroundAttributes );\n                case Colour::White:     return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );\n                case Colour::Red:       return setTextAttribute( FOREGROUND_RED );\n                case Colour::Green:     return setTextAttribute( FOREGROUND_GREEN );\n                case Colour::Blue:      return setTextAttribute( FOREGROUND_BLUE );\n                case Colour::Cyan:      return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );\n                case Colour::Yellow:    return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );\n                case Colour::Grey:      return setTextAttribute( 0 );\n\n                case Colour::LightGrey:     return setTextAttribute( FOREGROUND_INTENSITY );\n                case Colour::BrightRed:     return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );\n                case Colour::BrightGreen:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );\n                case Colour::BrightWhite:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );\n                case Colour::BrightYellow:  return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN );\n\n                case Colour::Bright: CATCH_INTERNAL_ERROR( \"not a colour\" );\n\n                default:\n                    CATCH_ERROR( \"Unknown colour requested\" );\n            }\n        }\n\n        void setTextAttribute( WORD _textAttribute ) const {\n            SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ),\n                                     _textAttribute |\n                                         originalBackgroundAttributes );\n        }\n        WORD originalForegroundAttributes;\n        WORD originalBackgroundAttributes;\n    };\n\n} // end anon namespace\n} // end namespace Catch\n\n#endif // Windows/ ANSI/ None\n\n\n#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) || defined( __GLIBC__ )\n#    define CATCH_INTERNAL_HAS_ISATTY\n#    include <unistd.h>\n#endif\n\nnamespace Catch {\nnamespace {\n\n    class ANSIColourImpl final : public ColourImpl {\n    public:\n        ANSIColourImpl( IStream* stream ): ColourImpl( stream ) {}\n\n        static bool useImplementationForStream(IStream const& stream) {\n            // This is kinda messy due to trying to support a bunch of\n            // different platforms at once.\n            // The basic idea is that if we are asked to do autodetection (as\n            // opposed to being told to use posixy colours outright), then we\n            // only want to use the colours if we are writing to console.\n            // However, console might be redirected, so we make an attempt at\n            // checking for that on platforms where we know how to do that.\n            bool useColour = stream.isConsole();\n#if defined( CATCH_INTERNAL_HAS_ISATTY ) && \\\n    !( defined( __DJGPP__ ) && defined( __STRICT_ANSI__ ) )\n            ErrnoGuard _; // for isatty\n            useColour = useColour && isatty( STDOUT_FILENO );\n#    endif\n#    if defined( CATCH_PLATFORM_MAC ) || defined( CATCH_PLATFORM_IPHONE )\n            useColour = useColour && !isDebuggerActive();\n#    endif\n\n            return useColour;\n        }\n\n    private:\n        void use( Colour::Code _colourCode ) const override {\n            auto setColour = [&out =\n                                  m_stream->stream()]( char const* escapeCode ) {\n                // The escape sequence must be flushed to console, otherwise\n                // if stdin and stderr are intermixed, we'd get accidentally\n                // coloured output.\n                out << '\\033' << escapeCode << std::flush;\n            };\n            switch( _colourCode ) {\n                case Colour::None:\n                case Colour::White:     return setColour( \"[0m\" );\n                case Colour::Red:       return setColour( \"[0;31m\" );\n                case Colour::Green:     return setColour( \"[0;32m\" );\n                case Colour::Blue:      return setColour( \"[0;34m\" );\n                case Colour::Cyan:      return setColour( \"[0;36m\" );\n                case Colour::Yellow:    return setColour( \"[0;33m\" );\n                case Colour::Grey:      return setColour( \"[1;30m\" );\n\n                case Colour::LightGrey:     return setColour( \"[0;37m\" );\n                case Colour::BrightRed:     return setColour( \"[1;31m\" );\n                case Colour::BrightGreen:   return setColour( \"[1;32m\" );\n                case Colour::BrightWhite:   return setColour( \"[1;37m\" );\n                case Colour::BrightYellow:  return setColour( \"[1;33m\" );\n\n                case Colour::Bright: CATCH_INTERNAL_ERROR( \"not a colour\" );\n                default: CATCH_INTERNAL_ERROR( \"Unknown colour requested\" );\n            }\n        }\n    };\n\n} // end anon namespace\n} // end namespace Catch\n\nnamespace Catch {\n\n    Detail::unique_ptr<ColourImpl> makeColourImpl( ColourMode colourSelection,\n                                                   IStream* stream ) {\n#if defined( CATCH_CONFIG_COLOUR_WIN32 )\n        if ( colourSelection == ColourMode::Win32 ) {\n            return Detail::make_unique<Win32ColourImpl>( stream );\n        }\n#endif\n        if ( colourSelection == ColourMode::ANSI ) {\n            return Detail::make_unique<ANSIColourImpl>( stream );\n        }\n        if ( colourSelection == ColourMode::None ) {\n            return Detail::make_unique<NoColourImpl>( stream );\n        }\n\n        if ( colourSelection == ColourMode::PlatformDefault) {\n#if defined( CATCH_CONFIG_COLOUR_WIN32 )\n            if ( Win32ColourImpl::useImplementationForStream( *stream ) ) {\n                return Detail::make_unique<Win32ColourImpl>( stream );\n            }\n#endif\n            if ( ANSIColourImpl::useImplementationForStream( *stream ) ) {\n                return Detail::make_unique<ANSIColourImpl>( stream );\n            }\n            return Detail::make_unique<NoColourImpl>( stream );\n        }\n\n        CATCH_ERROR( \"Could not create colour impl for selection \" << static_cast<int>(colourSelection) );\n    }\n\n    bool isColourImplAvailable( ColourMode colourSelection ) {\n        switch ( colourSelection ) {\n#if defined( CATCH_CONFIG_COLOUR_WIN32 )\n        case ColourMode::Win32:\n#endif\n        case ColourMode::ANSI:\n        case ColourMode::None:\n        case ColourMode::PlatformDefault:\n            return true;\n        default:\n            return false;\n        }\n    }\n\n\n} // end namespace Catch\n\n#if defined(__clang__)\n#    pragma clang diagnostic pop\n#endif\n\n\n\n\nnamespace Catch {\n\n    Context* Context::currentContext = nullptr;\n\n    void cleanUpContext() {\n        delete Context::currentContext;\n        Context::currentContext = nullptr;\n    }\n    void Context::createContext() {\n        currentContext = new Context();\n    }\n\n    Context& getCurrentMutableContext() {\n        if ( !Context::currentContext ) { Context::createContext(); }\n        // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)\n        return *Context::currentContext;\n    }\n\n    SimplePcg32& sharedRng() {\n        static SimplePcg32 s_rng;\n        return s_rng;\n    }\n\n}\n\n\n\n\n\n#include <ostream>\n\n#if defined(CATCH_CONFIG_ANDROID_LOGWRITE)\n#include <android/log.h>\n\n    namespace Catch {\n        void writeToDebugConsole( std::string const& text ) {\n            __android_log_write( ANDROID_LOG_DEBUG, \"Catch\", text.c_str() );\n        }\n    }\n\n#elif defined(CATCH_PLATFORM_WINDOWS)\n\n    namespace Catch {\n        void writeToDebugConsole( std::string const& text ) {\n            ::OutputDebugStringA( text.c_str() );\n        }\n    }\n\n#else\n\n    namespace Catch {\n        void writeToDebugConsole( std::string const& text ) {\n            // !TBD: Need a version for Mac/ XCode and other IDEs\n            Catch::cout() << text;\n        }\n    }\n\n#endif // Platform\n\n\n\n#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE)\n\n#  include <cassert>\n#  include <sys/types.h>\n#  include <unistd.h>\n#  include <cstddef>\n#  include <ostream>\n\n#ifdef __apple_build_version__\n    // These headers will only compile with AppleClang (XCode)\n    // For other compilers (Clang, GCC, ... ) we need to exclude them\n#  include <sys/sysctl.h>\n#endif\n\n    namespace Catch {\n        #ifdef __apple_build_version__\n        // The following function is taken directly from the following technical note:\n        // https://developer.apple.com/library/archive/qa/qa1361/_index.html\n\n        // Returns true if the current process is being debugged (either\n        // running under the debugger or has a debugger attached post facto).\n        bool isDebuggerActive(){\n            int                 mib[4];\n            struct kinfo_proc   info;\n            std::size_t         size;\n\n            // Initialize the flags so that, if sysctl fails for some bizarre\n            // reason, we get a predictable result.\n\n            info.kp_proc.p_flag = 0;\n\n            // Initialize mib, which tells sysctl the info we want, in this case\n            // we're looking for information about a specific process ID.\n\n            mib[0] = CTL_KERN;\n            mib[1] = KERN_PROC;\n            mib[2] = KERN_PROC_PID;\n            mib[3] = getpid();\n\n            // Call sysctl.\n\n            size = sizeof(info);\n            if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) {\n                Catch::cerr() << \"\\n** Call to sysctl failed - unable to determine if debugger is active **\\n\\n\" << std::flush;\n                return false;\n            }\n\n            // We're being debugged if the P_TRACED flag is set.\n\n            return ( (info.kp_proc.p_flag & P_TRACED) != 0 );\n        }\n        #else\n        bool isDebuggerActive() {\n            // We need to find another way to determine this for non-appleclang compilers on macOS\n            return false;\n        }\n        #endif\n    } // namespace Catch\n\n#elif defined(CATCH_PLATFORM_LINUX)\n    #include <fstream>\n    #include <string>\n\n    namespace Catch{\n        // The standard POSIX way of detecting a debugger is to attempt to\n        // ptrace() the process, but this needs to be done from a child and not\n        // this process itself to still allow attaching to this process later\n        // if wanted, so is rather heavy. Under Linux we have the PID of the\n        // \"debugger\" (which doesn't need to be gdb, of course, it could also\n        // be strace, for example) in /proc/$PID/status, so just get it from\n        // there instead.\n        bool isDebuggerActive(){\n            // Libstdc++ has a bug, where std::ifstream sets errno to 0\n            // This way our users can properly assert over errno values\n            ErrnoGuard guard;\n            std::ifstream in(\"/proc/self/status\");\n            for( std::string line; std::getline(in, line); ) {\n                static const int PREFIX_LEN = 11;\n                if( line.compare(0, PREFIX_LEN, \"TracerPid:\\t\") == 0 ) {\n                    // We're traced if the PID is not 0 and no other PID starts\n                    // with 0 digit, so it's enough to check for just a single\n                    // character.\n                    return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';\n                }\n            }\n\n            return false;\n        }\n    } // namespace Catch\n#elif defined(_MSC_VER)\n    extern \"C\" __declspec(dllimport) int __stdcall IsDebuggerPresent();\n    namespace Catch {\n        bool isDebuggerActive() {\n            return IsDebuggerPresent() != 0;\n        }\n    }\n#elif defined(__MINGW32__)\n    extern \"C\" __declspec(dllimport) int __stdcall IsDebuggerPresent();\n    namespace Catch {\n        bool isDebuggerActive() {\n            return IsDebuggerPresent() != 0;\n        }\n    }\n#else\n    namespace Catch {\n       bool isDebuggerActive() { return false; }\n    }\n#endif // Platform\n\n\n\n\nnamespace Catch {\n\n    void ITransientExpression::streamReconstructedExpression(\n        std::ostream& os ) const {\n        // We can't make this function pure virtual to keep ITransientExpression\n        // constexpr, so we write error message instead\n        os << \"Some class derived from ITransientExpression without overriding streamReconstructedExpression\";\n    }\n\n    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) {\n        if( lhs.size() + rhs.size() < 40 &&\n                lhs.find('\\n') == std::string::npos &&\n                rhs.find('\\n') == std::string::npos )\n            os << lhs << ' ' << op << ' ' << rhs;\n        else\n            os << lhs << '\\n' << op << '\\n' << rhs;\n    }\n}\n\n\n\n#include <stdexcept>\n\n\nnamespace Catch {\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER)\n    [[noreturn]]\n    void throw_exception(std::exception const& e) {\n        Catch::cerr() << \"Catch will terminate because it needed to throw an exception.\\n\"\n                      << \"The message was: \" << e.what() << '\\n';\n        std::terminate();\n    }\n#endif\n\n    [[noreturn]]\n    void throw_logic_error(std::string const& msg) {\n        throw_exception(std::logic_error(msg));\n    }\n\n    [[noreturn]]\n    void throw_domain_error(std::string const& msg) {\n        throw_exception(std::domain_error(msg));\n    }\n\n    [[noreturn]]\n    void throw_runtime_error(std::string const& msg) {\n        throw_exception(std::runtime_error(msg));\n    }\n\n\n\n} // namespace Catch;\n\n\n\n#include <cassert>\n\nnamespace Catch {\n\n    IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() = default;\n\n    namespace Detail {\n\n        namespace {\n            // Extracts the actual name part of an enum instance\n            // In other words, it returns the Blue part of Bikeshed::Colour::Blue\n            StringRef extractInstanceName(StringRef enumInstance) {\n                // Find last occurrence of \":\"\n                size_t name_start = enumInstance.size();\n                while (name_start > 0 && enumInstance[name_start - 1] != ':') {\n                    --name_start;\n                }\n                return enumInstance.substr(name_start, enumInstance.size() - name_start);\n            }\n        }\n\n        std::vector<StringRef> parseEnums( StringRef enums ) {\n            auto enumValues = splitStringRef( enums, ',' );\n            std::vector<StringRef> parsed;\n            parsed.reserve( enumValues.size() );\n            for( auto const& enumValue : enumValues ) {\n                parsed.push_back(trim(extractInstanceName(enumValue)));\n            }\n            return parsed;\n        }\n\n        EnumInfo::~EnumInfo() = default;\n\n        StringRef EnumInfo::lookup( int value ) const {\n            for( auto const& valueToName : m_values ) {\n                if( valueToName.first == value )\n                    return valueToName.second;\n            }\n            return \"{** unexpected enum value **}\"_sr;\n        }\n\n        Catch::Detail::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {\n            auto enumInfo = Catch::Detail::make_unique<EnumInfo>();\n            enumInfo->m_name = enumName;\n            enumInfo->m_values.reserve( values.size() );\n\n            const auto valueNames = Catch::Detail::parseEnums( allValueNames );\n            assert( valueNames.size() == values.size() );\n            std::size_t i = 0;\n            for( auto value : values )\n                enumInfo->m_values.emplace_back(value, valueNames[i++]);\n\n            return enumInfo;\n        }\n\n        EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {\n            m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values));\n            return *m_enumInfos.back();\n        }\n\n    } // Detail\n} // Catch\n\n\n\n\n\n#include <cerrno>\n\nnamespace Catch {\n        ErrnoGuard::ErrnoGuard():m_oldErrno(errno){}\n        ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; }\n}\n\n\n\n#include <exception>\n\nnamespace Catch {\n\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    namespace {\n        static std::string tryTranslators(\n            std::vector<\n                Detail::unique_ptr<IExceptionTranslator const>> const& translators ) {\n            if ( translators.empty() ) {\n                std::rethrow_exception( std::current_exception() );\n            } else {\n                return translators[0]->translate( translators.begin() + 1,\n                                                  translators.end() );\n            }\n        }\n\n    }\n#endif //!defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n\n    ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() = default;\n\n    void ExceptionTranslatorRegistry::registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) {\n        m_translators.push_back( CATCH_MOVE( translator ) );\n    }\n\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    std::string ExceptionTranslatorRegistry::translateActiveException() const {\n        // Compiling a mixed mode project with MSVC means that CLR\n        // exceptions will be caught in (...) as well. However, these do\n        // do not fill-in std::current_exception and thus lead to crash\n        // when attempting rethrow.\n        // /EHa switch also causes structured exceptions to be caught\n        // here, but they fill-in current_exception properly, so\n        // at worst the output should be a little weird, instead of\n        // causing a crash.\n        if ( std::current_exception() == nullptr ) {\n            return \"Non C++ exception. Possibly a CLR exception.\";\n        }\n\n        // First we try user-registered translators. If none of them can\n        // handle the exception, it will be rethrown handled by our defaults.\n        try {\n            return tryTranslators(m_translators);\n        }\n        // To avoid having to handle TFE explicitly everywhere, we just\n        // rethrow it so that it goes back up the caller.\n        catch( TestFailureException& ) {\n            std::rethrow_exception(std::current_exception());\n        }\n        catch( TestSkipException& ) {\n            std::rethrow_exception(std::current_exception());\n        }\n        catch( std::exception const& ex ) {\n            return ex.what();\n        }\n        catch( std::string const& msg ) {\n            return msg;\n        }\n        catch( const char* msg ) {\n            return msg;\n        }\n        catch(...) {\n            return \"Unknown exception\";\n        }\n    }\n\n#else // ^^ Exceptions are enabled // Exceptions are disabled vv\n    std::string ExceptionTranslatorRegistry::translateActiveException() const {\n        CATCH_INTERNAL_ERROR(\"Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n    }\n#endif\n\n}\n\n\n\n/** \\file\n * This file provides platform specific implementations of FatalConditionHandler\n *\n * This means that there is a lot of conditional compilation, and platform\n * specific code. Currently, Catch2 supports a dummy handler (if no\n * handler is desired), and 2 platform specific handlers:\n *  * Windows' SEH\n *  * POSIX signals\n *\n * Consequently, various pieces of code below are compiled if either of\n * the platform specific handlers is enabled, or if none of them are\n * enabled. It is assumed that both cannot be enabled at the same time,\n * and doing so should cause a compilation error.\n *\n * If another platform specific handler is added, the compile guards\n * below will need to be updated taking these assumptions into account.\n */\n\n\n\n#include <algorithm>\n\n#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS )\n\nnamespace Catch {\n\n    // If neither SEH nor signal handling is required, the handler impls\n    // do not have to do anything, and can be empty.\n    void FatalConditionHandler::engage_platform() {}\n    void FatalConditionHandler::disengage_platform() noexcept {}\n    FatalConditionHandler::FatalConditionHandler() = default;\n    FatalConditionHandler::~FatalConditionHandler() = default;\n\n} // end namespace Catch\n\n#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS\n\n#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS )\n#error \"Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time\"\n#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS\n\n#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )\n\nnamespace {\n    //! Signals fatal error message to the run context\n    void reportFatal( char const * const message ) {\n        Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );\n    }\n\n    //! Minimal size Catch2 needs for its own fatal error handling.\n    //! Picked empirically, so it might not be sufficient on all\n    //! platforms, and for all configurations.\n    constexpr std::size_t minStackSizeForErrors = 32 * 1024;\n} // end unnamed namespace\n\n#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS\n\n#if defined( CATCH_CONFIG_WINDOWS_SEH )\n\nnamespace Catch {\n\n    struct SignalDefs { DWORD id; const char* name; };\n\n    // There is no 1-1 mapping between signals and windows exceptions.\n    // Windows can easily distinguish between SO and SigSegV,\n    // but SigInt, SigTerm, etc are handled differently.\n    static constexpr SignalDefs signalDefs[] = {\n        { EXCEPTION_ILLEGAL_INSTRUCTION,  \"SIGILL - Illegal instruction signal\" },\n        { EXCEPTION_STACK_OVERFLOW, \"SIGSEGV - Stack overflow\" },\n        { EXCEPTION_ACCESS_VIOLATION, \"SIGSEGV - Segmentation violation signal\" },\n        { EXCEPTION_INT_DIVIDE_BY_ZERO, \"Divide by zero error\" },\n    };\n\n    static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) {\n        for (auto const& def : signalDefs) {\n            if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {\n                reportFatal(def.name);\n            }\n        }\n        // If its not an exception we care about, pass it along.\n        // This stops us from eating debugger breaks etc.\n        return EXCEPTION_CONTINUE_SEARCH;\n    }\n\n    // Since we do not support multiple instantiations, we put these\n    // into global variables and rely on cleaning them up in outlined\n    // constructors/destructors\n    static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;\n\n\n    // For MSVC, we reserve part of the stack memory for handling\n    // memory overflow structured exception.\n    FatalConditionHandler::FatalConditionHandler() {\n        ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors);\n        if (!SetThreadStackGuarantee(&guaranteeSize)) {\n            // We do not want to fully error out, because needing\n            // the stack reserve should be rare enough anyway.\n            Catch::cerr()\n                << \"Failed to reserve piece of stack.\"\n                << \" Stack overflows will not be reported successfully.\";\n        }\n    }\n\n    // We do not attempt to unset the stack guarantee, because\n    // Windows does not support lowering the stack size guarantee.\n    FatalConditionHandler::~FatalConditionHandler() = default;\n\n\n    void FatalConditionHandler::engage_platform() {\n        // Register as a the top level exception filter.\n        previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter);\n    }\n\n    void FatalConditionHandler::disengage_platform() noexcept {\n        if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) {\n            Catch::cerr()\n                << \"Unexpected SEH unhandled exception filter on disengage.\"\n                << \" The filter was restored, but might be rolled back unexpectedly.\";\n        }\n        previousTopLevelExceptionFilter = nullptr;\n    }\n\n} // end namespace Catch\n\n#endif // CATCH_CONFIG_WINDOWS_SEH\n\n#if defined( CATCH_CONFIG_POSIX_SIGNALS )\n\n#include <signal.h>\n\nnamespace Catch {\n\n    struct SignalDefs {\n        int id;\n        const char* name;\n    };\n\n    static constexpr SignalDefs signalDefs[] = {\n        { SIGINT,  \"SIGINT - Terminal interrupt signal\" },\n        { SIGILL,  \"SIGILL - Illegal instruction signal\" },\n        { SIGFPE,  \"SIGFPE - Floating point error signal\" },\n        { SIGSEGV, \"SIGSEGV - Segmentation violation signal\" },\n        { SIGTERM, \"SIGTERM - Termination request signal\" },\n        { SIGABRT, \"SIGABRT - Abort (abnormal termination) signal\" }\n    };\n\n// Older GCCs trigger -Wmissing-field-initializers for T foo = {}\n// which is zero initialization, but not explicit. We want to avoid\n// that.\n#if defined(__GNUC__)\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n#endif\n\n    static char* altStackMem = nullptr;\n    static std::size_t altStackSize = 0;\n    static stack_t oldSigStack{};\n    static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{};\n\n    static void restorePreviousSignalHandlers() noexcept {\n        // We set signal handlers back to the previous ones. Hopefully\n        // nobody overwrote them in the meantime, and doesn't expect\n        // their signal handlers to live past ours given that they\n        // installed them after ours..\n        for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {\n            sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);\n        }\n        // Return the old stack\n        sigaltstack(&oldSigStack, nullptr);\n    }\n\n    static void handleSignal( int sig ) {\n        char const * name = \"<unknown signal>\";\n        for (auto const& def : signalDefs) {\n            if (sig == def.id) {\n                name = def.name;\n                break;\n            }\n        }\n        // We need to restore previous signal handlers and let them do\n        // their thing, so that the users can have the debugger break\n        // when a signal is raised, and so on.\n        restorePreviousSignalHandlers();\n        reportFatal( name );\n        raise( sig );\n    }\n\n    FatalConditionHandler::FatalConditionHandler() {\n        assert(!altStackMem && \"Cannot initialize POSIX signal handler when one already exists\");\n        if (altStackSize == 0) {\n            altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors);\n        }\n        altStackMem = new char[altStackSize]();\n    }\n\n    FatalConditionHandler::~FatalConditionHandler() {\n        delete[] altStackMem;\n        // We signal that another instance can be constructed by zeroing\n        // out the pointer.\n        altStackMem = nullptr;\n    }\n\n    void FatalConditionHandler::engage_platform() {\n        stack_t sigStack;\n        sigStack.ss_sp = altStackMem;\n        sigStack.ss_size = altStackSize;\n        sigStack.ss_flags = 0;\n        sigaltstack(&sigStack, &oldSigStack);\n        struct sigaction sa = { };\n\n        sa.sa_handler = handleSignal;\n        sa.sa_flags = SA_ONSTACK;\n        for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) {\n            sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);\n        }\n    }\n\n#if defined(__GNUC__)\n#    pragma GCC diagnostic pop\n#endif\n\n\n    void FatalConditionHandler::disengage_platform() noexcept {\n        restorePreviousSignalHandlers();\n    }\n\n} // end namespace Catch\n\n#endif // CATCH_CONFIG_POSIX_SIGNALS\n\n\n\n\n#include <cstring>\n\nnamespace Catch {\n    namespace Detail {\n\n        uint32_t convertToBits(float f) {\n            static_assert(sizeof(float) == sizeof(uint32_t), \"Important ULP matcher assumption violated\");\n            uint32_t i;\n            std::memcpy(&i, &f, sizeof(f));\n            return i;\n        }\n\n        uint64_t convertToBits(double d) {\n            static_assert(sizeof(double) == sizeof(uint64_t), \"Important ULP matcher assumption violated\");\n            uint64_t i;\n            std::memcpy(&i, &d, sizeof(d));\n            return i;\n        }\n\n#if defined( __GNUC__ ) || defined( __clang__ )\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n        bool directCompare( float lhs, float rhs ) { return lhs == rhs; }\n        bool directCompare( double lhs, double rhs ) { return lhs == rhs; }\n#if defined( __GNUC__ ) || defined( __clang__ )\n#    pragma GCC diagnostic pop\n#endif\n\n\n    } // end namespace Detail\n} // end namespace Catch\n\n\n\n\n\n\n#include <cstdlib>\n\nnamespace Catch {\n    namespace Detail {\n\n#if !defined (CATCH_CONFIG_GETENV)\n        char const* getEnv( char const* ) { return nullptr; }\n#else\n\n        char const* getEnv( char const* varName ) {\n#    if defined( _MSC_VER )\n#        pragma warning( push )\n#        pragma warning( disable : 4996 ) // use getenv_s instead of getenv\n#    endif\n\n            return std::getenv( varName );\n\n#    if defined( _MSC_VER )\n#        pragma warning( pop )\n#    endif\n        }\n#endif\n} // namespace Detail\n} // namespace Catch\n\n\n\n\n#include <cstdio>\n#include <fstream>\n\nnamespace Catch {\n\n    Catch::IStream::~IStream() = default;\n\nnamespace Detail {\n    namespace {\n        template<typename WriterF, std::size_t bufferSize=256>\n        class StreamBufImpl final : public std::streambuf {\n            char data[bufferSize];\n            WriterF m_writer;\n\n        public:\n            StreamBufImpl() {\n                setp( data, data + sizeof(data) );\n            }\n\n            ~StreamBufImpl() noexcept override {\n                StreamBufImpl::sync();\n            }\n\n        private:\n            int overflow( int c ) override {\n                sync();\n\n                if( c != EOF ) {\n                    if( pbase() == epptr() )\n                        m_writer( std::string( 1, static_cast<char>( c ) ) );\n                    else\n                        sputc( static_cast<char>( c ) );\n                }\n                return 0;\n            }\n\n            int sync() override {\n                if( pbase() != pptr() ) {\n                    m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) );\n                    setp( pbase(), epptr() );\n                }\n                return 0;\n            }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        struct OutputDebugWriter {\n\n            void operator()( std::string const& str ) {\n                if ( !str.empty() ) {\n                    writeToDebugConsole( str );\n                }\n            }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        class FileStream final : public IStream {\n            std::ofstream m_ofs;\n        public:\n            FileStream( std::string const& filename ) {\n                m_ofs.open( filename.c_str() );\n                CATCH_ENFORCE( !m_ofs.fail(), \"Unable to open file: '\" << filename << '\\'' );\n                m_ofs << std::unitbuf;\n            }\n        public: // IStream\n            std::ostream& stream() override {\n                return m_ofs;\n            }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        class CoutStream final : public IStream {\n            std::ostream m_os;\n        public:\n            // Store the streambuf from cout up-front because\n            // cout may get redirected when running tests\n            CoutStream() : m_os( Catch::cout().rdbuf() ) {}\n\n        public: // IStream\n            std::ostream& stream() override { return m_os; }\n            bool isConsole() const override { return true; }\n        };\n\n        class CerrStream : public IStream {\n            std::ostream m_os;\n\n        public:\n            // Store the streambuf from cerr up-front because\n            // cout may get redirected when running tests\n            CerrStream(): m_os( Catch::cerr().rdbuf() ) {}\n\n        public: // IStream\n            std::ostream& stream() override { return m_os; }\n            bool isConsole() const override { return true; }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        class DebugOutStream final : public IStream {\n            Detail::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf;\n            std::ostream m_os;\n        public:\n            DebugOutStream()\n            :   m_streamBuf( Detail::make_unique<StreamBufImpl<OutputDebugWriter>>() ),\n                m_os( m_streamBuf.get() )\n            {}\n\n        public: // IStream\n            std::ostream& stream() override { return m_os; }\n        };\n\n    } // unnamed namespace\n} // namespace Detail\n\n    ///////////////////////////////////////////////////////////////////////////\n\n    auto makeStream( std::string const& filename ) -> Detail::unique_ptr<IStream> {\n        if ( filename.empty() || filename == \"-\" ) {\n            return Detail::make_unique<Detail::CoutStream>();\n        }\n        if( filename[0] == '%' ) {\n            if ( filename == \"%debug\" ) {\n                return Detail::make_unique<Detail::DebugOutStream>();\n            } else if ( filename == \"%stderr\" ) {\n                return Detail::make_unique<Detail::CerrStream>();\n            } else if ( filename == \"%stdout\" ) {\n                return Detail::make_unique<Detail::CoutStream>();\n            } else {\n                CATCH_ERROR( \"Unrecognised stream: '\" << filename << '\\'' );\n            }\n        }\n        return Detail::make_unique<Detail::FileStream>( filename );\n    }\n\n}\n\n\n\nnamespace Catch {\n    void JsonUtils::indent( std::ostream& os, std::uint64_t level ) {\n        for ( std::uint64_t i = 0; i < level; ++i ) {\n            os << \"  \";\n        }\n    }\n    void JsonUtils::appendCommaNewline( std::ostream& os,\n                                        bool& should_comma,\n                                        std::uint64_t level ) {\n        if ( should_comma ) { os << ','; }\n        should_comma = true;\n        os << '\\n';\n        indent( os, level );\n    }\n\n    JsonObjectWriter::JsonObjectWriter( std::ostream& os ):\n        JsonObjectWriter{ os, 0 } {}\n\n    JsonObjectWriter::JsonObjectWriter( std::ostream& os,\n                                        std::uint64_t indent_level ):\n        m_os{ os }, m_indent_level{ indent_level } {\n        m_os << '{';\n    }\n    JsonObjectWriter::JsonObjectWriter( JsonObjectWriter&& source ) noexcept:\n        m_os{ source.m_os },\n        m_indent_level{ source.m_indent_level },\n        m_should_comma{ source.m_should_comma },\n        m_active{ source.m_active } {\n        source.m_active = false;\n    }\n\n    JsonObjectWriter::~JsonObjectWriter() {\n        if ( !m_active ) { return; }\n\n        m_os << '\\n';\n        JsonUtils::indent( m_os, m_indent_level );\n        m_os << '}';\n    }\n\n    JsonValueWriter JsonObjectWriter::write( StringRef key ) {\n        JsonUtils::appendCommaNewline(\n            m_os, m_should_comma, m_indent_level + 1 );\n\n        m_os << '\"' << key << \"\\\": \";\n        return JsonValueWriter{ m_os, m_indent_level + 1 };\n    }\n\n    JsonArrayWriter::JsonArrayWriter( std::ostream& os ):\n        JsonArrayWriter{ os, 0 } {}\n    JsonArrayWriter::JsonArrayWriter( std::ostream& os,\n                                      std::uint64_t indent_level ):\n        m_os{ os }, m_indent_level{ indent_level } {\n        m_os << '[';\n    }\n    JsonArrayWriter::JsonArrayWriter( JsonArrayWriter&& source ) noexcept:\n        m_os{ source.m_os },\n        m_indent_level{ source.m_indent_level },\n        m_should_comma{ source.m_should_comma },\n        m_active{ source.m_active } {\n        source.m_active = false;\n    }\n    JsonArrayWriter::~JsonArrayWriter() {\n        if ( !m_active ) { return; }\n\n        m_os << '\\n';\n        JsonUtils::indent( m_os, m_indent_level );\n        m_os << ']';\n    }\n\n    JsonObjectWriter JsonArrayWriter::writeObject() {\n        JsonUtils::appendCommaNewline(\n            m_os, m_should_comma, m_indent_level + 1 );\n        return JsonObjectWriter{ m_os, m_indent_level + 1 };\n    }\n\n    JsonArrayWriter JsonArrayWriter::writeArray() {\n        JsonUtils::appendCommaNewline(\n            m_os, m_should_comma, m_indent_level + 1 );\n        return JsonArrayWriter{ m_os, m_indent_level + 1 };\n    }\n\n    JsonArrayWriter& JsonArrayWriter::write( bool value ) {\n        return writeImpl( value );\n    }\n\n    JsonValueWriter::JsonValueWriter( std::ostream& os ):\n        JsonValueWriter{ os, 0 } {}\n\n    JsonValueWriter::JsonValueWriter( std::ostream& os,\n                                      std::uint64_t indent_level ):\n        m_os{ os }, m_indent_level{ indent_level } {}\n\n    JsonObjectWriter JsonValueWriter::writeObject() && {\n        return JsonObjectWriter{ m_os, m_indent_level };\n    }\n\n    JsonArrayWriter JsonValueWriter::writeArray() && {\n        return JsonArrayWriter{ m_os, m_indent_level };\n    }\n\n    void JsonValueWriter::write( Catch::StringRef value ) && {\n        writeImpl( value, true );\n    }\n\n    void JsonValueWriter::write( bool value ) && {\n        writeImpl( value ? \"true\"_sr : \"false\"_sr, false );\n    }\n\n    void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) {\n        if ( quote ) { m_os << '\"'; }\n        for (char c : value) {\n            // Escape list taken from https://www.json.org/json-en.html,\n            // string definition.\n            // Note that while forward slash _can_ be escaped, it does\n            // not have to be, if JSON is not further embedded somewhere\n            // where forward slash is meaningful.\n            if ( c == '\"' ) {\n                m_os << \"\\\\\\\"\";\n            } else if ( c == '\\\\' ) {\n                m_os << \"\\\\\\\\\";\n            } else if ( c == '\\b' ) {\n                m_os << \"\\\\b\";\n            } else if ( c == '\\f' ) {\n                m_os << \"\\\\f\";\n            } else if ( c == '\\n' ) {\n                m_os << \"\\\\n\";\n            } else if ( c == '\\r' ) {\n                m_os << \"\\\\r\";\n            } else if ( c == '\\t' ) {\n                m_os << \"\\\\t\";\n            } else {\n                m_os << c;\n            }\n        }\n        if ( quote ) { m_os << '\"'; }\n    }\n\n} // namespace Catch\n\n\n\n\nnamespace Catch {\n\n    auto operator << (std::ostream& os, LazyExpression const& lazyExpr) -> std::ostream& {\n        if (lazyExpr.m_isNegated)\n            os << '!';\n\n        if (lazyExpr) {\n            if (lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression())\n                os << '(' << *lazyExpr.m_transientExpression << ')';\n            else\n                os << *lazyExpr.m_transientExpression;\n        } else {\n            os << \"{** error - unchecked empty expression requested **}\";\n        }\n        return os;\n    }\n\n} // namespace Catch\n\n\n\n\n#ifdef CATCH_CONFIG_WINDOWS_CRTDBG\n#include <crtdbg.h>\n\nnamespace Catch {\n\n    LeakDetector::LeakDetector() {\n        int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);\n        flag |= _CRTDBG_LEAK_CHECK_DF;\n        flag |= _CRTDBG_ALLOC_MEM_DF;\n        _CrtSetDbgFlag(flag);\n        _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);\n        _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);\n        // Change this to leaking allocation's number to break there\n        _CrtSetBreakAlloc(-1);\n    }\n}\n\n#else // ^^ Windows crt debug heap enabled // Windows crt debug heap disabled vv\n\n    Catch::LeakDetector::LeakDetector() = default;\n\n#endif // CATCH_CONFIG_WINDOWS_CRTDBG\n\nCatch::LeakDetector::~LeakDetector() {\n    Catch::cleanUp();\n}\n\n\n\n\nnamespace Catch {\n    namespace {\n\n        void listTests(IEventListener& reporter, IConfig const& config) {\n            auto const& testSpec = config.testSpec();\n            auto matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config);\n            reporter.listTests(matchedTestCases);\n        }\n\n        void listTags(IEventListener& reporter, IConfig const& config) {\n            auto const& testSpec = config.testSpec();\n            std::vector<TestCaseHandle> matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config);\n\n            std::map<StringRef, TagInfo, Detail::CaseInsensitiveLess> tagCounts;\n            for (auto const& testCase : matchedTestCases) {\n                for (auto const& tagName : testCase.getTestCaseInfo().tags) {\n                    auto it = tagCounts.find(tagName.original);\n                    if (it == tagCounts.end())\n                        it = tagCounts.insert(std::make_pair(tagName.original, TagInfo())).first;\n                    it->second.add(tagName.original);\n                }\n            }\n\n            std::vector<TagInfo> infos; infos.reserve(tagCounts.size());\n            for (auto& tagc : tagCounts) {\n                infos.push_back(CATCH_MOVE(tagc.second));\n            }\n\n            reporter.listTags(infos);\n        }\n\n        void listReporters(IEventListener& reporter) {\n            std::vector<ReporterDescription> descriptions;\n\n            auto const& factories = getRegistryHub().getReporterRegistry().getFactories();\n            descriptions.reserve(factories.size());\n            for (auto const& fac : factories) {\n                descriptions.push_back({ fac.first, fac.second->getDescription() });\n            }\n\n            reporter.listReporters(descriptions);\n        }\n\n        void listListeners(IEventListener& reporter) {\n            std::vector<ListenerDescription> descriptions;\n\n            auto const& factories =\n                getRegistryHub().getReporterRegistry().getListeners();\n            descriptions.reserve( factories.size() );\n            for ( auto const& fac : factories ) {\n                descriptions.push_back( { fac->getName(), fac->getDescription() } );\n            }\n\n            reporter.listListeners( descriptions );\n        }\n\n    } // end anonymous namespace\n\n    void TagInfo::add( StringRef spelling ) {\n        ++count;\n        spellings.insert( spelling );\n    }\n\n    std::string TagInfo::all() const {\n        // 2 per tag for brackets '[' and ']'\n        size_t size =  spellings.size() * 2;\n        for (auto const& spelling : spellings) {\n            size += spelling.size();\n        }\n\n        std::string out; out.reserve(size);\n        for (auto const& spelling : spellings) {\n            out += '[';\n            out += spelling;\n            out += ']';\n        }\n        return out;\n    }\n\n    bool list( IEventListener& reporter, Config const& config ) {\n        bool listed = false;\n        if (config.listTests()) {\n            listed = true;\n            listTests(reporter, config);\n        }\n        if (config.listTags()) {\n            listed = true;\n            listTags(reporter, config);\n        }\n        if (config.listReporters()) {\n            listed = true;\n            listReporters(reporter);\n        }\n        if ( config.listListeners() ) {\n            listed = true;\n            listListeners( reporter );\n        }\n        return listed;\n    }\n\n} // end namespace Catch\n\n\n\nnamespace Catch {\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS\n    static const LeakDetector leakDetector;\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n}\n\n// Allow users of amalgamated .cpp file to remove our main and provide their own.\n#if !defined(CATCH_AMALGAMATED_CUSTOM_MAIN)\n\n#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)\n// Standard C/C++ Win32 Unicode wmain entry point\nextern \"C\" int __cdecl wmain (int argc, wchar_t * argv[], wchar_t * []) {\n#else\n// Standard C/C++ main entry point\nint main (int argc, char * argv[]) {\n#endif\n\n    // We want to force the linker not to discard the global variable\n    // and its constructor, as it (optionally) registers leak detector\n    (void)&Catch::leakDetector;\n\n    return Catch::Session().run( argc, argv );\n}\n\n#endif // !defined(CATCH_AMALGAMATED_CUSTOM_MAIN\n\n\n\n\nnamespace Catch {\n\n    MessageInfo::MessageInfo(   StringRef _macroName,\n                                SourceLineInfo const& _lineInfo,\n                                ResultWas::OfType _type )\n    :   macroName( _macroName ),\n        lineInfo( _lineInfo ),\n        type( _type ),\n        sequence( ++globalCount )\n    {}\n\n    // This may need protecting if threading support is added\n    unsigned int MessageInfo::globalCount = 0;\n\n} // end namespace Catch\n\n\n\n#include <cstdio>\n#include <cstring>\n#include <iosfwd>\n#include <sstream>\n\n#if defined( CATCH_CONFIG_NEW_CAPTURE )\n#    if defined( _MSC_VER )\n#        include <io.h> //_dup and _dup2\n#        define dup _dup\n#        define dup2 _dup2\n#        define fileno _fileno\n#    else\n#        include <unistd.h> // dup and dup2\n#    endif\n#endif\n\nnamespace Catch {\n\n    namespace {\n        //! A no-op implementation, used if no reporter wants output\n        //! redirection.\n        class NoopRedirect : public OutputRedirect {\n            void activateImpl() override {}\n            void deactivateImpl() override {}\n            std::string getStdout() override { return {}; }\n            std::string getStderr() override { return {}; }\n            void clearBuffers() override {}\n        };\n\n        /**\n         * Redirects specific stream's rdbuf with another's.\n         *\n         * Redirection can be stopped and started on-demand, assumes\n         * that the underlying stream's rdbuf aren't changed by other\n         * users.\n         */\n        class RedirectedStreamNew {\n            std::ostream& m_originalStream;\n            std::ostream& m_redirectionStream;\n            std::streambuf* m_prevBuf;\n\n        public:\n            RedirectedStreamNew( std::ostream& originalStream,\n                                 std::ostream& redirectionStream ):\n                m_originalStream( originalStream ),\n                m_redirectionStream( redirectionStream ),\n                m_prevBuf( m_originalStream.rdbuf() ) {}\n\n            void startRedirect() {\n                m_originalStream.rdbuf( m_redirectionStream.rdbuf() );\n            }\n            void stopRedirect() { m_originalStream.rdbuf( m_prevBuf ); }\n        };\n\n        /**\n         * Redirects the `std::cout`, `std::cerr`, `std::clog` streams,\n         * but does not touch the actual `stdout`/`stderr` file descriptors.\n         */\n        class StreamRedirect : public OutputRedirect {\n            ReusableStringStream m_redirectedOut, m_redirectedErr;\n            RedirectedStreamNew m_cout, m_cerr, m_clog;\n\n        public:\n            StreamRedirect():\n                m_cout( Catch::cout(), m_redirectedOut.get() ),\n                m_cerr( Catch::cerr(), m_redirectedErr.get() ),\n                m_clog( Catch::clog(), m_redirectedErr.get() ) {}\n\n            void activateImpl() override {\n                m_cout.startRedirect();\n                m_cerr.startRedirect();\n                m_clog.startRedirect();\n            }\n            void deactivateImpl() override {\n                m_cout.stopRedirect();\n                m_cerr.stopRedirect();\n                m_clog.stopRedirect();\n            }\n            std::string getStdout() override { return m_redirectedOut.str(); }\n            std::string getStderr() override { return m_redirectedErr.str(); }\n            void clearBuffers() override {\n                m_redirectedOut.str( \"\" );\n                m_redirectedErr.str( \"\" );\n            }\n        };\n\n#if defined( CATCH_CONFIG_NEW_CAPTURE )\n\n        // Windows's implementation of std::tmpfile is terrible (it tries\n        // to create a file inside system folder, thus requiring elevated\n        // privileges for the binary), so we have to use tmpnam(_s) and\n        // create the file ourselves there.\n        class TempFile {\n        public:\n            TempFile( TempFile const& ) = delete;\n            TempFile& operator=( TempFile const& ) = delete;\n            TempFile( TempFile&& ) = delete;\n            TempFile& operator=( TempFile&& ) = delete;\n\n#    if defined( _MSC_VER )\n            TempFile() {\n                if ( tmpnam_s( m_buffer ) ) {\n                    CATCH_RUNTIME_ERROR( \"Could not get a temp filename\" );\n                }\n                if ( fopen_s( &m_file, m_buffer, \"wb+\" ) ) {\n                    char buffer[100];\n                    if ( strerror_s( buffer, errno ) ) {\n                        CATCH_RUNTIME_ERROR(\n                            \"Could not translate errno to a string\" );\n                    }\n                    CATCH_RUNTIME_ERROR( \"Could not open the temp file: '\"\n                                         << m_buffer\n                                         << \"' because: \" << buffer );\n                }\n            }\n#    else\n            TempFile() {\n                m_file = std::tmpfile();\n                if ( !m_file ) {\n                    CATCH_RUNTIME_ERROR( \"Could not create a temp file.\" );\n                }\n            }\n#    endif\n\n            ~TempFile() {\n                // TBD: What to do about errors here?\n                std::fclose( m_file );\n                // We manually create the file on Windows only, on Linux\n                // it will be autodeleted\n#    if defined( _MSC_VER )\n                std::remove( m_buffer );\n#    endif\n            }\n\n            std::FILE* getFile() { return m_file; }\n            std::string getContents() {\n                ReusableStringStream sstr;\n                constexpr long buffer_size = 100;\n                char buffer[buffer_size + 1] = {};\n                long current_pos = ftell( m_file );\n                CATCH_ENFORCE( current_pos >= 0,\n                               \"ftell failed, errno: \" << errno );\n                std::rewind( m_file );\n                while ( current_pos > 0 ) {\n                    auto read_characters =\n                        std::fread( buffer,\n                                    1,\n                                    std::min( buffer_size, current_pos ),\n                                    m_file );\n                    buffer[read_characters] = '\\0';\n                    sstr << buffer;\n                    current_pos -= static_cast<long>( read_characters );\n                }\n                return sstr.str();\n            }\n\n            void clear() { std::rewind( m_file ); }\n\n        private:\n            std::FILE* m_file = nullptr;\n            char m_buffer[L_tmpnam] = { 0 };\n        };\n\n        /**\n         * Redirects the actual `stdout`/`stderr` file descriptors.\n         *\n         * Works by replacing the file descriptors numbered 1 and 2\n         * with an open temporary file.\n         */\n        class FileRedirect : public OutputRedirect {\n            TempFile m_outFile, m_errFile;\n            int m_originalOut = -1;\n            int m_originalErr = -1;\n\n            // Flushes cout/cerr/clog streams and stdout/stderr FDs\n            void flushEverything() {\n                Catch::cout() << std::flush;\n                fflush( stdout );\n                // Since we support overriding these streams, we flush cerr\n                // even though std::cerr is unbuffered\n                Catch::cerr() << std::flush;\n                Catch::clog() << std::flush;\n                fflush( stderr );\n            }\n\n        public:\n            FileRedirect():\n                m_originalOut( dup( fileno( stdout ) ) ),\n                m_originalErr( dup( fileno( stderr ) ) ) {\n                CATCH_ENFORCE( m_originalOut >= 0, \"Could not dup stdout\" );\n                CATCH_ENFORCE( m_originalErr >= 0, \"Could not dup stderr\" );\n            }\n\n            std::string getStdout() override { return m_outFile.getContents(); }\n            std::string getStderr() override { return m_errFile.getContents(); }\n            void clearBuffers() override {\n                m_outFile.clear();\n                m_errFile.clear();\n            }\n\n            void activateImpl() override {\n                // We flush before starting redirect, to ensure that we do\n                // not capture the end of message sent before activation.\n                flushEverything();\n\n                int ret;\n                ret = dup2( fileno( m_outFile.getFile() ), fileno( stdout ) );\n                CATCH_ENFORCE( ret >= 0,\n                               \"dup2 to stdout has failed, errno: \" << errno );\n                ret = dup2( fileno( m_errFile.getFile() ), fileno( stderr ) );\n                CATCH_ENFORCE( ret >= 0,\n                               \"dup2 to stderr has failed, errno: \" << errno );\n            }\n            void deactivateImpl() override {\n                // We flush before ending redirect, to ensure that we\n                // capture all messages sent while the redirect was active.\n                flushEverything();\n\n                int ret;\n                ret = dup2( m_originalOut, fileno( stdout ) );\n                CATCH_ENFORCE(\n                    ret >= 0,\n                    \"dup2 of original stdout has failed, errno: \" << errno );\n                ret = dup2( m_originalErr, fileno( stderr ) );\n                CATCH_ENFORCE(\n                    ret >= 0,\n                    \"dup2 of original stderr has failed, errno: \" << errno );\n            }\n        };\n\n#endif // CATCH_CONFIG_NEW_CAPTURE\n\n    } // end namespace\n\n    bool isRedirectAvailable( OutputRedirect::Kind kind ) {\n        switch ( kind ) {\n        // These two are always available\n        case OutputRedirect::None:\n        case OutputRedirect::Streams:\n            return true;\n#if defined( CATCH_CONFIG_NEW_CAPTURE )\n        case OutputRedirect::FileDescriptors:\n            return true;\n#endif\n        default:\n            return false;\n        }\n    }\n\n    Detail::unique_ptr<OutputRedirect> makeOutputRedirect( bool actual ) {\n        if ( actual ) {\n            // TODO: Clean this up later\n#if defined( CATCH_CONFIG_NEW_CAPTURE )\n            return Detail::make_unique<FileRedirect>();\n#else\n            return Detail::make_unique<StreamRedirect>();\n#endif\n        } else {\n            return Detail::make_unique<NoopRedirect>();\n        }\n    }\n\n    RedirectGuard scopedActivate( OutputRedirect& redirectImpl ) {\n        return RedirectGuard( true, redirectImpl );\n    }\n\n    RedirectGuard scopedDeactivate( OutputRedirect& redirectImpl ) {\n        return RedirectGuard( false, redirectImpl );\n    }\n\n    OutputRedirect::~OutputRedirect() = default;\n\n    RedirectGuard::RedirectGuard( bool activate, OutputRedirect& redirectImpl ):\n        m_redirect( &redirectImpl ),\n        m_activate( activate ),\n        m_previouslyActive( redirectImpl.isActive() ) {\n\n        // Skip cases where there is no actual state change.\n        if ( m_activate == m_previouslyActive ) { return; }\n\n        if ( m_activate ) {\n            m_redirect->activate();\n        } else {\n            m_redirect->deactivate();\n        }\n    }\n\n    RedirectGuard::~RedirectGuard() noexcept( false ) {\n        if ( m_moved ) { return; }\n        // Skip cases where there is no actual state change.\n        if ( m_activate == m_previouslyActive ) { return; }\n\n        if ( m_activate ) {\n            m_redirect->deactivate();\n        } else {\n            m_redirect->activate();\n        }\n    }\n\n    RedirectGuard::RedirectGuard( RedirectGuard&& rhs ) noexcept:\n        m_redirect( rhs.m_redirect ),\n        m_activate( rhs.m_activate ),\n        m_previouslyActive( rhs.m_previouslyActive ),\n        m_moved( false ) {\n        rhs.m_moved = true;\n    }\n\n    RedirectGuard& RedirectGuard::operator=( RedirectGuard&& rhs ) noexcept {\n        m_redirect = rhs.m_redirect;\n        m_activate = rhs.m_activate;\n        m_previouslyActive = rhs.m_previouslyActive;\n        m_moved = false;\n        rhs.m_moved = true;\n        return *this;\n    }\n\n} // namespace Catch\n\n#if defined( CATCH_CONFIG_NEW_CAPTURE )\n#    if defined( _MSC_VER )\n#        undef dup\n#        undef dup2\n#        undef fileno\n#    endif\n#endif\n\n\n\n\n#include <limits>\n#include <stdexcept>\n\nnamespace Catch {\n\n    Optional<unsigned int> parseUInt(std::string const& input, int base) {\n        auto trimmed = trim( input );\n        // std::stoull is annoying and accepts numbers starting with '-',\n        // it just negates them into unsigned int\n        if ( trimmed.empty() || trimmed[0] == '-' ) {\n            return {};\n        }\n\n        CATCH_TRY {\n            size_t pos = 0;\n            const auto ret = std::stoull( trimmed, &pos, base );\n\n            // We did not consume the whole input, so there is an issue\n            // This can be bunch of different stuff, like multiple numbers\n            // in the input, or invalid digits/characters and so on. Either\n            // way, we do not want to return the partially parsed result.\n            if ( pos != trimmed.size() ) {\n                return {};\n            }\n            // Too large\n            if ( ret > std::numeric_limits<unsigned int>::max() ) {\n                return {};\n            }\n            return static_cast<unsigned int>(ret);\n        }\n        CATCH_CATCH_ANON( std::invalid_argument const& ) {\n            // no conversion could be performed\n        }\n        CATCH_CATCH_ANON( std::out_of_range const& ) {\n            // the input does not fit into an unsigned long long\n        }\n        return {};\n    }\n\n} // namespace Catch\n\n\n\n\n#include <cmath>\n\nnamespace Catch {\n\n#if !defined(CATCH_CONFIG_POLYFILL_ISNAN)\n    bool isnan(float f) {\n        return std::isnan(f);\n    }\n    bool isnan(double d) {\n        return std::isnan(d);\n    }\n#else\n    // For now we only use this for embarcadero\n    bool isnan(float f) {\n        return std::_isnan(f);\n    }\n    bool isnan(double d) {\n        return std::_isnan(d);\n    }\n#endif\n\n#if !defined( CATCH_CONFIG_GLOBAL_NEXTAFTER )\n    float nextafter( float x, float y ) { return std::nextafter( x, y ); }\n    double nextafter( double x, double y ) { return std::nextafter( x, y ); }\n#else\n    float nextafter( float x, float y ) { return ::nextafterf( x, y ); }\n    double nextafter( double x, double y ) { return ::nextafter( x, y ); }\n#endif\n\n} // end namespace Catch\n\n\n\n#if defined( __clang__ )\n#    define CATCH2_CLANG_NO_SANITIZE_INTEGER \\\n        __attribute__( ( no_sanitize( \"unsigned-integer-overflow\" ) ) )\n#else\n#    define CATCH2_CLANG_NO_SANITIZE_INTEGER\n#endif\nnamespace Catch {\n\nnamespace {\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable:4146) // we negate uint32 during the rotate\n#endif\n        // Safe rotr implementation thanks to John Regehr\n        CATCH2_CLANG_NO_SANITIZE_INTEGER\n        uint32_t rotate_right(uint32_t val, uint32_t count) {\n            const uint32_t mask = 31;\n            count &= mask;\n            return (val >> count) | (val << (-count & mask));\n        }\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\n}\n\n\n    SimplePcg32::SimplePcg32(result_type seed_) {\n        seed(seed_);\n    }\n\n\n    void SimplePcg32::seed(result_type seed_) {\n        m_state = 0;\n        (*this)();\n        m_state += seed_;\n        (*this)();\n    }\n\n    void SimplePcg32::discard(uint64_t skip) {\n        // We could implement this to run in O(log n) steps, but this\n        // should suffice for our use case.\n        for (uint64_t s = 0; s < skip; ++s) {\n            static_cast<void>((*this)());\n        }\n    }\n\n    CATCH2_CLANG_NO_SANITIZE_INTEGER\n    SimplePcg32::result_type SimplePcg32::operator()() {\n        // prepare the output value\n        const uint32_t xorshifted = static_cast<uint32_t>(((m_state >> 18u) ^ m_state) >> 27u);\n        const auto output = rotate_right(xorshifted, static_cast<uint32_t>(m_state >> 59u));\n\n        // advance state\n        m_state = m_state * 6364136223846793005ULL + s_inc;\n\n        return output;\n    }\n\n    bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) {\n        return lhs.m_state == rhs.m_state;\n    }\n\n    bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) {\n        return lhs.m_state != rhs.m_state;\n    }\n}\n\n\n\n\n\n#include <ctime>\n#include <random>\n\nnamespace Catch {\n\n    std::uint32_t generateRandomSeed( GenerateFrom from ) {\n        switch ( from ) {\n        case GenerateFrom::Time:\n            return static_cast<std::uint32_t>( std::time( nullptr ) );\n\n        case GenerateFrom::Default:\n        case GenerateFrom::RandomDevice: {\n            std::random_device rd;\n            return Detail::fillBitsFrom<std::uint32_t>( rd );\n        }\n\n        default:\n            CATCH_ERROR(\"Unknown generation method\");\n        }\n    }\n\n} // end namespace Catch\n\n\n\n\nnamespace Catch {\n    struct ReporterRegistry::ReporterRegistryImpl {\n        std::vector<Detail::unique_ptr<EventListenerFactory>> listeners;\n        std::map<std::string, IReporterFactoryPtr, Detail::CaseInsensitiveLess>\n            factories;\n    };\n\n    ReporterRegistry::ReporterRegistry():\n        m_impl( Detail::make_unique<ReporterRegistryImpl>() ) {\n        // Because it is impossible to move out of initializer list,\n        // we have to add the elements manually\n        m_impl->factories[\"Automake\"] =\n            Detail::make_unique<ReporterFactory<AutomakeReporter>>();\n        m_impl->factories[\"compact\"] =\n            Detail::make_unique<ReporterFactory<CompactReporter>>();\n        m_impl->factories[\"console\"] =\n            Detail::make_unique<ReporterFactory<ConsoleReporter>>();\n        m_impl->factories[\"JUnit\"] =\n            Detail::make_unique<ReporterFactory<JunitReporter>>();\n        m_impl->factories[\"SonarQube\"] =\n            Detail::make_unique<ReporterFactory<SonarQubeReporter>>();\n        m_impl->factories[\"TAP\"] =\n            Detail::make_unique<ReporterFactory<TAPReporter>>();\n        m_impl->factories[\"TeamCity\"] =\n            Detail::make_unique<ReporterFactory<TeamCityReporter>>();\n        m_impl->factories[\"XML\"] =\n            Detail::make_unique<ReporterFactory<XmlReporter>>();\n        m_impl->factories[\"JSON\"] =\n            Detail::make_unique<ReporterFactory<JsonReporter>>();\n    }\n\n    ReporterRegistry::~ReporterRegistry() = default;\n\n    IEventListenerPtr\n    ReporterRegistry::create( std::string const& name,\n                              ReporterConfig&& config ) const {\n        auto it = m_impl->factories.find( name );\n        if ( it == m_impl->factories.end() ) return nullptr;\n        return it->second->create( CATCH_MOVE( config ) );\n    }\n\n    void ReporterRegistry::registerReporter( std::string const& name,\n                                             IReporterFactoryPtr factory ) {\n        CATCH_ENFORCE( name.find( \"::\" ) == name.npos,\n                       \"'::' is not allowed in reporter name: '\" + name +\n                           '\\'' );\n        auto ret = m_impl->factories.emplace( name, CATCH_MOVE( factory ) );\n        CATCH_ENFORCE( ret.second,\n                       \"reporter using '\" + name +\n                           \"' as name was already registered\" );\n    }\n    void ReporterRegistry::registerListener(\n        Detail::unique_ptr<EventListenerFactory> factory ) {\n        m_impl->listeners.push_back( CATCH_MOVE( factory ) );\n    }\n\n    std::map<std::string,\n             IReporterFactoryPtr,\n             Detail::CaseInsensitiveLess> const&\n    ReporterRegistry::getFactories() const {\n        return m_impl->factories;\n    }\n\n    std::vector<Detail::unique_ptr<EventListenerFactory>> const&\n    ReporterRegistry::getListeners() const {\n        return m_impl->listeners;\n    }\n} // namespace Catch\n\n\n\n\n\n#include <algorithm>\n\nnamespace Catch {\n\n    namespace {\n        struct kvPair {\n            StringRef key, value;\n        };\n\n        kvPair splitKVPair(StringRef kvString) {\n            auto splitPos = static_cast<size_t>(\n                std::find( kvString.begin(), kvString.end(), '=' ) -\n                kvString.begin() );\n\n            return { kvString.substr( 0, splitPos ),\n                     kvString.substr( splitPos + 1, kvString.size() ) };\n        }\n    }\n\n    namespace Detail {\n        std::vector<std::string> splitReporterSpec( StringRef reporterSpec ) {\n            static constexpr auto separator = \"::\";\n            static constexpr size_t separatorSize = 2;\n\n            size_t separatorPos = 0;\n            auto findNextSeparator = [&reporterSpec]( size_t startPos ) {\n                static_assert(\n                    separatorSize == 2,\n                    \"The code below currently assumes 2 char separator\" );\n\n                auto currentPos = startPos;\n                do {\n                    while ( currentPos < reporterSpec.size() &&\n                            reporterSpec[currentPos] != separator[0] ) {\n                        ++currentPos;\n                    }\n                    if ( currentPos + 1 < reporterSpec.size() &&\n                         reporterSpec[currentPos + 1] == separator[1] ) {\n                        return currentPos;\n                    }\n                    ++currentPos;\n                } while ( currentPos < reporterSpec.size() );\n\n                return static_cast<size_t>( -1 );\n            };\n\n            std::vector<std::string> parts;\n\n            while ( separatorPos < reporterSpec.size() ) {\n                const auto nextSeparator = findNextSeparator( separatorPos );\n                parts.push_back( static_cast<std::string>( reporterSpec.substr(\n                    separatorPos, nextSeparator - separatorPos ) ) );\n\n                if ( nextSeparator == static_cast<size_t>( -1 ) ) {\n                    break;\n                }\n                separatorPos = nextSeparator + separatorSize;\n            }\n\n            // Handle a separator at the end.\n            // This is not a valid spec, but we want to do validation in a\n            // centralized place\n            if ( separatorPos == reporterSpec.size() ) {\n                parts.emplace_back();\n            }\n\n            return parts;\n        }\n\n        Optional<ColourMode> stringToColourMode( StringRef colourMode ) {\n            if ( colourMode == \"default\" ) {\n                return ColourMode::PlatformDefault;\n            } else if ( colourMode == \"ansi\" ) {\n                return ColourMode::ANSI;\n            } else if ( colourMode == \"win32\" ) {\n                return ColourMode::Win32;\n            } else if ( colourMode == \"none\" ) {\n                return ColourMode::None;\n            } else {\n                return {};\n            }\n        }\n    } // namespace Detail\n\n\n    bool operator==( ReporterSpec const& lhs, ReporterSpec const& rhs ) {\n        return lhs.m_name == rhs.m_name &&\n               lhs.m_outputFileName == rhs.m_outputFileName &&\n               lhs.m_colourMode == rhs.m_colourMode &&\n               lhs.m_customOptions == rhs.m_customOptions;\n    }\n\n    Optional<ReporterSpec> parseReporterSpec( StringRef reporterSpec ) {\n        auto parts = Detail::splitReporterSpec( reporterSpec );\n\n        assert( parts.size() > 0 && \"Split should never return empty vector\" );\n\n        std::map<std::string, std::string> kvPairs;\n        Optional<std::string> outputFileName;\n        Optional<ColourMode> colourMode;\n\n        // First part is always reporter name, so we skip it\n        for ( size_t i = 1; i < parts.size(); ++i ) {\n            auto kv = splitKVPair( parts[i] );\n            auto key = kv.key, value = kv.value;\n\n            if ( key.empty() || value.empty() ) { // NOLINT(bugprone-branch-clone)\n                return {};\n            } else if ( key[0] == 'X' ) {\n                // This is a reporter-specific option, we don't check these\n                // apart from basic sanity checks\n                if ( key.size() == 1 ) {\n                    return {};\n                }\n\n                auto ret = kvPairs.emplace( std::string(kv.key), std::string(kv.value) );\n                if ( !ret.second ) {\n                    // Duplicated key. We might want to handle this differently,\n                    // e.g. by overwriting the existing value?\n                    return {};\n                }\n            } else if ( key == \"out\" ) {\n                // Duplicated key\n                if ( outputFileName ) {\n                    return {};\n                }\n                outputFileName = static_cast<std::string>( value );\n            } else if ( key == \"colour-mode\" ) {\n                // Duplicated key\n                if ( colourMode ) {\n                    return {};\n                }\n                colourMode = Detail::stringToColourMode( value );\n                // Parsing failed\n                if ( !colourMode ) {\n                    return {};\n                }\n            } else {\n                // Unrecognized option\n                return {};\n            }\n        }\n\n        return ReporterSpec{ CATCH_MOVE( parts[0] ),\n                             CATCH_MOVE( outputFileName ),\n                             CATCH_MOVE( colourMode ),\n                             CATCH_MOVE( kvPairs ) };\n    }\n\nReporterSpec::ReporterSpec(\n        std::string name,\n        Optional<std::string> outputFileName,\n        Optional<ColourMode> colourMode,\n        std::map<std::string, std::string> customOptions ):\n        m_name( CATCH_MOVE( name ) ),\n        m_outputFileName( CATCH_MOVE( outputFileName ) ),\n        m_colourMode( CATCH_MOVE( colourMode ) ),\n        m_customOptions( CATCH_MOVE( customOptions ) ) {}\n\n} // namespace Catch\n\n\n\n#include <cstdio>\n#include <sstream>\n#include <vector>\n\nnamespace Catch {\n\n    // This class encapsulates the idea of a pool of ostringstreams that can be reused.\n    struct StringStreams {\n        std::vector<Detail::unique_ptr<std::ostringstream>> m_streams;\n        std::vector<std::size_t> m_unused;\n        std::ostringstream m_referenceStream; // Used for copy state/ flags from\n\n        auto add() -> std::size_t {\n            if( m_unused.empty() ) {\n                m_streams.push_back( Detail::make_unique<std::ostringstream>() );\n                return m_streams.size()-1;\n            }\n            else {\n                auto index = m_unused.back();\n                m_unused.pop_back();\n                return index;\n            }\n        }\n\n        void release( std::size_t index ) {\n            m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state\n            m_unused.push_back(index);\n        }\n    };\n\n    ReusableStringStream::ReusableStringStream()\n    :   m_index( Singleton<StringStreams>::getMutable().add() ),\n        m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() )\n    {}\n\n    ReusableStringStream::~ReusableStringStream() {\n        static_cast<std::ostringstream*>( m_oss )->str(\"\");\n        m_oss->clear();\n        Singleton<StringStreams>::getMutable().release( m_index );\n    }\n\n    std::string ReusableStringStream::str() const {\n        return static_cast<std::ostringstream*>( m_oss )->str();\n    }\n\n    void ReusableStringStream::str( std::string const& str ) {\n        static_cast<std::ostringstream*>( m_oss )->str( str );\n    }\n\n\n}\n\n\n\n\n#include <cassert>\n#include <algorithm>\n\nnamespace Catch {\n\n    namespace Generators {\n        namespace {\n            struct GeneratorTracker final : TestCaseTracking::TrackerBase,\n                                      IGeneratorTracker {\n                GeneratorBasePtr m_generator;\n\n                GeneratorTracker(\n                    TestCaseTracking::NameAndLocation&& nameAndLocation,\n                    TrackerContext& ctx,\n                    ITracker* parent ):\n                    TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ) {}\n\n                static GeneratorTracker*\n                acquire( TrackerContext& ctx,\n                         TestCaseTracking::NameAndLocationRef const&\n                             nameAndLocation ) {\n                    GeneratorTracker* tracker;\n\n                    ITracker& currentTracker = ctx.currentTracker();\n                    // Under specific circumstances, the generator we want\n                    // to acquire is also the current tracker. If this is\n                    // the case, we have to avoid looking through current\n                    // tracker's children, and instead return the current\n                    // tracker.\n                    // A case where this check is important is e.g.\n                    //     for (int i = 0; i < 5; ++i) {\n                    //         int n = GENERATE(1, 2);\n                    //     }\n                    //\n                    // without it, the code above creates 5 nested generators.\n                    if ( currentTracker.nameAndLocation() == nameAndLocation ) {\n                        auto thisTracker = currentTracker.parent()->findChild(\n                            nameAndLocation );\n                        assert( thisTracker );\n                        assert( thisTracker->isGeneratorTracker() );\n                        tracker = static_cast<GeneratorTracker*>( thisTracker );\n                    } else if ( ITracker* childTracker =\n                                    currentTracker.findChild(\n                                        nameAndLocation ) ) {\n                        assert( childTracker );\n                        assert( childTracker->isGeneratorTracker() );\n                        tracker =\n                            static_cast<GeneratorTracker*>( childTracker );\n                    } else {\n                        return nullptr;\n                    }\n\n                    if ( !tracker->isComplete() ) { tracker->open(); }\n\n                    return tracker;\n                }\n\n                // TrackerBase interface\n                bool isGeneratorTracker() const override { return true; }\n                auto hasGenerator() const -> bool override {\n                    return !!m_generator;\n                }\n                void close() override {\n                    TrackerBase::close();\n                    // If a generator has a child (it is followed by a section)\n                    // and none of its children have started, then we must wait\n                    // until later to start consuming its values.\n                    // This catches cases where `GENERATE` is placed between two\n                    // `SECTION`s.\n                    // **The check for m_children.empty cannot be removed**.\n                    // doing so would break `GENERATE` _not_ followed by\n                    // `SECTION`s.\n                    const bool should_wait_for_child = [&]() {\n                        // No children -> nobody to wait for\n                        if ( m_children.empty() ) { return false; }\n                        // If at least one child started executing, don't wait\n                        if ( std::find_if(\n                                 m_children.begin(),\n                                 m_children.end(),\n                                 []( TestCaseTracking::ITrackerPtr const&\n                                         tracker ) {\n                                     return tracker->hasStarted();\n                                 } ) != m_children.end() ) {\n                            return false;\n                        }\n\n                        // No children have started. We need to check if they\n                        // _can_ start, and thus we should wait for them, or\n                        // they cannot start (due to filters), and we shouldn't\n                        // wait for them\n                        ITracker* parent = m_parent;\n                        // This is safe: there is always at least one section\n                        // tracker in a test case tracking tree\n                        while ( !parent->isSectionTracker() ) {\n                            parent = parent->parent();\n                        }\n                        assert( parent &&\n                                \"Missing root (test case) level section\" );\n\n                        auto const& parentSection =\n                            static_cast<SectionTracker const&>( *parent );\n                        auto const& filters = parentSection.getFilters();\n                        // No filters -> no restrictions on running sections\n                        if ( filters.empty() ) { return true; }\n\n                        for ( auto const& child : m_children ) {\n                            if ( child->isSectionTracker() &&\n                                 std::find( filters.begin(),\n                                            filters.end(),\n                                            static_cast<SectionTracker const&>(\n                                                *child )\n                                                .trimmedName() ) !=\n                                     filters.end() ) {\n                                return true;\n                            }\n                        }\n                        return false;\n                    }();\n\n                    // This check is a bit tricky, because m_generator->next()\n                    // has a side-effect, where it consumes generator's current\n                    // value, but we do not want to invoke the side-effect if\n                    // this generator is still waiting for any child to start.\n                    assert( m_generator && \"Tracker without generator\" );\n                    if ( should_wait_for_child ||\n                         ( m_runState == CompletedSuccessfully &&\n                           m_generator->countedNext() ) ) {\n                        m_children.clear();\n                        m_runState = Executing;\n                    }\n                }\n\n                // IGeneratorTracker interface\n                auto getGenerator() const -> GeneratorBasePtr const& override {\n                    return m_generator;\n                }\n                void setGenerator( GeneratorBasePtr&& generator ) override {\n                    m_generator = CATCH_MOVE( generator );\n                }\n            };\n        } // namespace\n    }\n\n    namespace Detail {\n        // Assertions are owned by the thread that is executing them.\n        // This allows for lock-free progress in common cases where we\n        // do not need to send the assertion events to the reporter.\n        // This also implies that messages are owned by their respective\n        // threads, and should not be shared across different threads.\n        //\n        // For simplicity, we disallow messages in multi-threaded contexts,\n        // but in the future we can enable them under this logic.\n        //\n        // This implies that various pieces of metadata referring to last\n        // assertion result/source location/message handling, etc\n        // should also be thread local. For now we just use naked globals\n        // below, in the future we will want to allocate piece of memory\n        // from heap, to avoid consuming too much thread-local storage.\n\n        // This is used for the \"if\" part of CHECKED_IF/CHECKED_ELSE\n        static thread_local bool g_lastAssertionPassed = false;\n        // Should we clear message scopes before sending off the messages to\n        // reporter? Set in `assertionPassedFastPath` to avoid doing the full\n        // clear there for performance reasons.\n        static thread_local bool g_clearMessageScopes = false;\n        // This is the source location for last encountered macro. It is\n        // used to provide the users with more precise location of error\n        // when an unexpected exception/fatal error happens.\n        static thread_local SourceLineInfo g_lastKnownLineInfo(\"DummyLocation\", static_cast<size_t>(-1));\n    }\n\n    RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)\n    :   m_runInfo(_config->name()),\n        m_config(_config),\n        m_reporter(CATCH_MOVE(reporter)),\n        m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ),\n        m_abortAfterXFailedAssertions( m_config->abortAfter() ),\n        m_reportAssertionStarting( m_reporter->getPreferences().shouldReportAllAssertionStarts ),\n        m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ),\n        m_shouldDebugBreak( m_config->shouldDebugBreak() )\n    {\n        getCurrentMutableContext().setResultCapture( this );\n        m_reporter->testRunStarting(m_runInfo);\n    }\n\n    RunContext::~RunContext() {\n        updateTotalsFromAtomics();\n        m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));\n    }\n\n    Totals RunContext::runTest(TestCaseHandle const& testCase) {\n        updateTotalsFromAtomics();\n        const Totals prevTotals = m_totals;\n\n        auto const& testInfo = testCase.getTestCaseInfo();\n        m_reporter->testCaseStarting(testInfo);\n        testCase.prepareTestCase();\n        m_activeTestCase = &testCase;\n\n\n        ITracker& rootTracker = m_trackerContext.startRun();\n        assert(rootTracker.isSectionTracker());\n        static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun());\n\n        // We intentionally only seed the internal RNG once per test case,\n        // before it is first invoked. The reason for that is a complex\n        // interplay of generator/section implementation details and the\n        // Random*Generator types.\n        //\n        // The issue boils down to us needing to seed the Random*Generators\n        // with different seed each, so that they return different sequences\n        // of random numbers. We do this by giving them a number from the\n        // shared RNG instance as their seed.\n        //\n        // However, this runs into an issue if the reseeding happens each\n        // time the test case is entered (as opposed to first time only),\n        // because multiple generators could get the same seed, e.g. in\n        // ```cpp\n        // TEST_CASE() {\n        //     auto i = GENERATE(take(10, random(0, 100));\n        //     SECTION(\"A\") {\n        //         auto j = GENERATE(take(10, random(0, 100));\n        //     }\n        //     SECTION(\"B\") {\n        //         auto k = GENERATE(take(10, random(0, 100));\n        //     }\n        // }\n        // ```\n        // `i` and `j` would properly return values from different sequences,\n        // but `i` and `k` would return the same sequence, because their seed\n        // would be the same.\n        // (The reason their seeds would be the same is that the generator\n        //  for k would be initialized when the test case is entered the second\n        //  time, after the shared RNG instance was reset to the same value\n        //  it had when the generator for i was initialized.)\n        seedRng( *m_config );\n\n        uint64_t testRuns = 0;\n        std::string redirectedCout;\n        std::string redirectedCerr;\n        do {\n            m_trackerContext.startCycle();\n            m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocationRef(testInfo.name, testInfo.lineInfo));\n\n            m_reporter->testCasePartialStarting(testInfo, testRuns);\n\n            updateTotalsFromAtomics();\n            const auto beforeRunTotals = m_totals;\n            runCurrentTest();\n            std::string oneRunCout = m_outputRedirect->getStdout();\n            std::string oneRunCerr = m_outputRedirect->getStderr();\n            m_outputRedirect->clearBuffers();\n            redirectedCout += oneRunCout;\n            redirectedCerr += oneRunCerr;\n\n            updateTotalsFromAtomics();\n            const auto singleRunTotals = m_totals.delta(beforeRunTotals);\n            auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting());\n            m_reporter->testCasePartialEnded(statsForOneRun, testRuns);\n\n            ++testRuns;\n        } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting());\n\n        Totals deltaTotals = m_totals.delta(prevTotals);\n        if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) {\n            deltaTotals.assertions.failed++;\n            deltaTotals.testCases.passed--;\n            deltaTotals.testCases.failed++;\n        }\n        m_totals.testCases += deltaTotals.testCases;\n        testCase.tearDownTestCase();\n        m_reporter->testCaseEnded(TestCaseStats(testInfo,\n                                  deltaTotals,\n                                  CATCH_MOVE(redirectedCout),\n                                  CATCH_MOVE(redirectedCerr),\n                                  aborting()));\n\n        m_activeTestCase = nullptr;\n        m_testCaseTracker = nullptr;\n\n        return deltaTotals;\n    }\n\n\n    void RunContext::assertionEnded(AssertionResult&& result) {\n        Detail::g_lastKnownLineInfo = result.m_info.lineInfo;\n        if (result.getResultType() == ResultWas::Ok) {\n            m_atomicAssertionCount.passed++;\n            Detail::g_lastAssertionPassed = true;\n        } else if (result.getResultType() == ResultWas::ExplicitSkip) {\n            m_atomicAssertionCount.skipped++;\n            Detail::g_lastAssertionPassed = true;\n        } else if (!result.succeeded()) {\n            Detail::g_lastAssertionPassed = false;\n            if (result.isOk()) {\n            }\n            else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine\n                m_atomicAssertionCount.failedButOk++;\n            else\n                m_atomicAssertionCount.failed++;\n        }\n        else {\n            Detail::g_lastAssertionPassed = true;\n        }\n\n        // From here, we are touching shared state and need mutex.\n        Detail::LockGuard lock( m_assertionMutex );\n        {\n            if ( Detail::g_clearMessageScopes ) {\n                m_messageScopes.clear();\n                Detail::g_clearMessageScopes = false;\n            }\n            auto _ = scopedDeactivate( *m_outputRedirect );\n            updateTotalsFromAtomics();\n            m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) );\n        }\n\n        if ( result.getResultType() != ResultWas::Warning ) {\n            m_messageScopes.clear();\n        }\n\n        // Reset working state. assertion info will be reset after\n        // populateReaction is run if it is needed\n        m_lastResult = CATCH_MOVE( result );\n    }\n\n    void RunContext::notifyAssertionStarted( AssertionInfo const& info ) {\n        if (m_reportAssertionStarting) {\n            Detail::LockGuard lock( m_assertionMutex );\n            auto _ = scopedDeactivate( *m_outputRedirect );\n            m_reporter->assertionStarting( info );\n        }\n    }\n\n    bool RunContext::sectionStarted( StringRef sectionName,\n                                     SourceLineInfo const& sectionLineInfo,\n                                     Counts& assertions ) {\n        ITracker& sectionTracker =\n            SectionTracker::acquire( m_trackerContext,\n                                     TestCaseTracking::NameAndLocationRef(\n                                         sectionName, sectionLineInfo ) );\n\n        if (!sectionTracker.isOpen())\n            return false;\n        m_activeSections.push_back(&sectionTracker);\n\n        SectionInfo sectionInfo( sectionLineInfo, static_cast<std::string>(sectionName) );\n        Detail::g_lastKnownLineInfo = sectionLineInfo;\n\n        {\n            auto _ = scopedDeactivate( *m_outputRedirect );\n            m_reporter->sectionStarting( sectionInfo );\n        }\n\n        updateTotalsFromAtomics();\n        assertions = m_totals.assertions;\n\n        return true;\n    }\n    IGeneratorTracker*\n    RunContext::acquireGeneratorTracker( StringRef generatorName,\n                                         SourceLineInfo const& lineInfo ) {\n        auto* tracker = Generators::GeneratorTracker::acquire(\n            m_trackerContext,\n            TestCaseTracking::NameAndLocationRef(\n                 generatorName, lineInfo ) );\n        Detail::g_lastKnownLineInfo = lineInfo;\n        return tracker;\n    }\n\n    IGeneratorTracker* RunContext::createGeneratorTracker(\n        StringRef generatorName,\n        SourceLineInfo lineInfo,\n        Generators::GeneratorBasePtr&& generator ) {\n\n        auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast<std::string>( generatorName ), lineInfo );\n        auto& currentTracker = m_trackerContext.currentTracker();\n        assert(\n            currentTracker.nameAndLocation() != nameAndLoc &&\n            \"Trying to create tracker for a generator that already has one\" );\n\n        auto newTracker = Catch::Detail::make_unique<Generators::GeneratorTracker>(\n            CATCH_MOVE(nameAndLoc), m_trackerContext, &currentTracker );\n        auto ret = newTracker.get();\n        currentTracker.addChild( CATCH_MOVE( newTracker ) );\n\n        ret->setGenerator( CATCH_MOVE( generator ) );\n        ret->open();\n        return ret;\n    }\n\n    bool RunContext::testForMissingAssertions(Counts& assertions) {\n        if (assertions.total() != 0)\n            return false;\n        if (!m_config->warnAboutMissingAssertions())\n            return false;\n        if (m_trackerContext.currentTracker().hasChildren())\n            return false;\n        m_atomicAssertionCount.failed++;\n        assertions.failed++;\n        return true;\n    }\n\n    void RunContext::sectionEnded(SectionEndInfo&& endInfo) {\n        updateTotalsFromAtomics();\n        Counts assertions = m_totals.assertions - endInfo.prevAssertions;\n        bool missingAssertions = testForMissingAssertions(assertions);\n\n        if (!m_activeSections.empty()) {\n            m_activeSections.back()->close();\n            m_activeSections.pop_back();\n        }\n\n        {\n            auto _ = scopedDeactivate( *m_outputRedirect );\n            m_reporter->sectionEnded(\n                SectionStats( CATCH_MOVE( endInfo.sectionInfo ),\n                              assertions,\n                              endInfo.durationInSeconds,\n                              missingAssertions ) );\n        }\n    }\n\n    void RunContext::sectionEndedEarly(SectionEndInfo&& endInfo) {\n        if ( m_unfinishedSections.empty() ) {\n            m_activeSections.back()->fail();\n        } else {\n            m_activeSections.back()->close();\n        }\n        m_activeSections.pop_back();\n\n        m_unfinishedSections.push_back(CATCH_MOVE(endInfo));\n    }\n\n    void RunContext::benchmarkPreparing( StringRef name ) {\n        auto _ = scopedDeactivate( *m_outputRedirect );\n        m_reporter->benchmarkPreparing( name );\n    }\n    void RunContext::benchmarkStarting( BenchmarkInfo const& info ) {\n        auto _ = scopedDeactivate( *m_outputRedirect );\n        m_reporter->benchmarkStarting( info );\n    }\n    void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) {\n        auto _ = scopedDeactivate( *m_outputRedirect );\n        m_reporter->benchmarkEnded( stats );\n    }\n    void RunContext::benchmarkFailed( StringRef error ) {\n        auto _ = scopedDeactivate( *m_outputRedirect );\n        m_reporter->benchmarkFailed( error );\n    }\n\n    void RunContext::pushScopedMessage(MessageInfo const & message) {\n        m_messages.push_back(message);\n    }\n\n    void RunContext::popScopedMessage( MessageInfo const& message ) {\n        // Note: On average, it would probably be better to look for the message\n        //       backwards. However, we do not expect to have to deal with more\n        //       messages than low single digits, so the optimization is tiny,\n        //       and we would have to hand-write the loop to avoid terrible\n        //       codegen of reverse iterators in debug mode.\n        m_messages.erase(\n            std::find_if( m_messages.begin(),\n                          m_messages.end(),\n                          [id = message.sequence]( MessageInfo const& msg ) {\n                              return msg.sequence == id;\n                          } ) );\n    }\n\n    void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {\n        m_messageScopes.emplace_back( CATCH_MOVE(builder) );\n    }\n\n    std::string RunContext::getCurrentTestName() const {\n        return m_activeTestCase\n            ? m_activeTestCase->getTestCaseInfo().name\n            : std::string();\n    }\n\n    const AssertionResult * RunContext::getLastResult() const {\n        // m_lastResult is updated inside the assertion slow-path, under\n        // a mutex, so the read needs to happen under mutex as well.\n\n        // TBD: The last result only makes sense if it is a thread-local\n        //      thing, because the answer is different per thread, like\n        //      last line info, whether last assertion passed, and so on.\n        //\n        //      However, the last result was also never updated in the\n        //      assertion fast path, so it was always somewhat broken,\n        //      and since IResultCapture::getLastResult is deprecated,\n        //      we will leave it as is, until it is finally removed.\n        Detail::LockGuard _( m_assertionMutex );\n        return &(*m_lastResult);\n    }\n\n    void RunContext::exceptionEarlyReported() {\n        m_shouldReportUnexpected = false;\n    }\n\n    void RunContext::handleFatalErrorCondition( StringRef message ) {\n        // We lock only when touching the reporters directly, to avoid\n        // deadlocks when we call into other functions that also want\n        // to lock the mutex before touching reporters.\n        //\n        // This does mean that we allow other threads to run while handling\n        // a fatal error, but this is all a best effort attempt anyway.\n        {\n            Detail::LockGuard lock( m_assertionMutex );\n            // TODO: scoped deactivate here? Just give up and do best effort?\n            //       the deactivation can break things further, OTOH so can the\n            //       capture\n            auto _ = scopedDeactivate( *m_outputRedirect );\n\n            // First notify reporter that bad things happened\n            m_reporter->fatalErrorEncountered( message );\n        }\n\n        // Don't rebuild the result -- the stringification itself can cause more fatal errors\n        // Instead, fake a result data.\n        AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } );\n        tempResult.message = static_cast<std::string>(message);\n        AssertionResult result( makeDummyAssertionInfo(),\n                                CATCH_MOVE( tempResult ) );\n\n        assertionEnded(CATCH_MOVE(result) );\n\n\n        // At this point we touch sections/test cases from this thread\n        // to try and end them. Technically that is not supported when\n        // using multiple threads, but the worst thing that can happen\n        // is that the process aborts harder :-D\n        Detail::LockGuard lock( m_assertionMutex );\n\n        // Best effort cleanup for sections that have not been destructed yet\n        // Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly\n        while (!m_activeSections.empty()) {\n            auto const& nl = m_activeSections.back()->nameAndLocation();\n            SectionEndInfo endInfo{ SectionInfo(nl.location, nl.name), {}, 0.0 };\n            sectionEndedEarly(CATCH_MOVE(endInfo));\n        }\n        handleUnfinishedSections();\n\n        // Recreate section for test case (as we will lose the one that was in scope)\n        auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();\n        SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);\n\n        Counts assertions;\n        assertions.failed = 1;\n        SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, 0, false);\n        m_reporter->sectionEnded( testCaseSectionStats );\n\n        auto const& testInfo = m_activeTestCase->getTestCaseInfo();\n\n        Totals deltaTotals;\n        deltaTotals.testCases.failed = 1;\n        deltaTotals.assertions.failed = 1;\n        m_reporter->testCaseEnded(TestCaseStats(testInfo,\n                                  deltaTotals,\n                                  std::string(),\n                                  std::string(),\n                                  false));\n        m_totals.testCases.failed++;\n        updateTotalsFromAtomics();\n        m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));\n    }\n\n    bool RunContext::lastAssertionPassed() {\n        return Detail::g_lastAssertionPassed;\n    }\n\n    void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) {\n        // We want to save the line info for better experience with unexpected assertions\n        Detail::g_lastKnownLineInfo = lineInfo;\n        ++m_atomicAssertionCount.passed;\n        Detail::g_lastAssertionPassed = true;\n        Detail::g_clearMessageScopes = true;\n    }\n\n    void RunContext::updateTotalsFromAtomics() {\n        m_totals.assertions = Counts{\n            m_atomicAssertionCount.passed,\n            m_atomicAssertionCount.failed,\n            m_atomicAssertionCount.failedButOk,\n            m_atomicAssertionCount.skipped,\n        };\n    }\n\n    bool RunContext::aborting() const {\n        return m_atomicAssertionCount.failed >= m_abortAfterXFailedAssertions;\n    }\n\n    void RunContext::runCurrentTest() {\n        auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();\n        SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);\n        m_reporter->sectionStarting(testCaseSection);\n        updateTotalsFromAtomics();\n        Counts prevAssertions = m_totals.assertions;\n        double duration = 0;\n        m_shouldReportUnexpected = true;\n        Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo;\n\n        Timer timer;\n        CATCH_TRY {\n            {\n                auto _ = scopedActivate( *m_outputRedirect );\n                timer.start();\n                invokeActiveTestCase();\n            }\n            duration = timer.getElapsedSeconds();\n        } CATCH_CATCH_ANON (TestFailureException&) {\n            // This just means the test was aborted due to failure\n        } CATCH_CATCH_ANON (TestSkipException&) {\n            // This just means the test was explicitly skipped\n        } CATCH_CATCH_ALL {\n            // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions\n            // are reported without translation at the point of origin.\n            if ( m_shouldReportUnexpected ) {\n                AssertionReaction dummyReaction;\n                handleUnexpectedInflightException( makeDummyAssertionInfo(),\n                                                   translateActiveException(),\n                                                   dummyReaction );\n            }\n        }\n        updateTotalsFromAtomics();\n        Counts assertions = m_totals.assertions - prevAssertions;\n        bool missingAssertions = testForMissingAssertions(assertions);\n\n        m_testCaseTracker->close();\n        handleUnfinishedSections();\n        m_messageScopes.clear();\n        // TBD: At this point, m_messages should be empty. Do we want to\n        //      assert that this is true, or keep the defensive clear call?\n        m_messages.clear();\n\n        SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);\n        m_reporter->sectionEnded(testCaseSectionStats);\n    }\n\n    void RunContext::invokeActiveTestCase() {\n        // We need to engage a handler for signals/structured exceptions\n        // before running the tests themselves, or the binary can crash\n        // without failed test being reported.\n        FatalConditionHandlerGuard _(&m_fatalConditionhandler);\n        // We keep having issue where some compilers warn about an unused\n        // variable, even though the type has non-trivial constructor and\n        // destructor. This is annoying and ugly, but it makes them stfu.\n        (void)_;\n\n        m_activeTestCase->invoke();\n    }\n\n    void RunContext::handleUnfinishedSections() {\n        // If sections ended prematurely due to an exception we stored their\n        // infos here so we can tear them down outside the unwind process.\n        for ( auto it = m_unfinishedSections.rbegin(),\n                   itEnd = m_unfinishedSections.rend();\n              it != itEnd;\n              ++it ) {\n            sectionEnded( CATCH_MOVE( *it ) );\n        }\n        m_unfinishedSections.clear();\n    }\n\n    void RunContext::handleExpr(\n        AssertionInfo const& info,\n        ITransientExpression const& expr,\n        AssertionReaction& reaction\n    ) {\n        bool negated = isFalseTest( info.resultDisposition );\n        bool result = expr.getResult() != negated;\n\n        if( result ) {\n            if (!m_includeSuccessfulResults) {\n                assertionPassedFastPath(info.lineInfo);\n            }\n            else {\n                reportExpr(info, ResultWas::Ok, &expr, negated);\n            }\n        }\n        else {\n            reportExpr(info, ResultWas::ExpressionFailed, &expr, negated );\n            populateReaction(\n                reaction, info.resultDisposition & ResultDisposition::Normal );\n        }\n    }\n    void RunContext::reportExpr(\n            AssertionInfo const &info,\n            ResultWas::OfType resultType,\n            ITransientExpression const *expr,\n            bool negated ) {\n\n        Detail::g_lastKnownLineInfo = info.lineInfo;\n        AssertionResultData data( resultType, LazyExpression( negated ) );\n\n        AssertionResult assertionResult{ info, CATCH_MOVE( data ) };\n        assertionResult.m_resultData.lazyExpression.m_transientExpression = expr;\n\n        assertionEnded( CATCH_MOVE(assertionResult) );\n    }\n\n    void RunContext::handleMessage(\n            AssertionInfo const& info,\n            ResultWas::OfType resultType,\n            std::string&& message,\n            AssertionReaction& reaction\n    ) {\n        Detail::g_lastKnownLineInfo = info.lineInfo;\n\n        AssertionResultData data( resultType, LazyExpression( false ) );\n        data.message = CATCH_MOVE( message );\n        AssertionResult assertionResult{ info,\n                                         CATCH_MOVE( data ) };\n\n        const auto isOk = assertionResult.isOk();\n        assertionEnded( CATCH_MOVE(assertionResult) );\n        if ( !isOk ) {\n            populateReaction(\n                reaction, info.resultDisposition & ResultDisposition::Normal );\n        } else if ( resultType == ResultWas::ExplicitSkip ) {\n            // TODO: Need to handle this explicitly, as ExplicitSkip is\n            // considered \"OK\"\n            reaction.shouldSkip = true;\n        }\n    }\n\n    void RunContext::handleUnexpectedExceptionNotThrown(\n            AssertionInfo const& info,\n            AssertionReaction& reaction\n    ) {\n        handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction);\n    }\n\n    void RunContext::handleUnexpectedInflightException(\n            AssertionInfo const& info,\n            std::string&& message,\n            AssertionReaction& reaction\n    ) {\n        Detail::g_lastKnownLineInfo = info.lineInfo;\n\n        AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );\n        data.message = CATCH_MOVE(message);\n        AssertionResult assertionResult{ info, CATCH_MOVE(data) };\n        assertionEnded( CATCH_MOVE(assertionResult) );\n        populateReaction( reaction,\n                          info.resultDisposition & ResultDisposition::Normal );\n    }\n\n    void RunContext::populateReaction( AssertionReaction& reaction,\n                                       bool has_normal_disposition ) {\n        reaction.shouldDebugBreak = m_shouldDebugBreak;\n        reaction.shouldThrow = aborting() || has_normal_disposition;\n    }\n\n    AssertionInfo RunContext::makeDummyAssertionInfo() {\n        const bool testCaseJustStarted =\n            Detail::g_lastKnownLineInfo ==\n            m_activeTestCase->getTestCaseInfo().lineInfo;\n\n        return AssertionInfo{\n            testCaseJustStarted ? \"TEST_CASE\"_sr : StringRef(),\n            Detail::g_lastKnownLineInfo,\n            testCaseJustStarted ? StringRef() : \"{Unknown expression after the reported line}\"_sr,\n            ResultDisposition::Normal\n        };\n    }\n\n    void RunContext::handleIncomplete(\n            AssertionInfo const& info\n    ) {\n        using namespace std::string_literals;\n        Detail::g_lastKnownLineInfo = info.lineInfo;\n\n        AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );\n        data.message = \"Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE\"s;\n        AssertionResult assertionResult{ info, CATCH_MOVE( data ) };\n        assertionEnded( CATCH_MOVE(assertionResult) );\n    }\n\n    void RunContext::handleNonExpr(\n            AssertionInfo const &info,\n            ResultWas::OfType resultType,\n            AssertionReaction &reaction\n    ) {\n        AssertionResultData data( resultType, LazyExpression( false ) );\n        AssertionResult assertionResult{ info, CATCH_MOVE( data ) };\n\n        const auto isOk = assertionResult.isOk();\n        if ( isOk && !m_includeSuccessfulResults ) {\n            assertionPassedFastPath( info.lineInfo );\n            return;\n        }\n\n        assertionEnded( CATCH_MOVE(assertionResult) );\n        if ( !isOk ) {\n            populateReaction(\n                reaction, info.resultDisposition & ResultDisposition::Normal );\n        }\n    }\n\n    IResultCapture& getResultCapture() {\n        if (auto* capture = getCurrentContext().getResultCapture())\n            return *capture;\n        else\n            CATCH_INTERNAL_ERROR(\"No result capture instance\");\n    }\n\n    void seedRng(IConfig const& config) {\n        sharedRng().seed(config.rngSeed());\n    }\n\n    unsigned int rngSeed() {\n        return getCurrentContext().getConfig()->rngSeed();\n    }\n\n}\n\n\n\nnamespace Catch {\n\n    Section::Section( SectionInfo&& info ):\n        m_info( CATCH_MOVE( info ) ),\n        m_sectionIncluded(\n            getResultCapture().sectionStarted( m_info.name, m_info.lineInfo, m_assertions ) ) {\n        // Non-\"included\" sections will not use the timing information\n        // anyway, so don't bother with the potential syscall.\n        if (m_sectionIncluded) {\n            m_timer.start();\n        }\n    }\n\n    Section::Section( SourceLineInfo const& _lineInfo,\n                      StringRef _name,\n                      const char* const ):\n        m_info( { \"invalid\", static_cast<std::size_t>( -1 ) }, std::string{} ),\n        m_sectionIncluded(\n            getResultCapture().sectionStarted( _name, _lineInfo, m_assertions ) ) {\n        // We delay initialization the SectionInfo member until we know\n        // this section needs it, so we avoid allocating std::string for name.\n        // We also delay timer start to avoid the potential syscall unless we\n        // will actually use the result.\n        if ( m_sectionIncluded ) {\n            m_info.name = static_cast<std::string>( _name );\n            m_info.lineInfo = _lineInfo;\n            m_timer.start();\n        }\n    }\n\n    Section::~Section() {\n        if( m_sectionIncluded ) {\n            SectionEndInfo endInfo{ CATCH_MOVE(m_info), m_assertions, m_timer.getElapsedSeconds() };\n            if ( uncaught_exceptions() ) {\n                getResultCapture().sectionEndedEarly( CATCH_MOVE(endInfo) );\n            } else {\n                getResultCapture().sectionEnded( CATCH_MOVE( endInfo ) );\n            }\n        }\n    }\n\n    // This indicates whether the section should be executed or not\n    Section::operator bool() const {\n        return m_sectionIncluded;\n    }\n\n\n} // end namespace Catch\n\n\n\n#include <vector>\n\nnamespace Catch {\n\n    namespace {\n        static auto getSingletons() -> std::vector<ISingleton*>*& {\n            static std::vector<ISingleton*>* g_singletons = nullptr;\n            if( !g_singletons )\n                g_singletons = new std::vector<ISingleton*>();\n            return g_singletons;\n        }\n    }\n\n    ISingleton::~ISingleton() = default;\n\n    void addSingleton(ISingleton* singleton ) {\n        getSingletons()->push_back( singleton );\n    }\n    void cleanupSingletons() {\n        auto& singletons = getSingletons();\n        for( auto singleton : *singletons )\n            delete singleton;\n        delete singletons;\n        singletons = nullptr;\n    }\n\n} // namespace Catch\n\n\n\n#include <cstring>\n#include <ostream>\n\nnamespace Catch {\n\n    bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept {\n        return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0);\n    }\n    bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept {\n        // We can assume that the same file will usually have the same pointer.\n        // Thus, if the pointers are the same, there is no point in calling the strcmp\n        return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0));\n    }\n\n    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {\n#ifndef __GNUG__\n        os << info.file << '(' << info.line << ')';\n#else\n        os << info.file << ':' << info.line;\n#endif\n        return os;\n    }\n\n} // end namespace Catch\n\n\n\n\nnamespace Catch {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept {\n        CATCH_TRY {\n            m_exceptions.push_back(exception);\n        } CATCH_CATCH_ALL {\n            // If we run out of memory during start-up there's really not a lot more we can do about it\n            std::terminate();\n        }\n    }\n\n    std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept {\n        return m_exceptions;\n    }\n#endif\n\n} // end namespace Catch\n\n\n\n\n\n#include <iostream>\n\nnamespace Catch {\n\n// If you #define this you must implement these functions\n#if !defined( CATCH_CONFIG_NOSTDOUT )\n    std::ostream& cout() { return std::cout; }\n    std::ostream& cerr() { return std::cerr; }\n    std::ostream& clog() { return std::clog; }\n#endif\n\n} // namespace Catch\n\n\n\n#include <ostream>\n#include <cstring>\n#include <cctype>\n#include <vector>\n\nnamespace Catch {\n\n    bool startsWith( std::string const& s, std::string const& prefix ) {\n        return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());\n    }\n    bool startsWith( StringRef s, char prefix ) {\n        return !s.empty() && s[0] == prefix;\n    }\n    bool endsWith( std::string const& s, std::string const& suffix ) {\n        return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());\n    }\n    bool endsWith( std::string const& s, char suffix ) {\n        return !s.empty() && s[s.size()-1] == suffix;\n    }\n    bool contains( std::string const& s, std::string const& infix ) {\n        return s.find( infix ) != std::string::npos;\n    }\n    void toLowerInPlace( std::string& s ) {\n        for ( char& c : s ) {\n            c = toLower( c );\n        }\n    }\n    std::string toLower( std::string const& s ) {\n        std::string lc = s;\n        toLowerInPlace( lc );\n        return lc;\n    }\n    char toLower(char c) {\n        return static_cast<char>(std::tolower(static_cast<unsigned char>(c)));\n    }\n\n    std::string trim( std::string const& str ) {\n        static char const* whitespaceChars = \"\\n\\r\\t \";\n        std::string::size_type start = str.find_first_not_of( whitespaceChars );\n        std::string::size_type end = str.find_last_not_of( whitespaceChars );\n\n        return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string();\n    }\n\n    StringRef trim(StringRef ref) {\n        const auto is_ws = [](char c) {\n            return c == ' ' || c == '\\t' || c == '\\n' || c == '\\r';\n        };\n        size_t real_begin = 0;\n        while (real_begin < ref.size() && is_ws(ref[real_begin])) { ++real_begin; }\n        size_t real_end = ref.size();\n        while (real_end > real_begin && is_ws(ref[real_end - 1])) { --real_end; }\n\n        return ref.substr(real_begin, real_end - real_begin);\n    }\n\n    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {\n        std::size_t i = str.find( replaceThis );\n        if (i == std::string::npos) {\n            return false;\n        }\n        std::size_t copyBegin = 0;\n        std::string origStr = CATCH_MOVE(str);\n        str.clear();\n        // There is at least one replacement, so reserve with the best guess\n        // we can make without actually counting the number of occurrences.\n        str.reserve(origStr.size() - replaceThis.size() + withThis.size());\n        do {\n            str.append(origStr, copyBegin, i-copyBegin );\n            str += withThis;\n            copyBegin = i + replaceThis.size();\n            if( copyBegin < origStr.size() )\n                i = origStr.find( replaceThis, copyBegin );\n            else\n                i = std::string::npos;\n        } while( i != std::string::npos );\n        if ( copyBegin < origStr.size() ) {\n            str.append(origStr, copyBegin, origStr.size() );\n        }\n        return true;\n    }\n\n    std::vector<StringRef> splitStringRef( StringRef str, char delimiter ) {\n        std::vector<StringRef> subStrings;\n        std::size_t start = 0;\n        for(std::size_t pos = 0; pos < str.size(); ++pos ) {\n            if( str[pos] == delimiter ) {\n                if( pos - start > 1 )\n                    subStrings.push_back( str.substr( start, pos-start ) );\n                start = pos+1;\n            }\n        }\n        if( start < str.size() )\n            subStrings.push_back( str.substr( start, str.size()-start ) );\n        return subStrings;\n    }\n\n    std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) {\n        os << pluraliser.m_count << ' ' << pluraliser.m_label;\n        if( pluraliser.m_count != 1 )\n            os << 's';\n        return os;\n    }\n\n}\n\n\n\n#include <algorithm>\n#include <ostream>\n#include <cstring>\n\nnamespace Catch {\n    StringRef::StringRef( char const* rawChars ) noexcept\n    : StringRef( rawChars, std::strlen(rawChars) )\n    {}\n\n\n    bool StringRef::operator<(StringRef rhs) const noexcept {\n        if (m_size < rhs.m_size) {\n            return strncmp(m_start, rhs.m_start, m_size) <= 0;\n        }\n        return strncmp(m_start, rhs.m_start, rhs.m_size) < 0;\n    }\n\n    int StringRef::compare( StringRef rhs ) const {\n        auto cmpResult =\n            strncmp( m_start, rhs.m_start, std::min( m_size, rhs.m_size ) );\n\n        // This means that strncmp found a difference before the strings\n        // ended, and we can return it directly\n        if ( cmpResult != 0 ) {\n            return cmpResult;\n        }\n\n        // If strings are equal up to length, then their comparison results on\n        // their size\n        if ( m_size < rhs.m_size ) {\n            return -1;\n        } else if ( m_size > rhs.m_size ) {\n            return 1;\n        } else {\n            return 0;\n        }\n    }\n\n    auto operator << ( std::ostream& os, StringRef str ) -> std::ostream& {\n        return os.write(str.data(), static_cast<std::streamsize>(str.size()));\n    }\n\n    std::string operator+(StringRef lhs, StringRef rhs) {\n        std::string ret;\n        ret.reserve(lhs.size() + rhs.size());\n        ret += lhs;\n        ret += rhs;\n        return ret;\n    }\n\n    auto operator+=( std::string& lhs, StringRef rhs ) -> std::string& {\n        lhs.append(rhs.data(), rhs.size());\n        return lhs;\n    }\n\n} // namespace Catch\n\n\n\nnamespace Catch {\n\n    TagAliasRegistry::~TagAliasRegistry() = default;\n\n    TagAlias const* TagAliasRegistry::find( std::string const& alias ) const {\n        auto it = m_registry.find( alias );\n        if( it != m_registry.end() )\n            return &(it->second);\n        else\n            return nullptr;\n    }\n\n    std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {\n        std::string expandedTestSpec = unexpandedTestSpec;\n        for( auto const& registryKvp : m_registry ) {\n            std::size_t pos = expandedTestSpec.find( registryKvp.first );\n            if( pos != std::string::npos ) {\n                expandedTestSpec =  expandedTestSpec.substr( 0, pos ) +\n                                    registryKvp.second.tag +\n                                    expandedTestSpec.substr( pos + registryKvp.first.size() );\n            }\n        }\n        return expandedTestSpec;\n    }\n\n    void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) {\n        CATCH_ENFORCE( startsWith(alias, \"[@\") && endsWith(alias, ']'),\n                      \"error: tag alias, '\" << alias << \"' is not of the form [@alias name].\\n\" << lineInfo );\n\n        CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second,\n                      \"error: tag alias, '\" << alias << \"' already registered.\\n\"\n                      << \"\\tFirst seen at: \" << find(alias)->lineInfo << \"\\n\"\n                      << \"\\tRedefined at: \" << lineInfo );\n    }\n\n    ITagAliasRegistry::~ITagAliasRegistry() = default;\n\n    ITagAliasRegistry const& ITagAliasRegistry::get() {\n        return getRegistryHub().getTagAliasRegistry();\n    }\n\n} // end namespace Catch\n\n\n\n\nnamespace Catch {\n    TestCaseInfoHasher::TestCaseInfoHasher( hash_t seed ): m_seed( seed ) {}\n\n    uint32_t TestCaseInfoHasher::operator()( TestCaseInfo const& t ) const {\n        // FNV-1a hash algorithm that is designed for uniqueness:\n        const hash_t prime = 1099511628211u;\n        hash_t hash = 14695981039346656037u;\n        for ( const char c : t.name ) {\n            hash ^= c;\n            hash *= prime;\n        }\n        for ( const char c : t.className ) {\n            hash ^= c;\n            hash *= prime;\n        }\n        for ( const Tag& tag : t.tags ) {\n            for ( const char c : tag.original ) {\n                hash ^= c;\n                hash *= prime;\n            }\n        }\n        hash ^= m_seed;\n        hash *= prime;\n        const uint32_t low{ static_cast<uint32_t>( hash ) };\n        const uint32_t high{ static_cast<uint32_t>( hash >> 32 ) };\n        return low * high;\n    }\n} // namespace Catch\n\n\n\n\n#include <algorithm>\n#include <set>\n\nnamespace Catch {\n\n    namespace {\n        static void enforceNoDuplicateTestCases(\n            std::vector<TestCaseHandle> const& tests ) {\n            auto testInfoCmp = []( TestCaseInfo const* lhs,\n                                   TestCaseInfo const* rhs ) {\n                return *lhs < *rhs;\n            };\n            std::set<TestCaseInfo const*, decltype( testInfoCmp )&> seenTests(\n                testInfoCmp );\n            for ( auto const& test : tests ) {\n                const auto infoPtr = &test.getTestCaseInfo();\n                const auto prev = seenTests.insert( infoPtr );\n                CATCH_ENFORCE( prev.second,\n                               \"error: test case \\\"\"\n                                   << infoPtr->name << \"\\\", with tags \\\"\"\n                                   << infoPtr->tagsAsString()\n                                   << \"\\\" already defined.\\n\"\n                                   << \"\\tFirst seen at \"\n                                   << ( *prev.first )->lineInfo << \"\\n\"\n                                   << \"\\tRedefined at \" << infoPtr->lineInfo );\n            }\n        }\n\n        static bool matchTest( TestCaseHandle const& testCase,\n                               TestSpec const& testSpec,\n                               IConfig const& config ) {\n            return testSpec.matches( testCase.getTestCaseInfo() ) &&\n                   isThrowSafe( testCase, config );\n        }\n\n    } // end unnamed namespace\n\n    std::vector<TestCaseHandle> sortTests( IConfig const& config, std::vector<TestCaseHandle> const& unsortedTestCases ) {\n        switch (config.runOrder()) {\n        case TestRunOrder::Declared:\n            return unsortedTestCases;\n\n        case TestRunOrder::LexicographicallySorted: {\n            std::vector<TestCaseHandle> sorted = unsortedTestCases;\n            std::sort(\n                sorted.begin(),\n                sorted.end(),\n                []( TestCaseHandle const& lhs, TestCaseHandle const& rhs ) {\n                    return lhs.getTestCaseInfo() < rhs.getTestCaseInfo();\n                }\n            );\n            return sorted;\n        }\n        case TestRunOrder::Randomized: {\n            using TestWithHash = std::pair<TestCaseInfoHasher::hash_t, TestCaseHandle>;\n\n            TestCaseInfoHasher h{ config.rngSeed() };\n            std::vector<TestWithHash> indexed_tests;\n            indexed_tests.reserve(unsortedTestCases.size());\n\n            for (auto const& handle : unsortedTestCases) {\n                indexed_tests.emplace_back(h(handle.getTestCaseInfo()), handle);\n            }\n\n            std::sort( indexed_tests.begin(),\n                       indexed_tests.end(),\n                       []( TestWithHash const& lhs, TestWithHash const& rhs ) {\n                           if ( lhs.first == rhs.first ) {\n                               return lhs.second.getTestCaseInfo() <\n                                      rhs.second.getTestCaseInfo();\n                           }\n                           return lhs.first < rhs.first;\n                       } );\n\n            std::vector<TestCaseHandle> randomized;\n            randomized.reserve(indexed_tests.size());\n\n            for (auto const& indexed : indexed_tests) {\n                randomized.push_back(indexed.second);\n            }\n\n            return randomized;\n        }\n        }\n\n        CATCH_INTERNAL_ERROR(\"Unknown test order value!\");\n    }\n\n    bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config ) {\n        return !testCase.getTestCaseInfo().throws() || config.allowThrows();\n    }\n\n    std::vector<TestCaseHandle> filterTests( std::vector<TestCaseHandle> const& testCases, TestSpec const& testSpec, IConfig const& config ) {\n        std::vector<TestCaseHandle> filtered;\n        filtered.reserve( testCases.size() );\n        for (auto const& testCase : testCases) {\n            if ((!testSpec.hasFilters() && !testCase.getTestCaseInfo().isHidden()) ||\n                (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) {\n                filtered.push_back(testCase);\n            }\n        }\n        return createShard(filtered, config.shardCount(), config.shardIndex());\n    }\n    std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config ) {\n        return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config );\n    }\n\n    TestRegistry::~TestRegistry() = default;\n\n    void TestRegistry::registerTest(Detail::unique_ptr<TestCaseInfo> testInfo, Detail::unique_ptr<ITestInvoker> testInvoker) {\n        m_handles.emplace_back(testInfo.get(), testInvoker.get());\n        m_viewed_test_infos.push_back(testInfo.get());\n        m_owned_test_infos.push_back(CATCH_MOVE(testInfo));\n        m_invokers.push_back(CATCH_MOVE(testInvoker));\n    }\n\n    std::vector<TestCaseInfo*> const& TestRegistry::getAllInfos() const {\n        return m_viewed_test_infos;\n    }\n\n    std::vector<TestCaseHandle> const& TestRegistry::getAllTests() const {\n        return m_handles;\n    }\n    std::vector<TestCaseHandle> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const {\n        if( m_sortedFunctions.empty() )\n            enforceNoDuplicateTestCases( m_handles );\n\n        if(  m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) {\n            m_sortedFunctions = sortTests( config, m_handles );\n            m_currentSortOrder = config.runOrder();\n        }\n        return m_sortedFunctions;\n    }\n\n} // end namespace Catch\n\n\n\n\n#include <algorithm>\n#include <cassert>\n\n#if defined(__clang__)\n#    pragma clang diagnostic push\n#    pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#endif\n\nnamespace Catch {\nnamespace TestCaseTracking {\n\n    NameAndLocation::NameAndLocation( std::string&& _name, SourceLineInfo const& _location )\n    :   name( CATCH_MOVE(_name) ),\n        location( _location )\n    {}\n\n\n    ITracker::~ITracker() = default;\n\n    void ITracker::markAsNeedingAnotherRun() {\n        m_runState = NeedsAnotherRun;\n    }\n\n    void ITracker::addChild( ITrackerPtr&& child ) {\n        m_children.push_back( CATCH_MOVE(child) );\n    }\n\n    ITracker* ITracker::findChild( NameAndLocationRef const& nameAndLocation ) {\n        auto it = std::find_if(\n            m_children.begin(),\n            m_children.end(),\n            [&nameAndLocation]( ITrackerPtr const& tracker ) {\n                auto const& tnameAndLoc = tracker->nameAndLocation();\n                if ( tnameAndLoc.location.line !=\n                     nameAndLocation.location.line ) {\n                    return false;\n                }\n                return tnameAndLoc == nameAndLocation;\n            } );\n        return ( it != m_children.end() ) ? it->get() : nullptr;\n    }\n\n    bool ITracker::isSectionTracker() const { return false; }\n    bool ITracker::isGeneratorTracker() const { return false; }\n\n    bool ITracker::isOpen() const {\n        return m_runState != NotStarted && !isComplete();\n    }\n\n    bool ITracker::hasStarted() const { return m_runState != NotStarted; }\n\n    void ITracker::openChild() {\n        if (m_runState != ExecutingChildren) {\n            m_runState = ExecutingChildren;\n            if (m_parent) {\n                m_parent->openChild();\n            }\n        }\n    }\n\n    ITracker& TrackerContext::startRun() {\n        using namespace std::string_literals;\n        m_rootTracker = Catch::Detail::make_unique<SectionTracker>(\n            NameAndLocation( \"{root}\"s, CATCH_INTERNAL_LINEINFO ),\n            *this,\n            nullptr );\n        m_currentTracker = nullptr;\n        m_runState = Executing;\n        return *m_rootTracker;\n    }\n\n    void TrackerContext::completeCycle() {\n        m_runState = CompletedCycle;\n    }\n\n    bool TrackerContext::completedCycle() const {\n        return m_runState == CompletedCycle;\n    }\n    void TrackerContext::setCurrentTracker( ITracker* tracker ) {\n        m_currentTracker = tracker;\n    }\n\n\n    TrackerBase::TrackerBase( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ):\n        ITracker(CATCH_MOVE(nameAndLocation), parent),\n        m_ctx( ctx )\n    {}\n\n    bool TrackerBase::isComplete() const {\n        return m_runState == CompletedSuccessfully || m_runState == Failed;\n    }\n\n    void TrackerBase::open() {\n        m_runState = Executing;\n        moveToThis();\n        if( m_parent )\n            m_parent->openChild();\n    }\n\n    void TrackerBase::close() {\n\n        // Close any still open children (e.g. generators)\n        while( &m_ctx.currentTracker() != this )\n            m_ctx.currentTracker().close();\n\n        switch( m_runState ) {\n            case NeedsAnotherRun:\n                break;\n\n            case Executing:\n                m_runState = CompletedSuccessfully;\n                break;\n            case ExecutingChildren:\n                if( std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const& t){ return t->isComplete(); }) )\n                    m_runState = CompletedSuccessfully;\n                break;\n\n            case NotStarted:\n            case CompletedSuccessfully:\n            case Failed:\n                CATCH_INTERNAL_ERROR( \"Illogical state: \" << m_runState );\n\n            default:\n                CATCH_INTERNAL_ERROR( \"Unknown state: \" << m_runState );\n        }\n        moveToParent();\n        m_ctx.completeCycle();\n    }\n    void TrackerBase::fail() {\n        m_runState = Failed;\n        if( m_parent )\n            m_parent->markAsNeedingAnotherRun();\n        moveToParent();\n        m_ctx.completeCycle();\n    }\n\n    void TrackerBase::moveToParent() {\n        assert( m_parent );\n        m_ctx.setCurrentTracker( m_parent );\n    }\n    void TrackerBase::moveToThis() {\n        m_ctx.setCurrentTracker( this );\n    }\n\n    SectionTracker::SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent )\n    :   TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ),\n        m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name)))\n    {\n        if( parent ) {\n            while ( !parent->isSectionTracker() ) {\n                parent = parent->parent();\n            }\n\n            SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );\n            addNextFilters( parentSection.m_filters );\n        }\n    }\n\n    bool SectionTracker::isComplete() const {\n        bool complete = true;\n\n        if (m_filters.empty()\n            || m_filters[0].empty()\n            || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) {\n            complete = TrackerBase::isComplete();\n        }\n        return complete;\n    }\n\n    bool SectionTracker::isSectionTracker() const { return true; }\n\n    SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocationRef const& nameAndLocation ) {\n        SectionTracker* tracker;\n\n        ITracker& currentTracker = ctx.currentTracker();\n        if ( ITracker* childTracker =\n                 currentTracker.findChild( nameAndLocation ) ) {\n            assert( childTracker );\n            assert( childTracker->isSectionTracker() );\n            tracker = static_cast<SectionTracker*>( childTracker );\n        } else {\n            auto newTracker = Catch::Detail::make_unique<SectionTracker>(\n                NameAndLocation{ static_cast<std::string>(nameAndLocation.name),\n                                 nameAndLocation.location },\n                ctx,\n                &currentTracker );\n            tracker = newTracker.get();\n            currentTracker.addChild( CATCH_MOVE( newTracker ) );\n        }\n\n        if ( !ctx.completedCycle() ) {\n            tracker->tryOpen();\n        }\n\n        return *tracker;\n    }\n\n    void SectionTracker::tryOpen() {\n        if( !isComplete() )\n            open();\n    }\n\n    void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) {\n        if( !filters.empty() ) {\n            m_filters.reserve( m_filters.size() + filters.size() + 2 );\n            m_filters.emplace_back(StringRef{}); // Root - should never be consulted\n            m_filters.emplace_back(StringRef{}); // Test Case - not a section filter\n            m_filters.insert( m_filters.end(), filters.begin(), filters.end() );\n        }\n    }\n    void SectionTracker::addNextFilters( std::vector<StringRef> const& filters ) {\n        if( filters.size() > 1 )\n            m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() );\n    }\n\n    StringRef SectionTracker::trimmedName() const {\n        return m_trimmed_name;\n    }\n\n} // namespace TestCaseTracking\n\n} // namespace Catch\n\n#if defined(__clang__)\n#    pragma clang diagnostic pop\n#endif\n\n\n\n\nnamespace Catch {\n\n    void throw_test_failure_exception() {\n#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS )\n        throw TestFailureException{};\n#else\n        CATCH_ERROR( \"Test failure requires aborting test!\" );\n#endif\n    }\n\n    void throw_test_skip_exception() {\n#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS )\n        throw Catch::TestSkipException();\n#else\n        CATCH_ERROR( \"Explicitly skipping tests during runtime requires exceptions\" );\n#endif\n    }\n\n} // namespace Catch\n\n\n\n#include <algorithm>\n#include <iterator>\n\nnamespace Catch {\n    void ITestInvoker::prepareTestCase() {}\n    void ITestInvoker::tearDownTestCase() {}\n    ITestInvoker::~ITestInvoker() = default;\n\n    namespace {\n        static StringRef extractClassName( StringRef classOrMethodName ) {\n            if ( !startsWith( classOrMethodName, '&' ) ) {\n                return classOrMethodName;\n            }\n\n            // Remove the leading '&' to avoid having to special case it later\n            const auto methodName =\n                classOrMethodName.substr( 1, classOrMethodName.size() );\n\n            auto reverseStart = std::make_reverse_iterator( methodName.end() );\n            auto reverseEnd = std::make_reverse_iterator( methodName.begin() );\n\n            // We make a simplifying assumption that \":\" is only present\n            // in the input as part of \"::\" from C++ typenames (this is\n            // relatively safe assumption because the input is generated\n            // as stringification of type through preprocessor).\n            auto lastColons = std::find( reverseStart, reverseEnd, ':' ) + 1;\n            auto secondLastColons =\n                std::find( lastColons + 1, reverseEnd, ':' );\n\n            auto const startIdx = reverseEnd - secondLastColons;\n            auto const classNameSize = secondLastColons - lastColons - 1;\n\n            return methodName.substr(\n                static_cast<std::size_t>( startIdx ),\n                static_cast<std::size_t>( classNameSize ) );\n        }\n\n        class TestInvokerAsFunction final : public ITestInvoker {\n            using TestType = void ( * )();\n            TestType m_testAsFunction;\n\n        public:\n            constexpr TestInvokerAsFunction( TestType testAsFunction ) noexcept:\n                m_testAsFunction( testAsFunction ) {}\n\n            void invoke() const override { m_testAsFunction(); }\n        };\n\n    } // namespace\n\n    Detail::unique_ptr<ITestInvoker> makeTestInvoker( void(*testAsFunction)() ) {\n        return Detail::make_unique<TestInvokerAsFunction>( testAsFunction );\n    }\n\n    AutoReg::AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept {\n        CATCH_TRY {\n            getMutableRegistryHub()\n                    .registerTest(\n                        makeTestCaseInfo(\n                            extractClassName( classOrMethod ),\n                            nameAndTags,\n                            lineInfo),\n                        CATCH_MOVE(invoker)\n                    );\n        } CATCH_CATCH_ALL {\n            // Do not throw when constructing global objects, instead register the exception to be processed later\n            getMutableRegistryHub().registerStartupException();\n        }\n    }\n}\n\n\n\n\n\nnamespace Catch {\n\n    TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}\n\n    TestSpecParser& TestSpecParser::parse( std::string const& arg ) {\n        m_mode = None;\n        m_exclusion = false;\n        m_arg = m_tagAliases->expandAliases( arg );\n        m_escapeChars.clear();\n        m_substring.reserve(m_arg.size());\n        m_patternName.reserve(m_arg.size());\n        m_realPatternPos = 0;\n\n        for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )\n          //if visitChar fails\n           if( !visitChar( m_arg[m_pos] ) ){\n               m_testSpec.m_invalidSpecs.push_back(arg);\n               break;\n           }\n        endMode();\n        return *this;\n    }\n    TestSpec TestSpecParser::testSpec() {\n        addFilter();\n        return CATCH_MOVE(m_testSpec);\n    }\n    bool TestSpecParser::visitChar( char c ) {\n        if( (m_mode != EscapedName) && (c == '\\\\') ) {\n            escape();\n            addCharToPattern(c);\n            return true;\n        }else if((m_mode != EscapedName) && (c == ',') )  {\n            return separate();\n        }\n\n        switch( m_mode ) {\n        case None:\n            if( processNoneChar( c ) )\n                return true;\n            break;\n        case Name:\n            processNameChar( c );\n            break;\n        case EscapedName:\n            endMode();\n            addCharToPattern(c);\n            return true;\n        default:\n        case Tag:\n        case QuotedName:\n            if( processOtherChar( c ) )\n                return true;\n            break;\n        }\n\n        m_substring += c;\n        if( !isControlChar( c ) ) {\n            m_patternName += c;\n            m_realPatternPos++;\n        }\n        return true;\n    }\n    // Two of the processing methods return true to signal the caller to return\n    // without adding the given character to the current pattern strings\n    bool TestSpecParser::processNoneChar( char c ) {\n        switch( c ) {\n        case ' ':\n            return true;\n        case '~':\n            m_exclusion = true;\n            return false;\n        case '[':\n            startNewMode( Tag );\n            return false;\n        case '\"':\n            startNewMode( QuotedName );\n            return false;\n        default:\n            startNewMode( Name );\n            return false;\n        }\n    }\n    void TestSpecParser::processNameChar( char c ) {\n        if( c == '[' ) {\n            if( m_substring == \"exclude:\" )\n                m_exclusion = true;\n            else\n                endMode();\n            startNewMode( Tag );\n        }\n    }\n    bool TestSpecParser::processOtherChar( char c ) {\n        if( !isControlChar( c ) )\n            return false;\n        m_substring += c;\n        endMode();\n        return true;\n    }\n    void TestSpecParser::startNewMode( Mode mode ) {\n        m_mode = mode;\n    }\n    void TestSpecParser::endMode() {\n        switch( m_mode ) {\n        case Name:\n        case QuotedName:\n            return addNamePattern();\n        case Tag:\n            return addTagPattern();\n        case EscapedName:\n            revertBackToLastMode();\n            return;\n        case None:\n        default:\n            return startNewMode( None );\n        }\n    }\n    void TestSpecParser::escape() {\n        saveLastMode();\n        m_mode = EscapedName;\n        m_escapeChars.push_back(m_realPatternPos);\n    }\n    bool TestSpecParser::isControlChar( char c ) const {\n        switch( m_mode ) {\n            default:\n                return false;\n            case None:\n                return c == '~';\n            case Name:\n                return c == '[';\n            case EscapedName:\n                return true;\n            case QuotedName:\n                return c == '\"';\n            case Tag:\n                return c == '[' || c == ']';\n        }\n    }\n\n    void TestSpecParser::addFilter() {\n        if( !m_currentFilter.m_required.empty() || !m_currentFilter.m_forbidden.empty() ) {\n            m_testSpec.m_filters.push_back( CATCH_MOVE(m_currentFilter) );\n            m_currentFilter = TestSpec::Filter();\n        }\n    }\n\n    void TestSpecParser::saveLastMode() {\n      lastMode = m_mode;\n    }\n\n    void TestSpecParser::revertBackToLastMode() {\n      m_mode = lastMode;\n    }\n\n    bool TestSpecParser::separate() {\n      if( (m_mode==QuotedName) || (m_mode==Tag) ){\n         //invalid argument, signal failure to previous scope.\n         m_mode = None;\n         m_pos = m_arg.size();\n         m_substring.clear();\n         m_patternName.clear();\n         m_realPatternPos = 0;\n         return false;\n      }\n      endMode();\n      addFilter();\n      return true; //success\n    }\n\n    std::string TestSpecParser::preprocessPattern() {\n        std::string token = m_patternName;\n        for (std::size_t i = 0; i < m_escapeChars.size(); ++i)\n            token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1);\n        m_escapeChars.clear();\n        if (startsWith(token, \"exclude:\")) {\n            m_exclusion = true;\n            token = token.substr(8);\n        }\n\n        m_patternName.clear();\n        m_realPatternPos = 0;\n\n        return token;\n    }\n\n    void TestSpecParser::addNamePattern() {\n        auto token = preprocessPattern();\n\n        if (!token.empty()) {\n            if (m_exclusion) {\n                m_currentFilter.m_forbidden.emplace_back(Detail::make_unique<TestSpec::NamePattern>(token, m_substring));\n            } else {\n                m_currentFilter.m_required.emplace_back(Detail::make_unique<TestSpec::NamePattern>(token, m_substring));\n            }\n        }\n        m_substring.clear();\n        m_exclusion = false;\n        m_mode = None;\n    }\n\n    void TestSpecParser::addTagPattern() {\n        auto token = preprocessPattern();\n\n        if (!token.empty()) {\n            // If the tag pattern is the \"hide and tag\" shorthand (e.g. [.foo])\n            // we have to create a separate hide tag and shorten the real one\n            if (token.size() > 1 && token[0] == '.') {\n                token.erase(token.begin());\n                if (m_exclusion) {\n                    m_currentFilter.m_forbidden.emplace_back(Detail::make_unique<TestSpec::TagPattern>(\".\", m_substring));\n                } else {\n                    m_currentFilter.m_required.emplace_back(Detail::make_unique<TestSpec::TagPattern>(\".\", m_substring));\n                }\n            }\n            if (m_exclusion) {\n                m_currentFilter.m_forbidden.emplace_back(Detail::make_unique<TestSpec::TagPattern>(token, m_substring));\n            } else {\n                m_currentFilter.m_required.emplace_back(Detail::make_unique<TestSpec::TagPattern>(token, m_substring));\n            }\n        }\n        m_substring.clear();\n        m_exclusion = false;\n        m_mode = None;\n    }\n\n} // namespace Catch\n\n\n\n#include <algorithm>\n#include <cstring>\n#include <ostream>\n\nnamespace {\n    bool isWhitespace( char c ) {\n        return c == ' ' || c == '\\t' || c == '\\n' || c == '\\r';\n    }\n\n    bool isBreakableBefore( char c ) {\n        static const char chars[] = \"[({<|\";\n        return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr;\n    }\n\n    bool isBreakableAfter( char c ) {\n        static const char chars[] = \"])}>.,:;*+-=&/\\\\\";\n        return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr;\n    }\n\n} // namespace\n\nnamespace Catch {\n    namespace TextFlow {\n        void AnsiSkippingString::preprocessString() {\n            for ( auto it = m_string.begin(); it != m_string.end(); ) {\n                // try to read through an ansi sequence\n                while ( it != m_string.end() && *it == '\\033' &&\n                        it + 1 != m_string.end() && *( it + 1 ) == '[' ) {\n                    auto cursor = it + 2;\n                    while ( cursor != m_string.end() &&\n                            ( isdigit( *cursor ) || *cursor == ';' ) ) {\n                        ++cursor;\n                    }\n                    if ( cursor == m_string.end() || *cursor != 'm' ) {\n                        break;\n                    }\n                    // 'm' -> 0xff\n                    *cursor = AnsiSkippingString::sentinel;\n                    // if we've read an ansi sequence, set the iterator and\n                    // return to the top of the loop\n                    it = cursor + 1;\n                }\n                if ( it != m_string.end() ) {\n                    ++m_size;\n                    ++it;\n                }\n            }\n        }\n\n        AnsiSkippingString::AnsiSkippingString( std::string const& text ):\n            m_string( text ) {\n            preprocessString();\n        }\n\n        AnsiSkippingString::AnsiSkippingString( std::string&& text ):\n            m_string( CATCH_MOVE( text ) ) {\n            preprocessString();\n        }\n\n        AnsiSkippingString::const_iterator AnsiSkippingString::begin() const {\n            return const_iterator( m_string );\n        }\n\n        AnsiSkippingString::const_iterator AnsiSkippingString::end() const {\n            return const_iterator( m_string, const_iterator::EndTag{} );\n        }\n\n        std::string AnsiSkippingString::substring( const_iterator begin,\n                                                   const_iterator end ) const {\n            // There's one caveat here to an otherwise simple substring: when\n            // making a begin iterator we might have skipped ansi sequences at\n            // the start. If `begin` here is a begin iterator, skipped over\n            // initial ansi sequences, we'll use the true beginning of the\n            // string. Lastly: We need to transform any chars we replaced with\n            // 0xff back to 'm'\n            auto str = std::string( begin == this->begin() ? m_string.begin()\n                                                           : begin.m_it,\n                                    end.m_it );\n            std::transform( str.begin(), str.end(), str.begin(), []( char c ) {\n                return c == AnsiSkippingString::sentinel ? 'm' : c;\n            } );\n            return str;\n        }\n\n        void AnsiSkippingString::const_iterator::tryParseAnsiEscapes() {\n            // check if we've landed on an ansi sequence, and if so read through\n            // it\n            while ( m_it != m_string->end() && *m_it == '\\033' &&\n                    m_it + 1 != m_string->end() &&  *( m_it + 1 ) == '[' ) {\n                auto cursor = m_it + 2;\n                while ( cursor != m_string->end() &&\n                        ( isdigit( *cursor ) || *cursor == ';' ) ) {\n                    ++cursor;\n                }\n                if ( cursor == m_string->end() ||\n                     *cursor != AnsiSkippingString::sentinel ) {\n                    break;\n                }\n                // if we've read an ansi sequence, set the iterator and\n                // return to the top of the loop\n                m_it = cursor + 1;\n            }\n        }\n\n        void AnsiSkippingString::const_iterator::advance() {\n            assert( m_it != m_string->end() );\n            m_it++;\n            tryParseAnsiEscapes();\n        }\n\n        void AnsiSkippingString::const_iterator::unadvance() {\n            assert( m_it != m_string->begin() );\n            m_it--;\n            // if *m_it is 0xff, scan back to the \\033 and then m_it-- once more\n            // (and repeat check)\n            while ( *m_it == AnsiSkippingString::sentinel ) {\n                while ( *m_it != '\\033' ) {\n                    assert( m_it != m_string->begin() );\n                    m_it--;\n                }\n                // if this happens, we must have been a begin iterator that had\n                // skipped over ansi sequences at the start of a string\n                assert( m_it != m_string->begin() );\n                assert( *m_it == '\\033' );\n                m_it--;\n            }\n        }\n\n        static bool isBoundary( AnsiSkippingString const& line,\n                                AnsiSkippingString::const_iterator it ) {\n            return it == line.end() ||\n                   ( isWhitespace( *it ) &&\n                     !isWhitespace( *it.oneBefore() ) ) ||\n                   isBreakableBefore( *it ) ||\n                   isBreakableAfter( *it.oneBefore() );\n        }\n\n        void Column::const_iterator::calcLength() {\n            m_addHyphen = false;\n            m_parsedTo = m_lineStart;\n            AnsiSkippingString const& current_line = m_column.m_string;\n\n            if ( m_parsedTo == current_line.end() ) {\n                m_lineEnd = m_parsedTo;\n                return;\n            }\n\n            assert( m_lineStart != current_line.end() );\n            if ( *m_lineStart == '\\n' ) { ++m_parsedTo; }\n\n            const auto maxLineLength = m_column.m_width - indentSize();\n            std::size_t lineLength = 0;\n            while ( m_parsedTo != current_line.end() &&\n                    lineLength < maxLineLength && *m_parsedTo != '\\n' ) {\n                ++m_parsedTo;\n                ++lineLength;\n            }\n\n            // If we encountered a newline before the column is filled,\n            // then we linebreak at the newline and consider this line\n            // finished.\n            if ( lineLength < maxLineLength ) {\n                m_lineEnd = m_parsedTo;\n            } else {\n                // Look for a natural linebreak boundary in the column\n                // (We look from the end, so that the first found boundary is\n                // the right one)\n                m_lineEnd = m_parsedTo;\n                while ( lineLength > 0 &&\n                        !isBoundary( current_line, m_lineEnd ) ) {\n                    --lineLength;\n                    --m_lineEnd;\n                }\n                while ( lineLength > 0 &&\n                        isWhitespace( *m_lineEnd.oneBefore() ) ) {\n                    --lineLength;\n                    --m_lineEnd;\n                }\n\n                // If we found one, then that is where we linebreak, otherwise\n                // we have to split text with a hyphen\n                if ( lineLength == 0 ) {\n                    m_addHyphen = true;\n                    m_lineEnd = m_parsedTo.oneBefore();\n                }\n            }\n        }\n\n        size_t Column::const_iterator::indentSize() const {\n            auto initial = m_lineStart == m_column.m_string.begin()\n                               ? m_column.m_initialIndent\n                               : std::string::npos;\n            return initial == std::string::npos ? m_column.m_indent : initial;\n        }\n\n        std::string Column::const_iterator::addIndentAndSuffix(\n            AnsiSkippingString::const_iterator start,\n            AnsiSkippingString::const_iterator end ) const {\n            std::string ret;\n            const auto desired_indent = indentSize();\n            // ret.reserve( desired_indent + (end - start) + m_addHyphen );\n            ret.append( desired_indent, ' ' );\n            // ret.append( start, end );\n            ret += m_column.m_string.substring( start, end );\n            if ( m_addHyphen ) { ret.push_back( '-' ); }\n\n            return ret;\n        }\n\n        Column::const_iterator::const_iterator( Column const& column ):\n            m_column( column ),\n            m_lineStart( column.m_string.begin() ),\n            m_lineEnd( column.m_string.begin() ),\n            m_parsedTo( column.m_string.begin() ) {\n            assert( m_column.m_width > m_column.m_indent );\n            assert( m_column.m_initialIndent == std::string::npos ||\n                    m_column.m_width > m_column.m_initialIndent );\n            calcLength();\n            if ( m_lineStart == m_lineEnd ) {\n                m_lineStart = m_column.m_string.end();\n            }\n        }\n\n        std::string Column::const_iterator::operator*() const {\n            assert( m_lineStart <= m_parsedTo );\n            return addIndentAndSuffix( m_lineStart, m_lineEnd );\n        }\n\n        Column::const_iterator& Column::const_iterator::operator++() {\n            m_lineStart = m_lineEnd;\n            AnsiSkippingString const& current_line = m_column.m_string;\n            if ( m_lineStart != current_line.end() && *m_lineStart == '\\n' ) {\n                m_lineStart++;\n            } else {\n                while ( m_lineStart != current_line.end() &&\n                        isWhitespace( *m_lineStart ) ) {\n                    ++m_lineStart;\n                }\n            }\n\n            if ( m_lineStart != current_line.end() ) { calcLength(); }\n            return *this;\n        }\n\n        Column::const_iterator Column::const_iterator::operator++( int ) {\n            const_iterator prev( *this );\n            operator++();\n            return prev;\n        }\n\n        std::ostream& operator<<( std::ostream& os, Column const& col ) {\n            bool first = true;\n            for ( auto line : col ) {\n                if ( first ) {\n                    first = false;\n                } else {\n                    os << '\\n';\n                }\n                os << line;\n            }\n            return os;\n        }\n\n        Column Spacer( size_t spaceWidth ) {\n            Column ret{ \"\" };\n            ret.width( spaceWidth );\n            return ret;\n        }\n\n        Columns::iterator::iterator( Columns const& columns, EndTag ):\n            m_columns( columns.m_columns ), m_activeIterators( 0 ) {\n\n            m_iterators.reserve( m_columns.size() );\n            for ( auto const& col : m_columns ) {\n                m_iterators.push_back( col.end() );\n            }\n        }\n\n        Columns::iterator::iterator( Columns const& columns ):\n            m_columns( columns.m_columns ),\n            m_activeIterators( m_columns.size() ) {\n\n            m_iterators.reserve( m_columns.size() );\n            for ( auto const& col : m_columns ) {\n                m_iterators.push_back( col.begin() );\n            }\n        }\n\n        std::string Columns::iterator::operator*() const {\n            std::string row, padding;\n\n            for ( size_t i = 0; i < m_columns.size(); ++i ) {\n                const auto width = m_columns[i].width();\n                if ( m_iterators[i] != m_columns[i].end() ) {\n                    std::string col = *m_iterators[i];\n                    row += padding;\n                    row += col;\n\n                    padding.clear();\n                    if ( col.size() < width ) {\n                        padding.append( width - col.size(), ' ' );\n                    }\n                } else {\n                    padding.append( width, ' ' );\n                }\n            }\n            return row;\n        }\n\n        Columns::iterator& Columns::iterator::operator++() {\n            for ( size_t i = 0; i < m_columns.size(); ++i ) {\n                if ( m_iterators[i] != m_columns[i].end() ) {\n                    ++m_iterators[i];\n                }\n            }\n            return *this;\n        }\n\n        Columns::iterator Columns::iterator::operator++( int ) {\n            iterator prev( *this );\n            operator++();\n            return prev;\n        }\n\n        std::ostream& operator<<( std::ostream& os, Columns const& cols ) {\n            bool first = true;\n            for ( auto line : cols ) {\n                if ( first ) {\n                    first = false;\n                } else {\n                    os << '\\n';\n                }\n                os << line;\n            }\n            return os;\n        }\n\n        Columns operator+( Column const& lhs, Column const& rhs ) {\n            Columns cols;\n            cols += lhs;\n            cols += rhs;\n            return cols;\n        }\n        Columns operator+( Column&& lhs, Column&& rhs ) {\n            Columns cols;\n            cols += CATCH_MOVE( lhs );\n            cols += CATCH_MOVE( rhs );\n            return cols;\n        }\n\n        Columns& operator+=( Columns& lhs, Column const& rhs ) {\n            lhs.m_columns.push_back( rhs );\n            return lhs;\n        }\n        Columns& operator+=( Columns& lhs, Column&& rhs ) {\n            lhs.m_columns.push_back( CATCH_MOVE( rhs ) );\n            return lhs;\n        }\n        Columns operator+( Columns const& lhs, Column const& rhs ) {\n            auto combined( lhs );\n            combined += rhs;\n            return combined;\n        }\n        Columns operator+( Columns&& lhs, Column&& rhs ) {\n            lhs += CATCH_MOVE( rhs );\n            return CATCH_MOVE( lhs );\n        }\n\n    } // namespace TextFlow\n} // namespace Catch\n\n\n\n\n#include <exception>\n\nnamespace Catch {\n    bool uncaught_exceptions() {\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n        return false;\n#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)\n        return std::uncaught_exceptions() > 0;\n#else\n        return std::uncaught_exception();\n#endif\n  }\n} // end namespace Catch\n\n\n\nnamespace Catch {\n\n    WildcardPattern::WildcardPattern( std::string const& pattern,\n                                      CaseSensitive caseSensitivity )\n    :   m_caseSensitivity( caseSensitivity ),\n        m_pattern( normaliseString( pattern ) )\n    {\n        if( startsWith( m_pattern, '*' ) ) {\n            m_pattern = m_pattern.substr( 1 );\n            m_wildcard = WildcardAtStart;\n        }\n        if( endsWith( m_pattern, '*' ) ) {\n            m_pattern = m_pattern.substr( 0, m_pattern.size()-1 );\n            m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd );\n        }\n    }\n\n    bool WildcardPattern::matches( std::string const& str ) const {\n        switch( m_wildcard ) {\n            case NoWildcard:\n                return m_pattern == normaliseString( str );\n            case WildcardAtStart:\n                return endsWith( normaliseString( str ), m_pattern );\n            case WildcardAtEnd:\n                return startsWith( normaliseString( str ), m_pattern );\n            case WildcardAtBothEnds:\n                return contains( normaliseString( str ), m_pattern );\n            default:\n                CATCH_INTERNAL_ERROR( \"Unknown enum\" );\n        }\n    }\n\n    std::string WildcardPattern::normaliseString( std::string const& str ) const {\n        return trim( m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str );\n    }\n}\n\n\n// Note: swapping these two includes around causes MSVC to error out\n//       while in /permissive- mode. No, I don't know why.\n//       Tested on VS 2019, 18.{3, 4}.x\n\n#include <cstdint>\n#include <iomanip>\n#include <type_traits>\n\nnamespace Catch {\n\nnamespace {\n\n    size_t trailingBytes(unsigned char c) {\n        if ((c & 0xE0) == 0xC0) {\n            return 2;\n        }\n        if ((c & 0xF0) == 0xE0) {\n            return 3;\n        }\n        if ((c & 0xF8) == 0xF0) {\n            return 4;\n        }\n        CATCH_INTERNAL_ERROR(\"Invalid multibyte utf-8 start byte encountered\");\n    }\n\n    uint32_t headerValue(unsigned char c) {\n        if ((c & 0xE0) == 0xC0) {\n            return c & 0x1F;\n        }\n        if ((c & 0xF0) == 0xE0) {\n            return c & 0x0F;\n        }\n        if ((c & 0xF8) == 0xF0) {\n            return c & 0x07;\n        }\n        CATCH_INTERNAL_ERROR(\"Invalid multibyte utf-8 start byte encountered\");\n    }\n\n    void hexEscapeChar(std::ostream& os, unsigned char c) {\n        std::ios_base::fmtflags f(os.flags());\n        os << \"\\\\x\"\n            << std::uppercase << std::hex << std::setfill('0') << std::setw(2)\n            << static_cast<int>(c);\n        os.flags(f);\n    }\n\n    constexpr bool shouldNewline(XmlFormatting fmt) {\n        return !!(static_cast<std::underlying_type_t<XmlFormatting>>(fmt & XmlFormatting::Newline));\n    }\n\n    constexpr bool shouldIndent(XmlFormatting fmt) {\n        return !!(static_cast<std::underlying_type_t<XmlFormatting>>(fmt & XmlFormatting::Indent));\n    }\n\n} // anonymous namespace\n\n    void XmlEncode::encodeTo( std::ostream& os ) const {\n        // Apostrophe escaping not necessary if we always use \" to write attributes\n        // (see: http://www.w3.org/TR/xml/#syntax)\n\n        for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {\n            unsigned char c = static_cast<unsigned char>(m_str[idx]);\n            switch (c) {\n            case '<':   os << \"&lt;\"; break;\n            case '&':   os << \"&amp;\"; break;\n\n            case '>':\n                // See: http://www.w3.org/TR/xml/#syntax\n                if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')\n                    os << \"&gt;\";\n                else\n                    os << c;\n                break;\n\n            case '\\\"':\n                if (m_forWhat == ForAttributes)\n                    os << \"&quot;\";\n                else\n                    os << c;\n                break;\n\n            default:\n                // Check for control characters and invalid utf-8\n\n                // Escape control characters in standard ascii\n                // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0\n                if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {\n                    hexEscapeChar(os, c);\n                    break;\n                }\n\n                // Plain ASCII: Write it to stream\n                if (c < 0x7F) {\n                    os << c;\n                    break;\n                }\n\n                // UTF-8 territory\n                // Check if the encoding is valid and if it is not, hex escape bytes.\n                // Important: We do not check the exact decoded values for validity, only the encoding format\n                // First check that this bytes is a valid lead byte:\n                // This means that it is not encoded as 1111 1XXX\n                // Or as 10XX XXXX\n                if (c <  0xC0 ||\n                    c >= 0xF8) {\n                    hexEscapeChar(os, c);\n                    break;\n                }\n\n                auto encBytes = trailingBytes(c);\n                // Are there enough bytes left to avoid accessing out-of-bounds memory?\n                if (idx + encBytes - 1 >= m_str.size()) {\n                    hexEscapeChar(os, c);\n                    break;\n                }\n                // The header is valid, check data\n                // The next encBytes bytes must together be a valid utf-8\n                // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)\n                bool valid = true;\n                uint32_t value = headerValue(c);\n                for (std::size_t n = 1; n < encBytes; ++n) {\n                    unsigned char nc = static_cast<unsigned char>(m_str[idx + n]);\n                    valid &= ((nc & 0xC0) == 0x80);\n                    value = (value << 6) | (nc & 0x3F);\n                }\n\n                if (\n                    // Wrong bit pattern of following bytes\n                    (!valid) ||\n                    // Overlong encodings\n                    (value < 0x80) ||\n                    (0x80 <= value && value < 0x800   && encBytes > 2) ||\n                    (0x800 < value && value < 0x10000 && encBytes > 3) ||\n                    // Encoded value out of range\n                    (value >= 0x110000)\n                    ) {\n                    hexEscapeChar(os, c);\n                    break;\n                }\n\n                // If we got here, this is in fact a valid(ish) utf-8 sequence\n                for (std::size_t n = 0; n < encBytes; ++n) {\n                    os << m_str[idx + n];\n                }\n                idx += encBytes - 1;\n                break;\n            }\n        }\n    }\n\n    std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {\n        xmlEncode.encodeTo( os );\n        return os;\n    }\n\n    XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt )\n    :   m_writer( writer ),\n        m_fmt(fmt)\n    {}\n\n    XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept\n    :   m_writer( other.m_writer ),\n        m_fmt(other.m_fmt)\n    {\n        other.m_writer = nullptr;\n        other.m_fmt = XmlFormatting::None;\n    }\n    XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept {\n        if ( m_writer ) {\n            m_writer->endElement();\n        }\n        m_writer = other.m_writer;\n        other.m_writer = nullptr;\n        m_fmt = other.m_fmt;\n        other.m_fmt = XmlFormatting::None;\n        return *this;\n    }\n\n\n    XmlWriter::ScopedElement::~ScopedElement() {\n        if (m_writer) {\n            m_writer->endElement(m_fmt);\n        }\n    }\n\n    XmlWriter::ScopedElement&\n    XmlWriter::ScopedElement::writeText( StringRef text, XmlFormatting fmt ) {\n        m_writer->writeText( text, fmt );\n        return *this;\n    }\n\n    XmlWriter::ScopedElement&\n    XmlWriter::ScopedElement::writeAttribute( StringRef name,\n                                              StringRef attribute ) {\n        m_writer->writeAttribute( name, attribute );\n        return *this;\n    }\n\n\n    XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )\n    {\n        writeDeclaration();\n    }\n\n    XmlWriter::~XmlWriter() {\n        while (!m_tags.empty()) {\n            endElement();\n        }\n        newlineIfNecessary();\n    }\n\n    XmlWriter& XmlWriter::startElement( std::string const& name, XmlFormatting fmt ) {\n        ensureTagClosed();\n        newlineIfNecessary();\n        if (shouldIndent(fmt)) {\n            m_os << m_indent;\n            m_indent += \"  \";\n        }\n        m_os << '<' << name;\n        m_tags.push_back( name );\n        m_tagIsOpen = true;\n        applyFormatting(fmt);\n        return *this;\n    }\n\n    XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) {\n        ScopedElement scoped( this, fmt );\n        startElement( name, fmt );\n        return scoped;\n    }\n\n    XmlWriter& XmlWriter::endElement(XmlFormatting fmt) {\n        m_indent = m_indent.substr(0, m_indent.size() - 2);\n\n        if( m_tagIsOpen ) {\n            m_os << \"/>\";\n            m_tagIsOpen = false;\n        } else {\n            newlineIfNecessary();\n            if (shouldIndent(fmt)) {\n                m_os << m_indent;\n            }\n            m_os << \"</\" << m_tags.back() << '>';\n        }\n        m_os << std::flush;\n        applyFormatting(fmt);\n        m_tags.pop_back();\n        return *this;\n    }\n\n    XmlWriter& XmlWriter::writeAttribute( StringRef name,\n                                          StringRef attribute ) {\n        if( !name.empty() && !attribute.empty() )\n            m_os << ' ' << name << \"=\\\"\" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '\"';\n        return *this;\n    }\n\n    XmlWriter& XmlWriter::writeAttribute( StringRef name, bool attribute ) {\n        writeAttribute(name, (attribute ? \"true\"_sr : \"false\"_sr));\n        return *this;\n    }\n\n    XmlWriter& XmlWriter::writeAttribute( StringRef name,\n                                          char const* attribute ) {\n        writeAttribute( name, StringRef( attribute ) );\n        return *this;\n    }\n\n    XmlWriter& XmlWriter::writeText( StringRef text, XmlFormatting fmt ) {\n        CATCH_ENFORCE(!m_tags.empty(), \"Cannot write text as top level element\");\n        if( !text.empty() ){\n            bool tagWasOpen = m_tagIsOpen;\n            ensureTagClosed();\n            if (tagWasOpen && shouldIndent(fmt)) {\n                m_os << m_indent;\n            }\n            m_os << XmlEncode( text, XmlEncode::ForTextNodes );\n            applyFormatting(fmt);\n        }\n        return *this;\n    }\n\n    XmlWriter& XmlWriter::writeComment( StringRef text, XmlFormatting fmt ) {\n        ensureTagClosed();\n        if (shouldIndent(fmt)) {\n            m_os << m_indent;\n        }\n        m_os << \"<!-- \" << text << \" -->\";\n        applyFormatting(fmt);\n        return *this;\n    }\n\n    void XmlWriter::writeStylesheetRef( StringRef url ) {\n        m_os << R\"(<?xml-stylesheet type=\"text/xsl\" href=\")\" << url << R\"(\"?>)\" << '\\n';\n    }\n\n    void XmlWriter::ensureTagClosed() {\n        if( m_tagIsOpen ) {\n            m_os << '>' << std::flush;\n            newlineIfNecessary();\n            m_tagIsOpen = false;\n        }\n    }\n\n    void XmlWriter::applyFormatting(XmlFormatting fmt) {\n        m_needsNewline = shouldNewline(fmt);\n    }\n\n    void XmlWriter::writeDeclaration() {\n        m_os << R\"(<?xml version=\"1.0\" encoding=\"UTF-8\"?>)\" << '\\n';\n    }\n\n    void XmlWriter::newlineIfNecessary() {\n        if( m_needsNewline ) {\n            m_os << '\\n' << std::flush;\n            m_needsNewline = false;\n        }\n    }\n}\n\n\n\n\n\nnamespace Catch {\nnamespace Matchers {\n\n    std::string MatcherUntypedBase::toString() const {\n        if (m_cachedToString.empty()) {\n            m_cachedToString = describe();\n        }\n        return m_cachedToString;\n    }\n\n    MatcherUntypedBase::~MatcherUntypedBase() = default;\n\n} // namespace Matchers\n} // namespace Catch\n\n\n\n\nnamespace Catch {\nnamespace Matchers {\n\n    std::string IsEmptyMatcher::describe() const {\n        return \"is empty\";\n    }\n\n    std::string HasSizeMatcher::describe() const {\n        ReusableStringStream sstr;\n        sstr << \"has size == \" << m_target_size;\n        return sstr.str();\n    }\n\n    IsEmptyMatcher IsEmpty() {\n        return {};\n    }\n\n    HasSizeMatcher SizeIs(std::size_t sz) {\n        return HasSizeMatcher{ sz };\n    }\n\n} // end namespace Matchers\n} // end namespace Catch\n\n\n\nnamespace Catch {\nnamespace Matchers {\n\nbool ExceptionMessageMatcher::match(std::exception const& ex) const {\n    return ex.what() == m_message;\n}\n\nstd::string ExceptionMessageMatcher::describe() const {\n    return \"exception message matches \\\"\" + m_message + '\"';\n}\n\nExceptionMessageMatcher Message(std::string const& message) {\n    return ExceptionMessageMatcher(message);\n}\n\n} // namespace Matchers\n} // namespace Catch\n\n\n\n#include <algorithm>\n#include <cmath>\n#include <cstdlib>\n#include <cstdint>\n#include <sstream>\n#include <iomanip>\n#include <limits>\n\n\nnamespace Catch {\nnamespace {\n\n    template <typename FP>\n    bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) {\n        // Comparison with NaN should always be false.\n        // This way we can rule it out before getting into the ugly details\n        if (Catch::isnan(lhs) || Catch::isnan(rhs)) {\n            return false;\n        }\n\n        // This should also handle positive and negative zeros, infinities\n        const auto ulpDist = ulpDistance(lhs, rhs);\n\n        return ulpDist <= maxUlpDiff;\n    }\n\n\ntemplate <typename FP>\nFP step(FP start, FP direction, uint64_t steps) {\n    for (uint64_t i = 0; i < steps; ++i) {\n        start = Catch::nextafter(start, direction);\n    }\n    return start;\n}\n\n// Performs equivalent check of std::fabs(lhs - rhs) <= margin\n// But without the subtraction to allow for INFINITY in comparison\nbool marginComparison(double lhs, double rhs, double margin) {\n    return (lhs + margin >= rhs) && (rhs + margin >= lhs);\n}\n\ntemplate <typename FloatingPoint>\nvoid write(std::ostream& out, FloatingPoint num) {\n    out << std::scientific\n        << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1)\n        << num;\n}\n\n} // end anonymous namespace\n\nnamespace Matchers {\nnamespace Detail {\n\n    enum class FloatingPointKind : uint8_t {\n        Float,\n        Double\n    };\n\n} // end namespace Detail\n\n\n    WithinAbsMatcher::WithinAbsMatcher(double target, double margin)\n        :m_target{ target }, m_margin{ margin } {\n        CATCH_ENFORCE(margin >= 0, \"Invalid margin: \" << margin << '.'\n            << \" Margin has to be non-negative.\");\n    }\n\n    // Performs equivalent check of std::fabs(lhs - rhs) <= margin\n    // But without the subtraction to allow for INFINITY in comparison\n    bool WithinAbsMatcher::match(double const& matchee) const {\n        return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);\n    }\n\n    std::string WithinAbsMatcher::describe() const {\n        return \"is within \" + ::Catch::Detail::stringify(m_margin) + \" of \" + ::Catch::Detail::stringify(m_target);\n    }\n\n\n    WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, Detail::FloatingPointKind baseType)\n        :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {\n        CATCH_ENFORCE(m_type == Detail::FloatingPointKind::Double\n                   || m_ulps < (std::numeric_limits<uint32_t>::max)(),\n            \"Provided ULP is impossibly large for a float comparison.\");\n        CATCH_ENFORCE( std::numeric_limits<double>::is_iec559,\n                       \"WithinUlp matcher only supports platforms with \"\n                       \"IEEE-754 compatible floating point representation\" );\n    }\n\n#if defined(__clang__)\n#pragma clang diagnostic push\n// Clang <3.5 reports on the default branch in the switch below\n#pragma clang diagnostic ignored \"-Wunreachable-code\"\n#endif\n\n    bool WithinUlpsMatcher::match(double const& matchee) const {\n        switch (m_type) {\n        case Detail::FloatingPointKind::Float:\n            return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps);\n        case Detail::FloatingPointKind::Double:\n            return almostEqualUlps<double>(matchee, m_target, m_ulps);\n        default:\n            CATCH_INTERNAL_ERROR( \"Unknown Detail::FloatingPointKind value\" );\n        }\n    }\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\n    std::string WithinUlpsMatcher::describe() const {\n        std::stringstream ret;\n\n        ret << \"is within \" << m_ulps << \" ULPs of \";\n\n        if (m_type == Detail::FloatingPointKind::Float) {\n            write(ret, static_cast<float>(m_target));\n            ret << 'f';\n        } else {\n            write(ret, m_target);\n        }\n\n        ret << \" ([\";\n        if (m_type == Detail::FloatingPointKind::Double) {\n            write( ret,\n                   step( m_target,\n                         -std::numeric_limits<double>::infinity(),\n                         m_ulps ) );\n            ret << \", \";\n            write( ret,\n                   step( m_target,\n                         std::numeric_limits<double>::infinity(),\n                         m_ulps ) );\n        } else {\n            // We have to cast INFINITY to float because of MinGW, see #1782\n            write( ret,\n                   step( static_cast<float>( m_target ),\n                         -std::numeric_limits<float>::infinity(),\n                         m_ulps ) );\n            ret << \", \";\n            write( ret,\n                   step( static_cast<float>( m_target ),\n                         std::numeric_limits<float>::infinity(),\n                         m_ulps ) );\n        }\n        ret << \"])\";\n\n        return ret.str();\n    }\n\n    WithinRelMatcher::WithinRelMatcher(double target, double epsilon):\n        m_target(target),\n        m_epsilon(epsilon){\n        CATCH_ENFORCE(m_epsilon >= 0., \"Relative comparison with epsilon <  0 does not make sense.\");\n        CATCH_ENFORCE(m_epsilon  < 1., \"Relative comparison with epsilon >= 1 does not make sense.\");\n    }\n\n    bool WithinRelMatcher::match(double const& matchee) const {\n        const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target));\n        return marginComparison(matchee, m_target,\n                                std::isinf(relMargin)? 0 : relMargin);\n    }\n\n    std::string WithinRelMatcher::describe() const {\n        Catch::ReusableStringStream sstr;\n        sstr << \"and \" << ::Catch::Detail::stringify(m_target) << \" are within \" << m_epsilon * 100. << \"% of each other\";\n        return sstr.str();\n    }\n\n\nWithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) {\n    return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Double);\n}\n\nWithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) {\n    return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Float);\n}\n\nWithinAbsMatcher WithinAbs(double target, double margin) {\n    return WithinAbsMatcher(target, margin);\n}\n\nWithinRelMatcher WithinRel(double target, double eps) {\n    return WithinRelMatcher(target, eps);\n}\n\nWithinRelMatcher WithinRel(double target) {\n    return WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100);\n}\n\nWithinRelMatcher WithinRel(float target, float eps) {\n    return WithinRelMatcher(target, eps);\n}\n\nWithinRelMatcher WithinRel(float target) {\n    return WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100);\n}\n\n\n\nbool IsNaNMatcher::match( double const& matchee ) const {\n    return std::isnan( matchee );\n}\n\nstd::string IsNaNMatcher::describe() const {\n    using namespace std::string_literals;\n    return \"is NaN\"s;\n}\n\nIsNaNMatcher IsNaN() { return IsNaNMatcher(); }\n\n    } // namespace Matchers\n} // namespace Catch\n\n\n\n\nstd::string Catch::Matchers::Detail::finalizeDescription(const std::string& desc) {\n    if (desc.empty()) {\n        return \"matches undescribed predicate\";\n    } else {\n        return \"matches predicate: \\\"\" + desc + '\"';\n    }\n}\n\n\n\nnamespace Catch {\n    namespace Matchers {\n        std::string AllTrueMatcher::describe() const { return \"contains only true\"; }\n\n        AllTrueMatcher AllTrue() { return AllTrueMatcher{}; }\n\n        std::string NoneTrueMatcher::describe() const { return \"contains no true\"; }\n\n        NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; }\n\n        std::string AnyTrueMatcher::describe() const { return \"contains at least one true\"; }\n\n        AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; }\n    } // namespace Matchers\n} // namespace Catch\n\n\n\n#include <regex>\n\nnamespace Catch {\nnamespace Matchers {\n\n    CasedString::CasedString( std::string const& str, CaseSensitive caseSensitivity )\n    :   m_caseSensitivity( caseSensitivity ),\n        m_str( adjustString( str ) )\n    {}\n    std::string CasedString::adjustString( std::string const& str ) const {\n        return m_caseSensitivity == CaseSensitive::No\n               ? toLower( str )\n               : str;\n    }\n    StringRef CasedString::caseSensitivitySuffix() const {\n        return m_caseSensitivity == CaseSensitive::Yes\n                   ? StringRef()\n                   : \" (case insensitive)\"_sr;\n    }\n\n\n    StringMatcherBase::StringMatcherBase( StringRef operation, CasedString const& comparator )\n    : m_comparator( comparator ),\n      m_operation( operation ) {\n    }\n\n    std::string StringMatcherBase::describe() const {\n        std::string description;\n        description.reserve(5 + m_operation.size() + m_comparator.m_str.size() +\n                                    m_comparator.caseSensitivitySuffix().size());\n        description += m_operation;\n        description += \": \\\"\";\n        description += m_comparator.m_str;\n        description += '\"';\n        description += m_comparator.caseSensitivitySuffix();\n        return description;\n    }\n\n    StringEqualsMatcher::StringEqualsMatcher( CasedString const& comparator ) : StringMatcherBase( \"equals\"_sr, comparator ) {}\n\n    bool StringEqualsMatcher::match( std::string const& source ) const {\n        return m_comparator.adjustString( source ) == m_comparator.m_str;\n    }\n\n\n    StringContainsMatcher::StringContainsMatcher( CasedString const& comparator ) : StringMatcherBase( \"contains\"_sr, comparator ) {}\n\n    bool StringContainsMatcher::match( std::string const& source ) const {\n        return contains( m_comparator.adjustString( source ), m_comparator.m_str );\n    }\n\n\n    StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( \"starts with\"_sr, comparator ) {}\n\n    bool StartsWithMatcher::match( std::string const& source ) const {\n        return startsWith( m_comparator.adjustString( source ), m_comparator.m_str );\n    }\n\n\n    EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( \"ends with\"_sr, comparator ) {}\n\n    bool EndsWithMatcher::match( std::string const& source ) const {\n        return endsWith( m_comparator.adjustString( source ), m_comparator.m_str );\n    }\n\n\n\n    RegexMatcher::RegexMatcher(std::string regex, CaseSensitive caseSensitivity): m_regex(CATCH_MOVE(regex)), m_caseSensitivity(caseSensitivity) {}\n\n    bool RegexMatcher::match(std::string const& matchee) const {\n        auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway\n        if (m_caseSensitivity == CaseSensitive::No) {\n            flags |= std::regex::icase;\n        }\n        auto reg = std::regex(m_regex, flags);\n        return std::regex_match(matchee, reg);\n    }\n\n    std::string RegexMatcher::describe() const {\n        return \"matches \" + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Yes)? \" case sensitively\" : \" case insensitively\");\n    }\n\n\n    StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity ) {\n        return StringEqualsMatcher( CasedString( str, caseSensitivity) );\n    }\n    StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity ) {\n        return StringContainsMatcher( CasedString( str, caseSensitivity) );\n    }\n    EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity ) {\n        return EndsWithMatcher( CasedString( str, caseSensitivity) );\n    }\n    StartsWithMatcher StartsWith( std::string const& str, CaseSensitive caseSensitivity ) {\n        return StartsWithMatcher( CasedString( str, caseSensitivity) );\n    }\n\n    RegexMatcher Matches(std::string const& regex, CaseSensitive caseSensitivity) {\n        return RegexMatcher(regex, caseSensitivity);\n    }\n\n} // namespace Matchers\n} // namespace Catch\n\n\n\nnamespace Catch {\nnamespace Matchers {\n    MatcherGenericBase::~MatcherGenericBase() = default;\n\n    namespace Detail {\n\n        std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end) {\n            std::string description;\n            std::size_t combined_size = 4;\n            for ( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) {\n                combined_size += desc->size();\n            }\n            combined_size += static_cast<size_t>(descriptions_end - descriptions_begin - 1) * combine.size();\n\n            description.reserve(combined_size);\n\n            description += \"( \";\n            bool first = true;\n            for( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) {\n                if( first )\n                    first = false;\n                else\n                    description += combine;\n                description += *desc;\n            }\n            description += \" )\";\n            return description;\n        }\n\n    } // namespace Detail\n} // namespace Matchers\n} // namespace Catch\n\n\n\n\nnamespace Catch {\n\n    // This is the general overload that takes a any string matcher\n    // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers\n    // the Equals matcher (so the header does not mention matchers)\n    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher ) {\n        std::string exceptionMessage = Catch::translateActiveException();\n        MatchExpr<std::string, StringMatcher const&> expr( CATCH_MOVE(exceptionMessage), matcher );\n        handler.handleExpr( expr );\n    }\n\n} // namespace Catch\n\n\n\n#include <ostream>\n\nnamespace Catch {\n\n    AutomakeReporter::~AutomakeReporter() = default;\n\n    void AutomakeReporter::testCaseEnded(TestCaseStats const& _testCaseStats) {\n        // Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR.\n        m_stream << \":test-result: \";\n        if ( _testCaseStats.totals.testCases.skipped > 0 ) {\n            m_stream << \"SKIP\";\n        } else if (_testCaseStats.totals.assertions.allPassed()) {\n            m_stream << \"PASS\";\n        } else if (_testCaseStats.totals.assertions.allOk()) {\n            m_stream << \"XFAIL\";\n        } else {\n            m_stream << \"FAIL\";\n        }\n        m_stream << ' ' << _testCaseStats.testInfo->name << '\\n';\n        StreamingReporterBase::testCaseEnded(_testCaseStats);\n    }\n\n    void AutomakeReporter::skipTest(TestCaseInfo const& testInfo) {\n        m_stream << \":test-result: SKIP \" << testInfo.name << '\\n';\n    }\n\n} // end namespace Catch\n\n\n\n\n\n\nnamespace Catch {\n    ReporterBase::ReporterBase( ReporterConfig&& config ):\n        IEventListener( config.fullConfig() ),\n        m_wrapped_stream( CATCH_MOVE(config).takeStream() ),\n        m_stream( m_wrapped_stream->stream() ),\n        m_colour( makeColourImpl( config.colourMode(), m_wrapped_stream.get() ) ),\n        m_customOptions( config.customOptions() )\n    {}\n\n    ReporterBase::~ReporterBase() = default;\n\n    void ReporterBase::listReporters(\n        std::vector<ReporterDescription> const& descriptions ) {\n        defaultListReporters(m_stream, descriptions, m_config->verbosity());\n    }\n\n    void ReporterBase::listListeners(\n        std::vector<ListenerDescription> const& descriptions ) {\n        defaultListListeners( m_stream, descriptions );\n    }\n\n    void ReporterBase::listTests(std::vector<TestCaseHandle> const& tests) {\n        defaultListTests(m_stream,\n                         m_colour.get(),\n                         tests,\n                         m_config->hasTestFilters(),\n                         m_config->verbosity());\n    }\n\n    void ReporterBase::listTags(std::vector<TagInfo> const& tags) {\n        defaultListTags( m_stream, tags, m_config->hasTestFilters() );\n    }\n\n} // namespace Catch\n\n\n\n\n#include <ostream>\n\nnamespace Catch {\nnamespace {\n\n    // Colour::LightGrey\n    static constexpr Colour::Code compactDimColour = Colour::FileName;\n\n#ifdef CATCH_PLATFORM_MAC\n    static constexpr Catch::StringRef compactFailedString = \"FAILED\"_sr;\n    static constexpr Catch::StringRef compactPassedString = \"PASSED\"_sr;\n#else\n    static constexpr Catch::StringRef compactFailedString = \"failed\"_sr;\n    static constexpr Catch::StringRef compactPassedString = \"passed\"_sr;\n#endif\n\n// Implementation of CompactReporter formatting\nclass AssertionPrinter {\npublic:\n    AssertionPrinter& operator= (AssertionPrinter const&) = delete;\n    AssertionPrinter(AssertionPrinter const&) = delete;\n    AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages, ColourImpl* colourImpl_)\n        : stream(_stream)\n        , result(_stats.assertionResult)\n        , messages(_stats.infoMessages)\n        , itMessage(_stats.infoMessages.begin())\n        , printInfoMessages(_printInfoMessages)\n        , colourImpl(colourImpl_)\n    {}\n\n    void print() {\n        printSourceInfo();\n\n        itMessage = messages.begin();\n\n        switch (result.getResultType()) {\n        case ResultWas::Ok:\n            printResultType(Colour::ResultSuccess, compactPassedString);\n            printOriginalExpression();\n            printReconstructedExpression();\n            if (!result.hasExpression())\n                printRemainingMessages(Colour::None);\n            else\n                printRemainingMessages();\n            break;\n        case ResultWas::ExpressionFailed:\n            if (result.isOk())\n                printResultType(Colour::ResultSuccess, compactFailedString + \" - but was ok\"_sr);\n            else\n                printResultType(Colour::Error, compactFailedString);\n            printOriginalExpression();\n            printReconstructedExpression();\n            printRemainingMessages();\n            break;\n        case ResultWas::ThrewException:\n            printResultType(Colour::Error, compactFailedString);\n            printIssue(\"unexpected exception with message:\");\n            printMessage();\n            printExpressionWas();\n            printRemainingMessages();\n            break;\n        case ResultWas::FatalErrorCondition:\n            printResultType(Colour::Error, compactFailedString);\n            printIssue(\"fatal error condition with message:\");\n            printMessage();\n            printExpressionWas();\n            printRemainingMessages();\n            break;\n        case ResultWas::DidntThrowException:\n            printResultType(Colour::Error, compactFailedString);\n            printIssue(\"expected exception, got none\");\n            printExpressionWas();\n            printRemainingMessages();\n            break;\n        case ResultWas::Info:\n            printResultType(Colour::None, \"info\"_sr);\n            printMessage();\n            printRemainingMessages();\n            break;\n        case ResultWas::Warning:\n            printResultType(Colour::None, \"warning\"_sr);\n            printMessage();\n            printRemainingMessages();\n            break;\n        case ResultWas::ExplicitFailure:\n            printResultType(Colour::Error, compactFailedString);\n            printIssue(\"explicitly\");\n            printRemainingMessages(Colour::None);\n            break;\n        case ResultWas::ExplicitSkip:\n            printResultType(Colour::Skip, \"skipped\"_sr);\n            printMessage();\n            printRemainingMessages();\n            break;\n            // These cases are here to prevent compiler warnings\n        case ResultWas::Unknown:\n        case ResultWas::FailureBit:\n        case ResultWas::Exception:\n            printResultType(Colour::Error, \"** internal error **\");\n            break;\n        }\n    }\n\nprivate:\n    void printSourceInfo() const {\n        stream << colourImpl->guardColour( Colour::FileName )\n               << result.getSourceInfo() << ':';\n    }\n\n    void printResultType(Colour::Code colour, StringRef passOrFail) const {\n        if (!passOrFail.empty()) {\n            stream << colourImpl->guardColour(colour) << ' ' << passOrFail;\n            stream << ':';\n        }\n    }\n\n    void printIssue(char const* issue) const {\n        stream << ' ' << issue;\n    }\n\n    void printExpressionWas() {\n        if (result.hasExpression()) {\n            stream << ';';\n            {\n                stream << colourImpl->guardColour(compactDimColour) << \" expression was:\";\n            }\n            printOriginalExpression();\n        }\n    }\n\n    void printOriginalExpression() const {\n        if (result.hasExpression()) {\n            stream << ' ' << result.getExpression();\n        }\n    }\n\n    void printReconstructedExpression() const {\n        if (result.hasExpandedExpression()) {\n            stream << colourImpl->guardColour(compactDimColour) << \" for: \";\n            stream << result.getExpandedExpression();\n        }\n    }\n\n    void printMessage() {\n        if (itMessage != messages.end()) {\n            stream << \" '\" << itMessage->message << '\\'';\n            ++itMessage;\n        }\n    }\n\n    void printRemainingMessages(Colour::Code colour = compactDimColour) {\n        if (itMessage == messages.end())\n            return;\n\n        const auto itEnd = messages.cend();\n        const auto N = static_cast<std::size_t>(itEnd - itMessage);\n\n        stream << colourImpl->guardColour( colour ) << \" with \"\n               << pluralise( N, \"message\"_sr ) << ':';\n\n        while (itMessage != itEnd) {\n            // If this assertion is a warning ignore any INFO messages\n            if (printInfoMessages || itMessage->type != ResultWas::Info) {\n                printMessage();\n                if (itMessage != itEnd) {\n                    stream << colourImpl->guardColour(compactDimColour) << \" and\";\n                }\n                continue;\n            }\n            ++itMessage;\n        }\n    }\n\nprivate:\n    std::ostream& stream;\n    AssertionResult const& result;\n    std::vector<MessageInfo> const& messages;\n    std::vector<MessageInfo>::const_iterator itMessage;\n    bool printInfoMessages;\n    ColourImpl* colourImpl;\n};\n\n} // anon namespace\n\n        std::string CompactReporter::getDescription() {\n            return \"Reports test results on a single line, suitable for IDEs\";\n        }\n\n        void CompactReporter::noMatchingTestCases( StringRef unmatchedSpec ) {\n            m_stream << \"No test cases matched '\" << unmatchedSpec << \"'\\n\";\n        }\n\n        void CompactReporter::testRunStarting( TestRunInfo const& ) {\n            if ( m_config->testSpec().hasFilters() ) {\n                m_stream << m_colour->guardColour( Colour::BrightYellow )\n                         << \"Filters: \"\n                         << m_config->testSpec()\n                         << '\\n';\n            }\n            m_stream << \"RNG seed: \" << getSeed() << '\\n'\n                     << std::flush;\n        }\n\n        void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) {\n            AssertionResult const& result = _assertionStats.assertionResult;\n\n            bool printInfoMessages = true;\n\n            // Drop out if result was successful and we're not printing those\n            if( !m_config->includeSuccessfulResults() && result.isOk() ) {\n                if( result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip )\n                    return;\n                printInfoMessages = false;\n            }\n\n            AssertionPrinter printer( m_stream, _assertionStats, printInfoMessages, m_colour.get() );\n            printer.print();\n\n            m_stream << '\\n' << std::flush;\n        }\n\n        void CompactReporter::sectionEnded(SectionStats const& _sectionStats) {\n            double dur = _sectionStats.durationInSeconds;\n            if ( shouldShowDuration( *m_config, dur ) ) {\n                m_stream << getFormattedDuration( dur ) << \" s: \" << _sectionStats.sectionInfo.name << '\\n' << std::flush;\n            }\n        }\n\n        void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) {\n            printTestRunTotals( m_stream, *m_colour, _testRunStats.totals );\n            m_stream << \"\\n\\n\" << std::flush;\n            StreamingReporterBase::testRunEnded( _testRunStats );\n        }\n\n        CompactReporter::~CompactReporter() = default;\n\n} // end namespace Catch\n\n\n\n\n#include <cstdio>\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch\n // Note that 4062 (not all labels are handled and default is missing) is enabled\n#endif\n\n#if defined(__clang__)\n#  pragma clang diagnostic push\n// For simplicity, benchmarking-only helpers are always enabled\n#  pragma clang diagnostic ignored \"-Wunused-function\"\n#endif\n\n\n\nnamespace Catch {\n\nnamespace {\n\n// Formatter impl for ConsoleReporter\nclass ConsoleAssertionPrinter {\npublic:\n    ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete;\n    ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete;\n    ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, ColourImpl* colourImpl_, bool _printInfoMessages)\n        : stream(_stream),\n        stats(_stats),\n        result(_stats.assertionResult),\n        colour(Colour::None),\n        messages(_stats.infoMessages),\n        colourImpl(colourImpl_),\n        printInfoMessages(_printInfoMessages) {\n        switch (result.getResultType()) {\n        case ResultWas::Ok:\n            colour = Colour::Success;\n            passOrFail = \"PASSED\"_sr;\n            //if( result.hasMessage() )\n            if (messages.size() == 1)\n                messageLabel = \"with message\"_sr;\n            if (messages.size() > 1)\n                messageLabel = \"with messages\"_sr;\n            break;\n        case ResultWas::ExpressionFailed:\n            if (result.isOk()) {\n                colour = Colour::Success;\n                passOrFail = \"FAILED - but was ok\"_sr;\n            } else {\n                colour = Colour::Error;\n                passOrFail = \"FAILED\"_sr;\n            }\n            if (messages.size() == 1)\n                messageLabel = \"with message\"_sr;\n            if (messages.size() > 1)\n                messageLabel = \"with messages\"_sr;\n            break;\n        case ResultWas::ThrewException:\n            colour = Colour::Error;\n            passOrFail = \"FAILED\"_sr;\n            // todo switch\n            switch (messages.size()) { case 0:\n                messageLabel = \"due to unexpected exception with \"_sr;\n                break;\n            case 1:\n                messageLabel = \"due to unexpected exception with message\"_sr;\n                break;\n            default:\n                messageLabel = \"due to unexpected exception with messages\"_sr;\n                break;\n            }\n            break;\n        case ResultWas::FatalErrorCondition:\n            colour = Colour::Error;\n            passOrFail = \"FAILED\"_sr;\n            messageLabel = \"due to a fatal error condition\"_sr;\n            break;\n        case ResultWas::DidntThrowException:\n            colour = Colour::Error;\n            passOrFail = \"FAILED\"_sr;\n            messageLabel = \"because no exception was thrown where one was expected\"_sr;\n            break;\n        case ResultWas::Info:\n            messageLabel = \"info\"_sr;\n            break;\n        case ResultWas::Warning:\n            messageLabel = \"warning\"_sr;\n            break;\n        case ResultWas::ExplicitFailure:\n            passOrFail = \"FAILED\"_sr;\n            colour = Colour::Error;\n            if (messages.size() == 1)\n                messageLabel = \"explicitly with message\"_sr;\n            if (messages.size() > 1)\n                messageLabel = \"explicitly with messages\"_sr;\n            break;\n        case ResultWas::ExplicitSkip:\n            colour = Colour::Skip;\n            passOrFail = \"SKIPPED\"_sr;\n            if (messages.size() == 1)\n                messageLabel = \"explicitly with message\"_sr;\n            if (messages.size() > 1)\n                messageLabel = \"explicitly with messages\"_sr;\n            break;\n            // These cases are here to prevent compiler warnings\n        case ResultWas::Unknown:\n        case ResultWas::FailureBit:\n        case ResultWas::Exception:\n            passOrFail = \"** internal error **\"_sr;\n            colour = Colour::Error;\n            break;\n        }\n    }\n\n    void print() const {\n        printSourceInfo();\n        if (stats.totals.assertions.total() > 0) {\n            printResultType();\n            printOriginalExpression();\n            printReconstructedExpression();\n        } else {\n            stream << '\\n';\n        }\n        printMessage();\n    }\n\nprivate:\n    void printResultType() const {\n        if (!passOrFail.empty()) {\n            stream << colourImpl->guardColour(colour) << passOrFail << \":\\n\";\n        }\n    }\n    void printOriginalExpression() const {\n        if (result.hasExpression()) {\n            stream << colourImpl->guardColour( Colour::OriginalExpression )\n                   << \"  \" << result.getExpressionInMacro() << '\\n';\n        }\n    }\n    void printReconstructedExpression() const {\n        if (result.hasExpandedExpression()) {\n            stream << \"with expansion:\\n\";\n            stream << colourImpl->guardColour( Colour::ReconstructedExpression )\n                   << TextFlow::Column( result.getExpandedExpression() )\n                          .indent( 2 )\n                   << '\\n';\n        }\n    }\n    void printMessage() const {\n        if (!messageLabel.empty())\n            stream << messageLabel << ':' << '\\n';\n        for (auto const& msg : messages) {\n            // If this assertion is a warning ignore any INFO messages\n            if (printInfoMessages || msg.type != ResultWas::Info)\n                stream << TextFlow::Column(msg.message).indent(2) << '\\n';\n        }\n    }\n    void printSourceInfo() const {\n        stream << colourImpl->guardColour( Colour::FileName )\n               << result.getSourceInfo() << \": \";\n    }\n\n    std::ostream& stream;\n    AssertionStats const& stats;\n    AssertionResult const& result;\n    Colour::Code colour;\n    StringRef passOrFail;\n    StringRef messageLabel;\n    std::vector<MessageInfo> const& messages;\n    ColourImpl* colourImpl;\n    bool printInfoMessages;\n};\n\nstd::size_t makeRatio( std::uint64_t number, std::uint64_t total ) {\n    const auto ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0;\n    return (ratio == 0 && number > 0) ? 1 : static_cast<std::size_t>(ratio);\n}\n\nstd::size_t&\nfindMax( std::size_t& i, std::size_t& j, std::size_t& k, std::size_t& l ) {\n    if (i > j && i > k && i > l)\n        return i;\n    else if (j > k && j > l)\n        return j;\n    else if (k > l)\n        return k;\n    else\n        return l;\n}\n\nstruct ColumnBreak {};\nstruct RowBreak {};\nstruct OutputFlush {};\n\nclass Duration {\n    enum class Unit : uint8_t {\n        Auto,\n        Nanoseconds,\n        Microseconds,\n        Milliseconds,\n        Seconds,\n        Minutes\n    };\n    static const uint64_t s_nanosecondsInAMicrosecond = 1000;\n    static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond;\n    static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond;\n    static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond;\n\n    double m_inNanoseconds;\n    Unit m_units;\n\npublic:\n    explicit Duration(double inNanoseconds, Unit units = Unit::Auto)\n        : m_inNanoseconds(inNanoseconds),\n        m_units(units) {\n        if (m_units == Unit::Auto) {\n            if (m_inNanoseconds < s_nanosecondsInAMicrosecond)\n                m_units = Unit::Nanoseconds;\n            else if (m_inNanoseconds < s_nanosecondsInAMillisecond)\n                m_units = Unit::Microseconds;\n            else if (m_inNanoseconds < s_nanosecondsInASecond)\n                m_units = Unit::Milliseconds;\n            else if (m_inNanoseconds < s_nanosecondsInAMinute)\n                m_units = Unit::Seconds;\n            else\n                m_units = Unit::Minutes;\n        }\n\n    }\n\n    auto value() const -> double {\n        switch (m_units) {\n        case Unit::Microseconds:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond);\n        case Unit::Milliseconds:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond);\n        case Unit::Seconds:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond);\n        case Unit::Minutes:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute);\n        default:\n            return m_inNanoseconds;\n        }\n    }\n    StringRef unitsAsString() const {\n        switch (m_units) {\n        case Unit::Nanoseconds:\n            return \"ns\"_sr;\n        case Unit::Microseconds:\n            return \"us\"_sr;\n        case Unit::Milliseconds:\n            return \"ms\"_sr;\n        case Unit::Seconds:\n            return \"s\"_sr;\n        case Unit::Minutes:\n            return \"m\"_sr;\n        default:\n            return \"** internal error **\"_sr;\n        }\n\n    }\n    friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& {\n        return os << duration.value() << ' ' << duration.unitsAsString();\n    }\n};\n} // end anon namespace\n\nenum class Justification : uint8_t {\n    Left,\n    Right\n};\n\nstruct ColumnInfo {\n    std::string name;\n    std::size_t width;\n    Justification justification;\n};\n\nclass TablePrinter {\n    std::ostream& m_os;\n    std::vector<ColumnInfo> m_columnInfos;\n    ReusableStringStream m_oss;\n    int m_currentColumn = -1;\n    bool m_isOpen = false;\n\npublic:\n    TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos )\n    :   m_os( os ),\n        m_columnInfos( CATCH_MOVE( columnInfos ) ) {}\n\n    auto columnInfos() const -> std::vector<ColumnInfo> const& {\n        return m_columnInfos;\n    }\n\n    void open() {\n        if (!m_isOpen) {\n            m_isOpen = true;\n            *this << RowBreak();\n\n\t\t\tTextFlow::Columns headerCols;\n\t\t\tfor (auto const& info : m_columnInfos) {\n                assert(info.width > 2);\n\t\t\t\theaderCols += TextFlow::Column(info.name).width(info.width - 2);\n                headerCols += TextFlow::Spacer( 2 );\n\t\t\t}\n\t\t\tm_os << headerCols << '\\n';\n\n            m_os << lineOfChars('-') << '\\n';\n        }\n    }\n    void close() {\n        if (m_isOpen) {\n            *this << RowBreak();\n            m_os << '\\n' << std::flush;\n            m_isOpen = false;\n        }\n    }\n\n    template<typename T>\n    friend TablePrinter& operator<< (TablePrinter& tp, T const& value) {\n        tp.m_oss << value;\n        return tp;\n    }\n\n    friend TablePrinter& operator<< (TablePrinter& tp, ColumnBreak) {\n        auto colStr = tp.m_oss.str();\n        const auto strSize = colStr.size();\n        tp.m_oss.str(\"\");\n        tp.open();\n        if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) {\n            tp.m_currentColumn = -1;\n            tp.m_os << '\\n';\n        }\n        tp.m_currentColumn++;\n\n        auto colInfo = tp.m_columnInfos[tp.m_currentColumn];\n        auto padding = (strSize + 1 < colInfo.width)\n            ? std::string(colInfo.width - (strSize + 1), ' ')\n            : std::string();\n        if (colInfo.justification == Justification::Left)\n            tp.m_os << colStr << padding << ' ';\n        else\n            tp.m_os << padding << colStr << ' ';\n        return tp;\n    }\n\n    friend TablePrinter& operator<< (TablePrinter& tp, RowBreak) {\n        if (tp.m_currentColumn > 0) {\n            tp.m_os << '\\n';\n            tp.m_currentColumn = -1;\n        }\n        return tp;\n    }\n\n    friend TablePrinter& operator<<(TablePrinter& tp, OutputFlush) {\n        tp.m_os << std::flush;\n        return tp;\n    }\n};\n\nConsoleReporter::ConsoleReporter(ReporterConfig&& config):\n    StreamingReporterBase( CATCH_MOVE( config ) ),\n    m_tablePrinter(Detail::make_unique<TablePrinter>(m_stream,\n        [&config]() -> std::vector<ColumnInfo> {\n        if (config.fullConfig()->benchmarkNoAnalysis())\n        {\n            return{\n                { \"benchmark name\", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left },\n                { \"     samples\", 14, Justification::Right },\n                { \"  iterations\", 14, Justification::Right },\n                { \"        mean\", 14, Justification::Right }\n            };\n        }\n        else\n        {\n            return{\n                { \"benchmark name\", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left },\n                { \"samples      mean       std dev\", 14, Justification::Right },\n                { \"iterations   low mean   low std dev\", 14, Justification::Right },\n                { \"est run time high mean  high std dev\", 14, Justification::Right }\n            };\n        }\n    }())) {\n    m_preferences.shouldReportAllAssertionStarts = false;\n}\n\nConsoleReporter::~ConsoleReporter() = default;\n\nstd::string ConsoleReporter::getDescription() {\n    return \"Reports test results as plain lines of text\";\n}\n\nvoid ConsoleReporter::noMatchingTestCases( StringRef unmatchedSpec ) {\n    m_stream << \"No test cases matched '\" << unmatchedSpec << \"'\\n\";\n}\n\nvoid ConsoleReporter::reportInvalidTestSpec( StringRef arg ) {\n    m_stream << \"Invalid Filter: \" << arg << '\\n';\n}\n\nvoid ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) {\n    AssertionResult const& result = _assertionStats.assertionResult;\n\n    bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();\n\n    // Drop out if result was successful but we're not printing them.\n    // TODO: Make configurable whether skips should be printed\n    if (!includeResults && result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip)\n        return;\n\n    lazyPrint();\n\n    ConsoleAssertionPrinter printer(m_stream, _assertionStats, m_colour.get(), includeResults);\n    printer.print();\n    m_stream << '\\n' << std::flush;\n}\n\nvoid ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) {\n    m_tablePrinter->close();\n    m_headerPrinted = false;\n    StreamingReporterBase::sectionStarting(_sectionInfo);\n}\nvoid ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) {\n    m_tablePrinter->close();\n    if (_sectionStats.missingAssertions) {\n        lazyPrint();\n        auto guard =\n            m_colour->guardColour( Colour::ResultError ).engage( m_stream );\n        if (m_sectionStack.size() > 1)\n            m_stream << \"\\nNo assertions in section\";\n        else\n            m_stream << \"\\nNo assertions in test case\";\n        m_stream << \" '\" << _sectionStats.sectionInfo.name << \"'\\n\\n\" << std::flush;\n    }\n    double dur = _sectionStats.durationInSeconds;\n    if (shouldShowDuration(*m_config, dur)) {\n        m_stream << getFormattedDuration(dur) << \" s: \" << _sectionStats.sectionInfo.name << '\\n' << std::flush;\n    }\n    if (m_headerPrinted) {\n        m_headerPrinted = false;\n    }\n    StreamingReporterBase::sectionEnded(_sectionStats);\n}\n\nvoid ConsoleReporter::benchmarkPreparing( StringRef name ) {\n\tlazyPrintWithoutClosingBenchmarkTable();\n\n\tauto nameCol = TextFlow::Column( static_cast<std::string>( name ) )\n                       .width( m_tablePrinter->columnInfos()[0].width - 2 );\n\n\tbool firstLine = true;\n\tfor (auto line : nameCol) {\n\t\tif (!firstLine)\n\t\t\t(*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak();\n\t\telse\n\t\t\tfirstLine = false;\n\n\t\t(*m_tablePrinter) << line << ColumnBreak();\n\t}\n}\n\nvoid ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) {\n    (*m_tablePrinter) << info.samples << ColumnBreak()\n        << info.iterations << ColumnBreak();\n    if ( !m_config->benchmarkNoAnalysis() ) {\n        ( *m_tablePrinter )\n            << Duration( info.estimatedDuration ) << ColumnBreak();\n    }\n    ( *m_tablePrinter ) << OutputFlush{};\n}\nvoid ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) {\n    if (m_config->benchmarkNoAnalysis())\n    {\n        (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak();\n    }\n    else\n    {\n        (*m_tablePrinter) << ColumnBreak()\n            << Duration(stats.mean.point.count()) << ColumnBreak()\n            << Duration(stats.mean.lower_bound.count()) << ColumnBreak()\n            << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak()\n            << Duration(stats.standardDeviation.point.count()) << ColumnBreak()\n            << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak()\n            << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak();\n    }\n}\n\nvoid ConsoleReporter::benchmarkFailed( StringRef error ) {\n    auto guard = m_colour->guardColour( Colour::Red ).engage( m_stream );\n    (*m_tablePrinter)\n        << \"Benchmark failed (\" << error << ')'\n        << ColumnBreak() << RowBreak();\n}\n\nvoid ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) {\n    m_tablePrinter->close();\n    StreamingReporterBase::testCaseEnded(_testCaseStats);\n    m_headerPrinted = false;\n}\nvoid ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) {\n    printTotalsDivider(_testRunStats.totals);\n    printTestRunTotals( m_stream, *m_colour, _testRunStats.totals );\n    m_stream << '\\n' << std::flush;\n    StreamingReporterBase::testRunEnded(_testRunStats);\n}\nvoid ConsoleReporter::testRunStarting(TestRunInfo const& _testRunInfo) {\n    StreamingReporterBase::testRunStarting(_testRunInfo);\n    if ( m_config->testSpec().hasFilters() ) {\n        m_stream << m_colour->guardColour( Colour::BrightYellow ) << \"Filters: \"\n                 << m_config->testSpec() << '\\n';\n    }\n    m_stream << \"Randomness seeded to: \" << getSeed() << '\\n'\n             << std::flush;\n}\n\nvoid ConsoleReporter::lazyPrint() {\n\n    m_tablePrinter->close();\n    lazyPrintWithoutClosingBenchmarkTable();\n}\n\nvoid ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {\n\n    if ( !m_testRunInfoPrinted ) {\n        lazyPrintRunInfo();\n    }\n    if (!m_headerPrinted) {\n        printTestCaseAndSectionHeader();\n        m_headerPrinted = true;\n    }\n}\nvoid ConsoleReporter::lazyPrintRunInfo() {\n    m_stream << '\\n'\n             << lineOfChars( '~' ) << '\\n'\n             << m_colour->guardColour( Colour::SecondaryText )\n             << currentTestRunInfo.name << \" is a Catch2 v\" << libraryVersion()\n             << \" host application.\\n\"\n             << \"Run with -? for options\\n\\n\";\n\n    m_testRunInfoPrinted = true;\n}\nvoid ConsoleReporter::printTestCaseAndSectionHeader() {\n    assert(!m_sectionStack.empty());\n    printOpenHeader(currentTestCaseInfo->name);\n\n    if (m_sectionStack.size() > 1) {\n        auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream );\n\n        auto\n            it = m_sectionStack.begin() + 1, // Skip first section (test case)\n            itEnd = m_sectionStack.end();\n        for (; it != itEnd; ++it)\n            printHeaderString(it->name, 2);\n    }\n\n    SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;\n\n\n    m_stream << lineOfChars( '-' ) << '\\n'\n             << m_colour->guardColour( Colour::FileName ) << lineInfo << '\\n'\n             << lineOfChars( '.' ) << \"\\n\\n\"\n             << std::flush;\n}\n\nvoid ConsoleReporter::printClosedHeader(std::string const& _name) {\n    printOpenHeader(_name);\n    m_stream << lineOfChars('.') << '\\n';\n}\nvoid ConsoleReporter::printOpenHeader(std::string const& _name) {\n    m_stream << lineOfChars('-') << '\\n';\n    {\n        auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream );\n        printHeaderString(_name);\n    }\n}\n\nvoid ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) {\n    // We want to get a bit fancy with line breaking here, so that subsequent\n    // lines start after \":\" if one is present, e.g.\n    // ```\n    // blablabla: Fancy\n    //            linebreaking\n    // ```\n    // but we also want to avoid problems with overly long indentation causing\n    // the text to take up too many lines, e.g.\n    // ```\n    // blablabla: F\n    //            a\n    //            n\n    //            c\n    //            y\n    //            .\n    //            .\n    //            .\n    // ```\n    // So we limit the prefix indentation check to first quarter of the possible\n    // width\n    std::size_t idx = _string.find( \": \" );\n    if ( idx != std::string::npos && idx < CATCH_CONFIG_CONSOLE_WIDTH / 4 ) {\n        idx += 2;\n    } else {\n        idx = 0;\n    }\n    m_stream << TextFlow::Column( _string )\n                  .indent( indent + idx )\n                  .initialIndent( indent )\n           << '\\n';\n}\n\nvoid ConsoleReporter::printTotalsDivider(Totals const& totals) {\n    if (totals.testCases.total() > 0) {\n        std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total());\n        std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total());\n        std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total());\n        std::size_t skippedRatio = makeRatio(totals.testCases.skipped, totals.testCases.total());\n        while (failedRatio + failedButOkRatio + passedRatio + skippedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1)\n            findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)++;\n        while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)\n            findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)--;\n\n        m_stream << m_colour->guardColour( Colour::Error )\n                 << std::string( failedRatio, '=' )\n                 << m_colour->guardColour( Colour::ResultExpectedFailure )\n                 << std::string( failedButOkRatio, '=' );\n        if ( totals.testCases.allPassed() ) {\n            m_stream << m_colour->guardColour( Colour::ResultSuccess )\n                     << std::string( passedRatio, '=' );\n        } else {\n            m_stream << m_colour->guardColour( Colour::Success )\n                     << std::string( passedRatio, '=' );\n        }\n        m_stream << m_colour->guardColour( Colour::Skip )\n                 << std::string( skippedRatio, '=' );\n    } else {\n        m_stream << m_colour->guardColour( Colour::Warning )\n                 << std::string( CATCH_CONFIG_CONSOLE_WIDTH - 1, '=' );\n    }\n    m_stream << '\\n';\n}\n\n} // end namespace Catch\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\n#if defined(__clang__)\n#  pragma clang diagnostic pop\n#endif\n\n\n\n\n#include <algorithm>\n#include <cassert>\n\nnamespace Catch {\n    namespace {\n        struct BySectionInfo {\n            BySectionInfo( SectionInfo const& other ): m_other( other ) {}\n            BySectionInfo( BySectionInfo const& other ) = default;\n            bool operator()(\n                Detail::unique_ptr<CumulativeReporterBase::SectionNode> const&\n                    node ) const {\n                return (\n                    ( node->stats.sectionInfo.name == m_other.name ) &&\n                    ( node->stats.sectionInfo.lineInfo == m_other.lineInfo ) );\n            }\n            void operator=( BySectionInfo const& ) = delete;\n\n        private:\n            SectionInfo const& m_other;\n        };\n\n    } // namespace\n\n    namespace Detail {\n        AssertionOrBenchmarkResult::AssertionOrBenchmarkResult(\n            AssertionStats const& assertion ):\n            m_assertion( assertion ) {}\n\n        AssertionOrBenchmarkResult::AssertionOrBenchmarkResult(\n            BenchmarkStats<> const& benchmark ):\n            m_benchmark( benchmark ) {}\n\n        bool AssertionOrBenchmarkResult::isAssertion() const {\n            return m_assertion.some();\n        }\n        bool AssertionOrBenchmarkResult::isBenchmark() const {\n            return m_benchmark.some();\n        }\n\n        AssertionStats const& AssertionOrBenchmarkResult::asAssertion() const {\n            assert(m_assertion.some());\n\n            return *m_assertion;\n        }\n        BenchmarkStats<> const& AssertionOrBenchmarkResult::asBenchmark() const {\n            assert(m_benchmark.some());\n\n            return *m_benchmark;\n        }\n\n    }\n\n    CumulativeReporterBase::~CumulativeReporterBase() = default;\n\n    void CumulativeReporterBase::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) {\n        m_sectionStack.back()->assertionsAndBenchmarks.emplace_back(benchmarkStats);\n    }\n\n    void\n    CumulativeReporterBase::sectionStarting( SectionInfo const& sectionInfo ) {\n        // We need a copy, because SectionStats expect to take ownership\n        SectionStats incompleteStats( SectionInfo(sectionInfo), Counts(), 0, false );\n        SectionNode* node;\n        if ( m_sectionStack.empty() ) {\n            if ( !m_rootSection ) {\n                m_rootSection =\n                    Detail::make_unique<SectionNode>( incompleteStats );\n            }\n            node = m_rootSection.get();\n        } else {\n            SectionNode& parentNode = *m_sectionStack.back();\n            auto it = std::find_if( parentNode.childSections.begin(),\n                                    parentNode.childSections.end(),\n                                    BySectionInfo( sectionInfo ) );\n            if ( it == parentNode.childSections.end() ) {\n                auto newNode =\n                    Detail::make_unique<SectionNode>( incompleteStats );\n                node = newNode.get();\n                parentNode.childSections.push_back( CATCH_MOVE( newNode ) );\n            } else {\n                node = it->get();\n            }\n        }\n\n        m_deepestSection = node;\n        m_sectionStack.push_back( node );\n    }\n\n    void CumulativeReporterBase::assertionEnded(\n        AssertionStats const& assertionStats ) {\n        assert( !m_sectionStack.empty() );\n        // AssertionResult holds a pointer to a temporary DecomposedExpression,\n        // which getExpandedExpression() calls to build the expression string.\n        // Our section stack copy of the assertionResult will likely outlive the\n        // temporary, so it must be expanded or discarded now to avoid calling\n        // a destroyed object later.\n        if ( m_shouldStoreFailedAssertions &&\n             !assertionStats.assertionResult.isOk() ) {\n            static_cast<void>(\n                assertionStats.assertionResult.getExpandedExpression() );\n        }\n        if ( m_shouldStoreSuccesfulAssertions &&\n             assertionStats.assertionResult.isOk() ) {\n            static_cast<void>(\n                assertionStats.assertionResult.getExpandedExpression() );\n        }\n        SectionNode& sectionNode = *m_sectionStack.back();\n        sectionNode.assertionsAndBenchmarks.emplace_back( assertionStats );\n    }\n\n    void CumulativeReporterBase::sectionEnded( SectionStats const& sectionStats ) {\n        assert( !m_sectionStack.empty() );\n        SectionNode& node = *m_sectionStack.back();\n        node.stats = sectionStats;\n        m_sectionStack.pop_back();\n    }\n\n    void CumulativeReporterBase::testCaseEnded(\n        TestCaseStats const& testCaseStats ) {\n        auto node = Detail::make_unique<TestCaseNode>( testCaseStats );\n        assert( m_sectionStack.size() == 0 );\n        node->children.push_back( CATCH_MOVE(m_rootSection) );\n        m_testCases.push_back( CATCH_MOVE(node) );\n\n        assert( m_deepestSection );\n        m_deepestSection->stdOut = testCaseStats.stdOut;\n        m_deepestSection->stdErr = testCaseStats.stdErr;\n    }\n\n\n    void CumulativeReporterBase::testRunEnded( TestRunStats const& testRunStats ) {\n        assert(!m_testRun && \"CumulativeReporterBase assumes there can only be one test run\");\n        m_testRun = Detail::make_unique<TestRunNode>( testRunStats );\n        m_testRun->children.swap( m_testCases );\n        testRunEndedCumulative();\n    }\n\n    bool CumulativeReporterBase::SectionNode::hasAnyAssertions() const {\n        return std::any_of(\n            assertionsAndBenchmarks.begin(),\n            assertionsAndBenchmarks.end(),\n            []( Detail::AssertionOrBenchmarkResult const& res ) {\n                return res.isAssertion();\n            } );\n    }\n\n} // end namespace Catch\n\n\n\n\nnamespace Catch {\n\n    void EventListenerBase::fatalErrorEncountered( StringRef ) {}\n\n    void EventListenerBase::benchmarkPreparing( StringRef ) {}\n    void EventListenerBase::benchmarkStarting( BenchmarkInfo const& ) {}\n    void EventListenerBase::benchmarkEnded( BenchmarkStats<> const& ) {}\n    void EventListenerBase::benchmarkFailed( StringRef ) {}\n\n    void EventListenerBase::assertionStarting( AssertionInfo const& ) {}\n\n    void EventListenerBase::assertionEnded( AssertionStats const& ) {}\n    void EventListenerBase::listReporters(\n        std::vector<ReporterDescription> const& ) {}\n    void EventListenerBase::listListeners(\n        std::vector<ListenerDescription> const& ) {}\n    void EventListenerBase::listTests( std::vector<TestCaseHandle> const& ) {}\n    void EventListenerBase::listTags( std::vector<TagInfo> const& ) {}\n    void EventListenerBase::noMatchingTestCases( StringRef ) {}\n    void EventListenerBase::reportInvalidTestSpec( StringRef ) {}\n    void EventListenerBase::testRunStarting( TestRunInfo const& ) {}\n    void EventListenerBase::testCaseStarting( TestCaseInfo const& ) {}\n    void EventListenerBase::testCasePartialStarting(TestCaseInfo const&, uint64_t) {}\n    void EventListenerBase::sectionStarting( SectionInfo const& ) {}\n    void EventListenerBase::sectionEnded( SectionStats const& ) {}\n    void EventListenerBase::testCasePartialEnded(TestCaseStats const&, uint64_t) {}\n    void EventListenerBase::testCaseEnded( TestCaseStats const& ) {}\n    void EventListenerBase::testRunEnded( TestRunStats const& ) {}\n    void EventListenerBase::skipTest( TestCaseInfo const& ) {}\n} // namespace Catch\n\n\n\n\n#include <algorithm>\n#include <cfloat>\n#include <cmath>\n#include <cstdio>\n#include <ostream>\n#include <iomanip>\n\nnamespace Catch {\n\n    namespace {\n        void listTestNamesOnly(std::ostream& out,\n                               std::vector<TestCaseHandle> const& tests) {\n            for (auto const& test : tests) {\n                auto const& testCaseInfo = test.getTestCaseInfo();\n\n                if (startsWith(testCaseInfo.name, '#')) {\n                    out << '\"' << testCaseInfo.name << '\"';\n                } else {\n                    out << testCaseInfo.name;\n                }\n\n                out << '\\n';\n            }\n            out << std::flush;\n        }\n    } // end unnamed namespace\n\n\n    // Because formatting using c++ streams is stateful, drop down to C is\n    // required Alternatively we could use stringstream, but its performance\n    // is... not good.\n    std::string getFormattedDuration( double duration ) {\n        // Max exponent + 1 is required to represent the whole part\n        // + 1 for decimal point\n        // + 3 for the 3 decimal places\n        // + 1 for null terminator\n        const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;\n        char buffer[maxDoubleSize];\n\n        // Save previous errno, to prevent sprintf from overwriting it\n        ErrnoGuard guard;\n#ifdef _MSC_VER\n        size_t printedLength = static_cast<size_t>(\n            sprintf_s( buffer, \"%.3f\", duration ) );\n#else\n        size_t printedLength = static_cast<size_t>(\n            std::snprintf( buffer, maxDoubleSize, \"%.3f\", duration ) );\n#endif\n        return std::string( buffer, printedLength );\n    }\n\n    bool shouldShowDuration( IConfig const& config, double duration ) {\n        if ( config.showDurations() == ShowDurations::Always ) {\n            return true;\n        }\n        if ( config.showDurations() == ShowDurations::Never ) {\n            return false;\n        }\n        const double min = config.minDuration();\n        return min >= 0 && duration >= min;\n    }\n\n    std::string serializeFilters( std::vector<std::string> const& filters ) {\n        // We add a ' ' separator between each filter\n        size_t serialized_size = filters.size() - 1;\n        for (auto const& filter : filters) {\n            serialized_size += filter.size();\n        }\n\n        std::string serialized;\n        serialized.reserve(serialized_size);\n        bool first = true;\n\n        for (auto const& filter : filters) {\n            if (!first) {\n                serialized.push_back(' ');\n            }\n            first = false;\n            serialized.append(filter);\n        }\n\n        return serialized;\n    }\n\n    std::ostream& operator<<( std::ostream& out, lineOfChars value ) {\n        for ( size_t idx = 0; idx < CATCH_CONFIG_CONSOLE_WIDTH - 1; ++idx ) {\n            out.put( value.c );\n        }\n        return out;\n    }\n\n    void\n    defaultListReporters( std::ostream& out,\n                          std::vector<ReporterDescription> const& descriptions,\n                          Verbosity verbosity ) {\n        out << \"Available reporters:\\n\";\n        const auto maxNameLen =\n            std::max_element( descriptions.begin(),\n                              descriptions.end(),\n                              []( ReporterDescription const& lhs,\n                                  ReporterDescription const& rhs ) {\n                                  return lhs.name.size() < rhs.name.size();\n                              } )\n                ->name.size();\n\n        for ( auto const& desc : descriptions ) {\n            if ( verbosity == Verbosity::Quiet ) {\n                out << TextFlow::Column( desc.name )\n                           .indent( 2 )\n                           .width( 5 + maxNameLen )\n                    << '\\n';\n            } else {\n                out << TextFlow::Column( desc.name + ':' )\n                               .indent( 2 )\n                               .width( 5 + maxNameLen ) +\n                           TextFlow::Column( desc.description )\n                               .initialIndent( 0 )\n                               .indent( 2 )\n                               .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 )\n                    << '\\n';\n            }\n        }\n        out << '\\n' << std::flush;\n    }\n\n    void defaultListListeners( std::ostream& out,\n                               std::vector<ListenerDescription> const& descriptions ) {\n        out << \"Registered listeners:\\n\";\n\n        if(descriptions.empty()) {\n            return;\n        }\n\n        const auto maxNameLen =\n            std::max_element( descriptions.begin(),\n                              descriptions.end(),\n                              []( ListenerDescription const& lhs,\n                                  ListenerDescription const& rhs ) {\n                                  return lhs.name.size() < rhs.name.size();\n                              } )\n                ->name.size();\n\n        for ( auto const& desc : descriptions ) {\n            out << TextFlow::Column( static_cast<std::string>( desc.name ) +\n                                     ':' )\n                           .indent( 2 )\n                           .width( maxNameLen + 5 ) +\n                       TextFlow::Column( desc.description )\n                           .initialIndent( 0 )\n                           .indent( 2 )\n                           .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 )\n                << '\\n';\n        }\n\n        out << '\\n' << std::flush;\n    }\n\n    void defaultListTags( std::ostream& out,\n                          std::vector<TagInfo> const& tags,\n                          bool isFiltered ) {\n        if ( isFiltered ) {\n            out << \"Tags for matching test cases:\\n\";\n        } else {\n            out << \"All available tags:\\n\";\n        }\n\n        // minimum whitespace to pad tag counts, possibly overwritten below\n        size_t maxTagCountLen = 2;\n\n        // determine necessary padding for tag count column\n        if ( ! tags.empty() ) {\n            const auto maxTagCount =\n                std::max_element( tags.begin(),\n                                  tags.end(),\n                                  []( auto const& lhs, auto const& rhs ) {\n                                      return lhs.count < rhs.count;\n                                  } )\n                    ->count;\n            \n            // more padding necessary for 3+ digits\n            if (maxTagCount >= 100) {\n                auto numDigits = 1 + std::floor( std::log10( maxTagCount ) );\n                maxTagCountLen = static_cast<size_t>( numDigits );\n            }\n        }\n\n        for ( auto const& tagCount : tags ) {\n            ReusableStringStream rss;\n            rss << \"  \" << std::setw( maxTagCountLen ) << tagCount.count << \"  \";\n            auto str = rss.str();\n            auto wrapper = TextFlow::Column( tagCount.all() )\n                               .initialIndent( 0 )\n                               .indent( str.size() )\n                               .width( CATCH_CONFIG_CONSOLE_WIDTH - 10 );\n            out << str << wrapper << '\\n';\n        }\n        out << pluralise(tags.size(), \"tag\"_sr) << \"\\n\\n\" << std::flush;\n    }\n\n    void defaultListTests(std::ostream& out, ColourImpl* streamColour, std::vector<TestCaseHandle> const& tests, bool isFiltered, Verbosity verbosity) {\n        // We special case this to provide the equivalent of old\n        // `--list-test-names-only`, which could then be used by the\n        // `--input-file` option.\n        if (verbosity == Verbosity::Quiet) {\n            listTestNamesOnly(out, tests);\n            return;\n        }\n\n        if (isFiltered) {\n            out << \"Matching test cases:\\n\";\n        } else {\n            out << \"All available test cases:\\n\";\n        }\n\n        for (auto const& test : tests) {\n            auto const& testCaseInfo = test.getTestCaseInfo();\n            Colour::Code colour = testCaseInfo.isHidden()\n                ? Colour::SecondaryText\n                : Colour::None;\n            auto colourGuard = streamColour->guardColour( colour ).engage( out );\n\n            out << TextFlow::Column(testCaseInfo.name).indent(2) << '\\n';\n            if (verbosity >= Verbosity::High) {\n                out << TextFlow::Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << '\\n';\n            }\n            if (!testCaseInfo.tags.empty() &&\n                verbosity > Verbosity::Quiet) {\n                out << TextFlow::Column(testCaseInfo.tagsAsString()).indent(6) << '\\n';\n            }\n        }\n\n        if (isFiltered) {\n            out << pluralise(tests.size(), \"matching test case\"_sr);\n        } else {\n            out << pluralise(tests.size(), \"test case\"_sr);\n        }\n        out << \"\\n\\n\" << std::flush;\n    }\n\n    namespace {\n        class SummaryColumn {\n        public:\n            SummaryColumn( std::string suffix, Colour::Code colour ):\n                m_suffix( CATCH_MOVE( suffix ) ), m_colour( colour ) {}\n\n            SummaryColumn&& addRow( std::uint64_t count ) && {\n                std::string row = std::to_string(count);\n                auto const new_width = std::max( m_width, row.size() );\n                if ( new_width > m_width ) {\n                    for ( auto& oldRow : m_rows ) {\n                        oldRow.insert( 0, new_width - m_width, ' ' );\n                    }\n                } else {\n                    row.insert( 0, m_width - row.size(), ' ' );\n                }\n                m_width = new_width;\n                m_rows.push_back( row );\n                return std::move( *this );\n            }\n\n            std::string const& getSuffix() const { return m_suffix; }\n            Colour::Code getColour() const { return m_colour; }\n            std::string const& getRow( std::size_t index ) const {\n                return m_rows[index];\n            }\n\n        private:\n            std::string m_suffix;\n            Colour::Code m_colour;\n            std::size_t m_width = 0;\n            std::vector<std::string> m_rows;\n        };\n\n        void printSummaryRow( std::ostream& stream,\n                              ColourImpl& colour,\n                              StringRef label,\n                              std::vector<SummaryColumn> const& cols,\n                              std::size_t row ) {\n            for ( auto const& col : cols ) {\n                auto const& value = col.getRow( row );\n                auto const& suffix = col.getSuffix();\n                if ( suffix.empty() ) {\n                    stream << label << \": \";\n                    if ( value != \"0\" ) {\n                        stream << value;\n                    } else {\n                        stream << colour.guardColour( Colour::Warning )\n                               << \"- none -\";\n                    }\n                } else if ( value != \"0\" ) {\n                    stream << colour.guardColour( Colour::LightGrey ) << \" | \"\n                           << colour.guardColour( col.getColour() ) << value\n                           << ' ' << suffix;\n                }\n            }\n            stream << '\\n';\n        }\n    } // namespace\n\n    void printTestRunTotals( std::ostream& stream,\n                             ColourImpl& streamColour,\n                             Totals const& totals ) {\n        if ( totals.testCases.total() == 0 ) {\n            stream << streamColour.guardColour( Colour::Warning )\n                   << \"No tests ran\\n\";\n            return;\n        }\n\n        if ( totals.assertions.total() > 0 && totals.testCases.allPassed() ) {\n            stream << streamColour.guardColour( Colour::ResultSuccess )\n                   << \"All tests passed\";\n            stream << \" (\"\n                   << pluralise( totals.assertions.passed, \"assertion\"_sr )\n                   << \" in \"\n                   << pluralise( totals.testCases.passed, \"test case\"_sr )\n                   << ')' << '\\n';\n            return;\n        }\n\n        std::vector<SummaryColumn> columns;\n        // Don't include \"skipped assertions\" in total count\n        const auto totalAssertionCount =\n            totals.assertions.total() - totals.assertions.skipped;\n        columns.push_back( SummaryColumn( \"\", Colour::None )\n                               .addRow( totals.testCases.total() )\n                               .addRow( totalAssertionCount ) );\n        columns.push_back( SummaryColumn( \"passed\", Colour::Success )\n                               .addRow( totals.testCases.passed )\n                               .addRow( totals.assertions.passed ) );\n        columns.push_back( SummaryColumn( \"failed\", Colour::ResultError )\n                               .addRow( totals.testCases.failed )\n                               .addRow( totals.assertions.failed ) );\n        columns.push_back( SummaryColumn( \"skipped\", Colour::Skip )\n                               .addRow( totals.testCases.skipped )\n                               // Don't print \"skipped assertions\"\n                               .addRow( 0 ) );\n        columns.push_back(\n            SummaryColumn( \"failed as expected\", Colour::ResultExpectedFailure )\n                .addRow( totals.testCases.failedButOk )\n                .addRow( totals.assertions.failedButOk ) );\n        printSummaryRow( stream, streamColour, \"test cases\"_sr, columns, 0 );\n        printSummaryRow( stream, streamColour, \"assertions\"_sr, columns, 1 );\n    }\n\n} // namespace Catch\n\n\n//\n\nnamespace Catch {\n    namespace {\n        void writeSourceInfo( JsonObjectWriter& writer,\n                              SourceLineInfo const& sourceInfo ) {\n            auto source_location_writer =\n                writer.write( \"source-location\"_sr ).writeObject();\n            source_location_writer.write( \"filename\"_sr )\n                .write( sourceInfo.file );\n            source_location_writer.write( \"line\"_sr ).write( sourceInfo.line );\n        }\n\n        void writeTags( JsonArrayWriter writer, std::vector<Tag> const& tags ) {\n            for ( auto const& tag : tags ) {\n                writer.write( tag.original );\n            }\n        }\n\n        void writeProperties( JsonArrayWriter writer,\n                              TestCaseInfo const& info ) {\n            if ( info.isHidden() ) { writer.write( \"is-hidden\"_sr ); }\n            if ( info.okToFail() ) { writer.write( \"ok-to-fail\"_sr ); }\n            if ( info.expectedToFail() ) {\n                writer.write( \"expected-to-fail\"_sr );\n            }\n            if ( info.throws() ) { writer.write( \"throws\"_sr ); }\n        }\n\n    } // namespace\n\n    JsonReporter::JsonReporter( ReporterConfig&& config ):\n        StreamingReporterBase{ CATCH_MOVE( config ) } {\n\n        m_preferences.shouldRedirectStdOut = true;\n        // TBD: Do we want to report all assertions? XML reporter does\n        //      not, but for machine-parseable reporters I think the answer\n        //      should be yes.\n        m_preferences.shouldReportAllAssertions = true;\n        // We only handle assertions when they end\n        m_preferences.shouldReportAllAssertionStarts = false;\n\n        m_objectWriters.emplace( m_stream );\n        m_writers.emplace( Writer::Object );\n        auto& writer = m_objectWriters.top();\n\n        writer.write( \"version\"_sr ).write( 1 );\n\n        {\n            auto metadata_writer = writer.write( \"metadata\"_sr ).writeObject();\n            metadata_writer.write( \"name\"_sr ).write( m_config->name() );\n            metadata_writer.write( \"rng-seed\"_sr ).write( m_config->rngSeed() );\n            metadata_writer.write( \"catch2-version\"_sr )\n                .write( libraryVersion() );\n            if ( m_config->testSpec().hasFilters() ) {\n                metadata_writer.write( \"filters\"_sr )\n                    .write( m_config->testSpec() );\n            }\n        }\n    }\n\n    JsonReporter::~JsonReporter() {\n        endListing();\n        // TODO: Ensure this closes the top level object, add asserts\n        assert( m_writers.size() == 1 && \"Only the top level object should be open\" );\n        assert( m_writers.top() == Writer::Object );\n        endObject();\n        m_stream << '\\n' << std::flush;\n        assert( m_writers.empty() );\n    }\n\n    JsonArrayWriter& JsonReporter::startArray() {\n        m_arrayWriters.emplace( m_arrayWriters.top().writeArray() );\n        m_writers.emplace( Writer::Array );\n        return m_arrayWriters.top();\n    }\n    JsonArrayWriter& JsonReporter::startArray( StringRef key ) {\n        m_arrayWriters.emplace(\n            m_objectWriters.top().write( key ).writeArray() );\n        m_writers.emplace( Writer::Array );\n        return m_arrayWriters.top();\n    }\n\n    JsonObjectWriter& JsonReporter::startObject() {\n        m_objectWriters.emplace( m_arrayWriters.top().writeObject() );\n        m_writers.emplace( Writer::Object );\n        return m_objectWriters.top();\n    }\n    JsonObjectWriter& JsonReporter::startObject( StringRef key ) {\n        m_objectWriters.emplace(\n            m_objectWriters.top().write( key ).writeObject() );\n        m_writers.emplace( Writer::Object );\n        return m_objectWriters.top();\n    }\n\n    void JsonReporter::endObject() {\n        assert( isInside( Writer::Object ) );\n        m_objectWriters.pop();\n        m_writers.pop();\n    }\n    void JsonReporter::endArray() {\n        assert( isInside( Writer::Array ) );\n        m_arrayWriters.pop();\n        m_writers.pop();\n    }\n\n    bool JsonReporter::isInside( Writer writer ) {\n        return !m_writers.empty() && m_writers.top() == writer;\n    }\n\n    void JsonReporter::startListing() {\n        if ( !m_startedListing ) { startObject( \"listings\"_sr ); }\n        m_startedListing = true;\n    }\n    void JsonReporter::endListing() {\n        if ( m_startedListing ) { endObject(); }\n        m_startedListing = false;\n    }\n\n    std::string JsonReporter::getDescription() {\n        return \"Outputs listings as JSON. Test listing is Work-in-Progress!\";\n    }\n\n    void JsonReporter::testRunStarting( TestRunInfo const& runInfo ) {\n        StreamingReporterBase::testRunStarting( runInfo );\n        endListing();\n\n        assert( isInside( Writer::Object ) );\n        startObject( \"test-run\"_sr );\n        startArray( \"test-cases\"_sr );\n    }\n\n     static void writeCounts( JsonObjectWriter&& writer, Counts const& counts ) {\n        writer.write( \"passed\"_sr ).write( counts.passed );\n        writer.write( \"failed\"_sr ).write( counts.failed );\n        writer.write( \"fail-but-ok\"_sr ).write( counts.failedButOk );\n        writer.write( \"skipped\"_sr ).write( counts.skipped );\n    }\n\n    void JsonReporter::testRunEnded(TestRunStats const& runStats) {\n        assert( isInside( Writer::Array ) );\n        // End \"test-cases\"\n        endArray();\n\n        {\n            auto totals =\n                m_objectWriters.top().write( \"totals\"_sr ).writeObject();\n            writeCounts( totals.write( \"assertions\"_sr ).writeObject(),\n                         runStats.totals.assertions );\n            writeCounts( totals.write( \"test-cases\"_sr ).writeObject(),\n                         runStats.totals.testCases );\n        }\n\n        // End the \"test-run\" object\n        endObject();\n    }\n\n    void JsonReporter::testCaseStarting( TestCaseInfo const& tcInfo ) {\n        StreamingReporterBase::testCaseStarting( tcInfo );\n\n        assert( isInside( Writer::Array ) &&\n                \"We should be in the 'test-cases' array\" );\n        startObject();\n        // \"test-info\" prelude\n        {\n            auto testInfo =\n                m_objectWriters.top().write( \"test-info\"_sr ).writeObject();\n            // TODO: handle testName vs className!!\n            testInfo.write( \"name\"_sr ).write( tcInfo.name );\n            writeSourceInfo(testInfo, tcInfo.lineInfo);\n            writeTags( testInfo.write( \"tags\"_sr ).writeArray(), tcInfo.tags );\n            writeProperties( testInfo.write( \"properties\"_sr ).writeArray(),\n                             tcInfo );\n        }\n\n\n        // Start the array for individual test runs (testCasePartial pairs)\n        startArray( \"runs\"_sr );\n    }\n\n    void JsonReporter::testCaseEnded( TestCaseStats const& tcStats ) {\n        StreamingReporterBase::testCaseEnded( tcStats );\n\n        // We need to close the 'runs' array before finishing the test case\n        assert( isInside( Writer::Array ) );\n        endArray();\n\n        {\n            auto totals =\n                m_objectWriters.top().write( \"totals\"_sr ).writeObject();\n            writeCounts( totals.write( \"assertions\"_sr ).writeObject(),\n                         tcStats.totals.assertions );\n            // We do not write the test case totals, because there will always be just one test case here.\n            // TODO: overall \"result\" -> success, skip, fail here? Or in partial result?\n        }\n        // We do not write out stderr/stdout, because we instead wrote those out in partial runs\n\n        // TODO: aborting?\n\n        // And we also close this test case's object\n        assert( isInside( Writer::Object ) );\n        endObject();\n    }\n\n    void JsonReporter::testCasePartialStarting( TestCaseInfo const& /*tcInfo*/,\n                                                uint64_t index ) {\n        startObject();\n        m_objectWriters.top().write( \"run-idx\"_sr ).write( index );\n        startArray( \"path\"_sr );\n        // TODO: we want to delay most of the printing to the 'root' section\n        // TODO: childSection key name?\n    }\n\n    void JsonReporter::testCasePartialEnded( TestCaseStats const& tcStats,\n                                             uint64_t /*index*/ ) {\n        // Fixme: the top level section handles this.\n        //// path object\n        endArray();\n        if ( !tcStats.stdOut.empty() ) {\n            m_objectWriters.top()\n                .write( \"captured-stdout\"_sr )\n                .write( tcStats.stdOut );\n        }\n        if ( !tcStats.stdErr.empty() ) {\n            m_objectWriters.top()\n                .write( \"captured-stderr\"_sr )\n                .write( tcStats.stdErr );\n        }\n        {\n            auto totals =\n                m_objectWriters.top().write( \"totals\"_sr ).writeObject();\n            writeCounts( totals.write( \"assertions\"_sr ).writeObject(),\n                         tcStats.totals.assertions );\n            // We do not write the test case totals, because there will\n            // always be just one test case here.\n            // TODO: overall \"result\" -> success, skip, fail here? Or in\n            // partial result?\n        }\n        // TODO: aborting?\n        // run object\n        endObject();\n    }\n\n    void JsonReporter::sectionStarting( SectionInfo const& sectionInfo ) {\n        assert( isInside( Writer::Array ) &&\n                \"Section should always start inside an object\" );\n        // We want to nest top level sections, even though it shares name\n        // and source loc with the TEST_CASE\n        auto& sectionObject = startObject();\n        sectionObject.write( \"kind\"_sr ).write( \"section\"_sr );\n        sectionObject.write( \"name\"_sr ).write( sectionInfo.name );\n        writeSourceInfo( m_objectWriters.top(), sectionInfo.lineInfo );\n\n\n        // TBD: Do we want to create this event lazily? It would become\n        //      rather complex, but we could do it, and it would look\n        //      better for empty sections. OTOH, empty sections should\n        //      be rare.\n        startArray( \"path\"_sr );\n    }\n    void JsonReporter::sectionEnded( SectionStats const& /*sectionStats */) {\n        // End the subpath array\n        endArray();\n        // TODO: metadata\n        // TODO: what info do we have here?\n\n        // End the section object\n        endObject();\n    }\n\n    void JsonReporter::assertionEnded( AssertionStats const& assertionStats ) {\n        // TODO: There is lot of different things to handle here, but\n        //       we can fill it in later, after we show that the basic\n        //       outline and streaming reporter impl works well enough.\n        //if ( !m_config->includeSuccessfulResults()\n        //    && assertionStats.assertionResult.isOk() ) {\n        //    return;\n        //}\n        assert( isInside( Writer::Array ) );\n        auto assertionObject = m_arrayWriters.top().writeObject();\n\n        assertionObject.write( \"kind\"_sr ).write( \"assertion\"_sr );\n        writeSourceInfo( assertionObject,\n                         assertionStats.assertionResult.getSourceInfo() );\n        assertionObject.write( \"status\"_sr )\n            .write( assertionStats.assertionResult.isOk() );\n        // TODO: handling of result.\n        // TODO: messages\n        // TODO: totals?\n    }\n\n\n    void JsonReporter::benchmarkPreparing( StringRef name ) { (void)name; }\n    void JsonReporter::benchmarkStarting( BenchmarkInfo const& ) {}\n    void JsonReporter::benchmarkEnded( BenchmarkStats<> const& ) {}\n    void JsonReporter::benchmarkFailed( StringRef error ) { (void)error; }\n\n    void JsonReporter::listReporters(\n        std::vector<ReporterDescription> const& descriptions ) {\n        startListing();\n\n        auto writer =\n            m_objectWriters.top().write( \"reporters\"_sr ).writeArray();\n        for ( auto const& desc : descriptions ) {\n            auto desc_writer = writer.writeObject();\n            desc_writer.write( \"name\"_sr ).write( desc.name );\n            desc_writer.write( \"description\"_sr ).write( desc.description );\n        }\n    }\n    void JsonReporter::listListeners(\n        std::vector<ListenerDescription> const& descriptions ) {\n        startListing();\n\n        auto writer =\n            m_objectWriters.top().write( \"listeners\"_sr ).writeArray();\n\n        for ( auto const& desc : descriptions ) {\n            auto desc_writer = writer.writeObject();\n            desc_writer.write( \"name\"_sr ).write( desc.name );\n            desc_writer.write( \"description\"_sr ).write( desc.description );\n        }\n    }\n    void JsonReporter::listTests( std::vector<TestCaseHandle> const& tests ) {\n        startListing();\n\n        auto writer = m_objectWriters.top().write( \"tests\"_sr ).writeArray();\n\n        for ( auto const& test : tests ) {\n            auto desc_writer = writer.writeObject();\n            auto const& info = test.getTestCaseInfo();\n\n            desc_writer.write( \"name\"_sr ).write( info.name );\n            desc_writer.write( \"class-name\"_sr ).write( info.className );\n            {\n                auto tag_writer = desc_writer.write( \"tags\"_sr ).writeArray();\n                for ( auto const& tag : info.tags ) {\n                    tag_writer.write( tag.original );\n                }\n            }\n            writeSourceInfo( desc_writer, info.lineInfo );\n        }\n    }\n    void JsonReporter::listTags( std::vector<TagInfo> const& tags ) {\n        startListing();\n\n        auto writer = m_objectWriters.top().write( \"tags\"_sr ).writeArray();\n        for ( auto const& tag : tags ) {\n            auto tag_writer = writer.writeObject();\n            {\n                auto aliases_writer =\n                    tag_writer.write( \"aliases\"_sr ).writeArray();\n                for ( auto alias : tag.spellings ) {\n                    aliases_writer.write( alias );\n                }\n            }\n            tag_writer.write( \"count\"_sr ).write( tag.count );\n        }\n    }\n} // namespace Catch\n\n\n\n\n#include <cassert>\n#include <ctime>\n#include <algorithm>\n#include <iomanip>\n\nnamespace Catch {\n\n    namespace {\n        std::string getCurrentTimestamp() {\n            time_t rawtime;\n            std::time(&rawtime);\n\n            std::tm timeInfo = {};\n#if defined (_MSC_VER) || defined (__MINGW32__)\n            gmtime_s(&timeInfo, &rawtime);\n#elif defined (CATCH_PLATFORM_PLAYSTATION)\n            gmtime_s(&rawtime, &timeInfo);\n#elif defined (__IAR_SYSTEMS_ICC__)\n            timeInfo = *std::gmtime(&rawtime);\n#else\n            gmtime_r(&rawtime, &timeInfo);\n#endif\n\n            auto const timeStampSize = sizeof(\"2017-01-16T17:06:45Z\");\n            char timeStamp[timeStampSize];\n            const char * const fmt = \"%Y-%m-%dT%H:%M:%SZ\";\n\n            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);\n\n            return std::string(timeStamp, timeStampSize - 1);\n        }\n\n        std::string fileNameTag(std::vector<Tag> const& tags) {\n            auto it = std::find_if(begin(tags),\n                                   end(tags),\n                                   [] (Tag const& tag) {\n                                       return tag.original.size() > 0\n                                           && tag.original[0] == '#'; });\n            if (it != tags.end()) {\n                return static_cast<std::string>(\n                    it->original.substr(1, it->original.size() - 1)\n                );\n            }\n            return std::string();\n        }\n\n        // Formats the duration in seconds to 3 decimal places.\n        // This is done because some genius defined Maven Surefire schema\n        // in a way that only accepts 3 decimal places, and tools like\n        // Jenkins use that schema for validation JUnit reporter output.\n        std::string formatDuration( double seconds ) {\n            ReusableStringStream rss;\n            rss << std::fixed << std::setprecision( 3 ) << seconds;\n            return rss.str();\n        }\n\n        static void normalizeNamespaceMarkers(std::string& str) {\n            std::size_t pos = str.find( \"::\" );\n            while ( pos != std::string::npos ) {\n                str.replace( pos, 2, \".\" );\n                pos += 1;\n                pos = str.find( \"::\", pos );\n            }\n        }\n\n    } // anonymous namespace\n\n    JunitReporter::JunitReporter( ReporterConfig&& _config )\n        :   CumulativeReporterBase( CATCH_MOVE(_config) ),\n            xml( m_stream )\n        {\n            m_preferences.shouldRedirectStdOut = true;\n            m_preferences.shouldReportAllAssertions = false;\n            m_preferences.shouldReportAllAssertionStarts = false;\n            m_shouldStoreSuccesfulAssertions = false;\n        }\n\n    std::string JunitReporter::getDescription() {\n        return \"Reports test results in an XML format that looks like Ant's junitreport target\";\n    }\n\n    void JunitReporter::testRunStarting( TestRunInfo const& runInfo )  {\n        CumulativeReporterBase::testRunStarting( runInfo );\n        xml.startElement( \"testsuites\" );\n        suiteTimer.start();\n        stdOutForSuite.clear();\n        stdErrForSuite.clear();\n        unexpectedExceptions = 0;\n    }\n\n    void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) {\n        m_okToFail = testCaseInfo.okToFail();\n    }\n\n    void JunitReporter::assertionEnded( AssertionStats const& assertionStats ) {\n        if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )\n            unexpectedExceptions++;\n        CumulativeReporterBase::assertionEnded( assertionStats );\n    }\n\n    void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {\n        stdOutForSuite += testCaseStats.stdOut;\n        stdErrForSuite += testCaseStats.stdErr;\n        CumulativeReporterBase::testCaseEnded( testCaseStats );\n    }\n\n    void JunitReporter::testRunEndedCumulative() {\n        const auto suiteTime = suiteTimer.getElapsedSeconds();\n        writeRun( *m_testRun, suiteTime );\n        xml.endElement();\n    }\n\n    void JunitReporter::writeRun( TestRunNode const& testRunNode, double suiteTime ) {\n        XmlWriter::ScopedElement e = xml.scopedElement( \"testsuite\" );\n\n        TestRunStats const& stats = testRunNode.value;\n        xml.writeAttribute( \"name\"_sr, stats.runInfo.name );\n        xml.writeAttribute( \"errors\"_sr, unexpectedExceptions );\n        xml.writeAttribute( \"failures\"_sr, stats.totals.assertions.failed-unexpectedExceptions );\n        xml.writeAttribute( \"skipped\"_sr, stats.totals.assertions.skipped );\n        xml.writeAttribute( \"tests\"_sr, stats.totals.assertions.total() );\n        xml.writeAttribute( \"hostname\"_sr, \"tbd\"_sr ); // !TBD\n        if( m_config->showDurations() == ShowDurations::Never )\n            xml.writeAttribute( \"time\"_sr, \"\"_sr );\n        else\n            xml.writeAttribute( \"time\"_sr, formatDuration( suiteTime ) );\n        xml.writeAttribute( \"timestamp\"_sr, getCurrentTimestamp() );\n\n        // Write properties\n        {\n            auto properties = xml.scopedElement(\"properties\");\n            xml.scopedElement(\"property\")\n                .writeAttribute(\"name\"_sr, \"random-seed\"_sr)\n                .writeAttribute(\"value\"_sr, m_config->rngSeed());\n            if (m_config->testSpec().hasFilters()) {\n                xml.scopedElement(\"property\")\n                    .writeAttribute(\"name\"_sr, \"filters\"_sr)\n                    .writeAttribute(\"value\"_sr, m_config->testSpec());\n            }\n        }\n\n        // Write test cases\n        for( auto const& child : testRunNode.children )\n            writeTestCase( *child );\n\n        xml.scopedElement( \"system-out\" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline );\n        xml.scopedElement( \"system-err\" ).writeText( trim( stdErrForSuite ), XmlFormatting::Newline );\n    }\n\n    void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) {\n        TestCaseStats const& stats = testCaseNode.value;\n\n        // All test cases have exactly one section - which represents the\n        // test case itself. That section may have 0-n nested sections\n        assert( testCaseNode.children.size() == 1 );\n        SectionNode const& rootSection = *testCaseNode.children.front();\n\n        std::string className =\n            static_cast<std::string>( stats.testInfo->className );\n\n        if( className.empty() ) {\n            className = fileNameTag(stats.testInfo->tags);\n            if ( className.empty() ) {\n                className = \"global\";\n            }\n        }\n\n        if ( !m_config->name().empty() )\n            className = static_cast<std::string>(m_config->name()) + '.' + className;\n\n        normalizeNamespaceMarkers(className);\n\n        writeSection( className, \"\", rootSection, stats.testInfo->okToFail() );\n    }\n\n    void JunitReporter::writeSection( std::string const& className,\n                                      std::string const& rootName,\n                                      SectionNode const& sectionNode,\n                                      bool testOkToFail) {\n        std::string name = trim( sectionNode.stats.sectionInfo.name );\n        if( !rootName.empty() )\n            name = rootName + '/' + name;\n\n        if ( sectionNode.stats.assertions.total() > 0\n           || !sectionNode.stdOut.empty()\n           || !sectionNode.stdErr.empty() ) {\n            XmlWriter::ScopedElement e = xml.scopedElement( \"testcase\" );\n            if( className.empty() ) {\n                xml.writeAttribute( \"classname\"_sr, name );\n                xml.writeAttribute( \"name\"_sr, \"root\"_sr );\n            }\n            else {\n                xml.writeAttribute( \"classname\"_sr, className );\n                xml.writeAttribute( \"name\"_sr, name );\n            }\n            xml.writeAttribute( \"time\"_sr, formatDuration( sectionNode.stats.durationInSeconds ) );\n            // This is not ideal, but it should be enough to mimic gtest's\n            // junit output.\n            // Ideally the JUnit reporter would also handle `skipTest`\n            // events and write those out appropriately.\n            xml.writeAttribute( \"status\"_sr, \"run\"_sr );\n\n            if (sectionNode.stats.assertions.failedButOk) {\n                xml.scopedElement(\"skipped\")\n                    .writeAttribute(\"message\", \"TEST_CASE tagged with !mayfail\");\n            }\n\n            writeAssertions( sectionNode );\n\n\n            if( !sectionNode.stdOut.empty() )\n                xml.scopedElement( \"system-out\" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline );\n            if( !sectionNode.stdErr.empty() )\n                xml.scopedElement( \"system-err\" ).writeText( trim( sectionNode.stdErr ), XmlFormatting::Newline );\n        }\n        for( auto const& childNode : sectionNode.childSections )\n            if( className.empty() )\n                writeSection( name, \"\", *childNode, testOkToFail );\n            else\n                writeSection( className, name, *childNode, testOkToFail );\n    }\n\n    void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {\n        for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) {\n            if (assertionOrBenchmark.isAssertion()) {\n                writeAssertion(assertionOrBenchmark.asAssertion());\n            }\n        }\n    }\n\n    void JunitReporter::writeAssertion( AssertionStats const& stats ) {\n        AssertionResult const& result = stats.assertionResult;\n        if ( !result.isOk() ||\n             result.getResultType() == ResultWas::ExplicitSkip ) {\n            std::string elementName;\n            switch( result.getResultType() ) {\n                case ResultWas::ThrewException:\n                case ResultWas::FatalErrorCondition:\n                    elementName = \"error\";\n                    break;\n                case ResultWas::ExplicitFailure:\n                case ResultWas::ExpressionFailed:\n                case ResultWas::DidntThrowException:\n                    elementName = \"failure\";\n                    break;\n                case ResultWas::ExplicitSkip:\n                    elementName = \"skipped\";\n                    break;\n                // We should never see these here:\n                case ResultWas::Info:\n                case ResultWas::Warning:\n                case ResultWas::Ok:\n                case ResultWas::Unknown:\n                case ResultWas::FailureBit:\n                case ResultWas::Exception:\n                    elementName = \"internalError\";\n                    break;\n            }\n\n            XmlWriter::ScopedElement e = xml.scopedElement( elementName );\n\n            xml.writeAttribute( \"message\"_sr, result.getExpression() );\n            xml.writeAttribute( \"type\"_sr, result.getTestMacroName() );\n\n            ReusableStringStream rss;\n            if ( result.getResultType() == ResultWas::ExplicitSkip ) {\n                rss << \"SKIPPED\\n\";\n            } else {\n                rss << \"FAILED\" << \":\\n\";\n                if (result.hasExpression()) {\n                    rss << \"  \";\n                    rss << result.getExpressionInMacro();\n                    rss << '\\n';\n                }\n                if (result.hasExpandedExpression()) {\n                    rss << \"with expansion:\\n\";\n                    rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\\n';\n                }\n            }\n\n            if( result.hasMessage() )\n                rss << result.getMessage() << '\\n';\n            for( auto const& msg : stats.infoMessages )\n                if( msg.type == ResultWas::Info )\n                    rss << msg.message << '\\n';\n\n            rss << \"at \" << result.getSourceInfo();\n            xml.writeText( rss.str(), XmlFormatting::Newline );\n        }\n    }\n\n} // end namespace Catch\n\n\n\n\n#include <ostream>\n\nnamespace Catch {\n    void MultiReporter::updatePreferences(IEventListener const& reporterish) {\n        m_preferences.shouldRedirectStdOut |=\n            reporterish.getPreferences().shouldRedirectStdOut;\n        m_preferences.shouldReportAllAssertions |=\n            reporterish.getPreferences().shouldReportAllAssertions;\n        m_preferences.shouldReportAllAssertionStarts |=\n            reporterish.getPreferences().shouldReportAllAssertionStarts;\n    }\n\n    void MultiReporter::addListener( IEventListenerPtr&& listener ) {\n        updatePreferences(*listener);\n        m_reporterLikes.insert(m_reporterLikes.begin() + m_insertedListeners, CATCH_MOVE(listener) );\n        ++m_insertedListeners;\n    }\n\n    void MultiReporter::addReporter( IEventListenerPtr&& reporter ) {\n        updatePreferences(*reporter);\n\n        // We will need to output the captured stdout if there are reporters\n        // that do not want it captured.\n        // We do not consider listeners, because it is generally assumed that\n        // listeners are output-transparent, even though they can ask for stdout\n        // capture to do something with it.\n        m_haveNoncapturingReporters |= !reporter->getPreferences().shouldRedirectStdOut;\n\n        // Reporters can always be placed to the back without breaking the\n        // reporting order\n        m_reporterLikes.push_back( CATCH_MOVE( reporter ) );\n    }\n\n    void MultiReporter::noMatchingTestCases( StringRef unmatchedSpec ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->noMatchingTestCases( unmatchedSpec );\n        }\n    }\n\n    void MultiReporter::fatalErrorEncountered( StringRef error ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->fatalErrorEncountered( error );\n        }\n    }\n\n    void MultiReporter::reportInvalidTestSpec( StringRef arg ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->reportInvalidTestSpec( arg );\n        }\n    }\n\n    void MultiReporter::benchmarkPreparing( StringRef name ) {\n        for (auto& reporterish : m_reporterLikes) {\n            reporterish->benchmarkPreparing(name);\n        }\n    }\n    void MultiReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->benchmarkStarting( benchmarkInfo );\n        }\n    }\n    void MultiReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->benchmarkEnded( benchmarkStats );\n        }\n    }\n\n    void MultiReporter::benchmarkFailed( StringRef error ) {\n        for (auto& reporterish : m_reporterLikes) {\n            reporterish->benchmarkFailed(error);\n        }\n    }\n\n    void MultiReporter::testRunStarting( TestRunInfo const& testRunInfo ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->testRunStarting( testRunInfo );\n        }\n    }\n\n    void MultiReporter::testCaseStarting( TestCaseInfo const& testInfo ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->testCaseStarting( testInfo );\n        }\n    }\n\n    void\n    MultiReporter::testCasePartialStarting( TestCaseInfo const& testInfo,\n                                                uint64_t partNumber ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->testCasePartialStarting( testInfo, partNumber );\n        }\n    }\n\n    void MultiReporter::sectionStarting( SectionInfo const& sectionInfo ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->sectionStarting( sectionInfo );\n        }\n    }\n\n    void MultiReporter::assertionStarting( AssertionInfo const& assertionInfo ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->assertionStarting( assertionInfo );\n        }\n    }\n\n    void MultiReporter::assertionEnded( AssertionStats const& assertionStats ) {\n        const bool reportByDefault =\n            assertionStats.assertionResult.getResultType() != ResultWas::Ok ||\n            m_config->includeSuccessfulResults();\n\n        for ( auto & reporterish : m_reporterLikes ) {\n            if ( reportByDefault ||\n                 reporterish->getPreferences().shouldReportAllAssertions ) {\n                    reporterish->assertionEnded( assertionStats );\n            }\n        }\n    }\n\n    void MultiReporter::sectionEnded( SectionStats const& sectionStats ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->sectionEnded( sectionStats );\n        }\n    }\n\n    void MultiReporter::testCasePartialEnded( TestCaseStats const& testStats,\n                                                  uint64_t partNumber ) {\n        if ( m_preferences.shouldRedirectStdOut &&\n             m_haveNoncapturingReporters ) {\n            if ( !testStats.stdOut.empty() ) {\n                Catch::cout() << testStats.stdOut << std::flush;\n            }\n            if ( !testStats.stdErr.empty() ) {\n                Catch::cerr() << testStats.stdErr << std::flush;\n            }\n        }\n\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->testCasePartialEnded( testStats, partNumber );\n        }\n    }\n\n    void MultiReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->testCaseEnded( testCaseStats );\n        }\n    }\n\n    void MultiReporter::testRunEnded( TestRunStats const& testRunStats ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->testRunEnded( testRunStats );\n        }\n    }\n\n\n    void MultiReporter::skipTest( TestCaseInfo const& testInfo ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->skipTest( testInfo );\n        }\n    }\n\n    void MultiReporter::listReporters(std::vector<ReporterDescription> const& descriptions) {\n        for (auto& reporterish : m_reporterLikes) {\n            reporterish->listReporters(descriptions);\n        }\n    }\n\n    void MultiReporter::listListeners(\n        std::vector<ListenerDescription> const& descriptions ) {\n        for ( auto& reporterish : m_reporterLikes ) {\n            reporterish->listListeners( descriptions );\n        }\n    }\n\n    void MultiReporter::listTests(std::vector<TestCaseHandle> const& tests) {\n        for (auto& reporterish : m_reporterLikes) {\n            reporterish->listTests(tests);\n        }\n    }\n\n    void MultiReporter::listTags(std::vector<TagInfo> const& tags) {\n        for (auto& reporterish : m_reporterLikes) {\n            reporterish->listTags(tags);\n        }\n    }\n\n} // end namespace Catch\n\n\n\n\n\nnamespace Catch {\n    namespace Detail {\n\n        void registerReporterImpl( std::string const& name,\n                                   IReporterFactoryPtr reporterPtr ) {\n            CATCH_TRY {\n                getMutableRegistryHub().registerReporter(\n                    name, CATCH_MOVE( reporterPtr ) );\n            }\n            CATCH_CATCH_ALL {\n                // Do not throw when constructing global objects, instead\n                // register the exception to be processed later\n                getMutableRegistryHub().registerStartupException();\n            }\n        }\n\n        void registerListenerImpl( Detail::unique_ptr<EventListenerFactory> listenerFactory ) {\n            getMutableRegistryHub().registerListener( CATCH_MOVE(listenerFactory) );\n        }\n\n\n    } // namespace Detail\n} // namespace Catch\n\n\n\n\n#include <map>\n\nnamespace Catch {\n\n    namespace {\n        std::string createMetadataString(IConfig const& config) {\n            ReusableStringStream sstr;\n            if ( config.testSpec().hasFilters() ) {\n                sstr << \"filters='\"\n                         << config.testSpec()\n                         << \"' \";\n            }\n            sstr << \"rng-seed=\" << config.rngSeed();\n            return sstr.str();\n        }\n    }\n\n    void SonarQubeReporter::testRunStarting(TestRunInfo const& testRunInfo) {\n        CumulativeReporterBase::testRunStarting(testRunInfo);\n\n        xml.writeComment( createMetadataString( *m_config ) );\n        xml.startElement(\"testExecutions\");\n        xml.writeAttribute(\"version\"_sr, '1');\n    }\n\n    void SonarQubeReporter::writeRun( TestRunNode const& runNode ) {\n        std::map<StringRef, std::vector<TestCaseNode const*>> testsPerFile;\n\n        for ( auto const& child : runNode.children ) {\n            testsPerFile[child->value.testInfo->lineInfo.file].push_back(\n                child.get() );\n        }\n\n        for ( auto const& kv : testsPerFile ) {\n            writeTestFile( kv.first, kv.second );\n        }\n    }\n\n    void SonarQubeReporter::writeTestFile(StringRef filename, std::vector<TestCaseNode const*> const& testCaseNodes) {\n        XmlWriter::ScopedElement e = xml.scopedElement(\"file\");\n        xml.writeAttribute(\"path\"_sr, filename);\n\n        for (auto const& child : testCaseNodes)\n            writeTestCase(*child);\n    }\n\n    void SonarQubeReporter::writeTestCase(TestCaseNode const& testCaseNode) {\n        // All test cases have exactly one section - which represents the\n        // test case itself. That section may have 0-n nested sections\n        assert(testCaseNode.children.size() == 1);\n        SectionNode const& rootSection = *testCaseNode.children.front();\n        writeSection(\"\", rootSection, testCaseNode.value.testInfo->okToFail());\n    }\n\n    void SonarQubeReporter::writeSection(std::string const& rootName, SectionNode const& sectionNode, bool okToFail) {\n        std::string name = trim(sectionNode.stats.sectionInfo.name);\n        if (!rootName.empty())\n            name = rootName + '/' + name;\n\n        if ( sectionNode.stats.assertions.total() > 0\n            || !sectionNode.stdOut.empty()\n            || !sectionNode.stdErr.empty() ) {\n            XmlWriter::ScopedElement e = xml.scopedElement(\"testCase\");\n            xml.writeAttribute(\"name\"_sr, name);\n            xml.writeAttribute(\"duration\"_sr, static_cast<long>(sectionNode.stats.durationInSeconds * 1000));\n\n            writeAssertions(sectionNode, okToFail);\n        }\n\n        for (auto const& childNode : sectionNode.childSections)\n            writeSection(name, *childNode, okToFail);\n    }\n\n    void SonarQubeReporter::writeAssertions(SectionNode const& sectionNode, bool okToFail) {\n        for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) {\n            if (assertionOrBenchmark.isAssertion()) {\n                writeAssertion(assertionOrBenchmark.asAssertion(), okToFail);\n            }\n        }\n    }\n\n    void SonarQubeReporter::writeAssertion(AssertionStats const& stats, bool okToFail) {\n        AssertionResult const& result = stats.assertionResult;\n        if ( !result.isOk() ||\n             result.getResultType() == ResultWas::ExplicitSkip ) {\n            std::string elementName;\n            if (okToFail) {\n                elementName = \"skipped\";\n            } else {\n                switch (result.getResultType()) {\n                case ResultWas::ThrewException:\n                case ResultWas::FatalErrorCondition:\n                    elementName = \"error\";\n                    break;\n                case ResultWas::ExplicitFailure:\n                case ResultWas::ExpressionFailed:\n                case ResultWas::DidntThrowException:\n                    elementName = \"failure\";\n                    break;\n                case ResultWas::ExplicitSkip:\n                    elementName = \"skipped\";\n                    break;\n                    // We should never see these here:\n                case ResultWas::Info:\n                case ResultWas::Warning:\n                case ResultWas::Ok:\n                case ResultWas::Unknown:\n                case ResultWas::FailureBit:\n                case ResultWas::Exception:\n                    elementName = \"internalError\";\n                    break;\n                }\n            }\n\n            XmlWriter::ScopedElement e = xml.scopedElement(elementName);\n\n            ReusableStringStream messageRss;\n            messageRss << result.getTestMacroName() << '(' << result.getExpression() << ')';\n            xml.writeAttribute(\"message\"_sr, messageRss.str());\n\n            ReusableStringStream textRss;\n            if ( result.getResultType() == ResultWas::ExplicitSkip ) {\n                textRss << \"SKIPPED\\n\";\n            } else {\n                textRss << \"FAILED:\\n\";\n                if (result.hasExpression()) {\n                    textRss << '\\t' << result.getExpressionInMacro() << '\\n';\n                }\n                if (result.hasExpandedExpression()) {\n                    textRss << \"with expansion:\\n\\t\" << result.getExpandedExpression() << '\\n';\n                }\n            }\n\n            if (result.hasMessage())\n                textRss << result.getMessage() << '\\n';\n\n            for (auto const& msg : stats.infoMessages)\n                if (msg.type == ResultWas::Info)\n                    textRss << msg.message << '\\n';\n\n            textRss << \"at \" << result.getSourceInfo();\n            xml.writeText(textRss.str(), XmlFormatting::Newline);\n        }\n    }\n\n} // end namespace Catch\n\n\n\nnamespace Catch {\n\n    StreamingReporterBase::~StreamingReporterBase() = default;\n\n    void\n    StreamingReporterBase::testRunStarting( TestRunInfo const& _testRunInfo ) {\n        currentTestRunInfo = _testRunInfo;\n    }\n\n    void StreamingReporterBase::testRunEnded( TestRunStats const& ) {\n        currentTestCaseInfo = nullptr;\n    }\n\n} // end namespace Catch\n\n\n\n#include <algorithm>\n#include <ostream>\n\nnamespace Catch {\n\n    namespace {\n        // Yes, this has to be outside the class and namespaced by naming.\n        // Making older compiler happy is hard.\n        static constexpr StringRef tapFailedString = \"not ok\"_sr;\n        static constexpr StringRef tapPassedString = \"ok\"_sr;\n        static constexpr Colour::Code tapDimColour = Colour::FileName;\n\n        class TapAssertionPrinter {\n        public:\n            TapAssertionPrinter& operator= (TapAssertionPrinter const&) = delete;\n            TapAssertionPrinter(TapAssertionPrinter const&) = delete;\n            TapAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter, ColourImpl* colour_)\n                : stream(_stream)\n                , result(_stats.assertionResult)\n                , messages(_stats.infoMessages)\n                , itMessage(_stats.infoMessages.begin())\n                , printInfoMessages(true)\n                , counter(_counter)\n                , colourImpl( colour_ ) {}\n\n            void print() {\n                itMessage = messages.begin();\n\n                switch (result.getResultType()) {\n                case ResultWas::Ok:\n                    printResultType(tapPassedString);\n                    printOriginalExpression();\n                    printReconstructedExpression();\n                    if (!result.hasExpression())\n                        printRemainingMessages(Colour::None);\n                    else\n                        printRemainingMessages();\n                    break;\n                case ResultWas::ExpressionFailed:\n                    if (result.isOk()) {\n                        printResultType(tapPassedString);\n                    } else {\n                        printResultType(tapFailedString);\n                    }\n                    printOriginalExpression();\n                    printReconstructedExpression();\n                    if (result.isOk()) {\n                        printIssue(\" # TODO\");\n                    }\n                    printRemainingMessages();\n                    break;\n                case ResultWas::ThrewException:\n                    printResultType(tapFailedString);\n                    printIssue(\"unexpected exception with message:\"_sr);\n                    printMessage();\n                    printExpressionWas();\n                    printRemainingMessages();\n                    break;\n                case ResultWas::FatalErrorCondition:\n                    printResultType(tapFailedString);\n                    printIssue(\"fatal error condition with message:\"_sr);\n                    printMessage();\n                    printExpressionWas();\n                    printRemainingMessages();\n                    break;\n                case ResultWas::DidntThrowException:\n                    printResultType(tapFailedString);\n                    printIssue(\"expected exception, got none\"_sr);\n                    printExpressionWas();\n                    printRemainingMessages();\n                    break;\n                case ResultWas::Info:\n                    printResultType(\"info\"_sr);\n                    printMessage();\n                    printRemainingMessages();\n                    break;\n                case ResultWas::Warning:\n                    printResultType(\"warning\"_sr);\n                    printMessage();\n                    printRemainingMessages();\n                    break;\n                case ResultWas::ExplicitFailure:\n                    printResultType(tapFailedString);\n                    printIssue(\"explicitly\"_sr);\n                    printRemainingMessages(Colour::None);\n                    break;\n                case ResultWas::ExplicitSkip:\n                    printResultType(tapPassedString);\n                    printIssue(\" # SKIP\"_sr);\n                    printMessage();\n                    printRemainingMessages();\n                    break;\n                    // These cases are here to prevent compiler warnings\n                case ResultWas::Unknown:\n                case ResultWas::FailureBit:\n                case ResultWas::Exception:\n                    printResultType(\"** internal error **\"_sr);\n                    break;\n                }\n            }\n\n        private:\n            void printResultType(StringRef passOrFail) const {\n                if (!passOrFail.empty()) {\n                    stream << passOrFail << ' ' << counter << \" -\";\n                }\n            }\n\n            void printIssue(StringRef issue) const {\n                stream << ' ' << issue;\n            }\n\n            void printExpressionWas() {\n                if (result.hasExpression()) {\n                    stream << ';';\n                    stream << colourImpl->guardColour( tapDimColour )\n                           << \" expression was:\";\n                    printOriginalExpression();\n                }\n            }\n\n            void printOriginalExpression() const {\n                if (result.hasExpression()) {\n                    stream << ' ' << result.getExpression();\n                }\n            }\n\n            void printReconstructedExpression() const {\n                if (result.hasExpandedExpression()) {\n                    stream << colourImpl->guardColour( tapDimColour ) << \" for: \";\n\n                    std::string expr = result.getExpandedExpression();\n                    std::replace(expr.begin(), expr.end(), '\\n', ' ');\n                    stream << expr;\n                }\n            }\n\n            void printMessage() {\n                if (itMessage != messages.end()) {\n                    stream << \" '\" << itMessage->message << '\\'';\n                    ++itMessage;\n                }\n            }\n\n            void printRemainingMessages(Colour::Code colour = tapDimColour) {\n                if (itMessage == messages.end()) {\n                    return;\n                }\n\n                // using messages.end() directly (or auto) yields compilation error:\n                std::vector<MessageInfo>::const_iterator itEnd = messages.end();\n                const std::size_t N = static_cast<std::size_t>(itEnd - itMessage);\n\n                stream << colourImpl->guardColour( colour ) << \" with \"\n                       << pluralise( N, \"message\"_sr ) << ':';\n\n                for (; itMessage != itEnd; ) {\n                    // If this assertion is a warning ignore any INFO messages\n                    if (printInfoMessages || itMessage->type != ResultWas::Info) {\n                        stream << \" '\" << itMessage->message << '\\'';\n                        if (++itMessage != itEnd) {\n                            stream << colourImpl->guardColour(tapDimColour) << \" and\";\n                        }\n                    }\n                }\n            }\n\n        private:\n            std::ostream& stream;\n            AssertionResult const& result;\n            std::vector<MessageInfo> const& messages;\n            std::vector<MessageInfo>::const_iterator itMessage;\n            bool printInfoMessages;\n            std::size_t counter;\n            ColourImpl* colourImpl;\n        };\n\n    } // End anonymous namespace\n\n    void TAPReporter::testRunStarting( TestRunInfo const& ) {\n        if ( m_config->testSpec().hasFilters() ) {\n            m_stream << \"# filters: \" << m_config->testSpec() << '\\n';\n        }\n        m_stream << \"# rng-seed: \" << m_config->rngSeed() << '\\n'\n                 << std::flush;\n    }\n\n    void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) {\n        m_stream << \"# No test cases matched '\" << unmatchedSpec << \"'\\n\";\n    }\n\n    void TAPReporter::assertionEnded(AssertionStats const& _assertionStats) {\n        ++counter;\n\n        m_stream << \"# \" << currentTestCaseInfo->name << '\\n';\n        TapAssertionPrinter printer(m_stream, _assertionStats, counter, m_colour.get());\n        printer.print();\n\n        m_stream << '\\n' << std::flush;\n    }\n\n    void TAPReporter::testRunEnded(TestRunStats const& _testRunStats) {\n        m_stream << \"1..\" << _testRunStats.totals.assertions.total();\n        if (_testRunStats.totals.testCases.total() == 0) {\n            m_stream << \" # Skipped: No tests ran.\";\n        }\n        m_stream << \"\\n\\n\" << std::flush;\n        StreamingReporterBase::testRunEnded(_testRunStats);\n    }\n\n\n\n\n} // end namespace Catch\n\n\n\n\n#include <cassert>\n#include <ostream>\n\nnamespace Catch {\n\n    namespace {\n        // if string has a : in first line will set indent to follow it on\n        // subsequent lines\n        void printHeaderString(std::ostream& os, std::string const& _string, std::size_t indent = 0) {\n            std::size_t i = _string.find(\": \");\n            if (i != std::string::npos)\n                i += 2;\n            else\n                i = 0;\n            os << TextFlow::Column(_string)\n                  .indent(indent + i)\n                  .initialIndent(indent) << '\\n';\n        }\n\n        std::string escape(StringRef str) {\n            std::string escaped = static_cast<std::string>(str);\n            replaceInPlace(escaped, \"|\", \"||\");\n            replaceInPlace(escaped, \"'\", \"|'\");\n            replaceInPlace(escaped, \"\\n\", \"|n\");\n            replaceInPlace(escaped, \"\\r\", \"|r\");\n            replaceInPlace(escaped, \"[\", \"|[\");\n            replaceInPlace(escaped, \"]\", \"|]\");\n            return escaped;\n        }\n    } // end anonymous namespace\n\n\n    TeamCityReporter::~TeamCityReporter() = default;\n\n    void TeamCityReporter::testRunStarting( TestRunInfo const& runInfo ) {\n        m_stream << \"##teamcity[testSuiteStarted name='\" << escape( runInfo.name )\n               << \"']\\n\";\n    }\n\n    void TeamCityReporter::testRunEnded( TestRunStats const& runStats ) {\n        m_stream << \"##teamcity[testSuiteFinished name='\"\n               << escape( runStats.runInfo.name ) << \"']\\n\";\n    }\n\n    void TeamCityReporter::assertionEnded(AssertionStats const& assertionStats) {\n        AssertionResult const& result = assertionStats.assertionResult;\n        if ( !result.isOk() ||\n             result.getResultType() == ResultWas::ExplicitSkip ) {\n\n            ReusableStringStream msg;\n            if (!m_headerPrintedForThisSection)\n                printSectionHeader(msg.get());\n            m_headerPrintedForThisSection = true;\n\n            msg << result.getSourceInfo() << '\\n';\n\n            switch (result.getResultType()) {\n            case ResultWas::ExpressionFailed:\n                msg << \"expression failed\";\n                break;\n            case ResultWas::ThrewException:\n                msg << \"unexpected exception\";\n                break;\n            case ResultWas::FatalErrorCondition:\n                msg << \"fatal error condition\";\n                break;\n            case ResultWas::DidntThrowException:\n                msg << \"no exception was thrown where one was expected\";\n                break;\n            case ResultWas::ExplicitFailure:\n                msg << \"explicit failure\";\n                break;\n            case ResultWas::ExplicitSkip:\n                msg << \"explicit skip\";\n                break;\n\n                // We shouldn't get here because of the isOk() test\n            case ResultWas::Ok:\n            case ResultWas::Info:\n            case ResultWas::Warning:\n                CATCH_ERROR(\"Internal error in TeamCity reporter\");\n                // These cases are here to prevent compiler warnings\n            case ResultWas::Unknown:\n            case ResultWas::FailureBit:\n            case ResultWas::Exception:\n                CATCH_ERROR(\"Not implemented\");\n            }\n            if (assertionStats.infoMessages.size() == 1)\n                msg << \" with message:\";\n            if (assertionStats.infoMessages.size() > 1)\n                msg << \" with messages:\";\n            for (auto const& messageInfo : assertionStats.infoMessages)\n                msg << \"\\n  \\\"\" << messageInfo.message << '\"';\n\n\n            if (result.hasExpression()) {\n                msg <<\n                    \"\\n  \" << result.getExpressionInMacro() << \"\\n\"\n                    \"with expansion:\\n\"\n                    \"  \" << result.getExpandedExpression() << '\\n';\n            }\n\n            if ( result.getResultType() == ResultWas::ExplicitSkip ) {\n                m_stream << \"##teamcity[testIgnored\";\n            } else if ( currentTestCaseInfo->okToFail() ) {\n                msg << \"- failure ignore as test marked as 'ok to fail'\\n\";\n                m_stream << \"##teamcity[testIgnored\";\n            } else {\n                m_stream << \"##teamcity[testFailed\";\n            }\n            m_stream << \" name='\" << escape( currentTestCaseInfo->name ) << '\\''\n                     << \" message='\" << escape( msg.str() ) << '\\'' << \"]\\n\";\n        }\n        m_stream.flush();\n    }\n\n    void TeamCityReporter::testCaseStarting(TestCaseInfo const& testInfo) {\n        m_testTimer.start();\n        StreamingReporterBase::testCaseStarting(testInfo);\n        m_stream << \"##teamcity[testStarted name='\"\n            << escape(testInfo.name) << \"']\\n\";\n        m_stream.flush();\n    }\n\n    void TeamCityReporter::testCaseEnded(TestCaseStats const& testCaseStats) {\n        StreamingReporterBase::testCaseEnded(testCaseStats);\n        auto const& testCaseInfo = *testCaseStats.testInfo;\n        if (!testCaseStats.stdOut.empty())\n            m_stream << \"##teamcity[testStdOut name='\"\n            << escape(testCaseInfo.name)\n            << \"' out='\" << escape(testCaseStats.stdOut) << \"']\\n\";\n        if (!testCaseStats.stdErr.empty())\n            m_stream << \"##teamcity[testStdErr name='\"\n            << escape(testCaseInfo.name)\n            << \"' out='\" << escape(testCaseStats.stdErr) << \"']\\n\";\n        m_stream << \"##teamcity[testFinished name='\"\n            << escape(testCaseInfo.name) << \"' duration='\"\n            << m_testTimer.getElapsedMilliseconds() << \"']\\n\";\n        m_stream.flush();\n    }\n\n    void TeamCityReporter::printSectionHeader(std::ostream& os) {\n        assert(!m_sectionStack.empty());\n\n        if (m_sectionStack.size() > 1) {\n            os << lineOfChars('-') << '\\n';\n\n            std::vector<SectionInfo>::const_iterator\n                it = m_sectionStack.begin() + 1, // Skip first section (test case)\n                itEnd = m_sectionStack.end();\n            for (; it != itEnd; ++it)\n                printHeaderString(os, it->name);\n            os << lineOfChars('-') << '\\n';\n        }\n\n        SourceLineInfo lineInfo = m_sectionStack.front().lineInfo;\n\n        os << lineInfo << '\\n';\n        os << lineOfChars('.') << \"\\n\\n\";\n    }\n\n} // end namespace Catch\n\n\n\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch\n                              // Note that 4062 (not all labels are handled\n                              // and default is missing) is enabled\n#endif\n\nnamespace Catch {\n    XmlReporter::XmlReporter( ReporterConfig&& _config )\n    :   StreamingReporterBase( CATCH_MOVE(_config) ),\n        m_xml(m_stream)\n    {\n        m_preferences.shouldRedirectStdOut = true;\n        m_preferences.shouldReportAllAssertions = true;\n        m_preferences.shouldReportAllAssertionStarts = false;\n    }\n\n    XmlReporter::~XmlReporter() = default;\n\n    std::string XmlReporter::getDescription() {\n        return \"Reports test results as an XML document\";\n    }\n\n    std::string XmlReporter::getStylesheetRef() const {\n        return std::string();\n    }\n\n    void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) {\n        m_xml\n            .writeAttribute( \"filename\"_sr, sourceInfo.file )\n            .writeAttribute( \"line\"_sr, sourceInfo.line );\n    }\n\n    void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) {\n        StreamingReporterBase::testRunStarting( testInfo );\n        std::string stylesheetRef = getStylesheetRef();\n        if( !stylesheetRef.empty() )\n            m_xml.writeStylesheetRef( stylesheetRef );\n        m_xml.startElement(\"Catch2TestRun\")\n             .writeAttribute(\"name\"_sr, m_config->name())\n             .writeAttribute(\"rng-seed\"_sr, m_config->rngSeed())\n             .writeAttribute(\"xml-format-version\"_sr, 3)\n             .writeAttribute(\"catch2-version\"_sr, libraryVersion());\n        if ( m_config->testSpec().hasFilters() ) {\n            m_xml.writeAttribute( \"filters\"_sr, m_config->testSpec() );\n        }\n    }\n\n    void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) {\n        StreamingReporterBase::testCaseStarting(testInfo);\n        m_xml.startElement( \"TestCase\" )\n            .writeAttribute( \"name\"_sr, trim( StringRef(testInfo.name) ) )\n            .writeAttribute( \"tags\"_sr, testInfo.tagsAsString() );\n\n        writeSourceInfo( testInfo.lineInfo );\n\n        if ( m_config->showDurations() == ShowDurations::Always )\n            m_testCaseTimer.start();\n        m_xml.ensureTagClosed();\n    }\n\n    void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) {\n        StreamingReporterBase::sectionStarting( sectionInfo );\n        if( m_sectionDepth++ > 0 ) {\n            m_xml.startElement( \"Section\" )\n                .writeAttribute( \"name\"_sr, trim( StringRef(sectionInfo.name) ) );\n            writeSourceInfo( sectionInfo.lineInfo );\n            m_xml.ensureTagClosed();\n        }\n    }\n\n    void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) {\n\n        AssertionResult const& result = assertionStats.assertionResult;\n\n        bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();\n\n        if( includeResults || result.getResultType() == ResultWas::Warning ) {\n            // Print any info messages in <Info> tags.\n            for( auto const& msg : assertionStats.infoMessages ) {\n                if( msg.type == ResultWas::Info && includeResults ) {\n                    auto t = m_xml.scopedElement( \"Info\" );\n                    writeSourceInfo( msg.lineInfo );\n                    t.writeText( msg.message );\n                } else if ( msg.type == ResultWas::Warning ) {\n                    auto t = m_xml.scopedElement( \"Warning\" );\n                    writeSourceInfo( msg.lineInfo );\n                    t.writeText( msg.message );\n                }\n            }\n        }\n\n        // Drop out if result was successful but we're not printing them.\n        if ( !includeResults && result.getResultType() != ResultWas::Warning &&\n             result.getResultType() != ResultWas::ExplicitSkip ) {\n            return;\n        }\n\n        // Print the expression if there is one.\n        if( result.hasExpression() ) {\n            m_xml.startElement( \"Expression\" )\n                .writeAttribute( \"success\"_sr, result.succeeded() )\n                .writeAttribute( \"type\"_sr, result.getTestMacroName() );\n\n            writeSourceInfo( result.getSourceInfo() );\n\n            m_xml.scopedElement( \"Original\" )\n                .writeText( result.getExpression() );\n            m_xml.scopedElement( \"Expanded\" )\n                .writeText( result.getExpandedExpression() );\n        }\n\n        // And... Print a result applicable to each result type.\n        switch( result.getResultType() ) {\n            case ResultWas::ThrewException:\n                m_xml.startElement( \"Exception\" );\n                writeSourceInfo( result.getSourceInfo() );\n                m_xml.writeText( result.getMessage() );\n                m_xml.endElement();\n                break;\n            case ResultWas::FatalErrorCondition:\n                m_xml.startElement( \"FatalErrorCondition\" );\n                writeSourceInfo( result.getSourceInfo() );\n                m_xml.writeText( result.getMessage() );\n                m_xml.endElement();\n                break;\n            case ResultWas::Info:\n                m_xml.scopedElement( \"Info\" )\n                     .writeText( result.getMessage() );\n                break;\n            case ResultWas::Warning:\n                // Warning will already have been written\n                break;\n            case ResultWas::ExplicitFailure:\n                m_xml.startElement( \"Failure\" );\n                writeSourceInfo( result.getSourceInfo() );\n                m_xml.writeText( result.getMessage() );\n                m_xml.endElement();\n                break;\n            case ResultWas::ExplicitSkip:\n                m_xml.startElement( \"Skip\" );\n                writeSourceInfo( result.getSourceInfo() );\n                m_xml.writeText( result.getMessage() );\n                m_xml.endElement();\n                break;\n            default:\n                break;\n        }\n\n        if( result.hasExpression() )\n            m_xml.endElement();\n    }\n\n    void XmlReporter::sectionEnded( SectionStats const& sectionStats ) {\n        StreamingReporterBase::sectionEnded( sectionStats );\n        if ( --m_sectionDepth > 0 ) {\n            {\n                XmlWriter::ScopedElement e = m_xml.scopedElement( \"OverallResults\" );\n                e.writeAttribute( \"successes\"_sr, sectionStats.assertions.passed );\n                e.writeAttribute( \"failures\"_sr, sectionStats.assertions.failed );\n                e.writeAttribute( \"expectedFailures\"_sr, sectionStats.assertions.failedButOk );\n                e.writeAttribute( \"skipped\"_sr, sectionStats.assertions.skipped > 0 );\n\n                if ( m_config->showDurations() == ShowDurations::Always )\n                    e.writeAttribute( \"durationInSeconds\"_sr, sectionStats.durationInSeconds );\n            }\n            // Ends assertion tag\n            m_xml.endElement();\n        }\n    }\n\n    void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {\n        StreamingReporterBase::testCaseEnded( testCaseStats );\n        XmlWriter::ScopedElement e = m_xml.scopedElement( \"OverallResult\" );\n        e.writeAttribute( \"success\"_sr, testCaseStats.totals.assertions.allOk() );\n        e.writeAttribute( \"skips\"_sr, testCaseStats.totals.assertions.skipped );\n\n        if ( m_config->showDurations() == ShowDurations::Always )\n            e.writeAttribute( \"durationInSeconds\"_sr, m_testCaseTimer.getElapsedSeconds() );\n        if( !testCaseStats.stdOut.empty() )\n            m_xml.scopedElement( \"StdOut\" ).writeText( trim( StringRef(testCaseStats.stdOut) ), XmlFormatting::Newline );\n        if( !testCaseStats.stdErr.empty() )\n            m_xml.scopedElement( \"StdErr\" ).writeText( trim( StringRef(testCaseStats.stdErr) ), XmlFormatting::Newline );\n\n        m_xml.endElement();\n    }\n\n    void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) {\n        StreamingReporterBase::testRunEnded( testRunStats );\n        m_xml.scopedElement( \"OverallResults\" )\n            .writeAttribute( \"successes\"_sr, testRunStats.totals.assertions.passed )\n            .writeAttribute( \"failures\"_sr, testRunStats.totals.assertions.failed )\n            .writeAttribute( \"expectedFailures\"_sr, testRunStats.totals.assertions.failedButOk )\n            .writeAttribute( \"skips\"_sr, testRunStats.totals.assertions.skipped );\n        m_xml.scopedElement( \"OverallResultsCases\")\n            .writeAttribute( \"successes\"_sr, testRunStats.totals.testCases.passed )\n            .writeAttribute( \"failures\"_sr, testRunStats.totals.testCases.failed )\n            .writeAttribute( \"expectedFailures\"_sr, testRunStats.totals.testCases.failedButOk )\n            .writeAttribute( \"skips\"_sr, testRunStats.totals.testCases.skipped );\n        m_xml.endElement();\n    }\n\n    void XmlReporter::benchmarkPreparing( StringRef name ) {\n        m_xml.startElement(\"BenchmarkResults\")\n             .writeAttribute(\"name\"_sr, name);\n    }\n\n    void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) {\n        m_xml.writeAttribute(\"samples\"_sr, info.samples)\n            .writeAttribute(\"resamples\"_sr, info.resamples)\n            .writeAttribute(\"iterations\"_sr, info.iterations)\n            .writeAttribute(\"clockResolution\"_sr, info.clockResolution)\n            .writeAttribute(\"estimatedDuration\"_sr, info.estimatedDuration)\n            .writeComment(\"All values in nano seconds\"_sr);\n    }\n\n    void XmlReporter::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) {\n        m_xml.scopedElement(\"mean\")\n            .writeAttribute(\"value\"_sr, benchmarkStats.mean.point.count())\n            .writeAttribute(\"lowerBound\"_sr, benchmarkStats.mean.lower_bound.count())\n            .writeAttribute(\"upperBound\"_sr, benchmarkStats.mean.upper_bound.count())\n            .writeAttribute(\"ci\"_sr, benchmarkStats.mean.confidence_interval);\n        m_xml.scopedElement(\"standardDeviation\")\n            .writeAttribute(\"value\"_sr, benchmarkStats.standardDeviation.point.count())\n            .writeAttribute(\"lowerBound\"_sr, benchmarkStats.standardDeviation.lower_bound.count())\n            .writeAttribute(\"upperBound\"_sr, benchmarkStats.standardDeviation.upper_bound.count())\n            .writeAttribute(\"ci\"_sr, benchmarkStats.standardDeviation.confidence_interval);\n        m_xml.scopedElement(\"outliers\")\n            .writeAttribute(\"variance\"_sr, benchmarkStats.outlierVariance)\n            .writeAttribute(\"lowMild\"_sr, benchmarkStats.outliers.low_mild)\n            .writeAttribute(\"lowSevere\"_sr, benchmarkStats.outliers.low_severe)\n            .writeAttribute(\"highMild\"_sr, benchmarkStats.outliers.high_mild)\n            .writeAttribute(\"highSevere\"_sr, benchmarkStats.outliers.high_severe);\n        m_xml.endElement();\n    }\n\n    void XmlReporter::benchmarkFailed(StringRef error) {\n        m_xml.scopedElement(\"failed\").\n            writeAttribute(\"message\"_sr, error);\n        m_xml.endElement();\n    }\n\n    void XmlReporter::listReporters(std::vector<ReporterDescription> const& descriptions) {\n        auto outerTag = m_xml.scopedElement(\"AvailableReporters\");\n        for (auto const& reporter : descriptions) {\n            auto inner = m_xml.scopedElement(\"Reporter\");\n            m_xml.startElement(\"Name\", XmlFormatting::Indent)\n                 .writeText(reporter.name, XmlFormatting::None)\n                 .endElement(XmlFormatting::Newline);\n            m_xml.startElement(\"Description\", XmlFormatting::Indent)\n                 .writeText(reporter.description, XmlFormatting::None)\n                 .endElement(XmlFormatting::Newline);\n        }\n    }\n\n    void XmlReporter::listListeners(std::vector<ListenerDescription> const& descriptions) {\n        auto outerTag = m_xml.scopedElement( \"RegisteredListeners\" );\n        for ( auto const& listener : descriptions ) {\n            auto inner = m_xml.scopedElement( \"Listener\" );\n            m_xml.startElement( \"Name\", XmlFormatting::Indent )\n                .writeText( listener.name, XmlFormatting::None )\n                .endElement( XmlFormatting::Newline );\n            m_xml.startElement( \"Description\", XmlFormatting::Indent )\n                .writeText( listener.description, XmlFormatting::None )\n                .endElement( XmlFormatting::Newline );\n        }\n    }\n\n    void XmlReporter::listTests(std::vector<TestCaseHandle> const& tests) {\n        auto outerTag = m_xml.scopedElement(\"MatchingTests\");\n        for (auto const& test : tests) {\n            auto innerTag = m_xml.scopedElement(\"TestCase\");\n            auto const& testInfo = test.getTestCaseInfo();\n            m_xml.startElement(\"Name\", XmlFormatting::Indent)\n                 .writeText(testInfo.name, XmlFormatting::None)\n                 .endElement(XmlFormatting::Newline);\n            m_xml.startElement(\"ClassName\", XmlFormatting::Indent)\n                 .writeText(testInfo.className, XmlFormatting::None)\n                 .endElement(XmlFormatting::Newline);\n            m_xml.startElement(\"Tags\", XmlFormatting::Indent)\n                 .writeText(testInfo.tagsAsString(), XmlFormatting::None)\n                 .endElement(XmlFormatting::Newline);\n\n            auto sourceTag = m_xml.scopedElement(\"SourceInfo\");\n            m_xml.startElement(\"File\", XmlFormatting::Indent)\n                 .writeText(testInfo.lineInfo.file, XmlFormatting::None)\n                 .endElement(XmlFormatting::Newline);\n            m_xml.startElement(\"Line\", XmlFormatting::Indent)\n                 .writeText(std::to_string(testInfo.lineInfo.line), XmlFormatting::None)\n                 .endElement(XmlFormatting::Newline);\n        }\n    }\n\n    void XmlReporter::listTags(std::vector<TagInfo> const& tags) {\n        auto outerTag = m_xml.scopedElement(\"TagsFromMatchingTests\");\n        for (auto const& tag : tags) {\n            auto innerTag = m_xml.scopedElement(\"Tag\");\n            m_xml.startElement(\"Count\", XmlFormatting::Indent)\n                 .writeText(std::to_string(tag.count), XmlFormatting::None)\n                 .endElement(XmlFormatting::Newline);\n            auto aliasTag = m_xml.scopedElement(\"Aliases\");\n            for (auto const& alias : tag.spellings) {\n                m_xml.startElement(\"Alias\", XmlFormatting::Indent)\n                     .writeText(alias, XmlFormatting::None)\n                     .endElement(XmlFormatting::Newline);\n            }\n        }\n    }\n\n} // end namespace Catch\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n"
  },
  {
    "path": "test/common/catch_amalgamated.hpp",
    "content": "\n//              Copyright Catch2 Authors\n// Distributed under the Boost Software License, Version 1.0.\n//   (See accompanying file LICENSE.txt or copy at\n//        https://www.boost.org/LICENSE_1_0.txt)\n\n// SPDX-License-Identifier: BSL-1.0\n\n//  Catch v3.9.0\n//  Generated: 2025-07-24 22:00:24.654688\n//  ----------------------------------------------------------\n//  This file is an amalgamation of multiple different files.\n//  You probably shouldn't edit it directly.\n//  ----------------------------------------------------------\n#ifndef CATCH_AMALGAMATED_HPP_INCLUDED\n#define CATCH_AMALGAMATED_HPP_INCLUDED\n\n\n/** \\file\n * This is a convenience header for Catch2. It includes **all** of Catch2 headers.\n *\n * Generally the Catch2 users should use specific includes they need,\n * but this header can be used instead for ease-of-experimentation, or\n * just plain convenience, at the cost of (significantly) increased\n * compilation times.\n *\n * When a new header is added to either the top level folder, or to the\n * corresponding internal subfolder, it should be added here. Headers\n * added to the various subparts (e.g. matchers, generators, etc...),\n * should go their respective catch-all headers.\n */\n\n#ifndef CATCH_ALL_HPP_INCLUDED\n#define CATCH_ALL_HPP_INCLUDED\n\n\n\n/** \\file\n * This is a convenience header for Catch2's benchmarking. It includes\n * **all** of Catch2 headers related to benchmarking.\n *\n * Generally the Catch2 users should use specific includes they need,\n * but this header can be used instead for ease-of-experimentation, or\n * just plain convenience, at the cost of (significantly) increased\n * compilation times.\n *\n * When a new header is added to either the `benchmark` folder, or to\n * the corresponding internal (detail) subfolder, it should be added here.\n */\n\n#ifndef CATCH_BENCHMARK_ALL_HPP_INCLUDED\n#define CATCH_BENCHMARK_ALL_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_BENCHMARK_HPP_INCLUDED\n#define CATCH_BENCHMARK_HPP_INCLUDED\n\n\n\n#ifndef CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED\n#define CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED\n\n// Detect a number of compiler features - by compiler\n// The following features are defined:\n//\n// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported?\n// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported?\n// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled?\n// ****************\n// Note to maintainers: if new toggles are added please document them\n// in configuration.md, too\n// ****************\n\n// In general each macro has a _NO_<feature name> form\n// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature.\n// Many features, at point of detection, define an _INTERNAL_ macro, so they\n// can be combined, en-mass, with the _NO_ forms later.\n\n\n\n#ifndef CATCH_PLATFORM_HPP_INCLUDED\n#define CATCH_PLATFORM_HPP_INCLUDED\n\n// See e.g.:\n// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html\n#ifdef __APPLE__\n#  ifndef __has_extension\n#    define __has_extension(x) 0\n#  endif\n#  include <TargetConditionals.h>\n#  if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \\\n      (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1)\n#    define CATCH_PLATFORM_MAC\n#  elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1)\n#    define CATCH_PLATFORM_IPHONE\n#  endif\n\n#elif defined(linux) || defined(__linux) || defined(__linux__)\n#  define CATCH_PLATFORM_LINUX\n\n#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)\n#  define CATCH_PLATFORM_WINDOWS\n\n#  if defined( WINAPI_FAMILY ) && ( WINAPI_FAMILY == WINAPI_FAMILY_APP )\n#      define CATCH_PLATFORM_WINDOWS_UWP\n#  endif\n\n#elif defined(__ORBIS__) || defined(__PROSPERO__)\n#  define CATCH_PLATFORM_PLAYSTATION\n\n#endif\n\n#endif // CATCH_PLATFORM_HPP_INCLUDED\n\n#ifdef __cplusplus\n\n#  if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)\n#    define CATCH_CPP17_OR_GREATER\n#  endif\n\n#  if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)\n#    define CATCH_CPP20_OR_GREATER\n#  endif\n\n#endif\n\n// Only GCC compiler should be used in this block, so other compilers trying to\n// mask themselves as GCC should be ignored.\n#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) && !defined(__NVCOMPILER)\n#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( \"GCC diagnostic push\" )\n#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  _Pragma( \"GCC diagnostic pop\" )\n\n// This only works on GCC 9+. so we have to also add a global suppression of Wparentheses\n// for older versions of GCC.\n#    define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \\\n         _Pragma( \"GCC diagnostic ignored \\\"-Wparentheses\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \\\n         _Pragma( \"GCC diagnostic ignored \\\"-Wunused-result\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n         _Pragma( \"GCC diagnostic ignored \\\"-Wunused-variable\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \\\n         _Pragma( \"GCC diagnostic ignored \\\"-Wuseless-cast\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \\\n         _Pragma( \"GCC diagnostic ignored \\\"-Wshadow\\\"\" )\n\n#    define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P\n\n#endif\n\n#if defined(__NVCOMPILER)\n#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( \"diag push\" )\n#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  _Pragma( \"diag pop\" )\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( \"diag_suppress declared_but_not_referenced\" )\n#endif\n\n#if defined(__CUDACC__) && !defined(__clang__)\n#  ifdef __NVCC_DIAG_PRAGMA_SUPPORT__\n// New pragmas introduced in CUDA 11.5+\n#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( \"nv_diagnostic push\" )\n#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  _Pragma( \"nv_diagnostic pop\" )\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( \"nv_diag_suppress 177\" )\n#  else\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( \"diag_suppress 177\" )\n#  endif\n#endif\n\n// clang-cl defines _MSC_VER as well as __clang__, which could cause the\n// start/stop internal suppression macros to be double defined.\n#if defined(__clang__) && !defined(_MSC_VER)\n#    define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P\n#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( \"clang diagnostic push\" )\n#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  _Pragma( \"clang diagnostic pop\" )\n#endif // __clang__ && !_MSC_VER\n\n#if defined(__clang__)\n\n#    define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wexit-time-destructors\\\"\" ) \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wglobal-constructors\\\"\")\n\n#    define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wparentheses\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wunused-variable\\\"\" )\n\n#    if (__clang_major__ >= 20)\n#        define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n             _Pragma( \"clang diagnostic ignored \\\"-Wvariadic-macro-arguments-omitted\\\"\" )\n#    elif (__clang_major__ == 19)\n#        define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n\t         _Pragma( \"clang diagnostic ignored \\\"-Wc++20-extensions\\\"\" )\n#    else\n#        define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS\n             _Pragma( \"clang diagnostic ignored \\\"-Wgnu-zero-variadic-macro-arguments\\\"\" )\n#    endif\n\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wunused-template\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \\\n        _Pragma( \"clang diagnostic ignored \\\"-Wcomma\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \\\n        _Pragma( \"clang diagnostic ignored \\\"-Wshadow\\\"\" )\n\n#endif // __clang__\n\n// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug\n// which results in calls to destructors being emitted for each temporary,\n// without a matching initialization. In practice, this can result in something\n// like `std::string::~string` being called on an uninitialized value.\n//\n// For example, this code will likely segfault under IBM XL:\n// ```\n// REQUIRE(std::string(\"12\") + \"34\" == \"1234\")\n// ```\n//\n// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which\n// results in calls to the immediately evaluated lambda expressions to be\n// reported as unevaluated lambdas.\n// https://developer.nvidia.com/nvidia_bug/3321845.\n//\n// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented.\n#if defined( __ibmxl__ ) || defined( __CUDACC__ ) || defined( __NVCOMPILER )\n#    define CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P\n#endif\n\n\n\n////////////////////////////////////////////////////////////////////////////////\n// We know some environments not to support full POSIX signals\n#if defined( CATCH_PLATFORM_WINDOWS ) ||                                       \\\n    defined( CATCH_PLATFORM_PLAYSTATION ) ||                                   \\\n    defined( __CYGWIN__ ) ||                                                   \\\n    defined( __QNX__ ) ||                                                      \\\n    defined( __EMSCRIPTEN__ ) ||                                               \\\n    defined( __DJGPP__ ) ||                                                    \\\n    defined( __OS400__ )\n#    define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS\n#else\n#    define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Assume that some platforms do not support getenv.\n#if defined( CATCH_PLATFORM_WINDOWS_UWP ) ||                                   \\\n    defined( CATCH_PLATFORM_PLAYSTATION ) ||                                   \\\n    defined( _GAMING_XBOX )\n#    define CATCH_INTERNAL_CONFIG_NO_GETENV\n#else\n#    define CATCH_INTERNAL_CONFIG_GETENV\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Android somehow still does not support std::to_string\n#if defined(__ANDROID__)\n#    define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Not all Windows environments support SEH properly\n#if defined(__MINGW32__)\n#    define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// PS4\n#if defined(__ORBIS__)\n#    define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Cygwin\n#ifdef __CYGWIN__\n\n// Required for some versions of Cygwin to declare gettimeofday\n// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin\n#   define _BSD_SOURCE\n// some versions of cygwin (most) do not support std::to_string. Use the libstd check.\n// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813\n# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \\\n           && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF))\n\n#    define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING\n\n# endif\n#endif // __CYGWIN__\n\n////////////////////////////////////////////////////////////////////////////////\n// Visual C++\n#if defined(_MSC_VER)\n\n// We want to defer to nvcc-specific warning suppression if we are compiled\n// with nvcc masquerading for MSVC.\n#    if !defined( __CUDACC__ )\n#        define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n            __pragma( warning( push ) )\n#        define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n            __pragma( warning( pop ) )\n#    endif\n\n// Universal Windows platform does not support SEH\n// Or console colours (or console at all...)\n#  if defined(CATCH_PLATFORM_WINDOWS_UWP)\n#    define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32\n#  else\n#    define CATCH_INTERNAL_CONFIG_WINDOWS_SEH\n#  endif\n\n// MSVC traditional preprocessor needs some workaround for __VA_ARGS__\n// _MSVC_TRADITIONAL == 0 means new conformant preprocessor\n// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor\n#  if !defined(__clang__) // Handle Clang masquerading for msvc\n#    if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL)\n#      define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#    endif // MSVC_TRADITIONAL\n#  endif // __clang__\n\n#endif // _MSC_VER\n\n#if defined(_REENTRANT) || defined(_MSC_VER)\n// Enable async processing, as -pthread is specified or no additional linking is required\n# define CATCH_INTERNAL_CONFIG_USE_ASYNC\n#endif // _MSC_VER\n\n////////////////////////////////////////////////////////////////////////////////\n// Check if we are compiled with -fno-exceptions or equivalent\n#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)\n#  define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED\n#endif\n\n\n////////////////////////////////////////////////////////////////////////////////\n// Embarcadero C++Build\n#if defined(__BORLANDC__)\n    #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n\n// RTX is a special version of Windows that is real time.\n// This means that it is detected as Windows, but does not provide\n// the same set of capabilities as real Windows does.\n#if defined(UNDER_RTSS) || defined(RTX64_BUILD)\n    #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH\n    #define CATCH_INTERNAL_CONFIG_NO_ASYNC\n    #define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32\n#endif\n\n#if !defined(_GLIBCXX_USE_C99_MATH_TR1)\n#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER\n#endif\n\n// Various stdlib support checks that require __has_include\n#if defined(__has_include)\n  // Check if string_view is available and usable\n  #if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER)\n  #    define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW\n  #endif\n\n  // Check if optional is available and usable\n  #  if __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER)\n  #    define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL\n  #  endif // __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER)\n\n  // Check if byte is available and usable\n  #  if __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER)\n  #    include <cstddef>\n  #    if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0)\n  #      define CATCH_INTERNAL_CONFIG_CPP17_BYTE\n  #    endif\n  #  endif // __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER)\n\n  // Check if variant is available and usable\n  #  if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)\n  #    if defined(__clang__) && (__clang_major__ < 8)\n         // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852\n         // fix should be in clang 8, workaround in libstdc++ 8.2\n  #      include <ciso646>\n  #      if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)\n  #        define CATCH_CONFIG_NO_CPP17_VARIANT\n  #      else\n  #        define CATCH_INTERNAL_CONFIG_CPP17_VARIANT\n  #      endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)\n  #    else\n  #      define CATCH_INTERNAL_CONFIG_CPP17_VARIANT\n  #    endif // defined(__clang__) && (__clang_major__ < 8)\n  #  endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)\n#endif // defined(__has_include)\n\n\n#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH)\n#   define CATCH_CONFIG_WINDOWS_SEH\n#endif\n// This is set by default, because we assume that unix compilers are posix-signal-compatible by default.\n#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)\n#   define CATCH_CONFIG_POSIX_SIGNALS\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_GETENV) && !defined(CATCH_INTERNAL_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_GETENV)\n#   define CATCH_CONFIG_GETENV\n#endif\n\n#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING)\n#    define CATCH_CONFIG_CPP11_TO_STRING\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL)\n#  define CATCH_CONFIG_CPP17_OPTIONAL\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW)\n#  define CATCH_CONFIG_CPP17_STRING_VIEW\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT)\n#  define CATCH_CONFIG_CPP17_VARIANT\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE)\n#  define CATCH_CONFIG_CPP17_BYTE\n#endif\n\n\n#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)\n#  define CATCH_INTERNAL_CONFIG_NEW_CAPTURE\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE)\n#  define CATCH_CONFIG_NEW_CAPTURE\n#endif\n\n#if !defined( CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED ) && \\\n    !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) &&          \\\n    !defined( CATCH_CONFIG_NO_DISABLE_EXCEPTIONS )\n#  define CATCH_CONFIG_DISABLE_EXCEPTIONS\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN)\n#  define CATCH_CONFIG_POLYFILL_ISNAN\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC)  && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC)\n#  define CATCH_CONFIG_USE_ASYNC\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)\n#  define CATCH_CONFIG_GLOBAL_NEXTAFTER\n#endif\n\n\n// The goal of this macro is to avoid evaluation of the arguments, but\n// still have the compiler warn on problems inside...\n#if defined( CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P ) && \\\n    !defined( CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P ) && !defined(CATCH_CONFIG_USE_BUILTIN_CONSTANT_P)\n#define CATCH_CONFIG_USE_BUILTIN_CONSTANT_P\n#endif\n\n#if defined( CATCH_CONFIG_USE_BUILTIN_CONSTANT_P ) && \\\n    !defined( CATCH_CONFIG_NO_USE_BUILTIN_CONSTANT_P )\n#    define CATCH_INTERNAL_IGNORE_BUT_WARN( ... )                                              \\\n        (void)__builtin_constant_p( __VA_ARGS__ ) /* NOLINT(cppcoreguidelines-pro-type-vararg, \\\n                                                     hicpp-vararg) */\n#else\n#    define CATCH_INTERNAL_IGNORE_BUT_WARN( ... )\n#endif\n\n// Even if we do not think the compiler has that warning, we still have\n// to provide a macro that can be used by the code.\n#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION)\n#   define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION\n#endif\n#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION)\n#   define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT)\n#   define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS\n#endif\n#if !defined( CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS )\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS\n#endif\n#if !defined( CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS )\n#    define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS\n#endif\n#if !defined( CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS )\n#    define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS\n#endif\n\n#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10)\n#   undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS\n#elif defined(__clang__) && (__clang_major__ < 5)\n#   undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS\n#endif\n\n\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n#define CATCH_TRY if ((true))\n#define CATCH_CATCH_ALL if ((false))\n#define CATCH_CATCH_ANON(type) if ((false))\n#else\n#define CATCH_TRY try\n#define CATCH_CATCH_ALL catch (...)\n#define CATCH_CATCH_ANON(type) catch (type)\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR)\n#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#endif\n\n#if defined( CATCH_PLATFORM_WINDOWS ) &&       \\\n    !defined( CATCH_CONFIG_COLOUR_WIN32 ) && \\\n    !defined( CATCH_CONFIG_NO_COLOUR_WIN32 ) && \\\n    !defined( CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 )\n#    define CATCH_CONFIG_COLOUR_WIN32\n#endif\n\n#if defined( CATCH_CONFIG_SHARED_LIBRARY ) && defined( _MSC_VER ) && \\\n    !defined( CATCH_CONFIG_STATIC )\n#    ifdef Catch2_EXPORTS\n#        define CATCH_EXPORT //__declspec( dllexport ) // not needed\n#    else\n#        define CATCH_EXPORT __declspec( dllimport )\n#    endif\n#else\n#    define CATCH_EXPORT\n#endif\n\n#endif // CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED\n\n\n#ifndef CATCH_CONTEXT_HPP_INCLUDED\n#define CATCH_CONTEXT_HPP_INCLUDED\n\n\nnamespace Catch {\n\n    class IResultCapture;\n    class IConfig;\n\n    class Context {\n        IConfig const* m_config = nullptr;\n        IResultCapture* m_resultCapture = nullptr;\n\n        CATCH_EXPORT static Context* currentContext;\n        friend Context& getCurrentMutableContext();\n        friend Context const& getCurrentContext();\n        static void createContext();\n        friend void cleanUpContext();\n\n    public:\n        constexpr IResultCapture* getResultCapture() const {\n            return m_resultCapture;\n        }\n        constexpr IConfig const* getConfig() const { return m_config; }\n        constexpr void setResultCapture( IResultCapture* resultCapture ) {\n            m_resultCapture = resultCapture;\n        }\n        constexpr void setConfig( IConfig const* config ) { m_config = config; }\n\n    };\n\n    Context& getCurrentMutableContext();\n\n    inline Context const& getCurrentContext() {\n        // We duplicate the logic from `getCurrentMutableContext` here,\n        // to avoid paying the call overhead in debug mode.\n        if ( !Context::currentContext ) { Context::createContext(); }\n        // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)\n        return *Context::currentContext;\n    }\n\n    void cleanUpContext();\n\n    class SimplePcg32;\n    SimplePcg32& sharedRng();\n}\n\n#endif // CATCH_CONTEXT_HPP_INCLUDED\n\n\n#ifndef CATCH_MOVE_AND_FORWARD_HPP_INCLUDED\n#define CATCH_MOVE_AND_FORWARD_HPP_INCLUDED\n\n#include <type_traits>\n\n//! Replacement for std::move with better compile time performance\n#define CATCH_MOVE(...) static_cast<std::remove_reference_t<decltype(__VA_ARGS__)>&&>(__VA_ARGS__)\n\n//! Replacement for std::forward with better compile time performance\n#define CATCH_FORWARD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)\n\n#endif // CATCH_MOVE_AND_FORWARD_HPP_INCLUDED\n\n\n#ifndef CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED\n#define CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED\n\nnamespace Catch {\n\n    //! Used to signal that an assertion macro failed\n    struct TestFailureException{};\n    //! Used to signal that the remainder of a test should be skipped\n    struct TestSkipException {};\n\n    /**\n     * Outlines throwing of `TestFailureException` into a single TU\n     *\n     * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers.\n     */\n    [[noreturn]] void throw_test_failure_exception();\n\n    /**\n     * Outlines throwing of `TestSkipException` into a single TU\n     *\n     * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers.\n     */\n    [[noreturn]] void throw_test_skip_exception();\n\n} // namespace Catch\n\n#endif // CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED\n\n\n#ifndef CATCH_UNIQUE_NAME_HPP_INCLUDED\n#define CATCH_UNIQUE_NAME_HPP_INCLUDED\n\n\n\n\n/** \\file\n * Wrapper for the CONFIG configuration option\n *\n * When generating internal unique names, there are two options. Either\n * we mix in the current line number, or mix in an incrementing number.\n * We prefer the latter, using `__COUNTER__`, but users might want to\n * use the former.\n */\n\n#ifndef CATCH_CONFIG_COUNTER_HPP_INCLUDED\n#define CATCH_CONFIG_COUNTER_HPP_INCLUDED\n\n\n#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L )\n    #define CATCH_INTERNAL_CONFIG_COUNTER\n#endif\n\n#if defined( CATCH_INTERNAL_CONFIG_COUNTER ) && \\\n    !defined( CATCH_CONFIG_NO_COUNTER ) && \\\n    !defined( CATCH_CONFIG_COUNTER )\n#    define CATCH_CONFIG_COUNTER\n#endif\n\n\n#endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED\n#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line\n#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )\n#ifdef CATCH_CONFIG_COUNTER\n#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )\n#else\n#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )\n#endif\n\n#endif // CATCH_UNIQUE_NAME_HPP_INCLUDED\n\n\n#ifndef CATCH_INTERFACES_CAPTURE_HPP_INCLUDED\n#define CATCH_INTERFACES_CAPTURE_HPP_INCLUDED\n\n#include <string>\n\n\n\n#ifndef CATCH_STRINGREF_HPP_INCLUDED\n#define CATCH_STRINGREF_HPP_INCLUDED\n\n#include <cstddef>\n#include <string>\n#include <iosfwd>\n#include <cassert>\n\n#include <cstring>\n\nnamespace Catch {\n\n    /// A non-owning string class (similar to the forthcoming std::string_view)\n    /// Note that, because a StringRef may be a substring of another string,\n    /// it may not be null terminated.\n    class StringRef {\n    public:\n        using size_type = std::size_t;\n        using const_iterator = const char*;\n\n        static constexpr size_type npos{ static_cast<size_type>( -1 ) };\n\n    private:\n        static constexpr char const* const s_empty = \"\";\n\n        char const* m_start = s_empty;\n        size_type m_size = 0;\n\n    public: // construction\n        constexpr StringRef() noexcept = default;\n\n        StringRef( char const* rawChars ) noexcept;\n\n        constexpr StringRef( char const* rawChars, size_type size ) noexcept\n        :   m_start( rawChars ),\n            m_size( size )\n        {}\n\n        StringRef( std::string const& stdString ) noexcept\n        :   m_start( stdString.c_str() ),\n            m_size( stdString.size() )\n        {}\n\n        explicit operator std::string() const {\n            return std::string(m_start, m_size);\n        }\n\n    public: // operators\n        auto operator == ( StringRef other ) const noexcept -> bool {\n            return m_size == other.m_size\n                && (std::memcmp( m_start, other.m_start, m_size ) == 0);\n        }\n        auto operator != (StringRef other) const noexcept -> bool {\n            return !(*this == other);\n        }\n\n        constexpr auto operator[] ( size_type index ) const noexcept -> char {\n            assert(index < m_size);\n            return m_start[index];\n        }\n\n        bool operator<(StringRef rhs) const noexcept;\n\n    public: // named queries\n        constexpr auto empty() const noexcept -> bool {\n            return m_size == 0;\n        }\n        constexpr auto size() const noexcept -> size_type {\n            return m_size;\n        }\n\n        // Returns a substring of [start, start + length).\n        // If start + length > size(), then the substring is [start, size()).\n        // If start > size(), then the substring is empty.\n        constexpr StringRef substr(size_type start, size_type length) const noexcept {\n            if (start < m_size) {\n                const auto shortened_size = m_size - start;\n                return StringRef(m_start + start, (shortened_size < length) ? shortened_size : length);\n            } else {\n                return StringRef();\n            }\n        }\n\n        // Returns the current start pointer. May not be null-terminated.\n        constexpr char const* data() const noexcept {\n            return m_start;\n        }\n\n        constexpr const_iterator begin() const { return m_start; }\n        constexpr const_iterator end() const { return m_start + m_size; }\n\n\n        friend std::string& operator += (std::string& lhs, StringRef rhs);\n        friend std::ostream& operator << (std::ostream& os, StringRef str);\n        friend std::string operator+(StringRef lhs, StringRef rhs);\n\n        /**\n         * Provides a three-way comparison with rhs\n         *\n         * Returns negative number if lhs < rhs, 0 if lhs == rhs, and a positive\n         * number if lhs > rhs\n         */\n        int compare( StringRef rhs ) const;\n    };\n\n\n    constexpr auto operator \"\"_sr( char const* rawChars, std::size_t size ) noexcept -> StringRef {\n        return StringRef( rawChars, size );\n    }\n} // namespace Catch\n\nconstexpr auto operator \"\"_catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef {\n    return Catch::StringRef( rawChars, size );\n}\n\n#endif // CATCH_STRINGREF_HPP_INCLUDED\n\n\n#ifndef CATCH_RESULT_TYPE_HPP_INCLUDED\n#define CATCH_RESULT_TYPE_HPP_INCLUDED\n\nnamespace Catch {\n\n    // ResultWas::OfType enum\n    struct ResultWas { enum OfType {\n        Unknown = -1,\n        Ok = 0,\n        Info = 1,\n        Warning = 2,\n        // TODO: Should explicit skip be considered \"not OK\" (cf. isOk)? I.e., should it have the failure bit?\n        ExplicitSkip = 4,\n\n        FailureBit = 0x10,\n\n        ExpressionFailed = FailureBit | 1,\n        ExplicitFailure = FailureBit | 2,\n\n        Exception = 0x100 | FailureBit,\n\n        ThrewException = Exception | 1,\n        DidntThrowException = Exception | 2,\n\n        FatalErrorCondition = 0x200 | FailureBit\n\n    }; };\n\n    constexpr bool isOk( ResultWas::OfType resultType ) {\n        return ( resultType & ResultWas::FailureBit ) == 0;\n    }\n    constexpr bool isJustInfo( int flags ) { return flags == ResultWas::Info; }\n\n\n    // ResultDisposition::Flags enum\n    struct ResultDisposition { enum Flags {\n        Normal = 0x01,\n\n        ContinueOnFailure = 0x02,   // Failures fail test, but execution continues\n        FalseTest = 0x04,           // Prefix expression with !\n        SuppressFail = 0x08         // Failures are reported but do not fail the test\n    }; };\n\n    constexpr ResultDisposition::Flags operator|( ResultDisposition::Flags lhs,\n                                        ResultDisposition::Flags rhs ) {\n        return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) |\n                                                      static_cast<int>( rhs ) );\n    }\n\n    constexpr bool isFalseTest( int flags ) {\n        return ( flags & ResultDisposition::FalseTest ) != 0;\n    }\n    constexpr bool shouldSuppressFailure( int flags ) {\n        return ( flags & ResultDisposition::SuppressFail ) != 0;\n    }\n\n} // end namespace Catch\n\n#endif // CATCH_RESULT_TYPE_HPP_INCLUDED\n\n\n#ifndef CATCH_UNIQUE_PTR_HPP_INCLUDED\n#define CATCH_UNIQUE_PTR_HPP_INCLUDED\n\n#include <cassert>\n#include <type_traits>\n\n\nnamespace Catch {\nnamespace Detail {\n    /**\n     * A reimplementation of `std::unique_ptr` for improved compilation performance\n     *\n     * Does not support arrays nor custom deleters.\n     */\n    template <typename T>\n    class unique_ptr {\n        T* m_ptr;\n    public:\n        constexpr unique_ptr(std::nullptr_t = nullptr):\n            m_ptr{}\n        {}\n        explicit constexpr unique_ptr(T* ptr):\n            m_ptr(ptr)\n        {}\n\n        template <typename U, typename = std::enable_if_t<std::is_base_of<T, U>::value>>\n        unique_ptr(unique_ptr<U>&& from):\n            m_ptr(from.release())\n        {}\n\n        template <typename U, typename = std::enable_if_t<std::is_base_of<T, U>::value>>\n        unique_ptr& operator=(unique_ptr<U>&& from) {\n            reset(from.release());\n\n            return *this;\n        }\n\n        unique_ptr(unique_ptr const&) = delete;\n        unique_ptr& operator=(unique_ptr const&) = delete;\n\n        unique_ptr(unique_ptr&& rhs) noexcept:\n            m_ptr(rhs.m_ptr) {\n            rhs.m_ptr = nullptr;\n        }\n        unique_ptr& operator=(unique_ptr&& rhs) noexcept {\n            reset(rhs.release());\n\n            return *this;\n        }\n\n        ~unique_ptr() {\n            delete m_ptr;\n        }\n\n        T& operator*() {\n            assert(m_ptr);\n            return *m_ptr;\n        }\n        T const& operator*() const {\n            assert(m_ptr);\n            return *m_ptr;\n        }\n        T* operator->() noexcept {\n            assert(m_ptr);\n            return m_ptr;\n        }\n        T const* operator->() const noexcept {\n            assert(m_ptr);\n            return m_ptr;\n        }\n\n        T* get() { return m_ptr; }\n        T const* get() const { return m_ptr; }\n\n        void reset(T* ptr = nullptr) {\n            delete m_ptr;\n            m_ptr = ptr;\n        }\n\n        T* release() {\n            auto temp = m_ptr;\n            m_ptr = nullptr;\n            return temp;\n        }\n\n        explicit operator bool() const {\n            return m_ptr;\n        }\n\n        friend void swap(unique_ptr& lhs, unique_ptr& rhs) {\n            auto temp = lhs.m_ptr;\n            lhs.m_ptr = rhs.m_ptr;\n            rhs.m_ptr = temp;\n        }\n    };\n\n    //! Specialization to cause compile-time error for arrays\n    template <typename T>\n    class unique_ptr<T[]>;\n\n    template <typename T, typename... Args>\n    unique_ptr<T> make_unique(Args&&... args) {\n        return unique_ptr<T>(new T(CATCH_FORWARD(args)...));\n    }\n\n\n} // end namespace Detail\n} // end namespace Catch\n\n#endif // CATCH_UNIQUE_PTR_HPP_INCLUDED\n\n\n#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED\n#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_CLOCK_HPP_INCLUDED\n#define CATCH_CLOCK_HPP_INCLUDED\n\n#include <chrono>\n\nnamespace Catch {\n    namespace Benchmark {\n        using IDuration = std::chrono::nanoseconds;\n        using FDuration = std::chrono::duration<double, std::nano>;\n\n        template <typename Clock>\n        using TimePoint = typename Clock::time_point;\n\n        using default_clock = std::chrono::steady_clock;\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_CLOCK_HPP_INCLUDED\n\nnamespace Catch {\n\n    // We cannot forward declare the type with default template argument\n    // multiple times, so it is split out into a separate header so that\n    // we can prevent multiple declarations in dependees\n    template <typename Duration = Benchmark::FDuration>\n    struct BenchmarkStats;\n\n} // end namespace Catch\n\n#endif // CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED\n\nnamespace Catch {\n\n    class AssertionResult;\n    struct AssertionInfo;\n    struct SectionInfo;\n    struct SectionEndInfo;\n    struct MessageInfo;\n    struct MessageBuilder;\n    struct Counts;\n    struct AssertionReaction;\n    struct SourceLineInfo;\n\n    class ITransientExpression;\n    class IGeneratorTracker;\n\n    struct BenchmarkInfo;\n\n    namespace Generators {\n        class GeneratorUntypedBase;\n        using GeneratorBasePtr = Catch::Detail::unique_ptr<GeneratorUntypedBase>;\n    }\n\n\n    class IResultCapture {\n    public:\n        virtual ~IResultCapture();\n\n        virtual void notifyAssertionStarted( AssertionInfo const& info ) = 0;\n        virtual bool sectionStarted( StringRef sectionName,\n                                     SourceLineInfo const& sectionLineInfo,\n                                     Counts& assertions ) = 0;\n        virtual void sectionEnded( SectionEndInfo&& endInfo ) = 0;\n        virtual void sectionEndedEarly( SectionEndInfo&& endInfo ) = 0;\n\n        virtual IGeneratorTracker*\n        acquireGeneratorTracker( StringRef generatorName,\n                                 SourceLineInfo const& lineInfo ) = 0;\n        virtual IGeneratorTracker*\n        createGeneratorTracker( StringRef generatorName,\n                                SourceLineInfo lineInfo,\n                                Generators::GeneratorBasePtr&& generator ) = 0;\n\n        virtual void benchmarkPreparing( StringRef name ) = 0;\n        virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0;\n        virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0;\n        virtual void benchmarkFailed( StringRef error ) = 0;\n\n        virtual void pushScopedMessage( MessageInfo const& message ) = 0;\n        virtual void popScopedMessage( MessageInfo const& message ) = 0;\n\n        virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0;\n\n        virtual void handleFatalErrorCondition( StringRef message ) = 0;\n\n        virtual void handleExpr\n                (   AssertionInfo const& info,\n                    ITransientExpression const& expr,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleMessage\n                (   AssertionInfo const& info,\n                    ResultWas::OfType resultType,\n                    std::string&& message,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleUnexpectedExceptionNotThrown\n                (   AssertionInfo const& info,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleUnexpectedInflightException\n                (   AssertionInfo const& info,\n                    std::string&& message,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleIncomplete\n                (   AssertionInfo const& info ) = 0;\n        virtual void handleNonExpr\n                (   AssertionInfo const &info,\n                    ResultWas::OfType resultType,\n                    AssertionReaction &reaction ) = 0;\n\n\n        virtual bool lastAssertionPassed() = 0;\n\n        // Deprecated, do not use:\n        virtual std::string getCurrentTestName() const = 0;\n        virtual const AssertionResult* getLastResult() const = 0;\n        virtual void exceptionEarlyReported() = 0;\n    };\n\n    IResultCapture& getResultCapture();\n}\n\n#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED\n\n\n#ifndef CATCH_INTERFACES_CONFIG_HPP_INCLUDED\n#define CATCH_INTERFACES_CONFIG_HPP_INCLUDED\n\n\n\n#ifndef CATCH_NONCOPYABLE_HPP_INCLUDED\n#define CATCH_NONCOPYABLE_HPP_INCLUDED\n\nnamespace Catch {\n    namespace Detail {\n\n        //! Deriving classes become noncopyable and nonmovable\n        class NonCopyable {\n        public:\n            NonCopyable( NonCopyable const& ) = delete;\n            NonCopyable( NonCopyable&& ) = delete;\n            NonCopyable& operator=( NonCopyable const& ) = delete;\n            NonCopyable& operator=( NonCopyable&& ) = delete;\n\n        protected:\n            NonCopyable() noexcept = default;\n        };\n\n    } // namespace Detail\n} // namespace Catch\n\n#endif // CATCH_NONCOPYABLE_HPP_INCLUDED\n\n#include <chrono>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    enum class Verbosity {\n        Quiet = 0,\n        Normal,\n        High\n    };\n\n    struct WarnAbout { enum What {\n        Nothing = 0x00,\n        //! A test case or leaf section did not run any assertions\n        NoAssertions = 0x01,\n        //! A command line test spec matched no test cases\n        UnmatchedTestSpec = 0x02,\n    }; };\n\n    enum class ShowDurations {\n        DefaultForReporter,\n        Always,\n        Never\n    };\n    enum class TestRunOrder {\n        Declared,\n        LexicographicallySorted,\n        Randomized\n    };\n    enum class ColourMode : std::uint8_t {\n        //! Let Catch2 pick implementation based on platform detection\n        PlatformDefault,\n        //! Use ANSI colour code escapes\n        ANSI,\n        //! Use Win32 console colour API\n        Win32,\n        //! Don't use any colour\n        None\n    };\n    struct WaitForKeypress { enum When {\n        Never,\n        BeforeStart = 1,\n        BeforeExit = 2,\n        BeforeStartAndExit = BeforeStart | BeforeExit\n    }; };\n\n    class TestSpec;\n    class IStream;\n\n    class IConfig : public Detail::NonCopyable {\n    public:\n        virtual ~IConfig();\n\n        virtual bool allowThrows() const = 0;\n        virtual StringRef name() const = 0;\n        virtual bool includeSuccessfulResults() const = 0;\n        virtual bool shouldDebugBreak() const = 0;\n        virtual bool warnAboutMissingAssertions() const = 0;\n        virtual bool warnAboutUnmatchedTestSpecs() const = 0;\n        virtual bool zeroTestsCountAsSuccess() const = 0;\n        virtual int abortAfter() const = 0;\n        virtual bool showInvisibles() const = 0;\n        virtual ShowDurations showDurations() const = 0;\n        virtual double minDuration() const = 0;\n        virtual TestSpec const& testSpec() const = 0;\n        virtual bool hasTestFilters() const = 0;\n        virtual std::vector<std::string> const& getTestsOrTags() const = 0;\n        virtual TestRunOrder runOrder() const = 0;\n        virtual uint32_t rngSeed() const = 0;\n        virtual unsigned int shardCount() const = 0;\n        virtual unsigned int shardIndex() const = 0;\n        virtual ColourMode defaultColourMode() const = 0;\n        virtual std::vector<std::string> const& getSectionsToRun() const = 0;\n        virtual Verbosity verbosity() const = 0;\n\n        virtual bool skipBenchmarks() const = 0;\n        virtual bool benchmarkNoAnalysis() const = 0;\n        virtual unsigned int benchmarkSamples() const = 0;\n        virtual double benchmarkConfidenceInterval() const = 0;\n        virtual unsigned int benchmarkResamples() const = 0;\n        virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0;\n    };\n}\n\n#endif // CATCH_INTERFACES_CONFIG_HPP_INCLUDED\n\n\n#ifndef CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED\n#define CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch {\n\n    class TestCaseHandle;\n    struct TestCaseInfo;\n    class ITestCaseRegistry;\n    class IExceptionTranslatorRegistry;\n    class IExceptionTranslator;\n    class ReporterRegistry;\n    class IReporterFactory;\n    class ITagAliasRegistry;\n    class ITestInvoker;\n    class IMutableEnumValuesRegistry;\n    struct SourceLineInfo;\n\n    class StartupExceptionRegistry;\n    class EventListenerFactory;\n\n    using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>;\n\n    class IRegistryHub {\n    public:\n        virtual ~IRegistryHub(); // = default\n\n        virtual ReporterRegistry const& getReporterRegistry() const = 0;\n        virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;\n        virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0;\n        virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0;\n\n\n        virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0;\n    };\n\n    class IMutableRegistryHub {\n    public:\n        virtual ~IMutableRegistryHub(); // = default\n        virtual void registerReporter( std::string const& name, IReporterFactoryPtr factory ) = 0;\n        virtual void registerListener( Detail::unique_ptr<EventListenerFactory> factory ) = 0;\n        virtual void registerTest(Detail::unique_ptr<TestCaseInfo>&& testInfo, Detail::unique_ptr<ITestInvoker>&& invoker) = 0;\n        virtual void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) = 0;\n        virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;\n        virtual void registerStartupException() noexcept = 0;\n        virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0;\n    };\n\n    IRegistryHub const& getRegistryHub();\n    IMutableRegistryHub& getMutableRegistryHub();\n    void cleanUp();\n    std::string translateActiveException();\n\n}\n\n#endif // CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED\n\n\n#ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED\n#define CATCH_BENCHMARK_STATS_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_ESTIMATE_HPP_INCLUDED\n#define CATCH_ESTIMATE_HPP_INCLUDED\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Type>\n        struct Estimate {\n            Type point;\n            Type lower_bound;\n            Type upper_bound;\n            double confidence_interval;\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_ESTIMATE_HPP_INCLUDED\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED\n#define CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED\n\nnamespace Catch {\n    namespace Benchmark {\n        struct OutlierClassification {\n            int samples_seen = 0;\n            int low_severe = 0;     // more than 3 times IQR below Q1\n            int low_mild = 0;       // 1.5 to 3 times IQR below Q1\n            int high_mild = 0;      // 1.5 to 3 times IQR above Q3\n            int high_severe = 0;    // more than 3 times IQR above Q3\n\n            constexpr int total() const {\n                return low_severe + low_mild + high_mild + high_severe;\n            }\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_OUTLIERS_CLASSIFICATION_HPP_INCLUDED\n// The fwd decl & default specialization needs to be seen by VS2017 before\n// BenchmarkStats itself, or VS2017 will report compilation error.\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    struct BenchmarkInfo {\n        std::string name;\n        double estimatedDuration;\n        int iterations;\n        unsigned int samples;\n        unsigned int resamples;\n        double clockResolution;\n        double clockCost;\n    };\n\n    // We need to keep template parameter for backwards compatibility,\n    // but we also do not want to use the template paraneter.\n    template <class Dummy>\n    struct BenchmarkStats {\n        BenchmarkInfo info;\n\n        std::vector<Benchmark::FDuration> samples;\n        Benchmark::Estimate<Benchmark::FDuration> mean;\n        Benchmark::Estimate<Benchmark::FDuration> standardDeviation;\n        Benchmark::OutlierClassification outliers;\n        double outlierVariance;\n    };\n\n\n} // end namespace Catch\n\n#endif // CATCH_BENCHMARK_STATS_HPP_INCLUDED\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_ENVIRONMENT_HPP_INCLUDED\n#define CATCH_ENVIRONMENT_HPP_INCLUDED\n\n\nnamespace Catch {\n    namespace Benchmark {\n        struct EnvironmentEstimate {\n            FDuration mean;\n            OutlierClassification outliers;\n        };\n        struct Environment {\n            EnvironmentEstimate clock_resolution;\n            EnvironmentEstimate clock_cost;\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_ENVIRONMENT_HPP_INCLUDED\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_EXECUTION_PLAN_HPP_INCLUDED\n#define CATCH_EXECUTION_PLAN_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED\n#define CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_CHRONOMETER_HPP_INCLUDED\n#define CATCH_CHRONOMETER_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_OPTIMIZER_HPP_INCLUDED\n#define CATCH_OPTIMIZER_HPP_INCLUDED\n\n#if defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__)\n#   include <atomic> // atomic_thread_fence\n#endif\n\n\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n#if defined(__GNUC__) || defined(__clang__)\n        template <typename T>\n        inline void keep_memory(T* p) {\n            asm volatile(\"\" : : \"g\"(p) : \"memory\");\n        }\n        inline void keep_memory() {\n            asm volatile(\"\" : : : \"memory\");\n        }\n\n        namespace Detail {\n            inline void optimizer_barrier() { keep_memory(); }\n        } // namespace Detail\n#elif defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__)\n\n#if defined(_MSVC_VER)\n#pragma optimize(\"\", off)\n#elif defined(__IAR_SYSTEMS_ICC__)\n// For IAR the pragma only affects the following function\n#pragma optimize=disable\n#endif\n        template <typename T>\n        inline void keep_memory(T* p) {\n            // thanks @milleniumbug\n            *reinterpret_cast<char volatile*>(p) = *reinterpret_cast<char const volatile*>(p);\n        }\n        // TODO equivalent keep_memory()\n#if defined(_MSVC_VER)\n#pragma optimize(\"\", on)\n#endif\n\n        namespace Detail {\n            inline void optimizer_barrier() {\n                std::atomic_thread_fence(std::memory_order_seq_cst);\n            }\n        } // namespace Detail\n\n#endif\n\n        template <typename T>\n        inline void deoptimize_value(T&& x) {\n            keep_memory(&x);\n        }\n\n        template <typename Fn, typename... Args>\n        inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t<!std::is_same<void, decltype(fn(args...))>::value> {\n            deoptimize_value(CATCH_FORWARD(fn) (CATCH_FORWARD(args)...));\n        }\n\n        template <typename Fn, typename... Args>\n        inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t<std::is_same<void, decltype(fn(args...))>::value> {\n            CATCH_FORWARD((fn)) (CATCH_FORWARD(args)...);\n        }\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_OPTIMIZER_HPP_INCLUDED\n\n\n#ifndef CATCH_META_HPP_INCLUDED\n#define CATCH_META_HPP_INCLUDED\n\n#include <type_traits>\n\nnamespace Catch {\n    template <typename>\n    struct true_given : std::true_type {};\n\n    struct is_callable_tester {\n        template <typename Fun, typename... Args>\n        static true_given<decltype(std::declval<Fun>()(std::declval<Args>()...))> test(int);\n        template <typename...>\n        static std::false_type test(...);\n    };\n\n    template <typename T>\n    struct is_callable;\n\n    template <typename Fun, typename... Args>\n    struct is_callable<Fun(Args...)> : decltype(is_callable_tester::test<Fun, Args...>(0)) {};\n\n\n#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703\n    // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is\n    // replaced with std::invoke_result here.\n    template <typename Func, typename... U>\n    using FunctionReturnType = std::remove_reference_t<std::remove_cv_t<std::invoke_result_t<Func, U...>>>;\n#else\n    template <typename Func, typename... U>\n    using FunctionReturnType = std::remove_reference_t<std::remove_cv_t<std::result_of_t<Func(U...)>>>;\n#endif\n\n} // namespace Catch\n\nnamespace mpl_{\n    struct na;\n}\n\n#endif // CATCH_META_HPP_INCLUDED\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            struct ChronometerConcept {\n                virtual void start() = 0;\n                virtual void finish() = 0;\n                virtual ~ChronometerConcept(); // = default;\n\n                ChronometerConcept() = default;\n                ChronometerConcept(ChronometerConcept const&) = default;\n                ChronometerConcept& operator=(ChronometerConcept const&) = default;\n            };\n            template <typename Clock>\n            struct ChronometerModel final : public ChronometerConcept {\n                void start() override { started = Clock::now(); }\n                void finish() override { finished = Clock::now(); }\n\n                IDuration elapsed() const {\n                    return std::chrono::duration_cast<std::chrono::nanoseconds>(\n                        finished - started );\n                }\n\n                TimePoint<Clock> started;\n                TimePoint<Clock> finished;\n            };\n        } // namespace Detail\n\n        struct Chronometer {\n        public:\n            template <typename Fun>\n            void measure(Fun&& fun) { measure(CATCH_FORWARD(fun), is_callable<Fun(int)>()); }\n\n            int runs() const { return repeats; }\n\n            Chronometer(Detail::ChronometerConcept& meter, int repeats_)\n                : impl(&meter)\n                , repeats(repeats_) {}\n\n        private:\n            template <typename Fun>\n            void measure(Fun&& fun, std::false_type) {\n                measure([&fun](int) { return fun(); }, std::true_type());\n            }\n\n            template <typename Fun>\n            void measure(Fun&& fun, std::true_type) {\n                Detail::optimizer_barrier();\n                impl->start();\n                for (int i = 0; i < repeats; ++i) invoke_deoptimized(fun, i);\n                impl->finish();\n                Detail::optimizer_barrier();\n            }\n\n            Detail::ChronometerConcept* impl;\n            int repeats;\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_CHRONOMETER_HPP_INCLUDED\n\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename T, typename U>\n            static constexpr bool is_related_v = std::is_same<std::decay_t<T>, std::decay_t<U>>::value;\n\n            /// We need to reinvent std::function because every piece of code that might add overhead\n            /// in a measurement context needs to have consistent performance characteristics so that we\n            /// can account for it in the measurement.\n            /// Implementations of std::function with optimizations that aren't always applicable, like\n            /// small buffer optimizations, are not uncommon.\n            /// This is effectively an implementation of std::function without any such optimizations;\n            /// it may be slow, but it is consistently slow.\n            struct BenchmarkFunction {\n            private:\n                struct callable {\n                    virtual void call(Chronometer meter) const = 0;\n                    virtual ~callable(); // = default;\n\n                    callable() = default;\n                    callable(callable&&) = default;\n                    callable& operator=(callable&&) = default;\n                };\n                template <typename Fun>\n                struct model : public callable {\n                    model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {}\n                    model(Fun const& fun_) : fun(fun_) {}\n\n                    void call(Chronometer meter) const override {\n                        call(meter, is_callable<Fun(Chronometer)>());\n                    }\n                    void call(Chronometer meter, std::true_type) const {\n                        fun(meter);\n                    }\n                    void call(Chronometer meter, std::false_type) const {\n                        meter.measure(fun);\n                    }\n\n                    Fun fun;\n                };\n\n            public:\n                BenchmarkFunction();\n\n                template <typename Fun,\n                    std::enable_if_t<!is_related_v<Fun, BenchmarkFunction>, int> = 0>\n                    BenchmarkFunction(Fun&& fun)\n                    : f(new model<std::decay_t<Fun>>(CATCH_FORWARD(fun))) {}\n\n                BenchmarkFunction( BenchmarkFunction&& that ) noexcept:\n                    f( CATCH_MOVE( that.f ) ) {}\n\n                BenchmarkFunction&\n                operator=( BenchmarkFunction&& that ) noexcept {\n                    f = CATCH_MOVE( that.f );\n                    return *this;\n                }\n\n                void operator()(Chronometer meter) const { f->call(meter); }\n\n            private:\n                Catch::Detail::unique_ptr<callable> f;\n            };\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_REPEAT_HPP_INCLUDED\n#define CATCH_REPEAT_HPP_INCLUDED\n\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Fun>\n            struct repeater {\n                void operator()(int k) const {\n                    for (int i = 0; i < k; ++i) {\n                        fun();\n                    }\n                }\n                Fun fun;\n            };\n            template <typename Fun>\n            repeater<std::decay_t<Fun>> repeat(Fun&& fun) {\n                return { CATCH_FORWARD(fun) };\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_REPEAT_HPP_INCLUDED\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED\n#define CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_MEASURE_HPP_INCLUDED\n#define CATCH_MEASURE_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_COMPLETE_INVOKE_HPP_INCLUDED\n#define CATCH_COMPLETE_INVOKE_HPP_INCLUDED\n\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename T>\n            struct CompleteType { using type = T; };\n            template <>\n            struct CompleteType<void> { struct type {}; };\n\n            template <typename T>\n            using CompleteType_t = typename CompleteType<T>::type;\n\n            template <typename Result>\n            struct CompleteInvoker {\n                template <typename Fun, typename... Args>\n                static Result invoke(Fun&& fun, Args&&... args) {\n                    return CATCH_FORWARD(fun)(CATCH_FORWARD(args)...);\n                }\n            };\n            template <>\n            struct CompleteInvoker<void> {\n                template <typename Fun, typename... Args>\n                static CompleteType_t<void> invoke(Fun&& fun, Args&&... args) {\n                    CATCH_FORWARD(fun)(CATCH_FORWARD(args)...);\n                    return {};\n                }\n            };\n\n            // invoke and not return void :(\n            template <typename Fun, typename... Args>\n            CompleteType_t<FunctionReturnType<Fun, Args...>> complete_invoke(Fun&& fun, Args&&... args) {\n                return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...);\n            }\n\n        } // namespace Detail\n\n        template <typename Fun>\n        Detail::CompleteType_t<FunctionReturnType<Fun>> user_code(Fun&& fun) {\n            return Detail::complete_invoke(CATCH_FORWARD(fun));\n        }\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_COMPLETE_INVOKE_HPP_INCLUDED\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_TIMING_HPP_INCLUDED\n#define CATCH_TIMING_HPP_INCLUDED\n\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Result>\n        struct Timing {\n            IDuration elapsed;\n            Result result;\n            int iterations;\n        };\n        template <typename Func, typename... Args>\n        using TimingOf = Timing<Detail::CompleteType_t<FunctionReturnType<Func, Args...>>>;\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_TIMING_HPP_INCLUDED\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Clock, typename Fun, typename... Args>\n            TimingOf<Fun, Args...> measure(Fun&& fun, Args&&... args) {\n                auto start = Clock::now();\n                auto&& r = Detail::complete_invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...);\n                auto end = Clock::now();\n                auto delta = end - start;\n                return { delta, CATCH_FORWARD(r), 1 };\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_MEASURE_HPP_INCLUDED\n\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Clock, typename Fun>\n            TimingOf<Fun, int> measure_one(Fun&& fun, int iters, std::false_type) {\n                return Detail::measure<Clock>(fun, iters);\n            }\n            template <typename Clock, typename Fun>\n            TimingOf<Fun, Chronometer> measure_one(Fun&& fun, int iters, std::true_type) {\n                Detail::ChronometerModel<Clock> meter;\n                auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters));\n\n                return { meter.elapsed(), CATCH_MOVE(result), iters };\n            }\n\n            template <typename Clock, typename Fun>\n            using run_for_at_least_argument_t = std::conditional_t<is_callable<Fun(Chronometer)>::value, Chronometer, int>;\n\n\n            [[noreturn]]\n            void throw_optimized_away_error();\n\n            template <typename Clock, typename Fun>\n            TimingOf<Fun, run_for_at_least_argument_t<Clock, Fun>>\n                run_for_at_least(IDuration how_long,\n                                 const int initial_iterations,\n                                 Fun&& fun) {\n                auto iters = initial_iterations;\n                while (iters < (1 << 30)) {\n                    auto&& Timing = measure_one<Clock>(fun, iters, is_callable<Fun(Chronometer)>());\n\n                    if (Timing.elapsed >= how_long) {\n                        return { Timing.elapsed, CATCH_MOVE(Timing.result), iters };\n                    }\n                    iters *= 2;\n                }\n                throw_optimized_away_error();\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED\n\n#include <vector>\n\nnamespace Catch {\n    namespace Benchmark {\n        struct ExecutionPlan {\n            int iterations_per_sample;\n            FDuration estimated_duration;\n            Detail::BenchmarkFunction benchmark;\n            FDuration warmup_time;\n            int warmup_iterations;\n\n            template <typename Clock>\n            std::vector<FDuration> run(const IConfig &cfg, Environment env) const {\n                // warmup a bit\n                Detail::run_for_at_least<Clock>(\n                    std::chrono::duration_cast<IDuration>( warmup_time ),\n                    warmup_iterations,\n                    Detail::repeat( []() { return Clock::now(); } )\n                );\n\n                std::vector<FDuration> times;\n                const auto num_samples = cfg.benchmarkSamples();\n                times.reserve( num_samples );\n                for ( size_t i = 0; i < num_samples; ++i ) {\n                    Detail::ChronometerModel<Clock> model;\n                    this->benchmark( Chronometer( model, iterations_per_sample ) );\n                    auto sample_time = model.elapsed() - env.clock_cost.mean;\n                    if ( sample_time < FDuration::zero() ) {\n                        sample_time = FDuration::zero();\n                    }\n                    times.push_back(sample_time / iterations_per_sample);\n                }\n                return times;\n            }\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_EXECUTION_PLAN_HPP_INCLUDED\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_ESTIMATE_CLOCK_HPP_INCLUDED\n#define CATCH_ESTIMATE_CLOCK_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_STATS_HPP_INCLUDED\n#define CATCH_STATS_HPP_INCLUDED\n\n\n#include <vector>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            using sample = std::vector<double>;\n\n            double weighted_average_quantile( int k,\n                                              int q,\n                                              double* first,\n                                              double* last );\n\n            OutlierClassification\n            classify_outliers( double const* first, double const* last );\n\n            double mean( double const* first, double const* last );\n\n            double normal_cdf( double x );\n\n            double erfc_inv(double x);\n\n            double normal_quantile(double p);\n\n            Estimate<double>\n            bootstrap( double confidence_level,\n                       double* first,\n                       double* last,\n                       sample const& resample,\n                       double ( *estimator )( double const*, double const* ) );\n\n            struct bootstrap_analysis {\n                Estimate<double> mean;\n                Estimate<double> standard_deviation;\n                double outlier_variance;\n            };\n\n            bootstrap_analysis analyse_samples(double confidence_level,\n                                               unsigned int n_resamples,\n                                               double* first,\n                                               double* last);\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_STATS_HPP_INCLUDED\n\n#include <algorithm>\n#include <vector>\n#include <cmath>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Clock>\n            std::vector<double> resolution(int k) {\n                const size_t points = static_cast<size_t>( k + 1 );\n                // To avoid overhead from the branch inside vector::push_back,\n                // we allocate them all and then overwrite.\n                std::vector<TimePoint<Clock>> times(points);\n                for ( auto& time : times ) {\n                    time = Clock::now();\n                }\n\n                std::vector<double> deltas;\n                deltas.reserve(static_cast<size_t>(k));\n                for ( size_t idx = 1; idx < points; ++idx ) {\n                    deltas.push_back( static_cast<double>(\n                        ( times[idx] - times[idx - 1] ).count() ) );\n                }\n\n                return deltas;\n            }\n\n            constexpr auto warmup_iterations = 10000;\n            constexpr auto warmup_time = std::chrono::milliseconds(100);\n            constexpr auto minimum_ticks = 1000;\n            constexpr auto warmup_seed = 10000;\n            constexpr auto clock_resolution_estimation_time = std::chrono::milliseconds(500);\n            constexpr auto clock_cost_estimation_time_limit = std::chrono::seconds(1);\n            constexpr auto clock_cost_estimation_tick_limit = 100000;\n            constexpr auto clock_cost_estimation_time = std::chrono::milliseconds(10);\n            constexpr auto clock_cost_estimation_iterations = 10000;\n\n            template <typename Clock>\n            int warmup() {\n                return run_for_at_least<Clock>(warmup_time, warmup_seed, &resolution<Clock>)\n                    .iterations;\n            }\n            template <typename Clock>\n            EnvironmentEstimate estimate_clock_resolution(int iterations) {\n                auto r = run_for_at_least<Clock>(clock_resolution_estimation_time, iterations, &resolution<Clock>)\n                    .result;\n                return {\n                    FDuration(mean(r.data(), r.data() + r.size())),\n                    classify_outliers(r.data(), r.data() + r.size()),\n                };\n            }\n            template <typename Clock>\n            EnvironmentEstimate estimate_clock_cost(FDuration resolution) {\n                auto time_limit = (std::min)(\n                    resolution * clock_cost_estimation_tick_limit,\n                    FDuration(clock_cost_estimation_time_limit));\n                auto time_clock = [](int k) {\n                    return Detail::measure<Clock>([k] {\n                        for (int i = 0; i < k; ++i) {\n                            volatile auto ignored = Clock::now();\n                            (void)ignored;\n                        }\n                    }).elapsed;\n                };\n                time_clock(1);\n                int iters = clock_cost_estimation_iterations;\n                auto&& r = run_for_at_least<Clock>(clock_cost_estimation_time, iters, time_clock);\n                std::vector<double> times;\n                int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed));\n                times.reserve(static_cast<size_t>(nsamples));\n                for ( int s = 0; s < nsamples; ++s ) {\n                    times.push_back( static_cast<double>(\n                        ( time_clock( r.iterations ) / r.iterations )\n                            .count() ) );\n                }\n                return {\n                    FDuration(mean(times.data(), times.data() + times.size())),\n                    classify_outliers(times.data(), times.data() + times.size()),\n                };\n            }\n\n            template <typename Clock>\n            Environment measure_environment() {\n#if defined(__clang__)\n#    pragma clang diagnostic push\n#    pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#endif\n                static Catch::Detail::unique_ptr<Environment> env;\n#if defined(__clang__)\n#    pragma clang diagnostic pop\n#endif\n                if (env) {\n                    return *env;\n                }\n\n                auto iters = Detail::warmup<Clock>();\n                auto resolution = Detail::estimate_clock_resolution<Clock>(iters);\n                auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean);\n\n                env = Catch::Detail::make_unique<Environment>( Environment{resolution, cost} );\n                return *env;\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_ESTIMATE_CLOCK_HPP_INCLUDED\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_ANALYSE_HPP_INCLUDED\n#define CATCH_ANALYSE_HPP_INCLUDED\n\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED\n#define CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED\n\n\n#include <vector>\n\nnamespace Catch {\n    namespace Benchmark {\n        struct SampleAnalysis {\n            std::vector<FDuration> samples;\n            Estimate<FDuration> mean;\n            Estimate<FDuration> standard_deviation;\n            OutlierClassification outliers;\n            double outlier_variance;\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED\n\n\nnamespace Catch {\n    class IConfig;\n\n    namespace Benchmark {\n        namespace Detail {\n            SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last);\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_ANALYSE_HPP_INCLUDED\n\n#include <algorithm>\n#include <chrono>\n#include <exception>\n#include <string>\n#include <cmath>\n\nnamespace Catch {\n    namespace Benchmark {\n        struct Benchmark {\n            Benchmark(std::string&& benchmarkName)\n                : name(CATCH_MOVE(benchmarkName)) {}\n\n            template <class FUN>\n            Benchmark(std::string&& benchmarkName , FUN &&func)\n                : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {}\n\n            template <typename Clock>\n            ExecutionPlan prepare(const IConfig &cfg, Environment env) {\n                auto min_time = env.clock_resolution.mean * Detail::minimum_ticks;\n                auto run_time = std::max(min_time, std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime()));\n                auto&& test = Detail::run_for_at_least<Clock>(std::chrono::duration_cast<IDuration>(run_time), 1, fun);\n                int new_iters = static_cast<int>(std::ceil(min_time * test.iterations / test.elapsed));\n                return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), CATCH_MOVE(fun), std::chrono::duration_cast<FDuration>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations };\n            }\n\n            template <typename Clock = default_clock>\n            void run() {\n                static_assert( Clock::is_steady,\n                               \"Benchmarking clock should be steady\" );\n                auto const* cfg = getCurrentContext().getConfig();\n\n                auto env = Detail::measure_environment<Clock>();\n\n                getResultCapture().benchmarkPreparing(name);\n                CATCH_TRY{\n                    auto plan = user_code([&] {\n                        return prepare<Clock>(*cfg, env);\n                    });\n\n                    BenchmarkInfo info {\n                        CATCH_MOVE(name),\n                        plan.estimated_duration.count(),\n                        plan.iterations_per_sample,\n                        cfg->benchmarkSamples(),\n                        cfg->benchmarkResamples(),\n                        env.clock_resolution.mean.count(),\n                        env.clock_cost.mean.count()\n                    };\n\n                    getResultCapture().benchmarkStarting(info);\n\n                    auto samples = user_code([&] {\n                        return plan.template run<Clock>(*cfg, env);\n                    });\n\n                    auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size());\n                    BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance };\n                    getResultCapture().benchmarkEnded(stats);\n                } CATCH_CATCH_ANON (TestFailureException const&) {\n                    getResultCapture().benchmarkFailed(\"Benchmark failed due to failed assertion\"_sr);\n                } CATCH_CATCH_ALL{\n                    getResultCapture().benchmarkFailed(translateActiveException());\n                    // We let the exception go further up so that the\n                    // test case is marked as failed.\n                    std::rethrow_exception(std::current_exception());\n                }\n            }\n\n            // sets lambda to be used in fun *and* executes benchmark!\n            template <typename Fun, std::enable_if_t<!Detail::is_related_v<Fun, Benchmark>, int> = 0>\n                Benchmark & operator=(Fun func) {\n                auto const* cfg = getCurrentContext().getConfig();\n                if (!cfg->skipBenchmarks()) {\n                    fun = Detail::BenchmarkFunction(func);\n                    run();\n                }\n                return *this;\n            }\n\n            explicit operator bool() {\n                return true;\n            }\n\n        private:\n            Detail::BenchmarkFunction fun;\n            std::string name;\n        };\n    }\n} // namespace Catch\n\n#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1\n#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2\n\n#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\\\n    if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \\\n        BenchmarkName = [&](int benchmarkIndex)\n\n#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\\\n    if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \\\n        BenchmarkName = [&]\n\n#if defined(CATCH_CONFIG_PREFIX_ALL)\n\n#define CATCH_BENCHMARK(...) \\\n    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))\n#define CATCH_BENCHMARK_ADVANCED(name) \\\n    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name)\n\n#else\n\n#define BENCHMARK(...) \\\n    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))\n#define BENCHMARK_ADVANCED(name) \\\n    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name)\n\n#endif\n\n#endif // CATCH_BENCHMARK_HPP_INCLUDED\n\n\n// Adapted from donated nonius code.\n\n#ifndef CATCH_CONSTRUCTOR_HPP_INCLUDED\n#define CATCH_CONSTRUCTOR_HPP_INCLUDED\n\n\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename T, bool Destruct>\n            struct ObjectStorage\n            {\n                ObjectStorage() = default;\n\n                ObjectStorage(const ObjectStorage& other)\n                {\n                    new(&data) T(other.stored_object());\n                }\n\n                ObjectStorage(ObjectStorage&& other)\n                {\n                    new(data) T(CATCH_MOVE(other.stored_object()));\n                }\n\n                ~ObjectStorage() { destruct_on_exit<T>(); }\n\n                template <typename... Args>\n                void construct(Args&&... args)\n                {\n                    new (data) T(CATCH_FORWARD(args)...);\n                }\n\n                template <bool AllowManualDestruction = !Destruct>\n                std::enable_if_t<AllowManualDestruction> destruct()\n                {\n                    stored_object().~T();\n                }\n\n            private:\n                // If this is a constructor benchmark, destruct the underlying object\n                template <typename U>\n                void destruct_on_exit(std::enable_if_t<Destruct, U>* = nullptr) { destruct<true>(); }\n                // Otherwise, don't\n                template <typename U>\n                void destruct_on_exit(std::enable_if_t<!Destruct, U>* = nullptr) { }\n\n#if defined( __GNUC__ ) && __GNUC__ <= 6\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wstrict-aliasing\"\n#endif\n                T& stored_object() { return *reinterpret_cast<T*>( data ); }\n\n                T const& stored_object() const {\n                    return *reinterpret_cast<T const*>( data );\n                }\n#if defined( __GNUC__ ) && __GNUC__ <= 6\n#    pragma GCC diagnostic pop\n#endif\n\n                alignas( T ) unsigned char data[sizeof( T )]{};\n            };\n        } // namespace Detail\n\n        template <typename T>\n        using storage_for = Detail::ObjectStorage<T, true>;\n\n        template <typename T>\n        using destructable_object = Detail::ObjectStorage<T, false>;\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_CONSTRUCTOR_HPP_INCLUDED\n\n#endif // CATCH_BENCHMARK_ALL_HPP_INCLUDED\n\n\n#ifndef CATCH_APPROX_HPP_INCLUDED\n#define CATCH_APPROX_HPP_INCLUDED\n\n\n\n#ifndef CATCH_TOSTRING_HPP_INCLUDED\n#define CATCH_TOSTRING_HPP_INCLUDED\n\n\n#include <vector>\n#include <cstddef>\n#include <type_traits>\n#include <string>\n\n\n\n\n/** \\file\n * Wrapper for the WCHAR configuration option\n *\n * We want to support platforms that do not provide `wchar_t`, so we\n * sometimes have to disable providing wchar_t overloads through Catch2,\n * e.g. the StringMaker specialization for `std::wstring`.\n */\n\n#ifndef CATCH_CONFIG_WCHAR_HPP_INCLUDED\n#define CATCH_CONFIG_WCHAR_HPP_INCLUDED\n\n\n// We assume that WCHAR should be enabled by default, and only disabled\n// for a shortlist (so far only DJGPP) of compilers.\n\n#if defined(__DJGPP__)\n#  define CATCH_INTERNAL_CONFIG_NO_WCHAR\n#endif // __DJGPP__\n\n#if !defined( CATCH_INTERNAL_CONFIG_NO_WCHAR ) && \\\n    !defined( CATCH_CONFIG_NO_WCHAR ) && \\\n    !defined( CATCH_CONFIG_WCHAR )\n#    define CATCH_CONFIG_WCHAR\n#endif\n\n#endif // CATCH_CONFIG_WCHAR_HPP_INCLUDED\n\n\n#ifndef CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED\n#define CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED\n\n\n#include <iosfwd>\n#include <cstddef>\n#include <ostream>\n#include <string>\n\nnamespace Catch {\n\n    class ReusableStringStream : Detail::NonCopyable {\n        std::size_t m_index;\n        std::ostream* m_oss;\n    public:\n        ReusableStringStream();\n        ~ReusableStringStream();\n\n        //! Returns the serialized state\n        std::string str() const;\n        //! Sets internal state to `str`\n        void str(std::string const& str);\n\n#if defined(__GNUC__) && !defined(__clang__)\n#pragma GCC diagnostic push\n// Old versions of GCC do not understand -Wnonnull-compare\n#pragma GCC diagnostic ignored \"-Wpragmas\"\n// Streaming a function pointer triggers Waddress and Wnonnull-compare\n// on GCC, because it implicitly converts it to bool and then decides\n// that the check it uses (a? true : false) is tautological and cannot\n// be null...\n#pragma GCC diagnostic ignored \"-Waddress\"\n#pragma GCC diagnostic ignored \"-Wnonnull-compare\"\n#endif\n\n        template<typename T>\n        auto operator << ( T const& value ) -> ReusableStringStream& {\n            *m_oss << value;\n            return *this;\n        }\n\n#if defined(__GNUC__) && !defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n        auto get() -> std::ostream& { return *m_oss; }\n    };\n}\n\n#endif // CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED\n\n\n#ifndef CATCH_VOID_TYPE_HPP_INCLUDED\n#define CATCH_VOID_TYPE_HPP_INCLUDED\n\n\nnamespace Catch {\n    namespace Detail {\n\n        template <typename...>\n        struct make_void { using type = void; };\n\n        template <typename... Ts>\n        using void_t = typename make_void<Ts...>::type;\n\n    } // namespace Detail\n} // namespace Catch\n\n\n#endif // CATCH_VOID_TYPE_HPP_INCLUDED\n\n\n#ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED\n#define CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED\n\n\n#include <vector>\n\nnamespace Catch {\n\n    namespace Detail {\n        struct EnumInfo {\n            StringRef m_name;\n            std::vector<std::pair<int, StringRef>> m_values;\n\n            ~EnumInfo();\n\n            StringRef lookup( int value ) const;\n        };\n    } // namespace Detail\n\n    class IMutableEnumValuesRegistry {\n    public:\n        virtual ~IMutableEnumValuesRegistry(); // = default;\n\n        virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values ) = 0;\n\n        template<typename E>\n        Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list<E> values ) {\n            static_assert(sizeof(int) >= sizeof(E), \"Cannot serialize enum to int\");\n            std::vector<int> intValues;\n            intValues.reserve( values.size() );\n            for( auto enumValue : values )\n                intValues.push_back( static_cast<int>( enumValue ) );\n            return registerEnum( enumName, allEnums, intValues );\n        }\n    };\n\n} // Catch\n\n#endif // CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\n#include <string_view>\n#endif\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless\n#endif\n\n// We need a dummy global operator<< so we can bring it into Catch namespace later\nstruct Catch_global_namespace_dummy{};\nstd::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy);\n\nnamespace Catch {\n    // Bring in global namespace operator<< for ADL lookup in\n    // `IsStreamInsertable` below.\n    using ::operator<<;\n\n    namespace Detail {\n\n        inline std::size_t catch_strnlen(const char *str, std::size_t n) {\n            auto ret = std::char_traits<char>::find(str, n, '\\0');\n            if (ret != nullptr) {\n                return static_cast<std::size_t>(ret - str);\n            }\n            return n;\n        }\n\n        constexpr StringRef unprintableString = \"{?}\"_sr;\n\n        //! Encases `string in quotes, and optionally escapes invisibles\n        std::string convertIntoString( StringRef string, bool escapeInvisibles );\n\n        //! Encases `string` in quotes, and escapes invisibles if user requested\n        //! it via CLI\n        std::string convertIntoString( StringRef string );\n\n        std::string rawMemoryToString( const void *object, std::size_t size );\n\n        template<typename T>\n        std::string rawMemoryToString( const T& object ) {\n          return rawMemoryToString( &object, sizeof(object) );\n        }\n\n        template<typename T,typename = void>\n        static constexpr bool IsStreamInsertable_v = false;\n\n        template <typename T>\n        static constexpr bool IsStreamInsertable_v<\n            T,\n            decltype( void( std::declval<std::ostream&>() << std::declval<T>() ) )> =\n            true;\n\n        template<typename E>\n        std::string convertUnknownEnumToString( E e );\n\n        template<typename T>\n        std::enable_if_t<\n            !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value,\n        std::string> convertUnstreamable( T const& ) {\n            return std::string(Detail::unprintableString);\n        }\n        template<typename T>\n        std::enable_if_t<\n            !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value,\n         std::string> convertUnstreamable(T const& ex) {\n            return ex.what();\n        }\n\n\n        template<typename T>\n        std::enable_if_t<\n            std::is_enum<T>::value,\n        std::string> convertUnstreamable( T const& value ) {\n            return convertUnknownEnumToString( value );\n        }\n\n#if defined(_MANAGED)\n        //! Convert a CLR string to a utf8 std::string\n        template<typename T>\n        std::string clrReferenceToString( T^ ref ) {\n            if (ref == nullptr)\n                return std::string(\"null\");\n            auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString());\n            cli::pin_ptr<System::Byte> p = &bytes[0];\n            return std::string(reinterpret_cast<char const *>(p), bytes->Length);\n        }\n#endif\n\n    } // namespace Detail\n\n\n    template <typename T, typename = void>\n    struct StringMaker {\n        template <typename Fake = T>\n        static\n        std::enable_if_t<::Catch::Detail::IsStreamInsertable_v<Fake>, std::string>\n            convert(const Fake& value) {\n                ReusableStringStream rss;\n                // NB: call using the function-like syntax to avoid ambiguity with\n                // user-defined templated operator<< under clang.\n                rss.operator<<(value);\n                return rss.str();\n        }\n\n        template <typename Fake = T>\n        static\n        std::enable_if_t<!::Catch::Detail::IsStreamInsertable_v<Fake>, std::string>\n            convert( const Fake& value ) {\n#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)\n            return Detail::convertUnstreamable(value);\n#else\n            return CATCH_CONFIG_FALLBACK_STRINGIFIER(value);\n#endif\n        }\n    };\n\n    namespace Detail {\n\n        // This function dispatches all stringification requests inside of Catch.\n        // Should be preferably called fully qualified, like ::Catch::Detail::stringify\n        template <typename T>\n        std::string stringify(const T& e) {\n            return ::Catch::StringMaker<std::remove_cv_t<std::remove_reference_t<T>>>::convert(e);\n        }\n\n        template<typename E>\n        std::string convertUnknownEnumToString( E e ) {\n            return ::Catch::Detail::stringify(static_cast<std::underlying_type_t<E>>(e));\n        }\n\n#if defined(_MANAGED)\n        template <typename T>\n        std::string stringify( T^ e ) {\n            return ::Catch::StringMaker<T^>::convert(e);\n        }\n#endif\n\n    } // namespace Detail\n\n    // Some predefined specializations\n\n    template<>\n    struct StringMaker<std::string> {\n        static std::string convert(const std::string& str);\n    };\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\n    template<>\n    struct StringMaker<std::string_view> {\n        static std::string convert(std::string_view str);\n    };\n#endif\n\n    template<>\n    struct StringMaker<char const *> {\n        static std::string convert(char const * str);\n    };\n    template<>\n    struct StringMaker<char *> {\n        static std::string convert(char * str);\n    };\n\n#if defined(CATCH_CONFIG_WCHAR)\n    template<>\n    struct StringMaker<std::wstring> {\n        static std::string convert(const std::wstring& wstr);\n    };\n\n# ifdef CATCH_CONFIG_CPP17_STRING_VIEW\n    template<>\n    struct StringMaker<std::wstring_view> {\n        static std::string convert(std::wstring_view str);\n    };\n# endif\n\n    template<>\n    struct StringMaker<wchar_t const *> {\n        static std::string convert(wchar_t const * str);\n    };\n    template<>\n    struct StringMaker<wchar_t *> {\n        static std::string convert(wchar_t * str);\n    };\n#endif // CATCH_CONFIG_WCHAR\n\n    template<size_t SZ>\n    struct StringMaker<char[SZ]> {\n        static std::string convert(char const* str) {\n            return Detail::convertIntoString(\n                StringRef( str, Detail::catch_strnlen( str, SZ ) ) );\n        }\n    };\n    template<size_t SZ>\n    struct StringMaker<signed char[SZ]> {\n        static std::string convert(signed char const* str) {\n            auto reinterpreted = reinterpret_cast<char const*>(str);\n            return Detail::convertIntoString(\n                StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ)));\n        }\n    };\n    template<size_t SZ>\n    struct StringMaker<unsigned char[SZ]> {\n        static std::string convert(unsigned char const* str) {\n            auto reinterpreted = reinterpret_cast<char const*>(str);\n            return Detail::convertIntoString(\n                StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ)));\n        }\n    };\n\n#if defined(CATCH_CONFIG_CPP17_BYTE)\n    template<>\n    struct StringMaker<std::byte> {\n        static std::string convert(std::byte value);\n    };\n#endif // defined(CATCH_CONFIG_CPP17_BYTE)\n    template<>\n    struct StringMaker<int> {\n        static std::string convert(int value);\n    };\n    template<>\n    struct StringMaker<long> {\n        static std::string convert(long value);\n    };\n    template<>\n    struct StringMaker<long long> {\n        static std::string convert(long long value);\n    };\n    template<>\n    struct StringMaker<unsigned int> {\n        static std::string convert(unsigned int value);\n    };\n    template<>\n    struct StringMaker<unsigned long> {\n        static std::string convert(unsigned long value);\n    };\n    template<>\n    struct StringMaker<unsigned long long> {\n        static std::string convert(unsigned long long value);\n    };\n\n    template<>\n    struct StringMaker<bool> {\n        static std::string convert(bool b) {\n            using namespace std::string_literals;\n            return b ? \"true\"s : \"false\"s;\n        }\n    };\n\n    template<>\n    struct StringMaker<char> {\n        static std::string convert(char c);\n    };\n    template<>\n    struct StringMaker<signed char> {\n        static std::string convert(signed char value);\n    };\n    template<>\n    struct StringMaker<unsigned char> {\n        static std::string convert(unsigned char value);\n    };\n\n    template<>\n    struct StringMaker<std::nullptr_t> {\n        static std::string convert(std::nullptr_t) {\n            using namespace std::string_literals;\n            return \"nullptr\"s;\n        }\n    };\n\n    template<>\n    struct StringMaker<float> {\n        static std::string convert(float value);\n        CATCH_EXPORT static int precision;\n    };\n\n    template<>\n    struct StringMaker<double> {\n        static std::string convert(double value);\n        CATCH_EXPORT static int precision;\n    };\n\n    template <typename T>\n    struct StringMaker<T*> {\n        template <typename U>\n        static std::string convert(U* p) {\n            if (p) {\n                return ::Catch::Detail::rawMemoryToString(p);\n            } else {\n                return \"nullptr\";\n            }\n        }\n    };\n\n    template <typename R, typename C>\n    struct StringMaker<R C::*> {\n        static std::string convert(R C::* p) {\n            if (p) {\n                return ::Catch::Detail::rawMemoryToString(p);\n            } else {\n                return \"nullptr\";\n            }\n        }\n    };\n\n#if defined(_MANAGED)\n    template <typename T>\n    struct StringMaker<T^> {\n        static std::string convert( T^ ref ) {\n            return ::Catch::Detail::clrReferenceToString(ref);\n        }\n    };\n#endif\n\n    namespace Detail {\n        template<typename InputIterator, typename Sentinel = InputIterator>\n        std::string rangeToString(InputIterator first, Sentinel last) {\n            ReusableStringStream rss;\n            rss << \"{ \";\n            if (first != last) {\n                rss << ::Catch::Detail::stringify(*first);\n                for (++first; first != last; ++first)\n                    rss << \", \" << ::Catch::Detail::stringify(*first);\n            }\n            rss << \" }\";\n            return rss.str();\n        }\n    }\n\n} // namespace Catch\n\n//////////////////////////////////////////////////////\n// Separate std-lib types stringification, so it can be selectively enabled\n// This means that we do not bring in their headers\n\n#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS)\n#  define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER\n#  define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER\n#  define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER\n#  define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER\n#endif\n\n// Separate std::pair specialization\n#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER)\n#include <utility>\nnamespace Catch {\n    template<typename T1, typename T2>\n    struct StringMaker<std::pair<T1, T2> > {\n        static std::string convert(const std::pair<T1, T2>& pair) {\n            ReusableStringStream rss;\n            rss << \"{ \"\n                << ::Catch::Detail::stringify(pair.first)\n                << \", \"\n                << ::Catch::Detail::stringify(pair.second)\n                << \" }\";\n            return rss.str();\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER\n\n#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL)\n#include <optional>\nnamespace Catch {\n    template<typename T>\n    struct StringMaker<std::optional<T> > {\n        static std::string convert(const std::optional<T>& optional) {\n            if (optional.has_value()) {\n                return ::Catch::Detail::stringify(*optional);\n            } else {\n                return \"{ }\";\n            }\n        }\n    };\n    template <>\n    struct StringMaker<std::nullopt_t> {\n        static std::string convert(const std::nullopt_t&) {\n            return \"{ }\";\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER\n\n// Separate std::tuple specialization\n#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)\n#include <tuple>\nnamespace Catch {\n    namespace Detail {\n        template<\n            typename Tuple,\n            std::size_t N = 0,\n            bool = (N < std::tuple_size<Tuple>::value)\n            >\n            struct TupleElementPrinter {\n            static void print(const Tuple& tuple, std::ostream& os) {\n                os << (N ? \", \" : \" \")\n                    << ::Catch::Detail::stringify(std::get<N>(tuple));\n                TupleElementPrinter<Tuple, N + 1>::print(tuple, os);\n            }\n        };\n\n        template<\n            typename Tuple,\n            std::size_t N\n        >\n            struct TupleElementPrinter<Tuple, N, false> {\n            static void print(const Tuple&, std::ostream&) {}\n        };\n\n    }\n\n\n    template<typename ...Types>\n    struct StringMaker<std::tuple<Types...>> {\n        static std::string convert(const std::tuple<Types...>& tuple) {\n            ReusableStringStream rss;\n            rss << '{';\n            Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get());\n            rss << \" }\";\n            return rss.str();\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER\n\n#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT)\n#include <variant>\nnamespace Catch {\n    template<>\n    struct StringMaker<std::monostate> {\n        static std::string convert(const std::monostate&) {\n            return \"{ }\";\n        }\n    };\n\n    template<typename... Elements>\n    struct StringMaker<std::variant<Elements...>> {\n        static std::string convert(const std::variant<Elements...>& variant) {\n            if (variant.valueless_by_exception()) {\n                return \"{valueless variant}\";\n            } else {\n                return std::visit(\n                    [](const auto& value) {\n                        return ::Catch::Detail::stringify(value);\n                    },\n                    variant\n                );\n            }\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER\n\nnamespace Catch {\n    // Import begin/ end from std here\n    using std::begin;\n    using std::end;\n\n    namespace Detail {\n        template <typename T, typename = void>\n        struct is_range_impl : std::false_type {};\n\n        template <typename T>\n        struct is_range_impl<T, void_t<decltype(begin(std::declval<T>()))>> : std::true_type {};\n    } // namespace Detail\n\n    template <typename T>\n    struct is_range : Detail::is_range_impl<T> {};\n\n#if defined(_MANAGED) // Managed types are never ranges\n    template <typename T>\n    struct is_range<T^> {\n        static const bool value = false;\n    };\n#endif\n\n    template<typename Range>\n    std::string rangeToString( Range const& range ) {\n        return ::Catch::Detail::rangeToString( begin( range ), end( range ) );\n    }\n\n    // Handle vector<bool> specially\n    template<typename Allocator>\n    std::string rangeToString( std::vector<bool, Allocator> const& v ) {\n        ReusableStringStream rss;\n        rss << \"{ \";\n        bool first = true;\n        for( bool b : v ) {\n            if( first )\n                first = false;\n            else\n                rss << \", \";\n            rss << ::Catch::Detail::stringify( b );\n        }\n        rss << \" }\";\n        return rss.str();\n    }\n\n    template<typename R>\n    struct StringMaker<R, std::enable_if_t<is_range<R>::value && !::Catch::Detail::IsStreamInsertable_v<R>>> {\n        static std::string convert( R const& range ) {\n            return rangeToString( range );\n        }\n    };\n\n    template <typename T, size_t SZ>\n    struct StringMaker<T[SZ]> {\n        static std::string convert(T const(&arr)[SZ]) {\n            return rangeToString(arr);\n        }\n    };\n\n\n} // namespace Catch\n\n// Separate std::chrono::duration specialization\n#include <ctime>\n#include <ratio>\n#include <chrono>\n\n\nnamespace Catch {\n\ntemplate <class Ratio>\nstruct ratio_string {\n    static std::string symbol() {\n        Catch::ReusableStringStream rss;\n        rss << '[' << Ratio::num << '/'\n            << Ratio::den << ']';\n        return rss.str();\n    }\n};\n\ntemplate <>\nstruct ratio_string<std::atto> {\n    static char symbol() { return 'a'; }\n};\ntemplate <>\nstruct ratio_string<std::femto> {\n    static char symbol() { return 'f'; }\n};\ntemplate <>\nstruct ratio_string<std::pico> {\n    static char symbol() { return 'p'; }\n};\ntemplate <>\nstruct ratio_string<std::nano> {\n    static char symbol() { return 'n'; }\n};\ntemplate <>\nstruct ratio_string<std::micro> {\n    static char symbol() { return 'u'; }\n};\ntemplate <>\nstruct ratio_string<std::milli> {\n    static char symbol() { return 'm'; }\n};\n\n    ////////////\n    // std::chrono::duration specializations\n    template<typename Value, typename Ratio>\n    struct StringMaker<std::chrono::duration<Value, Ratio>> {\n        static std::string convert(std::chrono::duration<Value, Ratio> const& duration) {\n            ReusableStringStream rss;\n            rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's';\n            return rss.str();\n        }\n    };\n    template<typename Value>\n    struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> {\n        static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) {\n            ReusableStringStream rss;\n            rss << duration.count() << \" s\";\n            return rss.str();\n        }\n    };\n    template<typename Value>\n    struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> {\n        static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) {\n            ReusableStringStream rss;\n            rss << duration.count() << \" m\";\n            return rss.str();\n        }\n    };\n    template<typename Value>\n    struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> {\n        static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) {\n            ReusableStringStream rss;\n            rss << duration.count() << \" h\";\n            return rss.str();\n        }\n    };\n\n    ////////////\n    // std::chrono::time_point specialization\n    // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock>\n    template<typename Clock, typename Duration>\n    struct StringMaker<std::chrono::time_point<Clock, Duration>> {\n        static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) {\n            return ::Catch::Detail::stringify(time_point.time_since_epoch()) + \" since epoch\";\n        }\n    };\n    // std::chrono::time_point<system_clock> specialization\n    template<typename Duration>\n    struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {\n        static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) {\n            auto converted = std::chrono::system_clock::to_time_t(time_point);\n\n#ifdef _MSC_VER\n            std::tm timeInfo = {};\n            const auto err = gmtime_s(&timeInfo, &converted);\n            if ( err ) {\n                return \"gmtime from provided timepoint has failed. This \"\n                       \"happens e.g. with pre-1970 dates using Microsoft libc\";\n            }\n#else\n            std::tm* timeInfo = std::gmtime(&converted);\n#endif\n\n            auto const timeStampSize = sizeof(\"2017-01-16T17:06:45Z\");\n            char timeStamp[timeStampSize];\n            const char * const fmt = \"%Y-%m-%dT%H:%M:%SZ\";\n\n#ifdef _MSC_VER\n            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);\n#else\n            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);\n#endif\n            return std::string(timeStamp, timeStampSize - 1);\n        }\n    };\n}\n\n\n#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \\\nnamespace Catch { \\\n    template<> struct StringMaker<enumName> { \\\n        static std::string convert( enumName value ) { \\\n            static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \\\n            return static_cast<std::string>(enumInfo.lookup( static_cast<int>( value ) )); \\\n        } \\\n    }; \\\n}\n\n#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ )\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n#endif // CATCH_TOSTRING_HPP_INCLUDED\n\n#include <type_traits>\n\nnamespace Catch {\n\n    class Approx {\n    private:\n        bool equalityComparisonImpl(double other) const;\n        // Sets and validates the new margin (margin >= 0)\n        void setMargin(double margin);\n        // Sets and validates the new epsilon (0 < epsilon < 1)\n        void setEpsilon(double epsilon);\n\n    public:\n        explicit Approx ( double value );\n\n        static Approx custom();\n\n        Approx operator-() const;\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        Approx operator()( T const& value ) const {\n            Approx approx( static_cast<double>(value) );\n            approx.m_epsilon = m_epsilon;\n            approx.m_margin = m_margin;\n            approx.m_scale = m_scale;\n            return approx;\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        explicit Approx( T const& value ): Approx(static_cast<double>(value))\n        {}\n\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        friend bool operator == ( const T& lhs, Approx const& rhs ) {\n            auto lhs_v = static_cast<double>(lhs);\n            return rhs.equalityComparisonImpl(lhs_v);\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        friend bool operator == ( Approx const& lhs, const T& rhs ) {\n            return operator==( rhs, lhs );\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        friend bool operator != ( T const& lhs, Approx const& rhs ) {\n            return !operator==( lhs, rhs );\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        friend bool operator != ( Approx const& lhs, T const& rhs ) {\n            return !operator==( rhs, lhs );\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        friend bool operator <= ( T const& lhs, Approx const& rhs ) {\n            return static_cast<double>(lhs) < rhs.m_value || lhs == rhs;\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        friend bool operator <= ( Approx const& lhs, T const& rhs ) {\n            return lhs.m_value < static_cast<double>(rhs) || lhs == rhs;\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        friend bool operator >= ( T const& lhs, Approx const& rhs ) {\n            return static_cast<double>(lhs) > rhs.m_value || lhs == rhs;\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        friend bool operator >= ( Approx const& lhs, T const& rhs ) {\n            return lhs.m_value > static_cast<double>(rhs) || lhs == rhs;\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        Approx& epsilon( T const& newEpsilon ) {\n            const auto epsilonAsDouble = static_cast<double>(newEpsilon);\n            setEpsilon(epsilonAsDouble);\n            return *this;\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        Approx& margin( T const& newMargin ) {\n            const auto marginAsDouble = static_cast<double>(newMargin);\n            setMargin(marginAsDouble);\n            return *this;\n        }\n\n        template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        Approx& scale( T const& newScale ) {\n            m_scale = static_cast<double>(newScale);\n            return *this;\n        }\n\n        std::string toString() const;\n\n    private:\n        double m_epsilon;\n        double m_margin;\n        double m_scale;\n        double m_value;\n    };\n\nnamespace literals {\n    Approx operator \"\"_a(long double val);\n    Approx operator \"\"_a(unsigned long long val);\n} // end namespace literals\n\ntemplate<>\nstruct StringMaker<Catch::Approx> {\n    static std::string convert(Catch::Approx const& value);\n};\n\n} // end namespace Catch\n\n#endif // CATCH_APPROX_HPP_INCLUDED\n\n\n#ifndef CATCH_ASSERTION_INFO_HPP_INCLUDED\n#define CATCH_ASSERTION_INFO_HPP_INCLUDED\n\n\n\n#ifndef CATCH_SOURCE_LINE_INFO_HPP_INCLUDED\n#define CATCH_SOURCE_LINE_INFO_HPP_INCLUDED\n\n#include <cstddef>\n#include <iosfwd>\n\nnamespace Catch {\n\n    struct SourceLineInfo {\n\n        SourceLineInfo() = delete;\n        constexpr SourceLineInfo( char const* _file, std::size_t _line ) noexcept:\n            file( _file ),\n            line( _line )\n        {}\n\n        bool operator == ( SourceLineInfo const& other ) const noexcept;\n        bool operator < ( SourceLineInfo const& other ) const noexcept;\n\n        char const* file;\n        std::size_t line;\n\n        friend std::ostream& operator << (std::ostream& os, SourceLineInfo const& info);\n    };\n}\n\n#define CATCH_INTERNAL_LINEINFO \\\n    ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )\n\n#endif // CATCH_SOURCE_LINE_INFO_HPP_INCLUDED\n\nnamespace Catch {\n\n    struct AssertionInfo {\n        // AssertionInfo() = delete;\n\n        StringRef macroName;\n        SourceLineInfo lineInfo;\n        StringRef capturedExpression;\n        ResultDisposition::Flags resultDisposition;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_ASSERTION_INFO_HPP_INCLUDED\n\n\n#ifndef CATCH_ASSERTION_RESULT_HPP_INCLUDED\n#define CATCH_ASSERTION_RESULT_HPP_INCLUDED\n\n\n\n#ifndef CATCH_LAZY_EXPR_HPP_INCLUDED\n#define CATCH_LAZY_EXPR_HPP_INCLUDED\n\n#include <iosfwd>\n\nnamespace Catch {\n\n    class ITransientExpression;\n\n    class LazyExpression {\n        friend class AssertionHandler;\n        friend struct AssertionStats;\n        friend class RunContext;\n\n        ITransientExpression const* m_transientExpression = nullptr;\n        bool m_isNegated;\n    public:\n        constexpr LazyExpression( bool isNegated ):\n            m_isNegated(isNegated)\n        {}\n        constexpr LazyExpression(LazyExpression const& other) = default;\n        LazyExpression& operator = ( LazyExpression const& ) = delete;\n\n        constexpr explicit operator bool() const {\n            return m_transientExpression != nullptr;\n        }\n\n        friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&;\n    };\n\n} // namespace Catch\n\n#endif // CATCH_LAZY_EXPR_HPP_INCLUDED\n\n#include <string>\n\nnamespace Catch {\n\n    struct AssertionResultData\n    {\n        AssertionResultData() = delete;\n\n        AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression );\n\n        std::string message;\n        mutable std::string reconstructedExpression;\n        LazyExpression lazyExpression;\n        ResultWas::OfType resultType;\n\n        std::string reconstructExpression() const;\n    };\n\n    class AssertionResult {\n    public:\n        AssertionResult() = delete;\n        AssertionResult( AssertionInfo const& info, AssertionResultData&& data );\n\n        bool isOk() const;\n        bool succeeded() const;\n        ResultWas::OfType getResultType() const;\n        bool hasExpression() const;\n        bool hasMessage() const;\n        std::string getExpression() const;\n        std::string getExpressionInMacro() const;\n        bool hasExpandedExpression() const;\n        std::string getExpandedExpression() const;\n        StringRef getMessage() const;\n        SourceLineInfo getSourceInfo() const;\n        StringRef getTestMacroName() const;\n\n    //protected:\n        AssertionInfo m_info;\n        AssertionResultData m_resultData;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_ASSERTION_RESULT_HPP_INCLUDED\n\n\n#ifndef CATCH_CASE_SENSITIVE_HPP_INCLUDED\n#define CATCH_CASE_SENSITIVE_HPP_INCLUDED\n\nnamespace Catch {\n\n    enum class CaseSensitive { Yes, No };\n\n} // namespace Catch\n\n#endif // CATCH_CASE_SENSITIVE_HPP_INCLUDED\n\n\n#ifndef CATCH_CONFIG_HPP_INCLUDED\n#define CATCH_CONFIG_HPP_INCLUDED\n\n\n\n#ifndef CATCH_TEST_SPEC_HPP_INCLUDED\n#define CATCH_TEST_SPEC_HPP_INCLUDED\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\n\n\n#ifndef CATCH_WILDCARD_PATTERN_HPP_INCLUDED\n#define CATCH_WILDCARD_PATTERN_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch\n{\n    class WildcardPattern {\n        enum WildcardPosition {\n            NoWildcard = 0,\n            WildcardAtStart = 1,\n            WildcardAtEnd = 2,\n            WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd\n        };\n\n    public:\n\n        WildcardPattern( std::string const& pattern, CaseSensitive caseSensitivity );\n        bool matches( std::string const& str ) const;\n\n    private:\n        std::string normaliseString( std::string const& str ) const;\n        CaseSensitive m_caseSensitivity;\n        WildcardPosition m_wildcard = NoWildcard;\n        std::string m_pattern;\n    };\n}\n\n#endif // CATCH_WILDCARD_PATTERN_HPP_INCLUDED\n\n#include <iosfwd>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    class IConfig;\n    struct TestCaseInfo;\n    class TestCaseHandle;\n\n    class TestSpec {\n\n        class Pattern {\n        public:\n            explicit Pattern( std::string const& name );\n            virtual ~Pattern();\n            virtual bool matches( TestCaseInfo const& testCase ) const = 0;\n            std::string const& name() const;\n        private:\n            virtual void serializeTo( std::ostream& out ) const = 0;\n            // Writes string that would be reparsed into the pattern\n            friend std::ostream& operator<<(std::ostream& out,\n                                            Pattern const& pattern) {\n                pattern.serializeTo( out );\n                return out;\n            }\n\n            std::string const m_name;\n        };\n\n        class NamePattern : public Pattern {\n        public:\n            explicit NamePattern( std::string const& name, std::string const& filterString );\n            bool matches( TestCaseInfo const& testCase ) const override;\n        private:\n            void serializeTo( std::ostream& out ) const override;\n\n            WildcardPattern m_wildcardPattern;\n        };\n\n        class TagPattern : public Pattern {\n        public:\n            explicit TagPattern( std::string const& tag, std::string const& filterString );\n            bool matches( TestCaseInfo const& testCase ) const override;\n        private:\n            void serializeTo( std::ostream& out ) const override;\n\n            std::string m_tag;\n        };\n\n        struct Filter {\n            std::vector<Detail::unique_ptr<Pattern>> m_required;\n            std::vector<Detail::unique_ptr<Pattern>> m_forbidden;\n\n            //! Serializes this filter into a string that would be parsed into\n            //! an equivalent filter\n            void serializeTo( std::ostream& out ) const;\n            friend std::ostream& operator<<(std::ostream& out, Filter const& f) {\n                f.serializeTo( out );\n                return out;\n            }\n\n            bool matches( TestCaseInfo const& testCase ) const;\n        };\n\n        static std::string extractFilterName( Filter const& filter );\n\n    public:\n        struct FilterMatch {\n            std::string name;\n            std::vector<TestCaseHandle const*> tests;\n        };\n        using Matches = std::vector<FilterMatch>;\n        using vectorStrings = std::vector<std::string>;\n\n        bool hasFilters() const;\n        bool matches( TestCaseInfo const& testCase ) const;\n        Matches matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const;\n        const vectorStrings & getInvalidSpecs() const;\n\n    private:\n        std::vector<Filter> m_filters;\n        std::vector<std::string> m_invalidSpecs;\n\n        friend class TestSpecParser;\n        //! Serializes this test spec into a string that would be parsed into\n        //! equivalent test spec\n        void serializeTo( std::ostream& out ) const;\n        friend std::ostream& operator<<(std::ostream& out,\n                                        TestSpec const& spec) {\n            spec.serializeTo( out );\n            return out;\n        }\n    };\n}\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n#endif // CATCH_TEST_SPEC_HPP_INCLUDED\n\n\n#ifndef CATCH_OPTIONAL_HPP_INCLUDED\n#define CATCH_OPTIONAL_HPP_INCLUDED\n\n\n#include <cassert>\n\nnamespace Catch {\n\n    // An optional type\n    template<typename T>\n    class Optional {\n    public:\n        Optional(): nullableValue( nullptr ) {}\n        ~Optional() { reset(); }\n\n        Optional( T const& _value ):\n            nullableValue( new ( storage ) T( _value ) ) {}\n        Optional( T&& _value ):\n            nullableValue( new ( storage ) T( CATCH_MOVE( _value ) ) ) {}\n\n        Optional& operator=( T const& _value ) {\n            reset();\n            nullableValue = new ( storage ) T( _value );\n            return *this;\n        }\n        Optional& operator=( T&& _value ) {\n            reset();\n            nullableValue = new ( storage ) T( CATCH_MOVE( _value ) );\n            return *this;\n        }\n\n        Optional( Optional const& _other ):\n            nullableValue( _other ? new ( storage ) T( *_other ) : nullptr ) {}\n        Optional( Optional&& _other ):\n            nullableValue( _other ? new ( storage ) T( CATCH_MOVE( *_other ) )\n                                  : nullptr ) {}\n\n        Optional& operator=( Optional const& _other ) {\n            if ( &_other != this ) {\n                reset();\n                if ( _other ) { nullableValue = new ( storage ) T( *_other ); }\n            }\n            return *this;\n        }\n        Optional& operator=( Optional&& _other ) {\n            if ( &_other != this ) {\n                reset();\n                if ( _other ) {\n                    nullableValue = new ( storage ) T( CATCH_MOVE( *_other ) );\n                }\n            }\n            return *this;\n        }\n\n        void reset() {\n            if ( nullableValue ) { nullableValue->~T(); }\n            nullableValue = nullptr;\n        }\n\n        T& operator*() {\n            assert(nullableValue);\n            return *nullableValue;\n        }\n        T const& operator*() const {\n            assert(nullableValue);\n            return *nullableValue;\n        }\n        T* operator->() {\n            assert(nullableValue);\n            return nullableValue;\n        }\n        const T* operator->() const {\n            assert(nullableValue);\n            return nullableValue;\n        }\n\n        T valueOr( T const& defaultValue ) const {\n            return nullableValue ? *nullableValue : defaultValue;\n        }\n\n        bool some() const { return nullableValue != nullptr; }\n        bool none() const { return nullableValue == nullptr; }\n\n        bool operator !() const { return nullableValue == nullptr; }\n        explicit operator bool() const {\n            return some();\n        }\n\n        friend bool operator==(Optional const& a, Optional const& b) {\n            if (a.none() && b.none()) {\n                return true;\n            } else if (a.some() && b.some()) {\n                return *a == *b;\n            } else {\n                return false;\n            }\n        }\n        friend bool operator!=(Optional const& a, Optional const& b) {\n            return !( a == b );\n        }\n\n    private:\n        T* nullableValue;\n        alignas(alignof(T)) char storage[sizeof(T)];\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_OPTIONAL_HPP_INCLUDED\n\n\n#ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED\n#define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED\n\n#include <cstdint>\n\nnamespace Catch {\n\n    enum class GenerateFrom {\n        Time,\n        RandomDevice,\n        //! Currently equivalent to RandomDevice, but can change at any point\n        Default\n    };\n\n    std::uint32_t generateRandomSeed(GenerateFrom from);\n\n} // end namespace Catch\n\n#endif // CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED\n#define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED\n\n\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    enum class ColourMode : std::uint8_t;\n\n    namespace Detail {\n        //! Splits the reporter spec into reporter name and kv-pair options\n        std::vector<std::string> splitReporterSpec( StringRef reporterSpec );\n\n        Optional<ColourMode> stringToColourMode( StringRef colourMode );\n    }\n\n    /**\n     * Structured reporter spec that a reporter can be created from\n     *\n     * Parsing has been validated, but semantics have not. This means e.g.\n     * that the colour mode is known to Catch2, but it might not be\n     * compiled into the binary, and the output filename might not be\n     * openable.\n     */\n    class ReporterSpec {\n        std::string m_name;\n        Optional<std::string> m_outputFileName;\n        Optional<ColourMode> m_colourMode;\n        std::map<std::string, std::string> m_customOptions;\n\n        friend bool operator==( ReporterSpec const& lhs,\n                                ReporterSpec const& rhs );\n        friend bool operator!=( ReporterSpec const& lhs,\n                                ReporterSpec const& rhs ) {\n            return !( lhs == rhs );\n        }\n\n    public:\n        ReporterSpec(\n            std::string name,\n            Optional<std::string> outputFileName,\n            Optional<ColourMode> colourMode,\n            std::map<std::string, std::string> customOptions );\n\n        std::string const& name() const { return m_name; }\n\n        Optional<std::string> const& outputFile() const {\n            return m_outputFileName;\n        }\n\n        Optional<ColourMode> const& colourMode() const { return m_colourMode; }\n\n        std::map<std::string, std::string> const& customOptions() const {\n            return m_customOptions;\n        }\n    };\n\n    /**\n     * Parses provided reporter spec string into\n     *\n     * Returns empty optional on errors, e.g.\n     *  * field that is not first and not a key+value pair\n     *  * duplicated keys in kv pair\n     *  * unknown catch reporter option\n     *  * empty key/value in an custom kv pair\n     *  * ...\n     */\n    Optional<ReporterSpec> parseReporterSpec( StringRef reporterSpec );\n\n}\n\n#endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED\n\n#include <chrono>\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    class IStream;\n\n    /**\n     * `ReporterSpec` but with the defaults filled in.\n     *\n     * Like `ReporterSpec`, the semantics are unchecked.\n     */\n    struct ProcessedReporterSpec {\n        std::string name;\n        std::string outputFilename;\n        ColourMode colourMode;\n        std::map<std::string, std::string> customOptions;\n        friend bool operator==( ProcessedReporterSpec const& lhs,\n                                ProcessedReporterSpec const& rhs );\n        friend bool operator!=( ProcessedReporterSpec const& lhs,\n                                ProcessedReporterSpec const& rhs ) {\n            return !( lhs == rhs );\n        }\n    };\n\n    struct ConfigData {\n\n        bool listTests = false;\n        bool listTags = false;\n        bool listReporters = false;\n        bool listListeners = false;\n\n        bool showSuccessfulTests = false;\n        bool shouldDebugBreak = false;\n        bool noThrow = false;\n        bool showHelp = false;\n        bool showInvisibles = false;\n        bool filenamesAsTags = false;\n        bool libIdentify = false;\n        bool allowZeroTests = false;\n\n        int abortAfter = -1;\n        uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default);\n\n        unsigned int shardCount = 1;\n        unsigned int shardIndex = 0;\n\n        bool skipBenchmarks = false;\n        bool benchmarkNoAnalysis = false;\n        unsigned int benchmarkSamples = 100;\n        double benchmarkConfidenceInterval = 0.95;\n        unsigned int benchmarkResamples = 100'000;\n        std::chrono::milliseconds::rep benchmarkWarmupTime = 100;\n\n        Verbosity verbosity = Verbosity::Normal;\n        WarnAbout::What warnings = WarnAbout::Nothing;\n        ShowDurations showDurations = ShowDurations::DefaultForReporter;\n        double minDuration = -1;\n        TestRunOrder runOrder = TestRunOrder::Randomized;\n        ColourMode defaultColourMode = ColourMode::PlatformDefault;\n        WaitForKeypress::When waitForKeypress = WaitForKeypress::Never;\n\n        std::string defaultOutputFilename;\n        std::string name;\n        std::string processName;\n        std::vector<ReporterSpec> reporterSpecifications;\n\n        std::vector<std::string> testsOrTags;\n        std::vector<std::string> sectionsToRun;\n    };\n\n\n    class Config : public IConfig {\n    public:\n\n        Config() = default;\n        Config( ConfigData const& data );\n        ~Config() override; // = default in the cpp file\n\n        bool listTests() const;\n        bool listTags() const;\n        bool listReporters() const;\n        bool listListeners() const;\n\n        std::vector<ReporterSpec> const& getReporterSpecs() const;\n        std::vector<ProcessedReporterSpec> const&\n        getProcessedReporterSpecs() const;\n\n        std::vector<std::string> const& getTestsOrTags() const override;\n        std::vector<std::string> const& getSectionsToRun() const override;\n\n        TestSpec const& testSpec() const override;\n        bool hasTestFilters() const override;\n\n        bool showHelp() const;\n\n        // IConfig interface\n        bool allowThrows() const override;\n        StringRef name() const override;\n        bool includeSuccessfulResults() const override;\n        bool warnAboutMissingAssertions() const override;\n        bool warnAboutUnmatchedTestSpecs() const override;\n        bool zeroTestsCountAsSuccess() const override;\n        ShowDurations showDurations() const override;\n        double minDuration() const override;\n        TestRunOrder runOrder() const override;\n        uint32_t rngSeed() const override;\n        unsigned int shardCount() const override;\n        unsigned int shardIndex() const override;\n        ColourMode defaultColourMode() const override;\n        bool shouldDebugBreak() const override;\n        int abortAfter() const override;\n        bool showInvisibles() const override;\n        Verbosity verbosity() const override;\n        bool skipBenchmarks() const override;\n        bool benchmarkNoAnalysis() const override;\n        unsigned int benchmarkSamples() const override;\n        double benchmarkConfidenceInterval() const override;\n        unsigned int benchmarkResamples() const override;\n        std::chrono::milliseconds benchmarkWarmupTime() const override;\n\n    private:\n        // Reads Bazel env vars and applies them to the config\n        void readBazelEnvVars();\n\n        ConfigData m_data;\n        std::vector<ProcessedReporterSpec> m_processedReporterSpecs;\n        TestSpec m_testSpec;\n        bool m_hasTestFilters = false;\n    };\n} // end namespace Catch\n\n#endif // CATCH_CONFIG_HPP_INCLUDED\n\n\n#ifndef CATCH_GET_RANDOM_SEED_HPP_INCLUDED\n#define CATCH_GET_RANDOM_SEED_HPP_INCLUDED\n\n#include <cstdint>\n\nnamespace Catch {\n    //! Returns Catch2's current RNG seed.\n    std::uint32_t getSeed();\n}\n\n#endif // CATCH_GET_RANDOM_SEED_HPP_INCLUDED\n\n\n#ifndef CATCH_MESSAGE_HPP_INCLUDED\n#define CATCH_MESSAGE_HPP_INCLUDED\n\n\n\n\n/** \\file\n * Wrapper for the CATCH_CONFIG_PREFIX_MESSAGES configuration option\n *\n * CATCH_CONFIG_PREFIX_ALL can be used to avoid clashes with other macros\n * by prepending CATCH_. This may not be desirable if the only clashes are with\n * logger macros such as INFO and WARN. In this cases\n * CATCH_CONFIG_PREFIX_MESSAGES can be used to only prefix a small subset\n * of relevant macros.\n *\n */\n\n#ifndef CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED\n#define CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED\n\n\n#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_PREFIX_MESSAGES)\n    #define CATCH_CONFIG_PREFIX_MESSAGES\n#endif\n\n#endif // CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED\n\n\n#ifndef CATCH_STREAM_END_STOP_HPP_INCLUDED\n#define CATCH_STREAM_END_STOP_HPP_INCLUDED\n\n\nnamespace Catch {\n\n    // Use this in variadic streaming macros to allow\n    //    << +StreamEndStop\n    // as well as\n    //    << stuff +StreamEndStop\n    struct StreamEndStop {\n        constexpr StringRef operator+() const { return StringRef(); }\n\n        template <typename T>\n        constexpr friend T const& operator+( T const& value, StreamEndStop ) {\n            return value;\n        }\n    };\n\n} // namespace Catch\n\n#endif // CATCH_STREAM_END_STOP_HPP_INCLUDED\n\n\n#ifndef CATCH_MESSAGE_INFO_HPP_INCLUDED\n#define CATCH_MESSAGE_INFO_HPP_INCLUDED\n\n\n\n#ifndef CATCH_DEPRECATION_MACRO_HPP_INCLUDED\n#define CATCH_DEPRECATION_MACRO_HPP_INCLUDED\n\n\n#if !defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS )\n#    define DEPRECATED( msg ) [[deprecated( msg )]]\n#else\n#    define DEPRECATED( msg )\n#endif\n\n#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED\n\n#include <string>\n\nnamespace Catch {\n\n    struct MessageInfo {\n        MessageInfo(    StringRef _macroName,\n                        SourceLineInfo const& _lineInfo,\n                        ResultWas::OfType _type );\n\n        StringRef macroName;\n        std::string message;\n        SourceLineInfo lineInfo;\n        ResultWas::OfType type;\n        unsigned int sequence;\n\n        DEPRECATED( \"Explicitly use the 'sequence' member instead\" )\n        bool operator == (MessageInfo const& other) const {\n            return sequence == other.sequence;\n        }\n        DEPRECATED( \"Explicitly use the 'sequence' member instead\" )\n        bool operator < (MessageInfo const& other) const {\n            return sequence < other.sequence;\n        }\n    private:\n        static unsigned int globalCount;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_MESSAGE_INFO_HPP_INCLUDED\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    struct SourceLineInfo;\n    class IResultCapture;\n\n    struct MessageStream {\n\n        template<typename T>\n        MessageStream& operator << ( T const& value ) {\n            m_stream << value;\n            return *this;\n        }\n\n        ReusableStringStream m_stream;\n    };\n\n    struct MessageBuilder : MessageStream {\n        MessageBuilder( StringRef macroName,\n                        SourceLineInfo const& lineInfo,\n                        ResultWas::OfType type ):\n            m_info(macroName, lineInfo, type) {}\n\n        template<typename T>\n        MessageBuilder&& operator << ( T const& value ) && {\n            m_stream << value;\n            return CATCH_MOVE(*this);\n        }\n\n        MessageInfo m_info;\n    };\n\n    class ScopedMessage {\n    public:\n        explicit ScopedMessage( MessageBuilder&& builder );\n        ScopedMessage( ScopedMessage& duplicate ) = delete;\n        ScopedMessage( ScopedMessage&& old ) noexcept;\n        ~ScopedMessage();\n\n        MessageInfo m_info;\n        bool m_moved = false;\n    };\n\n    class Capturer {\n        std::vector<MessageInfo> m_messages;\n        IResultCapture& m_resultCapture;\n        size_t m_captured = 0;\n    public:\n        Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );\n\n        Capturer(Capturer const&) = delete;\n        Capturer& operator=(Capturer const&) = delete;\n\n        ~Capturer();\n\n        void captureValue( size_t index, std::string const& value );\n\n        template<typename T>\n        void captureValues( size_t index, T const& value ) {\n            captureValue( index, Catch::Detail::stringify( value ) );\n        }\n\n        template<typename T, typename... Ts>\n        void captureValues( size_t index, T const& value, Ts const&... values ) {\n            captureValue( index, Catch::Detail::stringify(value) );\n            captureValues( index+1, values... );\n        }\n    };\n\n} // end namespace Catch\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \\\n        catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \\\n        catchAssertionHandler.complete(); \\\n    } while( false )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \\\n    Catch::Capturer varName( macroName##_catch_sr,        \\\n                             CATCH_INTERNAL_LINEINFO,     \\\n                             Catch::ResultWas::Info,      \\\n                             #__VA_ARGS__##_catch_sr );   \\\n    varName.captureValues( 0, __VA_ARGS__ )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_INFO( macroName, log ) \\\n    const Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \\\n    Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )\n\n\n#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)\n\n  #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( \"CATCH_INFO\", msg )\n  #define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( \"CATCH_UNSCOPED_INFO\", msg )\n  #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( \"CATCH_WARN\", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )\n  #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), \"CATCH_CAPTURE\", __VA_ARGS__ )\n\n#elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE)\n\n  #define CATCH_INFO( msg )          (void)(0)\n  #define CATCH_UNSCOPED_INFO( msg ) (void)(0)\n  #define CATCH_WARN( msg )          (void)(0)\n  #define CATCH_CAPTURE( ... )       (void)(0)\n\n#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)\n\n  #define INFO( msg ) INTERNAL_CATCH_INFO( \"INFO\", msg )\n  #define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( \"UNSCOPED_INFO\", msg )\n  #define WARN( msg ) INTERNAL_CATCH_MSG( \"WARN\", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )\n  #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), \"CAPTURE\", __VA_ARGS__ )\n\n#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE)\n\n  #define INFO( msg )          (void)(0)\n  #define UNSCOPED_INFO( msg ) (void)(0)\n  #define WARN( msg )          (void)(0)\n  #define CAPTURE( ... )       (void)(0)\n\n#endif // end of user facing macro declarations\n\n\n\n\n#endif // CATCH_MESSAGE_HPP_INCLUDED\n\n\n#ifndef CATCH_SECTION_INFO_HPP_INCLUDED\n#define CATCH_SECTION_INFO_HPP_INCLUDED\n\n\n\n#ifndef CATCH_TOTALS_HPP_INCLUDED\n#define CATCH_TOTALS_HPP_INCLUDED\n\n#include <cstdint>\n\nnamespace Catch {\n\n    struct Counts {\n        Counts operator - ( Counts const& other ) const;\n        Counts& operator += ( Counts const& other );\n\n        std::uint64_t total() const;\n        bool allPassed() const;\n        bool allOk() const;\n\n        std::uint64_t passed = 0;\n        std::uint64_t failed = 0;\n        std::uint64_t failedButOk = 0;\n        std::uint64_t skipped = 0;\n    };\n\n    struct Totals {\n\n        Totals operator - ( Totals const& other ) const;\n        Totals& operator += ( Totals const& other );\n\n        Totals delta( Totals const& prevTotals ) const;\n\n        Counts assertions;\n        Counts testCases;\n    };\n}\n\n#endif // CATCH_TOTALS_HPP_INCLUDED\n\n#include <string>\n\nnamespace Catch {\n\n    struct SectionInfo {\n        // The last argument is ignored, so that people can write\n        // SECTION(\"ShortName\", \"Proper description that is long\") and\n        // still use the `-c` flag comfortably.\n        SectionInfo( SourceLineInfo const& _lineInfo, std::string _name,\n                    const char* const = nullptr ):\n            name(CATCH_MOVE(_name)),\n            lineInfo(_lineInfo)\n            {}\n\n        std::string name;\n        SourceLineInfo lineInfo;\n    };\n\n    struct SectionEndInfo {\n        SectionInfo sectionInfo;\n        Counts prevAssertions;\n        double durationInSeconds;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_SECTION_INFO_HPP_INCLUDED\n\n\n#ifndef CATCH_SESSION_HPP_INCLUDED\n#define CATCH_SESSION_HPP_INCLUDED\n\n\n\n#ifndef CATCH_COMMANDLINE_HPP_INCLUDED\n#define CATCH_COMMANDLINE_HPP_INCLUDED\n\n\n\n#ifndef CATCH_CLARA_HPP_INCLUDED\n#define CATCH_CLARA_HPP_INCLUDED\n\n#if defined( __clang__ )\n#    pragma clang diagnostic push\n#    pragma clang diagnostic ignored \"-Wweak-vtables\"\n#    pragma clang diagnostic ignored \"-Wshadow\"\n#    pragma clang diagnostic ignored \"-Wdeprecated\"\n#endif\n\n#if defined( __GNUC__ )\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wsign-conversion\"\n#endif\n\n#ifndef CLARA_CONFIG_OPTIONAL_TYPE\n#    ifdef __has_include\n#        if __has_include( <optional>) && __cplusplus >= 201703L\n#            include <optional>\n#            define CLARA_CONFIG_OPTIONAL_TYPE std::optional\n#        endif\n#    endif\n#endif\n\n\n#include <cassert>\n#include <memory>\n#include <ostream>\n#include <sstream>\n#include <string>\n#include <type_traits>\n#include <vector>\n\nnamespace Catch {\n    namespace Clara {\n\n        class Args;\n        class Parser;\n\n        // enum of result types from a parse\n        enum class ParseResultType {\n            Matched,\n            NoMatch,\n            ShortCircuitAll,\n            ShortCircuitSame\n        };\n\n        struct accept_many_t {};\n        constexpr accept_many_t accept_many {};\n\n        namespace Detail {\n            struct fake_arg {\n                template <typename T>\n                operator T();\n            };\n\n            template <typename F, typename = void>\n            static constexpr bool is_unary_function_v = false;\n\n            template <typename F>\n            static constexpr bool is_unary_function_v<\n                F,\n                Catch::Detail::void_t<decltype( std::declval<F>()(\n                    fake_arg() ) )>> = true;\n\n            // Traits for extracting arg and return type of lambdas (for single\n            // argument lambdas)\n            template <typename L>\n            struct UnaryLambdaTraits\n                : UnaryLambdaTraits<decltype( &L::operator() )> {};\n\n            template <typename ClassT, typename ReturnT, typename... Args>\n            struct UnaryLambdaTraits<ReturnT ( ClassT::* )( Args... ) const> {\n                static const bool isValid = false;\n            };\n\n            template <typename ClassT, typename ReturnT, typename ArgT>\n            struct UnaryLambdaTraits<ReturnT ( ClassT::* )( ArgT ) const> {\n                static const bool isValid = true;\n                using ArgType = std::remove_const_t<std::remove_reference_t<ArgT>>;\n                using ReturnType = ReturnT;\n            };\n\n            class TokenStream;\n\n            // Wraps a token coming from a token stream. These may not directly\n            // correspond to strings as a single string may encode an option +\n            // its argument if the : or = form is used\n            enum class TokenType { Option, Argument };\n            struct Token {\n                TokenType type;\n                StringRef token;\n            };\n\n            // Abstracts iterators into args as a stream of tokens, with option\n            // arguments uniformly handled\n            class TokenStream {\n                using Iterator = std::vector<StringRef>::const_iterator;\n                Iterator it;\n                Iterator itEnd;\n                std::vector<Token> m_tokenBuffer;\n                void loadBuffer();\n\n            public:\n                explicit TokenStream( Args const& args );\n                TokenStream( Iterator it, Iterator itEnd );\n\n                explicit operator bool() const {\n                    return !m_tokenBuffer.empty() || it != itEnd;\n                }\n\n                size_t count() const {\n                    return m_tokenBuffer.size() + ( itEnd - it );\n                }\n\n                Token operator*() const {\n                    assert( !m_tokenBuffer.empty() );\n                    return m_tokenBuffer.front();\n                }\n\n                Token const* operator->() const {\n                    assert( !m_tokenBuffer.empty() );\n                    return &m_tokenBuffer.front();\n                }\n\n                TokenStream& operator++();\n            };\n\n            //! Denotes type of a parsing result\n            enum class ResultType {\n                Ok,          ///< No errors\n                LogicError,  ///< Error in user-specified arguments for\n                             ///< construction\n                RuntimeError ///< Error in parsing inputs\n            };\n\n            class ResultBase {\n            protected:\n                ResultBase( ResultType type ): m_type( type ) {}\n                virtual ~ResultBase(); // = default;\n\n\n                ResultBase(ResultBase const&) = default;\n                ResultBase& operator=(ResultBase const&) = default;\n                ResultBase(ResultBase&&) = default;\n                ResultBase& operator=(ResultBase&&) = default;\n\n                virtual void enforceOk() const = 0;\n\n                ResultType m_type;\n            };\n\n            template <typename T>\n            class ResultValueBase : public ResultBase {\n            public:\n                T const& value() const& {\n                    enforceOk();\n                    return m_value;\n                }\n                T&& value() && {\n                    enforceOk();\n                    return CATCH_MOVE( m_value );\n                }\n\n            protected:\n                ResultValueBase( ResultType type ): ResultBase( type ) {}\n\n                ResultValueBase( ResultValueBase const& other ):\n                    ResultBase( other ) {\n                    if ( m_type == ResultType::Ok )\n                        new ( &m_value ) T( other.m_value );\n                }\n                ResultValueBase( ResultValueBase&& other ):\n                    ResultBase( other ) {\n                    if ( m_type == ResultType::Ok )\n                        new ( &m_value ) T( CATCH_MOVE(other.m_value) );\n                }\n\n\n                ResultValueBase( ResultType, T const& value ):\n                    ResultBase( ResultType::Ok ) {\n                    new ( &m_value ) T( value );\n                }\n                ResultValueBase( ResultType, T&& value ):\n                    ResultBase( ResultType::Ok ) {\n                    new ( &m_value ) T( CATCH_MOVE(value) );\n                }\n\n                ResultValueBase& operator=( ResultValueBase const& other ) {\n                    if ( m_type == ResultType::Ok )\n                        m_value.~T();\n                    ResultBase::operator=( other );\n                    if ( m_type == ResultType::Ok )\n                        new ( &m_value ) T( other.m_value );\n                    return *this;\n                }\n                ResultValueBase& operator=( ResultValueBase&& other ) {\n                    if ( m_type == ResultType::Ok ) m_value.~T();\n                    ResultBase::operator=( other );\n                    if ( m_type == ResultType::Ok )\n                        new ( &m_value ) T( CATCH_MOVE(other.m_value) );\n                    return *this;\n                }\n\n\n                ~ResultValueBase() override {\n                    if ( m_type == ResultType::Ok )\n                        m_value.~T();\n                }\n\n                union {\n                    T m_value;\n                };\n            };\n\n            template <> class ResultValueBase<void> : public ResultBase {\n            protected:\n                using ResultBase::ResultBase;\n            };\n\n            template <typename T = void>\n            class BasicResult : public ResultValueBase<T> {\n            public:\n                template <typename U>\n                explicit BasicResult( BasicResult<U> const& other ):\n                    ResultValueBase<T>( other.type() ),\n                    m_errorMessage( other.errorMessage() ) {\n                    assert( type() != ResultType::Ok );\n                }\n\n                template <typename U>\n                static auto ok( U&& value ) -> BasicResult {\n                    return { ResultType::Ok, CATCH_FORWARD(value) };\n                }\n                static auto ok() -> BasicResult { return { ResultType::Ok }; }\n                static auto logicError( std::string&& message )\n                    -> BasicResult {\n                    return { ResultType::LogicError, CATCH_MOVE(message) };\n                }\n                static auto runtimeError( std::string&& message )\n                    -> BasicResult {\n                    return { ResultType::RuntimeError, CATCH_MOVE(message) };\n                }\n\n                explicit operator bool() const {\n                    return m_type == ResultType::Ok;\n                }\n                auto type() const -> ResultType { return m_type; }\n                auto errorMessage() const -> std::string const& {\n                    return m_errorMessage;\n                }\n\n            protected:\n                void enforceOk() const override {\n\n                    // Errors shouldn't reach this point, but if they do\n                    // the actual error message will be in m_errorMessage\n                    assert( m_type != ResultType::LogicError );\n                    assert( m_type != ResultType::RuntimeError );\n                    if ( m_type != ResultType::Ok )\n                        std::abort();\n                }\n\n                std::string\n                    m_errorMessage; // Only populated if resultType is an error\n\n                BasicResult( ResultType type,\n                             std::string&& message ):\n                    ResultValueBase<T>( type ), m_errorMessage( CATCH_MOVE(message) ) {\n                    assert( m_type != ResultType::Ok );\n                }\n\n                using ResultValueBase<T>::ResultValueBase;\n                using ResultBase::m_type;\n            };\n\n            class ParseState {\n            public:\n                ParseState( ParseResultType type,\n                            TokenStream remainingTokens );\n\n                ParseResultType type() const { return m_type; }\n                TokenStream const& remainingTokens() const& {\n                    return m_remainingTokens;\n                }\n                TokenStream&& remainingTokens() && {\n                    return CATCH_MOVE( m_remainingTokens );\n                }\n\n            private:\n                ParseResultType m_type;\n                TokenStream m_remainingTokens;\n            };\n\n            using Result = BasicResult<void>;\n            using ParserResult = BasicResult<ParseResultType>;\n            using InternalParseResult = BasicResult<ParseState>;\n\n            struct HelpColumns {\n                std::string left;\n                StringRef descriptions;\n            };\n\n            template <typename T>\n            ParserResult convertInto( std::string const& source, T& target ) {\n                std::stringstream ss( source );\n                ss >> target;\n                if ( ss.fail() ) {\n                    return ParserResult::runtimeError(\n                        \"Unable to convert '\" + source +\n                        \"' to destination type\" );\n                } else {\n                    return ParserResult::ok( ParseResultType::Matched );\n                }\n            }\n            ParserResult convertInto( std::string const& source,\n                                      std::string& target );\n            ParserResult convertInto( std::string const& source, bool& target );\n\n#ifdef CLARA_CONFIG_OPTIONAL_TYPE\n            template <typename T>\n            auto convertInto( std::string const& source,\n                              CLARA_CONFIG_OPTIONAL_TYPE<T>& target )\n                -> ParserResult {\n                T temp;\n                auto result = convertInto( source, temp );\n                if ( result )\n                    target = CATCH_MOVE( temp );\n                return result;\n            }\n#endif // CLARA_CONFIG_OPTIONAL_TYPE\n\n            struct BoundRef : Catch::Detail::NonCopyable {\n                virtual ~BoundRef() = default;\n                virtual bool isContainer() const;\n                virtual bool isFlag() const;\n            };\n            struct BoundValueRefBase : BoundRef {\n                virtual auto setValue( std::string const& arg )\n                    -> ParserResult = 0;\n            };\n            struct BoundFlagRefBase : BoundRef {\n                virtual auto setFlag( bool flag ) -> ParserResult = 0;\n                bool isFlag() const override;\n            };\n\n            template <typename T> struct BoundValueRef : BoundValueRefBase {\n                T& m_ref;\n\n                explicit BoundValueRef( T& ref ): m_ref( ref ) {}\n\n                ParserResult setValue( std::string const& arg ) override {\n                    return convertInto( arg, m_ref );\n                }\n            };\n\n            template <typename T>\n            struct BoundValueRef<std::vector<T>> : BoundValueRefBase {\n                std::vector<T>& m_ref;\n\n                explicit BoundValueRef( std::vector<T>& ref ): m_ref( ref ) {}\n\n                auto isContainer() const -> bool override { return true; }\n\n                auto setValue( std::string const& arg )\n                    -> ParserResult override {\n                    T temp;\n                    auto result = convertInto( arg, temp );\n                    if ( result )\n                        m_ref.push_back( temp );\n                    return result;\n                }\n            };\n\n            struct BoundFlagRef : BoundFlagRefBase {\n                bool& m_ref;\n\n                explicit BoundFlagRef( bool& ref ): m_ref( ref ) {}\n\n                ParserResult setFlag( bool flag ) override;\n            };\n\n            template <typename ReturnType> struct LambdaInvoker {\n                static_assert(\n                    std::is_same<ReturnType, ParserResult>::value,\n                    \"Lambda must return void or clara::ParserResult\" );\n\n                template <typename L, typename ArgType>\n                static auto invoke( L const& lambda, ArgType const& arg )\n                    -> ParserResult {\n                    return lambda( arg );\n                }\n            };\n\n            template <> struct LambdaInvoker<void> {\n                template <typename L, typename ArgType>\n                static auto invoke( L const& lambda, ArgType const& arg )\n                    -> ParserResult {\n                    lambda( arg );\n                    return ParserResult::ok( ParseResultType::Matched );\n                }\n            };\n\n            template <typename ArgType, typename L>\n            auto invokeLambda( L const& lambda, std::string const& arg )\n                -> ParserResult {\n                ArgType temp{};\n                auto result = convertInto( arg, temp );\n                return !result ? result\n                               : LambdaInvoker<typename UnaryLambdaTraits<\n                                     L>::ReturnType>::invoke( lambda, temp );\n            }\n\n            template <typename L> struct BoundLambda : BoundValueRefBase {\n                L m_lambda;\n\n                static_assert(\n                    UnaryLambdaTraits<L>::isValid,\n                    \"Supplied lambda must take exactly one argument\" );\n                explicit BoundLambda( L const& lambda ): m_lambda( lambda ) {}\n\n                auto setValue( std::string const& arg )\n                    -> ParserResult override {\n                    return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>(\n                        m_lambda, arg );\n                }\n            };\n\n            template <typename L> struct BoundManyLambda : BoundLambda<L> {\n                explicit BoundManyLambda( L const& lambda ): BoundLambda<L>( lambda ) {}\n                bool isContainer() const override { return true; }\n            };\n\n            template <typename L> struct BoundFlagLambda : BoundFlagRefBase {\n                L m_lambda;\n\n                static_assert(\n                    UnaryLambdaTraits<L>::isValid,\n                    \"Supplied lambda must take exactly one argument\" );\n                static_assert(\n                    std::is_same<typename UnaryLambdaTraits<L>::ArgType,\n                                 bool>::value,\n                    \"flags must be boolean\" );\n\n                explicit BoundFlagLambda( L const& lambda ):\n                    m_lambda( lambda ) {}\n\n                auto setFlag( bool flag ) -> ParserResult override {\n                    return LambdaInvoker<typename UnaryLambdaTraits<\n                        L>::ReturnType>::invoke( m_lambda, flag );\n                }\n            };\n\n            enum class Optionality { Optional, Required };\n\n            class ParserBase {\n            public:\n                virtual ~ParserBase() = default;\n                virtual auto validate() const -> Result { return Result::ok(); }\n                virtual auto parse( std::string const& exeName,\n                                    TokenStream tokens ) const\n                    -> InternalParseResult = 0;\n                virtual size_t cardinality() const;\n\n                InternalParseResult parse( Args const& args ) const;\n            };\n\n            template <typename DerivedT>\n            class ComposableParserImpl : public ParserBase {\n            public:\n                template <typename T>\n                auto operator|( T const& other ) const -> Parser;\n            };\n\n            // Common code and state for Args and Opts\n            template <typename DerivedT>\n            class ParserRefImpl : public ComposableParserImpl<DerivedT> {\n            protected:\n                Optionality m_optionality = Optionality::Optional;\n                std::shared_ptr<BoundRef> m_ref;\n                StringRef m_hint;\n                StringRef m_description;\n\n                explicit ParserRefImpl( std::shared_ptr<BoundRef> const& ref ):\n                    m_ref( ref ) {}\n\n            public:\n                template <typename LambdaT>\n                ParserRefImpl( accept_many_t,\n                               LambdaT const& ref,\n                               StringRef hint ):\n                    m_ref( std::make_shared<BoundManyLambda<LambdaT>>( ref ) ),\n                    m_hint( hint ) {}\n\n                template <typename T,\n                          typename = typename std::enable_if_t<\n                              !Detail::is_unary_function_v<T>>>\n                ParserRefImpl( T& ref, StringRef hint ):\n                    m_ref( std::make_shared<BoundValueRef<T>>( ref ) ),\n                    m_hint( hint ) {}\n\n                template <typename LambdaT,\n                          typename = typename std::enable_if_t<\n                              Detail::is_unary_function_v<LambdaT>>>\n                ParserRefImpl( LambdaT const& ref, StringRef hint ):\n                    m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ),\n                    m_hint( hint ) {}\n\n                DerivedT& operator()( StringRef description ) & {\n                    m_description = description;\n                    return static_cast<DerivedT&>( *this );\n                }\n                DerivedT&& operator()( StringRef description ) && {\n                    m_description = description;\n                    return static_cast<DerivedT&&>( *this );\n                }\n\n                auto optional() -> DerivedT& {\n                    m_optionality = Optionality::Optional;\n                    return static_cast<DerivedT&>( *this );\n                }\n\n                auto required() -> DerivedT& {\n                    m_optionality = Optionality::Required;\n                    return static_cast<DerivedT&>( *this );\n                }\n\n                auto isOptional() const -> bool {\n                    return m_optionality == Optionality::Optional;\n                }\n\n                auto cardinality() const -> size_t override {\n                    if ( m_ref->isContainer() )\n                        return 0;\n                    else\n                        return 1;\n                }\n\n                StringRef hint() const { return m_hint; }\n            };\n\n        } // namespace detail\n\n\n        // A parser for arguments\n        class Arg : public Detail::ParserRefImpl<Arg> {\n        public:\n            using ParserRefImpl::ParserRefImpl;\n            using ParserBase::parse;\n\n            Detail::InternalParseResult\n                parse(std::string const&,\n                      Detail::TokenStream tokens) const override;\n        };\n\n        // A parser for options\n        class Opt : public Detail::ParserRefImpl<Opt> {\n        protected:\n            std::vector<StringRef> m_optNames;\n\n        public:\n            template <typename LambdaT>\n            explicit Opt(LambdaT const& ref) :\n                ParserRefImpl(\n                    std::make_shared<Detail::BoundFlagLambda<LambdaT>>(ref)) {}\n\n            explicit Opt(bool& ref);\n\n            template <typename LambdaT,\n                      typename = typename std::enable_if_t<\n                          Detail::is_unary_function_v<LambdaT>>>\n            Opt( LambdaT const& ref, StringRef hint ):\n                ParserRefImpl( ref, hint ) {}\n\n            template <typename LambdaT>\n            Opt( accept_many_t, LambdaT const& ref, StringRef hint ):\n                ParserRefImpl( accept_many, ref, hint ) {}\n\n            template <typename T,\n                      typename = typename std::enable_if_t<\n                          !Detail::is_unary_function_v<T>>>\n            Opt( T& ref, StringRef hint ):\n                ParserRefImpl( ref, hint ) {}\n\n            Opt& operator[]( StringRef optName ) & {\n                m_optNames.push_back(optName);\n                return *this;\n            }\n            Opt&& operator[]( StringRef optName ) && {\n                m_optNames.push_back( optName );\n                return CATCH_MOVE(*this);\n            }\n\n            Detail::HelpColumns getHelpColumns() const;\n\n            bool isMatch(StringRef optToken) const;\n\n            using ParserBase::parse;\n\n            Detail::InternalParseResult\n                parse(std::string const&,\n                      Detail::TokenStream tokens) const override;\n\n            Detail::Result validate() const override;\n        };\n\n        // Specifies the name of the executable\n        class ExeName : public Detail::ComposableParserImpl<ExeName> {\n            std::shared_ptr<std::string> m_name;\n            std::shared_ptr<Detail::BoundValueRefBase> m_ref;\n\n        public:\n            ExeName();\n            explicit ExeName(std::string& ref);\n\n            template <typename LambdaT>\n            explicit ExeName(LambdaT const& lambda) : ExeName() {\n                m_ref = std::make_shared<Detail::BoundLambda<LambdaT>>(lambda);\n            }\n\n            // The exe name is not parsed out of the normal tokens, but is\n            // handled specially\n            Detail::InternalParseResult\n                parse(std::string const&,\n                      Detail::TokenStream tokens) const override;\n\n            std::string const& name() const { return *m_name; }\n            Detail::ParserResult set(std::string const& newName);\n        };\n\n\n        // A Combined parser\n        class Parser : Detail::ParserBase {\n            mutable ExeName m_exeName;\n            std::vector<Opt> m_options;\n            std::vector<Arg> m_args;\n\n        public:\n\n            auto operator|=(ExeName const& exeName) -> Parser& {\n                m_exeName = exeName;\n                return *this;\n            }\n\n            auto operator|=(Arg const& arg) -> Parser& {\n                m_args.push_back(arg);\n                return *this;\n            }\n\n            friend Parser& operator|=( Parser& p, Opt const& opt ) {\n                p.m_options.push_back( opt );\n                return p;\n            }\n            friend Parser& operator|=( Parser& p, Opt&& opt ) {\n                p.m_options.push_back( CATCH_MOVE(opt) );\n                return p;\n            }\n\n            Parser& operator|=(Parser const& other);\n\n            template <typename T>\n            friend Parser operator|( Parser const& p, T&& rhs ) {\n                Parser temp( p );\n                temp |= rhs;\n                return temp;\n            }\n\n            template <typename T>\n            friend Parser operator|( Parser&& p, T&& rhs ) {\n                p |= CATCH_FORWARD(rhs);\n                return CATCH_MOVE(p);\n            }\n\n            std::vector<Detail::HelpColumns> getHelpColumns() const;\n\n            void writeToStream(std::ostream& os) const;\n\n            friend auto operator<<(std::ostream& os, Parser const& parser)\n                -> std::ostream& {\n                parser.writeToStream(os);\n                return os;\n            }\n\n            Detail::Result validate() const override;\n\n            using ParserBase::parse;\n            Detail::InternalParseResult\n                parse(std::string const& exeName,\n                      Detail::TokenStream tokens) const override;\n        };\n\n        /**\n         * Wrapper over argc + argv, assumes that the inputs outlive it\n         */\n        class Args {\n            friend Detail::TokenStream;\n            StringRef m_exeName;\n            std::vector<StringRef> m_args;\n\n        public:\n            Args(int argc, char const* const* argv);\n            // Helper constructor for testing\n            Args(std::initializer_list<StringRef> args);\n\n            StringRef exeName() const { return m_exeName; }\n        };\n\n\n        // Convenience wrapper for option parser that specifies the help option\n        struct Help : Opt {\n            Help(bool& showHelpFlag);\n        };\n\n        // Result type for parser operation\n        using Detail::ParserResult;\n\n        namespace Detail {\n            template <typename DerivedT>\n            template <typename T>\n            Parser\n                ComposableParserImpl<DerivedT>::operator|(T const& other) const {\n                return Parser() | static_cast<DerivedT const&>(*this) | other;\n            }\n        }\n\n    } // namespace Clara\n} // namespace Catch\n\n#if defined( __clang__ )\n#    pragma clang diagnostic pop\n#endif\n\n#if defined( __GNUC__ )\n#    pragma GCC diagnostic pop\n#endif\n\n#endif // CATCH_CLARA_HPP_INCLUDED\n\nnamespace Catch {\n\n    struct ConfigData;\n\n    Clara::Parser makeCommandLineParser( ConfigData& config );\n\n} // end namespace Catch\n\n#endif // CATCH_COMMANDLINE_HPP_INCLUDED\n\nnamespace Catch {\n\n    // TODO: Use C++17 `inline` variables\n    constexpr int UnspecifiedErrorExitCode = 1;\n    constexpr int NoTestsRunExitCode = 2;\n    constexpr int UnmatchedTestSpecExitCode = 3;\n    constexpr int AllTestsSkippedExitCode = 4;\n    constexpr int InvalidTestSpecExitCode = 5;\n    constexpr int TestFailureExitCode = 42;\n\n    class Session : Detail::NonCopyable {\n    public:\n\n        Session();\n        ~Session();\n\n        void showHelp() const;\n        void libIdentify();\n\n        int applyCommandLine( int argc, char const * const * argv );\n    #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)\n        int applyCommandLine( int argc, wchar_t const * const * argv );\n    #endif\n\n        void useConfigData( ConfigData const& configData );\n\n        template<typename CharT>\n        int run(int argc, CharT const * const argv[]) {\n            if (m_startupExceptions)\n                return 1;\n            int returnCode = applyCommandLine(argc, argv);\n            if (returnCode == 0)\n                returnCode = run();\n            return returnCode;\n        }\n\n        int run();\n\n        Clara::Parser const& cli() const;\n        void cli( Clara::Parser const& newParser );\n        ConfigData& configData();\n        Config& config();\n    private:\n        int runInternal();\n\n        Clara::Parser m_cli;\n        ConfigData m_configData;\n        Detail::unique_ptr<Config> m_config;\n        bool m_startupExceptions = false;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_SESSION_HPP_INCLUDED\n\n\n#ifndef CATCH_TAG_ALIAS_HPP_INCLUDED\n#define CATCH_TAG_ALIAS_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch {\n\n    struct TagAlias {\n        TagAlias(std::string const& _tag, SourceLineInfo _lineInfo):\n            tag(_tag),\n            lineInfo(_lineInfo)\n        {}\n\n        std::string tag;\n        SourceLineInfo lineInfo;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_TAG_ALIAS_HPP_INCLUDED\n\n\n#ifndef CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED\n#define CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED\n\n\nnamespace Catch {\n\n    struct RegistrarForTagAliases {\n        RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );\n    };\n\n} // end namespace Catch\n\n#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n    namespace{ const Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED\n\n\n#ifndef CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED\n#define CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED\n\n// We need this suppression to leak, because it took until GCC 10\n// for the front end to handle local suppression via _Pragma properly\n// inside templates (so `TEMPLATE_TEST_CASE` and co).\n// **THIS IS DIFFERENT FOR STANDARD TESTS, WHERE GCC 9 IS SUFFICIENT**\n#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10\n#pragma GCC diagnostic ignored \"-Wparentheses\"\n#endif\n\n\n\n\n#ifndef CATCH_TEST_MACROS_HPP_INCLUDED\n#define CATCH_TEST_MACROS_HPP_INCLUDED\n\n\n\n#ifndef CATCH_TEST_MACRO_IMPL_HPP_INCLUDED\n#define CATCH_TEST_MACRO_IMPL_HPP_INCLUDED\n\n\n\n#ifndef CATCH_ASSERTION_HANDLER_HPP_INCLUDED\n#define CATCH_ASSERTION_HANDLER_HPP_INCLUDED\n\n\n\n#ifndef CATCH_DECOMPOSER_HPP_INCLUDED\n#define CATCH_DECOMPOSER_HPP_INCLUDED\n\n\n\n#ifndef CATCH_COMPARE_TRAITS_HPP_INCLUDED\n#define CATCH_COMPARE_TRAITS_HPP_INCLUDED\n\n\n#include <type_traits>\n\nnamespace Catch {\n    namespace Detail {\n\n#if defined( __GNUC__ ) && !defined( __clang__ )\n#    pragma GCC diagnostic push\n    // GCC likes to complain about comparing bool with 0, in the decltype()\n    // that defines the comparable traits below.\n#    pragma GCC diagnostic ignored \"-Wbool-compare\"\n    // \"ordered comparison of pointer with integer zero\" same as above,\n    // but it does not have a separate warning flag to suppress\n#    pragma GCC diagnostic ignored \"-Wextra\"\n    // Did you know that comparing floats with `0` directly\n    // is super-duper dangerous in unevaluated context?\n#    pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n\n#if defined( __clang__ )\n#    pragma clang diagnostic push\n    // Did you know that comparing floats with `0` directly\n    // is super-duper dangerous in unevaluated context?\n#    pragma clang diagnostic ignored \"-Wfloat-equal\"\n#endif\n\n#define CATCH_DEFINE_COMPARABLE_TRAIT( id, op )                               \\\n    template <typename, typename, typename = void>                            \\\n    struct is_##id##_comparable : std::false_type {};                         \\\n    template <typename T, typename U>                                         \\\n    struct is_##id##_comparable<                                              \\\n        T,                                                                    \\\n        U,                                                                    \\\n        void_t<decltype( std::declval<T>() op std::declval<U>() )>>           \\\n        : std::true_type {};                                                  \\\n    template <typename, typename = void>                                      \\\n    struct is_##id##_0_comparable : std::false_type {};                       \\\n    template <typename T>                                                     \\\n    struct is_##id##_0_comparable<T,                                          \\\n                                  void_t<decltype( std::declval<T>() op 0 )>> \\\n        : std::true_type {};\n\n        // We need all 6 pre-spaceship comparison ops: <, <=, >, >=, ==, !=\n        CATCH_DEFINE_COMPARABLE_TRAIT( lt, < )\n        CATCH_DEFINE_COMPARABLE_TRAIT( le, <= )\n        CATCH_DEFINE_COMPARABLE_TRAIT( gt, > )\n        CATCH_DEFINE_COMPARABLE_TRAIT( ge, >= )\n        CATCH_DEFINE_COMPARABLE_TRAIT( eq, == )\n        CATCH_DEFINE_COMPARABLE_TRAIT( ne, != )\n\n#undef CATCH_DEFINE_COMPARABLE_TRAIT\n\n#if defined( __GNUC__ ) && !defined( __clang__ )\n#    pragma GCC diagnostic pop\n#endif\n#if defined( __clang__ )\n#    pragma clang diagnostic pop\n#endif\n\n\n    } // namespace Detail\n} // namespace Catch\n\n#endif // CATCH_COMPARE_TRAITS_HPP_INCLUDED\n\n\n#ifndef CATCH_LOGICAL_TRAITS_HPP_INCLUDED\n#define CATCH_LOGICAL_TRAITS_HPP_INCLUDED\n\n#include <type_traits>\n\nnamespace Catch {\nnamespace Detail {\n\n#if defined( __cpp_lib_logical_traits ) && __cpp_lib_logical_traits >= 201510\n\n    using std::conjunction;\n    using std::disjunction;\n    using std::negation;\n\n#else\n\n    template <class...> struct conjunction : std::true_type {};\n    template <class B1> struct conjunction<B1> : B1 {};\n    template <class B1, class... Bn>\n    struct conjunction<B1, Bn...>\n        : std::conditional_t<bool( B1::value ), conjunction<Bn...>, B1> {};\n\n    template <class...> struct disjunction : std::false_type {};\n    template <class B1> struct disjunction<B1> : B1 {};\n    template <class B1, class... Bn>\n    struct disjunction<B1, Bn...>\n        : std::conditional_t<bool( B1::value ), B1, disjunction<Bn...>> {};\n\n    template <class B>\n    struct negation : std::integral_constant<bool, !bool(B::value)> {};\n\n#endif\n\n} // namespace Detail\n} // namespace Catch\n\n#endif // CATCH_LOGICAL_TRAITS_HPP_INCLUDED\n\n#include <type_traits>\n#include <iosfwd>\n\n/** \\file\n * Why does decomposing look the way it does:\n *\n * Conceptually, decomposing is simple. We change `REQUIRE( a == b )` into\n * `Decomposer{} <= a == b`, so that `Decomposer{} <= a` is evaluated first,\n * and our custom operator is used for `a == b`, because `a` is transformed\n * into `ExprLhs<T&>` and then into `BinaryExpr<T&, U&>`.\n *\n * In practice, decomposing ends up a mess, because we have to support\n * various fun things.\n *\n * 1) Types that are only comparable with literal 0, and they do this by\n *    comparing against a magic type with pointer constructor and deleted\n *    other constructors. Example: `REQUIRE((a <=> b) == 0)` in libstdc++\n *\n * 2) Types that are only comparable with literal 0, and they do this by\n *    comparing against a magic type with consteval integer constructor.\n *    Example: `REQUIRE((a <=> b) == 0)` in current MSVC STL.\n *\n * 3) Types that have no linkage, and so we cannot form a reference to\n *    them. Example: some implementations of traits.\n *\n * 4) Starting with C++20, when the compiler sees `a == b`, it also uses\n *    `b == a` when constructing the overload set. For us this means that\n *    when the compiler handles `ExprLhs<T> == b`, it also tries to resolve\n *    the overload set for `b == ExprLhs<T>`.\n *\n * To accommodate these use cases, decomposer ended up rather complex.\n *\n * 1) These types are handled by adding SFINAE overloads to our comparison\n *    operators, checking whether `T == U` are comparable with the given\n *    operator, and if not, whether T (or U) are comparable with literal 0.\n *    If yes, the overload compares T (or U) with 0 literal inline in the\n *    definition.\n *\n *    Note that for extra correctness, we check  that the other type is\n *    either an `int` (literal 0 is captured as `int` by templates), or\n *    a `long` (some platforms use 0L for `NULL` and we want to support\n *    that for pointer comparisons).\n *\n * 2) For these types, `is_foo_comparable<T, int>` is true, but letting\n *    them fall into the overload that actually does `T == int` causes\n *    compilation error. Handling them requires that the decomposition\n *    is `constexpr`, so that P2564R3 applies and the `consteval` from\n *    their accompanying magic type is propagated through the `constexpr`\n *    call stack.\n *\n *    However this is not enough to handle these types automatically,\n *    because our default is to capture types by reference, to avoid\n *    runtime copies. While these references cannot become dangling,\n *    they outlive the constexpr context and thus the default capture\n *    path cannot be actually constexpr.\n *\n *    The solution is to capture these types by value, by explicitly\n *    specializing `Catch::capture_by_value` for them. Catch2 provides\n *    specialization for `std::foo_ordering`s, but users can specialize\n *    the trait for their own types as well.\n *\n * 3) If a type has no linkage, we also cannot capture it by reference.\n *    The solution is once again to capture them by value. We handle\n *    the common cases by using `std::is_arithmetic` as the default\n *    for `Catch::capture_by_value`, but that is only a some-effort\n *    heuristic. But as with 2), users can specialize `capture_by_value`\n *    for their own types as needed.\n *\n * 4) To support C++20 and make the SFINAE on our decomposing operators\n *    work, the SFINAE has to happen in return type, rather than in\n *    a template type. This is due to our use of logical type traits\n *    (`conjunction`/`disjunction`/`negation`), that we use to workaround\n *    an issue in older (9-) versions of GCC. I still blame C++20 for\n *    this, because without the comparison order switching, the logical\n *    traits could still be used in template type.\n *\n * There are also other side concerns, e.g. supporting both `REQUIRE(a)`\n * and `REQUIRE(a == b)`, or making `REQUIRE_THAT(a, IsEqual(b))` slot\n * nicely into the same expression handling logic, but these are rather\n * straightforward and add only a bit of complexity (e.g. common base\n * class for decomposed expressions).\n */\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable:4389) // '==' : signed/unsigned mismatch\n#pragma warning(disable:4018) // more \"signed/unsigned mismatch\"\n#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform)\n#pragma warning(disable:4180) // qualifier applied to function type has no meaning\n#pragma warning(disable:4800) // Forcing result to true or false\n#endif\n\n#ifdef __clang__\n#  pragma clang diagnostic push\n#  pragma clang diagnostic ignored \"-Wsign-compare\"\n#  pragma clang diagnostic ignored \"-Wnon-virtual-dtor\"\n#elif defined __GNUC__\n#  pragma GCC diagnostic push\n#  pragma GCC diagnostic ignored \"-Wsign-compare\"\n#  pragma GCC diagnostic ignored \"-Wnon-virtual-dtor\"\n#endif\n\n#if defined(CATCH_CPP20_OR_GREATER) && __has_include(<compare>)\n#  include <compare>\n#    if defined( __cpp_lib_three_way_comparison ) && \\\n            __cpp_lib_three_way_comparison >= 201907L\n#      define CATCH_CONFIG_CPP20_COMPARE_OVERLOADS\n#    endif\n#endif\n\nnamespace Catch {\n\n    namespace Detail {\n        // This was added in C++20, but we require only C++14 for now.\n        template <typename T>\n        using RemoveCVRef_t = std::remove_cv_t<std::remove_reference_t<T>>;\n    }\n\n    // Note: There is nothing that stops us from extending this,\n    //       e.g. to `std::is_scalar`, but the more encompassing\n    //       traits are usually also more expensive. For now we\n    //       keep this as it used to be and it can be changed later.\n    template <typename T>\n    struct capture_by_value\n        : std::integral_constant<bool, std::is_arithmetic<T>{}> {};\n\n#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS )\n    template <>\n    struct capture_by_value<std::strong_ordering> : std::true_type {};\n    template <>\n    struct capture_by_value<std::weak_ordering> : std::true_type {};\n    template <>\n    struct capture_by_value<std::partial_ordering> : std::true_type {};\n#endif\n\n    template <typename T>\n    struct always_false : std::false_type {};\n\n    class ITransientExpression {\n        bool m_isBinaryExpression;\n        bool m_result;\n\n    protected:\n        ~ITransientExpression() = default;\n\n    public:\n        constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; }\n        constexpr auto getResult() const -> bool { return m_result; }\n        //! This function **has** to be overridden by the derived class.\n        virtual void streamReconstructedExpression( std::ostream& os ) const;\n\n        constexpr ITransientExpression( bool isBinaryExpression, bool result )\n        :   m_isBinaryExpression( isBinaryExpression ),\n            m_result( result )\n        {}\n\n        constexpr ITransientExpression( ITransientExpression const& ) = default;\n        constexpr ITransientExpression& operator=( ITransientExpression const& ) = default;\n\n        friend std::ostream& operator<<(std::ostream& out, ITransientExpression const& expr) {\n            expr.streamReconstructedExpression(out);\n            return out;\n        }\n    };\n\n    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );\n\n    template<typename LhsT, typename RhsT>\n    class BinaryExpr  : public ITransientExpression {\n        LhsT m_lhs;\n        StringRef m_op;\n        RhsT m_rhs;\n\n        void streamReconstructedExpression( std::ostream &os ) const override {\n            formatReconstructedExpression\n                    ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) );\n        }\n\n    public:\n        constexpr BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs )\n        :   ITransientExpression{ true, comparisonResult },\n            m_lhs( lhs ),\n            m_op( op ),\n            m_rhs( rhs )\n        {}\n\n        template<typename T>\n        auto operator && ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator || ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator == ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator != ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator > ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator < ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator >= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator <= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n    };\n\n    template<typename LhsT>\n    class UnaryExpr : public ITransientExpression {\n        LhsT m_lhs;\n\n        void streamReconstructedExpression( std::ostream &os ) const override {\n            os << Catch::Detail::stringify( m_lhs );\n        }\n\n    public:\n        explicit constexpr UnaryExpr( LhsT lhs )\n        :   ITransientExpression{ false, static_cast<bool>(lhs) },\n            m_lhs( lhs )\n        {}\n    };\n\n\n    template<typename LhsT>\n    class ExprLhs {\n        LhsT m_lhs;\n    public:\n        explicit constexpr ExprLhs( LhsT lhs ) : m_lhs( lhs ) {}\n\n#define CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( id, op )           \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs )             \\\n        -> std::enable_if_t<                                                   \\\n            Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>,      \\\n                                Detail::negation<capture_by_value<             \\\n                                    Detail::RemoveCVRef_t<RhsT>>>>::value,     \\\n            BinaryExpr<LhsT, RhsT const&>> {                                   \\\n        return {                                                               \\\n            static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \\\n    }                                                                          \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \\\n        -> std::enable_if_t<                                                   \\\n            Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>,      \\\n                                capture_by_value<RhsT>>::value,                \\\n            BinaryExpr<LhsT, RhsT>> {                                          \\\n        return {                                                               \\\n            static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \\\n    }                                                                          \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \\\n        -> std::enable_if_t<                                                   \\\n            Detail::conjunction<                                               \\\n                Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>,    \\\n                Detail::is_eq_0_comparable<LhsT>,                              \\\n              /* We allow long because we want `ptr op NULL` to be accepted */ \\\n                Detail::disjunction<std::is_same<RhsT, int>,                   \\\n                                    std::is_same<RhsT, long>>>::value,         \\\n            BinaryExpr<LhsT, RhsT>> {                                          \\\n        if ( rhs != 0 ) { throw_test_failure_exception(); }                    \\\n        return {                                                               \\\n            static_cast<bool>( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs };   \\\n    }                                                                          \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \\\n        -> std::enable_if_t<                                                   \\\n            Detail::conjunction<                                               \\\n                Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>,    \\\n                Detail::is_eq_0_comparable<RhsT>,                              \\\n              /* We allow long because we want `ptr op NULL` to be accepted */ \\\n                Detail::disjunction<std::is_same<LhsT, int>,                   \\\n                                    std::is_same<LhsT, long>>>::value,         \\\n            BinaryExpr<LhsT, RhsT>> {                                          \\\n        if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); }              \\\n        return { static_cast<bool>( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs };    \\\n    }\n\n        CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( eq, == )\n        CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( ne, != )\n\n    #undef CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR\n\n\n#define CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( id, op )         \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs )             \\\n        -> std::enable_if_t<                                                   \\\n            Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>,      \\\n                                Detail::negation<capture_by_value<             \\\n                                    Detail::RemoveCVRef_t<RhsT>>>>::value,     \\\n            BinaryExpr<LhsT, RhsT const&>> {                                   \\\n        return {                                                               \\\n            static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \\\n    }                                                                          \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \\\n        -> std::enable_if_t<                                                   \\\n            Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>,      \\\n                                capture_by_value<RhsT>>::value,                \\\n            BinaryExpr<LhsT, RhsT>> {                                          \\\n        return {                                                               \\\n            static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \\\n    }                                                                          \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \\\n        -> std::enable_if_t<                                                   \\\n            Detail::conjunction<                                               \\\n                Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>,    \\\n                Detail::is_##id##_0_comparable<LhsT>,                          \\\n                std::is_same<RhsT, int>>::value,                               \\\n            BinaryExpr<LhsT, RhsT>> {                                          \\\n        if ( rhs != 0 ) { throw_test_failure_exception(); }                    \\\n        return {                                                               \\\n            static_cast<bool>( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs };   \\\n    }                                                                          \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \\\n        -> std::enable_if_t<                                                   \\\n            Detail::conjunction<                                               \\\n                Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>,    \\\n                Detail::is_##id##_0_comparable<RhsT>,                          \\\n                std::is_same<LhsT, int>>::value,                               \\\n            BinaryExpr<LhsT, RhsT>> {                                          \\\n        if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); }              \\\n        return { static_cast<bool>( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs };    \\\n    }\n\n        CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( lt, < )\n        CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( le, <= )\n        CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( gt, > )\n        CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( ge, >= )\n\n    #undef CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR\n\n\n#define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( op )                        \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs )             \\\n        -> std::enable_if_t<                                                   \\\n            !capture_by_value<Detail::RemoveCVRef_t<RhsT>>::value,             \\\n            BinaryExpr<LhsT, RhsT const&>> {                                   \\\n        return {                                                               \\\n            static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \\\n    }                                                                          \\\n    template <typename RhsT>                                                   \\\n    constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs )               \\\n        -> std::enable_if_t<capture_by_value<RhsT>::value,                     \\\n                            BinaryExpr<LhsT, RhsT>> {                          \\\n        return {                                                               \\\n            static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \\\n    }\n\n        CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(|)\n        CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(&)\n        CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(^)\n\n    #undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR\n\n        template<typename RhsT>\n        friend auto operator && ( ExprLhs &&, RhsT && ) -> BinaryExpr<LhsT, RhsT const&> {\n            static_assert(always_false<RhsT>::value,\n            \"operator&& is not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename RhsT>\n        friend auto operator || ( ExprLhs &&, RhsT && ) -> BinaryExpr<LhsT, RhsT const&> {\n            static_assert(always_false<RhsT>::value,\n            \"operator|| is not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        constexpr auto makeUnaryExpr() const -> UnaryExpr<LhsT> {\n            return UnaryExpr<LhsT>{ m_lhs };\n        }\n    };\n\n    struct Decomposer {\n        template <typename T,\n                  std::enable_if_t<!capture_by_value<Detail::RemoveCVRef_t<T>>::value,\n                      int> = 0>\n        constexpr friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs<T const&> {\n            return ExprLhs<const T&>{ lhs };\n        }\n\n        template <typename T,\n                  std::enable_if_t<capture_by_value<T>::value, int> = 0>\n        constexpr friend auto operator <= ( Decomposer &&, T value ) -> ExprLhs<T> {\n            return ExprLhs<T>{ value };\n        }\n    };\n\n} // end namespace Catch\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n#ifdef __clang__\n#  pragma clang diagnostic pop\n#elif defined __GNUC__\n#  pragma GCC diagnostic pop\n#endif\n\n#endif // CATCH_DECOMPOSER_HPP_INCLUDED\n\n#include <string>\n\nnamespace Catch {\n\n    struct AssertionReaction {\n        bool shouldDebugBreak = false;\n        bool shouldThrow = false;\n        bool shouldSkip = false;\n    };\n\n    class AssertionHandler {\n        AssertionInfo m_assertionInfo;\n        AssertionReaction m_reaction;\n        bool m_completed = false;\n        IResultCapture& m_resultCapture;\n\n    public:\n        AssertionHandler\n            (   StringRef macroName,\n                SourceLineInfo const& lineInfo,\n                StringRef capturedExpression,\n                ResultDisposition::Flags resultDisposition );\n        ~AssertionHandler() {\n            if ( !m_completed ) {\n                m_resultCapture.handleIncomplete( m_assertionInfo );\n            }\n        }\n\n\n        template<typename T>\n        constexpr void handleExpr( ExprLhs<T> const& expr ) {\n            handleExpr( expr.makeUnaryExpr() );\n        }\n        void handleExpr( ITransientExpression const& expr );\n\n        void handleMessage(ResultWas::OfType resultType, std::string&& message);\n\n        void handleExceptionThrownAsExpected();\n        void handleUnexpectedExceptionNotThrown();\n        void handleExceptionNotThrownAsExpected();\n        void handleThrowingCallSkipped();\n        void handleUnexpectedInflightException();\n\n        void complete();\n\n        // query\n        auto allowThrows() const -> bool;\n    };\n\n    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str );\n\n} // namespace Catch\n\n#endif // CATCH_ASSERTION_HANDLER_HPP_INCLUDED\n\n\n#ifndef CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED\n#define CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED\n\n\n#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION)\n  #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__##_catch_sr\n#else\n  #define CATCH_INTERNAL_STRINGIFY(...) \"Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION\"_catch_sr\n#endif\n\n#endif // CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED\n\n// We need this suppression to leak, because it took until GCC 10\n// for the front end to handle local suppression via _Pragma properly\n#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ <= 9\n  #pragma GCC diagnostic ignored \"-Wparentheses\"\n#endif\n\n#if !defined(CATCH_CONFIG_DISABLE)\n\n#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n\n///////////////////////////////////////////////////////////////////////////////\n// Another way to speed-up compilation is to omit local try-catch for REQUIRE*\n// macros.\n#define INTERNAL_CATCH_TRY\n#define INTERNAL_CATCH_CATCH( capturer )\n\n#else // CATCH_CONFIG_FAST_COMPILE\n\n#define INTERNAL_CATCH_TRY try\n#define INTERNAL_CATCH_CATCH( handler ) catch(...) { (handler).handleUnexpectedInflightException(); }\n\n#endif\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \\\n    do { /* NOLINT(bugprone-infinite-loop) */ \\\n        /* The expression should not be evaluated, but warnings should hopefully be checked */ \\\n        CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \\\n        INTERNAL_CATCH_TRY { \\\n            CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n            CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \\\n            catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); /* NOLINT(bugprone-chained-comparison) */ \\\n            CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \\\n        catchAssertionHandler.complete(); \\\n    } while( (void)0, (false) && static_cast<const bool&>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look\n    // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&.\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \\\n    INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \\\n    if( Catch::getResultCapture().lastAssertionPassed() )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \\\n    INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \\\n    if( !Catch::getResultCapture().lastAssertionPassed() )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \\\n        try { \\\n            CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n            CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \\\n            static_cast<void>(__VA_ARGS__); \\\n            CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n            catchAssertionHandler.handleExceptionNotThrownAsExpected(); \\\n        } \\\n        catch( ... ) { \\\n            catchAssertionHandler.handleUnexpectedInflightException(); \\\n        } \\\n        catchAssertionHandler.complete(); \\\n    } while( false )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n                CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \\\n                CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \\\n                static_cast<void>(__VA_ARGS__); \\\n                CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( ... ) { \\\n                catchAssertionHandler.handleExceptionThrownAsExpected(); \\\n            } \\\n        else \\\n            catchAssertionHandler.handleThrowingCallSkipped(); \\\n        catchAssertionHandler.complete(); \\\n    } while( false )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) \", \" CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n                CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \\\n                CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \\\n                static_cast<void>(expr); \\\n                CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( exceptionType const& ) { \\\n                catchAssertionHandler.handleExceptionThrownAsExpected(); \\\n            } \\\n            catch( ... ) { \\\n                catchAssertionHandler.handleUnexpectedInflightException(); \\\n            } \\\n        else \\\n            catchAssertionHandler.handleThrowingCallSkipped(); \\\n        catchAssertionHandler.complete(); \\\n    } while( false )\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// Although this is matcher-based, it can be used with just a string\n#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) \", \" CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n                CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \\\n                CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \\\n                static_cast<void>(__VA_ARGS__); \\\n                CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( ... ) { \\\n                Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher ); \\\n            } \\\n        else \\\n            catchAssertionHandler.handleThrowingCallSkipped(); \\\n        catchAssertionHandler.complete(); \\\n    } while( false )\n\n#endif // CATCH_CONFIG_DISABLE\n\n#endif // CATCH_TEST_MACRO_IMPL_HPP_INCLUDED\n\n\n#ifndef CATCH_SECTION_HPP_INCLUDED\n#define CATCH_SECTION_HPP_INCLUDED\n\n\n\n\n/** \\file\n * Wrapper for the STATIC_ANALYSIS_SUPPORT configuration option\n *\n * Some of Catch2's macros can be defined differently to work better with\n * static analysis tools, like clang-tidy or coverity.\n * Currently the main use case is to show that `SECTION`s are executed\n * exclusively, and not all in one run of a `TEST_CASE`.\n */\n\n#ifndef CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED\n#define CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED\n\n\n#if defined(__clang_analyzer__) || defined(__COVERITY__)\n    #define CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT\n#endif\n\n#if defined( CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT ) && \\\n    !defined( CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ) && \\\n    !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT )\n#    define CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT\n#endif\n\n\n#endif // CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED\n\n\n#ifndef CATCH_TIMER_HPP_INCLUDED\n#define CATCH_TIMER_HPP_INCLUDED\n\n#include <cstdint>\n\nnamespace Catch {\n\n    class Timer {\n        uint64_t m_nanoseconds = 0;\n    public:\n        void start();\n        auto getElapsedNanoseconds() const -> uint64_t;\n        auto getElapsedMicroseconds() const -> uint64_t;\n        auto getElapsedMilliseconds() const -> unsigned int;\n        auto getElapsedSeconds() const -> double;\n    };\n\n} // namespace Catch\n\n#endif // CATCH_TIMER_HPP_INCLUDED\n\nnamespace Catch {\n\n    class Section : Detail::NonCopyable {\n    public:\n        Section( SectionInfo&& info );\n        Section( SourceLineInfo const& _lineInfo,\n                 StringRef _name,\n                 const char* const = nullptr );\n        ~Section();\n\n        // This indicates whether the section should be executed or not\n        explicit operator bool() const;\n\n    private:\n        SectionInfo m_info;\n\n        Counts m_assertions;\n        bool m_sectionIncluded;\n        Timer m_timer;\n    };\n\n} // end namespace Catch\n\n#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT)\n#    define INTERNAL_CATCH_SECTION( ... )                                 \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                         \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS                  \\\n        if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME(            \\\n                 catch_internal_Section ) =                               \\\n                 Catch::Section( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#    define INTERNAL_CATCH_DYNAMIC_SECTION( ... )                     \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                     \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS              \\\n        if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME(        \\\n                 catch_internal_Section ) =                           \\\n                 Catch::SectionInfo(                                  \\\n                     CATCH_INTERNAL_LINEINFO,                         \\\n                     ( Catch::ReusableStringStream() << __VA_ARGS__ ) \\\n                         .str() ) )                                   \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#else\n\n// These section definitions imply that at most one section at one level\n// will be entered (because only one section's __LINE__ can be equal to\n// the dummy `catchInternalSectionHint` variable from `TEST_CASE`).\n\nnamespace Catch {\n    namespace Detail {\n        // Intentionally without linkage, as it should only be used as a dummy\n        // symbol for static analysis.\n        // The arguments are used as a dummy for checking warnings in the passed\n        // expressions.\n        int GetNewSectionHint( StringRef, const char* const = nullptr );\n    } // namespace Detail\n} // namespace Catch\n\n\n#    define INTERNAL_CATCH_SECTION( ... )                                   \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                           \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS                    \\\n        CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS                             \\\n        if ( [[maybe_unused]] const int catchInternalPreviousSectionHint =  \\\n                 catchInternalSectionHint,                                  \\\n             catchInternalSectionHint =                                     \\\n                 Catch::Detail::GetNewSectionHint(__VA_ARGS__);             \\\n             catchInternalPreviousSectionHint == __LINE__ )                 \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#    define INTERNAL_CATCH_DYNAMIC_SECTION( ... )                           \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                           \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS                    \\\n        CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS                             \\\n        if ( [[maybe_unused]] const int catchInternalPreviousSectionHint =  \\\n                 catchInternalSectionHint,                                  \\\n             catchInternalSectionHint = Catch::Detail::GetNewSectionHint(   \\\n                ( Catch::ReusableStringStream() << __VA_ARGS__ ).str());    \\\n             catchInternalPreviousSectionHint == __LINE__ )                 \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#endif\n\n\n#endif // CATCH_SECTION_HPP_INCLUDED\n\n\n#ifndef CATCH_TEST_REGISTRY_HPP_INCLUDED\n#define CATCH_TEST_REGISTRY_HPP_INCLUDED\n\n\n\n#ifndef CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED\n#define CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED\n\nnamespace Catch {\n\n    class ITestInvoker {\n    public:\n        virtual void prepareTestCase();\n        virtual void tearDownTestCase();\n        virtual void invoke() const = 0;\n        virtual ~ITestInvoker(); // = default\n    };\n\n} // namespace Catch\n\n#endif // CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED\n\n\n#ifndef CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED\n#define CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED\n\n#define INTERNAL_CATCH_EXPAND1( param ) INTERNAL_CATCH_EXPAND2( param )\n#define INTERNAL_CATCH_EXPAND2( ... ) INTERNAL_CATCH_NO##__VA_ARGS__\n#define INTERNAL_CATCH_DEF( ... ) INTERNAL_CATCH_DEF __VA_ARGS__\n#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF\n\n#define INTERNAL_CATCH_REMOVE_PARENS( ... ) \\\n    INTERNAL_CATCH_EXPAND1( INTERNAL_CATCH_DEF __VA_ARGS__ )\n\n#endif // CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED\n\n// GCC 5 and older do not properly handle disabling unused-variable warning\n// with a _Pragma. This means that we have to leak the suppression to the\n// user code as well :-(\n#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5\n#pragma GCC diagnostic ignored \"-Wunused-variable\"\n#endif\n\n\n\nnamespace Catch {\n\ntemplate<typename C>\nclass TestInvokerAsMethod : public ITestInvoker {\n    void (C::*m_testAsMethod)();\npublic:\n    constexpr TestInvokerAsMethod( void ( C::*testAsMethod )() ) noexcept:\n        m_testAsMethod( testAsMethod ) {}\n\n    void invoke() const override {\n        C obj;\n        (obj.*m_testAsMethod)();\n    }\n};\n\nDetail::unique_ptr<ITestInvoker> makeTestInvoker( void(*testAsFunction)() );\n\ntemplate<typename C>\nDetail::unique_ptr<ITestInvoker> makeTestInvoker( void (C::*testAsMethod)() ) {\n    return Detail::make_unique<TestInvokerAsMethod<C>>( testAsMethod );\n}\n\ntemplate <typename C>\nclass TestInvokerFixture : public ITestInvoker {\n    void ( C::*m_testAsMethod )() const;\n    Detail::unique_ptr<C> m_fixture = nullptr;\n\npublic:\n    constexpr TestInvokerFixture( void ( C::*testAsMethod )() const ) noexcept:\n        m_testAsMethod( testAsMethod ) {}\n\n    void prepareTestCase() override {\n        m_fixture = Detail::make_unique<C>();\n    }\n\n    void tearDownTestCase() override {\n        m_fixture.reset();\n    }\n\n    void invoke() const override {\n        auto* f = m_fixture.get();\n        ( f->*m_testAsMethod )();\n    }\n};\n\ntemplate<typename C>\nDetail::unique_ptr<ITestInvoker> makeTestInvokerFixture( void ( C::*testAsMethod )() const ) {\n    return Detail::make_unique<TestInvokerFixture<C>>( testAsMethod );\n}\n\nstruct NameAndTags {\n    constexpr NameAndTags( StringRef name_ = StringRef(),\n                           StringRef tags_ = StringRef() ) noexcept:\n        name( name_ ), tags( tags_ ) {}\n    StringRef name;\n    StringRef tags;\n};\n\nstruct AutoReg : Detail::NonCopyable {\n    AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept;\n};\n\n} // end namespace Catch\n\n#if defined(CATCH_CONFIG_DISABLE)\n    #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \\\n        static inline void TestName()\n    #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \\\n        namespace{                        \\\n            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \\\n                void test();              \\\n            };                            \\\n        }                                 \\\n        void TestName::test()\n#endif\n\n\n#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT)\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \\\n        static void TestName(); \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n        namespace{ const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        static void TestName()\n    #define INTERNAL_CATCH_TESTCASE( ... ) \\\n        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__ )\n\n#else  // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT\n\n\n// Dummy registrator for the dumy test case macros\nnamespace Catch {\n    namespace Detail {\n        struct DummyUse {\n            DummyUse( void ( * )( int ), Catch::NameAndTags const& );\n        };\n    } // namespace Detail\n} // namespace Catch\n\n// Note that both the presence of the argument and its exact name are\n// necessary for the section support.\n\n// We provide a shadowed variable so that a `SECTION` inside non-`TEST_CASE`\n// tests can compile. The redefined `TEST_CASE` shadows this with param.\nstatic int catchInternalSectionHint = 0;\n\n#    define INTERNAL_CATCH_TESTCASE2( fname, ... )                         \\\n        static void fname( int );                                          \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                          \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                           \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS                   \\\n        static const Catch::Detail::DummyUse INTERNAL_CATCH_UNIQUE_NAME(   \\\n            dummyUser )( &(fname), Catch::NameAndTags{ __VA_ARGS__ } );    \\\n        CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS                            \\\n        static void fname( [[maybe_unused]] int catchInternalSectionHint ) \\\n            CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n#    define INTERNAL_CATCH_TESTCASE( ... ) \\\n        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( dummyFunction ), __VA_ARGS__ )\n\n\n#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n        namespace{ \\\n            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \\\n                void test(); \\\n            }; \\\n            const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \\\n            Catch::makeTestInvoker( &TestName::test ),                    \\\n            CATCH_INTERNAL_LINEINFO,                                      \\\n            #ClassName##_catch_sr,                                        \\\n            Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \\\n        } \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        void TestName::test()\n    #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \\\n        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ )\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2( TestName, ClassName, ... )      \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                             \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                              \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS                      \\\n        namespace {                                                           \\\n            struct TestName : INTERNAL_CATCH_REMOVE_PARENS( ClassName ) {     \\\n                void test() const;                                            \\\n            };                                                                \\\n            const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \\\n                Catch::makeTestInvokerFixture( &TestName::test ),                    \\\n                CATCH_INTERNAL_LINEINFO,                                      \\\n                #ClassName##_catch_sr,                                        \\\n                Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */             \\\n        }                                                                     \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                              \\\n        void TestName::test() const\n    #define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( ClassName, ... )    \\\n        INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ )\n\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n        namespace {                                                           \\\n        const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \\\n            Catch::makeTestInvoker( &QualifiedMethod ),                   \\\n            CATCH_INTERNAL_LINEINFO,                                      \\\n            \"&\" #QualifiedMethod##_catch_sr,                              \\\n            Catch::NameAndTags{ __VA_ARGS__ } );                          \\\n    } /* NOLINT */ \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \\\n        do { \\\n            CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n            CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n            CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \\\n            CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        } while(false)\n\n\n#endif // CATCH_TEST_REGISTRY_HPP_INCLUDED\n\n\n#ifndef CATCH_UNREACHABLE_HPP_INCLUDED\n#define CATCH_UNREACHABLE_HPP_INCLUDED\n\n/**\\file\n * Polyfill `std::unreachable`\n *\n * We need something like `std::unreachable` to tell the compiler that\n * some macros, e.g. `FAIL` or `SKIP`, do not continue execution in normal\n * manner, and should handle it as such, e.g. not warn if there is no return\n * from non-void function after a `FAIL` or `SKIP`.\n */\n\n#include <exception>\n\n#if defined( __cpp_lib_unreachable ) && __cpp_lib_unreachable > 202202L\n#    include <utility>\nnamespace Catch {\n    namespace Detail {\n        using Unreachable = std::unreachable;\n    }\n} // namespace Catch\n\n#else // vv If we do not have std::unreachable, we implement something similar\n\nnamespace Catch {\n    namespace Detail {\n\n        [[noreturn]]\n        inline void Unreachable() noexcept {\n#    if defined( NDEBUG )\n#        if defined( _MSC_VER ) && !defined( __clang__ )\n            __assume( false );\n#        elif defined( __GNUC__ )\n            __builtin_unreachable();\n#        endif\n#    endif // ^^ NDEBUG\n            std::terminate();\n        }\n\n    } // namespace Detail\n} // end namespace Catch\n\n#endif\n\n#endif // CATCH_UNREACHABLE_HPP_INCLUDED\n\n\n// All of our user-facing macros support configuration toggle, that\n// forces them to be defined prefixed with CATCH_. We also like to\n// support another toggle that can minimize (disable) their implementation.\n// Given this, we have 4 different configuration options below\n\n#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)\n\n  #define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( \"CATCH_REQUIRE\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n  #define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( \"CATCH_REQUIRE_FALSE\", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n\n  #define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( \"CATCH_REQUIRE_THROWS\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n  #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"CATCH_REQUIRE_THROWS_AS\", exceptionType, Catch::ResultDisposition::Normal, expr )\n  #define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"CATCH_REQUIRE_NOTHROW\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n\n  #define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( \"CATCH_CHECK\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n  #define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( \"CATCH_CHECK_FALSE\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n  #define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( \"CATCH_CHECKED_IF\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n  #define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( \"CATCH_CHECKED_ELSE\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n  #define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( \"CATCH_CHECK_NOFAIL\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n\n  #define CATCH_CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( \"CATCH_CHECK_THROWS\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n  #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"CATCH_CHECK_THROWS_AS\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )\n  #define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"CATCH_CHECK_NOTHROW\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n\n  #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )\n  #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )\n  #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )\n  #define CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, __VA_ARGS__ )\n  #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )\n  #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )\n  #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )\n  #define CATCH_FAIL( ... ) do { \\\n           INTERNAL_CATCH_MSG(\"CATCH_FAIL\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ );  \\\n           Catch::Detail::Unreachable(); \\\n         } while ( false )\n  #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( \"CATCH_FAIL_CHECK\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n  #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( \"CATCH_SUCCEED\", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n  #define CATCH_SKIP( ... ) do { \\\n           INTERNAL_CATCH_MSG( \"CATCH_SKIP\", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \\\n           Catch::Detail::Unreachable(); \\\n         } while (false)\n\n\n  #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)\n    #define CATCH_STATIC_REQUIRE( ... )       static_assert(   __VA_ARGS__ ,      #__VA_ARGS__ );     CATCH_SUCCEED( #__VA_ARGS__ )\n    #define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\" ); CATCH_SUCCEED( #__VA_ARGS__ )\n    #define CATCH_STATIC_CHECK( ... )       static_assert(   __VA_ARGS__ ,      #__VA_ARGS__ );     CATCH_SUCCEED( #__VA_ARGS__ )\n    #define CATCH_STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\" ); CATCH_SUCCEED( #__VA_ARGS__ )\n  #else\n    #define CATCH_STATIC_REQUIRE( ... )       CATCH_REQUIRE( __VA_ARGS__ )\n    #define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ )\n    #define CATCH_STATIC_CHECK( ... )       CATCH_CHECK( __VA_ARGS__ )\n    #define CATCH_STATIC_CHECK_FALSE( ... ) CATCH_CHECK_FALSE( __VA_ARGS__ )\n  #endif\n\n\n  // \"BDD-style\" convenience wrappers\n  #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( \"Scenario: \" __VA_ARGS__ )\n  #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, \"Scenario: \" __VA_ARGS__ )\n  #define CATCH_GIVEN( desc )     INTERNAL_CATCH_DYNAMIC_SECTION( \"    Given: \" << desc )\n  #define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( \"And given: \" << desc )\n  #define CATCH_WHEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( \"     When: \" << desc )\n  #define CATCH_AND_WHEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( \" And when: \" << desc )\n  #define CATCH_THEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( \"     Then: \" << desc )\n  #define CATCH_AND_THEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( \"      And: \" << desc )\n\n#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, implemented | vv prefixed, disabled\n\n  #define CATCH_REQUIRE( ... )        (void)(0)\n  #define CATCH_REQUIRE_FALSE( ... )  (void)(0)\n\n  #define CATCH_REQUIRE_THROWS( ... ) (void)(0)\n  #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)\n  #define CATCH_REQUIRE_NOTHROW( ... ) (void)(0)\n\n  #define CATCH_CHECK( ... )         (void)(0)\n  #define CATCH_CHECK_FALSE( ... )   (void)(0)\n  #define CATCH_CHECKED_IF( ... )    if (__VA_ARGS__)\n  #define CATCH_CHECKED_ELSE( ... )  if (!(__VA_ARGS__))\n  #define CATCH_CHECK_NOFAIL( ... )  (void)(0)\n\n  #define CATCH_CHECK_THROWS( ... )  (void)(0)\n  #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0)\n  #define CATCH_CHECK_NOTHROW( ... ) (void)(0)\n\n  #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))\n  #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))\n  #define CATCH_METHOD_AS_TEST_CASE( method, ... )\n  #define CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))\n  #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0)\n  #define CATCH_SECTION( ... )\n  #define CATCH_DYNAMIC_SECTION( ... )\n  #define CATCH_FAIL( ... ) (void)(0)\n  #define CATCH_FAIL_CHECK( ... ) (void)(0)\n  #define CATCH_SUCCEED( ... ) (void)(0)\n  #define CATCH_SKIP( ... ) (void)(0)\n\n  #define CATCH_STATIC_REQUIRE( ... )       (void)(0)\n  #define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0)\n  #define CATCH_STATIC_CHECK( ... )       (void)(0)\n  #define CATCH_STATIC_CHECK_FALSE( ... ) (void)(0)\n\n  // \"BDD-style\" convenience wrappers\n  #define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))\n  #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className )\n  #define CATCH_GIVEN( desc )\n  #define CATCH_AND_GIVEN( desc )\n  #define CATCH_WHEN( desc )\n  #define CATCH_AND_WHEN( desc )\n  #define CATCH_THEN( desc )\n  #define CATCH_AND_THEN( desc )\n\n#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, disabled | vv unprefixed, implemented\n\n  #define REQUIRE( ... ) INTERNAL_CATCH_TEST( \"REQUIRE\", Catch::ResultDisposition::Normal, __VA_ARGS__  )\n  #define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( \"REQUIRE_FALSE\", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n\n  #define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( \"REQUIRE_THROWS\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n  #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"REQUIRE_THROWS_AS\", exceptionType, Catch::ResultDisposition::Normal, expr )\n  #define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"REQUIRE_NOTHROW\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n\n  #define CHECK( ... ) INTERNAL_CATCH_TEST( \"CHECK\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n  #define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( \"CHECK_FALSE\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n  #define CHECKED_IF( ... ) INTERNAL_CATCH_IF( \"CHECKED_IF\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n  #define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( \"CHECKED_ELSE\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n  #define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( \"CHECK_NOFAIL\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n\n  #define CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( \"CHECK_THROWS\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n  #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"CHECK_THROWS_AS\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )\n  #define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"CHECK_NOTHROW\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n\n  #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )\n  #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )\n  #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )\n  #define TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, __VA_ARGS__ )\n  #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )\n  #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )\n  #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )\n  #define FAIL( ... ) do { \\\n           INTERNAL_CATCH_MSG( \"FAIL\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \\\n           Catch::Detail::Unreachable(); \\\n         } while (false)\n  #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( \"FAIL_CHECK\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n  #define SUCCEED( ... ) INTERNAL_CATCH_MSG( \"SUCCEED\", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n  #define SKIP( ... ) do { \\\n           INTERNAL_CATCH_MSG( \"SKIP\", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \\\n           Catch::Detail::Unreachable(); \\\n         } while (false)\n\n\n  #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)\n    #define STATIC_REQUIRE( ... )       static_assert(   __VA_ARGS__,  #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ )\n    #define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\" ); SUCCEED( \"!(\" #__VA_ARGS__ \")\" )\n    #define STATIC_CHECK( ... )       static_assert(   __VA_ARGS__,  #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ )\n    #define STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\" ); SUCCEED( \"!(\" #__VA_ARGS__ \")\" )\n  #else\n    #define STATIC_REQUIRE( ... )       REQUIRE( __VA_ARGS__ )\n    #define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ )\n    #define STATIC_CHECK( ... )       CHECK( __VA_ARGS__ )\n    #define STATIC_CHECK_FALSE( ... ) CHECK_FALSE( __VA_ARGS__ )\n  #endif\n\n  // \"BDD-style\" convenience wrappers\n  #define SCENARIO( ... ) TEST_CASE( \"Scenario: \" __VA_ARGS__ )\n  #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, \"Scenario: \" __VA_ARGS__ )\n  #define GIVEN( desc )     INTERNAL_CATCH_DYNAMIC_SECTION( \"    Given: \" << desc )\n  #define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( \"And given: \" << desc )\n  #define WHEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( \"     When: \" << desc )\n  #define AND_WHEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( \" And when: \" << desc )\n  #define THEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( \"     Then: \" << desc )\n  #define AND_THEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( \"      And: \" << desc )\n\n#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ unprefixed, implemented | vv unprefixed, disabled\n\n  #define REQUIRE( ... )       (void)(0)\n  #define REQUIRE_FALSE( ... ) (void)(0)\n\n  #define REQUIRE_THROWS( ... ) (void)(0)\n  #define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)\n  #define REQUIRE_NOTHROW( ... ) (void)(0)\n\n  #define CHECK( ... ) (void)(0)\n  #define CHECK_FALSE( ... ) (void)(0)\n  #define CHECKED_IF( ... ) if (__VA_ARGS__)\n  #define CHECKED_ELSE( ... ) if (!(__VA_ARGS__))\n  #define CHECK_NOFAIL( ... ) (void)(0)\n\n  #define CHECK_THROWS( ... )  (void)(0)\n  #define CHECK_THROWS_AS( expr, exceptionType ) (void)(0)\n  #define CHECK_NOTHROW( ... ) (void)(0)\n\n  #define TEST_CASE( ... )  INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__)\n  #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ))\n  #define METHOD_AS_TEST_CASE( method, ... )\n  #define TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__)\n  #define REGISTER_TEST_CASE( Function, ... ) (void)(0)\n  #define SECTION( ... )\n  #define DYNAMIC_SECTION( ... )\n  #define FAIL( ... ) (void)(0)\n  #define FAIL_CHECK( ... ) (void)(0)\n  #define SUCCEED( ... ) (void)(0)\n  #define SKIP( ... ) (void)(0)\n\n  #define STATIC_REQUIRE( ... )       (void)(0)\n  #define STATIC_REQUIRE_FALSE( ... ) (void)(0)\n  #define STATIC_CHECK( ... )       (void)(0)\n  #define STATIC_CHECK_FALSE( ... ) (void)(0)\n\n  // \"BDD-style\" convenience wrappers\n  #define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ) )\n  #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className )\n\n  #define GIVEN( desc )\n  #define AND_GIVEN( desc )\n  #define WHEN( desc )\n  #define AND_WHEN( desc )\n  #define THEN( desc )\n  #define AND_THEN( desc )\n\n#endif // ^^ unprefixed, disabled\n\n// end of user facing macros\n\n#endif // CATCH_TEST_MACROS_HPP_INCLUDED\n\n\n#ifndef CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED\n#define CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED\n\n\n\n#ifndef CATCH_PREPROCESSOR_HPP_INCLUDED\n#define CATCH_PREPROCESSOR_HPP_INCLUDED\n\n\n#if defined(__GNUC__)\n// We need to silence \"empty __VA_ARGS__ warning\", and using just _Pragma does not work\n#pragma GCC system_header\n#endif\n\n\nnamespace Catch {\n    namespace Detail {\n        template <int N>\n        struct priority_tag : priority_tag<N - 1> {};\n        template <>\n        struct priority_tag<0> {};\n    }\n}\n\n#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__\n#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__)))\n\n#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__\n// MSVC needs more evaluations\n#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__)))\n#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__))\n#else\n#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL5(__VA_ARGS__)\n#endif\n\n#define CATCH_REC_END(...)\n#define CATCH_REC_OUT\n\n#define CATCH_EMPTY()\n#define CATCH_DEFER(id) id CATCH_EMPTY()\n\n#define CATCH_REC_GET_END2() 0, CATCH_REC_END\n#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2\n#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1\n#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT\n#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0)\n#define CATCH_REC_NEXT(test, next)  CATCH_REC_NEXT1(CATCH_REC_GET_END test, next)\n\n#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST2(f, x, peek, ...)   f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )\n\n#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...)   f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )\n\n// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results,\n// and passes userdata as the first parameter to each invocation,\n// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c)\n#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0))\n\n#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))\n\n#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__)\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__\n#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param))\n#else\n// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF\n#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__)\n#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__\n#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1)\n#endif\n\n#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__\n#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name)\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>(Catch::Detail::priority_tag<1>{}))\n#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))\n#else\n#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>(Catch::Detail::priority_tag<1>{})))\n#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)))\n#endif\n\n#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\\\n    CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__)\n\n#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0)\n#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1)\n#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2)\n#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3)\n#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4)\n#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5)\n#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6)\n#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7)\n#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8)\n#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9)\n#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10)\n\n#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N\n\n#define INTERNAL_CATCH_TYPE_GEN\\\n    template<typename...> struct TypeList {};\\\n    template<typename... Ts>\\\n    constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TypeList<Ts...> { return {}; }\\\n    template<template<typename...> class...> struct TemplateTypeList{};\\\n    template<template<typename...> class...Cs>\\\n    constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TemplateTypeList<Cs...> { return {}; }\\\n    template<typename...>\\\n    struct append;\\\n    template<typename...>\\\n    struct rewrap;\\\n    template<template<typename...> class, typename...>\\\n    struct create;\\\n    template<template<typename...> class, typename>\\\n    struct convert;\\\n    \\\n    template<typename T> \\\n    struct append<T> { using type = T; };\\\n    template< template<typename...> class L1, typename...E1, template<typename...> class L2, typename...E2, typename...Rest>\\\n    struct append<L1<E1...>, L2<E2...>, Rest...> { using type = typename append<L1<E1...,E2...>, Rest...>::type; };\\\n    template< template<typename...> class L1, typename...E1, typename...Rest>\\\n    struct append<L1<E1...>, TypeList<mpl_::na>, Rest...> { using type = L1<E1...>; };\\\n    \\\n    template< template<typename...> class Container, template<typename...> class List, typename...elems>\\\n    struct rewrap<TemplateTypeList<Container>, List<elems...>> { using type = TypeList<Container<elems...>>; };\\\n    template< template<typename...> class Container, template<typename...> class List, class...Elems, typename...Elements>\\\n    struct rewrap<TemplateTypeList<Container>, List<Elems...>, Elements...> { using type = typename append<TypeList<Container<Elems...>>, typename rewrap<TemplateTypeList<Container>, Elements...>::type>::type; };\\\n    \\\n    template<template <typename...> class Final, template< typename...> class...Containers, typename...Types>\\\n    struct create<Final, TemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<TemplateTypeList<Containers>, Types...>::type...>::type; };\\\n    template<template <typename...> class Final, template <typename...> class List, typename...Ts>\\\n    struct convert<Final, List<Ts...>> { using type = typename append<Final<>,TypeList<Ts>...>::type; };\n\n#define INTERNAL_CATCH_NTTP_1(signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)> struct Nttp{};\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> Nttp<__VA_ARGS__> { return {}; } \\\n    template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...> struct NttpTemplateTypeList{};\\\n    template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Cs>\\\n    constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \\\n    \\\n    template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> { using type = TypeList<Container<__VA_ARGS__>>; };\\\n    template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature), typename...Elements>\\\n    struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>, Elements...> { using type = typename append<TypeList<Container<__VA_ARGS__>>, typename rewrap<NttpTemplateTypeList<Container>, Elements...>::type>::type; };\\\n    template<template <typename...> class Final, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Containers, typename...Types>\\\n    struct create<Final, NttpTemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<NttpTemplateTypeList<Containers>, Types...>::type...>::type; };\n\n#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    static void TestName()\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    static void TestName()\n\n#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    static void TestName()\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature,...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    static void TestName()\n\n#define INTERNAL_CATCH_TYPES_REGISTER(TestFunc)\\\n    template<typename... Ts>\\\n    void reg_test(TypeList<Ts...>, Catch::NameAndTags nameAndTags)\\\n    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Ts...>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\\\n    }\n\n#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature, ...)\n#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\\\n    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\\\n    }\n\n#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...)\\\n    template<typename Type>\\\n    void reg_test(TypeList<Type>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\\\n    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestName<Type>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\\\n    }\n\n#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\\\n    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\\\n    }\n\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature)\\\n    template<typename TestType> \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<TestType> { \\\n        void test();\\\n    }\n\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> { \\\n        void test();\\\n    }\n\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature)\\\n    template<typename TestType> \\\n    void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<TestType>::test()\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \\\n    void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_NTTP_0\n#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__),INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_0)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)\n#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)\n#else\n#define INTERNAL_CATCH_NTTP_0(signature)\n#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)( __VA_ARGS__))\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__))\n#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__))\n#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__))\n#endif\n\n#endif // CATCH_PREPROCESSOR_HPP_INCLUDED\n\n\n// GCC 5 and older do not properly handle disabling unused-variable warning\n// with a _Pragma. This means that we have to leak the suppression to the\n// user code as well :-(\n#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5\n#pragma GCC diagnostic ignored \"-Wunused-variable\"\n#endif\n\n#if defined(CATCH_CONFIG_DISABLE)\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( TestName, TestFunc, Name, Tags, Signature, ... )  \\\n        INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... )    \\\n        namespace{                                                                                  \\\n            namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                      \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\\\n        }                                                                                           \\\n        }                                                                                           \\\n        INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n\n    #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ )\n    #else\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \\\n            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) )\n    #endif\n\n    #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ )\n    #else\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \\\n            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) )\n    #endif\n\n    #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ )\n    #else\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \\\n            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) )\n    #endif\n\n    #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ )\n    #else\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \\\n            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )\n    #endif\n#endif\n\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, Signature, ... )\\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \\\n        INTERNAL_CATCH_DECLARE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature));\\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\\\n            INTERNAL_CATCH_TYPE_GEN\\\n            INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            INTERNAL_CATCH_NTTP_REG_GEN(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            template<typename...Types> \\\n            struct TestName{\\\n                TestName(){\\\n                    size_t index = 0;                                    \\\n                    constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)}; /* NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,hicpp-avoid-c-arrays) */\\\n                    using expander = size_t[]; /* NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,hicpp-avoid-c-arrays) */\\\n                    (void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \\\n                }\\\n            };\\\n            static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\\\n            TestName<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\\\n            return 0;\\\n        }();\\\n        }\\\n        }\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \\\n        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) )\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \\\n        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                      \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                      \\\n        CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS                \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS       \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \\\n        template<typename TestType> static void TestFuncName();       \\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                     \\\n            INTERNAL_CATCH_TYPE_GEN                                                  \\\n            INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))         \\\n            template<typename... Types>                               \\\n            struct TestName {                                         \\\n                void reg_tests() {                                          \\\n                    size_t index = 0;                                    \\\n                    using expander = size_t[];                           \\\n                    constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\\\n                    constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\\\n                    constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\\\n                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */\\\n                }                                                     \\\n            };                                                        \\\n            static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \\\n                using TestInit = typename create<TestName, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>(Catch::Detail::priority_tag<1>{})), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \\\n                TestInit t;                                           \\\n                t.reg_tests();                                        \\\n                return 0;                                             \\\n            }();                                                      \\\n        }                                                             \\\n        }                                                             \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                       \\\n        template<typename TestType>                                   \\\n        static void TestFuncName()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\\\n        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename T,__VA_ARGS__)\n#else\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename T, __VA_ARGS__ ) )\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\\\n        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__)\n#else\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList)\\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \\\n        template<typename TestType> static void TestFunc();       \\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\\\n        INTERNAL_CATCH_TYPE_GEN\\\n        template<typename... Types>                               \\\n        struct TestName {                                         \\\n            void reg_tests() {                                          \\\n                size_t index = 0;                                    \\\n                using expander = size_t[];                           \\\n                (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name \" - \" INTERNAL_CATCH_STRINGIZE(TmplList) \" - \" + std::to_string(index), Tags } ), index++)... };/* NOLINT */\\\n            }                                                     \\\n        };\\\n        static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \\\n                using TestInit = typename convert<TestName, TmplList>::type; \\\n                TestInit t;                                           \\\n                t.reg_tests();                                        \\\n                return 0;                                             \\\n            }();                                                      \\\n        }}\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                       \\\n        template<typename TestType>                                   \\\n        static void TestFunc()\n\n    #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \\\n        INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, TmplList )\n\n\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \\\n            INTERNAL_CATCH_TYPE_GEN\\\n            INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\\\n            INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            template<typename...Types> \\\n            struct TestNameClass{\\\n                TestNameClass(){\\\n                    size_t index = 0;                                    \\\n                    constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\\\n                    using expander = size_t[];\\\n                    (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \\\n                }\\\n            };\\\n            static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\\\n                TestNameClass<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\\\n                return 0;\\\n        }();\\\n        }\\\n        }\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \\\n        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) )\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \\\n        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n        template<typename TestType> \\\n            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \\\n                void test();\\\n            };\\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) {\\\n            INTERNAL_CATCH_TYPE_GEN                  \\\n            INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            template<typename...Types>\\\n            struct TestNameClass{\\\n                void reg_tests(){\\\n                    std::size_t index = 0;\\\n                    using expander = std::size_t[];\\\n                    constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\\\n                    constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\\\n                    constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\\\n                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */ \\\n                }\\\n            };\\\n            static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\\\n                using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>(Catch::Detail::priority_tag<1>{})), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;\\\n                TestInit t;\\\n                t.reg_tests();\\\n                return 0;\\\n            }(); \\\n        }\\\n        }\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        template<typename TestType> \\\n        void TestName<TestType>::test()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\\\n        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, typename T, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) )\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\\\n        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, Signature, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \\\n        template<typename TestType> \\\n        struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \\\n            void test();\\\n        };\\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \\\n            INTERNAL_CATCH_TYPE_GEN\\\n            template<typename...Types>\\\n            struct TestNameClass{\\\n                void reg_tests(){\\\n                    size_t index = 0;\\\n                    using expander = size_t[];\\\n                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{ Name \" - \" INTERNAL_CATCH_STRINGIZE(TmplList) \" - \" + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \\\n                }\\\n            };\\\n            static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\\\n                using TestInit = typename convert<TestNameClass, TmplList>::type;\\\n                TestInit t;\\\n                t.reg_tests();\\\n                return 0;\\\n            }(); \\\n        }}\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        template<typename TestType> \\\n        void TestName<TestType>::test()\n\n#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \\\n        INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, TmplList )\n\n\n#endif // CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED\n\n\n#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)\n\n  #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n    #define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ )\n    #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n    #define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )\n    #define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ )\n    #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ )\n    #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ )\n    #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )\n    #define CATCH_TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__)\n    #define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ )\n  #else\n    #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n  #endif\n\n#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)\n\n  #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)\n    #define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)\n    #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)\n    #define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ )\n  #else\n    #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) )\n    #define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) )\n    #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) )\n    #define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) )\n  #endif\n\n  // When disabled, these can be shared between proper preprocessor and MSVC preprocessor\n  #define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n  #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n  #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n  #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n  #define CATCH_TEMPLATE_LIST_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__)\n  #define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n\n#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)\n\n  #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n    #define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ )\n    #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n    #define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )\n    #define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ )\n    #define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ )\n    #define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ )\n    #define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )\n    #define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__)\n    #define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ )\n  #else\n    #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )\n    #define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) )\n    #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n    #define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n    #define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) )\n    #define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) )\n    #define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n    #define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n    #define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) )\n    #define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n  #endif\n\n#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)\n\n  #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)\n    #define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)\n    #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)\n    #define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ )\n  #else\n    #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) )\n    #define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) )\n    #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) )\n    #define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) )\n  #endif\n\n  // When disabled, these can be shared between proper preprocessor and MSVC preprocessor\n  #define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ )\n  #define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ )\n  #define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n  #define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n  #define TEMPLATE_LIST_TEST_CASE( ... ) TEMPLATE_TEST_CASE(__VA_ARGS__)\n  #define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n\n#endif // end of user facing macro declarations\n\n\n#endif // CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED\n\n\n#ifndef CATCH_TEST_CASE_INFO_HPP_INCLUDED\n#define CATCH_TEST_CASE_INFO_HPP_INCLUDED\n\n\n\n#include <cstdint>\n#include <string>\n#include <vector>\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\nnamespace Catch {\n\n    /**\n     * A **view** of a tag string that provides case insensitive comparisons\n     *\n     * Note that in Catch2 internals, the square brackets around tags are\n     * not a part of tag's representation, so e.g. \"[cool-tag]\" is represented\n     * as \"cool-tag\" internally.\n     */\n    struct Tag {\n        constexpr Tag(StringRef original_):\n            original(original_)\n        {}\n        StringRef original;\n\n        friend bool operator< ( Tag const& lhs, Tag const& rhs );\n        friend bool operator==( Tag const& lhs, Tag const& rhs );\n    };\n\n    class ITestInvoker;\n    struct NameAndTags;\n\n    enum class TestCaseProperties : uint8_t {\n        None = 0,\n        IsHidden = 1 << 1,\n        ShouldFail = 1 << 2,\n        MayFail = 1 << 3,\n        Throws = 1 << 4,\n        NonPortable = 1 << 5,\n        Benchmark = 1 << 6\n    };\n\n    /**\n     * Various metadata about the test case.\n     *\n     * A test case is uniquely identified by its (class)name and tags\n     * combination, with source location being ignored, and other properties\n     * being determined from tags.\n     *\n     * Tags are kept sorted.\n     */\n    struct TestCaseInfo : Detail::NonCopyable {\n\n        TestCaseInfo(StringRef _className,\n                     NameAndTags const& _nameAndTags,\n                     SourceLineInfo const& _lineInfo);\n\n        bool isHidden() const;\n        bool throws() const;\n        bool okToFail() const;\n        bool expectedToFail() const;\n\n        // Adds the tag(s) with test's filename (for the -# flag)\n        void addFilenameTag();\n\n        //! Orders by name, classname and tags\n        friend bool operator<( TestCaseInfo const& lhs,\n                               TestCaseInfo const& rhs );\n\n\n        std::string tagsAsString() const;\n\n        std::string name;\n        StringRef className;\n    private:\n        std::string backingTags;\n        // Internally we copy tags to the backing storage and then add\n        // refs to this storage to the tags vector.\n        void internalAppendTag(StringRef tagString);\n    public:\n        std::vector<Tag> tags;\n        SourceLineInfo lineInfo;\n        TestCaseProperties properties = TestCaseProperties::None;\n    };\n\n    /**\n     * Wrapper over the test case information and the test case invoker\n     *\n     * Does not own either, and is specifically made to be cheap\n     * to copy around.\n     */\n    class TestCaseHandle {\n        TestCaseInfo* m_info;\n        ITestInvoker* m_invoker;\n    public:\n        constexpr TestCaseHandle(TestCaseInfo* info, ITestInvoker* invoker) :\n            m_info(info), m_invoker(invoker) {}\n\n        void prepareTestCase() const {\n            m_invoker->prepareTestCase();\n        }\n\n        void tearDownTestCase() const {\n            m_invoker->tearDownTestCase();\n        }\n\n        void invoke() const {\n            m_invoker->invoke();\n        }\n\n        constexpr TestCaseInfo const& getTestCaseInfo() const {\n            return *m_info;\n        }\n    };\n\n    Detail::unique_ptr<TestCaseInfo>\n    makeTestCaseInfo( StringRef className,\n                      NameAndTags const& nameAndTags,\n                      SourceLineInfo const& lineInfo );\n}\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n#endif // CATCH_TEST_CASE_INFO_HPP_INCLUDED\n\n\n#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED\n#define CATCH_TEST_RUN_INFO_HPP_INCLUDED\n\n\nnamespace Catch {\n\n    struct TestRunInfo {\n        constexpr TestRunInfo(StringRef _name) : name(_name) {}\n        StringRef name;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED\n\n\n#ifndef CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED\n#define CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED\n\n\n\n#ifndef CATCH_INTERFACES_EXCEPTION_HPP_INCLUDED\n#define CATCH_INTERFACES_EXCEPTION_HPP_INCLUDED\n\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\n    using exceptionTranslateFunction = std::string(*)();\n\n    class IExceptionTranslator;\n    using ExceptionTranslators = std::vector<Detail::unique_ptr<IExceptionTranslator const>>;\n\n    class IExceptionTranslator {\n    public:\n        virtual ~IExceptionTranslator(); // = default\n        virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0;\n    };\n\n    class IExceptionTranslatorRegistry {\n    public:\n        virtual ~IExceptionTranslatorRegistry(); // = default\n        virtual std::string translateActiveException() const = 0;\n    };\n\n} // namespace Catch\n\n#endif // CATCH_INTERFACES_EXCEPTION_HPP_INCLUDED\n\n#include <exception>\n\nnamespace Catch {\n    namespace Detail {\n        void registerTranslatorImpl(\n            Detail::unique_ptr<IExceptionTranslator>&& translator );\n    }\n\n    class ExceptionTranslatorRegistrar {\n        template<typename T>\n        class ExceptionTranslator : public IExceptionTranslator {\n        public:\n\n            constexpr ExceptionTranslator( std::string(*translateFunction)( T const& ) )\n            : m_translateFunction( translateFunction )\n            {}\n\n            std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n                try {\n                    if( it == itEnd )\n                        std::rethrow_exception(std::current_exception());\n                    else\n                        return (*it)->translate( it+1, itEnd );\n                }\n                catch( T const& ex ) {\n                    return m_translateFunction( ex );\n                }\n#else\n                return \"You should never get here!\";\n#endif\n            }\n\n        protected:\n            std::string(*m_translateFunction)( T const& );\n        };\n\n    public:\n        template<typename T>\n        ExceptionTranslatorRegistrar( std::string(*translateFunction)( T const& ) ) {\n            Detail::registerTranslatorImpl(\n                Detail::make_unique<ExceptionTranslator<T>>(\n                    translateFunction ) );\n        }\n    };\n\n} // namespace Catch\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \\\n    static std::string translatorName( signature ); \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n    namespace{ const Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n    static std::string translatorName( signature )\n\n#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )\n\n#if defined(CATCH_CONFIG_DISABLE)\n    #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \\\n            static std::string translatorName( signature )\n#endif\n\n\n// This macro is always prefixed\n#if !defined(CATCH_CONFIG_DISABLE)\n#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature )\n#else\n#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )\n#endif\n\n\n#endif // CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED\n\n\n#ifndef CATCH_VERSION_HPP_INCLUDED\n#define CATCH_VERSION_HPP_INCLUDED\n\n#include <iosfwd>\n\nnamespace Catch {\n\n    // Versioning information\n    struct Version {\n        Version( Version const& ) = delete;\n        Version& operator=( Version const& ) = delete;\n        Version(    unsigned int _majorVersion,\n                    unsigned int _minorVersion,\n                    unsigned int _patchNumber,\n                    char const * const _branchName,\n                    unsigned int _buildNumber );\n\n        unsigned int const majorVersion;\n        unsigned int const minorVersion;\n        unsigned int const patchNumber;\n\n        // buildNumber is only used if branchName is not null\n        char const * const branchName;\n        unsigned int const buildNumber;\n\n        friend std::ostream& operator << ( std::ostream& os, Version const& version );\n    };\n\n    Version const& libraryVersion();\n}\n\n#endif // CATCH_VERSION_HPP_INCLUDED\n\n\n#ifndef CATCH_VERSION_MACROS_HPP_INCLUDED\n#define CATCH_VERSION_MACROS_HPP_INCLUDED\n\n#define CATCH_VERSION_MAJOR 3\n#define CATCH_VERSION_MINOR 9\n#define CATCH_VERSION_PATCH 0\n\n#endif // CATCH_VERSION_MACROS_HPP_INCLUDED\n\n\n/** \\file\n * This is a convenience header for Catch2's Generator support. It includes\n * **all** of Catch2 headers related to generators.\n *\n * Generally the Catch2 users should use specific includes they need,\n * but this header can be used instead for ease-of-experimentation, or\n * just plain convenience, at the cost of (significantly) increased\n * compilation times.\n *\n * When a new header is added to either the `generators` folder,\n * or to the corresponding internal subfolder, it should be added here.\n */\n\n#ifndef CATCH_GENERATORS_ALL_HPP_INCLUDED\n#define CATCH_GENERATORS_ALL_HPP_INCLUDED\n\n\n\n#ifndef CATCH_GENERATOR_EXCEPTION_HPP_INCLUDED\n#define CATCH_GENERATOR_EXCEPTION_HPP_INCLUDED\n\n#include <exception>\n\nnamespace Catch {\n\n    // Exception type to be thrown when a Generator runs into an error,\n    // e.g. it cannot initialize the first return value based on\n    // runtime information\n    class GeneratorException : public std::exception {\n        const char* const m_msg = \"\";\n\n    public:\n        GeneratorException(const char* msg):\n            m_msg(msg)\n        {}\n\n        const char* what() const noexcept final;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_GENERATOR_EXCEPTION_HPP_INCLUDED\n\n\n#ifndef CATCH_GENERATORS_HPP_INCLUDED\n#define CATCH_GENERATORS_HPP_INCLUDED\n\n\n\n#ifndef CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED\n#define CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch {\n\n    namespace Generators {\n        class GeneratorUntypedBase {\n            // Caches result from `toStringImpl`, assume that when it is an\n            // empty string, the cache is invalidated.\n            mutable std::string m_stringReprCache;\n\n            // Counts based on `next` returning true\n            std::size_t m_currentElementIndex = 0;\n\n            /**\n             * Attempts to move the generator to the next element\n             *\n             * Returns true iff the move succeeded (and a valid element\n             * can be retrieved).\n             */\n            virtual bool next() = 0;\n\n            //! Customization point for `currentElementAsString`\n            virtual std::string stringifyImpl() const = 0;\n\n        public:\n            GeneratorUntypedBase() = default;\n            // Generation of copy ops is deprecated (and Clang will complain)\n            // if there is a user destructor defined\n            GeneratorUntypedBase(GeneratorUntypedBase const&) = default;\n            GeneratorUntypedBase& operator=(GeneratorUntypedBase const&) = default;\n\n            virtual ~GeneratorUntypedBase(); // = default;\n\n            /**\n             * Attempts to move the generator to the next element\n             *\n             * Serves as a non-virtual interface to `next`, so that the\n             * top level interface can provide sanity checking and shared\n             * features.\n             *\n             * As with `next`, returns true iff the move succeeded and\n             * the generator has new valid element to provide.\n             */\n            bool countedNext();\n\n            std::size_t currentElementIndex() const { return m_currentElementIndex; }\n\n            /**\n             * Returns generator's current element as user-friendly string.\n             *\n             * By default returns string equivalent to calling\n             * `Catch::Detail::stringify` on the current element, but generators\n             * can customize their implementation as needed.\n             *\n             * Not thread-safe due to internal caching.\n             *\n             * The returned ref is valid only until the generator instance\n             * is destructed, or it moves onto the next element, whichever\n             * comes first.\n             */\n            StringRef currentElementAsString() const;\n        };\n        using GeneratorBasePtr = Catch::Detail::unique_ptr<GeneratorUntypedBase>;\n\n    } // namespace Generators\n\n    class IGeneratorTracker {\n    public:\n        virtual ~IGeneratorTracker(); // = default;\n        virtual auto hasGenerator() const -> bool = 0;\n        virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0;\n        virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0;\n    };\n\n} // namespace Catch\n\n#endif // CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED\n\n#include <vector>\n#include <tuple>\n\nnamespace Catch {\n\nnamespace Generators {\n\nnamespace Detail {\n\n    //! Throws GeneratorException with the provided message\n    [[noreturn]]\n    void throw_generator_exception(char const * msg);\n\n} // end namespace detail\n\n    template<typename T>\n    class IGenerator : public GeneratorUntypedBase {\n        std::string stringifyImpl() const override {\n            return ::Catch::Detail::stringify( get() );\n        }\n\n    public:\n        // Returns the current element of the generator\n        //\n        // \\Precondition The generator is either freshly constructed,\n        // or the last call to `next()` returned true\n        virtual T const& get() const = 0;\n        using type = T;\n    };\n\n    template <typename T>\n    using GeneratorPtr = Catch::Detail::unique_ptr<IGenerator<T>>;\n\n    template <typename T>\n    class GeneratorWrapper final {\n        GeneratorPtr<T> m_generator;\n    public:\n        //! Takes ownership of the passed pointer.\n        GeneratorWrapper(IGenerator<T>* generator):\n            m_generator(generator) {}\n        GeneratorWrapper(GeneratorPtr<T> generator):\n            m_generator(CATCH_MOVE(generator)) {}\n\n        T const& get() const {\n            return m_generator->get();\n        }\n        bool next() {\n            return m_generator->countedNext();\n        }\n    };\n\n\n    template<typename T>\n    class SingleValueGenerator final : public IGenerator<T> {\n        T m_value;\n    public:\n        SingleValueGenerator(T const& value) :\n            m_value(value)\n        {}\n        SingleValueGenerator(T&& value):\n            m_value(CATCH_MOVE(value))\n        {}\n\n        T const& get() const override {\n            return m_value;\n        }\n        bool next() override {\n            return false;\n        }\n    };\n\n    template<typename T>\n    class FixedValuesGenerator final : public IGenerator<T> {\n        static_assert(!std::is_same<T, bool>::value,\n            \"FixedValuesGenerator does not support bools because of std::vector<bool>\"\n            \"specialization, use SingleValue Generator instead.\");\n        std::vector<T> m_values;\n        size_t m_idx = 0;\n    public:\n        FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {}\n\n        T const& get() const override {\n            return m_values[m_idx];\n        }\n        bool next() override {\n            ++m_idx;\n            return m_idx < m_values.size();\n        }\n    };\n\n    template <typename T, typename DecayedT = std::decay_t<T>>\n    GeneratorWrapper<DecayedT> value( T&& value ) {\n        return GeneratorWrapper<DecayedT>(\n            Catch::Detail::make_unique<SingleValueGenerator<DecayedT>>(\n                CATCH_FORWARD( value ) ) );\n    }\n    template <typename T>\n    GeneratorWrapper<T> values(std::initializer_list<T> values) {\n        return GeneratorWrapper<T>(Catch::Detail::make_unique<FixedValuesGenerator<T>>(values));\n    }\n\n    template<typename T>\n    class Generators : public IGenerator<T> {\n        std::vector<GeneratorWrapper<T>> m_generators;\n        size_t m_current = 0;\n\n        void add_generator( GeneratorWrapper<T>&& generator ) {\n            m_generators.emplace_back( CATCH_MOVE( generator ) );\n        }\n        void add_generator( T const& val ) {\n            m_generators.emplace_back( value( val ) );\n        }\n        void add_generator( T&& val ) {\n            m_generators.emplace_back( value( CATCH_MOVE( val ) ) );\n        }\n        template <typename U>\n        std::enable_if_t<!std::is_same<std::decay_t<U>, T>::value>\n        add_generator( U&& val ) {\n            add_generator( T( CATCH_FORWARD( val ) ) );\n        }\n\n        template <typename U> void add_generators( U&& valueOrGenerator ) {\n            add_generator( CATCH_FORWARD( valueOrGenerator ) );\n        }\n\n        template <typename U, typename... Gs>\n        void add_generators( U&& valueOrGenerator, Gs&&... moreGenerators ) {\n            add_generator( CATCH_FORWARD( valueOrGenerator ) );\n            add_generators( CATCH_FORWARD( moreGenerators )... );\n        }\n\n    public:\n        template <typename... Gs>\n        Generators(Gs &&... moreGenerators) {\n            m_generators.reserve(sizeof...(Gs));\n            add_generators(CATCH_FORWARD(moreGenerators)...);\n        }\n\n        T const& get() const override {\n            return m_generators[m_current].get();\n        }\n\n        bool next() override {\n            if (m_current >= m_generators.size()) {\n                return false;\n            }\n            const bool current_status = m_generators[m_current].next();\n            if (!current_status) {\n                ++m_current;\n            }\n            return m_current < m_generators.size();\n        }\n    };\n\n\n    template <typename... Ts>\n    GeneratorWrapper<std::tuple<std::decay_t<Ts>...>>\n    table( std::initializer_list<std::tuple<std::decay_t<Ts>...>> tuples ) {\n        return values<std::tuple<Ts...>>( tuples );\n    }\n\n    // Tag type to signal that a generator sequence should convert arguments to a specific type\n    template <typename T>\n    struct as {};\n\n    template<typename T, typename... Gs>\n    auto makeGenerators( GeneratorWrapper<T>&& generator, Gs &&... moreGenerators ) -> Generators<T> {\n        return Generators<T>(CATCH_MOVE(generator), CATCH_FORWARD(moreGenerators)...);\n    }\n    template<typename T>\n    auto makeGenerators( GeneratorWrapper<T>&& generator ) -> Generators<T> {\n        return Generators<T>(CATCH_MOVE(generator));\n    }\n    template<typename T, typename... Gs>\n    auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<std::decay_t<T>> {\n        return makeGenerators( value( CATCH_FORWARD( val ) ), CATCH_FORWARD( moreGenerators )... );\n    }\n    template<typename T, typename U, typename... Gs>\n    auto makeGenerators( as<T>, U&& val, Gs &&... moreGenerators ) -> Generators<T> {\n        return makeGenerators( value( T( CATCH_FORWARD( val ) ) ), CATCH_FORWARD( moreGenerators )... );\n    }\n\n    IGeneratorTracker* acquireGeneratorTracker( StringRef generatorName,\n                                                SourceLineInfo const& lineInfo );\n    IGeneratorTracker* createGeneratorTracker( StringRef generatorName,\n                                               SourceLineInfo lineInfo,\n                                               GeneratorBasePtr&& generator );\n\n    template<typename L>\n    auto generate( StringRef generatorName, SourceLineInfo const& lineInfo, L const& generatorExpression ) -> typename decltype(generatorExpression())::type {\n        using UnderlyingType = typename decltype(generatorExpression())::type;\n\n        IGeneratorTracker* tracker = acquireGeneratorTracker( generatorName, lineInfo );\n        // Creation of tracker is delayed after generator creation, so\n        // that constructing generator can fail without breaking everything.\n        if (!tracker) {\n            tracker = createGeneratorTracker(\n                generatorName,\n                lineInfo,\n                Catch::Detail::make_unique<Generators<UnderlyingType>>(\n                    generatorExpression() ) );\n        }\n\n        auto const& generator = static_cast<IGenerator<UnderlyingType> const&>( *tracker->getGenerator() );\n        return generator.get();\n    }\n\n} // namespace Generators\n} // namespace Catch\n\n#define CATCH_INTERNAL_GENERATOR_STRINGIZE_IMPL( ... ) #__VA_ARGS__##_catch_sr\n#define CATCH_INTERNAL_GENERATOR_STRINGIZE(...) CATCH_INTERNAL_GENERATOR_STRINGIZE_IMPL(__VA_ARGS__)\n\n#define GENERATE( ... ) \\\n    Catch::Generators::generate( CATCH_INTERNAL_GENERATOR_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \\\n                                 CATCH_INTERNAL_LINEINFO, \\\n                                 [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace)\n#define GENERATE_COPY( ... ) \\\n    Catch::Generators::generate( CATCH_INTERNAL_GENERATOR_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \\\n                                 CATCH_INTERNAL_LINEINFO, \\\n                                 [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace)\n#define GENERATE_REF( ... ) \\\n    Catch::Generators::generate( CATCH_INTERNAL_GENERATOR_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \\\n                                 CATCH_INTERNAL_LINEINFO, \\\n                                 [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace)\n\n#endif // CATCH_GENERATORS_HPP_INCLUDED\n\n\n#ifndef CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED\n#define CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED\n\n\n#include <cassert>\n\nnamespace Catch {\nnamespace Generators {\n\n    template <typename T>\n    class TakeGenerator final : public IGenerator<T> {\n        GeneratorWrapper<T> m_generator;\n        size_t m_returned = 0;\n        size_t m_target;\n    public:\n        TakeGenerator(size_t target, GeneratorWrapper<T>&& generator):\n            m_generator(CATCH_MOVE(generator)),\n            m_target(target)\n        {\n            assert(target != 0 && \"Empty generators are not allowed\");\n        }\n        T const& get() const override {\n            return m_generator.get();\n        }\n        bool next() override {\n            ++m_returned;\n            if (m_returned >= m_target) {\n                return false;\n            }\n\n            const auto success = m_generator.next();\n            // If the underlying generator does not contain enough values\n            // then we cut short as well\n            if (!success) {\n                m_returned = m_target;\n            }\n            return success;\n        }\n    };\n\n    template <typename T>\n    GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<T>(Catch::Detail::make_unique<TakeGenerator<T>>(target, CATCH_MOVE(generator)));\n    }\n\n\n    template <typename T, typename Predicate>\n    class FilterGenerator final : public IGenerator<T> {\n        GeneratorWrapper<T> m_generator;\n        Predicate m_predicate;\n    public:\n        template <typename P = Predicate>\n        FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator):\n            m_generator(CATCH_MOVE(generator)),\n            m_predicate(CATCH_FORWARD(pred))\n        {\n            if (!m_predicate(m_generator.get())) {\n                // It might happen that there are no values that pass the\n                // filter. In that case we throw an exception.\n                auto has_initial_value = next();\n                if (!has_initial_value) {\n                    Detail::throw_generator_exception(\"No valid value found in filtered generator\");\n                }\n            }\n        }\n\n        T const& get() const override {\n            return m_generator.get();\n        }\n\n        bool next() override {\n            bool success = m_generator.next();\n            if (!success) {\n                return false;\n            }\n            while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true);\n            return success;\n        }\n    };\n\n\n    template <typename T, typename Predicate>\n    GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, Predicate>>(CATCH_FORWARD(pred), CATCH_MOVE(generator)));\n    }\n\n    template <typename T>\n    class RepeatGenerator final : public IGenerator<T> {\n        static_assert(!std::is_same<T, bool>::value,\n            \"RepeatGenerator currently does not support bools\"\n            \"because of std::vector<bool> specialization\");\n        GeneratorWrapper<T> m_generator;\n        mutable std::vector<T> m_returned;\n        size_t m_target_repeats;\n        size_t m_current_repeat = 0;\n        size_t m_repeat_index = 0;\n    public:\n        RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator):\n            m_generator(CATCH_MOVE(generator)),\n            m_target_repeats(repeats)\n        {\n            assert(m_target_repeats > 0 && \"Repeat generator must repeat at least once\");\n        }\n\n        T const& get() const override {\n            if (m_current_repeat == 0) {\n                m_returned.push_back(m_generator.get());\n                return m_returned.back();\n            }\n            return m_returned[m_repeat_index];\n        }\n\n        bool next() override {\n            // There are 2 basic cases:\n            // 1) We are still reading the generator\n            // 2) We are reading our own cache\n\n            // In the first case, we need to poke the underlying generator.\n            // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache\n            if (m_current_repeat == 0) {\n                const auto success = m_generator.next();\n                if (!success) {\n                    ++m_current_repeat;\n                }\n                return m_current_repeat < m_target_repeats;\n            }\n\n            // In the second case, we need to move indices forward and check that we haven't run up against the end\n            ++m_repeat_index;\n            if (m_repeat_index == m_returned.size()) {\n                m_repeat_index = 0;\n                ++m_current_repeat;\n            }\n            return m_current_repeat < m_target_repeats;\n        }\n    };\n\n    template <typename T>\n    GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<T>(Catch::Detail::make_unique<RepeatGenerator<T>>(repeats, CATCH_MOVE(generator)));\n    }\n\n    template <typename T, typename U, typename Func>\n    class MapGenerator final : public IGenerator<T> {\n        // TBD: provide static assert for mapping function, for friendly error message\n        GeneratorWrapper<U> m_generator;\n        Func m_function;\n        // To avoid returning dangling reference, we have to save the values\n        T m_cache;\n    public:\n        template <typename F2 = Func>\n        MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) :\n            m_generator(CATCH_MOVE(generator)),\n            m_function(CATCH_FORWARD(function)),\n            m_cache(m_function(m_generator.get()))\n        {}\n\n        T const& get() const override {\n            return m_cache;\n        }\n        bool next() override {\n            const auto success = m_generator.next();\n            if (success) {\n                m_cache = m_function(m_generator.get());\n            }\n            return success;\n        }\n    };\n\n    template <typename Func, typename U, typename T = FunctionReturnType<Func, U>>\n    GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) {\n        return GeneratorWrapper<T>(\n            Catch::Detail::make_unique<MapGenerator<T, U, Func>>(CATCH_FORWARD(function), CATCH_MOVE(generator))\n        );\n    }\n\n    template <typename T, typename U, typename Func>\n    GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) {\n        return GeneratorWrapper<T>(\n            Catch::Detail::make_unique<MapGenerator<T, U, Func>>(CATCH_FORWARD(function), CATCH_MOVE(generator))\n        );\n    }\n\n    template <typename T>\n    class ChunkGenerator final : public IGenerator<std::vector<T>> {\n        std::vector<T> m_chunk;\n        size_t m_chunk_size;\n        GeneratorWrapper<T> m_generator;\n        bool m_used_up = false;\n    public:\n        ChunkGenerator(size_t size, GeneratorWrapper<T> generator) :\n            m_chunk_size(size), m_generator(CATCH_MOVE(generator))\n        {\n            m_chunk.reserve(m_chunk_size);\n            if (m_chunk_size != 0) {\n                m_chunk.push_back(m_generator.get());\n                for (size_t i = 1; i < m_chunk_size; ++i) {\n                    if (!m_generator.next()) {\n                        Detail::throw_generator_exception(\"Not enough values to initialize the first chunk\");\n                    }\n                    m_chunk.push_back(m_generator.get());\n                }\n            }\n        }\n        std::vector<T> const& get() const override {\n            return m_chunk;\n        }\n        bool next() override {\n            m_chunk.clear();\n            for (size_t idx = 0; idx < m_chunk_size; ++idx) {\n                if (!m_generator.next()) {\n                    return false;\n                }\n                m_chunk.push_back(m_generator.get());\n            }\n            return true;\n        }\n    };\n\n    template <typename T>\n    GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<std::vector<T>>(\n            Catch::Detail::make_unique<ChunkGenerator<T>>(size, CATCH_MOVE(generator))\n        );\n    }\n\n} // namespace Generators\n} // namespace Catch\n\n\n#endif // CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED\n\n\n#ifndef CATCH_GENERATORS_RANDOM_HPP_INCLUDED\n#define CATCH_GENERATORS_RANDOM_HPP_INCLUDED\n\n\n\n#ifndef CATCH_RANDOM_NUMBER_GENERATOR_HPP_INCLUDED\n#define CATCH_RANDOM_NUMBER_GENERATOR_HPP_INCLUDED\n\n#include <cstdint>\n\nnamespace Catch {\n\n    // This is a simple implementation of C++11 Uniform Random Number\n    // Generator. It does not provide all operators, because Catch2\n    // does not use it, but it should behave as expected inside stdlib's\n    // distributions.\n    // The implementation is based on the PCG family (http://pcg-random.org)\n    class SimplePcg32 {\n        using state_type = std::uint64_t;\n    public:\n        using result_type = std::uint32_t;\n        static constexpr result_type (min)() {\n            return 0;\n        }\n        static constexpr result_type (max)() {\n            return static_cast<result_type>(-1);\n        }\n\n        // Provide some default initial state for the default constructor\n        SimplePcg32():SimplePcg32(0xed743cc4U) {}\n\n        explicit SimplePcg32(result_type seed_);\n\n        void seed(result_type seed_);\n        void discard(uint64_t skip);\n\n        result_type operator()();\n\n    private:\n        friend bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs);\n        friend bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs);\n\n        // In theory we also need operator<< and operator>>\n        // In practice we do not use them, so we will skip them for now\n\n\n        std::uint64_t m_state;\n        // This part of the state determines which \"stream\" of the numbers\n        // is chosen -- we take it as a constant for Catch2, so we only\n        // need to deal with seeding the main state.\n        // Picked by reading 8 bytes from `/dev/random` :-)\n        static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_RANDOM_NUMBER_GENERATOR_HPP_INCLUDED\n\n\n\n#ifndef CATCH_UNIFORM_INTEGER_DISTRIBUTION_HPP_INCLUDED\n#define CATCH_UNIFORM_INTEGER_DISTRIBUTION_HPP_INCLUDED\n\n\n\n\n#ifndef CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED\n#define CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED\n\n#include <climits>\n#include <cstddef>\n#include <cstdint>\n#include <type_traits>\n\n// Note: We use the usual enable-disable-autodetect dance here even though\n//       we do not support these in CMake configuration options (yet?).\n//       It is highly unlikely that we will need to make these actually\n//       user-configurable, but this will make it simpler if weend up needing\n//       it, and it provides an escape hatch to the users who need it.\n#if defined( __SIZEOF_INT128__ )\n#    define CATCH_CONFIG_INTERNAL_UINT128\n// Unlike GCC, MSVC does not polyfill umul as mulh + mul pair on ARM machines.\n// Currently we do not bother doing this ourselves, but we could if it became\n// important for perf.\n#elif defined( _MSC_VER ) && defined( _M_X64 )\n#    define CATCH_CONFIG_INTERNAL_MSVC_UMUL128\n#endif\n\n#if defined( CATCH_CONFIG_INTERNAL_UINT128 ) && \\\n    !defined( CATCH_CONFIG_NO_UINT128 ) &&      \\\n    !defined( CATCH_CONFIG_UINT128 )\n#define CATCH_CONFIG_UINT128\n#endif\n\n#if defined( CATCH_CONFIG_INTERNAL_MSVC_UMUL128 ) && \\\n    !defined( CATCH_CONFIG_NO_MSVC_UMUL128 ) &&      \\\n    !defined( CATCH_CONFIG_MSVC_UMUL128 )\n#    define CATCH_CONFIG_MSVC_UMUL128\n#    include <intrin.h>\n#endif\n\n\nnamespace Catch {\n    namespace Detail {\n\n        template <std::size_t>\n        struct SizedUnsignedType;\n#define SizedUnsignedTypeHelper( TYPE )        \\\n    template <>                                \\\n    struct SizedUnsignedType<sizeof( TYPE )> { \\\n        using type = TYPE;                     \\\n    }\n\n        SizedUnsignedTypeHelper( std::uint8_t );\n        SizedUnsignedTypeHelper( std::uint16_t );\n        SizedUnsignedTypeHelper( std::uint32_t );\n        SizedUnsignedTypeHelper( std::uint64_t );\n#undef SizedUnsignedTypeHelper\n\n        template <std::size_t sz>\n        using SizedUnsignedType_t = typename SizedUnsignedType<sz>::type;\n\n        template <typename T>\n        using DoubleWidthUnsignedType_t = SizedUnsignedType_t<2 * sizeof( T )>;\n\n        template <typename T>\n        struct ExtendedMultResult {\n            T upper;\n            T lower;\n            constexpr bool operator==( ExtendedMultResult const& rhs ) const {\n                return upper == rhs.upper && lower == rhs.lower;\n            }\n        };\n\n        /**\n         * Returns 128 bit result of lhs * rhs using portable C++ code\n         *\n         * This implementation is almost twice as fast as naive long multiplication,\n         * and unlike intrinsic-based approach, it supports constexpr evaluation.\n         */\n        constexpr ExtendedMultResult<std::uint64_t>\n        extendedMultPortable(std::uint64_t lhs, std::uint64_t rhs) {\n#define CarryBits( x ) ( x >> 32 )\n#define Digits( x ) ( x & 0xFF'FF'FF'FF )\n            std::uint64_t lhs_low = Digits( lhs );\n            std::uint64_t rhs_low = Digits( rhs );\n            std::uint64_t low_low = ( lhs_low * rhs_low );\n            std::uint64_t high_high = CarryBits( lhs ) * CarryBits( rhs );\n\n            // We add in carry bits from low-low already\n            std::uint64_t high_low =\n                ( CarryBits( lhs ) * rhs_low ) + CarryBits( low_low );\n            // Note that we can add only low bits from high_low, to avoid\n            // overflow with large inputs\n            std::uint64_t low_high =\n                ( lhs_low * CarryBits( rhs ) ) + Digits( high_low );\n\n            return { high_high + CarryBits( high_low ) + CarryBits( low_high ),\n                     ( low_high << 32 ) | Digits( low_low ) };\n#undef CarryBits\n#undef Digits\n        }\n\n        //! Returns 128 bit result of lhs * rhs\n        inline ExtendedMultResult<std::uint64_t>\n        extendedMult( std::uint64_t lhs, std::uint64_t rhs ) {\n#if defined( CATCH_CONFIG_UINT128 )\n            auto result = __uint128_t( lhs ) * __uint128_t( rhs );\n            return { static_cast<std::uint64_t>( result >> 64 ),\n                     static_cast<std::uint64_t>( result ) };\n#elif defined( CATCH_CONFIG_MSVC_UMUL128 )\n            std::uint64_t high;\n            std::uint64_t low = _umul128( lhs, rhs, &high );\n            return { high, low };\n#else\n            return extendedMultPortable( lhs, rhs );\n#endif\n        }\n\n\n        template <typename UInt>\n        constexpr ExtendedMultResult<UInt> extendedMult( UInt lhs, UInt rhs ) {\n            static_assert( std::is_unsigned<UInt>::value,\n                           \"extendedMult can only handle unsigned integers\" );\n            static_assert( sizeof( UInt ) < sizeof( std::uint64_t ),\n                           \"Generic extendedMult can only handle types smaller \"\n                           \"than uint64_t\" );\n            using WideType = DoubleWidthUnsignedType_t<UInt>;\n\n            auto result = WideType( lhs ) * WideType( rhs );\n            return {\n                static_cast<UInt>( result >> ( CHAR_BIT * sizeof( UInt ) ) ),\n                static_cast<UInt>( result & UInt( -1 ) ) };\n        }\n\n\n        template <typename TargetType,\n                  typename Generator>\n            std::enable_if_t<sizeof(typename Generator::result_type) >= sizeof(TargetType),\n            TargetType> fillBitsFrom(Generator& gen) {\n            using gresult_type = typename Generator::result_type;\n            static_assert( std::is_unsigned<TargetType>::value, \"Only unsigned integers are supported\" );\n            static_assert( Generator::min() == 0 &&\n                           Generator::max() == static_cast<gresult_type>( -1 ),\n                           \"Generator must be able to output all numbers in its result type (effectively it must be a random bit generator)\" );\n\n            // We want to return the top bits from a generator, as they are\n            // usually considered higher quality.\n            constexpr auto generated_bits = sizeof( gresult_type ) * CHAR_BIT;\n            constexpr auto return_bits = sizeof( TargetType ) * CHAR_BIT;\n\n            return static_cast<TargetType>( gen() >>\n                                            ( generated_bits - return_bits) );\n        }\n\n        template <typename TargetType,\n                  typename Generator>\n            std::enable_if_t<sizeof(typename Generator::result_type) < sizeof(TargetType),\n            TargetType> fillBitsFrom(Generator& gen) {\n            using gresult_type = typename Generator::result_type;\n            static_assert( std::is_unsigned<TargetType>::value,\n                           \"Only unsigned integers are supported\" );\n            static_assert( Generator::min() == 0 &&\n                           Generator::max() == static_cast<gresult_type>( -1 ),\n                           \"Generator must be able to output all numbers in its result type (effectively it must be a random bit generator)\" );\n\n            constexpr auto generated_bits = sizeof( gresult_type ) * CHAR_BIT;\n            constexpr auto return_bits = sizeof( TargetType ) * CHAR_BIT;\n            std::size_t filled_bits = 0;\n            TargetType ret = 0;\n            do {\n                ret <<= generated_bits;\n                ret |= gen();\n                filled_bits += generated_bits;\n            } while ( filled_bits < return_bits );\n\n            return ret;\n        }\n\n        /*\n         * Transposes numbers into unsigned type while keeping their ordering\n         *\n         * This means that signed types are changed so that the ordering is\n         * [INT_MIN, ..., -1, 0, ..., INT_MAX], rather than order we would\n         * get by simple casting ([0, ..., INT_MAX, INT_MIN, ..., -1])\n         */\n        template <typename OriginalType, typename UnsignedType>\n        constexpr\n        std::enable_if_t<std::is_signed<OriginalType>::value, UnsignedType>\n        transposeToNaturalOrder( UnsignedType in ) {\n            static_assert(\n                sizeof( OriginalType ) == sizeof( UnsignedType ),\n                \"reordering requires the same sized types on both sides\" );\n            static_assert( std::is_unsigned<UnsignedType>::value,\n                           \"Input type must be unsigned\" );\n            // Assuming 2s complement (standardized in current C++), the\n            // positive and negative numbers are already internally ordered,\n            // and their difference is in the top bit. Swapping it orders\n            // them the desired way.\n            constexpr auto highest_bit =\n                UnsignedType( 1 ) << ( sizeof( UnsignedType ) * CHAR_BIT - 1 );\n            return static_cast<UnsignedType>( in ^ highest_bit );\n        }\n\n\n\n        template <typename OriginalType,\n                  typename UnsignedType>\n        constexpr\n        std::enable_if_t<std::is_unsigned<OriginalType>::value, UnsignedType>\n            transposeToNaturalOrder(UnsignedType in) {\n            static_assert(\n                sizeof( OriginalType ) == sizeof( UnsignedType ),\n                \"reordering requires the same sized types on both sides\" );\n            static_assert( std::is_unsigned<UnsignedType>::value, \"Input type must be unsigned\" );\n            // No reordering is needed for unsigned -> unsigned\n            return in;\n        }\n    } // namespace Detail\n} // namespace Catch\n\n#endif // CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED\n\nnamespace Catch {\n\n/**\n * Implementation of uniform distribution on integers.\n *\n * Unlike `std::uniform_int_distribution`, this implementation supports\n * various 1 byte integral types, including bool (but you should not\n * actually use it for bools).\n *\n * The underlying algorithm is based on the one described in \"Fast Random\n * Integer Generation in an Interval\" by Daniel Lemire, but has been\n * optimized under the assumption of reuse of the same distribution object.\n */\ntemplate <typename IntegerType>\nclass uniform_integer_distribution {\n    static_assert(std::is_integral<IntegerType>::value, \"...\");\n\n    using UnsignedIntegerType = Detail::SizedUnsignedType_t<sizeof(IntegerType)>;\n\n    // Only the left bound is stored, and we store it converted to its\n    // unsigned image. This avoids having to do the conversions inside\n    // the operator(), at the cost of having to do the conversion in\n    // the a() getter. The right bound is only needed in the b() getter,\n    // so we recompute it there from other stored data.\n    UnsignedIntegerType m_a;\n\n    // How many different values are there in [a, b]. a == b => 1, can be 0 for distribution over all values in the type.\n    UnsignedIntegerType m_ab_distance;\n\n    // We hoisted this out of the main generation function. Technically,\n    // this means that using this distribution will be slower than Lemire's\n    // algorithm if this distribution instance will be used only few times,\n    // but it will be faster if it is used many times. Since Catch2 uses\n    // distributions only to implement random generators, we assume that each\n    // distribution will be reused many times and this is an optimization.\n    UnsignedIntegerType m_rejection_threshold = 0;\n\n    static constexpr UnsignedIntegerType computeDistance(IntegerType a, IntegerType b) {\n        // This overflows and returns 0 if a == 0 and b == TYPE_MAX.\n        // We handle that later when generating the number.\n        return transposeTo(b) - transposeTo(a) + 1;\n    }\n\n    static constexpr UnsignedIntegerType computeRejectionThreshold(UnsignedIntegerType ab_distance) {\n        // distance == 0 means that we will return all possible values from\n        // the type's range, and that we shouldn't reject anything.\n        if ( ab_distance == 0 ) { return 0; }\n        return ( ~ab_distance + 1 ) % ab_distance;\n    }\n\n    static constexpr UnsignedIntegerType transposeTo(IntegerType in) {\n        return Detail::transposeToNaturalOrder<IntegerType>(\n            static_cast<UnsignedIntegerType>( in ) );\n    }\n    static constexpr IntegerType transposeBack(UnsignedIntegerType in) {\n        return static_cast<IntegerType>(\n            Detail::transposeToNaturalOrder<IntegerType>(in) );\n    }\n\npublic:\n    using result_type = IntegerType;\n\n    constexpr uniform_integer_distribution( IntegerType a, IntegerType b ):\n        m_a( transposeTo(a) ),\n        m_ab_distance( computeDistance(a, b) ),\n        m_rejection_threshold( computeRejectionThreshold(m_ab_distance) ) {\n        assert( a <= b );\n    }\n\n    template <typename Generator>\n    constexpr result_type operator()( Generator& g ) {\n        // All possible values of result_type are valid.\n        if ( m_ab_distance == 0 ) {\n            return transposeBack( Detail::fillBitsFrom<UnsignedIntegerType>( g ) );\n        }\n\n        auto random_number = Detail::fillBitsFrom<UnsignedIntegerType>( g );\n        auto emul = Detail::extendedMult( random_number, m_ab_distance );\n        // Unlike Lemire's algorithm we skip the ab_distance check, since\n        // we precomputed the rejection threshold, which is always tighter.\n        while (emul.lower < m_rejection_threshold) {\n            random_number = Detail::fillBitsFrom<UnsignedIntegerType>( g );\n            emul = Detail::extendedMult( random_number, m_ab_distance );\n        }\n\n        return transposeBack(m_a + emul.upper);\n    }\n\n    constexpr result_type a() const { return transposeBack(m_a); }\n    constexpr result_type b() const { return transposeBack(m_ab_distance + m_a - 1); }\n};\n\n} // end namespace Catch\n\n#endif // CATCH_UNIFORM_INTEGER_DISTRIBUTION_HPP_INCLUDED\n\n\n\n#ifndef CATCH_UNIFORM_FLOATING_POINT_DISTRIBUTION_HPP_INCLUDED\n#define CATCH_UNIFORM_FLOATING_POINT_DISTRIBUTION_HPP_INCLUDED\n\n\n\n\n#ifndef CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED\n#define CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED\n\n\n\n#ifndef CATCH_POLYFILLS_HPP_INCLUDED\n#define CATCH_POLYFILLS_HPP_INCLUDED\n\nnamespace Catch {\n\n    bool isnan(float f);\n    bool isnan(double d);\n\n    float nextafter(float x, float y);\n    double nextafter(double x, double y);\n\n}\n\n#endif // CATCH_POLYFILLS_HPP_INCLUDED\n\n#include <cassert>\n#include <cmath>\n#include <cstdint>\n#include <limits>\n#include <type_traits>\n\nnamespace Catch {\n\n    namespace Detail {\n        /**\n         * Returns the largest magnitude of 1-ULP distance inside the [a, b] range.\n         *\n         * Assumes `a < b`.\n         */\n        template <typename FloatType>\n        FloatType gamma(FloatType a, FloatType b) {\n            static_assert( std::is_floating_point<FloatType>::value,\n                           \"gamma returns the largest ULP magnitude within \"\n                           \"floating point range [a, b]. This only makes sense \"\n                           \"for floating point types\" );\n            assert( a <= b );\n\n            const auto gamma_up = Catch::nextafter( a, std::numeric_limits<FloatType>::infinity() ) - a;\n            const auto gamma_down = b - Catch::nextafter( b, -std::numeric_limits<FloatType>::infinity() );\n\n            return gamma_up < gamma_down ? gamma_down : gamma_up;\n        }\n\n        template <typename FloatingPoint>\n        struct DistanceTypePicker;\n        template <>\n        struct DistanceTypePicker<float> {\n            using type = std::uint32_t;\n        };\n        template <>\n        struct DistanceTypePicker<double> {\n            using type = std::uint64_t;\n        };\n\n        template <typename T>\n        using DistanceType = typename DistanceTypePicker<T>::type;\n\n#if defined( __GNUC__ ) || defined( __clang__ )\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n        /**\n         * Computes the number of equi-distant floats in [a, b]\n         *\n         * Since not every range can be split into equidistant floats\n         * exactly, we actually compute ceil(b/distance - a/distance),\n         * because in those cases we want to overcount.\n         *\n         * Uses modified Dekker's FastTwoSum algorithm to handle rounding.\n         */\n        template <typename FloatType>\n        DistanceType<FloatType>\n        count_equidistant_floats( FloatType a, FloatType b, FloatType distance ) {\n            assert( a <= b );\n            // We get distance as gamma for our uniform float distribution,\n            // so this will round perfectly.\n            const auto ag = a / distance;\n            const auto bg = b / distance;\n\n            const auto s = bg - ag;\n            const auto err = ( std::fabs( a ) <= std::fabs( b ) )\n                                 ? -ag - ( s - bg )\n                                 : bg - ( s + ag );\n            const auto ceil_s = static_cast<DistanceType<FloatType>>( std::ceil( s ) );\n\n            return ( ceil_s != s ) ? ceil_s : ceil_s + ( err > 0 );\n        }\n#if defined( __GNUC__ ) || defined( __clang__ )\n#    pragma GCC diagnostic pop\n#endif\n\n    }\n\n} // end namespace Catch\n\n#endif // CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED\n\n#include <cmath>\n#include <type_traits>\n\nnamespace Catch {\n\n    namespace Detail {\n#if defined( __GNUC__ ) || defined( __clang__ )\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n        // The issue with overflow only happens with maximal ULP and HUGE\n        // distance, e.g. when generating numbers in [-inf, inf] for given\n        // type. So we only check for the largest possible ULP in the\n        // type, and return something that does not overflow to inf in 1 mult.\n        constexpr std::uint64_t calculate_max_steps_in_one_go(double gamma) {\n            if ( gamma == 1.99584030953472e+292 ) { return 9007199254740991; }\n            return static_cast<std::uint64_t>( -1 );\n        }\n        constexpr std::uint32_t calculate_max_steps_in_one_go(float gamma) {\n            if ( gamma == 2.028241e+31f ) { return 16777215; }\n            return static_cast<std::uint32_t>( -1 );\n        }\n#if defined( __GNUC__ ) || defined( __clang__ )\n#    pragma GCC diagnostic pop\n#endif\n    }\n\n/**\n * Implementation of uniform distribution on floating point numbers.\n *\n * Note that we support only `float` and `double` types, because these\n * usually mean the same thing across different platform. `long double`\n * varies wildly by platform and thus we cannot provide reproducible\n * implementation. Also note that we don't implement all parts of\n * distribution per standard: this distribution is not serializable, nor\n * can the range be arbitrarily reset.\n *\n * The implementation also uses different approach than the one taken by\n * `std::uniform_real_distribution`, where instead of generating a number\n * between [0, 1) and then multiplying the range bounds with it, we first\n * split the [a, b] range into a set of equidistributed floating point\n * numbers, and then use uniform int distribution to pick which one to\n * return.\n *\n * This has the advantage of guaranteeing uniformity (the multiplication\n * method loses uniformity due to rounding when multiplying floats), except\n * for small non-uniformity at one side of the interval, where we have\n * to deal with the fact that not every interval is splittable into\n * equidistributed floats.\n *\n * Based on \"Drawing random floating-point numbers from an interval\" by\n * Frederic Goualard.\n */\ntemplate <typename FloatType>\nclass uniform_floating_point_distribution {\n    static_assert(std::is_floating_point<FloatType>::value, \"...\");\n    static_assert(!std::is_same<FloatType, long double>::value,\n                  \"We do not support long double due to inconsistent behaviour between platforms\");\n\n    using WidthType = Detail::DistanceType<FloatType>;\n\n    FloatType m_a, m_b;\n    FloatType m_ulp_magnitude;\n    WidthType m_floats_in_range;\n    uniform_integer_distribution<WidthType> m_int_dist;\n\n    // In specific cases, we can overflow into `inf` when computing the\n    // `steps * g` offset. To avoid this, we don't offset by more than this\n    // in one multiply + addition.\n    WidthType m_max_steps_in_one_go;\n    // We don't want to do the magnitude check every call to `operator()`\n    bool m_a_has_leq_magnitude;\n\npublic:\n    using result_type = FloatType;\n\n    uniform_floating_point_distribution( FloatType a, FloatType b ):\n        m_a( a ),\n        m_b( b ),\n        m_ulp_magnitude( Detail::gamma( m_a, m_b ) ),\n        m_floats_in_range( Detail::count_equidistant_floats( m_a, m_b, m_ulp_magnitude ) ),\n        m_int_dist(0, m_floats_in_range),\n        m_max_steps_in_one_go( Detail::calculate_max_steps_in_one_go(m_ulp_magnitude)),\n        m_a_has_leq_magnitude(std::fabs(m_a) <= std::fabs(m_b))\n    {\n        assert( a <= b );\n    }\n\n    template <typename Generator>\n    result_type operator()( Generator& g ) {\n        WidthType steps = m_int_dist( g );\n        if ( m_a_has_leq_magnitude ) {\n            if ( steps == m_floats_in_range ) { return m_a; }\n            auto b = m_b;\n            while (steps > m_max_steps_in_one_go) {\n                b -= m_max_steps_in_one_go * m_ulp_magnitude;\n                steps -= m_max_steps_in_one_go;\n            }\n            return b - steps * m_ulp_magnitude;\n        } else {\n            if ( steps == m_floats_in_range ) { return m_b; }\n            auto a = m_a;\n            while (steps > m_max_steps_in_one_go) {\n                a += m_max_steps_in_one_go * m_ulp_magnitude;\n                steps -= m_max_steps_in_one_go;\n            }\n            return a + steps * m_ulp_magnitude;\n        }\n    }\n\n    result_type a() const { return m_a; }\n    result_type b() const { return m_b; }\n};\n\n} // end namespace Catch\n\n#endif // CATCH_UNIFORM_FLOATING_POINT_DISTRIBUTION_HPP_INCLUDED\n\nnamespace Catch {\nnamespace Generators {\nnamespace Detail {\n    // Returns a suitable seed for a random floating generator based off\n    // the primary internal rng. It does so by taking current value from\n    // the rng and returning it as the seed.\n    std::uint32_t getSeed();\n}\n\ntemplate <typename Float>\nclass RandomFloatingGenerator final : public IGenerator<Float> {\n    Catch::SimplePcg32 m_rng;\n    Catch::uniform_floating_point_distribution<Float> m_dist;\n    Float m_current_number;\npublic:\n    RandomFloatingGenerator( Float a, Float b, std::uint32_t seed ):\n        m_rng(seed),\n        m_dist(a, b) {\n        static_cast<void>(next());\n    }\n\n    Float const& get() const override {\n        return m_current_number;\n    }\n    bool next() override {\n        m_current_number = m_dist(m_rng);\n        return true;\n    }\n};\n\ntemplate <>\nclass RandomFloatingGenerator<long double> final : public IGenerator<long double> {\n    // We still rely on <random> for this specialization, but we don't\n    // want to drag it into the header.\n    struct PImpl;\n    Catch::Detail::unique_ptr<PImpl> m_pimpl;\n    long double m_current_number;\n\npublic:\n    RandomFloatingGenerator( long double a, long double b, std::uint32_t seed );\n\n    long double const& get() const override { return m_current_number; }\n    bool next() override;\n\n    ~RandomFloatingGenerator() override; // = default\n};\n\ntemplate <typename Integer>\nclass RandomIntegerGenerator final : public IGenerator<Integer> {\n    Catch::SimplePcg32 m_rng;\n    Catch::uniform_integer_distribution<Integer> m_dist;\n    Integer m_current_number;\npublic:\n    RandomIntegerGenerator( Integer a, Integer b, std::uint32_t seed ):\n        m_rng(seed),\n        m_dist(a, b) {\n        static_cast<void>(next());\n    }\n\n    Integer const& get() const override {\n        return m_current_number;\n    }\n    bool next() override {\n        m_current_number = m_dist(m_rng);\n        return true;\n    }\n};\n\ntemplate <typename T>\nstd::enable_if_t<std::is_integral<T>::value, GeneratorWrapper<T>>\nrandom(T a, T b) {\n    return GeneratorWrapper<T>(\n        Catch::Detail::make_unique<RandomIntegerGenerator<T>>(a, b, Detail::getSeed())\n    );\n}\n\ntemplate <typename T>\nstd::enable_if_t<std::is_floating_point<T>::value,\nGeneratorWrapper<T>>\nrandom(T a, T b) {\n    return GeneratorWrapper<T>(\n        Catch::Detail::make_unique<RandomFloatingGenerator<T>>(a, b, Detail::getSeed())\n    );\n}\n\n\n} // namespace Generators\n} // namespace Catch\n\n\n#endif // CATCH_GENERATORS_RANDOM_HPP_INCLUDED\n\n\n#ifndef CATCH_GENERATORS_RANGE_HPP_INCLUDED\n#define CATCH_GENERATORS_RANGE_HPP_INCLUDED\n\n\n#include <iterator>\n#include <type_traits>\n\nnamespace Catch {\nnamespace Generators {\n\n\ntemplate <typename T>\nclass RangeGenerator final : public IGenerator<T> {\n    T m_current;\n    T m_end;\n    T m_step;\n    bool m_positive;\n\npublic:\n    RangeGenerator(T const& start, T const& end, T const& step):\n        m_current(start),\n        m_end(end),\n        m_step(step),\n        m_positive(m_step > T(0))\n    {\n        assert(m_current != m_end && \"Range start and end cannot be equal\");\n        assert(m_step != T(0) && \"Step size cannot be zero\");\n        assert(((m_positive && m_current <= m_end) || (!m_positive && m_current >= m_end)) && \"Step moves away from end\");\n    }\n\n    RangeGenerator(T const& start, T const& end):\n        RangeGenerator(start, end, (start < end) ? T(1) : T(-1))\n    {}\n\n    T const& get() const override {\n        return m_current;\n    }\n\n    bool next() override {\n        m_current += m_step;\n        return (m_positive) ? (m_current < m_end) : (m_current > m_end);\n    }\n};\n\ntemplate <typename T>\nGeneratorWrapper<T> range(T const& start, T const& end, T const& step) {\n    static_assert(std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, \"Type must be numeric\");\n    return GeneratorWrapper<T>(Catch::Detail::make_unique<RangeGenerator<T>>(start, end, step));\n}\n\ntemplate <typename T>\nGeneratorWrapper<T> range(T const& start, T const& end) {\n    static_assert(std::is_integral<T>::value && !std::is_same<T, bool>::value, \"Type must be an integer\");\n    return GeneratorWrapper<T>(Catch::Detail::make_unique<RangeGenerator<T>>(start, end));\n}\n\n\ntemplate <typename T>\nclass IteratorGenerator final : public IGenerator<T> {\n    static_assert(!std::is_same<T, bool>::value,\n        \"IteratorGenerator currently does not support bools\"\n        \"because of std::vector<bool> specialization\");\n\n    std::vector<T> m_elems;\n    size_t m_current = 0;\npublic:\n    template <typename InputIterator, typename InputSentinel>\n    IteratorGenerator(InputIterator first, InputSentinel last):m_elems(first, last) {\n        if (m_elems.empty()) {\n            Detail::throw_generator_exception(\"IteratorGenerator received no valid values\");\n        }\n    }\n\n    T const& get() const override {\n        return m_elems[m_current];\n    }\n\n    bool next() override {\n        ++m_current;\n        return m_current != m_elems.size();\n    }\n};\n\ntemplate <typename InputIterator,\n          typename InputSentinel,\n          typename ResultType = std::remove_const_t<typename std::iterator_traits<InputIterator>::value_type>>\nGeneratorWrapper<ResultType> from_range(InputIterator from, InputSentinel to) {\n    return GeneratorWrapper<ResultType>(Catch::Detail::make_unique<IteratorGenerator<ResultType>>(from, to));\n}\n\ntemplate <typename Container>\nauto from_range(Container const& cnt) {\n    using std::begin;\n    using std::end;\n    return from_range( begin( cnt ), end( cnt ) );\n}\n\n\n} // namespace Generators\n} // namespace Catch\n\n\n#endif // CATCH_GENERATORS_RANGE_HPP_INCLUDED\n\n#endif // CATCH_GENERATORS_ALL_HPP_INCLUDED\n\n\n/** \\file\n * This is a convenience header for Catch2's interfaces. It includes\n * **all** of Catch2 headers related to interfaces.\n *\n * Generally the Catch2 users should use specific includes they need,\n * but this header can be used instead for ease-of-experimentation, or\n * just plain convenience, at the cost of somewhat increased compilation\n * times.\n *\n * When a new header is added to either the `interfaces` folder, or to\n * the corresponding internal subfolder, it should be added here.\n */\n\n\n#ifndef CATCH_INTERFACES_ALL_HPP_INCLUDED\n#define CATCH_INTERFACES_ALL_HPP_INCLUDED\n\n\n\n#ifndef CATCH_INTERFACES_REPORTER_HPP_INCLUDED\n#define CATCH_INTERFACES_REPORTER_HPP_INCLUDED\n\n\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    struct ReporterDescription;\n    struct ListenerDescription;\n    struct TagInfo;\n    struct TestCaseInfo;\n    class TestCaseHandle;\n    class IConfig;\n    class IStream;\n    enum class ColourMode : std::uint8_t;\n\n    struct ReporterConfig {\n        ReporterConfig( IConfig const* _fullConfig,\n                        Detail::unique_ptr<IStream> _stream,\n                        ColourMode colourMode,\n                        std::map<std::string, std::string> customOptions );\n\n        ReporterConfig( ReporterConfig&& ) = default;\n        ReporterConfig& operator=( ReporterConfig&& ) = default;\n        ~ReporterConfig(); // = default\n\n        Detail::unique_ptr<IStream> takeStream() &&;\n        IConfig const* fullConfig() const;\n        ColourMode colourMode() const;\n        std::map<std::string, std::string> const& customOptions() const;\n\n    private:\n        Detail::unique_ptr<IStream> m_stream;\n        IConfig const* m_fullConfig;\n        ColourMode m_colourMode;\n        std::map<std::string, std::string> m_customOptions;\n    };\n\n    struct AssertionStats {\n        AssertionStats( AssertionResult const& _assertionResult,\n                        std::vector<MessageInfo> const& _infoMessages,\n                        Totals const& _totals );\n\n        AssertionStats( AssertionStats const& )              = default;\n        AssertionStats( AssertionStats && )                  = default;\n        AssertionStats& operator = ( AssertionStats const& ) = delete;\n        AssertionStats& operator = ( AssertionStats && )     = delete;\n\n        AssertionResult assertionResult;\n        std::vector<MessageInfo> infoMessages;\n        Totals totals;\n    };\n\n    struct SectionStats {\n        SectionStats(   SectionInfo&& _sectionInfo,\n                        Counts const& _assertions,\n                        double _durationInSeconds,\n                        bool _missingAssertions );\n\n        SectionInfo sectionInfo;\n        Counts assertions;\n        double durationInSeconds;\n        bool missingAssertions;\n    };\n\n    struct TestCaseStats {\n        TestCaseStats(  TestCaseInfo const& _testInfo,\n                        Totals const& _totals,\n                        std::string&& _stdOut,\n                        std::string&& _stdErr,\n                        bool _aborting );\n\n        TestCaseInfo const * testInfo;\n        Totals totals;\n        std::string stdOut;\n        std::string stdErr;\n        bool aborting;\n    };\n\n    struct TestRunStats {\n        TestRunStats(   TestRunInfo const& _runInfo,\n                        Totals const& _totals,\n                        bool _aborting );\n\n        TestRunInfo runInfo;\n        Totals totals;\n        bool aborting;\n    };\n\n    //! By setting up its preferences, a reporter can modify Catch2's behaviour\n    //! in some regards, e.g. it can request Catch2 to capture writes to\n    //! stdout/stderr during test execution, and pass them to the reporter.\n    struct ReporterPreferences {\n        //! Catch2 should redirect writes to stdout and pass them to the\n        //! reporter\n        bool shouldRedirectStdOut = false;\n        //! Catch2 should call `Reporter::assertionEnded` even for passing\n        //! assertions\n        bool shouldReportAllAssertions = false;\n        //! Catch2 should call `Reporter::assertionStarting` for all assertions\n        // Defaults to true for backwards compatibility, but none of our current\n        // reporters actually want this, and it enables a fast path in assertion\n        // handling.\n        bool shouldReportAllAssertionStarts = true;\n    };\n\n    /**\n     * The common base for all reporters and event listeners\n     *\n     * Implementing classes must also implement:\n     *\n     *     //! User-friendly description of the reporter/listener type\n     *     static std::string getDescription()\n     *\n     * Generally shouldn't be derived from by users of Catch2 directly,\n     * instead they should derive from one of the utility bases that\n     * derive from this class.\n     */\n    class IEventListener {\n    protected:\n        //! Derived classes can set up their preferences here\n        ReporterPreferences m_preferences;\n        //! The test run's config as filled in from CLI and defaults\n        IConfig const* m_config;\n\n    public:\n        IEventListener( IConfig const* config ): m_config( config ) {}\n\n        virtual ~IEventListener(); // = default;\n\n        // Implementing class must also provide the following static methods:\n        // static std::string getDescription();\n\n        ReporterPreferences const& getPreferences() const {\n            return m_preferences;\n        }\n\n        //! Called when no test cases match provided test spec\n        virtual void noMatchingTestCases( StringRef unmatchedSpec ) = 0;\n        //! Called for all invalid test specs from the cli\n        virtual void reportInvalidTestSpec( StringRef invalidArgument ) = 0;\n\n        /**\n         * Called once in a testing run before tests are started\n         *\n         * Not called if tests won't be run (e.g. only listing will happen)\n         */\n        virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;\n\n        //! Called _once_ for each TEST_CASE, no matter how many times it is entered\n        virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0;\n        //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections)\n        virtual void testCasePartialStarting( TestCaseInfo const& testInfo, uint64_t partNumber ) = 0;\n        //! Called when a `SECTION` is being entered. Not called for skipped sections\n        virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0;\n\n        //! Called when user-code is being probed before the actual benchmark runs\n        virtual void benchmarkPreparing( StringRef benchmarkName ) = 0;\n        //! Called after probe but before the user-code is being benchmarked\n        virtual void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) = 0;\n        //! Called with the benchmark results if benchmark successfully finishes\n        virtual void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) = 0;\n        //! Called if running the benchmarks fails for any reason\n        virtual void benchmarkFailed( StringRef benchmarkName ) = 0;\n\n        //! Called before assertion success/failure is evaluated\n        virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;\n\n        //! Called after assertion was fully evaluated\n        virtual void assertionEnded( AssertionStats const& assertionStats ) = 0;\n\n        //! Called after a `SECTION` has finished running\n        virtual void sectionEnded( SectionStats const& sectionStats ) = 0;\n        //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections)\n        virtual void testCasePartialEnded(TestCaseStats const& testCaseStats, uint64_t partNumber ) = 0;\n        //! Called _once_ for each TEST_CASE, no matter how many times it is entered\n        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;\n        /**\n         * Called once after all tests in a testing run are finished\n         *\n         * Not called if tests weren't run (e.g. only listings happened)\n         */\n        virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;\n\n        /**\n         * Called with test cases that are skipped due to the test run aborting.\n         * NOT called for test cases that are explicitly skipped using the `SKIP` macro.\n         *\n         * Deprecated - will be removed in the next major release.\n         */\n        virtual void skipTest( TestCaseInfo const& testInfo ) = 0;\n\n        //! Called if a fatal error (signal/structured exception) occurred\n        virtual void fatalErrorEncountered( StringRef error ) = 0;\n\n        //! Writes out information about provided reporters using reporter-specific format\n        virtual void listReporters(std::vector<ReporterDescription> const& descriptions) = 0;\n        //! Writes out the provided listeners descriptions using reporter-specific format\n        virtual void listListeners(std::vector<ListenerDescription> const& descriptions) = 0;\n        //! Writes out information about provided tests using reporter-specific format\n        virtual void listTests(std::vector<TestCaseHandle> const& tests) = 0;\n        //! Writes out information about the provided tags using reporter-specific format\n        virtual void listTags(std::vector<TagInfo> const& tags) = 0;\n    };\n    using IEventListenerPtr = Detail::unique_ptr<IEventListener>;\n\n} // end namespace Catch\n\n#endif // CATCH_INTERFACES_REPORTER_HPP_INCLUDED\n\n\n#ifndef CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED\n#define CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch {\n\n    struct ReporterConfig;\n    class IConfig;\n    class IEventListener;\n    using IEventListenerPtr = Detail::unique_ptr<IEventListener>;\n\n\n    class IReporterFactory {\n    public:\n        virtual ~IReporterFactory(); // = default\n\n        virtual IEventListenerPtr\n        create( ReporterConfig&& config ) const = 0;\n        virtual std::string getDescription() const = 0;\n    };\n    using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>;\n\n    class EventListenerFactory {\n    public:\n        virtual ~EventListenerFactory(); // = default\n        virtual IEventListenerPtr create( IConfig const* config ) const = 0;\n        //! Return a meaningful name for the listener, e.g. its type name\n        virtual StringRef getName() const = 0;\n        //! Return listener's description if available\n        virtual std::string getDescription() const = 0;\n    };\n} // namespace Catch\n\n#endif // CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED\n\n\n#ifndef CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED\n#define CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED\n\n#include <string>\n\nnamespace Catch {\n\n    struct TagAlias;\n\n    class ITagAliasRegistry {\n    public:\n        virtual ~ITagAliasRegistry(); // = default\n        // Nullptr if not present\n        virtual TagAlias const* find( std::string const& alias ) const = 0;\n        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0;\n\n        static ITagAliasRegistry const& get();\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED\n\n\n#ifndef CATCH_INTERFACES_TESTCASE_HPP_INCLUDED\n#define CATCH_INTERFACES_TESTCASE_HPP_INCLUDED\n\n#include <vector>\n\nnamespace Catch {\n\n    struct TestCaseInfo;\n    class TestCaseHandle;\n    class IConfig;\n\n    class ITestCaseRegistry {\n    public:\n        virtual ~ITestCaseRegistry(); // = default\n        // TODO: this exists only for adding filenames to test cases -- let's expose this in a saner way later\n        virtual std::vector<TestCaseInfo* > const& getAllInfos() const = 0;\n        virtual std::vector<TestCaseHandle> const& getAllTests() const = 0;\n        virtual std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const = 0;\n    };\n\n}\n\n#endif // CATCH_INTERFACES_TESTCASE_HPP_INCLUDED\n\n#endif // CATCH_INTERFACES_ALL_HPP_INCLUDED\n\n\n#ifndef CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED\n#define CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED\n\n\nnamespace Catch {\n    namespace Detail {\n        //! Provides case-insensitive `op<` semantics when called\n        struct CaseInsensitiveLess {\n            bool operator()( StringRef lhs,\n                             StringRef rhs ) const;\n        };\n\n        //! Provides case-insensitive `op==` semantics when called\n        struct CaseInsensitiveEqualTo {\n            bool operator()( StringRef lhs,\n                             StringRef rhs ) const;\n        };\n\n    } // namespace Detail\n} // namespace Catch\n\n#endif // CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED\n\n\n\n/** \\file\n * Wrapper for ANDROID_LOGWRITE configuration option\n *\n * We want to default to enabling it when compiled for android, but\n * users of the library should also be able to disable it if they want\n * to.\n */\n\n#ifndef CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED\n#define CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED\n\n\n#if defined(__ANDROID__)\n#    define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE\n#endif\n\n\n#if defined( CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE ) && \\\n    !defined( CATCH_CONFIG_NO_ANDROID_LOGWRITE ) &&      \\\n    !defined( CATCH_CONFIG_ANDROID_LOGWRITE )\n#    define CATCH_CONFIG_ANDROID_LOGWRITE\n#endif\n\n#endif // CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED\n\n\n\n/** \\file\n * Wrapper for UNCAUGHT_EXCEPTIONS configuration option\n *\n * For some functionality, Catch2 requires to know whether there is\n * an active exception. Because `std::uncaught_exception` is deprecated\n * in C++17, we want to use `std::uncaught_exceptions` if possible.\n */\n\n#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED\n#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED\n\n\n#if defined(_MSC_VER)\n#  if _MSC_VER >= 1900 // Visual Studio 2015 or newer\n#    define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS\n#  endif\n#endif\n\n\n#include <exception>\n\n#if defined(__cpp_lib_uncaught_exceptions) \\\n    && !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)\n\n#  define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS\n#endif // __cpp_lib_uncaught_exceptions\n\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) \\\n    && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) \\\n    && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)\n\n#  define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS\n#endif\n\n\n#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED\n\n\n#ifndef CATCH_CONSOLE_COLOUR_HPP_INCLUDED\n#define CATCH_CONSOLE_COLOUR_HPP_INCLUDED\n\n\n#include <iosfwd>\n#include <cstdint>\n\nnamespace Catch {\n\n    enum class ColourMode : std::uint8_t;\n    class IStream;\n\n    struct Colour {\n        enum Code {\n            None = 0,\n\n            White,\n            Red,\n            Green,\n            Blue,\n            Cyan,\n            Yellow,\n            Grey,\n\n            Bright = 0x10,\n\n            BrightRed = Bright | Red,\n            BrightGreen = Bright | Green,\n            LightGrey = Bright | Grey,\n            BrightWhite = Bright | White,\n            BrightYellow = Bright | Yellow,\n\n            // By intention\n            FileName = LightGrey,\n            Warning = BrightYellow,\n            ResultError = BrightRed,\n            ResultSuccess = BrightGreen,\n            ResultExpectedFailure = Warning,\n\n            Error = BrightRed,\n            Success = Green,\n            Skip = LightGrey,\n\n            OriginalExpression = Cyan,\n            ReconstructedExpression = BrightYellow,\n\n            SecondaryText = LightGrey,\n            Headers = White\n        };\n    };\n\n    class ColourImpl {\n    protected:\n        //! The associated stream of this ColourImpl instance\n        IStream* m_stream;\n    public:\n        ColourImpl( IStream* stream ): m_stream( stream ) {}\n\n        //! RAII wrapper around writing specific colour of text using specific\n        //! colour impl into a stream.\n        class ColourGuard {\n            ColourImpl const* m_colourImpl;\n            Colour::Code m_code;\n            bool m_engaged = false;\n\n        public:\n            //! Does **not** engage the guard/start the colour\n            ColourGuard( Colour::Code code,\n                         ColourImpl const* colour );\n\n            ColourGuard( ColourGuard const& rhs ) = delete;\n            ColourGuard& operator=( ColourGuard const& rhs ) = delete;\n\n            ColourGuard( ColourGuard&& rhs ) noexcept;\n            ColourGuard& operator=( ColourGuard&& rhs ) noexcept;\n\n            //! Removes colour _if_ the guard was engaged\n            ~ColourGuard();\n\n            /**\n             * Explicitly engages colour for given stream.\n             *\n             * The API based on operator<< should be preferred.\n             */\n            ColourGuard& engage( std::ostream& stream ) &;\n            /**\n             * Explicitly engages colour for given stream.\n             *\n             * The API based on operator<< should be preferred.\n             */\n            ColourGuard&& engage( std::ostream& stream ) &&;\n\n        private:\n            //! Engages the guard and starts using colour\n            friend std::ostream& operator<<( std::ostream& lhs,\n                                             ColourGuard& guard ) {\n                guard.engageImpl( lhs );\n                return lhs;\n            }\n            //! Engages the guard and starts using colour\n            friend std::ostream& operator<<( std::ostream& lhs,\n                                            ColourGuard&& guard) {\n                guard.engageImpl( lhs );\n                return lhs;\n            }\n\n            void engageImpl( std::ostream& stream );\n\n        };\n\n        virtual ~ColourImpl(); // = default\n        /**\n         * Creates a guard object for given colour and this colour impl\n         *\n         * **Important:**\n         * the guard starts disengaged, and has to be engaged explicitly.\n         */\n        ColourGuard guardColour( Colour::Code colourCode );\n\n    private:\n        virtual void use( Colour::Code colourCode ) const = 0;\n    };\n\n    //! Provides ColourImpl based on global config and target compilation platform\n    Detail::unique_ptr<ColourImpl> makeColourImpl( ColourMode colourSelection,\n                                                   IStream* stream );\n\n    //! Checks if specific colour impl has been compiled into the binary\n    bool isColourImplAvailable( ColourMode colourSelection );\n\n} // end namespace Catch\n\n#endif // CATCH_CONSOLE_COLOUR_HPP_INCLUDED\n\n\n#ifndef CATCH_CONSOLE_WIDTH_HPP_INCLUDED\n#define CATCH_CONSOLE_WIDTH_HPP_INCLUDED\n\n// This include must be kept so that user's configured value for CONSOLE_WIDTH\n// is used before we attempt to provide a default value\n\n#ifndef CATCH_CONFIG_CONSOLE_WIDTH\n#define CATCH_CONFIG_CONSOLE_WIDTH 80\n#endif\n\n#endif // CATCH_CONSOLE_WIDTH_HPP_INCLUDED\n\n\n#ifndef CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED\n#define CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED\n\n\n#include <cstddef>\n#include <initializer_list>\n\n// We want a simple polyfill over `std::empty`, `std::size` and so on\n// for C++14 or C++ libraries with incomplete support.\n// We also have to handle that MSVC std lib will happily provide these\n// under older standards.\n#if defined(CATCH_CPP17_OR_GREATER) || defined(_MSC_VER)\n\n// We are already using this header either way, so there shouldn't\n// be much additional overhead in including it to get the feature\n// test macros\n#include <string>\n\n#  if !defined(__cpp_lib_nonmember_container_access)\n#      define CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS\n#  endif\n\n#else\n#define CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS\n#endif\n\n\n\nnamespace Catch {\nnamespace Detail {\n\n#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS)\n    template <typename Container>\n    constexpr auto empty(Container const& cont) -> decltype(cont.empty()) {\n        return cont.empty();\n    }\n    template <typename T, std::size_t N>\n    constexpr bool empty(const T (&)[N]) noexcept {\n        // GCC < 7 does not support the const T(&)[] parameter syntax\n        // so we have to ignore the length explicitly\n        (void)N;\n        return false;\n    }\n    template <typename T>\n    constexpr bool empty(std::initializer_list<T> list) noexcept {\n        return list.size() > 0;\n    }\n\n\n    template <typename Container>\n    constexpr auto size(Container const& cont) -> decltype(cont.size()) {\n        return cont.size();\n    }\n    template <typename T, std::size_t N>\n    constexpr std::size_t size(const T(&)[N]) noexcept {\n        return N;\n    }\n#endif // CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS\n\n} // end namespace Detail\n} // end namespace Catch\n\n\n\n#endif // CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED\n\n\n#ifndef CATCH_DEBUG_CONSOLE_HPP_INCLUDED\n#define CATCH_DEBUG_CONSOLE_HPP_INCLUDED\n\n#include <string>\n\nnamespace Catch {\n    void writeToDebugConsole( std::string const& text );\n}\n\n#endif // CATCH_DEBUG_CONSOLE_HPP_INCLUDED\n\n\n#ifndef CATCH_DEBUGGER_HPP_INCLUDED\n#define CATCH_DEBUGGER_HPP_INCLUDED\n\n\nnamespace Catch {\n    bool isDebuggerActive();\n}\n\n#ifdef CATCH_PLATFORM_MAC\n\n    #if defined(__i386__) || defined(__x86_64__)\n        #define CATCH_TRAP() __asm__(\"int $3\\n\" : : ) /* NOLINT */\n    #elif defined(__aarch64__)\n        #define CATCH_TRAP() __asm__(\".inst 0xd43e0000\")\n    #elif defined(__POWERPC__)\n        #define CATCH_TRAP() __asm__(\"li r0, 20\\nsc\\nnop\\nli r0, 37\\nli r4, 2\\nsc\\nnop\\n\" \\\n        : : : \"memory\",\"r0\",\"r3\",\"r4\" ) /* NOLINT */\n    #endif\n\n#elif defined(CATCH_PLATFORM_IPHONE)\n\n    // use inline assembler\n    #if defined(__i386__) || defined(__x86_64__)\n        #define CATCH_TRAP()  __asm__(\"int $3\")\n    #elif defined(__aarch64__)\n        #define CATCH_TRAP()  __asm__(\".inst 0xd4200000\")\n    #elif defined(__arm__) && !defined(__thumb__)\n        #define CATCH_TRAP()  __asm__(\".inst 0xe7f001f0\")\n    #elif defined(__arm__) &&  defined(__thumb__)\n        #define CATCH_TRAP()  __asm__(\".inst 0xde01\")\n    #endif\n\n#elif defined(CATCH_PLATFORM_LINUX)\n    // If we can use inline assembler, do it because this allows us to break\n    // directly at the location of the failing check instead of breaking inside\n    // raise() called from it, i.e. one stack frame below.\n    #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))\n        #define CATCH_TRAP() asm volatile (\"int $3\") /* NOLINT */\n    #else // Fall back to the generic way.\n        #include <signal.h>\n\n        #define CATCH_TRAP() raise(SIGTRAP)\n    #endif\n#elif defined(_MSC_VER)\n    #define CATCH_TRAP() __debugbreak()\n#elif defined(__MINGW32__)\n    extern \"C\" __declspec(dllimport) void __stdcall DebugBreak();\n    #define CATCH_TRAP() DebugBreak()\n#endif\n\n#ifndef CATCH_BREAK_INTO_DEBUGGER\n    #ifdef CATCH_TRAP\n        #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }()\n    #else\n        #define CATCH_BREAK_INTO_DEBUGGER() []{}()\n    #endif\n#endif\n\n#endif // CATCH_DEBUGGER_HPP_INCLUDED\n\n\n#ifndef CATCH_ENFORCE_HPP_INCLUDED\n#define CATCH_ENFORCE_HPP_INCLUDED\n\n\n#include <exception> // for `std::exception` in no-exception configuration\n\nnamespace Catch {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    template <typename Ex>\n    [[noreturn]]\n    void throw_exception(Ex const& e) {\n        throw e;\n    }\n#else // ^^ Exceptions are enabled //  Exceptions are disabled vv\n    [[noreturn]]\n    void throw_exception(std::exception const& e);\n#endif\n\n    [[noreturn]]\n    void throw_logic_error(std::string const& msg);\n    [[noreturn]]\n    void throw_domain_error(std::string const& msg);\n    [[noreturn]]\n    void throw_runtime_error(std::string const& msg);\n\n} // namespace Catch;\n\n#define CATCH_MAKE_MSG(...) \\\n    (Catch::ReusableStringStream() << __VA_ARGS__).str()\n\n#define CATCH_INTERNAL_ERROR(...) \\\n    Catch::throw_logic_error(CATCH_MAKE_MSG( CATCH_INTERNAL_LINEINFO << \": Internal Catch2 error: \" << __VA_ARGS__))\n\n#define CATCH_ERROR(...) \\\n    Catch::throw_domain_error(CATCH_MAKE_MSG( __VA_ARGS__ ))\n\n#define CATCH_RUNTIME_ERROR(...) \\\n    Catch::throw_runtime_error(CATCH_MAKE_MSG( __VA_ARGS__ ))\n\n#define CATCH_ENFORCE( condition, ... ) \\\n    do{ if( !(condition) ) CATCH_ERROR( __VA_ARGS__ ); } while(false)\n\n\n#endif // CATCH_ENFORCE_HPP_INCLUDED\n\n\n#ifndef CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED\n#define CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED\n\n\n#include <vector>\n\nnamespace Catch {\n\n    namespace Detail {\n\n        Catch::Detail::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values );\n\n        class EnumValuesRegistry : public IMutableEnumValuesRegistry {\n\n            std::vector<Catch::Detail::unique_ptr<EnumInfo>> m_enumInfos;\n\n            EnumInfo const& registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values) override;\n        };\n\n        std::vector<StringRef> parseEnums( StringRef enums );\n\n    } // Detail\n\n} // Catch\n\n#endif // CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED\n\n\n#ifndef CATCH_ERRNO_GUARD_HPP_INCLUDED\n#define CATCH_ERRNO_GUARD_HPP_INCLUDED\n\nnamespace Catch {\n\n    //! Simple RAII class that stores the value of `errno`\n    //! at construction and restores it at destruction.\n    class ErrnoGuard {\n    public:\n        // Keep these outlined to avoid dragging in macros from <cerrno>\n\n        ErrnoGuard();\n        ~ErrnoGuard();\n    private:\n        int m_oldErrno;\n    };\n\n}\n\n#endif // CATCH_ERRNO_GUARD_HPP_INCLUDED\n\n\n#ifndef CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED\n#define CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch {\n\n    class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {\n    public:\n        ~ExceptionTranslatorRegistry() override;\n        void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator );\n        std::string translateActiveException() const override;\n\n    private:\n        ExceptionTranslators m_translators;\n    };\n}\n\n#endif // CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED\n\n\n#ifndef CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED\n#define CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED\n\n#include <cassert>\n\nnamespace Catch {\n\n    /**\n     * Wrapper for platform-specific fatal error (signals/SEH) handlers\n     *\n     * Tries to be cooperative with other handlers, and not step over\n     * other handlers. This means that unknown structured exceptions\n     * are passed on, previous signal handlers are called, and so on.\n     *\n     * Can only be instantiated once, and assumes that once a signal\n     * is caught, the binary will end up terminating. Thus, there\n     */\n    class FatalConditionHandler {\n        bool m_started = false;\n\n        // Install/disengage implementation for specific platform.\n        // Should be if-defed to work on current platform, can assume\n        // engage-disengage 1:1 pairing.\n        void engage_platform();\n        void disengage_platform() noexcept;\n    public:\n        // Should also have platform-specific implementations as needed\n        FatalConditionHandler();\n        ~FatalConditionHandler();\n\n        void engage() {\n            assert(!m_started && \"Handler cannot be installed twice.\");\n            m_started = true;\n            engage_platform();\n        }\n\n        void disengage() noexcept {\n            assert(m_started && \"Handler cannot be uninstalled without being installed first\");\n            m_started = false;\n            disengage_platform();\n        }\n    };\n\n    //! Simple RAII guard for (dis)engaging the FatalConditionHandler\n    class FatalConditionHandlerGuard {\n        FatalConditionHandler* m_handler;\n    public:\n        FatalConditionHandlerGuard(FatalConditionHandler* handler):\n            m_handler(handler) {\n            m_handler->engage();\n        }\n        ~FatalConditionHandlerGuard() {\n            m_handler->disengage();\n        }\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED\n\n\n#ifndef CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED\n#define CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED\n\n\n#include <cassert>\n#include <cmath>\n#include <cstdint>\n#include <utility>\n#include <limits>\n\nnamespace Catch {\n    namespace Detail {\n\n        uint32_t convertToBits(float f);\n        uint64_t convertToBits(double d);\n\n        // Used when we know we want == comparison of two doubles\n        // to centralize warning suppression\n        bool directCompare( float lhs, float rhs );\n        bool directCompare( double lhs, double rhs );\n\n    } // end namespace Detail\n\n\n\n#if defined( __GNUC__ ) || defined( __clang__ )\n#    pragma GCC diagnostic push\n    // We do a bunch of direct compensations of floating point numbers,\n    // because we know what we are doing and actually do want the direct\n    // comparison behaviour.\n#    pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n\n    /**\n     * Calculates the ULP distance between two floating point numbers\n     *\n     * The ULP distance of two floating point numbers is the count of\n     * valid floating point numbers representable between them.\n     *\n     * There are some exceptions between how this function counts the\n     * distance, and the interpretation of the standard as implemented.\n     * by e.g. `nextafter`. For this function it always holds that:\n     * * `(x == y) => ulpDistance(x, y) == 0` (so `ulpDistance(-0, 0) == 0`)\n     * * `ulpDistance(maxFinite, INF) == 1`\n     * * `ulpDistance(x, -x) == 2 * ulpDistance(x, 0)`\n     *\n     * \\pre `!isnan( lhs )`\n     * \\pre `!isnan( rhs )`\n     * \\pre floating point numbers are represented in IEEE-754 format\n     */\n    template <typename FP>\n    uint64_t ulpDistance( FP lhs, FP rhs ) {\n        assert( std::numeric_limits<FP>::is_iec559 &&\n            \"ulpDistance assumes IEEE-754 format for floating point types\" );\n        assert( !Catch::isnan( lhs ) &&\n                \"Distance between NaN and number is not meaningful\" );\n        assert( !Catch::isnan( rhs ) &&\n                \"Distance between NaN and number is not meaningful\" );\n\n        // We want X == Y to imply 0 ULP distance even if X and Y aren't\n        // bit-equal (-0 and 0), or X - Y != 0 (same sign infinities).\n        if ( lhs == rhs ) { return 0; }\n\n        // We need a properly typed positive zero for type inference.\n        static constexpr FP positive_zero{};\n\n        // We want to ensure that +/- 0 is always represented as positive zero\n        if ( lhs == positive_zero ) { lhs = positive_zero; }\n        if ( rhs == positive_zero ) { rhs = positive_zero; }\n\n        // If arguments have different signs, we can handle them by summing\n        // how far are they from 0 each.\n        if ( std::signbit( lhs ) != std::signbit( rhs ) ) {\n            return ulpDistance( std::abs( lhs ), positive_zero ) +\n                   ulpDistance( std::abs( rhs ), positive_zero );\n        }\n\n        // When both lhs and rhs are of the same sign, we can just\n        // read the numbers bitwise as integers, and then subtract them\n        // (assuming IEEE).\n        uint64_t lc = Detail::convertToBits( lhs );\n        uint64_t rc = Detail::convertToBits( rhs );\n\n        // The ulp distance between two numbers is symmetric, so to avoid\n        // dealing with overflows we want the bigger converted number on the lhs\n        if ( lc < rc ) {\n            std::swap( lc, rc );\n        }\n\n        return lc - rc;\n    }\n\n#if defined( __GNUC__ ) || defined( __clang__ )\n#    pragma GCC diagnostic pop\n#endif\n\n\n} // end namespace Catch\n\n#endif // CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED\n\n\n#ifndef CATCH_GETENV_HPP_INCLUDED\n#define CATCH_GETENV_HPP_INCLUDED\n\nnamespace Catch {\nnamespace Detail {\n\n    //! Wrapper over `std::getenv` that compiles on UWP (and always returns nullptr there)\n    char const* getEnv(char const* varName);\n\n}\n}\n\n#endif // CATCH_GETENV_HPP_INCLUDED\n\n\n#ifndef CATCH_IS_PERMUTATION_HPP_INCLUDED\n#define CATCH_IS_PERMUTATION_HPP_INCLUDED\n\n#include <iterator>\n#include <type_traits>\n\nnamespace Catch {\n    namespace Detail {\n\n        template <typename ForwardIter,\n                  typename Sentinel,\n                  typename T,\n                  typename Comparator>\n        constexpr\n        ForwardIter find_sentinel( ForwardIter start,\n                                   Sentinel sentinel,\n                                   T const& value,\n                                   Comparator cmp ) {\n            while ( start != sentinel ) {\n                if ( cmp( *start, value ) ) { break; }\n                ++start;\n            }\n            return start;\n        }\n\n        template <typename ForwardIter,\n                  typename Sentinel,\n                  typename T,\n                  typename Comparator>\n        constexpr\n        std::ptrdiff_t count_sentinel( ForwardIter start,\n                                       Sentinel sentinel,\n                                       T const& value,\n                                       Comparator cmp ) {\n            std::ptrdiff_t count = 0;\n            while ( start != sentinel ) {\n                if ( cmp( *start, value ) ) { ++count; }\n                ++start;\n            }\n            return count;\n        }\n\n        template <typename ForwardIter, typename Sentinel>\n        constexpr\n        std::enable_if_t<!std::is_same<ForwardIter, Sentinel>::value,\n                         std::ptrdiff_t>\n        sentinel_distance( ForwardIter iter, const Sentinel sentinel ) {\n            std::ptrdiff_t dist = 0;\n            while ( iter != sentinel ) {\n                ++iter;\n                ++dist;\n            }\n            return dist;\n        }\n\n        template <typename ForwardIter>\n        constexpr std::ptrdiff_t sentinel_distance( ForwardIter first,\n                                                    ForwardIter last ) {\n            return std::distance( first, last );\n        }\n\n        template <typename ForwardIter1,\n                  typename Sentinel1,\n                  typename ForwardIter2,\n                  typename Sentinel2,\n                  typename Comparator>\n        constexpr bool check_element_counts( ForwardIter1 first_1,\n                                             const Sentinel1 end_1,\n                                             ForwardIter2 first_2,\n                                             const Sentinel2 end_2,\n                                             Comparator cmp ) {\n            auto cursor = first_1;\n            while ( cursor != end_1 ) {\n                if ( find_sentinel( first_1, cursor, *cursor, cmp ) ==\n                     cursor ) {\n                    // we haven't checked this element yet\n                    const auto count_in_range_2 =\n                        count_sentinel( first_2, end_2, *cursor, cmp );\n                    // Not a single instance in 2nd range, so it cannot be a\n                    // permutation of 1st range\n                    if ( count_in_range_2 == 0 ) { return false; }\n\n                    const auto count_in_range_1 =\n                        count_sentinel( cursor, end_1, *cursor, cmp );\n                    if ( count_in_range_1 != count_in_range_2 ) {\n                        return false;\n                    }\n                }\n\n                ++cursor;\n            }\n\n            return true;\n        }\n\n        template <typename ForwardIter1,\n                  typename Sentinel1,\n                  typename ForwardIter2,\n                  typename Sentinel2,\n                  typename Comparator>\n        constexpr bool is_permutation( ForwardIter1 first_1,\n                                       const Sentinel1 end_1,\n                                       ForwardIter2 first_2,\n                                       const Sentinel2 end_2,\n                                       Comparator cmp ) {\n            // TODO: no optimization for stronger iterators, because we would also have to constrain on sentinel vs not sentinel types\n            // TODO: Comparator has to be \"both sides\", e.g. a == b => b == a\n            // This skips shared prefix of the two ranges\n            while (first_1 != end_1 && first_2 != end_2 && cmp(*first_1, *first_2)) {\n                ++first_1;\n                ++first_2;\n            }\n\n            // We need to handle case where at least one of the ranges has no more elements\n            if (first_1 == end_1 || first_2 == end_2) {\n                return first_1 == end_1 && first_2 == end_2;\n            }\n\n            // pair counting is n**2, so we pay linear walk to compare the sizes first\n            auto dist_1 = sentinel_distance( first_1, end_1 );\n            auto dist_2 = sentinel_distance( first_2, end_2 );\n\n            if (dist_1 != dist_2) { return false; }\n\n            // Since we do not try to handle stronger iterators pair (e.g.\n            // bidir) optimally, the only thing left to do is to check counts in\n            // the remaining ranges.\n            return check_element_counts( first_1, end_1, first_2, end_2, cmp );\n        }\n\n    } // namespace Detail\n} // namespace Catch\n\n#endif // CATCH_IS_PERMUTATION_HPP_INCLUDED\n\n\n#ifndef CATCH_ISTREAM_HPP_INCLUDED\n#define CATCH_ISTREAM_HPP_INCLUDED\n\n\n#include <iosfwd>\n#include <string>\n\nnamespace Catch {\n\n    class IStream {\n    public:\n        virtual ~IStream(); // = default\n        virtual std::ostream& stream() = 0;\n        /**\n         * Best guess on whether the instance is writing to a console (e.g. via stdout/stderr)\n         *\n         * This is useful for e.g. Win32 colour support, because the Win32\n         * API manipulates console directly, unlike POSIX escape codes,\n         * that can be written anywhere.\n         *\n         * Due to variety of ways to change where the stdout/stderr is\n         * _actually_ being written, users should always assume that\n         * the answer might be wrong.\n         */\n        virtual bool isConsole() const { return false; }\n    };\n\n    /**\n     * Creates a stream wrapper that writes to specific file.\n     *\n     * Also recognizes 4 special filenames\n     * * `-` for stdout\n     * * `%stdout` for stdout\n     * * `%stderr` for stderr\n     * * `%debug` for platform specific debugging output\n     *\n     * \\throws if passed an unrecognized %-prefixed stream\n     */\n    auto makeStream( std::string const& filename ) -> Detail::unique_ptr<IStream>;\n\n}\n\n#endif // CATCH_STREAM_HPP_INCLUDED\n\n\n#ifndef CATCH_JSONWRITER_HPP_INCLUDED\n#define CATCH_JSONWRITER_HPP_INCLUDED\n\n\n#include <cstdint>\n#include <sstream>\n\nnamespace Catch {\n    class JsonObjectWriter;\n    class JsonArrayWriter;\n\n    struct JsonUtils {\n        static void indent( std::ostream& os, std::uint64_t level );\n        static void appendCommaNewline( std::ostream& os,\n                                        bool& should_comma,\n                                        std::uint64_t level );\n    };\n\n    class JsonValueWriter {\n    public:\n        JsonValueWriter( std::ostream& os );\n        JsonValueWriter( std::ostream& os, std::uint64_t indent_level );\n\n        JsonObjectWriter writeObject() &&;\n        JsonArrayWriter writeArray() &&;\n\n        template <typename T>\n        void write( T const& value ) && {\n            writeImpl( value, !std::is_arithmetic<T>::value );\n        }\n        void write( StringRef value ) &&;\n        void write( bool value ) &&;\n\n    private:\n        void writeImpl( StringRef value, bool quote );\n\n        // Without this SFINAE, this overload is a better match\n        // for `std::string`, `char const*`, `char const[N]` args.\n        // While it would still work, it would cause code bloat\n        // and multiple iteration over the strings\n        template <typename T,\n                  typename = typename std::enable_if_t<\n                      !std::is_convertible<T, StringRef>::value>>\n        void writeImpl( T const& value, bool quote_value ) {\n            m_sstream << value;\n            writeImpl( m_sstream.str(), quote_value );\n        }\n\n        std::ostream& m_os;\n        std::stringstream m_sstream;\n        std::uint64_t m_indent_level;\n    };\n\n    class JsonObjectWriter {\n    public:\n        JsonObjectWriter( std::ostream& os );\n        JsonObjectWriter( std::ostream& os, std::uint64_t indent_level );\n\n        JsonObjectWriter( JsonObjectWriter&& source ) noexcept;\n        JsonObjectWriter& operator=( JsonObjectWriter&& source ) = delete;\n\n        ~JsonObjectWriter();\n\n        JsonValueWriter write( StringRef key );\n\n    private:\n        std::ostream& m_os;\n        std::uint64_t m_indent_level;\n        bool m_should_comma = false;\n        bool m_active = true;\n    };\n\n    class JsonArrayWriter {\n    public:\n        JsonArrayWriter( std::ostream& os );\n        JsonArrayWriter( std::ostream& os, std::uint64_t indent_level );\n\n        JsonArrayWriter( JsonArrayWriter&& source ) noexcept;\n        JsonArrayWriter& operator=( JsonArrayWriter&& source ) = delete;\n\n        ~JsonArrayWriter();\n\n        JsonObjectWriter writeObject();\n        JsonArrayWriter writeArray();\n\n        template <typename T>\n        JsonArrayWriter& write( T const& value ) {\n            return writeImpl( value );\n        }\n\n        JsonArrayWriter& write( bool value );\n\n    private:\n        template <typename T>\n        JsonArrayWriter& writeImpl( T const& value ) {\n            JsonUtils::appendCommaNewline(\n                m_os, m_should_comma, m_indent_level + 1 );\n            JsonValueWriter{ m_os }.write( value );\n\n            return *this;\n        }\n\n        std::ostream& m_os;\n        std::uint64_t m_indent_level;\n        bool m_should_comma = false;\n        bool m_active = true;\n    };\n\n} // namespace Catch\n\n#endif // CATCH_JSONWRITER_HPP_INCLUDED\n\n\n#ifndef CATCH_LEAK_DETECTOR_HPP_INCLUDED\n#define CATCH_LEAK_DETECTOR_HPP_INCLUDED\n\nnamespace Catch {\n\n    struct LeakDetector {\n        LeakDetector();\n        ~LeakDetector();\n    };\n\n}\n#endif // CATCH_LEAK_DETECTOR_HPP_INCLUDED\n\n\n#ifndef CATCH_LIST_HPP_INCLUDED\n#define CATCH_LIST_HPP_INCLUDED\n\n\n#include <set>\n#include <string>\n\n\nnamespace Catch {\n\n    class IEventListener;\n    class Config;\n\n\n    struct ReporterDescription {\n        std::string name, description;\n    };\n    struct ListenerDescription {\n        StringRef name;\n        std::string description;\n    };\n\n    struct TagInfo {\n        void add(StringRef spelling);\n        std::string all() const;\n\n        std::set<StringRef> spellings;\n        std::size_t count = 0;\n    };\n\n    bool list( IEventListener& reporter, Config const& config );\n\n} // end namespace Catch\n\n#endif // CATCH_LIST_HPP_INCLUDED\n\n\n#ifndef CATCH_OUTPUT_REDIRECT_HPP_INCLUDED\n#define CATCH_OUTPUT_REDIRECT_HPP_INCLUDED\n\n\n#include <cassert>\n#include <string>\n\nnamespace Catch {\n\n    class OutputRedirect {\n        bool m_redirectActive = false;\n        virtual void activateImpl() = 0;\n        virtual void deactivateImpl() = 0;\n    public:\n        enum Kind {\n            //! No redirect (noop implementation)\n            None,\n            //! Redirect std::cout/std::cerr/std::clog streams internally\n            Streams,\n            //! Redirect the stdout/stderr file descriptors into files\n            FileDescriptors,\n        };\n\n        virtual ~OutputRedirect(); // = default;\n\n        // TODO: Do we want to check that redirect is not active before retrieving the output?\n        virtual std::string getStdout() = 0;\n        virtual std::string getStderr() = 0;\n        virtual void clearBuffers() = 0;\n        bool isActive() const { return m_redirectActive; }\n        void activate() {\n            assert( !m_redirectActive && \"redirect is already active\" );\n            activateImpl();\n            m_redirectActive = true;\n        }\n        void deactivate() {\n            assert( m_redirectActive && \"redirect is not active\" );\n            deactivateImpl();\n            m_redirectActive = false;\n        }\n    };\n\n    bool isRedirectAvailable( OutputRedirect::Kind kind);\n    Detail::unique_ptr<OutputRedirect> makeOutputRedirect( bool actual );\n\n    class RedirectGuard {\n        OutputRedirect* m_redirect;\n        bool m_activate;\n        bool m_previouslyActive;\n        bool m_moved = false;\n\n    public:\n        RedirectGuard( bool activate, OutputRedirect& redirectImpl );\n        ~RedirectGuard() noexcept( false );\n\n        RedirectGuard( RedirectGuard const& ) = delete;\n        RedirectGuard& operator=( RedirectGuard const& ) = delete;\n\n        // C++14 needs move-able guards to return them from functions\n        RedirectGuard( RedirectGuard&& rhs ) noexcept;\n        RedirectGuard& operator=( RedirectGuard&& rhs ) noexcept;\n    };\n\n    RedirectGuard scopedActivate( OutputRedirect& redirectImpl );\n    RedirectGuard scopedDeactivate( OutputRedirect& redirectImpl );\n\n} // end namespace Catch\n\n#endif // CATCH_OUTPUT_REDIRECT_HPP_INCLUDED\n\n\n#ifndef CATCH_PARSE_NUMBERS_HPP_INCLUDED\n#define CATCH_PARSE_NUMBERS_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch {\n\n    /**\n     * Parses unsigned int from the input, using provided base\n     *\n     * Effectively a wrapper around std::stoul but with better error checking\n     * e.g. \"-1\" is rejected, instead of being parsed as UINT_MAX.\n     */\n    Optional<unsigned int> parseUInt(std::string const& input, int base = 10);\n}\n\n#endif // CATCH_PARSE_NUMBERS_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_REGISTRY_HPP_INCLUDED\n#define CATCH_REPORTER_REGISTRY_HPP_INCLUDED\n\n\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    class IEventListener;\n    using IEventListenerPtr = Detail::unique_ptr<IEventListener>;\n    class IReporterFactory;\n    using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>;\n    struct ReporterConfig;\n    class EventListenerFactory;\n\n    class ReporterRegistry {\n        struct ReporterRegistryImpl;\n        Detail::unique_ptr<ReporterRegistryImpl> m_impl;\n\n    public:\n        ReporterRegistry();\n        ~ReporterRegistry(); // = default;\n\n        IEventListenerPtr create( std::string const& name,\n                                  ReporterConfig&& config ) const;\n\n        void registerReporter( std::string const& name,\n                               IReporterFactoryPtr factory );\n\n        void\n        registerListener( Detail::unique_ptr<EventListenerFactory> factory );\n\n        std::map<std::string,\n                 IReporterFactoryPtr,\n                 Detail::CaseInsensitiveLess> const&\n        getFactories() const;\n\n        std::vector<Detail::unique_ptr<EventListenerFactory>> const&\n        getListeners() const;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_REGISTRY_HPP_INCLUDED\n\n\n#ifndef CATCH_RUN_CONTEXT_HPP_INCLUDED\n#define CATCH_RUN_CONTEXT_HPP_INCLUDED\n\n\n\n#ifndef CATCH_TEST_CASE_TRACKER_HPP_INCLUDED\n#define CATCH_TEST_CASE_TRACKER_HPP_INCLUDED\n\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\nnamespace TestCaseTracking {\n\n    struct NameAndLocation {\n        std::string name;\n        SourceLineInfo location;\n\n        NameAndLocation( std::string&& _name, SourceLineInfo const& _location );\n        friend bool operator==(NameAndLocation const& lhs, NameAndLocation const& rhs) {\n            // This is a very cheap check that should have a very high hit rate.\n            // If we get to SourceLineInfo::operator==, we will redo it, but the\n            // cost of repeating is trivial at that point (we will be paying\n            // multiple strcmp/memcmps at that point).\n            if ( lhs.location.line != rhs.location.line ) { return false; }\n            return lhs.name == rhs.name && lhs.location == rhs.location;\n        }\n        friend bool operator!=(NameAndLocation const& lhs,\n                               NameAndLocation const& rhs) {\n            return !( lhs == rhs );\n        }\n    };\n\n    /**\n     * This is a variant of `NameAndLocation` that does not own the name string\n     *\n     * This avoids extra allocations when trying to locate a tracker by its\n     * name and location, as long as we make sure that trackers only keep\n     * around the owning variant.\n     */\n    struct NameAndLocationRef {\n        StringRef name;\n        SourceLineInfo location;\n\n        constexpr NameAndLocationRef( StringRef name_,\n                                      SourceLineInfo location_ ):\n            name( name_ ), location( location_ ) {}\n\n        friend bool operator==( NameAndLocation const& lhs,\n                                NameAndLocationRef const& rhs ) {\n            // This is a very cheap check that should have a very high hit rate.\n            // If we get to SourceLineInfo::operator==, we will redo it, but the\n            // cost of repeating is trivial at that point (we will be paying\n            // multiple strcmp/memcmps at that point).\n            if ( lhs.location.line != rhs.location.line ) { return false; }\n            return StringRef( lhs.name ) == rhs.name &&\n                   lhs.location == rhs.location;\n        }\n        friend bool operator==( NameAndLocationRef const& lhs,\n                                NameAndLocation const& rhs ) {\n            return rhs == lhs;\n        }\n    };\n\n    class ITracker;\n\n    using ITrackerPtr = Catch::Detail::unique_ptr<ITracker>;\n\n    class ITracker {\n        NameAndLocation m_nameAndLocation;\n\n        using Children = std::vector<ITrackerPtr>;\n\n    protected:\n        enum CycleState {\n            NotStarted,\n            Executing,\n            ExecutingChildren,\n            NeedsAnotherRun,\n            CompletedSuccessfully,\n            Failed\n        };\n\n        ITracker* m_parent = nullptr;\n        Children m_children;\n        CycleState m_runState = NotStarted;\n\n    public:\n        ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ):\n            m_nameAndLocation( CATCH_MOVE(nameAndLoc) ),\n            m_parent( parent )\n        {}\n\n\n        // static queries\n        NameAndLocation const& nameAndLocation() const {\n            return m_nameAndLocation;\n        }\n        ITracker* parent() const {\n            return m_parent;\n        }\n\n        virtual ~ITracker(); // = default\n\n\n        // dynamic queries\n\n        //! Returns true if tracker run to completion (successfully or not)\n        virtual bool isComplete() const = 0;\n        //! Returns true if tracker run to completion successfully\n        bool isSuccessfullyCompleted() const {\n            return m_runState == CompletedSuccessfully;\n        }\n        //! Returns true if tracker has started but hasn't been completed\n        bool isOpen() const;\n        //! Returns true iff tracker has started\n        bool hasStarted() const;\n\n        // actions\n        virtual void close() = 0; // Successfully complete\n        virtual void fail() = 0;\n        void markAsNeedingAnotherRun();\n\n        //! Register a nested ITracker\n        void addChild( ITrackerPtr&& child );\n        /**\n         * Returns ptr to specific child if register with this tracker.\n         *\n         * Returns nullptr if not found.\n         */\n        ITracker* findChild( NameAndLocationRef const& nameAndLocation );\n        //! Have any children been added?\n        bool hasChildren() const {\n            return !m_children.empty();\n        }\n\n\n        //! Marks tracker as executing a child, doing se recursively up the tree\n        void openChild();\n\n        /**\n         * Returns true if the instance is a section tracker\n         *\n         * Subclasses should override to true if they are, replaces RTTI\n         * for internal debug checks.\n         */\n        virtual bool isSectionTracker() const;\n        /**\n         * Returns true if the instance is a generator tracker\n         *\n         * Subclasses should override to true if they are, replaces RTTI\n         * for internal debug checks.\n         */\n        virtual bool isGeneratorTracker() const;\n    };\n\n    class TrackerContext {\n\n        enum RunState {\n            NotStarted,\n            Executing,\n            CompletedCycle\n        };\n\n        ITrackerPtr m_rootTracker;\n        ITracker* m_currentTracker = nullptr;\n        RunState m_runState = NotStarted;\n\n    public:\n\n        ITracker& startRun();\n\n        void startCycle() {\n            m_currentTracker = m_rootTracker.get();\n            m_runState = Executing;\n        }\n        void completeCycle();\n\n        bool completedCycle() const;\n        ITracker& currentTracker() { return *m_currentTracker; }\n        void setCurrentTracker( ITracker* tracker );\n    };\n\n    class TrackerBase : public ITracker {\n    protected:\n\n        TrackerContext& m_ctx;\n\n    public:\n        TrackerBase( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent );\n\n        bool isComplete() const override;\n\n        void open();\n\n        void close() override;\n        void fail() override;\n\n    private:\n        void moveToParent();\n        void moveToThis();\n    };\n\n    class SectionTracker : public TrackerBase {\n        std::vector<StringRef> m_filters;\n        // Note that lifetime-wise we piggy back off the name stored in the `ITracker` parent`.\n        // Currently it allocates owns the name, so this is safe. If it is later refactored\n        // to not own the name, the name still has to outlive the `ITracker` parent, so\n        // this should still be safe.\n        StringRef m_trimmed_name;\n    public:\n        SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent );\n\n        bool isSectionTracker() const override;\n\n        bool isComplete() const override;\n\n        static SectionTracker& acquire( TrackerContext& ctx, NameAndLocationRef const& nameAndLocation );\n\n        void tryOpen();\n\n        void addInitialFilters( std::vector<std::string> const& filters );\n        void addNextFilters( std::vector<StringRef> const& filters );\n        //! Returns filters active in this tracker\n        std::vector<StringRef> const& getFilters() const { return m_filters; }\n        //! Returns whitespace-trimmed name of the tracked section\n        StringRef trimmedName() const;\n    };\n\n} // namespace TestCaseTracking\n\nusing TestCaseTracking::ITracker;\nusing TestCaseTracking::TrackerContext;\nusing TestCaseTracking::SectionTracker;\n\n} // namespace Catch\n\n#endif // CATCH_TEST_CASE_TRACKER_HPP_INCLUDED\n\n\n#ifndef CATCH_THREAD_SUPPORT_HPP_INCLUDED\n#define CATCH_THREAD_SUPPORT_HPP_INCLUDED\n\n\n#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )\n#    include <atomic>\n#    include <mutex>\n#endif\n\n\nnamespace Catch {\n    namespace Detail {\n#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )\n        using Mutex = std::mutex;\n        using LockGuard = std::lock_guard<std::mutex>;\n        struct AtomicCounts {\n            std::atomic<std::uint64_t> passed = 0;\n            std::atomic<std::uint64_t> failed = 0;\n            std::atomic<std::uint64_t> failedButOk = 0;\n            std::atomic<std::uint64_t> skipped = 0;\n        };\n#else // ^^ Use actual mutex, lock and atomics\n      // vv Dummy implementations for single-thread performance\n\n        struct Mutex {\n            void lock() {}\n            void unlock() {}\n        };\n\n        struct LockGuard {\n            LockGuard( Mutex ) {}\n        };\n\n        using AtomicCounts = Counts;\n#endif\n\n    } // namespace Detail\n} // namespace Catch\n\n#endif // CATCH_THREAD_SUPPORT_HPP_INCLUDED\n\n#include <string>\n\nnamespace Catch {\n\n    class IGeneratorTracker;\n    class IConfig;\n    class IEventListener;\n    using IEventListenerPtr = Detail::unique_ptr<IEventListener>;\n    class OutputRedirect;\n\n    ///////////////////////////////////////////////////////////////////////////\n\n    class RunContext final : public IResultCapture {\n\n    public:\n        RunContext( RunContext const& ) = delete;\n        RunContext& operator =( RunContext const& ) = delete;\n\n        explicit RunContext( IConfig const* _config, IEventListenerPtr&& reporter );\n\n        ~RunContext() override;\n\n        Totals runTest(TestCaseHandle const& testCase);\n\n    public: // IResultCapture\n\n        // Assertion handlers\n        void handleExpr\n                (   AssertionInfo const& info,\n                    ITransientExpression const& expr,\n                    AssertionReaction& reaction ) override;\n        void handleMessage\n                (   AssertionInfo const& info,\n                    ResultWas::OfType resultType,\n                    std::string&& message,\n                    AssertionReaction& reaction ) override;\n        void handleUnexpectedExceptionNotThrown\n                (   AssertionInfo const& info,\n                    AssertionReaction& reaction ) override;\n        void handleUnexpectedInflightException\n                (   AssertionInfo const& info,\n                    std::string&& message,\n                    AssertionReaction& reaction ) override;\n        void handleIncomplete\n                (   AssertionInfo const& info ) override;\n        void handleNonExpr\n                (   AssertionInfo const &info,\n                    ResultWas::OfType resultType,\n                    AssertionReaction &reaction ) override;\n\n        void notifyAssertionStarted( AssertionInfo const& info ) override;\n        bool sectionStarted( StringRef sectionName,\n                             SourceLineInfo const& sectionLineInfo,\n                             Counts& assertions ) override;\n\n        void sectionEnded( SectionEndInfo&& endInfo ) override;\n        void sectionEndedEarly( SectionEndInfo&& endInfo ) override;\n\n        IGeneratorTracker*\n        acquireGeneratorTracker( StringRef generatorName,\n                                 SourceLineInfo const& lineInfo ) override;\n        IGeneratorTracker* createGeneratorTracker(\n            StringRef generatorName,\n            SourceLineInfo lineInfo,\n            Generators::GeneratorBasePtr&& generator ) override;\n\n\n        void benchmarkPreparing( StringRef name ) override;\n        void benchmarkStarting( BenchmarkInfo const& info ) override;\n        void benchmarkEnded( BenchmarkStats<> const& stats ) override;\n        void benchmarkFailed( StringRef error ) override;\n\n        void pushScopedMessage( MessageInfo const& message ) override;\n        void popScopedMessage( MessageInfo const& message ) override;\n\n        void emplaceUnscopedMessage( MessageBuilder&& builder ) override;\n\n        std::string getCurrentTestName() const override;\n\n        const AssertionResult* getLastResult() const override;\n\n        void exceptionEarlyReported() override;\n\n        void handleFatalErrorCondition( StringRef message ) override;\n\n        bool lastAssertionPassed() override;\n\n    public:\n        // !TBD We need to do this another way!\n        bool aborting() const;\n\n    private:\n        void assertionPassedFastPath( SourceLineInfo lineInfo );\n        // Update the non-thread-safe m_totals from the atomic assertion counts.\n        void updateTotalsFromAtomics();\n\n        void runCurrentTest();\n        void invokeActiveTestCase();\n\n        bool testForMissingAssertions( Counts& assertions );\n\n        void assertionEnded( AssertionResult&& result );\n        void reportExpr\n                (   AssertionInfo const &info,\n                    ResultWas::OfType resultType,\n                    ITransientExpression const *expr,\n                    bool negated );\n\n        void populateReaction( AssertionReaction& reaction, bool has_normal_disposition );\n\n        // Creates dummy info for unexpected exceptions/fatal errors,\n        // where we do not have the access to one, but we still need\n        // to send one to the reporters.\n        AssertionInfo makeDummyAssertionInfo();\n\n    private:\n\n        void handleUnfinishedSections();\n        mutable Detail::Mutex m_assertionMutex;\n        TestRunInfo m_runInfo;\n        TestCaseHandle const* m_activeTestCase = nullptr;\n        ITracker* m_testCaseTracker = nullptr;\n        Optional<AssertionResult> m_lastResult;\n        IConfig const* m_config;\n        Totals m_totals;\n        Detail::AtomicCounts m_atomicAssertionCount;\n        IEventListenerPtr m_reporter;\n        std::vector<MessageInfo> m_messages;\n        // Owners for the UNSCOPED_X information macro\n        std::vector<ScopedMessage> m_messageScopes;\n        std::vector<SectionEndInfo> m_unfinishedSections;\n        std::vector<ITracker*> m_activeSections;\n        TrackerContext m_trackerContext;\n        Detail::unique_ptr<OutputRedirect> m_outputRedirect;\n        FatalConditionHandler m_fatalConditionhandler;\n        // Caches m_config->abortAfter() to avoid vptr calls/allow inlining\n        size_t m_abortAfterXFailedAssertions;\n        bool m_shouldReportUnexpected = true;\n        // Caches whether `assertionStarting` events should be sent to the reporter.\n        bool m_reportAssertionStarting;\n        // Caches whether `assertionEnded` events for successful assertions should be sent to the reporter\n        bool m_includeSuccessfulResults;\n        // Caches m_config->shouldDebugBreak() to avoid vptr calls/allow inlining\n        bool m_shouldDebugBreak;\n    };\n\n    void seedRng(IConfig const& config);\n    unsigned int rngSeed();\n} // end namespace Catch\n\n#endif // CATCH_RUN_CONTEXT_HPP_INCLUDED\n\n\n#ifndef CATCH_SHARDING_HPP_INCLUDED\n#define CATCH_SHARDING_HPP_INCLUDED\n\n#include <cassert>\n#include <algorithm>\n\nnamespace Catch {\n\n    template<typename Container>\n    Container createShard(Container const& container, std::size_t const shardCount, std::size_t const shardIndex) {\n        assert(shardCount > shardIndex);\n\n        if (shardCount == 1) {\n            return container;\n        }\n\n        const std::size_t totalTestCount = container.size();\n\n        const std::size_t shardSize = totalTestCount / shardCount;\n        const std::size_t leftoverTests = totalTestCount % shardCount;\n\n        const std::size_t startIndex = shardIndex * shardSize + (std::min)(shardIndex, leftoverTests);\n        const std::size_t endIndex = (shardIndex + 1) * shardSize + (std::min)(shardIndex + 1, leftoverTests);\n\n        auto startIterator = std::next(container.begin(), static_cast<std::ptrdiff_t>(startIndex));\n        auto endIterator = std::next(container.begin(), static_cast<std::ptrdiff_t>(endIndex));\n\n        return Container(startIterator, endIterator);\n    }\n\n}\n\n#endif // CATCH_SHARDING_HPP_INCLUDED\n\n\n#ifndef CATCH_SINGLETONS_HPP_INCLUDED\n#define CATCH_SINGLETONS_HPP_INCLUDED\n\nnamespace Catch {\n\n    struct ISingleton {\n        virtual ~ISingleton(); // = default\n    };\n\n\n    void addSingleton( ISingleton* singleton );\n    void cleanupSingletons();\n\n\n    template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT>\n    class Singleton : SingletonImplT, public ISingleton {\n\n        static auto getInternal() -> Singleton* {\n            static Singleton* s_instance = nullptr;\n            if( !s_instance ) {\n                s_instance = new Singleton;\n                addSingleton( s_instance );\n            }\n            return s_instance;\n        }\n\n    public:\n        static auto get() -> InterfaceT const& {\n            return *getInternal();\n        }\n        static auto getMutable() -> MutableInterfaceT& {\n            return *getInternal();\n        }\n    };\n\n} // namespace Catch\n\n#endif // CATCH_SINGLETONS_HPP_INCLUDED\n\n\n#ifndef CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED\n#define CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED\n\n\n#include <vector>\n#include <exception>\n\nnamespace Catch {\n\n    class StartupExceptionRegistry {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    public:\n        void add(std::exception_ptr const& exception) noexcept;\n        std::vector<std::exception_ptr> const& getExceptions() const noexcept;\n    private:\n        std::vector<std::exception_ptr> m_exceptions;\n#endif\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED\n\n\n\n#ifndef CATCH_STDSTREAMS_HPP_INCLUDED\n#define CATCH_STDSTREAMS_HPP_INCLUDED\n\n#include <iosfwd>\n\nnamespace Catch {\n\n    std::ostream& cout();\n    std::ostream& cerr();\n    std::ostream& clog();\n\n} // namespace Catch\n\n#endif\n\n\n#ifndef CATCH_STRING_MANIP_HPP_INCLUDED\n#define CATCH_STRING_MANIP_HPP_INCLUDED\n\n\n#include <cstdint>\n#include <string>\n#include <iosfwd>\n#include <vector>\n\nnamespace Catch {\n\n    bool startsWith( std::string const& s, std::string const& prefix );\n    bool startsWith( StringRef s, char prefix );\n    bool endsWith( std::string const& s, std::string const& suffix );\n    bool endsWith( std::string const& s, char suffix );\n    bool contains( std::string const& s, std::string const& infix );\n    void toLowerInPlace( std::string& s );\n    std::string toLower( std::string const& s );\n    char toLower( char c );\n    //! Returns a new string without whitespace at the start/end\n    std::string trim( std::string const& str );\n    //! Returns a substring of the original ref without whitespace. Beware lifetimes!\n    StringRef trim(StringRef ref);\n\n    // !!! Be aware, returns refs into original string - make sure original string outlives them\n    std::vector<StringRef> splitStringRef( StringRef str, char delimiter );\n    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );\n\n    /**\n     * Helper for streaming a \"count [maybe-plural-of-label]\" human-friendly string\n     *\n     * Usage example:\n     * ```cpp\n     * std::cout << \"Found \" << pluralise(count, \"error\") << '\\n';\n     * ```\n     *\n     * **Important:** The provided string must outlive the instance\n     */\n    class pluralise {\n        std::uint64_t m_count;\n        StringRef m_label;\n\n    public:\n        constexpr pluralise(std::uint64_t count, StringRef label):\n            m_count(count),\n            m_label(label)\n        {}\n\n        friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser );\n    };\n}\n\n#endif // CATCH_STRING_MANIP_HPP_INCLUDED\n\n\n#ifndef CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED\n#define CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED\n\n\n#include <map>\n#include <string>\n\nnamespace Catch {\n    struct SourceLineInfo;\n\n    class TagAliasRegistry : public ITagAliasRegistry {\n    public:\n        ~TagAliasRegistry() override;\n        TagAlias const* find( std::string const& alias ) const override;\n        std::string expandAliases( std::string const& unexpandedTestSpec ) const override;\n        void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo );\n\n    private:\n        std::map<std::string, TagAlias> m_registry;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED\n\n\n#ifndef CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED\n#define CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED\n\n#include <cstdint>\n\nnamespace Catch {\n\n    struct TestCaseInfo;\n\n    class TestCaseInfoHasher {\n    public:\n        using hash_t = std::uint64_t;\n        TestCaseInfoHasher( hash_t seed );\n        uint32_t operator()( TestCaseInfo const& t ) const;\n\n    private:\n        hash_t m_seed;\n    };\n\n} // namespace Catch\n\n#endif /* CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED */\n\n\n#ifndef CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED\n#define CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED\n\n\n#include <vector>\n\nnamespace Catch {\n\n    class IConfig;\n    class ITestInvoker;\n    class TestCaseHandle;\n    class TestSpec;\n\n    std::vector<TestCaseHandle> sortTests( IConfig const& config, std::vector<TestCaseHandle> const& unsortedTestCases );\n\n    bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config );\n\n    std::vector<TestCaseHandle> filterTests( std::vector<TestCaseHandle> const& testCases, TestSpec const& testSpec, IConfig const& config );\n    std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config );\n\n    class TestRegistry : public ITestCaseRegistry {\n    public:\n        void registerTest( Detail::unique_ptr<TestCaseInfo> testInfo, Detail::unique_ptr<ITestInvoker> testInvoker );\n\n        std::vector<TestCaseInfo*> const& getAllInfos() const override;\n        std::vector<TestCaseHandle> const& getAllTests() const override;\n        std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const override;\n\n        ~TestRegistry() override; // = default\n\n    private:\n        std::vector<Detail::unique_ptr<TestCaseInfo>> m_owned_test_infos;\n        // Keeps a materialized vector for `getAllInfos`.\n        // We should get rid of that eventually (see interface note)\n        std::vector<TestCaseInfo*> m_viewed_test_infos;\n\n        std::vector<Detail::unique_ptr<ITestInvoker>> m_invokers;\n        std::vector<TestCaseHandle> m_handles;\n        mutable TestRunOrder m_currentSortOrder = TestRunOrder::Declared;\n        mutable std::vector<TestCaseHandle> m_sortedFunctions;\n    };\n\n    ///////////////////////////////////////////////////////////////////////////\n\n\n} // end namespace Catch\n\n\n#endif // CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED\n\n\n#ifndef CATCH_TEST_SPEC_PARSER_HPP_INCLUDED\n#define CATCH_TEST_SPEC_PARSER_HPP_INCLUDED\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\n\n#include <vector>\n#include <string>\n\nnamespace Catch {\n\n    class ITagAliasRegistry;\n\n    class TestSpecParser {\n        enum Mode{ None, Name, QuotedName, Tag, EscapedName };\n        Mode m_mode = None;\n        Mode lastMode = None;\n        bool m_exclusion = false;\n        std::size_t m_pos = 0;\n        std::size_t m_realPatternPos = 0;\n        std::string m_arg;\n        std::string m_substring;\n        std::string m_patternName;\n        std::vector<std::size_t> m_escapeChars;\n        TestSpec::Filter m_currentFilter;\n        TestSpec m_testSpec;\n        ITagAliasRegistry const* m_tagAliases = nullptr;\n\n    public:\n        TestSpecParser( ITagAliasRegistry const& tagAliases );\n\n        TestSpecParser& parse( std::string const& arg );\n        TestSpec testSpec();\n\n    private:\n        bool visitChar( char c );\n        void startNewMode( Mode mode );\n        bool processNoneChar( char c );\n        void processNameChar( char c );\n        bool processOtherChar( char c );\n        void endMode();\n        void escape();\n        bool isControlChar( char c ) const;\n        void saveLastMode();\n        void revertBackToLastMode();\n        void addFilter();\n        bool separate();\n\n        // Handles common preprocessing of the pattern for name/tag patterns\n        std::string preprocessPattern();\n        // Adds the current pattern as a test name\n        void addNamePattern();\n        // Adds the current pattern as a tag\n        void addTagPattern();\n\n        inline void addCharToPattern(char c) {\n            m_substring += c;\n            m_patternName += c;\n            m_realPatternPos++;\n        }\n\n    };\n\n} // namespace Catch\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n#endif // CATCH_TEST_SPEC_PARSER_HPP_INCLUDED\n\n\n#ifndef CATCH_TEXTFLOW_HPP_INCLUDED\n#define CATCH_TEXTFLOW_HPP_INCLUDED\n\n\n#include <cassert>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n    namespace TextFlow {\n\n        class Columns;\n\n        /**\n         * Abstraction for a string with ansi escape sequences that\n         * automatically skips over escapes when iterating. Only graphical\n         * escape sequences are considered.\n         *\n         * Internal representation:\n         * An escape sequence looks like \\033[39;49m\n         * We need bidirectional iteration and the unbound length of escape\n         * sequences poses a problem for operator-- To make this work we'll\n         * replace the last `m` with a 0xff (this is a codepoint that won't have\n         * any utf-8 meaning).\n         */\n        class AnsiSkippingString {\n            std::string m_string;\n            std::size_t m_size = 0;\n\n            // perform 0xff replacement and calculate m_size\n            void preprocessString();\n\n        public:\n            class const_iterator;\n            using iterator = const_iterator;\n            // note: must be u-suffixed or this will cause a \"truncation of\n            // constant value\" warning on MSVC\n            static constexpr char sentinel = static_cast<char>( 0xffu );\n\n            explicit AnsiSkippingString( std::string const& text );\n            explicit AnsiSkippingString( std::string&& text );\n\n            const_iterator begin() const;\n            const_iterator end() const;\n\n            size_t size() const { return m_size; }\n\n            std::string substring( const_iterator begin,\n                                   const_iterator end ) const;\n        };\n\n        class AnsiSkippingString::const_iterator {\n            friend AnsiSkippingString;\n            struct EndTag {};\n\n            const std::string* m_string;\n            std::string::const_iterator m_it;\n\n            explicit const_iterator( const std::string& string, EndTag ):\n                m_string( &string ), m_it( string.end() ) {}\n\n            void tryParseAnsiEscapes();\n            void advance();\n            void unadvance();\n\n        public:\n            using difference_type = std::ptrdiff_t;\n            using value_type = char;\n            using pointer = value_type*;\n            using reference = value_type&;\n            using iterator_category = std::bidirectional_iterator_tag;\n\n            explicit const_iterator( const std::string& string ):\n                m_string( &string ), m_it( string.begin() ) {\n                tryParseAnsiEscapes();\n            }\n\n            char operator*() const { return *m_it; }\n\n            const_iterator& operator++() {\n                advance();\n                return *this;\n            }\n            const_iterator operator++( int ) {\n                iterator prev( *this );\n                operator++();\n                return prev;\n            }\n            const_iterator& operator--() {\n                unadvance();\n                return *this;\n            }\n            const_iterator operator--( int ) {\n                iterator prev( *this );\n                operator--();\n                return prev;\n            }\n\n            bool operator==( const_iterator const& other ) const {\n                return m_it == other.m_it;\n            }\n            bool operator!=( const_iterator const& other ) const {\n                return !operator==( other );\n            }\n            bool operator<=( const_iterator const& other ) const {\n                return m_it <= other.m_it;\n            }\n\n            const_iterator oneBefore() const {\n                auto it = *this;\n                return --it;\n            }\n        };\n\n        /**\n         * Represents a column of text with specific width and indentation\n         *\n         * When written out to a stream, it will perform linebreaking\n         * of the provided text so that the written lines fit within\n         * target width.\n         */\n        class Column {\n            // String to be written out\n            AnsiSkippingString m_string;\n            // Width of the column for linebreaking\n            size_t m_width = CATCH_CONFIG_CONSOLE_WIDTH - 1;\n            // Indentation of other lines (including first if initial indent is\n            // unset)\n            size_t m_indent = 0;\n            // Indentation of the first line\n            size_t m_initialIndent = std::string::npos;\n\n        public:\n            /**\n             * Iterates \"lines\" in `Column` and returns them\n             */\n            class const_iterator {\n                friend Column;\n                struct EndTag {};\n\n                Column const& m_column;\n                // Where does the current line start?\n                AnsiSkippingString::const_iterator m_lineStart;\n                // How long should the current line be?\n                AnsiSkippingString::const_iterator m_lineEnd;\n                // How far have we checked the string to iterate?\n                AnsiSkippingString::const_iterator m_parsedTo;\n                // Should a '-' be appended to the line?\n                bool m_addHyphen = false;\n\n                const_iterator( Column const& column, EndTag ):\n                    m_column( column ),\n                    m_lineStart( m_column.m_string.end() ),\n                    m_lineEnd( column.m_string.end() ),\n                    m_parsedTo( column.m_string.end() ) {}\n\n                // Calculates the length of the current line\n                void calcLength();\n\n                // Returns current indentation width\n                size_t indentSize() const;\n\n                // Creates an indented and (optionally) suffixed string from\n                // current iterator position, indentation and length.\n                std::string addIndentAndSuffix(\n                    AnsiSkippingString::const_iterator start,\n                    AnsiSkippingString::const_iterator end ) const;\n\n            public:\n                using difference_type = std::ptrdiff_t;\n                using value_type = std::string;\n                using pointer = value_type*;\n                using reference = value_type&;\n                using iterator_category = std::forward_iterator_tag;\n\n                explicit const_iterator( Column const& column );\n\n                std::string operator*() const;\n\n                const_iterator& operator++();\n                const_iterator operator++( int );\n\n                bool operator==( const_iterator const& other ) const {\n                    return m_lineStart == other.m_lineStart &&\n                           &m_column == &other.m_column;\n                }\n                bool operator!=( const_iterator const& other ) const {\n                    return !operator==( other );\n                }\n            };\n            using iterator = const_iterator;\n\n            explicit Column( std::string const& text ): m_string( text ) {}\n            explicit Column( std::string&& text ):\n                m_string( CATCH_MOVE( text ) ) {}\n\n            Column& width( size_t newWidth ) & {\n                assert( newWidth > 0 );\n                m_width = newWidth;\n                return *this;\n            }\n            Column&& width( size_t newWidth ) && {\n                assert( newWidth > 0 );\n                m_width = newWidth;\n                return CATCH_MOVE( *this );\n            }\n            Column& indent( size_t newIndent ) & {\n                m_indent = newIndent;\n                return *this;\n            }\n            Column&& indent( size_t newIndent ) && {\n                m_indent = newIndent;\n                return CATCH_MOVE( *this );\n            }\n            Column& initialIndent( size_t newIndent ) & {\n                m_initialIndent = newIndent;\n                return *this;\n            }\n            Column&& initialIndent( size_t newIndent ) && {\n                m_initialIndent = newIndent;\n                return CATCH_MOVE( *this );\n            }\n\n            size_t width() const { return m_width; }\n            const_iterator begin() const { return const_iterator( *this ); }\n            const_iterator end() const {\n                return { *this, const_iterator::EndTag{} };\n            }\n\n            friend std::ostream& operator<<( std::ostream& os,\n                                             Column const& col );\n\n            friend Columns operator+( Column const& lhs, Column const& rhs );\n            friend Columns operator+( Column&& lhs, Column&& rhs );\n        };\n\n        //! Creates a column that serves as an empty space of specific width\n        Column Spacer( size_t spaceWidth );\n\n        class Columns {\n            std::vector<Column> m_columns;\n\n        public:\n            class iterator {\n                friend Columns;\n                struct EndTag {};\n\n                std::vector<Column> const& m_columns;\n                std::vector<Column::const_iterator> m_iterators;\n                size_t m_activeIterators;\n\n                iterator( Columns const& columns, EndTag );\n\n            public:\n                using difference_type = std::ptrdiff_t;\n                using value_type = std::string;\n                using pointer = value_type*;\n                using reference = value_type&;\n                using iterator_category = std::forward_iterator_tag;\n\n                explicit iterator( Columns const& columns );\n\n                auto operator==( iterator const& other ) const -> bool {\n                    return m_iterators == other.m_iterators;\n                }\n                auto operator!=( iterator const& other ) const -> bool {\n                    return m_iterators != other.m_iterators;\n                }\n                std::string operator*() const;\n                iterator& operator++();\n                iterator operator++( int );\n            };\n            using const_iterator = iterator;\n\n            iterator begin() const { return iterator( *this ); }\n            iterator end() const { return { *this, iterator::EndTag() }; }\n\n            friend Columns& operator+=( Columns& lhs, Column const& rhs );\n            friend Columns& operator+=( Columns& lhs, Column&& rhs );\n            friend Columns operator+( Columns const& lhs, Column const& rhs );\n            friend Columns operator+( Columns&& lhs, Column&& rhs );\n\n            friend std::ostream& operator<<( std::ostream& os,\n                                             Columns const& cols );\n        };\n\n    } // namespace TextFlow\n} // namespace Catch\n#endif // CATCH_TEXTFLOW_HPP_INCLUDED\n\n\n#ifndef CATCH_TO_STRING_HPP_INCLUDED\n#define CATCH_TO_STRING_HPP_INCLUDED\n\n#include <string>\n\n\nnamespace Catch {\n    template <typename T>\n    std::string to_string(T const& t) {\n#if defined(CATCH_CONFIG_CPP11_TO_STRING)\n        return std::to_string(t);\n#else\n        ReusableStringStream rss;\n        rss << t;\n        return rss.str();\n#endif\n    }\n} // end namespace Catch\n\n#endif // CATCH_TO_STRING_HPP_INCLUDED\n\n\n#ifndef CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED\n#define CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED\n\nnamespace Catch {\n    bool uncaught_exceptions();\n} // end namespace Catch\n\n#endif // CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED\n\n\n#ifndef CATCH_XMLWRITER_HPP_INCLUDED\n#define CATCH_XMLWRITER_HPP_INCLUDED\n\n\n#include <iosfwd>\n#include <vector>\n#include <cstdint>\n\nnamespace Catch {\n    enum class XmlFormatting : std::uint8_t {\n        None = 0x00,\n        Indent = 0x01,\n        Newline = 0x02,\n    };\n\n    constexpr XmlFormatting operator|( XmlFormatting lhs, XmlFormatting rhs ) {\n        return static_cast<XmlFormatting>( static_cast<std::uint8_t>( lhs ) |\n                                           static_cast<std::uint8_t>( rhs ) );\n    }\n\n    constexpr XmlFormatting operator&( XmlFormatting lhs, XmlFormatting rhs ) {\n        return static_cast<XmlFormatting>( static_cast<std::uint8_t>( lhs ) &\n                                           static_cast<std::uint8_t>( rhs ) );\n    }\n\n\n    /**\n     * Helper for XML-encoding text (escaping angle brackets, quotes, etc)\n     *\n     * Note: doesn't take ownership of passed strings, and thus the\n     *       encoded string must outlive the encoding instance.\n     */\n    class XmlEncode {\n    public:\n        enum ForWhat { ForTextNodes, ForAttributes };\n\n        constexpr XmlEncode( StringRef str, ForWhat forWhat = ForTextNodes ):\n            m_str( str ), m_forWhat( forWhat ) {}\n\n\n        void encodeTo( std::ostream& os ) const;\n\n        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode );\n\n    private:\n        StringRef m_str;\n        ForWhat m_forWhat;\n    };\n\n    class XmlWriter {\n    public:\n\n        class ScopedElement {\n        public:\n            ScopedElement( XmlWriter* writer, XmlFormatting fmt );\n\n            ScopedElement( ScopedElement&& other ) noexcept;\n            ScopedElement& operator=( ScopedElement&& other ) noexcept;\n\n            ~ScopedElement();\n\n            ScopedElement&\n            writeText( StringRef text,\n                       XmlFormatting fmt = XmlFormatting::Newline |\n                                           XmlFormatting::Indent );\n\n            ScopedElement& writeAttribute( StringRef name,\n                                           StringRef attribute );\n            template <typename T,\n                      // Without this SFINAE, this overload is a better match\n                      // for `std::string`, `char const*`, `char const[N]` args.\n                      // While it would still work, it would cause code bloat\n                      // and multiple iteration over the strings\n                      typename = typename std::enable_if_t<\n                          !std::is_convertible<T, StringRef>::value>>\n            ScopedElement& writeAttribute( StringRef name,\n                                           T const& attribute ) {\n                m_writer->writeAttribute( name, attribute );\n                return *this;\n            }\n\n        private:\n            XmlWriter* m_writer = nullptr;\n            XmlFormatting m_fmt;\n        };\n\n        XmlWriter( std::ostream& os );\n        ~XmlWriter();\n\n        XmlWriter( XmlWriter const& ) = delete;\n        XmlWriter& operator=( XmlWriter const& ) = delete;\n\n        XmlWriter& startElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        ScopedElement scopedElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        XmlWriter& endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        //! The attribute content is XML-encoded\n        XmlWriter& writeAttribute( StringRef name, StringRef attribute );\n\n        //! Writes the attribute as \"true/false\"\n        XmlWriter& writeAttribute( StringRef name, bool attribute );\n\n        //! The attribute content is XML-encoded\n        XmlWriter& writeAttribute( StringRef name, char const* attribute );\n\n        //! The attribute value must provide op<<(ostream&, T). The resulting\n        //! serialization is XML-encoded\n        template <typename T,\n                  // Without this SFINAE, this overload is a better match\n                  // for `std::string`, `char const*`, `char const[N]` args.\n                  // While it would still work, it would cause code bloat\n                  // and multiple iteration over the strings\n                  typename = typename std::enable_if_t<\n                      !std::is_convertible<T, StringRef>::value>>\n        XmlWriter& writeAttribute( StringRef name, T const& attribute ) {\n            ReusableStringStream rss;\n            rss << attribute;\n            return writeAttribute( name, rss.str() );\n        }\n\n        //! Writes escaped `text` in a element\n        XmlWriter& writeText( StringRef text,\n                              XmlFormatting fmt = XmlFormatting::Newline |\n                                                  XmlFormatting::Indent );\n\n        //! Writes XML comment as \"<!-- text -->\"\n        XmlWriter& writeComment( StringRef text,\n                                 XmlFormatting fmt = XmlFormatting::Newline |\n                                                     XmlFormatting::Indent );\n\n        void writeStylesheetRef( StringRef url );\n\n        void ensureTagClosed();\n\n    private:\n\n        void applyFormatting(XmlFormatting fmt);\n\n        void writeDeclaration();\n\n        void newlineIfNecessary();\n\n        bool m_tagIsOpen = false;\n        bool m_needsNewline = false;\n        std::vector<std::string> m_tags;\n        std::string m_indent;\n        std::ostream& m_os;\n    };\n\n}\n\n#endif // CATCH_XMLWRITER_HPP_INCLUDED\n\n\n/** \\file\n * This is a convenience header for Catch2's Matcher support. It includes\n * **all** of Catch2 headers related to matchers.\n *\n * Generally the Catch2 users should use specific includes they need,\n * but this header can be used instead for ease-of-experimentation, or\n * just plain convenience, at the cost of increased compilation times.\n *\n * When a new header is added to either the `matchers` folder, or to\n * the corresponding internal subfolder, it should be added here.\n */\n\n#ifndef CATCH_MATCHERS_ALL_HPP_INCLUDED\n#define CATCH_MATCHERS_ALL_HPP_INCLUDED\n\n\n\n#ifndef CATCH_MATCHERS_HPP_INCLUDED\n#define CATCH_MATCHERS_HPP_INCLUDED\n\n\n\n#ifndef CATCH_MATCHERS_IMPL_HPP_INCLUDED\n#define CATCH_MATCHERS_IMPL_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch {\n\n#ifdef __clang__\n#    pragma clang diagnostic push\n#    pragma clang diagnostic ignored \"-Wsign-compare\"\n#    pragma clang diagnostic ignored \"-Wnon-virtual-dtor\"\n#elif defined __GNUC__\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wsign-compare\"\n#    pragma GCC diagnostic ignored \"-Wnon-virtual-dtor\"\n#endif\n\n    template<typename ArgT, typename MatcherT>\n    class MatchExpr : public ITransientExpression {\n        ArgT && m_arg;\n        MatcherT const& m_matcher;\n    public:\n        constexpr MatchExpr( ArgT && arg, MatcherT const& matcher )\n        :   ITransientExpression{ true, matcher.match( arg ) }, // not forwarding arg here on purpose\n            m_arg( CATCH_FORWARD(arg) ),\n            m_matcher( matcher )\n        {}\n\n        void streamReconstructedExpression( std::ostream& os ) const override {\n            os << Catch::Detail::stringify( m_arg )\n               << ' '\n               << m_matcher.toString();\n        }\n    };\n\n#ifdef __clang__\n#    pragma clang diagnostic pop\n#elif defined __GNUC__\n#    pragma GCC diagnostic pop\n#endif\n\n\n    namespace Matchers {\n        template <typename ArgT>\n        class MatcherBase;\n    }\n\n    using StringMatcher = Matchers::MatcherBase<std::string>;\n\n    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher );\n\n    template<typename ArgT, typename MatcherT>\n    constexpr MatchExpr<ArgT, MatcherT>\n    makeMatchExpr( ArgT&& arg, MatcherT const& matcher ) {\n        return MatchExpr<ArgT, MatcherT>( CATCH_FORWARD(arg), matcher );\n    }\n\n} // namespace Catch\n\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) \", \" CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \\\n        INTERNAL_CATCH_TRY { \\\n            catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher ) ); \\\n        } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \\\n        catchAssertionHandler.complete(); \\\n    } while( false )\n\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) \", \" CATCH_INTERNAL_STRINGIFY(exceptionType) \", \" CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n                CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \\\n                static_cast<void>(__VA_ARGS__ ); \\\n                CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( exceptionType const& ex ) { \\\n                catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher ) ); \\\n            } \\\n            catch( ... ) { \\\n                catchAssertionHandler.handleUnexpectedInflightException(); \\\n            } \\\n        else \\\n            catchAssertionHandler.handleThrowingCallSkipped(); \\\n        catchAssertionHandler.complete(); \\\n    } while( false )\n\n\n#endif // CATCH_MATCHERS_IMPL_HPP_INCLUDED\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\nnamespace Matchers {\n\n    class MatcherUntypedBase {\n    public:\n        MatcherUntypedBase() = default;\n\n        MatcherUntypedBase(MatcherUntypedBase const&) = default;\n        MatcherUntypedBase(MatcherUntypedBase&&) = default;\n\n        MatcherUntypedBase& operator = (MatcherUntypedBase const&) = delete;\n        MatcherUntypedBase& operator = (MatcherUntypedBase&&) = delete;\n\n        std::string toString() const;\n\n    protected:\n        virtual ~MatcherUntypedBase(); // = default;\n        virtual std::string describe() const = 0;\n        mutable std::string m_cachedToString;\n    };\n\n\n    template<typename T>\n    class MatcherBase : public MatcherUntypedBase {\n    public:\n        virtual bool match( T const& arg ) const = 0;\n    };\n\n    namespace Detail {\n\n        template<typename ArgT>\n        class MatchAllOf final : public MatcherBase<ArgT> {\n            std::vector<MatcherBase<ArgT> const*> m_matchers;\n\n        public:\n            MatchAllOf() = default;\n            MatchAllOf(MatchAllOf const&) = delete;\n            MatchAllOf& operator=(MatchAllOf const&) = delete;\n            MatchAllOf(MatchAllOf&&) = default;\n            MatchAllOf& operator=(MatchAllOf&&) = default;\n\n\n            bool match( ArgT const& arg ) const override {\n                for( auto matcher : m_matchers ) {\n                    if (!matcher->match(arg))\n                        return false;\n                }\n                return true;\n            }\n            std::string describe() const override {\n                std::string description;\n                description.reserve( 4 + m_matchers.size()*32 );\n                description += \"( \";\n                bool first = true;\n                for( auto matcher : m_matchers ) {\n                    if( first )\n                        first = false;\n                    else\n                        description += \" and \";\n                    description += matcher->toString();\n                }\n                description += \" )\";\n                return description;\n            }\n\n            friend MatchAllOf operator&& (MatchAllOf&& lhs, MatcherBase<ArgT> const& rhs) {\n                lhs.m_matchers.push_back(&rhs);\n                return CATCH_MOVE(lhs);\n            }\n            friend MatchAllOf operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf&& rhs) {\n                rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs);\n                return CATCH_MOVE(rhs);\n            }\n        };\n\n        //! lvalue overload is intentionally deleted, users should\n        //! not be trying to compose stored composition matchers\n        template<typename ArgT>\n        MatchAllOf<ArgT> operator&& (MatchAllOf<ArgT> const& lhs, MatcherBase<ArgT> const& rhs) = delete;\n        //! lvalue overload is intentionally deleted, users should\n        //! not be trying to compose stored composition matchers\n        template<typename ArgT>\n        MatchAllOf<ArgT> operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf<ArgT> const& rhs) = delete;\n\n        template<typename ArgT>\n        class MatchAnyOf final : public MatcherBase<ArgT> {\n            std::vector<MatcherBase<ArgT> const*> m_matchers;\n        public:\n            MatchAnyOf() = default;\n            MatchAnyOf(MatchAnyOf const&) = delete;\n            MatchAnyOf& operator=(MatchAnyOf const&) = delete;\n            MatchAnyOf(MatchAnyOf&&) = default;\n            MatchAnyOf& operator=(MatchAnyOf&&) = default;\n\n            bool match( ArgT const& arg ) const override {\n                for( auto matcher : m_matchers ) {\n                    if (matcher->match(arg))\n                        return true;\n                }\n                return false;\n            }\n            std::string describe() const override {\n                std::string description;\n                description.reserve( 4 + m_matchers.size()*32 );\n                description += \"( \";\n                bool first = true;\n                for( auto matcher : m_matchers ) {\n                    if( first )\n                        first = false;\n                    else\n                        description += \" or \";\n                    description += matcher->toString();\n                }\n                description += \" )\";\n                return description;\n            }\n\n            friend MatchAnyOf operator|| (MatchAnyOf&& lhs, MatcherBase<ArgT> const& rhs) {\n                lhs.m_matchers.push_back(&rhs);\n                return CATCH_MOVE(lhs);\n            }\n            friend MatchAnyOf operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf&& rhs) {\n                rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs);\n                return CATCH_MOVE(rhs);\n            }\n        };\n\n        //! lvalue overload is intentionally deleted, users should\n        //! not be trying to compose stored composition matchers\n        template<typename ArgT>\n        MatchAnyOf<ArgT> operator|| (MatchAnyOf<ArgT> const& lhs, MatcherBase<ArgT> const& rhs) = delete;\n        //! lvalue overload is intentionally deleted, users should\n        //! not be trying to compose stored composition matchers\n        template<typename ArgT>\n        MatchAnyOf<ArgT> operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf<ArgT> const& rhs) = delete;\n\n        template<typename ArgT>\n        class MatchNotOf final : public MatcherBase<ArgT> {\n            MatcherBase<ArgT> const& m_underlyingMatcher;\n\n        public:\n            explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ):\n                m_underlyingMatcher( underlyingMatcher )\n            {}\n\n            bool match( ArgT const& arg ) const override {\n                return !m_underlyingMatcher.match( arg );\n            }\n\n            std::string describe() const override {\n                return \"not \" + m_underlyingMatcher.toString();\n            }\n        };\n\n    } // namespace Detail\n\n    template <typename T>\n    Detail::MatchAllOf<T> operator&& (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) {\n        return Detail::MatchAllOf<T>{} && lhs && rhs;\n    }\n    template <typename T>\n    Detail::MatchAnyOf<T> operator|| (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) {\n        return Detail::MatchAnyOf<T>{} || lhs || rhs;\n    }\n\n    template <typename T>\n    Detail::MatchNotOf<T> operator! (MatcherBase<T> const& matcher) {\n        return Detail::MatchNotOf<T>{ matcher };\n    }\n\n\n} // namespace Matchers\n} // namespace Catch\n\n\n#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)\n  #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"CATCH_REQUIRE_THROWS_WITH\", Catch::ResultDisposition::Normal, matcher, expr )\n  #define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"CATCH_REQUIRE_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )\n\n  #define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"CATCH_CHECK_THROWS_WITH\", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n  #define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"CATCH_CHECK_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n\n  #define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"CATCH_CHECK_THAT\", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )\n  #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"CATCH_REQUIRE_THAT\", matcher, Catch::ResultDisposition::Normal, arg )\n\n#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)\n\n  #define CATCH_REQUIRE_THROWS_WITH( expr, matcher )                   (void)(0)\n  #define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)\n\n  #define CATCH_CHECK_THROWS_WITH( expr, matcher )                     (void)(0)\n  #define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher )   (void)(0)\n\n  #define CATCH_CHECK_THAT( arg, matcher )                             (void)(0)\n  #define CATCH_REQUIRE_THAT( arg, matcher )                           (void)(0)\n\n#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)\n\n  #define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"REQUIRE_THROWS_WITH\", Catch::ResultDisposition::Normal, matcher, expr )\n  #define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"REQUIRE_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )\n\n  #define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"CHECK_THROWS_WITH\", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n  #define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"CHECK_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n\n  #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"CHECK_THAT\", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )\n  #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"REQUIRE_THAT\", matcher, Catch::ResultDisposition::Normal, arg )\n\n#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)\n\n  #define REQUIRE_THROWS_WITH( expr, matcher )                   (void)(0)\n  #define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)\n\n  #define CHECK_THROWS_WITH( expr, matcher )                     (void)(0)\n  #define CHECK_THROWS_MATCHES( expr, exceptionType, matcher )   (void)(0)\n\n  #define CHECK_THAT( arg, matcher )                             (void)(0)\n  #define REQUIRE_THAT( arg, matcher )                           (void)(0)\n\n#endif // end of user facing macro declarations\n\n#endif // CATCH_MATCHERS_HPP_INCLUDED\n\n\n#ifndef CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED\n#define CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED\n\n\n\n#ifndef CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED\n#define CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED\n\n\n#include <array>\n#include <algorithm>\n#include <string>\n#include <type_traits>\n\nnamespace Catch {\nnamespace Matchers {\n    class MatcherGenericBase : public MatcherUntypedBase {\n    public:\n        MatcherGenericBase() = default;\n        ~MatcherGenericBase() override; // = default;\n\n        MatcherGenericBase(MatcherGenericBase const&) = default;\n        MatcherGenericBase(MatcherGenericBase&&) = default;\n\n        MatcherGenericBase& operator=(MatcherGenericBase const&) = delete;\n        MatcherGenericBase& operator=(MatcherGenericBase&&) = delete;\n    };\n\n\n    namespace Detail {\n        template<std::size_t N, std::size_t M>\n        std::array<void const*, N + M> array_cat(std::array<void const*, N> && lhs, std::array<void const*, M> && rhs) {\n            std::array<void const*, N + M> arr{};\n            std::copy_n(lhs.begin(), N, arr.begin());\n            std::copy_n(rhs.begin(), M, arr.begin() + N);\n            return arr;\n        }\n\n        template<std::size_t N>\n        std::array<void const*, N+1> array_cat(std::array<void const*, N> && lhs, void const* rhs) {\n            std::array<void const*, N+1> arr{};\n            std::copy_n(lhs.begin(), N, arr.begin());\n            arr[N] = rhs;\n            return arr;\n        }\n\n        template<std::size_t N>\n        std::array<void const*, N+1> array_cat(void const* lhs, std::array<void const*, N> && rhs) {\n            std::array<void const*, N + 1> arr{ {lhs} };\n            std::copy_n(rhs.begin(), N, arr.begin() + 1);\n            return arr;\n        }\n\n        template<typename T>\n        static constexpr bool is_generic_matcher_v = std::is_base_of<\n            Catch::Matchers::MatcherGenericBase,\n            std::remove_cv_t<std::remove_reference_t<T>>\n        >::value;\n\n        template<typename... Ts>\n        static constexpr bool are_generic_matchers_v = Catch::Detail::conjunction<std::integral_constant<bool,is_generic_matcher_v<Ts>>...>::value;\n\n        template<typename T>\n        static constexpr bool is_matcher_v = std::is_base_of<\n            Catch::Matchers::MatcherUntypedBase,\n            std::remove_cv_t<std::remove_reference_t<T>>\n        >::value;\n\n\n        template<std::size_t N, typename Arg>\n        bool match_all_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) {\n            return true;\n        }\n\n        template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices>\n        bool match_all_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) {\n            return static_cast<T const*>(matchers[Idx])->match(arg) && match_all_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{});\n        }\n\n\n        template<std::size_t N, typename Arg>\n        bool match_any_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) {\n            return false;\n        }\n\n        template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices>\n        bool match_any_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) {\n            return static_cast<T const*>(matchers[Idx])->match(arg) || match_any_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{});\n        }\n\n        std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end);\n\n        template<typename... MatcherTs, std::size_t... Idx>\n        std::string describe_multi_matcher(StringRef combine, std::array<void const*, sizeof...(MatcherTs)> const& matchers, std::index_sequence<Idx...>) {\n            std::array<std::string, sizeof...(MatcherTs)> descriptions {{\n                static_cast<MatcherTs const*>(matchers[Idx])->toString()...\n            }};\n\n            return describe_multi_matcher(combine, descriptions.data(), descriptions.data() + descriptions.size());\n        }\n\n\n        template<typename... MatcherTs>\n        class MatchAllOfGeneric final : public MatcherGenericBase {\n        public:\n            MatchAllOfGeneric(MatchAllOfGeneric const&) = delete;\n            MatchAllOfGeneric& operator=(MatchAllOfGeneric const&) = delete;\n            MatchAllOfGeneric(MatchAllOfGeneric&&) = default;\n            MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default;\n\n            MatchAllOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {}\n            explicit MatchAllOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}\n\n            template<typename Arg>\n            bool match(Arg&& arg) const {\n                return match_all_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{});\n            }\n\n            std::string describe() const override {\n                return describe_multi_matcher<MatcherTs...>(\" and \"_sr, m_matchers, std::index_sequence_for<MatcherTs...>{});\n            }\n\n            // Has to be public to enable the concatenating operators\n            // below, because they are not friend of the RHS, only LHS,\n            // and thus cannot access private fields of RHS\n            std::array<void const*, sizeof...( MatcherTs )> m_matchers;\n\n\n            //! Avoids type nesting for `GenericAllOf && GenericAllOf` case\n            template<typename... MatchersRHS>\n            friend\n            MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && (\n                    MatchAllOfGeneric<MatcherTs...>&& lhs,\n                    MatchAllOfGeneric<MatchersRHS...>&& rhs) {\n                return MatchAllOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};\n            }\n\n            //! Avoids type nesting for `GenericAllOf && some matcher` case\n            template<typename MatcherRHS>\n            friend std::enable_if_t<is_matcher_v<MatcherRHS>,\n            MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && (\n                    MatchAllOfGeneric<MatcherTs...>&& lhs,\n                    MatcherRHS const& rhs) {\n                return MatchAllOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(&rhs))};\n            }\n\n            //! Avoids type nesting for `some matcher && GenericAllOf` case\n            template<typename MatcherLHS>\n            friend std::enable_if_t<is_matcher_v<MatcherLHS>,\n            MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && (\n                    MatcherLHS const& lhs,\n                    MatchAllOfGeneric<MatcherTs...>&& rhs) {\n                return MatchAllOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))};\n            }\n        };\n\n\n        template<typename... MatcherTs>\n        class MatchAnyOfGeneric final : public MatcherGenericBase {\n        public:\n            MatchAnyOfGeneric(MatchAnyOfGeneric const&) = delete;\n            MatchAnyOfGeneric& operator=(MatchAnyOfGeneric const&) = delete;\n            MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default;\n            MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default;\n\n            MatchAnyOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {}\n            explicit MatchAnyOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}\n\n            template<typename Arg>\n            bool match(Arg&& arg) const {\n                return match_any_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{});\n            }\n\n            std::string describe() const override {\n                return describe_multi_matcher<MatcherTs...>(\" or \"_sr, m_matchers, std::index_sequence_for<MatcherTs...>{});\n            }\n\n\n            // Has to be public to enable the concatenating operators\n            // below, because they are not friend of the RHS, only LHS,\n            // and thus cannot access private fields of RHS\n            std::array<void const*, sizeof...( MatcherTs )> m_matchers;\n\n            //! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case\n            template<typename... MatchersRHS>\n            friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || (\n                    MatchAnyOfGeneric<MatcherTs...>&& lhs,\n                    MatchAnyOfGeneric<MatchersRHS...>&& rhs) {\n                return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};\n            }\n\n            //! Avoids type nesting for `GenericAnyOf || some matcher` case\n            template<typename MatcherRHS>\n            friend std::enable_if_t<is_matcher_v<MatcherRHS>,\n            MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || (\n                    MatchAnyOfGeneric<MatcherTs...>&& lhs,\n                    MatcherRHS const& rhs) {\n                return MatchAnyOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))};\n            }\n\n            //! Avoids type nesting for `some matcher || GenericAnyOf` case\n            template<typename MatcherLHS>\n            friend std::enable_if_t<is_matcher_v<MatcherLHS>,\n            MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || (\n                MatcherLHS const& lhs,\n                MatchAnyOfGeneric<MatcherTs...>&& rhs) {\n                return MatchAnyOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))};\n            }\n        };\n\n\n        template<typename MatcherT>\n        class MatchNotOfGeneric final : public MatcherGenericBase {\n            MatcherT const& m_matcher;\n\n        public:\n            MatchNotOfGeneric(MatchNotOfGeneric const&) = delete;\n            MatchNotOfGeneric& operator=(MatchNotOfGeneric const&) = delete;\n            MatchNotOfGeneric(MatchNotOfGeneric&&) = default;\n            MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default;\n\n            explicit MatchNotOfGeneric(MatcherT const& matcher) : m_matcher{matcher} {}\n\n            template<typename Arg>\n            bool match(Arg&& arg) const {\n                return !m_matcher.match(arg);\n            }\n\n            std::string describe() const override {\n                return \"not \" + m_matcher.toString();\n            }\n\n            //! Negating negation can just unwrap and return underlying matcher\n            friend MatcherT const& operator ! (MatchNotOfGeneric<MatcherT> const& matcher) {\n                return matcher.m_matcher;\n            }\n        };\n    } // namespace Detail\n\n\n    // compose only generic matchers\n    template<typename MatcherLHS, typename MatcherRHS>\n    std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>>\n        operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) {\n        return { lhs, rhs };\n    }\n\n    template<typename MatcherLHS, typename MatcherRHS>\n    std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>\n        operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) {\n        return { lhs, rhs };\n    }\n\n    //! Wrap provided generic matcher in generic negator\n    template<typename MatcherT>\n    std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>, Detail::MatchNotOfGeneric<MatcherT>>\n        operator ! (MatcherT const& matcher) {\n        return Detail::MatchNotOfGeneric<MatcherT>{matcher};\n    }\n\n\n    // compose mixed generic and non-generic matchers\n    template<typename MatcherLHS, typename ArgRHS>\n    std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>\n        operator && (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {\n        return { lhs, rhs };\n    }\n\n    template<typename ArgLHS, typename MatcherRHS>\n    std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAllOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>\n        operator && (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {\n        return { lhs, rhs };\n    }\n\n    template<typename MatcherLHS, typename ArgRHS>\n    std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>\n        operator || (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {\n        return { lhs, rhs };\n    }\n\n    template<typename ArgLHS, typename MatcherRHS>\n    std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>\n        operator || (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {\n        return { lhs, rhs };\n    }\n\n} // namespace Matchers\n} // namespace Catch\n\n#endif // CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED\n\nnamespace Catch {\n    namespace Matchers {\n\n        class IsEmptyMatcher final : public MatcherGenericBase {\n        public:\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS)\n                using Catch::Detail::empty;\n#else\n                using std::empty;\n#endif\n                return empty(rng);\n            }\n\n            std::string describe() const override;\n        };\n\n        class HasSizeMatcher final : public MatcherGenericBase {\n            std::size_t m_target_size;\n        public:\n            explicit HasSizeMatcher(std::size_t target_size):\n                m_target_size(target_size)\n            {}\n\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS)\n                using Catch::Detail::size;\n#else\n                using std::size;\n#endif\n                return size(rng) == m_target_size;\n            }\n\n            std::string describe() const override;\n        };\n\n        template <typename Matcher>\n        class SizeMatchesMatcher final : public MatcherGenericBase {\n            Matcher m_matcher;\n        public:\n            explicit SizeMatchesMatcher(Matcher m):\n                m_matcher(CATCH_MOVE(m))\n            {}\n\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS)\n                using Catch::Detail::size;\n#else\n                using std::size;\n#endif\n                return m_matcher.match(size(rng));\n            }\n\n            std::string describe() const override {\n                return \"size matches \" + m_matcher.describe();\n            }\n        };\n\n\n        //! Creates a matcher that accepts empty ranges/containers\n        IsEmptyMatcher IsEmpty();\n        //! Creates a matcher that accepts ranges/containers with specific size\n        HasSizeMatcher SizeIs(std::size_t sz);\n        template <typename Matcher>\n        std::enable_if_t<Detail::is_matcher_v<Matcher>,\n        SizeMatchesMatcher<Matcher>> SizeIs(Matcher&& m) {\n            return SizeMatchesMatcher<Matcher>{CATCH_FORWARD(m)};\n        }\n\n    } // end namespace Matchers\n} // end namespace Catch\n\n#endif // CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED\n\n\n#ifndef CATCH_MATCHERS_CONTAINS_HPP_INCLUDED\n#define CATCH_MATCHERS_CONTAINS_HPP_INCLUDED\n\n\n#include <functional>\n#include <type_traits>\n\nnamespace Catch {\n    namespace Matchers {\n        //! Matcher for checking that an element in range is equal to specific element\n        template <typename T, typename Equality>\n        class ContainsElementMatcher final : public MatcherGenericBase {\n            T m_desired;\n            Equality m_eq;\n        public:\n            template <typename T2, typename Equality2>\n            ContainsElementMatcher(T2&& target, Equality2&& predicate):\n                m_desired(CATCH_FORWARD(target)),\n                m_eq(CATCH_FORWARD(predicate))\n            {}\n\n            std::string describe() const override {\n                return \"contains element \" + Catch::Detail::stringify(m_desired);\n            }\n\n            template <typename RangeLike>\n            bool match( RangeLike&& rng ) const {\n                for ( auto&& elem : rng ) {\n                    if ( m_eq( elem, m_desired ) ) { return true; }\n                }\n                return false;\n            }\n        };\n\n        //! Meta-matcher for checking that an element in a range matches a specific matcher\n        template <typename Matcher>\n        class ContainsMatcherMatcher final : public MatcherGenericBase {\n            Matcher m_matcher;\n        public:\n            // Note that we do a copy+move to avoid having to SFINAE this\n            // constructor (and also avoid some perfect forwarding failure\n            // cases)\n            ContainsMatcherMatcher(Matcher matcher):\n                m_matcher(CATCH_MOVE(matcher))\n            {}\n\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n                for (auto&& elem : rng) {\n                    if (m_matcher.match(elem)) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n\n            std::string describe() const override {\n                return \"contains element matching \" + m_matcher.describe();\n            }\n        };\n\n        /**\n         * Creates a matcher that checks whether a range contains a specific element.\n         *\n         * Uses `std::equal_to` to do the comparison\n         */\n        template <typename T>\n        std::enable_if_t<!Detail::is_matcher_v<T>,\n        ContainsElementMatcher<T, std::equal_to<>>> Contains(T&& elem) {\n            return { CATCH_FORWARD(elem), std::equal_to<>{} };\n        }\n\n        //! Creates a matcher that checks whether a range contains element matching a matcher\n        template <typename Matcher>\n        std::enable_if_t<Detail::is_matcher_v<Matcher>,\n        ContainsMatcherMatcher<Matcher>> Contains(Matcher&& matcher) {\n            return { CATCH_FORWARD(matcher) };\n        }\n\n        /**\n         * Creates a matcher that checks whether a range contains a specific element.\n         *\n         * Uses `eq` to do the comparisons, the element is provided on the rhs\n         */\n        template <typename T, typename Equality>\n        ContainsElementMatcher<T, Equality> Contains(T&& elem, Equality&& eq) {\n            return { CATCH_FORWARD(elem), CATCH_FORWARD(eq) };\n        }\n\n    }\n}\n\n#endif // CATCH_MATCHERS_CONTAINS_HPP_INCLUDED\n\n\n#ifndef CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED\n#define CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED\n\n\nnamespace Catch {\nnamespace Matchers {\n\nclass ExceptionMessageMatcher final : public MatcherBase<std::exception> {\n    std::string m_message;\npublic:\n\n    ExceptionMessageMatcher(std::string const& message):\n        m_message(message)\n    {}\n\n    bool match(std::exception const& ex) const override;\n\n    std::string describe() const override;\n};\n\n//! Creates a matcher that checks whether a std derived exception has the provided message\nExceptionMessageMatcher Message(std::string const& message);\n\ntemplate <typename StringMatcherType>\nclass ExceptionMessageMatchesMatcher final\n    : public MatcherBase<std::exception> {\n    StringMatcherType m_matcher;\n\npublic:\n    ExceptionMessageMatchesMatcher( StringMatcherType matcher ):\n        m_matcher( CATCH_MOVE( matcher ) ) {}\n\n    bool match( std::exception const& ex ) const override {\n        return m_matcher.match( ex.what() );\n    }\n\n    std::string describe() const override {\n        return \" matches \\\"\" + m_matcher.describe() + '\"';\n    }\n};\n\n//! Creates a matcher that checks whether a message from an std derived\n//! exception matches a provided matcher\ntemplate <typename StringMatcherType>\nExceptionMessageMatchesMatcher<StringMatcherType>\nMessageMatches( StringMatcherType&& matcher ) {\n    return { CATCH_FORWARD( matcher ) };\n}\n\n} // namespace Matchers\n} // namespace Catch\n\n#endif // CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED\n\n\n#ifndef CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED\n#define CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED\n\n\nnamespace Catch {\nnamespace Matchers {\n\n    namespace Detail {\n        enum class FloatingPointKind : uint8_t;\n    }\n\n    class  WithinAbsMatcher final : public MatcherBase<double> {\n    public:\n        WithinAbsMatcher(double target, double margin);\n        bool match(double const& matchee) const override;\n        std::string describe() const override;\n    private:\n        double m_target;\n        double m_margin;\n    };\n\n    //! Creates a matcher that accepts numbers within certain range of target\n    WithinAbsMatcher WithinAbs( double target, double margin );\n\n\n\n    class WithinUlpsMatcher final : public MatcherBase<double> {\n    public:\n        WithinUlpsMatcher( double target,\n                           uint64_t ulps,\n                           Detail::FloatingPointKind baseType );\n        bool match(double const& matchee) const override;\n        std::string describe() const override;\n    private:\n        double m_target;\n        uint64_t m_ulps;\n        Detail::FloatingPointKind m_type;\n    };\n\n    //! Creates a matcher that accepts doubles within certain ULP range of target\n    WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff);\n    //! Creates a matcher that accepts floats within certain ULP range of target\n    WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff);\n\n\n\n    // Given IEEE-754 format for floats and doubles, we can assume\n    // that float -> double promotion is lossless. Given this, we can\n    // assume that if we do the standard relative comparison of\n    // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get\n    // the same result if we do this for floats, as if we do this for\n    // doubles that were promoted from floats.\n    class WithinRelMatcher final : public MatcherBase<double> {\n    public:\n        WithinRelMatcher( double target, double epsilon );\n        bool match(double const& matchee) const override;\n        std::string describe() const override;\n    private:\n        double m_target;\n        double m_epsilon;\n    };\n\n    //! Creates a matcher that accepts doubles within certain relative range of target\n    WithinRelMatcher WithinRel(double target, double eps);\n    //! Creates a matcher that accepts doubles within 100*DBL_EPS relative range of target\n    WithinRelMatcher WithinRel(double target);\n    //! Creates a matcher that accepts doubles within certain relative range of target\n    WithinRelMatcher WithinRel(float target, float eps);\n    //! Creates a matcher that accepts floats within 100*FLT_EPS relative range of target\n    WithinRelMatcher WithinRel(float target);\n\n\n\n    class IsNaNMatcher final : public MatcherBase<double> {\n    public:\n        IsNaNMatcher() = default;\n        bool match( double const& matchee ) const override;\n        std::string describe() const override;\n    };\n\n    IsNaNMatcher IsNaN();\n\n} // namespace Matchers\n} // namespace Catch\n\n#endif // CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED\n\n\n#ifndef CATCH_MATCHERS_PREDICATE_HPP_INCLUDED\n#define CATCH_MATCHERS_PREDICATE_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch {\nnamespace Matchers {\n\nnamespace Detail {\n    std::string finalizeDescription(const std::string& desc);\n} // namespace Detail\n\ntemplate <typename T, typename Predicate>\nclass PredicateMatcher final : public MatcherBase<T> {\n    Predicate m_predicate;\n    std::string m_description;\npublic:\n\n    PredicateMatcher(Predicate&& elem, std::string const& descr)\n        :m_predicate(CATCH_FORWARD(elem)),\n        m_description(Detail::finalizeDescription(descr))\n    {}\n\n    bool match( T const& item ) const override {\n        return m_predicate(item);\n    }\n\n    std::string describe() const override {\n        return m_description;\n    }\n};\n\n    /**\n     * Creates a matcher that calls delegates `match` to the provided predicate.\n     *\n     * The user has to explicitly specify the argument type to the matcher\n     */\n    template<typename T, typename Pred>\n    PredicateMatcher<T, Pred> Predicate(Pred&& predicate, std::string const& description = \"\") {\n        static_assert(is_callable<Pred(T)>::value, \"Predicate not callable with argument T\");\n        static_assert(std::is_same<bool, FunctionReturnType<Pred, T>>::value, \"Predicate does not return bool\");\n        return PredicateMatcher<T, Pred>(CATCH_FORWARD(predicate), description);\n    }\n\n} // namespace Matchers\n} // namespace Catch\n\n#endif // CATCH_MATCHERS_PREDICATE_HPP_INCLUDED\n\n\n#ifndef CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED\n#define CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED\n\n\nnamespace Catch {\n    namespace Matchers {\n        // Matcher for checking that all elements in range matches a given matcher.\n        template <typename Matcher>\n        class AllMatchMatcher final : public MatcherGenericBase {\n            Matcher m_matcher;\n        public:\n            AllMatchMatcher(Matcher matcher):\n                m_matcher(CATCH_MOVE(matcher))\n            {}\n\n            std::string describe() const override {\n                return \"all match \" + m_matcher.describe();\n            }\n\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n                for (auto&& elem : rng) {\n                    if (!m_matcher.match(elem)) {\n                        return false;\n                    }\n                }\n                return true;\n            }\n        };\n\n        // Matcher for checking that no element in range matches a given matcher.\n        template <typename Matcher>\n        class NoneMatchMatcher final : public MatcherGenericBase {\n            Matcher m_matcher;\n        public:\n            NoneMatchMatcher(Matcher matcher):\n                m_matcher(CATCH_MOVE(matcher))\n            {}\n\n            std::string describe() const override {\n                return \"none match \" + m_matcher.describe();\n            }\n\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n                for (auto&& elem : rng) {\n                    if (m_matcher.match(elem)) {\n                        return false;\n                    }\n                }\n                return true;\n            }\n        };\n\n        // Matcher for checking that at least one element in range matches a given matcher.\n        template <typename Matcher>\n        class AnyMatchMatcher final : public MatcherGenericBase {\n            Matcher m_matcher;\n        public:\n            AnyMatchMatcher(Matcher matcher):\n                m_matcher(CATCH_MOVE(matcher))\n            {}\n\n            std::string describe() const override {\n                return \"any match \" + m_matcher.describe();\n            }\n\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n                for (auto&& elem : rng) {\n                    if (m_matcher.match(elem)) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n        };\n\n        // Matcher for checking that all elements in range are true.\n        class AllTrueMatcher final : public MatcherGenericBase {\n        public:\n            std::string describe() const override;\n\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n                for (auto&& elem : rng) {\n                    if (!elem) {\n                        return false;\n                    }\n                }\n                return true;\n            }\n        };\n\n        // Matcher for checking that no element in range is true.\n        class NoneTrueMatcher final : public MatcherGenericBase {\n        public:\n            std::string describe() const override;\n\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n                for (auto&& elem : rng) {\n                    if (elem) {\n                        return false;\n                    }\n                }\n                return true;\n            }\n        };\n\n        // Matcher for checking that any element in range is true.\n        class AnyTrueMatcher final : public MatcherGenericBase {\n        public:\n            std::string describe() const override;\n\n            template <typename RangeLike>\n            bool match(RangeLike&& rng) const {\n                for (auto&& elem : rng) {\n                    if (elem) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n        };\n\n        // Creates a matcher that checks whether all elements in a range match a matcher\n        template <typename Matcher>\n        AllMatchMatcher<Matcher> AllMatch(Matcher&& matcher) {\n            return { CATCH_FORWARD(matcher) };\n        }\n\n        // Creates a matcher that checks whether no element in a range matches a matcher.\n        template <typename Matcher>\n        NoneMatchMatcher<Matcher> NoneMatch(Matcher&& matcher) {\n            return { CATCH_FORWARD(matcher) };\n        }\n\n        // Creates a matcher that checks whether any element in a range matches a matcher.\n        template <typename Matcher>\n        AnyMatchMatcher<Matcher> AnyMatch(Matcher&& matcher) {\n            return { CATCH_FORWARD(matcher) };\n        }\n\n        // Creates a matcher that checks whether all elements in a range are true\n        AllTrueMatcher AllTrue();\n\n        // Creates a matcher that checks whether no element in a range is true\n        NoneTrueMatcher NoneTrue();\n\n        // Creates a matcher that checks whether any element in a range is true\n        AnyTrueMatcher AnyTrue();\n    }\n}\n\n#endif // CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED\n\n\n#ifndef CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED\n#define CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED\n\n\n#include <functional>\n\nnamespace Catch {\n    namespace Matchers {\n\n        /**\n         * Matcher for checking that an element contains the same\n         * elements in the same order\n         */\n        template <typename TargetRangeLike, typename Equality>\n        class RangeEqualsMatcher final : public MatcherGenericBase {\n            TargetRangeLike m_desired;\n            Equality m_predicate;\n\n        public:\n            template <typename TargetRangeLike2, typename Equality2>\n            constexpr\n            RangeEqualsMatcher( TargetRangeLike2&& range,\n                                Equality2&& predicate ):\n                m_desired( CATCH_FORWARD( range ) ),\n                m_predicate( CATCH_FORWARD( predicate ) ) {}\n\n            template <typename RangeLike>\n            constexpr\n            bool match( RangeLike&& rng ) const {\n                auto rng_start = begin( rng );\n                const auto rng_end = end( rng );\n                auto target_start = begin( m_desired );\n                const auto target_end = end( m_desired );\n\n                while (rng_start != rng_end && target_start != target_end) {\n                    if (!m_predicate(*rng_start, *target_start)) {\n                        return false;\n                    }\n                    ++rng_start;\n                    ++target_start;\n                }\n                return rng_start == rng_end && target_start == target_end;\n            }\n\n            std::string describe() const override {\n                return \"elements are \" + Catch::Detail::stringify( m_desired );\n            }\n        };\n\n        /**\n         * Matcher for checking that an element contains the same\n         * elements (but not necessarily in the same order)\n         */\n        template <typename TargetRangeLike, typename Equality>\n        class UnorderedRangeEqualsMatcher final : public MatcherGenericBase {\n            TargetRangeLike m_desired;\n            Equality m_predicate;\n\n        public:\n            template <typename TargetRangeLike2, typename Equality2>\n            constexpr\n            UnorderedRangeEqualsMatcher( TargetRangeLike2&& range,\n                                         Equality2&& predicate ):\n                m_desired( CATCH_FORWARD( range ) ),\n                m_predicate( CATCH_FORWARD( predicate ) ) {}\n\n            template <typename RangeLike>\n            constexpr\n            bool match( RangeLike&& rng ) const {\n                using std::begin;\n                using std::end;\n                return Catch::Detail::is_permutation( begin( m_desired ),\n                                                      end( m_desired ),\n                                                      begin( rng ),\n                                                      end( rng ),\n                                                      m_predicate );\n            }\n\n            std::string describe() const override {\n                return \"unordered elements are \" +\n                       ::Catch::Detail::stringify( m_desired );\n            }\n        };\n\n        /**\n         * Creates a matcher that checks if all elements in a range are equal\n         * to all elements in another range.\n         *\n         * Uses the provided predicate `predicate` to do the comparisons\n         * (defaulting to `std::equal_to`)\n         */\n        template <typename RangeLike,\n                  typename Equality = decltype( std::equal_to<>{} )>\n        constexpr\n        RangeEqualsMatcher<RangeLike, Equality>\n        RangeEquals( RangeLike&& range,\n                     Equality&& predicate = std::equal_to<>{} ) {\n            return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) };\n        }\n\n        /**\n         * Creates a matcher that checks if all elements in a range are equal\n         * to all elements in an initializer list.\n         *\n         * Uses the provided predicate `predicate` to do the comparisons\n         * (defaulting to `std::equal_to`)\n         */\n        template <typename T,\n                  typename Equality = decltype( std::equal_to<>{} )>\n        constexpr\n        RangeEqualsMatcher<std::initializer_list<T>, Equality>\n        RangeEquals( std::initializer_list<T> range,\n                     Equality&& predicate = std::equal_to<>{} ) {\n            return { range, CATCH_FORWARD( predicate ) };\n        }\n\n        /**\n         * Creates a matcher that checks if all elements in a range are equal\n         * to all elements in another range, in some permutation.\n         *\n         * Uses the provided predicate `predicate` to do the comparisons\n         * (defaulting to `std::equal_to`)\n         */\n        template <typename RangeLike,\n                  typename Equality = decltype( std::equal_to<>{} )>\n        constexpr\n        UnorderedRangeEqualsMatcher<RangeLike, Equality>\n        UnorderedRangeEquals( RangeLike&& range,\n                              Equality&& predicate = std::equal_to<>{} ) {\n            return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) };\n        }\n\n        /**\n         * Creates a matcher that checks if all elements in a range are equal\n         * to all elements in an initializer list, in some permutation.\n         *\n         * Uses the provided predicate `predicate` to do the comparisons\n         * (defaulting to `std::equal_to`)\n         */\n        template <typename T,\n                  typename Equality = decltype( std::equal_to<>{} )>\n        constexpr\n        UnorderedRangeEqualsMatcher<std::initializer_list<T>, Equality>\n        UnorderedRangeEquals( std::initializer_list<T> range,\n                              Equality&& predicate = std::equal_to<>{} ) {\n            return { range, CATCH_FORWARD( predicate ) };\n        }\n    } // namespace Matchers\n} // namespace Catch\n\n#endif // CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED\n\n\n#ifndef CATCH_MATCHERS_STRING_HPP_INCLUDED\n#define CATCH_MATCHERS_STRING_HPP_INCLUDED\n\n\n#include <string>\n\nnamespace Catch {\nnamespace Matchers {\n\n    struct CasedString {\n        CasedString( std::string const& str, CaseSensitive caseSensitivity );\n        std::string adjustString( std::string const& str ) const;\n        StringRef caseSensitivitySuffix() const;\n\n        CaseSensitive m_caseSensitivity;\n        std::string m_str;\n    };\n\n    class StringMatcherBase : public MatcherBase<std::string> {\n    protected:\n        CasedString m_comparator;\n        StringRef m_operation;\n\n    public:\n        StringMatcherBase( StringRef operation,\n                           CasedString const& comparator );\n        std::string describe() const override;\n    };\n\n    class StringEqualsMatcher final : public StringMatcherBase {\n    public:\n        StringEqualsMatcher( CasedString const& comparator );\n        bool match( std::string const& source ) const override;\n    };\n    class StringContainsMatcher final : public StringMatcherBase {\n    public:\n        StringContainsMatcher( CasedString const& comparator );\n        bool match( std::string const& source ) const override;\n    };\n    class StartsWithMatcher final : public StringMatcherBase {\n    public:\n        StartsWithMatcher( CasedString const& comparator );\n        bool match( std::string const& source ) const override;\n    };\n    class EndsWithMatcher final : public StringMatcherBase {\n    public:\n        EndsWithMatcher( CasedString const& comparator );\n        bool match( std::string const& source ) const override;\n    };\n\n    class RegexMatcher final : public MatcherBase<std::string> {\n        std::string m_regex;\n        CaseSensitive m_caseSensitivity;\n\n    public:\n        RegexMatcher( std::string regex, CaseSensitive caseSensitivity );\n        bool match( std::string const& matchee ) const override;\n        std::string describe() const override;\n    };\n\n    //! Creates matcher that accepts strings that are exactly equal to `str`\n    StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes );\n    //! Creates matcher that accepts strings that contain `str`\n    StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes );\n    //! Creates matcher that accepts strings that _end_ with `str`\n    EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes );\n    //! Creates matcher that accepts strings that _start_ with `str`\n    StartsWithMatcher StartsWith( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes );\n    //! Creates matcher that accepts strings matching `regex`\n    RegexMatcher Matches( std::string const& regex, CaseSensitive caseSensitivity = CaseSensitive::Yes );\n\n} // namespace Matchers\n} // namespace Catch\n\n#endif // CATCH_MATCHERS_STRING_HPP_INCLUDED\n\n\n#ifndef CATCH_MATCHERS_VECTOR_HPP_INCLUDED\n#define CATCH_MATCHERS_VECTOR_HPP_INCLUDED\n\n\n#include <algorithm>\n\nnamespace Catch {\nnamespace Matchers {\n\n    template<typename T, typename Alloc>\n    class VectorContainsElementMatcher final : public MatcherBase<std::vector<T, Alloc>> {\n        T const& m_comparator;\n\n    public:\n        VectorContainsElementMatcher(T const& comparator):\n            m_comparator(comparator)\n        {}\n\n        bool match(std::vector<T, Alloc> const& v) const override {\n            for (auto const& el : v) {\n                if (el == m_comparator) {\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        std::string describe() const override {\n            return \"Contains: \" + ::Catch::Detail::stringify( m_comparator );\n        }\n    };\n\n    template<typename T, typename AllocComp, typename AllocMatch>\n    class ContainsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> {\n        std::vector<T, AllocComp> const& m_comparator;\n\n    public:\n        ContainsMatcher(std::vector<T, AllocComp> const& comparator):\n            m_comparator( comparator )\n        {}\n\n        bool match(std::vector<T, AllocMatch> const& v) const override {\n            // !TBD: see note in EqualsMatcher\n            if (m_comparator.size() > v.size())\n                return false;\n            for (auto const& comparator : m_comparator) {\n                auto present = false;\n                for (const auto& el : v) {\n                    if (el == comparator) {\n                        present = true;\n                        break;\n                    }\n                }\n                if (!present) {\n                    return false;\n                }\n            }\n            return true;\n        }\n        std::string describe() const override {\n            return \"Contains: \" + ::Catch::Detail::stringify( m_comparator );\n        }\n    };\n\n    template<typename T, typename AllocComp, typename AllocMatch>\n    class EqualsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> {\n        std::vector<T, AllocComp> const& m_comparator;\n\n    public:\n        EqualsMatcher(std::vector<T, AllocComp> const& comparator):\n            m_comparator( comparator )\n        {}\n\n        bool match(std::vector<T, AllocMatch> const& v) const override {\n            // !TBD: This currently works if all elements can be compared using !=\n            // - a more general approach would be via a compare template that defaults\n            // to using !=. but could be specialised for, e.g. std::vector<T> etc\n            // - then just call that directly\n            if ( m_comparator.size() != v.size() ) { return false; }\n            for ( std::size_t i = 0; i < v.size(); ++i ) {\n                if ( !( m_comparator[i] == v[i] ) ) { return false; }\n            }\n            return true;\n        }\n        std::string describe() const override {\n            return \"Equals: \" + ::Catch::Detail::stringify( m_comparator );\n        }\n    };\n\n    template<typename T, typename AllocComp, typename AllocMatch>\n    class ApproxMatcher final : public MatcherBase<std::vector<T, AllocMatch>> {\n        std::vector<T, AllocComp> const& m_comparator;\n        mutable Catch::Approx approx = Catch::Approx::custom();\n\n    public:\n        ApproxMatcher(std::vector<T, AllocComp> const& comparator):\n            m_comparator( comparator )\n        {}\n\n        bool match(std::vector<T, AllocMatch> const& v) const override {\n            if (m_comparator.size() != v.size())\n                return false;\n            for (std::size_t i = 0; i < v.size(); ++i)\n                if (m_comparator[i] != approx(v[i]))\n                    return false;\n            return true;\n        }\n        std::string describe() const override {\n            return \"is approx: \" + ::Catch::Detail::stringify( m_comparator );\n        }\n        template <typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        ApproxMatcher& epsilon( T const& newEpsilon ) {\n            approx.epsilon(static_cast<double>(newEpsilon));\n            return *this;\n        }\n        template <typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        ApproxMatcher& margin( T const& newMargin ) {\n            approx.margin(static_cast<double>(newMargin));\n            return *this;\n        }\n        template <typename = std::enable_if_t<std::is_constructible<double, T>::value>>\n        ApproxMatcher& scale( T const& newScale ) {\n            approx.scale(static_cast<double>(newScale));\n            return *this;\n        }\n    };\n\n    template<typename T, typename AllocComp, typename AllocMatch>\n    class UnorderedEqualsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> {\n        std::vector<T, AllocComp> const& m_target;\n\n    public:\n        UnorderedEqualsMatcher(std::vector<T, AllocComp> const& target):\n            m_target(target)\n        {}\n        bool match(std::vector<T, AllocMatch> const& vec) const override {\n            if (m_target.size() != vec.size()) {\n                return false;\n            }\n            return std::is_permutation(m_target.begin(), m_target.end(), vec.begin());\n        }\n\n        std::string describe() const override {\n            return \"UnorderedEquals: \" + ::Catch::Detail::stringify(m_target);\n        }\n    };\n\n\n    // The following functions create the actual matcher objects.\n    // This allows the types to be inferred\n\n    //! Creates a matcher that matches vectors that contain all elements in `comparator`\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) {\n        return ContainsMatcher<T, AllocComp, AllocMatch>(comparator);\n    }\n\n    //! Creates a matcher that matches vectors that contain `comparator` as an element\n    template<typename T, typename Alloc = std::allocator<T>>\n    VectorContainsElementMatcher<T, Alloc> VectorContains( T const& comparator ) {\n        return VectorContainsElementMatcher<T, Alloc>(comparator);\n    }\n\n    //! Creates a matcher that matches vectors that are exactly equal to `comparator`\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    EqualsMatcher<T, AllocComp, AllocMatch> Equals( std::vector<T, AllocComp> const& comparator ) {\n        return EqualsMatcher<T, AllocComp, AllocMatch>(comparator);\n    }\n\n    //! Creates a matcher that matches vectors that `comparator` as an element\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    ApproxMatcher<T, AllocComp, AllocMatch> Approx( std::vector<T, AllocComp> const& comparator ) {\n        return ApproxMatcher<T, AllocComp, AllocMatch>(comparator);\n    }\n\n    //! Creates a matcher that matches vectors that is equal to `target` modulo permutation\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(std::vector<T, AllocComp> const& target) {\n        return UnorderedEqualsMatcher<T, AllocComp, AllocMatch>(target);\n    }\n\n} // namespace Matchers\n} // namespace Catch\n\n#endif // CATCH_MATCHERS_VECTOR_HPP_INCLUDED\n\n#endif // CATCH_MATCHERS_ALL_HPP_INCLUDED\n\n\n/** \\file\n * This is a convenience header for Catch2's Reporter support. It includes\n * **all** of Catch2 headers related to reporters, including all reporters.\n *\n * Generally the Catch2 users should use specific includes they need,\n * but this header can be used instead for ease-of-experimentation, or\n * just plain convenience, at the cost of (significantly) increased\n * compilation times.\n *\n * When a new header (reporter) is added to either the `reporter` folder,\n * or to the corresponding internal subfolder, it should be added here.\n */\n\n#ifndef CATCH_REPORTERS_ALL_HPP_INCLUDED\n#define CATCH_REPORTERS_ALL_HPP_INCLUDED\n\n\n\n#ifndef CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED\n#define CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED\n\n\n\n#ifndef CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED\n#define CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED\n\n\n\n#ifndef CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED\n#define CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED\n\n\n#include <map>\n#include <string>\n\nnamespace Catch {\n    class ColourImpl;\n\n    /**\n     * This is the base class for all reporters.\n     *\n     * If are writing a reporter, you must derive from this type, or one\n     * of the helper reporter bases that are derived from this type.\n     *\n     * ReporterBase centralizes handling of various common tasks in reporters,\n     * like storing the right stream for the reporters to write to, and\n     * providing the default implementation of the different listing events.\n     */\n    class ReporterBase : public IEventListener {\n    protected:\n        //! The stream wrapper as passed to us by outside code\n        Detail::unique_ptr<IStream> m_wrapped_stream;\n        //! Cached output stream from `m_wrapped_stream` to reduce\n        //! number of indirect calls needed to write output.\n        std::ostream& m_stream;\n        //! Colour implementation this reporter was configured for\n        Detail::unique_ptr<ColourImpl> m_colour;\n        //! The custom reporter options user passed down to the reporter\n        std::map<std::string, std::string> m_customOptions;\n\n    public:\n        ReporterBase( ReporterConfig&& config );\n        ~ReporterBase() override; // = default;\n\n        /**\n         * Provides a simple default listing of reporters.\n         *\n         * Should look roughly like the reporter listing in v2 and earlier\n         * versions of Catch2.\n         */\n        void listReporters(\n            std::vector<ReporterDescription> const& descriptions ) override;\n        /**\n         * Provides a simple default listing of listeners\n         *\n         * Looks similarly to listing of reporters, but with listener type\n         * instead of reporter name.\n         */\n        void listListeners(\n            std::vector<ListenerDescription> const& descriptions ) override;\n        /**\n         * Provides a simple default listing of tests.\n         *\n         * Should look roughly like the test listing in v2 and earlier versions\n         * of Catch2. Especially supports low-verbosity listing that mimics the\n         * old `--list-test-names-only` output.\n         */\n        void listTests( std::vector<TestCaseHandle> const& tests ) override;\n        /**\n         * Provides a simple default listing of tags.\n         *\n         * Should look roughly like the tag listing in v2 and earlier versions\n         * of Catch2.\n         */\n        void listTags( std::vector<TagInfo> const& tags ) override;\n    };\n} // namespace Catch\n\n#endif // CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED\n\n#include <vector>\n\nnamespace Catch {\n\n    class StreamingReporterBase : public ReporterBase {\n    public:\n        // GCC5 compat: we cannot use inherited constructor, because it\n        //              doesn't implement backport of P0136\n        StreamingReporterBase(ReporterConfig&& _config):\n            ReporterBase(CATCH_MOVE(_config))\n        {}\n        ~StreamingReporterBase() override;\n\n        void benchmarkPreparing( StringRef ) override {}\n        void benchmarkStarting( BenchmarkInfo const& ) override {}\n        void benchmarkEnded( BenchmarkStats<> const& ) override {}\n        void benchmarkFailed( StringRef ) override {}\n\n        void fatalErrorEncountered( StringRef /*error*/ ) override {}\n        void noMatchingTestCases( StringRef /*unmatchedSpec*/ ) override {}\n        void reportInvalidTestSpec( StringRef /*invalidArgument*/ ) override {}\n\n        void testRunStarting( TestRunInfo const& _testRunInfo ) override;\n\n        void testCaseStarting(TestCaseInfo const& _testInfo) override  {\n            currentTestCaseInfo = &_testInfo;\n        }\n        void testCasePartialStarting( TestCaseInfo const&, uint64_t ) override {}\n        void sectionStarting(SectionInfo const& _sectionInfo) override {\n            m_sectionStack.push_back(_sectionInfo);\n        }\n\n        void assertionStarting( AssertionInfo const& ) override {}\n        void assertionEnded( AssertionStats const& ) override {}\n\n        void sectionEnded(SectionStats const& /* _sectionStats */) override {\n            m_sectionStack.pop_back();\n        }\n        void testCasePartialEnded( TestCaseStats const&, uint64_t ) override {}\n        void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override {\n            currentTestCaseInfo = nullptr;\n        }\n        void testRunEnded( TestRunStats const& /* _testRunStats */ ) override;\n\n        void skipTest(TestCaseInfo const&) override {\n            // Don't do anything with this by default.\n            // It can optionally be overridden in the derived class.\n        }\n\n    protected:\n        TestRunInfo currentTestRunInfo{ \"test run has not started yet\"_sr };\n        TestCaseInfo const* currentTestCaseInfo = nullptr;\n\n        //! Stack of all _active_ sections in the _current_ test case\n        std::vector<SectionInfo> m_sectionStack;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED\n\n#include <string>\n\nnamespace Catch {\n\n    class AutomakeReporter final : public StreamingReporterBase {\n    public:\n        // GCC5 compat: we cannot use inherited constructor, because it\n        //              doesn't implement backport of P0136\n        AutomakeReporter( ReporterConfig&& _config ):\n            StreamingReporterBase( CATCH_MOVE( _config ) ) {\n            m_preferences.shouldReportAllAssertionStarts = false;\n        }\n\n        ~AutomakeReporter() override;\n\n        static std::string getDescription() {\n            using namespace std::string_literals;\n            return \"Reports test results in the format of Automake .trs files\"s;\n        }\n\n        void testCaseEnded(TestCaseStats const& _testCaseStats) override;\n        void skipTest(TestCaseInfo const& testInfo) override;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_COMPACT_HPP_INCLUDED\n#define CATCH_REPORTER_COMPACT_HPP_INCLUDED\n\n\n\n\nnamespace Catch {\n\n    class CompactReporter final : public StreamingReporterBase {\n    public:\n        CompactReporter( ReporterConfig&& _config ):\n            StreamingReporterBase( CATCH_MOVE( _config ) ) {\n            m_preferences.shouldReportAllAssertionStarts = false;\n        }\n\n        ~CompactReporter() override;\n\n        static std::string getDescription();\n\n        void noMatchingTestCases( StringRef unmatchedSpec ) override;\n\n        void testRunStarting( TestRunInfo const& _testInfo ) override;\n\n        void assertionEnded(AssertionStats const& _assertionStats) override;\n\n        void sectionEnded(SectionStats const& _sectionStats) override;\n\n        void testRunEnded(TestRunStats const& _testRunStats) override;\n\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_COMPACT_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_CONSOLE_HPP_INCLUDED\n#define CATCH_REPORTER_CONSOLE_HPP_INCLUDED\n\n\nnamespace Catch {\n    // Fwd decls\n    class TablePrinter;\n\n    class ConsoleReporter final : public StreamingReporterBase {\n        Detail::unique_ptr<TablePrinter> m_tablePrinter;\n\n    public:\n        ConsoleReporter(ReporterConfig&& config);\n        ~ConsoleReporter() override;\n        static std::string getDescription();\n\n        void noMatchingTestCases( StringRef unmatchedSpec ) override;\n        void reportInvalidTestSpec( StringRef arg ) override;\n\n        void assertionEnded(AssertionStats const& _assertionStats) override;\n\n        void sectionStarting(SectionInfo const& _sectionInfo) override;\n        void sectionEnded(SectionStats const& _sectionStats) override;\n\n        void benchmarkPreparing( StringRef name ) override;\n        void benchmarkStarting(BenchmarkInfo const& info) override;\n        void benchmarkEnded(BenchmarkStats<> const& stats) override;\n        void benchmarkFailed( StringRef error ) override;\n\n        void testCaseEnded(TestCaseStats const& _testCaseStats) override;\n        void testRunEnded(TestRunStats const& _testRunStats) override;\n        void testRunStarting(TestRunInfo const& _testRunInfo) override;\n\n    private:\n        void lazyPrint();\n\n        void lazyPrintWithoutClosingBenchmarkTable();\n        void lazyPrintRunInfo();\n        void printTestCaseAndSectionHeader();\n\n        void printClosedHeader(std::string const& _name);\n        void printOpenHeader(std::string const& _name);\n\n        // if string has a : in first line will set indent to follow it on\n        // subsequent lines\n        void printHeaderString(std::string const& _string, std::size_t indent = 0);\n\n        void printTotalsDivider(Totals const& totals);\n\n        bool m_headerPrinted = false;\n        bool m_testRunInfoPrinted = false;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_CONSOLE_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED\n#define CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED\n\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    namespace Detail {\n\n        //! Represents either an assertion or a benchmark result to be handled by cumulative reporter later\n        class AssertionOrBenchmarkResult {\n            // This should really be a variant, but this is much faster\n            // to write and the data layout here is already terrible\n            // enough that we do not have to care about the object size.\n            Optional<AssertionStats> m_assertion;\n            Optional<BenchmarkStats<>> m_benchmark;\n        public:\n            AssertionOrBenchmarkResult(AssertionStats const& assertion);\n            AssertionOrBenchmarkResult(BenchmarkStats<> const& benchmark);\n\n            bool isAssertion() const;\n            bool isBenchmark() const;\n\n            AssertionStats const& asAssertion() const;\n            BenchmarkStats<> const& asBenchmark() const;\n        };\n    }\n\n    /**\n     * Utility base for reporters that need to handle all results at once\n     *\n     * It stores tree of all test cases, sections and assertions, and after the\n     * test run is finished, calls into `testRunEndedCumulative` to pass the\n     * control to the deriving class.\n     *\n     * If you are deriving from this class and override any testing related\n     * member functions, you should first call into the base's implementation to\n     * avoid breaking the tree construction.\n     *\n     * Due to the way this base functions, it has to expand assertions up-front,\n     * even if they are later unused (e.g. because the deriving reporter does\n     * not report successful assertions, or because the deriving reporter does\n     * not use assertion expansion at all). Derived classes can use two\n     * customization points, `m_shouldStoreSuccesfulAssertions` and\n     * `m_shouldStoreFailedAssertions`, to disable the expansion and gain extra\n     * performance. **Accessing the assertion expansions if it wasn't stored is\n     * UB.**\n     */\n    class CumulativeReporterBase : public ReporterBase {\n    public:\n        template<typename T, typename ChildNodeT>\n        struct Node {\n            explicit Node( T const& _value ) : value( _value ) {}\n\n            using ChildNodes = std::vector<Detail::unique_ptr<ChildNodeT>>;\n            T value;\n            ChildNodes children;\n        };\n        struct SectionNode {\n            explicit SectionNode(SectionStats const& _stats) : stats(_stats) {}\n\n            bool operator == (SectionNode const& other) const {\n                return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;\n            }\n\n            bool hasAnyAssertions() const;\n\n            SectionStats stats;\n            std::vector<Detail::unique_ptr<SectionNode>> childSections;\n            std::vector<Detail::AssertionOrBenchmarkResult> assertionsAndBenchmarks;\n            std::string stdOut;\n            std::string stdErr;\n        };\n\n\n        using TestCaseNode = Node<TestCaseStats, SectionNode>;\n        using TestRunNode = Node<TestRunStats, TestCaseNode>;\n\n        // GCC5 compat: we cannot use inherited constructor, because it\n        //              doesn't implement backport of P0136\n        CumulativeReporterBase(ReporterConfig&& _config):\n            ReporterBase(CATCH_MOVE(_config))\n        {}\n        ~CumulativeReporterBase() override;\n\n        void benchmarkPreparing( StringRef ) override {}\n        void benchmarkStarting( BenchmarkInfo const& ) override {}\n        void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override;\n        void benchmarkFailed( StringRef ) override {}\n\n        void noMatchingTestCases( StringRef ) override {}\n        void reportInvalidTestSpec( StringRef ) override {}\n        void fatalErrorEncountered( StringRef /*error*/ ) override {}\n\n        void testRunStarting( TestRunInfo const& ) override {}\n\n        void testCaseStarting( TestCaseInfo const& ) override {}\n        void testCasePartialStarting( TestCaseInfo const&, uint64_t ) override {}\n        void sectionStarting( SectionInfo const& sectionInfo ) override;\n\n        void assertionStarting( AssertionInfo const& ) override {}\n\n        void assertionEnded( AssertionStats const& assertionStats ) override;\n        void sectionEnded( SectionStats const& sectionStats ) override;\n        void testCasePartialEnded( TestCaseStats const&, uint64_t ) override {}\n        void testCaseEnded( TestCaseStats const& testCaseStats ) override;\n        void testRunEnded( TestRunStats const& testRunStats ) override;\n        //! Customization point: called after last test finishes (testRunEnded has been handled)\n        virtual void testRunEndedCumulative() = 0;\n\n        void skipTest(TestCaseInfo const&) override {}\n\n    protected:\n        //! Should the cumulative base store the assertion expansion for successful assertions?\n        bool m_shouldStoreSuccesfulAssertions = true;\n        //! Should the cumulative base store the assertion expansion for failed assertions?\n        bool m_shouldStoreFailedAssertions = true;\n\n        // We need lazy construction here. We should probably refactor it\n        // later, after the events are redone.\n        //! The root node of the test run tree.\n        Detail::unique_ptr<TestRunNode> m_testRun;\n\n    private:\n        // Note: We rely on pointer identity being stable, which is why\n        //       we store pointers to the nodes rather than the values.\n        std::vector<Detail::unique_ptr<TestCaseNode>> m_testCases;\n        // Root section of the _current_ test case\n        Detail::unique_ptr<SectionNode> m_rootSection;\n        // Deepest section of the _current_ test case\n        SectionNode* m_deepestSection = nullptr;\n        // Stack of _active_ sections in the _current_ test case\n        std::vector<SectionNode*> m_sectionStack;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_EVENT_LISTENER_HPP_INCLUDED\n#define CATCH_REPORTER_EVENT_LISTENER_HPP_INCLUDED\n\n\nnamespace Catch {\n\n    /**\n     * Base class to simplify implementing listeners.\n     *\n     * Provides empty default implementation for all IEventListener member\n     * functions, so that a listener implementation can pick which\n     * member functions it actually cares about.\n     */\n    class EventListenerBase : public IEventListener {\n    public:\n        using IEventListener::IEventListener;\n\n        void reportInvalidTestSpec( StringRef unmatchedSpec ) override;\n        void fatalErrorEncountered( StringRef error ) override;\n\n        void benchmarkPreparing( StringRef name ) override;\n        void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override;\n        void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override;\n        void benchmarkFailed( StringRef error ) override;\n\n        void assertionStarting( AssertionInfo const& assertionInfo ) override;\n        void assertionEnded( AssertionStats const& assertionStats ) override;\n\n        void listReporters(\n            std::vector<ReporterDescription> const& descriptions ) override;\n        void listListeners(\n            std::vector<ListenerDescription> const& descriptions ) override;\n        void listTests( std::vector<TestCaseHandle> const& tests ) override;\n        void listTags( std::vector<TagInfo> const& tagInfos ) override;\n\n        void noMatchingTestCases( StringRef unmatchedSpec ) override;\n        void testRunStarting( TestRunInfo const& testRunInfo ) override;\n        void testCaseStarting( TestCaseInfo const& testInfo ) override;\n        void testCasePartialStarting( TestCaseInfo const& testInfo,\n                                      uint64_t partNumber ) override;\n        void sectionStarting( SectionInfo const& sectionInfo ) override;\n        void sectionEnded( SectionStats const& sectionStats ) override;\n        void testCasePartialEnded( TestCaseStats const& testCaseStats,\n                                   uint64_t partNumber ) override;\n        void testCaseEnded( TestCaseStats const& testCaseStats ) override;\n        void testRunEnded( TestRunStats const& testRunStats ) override;\n        void skipTest( TestCaseInfo const& testInfo ) override;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_EVENT_LISTENER_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_HELPERS_HPP_INCLUDED\n#define CATCH_REPORTER_HELPERS_HPP_INCLUDED\n\n#include <iosfwd>\n#include <string>\n#include <vector>\n\n\nnamespace Catch {\n\n    class IConfig;\n    class TestCaseHandle;\n    class ColourImpl;\n\n    // Returns double formatted as %.3f (format expected on output)\n    std::string getFormattedDuration( double duration );\n\n    //! Should the reporter show duration of test given current configuration?\n    bool shouldShowDuration( IConfig const& config, double duration );\n\n    std::string serializeFilters( std::vector<std::string> const& filters );\n\n    struct lineOfChars {\n        char c;\n        constexpr lineOfChars( char c_ ): c( c_ ) {}\n\n        friend std::ostream& operator<<( std::ostream& out, lineOfChars value );\n    };\n\n    /**\n     * Lists reporter descriptions to the provided stream in user-friendly\n     * format\n     *\n     * Used as the default listing implementation by the first party reporter\n     * bases. The output should be backwards compatible with the output of\n     * Catch2 v2 binaries.\n     */\n    void\n    defaultListReporters( std::ostream& out,\n                          std::vector<ReporterDescription> const& descriptions,\n                          Verbosity verbosity );\n\n    /**\n     * Lists listeners descriptions to the provided stream in user-friendly\n     * format\n     */\n    void defaultListListeners( std::ostream& out,\n                               std::vector<ListenerDescription> const& descriptions );\n\n    /**\n     * Lists tag information to the provided stream in user-friendly format\n     *\n     * Used as the default listing implementation by the first party reporter\n     * bases. The output should be backwards compatible with the output of\n     * Catch2 v2 binaries.\n     */\n    void defaultListTags( std::ostream& out, std::vector<TagInfo> const& tags, bool isFiltered );\n\n    /**\n     * Lists test case information to the provided stream in user-friendly\n     * format\n     *\n     * Used as the default listing implementation by the first party reporter\n     * bases. The output is backwards compatible with the output of Catch2\n     * v2 binaries, and also supports the format specific to the old\n     * `--list-test-names-only` option, for people who used it in integrations.\n     */\n    void defaultListTests( std::ostream& out,\n                           ColourImpl* streamColour,\n                           std::vector<TestCaseHandle> const& tests,\n                           bool isFiltered,\n                           Verbosity verbosity );\n\n    /**\n     * Prints test run totals to the provided stream in user-friendly format\n     *\n     * Used by the console and compact reporters.\n     */\n    void printTestRunTotals( std::ostream& stream,\n                      ColourImpl& streamColour,\n                      Totals const& totals );\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_HELPERS_HPP_INCLUDED\n\n\n\n#ifndef CATCH_REPORTER_JSON_HPP_INCLUDED\n#define CATCH_REPORTER_JSON_HPP_INCLUDED\n\n\n#include <stack>\n\nnamespace Catch {\n    class JsonReporter : public StreamingReporterBase {\n    public:\n        JsonReporter( ReporterConfig&& config );\n\n        ~JsonReporter() override;\n\n        static std::string getDescription();\n\n    public: // StreamingReporterBase\n        void testRunStarting( TestRunInfo const& runInfo ) override;\n        void testRunEnded( TestRunStats const& runStats ) override;\n\n        void testCaseStarting( TestCaseInfo const& tcInfo ) override;\n        void testCaseEnded( TestCaseStats const& tcStats ) override;\n\n        void testCasePartialStarting( TestCaseInfo const& tcInfo,\n                                      uint64_t index ) override;\n        void testCasePartialEnded( TestCaseStats const& tcStats,\n                                   uint64_t index ) override;\n\n        void sectionStarting( SectionInfo const& sectionInfo ) override;\n        void sectionEnded( SectionStats const& sectionStats ) override;\n\n        void assertionEnded( AssertionStats const& assertionStats ) override;\n\n        //void testRunEndedCumulative() override;\n\n        void benchmarkPreparing( StringRef name ) override;\n        void benchmarkStarting( BenchmarkInfo const& ) override;\n        void benchmarkEnded( BenchmarkStats<> const& ) override;\n        void benchmarkFailed( StringRef error ) override;\n\n        void listReporters(\n            std::vector<ReporterDescription> const& descriptions ) override;\n        void listListeners(\n            std::vector<ListenerDescription> const& descriptions ) override;\n        void listTests( std::vector<TestCaseHandle> const& tests ) override;\n        void listTags( std::vector<TagInfo> const& tags ) override;\n\n    private:\n        Timer m_testCaseTimer;\n        enum class Writer {\n            Object,\n            Array\n        };\n\n        JsonArrayWriter& startArray();\n        JsonArrayWriter& startArray( StringRef key );\n\n        JsonObjectWriter& startObject();\n        JsonObjectWriter& startObject( StringRef key );\n\n        void endObject();\n        void endArray();\n\n        bool isInside( Writer writer );\n\n        void startListing();\n        void endListing();\n\n        // Invariant:\n        // When m_writers is not empty and its top element is\n        // - Writer::Object, then m_objectWriters is not be empty\n        // - Writer::Array,  then m_arrayWriters shall not be empty\n        std::stack<JsonObjectWriter> m_objectWriters{};\n        std::stack<JsonArrayWriter> m_arrayWriters{};\n        std::stack<Writer> m_writers{};\n\n        bool m_startedListing = false;\n\n        // std::size_t m_sectionDepth = 0;\n        // std::size_t m_sectionStarted = 0;\n    };\n} // namespace Catch\n\n#endif // CATCH_REPORTER_JSON_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_JUNIT_HPP_INCLUDED\n#define CATCH_REPORTER_JUNIT_HPP_INCLUDED\n\n\n\nnamespace Catch {\n\n    class JunitReporter final : public CumulativeReporterBase {\n    public:\n        JunitReporter(ReporterConfig&& _config);\n\n        static std::string getDescription();\n\n        void testRunStarting(TestRunInfo const& runInfo) override;\n\n        void testCaseStarting(TestCaseInfo const& testCaseInfo) override;\n        void assertionEnded(AssertionStats const& assertionStats) override;\n\n        void testCaseEnded(TestCaseStats const& testCaseStats) override;\n\n        void testRunEndedCumulative() override;\n\n    private:\n        void writeRun(TestRunNode const& testRunNode, double suiteTime);\n\n        void writeTestCase(TestCaseNode const& testCaseNode);\n\n        void writeSection( std::string const& className,\n                           std::string const& rootName,\n                           SectionNode const& sectionNode,\n                           bool testOkToFail );\n\n        void writeAssertions(SectionNode const& sectionNode);\n        void writeAssertion(AssertionStats const& stats);\n\n        XmlWriter xml;\n        Timer suiteTimer;\n        std::string stdOutForSuite;\n        std::string stdErrForSuite;\n        unsigned int unexpectedExceptions = 0;\n        bool m_okToFail = false;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_JUNIT_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_MULTI_HPP_INCLUDED\n#define CATCH_REPORTER_MULTI_HPP_INCLUDED\n\n\nnamespace Catch {\n\n    class MultiReporter final : public IEventListener {\n        /*\n         * Stores all added reporters and listeners\n         *\n         * All Listeners are stored before all reporters, and individual\n         * listeners/reporters are stored in order of insertion.\n         */\n        std::vector<IEventListenerPtr> m_reporterLikes;\n        bool m_haveNoncapturingReporters = false;\n\n        // Keep track of how many listeners we have already inserted,\n        // so that we can insert them into the main vector at the right place\n        size_t m_insertedListeners = 0;\n\n        void updatePreferences(IEventListener const& reporterish);\n\n    public:\n        MultiReporter( IConfig const* config ):\n            IEventListener( config ) {\n            m_preferences.shouldReportAllAssertionStarts = false;\n        }\n\n        using IEventListener::IEventListener;\n\n        void addListener( IEventListenerPtr&& listener );\n        void addReporter( IEventListenerPtr&& reporter );\n\n    public: // IEventListener\n\n        void noMatchingTestCases( StringRef unmatchedSpec ) override;\n        void fatalErrorEncountered( StringRef error ) override;\n        void reportInvalidTestSpec( StringRef arg ) override;\n\n        void benchmarkPreparing( StringRef name ) override;\n        void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override;\n        void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override;\n        void benchmarkFailed( StringRef error ) override;\n\n        void testRunStarting( TestRunInfo const& testRunInfo ) override;\n        void testCaseStarting( TestCaseInfo const& testInfo ) override;\n        void testCasePartialStarting(TestCaseInfo const& testInfo, uint64_t partNumber) override;\n        void sectionStarting( SectionInfo const& sectionInfo ) override;\n        void assertionStarting( AssertionInfo const& assertionInfo ) override;\n\n        void assertionEnded( AssertionStats const& assertionStats ) override;\n        void sectionEnded( SectionStats const& sectionStats ) override;\n        void testCasePartialEnded(TestCaseStats const& testStats, uint64_t partNumber) override;\n        void testCaseEnded( TestCaseStats const& testCaseStats ) override;\n        void testRunEnded( TestRunStats const& testRunStats ) override;\n\n        void skipTest( TestCaseInfo const& testInfo ) override;\n\n        void listReporters(std::vector<ReporterDescription> const& descriptions) override;\n        void listListeners(std::vector<ListenerDescription> const& descriptions) override;\n        void listTests(std::vector<TestCaseHandle> const& tests) override;\n        void listTags(std::vector<TagInfo> const& tags) override;\n\n\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_MULTI_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_REGISTRARS_HPP_INCLUDED\n#define CATCH_REPORTER_REGISTRARS_HPP_INCLUDED\n\n\n#include <type_traits>\n\nnamespace Catch {\n\n    namespace Detail {\n\n        template <typename T, typename = void>\n        struct has_description : std::false_type {};\n\n        template <typename T>\n        struct has_description<\n            T,\n            void_t<decltype( T::getDescription() )>>\n            : std::true_type {};\n\n        //! Indirection for reporter registration, so that the error handling is\n        //! independent on the reporter's concrete type\n        void registerReporterImpl( std::string const& name,\n                                   IReporterFactoryPtr reporterPtr );\n        //! Actually registers the factory, independent on listener's concrete type\n        void registerListenerImpl( Detail::unique_ptr<EventListenerFactory> listenerFactory );\n    } // namespace Detail\n\n    class IEventListener;\n    using IEventListenerPtr = Detail::unique_ptr<IEventListener>;\n\n    template <typename T>\n    class ReporterFactory : public IReporterFactory {\n\n        IEventListenerPtr create( ReporterConfig&& config ) const override {\n            return Detail::make_unique<T>( CATCH_MOVE(config) );\n        }\n\n        std::string getDescription() const override {\n            return T::getDescription();\n        }\n    };\n\n\n    template<typename T>\n    class ReporterRegistrar {\n    public:\n        explicit ReporterRegistrar( std::string const& name ) {\n            registerReporterImpl( name,\n                                  Detail::make_unique<ReporterFactory<T>>() );\n        }\n    };\n\n    template<typename T>\n    class ListenerRegistrar {\n\n        class TypedListenerFactory : public EventListenerFactory {\n            StringRef m_listenerName;\n\n            std::string getDescriptionImpl( std::true_type ) const {\n                return T::getDescription();\n            }\n\n            std::string getDescriptionImpl( std::false_type ) const {\n                return \"(No description provided)\";\n            }\n\n        public:\n            TypedListenerFactory( StringRef listenerName ):\n                m_listenerName( listenerName ) {}\n\n            IEventListenerPtr create( IConfig const* config ) const override {\n                return Detail::make_unique<T>( config );\n            }\n\n            StringRef getName() const override {\n                return m_listenerName;\n            }\n\n            std::string getDescription() const override {\n                return getDescriptionImpl( Detail::has_description<T>{} );\n            }\n        };\n\n    public:\n        ListenerRegistrar(StringRef listenerName) {\n            registerListenerImpl( Detail::make_unique<TypedListenerFactory>(listenerName) );\n        }\n    };\n}\n\n#if !defined(CATCH_CONFIG_DISABLE)\n\n#    define CATCH_REGISTER_REPORTER( name, reporterType )                  \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                          \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                           \\\n        namespace {                                                        \\\n            const Catch::ReporterRegistrar<reporterType>                   \\\n                INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \\\n                    name );                                                \\\n        }                                                                  \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#    define CATCH_REGISTER_LISTENER( listenerType )                        \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                          \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                           \\\n        namespace {                                                        \\\n            const Catch::ListenerRegistrar<listenerType>                   \\\n                INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \\\n                    #listenerType##_catch_sr );                            \\\n        }                                                                  \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#else // CATCH_CONFIG_DISABLE\n\n#define CATCH_REGISTER_REPORTER(name, reporterType)\n#define CATCH_REGISTER_LISTENER(listenerType)\n\n#endif // CATCH_CONFIG_DISABLE\n\n#endif // CATCH_REPORTER_REGISTRARS_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_SONARQUBE_HPP_INCLUDED\n#define CATCH_REPORTER_SONARQUBE_HPP_INCLUDED\n\n\n\nnamespace Catch {\n\n    class SonarQubeReporter final : public CumulativeReporterBase {\n    public:\n        SonarQubeReporter(ReporterConfig&& config)\n        : CumulativeReporterBase(CATCH_MOVE(config))\n        , xml(m_stream) {\n            m_preferences.shouldRedirectStdOut = true;\n            m_preferences.shouldReportAllAssertions = false;\n            m_preferences.shouldReportAllAssertionStarts = false;\n            m_shouldStoreSuccesfulAssertions = false;\n        }\n\n        static std::string getDescription() {\n            using namespace std::string_literals;\n            return \"Reports test results in the Generic Test Data SonarQube XML format\"s;\n        }\n\n        void testRunStarting( TestRunInfo const& testRunInfo ) override;\n\n        void testRunEndedCumulative() override {\n            writeRun( *m_testRun );\n            xml.endElement();\n        }\n\n        void writeRun( TestRunNode const& runNode );\n\n        void writeTestFile(StringRef filename, std::vector<TestCaseNode const*> const& testCaseNodes);\n\n        void writeTestCase(TestCaseNode const& testCaseNode);\n\n        void writeSection(std::string const& rootName, SectionNode const& sectionNode, bool okToFail);\n\n        void writeAssertions(SectionNode const& sectionNode, bool okToFail);\n\n        void writeAssertion(AssertionStats const& stats, bool okToFail);\n\n    private:\n        XmlWriter xml;\n    };\n\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_SONARQUBE_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_TAP_HPP_INCLUDED\n#define CATCH_REPORTER_TAP_HPP_INCLUDED\n\n\nnamespace Catch {\n\n    class TAPReporter final : public StreamingReporterBase {\n    public:\n        TAPReporter( ReporterConfig&& config ):\n            StreamingReporterBase( CATCH_MOVE(config) ) {\n            m_preferences.shouldReportAllAssertions = true;\n            m_preferences.shouldReportAllAssertionStarts = false;\n        }\n\n        static std::string getDescription() {\n            using namespace std::string_literals;\n            return \"Reports test results in TAP format, suitable for test harnesses\"s;\n        }\n\n        void testRunStarting( TestRunInfo const& testInfo ) override;\n\n        void noMatchingTestCases( StringRef unmatchedSpec ) override;\n\n        void assertionEnded(AssertionStats const& _assertionStats) override;\n\n        void testRunEnded(TestRunStats const& _testRunStats) override;\n\n    private:\n        std::size_t counter = 0;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_TAP_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_TEAMCITY_HPP_INCLUDED\n#define CATCH_REPORTER_TEAMCITY_HPP_INCLUDED\n\n\n#include <cstring>\n\n#ifdef __clang__\n#   pragma clang diagnostic push\n#   pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\nnamespace Catch {\n\n    class TeamCityReporter final : public StreamingReporterBase {\n    public:\n        TeamCityReporter( ReporterConfig&& _config )\n        :   StreamingReporterBase( CATCH_MOVE(_config) )\n        {\n            m_preferences.shouldRedirectStdOut = true;\n            m_preferences.shouldReportAllAssertionStarts = false;\n        }\n\n        ~TeamCityReporter() override;\n\n        static std::string getDescription() {\n            using namespace std::string_literals;\n            return \"Reports test results as TeamCity service messages\"s;\n        }\n\n        void testRunStarting( TestRunInfo const& runInfo ) override;\n        void testRunEnded( TestRunStats const& runStats ) override;\n\n\n        void assertionEnded(AssertionStats const& assertionStats) override;\n\n        void sectionStarting(SectionInfo const& sectionInfo) override {\n            m_headerPrintedForThisSection = false;\n            StreamingReporterBase::sectionStarting( sectionInfo );\n        }\n\n        void testCaseStarting(TestCaseInfo const& testInfo) override;\n\n        void testCaseEnded(TestCaseStats const& testCaseStats) override;\n\n    private:\n        void printSectionHeader(std::ostream& os);\n\n        bool m_headerPrintedForThisSection = false;\n        Timer m_testTimer;\n    };\n\n} // end namespace Catch\n\n#ifdef __clang__\n#   pragma clang diagnostic pop\n#endif\n\n#endif // CATCH_REPORTER_TEAMCITY_HPP_INCLUDED\n\n\n#ifndef CATCH_REPORTER_XML_HPP_INCLUDED\n#define CATCH_REPORTER_XML_HPP_INCLUDED\n\n\n\n\nnamespace Catch {\n    class XmlReporter : public StreamingReporterBase {\n    public:\n        XmlReporter(ReporterConfig&& _config);\n\n        ~XmlReporter() override;\n\n        static std::string getDescription();\n\n        virtual std::string getStylesheetRef() const;\n\n        void writeSourceInfo(SourceLineInfo const& sourceInfo);\n\n    public: // StreamingReporterBase\n\n        void testRunStarting(TestRunInfo const& testInfo) override;\n\n        void testCaseStarting(TestCaseInfo const& testInfo) override;\n\n        void sectionStarting(SectionInfo const& sectionInfo) override;\n\n        void assertionEnded(AssertionStats const& assertionStats) override;\n\n        void sectionEnded(SectionStats const& sectionStats) override;\n\n        void testCaseEnded(TestCaseStats const& testCaseStats) override;\n\n        void testRunEnded(TestRunStats const& testRunStats) override;\n\n        void benchmarkPreparing( StringRef name ) override;\n        void benchmarkStarting(BenchmarkInfo const&) override;\n        void benchmarkEnded(BenchmarkStats<> const&) override;\n        void benchmarkFailed( StringRef error ) override;\n\n        void listReporters(std::vector<ReporterDescription> const& descriptions) override;\n        void listListeners(std::vector<ListenerDescription> const& descriptions) override;\n        void listTests(std::vector<TestCaseHandle> const& tests) override;\n        void listTags(std::vector<TagInfo> const& tags) override;\n\n    private:\n        Timer m_testCaseTimer;\n        XmlWriter m_xml;\n        int m_sectionDepth = 0;\n    };\n\n} // end namespace Catch\n\n#endif // CATCH_REPORTER_XML_HPP_INCLUDED\n\n#endif // CATCH_REPORTERS_ALL_HPP_INCLUDED\n\n#endif // CATCH_ALL_HPP_INCLUDED\n#endif // CATCH_AMALGAMATED_HPP_INCLUDED\n"
  },
  {
    "path": "test/levels/Beech kick 1.lvl",
    "content": "64\r\n0\r\n\"Beech kick\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199836\r\n-200438\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-199904\r\n-200384\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200384\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200384\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200384\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200384\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199424\r\n-200224\r\n-1\r\n55\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200128\r\n-1\r\n119\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200224\r\n-1\r\n45\r\n#TRUE#\r\n1\r\n1\r\n10\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200128\r\n-1\r\n45\r\n#TRUE#\r\n1\r\n1\r\n10\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200224\r\n-1\r\n5\r\n#TRUE#\r\n1\r\n1\r\n10\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200128\r\n-1\r\n5\r\n#TRUE#\r\n1\r\n1\r\n10\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Beech kick 2.lvl",
    "content": "64\r\n0\r\n\"Kick55\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199660\r\n-200470\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200416\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200384\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200288\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200192\r\n32\r\n32\r\n48\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200128\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200064\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199296\r\n-200160\r\n-1\r\n119\r\n#TRUE#\r\n1\r\n1\r\n5\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200352\r\n-1\r\n45\r\n#TRUE#\r\n1\r\n1\r\n1\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200256\r\n-1\r\n45\r\n#TRUE#\r\n1\r\n1\r\n1\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200160\r\n-1\r\n45\r\n#TRUE#\r\n1\r\n1\r\n1\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200352\r\n-1\r\n55\r\n#TRUE#\r\n1\r\n1\r\n5\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200256\r\n-1\r\n55\r\n#TRUE#\r\n1\r\n1\r\n5\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Conveyor hell.lvl",
    "content": "64\r\n0\r\n\"Conveyor hell\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199612\r\n-200310\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200256\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200160\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200128\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200256\r\n32\r\n32\r\n169\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200256\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200160\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200128\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200096\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-200064\r\n-200064\r\n71\r\n\"Default\"\r\n-200032\r\n-200064\r\n71\r\n\"Default\"\r\n-200000\r\n-200064\r\n71\r\n\"Default\"\r\n-199968\r\n-200064\r\n71\r\n\"Default\"\r\n-199936\r\n-200064\r\n71\r\n\"Default\"\r\n-199904\r\n-200064\r\n71\r\n\"Default\"\r\n-199872\r\n-200064\r\n71\r\n\"Default\"\r\n-199840\r\n-200064\r\n71\r\n\"Default\"\r\n-199808\r\n-200064\r\n71\r\n\"Default\"\r\n-199776\r\n-200064\r\n71\r\n\"Default\"\r\n-199744\r\n-200064\r\n71\r\n\"Default\"\r\n-199712\r\n-200064\r\n71\r\n\"Default\"\r\n-199680\r\n-200064\r\n71\r\n\"Default\"\r\n-199648\r\n-200064\r\n71\r\n\"Default\"\r\n-199616\r\n-200064\r\n71\r\n\"Default\"\r\n-199584\r\n-200064\r\n71\r\n\"Default\"\r\n-199552\r\n-200064\r\n71\r\n\"Default\"\r\n-199520\r\n-200064\r\n71\r\n\"Default\"\r\n-199488\r\n-200064\r\n71\r\n\"Default\"\r\n-199456\r\n-200064\r\n71\r\n\"Default\"\r\n-199424\r\n-200064\r\n71\r\n\"Default\"\r\n-199392\r\n-200064\r\n71\r\n\"Default\"\r\n-199360\r\n-200064\r\n71\r\n\"Default\"\r\n-199328\r\n-200064\r\n71\r\n\"Default\"\r\n-199296\r\n-200064\r\n71\r\n\"Default\"\r\n-199264\r\n-200064\r\n71\r\n\"Default\"\r\n-199232\r\n-200080\r\n73\r\n\"Default\"\r\n\"next\"\r\n-199744\r\n-200128\r\n-1\r\n57\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200128\r\n-1\r\n57\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200128\r\n-1\r\n57\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200128\r\n-1\r\n57\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200128\r\n-1\r\n57\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200128\r\n-1\r\n57\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200128\r\n-1\r\n57\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200128\r\n-1\r\n57\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200128\r\n-1\r\n57\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199248\r\n-200112\r\n1\r\n104\r\n#TRUE#\r\n2\r\n1\r\n1\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Exits test.lvl",
    "content": "64\r\n0\r\n\"Exits test\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199900\r\n-200182\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-199968\r\n-200128\r\n128\r\n800\r\n28\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-199520\r\n-200192\r\n35\r\n\"Default\"\r\n-199648\r\n-200192\r\n107\r\n\"Default\"\r\n\"next\"\r\n-199968\r\n-200160\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199248\r\n-200224\r\n-1\r\n197\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"bonus\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200160\r\n-1\r\n16\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200160\r\n-1\r\n41\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200224\r\n-1\r\n11\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"bonus\"\r\n\"\"\r\n\"hide\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200160\r\n-1\r\n31\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-199648\r\n-200160\r\n-199648\r\n-200160\r\n1\r\n1\r\n2\r\n\"\"\r\n0\r\n#FALSE#\r\n#TRUE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"bonus\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"hide\"\r\n\"\"\r\n0\r\n0\r\n\"bonus\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Fall test.lvl",
    "content": "64\r\n0\r\n\"Fall test\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n48\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199852\r\n-200086\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-200000\r\n-200448\r\n55\r\n\"Default\"\r\n-199968\r\n-200448\r\n55\r\n\"Default\"\r\n-199936\r\n-200448\r\n55\r\n\"Default\"\r\n-199904\r\n-200448\r\n55\r\n\"Default\"\r\n-199872\r\n-200448\r\n55\r\n\"Default\"\r\n-199840\r\n-200448\r\n55\r\n\"Default\"\r\n-199808\r\n-200448\r\n55\r\n\"Default\"\r\n-199776\r\n-200448\r\n55\r\n\"Default\"\r\n-199744\r\n-200448\r\n55\r\n\"Default\"\r\n-199712\r\n-200448\r\n55\r\n\"Default\"\r\n-199680\r\n-200448\r\n55\r\n\"Default\"\r\n-199648\r\n-200448\r\n55\r\n\"Default\"\r\n-199616\r\n-200448\r\n55\r\n\"Default\"\r\n-199584\r\n-200448\r\n55\r\n\"Default\"\r\n-199552\r\n-200448\r\n55\r\n\"Default\"\r\n-199520\r\n-200448\r\n55\r\n\"Default\"\r\n-199488\r\n-200448\r\n55\r\n\"Default\"\r\n-199456\r\n-200448\r\n55\r\n\"Default\"\r\n-199424\r\n-200448\r\n55\r\n\"Default\"\r\n-199392\r\n-200448\r\n55\r\n\"Default\"\r\n-199360\r\n-200448\r\n55\r\n\"Default\"\r\n-199328\r\n-200448\r\n55\r\n\"Default\"\r\n-199296\r\n-200448\r\n55\r\n\"Default\"\r\n-199264\r\n-200448\r\n55\r\n\"Default\"\r\n-199232\r\n-200448\r\n55\r\n\"Default\"\r\n-199696\r\n-200176\r\n70\r\n\"Default\"\r\n-199568\r\n-200176\r\n70\r\n\"Default\"\r\n-199440\r\n-200176\r\n70\r\n\"Default\"\r\n-199824\r\n-200176\r\n70\r\n\"Default\"\r\n-199952\r\n-200176\r\n70\r\n\"Default\"\r\n-199312\r\n-200176\r\n70\r\n\"Default\"\r\n-199936\r\n-200512\r\n71\r\n\"Default\"\r\n-199904\r\n-200512\r\n71\r\n\"Default\"\r\n-199872\r\n-200512\r\n71\r\n\"Default\"\r\n-199840\r\n-200512\r\n71\r\n\"Default\"\r\n-199808\r\n-200512\r\n71\r\n\"Default\"\r\n-199776\r\n-200512\r\n71\r\n\"Default\"\r\n-199744\r\n-200512\r\n71\r\n\"Default\"\r\n-199712\r\n-200512\r\n71\r\n\"Default\"\r\n-199680\r\n-200512\r\n71\r\n\"Default\"\r\n-199648\r\n-200512\r\n71\r\n\"Default\"\r\n-199616\r\n-200512\r\n71\r\n\"Default\"\r\n-199584\r\n-200512\r\n71\r\n\"Default\"\r\n-199552\r\n-200512\r\n71\r\n\"Default\"\r\n-199520\r\n-200512\r\n71\r\n\"Default\"\r\n-199488\r\n-200512\r\n71\r\n\"Default\"\r\n-199456\r\n-200512\r\n71\r\n\"Default\"\r\n-199424\r\n-200512\r\n71\r\n\"Default\"\r\n-199392\r\n-200512\r\n71\r\n\"Default\"\r\n-199360\r\n-200512\r\n71\r\n\"Default\"\r\n-199328\r\n-200512\r\n71\r\n\"Default\"\r\n-199296\r\n-200512\r\n71\r\n\"Default\"\r\n-199968\r\n-200512\r\n70\r\n\"Default\"\r\n-199264\r\n-200512\r\n70\r\n\"Default\"\r\n-199232\r\n-200160\r\n70\r\n\"Default\"\r\n-199232\r\n-200416\r\n70\r\n\"Default\"\r\n-199232\r\n-200384\r\n72\r\n\"Default\"\r\n-199232\r\n-200352\r\n72\r\n\"Default\"\r\n-199232\r\n-200320\r\n72\r\n\"Default\"\r\n-199232\r\n-200288\r\n72\r\n\"Default\"\r\n-199232\r\n-200256\r\n72\r\n\"Default\"\r\n-199232\r\n-200224\r\n72\r\n\"Default\"\r\n-199232\r\n-200192\r\n72\r\n\"Default\"\r\n\"next\"\r\n-199904\r\n-200064\r\n-1\r\n182\r\n#TRUE#\r\n1\r\n1\r\n10\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200064\r\n-1\r\n264\r\n#TRUE#\r\n1\r\n1\r\n10\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199728\r\n-200416\r\n1\r\n104\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200052\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199856\r\n-200416\r\n1\r\n62\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199600\r\n-200416\r\n1\r\n66\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199472\r\n-200416\r\n1\r\n60\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199984\r\n-200416\r\n1\r\n64\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199320\r\n-200416\r\n-1\r\n179\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200512\r\n1\r\n104\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199504\r\n-200512\r\n1\r\n104\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200512\r\n1\r\n104\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200352\r\n1\r\n104\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Fence climb Move.lvl",
    "content": "64\r\n0\r\n\"Fence climb Move\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n27\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199852\r\n-200086\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200160\r\n32\r\n32\r\n169\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"HideShow Fence\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200128\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200128\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200128\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200128\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200128\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200128\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-199744\r\n-200512\r\n182\r\n\"fence\"\r\n-199744\r\n-200480\r\n182\r\n\"fence\"\r\n-199744\r\n-200448\r\n182\r\n\"fence\"\r\n-199744\r\n-200416\r\n182\r\n\"fence\"\r\n-199744\r\n-200384\r\n182\r\n\"fence\"\r\n-199744\r\n-200352\r\n182\r\n\"fence\"\r\n-199744\r\n-200320\r\n182\r\n\"fence\"\r\n-199744\r\n-200288\r\n182\r\n\"fence\"\r\n-199744\r\n-200256\r\n182\r\n\"fence\"\r\n-199744\r\n-200224\r\n182\r\n\"fence\"\r\n-199744\r\n-200192\r\n182\r\n\"fence\"\r\n-199744\r\n-200160\r\n182\r\n\"fence\"\r\n-199744\r\n-200128\r\n182\r\n\"fence\"\r\n-199744\r\n-200096\r\n182\r\n\"fence\"\r\n-199744\r\n-200064\r\n182\r\n\"fence\"\r\n-199712\r\n-200512\r\n182\r\n\"fence\"\r\n-199712\r\n-200480\r\n182\r\n\"fence\"\r\n-199712\r\n-200448\r\n182\r\n\"fence\"\r\n-199712\r\n-200416\r\n182\r\n\"fence\"\r\n-199712\r\n-200384\r\n182\r\n\"fence\"\r\n-199712\r\n-200352\r\n182\r\n\"fence\"\r\n-199712\r\n-200320\r\n182\r\n\"fence\"\r\n-199712\r\n-200288\r\n182\r\n\"fence\"\r\n-199712\r\n-200256\r\n182\r\n\"fence\"\r\n-199712\r\n-200224\r\n182\r\n\"fence\"\r\n-199712\r\n-200192\r\n182\r\n\"fence\"\r\n-199712\r\n-200160\r\n182\r\n\"fence\"\r\n-199712\r\n-200128\r\n182\r\n\"fence\"\r\n-199712\r\n-200096\r\n182\r\n\"fence\"\r\n-199712\r\n-200064\r\n182\r\n\"fence\"\r\n-199680\r\n-200512\r\n182\r\n\"fence\"\r\n-199680\r\n-200480\r\n182\r\n\"fence\"\r\n-199680\r\n-200448\r\n182\r\n\"fence\"\r\n-199680\r\n-200416\r\n182\r\n\"fence\"\r\n-199680\r\n-200384\r\n182\r\n\"fence\"\r\n-199680\r\n-200352\r\n182\r\n\"fence\"\r\n-199680\r\n-200320\r\n182\r\n\"fence\"\r\n-199680\r\n-200288\r\n182\r\n\"fence\"\r\n-199680\r\n-200256\r\n182\r\n\"fence\"\r\n-199680\r\n-200224\r\n182\r\n\"fence\"\r\n-199680\r\n-200192\r\n182\r\n\"fence\"\r\n-199680\r\n-200160\r\n182\r\n\"fence\"\r\n-199680\r\n-200128\r\n182\r\n\"fence\"\r\n-199680\r\n-200096\r\n182\r\n\"fence\"\r\n-199680\r\n-200064\r\n182\r\n\"fence\"\r\n-199648\r\n-200512\r\n182\r\n\"fence\"\r\n-199648\r\n-200480\r\n182\r\n\"fence\"\r\n-199648\r\n-200448\r\n182\r\n\"fence\"\r\n-199648\r\n-200416\r\n182\r\n\"fence\"\r\n-199648\r\n-200384\r\n182\r\n\"fence\"\r\n-199648\r\n-200352\r\n182\r\n\"fence\"\r\n-199648\r\n-200320\r\n182\r\n\"fence\"\r\n-199648\r\n-200288\r\n182\r\n\"fence\"\r\n-199648\r\n-200256\r\n182\r\n\"fence\"\r\n-199648\r\n-200224\r\n182\r\n\"fence\"\r\n-199648\r\n-200192\r\n182\r\n\"fence\"\r\n-199648\r\n-200160\r\n182\r\n\"fence\"\r\n-199648\r\n-200128\r\n182\r\n\"fence\"\r\n-199648\r\n-200096\r\n182\r\n\"fence\"\r\n-199648\r\n-200064\r\n182\r\n\"fence\"\r\n-199616\r\n-200512\r\n182\r\n\"fence\"\r\n-199616\r\n-200480\r\n182\r\n\"fence\"\r\n-199616\r\n-200448\r\n182\r\n\"fence\"\r\n-199616\r\n-200416\r\n182\r\n\"fence\"\r\n-199616\r\n-200384\r\n182\r\n\"fence\"\r\n-199616\r\n-200352\r\n182\r\n\"fence\"\r\n-199616\r\n-200320\r\n182\r\n\"fence\"\r\n-199616\r\n-200288\r\n182\r\n\"fence\"\r\n-199616\r\n-200256\r\n182\r\n\"fence\"\r\n-199616\r\n-200224\r\n182\r\n\"fence\"\r\n-199616\r\n-200192\r\n182\r\n\"fence\"\r\n-199616\r\n-200160\r\n182\r\n\"fence\"\r\n-199616\r\n-200128\r\n182\r\n\"fence\"\r\n-199616\r\n-200096\r\n182\r\n\"fence\"\r\n-199616\r\n-200064\r\n182\r\n\"fence\"\r\n-199584\r\n-200512\r\n182\r\n\"fence\"\r\n-199584\r\n-200480\r\n182\r\n\"fence\"\r\n-199584\r\n-200448\r\n182\r\n\"fence\"\r\n-199584\r\n-200416\r\n182\r\n\"fence\"\r\n-199584\r\n-200384\r\n182\r\n\"fence\"\r\n-199584\r\n-200352\r\n182\r\n\"fence\"\r\n-199584\r\n-200320\r\n182\r\n\"fence\"\r\n-199584\r\n-200288\r\n182\r\n\"fence\"\r\n-199584\r\n-200256\r\n182\r\n\"fence\"\r\n-199584\r\n-200224\r\n182\r\n\"fence\"\r\n-199584\r\n-200192\r\n182\r\n\"fence\"\r\n-199584\r\n-200160\r\n182\r\n\"fence\"\r\n-199584\r\n-200128\r\n182\r\n\"fence\"\r\n-199584\r\n-200096\r\n182\r\n\"fence\"\r\n-199584\r\n-200064\r\n182\r\n\"fence\"\r\n-199552\r\n-200512\r\n182\r\n\"fence\"\r\n-199552\r\n-200480\r\n182\r\n\"fence\"\r\n-199552\r\n-200448\r\n182\r\n\"fence\"\r\n-199552\r\n-200416\r\n182\r\n\"fence\"\r\n-199552\r\n-200384\r\n182\r\n\"fence\"\r\n-199552\r\n-200352\r\n182\r\n\"fence\"\r\n-199552\r\n-200320\r\n182\r\n\"fence\"\r\n-199552\r\n-200288\r\n182\r\n\"fence\"\r\n-199552\r\n-200256\r\n182\r\n\"fence\"\r\n-199552\r\n-200224\r\n182\r\n\"fence\"\r\n-199552\r\n-200192\r\n182\r\n\"fence\"\r\n-199552\r\n-200160\r\n182\r\n\"fence\"\r\n-199552\r\n-200128\r\n182\r\n\"fence\"\r\n-199552\r\n-200096\r\n182\r\n\"fence\"\r\n-199552\r\n-200064\r\n182\r\n\"fence\"\r\n-199520\r\n-200512\r\n182\r\n\"fence\"\r\n-199520\r\n-200480\r\n182\r\n\"fence\"\r\n-199520\r\n-200448\r\n182\r\n\"fence\"\r\n-199520\r\n-200416\r\n182\r\n\"fence\"\r\n-199520\r\n-200384\r\n182\r\n\"fence\"\r\n-199520\r\n-200352\r\n182\r\n\"fence\"\r\n-199520\r\n-200320\r\n182\r\n\"fence\"\r\n-199520\r\n-200288\r\n182\r\n\"fence\"\r\n-199520\r\n-200256\r\n182\r\n\"fence\"\r\n-199520\r\n-200224\r\n182\r\n\"fence\"\r\n-199520\r\n-200192\r\n182\r\n\"fence\"\r\n-199520\r\n-200160\r\n182\r\n\"fence\"\r\n-199520\r\n-200128\r\n182\r\n\"fence\"\r\n-199520\r\n-200096\r\n182\r\n\"fence\"\r\n-199520\r\n-200064\r\n182\r\n\"fence\"\r\n-199488\r\n-200512\r\n182\r\n\"fence\"\r\n-199488\r\n-200480\r\n182\r\n\"fence\"\r\n-199488\r\n-200448\r\n182\r\n\"fence\"\r\n-199488\r\n-200416\r\n182\r\n\"fence\"\r\n-199488\r\n-200384\r\n182\r\n\"fence\"\r\n-199488\r\n-200352\r\n182\r\n\"fence\"\r\n-199488\r\n-200320\r\n182\r\n\"fence\"\r\n-199488\r\n-200288\r\n182\r\n\"fence\"\r\n-199488\r\n-200256\r\n182\r\n\"fence\"\r\n-199488\r\n-200224\r\n182\r\n\"fence\"\r\n-199488\r\n-200192\r\n182\r\n\"fence\"\r\n-199488\r\n-200160\r\n182\r\n\"fence\"\r\n-199488\r\n-200128\r\n182\r\n\"fence\"\r\n-199488\r\n-200096\r\n182\r\n\"fence\"\r\n-199488\r\n-200064\r\n182\r\n\"fence\"\r\n-199456\r\n-200512\r\n182\r\n\"fence\"\r\n-199456\r\n-200480\r\n182\r\n\"fence\"\r\n-199456\r\n-200448\r\n182\r\n\"fence\"\r\n-199456\r\n-200416\r\n182\r\n\"fence\"\r\n-199456\r\n-200384\r\n182\r\n\"fence\"\r\n-199456\r\n-200352\r\n182\r\n\"fence\"\r\n-199456\r\n-200320\r\n182\r\n\"fence\"\r\n-199456\r\n-200288\r\n182\r\n\"fence\"\r\n-199456\r\n-200256\r\n182\r\n\"fence\"\r\n-199456\r\n-200224\r\n182\r\n\"fence\"\r\n-199456\r\n-200192\r\n182\r\n\"fence\"\r\n-199456\r\n-200160\r\n182\r\n\"fence\"\r\n-199456\r\n-200128\r\n182\r\n\"fence\"\r\n-199456\r\n-200096\r\n182\r\n\"fence\"\r\n-199456\r\n-200064\r\n182\r\n\"fence\"\r\n-199392\r\n-200224\r\n182\r\n\"I like to move move\"\r\n-199392\r\n-200256\r\n182\r\n\"I like to move move\"\r\n-199392\r\n-200288\r\n182\r\n\"I like to move move\"\r\n-199392\r\n-200320\r\n182\r\n\"I like to move move\"\r\n-199392\r\n-200352\r\n182\r\n\"I like to move move\"\r\n-199360\r\n-200352\r\n182\r\n\"I like to move move\"\r\n-199328\r\n-200352\r\n182\r\n\"I like to move move\"\r\n-199296\r\n-200352\r\n182\r\n\"I like to move move\"\r\n-199296\r\n-200320\r\n182\r\n\"I like to move move\"\r\n-199296\r\n-200288\r\n182\r\n\"I like to move move\"\r\n-199296\r\n-200256\r\n182\r\n\"I like to move move\"\r\n-199296\r\n-200224\r\n182\r\n\"I like to move move\"\r\n-199328\r\n-200224\r\n182\r\n\"I like to move move\"\r\n-199360\r\n-200224\r\n182\r\n\"I like to move move\"\r\n-199360\r\n-200256\r\n182\r\n\"I like to move move\"\r\n-199360\r\n-200288\r\n182\r\n\"I like to move move\"\r\n-199360\r\n-200320\r\n182\r\n\"I like to move move\"\r\n-199328\r\n-200320\r\n182\r\n\"I like to move move\"\r\n-199328\r\n-200288\r\n182\r\n\"I like to move move\"\r\n-199328\r\n-200256\r\n182\r\n\"I like to move move\"\r\n\"next\"\r\n-199256\r\n-200352\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199256\r\n-200320\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199256\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199256\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199256\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199256\r\n-200192\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"fence\"\r\n#FALSE#\r\n\"I like to move move\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"HideShow Fence\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"fence\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"MoveUp\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"StopUp\"\r\n10\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#TRUE#\r\n\"I like to move move\"\r\n0\r\n-0.492308\r\n0\r\n0\r\n0\r\n\"StopUp\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"MoveDown\"\r\n10\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"MoveDown\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"StopDown\"\r\n10\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n0\r\n0.492308\r\n0\r\n0\r\n0\r\n\"StopDown\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"MoveUp\"\r\n10\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"I like to move move\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Fence climb.lvl",
    "content": "64\r\n0\r\n\"Fence climb\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n27\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199852\r\n-200086\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200160\r\n32\r\n32\r\n169\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"HideShow Fence\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-199744\r\n-200512\r\n182\r\n\"fence\"\r\n-199744\r\n-200480\r\n182\r\n\"fence\"\r\n-199744\r\n-200448\r\n182\r\n\"fence\"\r\n-199744\r\n-200416\r\n182\r\n\"fence\"\r\n-199744\r\n-200384\r\n182\r\n\"fence\"\r\n-199744\r\n-200352\r\n182\r\n\"fence\"\r\n-199744\r\n-200320\r\n182\r\n\"fence\"\r\n-199744\r\n-200288\r\n182\r\n\"fence\"\r\n-199744\r\n-200256\r\n182\r\n\"fence\"\r\n-199744\r\n-200224\r\n182\r\n\"fence\"\r\n-199744\r\n-200192\r\n182\r\n\"fence\"\r\n-199744\r\n-200160\r\n182\r\n\"fence\"\r\n-199744\r\n-200128\r\n182\r\n\"fence\"\r\n-199744\r\n-200096\r\n182\r\n\"fence\"\r\n-199744\r\n-200064\r\n182\r\n\"fence\"\r\n-199712\r\n-200512\r\n182\r\n\"fence\"\r\n-199712\r\n-200480\r\n182\r\n\"fence\"\r\n-199712\r\n-200448\r\n182\r\n\"fence\"\r\n-199712\r\n-200416\r\n182\r\n\"fence\"\r\n-199712\r\n-200384\r\n182\r\n\"fence\"\r\n-199712\r\n-200352\r\n182\r\n\"fence\"\r\n-199712\r\n-200320\r\n182\r\n\"fence\"\r\n-199712\r\n-200288\r\n182\r\n\"fence\"\r\n-199712\r\n-200256\r\n182\r\n\"fence\"\r\n-199712\r\n-200224\r\n182\r\n\"fence\"\r\n-199712\r\n-200192\r\n182\r\n\"fence\"\r\n-199712\r\n-200160\r\n182\r\n\"fence\"\r\n-199712\r\n-200128\r\n182\r\n\"fence\"\r\n-199712\r\n-200096\r\n182\r\n\"fence\"\r\n-199712\r\n-200064\r\n182\r\n\"fence\"\r\n-199680\r\n-200512\r\n182\r\n\"fence\"\r\n-199680\r\n-200480\r\n182\r\n\"fence\"\r\n-199680\r\n-200448\r\n182\r\n\"fence\"\r\n-199680\r\n-200416\r\n182\r\n\"fence\"\r\n-199680\r\n-200384\r\n182\r\n\"fence\"\r\n-199680\r\n-200352\r\n182\r\n\"fence\"\r\n-199680\r\n-200320\r\n182\r\n\"fence\"\r\n-199680\r\n-200288\r\n182\r\n\"fence\"\r\n-199680\r\n-200256\r\n182\r\n\"fence\"\r\n-199680\r\n-200224\r\n182\r\n\"fence\"\r\n-199680\r\n-200192\r\n182\r\n\"fence\"\r\n-199680\r\n-200160\r\n182\r\n\"fence\"\r\n-199680\r\n-200128\r\n182\r\n\"fence\"\r\n-199680\r\n-200096\r\n182\r\n\"fence\"\r\n-199680\r\n-200064\r\n182\r\n\"fence\"\r\n-199648\r\n-200512\r\n182\r\n\"fence\"\r\n-199648\r\n-200480\r\n182\r\n\"fence\"\r\n-199648\r\n-200448\r\n182\r\n\"fence\"\r\n-199648\r\n-200416\r\n182\r\n\"fence\"\r\n-199648\r\n-200384\r\n182\r\n\"fence\"\r\n-199648\r\n-200352\r\n182\r\n\"fence\"\r\n-199648\r\n-200320\r\n182\r\n\"fence\"\r\n-199648\r\n-200288\r\n182\r\n\"fence\"\r\n-199648\r\n-200256\r\n182\r\n\"fence\"\r\n-199648\r\n-200224\r\n182\r\n\"fence\"\r\n-199648\r\n-200192\r\n182\r\n\"fence\"\r\n-199648\r\n-200160\r\n182\r\n\"fence\"\r\n-199648\r\n-200128\r\n182\r\n\"fence\"\r\n-199648\r\n-200096\r\n182\r\n\"fence\"\r\n-199648\r\n-200064\r\n182\r\n\"fence\"\r\n-199616\r\n-200512\r\n182\r\n\"fence\"\r\n-199616\r\n-200480\r\n182\r\n\"fence\"\r\n-199616\r\n-200448\r\n182\r\n\"fence\"\r\n-199616\r\n-200416\r\n182\r\n\"fence\"\r\n-199616\r\n-200384\r\n182\r\n\"fence\"\r\n-199616\r\n-200352\r\n182\r\n\"fence\"\r\n-199616\r\n-200320\r\n182\r\n\"fence\"\r\n-199616\r\n-200288\r\n182\r\n\"fence\"\r\n-199616\r\n-200256\r\n182\r\n\"fence\"\r\n-199616\r\n-200224\r\n182\r\n\"fence\"\r\n-199616\r\n-200192\r\n182\r\n\"fence\"\r\n-199616\r\n-200160\r\n182\r\n\"fence\"\r\n-199616\r\n-200128\r\n182\r\n\"fence\"\r\n-199616\r\n-200096\r\n182\r\n\"fence\"\r\n-199616\r\n-200064\r\n182\r\n\"fence\"\r\n-199584\r\n-200512\r\n182\r\n\"fence\"\r\n-199584\r\n-200480\r\n182\r\n\"fence\"\r\n-199584\r\n-200448\r\n182\r\n\"fence\"\r\n-199584\r\n-200416\r\n182\r\n\"fence\"\r\n-199584\r\n-200384\r\n182\r\n\"fence\"\r\n-199584\r\n-200352\r\n182\r\n\"fence\"\r\n-199584\r\n-200320\r\n182\r\n\"fence\"\r\n-199584\r\n-200288\r\n182\r\n\"fence\"\r\n-199584\r\n-200256\r\n182\r\n\"fence\"\r\n-199584\r\n-200224\r\n182\r\n\"fence\"\r\n-199584\r\n-200192\r\n182\r\n\"fence\"\r\n-199584\r\n-200160\r\n182\r\n\"fence\"\r\n-199584\r\n-200128\r\n182\r\n\"fence\"\r\n-199584\r\n-200096\r\n182\r\n\"fence\"\r\n-199584\r\n-200064\r\n182\r\n\"fence\"\r\n-199552\r\n-200512\r\n182\r\n\"fence\"\r\n-199552\r\n-200480\r\n182\r\n\"fence\"\r\n-199552\r\n-200448\r\n182\r\n\"fence\"\r\n-199552\r\n-200416\r\n182\r\n\"fence\"\r\n-199552\r\n-200384\r\n182\r\n\"fence\"\r\n-199552\r\n-200352\r\n182\r\n\"fence\"\r\n-199552\r\n-200320\r\n182\r\n\"fence\"\r\n-199552\r\n-200288\r\n182\r\n\"fence\"\r\n-199552\r\n-200256\r\n182\r\n\"fence\"\r\n-199552\r\n-200224\r\n182\r\n\"fence\"\r\n-199552\r\n-200192\r\n182\r\n\"fence\"\r\n-199552\r\n-200160\r\n182\r\n\"fence\"\r\n-199552\r\n-200128\r\n182\r\n\"fence\"\r\n-199552\r\n-200096\r\n182\r\n\"fence\"\r\n-199552\r\n-200064\r\n182\r\n\"fence\"\r\n-199520\r\n-200512\r\n182\r\n\"fence\"\r\n-199520\r\n-200480\r\n182\r\n\"fence\"\r\n-199520\r\n-200448\r\n182\r\n\"fence\"\r\n-199520\r\n-200416\r\n182\r\n\"fence\"\r\n-199520\r\n-200384\r\n182\r\n\"fence\"\r\n-199520\r\n-200352\r\n182\r\n\"fence\"\r\n-199520\r\n-200320\r\n182\r\n\"fence\"\r\n-199520\r\n-200288\r\n182\r\n\"fence\"\r\n-199520\r\n-200256\r\n182\r\n\"fence\"\r\n-199520\r\n-200224\r\n182\r\n\"fence\"\r\n-199520\r\n-200192\r\n182\r\n\"fence\"\r\n-199520\r\n-200160\r\n182\r\n\"fence\"\r\n-199520\r\n-200128\r\n182\r\n\"fence\"\r\n-199520\r\n-200096\r\n182\r\n\"fence\"\r\n-199520\r\n-200064\r\n182\r\n\"fence\"\r\n-199488\r\n-200512\r\n182\r\n\"fence\"\r\n-199488\r\n-200480\r\n182\r\n\"fence\"\r\n-199488\r\n-200448\r\n182\r\n\"fence\"\r\n-199488\r\n-200416\r\n182\r\n\"fence\"\r\n-199488\r\n-200384\r\n182\r\n\"fence\"\r\n-199488\r\n-200352\r\n182\r\n\"fence\"\r\n-199488\r\n-200320\r\n182\r\n\"fence\"\r\n-199488\r\n-200288\r\n182\r\n\"fence\"\r\n-199488\r\n-200256\r\n182\r\n\"fence\"\r\n-199488\r\n-200224\r\n182\r\n\"fence\"\r\n-199488\r\n-200192\r\n182\r\n\"fence\"\r\n-199488\r\n-200160\r\n182\r\n\"fence\"\r\n-199488\r\n-200128\r\n182\r\n\"fence\"\r\n-199488\r\n-200096\r\n182\r\n\"fence\"\r\n-199488\r\n-200064\r\n182\r\n\"fence\"\r\n-199456\r\n-200512\r\n182\r\n\"fence\"\r\n-199456\r\n-200480\r\n182\r\n\"fence\"\r\n-199456\r\n-200448\r\n182\r\n\"fence\"\r\n-199456\r\n-200416\r\n182\r\n\"fence\"\r\n-199456\r\n-200384\r\n182\r\n\"fence\"\r\n-199456\r\n-200352\r\n182\r\n\"fence\"\r\n-199456\r\n-200320\r\n182\r\n\"fence\"\r\n-199456\r\n-200288\r\n182\r\n\"fence\"\r\n-199456\r\n-200256\r\n182\r\n\"fence\"\r\n-199456\r\n-200224\r\n182\r\n\"fence\"\r\n-199456\r\n-200192\r\n182\r\n\"fence\"\r\n-199456\r\n-200160\r\n182\r\n\"fence\"\r\n-199456\r\n-200128\r\n182\r\n\"fence\"\r\n-199456\r\n-200096\r\n182\r\n\"fence\"\r\n-199456\r\n-200064\r\n182\r\n\"fence\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"fence\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"HideShow Fence\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"fence\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Player clip - min.lvl",
    "content": "64\r\n0\r\n\"Player clip [Minimal]\"\r\n-200000\r\n-201184\r\n-200000\r\n-198496\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199452\r\n-200534\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200384\r\n32\r\n128\r\n321\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200384\r\n32\r\n128\r\n319\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199584\r\n-200512\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"kurwa\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"Up\"\r\n40\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n0\r\n1\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"Up\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"Level - Start\"\r\n40\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n0\r\n-1\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Player clip.lvl",
    "content": "64\r\n0\r\n\"Player clip\"\r\n-200000\r\n-201184\r\n-200000\r\n-198496\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199516\r\n-200534\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200384\r\n32\r\n128\r\n321\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200384\r\n32\r\n128\r\n319\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200416\r\n96\r\n192\r\n28\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200384\r\n64\r\n160\r\n28\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200384\r\n32\r\n128\r\n319\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200480\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200480\r\n32\r\n128\r\n319\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200384\r\n32\r\n128\r\n321\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198784\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198784\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198784\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198720\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198720\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198720\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200352\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198656\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198656\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198624\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198624\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198592\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198592\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198560\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198560\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198528\r\n-200224\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198528\r\n-200192\r\n32\r\n32\r\n54\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199424\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200512\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199614\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198784\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198720\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198656\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198624\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198592\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198560\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198528\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200576\r\n-1\r\n264\r\n#FALSE#\r\n\"\"\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"kurwa\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"Up\"\r\n40\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n0\r\n1\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"Up\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"Level - Start\"\r\n40\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"kurwa\"\r\n0\r\n-1\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Player through block.lvl",
    "content": "64\r\n0\r\n\"Player through block\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199916\r\n-200086\r\n24\r\n54\r\n-199484\r\n-200348\r\n24\r\n60\r\n-200000\r\n-200192\r\n32\r\n32\r\n628\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200192\r\n32\r\n32\r\n628\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200192\r\n32\r\n32\r\n628\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200192\r\n32\r\n32\r\n624\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200192\r\n32\r\n32\r\n622\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200320\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200320\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200320\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200320\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200320\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200320\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200320\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200320\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200416\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200384\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200352\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200320\r\n32\r\n32\r\n626\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200288\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200256\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200224\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200192\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200160\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n52\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199616\r\n-200288\r\n-1\r\n1\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Pockey test.lvl",
    "content": "64\r\n0\r\n\"Pockey test\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199996\r\n-200150\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200032\r\n-200608\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200576\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200544\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200512\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200480\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200448\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200416\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200384\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200352\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200320\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200288\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200256\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200224\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200192\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200160\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200128\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200096\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200124\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200128\r\n32\r\n32\r\n600\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200096\r\n32\r\n32\r\n602\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200160\r\n32\r\n32\r\n600\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200128\r\n32\r\n32\r\n602\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200096\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200192\r\n32\r\n32\r\n600\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200160\r\n32\r\n32\r\n602\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200128\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200096\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200192\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200160\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200128\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200096\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200192\r\n32\r\n32\r\n6\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200160\r\n32\r\n32\r\n17\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200128\r\n32\r\n32\r\n17\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200096\r\n32\r\n32\r\n17\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200064\r\n32\r\n32\r\n17\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n17\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200320\r\n32\r\n32\r\n557\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200288\r\n32\r\n32\r\n561\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200256\r\n32\r\n32\r\n561\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200224\r\n32\r\n32\r\n561\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200192\r\n32\r\n32\r\n561\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200160\r\n32\r\n32\r\n561\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200128\r\n32\r\n32\r\n561\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200096\r\n32\r\n32\r\n561\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200064\r\n32\r\n32\r\n561\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n561\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200192\r\n32\r\n32\r\n7\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200160\r\n32\r\n32\r\n15\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200128\r\n32\r\n32\r\n15\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200096\r\n32\r\n32\r\n15\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200064\r\n32\r\n32\r\n15\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n15\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200192\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200160\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200128\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200096\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200192\r\n32\r\n32\r\n601\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200160\r\n32\r\n32\r\n603\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200128\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200096\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200160\r\n32\r\n32\r\n601\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200128\r\n32\r\n32\r\n603\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200096\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200128\r\n32\r\n32\r\n601\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200096\r\n32\r\n32\r\n603\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200124\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200608\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200576\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200544\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200512\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200480\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200448\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200416\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200384\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200352\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200320\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200288\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200256\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200224\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200192\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200160\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200128\r\n32\r\n32\r\n558\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200096\r\n32\r\n32\r\n3\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200064\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200032\r\n32\r\n32\r\n16\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199392\r\n-200158\r\n-1\r\n247\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200190\r\n-1\r\n247\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200222\r\n-1\r\n247\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200126\r\n-1\r\n247\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200222\r\n-1\r\n247\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200190\r\n-1\r\n247\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200158\r\n-1\r\n247\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200126\r\n-1\r\n247\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Raft ride.lvl",
    "content": "64\r\n0\r\n\"Raft ride\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199996\r\n-200502\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200032\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200448\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200288\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200064\r\n160\r\n224\r\n568\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200160\r\n224\r\n160\r\n568\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200576\r\n32\r\n32\r\n173\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200448\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200256\r\n32\r\n32\r\n179\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200480\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200512\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200608\r\n32\r\n32\r\n174\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200576\r\n32\r\n32\r\n174\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200544\r\n32\r\n32\r\n174\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200512\r\n32\r\n32\r\n174\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200480\r\n32\r\n32\r\n174\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200448\r\n32\r\n32\r\n174\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200224\r\n32\r\n32\r\n180\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200192\r\n32\r\n32\r\n180\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200160\r\n32\r\n32\r\n180\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200512\r\n480\r\n192\r\n568\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200384\r\n32\r\n32\r\n177\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200352\r\n32\r\n32\r\n177\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200320\r\n32\r\n32\r\n177\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200288\r\n32\r\n32\r\n177\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200160\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200288\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200160\r\n32\r\n32\r\n451\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200384\r\n32\r\n32\r\n176\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200288\r\n32\r\n32\r\n451\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200448\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200416\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200288\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199904\r\n-200148\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200148\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200148\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200148\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200148\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200276\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200276\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200276\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200276\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200276\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200436\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200436\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200436\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200436\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200436\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200448\r\n-1\r\n89\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Shelf surf.lvl",
    "content": "64\r\n0\r\n\"Shelf surf\"\r\n-200000\r\n-200600\r\n-200000\r\n-198048\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199564\r\n-200086\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200064\r\n-200288\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200256\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200224\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200192\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200160\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200128\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200096\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200064\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198784\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198720\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198656\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198592\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198528\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198464\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198400\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198336\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198272\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198208\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198144\r\n-200032\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200480\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200448\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200416\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200384\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200352\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200320\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200288\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200256\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200224\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200192\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200160\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200128\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200096\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200064\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199392\r\n-200064\r\n-1\r\n195\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200064\r\n-1\r\n169\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Shell on spring.lvl",
    "content": "64\r\n0\r\n\"Shell on spring\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199772\r\n-200278\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-199840\r\n-200224\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200224\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200224\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200224\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200224\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200224\r\n32\r\n32\r\n229\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200192\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200192\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200192\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200224\r\n32\r\n32\r\n227\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200224\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200224\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200224\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200224\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199712\r\n-200256\r\n-1\r\n113\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200224\r\n-1\r\n26\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Skull raft 2.lvl",
    "content": "64\r\n0\r\n\"Skull raft 2\"\r\n-200000\r\n-200600\r\n-200000\r\n-197888\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199980\r\n-200310\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200064\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200384\r\n32\r\n32\r\n451\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200352\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200320\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200288\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200256\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200064\r\n32\r\n128\r\n319\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200352\r\n32\r\n32\r\n451\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200320\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200288\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200256\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200320\r\n32\r\n32\r\n451\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200288\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200256\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200288\r\n32\r\n32\r\n451\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200256\r\n32\r\n32\r\n415\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200256\r\n32\r\n32\r\n451\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n64\r\n366\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n451\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200000\r\n32\r\n32\r\n453\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200000\r\n32\r\n32\r\n453\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200000\r\n32\r\n32\r\n453\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200000\r\n32\r\n32\r\n453\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200288\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200256\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200000\r\n32\r\n32\r\n453\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200064\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200096\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200128\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200160\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200192\r\n32\r\n32\r\n452\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200224\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200224\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200224\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200224\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200224\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200224\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200224\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200224\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200224\r\n32\r\n64\r\n366\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200192\r\n32\r\n64\r\n366\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200160\r\n32\r\n64\r\n366\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200128\r\n32\r\n32\r\n315\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200096\r\n32\r\n32\r\n315\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200064\r\n32\r\n32\r\n315\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198784\r\n-200064\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198784\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200064\r\n32\r\n32\r\n315\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198720\r\n-200064\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198720\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200064\r\n32\r\n32\r\n315\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198656\r\n-200064\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198656\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198624\r\n-200064\r\n32\r\n32\r\n315\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198624\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198592\r\n-200064\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198592\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198560\r\n-200064\r\n32\r\n32\r\n315\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198560\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198528\r\n-200064\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198528\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198496\r\n-200064\r\n32\r\n32\r\n315\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198496\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198464\r\n-200064\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198464\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198432\r\n-200096\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198432\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198432\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198400\r\n-200128\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198400\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198400\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198400\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198368\r\n-200160\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198368\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198368\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198368\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198368\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198336\r\n-200192\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198336\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198336\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198336\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198336\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198336\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198304\r\n-200224\r\n32\r\n32\r\n316\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198304\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198304\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198304\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198304\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198304\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198304\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198272\r\n-200224\r\n32\r\n64\r\n366\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198272\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198272\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198272\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198272\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198272\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198272\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198240\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198240\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198240\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198240\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198240\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198240\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198208\r\n-200192\r\n32\r\n64\r\n366\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198208\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198208\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198208\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198208\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198208\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198144\r\n-200160\r\n32\r\n64\r\n366\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198144\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198144\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198144\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198144\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198112\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198080\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198080\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198080\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198080\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198048\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198048\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198048\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198048\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198016\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198016\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198016\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198016\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197984\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197984\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197984\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197984\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197952\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197952\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197952\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197952\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197920\r\n-200192\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197920\r\n-200160\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197920\r\n-200128\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197920\r\n-200096\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197920\r\n-200064\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-197920\r\n-200032\r\n32\r\n32\r\n251\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n-199936\r\n-200404\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200372\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200340\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200308\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200276\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200212\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200180\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200148\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200116\r\n-1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200212\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200212\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200180\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200180\r\n1\r\n190\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Slope test.lvl",
    "content": "64\r\n0\r\n\"Slope test\"\r\n-200800\r\n-200600\r\n-200000\r\n-198176\r\n24\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n14\r\n#FALSE#\r\n#FALSE#\r\n\"Slope test/Charles Ray - Hit the road jack - loop-Nineko.vgz\"\r\n-180000\r\n-180600\r\n-180000\r\n-179200\r\n24\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Slope test/Charles Ray - Hit the road jack - loop-Nineko.vgz\"\r\n-160000\r\n-160600\r\n-160000\r\n-159200\r\n24\r\n16291944\r\n#TRUE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Slope test/Charles Ray - Hit the road jack - loop-Nineko.vgz\"\r\n-140000\r\n-140600\r\n-140000\r\n-139200\r\n24\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Slope test/Charles Ray - Hit the road jack - loop-Nineko.vgz\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-384\r\n-312\r\n288\r\n416\r\n24\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Slope test/Charles Ray - Hit the road jack - loop-Nineko.vgz\"\r\n20000\r\n19400\r\n20000\r\n20800\r\n24\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Slope test/Charles Ray - Hit the road jack - loop-Nineko.vgz\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-198396\r\n-200086\r\n24\r\n54\r\n-198396\r\n-200220\r\n24\r\n60\r\n-200800\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200768\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200736\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200704\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200672\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200640\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200608\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200576\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200576\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200544\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200544\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200512\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200512\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200480\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200480\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200448\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200448\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200416\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200416\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200384\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200384\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200352\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200352\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200320\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200320\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200288\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200288\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200256\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200256\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200224\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200224\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200192\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200192\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200160\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200160\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200128\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200128\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200096\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200096\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200032\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200736\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200704\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200672\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200640\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200608\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200576\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200544\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200512\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200480\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200448\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200416\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200384\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200352\r\n32\r\n32\r\n111\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200320\r\n32\r\n32\r\n268\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200352\r\n32\r\n32\r\n4\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200288\r\n32\r\n32\r\n193\r\n1009\r\n#FALSE#\r\n#FALSE#\r\n\"superMushrook\"\r\n\"\"\r\n\"superkek\"\r\n\"\"\r\n-199680\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200352\r\n32\r\n32\r\n4\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200288\r\n32\r\n32\r\n282\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"Скрыть-показать забор\"\r\n\"\"\r\n-199520\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200160\r\n32\r\n64\r\n638\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200160\r\n32\r\n64\r\n636\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199200\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199168\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199136\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200320\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199104\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200320\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199072\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200320\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200320\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199008\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198976\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198944\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198912\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198880\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198848\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200608\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200576\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200544\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200512\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200480\r\n32\r\n64\r\n140\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200160\r\n32\r\n32\r\n183\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198784\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198784\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200608\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200576\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200544\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200512\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200480\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200448\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200416\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200384\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200352\r\n32\r\n64\r\n140\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200160\r\n32\r\n32\r\n183\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198720\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198720\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200608\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200576\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200544\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200512\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200480\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200448\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200416\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200384\r\n32\r\n64\r\n145\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200352\r\n32\r\n64\r\n140\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200160\r\n32\r\n32\r\n183\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198656\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198656\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198624\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198624\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198592\r\n-200288\r\n32\r\n32\r\n5\r\n1034\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198592\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198592\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198560\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198560\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198528\r\n-200288\r\n32\r\n32\r\n5\r\n1009\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198528\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198528\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198496\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198496\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198464\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198464\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198432\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198432\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198400\r\n-200160\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198400\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198368\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198336\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198304\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198272\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198240\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198208\r\n-200256\r\n32\r\n32\r\n173\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"move forward\"\r\n\"\"\r\n-198208\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200640\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200608\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200576\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200544\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200512\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200480\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200448\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200416\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200384\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200352\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200320\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200288\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200256\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200224\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200192\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198176\r\n-200032\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198144\r\n-200192\r\n32\r\n32\r\n163\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-180000\r\n-180224\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-180000\r\n-180192\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-180000\r\n-180160\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-180000\r\n-180128\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-180000\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-180000\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-180000\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179968\r\n-180224\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179968\r\n-180192\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179968\r\n-180160\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179968\r\n-180128\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179968\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179968\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179968\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179936\r\n-180224\r\n32\r\n32\r\n96\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179936\r\n-180192\r\n32\r\n32\r\n99\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179936\r\n-180160\r\n32\r\n32\r\n99\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179936\r\n-180128\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179936\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179936\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179936\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179904\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179904\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179904\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179904\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179872\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179872\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179872\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179872\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179840\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179840\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179840\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179840\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179808\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179808\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179808\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179808\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179776\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179776\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179776\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179776\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179744\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179744\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179744\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179744\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179712\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179712\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179712\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179712\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179680\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179680\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179680\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179680\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179648\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179648\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179648\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179648\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179616\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179616\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179616\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179616\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179584\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179584\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179584\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179584\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179552\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179552\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179552\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179552\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179520\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179520\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179520\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179520\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179488\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179488\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179488\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179488\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179456\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179456\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179456\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179456\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179424\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179424\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179424\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179424\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179392\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179392\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179392\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179392\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179360\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179360\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179360\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179360\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179328\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179328\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179328\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179328\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179296\r\n-180128\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179296\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179296\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179296\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179264\r\n-180256\r\n32\r\n32\r\n94\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179264\r\n-180224\r\n32\r\n32\r\n97\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179264\r\n-180192\r\n32\r\n32\r\n97\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179264\r\n-180160\r\n32\r\n32\r\n97\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179264\r\n-180128\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179264\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179264\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179264\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179232\r\n-180256\r\n32\r\n32\r\n95\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179232\r\n-180224\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179232\r\n-180192\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179232\r\n-180160\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179232\r\n-180128\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179232\r\n-180096\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179232\r\n-180064\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179232\r\n-180032\r\n32\r\n32\r\n98\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-160000\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159968\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159936\r\n-160576\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159936\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159904\r\n-160576\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159904\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159872\r\n-160448\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159872\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159840\r\n-160448\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159840\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159808\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159776\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159776\r\n-160064\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159744\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159744\r\n-160064\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159712\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159712\r\n-160064\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159680\r\n-160064\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159520\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159488\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159456\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159424\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159392\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159360\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159328\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159296\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159264\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159232\r\n-160320\r\n32\r\n32\r\n89\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-140000\r\n-140096\r\n32\r\n64\r\n141\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-140000\r\n-140064\r\n32\r\n64\r\n146\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-140000\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139968\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139936\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139904\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139872\r\n-140320\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139872\r\n-140288\r\n32\r\n64\r\n146\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139872\r\n-140256\r\n32\r\n64\r\n146\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139872\r\n-140224\r\n32\r\n64\r\n141\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139872\r\n-140064\r\n32\r\n64\r\n141\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139872\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139840\r\n-140320\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139840\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139808\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139776\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139744\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139712\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139680\r\n-140096\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139680\r\n-140064\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139680\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139648\r\n-140416\r\n64\r\n64\r\n224\r\n1\r\n#FALSE#\r\n#FALSE#\r\n\"lol\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139648\r\n-140096\r\n64\r\n32\r\n156\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139648\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139616\r\n-140096\r\n64\r\n32\r\n150\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139616\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139584\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139552\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139520\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139488\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139456\r\n-140096\r\n64\r\n32\r\n150\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139456\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139424\r\n-140096\r\n64\r\n32\r\n156\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139424\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139392\r\n-140096\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139392\r\n-140064\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139392\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139360\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139328\r\n-140160\r\n32\r\n64\r\n141\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139328\r\n-140128\r\n32\r\n64\r\n146\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139328\r\n-140096\r\n32\r\n64\r\n146\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139328\r\n-140064\r\n32\r\n64\r\n146\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139328\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139296\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139264\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-139232\r\n-140032\r\n32\r\n32\r\n397\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-384\r\n-96\r\n32\r\n32\r\n169\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"makeNewKoopa\"\r\n\"\"\r\n-384\r\n32\r\n32\r\n32\r\n229\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-384\r\n64\r\n32\r\n32\r\n235\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-384\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-384\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-384\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-384\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-384\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-352\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-352\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-352\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-352\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-352\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-320\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-320\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-320\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-320\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-320\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-288\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-288\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-288\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-288\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-288\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-288\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-256\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-256\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-256\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-256\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-256\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-256\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-224\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-224\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-224\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-224\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-224\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-224\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-192\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-192\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-192\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-192\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-192\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-192\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-160\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-160\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-160\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-160\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-160\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-160\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-128\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-128\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-128\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-128\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-128\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-128\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-96\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-96\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-96\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-96\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-96\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-96\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-64\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-64\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-64\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-64\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-64\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-64\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-32\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-32\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-32\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-32\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-32\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-32\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n0\r\n32\r\n32\r\n289\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n128\r\n32\r\n32\r\n228\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n0\r\n32\r\n32\r\n227\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n32\r\n32\r\n32\r\n230\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n64\r\n32\r\n32\r\n230\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n96\r\n32\r\n32\r\n230\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n128\r\n32\r\n32\r\n238\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n224\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n256\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20000\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20000\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20000\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20000\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20032\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20032\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20032\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20032\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20064\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20064\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20064\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20064\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20096\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20096\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20096\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20096\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20128\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20128\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20128\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20128\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20160\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20160\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20160\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20160\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20192\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20192\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20192\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20192\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20224\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20224\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20224\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20224\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20256\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20256\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20256\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20256\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20288\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20288\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20288\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20288\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20320\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20320\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20320\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20320\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20352\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20352\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20352\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20352\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20384\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20384\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20384\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20384\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20416\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20416\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20416\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20416\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20448\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20448\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20448\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20448\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20480\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20480\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20480\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20480\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20512\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20512\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20512\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20512\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20544\r\n19872\r\n32\r\n64\r\n140\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20544\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20544\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20544\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20576\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20576\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20576\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20608\r\n19552\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20608\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20608\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20608\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20608\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20640\r\n19552\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20640\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20640\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20640\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20640\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20672\r\n19552\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20672\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20672\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20672\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20672\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20704\r\n19552\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20704\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20704\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20704\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20704\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20736\r\n19552\r\n32\r\n32\r\n188\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20736\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20736\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20736\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20736\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20768\r\n19872\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20768\r\n19904\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20768\r\n19936\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20768\r\n19968\r\n32\r\n32\r\n521\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-179904\r\n-180192\r\n169\r\n\"Default\"\r\n-179904\r\n-180224\r\n168\r\n\"Default\"\r\n-179840\r\n-180224\r\n168\r\n\"Default\"\r\n-179840\r\n-180192\r\n169\r\n\"Default\"\r\n-179776\r\n-180224\r\n168\r\n\"Default\"\r\n-179776\r\n-180192\r\n169\r\n\"Default\"\r\n-179712\r\n-180224\r\n168\r\n\"Default\"\r\n-179712\r\n-180192\r\n169\r\n\"Default\"\r\n-179648\r\n-180224\r\n168\r\n\"Default\"\r\n-179648\r\n-180192\r\n169\r\n\"Default\"\r\n-179584\r\n-180224\r\n168\r\n\"Default\"\r\n-179584\r\n-180192\r\n169\r\n\"Default\"\r\n-179520\r\n-180192\r\n169\r\n\"Default\"\r\n-179520\r\n-180224\r\n168\r\n\"Default\"\r\n-179456\r\n-180192\r\n169\r\n\"Default\"\r\n-179456\r\n-180224\r\n168\r\n\"Default\"\r\n-179392\r\n-180192\r\n169\r\n\"Default\"\r\n-179392\r\n-180224\r\n168\r\n\"Default\"\r\n-179328\r\n-180192\r\n169\r\n\"Default\"\r\n-179328\r\n-180224\r\n168\r\n\"Default\"\r\n-200320\r\n-200192\r\n134\r\n\"Default\"\r\n-200320\r\n-200256\r\n136\r\n\"Default\"\r\n-200320\r\n-200224\r\n135\r\n\"Default\"\r\n-199456\r\n-200288\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200416\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200448\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200320\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200512\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200384\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200224\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200352\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200192\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200256\r\n182\r\n\"fence to show/hide\"\r\n-199456\r\n-200480\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200192\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200256\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200384\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200288\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200416\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200352\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200448\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200320\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200480\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200512\r\n182\r\n\"fence to show/hide\"\r\n-199424\r\n-200224\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200320\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200480\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200352\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200256\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200384\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200288\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200416\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200512\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200448\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200192\r\n182\r\n\"fence to show/hide\"\r\n-199392\r\n-200224\r\n182\r\n\"fence to show/hide\"\r\n-199136\r\n-200256\r\n182\r\n\"Default\"\r\n-199136\r\n-200416\r\n182\r\n\"Default\"\r\n-199136\r\n-200448\r\n182\r\n\"Default\"\r\n-199136\r\n-200320\r\n182\r\n\"Default\"\r\n-199136\r\n-200480\r\n182\r\n\"Default\"\r\n-199136\r\n-200384\r\n182\r\n\"Default\"\r\n-199136\r\n-200512\r\n182\r\n\"Default\"\r\n-199136\r\n-200352\r\n182\r\n\"Default\"\r\n-199136\r\n-200224\r\n182\r\n\"Default\"\r\n-199136\r\n-200288\r\n182\r\n\"Default\"\r\n-199136\r\n-200192\r\n182\r\n\"Default\"\r\n-199104\r\n-200320\r\n182\r\n\"Default\"\r\n-199104\r\n-200384\r\n182\r\n\"Default\"\r\n-199104\r\n-200192\r\n182\r\n\"Default\"\r\n-199104\r\n-200416\r\n182\r\n\"Default\"\r\n-199104\r\n-200256\r\n182\r\n\"Default\"\r\n-199104\r\n-200448\r\n182\r\n\"Default\"\r\n-199104\r\n-200352\r\n182\r\n\"Default\"\r\n-199104\r\n-200480\r\n182\r\n\"Default\"\r\n-199104\r\n-200288\r\n182\r\n\"Default\"\r\n-199104\r\n-200512\r\n182\r\n\"Default\"\r\n-199104\r\n-200224\r\n182\r\n\"Default\"\r\n-199104\r\n-200384\r\n118\r\n\"Floating bricks\"\r\n-199072\r\n-200512\r\n182\r\n\"Default\"\r\n-199072\r\n-200320\r\n182\r\n\"Default\"\r\n-199072\r\n-200352\r\n182\r\n\"Default\"\r\n-199072\r\n-200224\r\n182\r\n\"Default\"\r\n-199072\r\n-200384\r\n182\r\n\"Default\"\r\n-199072\r\n-200288\r\n182\r\n\"Default\"\r\n-199072\r\n-200416\r\n182\r\n\"Default\"\r\n-199072\r\n-200256\r\n182\r\n\"Default\"\r\n-199072\r\n-200448\r\n182\r\n\"Default\"\r\n-199072\r\n-200192\r\n182\r\n\"Default\"\r\n-199072\r\n-200480\r\n182\r\n\"Default\"\r\n-199040\r\n-200320\r\n182\r\n\"Default\"\r\n-199040\r\n-200384\r\n182\r\n\"Default\"\r\n-199040\r\n-200352\r\n182\r\n\"Default\"\r\n-199040\r\n-200480\r\n182\r\n\"Default\"\r\n-199040\r\n-200288\r\n182\r\n\"Default\"\r\n-199040\r\n-200416\r\n182\r\n\"Default\"\r\n-199040\r\n-200256\r\n182\r\n\"Default\"\r\n-199040\r\n-200448\r\n182\r\n\"Default\"\r\n-199040\r\n-200224\r\n182\r\n\"Default\"\r\n-199040\r\n-200512\r\n182\r\n\"Default\"\r\n-199040\r\n-200192\r\n182\r\n\"Default\"\r\n-199008\r\n-200320\r\n182\r\n\"Default\"\r\n-199008\r\n-200512\r\n182\r\n\"Default\"\r\n-199008\r\n-200192\r\n182\r\n\"Default\"\r\n-199008\r\n-200352\r\n182\r\n\"Default\"\r\n-199008\r\n-200384\r\n182\r\n\"Default\"\r\n-199008\r\n-200288\r\n182\r\n\"Default\"\r\n-199008\r\n-200416\r\n182\r\n\"Default\"\r\n-199008\r\n-200256\r\n182\r\n\"Default\"\r\n-199008\r\n-200448\r\n182\r\n\"Default\"\r\n-199008\r\n-200224\r\n182\r\n\"Default\"\r\n-199008\r\n-200480\r\n182\r\n\"Default\"\r\n-198976\r\n-200512\r\n182\r\n\"Default\"\r\n-198976\r\n-200384\r\n182\r\n\"Default\"\r\n-198976\r\n-200352\r\n182\r\n\"Default\"\r\n-198976\r\n-200480\r\n182\r\n\"Default\"\r\n-198976\r\n-200320\r\n182\r\n\"Default\"\r\n-198976\r\n-200416\r\n182\r\n\"Default\"\r\n-198976\r\n-200288\r\n182\r\n\"Default\"\r\n-198976\r\n-200448\r\n182\r\n\"Default\"\r\n-198976\r\n-200256\r\n182\r\n\"Default\"\r\n-198976\r\n-200192\r\n182\r\n\"Default\"\r\n-198976\r\n-200224\r\n182\r\n\"Default\"\r\n-198944\r\n-200384\r\n182\r\n\"Default\"\r\n-198944\r\n-200352\r\n182\r\n\"Default\"\r\n-198944\r\n-200320\r\n182\r\n\"Default\"\r\n-198944\r\n-200480\r\n182\r\n\"Default\"\r\n-198944\r\n-200288\r\n182\r\n\"Default\"\r\n-198944\r\n-200416\r\n182\r\n\"Default\"\r\n-198944\r\n-200256\r\n182\r\n\"Default\"\r\n-198944\r\n-200448\r\n182\r\n\"Default\"\r\n-198944\r\n-200224\r\n182\r\n\"Default\"\r\n-198944\r\n-200512\r\n182\r\n\"Default\"\r\n-198944\r\n-200192\r\n182\r\n\"Default\"\r\n-198912\r\n-200416\r\n182\r\n\"Default\"\r\n-198912\r\n-200320\r\n182\r\n\"Default\"\r\n-198912\r\n-200352\r\n182\r\n\"Default\"\r\n-198912\r\n-200224\r\n182\r\n\"Default\"\r\n-198912\r\n-200384\r\n182\r\n\"Default\"\r\n-198912\r\n-200288\r\n182\r\n\"Default\"\r\n-198912\r\n-200512\r\n182\r\n\"Default\"\r\n-198912\r\n-200256\r\n182\r\n\"Default\"\r\n-198912\r\n-200448\r\n182\r\n\"Default\"\r\n-198912\r\n-200192\r\n182\r\n\"Default\"\r\n-198912\r\n-200480\r\n182\r\n\"Default\"\r\n-198880\r\n-200480\r\n182\r\n\"Default\"\r\n-198880\r\n-200288\r\n182\r\n\"Default\"\r\n-198880\r\n-200512\r\n182\r\n\"Default\"\r\n-198880\r\n-200192\r\n182\r\n\"Default\"\r\n-198880\r\n-200448\r\n182\r\n\"Default\"\r\n-198880\r\n-200320\r\n182\r\n\"Default\"\r\n-198880\r\n-200224\r\n182\r\n\"Default\"\r\n-198880\r\n-200352\r\n182\r\n\"Default\"\r\n-198880\r\n-200416\r\n182\r\n\"Default\"\r\n-198880\r\n-200256\r\n182\r\n\"Default\"\r\n-198880\r\n-200384\r\n182\r\n\"Default\"\r\n-159904\r\n-160192\r\n161\r\n\"Default\"\r\n-159392\r\n-160480\r\n161\r\n\"Default\"\r\n64\r\n-96\r\n136\r\n\"Default\"\r\n64\r\n-32\r\n134\r\n\"Default\"\r\n64\r\n-64\r\n135\r\n\"Default\"\r\n20192\r\n19808\r\n135\r\n\"Default\"\r\n20192\r\n19840\r\n134\r\n\"Default\"\r\n20192\r\n19776\r\n136\r\n\"Default\"\r\n20608\r\n19488\r\n118\r\n\"Floating bricks\"\r\n-200016\r\n-200256\r\n70\r\n\"Default\"\r\n-198784\r\n-200128\r\n141\r\n\"Default\"\r\n-139760\r\n-140128\r\n141\r\n\"Default\"\r\n-198848\r\n-200096\r\n88\r\n\"Default\"\r\n-198688\r\n-200096\r\n107\r\n\"Default\"\r\n-198592\r\n-200096\r\n88\r\n\"Default\"\r\n-198496\r\n-200096\r\n87\r\n\"Default\"\r\n-198496\r\n-200224\r\n92\r\n\"Default\"\r\n-179968\r\n-180288\r\n88\r\n\"Default\"\r\n-159840\r\n-160384\r\n107\r\n\"Default\"\r\n-384\r\n-32\r\n87\r\n\"Default\"\r\n20128\r\n19808\r\n92\r\n\"Default\"\r\n-200256\r\n-200224\r\n137\r\n\"Default\"\r\n-200256\r\n-200192\r\n137\r\n\"Default\"\r\n-200256\r\n-200256\r\n138\r\n\"Default\"\r\n128\r\n-64\r\n137\r\n\"Default\"\r\n128\r\n-96\r\n138\r\n\"Default\"\r\n128\r\n-32\r\n137\r\n\"Default\"\r\n20256\r\n19808\r\n137\r\n\"Default\"\r\n20256\r\n19840\r\n137\r\n\"Default\"\r\n20256\r\n19776\r\n138\r\n\"Default\"\r\n\"next\"\r\n20560\r\n19840\r\n-1\r\n109\r\n#TRUE#\r\n1\r\n1\r\n10\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20224\r\n19808\r\n-1\r\n192\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20672\r\n19552\r\n-1\r\n91\r\n134\r\n#TRUE#\r\n1\r\n1\r\n31\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20740\r\n19520\r\n-1\r\n33\r\n#TRUE#\r\n1\r\n1\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n20612\r\n19488\r\n-1\r\n33\r\n#TRUE#\r\n1\r\n1\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n96\r\n-1\r\n111\r\n#TRUE#\r\n1\r\n1\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"makeNewKoopa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n-64\r\n-1\r\n192\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159776\r\n-160106\r\n1\r\n158\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159744\r\n-160352\r\n-1\r\n49\r\n#TRUE#\r\n1\r\n1\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159936\r\n-160362\r\n-1\r\n158\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-159456\r\n-160362\r\n-1\r\n158\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179552\r\n-180192\r\n-1\r\n111\r\n#TRUE#\r\n1\r\n1\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"makeNewKoopa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-179248\r\n-180288\r\n-1\r\n35\r\n#TRUE#\r\n1\r\n1\r\n10\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200192\r\n1\r\n98\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200192\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200192\r\n-1\r\n116\r\n#TRUE#\r\n1\r\n1\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198400\r\n-200192\r\n-1\r\n49\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200192\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200064\r\n-200192\r\n0\r\n273\r\n#TRUE#\r\n1\r\n1\r\n1\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200192\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200192\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200192\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200192\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200192\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198752\r\n-200208\r\n-1\r\n8\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198816\r\n-200208\r\n-1\r\n8\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198688\r\n-200208\r\n-1\r\n93\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200288\r\n-200224\r\n-1\r\n192\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200224\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200256\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200288\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199040\r\n-200320\r\n-1\r\n91\r\n134\r\n#TRUE#\r\n1\r\n1\r\n1\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200320\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200320\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198736\r\n-200320\r\n-1\r\n51\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198672\r\n-200320\r\n-1\r\n51\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200320\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200352\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200352\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200352\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198624\r\n-200352\r\n1\r\n52\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199100\r\n-200384\r\n-1\r\n33\r\n#TRUE#\r\n1\r\n1\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200384\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200384\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200384\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200416\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200048\r\n-200416\r\n1\r\n104\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200416\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200416\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200448\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198808\r\n-200448\r\n-1\r\n257\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200448\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200448\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198864\r\n-200480\r\n-1\r\n52\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200480\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200480\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200480\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200512\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200512\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200512\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200544\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200544\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200544\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199016\r\n-200560\r\n-1\r\n283\r\n169\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199112\r\n-200560\r\n-1\r\n283\r\n169\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200576\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200576\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200576\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200608\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199192\r\n-200608\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199160\r\n-200608\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-198456\r\n-200640\r\n-1\r\n213\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-198496\r\n-200192\r\n20128\r\n19840\r\n1\r\n1\r\n2\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n-198496\r\n-200064\r\n-384\r\n0\r\n1\r\n1\r\n2\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n-198592\r\n-200064\r\n-179968\r\n-180256\r\n1\r\n1\r\n2\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n-198688\r\n-200064\r\n-159840\r\n-160352\r\n1\r\n1\r\n2\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n-159376\r\n-160464\r\n-159888\r\n-160176\r\n1\r\n1\r\n3\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n-198768\r\n-200064\r\n-139744\r\n-140064\r\n1\r\n1\r\n2\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n-139856\r\n-140096\r\n-139856\r\n-140192\r\n3\r\n1\r\n1\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n-139984\r\n-140128\r\n-139312\r\n-140192\r\n3\r\n3\r\n1\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n-139584\r\n-140064\r\n-139488\r\n-140064\r\n2\r\n4\r\n1\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n-198848\r\n-200064\r\n-198560\r\n-200192\r\n1\r\n1\r\n2\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n500\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"next\"\r\n-179904\r\n-180208\r\n640\r\n80\r\n0\r\n#FALSE#\r\n\"Default\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"Floating bricks\"\r\n#FALSE#\r\n\"makeNewKoopa\"\r\n#TRUE#\r\n\"superMushrook\"\r\n#FALSE#\r\n\"fence to show/hide\"\r\n#FALSE#\r\n\"stopper\"\r\n#FALSE#\r\n\"reverser\"\r\n#FALSE#\r\n\"lol\"\r\n#TRUE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"briks A\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"briks B\"\r\n3\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n0\r\n.1\r\n0\r\n0\r\n0\r\n\"briks B\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"briks C\"\r\n15\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n0\r\n.3\r\n0\r\n0\r\n0\r\n\"briks C\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"briks D\"\r\n4\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n0\r\n.1\r\n0\r\n0\r\n0\r\n\"briks D\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"briks E\"\r\n3\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n0\r\n-.1\r\n0\r\n0\r\n0\r\n\"briks E\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"briks F\"\r\n15\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n0\r\n-.3\r\n0\r\n0\r\n0\r\n\"briks F\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"briks A\"\r\n4\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Floating bricks\"\r\n0\r\n-.1\r\n0\r\n0\r\n0\r\n\"makeNewKoopa\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"makeNewKoopa\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"superkek\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"Destroyed Blocks\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"Скрыть-показать забор\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"fence to show/hide\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"move forward\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"move back\"\r\n100\r\n#FALSE#\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"move back\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"stop\"\r\n100\r\n#FALSE#\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#TRUE#\r\n#TRUE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"stop\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"lol\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"lol\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"lolHide\"\r\n20\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"lolHide\"\r\n\"\"\r\n0\r\n0\r\n\"lol\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/Veggies shoot.lvl",
    "content": "64\r\n0\r\n\"Veggies shoot\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-199868\r\n-200086\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199584\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199552\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199296\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199264\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199232\r\n-200032\r\n32\r\n32\r\n223\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-199680\r\n-200096\r\n148\r\n\"Default\"\r\n-199552\r\n-200096\r\n148\r\n\"Default\"\r\n-199424\r\n-200096\r\n148\r\n\"Default\"\r\n-199296\r\n-200096\r\n148\r\n\"Default\"\r\n\"next\"\r\n-199664\r\n-200128\r\n-1\r\n147\r\n#TRUE#\r\n1\r\n2\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199536\r\n-200128\r\n-1\r\n147\r\n#TRUE#\r\n1\r\n2\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199408\r\n-200128\r\n-1\r\n147\r\n#TRUE#\r\n1\r\n2\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199280\r\n-200128\r\n-1\r\n5\r\n#TRUE#\r\n1\r\n2\r\n20\r\n\"\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/levels/compat.ini",
    "content": "[compatibility]\nenable-last-warp-hub-resume = true\nfix-platform-acceleration = true\nfix-pokey-collapse = true\nfix-player-filter-bounce = true\nfix-player-downward-clip = true\nfix-npc-downward-clip = true\nfix-npc55-kick-ice-blocks = true\nfix-climb-invisible-fences = true\nfix-climb-bgo-speed-adding = true\nenable-climb-bgo-layer-move = true\nfix-player-clip-wall-at-npc = true\nfix-skull-raft = true\nfix-peach-escape-shell-surf = false\n"
  },
  {
    "path": "test/levels/doors test.lvl",
    "content": "64\r\n0\r\n\"Doors test\"\r\n-200000\r\n-200600\r\n-200000\r\n-199200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-40000\r\n-40600\r\n-40000\r\n-39200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n-20000\r\n-20600\r\n-20000\r\n-19200\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n296\r\n896\r\n800\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n20000\r\n19400\r\n20000\r\n20800\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n40000\r\n39400\r\n40000\r\n40800\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n60000\r\n59400\r\n60000\r\n60800\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n80000\r\n79400\r\n80000\r\n80800\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n16291944\r\n#FALSE#\r\n#FALSE#\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n372\r\n458\r\n24\r\n54\r\n0\r\n0\r\n0\r\n0\r\n-200000\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-200000\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199968\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200480\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199936\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200512\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199904\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200544\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199872\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200576\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199840\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200576\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199808\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200576\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200096\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199776\r\n-200064\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200576\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199744\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200192\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199712\r\n-200032\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200160\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200128\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200096\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199680\r\n-200064\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200384\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200352\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200320\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200288\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200256\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199648\r\n-200224\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199616\r\n-200384\r\n192\r\n416\r\n575\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199520\r\n-200480\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199488\r\n-200512\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199456\r\n-200544\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199424\r\n-200576\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200576\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199360\r\n-200576\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199328\r\n-200576\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n0\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n32\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n416\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n64\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n384\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n96\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n352\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n128\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n320\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n736\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n160\r\n864\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n320\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n704\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n736\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n768\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n800\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n832\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n192\r\n864\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n320\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n224\r\n864\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n320\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n256\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n736\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n768\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n800\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n288\r\n832\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n704\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n320\r\n864\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n512\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n544\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n576\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n608\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n640\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n672\r\n32\r\n32\r\n413\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n704\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n352\r\n864\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n512\r\n192\r\n416\r\n575\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n736\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n768\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n800\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n384\r\n832\r\n32\r\n32\r\n231\r\n0\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n480\r\n416\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n512\r\n384\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n544\r\n352\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n576\r\n320\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n608\r\n320\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n640\r\n320\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n672\r\n320\r\n32\r\n32\r\n5\r\n99\r\n#FALSE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n-199648\r\n-200448\r\n88\r\n\"Default\"\r\n320\r\n448\r\n88\r\n\"Default\"\r\n\"next\"\r\n608\r\n480\r\n-1\r\n9\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-199392\r\n-200416\r\n-1\r\n9\r\n#FALSE#\r\n\"\"\r\n#FALSE#\r\n#TRUE#\r\n#FALSE#\r\n\"Default\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"next\"\r\n320\r\n480\r\n-199648\r\n-200416\r\n1\r\n1\r\n2\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n-199648\r\n-200416\r\n320\r\n480\r\n1\r\n1\r\n2\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n-1\r\n-1\r\n0\r\n\"Default\"\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"next\"\r\n\"next\"\r\n\"Default\"\r\n#FALSE#\r\n\"Destroyed Blocks\"\r\n#TRUE#\r\n\"Spawned NPCs\"\r\n#FALSE#\r\n\"next\"\r\n\"Level - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - Start\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n\"P Switch - End\"\r\n\"\"\r\n0\r\n0\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n\"\"\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n-1\r\n-1\r\n-1\r\n0\r\n0\r\n0\r\n\"\"\r\n0\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n#FALSE#\r\n\"\"\r\n0\r\n0\r\n0\r\n0\r\n0\r\n"
  },
  {
    "path": "test/test_msg_macro/CMakeLists.txt",
    "content": "set(CMAKE_CXX_STANDARD 14)\n\nadd_executable(TestMsgMacro\n    ${TheXTech_SOURCE_DIR}/src/script/msg_macros.cpp\n    ${TheXTech_SOURCE_DIR}/src/script/msg_macros.h\n    test_msg_macro.cpp\n    $<TARGET_OBJECTS:Catch-objects>\n)\ntarget_link_libraries(TestMsgMacro PRIVATE test_common)\ntarget_compile_definitions(TestMsgMacro PRIVATE -DMOONDUST_UNIT_TEST)\nadd_test(NAME TestMsgMacro COMMAND ConversionTest)\n"
  },
  {
    "path": "test/test_msg_macro/test_msg_macro.cpp",
    "content": "#include \"catch_amalgamated.hpp\"\n#include \"script/msg_macros.h\"\n\nstatic std::string str2escaped(std::string src)\n{\n    if(src.empty())\n        return src;\n\n    static const std::string from = \"\\n\";\n    static const std::string to = \"\\\\n\";\n\n    size_t start_pos = 0;\n    while((start_pos = src.find(from, start_pos)) != std::string::npos)\n    {\n        src.replace(start_pos, from.length(), to);\n        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'\n    }\n\n    return src;\n}\n\nvoid testMessageCondition(const char *line, const std::string &expected, int macro_character, int macro_state)\n{\n    std::string ret;\n    MsgMacroErrors err;\n    msgMacroProcess(std::string(line), ret, macro_character, macro_state, &err);\n\n    INFO(\"Error handling of input:\\n------------------------------------\\n\" << line << \"\\n------------------------------------\\n\");\n    CAPTURE(str2escaped(line));\n    CHECK(err == MSG_MACRO_ERROR_OK);\n\n    INFO(\"Comparison of:\\n-----------Expected:----------------\\n\" << expected  << \"\\n-----------Actual:------------------\\n\" << ret << \"\\n------------------------------------\");\n    CAPTURE(str2escaped(ret), str2escaped(expected));\n    CHECK(ret == expected);\n}\n\nvoid testMessageValidness(const char *line, int macro_character, int macro_state, MsgMacroErrors expected)\n{\n    std::string ret;\n    MsgMacroErrors err;\n    INFO(\"Error handling of input:\\n------------------------------------\\n\" << line << \"\\n------------------------------------\\n\");\n    msgMacroProcess(std::string(line), ret, macro_character, macro_state, &err);\n    INFO(\"Expected error code: \" << expected << \"\\nActual error code: \" << err);\n    CHECK(err == expected);\n}\n\n\nTEST_CASE(\"[message-box text macros] Ordinary usage\")\n{\n    const char *sample_001 =\n        \"Hello!\\n\"\n        \"#if player(1,2)\\n\"\n        \"You are plumber?!\\n\"\n        \"#elif player(3)\\n\"\n        \"You are princess?!\\n\"\n        \"#elif player(4)\\n\"\n        \"What the heck yo uare here?! Go clean toilets!\\n\"\n        \"#else\\n\"\n        \"Welcome to our world, stranger!\\n\"\n        \"#endif\\n\"\n        \"I have nothing to tell you!\";\n\n    SECTION(\"Sample 001\");\n    testMessageCondition(sample_001, \"Hello!\\nYou are plumber?!\\nI have nothing to tell you!\", 1, -1);\n    testMessageCondition(sample_001, \"Hello!\\nYou are plumber?!\\nI have nothing to tell you!\", 2, -1);\n    testMessageCondition(sample_001, \"Hello!\\nYou are princess?!\\nI have nothing to tell you!\", 3, -1);\n    testMessageCondition(sample_001, \"Hello!\\nWhat the heck yo uare here?! Go clean toilets!\\nI have nothing to tell you!\", 4, -1);\n    testMessageCondition(sample_001, \"Hello!\\nWelcome to our world, stranger!\\nI have nothing to tell you!\", 5, -1);\n\n    const char *sample_002 =\n        \"#if player(3)\\nХахаха. Ты сюда не вой-\\n\"\n        \"дёшь. Эта дверь запечатана магически. Ты должна соб-\\n\"\n        \"рать 15 звёзд, чтобы она открылась. Проваливай, малявка!\\n\"\n        \"#else\\n\"\n        \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\n\"\n        \"рать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\\n\"\n        \"#endif\";\n\n    SECTION(\"Sample 002\");\n    testMessageCondition(sample_002, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", 1, -1);\n    testMessageCondition(sample_002, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", 2, -1);\n    testMessageCondition(sample_002, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должна соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, малявка!\", 3, -1);\n    testMessageCondition(sample_002, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", 4, -1);\n    testMessageCondition(sample_002, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", 5, -1);\n    testMessageCondition(sample_002, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", -1, -1);\n\n    const char *sample_003 =\n        \"#if player(3)\\n\"\n        \"Hey, you! Get off my lawn!\\n\"\n        \"I don't care if you're a princess, just, get out!\\n\"\n        \"#else\\n\"\n        \"You boys better get off my lawn!\\n\"\n        \"#endif\";\n    testMessageCondition(sample_003, \"You boys better get off my lawn!\", 1, -1);\n    testMessageCondition(sample_003, \"You boys better get off my lawn!\", 2, -1);\n    testMessageCondition(sample_003, \"Hey, you! Get off my lawn!\\nI don't care if you're a princess, just, get out!\", 3, -1);\n    testMessageCondition(sample_003, \"You boys better get off my lawn!\", 4, -1);\n    testMessageCondition(sample_003, \"You boys better get off my lawn!\", 5, -1);\n    testMessageCondition(sample_003, \"You boys better get off my lawn!\", -1, -1);\n\n\n    const char *sample_004 = \"#if player(3)\\nБудь осторожна,\\n#else\\nБудь осторожен,\\n#endif\\nна пути много опасностей!\";\n    testMessageCondition(sample_004, \"Будь осторожен,\\nна пути много опасностей!\", 1, -1);\n    testMessageCondition(sample_004, \"Будь осторожен,\\nна пути много опасностей!\", 2, -1);\n    testMessageCondition(sample_004, \"Будь осторожна,\\nна пути много опасностей!\", 3, -1);\n    testMessageCondition(sample_004, \"Будь осторожен,\\nна пути много опасностей!\", 4, -1);\n    testMessageCondition(sample_004, \"Будь осторожен,\\nна пути много опасностей!\", 5, -1);\n    testMessageCondition(sample_004, \"Будь осторожен,\\nна пути много опасностей!\", -1, -1);\n\n\n    const char *sample_005 = \"#if player(3)\\n\"\n        \"Oh, Your Highness! It's such an honor to see you, Princess! I have a gift for you, let me open the chest...\\n\"\n        \"#elif player(4)\\n\"\n        \"Hello, bro! What's up? Take the thing from the chest, you'll need it!\\n\"\n        \"#elif player(5)\\n\"\n        \"Oh, you that hero from a far kingdom that everyone keep talking about? Here, you can have what's in this chest.\\n\"\n        \"#else\\n\"\n        \"Aren't you that plumber that everyone keeps talking about? Here, you can have what's in this chest.\\n\"\n        \"#endif\";\n    testMessageCondition(sample_005, \"Aren't you that plumber that everyone keeps talking about? Here, you can have what's in this chest.\", 1, -1);\n    testMessageCondition(sample_005, \"Aren't you that plumber that everyone keeps talking about? Here, you can have what's in this chest.\", 2, -1);\n    testMessageCondition(sample_005, \"Oh, Your Highness! It's such an honor to see you, Princess! I have a gift for you, let me open the chest...\", 3, -1);\n    testMessageCondition(sample_005, \"Hello, bro! What's up? Take the thing from the chest, you'll need it!\", 4, -1);\n    testMessageCondition(sample_005, \"Oh, you that hero from a far kingdom that everyone keep talking about? Here, you can have what's in this chest.\", 5, -1);\n    testMessageCondition(sample_005, \"Aren't you that plumber that everyone keeps talking about? Here, you can have what's in this chest.\", -1, -1);\n\n    const char *sample_006 = \"#if player(3)\\n\"\n        \"Did you like my gift?\\n\"\n        \"#else\\n\"\n        \"I hope that was helpful.\\n\"\n        \"#endif\";\n    testMessageCondition(sample_006, \"I hope that was helpful.\", 1, -1);\n    testMessageCondition(sample_006, \"I hope that was helpful.\", 2, -1);\n    testMessageCondition(sample_006, \"Did you like my gift?\", 3, -1);\n    testMessageCondition(sample_006, \"I hope that was helpful.\", 4, -1);\n    testMessageCondition(sample_006, \"I hope that was helpful.\", 5, -1);\n    testMessageCondition(sample_006, \"I hope that was helpful.\", -1, -1);\n\n    const char *sample_007 = \"#if player(3)\\n\"\n        \"О, Ваше Высочество! Для меня такая честь повидать Вас! У меня есть для Вас подарок, позвольте открыть сундук...\\n\"\n        \"#elif player(4)\\n\"\n        \"Здарова, брательник! Как поживаешь? Возьми кое-что из сундука, это тебе пригодится!\\n\"\n        \"#elif player(5)\\n\"\n        \"О, ты случайно не тот герой из далёкой страны, про которого все рассказывали? Возьми кое-что из этого сундука.\\n\"\n        \"#else\\n\"\n        \"Ты случайно не тот санте-\\n\"\n        \"хник, о котором все гово-\\n\"\n        \"рят? Возьми кое-что из \\n\"\n        \"этого сундука.\\n\"\n        \"#endif\";\n    testMessageCondition(sample_007, \"Ты случайно не тот санте-\\nхник, о котором все гово-\\nрят? Возьми кое-что из \\nэтого сундука.\", 1, -1);\n    testMessageCondition(sample_007, \"Ты случайно не тот санте-\\nхник, о котором все гово-\\nрят? Возьми кое-что из \\nэтого сундука.\", 2, -1);\n    testMessageCondition(sample_007, \"О, Ваше Высочество! Для меня такая честь повидать Вас! У меня есть для Вас подарок, позвольте открыть сундук...\", 3, -1);\n    testMessageCondition(sample_007, \"Здарова, брательник! Как поживаешь? Возьми кое-что из сундука, это тебе пригодится!\", 4, -1);\n    testMessageCondition(sample_007, \"О, ты случайно не тот герой из далёкой страны, про которого все рассказывали? Возьми кое-что из этого сундука.\", 5, -1);\n    testMessageCondition(sample_007, \"Ты случайно не тот санте-\\nхник, о котором все гово-\\nрят? Возьми кое-что из \\nэтого сундука.\", -1, -1);\n}\n\nTEST_CASE(\"[message-box text macros] State function\")\n{\n    const char *sample_001 =\n        \"#if state(1)\\n\"\n        \"Tiny as mouse!\\n\"\n        \"#elif state(2)\\n\"\n        \"Just a human.\\n\"\n        \"#elif state(3)\\n\"\n        \"Don't burn me!\\n\"\n        \"#elif state(4)\\n\"\n        \"So cute raccoon!\\n\"\n        \"#elif state(5)\\n\"\n        \"Hey, what this statue does here?\\n\"\n        \"#elif state(6)\\n\"\n        \"Armored warrior!\\n\"\n        \"#elif state(7)\\n\"\n        \"Freeze me, I want see the future!\\n\"\n        \"#else\\n\"\n        \"Who are you?!\\n\"\n        \"#endif\";\n\n    testMessageCondition(sample_001, \"Tiny as mouse!\", 1, 1);\n    testMessageCondition(sample_001, \"Just a human.\", 1, 2);\n    testMessageCondition(sample_001, \"Don't burn me!\", 1, 3);\n    testMessageCondition(sample_001, \"So cute raccoon!\", 3, 4);\n    testMessageCondition(sample_001, \"Hey, what this statue does here?\", 3, 5);\n    testMessageCondition(sample_001, \"Armored warrior!\", 3, 6);\n    testMessageCondition(sample_001, \"Freeze me, I want see the future!\", 3, 7);\n    testMessageCondition(sample_001, \"Who are you?!\", 3, 8);\n    testMessageCondition(sample_001, \"Who are you?!\", 3, 9);\n\n    const char *sample_002 =\n        \"You have power of\\n\"\n        \"#if_iw state(1)\\n\"\n        \"tininess\\n\"\n        \"#elif state(2)\\n\"\n        \"humanity\\n\"\n        \"#elif state(3)\\n\"\n        \"fire\\n\"\n        \"#elif state(4)\\n\"\n        \"tail\\n\"\n        \"#elif state(5)\\n\"\n        \"fur\\n\"\n        \"#elif state(6)\\n\"\n        \"armor\\n\"\n        \"#elif state(7)\\n\"\n        \"ice\\n\"\n        \"#else\\n\"\n        \"what?\\n\"\n        \"#endif_in\\n\"\n        \"!\";\n\n    testMessageCondition(sample_002, \"You have power of tininess!\", 1, 1);\n    testMessageCondition(sample_002, \"You have power of humanity!\", 1, 2);\n    testMessageCondition(sample_002, \"You have power of fire!\", 1, 3);\n    testMessageCondition(sample_002, \"You have power of tail!\", 3, 4);\n    testMessageCondition(sample_002, \"You have power of fur!\", 3, 5);\n    testMessageCondition(sample_002, \"You have power of armor!\", 3, 6);\n    testMessageCondition(sample_002, \"You have power of ice!\", 3, 7);\n    testMessageCondition(sample_002, \"You have power of what?!\", 3, 8);\n    testMessageCondition(sample_002, \"You have power of what?!\", 3, 9);\n}\n\nTEST_CASE(\"[message-box text macros] Extra spaces\")\n{\n    const char *sample_001 =\n        \"Hello!\\n\"\n        \"#  if     player(   1 ,      2       )   \\n\"\n        \"You are plumber?!\\n\"\n        \"# elif             player(  3      )   \\n\"\n        \"You are princess?!\\n\"\n        \"#          elif player( 4    )  \\n\"\n        \"What the heck yo uare here?! Go clean toilets!\\n\"\n        \"#    else  \\n\"\n        \"Welcome to our world, stranger!\\n\"\n        \"#     endif   \\n\"\n        \"I have nothing to tell you!\";\n\n    testMessageCondition(sample_001, \"Hello!\\nYou are plumber?!\\nI have nothing to tell you!\", 1, -1);\n    testMessageCondition(sample_001, \"Hello!\\nYou are plumber?!\\nI have nothing to tell you!\", 2, -1);\n    testMessageCondition(sample_001, \"Hello!\\nYou are princess?!\\nI have nothing to tell you!\", 3, -1);\n    testMessageCondition(sample_001, \"Hello!\\nWhat the heck yo uare here?! Go clean toilets!\\nI have nothing to tell you!\", 4, -1);\n    testMessageCondition(sample_001, \"Hello!\\nWelcome to our world, stranger!\\nI have nothing to tell you!\", 5, -1);\n}\n\nTEST_CASE(\"[message-box text macros] Non-Line-breaking branches\")\n{\n    const char *sample_001 =\n        \"Хахаха. Ты сюда не вой-\\n\"\n        \"дёшь. Эта дверь запечатана магически. Ты\\n\"\n        \"#if_iw player(3)\\n\"\n        \"должна\\n\"\n        \"#else\\n\"\n        \"должен\\n\"\n        \"#endif_iw\\n\"\n        \"соб-\\n\"\n        \"рать 15 звёзд, чтобы она открылась. Проваливай,\\n\"\n        \"#if_iw player(3)\\n\"\n        \"малявка!\\n\"\n        \"#else\\n\"\n        \"сопляк!\\n\"\n        \"#endif_in\";\n\n    testMessageCondition(sample_001, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", 1, -1);\n    testMessageCondition(sample_001, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", 2, -1);\n    testMessageCondition(sample_001, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должна соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, малявка!\", 3, -1);\n    testMessageCondition(sample_001, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", 4, -1);\n    testMessageCondition(sample_001, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", 5, -1);\n    testMessageCondition(sample_001, \"Хахаха. Ты сюда не вой-\\nдёшь. Эта дверь запечатана магически. Ты должен соб-\\nрать 15 звёзд, чтобы она открылась. Проваливай, сопляк!\", -1, -1);\n\n    const char *sample_002 =\n        \"Some phrase with\\n\"\n        \"#if_iw player(1)\\n\"\n        \"first\\n\"\n        \"#else\\n\"\n        \"another\\n\"\n        \"#endif_iw\\n\"\n        \"player.\";\n\n    testMessageCondition(sample_002, \"Some phrase with first player.\", 1, -1);\n    testMessageCondition(sample_002, \"Some phrase with another player.\", 2, -1);\n    testMessageCondition(sample_002, \"Some phrase with another player.\", 3, -1);\n    testMessageCondition(sample_002, \"Some phrase with another player.\", 4, -1);\n    testMessageCondition(sample_002, \"Some phrase with another player.\", 5, -1);\n    testMessageCondition(sample_002, \"Some phrase with another player.\", -1, -1);\n\n    const char *sample_003 =\n        \"This is mega\\n\"\n        \"#if_in player(1)\\n\"\n        \"demo\\n\"\n        \"#elif player(2)\\n\"\n        \"iris\\n\"\n        \"#elif player(3)\\n\"\n        \"tortoise\\n\"\n        \"#elif player(4,5)\\n\"\n        \"human\\n\"\n        \"#else\\n\"\n        \"trash\\n\"\n        \"#endif_in\\n\"\n        \"!\";\n\n    testMessageCondition(sample_003, \"This is megademo!\", 1, -1);\n    testMessageCondition(sample_003, \"This is megairis!\", 2, -1);\n    testMessageCondition(sample_003, \"This is megatortoise!\", 3, -1);\n    testMessageCondition(sample_003, \"This is megahuman!\", 4, -1);\n    testMessageCondition(sample_003, \"This is megahuman!\", 5, -1);\n    testMessageCondition(sample_003, \"This is megatrash!\", 6, -1);\n    testMessageCondition(sample_003, \"This is megatrash!\", -1, -1);\n}\n\nTEST_CASE(\"[message-box text macros] Error handling\")\n{\n    SECTION(\"Valid example\")\n    {\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#else\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_OK);\n    }\n\n    SECTION(\"Unknown condition command\")\n    {\n        testMessageValidness(\"Hello!\\n#wtfcond player(1)\\nYou are plumber?!\\n#else\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_UNKNOWN_CMD);\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#what\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_UNKNOWN_CMD);\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#else\\nWho are you?\\n#no you are mess\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_UNKNOWN_CMD);\n    }\n\n    SECTION(\"Unknown function\")\n    {\n        testMessageValidness(\"Hello!\\n#if drillingneighbour(1)\\nYou are plumber?!\\n#else\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_UNKNOWN_FUNC);\n        testMessageValidness(\"Hello!\\n#if drillingneighbour(1) , some\\nYou are plumber?!\\n#else\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_UNKNOWN_FUNC);\n    }\n\n\n    SECTION(\"Bad function syntax\")\n    {\n        testMessageValidness(\"Hello!\\n#if player\\nYou are plumber?!\\n#elif player(2)\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_SYNTAX);\n        testMessageValidness(\"Hello!\\n#if player(\\nYou are plumber?!\\n#elif player(2)\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_SYNTAX);\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#elif player)\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_SYNTAX);\n        testMessageValidness(\"Hello!\\n#if player)1)\\nYou are plumber?!\\n#else\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_SYNTAX);\n        testMessageValidness(\"Hello!\\n#if state ] 1  \\nYou are plumber?!\\n#else\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_SYNTAX);\n        testMessageValidness(\"Hello!\\n#if player(1 3)\\nYou are plumber?!\\n#else\\nWho are you?\\n#no you are mess\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_SYNTAX);\n        testMessageValidness(\"Hello!\\n#if player(3 (2 3)\\nYou are plumber?!\\n#else\\nWho are you?\\n#no you are mess\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_SYNTAX);\n    }\n\n    SECTION(\"Bad function arguments\")\n    {\n        testMessageValidness(\"Hello!\\n#if player(1x)\\nYou are plumber?!\\n#else\\nWho are you?\\n#no you are mess\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_ARGS);\n        testMessageValidness(\"Hello!\\n#if player(f, \\\", 3)\\nYou are plumber?!\\n#else\\nWho are you?\\n#no you are mess\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_ARGS);\n        testMessageValidness(\"Hello!\\n#if player(fe,(,2, 3)\\nYou are plumber?!\\n#else\\nWho are you?\\n#no you are mess\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_ARGS);\n        testMessageValidness(\"Hello!\\n#if player(ff (2 3)\\nYou are plumber?!\\n#else\\nWho are you?\\n#no you are mess\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_BAD_FUNC_ARGS);\n    }\n\n    SECTION(\"Junk at end of line\")\n    {\n        testMessageValidness(\"Hello!\\n#if player(1)мыши\\nYou are plumber?!\\n#else\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_EXTRA_SYMBOLS_AT_END);\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#elif player(  3331, 31) , some\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_EXTRA_SYMBOLS_AT_END);\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#else player(2)\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_EXTRA_SYMBOLS_AT_END);\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#else\\nWho are you?\\n#endif player(2)\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_EXTRA_SYMBOLS_AT_END);\n    }\n\n    SECTION(\"Illegal command\")\n    {\n        testMessageValidness(\"Hello!\\n#elif player(1)\\nYou are plumber?!\\n#else\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_ILLEGAL_CMD);\n        testMessageValidness(\"Hello!\\n#else\\nYou are plumber?!\\nWho are you?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_ILLEGAL_CMD);\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#else\\nWho are you?\\n#else\\nWho are you really?\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_ILLEGAL_CMD);\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#else\\nWho are you?\\n#elif player(2)\\nL is real...ly out of order\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_ILLEGAL_CMD);\n        testMessageValidness(\"Hello!\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_ILLEGAL_CMD);\n        testMessageValidness(\"Hello!\\n#if player(1)\\nYou are plumber?!\\n#if player(2)\\nsecond if without finishing first\\n#endif\\nI have nothing to tell you!\", 1, -1, MSG_MACRO_ERROR_ILLEGAL_CMD);\n    }\n}\n"
  },
  {
    "path": "test/worlds/speedrun-unit-test/unit-test.lvlx",
    "content": "HEAD\nTL:\"test\";CPID:\"SMBX1.3Compat\";\nHEAD_END\nSECTION\nSC:0;L:-200000;R:-192320;T:-200600;B:-200000;MZ:54;MF:\"\";BG:13;\nSECTION_END\nSTARTPOINT\nID:1;X:-199916;Y:-200118;D:1;\nID:2;X:-199980;Y:-200124;D:1;\nSTARTPOINT_END\nBLOCK\nID:3;X:-200000;Y:-200064;W:32;H:32;\nID:276;X:-200000;Y:-200032;W:32;H:32;\nID:3;X:-199968;Y:-200064;W:32;H:32;\nID:276;X:-199968;Y:-200032;W:32;H:32;\nID:3;X:-199936;Y:-200064;W:32;H:32;\nID:276;X:-199936;Y:-200032;W:32;H:32;\nID:3;X:-199904;Y:-200064;W:32;H:32;\nID:276;X:-199904;Y:-200032;W:32;H:32;\nID:3;X:-199872;Y:-200064;W:32;H:32;\nID:276;X:-199872;Y:-200032;W:32;H:32;\nID:3;X:-199840;Y:-200064;W:32;H:32;\nID:276;X:-199840;Y:-200032;W:32;H:32;\nID:3;X:-199808;Y:-200064;W:32;H:32;\nID:276;X:-199808;Y:-200032;W:32;H:32;\nID:3;X:-199776;Y:-200064;W:32;H:32;\nID:276;X:-199776;Y:-200032;W:32;H:32;\nID:3;X:-199744;Y:-200064;W:32;H:32;\nID:276;X:-199744;Y:-200032;W:32;H:32;\nID:3;X:-199712;Y:-200064;W:32;H:32;\nID:276;X:-199712;Y:-200032;W:32;H:32;\nID:3;X:-199680;Y:-200064;W:32;H:32;\nID:276;X:-199680;Y:-200032;W:32;H:32;\nID:3;X:-199648;Y:-200064;W:32;H:32;\nID:276;X:-199648;Y:-200032;W:32;H:32;\nID:3;X:-199616;Y:-200064;W:32;H:32;\nID:276;X:-199616;Y:-200032;W:32;H:32;\nID:3;X:-199584;Y:-200064;W:32;H:32;\nID:276;X:-199584;Y:-200032;W:32;H:32;\nID:3;X:-199552;Y:-200064;W:32;H:32;\nID:276;X:-199552;Y:-200032;W:32;H:32;\nID:3;X:-199520;Y:-200064;W:32;H:32;\nID:276;X:-199520;Y:-200032;W:32;H:32;\nID:3;X:-199488;Y:-200064;W:32;H:32;\nID:276;X:-199488;Y:-200032;W:32;H:32;\nID:3;X:-199456;Y:-200064;W:32;H:32;\nID:276;X:-199456;Y:-200032;W:32;H:32;\nID:3;X:-199424;Y:-200064;W:32;H:32;\nID:276;X:-199424;Y:-200032;W:32;H:32;\nID:3;X:-199392;Y:-200064;W:32;H:32;\nID:276;X:-199392;Y:-200032;W:32;H:32;\nID:3;X:-199360;Y:-200064;W:32;H:32;\nID:276;X:-199360;Y:-200032;W:32;H:32;\nID:3;X:-199328;Y:-200064;W:32;H:32;\nID:276;X:-199328;Y:-200032;W:32;H:32;\nID:3;X:-199296;Y:-200064;W:32;H:32;\nID:276;X:-199296;Y:-200032;W:32;H:32;\nID:3;X:-199264;Y:-200064;W:32;H:32;\nID:276;X:-199264;Y:-200032;W:32;H:32;\nID:3;X:-199232;Y:-200064;W:32;H:32;\nID:276;X:-199232;Y:-200032;W:32;H:32;\nID:3;X:-199200;Y:-200064;W:32;H:32;\nID:276;X:-199200;Y:-200032;W:32;H:32;\nID:3;X:-199168;Y:-200064;W:32;H:32;\nID:276;X:-199168;Y:-200032;W:32;H:32;\nID:3;X:-199136;Y:-200064;W:32;H:32;\nID:276;X:-199136;Y:-200032;W:32;H:32;\nID:3;X:-199104;Y:-200064;W:32;H:32;\nID:276;X:-199104;Y:-200032;W:32;H:32;\nID:3;X:-199072;Y:-200064;W:32;H:32;\nID:276;X:-199072;Y:-200032;W:32;H:32;\nID:3;X:-199040;Y:-200064;W:32;H:32;\nID:276;X:-199040;Y:-200032;W:32;H:32;\nID:3;X:-199008;Y:-200064;W:32;H:32;\nID:276;X:-199008;Y:-200032;W:32;H:32;\nID:3;X:-198976;Y:-200064;W:32;H:32;\nID:276;X:-198976;Y:-200032;W:32;H:32;\nID:3;X:-198944;Y:-200064;W:32;H:32;\nID:276;X:-198944;Y:-200032;W:32;H:32;\nID:3;X:-198912;Y:-200064;W:32;H:32;\nID:276;X:-198912;Y:-200032;W:32;H:32;\nID:3;X:-198880;Y:-200064;W:32;H:32;\nID:276;X:-198880;Y:-200032;W:32;H:32;\nID:3;X:-198848;Y:-200064;W:32;H:32;\nID:276;X:-198848;Y:-200032;W:32;H:32;\nID:3;X:-198816;Y:-200064;W:32;H:32;\nID:276;X:-198816;Y:-200032;W:32;H:32;\nID:3;X:-198784;Y:-200064;W:32;H:32;\nID:276;X:-198784;Y:-200032;W:32;H:32;\nID:3;X:-198752;Y:-200064;W:32;H:32;\nID:276;X:-198752;Y:-200032;W:32;H:32;\nID:3;X:-198720;Y:-200064;W:32;H:32;\nID:276;X:-198720;Y:-200032;W:32;H:32;\nID:3;X:-198688;Y:-200064;W:32;H:32;\nID:276;X:-198688;Y:-200032;W:32;H:32;\nID:3;X:-198656;Y:-200064;W:32;H:32;\nID:276;X:-198656;Y:-200032;W:32;H:32;\nID:3;X:-198624;Y:-200064;W:32;H:32;\nID:276;X:-198624;Y:-200032;W:32;H:32;\nID:3;X:-198592;Y:-200064;W:32;H:32;\nID:276;X:-198592;Y:-200032;W:32;H:32;\nID:3;X:-198560;Y:-200064;W:32;H:32;\nID:276;X:-198560;Y:-200032;W:32;H:32;\nID:3;X:-198528;Y:-200064;W:32;H:32;\nID:276;X:-198528;Y:-200032;W:32;H:32;\nID:3;X:-198496;Y:-200064;W:32;H:32;\nID:276;X:-198496;Y:-200032;W:32;H:32;\nID:3;X:-198464;Y:-200064;W:32;H:32;\nID:276;X:-198464;Y:-200032;W:32;H:32;\nID:3;X:-198432;Y:-200064;W:32;H:32;\nID:276;X:-198432;Y:-200032;W:32;H:32;\nID:3;X:-198400;Y:-200064;W:32;H:32;\nID:276;X:-198400;Y:-200032;W:32;H:32;\nID:3;X:-198368;Y:-200064;W:32;H:32;\nID:276;X:-198368;Y:-200032;W:32;H:32;\nID:3;X:-198336;Y:-200064;W:32;H:32;\nID:276;X:-198336;Y:-200032;W:32;H:32;\nID:3;X:-198304;Y:-200064;W:32;H:32;\nID:276;X:-198304;Y:-200032;W:32;H:32;\nID:3;X:-198272;Y:-200064;W:32;H:32;\nID:276;X:-198272;Y:-200032;W:32;H:32;\nID:3;X:-198240;Y:-200064;W:32;H:32;\nID:276;X:-198240;Y:-200032;W:32;H:32;\nID:3;X:-198208;Y:-200064;W:32;H:32;\nID:276;X:-198208;Y:-200032;W:32;H:32;\nID:3;X:-198176;Y:-200064;W:32;H:32;\nID:276;X:-198176;Y:-200032;W:32;H:32;\nID:3;X:-198144;Y:-200064;W:32;H:32;\nID:276;X:-198144;Y:-200032;W:32;H:32;\nID:3;X:-198112;Y:-200064;W:32;H:32;\nID:276;X:-198112;Y:-200032;W:32;H:32;\nID:3;X:-198080;Y:-200064;W:32;H:32;\nID:276;X:-198080;Y:-200032;W:32;H:32;\nID:3;X:-198048;Y:-200064;W:32;H:32;\nID:276;X:-198048;Y:-200032;W:32;H:32;\nID:3;X:-198016;Y:-200064;W:32;H:32;\nID:276;X:-198016;Y:-200032;W:32;H:32;\nID:3;X:-197984;Y:-200064;W:32;H:32;\nID:276;X:-197984;Y:-200032;W:32;H:32;\nID:3;X:-197952;Y:-200064;W:32;H:32;\nID:276;X:-197952;Y:-200032;W:32;H:32;\nID:3;X:-197920;Y:-200064;W:32;H:32;\nID:276;X:-197920;Y:-200032;W:32;H:32;\nID:3;X:-197888;Y:-200064;W:32;H:32;\nID:276;X:-197888;Y:-200032;W:32;H:32;\nID:3;X:-197856;Y:-200064;W:32;H:32;\nID:276;X:-197856;Y:-200032;W:32;H:32;\nID:3;X:-197824;Y:-200064;W:32;H:32;\nID:276;X:-197824;Y:-200032;W:32;H:32;\nID:3;X:-197792;Y:-200064;W:32;H:32;\nID:276;X:-197792;Y:-200032;W:32;H:32;\nID:3;X:-197760;Y:-200064;W:32;H:32;\nID:276;X:-197760;Y:-200032;W:32;H:32;\nID:3;X:-197728;Y:-200064;W:32;H:32;\nID:276;X:-197728;Y:-200032;W:32;H:32;\nID:3;X:-197696;Y:-200064;W:32;H:32;\nID:276;X:-197696;Y:-200032;W:32;H:32;\nID:3;X:-197664;Y:-200064;W:32;H:32;\nID:276;X:-197664;Y:-200032;W:32;H:32;\nID:3;X:-197632;Y:-200064;W:32;H:32;\nID:276;X:-197632;Y:-200032;W:32;H:32;\nID:3;X:-197600;Y:-200064;W:32;H:32;\nID:276;X:-197600;Y:-200032;W:32;H:32;\nID:3;X:-197568;Y:-200064;W:32;H:32;\nID:276;X:-197568;Y:-200032;W:32;H:32;\nID:3;X:-197536;Y:-200064;W:32;H:32;\nID:276;X:-197536;Y:-200032;W:32;H:32;\nID:3;X:-197504;Y:-200064;W:32;H:32;\nID:276;X:-197504;Y:-200032;W:32;H:32;\nID:3;X:-197472;Y:-200064;W:32;H:32;\nID:276;X:-197472;Y:-200032;W:32;H:32;\nID:3;X:-197440;Y:-200064;W:32;H:32;\nID:276;X:-197440;Y:-200032;W:32;H:32;\nID:3;X:-197408;Y:-200064;W:32;H:32;\nID:276;X:-197408;Y:-200032;W:32;H:32;\nID:3;X:-197376;Y:-200064;W:32;H:32;\nID:276;X:-197376;Y:-200032;W:32;H:32;\nID:3;X:-197344;Y:-200064;W:32;H:32;\nID:276;X:-197344;Y:-200032;W:32;H:32;\nID:3;X:-197312;Y:-200064;W:32;H:32;\nID:276;X:-197312;Y:-200032;W:32;H:32;\nID:3;X:-197280;Y:-200064;W:32;H:32;\nID:276;X:-197280;Y:-200032;W:32;H:32;\nID:3;X:-197248;Y:-200064;W:32;H:32;\nID:276;X:-197248;Y:-200032;W:32;H:32;\nID:3;X:-197216;Y:-200064;W:32;H:32;\nID:276;X:-197216;Y:-200032;W:32;H:32;\nID:3;X:-197184;Y:-200064;W:32;H:32;\nID:276;X:-197184;Y:-200032;W:32;H:32;\nID:3;X:-197152;Y:-200064;W:32;H:32;\nID:276;X:-197152;Y:-200032;W:32;H:32;\nID:3;X:-197120;Y:-200064;W:32;H:32;\nID:276;X:-197120;Y:-200032;W:32;H:32;\nID:3;X:-197088;Y:-200064;W:32;H:32;\nID:276;X:-197088;Y:-200032;W:32;H:32;\nID:3;X:-197056;Y:-200064;W:32;H:32;\nID:276;X:-197056;Y:-200032;W:32;H:32;\nID:3;X:-197024;Y:-200064;W:32;H:32;\nID:276;X:-197024;Y:-200032;W:32;H:32;\nID:3;X:-196992;Y:-200064;W:32;H:32;\nID:276;X:-196992;Y:-200032;W:32;H:32;\nID:3;X:-196960;Y:-200064;W:32;H:32;\nID:276;X:-196960;Y:-200032;W:32;H:32;\nID:3;X:-196928;Y:-200064;W:32;H:32;\nID:276;X:-196928;Y:-200032;W:32;H:32;\nID:3;X:-196896;Y:-200064;W:32;H:32;\nID:276;X:-196896;Y:-200032;W:32;H:32;\nID:3;X:-196864;Y:-200064;W:32;H:32;\nID:276;X:-196864;Y:-200032;W:32;H:32;\nID:3;X:-196832;Y:-200064;W:32;H:32;\nID:276;X:-196832;Y:-200032;W:32;H:32;\nID:3;X:-196800;Y:-200064;W:32;H:32;\nID:276;X:-196800;Y:-200032;W:32;H:32;\nID:3;X:-196768;Y:-200064;W:32;H:32;\nID:276;X:-196768;Y:-200032;W:32;H:32;\nID:3;X:-196736;Y:-200064;W:32;H:32;\nID:276;X:-196736;Y:-200032;W:32;H:32;\nID:3;X:-196704;Y:-200064;W:32;H:32;\nID:276;X:-196704;Y:-200032;W:32;H:32;\nID:3;X:-196672;Y:-200064;W:32;H:32;\nID:276;X:-196672;Y:-200032;W:32;H:32;\nID:3;X:-196640;Y:-200064;W:32;H:32;\nID:276;X:-196640;Y:-200032;W:32;H:32;\nID:3;X:-196608;Y:-200064;W:32;H:32;\nID:276;X:-196608;Y:-200032;W:32;H:32;\nID:3;X:-196576;Y:-200064;W:32;H:32;\nID:276;X:-196576;Y:-200032;W:32;H:32;\nID:3;X:-196544;Y:-200064;W:32;H:32;\nID:276;X:-196544;Y:-200032;W:32;H:32;\nID:3;X:-196512;Y:-200064;W:32;H:32;\nID:276;X:-196512;Y:-200032;W:32;H:32;\nID:3;X:-196480;Y:-200064;W:32;H:32;\nID:276;X:-196480;Y:-200032;W:32;H:32;\nID:3;X:-196448;Y:-200064;W:32;H:32;\nID:276;X:-196448;Y:-200032;W:32;H:32;\nID:3;X:-196416;Y:-200064;W:32;H:32;\nID:276;X:-196416;Y:-200032;W:32;H:32;\nID:3;X:-196384;Y:-200064;W:32;H:32;\nID:276;X:-196384;Y:-200032;W:32;H:32;\nID:3;X:-196352;Y:-200064;W:32;H:32;\nID:276;X:-196352;Y:-200032;W:32;H:32;\nID:3;X:-196320;Y:-200064;W:32;H:32;\nID:276;X:-196320;Y:-200032;W:32;H:32;\nID:3;X:-196288;Y:-200064;W:32;H:32;\nID:276;X:-196288;Y:-200032;W:32;H:32;\nID:3;X:-196256;Y:-200064;W:32;H:32;\nID:276;X:-196256;Y:-200032;W:32;H:32;\nID:3;X:-196224;Y:-200064;W:32;H:32;\nID:276;X:-196224;Y:-200032;W:32;H:32;\nID:3;X:-196192;Y:-200064;W:32;H:32;\nID:276;X:-196192;Y:-200032;W:32;H:32;\nID:3;X:-196160;Y:-200064;W:32;H:32;\nID:276;X:-196160;Y:-200032;W:32;H:32;\nID:3;X:-196128;Y:-200064;W:32;H:32;\nID:276;X:-196128;Y:-200032;W:32;H:32;\nID:3;X:-196096;Y:-200064;W:32;H:32;\nID:276;X:-196096;Y:-200032;W:32;H:32;\nID:3;X:-196064;Y:-200064;W:32;H:32;\nID:276;X:-196064;Y:-200032;W:32;H:32;\nID:3;X:-196032;Y:-200064;W:32;H:32;\nID:276;X:-196032;Y:-200032;W:32;H:32;\nID:3;X:-196000;Y:-200064;W:32;H:32;\nID:276;X:-196000;Y:-200032;W:32;H:32;\nID:3;X:-195968;Y:-200064;W:32;H:32;\nID:276;X:-195968;Y:-200032;W:32;H:32;\nID:3;X:-195936;Y:-200064;W:32;H:32;\nID:276;X:-195936;Y:-200032;W:32;H:32;\nID:3;X:-195904;Y:-200064;W:32;H:32;\nID:276;X:-195904;Y:-200032;W:32;H:32;\nID:3;X:-195872;Y:-200064;W:32;H:32;\nID:276;X:-195872;Y:-200032;W:32;H:32;\nID:3;X:-195840;Y:-200064;W:32;H:32;\nID:276;X:-195840;Y:-200032;W:32;H:32;\nID:3;X:-195808;Y:-200064;W:32;H:32;\nID:276;X:-195808;Y:-200032;W:32;H:32;\nID:3;X:-195776;Y:-200064;W:32;H:32;\nID:276;X:-195776;Y:-200032;W:32;H:32;\nID:3;X:-195744;Y:-200064;W:32;H:32;\nID:276;X:-195744;Y:-200032;W:32;H:32;\nID:3;X:-195712;Y:-200064;W:32;H:32;\nID:276;X:-195712;Y:-200032;W:32;H:32;\nID:3;X:-195680;Y:-200064;W:32;H:32;\nID:276;X:-195680;Y:-200032;W:32;H:32;\nID:3;X:-195648;Y:-200064;W:32;H:32;\nID:276;X:-195648;Y:-200032;W:32;H:32;\nID:3;X:-195616;Y:-200064;W:32;H:32;\nID:276;X:-195616;Y:-200032;W:32;H:32;\nID:3;X:-195584;Y:-200064;W:32;H:32;\nID:276;X:-195584;Y:-200032;W:32;H:32;\nID:3;X:-195552;Y:-200064;W:32;H:32;\nID:276;X:-195552;Y:-200032;W:32;H:32;\nID:3;X:-195520;Y:-200064;W:32;H:32;\nID:276;X:-195520;Y:-200032;W:32;H:32;\nID:3;X:-195488;Y:-200064;W:32;H:32;\nID:276;X:-195488;Y:-200032;W:32;H:32;\nID:3;X:-195456;Y:-200064;W:32;H:32;\nID:276;X:-195456;Y:-200032;W:32;H:32;\nID:3;X:-195424;Y:-200064;W:32;H:32;\nID:276;X:-195424;Y:-200032;W:32;H:32;\nID:3;X:-195392;Y:-200064;W:32;H:32;\nID:276;X:-195392;Y:-200032;W:32;H:32;\nID:3;X:-195360;Y:-200064;W:32;H:32;\nID:276;X:-195360;Y:-200032;W:32;H:32;\nID:3;X:-195328;Y:-200064;W:32;H:32;\nID:276;X:-195328;Y:-200032;W:32;H:32;\nID:3;X:-195296;Y:-200064;W:32;H:32;\nID:276;X:-195296;Y:-200032;W:32;H:32;\nID:3;X:-195264;Y:-200064;W:32;H:32;\nID:276;X:-195264;Y:-200032;W:32;H:32;\nID:3;X:-195232;Y:-200064;W:32;H:32;\nID:276;X:-195232;Y:-200032;W:32;H:32;\nID:3;X:-195200;Y:-200064;W:32;H:32;\nID:276;X:-195200;Y:-200032;W:32;H:32;\nID:3;X:-195168;Y:-200064;W:32;H:32;\nID:276;X:-195168;Y:-200032;W:32;H:32;\nID:3;X:-195136;Y:-200064;W:32;H:32;\nID:276;X:-195136;Y:-200032;W:32;H:32;\nID:3;X:-195104;Y:-200064;W:32;H:32;\nID:276;X:-195104;Y:-200032;W:32;H:32;\nID:3;X:-195072;Y:-200064;W:32;H:32;\nID:276;X:-195072;Y:-200032;W:32;H:32;\nID:3;X:-195040;Y:-200064;W:32;H:32;\nID:276;X:-195040;Y:-200032;W:32;H:32;\nID:3;X:-195008;Y:-200064;W:32;H:32;\nID:276;X:-195008;Y:-200032;W:32;H:32;\nID:3;X:-194976;Y:-200064;W:32;H:32;\nID:276;X:-194976;Y:-200032;W:32;H:32;\nID:3;X:-194944;Y:-200064;W:32;H:32;\nID:276;X:-194944;Y:-200032;W:32;H:32;\nID:3;X:-194912;Y:-200064;W:32;H:32;\nID:276;X:-194912;Y:-200032;W:32;H:32;\nID:3;X:-194880;Y:-200064;W:32;H:32;\nID:276;X:-194880;Y:-200032;W:32;H:32;\nID:3;X:-194848;Y:-200064;W:32;H:32;\nID:276;X:-194848;Y:-200032;W:32;H:32;\nID:3;X:-194816;Y:-200064;W:32;H:32;\nID:276;X:-194816;Y:-200032;W:32;H:32;\nID:3;X:-194784;Y:-200064;W:32;H:32;\nID:276;X:-194784;Y:-200032;W:32;H:32;\nID:3;X:-194752;Y:-200064;W:32;H:32;\nID:276;X:-194752;Y:-200032;W:32;H:32;\nID:3;X:-194720;Y:-200064;W:32;H:32;\nID:276;X:-194720;Y:-200032;W:32;H:32;\nID:3;X:-194688;Y:-200064;W:32;H:32;\nID:276;X:-194688;Y:-200032;W:32;H:32;\nID:3;X:-194656;Y:-200064;W:32;H:32;\nID:276;X:-194656;Y:-200032;W:32;H:32;\nID:3;X:-194624;Y:-200064;W:32;H:32;\nID:276;X:-194624;Y:-200032;W:32;H:32;\nID:3;X:-194592;Y:-200064;W:32;H:32;\nID:276;X:-194592;Y:-200032;W:32;H:32;\nID:3;X:-194560;Y:-200064;W:32;H:32;\nID:276;X:-194560;Y:-200032;W:32;H:32;\nID:3;X:-194528;Y:-200064;W:32;H:32;\nID:276;X:-194528;Y:-200032;W:32;H:32;\nID:3;X:-194496;Y:-200064;W:32;H:32;\nID:276;X:-194496;Y:-200032;W:32;H:32;\nID:3;X:-194464;Y:-200064;W:32;H:32;\nID:276;X:-194464;Y:-200032;W:32;H:32;\nID:3;X:-194432;Y:-200064;W:32;H:32;\nID:276;X:-194432;Y:-200032;W:32;H:32;\nID:3;X:-194400;Y:-200064;W:32;H:32;\nID:276;X:-194400;Y:-200032;W:32;H:32;\nID:3;X:-194368;Y:-200064;W:32;H:32;\nID:276;X:-194368;Y:-200032;W:32;H:32;\nID:3;X:-194336;Y:-200064;W:32;H:32;\nID:276;X:-194336;Y:-200032;W:32;H:32;\nID:3;X:-194304;Y:-200064;W:32;H:32;\nID:276;X:-194304;Y:-200032;W:32;H:32;\nID:3;X:-194272;Y:-200064;W:32;H:32;\nID:276;X:-194272;Y:-200032;W:32;H:32;\nID:3;X:-194240;Y:-200064;W:32;H:32;\nID:276;X:-194240;Y:-200032;W:32;H:32;\nID:3;X:-194208;Y:-200064;W:32;H:32;\nID:276;X:-194208;Y:-200032;W:32;H:32;\nID:3;X:-194176;Y:-200064;W:32;H:32;\nID:276;X:-194176;Y:-200032;W:32;H:32;\nID:3;X:-194144;Y:-200064;W:32;H:32;\nID:276;X:-194144;Y:-200032;W:32;H:32;\nID:3;X:-194112;Y:-200064;W:32;H:32;\nID:276;X:-194112;Y:-200032;W:32;H:32;\nID:3;X:-194080;Y:-200064;W:32;H:32;\nID:276;X:-194080;Y:-200032;W:32;H:32;\nID:3;X:-194048;Y:-200064;W:32;H:32;\nID:276;X:-194048;Y:-200032;W:32;H:32;\nID:3;X:-194016;Y:-200064;W:32;H:32;\nID:276;X:-194016;Y:-200032;W:32;H:32;\nID:3;X:-193984;Y:-200064;W:32;H:32;\nID:276;X:-193984;Y:-200032;W:32;H:32;\nID:3;X:-193952;Y:-200064;W:32;H:32;\nID:276;X:-193952;Y:-200032;W:32;H:32;\nID:3;X:-193920;Y:-200064;W:32;H:32;\nID:276;X:-193920;Y:-200032;W:32;H:32;\nID:3;X:-193888;Y:-200064;W:32;H:32;\nID:276;X:-193888;Y:-200032;W:32;H:32;\nID:3;X:-193856;Y:-200064;W:32;H:32;\nID:276;X:-193856;Y:-200032;W:32;H:32;\nID:3;X:-193824;Y:-200064;W:32;H:32;\nID:276;X:-193824;Y:-200032;W:32;H:32;\nID:3;X:-193792;Y:-200064;W:32;H:32;\nID:276;X:-193792;Y:-200032;W:32;H:32;\nID:3;X:-193760;Y:-200064;W:32;H:32;\nID:276;X:-193760;Y:-200032;W:32;H:32;\nID:3;X:-193728;Y:-200064;W:32;H:32;\nID:276;X:-193728;Y:-200032;W:32;H:32;\nID:3;X:-193696;Y:-200064;W:32;H:32;\nID:276;X:-193696;Y:-200032;W:32;H:32;\nID:3;X:-193664;Y:-200064;W:32;H:32;\nID:276;X:-193664;Y:-200032;W:32;H:32;\nID:3;X:-193632;Y:-200064;W:32;H:32;\nID:276;X:-193632;Y:-200032;W:32;H:32;\nID:3;X:-193600;Y:-200064;W:32;H:32;\nID:276;X:-193600;Y:-200032;W:32;H:32;\nID:3;X:-193568;Y:-200064;W:32;H:32;\nID:276;X:-193568;Y:-200032;W:32;H:32;\nID:3;X:-193536;Y:-200064;W:32;H:32;\nID:276;X:-193536;Y:-200032;W:32;H:32;\nID:3;X:-193504;Y:-200064;W:32;H:32;\nID:276;X:-193504;Y:-200032;W:32;H:32;\nID:3;X:-193472;Y:-200064;W:32;H:32;\nID:276;X:-193472;Y:-200032;W:32;H:32;\nID:3;X:-193440;Y:-200064;W:32;H:32;\nID:276;X:-193440;Y:-200032;W:32;H:32;\nID:3;X:-193408;Y:-200064;W:32;H:32;\nID:276;X:-193408;Y:-200032;W:32;H:32;\nID:3;X:-193376;Y:-200064;W:32;H:32;\nID:276;X:-193376;Y:-200032;W:32;H:32;\nID:3;X:-193344;Y:-200064;W:32;H:32;\nID:276;X:-193344;Y:-200032;W:32;H:32;\nID:3;X:-193312;Y:-200064;W:32;H:32;\nID:276;X:-193312;Y:-200032;W:32;H:32;\nID:3;X:-193280;Y:-200064;W:32;H:32;\nID:276;X:-193280;Y:-200032;W:32;H:32;\nID:3;X:-193248;Y:-200064;W:32;H:32;\nID:276;X:-193248;Y:-200032;W:32;H:32;\nID:3;X:-193216;Y:-200064;W:32;H:32;\nID:276;X:-193216;Y:-200032;W:32;H:32;\nID:3;X:-193184;Y:-200064;W:32;H:32;\nID:276;X:-193184;Y:-200032;W:32;H:32;\nID:3;X:-193152;Y:-200064;W:32;H:32;\nID:276;X:-193152;Y:-200032;W:32;H:32;\nID:3;X:-193120;Y:-200064;W:32;H:32;\nID:276;X:-193120;Y:-200032;W:32;H:32;\nID:3;X:-193088;Y:-200064;W:32;H:32;\nID:276;X:-193088;Y:-200032;W:32;H:32;\nID:3;X:-193056;Y:-200064;W:32;H:32;\nID:276;X:-193056;Y:-200032;W:32;H:32;\nID:3;X:-193024;Y:-200064;W:32;H:32;\nID:276;X:-193024;Y:-200032;W:32;H:32;\nID:3;X:-192992;Y:-200064;W:32;H:32;\nID:276;X:-192992;Y:-200032;W:32;H:32;\nID:3;X:-192960;Y:-200064;W:32;H:32;\nID:276;X:-192960;Y:-200032;W:32;H:32;\nID:3;X:-192928;Y:-200064;W:32;H:32;\nID:276;X:-192928;Y:-200032;W:32;H:32;\nID:3;X:-192896;Y:-200064;W:32;H:32;\nID:276;X:-192896;Y:-200032;W:32;H:32;\nID:3;X:-192864;Y:-200064;W:32;H:32;\nID:276;X:-192864;Y:-200032;W:32;H:32;\nID:3;X:-192832;Y:-200064;W:32;H:32;\nID:276;X:-192832;Y:-200032;W:32;H:32;\nID:3;X:-192800;Y:-200064;W:32;H:32;\nID:276;X:-192800;Y:-200032;W:32;H:32;\nID:3;X:-192768;Y:-200064;W:32;H:32;\nID:276;X:-192768;Y:-200032;W:32;H:32;\nID:3;X:-192736;Y:-200064;W:32;H:32;\nID:276;X:-192736;Y:-200032;W:32;H:32;\nID:3;X:-192704;Y:-200064;W:32;H:32;\nID:276;X:-192704;Y:-200032;W:32;H:32;\nID:3;X:-192672;Y:-200064;W:32;H:32;\nID:276;X:-192672;Y:-200032;W:32;H:32;\nID:3;X:-192640;Y:-200064;W:32;H:32;\nID:276;X:-192640;Y:-200032;W:32;H:32;\nID:3;X:-192608;Y:-200064;W:32;H:32;\nID:276;X:-192608;Y:-200032;W:32;H:32;\nID:3;X:-192576;Y:-200064;W:32;H:32;\nID:276;X:-192576;Y:-200032;W:32;H:32;\nID:3;X:-192544;Y:-200064;W:32;H:32;\nID:276;X:-192544;Y:-200032;W:32;H:32;\nID:3;X:-192512;Y:-200064;W:32;H:32;\nID:276;X:-192512;Y:-200032;W:32;H:32;\nID:3;X:-192480;Y:-200064;W:32;H:32;\nID:276;X:-192480;Y:-200032;W:32;H:32;\nID:3;X:-192448;Y:-200064;W:32;H:32;\nID:276;X:-192448;Y:-200032;W:32;H:32;\nID:3;X:-192416;Y:-200064;W:32;H:32;\nID:276;X:-192416;Y:-200032;W:32;H:32;\nID:3;X:-192384;Y:-200064;W:32;H:32;\nID:276;X:-192384;Y:-200032;W:32;H:32;\nID:3;X:-192352;Y:-200064;W:32;H:32;\nID:276;X:-192352;Y:-200032;W:32;H:32;\nBLOCK_END\nNPC\nID:88;X:-199866;Y:-200096;D:-1;\nID:88;X:-199834;Y:-200096;D:-1;\nID:88;X:-199802;Y:-200096;D:-1;\nID:88;X:-199770;Y:-200096;D:-1;\nID:88;X:-199738;Y:-200096;D:-1;\nID:88;X:-199706;Y:-200096;D:-1;\nID:88;X:-199674;Y:-200096;D:-1;\nID:88;X:-199642;Y:-200096;D:-1;\nID:88;X:-199610;Y:-200096;D:-1;\nID:88;X:-199578;Y:-200096;D:-1;\nID:88;X:-199546;Y:-200096;D:-1;\nID:88;X:-199514;Y:-200096;D:-1;\nID:88;X:-199482;Y:-200096;D:-1;\nID:88;X:-199450;Y:-200096;D:-1;\nID:88;X:-199418;Y:-200096;D:-1;\nID:88;X:-199386;Y:-200096;D:-1;\nID:88;X:-199354;Y:-200096;D:-1;\nID:88;X:-199322;Y:-200096;D:-1;\nID:88;X:-199290;Y:-200096;D:-1;\nID:88;X:-199258;Y:-200096;D:-1;\nID:88;X:-199226;Y:-200096;D:-1;\nID:88;X:-199194;Y:-200096;D:-1;\nID:88;X:-199162;Y:-200096;D:-1;\nID:88;X:-199130;Y:-200096;D:-1;\nID:88;X:-199098;Y:-200096;D:-1;\nID:88;X:-199066;Y:-200096;D:-1;\nID:88;X:-199034;Y:-200096;D:-1;\nID:88;X:-199002;Y:-200096;D:-1;\nID:88;X:-198970;Y:-200096;D:-1;\nID:88;X:-198938;Y:-200096;D:-1;\nID:88;X:-198906;Y:-200096;D:-1;\nID:88;X:-198874;Y:-200096;D:-1;\nID:88;X:-198842;Y:-200096;D:-1;\nID:88;X:-198810;Y:-200096;D:-1;\nID:88;X:-198778;Y:-200096;D:-1;\nID:88;X:-198746;Y:-200096;D:-1;\nID:88;X:-198714;Y:-200096;D:-1;\nID:88;X:-198682;Y:-200096;D:-1;\nID:88;X:-198650;Y:-200096;D:-1;\nID:88;X:-198618;Y:-200096;D:-1;\nID:88;X:-198586;Y:-200096;D:-1;\nID:88;X:-198554;Y:-200096;D:-1;\nID:88;X:-198522;Y:-200096;D:-1;\nID:88;X:-198490;Y:-200096;D:-1;\nID:88;X:-198458;Y:-200096;D:-1;\nID:88;X:-198426;Y:-200096;D:-1;\nID:88;X:-198394;Y:-200096;D:-1;\nID:88;X:-198362;Y:-200096;D:-1;\nID:88;X:-198330;Y:-200096;D:-1;\nID:88;X:-198298;Y:-200096;D:-1;\nID:88;X:-198266;Y:-200096;D:-1;\nID:88;X:-198234;Y:-200096;D:-1;\nID:88;X:-198202;Y:-200096;D:-1;\nID:88;X:-198170;Y:-200096;D:-1;\nID:88;X:-198138;Y:-200096;D:-1;\nID:88;X:-198106;Y:-200096;D:-1;\nID:88;X:-198074;Y:-200096;D:-1;\nID:88;X:-198042;Y:-200096;D:-1;\nID:88;X:-198010;Y:-200096;D:-1;\nID:88;X:-197978;Y:-200096;D:-1;\nID:88;X:-197946;Y:-200096;D:-1;\nID:88;X:-197914;Y:-200096;D:-1;\nID:88;X:-197882;Y:-200096;D:-1;\nID:88;X:-197850;Y:-200096;D:-1;\nID:88;X:-197818;Y:-200096;D:-1;\nID:88;X:-197786;Y:-200096;D:-1;\nID:88;X:-197754;Y:-200096;D:-1;\nID:88;X:-197722;Y:-200096;D:-1;\nID:88;X:-197690;Y:-200096;D:-1;\nID:88;X:-197658;Y:-200096;D:-1;\nID:88;X:-197626;Y:-200096;D:-1;\nID:88;X:-197594;Y:-200096;D:-1;\nID:88;X:-197562;Y:-200096;D:-1;\nID:88;X:-197530;Y:-200096;D:-1;\nID:88;X:-197498;Y:-200096;D:-1;\nID:88;X:-197466;Y:-200096;D:-1;\nID:88;X:-197434;Y:-200096;D:-1;\nID:88;X:-197402;Y:-200096;D:-1;\nID:88;X:-197370;Y:-200096;D:-1;\nID:88;X:-197338;Y:-200096;D:-1;\nID:88;X:-197306;Y:-200096;D:-1;\nID:88;X:-197274;Y:-200096;D:-1;\nID:88;X:-197242;Y:-200096;D:-1;\nID:88;X:-197210;Y:-200096;D:-1;\nID:88;X:-197178;Y:-200096;D:-1;\nID:88;X:-197146;Y:-200096;D:-1;\nID:88;X:-197114;Y:-200096;D:-1;\nID:88;X:-197082;Y:-200096;D:-1;\nID:88;X:-197050;Y:-200096;D:-1;\nID:88;X:-197018;Y:-200096;D:-1;\nID:88;X:-196986;Y:-200096;D:-1;\nID:88;X:-196954;Y:-200096;D:-1;\nID:88;X:-196922;Y:-200096;D:-1;\nID:88;X:-196890;Y:-200096;D:-1;\nID:88;X:-196858;Y:-200096;D:-1;\nID:88;X:-196826;Y:-200096;D:-1;\nID:88;X:-196794;Y:-200096;D:-1;\nID:88;X:-196762;Y:-200096;D:-1;\nID:88;X:-196730;Y:-200096;D:-1;\nID:88;X:-196698;Y:-200096;D:-1;\nID:88;X:-196666;Y:-200096;D:-1;\nID:88;X:-196634;Y:-200096;D:-1;\nID:88;X:-196602;Y:-200096;D:-1;\nID:88;X:-196570;Y:-200096;D:-1;\nID:88;X:-196538;Y:-200096;D:-1;\nID:88;X:-196506;Y:-200096;D:-1;\nID:88;X:-196474;Y:-200096;D:-1;\nID:88;X:-196442;Y:-200096;D:-1;\nID:88;X:-196410;Y:-200096;D:-1;\nID:88;X:-196378;Y:-200096;D:-1;\nID:88;X:-196346;Y:-200096;D:-1;\nID:88;X:-196314;Y:-200096;D:-1;\nID:88;X:-196282;Y:-200096;D:-1;\nID:88;X:-196250;Y:-200096;D:-1;\nID:88;X:-196218;Y:-200096;D:-1;\nID:88;X:-196186;Y:-200096;D:-1;\nID:88;X:-196154;Y:-200096;D:-1;\nID:88;X:-196122;Y:-200096;D:-1;\nID:88;X:-196090;Y:-200096;D:-1;\nID:88;X:-196058;Y:-200096;D:-1;\nID:88;X:-196026;Y:-200096;D:-1;\nID:88;X:-195994;Y:-200096;D:-1;\nID:88;X:-195962;Y:-200096;D:-1;\nID:88;X:-195930;Y:-200096;D:-1;\nID:88;X:-195898;Y:-200096;D:-1;\nID:88;X:-195866;Y:-200096;D:-1;\nID:88;X:-195834;Y:-200096;D:-1;\nID:88;X:-195802;Y:-200096;D:-1;\nID:88;X:-195770;Y:-200096;D:-1;\nID:88;X:-195738;Y:-200096;D:-1;\nID:88;X:-195706;Y:-200096;D:-1;\nID:88;X:-195674;Y:-200096;D:-1;\nID:88;X:-195642;Y:-200096;D:-1;\nID:88;X:-195610;Y:-200096;D:-1;\nID:88;X:-195578;Y:-200096;D:-1;\nID:88;X:-195546;Y:-200096;D:-1;\nID:88;X:-195514;Y:-200096;D:-1;\nID:88;X:-195482;Y:-200096;D:-1;\nID:88;X:-195450;Y:-200096;D:-1;\nID:88;X:-195418;Y:-200096;D:-1;\nID:88;X:-195386;Y:-200096;D:-1;\nID:88;X:-195354;Y:-200096;D:-1;\nID:88;X:-195322;Y:-200096;D:-1;\nID:88;X:-195290;Y:-200096;D:-1;\nID:88;X:-195258;Y:-200096;D:-1;\nID:88;X:-195226;Y:-200096;D:-1;\nID:88;X:-195194;Y:-200096;D:-1;\nID:88;X:-195162;Y:-200096;D:-1;\nID:88;X:-195130;Y:-200096;D:-1;\nID:88;X:-195098;Y:-200096;D:-1;\nID:88;X:-195066;Y:-200096;D:-1;\nID:88;X:-195034;Y:-200096;D:-1;\nID:88;X:-195002;Y:-200096;D:-1;\nID:88;X:-194970;Y:-200096;D:-1;\nID:88;X:-194938;Y:-200096;D:-1;\nID:88;X:-194906;Y:-200096;D:-1;\nID:88;X:-194874;Y:-200096;D:-1;\nID:88;X:-194842;Y:-200096;D:-1;\nID:88;X:-194810;Y:-200096;D:-1;\nID:88;X:-194778;Y:-200096;D:-1;\nID:88;X:-194746;Y:-200096;D:-1;\nID:88;X:-194714;Y:-200096;D:-1;\nID:88;X:-194682;Y:-200096;D:-1;\nID:88;X:-194650;Y:-200096;D:-1;\nID:88;X:-194618;Y:-200096;D:-1;\nID:88;X:-194586;Y:-200096;D:-1;\nID:88;X:-194554;Y:-200096;D:-1;\nID:88;X:-194522;Y:-200096;D:-1;\nID:88;X:-194490;Y:-200096;D:-1;\nID:88;X:-194458;Y:-200096;D:-1;\nID:88;X:-194426;Y:-200096;D:-1;\nID:88;X:-194394;Y:-200096;D:-1;\nID:88;X:-194362;Y:-200096;D:-1;\nID:88;X:-194330;Y:-200096;D:-1;\nID:88;X:-194298;Y:-200096;D:-1;\nID:88;X:-194266;Y:-200096;D:-1;\nID:88;X:-194234;Y:-200096;D:-1;\nID:88;X:-194202;Y:-200096;D:-1;\nID:88;X:-194170;Y:-200096;D:-1;\nID:88;X:-194138;Y:-200096;D:-1;\nID:88;X:-194106;Y:-200096;D:-1;\nID:88;X:-194074;Y:-200096;D:-1;\nID:88;X:-194042;Y:-200096;D:-1;\nID:88;X:-194010;Y:-200096;D:-1;\nID:88;X:-193978;Y:-200096;D:-1;\nID:88;X:-193946;Y:-200096;D:-1;\nID:88;X:-193914;Y:-200096;D:-1;\nID:88;X:-193882;Y:-200096;D:-1;\nID:88;X:-193850;Y:-200096;D:-1;\nID:88;X:-193818;Y:-200096;D:-1;\nID:88;X:-193786;Y:-200096;D:-1;\nID:88;X:-193754;Y:-200096;D:-1;\nID:88;X:-193722;Y:-200096;D:-1;\nID:88;X:-193690;Y:-200096;D:-1;\nID:88;X:-193658;Y:-200096;D:-1;\nID:88;X:-193626;Y:-200096;D:-1;\nID:88;X:-193594;Y:-200096;D:-1;\nID:88;X:-193562;Y:-200096;D:-1;\nID:88;X:-193530;Y:-200096;D:-1;\nID:88;X:-193498;Y:-200096;D:-1;\nID:88;X:-193466;Y:-200096;D:-1;\nID:88;X:-193434;Y:-200096;D:-1;\nID:88;X:-193402;Y:-200096;D:-1;\nID:88;X:-193370;Y:-200096;D:-1;\nID:88;X:-193338;Y:-200096;D:-1;\nID:88;X:-193306;Y:-200096;D:-1;\nID:88;X:-193274;Y:-200096;D:-1;\nID:88;X:-193242;Y:-200096;D:-1;\nID:88;X:-193210;Y:-200096;D:-1;\nID:88;X:-193178;Y:-200096;D:-1;\nID:88;X:-193146;Y:-200096;D:-1;\nID:88;X:-193114;Y:-200096;D:-1;\nID:88;X:-193082;Y:-200096;D:-1;\nID:88;X:-193050;Y:-200096;D:-1;\nID:88;X:-193018;Y:-200096;D:-1;\nID:88;X:-192986;Y:-200096;D:-1;\nID:88;X:-192954;Y:-200096;D:-1;\nID:88;X:-192922;Y:-200096;D:-1;\nID:88;X:-192890;Y:-200096;D:-1;\nID:88;X:-192858;Y:-200096;D:-1;\nID:88;X:-192826;Y:-200096;D:-1;\nID:88;X:-192794;Y:-200096;D:-1;\nID:88;X:-192762;Y:-200096;D:-1;\nID:88;X:-192730;Y:-200096;D:-1;\nID:88;X:-192698;Y:-200096;D:-1;\nID:88;X:-192666;Y:-200096;D:-1;\nID:88;X:-192634;Y:-200096;D:-1;\nID:88;X:-192602;Y:-200096;D:-1;\nID:88;X:-192570;Y:-200096;D:-1;\nID:88;X:-192538;Y:-200096;D:-1;\nID:88;X:-192506;Y:-200096;D:-1;\nID:88;X:-192474;Y:-200096;D:-1;\nID:88;X:-192442;Y:-200096;D:-1;\nID:88;X:-192410;Y:-200096;D:-1;\nID:88;X:-192378;Y:-200096;D:-1;\nID:11;X:-192352;Y:-200096;D:-1;\nID:103;X:-199582;Y:-200320;D:-1;\nID:103;X:-199262;Y:-200320;D:-1;\nID:103;X:-199262;Y:-200352;D:-1;\nID:103;X:-198942;Y:-200320;D:-1;\nID:103;X:-198942;Y:-200352;D:-1;\nID:103;X:-198942;Y:-200384;D:-1;\nID:103;X:-198622;Y:-200320;D:-1;\nID:103;X:-198622;Y:-200352;D:-1;\nID:103;X:-198622;Y:-200384;D:-1;\nID:103;X:-198622;Y:-200416;D:-1;\nID:103;X:-198302;Y:-200320;D:-1;\nID:103;X:-198302;Y:-200352;D:-1;\nID:103;X:-198302;Y:-200384;D:-1;\nID:103;X:-198302;Y:-200416;D:-1;\nID:103;X:-198302;Y:-200448;D:-1;\nID:103;X:-197982;Y:-200320;D:-1;\nID:103;X:-197982;Y:-200352;D:-1;\nID:103;X:-197982;Y:-200384;D:-1;\nID:103;X:-197982;Y:-200416;D:-1;\nID:103;X:-197982;Y:-200448;D:-1;\nID:103;X:-197982;Y:-200480;D:-1;\nID:103;X:-197662;Y:-200320;D:-1;\nID:103;X:-197662;Y:-200352;D:-1;\nID:103;X:-197662;Y:-200384;D:-1;\nID:103;X:-197662;Y:-200416;D:-1;\nID:103;X:-197662;Y:-200448;D:-1;\nID:103;X:-197662;Y:-200480;D:-1;\nID:103;X:-197662;Y:-200512;D:-1;\nID:103;X:-197342;Y:-200320;D:-1;\nID:103;X:-197342;Y:-200352;D:-1;\nID:103;X:-197342;Y:-200384;D:-1;\nID:103;X:-197342;Y:-200416;D:-1;\nID:103;X:-197342;Y:-200448;D:-1;\nID:103;X:-197342;Y:-200480;D:-1;\nID:103;X:-197342;Y:-200512;D:-1;\nID:103;X:-197342;Y:-200544;D:-1;\nID:103;X:-197022;Y:-200320;D:-1;\nID:103;X:-197022;Y:-200352;D:-1;\nID:103;X:-197022;Y:-200384;D:-1;\nID:103;X:-197022;Y:-200416;D:-1;\nID:103;X:-197022;Y:-200448;D:-1;\nID:103;X:-197022;Y:-200480;D:-1;\nID:103;X:-197022;Y:-200512;D:-1;\nID:103;X:-197022;Y:-200544;D:-1;\nID:103;X:-197022;Y:-200576;D:-1;\nID:258;X:-196700;Y:-200320;D:-1;\nID:258;X:-196380;Y:-200320;D:-1;\nID:103;X:-196382;Y:-200352;D:-1;\nID:103;X:-196062;Y:-200352;D:-1;\nID:258;X:-196060;Y:-200320;D:-1;\nID:103;X:-196062;Y:-200384;D:-1;\nID:103;X:-195742;Y:-200352;D:-1;\nID:103;X:-195742;Y:-200384;D:-1;\nID:258;X:-195740;Y:-200320;D:-1;\nID:258;X:-195420;Y:-200320;D:-1;\nID:258;X:-195100;Y:-200320;D:-1;\nID:258;X:-194780;Y:-200320;D:-1;\nID:258;X:-194460;Y:-200320;D:-1;\nID:258;X:-194140;Y:-200320;D:-1;\nID:258;X:-193820;Y:-200320;D:-1;\nID:103;X:-195422;Y:-200352;D:-1;\nID:103;X:-195422;Y:-200384;D:-1;\nID:103;X:-195422;Y:-200416;D:-1;\nID:103;X:-195102;Y:-200352;D:-1;\nID:103;X:-195102;Y:-200384;D:-1;\nID:103;X:-195102;Y:-200416;D:-1;\nID:103;X:-195102;Y:-200448;D:-1;\nID:103;X:-194782;Y:-200352;D:-1;\nID:103;X:-194782;Y:-200384;D:-1;\nID:103;X:-194782;Y:-200416;D:-1;\nID:103;X:-194782;Y:-200448;D:-1;\nID:103;X:-194782;Y:-200480;D:-1;\nID:103;X:-194462;Y:-200352;D:-1;\nID:103;X:-194462;Y:-200384;D:-1;\nID:103;X:-194462;Y:-200416;D:-1;\nID:103;X:-194462;Y:-200448;D:-1;\nID:103;X:-194462;Y:-200480;D:-1;\nID:103;X:-194462;Y:-200512;D:-1;\nID:103;X:-194142;Y:-200352;D:-1;\nID:103;X:-194142;Y:-200384;D:-1;\nID:103;X:-194142;Y:-200416;D:-1;\nID:103;X:-194142;Y:-200448;D:-1;\nID:103;X:-194142;Y:-200480;D:-1;\nID:103;X:-194142;Y:-200512;D:-1;\nID:103;X:-194142;Y:-200544;D:-1;\nID:103;X:-193822;Y:-200352;D:-1;\nID:103;X:-193822;Y:-200384;D:-1;\nID:103;X:-193822;Y:-200416;D:-1;\nID:103;X:-193822;Y:-200448;D:-1;\nID:103;X:-193822;Y:-200480;D:-1;\nID:103;X:-193822;Y:-200512;D:-1;\nID:103;X:-193822;Y:-200544;D:-1;\nID:103;X:-193822;Y:-200576;D:-1;\nID:103;X:-193502;Y:-200352;D:-1;\nID:103;X:-193502;Y:-200384;D:-1;\nID:103;X:-193502;Y:-200416;D:-1;\nID:103;X:-193502;Y:-200448;D:-1;\nID:103;X:-193502;Y:-200480;D:-1;\nID:103;X:-193502;Y:-200512;D:-1;\nID:103;X:-193502;Y:-200544;D:-1;\nID:103;X:-193502;Y:-200576;D:-1;\nID:103;X:-193502;Y:-200608;D:-1;\nID:258;X:-193500;Y:-200320;D:-1;\nID:258;X:-193180;Y:-200320;D:-1;\nID:258;X:-193180;Y:-200352;D:-1;\nID:258;X:-192860;Y:-200320;D:-1;\nID:258;X:-192860;Y:-200352;D:-1;\nID:258;X:-192540;Y:-200320;D:-1;\nID:258;X:-192540;Y:-200352;D:-1;\nID:103;X:-193182;Y:-200384;D:-1;\nID:103;X:-192862;Y:-200384;D:-1;\nID:103;X:-192862;Y:-200416;D:-1;\nID:103;X:-192542;Y:-200384;D:-1;\nID:103;X:-192542;Y:-200416;D:-1;\nID:103;X:-192542;Y:-200448;D:-1;\nNPC_END\nLAYERS\nLR:\"Default\";\nLR:\"Destroyed Blocks\";HD:1;\nLR:\"Spawned NPCs\";\nLAYERS_END\nEVENTS_CLASSIC\nET:\"Level - Start\";PC:000110000011;AS:0;AX:0;AY:0;\nET:\"P Switch - Start\";AS:0;AX:0;AY:0;\nET:\"P Switch - End\";AS:0;AX:0;AY:0;\nEVENTS_CLASSIC_END\n"
  },
  {
    "path": "test/worlds/speedrun-unit-test/unit-test.wldx",
    "content": "HEAD\nTL:\"Speed-run unit-test\";DC:11101;IT:\"unit-test.lvlx\";CPID:\"SMBX1.3Compat\";\nHEAD_END\nLEVELS\nID:12;X:0;Y:0;LT:\"Auto-runnable level\";LF:\"unit-test.lvlx\";SP:1;\nLEVELS_END\n"
  },
  {
    "path": "utils/convertkit/GIFs2PNG/CMakeLists.txt",
    "content": "cmake_minimum_required (VERSION 3.5)\nproject(MoondustGIFs2PNG LANGUAGES C CXX)\n\ninclude(CheckCXXCompilerFlag)\ninclude(ExternalProject)\ninclude(GNUInstallDirs)\n\nset(CMAKE_INSTALL_RPATH \".\")\nset(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)\n\nset(DEPENDENCIES_INSTALL_DIR ${CMAKE_BINARY_DIR}/output)\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib)\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib)\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/bin)\n\nforeach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})\n    message(\"--> ${OUTPUTCONFIG}\")\n    string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)\n    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} \"${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}\")\n    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} \"${CMAKE_LIBRARY_OUTPUT_DIRECTORY}\")\n    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\")\nendforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES )\n\nset(PGE_INSTALL_DIRECTORY \"GIFs2PNG\")\n\ninclude_directories(\n    ${CMAKE_CURRENT_SOURCE_DIR}\n    ${DEPENDENCIES_INSTALL_DIR}/include\n)\nlink_directories(${DEPENDENCIES_INSTALL_DIR}/lib)\n\nif(NOT CMAKE_BUILD_TYPE)\n    set(CMAKE_BUILD_TYPE \"Release\" CACHE STRING \"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel.\" FORCE)\n    message(\"== Using default build configuration which is a Release!\")\nendif()\n\nif(NOT WIN32 AND (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX))\n    check_cxx_compiler_flag(\"-no-pie\" HAS_NO_PIE)\nendif()\n\nfunction(pge_set_nopie _target)\n    set_target_properties(${_target} PROPERTIES\n        POSITION_INDEPENDENT_CODE False\n    )\n    if(HAS_NO_PIE AND (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX))\n        set_property(TARGET ${_target} APPEND_STRING PROPERTY LINK_FLAGS \" -no-pie\")\n    endif()\nendfunction()\n\n# Version\ninclude(version.cmake)\n# Default GIT version\ninclude(../../../cmake/git_version.cmake)\n# Common building properties and optimization flags\ninclude(../../../cmake/build_props.cmake)\n\ninclude(../../../lib/tclap/tclap.cmake)\ninclude(../../../lib/DirManager/dirman.cmake)\ninclude(../../../lib/Utils/Utils.cmake)\ninclude(../../../lib/FileMapper/FileMapper.cmake)\ninclude(../../../lib/Utf8Main/utf8main.cmake)\ninclude(../../../lib/IniProcessor/IniProcessor.cmake)\ninclude(../../../lib/Graphics/bitmask2rgba.cmake)\n\npge_cxx_standard(14)\n\nset(GIFs2PNG_SRCS)\n\nlist(APPEND GIFs2PNG_SRCS\n    gifs2png.cpp\n    common_features/config_manager.cpp\n)\n\nif(WIN32 AND NOT EMSCRIPTEN)\n    list(APPEND GIFs2PNG_SRCS\n        _resources/gifs2png.rc\n    )\nendif()\n\nadd_executable(GIFs2PNG\n    ${GIFs2PNG_SRCS}\n    ${DIRMANAGER_SRCS}\n    ${FILEMAPPER_SRCS}\n    ${INIPROCESSOR_SRCS}\n    ${UTF8MAIN_SRCS}\n    ${UTILS_SRCS}\n    ${BITMASK2RGBA_SRCS}\n)\n\nif(WIN32 AND NOT EMSCRIPTEN)\n    target_compile_definitions(GIFs2PNG\n        PUBLIC\n        -DUNICODE\n        -D_UNICODE\n        -DNOMINMAX\n    )\n    if(MSVC)\n        target_compile_definitions(GIFs2PNG PRIVATE -DWIN32_CONSOLE)\n    endif()\nendif()\n\nstring(TOLOWER \"${CMAKE_BUILD_TYPE}\" CMAKE_BUILD_TYPE_LOWER)\nif(${CMAKE_BUILD_TYPE_LOWER} STREQUAL \"debug\")\n    target_compile_definitions(GIFs2PNG\n        PRIVATE\n        -DDEBUG_BUILD\n    )\nendif()\n\ninclude(../../../cmake/library_FreeImage.cmake)\n\nadd_dependencies(GIFs2PNG\n    FreeImage_Local\n)\n\nset(GIFs2PNG_LINK_LIBS)\nset(GIFs2PNG_INSTALLS)\n\nlist(APPEND GIFs2PNG_LINK_LIBS\n    ${UTILS_LIBS}\n    PGE_FreeImage\n)\n\nlist(APPEND GIFs2PNG_INSTALLS\n    GIFs2PNG\n)\n\nif(NOT APPLE AND NOT MSVC AND NOT USE_SYSTEM_LIBC)\n    set_property(TARGET GIFs2PNG APPEND_STRING PROPERTY LINK_FLAGS \"-static-libgcc -static-libstdc++\")\nendif()\n\nif(NOT EMSCRIPTEN AND NOT MSVC)\n    if(WIN32)\n        set_target_properties(GIFs2PNG PROPERTIES WIN32_EXECUTABLE OFF)\n        list(APPEND GIFs2PNG_LINK_LIBS \"-static\")\n    endif()\n    find_library(_LIB_PTHREAD pthread)\n    if(_LIB_PTHREAD)\n        list(APPEND GIFs2PNG_LINK_LIBS ${_LIB_PTHREAD})\n    endif()\nendif()\n\ntarget_link_libraries(GIFs2PNG\n    ${GIFs2PNG_LINK_LIBS}\n)\n\npge_set_nopie(GIFs2PNG)\n\ninstall(TARGETS ${GIFs2PNG_INSTALLS}\n        RUNTIME DESTINATION \"${PGE_INSTALL_DIRECTORY}/\"\n)\n"
  },
  {
    "path": "utils/convertkit/GIFs2PNG/_resources/cat_gif2png/make_icns.sh",
    "content": "#!/bin/bash\n\nTYPE=\"${PWD##*/}\"\nif [[ \"$TYPE\" == \"\" ]]; then\necho \"Error: empty name!\"\nexit 1\nfi\n\nmkdir $TYPE\".iconset\"\ncp $TYPE\"_16.png\" $TYPE\".iconset/icon_16x16.png\"\nsips -Z 32 $TYPE\".iconset/icon_16x16.png\" --out $TYPE\".iconset/icon_16x16@2x.png\"\ncp $TYPE\"_32.png\" $TYPE\".iconset/icon_32x32.png\"\nsips -Z 64 $TYPE\".iconset/icon_32x32.png\" --out $TYPE\".iconset/icon_32x32@2x.png\"\ncp $TYPE\"_48.png\" $TYPE\".iconset/icon_48x48.png\"\nsips -Z 96 $TYPE\".iconset/icon_48x48.png\" --out $TYPE\".iconset/icon_48x48@2x.png\"\ncp $TYPE\"_256.png\" $TYPE\".iconset/icon_256x256.png\"\ncp $TYPE\"_256.png\" $TYPE\".iconset/icon_128x128@2x.png\"\nsips -Z 512 $TYPE\".iconset/icon_256x256.png\" --out $TYPE\".iconset/icon_256x256@2x.png\"\n#sips -Z 512 $TYPE\".iconset/icon_256x256.png\" --out $TYPE\".iconset/icon_512x512.png\"\nsips -Z 128 $TYPE\".iconset/icon_256x256.png\" --out $TYPE\".iconset/icon_128x128.png\"\n\necho \"makeIcon...\"\n\niconutil -c icns $TYPE\".iconset\"\nmv $TYPE\".icns\" ../\n\nrm -Rf $TYPE\".iconset\"\n"
  },
  {
    "path": "utils/convertkit/GIFs2PNG/_resources/gifs2png.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/\">\n        <file>cat_gif2png.icns</file>\n        <file>cat_gif2png.ico</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "utils/convertkit/GIFs2PNG/_resources/gifs2png.rc",
    "content": "IDI_ICON1               ICON    DISCARDABLE     \"cat_gif2png.ico\"\n\n#include <windows.h>\n#include \"../version.h\"\n\nVS_VERSION_INFO     VERSIONINFO\nFILEVERSION         V_VF1, V_VF2, V_VF3, V_VF4\nPRODUCTVERSION      V_VP1, V_VP2, V_VP3, V_VP4\nFILEFLAGSMASK       0x3fL\nFILEFLAGS           0\nFILEOS              VOS_NT_WINDOWS32\nFILETYPE            VFT_APP\nFILESUBTYPE         VFT2_UNKNOWN\nBEGIN\nBLOCK   \"VarFileInfo\"\nBEGIN\n    VALUE   \"Translation\",  0x409,  1200\nEND\nBLOCK   \"StringFileInfo\"\nBEGIN\n    BLOCK   \"040904b0\"\n    BEGIN\n        VALUE   \"CompanyName\",      V_COMPANY\n        VALUE   \"FileDescription\",  V_FILE_DESC\n        VALUE   \"FileVersion\",      V_FILE_VERSION V_FILE_RELEASE\n        VALUE   \"InternalName\",     V_INTERNAL_NAME\n        VALUE   \"LegalCopyright\",   V_COPYRIGHT\n        VALUE   \"OriginalFilename\", V_ORIGINAL_NAME\n        VALUE   \"ProductName\",      V_PRODUCT_NAME\n        VALUE   \"ProductVersion\",   V_VERSION V_RELEASE\n    END\nEND\nEND\n"
  },
  {
    "path": "utils/convertkit/GIFs2PNG/common_features/config_manager.cpp",
    "content": "#include \"config_manager.h\"\n\n#include <IniProcessor/ini_processing.h>\n#include <DirManager/dirman.h>\n#include <Utils/files.h>\n#include <algorithm>\n\nstd::string g_ApplicationPath = \"./\";\n\nstatic void addSlashToTail(std::string &str)\n{\n    if(str.empty())\n        return;\n\n    if((str.back() != '/') && (str.back() != '\\\\'))\n        str.push_back('/');\n}\n\nstatic void removeDoubleSlash(std::string &dir)\n{\n    std::string dirN;\n    dirN.reserve(dir.size());\n    char c_prev = 0;\n    for(char c : dir)\n    {\n        if(c != c_prev)\n            dirN.push_back(c);\n        c_prev = (c == '/') ? c : 0;\n    }\n    dir = dirN;\n}\n\nConfigPackMiniManager::ConfigPackMiniManager() :\n    m_is_using(false)\n{}\n\nvoid ConfigPackMiniManager::setConfigDir(const std::string &config_dir)\n{\n    if(config_dir.empty())\n        return;\n\n    DirMan confDir(config_dir);\n\n    if(!confDir.exists())\n        return;\n\n    if(!Files::fileExists(confDir.absolutePath() + \"/main.ini\"))\n        return;\n\n    m_cp_root_path = confDir.absolutePath() + \"/\";\n\n    std::string main_ini = m_cp_root_path + \"main.ini\";\n    IniProcessing mainset(main_ini);\n\n    std::string customAppPath = g_ApplicationPath;\n    customAppPath.push_back('/');\n\n    m_dir_list.clear();\n    m_dir_listUQ.insert(m_cp_root_path);\n\n    mainset.beginGroup(\"main\");\n    {\n        customAppPath = mainset.value(\"application-path\", customAppPath).toString();\n        std::replace(customAppPath.begin(), customAppPath.end(), '\\\\', '/');\n\n        m_cp_root_path = (mainset.value(\"application-dir\", false).toBool() ?\n                              customAppPath + \"/\" : m_cp_root_path + \"/data/\" );\n\n        std::string tmpPath;\n\n        mainset.read(\"graphics-level\", tmpPath, \"data/graphics/level\");\n        addIntoDirList(m_cp_root_path + tmpPath);\n\n        mainset.read(\"graphics-worldmap\", tmpPath, \"data/graphics/worldmap\");\n        addIntoDirList(m_cp_root_path + tmpPath);\n\n        mainset.read(\"graphics-characters\", tmpPath, \"data/graphics/characters\");\n        addIntoDirList(m_cp_root_path + tmpPath);\n\n        mainset.read(\"custom-data\", tmpPath, \"data-custom\");\n        addIntoDirList(m_cp_root_path + tmpPath);\n    }\n    mainset.endGroup();\n\n    m_is_using = true;\n}\n\nbool ConfigPackMiniManager::isUsing()\n{\n    return m_is_using;\n}\n\nvoid ConfigPackMiniManager::addIntoDirList(std::string dir)\n{\n    appendDir(dir);\n    appendDirList(dir);\n}\n\nvoid ConfigPackMiniManager::appendDir(std::string dir)\n{\n    std::replace(dir.begin(), dir.end(), '\\\\', '/');\n    removeDoubleSlash(dir);\n    addSlashToTail(dir);\n    if(m_dir_listUQ.find(dir) == m_dir_listUQ.end())\n    {\n        m_dir_list.push_back(dir);\n        m_dir_listUQ.insert(dir);\n    }\n\n}\n\nvoid ConfigPackMiniManager::appendDirList(const std::string& dir)\n{\n    DirMan dirs(dir);\n    std::vector<std::string> folders;\n    if(dirs.getListOfFolders(folders))\n    {\n        for(std::string &f : folders)\n        {\n            std::string newpath = dirs.absolutePath() + \"/\" + f;\n            appendDir(newpath);\n        }\n    }\n}\n\nstd::string ConfigPackMiniManager::getFile(const std::string &file, std::string customPath, bool *isReadonly)\n{\n    if(!customPath.empty())\n        addSlashToTail(customPath);\n\n    if(!m_is_using)\n    {\n        if(isReadonly)\n            *isReadonly = false;\n        return customPath + file;\n    }\n\n    if(!customPath.empty() && Files::fileExists(customPath + file))\n    {\n        if(isReadonly)\n            *isReadonly = false;\n        return customPath + file;\n    }\n\n    if(isReadonly)\n        *isReadonly = true; //File is not custom, must not be removed!\n\n    for(std::vector<std::string>::reverse_iterator it = m_dir_list.rbegin();\n        it != m_dir_list.rend();\n        it++)\n    {\n        std::string path = *it;\n        addSlashToTail(path);\n        if(Files::fileExists(path + file))\n            return path + file;\n    }\n    return customPath + file;\n}\n"
  },
  {
    "path": "utils/convertkit/GIFs2PNG/common_features/config_manager.h",
    "content": "#pragma once\n#ifndef CONFIGMANAGER_H\n#define CONFIGMANAGER_H\n\n#include <string>\n#include <vector>\n#include <set>\n\nextern std::string g_ApplicationPath;\n\nclass ConfigPackMiniManager\n{\npublic:\n    typedef std::vector<std::string> StringList;\n    ConfigPackMiniManager();\n    ~ConfigPackMiniManager() {}\n\n    /**\n     * @brief Initialize config pack's directory tree\n     * @param config_dir path to config pack's root directory\n     */\n    void setConfigDir(const std::string &config_dir);\n    /**\n     * @brief Is config pack initialized and in use?\n     * @return true if config pack is initialized\n     */\n    bool isUsing();\n\n    /**\n     * @brief Add a data directory with all it's sub-directories with depth 1\n     * @param dir Path to data directory to add\n     */\n    void addIntoDirList(std::string dir);\n\n    /**\n     * @brief Add a single data directory\n     * @param dir Path to data directory to add\n     */\n    void appendDir(std::string dir);\n    /**\n     * @brief Append all sub-directories in this directory\n     * @param dir Path to data directory to add it's sub-directories\n     */\n    void appendDirList(const std::string &dir);\n\n    /**\n     * @brief Find file in added data directories\n     * @param [__in] file file to find\n     * @param [__in] customPath custom directory path\n     * @param [__out] isReadonly Is file a part of config pack (true) or is a custom file which located in same folder as custom Path (false)\n     * @return absolute path to found file. Empty string if no files are found.\n     */\n    std::string getFile(const std::string &file, std::string customPath = \"\", bool *isReadonly = nullptr);\n\nprivate:\n    bool                    m_is_using;\n    std::string             m_cp_root_path;\n    std::string             m_custom_path;\n    //! Unique checking list\n    std::set<std::string>    m_dir_listUQ;\n    //! Ordered list of folderd by priority sequence\n    std::vector<std::string> m_dir_list;\n};\n\n#endif // CONFIGMANAGER_H\n"
  },
  {
    "path": "utils/convertkit/GIFs2PNG/gifs2png.cpp",
    "content": "/*\n * GIFs2PNG, a free tool for merge GIF images with his masks and save into PNG\n * This is a part of the Platformer Game Engine by Wohlstand, a free platform for game making\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <locale>\n#include <iostream>\n#include <set>\n#include <algorithm>\n#include <cctype>\n#include <stdio.h>\n\n#include <FileMapper/file_mapper.h>\n#include <DirManager/dirman.h>\n#include <Utils/files.h>\n#include <Graphics/bitmask2rgba.h>\n#include <Utf8Main/utf8main.h>\n#include <tclap/CmdLine.h>\n#include \"version.h\"\n\n#include <FreeImageLite.h>\n\n#include \"common_features/config_manager.h\"\n\nstatic FIBITMAP *loadImage(const std::string &file, bool convertTo32bit = true)\n{\n#if  defined(__unix__) || defined(__APPLE__) || defined(_WIN32)\n    FileMapper fileMap;\n    if(!fileMap.open_file(file.c_str()))\n        return nullptr;\n\n    FIMEMORY *imgMEM = FreeImage_OpenMemory(reinterpret_cast<unsigned char *>(fileMap.data()),\n                                            (unsigned int)fileMap.size());\n    FREE_IMAGE_FORMAT formato = FreeImage_GetFileTypeFromMemory(imgMEM);\n    if(formato  == FIF_UNKNOWN)\n        return nullptr;\n    FIBITMAP *img = FreeImage_LoadFromMemory(formato, imgMEM, 0);\n    FreeImage_CloseMemory(imgMEM);\n    fileMap.close_file();\n    if(!img)\n        return nullptr;\n#else\n    FREE_IMAGE_FORMAT formato = FreeImage_GetFileType(file.c_str(), 0);\n    if(formato  == FIF_UNKNOWN)\n        return nullptr;\n    FIBITMAP *img = FreeImage_Load(formato, file.c_str());\n    if(!img)\n        return nullptr;\n#endif\n\n    if(convertTo32bit)\n    {\n        FIBITMAP *temp;\n        temp = FreeImage_ConvertTo32Bits(img);\n        if(!temp)\n            return nullptr;\n        FreeImage_Unload(img);\n        img = temp;\n    }\n    return img;\n}\n\nstatic void mergeBitBltToRGBA(FIBITMAP *image, const std::string &pathToMask, FIBITMAP *extMask = nullptr)\n{\n    if(!image)\n        return;\n\n    if(!Files::fileExists(pathToMask) && !extMask)\n        return; //Nothing to do\n\n    FIBITMAP *mask = extMask ? extMask : loadImage(pathToMask);\n\n    if(!mask)\n        return;//Nothing to do\n\n    bitmask_to_rgba(image, mask);\n\n    if(!extMask)\n        FreeImage_Unload(mask);\n}\n\n\nstruct GIFs2PNG_Setup\n{\n    //! Input path (folder)\n    std::string pathIn;\n    //! Is a list of files mode (otherwise, checking entire folder)\n    bool listOfFiles        = false;\n\n    //! Output path (folder where destinition images will be saved)\n    std::string pathOut;\n    //! Put every image into same folder where original is located\n    bool pathOutSame        = false;\n\n    //! Path to configuration package which a source of missing mask files\n    std::string configPath;\n    //! List of masks (which are located in the episode-folder, are dependent to sub-directories and must be deleted after conversion completion)\n    std::set<std::string> deleteLater;\n\n    //! Source images are will be removed after conversion\n    bool removeSource       = false;\n    //! Scan also all subdirectories, otherwise only current folder content will be converted\n    bool walkSubDirs        = false;\n    //! Skip background2-*.gif images (which are rendering buggy in LunaLua in PNG format, in GIF there are valid)\n    bool skipBackground2    = false;\n    //! Count of successfully converted images\n    unsigned int count_success  = 0;\n    //! Count of failed conversions\n    unsigned int count_failed   = 0;\n    //! Count of skipped image conversions\n    unsigned int count_skipped  = 0;\n};\n\nstatic inline void delEndSlash(std::string &dirPath)\n{\n    if(!dirPath.empty())\n    {\n        char last = dirPath[dirPath.size() - 1];\n        if((last == '/') || (last == '\\\\'))\n            dirPath.resize(dirPath.size() - 1);\n    }\n}\n\nstatic inline void getGifMask(std::string &mask, const std::string &front)\n{\n    mask = front;\n    //Make mask filename\n    size_t dotPos = mask.find_last_of('.');\n    if(dotPos == std::string::npos)\n        mask.push_back('m');\n    else\n        mask.insert(mask.begin() + std::string::difference_type(dotPos), 'm');\n}\n\nvoid doGifs2PNG(std::string pathIn,  std::string imgFileIn,\n                std::string pathOut,\n                GIFs2PNG_Setup &setup,\n                ConfigPackMiniManager &cnf)\n{\n    if(Files::hasSuffix(imgFileIn, \"m.gif\"))\n        return; //Skip mask files\n\n    //! Lower-case filename for case-insensitive checks\n    std::string imgFileInL = imgFileIn;\n    std::transform(imgFileInL.begin(), imgFileInL.end(), imgFileInL.begin(),\n                   [](unsigned char c){ return std::tolower(c); });\n\n    std::string imgPathIn = pathIn + \"/\" + imgFileIn;\n    std::string maskPathIn;\n\n    std::cout << imgPathIn;\n    std::cout.flush();\n\n    bool isBackground2 = (imgFileInL.compare(0, 11, \"background2\", 11) == 0);\n\n    if(setup.skipBackground2 && isBackground2)\n    {\n        setup.count_skipped++;\n        std::cout << \"...SKIP!\\n\";\n        std::cout.flush();\n        return;\n    }\n\n    std::string maskFileIn;\n    bool maskIsReadOnly = false;\n    getGifMask(maskFileIn, imgFileIn);\n\n    maskPathIn = cnf.getFile(maskFileIn, pathIn, &maskIsReadOnly);\n\n    FIBITMAP *image = loadImage(imgPathIn);\n    if(!image)\n    {\n        setup.count_failed++;\n        std::cout << \"...CAN'T OPEN!\\n\";\n        std::cout.flush();\n        return;\n    }\n\n    bool isFail = false;\n    bool maskIsExists = Files::fileExists(maskPathIn);\n\n    if(maskIsExists) /* When mask exists, use it */\n        mergeBitBltToRGBA(image, maskPathIn);\n    else if(!isBackground2)/* Try to find the PNG as source of the mask, except of backgrounds */\n    {\n        maskFileIn = Files::changeSuffix(imgFileIn, \".png\");\n        maskPathIn = cnf.getFile(maskFileIn);\n        std::cout << \".chkPNG.\";\n        if(Files::fileExists(maskPathIn))\n        {\n            FIBITMAP *front = loadImage(maskPathIn);\n            if(front)\n            {\n                FIBITMAP *mask = nullptr;\n                std::cout << \".PNG-AS-MASK.\";\n                bitmask_get_mask_from_rgba(front, &mask);\n                FreeImage_Unload(front);\n                mergeBitBltToRGBA(image, \"\", mask);\n                FreeImage_Unload(mask);\n            }\n            else\n            {\n                std::cout << \".NO-MASK.\";\n            }\n        }\n    }\n\n    if(image)\n    {\n        std::string outPath = pathOut + \"/\" + Files::changeSuffix(imgFileIn, \".png\");\n        int ret = FreeImage_Save(FIF_PNG, image, outPath.c_str());\n        if(!ret)\n        {\n            std::cout << \"...F-WRT FAILED!\\n\";\n            isFail = true;\n        }\n        FreeImage_Unload(image);\n    }\n\n    if(isFail)\n    {\n        setup.count_failed++;\n        std::cout << \"...FAILED!\\n\";\n    }\n    else\n    {\n        setup.count_success++;\n        if(setup.removeSource)// Detele old files\n        {\n            if(Files::deleteFile(imgPathIn))\n                std::cout << \".F-DEL.\";\n            //Try to delete or delete-late mask if it is exist and is not read-only\n            if(maskIsExists && !maskIsReadOnly)\n            {\n                /* Delete-Later if mask file is stored in the root of episode directory.\n                   Mask file is dependent to images are inside the subfolder */\n                if(!setup.listOfFiles && setup.walkSubDirs && (setup.pathIn == Files::dirname(maskPathIn)))\n                    setup.deleteLater.insert(maskPathIn);\n                else if(Files::deleteFile(maskPathIn)) //Or just delete the mask file\n                    std::cout << \".M-DEL.\";\n            }\n        }\n        std::cout << \"...done\\n\";\n    }\n\n    std::cout.flush();\n}\n\n\nint main(int argc, char *argv[])\n{\n    if(argc > 0)\n        g_ApplicationPath = Files::dirname(argv[0]);\n    g_ApplicationPath = DirMan(g_ApplicationPath).absolutePath();\n\n    DirMan imagesDir;\n    std::vector<std::string> fileList;\n    FreeImage_Initialise();\n    ConfigPackMiniManager config;\n\n    GIFs2PNG_Setup setup;\n\n    try\n    {\n        // Define the command line object.\n        TCLAP::CmdLine  cmd(V_FILE_DESC \"\\n\"\n                            \"Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\\n\"\n                            \"This program is distributed under the GNU GPLv3+ license\\n\", ' ', V_FILE_VERSION V_FILE_RELEASE);\n\n        TCLAP::SwitchArg switchRemove(\"r\", \"remove\", \"Remove source images after a succesful conversion\", false);\n        TCLAP::SwitchArg switchSkipBG(\"b\", \"ingnore-bg\", \"Skip all \\\"background2-*.gif\\\" sprites\", false);\n        TCLAP::SwitchArg switchDigRecursive(\"d\", \"dig-recursive\", \"Look for images in subdirectories\", false);\n        TCLAP::SwitchArg switchDigRecursiveDEP(\"w\", \"dig-recursive-old\", \"Look for images in subdirectories [deprecated]\", false);\n\n        TCLAP::ValueArg<std::string> outputDirectory(\"O\", \"output\",\n                \"path to a directory where the PNG images will be saved\",\n                false, \"\", \"/path/to/output/directory/\");\n        TCLAP::ValueArg<std::string> configDirectory(\"C\", \"config\",\n                \"Allow usage of default masks from specific PGE config pack \"\n                \"(Useful for cases where the GFX designer didn't make a mask image)\",\n                false, \"\", \"/path/to/config/pack\");\n        TCLAP::UnlabeledMultiArg<std::string> inputFileNames(\"filePath(s)\",\n                \"Input GIF file(s)\",\n                true,\n                \"Input file path(s)\");\n\n        cmd.add(&switchRemove);\n        cmd.add(&switchSkipBG);\n        cmd.add(&switchDigRecursive);\n        cmd.add(&switchDigRecursiveDEP);\n        cmd.add(&outputDirectory);\n        cmd.add(&configDirectory);\n        cmd.add(&inputFileNames);\n\n        cmd.parse(argc, argv);\n\n        setup.removeSource      = switchRemove.getValue();\n        setup.skipBackground2 = switchSkipBG.getValue();\n        setup.walkSubDirs     = switchDigRecursive.getValue() | switchDigRecursiveDEP.getValue();\n        //nopause         = switchNoPause.getValue();\n\n        setup.pathOut     = outputDirectory.getValue();\n        setup.configPath  = configDirectory.getValue();\n\n        for(const std::string &fpath : inputFileNames.getValue())\n        {\n            if(Files::hasSuffix(fpath, \"m.gif\"))\n                continue;\n            else if(DirMan::exists(fpath))\n                setup.pathIn = fpath;\n            else\n            {\n                fileList.push_back(fpath);\n                setup.listOfFiles = true;\n            }\n        }\n\n        if((argc <= 1) || (setup.pathIn.empty() && !setup.listOfFiles))\n        {\n            fprintf(stderr, \"\\n\"\n                    \"ERROR: Missing input files!\\n\"\n                    \"Type \\\"%s --help\\\" to display usage.\\n\\n\", argv[0]);\n            return 2;\n        }\n    }\n    catch(TCLAP::ArgException &e)   // catch any exceptions\n    {\n        std::cerr << \"Error: \" << e.error() << \" for arg \" << e.argId() << std::endl;\n        return 2;\n    }\n\n    fprintf(stderr, \"============================================================================\\n\"\n            \"Pair of GIFs to PNG converter tool by Wohlstand. Version \" V_FILE_VERSION V_FILE_RELEASE \"\\n\"\n            \"============================================================================\\n\"\n            \"This program is distributed under the GNU GPLv3 license \\n\"\n            \"============================================================================\\n\");\n    fflush(stderr);\n\n    if(!setup.listOfFiles)\n    {\n        if(setup.pathIn.empty())\n            goto WrongInputPath;\n        if(!DirMan::exists(setup.pathIn))\n            goto WrongInputPath;\n\n        imagesDir.setPath(setup.pathIn);\n        setup.pathIn = imagesDir.absolutePath();\n    }\n\n    if(!setup.pathOut.empty())\n    {\n        if(!DirMan::exists(setup.pathOut) && !DirMan::mkAbsPath(setup.pathOut))\n            goto WrongOutputPath;\n\n        setup.pathOut = DirMan(setup.pathOut).absolutePath();\n        setup.pathOutSame   = false;\n    }\n    else\n    {\n        setup.pathOut       = setup.pathIn;\n        setup.pathOutSame   = true;\n    }\n\n    delEndSlash(setup.pathIn);\n    delEndSlash(setup.pathOut);\n\n    printf(\"============================================================================\\n\"\n           \"Converting images...\\n\"\n           \"============================================================================\\n\");\n    fflush(stdout);\n\n    if(!setup.listOfFiles)\n        std::cout << (\"Input path:  \" + setup.pathIn + \"\\n\");\n\n    std::cout << (\"Output path: \" + setup.pathOut + \"\\n\");\n\n    std::cout << \"============================================================================\\n\";\n    std::cout.flush();\n\n    config.setConfigDir(setup.configPath);\n\n    if(config.isUsing())\n    {\n        std::cout << \"============================================================================\\n\";\n        std::cout << (\"Used config pack: \" + setup.configPath + \"\\n\");\n        if(!setup.listOfFiles)\n        {\n            config.appendDir(setup.pathIn);\n            std::cout << (\"With episode directory: \" + setup.pathIn + \"\\n\");\n        }\n        std::cout << \"============================================================================\\n\";\n        std::cout.flush();\n    }\n\n    if(setup.listOfFiles)// Process a list of flies\n    {\n        for(std::string &file : fileList)\n        {\n            std::string fname   = Files::basename(file);\n            setup.pathIn = DirMan(Files::dirname(file)).absolutePath();\n            if(setup.pathOutSame)\n                setup.pathOut = DirMan(Files::dirname(file)).absolutePath();\n            doGifs2PNG(setup.pathIn, fname , setup.pathOut, setup, config);\n        }\n    }\n    else // Process directories with a source files\n    {\n        imagesDir.getListOfFiles(fileList, {\".gif\"});\n        if(!setup.walkSubDirs) //By directories\n        {\n            for(std::string &fname : fileList)\n                doGifs2PNG(setup.pathIn, fname, setup.pathOut, setup, config);\n        }\n        else\n        {\n            imagesDir.beginWalking({\".gif\"});\n            std::string curPath;\n            while(imagesDir.fetchListFromWalker(curPath, fileList))\n            {\n                if(Files::hasSuffix(curPath, \"/_backup\"))\n                    continue; //Skip LazyFix's backup directories\n                for(std::string &file : fileList)\n                {\n                    if(setup.pathOutSame)\n                        setup.pathOut = curPath;\n                    doGifs2PNG(curPath, file, setup.pathOut, setup, config);\n                }\n            }\n        }\n    }\n\n    if(!setup.deleteLater.empty())\n    {\n        printf(\"======================Deleting old files...=================================\\n\");\n        fflush(stdout);\n        for(const std::string &s : setup.deleteLater)\n        {\n            std::cout << s << \"...\";\n            std::cout.flush();\n            if(Files::deleteFile(s))\n                std::cout << \".DEL.\";\n            std::cout << \"\\n\";\n            std::cout.flush();\n        }\n    }\n\n    printf(\"============================================================================\\n\"\n           \"                      Conversion has been completed!\\n\"\n           \"============================================================================\\n\"\n           \"Successfully merged:        %d\\n\"\n           \"Conversion failed:          %d\\n\"\n           \"Skipped files (bg2-*):      %d\\n\"\n           \"\\n\",\n           setup.count_success,\n           setup.count_failed,\n           setup.count_skipped);\n    fflush(stdout);\n    return (setup.count_failed == 0) ? 0 : 1;\n\nWrongInputPath:\n    std::cout.flush();\n    std::cerr.flush();\n    printf(\"============================================================================\\n\"\n           \"                           Wrong input path!\\n\"\n           \"============================================================================\\n\");\n    fflush(stdout);\n    return 2;\n\nWrongOutputPath:\n    std::cout.flush();\n    std::cerr.flush();\n    printf(\"============================================================================\\n\"\n           \"                          Wrong output path!\\n\"\n           \"============================================================================\\n\");\n    fflush(stdout);\n    return 2;\n}\n"
  },
  {
    "path": "utils/convertkit/GIFs2PNG/version.cmake",
    "content": "# Major\nset(GIFS2PNG_VERSION_1 3)\n# Minor\nset(GIFS2PNG_VERSION_2 1)\n# Revision\nset(GIFS2PNG_VERSION_3 0)\n# Patch\nset(GIFS2PNG_VERSION_4 0)\n# Type of version: \"-alpha\",\"-beta\",\"-dev\", or \"\" aka \"release\"\nset(GIFS2PNG_VERSION_REL \"\")\n\nadd_definitions(-DGIFS2PNG_VERSION_1=${GIFS2PNG_VERSION_1})\nadd_definitions(-DGIFS2PNG_VERSION_2=${GIFS2PNG_VERSION_2})\nadd_definitions(-DGIFS2PNG_VERSION_3=${GIFS2PNG_VERSION_3})\nadd_definitions(-DGIFS2PNG_VERSION_4=${GIFS2PNG_VERSION_4})\nadd_definitions(-DGIFS2PNG_VERSION_REL=${GIFS2PNG_VERSION_REL})\n\nset(GIFS2PNG_VERSION_STRING \"${GIFS2PNG_VERSION_1}.${GIFS2PNG_VERSION_2}\")\n\nif(NOT ${GIFS2PNG_VERSION_3} EQUAL 0 OR NOT ${GIFS2PNG_VERSION_4} EQUAL 0)\n    string(CONCAT GIFS2PNG_VERSION_STRING \"${GIFS2PNG_VERSION_STRING}\" \".${GIFS2PNG_VERSION_3}\")\nendif()\n\nif(NOT ${GIFS2PNG_VERSION_4} EQUAL 0)\n    string(CONCAT GIFS2PNG_VERSION_STRING \"${GIFS2PNG_VERSION_STRING}\" \".${GIFS2PNG_VERSION_4}\")\nendif()\n\nif(NOT \"${GIFS2PNG_VERSION_REL}\" STREQUAL \"\")\n    string(CONCAT GIFS2PNG_VERSION_STRING \"${GIFS2PNG_VERSION_STRING}\" \"${GIFS2PNG_VERSION_REL}\")\nendif()\n\nmessage(\"== GIFs2PNG version ${GIFS2PNG_VERSION_STRING} ==\")\n\n"
  },
  {
    "path": "utils/convertkit/GIFs2PNG/version.h",
    "content": "/*\n * GIFs2PNG, a free tool for merge GIF images with his masks and save into PNG\n * This is a part of the Platformer Game Engine by Wohlstand, a free platform for game making\n * Copyright (c) 2017-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../pge_version.h\" //Global Project version file\n\n#pragma once\n#ifndef GIFS2PNG_VERSION_H\n#define GIFS2PNG_VERSION_H\n\n//Version of this program\n\n#ifdef GIFS2PNG_VERSION_1\n#   define V_VF1 GIFS2PNG_VERSION_1\n#else\n#   define V_VF1 0\n#endif\n\n#ifdef GIFS2PNG_VERSION_2\n#   define V_VF2 GIFS2PNG_VERSION_2\n#else\n#   define V_VF2 0\n#endif\n\n#ifdef GIFS2PNG_VERSION_3\n#   define V_VF3 GIFS2PNG_VERSION_3\n#else\n#   define V_VF3 0\n#endif\n\n#ifdef GIFS2PNG_VERSION_4\n#   define V_VF4 GIFS2PNG_VERSION_4\n#else\n#   define V_VF4 0\n#endif\n\n#ifdef GIFS2PNG_VERSION_REL\n#   define V_FILE_RELEASE STR_VALUE(GIFS2PNG_VERSION_REL)\n#else\n#   define V_FILE_RELEASE \"-unk\" //\"-alpha\",\"-beta\",\"-dev\", or \"\" aka \"release\"\n#endif\n\n\n#define V_VF1_s STR_VALUE(V_VF1)\n#define V_VF2_s STR_VALUE(V_VF2)\n#define V_VF3_s STR_VALUE(V_VF3)\n#define V_VF4_s STR_VALUE(V_VF4)\n#if V_VF4 == 0\n    #if V_VF3 == 0\n        #define V_FILE_VERSION_NUM GEN_VERSION_NUMBER_2(V_VF1_s, V_VF2_s)\n    #else\n        #define V_FILE_VERSION_NUM GEN_VERSION_NUMBER_3(V_VF1_s, V_VF2_s, V_VF3_s)\n    #endif\n#else\n    #define V_FILE_VERSION_NUM GEN_VERSION_NUMBER_4(V_VF1_s, V_VF2_s, V_VF3_s, V_VF4_s)\n#endif\n\n//Version of this program\n#define V_FILE_VERSION V_FILE_VERSION_NUM\n\n#define V_FILE_DESC \"Bit-Mask GIFs pair to PNG with transparency converter by Wohlstand\"\n\n#define V_INTERNAL_NAME \"GIFs2PNG\"\n\n#ifdef _WIN32\n\t#define V_ORIGINAL_NAME \"GIFs2PNG.exe\" // for Windows platforms\n#else\n\t#define V_ORIGINAL_NAME \"GIFs2PNG\" // for any other platforms\n#endif\n\n//Uncomment this for enable detal logging\n//#define DEBUG\n\n#endif\n"
  },
  {
    "path": "utils/convertkit/assets-convert-homebrew.py",
    "content": "#!/usr/bin/python3\n\n# this should be fixed later but currently delegates to the other two scripts in the directory\n\nimport os\nimport sys\nimport subprocess\nimport argparse\n\nparser = argparse.ArgumentParser(\n    description='Converts TheXTech assets for use on Nintendo homebrew devices')\n\nparser.add_argument('input')\nparser.add_argument('output')\nparser.add_argument('-t', '--target', choices=['3ds', 'wii'], required=True)\n\nargs = parser.parse_args()\n\nscript_directory = os.path.dirname(os.path.abspath(sys.argv[0]))\n\nif args.target == '3ds':\n    subprocess.run(['python3', os.path.join(script_directory, 'gfx-convert-3ds.py'), args.input, args.output])\nelif args.target == 'wii':\n    subprocess.run(['python3', os.path.join(script_directory, 'gfx-convert-wii.py'), args.input, args.output])\n"
  },
  {
    "path": "utils/convertkit/audio_convert_16m.py",
    "content": "#!/usr/bin/python3\n\nimport os\nimport sys\nimport shutil\nimport configparser\n\ndef convert_audio(datadir, outdir):\n    music_filenames = {}\n    sound_filenames = {}\n\n    os.makedirs(os.path.join(outdir, 'temp', 'music'), exist_ok=True)\n    os.makedirs(os.path.join(outdir, 'temp', 'sound'), exist_ok=True)\n\n    # load the INI files\n    for filenames_dict, ini_filename in [(music_filenames, 'music.ini'), (sound_filenames, 'sounds.ini')]:\n        ini = configparser.ConfigParser(inline_comment_prefixes=';')\n        ini.read(os.path.join(datadir, ini_filename))\n\n        for name in ini.sections():\n            try:\n                file = ini[name]['file']\n            except KeyError:\n                continue\n\n            if len(file) < 3 or file[0] != '\"' or file[-1] != '\"':\n                continue\n\n            file = file[1:-1]\n\n            if ini_filename == \"music.ini\" and '|' in file:\n                file = '|'.join(file.split('|')[:-1])\n\n            filenames_dict[name] = file\n\n    # temporary transformations (spc2it, ogg2wav)\n    for key in list(music_filenames.keys()):\n        filename = music_filenames[key]\n        use_fn = os.path.join(datadir, 'music', filename)\n\n        if os.path.exists(use_fn + '.spc'):\n            filename += '.spc'\n            use_fn += '.spc'\n\n        if os.path.exists(use_fn + '.it'):\n            filename += '.it'\n            use_fn += '.it'\n\n        temp_fn = os.path.join(outdir, 'temp', 'music', filename)\n\n        if filename.endswith('.spc'):\n            shutil.copy(use_fn, temp_fn)\n            os.system(f'spc2it \"{temp_fn}\"')\n            use_fn = temp_fn[:-4] + '.it'\n\n        music_filenames[key] = use_fn\n\n        okay = (music_filenames[key].endswith('.it')\n            or music_filenames[key].endswith('.mod')\n            or music_filenames[key].endswith('.s3m')\n            or music_filenames[key].endswith('.xm'))\n\n        if not okay:\n            del music_filenames[key]\n\n    for key in list(sound_filenames.keys()):\n        filename = sound_filenames[key]\n        use_fn = os.path.join(datadir, 'sound', filename)\n        temp_fn = os.path.join(outdir, 'temp', 'sound', filename)\n\n        if filename.endswith('.ogg'):\n            os.system(f'ffmpeg -i \"{use_fn}\" -ac 1 -ar 24000 -c:a pcm_u8 -y \"{temp_fn}.wav\"')\n            use_fn = temp_fn + '.wav'\n\n        sound_filenames[key] = use_fn\n\n        okay = (sound_filenames[key].endswith('.wav'))\n\n        if not okay:\n            del sound_filenames[key]\n\n    # make the mmutil invocation\n    mmutil_invoke = f'mmutil -d \"-o{outdir}soundbank.bin\" \"-c{outdir}soundbank.ini\"'\n    for filename in set(music_filenames.values()):\n        mmutil_invoke += f' \"{filename}\"'\n\n    for filename in set(sound_filenames.values()):\n        mmutil_invoke += f' \"{filename}\"'\n\n    os.system(mmutil_invoke)\n\n    # update the INI files\n    mmutil_out = configparser.ConfigParser(inline_comment_prefixes=';')\n    mmutil_out.read(os.path.join(outdir, 'soundbank.ini'))\n\n    for filenames_dict, ini_filename, group_name, resolved_name in [(music_filenames, 'music.ini', 'modules', 'resolved-mod'), (sound_filenames, 'sounds.ini', 'samples', 'resolved-sfx')]:\n        ini = configparser.ConfigParser(inline_comment_prefixes=';')\n        ini.read(os.path.join(datadir, ini_filename))\n\n        for key in filenames_dict.keys():\n            ini[key][resolved_name] = mmutil_out[group_name][filenames_dict[key]]\n\n        ini.write(open(os.path.join(outdir, ini_filename), 'w'))\n\n    # delete temp files\n    os.remove(os.path.join(outdir, 'soundbank.ini'))\n    shutil.rmtree(os.path.join(outdir, 'temp'))\n"
  },
  {
    "path": "utils/convertkit/convert_plr.py",
    "content": "import os, sys\n\ndef blit(fn, sx, sy, dx, dy, w, h, flip):\n    flip = '-flop ' if flip else ''\n    return f'\\\\( \"{fn}\" -crop {w}x{h}+{sx}+{sy} +repage {flip}\\\\) -geometry +{dx}+{dy} -composite '\n\ndef check_trim(fn, sx, sy, w, h):\n    return w - int(os.popen(f'convert \"{fn}\" -crop {w}x{h}+{sx}+{sy} +repage -define trim:edges=east -trim -format \\\"%w\\\" info:').read())\n\n\ndef do_sheet(fn, frames, out, w, h, cols, rows, make_flippable=False):\n    ow = 100\n    oh = 100\n    ocols = 10\n    orows = 10\n\n    cmd = f'convert -size {w*cols}x{h*rows*2} xc:none -compose Replace -gravity NorthWest -define trim:edges=east '\n\n    for df, sf in enumerate(frames):\n        for dir in [-1, 1]:\n            o_coord = dir * sf + 49\n            sx = ow * (o_coord // orows)\n            sy = oh * (o_coord % orows)\n\n            dx = w * (df // rows)\n            dy = h * (df % rows)\n\n            flip = False\n\n            dw = w\n\n            if dir == -1:\n                dx = (cols - 1) * w - dx\n                dy += rows * h\n\n                if make_flippable:\n                    flip = (sf == 0)\n\n                    if not flip:\n                        trim = check_trim(fn, sx, sy, w, h)\n                        print(f'lo-{sf} = {trim}')\n                        dx += trim\n                        dw -= trim\n\n                    if sf == 25 or sf == 26:\n                        flip = True\n                elif sf == 0:\n                    continue\n\n            cmd += blit(fn, sx, sy, dx, dy, dw, h, flip)\n\n    cmd += '\"' + out + '\"'\n\n    cmd.replace('\\'', '\\\\\\'')\n\n    print(cmd)\n\n    return cmd\n\ndef do_sheet_nonlink(fn, outfn):\n    return do_sheet(fn, list(range(0,33))+list(range(40,45)), outfn, 48, 64, 10, 4)\n\ndef do_sheet_link(fn, outfn):\n    return do_sheet(fn, range(1,17), outfn, 64, 64, 4, 4)\n\nif __name__ == '__main__':\n    if len(sys.argv) > 1:\n        fns = sys.argv[1:]\n    else:\n        fns = os.listdir()\n\n    for fn in fns:\n        if 'link' in fn:\n            os.system(do_sheet_link(fn, fn[:-4]+'.out.png'))\n        else:\n            os.system(do_sheet_nonlink(fn, fn[:-4]+'.out.png'))\n"
  },
  {
    "path": "utils/convertkit/dist/gameinfo.ini",
    "content": "[game]\ntitle=\"Super Mario Bros. X\"\n\n[characters]\nname1=\"Mario\"\nname2=\"Luigi\"\nname3=\"Peach\"\nname4=\"Toad\"\nname5=\"Link\"\n"
  },
  {
    "path": "utils/convertkit/dist/gfx-convert-lin.sh",
    "content": "#!/bin/bash\n\necho \"------------------------------------------------------------------------\"\necho \"   All default masked GIF stuff will be converted into PNG\"\necho \"------------------------------------------------------------------------\"\necho \" Do you want to remove all old GIFs [Y]? or keep both GIFs and PNG [N]?\"\necho \"------------------------------------------------------------------------\"\necho \"Answer Y/N ?\"\nread -n 1 REMOVE\n\necho -e \"\\n\\n\"\n\nif [[ \"$REMOVE\" == \"y\" ]]; then\n    REMOVE=-r\nelse\n    REMOVE=\nfi\n\nsmbxexe=\"smbx.exe\"\nif [[ -f \"asmbxt.exe\" ]]; then\n    smbxexe=\"asmbxt.exe\"\nelif [[ -f \"a2mbxt.exe\" ]]; then\n    smbxexe=\"a2mbxt.exe\"\nfi\n\nchmod +x exe2ui-linux-x86_64\necho \"Executing exe2ui ${smbxexe}...\"\n./exe2ui-linux-x86_64 \"${smbxexe}\"\n\nchmod +x GIFs2PNG-linux-x86_64\necho \"Executing ./GIFs2PNG-linux-x86_64 -d $REMOVE graphics ...\"\n./GIFs2PNG-linux-x86_64 -d $REMOVE graphics\n\necho \"Cleaning up from garbage...\"\nfind graphics -name \"npc*.txt\" -delete\nfind graphics -name \"Thumbds.db\" -delete\n\necho -e \"\\n\\n\"\necho \"---------------------------------------------------------------\"\necho \"  Press any key to quit...\"\necho \"---------------------------------------------------------------\"\nread -n 1\n"
  },
  {
    "path": "utils/convertkit/dist/gfx-convert-win.cmd",
    "content": "@echo off\n\necho ------------------------------------------------------------------------\necho    All default masked GIF stuff will be converted into PNG\necho ------------------------------------------------------------------------\necho  Do you want to remove all old GIFs [Y]? or keep both GIFs and PNG [N]?\necho ------------------------------------------------------------------------\nchoice\nif errorlevel 2 set REMOVE=\nif errorlevel 1 set REMOVE=-r\n\nset smbxexe=smbx.exe\nif exist asmbxt.exe set smbxexe=asmbxt.exe\nif exist a2mbxt.exe set smbxexe=a2mbxt.exe\n\necho Executing exe2ui %smbxexe% ...\nexe2ui.exe %smbxexe%\n\necho Executing GIFs2PNG.exe -d %REMOVE% graphics ...\nGIFs2PNG.exe -d %REMOVE% graphics\n\necho Cleaning up from garbage...\ndel /s graphics\\block\\*block-*.txt > NUL\ndel /s graphics\\background\\*background-*.txt > NUL\ndel /s graphics\\npc\\*npc-*.txt > NUL\n\necho .\necho .\necho ---------------------------------------------------------------\necho   Press any key to quit...\necho ---------------------------------------------------------------\npause > NUL\n"
  },
  {
    "path": "utils/convertkit/dist/music.ini",
    "content": "[music-main]\ntotal-world=17\ntotal-level=56\ntotal-special=3\nlevel-custom-music-id=24\nworld-custom-music-id=17\n\n[world-music-1]\nname=\"SMB3 World 1 (Grass land)\"\nfile=\"smb3-world1.mp3\"\n\n\n[world-music-2]\nname=\"SMB3 World 4 (Giant land)\"\nfile=\"smb3-world4.mp3\"\n\n\n[world-music-3]\nname=\"SMB3 World 7 (Pipe land)\"\nfile=\"smb3-world7.mp3\"\n\n\n[world-music-4]\nname=\"SMW: Wandering the Plains\"\nfile=\"smw-worldmap.mp3\"\n\n\n[world-music-5]\nname=\"SSBB SMB3-Remix fragment\"\nfile=\"nsmb-world.mp3\"\n\n\n[world-music-6]\nname=\"SMB3 World 2 (Desert land)\"\nfile=\"smb3-world2.mp3\"\n\n\n[world-music-7]\nname=\"SMW Forest\"\nfile=\"smw-forestofillusion.mp3\"\n\n\n[world-music-8]\nname=\"SMB3 World 3 (Ocean land)\"\nfile=\"smb3-world3.mp3\"\n\n\n[world-music-9]\nname=\"SMB3 World 8 (Dark land)\"\nfile=\"smb3-world8.mp3\"\n\n\n[world-music-10]\nname=\"SMB3 World 6 (Ice land)\"\nfile=\"smb3-world6.mp3\"\n\n\n[world-music-11]\nname=\"SMB3 World 5 (Sky land (Ground))\"\nfile=\"smb3-world5.mp3\"\n\n\n[world-music-12]\nname=\"SMW Special\"\nfile=\"smw-special.mp3\"\n\n\n[world-music-13]\nname=\"SMW Bowser\"\nfile=\"smw-bowserscastle.mp3\"\n\n\n[world-music-14]\nname=\"SMW Star Road\"\nfile=\"smw-starroad.mp3\"\n\n\n[world-music-15]\nname=\"SMW Island\"\nfile=\"smw-yoshisisland.mp3\"\n\n\n[world-music-16]\nname=\"SMW Cave\"\nfile=\"smw-vanilladome.mp3\"\n\n[world-music-17]\nname=\"[custom]\"\nfile=\"dummy\"\n\n[special-music-1]\nname=\"SMW P-Switch\"\nfile=\"smw-switch.mp3\"\n\n\n[special-music-2]\nname=\"SMB3 P-Switch\"\nfile=\"smb3-switch.mp3\"\n\n\n[special-music-3]\nname=\"Credits (SMG Title)\"\nfile=\"smg-title.mp3\"\n\n\n[level-music-1]\nname=\"SMB3 Overworld\"\nfile=\"smb3-overworld.mp3\"\n\n\n[level-music-2]\nname=\"SMB3 Sky\"\nfile=\"smb3-sky.mp3\"\n\n\n[level-music-3]\nname=\"SMB3 Castle\"\nfile=\"smb3-castle.mp3\"\n\n\n[level-music-4]\nname=\"SMB3 Underground\"\nfile=\"smb3-underground.mp3\"\n\n\n[level-music-5]\nname=\"SMB2 Overworld\"\nfile=\"smb2-overworld.mp3\"\n\n\n[level-music-6]\nname=\"SMB3 Battle\"\nfile=\"smb3-boss.mp3\"\n\n\n[level-music-7]\nname=\"SMB Underground\"\nfile=\"smb-underground.mp3\"\n\n\n[level-music-8]\nname=\"Corneria\"\nfile=\"sf-corneria.mp3\"\n\n\n[level-music-9]\nname=\"SMB Overworld\"\nfile=\"smb-overworld.mp3\"\n\n\n[level-music-10]\nname=\"SMW Overworld\"\nfile=\"smw-overworld.mp3\"\n\n\n[level-music-11]\nname=\"Brinstar\"\nfile=\"sm-brinstar.mp3\"\n\n\n[level-music-12]\nname=\"Crateria\"\nfile=\"sm-crateria.mp3\"\n\n\n[level-music-13]\nname=\"New SMB\"\nfile=\"nsmb-overworld.mp3\"\n\n\n[level-music-14]\nname=\"SM64 Desert\"\nfile=\"sm64-desert.mp3\"\n\n\n[level-music-15]\nname=\"SMB2 Boss\"\nfile=\"smb2-boss.mp3\"\n\n\n[level-music-16]\nname=\"SMRPG Forest Maze\"\nfile=\"mariorpg-forestmaze.mp3\"\n\n\n[level-music-17]\nname=\"SMW Ghost House\"\nfile=\"smw-ghosthouse.mp3\"\n\n\n[level-music-18]\nname=\"Beach Bowl\"\nfile=\"smg-beach-bowl-galaxy.mp3\"\n\n\n[level-music-19]\nname=\"Airship Theme\"\nfile=\"ssbb-airship.mp3\"\n\n\n[level-music-20]\nname=\"SMG Star Reactor\"\nfile=\"smg-star-reactor.mp3\"\n\n\n[level-music-21]\nname=\"SMRPG Bowser Battle\"\nfile=\"mariorpg-bowser.mp3\"\n\n\n[level-music-22]\nname=\"Metroid Charge\"\nfile=\"tds-metroid-charge.mp3\"\n\n\n[level-music-23]\nname=\"Lost Woods\"\nfile=\"z3-lost-woods.mp3\"\n\n\n[level-music-24]\nname=\"Custom\"\nfile=\"\"\n\n\n[level-music-25]\nname=\"SMB2 Underground\"\nfile=\"smb2-underground.mp3\"\n\n\n[level-music-26]\nname=\"SM64 Castle\"\nfile=\"mario64-castle.mp3\"\n\n\n[level-music-27]\nname=\"SM64 Main Theme\"\nfile=\"mario64-maintheme.mp3\"\n\n\n[level-music-28]\nname=\"SMW Athletic\"\nfile=\"smw-sky.mp3\"\n\n\n[level-music-29]\nname=\"SMW Cave\"\nfile=\"smw-cave.mp3\"\n\n\n[level-music-30]\nname=\"SMRPG Mario's Pad\"\nfile=\"mariorpg-mariospad.mp3\"\n\n\n[level-music-31]\nname=\"SMRPG Seaside Town\"\nfile=\"mariorpg-seasidetown.mp3\"\n\n\n[level-music-32]\nname=\"SMRPG Tadpole Pond\"\nfile=\"mariorpg-tadpolepond.mp3\"\n\n\n[level-music-33]\nname=\"SMRPG Nimbus Land\"\nfile=\"mariorpg-nimbusland.mp3\"\n\n\n[level-music-34]\nname=\"SMRPG Rose Town\"\nfile=\"mariorpg-rosetown.mp3\"\n\n\n[level-music-35]\nname=\"SM64 Snow\"\nfile=\"mario64-snowmountain.mp3\"\n\n\n[level-music-36]\nname=\"SM64 Boss\"\nfile=\"mario64-boss.mp3\"\n\n\n[level-music-37]\nname=\"Shiver Mnt\"\nfile=\"pm-shiver-mountain.mp3\"\n\n\n[level-music-38]\nname=\"Yoshi's Village\"\nfile=\"pm-yoshis-village.mp3\"\n\n\n[level-music-39]\nname=\"Hyrule Temple\"\nfile=\"ssbb-zelda2.mp3\"\n\n\n[level-music-40]\nname=\"Meta Knight\"\nfile=\"ssbb-meta.mp3\"\n\n\n[level-music-41]\nname=\"SMW Castle\"\nfile=\"smw-castle.mp3\"\n\n\n[level-music-42]\nname=\"SMB Castle\"\nfile=\"smb-castle.mp3\"\n\n\n[level-music-43]\nname=\"SMB2 Wart\"\nfile=\"smb2-wart.mp3\"\n\n\n[level-music-44]\nname=\"Item Room\"\nfile=\"sm-itemroom.mp3\"\n\n\n[level-music-45]\nname=\"Mother Brain\"\nfile=\"sm-brain.mp3\"\n\n\n[level-music-46]\nname=\"SMB Water\"\nfile=\"smb-water.mp3\"\n\n\n[level-music-47]\nname=\"SMB3 Water\"\nfile=\"smb3-water.mp3\"\n\n\n[level-music-48]\nname=\"SMW Water\"\nfile=\"smw-water.mp3\"\n\n\n[level-music-49]\nname=\"SM64 Water\"\nfile=\"mario64-water.mp3\"\n\n\n[level-music-50]\nname=\"SM64 Cave\"\nfile=\"mario64-cave.mp3\"\n\n\n[level-music-51]\nname=\"SMW Boss\"\nfile=\"smw-boss.mp3\"\n\n\n[level-music-52]\nname=\"Underground\"\nfile=\"ssbb-underground.mp3\"\n\n\n[level-music-53]\nname=\"Waluigi\"\nfile=\"ssbb-waluigi.mp3\"\n\n\n[level-music-54]\nname=\"SMB3 Hammer Bros\"\nfile=\"smb3-hammer.mp3\"\n\n\n[level-music-55]\nname=\"Fleet Glide\"\nfile=\"smg2-fg.mp3\"\n\n\n[level-music-56]\nname=\"M. Gorge\"\nfile=\"mkwii-mushroom-gorge.mp3\"\n"
  },
  {
    "path": "utils/convertkit/dist/sounds.ini",
    "content": "[sound-main]\ntotal=91\n\n[sound-1]\nname=\"Jump\"\nfile=\"player-jump.mp3\"\nsingle-channel=1 ;don't dupe sound in another channel\nhidden=0\n\n[sound-2]\nname=\"Stomp\"\nfile=\"stomped.mp3\"\nhidden=0\n\n[sound-3]\nname=\"Block Hit\"\nfile=\"block-hit.mp3\"\nhidden=0\n\n[sound-4]\nname=\"Block Smashed\"\nfile=\"block-smash.mp3\"\nhidden=0\n\n[sound-5]\nname=\"Shrink\"\nfile=\"player-shrink.mp3\"\nhidden=0\n\n[sound-6]\nname=\"Grow\"\nfile=\"player-grow.mp3\"\nhidden=0\n\n[sound-7]\nname=\"Mushroom\"\nfile=\"mushroom.mp3\"\nhidden=0\n\n[sound-8]\nname=\"Player Died\"\nfile=\"player-died.mp3\"\nhidden=0\n\n[sound-9]\nname=\"Shell Kick\"\nfile=\"shell-hit.mp3\"\nsingle-channel=1\nhidden=0\n\n[sound-10]\nname=\"Skid\"\nfile=\"player-slide.mp3\"\nsingle-channel=1\nhidden=0\n\n[sound-11]\nname=\"Drop Item\"\nfile=\"item-dropped.mp3\"\nhidden=0\n\n[sound-12]\nname=\"Got Item\"\nfile=\"has-item.mp3\"\nhidden=0\n\n[sound-13]\nname=\"Camera\"\nfile=\"camera-change.mp3\"\nhidden=0\n\n[sound-14]\nname=\"Coin\"\nfile=\"coin.mp3\"\nhidden=0\n\n[sound-15]\nname=\"1up\"\nfile=\"1up.mp3\"\nhidden=0\n\n[sound-16]\nname=\"Lava\"\nfile=\"lava.mp3\"\nhidden=0\n\n[sound-17]\nname=\"Warp\"\nfile=\"warp.mp3\"\nhidden=0\n\n[sound-18]\nname=\"Fireball\"\nfile=\"fireball.mp3\"\nhidden=0\n\n[sound-19]\nname=\"SMB3 Exit\"\nfile=\"level-win.mp3\"\nhidden=0\n\n[sound-20]\nname=\"Defeat Boss\"\nfile=\"boss-beat.mp3\"\nhidden=0\n\n[sound-21]\nname=\"Dungeon Clear \"\nfile=\"dungeon-win.mp3\"\nhidden=0\n\n[sound-22]\nname=\"Bullet Bill\"\nfile=\"bullet-bill.mp3\"\nhidden=0\n\n[sound-23]\nname=\"Grab\"\nfile=\"grab.mp3\"\nhidden=0\n\n[sound-24]\nname=\"Spring\"\nfile=\"spring.mp3\"\nhidden=0\n\n[sound-25]\nname=\"Hammer Toss\"\nfile=\"hammer.mp3\"\nhidden=0\n\n[sound-26]\nname=\"Slide\"\nfile=\"slide.mp3\"\nhidden=0\n\n[sound-27]\nname=\"New Path\"\nfile=\"newpath.mp3\"\nsingle-channel=1\nhidden=0\n\n[sound-28]\nname=\"Level Select\"\nfile=\"level-select.mp3\"\nhidden=0\n\n[sound-29]\nname=\"Do\"\nfile=\"do.mp3\"\nhidden=0\n\n[sound-30]\nname=\"Pause\"\nfile=\"pause.mp3\"\nhidden=0\n\n[sound-31]\nname=\"Key\"\nfile=\"key.mp3\"\nhidden=0\n\n[sound-32]\nname=\"Switch\"\nfile=\"pswitch.mp3\"\nhidden=0\n\n[sound-33]\nname=\"Tail\"\nfile=\"tail.mp3\"\nsingle-channel=1\nhidden=0\n\n[sound-34]\nname=\"Racoon\"\nfile=\"racoon.mp3\"\nhidden=0\n\n[sound-35]\nname=\"Boot\"\nfile=\"boot.mp3\"\nhidden=0\n\n[sound-36]\nname=\"Smash\"\nfile=\"smash.mp3\"\nsingle-channel=1\nhidden=0\n\n[sound-37]\nname=\"Thwomp\"\nfile=\"thwomp.mp3\"\nhidden=0\n\n[sound-38]\nname=\"Birdo Spit\"\nfile=\"birdo-spit.mp3\"\nhidden=0\n\n[sound-39]\nname=\"Birdo Hit\"\nfile=\"birdo-hit.mp3\"\nhidden=0\n\n[sound-40]\nname=\"SMB2 Exit\"\nfile=\"smb2-exit.mp3\"\nhidden=0\n\n[sound-41]\nname=\"Birdo Beat\"\nfile=\"birdo-beat.mp3\"\nhidden=0\n\n[sound-42]\nname=\"Big Fireball\"\nfile=\"npc-fireball.mp3\"\nhidden=0\n\n[sound-43]\nname=\"Fireworks\"\nfile=\"fireworks.mp3\"\nhidden=0\n\n[sound-44]\nname=\"Bowser Killed\"\nfile=\"bowser-killed.mp3\"\nhidden=0\n\n[sound-45]\nname=\"SMB3 Game Beat\"\nfile=\"game-beat.mp3\"\nhidden=0\n\n[sound-46]\nname=\"Door\"\nfile=\"door.mp3\"\nhidden=0\n\n[sound-47]\nname=\"Message\"\nfile=\"message.mp3\"\nhidden=0\n\n[sound-48]\nname=\"Yoshi\"\nfile=\"yoshi.mp3\"\nhidden=0\n\n[sound-49]\nname=\"Yoshi Hurt\"\nfile=\"yoshi-hurt.mp3\"\nhidden=0\n\n[sound-50]\nname=\"Yoshi Tongue\"\nfile=\"yoshi-tongue.mp3\"\nsingle-channel=1\nhidden=0\n\n[sound-51]\nname=\"Yoshi Egg\"\nfile=\"yoshi-egg.mp3\"\nhidden=0\n\n[sound-52]\nname=\"Got Star Exit\"\nfile=\"got-star.mp3\"\nhidden=0\n\n[sound-53]\nname=\"Zelda Kill\"\nfile=\"zelda-kill.mp3\"\nhidden=0\n\n[sound-54]\nname=\"Player Died 2\"\nfile=\"player-died2.mp3\"\nhidden=0\n\n[sound-55]\nname=\"Yoshi Swallow\"\nfile=\"yoshi-swallow.mp3\"\nhidden=0\n\n[sound-56]\nname=\"Sonic Ring\"\nfile=\"ring.mp3\"\nhidden=0\n\n[sound-57]\nname=\"Dry Bones\"\nfile=\"dry-bones.mp3\"\nhidden=0\n\n[sound-58]\nname=\"SMW Checkpoint\"\nfile=\"smw-checkpoint.mp3\"\nhidden=0\n\n[sound-59]\nname=\"Dragon Coin\"\nfile=\"dragon-coin.mp3\"\nhidden=0\n\n[sound-60]\nname=\"SMW Exit\"\nfile=\"smw-exit.mp3\"\nhidden=0\n\n[sound-61]\nname=\"Blaarg\"\nfile=\"smw-blaarg.mp3\"\nhidden=0\n\n[sound-62]\nname=\"Wart Bubbles\"\nfile=\"wart-bubble.mp3\"\nhidden=0\n\n[sound-63]\nname=\"Wart Killed\"\nfile=\"wart-die.mp3\"\nhidden=0\n\n[sound-64]\nname=\"SM Block Hit\"\nfile=\"sm-block-hit.mp3\"\nhidden=0\n\n[sound-65]\nname=\"SM Killed\"\nfile=\"sm-killed.mp3\"\nhidden=0\n\n[sound-66]\nname=\"SM Hurt\"\nfile=\"sm-hurt.mp3\"\nhidden=0\n\n[sound-67]\nname=\"SM Glass\"\nfile=\"sm-glass.mp3\"\nhidden=0\n\n[sound-68]\nname=\"SM Boss Hit\"\nfile=\"sm-boss-hit.mp3\"\nhidden=0\n\n[sound-69]\nname=\"SM Cry\"\nfile=\"sm-cry.mp3\"\nhidden=0\n\n[sound-70]\nname=\"SM Explosion\"\nfile=\"sm-explosion.mp3\"\nhidden=0\n\n[sound-71]\nname=\"Climbing\"\nfile=\"climbing.mp3\"\nsingle-channel=1\nhidden=0\n\n[sound-72]\nname=\"Swim\"\nfile=\"swim.mp3\"\nsingle-channel=1\nhidden=0\n\n[sound-73]\nname=\"Light Grab\"\nfile=\"grab2.mp3\"\nhidden=0\n\n[sound-74]\nname=\"Saw\"\nfile=\"smw-saw.mp3\"\nhidden=0\n\n[sound-75]\nname=\"SMB2 Throw\"\nfile=\"smb2-throw.mp3\"\nhidden=0\n\n[sound-76]\nname=\"SMB2 Hit\"\nfile=\"smb2-hit.mp3\"\nhidden=0\n\n[sound-77]\nname=\"Zelda Stab\"\nfile=\"zelda-stab.mp3\"\nhidden=0\n\n[sound-78]\nname=\"Zelda Hurt\"\nfile=\"zelda-hurt.mp3\"\nhidden=0\n\n[sound-79]\nname=\"Zelda Heart\"\nfile=\"zelda-heart.mp3\"\nhidden=0\n\n[sound-80]\nname=\"Zelda Died\"\nfile=\"zelda-died.mp3\"\nhidden=0\n\n[sound-81]\nname=\"Zelda Rupee\"\nfile=\"zelda-rupee.mp3\"\nhidden=0\n\n[sound-82]\nname=\"Zelda Fire\"\nfile=\"zelda-fire.mp3\"\nhidden=0\n\n[sound-83]\nname=\"Zelda Item\"\nfile=\"zelda-item.mp3\"\nhidden=0\n\n[sound-84]\nname=\"Zelda Key\"\nfile=\"zelda-key.mp3\"\nhidden=0\n\n[sound-85]\nname=\"Zelda Shield\"\nfile=\"zelda-shield.mp3\"\nhidden=0\n\n[sound-86]\nname=\"Zelda Dash\"\nfile=\"zelda-dash.mp3\"\nhidden=0\n\n[sound-87]\nname=\"Zelda Fairy\"\nfile=\"zelda-fairy.mp3\"\nhidden=0\n\n[sound-88]\nname=\"Zelda Grass\"\nfile=\"zelda-grass.mp3\"\nhidden=0\n\n[sound-89]\nname=\"Zelda Hit\"\nfile=\"zelda-hit.mp3\"\nhidden=1\n\n[sound-90]\nname=\"Zelda Sword Beam\"\nfile=\"zelda-sword-beam.mp3\"\nhidden=1\n\n[sound-91]\nname=\"Bubble\"\nfile=\"bubble.mp3\"\nhidden=1\n"
  },
  {
    "path": "utils/convertkit/dist/thextech.ini",
    "content": "[Main]\nforce-portable = true\n\n"
  },
  {
    "path": "utils/convertkit/exe2ui/exe2ui.c",
    "content": "#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n\n\nstatic unsigned char g_Font2_1_gif[] =\n{\n    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0xdf, 0x03, 0x11, 0x00, 0x80, 0x01,\n    0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x21, 0xfe, 0x11, 0x43, 0x72,\n    0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47,\n    0x49, 0x4d, 0x50, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x03, 0x11,\n    0x00, 0x00, 0x02, 0x9e, 0x84, 0x8f, 0xa9, 0xcb, 0xed, 0x0f, 0xa3, 0x9c,\n    0xb4, 0xda, 0x8b, 0xb3, 0xde, 0xbc, 0xfb, 0x0f, 0x86, 0xe2, 0x48, 0x96,\n    0xe6, 0x89, 0xa6, 0xea, 0xca, 0xb6, 0xee, 0x0b, 0xc7, 0xf2, 0x4c, 0xd7,\n    0xf6, 0x8d, 0xe7, 0xfa, 0xce, 0xf7, 0xfe, 0x0f, 0x0c, 0x0a, 0x87, 0xc4,\n    0xa2, 0xf1, 0x88, 0x4c, 0x2a, 0x97, 0xcc, 0xa6, 0xf3, 0x09, 0x8d, 0x4a,\n    0xa7, 0xd4, 0xaa, 0xf5, 0x8a, 0xcd, 0x6a, 0xb7, 0xdc, 0xae, 0xf7, 0x0b,\n    0x0e, 0x8b, 0xc7, 0xe4, 0xb2, 0xf9, 0x8c, 0x4e, 0xab, 0xd7, 0xec, 0xb6,\n    0xfb, 0x0d, 0x8f, 0xcb, 0xe7, 0xf4, 0xba, 0xfd, 0x8e, 0xcf, 0xeb, 0xf7,\n    0xfc, 0xbe, 0xff, 0x0f, 0x18, 0x28, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88,\n    0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x08, 0x19, 0x29, 0x39, 0x49,\n    0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x09,\n    0x1a, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca,\n    0xda, 0xea, 0xfa, 0x0a, 0x1b, 0x2b, 0x3b, 0x4b, 0x5b, 0x6b, 0x7b, 0x8b,\n    0x9b, 0xab, 0xbb, 0xfb, 0x50, 0x00, 0x00, 0x3b,\n    0x00\n};\n\nstatic unsigned char g_Font2_3m_gif[] =\n{\n    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x11, 0x00, 0xe3, 0x05, 0xa1, 0x02,\n    0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0x21, 0xfe, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20,\n    0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x00, 0x2c, 0x00,\n    0x00, 0x00, 0x00, 0x11, 0x00, 0xe3, 0x05, 0x00, 0x02, 0xfe, 0x94, 0x8f,\n    0xa9, 0xcb, 0x08, 0xff, 0xda, 0x81, 0x51, 0x0a, 0x0a, 0xec, 0xa5, 0x1a,\n    0x77, 0x6e, 0x79, 0x21, 0xa8, 0x95, 0x66, 0x22, 0x4a, 0xe9, 0xc9, 0xb6,\n    0x6e, 0x03, 0x4d, 0x95, 0x12, 0x1b, 0x35, 0x3d, 0xdf, 0x68, 0x3e, 0xef,\n    0x99, 0xdd, 0x73, 0xf0, 0x7e, 0xaf, 0xa2, 0xf1, 0x88, 0x4c, 0x2a, 0x8f,\n    0x37, 0x9d, 0x90, 0xe8, 0xdc, 0x60, 0xa6, 0xcf, 0x29, 0x49, 0x06, 0x0d,\n    0x62, 0xb7, 0x3e, 0xab, 0xce, 0xbb, 0x02, 0x66, 0x89, 0x3e, 0xee, 0xf2,\n    0x8c, 0x3e, 0x5b, 0x55, 0xd4, 0x85, 0x27, 0x2c, 0x8d, 0xc0, 0xc5, 0x9c,\n    0xd5, 0x5a, 0xf4, 0x6e, 0xd3, 0x33, 0xeb, 0x38, 0xd9, 0xbf, 0xd7, 0x45,\n    0x06, 0x06, 0xe8, 0x57, 0x77, 0x95, 0x86, 0x15, 0xf4, 0x85, 0xc8, 0xd8,\n    0x74, 0x95, 0x32, 0xa7, 0xf8, 0xe7, 0x86, 0x28, 0xf8, 0x11, 0x55, 0x45,\n    0x89, 0x93, 0x39, 0xe9, 0xf9, 0x59, 0x18, 0x86, 0xd7, 0x68, 0x99, 0x68,\n    0x5a, 0x29, 0xb9, 0xa7, 0x15, 0x08, 0x97, 0xd7, 0x79, 0x07, 0x49, 0x35,\n    0x1a, 0xd3, 0xc7, 0xda, 0x26, 0x0a, 0x82, 0xeb, 0x28, 0x9b, 0xeb, 0x55,\n    0x78, 0x0a, 0x0c, 0x53, 0x7a, 0x39, 0xd2, 0xa9, 0xc9, 0x66, 0x0c, 0x1a,\n    0xbc, 0xcc, 0xdc, 0xec, 0xfc, 0x2b, 0x9c, 0x9c, 0xaa, 0xcc, 0x30, 0x1d,\n    0x58, 0xbc, 0x4a, 0x8c, 0x9c, 0x7d, 0x5c, 0x62, 0x0d, 0xfd, 0xbc, 0xfc,\n    0xfd, 0x7d, 0xbd, 0xbd, 0xa9, 0x1d, 0xcd, 0x4d, 0x8d, 0xaa, 0x5e, 0x9e,\n    0x7e, 0xee, 0x5e, 0x3d, 0x1c, 0x8e, 0xe6, 0xaa, 0x65, 0x4f, 0x09, 0x76,\n    0xaf, 0x2f, 0x3f, 0x5d, 0x2b, 0xa8, 0x67, 0x52, 0x40, 0x5b, 0x51, 0xf0,\n    0xd1, 0x3b, 0x98, 0x66, 0xdc, 0xbc, 0x75, 0x9c, 0xb2, 0xe9, 0xdb, 0x45,\n    0x88, 0x1d, 0x3c, 0x86, 0xe8, 0xfa, 0xb5, 0x43, 0x88, 0x31, 0xa3, 0xc6,\n    0xfe, 0x84, 0x0b, 0xe3, 0x49, 0xc4, 0xa4, 0x4e, 0x61, 0xb2, 0x8d, 0x24,\n    0x9f, 0x3d, 0xec, 0x71, 0x72, 0x62, 0xc9, 0x95, 0x2c, 0x5b, 0xba, 0x2c,\n    0x29, 0xf2, 0x62, 0x4c, 0x95, 0x2f, 0x5b, 0xcc, 0x34, 0x07, 0x72, 0xe2,\n    0xcd, 0x77, 0x39, 0x31, 0x99, 0x18, 0x59, 0xb3, 0x65, 0xc4, 0x86, 0xad,\n    0x48, 0xd8, 0x31, 0x1a, 0x8b, 0x56, 0x40, 0x7c, 0x28, 0x79, 0x41, 0x94,\n    0x93, 0x74, 0x48, 0xd3, 0x47, 0x05, 0x7d, 0x49, 0xec, 0x18, 0x14, 0x1b,\n    0x4d, 0x7f, 0x03, 0x01, 0x6e, 0xc5, 0xba, 0x13, 0xa7, 0xd6, 0x9e, 0xef,\n    0x86, 0x0d, 0xcd, 0xca, 0xe2, 0xac, 0x26, 0x4b, 0x91, 0xa2, 0x8e, 0x91,\n    0x09, 0x96, 0xad, 0x55, 0xa2, 0x72, 0x81, 0xce, 0x9c, 0x6b, 0xe8, 0x1f,\n    0xda, 0x9c, 0x66, 0xf1, 0x96, 0x3b, 0x4a, 0x35, 0x64, 0x5c, 0xa0, 0x1e,\n    0x89, 0x76, 0x83, 0xd6, 0xb6, 0xaa, 0x5e, 0x81, 0x84, 0xf7, 0x16, 0xae,\n    0xf8, 0xb1, 0xa8, 0x52, 0xc5, 0x50, 0x29, 0xf3, 0xa9, 0xcb, 0x8f, 0x71,\n    0xd7, 0xc3, 0x3c, 0xc7, 0x7a, 0x76, 0xcc, 0xf7, 0x5f, 0xca, 0x27, 0x3f,\n    0x2f, 0xaa, 0x92, 0xec, 0xb7, 0xb3, 0xd8, 0xbf, 0x6e, 0xd7, 0xa9, 0xd5,\n    0x4c, 0x13, 0x34, 0x6c, 0x8b, 0xa8, 0xbf, 0xda, 0xcd, 0xbc, 0xb6, 0xb6,\n    0xb2, 0xc4, 0x53, 0xa5, 0x0e, 0x42, 0x6a, 0xb9, 0xef, 0x62, 0xd9, 0x74,\n    0x45, 0x9f, 0x5c, 0xad, 0x3a, 0x39, 0x6d, 0xe4, 0xcb, 0x3f, 0x2b, 0xff,\n    0xe8, 0x9c, 0xb8, 0x56, 0xe1, 0x5d, 0x79, 0xff, 0x0e, 0x9c, 0x2f, 0xf5,\n    0xe9, 0x7d, 0xd8, 0x0f, 0x03, 0xf6, 0xed, 0x1d, 0xb3, 0xa4, 0xd7, 0xd2,\n    0x8b, 0x3b, 0x4c, 0x6d, 0x3d, 0x3c, 0xc4, 0xdd, 0xc0, 0xb9, 0xa3, 0xc7,\n    0xfd, 0xdc, 0xb0, 0xd7, 0xf3, 0xc3, 0xcb, 0xcb, 0x67, 0x0e, 0x1d, 0x7f,\n    0xb8, 0xb0, 0xf1, 0xfe, 0x21, 0xdb, 0x3f, 0xc8, 0x5f, 0x73, 0xfa, 0x99,\n    0x34, 0x18, 0x5c, 0xb7, 0x1d, 0x58, 0x5a, 0x6c, 0xff, 0x71, 0x26, 0x60,\n    0x7e, 0xfd, 0x3d, 0xc6, 0xe0, 0x83, 0x14, 0xdd, 0x27, 0xe1, 0x82, 0xa7,\n    0x3c, 0x64, 0x5e, 0x70, 0xb5, 0x6d, 0x06, 0x0c, 0x86, 0xf3, 0x69, 0x28,\n    0x1e, 0x87, 0x16, 0x9a, 0x12, 0x60, 0x85, 0xe0, 0x98, 0xc8, 0xd5, 0x81,\n    0x82, 0xa9, 0xa8, 0xe0, 0x88, 0x89, 0x90, 0x97, 0x97, 0x7b, 0x6f, 0x5d,\n    0x07, 0x5e, 0x74, 0x0e, 0xe6, 0x66, 0x8e, 0x69, 0x24, 0x16, 0xd8, 0x22,\n    0x71, 0x30, 0xbe, 0x96, 0x1e, 0x28, 0xdf, 0x5d, 0x36, 0x64, 0x5e, 0xc7,\n    0xe8, 0xa6, 0x9b, 0x8d, 0xb4, 0x55, 0x07, 0xdf, 0x88, 0x3f, 0xbe, 0xd7,\n    0x9d, 0x90, 0xad, 0xb9, 0x13, 0xe4, 0x76, 0x43, 0xe2, 0x55, 0xe5, 0x2f,\n    0x45, 0x52, 0xd9, 0x5e, 0x8f, 0x41, 0x79, 0x98, 0x9b, 0x78, 0xec, 0x45,\n    0x69, 0xa5, 0x8c, 0x4c, 0x92, 0xa9, 0xe5, 0x94, 0x88, 0x75, 0x19, 0x26,\n    0x88, 0x8d, 0xc9, 0xf6, 0xe4, 0x62, 0x59, 0xce, 0xc9, 0x23, 0x59, 0x03,\n    0xaa, 0xd7, 0xdb, 0x8c, 0x19, 0x2e, 0xf9, 0xe6, 0x5e, 0x60, 0xc2, 0x26,\n    0xe3, 0x98, 0x35, 0xb2, 0xb6, 0x1e, 0x97, 0x68, 0xd2, 0x79, 0x28, 0x9b,\n    0x80, 0x66, 0x07, 0xa3, 0x7d, 0xa3, 0x2d, 0x9a, 0xa7, 0x9d, 0x65, 0xbd,\n    0xa2, 0xdd, 0x89, 0x14, 0x36, 0x88, 0xe9, 0x55, 0xc6, 0xfd, 0xb9, 0xe0,\n    0xa3, 0x65, 0x16, 0x7a, 0xe7, 0x6c, 0x9a, 0xd2, 0x67, 0x60, 0xa9, 0xb6,\n    0x99, 0x2a, 0xa9, 0x8b, 0xa3, 0x9e, 0x03, 0xa4, 0xa2, 0x84, 0x46, 0xaa,\n    0xe4, 0x25, 0x9b, 0x56, 0xa6, 0xe6, 0x96, 0x27, 0xc6, 0x29, 0x62, 0x79,\n    0x88, 0x0e, 0x9a, 0x26, 0xa8, 0x57, 0xce, 0x7a, 0xd6, 0xae, 0x7a, 0x4a,\n    0xe9, 0x6b, 0xad, 0xc6, 0xf6, 0x89, 0xfe, 0x16, 0xae, 0x29, 0xa2, 0x1a,\n    0x6b, 0x84, 0x97, 0xde, 0x08, 0xed, 0xb3, 0x6d, 0xf2, 0xa9, 0x23, 0x9c,\n    0x4d, 0xc6, 0x28, 0x4d, 0x9d, 0xcd, 0x4e, 0xc8, 0xad, 0x47, 0x06, 0xc9,\n    0x4a, 0x5d, 0xb6, 0xe4, 0xf8, 0xe8, 0x6a, 0xaf, 0x91, 0x22, 0x19, 0x22,\n    0xa5, 0xf5, 0x29, 0x1b, 0xee, 0xad, 0x87, 0x58, 0x26, 0xe8, 0xb0, 0x4e,\n    0x6a, 0x1b, 0x2a, 0x84, 0xdd, 0x5a, 0xea, 0x5f, 0xbe, 0xce, 0xea, 0xbb,\n    0x68, 0x86, 0xd5, 0xfa, 0x49, 0x4a, 0x0d, 0xba, 0x54, 0x00, 0xcb, 0x2c,\n    0xef, 0xf6, 0xf2, 0x68, 0x4a, 0x04, 0xbf, 0x1b, 0x0a, 0xc3, 0x06, 0xff,\n    0xf1, 0x30, 0x31, 0x03, 0xff, 0x30, 0xf1, 0xbc, 0x89, 0x5e, 0x7c, 0x30,\n    0x8d, 0x93, 0xe5, 0x89, 0xe5, 0x71, 0x90, 0x6a, 0xdc, 0xf1, 0xc6, 0xeb,\n    0x01, 0x1b, 0xef, 0xbd, 0xd6, 0xae, 0x0b, 0x25, 0xaf, 0xc2, 0x96, 0x6c,\n    0xa8, 0xca, 0xe5, 0xae, 0xec, 0x6d, 0xa5, 0x8d, 0xea, 0x7a, 0x2d, 0xa7,\n    0xe6, 0x6a, 0xcc, 0xf2, 0x9a, 0x18, 0xcb, 0x59, 0x33, 0xbd, 0x28, 0x62,\n    0x65, 0x31, 0x87, 0xad, 0xee, 0x9c, 0xb3, 0xad, 0x46, 0x1f, 0x7b, 0xae,\n    0x98, 0xe3, 0x39, 0x55, 0x9c, 0x14, 0xaa, 0xda, 0x0c, 0xf5, 0xcb, 0x52,\n    0x13, 0xdd, 0x32, 0xcf, 0x43, 0xbb, 0x4c, 0xb5, 0xce, 0xbc, 0x06, 0xbd,\n    0xb4, 0xd5, 0x8b, 0xc0, 0x6a, 0x86, 0xa8, 0x7b, 0x7a, 0xe3, 0x33, 0xcc,\n    0xee, 0xa2, 0x5c, 0x9f, 0xa3, 0x1e, 0x7f, 0x4a, 0xea, 0xa9, 0x6e, 0xa7,\n    0x1a, 0x2d, 0xbf, 0xf6, 0xce, 0x8d, 0xef, 0xbe, 0x9d, 0x4e, 0x8d, 0x75,\n    0xde, 0xf2, 0xde, 0x8c, 0x27, 0xce, 0x55, 0x17, 0x8d, 0x34, 0xa3, 0x42,\n    0x57, 0xaa, 0x76, 0xd6, 0x47, 0x17, 0x8b, 0x38, 0xd8, 0x87, 0x2b, 0x9e,\n    0x6e, 0xdb, 0x0b, 0x95, 0xc8, 0xb5, 0xc4, 0x01, 0x2f, 0xfe, 0xec, 0x44,\n    0xc4, 0xdd, 0xb8, 0xd2, 0xf0, 0x64, 0x99, 0x7b, 0xac, 0x30, 0xc5, 0x4e,\n    0x21, 0x9c, 0x31, 0xe5, 0x5f, 0x0b, 0x3c, 0xb9, 0x97, 0x2e, 0x99, 0xbd,\n    0xa1, 0xba, 0xb9, 0x42, 0x2e, 0xf6, 0x87, 0xf7, 0x2d, 0x7e, 0x1d, 0x2a,\n    0xa4, 0x71, 0xf2, 0x34, 0xde, 0x7b, 0xa3, 0xde, 0x38, 0xab, 0x84, 0xd7,\n    0x7d, 0x2f, 0xeb, 0x76, 0x9b, 0x4c, 0xee, 0x9f, 0x9e, 0xc6, 0xfd, 0xbb,\n    0xef, 0xbc, 0xdb, 0x6b, 0x3c, 0xdb, 0x20, 0xa7, 0xfd, 0x1f, 0xf3, 0xce,\n    0xfb, 0x9c, 0xa9, 0xb4, 0x72, 0x1f, 0x4f, 0xfd, 0x5d, 0x3a, 0xe5, 0x2a,\n    0x5d, 0xf2, 0xbd, 0x03, 0x5d, 0x3d, 0xf7, 0xda, 0x8b, 0x91, 0xa0, 0xaa,\n    0xfb, 0xad, 0x8e, 0x7d, 0xf7, 0x2c, 0xc2, 0x2d, 0xfd, 0xef, 0x74, 0x2f,\n    0x2b, 0x2e, 0xf7, 0x2f, 0x92, 0xdf, 0x35, 0xad, 0x82, 0x13, 0xf9, 0xaf,\n    0xf8, 0xf6, 0xdf, 0x8f, 0x7f, 0xfe, 0x46, 0x0c, 0x3f, 0x7c, 0xe4, 0xdb,\n    0xae, 0xef, 0x3d, 0xe8, 0xe9, 0x6f, 0x80, 0x42, 0xe9, 0x59, 0xb0, 0x6c,\n    0x07, 0xb8, 0xc4, 0xf9, 0x4d, 0x6b, 0x7b, 0x3b, 0x0d, 0x03, 0xe5, 0xc7,\n    0x0c, 0xed, 0x49, 0xd0, 0x80, 0x29, 0xbb, 0x1d, 0x02, 0xff, 0x86, 0x41,\n    0x06, 0xc6, 0xeb, 0x4c, 0xbb, 0x23, 0x60, 0x11, 0xd8, 0x05, 0xaf, 0x0c,\n    0xda, 0xea, 0x67, 0x86, 0xcb, 0x1d, 0xb5, 0x4c, 0xf7, 0xbe, 0xf3, 0xfd,\n    0xef, 0x6c, 0x15, 0x5c, 0xca, 0x05, 0x35, 0x28, 0xc2, 0xc0, 0xf5, 0xcd,\n    0x5f, 0xf3, 0x2b, 0x9f, 0x07, 0x4f, 0x00, 0xc2, 0x40, 0xc5, 0xf0, 0x29,\n    0xa9, 0x13, 0xd3, 0x0a, 0x3f, 0x45, 0x43, 0xfd, 0x3d, 0x2f, 0x7e, 0x6f,\n    0x9b, 0x94, 0xe3, 0xda, 0x57, 0xc4, 0x7a, 0x99, 0x8f, 0x59, 0x37, 0xac,\n    0x07, 0x05, 0x49, 0xb6, 0x43, 0xc6, 0x29, 0x30, 0x88, 0x1f, 0x62, 0xe2,\n    0xfe, 0x73, 0x72, 0x38, 0x3f, 0xf4, 0x4d, 0xcf, 0x7a, 0xb9, 0xd3, 0x21,\n    0x0c, 0xbf, 0x08, 0xbb, 0x19, 0x12, 0x4b, 0x8a, 0x64, 0x44, 0x21, 0x12,\n    0xbe, 0xd7, 0x21, 0x01, 0xa6, 0x6f, 0x7b, 0x2a, 0x54, 0xe2, 0x04, 0x91,\n    0xb5, 0xa3, 0x36, 0x22, 0xe4, 0x8d, 0x56, 0x8c, 0xde, 0x16, 0x05, 0xf8,\n    0xad, 0x23, 0x85, 0x6b, 0x45, 0xed, 0x5a, 0x21, 0x1d, 0x2d, 0xb8, 0x33,\n    0x0e, 0x7a, 0x8d, 0x88, 0xed, 0x42, 0x57, 0x08, 0xc1, 0xe8, 0x3e, 0x25,\n    0xfc, 0x51, 0x8b, 0xea, 0x5b, 0x62, 0x12, 0x49, 0x58, 0x47, 0xe2, 0x79,\n    0x25, 0x47, 0x66, 0x6c, 0x22, 0x63, 0x1e, 0x48, 0x3f, 0x9c, 0x89, 0x8c,\n    0x20, 0x9b, 0x23, 0x52, 0x27, 0x9d, 0x16, 0xb1, 0x4d, 0x02, 0x42, 0x94,\n    0x98, 0x0b, 0x25, 0x04, 0x2d, 0xb9, 0xbf, 0x27, 0x0a, 0x32, 0x81, 0x65,\n    0x14, 0xe3, 0xab, 0x16, 0x18, 0xc6, 0x31, 0x56, 0x12, 0x95, 0x56, 0x3a,\n    0xe1, 0xeb, 0x64, 0x28, 0x4b, 0x57, 0xf2, 0x2d, 0x97, 0xae, 0x5b, 0x15,\n    0x2d, 0x99, 0xa0, 0xca, 0x24, 0x21, 0x72, 0x8a, 0xbc, 0x04, 0x62, 0x15,\n    0xb3, 0xe8, 0x47, 0x35, 0xde, 0x2f, 0x6a, 0x07, 0x2c, 0x21, 0x31, 0x5f,\n    0x49, 0xc5, 0x36, 0x99, 0x8a, 0x8f, 0x48, 0x64, 0x64, 0xf0, 0x4e, 0xa9,\n    0x47, 0x0d, 0x61, 0xf2, 0x8b, 0x90, 0x7c, 0xa4, 0x1d, 0x1b, 0xf9, 0x4b,\n    0x9b, 0xc8, 0xac, 0x82, 0xd6, 0x9c, 0x96, 0x2f, 0xc9, 0x26, 0x47, 0x66,\n    0x76, 0x90, 0x66, 0x72, 0x8c, 0x66, 0xbf, 0x24, 0x09, 0xc0, 0x76, 0x46,\n    0x46, 0x69, 0x7b, 0xbc, 0xde, 0xb8, 0xc2, 0x79, 0xcb, 0x67, 0xee, 0x12,\n    0x9a, 0xc5, 0xdc, 0xa7, 0x3f, 0xb3, 0xe4, 0xc0, 0x5f, 0x51, 0x0f, 0x9f,\n    0xfd, 0xc4, 0x9d, 0x3e, 0x0d, 0x6a, 0x26, 0xd5, 0xb1, 0xaf, 0x9c, 0x04,\n    0xe5, 0x09, 0x29, 0x74, 0x1d, 0x06, 0x3a, 0x4f, 0x9a, 0x32, 0x93, 0x9c,\n    0x9c, 0xe8, 0x27, 0x1f, 0xaa, 0xb9, 0x22, 0x91, 0x8c, 0x79, 0x0d, 0x2d,\n    0xa8, 0xed, 0x42, 0x16, 0xcc, 0x23, 0x26, 0xd4, 0x85, 0xc2, 0x7b, 0xe1,\n    0x3d, 0x1b, 0x8a, 0x50, 0x58, 0xe2, 0x92, 0x9f, 0x2c, 0x7d, 0xa7, 0x34,\n    0xbd, 0x09, 0x2d, 0x2c, 0xda, 0x8f, 0x7f, 0x6b, 0x83, 0x27, 0xef, 0x8c,\n    0x87, 0xd3, 0x9a, 0xf6, 0x4f, 0x7c, 0x39, 0x95, 0xa7, 0x03, 0x27, 0x95,\n    0xa0, 0x48, 0xae, 0x91, 0x6e, 0xf1, 0xac, 0x1f, 0x68, 0x16, 0x99, 0x4c,\n    0x9f, 0xde, 0x51, 0xa9, 0x8d, 0x44, 0x63, 0xf6, 0x94, 0x39, 0xd4, 0x81,\n    0x1a, 0x09, 0xa8, 0x36, 0x45, 0xea, 0x37, 0x81, 0xd7, 0xd1, 0xab, 0x22,\n    0x0f, 0x82, 0x8e, 0x18, 0x25, 0x35, 0x19, 0x9a, 0xd5, 0x96, 0x14, 0x00,\n    0x00, 0x3b,\n    0x00\n};\n\nstatic unsigned char g_Font2S_gif[] =\n{\n    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0xf0, 0x01, 0x10, 0x00, 0x80, 0x01,\n    0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00,\n    0xf0, 0x01, 0x10, 0x00, 0x00, 0x02, 0x64, 0x84, 0x8f, 0xa9, 0xcb, 0xed,\n    0x0f, 0xa3, 0x9c, 0xb4, 0xda, 0x8b, 0xb3, 0xde, 0xbc, 0xfb, 0x0f, 0x86,\n    0xe2, 0x48, 0x96, 0xe6, 0x89, 0xa6, 0xea, 0xca, 0xb6, 0xee, 0x0b, 0xc7,\n    0xf2, 0x4c, 0xd7, 0xf6, 0x8d, 0xe7, 0xfa, 0xce, 0xf7, 0xfe, 0x0f, 0x0c,\n    0x0a, 0x87, 0xc4, 0xa2, 0xf1, 0x88, 0x4c, 0x2a, 0x97, 0xcc, 0xa6, 0xf3,\n    0x09, 0x8d, 0x4a, 0xa7, 0xd4, 0xaa, 0xf5, 0x8a, 0xcd, 0x6a, 0xb7, 0xdc,\n    0xae, 0xf7, 0x0b, 0x0e, 0x8b, 0xc7, 0xe4, 0xb2, 0xf9, 0x8c, 0x4e, 0xab,\n    0xd7, 0xec, 0xb6, 0xfb, 0x0d, 0x8f, 0xcb, 0xe7, 0xf4, 0xba, 0xfd, 0x8e,\n    0xcf, 0xeb, 0xf7, 0xfc, 0xbe, 0xff, 0x0f, 0x18, 0x18, 0x54, 0x00, 0x00,\n    0x3b,\n    0x00\n};\n\n\n\nstatic unsigned char g_MenuGFX1m_orig_gif[] =\n{\n    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x20, 0x03, 0x30, 0x00, 0x87, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00,\n    0x00, 0x00, 0x20, 0x03, 0x30, 0x00, 0x00, 0x08, 0xff, 0x00, 0x01, 0x08,\n    0x1c, 0x48, 0xb0, 0xa0, 0xc1, 0x83, 0x08, 0x13, 0x2a, 0x5c, 0xc8, 0xb0,\n    0xa1, 0xc3, 0x87, 0x10, 0x23, 0x4a, 0x9c, 0x48, 0xb1, 0xa2, 0xc5, 0x8b,\n    0x18, 0x33, 0x6a, 0xdc, 0xc8, 0xb1, 0xa3, 0xc7, 0x8f, 0x20, 0x43, 0x8a,\n    0x1c, 0x49, 0xb2, 0xa4, 0xc9, 0x93, 0x28, 0x53, 0xaa, 0x5c, 0xc9, 0xb2,\n    0xa5, 0xcb, 0x97, 0x30, 0x63, 0xca, 0x9c, 0x49, 0xb3, 0xa6, 0xcd, 0x9b,\n    0x38, 0x73, 0xea, 0xdc, 0xc9, 0xb3, 0xa7, 0xcf, 0x9f, 0x40, 0x83, 0x0a,\n    0x1d, 0x4a, 0xb4, 0xa8, 0xd1, 0xa3, 0x48, 0x93, 0x2a, 0x5d, 0xca, 0xb4,\n    0xa9, 0xd3, 0xa7, 0x50, 0xa3, 0x4a, 0x9d, 0x4a, 0xb5, 0xaa, 0xd5, 0xab,\n    0x58, 0xb3, 0x6a, 0xdd, 0xca, 0xb5, 0xab, 0xd7, 0xaf, 0x60, 0xc3, 0x8a,\n    0x1d, 0x4b, 0xb6, 0xac, 0xd9, 0xb3, 0x68, 0xd3, 0xaa, 0x5d, 0xcb, 0xb6,\n    0xad, 0xdb, 0xb7, 0x70, 0xe3, 0xca, 0x9d, 0x4b, 0xb7, 0xae, 0xdd, 0xbb,\n    0x78, 0xf3, 0xea, 0xdd, 0xcb, 0xb7, 0xaf, 0xdf, 0xbf, 0x80, 0x03, 0x0b,\n    0x1e, 0x4c, 0xb8, 0xb0, 0xe1, 0xc3, 0x88, 0x13, 0x2b, 0x5e, 0xcc, 0xb8,\n    0xb1, 0xe3, 0xc7, 0x90, 0x23, 0x4b, 0x9e, 0x4c, 0xb9, 0xb2, 0xe5, 0xcb,\n    0x98, 0x33, 0x6b, 0xde, 0xcc, 0xb9, 0xb3, 0xe7, 0xcf, 0xa0, 0x43, 0x8b,\n    0x1e, 0x4d, 0xba, 0xb4, 0xe9, 0xd3, 0xa8, 0x53, 0xab, 0x5e, 0xcd, 0xba,\n    0xb5, 0xeb, 0xd7, 0xb0, 0x63, 0xcb, 0x9e, 0x4d, 0xbb, 0xb6, 0xed, 0xdb,\n    0xb8, 0x73, 0xeb, 0xde, 0xcd, 0xbb, 0xb7, 0xef, 0xdf, 0xc0, 0x83, 0x0b,\n    0x1f, 0x4e, 0xbc, 0xb8, 0xf1, 0xe3, 0xc8, 0x93, 0x2b, 0x5f, 0xbe, 0x38,\n    0x80, 0xf3, 0x00, 0x3c, 0x9f, 0x43, 0xdf, 0x29, 0x3d, 0xfa, 0x73, 0xeb,\n    0xce, 0xb1, 0x4f, 0xd7, 0x59, 0x9d, 0xfa, 0x75, 0xef, 0xd9, 0xc1, 0x6f,\n    0xff, 0xcf, 0xd9, 0x9d, 0xfb, 0x77, 0xad, 0xe5, 0xc9, 0x9f, 0x57, 0x1f,\n    0xde, 0x7c, 0x7b, 0xf6, 0xe3, 0x71, 0xa6, 0x97, 0xbf, 0x9e, 0xfe, 0x7b,\n    0xfb, 0xf1, 0x6f, 0xce, 0xd7, 0x5f, 0x9f, 0xff, 0xfd, 0xab, 0xfb, 0xd9,\n    0x14, 0x60, 0x4d, 0x03, 0xd2, 0x54, 0xe0, 0x4c, 0x07, 0xca, 0x94, 0x60,\n    0x4c, 0x0b, 0xc2, 0xd4, 0xe0, 0x4b, 0x0f, 0xba, 0x14, 0x61, 0x4b, 0x13,\n    0xb2, 0x54, 0xe1, 0x52, 0x17, 0xaa, 0x94, 0x61, 0x4a, 0x1b, 0xa2, 0xd4,\n    0xe1, 0x49, 0x1f, 0x9a, 0x14, 0x62, 0x49, 0x23, 0x92, 0x54, 0xe2, 0x48,\n    0x27, 0x8a, 0x94, 0x62, 0x48, 0x2b, 0x82, 0xd4, 0x62, 0x4f, 0xd2, 0xbd,\n    0xa8, 0x51, 0x8c, 0xfd, 0x29, 0x48, 0x63, 0x7e, 0x36, 0xd2, 0x88, 0x9f,\n    0x8c, 0x19, 0xdd, 0x88, 0x23, 0x83, 0x37, 0xee, 0x58, 0x23, 0x90, 0x3a,\n    0xfa, 0x17, 0xa3, 0x90, 0xff, 0xe5, 0x78, 0x24, 0x80, 0x41, 0x1a, 0xc9,\n    0x23, 0x46, 0x3e, 0x22, 0xf9, 0xa3, 0x83, 0x4d, 0x0a, 0x58, 0x25, 0x81,\n    0x57, 0x1a, 0x98, 0x25, 0x82, 0x5b, 0x2a, 0xf9, 0xe4, 0x45, 0x51, 0x3a,\n    0x39, 0x24, 0x54, 0x61, 0x5a, 0x59, 0xa4, 0x99, 0x4b, 0xa2, 0xf9, 0xa5,\n    0x45, 0x65, 0x62, 0x79, 0xa6, 0x9b, 0x69, 0xc2, 0xb9, 0x66, 0x45, 0x6d,\n    0x6a, 0xf9, 0xa6, 0x9d, 0x71, 0xe2, 0x39, 0xa7, 0x9e, 0x63, 0x42, 0xd8,\n    0x25, 0x91, 0x79, 0x72, 0x79, 0xa7, 0xa0, 0x81, 0x7a, 0xd9, 0xa7, 0x84,\n    0x7f, 0x52, 0x39, 0xa8, 0xa1, 0x49, 0x02, 0xba, 0xe7, 0x44, 0x75, 0x12,\n    0xfa, 0xa8, 0xa2, 0x3e, 0x4e, 0xb9, 0x52, 0xa5, 0x87, 0x72, 0x88, 0xa9,\n    0xa5, 0x1a, 0x6e, 0xca, 0xe7, 0xa2, 0x14, 0x7a, 0x2a, 0x69, 0xa2, 0x97,\n    0x8a, 0xca, 0x68, 0xa1, 0xa1, 0x62, 0xfa, 0x29, 0xaa, 0x16, 0x9a, 0x1a,\n    0xd5, 0xa6, 0x9c, 0x6a, 0xff, 0xaa, 0xea, 0xa8, 0xa0, 0xb6, 0x3a, 0xeb,\n    0xa9, 0x93, 0x36, 0x04, 0xeb, 0xaa, 0xb9, 0x32, 0xb4, 0x2b, 0xad, 0xac,\n    0x96, 0x7a, 0xab, 0xa3, 0xa4, 0x76, 0x3a, 0xac, 0x53, 0xb0, 0xf6, 0x7a,\n    0x50, 0xb2, 0x99, 0x9a, 0xc8, 0x6c, 0xac, 0xce, 0x32, 0x4b, 0xe9, 0xaf,\n    0x88, 0x4a, 0xeb, 0xa7, 0xb5, 0xd5, 0x26, 0x3b, 0xad, 0xab, 0xb6, 0x6a,\n    0x7b, 0xad, 0xb7, 0xd9, 0x52, 0xdb, 0xd4, 0xb3, 0xd0, 0xa2, 0xf8, 0xec,\n    0xb6, 0xc7, 0x0a, 0x0b, 0x6e, 0xaa, 0xeb, 0x76, 0x2b, 0xae, 0xbb, 0xdc,\n    0xaa, 0xfb, 0xae, 0xbc, 0xf1, 0x1a, 0xdb, 0x2e, 0xbd, 0xe9, 0xda, 0x3b,\n    0x2f, 0x52, 0xe4, 0x2a, 0x2b, 0x50, 0xbf, 0xcd, 0x7e, 0x04, 0x70, 0xa3,\n    0xd1, 0xf6, 0x0b, 0x2f, 0xb6, 0xb2, 0x1a, 0x8c, 0xef, 0xbd, 0x22, 0x0e,\n    0x7c, 0x30, 0xc3, 0x24, 0x3a, 0xbc, 0xf0, 0xbe, 0x11, 0x03, 0xcc, 0xd4,\n    0xc0, 0xe5, 0x7a, 0x84, 0xf1, 0xc3, 0x14, 0x17, 0x4c, 0x2e, 0xc7, 0xf5,\n    0x56, 0xac, 0xb0, 0xbe, 0xe7, 0x4e, 0x1c, 0xb2, 0xc7, 0x25, 0x93, 0x8c,\n    0xb0, 0x87, 0x12, 0xab, 0x0c, 0xb1, 0x50, 0x18, 0xe7, 0x1a, 0x73, 0xc0,\n    0x33, 0xce, 0x9c, 0x71, 0x8f, 0x36, 0xb3, 0x3c, 0xb3, 0xce, 0x31, 0xf3,\n    0xbc, 0x31, 0x88, 0x39, 0x03, 0xbd, 0xb3, 0xd0, 0x3d, 0x13, 0xfd, 0x73,\n    0xc3, 0x43, 0x1b, 0x65, 0xf3, 0xcd, 0x50, 0x06, 0x8d, 0x74, 0xd1, 0x4f,\n    0x1f, 0x2d, 0xb2, 0xd4, 0x28, 0x5b, 0x6c, 0x74, 0xcb, 0x53, 0x63, 0x5d,\n    0xf5, 0xc8, 0x59, 0x5b, 0x1d, 0xb5, 0xd6, 0xe6, 0x26, 0x1d, 0xd4, 0xd2,\n    0x91, 0xd6, 0x4c, 0x76, 0xb0, 0x6c, 0x9e, 0x8d, 0x36, 0x9d, 0x6a, 0xf3,\n    0xd8, 0xb6, 0xdb, 0x6f, 0x13, 0x0c, 0x66, 0xdc, 0x4c, 0x3f, 0x44, 0x77,\n    0xdd, 0x0e, 0xdd, 0xed, 0x22, 0xdd, 0x7b, 0xc7, 0xb7, 0xdd, 0xf7, 0xdb,\n    0x2e, 0x0a, 0xcc, 0xb7, 0xe0, 0x7e, 0x13, 0x0e, 0xb8, 0xe1, 0x6d, 0xff,\n    0x9d, 0x38, 0xe2, 0x6a, 0x2b, 0xde, 0x38, 0xe3, 0x67, 0x3b, 0x1e, 0x39,\n    0xe4, 0x64, 0x4b, 0x5e, 0x39, 0xe5, 0x4b, 0xff, 0xdd, 0xf4, 0xdd, 0xb5,\n    0xda, 0xcd, 0x79, 0xe7, 0x79, 0x7f, 0xbe, 0xb6, 0xaf, 0xa2, 0x8f, 0x58,\n    0xba, 0xe9, 0xa7, 0xcb, 0x1d, 0x7a, 0xea, 0x9b, 0x9f, 0xde, 0x7a, 0xe9,\n    0xaf, 0x8b, 0x1e, 0xfb, 0xe7, 0xb3, 0x73, 0x5e, 0x7b, 0xed, 0x7a, 0xcf,\n    0xcd, 0xba, 0xee, 0xae, 0xf3, 0x0e, 0xbb, 0xef, 0xb2, 0x03, 0x4f, 0xbb,\n    0xf0, 0xb6, 0x13, 0x9f, 0x7b, 0xda, 0xbb, 0x23, 0xdf, 0xbb, 0xf2, 0xbf,\n    0x33, 0x1f, 0xbc, 0xf3, 0x65, 0x17, 0x94, 0xfa, 0xca, 0xcb, 0x4e, 0xdf,\n    0xf1, 0x40, 0xd6, 0x5f, 0xff, 0x6f, 0xf6, 0xf9, 0x4a, 0xcf, 0x7d, 0xa5,\n    0xa4, 0x7f, 0x5f, 0x2c, 0x00, 0xe2, 0x47, 0x4f, 0x50, 0xf9, 0xe3, 0xa3,\n    0x0f, 0x3a, 0xf9, 0xea, 0x2f, 0xd8, 0xbe, 0xfb, 0xef, 0xab, 0xce, 0xbe,\n    0xd7, 0x09, 0xc5, 0x2f, 0xbf, 0xfd, 0xb1, 0xe2, 0x1f, 0xfe, 0xfb, 0xfb,\n    0xb7, 0xdf, 0xbf, 0xfa, 0xff, 0x43, 0x5f, 0x00, 0xcb, 0x37, 0x40, 0xf1,\n    0x15, 0xf0, 0x7b, 0x07, 0xe4, 0x5e, 0x02, 0xb3, 0x97, 0xc0, 0x80, 0x00,\n    0x00, 0x3b,\n    0x00\n};\n\nstatic unsigned char g_MenuGFX1m_fixed_gif[] =\n{\n    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x20, 0x03, 0x3c, 0x00, 0x80, 0x01,\n    0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x21, 0xfe, 0x11, 0x43, 0x72,\n    0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47,\n    0x49, 0x4d, 0x50, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x20, 0x03, 0x3c,\n    0x00, 0x00, 0x02, 0xfe, 0x84, 0x8f, 0xa9, 0xcb, 0xed, 0x0f, 0xa3, 0x9c,\n    0xb4, 0xda, 0x8b, 0xb3, 0xde, 0xbc, 0xfb, 0x0f, 0x86, 0xe2, 0x48, 0x96,\n    0xe6, 0x89, 0xa6, 0xea, 0xca, 0xb6, 0xee, 0x0b, 0xc7, 0xf2, 0x4c, 0xd7,\n    0xf6, 0x8d, 0xe7, 0xfa, 0xce, 0xf7, 0xfe, 0x0f, 0x0c, 0x0a, 0x87, 0xc4,\n    0xa2, 0xf1, 0x88, 0x4c, 0x2a, 0x97, 0xcc, 0xa6, 0xf3, 0x09, 0x8d, 0x4a,\n    0xa7, 0xd4, 0xaa, 0xf5, 0x8a, 0xcd, 0x6a, 0xb7, 0xdc, 0xae, 0xf7, 0x0b,\n    0x0e, 0x8b, 0xc7, 0xe4, 0xb2, 0xf9, 0x8c, 0x4e, 0xab, 0xd7, 0xec, 0xb6,\n    0xfb, 0x0d, 0x8f, 0xcb, 0xe7, 0xf4, 0xba, 0xfd, 0x8e, 0xcf, 0xeb, 0xf7,\n    0xfc, 0xbe, 0xff, 0x0f, 0x18, 0x28, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88,\n    0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x08, 0x19, 0x29, 0x39, 0x49,\n    0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x09,\n    0x1a, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca,\n    0xda, 0xea, 0xfa, 0x0a, 0x1b, 0x2b, 0x3b, 0x4b, 0x5b, 0x6b, 0x7b, 0x8b,\n    0x9b, 0xab, 0xbb, 0xcb, 0xdb, 0xeb, 0xfb, 0x0b, 0x1c, 0x2c, 0x3c, 0x4c,\n    0x5c, 0x6c, 0x7c, 0x8c, 0x9c, 0xac, 0xbc, 0xcc, 0xdc, 0xec, 0xfc, 0xfc,\n    0x18, 0x20, 0x1d, 0x00, 0x34, 0x4d, 0xfd, 0x63, 0x5d, 0x3d, 0xad, 0x2d,\n    0xcd, 0x7d, 0xed, 0x93, 0x8d, 0xbd, 0x2d, 0xde, 0x4d, 0xfe, 0xdd, 0x13,\n    0x0e, 0x3e, 0xee, 0x95, 0x8e, 0xbe, 0xee, 0x5e, 0xae, 0x1e, 0x0f, 0x7f,\n    0xce, 0xd3, 0x6e, 0xff, 0x8e, 0x3f, 0xaf, 0x5f, 0xbf, 0x73, 0xef, 0x9f,\n    0x0f, 0xe0, 0xbe, 0x2d, 0xff, 0x74, 0x14, 0xcc, 0x71, 0x10, 0x47, 0xc2,\n    0x1b, 0x0b, 0x6d, 0x34, 0xac, 0xf1, 0x90, 0x46, 0xc4, 0x19, 0x13, 0x65,\n    0x54, 0x8c, 0x71, 0x11, 0x46, 0xc6, 0xfe, 0x27, 0x1b, 0x5d, 0x74, 0x6c,\n    0xf1, 0x91, 0x45, 0xc8, 0x15, 0x23, 0x55, 0x94, 0x4c, 0x71, 0x12, 0x45,\n    0xca, 0x13, 0x2b, 0x4d, 0xb4, 0x2c, 0xf1, 0x92, 0x44, 0xcc, 0x20, 0xd6,\n    0x66, 0x7a, 0xa8, 0x19, 0xd0, 0x21, 0xce, 0x7e, 0x3a, 0x71, 0xf2, 0xb3,\n    0xd9, 0x61, 0x27, 0x4f, 0x88, 0x3b, 0x7f, 0xe6, 0x24, 0xea, 0x53, 0x60,\n    0x4d, 0xa3, 0x03, 0x7b, 0x2e, 0x25, 0x58, 0x54, 0x29, 0x50, 0x0e, 0x42,\n    0x99, 0x0e, 0x95, 0x18, 0xd5, 0x60, 0x56, 0x84, 0x5b, 0x15, 0x76, 0x65,\n    0xf8, 0xd5, 0xe9, 0xd4, 0x0d, 0x55, 0xa5, 0x1e, 0xa5, 0x52, 0x56, 0x6b,\n    0x52, 0xb5, 0x4f, 0xd9, 0x8e, 0xd5, 0x90, 0x96, 0xeb, 0x5a, 0xb9, 0x6d,\n    0xe9, 0xbe, 0xcd, 0x10, 0xd7, 0xeb, 0x5c, 0xbd, 0x75, 0xf9, 0xde, 0xf5,\n    0x7b, 0x96, 0x62, 0x58, 0xa4, 0x7d, 0xc1, 0xee, 0x35, 0x5c, 0x58, 0x6c,\n    0x60, 0x8b, 0x83, 0xb1, 0x1e, 0x56, 0xdc, 0x94, 0xf0, 0xdf, 0x0b, 0x79,\n    0x11, 0x4f, 0x76, 0x2c, 0xf4, 0xea, 0x8b, 0xcc, 0x8b, 0x41, 0x72, 0xd6,\n    0xec, 0xf1, 0x33, 0xe0, 0xc7, 0x18, 0x45, 0x5b, 0x6e, 0xbc, 0xd9, 0x34,\n    0xe4, 0xc4, 0xa5, 0x39, 0x8f, 0x66, 0xad, 0x51, 0x75, 0x95, 0xcf, 0xa0,\n    0x3d, 0xbb, 0x3e, 0x4d, 0x3a, 0xf6, 0xed, 0xd5, 0x97, 0x23, 0xd0, 0x7e,\n    0xdd, 0x1b, 0xc2, 0x6f, 0xdc, 0xb0, 0x53, 0xef, 0x96, 0x8c, 0x3a, 0xf4,\n    0x71, 0x29, 0xb4, 0x83, 0x2f, 0x68, 0xde, 0x59, 0x25, 0xf4, 0xda, 0xd2,\n    0xa1, 0x63, 0x1e, 0xce, 0xd8, 0xba, 0x60, 0xed, 0xd9, 0x9b, 0x5f, 0x97,\n    0xad, 0xdb, 0xfb, 0x76, 0xf1, 0xdd, 0xb1, 0x47, 0x99, 0x4e, 0x9d, 0xe5,\n    0xf4, 0xef, 0xcb, 0x8d, 0x93, 0x6f, 0xfd, 0x3e, 0xbc, 0x79, 0xf9, 0xe0,\n    0xdd, 0xcf, 0xb7, 0x5f, 0x5f, 0x79, 0x7c, 0xfc, 0xed, 0xfe, 0xf5, 0xdf,\n    0x67, 0x82, 0x9e, 0x73, 0x06, 0x04, 0x18, 0xdd, 0x08, 0x04, 0x46, 0x56,\n    0x5d, 0x80, 0xf4, 0x71, 0x67, 0x9b, 0x82, 0xfc, 0xed, 0x67, 0xd2, 0x81,\n    0x0b, 0x42, 0x88, 0x92, 0x84, 0x0f, 0xfe, 0x57, 0x21, 0x81, 0x50, 0x1c,\n    0x98, 0x9e, 0x08, 0x1c, 0x4e, 0x88, 0x61, 0x82, 0xe8, 0x81, 0x98, 0x5f,\n    0x86, 0x0e, 0xfa, 0xb7, 0xde, 0x85, 0x25, 0x8a, 0x98, 0x22, 0x8a, 0x0c,\n    0x8a, 0x64, 0xa1, 0x8b, 0x14, 0x1a, 0xc1, 0x61, 0x6f, 0x35, 0x16, 0x78,\n    0xd3, 0x8d, 0x1d, 0x06, 0xa5, 0x23, 0x8c, 0x37, 0xfa, 0x58, 0x23, 0x90,\n    0x1f, 0x92, 0xd4, 0x23, 0x91, 0x3f, 0x1a, 0x19, 0x24, 0x92, 0x43, 0x46,\n    0x78, 0xa4, 0x12, 0x3a, 0xee, 0x48, 0x55, 0x91, 0x4c, 0x26, 0x39, 0xe5,\n    0x92, 0x26, 0x5a, 0xc9, 0xa2, 0x86, 0x4a, 0xc6, 0x78, 0x25, 0x97, 0x59,\n    0x9e, 0xd8, 0xa5, 0x96, 0x55, 0x7a, 0xa9, 0x5e, 0x93, 0x45, 0x3c, 0x59,\n    0x59, 0x8e, 0x68, 0x16, 0x07, 0xd7, 0x9a, 0x6c, 0xe2, 0xe5, 0x26, 0x50,\n    0x71, 0xca, 0x39, 0x27, 0x82, 0x64, 0xd5, 0x09, 0xe5, 0x04, 0x78, 0xe6,\n    0x29, 0xc1, 0x9e, 0x32, 0xe1, 0xf9, 0x67, 0x9d, 0x81, 0xce, 0x29, 0x93,\n    0x81, 0x80, 0x1a, 0x2a, 0x28, 0xa2, 0x84, 0x2a, 0x1a, 0xe7, 0xa0, 0x8d,\n    0x32, 0xea, 0xa6, 0xa3, 0x91, 0x42, 0xba, 0xa6, 0xa4, 0x95, 0x52, 0x8a,\n    0xa6, 0xa5, 0x99, 0x62, 0xfa, 0xe4, 0xa0, 0x51, 0xee, 0x99, 0x9b, 0x9e,\n    0xa0, 0x86, 0xda, 0xe7, 0xa8, 0x6f, 0x0a, 0x67, 0xea, 0x49, 0xa9, 0xaa,\n    0xba, 0xaa, 0x9d, 0xa5, 0xb6, 0xfa, 0xe9, 0xaa, 0xb1, 0xa6, 0x3a, 0xab,\n    0xa9, 0xb5, 0x8e, 0x7a, 0x2b, 0xa8, 0xb9, 0xe6, 0xea, 0xe7, 0x9d, 0xb0,\n    0xfa, 0x2a, 0x2b, 0xb0, 0xb4, 0x0a, 0x6b, 0x2b, 0xb1, 0xb8, 0x1a, 0xab,\n    0xd6, 0x2b, 0xb2, 0xbd, 0xb6, 0xf9, 0x2b, 0xb3, 0xc1, 0x3a, 0x3b, 0x2c,\n    0xb4, 0xc5, 0x4a, 0x9b, 0x66, 0x02, 0xad, 0xbe, 0xf8, 0xdc, 0xb5, 0x21,\n    0x1e, 0xa0, 0xed, 0xb6, 0x03, 0x76, 0xdb, 0x9f, 0xb5, 0xe0, 0x66, 0x86,\n    0xea, 0xb8, 0xc9, 0x01, 0x60, 0x6e, 0xb5, 0x08, 0xa4, 0x7b, 0x2e, 0xbb,\n    0xa4, 0xa2, 0xeb, 0xee, 0x43, 0xf1, 0xca, 0x3b, 0xaf, 0xab, 0xf0, 0x8a,\n    0xd9, 0x40, 0xbd, 0xf6, 0xea, 0x5b, 0x1b, 0xbf, 0xe5, 0xce, 0xfb, 0x6f,\n    0xbc, 0x01, 0xbb, 0x3b, 0x30, 0xbb, 0x05, 0xa7, 0x7b, 0xb0, 0xb9, 0x09,\n    0x8f, 0xbb, 0x30, 0xb8, 0x0d, 0x77, 0xfb, 0x30, 0xbf, 0x12, 0x4f, 0x4c,\n    0x71, 0xc5, 0x16, 0x5f, 0x8c, 0x71, 0xc6, 0x1a, 0x6f, 0xcc, 0x71, 0xc7,\n    0x1e, 0x7f, 0x0c, 0x72, 0xc8, 0x22, 0x8f, 0x4c, 0x72, 0xc9, 0x26, 0x9f,\n    0x8c, 0x72, 0xca, 0x2a, 0xaf, 0xcc, 0x72, 0xcb, 0x2e, 0xbf, 0x0c, 0x73,\n    0xcc, 0x32, 0xcf, 0x4c, 0x73, 0xcd, 0x36, 0xdf, 0x8c, 0x73, 0xce, 0x3a,\n    0xef, 0xcc, 0x73, 0xcf, 0x3e, 0xff, 0x0c, 0x74, 0xd0, 0x42, 0x0f, 0x4d,\n    0x74, 0xd1, 0x46, 0x1f, 0x8d, 0x74, 0xd2, 0x4a, 0x2f, 0xcd, 0x74, 0xd3,\n    0x4e, 0x3f, 0x0d, 0x75, 0xd4, 0x52, 0x4f, 0x4d, 0x75, 0xd5, 0x56, 0x5f,\n    0x8d, 0x75, 0xd6, 0x5a, 0x6f, 0xcd, 0x75, 0xd7, 0x5e, 0x7f, 0x0d, 0x76,\n    0xd8, 0x62, 0x8f, 0x4d, 0x76, 0xd9, 0x66, 0x9f, 0x8d, 0x76, 0xda, 0x6a,\n    0xaf, 0xcd, 0x76, 0xdb, 0x6e, 0xbf, 0x0d, 0xb7, 0xbb, 0x05, 0x00, 0x00,\n    0x3b,\n    0x00\n};\n\n\n\nstruct Exe2UI\n{\n    off_t offset;\n    size_t size;\n    const char* filename;\n};\n\nstatic struct Exe2UI s_exe2ui[] =\n{\n    {207393, 10862, \"EditorSplash.gif\"},\n    {827416, 35359, \"MenuGFX4.gif\"},\n    {888848, 1032, \"Coin.gif\"},\n    {889931, 1126, \"Loader.gif\"},\n    {2997483, 898, \"BMVsm.gif\"},\n    {2998435, 940, \"BMVs.gif\"},\n    {2999434, 1001, \"BMWinm.gif\"},\n    {3000490, 1156, \"BMWin.gif\"},\n    {3001718, 976, \"CharacterName5m.gif\"},\n    {3002766, 985, \"CharacterName4m.gif\"},\n    {3003823, 1004, \"CharacterName3m.gif\"},\n    {3004899, 1005, \"CharacterName2m.gif\"},\n    {3005976, 1015, \"CharacterName1m.gif\"},\n    {3007059, 1095, \"CharacterName5.gif\"},\n    {3008222, 1109, \"CharacterName4.gif\"},\n    {3009399, 1166, \"CharacterName3.gif\"},\n    {3010633, 1136, \"CharacterName2.gif\"},\n    {3011837, 1177, \"CharacterName1.gif\"},\n    {3013076, 1119, \"LoadCoinm.gif\"},\n    {3014253, 1338, \"LoadCoin.gif\"},\n    {3015975, 3126, \"Warpm.gif\"},\n    {3019157, 3126, \"Warp.gif\"},\n    {3022441, 1307, \"YoshiWings.gif\"},\n    {3023807, 1116, \"YoshiWingsm.gif\"},\n    {3024979, 902, \"Tongue1.gif\"},\n    {3025941, 879, \"Tongue1m.gif\"},\n    {3026876, 985, \"Tongue2.gif\"},\n    {3027921, 985, \"Tongue2m.gif\"},\n    {3029007, 1423,\"Boot3m.gif\"},\n    {3030484, 1423, \"Boot3.gif\"},\n    {3031961, 1503, \"Boot2.gif\"},\n    {3033522, 1041, \"Boot2m.gif\"},\n    {3034618, 13640, \"Mount.gif\"},\n    {3048317, 3496, \"Mountm.gif\"},\n    {3051871, 1041, \"Boot1m.gif\"},\n    {3052966, 1503, \"Boot1.gif\"},\n    {3054571, 1754, \"MenuGFX1m.gif\"},\n    {3056382, 35359, \"MenuGFX4.gif\"},\n    {3091802, 1496, \"MenuGFX3m.gif\"},\n    {3093355, 1765, \"MenuGFX3.gif\"},\n    {3095181, 2886, \"MenuGFX2m.gif\"},\n    {3098124, 8430, \"MenuGFX2.gif\"},\n    {3106611, 4595, \"MenuGFX1.gif\"},\n    {3111314, 895, \"Interface8m.gif\"},\n    {3112268, 895, \"Interface8.gif\"},\n    {3113226, 936, \"Interface7m.gif\"},\n    {3114221, 936, \"Interface7.gif\"},\n    {3115220, 854, \"Interface6m.gif\"},\n    {3116133, 879, \"Interface6.gif\"},\n    {3117075, 862, \"Interface0m.gif\"},\n    {3117996, 861, \"Interface0.gif\"},\n    {3118914, 856, \"MCursor3.gif\"},\n    {3159803, 822, \"MCursor3m.gif\"},\n    {3119829, 896, \"Heart2m.gif\"},\n    {3120784, 896, \"Heart1m.gif\"},\n    {3121735, 992, \"Heart2.gif\"},\n    {3122782, 992, \"Heart1.gif\"},\n    {3123831, 874, \"MCursor2.gif\"},\n    {3124766, 874, \"MCursor2m.gif\"},\n    {3125697, 874, \"MCursor1.gif\"},\n    {3126632, 874, \"MCursor1m.gif\"},\n    {3127567, 931, \"nCursor7m.gif\"},\n    {3128555, 994, \"nCursor7.gif\"},\n    {3129606, 994, \"nCursor6.gif\"},\n    {3130661, 931, \"nCursor6m.gif\"},\n    {3131649, 994, \"nCursor5.gif\"},\n    {3132704, 931, \"nCursor5m.gif\"},\n    {3133692, 994, \"nCursor3.gif\"},\n    {3134747, 931, \"nCursor3m.gif\"},\n    {3135735, 994, \"nCursor2.gif\"},\n    {3136790, 931, \"nCursor2m.gif\"},\n    {3137778, 994, \"nCursor1.gif\"},\n    {3138833, 931, \"nCursor1m.gif\"},\n    {3139821, 994, \"nCursor0.gif\"},\n    {3140876, 931, \"nCursor0m.gif\"},\n    {3141868, 931, \"nCursor4m.gif\"},\n    {3142856, 994, \"nCursor4.gif\"},\n    {3143906, 1540, \"Font2Sm.gif\"}, // GENERATE BLACK-FILLED FRONT OF SAME SIZE!!!\n    {3145507, 866, \"ECursor3m.gif\"},\n    {3146430, 934, \"ECursor3.gif\"},\n    {3147421, 981, \"ECursor2.gif\"},\n    {3148463, 931, \"ECursor2m.gif\"},\n    {3149451, 994, \"ECursor1.gif\"},\n    {3150506, 931, \"ECursor1m.gif\"},\n    {3151500, 865, \"Interface5m.gif\"},\n    {3152424, 865, \"Interface5.gif\"},\n    {3153338, 864, \"Chat.gif\"},\n    {3154255, 864, \"Chatm.gif\"},\n    {3155174, 2613, \"Font2_3.gif\"}, // GENERATE INVERTED MASK OF SAME SIZE!!!\n    {3157839, 1903, \"TextBox.gif\"},\n    {3159803, 822, \"MCursor0m.gif\"},\n    {3160682, 822, \"MCursor0.gif\"},\n    {3161563, 116454, \"Font2_2m.gif\"},\n    {3278072, 116454, \"Font2_2.gif\"},\n    {3394581, 50646, \"Font2_1m.gif\"}, // GENERATE BLACK-FILLED FRONT OF SAME SIZE!!!\n    {3445286, 1440054, \"Interface4.gif\"},\n    {4885399, 9462, \"Container1.gif\"},\n    {4894924, 9462, \"Container1m.gif\"},\n    {4904449, 9462, \"Container2m.gif\"},\n    {4913970, 9462, \"Container2.gif\"},\n    {4923495, 9462, \"Container0m.gif\"},\n    {4933016, 9462, \"Container0.gif\"},\n    {4942533, 726, \"Font1_0.gif\"},\n    {4943315, 726, \"Font1_0m.gif\"},\n    {4944096, 726, \"Font1_1.gif\"},\n    {4944878, 726, \"Font1_1m.gif\"},\n    {4945659, 726, \"Font1_2.gif\"},\n    {4946441, 726, \"Font1_2m.gif\"},\n    {4947222, 726, \"Font1_3.gif\"},\n    {4948004, 726, \"Font1_3m.gif\"},\n    {4948785, 726, \"Font1_4.gif\"},\n    {4949567, 726, \"Font1_4m.gif\"},\n    {4950348, 726, \"Font1_5.gif\"},\n    {4951130, 726, \"Font1_5m.gif\"},\n    {4951911, 726, \"Font1_6.gif\"},\n    {4952693, 726, \"Font1_6m.gif\"},\n    {4953474, 726, \"Font1_7.gif\"},\n    {4954256, 726, \"Font1_7m.gif\"},\n    {4955037, 726, \"Font1_8.gif\"},\n    {4955819, 726, \"Font1_8m.gif\"},\n    {4956600, 726, \"Font1_9.gif\"},\n    {4957382, 726, \"Font1_9m.gif\"},\n    {4958167, 670, \"Interface1.gif\"},\n    {4958900, 670, \"Interface1m.gif\"},\n    {4959629, 822, \"Interface2.gif\"},\n    {4960514, 822, \"Interface2m.gif\"},\n    {4961395, 1590, \"Interface3.gif\"},\n    {4963048, 1590, \"Interface3m.gif\"},\n    {0, 0, NULL}\n};\n\n\nint dumpMem(unsigned char*in, size_t in_size, const char *name)\n{\n    char filename[1024];\n    FILE *out;\n\n    snprintf(filename, 1024, \"graphics/ui/%s\", name);\n    out = fopen(filename, \"wb\");\n    if(!out)\n    {\n        printf(\"ОШИБКА - не удалось открыть файл %s для записи\", filename);\n        return 0;\n    }\n\n    fwrite(in, 1, in_size, out);\n    fclose(out);\n    return 1;\n}\n\nint dumpFile(FILE *in, off_t src_pos, size_t fileSize, const char *name)\n{\n    uint32_t writtenSize = 0, readSize = 0;\n    char filename[1024];\n    char outBuffer[1024];\n    int outBufferSize = 0;\n    FILE *out;\n\n    fseek(in, src_pos, SEEK_SET);\n\n    snprintf(filename, 1024, \"graphics/ui/%s\", name);\n\n    out = fopen(filename, \"wb\");\n    if(!out)\n    {\n        printf(\"ОШИБКА - не удалось открыть файл %s для записи\", filename);\n        return 0;\n    }\n\n    writtenSize = 0;\n    do\n    {\n        readSize = (writtenSize + 1024 >= fileSize) ? (fileSize - writtenSize) : 1024;\n        outBufferSize = fread(outBuffer, 1, readSize, in);\n        fwrite(outBuffer, 1, outBufferSize, out);\n        writtenSize += outBufferSize;\n    } while(writtenSize < fileSize);\n\n    fclose(out);\n    return 1;\n}\n\nint main(int argc, char **argv)\n{\n    struct Exe2UI *it;\n    FILE *in;\n    const char *smbxName = \"smbx.exe\";\n    char buffer[2048];\n\n    if(argc > 1)\n        smbxName = argv[1];\n\n    in = fopen(smbxName, \"rb\");\n    if(!in)\n    {\n        fprintf(stderr, \"Can't open smbx.exe\\n\");\n        return 1;\n    }\n\n    for(it = s_exe2ui; it->filename; it++)\n    {\n        fseek(in, it->offset, SEEK_SET);\n        fprintf(stdout, \"Dumping graphics/ui/%s ...\\n\", it->filename);\n        fflush(stdout);\n        if(strcmp(it->filename, \"MenuGFX1m.gif\") == 0)\n        {\n            if(fread(buffer, 1, it->size, in) != it->size)\n            {\n                --it;\n                continue;\n            }\n\n            if(memcmp(buffer, g_MenuGFX1m_orig_gif, it->size) == 0)\n            {\n                dumpMem(g_MenuGFX1m_fixed_gif, sizeof(g_MenuGFX1m_fixed_gif), \"MenuGFX1m.gif\");\n                continue;\n            }\n            else\n            {\n                fprintf(stderr, \"...MenuGFX1m.gif doesn't matches to original, keeping existing one...\\n\");\n                fflush(stdout);\n            }\n            fseek(in, it->offset, SEEK_SET);\n        }\n        dumpFile(in, it->offset, it->size, it->filename);\n    }\n\n    fclose(in);\n\n    dumpMem(g_Font2S_gif, sizeof(g_Font2S_gif), \"Font2S.gif\");\n    dumpMem(g_Font2_1_gif, sizeof(g_Font2_1_gif), \"Font2_1.gif\");\n    dumpMem(g_Font2_3m_gif, sizeof(g_Font2_3m_gif), \"Font2_3m.gif\");\n\n    fprintf(stdout, \"Done!\\n\");\n    fflush(stdout);\n\n    return 0;\n}\n"
  },
  {
    "path": "utils/convertkit/exe2ui/exe2ui_build.sh",
    "content": "#!/bin/bash\n\ngcc exe2ui.c -static -static-libgcc -O3 -no-pie -o exe2ui-linux-x86_64\ni686-w64-mingw32-gcc exe2ui.c -static -static-libgcc -O3 -no-pie -o exe2ui.exe\n"
  },
  {
    "path": "utils/convertkit/gfx-convert-16m.py",
    "content": "#!/usr/bin/python3\n\n# this is a simple script\n\nimport os\nimport sys\nimport math\nimport shutil\nimport configparser\n\nimport audio_convert_16m\n\nfrom convert_plr import *\n\nPREVIEW = False\nREDO = False\ndatadir = sys.argv[1]\ngraphicsdir = os.path.join(datadir, 'graphics')\nfallbackdir = os.path.join(datadir, 'graphics', 'fallback')\noutdir = sys.argv[2]\n\nif not datadir.endswith('/'): datadir += '/'\nif not outdir.endswith('/'): outdir += '/'\n\ndef has_dot(p):\n    if not p:\n        return False\n\n    p, t = os.path.split(p)\n\n    if t.startswith('.'):\n        return True\n\n    return has_dot(p)\n\nfor dirpath, _, files in os.walk(datadir, topdown=True):\n    if dirpath.endswith('fallback'):\n        continue\n\n    if has_dot(dirpath[len(datadir):]):\n        continue\n\n    if dirpath[len(datadir):] == 'music' or dirpath[len(datadir):] == 'sound':\n        continue\n\n    print(dirpath)\n\n    if dirpath.endswith('fonts'):\n        print('found fonts dir', dirpath)\n        is_fonts_dir = True\n        texture_1x = set()\n\n        for fn in files:\n            if fn.endswith('.ini'):\n                rfn = os.path.join(dirpath, fn)\n\n                font = configparser.ConfigParser(inline_comment_prefixes=';')\n                font.read(rfn)\n\n                if 'font-map' in font:\n                    if 'texture-scale' in font['font-map'] and font['font-map']['texture-scale'].strip != '1':\n                        if 'texture' in font['font-map']:\n                            tex_string = font['font-map']['texture'].strip().strip('\\\"')\n                            print(f\"{tex_string} is 1x\")\n                            texture_1x.add(tex_string)\n\n    else:\n        is_fonts_dir = False\n\n    outpath = outdir+dirpath[len(datadir):]\n    os.makedirs(outpath, exist_ok=True)\n    for fn in files:\n        if fn.startswith('.'):\n            continue\n\n        rfn = os.path.join(dirpath, fn)\n        if not os.path.isfile(rfn): continue\n        destfn = os.path.join(outpath, fn)\n        bmpfn = os.path.join(outpath, fn[:-3]+'bmp')\n        dsgfn = os.path.join(outpath, fn[:-3]+'dsg')\n        if not REDO and (os.path.isfile(destfn) or os.path.isfile(destfn+'.wav' or os.path.isfile(destfn+'.qoa')) or ((fn.endswith('.gif') or fn.endswith('.png')) and os.path.isfile(dsgfn))): continue\n        print(rfn)\n\n        is_1x = is_fonts_dir and fn in texture_1x\n\n        if is_1x:\n            downscale = \"-sample 100%\"\n        else:\n            downscale = \"-sample 50%\"\n\n        if not REDO and not is_fonts_dir and (os.path.isfile(destfn) or os.path.isfile(destfn+'.wav') or ((fn.endswith('.gif') or fn.endswith('.png')) and os.path.isfile(dsgfn))): continue\n\n        is_image = fn.endswith('.png') or fn.endswith('.gif')\n        if fn.startswith('link') and is_image:\n            os.system(do_sheet_link(rfn, bmpfn))\n            rfn = bmpfn\n        elif fn[:5] in ('mario', 'luigi', 'peach', 'toad-') and is_image:\n            os.system(do_sheet_nonlink(rfn, bmpfn))\n            rfn = bmpfn\n\n        if fn.endswith('.png'):\n            os.system(f'convert {downscale} \"{rfn}\" \"{bmpfn}\"')\n        elif fn.endswith('m.gif') and os.path.isfile(rfn[:-5]+'.gif'):\n            continue\n        elif fn.endswith('.gif'):\n            maskfn = rfn[:-4]+'m.gif'\n            ftype = fn[:fn.rfind('-')]\n            altmaskfn_gif_1 = os.path.join(fallbackdir, fn[:-4]+'m.gif')\n            altmaskfn_gif = os.path.join(graphicsdir, ftype, fn[:-4]+'m.gif')\n            altmaskfn_png = os.path.join(graphicsdir, ftype, fn[:-4]+'.png')\n            if os.path.isfile(maskfn):\n                os.system(f'convert \"{rfn}\" \"{maskfn}\" -alpha Off -compose CopyOpacity -composite -channel a -negate +channel {downscale} \"{bmpfn}\"')\n            elif os.path.isfile(altmaskfn_gif_1):\n                if os.popen(f'identify -format \"%[fx:w*2],%[fx:h*2]\" \"{rfn}\"').read() == os.popen(f'identify -format \"%[fx:w*2],%[fx:h*2]\" \"{altmaskfn_gif_1}\"').read():\n                    os.system(f'convert \"{rfn}\" \"{altmaskfn_gif_1}\" -alpha Off -compose CopyOpacity -composite -channel a -negate +channel {downscale} \"{bmpfn}\"')\n                else:\n                    os.system(f'convert {downscale} \"{rfn}\" \"{bmpfn}\"')\n            elif os.path.isfile(altmaskfn_gif):\n                if os.popen(f'identify -format \"%[fx:w*2],%[fx:h*2]\" \"{rfn}\"').read() == os.popen(f'identify -format \"%[fx:w*2],%[fx:h*2]\" \"{altmaskfn_gif}\"').read():\n                    os.system(f'convert \"{rfn}\" \"{altmaskfn_gif}\" -alpha Off -compose CopyOpacity -composite -channel a -negate +channel {downscale} \"{bmpfn}\"')\n                else:\n                    os.system(f'convert {downscale} \"{rfn}\" \"{bmpfn}\"')\n            elif os.path.isfile(altmaskfn_png):\n                if os.popen(f'identify -format \"%[fx:w*2],%[fx:h*2]\" \"{rfn}\"').read() == os.popen(f'identify -format \"%[fx:w*2],%[fx:h*2]\" \"{altmaskfn_png}\"').read():\n                    os.system(f'convert \"{rfn}\" \"{altmaskfn_png}\" -alpha On -compose CopyOpacity -composite {downscale} \"{bmpfn}\"')\n                else:\n                    os.system(f'convert {downscale} \"{rfn}\" \"{bmpfn}\"')\n            else:\n                os.system(f'convert {downscale} \"{rfn}\" \"{bmpfn}\"')\n\n            # handle animated GIFs\n            if not os.path.isfile(bmpfn) and os.path.isfile(bmpfn[:-4]+'-0.bmp'):\n                shutil.move(bmpfn[:-4]+'-0.bmp', bmpfn)\n\n                # get rid of junk frames\n                i = 1\n                while os.path.isfile(bmpfn[:-4]+f'-{i}.bmp'):\n                    os.remove(bmpfn[:-4]+f'-{i}.bmp')\n                    i += 1\n        elif fn.endswith('.db') or fn.endswith('.xcf') or fn.endswith('.odt') or fn.endswith('.pdf'):\n            continue\n        elif fn.endswith('.ogg') and '/sound/' in rfn:\n            # os.system(f'ffmpeg -i \"{rfn}\" \"{destfn}.wav\"')\n            # os.system(f'ffmpeg -i \"{rfn}\" -acodec pcm_u8 -ar 16000 \"{destfn}.wav\"')\n            continue\n        elif is_fonts_dir and fn.endswith('.ini'):\n            shutil.copy(rfn, destfn)\n            os.system(f'sed \\'s/\\\\.png/\\\\.dsg/\\' -i \"{destfn}\"')\n            continue\n        elif fn == 'sounds.ini':\n            # shutil.copy(rfn, destfn)\n            # os.system(f'sed \\'s/\\\\.ogg\"/\\\\.ogg.wav\"/\\' -i \"{destfn}\"')\n            continue\n        elif fn.lower().endswith('.mp3') or fn.lower().endswith('.ogg'):\n            wavfn = destfn + \".wav\"\n            qoafn = destfn + \".qoa\"\n            os.system(f'ffmpeg -i \"{rfn}\" -acodec pcm_s16le -ar 24000 -ac 1 \"{wavfn}\"')\n            os.system(f'qoaconv \"{wavfn}\" \"{qoafn}\"')\n            os.remove(wavfn)\n            continue\n        else:\n            shutil.copy(rfn, destfn)\n            continue\n        w, h, op = os.popen(f'identify -format \"%[fx:w*2],%[fx:h*2],%[opaque]\" \"{bmpfn}\"').read().split(',')\n\n        # downscale further if needed\n        flags = 0\n        i_w = int(w) / 2; i_h = int(h) / 2\n        i_w = max(8, 2 ** math.ceil(math.log2(i_w)))\n        i_h = max(8, 2 ** math.ceil(math.log2(i_h)))\n        while (i_w * i_h) > 65536:\n            print(\"SCALING DOWN. Consider revising the asset.\")\n            flags += 1\n            i_w /= 2\n            i_h /= 2\n        i_w = int(w) >> (flags + 1)\n        i_h = int(h) >> (flags + 1)\n\n        if i_w != int(w) / 2:\n            os.system(f'convert -resize {200 * i_w / int(w)}% \"{bmpfn}\" \"{bmpfn}\"')\n\n        if op.lower().strip() == 'true':\n            flags |= 1 << 4\n\n        open(dsgfn+'.size','w').write(f'{w:>4}\\n{h:>4}\\n{flags}\\n')\n        dsgfns = [dsgfn]\n        bmpfns = [bmpfn]\n        if int(h) > 2048:\n            os.system(f'convert \"{bmpfn}\" -crop {w}x{2048 * i_h / int(h)} \"{bmpfn}%d.bmp\"')\n            os.remove(bmpfn)\n            shutil.move(bmpfn+'0.bmp', bmpfn) #???\n            dsgfns.append(dsgfn+'1')\n            bmpfns.append(bmpfn+'1.bmp')\n            if int(h) > 4096:\n                dsgfns.append(dsgfn+'2')\n                bmpfns.append(bmpfn+'2.bmp')\n        for dsgfn_i, bmpfn_i in zip(dsgfns, bmpfns):\n            h_i = int(h)\n            if h_i > 2048:\n                h_i = 2048\n            h = int(h) - h_i\n\n            cont_w = max(8, 2 ** math.ceil(math.log2(i_w)))\n            cont_h = max(8, 2 ** math.ceil(math.log2(h_i * i_w / int(w))))\n\n            basefn_i = bmpfn_i.replace('.bmp', '')\n            pngfn_i = basefn_i + '.png'\n            os.system(f'convert \"{bmpfn_i}\" \"{pngfn_i}\"')\n            os.system(f'pngquant 16 \"{pngfn_i}\"')\n            os.system(f'grit \"{basefn_i}-fs8.png\" -gb -gB 4 -pe16 -ftb -aw {cont_w} -ah {cont_h} -o \"{basefn_i}-fs8.png\"')\n            pal = open(f'{basefn_i}-fs8.pal.bin', 'rb').read()\n            img = open(f'{basefn_i}-fs8.img.bin', 'rb').read()\n            if len(pal) < 32:\n                pal = pal + bytes([0]) * (32 - len(pal))\n            open(dsgfn_i, 'wb').write(pal + img)\n\n            os.remove(bmpfn_i)\n            os.remove(pngfn_i)\n            os.remove(basefn_i+'-fs8.pal.bin')\n            os.remove(basefn_i+'-fs8.img.bin')\n            os.remove(basefn_i+'-fs8.h')\n\n            if not PREVIEW:\n                os.remove(basefn_i+'-fs8.png')\n\n# construct graphics load lists\nfor dirpath, dirs, files in os.walk(outdir, topdown=True):\n    print(dirpath)\n\n    if dirpath.endswith('graphics'):\n        l = open(os.path.join(dirpath, 'graphics.list'), 'w')\n\n        for d in dirs:\n            if d == 'touchscreen' or d == 'ui' or d == 'fallback':\n                continue\n\n            for f in os.listdir(os.path.join(dirpath, d)):\n                if not f.endswith('.size'):\n                    continue\n\n                abs_f = os.path.join(dirpath, d, f)\n\n                basename = f[:f.find('.')]\n                if len(basename.split('-')) != 2:\n                    continue\n\n                basename = basename.replace('-', ' ').lower()\n\n                if not basename[:basename.find(' ')] in ('background', 'background2', 'block', 'effect', 'level',\n                        'link', 'luigi', 'mario', 'npc', 'path',\n                        'peach', 'player', 'scene', 'tile', 'toad',\n                        'yoshib', 'yoshit'):\n                    continue\n\n                fullname = os.path.join(d, f[:-5])\n                l.write(basename+'\\n')\n                l.write(fullname+'\\n')\n                l.write(open(abs_f, 'r').read())\n                l.write('\\n')\n\n        l.close()\n        continue\n    elif 'graphics' in os.path.split(dirpath):\n        continue\n\n    opened = False\n\n    for f in files:\n        if not f.endswith('.size'):\n            continue\n\n        if not opened:\n            l = open(os.path.join(dirpath, 'graphics.list'), 'w')\n            opened = True\n\n        abs_f = os.path.join(dirpath, f)\n\n        basename = f[:f.find('.')]\n        if len(basename.split('-')) != 2:\n            continue\n\n        basename = basename.replace('-', ' ').lower()\n\n        if not basename[:basename.find(' ')] in ('background', 'background2', 'block', 'effect', 'level',\n                'link', 'luigi', 'mario', 'npc', 'path',\n                'peach', 'player', 'scene', 'tile', 'toad',\n                'yoshib', 'yoshit'):\n            continue\n\n        fullname = f[:-5]\n\n        l.write(basename+'\\n')\n        l.write(fullname+'\\n')\n        l.write(open(abs_f, 'r').read())\n        l.write('\\n')\n\n\n    if opened:\n        l.close()\n\n# construct soundbank\nif REDO or not os.path.isfile(os.path.join(outdir, 'soundbank.bin')):\n    audio_convert_16m.convert_audio(datadir, outdir)\n\nif os.path.isdir(os.path.join(outdir, 'music')):\n    shutil.rmtree(os.path.join(outdir, 'music'))\n\nif os.path.isdir(os.path.join(outdir, 'sound')):\n    shutil.rmtree(os.path.join(outdir, 'sound'))\n"
  },
  {
    "path": "utils/convertkit/gfx-convert-3ds.py",
    "content": "#!/usr/bin/python3\n\n# this is a simple script\n\nimport os\nimport sys\nimport shutil\nimport configparser\n\nPREVIEW = False\nREDO = False\ndatadir = sys.argv[1]\ngraphicsdir = os.path.join(datadir, 'graphics')\noutdir = sys.argv[2]\n\nif not datadir.endswith('/'): datadir += '/'\nif not outdir.endswith('/'): outdir += '/'\n\nos.makedirs(os.path.join(outdir, 'graphics', 'fallback'), exist_ok=True)\n\ndef has_dot(p):\n    if not p:\n        return False\n\n    p, t = os.path.split(p)\n\n    if t.startswith('.'):\n        return True\n\n    return has_dot(p)\n\nfor dirpath, _, files in os.walk(datadir, topdown=True):\n    if has_dot(dirpath[len(datadir):]):\n        continue\n\n    outpath = os.path.join(outdir, dirpath[len(datadir):])\n    os.makedirs(outpath, exist_ok=True)\n\n    if dirpath.endswith('fonts'):\n        print('found fonts dir', dirpath)\n        is_fonts_dir = True\n        texture_1x = set()\n\n        for fn in files:\n            if fn.endswith('.ini'):\n                rfn = os.path.join(dirpath, fn)\n\n                font = configparser.ConfigParser(inline_comment_prefixes=';')\n                font.read(rfn)\n\n                if 'font-map' in font:\n                    if 'texture-scale' in font['font-map'] and font['font-map']['texture-scale'].strip() != '1':\n                        if 'texture' in font['font-map']:\n                            tex_string = font['font-map']['texture'].strip().strip('\\\"')\n                            print(f\"{tex_string} is 1x\")\n                            texture_1x.add(tex_string)\n\n    else:\n        is_fonts_dir = False\n\n    for fn in files:\n        rfn = os.path.join(dirpath, fn)\n        if not os.path.isfile(rfn): continue\n        destfn = os.path.join(outpath, fn)\n        bmpfn = os.path.join(outpath, fn[:-3]+'bmp')\n        t3xfn = os.path.join(outpath, fn[:-3]+'t3x')\n\n        is_1x = is_fonts_dir and fn in texture_1x\n\n        if is_1x:\n            downscale = \"-sample 100%\"\n        else:\n            downscale = \"-sample 50%\"\n\n        if not REDO and not is_fonts_dir and not fn.endswith('m.gif') and (os.path.isfile(destfn) or os.path.isfile(destfn+'.wav') or ((fn.endswith('.gif') or fn.endswith('.png')) and os.path.isfile(t3xfn)) or os.path.isfile(destfn+'.it')):\n            continue\n\n        print(rfn)\n        if fn.endswith('.png'):\n            os.system(f'convert {downscale} \"{rfn}\" \"{bmpfn}\"')\n\n            ftype = fn[:fn.rfind('-')]\n\n            if f'/graphics/{ftype}/' in rfn:\n                dest_maskfn = destfn[:-4] + 'm.gif'\n                dest_maskfn = dest_maskfn.replace(f'/graphics/{ftype}/', '/graphics/fallback/')\n\n                if not os.path.isfile(dest_maskfn):\n                    os.system(f'convert \"{rfn}\" -set colorspace RGB -alpha extract -negate \"{dest_maskfn}\"')\n        elif fn.endswith('m.gif') and os.path.isfile(rfn[:-5]+'.gif'):\n            continue\n        elif fn.endswith('m.gif'):\n            shutil.copy(rfn, destfn)\n            continue\n        elif fn.endswith('.gif'):\n            maskfn = rfn[:-4]+'m.gif'\n            ftype = fn[:fn.rfind('-')]\n            altmaskfn_gif = os.path.join(graphicsdir, 'fallback', fn[:-4]+'m.gif')\n            altmaskfn_gif2 = os.path.join(graphicsdir, ftype, fn[:-4]+'m.gif')\n            altmaskfn_png = os.path.join(graphicsdir, ftype, fn[:-4]+'.png')\n\n            # would be nice to confirm merge safety\n            if os.path.isfile(maskfn):\n                os.system(f'convert \"{rfn}\" \"{maskfn}\" -alpha Off -compose CopyOpacity -composite -channel a -negate +channel {downscale} \"{bmpfn}\"')\n\n                if f'/graphics/{ftype}/' in rfn:\n                    dest_maskfn = destfn[:-4] + 'm.gif'\n                    dest_maskfn = dest_maskfn.replace(f'/graphics/{ftype}/', '/graphics/fallback/')\n\n                    if not os.path.isfile(dest_maskfn):\n                        shutil.copy(maskfn, dest_maskfn)\n            elif os.path.isfile(altmaskfn_gif):\n                os.system(f'convert \"{rfn}\" \"{altmaskfn_gif}\" -alpha Off -compose CopyOpacity -composite -channel a -negate +channel {downscale} \"{bmpfn}\"')\n            elif os.path.isfile(altmaskfn_gif2):\n                os.system(f'convert \"{rfn}\" \"{altmaskfn_gif2}\" -alpha Off -compose CopyOpacity -composite -channel a -negate +channel {downscale} \"{bmpfn}\"')\n            elif os.path.isfile(altmaskfn_png):\n                os.system(f'convert \"{rfn}\" \"{altmaskfn_png}\" -alpha On -compose CopyOpacity -composite {downscale} \"{bmpfn}\"')\n            else:\n                os.system(f'convert {downscale} \"{rfn}\" \"{bmpfn}\"')\n\n            # handle animated GIFs\n            if not os.path.isfile(bmpfn) and os.path.isfile(bmpfn[:-4]+'-0.bmp'):\n                shutil.move(bmpfn[:-4]+'-0.bmp', bmpfn)\n\n                # get rid of junk frames\n                i = 1\n                while os.path.isfile(bmpfn[:-4]+f'-{i}.bmp'):\n                    os.remove(bmpfn[:-4]+f'-{i}.bmp')\n                    i += 1\n        elif fn.endswith('.db'):\n            continue\n        elif fn.endswith('.ogg') and '/sound/' in rfn:\n            os.system(f'ffmpeg -i \"{rfn}\" \"{destfn}.wav\"')\n            continue\n        elif fn.endswith('.spc') and os.path.join(datadir, 'music/') in rfn:\n            shutil.copy(rfn, destfn)\n            os.system(f'spc2it \"{destfn}\"')\n            os.remove(destfn)\n            shutil.move(destfn[:-3] + 'it', destfn + '.it')\n            continue\n        elif fn.endswith('.spc'):\n            # hackish for now\n            shutil.copy(rfn, destfn)\n            os.system(f'spc2it \"{destfn}\"')\n            os.remove(destfn)\n            shutil.move(destfn[:-3] + 'it', destfn)\n            continue\n        elif is_fonts_dir and fn.endswith('.ini'):\n            shutil.copy(rfn, destfn)\n            os.system(f'sed \\'s/\\\\.png/\\\\.t3x/\\' -i \"{destfn}\"')\n            continue\n        elif rfn == os.path.join(datadir, 'sounds.ini'):\n            shutil.copy(rfn, destfn)\n            os.system(f'sed \\'s/\\\\.ogg\"/\\\\.ogg.wav\"/\\' -i \"{destfn}\"')\n            continue\n        elif rfn == os.path.join(datadir, 'music.ini'):\n            shutil.copy(rfn, destfn)\n            os.system(f'sed \\'s/\\\\.spc\"/\\\\.spc.it\"/\\' -i \"{destfn}\"')\n            os.system(f'sed \\'s/\\\\.spc|/\\\\.spc.it|/\\' -i \"{destfn}\"')\n            continue\n        # elif fn.endswith('.mp3'):\n        #     os.system(f'ffmpeg -i \"{rfn}\" -aq 1 \"{destfn}.ogg\"')\n        #     shutil.move(destfn+'.ogg', destfn)\n        #     continue\n        else:\n            shutil.copy(rfn, destfn)\n            continue\n        w, h = os.popen(f'identify -format \"%[fx:w*2],%[fx:h*2]\" \"{bmpfn}\"').read().split(',')\n        open(t3xfn+'.size','w').write(f'{w:>4}\\n{h:>4}\\n')\n        t3xfns = [t3xfn]\n        bmpfns = [bmpfn]\n        if int(h) > 2048:\n            os.system(f'convert \"{bmpfn}\" -crop {w}x1024 \"{bmpfn}%d.bmp\"')\n            os.remove(bmpfn)\n            shutil.move(bmpfn+'0.bmp', bmpfn) #???\n            t3xfns.append(t3xfn+'1')\n            bmpfns.append(bmpfn+'1.bmp')\n            if int(h) > 4096:\n                t3xfns.append(t3xfn+'2')\n                bmpfns.append(bmpfn+'2.bmp')\n        for t3xfn_i, bmpfn_i in zip(t3xfns, bmpfns):\n            if PREVIEW:\n                pvwfn_i = t3xfn_i+'.bmp'\n                os.system(f'tex3ds \"{bmpfn_i}\" -f rgba8888 -o \"{t3xfn_i}\" -p \"{pvwfn_i}\"')\n            else:\n                if os.system(f'tex3ds \"{bmpfn_i}\" -f rgba8888 -o \"{t3xfn_i}\"'):\n                    print(f\"It didn't work and {t3xfn_i} is missing. (Size: {w}x{h})\")\n                os.remove(bmpfn_i)\n\n# construct graphics load lists\nfor dirpath, dirs, files in os.walk(outdir, topdown=True):\n    print(dirpath)\n\n    if dirpath.endswith('graphics'):\n        l = open(os.path.join(dirpath, 'graphics.list'), 'w')\n\n        for d in dirs:\n            if d == 'touchscreen' or d == 'ui' or d == 'fallback':\n                continue\n\n            for f in os.listdir(os.path.join(dirpath, d)):\n                if not f.endswith('.size'):\n                    continue\n\n                abs_f = os.path.join(dirpath, d, f)\n\n                basename = f[:f.find('.')]\n                if len(basename.split('-')) != 2:\n                    continue\n\n                basename = basename.replace('-', ' ').lower()\n\n                if not basename[:basename.find(' ')] in ('background', 'background2', 'block', 'effect', 'level',\n                        'link', 'luigi', 'mario', 'npc', 'path',\n                        'peach', 'player', 'scene', 'tile', 'toad',\n                        'yoshib', 'yoshit'):\n                    continue\n\n                fullname = os.path.join(d, f[:-5])\n                l.write(basename+'\\n')\n                l.write(fullname+'\\n')\n                l.write(open(abs_f, 'r').read())\n                l.write('\\n')\n\n        l.close()\n        continue\n    elif 'graphics' in os.path.split(dirpath):\n        continue\n\n    opened = False\n\n    for f in files:\n        if not f.endswith('.size'):\n            continue\n\n        if not opened:\n            l = open(os.path.join(dirpath, 'graphics.list'), 'w')\n            opened = True\n\n        abs_f = os.path.join(dirpath, f)\n\n        basename = f[:f.find('.')]\n        if len(basename.split('-')) != 2:\n            continue\n\n        basename = basename.replace('-', ' ').lower()\n\n        if not basename[:basename.find(' ')] in ('background', 'background2', 'block', 'effect', 'level',\n                'link', 'luigi', 'mario', 'npc', 'path',\n                'peach', 'player', 'scene', 'tile', 'toad',\n                'yoshib', 'yoshit'):\n            continue\n\n        fullname = f[:-5]\n\n        l.write(basename+'\\n')\n        l.write(fullname+'\\n')\n        l.write(open(abs_f, 'r').read())\n        l.write('\\n')\n\n\n    if opened:\n        l.close()\n"
  },
  {
    "path": "utils/convertkit/gfx-convert-wii.py",
    "content": "#!/usr/bin/python3\n\n# this is a simple script\n\nimport os\nimport sys\nimport shutil\nimport configparser\n\nPREVIEW = False\nREDO = False\ndatadir = sys.argv[1]\ngraphicsdir = os.path.join(datadir, 'graphics')\noutdir = sys.argv[2]\n\nif not datadir.endswith('/'): datadir += '/'\nif not outdir.endswith('/'): outdir += '/'\n\nos.makedirs(os.path.join(outdir, 'graphics', 'fallback'), exist_ok=True)\n\ndef has_dot(p):\n    if not p:\n        return False\n\n    p, t = os.path.split(p)\n\n    if t.startswith('.'):\n        return True\n\n    return has_dot(p)\n\nfor dirpath, _, files in os.walk(datadir, topdown=True):\n    if has_dot(dirpath[len(datadir):]):\n        continue\n\n    outpath = os.path.join(outdir, dirpath[len(datadir):])\n    os.makedirs(outpath, exist_ok=True)\n\n    if dirpath.endswith('fonts'):\n        print('found fonts dir')\n        is_fonts_dir = True\n        texture_1x = set()\n\n        for fn in files:\n            if fn.endswith('.ini'):\n                rfn = os.path.join(dirpath, fn)\n\n                font = configparser.ConfigParser(inline_comment_prefixes=';')\n                font.read(rfn)\n\n                if 'font-map' in font:\n                    if 'texture-scale' in font['font-map'] and font['font-map']['texture-scale'].strip() != '1':\n                        if 'texture' in font['font-map']:\n                            tex_string = font['font-map']['texture'].strip().strip('\\\"')\n                            print(f\"{tex_string} is 1x\")\n                            texture_1x.add(tex_string)\n\n    else:\n        is_fonts_dir = False\n\n    for fn in files:\n        rfn = os.path.join(dirpath, fn)\n        if not os.path.isfile(rfn): continue\n        destfn = os.path.join(outpath, fn)\n        bmpfn = os.path.join(outpath, fn[:-3]+'bmp')\n        tplfn = os.path.join(outpath, fn[:-3]+'tpl')\n\n        is_1x = is_fonts_dir and fn in texture_1x\n\n        if is_1x:\n            downscale = \"-sample 100%\"\n        else:\n            downscale = \"-sample 50%\"\n\n        if not REDO and not is_fonts_dir and not fn.endswith('m.gif') and (os.path.isfile(destfn) or os.path.isfile(destfn+'.wav') or ((fn.endswith('.gif') or fn.endswith('.png')) and os.path.isfile(tplfn))): continue\n\n        print(rfn)\n        if fn.endswith('.png'):\n            os.system(f'convert {downscale} \"{rfn}\" \"{bmpfn}\"')\n\n            ftype = fn[:fn.rfind('-')]\n\n            if f'/graphics/{ftype}/' in rfn:\n                dest_maskfn = destfn[:-4] + 'm.gif'\n                dest_maskfn = dest_maskfn.replace(f'/graphics/{ftype}/', '/graphics/fallback/')\n\n                if not os.path.isfile(dest_maskfn):\n                    os.system(f'convert \"{rfn}\" -set colorspace RGB -alpha extract -negate \"{dest_maskfn}\"')\n        elif fn.endswith('m.gif') and os.path.isfile(rfn[:-5]+'.gif'):\n            continue\n        elif fn.endswith('m.gif'):\n            shutil.copy(rfn, destfn)\n            continue\n        elif fn.endswith('.gif'):\n            maskfn = rfn[:-4]+'m.gif'\n            ftype = fn[:fn.rfind('-')]\n            altmaskfn_gif = os.path.join(graphicsdir, 'fallback', fn[:-4]+'m.gif')\n            altmaskfn_gif2 = os.path.join(graphicsdir, ftype, fn[:-4]+'m.gif')\n            altmaskfn_png = os.path.join(graphicsdir, ftype, fn[:-4]+'.png')\n\n            # would be nice to confirm merge safety\n            if os.path.isfile(maskfn):\n                os.system(f'convert \"{rfn}\" \"{maskfn}\" -alpha Off -compose CopyOpacity -composite -channel a -negate +channel {downscale} \"{bmpfn}\"')\n\n                if f'/graphics/{ftype}/' in rfn:\n                    dest_maskfn = destfn[:-4] + 'm.gif'\n                    dest_maskfn = dest_maskfn.replace(f'/graphics/{ftype}/', '/graphics/fallback/')\n\n                    if not os.path.isfile(dest_maskfn):\n                        shutil.copy(maskfn, dest_maskfn)\n            elif os.path.isfile(altmaskfn_gif):\n                os.system(f'convert \"{rfn}\" \"{altmaskfn_gif}\" -alpha Off -compose CopyOpacity -composite -channel a -negate +channel {downscale} \"{bmpfn}\"')\n            elif os.path.isfile(altmaskfn_gif2):\n                os.system(f'convert \"{rfn}\" \"{altmaskfn_gif2}\" -alpha Off -compose CopyOpacity -composite -channel a -negate +channel {downscale} \"{bmpfn}\"')\n            elif os.path.isfile(altmaskfn_png):\n                os.system(f'convert \"{rfn}\" \"{altmaskfn_png}\" -alpha On -compose CopyOpacity -composite {downscale} \"{bmpfn}\"')\n            else:\n                os.system(f'convert {downscale} \"{rfn}\" \"{bmpfn}\"')\n\n            # handle animated GIFs\n            if not os.path.isfile(bmpfn) and os.path.isfile(bmpfn[:-4]+'-0.bmp'):\n                shutil.move(bmpfn[:-4]+'-0.bmp', bmpfn)\n\n                # get rid of junk frames\n                i = 1\n                while os.path.isfile(bmpfn[:-4]+f'-{i}.bmp'):\n                    os.remove(bmpfn[:-4]+f'-{i}.bmp')\n                    i += 1\n        elif fn.endswith('.db'):\n            continue\n        elif fn.endswith('.ogg') and '/sound/' in rfn:\n            os.system(f'ffmpeg -i \"{rfn}\" \"{destfn}.wav\"')\n            continue\n        elif is_fonts_dir and fn.endswith('.ini'):\n            shutil.copy(rfn, destfn)\n            os.system(f'sed \\'s/\\\\.png/\\\\.tpl/\\' -i \"{destfn}\"')\n            continue\n        elif fn == 'sounds.ini':\n            shutil.copy(rfn, destfn)\n            os.system(f'sed \\'s/\\\\.ogg\"/\\\\.ogg.wav\"/\\' -i \"{destfn}\"')\n            continue\n        else:\n            shutil.copy(rfn, destfn)\n            continue\n        w, h = os.popen(f'identify -format \"%[fx:w*2],%[fx:h*2]\" \"{bmpfn}\"').read().split(',')\n        open(tplfn+'.size','w').write(f'{w:>4}\\n{h:>4}\\n')\n        if int(w) & 6 or int(h) & 6:\n            w_ = int(w) // 2\n            h_ = int(h) // 2\n            print(\"is\", w_, h_)\n            if w_ & 3: w_ += 4 - w_ & 3\n            if h_ & 3: h_ += 4 - h_ & 3\n            print(\"> extending to\", w_, h_)\n            os.system(f'convert \"{bmpfn}\" -gravity NorthWest -background none -extent {w_}x{h_} \"{bmpfn}e.bmp\"')\n            os.system(f'mv \"{bmpfn}e.bmp\" \"{bmpfn}\"')\n        tplfns = [tplfn]\n        bmpfns = [bmpfn]\n        if int(h) > 2048:\n            os.system(f'convert \"{bmpfn}\" -crop {w}x1024 \"{bmpfn}%d.bmp\"')\n            os.remove(bmpfn)\n            shutil.move(bmpfn+'0.bmp', bmpfn) #???\n            tplfns.append(tplfn+'1')\n            bmpfns.append(bmpfn+'1.bmp')\n            if int(h) > 4096:\n                tplfns.append(tplfn+'2')\n                bmpfns.append(bmpfn+'2.bmp')\n        for tplfn_i, bmpfn_i in zip(tplfns, bmpfns):\n            colors = int(os.popen(f'identify -format %k \"{bmpfn_i}\"').read())\n\n            colfmt = 6\n\n            # PREVIEW = True\n\n            if PREVIEW:\n                os.system(f'gxtexconv -i \"{bmpfn_i}\" -o \"{tplfn_i}\" colfmt={colfmt} mipmap=no')\n                os.remove(tplfn.replace('.tpl', '.h'))\n            else:\n                if os.system(f'gxtexconv -i \"{bmpfn_i}\" -o \"{tplfn_i}\" colfmt={colfmt} mipmap=no'):\n                    print(f\"It didn't work and {tplfn_i} is missing. (Size: {w}x{h})\")\n                else:\n                    os.remove(tplfn.replace('.tpl', '.h'))\n                os.remove(bmpfn_i)\n\n# construct graphics load lists\nfor dirpath, dirs, files in os.walk(outdir, topdown=True):\n    print(dirpath)\n\n    if dirpath.endswith('graphics'):\n        l = open(os.path.join(dirpath, 'graphics.list'), 'w')\n\n        for d in dirs:\n            if d == 'touchscreen' or d == 'ui' or d == 'fallback':\n                continue\n\n            for f in os.listdir(os.path.join(dirpath, d)):\n                if not f.endswith('.size'):\n                    continue\n\n                abs_f = os.path.join(dirpath, d, f)\n\n                basename = f[:f.find('.')]\n                if len(basename.split('-')) != 2:\n                    continue\n\n                basename = basename.replace('-', ' ').lower()\n\n                if not basename[:basename.find(' ')] in ('background', 'background2', 'block', 'effect', 'level',\n                        'link', 'luigi', 'mario', 'npc', 'path',\n                        'peach', 'player', 'scene', 'tile', 'toad',\n                        'yoshib', 'yoshit'):\n                    continue\n\n                fullname = os.path.join(d, f[:-5])\n                l.write(basename+'\\n')\n                l.write(fullname+'\\n')\n                l.write(open(abs_f, 'r').read())\n                l.write('\\n')\n\n                os.remove(abs_f)\n\n        l.close()\n        continue\n    elif 'graphics' in os.path.split(dirpath):\n        continue\n\n    opened = False\n\n    for f in files:\n        if not f.endswith('.size'):\n            continue\n\n        abs_f = os.path.join(dirpath, f)\n\n        basename = f[:f.find('.')]\n        if len(basename.split('-')) != 2:\n            continue\n\n        basename = basename.replace('-', ' ').lower()\n\n        if not basename[:basename.find(' ')] in ('background', 'background2', 'block', 'effect', 'level',\n                'link', 'luigi', 'mario', 'npc', 'path',\n                'peach', 'player', 'scene', 'tile', 'toad',\n                'yoshib', 'yoshit'):\n            continue\n\n        if not opened:\n            l = open(os.path.join(dirpath, 'graphics.list'), 'w')\n            opened = True\n\n        fullname = f[:-5]\n\n        l.write(basename+'\\n')\n        l.write(fullname+'\\n')\n        l.write(open(abs_f, 'r').read())\n        l.write('\\n')\n\n        os.remove(abs_f)\n\n\n    if opened:\n        l.close()\n"
  },
  {
    "path": "utils/submodule-update.sh",
    "content": "#!/bin/bash\n\nprojroot=$(dirname \"$1\")\nprojroot_len=$((${#projroot}+1))\ncur_dir=$(pwd)\nrepo_path=${cur_dir:$projroot_len}\n\necho \"-----------------------------------\"\n#echo \"Debug: projroot=$projroot\"\n#echo \"Debug: projroot_len=$projroot_len\"\n#echo \"Debug: cur_dir=$cur_dir\"\n#echo \"Debug: repo_path=$repo_path\"\n\nSED_EXEC=sed\n\nif [[ \"$OSTYPE\" == \"darwin\"* || \"$OSTYPE\" == \"freebsd\"* ]]; then\n    SED_EXEC=gsed\nfi\n\ndstbranch=\"\"\nfound=false\n\nwhile IFS= read -r line; do\n    line_clear=$(echo \"$line\" | $SED_EXEC 's/^[ \\t]*//g')\n\n    if $found ; then\n        if [[ \"$line_clear\" == \"branch = \"* ]]; then\n            dstbranch=$(echo \"$line_clear\" | $SED_EXEC 's/^branch = *//g')\n            echo \"Found branch name: $dstbranch\"\n            break\n        fi\n    elif [[ \"$line_clear\" == \"path = $repo_path\" ]]; then\n        found=true\n    fi\ndone < \"$1\"\n\nif [[ \"$dstbranch\" == \"\" ]]; then\n    echo \"Failed to detect brach at the repo $reponame ($repo_path)\"\n    exit 1\nfi\n\nreponame=$(basename `git rev-parse --show-toplevel`)\necho \"Updating repo $reponame (path=$repo_path), branch $dstbranch\"\n\ngit checkout $dstbranch\ngit pull origin $dstbranch\n\nif [[ -f .gitmodules ]]; then\n    echo \"=========================================\"\n    echo \"Running recursive submodule sync...\"\n    echo \"=========================================\"\n    git submodule init\n    git submodule update\n    echo \"\"\n    git submodule foreach submodule-update.sh \"$PWD/.gitmodules\"\n    echo \"=========================================\"\n    echo \"Exiting recursive scan of the $reponame's submodules!\"\n    echo \"=========================================\"\nfi\n"
  },
  {
    "path": "utils/update-copyright.sh",
    "content": "#!/bin/bash\n\nfind . -type f -exec grep -Il \"Copyright\" {} \\;     \\\n| grep -v \\.git \\\n| while read file;                            \\\ndo \\\n  LC_ALL=C sed -b -i \"s/\\(.*Copyright.*\\)[0-9]\\{4\\}\\( *Vitali\\?y Novichkov\\)/\\1`date +%Y`\\2/\" \"$file\"; \\\ndone\n\nif [[ -f \"pge_version.h\" ]]; then\n    LC_ALL=C sed -b -i \"s/\\(.*V_COPYRIGHT.*\\)[0-9]\\{4\\}\\( *by Wohlstand\\)/\\1`date +%Y`\\2/\" \"pge_version.h\";\nfi\n"
  },
  {
    "path": "version.cmake",
    "content": "# Major\nset(THEXTECH_VERSION_1 1)\n# Minor\nset(THEXTECH_VERSION_2 3)\n# Revision\nset(THEXTECH_VERSION_3 8)\n# Patch\nset(THEXTECH_VERSION_4 0)\n# Type of version: \"-alpha\",\"-beta\",\"-dev\", or \"\" aka \"release\"\nset(THEXTECH_VERSION_REL \"-dev\")\n\n# Static version values for F-Droid to parse (PLEASE KEEP IT IN SYNC WITH VERSION NUMBER PARTS FROM ABOVE)\nset(THEXTECH_ANDROID_VERSION_NAME \"1.3.8-dev\")\nset(THEXTECH_ANDROID_VERSION_CODE \"1030800\")\n\n# Defining global macros\nadd_definitions(-DTHEXTECH_VERSION_1=${THEXTECH_VERSION_1})\nadd_definitions(-DTHEXTECH_VERSION_2=${THEXTECH_VERSION_2})\nadd_definitions(-DTHEXTECH_VERSION_3=${THEXTECH_VERSION_3})\nadd_definitions(-DTHEXTECH_VERSION_4=${THEXTECH_VERSION_4})\nadd_definitions(-DTHEXTECH_VERSION_REL=${THEXTECH_VERSION_REL})\n\n# Buildin the version name\nset(THEXTECH_VERSION_STRING \"${THEXTECH_VERSION_1}.${THEXTECH_VERSION_2}\")\n\nif(NOT ${THEXTECH_VERSION_3} EQUAL 0 OR NOT ${THEXTECH_VERSION_4} EQUAL 0)\n    string(CONCAT THEXTECH_VERSION_STRING \"${THEXTECH_VERSION_STRING}\" \".${THEXTECH_VERSION_3}\")\nendif()\n\nif(NOT ${THEXTECH_VERSION_4} EQUAL 0)\n    string(CONCAT THEXTECH_VERSION_STRING \"${THEXTECH_VERSION_STRING}\" \".${THEXTECH_VERSION_4}\")\nendif()\n\nif(NOT \"${THEXTECH_VERSION_REL}\" STREQUAL \"\")\n    string(CONCAT THEXTECH_VERSION_STRING \"${THEXTECH_VERSION_STRING}\" \"${THEXTECH_VERSION_REL}\")\nendif()\n\n# Haiku-specific version name\nif(HAIKU)\n    set(THEXTECH_HAIKU_VERSION_STRING \"${THEXTECH_VERSION_1}.${THEXTECH_VERSION_2}\")\n\n    if(NOT ${THEXTECH_VERSION_3} EQUAL 0 OR NOT ${THEXTECH_VERSION_4} EQUAL 0)\n        string(CONCAT THEXTECH_HAIKU_VERSION_STRING \"${THEXTECH_HAIKU_VERSION_STRING}\" \".${THEXTECH_VERSION_3}\")\n    endif()\n\n    if(NOT ${THEXTECH_VERSION_4} EQUAL 0)\n        string(CONCAT THEXTECH_HAIKU_VERSION_STRING \"${THEXTECH_HAIKU_VERSION_STRING}\" \".${THEXTECH_VERSION_4}\")\n    endif()\n\n    if(NOT \"${THEXTECH_VERSION_REL}\" STREQUAL \"\")\n        string(SUBSTRING \"${THEXTECH_VERSION_REL}\" 1 10 THEXTECH_HAIKU_VERSION_REL)\n        string(CONCAT THEXTECH_HAIKU_VERSION_STRING \"${THEXTECH_HAIKU_VERSION_STRING}\" \"~${THEXTECH_HAIKU_VERSION_REL}\")\n    endif()\n\n    string(CONCAT THEXTECH_HAIKU_VERSION_STRING \"${THEXTECH_HAIKU_VERSION_STRING}\" \"-1\")\nendif()\n\n# Building the version code (for Android)\nmath(EXPR THEXTECH_VERSION_CODE \"1000000 * ${THEXTECH_VERSION_1} + 10000 * ${THEXTECH_VERSION_2} + 100 * ${THEXTECH_VERSION_3} + ${THEXTECH_VERSION_4}\")\n\nmessage(\"== TheXTech version ${THEXTECH_VERSION_STRING} (${THEXTECH_VERSION_CODE}) ==\")\n\n# A reminder that will compare dynamically computed values with a static value, and will fail a configure if mismatch:\n# ==================================================\n\nif(NOT \"${THEXTECH_ANDROID_VERSION_NAME}\" STREQUAL \"${THEXTECH_VERSION_STRING}\")\n    message(FATAL_ERROR \"Android version name value doesn't matching to the primary version name\\n(expected ${THEXTECH_VERSION_STRING}, ${THEXTECH_ANDROID_VERSION_NAME} actually)\")\nendif()\n\nif(NOT \"${THEXTECH_ANDROID_VERSION_CODE}\" STREQUAL \"${THEXTECH_VERSION_CODE}\")\n    message(FATAL_ERROR \"Android version code doesn't matching to the primary version code\\n(expected ${THEXTECH_VERSION_CODE}, ${THEXTECH_ANDROID_VERSION_CODE} actually)\")\nendif()\n# ==================================================\n"
  },
  {
    "path": "version.h",
    "content": "/*\n * TheXTech - A platform game engine ported from old source code for VB6\n *\n * Copyright (c) 2009-2011 Andrew Spinks, original VB6 code\n * Copyright (c) 2020-2026 Vitaly Novichkov <admin@wohlnet.ru>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"pge_version.h\" //Global Project version file\n#include \"git_version.h\" // generated git version info\n\n#ifndef THEXTECH_VERSION_H\n#define THEXTECH_VERSION_H\n\n#ifdef GIT_VERSION\n#define V_BUILD_VER GIT_VERSION\n#else\n#define V_BUILD_VER \"<empty>\"\n#endif\n\n#ifdef GIT_BRANCH\n#define V_BUILD_BRANCH GIT_BRANCH\n#else\n#define V_BUILD_BRANCH \"<unknown>\"\n#endif\n\n#ifdef THEXTECH_VERSION_1\n#   define V_VF1 THEXTECH_VERSION_1\n#else\n#   define V_VF1 0\n#endif\n\n#ifdef THEXTECH_VERSION_2\n#   define V_VF2 THEXTECH_VERSION_2\n#else\n#   define V_VF2 0\n#endif\n\n#ifdef THEXTECH_VERSION_3\n#   define V_VF3 THEXTECH_VERSION_3\n#else\n#   define V_VF3 0\n#endif\n\n#ifdef THEXTECH_VERSION_4\n#   define V_VF4 THEXTECH_VERSION_4\n#else\n#   define V_VF4 0\n#endif\n\n#define V_FEATURE_LEVEL (((V_VF1 * 100 + V_VF2) * 100 + V_VF3) * 100 + V_VF4)\n\n#ifdef THEXTECH_VERSION_REL\n#   define V_FILE_RELEASE STR_VALUE(THEXTECH_VERSION_REL)\n#else\n#   define V_FILE_RELEASE \"-unk\" //\"-alpha\",\"-beta\",\"-dev\", or \"\" aka \"release\"\n#endif\n\n#define V_VF1_s STR_VALUE(V_VF1)\n#define V_VF2_s STR_VALUE(V_VF2)\n#define V_VF3_s STR_VALUE(V_VF3)\n#define V_VF4_s STR_VALUE(V_VF4)\n#if V_VF4 == 0\n    #if V_VF3 == 0\n        #define V_FILE_VERSION_NUM GEN_VERSION_NUMBER_2(V_VF1_s, V_VF2_s)\n    #else\n        #define V_FILE_VERSION_NUM GEN_VERSION_NUMBER_3(V_VF1_s, V_VF2_s, V_VF3_s)\n    #endif\n#else\n    #define V_FILE_VERSION_NUM GEN_VERSION_NUMBER_4(V_VF1_s, V_VF2_s, V_VF3_s, V_VF4_s)\n#endif\n\n#define V_FILE_VERSION V_FILE_VERSION_NUM\n//Version of this program\n#define V_LATEST_STABLE V_FILE_VERSION_NUM V_FILE_RELEASE\n\n#define V_FILE_DESC \"TheXTech - the modern C++ port and successor of the SMBX engine\"\n\n#define V_INTERNAL_NAME \"thextech\"\n\n#ifdef _WIN32\n    #define V_ORIGINAL_NAME \"thextech.exe\" // for Windows platforms\n#else\n    #define V_ORIGINAL_NAME \"thextech\" // for any other platforms\n#endif\n\n//Uncomment this for enable detal logging\n//#define DEBUG\n\n#endif\n"
  }
]